← Commit history
BRIDGE_VERSION+1−1
@@ -1 +1 @@-1.10.8\ No newline at end of file+1.10.9\ No newline at end of file
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]"-  }-}\ No newline at end of file+  ]+}
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.sh

  
tools/check-portable.sh

  
tools/promote.sh

  
tools/release.sh