Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Magnetic Train Track Kicad Plugin
"""KiCad plugin to generate linear stepper track traces in KiCad"""
import pcbnew
import numpy as np
WIDTH=13.2
PITCH=1.0
WIDTH_MARGIN = 1.2
LINE_WIDTH = 0.3
VIA_DRILL = 0.3
VIA_PAD = 0.6
GUIDE_RAIL_SPACE = 9.0
GUIDE_RAIL_WIDTH = 2.0
CENTER_RAIL_WIDTH = 5.0
def rotate(x, theta):
x = np.asarray(x)
R = np.array([
[np.cos(theta), np.sin(theta)],
[-np.sin(theta), np.cos(theta)]
])
return np.dot(R, x.T).T
def pcbpoint(p):
return pcbnew.wxPointMM(float(p[0]), float(p[1]))
def append_straight(board, start, cycles, angle):
start = np.array(start)
angle = np.deg2rad(angle)
for d in range(cycles):
a = np.array([
(d*4 * PITCH, -(WIDTH / 2 + WIDTH_MARGIN)),
(d*4 * PITCH, WIDTH / 2 + WIDTH_MARGIN),
((d*4+2) * PITCH, WIDTH / 2 + WIDTH_MARGIN),
((d*4+2) * PITCH, -(WIDTH / 2 + WIDTH_MARGIN)),
((d*4+4) * PITCH, -(WIDTH / 2 + WIDTH_MARGIN)),
])
b = np.array([
((d*4 + 1) * PITCH, -WIDTH / 2),
((d*4 + 1) * PITCH, WIDTH / 2),
((d*4 + 3) * PITCH, WIDTH / 2),
((d*4 + 3) * PITCH, -WIDTH / 2),
((d*4 + 5) * PITCH, -WIDTH / 2),
])
# Rotate points about (0, 0) by angle
a = rotate(a, angle)
b = rotate(b, angle)
a += start
b += start
for i in range(len(a)-1):
track = pcbnew.PCB_TRACK(board)
track.SetStart(pcbpoint(a[i]))
track.SetEnd(pcbpoint(a[i+1]))
track.SetWidth(int(LINE_WIDTH * 1e6))
track.SetLayer(pcbnew.F_Cu)
board.Add(track)
for i in range(len(b) - 1):
if (i%2) == 0:
layer = pcbnew.F_Cu
else:
layer = pcbnew.B_Cu
track = pcbnew.PCB_TRACK(board)
track.SetStart(pcbpoint(b[i]))
track.SetEnd(pcbpoint(b[i+1]))
track.SetWidth(int(LINE_WIDTH * 1e6))
track.SetLayer(layer)
board.Add(track)
via = pcbnew.PCB_VIA(board)
via.SetPosition(pcbpoint(b[i]))
via.SetDrill(int(VIA_DRILL * 1e6))
via.SetWidth(int(VIA_PAD * 1e6))
board.Add(via)
# Generate guide rails
def add_line(offset, thickness):
p0 = start + rotate((0, offset), angle)
p1 = start + rotate((cycles * PITCH * 4, offset), angle)
track = pcbnew.PCB_TRACK(board)
track.SetStart(pcbpoint(p0))
track.SetEnd(pcbpoint(p1))
track.SetWidth(int(thickness * 1e6))
track.SetLayer(pcbnew.B_Cu)
board.Add(track)
add_line(-GUIDE_RAIL_SPACE / 2, GUIDE_RAIL_WIDTH)
add_line(GUIDE_RAIL_SPACE / 2, GUIDE_RAIL_WIDTH)
# return new starting point
return tuple(start + rotate(np.array((PITCH * cycles * 4, 0)), angle))
def append_arc(board, start, radius, start_angle, arc_angle):
start = np.array(start)
arc_angle = np.deg2rad(arc_angle)
start_angle = np.deg2rad(start_angle)
# Pick an angle step so that the center pitch is maintained
# one PITCH step is 2*atan(PITCH / radius / 2), but we need to create a
# full 2-phase cycle, hence multiplied by 4, but after atan for better small
# angle approximation
ideal_angle_step = 8 * np.arctan(PITCH / radius / 2)
# Now we choose to compromise on pitch in order to achieve an exact angle
# Choose an angle step into which the total angle can divide evenly
n_angle_steps = int(np.round(arc_angle / ideal_angle_step))
angle_step = arc_angle / n_angle_steps
# Compute the center of the arc
center = start + rotate(np.array([0, -radius]), start_angle)
# Define points as angle_step, radius offset (from center)
a = [
(0, -WIDTH / 2 - WIDTH_MARGIN),
(0, WIDTH / 2 + WIDTH_MARGIN),
(2, WIDTH / 2 + WIDTH_MARGIN),
(2, -WIDTH / 2 - WIDTH_MARGIN),
(4, -WIDTH / 2 - WIDTH_MARGIN),
]
b = [
(1, -WIDTH / 2),
(1, WIDTH / 2),
(3, WIDTH / 2),
(3, -WIDTH / 2),
(5, -WIDTH / 2),
]
def p2c(c, alpha, r):
return c + np.array([r * np.cos(alpha), -r * np.sin(alpha)])
for n in range(n_angle_steps):
for i in range(len(a) - 1):
p1 = p2c(center, (n*4 + a[i][0]) * angle_step / 4 + start_angle - np.pi / 2, radius + a[i][1])
p2 = p2c(center, (n*4 + a[i+1][0]) * angle_step / 4 + start_angle - np.pi / 2, radius + a[i+1][1])
track = pcbnew.PCB_TRACK(board)
track.SetStart(pcbpoint(p1))
track.SetEnd(pcbpoint(p2))
track.SetWidth(int(LINE_WIDTH * 1e6))
track.SetLayer(pcbnew.F_Cu)
board.Add(track)
for i in range(len(b) - 1):
p1 = p2c(center, (n*4 + b[i][0]) * angle_step / 4 + start_angle - np.pi / 2, radius + b[i][1])
p2 = p2c(center, (n*4 + b[i+1][0]) * angle_step / 4 + start_angle - np.pi / 2, radius + b[i+1][1])
if (i%2) == 0:
layer = pcbnew.F_Cu
else:
layer = pcbnew.B_Cu
track = pcbnew.PCB_TRACK(board)
track.SetStart(pcbpoint(p1))
track.SetEnd(pcbpoint(p2))
track.SetWidth(int(LINE_WIDTH * 1e6))
track.SetLayer(layer)
board.Add(track)
via = pcbnew.PCB_VIA(board)
via.SetPosition(pcbpoint(p1))
via.SetDrill(int(VIA_DRILL * 1e6))
via.SetWidth(int(VIA_PAD * 1e6))
board.Add(via)
# Add guard rails
def add_arc(offset, thickness):
arc_start = center + rotate((0, -radius + offset), start_angle - np.pi / 2)
arc_end = center + rotate(arc_start - center, -arc_angle)
arc_mid = center + rotate(arc_start - center, -arc_angle / 2)
track = pcbnew.PCB_ARC(board)
#track.SetPosition(pcbpoint(center))
track.SetStart(pcbpoint(arc_start))
track.SetMid(pcbpoint(arc_mid))
track.SetEnd(pcbpoint(arc_end))
track.SetWidth(int(thickness * 1e6))
track.SetLayer(pcbnew.B_Cu)
board.Add(track)
add_arc(GUIDE_RAIL_SPACE / 2, GUIDE_RAIL_WIDTH)
add_arc(-GUIDE_RAIL_SPACE / 2, GUIDE_RAIL_WIDTH)
return tuple(center + rotate(start - center, arc_angle))
class TrackLayout(pcbnew.ActionPlugin):
def defaults(self):
self.name = "Layout race track"
self.category = "Modify PCB"
self.description = "Layout race track"
self.show_toolbar_button = True
def Run(self):
board = pcbnew.GetBoard()
TURN_RADIUS = 30
pos = (150, 150)
pos = append_straight(board, pos, 10, 0)
pos = append_arc(board, pos, TURN_RADIUS, 0, 90)
#pos = append_straight(board, pos, 5, 90)
pos = append_arc(board, pos, TURN_RADIUS, 90, 90)
pos = append_straight(board, pos, 10, 180)
pos = append_arc(board, pos, TURN_RADIUS, 180, 90)
#pos = append_straight(board, pos, 5, 270)
pos = append_arc(board, pos, TURN_RADIUS, 270, 90)
TrackLayout().register()
@mcbridejc
Copy link
Author

mcbridejc commented Feb 17, 2022

This uses numpy. I ran it on linux, where kicad uses the system python. On windows/mac, I believe kicad will be using its own python installation, which will probably not have numpy. See instructions here for installing new packages (i.e. numpy) in the kicad python env: https://github.com/mcbridejc/kicad_component_layout#how-to-install

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment