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