Skip to content

Instantly share code, notes, and snippets.

@akirayou
Created December 27, 2025 11:14
Show Gist options
  • Select an option

  • Save akirayou/b6a170f7a5e8c66885c4cc9ef421e353 to your computer and use it in GitHub Desktop.

Select an option

Save akirayou/b6a170f7a5e8c66885c4cc9ef421e353 to your computer and use it in GitHub Desktop.
import math
import pcbnew
# ----------------- KiCad9 helpers -----------------
def v2i_mm(x_mm: float, y_mm: float) -> "pcbnew.VECTOR2I":
return pcbnew.VECTOR2I(pcbnew.FromMM(x_mm), pcbnew.FromMM(y_mm))
def mm_to_iu(mm: float) -> int:
return int(pcbnew.FromMM(mm))
def ensure_net(board, net_name):
if not net_name:
return None
net = board.FindNet(net_name)
if net is None:
net = pcbnew.NETINFO_ITEM(board, net_name)
board.Add(net)
return net
# ----------------- primitives (return end cursor) -----------------
def add_track(board, start_mm, diff_mm, width_iu, layer, netcode):
"""Draw straight segment. Returns end_mm."""
end_mm = (start_mm[0] + diff_mm[0], start_mm[1] + diff_mm[1])
if start_mm == end_mm:
return end_mm
t = pcbnew.PCB_TRACK(board)
t.SetStart(v2i_mm(*start_mm))
t.SetEnd(v2i_mm(*end_mm))
t.SetWidth(width_iu)
t.SetLayer(layer)
if netcode is not None:
t.SetNetCode(netcode)
board.Add(t)
return end_mm
def add_arc_90_ccw(board, start_mm, dir_in, r_mm, width_iu, layer, netcode):
"""
Draw a 90-degree CCW fillet arc around the sharp corner, using only:
- start_mm: expected to be the tangent point on the incoming edg
- dir_in: incoming direction (0:R, 1:U, 2:L, 3:D)
- r_mm: radius
Returns arc end point (tangent on outgoing edge).
"""
if r_mm <= 0:
# No arc: just return the corner itself (caller can decide)
return start_mm
start = start_mm
sx, sy = start_mm
r = r_mm
# CCW turn: dir_out = (dir_in + 1) % 4
# Define start/end tangent points and arc center for each incoming dir.
# right is positive X, up is positive Y
if dir_in == 0: # Right -> Up at (cx,cy)
center = (sx, sy + r)
end = (sx + r, sy + r)
elif dir_in == 1: # Up -> Left
center = (sx - r, sy )
end = (sx - r, sy + r)
elif dir_in == 2: # Left -> Down
center = (sx, sy - r)
end = (sx - r , sy - r)
elif dir_in == 3: # Down -> Right
center = (sx + r, sy )
end = (sx + r, sy - r)
else:
raise ValueError("dir_in must be 0(R),1(U),2(L),3(D)")
# Compute mid-point for KiCad ARC (start/center/end -> 45deg point)
sx, sy = start
ccx, ccy = center
ex, ey = end
vs = (sx - ccx, sy - ccy)
ve = (ex - ccx, ey - ccy)
v = (vs[0] + ve[0], vs[1] + ve[1])
nv = math.hypot(v[0], v[1])
if nv == 0:
raise ValueError("Degenerate arc geometry (check r/pitch/size).")
rr = math.hypot(vs[0], vs[1])
mx = ccx + (v[0] / nv) * rr
my = ccy + (v[1] / nv) * rr
a = pcbnew.PCB_ARC(board)
a.SetStart(v2i_mm(sx, sy))
a.SetMid(v2i_mm(mx, my))
a.SetEnd(v2i_mm(ex, ey))
a.SetWidth(width_iu)
a.SetLayer(layer)
if netcode is not None:
a.SetNetCode(netcode)
board.Add(a)
return end
# ----------------- validation -----------------
def validate_params(outer_w, outer_h, turns, pitch, r, track_w):
if int(turns) != turns or turns < 1:
raise ValueError("turns must be a positive integer.")
if pitch <= 0:
raise ValueError("pitch_mm must be > 0.")
if track_w <= 0:
raise ValueError("track_w_mm must be > 0.")
if r < 0:
raise ValueError("corner_r_mm must be >= 0.")
if outer_w <= 0 or outer_h <= 0:
raise ValueError("outer_w_mm / outer_h_mm must be > 0.")
if outer_w <= 2*r or outer_h <= 2*r:
raise ValueError("outer size too small for chosen corner radius.")
# practical guard (keeps adjacent turns from kissing at corners)
if r > 0.5 * pitch:
raise ValueError("corner_r_mm too large; recommend corner_r_mm <= pitch_mm/2.")
# Need positive width/height at the last turn BEFORE drawing edges
# width_i = outer_w - 2*i*pitch, i=turns-1 must satisfy width_i > 2r (same for height)
min_w = outer_w - 2.0 * (turns - 1) * pitch
min_h = outer_h - 2.0 * (turns - 1) * pitch
if min_w <= 2.0 * r or min_h <= 2.0 * r:
raise ValueError(
f"Spiral collapses: min_w={min_w:.3f}mm, min_h={min_h:.3f}mm, need > 2r={2*r:.3f}mm. "
f"Increase outer size or reduce turns/pitch/r."
)
# ----------------- main -----------------
def draw_rect_spiral_rounded_ccw(
start_x_mm=100.0,
start_y_mm=100.0,
outer_w_mm=30.0,
outer_h_mm=18.0,
turns=8,
pitch_mm=0.7,
track_w_mm=0.35,
corner_r_mm=None, # None -> pitch/2
layer=pcbnew.F_Cu,
net_name=None,
):
"""
Rectangular spiral, CCW fixed:
Right -> Up -> Left -> Down ... (repeat)
Boundary shrink rules (per full turn):
bottom += pitch, right -= pitch, top -= pitch, left += pitch
Each corner is a 90° CCW arc with radius r.
Pads/vias are NOT touched.
"""
turns = int(turns)
r = (0.5 * pitch_mm) if corner_r_mm is None else float(corner_r_mm)
validate_params(outer_w_mm, outer_h_mm, turns, pitch_mm, r, track_w_mm)
board = pcbnew.GetBoard()
net = ensure_net(board, net_name)
netcode = net.GetNetCode() if net else None
wiu = mm_to_iu(track_w_mm)
left = start_x_mm
bottom = start_y_mm
# Start on bottom edge heading right: (left+r, bottom)
cur = (left + r, bottom)
w=outer_w_mm - 2*r
h=outer_h_mm - 2*r
for _ in range(turns):
# ---- corner at (right, bottom): dir_in=Right(0) ----
cur = add_track(board, cur, (w,0), wiu, layer, netcode)
cur = add_arc_90_ccw(board, cur, dir_in=0, r_mm=r, width_iu=wiu, layer=layer, netcode=netcode)
w-= pitch_mm
# ---- corner at (right, top): dir_in=Up(1) ----
cur = add_track(board, cur, (0,h), wiu, layer, netcode)
cur = add_arc_90_ccw(board, cur, dir_in=1, r_mm=r, width_iu=wiu, layer=layer, netcode=netcode)
h-= pitch_mm
# ---- corner at (left, top): dir_in=Left(2) ----
cur = add_track(board, cur, (-w, 0), wiu, layer, netcode)
cur = add_arc_90_ccw(board, cur, dir_in=2, r_mm=r, width_iu=wiu, layer=layer, netcode=netcode)
w-= pitch_mm
# ---- corner at (left, bottom): dir_in=Down(3) ----
cur = add_track(board, cur, (0,-h), wiu, layer, netcode)
cur = add_arc_90_ccw(board, cur, dir_in=3, r_mm=r, width_iu=wiu, layer=layer, netcode=netcode)
h-= pitch_mm
# Now we are at (left_old + r, bottom). Update left boundary for next loop.
# IMPORTANT: connect to next loop's start (left_new + r, bottom) to keep continuity
#cur = add_track(board, cur, (left + r, bottom), wiu, layer, netcode)
pcbnew.Refresh()
# ----------------- example -----------------
W=60
H=40
T=10
LW=0.5
D=LW+0.2
draw_rect_spiral_rounded_ccw(
start_x_mm=100.0,
start_y_mm=100.0,
outer_w_mm=W,
outer_h_mm=H,
turns=T,
pitch_mm=D*2,
track_w_mm=LW,
corner_r_mm=None, # None -> pitch/2
layer=pcbnew.F_Cu,
net_name="PCB_heater"
)
draw_rect_spiral_rounded_ccw(
start_x_mm=100.0+D,
start_y_mm=100.0+D,
outer_w_mm=W-D*2,
outer_h_mm=H-D*2,
turns=T,
pitch_mm=D*2,
track_w_mm=LW,
corner_r_mm=None, # None -> pitch/2
layer=pcbnew.F_Cu,
net_name="PCB_heater"
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment