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
v1.9.61: etiquette coverage + zombie gate + stop hardening
5 files changed
+90−19
BRIDGE_VERSION+1−1@@ -1 +1 @@-1.9.59+1.9.61
CHANGELOG.md+13@@ -3,6 +3,19 @@ All notable changes to the Adom Bridge Fusion 360 bridge, newest first. Canonical: https://wiki.adom.inc/adom/fusion-bridge ยท GitHub backup: `adom-inc/fusion-bridge` +## 1.9.61 - 2026-08-18++- **windowEtiquette annotation now covers ORCHESTRATED verbs** (show_*, make_test_project, tour,+ bind_library_3d, ...): a thin dispatch wrapper guarantees every backgrounded window-raising+ result carries the marker + re-call shape - the inner closure only covered direct/add-in paths.+- **Zombie-window gate on the save-prompt sentinel**: an UNATTRIBUTED "Save" dialog is only+ escalated (Escape) when the add-in main thread is actually blocked; responsive-thread ghosts+ (ConfRoomROG's black no-UIA windows) are left alone instead of escape-spammed every sweep.+- **Graceful stop hardening**: the clean-close document pass retries once (3s + 10s budget)+ instead of silently skipping on a transiently-busy add-in - skipping was what let WM_CLOSE hit+ the unsaved-doc Save modal. On failure, `bridgeDocsOnly:true` + a kill-is-safe hint when every+ blocking doc is a bridge-created throwaway, so callers can escalate to fusion_kill confidently.+ ## 1.9.60 - 2026-08-18 (dashboard only; bridge zip stays 1.9.59) - **Every interactive element now acknowledges the human (John: the UI must say "yes, you just
adom-bridge-fusion-manifest.json+4−4@@ -1,10 +1,10 @@ { "manifest_version": 1, "name": "fusion360",- "version": "1.9.59",- "url": "https://wiki.adom.inc/download/adom/fusion-bridge/1.9.59/adom-bridge-fusion-v1.9.59.zip",- "sha256": "44aa7bcc595a5b4aa944e34a5bcf7c8dabd3fb0a45a1a30a78d22e1bf1353fea",- "size": 404015,+ "version": "1.9.61",+ "url": "https://wiki.adom.inc/download/adom/fusion-bridge/1.9.61/adom-bridge-fusion-v1.9.61.zip",+ "sha256": "b511771ebbb10ce1e1570c4064ae23160c32c6da7a713ba9463a58c49e97f879",+ "size": 404984, "verbPrefixes": [ "fusion_" ],
handlers/close_fusion.py+21−3@@ -125,7 +125,16 @@ def _close_all_documents_via_addin() -> dict: with urllib.request.urlopen(req, timeout=5) as resp: resp_data = json.loads(resp.read()) except Exception:- return result # Add-in not reachable, skip clean close+ # ONE retry with a longer budget before giving up: a transiently-busy main thread+ # here silently skipped the clean close, so WM_CLOSE later hit the unsaved-doc+ # Save modal and the whole graceful stop failed (live, 2026-08-17).+ try:+ import time as _t+ _t.sleep(3)+ with urllib.request.urlopen(req, timeout=10) as resp:+ resp_data = json.loads(resp.read())+ except Exception:+ return result # Add-in truly unreachable, skip clean close if not resp_data.get("success"): return result@@ -284,12 +293,21 @@ def handle_fusion_stop(fusion_info: dict, args: dict) -> dict: (closed if _close_and_wait(hwnd, title) else failed).append(title) if failed:+ # Bridge-created throwaway docs (keep the tuple in sync with server._BRIDGE_DOC_BUILTINS):+ # when EVERY failed window is one of ours, force-kill risks nothing of the user's, and+ # callers (tour cleanup, the test suite) can escalate automatically on bridgeDocsOnly.+ _bridge_marks = ("adom-test-", "adomdemosample", "adom-fusion-wrap", "ads8588sipm", "0603waf")+ bridge_only = all(any(m in t.lower() for m in _bridge_marks) for t in failed) return { "success": False, "error": f"Graceful stop failed for: {', '.join(failed)}", "output": f"Closed: {', '.join(closed)}" if closed else "",- "_hint": "A window would not close gracefully (a modal dialog often blocks WM_CLOSE). "- "Use fusion_kill to force-kill, then retry.",+ "bridgeDocsOnly": bridge_only,+ "_hint": ("The unsaved document(s) blocking the close are BRIDGE-created throwaways "+ "(sample/test docs) - fusion_kill is SAFE here, nothing of the user's is at "+ "risk. Kill, then retry." if bridge_only else+ "A window would not close gracefully (a modal dialog often blocks WM_CLOSE). "+ "Use fusion_kill to force-kill, then retry."), } for _ in range(10):
server.py+51−11@@ -4350,6 +4350,20 @@ def _dismiss_save_prompt_if_bridge_doc() -> dict: # the doc stays open, nothing saved or discarded). A modal that blocks the add-in # main thread for 2+ sweeps gets escaped rather than sat on; Don't Save is still # NEVER clicked without attribution.+ # ZOMBIE GATE (2026-08-17, found live on ConfRoomROG): "Save"-titled windows can be+ # non-modal GHOSTS that block nothing (main thread fully responsive, PrintWindow+ # black, no UIA). Escaping those every other tick is pointless input spam - only+ # escalate on an unattributed prompt when the add-in main thread is ACTUALLY+ # blocked (status probe busy or unanswerable).+ try:+ _st = _check_addin_status(timeout=2.0)+ _blocked = (_st is None) or bool(_st.get("busy"))+ except Exception:+ _blocked = True+ if not _blocked:+ return {"handled": False,+ "why": "unattributed dialog but the main thread is responsive - zombie window, leaving it alone",+ "hwnd": prompt["hwnd"]} n = _UNATTRIBUTED_PROMPT_STRIKES.get(prompt["hwnd"], 0) + 1 _UNATTRIBUTED_PROMPT_STRIKES[prompt["hwnd"]] = n if n >= 2:@@ -7799,7 +7813,44 @@ def _assert_active_document(expect: str) -> dict | None: return None +_WINDOW_RAISING = {+ "open_cloud_file", "open_by_urn", "aps_open", "open_design", "open_lbr",+ "open_schematic", "open_board", "import_electronics", "import_step",+ "new_electronics_from_eagle", "show_schematic", "show_2d_board", "show_3d_board",+ "run_modeling_script", "execute_text_command", "electron_run",+ "attach_3d_package", "make_3d_package", "build_library_3d",+ "capture_library_views", "generate_package", "export_optimized_glb",+ "board_stackup", "save_to_cloud", "start",+ "show_symbol", "show_footprint", "show_3d_package", "make_test_project",+ "bind_library_3d", "show_library", "show_project",+}++ def dispatch_command(command: str, args: dict, caller_identity: dict = None) -> dict:+ """Thin wrapper: run the verb, then guarantee the windowEtiquette annotation lands on+ EVERY backgrounded window-raising result - the inner dispatcher's closure only covered+ direct-handler and add-in paths, so orchestrated verbs (show_*, make_test_project, the+ tour, ...) returned unannotated (the calling AI could not know no focus was taken).+ Idempotent with the inner annotation (the note is only appended once)."""+ _fg = bool((args or {}).get("foreground"))+ res = _dispatch_command_inner(command, args, caller_identity)+ try:+ cmd = command[7:] if command.startswith("fusion_") else command+ if cmd in _WINDOW_RAISING and not _fg and isinstance(res, dict):+ res.setdefault("windowEtiquette", "background")+ note = ("Window opened/changed in the BACKGROUND - the user's focus was NOT touched; "+ "do NOT foreground anything to compensate. Render results via fusion_state. "+ "If the USER asks to watch, re-call with {\"foreground\": true, "+ "\"foregroundReason\": \"<why, shown to the user as a 3s caption>\"}.")+ h = res.get("_hint")+ if not (isinstance(h, str) and "focus was NOT touched" in h):+ res["_hint"] = (h + " " + note) if isinstance(h, str) else note+ except Exception:+ pass+ return res+++def _dispatch_command_inner(command: str, args: dict, caller_identity: dict = None) -> dict: """Dispatch a command to the appropriate handler. caller_identity: {'thread','container','reason'} from the X-Adom-Caller-* headers AD stamped on@@ -7836,17 +7887,6 @@ def dispatch_command(command: str, args: dict, caller_identity: dict = None) -> # so a cloud open / view switch / import NEVER steals the user's foreground mid-typing. The # deliberate-foreground verbs (prefs_open, mcp_enable, demo - which announce themselves with # captions) are excluded on purpose. Refcounted, so overlapping verbs nest cleanly.- _WINDOW_RAISING = {- "open_cloud_file", "open_by_urn", "aps_open", "open_design", "open_lbr",- "open_schematic", "open_board", "import_electronics", "import_step",- "new_electronics_from_eagle", "show_schematic", "show_2d_board", "show_3d_board",- "run_modeling_script", "execute_text_command", "electron_run",- "attach_3d_package", "make_3d_package", "build_library_3d",- "capture_library_views", "generate_package", "export_optimized_glb",- "board_stackup", "save_to_cloud", "start",- "show_symbol", "show_footprint", "show_3d_package", "make_test_project",- "bind_library_3d", "show_library", "show_project",- } _ran_backgrounded = False if command in _WINDOW_RAISING: if (args or {}).get("foreground"):