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.63: FIX launch regression - run the FusionLauncher.exe that HAS its .ini beside it (the stub), not the app-dir launcher (no .ini) which throws 'Error Launching Streamed Application'. Separate 'installed?'(Fusion360.exe) from 'which launcher'(the .ini-bearing one). Stop mis-blaming a healthy install as incomplete.
4 files changed
+67−41
BRIDGE_VERSION+1−1@@ -1 +1 @@-1.6.62+1.6.63
adom-bridge-fusion-manifest.json+4−4@@ -1,10 +1,10 @@ { "manifest_version": 1, "name": "fusion360",- "version": "1.6.62",- "url": "https://wiki.adom.inc/download/adom/adom-desktop-fusion-bridge/1.6.62/adom-bridge-fusion-v1.6.62.zip",- "sha256": "71007d08f29036151f947769a7cab289a20a2a9a5f71b6d6044ccf9b9356d4c7",- "size": 357246,+ "version": "1.6.63",+ "url": "https://wiki.adom.inc/download/adom/adom-desktop-fusion-bridge/1.6.63/adom-bridge-fusion-v1.6.63.zip",+ "sha256": "8db07d4fb1be353eb055cc37681e14700b4c4da3e47cd2eecbb46c14cad91a17",+ "size": 357907, "verbPrefixes": [ "fusion_" ],
bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "fusion360", "displayName": "Autodesk Fusion 360",- "version": "1.6.62",+ "version": "1.6.63", "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+61−35@@ -129,11 +129,25 @@ def _any_app_complete() -> bool: def _find_fusion_launcher() -> Path | None:- """Return the FusionLauncher.exe to launch, but ONLY when a COMPLETE install exists (a hash dir- with Fusion360.exe). Otherwise None -> detect_fusion() reports NOT installed, and readiness- surfaces the streamer as installing. Prefers the launcher living beside the newest complete app;- falls back to any FusionLauncher.exe on disk, then to Fusion360.exe itself."""- complete_dirs = [] # (mtime, subdir)+ """Return the FusionLauncher.exe to RUN, but only when Fusion is actually INSTALLED.++ TWO SEPARATE QUESTIONS (conflating them is what broke the launch, John 2026-07-14):+ 1. Is Fusion installed? -> a hash dir contains the real app binary Fusion360.exe.+ 2. Which launcher do I run? -> a FusionLauncher.exe that HAS its FusionLauncher.exe.ini+ BESIDE IT. Running a FusionLauncher.exe WITHOUT its .ini throws Fusion's own+ "Error Launching Streamed Application ... FusionLauncher.exe.ini is missing or incomplete".+ The complete APP dir (615 files, Fusion360.exe) frequently has the launcher but NO .ini; a+ separate tiny STUB dir carries FusionLauncher.exe + its ~412-byte .ini and knows how to load+ the app. So we must return the launcher-WITH-.ini, NOT the app dir's bare launcher.++ Because we gate on Fusion360.exe first, the stub's .ini is guaranteed complete by then (mid-stream+ there is no Fusion360.exe, so we return None and nothing launches into a half-written install)."""+ if not _any_app_complete():+ return None # not installed (or still streaming) -> None++ runnable = [] # (mtime, launcher) - FusionLauncher.exe that has its .ini beside it+ bare = [] # (mtime, launcher) - FusionLauncher.exe with NO .ini (fallback only)+ app_exes = [] # (mtime, Fusion360.exe) - last-ditch fallback for base in _WEBDEPLOY_BASES: if not base.exists(): continue@@ -142,26 +156,24 @@ def _find_fusion_launcher() -> Path | None: except OSError: continue for subdir in subdirs:+ try:+ mtime = subdir.stat().st_mtime+ except OSError:+ mtime = 0.0+ launcher = subdir / "FusionLauncher.exe"+ if launcher.exists():+ if (subdir / "FusionLauncher.exe.ini").exists():+ runnable.append((mtime, launcher))+ else:+ bare.append((mtime, launcher)) if _app_dir_complete(subdir):- try:- mtime = subdir.stat().st_mtime- except OSError:- mtime = 0.0- complete_dirs.append((mtime, subdir))-- if not complete_dirs:- return None # nothing complete -> NOT installed- complete_dirs.sort(key=lambda c: c[0], reverse=True) # newest complete install wins- newest = complete_dirs[0][1]- launcher = newest / "FusionLauncher.exe"- if launcher.exists():- return launcher- # complete app dir lacks its own launcher (unusual) - use any FusionLauncher.exe, else the app exe- for _mt, subdir in complete_dirs:- cand = subdir / "FusionLauncher.exe"- if cand.exists():- return cand- return newest / _FUSION_APP_EXE+ app_exes.append((mtime, subdir / _FUSION_APP_EXE))++ for pool in (runnable, bare, app_exes): # prefer launcher+ini, then bare launcher, then app exe+ if pool:+ pool.sort(key=lambda c: c[0], reverse=True)+ return pool[0][1]+ return None def _incomplete_webdeploy_present() -> bool:@@ -800,23 +812,37 @@ def ensure_fusion_running(fusion_info: dict, wait_addin: bool = False) -> dict | close_dialog_bg(d.get("hwnd")) except Exception: pass- removed = _clean_incomplete_webdeploy()- if launch_err or removed:++ # DISTINGUISH the two causes (John 2026-07-14 - conflating them mis-blamed a healthy install):+ # (a) app is COMPLETE (Fusion360.exe present) but launch failed -> almost always we launched a+ # FusionLauncher.exe WITHOUT its .ini, or a stale lock from a just-killed instance. The+ # install is FINE; do NOT tell the user to re-stream. _find_fusion_launcher now prefers the+ # launcher-with-.ini, so a retry usually succeeds.+ # (b) app is NOT complete -> a genuinely half-streamed install; clean the partial dirs so the+ # next fusion_install_fusion re-streams clean.+ if _any_app_complete(): return { "success": False,- "error": "Fusion launch failed: INCOMPLETE streamed install (FusionLauncher.exe.ini "- "missing or truncated).",- "errorCode": "fusion_incomplete_install",- "cleanedWebdeployDirs": removed,+ "error": "Fusion is INSTALLED (Fusion360.exe present) but did not start within 30s"+ + (" - a launch-error dialog was dismissed" if launch_err else "") + ".",+ "errorCode": "fusion_launch_failed",+ "installed": True, "dialog": (launch_err or {}).get("title"),- "_hint": "AUTO-RECOVERED: dismissed the launch-error dialog and removed the corrupt "- "webdeploy dir(s). Re-run fusion_install_fusion, then WAIT for fusion_readiness "- "to report installing:false (the streamer has exited) BEFORE fusion_start - "- "starting mid-stream is what corrupts the install.",+ "_hint": "The install is COMPLETE - do NOT re-stream. Retry fusion_start (it now picks the "+ "FusionLauncher.exe that has its .ini beside it; a stale lock from a just-killed "+ "instance also clears on a retry). If it keeps failing, fusion_kill then wait a "+ "few seconds before fusion_start.", }+ removed = _clean_incomplete_webdeploy() return { "success": False,- "error": "Fusion 360 launched but process not found after 30s",+ "error": "Fusion launch failed: the streamed install is INCOMPLETE (no Fusion360.exe yet).",+ "errorCode": "fusion_incomplete_install",+ "cleanedWebdeployDirs": removed,+ "dialog": (launch_err or {}).get("title"),+ "_hint": "AUTO-RECOVERED: dismissed the launch-error dialog and removed the partial webdeploy "+ "dir(s). Re-run fusion_install_fusion, then WAIT for fusion_readiness to report "+ "installed:true BEFORE fusion_start - starting mid-stream is what corrupts the install.", } # Give it time to finish initializing UI