app
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.
main
John Lauer
Publish 0.2.50
01ab14c
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
use aiflow_board::Board;
use aiflow_router::{Router, Rules};
fn fixture() -> Router {
let board = Board::from_text("regression.kicad_pcb", r#"(kicad_pcb
(layers (0 "F.Cu" signal) (31 "B.Cu" signal))
(net 1 "SIG")
(gr_rect (start 0 0) (end 12 12) (layer "Edge.Cuts"))
(footprint "test" (layer "F.Cu") (at 5 5) (property "Reference" "U1")
(pad "1" smd rect (at -2 0) (size 0.25 1) (layers "F.Cu") (net 1 "SIG"))
(pad "2" smd rect (at 0 0) (size 1 1) (layers "F.Cu"))
(pad "3" smd rect (at 2 0) (size 0.25 1) (layers "F.Cu") (net 1 "SIG"))))"#.into()).unwrap();
Router::new(board, Rules::default(), false)
}
#[test]
fn no_net_copper_blocks_tracks_and_vias_without_inventing_a_net() {
let mut r = fixture();
assert_eq!(r.nets.len(), 1);
assert_eq!(r.board.pad_by_key("U1.2").unwrap().net, "");
let (i,j) = r.grid.to_cell(5.,5.);
let win = (0,0,r.grid.w as i64-1,r.grid.h as i64-1);
let (orth, diagonal, _, via) = r.obstacle_maps(0,0.25,win,false);
assert!(orth[0].get(i as usize,j as usize));
assert!(diagonal[0].get(i as usize,j as usize));
assert!(!via.get(i as usize,j as usize));
}
#[test]
fn released_clear_escape_becomes_searchable_again() {
let mut r=fixture();
let c=r.nets[0].clusters.iter().position(|c| !c.escapes.is_empty()).expect("narrow-pad escape");
let t=*r.nets[0].clusters[c].escapes.keys().next().unwrap();
r.nets[0].clusters[c].terminals.retain(|x| *x!=t);
r.nets[0].clusters[c].released.insert(t);
r.reserve_escapes(0,c);
assert!(r.nets[0].clusters[c].terminals.contains(&t));
assert!(!r.nets[0].clusters[c].released.contains(&t));
r.reserve_escapes(0,c);
assert_eq!(r.nets[0].clusters[c].terminals.iter().filter(|x| **x==t).count(),1);
}
/// Issue #36: an unnumbered, no-net NPTH mounting hole (KiCad 7 "*.Cu" and KiCad 8+ "F&B.Cu"
/// layer spellings) stays a router obstacle: no via on or beside it, no track through it.
#[test]
fn unnumbered_npth_mounting_hole_blocks_vias_and_tracks() {
for layers in ["\"*.Cu\" \"*.Mask\"", "\"F&B.Cu\" \"*.Mask\""] {
let board = Board::from_text("npth.kicad_pcb", format!(r#"(kicad_pcb
(layers (0 "F.Cu" signal) (31 "B.Cu" signal))
(net 1 "SIG")
(gr_rect (start 0 0) (end 20 12) (layer "Edge.Cuts"))
(footprint "test" (layer "F.Cu") (at 3 6) (property "Reference" "J1")
(pad "1" smd rect (at 0 0) (size 1 1) (layers "F.Cu") (net 1 "SIG")))
(footprint "test" (layer "F.Cu") (at 17 6) (property "Reference" "J2")
(pad "1" smd rect (at 0 0) (size 1 1) (layers "F.Cu") (net 1 "SIG")))
(footprint "MountingHole" (layer "F.Cu") (at 10 6) (property "Reference" "H1")
(pad "" np_thru_hole circle (at 0 0) (size 3.2 3.2) (drill 3.2) (layers {layers}))))"#)).unwrap();
let hole = board.pads.iter().find(|p| p.reference == "H1").expect("the unnumbered hole is kept");
assert!(hole.net.is_empty() && hole.thru);
let mut r = Router::new(board, Rules::default(), false);
let win = (0, 0, r.grid.w as i64 - 1, r.grid.h as i64 - 1);
let (orth, _, _, via_ok) = r.obstacle_maps(0, 0.25, win, false);
// the hole's centre, and a point just outside its edge (a via there would overlap the drill)
for x in [10.0, 10.0 + 1.6 + 0.2] {
let (i, j) = r.grid.to_cell(x, 6.0);
assert!(!via_ok.get(i as usize, j as usize), "{layers}: via allowed at x={x}");
}
let (i, j) = r.grid.to_cell(10.0, 6.0);
assert!(orth[0].get(i as usize, j as usize) && orth[1].get(i as usize, j as usize), "{layers}: track allowed through the hole");
}
}
#[test]
fn edge_cuts_bore_is_outside_the_board_and_keeps_edge_clearance() {
// issue #77: an outline drawn in lines with a round bore (gr_circle) on Edge.Cuts. The outline is
// the outer loop; the bore is a cutout the router may not route through or hug.
let board = Board::from_text("bore.kicad_pcb", r#"(kicad_pcb
(layers (0 "F.Cu" signal) (31 "B.Cu" signal))
(net 1 "SIG")
(gr_line (start 0 0) (end 20 0) (layer "Edge.Cuts"))
(gr_line (start 20 0) (end 20 12) (layer "Edge.Cuts"))
(gr_line (start 20 12) (end 0 12) (layer "Edge.Cuts"))
(gr_line (start 0 12) (end 0 0) (layer "Edge.Cuts"))
(gr_circle (center 10 6) (end 12 6) (layer "Edge.Cuts"))
(footprint "test" (layer "F.Cu") (at 3 6) (property "Reference" "J1")
(pad "1" smd rect (at 0 0) (size 1 1) (layers "F.Cu") (net 1 "SIG"))))"#.into()).unwrap();
assert_eq!(board.outline_cutouts.len(), 1);
assert!(aiflow_board::geom::polygon_area(&board.outline).abs() > 200.0, "the outline is the outer rectangle");
let r = Router::new(board, Rules::default(), false);
let (i, j) = r.grid.to_cell(10.0, 6.0);
assert!(!r.inside.get(i as usize, j as usize), "the bore's centre is not board");
let (i, j) = r.grid.to_cell(12.3, 6.0);
assert!(r.inside.get(i as usize, j as usize), "just outside the bore is board");
assert!(r.edge_dist[j as usize * r.grid.w + i as usize] < 0.5, "and it is close to an edge");
let (i, j) = r.grid.to_cell(3.0, 3.0);
assert!(r.inside.get(i as usize, j as usize));
}
/// Issue #140, the reported panic (lib.rs:842 in 0.2.49, "index out of bounds" in obstacle_maps): a plane
/// net's pad on a part parked outside the outline made a via-search window wholly past the grid. Now the
/// part's pads are set aside and reported, with the part named.
#[test]
fn plane_pad_on_a_part_off_the_board_does_not_panic() {
let t = include_str!("../../../tests/fixtures/bottom-side.kicad_pcb").replace("(footprint \"T:R3\"\n\t\t(layer \"F.Cu\")\n\t\t(at 21 15)", "(footprint \"T:R3\"\n\t\t(layer \"F.Cu\")\n\t\t(at 61 46)");
let b = aiflow_board::Board::from_text("x.kicad_pcb", t).unwrap();
let mut rules = aiflow_router::Rules::default();
rules.planes.insert("GND".into(), "B.Cu".into());
let mut r = aiflow_router::Router::new(b, rules, false);
let (_, report, failed) = aiflow_router::drive::route_board(&mut r, 2, 4, 40);
let un = report["unrouted"].as_array().unwrap();
for key in ["R3.1", "R3.2"] {
assert!(un.iter().any(|u| u[1][0] == key && u[1][1].as_str().unwrap().starts_with("R3 is outside the board outline")), "{key}: {un:?}");
}
assert!(failed >= 2);
}