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
Update 164 file(s)
17 files changed
+67−38
BRIDGE_VERSION+1−1README.md+3−2bridge.json+10−3contracts/describe.json+15dashboard/Cargo.lock+1−1dashboard/Cargo.toml+1−1describe.py+2install.sh+1−1package.json+1−1page.json+10−26rust/Cargo.toml+1−1rust/bridge/src/main.rs+20rust/bridge/src/services.rs+1−1tools/check-naming.shtools/check-portable.shtools/promote.shtools/release.shBRIDGE_VERSION+1−1@@ -1 +1 @@-1.10.42+1.10.43
README.md+3−2@@ -482,7 +482,7 @@ The AI does the whole setup - the user never gets handed a download link: ## Full verb catalog All verbs return a rich `_hint`. `fusion_describe '{}'` is the machine-readable source of truth; this table is generated from it.-_160 verbs._ Regenerate with `python3 tools/gen-readme-verbs.py`.+_161 verbs._ Regenerate with `python3 tools/gen-readme-verbs.py`. **Lifecycle and readiness** @@ -678,6 +678,7 @@ _160 verbs._ Regenerate with `python3 tools/gen-readme-verbs.py`. - `fusion_mcp_tools` - Full tool list from Autodesk's Fusion MCP server INCLUDING input schemas, so you can construct a valid fusion_mcp_call. - `fusion_prefs_close` - Apply+OK (save:true, the default) or Cancel Fusion's Preferences dialog. - `fusion_prefs_open` - Open Fusion's Preferences dialog and optionally jump to a section, then hand back a SCREENSHOT + shotId you can click in.+- `fusion_prewarm` - Warm the bridge ahead of the first real call, WITHOUT starting Fusion: confirms the add-in is synced into every Fusion add-in folder, readies the space-free... - `fusion_set_preference` - Set Fusion appearance/navigation preferences in the LIVE session - applies immediately, NO restart. ## Two install layers (they are NOT the same thing)@@ -704,7 +705,7 @@ adom-wiki pkg install adom/fusion-bridge # the skills pack for your AI Auto-updates via `updateManifestUrl` (ab polls on launch + every 4h). Every release lands on the insiders manifest first, and the public manifest moves only after the pre-production gate, so the-two tiers can serve different versions. 160 verbs -+two tiers can serve different versions. 161 verbs - `fusion_describe '{}'` returns the full machine-readable catalog. More demos: <video controls width="100%" src="https://wiki.adom.inc/api/v1/pages/fusion-bridge/files/library2-3d-montage.mp4"></video>
bridge.json+10−3@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "fusion360", "displayName": "Autodesk Fusion 360",- "version": "1.10.42",+ "version": "1.10.43", "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.",@@ -141,6 +141,7 @@ "fusion_preflight_lbr", "fusion_prefs_close", "fusion_prefs_open",+ "fusion_prewarm", "fusion_prove_result", "fusion_readiness", "fusion_reset_workspace",@@ -242,7 +243,12 @@ "installVerb": "fusion_install_fusion" }, "prewarm": {- "assets": []+ "verb": "fusion_prewarm",+ "requiresHostApp": true,+ "assets": [+ "AdomBridge add-in sync",+ "PCB design-rules catalog cache"+ ] }, "timeouts": { "default": 60,@@ -277,7 +283,8 @@ "set_preference": 40, "get_preferences": 25, "demo": 300,- "signin": 150+ "signin": 150,+ "prewarm": 120 }, "prefixes": [ {
contracts/describe.json+15@@ -2322,6 +2322,21 @@ "summary": "Open Fusion's Preferences dialog and optionally jump to a section, then hand back a SCREENSHOT + shotId you can click in. This is how you reach ANY Fusion preference: the Python API exposes only a thin slice (theme/orbit/units, debuggingPort, developer tools) and everything else, including the MCP server toggle, is UI-only. Sections: general, api, design, manufacture, electronics, render, drawing, material, graphics, network, preview features. Child sections under General are lazily rendered by Qt and never appear in the UIA tree, which is why this returns an image to click rather than control names. ALWAYS finish with fusion_prefs_close {save:true} - nothing is saved otherwise, and killing/restarting Fusion first discards the change.", "timeoutSeconds": 120 },+ {+ "example": {},+ "input": {},+ "longRunning": false,+ "name": "fusion_prewarm",+ "output": {+ "fusionInstalled": "bool",+ "fusionStarted": "bool (always false)",+ "ok": "bool",+ "steps": "[{step, ok, ...}]"+ },+ "statusVerb": null,+ "summary": "Warm the bridge ahead of the first real call, WITHOUT starting Fusion: confirms the add-in is synced into every Fusion add-in folder, readies the space-free staging folder, and caches the PCB design-rules catalog from adom/pcb-design-rules. Adom Bridge calls it once per installed version in the background (bridge.json prewarm.verb); safe to call again any time. Launching Fusion is never part of it.",+ "timeoutSeconds": 120+ }, { "example": { "runId": "a3f1c2d4e5b6"
dashboard/Cargo.lock+1−1@@ -4,7 +4,7 @@ version = 4 [[package]] name = "fusion-dashboard"-version = "1.10.42"+version = "1.10.43" dependencies = [ "serde_json", ]
dashboard/Cargo.toml+1−1@@ -1,6 +1,6 @@ [package] name = "fusion-dashboard"-version = "1.10.42"+version = "1.10.43" edition = "2021" [dependencies]
describe.py+2@@ -40,6 +40,8 @@ _T = [ {"hwnd": "required int (the dialog window)"}, {"success": "bool"}, 30, None, False, {"hwnd": 123456}), ("fusion_get_app_state", "Current app state: running, active document, workspace, isElectronics. The poll/status verb for long-running calls.", {}, {"activeDocument": "str", "activeWorkspace": "str", "isElectronics": "bool"}, 20, None, False, {}),+ ("fusion_prewarm", "Warm the bridge ahead of the first real call, WITHOUT starting Fusion: confirms the add-in is synced into every Fusion add-in folder, readies the space-free staging folder, and caches the PCB design-rules catalog from adom/pcb-design-rules. Adom Bridge calls it once per installed version in the background (bridge.json prewarm.verb); safe to call again any time. Launching Fusion is never part of it.",+ {}, {"ok": "bool", "steps": "[{step, ok, ...}]", "fusionInstalled": "bool", "fusionStarted": "bool (always false)"}, 120, None, False, {}), ("fusion_readiness", "FAST readiness check (does NOT launch Fusion): is the Fusion host app installed + running + the bridge ready to drive? ab DETECTS Fusion (never installs it) via bridge.json 'detect', and auto-installs Python if missing (ab >=1.9.47). Call this before driving Fusion; if installed-but-not-running, fusion_start. SELF-HEALS the seat/licensing dialog (background UIA + screenshot-verify) whenever it detects one. Pairs with ab's bridge_readiness.", {}, {"installed": "bool", "running": "bool", "ready": "bool", "hostApp": "str", "bridgeVersion": "str"}, 15, None, False, {}),
install.sh+1−1@@ -21,7 +21,7 @@ echo "Installed fusion-bridge skills: $(ls "$PKG_DIR/skills" 2>/dev/null | tr '\ # binary (the three-artifact pattern: bridge zip -> ab, THIS pkg -> container skills + # this download, page media -> wiki). Per the wiki's PKG_VS_RELEASE policy, binaries are # platform-tagged RELEASE assets, never tarball content - the installer fetches them.-DASH_VERSION="1.10.42"+DASH_VERSION="1.10.43" DASH_URL="https://wiki.adom.inc/download/adom/fusion-bridge/${DASH_VERSION}/fusion-dashboard" DEST="" if [ -w /usr/local/bin ]; then DEST=/usr/local/bin
package.json+1−1@@ -1,6 +1,6 @@ { "slug": "fusion-bridge",- "version": "1.10.42",+ "version": "1.10.43", "type": "app", "description": "This package installs the bridge's SKILLS into your container so your AI knows how to drive it; Adom Bridge loads the bridge runtime itself from the release zip.", "dependencies": {
page.json+10−26@@ -1,6 +1,6 @@ { "slug": "fusion-bridge",- "version": "1.10.42",+ "version": "1.10.43", "type": "app", "description": "This package installs the bridge's SKILLS into your container so your AI knows how to drive it; Adom Bridge loads the bridge runtime itself from the release zip.", "dependencies": {@@ -11,6 +11,10 @@ "uninstall": "./uninstall.sh" }, "tags": [+ "ab",+ "bridge",+ "fusion",+ "desktop-app", "fusion360", "autodesk", "cad",@@ -76,6 +80,9 @@ "add a 3d model to a component", "make a fusion 360 library with 3d", "set up the fusion bridge",+ "fusion dashboard",+ "fusion status page",+ "open the fusion launch page", "aps not configured", "share aps client id across my org", "set up fusion cloud search for my company",@@ -108,28 +115,5 @@ "label": "Part volume", "prompt": "How much material does each printed part in this assembly use?" }- ],- "agent_permissions": {- "allow_bash": [],- "allow": [- "adom-desktop-fusion-bridge read-only verbs (via the adom-bridge CLI): fusion_readiness, fusion_get_app_state, fusion_addin_status, fusion_check_dialogs - state queries; they change nothing.",- "adom-desktop-fusion-bridge read-only verbs: fusion_alerts (reads Fusion's own log for toasts), fusion_document_info, fusion_window_info, fusion_board_info, fusion_library_parts - inspect only.",- "adom-desktop-fusion-bridge read-only verbs: fusion_prove_result (a persisted run report), fusion_plan_library_import (report-only preflight), fusion_list_cloud_projects, fusion_describe.",- "adom-desktop-fusion-bridge evidence captures fusion_screenshot_fusion and fusion_capture_library_views read pixels and write local image files only."- ],- "soft_deny": [- "adom-desktop-fusion-bridge lifecycle verbs fusion_stop, fusion_kill, fusion_restart_for_addin close or kill the Fusion application - keep the human prompt.",- "adom-desktop-fusion-bridge fusion_install_fusion silently installs Autodesk Fusion on the user's machine.",- "adom-desktop-fusion-bridge fusion_cleanup_cloud_files deletes files from the user's Autodesk cloud hub.",- "adom-desktop-fusion-bridge managed-library verbs (fusion_managed_library_push/pull/create/link/unlink) publish to or overwrite the user's library.io libraries."- ],- "environment": [- "adom-desktop-fusion-bridge is the Fusion 360 bridge for Adom Bridge; its fusion_* verbs are invoked through the adom-bridge CLI and drive Autodesk Fusion on the user's own desktop.",- "adom-desktop-fusion-bridge desktop actions also pass Adom Bridge's own human-approval gate (caller identity, per-call reason, Activity Log) - a container approval is the first of two gates."- ]- },- "author": {- "name": "John Lauer",- "email": "[email protected]"- }-}+ ]+}
rust/Cargo.toml+1−1@@ -4,7 +4,7 @@ members = ["core", "lbr", "os-win", "os-mac", "bridge"] # One version for the whole bridge, in lockstep with BRIDGE_VERSION (tools/release.sh bumps both). [workspace.package]-version = "1.10.42"+version = "1.10.43" edition = "2021" license = "MIT"
rust/bridge/src/main.rs+20@@ -454,6 +454,26 @@ fn main() { "fusion_live_feed" | "live_feed" if addin_owner_is_exe(&**h) => { let pids: Vec<u32> = h.fusion_processes().iter().map(|p| p.pid).collect(); let mut r = livefeed::live_feed(&caller, pids, &parsed["args"]); r["shell"] = json!("rust"); r }, "fusion_check_3d_thumbnail" | "check_3d_thumbnail" if addin_owner_is_exe(&**h) => { let mut r = prove::check_3d_thumbnail(&parsed["args"]); r["shell"] = json!("rust"); r }, "fusion_resolve_seat_dialog" | "resolve_seat_dialog" if addin_owner_is_exe(&**h) => { let (sc, ca) = (sidecar.clone(), caller.clone()); let side = move |verb: &str, a: Value, t: u64| proxy_with_timeout(&sc, &json!({"command": verb, "args": a}).to_string(), &ca, t); let dlg = |r: Value| r; let ctx = addin::Ctx { host: &**h, fusion_exe: h.fusion_executable(), short_path: |p| host().short_path(p), state_dir: h.bridge_state_dir(), caller: caller.clone(), pids: h.fusion_processes().iter().map(|p| p.pid).collect(), sidecar: &side, dialogs: &dlg }; let mut r = readiness_exec::resolve_seat_dialog_verb(&ctx, &parsed["args"]); r["shell"] = json!("rust"); r },+ "fusion_prewarm" | "prewarm" => {+ // ab calls this once per installed version, in the background, after the first+ // boot that follows an install (adom-bridge 2.1.153, page issue #99). It fetches+ // what a first real call would wait for and NEVER starts Fusion: launching the+ // user's CAD app unasked is not a prewarm. Idempotent.+ let t0 = Instant::now(); let sd = h.bridge_state_dir();+ let mut steps = Vec::new(); let mut ok = true;+ let fusion_installed = h.fusion_executable().is_some();+ let dirs = h.addin_dirs();+ if fusion_installed || dirs.iter().any(|d| d.exists()) {+ let r = addin_install::install(&dirs);+ let refused = r["refused"].as_str().map(str::to_string);+ if refused.is_some() { ok = false; }+ steps.push(json!({"step": "add-in synced into Fusion's add-in folders", "ok": refused.is_none(), "installedTo": r["installedTo"], "refused": refused}));+ } else { steps.push(json!({"step": "add-in sync", "ok": true, "skipped": "Fusion is not installed on this machine"})); }+ match fusionpath::stage_root(&sd) { Ok(p) => steps.push(json!({"step": "space-free staging folder ready", "ok": true, "path": p.to_string_lossy()})), Err(e) => { ok = false; steps.push(json!({"step": "staging folder", "ok": false, "error": e})); } }+ match services::design_rules_index(&sd) { Ok(i) => steps.push(json!({"step": "PCB design-rules catalog cached", "ok": true, "packs": i["vendors"].as_array().map(|a| a.len()).unwrap_or(0)})), Err(e) => steps.push(json!({"step": "PCB design-rules catalog", "ok": false, "error": e.chars().take(160).collect::<String>(), "note": "not fatal: fetched on first use"})) }+ json!({"success": ok, "ok": ok, "shell": "rust", "fusionInstalled": fusion_installed, "fusionStarted": false, "steps": steps, "durationMs": t0.elapsed().as_millis() as u64,+ "_hint": if fusion_installed { "Prewarmed without starting Fusion. The first fusion_start still pays Fusion's own launch; nothing a bridge can do ahead of time shortens that." } else { "Fusion is not installed here, so there is nothing to warm. fusion_readiness explains onboarding." }})+ }, v @ ("fusion_check_dialogs" | "check_dialogs" | "fusion_dismiss_recovery" | "dismiss_recovery" | "fusion_relocate_recovery" | "relocate_recovery" | "fusion_close" | "close") if addin_owner_is_exe(&**h) => { // Legacy window verbs the skills still name (the Python sidecar owned them until // 1.10.0). Each maps onto code the exe already runs on its own at launch and in
rust/bridge/src/services.rs+1−1@@ -183,7 +183,7 @@ pub fn glb_export_async_status(args: &Value) -> Value { out } -fn design_rules_index(state_dir: &std::path::Path) -> Result<Value, String> {+pub fn design_rules_index(state_dir: &std::path::Path) -> Result<Value, String> { let cache = state_dir.join("design-rules-index.json"); if let Ok(m) = std::fs::metadata(&cache) { if m.modified().ok().and_then(|t| t.elapsed().ok()).map(|e| e.as_secs() < 3600).unwrap_or(false) { if let Some(v) = std::fs::read_to_string(&cache).ok().and_then(|t| serde_json::from_str(&t).ok()) { return Ok(v); } } } let idx: Value = ureq::get(&format!("{DESIGN_RULES_PAGE}/rules/index.json")).timeout(Duration::from_secs(20)).set("User-Agent", &service_ua()).call().map_err(|e| e.to_string())?.into_json().map_err(|e| e.to_string())?;
tools/check-naming.shtools/check-portable.shtools/promote.shtools/release.sh