Files
rpicarrierboard/tools/kicad-build
2026-06-29 16:31:02 -04:00

257 lines
8.6 KiB
Bash
Executable File

#!/usr/bin/env bash
# kicad-build — orchestrator for circuit_toolkit + kicad-cli.
#
# Usage:
# ./kicad-build <board-dir> [--erc] [--drc] [--gerber] [--bom] [--schematic] [--sim] [--all]
#
# Steps (all are optional, --all runs everything):
# 1. circuit_toolkit build (generates .kicad_sch + .kicad_pcb)
# 2. kicad-cli sch erc (electrical rules check)
# 3. kicad-cli pcb drc (design rules check)
# 4. kicad-cli pcb export gerber/drill/position
# 5. kicad-cli sch export bom/pdf/netlist
# 6. SPICE simulation (if sim/ present)
#
# Requires:
# - KiCad.app installed (for pcbnew)
# - kicad-cli on PATH (homebrew: brew install kicad)
# - ngspice on PATH (for --sim, brew install ngspice)
# - netlistsvg on PATH (for --schematic SVG, npm install -g netlistsvg)
# - circuit_toolkit installed into KiCad's Python (run install-kicad-toolkit.sh)
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
KICAD_PYTHON="$SCRIPT_DIR/kicad-python"
KICAD_CLI=$(command -v kicad-cli 2>/dev/null || echo "")
# circuit_toolkit paths
TOOLKIT_SRC="$PROJECT_ROOT/kicad-claude-toolkit/python/circuit_toolkit"
TOOLKIT_PY="$TOOLKIT_SRC/circuit_toolkit"
# Output directory (relative to board dir)
OUTPUT_DIR=""
usage() {
echo "Usage: $0 <board-dir> [--erc] [--drc] [--gerber] [--bom] [--schematic] [--sim] [--all]"
echo ""
echo " --erc Run ERC on generated schematic"
echo " --drc Run DRC on generated PCB"
echo " --gerber Export Gerber + drill + position files"
echo " --bom Generate BOM (flat + JLCPCB)"
echo " --schematic Generate schematic SVG via netlistsvg"
echo " --sim Run SPICE simulations (if sim/ present)"
echo " --all Run all steps (default)"
echo " --dry-run Print planned commands without executing"
exit 1
}
# Parse args
BOARD_DIR=""
DO_ERC=false
DO_DRC=false
DO_GERBER=false
DO_BOM=false
DO_SCHEMATIC=false
DO_SIM=false
DO_ALL=true
DRY_RUN=false
for arg in "$@"; do
case "$arg" in
--erc) DO_ERC=true; DO_ALL=false;;
--drc) DO_DRC=true; DO_ALL=false;;
--gerber) DO_GERBER=true; DO_ALL=false;;
--bom) DO_BOM=true; DO_ALL=false;;
--schematic) DO_SCHEMATIC=true; DO_ALL=false;;
--sim) DO_SIM=true; DO_ALL=false;;
--dry-run) DRY_RUN=true;;
-h|--help) usage;;
-*)
echo "Unknown option: $arg" >&2
usage;;
*)
if [ -z "$BOARD_DIR" ]; then
BOARD_DIR="$arg"
else
echo "Unexpected argument: $arg" >&2
usage
fi
;;
esac
done
if [ -z "$BOARD_DIR" ]; then
echo "ERROR: board directory required" >&2
usage
fi
BOARD_DIR="$(cd "$BOARD_DIR" && pwd)"
OUTPUT_DIR="$BOARD_DIR/output"
if $DO_ALL; then
DO_ERC=true; DO_DRC=true; DO_GERBER=true; DO_BOM=true; DO_SCHEMATIC=true
fi
# ── Pre-flight checks ──────────────────────────────────────────────────
check_kicad_python() {
if ! $DRY_RUN && ! bash "$KICAD_PYTHON" -c "import pcbnew" 2>/dev/null; then
echo "ERROR: pcbnew not available. Run KiCad.app at least once, then retry." >&2
exit 1
fi
}
check_kicad_cli() {
if [ -z "$KICAD_CLI" ]; then
echo "ERROR: kicad-cli not on PATH. Install via: brew install kicad" >&2
exit 1
fi
}
# ── Step 1: circuit_toolkit build ──────────────────────────────────────
step_build() {
local build_script="$BOARD_DIR/build.py"
if [ ! -f "$build_script" ]; then
echo "SKIP build: no build.py in $BOARD_DIR"
return 0
fi
echo "=== Step 1: circuit_toolkit build ==="
if $DRY_RUN; then
echo " [dry-run] KiCad Python $KICAD_PYTHON"
echo " [dry-run] cd $BOARD_DIR && $KICAD_PYTHON build.py"
return 0
fi
mkdir -p "$OUTPUT_DIR"
pushd "$BOARD_DIR" >/dev/null
PYTHONPATH="$TOOLKIT_SRC:$PYTHONPATH" \
bash "$KICAD_PYTHON" build.py
popd >/dev/null
echo " -> $OUTPUT_DIR/"
}
# ── Step 2: ERC ────────────────────────────────────────────────────────
step_erc() {
local sch="$BOARD_DIR/*.kicad_sch"
sch=$(ls $sch 2>/dev/null | head -1 || true)
if [ -z "$sch" ]; then
echo "SKIP ERC: no .kicad_sch found"
return 0
fi
echo "=== Step 2: ERC ==="
if $DRY_RUN; then
echo " [dry-run] $KICAD_CLI sch erc $sch"
return 0
fi
mkdir -p "$OUTPUT_DIR/erc"
$KICAD_CLI sch erc "$sch" --output "$OUTPUT_DIR/erc/erc_report.txt" 2>&1 || true
echo " -> $OUTPUT_DIR/erc/"
}
# ── Step 3: DRC ────────────────────────────────────────────────────────
step_drc() {
local pcb="$BOARD_DIR/*.kicad_pcb"
pcb=$(ls $pcb 2>/dev/null | head -1 || true)
if [ -z "$pcb" ]; then
echo "SKIP DRC: no .kicad_pcb found"
return 0
fi
echo "=== Step 3: DRC ==="
if $DRY_RUN; then
echo " [dry-run] $KICAD_CLI pcb drc $pcb"
return 0
fi
mkdir -p "$OUTPUT_DIR/drc"
$KICAD_CLI pcb drc "$pcb" --output "$OUTPUT_DIR/drc/drc_report.html" 2>&1 || true
echo " -> $OUTPUT_DIR/drc/"
}
# ── Step 4: Gerber/Drill/Position export ──────────────────────────────
step_gerber() {
local pcb="$BOARD_DIR/*.kicad_pcb"
pcb=$(ls $pcb 2>/dev/null | head -1 || true)
if [ -z "$pcb" ]; then
echo "SKIP gerber: no .kicad_pcb found"
return 0
fi
echo "=== Step 4: Gerber + Drill + Position export ==="
if $DRY_RUN; then
echo " [dry-run] $KICAD_CLI pcb export gerbers $pcb --output $OUTPUT_DIR/fab/gerber"
return 0
fi
mkdir -p "$OUTPUT_DIR/fab/gerber"
$KICAD_CLI pcb export gerbers "$pcb" --output "$OUTPUT_DIR/fab/gerber" 2>&1
$KICAD_CLI pcb export drill "$pcb" --output "$OUTPUT_DIR/fab/gerber" 2>&1
$KICAD_CLI pcb export pos "$pcb" --output "$OUTPUT_DIR/fab/positions.csv" 2>&1
echo " -> $OUTPUT_DIR/fab/"
}
# ── Step 5: BOM + schematic PDF + netlist ─────────────────────────────
step_bom() {
local sch="$BOARD_DIR/*.kicad_sch"
sch=$(ls $sch 2>/dev/null | head -1 || true)
if [ -z "$sch" ]; then
echo "SKIP bom: no .kicad_sch found"
return 0
fi
echo "=== Step 5: BOM + schematic PDF + netlist ==="
if $DRY_RUN; then
echo " [dry-run] $KICAD_CLI sch export bom $sch --output $OUTPUT_DIR/fab"
return 0
fi
mkdir -p "$OUTPUT_DIR/fab"
$KICAD_CLI sch export bom "$sch" --output "$OUTPUT_DIR/fab" 2>&1
$KICAD_CLI sch export pdf "$sch" --output "$OUTPUT_DIR/schematic.pdf" 2>&1
$KICAD_CLI sch export "$sch" --format netlist --output "$OUTPUT_DIR/fab/netlist.xml" 2>&1
echo " -> $OUTPUT_DIR/fab/"
}
# ── Step 6: SPICE simulation ──────────────────────────────────────────
step_sim() {
if [ ! -d "$BOARD_DIR/sim" ]; then
echo "SKIP sim: no sim/ directory"
return 0
fi
echo "=== Step 6: SPICE simulation ==="
if $DRY_RUN; then
echo " [dry-run] ngspice -b $BOARD_DIR/sim/*.cir"
return 0
fi
if ! command -v ngspice &>/dev/null; then
echo "SKIP sim: ngspice not on PATH (brew install ngspice)"
return 0
fi
mkdir -p "$OUTPUT_DIR/sim"
for cir in "$BOARD_DIR"/sim/*.cir; do
echo " sim: $(basename "$cir")"
ngspice -b -r "$OUTPUT_DIR/sim/$(basename "$cir" .cir).raw" "$cir" 2>&1 || true
done
echo " -> $OUTPUT_DIR/sim/"
}
# ── Run ────────────────────────────────────────────────────────────────
echo "kicad-build: $BOARD_DIR"
echo " KiCad Python: $KICAD_PYTHON"
echo " kicad-cli: $KICAD_CLI"
echo " output: $OUTPUT_DIR"
echo ""
check_kicad_python
if [ -n "$KICAD_CLI" ]; then
: # ok
else
echo "WARNING: kicad-cli not found (ERC/DRC/gerber/bom will be skipped)"
fi
step_build
$DO_ERC && step_erc
$DO_DRC && step_drc
$DO_GERBER && step_gerber
$DO_BOM && step_bom
$DO_SIM && step_sim
echo ""
echo "=== Done ==="
echo "Output: $OUTPUT_DIR/"
ls -la "$OUTPUT_DIR/" 2>/dev/null || true