The router began as a port of FreeRouting and has grown its own engine (negotiated congestion, tree nets, exact checks, axis escapes), and 'freeroute' was one letter from the project it no longer follows. The package, module and command are now rpmroute; the freeroute command stays as an alias, and the RPMROUTE / RPMROUTE_ORACLE_JAVA variables fall back to the old FREEROUTE names. Version 2026.10.07. Routing is unchanged: a PCBench board routes to a byte-identical session either way.
68 lines
3.1 KiB
Bash
Executable File
68 lines
3.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Route one KiCad board with rpmroute and judge the result with KiCad's DRC.
|
|
#
|
|
# bench/drc_check.sh [--keep] <board.kicad_pcb> <out-dir> [rpmroute args...]
|
|
#
|
|
# Copies the board (and its .kicad_pro) into <out-dir>, strips its tracks and
|
|
# vias unless --keep is given (then they reach the router as fixed wiring),
|
|
# exports the DSN through mckicad, routes, applies the session, and runs DRC
|
|
# with zones refilled at every severity. Prints one line: time, copper added,
|
|
# shorts, opens, hole problems (whatever severity the board gives them) and
|
|
# the other errors. The source board is never touched.
|
|
#
|
|
# RPMROUTE (or the older FREEROUTE; default: this checkout's .venv/bin/rpmroute) and MCK (default
|
|
# ~/claude/mckicad) pick the builds to judge.
|
|
set -euo pipefail
|
|
keep=0
|
|
if [[ "${1:-}" == "--keep" ]]; then keep=1; shift; fi
|
|
src="$1"; out="$2"; shift 2
|
|
here="$(cd "$(dirname "$0")/.." && pwd)"
|
|
RPMROUTE="${RPMROUTE:-${FREEROUTE:-$here/.venv/bin/rpmroute}}"
|
|
MCK="${MCK:-$HOME/claude/mckicad}"
|
|
rm -rf "$out"; mkdir -p "$out"
|
|
cp "${src%.kicad_pcb}.kicad_pro" "$out/board.kicad_pro" 2>/dev/null || true
|
|
|
|
if [[ $keep == 1 ]]; then
|
|
cp "$src" "$out/board.kicad_pcb"
|
|
else
|
|
(cd "$MCK" && uv run --no-sync python - "$src" "$out/board.kicad_pcb" <<'EOF'
|
|
import sys
|
|
from mckicad.utils.sexp_tree import parse
|
|
text = open(sys.argv[1]).read()
|
|
spans = sorted((c.span for c in parse(text).children if c.tag in ("segment", "arc", "via")), reverse=True)
|
|
for a, b in spans:
|
|
text = text[:a] + text[b:]
|
|
open(sys.argv[2], "w").write(text)
|
|
EOF
|
|
)
|
|
fi
|
|
|
|
(cd "$MCK" && uv run --no-sync python -c "
|
|
from mckicad.utils.freerouting import FreeRoutingEngine
|
|
assert FreeRoutingEngine().export_dsn_from_kicad('$out/board.kicad_pcb', '$out/board.dsn', {})") \
|
|
>/dev/null 2>&1 || { echo "$out: DSN export failed"; exit 1; }
|
|
|
|
start=$(date +%s.%N)
|
|
"$RPMROUTE" "$out/board.dsn" -do "$out/board.ses" "$@" >"$out/route.log" 2>&1 || true
|
|
secs=$(python3 -c "print(round($(date +%s.%N)-$start,1))")
|
|
[[ -s "$out/board.ses" ]] || { echo "$out: no session ($(tail -1 "$out/route.log"))"; exit 1; }
|
|
|
|
(cd "$MCK" && uv run --no-sync python -c "
|
|
from mckicad.utils.ses_apply import apply_ses_to_board as a
|
|
r = a('$out/board.kicad_pcb', '$out/board.ses', '$out/board.kicad_pcb')
|
|
print(r['segments_added'], r['vias_added'])" 2>/dev/null) >"$out/apply.txt"
|
|
|
|
kicad-cli pcb drc --refill-zones --format json --severity-all \
|
|
-o "$out/drc.json" "$out/board.kicad_pcb" >/dev/null 2>&1 || true
|
|
python3 - "$out" "$secs" <<'EOF'
|
|
import collections, json, sys
|
|
out, secs = sys.argv[1:]
|
|
seg, via = open(f"{out}/apply.txt").read().split()
|
|
d = json.load(open(f"{out}/drc.json"))
|
|
errors = collections.Counter(v["type"] for v in d["violations"] if v.get("severity") == "error")
|
|
other = collections.Counter(v["type"] for v in d["violations"] if v.get("severity") != "error")
|
|
holes = {k: errors.pop(k, 0) + other.get(k, 0) for k in ("holes_co_located", "hole_to_hole")}
|
|
print(f"{out}: {secs}s {seg} seg {via} via shorts {errors.pop('shorting_items', 0)} "
|
|
f"opens {len(d.get('unconnected_items', []))} holes {holes} errors {dict(errors)}")
|
|
EOF
|