#!/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()