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.23: fusion_install_fusion (zero-approval install, elevation-aware)
8 files changed
+104−14
BRIDGE_VERSION+1−1@@ -1 +1 @@-1.6.22+1.6.23
CHANGELOG.md+11@@ -3,6 +3,17 @@ 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.23 - 2026-07-04++- **NEW `fusion_install_fusion` - install Fusion for the user with ZERO approval-gated shell.** The+ clean-VM drill showed every shell_execute step (download/spawn/tasklist) hits AD's "approve 1 hr"+ gate; bridge verbs run as trusted installed code and don't. The verb downloads Autodesk's official+ Client Downloader inside the bridge (urllib), is ELEVATION-AWARE (--globalinstall when admin,+ PER-USER otherwise - a non-elevated globalinstall dies silently on UAC, learned live), spawns it+ detached, and returns promptly. `fusion_readiness` now live-redetects and reports `installing:true`+ while the streamer runs. Declared as `detect.installVerb` (AD >=1.9.79) + in hostAppOptionalVerbs+ so it forwards on a Fusion-less box - completing the contract slot left empty in 1.6.22.+ ## 1.6.22 - 2026-07-04 - **Adopted AD 1.9.79's two new GENERIC bridge contracts** (previously Fusion-specific AD-core code):
SKILL.md+1−1@@ -24,7 +24,7 @@ Branch on the stable `errorCode`, never the prose: | `ready:true` (fusion_readiness) | Fusion up + add-in current | drive | | `running:false` / `fusion_not_running` | installed, not launched | `fusion_start` (returns when the add-in is ready, or `stillLaunching` on a slow first launch) | | `stillLaunching:true` (fusion_start) | first launch, add-in not up yet (can take 2-4 min) | do NOT re-call `fusion_start`; POLL `fusion_readiness` every ~10s until `ready:true` |-| `installed:false` / `host_app_not_installed` (AD >=1.9.79, + a `hostApp` field) | no Fusion host app | [fusion-onboarding](skills/fusion-onboarding/SKILL.md); AD DETECTS Fusion (never installs it) - OFFER to install for the user |+| `installed:false` / `host_app_not_installed` (AD >=1.9.79, + a `hostApp` field) | no Fusion host app | OFFER to install, then **`fusion_install_fusion`** (no shell approval; elevation-aware, streams the free trial) -> poll `fusion_readiness` (`installing:true`) until `installed:true` -> `fusion_start`. Details: [fusion-onboarding](skills/fusion-onboarding/SKILL.md) | | `addinStale:true` / `addin_stale` | Fusion runs an OLD add-in that never re-synced (silent-failure risk) | `fusion_stop` then `fusion_start` — the bridge re-syncs the add-in from cache on start (Fusion must be CLOSED) | | `bridge_restarting` + `stillRunning:true` (AD ≥1.9.77) | the bridge is respawning after a `bridge_install` | NEVER a not-installed/fatal signal: poll `fusion_readiness` until `ready:true`, then retry. Read-only verbs (`fusion_readiness`/`fusion_describe`/`fusion_get_app_state`) forward straight through even mid-respawn — no sleep-and-recheck hacks needed | | `blockingDialogs:[…]` | a modal is blocking the add-in | dismiss in the BACKGROUND, then the op auto-resumes |
adom-bridge-fusion-manifest.json+5−5@@ -1,13 +1,13 @@ { "manifest_version": 1, "name": "fusion360",- "version": "1.6.22",- "url": "https://wiki.adom.inc/download/adom/adom-desktop-fusion-bridge/1.6.22/adom-bridge-fusion-v1.6.22.zip",- "sha256": "6d5cc9750ed8041620ae85daa0a66e9c00a9f3863d64d914fa69a7ea87eed716",- "size": 313297,+ "version": "1.6.23",+ "url": "https://wiki.adom.inc/download/adom/adom-desktop-fusion-bridge/1.6.23/adom-bridge-fusion-v1.6.23.zip",+ "sha256": "bc25141b660d02172c07f679f1c8910e7532030092504774585bc9c39ff59275",+ "size": 315255, "verbPrefixes": [ "fusion_" ], "healthEndpoint": "/status",- "released_at": "2026-07-04T21:30:00Z"+ "released_at": "2026-07-04T22:40:00Z" }
bridge.json+8−4@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "fusion360", "displayName": "Autodesk Fusion 360",- "version": "1.6.22",+ "version": "1.6.23", "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.",@@ -59,6 +59,7 @@ "fusion_export_usdz", "fusion_generate_package", "fusion_get_app_state",+ "fusion_install_fusion", "fusion_kill", "fusion_load_design_rules", "fusion_make_3d_package",@@ -130,8 +131,10 @@ "aps_status", "aps_search", "aps_browse",- "aps_recent"- ]+ "aps_recent",+ "install_fusion"+ ],+ "installVerb": "fusion_install_fusion" }, "prewarm": { "assets": []@@ -164,7 +167,8 @@ "make_3d_package": 200, "build_library_3d": 1800, "capture_library_views": 600,- "start": 90+ "start": 90,+ "install_fusion": 90 }, "prefixes": [ {
describe.py+2@@ -79,6 +79,8 @@ _T = [ ("fusion_attach_3d_package", "Attach a real 3D chip model to a library package, end to end: opens the .lbr (library active), runs Electron.Create3DPackage to enter the Package3DEnvironment showing the footprint, imports the STEP + auto-orients it flat on the footprint, then executes Package3DStop (FINISH). This BINDS the 3D onto the deviceset (Content Manager shows the 3D nested under the package; the Packages 'Package' column flips Placeholder->part-name). ONE desktop-side step remains: Fusion pops a modal Save dialog (an OWNED popup) - the response _hint gives the exact desktop_ui_click to confirm it. AFTER: re-grab desktop_screenshot_window and check ownedPopupCount for errors; the 3D preview LAGS a beat before showing the real chip. Both paths (filePath/modelPath) are WINDOWS paths - stage from the container first.", {"filePath": "required str (.lbr WINDOWS path C:/...)", "modelPath": "required str (.step WINDOWS path C:/...)", "packageName": "optional str"}, {"success": "bool", "savePending": "bool", "data": "{steps}", "_hint": "the Save-dialog click + verify steps"}, 240, _STATUS, True, {"filePath": "C:/Users/<user>/adom-lib/ADS8588SIPM.fusion.lbr", "modelPath": "C:/Users/<user>/adom-lib/ADS8588SIPM.step", "packageName": "ADS8588SIPM"}),+ ("fusion_install_fusion", "Install Fusion 360 FOR the user - downloads Autodesk's official Client Downloader and streams the free trial silently, with NO shell_execute and NO AD approval gate (runs inside the trusted bridge). Elevation-aware: --globalinstall when admin, PER-USER otherwise (a non-elevated globalinstall dies silently on UAC - learned live). Returns promptly; poll fusion_readiness (reports installing:true) until installed:true, then fusion_start. Declared as detect.installVerb so AD's bridge_readiness recommends it.",+ {}, {"success": "bool", "installing": "bool", "alreadyInstalled": "bool?", "mode": "globalinstall|per-user", "statusVerb": "str"}, 90, _STATUS, True, {}), ("fusion_generate_package", "Generate an IPC-7351-compliant parametric 3D package via Fusion's BUILT-IN ElectronicsPackageGenerator (EPG) - ~50 package families (chip/soic/qfn/qfp/bga/sot23/dfn/melf/ecap/crystal/headers/...), each a few seconds, ZERO GUI. Optionally LASER-ETCH a marking (MPN / '103') into the body top as REAL cut geometry (survives STEP), and export STEP in the same call - the royalty-free, license-clean path to a whole component library (our own generated output, not vendor models). Proven live: 0603 + '103' etch + STEP in ~2s.", {"type": "required str (EPG family, e.g. chip|soic|qfn|qfp|bga|sot23|dfn2|melf|ecap|crystal|header_straight - see supportedTypes in the error for the full list)", "params": "optional dict (generator dims in MM by default, e.g. chip: {D,E,A,L,L1}; soic: {A,A1,b,D,E,E1,e,L,DPins}; omitted keys use EPG defaults)", "unitsCm": "optional bool (params already EPG-native cm)", "etch": "optional str (marking text on the chip top: MPN / '103'; multi-line with newline, e.g. MPN+variant - laid along the LONGEST face axis)", "etchStyle": "optional str raised|engraved (default raised = thin WHITE positive extrude, silkscreen-style contrast; engraved = sunken cut)", "etchDepthMm": "optional float (marking height/depth, default 0.03)", "etchHeightMm": "optional float (text height; default auto-fit: top-5%-band face, 10% margin, width-aware)", "outputStep": "optional str (WINDOWS path; exports STEP incl. the marking)"}, {"success": "bool", "bodies": "[{name,vol}]", "etched": "str|null", "stepPath": "str|null", "manifest": "obj (settings+why+bboxes; also written as <step>.manifest.json sidecar for marking refreshes)", "manifestPath": "str|null", "epgDir": "str"}, 120, _STATUS, True, {"type": "chip", "params": {"D": 1.6, "E": 0.8, "A": 0.45, "L": 0.3, "L1": 0.3}, "etch": "103", "outputStep": "C:/tmp/ADOM-R0603-103.step"}),
page.json+1−1@@ -1,6 +1,6 @@ { "slug": "adom-desktop-fusion-bridge",- "version": "1.6.22",+ "version": "1.6.23", "type": "app", "description": "Drive Autodesk Fusion 360 from the cloud via Adom Desktop: 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 with browse/recent/versions, cloud download/upload and folder creation, and in-app parametric modeling. Never-charge: APS calls are capped to the free tier.", "dependencies": {},
server.py+75−2@@ -648,14 +648,82 @@ def _handle_relocate_recovery(fusion_info: dict, args: dict) -> dict: } +def _installer_running() -> bool:+ """Is the Fusion Client Downloader / streamer currently installing? Bridge-side+ check (subprocess from OUR process = no AD shell-approval gate)."""+ try:+ import subprocess as _sp+ out = _sp.run(["tasklist", "/FO", "CSV", "/NH"], capture_output=True, text=True,+ timeout=15, creationflags=getattr(_sp, "CREATE_NO_WINDOW", 0)).stdout+ return ("streamer.exe" in out) or ("FusionDL.exe" in out) or ("Fusion Client Downloader" in out)+ except Exception:+ return False+++def _handle_install_fusion(fusion_info: dict, args: dict) -> dict:+ """Install Fusion 360 FOR the user - no shell_execute, no AD approval gate.++ Declared as detect.installVerb (AD >=1.9.79) so bridge_readiness recommends+ THIS over the generic winget fallback. Downloads the official Fusion Client+ Downloader and runs it silently: --globalinstall when elevated, PER-USER when+ not (learned live: a non-admin shell makes --globalinstall die silently on+ UAC; the per-user install needs no elevation and lands in %LOCALAPPDATA%,+ which detect covers). Returns PROMPTLY - the stream takes 10-30 min; poll+ fusion_readiness until installed:true."""+ if fusion_info.get("installed") or detect_fusion().get("installed"):+ return {"success": True, "alreadyInstalled": True,+ "_hint": "Fusion 360 is already installed - call fusion_start to launch it."}+ if _installer_running():+ return {"success": True, "installing": True, "statusVerb": "fusion_readiness",+ "_hint": "An install is ALREADY streaming (10-30 min). Poll fusion_readiness "+ "until installed:true - do not start a second installer."}++ import tempfile, urllib.request as _ur, subprocess as _sp, ctypes as _ct+ stub = os.path.join(tempfile.gettempdir(), "FusionClientDownloader.exe")+ try:+ _ur.urlretrieve(+ "https://dl.appstreaming.autodesk.com/production/installers/Fusion%20Client%20Downloader.exe",+ stub)+ except Exception as e:+ return {"success": False, "error": f"Installer download failed: {e}",+ "errorCode": "installer_download_failed",+ "_hint": "Check the box is online; retry fusion_install_fusion. The stub URL is "+ "Autodesk's official appstreaming installer."}+ try:+ elevated = bool(_ct.windll.shell32.IsUserAnAdmin()) if hasattr(_ct, "windll") else False+ except Exception:+ elevated = False+ cmd = [stub, "--quiet"] + (["--globalinstall"] if elevated else [])+ try:+ _sp.Popen(cmd, stdin=_sp.DEVNULL, stdout=_sp.DEVNULL, stderr=_sp.DEVNULL,+ creationflags=getattr(_sp, "CREATE_NO_WINDOW", 0) | getattr(_sp, "DETACHED_PROCESS", 0))+ except Exception as e:+ return {"success": False, "error": f"Installer launch failed: {e}",+ "errorCode": "installer_launch_failed"}+ return {+ "success": True, "installing": True, "elevated": elevated,+ "mode": "globalinstall" if elevated else "per-user",+ "statusVerb": "fusion_readiness",+ "_hint": ("Fusion 360 install STARTED (" + ("system-wide" if elevated else+ "per-user - no admin needed; a non-elevated --globalinstall dies silently on UAC") ++ "). It streams several GB (10-30 min): poll fusion_readiness until installed:true "+ "(it also reports installing:true while the streamer runs), then fusion_start. "+ "NOTIFY the user it is underway (progress toast) and again at the Autodesk sign-in."),+ }++ def _handle_fusion_readiness(fusion_info: dict, args: dict) -> dict: """FAST readiness check for the AI: is the Fusion host app present + running + the bridge ready? Does NOT launch Fusion (fast-fails when it's not running). AD DETECTS Fusion (never installs it) via bridge.json 'detect', and auto-installs Python if missing (AD >=1.9.47) - this verb just reports state. Pairs with AD's bridge_readiness.""" installed = bool(fusion_info.get("installed"))+ if not installed:+ # re-detect live: an install may have JUST finished (fusion_info is from bridge start)+ installed = bool(detect_fusion().get("installed")) running = _is_fusion_running() if installed else False ready = installed and running+ installing = (not installed) and _installer_running() # When Fusion is up, also check the add-in isn't STALE (issue #55) so a fresh # readiness call surfaces the mismatch instead of a later verb failing silently.@@ -672,13 +740,17 @@ def _handle_fusion_readiness(fusion_info: dict, args: dict) -> dict: elif installed: hint = "Fusion is INSTALLED but not running - call fusion_start (blocks until the add-in is ready), then retry." else:- hint = ("Fusion 360 is NOT installed. AD detects (never installs) the host app via bridge.json 'detect'; "- "ask the user to install Fusion from autodesk.com. (AD provisions Python itself - no bridge bootstrap.)")+ hint = ("Fusion install is STREAMING (10-30 min) - keep polling fusion_readiness until installed:true."+ if installing else+ "Fusion 360 is NOT installed. OFFER to install it for the user, then call "+ "fusion_install_fusion (no shell approval needed) - it streams the free trial and "+ "this verb reports installing:true until done.") return { "success": True, "hostApp": "Fusion 360", "installed": installed,+ "installing": installing, "running": running, "ready": ready, "bridgeVersion": BRIDGE_VERSION,@@ -690,6 +762,7 @@ def _handle_fusion_readiness(fusion_info: dict, args: dict) -> dict: COMMAND_HANDLERS = { "describe": lambda fi, args: describe.handle_describe(args), "readiness": _handle_fusion_readiness,+ "install_fusion": _handle_install_fusion, "open_design": handle_open_design, "close": handle_close_fusion, # deprecated alias - use stop (graceful) / kill (force) "stop": handle_fusion_stop, # graceful: close docs + WM_CLOSE, NO force-kill