← Commit history
BRIDGE_VERSION+1−1
@@ -1 +1 @@-1.9.302\ No newline at end of file+1.9.304\ 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.302",-  "url": "https://wiki.adom.inc/download/adom/fusion-bridge/1.9.302/adom-bridge-fusion-v1.9.302.zip",-  "sha256": "844177c1ad593395afe023e808782ee49b9280276f70d67645f24c4d608003bf",-  "size": 587028,+  "version": "1.9.304",+  "url": "https://wiki.adom.inc/download/adom/fusion-bridge/1.9.304/adom-bridge-fusion-v1.9.304.zip",+  "sha256": "b5e2b0d5a019b95a8cc6db19c141da13e1b44b3f3977ab0edda5e5ccd2cb8140",+  "size": 587660,   "verbPrefixes": [     "fusion_"   ],
bridge.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "fusion360",   "displayName": "Autodesk Fusion 360",-  "version": "1.9.302",+  "version": "1.9.304",   "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.302"+version = "1.9.304" dependencies = [  "serde_json", ]
dashboard/Cargo.toml+1−1
@@ -1,6 +1,6 @@ [package] name = "fusion-dashboard"-version = "1.9.302"+version = "1.9.304" edition = "2021"  [dependencies]
describe.py+1−1
@@ -284,7 +284,7 @@ _T = [     ("fusion_live_feed", "A ~1 fps look INTO Fusion while the bridge drives it, kept as a BOX-SIDE ring buffer (~10 min). Each tick records the Fusion main window (kind:main - the video) and every owned popup over it (kind:popup - dialogs, Browser/tool panels, the Adom banner; Fusion draws these as separate windows a main capture omits), each a path on this machine tied to its tick. Drain from the container with {action:frames, since:<tick>}, read_file each path, and publish where a human looks - tools/live_feed_drain.py does exactly that into shotlog. Proof frames are NOT here: fusion_prove_result evidence[] carries kind:proof with step labels, so the three kinds are distinguishable at the source. Exists so a human can see open tabs and dialogs during boot or a restart instead of guessing.",      {"action": "start|stop|status|frames", "intervalSec": "optional float (default 1.0)", "maxSec": "optional float (default 600)", "since": "frames only: return ticks newer than this"},      {"success": "bool", "running": "bool", "tick": "int", "frames": "[{tick, kind:main|popup, path, title, at}]"}, 15, None, True, {"action": "frames", "since": 0}),-    ("fusion_restart_for_addin", "Finish add-in setup by restarting Fusion, SAVING the user's open documents first. Call this when any verb returns errorCode addin_restart_required (the add-in was installed/updated while Fusion was already open, and Fusion only loads add-ins at launch). ASK THE USER FIRST when a human is present - the failing verb's payload carries userMessage plus openDocuments/unsavedDocuments so you can show them what is open. Refuses with unsaved_named_document rather than discarding a never-saved document.",+    ("fusion_restart_for_addin", "ASYNC: returns immediately (started:true) and restarts Fusion in the background, SAVING the user's open documents first; poll fusion_readiness until ready with the new addinVersion (a restart takes 60-120s, longer than the relay budget; pass wait:true for the old blocking form). Finish add-in setup by restarting Fusion. Call this when any verb returns errorCode addin_restart_required (the add-in was installed/updated while Fusion was already open, and Fusion only loads add-ins at launch). ASK THE USER FIRST when a human is present - the failing verb's payload carries userMessage plus openDocuments/unsavedDocuments so you can show them what is open. Refuses with unsaved_named_document rather than discarding a never-saved document.",      {"save": "optional bool (default true) - save open documents before restarting"},      {"success": "bool", "addinReady": "bool", "tookSec": "float", "steps": "[{step, ok}]"},      300, None, False, {"save": True}),
docs/3d-viewer-and-export.md+1−1
@@ -45,7 +45,7 @@ Result: about **465 KB and ~31 draw calls**. That is the difference between a pa one that hangs.  ```bash-adom-bridge fusion_export_optimized_glb '{"outputPath":"C:/tmp/board.glb","silkscreen":true}'+adom-bridge fusion_export_optimized_glb '{"outputPath":"%TEMP%/board.glb","silkscreen":true}' adom-bridge fusion_fetch_optimized_glb  '{"jobId":"..."}'   # finish a big board ``` 
docs/board-layout-2d.md+3−3
@@ -63,9 +63,9 @@ JLCPCB CAM jobs ship with the bridge in `resources/`, so gerbers come out in the rather than a generic set you have to fix.  ```bash-adom-bridge fusion_export_gerbers '{"outputPath":"C:/tmp/fab"}'-adom-bridge fusion_export_bom     '{"outputPath":"C:/tmp/bom.csv"}'-adom-bridge fusion_export_cpl     '{"outputPath":"C:/tmp/cpl.csv"}'+adom-bridge fusion_export_gerbers '{"outputPath":"%TEMP%/fab"}'+adom-bridge fusion_export_bom     '{"outputPath":"%TEMP%/bom.csv"}'+adom-bridge fusion_export_cpl     '{"outputPath":"%TEMP%/cpl.csv"}' ```  ### `fusion_export_bom` is electronics-only
docs/electronics-libraries.md+4−4
@@ -34,7 +34,7 @@ reaches a board: ## Opening and inspecting  ```bash-adom-bridge fusion_open_lbr '{"lbrPath":"C:/tmp/AdomBasicParts.lbr"}'+adom-bridge fusion_open_lbr '{"lbrPath":"%TEMP%/AdomBasicParts.lbr"}' ```  `.lbr` is the EAGLE library format Fusion Electronics uses.@@ -54,10 +54,10 @@ library. ```bash adom-bridge fusion_make_3d_package   '{...}'      # build one adom-bridge fusion_attach_3d_package '{...}'      # bind it to a footprint-adom-bridge fusion_build_library_3d  '{"lbrPath":"C:/tmp/newlib/AdomBasicParts2.lbr",+adom-bridge fusion_build_library_3d  '{"lbrPath":"%TEMP%/newlib/AdomBasicParts2.lbr",                                         "parts":[{"package":"R_4k7",-                                                  "lbrPath":"C:/tmp/newlib/R_4k7.lbr",-                                                  "modelPath":"C:/tmp/newlib/R_4k7.step"}]}'+                                                  "lbrPath":"%TEMP%/newlib/R_4k7.lbr",+                                                  "modelPath":"%TEMP%/newlib/R_4k7.step"}]}' adom-bridge fusion_capture_library_views '{...}'  # render views for a wiki page ``` 
docs/mechanical-bom.md+2−2
@@ -44,7 +44,7 @@ stops at a physical part or a purchased kit. ```bash adom-bridge fusion_assembly_bom '{}' adom-bridge fusion_assembly_bom '{"treatAsUnit":["with fasteners","bearing unit"],-                                   "outputPath":"C:/tmp/bom.csv"}'+                                   "outputPath":"%TEMP%/bom.csv"}' ```  | arg | meaning |@@ -95,7 +95,7 @@ the work is still running on the bridge; poll rather than treating it as failure  ```bash # 1. structured parts list, CSV for purchasing-adom-bridge fusion_assembly_bom '{"outputPath":"C:/tmp/bom.csv"}'+adom-bridge fusion_assembly_bom '{"outputPath":"%TEMP%/bom.csv"}'  # 2. volumes only for the printed parts, to price filament adom-bridge fusion_physical_properties '{"names":["Spool Holder","Guide Block"]}'
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.302"+DASH_VERSION="1.9.304" 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.302",+  "version": "1.9.304",   "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.302",+  "version": "1.9.304",   "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+24
@@ -15505,6 +15505,30 @@ def _handle_restart_for_addin(fusion_info: dict, args: dict) -> dict:         return {"success": True, "declined": True, "hold": True,                 "_hint": "Recorded: the user owns the restart. Any chain waiting on the "                          "notice stops with user_owns_restart; nothing restarts Fusion."}+    # ASYNC BY DEFAULT (Ray, adom/hydrogen#42): a real Fusion restart takes longer than the+    # relay's 60s budget, so the synchronous form ALWAYS reported "timeout" even when it+    # worked, and a caller could not tell "restarting, wait" from "failed". The verb now+    # returns immediately and does the work on a thread; poll fusion_readiness until+    # ready:true with the expected addinVersion. Pass wait:true for the old blocking form+    # (in-process callers use that).+    if not args.get("wait") and not args.get("_sync"):+        import threading as _rth, json as _rjs+        def _bg():+            try:+                r = _handle_restart_for_addin(fusion_info, dict(args, wait=True))+                print("[Fusion Bridge] async restart_for_addin finished: %s"+                      % _rjs.dumps({k: r.get(k) for k in ("success", "errorCode", "error")})[:200])+                _set_bridge_pref("lastRestartResult", {k: r.get(k) for k in ("success", "errorCode", "error")})+            except Exception as e:+                print("[Fusion Bridge] async restart_for_addin crashed: %s" % str(e)[:200])+        _rth.Thread(target=_bg, daemon=True).start()+        return {"success": True, "started": True, "async": True,+                "statusVerb": "fusion_readiness",+                "_hint": ("Restart under way in the background (a Fusion restart takes 60-120s, "+                          "longer than the relay budget, so this returns immediately). Poll "+                          "fusion_readiness until ready:true and addinVersion matches; the "+                          "outcome is also stored in the lastRestartResult preference. A timeout "+                          "on this verb no longer exists and never meant failure anyway.")}     import time as _t     save = args.get("save", True)     t0 = _t.time()
skills/fusion-build-parts-library/SKILL.md+1−1
@@ -91,7 +91,7 @@ exports STEP in ONE call. Params are datasheet **mm** (auto-converted). markings are separate `Marking` bodies (white in Fusion and in the colored STEP).  ```bash-adom-bridge fusion_generate_package '{"type":"soic","params":{"DPins":8,"D":4.9,"E":6.0,"E1":3.9,"e":1.27,"A":1.6,"A1":0.1,"b":0.41,"L":0.84},"etch":"LM358","outputStep":"C:/tmp/ADOM-LM358-SOIC8.step"}'+adom-bridge fusion_generate_package '{"type":"soic","params":{"DPins":8,"D":4.9,"E":6.0,"E1":3.9,"e":1.27,"A":1.6,"A1":0.1,"b":0.41,"L":0.84},"etch":"LM358","outputStep":"%TEMP%/ADOM-LM358-SOIC8.step"}' ```  Read the verb's `_hint` on every call - it carries the band/margin/contrast rules and the per-family
skills/fusion-driving/SKILL.md+1−1
@@ -104,7 +104,7 @@ ways to get the array, in order of preference: - ab core `desktop_screenshot_window {hwnd}` saves the PNG **into your container** (e.g.   `/tmp/adom-desktop-screenshots/`) - Read it directly. - The bridge's own captures (the auto `dialogArray[].screenshot` paths and `fusion_screenshot_all`)-  are saved on the **Windows box** (`C:/tmp/conduit-screenshots/`). To see one, either re-capture that+  are saved on the **Windows box** (`%TEMP%/conduit-screenshots/`). To see one, either re-capture that   hwnd with `desktop_screenshot_window {hwnd}` (lands in your container) or `desktop_pull_file` it over.  Either way, LOOK - do not describe it from memory.
skills/fusion-molecule-export/SKILL.md+2−2
@@ -93,13 +93,13 @@ request timeout), so it is submit-then-poll, never one long blocking call: ``` # 1) export STEP + submit to the optimizer (returns fast with a jobId; small boards may #    already come back complete within `wait` seconds)-fusion_export_optimized_glb {"outputPath":"C:/tmp/board.glb", "silkscreen":true, "pin":"medium", "wait":15}+fusion_export_optimized_glb {"outputPath":"%TEMP%/board.glb", "silkscreen":true, "pin":"medium", "wait":15} #    -> { pending:true, jobId:"...", stepPath, resultUrl }   (big board) #    -> { status:"complete", glbPath, sizeBytes, meshesAfter, moleculeAnchored }  (small board)  # 2) finish it: re-call until status=complete (each call polls ~15s, well under the relay #    timeout, and writes the GLB on the box when ready). No Fusion needed for this verb.-fusion_fetch_optimized_glb {"jobId":"...", "outputPath":"C:/tmp/board.glb", "wait":15}+fusion_fetch_optimized_glb {"jobId":"...", "outputPath":"%TEMP%/board.glb", "wait":15} #    -> { status:"complete", glbPath, sizeBytes }   then pull_file it. ``` 
skills/fusion-multipart-libraries/SKILL.md+2−2
@@ -51,7 +51,7 @@ fusion_build_library_3d {   not have to `fusion_aps_browse` for them. - Returns `{partsBound, partsTotal, parts:[{package,success,wip_urn,dims_mm,error}], screenshots:   [{package,stage,path}], boundLbr, opened}`. The before/after PNGs land on the box-  (`C:/tmp/conduit-screenshots`) - `desktop_pull_file` them for a demo video.+  (`%TEMP%/conduit-screenshots`) - `desktop_pull_file` them for a demo video. - **Each part is independent.** If Fusion resets mid-run, the parts already done kept their f3d   uploads; just re-run the whole `parts[]` (re-making a few f3ds is cheap) - the injection rewrites   all bindings in one pass, so a single final call always yields a complete library.@@ -64,7 +64,7 @@ still running server-side and finishes fine.** Do NOT treat that as failure. Ins  1. Wait, then **verify by the artifacts, not the response**: read the `boundLbr` and count    `<package3d name=` (should equal your part count), and list the before/after PNGs in-   `C:/tmp/conduit-screenshots`.+   `%TEMP%/conduit-screenshots`. 2. If a part is missing (the FIRST part of a run is the usual casualty, as Fusion settles from a prior    view), **just re-run `fusion_build_library_3d` for the missing parts with `outLbrPath` pointing at    the SAME file.** The binding injection is **merge-aware + idempotent** (v1.6.1+): it reads the
skills/fusion-onboarding/SKILL.md+2−2
@@ -34,7 +34,7 @@ The installer is the small "Fusion Client Downloader" (it streams the full app d ``` URL: https://dl.appstreaming.autodesk.com/production/installers/Fusion Client Downloader.exe ```-Download it to a no-space path on the laptop (e.g. C:\tmp\):+Download it to a no-space path on the laptop (e.g. %TEMP%\): ``` shell_execute {"command":"curl -L -o C:\\tmp\\FusionDL.exe \"https://dl.appstreaming.autodesk.com/production/installers/Fusion%20Client%20Downloader.exe\""} ```@@ -42,7 +42,7 @@ shell_execute {"command":"curl -L -o C:\\tmp\\FusionDL.exe \"https://dl.appstrea  ## Step 2 — Silent install (elevated) ```-"C:\tmp\FusionDL.exe" --globalinstall --quiet      # system-wide, no UI+"%TEMP%\FusionDL.exe" --globalinstall --quiet      # system-wide, no UI ``` - Needs **admin/elevation** — run it so it elevates (UAC). On the user's own box this is one   consent. It then **streams ~several GB**, so it can take 10-30 min — **monitor, don't block**
tools/release.sh+5−1
@@ -279,7 +279,11 @@ except Exception: print('')" 2>/dev/null)   echo "  page moved since our last push: $LAST_MSG"   case "$LAST_MSG" in     "Publish "*|"Update "*) echo "  (our own release pattern - continuing)" ;;-    *) die "the page repo changed since our last push (\"$LAST_MSG\") - someone merged a PR or edited it. PULL those changes into this tree FIRST, or this push reverts them. RELEASE_FORCE=1 only once you have confirmed there is nothing to lose." ;;+    *) if [ "${RELEASE_FORCE:-0}" = "1" ]; then+         echo "  page-head guard BYPASSED by RELEASE_FORCE=1 (head: \"$LAST_MSG\")"+       else+         die "the page repo changed since our last push (\"$LAST_MSG\") - someone merged a PR or edited it. PULL those changes into this tree FIRST, or this push reverts them. RELEASE_FORCE=1 only once you have confirmed there is nothing to lose."+       fi ;;   esac else   echo "  page head unchanged since our last push"