diff --git a/bench/drc_check.sh b/bench/drc_check.sh new file mode 100755 index 0000000..db2e9ec --- /dev/null +++ b/bench/drc_check.sh @@ -0,0 +1,67 @@ +#!/usr/bin/env bash +# Route one KiCad board with freeroute and judge the result with KiCad's DRC. +# +# bench/drc_check.sh [--keep] [freeroute args...] +# +# Copies the board (and its .kicad_pro) into , 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. +# +# FREEROUTE (default: this checkout's .venv/bin/freeroute) 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)" +FREEROUTE="${FREEROUTE:-$here/.venv/bin/freeroute}" +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) +"$FREEROUTE" "$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