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
+40−42
BRIDGE_VERSION+1−1bridge.json+1−1dashboard/Cargo.lock+1−1dashboard/Cargo.toml+1−1install.sh+1−1package.json+1−1page.json+10−26rust/Cargo.toml+1−1rust/bridge/src/main.rs+6rust/bridge/src/mint.rs+1−1rust/bridge/src/orchestrations.rs+13−5skills/fusion-driving/SKILL.md+1−1skills/fusion-multipart-libraries/SKILL.md+2−2tools/check-naming.shtools/check-portable.shtools/promote.shtools/release.shBRIDGE_VERSION+1−1@@ -1 +1 @@-1.10.38+1.10.39
bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "fusion360", "displayName": "Autodesk Fusion 360",- "version": "1.10.38",+ "version": "1.10.39", "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.",
dashboard/Cargo.lock+1−1@@ -4,7 +4,7 @@ version = 4 [[package]] name = "fusion-dashboard"-version = "1.10.38"+version = "1.10.39" dependencies = [ "serde_json", ]
dashboard/Cargo.toml+1−1@@ -1,6 +1,6 @@ [package] name = "fusion-dashboard"-version = "1.10.38"+version = "1.10.39" edition = "2021" [dependencies]
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.38"+DASH_VERSION="1.10.39" 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.38",+ "version": "1.10.39", "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.38",+ "version": "1.10.39", "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.38"+version = "1.10.39" edition = "2021" license = "MIT"
rust/bridge/src/main.rs+6@@ -424,6 +424,12 @@ fn main() { "show_schematic" | "show_2d_board" | "show_3d_board" => orchestrations::show_with_document(&cmd, &parsed["args"], &pt), "export_source" | "export_eagle_source" => orchestrations::export_in_right_view(&cmd, &parsed["args"], &pt), "activate_document" => orchestrations::activate_with_same_name_note(&parsed["args"], &pt),+ "take_screenshot" if parsed["args"]["outputPath"].as_str().map(|p| p.trim().is_empty()).unwrap_or(true) => {+ // the contract says outputPath is optional; the add-in insisted on one+ let mut a = if parsed["args"].is_object() { parsed["args"].clone() } else { json!({}) };+ a["outputPath"] = json!(orchestrations::shots_dir().join(format!("viewport-{}.png", std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map(|d| d.as_millis()).unwrap_or(0))).to_string_lossy().replace('\\', "/"));+ addin::pass_through(&cmd, &a, &ctx)+ } _ => addin::pass_through(&cmd, &parsed["args"], &ctx), }; small_verbs::add_image_delivery(&mut r);
rust/bridge/src/mint.rs+1−1@@ -322,7 +322,7 @@ pub fn build_library_3d_core(ctx: &Ctx, args: &Value, pass: &dyn Fn(&str, Value) if let Some(b) = &bound { if args["openWhenDone"].as_bool().unwrap_or(true) { opened = json!(crate::orchestrations::open_lbr(ctx, &json!({"filePath": b}), pass)["success"].as_bool() == Some(true)); } } let n_ok = results.iter().filter(|r| r["success"].as_bool() == Some(true)).count(); json!({"success": n_ok > 0, "cancelled": cancelled, "boundLbr": bound, "partsBound": n_ok, "partsTotal": total, "parts": results, "screenshots": shots, "opened": opened,- "_hint": format!("{n_ok}/{total} parts got a RENDERING footprint+chip 3D package; all bindings injected into {} and the library opened once. NEXT: fusion_check_dialogs should be 0 (no broken-ref). For clean device previews + screenshots, save to the cloud and REOPEN from the cloud (fusion_aps_open) - the in-session view often gets an empty 'Untitled' shoved in front. Each device's lower-right 3D preview should show footprint + chip (NOT 'Thumbnail download failed'). The BEFORE (footprint) / AFTER (chip placed) PNGs are in screenshots[] on the Windows box (C:/tmp/conduit-screenshots) - pull them with desktop_pull_file (or re-capture via desktop_screenshot_window) for the demo video. AFTER (recommended): call fusion_capture_library_views per showcase part to grab the symbol / footprint / component (pin<->pad mapped) views - those are what make EEs trust the library, and the 3D-only shots miss them. PITFALLS: needs Fusion running. ⚠️ RELAY TIMEOUT: a many-part run takes minutes but the Adom Bridge relay times out the REQUEST at ~60s - you may get 'Request timed out' even though the build KEEPS RUNNING server-side and finishes. Do NOT assume it failed: wait, then verify by reading the boundLbr (count <package3d name=) + the before/after PNGs in C:/tmp/conduit-screenshots. If a part is missing, just re-run build_library_3d for the missing parts pointing outLbrPath at the SAME file - binding injection is now MERGE-AWARE and idempotent, so it accumulates onto the existing bindings (it no longer wipes the parts that already succeeded). Each part also self-retries once on a transient first-part failure. Full recipe: the fusion-multipart-libraries skill.", bound.as_deref().unwrap_or("None"))})+ "_hint": format!("{n_ok}/{total} parts got a RENDERING footprint+chip 3D package; all bindings injected into {} and the library opened once. NEXT: fusion_check_dialogs should be 0 (no broken-ref). For clean device previews + screenshots, save to the cloud and REOPEN from the cloud (fusion_aps_open) - the in-session view often gets an empty 'Untitled' shoved in front. Each device's lower-right 3D preview should show footprint + chip (NOT 'Thumbnail download failed'). The BEFORE (footprint) / AFTER (chip placed) PNGs are in screenshots[] (on the box in %TEMP%/adom-fusion-shots; ab pulls them to you as localFullPath) - pull them with desktop_pull_file (or re-capture via desktop_screenshot_window) for the demo video. AFTER (recommended): call fusion_capture_library_views per showcase part to grab the symbol / footprint / component (pin<->pad mapped) views - those are what make EEs trust the library, and the 3D-only shots miss them. PITFALLS: needs Fusion running. ⚠️ RELAY TIMEOUT: a many-part run takes minutes but the Adom Bridge relay times out the REQUEST at ~60s - you may get 'Request timed out' even though the build KEEPS RUNNING server-side and finishes. Do NOT assume it failed: wait, then verify by reading the boundLbr (count <package3d name=) + the before/after PNGs (%TEMP%/adom-fusion-shots on the box, pulled to you as localFullPath). If a part is missing, just re-run build_library_3d for the missing parts pointing outLbrPath at the SAME file - binding injection is now MERGE-AWARE and idempotent, so it accumulates onto the existing bindings (it no longer wipes the parts that already succeeded). Each part also self-retries once on a transient first-part failure. Full recipe: the fusion-multipart-libraries skill.", bound.as_deref().unwrap_or("None"))}) } #[cfg(test)]
rust/bridge/src/orchestrations.rs+13−5@@ -721,15 +721,23 @@ pub fn fusion_state(ctx: &Ctx, args: &Value) -> Value { result } -/// `_capture_labeled`: the main window to a labeled PNG under C:/tmp/conduit-screenshots+/// Where the bridge's own captures go: %TEMP%\\adom-fusion-shots. ab auto-pulls a reply's+/// `fullPath` to the caller only from the user-profile and temp roots, so the old+/// C:/tmp/conduit-screenshots folder was never delivered (page issue #98).+pub fn shots_dir() -> std::path::PathBuf {+ let d = std::env::temp_dir().join("adom-fusion-shots");+ let _ = std::fs::create_dir_all(&d);+ d+}++/// `_capture_labeled`: the main window to a labeled PNG under shots_dir() /// (ab captures to its own shot dir; the file is copied so pull paths stay where the skills say). pub fn capture_labeled(ctx: &Ctx, label: &str) -> Option<String> { if label.is_empty() { return None; } let shot = crate::small_verbs::screenshot_fusion(&json!({}), &ctx.caller); let src = shot["savedTo"].as_str().filter(|_| shot["success"].as_bool() == Some(true))?.to_string(); let safe: String = regex::Regex::new(r#"[<>:"/\\|?*]"#).unwrap().replace_all(label, "").replace(' ', "_").chars().take(40).collect();- let dir = std::path::Path::new("C:/tmp/conduit-screenshots");- let _ = std::fs::create_dir_all(dir);+ let dir_buf = shots_dir(); let dir = dir_buf.as_path(); let ts = (now_f() * 1000.0) as u64; let ext = std::path::Path::new(&src).extension().map(|e| e.to_string_lossy().to_string()).unwrap_or_else(|| "png".into()); let dst = dir.join(format!("fusion-{safe}-{ts}.{ext}"));@@ -848,7 +856,7 @@ pub fn capture_library_views(ctx: &Ctx, args: &Value, pass: &dyn Fn(&str, Value) } let n_ok = out.iter().filter(|o| o["ok"].as_bool() == Some(true)).count(); json!({"success": n_ok > 0, "captured": out,- "_hint": format!("Captured {n_ok}/{} library-editor views to C:/tmp/conduit-screenshots (pull with desktop_pull_file). SYMBOL = full pinout; FOOTPRINT = pads + layer stack; COMPONENT = the Content Manager device view with the footprint<->package Mapped check + pin/pad counts. ⚠️ NAMES: the COMPONENT (.dev) view opens by DEVICESET name (e.g. R-0603-10K). But SYMBOL (.sym) and FOOTPRINT (.pac) open by the SYMBOL / PACKAGE name - in a SHARED-FOOTPRINT library (one symbol/footprint per package, many value devicesets) that is the COMBO name (e.g. 'R-0603'), NOT the deviceset name ('R-0603-10K'). Passing a deviceset name to .sym/.pac makes Fusion pop 'Create new symbol/footprint?'; the bridge now auto-dismisses that in the BACKGROUND (Cancel/No, no focus steal - see entries with dialogDismissed) and marks the entry ok:false. Pass libraryPath so names are resolved from the file up front.", out.len())})+ "_hint": format!("Captured {n_ok}/{} library-editor views to %TEMP%/adom-fusion-shots (ab pulls them to you as localFullPath; or pull with desktop_pull_file). SYMBOL = full pinout; FOOTPRINT = pads + layer stack; COMPONENT = the Content Manager device view with the footprint<->package Mapped check + pin/pad counts. ⚠️ NAMES: the COMPONENT (.dev) view opens by DEVICESET name (e.g. R-0603-10K). But SYMBOL (.sym) and FOOTPRINT (.pac) open by the SYMBOL / PACKAGE name - in a SHARED-FOOTPRINT library (one symbol/footprint per package, many value devicesets) that is the COMBO name (e.g. 'R-0603'), NOT the deviceset name ('R-0603-10K'). Passing a deviceset name to .sym/.pac makes Fusion pop 'Create new symbol/footprint?'; the bridge now auto-dismisses that in the BACKGROUND (Cancel/No, no focus steal - see entries with dialogDismissed) and marks the entry ok:false. Pass libraryPath so names are resolved from the file up front.", out.len())}) } fn entry_null(v: &Option<String>) -> bool { v.is_none() } @@ -1093,7 +1101,7 @@ pub fn show_attributes(ctx: &Ctx, args: &Value, pass: &dyn Fn(&str, Value) -> Va if let Ok(r) = ab.call("desktop_screenshot_window", json!({"hwnd": hwnd}), &ctx.caller, 20) { let img = r["image"].as_str().or(r["data"]["image"].as_str()).unwrap_or(""); if let Ok(bytes) = base64::engine::general_purpose::STANDARD.decode(img) {- let dir = std::path::Path::new("C:/tmp/conduit-screenshots"); let _ = std::fs::create_dir_all(dir);+ let dir_buf = shots_dir(); let dir = dir_buf.as_path(); let dst = dir.join(format!("fusion-variant-attributes_{}-{}.png", ds.replace(|c: char| !c.is_ascii_alphanumeric(), "_"), (now_f() * 1000.0) as u64)); if std::fs::write(&dst, bytes).is_ok() { dshot = json!(dst.to_string_lossy().replace('\\', "/")); } }
skills/fusion-driving/SKILL.md+1−1@@ -106,7 +106,7 @@ ways to get the array, in order of preference: - ab core `desktop_screenshot_window {hwnd}` saves the PNG **into your container** (e.g. `/tmp/adom-desktop-screenshots/`) - Read it directly. - The bridge's own captures (the auto `dialogArray[].screenshot` paths and `fusion_screenshot_fusion`)- are saved on the **Windows box** (`%TEMP%/conduit-screenshots/`). To see one, either re-capture that+ are saved on the **Windows box** (`%TEMP%/adom-fusion-shots/`) and, since 1.10.38, pulled to YOU automatically: read `localFullPath` / `localSafePath` in the reply. On older bridges, re-capture that hwnd with `desktop_screenshot_window {hwnd}` (lands in your container) or `desktop_pull_file` it over. Either way, LOOK - do not describe it from memory.
skills/fusion-multipart-libraries/SKILL.md+2−2@@ -51,7 +51,7 @@ fusion_build_library_3d { not have to `fusion_aps_browse` for them. - Returns `{partsBound, partsTotal, parts:[{package,success,wip_urn,dims_mm,error}], screenshots: [{package,stage,path}], boundLbr, opened}`. The before/after PNGs land on the box- (`%TEMP%/conduit-screenshots`) - `desktop_pull_file` them for a demo video.+ (`%TEMP%/adom-fusion-shots`); since 1.10.38 ab pulls them into your container automatically (`localFullPath` in each `screenshots[]` entry) - use those for a demo video. - **Each part is independent.** If Fusion resets mid-run, the parts already done kept their f3d uploads; just re-run the whole `parts[]` (re-making a few f3ds is cheap) - the injection rewrites all bindings in one pass, so a single final call always yields a complete library.@@ -93,7 +93,7 @@ still running server-side and finishes fine.** Do NOT treat that as failure. Ins 1. Wait, then **verify by the artifacts, not the response**: read the `boundLbr` and count `<package3d name=` (should equal your part count), and list the before/after PNGs in- `%TEMP%/conduit-screenshots`.+ `%TEMP%/adom-fusion-shots` (pulled to you as `localFullPath`). 2. If a part is missing (the FIRST part of a run is the usual casualty, as Fusion settles from a prior view), **just re-run `fusion_build_library_3d` for the missing parts with `outLbrPath` pointing at the SAME file.** The binding injection is **merge-aware + idempotent** (v1.6.1+): it reads the
tools/check-naming.shtools/check-portable.shtools/promote.shtools/release.sh