The idea
Most of my boards are variations on one theme: a microcontroller, a cellular modem, relays, mains-sense inputs, a power tree. Drawing the fifth variation by hand is not engineering, it is typing. So on 8 July 2026 I tried the other way round: describe the board in Python and let the script produce the KiCad project. tools/kigen.py and its companions are about 1 500 lines.
What the generator does
- Parses KiCad s-expressions. A small from-scratch parser reads
.kicad_symlibraries (including easyeda2kicad conversions of LCSC parts), so the generator can embed the exact symbols the board needs. - Places symbol instances on a 2.54 mm grid and computes rotated pin endpoints, so wires and labels land on pins for any rotation.
- Wires every pin with global labels. No drawn wires; each net is a label at each pin. It is not pretty, but it is unambiguous, ERC-clean and trivially diffable.
- Generates the board through the pcbnew Python API: footprint placement by functional group, board outline, copper zones, and net classes.
- Routes and exports with freerouting (via a Specctra DSN) and
kicad-cli, then a post-route step re-applies zones and rules.
The chain is one command: gen_sch → ERC → netlist → gen_pcb → DSN → freerouting → post_route → export_all. Change a part in the generator, rerun, get a new project.
The board it produced
A 220 VAC lighting controller on a two-layer 175 × 115 mm board:
- four HF32FV-16 16 A relay outputs with S8050 drivers, flyback diodes, status LEDs and pluggable 5.08 mm terminals carrying switched L and N per channel;
- eight 220 V presence-sense inputs through LTV-817S optocouplers — 3 × 100 kΩ series, 1N4148 anti-parallel, and a 47 kΩ / 1 µF RC filter to bridge the half-cycle gaps;
- power tree: HLK-10M05 AC-DC behind a 5 × 20 T2A fuse and a 10D471K MOV → TLV62585 3 A buck for the 3.8 V modem rail with polymer bulk capacitors → AMS1117 for 3.3 V;
- ESP32-S3 with native USB-C (GCT USB4145 connector, USBLC6 ESD), BOOT/RESET buttons, Wi-Fi/BLE as secondary connectivity;
- SIMCom A7682E LTE Cat-1 with micro-SIM, ESD arrays, an SMA edge-mount antenna on a ~50 Ω coplanar trace and SN74LVC1T45 level shifting for the 1.8 V UART;
- DS3231MZ TCXO RTC with CR2032 backup and an alarm interrupt to a GPIO.
Mains and logic areas are physically separated with dedicated net classes: MAINS with 2.5 mm / 1.5 mm clearance, MAINS_SENSE, POWER and RF. Every part is stockable at LCSC/JLCPCB on purpose.
Honest status
The generated schematic passes ERC; the board is placed with zones and rules. It is not routed yet — freerouting is in the chain but the run has not been finished — so this is a generator demo and a design study, not a fabricated board. The fabricated boards of the same family are the FOURLEDS controllers, drawn by hand in KiCad.
Why bother
Because the second board takes an afternoon instead of a week, because every design decision is in version control as code, and because a reviewer can read gen_sch.py faster than they can click through forty sheets. The parser and the placement helpers are reusable; the board description is the only thing that changes.
What I would do differently
Generate real wires for the short local nets (decoupling, LED resistors) and keep global labels only for buses and power — the all-labels schematic is correct but hard to read for anyone else. And run freerouting to completion before calling it a day.