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.33: launch Fusion in background (minimized+no-activate+focus-guard); dev-skill invariant
6 files changed
+146−18
BRIDGE_VERSION+1−1@@ -1 +1 @@-1.6.32+1.6.33
CHANGELOG.md+12@@ -3,6 +3,18 @@ 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.33 - 2026-07-06++- **Fusion now launches in the BACKGROUND - never steals the user's foreground** (John's directive).+ The bridge runs on a machine the user is actively working on, and a bare `Popen([launcher])` opened+ Fusion's splash + main window in the foreground, yanking focus off their work. Now `ensure_fusion_+ running()` starts the launcher MINIMIZED + NOT ACTIVATED (`STARTUPINFO SW_SHOWMINNOACTIVE`) and fires+ a ~120s **focus-guard** daemon that minimizes (no-activate) *only* a Fusion window that is *currently*+ the foreground (focus-theft detected via `GetForegroundWindow` + PID match, so it never fights a user+ who deliberately opened Fusion). The add-in still loads fully while minimized. Foreground is now+ opt-in, only when a verb's job is to SHOW the user. Codified as a HARD invariant in the+ `fusion-bridge-dev` skill so every future window-opening path follows it.+ ## 1.6.32 - 2026-07-06 - **Fixed a false positive in 1.6.31's `updating` detection** (caught immediately on a real laptop
adom-bridge-fusion-manifest.json+5−5@@ -1,13 +1,13 @@ { "manifest_version": 1, "name": "fusion360",- "version": "1.6.32",- "url": "https://wiki.adom.inc/download/adom/adom-desktop-fusion-bridge/1.6.32/adom-bridge-fusion-v1.6.32.zip",- "sha256": "daaf252426ff17ee0a327ce003cabe704d525b871f0fefe90f1969c7da184086",- "size": 330230,+ "version": "1.6.33",+ "url": "https://wiki.adom.inc/download/adom/adom-desktop-fusion-bridge/1.6.33/adom-bridge-fusion-v1.6.33.zip",+ "sha256": "70b10a6c7eabfa5116fa749a1d02b76fc5b918787fcf64b5387da567f317bc65",+ "size": 332961, "verbPrefixes": [ "fusion_" ], "healthEndpoint": "/status",- "released_at": "2026-07-06T10:51:48Z"+ "released_at": "2026-07-06T11:01:29Z" }
bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "fusion360", "displayName": "Autodesk Fusion 360",- "version": "1.6.32",+ "version": "1.6.33", "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+98−11@@ -4,7 +4,9 @@ import ctypes import ctypes.wintypes import json import os+import re import subprocess+import threading import time import urllib.error import urllib.request@@ -283,6 +285,94 @@ def _dismiss_startup_dialogs(): pass +# ── Background launch: NEVER steal the user's foreground ──────────────────────+# The bridge launches Fusion for AUTOMATION, on a machine the user is actively+# working on. Fusion normally opens its splash + main window in the FOREGROUND,+# yanking focus off whatever the user is doing. That is unacceptable (John, 2026-+# 07-06: "make sure new windows ALWAYS only open in the background"). So we (1)+# start the process MINIMIZED + NOT ACTIVATED via STARTUPINFO, and (2) run a short+# focus-guard thread that, whenever a Fusion window steals the foreground during+# the noisy startup window, minimizes it WITHOUT activating - returning focus to+# the user's work. The add-in still loads fully while minimized. To SHOW Fusion to+# the user later, a verb explicitly restores+foregrounds it (the ONLY time we ever+# foreground). See the fusion-bridge-dev skill "Background-launch invariant".+_SW_SHOWMINNOACTIVE = 7 # show minimized, do NOT activate/steal focus+++def _fusion_pids() -> set:+ """PIDs of running Fusion360.exe processes (via tasklist CSV). Empty on error."""+ try:+ cnw = subprocess.CREATE_NO_WINDOW if hasattr(subprocess, "CREATE_NO_WINDOW") else 0+ out = subprocess.check_output(+ ["tasklist", "/FI", "IMAGENAME eq Fusion360.exe", "/FO", "CSV", "/NH"],+ stdin=subprocess.DEVNULL, stderr=subprocess.DEVNULL, creationflags=cnw,+ ).decode("utf-8", "replace")+ except Exception:+ return set()+ return {int(p) for p in re.findall(r'"Fusion360\.exe","(\d+)"', out)}+++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+ startupinfo = None+ try:+ startupinfo = subprocess.STARTUPINFO()+ startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW+ startupinfo.wShowWindow = _SW_SHOWMINNOACTIVE+ except Exception:+ startupinfo = None+ return subprocess.Popen(+ [exe_path],+ stdin=subprocess.DEVNULL,+ stdout=subprocess.DEVNULL,+ stderr=subprocess.DEVNULL,+ creationflags=cnw,+ startupinfo=startupinfo,+ )+++def _focus_guard(seconds: float = 120.0):+ """For `seconds`, demote any Fusion window that steals the foreground.++ SURGICAL: only acts when the CURRENT foreground window belongs to a Fusion+ process (i.e. Fusion just grabbed focus). It minimizes that window without+ activating anything, so focus falls back to whatever the user was using. It+ does NOT touch Fusion when the user is in another app, so it never fights a+ user who deliberately opened Fusion. Best-effort; never raises."""+ try:+ user32 = ctypes.windll.user32+ except Exception:+ return+ end = time.time() + seconds+ pids = set()+ last_refresh = 0.0+ while time.time() < end:+ now = time.time()+ if now - last_refresh > 3.0:+ pids = _fusion_pids()+ last_refresh = now+ try:+ fg = user32.GetForegroundWindow()+ if fg and pids:+ wpid = ctypes.wintypes.DWORD(0)+ user32.GetWindowThreadProcessId(fg, ctypes.byref(wpid))+ if wpid.value in pids:+ # minimize the focus-stealing Fusion window, no activation+ user32.ShowWindowAsync(fg, _SW_SHOWMINNOACTIVE)+ except Exception:+ pass+ time.sleep(0.2)+++def _start_focus_guard(seconds: float = 120.0):+ """Fire-and-forget the focus guard on a daemon thread. Never raises."""+ try:+ threading.Thread(target=_focus_guard, args=(seconds,), daemon=True).start()+ except Exception:+ pass++ def ensure_fusion_running(fusion_info: dict, wait_addin: bool = False) -> dict | None: """Launch Fusion 360 if not already running. @@ -312,17 +402,14 @@ def ensure_fusion_running(fusion_info: dict, wait_addin: bool = False) -> dict | return {"success": False, "error": "Fusion 360 executable not found"} try:- # 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. Passing them through to the Fusion launcher risks a hang.- _cnw = subprocess.CREATE_NO_WINDOW if hasattr(subprocess, "CREATE_NO_WINDOW") else 0- subprocess.Popen(- [exe_path],- stdin=subprocess.DEVNULL,- stdout=subprocess.DEVNULL,- stderr=subprocess.DEVNULL,- creationflags=_cnw,- )+ # 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.+ _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) except Exception as e: return {"success": False, "error": f"Failed to launch Fusion 360: {e}"}
skills/fusion-bridge-dev/SKILL.md+29@@ -5,6 +5,35 @@ description: How to develop, version, and PUBLISH the Adom Desktop Fusion 360 br # Fusion bridge — dev + publish workflow +## ⛔ Background-launch invariant: NEVER steal the user's foreground (HARD RULE)++The bridge runs on a machine the **user is actively working on**. Anything this bridge launches or+opens must appear in the **BACKGROUND** and must **never yank focus** off what the user is doing+(John, 2026-07-06: "make sure new windows ALWAYS only open in the background so as not to disturb my+main work"). This is not best-effort etiquette — it is enforced **in code**, and any new window-+opening path you add MUST follow it:++- **Launch minimized + not-activated.** Start GUI processes with `STARTUPINFO.wShowWindow =+ SW_SHOWMINNOACTIVE (7)` + `CREATE_NO_WINDOW`. See `_popen_fusion_background()` in `fusion_detect.py`+ — copy that, never a bare `subprocess.Popen([exe])` (which opens Fusion in the foreground).+- **Guard the startup window.** `STARTUPINFO` alone is not enough — Fusion re-foregrounds itself+ several times as its splash/main window initialize. `ensure_fusion_running()` fires+ `_start_focus_guard()`: a daemon thread that, for ~120s, minimizes (no-activate) **only** a Fusion+ window that is **currently the foreground** (it detects focus-theft via `GetForegroundWindow` ++ PID match, so it never fights a user who deliberately opened Fusion). Reuse it for any new launch.+- **The add-in still loads while minimized** — background operation (open/model/export) needs no+ visible window. So minimized costs you nothing.+- **Foreground is opt-in, only to SHOW the user something.** The ONLY time you may foreground/restore+ a Fusion window is when a verb's explicit job is to show the user (and even then, per the user's+ standing rule, you surface the exact window + their viewer). Never `SetForegroundWindow` as a side+ effect of automation.+- **Applies to every window surface**, not just the Fusion launcher: any child process, helper, or+ dialog you spawn opens background/no-activate by the same means. When you add one, add a focus-guard+ or launch flag — do not assume it will behave.++If you catch yourself about to `Popen` a GUI or call a foreground API, stop and route it through the+background-launch helpers. A window that steals the user's focus is a **regression**, full stop.+ ## You OWN this bridge (do NOT file your own bugs as adom-desktop requests) AD core only HOSTS/LOADS this bridge: the `adom-desktop` CLI, the relay/passthrough, bridge lifecycle + streaming this bridge into `…\bridges-cache\fusion360`, and the core `desktop_*` verbs. **Everything