← Commit history
BRIDGE_VERSION+1−1
@@ -1 +1 @@-1.9.293\ No newline at end of file+1.9.294\ No newline at end of file
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]"-  }-}\ No newline at end of file+  ]+}
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,