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
Merge PR #415: Report a library Fusion refuses instead of calling it opened
3 files changed
+38−4
rust/bridge/src/mint.rs+9−2@@ -367,9 +367,16 @@ pub fn build_library_3d_core(ctx: &Ctx, args: &Value, pass: &dyn Fn(&str, Value) } } let mut opened = Value::Null;- 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 mut open_error = Value::Null;+ if let Some(b) = &bound { if args["openWhenDone"].as_bool().unwrap_or(true) {+ let op = crate::orchestrations::open_lbr(ctx, &json!({"filePath": b}), pass);+ let ok = op["success"].as_bool() == Some(true);+ opened = json!(ok);+ if !ok { open_error = op["error"].clone(); }+ } } 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,+ // Parts minted but a library Fusion will not open is a failed build, whatever each part did.+ json!({"success": n_ok > 0 && open_error.is_null(), "error": open_error, "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 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"))}) }
rust/bridge/src/orchestrations.rs+4−2@@ -99,7 +99,7 @@ pub fn open_lbr(ctx: &Ctx, args: &Value, pass: &dyn Fn(&str, Value) -> Value) -> "acceptedArgs": ["filePath", "lbrPath", "libraryPath", "path"], "receivedArgs": received, "_hint": "Re-call with a Windows-local path, e.g. fusion_open_lbr {\"lbrPath\": \"C:/tmp/rayparts/tja1050.lbr\"}."}); } let pf = fusion_lbr::preflight_lbr(std::path::Path::new(&file_path));- let hard_ok = ["file-readable", "size-sane", "xml-parses", "root-is-eagle"].iter().all(|k| pf.checks.iter().find(|c| c.check == *k).map(|c| c.ok).unwrap_or(true));+ let hard_ok = ["file-readable", "size-sane", "xml-parses", "root-is-eagle", "package3d-refs-resolve"].iter().all(|k| pf.checks.iter().find(|c| c.check == *k).map(|c| c.ok).unwrap_or(true)); let file_path = file_path.replace('\\', "/"); if !hard_ok { let sz = std::fs::metadata(&file_path).ok().map(|m| m.len());@@ -164,7 +164,9 @@ pub fn open_lbr(ctx: &Ctx, args: &Value, pass: &dyn Fn(&str, Value) -> Value) -> if !verification.is_null() { response["data"]["verification"] = verification; } let (shots, _) = crate::lifecycle::post_open_screenshot(ctx, 2.0); response["data"]["postOpenScreenshot"] = shots;- response+ // Fusion reports a refused library only through its Error modal: the command itself succeeds+ // and leaves an empty design. Without this, every caller reads the refusal as a good open.+ crate::small_verbs::apply_failure_dialogs(response, &ctx.caller, &ctx.pids) } /// Python's `repr(bytes)`.
rust/lbr/src/lib.rs+25@@ -889,6 +889,14 @@ pub fn preflight_lbr(path: &std::path::Path) -> Preflight { } c("pads-unique", dups.is_empty(), if dups.is_empty() { "all pad names unique per package".into() } else { format!("duplicate pad names - Fusion's importer refuses the ENTIRE library over these: {}. Rename to e.g. 17, 17@2, 17@3...", dups.iter().take(6).cloned().collect::<Vec<_>>().join(", ")) });+ // A 3D package naming a package the library does not have makes Fusion refuse the ENTIRE+ // library: an Error modal and an empty design. Only libraries with bindings get this check.+ if root.descendants().any(|n| el(n, "packageinstance")) {+ let names: Vec<&str> = root.descendants().filter(|n| el(*n, "package")).filter_map(|p| p.attribute("name")).collect();+ let dangling: Vec<&str> = root.descendants().filter(|n| el(*n, "packageinstance")).filter_map(|p| p.attribute("name")).filter(|n| !names.iter().any(|k| k.eq_ignore_ascii_case(n))).collect();+ c("package3d-refs-resolve", dangling.is_empty(), if dangling.is_empty() { "every 3D binding names a package in this library".into() } else {+ format!("3D binding(s) name packages this library does not have, so Fusion refuses the ENTIRE library: {}. Bind to the <package> name, not the deviceset's.", dangling.iter().take(6).copied().collect::<Vec<_>>().join(", ")) });+ } } finish(checks, xml.as_deref()) }@@ -913,6 +921,23 @@ fn finish(checks: Vec<Check>, xml: Option<&str>) -> Preflight { #[cfg(test)] mod heatsink_tests {+ #[test]+ fn preflight_refuses_a_3d_binding_to_a_missing_package() {+ let lib = |instance: &str| format!(r#"<?xml version="1.0"?><eagle><drawing><library><packages><package name="SOIC-8"><smd name="1" x="0" y="0" dx="1" dy="1" layer="1"/></package></packages><packages3d><package3d name="{instance}" wip_urn="urn:x"><packageinstances><packageinstance name="{instance}"/></packageinstances></package3d></packages3d><symbols><symbol name="S"/></symbols><devicesets><deviceset name="NY8A054ES8"><devices><device name="" package="SOIC-8"><connects/></device></devices></deviceset></devicesets></library></drawing></eagle>"#);+ let dir = std::env::temp_dir().join(format!("fusion-lbr-refs-{}", std::process::id()));+ std::fs::create_dir_all(&dir).unwrap();+ let check = |instance: &str| {+ let path = dir.join(format!("{instance}.lbr"));+ std::fs::write(&path, lib(instance)).unwrap();+ crate::preflight_lbr(&path).checks.into_iter().find(|c| c.check == "package3d-refs-resolve").expect("the check runs on a library with bindings")+ };+ let bad = check("NY8A054ES8");+ assert!(!bad.ok, "a binding to the deviceset's name is refused");+ assert!(bad.detail.contains("NY8A054ES8"), "and names it: {}", bad.detail);+ assert!(check("SOIC-8").ok, "a binding to the real package passes");+ let _ = std::fs::remove_dir_all(&dir);+ }+ #[test] fn heatsink_vias_are_not_shorts() { let lbr = r#"<eagle version="9.6.2"><drawing><library><packages><package name="QFN-EP">