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 102 file(s)
9 files changed
+184−162
BRIDGE_VERSION+1−1@@ -1 +1 @@-1.9.293+1.9.294
adom-bridge-fusion-manifest.json+4−4@@ -1,10 +1,10 @@ { "manifest_version": 1, "name": "fusion360",- "version": "1.9.293",- "url": "https://wiki.adom.inc/download/adom/fusion-bridge/1.9.293/adom-bridge-fusion-v1.9.293.zip",- "sha256": "2bb4b2c9eef5116f0cd242053224db451e2d6f21bfb3e3c57f65ed6b8f436614",- "size": 583014,+ "version": "1.9.294",+ "url": "https://wiki.adom.inc/download/adom/fusion-bridge/1.9.294/adom-bridge-fusion-v1.9.294.zip",+ "sha256": "1d1c618c6d5eace5e0467f1ee85e27e4d249649ccdffad5e8623e2721cbee997",+ "size": 583360, "verbPrefixes": [ "fusion_" ],
bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "fusion360", "displayName": "Autodesk Fusion 360",- "version": "1.9.293",+ "version": "1.9.294", "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.293"+version = "1.9.294" dependencies = [ "serde_json", ]
dashboard/Cargo.toml+1−1@@ -1,6 +1,6 @@ [package] name = "fusion-dashboard"-version = "1.9.293"+version = "1.9.294" edition = "2021" [dependencies]
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.293"+DASH_VERSION="1.9.294" 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.293",+ "version": "1.9.294", "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.293",+ "version": "1.9.294", "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+163−144@@ -11791,157 +11791,174 @@ def _handle_prove_library(fusion_info: dict, args: dict) -> dict: # package was proved earlier in the run; only its symbol and ganged frames are taken. _skip = set(args.get("_skipSteps") or []) _shared_with = args.get("_sharedWith")+ # PACKAGE VARIANTS (John, 2026-08-30): a deviceset may carry several devices on several+ # packages (LAN7800 "", -HV, -SB, -RR). The symbol is one frame; the footprint and the+ # bound 3D are one frame PER PACKAGE, each named by the package, so every variant is+ # proved, not just the first device's.+ _pkgs = ((_library_artifact_names(lbr).get("dev_pac") or {}).get(want) or [])+ _multi = len(_pkgs) > 1+ _pk_iter = _pkgs if _multi else [None] # 1 + 2: the two editors, each full screen.- for label, suffix, view in (("symbol", "sym", "symbol-editor"),- ("footprint", "pac", "footprint-editor")):- if label in _skip:- continue- r = _show_library_view(want, suffix, lbr)+ if "symbol" not in _skip:+ r = _show_library_view(want, "sym", lbr) if not r.get("success"):- evidence.append(_emit({"step": label, "view": view, "ok": False,+ evidence.append(_emit({"step": "symbol", "view": "symbol-editor", "ok": False, "error": str(r.get("error"))[:160]}))- continue- evidence.append(_emit(_prove_capture(label, view)))+ else:+ evidence.append(_emit(_prove_capture("symbol", "symbol-editor")))+ if "footprint" not in _skip:+ for _pk in _pk_iter:+ r = _show_library_view(_pk or want, "pac", lbr)+ if not r.get("success"):+ evidence.append(_emit({"step": "footprint", "view": "footprint-editor", "ok": False,+ "package": _pk, "error": str(r.get("error"))[:160]}))+ continue+ _ef = _prove_capture("footprint", "footprint-editor"); _ef["package"] = _pk+ evidence.append(_emit(_ef)) # 3: the 3D editor. Only reachable when a real package3d is bound - with none, Fusion # shows its Placeholder, which is exactly the false pass this verb exists to prevent.- if artifacts["package3d"]["present"] and "package3d" not in _skip:- # THE BOUND MODEL, NOT THE GENERATOR (caught by the bodies gate, run be27c38d034e:- # Create3DPackage opens the empty Package3D GENERATOR for an already-bound library -- # bodies=0, a frame of nothing). The evidence is the f3d the package3d urn points- # at: open THAT document, which loads the real geometry the Library Manager shows.- # THE URN LIVES IN wip_urn, NOT urn (run 7d4427d443f5): this bridge's own bind- # writes `<package3d urn="" wip_urn="urn:adsk.wipprod:fs.file:vf...">` - urn stays- # empty by the EAGLE-XML convention used everywhere else in this file. Reading only- # `urn` made a bound library look unopenable ("no urn"). A caller (the create_library- # chain) can also hand the bind result's urn straight in as f3dUrn.- # THIS PART'S urn, not the library's last one (ten-part run 2934addbae6a, 2026-08-29):- # this loop used to keep the LAST <package3d> in the file, so every part's 3D frame- # opened the last-minted design (IN-S63BTG_3d) and "proved" a JST connector with a- # picture of an LED. Resolve the deviceset -> its device's package -> the package3d- # whose packageinstance names that package. The chain's f3dUrn is only a fallback- # for a single-part library.- _p3d_urn = ""- _p3d_pkg = ""- try:- import xml.etree.ElementTree as _ET3- _root3 = _ET3.parse(lbr).getroot()- _want_l = str(want or "").strip().lower()- for _ds in _root3.iter("deviceset"):- if str(_ds.get("name") or "").lower() == _want_l:- for _dv in _ds.iter("device"):- if _dv.get("package"):- _p3d_pkg = _dv.get("package"); break- break- _all3 = list(_root3.iter("package3d"))- for _p3 in _all3:- _insts = [str(i.get("name") or "").lower() for i in _p3.iter("packageinstance")]- if _p3d_pkg and _p3d_pkg.lower() in _insts:- _p3d_urn = (_p3.get("urn") or _p3.get("wip_urn") or "").strip(); break- if not _p3d_urn and len(_all3) == 1:- _p3d_urn = (_all3[0].get("urn") or _all3[0].get("wip_urn") or "").strip()- except Exception:- pass- if not _p3d_urn and (args.get("f3dUrn") or "").strip() and not _p3d_pkg:- _p3d_urn = (args.get("f3dUrn") or "").strip()- opened3d = None- if _p3d_urn:- # A JUST-MINTED f3d may not be durably openable yet (page issue #47 failure- # mode 3: bind success + thumbnail ready, open refused moments later). The- # lineage commits asynchronously - retry over ~45s before failing the frame.- import time as _t3r- for _attempt in range(3):- opened3d = dispatch_command("open_by_urn", {"urn": _p3d_urn})- if opened3d.get("success"):- break- if _attempt < 2:- print("[Fusion Bridge] bound f3d not openable yet (attempt %d) - "- "waiting 15s for the lineage to commit" % (_attempt + 1))- _t3r.sleep(15)- if opened3d and opened3d.get("success"):- # WORKSPACE GUARANTEE (#51, 2026-08-23): the minted f3d sometimes reopens into- # the electronics PACKAGE workspace showing its 2D SHEET (s1 tPlace, layer- # sidebar) instead of the 3D canvas - 7 of the matrix's 20 frames photographed- # that sheet while prove stamped them 4/4. The active view is STICKY per-user- # state, so never trust it: read DebugCommands.ActiveWorkspace, force the- # Design (modeling) workspace when it is anything else, and normalize the- # camera (goHome+fit - also fixes the two chip-on-edge captures). The frame- # then records the workspace it was actually shot in, and cannot be ok unless- # that workspace shows the 3D canvas.- _ws_after = ""- try:- _wsfix = _proxy_to_addin("run_modeling_script", {"script": (- "import adsk.core, time\n"- "app=adsk.core.Application.get()\n"- "def _ws():\n"- " try: return app.executeTextCommand('DebugCommands.ActiveWorkspace').strip()\n"- " except Exception: return ''\n"- "before=_ws()\n"- "if 'design' not in before.lower():\n"- " try:\n"- " w=app.userInterface.workspaces.itemById('FusionSolidEnvironment')\n"- " if w: w.activate()\n"- " adsk.doEvents(); time.sleep(2)\n"- " except Exception: pass\n"- "try:\n"- " app.activeViewport.goHome(False)\n"- " app.activeViewport.fit()\n"- "except Exception: pass\n"- "result={'before': before, 'after': _ws()}\n")}, timeout=60)- _ws_after = str(((_wsfix.get("data") or {}).get("result") or {}).get("after") or "")- except Exception:- pass- # THE RIGHT DOCUMENT, VERIFIED: the mint names the design "<package>_3d"; the- # document in front must be that one, or the frame is a picture of something else.- _doc3 = ""- try:- _st3 = _proxy_to_addin("get_app_state", {}, timeout=8)- _doc3 = str((_st3.get("data") or {}).get("activeDocument") or "").replace("*", "").strip()- except Exception:- pass- e3 = _prove_capture("package3d", "3d-editor")- e3["activeDocument"] = _doc3- e3["expectedDocument"] = ("%s_3d" % _p3d_pkg) if _p3d_pkg else None- if _p3d_pkg and _doc3 and ("%s_3d" % _p3d_pkg).lower() not in _doc3.lower():- e3["ok"] = False- e3["error"] = ("wrong_document_captured: the 3D frame shows %r, not this part's "- "design %r" % (_doc3, "%s_3d" % _p3d_pkg))- e3["activeWorkspace"] = _ws_after- if _ws_after and "design" not in _ws_after.lower():- e3["ok"] = False- e3["error"] = ("package3d frame captured in workspace %r, not the Design 3D "- "canvas - a 2D package sheet is not evidence of a 3D chip "- "(the workspace switch did not take)." % _ws_after)- chk = _count_3d_bodies()- e3["bodies"] = chk["bodies"]- e3["bboxMm3"] = chk["bboxMm3"]- if chk["bodies"] == 0:- e3["ok"] = False- e3["error"] = ("The bound f3d opened but contains NO bodies - an empty "- "viewport is not evidence of a 3D chip.")- elif chk["bodies"] < 0:- e3["bodiesUnverified"] = True- evidence.append(_emit(e3))- # HAND THE APP BACK (deterministic-close rule): the f3d doc is render scratch- # here; close it verified so stage 4 is not stranded in the wrong workspace.- # Same upload gate as _close_bridge_doc_verified: closing over an in-flight- # Hub upload raises the lose-changes modal (John saw it live, 2026-08-21).+ for _pk in (_pk_iter if artifacts["package3d"]["present"] and "package3d" not in _skip else []):+ # THE BOUND MODEL, NOT THE GENERATOR (caught by the bodies gate, run be27c38d034e:+ # Create3DPackage opens the empty Package3D GENERATOR for an already-bound library -+ # bodies=0, a frame of nothing). The evidence is the f3d the package3d urn points+ # at: open THAT document, which loads the real geometry the Library Manager shows.+ # THE URN LIVES IN wip_urn, NOT urn (run 7d4427d443f5): this bridge's own bind+ # writes `<package3d urn="" wip_urn="urn:adsk.wipprod:fs.file:vf...">` - urn stays+ # empty by the EAGLE-XML convention used everywhere else in this file. Reading only+ # `urn` made a bound library look unopenable ("no urn"). A caller (the create_library+ # chain) can also hand the bind result's urn straight in as f3dUrn.+ # THIS PART'S urn, not the library's last one (ten-part run 2934addbae6a, 2026-08-29):+ # this loop used to keep the LAST <package3d> in the file, so every part's 3D frame+ # opened the last-minted design (IN-S63BTG_3d) and "proved" a JST connector with a+ # picture of an LED. Resolve the deviceset -> its device's package -> the package3d+ # whose packageinstance names that package. The chain's f3dUrn is only a fallback+ # for a single-part library.+ _p3d_urn = ""+ _p3d_pkg = "" try:- _proxy_to_addin("run_modeling_script",- {"script": "import adsk.core, time\n"- "app=adsk.core.Application.get()\n"- "for _ in range(24):\n"- " if not app.hasActiveJobs: break\n"- " time.sleep(5)\n"- "app.activeDocument.close(False)\nresult={}"},- timeout=140)+ import xml.etree.ElementTree as _ET3+ _root3 = _ET3.parse(lbr).getroot()+ _want_l = str(want or "").strip().lower()+ if _pk:+ _p3d_pkg = _pk+ else:+ for _ds in _root3.iter("deviceset"):+ if str(_ds.get("name") or "").lower() == _want_l:+ for _dv in _ds.iter("device"):+ if _dv.get("package"):+ _p3d_pkg = _dv.get("package"); break+ break+ _all3 = list(_root3.iter("package3d"))+ for _p3 in _all3:+ _insts = [str(i.get("name") or "").lower() for i in _p3.iter("packageinstance")]+ if _p3d_pkg and _p3d_pkg.lower() in _insts:+ _p3d_urn = (_p3.get("urn") or _p3.get("wip_urn") or "").strip(); break+ if not _p3d_urn and len(_all3) == 1:+ _p3d_urn = (_all3[0].get("urn") or _all3[0].get("wip_urn") or "").strip() except Exception: pass- else:- evidence.append(_emit({"step": "package3d", "view": "3d-editor", "ok": False,- "error": "Could not open the bound f3d (%s): %s" % (- _p3d_urn[-30:] if _p3d_urn else "no urn",- str((opened3d or {}).get("error"))[:100])}))- elif "package3d" not in _skip:+ if not _p3d_urn and (args.get("f3dUrn") or "").strip() and not _p3d_pkg:+ _p3d_urn = (args.get("f3dUrn") or "").strip()+ opened3d = None+ if _p3d_urn:+ # A JUST-MINTED f3d may not be durably openable yet (page issue #47 failure+ # mode 3: bind success + thumbnail ready, open refused moments later). The+ # lineage commits asynchronously - retry over ~45s before failing the frame.+ import time as _t3r+ for _attempt in range(3):+ opened3d = dispatch_command("open_by_urn", {"urn": _p3d_urn})+ if opened3d.get("success"):+ break+ if _attempt < 2:+ print("[Fusion Bridge] bound f3d not openable yet (attempt %d) - "+ "waiting 15s for the lineage to commit" % (_attempt + 1))+ _t3r.sleep(15)+ if opened3d and opened3d.get("success"):+ # WORKSPACE GUARANTEE (#51, 2026-08-23): the minted f3d sometimes reopens into+ # the electronics PACKAGE workspace showing its 2D SHEET (s1 tPlace, layer+ # sidebar) instead of the 3D canvas - 7 of the matrix's 20 frames photographed+ # that sheet while prove stamped them 4/4. The active view is STICKY per-user+ # state, so never trust it: read DebugCommands.ActiveWorkspace, force the+ # Design (modeling) workspace when it is anything else, and normalize the+ # camera (goHome+fit - also fixes the two chip-on-edge captures). The frame+ # then records the workspace it was actually shot in, and cannot be ok unless+ # that workspace shows the 3D canvas.+ _ws_after = ""+ try:+ _wsfix = _proxy_to_addin("run_modeling_script", {"script": (+ "import adsk.core, time\n"+ "app=adsk.core.Application.get()\n"+ "def _ws():\n"+ " try: return app.executeTextCommand('DebugCommands.ActiveWorkspace').strip()\n"+ " except Exception: return ''\n"+ "before=_ws()\n"+ "if 'design' not in before.lower():\n"+ " try:\n"+ " w=app.userInterface.workspaces.itemById('FusionSolidEnvironment')\n"+ " if w: w.activate()\n"+ " adsk.doEvents(); time.sleep(2)\n"+ " except Exception: pass\n"+ "try:\n"+ " app.activeViewport.goHome(False)\n"+ " app.activeViewport.fit()\n"+ "except Exception: pass\n"+ "result={'before': before, 'after': _ws()}\n")}, timeout=60)+ _ws_after = str(((_wsfix.get("data") or {}).get("result") or {}).get("after") or "")+ except Exception:+ pass+ # THE RIGHT DOCUMENT, VERIFIED: the mint names the design "<package>_3d"; the+ # document in front must be that one, or the frame is a picture of something else.+ _doc3 = ""+ try:+ _st3 = _proxy_to_addin("get_app_state", {}, timeout=8)+ _doc3 = str((_st3.get("data") or {}).get("activeDocument") or "").replace("*", "").strip()+ except Exception:+ pass+ e3 = _prove_capture("package3d", "3d-editor")+ e3["package"] = _pk+ e3["activeDocument"] = _doc3+ e3["expectedDocument"] = ("%s_3d" % _p3d_pkg) if _p3d_pkg else None+ if _p3d_pkg and _doc3 and ("%s_3d" % _p3d_pkg).lower() not in _doc3.lower():+ e3["ok"] = False+ e3["error"] = ("wrong_document_captured: the 3D frame shows %r, not this part's "+ "design %r" % (_doc3, "%s_3d" % _p3d_pkg))+ e3["activeWorkspace"] = _ws_after+ if _ws_after and "design" not in _ws_after.lower():+ e3["ok"] = False+ e3["error"] = ("package3d frame captured in workspace %r, not the Design 3D "+ "canvas - a 2D package sheet is not evidence of a 3D chip "+ "(the workspace switch did not take)." % _ws_after)+ chk = _count_3d_bodies()+ e3["bodies"] = chk["bodies"]+ e3["bboxMm3"] = chk["bboxMm3"]+ if chk["bodies"] == 0:+ e3["ok"] = False+ e3["error"] = ("The bound f3d opened but contains NO bodies - an empty "+ "viewport is not evidence of a 3D chip.")+ elif chk["bodies"] < 0:+ e3["bodiesUnverified"] = True+ evidence.append(_emit(e3))+ # HAND THE APP BACK (deterministic-close rule): the f3d doc is render scratch+ # here; close it verified so stage 4 is not stranded in the wrong workspace.+ # Same upload gate as _close_bridge_doc_verified: closing over an in-flight+ # Hub upload raises the lose-changes modal (John saw it live, 2026-08-21).+ try:+ _proxy_to_addin("run_modeling_script",+ {"script": "import adsk.core, time\n"+ "app=adsk.core.Application.get()\n"+ "for _ in range(24):\n"+ " if not app.hasActiveJobs: break\n"+ " time.sleep(5)\n"+ "app.activeDocument.close(False)\nresult={}"},+ timeout=140)+ except Exception:+ pass+ else:+ evidence.append(_emit({"step": "package3d", "view": "3d-editor", "ok": False,+ "error": "Could not open the bound f3d (%s): %s" % (+ _p3d_urn[-30:] if _p3d_urn else "no urn",+ str((opened3d or {}).get("error"))[:100])}))+ if not artifacts["package3d"]["present"] and "package3d" not in _skip: evidence.append(_emit({"step": "package3d", "view": "3d-editor", "ok": False, "error": "No package3d bound - the 3D editor would only show Fusion's " "grey Placeholder, which is not evidence."}))@@ -11991,7 +12008,9 @@ def _handle_prove_library(fusion_info: dict, args: dict) -> dict: # of every message began - "Fusion offere[d ...]" - and cost the reporter a diagnosis) missing += ["frame:%s(%s)" % (e.get("step"), str(e.get("error"))) for e in evidence if not (e.get("ok") and e.get("screenshot"))]- _expected = 4 - len(_skip & {"symbol", "footprint", "package3d", "ganged"})+ _npk = max(1, len(_pkgs))+ _expected = ((0 if "symbol" in _skip else 1) + (0 if "ganged" in _skip else 1)+ + (0 if "footprint" in _skip else _npk) + (0 if "package3d" in _skip else _npk)) proven = (not missing) and len(captured) == _expected out = {"success": proven, "proven": proven, "libraryPath": lbr, "deviceset": want, "artifacts": artifacts, "missing": missing,