Files
SN-L00/hardware/kicad/scripts/autoroute_full.py
T
florian.berthold 1ae49dc1bb Add 8HP layout with fully automated routing pipeline
- Update PCB to 8HP format (40x100mm) with v2 component placement
- Add automated routing scripts (autoroute.py runs full pipeline headlessly)
- Update panel spec and SVG for 8HP dimensions
- Board routes in <1 second with 0 unconnected pads

Scripts:
- autoroute.py: Full CLI pipeline (place → export → route → import → DRC)
- autoroute_full.py: Same pipeline for KiCad scripting console
- place_8hp.py: Component placement only
- route.sh/freeroute.sh: Routing helpers
2026-01-23 07:59:50 +01:00

162 lines
4.5 KiB
Python

#!/usr/bin/env python3
"""
SN-L00 Full Autoroute Pipeline for 8HP Eurorack
Run in KiCad PCB Editor: Tools → Scripting Console
Then: exec(open('scripts/autoroute_full.py').read())
This script:
1. Updates board outline to 8HP (40mm x 100mm)
2. Deletes existing tracks
3. Places all components
4. Exports DSN file
5. Runs Freerouting
6. Imports routed SES file
"""
import pcbnew
import subprocess
import os
import time
board = pcbnew.GetBoard()
board_path = board.GetFileName()
board_dir = os.path.dirname(board_path)
def mm(val):
return pcbnew.FromMM(val)
def place(x, y):
return pcbnew.VECTOR2I(mm(x), mm(y))
print("=" * 50)
print("SN-L00 Full Autoroute Pipeline")
print("=" * 50)
# Step 1: Update board outline to 8HP (40mm x 100mm)
print("\n[1/6] Updating board outline to 40mm x 100mm...")
for drawing in board.GetDrawings():
if drawing.GetClass() == "PCB_SHAPE":
if drawing.GetShape() == pcbnew.SHAPE_T_RECT:
drawing.SetStart(pcbnew.VECTOR2I(mm(0), mm(0)))
drawing.SetEnd(pcbnew.VECTOR2I(mm(40), mm(100)))
print(" Board outline updated")
break
# Step 2: Delete all tracks
print("\n[2/6] Deleting existing tracks...")
tracks = list(board.GetTracks())
for track in tracks:
board.Delete(track)
print(f" Deleted {len(tracks)} tracks")
# Step 3: Place components (v2 - fixed spacing)
print("\n[3/6] Placing components...")
placements = {
# Top - OLED display (rotated 90°)
"MOD3": (20, 15, 90),
# Audio jacks - centered with good spacing
"J3": (10, 35, 0), # RETURN_IN
"J4": (30, 35, 0), # TRIG_OUT
# Button + LED
"SW1": (20, 48, 0),
"D5": (32, 48, 0), # LED near button
# RP2040-Zero module - centered
"MOD2": (20, 62, 0),
# Signal conditioning ICs - well spaced
"U2": (8, 78, 0), # 74LVC1G17
"U4": (32, 78, 0), # MCP6001
# Decoupling caps near ICs
"C4": (4, 78, 90),
"C5": (14, 78, 90),
"C6": (28, 82, 90),
# Protection diodes near jacks circuits
"D3": (4, 82, 0),
"D4": (36, 78, 0),
# Resistors - spread across middle
"R2": (4, 86, 90),
"R3": (10, 86, 90),
"R4": (16, 86, 90),
"R5": (24, 86, 90),
"R6": (30, 86, 90),
"R7": (36, 86, 90),
# Power section - J2 moved up, components spread out
"J2": (20, 88, 0), # Eurorack power header (extends ~6mm down)
"D2": (4, 92, 0), # Protection diode
"U3": (32, 94, 180), # LDO - moved left from edge
"C2": (10, 94, 90), # Input cap
"C3": (26, 94, 90), # Output cap
}
placed = 0
for ref, (x, y, rot) in placements.items():
fp = board.FindFootprintByReference(ref)
if fp:
fp.SetPosition(place(x, y))
fp.SetOrientationDegrees(rot)
placed += 1
print(f" Placed {placed} components")
# Step 4: Save and export DSN
print("\n[4/6] Saving board and exporting DSN...")
pcbnew.Refresh()
board.Save(board_path)
dsn_path = os.path.join(board_dir, "SN-L00.dsn")
ses_path = os.path.join(board_dir, "SN-L00.ses")
# Export DSN
if hasattr(pcbnew, 'ExportSpecctraDSN'):
pcbnew.ExportSpecctraDSN(dsn_path)
print(f" Exported: {dsn_path}")
else:
# Fallback for older KiCad versions
exporter = pcbnew.SPECCTRA_DB()
exporter.ExportPCB(dsn_path, False)
print(f" Exported: {dsn_path}")
# Step 5: Run Freerouting
print("\n[5/6] Running Freerouting...")
freerouting_jar = "/tmp/freerouting.jar"
if not os.path.exists(freerouting_jar):
print(" Downloading Freerouting...")
url = "https://github.com/freerouting/freerouting/releases/download/v2.0.1/freerouting-2.0.1.jar"
subprocess.run(["curl", "-L", "-o", freerouting_jar, url], check=True)
result = subprocess.run(
["java", "-jar", freerouting_jar,
"-de", dsn_path,
"-do", ses_path,
"-mp", "200",
"-mt", "1",
"-oit"],
capture_output=True,
text=True,
cwd=board_dir
)
if result.returncode == 0:
print(" Freerouting completed successfully")
else:
print(f" Freerouting error: {result.stderr}")
# Step 6: Import SES
print("\n[6/6] Importing routed session...")
if os.path.exists(ses_path):
if hasattr(pcbnew, 'ImportSpecctraSES'):
pcbnew.ImportSpecctraSES(ses_path)
print(f" Imported: {ses_path}")
else:
# Fallback
importer = pcbnew.SPECCTRA_DB()
importer.ImportSES(ses_path)
print(f" Imported: {ses_path}")
pcbnew.Refresh()
print("\n" + "=" * 50)
print("DONE! Save (Ctrl+S) and run DRC to verify.")
print("=" * 50)
else:
print(f" ERROR: SES file not found: {ses_path}")