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)
10 files changed
+107−22
BRIDGE_VERSION+1−1@@ -1 +1 @@-1.9.276+1.9.277
adom-bridge-fusion-manifest.json+4−4@@ -1,10 +1,10 @@ { "manifest_version": 1, "name": "fusion360",- "version": "1.9.276",- "url": "https://wiki.adom.inc/download/adom/fusion-bridge/1.9.276/adom-bridge-fusion-v1.9.276.zip",- "sha256": "f255b3ef27ee8a74ffcc95559b5555f760e347fb1aa50d9eea618ba7b1c4cd16",- "size": 568530,+ "version": "1.9.277",+ "url": "https://wiki.adom.inc/download/adom/fusion-bridge/1.9.277/adom-bridge-fusion-v1.9.277.zip",+ "sha256": "37a9a0017a7ac7beba3b48ffa7f9b87214e31b8b0910c6ecb3fc64079743a9aa",+ "size": 569762, "verbPrefixes": [ "fusion_" ],
bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "fusion360", "displayName": "Autodesk Fusion 360",- "version": "1.9.276",+ "version": "1.9.277", "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.276"+version = "1.9.277" dependencies = [ "serde_json", ]
dashboard/Cargo.toml+1−1@@ -1,6 +1,6 @@ [package] name = "fusion-dashboard"-version = "1.9.276"+version = "1.9.277" edition = "2021" [dependencies]
fusion_detect.py+65−2@@ -321,8 +321,71 @@ def _is_fusion_running() -> bool: return _has_fusion_process() +_FUSION_RUNNING_MEMO = {"value": None, "at": 0.0}+++def _fusion_pids_toolhelp() -> list:+ """PIDs of Fusion360.exe via a Toolhelp32 snapshot: no child process, no console,+ milliseconds even when the CPU is pegged. Empty list on any failure."""+ try:+ import ctypes+ from ctypes import wintypes+ k32 = ctypes.windll.kernel32+ TH32CS_SNAPPROCESS = 0x2++ class PE32(ctypes.Structure):+ _fields_ = [("dwSize", wintypes.DWORD), ("cntUsage", wintypes.DWORD),+ ("th32ProcessID", wintypes.DWORD),+ ("th32DefaultHeapID", ctypes.POINTER(ctypes.c_ulong)),+ ("th32ModuleID", wintypes.DWORD), ("cntThreads", wintypes.DWORD),+ ("th32ParentProcessID", wintypes.DWORD), ("pcPriClassBase", ctypes.c_long),+ ("dwFlags", wintypes.DWORD), ("szExeFile", ctypes.c_char * 260)]+ snap = k32.CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)+ if snap == wintypes.HANDLE(-1).value or not snap:+ return []+ try:+ pe = PE32(); pe.dwSize = ctypes.sizeof(PE32)+ out = []+ ok = k32.Process32First(snap, ctypes.byref(pe))+ while ok:+ if pe.szExeFile.decode(errors="ignore").lower() == "fusion360.exe":+ out.append(int(pe.th32ProcessID))+ ok = k32.Process32Next(snap, ctypes.byref(pe))+ return out+ finally:+ k32.CloseHandle(snap)+ except Exception:+ return []++ def _has_fusion_process() -> bool:- """Check if any Fusion 360 process is running via tasklist."""+ """Check if any Fusion 360 process is running.++ ⛔ A FALSE "NOT RUNNING" LAUNCHES A SECOND FUSION (2026-08-29, John saw Fusion's+ "Multiple instances are not supported" dialog mid-import): the tasklist probe below+ timed out while Fusion pegged the CPU importing a STEP, the except path answered+ False, and the dispatcher's auto-launch started another instance. So: a snapshot+ probe that cannot stall comes first, tasklist is the confirmation, and a probe that+ FAILS answers with the last known state, never False.+ """+ try:+ _pids = _fusion_pids_toolhelp()+ if _pids and any(_pid_alive(p) for p in _pids):+ _FUSION_RUNNING_MEMO.update(value=True, at=time.time())+ return True+ except Exception:+ pass+ try:+ r = _has_fusion_process_tasklist()+ _FUSION_RUNNING_MEMO.update(value=r, at=time.time())+ return r+ except Exception:+ last = _FUSION_RUNNING_MEMO.get("value")+ return bool(last) if last is not None else False+++def _has_fusion_process_tasklist() -> bool:+ """tasklist-based check (raises on timeout so the caller can fall back).""" try: # CREATE_NO_WINDOW (0x08000000) on Windows so the child cmd window doesn't # flash on screen for every poll. The Tauri side spawns the Python bridge@@ -368,7 +431,7 @@ def _has_fusion_process() -> bool: return "Fusion360.exe" in output # cannot parse -> old behaviour return any(_pid_alive(p) for p in pids) except Exception:- return False+ raise # the caller answers with the last known state, never a guessed False _STILL_ACTIVE = 259
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.276"+DASH_VERSION="1.9.277" 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.276",+ "version": "1.9.277", "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.276",+ "version": "1.9.277", "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+21−2@@ -13043,13 +13043,19 @@ def _handle_create_library(fusion_info: dict, args: dict) -> dict: # save settled, so part 1 of 10 scored not_proven on library_not_active and the other # nine passed. That is a timing miss, not a bad part: re-prove exactly those parts once, # after the rest, when the library has long been active.+ # Also retried: add-in timeouts and "not responding" (run 6aeb6f853c17, 2026-08-29:+ # CH340G and IN-S63BTG missed on addin_command_timeout while Fusion was sluggish and+ # passed nothing else wrong). Those are timing misses too; a real geometry failure+ # says so in its error and is NOT retried.+ _transient = ("ACTIVE document", "addin_command_timeout", "not responding",+ "main thread busy", "main_thread_busy") _retry = [r for r in _prove_results if not r["proven"]- and "ACTIVE document" in str(r.get("error") or "")]+ and any(k in str(r.get("error") or "") for k in _transient)] for _rr in _retry: if _cancel_requested(run_id): break _dev = _rr["part"]- state["stageLabel"] = "re-proving %s (the library was not active on its first pass)" % _dev+ state["stageLabel"] = "re-proving %s (its first pass hit a timing miss, not a part fault)" % _dev _save() def _frame_for_retry(entry, _d=_dev): entry = dict(entry); entry["part"] = _d; entry["kind"] = "proof"; entry["retry"] = True@@ -13752,6 +13758,19 @@ def _dispatch_command_inner(command: str, args: dict, caller_identity: dict = No "errorCode": "fusion_not_installed", "_hint": "Fusion 360 isn't installed. Do NOT tell the user to install it themselves - OFFER to install it FOR them and do it on a yes: the fusion-onboarding skill silent-installs Fusion + drives the Autodesk sign-in.", }+ if not fusion_running:+ # SECOND OPINION BEFORE A LAUNCH (2026-08-29): a launch on a false "not running"+ # starts a SECOND Fusion ("Multiple instances are not supported" in the user's+ # face, mid-import). A probe can miss while Fusion pegs the CPU; the add-in+ # answering on its port, or a Fusion main window, proves it is up. Re-ask twice+ # over ~3s and only launch when every signal still says closed.+ import time as _sop+ from fusion_detect import _fusion_main_hwnd_quick as _hwnd_quick+ for _ in range(2):+ _sop.sleep(1.5)+ if _is_fusion_running() or _probe_addin(timeout=1.5) or _hwnd_quick():+ fusion_running = True+ break if not fusion_running: # AUTO-OPEN, DO NOT REFUSE (page issue #64, 2026-08-26). # CLAUDE.md's automation doctrine: "If Fusion is closed, launch it ... Never