app
Fusion - the Fusion 360 Bridge
Public Made by Adomby adom
Drive Autodesk Fusion 360 from the cloud via Adom Bridge: component libraries, IPC package generation, board layout, exports (STEP/Gerbers/BOM/CPL), fast APS cloud search, and parametric modeling.
← Commit history
Update 15 file(s)
11 files changed
+78−22
BRIDGE_VERSION+1−1@@ -1 +1 @@-1.9.183+1.9.184
adom-bridge-fusion-manifest.json+4−4@@ -1,10 +1,10 @@ { "manifest_version": 1, "name": "fusion360",- "version": "1.9.183",- "url": "https://wiki.adom.inc/download/adom/fusion-bridge/1.9.183/adom-bridge-fusion-v1.9.183.zip",- "sha256": "f464843ccc7817d407503303263b0cde1e0026be86ea83e5c50df0f06931ea7b",- "size": 486500,+ "version": "1.9.184",+ "url": "https://wiki.adom.inc/download/adom/fusion-bridge/1.9.184/adom-bridge-fusion-v1.9.184.zip",+ "sha256": "0f4e13c1da23f6da0e355b5506cf326271709ebe27e163f88a67eb74edd64c0b",+ "size": 487334, "verbPrefixes": [ "fusion_" ],
bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "fusion360", "displayName": "Autodesk Fusion 360",- "version": "1.9.183",+ "version": "1.9.184", "description": "Drive Autodesk Fusion 360 from the cloud: launch Fusion, electronics board layout, design rules, exports (STEP/IGES/STL/3MF/USDZ/OBJ/DXF/DWG/Gerbers/BOM/CPL), fast APS server-indexed cloud search plus browse/recent/file-info/versions, cloud file download/upload and folder creation, and in-app parametric modeling (fusion_run_modeling_script). Never-charge: APS calls are capped to the free tier.", "homepage": "https://wiki.adom.inc/adom/fusion-bridge", "author": "Adom Inc.",
dashboard/Cargo.lock+1−1@@ -4,7 +4,7 @@ version = 4 [[package]] name = "fusion-dashboard"-version = "1.9.183"+version = "1.9.184" dependencies = [ "serde_json", ]
dashboard/Cargo.toml+1−1@@ -1,6 +1,6 @@ [package] name = "fusion-dashboard"-version = "1.9.183"+version = "1.9.184" edition = "2021" [dependencies]
dashboard/src/main.rs+9@@ -1019,6 +1019,15 @@ fn handle_conn(app: &Arc<App>, mut stream: TcpStream) { if path != "/events" { *app.last_activity.lock().unwrap() = Instant::now(); }+ // CORS preflight (Dock Bar contract, project-a6 2026-08-23): every /api/* route answers+ // OPTIONS with 204 + the method/header allow-list, so the dock shell (whose origin varies+ // between dev and release) and the dashboards' own cross-origin fetches can POST JSON.+ // Allow-Origin: * rides every response already (respond() + the SSE head).+ if method == "OPTIONS" {+ let head = "HTTP/1.1 204 No Content\r\nAccess-Control-Allow-Origin: *\r\nAccess-Control-Allow-Methods: GET, POST, OPTIONS\r\nAccess-Control-Allow-Headers: Content-Type\r\nAccess-Control-Max-Age: 600\r\nContent-Length: 0\r\nConnection: close\r\n\r\n";+ let _ = stream.write_all(head.as_bytes());+ return;+ } match (method.as_str(), path.as_str()) { ("GET", "/") | ("GET", "/index.html") => { respond(&mut stream, "200 OK", "text/html; charset=utf-8", UI_HTML.as_bytes());
handlers/dismiss_recovery.py+40−3@@ -163,6 +163,7 @@ def _find_fusion_dialog_windows() -> list: 'Autodesk Fusion' in its title). """ results = []+ _matched, _skipped_big = [], [] WNDENUMPROC = ctypes.WINFUNCTYPE( ctypes.c_bool, ctypes.wintypes.HWND, ctypes.wintypes.LPARAM )@@ -184,16 +185,41 @@ def _find_fusion_dialog_windows() -> list: # Look for Qt dialog windows titled "Fusion360" (not the main window) if "Qt" in class_name and title == "Fusion360":- results.append(hwnd)+ # SIZE GATE (#50, 2026-08-23): the recovery LIST is a small dialog. A+ # window this title+class but main-window sized is Fusion's product frame+ # (seen with zero documents open, i.e. right after a chain closes them) -+ # WM_CLOSE to that is a clean, artifact-free Fusion EXIT. Never close it.+ r = ctypes.wintypes.RECT()+ user32.GetWindowRect(hwnd, ctypes.byref(r))+ w, h = r.right - r.left, r.bottom - r.top+ if w >= 700 or h >= 500:+ _skipped_big.append({"hwnd": hwnd, "cls": class_name, "w": w, "h": h})+ else:+ results.append(hwnd)+ _matched.append({"hwnd": hwnd, "cls": class_name, "w": w, "h": h}) return True cb = WNDENUMPROC(callback) _callbacks.append(cb) user32.EnumWindows(cb, 0) _callbacks.remove(cb)+ LAST_SCAN["matched"] = _matched+ LAST_SCAN["skippedBig"] = _skipped_big return results +LAST_SCAN = {"matched": [], "skippedBig": []} # what the last "Fusion360" scan saw+LEDGER = None # server.py installs its _bridge_action_ledger here so closes are recorded+++def _ledger(action, detail):+ try:+ if LEDGER:+ LEDGER(action, detail)+ except Exception:+ pass++ def _find_child_button(parent_hwnd: int, button_text: str) -> int: """Find a child button control with the given text.""" result = [0]@@ -248,7 +274,7 @@ def _dismiss_recovery_prompt(hwnd: int) -> bool: return True -def dismiss_recovery_dialog(max_wait: float = 5.0) -> bool:+def dismiss_recovery_dialog(max_wait: float = 5.0, background: bool = False) -> bool: """Find and dismiss all Fusion 360 recovery dialogs. Before dismissing, relocates recovery files from Fusion's CrashRecovery@@ -290,9 +316,12 @@ def dismiss_recovery_dialog(max_wait: float = 5.0) -> bool: # Priority 2: "Fusion360" titled Qt dialogs (Recovered Documents list) dialog_hwnds = _find_fusion_dialog_windows()+ if LAST_SCAN.get("skippedBig"):+ _ledger("recovery_scan_skipped_main_sized", {"windows": LAST_SCAN["skippedBig"]}) if dialog_hwnds: for hwnd in dialog_hwnds: user32.PostMessageW(hwnd, WM_CLOSE, 0, 0)+ _ledger("recovery_wm_close", {"windows": LAST_SCAN.get("matched")}) dismissed_any = True time.sleep(0.5) continue # Check for more@@ -319,10 +348,18 @@ def dismiss_recovery_dialog(max_wait: float = 5.0) -> bool: break continue - # Priority 4: Send Escape to the main Fusion window (last resort)+ # Priority 4: Send Escape to the main Fusion window (last resort).+ # ⛔ LAUNCH-TIME ONLY. This used to fire from the janitor's 300s sweep on EVERY+ # live Fusion when nothing matched - an Escape keystroke into a working Fusion+ # every five minutes, cancelling whatever command was running (lost evidence+ # frames, 'f3d saveAs did not produce a path', #50). In background mode there is+ # no fallback: if no recovery dialog is found, nothing is touched.+ if background:+ break fusion_hwnd = _find_window_by_title("Autodesk Fusion") if fusion_hwnd: _send_key(fusion_hwnd, VK_ESCAPE)+ _ledger("recovery_escape_fallback", {"hwnd": fusion_hwnd}) dismissed_any = True time.sleep(1.0) continue
install.sh+1−1@@ -21,7 +21,7 @@ echo "Installed fusion-bridge skills: $(ls "$PKG_DIR/skills" 2>/dev/null | tr '\ # binary (the three-artifact pattern: bridge zip -> ab, THIS pkg -> container skills + # this download, page media -> wiki). Per the wiki's PKG_VS_RELEASE policy, binaries are # platform-tagged RELEASE assets, never tarball content - the installer fetches them.-DASH_VERSION="1.9.183"+DASH_VERSION="1.9.184" DASH_URL="https://wiki.adom.inc/download/adom/fusion-bridge/${DASH_VERSION}/fusion-dashboard" DEST="" if [ -w /usr/local/bin ]; then DEST=/usr/local/bin
package.json+1−1@@ -1,6 +1,6 @@ { "slug": "fusion-bridge",- "version": "1.9.183",+ "version": "1.9.184", "type": "app", "description": "This package installs the bridge's SKILLS into your container so your AI knows how to drive it; Adom Bridge loads the bridge runtime itself from the release zip.", "dependencies": {
page.json+11−8@@ -1,6 +1,6 @@ { "slug": "fusion-bridge",- "version": "1.9.183",+ "version": "1.9.184", "type": "app", "description": "This package installs the bridge's SKILLS into your container so your AI knows how to drive it; Adom Bridge loads the bridge runtime itself from the release zip.", "dependencies": {@@ -11,6 +11,10 @@ "uninstall": "./uninstall.sh" }, "tags": [+ "ab",+ "bridge",+ "fusion",+ "desktop-app", "fusion360", "autodesk", "cad",@@ -75,7 +79,10 @@ "build a basic parts library", "add a 3d model to a component", "make a fusion 360 library with 3d",- "set up the fusion bridge"+ "set up the fusion bridge",+ "fusion dashboard",+ "fusion status page",+ "open the fusion launch page" ], "discovery_pitch": "Use the Adom Bridge Fusion 360 bridge when the user wants to DRIVE Autodesk Fusion 360 from the cloud: open/search their team's Fusion cloud designs, lay out a PCB or do electronics, export STEP/IGES/STL/Gerbers/BOM, or build a component library with real rendering 3D. It runs Fusion on the user's own machine via Adom Bridge. NOT for web/browser/login/form-filling (that's the native-browser extension) and NOT for KiCad (that's the kicad bridge).", "brief": "Drive Autodesk Fusion 360 from the cloud via Adom Bridge: component libraries, IPC package generation, board layout, exports (STEP/Gerbers/BOM/CPL), fast APS cloud search, and parametric modeling.",@@ -104,9 +111,5 @@ "label": "Part volume", "prompt": "How much material does each printed part in this assembly use?" }- ],- "author": {- "name": "John Lauer",- "email": "[email protected]"- }-}+ ]+}
server.py+8−1@@ -5092,6 +5092,13 @@ def _bridge_action_ledger(action: str, detail: dict): pass +try:+ import handlers.dismiss_recovery as _dr_mod+ _dr_mod.LEDGER = _bridge_action_ledger # every recovery WM_CLOSE / Escape is recorded+except Exception:+ pass++ def _fusion_verifiably_idle() -> dict: """May the bridge do something that EXITS Fusion right now? Only when ALL of: no registered long op, no driving verb for 3+ minutes, and no open documents@@ -6573,7 +6580,7 @@ def _signin_janitor_loop(): # so it is ALWAYS safe to close in the background. (John, 2026-08-16: # "anytime it comes up, just ignore it and close it".) try:- dismiss_recovery_dialog()+ dismiss_recovery_dialog(background=True) except Exception: pass except Exception: