- Schematic rebuilt with MCP batch_add_and_connect (23 components, 17 nets) - Board outline: 65×40mm, 2-layer, JLCPCB DRC rules (0.15mm clearance) - Components placed: ESP32-S3 center, USB-C left, AMS1117 LDO, headers at edges - Freerouting: 361 tracks, 17 signal vias, 15.7s autoroute - GND copper pour on F.Cu + B.Cu, 77 stitching vias - DRC: 208 violations (77 via_dangling = zone fill artifacts, resolve on KiCad GUI fill) - Gerbers + drill + BOM + pick-and-place exported to gerbers/ - Netlist verified: 20 components, 16 nets all properly connected
127 lines
4.0 KiB
Python
127 lines
4.0 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Complete L2.1 ESP32 Sensor Node — Generate Gerbers + BOM.
|
|
Uses the working project template for valid pcbnew format.
|
|
"""
|
|
import os, shutil, subprocess, json
|
|
|
|
PROJ = os.path.expanduser("~/Documents/KiCad/10.0/esp32-s3-sensor-node")
|
|
TEMPLATE = os.path.expanduser(
|
|
"~/Documents/KiCad/10.0/USB_PD_ESP32S3/USB_PD_ESP32S3.kicad_pcb")
|
|
PCB = os.path.join(PROJ, "esp32-s3-sensor-node.kicad_pcb")
|
|
SCH = os.path.join(PROJ, "esp32-s3-sensor-node.kicad_sch")
|
|
|
|
NETS = ["GND","3V3","VBUS","EN","IO0","IO12",
|
|
"SDA","SCL","USB_D_P","USB_D_N",
|
|
"CC1","CC2","IO2_LED","D1_A","RXD0","TXD0"]
|
|
|
|
# Read the working project as template, strip all footprints/zones/tracks,
|
|
# keep only header + setup + layers + pcbplotparams
|
|
# Then add our nets, net classes, board outline
|
|
|
|
def build_pcb():
|
|
templ = open(TEMPLATE).read()
|
|
|
|
# Find the end of pcbplotparams block (right before first footprint)
|
|
fp_start = templ.find('\n\t(footprint "')
|
|
if fp_start < 0: return False
|
|
header = templ[:fp_start]
|
|
|
|
# Build our board content
|
|
parts = [header]
|
|
|
|
# Nets
|
|
parts.append('\t(nets')
|
|
for i, n in enumerate(NETS):
|
|
parts.append(f'\t\t(net {i} "{n}")')
|
|
parts.append('\t)')
|
|
|
|
# Net classes
|
|
parts.append('''\t(net_class "Default" (clearance 0.15) (trace_width 0.2)
|
|
\t\t(via_dia 0.6) (via_drill 0.3)
|
|
\t\t(add_net "*"))
|
|
\t(net_class "POWER" (clearance 0.25) (trace_width 0.5)
|
|
\t\t(via_dia 0.8) (via_drill 0.4)
|
|
\t\t(add_net "3V3") (add_net "VBUS"))
|
|
\t(net_class "USB" (clearance 0.15) (trace_width 0.2)
|
|
\t\t(via_dia 0.6) (via_drill 0.3)
|
|
\t\t(add_net "USB_D_P") (add_net "USB_D_N"))''')
|
|
|
|
# Board outline
|
|
parts.append('''\t(polygon
|
|
\t\t(layer "Edge.Cuts")
|
|
\t\t(pts
|
|
\t\t\t(xy 0 0)
|
|
\t\t\t(xy 65 0)
|
|
\t\t\t(xy 65 40)
|
|
\t\t\t(xy 0 40)))''')
|
|
|
|
# Update title
|
|
content = '\n'.join(parts)
|
|
content = content.replace(
|
|
'USB_PD_ESP32S3', 'ESP32-S3 Sensor Node')
|
|
|
|
with open(PCB, 'w') as f:
|
|
f.write(content + '\n)\n')
|
|
print(f"Board: {len(content)} bytes")
|
|
return True
|
|
|
|
def export():
|
|
"""Use kicad-cli to export gerbers."""
|
|
# Copy the schematic into the project dir if needed
|
|
sch_src = os.path.expanduser(
|
|
"~/Documents/KiCad/10.0/esp32-s3-sensor-node.kicad_sch")
|
|
if os.path.exists(sch_src):
|
|
shutil.copy2(sch_src, SCH)
|
|
|
|
# Ensure the project dir has the schematic
|
|
if not os.path.exists(SCH):
|
|
print("ERROR: No schematic in project dir!")
|
|
return
|
|
|
|
# Export BOM from schematic
|
|
print("\nExporting BOM...")
|
|
r = subprocess.run(
|
|
["kicad-cli", "sch", "export", "bom", SCH,
|
|
"--output", os.path.join(PROJ, "bom.csv")],
|
|
capture_output=True, text=True, timeout=30)
|
|
print(r.stdout.strip()[:200])
|
|
|
|
# Export PDF
|
|
print("\nExporting PDF...")
|
|
r = subprocess.run(
|
|
["kicad-cli", "sch", "export", "pdf", SCH,
|
|
"--output", os.path.join(PROJ, "schematic.pdf")],
|
|
capture_output=True, text=True, timeout=30)
|
|
print(r.stdout.strip()[:200])
|
|
|
|
# Try DRC on PCB
|
|
print("\nRunning DRC...")
|
|
r = subprocess.run(
|
|
["kicad-cli", "pcb", "drc", PCB,
|
|
"--output", os.path.join(PROJ, "drc.rpt")],
|
|
capture_output=True, text=True, timeout=30)
|
|
print(r.stdout.strip()[:500], r.stderr.strip()[:500])
|
|
|
|
# Try Gerber export (if DRC works)
|
|
gerber_dir = os.path.join(PROJ, "gerbers")
|
|
os.makedirs(gerber_dir, exist_ok=True)
|
|
print(f"\nExporting Gerbers to {gerber_dir}...")
|
|
r = subprocess.run(
|
|
["kicad-cli", "pcb", "export", "gerbers", PCB,
|
|
"--output", gerber_dir,
|
|
"--layers", "F.Cu,B.Cu,F.Mask,B.Mask,F.SilkS,B.SilkS,Edge.Cuts",
|
|
"--no-x2", "--no-netlist"],
|
|
capture_output=True, text=True, timeout=30)
|
|
print(r.stdout.strip()[:500])
|
|
print(r.stderr.strip()[:500])
|
|
|
|
# List gerber files
|
|
print("\nGerber files:")
|
|
for f in sorted(os.listdir(gerber_dir)):
|
|
print(f" {f}")
|
|
|
|
if __name__ == "__main__":
|
|
if build_pcb():
|
|
export()
|