← Commit history

v1.6.66: find_licensing_dialog never matches the MAIN app window (was false-firing on a non-maximized main window inside the shape gate, and once ad_client came online the seat resolver churned on it and hung readiness). Exclude the largest family window; bound the seat resolver (max_clicks 3, 4s AD-call timeout) so a readiness tick can't hang.

John Lauer ·bbc3761b4e ·3mo ago ·parent 73483c4
5 files changed +27−10
BRIDGE_VERSION+1−1
@@ -1 +1 @@-1.6.65\ No newline at end of file+1.6.66\ No newline at end of file
adom-bridge-fusion-manifest.json+4−4
@@ -1,10 +1,10 @@ {   "manifest_version": 1,   "name": "fusion360",-  "version": "1.6.65",-  "url": "https://wiki.adom.inc/download/adom/adom-desktop-fusion-bridge/1.6.65/adom-bridge-fusion-v1.6.65.zip",-  "sha256": "c8b457a911913e54afa2679cddc4e321129fa3aec02bbaffb6aab1bc5876f445",-  "size": 359576,+  "version": "1.6.66",+  "url": "https://wiki.adom.inc/download/adom/adom-desktop-fusion-bridge/1.6.66/adom-bridge-fusion-v1.6.66.zip",+  "sha256": "64833c2f7d513837e4a70b86f2de3939c57c7dee06b5ad46f2a326c03869e479",+  "size": 360022,   "verbPrefixes": [     "fusion_"   ],
bridge.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "fusion360",   "displayName": "Autodesk Fusion 360",-  "version": "1.6.65",+  "version": "1.6.66",   "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+15−1
@@ -564,13 +564,27 @@ def find_licensing_dialog():     narrow panels), height >= 180 (excludes toolbar strips), and smaller than the maximized     main app."""     wins = family_windows()+    if not wins:+        return None+    # NEVER match the MAIN app window. A seat/sign-in modal is a smaller popup; the main Fusion+    # window is the LARGEST family window. The old no-owner fallback matched the main window whenever+    # it happened to fall inside the shape gate (e.g. a non-maximized 1300x820 window fits+    # 480<=w<=1500, 180<=h<=900) - so find_licensing_dialog false-fired on the drivable main app,+    # and once ad_client came online the seat resolver churned 8x on it and HUNG readiness (John,+    # live 2026-07-14). Excluding the largest window is size-independent and safe: a dialog is never+    # bigger than the app that owns it.+    main_hwnd = max(wins, key=lambda w: w["width"] * w["height"])["hwnd"]     # Primary: an OWNED popup that is DIALOG-SHAPED (wide modal, not a narrow side panel).     for w in wins:+        if w["hwnd"] == main_hwnd:+            continue         ww, hh = w["width"], w["height"]         if w.get("owner") and 480 <= ww <= 1500 and 180 <= hh <= 900:             return w-    # Fallback (owner==0 for all - owner detection failed): same shape gate, no owner req.+    # Fallback (owner==0 for all - owner detection failed): same shape gate, still NOT the main window.     for w in wins:+        if w["hwnd"] == main_hwnd:+            continue         ww, hh = w["width"], w["height"]         if 480 <= ww <= 1500 and 180 <= hh <= 900:             return w
server.py+6−3
@@ -131,7 +131,7 @@ def _resolve_seat_via_uia(dlg: dict) -> bool:         hwnd = dlg.get("hwnd")         if not hwnd or not ad_client.available():             return False-        res = ad_client.call("desktop_ui_click", {"hwnd": hwnd, "name": "Continue"})+        res = ad_client.call("desktop_ui_click", {"hwnd": hwnd, "name": "Continue"}, timeout=4)         return bool(res and (res.get("success") or res.get("status") == "ok"))     except Exception:         return False@@ -147,7 +147,7 @@ def _owned_popup_count(main_hwnd) -> int:     try:         if not main_hwnd or not ad_client.available():             return -1-        res = ad_client.call("desktop_screenshot_window", {"hwnd": int(main_hwnd)})+        res = ad_client.call("desktop_screenshot_window", {"hwnd": int(main_hwnd)}, timeout=4)         if isinstance(res, dict):             c = res.get("ownedPopupCount")             if c is None and isinstance(res.get("data"), dict):@@ -159,7 +159,10 @@ def _owned_popup_count(main_hwnd) -> int:     return -1  -def _resolve_seat_dialog(max_clicks: int = 8) -> dict:+def _resolve_seat_dialog(max_clicks: int = 3) -> dict:+    # max_clicks kept LOW (was 8): readiness calls this every tick and it SELF-HEALS across ticks, so+    # a single call must stay responsive - 8 clicks x (UIA click + screenshot-verify, each up to its+    # timeout) could blow readiness's whole budget and make the bridge look hung (John 2026-07-14).     """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: