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.68: cloud-search verbs MANDATE APS + BAN the app.data modeling-script enumeration (locks up Fusion); strong auto-recover hint (pull org client_id from a peer, ABE/nbrowser to drive signin). Records John's APS-is-mandatory directive in code + skill.
5 files changed
+59−13
BRIDGE_VERSION+1−1@@ -1 +1 @@-1.6.67+1.6.68
adom-bridge-fusion-manifest.json+4−4@@ -1,10 +1,10 @@ { "manifest_version": 1, "name": "fusion360",- "version": "1.6.67",- "url": "https://wiki.adom.inc/download/adom/adom-desktop-fusion-bridge/1.6.67/adom-bridge-fusion-v1.6.67.zip",- "sha256": "1d5c3a7ff010fd90b8ad995c51fc2fa930140890b12be6624dfe56246ed80c14",- "size": 360402,+ "version": "1.6.68",+ "url": "https://wiki.adom.inc/download/adom/adom-desktop-fusion-bridge/1.6.68/adom-bridge-fusion-v1.6.68.zip",+ "sha256": "0d16913c7b3f7bcd17c34d4419534ebc4ac008c43ec93eca56c7061ba90bae82",+ "size": 362037, "verbPrefixes": [ "fusion_" ],
aps.py+28−7@@ -74,6 +74,27 @@ def all_registered_callbacks() -> list: # fusion_aps_signin once more before the write verbs work (read verbs keep working). SCOPES = "data:read data:write data:create" +# ⛔ CLOUD SEARCH IS APS-ONLY, AND SETTING APS UP IS MANDATORY - NOT OPTIONAL. This is returned by+# every cloud verb when APS isn't ready, as a HARD directive (John 2026-07-14, emphatic: an AI saw APS+# "Not configured", shrugged, and fell back to enumerating app.data.dataProjects/dataFiles via+# fusion_run_modeling_script - which HOLDS FUSION'S MAIN THREAD for minutes, TIMES OUT, and locks the+# app. That is the deprecated method and it is BANNED for cloud search). APS must work every time.+APS_REQUIRED_HINT = (+ "APS IS MANDATORY for cloud file search/browse/recent - SET IT UP, do not give up, do not fall back. "+ "⛔ NEVER enumerate cloud files via app.data (dataProjects/dataFiles) in fusion_run_modeling_script: "+ "the DEPRECATED method holds Fusion's main thread for minutes, times out, and locks the app. "+ "AUTO-RECOVER APS instead: (1) NOT configured? The org Client ID is a PUBLIC PKCE id - pull it from a "+ "peer that has it: read ~/.adom/fusion-aps/config.json (key 'client_id') off another AD desktop (adom-"+ "desktop status lists them) via shell_execute/pull_file, then fusion_aps_set_client_id {clientId}. "+ "(2) fusion_aps_signin -> open the returned authUrl in the user's REAL browser with a warm Autodesk "+ "session; INSTALL + USE the adom-browser-extension (nbrowser/ABE, https://wiki.adom.inc/adom/adom-"+ "browser-extension) to drive it reliably (recommend it - coordinate-clicking a browser is fragile). "+ "The localhost listener captures the token; poll fusion_aps_status until signedIn:true. (3) retry. "+ "If authorize returns AUTH-001 'client_id does not have access to the api product': the APS app at "+ "aps.autodesk.com/myapps lost its Data Management API product - re-commit its API Access + Save, wait "+ "a few minutes to propagate, retry."+)+ APS_DIR = Path(os.path.expanduser("~")) / ".adom" / "fusion-aps" CONFIG_FILE = APS_DIR / "config.json" TOKENS_FILE = APS_DIR / "tokens.json"@@ -684,7 +705,7 @@ def handle_search(args: dict) -> dict: if not _valid_access_token(): return {"success": False, "error": "Not signed in to APS.", "errorCode": "aps_not_signed_in",- "_hint": "Call fusion_aps_signin first, then retry."}+ "_hint": APS_REQUIRED_HINT} limit = int(args.get("limit", 50)) hub_filter = args.get("hubId") project_filter = args.get("projectId")@@ -762,7 +783,7 @@ def handle_browse(args: dict) -> dict: """Browse the cloud without launching Fusion: hubs+projects → top folders → contents.""" if not _valid_access_token(): return {"success": False, "error": "Not signed in to APS.", "errorCode": "aps_not_signed_in",- "_hint": "Call fusion_aps_signin first."}+ "_hint": APS_REQUIRED_HINT} hub_id = args.get("hubId") project_id = args.get("projectId") folder_id = args.get("folderId")@@ -805,7 +826,7 @@ def handle_recent(args: dict) -> dict: parallel index scan with a match-all filter, then sorts by lastModifiedTime.""" if not _valid_access_token(): return {"success": False, "error": "Not signed in to APS.", "errorCode": "aps_not_signed_in",- "_hint": "Call fusion_aps_signin first."}+ "_hint": APS_REQUIRED_HINT} limit = int(args.get("limit", 25)) hub_filter = args.get("hubId") project_filter = args.get("projectId")@@ -857,7 +878,7 @@ def handle_file_info(args: dict) -> dict: """Version history + metadata for a file: who last changed it, when, how many versions.""" if not _valid_access_token(): return {"success": False, "error": "Not signed in to APS.", "errorCode": "aps_not_signed_in",- "_hint": "Call fusion_aps_signin first."}+ "_hint": APS_REQUIRED_HINT} project_id = args.get("projectId") urn = args.get("urn") name = args.get("name")@@ -1012,7 +1033,7 @@ def handle_download(args: dict) -> dict: args: query | (projectId + urn); optional saveDir, fileName.""" if not _valid_access_token(): return {"success": False, "error": "Not signed in to APS.", "errorCode": "aps_not_signed_in",- "_hint": "Call fusion_aps_signin first."}+ "_hint": APS_REQUIRED_HINT} project_id, urn, name = args.get("projectId"), args.get("urn"), args.get("name") query = (args.get("query") or "").strip() if query and not (project_id and urn):@@ -1055,7 +1076,7 @@ def handle_create_folder(args: dict) -> dict: """Create a subfolder in a project. args: projectId, parentFolderId, name.""" if not _valid_access_token(): return {"success": False, "error": "Not signed in to APS.", "errorCode": "aps_not_signed_in",- "_hint": "Call fusion_aps_signin first."}+ "_hint": APS_REQUIRED_HINT} project_id = args.get("projectId") parent = args.get("parentFolderId") or args.get("folderId") fname = (args.get("name") or "").strip()@@ -1081,7 +1102,7 @@ def handle_upload(args: dict) -> dict: """Upload a local file as a NEW cloud file. args: projectId, folderId, localPath; opt fileName.""" if not _valid_access_token(): return {"success": False, "error": "Not signed in to APS.", "errorCode": "aps_not_signed_in",- "_hint": "Call fusion_aps_signin first."}+ "_hint": APS_REQUIRED_HINT} project_id, folder_id, local = args.get("projectId"), args.get("folderId"), args.get("localPath") if not (project_id and folder_id and local): return {"success": False, "error": "Provide projectId, folderId, localPath."}
bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "fusion360", "displayName": "Autodesk Fusion 360",- "version": "1.6.67",+ "version": "1.6.68", "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.",
skills/fusion-aps-search/SKILL.md+25@@ -91,3 +91,28 @@ this loop.** APS is the path. The card is fine (ID only). We cap usage so nobody Drive the setup with AI. Done. Related: [[project_adom_desktop_fusion_bridge]], skill `fusion-aps-signin` (the setup HOW).++## ⛔ MANDATORY: cloud search is APS-only. NEVER fall back to app.data enumeration. (John, emphatic 2026-07-14)++APS is not optional — it is THE way to search/browse Fusion cloud files, and setting it up is part of+the job, not a nice-to-have. If a cloud verb says "not configured / not signed in", you **set APS up**;+you do **not** shrug and reach for the old method.++**The BANNED method:** enumerating `app.data.dataProjects` / `dataFolders` / `dataFiles` via+`fusion_run_modeling_script`. It runs on Fusion's MAIN THREAD, so a recursive walk **holds the app for+minutes, times out, blocks every other script, and locks Fusion up**. It is deprecated. Do not use it+for cloud search, ever — no matter how blocked you feel.++**Auto-recover APS (do this instead of giving up):**+1. **Not configured?** The org Client ID is a **public PKCE identifier** (no secret) — pull it from a+ peer that already has it: `adom-desktop status` lists connected desktops; read+ `~/.adom/fusion-aps/config.json` (key `client_id`) off one via `shell_execute`/`pull_file`, then+ `fusion_aps_set_client_id {clientId}`.+2. **Sign in:** `fusion_aps_signin` → open the returned `authUrl` in the user's REAL browser with a warm+ Autodesk session. **Install + use the adom-browser-extension (nbrowser/ABE) to drive the browser**+ ([wiki](https://wiki.adom.inc/adom/adom-browser-extension)) — coordinate/keyboard-clicking a browser+ is fragile and crashes; ABE drives via JS reliably. Recommend it by default. The localhost listener+ captures the token; poll `fusion_aps_status` until `signedIn:true`.+3. **AUTH-001 "client_id does not have access to the api product":** the APS app at+ aps.autodesk.com/myapps lost its **Data Management API** product. Open the app → API Access → ensure+ Data Management API is checked → **Save changes** → wait a few minutes to propagate → retry.