main
John Lauer Publish 0.1.94 bca797f
//! The route plan JSON shape `gate --plan` and `land route --plan` accept (docs/plan-schema.md).
use aiflow_board::Board;
use aiflow_copper::{apply, normalize_plan, plan_entries, plan_to_copper};
use serde_json::json;

fn board() -> Board {
    Board::from_text("plan.kicad_pcb", r#"(kicad_pcb
      (layers (0 "F.Cu" signal) (31 "B.Cu" signal))
      (net 1 "SIG")
      (gr_rect (start 0 0) (end 20 20) (layer "Edge.Cuts"))
      (footprint "smd" (layer "F.Cu") (at 5 5) (property "Reference" "U1")
        (pad "1" smd rect (at 0 0) (size 1 1) (layers "F.Cu") (net 1 "SIG")))
      (footprint "tht" (layer "F.Cu") (at 15 5) (property "Reference" "J1")
        (pad "1" thru_hole circle (at 0 0) (size 1.6 1.6) (drill 0.8) (layers "*.Cu" "*.Mask") (net 1 "SIG"))))"#.into()).unwrap()
}

#[test]
fn every_point_form_and_layer_marker() {
    let b = board();
    // pad key, [x, y], {x, y, layer} (a via where the layer changes), {pad, layer}
    let e = json!({"net": "SIG", "width": 0.3, "viaSize": 0.7, "viaDrill": 0.35,
        "paths": [["U1.1", [10.0, 5.0], {"x": 10.0, "y": 8.0, "layer": "B.Cu"}, {"pad": "J1.1", "layer": "B.Cu"}]]});
    let (segs, vias) = plan_to_copper(&b, &e).unwrap();
    assert_eq!(segs.len(), 3);
    assert_eq!((segs[0].layer.as_str(), segs[1].layer.as_str(), segs[2].layer.as_str()), ("F.Cu", "F.Cu", "B.Cu"));
    assert!(segs.iter().all(|s| s.width == 0.3));
    assert_eq!(vias.len(), 1);
    assert_eq!((vias[0].x, vias[0].y, vias[0].size, vias[0].drill), (10.0, 8.0, 0.7, 0.35));
    // defaults: width 0.25, via 0.6 / 0.3; one path may be given as "points"
    let (segs, _) = plan_to_copper(&b, &json!({"net": "SIG", "points": ["U1.1", [8.0, 5.0]]})).unwrap();
    assert_eq!(segs[0].width, 0.25);
    // a via ending a path: copper on the arrival layer only, the other side meets a pour
    let (segs, vias) = plan_to_copper(&b, &json!({"net": "SIG", "paths": [["U1.1", [7.0, 5.0], {"x": 7.0, "y": 5.0, "layer": "B.Cu"}]]})).unwrap();
    assert_eq!((segs.len(), vias.len()), (1, 1));
    assert_eq!(segs[0].layer, "F.Cu");
    // an SMD pad reached on the wrong layer is refused
    assert!(plan_to_copper(&b, &json!({"net": "SIG", "paths": [[{"pad": "U1.1", "layer": "B.Cu"}, [8.0, 5.0]]]})).unwrap_err().contains("no copper on B.Cu"));
}

#[test]
fn object_and_bare_array_plans_are_the_same() {
    let b = board();
    let entry = json!({"net": "SIG", "paths": [["U1.1", "J1.1"]]});
    let (_, a) = apply(&b, &json!({"nets": [entry.clone()]}), false).unwrap();
    let (_, bare) = apply(&b, &json!([entry.clone()]), false).unwrap();
    assert_eq!(a, bare);
    assert_eq!(normalize_plan(json!([entry.clone()])), json!({"nets": [entry]}));
    assert_eq!(normalize_plan(json!({"nets": [], "note": "x"}))["note"], "x");
    let e = plan_entries(&json!({"traces": []})).unwrap_err();
    assert!(e.starts_with("plan needs nets") && e.contains("bare [entry") && e.contains("{\"pad\"") && e.contains("plan-schema.md"), "{e}");
    assert!(apply(&b, &json!({"nets": [{"paths": [["U1.1", "J1.1"]]}]}), false).unwrap_err().contains("entry needs net"));
}