Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
#!/usr/bin/env python3
"""Reproduce the worked example end-to-end — one command, fully offline.
Runs the whole chain for `projects/example-usb-c-3v3` and writes the committed phase reports:
regenerate board -> netlist structure -> ERC -> import-check (board == netlist)
-> KiCad DRC (kicad-cli) -> export gerbers -> BOM
Exits 0 only if every checkable step passes. DRC + gerbers need `kicad-cli` on PATH (a stated
requirement from phase 10 on); without it those two steps are reported SKIPPED and the offline
checks (netlist/ERC/import) still run — so a reviewer without KiCad still verifies most of the
chain, and a reviewer with KiCad gets a DRC-clean board + gerbers.
Usage:
python3 tools/reproduce_example.py # regenerate + verify + write reports + gerbers
python3 tools/reproduce_example.py --check # verify the committed board only (no regen)
Windows-safe: pure Python, no shell, uses the current interpreter. Pure stdlib + pcbflow.
"""
import argparse
import shutil
import subprocess
import sys
from pathlib import Path
REPO = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(REPO))
from pcbflow import enet as enet_mod, erc, findings, import_diff # noqa: E402
EX = REPO / "projects" / "example-usb-c-3v3"
NETLIST = EX / "04_schematic" / "netlist.enet"
BOARD = EX / "10_kicad_export" / "board.kicad_pcb"
GEN = REPO / "tools" / "gen_example_board.py"
V_DIR = EX / "12_verification"
GERBERS = V_DIR / "gerbers"
def _run(*args, **kw):
return subprocess.run([str(a) for a in args], capture_output=True, text=True, **kw)
def _write(path, text):
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(text, encoding="utf-8")
def step_regen(check_only):
if check_only or not GEN.exists():
return "SKIP", "using the committed board (no regeneration)"
r = _run(sys.executable, GEN)
return ("PASS", "board regenerated from tools/gen_example_board.py") if r.returncode == 0 \
else ("FAIL", (r.stderr or r.stdout)[:300])
def step_structure():
net = enet_mod.Enet.load(NETLIST)
issues = net.verify()
return ("PASS" if not issues else "FAIL",
"netlist structurally clean" if not issues else f"{len(issues)} issue(s): {issues}")
def step_erc():
net = enet_mod.Enet.load(NETLIST)
fs = erc.run_erc(net)
rep = findings.report(fs)
_write(EX / "05_schematic_audit" / "audit.report.md",
findings.to_markdown(fs, "Phase 5 — Schematic audit (ERC)") + "\n\n"
"_Generated by `tools/reproduce_example.py` from `04_schematic/netlist.enet`._\n")
return ("PASS" if rep["pass"] else "FAIL",
f"{rep['errors']} error(s), {rep['warnings']} warning(s)")
def step_import_check():
if not BOARD.exists():
return "SKIP", f"no board yet at {BOARD.relative_to(REPO)}"
fs, rep = import_diff.check(str(NETLIST), str(BOARD))
_write(EX / "10_kicad_export" / "import_diff.report.md",
findings.to_markdown(fs, "Phase 10 — EasyEDA→KiCad import diff") + "\n\n"
"_The board netlist read back from `board.kicad_pcb` vs the schematic `.enet`._\n")
return ("PASS" if rep["pass"] else "FAIL",
"board matches the netlist" if rep["pass"] else f"{rep['errors']} mismatch(es)")
def step_drc():
if not shutil.which("kicad-cli"):
return "SKIP", "kicad-cli not on PATH (install KiCad to produce the DRC report)"
if not BOARD.exists():
return "SKIP", "no board to check"
rpt = V_DIR / "drc.rpt"
rpt.parent.mkdir(parents=True, exist_ok=True)
r = _run("kicad-cli", "pcb", "drc", "--severity-error", "--exit-code-violations",
"--output", rpt, BOARD)
counts = ""
try:
import re
txt = rpt.read_text(encoding="utf-8")
v = re.search(r"Found (\d+) DRC violations", txt)
u = re.search(r"Found (\d+) unconnected", txt)
if v and u:
counts = f"{v.group(1)} violations / {u.group(1)} unconnected"
except OSError:
pass
return ("PASS" if r.returncode == 0 else "FAIL",
(counts or f"DRC report → {rpt.name}")
+ ("" if r.returncode == 0 else f" (exit {r.returncode})"))
def step_gerbers():
if not shutil.which("kicad-cli") or not BOARD.exists():
return "SKIP", "needs kicad-cli + a board"
GERBERS.mkdir(parents=True, exist_ok=True)
a = _run("kicad-cli", "pcb", "export", "gerbers", "--output", GERBERS, BOARD)
b = _run("kicad-cli", "pcb", "export", "drill", "--output", GERBERS, BOARD)
n = len(list(GERBERS.glob("*")))
return ("PASS" if a.returncode == 0 and b.returncode == 0 and n else "FAIL",
f"{n} fab file(s) → {GERBERS.relative_to(REPO)}")
def step_bom():
net = enet_mod.Enet.load(NETLIST)
rows = net.bom()
lines = ["ref,qty,value,device,footprint,mfr_part,lcsc"]
for r in rows:
lines.append(",".join([" ".join(r["refs"]), str(r["qty"]), r.get("value", ""),
r.get("device", ""), r.get("footprint", ""),
r.get("mfr_part", "") or "", r.get("lcsc", "") or ""]))
_write(V_DIR / "bom.csv", "\n".join(lines) + "\n")
return "PASS", f"{len(rows)} BOM line(s) → {(V_DIR / 'bom.csv').relative_to(REPO)}"
STEPS = [("regenerate board", step_regen), ("netlist structure", step_structure),
("ERC", step_erc), ("import-check", step_import_check), ("DRC", step_drc),
("gerbers", step_gerbers), ("BOM", step_bom)]
def main(argv=None):
ap = argparse.ArgumentParser(description="reproduce the worked example end-to-end")
ap.add_argument("--check", action="store_true", help="verify the committed board (no regen)")
a = ap.parse_args(argv)
print(f"== reproduce {EX.relative_to(REPO)} ==")
rows, failed = [], False
for name, fn in STEPS:
status, detail = fn(a.check) if fn is step_regen else fn()
mark = {"PASS": "OK ", "FAIL": "FAIL", "SKIP": "skip"}[status]
print(f" [{mark}] {name:<18} {detail}")
rows.append((name, status, detail))
if status == "FAIL":
failed = True
verdict = "FAIL" if failed else "PASS"
print(" ------------------------------------------------------------")
print(f" VERDICT: {verdict}")
md = ["# Phase 12 — Verification report", "",
f"**VERDICT: {verdict}** — produced by `tools/reproduce_example.py`.", "",
"| step | status | detail |", "|---|---|---|"]
md += [f"| {n} | {s} | {d} |" for n, s, d in rows]
md += ["", "Ground truth: KiCad DRC is `kicad-cli` against the board's own ruleset; the "
"board is proven to match the schematic by `pcbflow import-check`. Placement and "
"routing for this reference are generated by `tools/gen_example_board.py` (the "
"EasyEDA capture of phases 3–9 is the live front-end step — see the project README).\n"]
_write(V_DIR / "verification.report.md", "\n".join(md))
return 1 if failed else 0
if __name__ == "__main__":
sys.exit(main())
7315b96d0This file is pinned to the selected release. Download the original to open it in your design tool.