app
AI Flow: Routing
Public Made by Adomby adom
The routing stage of AI Flow: routes a placed KiCad board connection by connection and reports what it made, what it could not and why. A guide, never a gate.
main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
//! Connectivity from copper alone: which pads of a net are joined by tracks, vias and zones.
//! Used by the checker and to find what still has to be routed on a partly routed board.
use crate::board::*;
use crate::geom::*;
use std::collections::HashMap;
#[derive(Clone)]
pub struct Uf(Vec<u32>);
impl Uf {
pub fn new(n: usize) -> Uf {
Uf((0..n as u32).collect())
}
pub fn find(&mut self, a: u32) -> u32 {
let mut r = a;
while self.0[r as usize] != r {
r = self.0[r as usize];
}
let mut c = a;
while self.0[c as usize] != r {
let n = self.0[c as usize];
self.0[c as usize] = r;
c = n;
}
r
}
pub fn union(&mut self, a: u32, b: u32) {
let (x, y) = (self.find(a), self.find(b));
if x != y {
self.0[x as usize] = y;
}
}
}
/// One piece of copper on one or more layers, for connectivity.
pub enum Piece {
Pad(usize),
Seg { layer: usize, a: V, b: V, r: f64 },
Via { at: V, r: f64 },
Fill { layer: usize, pts: Vec<V> },
}
pub fn touches(p: &Piece, q: &Piece, board: &Board) -> bool {
use Piece::*;
match (p, q) {
(Pad(i), Pad(j)) => {
let (a, b) = (&board.pads[*i], &board.pads[*j]);
a.layers.iter().any(|l| b.layers.contains(l)) && a.shape.dist_shape(&b.shape) <= 1.0
}
(Pad(i), Seg { layer, a, b, r }) | (Seg { layer, a, b, r }, Pad(i)) => {
let pad = &board.pads[*i];
pad.layers.contains(layer) && pad.shape.dist_seg(*a, *b) <= r + 1.0
}
(Pad(i), Via { at, r }) | (Via { at, r }, Pad(i)) => board.pads[*i].shape.dist_pt(*at) <= r + 1.0 && !board.pads[*i].layers.is_empty(),
(Pad(i), Fill { layer, pts }) | (Fill { layer, pts }, Pad(i)) => {
let pad = &board.pads[*i];
pad.layers.contains(layer) && shape_meets_poly(&pad.shape, pts)
}
(Seg { layer: l1, a, b, r }, Seg { layer: l2, a: c, b: d, r: s }) => l1 == l2 && seg_seg(*a, *b, *c, *d) <= r + s + 1.0,
(Seg { a, b, r, .. }, Via { at, r: s }) | (Via { at, r: s }, Seg { a, b, r, .. }) => pt_seg(*at, *a, *b) <= r + s + 1.0,
(Seg { layer: l1, a, b, r }, Fill { layer: l2, pts }) | (Fill { layer: l2, pts }, Seg { layer: l1, a, b, r }) => {
l1 == l2 && (in_poly(*a, pts) || in_poly(*b, pts) || poly_seg_dist(pts, *a, *b) <= *r + 1.0)
}
(Via { at: a, r }, Via { at: b, r: s }) => hypot(sub(*a, *b)) <= r + s + 1.0,
(Via { at, r }, Fill { pts, .. }) | (Fill { pts, .. }, Via { at, r }) => in_poly(*at, pts) || poly_seg_dist(pts, *at, *at) <= *r,
(Fill { layer: l1, pts: a }, Fill { layer: l2, pts: b }) => l1 == l2 && polys_meet(a, b),
}
}
/// Two fills are one piece of copper as soon as they overlap anywhere. Testing a single vertex
/// missed overlapping zones whose first vertices lie outside each other, which split nets KiCad
/// sees as joined (benchmark B's GND, +PS1_SENS, -PS1 and +PS2_RLY fills).
fn polys_meet(a: &[V], b: &[V]) -> bool {
let bb = |p: &[V]| p.iter().fold((f64::MAX, f64::MAX, f64::MIN, f64::MIN), |m, v| (m.0.min(v.0), m.1.min(v.1), m.2.max(v.0), m.3.max(v.1)));
let (ba, bc) = (bb(a), bb(b));
if ba.0 > bc.2 + 1.0 || bc.0 > ba.2 + 1.0 || ba.1 > bc.3 + 1.0 || bc.1 > ba.3 + 1.0 {
return false;
}
if a.iter().any(|p| in_poly(*p, b)) || b.iter().any(|p| in_poly(*p, a)) {
return true;
}
let (n, m) = (a.len(), b.len());
for i in 0..n {
let (p, q) = (a[i], a[(i + 1) % n]);
if p.0.max(q.0) < bc.0 || p.0.min(q.0) > bc.2 || p.1.max(q.1) < bc.1 || p.1.min(q.1) > bc.3 {
continue;
}
for j in 0..m {
if seg_seg(p, q, b[j], b[(j + 1) % m]) <= 1.0 {
return true;
}
}
}
false
}
fn poly_seg_dist(pts: &[V], a: V, b: V) -> f64 {
let n = pts.len();
let mut d = f64::INFINITY;
for i in 0..n {
d = d.min(seg_seg(a, b, pts[i], pts[(i + 1) % n]));
}
d
}
pub(crate) fn shape_meets_poly(s: &Shape, pts: &[V]) -> bool {
let c = s.center();
if in_poly(c, pts) {
return true;
}
let n = pts.len();
for i in 0..n {
if s.dist_seg(pts[i], pts[(i + 1) % n]) <= 1.0 {
return true;
}
}
false
}
/// Groups of pad indices per net that the copper on `board` already joins. `extra` adds copper
/// from a route in progress, as (net, piece).
/// One connected piece of a net's copper.
pub struct Island {
pub net: NetId,
pub has_pad: bool,
pub fill_pieces: usize,
pub tracks: usize,
pub vias: usize,
pub layer: usize,
pub at: V,
pub area: f64,
}
/// Every connected piece of copper on the board, net by net, pads or none. `pad_groups` keeps only
/// what touches a pad, so a fill nothing feeds is invisible to it.
pub fn islands(board: &Board) -> Vec<Island> {
let mut by_net: HashMap<NetId, Vec<Piece>> = HashMap::new();
for (i, p) in board.pads.iter().enumerate() {
if p.net != NO_NET && !p.layers.is_empty() {
by_net.entry(p.net).or_default().push(Piece::Pad(i));
}
}
for t in &board.tracks {
if t.net != NO_NET {
by_net.entry(t.net).or_default().push(Piece::Seg { layer: t.layer, a: t.a.into(), b: t.b.into(), r: t.width as f64 / 2.0 });
}
}
for v in &board.vias {
if v.net != NO_NET {
by_net.entry(v.net).or_default().push(Piece::Via { at: v.at.into(), r: v.size as f64 / 2.0 });
}
}
for z in &board.zones {
if z.net == NO_NET || z.keepout_tracks {
continue;
}
if z.fills.is_empty() {
for &l in &z.layers {
by_net.entry(z.net).or_default().push(Piece::Fill { layer: l, pts: z.outline.clone() });
}
} else {
for (l, pts) in &z.fills {
by_net.entry(z.net).or_default().push(Piece::Fill { layer: *l, pts: pts.clone() });
}
}
}
let mut out = Vec::new();
for (net, pieces) in by_net {
let mut uf = Uf::new(pieces.len());
for i in 0..pieces.len() {
for j in i + 1..pieces.len() {
if uf.find(i as u32) != uf.find(j as u32) && touches(&pieces[i], &pieces[j], board) {
uf.union(i as u32, j as u32);
}
}
}
let mut groups: HashMap<u32, Island> = HashMap::new();
for (i, p) in pieces.iter().enumerate() {
let isl = groups.entry(uf.find(i as u32)).or_insert(Island { net, has_pad: false, fill_pieces: 0, tracks: 0, vias: 0, layer: 0, at: V(0.0, 0.0), area: 0.0 });
match p {
Piece::Pad(_) => isl.has_pad = true,
Piece::Seg { layer, a, .. } => {
isl.tracks += 1;
if isl.fill_pieces == 0 {
isl.layer = *layer;
isl.at = *a;
}
}
Piece::Via { at, .. } => {
isl.vias += 1;
if isl.fill_pieces == 0 && isl.tracks == 0 {
isl.at = *at;
}
}
Piece::Fill { layer, pts } => {
isl.fill_pieces += 1;
isl.layer = *layer;
isl.at = pts.first().copied().unwrap_or(V(0.0, 0.0));
let n = pts.len();
let a: f64 = (0..n).map(|k| pts[k].0 * pts[(k + 1) % n].1 - pts[(k + 1) % n].0 * pts[k].1).sum();
isl.area += (a / 2.0).abs();
}
}
}
out.extend(groups.into_values());
}
out.sort_by(|a, b| (a.net, a.at.0.to_bits()).cmp(&(b.net, b.at.0.to_bits())));
out
}
pub fn pad_groups(board: &Board, extra: &[(NetId, Piece)]) -> HashMap<NetId, Vec<Vec<usize>>> {
let mut by_net: HashMap<NetId, Vec<Piece>> = HashMap::new();
for (i, p) in board.pads.iter().enumerate() {
if p.net != NO_NET && !p.layers.is_empty() {
by_net.entry(p.net).or_default().push(Piece::Pad(i));
}
}
for t in &board.tracks {
by_net.entry(t.net).or_default().push(Piece::Seg { layer: t.layer, a: t.a.into(), b: t.b.into(), r: t.width as f64 / 2.0 });
}
for v in &board.vias {
by_net.entry(v.net).or_default().push(Piece::Via { at: v.at.into(), r: v.size as f64 / 2.0 });
}
for z in &board.zones {
if z.net == NO_NET || z.keepout_tracks {
continue;
}
if z.fills.is_empty() {
for &l in &z.layers {
by_net.entry(z.net).or_default().push(Piece::Fill { layer: l, pts: z.outline.clone() });
}
} else {
for (l, pts) in &z.fills {
by_net.entry(z.net).or_default().push(Piece::Fill { layer: *l, pts: pts.clone() });
}
}
}
for (n, p) in extra {
let q = match p {
Piece::Pad(i) => Piece::Pad(*i),
Piece::Seg { layer, a, b, r } => Piece::Seg { layer: *layer, a: *a, b: *b, r: *r },
Piece::Via { at, r } => Piece::Via { at: *at, r: *r },
Piece::Fill { layer, pts } => Piece::Fill { layer: *layer, pts: pts.clone() },
};
by_net.entry(*n).or_default().push(q);
}
let mut out = HashMap::new();
for (net, pieces) in by_net {
let mut uf = Uf::new(pieces.len());
for i in 0..pieces.len() {
for j in i + 1..pieces.len() {
if uf.find(i as u32) != uf.find(j as u32) && touches(&pieces[i], &pieces[j], board) {
uf.union(i as u32, j as u32);
}
}
}
let mut groups: HashMap<u32, Vec<usize>> = HashMap::new();
for (i, p) in pieces.iter().enumerate() {
if let Piece::Pad(k) = p {
groups.entry(uf.find(i as u32)).or_default().push(*k);
}
}
let mut v: Vec<Vec<usize>> = groups.into_values().collect();
v.sort();
out.insert(net, v);
}
out
}
/// `pad_groups` for one net, with routed copper in progress: cheap enough to ask during routing.
pub fn net_groups(board: &Board, net: NetId, extra: &[(NetId, Piece)]) -> Vec<Vec<usize>> {
let mut pieces: Vec<&Piece> = Vec::new();
let mut own: Vec<Piece> = Vec::new();
for (i, p) in board.pads.iter().enumerate() {
if p.net == net && !p.layers.is_empty() {
own.push(Piece::Pad(i));
}
}
for t in board.tracks.iter().filter(|t| t.net == net) {
own.push(Piece::Seg { layer: t.layer, a: t.a.into(), b: t.b.into(), r: t.width as f64 / 2.0 });
}
for v in board.vias.iter().filter(|v| v.net == net) {
own.push(Piece::Via { at: v.at.into(), r: v.size as f64 / 2.0 });
}
for z in board.zones.iter().filter(|z| z.net == net && !z.keepout_tracks) {
if z.fills.is_empty() {
for &l in &z.layers {
own.push(Piece::Fill { layer: l, pts: z.outline.clone() });
}
} else {
for (l, pts) in &z.fills {
own.push(Piece::Fill { layer: *l, pts: pts.clone() });
}
}
}
pieces.extend(own.iter());
pieces.extend(extra.iter().filter(|(n, _)| *n == net).map(|(_, p)| p));
let mut uf = Uf::new(pieces.len());
for i in 0..pieces.len() {
for j in i + 1..pieces.len() {
if uf.find(i as u32) != uf.find(j as u32) && touches(pieces[i], pieces[j], board) {
uf.union(i as u32, j as u32);
}
}
}
let mut groups: HashMap<u32, Vec<usize>> = HashMap::new();
for (i, p) in pieces.iter().enumerate() {
if let Piece::Pad(k) = p {
groups.entry(uf.find(i as u32)).or_default().push(*k);
}
}
let mut v: Vec<Vec<usize>> = groups.into_values().collect();
v.sort();
v
}