app
KiCad - the KiCad Bridge
Public Made by Adomby adom
Drive your own KiCad from an AI, in the background: open every editor, read the whole board as data (the EDA Bridge Standard board model), stack-up, part heights, netlists, place, route, pour, DRC and exports.
← Commit history
1.0.12: 1.0.12: footprint-only lint stops blaming a missing kicad-cli (wiki #103), and Freerouting wires that come back under the board minimum are widened to the net class with a count (wiki #93).
4 files changed
+45−5
rust/Cargo.lock+3−3@@ -244,7 +244,7 @@ dependencies = [ [[package]] name = "kicad-bridge"-version = "1.0.11"+version = "1.0.12" dependencies = [ "kicad-core", "kicad-platform",@@ -255,7 +255,7 @@ dependencies = [ [[package]] name = "kicad-core"-version = "1.0.11"+version = "1.0.12" dependencies = [ "kicad-ipc-rs", "kicad-platform",@@ -281,7 +281,7 @@ dependencies = [ [[package]] name = "kicad-platform"-version = "1.0.11"+version = "1.0.12" dependencies = [ "serde", "serde_json",
rust/Cargo.toml+1−1@@ -3,7 +3,7 @@ resolver = "2" members = ["crates/*"] [workspace.package]-version = "1.0.11"+version = "1.0.12" edition = "2021" license = "MIT" publish = false
rust/crates/kicad-core/src/dsn.rs+31−1@@ -1064,6 +1064,8 @@ pub struct NetCopper { /// Session items identical to copper already on the board (protected wiring echoed back). #[serde(rename = "skippedExisting")] pub skipped_existing: usize,+ /// segments widened to the net class minimum because the router returned them thinner (wiki #93)+ pub widened: usize, } fn same_xy(a: [f64; 2], b: [f64; 2]) -> bool {@@ -1107,7 +1109,13 @@ pub fn copper_for(session: &Session, board: &Board, nets: Option<&[String]>, rul out[i].skipped_existing += 1; continue; }- out[i].segments.push(PlanSegment { start: a, end: b, layer: w.layer.clone(), width: w.width });+ // wiki #93: Freerouting hands back widths a hair under what the DSN asked for+ // (0.1874 where the class says 0.2), and KiCad's DRC then calls every one of them a+ // track_width error. The class width is already floored at the board minimum, so a+ // returned wire never lands thinner than the board allows; we count what we widened.+ let (_, class_w, _, _) = rules.class_for(&w.net);+ let width = if w.width + 1e-6 < class_w { out[i].widened += 1; class_w } else { w.width };+ out[i].segments.push(PlanSegment { start: a, end: b, layer: w.layer.clone(), width }); } } for v in &session.vias {@@ -1373,6 +1381,28 @@ mod tests { assert!(copper_for(&s, &board2, Some(&["NET_1".into()]), &Rules::default()).is_empty()); } + #[test]+ fn router_wire_thinner_than_the_class_is_widened_to_it() {+ // wiki #93: Freerouting answers 0.1874 mm where the DSN asked for the class width, and+ // KiCad's DRC calls every such segment a track_width error. The applier widens it back.+ let (_, board) = fixture();+ let thin = Session {+ unit: "um".into(),+ resolution: 10.0,+ nets: vec!["NET_3".into()],+ wires: vec![+ SesWire { net: "NET_3".into(), layer: "F.Cu".into(), width: 0.1874, points: vec![[105.0, 95.0], [112.0, 95.0]] },+ SesWire { net: "NET_3".into(), layer: "F.Cu".into(), width: 0.25, points: vec![[112.0, 95.0], [138.0, 95.0]] },+ ],+ ..Default::default()+ };+ let copper = copper_for(&thin, &board, None, &Rules::default());+ assert_eq!(copper.len(), 1);+ assert_eq!(copper[0].widened, 1, "only the thin one is widened: {copper:?}");+ assert_eq!(copper[0].segments[0].width, Rules::default().track_width);+ assert_eq!(copper[0].segments[1].width, 0.25, "a wire at the class width is left alone");+ }+ #[test] fn specctra_tokenizer_handles_string_quote_and_bad_input() { let sx = parse_specctra("(a (string_quote \") (b \"x y\") c)").unwrap();
rust/crates/kicad-core/src/lint.rs+10@@ -938,6 +938,16 @@ pub fn library_hint( kicad_install_library / kicad_install_symbol / kicad_install_footprint.", kp_ok.len() )+ } else if kicad_verdicts.is_empty() && footprint_count > 0 && symbol_count == 0 {+ // wiki #103: footprints alone. kicad-cli lints SYMBOL libraries; it has no footprint+ // verdict, so saying "kicad-cli not found" blames a missing tool for a gap that is by+ // design, and sends the caller hunting for an executable that is right there.+ format!(+ "✅ {footprint_count} footprint(s) balanced by the BRIDGE's reader, \+ format version(s) {versions_s}. KiCad's own parser has no footprint verdict to give \+ (kicad-cli checks symbol libraries), so footprints are a structural check by design, \+ not a missing tool. Safe to install via kicad_install_footprint."+ ) } else { format!( "✅ {symbol_count} symbol(s) + {footprint_count} footprint(s) balanced by the BRIDGE's reader, \