- Rust 95.8%
- Python 3.4%
- HTML 0.4%
- Shell 0.3%
- Dockerfile 0.1%
Router stamped keepouts against cell centres only, letting diagonal moves cut corners through foreign-pad keepouts; cells are now blocked when the keepout reaches anywhere into the cell square, and foreign copper is stamped with per-net-pair class_clearance. DRC track/via-pad checks use the exact rotated-rect pad shape instead of a max-half-extent circle, removing false positives near wide pads' short edges. One-Air-Max: autoroute-introduced DRC violations 49 -> 0 (baseline 813 -> 501). Corpus report regenerated: all 20 boards, exit 0, deterministic, referee ok; new-DRC violations 0 on every routable board except RoyalBlue (1). |
||
|---|---|---|
| .forgejo/workflows | ||
| benchmarks | ||
| crates | ||
| docs | ||
| fixtures | ||
| scripts | ||
| .dockerignore | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| CHANGELOG.md | ||
| Dockerfile | ||
| LICENSE | ||
| README.md | ||
| rust-toolchain | ||
NodeBound
A headless, AI-native EDA engine in the spirit of Flux.ai — the hard part (constraint engine, routing, DRC, format import/export) without any UI. Everything runs fully headless (CLI/CI) and is tested with golden files and property tests. The reactive engine is IR-first: importers/exporters are IR↔format codecs, which makes multi-format output cheap.
Phase 2 (current)
- Board IR in
ir: footprints (pads with shape/size/layers/net), tracks, vias, zones, stackup (copper layers, thickness), design rules (clearance, min track width, min via annular ring). - DRC engine in
engine, run headless over the board IR:nodebound drc board.kicad_pcbprints a JSON report (violation type, location, nets) and exits nonzero on violations;--checkis silent (exit 0/1) for CI. Checks: pad–pad / track–track / track–pad / via clearance (different nets), unconnected nets, track width below minimum, via annular ring below minimum, silkscreen over pads. - KiCad importer upgrades: footprints/pads/tracks/vias/zones/stackup parse into the board IR; the P1 S-expression round-trip is unchanged and still byte-stable.
- SPICE netlist export:
nodebound export-netlist board.kicad_pcb --out circuit.ciremits a ngspice-runnable netlist from board connectivity (each two-terminal footprint stubbed as a resistor, ground-like nets map to node0); CI runsngspice -bon it. - IPC-2581 export (
altiumcrate): the open-standard XML fallback for Altium interop —nodebound export <in> --out out.xml --format ipc2581. Full.PcbDocbinary writing stays out of scope. - Phase 1 capabilities remain: KiCad S-expression round-trip (parse → IR → write, byte-identical from the second write on), CLI import/export.
Build & test
Requires Rust (stable; see rust-toolchain).
cargo build
cargo test --all
cargo clippy --all -- -D warnings
cargo fmt --check
CLI:
cargo run -p nodebound-cli -- import fixtures/golden.kicad_pcb
cargo run -p nodebound-cli -- export fixtures/golden.kicad_pcb --out out.kicad_pcb --format kicad
cargo run -p nodebound-cli -- export fixtures/golden.kicad_pcb --out out.xml --format ipc2581
cargo run -p nodebound-cli -- drc fixtures/golden.kicad_pcb # JSON report
cargo run -p nodebound-cli -- drc fixtures/bad_board.kicad_pcb # nonzero exit on violations
cargo run -p nodebound-cli -- drc --check fixtures/golden.kicad_pcb # silent CI mode (0/1)
cargo run -p nodebound-cli -- export-netlist fixtures/golden.kicad_pcb --out circuit.cir
ngspice -b circuit.cir
# Controlled impedance -> KiCad net classes (project file):
# 50 ohm on the default FR-4 stackup stamps Power's track width;
# 90 ohm differential stamps USB's diff-pair width + gap.
cargo run -p nodebound-cli -- export-project fixtures/golden.kicad_pcb \
--out out.kicad_pro --set Power=50 --set-diff USB=90
python3 scripts/referee/proj_load_check.py out.kicad_pcb ... # pcbnew referee (needs an adjacent board)
# Web client (read-only by default): http://127.0.0.1:2581
cargo run -p nodebound-server --bin nodebound-web-server
# POST /rpc speaks the same JSON-RPC 2.0 envelope as the MCP server.
# Container release artifact (headless, no Rust toolchain needed):
docker build -t nodebound .
docker run --rm -v "$PWD:/data" nodebound drc /data/fixtures/golden.kicad_pcb
scripts/container_smoke.sh # full command surface in one piped script
Layout
| Crate | Purpose |
|---|---|
crates/ir |
S-expression tree + semantic netlist/board IR |
crates/kicad |
KiCad 7/8 S-expression parser, writer, board extraction |
crates/engine |
Board loading, DRC engine, SPICE netlist export |
crates/server |
Headless JSON-RPC server skeleton |
crates/cli |
The nodebound binary |
crates/altium |
IPC-2581 (XML) exporter — open Altium fallback |
fixtures/ |
Hand-written golden KiCad fixtures |
The canonical formatting of the golden fixture is checked against
fixtures/golden_canonical.kicad_pcb; regenerate with:
cargo run -p nodebound-kicad --example gen_canonical -- fixtures/golden.kicad_pcb \
> fixtures/golden_canonical.kicad_pcb
Testing & open-source tooling policy
Headless, open-source-first: we compose mature open-source tools as referees in CI instead of reimplementing their logic.
- KiCad
pcbnew/kicad-cli— independent referee:kicad_load_check.pyloads our exports with KiCad's own parser, andkicad-cli pcb drcruns KiCad's DRC on our exported golden board in the CI referee job. - ngspice — circuit simulation; netlists are extracted from the IR (
nodebound export-netlist) and run in batch mode (ngspice -b) in CI. - pcb-rnd / py-altium — read-only references and cross-checks for format work.
- LVS-style electrical checks compare the netlist extracted from layout against the schematic netlist.
Everything runs headless (no GUI) and deterministically (seeded RNG only).
Roadmap
See flux-clone-plan.md (P2 board semantics + DRC, P3 autorouter, P4
assistant layer, P5 UI).
License
MIT — see LICENSE.