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
Colby Knox
0.1.99: EDA backends (KiCad, Fusion, Altium behind one Eda trait) and the Altium lane
eeecdb7
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960
//! Live: the Altium backend through the real Bridge against a SCRATCH board open in Altium on a box.
//! AIFLOW_ALTIUM_LIVE=<absolute .PcbDoc path on the box> AIFLOW_TARGET=<box> cargo test -p aiflow-bridge --test altium_live -- --ignored --nocapture
//! It moves a part and back, routes a short track with a via, removes them, and reads the pours.
use aiflow_bridge::Bridge;
use serde_json::{json, Value};
#[test]
#[ignore]
fn altium_backend_round_trip() {
let path = std::env::var("AIFLOW_ALTIUM_LIVE").expect("AIFLOW_ALTIUM_LIVE");
let target = std::env::var("AIFLOW_TARGET").unwrap_or("DESKTOP-BAR6S0F".into());
let board = format!("altium:{path}");
aiflow_bridge::eda::set_run_board(&board);
let br = Bridge { ai_thread: "aiflow-altium-live-test".into(), target };
let st = br.call("kicad_routing_state", &json!({"filePath": board}));
assert_eq!(st["success"], json!(true), "{st}");
let rev = st["revision"].as_str().unwrap().to_string();
eprintln!("read: {} segments, {} vias, {} pads, unconnected {}", st["segments"].as_array().unwrap().len(), st["vias"].as_array().unwrap().len(), st["pads"].as_array().unwrap().len(), st["unconnected"]);
// move the first part by +1 mm x (plan frame) and back
let raw = br.raw_call("altium_routing_state", &json!({"expectPath": path}));
let c = raw["components"].as_array().unwrap().iter().find(|c| c["side"] == "top").unwrap().clone();
let (x, y, rot) = (c["x"].as_f64().unwrap(), -c["y"].as_f64().unwrap(), c["rotation"].as_f64().unwrap());
let m = br.call("kicad_move_footprint", &json!({"filePath": board, "expectedRevision": rev, "refs": [{"ref": c["ref"], "x": x + 1.0, "y": y, "rotation": rot}]}));
assert_eq!(m["success"], json!(true), "{m}");
let back = br.call("kicad_move_footprint", &json!({"filePath": board, "expectedRevision": m["revision"], "refs": [{"ref": c["ref"], "x": x, "y": y, "rotation": rot}]}));
assert_eq!(back["success"], json!(true), "{back}");
// a stale revision is refused
let stale = br.call("kicad_route_net", &json!({"filePath": board, "expectedRevision": "0000", "net": "GND", "paths": [[[0.0, 0.0], [1.0, 0.0]]]}));
assert_eq!(stale["errorCode"], json!("stale_board"), "{stale}");
// a short GND track, then a via down to B.Cu, inside the board's lower-left corner (plan frame: y down)
let pts: Vec<(f64, f64)> = raw["outline"].as_array().unwrap().iter().map(|p| (p[0].as_f64().unwrap(), -p[1].as_f64().unwrap())).collect();
let (x0, y0) = (pts.iter().map(|p| p.0).fold(f64::MAX, f64::min) + 3.0, pts.iter().map(|p| p.1).fold(f64::MIN, f64::max) - 3.0);
let rt = br.call("kicad_route_net", &json!({"filePath": board, "expectedRevision": back["revision"], "net": "GND", "width": 0.3, "layer": "F.Cu",
"paths": [[[x0, y0], [x0 + 2.0, y0], {"x": x0 + 2.0, "y": y0 - 1.0, "layer": "B.Cu"}]]}));
assert_eq!(rt["success"], json!(true), "{rt}");
let after = br.call("kicad_routing_state", &json!({"filePath": board}));
let near = |a: f64, b: f64| (a - b).abs() < 0.002;
let mine: Vec<Value> = after["segments"].as_array().unwrap().iter().chain(after["vias"].as_array().unwrap()).filter(|o| {
let s = &o["start"]; let p = &o["position"];
(s.is_object() && near(s["y"].as_f64().unwrap(), y0) && near(s["x"].as_f64().unwrap(), x0))
|| (s.is_object() && near(s["x"].as_f64().unwrap(), x0 + 2.0) && near(s["y"].as_f64().unwrap(), y0))
|| (p.is_object() && near(p["x"].as_f64().unwrap(), x0 + 2.0) && near(p["y"].as_f64().unwrap(), y0 - 1.0))
}).map(|o| o["uuid"].clone()).collect();
eprintln!("landed ids: {mine:?}");
assert_eq!(mine.len(), 3, "2 tracks + 1 via in the plan frame where they were asked for");
let rm = br.call("kicad_remove_route", &json!({"filePath": board, "expectedRevision": after["revision"], "itemIds": mine}));
assert_eq!(rm["success"], json!(true), "{rm}");
let end = br.call("kicad_routing_state", &json!({"filePath": board}));
assert_eq!(end["segments"].as_array().unwrap().len(), st["segments"].as_array().unwrap().len());
assert_eq!(end["vias"].as_array().unwrap().len(), st["vias"].as_array().unwrap().len());
let z = br.call("kicad_zone_state", &json!({"filePath": board, "refill": false}));
assert_eq!(z["success"], json!(true), "{z}");
eprintln!("zones: {}", z["filledAreaByLayerMm2"]);
let v = br.call("kicad_routing_validate", &json!({"filePath": board}));
eprintln!("validate: unconnected {} errors {}", v["unconnected"], v["errors"]);
}