Skip to content

Instantly share code, notes, and snippets.

@mcbridejc
Last active April 4, 2022 22:23
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
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

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