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
server.py+134−3@@ -1367,25 +1367,40 @@ def _handle_fusion_readiness(fusion_info: dict, args: dict) -> dict: elif ready and stale_info.get("addinStale"): hint = stale_info["_staleHint"] elif ready:+ # AUTO-UPDATE, checked on EVERY launch and applied SILENTLY (John, 2026-07-22: "i just+ # 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() # 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 # share the warm browser SSO session - so APS consent is silent right after a Fusion login. _aps = _aps_quick_state()+ _updmsg = ""+ if _upd.get("applied"):+ _updmsg = (" AUTO-UPDATE: Fusion was offering an update and I clicked %r for the user "+ "IN THE BACKGROUND - it downloads now and the new build comes up on the next "+ "launch. TELL THE USER it is handled; they never need to click the nag. "+ "They can opt out with fusion_set_auto_update {enabled:false} (sticky)."+ % _upd.get("button"))+ elif not _auto_update_enabled():+ _updmsg = (" AUTO-UPDATE is OFF (the user opted out, sticky) - do NOT click Fusion's "+ "update prompts for them.") if _aps.get("signedIn"): hint = ("READY - drive Fusion (fusion_open_lbr / build_library_3d / etc.). APS cloud "- "search is signed in too, so use fusion_aps_search for ~2s cloud lookups.")+ "search is signed in too, so use fusion_aps_search for ~2s cloud lookups."+ + _updmsg) elif _aps.get("configured"): hint = ("READY to drive Fusion, but APS CLOUD SEARCH IS NOT SIGNED IN - cloud file " "search will be slow/blocked. DO IT NOW, do not make the user log in a second " "time: call fusion_aps_signin RIGHT AFTER a Fusion sign-in, while the browser " "profile still has a warm Autodesk SSO session - it consents silently (no "- "password, no 2FA). Then fusion_aps_search works.")+ "password, no 2FA). Then fusion_aps_search works." + _updmsg) else: hint = ("READY to drive Fusion. APS cloud search is NOT SET UP (no client ID) - cloud " "search is the slow path without it. Offer to set it up: fusion_aps_set_client_id " "then fusion_aps_signin (silent while the browser SSO session is warm). APS is "- "how cloud search gets to ~2 seconds.")+ "how cloud search gets to ~2 seconds." + _updmsg) elif installed and running and licensing: hint = ("Fusion is blocked by a SEAT/LICENSING dialog ('Active Sessions Exceeded' / 'Suspend " "Remote Session') - detected DETERMINISTICALLY as an OWNED POPUP of the main Fusion "@@ -1685,6 +1700,7 @@ COMMAND_HANDLERS = { "describe": lambda fi, args: describe.handle_describe(args), "readiness": _handle_fusion_readiness, "set_preference": _handle_set_preference,+ "set_auto_update": _handle_set_auto_update, "get_preferences": _handle_get_preferences, "install_fusion": _handle_install_fusion, "open_design": handle_open_design,@@ -3677,6 +3693,121 @@ def _ad_call(verb: str, args: dict, timeout: int = 60) -> dict: return {"_adCallError": "; ".join(why)} +# ── FUSION AUTO-UPDATE (John, 2026-07-22) ────────────────────────────────────────────────────+# "fusion ships updates non-stop and its annoying to click this. i just always want the latest+# fusion... just make this generally invisible to me and do that by default for all other adom+# users. but let them tell you they don't want you doing that and you make that a sticky setting."+#+# So: ON BY DEFAULT for everyone, applied silently in the BACKGROUND, opt-out is sticky per user.+# Fusion's own updater downloads in the background and swaps the build on the next launch, so all+# we have to do is stop making the human click "Update Now" on a nag panel.+def _bridge_prefs_path():+ import pathlib+ d = pathlib.Path(os.path.expanduser("~")) / ".adom" / "fusion-bridge"+ d.mkdir(parents=True, exist_ok=True)+ return d / "prefs.json"+++def _get_bridge_pref(key: str, default=None):+ try:+ import json as _j+ p = _bridge_prefs_path()+ if p.exists():+ v = (_j.loads(p.read_text()) or {})+ return v.get(key, default)+ except Exception:+ pass+ return default+++def _set_bridge_pref(key: str, value) -> bool:+ try:+ import json as _j+ p = _bridge_prefs_path()+ cur = {}+ if p.exists():+ try: cur = _j.loads(p.read_text()) or {}+ except Exception: cur = {}+ cur[key] = value+ p.write_text(_j.dumps(cur, indent=2))+ return True+ except Exception:+ return False+++def _auto_update_enabled() -> bool:+ """Default TRUE. Only a user who explicitly opted out gets False, and that sticks."""+ return bool(_get_bridge_pref("autoUpdateFusion", True))+++# Buttons Fusion puts on its update nag / Job Status panel, best-first.+_UPDATE_BUTTONS = ("Update Now", "Update now", "Install Now", "Restart Now")+++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."""+ try:+ hw = hwnd or _fusion_window_hwnd()+ if not hw:+ return {"offered": False}+ for label in _UPDATE_BUTTONS:+ r = _unwrap(_ad_call("desktop_find_control", {"hwnd": int(hw), "name": label}, timeout=25))+ best = r.get("best") or {}+ if best.get("name") and best.get("invokable"):+ return {"offered": True, "button": best.get("name"), "hwnd": hw,+ "detail": "Fusion is offering an update ('%s' is on screen)" % best.get("name")}+ return {"offered": False, "hwnd": hw}+ except Exception:+ return {"offered": False}+++def _apply_fusion_update_silently(hwnd=None) -> dict:+ """Click Fusion's update button IN THE BACKGROUND so the download starts and the new build+ is applied on the next launch. No foreground, no caption - the user asked for this to be+ invisible. Returns {applied, button, why}."""+ if not _auto_update_enabled():+ return {"applied": False, "why": "user opted out (sticky pref autoUpdateFusion=false)"}+ offer = _find_update_offer(hwnd)+ if not offer.get("offered"):+ return {"applied": False, "why": "no update offered"}+ r = _unwrap(_ad_call("desktop_ui_click",+ {"hwnd": int(offer["hwnd"]), "name": offer["button"]}, timeout=30))+ ok = bool(r.get("invoked") or r.get("success"))+ if ok:+ print("[Fusion Bridge] auto-update: clicked %r in the background" % offer["button"])+ return {"applied": ok, "button": offer["button"],+ "why": ("clicked %r in the background; Fusion downloads now and swaps on next launch"+ % offer["button"]) if ok else "found the button but the UIA invoke did not take"}+++def _handle_set_auto_update(fusion_info: dict, args: dict) -> dict:+ """Turn Fusion auto-updating on/off. STICKY - remembered for this user forever."""+ if "enabled" not in (args or {}):+ cur = _auto_update_enabled()+ return {"success": True, "autoUpdateFusion": cur,+ "_hint": ("Fusion auto-update is currently %s. It is ON BY DEFAULT: the bridge "+ "clicks Fusion's 'Update Now' for the user in the BACKGROUND so they "+ "always run the latest build and never see the nag. To opt out: "+ "fusion_set_auto_update {\"enabled\": false} - that choice is STICKY."+ % ("ON" if cur else "OFF")),+ "statusVerb": "fusion_readiness"}+ enabled = bool(args.get("enabled"))+ _set_bridge_pref("autoUpdateFusion", enabled)+ return {"success": True, "autoUpdateFusion": enabled,+ "narrate": ("I'll keep Fusion updated automatically in the background."+ if enabled else+ "I'll stop auto-updating Fusion. You'll see Autodesk's own update prompts."),+ "_hint": ("Saved and sticky (~/.adom/fusion-bridge/prefs.json). %s"+ % ("Auto-update ON: the bridge silently clicks 'Update Now' whenever Fusion "+ "offers one, so the user always runs the newest build. Tell them it is "+ "handled and they never need to click it."+ if enabled else+ "Auto-update OFF: the bridge will NOT touch update prompts; the user "+ "handles Autodesk's nag themselves. Do not override this.")),+ "statusVerb": "fusion_readiness"}++ def _fusion_signin_cfg_path(): import pathlib d = pathlib.Path(os.path.expanduser("~")) / ".adom" / "fusion-signin"