← Commit history
BRIDGE_VERSION+1−1
@@ -1 +1 @@-1.6.57\ No newline at end of file+1.6.58\ No newline at end of file
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.58 - 2026-07-08++- **Long cloud ops now SURVIVE AD's 60s relay cap (John: "get all 4 pins perfect" + "update your code+  with your learnings").** `fusion_build_library_3d` cloud Package3D + Hub upload takes MINUTES, but the+  relay hard-caps requests at ~60s and `timeoutSeconds` is ignored - so the synchronous build got its+  thread killed at 60s, wrote no bound .lbr, and lost every wip_urn (a demo board's 4th pin came back a+  flat pad). It now runs DETACHED (`async` default true) + is POLLABLE: returns immediately, keeps+  building in a daemon thread, writes the bound .lbr when done; caller polls it for one `<package3d+  wip_urn=urn:...>` per part. `async:false` for fast machines.+- **Background, no-dialog EAGLE board import documented + steered to.** For a `.brd` with placed+  `<elements>`, `fusion_open_board` (newDesignFromLocal) instantiates them with ZERO modal dialogs / ZERO+  foreground - vs `fusion_new_electronics_from_eagle`'s ImportSCHAndBRDCmd, which pops two Open dialogs+  that STEAL focus. The import verb no longer dead-ends when the AD direct API is absent; it points to+  `fusion_open_board`. NEW: embed `<packages3d>` + a per-`<element>` `package3d_urn` in the `.brd` to get+  REAL 3D component bodies on the board (Fusion resolves the Hub urns) - documented in the verb docstring.+- **Fixed the HWND-truncation window-finder bug (cost hours).** `handlers/fusion_ui.py` declared NO+  ctypes argtypes, so 64-bit HWND handles truncated to 32-bit `c_int` - `_find_fusion_hwnd`'s+  GetWindowTextW/IsWindowVisible ran on garbage handles and matched nothing, so `get_fusion_window_info`+  returned `mainHwnd:None` even though Fusion's window plainly existed, blinding all background driving.+  Declared HWND argtypes/restypes for the window funcs so the full handle marshals.+ ## 1.6.57 - 2026-07-08  - **APS sign-in now REMEMBERS the browser+profile that authed to Autodesk and reuses it, instead of
adom-bridge-fusion-manifest.json+4−4
@@ -1,10 +1,10 @@ {   "manifest_version": 1,   "name": "fusion360",-  "version": "1.6.57",-  "url": "https://wiki.adom.inc/download/adom/adom-desktop-fusion-bridge/1.6.57/adom-bridge-fusion-v1.6.57.zip",-  "sha256": "77349a04a8486c16c371364ea1fa01e331e2f8de81cfc2dc567d3694eb81ac00",-  "size": 344711,+  "version": "1.6.58",+  "url": "https://wiki.adom.inc/download/adom/adom-desktop-fusion-bridge/1.6.58/adom-bridge-fusion-v1.6.58.zip",+  "sha256": "d528bad4a61f7adab8a15a77eaaa4c197e263ee392cb43b5d627a98943a3c0a9",+  "size": 347717,   "verbPrefixes": [     "fusion_"   ],
bridge.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "fusion360",   "displayName": "Autodesk Fusion 360",-  "version": "1.6.57",+  "version": "1.6.58",   "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.",
handlers/fusion_ui.py+25
@@ -20,6 +20,31 @@ user32 = ctypes.windll.user32 if _have_windll else None gdi32 = ctypes.windll.gdi32 if _have_windll else None kernel32 = ctypes.windll.kernel32 if _have_windll else None +# ⚠️ HWND TRUNCATION BUG (fixed 2026-07-08, cost hours): with NO argtypes declared, ctypes+# defaults every function arg + return to 32-bit `c_int`, which TRUNCATES 64-bit HWND handles.+# So `_find_fusion_hwnd`'s GetWindowTextW/IsWindowVisible ran on a garbage handle and matched+# NOTHING - get_fusion_window_info returned mainHwnd:None even though Fusion's window ("Untitled+# - Autodesk Fusion") plainly existed, which blinded all background driving. (Verified live: the+# window's real hwnd via `(Get-Process Fusion360).MainWindowHandle` worked fine.) Declaring HWND+# argtypes/restypes makes ctypes marshal the full 64-bit handle. If the finder ever returns 0/None+# while Fusion IS running, the process-handle fallback in _find_fusion_hwnd covers it.+if _have_windll:+    _W = ctypes.wintypes+    for _fn, _arg, _ret in (+        ("IsWindowVisible",       [_W.HWND],                              ctypes.c_bool),+        ("IsWindowEnabled",       [_W.HWND],                              ctypes.c_bool),+        ("GetWindowTextLengthW",  [_W.HWND],                              ctypes.c_int),+        ("GetWindowTextW",        [_W.HWND, _W.LPWSTR, ctypes.c_int],     ctypes.c_int),+        ("GetClassNameW",         [_W.HWND, _W.LPWSTR, ctypes.c_int],     ctypes.c_int),+        ("GetWindow",             [_W.HWND, ctypes.c_uint],               _W.HWND),+        ("GetParent",             [_W.HWND],                              _W.HWND),+        ("IsWindow",              [_W.HWND],                              ctypes.c_bool),+    ):+        try:+            _f = getattr(user32, _fn); _f.argtypes = _arg; _f.restype = _ret+        except Exception:+            pass+ # Enable DPI awareness so GetWindowRect returns physical pixel coordinates # (not logical/scaled). Without this, screenshots are cropped on HiDPI displays. try:
server.py+76−12
@@ -270,14 +270,28 @@ def _handle_new_electronics_from_eagle(args: dict) -> dict:     so the parts actually INSTANTIATE (schematic + populated board), then land in the PCB     editor. Proven live 2026-07-07 on winvm. -    THE KEY FINDING: `Document.newDesignFromLocal <file.sch>` opens the schematic EDITOR-    but does NOT instantiate the parts (board_info == 0). Fusion's REAL EAGLE importer is-    the internal command `ImportSCHAndBRDCmd` - it pops TWO native "Open" file dialogs (first-    the .sch, then the .brd) and builds a proper board-linked electronics design. This verb-    fires that command and drives BOTH dialogs entirely in the BACKGROUND via the AD direct-    API (desktop_ui_click by accessible name - no foreground, no coordinates). The Fusion--    native .fsch/.fbrd container is opaque BINARY (Manifest.dat + BulkStream segments), so-    you CANNOT author it offline - going through Fusion's importer is the only way.+    ⭐ PREFER THE BACKGROUND PATH FIRST (learned 2026-07-08, the hard way): if you have a+    `.brd` with the parts ALREADY PLACED (`<elements>` with x/y + an embedded `<library>`),+    do NOT use this verb - call **`fusion_open_board {filePath: <.brd>}`** instead. It opens+    the board via `Document.newDesignFromLocal`, which INSTANTIATES the placed elements with+    ZERO modal file-dialogs and ZERO foreground (this verb's ImportSCHAndBRDCmd pops TWO+    native Open dialogs that STEAL the user's focus - modal dialogs always foreground, there+    is no background way to drive them). A hand-authored EAGLE `.brd` (well-formed XML: layers+    + board outline on layer 20 + libraries/packages + elements) imports cleanly this way.+    ➜ FOR 3D BODIES ON THAT BOARD (not flat pads): embed a `<packages3d>` section (each+    `<package3d name=... wip_urn="urn:adsk.wipprod:fs.file:vf...."/>` from a prior+    `fusion_build_library_3d` bind) in the `.brd`'s `<library>`, AND give every `<element>` a+    `package3d_urn="<that pin's wip_urn>"`. Then `fusion_open_board` + `fusion_show_3d_board`+    renders the REAL component 3D (Fusion resolves the urns from the Hub). No urn on the+    element = a flat pad. (If a urn is stale/unresolved, re-run `fusion_build_library_3d` for+    that part to mint a fresh one.)++    THIS VERB is only for a legacy `.sch` whose parts must be instantiated by Fusion's own+    importer: `Document.newDesignFromLocal <file.sch>` opens the schematic EDITOR but does NOT+    instantiate parts (board_info == 0); Fusion's REAL EAGLE importer is `ImportSCHAndBRDCmd`,+    which pops the two Open dialogs. This verb drives them via the AD direct API when present -+    but that FOREGROUNDS Fusion, so use the `.brd`+`fusion_open_board` route whenever you can+    place the parts yourself. The native `.fsch/.fbrd` container is opaque BINARY, un-authorable.      args: {schPath: Windows path to the .sch (required), brdPath: Windows path to the .brd            (optional; defaults to the .sch's sibling .brd)}@@ -290,10 +304,18 @@ def _handle_new_electronics_from_eagle(args: dict) -> dict:                          "to Windows (send_files), then call with {schPath, brdPath}."}     brd = (args.get("brdPath") or (sch[:-4] + ".brd")).replace("\\", "/")     if not ad_client.available():-        return {"success": False, "error": "AD direct API unavailable - cannot drive the import dialogs.",-                "_hint": "This verb needs AD >=1.9.84 (the bridge drives the two Open dialogs via desktop_ui_click). "-                         "Fallback: fire 'Commands.Start ImportSCHAndBRDCmd' via fusion_execute_text_command and "-                         "drive the two Open dialogs yourself (select the .sch then .brd by accessible name)."}+        # Do NOT dead-end here (old bug: hard-failed even on latest AD when this bridge process+        # didn't get AD's injected direct-API env). The RIGHT move is the background route.+        brd_guess = (args.get("brdPath") or (sch[:-4] + ".brd")).replace("\\", "/")+        return {"success": False, "errorCode": "use_open_board_instead",+                "error": "AD direct API unavailable - and this dialog-driven import FOREGROUNDS Fusion anyway.",+                "_hint": ("Don't drive the Open dialogs. If the parts are placed in the .brd (elements + "+                          "embedded library), call `fusion_open_board {filePath:\"" + brd_guess + "\"}` - it "+                          "instantiates them via newDesignFromLocal with NO dialogs and NO foreground (the "+                          "background way). For 3D bodies, embed <packages3d> + per-element package3d_urn in the "+                          ".brd (see this verb's docstring), then fusion_show_3d_board. Only if you truly have a "+                          "bare .sch that Fusion must import: fire 'Commands.Start ImportSCHAndBRDCmd' via "+                          "fusion_execute_text_command and drive the two Open dialogs (foreground; last resort).")}      def _find_open_dialog():         res = ad_client.call("desktop_list_windows", {}) or {}@@ -2459,6 +2481,48 @@ def _orchestrate_make_3d_package(args: dict) -> dict:   def _orchestrate_build_library_3d(args: dict) -> dict:+    """Bind real 3D onto a multi-part library. Runs DETACHED by default (async=True) so it+    SURVIVES AD's 60s relay cap.++    ⚠️ LEARNED THE HARD WAY (2026-07-08): the cloud Package3D generate + Hub upload takes MINUTES+    on a real machine, but AD's relay hard-caps every request at ~60s and `timeoutSeconds` is NOT+    honored for this verb - so the old synchronous build got its thread KILLED at 60s, wrote no+    bound .lbr, and lost every wip_urn (3/4 pins on a demo board came back as flat pads because the+    4th never got a fresh urn). Fix: the build now runs in a BACKGROUND daemon thread and returns+    immediately; the caller POLLS the bound `outLbrPath` file (read_file) until it has one+    `<package3d ... wip_urn=urn:...>` per part. Pass `async:false` only on a fast machine where the+    whole build fits under 60s. Then embed those package3d + a per-`<element>` `package3d_urn` in a+    `.brd` and `fusion_open_board` + `fusion_show_3d_board` shows the REAL 3D bodies (background).++    args: {..., async?: bool (default TRUE - detached + pollable)}. See _build_library_3d_core.+    """+    combined = (args.get("lbrPath") or "").replace("\\", "/")+    out_path = (args.get("outLbrPath") or combined).replace("\\", "/")+    if args.get("async", True) and args.get("parts"):+        import threading as _th++        def _run():+            try:+                _build_library_3d_core(args)+            except Exception:+                pass+        _th.Thread(target=_run, daemon=True).start()+        return {+            "success": True, "status": "started", "async": True,+            "boundLbr": out_path, "partsTotal": len(args.get("parts") or []),+            "_hint": (+                "⏳ 3D bind runs DETACHED (survives AD's 60s relay cap, which used to kill it + lose "+                "every wip_urn). POLL the boundLbr via read_file every ~30s until it has one "+                "<package3d name=... wip_urn=urn:...> PER PART (count == partsTotal). Do NOT re-fire "+                "while running (check fusion_addin_status.busy). When done, embed those <packages3d> + "+                "a per-<element> package3d_urn in your .brd, then fusion_open_board + fusion_show_3d_board "+                "for real 3D bodies (all background). If a urn stays unresolved in the 3D view, re-run "+                "for just that part - its f3d upload failed."),+        }+    return _build_library_3d_core(args)+++def _build_library_3d_core(args: dict) -> dict:     """Build a RENDERING multi-part 3D library in ONE call - the whole programmatic pipeline.      For each part: make its footprint+chip f3d package (no GUI dialogs, optional BEFORE/AFTER