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.14: FusionAddins root cause (#63) + install-to-all-paths + never-ask-user-to-restart
12 files changed
+124−69
BRIDGE_VERSION+1−1@@ -1 +1 @@-1.6.13+1.6.14
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.14 - 2026-07-03++- **#63 ROOT-CAUSED + FIXED: Fusion MOVED its add-in directory; we now install to ALL of them.**+ 2025+ Fusion scans `%APPDATA%\Autodesk\FusionAddins\` and SILENTLY ignores the legacy+ `Autodesk Fusion[ 360]\API\AddIns\` dirs (proven live on a fresh install: add-in in the legacy dir+ never loaded through a full launch; moved to FusionAddins it came up in 20s, runOnStartup honored,+ zero manual enabling). `install_addin.py` now installs/uninstalls to EVERY known location+ (TARGET_CANDIDATES, newest first), `fusion_detect` scans all of them, and the add-in-not-responding+ errors carry the FusionAddins explanation + a stable `errorCode`.+- **⛔ NEVER ask the user to restart Fusion / enable the add-in - baked into code + skills.** Every+ hint that said "restart Fusion 360" / "enable via UTILITIES > ADD-INS" now instructs the AI to DO+ IT ITSELF (`fusion_stop` + `fusion_start`); the USER skill (convention 6), fusion-onboarding, and+ CLAUDE.md all carry the rule. Doing the work for the user is the point of the bridge.+ ## 1.6.13 - 2026-07-03 - **`bridge.json` dependencies described honestly.** Dropped `fusion360: ">=2.0.0"` - Fusion 360 is
CLAUDE.md+9−3@@ -36,9 +36,15 @@ all of this and filed its own bridge bugs as "adom-desktop" requests. Don't.) ## How you DEPLOY when you change your code (VERIFIED by watching v1.5.1 ship, 2026-06-28) -Fusion loads the add-in from **`%APPDATA%\Roaming\Autodesk\Autodesk Fusion[ 360]\API\AddIns\AdomBridge\`**,-NOT from this repo or the cache. `install_addin.py` (`_sync_directory`) copies `addin/AdomBridge` ->-there. The exact, watched-it-happen release sequence (AFTER publishing to the wiki):+Fusion loads the add-in from a PER-USER add-in dir that Autodesk has MOVED across versions:+**2025+ Fusion scans `%APPDATA%\Roaming\Autodesk\FusionAddins\AdomBridge\`**; older builds used+`%APPDATA%\Roaming\Autodesk\Autodesk Fusion[ 360]\API\AddIns\AdomBridge\`. An add-in in the+wrong dir is SILENTLY ignored (Fusion runs fine, port 8774 never comes up - issue #63, root-caused+live). `install_addin.py` therefore installs to ALL of them (TARGET_CANDIDATES); NEVER assume a+single path, and if a future Fusion stops loading the add-in, FIRST suspect the dir moved again+(hunt for new dirs under %APPDATA%\Autodesk yourself). NOT from this repo or the cache.+⛔ NEVER ask the user to restart Fusion or enable the add-in - YOU restart (fusion_stop ++fusion_start); runOnStartup does the rest. The exact, watched-it-happen release sequence (AFTER publishing to the wiki): 1. **`bridge_install {"manifestUrl": ".../adom-bridge-fusion-manifest.json"}`** - streams the new zip into the CACHE via `method: in_place_merge`. Updates the cache ONLY; it does NOT touch Fusion's
SKILL.md+1−1@@ -37,7 +37,7 @@ Branch on the stable `errorCode`, never the prose: 3. **Core verbs live in the table below; `fusion_describe '{}'` is the full machine-readable catalog.** Read the table to act fast; hit `describe` for completeness — don't guess a verb. 4. **Background by default — but NOTIFY on every active step.** This bridge drives the AI's workspace, not the user's screen: dismiss dialogs in the BACKGROUND (`fusion_close_window`, never `fusion_send_key`/Escape, which steals focus). **The instant the user must DO something you can't (Autodesk sign-in, 2FA, a UAC/elevation prompt, accepting a license, a manual choice), fire an AD `notify_user` (a toast, and a progress toast for long installs) so they're pulled in — never silently wait on a screen they aren't looking at.** Only foreground Fusion to SHOW the user something. See [fusion-driving](skills/fusion-driving/SKILL.md) and [fusion-onboarding](skills/fusion-onboarding/SKILL.md). 5. **`ok:true` is not enough — verify.** A long op that "times out" at ~60s is usually STILL RUNNING server-side (the relay caps the request, not the work). Verify by artifacts (the saved file / a screenshot / `fusion_get_app_state`), don't blindly retry.-6. **Reuse the running Fusion, don't relaunch.** Fusion is a singleton — `fusion_readiness` before `fusion_start` (especially after a compaction); if it's already `ready`, just drive. Relaunching churns state.+6. **Reuse the running Fusion — and when a restart IS needed, YOU do it, never the user.** Fusion is a singleton: `fusion_readiness` before `fusion_start`; if `ready`, just drive. But a stale/missing add-in or a wedged session needs a restart, and that is YOUR job — `fusion_stop` then `fusion_start`. NEVER tell the user "restart Fusion" / "enable the add-in in UTILITIES" — doing it for them is the entire point of this bridge. (Add-in installs land in ALL Fusion add-in dirs — 2025+ Fusion scans `%APPDATA%\Autodesk\FusionAddins`, older builds use `...\Autodesk Fusion[ 360]\API\AddIns` — so a restart always picks it up.) 7. **Surface `_hint` verbatim + branch on `errorCode`.** Every verb returns an actionable `_hint`; relay it to the user unchanged and key your logic off the stable `errorCode`, not the prose. 8. **Never write files to a shared cloud ROOT.** Uploads default to an `Adom AI Workspace` folder; see [fusion-cloud-hygiene](skills/fusion-cloud-hygiene/SKILL.md).
adom-bridge-fusion-manifest.json+5−5@@ -1,13 +1,13 @@ { "manifest_version": 1, "name": "fusion360",- "version": "1.6.13",- "url": "https://wiki.adom.inc/download/adom/adom-desktop-fusion-bridge/1.6.13/adom-bridge-fusion-v1.6.13.zip",- "sha256": "708daafd1d5d66adebf1a77082ea77f606e0546c450252e78fff21f4be001c6f",- "size": 302647,+ "version": "1.6.14",+ "url": "https://wiki.adom.inc/download/adom/adom-desktop-fusion-bridge/1.6.14/adom-bridge-fusion-v1.6.14.zip",+ "sha256": "cba2e918b3e4d90743675d1de369e82f781ddb49518af64ddedbf5ba24e324ad",+ "size": 304852, "verbPrefixes": [ "fusion_" ], "healthEndpoint": "/status",- "released_at": "2026-07-03T17:30:00Z"+ "released_at": "2026-07-03T19:00:00Z" }
bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "fusion360", "displayName": "Autodesk Fusion 360",- "version": "1.6.13",+ "version": "1.6.14", "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+23−8@@ -31,8 +31,12 @@ WEBDEPLOY_BASE = _WEBDEPLOY_BASES[0] if _WEBDEPLOY_BASES else ( Path(os.environ.get("LOCALAPPDATA", "")) / "Autodesk" / "webdeploy" / "production" ) -# Add-ins can live in either of these directories (varies by Fusion version)+# EVERY known per-user add-in dir, newest convention first. Fusion MOVED this+# across versions and silently ignores the others (issue #63: an add-in in the+# legacy API\AddIns dir never loads on 2025+ Fusion, which scans FusionAddins -+# proven live on a fresh install). Track ALL of them; never assume one path. ADDINS_DIR_CANDIDATES = [+ Path(os.environ.get("APPDATA", "")) / "Autodesk" / "FusionAddins", Path(os.environ.get("APPDATA", "")) / "Autodesk" / "Autodesk Fusion" / "API" / "AddIns", Path(os.environ.get("APPDATA", "")) / "Autodesk" / "Autodesk Fusion 360" / "API" / "AddIns", ]@@ -116,11 +120,17 @@ def _find_addins_dir() -> Path | None: def _check_addin_installed(addins_dir: Path | None) -> bool:- """Check if the AdomBridge add-in is installed."""- if not addins_dir:- return False- addin_dir = addins_dir / "AdomBridge"- return (addin_dir / "AdomBridge.py").exists()+ """Check if the AdomBridge add-in is installed in ANY known add-in dir.++ Scans every ADDINS_DIR_CANDIDATES entry (not just the preferred one) - the+ copy that counts is whichever dir THIS Fusion version scans, and we can't+ know that a priori, so presence anywhere counts as installed."""+ for candidate in ADDINS_DIR_CANDIDATES:+ if (candidate / "AdomBridge" / "AdomBridge.py").exists():+ return True+ if addins_dir and (Path(addins_dir) / "AdomBridge" / "AdomBridge.py").exists():+ return True+ return False def _is_fusion_running() -> bool:@@ -252,8 +262,13 @@ def ensure_fusion_running(fusion_info: dict, wait_addin: bool = False) -> dict | if not wait_for_addin(timeout=10): return { "success": False,- "error": "Fusion 360 is running but AdomBridge add-in not responding. "- "Enable it: UTILITIES > ADD-INS > AdomBridge > Run on Startup + Run",+ "error": "Fusion 360 is running but AdomBridge add-in not responding.",+ "errorCode": "fusion_addin_not_responding",+ "_hint": "Fix it YOURSELF - never ask the user: (1) run install_addin() so the "+ "add-in lands in EVERY Fusion add-in dir (Fusion moved it to "+ "%APPDATA%\\Autodesk\\FusionAddins on 2025+ builds; an add-in in the "+ "legacy API\\AddIns dir is silently ignored - issue #63), then "+ "(2) restart Fusion via fusion_stop + fusion_start so it rescans.", } return None # Already running (and add-in ready if requested)
handlers/open_design.py+1−1@@ -41,7 +41,7 @@ def _import_via_addin(file_path: str, timeout: int = 30) -> dict: return { "success": False, "error": f"AdomBridge add-in not responding (port {ADDIN_PORT}): {e}. "- "Ensure the add-in is running in Fusion (UTILITIES > ADD-INS > AdomBridge).",+ "The add-in is not responding - fix it YOURSELF (never ask the user): fusion_stop + fusion_start reloads it (installed to all add-in dirs incl. FusionAddins).", } except Exception as e: return {"success": False, "error": f"Import request failed: {e}"}
install_addin.py+53−40@@ -1,9 +1,20 @@ #!/usr/bin/env python3-"""Install the AdomBridge add-in into Fusion 360's AddIns directory.+"""Install the AdomBridge add-in into EVERY Fusion add-in location. -Copies the addin/AdomBridge/ folder to:- %APPDATA%/Autodesk/Autodesk Fusion/API/AddIns/AdomBridge/- (or Autodesk Fusion 360 variant)+Fusion has MOVED its per-user add-in directory across versions, and an add-in+in the wrong one is silently ignored (Fusion loads fine, the add-in never runs,+port 8774 stays down - issue #63, root-caused live on a fresh 2026 install):++ NEW (2025+): %APPDATA%/Autodesk/FusionAddins/AdomBridge/ <- current Fusion scans THIS+ legacy: %APPDATA%/Autodesk/Autodesk Fusion/API/AddIns/ <- older builds+ legacy: %APPDATA%/Autodesk/Autodesk Fusion 360/API/AddIns/ <- oldest naming++Verified live (ADOMBASELINE, Fusion 2026.x): the add-in sat in the legacy+API/AddIns dir through a full launch and NEVER loaded; moved to FusionAddins it+came up in 20s with zero manual enabling (runOnStartup honored). So we install+to ALL locations - each Fusion version loads from the one it scans and ignores+the others (a duplicate instance would fail its 8774 bind and no-op). NEVER+assume a single path; scan all of TARGET_CANDIDATES. Usage: python install_addin.py@@ -17,8 +28,11 @@ from pathlib import Path # Source: the add-in bundled with this plugin SOURCE_DIR = Path(__file__).resolve().parent / "addin" / "AdomBridge" -# Possible target directories+# EVERY known Fusion add-in directory convention, newest first. Keep this list+# complete - see the module docstring for why (a single-path assumption caused+# the #63 silent-failure). FusionAddins is created even on a fresh profile. TARGET_CANDIDATES = [+ Path.home() / "AppData" / "Roaming" / "Autodesk" / "FusionAddins", Path.home() / "AppData" / "Roaming" / "Autodesk" / "Autodesk Fusion" / "API" / "AddIns", Path.home() / "AppData" / "Roaming" / "Autodesk" / "Autodesk Fusion 360" / "API" / "AddIns", ]@@ -74,58 +88,57 @@ def find_addins_dir() -> Path | None: def install():- """Install the AdomBridge add-in.+ """Install the AdomBridge add-in into EVERY known Fusion add-in location.++ ALL of TARGET_CANDIDATES get the add-in (see module docstring - Fusion moved+ the scan dir across versions and ignores the others, so single-path installs+ silently fail on the "wrong" version). Smart update per target: copies+ individual changed files rather than deleting the folder, preserving Fusion's+ "Run on Startup" metadata. - Uses a smart update strategy: copies individual files from source to target- rather than deleting the entire target folder. This preserves Fusion 360's- "Run on Startup" setting which is stored in the AddIns directory metadata.+ NOTE for the AI running this: after install, YOU restart Fusion yourself+ (fusion_stop then fusion_start) so it rescans - NEVER ask the user to+ restart Fusion; doing things for the user is the entire point of the bridge. """ if not SOURCE_DIR.exists(): print(f"ERROR: Source add-in not found at {SOURCE_DIR}") sys.exit(1) - addins_dir = find_addins_dir()- if not addins_dir:- print("ERROR: Could not determine Fusion 360 AddIns directory.")- sys.exit(1)-- target = addins_dir / "AdomBridge"- print(f"Source: {SOURCE_DIR}")- print(f"Target: {target}")-- addins_dir.mkdir(parents=True, exist_ok=True)-- if target.exists():- # Smart update: sync files from source to target without deleting the folder- _sync_directory(SOURCE_DIR, target)- print(f"Updated AdomBridge add-in at {target}")- else:- # Fresh install- shutil.copytree(SOURCE_DIR, target)- print(f"Installed AdomBridge add-in to {target}")- print()- # The manifest sets "runOnStartup": true, so Fusion auto-runs the add-in- # on its next launch — no manual enabling needed. Just (re)start Fusion and- # the bridge's HTTP server comes up automatically; verify with- # fusion_addin_status. (Older Fusion builds may require a one-time enable- # via UTILITIES > ADD-INS > AdomBridge > Run on Startup if it doesn't load.)- print("Restart Fusion 360 — the add-in auto-runs on startup (runOnStartup:true).")+ installed_to = []+ for addins_dir in TARGET_CANDIDATES:+ target = addins_dir / "AdomBridge"+ try:+ addins_dir.mkdir(parents=True, exist_ok=True)+ if target.exists():+ _sync_directory(SOURCE_DIR, target)+ print(f"Updated: {target}")+ else:+ shutil.copytree(SOURCE_DIR, target)+ print(f"Installed: {target}")+ installed_to.append(str(target))+ except OSError as e:+ print(f" skip {target}: {e}") print()- print("The add-in will start an HTTP server on port 8774.")+ print(f"Add-in present in {len(installed_to)} location(s); Fusion loads it from the one "+ "its version scans (runOnStartup:true) and ignores the rest.")+ print("Restart Fusion via the BRIDGE (fusion_stop then fusion_start) so it rescans - "+ "never ask the user to do it. The add-in serves http://127.0.0.1:8774.")+ return installed_to def uninstall():- """Uninstall the AdomBridge add-in."""+ """Uninstall the AdomBridge add-in from EVERY known location."""+ removed = 0 for candidate in TARGET_CANDIDATES: target = candidate / "AdomBridge" if target.exists(): shutil.rmtree(target) print(f"Removed: {target}")- return-- print("AdomBridge add-in is not installed.")+ removed += 1+ if not removed:+ print("AdomBridge add-in is not installed.") def main():
page.json+1−1@@ -1,6 +1,6 @@ { "slug": "adom-desktop-fusion-bridge",- "version": "1.6.14",+ "version": "1.6.15", "type": "app", "description": "Drive Autodesk Fusion 360 from the cloud via Adom Desktop: 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 with browse/recent/versions, cloud download/upload and folder creation, and in-app parametric modeling. Never-charge: APS calls are capped to the free tier.", "dependencies": {},
server.py+7−5@@ -1178,9 +1178,11 @@ def _diagnose_addin_timeout(command: str, original_result: dict) -> dict: # Health probe failed — add-in HTTP server is down return { "success": False,- "error": "AdomBridge add-in not responding. It may have crashed. "- "Restart Fusion 360 or re-enable the add-in: "- "UTILITIES > ADD-INS > AdomBridge > Run",+ "error": "AdomBridge add-in not responding (it may have crashed).",+ "errorCode": "fusion_addin_not_responding",+ "_hint": "Fix it YOURSELF - never ask the user: restart Fusion via fusion_stop + "+ "fusion_start (the bridge installs the add-in to ALL Fusion add-in dirs incl. "+ "%APPDATA%\\Autodesk\\FusionAddins, and runOnStartup reloads it).", } @@ -1999,7 +2001,7 @@ def dispatch_command(command: str, args: dict) -> dict: "success": False, "error": "Fusion 360 is running but AdomBridge add-in not responding.", "errorCode": "fusion_addin_not_responding",- "_hint": "The add-in may be disabled or crashed. Try fusion_start which will restart Fusion cleanly. If that fails, the user must manually enable it via UTILITIES > ADD-INS > AdomBridge > Run on Startup + Run.",+ "_hint": "The add-in isn't responding. Fix it YOURSELF - never ask the user: the bridge auto-installs the add-in to ALL Fusion add-in dirs (2025+ Fusion scans %APPDATA%/Autodesk/FusionAddins; the legacy API/AddIns dirs are silently ignored - issue #63), so restart Fusion via fusion_stop + fusion_start and it loads (runOnStartup). Verify with fusion_addin_status.", } # ── Busy gate: reject add-in commands immediately if a long command is running ──@@ -2177,7 +2179,7 @@ def _build_status() -> dict: elif not addin_ok: led, summary = "yellow", "Add-in not connected" tooltip = ("Fusion 360 is running but the AdomBridge add-in isn't responding yet "- "(still loading, or needs enabling via UTILITIES > ADD-INS).")+ "(still loading; if it persists the AI restarts Fusion via fusion_stop/start).") else: led, summary = "green", "Fusion ready" tooltip = "Fusion 360 running, AdomBridge add-in connected. All fusion_* verbs available."
skills/fusion-onboarding/SKILL.md+8−3@@ -67,9 +67,14 @@ viewing and a lot of work. Don't auto-enter their password; do drive the OTP/for ## Step 4 — Complete the circuit: auto-install the AdomBridge add-in This is what lets the bridge inject commands into Fusion after launch.-- **The bridge does this for you:** on first dispatch, `server.py` auto-runs `install_addin` if- the add-in isn't present (copies `addin/AdomBridge/` → Fusion's `API/AddIns/`). You can also- force it: run the cache's `install_addin.py` (`python <bridges-cache>/fusion360/install_addin.py`).+- **The bridge does this for you:** on start, `server.py` auto-runs `install_addin`, which lands the+ add-in in **EVERY known Fusion add-in dir** — `%APPDATA%\Autodesk\FusionAddins\` (what 2025++ Fusion actually scans) AND the legacy `Autodesk Fusion[ 360]\API\AddIns\` dirs. ⚠ Fusion MOVED+ this directory across versions and silently ignores the others (an add-in only in the legacy dir+ never loads on current Fusion — issue #63, proven live on a fresh install). Never assume one path.+- **⛔ NEVER ask the user to restart Fusion or enable the add-in.** After installing, YOU restart it:+ `fusion_stop` then `fusion_start` — the add-in auto-loads (`runOnStartup:true`, verified live:+ port 8774 up 20s after restart, zero manual enabling). Asking the user to do it defeats the bridge. - **It auto-loads every launch:** `AdomBridge.manifest` has **`"runOnStartup": true`**, so Fusion runs the add-in (and its localhost:8774 HTTP server) automatically on startup — **no manual "enable" needed**. (Verified: after a Fusion restart the add-in comes back `addinReady:true`