/viastitching_dialog.py
Python | 334 lines | 283 code | 27 blank | 24 comment | 23 complexity | 9b1afa1e1ef123b7a1e9651508f02536 MD5 | raw file
1#!/usr/bin/env python
2
3# ViaStitching for pcbnew
4# This is the plugin WX dialog
5# (c) Michele Santucci 2019
6#
7
8import wx
9import pcbnew
10import gettext
11import math
12
13from .viastitching_gui import viastitching_gui
14from math import sqrt
15
16_ = gettext.gettext
17__version__ = "0.2"
18__timecode__= 1972
19
20class ViaStitchingDialog(viastitching_gui):
21 '''Class that gathers all the Gui controls.'''
22
23 def __init__(self, board):
24 '''Initialize the brand new instance.'''
25
26 super(ViaStitchingDialog, self).__init__(None)
27 self.SetTitle(_(u"ViaStitching v{0}").format(__version__))
28 self.Bind(wx.EVT_CLOSE, self.onCloseWindow)
29 self.m_btnCancel.Bind(wx.EVT_BUTTON, self.onCloseWindow)
30 self.m_btnOk.Bind(wx.EVT_BUTTON, self.onProcessAction)
31 self.m_rClear.Bind(wx.EVT_RADIOBUTTON, self.onRadioButtonCheck)
32 self.m_rFill.Bind(wx.EVT_RADIOBUTTON, self.onRadioButtonCheck)
33 self.board = pcbnew.GetBoard()
34 #Use the same unit set int PCBNEW
35 self.ToUserUnit = None
36 self.FromUserUnit = None
37 units_mode = pcbnew.GetUserUnits()
38
39 if units_mode == 0:
40 self.ToUserUnit = pcbnew.ToMils
41 self.FromUserUnit = pcbnew.FromMils
42 self.m_lblUnit1.SetLabel(_(u"mils"))
43 self.m_lblUnit2.SetLabel(_(u"mils"))
44 self.m_txtVSpacing.SetValue("40")
45 self.m_txtHSpacing.SetValue("40")
46 elif units_mode == 1:
47 self.ToUserUnit = pcbnew.ToMM
48 self.FromUserUnit = pcbnew.FromMM
49 self.m_lblUnit1.SetLabel(_(u"mm"))
50 self.m_lblUnit2.SetLabel(_(u"mm"))
51 self.m_txtVSpacing.SetValue("1")
52 self.m_txtHSpacing.SetValue("1")
53 elif units_mode == -1:
54 wx.MessageBox(_(u"Not a valid frame"))
55 self.Destroy()
56
57 #Get default Vias dimensions
58 via_dim_list = self.board.GetViasDimensionsList()
59 via_dims = via_dim_list.pop()
60 self.m_txtViaSize.SetValue("%.6f" % self.ToUserUnit(via_dims.m_Diameter))
61 self.m_txtViaDrillSize.SetValue("%.6f" % self.ToUserUnit(via_dims.m_Drill))
62 via_dim_list.push_back(via_dims)
63 self.area = None
64 self.net = None
65 self.overlappings = None
66
67 #Check for selected area
68 if not self.GetAreaConfig():
69 wx.MessageBox(_(u"Please select a valid area"))
70 self.Destroy()
71 else:
72 #Get overlapping items
73 self.GetOverlappingItems()
74 #Populate nets checkbox
75 self.PopulateNets()
76
77
78 def GetOverlappingItems(self):
79 """Collect overlapping items.
80 Every item found inside are bounding box is a candidate to be inspected for overlapping.
81 """
82
83 area_bbox = self.area.GetBoundingBox()
84
85 if hasattr(self.board, 'GetModules'):
86 modules = self.board.GetModules()
87 else:
88 modules = self.board.GetFootprints()
89
90 tracks = self.board.GetTracks()
91
92 self.overlappings = []
93
94 for item in tracks:
95 if (type(item) is pcbnew.PCB_VIA) and (item.GetBoundingBox().Intersects(area_bbox)):
96 self.overlappings.append(item)
97
98 for item in modules:
99 if item.GetBoundingBox().Intersects(area_bbox):
100 for pad in item.Pads():
101 self.overlappings.append(pad)
102
103 #TODO: change algorithm to 'If one of the candidate area's edges overlaps with target area declare candidate as overlapping'
104 for i in range(0, self.board.GetAreaCount()):
105 item = self.board.GetArea(i)
106 if item.GetBoundingBox().Intersects(area_bbox):
107 if item.GetNetname() != self.net:
108 self.overlappings.append(item)
109
110
111 def GetAreaConfig(self):
112 """Check selected area (if any) and verify if it is a valid container for vias.
113
114 Returns:
115 bool: Returns True if an area/zone is selected and match implant criteria, False otherwise.
116 """
117
118 for i in range(0, self.board.GetAreaCount()):
119 area = self.board.GetArea(i)
120 if area.IsSelected():
121 if not area.IsOnCopperLayer():
122 return False
123 elif area.GetDoNotAllowCopperPour():
124 return False
125 self.area = area
126 self.net = area.GetNetname()
127 return True
128
129 return False
130
131
132 def PopulateNets(self):
133 '''Populate nets widget.'''
134
135 nets = self.board.GetNetsByName()
136
137 #Tricky loop, the iterator should return two values, unluckly I'm not able to use the
138 #first value of the couple so I'm recycling it as netname.
139 for netname, net in nets.items():
140 netname = net.GetNetname()
141 if (netname != None) and (netname != ""):
142 self.m_cbNet.Append(netname)
143
144 #Select the net used by area (if any)
145 if self.net != None:
146 index = self.m_cbNet.FindString(self.net)
147 self.m_cbNet.Select(index)
148
149
150 def ClearArea(self):
151 '''Clear selected area.'''
152
153 undo = self.m_chkClearOwn.IsChecked()
154 drillsize = self.FromUserUnit(float(self.m_txtViaDrillSize.GetValue()))
155 viasize = self.FromUserUnit(float(self.m_txtViaSize.GetValue()))
156 netname = self.m_cbNet.GetStringSelection()
157 netcode = self.board.GetNetcodeFromNetname(netname)
158 #commit = pcbnew.COMMIT()
159 viacount = 0
160
161 for item in self.board.GetTracks():
162 if type(item) is pcbnew.PCB_VIA:
163 #If the user selected the Undo action only signed vias are removed,
164 #otherwise are removed vias matching values set in the dialog.
165 if undo and (item.GetTimeStamp() == __timecode__):
166 self.board.Remove(item)
167 viacount+=1
168 #commit.Remove(item)
169 elif (not undo) and self.area.HitTestInsideZone(item.GetPosition()) and (item.GetDrillValue() == drillsize) and (item.GetWidth() == viasize) and (item.GetNetname() == netname):
170 self.board.Remove(item)
171 viacount+=1
172 #commit.Remove(item)
173
174 if viacount > 0:
175 wx.MessageBox(_(u"Removed: %d vias!") % viacount)
176 #commit.Push()
177 pcbnew.Refresh()
178
179
180 def CheckClearance(self, p1, area, clearance):
181 """Check if position specified by p1 comply with given clearance in area.
182
183 Parameters:
184 p1 (wxPoint): Position to test
185 area (pcbnew.ZONE_CONTAINER): Area
186 clearance (int): Clearance value
187
188 Returns:
189 bool: True if p1 position comply with clearance value False otherwise.
190
191 """
192
193 corners = area.GetNumCorners()
194 #Calculate minimum distance from corners
195 #TODO: remove?
196 for i in range(0, corners):
197 corner = area.GetCornerPosition(i)
198 p2 = corner.getWxPoint()
199 distance = sqrt((p2.x - p1.x)**2 + (p2.y - p1.y)**2)
200
201 if distance < clearance:
202 return False
203
204 #Calculate minimum distance from edges
205 for i in range(0, corners):
206 if i == corners-1:
207 corner1 = area.GetCornerPosition(corners-1)
208 corner2 = area.GetCornerPosition(0)
209 else:
210 corner1 = area.GetCornerPosition(i)
211 corner2 = area.GetCornerPosition(i+1)
212 pc1 = corner1.getWxPoint()
213 pc2 = corner2.getWxPoint()
214
215 if pc1.x != pc2.x:
216 m = (pc1.y - pc2.y)/(pc1.x - pc2.x)
217 q = pc1.y - (m*pc1.x)
218 distance = math.fabs(p1.y-m*p1.x-q)/math.sqrt(1+m**2)
219 else:
220 distance = math.fabs(pc1.x - p1.x)
221
222 if distance < clearance:
223 return False
224
225 return True
226
227
228 def CheckOverlap(self, via):
229 """Check if via overlaps or interfere with other items on the board.
230
231 Parameters:
232 via (pcbnew.VIA): Via to be checked
233
234 Returns:
235 bool: True if via overlaps with an item, False otherwise.
236 """
237
238 for item in self.overlappings:
239 if type(item) is pcbnew.D_PAD:
240 if item.GetBoundingBox().Intersects( via.GetBoundingBox() ):
241 return True
242 elif type(item) is pcbnew.PCB_VIA:
243 #Overlapping with vias work best if checking is performed by intersection
244 if item.GetBoundingBox().Intersects( via.GetBoundingBox() ):
245 return True
246 elif type(item) is pcbnew.ZONE_CONTAINER:
247 if item.HitTestInsideZone( via.GetPosition() ):
248 return True
249
250 return False
251
252
253 def FillupArea(self):
254 '''Fills selected area with vias.'''
255
256 drillsize = self.FromUserUnit(float(self.m_txtViaDrillSize.GetValue()))
257 viasize = self.FromUserUnit(float(self.m_txtViaSize.GetValue()))
258 step_x = self.FromUserUnit(float(self.m_txtHSpacing.GetValue()))
259 step_y = self.FromUserUnit(float(self.m_txtVSpacing.GetValue()))
260 clearance = self.FromUserUnit(float(self.m_txtClearance.GetValue()))
261 bbox = self.area.GetBoundingBox()
262 top = bbox.GetTop()
263 bottom = bbox.GetBottom()
264 right = bbox.GetRight()
265 left = bbox.GetLeft()
266 netname = self.m_cbNet.GetStringSelection()
267 netcode = self.board.GetNetcodeFromNetname(netname)
268 #commit = pcbnew.COMMIT()
269 viacount = 0
270 x = left
271
272 #Cycle trough area bounding box checking and implanting vias
273 layer = self.area.GetLayer()
274 while x <= right:
275 y = top
276 while y <= bottom:
277 p = pcbnew.wxPoint(x,y)
278 if self.area.HitTestInsideZone(p):
279 via = pcbnew.VIA(self.board)
280 via.SetPosition(p)
281 via.SetLayer(layer)
282 via.SetNetCode(netcode)
283 via.SetDrill(drillsize)
284 via.SetWidth(viasize)
285 via.SetTimeStamp(__timecode__)
286 if not self.CheckOverlap(via):
287 #Check clearance only if clearance value differs from 0 (disabled)
288 if (clearance == 0) or self.CheckClearance(p, self.area, clearance):
289 self.board.Add(via)
290 #commit.Add(via)
291 viacount +=1
292 y += step_y
293 x += step_x
294
295 if viacount > 0:
296 wx.MessageBox(_(u"Implanted: %d vias!") % viacount)
297 #commit.Push()
298 pcbnew.Refresh()
299 else:
300 wx.MessageBox(_(u"No vias implanted!"))
301
302
303 def onProcessAction(self, event):
304 '''Manage main button (Ok) click event.'''
305
306 if(self.m_rFill.GetValue()):
307 self.FillupArea()
308 else:
309 self.ClearArea()
310
311 self.Destroy()
312
313
314 def onRadioButtonCheck(self, event):
315 '''Manage radio button state change event.'''
316
317 if self.m_rClear.GetValue():
318 self.m_chkClearOwn.Enable()
319 else:
320 self.m_chkClearOwn.Disable()
321
322
323 def onCloseWindow(self, event):
324 '''Manage Close button click event.'''
325
326 self.Destroy()
327
328
329def InitViaStitchingDialog(board):
330 '''Initalize dialog.'''
331
332 dlg = ViaStitchingDialog(board)
333 dlg.Show(True)
334 return dlg