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.9.38: envelope-proof update detection + readiness offers
4 files changed
+51−9
BRIDGE_VERSION+1−1@@ -1 +1 @@-1.9.37+1.9.38
adom-bridge-fusion-manifest.json+4−4@@ -1,10 +1,10 @@ { "manifest_version": 1, "name": "fusion360",- "version": "1.9.37",- "url": "https://wiki.adom.inc/download/adom/fusion-bridge/1.9.37/adom-bridge-fusion-v1.9.37.zip",- "sha256": "478062c241e344a8258dacde34281ff237217557ec039af24dc167caa88d8d4a",- "size": 384345,+ "version": "1.9.38",+ "url": "https://wiki.adom.inc/download/adom/fusion-bridge/1.9.38/adom-bridge-fusion-v1.9.38.zip",+ "sha256": "128cd2992aab0db5274f67acc9fd452f5d757777bcea8c15dd0c18d14308f869",+ "size": 384995, "verbPrefixes": [ "fusion_" ],
bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "fusion360", "displayName": "Autodesk Fusion 360",- "version": "1.9.37",+ "version": "1.9.38", "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/fusion-bridge", "author": "Adom Inc.",
server.py+45−3@@ -1445,6 +1445,21 @@ def _handle_fusion_readiness(fusion_info: dict, args: dict) -> dict: # always want the latest fusion... make this generally invisible to me"). Fusion nags # constantly; we click it in the BACKGROUND so the user never sees it. Sticky opt-out. _upd = _apply_fusion_update_silently()+ if _LAST_UPDATE_OFFER.get("offered"):+ out["fusionUpdateOffered"] = True+ out["fusionUpdateHandled"] = bool(_upd.get("applied"))+ if _upd.get("applied"):+ out.setdefault("_hints", []).append(+ "Fusion offered an update and I already clicked %r in the background - "+ "tell the user it is handled, they never need to touch the nag."+ % _upd.get("button"))+ else:+ out.setdefault("_hints", []).append(+ "Fusion is OFFERING AN UPDATE and auto-update is %s. OFFER to run it for "+ "the user ('want me to update Fusion in the background?') - "+ "fusion_set_auto_update {enabled:true} makes it permanent, or a one-off "+ "background click happens on the next sweep after they say yes."+ % ("opted out" if not _auto_update_enabled() else "on but the click failed")) # APS is checked on EVERY launch now (John, 2026-07-22: "why aren't those just driven from # 1 login?"). Fusion sign-in and APS are both Autodesk logins; the user should authenticate # ONCE. They cannot share a token (Autodesk DPAPI-encrypts Fusion's store), but they CAN@@ -4063,6 +4078,31 @@ def _auto_update_enabled() -> bool: _UPDATE_BUTTONS = ("Update Now", "Update now", "Install Now", "Restart Now") +def _find_controls_any(obj, depth=0):+ """Envelope-proof extraction of UIA find_control results: ab has moved best/controls+ across response levels between versions (same drift family as coordMap). Returns+ {best, controls} from anywhere in the response, parsing stringified output."""+ if depth > 5:+ return {}+ if isinstance(obj, str) and obj[:1] in "[{":+ try:+ import json as _j+ return _find_controls_any(_j.loads(obj), depth + 1)+ except Exception:+ return {}+ if isinstance(obj, dict):+ if isinstance(obj.get("best"), dict) or isinstance(obj.get("controls"), list):+ return {"best": obj.get("best") or {}, "controls": obj.get("controls") or []}+ for k in ("data", "output", "result", "response"):+ got = _find_controls_any(obj.get(k), depth + 1)+ if got:+ return got+ return {}+++_LAST_UPDATE_OFFER = {"offered": False, "at": 0}++ def _find_update_offer(hwnd=None) -> dict: """Is Fusion offering an update right now? Detected via UIA (background, no foreground). Returns {offered, button, hwnd, detail} - never raises."""@@ -4083,9 +4123,11 @@ def _find_update_offer(hwnd=None) -> dict: pass for h in candidates: for label in _UPDATE_BUTTONS:- r = _unwrap(_ad_call("desktop_find_control", {"hwnd": h, "name": label}, timeout=20))+ r = _find_controls_any(_ad_call("desktop_find_control", {"hwnd": h, "name": label}, timeout=20)) best = r.get("best") or {} if best.get("name") and best.get("invokable"):+ import time as _ut+ _LAST_UPDATE_OFFER.update(offered=True, at=_ut.time(), button=best.get("name")) return {"offered": True, "button": best.get("name"), "hwnd": h, "detail": "Fusion is offering an update ('%s' is on screen)" % best.get("name")} return {"offered": False, "hwnd": hw}@@ -4805,8 +4847,8 @@ def _advance_credentials_page(hwnd, email) -> dict: import time as _t out = {"advanced": False, "wall": "credentials", "detail": ""} try:- found = _unwrap(_ad_call("desktop_find_control", {"hwnd": int(hwnd), "role": "edit"}, timeout=40))- ctrls = found.get("controls") or found.get("matches") or []+ found = _find_controls_any(_ad_call("desktop_find_control", {"hwnd": int(hwnd), "role": "edit"}, timeout=40))+ ctrls = found.get("controls") or [] if not ctrls and found.get("best"): ctrls = [found["best"]] pwd = [c for c in ctrls if "password" in str(c.get("name", "")).lower()