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
source: the crates, flows, docs and skills of adom-aiflow 0.1.5, so PRs can be opened against them
ad411ea
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
//! The raster every geometric stage works on: 0.1 mm cells over the outline's box, with
//! conservative rasterisers (a cell counts as copper when any part of the shape touches its
//! square), disk dilation for clearance maps, and the outline's inside and edge distance.
use aiflow_board::Pt;
pub const RES: f64 = 0.1;
#[derive(Clone)]
pub struct Grid {
pub res: f64,
pub x0: f64,
pub y0: f64,
pub w: usize,
pub h: usize,
}
/// A boolean raster, row-major (index = j * w + i).
#[derive(Clone)]
pub struct Mask {
pub w: usize,
pub h: usize,
pub bits: Vec<bool>,
}
impl Mask {
pub fn new(w: usize, h: usize) -> Mask {
Mask { w, h, bits: vec![false; w * h] }
}
#[inline]
pub fn get(&self, i: usize, j: usize) -> bool {
self.bits[j * self.w + i]
}
#[inline]
pub fn set(&mut self, i: usize, j: usize, v: bool) {
self.bits[j * self.w + i] = v;
}
pub fn or_with(&mut self, other: &Mask) {
for (a, b) in self.bits.iter_mut().zip(&other.bits) {
*a |= *b;
}
}
pub fn and_not(&mut self, other: &Mask) {
for (a, b) in self.bits.iter_mut().zip(&other.bits) {
*a &= !*b;
}
}
pub fn any(&self) -> bool {
self.bits.iter().any(|b| *b)
}
pub fn count(&self) -> usize {
self.bits.iter().filter(|b| **b).count()
}
}
impl Grid {
pub fn new(bbox: (f64, f64, f64, f64)) -> Grid {
Grid::with_res(bbox, RES)
}
pub fn with_res(bbox: (f64, f64, f64, f64), res: f64) -> Grid {
Grid { res, x0: bbox.0, y0: bbox.1, w: ((bbox.2 - bbox.0) / res).round() as usize + 1, h: ((bbox.3 - bbox.1) / res).round() as usize + 1 }
}
/// Round half up (pads on .x5 coordinates at 0.5 mm pitch snap a consistent 5 cells apart),
/// with 0.1 um of tolerance for KiCad's nanometre noise.
#[inline]
pub fn to_cell(&self, x: f64, y: f64) -> (i64, i64) {
(((x - self.x0) / self.res + 0.5 + 1e-3).floor() as i64, ((y - self.y0) / self.res + 0.5 + 1e-3).floor() as i64)
}
#[inline]
pub fn to_xy(&self, i: i64, j: i64) -> Pt {
let r = |v: f64| (v * 10000.0).round() / 10000.0;
(r(self.x0 + i as f64 * self.res), r(self.y0 + j as f64 * self.res))
}
#[inline]
pub fn in_bounds(&self, i: i64, j: i64) -> bool {
i >= 0 && j >= 0 && (i as usize) < self.w && (j as usize) < self.h
}
pub fn mask(&self) -> Mask {
Mask::new(self.w, self.h)
}
/// Mark every cell whose centre is inside the polygon, plus the cells its edges pass through.
pub fn poly_mask(&self, poly: &[Pt], mask: &mut Mask) {
let n = poly.len();
if n < 3 {
return;
}
let (bx0, by0, bx1, by1) = aiflow_board::geom::bbox(poly);
let i0 = (((bx0 - self.x0) / self.res).floor() as i64 - 1).max(0) as usize;
let i1 = ((((bx1 - self.x0) / self.res).ceil() as i64 + 1).min(self.w as i64 - 1)).max(0) as usize;
let j0 = (((by0 - self.y0) / self.res).floor() as i64 - 1).max(0) as usize;
let j1 = ((((by1 - self.y0) / self.res).ceil() as i64 + 1).min(self.h as i64 - 1)).max(0) as usize;
if i1 < i0 || j1 < j0 {
return;
}
for j in j0..=j1 {
let cy = self.y0 + j as f64 * self.res;
for i in i0..=i1 {
let cx = self.x0 + i as f64 * self.res;
if aiflow_board::geom::point_in_poly(cx, cy, poly) {
mask.set(i, j, true);
}
}
}
// boundary supercover
for k in 0..n {
let (xa, ya) = poly[k];
let (xb, yb) = poly[(k + 1) % n];
let l = (xb - xa).hypot(yb - ya);
let steps = ((l / 0.025).ceil() as usize).max(1);
for s in 0..=steps {
let t = s as f64 / steps as f64;
let ex = xa + (xb - xa) * t;
let ey = ya + (yb - ya) * t;
let ii = ((ex - self.x0) / self.res).round() as i64;
let jj = ((ey - self.y0) / self.res).round() as i64;
if ii >= i0 as i64 && ii <= i1 as i64 && jj >= j0 as i64 && jj <= j1 as i64 {
mask.set(ii as usize, jj as usize, true);
}
}
}
}
/// Flat indices of the cells whose centre is within half_width + slack of segment a-b.
pub fn segment_cells(&self, a: Pt, b: Pt, half_width: f64, slack: Option<f64>) -> Vec<usize> {
let slack = slack.unwrap_or(self.res / 2.0);
let r = half_width + slack;
let (xa, ya) = a;
let (xb, yb) = b;
let i0 = (((xa.min(xb) - r - self.x0) / self.res).floor() as i64).max(0);
let i1 = (((xa.max(xb) + r - self.x0) / self.res).ceil() as i64).min(self.w as i64 - 1);
let j0 = (((ya.min(yb) - r - self.y0) / self.res).floor() as i64).max(0);
let j1 = (((ya.max(yb) + r - self.y0) / self.res).ceil() as i64).min(self.h as i64 - 1);
let mut out = Vec::new();
if i1 < i0 || j1 < j0 {
return out;
}
let (dx, dy) = (xb - xa, yb - ya);
let l2 = dx * dx + dy * dy;
let r2 = (r + 1e-9) * (r + 1e-9);
for j in j0..=j1 {
let cy = self.y0 + j as f64 * self.res;
for i in i0..=i1 {
let cx = self.x0 + i as f64 * self.res;
let t = if l2 < 1e-12 { 0.0 } else { (((cx - xa) * dx + (cy - ya) * dy) / l2).clamp(0.0, 1.0) };
let px = xa + t * dx;
let py = ya + t * dy;
let d2 = (cx - px) * (cx - px) + (cy - py) * (cy - py);
if d2 <= r2 {
out.push(j as usize * self.w + i as usize);
}
}
}
out
}
pub fn disk_cells(&self, c: Pt, radius: f64, slack: Option<f64>) -> Vec<usize> {
self.segment_cells(c, c, radius, slack)
}
/// Binary dilation by a disk of `r_cells` cells. Separable enough to be fast: per row offsets.
pub fn dilate(&self, mask: &Mask, r_cells: f64) -> Mask {
if r_cells <= 0.0 {
return mask.clone();
}
let rr = r_cells.floor() as i64;
let r2 = r_cells * r_cells + 1e-9;
// horizontal half-chords per dy
let mut chords = Vec::with_capacity((2 * rr + 1) as usize);
for dy in -rr..=rr {
let rem = r2 - (dy * dy) as f64;
chords.push((dy, if rem >= 0.0 { rem.sqrt().floor() as i64 } else { -1 }));
}
let w = mask.w as i64;
let h = mask.h as i64;
let mut out = Mask::new(mask.w, mask.h);
// pass 1: horizontal runs per dy would require a 2D scan; do it directly but skip empty rows
for j in 0..h {
let row = &mask.bits[(j * w) as usize..((j + 1) * w) as usize];
if !row.iter().any(|b| *b) {
continue;
}
for i in 0..w {
if !row[i as usize] {
continue;
}
for &(dy, hc) in &chords {
if hc < 0 {
continue;
}
let jj = j + dy;
if jj < 0 || jj >= h {
continue;
}
let a = (i - hc).max(0) as usize;
let b = (i + hc).min(w - 1) as usize;
let base = (jj * w) as usize;
for k in a..=b {
out.bits[base + k] = true;
}
}
}
}
out
}
/// (inside mask, distance of every cell centre to the nearest outline edge).
pub fn outline_distance(&self, outline: &[Pt]) -> (Mask, Vec<f32>) {
let mut inside = self.mask();
let mut dmin = vec![f32::INFINITY; self.w * self.h];
let n = outline.len();
for j in 0..self.h {
let cy = self.y0 + j as f64 * self.res;
for i in 0..self.w {
let cx = self.x0 + i as f64 * self.res;
let mut ins = false;
let mut best = f64::INFINITY;
for k in 0..n {
let (xa, ya) = outline[k];
let (xb, yb) = outline[(k + 1) % n];
if (ya > cy) != (yb > cy) {
let xint = xa + (cy - ya) * (xb - xa) / (yb - ya);
if cx < xint {
ins = !ins;
}
}
let d = aiflow_board::geom::seg_distance(cx, cy, xa, ya, xb, yb);
if d < best {
best = d;
}
}
inside.set(i, j, ins);
dmin[j * self.w + i] = best as f32;
}
}
(inside, dmin)
}
pub fn outline_inside(&self, outline: &[Pt], margin: f64) -> Mask {
let (mut inside, dmin) = self.outline_distance(outline);
for (b, d) in inside.bits.iter_mut().zip(&dmin) {
*b = *b && (*d as f64) >= margin;
}
inside
}
}