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
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
//! A plain SVG of the copper, to look at a result. Top layer red, bottom blue, vias white.
use crate::board::*;
use crate::geom::*;
use std::fmt::Write;
/// How to draw. `fills`: the zones' filled copper as saved, not their outlines, so a board KiCad has
/// refilled shows what the fab gets. `highlight`: only these nets at full strength. `labels`: name pads.
#[derive(Default)]
pub struct View {
pub fills: bool,
pub highlight: Vec<String>,
pub labels: bool,
}
pub fn svg(b: &Board, window: Option<(f64, f64, f64, f64)>) -> String {
svg_view(b, window, &View { labels: window.is_some(), ..Default::default() })
}
pub fn svg_view(b: &Board, window: Option<(f64, f64, f64, f64)>, view: &View) -> String {
let bb = window.map(|w| (w.0 * NM, w.1 * NM, w.2 * NM, w.3 * NM)).unwrap_or(b.bbox);
let (w, h) = ((bb.2 - bb.0) / NM, (bb.3 - bb.1) / NM);
let last = b.layers.len() - 1;
let mut s = String::new();
let hit = |b: &Board, n: NetId| view.highlight.is_empty() || view.highlight.iter().any(|h| h == b.net_name(n));
let op = |b: &Board, n: NetId, full: f64| if hit(b, n) { full } else { 0.18 };
let _ = write!(s, r##"<svg xmlns="http://www.w3.org/2000/svg" viewBox="{} {} {} {}" width="{}" height="{}"><rect x="{}" y="{}" width="{}" height="{}" fill="#101418"/>"##, bb.0 / NM, bb.1 / NM, w, h, 1500.0, 1500.0 * h / w, bb.0 / NM, bb.1 / NM, w, h);
for z in &b.zones {
if z.keepout_tracks || z.outline.len() < 3 || !(z.layers.contains(&0) || z.layers.contains(&last)) {
continue;
}
let col = if z.layers.contains(&0) { "#a03030" } else { "#3050a0" };
let lit = hit(b, z.net);
if view.fills && !z.fills.is_empty() {
for (l, f) in &z.fills {
if *l != 0 && *l != last {
continue;
}
let col = if *l == 0 { "#a03030" } else { "#3050a0" };
let pts: String = f.iter().map(|p| format!("{:.4},{:.4} ", p.0 / NM, p.1 / NM)).collect();
let _ = write!(s, r#"<polygon points="{pts}" fill="{col}" fill-opacity="{}"/>"#, if lit { 0.55 } else { 0.22 });
}
continue;
}
let pts: String = z.outline.iter().map(|p| format!("{:.4},{:.4} ", p.0 / NM, p.1 / NM)).collect();
let _ = write!(s, r#"<polygon points="{pts}" fill="{col}" fill-opacity="{}"/>"#, if lit { 0.4 } else { 0.25 });
}
fn draw(s: &mut String, shape: &Shape, col: &str) {
match shape {
Shape::Circle { c, r } => {
let _ = write!(s, r#"<circle cx="{:.4}" cy="{:.4}" r="{:.4}" fill="{col}"/>"#, c.0 / NM, c.1 / NM, r / NM);
}
Shape::Capsule { a, b, r } => {
let _ = write!(s, r#"<line x1="{:.4}" y1="{:.4}" x2="{:.4}" y2="{:.4}" stroke="{col}" stroke-width="{:.4}" stroke-linecap="round"/>"#, a.0 / NM, a.1 / NM, b.0 / NM, b.1 / NM, r * 2.0 / NM);
}
Shape::Poly { pts, .. } => {
let t: String = pts.iter().map(|p| format!("{:.4},{:.4} ", p.0 / NM, p.1 / NM)).collect();
let _ = write!(s, r#"<polygon points="{t}" fill="{col}"/>"#);
}
Shape::Multi(v) => v.iter().for_each(|x| draw(s, x, col)),
}
}
let pad = |s: &mut String, p: &Pad, col: &str| draw(s, &p.shape, col);
for p in b.pads.iter().filter(|p| p.layers.contains(&last) && !p.layers.contains(&0)) {
pad(&mut s, p, "#5070c0");
}
for t in b.tracks.iter().filter(|t| t.layer != 0) {
let _ = write!(s, r##"<line x1="{:.4}" y1="{:.4}" x2="{:.4}" y2="{:.4}" stroke="#4d8dff" stroke-opacity="{:.2}" stroke-width="{:.4}" stroke-linecap="round"/>"##, to_mm(t.a.0), to_mm(t.a.1), to_mm(t.b.0), to_mm(t.b.1), op(b, t.net, 0.85), to_mm(t.width));
}
for p in b.pads.iter().filter(|p| p.layers.contains(&0)) {
pad(&mut s, p, "#c8a040");
}
for t in b.tracks.iter().filter(|t| t.layer == 0) {
let _ = write!(s, r##"<line x1="{:.4}" y1="{:.4}" x2="{:.4}" y2="{:.4}" stroke="#ff5050" stroke-opacity="{:.2}" stroke-width="{:.4}" stroke-linecap="round"/>"##, to_mm(t.a.0), to_mm(t.a.1), to_mm(t.b.0), to_mm(t.b.1), op(b, t.net, 0.9), to_mm(t.width));
}
for v in b.vias.iter().filter(|v| hit(b, v.net)) {
let _ = write!(s, r##"<circle cx="{:.4}" cy="{:.4}" r="{:.4}" fill="#e8e8e8"/><circle cx="{:.4}" cy="{:.4}" r="{:.4}" fill="#101418"/>"##, to_mm(v.at.0), to_mm(v.at.1), to_mm(v.size) / 2.0, to_mm(v.at.0), to_mm(v.at.1), to_mm(v.drill) / 2.0);
}
// Zoomed in, name the pads: ref.pin and net, small enough to sit inside the view.
if view.labels {
let size = w.min(h) / 42.0;
for p in &b.pads {
let c: V = p.center.into();
if c.0 < bb.0 || c.0 > bb.2 || c.1 < bb.1 || c.1 > bb.3 || p.layers.is_empty() || !hit(b, p.net) {
continue;
}
let net = b.net_name(p.net).rsplit('/').next().unwrap_or("");
let net = if net.starts_with("unconnected-") { "n/c" } else { net };
let label = format!("{} {}", b.pad_name(b.pads.iter().position(|q| std::ptr::eq(q, p)).unwrap()), net.replace('&', "and").replace('<', "").replace('>', ""));
let tw = label.len() as f64 * size * 0.56;
let _ = write!(s, r##"<rect x="{:.4}" y="{:.4}" width="{tw:.4}" height="{:.4}" fill="#000000" fill-opacity="0.7"/><text x="{:.4}" y="{:.4}" font-family="Arial" font-size="{size:.4}" fill="#ffffff" text-anchor="middle">{label}</text>"##, c.0 / NM - tw / 2.0, c.1 / NM - size * 0.75, size * 1.15, c.0 / NM, c.1 / NM + size * 0.15);
}
}
for (a, c) in &b.edges {
let _ = write!(s, r##"<line x1="{:.4}" y1="{:.4}" x2="{:.4}" y2="{:.4}" stroke="#d0d040" stroke-width="0.08"/>"##, a.0 / NM, a.1 / NM, c.0 / NM, c.1 / NM);
}
s.push_str("</svg>");
s
}