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.58
6 files changed
+128−18
BRIDGE_VERSION+1−1@@ -1 +1 @@-1.6.57+1.6.58
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