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.
← Commit history
AI Flow 0.1.56: KiCad 10 net references in the Fusion converter; via size as a [size, drill] pair
8 files changed
+41−19
Cargo.lock+10−10@@ -4,7 +4,7 @@ version = 4 [[package]] name = "adom-aiflow"-version = "0.1.55"+version = "0.1.56" dependencies = [ "aiflow-analyze", "aiflow-board",@@ -24,7 +24,7 @@ dependencies = [ [[package]] name = "aiflow-analyze"-version = "0.1.55"+version = "0.1.56" dependencies = [ "serde", "serde_json",@@ -33,7 +33,7 @@ dependencies = [ [[package]] name = "aiflow-board"-version = "0.1.55"+version = "0.1.56" dependencies = [ "roxmltree", "serde",@@ -42,7 +42,7 @@ dependencies = [ [[package]] name = "aiflow-bridge"-version = "0.1.55"+version = "0.1.56" dependencies = [ "aiflow-board", "serde",@@ -51,7 +51,7 @@ dependencies = [ [[package]] name = "aiflow-copper"-version = "0.1.55"+version = "0.1.56" dependencies = [ "aiflow-board", "aiflow-grid",@@ -61,7 +61,7 @@ dependencies = [ [[package]] name = "aiflow-grid"-version = "0.1.55"+version = "0.1.56" dependencies = [ "aiflow-board", "serde",@@ -70,7 +70,7 @@ dependencies = [ [[package]] name = "aiflow-place"-version = "0.1.55"+version = "0.1.56" dependencies = [ "aiflow-board", "serde",@@ -79,7 +79,7 @@ dependencies = [ [[package]] name = "aiflow-pours"-version = "0.1.55"+version = "0.1.56" dependencies = [ "aiflow-board", "aiflow-copper",@@ -89,7 +89,7 @@ dependencies = [ [[package]] name = "aiflow-router"-version = "0.1.55"+version = "0.1.56" dependencies = [ "aiflow-board", "aiflow-grid",@@ -99,7 +99,7 @@ dependencies = [ [[package]] name = "aiflow-run"-version = "0.1.55"+version = "0.1.56" dependencies = [ "serde", "serde_json",
Cargo.toml+1−1@@ -14,7 +14,7 @@ members = [ ] [workspace.package]-version = "0.1.55"+version = "0.1.56" edition = "2021" license = "MIT" repository = "https://wiki.adom.inc/adom/adom-aiflow"
bin/adom-aiflow⋯ 1 unchanged line ⋯
crates/aiflow-board/src/eagle.rs+7−3@@ -43,7 +43,11 @@ pub fn to_kicad(text: &str) -> Result<String, String> { for s in &signals { let name = s.attribute("name").unwrap_or("").to_string(); if !name.is_empty() && !nets.contains_key(&name) { order.push(name.clone()); nets.insert(name, order.len()); } } let mut pad_net: BTreeMap<(String, String), String> = BTreeMap::new(); for s in &signals { for c in s.children().filter(|x| x.has_tag_name("contactref")) { pad_net.insert((c.attribute("element").unwrap_or("").into(), c.attribute("pad").unwrap_or("").into()), s.attribute("name").unwrap_or("").into()); } }+ // KiCad's numbered net format: a pad names its net, a track or via gives the number only, and a zone+ // gives the number and then (net_name ...). KiCad 10 refuses a track, via or zone written like a pad. let net = |name: &str| -> String { match nets.get(name) { Some(k) => format!("(net {k} {})", q(name)), None => "(net 0 \"\")".into() } };+ let net_id = |name: &str| -> String { format!("(net {})", nets.get(name).copied().unwrap_or(0)) };+ let zone_net = |name: &str| -> String { format!("(net {}) (net_name {})", nets.get(name).copied().unwrap_or(0), q(name)) }; let mut o = String::new(); o.push_str("(kicad_pcb (version 20241229) (generator \"adom-aiflow-fusion\") (generator_version \"1\")\n (general (thickness 1.6))\n (paper \"A4\")\n");@@ -122,11 +126,11 @@ pub fn to_kicad(text: &str) -> Result<String, String> { let name = s.attribute("name").unwrap_or(""); for w in s.children().filter(|x| x.has_tag_name("wire")) { let Some(l) = layer(w.attribute("layer")) else { continue };- let _ = writeln!(o, " (segment (start {} {}) (end {} {}) (width {}) (layer {l}) {})", n(f(&w, "x1")), n(yy(f(&w, "y1"))), n(f(&w, "x2")), n(yy(f(&w, "y2"))), n(f(&w, "width").max(0.05)), net(name));+ let _ = writeln!(o, " (segment (start {} {}) (end {} {}) (width {}) (layer {l}) {})", n(f(&w, "x1")), n(yy(f(&w, "y1"))), n(f(&w, "x2")), n(yy(f(&w, "y2"))), n(f(&w, "width").max(0.05)), net_id(name)); } for v in s.children().filter(|x| x.has_tag_name("via")) { let drill = f(&v, "drill");- let _ = writeln!(o, " (via (at {} {}) (size {}) (drill {}) (layers \"F.Cu\" \"B.Cu\") {})", n(f(&v, "x")), n(yy(f(&v, "y"))), n(f(&v, "diameter").max(drill + 2.0 * ring(drill, via_rv, via_min, via_max))), n(drill), net(name));+ let _ = writeln!(o, " (via (at {} {}) (size {}) (drill {}) (layers \"F.Cu\" \"B.Cu\") {})", n(f(&v, "x")), n(yy(f(&v, "y"))), n(f(&v, "diameter").max(drill + 2.0 * ring(drill, via_rv, via_min, via_max))), n(drill), net_id(name)); } for p in s.children().filter(|x| x.has_tag_name("polygonpour") || x.has_tag_name("polygon")) { let Some(l) = layer(p.attribute("layer")) else { continue };@@ -134,7 +138,7 @@ pub fn to_kicad(text: &str) -> Result<String, String> { let outline = if p.has_tag_name("polygon") { verts(p) } else { p.children().find(|x| x.has_tag_name("polygonoutlineobjects")).map(verts).unwrap_or_default() }; if outline.is_empty() { continue; } let pname = p.attribute("name").map(|v| format!(" (name {})", q(v))).unwrap_or_default();- let _ = write!(o, " (zone {} (layer {l}){pname} (hatch edge 0.5) (priority {}) (connect_pads (clearance {})) (min_thickness {}) (fill yes (thermal_gap 0.3) (thermal_bridge_width 0.3))\n (polygon (pts {outline}))", net(name), p.attribute("rank").and_then(|r| r.parse::<i64>().ok()).map(|r| (7 - r).max(0)).unwrap_or(0), n(f(&p, "isolate").max(0.1)), n(f(&p, "width").max(0.1)));+ let _ = write!(o, " (zone {} (layer {l}){pname} (hatch edge 0.5) (priority {}) (connect_pads (clearance {})) (min_thickness {}) (fill yes (thermal_gap 0.3) (thermal_bridge_width 0.3))\n (polygon (pts {outline}))", zone_net(name), p.attribute("rank").and_then(|r| r.parse::<i64>().ok()).map(|r| (7 - r).max(0)).unwrap_or(0), n(f(&p, "isolate").max(0.1)), n(f(&p, "width").max(0.1))); if let Some(fd) = p.children().find(|x| x.has_tag_name("polygonfilldetails")) { for sh in fd.children().filter(|x| x.has_tag_name("polygonshape")) { if let Some(seg) = sh.children().find(|x| x.has_tag_name("polygonoutlinesegments")) { let _ = write!(o, "\n (filled_polygon (layer {l}) (pts {}))", verts(seg)); }
crates/aiflow-router/src/rules.rs+16@@ -82,9 +82,14 @@ impl Rules { // Escape necks inherit the requested track width. An explicit smaller // stub must still satisfy the separately declared manufacturing minimum. r.stub = num("stub").unwrap_or(r.track).max(num("minTrackWidth").unwrap_or(r.track));+ // a via is a size, or a [size, drill] pair (the way wideNets gives widths); either form is read,+ // never silently skipped+ let pair = |k: &str| spec.get(k).and_then(|v| v.as_array()).and_then(|a| Some((a.first()?.as_f64()?, a.get(1).and_then(|d| d.as_f64())))); if let Some(v) = num("viaSignal") { r.via_small = v; }+ if let Some((v, d)) = pair("viaSignal") { r.via_small = v; if let Some(d) = d { r.drill_small = d; } } if let Some(v) = num("drillSignal") { r.drill_small = v; } if let Some(v) = num("viaPower") { r.via = v; }+ if let Some((v, d)) = pair("viaPower") { r.via = v; if let Some(d) = d { r.drill = d; } } if let Some(v) = num("drillPower") { r.drill = v; } r }@@ -117,3 +122,14 @@ mod tests { assert!(Rules::from_spec(&json!({})).plane_under.is_empty()); } }++#[cfg(test)]+mod via_pair_tests {+ #[test]+ fn via_power_as_pair_or_number() {+ let a = super::Rules::from_spec(&serde_json::json!({"viaPower": [1.0, 0.4]}));+ assert_eq!((a.via, a.drill), (1.0, 0.4));+ let b = super::Rules::from_spec(&serde_json::json!({"viaPower": 0.9, "drillPower": 0.35}));+ assert_eq!((b.via, b.drill), (0.9, 0.35));+ }+}
docs/release-0.1.56.mdadded+4@@ -0,0 +1,4 @@+# AI Flow 0.1.56++- **KiCad 10 loads a converted Fusion board.** The Fusion-to-KiCad converter wrote every net reference the way a pad does, `(net 1 "GND")`, and KiCad 10.0.3 refused the board ("Failed to load board"), so the DRC gate could not run. Tracks and vias now give the number only, `(net 1)`, and zones give `(net 1) (net_name "GND")`, as KiCad writes them. Checked with KiCad 10.0.3 on the buck molecule's live board.+- **A via size can be a `[size, drill]` pair.** `"viaPower": [1.0, 0.4]` (the way `wideNets` gives widths) was silently ignored and cost a refine pass; it is now read, as is a plain number with `drillPower`. The same for `viaSignal`.
package.json+1−1@@ -1,7 +1,7 @@ { "slug": "adom-aiflow", "type": "app",- "version": "0.1.55",+ "version": "0.1.56", "title": "AI Flow", "description": "Adom's AI Flow: a tool to help the AI follow all of the steps it takes to build a board. One Rust binary with a crate per step (placement helpers, a grid router with Kelvin taps, pours with keepouts, KiCad's DRC gate, live landing through the KiCad Bridge, copper measurement, current and thermal analysis) and a finish line that refuses an unfinished board. Every command answers with hints for the AI; every turn, its thinking time and every rework loop go into run.jsonl, so Claude, Codex and any other engine are compared on the same flow. KiCad today; Altium, Fusion and Adom's own web apps next.", "summary": "Adom's AI Flow: a tool to help the AI follow all of the steps it takes to build a board. The AI thinks its way from placement through routing, pours, current and thermal analysis to a delivered video; the binary does the fast, deterministic parts of every step, hands back hints, and keeps a ledger of every turn, every return to an earlier step, and the clock from the prompt to done.",
page.json+2−4@@ -1,7 +1,7 @@ { "slug": "adom-aiflow", "type": "app",- "version": "0.1.55",+ "version": "0.1.56", "title": "AI Flow", "description": "Adom's AI Flow: a tool to help the AI follow all of the steps it takes to build a board. One Rust binary with a crate per step (placement helpers, a grid router with Kelvin taps, pours with keepouts, KiCad's DRC gate, live landing through the KiCad Bridge, copper measurement, current and thermal analysis) and a finish line that refuses an unfinished board. Every command answers with hints for the AI; every turn, its thinking time and every rework loop go into run.jsonl, so Claude, Codex and any other engine are compared on the same flow. KiCad today; Altium, Fusion and Adom's own web apps next.", "summary": "Adom's AI Flow: a tool to help the AI follow all of the steps it takes to build a board. The AI thinks its way from placement through routing, pours, current and thermal analysis to a delivered video; the binary does the fast, deterministic parts of every step, hands back hints, and keeps a ledger of every turn, every return to an earlier step, and the clock from the prompt to done.",@@ -44,9 +44,7 @@ "run.jsonl", "thermal analysis" ],- "dependencies": {- "adom/adom-lbr": "*"- },+ "dependencies": {}, "scripts": { "install": "./install.sh", "uninstall": "./uninstall.sh"