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.9.148: user-watching latch + grid auto-dismiss
7 files changed
+57−21
BRIDGE_VERSION+1−1@@ -1 +1 @@-1.9.147+1.9.148
adom-bridge-fusion-manifest.json+4−4@@ -1,10 +1,10 @@ { "manifest_version": 1, "name": "fusion360",- "version": "1.9.147",- "url": "https://wiki.adom.inc/download/adom/fusion-bridge/1.9.147/adom-bridge-fusion-v1.9.147.zip",- "sha256": "2764a73072e068bd443e204d270b6ebb748c53b2de7bf54d0ae571b0aa90aea0",- "size": 479440,+ "version": "1.9.148",+ "url": "https://wiki.adom.inc/download/adom/fusion-bridge/1.9.148/adom-bridge-fusion-v1.9.148.zip",+ "sha256": "c7be24864786169a633ec869e1f4ee45df2f02ff08c998b3a3cec586558a7cb6",+ "size": 480281, "verbPrefixes": [ "fusion_" ],
bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "fusion360", "displayName": "Autodesk Fusion 360",- "version": "1.9.147",+ "version": "1.9.148", "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.",
fusion_detect.py+29−6@@ -814,6 +814,9 @@ def _popen_fusion_background(exe_path: str): _ENVELOPE_LOCK = threading.Lock() _ENVELOPE_COUNT = 0 _ENVELOPE_SAVED = {}+# The user-is-watching latch (shared across concurrent envelope watchers): once the USER+# raises a Fusion window, ALL watchers stand down until the user focuses something else.+_USER_WATCHING = [False] def _fusion_main_hwnd_quick():@@ -893,6 +896,15 @@ def _envelope_watch(seconds: float, minimize: bool): return False end = time.time() + seconds pids, last_refresh = set(), 0.0+ # ⭐ THE USER-IS-WATCHING LATCH (John, 2026-08-23: "if I want to watch it do its+ # thing, I should be allowed to" - he raised Fusion and the watcher shoved it back).+ # The 2s input window is too short for WATCHING: you click Fusion up, then just look,+ # and 2s later this loop demotes it. So when the user RAISES a Fusion window (a+ # non-Fusion -> Fusion foreground transition with recent input), LATCH hands-off and+ # NEVER demote until the user themselves moves focus to a non-Fusion window. This only+ # fights Fusion's OWN self-activation (a transition with no user input behind it).+ _prev_was_fusion = False+ _fg_pid = ctypes.wintypes.DWORD(0) while time.time() < end: now = time.time() if now - last_refresh > 2.0:@@ -900,14 +912,25 @@ def _envelope_watch(seconds: float, minimize: bool): last_refresh = now try: fg = user32.GetForegroundWindow()- if fg and pids and not _user_active_recently():- wpid = ctypes.wintypes.DWORD(0)- user32.GetWindowThreadProcessId(fg, ctypes.byref(wpid))- if wpid.value in pids:+ fg_is_fusion = False+ if fg and pids:+ user32.GetWindowThreadProcessId(fg, ctypes.byref(_fg_pid))+ fg_is_fusion = _fg_pid.value in pids+ if fg_is_fusion:+ # User deliberately brought Fusion up -> latch and stand down.+ if (not _prev_was_fusion) and _user_active_recently():+ _USER_WATCHING[0] = True+ if not _USER_WATCHING[0]:+ # Fusion self-activated (no user behind this raise) -> demote. if minimize: user32.ShowWindowAsync(fg, _SW_SHOWMINNOACTIVE) else: user32.SetWindowPos(fg, HWND_BOTTOM, 0, 0, 0, 0, SWP_FLAGS)+ elif fg:+ # Foreground is something else - the user is not watching Fusion now,+ # so resume guarding against Fusion's next self-grab.+ _USER_WATCHING[0] = False+ _prev_was_fusion = fg_is_fusion except Exception: pass time.sleep(0.05)@@ -918,8 +941,8 @@ def _envelope_watch(seconds: float, minimize: bool): grace_end = time.time() + 20 while time.time() < grace_end: try:- if _user_active_recently():- break # the user is driving - the foreground is THEIRS now+ if _user_active_recently() or _USER_WATCHING[0]:+ break # the user is driving/watching - the foreground is THEIRS now fg = user32.GetForegroundWindow() if not (fg and pids): break
handlers/dialog_classify.py+11−1@@ -120,6 +120,16 @@ _RULES = [ "bridge clicks 'Check again' to regrab it (background UIA). Never click 'Shut Down' - " "that signs this box out."), + # Electronics-editor SETTINGS dialogs that block automation and carry NO data (John saw+ # the Grid dialog full-screen-blocking during a matrix run, 2026-08-23). Nothing we send+ # opens GRID on purpose - it is a stray/inadvertent raise - but a settings dialog must+ # never block the queue: it is safe to Cancel unconditionally (nothing is lost).+ (("grid", "display style dots lines", "finest", "altdistance"),+ "settings_dialog_cancel",+ "A Fusion electronics SETTINGS dialog (e.g. Grid) is open and blocking. It carries no "+ "data - Cancel it (WM_CLOSE / Cancel). Nothing we drive opens these deliberately; if it "+ "keeps recurring, something is injecting a stray GRID/settings command or keystroke."),+ (("active sessions exceeded", "more active sessions", "more sessions running than are allowed", "sessions running than are allowed"), "session_conflict_decision",@@ -189,7 +199,7 @@ FAILURE_CATEGORIES = {"operation_error"} DECISION_CATEGORIES = {"session_conflict_decision", "session_suspend_confirm"} # Categories the launch code MAY auto-dismiss (a benign ack; WM_CLOSE == Cancel/OK). # Everything else (decisions, sign-in) is SURFACED, never auto-closed.-AUTO_DISMISS_CATEGORIES = {"launch_error", "already_open", "crash_report", "create_confirm"}+AUTO_DISMISS_CATEGORIES = {"launch_error", "already_open", "crash_report", "create_confirm", "settings_dialog_cancel"} # A crash report means the app is GONE, not merely dialog-blocked: clearing it is not # enough, the bridge must relaunch. Kept separate from AUTO_DISMISS so a caller can tell # "I closed a nuisance" from "I closed a nuisance and must now restart the app".
package.json+1−1@@ -1,6 +1,6 @@ { "slug": "fusion-bridge",- "version": "1.9.169",+ "version": "1.9.170", "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.169",+ "version": "1.9.170", "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]"- }+ ] }