← Commit history
BRIDGE_VERSION+1−1
@@ -1 +1 @@-1.13.23\ No newline at end of file+1.13.24\ No newline at end of file
CHANGELOG.md+12
@@ -3,6 +3,18 @@ All notable changes to the Adom Bridge Fusion 360 bridge, newest first. Canonical: https://wiki.adom.inc/adom/fusion-bridge ยท GitHub backup: `adom-inc/fusion-bridge` +## 1.13.24 (2026-10-30)++- `fusion_list_installed_libraries` (the standard's list_installed_libraries): the libraries Fusion offers to place+  from are the hub's electronics libraries (.flbr, Library Manager's Hub Libraries tab). Fusion keeps no local list and+  draws that tab without UI Automation, so the verb walks the hub's projects and folders through the signed-in session+  (breadth first, to a depth, within a time budget).+- `fusion_add_symbol_art` (the standard's add_symbol_art): the chip outline adom-symbol baked into a KiCad symbol, drawn+  into the component's symbol fitted to its body (the TPS54202's 162 polylines live). Offline on a .lbr.+- uninstall_library, normalize_bodies and sch_sheet_symbol answer not_supported: taking a hub library out would delete+  the file for everyone; a Fusion footprint's 3D model is a cloud package placed when made, with no stored offsets; and+  a Fusion module cannot be made from a text command (checked live). 347 verbs.+ ## 1.13.23 (2026-10-30)  - The standard's app tier in Fusion's terms:
bridge.json+3−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "fusion360",   "displayName": "Autodesk Fusion 360",-  "version": "1.13.23",+  "version": "1.13.24",   "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.",@@ -26,6 +26,7 @@     "fusion_activate_document",     "fusion_add_models",     "fusion_add_rule",+    "fusion_add_symbol_art",     "fusion_addin_status",     "fusion_alerts",     "fusion_align_parts",@@ -526,6 +527,7 @@       "project_snapshot": 300,       "project_create": 180,       "test_board": 180,+      "add_symbol_art": 30,       "list_installed_libraries": 300,       "diagnostics": 30,       "instances": 10,
contracts/describe.json+28
@@ -119,6 +119,34 @@       "summary": "The standard's add_rule as EAGLE net classes, the board's scoped rules: kind width (min), clearance (gap, to every class and within the class) or via (drill). scope1 InNetClass('X') edits that class; InNet('A') (or nets) puts the nets in a class of their own, Adom_<name>, each joined with one pick on its wire. Read back from the board; every change undone on a mismatch. Length, matched, diffpair and polyconnect rules have no class equivalent and are refused.",       "timeoutSeconds": 120     },+    {+      "example": {+        "dryRun": true,+        "kicadSym": "C:/staged/AMPLH5030S-150MT.kicad_sym",+        "libraryPath": "C:/libs/parts.lbr",+        "symbolName": "AMPLH5030S-150MT"+      },+      "input": {+        "clear": "bool, default true",+        "dryRun": "bool",+        "kicadSym": ".kicad_sym on the Fusion machine carrying the baked outline (required)",+        "libraryPath": ".lbr on the Fusion machine (required)",+        "symbolName": "the component (required)"+      },+      "longRunning": false,+      "name": "fusion_add_symbol_art",+      "output": {+        "bodyRectMil": "[x1, y1, x2, y2]",+        "component": "str",+        "lines": "int",+        "points": "int",+        "polylines": "int",+        "removed": "int"+      },+      "statusVerb": null,+      "summary": "Draw a chip's outline artwork into a component's symbol (the standard's add_symbol_art), offline in a .lbr: the polylines adom-symbol baked into the KiCad symbol, the KiCad body rectangle mapped onto the EAGLE symbol's own body box, as lines on the symbol layer, so the Fusion symbol shows the same chip as KiCad and Altium. clear (default) removes earlier art first, keeping the body's edges. Preflighted before writing.",+      "timeoutSeconds": 30+    },     {       "example": {},       "input": {},
dashboard/Cargo.lock+1−1
@@ -4,7 +4,7 @@ version = 4  [[package]] name = "fusion-dashboard"-version = "1.13.23"+version = "1.13.24" dependencies = [  "serde_json", ]
dashboard/Cargo.toml+1−1
@@ -1,6 +1,6 @@ [package] name = "fusion-dashboard"-version = "1.13.23"+version = "1.13.24" edition = "2021"  [dependencies]
describe.py+4
@@ -450,6 +450,10 @@ _T = [      {"projectName": "one project to walk (default: every project)", "depth": "folder depth, default 3", "budgetSeconds": "default 150"},      {"count": "int", "libraries": "[{name, project, folder, version, id, modified}]", "foldersWalked": "int", "complete": "bool"},      300, None, False, {"projectName": "Default Project"}),+    ('fusion_add_symbol_art', "Draw a chip's outline artwork into a component's symbol (the standard's add_symbol_art), offline in a .lbr: the polylines adom-symbol baked into the KiCad symbol, the KiCad body rectangle mapped onto the EAGLE symbol's own body box, as lines on the symbol layer, so the Fusion symbol shows the same chip as KiCad and Altium. clear (default) removes earlier art first, keeping the body's edges. Preflighted before writing.",+     {"libraryPath": ".lbr on the Fusion machine (required)", "symbolName": "the component (required)", "kicadSym": ".kicad_sym on the Fusion machine carrying the baked outline (required)", "clear": "bool, default true", "dryRun": "bool"},+     {"component": "str", "polylines": "int", "points": "int", "lines": "int", "removed": "int", "bodyRectMil": "[x1, y1, x2, y2]"},+     30, None, False, {"libraryPath": "C:/libs/parts.lbr", "symbolName": "AMPLH5030S-150MT", "kicadSym": "C:/staged/AMPLH5030S-150MT.kicad_sym", "dryRun": True}),     ('fusion_test_board', "Footprints from a .lbr on ONE board (the standard's test_board): a NEW board (wMm x hMm outline, or one that fits the row) when pcbPath is new or omitted, APPENDED right of the current parts when pcbPath is an existing .brd, designators continuing (U4...). Written as EAGLE source, opened in Fusion as a local design dialog-free, and read back. assigns and stubs apply to the LAST part placed.",      {"libraryPath": "a .lbr on the Fusion machine (required)", "footprintName": "str | 'A;B;C' (required)", "pcbPath": "a .brd: existing = append, new = create (default %TEMP%/adom-test-boards/<name>.brd)", "wMm": "number (new board only)", "hMm": "number (new board only)", "assigns": "pad=NET;... (last part)", "stubs": "pad=x,y;... mm from the part's origin (last part): a 0.2 mm top track", "prefix": "designator prefix, default U", "layers": "2|4|6|8 (new board)", "open": "bool, default true"},      {"appended": "bool", "pcb": "path", "placed": "'U1;U2'", "count": "int (parts on the board)", "existingParts": "int", "padsOnComp": "int", "bodiesOnComp": "int", "saved": "bool (checked on disk)", "documentName": "str", "unknownPins": "[]"},
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.13.23"+DASH_VERSION="1.13.24" 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.13.23",+  "version": "1.13.24",   "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+2−2
@@ -1,6 +1,6 @@ {   "slug": "fusion-bridge",-  "version": "1.13.23",+  "version": "1.13.24",   "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": {@@ -152,4 +152,4 @@   "confirmed_user_skills": [     "fusion-design-efficiency"   ]-}\ No newline at end of file+}
rust/Cargo.lock+5−5
@@ -19,7 +19,7 @@ dependencies = [  [[package]] name = "adom-fusion-bridge"-version = "1.13.23"+version = "1.13.24" dependencies = [  "adom-eda-checks",  "base64",@@ -299,7 +299,7 @@ dependencies = [  [[package]] name = "fusion-core"-version = "1.13.23"+version = "1.13.24" dependencies = [  "serde",  "serde_json",@@ -310,7 +310,7 @@ dependencies = [  [[package]] name = "fusion-lbr"-version = "1.13.23"+version = "1.13.24" dependencies = [  "regex",  "roxmltree",@@ -322,14 +322,14 @@ dependencies = [  [[package]] name = "fusion-os-mac"-version = "1.13.23"+version = "1.13.24" dependencies = [  "fusion-core", ]  [[package]] name = "fusion-os-win"-version = "1.13.23"+version = "1.13.24" dependencies = [  "fusion-core",  "windows-sys 0.59.0",
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.13.23"+version = "1.13.24" edition = "2021" license = "MIT" 
rust/bridge/src/main.rs+8−1
@@ -691,7 +691,7 @@ fn main() {                     "fusion_edit_footprint" | "edit_footprint" => native_edit_footprint(&parsed["args"]),                     v @ ("fusion_create_footprint" | "create_footprint" | "fusion_create_symbol" | "create_symbol" | "fusion_read_symbol" | "read_symbol" | "fusion_find_symbol" | "find_symbol"                         | "fusion_copy_footprint" | "copy_footprint" | "fusion_rename_footprint" | "rename_footprint" | "fusion_remove_footprint" | "remove_footprint" | "fusion_xcopy_footprint" | "xcopy_footprint"-                        | "fusion_clone_symbol" | "clone_symbol" | "fusion_rename_symbol" | "rename_symbol" | "fusion_remove_symbol" | "remove_symbol" | "fusion_set_params" | "set_params" | "fusion_footprint_from_land_pattern" | "footprint_from_land_pattern" | "fusion_set_models" | "set_models" | "fusion_add_models" | "add_models" | "fusion_symbol_prims" | "symbol_prims" | "fusion_set_visibility" | "set_visibility") => native_lib_authoring(v.strip_prefix("fusion_").unwrap_or(v), &parsed["args"]),+                        | "fusion_clone_symbol" | "clone_symbol" | "fusion_rename_symbol" | "rename_symbol" | "fusion_remove_symbol" | "remove_symbol" | "fusion_set_params" | "set_params" | "fusion_footprint_from_land_pattern" | "footprint_from_land_pattern" | "fusion_set_models" | "set_models" | "fusion_add_models" | "add_models" | "fusion_symbol_prims" | "symbol_prims" | "fusion_set_visibility" | "set_visibility" | "fusion_add_symbol_art" | "add_symbol_art") => native_lib_authoring(v.strip_prefix("fusion_").unwrap_or(v), &parsed["args"]),                     "fusion_preflight_lbr" | "preflight_lbr" => native_preflight_lbr(&parsed["args"]),                     "fusion_not_supported" | "not_supported" => unsupported::answer(&parsed["args"]),                     // Native since Phase 2: the raw authenticated APS GET (never-charge guarded).@@ -1062,6 +1062,13 @@ fn native_lib_authoring(verb: &str, args: &Value) -> Value {         "clone_symbol" => a::clone_symbol(text.as_deref().unwrap(), &s("from").unwrap_or_default(), &s("to").unwrap_or_default()),         "rename_symbol" => a::rename_symbol(text.as_deref().unwrap(), &s("from").unwrap_or_default(), &s("to").unwrap_or_default()),         "remove_symbol" => a::remove_symbol(text.as_deref().unwrap(), &s("symbolName").or(s("name")).unwrap_or_default()),+        "add_symbol_art" => match s("kicadSym") {+            Some(k) => match std::fs::read_to_string(k.replace('\\', "/")) {+                Ok(t) => a::add_symbol_art(text.as_deref().unwrap(), &s("symbolName").or(s("name")).unwrap_or_default(), &t, args["clear"].as_bool() != Some(false)),+                Err(e) => Err(format!("cannot read kicadSym {k}: {e} (send_files the .kicad_sym to this machine first)")),+            },+            None => Err("kicadSym is required: the .kicad_sym (on the Fusion machine) carrying the baked outline".to_string()),+        },         "set_visibility" => match s("parameter").or(s("paramName")) {             Some(prm) => a::set_visibility(text.as_deref().unwrap(), &s("symbolName").or(s("name")).unwrap_or_default(), &prm, args["visible"].as_bool().unwrap_or(true)),             None => Err("parameter is required (the attribute to show or hide, e.g. MPN)".to_string()),
rust/bridge/src/unsupported.rs+2
@@ -30,6 +30,8 @@ pub const TABLE: &[(&str, &str)] = &[     ("panel", "Altium's docked panel tabs; Fusion's editors show their own panels, and the bridge opens a surface (library, symbol, footprint, board, schematic, 3D) with fusion_show_* in the background"),     ("selection", "Fusion does not show the user's board selection to the bridge: a ULP's ingroup() reads no part even right after a GROUP (checked live on Fusion 2706, 2026-10-30). Name the parts instead; fusion_electron_select shows one to the user"),     ("uninstall_library", "a library Fusion offers is a file in the Autodesk hub, so taking it out means deleting that file for everyone in the hub, which the standard's uninstall never does (it leaves files alone); a hub file the AI made itself is deleted with fusion_cleanup_cloud_files"),+    ("normalize_bodies", "a Fusion footprint's 3D model is a cloud 3D package placed when it is made (fusion_make_3d_package and fusion_bind_library_3d orient it on the pads; fusion_show_3d_package places one by hand): a library holds no body offsets to move"),+    ("sch_sheet_symbol", "Fusion's hierarchy is modules, and a module cannot be made from a text command (EDIT 'name.m' answers nothing and the design gets no module, checked live on Fusion 2706); module instances are placed in Fusion's module dialog. fusion_sch_hierarchy reads an existing hierarchy and fusion_sch_sheet_pins its ports"),     ("component_classes", "an EAGLE board has net classes (fusion_net_classes, fusion_add_rule) but no component classes or rooms: group a channel by its designators (fusion_replicate_layout takes them as lists)"), ]; 
rust/lbr/src/authoring.rs+95
@@ -577,6 +577,80 @@ pub fn set_visibility(xml: &str, name: &str, parameter: &str, visible: bool) ->     Ok((root.to_xml_document(), json!({"component": comp, "parameter": key.trim_start_matches('>'), "visible": visible, "symbolsChanged": changed}))) } +/// A KiCad symbol's body rectangle (mm, x0 y0 x1 y1) and polylines, from a .kicad_sym (the symbol+/// and its unit blocks, which nest inside it).+fn kicad_art(text: &str, name: &str) -> (Option<(f64, f64, f64, f64)>, Vec<Vec<(f64, f64)>>) {+    let head = format!("(symbol \"{name}\"");+    let Some(start) = text.find(&head) else { return (None, vec![]) };+    let b = text.as_bytes(); let (mut depth, mut end) = (0i32, text.len());+    for (i, &c) in b.iter().enumerate().skip(start) { if c == b'(' { depth += 1; } else if c == b')' { depth -= 1; if depth == 0 { end = i + 1; break; } } }+    let blk = &text[start..end];+    let num = |t: &str| t.trim().parse::<f64>().ok();+    let rect = regex::Regex::new(r"\(rectangle\s*\(start\s+([-\d.]+)\s+([-\d.]+)\)\s*\(end\s+([-\d.]+)\s+([-\d.]+)\)").ok()+        .and_then(|re| re.captures(blk)).and_then(|c| Some((num(&c[1])?, num(&c[2])?, num(&c[3])?, num(&c[4])?)))+        .map(|(a, b2, c, d)| (a.min(c), b2.min(d), a.max(c), b2.max(d)));+    let xy = regex::Regex::new(r"\(xy\s+([-\d.]+)\s+([-\d.]+)\)").unwrap();+    let mut polys = Vec::new();+    let mut rest = blk;+    while let Some(i) = rest.find("(polyline") {+        let sub = &rest[i..]; let sb = sub.as_bytes(); let (mut dep, mut e) = (0i32, sub.len());+        for (j, &c) in sb.iter().enumerate() { if c == b'(' { dep += 1; } else if c == b')' { dep -= 1; if dep == 0 { e = j + 1; break; } } }+        let pts: Vec<(f64, f64)> = xy.captures_iter(&sub[..e]).filter_map(|c| Some((num(&c[1])?, num(&c[2])?))).collect();+        if pts.len() >= 2 { polys.push(pts); }+        rest = &sub[e..];+    }+    (rect, polys)+}++/// The standard's add_symbol_art: the chip-outline artwork adom-symbol baked into a KiCad symbol,+/// drawn into the EAGLE component's symbol(s) on the symbol layer, the KiCad body rectangle mapped+/// onto the symbol's own body box. clear (default) removes earlier art first: every symbol-layer+/// wire and polygon except the body's own edges.+pub fn add_symbol_art(xml: &str, name: &str, kicad_sym: &str, clear: bool) -> Result<(String, Value), String> {+    let mut root = Element::parse(xml).map_err(|e| e.to_string())?;+    let (comp, syms) = component_symbols(&mut root, name)?;+    // the KiCad symbol by the component's own name (as asked, when it differs in case)+    let (rect, polys) = { let a = kicad_art(kicad_sym, &comp); if a.0.is_some() { a } else { kicad_art(kicad_sym, name) } };+    let kr = rect.ok_or_else(|| format!("no (rectangle ...) body for symbol {comp} in the .kicad_sym"))?;+    if polys.is_empty() { return Err(format!("no polylines in symbol {comp}: bake the outline first (adom-symbol create --outline-svg <mpn>-3d-outline-heavy.svg --outline-name none)")); }+    let lib = lib_mut(&mut root)?;+    let num = |e: &Element, k: &str| e.attr(k).and_then(|v| v.parse::<f64>().ok()).unwrap_or(0.0);+    let (mut drawn, mut removed) = (0usize, 0usize);+    let mut body_out = Value::Null;+    for sn in &syms {+        let Some(sym) = section(lib, "symbols").children.iter_mut().find(|x| x.tag == "symbol" && x.attr("name") == Some(sn.as_str())) else { continue };+        // the body box: the symbol layer's lines and rectangles+        let mut bb = (f64::MAX, f64::MAX, f64::MIN, f64::MIN);+        for c in sym.children.iter().filter(|c| (c.tag == "wire" || c.tag == "rectangle") && c.attr("layer") == Some("94")) {+            for (x, y) in [(num(c, "x1"), num(c, "y1")), (num(c, "x2"), num(c, "y2"))] { bb.0 = bb.0.min(x); bb.1 = bb.1.min(y); bb.2 = bb.2.max(x); bb.3 = bb.3.max(y); }+        }+        if bb.0 == f64::MAX { return Err(format!("symbol {sn} has no body (no lines on the symbol layer) to fit the art to")); }+        if clear {+            let on_edge = |c: &Element| {+                let (x1, y1, x2, y2) = (num(c, "x1"), num(c, "y1"), num(c, "x2"), num(c, "y2"));+                let e = 1e-6;+                ((x1 - x2).abs() < e && ((x1 - bb.0).abs() < e || (x1 - bb.2).abs() < e)) || ((y1 - y2).abs() < e && ((y1 - bb.1).abs() < e || (y1 - bb.3).abs() < e))+            };+            let before = sym.children.len();+            sym.children.retain(|c| !(c.attr("layer") == Some("94") && ((c.tag == "wire" && !on_edge(c)) || c.tag == "polygon")));+            removed += before - sym.children.len();+        }+        let (sx, sy) = ((bb.2 - bb.0) / (kr.2 - kr.0).max(1e-9), (bb.3 - bb.1) / (kr.3 - kr.1).max(1e-9));+        let map = |(x, y): (f64, f64)| (bb.0 + (x - kr.0) * sx, bb.1 + (y - kr.1) * sy);+        for pl in &polys {+            for w in pl.windows(2) {+                let (a, b) = (map(w[0]), map(w[1]));+                if (a.0 - b.0).abs() + (a.1 - b.1).abs() < 1e-9 { continue; }+                sym.children.push(el("wire", &[("x1", f(a.0)), ("y1", f(a.1)), ("x2", f(b.0)), ("y2", f(b.1)), ("width", "0.1524".into()), ("layer", "94".into())]));+                drawn += 1;+            }+        }+        let mm2mil = |v: f64| (v / MIL * 10.0).round() / 10.0;+        body_out = json!([mm2mil(bb.0), mm2mil(bb.1), mm2mil(bb.2), mm2mil(bb.3)]);+    }+    Ok((root.to_xml_document(), json!({"component": comp, "polylines": polys.len(), "points": polys.iter().map(|p| p.len()).sum::<usize>(), "lines": drawn, "removed": removed, "bodyRectMil": body_out})))+}+ /// The standard's add_models: each footprint becomes another device of the component (EAGLE's /// several-packages-per-component), pins joined by pad number; a footprint already attached is /// skipped, since a device names it once. `sources` holds other .lbr texts a model's footprint is@@ -684,6 +758,27 @@ pub fn preflight_text(path: &std::path::Path) -> (bool, Vec<String>) {     (errs.is_empty(), errs) } +#[cfg(test)]+mod art_tests {+    use super::*;++    #[test]+    fn kicad_art_is_fitted_to_the_eagle_body() {+        let sym = "META|IND-X|ind|L\nMODE|0|n\nRECT|0|0|-400|800|0\nPIN|0|1|1|0|-200|left|300||passive\nPIN|0|2|2|800|-200|right|300||passive\n";+        let fp = "META|IND-FP|x\nPAD|1|-1|0|1|1|rect|0|top|0\nPAD|2|1|0|1|1|rect|0|top|0\n";+        let (xml, _) = crate::import::import_part(None, Some(fp), Some(sym), None, &json!({})).unwrap();+        let ks = "(kicad_symbol_lib (symbol \"IND-X\" (symbol \"IND-X_0_1\" (rectangle (start -2 2) (end 2 -2)) (polyline (pts (xy -2 0) (xy 0 2) (xy 2 0)))) ))";+        let (out, r) = add_symbol_art(&xml, "ind-x", ks, true).unwrap();+        assert_eq!((r["polylines"].as_u64(), r["lines"].as_u64(), r["removed"].as_u64()), (Some(1), Some(2), Some(0)), "{r}");+        // KiCad (-2,0) -> the body's left edge at mid height: x 0, y -200 mil; (0,2) -> top middle+        assert!(out.contains(r#"<wire x1="0" y1="-5.08" x2="10.16" y2="0" width="0.1524" layer="94" />"#), "{out}");+        // again with clear: the old art goes, the body edges stay+        let (_, r2) = add_symbol_art(&out, "IND-X", ks, true).unwrap();+        assert_eq!(r2["removed"].as_u64(), Some(2));+        assert!(add_symbol_art(&xml, "IND-X", "(kicad_symbol_lib (symbol \"IND-X\" (rectangle (start 0 0) (end 1 1))))", true).is_err());+    }+}+ #[cfg(test)] mod prims_tests {     use super::*;
tests/run_verb_tests.py+1
@@ -469,6 +469,7 @@ t("fusion_progress", "daily", {}, {"ok": True, "fields": ["active", "verbTimes"] t("fusion_foreground", "manual", {"reason": "battery check of the foreground verb"}, {"ok": True}, "brings Fusion in front: manual only, never in an unattended battery") t("fusion_foreground#noreason", "negative", {}, {"pred": "must_refuse"}, "a foreground with no reason is refused") t("fusion_list_installed_libraries", "daily", {"projectName": "Default Project", "depth": 2}, {"ok": True, "fields": ["libraries", "complete"]}, "the hub's .flbr libraries in one project")+t("fusion_add_symbol_art", "daily", {"libraryPath": "C:/Users/olive/Documents/fusion-lane-tests/art/art-test.lbr", "symbolName": "TPS54202DDCR", "kicadSym": "C:/Users/olive/Documents/fusion-lane-tests/art/tps54202ddcr-outline.kicad_sym", "dryRun": True}, {"ok": True, "fields": ["data"]}, "the TPS54202 chip outline fitted to the symbol body (dry run)") t("fusion_build_board", "daily", {"libraryPath": BOARDLIB, "pcbPath": BOARDDIR + "/build.brd", "parts": [{"ref": "R1", "footprint": "R0603", "x": 5, "y": 5}, {"ref": "Q1", "footprint": "SOT23", "x": 12, "y": 5}], "nets": {"GND": ["R1.2", "Q1.2"]}, "outline": {"rect": [0, 0, 18, 10], "cornerRadius": 1}, "open": False}, {"ok": True, "fields": ["pcbPath", "unknownPins"]}, "written offline (open:false keeps the battery from opening a design each run)") t("fusion_build_board#missing", "negative", {"libraryPath": BOARDLIB, "pcbPath": BOARDDIR + "/bad.brd", "parts": [{"ref": "U1", "footprint": "NO_SUCH_FP", "x": 0, "y": 0}], "open": False}, {"pred": "must_refuse"}, "a footprint missing from the library refuses the whole build") t("fusion_test_board", "daily", {"libraryPath": BOARDLIB, "footprintName": "R0603;SOT23", "pcbPath": BOARDDIR + "/test.brd", "open": False}, {"ok": True, "fields": ["pcb", "placed", "saved"]}, "test.brd is made on the first run and appended to after (designators continue); written offline")