ToolPunchGerber.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  1. # ##########################################################
  2. # FlatCAM: 2D Post-processing for Manufacturing #
  3. # File Author: Marius Adrian Stanciu (c) #
  4. # Date: 1/24/2020 #
  5. # MIT Licence #
  6. # ##########################################################
  7. from PyQt5 import QtGui, QtCore, QtWidgets
  8. from FlatCAMTool import FlatCAMTool
  9. from flatcamGUI.GUIElements import RadioSet, FCDoubleSpinner, FCCheckBox, \
  10. OptionalHideInputSection, OptionalInputSection, FCComboBox
  11. from copy import deepcopy
  12. import logging
  13. from shapely.geometry import Polygon, MultiPolygon, Point
  14. from reportlab.graphics import renderPDF
  15. from reportlab.pdfgen import canvas
  16. from reportlab.graphics import renderPM
  17. from reportlab.lib.units import inch, mm
  18. from reportlab.lib.pagesizes import landscape, portrait
  19. from svglib.svglib import svg2rlg
  20. from xml.dom.minidom import parseString as parse_xml_string
  21. from lxml import etree as ET
  22. from io import StringIO
  23. import gettext
  24. import FlatCAMTranslation as fcTranslate
  25. import builtins
  26. fcTranslate.apply_language('strings')
  27. if '_' not in builtins.__dict__:
  28. _ = gettext.gettext
  29. log = logging.getLogger('base')
  30. class ToolPunchGerber(FlatCAMTool):
  31. toolName = _("Punch Gerber")
  32. def __init__(self, app):
  33. FlatCAMTool.__init__(self, app)
  34. self.decimals = self.app.decimals
  35. # Title
  36. title_label = QtWidgets.QLabel("%s" % self.toolName)
  37. title_label.setStyleSheet("""
  38. QLabel
  39. {
  40. font-size: 16px;
  41. font-weight: bold;
  42. }
  43. """)
  44. self.layout.addWidget(title_label)
  45. # Punch Drill holes
  46. self.layout.addWidget(QtWidgets.QLabel(""))
  47. # ## Grid Layout
  48. grid_lay = QtWidgets.QGridLayout()
  49. self.layout.addLayout(grid_lay)
  50. grid_lay.setColumnStretch(0, 1)
  51. grid_lay.setColumnStretch(1, 0)
  52. # ## Gerber Object
  53. self.gerber_object_combo = QtWidgets.QComboBox()
  54. self.gerber_object_combo.setModel(self.app.collection)
  55. self.gerber_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
  56. self.gerber_object_combo.setCurrentIndex(1)
  57. self.grb_label = QtWidgets.QLabel("<b>%s:</b>" % _("GERBER"))
  58. self.grb_label.setToolTip('%s.' % _("Gerber into which to punch holes"))
  59. grid_lay.addWidget(self.grb_label, 0, 0, 1, 2)
  60. grid_lay.addWidget(self.gerber_object_combo, 1, 0, 1, 2)
  61. separator_line = QtWidgets.QFrame()
  62. separator_line.setFrameShape(QtWidgets.QFrame.HLine)
  63. separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
  64. grid_lay.addWidget(separator_line, 2, 0, 1, 2)
  65. # Grid Layout
  66. grid0 = QtWidgets.QGridLayout()
  67. self.layout.addLayout(grid0)
  68. grid0.setColumnStretch(0, 0)
  69. grid0.setColumnStretch(1, 1)
  70. self.method_label = QtWidgets.QLabel('%s:' % _("Method"))
  71. self.method_label.setToolTip(
  72. _("The punch hole source can be:\n"
  73. "- Excellon -> an Excellon holes center will serve as reference.\n"
  74. "- Fixed Diameter -> will try to use the pads center as reference.\n"
  75. "- Fixed Annular Ring -> will try to use the pads center as reference.\n"
  76. "- Proportional -> will try to use the pads center as reference.\n")
  77. )
  78. self.method_punch = RadioSet(
  79. [
  80. {'label': _('Excellon'), 'value': 'exc'},
  81. {'label': _("Fixed Diameter"), 'value': 'fixed'},
  82. {'label': _("Fixed Annular Ring"), 'value': 'ring'},
  83. {'label': _("Proportional"), 'value': 'prop'}
  84. ],
  85. orientation='vertical',
  86. stretch=False)
  87. grid0.addWidget(self.method_label, 0, 0)
  88. grid0.addWidget(self.method_punch, 0, 1)
  89. separator_line = QtWidgets.QFrame()
  90. separator_line.setFrameShape(QtWidgets.QFrame.HLine)
  91. separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
  92. grid0.addWidget(separator_line, 1, 0, 1, 2)
  93. self.exc_label = QtWidgets.QLabel('<b>%s</b>' % _("Excellon"))
  94. self.exc_label.setToolTip(
  95. _("Remove the geometry of Excellon from the Gerber to create the holes in pads.")
  96. )
  97. self.exc_combo = QtWidgets.QComboBox()
  98. self.exc_combo.setModel(self.app.collection)
  99. self.exc_combo.setRootModelIndex(self.app.collection.index(1, 0, QtCore.QModelIndex()))
  100. self.exc_combo.setCurrentIndex(1)
  101. grid0.addWidget(self.exc_label, 2, 0, 1, 2)
  102. grid0.addWidget(self.exc_combo, 3, 0, 1, 2)
  103. separator_line = QtWidgets.QFrame()
  104. separator_line.setFrameShape(QtWidgets.QFrame.HLine)
  105. separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
  106. grid0.addWidget(separator_line, 5, 0, 1, 2)
  107. # Fixed Dia
  108. self.fixed_label = QtWidgets.QLabel('<b>%s</b>' % _("Fixed Diameter"))
  109. grid0.addWidget(self.fixed_label, 6, 0, 1, 2)
  110. # Diameter value
  111. self.dia_entry = FCDoubleSpinner()
  112. self.dia_entry.set_precision(self.decimals)
  113. self.dia_entry.set_range(0.0000, 9999.9999)
  114. self.dia_label = QtWidgets.QLabel('%s:' % _("Value"))
  115. self.dia_label.setToolTip(
  116. _("Fixed hole diameter.")
  117. )
  118. grid0.addWidget(self.dia_label, 8, 0)
  119. grid0.addWidget(self.dia_entry, 8, 1)
  120. separator_line = QtWidgets.QFrame()
  121. separator_line.setFrameShape(QtWidgets.QFrame.HLine)
  122. separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
  123. grid0.addWidget(separator_line, 9, 0, 1, 2)
  124. self.ring_frame = QtWidgets.QFrame()
  125. self.ring_frame.setContentsMargins(0, 0, 0, 0)
  126. grid0.addWidget(self.ring_frame, 10, 0, 1, 2)
  127. self.ring_box = QtWidgets.QVBoxLayout()
  128. self.ring_box.setContentsMargins(0, 0, 0, 0)
  129. self.ring_frame.setLayout(self.ring_box)
  130. # ## Grid Layout
  131. grid1 = QtWidgets.QGridLayout()
  132. grid1.setColumnStretch(0, 0)
  133. grid1.setColumnStretch(1, 1)
  134. self.ring_box.addLayout(grid1)
  135. # Annular Ring value
  136. self.ring_label = QtWidgets.QLabel('<b>%s</b>' % _("Fixed Annular Ring"))
  137. self.ring_label.setToolTip(
  138. _("The size of annular ring.\n"
  139. "The copper sliver between the drill hole exterior\n"
  140. "and the margin of the copper pad.")
  141. )
  142. grid1.addWidget(self.ring_label, 0, 0, 1, 2)
  143. # Circular Annular Ring Value
  144. self.circular_ring_label = QtWidgets.QLabel('%s:' % _("Circular"))
  145. self.circular_ring_label.setToolTip(
  146. _("The size of annular ring for circular pads.")
  147. )
  148. self.circular_ring_entry = FCDoubleSpinner()
  149. self.circular_ring_entry.set_precision(self.decimals)
  150. self.circular_ring_entry.set_range(0.0000, 9999.9999)
  151. grid1.addWidget(self.circular_ring_label, 1, 0)
  152. grid1.addWidget(self.circular_ring_entry, 1, 1)
  153. # Oblong Annular Ring Value
  154. self.oblong_ring_label = QtWidgets.QLabel('%s:' % _("Oblong"))
  155. self.oblong_ring_label.setToolTip(
  156. _("The size of annular ring for oblong pads.")
  157. )
  158. self.oblong_ring_entry = FCDoubleSpinner()
  159. self.oblong_ring_entry.set_precision(self.decimals)
  160. self.oblong_ring_entry.set_range(0.0000, 9999.9999)
  161. grid1.addWidget(self.oblong_ring_label, 2, 0)
  162. grid1.addWidget(self.oblong_ring_entry, 2, 1)
  163. # Square Annular Ring Value
  164. self.square_ring_label = QtWidgets.QLabel('%s:' % _("Square"))
  165. self.square_ring_label.setToolTip(
  166. _("The size of annular ring for square pads.")
  167. )
  168. self.square_ring_entry = FCDoubleSpinner()
  169. self.square_ring_entry.set_precision(self.decimals)
  170. self.square_ring_entry.set_range(0.0000, 9999.9999)
  171. grid1.addWidget(self.square_ring_label, 3, 0)
  172. grid1.addWidget(self.square_ring_entry, 3, 1)
  173. # Rectangular Annular Ring Value
  174. self.rectangular_ring_label = QtWidgets.QLabel('%s:' % _("Rectangular"))
  175. self.rectangular_ring_label.setToolTip(
  176. _("The size of annular ring for rectangular pads.")
  177. )
  178. self.rectangular_ring_entry = FCDoubleSpinner()
  179. self.rectangular_ring_entry.set_precision(self.decimals)
  180. self.rectangular_ring_entry.set_range(0.0000, 9999.9999)
  181. grid1.addWidget(self.rectangular_ring_label, 4, 0)
  182. grid1.addWidget(self.rectangular_ring_entry, 4, 1)
  183. # Others Annular Ring Value
  184. self.other_ring_label = QtWidgets.QLabel('%s:' % _("Others"))
  185. self.other_ring_label.setToolTip(
  186. _("The size of annular ring for other pads.")
  187. )
  188. self.other_ring_entry = FCDoubleSpinner()
  189. self.other_ring_entry.set_precision(self.decimals)
  190. self.other_ring_entry.set_range(0.0000, 9999.9999)
  191. grid1.addWidget(self.other_ring_label, 5, 0)
  192. grid1.addWidget(self.other_ring_entry, 5, 1)
  193. separator_line = QtWidgets.QFrame()
  194. separator_line.setFrameShape(QtWidgets.QFrame.HLine)
  195. separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
  196. grid0.addWidget(separator_line, 11, 0, 1, 2)
  197. # Proportional value
  198. self.prop_label = QtWidgets.QLabel('<b>%s</b>' % _("Proportional Diameter"))
  199. grid0.addWidget(self.prop_label, 12, 0, 1, 2)
  200. # Diameter value
  201. self.factor_entry = FCDoubleSpinner(suffix='%')
  202. self.factor_entry.set_precision(self.decimals)
  203. self.factor_entry.set_range(0.0000, 100.0000)
  204. self.factor_entry.setSingleStep(0.1)
  205. self.factor_label = QtWidgets.QLabel('%s:' % _("Value"))
  206. self.factor_label.setToolTip(
  207. _("Proportional Diameter.\n"
  208. "The drill diameter will be a fraction of the pad size.")
  209. )
  210. grid0.addWidget(self.factor_label, 13, 0)
  211. grid0.addWidget(self.factor_entry, 13, 1)
  212. separator_line3 = QtWidgets.QFrame()
  213. separator_line3.setFrameShape(QtWidgets.QFrame.HLine)
  214. separator_line3.setFrameShadow(QtWidgets.QFrame.Sunken)
  215. grid0.addWidget(separator_line3, 14, 0, 1, 2)
  216. # Buttons
  217. self.punch_object_button = QtWidgets.QPushButton(_("Punch Gerber"))
  218. self.punch_object_button.setToolTip(
  219. _("Create a Gerber object from the selected object, within\n"
  220. "the specified box.")
  221. )
  222. self.punch_object_button.setStyleSheet("""
  223. QPushButton
  224. {
  225. font-weight: bold;
  226. }
  227. """)
  228. self.layout.addWidget(self.punch_object_button)
  229. self.layout.addStretch()
  230. # ## Reset Tool
  231. self.reset_button = QtWidgets.QPushButton(_("Reset Tool"))
  232. self.reset_button.setToolTip(
  233. _("Will reset the tool parameters.")
  234. )
  235. self.reset_button.setStyleSheet("""
  236. QPushButton
  237. {
  238. font-weight: bold;
  239. }
  240. """)
  241. self.layout.addWidget(self.reset_button)
  242. self.units = self.app.defaults['units']
  243. # ## Signals
  244. self.method_punch.activated_custom.connect(self.on_method)
  245. self.reset_button.clicked.connect(self.set_tool_ui)
  246. def run(self, toggle=True):
  247. self.app.report_usage("ToolPunchGerber()")
  248. if toggle:
  249. # if the splitter is hidden, display it, else hide it but only if the current widget is the same
  250. if self.app.ui.splitter.sizes()[0] == 0:
  251. self.app.ui.splitter.setSizes([1, 1])
  252. else:
  253. try:
  254. if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
  255. # if tab is populated with the tool but it does not have the focus, focus on it
  256. if not self.app.ui.notebook.currentWidget() is self.app.ui.tool_tab:
  257. # focus on Tool Tab
  258. self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
  259. else:
  260. self.app.ui.splitter.setSizes([0, 1])
  261. except AttributeError:
  262. pass
  263. else:
  264. if self.app.ui.splitter.sizes()[0] == 0:
  265. self.app.ui.splitter.setSizes([1, 1])
  266. FlatCAMTool.run(self)
  267. self.set_tool_ui()
  268. self.app.ui.notebook.setTabText(2, _("Punch Tool"))
  269. def install(self, icon=None, separator=None, **kwargs):
  270. FlatCAMTool.install(self, icon, separator, shortcut='ALT+H', **kwargs)
  271. def set_tool_ui(self):
  272. self.reset_fields()
  273. self.method_punch.set_value('exc')
  274. def on_method(self, val):
  275. self.exc_label.setEnabled(False)
  276. self.exc_combo.setEnabled(False)
  277. self.fixed_label.setEnabled(False)
  278. self.dia_label.setEnabled(False)
  279. self.dia_entry.setEnabled(False)
  280. self.ring_frame.setEnabled(False)
  281. self.prop_label.setEnabled(False)
  282. self.factor_label.setEnabled(False)
  283. self.factor_entry.setEnabled(False)
  284. if val == 'exc':
  285. self.exc_label.setEnabled(True)
  286. self.exc_combo.setEnabled(True)
  287. elif val == 'fixed':
  288. self.fixed_label.setEnabled(True)
  289. self.dia_label.setEnabled(True)
  290. self.dia_entry.setEnabled(True)
  291. elif val == 'ring':
  292. self.ring_frame.setEnabled(True)
  293. elif val == 'prop':
  294. self.prop_label.setEnabled(True)
  295. self.factor_label.setEnabled(True)
  296. self.factor_entry.setEnabled(True)
  297. def reset_fields(self):
  298. self.gerber_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
  299. self.exc_combo.setRootModelIndex(self.app.collection.index(1, 0, QtCore.QModelIndex()))