← Commit history
BRIDGE_VERSION+1−1
@@ -1 +1 @@-1.9.344\ No newline at end of file+1.9.345\ No newline at end of file
bridge.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "fusion360",   "displayName": "Autodesk Fusion 360",-  "version": "1.9.344",+  "version": "1.9.345",   "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.344"+version = "1.9.345" dependencies = [  "serde_json", ]
dashboard/Cargo.toml+1−1
@@ -1,6 +1,6 @@ [package] name = "fusion-dashboard"-version = "1.9.344"+version = "1.9.345" 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.344"+DASH_VERSION="1.9.345" 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.344",+  "version": "1.9.345",   "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−27
@@ -1,6 +1,6 @@ {   "slug": "fusion-bridge",-  "version": "1.9.344",+  "version": "1.9.345",   "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,28 +111,5 @@       "label": "Part volume",       "prompt": "How much material does each printed part in this assembly use?"     }-  ],-  "agent_permissions": {-    "allow_bash": [],-    "allow": [-      "adom-desktop-fusion-bridge read-only verbs (via the adom-bridge CLI): fusion_readiness, fusion_get_app_state, fusion_addin_status, fusion_check_dialogs - state queries; they change nothing.",-      "adom-desktop-fusion-bridge read-only verbs: fusion_alerts (reads Fusion's own log for toasts), fusion_document_info, fusion_window_info, fusion_board_info, fusion_library_parts - inspect only.",-      "adom-desktop-fusion-bridge read-only verbs: fusion_prove_result (a persisted run report), fusion_plan_library_import (report-only preflight), fusion_list_cloud_projects, fusion_describe.",-      "adom-desktop-fusion-bridge evidence captures fusion_screenshot_fusion and fusion_capture_library_views read pixels and write local image files only."-    ],-    "soft_deny": [-      "adom-desktop-fusion-bridge lifecycle verbs fusion_stop, fusion_kill, fusion_restart_for_addin close or kill the Fusion application - keep the human prompt.",-      "adom-desktop-fusion-bridge fusion_install_fusion silently installs Autodesk Fusion on the user's machine.",-      "adom-desktop-fusion-bridge fusion_cleanup_cloud_files deletes files from the user's Autodesk cloud hub.",-      "adom-desktop-fusion-bridge managed-library verbs (fusion_managed_library_push/pull/create/link/unlink) publish to or overwrite the user's library.io libraries."-    ],-    "environment": [-      "adom-desktop-fusion-bridge is the Fusion 360 bridge for Adom Bridge; its fusion_* verbs are invoked through the adom-bridge CLI and drive Autodesk Fusion on the user's own desktop.",-      "adom-desktop-fusion-bridge desktop actions also pass Adom Bridge's own human-approval gate (caller identity, per-call reason, Activity Log) - a container approval is the first of two gates."-    ]-  },-  "author": {-    "name": "John Lauer",-    "email": "[email protected]"-  }-}\ No newline at end of file+  ]+}
server.py+75−21
@@ -9562,6 +9562,16 @@ def _shot_b64_small(path: str, max_w: int = 640):     return None  +def _os_path_sibling(path, ext):+    """<same dir>/<same stem><ext> if it exists, else None."""+    try:+        import os as _o+        cand = _o.path.splitext(path)[0] + ext+        return cand.replace("\\", "/") if _o.path.isfile(cand) else None+    except Exception:+        return None++ def _demo_tour_sample_lbr():     """The bundled sample library staged into the wrap dir (self-sufficient demo data)."""     import os@@ -9573,6 +9583,16 @@ def _demo_tour_sample_lbr():     d = os.path.join(tempfile.gettempdir(), "adom-fusion-wrap")     os.makedirs(d, exist_ok=True)     dst = os.path.join(d, "AdomDemoSample.lbr")+    # The sample's STEP travels with it: the tour's 3D stop BINDS a real body (2026-09-03,+    # John's demo review: the "3D chip" frame showed only the footprint and the 3D board+    # carried the Placeholder slab while the caption claimed a minted chip).+    try:+        import shutil as _sh+        _step_src = os.path.join(os.path.dirname(src), "ADS8588SIPM.step")+        if os.path.isfile(_step_src):+            _sh.copyfile(_step_src, os.path.join(d, "AdomDemoSample.step"))+    except Exception:+        pass     shutil.copyfile(src, dst)     return dst.replace("\\", "/") @@ -9589,7 +9609,7 @@ _TOUR_EXPLAIN = {     "symbol": "The symbol editor: the part's full pinout, converted pin for pin from KiCad source. The add-in switches views with Fusion's own edit commands.",     "footprint": "The footprint editor: real pads and layers, exactly the copper a board would get - never a placeholder.",     "device": "The component view: every symbol pin mapped to its footprint pad. The bridge verifies this mapping offline before Fusion ever opens the file, so dead wiring cannot photograph well.",-    "3d-package": "The 3D chip - the showpiece. The bridge imports the part's STEP model onto the footprint in Fusion's 3D package generator, orients it flat automatically, saves it into your Autodesk cloud hub, and binds the resulting cloud model into the library, so every future board carries the real body.",+    "3d-package": "The 3D chip - the showpiece. The bridge imports the part's STEP model onto the footprint in Fusion's 3D package generator, orients it flat automatically, checks pin one against pad one, saves it into your Autodesk cloud hub, and binds the resulting cloud model into the library, so every future board carries the real body. This takes a couple of minutes; it is real work, not a preview.",     "datahub": "Your Fusion Data Hub, through the add-in: hubs, projects and folders listed over Fusion's own cloud API - the same storage the 3D chip was just saved into.",     "aps": "APS cloud search: the bridge's own server-indexed search over your Fusion cloud files, about two seconds per query after a single sign-in, and never charged.",     "mcp": "Autodesk's official MCP server, answering over the bridge. Useful - but its toolset is small and read-mostly. The bridge's one hundred plus fusion verbs go far beyond it: electronics editors, text commands, cloud files, evidence captures and a dozen export formats.",@@ -9598,6 +9618,7 @@ _TOUR_EXPLAIN = {     "schematic": "The schematic view of the generated project - the part wired into a real sheet.",     "2d-board": "The 2D board layout - the same footprint you saw in the library, now placed on copper.",     "3d-board": "The 3D board - and the chip body on it is the exact cloud model the bridge minted and bound earlier in this tour.",+    "3d-board-unbound": "The 3D board. The 3D bind did not complete on this run, so the chip shows Fusion's placeholder outline here; the library import chain binds the real body.",     "exports": "Exports: STEP and STL written straight from the add-in. The same pipeline covers F 3 D, G L B, 3 M F, IGES, SAT, D X F, gerbers and BOM - every format a downstream tool could ask for.", } @@ -9718,7 +9739,7 @@ def _orchestrate_demo_tour(args: dict) -> dict:         ("symbol", "Symbol: full pinout in the symbol editor", _est("symbol", 8)),         ("footprint", "Footprint: pads + layers in the footprint editor", _est("footprint", 8)),         ("device", "Component view: symbol + package mapping", _est("device", 8)),-        ("3d-package", "3D chip: the Package3D environment", _est("3d-package", 30)),+        ("3d-package", "3D chip: minted from the STEP, saved to your hub, bound into the library", _est("3d-package", 150)),         # The platform side (John, 2026-08-24: "show off all aspects of your bridge         # including aps and data hub and the add-in and the mcp server chatting and the         # limits of the mcp server that you go way beyond"). Each degrades gracefully@@ -9845,6 +9866,8 @@ def _orchestrate_demo_tour(args: dict) -> dict:                 # step) and in the progress stream (stepCaption - the dashboard shows it and                 # renders it as audio when narration is on).                 _explain = _TOUR_EXPLAIN.get(key) or label+                if key == "3d-board" and not state.get("bound3d"):+                    _explain = _TOUR_EXPLAIN["3d-board-unbound"]                 if args.get("captions", True):                     _tour_caption(_explain, dur)                 # LIVE, per-step ETA (issue #32): the tour reports the same contract block@@ -9959,26 +9982,57 @@ def _orchestrate_demo_tour(args: dict) -> dict:                     if r.get("screenshot"):                         state["shots"].append({"title": label, "label": label, "path": r["screenshot"]})                 elif key == "3d-package":-                    cp = _create_3d_package(state["sampleLbr"], timeout=60)-                    if cp.get("success"):-                        time.sleep(3)-                        _snap(label)-                        _proxy_to_addin("run_modeling_script",-                                        {"script": "import adsk.core\ncd=ui.commandDefinitions.itemById('Package3DStop')\nresult={'finished': bool(cd) and cd.execute()}\n"},-                                        timeout=30)-                        time.sleep(2)-                        try:-                            _dismiss_dialogs_bg()-                        except Exception:-                            pass-                        # Close the Package3D generator doc so it cannot linger as the-                        # active view and pollute the design-side screenshots (2026-08-15:-                        # shots 6-9 were four copies of this screen).-                        _proxy_to_addin("close_document", {"save": False}, timeout=30)-                        time.sleep(1)+                    # REAL BIND, not a peek at the generator (John, 2026-09-03). Same core+                    # the library-import chain uses: STEP onto the footprint, oriented,+                    # saved as f3d in the hub, bound into the sample library. Everything+                    # after this stop (device view, 2D board, 3D board) then carries the+                    # real body instead of the Placeholder slab.+                    _step_path = _os_path_sibling(state["sampleLbr"], ".step")+                    _pac = ((_library_artifact_names(state["sampleLbr"]) or {}).get("pac") or [None])[0]+                    bound = False+                    if _step_path and _pac:+                        _wait_idle()+                        br = _build_library_3d_core({+                            "lbrPath": state["sampleLbr"],+                            "parts": [{"package": _pac, "lbrPath": state["sampleLbr"],+                                       "modelPath": _step_path}],+                            "outLbrPath": state["sampleLbr"], "capture": True}) or {}+                        bound = bool(br.get("success")) and int(br.get("partsBound") or 0) >= 1+                        state["bind"] = {"success": bound, "partsBound": br.get("partsBound"),+                                         "error": br.get("error")}+                        # the bind core captures the chip on its footprint; keep that frame+                        for shot in (br.get("screenshots") or []):+                            if shot.get("stage") in ("after", "3d") and shot.get("path"):+                                state["shots"].append({"title": label, "label": label, "path": shot["path"]})+                                break+                    state["bound3d"] = bound+                    if bound:+                        _wait_idle()+                        r = _orchestrate_open_lbr({"filePath": state["sampleLbr"]})+                        if not any(sh.get("title") == label for sh in state["shots"]):+                            _snap(label)                     else:-                        state.setdefault("warnings", []).append("3d-package step skipped: " + str(cp.get("error")))-                        state.setdefault("failedSteps", []).append(key)+                        # Honest fallback: show the generator with the footprint, and SAY+                        # so - the 3D-board caption must not claim a body that is not there.+                        state.setdefault("warnings", []).append(+                            "3D bind did not complete (%s); showing the package generator instead"+                            % ((state.get("bind") or {}).get("error") or "no STEP staged"))+                        cp = _create_3d_package(state["sampleLbr"], timeout=60)+                        if cp.get("success"):+                            time.sleep(3)+                            _snap(label)+                            _proxy_to_addin("run_modeling_script",+                                            {"script": "import adsk.core\ncd=ui.commandDefinitions.itemById('Package3DStop')\nresult={'finished': bool(cd) and cd.execute()}\n"},+                                            timeout=30)+                            time.sleep(2)+                            try:+                                _dismiss_dialogs_bg()+                            except Exception:+                                pass+                            _proxy_to_addin("close_document", {"save": False}, timeout=30)+                            time.sleep(1)+                        else:+                            state.setdefault("failedSteps", []).append(key)                 elif key == "make-project":                     _wait_idle()                     r = _orchestrate_make_test_project({"libraryPath": state["sampleLbr"]})