Add Via Net Assign feature
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>
This commit is contained in:
@@ -2,6 +2,8 @@ import pcbnew
|
||||
import wx
|
||||
import traceback
|
||||
|
||||
from .via_net_assign import Via_Net_Assign_Action
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Board helpers
|
||||
@@ -241,3 +243,4 @@ class Layout_Replicator_Action(pcbnew.ActionPlugin):
|
||||
dlg.Destroy()
|
||||
|
||||
Layout_Replicator_Action().register()
|
||||
Via_Net_Assign_Action().register()
|
||||
|
||||
121
plugins/layout_replicator/via_net_assign.py
Normal file
121
plugins/layout_replicator/via_net_assign.py
Normal file
@@ -0,0 +1,121 @@
|
||||
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()
|
||||
Reference in New Issue
Block a user