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.55: universal error-dialog guard (auto-detect Fusion Error dialog on every open/mutating verb)
6 files changed
+104−12
BRIDGE_VERSION+1−1@@ -1 +1 @@-1.6.54+1.6.55
CHANGELOG.md+14@@ -3,6 +3,20 @@ 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.55 - 2026-07-07++- **Universal error-dialog guard, baked into every mutating/open verb (John: "you have to+ screenshot to get the parent/child array to constantly look for errors ... bake this into every+ command").** A malformed `.lbr` makes Fusion pop an owned "Error" dialog ("<file>.lbr has errors+ and cannot be opened") while `Document.newDesignFromLocal` still returns ok (it opened an EMPTY+ design behind the error), so `open_lbr` reported success on a library that never loaded. New+ `operation_error` dialog classification (title "Error" / "cannot be opened" / "has errors") + a+ shared `_apply_failure_dialogs()` now runs after every open/mutating verb: it screenshots the+ owned popup (hwnd-targeted, background), flips the result to `success:false` ++ `errorCode:fusion_operation_error` with the exact resolution, attaches the shot to+ `data.errorDialog`, and dismisses the pure-ack popup so it can't wedge the next command. A failed+ op can no longer be returned as success.+ ## 1.6.54 - 2026-07-07 - **Fixed 3D auto-orient laying TALL through-hole parts on their side** (John caught it: a machine
adom-bridge-fusion-manifest.json+5−5@@ -1,13 +1,13 @@ { "manifest_version": 1, "name": "fusion360",- "version": "1.6.54",- "url": "https://wiki.adom.inc/download/adom/adom-desktop-fusion-bridge/1.6.54/adom-bridge-fusion-v1.6.54.zip",- "sha256": "e2920d0d79a89fcc53c808dc2f8456c068c8be3929952f2bb8fd4b096a85e5fe",- "size": 337157,+ "version": "1.6.55",+ "url": "https://wiki.adom.inc/download/adom/adom-desktop-fusion-bridge/1.6.55/adom-bridge-fusion-v1.6.55.zip",+ "sha256": "f1cf50ad2e471dc9ba6bb1e9cf2184562b382c03d8efc10fcca3b7ead58770cf",+ "size": 339847, "verbPrefixes": [ "fusion_" ], "healthEndpoint": "/status", "released_at": "2026-07-07T16:20:00Z"-}+}
bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "fusion360", "displayName": "Autodesk Fusion 360",- "version": "1.6.54",+ "version": "1.6.55", "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.",
handlers/dialog_classify.py+22@@ -112,8 +112,30 @@ _RULES = [ "Launch error from an INCOMPLETE webdeploy folder (missing FusionLauncher.exe.ini). Safe to " "dismiss (OK/close) - it is only an acknowledgement. Launch the COMPLETE production folder (the " "hash dir that HAS FusionLauncher.exe.ini); detect_fusion now prefers it."),++ # ⚠️ OPERATION FAILED. A bare "Error"-titled owned popup means the LAST command did+ # NOT succeed even if the API call returned ok - e.g. an .lbr that "has errors and+ # cannot be opened" (Fusion opened an EMPTY design instead), a failed import/export,+ # a bad STEP. This is NOT a decision and NOT a benign launch ack: the bridge flips the+ # command's success to False when it sees this (see server._apply_failure_dialogs), so+ # a malformed library can never again be reported as "opened". Keep this rule LAST so+ # the specific "error launching..."/seat rules above win first. It is a pure OK+ # acknowledgement, so it is safe to close (WM_CLOSE) after the failure is surfaced.+ (("error", "cannot be opened", "has errors", "failed to", "could not", "invalid"),+ "operation_error",+ "The last operation FAILED - Fusion is showing an Error dialog (the API call may still have "+ "returned ok because it opened an EMPTY design). READ the dialog screenshot for the exact "+ "message. Common case: '<file>.lbr has errors and cannot be opened' = the library is malformed "+ "(re-lint/regenerate; a frequent cause is a single UNNAMED symbol pin - EAGLE <pin name=''> + "+ "<connect pin=''> - name the pin from the KiCad pin NUMBER). The bridge already flipped success "+ "to False and dismissed the empty design; FIX the source file and retry. Do NOT report success."), ] +# Categories that mean the OPERATION FAILED. The bridge downgrades a command's+# success to False when a popup of one of these categories is up after it runs, so+# a failure (e.g. a malformed .lbr) can never be reported as success. These are pure+# OK-acknowledgements, so the bridge may close them after surfacing the failure.+FAILURE_CATEGORIES = {"operation_error"} # Categories that require a USER decision — the launch code must NOT auto-dismiss # these; it surfaces them + fires a notify_user + asks. DECISION_CATEGORIES = {"session_conflict_decision", "session_suspend_confirm"}
server.py+61−5@@ -641,7 +641,10 @@ def _post_open_screenshot(result: dict, settle_time: float = 2.0) -> dict: result["data"] = {} result["data"]["postOpenScreenshot"] = screenshot_info - return result+ # UNIVERSAL: if an owned "Error" popup is up (e.g. malformed .lbr "has errors and+ # cannot be opened"), flip success -> False + surface it. An open that silently+ # errored must never be reported as ok.+ return _apply_failure_dialogs(result) def _capture_dialog_array(settle: float = 0.6) -> dict | None:@@ -738,6 +741,59 @@ def _merge_dialog_array(result: dict) -> dict: result["data"]["dialogArray"] = arr existing = result.get("_hint") result["_hint"] = arr["_hint"] + ((" | " + existing) if existing else "")+ return _apply_failure_dialogs(result)+++def _apply_failure_dialogs(result: dict) -> dict:+ """UNIVERSAL error-dialog guard — baked into every mutating/open verb so the AI+ can never again report a failed op as success.++ Right after an operation, an owned Fusion popup titled "Error" (e.g. "<file>.lbr+ has errors and cannot be opened") means the op FAILED even when the underlying API+ call returned ok (Fusion opened an EMPTY design behind the error). This classifies+ any up dialog; if one is in FAILURE_CATEGORIES it:+ 1. SCREENSHOTS the popup (hwnd-targeted, background) so the AI SEES it,+ 2. flips result.success -> False + sets errorCode/error/_hint,+ 3. dismisses the pure-ack popup (WM_CLOSE) so it can't block the next command.+ Best-effort + cheap when nothing is up (title enumeration, no screenshot). Never raises."""+ try:+ from handlers.dialog_classify import classify_blocking_dialogs, FAILURE_CATEGORIES+ dialogs = classify_blocking_dialogs()+ except Exception:+ return result+ failures = [d for d in (dialogs or []) if d.get("category") in FAILURE_CATEGORIES]+ if not failures or not isinstance(result, dict):+ return result+ f = failures[0]+ shots = []+ for d in failures:+ try:+ ss = screenshot_hwnd(d.get("hwnd"), label="error-dialog")+ if ss.get("success"):+ shots.append(ss["savedTo"])+ except Exception:+ pass+ prev_err = result.get("error") or ""+ result["success"] = False+ result["errorCode"] = "fusion_operation_error"+ result["error"] = (+ f"Operation FAILED - Fusion '{f.get('title') or 'Error'}' dialog is up"+ + (f" (prior: {prev_err})" if prev_err else "")+ )+ result["_hint"] = (f.get("resolution") or+ "Fusion shows an Error dialog; the operation failed. Read the screenshot.")+ result.setdefault("data", {})+ if isinstance(result.get("data"), dict):+ result["data"]["errorDialog"] = {+ "title": f.get("title"), "category": f.get("category"),+ "resolution": f.get("resolution"), "screenshots": shots,+ }+ # Pure OK-acknowledgement — dismiss so it doesn't wedge the next command.+ for d in failures:+ try:+ close_window(d.get("hwnd"))+ except Exception:+ pass return result @@ -3111,15 +3167,15 @@ def dispatch_command(command: str, args: dict) -> dict: if command == "attach_3d_package": return _merge_dialog_array(_orchestrate_attach_3d_package(args)) if command == "make_3d_package":- return _orchestrate_make_3d_package(args)+ return _apply_failure_dialogs(_orchestrate_make_3d_package(args)) if command == "build_library_3d":- return _orchestrate_build_library_3d(args)+ return _apply_failure_dialogs(_orchestrate_build_library_3d(args)) if command == "capture_library_views":- return _orchestrate_capture_library_views(args)+ return _apply_failure_dialogs(_orchestrate_capture_library_views(args)) if command == "cleanup_cloud_files": return _orchestrate_cleanup_cloud_files(args) if command == "generate_package":- return _orchestrate_generate_package(args)+ return _apply_failure_dialogs(_orchestrate_generate_package(args)) # Add-in proxy commands if command in ADDIN_COMMANDS: