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+79−41@@ -3731,46 +3731,74 @@ def _chrome_authorize_urls(max_age_min: int = 15): return found -def _fusion_window_hwnd():- """Find the main Fusion window's hwnd by title (via desktop_list_windows). Returns int|None."""- r = _ad_call("desktop_list_windows", {}, timeout=40)- wins = (r.get("windows") if isinstance(r, dict) else None) or []- if not wins and isinstance(r, dict):- wins = ((r.get("data") or {}).get("windows")) or []- cand = None+def _fusion_window():+ """Find the main Fusion window via desktop_list_windows. Returns the whole entry (it already+ carries `rect`, so we get geometry in the SAME call - there is no desktop_window_info verb).+ Returns {} when not found."""+ wins = _unwrap(_ad_call("desktop_list_windows", {}, timeout=40)).get("windows") or []+ cand = {} for w in wins: t = str(w.get("title", "")) tl = t.lower()- if "autodesk fusion" in tl or tl.startswith("signing in") or t.strip() == "Autodesk Fusion":- # prefer the sign-in/welcome window- if "signing in" in tl or "welcome" in tl:- return w.get("hwnd")- cand = cand or w.get("hwnd")+ if "autodesk fusion" in tl or tl.startswith("signing in"):+ if "signing in" in tl or "welcome" in tl: # prefer the sign-in/welcome window+ return w+ cand = cand or w return cand -def _fusion_click_signin() -> bool:- """Click Fusion's 'Welcome to Fusion' -> Sign In button SERVER-SIDE (it is a webview with- no UIA node, so image-space click). Returns True if a click was dispatched. Runs on-box so- it adds no remote round-trip to the ~2-min OAuth window."""- hwnd = _fusion_window_hwnd()- if not hwnd:- rd = _handle_fusion_readiness({}, {})- hwnd = rd.get("hwnd") or rd.get("windowHandle")+def _fusion_window_hwnd():+ """Just the hwnd of the main Fusion window (int|None)."""+ return (_fusion_window() or {}).get("hwnd")+++def _unwrap(r):+ """AD verb responses come back either flat or nested under `data` (the CLI puts the real+ payload in output.data). Always look through both - forgetting this silently broke the+ server-side Sign In click, because coordMap/shotId live one level down."""+ if not isinstance(r, dict):+ return {}+ inner = r.get("data")+ if isinstance(inner, dict) and inner:+ return inner+ return r+++def _fusion_click_signin() -> dict:+ """Click Fusion's 'Welcome to Fusion' -> Sign In button SERVER-SIDE, ALWAYS, on the user's+ behalf (John, 2026-07-22: "you should ALWAYS just click sign in on behalf of the adom+ users"). The button is a webview with no UIA node, so this is an image-space click.++ Runs on-box, so it costs no remote round-trip against Fusion's ~2-min OAuth expiry.+ Returns {clicked: bool, why: str} - never raises.+ """+ import time as _t+ win = _fusion_window()+ hwnd = win.get("hwnd") or _unwrap(_handle_fusion_readiness({}, {})).get("hwnd") if not hwnd:- return False- shot = _ad_call("desktop_screenshot_window", {"hwnd": int(hwnd)}, timeout=45)- sd = shot if isinstance(shot, dict) else {}- cm = sd.get("coordMap") or {}- sid = cm.get("shotId") or sd.get("shotId")- w = sd.get("width") or (cm.get("imageWidth") if isinstance(cm, dict) else None) or 1400- h = sd.get("height") or (cm.get("imageHeight") if isinstance(cm, dict) else None) or 860+ return {"clicked": False, "why": "could not find the Fusion window"}++ # A MINIMIZED Fusion captures as a ~237x39 title-bar sliver, so the click lands on nothing.+ # Restore it first (seen live: rect was at -32000,-32000, the Windows minimized position).+ rect = win.get("rect") or {}+ if (rect.get("left") is not None and rect["left"] <= -30000) or \+ (0 < (rect.get("width") or 0) < 600) or (0 < (rect.get("height") or 0) < 400):+ _ad_call("desktop_set_window_state", {"hwnd": int(hwnd), "state": "restore"}, timeout=30)+ _t.sleep(1.5)++ shot = _unwrap(_ad_call("desktop_screenshot_window", {"hwnd": int(hwnd)}, timeout=45))+ cm = shot.get("coordMap") or {}+ sid = cm.get("shotId") or shot.get("shotId") if not sid:- return False+ return {"clicked": False, "why": "screenshot returned no shotId"}+ img = cm.get("image") or {}+ w = shot.get("width") or img.get("width") or cm.get("imageWidth") or 1400+ h = shot.get("height") or img.get("height") or cm.get("imageHeight") or 860 # the Sign In button sits centered, ~57% down the welcome screen- _ad_call("desktop_click", {"space": "image", "shotId": sid,- "x": int(w * 0.5), "y": int(h * 0.57)}, timeout=30)- return True+ r = _unwrap(_ad_call("desktop_click", {"space": "image", "shotId": sid,+ "x": int(w * 0.5), "y": int(h * 0.57)}, timeout=30))+ return {"clicked": True, "why": "clicked Sign In at image (%d,%d) of %sx%s" % (+ int(w * 0.5), int(h * 0.57), w, h)} def _capture_fresh_authorize(wait_sec: int = 30, max_age: int = 120):@@ -3804,7 +3832,10 @@ def _orchestrate_signin(args: dict) -> dict: the remote relay is slow. This is the reliable path; the staged/manual path is the fallback. """ steps = []- auto = bool(args.get("auto"))+ # ALWAYS click Sign In for the user unless they explicitly opt out (auto:false). Adom users+ # should never be told "now go click Sign In yourself" - we do it for them and SAY so.+ auto = args.get("auto")+ auto = True if auto is None else bool(auto) wait_sec = int(args.get("waitSec") or 32) target = args.get("profile") # explicit override, e.g. "chrome:[email protected]" if target:@@ -3832,9 +3863,11 @@ def _orchestrate_signin(args: dict) -> dict: urls = _chrome_authorize_urls() fresh = urls[0] if (urls and urls[0]["ageSec"] <= 150) else None if auto and not fresh:- # ONE server-side pass: click Fusion's Sign In, then poll on-box for the fresh URL.- clicked = _fusion_click_signin()- steps.append("clicked Fusion 'Sign In'" if clicked else "could not click Fusion 'Sign In'")+ # ONE server-side pass: click Fusion's Sign In FOR THE USER, then poll on-box for the URL.+ ck = _fusion_click_signin()+ clicked = ck.get("clicked")+ steps.append("clicked Fusion 'Sign In' for the user" if clicked+ else "could not click Fusion 'Sign In' (%s)" % ck.get("why")) got = _capture_fresh_authorize(wait_sec=wait_sec, max_age=140) if got and got["ageSec"] <= 150: urls = [got]@@ -3844,12 +3877,17 @@ def _orchestrate_signin(args: dict) -> dict: urls = [] if not urls: return {"success": True, "stage": "click_signin", "done": False, "steps": steps,- "_hint": ("No recent Fusion OAuth URL in any browser's history yet - Fusion has not "- "opened the sign-in browser. CLICK Fusion's 'Sign In' button (it is a "- "webview with no UIA node, so image-space desktop_click on the button - "- "screenshot the Fusion window for a shotId first), wait ~4s, then call "- "fusion_signin again. This verb then grabs the URL Fusion opened in the "- "WRONG default browser and re-opens it in the RIGHT profile."),+ "narrate": ("I clicked Fusion's Sign In for you, but Fusion has not written its "+ "sign-in URL to a browser yet. Give it a few seconds."),+ "_hint": ("THIS VERB CLICKS FUSION'S 'Sign In' FOR THE USER - never tell them to go "+ "click it themselves, and do not click it yourself. It just did (see "+ "steps[]), but no Fusion OAuth URL has landed in any browser's history "+ "yet. Simply CALL fusion_signin AGAIN in a few seconds; it will click if "+ "needed, wait on-box for the URL, and re-open it in the right profile. "+ "If steps[] says the click FAILED, that is a bridge bug worth reporting - "+ "the usual causes are a minimized Fusion window (this verb restores it) "+ "or the sign-in wall not being up yet (check fusion_readiness "+ "needsSignin:true)."), "statusVerb": "fusion_signin"} auth = urls[0] steps.append("captured Fusion OAuth URL from %s profile '%s' (%ss old)"