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 9 file(s)
9 files changed
+143−18
BRIDGE_VERSION+1−1@@ -1 +1 @@-1.9.89+1.9.90
adom-bridge-fusion-manifest.json+4−4@@ -1,10 +1,10 @@ { "manifest_version": 1, "name": "fusion360",- "version": "1.9.89",- "url": "https://wiki.adom.inc/download/adom/fusion-bridge/1.9.104/adom-bridge-fusion-v1.9.89.zip",- "sha256": "26036f69bd05b2f09c238fa337230ff96fc5cf230c31cf6a928f548abf60cfc7",- "size": 426798,+ "version": "1.9.90",+ "url": "https://wiki.adom.inc/download/adom/fusion-bridge/1.9.105/adom-bridge-fusion-v1.9.90.zip",+ "sha256": "f8bb0b9e3d0392f795cf7d86d6895f821f7db1a09f4797bfb9e5c8a6368f29a1",+ "size": 428521, "verbPrefixes": [ "fusion_" ],
bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "fusion360", "displayName": "Autodesk Fusion 360",- "version": "1.9.89",+ "version": "1.9.90", "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.toml+1−1@@ -1,6 +1,6 @@ [package] name = "fusion-dashboard"-version = "1.9.104"+version = "1.9.105" edition = "2021" [dependencies]
handlers/fusion_ui.py+58@@ -953,3 +953,61 @@ def ensure_reasonable_window(fraction: float = 0.92, hwnd=None) -> dict: "why": "grew to %d%% of the work area without activating it" % int(fraction * 100)} except Exception as e: return {"sized": False, "why": "error: %s" % str(e)[:120]}+++def is_fusion_hung() -> dict:+ """Is the Fusion HOST PROCESS hung, as opposed to the add-in being down?++ They need OPPOSITE responses from a caller (page issue #38): an add-in that is down+ can be reloaded by restarting Fusion politely; a process that has stopped pumping its+ message queue cannot be reached over IPC at all, so nothing but a forced restart helps.+ Reporting the second as the first sent a reader hunting a blocking dialog that did not+ exist.++ Two signals, cheap and independent:+ - IsHungAppWindow: the OS's own answer, which is what the shell uses.+ - a top-level window of class "Ghost": the placeholder Windows substitutes for a+ non-pumping window, so its presence is corroboration that survives a racy probe.+ Returns {hung, signals[], hwnd}. Never raises - a detector that can throw is worse+ than no detector.+ """+ out = {"hung": False, "signals": [], "hwnd": None}+ try:+ hwnd = _find_fusion_hwnd()+ out["hwnd"] = hwnd or None+ if hwnd:+ try:+ if bool(user32.IsHungAppWindow(hwnd)):+ out["hung"] = True+ out["signals"].append("IsHungAppWindow")+ except Exception:+ pass+ # Ghost-class corroboration: enumerate top-level windows and look at the class.+ ghosts = []+ WNDENUMPROC_G = ctypes.WINFUNCTYPE(ctypes.c_bool, ctypes.wintypes.HWND,+ ctypes.wintypes.LPARAM)++ def cb(h, _l):+ try:+ buf = ctypes.create_unicode_buffer(64)+ user32.GetClassNameW(h, buf, 64)+ if buf.value == "Ghost" and user32.IsWindowVisible(h):+ t = ctypes.create_unicode_buffer(256)+ user32.GetWindowTextW(h, t, 256)+ if "Fusion" in (t.value or ""):+ ghosts.append(h)+ except Exception:+ pass+ return True++ c = WNDENUMPROC_G(cb)+ _callbacks.append(c)+ user32.EnumWindows(c, 0)+ _callbacks.remove(c)+ if ghosts:+ out["hung"] = True+ out["signals"].append("GhostWindow")+ out["ghostHwnds"] = ghosts+ except Exception:+ pass+ return out
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.104"+DASH_VERSION="1.9.105" 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.104",+ "version": "1.9.105", "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+10−7@@ -1,6 +1,6 @@ { "slug": "fusion-bridge",- "version": "1.9.104",+ "version": "1.9.105", "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",@@ -70,7 +74,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.",@@ -99,9 +106,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+66−2@@ -2733,14 +2733,50 @@ def _diagnose_addin_timeout(command: str, original_result: dict) -> dict: }, } - # Health probe failed — add-in HTTP server is down+ # Health probe failed. TWO DIFFERENT FAULTS LIVE HERE and they need opposite+ # responses (page issue #38): the add-in being down is recoverable over IPC, a HUNG+ # HOST PROCESS is not. Reporting the second as the first sent a reader hunting a+ # blocking dialog that did not exist.+ try:+ from handlers.fusion_ui import is_fusion_hung+ _hung = is_fusion_hung()+ except Exception:+ _hung = {"hung": False, "signals": []}+ if _hung.get("hung"):+ # DOCTRINE: this is mechanically fixable and the user should never have to do it.+ # A hung process will not close politely, so go straight to the force path.+ _healed = False+ try:+ dispatch_command("kill", {})+ _t3 = __import__("time"); _t3.sleep(6)+ dispatch_command("start", {})+ _healed = True+ except Exception:+ pass+ return {+ "success": False,+ "error": "Fusion 360 itself is NOT RESPONDING (the host process stopped pumping "+ "its message queue), so the add-in cannot answer.",+ "errorCode": "fusion_host_hung",+ "hungSignals": _hung.get("signals"),+ "ghostWindow": bool("GhostWindow" in (_hung.get("signals") or [])),+ "restartAttempted": _healed,+ "_hint": ("This is NOT a blocking dialog and NOT an add-in crash - do not go "+ "looking for a modal, there is not one. Nothing reaches Fusion over IPC "+ "while it is hung; only a restart clears it. "+ + ("The bridge already force-restarted it (kill + start) - poll "+ "fusion_readiness until ready:true." if _healed else+ "Restart it with fusion_kill then fusion_start.")),+ } return { "success": False, "error": "AdomBridge add-in not responding (it may have crashed).", "errorCode": "fusion_addin_not_responding",+ "hostResponding": True, "_hint": "Fix it YOURSELF - never ask the user: restart Fusion via fusion_stop + " "fusion_start (the bridge installs the add-in to ALL Fusion add-in dirs incl. "- "%APPDATA%\\Autodesk\\FusionAddins, and runOnStartup reloads it).",+ "%APPDATA%\\Autodesk\\FusionAddins, and runOnStartup reloads it). The host "+ "process IS responding - this is the add-in only, so no dialog is implied.", } @@ -8619,10 +8655,38 @@ def _dispatch_command_inner(command: str, args: dict, caller_identity: dict = No if addin_busy and addin_busy.get("busy"): pass # Fall through to busy gate elif not wait_for_addin(timeout=5):+ # Hung HOST vs down ADD-IN (page issue #38) - opposite remedies, so say which.+ try:+ from handlers.fusion_ui import is_fusion_hung+ _h = is_fusion_hung()+ except Exception:+ _h = {"hung": False}+ if _h.get("hung"):+ _ok = False+ try:+ dispatch_command("kill", {})+ _time.sleep(6)+ dispatch_command("start", {})+ _ok = True+ except Exception:+ pass+ return {+ "success": False,+ "error": "Fusion 360 itself is NOT RESPONDING (hung host process); the "+ "add-in cannot answer while it is.",+ "errorCode": "fusion_host_hung",+ "hungSignals": _h.get("signals"),+ "restartAttempted": _ok,+ "_hint": ("Do NOT hunt for a blocking dialog - there is not one. Nothing "+ "reaches Fusion over IPC while the host is hung. "+ + ("The bridge force-restarted it; poll fusion_readiness."+ if _ok else "Use fusion_kill then fusion_start.")),+ } return { "success": False, "error": "Fusion 360 is running but AdomBridge add-in not responding.", "errorCode": "fusion_addin_not_responding",+ "hostResponding": True, "_hint": "The add-in isn't responding. Fix it YOURSELF - never ask the user: the bridge auto-installs the add-in to ALL Fusion add-in dirs (2025+ Fusion scans %APPDATA%/Autodesk/FusionAddins; the legacy API/AddIns dirs are silently ignored - issue #63), so restart Fusion via fusion_stop + fusion_start and it loads (runOnStartup). Verify with fusion_addin_status.", }