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
This commit is contained in:
Executable
+214
@@ -0,0 +1,214 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
SN-L00 Fully Automated Routing Pipeline
|
||||
Works headlessly on KiCad 9+
|
||||
|
||||
Usage: python3 scripts/autoroute.py
|
||||
|
||||
Pipeline:
|
||||
1. Load board
|
||||
2. Update outline to 8HP (40x100mm)
|
||||
3. Delete existing tracks
|
||||
4. Place components (v2 layout)
|
||||
5. Export Specctra DSN
|
||||
6. Run Freerouting autorouter
|
||||
7. Import Specctra SES
|
||||
8. Save board
|
||||
9. Run DRC
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
|
||||
# Paths
|
||||
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
KICAD_DIR = os.path.dirname(SCRIPT_DIR)
|
||||
os.chdir(KICAD_DIR)
|
||||
|
||||
PCB_FILE = "SN-L00.kicad_pcb"
|
||||
DSN_FILE = "SN-L00.dsn"
|
||||
SES_FILE = "SN-L00.ses"
|
||||
DRC_FILE = "DRC.rpt"
|
||||
FREEROUTING_JAR = "/tmp/freerouting.jar"
|
||||
FREEROUTING_URL = "https://github.com/freerouting/freerouting/releases/download/v2.0.1/freerouting-2.0.1.jar"
|
||||
|
||||
# Component positions for 8HP (40mm x 100mm) layout v2
|
||||
PLACEMENTS = {
|
||||
# Top - OLED display
|
||||
"MOD3": (20, 15, 90),
|
||||
# Audio jacks
|
||||
"J3": (10, 35, 0), # RETURN_IN
|
||||
"J4": (30, 35, 0), # TRIG_OUT
|
||||
# Button + LED
|
||||
"SW1": (20, 48, 0),
|
||||
"D5": (32, 48, 0),
|
||||
# RP2040-Zero
|
||||
"MOD2": (20, 62, 0),
|
||||
# Signal conditioning
|
||||
"U2": (8, 78, 0), # 74LVC1G17
|
||||
"U4": (32, 78, 0), # MCP6001
|
||||
# Decoupling caps
|
||||
"C4": (4, 78, 90),
|
||||
"C5": (14, 78, 90),
|
||||
"C6": (28, 82, 90),
|
||||
# Protection diodes
|
||||
"D3": (4, 82, 0),
|
||||
"D4": (36, 78, 0),
|
||||
# Resistors
|
||||
"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": (20, 88, 0), # Eurorack power
|
||||
"D2": (4, 92, 0),
|
||||
"U3": (32, 94, 180), # LDO
|
||||
"C2": (10, 94, 90),
|
||||
"C3": (26, 94, 90),
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
print("=" * 50)
|
||||
print("SN-L00 Automated Routing Pipeline")
|
||||
print("=" * 50)
|
||||
|
||||
# Import pcbnew
|
||||
try:
|
||||
import pcbnew
|
||||
print(f"KiCad version: {pcbnew.Version()}")
|
||||
except ImportError:
|
||||
print("ERROR: pcbnew not available. Install KiCad or set PYTHONPATH.")
|
||||
sys.exit(1)
|
||||
|
||||
# Helper functions
|
||||
def mm(val):
|
||||
return pcbnew.FromMM(val)
|
||||
|
||||
def place(x, y):
|
||||
return pcbnew.VECTOR2I(mm(x), mm(y))
|
||||
|
||||
# Step 1: Load board
|
||||
print("\n[1/9] Loading board...")
|
||||
board = pcbnew.LoadBoard(PCB_FILE)
|
||||
print(f" Loaded {len(list(board.GetFootprints()))} footprints")
|
||||
|
||||
# Step 2: Update outline
|
||||
print("\n[2/9] Updating board outline to 40x100mm...")
|
||||
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(" Done")
|
||||
break
|
||||
|
||||
# Step 3: Delete tracks
|
||||
print("\n[3/9] Deleting existing tracks...")
|
||||
tracks = list(board.GetTracks())
|
||||
for track in tracks:
|
||||
board.Delete(track)
|
||||
print(f" Deleted {len(tracks)} tracks")
|
||||
|
||||
# Step 4: Place components
|
||||
print("\n[4/9] Placing components...")
|
||||
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 5: Export DSN
|
||||
print("\n[5/9] Exporting Specctra DSN...")
|
||||
board.Save(PCB_FILE) # Save first
|
||||
result = pcbnew.ExportSpecctraDSN(board, DSN_FILE)
|
||||
if result and os.path.exists(DSN_FILE):
|
||||
print(f" Created {DSN_FILE} ({os.path.getsize(DSN_FILE)} bytes)")
|
||||
else:
|
||||
print(" ERROR: DSN export failed")
|
||||
sys.exit(1)
|
||||
|
||||
# Step 6: Download and run Freerouting
|
||||
print("\n[6/9] Running Freerouting...")
|
||||
if not os.path.exists(FREEROUTING_JAR):
|
||||
print(" Downloading Freerouting...")
|
||||
subprocess.run(["curl", "-sL", "-o", FREEROUTING_JAR, FREEROUTING_URL], check=True)
|
||||
|
||||
result = subprocess.run(
|
||||
["java", "-jar", FREEROUTING_JAR,
|
||||
"-de", DSN_FILE,
|
||||
"-do", SES_FILE,
|
||||
"-mp", "200",
|
||||
"-mt", "1",
|
||||
"-oit"],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
|
||||
# Parse output
|
||||
for line in (result.stdout + result.stderr).split('\n'):
|
||||
if 'auto-rout' in line.lower() or 'completed' in line.lower():
|
||||
print(f" {line.split(']')[-1].strip()}")
|
||||
|
||||
if not os.path.exists(SES_FILE):
|
||||
print(" ERROR: Freerouting failed")
|
||||
sys.exit(1)
|
||||
|
||||
# Step 7: Import SES
|
||||
print("\n[7/9] Importing routed session...")
|
||||
# Reload board to get fresh state
|
||||
board = pcbnew.LoadBoard(PCB_FILE)
|
||||
result = pcbnew.ImportSpecctraSES(board, SES_FILE)
|
||||
if result:
|
||||
print(f" Imported {len(list(board.GetTracks()))} tracks")
|
||||
else:
|
||||
print(" WARNING: SES import returned False")
|
||||
|
||||
# Step 8: Save
|
||||
print("\n[8/9] Saving board...")
|
||||
board.Save(PCB_FILE)
|
||||
print(f" Saved {PCB_FILE}")
|
||||
|
||||
# Step 9: Run DRC
|
||||
print("\n[9/9] Running DRC...")
|
||||
result = subprocess.run(
|
||||
["kicad-cli", "pcb", "drc",
|
||||
"--severity-all",
|
||||
"--units", "mm",
|
||||
"-o", DRC_FILE,
|
||||
PCB_FILE],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
|
||||
# Parse DRC output
|
||||
errors = 0
|
||||
warnings = 0
|
||||
if os.path.exists(DRC_FILE):
|
||||
with open(DRC_FILE) as f:
|
||||
content = f.read()
|
||||
errors = content.count('; error')
|
||||
warnings = content.count('; warning')
|
||||
|
||||
print(f" {errors} errors, {warnings} warnings")
|
||||
|
||||
# Summary
|
||||
print("\n" + "=" * 50)
|
||||
if errors == 0:
|
||||
print("SUCCESS! Board routed with no DRC errors.")
|
||||
else:
|
||||
print(f"Done. {errors} DRC errors remain - see {DRC_FILE}")
|
||||
print("=" * 50)
|
||||
|
||||
return errors == 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
success = main()
|
||||
sys.exit(0 if success else 1)
|
||||
@@ -0,0 +1,161 @@
|
||||
#!/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}")
|
||||
Executable
+46
@@ -0,0 +1,46 @@
|
||||
#!/bin/bash
|
||||
# Freerouting automation script
|
||||
# Usage: ./scripts/freeroute.sh
|
||||
#
|
||||
# Prerequisites: Export DSN from KiCad first (File → Export → Specctra DSN)
|
||||
|
||||
set -e
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
DSN_FILE="SN-L00.dsn"
|
||||
SES_FILE="SN-L00.ses"
|
||||
FREEROUTING_JAR="/tmp/freerouting.jar"
|
||||
FREEROUTING_URL="https://github.com/freerouting/freerouting/releases/download/v2.0.1/freerouting-2.0.1.jar"
|
||||
|
||||
echo "=================================="
|
||||
echo "SN-L00 Freerouting Automation"
|
||||
echo "=================================="
|
||||
|
||||
# Check DSN exists
|
||||
if [ ! -f "$DSN_FILE" ]; then
|
||||
echo "ERROR: $DSN_FILE not found"
|
||||
echo "Export from KiCad: File → Export → Specctra DSN"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Download Freerouting if needed
|
||||
if [ ! -f "$FREEROUTING_JAR" ]; then
|
||||
echo "Downloading Freerouting..."
|
||||
curl -L -o "$FREEROUTING_JAR" "$FREEROUTING_URL"
|
||||
fi
|
||||
|
||||
# Run Freerouting
|
||||
echo "Running Freerouting..."
|
||||
java -jar "$FREEROUTING_JAR" \
|
||||
-de "$DSN_FILE" \
|
||||
-do "$SES_FILE" \
|
||||
-mp 200 \
|
||||
-mt 1 \
|
||||
-oit
|
||||
|
||||
echo ""
|
||||
echo "=================================="
|
||||
echo "DONE! Import in KiCad:"
|
||||
echo " File → Import → Specctra Session"
|
||||
echo " Select: $SES_FILE"
|
||||
echo "=================================="
|
||||
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Move board and all components to origin (0,0)
|
||||
Run in KiCad PCB Editor: Tools → Scripting Console
|
||||
Then: exec(open('scripts/move_to_origin.py').read())
|
||||
"""
|
||||
|
||||
import pcbnew
|
||||
|
||||
board = pcbnew.GetBoard()
|
||||
|
||||
# Get current board origin from edge cuts
|
||||
bbox = board.GetBoardEdgesBoundingBox()
|
||||
offset_x = bbox.GetX()
|
||||
offset_y = bbox.GetY()
|
||||
|
||||
print(f"Current board origin: ({pcbnew.ToMM(offset_x):.2f}, {pcbnew.ToMM(offset_y):.2f}) mm")
|
||||
|
||||
if offset_x == 0 and offset_y == 0:
|
||||
print("Board is already at origin!")
|
||||
else:
|
||||
# Move all footprints
|
||||
for fp in board.GetFootprints():
|
||||
pos = fp.GetPosition()
|
||||
new_pos = pcbnew.VECTOR2I(pos.x - offset_x, pos.y - offset_y)
|
||||
fp.SetPosition(new_pos)
|
||||
|
||||
# Move all drawings (including board outline)
|
||||
for drawing in board.GetDrawings():
|
||||
if hasattr(drawing, 'Move'):
|
||||
drawing.Move(pcbnew.VECTOR2I(-offset_x, -offset_y))
|
||||
|
||||
# Move all tracks
|
||||
for track in board.GetTracks():
|
||||
track.Move(pcbnew.VECTOR2I(-offset_x, -offset_y))
|
||||
|
||||
# Move all zones
|
||||
for zone in board.Zones():
|
||||
zone.Move(pcbnew.VECTOR2I(-offset_x, -offset_y))
|
||||
|
||||
print(f"Moved everything by ({-pcbnew.ToMM(offset_x):.2f}, {-pcbnew.ToMM(offset_y):.2f}) mm")
|
||||
print("Board is now at origin (0, 0)")
|
||||
|
||||
pcbnew.Refresh()
|
||||
print("\nDone! Save (Ctrl+S), delete all tracks, then run place_6hp.py")
|
||||
@@ -0,0 +1,79 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
SN-L00 Component Placement Script for 8HP Eurorack (40mm x 100mm)
|
||||
Run in KiCad PCB Editor: Tools → Scripting Console
|
||||
Then: exec(open('scripts/place_8hp.py').read())
|
||||
"""
|
||||
|
||||
import pcbnew
|
||||
|
||||
board = pcbnew.GetBoard()
|
||||
|
||||
def mm(val):
|
||||
return pcbnew.FromMM(val)
|
||||
|
||||
def place(x, y):
|
||||
return pcbnew.VECTOR2I(mm(x), mm(y))
|
||||
|
||||
# First, update the board outline to 8HP (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("Updated board outline to 40mm x 100mm (8HP)")
|
||||
|
||||
# Delete all tracks for fresh routing
|
||||
tracks = list(board.GetTracks())
|
||||
for track in tracks:
|
||||
board.Delete(track)
|
||||
print(f"Deleted {len(tracks)} tracks")
|
||||
|
||||
# Component positions for 8HP (40mm x 100mm) layout v2
|
||||
placements = {
|
||||
"MOD3": (20, 15, 90), # OLED display
|
||||
"J3": (10, 35, 0), # RETURN_IN jack
|
||||
"J4": (30, 35, 0), # TRIG_OUT jack
|
||||
"SW1": (20, 48, 0), # Button
|
||||
"D5": (32, 48, 0), # LED
|
||||
"MOD2": (20, 62, 0), # RP2040-Zero
|
||||
"U2": (8, 78, 0), # 74LVC1G17
|
||||
"U4": (32, 78, 0), # MCP6001
|
||||
"C4": (4, 78, 90), # Decoupling
|
||||
"C5": (14, 78, 90),
|
||||
"C6": (28, 82, 90),
|
||||
"D3": (4, 82, 0), # Protection diodes
|
||||
"D4": (36, 78, 0),
|
||||
"R2": (4, 86, 90), # Resistors
|
||||
"R3": (10, 86, 90),
|
||||
"R4": (16, 86, 90),
|
||||
"R5": (24, 86, 90),
|
||||
"R6": (30, 86, 90),
|
||||
"R7": (36, 86, 90),
|
||||
"J2": (20, 88, 0), # Eurorack power
|
||||
"D2": (4, 92, 0),
|
||||
"U3": (32, 94, 180), # LDO
|
||||
"C2": (10, 94, 90),
|
||||
"C3": (26, 94, 90),
|
||||
}
|
||||
|
||||
print("\nPlacing components for 8HP Eurorack layout (v2)...")
|
||||
placed = 0
|
||||
not_found = []
|
||||
|
||||
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" {ref} -> ({x}, {y}) rot={rot}°")
|
||||
else:
|
||||
not_found.append(ref)
|
||||
|
||||
print(f"\nPlaced {placed} components")
|
||||
if not_found:
|
||||
print(f"Not found: {', '.join(not_found)}")
|
||||
|
||||
pcbnew.Refresh()
|
||||
print("\nDone! Save (Ctrl+S), then run: ./scripts/route.sh")
|
||||
Executable
+97
@@ -0,0 +1,97 @@
|
||||
#!/bin/bash
|
||||
# Full routing and DRC workflow
|
||||
# Usage: ./scripts/route.sh
|
||||
#
|
||||
# Prerequisites:
|
||||
# 1. Run place_8hp.py in KiCad scripting console
|
||||
# 2. Export DSN: File → Export → Specctra DSN
|
||||
# 3. Save PCB
|
||||
|
||||
set -e
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
PCB_FILE="SN-L00.kicad_pcb"
|
||||
DSN_FILE="SN-L00.dsn"
|
||||
SES_FILE="SN-L00.ses"
|
||||
DRC_FILE="DRC.rpt"
|
||||
FREEROUTING_JAR="/tmp/freerouting.jar"
|
||||
FREEROUTING_URL="https://github.com/freerouting/freerouting/releases/download/v2.0.1/freerouting-2.0.1.jar"
|
||||
|
||||
echo "============================================"
|
||||
echo "SN-L00 Routing Pipeline"
|
||||
echo "============================================"
|
||||
|
||||
# Check DSN exists
|
||||
if [ ! -f "$DSN_FILE" ]; then
|
||||
echo ""
|
||||
echo "ERROR: $DSN_FILE not found!"
|
||||
echo ""
|
||||
echo "In KiCad:"
|
||||
echo " 1. Run: exec(open('scripts/place_8hp.py').read())"
|
||||
echo " 2. Save: Ctrl+S"
|
||||
echo " 3. Export: File → Export → Specctra DSN"
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Download Freerouting if needed
|
||||
if [ ! -f "$FREEROUTING_JAR" ]; then
|
||||
echo "[1/3] Downloading Freerouting..."
|
||||
curl -L -o "$FREEROUTING_JAR" "$FREEROUTING_URL"
|
||||
else
|
||||
echo "[1/3] Freerouting ready"
|
||||
fi
|
||||
|
||||
# Run Freerouting
|
||||
echo "[2/3] Running Freerouting..."
|
||||
java -jar "$FREEROUTING_JAR" \
|
||||
-de "$DSN_FILE" \
|
||||
-do "$SES_FILE" \
|
||||
-mp 200 \
|
||||
-mt 1 \
|
||||
-oit 2>&1 | grep -E "(completed|INFO.*Auto|ERROR)" || true
|
||||
|
||||
if [ ! -f "$SES_FILE" ]; then
|
||||
echo "ERROR: Routing failed - no SES file created"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[3/3] Running DRC..."
|
||||
echo ""
|
||||
echo "============================================"
|
||||
echo "In KiCad: File → Import → Specctra Session"
|
||||
echo "Select: $SES_FILE"
|
||||
echo "Then save (Ctrl+S)"
|
||||
echo "============================================"
|
||||
echo ""
|
||||
echo "Waiting for you to import SES and save..."
|
||||
read -p "Press Enter when done: "
|
||||
|
||||
# Run DRC from CLI
|
||||
kicad-cli pcb drc \
|
||||
--severity-all \
|
||||
--units mm \
|
||||
--schematic-parity \
|
||||
-o "$DRC_FILE" \
|
||||
"$PCB_FILE" 2>&1
|
||||
|
||||
echo ""
|
||||
echo "============================================"
|
||||
echo "DRC Results:"
|
||||
echo "============================================"
|
||||
grep -E "^(Found|\*\*)" "$DRC_FILE" || cat "$DRC_FILE"
|
||||
echo ""
|
||||
|
||||
# Count errors vs warnings
|
||||
ERRORS=$(grep -c "error$" "$DRC_FILE" 2>/dev/null || echo "0")
|
||||
WARNINGS=$(grep -c "warning$" "$DRC_FILE" 2>/dev/null || echo "0")
|
||||
|
||||
echo "Summary: $ERRORS errors, $WARNINGS warnings"
|
||||
|
||||
if [ "$ERRORS" -eq 0 ]; then
|
||||
echo ""
|
||||
echo "✓ No DRC errors - ready for manufacturing!"
|
||||
else
|
||||
echo ""
|
||||
echo "Fix errors and re-run: ./scripts/route.sh"
|
||||
fi
|
||||
Reference in New Issue
Block a user