Magnetic Train Track Kicad Plugin
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"""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() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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