main
Colby Knox 0.1.99: EDA backends (KiCad, Fusion, Altium behind one Eda trait) and the Altium lane eeecdb7
//! 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"]);
}