skill
EDA Bridge Standard: one way for an AI to drive every PCB tool
Public Made by Adomby adom
The standard every Adom EDA bridge follows so one AI can drive Altium, KiCad, Fusion 360 and any future PCB tool the same way: shared verb names in tiers, the board model JSON, reply and refusal conventions, and a coverage table of each bridge against the Altium reference.
main
Colby Knox
standard 0.15.2: engine 0.12.2, assembly_check one-land pads and pad-less parts (#24), plan_route withoutAvoid (#21)
275a6ef
12d ago
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273
//! The engine against a bridge's own answers on the same boards. Fixtures are not kept with the standard: each bridge's
//! page carries its own (Altium's: lane and the Bluetooth Sentinel, from altium-lane, bridge 0.2.76). Point
//! ADOM_EDA_FIXTURES at a folder holding `altium/<board>.<model>.json` and `altium/expected/<board>.<check>.json`;
//! without it these tests are skipped. The result keys must match exactly; hints and notes may differ, since the engine
//! names verbs without a prefix.
use serde_json::{json, Value};
use std::path::PathBuf;
fn dir() -> Option<PathBuf> {
let d = PathBuf::from(std::env::var_os("ADOM_EDA_FIXTURES")?).join("altium");
d.is_dir().then_some(d)
}
fn load(p: PathBuf) -> Option<Value> { Some(serde_json::from_str(&std::fs::read_to_string(p).ok()?).unwrap()) }
fn bundle(d: &PathBuf, board: &str) -> Option<Value> {
Some(json!({"board": load(d.join(format!("{board}.routing_state.json")))?, "stack": load(d.join(format!("{board}.layer_stack.json")))?,
"pours": load(d.join(format!("{board}.pour_state.json")))?, "rules": load(d.join(format!("{board}.read_rules.json"))),
"silk": load(d.join(format!("{board}.silk_state.json"))), "bodies": load(d.join(format!("{board}.bodies.json"))), "args": {}}))
}
fn same(check: &str, keys: &[&str]) {
let Some(d) = dir() else { eprintln!("skipped: set ADOM_EDA_FIXTURES to a fixtures folder to compare {check} with a bridge"); return };
let mut ran = 0;
for board in ["lane", "sentinel"] {
let (Some(input), Some(want)) = (bundle(&d, board), load(d.join(format!("expected/{board}.{check}.json")))) else { continue };
let got = adom_eda_checks::run(check, &input);
assert_eq!(got["success"], json!(true), "{board} {check}: {got}");
for k in keys {
let ptr = format!("/{}", k.replace('.', "/"));
let optional = k.starts_with("measured.") || (k.starts_with("fix.") && want["fix"].is_null());
assert!(want.pointer(&ptr).is_some() || optional, "{board} {check}: the Altium reply has no {k}");
assert_eq!(got.pointer(&ptr), want.pointer(&ptr), "{board} {check}: {k} differs from the Altium bridge");
}
ran += 1;
}
assert!(ran > 0, "no fixtures for {check} in {}", d.display());
}
#[test]
fn copper_balance_matches_altium() {
same("copper_balance", &["boardMm2", "resolutionMm", "layers", "pairs", "balanced", "poursWithoutRegions"]);
}
#[test]
fn return_path_matches_altium() {
same("return_path", &["referenceNets", "layersWithNoReferenceCopper", "nets", "ok", "resolutionMm",
"fix.args", "fix.patches", "fix.closes", "fix.floating"]);
}
#[test]
fn fab_check_measures_what_altium_measures() {
same("fab_check", &["measured.boardSize", "measured.clearanceRule", "measured.copperLayers", "measured.counts", "measured.edgeClearance", "measured.holeToHolePad",
"measured.holeToHoleVia", "measured.largestDrill", "measured.padHole", "measured.smdPad", "measured.track",
"measured.viaAnnularRing", "measured.viaDiameter", "measured.viaDrill", "outsideLimits", "limitsChecked"]);
}
#[test]
fn height_check_matches_altium() {
same("height_check", &["ok", "complete", "bodies", "parts", "tallest", "overLimit", "regionHits", "collisions", "tooClose", "minGapMm", "noBody", "heights"]);
}
#[test]
fn placement_check_matches_altium() {
same("placement_check", &["ok", "parts", "courtyardOverlaps", "offOrOverEdge", "onMountingHoles", "nearEdge", "marginMm", "edgeMm"]);
}
#[test]
fn assembly_check_matches_altium() {
// board.parts: the engine counts only components with pads (a logo is no part, fusion-lane #24), Altium all of them
same("assembly_check", &["service", "fab", "board.bottomSideParts", "board.finestPitch", "board.smallestChip", "board.tinyFootprints", "board.fiducials", "board.throughHoleParts", "economic.ok", "standard.ok", "economic.rules.0.ok", "economic.rules.1.ok", "economic.rules.2.ok",
"economic.rules.3.ok", "economic.rules.4.ok", "economic.rules.5.ok", "standard.rules.0.ok", "standard.rules.3.ok"]);
}