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.46: seat dialog owned-popup detection + screenshot-verified resolve + readiness self-heal
6 files changed
+164−21
BRIDGE_VERSION+1−1@@ -1 +1 @@-1.6.45+1.6.46
CHANGELOG.md+21@@ -3,6 +3,27 @@ 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.46 - 2026-07-06++- **Fixed the seat/licensing dialog going UNDETECTED (John caught it live on winvm).** The+ "Suspend Remote Session" seat-confirm variant is only **823x262**, but `find_licensing_dialog()`+ required height **320-820**, so it silently slipped through - readiness reported `licensingDialog`+ gone while the dialog sat there blocking the whole launch. Root cause: detecting a modal by a+ fragile SIZE window. Now detected DETERMINISTICALLY by the **owned-popup** signal: a seat/sign-in+ modal is always an OWNED POPUP of the main Fusion window (the splash/main app never are), found via+ `GetWindow(GW_OWNER)` in `family_windows()`. Size is only a fallback.+- **Resolution is now SCREENSHOT-VERIFIED, not heuristic-verified.** `_resolve_seat_dialog()` clicks+ 'Continue' (background UIA) then confirms the dialog truly cleared by reading the parent window's+ `ownedPopupCount` via `desktop_screenshot_window` (parent/child screenshotting) - instead of trusting+ the same size filter that had false-'resolved' it. This is the check that would have caught the+ earlier "I solved it" that hadn't.+- **Closed the auto-resolve GAP.** The launch loop only auto-resolved the seat dialog during its ~50s+ window. But the dialog also appears LATER (minutes post-sign-in, when the license server notices too+ many sessions), long after that loop exited. `fusion_readiness` now SELF-HEALS: whenever it detects+ the dialog it auto-resolves it in the background and re-probes - so the seat is handled in CODE no+ matter when it shows up, and the AI never has to notice or act.+- ctypes `GetWindow` given explicit `restype/argtypes` (HWND) to avoid 64-bit handle truncation.+ ## 1.6.45 - 2026-07-06 - **Add-in changes now AUTO-DEPLOY - the bridge no longer only installs the add-in when it's MISSING.**
adom-bridge-fusion-manifest.json+5−5@@ -1,13 +1,13 @@ { "manifest_version": 1, "name": "fusion360",- "version": "1.6.45",- "url": "https://wiki.adom.inc/download/adom/adom-desktop-fusion-bridge/1.6.45/adom-bridge-fusion-v1.6.45.zip",- "sha256": "950ef28e331e8ef278e020205d547fa732ce07973daa9b2fcbe992a441d49be1",- "size": 342643,+ "version": "1.6.46",+ "url": "https://wiki.adom.inc/download/adom/adom-desktop-fusion-bridge/1.6.46/adom-bridge-fusion-v1.6.46.zip",+ "sha256": "806b9b6dc88b289aa23fc6dbebd7da33944045dfd150808f12a0de69b4152278",+ "size": 345325, "verbPrefixes": [ "fusion_" ], "healthEndpoint": "/status",- "released_at": "2026-07-06T14:10:08Z"+ "released_at": "2026-07-06T14:51:53Z" }
bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "fusion360", "displayName": "Autodesk Fusion 360",- "version": "1.6.45",+ "version": "1.6.46", "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+32−6@@ -362,6 +362,11 @@ def family_windows() -> list: HWND = ctypes.wintypes.HWND user32.GetWindowThreadProcessId.argtypes = [HWND, ctypes.POINTER(ctypes.wintypes.DWORD)] user32.IsWindowVisible.argtypes = [HWND]+ # restype/argtypes MUST be set or the returned HWND is truncated to 32-bit on+ # 64-bit Windows and the owner handle comes back wrong (same class of bug that+ # silently broke the focus-guard, 2026-07-06).+ user32.GetWindow.restype = HWND+ user32.GetWindow.argtypes = [HWND, ctypes.c_uint] fam = _fusion_family_pids() if not fam: return results@@ -390,10 +395,13 @@ def family_windows() -> list: h = rect.bottom - rect.top if w <= 0 or h <= 0: return True+ GW_OWNER = 4+ owner = user32.GetWindow(hwnd, GW_OWNER) results.append({ "hwnd": int(hwnd), "title": title, "pid": int(wpid.value), "image": pid_img.get(int(wpid.value), ""), "width": w, "height": h, "left": rect.left, "top": rect.top,+ "owner": int(owner) if owner else 0, }) except Exception: pass@@ -413,13 +421,31 @@ def family_windows() -> list: def find_licensing_dialog():- """The seat/licensing modal ("Active Sessions Exceeded", or a sign-in prompt): a- family window that is DIALOG-SIZED (the maximized main app is far bigger; tooltips- far smaller). Detected by owning process + size, so the "Fusion360" title never- matters. Returns the window dict, or None. The seat dialog is ~1235x527."""- for w in family_windows():+ """The seat/licensing or sign-in modal ("Active Sessions Exceeded" / "Suspend Remote+ Session" / a sign-in prompt). Detected DETERMINISTICALLY by the OWNED-POPUP signal,+ NOT a fragile height window:++ A seat/sign-in modal is ALWAYS an owned popup of the main Fusion window (confirmed+ via desktop_screenshot_window's ownedPopupCount). The splash and the maximized main+ app are NEVER owned popups. So "family window that has an owner" is the reliable+ tell - it does not care about the exact dialog size or the "Fusion360" title.++ This replaced a size-only filter whose 320px height floor SILENTLY MISSED the+ 823x262 "Suspend Remote Session" confirm variant, so readiness reported the dialog+ gone while it sat there blocking (John caught it live, 2026-07-06). A size-based+ fallback is kept for safety in case owner detection ever returns 0.+ Returns the window dict (with 'owner'), or None."""+ wins = family_windows()+ # Primary: an OWNED popup (has an owner window) that is bigger than a tooltip.+ for w in wins:+ if w.get("owner") and w["width"] >= 300 and w["height"] >= 120:+ return w+ # Fallback (owner==0 for all): a dialog-sized family window that is NOT the+ # maximized main app (>1500 wide / >900 tall) and not a tooltip. Height floor 180+ # so the 262px confirm still matches.+ for w in wins: ww, hh = w["width"], w["height"]- if 640 <= ww <= 1500 and 320 <= hh <= 820:+ if 480 <= ww <= 1500 and 180 <= hh <= 900: return w return None
server.py+104−8@@ -137,6 +137,67 @@ def _resolve_seat_via_uia(dlg: dict) -> bool: return False +def _owned_popup_count(main_hwnd) -> int:+ """ownedPopupCount on a window via AD's desktop_screenshot_window - the SAME+ parent/child screenshot signal John kept pointing at (2026-07-06): a modal seat/+ error dialog shows up as an OWNED POPUP of the main Fusion window. This is the+ ground-truth 'is a dialog still up' check used to VERIFY a seat dialog actually+ cleared, instead of trusting a window-size heuristic (which false-'resolved' the+ 823x262 'Suspend Remote Session' confirm). Returns the count, or -1 if unavailable."""+ try:+ if not main_hwnd or not ad_client.available():+ return -1+ res = ad_client.call("desktop_screenshot_window", {"hwnd": int(main_hwnd)})+ if isinstance(res, dict):+ c = res.get("ownedPopupCount")+ if c is None and isinstance(res.get("data"), dict):+ c = res["data"].get("ownedPopupCount")+ if c is not None:+ return int(c)+ except Exception:+ pass+ return -1+++def _resolve_seat_dialog(max_clicks: int = 8) -> dict:+ """Detect + resolve the seat/licensing modal deterministically, IN THE BACKGROUND,+ and VERIFY it truly cleared by SCREENSHOTTING the parent window's ownedPopupCount -+ not by re-running a size heuristic (that false-'resolved' bit John hard, 2026-07-06:+ the code clicked once, the size filter then failed to re-detect the short confirm+ variant, and readiness lied that the dialog was gone while it sat there blocking).++ Each pass: find the dialog -> UIA-invoke 'Continue' (background, Suspend pre-selected+ so it grabs the license) -> screenshot the parent's ownedPopupCount. Done only when+ BOTH find_licensing_dialog() sees nothing AND the parent shows 0 owned popups (or the+ screenshot signal is unavailable and the heuristic agrees). Returns+ {sawDialog, clicks, verified}. Never raises."""+ import time as _t+ clicks = 0+ saw = False+ verified = False+ for _ in range(max_clicks):+ try:+ dlg = find_licensing_dialog()+ except Exception:+ dlg = None+ if not dlg:+ verified = True+ break+ saw = True+ parent = dlg.get("owner")+ _resolve_seat_via_uia(dlg)+ clicks += 1+ _t.sleep(1.5)+ # Ground-truth confirm via parent/child screenshot (John's insisted-on check).+ cnt = _owned_popup_count(parent)+ if cnt == 0:+ verified = True+ break+ # cnt == -1 (screenshot unavailable): fall through, the next find_licensing_dialog+ # pass decides. cnt > 0: still up, loop and click again.+ return {"sawDialog": saw, "clicks": clicks, "verified": verified}++ def _handle_launch(fusion_info: dict, args: dict) -> dict: """Launch Fusion 360 and optionally wait for the AdomBridge add-in.""" # LIVE detect (not the stale bridge-start fusion_info snapshot) so a launch right@@ -221,10 +282,12 @@ def _handle_launch(fusion_info: dict, args: dict) -> dict: except Exception: _lic = None if _lic:+ # Resolve + SCREENSHOT-VERIFY it cleared (parent ownedPopupCount), not a+ # size heuristic that false-'resolved' the short confirm variant. if _seat_clicks < 10:- if _resolve_seat_via_uia(_lic):- _seat_clicks += 1- _t.sleep(1.5)+ _r = _resolve_seat_dialog(max_clicks=3)+ _seat_clicks += _r.get("clicks", 0)+ _t.sleep(1.0) continue # No seat dialog -> is the add-in actually serving? (ground truth for drivable) if _probe_addin(timeout=1.5):@@ -853,12 +916,39 @@ def _handle_fusion_readiness(fusion_info: dict, args: dict) -> dict: # instance answers while the CURRENT Fusion sits blocked behind the seat dialog, so # gating on `not addin_ok` false-negatived `licensingDialog` while the dialog sat # right there on the user's screen.+ #+ # ⚠️ GAP FIX (John caught it live 2026-07-06): the launch loop only auto-resolves the+ # seat dialog during its ~50s window. But this dialog also appears LATER - minutes+ # after sign-in, when the license server notices too many sessions - long after that+ # loop exited, so it just sat there blocking while readiness passively reported+ # licensingDialog:true. Now readiness SELF-HEALS: whenever it detects the dialog it+ # AUTO-RESOLVES it in the background (UIA 'Continue' + screenshot-verify), so the seat+ # is handled deterministically in CODE no matter when it shows up - the AI never has+ # to notice or act. (John: "do all this tracking from your code so it's deterministic+ # ... the ai never follows skills.")+ seat_resolved = False licensing = False if running: try: licensing = find_licensing_dialog() is not None except Exception: licensing = False+ if licensing:+ try:+ _r = _resolve_seat_dialog()+ seat_resolved = _r.get("clicks", 0) > 0+ # Re-check: did it actually clear? (screenshot-verified inside.)+ licensing = find_licensing_dialog() is not None+ except Exception:+ pass+ if not licensing:+ # Cleared - re-probe the add-in; Fusion may be drivable now.+ try:+ addin_status = _check_addin_status(timeout=1.5)+ addin_ok = addin_status is not None+ ready = installed and running and addin_ok and not needs_signin+ except Exception:+ pass if licensing: ready = False # a seat/licensing dialog blocking the UI is never "ready" @@ -900,11 +990,14 @@ def _handle_fusion_readiness(fusion_info: dict, args: dict) -> dict: elif ready: hint = "READY - drive Fusion (fusion_open_lbr / build_library_3d / etc.)." elif installed and running and licensing:- hint = ("Fusion is blocked by a SEAT/LICENSING dialog ('Active Sessions Exceeded') - detected "- "by owning process + dialog size (its title is just 'Fusion360'). fusion_start "- "AUTO-RESOLVES it in the BACKGROUND by UIA-invoking its 'Continue' button (no "- "foreground, no user action) and retries until it clears. If you see this persist, just "- "call fusion_start again - it is not a crash, and never coordinate-click or ask the user.")+ hint = ("Fusion is blocked by a SEAT/LICENSING dialog ('Active Sessions Exceeded' / 'Suspend "+ "Remote Session') - detected DETERMINISTICALLY as an OWNED POPUP of the main Fusion "+ "window (its title is just 'Fusion360' and it can be as short as 262px, so it is found "+ "by owner+parent-screenshot, NOT a size guess). This verb JUST TRIED to auto-resolve it "+ "in the BACKGROUND (UIA 'Continue', Suspend pre-selected so it reclaims the seat) but it "+ "is still up - it may be mid-render (the first UIA Invoke no-ops). Just POLL "+ "fusion_readiness again in a few seconds; it keeps auto-resolving. Never coordinate-click "+ "or ask the user, and never kill/relaunch (the server keeps the seat).") 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 / signing in (poll fusion_readiness a few more times), or the "@@ -939,6 +1032,9 @@ def _handle_fusion_readiness(fusion_info: dict, args: dict) -> dict: if licensing: result["licensingDialog"] = True result["statusVerb"] = "fusion_readiness"+ if seat_resolved:+ # We acted on it this call (whether or not it fully cleared yet).+ result["seatDialogAutoResolved"] = True return result