Working with KiCAD 9.0.2 on Ubuntu 24.04
Partly as a hobby, but mostly because the via-fence generation plugins out there are buggy, I’m trying to create one based on the following principles: gather the selected trace — a sequence of “chunks” (chunk can be either a segment or an arc); split it into teeny tiny straight line segments of 0.01mm length (this is at the abstract mathematical level, not changing the PCB layout). For each of those, I generate two points, P_L and P_R (left and right of the trace), perpendicular to the tiny segment, 0.9mm away from it — thus, 0.9mm from the trace (for now, I’m hardcoding everything; I will worry about user-provided parameters once I manage to make the algorithm work).
The idea is that with these teeny tiny segments, the sequence of points P_L and P_R won’t do weird things as long as the trace does not have discontinuities in its tangent direction (which is normally the case for RF-style traces where I need the via fences).
I then create segments (again, at the abstract mathematical level) from P_L[k] to P_L[k+1] and same for P_R — so, I create two lines/curves that travel next to the original trace, 0.9mm away on each side. The segments of these two curves P_L and P_R are not 0.01mm in length (because of the curves); so, I now simply advance through the segments, adding their lengths until the accumulated sum reaches 0.75mm (again, hardcoding everything for now); then, I place a via there — I do this independently for P_L and P_R, because they don’t advance covering distance at the same rate. [[ actually, I determine the total length and do an integer division to see the exact number of vias that fit at approximately 0.75mm distance — so, it ends up being in general slightly above 0.75mm)
As a debugging assist / sanity-check, I am actually drawing the segments that define the trace as well as the P_L’s and P_R’s in sequence, on the User.Drawings layer.
See the script below — written with the help of chatGPT (please forgive me: I am pathologically Python-phobic and therefore Python impaired :‒\ ) … seems to work with a sequence of segments and arcs as shown in the screenshot below (with the expected glitches at the inner sides of the 45 degree corners between segments):
If I select those segments and do fillet tracks (with 2mm radius) to convert everything into a “smooth” curve, this is the horrific mess I get:
It looks like the endpoints in the arcs are inconsistent, or in any case something like the various chunks not being in sequence (end of one chunk coinciding with the start of the next chunk), and the script is unsuccessfully trying to put them in order (which worked for the other example, but not sure what’s going on here).
See script below. Can you confirm whether the above hypothesis has any merit? Any suggested debugging steps to help me figure out what’s going on?
import pcbnew
import wx
import math
class ViaFenceGeneratorProper(pcbnew.ActionPlugin):
def defaults(self):
self.name = "Via Fence Generator Proper"
self.category = "Via Tools"
self.description = "Draws $T$, $T_L$, $T_R$ and places evenly spaced vias along offsets"
self.show_toolbar_button = True
self.icon_file_name = ""
def Run(self):
board = pcbnew.GetBoard()
items = [t for t in board.GetTracks()
if t.IsSelected() and isinstance(t, (pcbnew.PCB_TRACK, pcbnew.PCB_ARC))]
if not items:
wx.MessageBox("No selected tracks or arcs found.")
return
# Determine GND-like net
netcodes = board.GetNetsByName()
preferred = ["GND", "GNDA", "GNDD"]
net = None
for name in preferred:
if name in netcodes:
net = netcodes[name]
break
if not net:
wx.MessageBox("No suitable net found (GND, GNDA, GNDD).")
return
# Build endpoint map for chaining
endpoint_map = {}
for item in items:
for pt in (item.GetStart(), item.GetEnd()):
key = (pt.x, pt.y)
endpoint_map.setdefault(key, []).append(item)
def find_chain(start_item):
visited_ids = set([id(start_item)])
chain = [start_item]
def extend(pt, prepend=False):
key = (pt.x, pt.y)
while True:
options = [s for s in endpoint_map.get(key, []) if id(s) not in visited_ids]
if not options:
break
nxt = options[0]
visited_ids.add(id(nxt))
next_pt = nxt.GetEnd() if (nxt.GetStart().x, nxt.GetStart().y) == key else nxt.GetStart()
if prepend:
chain.insert(0, nxt)
else:
chain.append(nxt)
key = (next_pt.x, next_pt.y)
extend(start_item.GetEnd(), prepend=False)
extend(start_item.GetStart(), prepend=True)
return chain
chain = find_chain(items[0])
seg_len = 0.01 * 1e6
offset_nm = 0.9 * 1e6
thickness = int(0.05 * 1e6)
layer = 17
drill = int(0.4 * 1e6)
diameter = int(0.6 * 1e6)
# Sample centerline
centerline = []
for t in chain:
if isinstance(t, pcbnew.PCB_ARC):
c = t.GetCenter()
r = t.GetRadius()
a0 = t.GetArcAngleStart().AsRadians()
da = t.GetAngle().AsRadians()
length = abs(da * r)
steps = max(1, int(length / seg_len))
for i in range(steps + 1):
theta = a0 + da * i / steps
centerline.append((c.x + r * math.cos(theta), c.y + r * math.sin(theta)))
else:
s = t.GetStart(); e = t.GetEnd()
dx = e.x - s.x; dy = e.y - s.y
length = math.hypot(dx, dy)
steps = max(1, int(length / seg_len))
for i in range(steps + 1):
f = i / steps
centerline.append((s.x + dx * f, s.y + dy * f))
# Deduplicate
dedup = []
for pt in centerline:
if not dedup or pt != dedup[-1]:
dedup.append(pt)
centerline = dedup
# Draw centerline T
for i in range(1, len(centerline)):
x0, y0 = centerline[i-1]; x1, y1 = centerline[i]
seg = pcbnew.PCB_SHAPE(board)
seg.SetShape(pcbnew.SHAPE_T_SEGMENT)
seg.SetStart(pcbnew.VECTOR2I(int(x0), int(y0)))
seg.SetEnd( pcbnew.VECTOR2I(int(x1), int(y1)))
seg.SetLayer(layer)
seg.SetWidth(thickness)
board.Add(seg)
# Offset curves
TL = []; TR = []
for i in range(len(centerline)):
if i == 0:
x1, y1 = centerline[0]; x2, y2 = centerline[1]
elif i == len(centerline)-1:
x1, y1 = centerline[-2]; x2, y2 = centerline[-1]
else:
x1, y1 = centerline[i-1]; x2, y2 = centerline[i+1]
dx = x2 - x1; dy = y2 - y1
d = math.hypot(dx, dy)
if d == 0:
TL.append(centerline[i]); TR.append(centerline[i])
else:
nx = -dy/d; ny = dx/d
cx, cy = centerline[i]
TL.append((cx + nx*offset_nm, cy + ny*offset_nm))
TR.append((cx - nx*offset_nm, cy - ny*offset_nm))
# Compute spacing
def compute_spacing(path):
total = 0.0
for i in range(1, len(path)):
x0, y0 = path[i-1]; x1, y1 = path[i]
total += math.hypot(x1 - x0, y1 - y0)
count = max(1, int(total // (0.75 * 1e6)))
return total / count if count > 0 else total
spacing_L = compute_spacing(TL)
spacing_R = compute_spacing(TR)
# Draw path and place vias: start, evenly spaced, end if needed
def draw_curve_with_vias(path, spacing_nm):
cumulative = 0.0
next_via_at = spacing_nm
# Place first via
x0, y0 = path[0]
via = pcbnew.PCB_VIA(board)
via.SetPosition(pcbnew.VECTOR2I(int(x0), int(y0)))
via.SetDrill(drill)
via.SetWidth(diameter)
via.SetLayerPair(pcbnew.F_Cu, pcbnew.B_Cu)
via.SetNet(net)
board.Add(via)
for i in range(1, len(path)):
x0, y0 = path[i-1]; x1, y1 = path[i]
dx = x1 - x0; dy = y1 - y0
d = math.hypot(dx, dy)
# Draw segment
seg = pcbnew.PCB_SHAPE(board)
seg.SetShape(pcbnew.SHAPE_T_SEGMENT)
seg.SetStart(pcbnew.VECTOR2I(int(x0), int(y0)))
seg.SetEnd( pcbnew.VECTOR2I(int(x1), int(y1)))
seg.SetLayer(layer)
seg.SetWidth(thickness)
board.Add(seg)
cumulative += d
while cumulative >= next_via_at:
ratio = (next_via_at - (cumulative - d)) / d
xv = x0 + dx * ratio
yv = y0 + dy * ratio
via = pcbnew.PCB_VIA(board)
via.SetPosition(pcbnew.VECTOR2I(int(xv), int(yv)))
via.SetDrill(drill)
via.SetWidth(diameter)
via.SetLayerPair(pcbnew.F_Cu, pcbnew.B_Cu)
via.SetNet(net)
board.Add(via)
next_via_at += spacing_nm
# Final via if remainder is significant
if cumulative - (next_via_at - spacing_nm) > 0.7 * 1e6:
xf, yf = path[-1]
via = pcbnew.PCB_VIA(board)
via.SetPosition(pcbnew.VECTOR2I(int(xf), int(yf)))
via.SetDrill(drill)
via.SetWidth(diameter)
via.SetLayerPair(pcbnew.F_Cu, pcbnew.B_Cu)
via.SetNet(net)
board.Add(via)
draw_curve_with_vias(TL, spacing_L)
draw_curve_with_vias(TR, spacing_R)
wx.MessageBox("Drew $T$, $T_L$, $T_R$ and placed evenly spaced vias (including start/end).")
pcbnew.Refresh()
ViaFenceGeneratorProper().register()