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
Recognize actual rectangular pad contact in explicit-current branch screening; retain disconnected-net refusal
2 files changed
+41−21
crates/aiflow-analyze/examples/branch_screen.rs+10−19@@ -1,22 +1,13 @@-use serde_json::{json, Value};+//! Read-only diagnostic for the optional explicit-terminal branch model.+use aiflow_board::Board;+use serde_json::Value; fn main() {- let a: Vec<_> = std::env::args().collect();- let b = aiflow_board::Board::load(&a[1]).unwrap();- let spec: Value = serde_json::from_slice(&std::fs::read(&a[2]).unwrap()).unwrap();- let mut result = Vec::new();- for (net, load) in spec["loads"].as_object().unwrap() {- if load["currentModel"] == "resistive-branches" {- match aiflow_analyze::branches::screen(&b, net, load, &spec) {- Ok(r) => result.push(r),- Err(e) => {- eprintln!("{net}: {e}");- std::process::exit(1);- }- }- }+ let args: Vec<String> = std::env::args().collect();+ assert_eq!(args.len(), 4, "usage: branch_screen BOARD SPEC NET");+ let board = Board::from_text(&args[1], std::fs::read_to_string(&args[1]).unwrap()).unwrap();+ let spec: Value = serde_json::from_str(&std::fs::read_to_string(&args[2]).unwrap()).unwrap();+ match aiflow_analyze::branches::screen(&board, &args[3], &spec["loads"][&args[3]], &spec) {+ Ok(value) => println!("{}", serde_json::to_string_pretty(&value).unwrap()),+ Err(error) => { eprintln!("{error}"); std::process::exit(2); } }- println!(- "{}",- serde_json::to_string_pretty(&json!({"results":result})).unwrap()- ); }
crates/aiflow-analyze/src/branches.rs+31−2@@ -304,7 +304,16 @@ pub fn screen(board: &Board, net: &str, load: &Value, spec: &Value) -> Result<Va let center = key(l, (p.x, p.y)); for k in &candidates { if k.0 == l- && distance(pt(*k), (p.x, p.y)) <= p.size.0.min(p.size.1) / 2. + 1e-7+ && if p.shape == "rect" {+ p.polys.iter().any(|poly| {+ let (x, y) = pt(*k);+ aiflow_board::geom::point_poly_distance(x, y, poly) <= 1e-7+ })+ } else {+ // Rounded pads retain the conservative central-circle model.+ // The board parser currently represents roundrects by bounding boxes.+ distance(pt(*k), (p.x, p.y)) <= p.size.0.min(p.size.1) / 2. + 1e-7+ } { unite(center, *k, &mut parents); }@@ -397,7 +406,7 @@ pub fn screen(board: &Board, net: &str, load: &Value, spec: &Value) -> Result<Va cases.push(json!({"throughPadLayerCase":mask,"conductors":conductors})); } Ok(- json!({"net":net,"status":if worst<=max_rise && peak_barrel<=barrel_limit{"pass"}else{"fail"},"method":"explicit-pin resistive centerlines plus mandatory same-input Fields","peakTrackRiseC":worst,"peakBarrelDensityAperMm2":peak_barrel,"barrelDensityLimitAperMm2":barrel_limit,"maxRiseC":max_rise,"cases":cases,"how":format!("branch currents give {worst:.2} C maximum track estimate against {max_rise:.2} C; barrel density {peak_barrel:.2} against {barrel_limit:.2} A/mm2; all through-terminal layer extremes checked"),"limits":"Nominal straight-track and barrel resistances, ideal pad central regions; no pad/contact thermal qualification. Filled-copper Fields and physical stack validation remain mandatory."}),+ json!({"net":net,"status":if worst<=max_rise && peak_barrel<=barrel_limit{"pass"}else{"fail"},"method":"explicit-pin resistive centerlines plus mandatory same-input Fields","peakTrackRiseC":worst,"peakBarrelDensityAperMm2":peak_barrel,"barrelDensityLimitAperMm2":barrel_limit,"maxRiseC":max_rise,"cases":cases,"how":format!("branch currents give {worst:.2} C maximum track estimate against {max_rise:.2} C; barrel density {peak_barrel:.2} against {barrel_limit:.2} A/mm2; all through-terminal layer extremes checked"),"limits":"Nominal straight-track and barrel resistances, ideal rectangular pad outlines and conservative rounded-pad central regions; no pad/contact thermal qualification. Filled-copper Fields and physical stack validation remain mandatory."}), ) } @@ -467,6 +476,26 @@ mod geometry_tests { assert_eq!(r["status"], "fail"); } #[test]+ fn rectangular_corner_contact_and_rotated_contact_are_connected_but_gaps_refuse() {+ for rotation in [0., 90., 37.] {+ let base = board(2.).text;+ let (dx, dy) = aiflow_board::geom::rot_kicad(0.45, 0.9, rotation);+ let shape = format!("(at 0 0 {rotation}) (size 1 2)");+ let text = base.replace(+ r#"(property "Reference" "F1") (pad "1" smd circle (at 0 0) (size 1 1)"#,+ &format!(r#"(property "Reference" "F1") (pad "1" smd rect {shape}"#),+ );+ for (offset, connected) in [(0., true), (2., false)] {+ let endpoint = format!("(end {:.6} {:.6})", 12. + dx + offset, 2. + dy);+ let text = text.replace("(end 12 2)", &endpoint);+ let b = Board::from_text("corner.kicad_pcb", text).unwrap();+ let s = spec();+ let result = screen(&b, "V", &s["loads"]["V"], &s);+ assert_eq!(result.is_ok(), connected, "rotation={rotation}, offset={offset}: {result:?}");+ }+ }+ }+ #[test] fn missing_pin_and_implicit_material_assumptions_refuse() { let mut s = spec(); s["loads"]["V"]["terminals"].as_array_mut().unwrap().pop();