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 161 file(s)
15 files changed
+73−40
BRIDGE_VERSION+1−1bridge.json+1−1dashboard/Cargo.lock+1−1dashboard/Cargo.toml+1−1install.sh+1−1package.json+1−1page.json+11−27rust/Cargo.toml+1−1rust/bridge/src/mint.rs+1−1rust/bridge/src/orchestrations.rs+33−5skills/fusion-multipart-libraries/SKILL.md+21tools/check-naming.shtools/check-portable.shtools/promote.shtools/release.shBRIDGE_VERSION+1−1@@ -1 +1 @@-1.10.8+1.10.9
bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "fusion360", "displayName": "Autodesk Fusion 360",- "version": "1.10.8",+ "version": "1.10.9", "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.8"+version = "1.10.9" dependencies = [ "serde_json", ]
dashboard/Cargo.toml+1−1@@ -1,6 +1,6 @@ [package] name = "fusion-dashboard"-version = "1.10.8"+version = "1.10.9" 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.8"+DASH_VERSION="1.10.9" 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.8",+ "version": "1.10.9", "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+11−27@@ -1,6 +1,6 @@ { "slug": "fusion-bridge",- "version": "1.10.8",+ "version": "1.10.9", "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",@@ -75,7 +79,10 @@ "build a basic parts library", "add a 3d model to a component", "make a fusion 360 library with 3d",- "set up the fusion bridge"+ "set up the fusion bridge",+ "fusion dashboard",+ "fusion status page",+ "open the fusion launch page" ], "discovery_pitch": "Use the Adom Bridge Fusion 360 bridge when the user wants to DRIVE Autodesk Fusion 360 from the cloud: open/search their team's Fusion cloud designs, lay out a PCB or do electronics, export STEP/IGES/STL/Gerbers/BOM, or build a component library with real rendering 3D. It runs Fusion on the user's own machine via Adom Bridge. NOT for web/browser/login/form-filling (that's the native-browser extension) and NOT for KiCad (that's the kicad bridge).", "brief": "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.",@@ -104,28 +111,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.8"+version = "1.10.9" edition = "2021" license = "MIT"
rust/bridge/src/mint.rs+1−1@@ -174,7 +174,7 @@ pub fn make_3d_package(ctx: &Ctx, args: &Value, pass: &dyn Fn(&str, Value) -> Va let expected_pkg = std::fs::read_to_string(&lbr_path).ok().and_then(|x| fusion_lbr::artifact_names(&x).ok()).and_then(|n| n.pac.first().cloned()).unwrap_or_default(); let di = pass("document_info", json!({})); let gen_doc = match &di["data"]["activeDocument"] { Value::Object(o) => o.get("name").and_then(|v| v.as_str()).unwrap_or(""), Value::String(s) => s.as_str(), _ => "" }.trim_end_matches('*').trim().to_string();- if !expected_pkg.is_empty() && !gen_doc.is_empty() && gen_doc.to_lowercase() != expected_pkg.to_lowercase() && !gen_doc.to_lowercase().starts_with("untitled") {+ if !expected_pkg.is_empty() && !gen_doc.is_empty() && !crate::orchestrations::doc_matches(&gen_doc, &expected_pkg) && !gen_doc.to_lowercase().starts_with("untitled") { return json!({"success": false, "errorCode": "wrong_footprint_loaded", "error": format!("The Package3D generator loaded footprint {} but this mint is for package {}; the chip would have landed on the wrong footprint.", fusion_lbr::py_repr_str(&gen_doc), fusion_lbr::py_repr_str(&expected_pkg)), "_hint": "Mint each part on a library whose FIRST package is that part's footprint (fusion_install_library_bundle splits a merged library per part for this reason)."}); }
rust/bridge/src/orchestrations.rs+33−5@@ -113,7 +113,7 @@ pub fn open_lbr(ctx: &Ctx, args: &Value, pass: &dyn Fn(&str, Value) -> Value) -> let di = pass("document_info", json!({})); for d in di["data"]["openDocuments"].as_array().cloned().unwrap_or_default() { let n0 = d["name"].as_str().unwrap_or("").trim_end_matches('*').trim().to_string();- if n0.to_lowercase() == stem.to_lowercase() { let _ = close_bridge_doc_verified(ctx, &n0, 30, pass); }+ if doc_matches(&n0, &stem) { let _ = close_bridge_doc_verified(ctx, &n0, 30, pass); } } let symbol_name = args["symbolName"].as_str().unwrap_or("").to_string(); let verify = args["verify"].as_bool().unwrap_or(false);@@ -127,7 +127,7 @@ pub fn open_lbr(ctx: &Ctx, args: &Value, pass: &dyn Fn(&str, Value) -> Value) -> std::thread::sleep(Duration::from_secs(3)); let st = pass("get_app_state", json!({})); let active = st["data"]["activeDocument"].as_str().unwrap_or("").to_lowercase();- if !expected.is_empty() && active.contains(&expected) { opened_anyway = true; break; }+ if !expected.is_empty() && doc_matches(&active, &expected) { opened_anyway = true; break; } } if !opened_anyway { return json!({"success": false, "error": format!("Document.newDesignFromLocal failed: {}", open_result["error"].as_str().unwrap_or("unknown")),@@ -303,6 +303,21 @@ fn front_doc_title(ctx: &Ctx) -> Option<String> { } /// `_ensure_library_active`: the .lbr is the ACTIVE document, verified, activating or reopening as needed.+/// Does a Fusion document title name this file? Fusion titles a document opened through a DOS+/// short path by the 8.3 stem ("0603WA~1" for 0603WAF3300T5E.lbr under a path with spaces), so+/// the long stem and the short form both count (issue #83 verification, ConfRoomROG 2026-09-10).+pub fn doc_matches(active: &str, stem: &str) -> bool {+ let a = active.trim_end_matches('*').trim().to_lowercase();+ let s = stem.trim().to_lowercase();+ if s.is_empty() || a.is_empty() { return false; }+ if a.contains(&s) { return true; }+ let prefix: String = s.chars().filter(|c| c.is_ascii_alphanumeric()).take(6).collect();+ if let Some(rest) = a.strip_prefix(&prefix) {+ if let Some(digits) = rest.strip_prefix('~') { return !digits.is_empty() && digits.chars().all(|c| c.is_ascii_digit()); }+ }+ false+}+ pub fn ensure_library_active(ctx: &Ctx, lbr_path: &str, timeout_s: u64, pass: &dyn Fn(&str, Value) -> Value) -> Value { let stem = stem_of(lbr_path); let deadline = Instant::now() + Duration::from_secs(timeout_s);@@ -315,13 +330,13 @@ pub fn ensure_library_active(ctx: &Ctx, lbr_path: &str, timeout_s: u64, pass: &d let wl = ws.to_lowercase(); let ws_ok = (wl.contains("librar") || wl.contains("schematic") || wl.contains("board") || wl.contains("pcb")) && !wl.contains("electronics design"); let ok = st["success"].as_bool() == Some(true);- if ok && active.to_lowercase().contains(&stem) && ws_ok {+ if ok && doc_matches(&active, &stem) && ws_ok { let front = front_doc_title(ctx);- if front.as_ref().map(|f| f.contains(&stem)).unwrap_or(true) { return json!({"ok": true, "activeDocument": active, "activeWorkspace": ws, "frontWindow": front}); }+ if front.as_ref().map(|f| doc_matches(f, &stem)).unwrap_or(true) { return json!({"ok": true, "activeDocument": active, "activeWorkspace": ws, "frontWindow": front}); } if !reopened { reopened = true; let _ = close_bridge_doc_verified(ctx, &stem, 30, pass); let _ = open_lbr(ctx, &json!({"lbrPath": lbr_path}), pass); std::thread::sleep(Duration::from_secs(3)); continue; } } tries += 1;- if ok && active.to_lowercase().contains(&stem) && !ws_ok && tries >= 4 && !reopened2 { reopened2 = true; let _ = open_lbr(ctx, &json!({"lbrPath": lbr_path}), pass); std::thread::sleep(Duration::from_secs(4)); continue; }+ if ok && doc_matches(&active, &stem) && !ws_ok && tries >= 4 && !reopened2 { reopened2 = true; let _ = open_lbr(ctx, &json!({"lbrPath": lbr_path}), pass); std::thread::sleep(Duration::from_secs(4)); continue; } if ok { let _ = pass("activate_document", json!({"name": stem})); } std::thread::sleep(Duration::from_millis(1500)); }@@ -867,3 +882,16 @@ pub fn aps_open(ctx: &Ctx, args: &Value, pass: &dyn Fn(&str, Value) -> Value) -> json!({"success": true, "output": format!("Found '{name}' in project {} — opening in Fusion.", crate::fusion_prefs::py_str(&m["projectName"])), "data": {"match": m, "opening": true}, "statusVerb": "fusion_get_app_state", "_hint": format!("The cloud file is opening in Fusion in the background (first open can take ~60-90s; large ASSEMBLIES download all referenced parts and take longer). Poll fusion_get_app_state until activeDocument is the file. Pass {{\"wait\": true}} to block instead. {READONLY_OPEN_NOTE}")}) }++#[cfg(test)]+mod doc_name_tests {+ #[test]+ fn short_names_count() {+ assert!(super::doc_matches("0603WA~1", "0603WAF3300T5E"));+ assert!(super::doc_matches("0603WA~1*", "0603waf3300t5e"));+ assert!(super::doc_matches("0603WAF3300T5E (v3)", "0603WAF3300T5E"));+ assert!(!super::doc_matches("0603WA~x", "0603WAF3300T5E"));+ assert!(!super::doc_matches("Untitled", "0603WAF3300T5E"));+ assert!(!super::doc_matches("ADS858~1", "0603WAF3300T5E"));+ }+}
skills/fusion-multipart-libraries/SKILL.md+21@@ -56,6 +56,27 @@ fusion_build_library_3d { 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. +### Authored placement beats every heuristic (1.10.8, issue #83)++When the manufacturer's STEP carries a deliberate transform (the TMF8829 ships with translation+[-0.00008128, 1.39191746, 0.18269966] mm and rotation [0, 0, 90] deg), pass it and nothing is guessed:++```jsonc+fusion_make_3d_package {"lbrPath": "...", "modelPath": "...",+ "placement": {"translation": [-0.00008128, 1.39191746, 0.18269966], "rotation": [0, 0, 90], "scale": 1}}+```++Rotation is applied about X, then Y, then Z (extrinsic, about the origin), then the translation, in+mm and degrees. With a placement the bounding-box orient, centering and pin-1 rotation heuristics+are all skipped; the reply carries `placement.appliedMatrix` (row-major 4x4 read back from the+occurrence, translation column in mm), `placement.verification` (verified | mismatch), `source`+{path, sha256, bytes, preserved} for the STEP Fusion read, and `warnings`. Non-finite values,+wrong shapes and any scale other than 1 are refused before Fusion is touched. `orient:false`+without a placement imports the model exactly as authored, no reorientation and no pin-1 rotation+(before 1.10.8 the pin-1 rotation survived orient:false). A default mint keeps both heuristics and+says so in `warnings`, so read `pin1.status` (`rotated` means the mark heuristic moved the chip).+Each `parts[]` entry of `fusion_build_library_3d` accepts the same `placement` and `orient`.+ ### ⚠️ Relay timeout - a many-part run "times out" but KEEPS GOING (don't panic) The `adom-bridge` relay times out the *request* at ~60s, but `fusion_build_library_3d` for a dozen
tools/check-naming.shtools/check-portable.shtools/promote.shtools/release.sh