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
v1.6.31: readiness updating:true (auto-update detect) + pref verbs retry-once (fresh-VM battery hardening)
6 files changed
+90−11
BRIDGE_VERSION+1−1@@ -1 +1 @@-1.6.30+1.6.31
CHANGELOG.md+14@@ -3,6 +3,20 @@ All notable changes to the Adom Desktop Fusion 360 bridge, newest first. Canonical: https://wiki.adom.inc/adom/adom-desktop-fusion-bridge ยท GitHub backup: `adom-inc/adom-desktop-fusion-bridge` +## 1.6.31 - 2026-07-05++- **`fusion_readiness` now reports a distinct `updating:true` status when Fusion is applying an+ auto-update.** New `fusion_update_in_progress()` detects the tell-tale TWO+ webdeploy `production`+ builds coexisting; while that applies, Fusion crash-restarts itself every ~30-60s, so verbs+ intermittently saw a bare "not running" that read as a crash. Readiness now flags `updating:true`+ with a hint to poll/retry (it stabilizes once the update finishes) rather than treat the flap as+ fatal. Both hardening items came straight out of the fresh-VM battery.+- **Preference verbs (`fusion_set_preference`/`fusion_get_preferences`) retry once on a transient+ add-in connection error.** The first call can hit the add-in mid doc-transition (e.g. right after+ opening a Library, when the pref script must create a Design) and come back "not running" though+ Fusion is up; a ~1.5s retry clears it (seen live on the battery - failed first call, passed on+ retry). A genuinely-down Fusion just costs one extra 1.5s attempt.+ ## 1.6.30 - 2026-07-05 - **`fusion_install_fusion` and `fusion_start` now live-detect too** (completing the 1.6.29 fix). Both
adom-bridge-fusion-manifest.json+5−5@@ -1,13 +1,13 @@ { "manifest_version": 1, "name": "fusion360",- "version": "1.6.30",- "url": "https://wiki.adom.inc/download/adom/adom-desktop-fusion-bridge/1.6.30/adom-bridge-fusion-v1.6.30.zip",- "sha256": "18af4245248fb8803c767164ba74fce5b8c25c54114e0661f7da44982850578e",- "size": 328197,+ "version": "1.6.31",+ "url": "https://wiki.adom.inc/download/adom/adom-desktop-fusion-bridge/1.6.31/adom-bridge-fusion-v1.6.31.zip",+ "sha256": "f40e26b5e42760dbec3bad48330606aa69884a67015513ffb546afc97130701b",+ "size": 329710, "verbPrefixes": [ "fusion_" ], "healthEndpoint": "/status",- "released_at": "2026-07-05T20:44:46Z"+ "released_at": "2026-07-05T21:35:14Z" }
bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "fusion360", "displayName": "Autodesk Fusion 360",- "version": "1.6.30",+ "version": "1.6.31", "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/adom-desktop-fusion-bridge", "author": "Adom Inc.",
fusion_detect.py+24@@ -110,6 +110,30 @@ def _find_fusion_launcher() -> Path | None: return candidates[0][2] +def fusion_update_in_progress() -> bool:+ """Best-effort: is Fusion mid AUTO-UPDATE? Signalled by TWO+ production build+ dirs (each with a FusionLauncher.exe) coexisting under webdeploy - the current+ build plus the new one Fusion's streamer is materializing. While that applies,+ Fusion crash-restarts ITSELF every ~30-60s, so verbs intermittently see 'not+ running' (observed live on a fresh VM 2026-07-05: hashes 441fa88... + 6a0c961...).+ Callers surface a distinct non-fatal `updating` status instead of a fatal error so+ a driving AI waits/retries rather than treating the flap as a crash. Never raises."""+ hashes = set()+ for base in _WEBDEPLOY_BASES:+ try:+ if not base.exists():+ continue+ for d in base.iterdir():+ try:+ if d.is_dir() and (d / "FusionLauncher.exe").exists():+ hashes.add(d.name)+ except OSError:+ continue+ except OSError:+ continue+ return len(hashes) > 1++ def _find_addins_dir() -> Path | None: """Find the Fusion 360 add-ins directory.""" for candidate in ADDINS_DIR_CANDIDATES:
server.py+45−4@@ -21,7 +21,7 @@ from pathlib import Path # Ensure the plugin root is on the path sys.path.insert(0, str(Path(__file__).parent)) -from fusion_detect import detect_fusion, ensure_fusion_running, wait_for_addin, _is_fusion_running+from fusion_detect import detect_fusion, ensure_fusion_running, wait_for_addin, _is_fusion_running, fusion_update_in_progress from handlers.open_design import handle_open_design from handlers.close_fusion import handle_close_fusion, handle_fusion_stop, handle_fusion_kill from handlers.dismiss_recovery import dismiss_recovery_dialog@@ -802,6 +802,18 @@ def _handle_fusion_readiness(fusion_info: dict, args: dict) -> dict: needs_signin = False ready = installed and running and not needs_signin + # Fusion applying an AUTO-UPDATE (two+ webdeploy builds) crash-restarts itself every+ # ~30-60s, so a verb intermittently sees running:false. That is NOT a crash or a+ # bridge failure - surface it as a distinct non-fatal `updating` flag so a driving AI+ # waits/retries instead of giving up. Only check when installed but not currently+ # ready (avoid noise when Fusion is happily drivable).+ updating = False+ if installed and not ready:+ try:+ updating = fusion_update_in_progress()+ except Exception:+ updating = False+ # When Fusion is up + signed in, also check the add-in isn't STALE (issue #55) so # a fresh readiness call surfaces the mismatch instead of a later verb failing # silently. Reuse the add-in status already fetched above (no second probe).@@ -809,7 +821,13 @@ def _handle_fusion_readiness(fusion_info: dict, args: dict) -> dict: if addin_ok: stale_info = _addin_staleness(addin_status.get("version")) - if needs_signin:+ if updating and not ready:+ hint = ("Fusion is applying an AUTO-UPDATE (multiple webdeploy builds present) and "+ "RESTARTS ITSELF every ~30-60s - this is NOT a crash or a bridge failure. Poll "+ "fusion_readiness; it stabilizes to ready:true once the update finishes (can take "+ "10-30 min). Meanwhile, wrap modeling/export verbs in a short retry/catch-loop to "+ "ride the up-windows rather than treating an intermittent 'not running' as fatal.")+ elif needs_signin: hint = ("Fusion is RUNNING but stuck at the first-run Autodesk SIGN-IN (main UI blocked, " "add-in cannot serve) - NOT ready. AUTOMATE the sign-in (see the fusion-onboarding " "skill for the proven sequence: clear stale signin.autodesk.com tabs, 'Retry browser "@@ -841,6 +859,9 @@ def _handle_fusion_readiness(fusion_info: dict, args: dict) -> dict: **stale_info, "_hint": hint, }+ if updating:+ result["updating"] = True+ result["statusVerb"] = "fusion_readiness" if needs_signin: result["needsSignin"] = True result["statusVerb"] = "fusion_readiness"@@ -968,6 +989,26 @@ def _shape_pref_result(res: dict, wrote: bool) -> dict: return out +def _run_pref_script(script: str, timeout: int) -> dict:+ """Proxy a preferences script to the add-in, retrying ONCE on a transient add-in+ connection error. The first pref call can hit the add-in mid doc-transition (e.g.+ right after opening a Library, when run_modeling_script must create a Design) and+ come back 'not running'/'not responding' even though Fusion is up - seen live on+ the fresh-VM battery, where a ~1.5s retry cleared it. A genuinely-down Fusion just+ costs one extra 1.5s try."""+ res = _proxy_to_addin("run_modeling_script", {"script": script}, timeout=timeout)+ if res and res.get("success"):+ return res+ err = ((res or {}).get("error") or "").lower()+ if any(s in err for s in ("not running", "not responding", "add-in",+ "connection", "timed out", "refused")):+ _time.sleep(1.5)+ res2 = _proxy_to_addin("run_modeling_script", {"script": script}, timeout=timeout)+ if res2 is not None:+ return res2+ return res++ def _handle_set_preference(fusion_info: dict, args: dict) -> dict: reqs = {k: v for k, v in (args or {}).items() if k not in ("settle",)} if not reqs:@@ -975,13 +1016,13 @@ def _handle_set_preference(fusion_info: dict, args: dict) -> dict: "_hint": "Pass e.g. {\"theme\":\"dark\"} or {\"invertScrollZoom\":true," "\"orbitScheme\":\"solidworks\"}. Call fusion_get_preferences to see current values + keys."} script = _PREF_SCRIPT.replace("__REQS__", json.dumps(json.dumps(reqs)))- res = _proxy_to_addin("run_modeling_script", {"script": script}, timeout=30)+ res = _run_pref_script(script, timeout=30) return _shape_pref_result(res, wrote=True) def _handle_get_preferences(fusion_info: dict, args: dict) -> dict: script = _PREF_SCRIPT.replace("__REQS__", json.dumps(json.dumps({})))- res = _proxy_to_addin("run_modeling_script", {"script": script}, timeout=20)+ res = _run_pref_script(script, timeout=20) return _shape_pref_result(res, wrote=False)