Iterates all vias in the active selection and assigns each one to the net of the nearest copper item (track or pad) on the board. Intended for re-using via layouts after copying traces to a new net. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
122 lines
3.5 KiB
Python
122 lines
3.5 KiB
Python
import pcbnew
|
|
import wx
|
|
import math
|
|
import traceback
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Geometry
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def _point_to_segment_dist(px, py, ax, ay, bx, by):
|
|
dx, dy = bx - ax, by - ay
|
|
if dx == 0 and dy == 0:
|
|
return math.hypot(px - ax, py - ay)
|
|
t = max(0.0, min(1.0, ((px - ax) * dx + (py - ay) * dy) / (dx * dx + dy * dy)))
|
|
return math.hypot(px - (ax + t * dx), py - (ay + t * dy))
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Logic
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def _build_candidates(board):
|
|
"""Collect all non-via tracks and pads that have a real net."""
|
|
candidates = []
|
|
|
|
for track in board.GetTracks():
|
|
if track.GetClass() == 'PCB_VIA':
|
|
continue
|
|
net = track.GetNet()
|
|
if net and net.GetNetCode() != 0:
|
|
candidates.append(('track', track, net))
|
|
|
|
for pad in board.GetPads():
|
|
net = pad.GetNet()
|
|
if net and net.GetNetCode() != 0:
|
|
candidates.append(('pad', pad, net))
|
|
|
|
return candidates
|
|
|
|
|
|
def _nearest_net(via, candidates):
|
|
vx = via.GetX()
|
|
vy = via.GetY()
|
|
vr = via.GetWidth() / 2
|
|
|
|
best_net = None
|
|
best_dist = None
|
|
|
|
for kind, item, net in candidates:
|
|
if kind == 'track':
|
|
ax, ay = item.GetStart().x, item.GetStart().y
|
|
bx, by = item.GetEnd().x, item.GetEnd().y
|
|
hw = item.GetWidth() / 2
|
|
dist = max(0.0, _point_to_segment_dist(vx, vy, ax, ay, bx, by) - hw - vr)
|
|
else: # pad
|
|
dist = max(0.0, math.hypot(vx - item.GetX(), vy - item.GetY()) - vr)
|
|
|
|
if best_dist is None or dist < best_dist:
|
|
best_dist = dist
|
|
best_net = net
|
|
|
|
return best_net, best_dist
|
|
|
|
|
|
def assign_via_nets(board):
|
|
vias = [t for t in board.GetTracks()
|
|
if t.GetClass() == 'PCB_VIA' and t.IsSelected()]
|
|
|
|
if not vias:
|
|
return 0, 'No vias in selection.'
|
|
|
|
candidates = _build_candidates(board)
|
|
if not candidates:
|
|
return 0, 'No routed copper found on board.'
|
|
|
|
count = 0
|
|
for via in vias:
|
|
net, _ = _nearest_net(via, candidates)
|
|
if net is not None:
|
|
via.SetNet(net)
|
|
count += 1
|
|
|
|
pcbnew.Refresh()
|
|
return count, None
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Plugin
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class Via_Net_Assign_Action(pcbnew.ActionPlugin):
|
|
def defaults(self):
|
|
self.name = 'Assign Via Nets from Nearest Copper'
|
|
self.category = 'Placement'
|
|
self.description = 'Update net assignment of all selected vias to match the nearest copper item'
|
|
self.show_toolbar_button = True
|
|
self.icon_file_name = ''
|
|
|
|
def Run(self):
|
|
try:
|
|
board = pcbnew.GetBoard()
|
|
count, err = assign_via_nets(board)
|
|
if err:
|
|
wx.MessageBox(err, 'Via Net Assign', wx.OK | wx.ICON_INFORMATION)
|
|
else:
|
|
wx.MessageBox(f'{count} via(s) updated.', 'Via Net Assign', wx.OK | wx.ICON_INFORMATION)
|
|
except Exception:
|
|
msg = traceback.format_exc()
|
|
dlg = wx.Dialog(None, title='Via Net Assign — Error', size=(520, 320),
|
|
style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
|
|
s = wx.BoxSizer(wx.VERTICAL)
|
|
txt = wx.TextCtrl(dlg, value=msg, style=wx.TE_MULTILINE | wx.TE_READONLY | wx.HSCROLL)
|
|
txt.SetFont(wx.Font(9, wx.FONTFAMILY_TELETYPE, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL))
|
|
s.Add(txt, 1, wx.EXPAND | wx.ALL, 6)
|
|
btn = wx.Button(dlg, wx.ID_CLOSE, 'Close')
|
|
btn.Bind(wx.EVT_BUTTON, lambda e: dlg.Close())
|
|
s.Add(btn, 0, wx.ALIGN_RIGHT | wx.ALL, 6)
|
|
dlg.SetSizer(s)
|
|
dlg.ShowModal()
|
|
dlg.Destroy()
|