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
1.6.53: slow-open verify-by-state + fusion_notify_owner + parent/child shot hints
6 files changed
+119−22
BRIDGE_VERSION+1−1@@ -1 +1 @@-1.6.47+1.6.53
CHANGELOG.md+17@@ -3,6 +3,23 @@ All notable changes to the Adom Desktop Fusion 360 bridge, newest first. Canonical: https://wiki.adom.inc/adom/adom-desktop-fusion-bridge · GitHub backup: `adom-inc/adom-desktop-fusion-bridge` +## 1.6.53 - 2026-07-07++- **Fixed slow-machine opens being mislabeled "add-in not running" (caught live on a GPU-less Azure+ VM).** `_orchestrate_open_lbr`'s `Document.newDesignFromLocal` call had a fixed 30s read timeout;+ on software-rendered machines the open takes 60s+, so the proxy timed out and the canned error+ claimed the ADD-IN was down while the document was actually opening fine -+ `fusion_build_library_3d` then aborted every part on a lie. Now: 120s timeout AND on any failure+ the open is VERIFIED BY STATE (poll `get_app_state` for the expected document) before failing -+ the doc actually being open outranks the synchronous return.+- **NEW verb `fusion_notify_owner` - deterministic last-resort human escalation (John's rule).**+ Toasts the user's MAIN computer by fanning out cross-AD to every relay peer, so a bridge on an+ unattended VM still reaches them. For true human walls only (password/2FA, UAC); hints tell the+ driving AI to exhaust programmatic options first and not to re-toast.+- **Hints now teach the parent/child screenshot discipline**: never poll blind, never fullscreen -+ `desktop_screenshot_window` returns a `screenshots[]` array where every OWNED POPUP rides back as+ its own child shot; launch/sign-in hints now say to READ those.+ ## 1.6.48-1.6.52 - 2026-07-06 (pkg hygiene, no runtime change) - Declared the `adom/adom-desktop` pkg dependency (SDK two-artifact audit, #83).
adom-bridge-fusion-manifest.json+5−5@@ -1,13 +1,13 @@ { "manifest_version": 1, "name": "fusion360",- "version": "1.6.47",- "url": "https://wiki.adom.inc/download/adom/adom-desktop-fusion-bridge/1.6.47/adom-bridge-fusion-v1.6.47.zip",- "sha256": "9cfa11c55d99baef1c268a4a13cf69a338eb4af53a8ed5ffe13541e16ec7b3cd",- "size": 345799,+ "version": "1.6.53",+ "url": "https://wiki.adom.inc/download/adom/adom-desktop-fusion-bridge/1.6.53/adom-bridge-fusion-v1.6.53.zip",+ "sha256": "93e1b055a799133d452813822f1979dd67a79d2af52bd586c5bf147a29e5758d",+ "size": 334089, "verbPrefixes": [ "fusion_" ], "healthEndpoint": "/status",- "released_at": "2026-07-06T15:05:00Z"+ "released_at": "2026-07-07T12:55:00Z" }
bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "fusion360", "displayName": "Autodesk Fusion 360",- "version": "1.6.47",+ "version": "1.6.53", "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/adom-desktop-fusion-bridge", "author": "Adom Inc.",
describe.py+6−1@@ -24,9 +24,14 @@ _T = [ {"name": "optional str (default: active)"}, {"success": "bool"}, 30, None, False, {}), ("fusion_get_app_state", "Current app state: running, active document, workspace, isElectronics. The poll/status verb for long-running calls.", {}, {"activeDocument": "str", "activeWorkspace": "str", "isElectronics": "bool"}, 20, None, False, {}),- ("fusion_readiness", "FAST readiness check (does NOT launch Fusion): is the Fusion host app installed + running + the bridge ready to drive? AD DETECTS Fusion (never installs it) via bridge.json 'detect', and auto-installs Python if missing (AD >=1.9.47). Call this before driving Fusion; if installed-but-not-running, fusion_start. Pairs with AD's bridge_readiness.",+ ("fusion_readiness", "FAST readiness check (does NOT launch Fusion): is the Fusion host app installed + running + the bridge ready to drive? AD DETECTS Fusion (never installs it) via bridge.json 'detect', and auto-installs Python if missing (AD >=1.9.47). Call this before driving Fusion; if installed-but-not-running, fusion_start. SELF-HEALS the seat/licensing dialog (background UIA + screenshot-verify) whenever it detects one. Pairs with AD's bridge_readiness.", {}, {"installed": "bool", "running": "bool", "ready": "bool", "hostApp": "str", "bridgeVersion": "str"}, 15, None, False, {}), + ("fusion_notify_owner", "LAST-RESORT human escalation: toast the user's MAIN computer (fans out cross-AD to every peer on the relay, so it reaches them even when this bridge runs on an unattended VM). ONLY for true human walls - password/2FA entry, UAC elevation, a physical step. Exhaust programmatic options first (UIA background clicks, seat auto-resolve, warm-SSO sign-in). After sending, WAIT and poll fusion_readiness; do not re-toast within a few minutes.",+ {"title": "optional str", "body": "required str", "level": "optional str info|warning|error"},+ {"success": "bool", "targets": "list"}, 20, None, False,+ {"title": "Fusion needs you", "body": "Please complete the Autodesk 2FA prompt on winvm - everything else is done."}),+ # ── Cloud search & files (APS) ───────────────────────────────────────── ("fusion_aps_status", "APS state: configured, signed in, token live, never-charge meter.", {}, {"configured": "bool", "signedIn": "bool", "neverCharge": "bool"}, 20, None, False, {}),
server.py+89−14@@ -198,6 +198,43 @@ def _resolve_seat_dialog(max_clicks: int = 8) -> dict: return {"sawDialog": saw, "clicks": clicks, "verified": verified} +def _handle_notify_owner(args: dict) -> dict:+ """LAST-RESORT: toast the user's MAIN desktop (cross-AD) to ask for a human step.++ John's standing rule (2026-07-07): the bridge/AI does EVERYTHING itself; the only+ legitimate uses are true human walls - password/2FA entry, UAC elevation, a physical+ action. When this bridge runs on an unattended VM, the toast must reach the machine+ the user is actually AT - ad_client.notify(reach_user=True) fans out to all peer ADs+ on the relay, so it lands on their main computer too. Returns which targets were hit."""+ title = args.get("title") or "Fusion bridge needs you"+ body = args.get("body") or args.get("message") or "A human step is required to continue."+ level = args.get("level") or "warning"+ if not ad_client.available():+ return {+ "success": False,+ "error": "AD direct API unavailable - cannot send a toast from the bridge.",+ "notifyUser": {"title": title, "body": body, "level": level},+ "_hint": ("Relay the included notifyUser payload yourself via AD's notify_user verb "+ "(add a cross-AD `target` if this bridge runs on a VM and the user is elsewhere)."),+ }+ peers = []+ try:+ peers = ad_client.peers()+ except Exception:+ pass+ res = ad_client.notify(title, body, level=level, reach_user=True)+ ok = bool(res and (res.get("success") or res.get("status") == "ok"))+ return {+ "success": ok,+ "targets": (["self"] + peers) if peers else ["self"],+ "_hint": ("Toast sent" + (" to this machine AND all peer ADs (user may be on another computer)."+ if peers else " to this machine (no peer ADs on the relay).")+ + " This is the LAST RESORT - only use after exhausting programmatic options "+ "(UIA background clicks, seat auto-resolve, warm-SSO sign-in). Now WAIT and poll "+ "fusion_readiness for the state to clear; do not re-toast within a few minutes."),+ }++ def _handle_launch(fusion_info: dict, args: dict) -> dict: """Launch Fusion 360 and optionally wait for the AdomBridge add-in.""" # LIVE detect (not the stale bridge-start fusion_info snapshot) so a launch right@@ -320,7 +357,10 @@ def _handle_launch(fusion_info: dict, args: dict) -> dict: "NOT a failure and NOT a retry-able timeout: do NOT re-call fusion_start. WHAT HAPPENS " "NEXT: POLL fusion_readiness every ~10s; it returns needsSignin/licensingDialog/updating " "if something is mid-flight (the bridge handles the seat dialog itself), and ready:true "- "when it is drivable - then run your fusion_* verbs."+ "when it is drivable - then run your fusion_* verbs. NEVER poll blind past ~2 min: "+ "desktop_screenshot_window the Fusion hwnd and READ the response's screenshots[] array "+ "- every OWNED POPUP rides back as its own child shot (this is how you SEE a blocking "+ "dialog; never desktop_screenshot_screen, the user is doing other things)." ), } @@ -980,11 +1020,13 @@ def _handle_fusion_readiness(fusion_info: dict, args: dict) -> dict: elif needs_signin: hint = ("Fusion is RUNNING but stuck at the first-run Autodesk SIGN-IN (main UI blocked, " "add-in cannot serve) - NOT ready. AUTOMATE the sign-in (see the fusion-onboarding "- "skill for the proven sequence: clear stale signin.autodesk.com tabs, 'Retry browser "- "sign-in', click 'Open Product' via desktop_click/isTrusted, tick 'Always allow' + "- "'Open' on the protocol overlay), then poll until ready:true. NEVER enter the "- "password/2FA; to ask the user, notify_user works directly (add a cross-AD `target` "- "if you're on a VM).")+ "skill for the proven sequence: desktop_screenshot_window the Fusion hwnd and READ "+ "its screenshots[] array - owned popups ride back as child shots; UIA-click 'Sign In', "+ "'Continue with Google' (warm SSO), then isTrusted-click 'Open Product' + 'Always "+ "allow'+'Open' on the protocol overlay), then poll until ready:true. NEVER enter the "+ "password/2FA yourself; if the flow truly needs the user (password/2FA prompt), call "+ "fusion_notify_owner {title, body} - it toasts their MAIN computer cross-AD. Last "+ "resort only.") elif ready and stale_info.get("addinStale"): hint = stale_info["_staleHint"] elif ready:@@ -1227,6 +1269,12 @@ COMMAND_HANDLERS = { "_hint": "No Fusion dialogs/owned popups are currently up."} ), "addin_status": lambda fi, args: _handle_addin_status(),+ # LAST-RESORT human escalation (John 2026-07-07): when the bridge is truly blocked+ # on the user (password/2FA, UAC), send an AD toast that reaches their MAIN machine+ # (reach_user=True fans out to peer ADs, so a bridge running on an unattended VM+ # still lands the toast where the user actually is). ALWAYS exhaust programmatic+ # options first - this exists so the AI has ONE deterministic call when it must ask.+ "notify_owner": lambda fi, args: _handle_notify_owner(args), # APS cloud search (pure HTTPS, works with Fusion closed). Lives in # COMMAND_HANDLERS so it returns BEFORE any Fusion-running gate. "aps_status": lambda fi, args: aps.handle_status(args),@@ -1721,18 +1769,45 @@ def _orchestrate_open_lbr(args: dict) -> dict: file_path = file_path.replace("\\", "/") results = [] - # Step 1: Open the .lbr via Document.newDesignFromLocal+ # Step 1: Open the .lbr via Document.newDesignFromLocal.+ # ⚠️ TIMEOUT + VERIFY-BY-STATE (fixed 2026-07-07, caught live on a GPU-less Azure+ # VM): the open can take 60s+ on slow/software-rendered machines, so a fixed 30s+ # read timeout expired mid-open and the canned proxy error claimed the ADD-IN was+ # "not running" while the document was actually opening fine (fusion_build_library_3d+ # then aborted all parts on a lie). Now: a generous timeout, AND on ANY failure we+ # poll get_app_state for the expected document name - the doc actually being open+ # outranks whatever the synchronous return claimed. open_result = _proxy_to_addin("execute_text_command", { "command": f"Document.newDesignFromLocal {file_path}",- }, timeout=30)+ }, timeout=120) if not open_result.get("success"):- return {- "success": False,- "error": f"Document.newDesignFromLocal failed: {open_result.get('error', 'unknown')}",- "data": {"filePath": file_path},- }- results.append("Document.newDesignFromLocal: ok")+ # Verify by STATE before failing: did the doc open anyway?+ expected = file_path.replace("\\", "/").rsplit("/", 1)[-1]+ expected = expected.rsplit(".", 1)[0].lower()+ opened_anyway = False+ for _ in range(20): # up to ~60s of settling+ time.sleep(3)+ try:+ st = _proxy_to_addin("get_app_state", {}, timeout=8)+ active = str((st.get("data") or {}).get("activeDocument", "")).lower()+ if expected and expected in active:+ opened_anyway = True+ break+ except Exception:+ pass+ if not opened_anyway:+ return {+ "success": False,+ "error": f"Document.newDesignFromLocal failed: {open_result.get('error', 'unknown')}",+ "_hint": ("The open did not complete AND the document never became active. On slow/"+ "software-rendered machines opens can take 60s+; this call already waited + "+ "verified by state. Check fusion_check_dialogs for a blocking modal, then retry."),+ "data": {"filePath": file_path},+ }+ results.append("Document.newDesignFromLocal: ok (verified by app state after slow open)")+ else:+ results.append("Document.newDesignFromLocal: ok") # Step 2: Navigate to symbol if requested if symbol_name: