PageRenderTime 61ms CodeModel.GetById 35ms app.highlight 22ms RepoModel.GetById 0ms app.codeStats 0ms

/viastitching_dialog.py

https://gitlab.com/benjamin.bonnal/viastitching
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