AI Flow
Public Made by Adomby adom
Adom's AI Flow: a tool to help the AI follow all of the steps it takes to build a board.
Retain unnumbered mounting holes as router obstacles #33
fix/unnumbered-mounting-hole-obstacles → mainWhen a KiCad footprint contains unnumbered mounting holes, the board reader drops them before routing. AIFlow can consequently propose a via through a USB connector's NPTH hole. Keep unnamed copper-layer pads as physical obstacles with separate internal keys, while preserving numbered pad keys and electrical net membership.
Addresses the parser omission in issue 36. This does not claim to resolve every drill-spacing, copper-clearance or connectivity finding in that board.
Base: 99058efe1045f5a7052d0f837a6d6d57a767968b (0.1.68). Two files; no dependencies on the sourcing PR, no runtime deployment or version bump. Immutable-base preflight passes and all diff hunks were reviewed, including the two deletions.
Validation: production cargo check --workspace --bins and cargo build -p adom-aiflow pass. cargo test --workspace: 91 passed, one existing ignored. The new regression checks two unnumbered holes remain distinct, do not enter the electrical net map, occupy the router's through-hole mask, reject vias at and near their centres, and permit a remote clear site.
Native acceptance remains in progress: the installed 0.1.65 planner's complete candidate was correctly rejected by KiCad 10.0.5; a separately built 0.1.68-plus-patch candidate is being replanned and will be checked independently. The accepted live board still contains only four manually validated sense branches. Maintainer review, merge, publication, installation and full-board acceptance remain separate steps.
Diff Skip to comments (1)
crates/aiflow-board/src/lib.rs+9−2@@ -254,7 +254,7 @@ impl Board { } } }- if !copper || name.is_empty() {+ if !copper { continue; } let pat = pn.find("at");@@ -284,8 +284,15 @@ impl Board { let (half_long, half_short, axis) = if lx >= ly { (lx, ly, geom::rot_kicad(1.0, 0.0, pang)) } else { (ly, lx, geom::rot_kicad(0.0, 1.0, pang)) }; let polys: Vec<Vec<Pt>> = local.iter().map(|poly| poly.iter().map(|&(a, b)| { let (rx, ry) = geom::rot_kicad(a, b, pang); (x + rx, y + ry) }).collect()).collect(); let (ccx, ccy) = geom::rot_kicad(lcx, lcy, pang);+ // Unnumbered copper-layer pads, including NPTH mounting holes,+ // are physical obstacles rather than electrical terminals.+ let key = if name.is_empty() {+ format!("{reference}.__physical_{}", self.pads.len())+ } else {+ format!("{reference}.{name}")+ }; self.pads.push(Pad {- key: format!("{reference}.{name}"), reference: reference.clone(), name, net, x, y, angle: pang, shape, size: (sx_, sy_), drill, thru,+ key, reference: reference.clone(), name, net, x, y, angle: pang, shape, size: (sx_, sy_), drill, thru, on_f: on_f && (thru || true) && on_f, on_b: on_b || thru, polys, fp_x: fx, fp_y: fy, long_axis: axis, half_long, half_short, uuid: pn.find("uuid").and_then(|u| u.value()).unwrap_or("").to_string(), polys_centre: (x + ccx, y + ccy), clearance: fp_clearance.max(pclr), });
crates/aiflow-router/src/board_copper.rs+27@@ -213,6 +213,33 @@ pub(crate) mod tests { format!("\t(segment\n\t\t(start {} {})\n\t\t(end {} {})\n\t\t(width 0.25)\n\t\t(layer \"{layer}\")\n\t\t(net \"{net}\")\n\t)\n", a.0, a.1, b.0, b.1) } + #[test]+ fn unnamed_mounting_holes_block_vias_without_becoming_terminals() {+ let extra = r#"(footprint "T:Mount"+ (layer "F.Cu") (at 10 5)+ (property "Reference" "MH")+ (pad "" np_thru_hole circle (at 0 -2) (size 1 1)+ (drill 1) (layers "*.Cu" "*.Mask"))+ (pad "" np_thru_hole circle (at 0 2) (size 1 1)+ (drill 1) (layers "*.Cu" "*.Mask")))"#;+ let b = board(&[("J1", "A", 2.0, 5.0, true)], extra);+ let holes = b.pads_of("MH");+ assert_eq!(holes.len(), 2);+ assert_ne!(holes[0].key, holes[1].key);+ assert!(holes.iter().all(|p| p.name.is_empty() && p.net.is_empty() && p.thru));+ assert_eq!(b.nets.len(), 1);+ assert_eq!(b.nets["A"].len(), 1);+ let mut r = Router::new(b, Rules::default(), false);+ let net = r.net_ids["A"];+ for y in [3.0, 7.0] {+ let (i, j) = r.grid.to_cell(10.0, y);+ assert!(r.thru_cells.get(i as usize, j as usize));+ assert!(!r.via_clear_exact(net, 10.0, y));+ assert!(!r.via_clear_exact(net, 10.0, y - 0.31));+ }+ assert!(r.via_clear_exact(net, 13.0, 5.0));+ }+ const PADS: [(&str, &str, f64, f64, bool); 4] = [("J1", "A", 2.0, 5.0, true), ("J2", "A", 18.0, 5.0, true), ("J3", "B", 10.0, 2.0, true), ("J4", "B", 10.0, 8.0, true)]; fn route(b: Board) -> (Router, Vec<serde_json::Value>, serde_json::Value, usize) {
Comments
Log in to comment.