Week 06 — PCB Design Documentation¶
Fab Academy | Dorian Fritze Date: March 24, 2026
Project Overview¶
Designing a custom artistic PCB in KiCad featuring: - A thought-cloud board outline (Edge.Cuts) - A lightbulb as a decorative copper element - Artistic hand-routed traces as part of the visual design - XIAO RP2040 microcontroller - Radiating slash marks as milled/engraved decorative elements
Original Design Sketch¶

Problem: PCB Was Too Large¶
The original PCB design was too large to mill on the Carvera. The goal was to scale it down to 50% (0.5x) while preserving the artistic traces and decorative elements.
Step 1: Attempting to Scale via KiCad Scripting Console¶
Tools → Scripting Console (or backtick ` key)
First Attempt — Multi-line Paste Fails¶
Pasting a multi-line Python script directly into the KiCad shell causes:
SyntaxError: multiple statements found while compiling a single statement

Fix: The KiCad scripting console is line-by-line. Use exec() with \n to run multi-line blocks, or save a .py file and run it with:
exec(open('/path/to/script.py').read())
Step 2: Successfully Scaling Footprints¶
Running each line separately, then using exec() for the loop:
import pcbnew
board = pcbnew.GetBoard()
scale = 0.5
origin = pcbnew.VECTOR2I(pcbnew.FromMM(100), pcbnew.FromMM(100))
exec("for item in board.GetFootprints():\n pos = item.GetPosition()\n new_x = origin.x + int((pos.x - origin.x) * scale)\n new_y = origin.y + int((pos.y - origin.y) * scale)\n item.SetPosition(pcbnew.VECTOR2I(new_x, new_y))")
pcbnew.Refresh()

Result: Footprints moved. Namespace confirms scale: 0.5, new_x and new_y values changed correctly.
Step 3: Diagnosing Edge.Cuts — What Shape Type Is It?¶
Running a diagnostic to check the Edge.Cuts layer:
exec("for item in board.GetDrawings():\n print(item.GetLayer(), item.GetClass(), item.GetStart(), item.GetEnd())")
The Edge.Cuts outline showed layer 25 (KiCad 9 numbering) with start and end at the same point — meaning it's a polygon, not individual line segments.

Key finding: GetStart()/GetEnd() won't work on polygons — need GetPolyShape().
Step 4: Board State After Initial Scaling¶
After scaling footprints but before scaling traces and drawings:

Problems visible: - Flower/cloud Edge.Cuts outline (top left) — old choppy outline from manual tracing - Actual PCB components (bottom right) — already scaled but offset - Red copper flood fill covering both areas
Step 5: Original Design Intent¶
The PCB is designed to look like the hand-drawn sketch — the traces ARE the art:
| Element | KiCad Layer | Carvera Operation |
|---|---|---|
| Cloud outline | Edge.Cuts | Mill/cut board shape |
| Radiating slash marks | Edge.Cuts or Eco1.User | Drill/mill slots |
| Lightbulb outline | F.Cu | Copper engraving |
| Circuit traces | F.Cu | Mill isolation |

Step 6: Curved Traces Didn't Scale — Arc Diagnosis¶
After scaling straight tracks, the curved artistic traces (thought bubble, lightbulb coil) remained at original size.

Diagnostic showed all tracks were PCB_TRACK (straight segments) — the curves were actually PCB_SHAPE arcs on layer 0 (F.Cu), not tracks.
Step 7: Bad Script — Circles Went Wrong¶
Attempting to scale F.Cu shapes with SetCenter() produced incorrect giant circles:

Lesson learned: SetCenter() does not correctly scale arcs in KiCad 9. Need to use SetMid() for arcs. Also, SetStart/SetEnd without SetMid distorts arc shapes.
Step 8: Undo Failed — Scripts Don't Enter Undo History¶
Python scripting changes in KiCad do not go into the undo buffer. Ctrl+Z could not reverse the bad script.

⚠️ Critical lesson: Always save a backup before running any script:
board.Save('/Users/dorianfritze/Desktop/week06-BACKUP.kicad_pcb')
Step 9: Recovering from Autosave¶
KiCad's autosave (_autosave-week06-assignment.kicad_pcb) was found with the first scaling partially done — footprints scaled but traces not yet scaled.

Recovery steps:
1. File → Save As → week06-assignment-v2.kicad_pcb
2. Run backup script before any further changes
Step 10: Fresh Start — Scripting Console Import Error¶
Opening a new scripting console requires re-importing pcbnew every time:

import pcbnew # Always run this first in a new session!
board = pcbnew.GetBoard()
Step 11: Traces Successfully Scaled¶
Working carefully with backups saved between each step:
import pcbnew
board = pcbnew.GetBoard()
board.Save('/Users/dorianfritze/Desktop/week06-SAFE-BACKUP.kicad_pcb') # BACKUP FIRST!
scale = 0.5
origin = pcbnew.VECTOR2I(pcbnew.FromMM(100), pcbnew.FromMM(100))
exec("for track in board.GetTracks():\n s = track.GetStart()\n e = track.GetEnd()\n track.SetStart(pcbnew.VECTOR2I(origin.x + int((s.x - origin.x) * scale), origin.y + int((s.y - origin.y) * scale)))\n track.SetEnd(pcbnew.VECTOR2I(origin.x + int((e.x - origin.x) * scale), origin.y + int((e.y - origin.y) * scale)))")
pcbnew.Refresh()

Status: Straight traces now aligned with scaled footprints. Curved F.Cu shapes still need attention.
Step 12: Straight Line Drawings Scaled, Curves Still Wrong¶
After scaling straight tracks successfully, ran script to scale straight F.Cu drawings (shape type 0):
exec("for item in board.GetDrawings():\n if item.GetLayer() == 0 and item.GetShape() == 0:\n ...")
Curved shapes still didn't scale. Zoomed in view showed the problem clearly:

Step 13: Discovering Curves Are Functional Traces with Nets¶
Right-clicking a stray curve revealed it was on F.Cu with Net: D3 assigned — meaning it's a functional copper trace, not just a decorative drawing. Line width was 0.2mm (too thin for Carvera).

Attempted script using GetNet() to find these shapes — but it made things worse again.
Step 14: Layer Diagnostic Returned Nothing¶
Ran diagnostic to find F.Cu.user layer number:
exec("for item in board.GetDrawings():\n if 'user' in str(item.GetLayer()).lower() or item.GetLayer() > 30:\n print(item.GetLayer(), item.GetClass(), item.GetShape(), item.GetStart())")

No output — all curved shapes were on layer 0 (F.Cu proper), not a user layer.
Step 15: Recovering to Backup2 — Good State Found¶
Restored from week06-SAFE-BACKUP2.kicad_pcb. Board looked nearly correct with cloud, lightbulb, and components visible:

However, on closer inspection the cloud and lightbulb art were still at original large scale — they hadn't been scaled at all. The components appeared small relative to the large artistic elements.
Step 16: Double-Scaling Problem Discovered¶
After further scripting attempts, some components appeared to have been scaled twice — the XIAO and J8 connector were far apart from the rest of the cluster:

Step 17: Key Insight — Footprints Must NOT Resize¶
The fundamental challenge clarified: - ✅ Footprint positions → must scale (move closer together) - ❌ Footprint pad/component sizes → must NOT scale (must match real parts) - ✅ Trace positions and routing → must scale - ✅ Artistic drawing positions → must scale - ❌ Trace widths → keep or increase for Carvera
SetPosition() correctly moves without resizing. The scripts were right — the problem was repeated execution causing double-scaling.
Step 18: Back to Original — Clean Restart¶
Opened the original unscaled week06-assignment.kicad_pcb. All components, traces, and artistic elements intact:

New Strategy: Delete Art First, Then Scale¶
Lesson learned the hard way: Scripting arc/curve scaling in KiCad is unreliable. The new approach:
- Save protected original immediately
- Manually delete all curved artistic elements before any scripting:
- Cloud interior curves
- Lightbulb outline
- Thought bubble circles
- Coil/spiral
- Radiating slashes
- Edge.Cuts outline
- Run proven scaling scripts on simple straight geometry only
- Rebuild all art manually at correct scale after scaling
- Import Inkscape SVG for cloud Edge.Cuts
This avoids all arc scripting problems entirely.
Step 19: Fresh Start — Delete Art First, Then Scale¶
Opened original file. Manually deleted all curved artistic elements first, leaving only straight traces and footprints.

Step 20: Footprints Scaled Successfully¶
scale = 0.5
origin = pcbnew.VECTOR2I(pcbnew.FromMM(100), pcbnew.FromMM(100))
exec("for item in board.GetFootprints():\n pos = item.GetPosition()\n item.SetPosition(pcbnew.VECTOR2I(origin.x + int((pos.x - origin.x) * scale), origin.y + int((pos.y - origin.y) * scale)))")
pcbnew.Refresh()

Step 21: Traces Scaled Successfully¶
exec("for track in board.GetTracks():\n s = track.GetStart()\n e = track.GetEnd()\n track.SetStart(pcbnew.VECTOR2I(origin.x + int((s.x - origin.x) * scale), origin.y + int((s.y - origin.y) * scale)))\n track.SetEnd(pcbnew.VECTOR2I(origin.x + int((e.x - origin.x) * scale), origin.y + int((e.y - origin.y) * scale)))")
pcbnew.Refresh()

Step 22: Trace Widths Fixed for Carvera¶
exec("for track in board.GetTracks():\n if track.GetWidth() < pcbnew.FromMM(0.4):\n track.SetWidth(pcbnew.FromMM(0.4))")
pcbnew.Refresh()

Saved as week06-SCALED-CLEAN.kicad_pcb.
Current Status¶
- Footprints scaled to 50%
- Straight traces scaled to 50%
- Trace widths fixed to 0.4mm minimum
- Delete stray vertical line at top
- Fix remaining ratsnest connections manually
- Trace cloud in Inkscape → import as Edge.Cuts
- Redraw lightbulb and artistic elements at new scale
- Run DRC check
- Export Gerbers for Carvera/MakerCAM
Key Lessons Learned¶
- KiCad scripting console is line-by-line — use
exec()with\nfor loops - Python scripts bypass the undo buffer — always save before scripting
- Arc scaling requires SetMid() — not just SetStart/SetEnd, and even then unreliable
- PCB_SHAPE arcs vs PCB_ARC tracks are different object types
- Layer 25 = Edge.Cuts in KiCad 9 (not layer 44)
- Polygon outlines need
GetPolyShape()notGetStart()/GetEnd() - Inkscape → Trace Bitmap → SVG → KiCad is the best workflow for organic board outlines
- Artistic curves with nets assigned are functional traces — behave differently from drawings
- Running scaling scripts twice causes double-scaling — always check state before running
- Delete curved art BEFORE scripting — rebuild by hand after scaling is safer and faster
- Always save a protected backup of the original before any modifications
Carvera Milling Specifications (Reference)¶
| Parameter | Value |
|---|---|
| Min trace width | 0.4mm (1/64" bit) |
| Min clearance | 0.4mm |
| Min drill size | 0.8mm |
| Edge clearance | 1.0mm |
Documentation continues as work progresses...