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.6.37: readiness no longer false-ready on stuck signin + proactive foreground-lock
6 files changed
+97−23
BRIDGE_VERSION+1−1@@ -1 +1 @@-1.6.36+1.6.37
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.37 - 2026-07-06++- **`fusion_readiness` no longer lies `ready:true` for a stuck/dead Fusion** (John caught it). It only+ checked for the "Signing in" window when the add-in was silent - but a STALE add-in server from a+ just-killed instance keeps port 8774 answering, so `addin_ok` went True while the CURRENT Fusion sat+ stuck on "Signing in", and readiness reported ready. Now the Fusion-process window title is checked+ WHENEVER running (authoritative: "Signing in" == not ready), and `ready` additionally requires the+ add-in to actually RESPOND (a live main thread), not just the process to exist. Added a distinct+ "running but add-in not responding yet" hint so a half-launched Fusion is never mistaken for ready.+- **Proactive foreground-lock during launch = no more flicker.** The reactive minimize-guard alone+ left a visible flash (window appears, then shrinks). Now the launch raises the Windows+ foreground-lock timeout so Fusion's splash/main/seat windows are DENIED a foreground grab outright+ (taskbar flash, not a real steal), restored to the user's original value when the launch window ends.+ ## 1.6.36 - 2026-07-06 - **Codified the background-launch rules in the `fusion-bridge-dev` skill** (John's request): the
adom-bridge-fusion-manifest.json+5−5@@ -1,13 +1,13 @@ { "manifest_version": 1, "name": "fusion360",- "version": "1.6.36",- "url": "https://wiki.adom.inc/download/adom/adom-desktop-fusion-bridge/1.6.36/adom-bridge-fusion-v1.6.36.zip",- "sha256": "d5e0a7eeb9bb7d0229137d77241bdc8c612f4288df75da661af8466480427ced",- "size": 336248,+ "version": "1.6.37",+ "url": "https://wiki.adom.inc/download/adom/adom-desktop-fusion-bridge/1.6.37/adom-bridge-fusion-v1.6.37.zip",+ "sha256": "3274f36e47192bb2a6ca48cb8262f6aba386e3dd9e9f4c031b73ed29339f4ca3",+ "size": 337727, "verbPrefixes": [ "fusion_" ], "healthEndpoint": "/status",- "released_at": "2026-07-06T11:34:07Z"+ "released_at": "2026-07-06T11:44:57Z" }
bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "fusion360", "displayName": "Autodesk Fusion 360",- "version": "1.6.36",+ "version": "1.6.37", "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.",
fusion_detect.py+56−10@@ -341,6 +341,40 @@ def _fusion_pids() -> set: return {int(p) for p in re.findall(r'"Fusion360\.exe","(\d+)"', out)} +_SPI_GETFOREGROUNDLOCKTIMEOUT = 0x2000+_SPI_SETFOREGROUNDLOCKTIMEOUT = 0x2001+++def _get_fg_lock_timeout():+ """Current SystemParametersInfo foreground-lock timeout (ms), or None."""+ try:+ user32 = ctypes.windll.user32+ val = ctypes.wintypes.DWORD(0)+ user32.SystemParametersInfoW(_SPI_GETFOREGROUNDLOCKTIMEOUT, 0, ctypes.byref(val), 0)+ return int(val.value)+ except Exception:+ return None+++def _set_fg_lock_timeout(ms) -> bool:+ """Set the foreground-lock timeout. A NONZERO value makes Windows DENY foreground+ changes from processes that don't own the foreground / have no recent user input -+ they get a taskbar flash instead of an actual foreground grab. We raise it right+ before launching Fusion so its splash/main/seat windows can't STEAL focus in the+ first place (proactive), rather than relying only on the reactive minimize-guard+ (which leaves a visible flash as the window appears-then-shrinks). Restored to the+ user's original value when the guard's launch window ends. Best-effort."""+ try:+ user32 = ctypes.windll.user32+ user32.SystemParametersInfoW.argtypes = [+ ctypes.c_uint, ctypes.c_uint, ctypes.c_void_p, ctypes.c_uint]+ user32.SystemParametersInfoW(+ _SPI_SETFOREGROUNDLOCKTIMEOUT, 0, ctypes.c_void_p(int(ms)), 0)+ return True+ except Exception:+ return False++ def _popen_fusion_background(exe_path: str): """Launch the Fusion launcher MINIMIZED + NOT ACTIVATED, console-less.""" cnw = subprocess.CREATE_NO_WINDOW if hasattr(subprocess, "CREATE_NO_WINDOW") else 0@@ -361,7 +395,7 @@ def _popen_fusion_background(exe_path: str): ) -def _focus_guard(seconds: float = 150.0):+def _focus_guard(seconds: float = 150.0, restore_fg_lock=None): """For `seconds`, demote any Autodesk-FAMILY window that steals the foreground. SURGICAL: only acts when the CURRENT foreground window belongs to an Autodesk@@ -405,12 +439,18 @@ def _focus_guard(seconds: float = 150.0): except Exception: pass time.sleep(0.1)+ # Launch window over: restore the user's original foreground-lock so normal apps+ # can foreground again (we only wanted the lock DURING Fusion's noisy startup).+ if restore_fg_lock is not None:+ _set_fg_lock_timeout(restore_fg_lock) -def _start_focus_guard(seconds: float = 120.0):+def _start_focus_guard(seconds: float = 150.0, restore_fg_lock=None): """Fire-and-forget the focus guard on a daemon thread. Never raises.""" try:- threading.Thread(target=_focus_guard, args=(seconds,), daemon=True).start()+ threading.Thread(+ target=_focus_guard, args=(seconds, restore_fg_lock), daemon=True+ ).start() except Exception: pass @@ -444,14 +484,20 @@ def ensure_fusion_running(fusion_info: dict, wait_addin: bool = False) -> dict | return {"success": False, "error": "Fusion 360 executable not found"} try:- # Launch MINIMIZED + NOT ACTIVATED so Fusion never yanks focus off the- # user's work (see the background-launch block above). Explicit DEVNULL on- # all three std streams + CREATE_NO_WINDOW: the bridge is itself a detached,- # console-less child of AD, so inherited handles are invalid.+ # PROACTIVE: raise the foreground-lock timeout so Windows DENIES Fusion's+ # splash/main/seat windows the ability to steal focus in the first place (they+ # get a taskbar flash, not a real foreground grab). This is what stops the+ # visible FLICKER the reactive guard alone leaves. Restored by the guard at the+ # end of the launch window.+ _orig_fg_lock = _get_fg_lock_timeout()+ _set_fg_lock_timeout(300000)+ # Launch MINIMIZED + NOT ACTIVATED (STARTUPINFO). DEVNULL on all three std+ # streams + CREATE_NO_WINDOW: the bridge is a detached, console-less child of+ # AD, so inherited handles are invalid. _popen_fusion_background(exe_path)- # Guard the noisy startup window: Fusion re-foregrounds itself several times- # while its splash/main window come up, so STARTUPINFO alone isn't enough.- _start_focus_guard(seconds=120.0)+ # Reactive backstop: minimize any family window that still slips to foreground,+ # and restore the user's original foreground-lock when the launch window ends.+ _start_focus_guard(seconds=150.0, restore_fg_lock=_orig_fg_lock) except Exception as e: return {"success": False, "error": f"Failed to launch Fusion 360: {e}"}
server.py+20−6@@ -828,12 +828,18 @@ def _handle_fusion_readiness(fusion_info: dict, args: dict) -> dict: # ("Signing in - Autodesk Fusion" / "Welcome to Fusion") blocks the main thread, # so the add-in never serves and every modeling verb hangs. The old code reported # ready:True here purely on installed+running, which was a lie during sign-in.- # Detect it from the FUSION-PROCESS window title/enabled state - NOT- # classify_launch_dialogs, which scans ALL top-level windows and false-matches a- # stray Edge "Sign in - Autodesk" BROWSER tab (caught live 2026-07-05). Only- # considered when the add-in is NOT responding (a responding add-in => signed in).+ # Detect it from the FUSION-PROCESS window title - NOT classify_launch_dialogs,+ # which scans ALL top-level windows and false-matches a stray Edge "Sign in -+ # Autodesk" BROWSER tab (caught 2026-07-05).+ #+ # ⚠️ CHECK IT WHENEVER RUNNING, not only when the add-in is silent (fix 2026-07-06,+ # John caught it lying): a STALE add-in HTTP server from a just-killed instance can+ # still hold port 8774 and answer, so `addin_ok` goes True while the CURRENT Fusion+ # sits stuck on "Signing in". Gating the sign-in check on `not addin_ok` then let+ # readiness report ready:true for a dead, stuck-signing-in window. The Fusion-process+ # window title is authoritative - if it says "Signing in", we are NOT ready, period. needs_signin = False- if running and not addin_ok:+ if running: try: finfo = get_fusion_window_info() or {} _t = (finfo.get("title") or "").lower()@@ -841,7 +847,10 @@ def _handle_fusion_readiness(fusion_info: dict, args: dict) -> dict: or ("sign in - autodesk" in _t and "fusion" in _t)) except Exception: needs_signin = False- ready = installed and running and not needs_signin+ # "Ready to drive" requires the add-in to actually RESPOND (not just the process to+ # exist) AND no sign-in modal. A responding add-in proves Fusion's main thread is+ # free; requiring it stops a half-launched or stuck Fusion from reading as ready.+ ready = installed and running and addin_ok and not needs_signin # Fusion applying an AUTO-UPDATE (two+ webdeploy builds) crash-restarts itself every # ~30-60s, so a verb intermittently sees running:false. That is NOT a crash or a@@ -880,6 +889,11 @@ def _handle_fusion_readiness(fusion_info: dict, args: dict) -> dict: hint = stale_info["_staleHint"] elif ready: hint = "READY - drive Fusion (fusion_open_lbr / build_library_3d / etc.)."+ elif installed and running:+ hint = ("Fusion is RUNNING but its add-in is NOT responding yet - NOT ready to drive. Either it "+ "is still finishing launch (poll fusion_readiness a few more times), or the add-in did "+ "not load (fusion_stop then fusion_start to re-deploy + re-load it). Do NOT fire "+ "modeling/export verbs until ready:true.") elif installed: hint = "Fusion is INSTALLED but not running - call fusion_start (blocks until the add-in is ready), then retry." else: