ToolPunchGerber.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500
  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. self.padt_label = QtWidgets.QLabel("<b>%s</b>" % _("Processed Pads Type"))
  66. self.padt_label.setToolTip(
  67. _("The type of pads shape to be processed.\n"
  68. "If the PCB has many SMD pads with rectangular pads,\n"
  69. "disable the Rectangular aperture.")
  70. )
  71. grid_lay.addWidget(self.padt_label, 3, 0, 1, 2)
  72. # Select all
  73. self.select_all_cb = FCCheckBox('%s' % _("ALL"))
  74. grid_lay.addWidget(self.select_all_cb)
  75. # Circular Aperture Selection
  76. self.circular_cb = FCCheckBox('%s' % _("Circular"))
  77. self.circular_cb.setToolTip(
  78. _("Create drills from circular pads.")
  79. )
  80. grid_lay.addWidget(self.circular_cb, 5, 0, 1, 2)
  81. # Oblong Aperture Selection
  82. self.oblong_cb = FCCheckBox('%s' % _("Oblong"))
  83. self.oblong_cb.setToolTip(
  84. _("Create drills from oblong pads.")
  85. )
  86. grid_lay.addWidget(self.oblong_cb, 6, 0, 1, 2)
  87. # Square Aperture Selection
  88. self.square_cb = FCCheckBox('%s' % _("Square"))
  89. self.square_cb.setToolTip(
  90. _("Create drills from square pads.")
  91. )
  92. grid_lay.addWidget(self.square_cb, 7, 0, 1, 2)
  93. # Rectangular Aperture Selection
  94. self.rectangular_cb = FCCheckBox('%s' % _("Rectangular"))
  95. self.rectangular_cb.setToolTip(
  96. _("Create drills from rectangular pads.")
  97. )
  98. grid_lay.addWidget(self.rectangular_cb, 8, 0, 1, 2)
  99. # Others type of Apertures Selection
  100. self.other_cb = FCCheckBox('%s' % _("Others"))
  101. self.other_cb.setToolTip(
  102. _("Create drills from other types of pad shape.")
  103. )
  104. grid_lay.addWidget(self.other_cb, 9, 0, 1, 2)
  105. separator_line = QtWidgets.QFrame()
  106. separator_line.setFrameShape(QtWidgets.QFrame.HLine)
  107. separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
  108. grid_lay.addWidget(separator_line, 10, 0, 1, 2)
  109. # Grid Layout
  110. grid0 = QtWidgets.QGridLayout()
  111. self.layout.addLayout(grid0)
  112. grid0.setColumnStretch(0, 0)
  113. grid0.setColumnStretch(1, 1)
  114. self.method_label = QtWidgets.QLabel('<b>%s:</b>' % _("Method"))
  115. self.method_label.setToolTip(
  116. _("The punch hole source can be:\n"
  117. "- Excellon Object-> the Excellon object drills center will serve as reference.\n"
  118. "- Fixed Diameter -> will try to use the pads center as reference adding fixed diameter holes.\n"
  119. "- Fixed Annular Ring -> will try to keep a set annular ring.\n"
  120. "- Proportional -> will make a Gerber punch hole having the diameter a percentage of the pad diameter.\n")
  121. )
  122. self.method_punch = RadioSet(
  123. [
  124. {'label': _('Excellon'), 'value': 'exc'},
  125. {'label': _("Fixed Diameter"), 'value': 'fixed'},
  126. {'label': _("Fixed Annular Ring"), 'value': 'ring'},
  127. {'label': _("Proportional"), 'value': 'prop'}
  128. ],
  129. orientation='vertical',
  130. stretch=False)
  131. grid0.addWidget(self.method_label, 0, 0, 1, 2)
  132. grid0.addWidget(self.method_punch, 1, 0, 1, 2)
  133. separator_line = QtWidgets.QFrame()
  134. separator_line.setFrameShape(QtWidgets.QFrame.HLine)
  135. separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
  136. grid0.addWidget(separator_line, 2, 0, 1, 2)
  137. self.exc_label = QtWidgets.QLabel('<b>%s</b>' % _("Excellon"))
  138. self.exc_label.setToolTip(
  139. _("Remove the geometry of Excellon from the Gerber to create the holes in pads.")
  140. )
  141. self.exc_combo = QtWidgets.QComboBox()
  142. self.exc_combo.setModel(self.app.collection)
  143. self.exc_combo.setRootModelIndex(self.app.collection.index(1, 0, QtCore.QModelIndex()))
  144. self.exc_combo.setCurrentIndex(1)
  145. grid0.addWidget(self.exc_label, 3, 0, 1, 2)
  146. grid0.addWidget(self.exc_combo, 4, 0, 1, 2)
  147. separator_line = QtWidgets.QFrame()
  148. separator_line.setFrameShape(QtWidgets.QFrame.HLine)
  149. separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
  150. grid0.addWidget(separator_line, 5, 0, 1, 2)
  151. # Fixed Dia
  152. self.fixed_label = QtWidgets.QLabel('<b>%s</b>' % _("Fixed Diameter"))
  153. grid0.addWidget(self.fixed_label, 6, 0, 1, 2)
  154. # Diameter value
  155. self.dia_entry = FCDoubleSpinner()
  156. self.dia_entry.set_precision(self.decimals)
  157. self.dia_entry.set_range(0.0000, 9999.9999)
  158. self.dia_label = QtWidgets.QLabel('%s:' % _("Value"))
  159. self.dia_label.setToolTip(
  160. _("Fixed hole diameter.")
  161. )
  162. grid0.addWidget(self.dia_label, 8, 0)
  163. grid0.addWidget(self.dia_entry, 8, 1)
  164. separator_line = QtWidgets.QFrame()
  165. separator_line.setFrameShape(QtWidgets.QFrame.HLine)
  166. separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
  167. grid0.addWidget(separator_line, 9, 0, 1, 2)
  168. self.ring_frame = QtWidgets.QFrame()
  169. self.ring_frame.setContentsMargins(0, 0, 0, 0)
  170. grid0.addWidget(self.ring_frame, 10, 0, 1, 2)
  171. self.ring_box = QtWidgets.QVBoxLayout()
  172. self.ring_box.setContentsMargins(0, 0, 0, 0)
  173. self.ring_frame.setLayout(self.ring_box)
  174. # Annular Ring value
  175. self.ring_label = QtWidgets.QLabel('<b>%s</b>' % _("Fixed Annular Ring"))
  176. self.ring_label.setToolTip(
  177. _("The size of annular ring.\n"
  178. "The copper sliver between the drill hole exterior\n"
  179. "and the margin of the copper pad.")
  180. )
  181. self.ring_box.addWidget(self.ring_label)
  182. # ## Grid Layout
  183. self.grid1 = QtWidgets.QGridLayout()
  184. self.grid1.setColumnStretch(0, 0)
  185. self.grid1.setColumnStretch(1, 1)
  186. self.ring_box.addLayout(self.grid1)
  187. # Circular Annular Ring Value
  188. self.circular_ring_label = QtWidgets.QLabel('%s:' % _("Circular"))
  189. self.circular_ring_label.setToolTip(
  190. _("The size of annular ring for circular pads.")
  191. )
  192. self.circular_ring_entry = FCDoubleSpinner()
  193. self.circular_ring_entry.set_precision(self.decimals)
  194. self.circular_ring_entry.set_range(0.0000, 9999.9999)
  195. self.grid1.addWidget(self.circular_ring_label, 3, 0)
  196. self.grid1.addWidget(self.circular_ring_entry, 3, 1)
  197. # Oblong Annular Ring Value
  198. self.oblong_ring_label= QtWidgets.QLabel('%s:' % _("Oblong"))
  199. self.oblong_ring_label.setToolTip(
  200. _("The size of annular ring for oblong pads.")
  201. )
  202. self.oblong_ring_entry = FCDoubleSpinner()
  203. self.oblong_ring_entry.set_precision(self.decimals)
  204. self.oblong_ring_entry.set_range(0.0000, 9999.9999)
  205. self.grid1.addWidget(self.oblong_ring_label, 4, 0)
  206. self.grid1.addWidget(self.oblong_ring_entry, 4, 1)
  207. # Square Annular Ring Value
  208. self.square_ring_label = QtWidgets.QLabel('%s:' % _("Square"))
  209. self.square_ring_label.setToolTip(
  210. _("The size of annular ring for square pads.")
  211. )
  212. self.square_ring_entry = FCDoubleSpinner()
  213. self.square_ring_entry.set_precision(self.decimals)
  214. self.square_ring_entry.set_range(0.0000, 9999.9999)
  215. self.grid1.addWidget(self.square_ring_label, 5, 0)
  216. self.grid1.addWidget(self.square_ring_entry, 5, 1)
  217. # Rectangular Annular Ring Value
  218. self.rectangular_ring_label = QtWidgets.QLabel('%s:' % _("Rectangular"))
  219. self.rectangular_ring_label.setToolTip(
  220. _("The size of annular ring for rectangular pads.")
  221. )
  222. self.rectangular_ring_entry = FCDoubleSpinner()
  223. self.rectangular_ring_entry.set_precision(self.decimals)
  224. self.rectangular_ring_entry.set_range(0.0000, 9999.9999)
  225. self.grid1.addWidget(self.rectangular_ring_label, 6, 0)
  226. self.grid1.addWidget(self.rectangular_ring_entry, 6, 1)
  227. # Others Annular Ring Value
  228. self.other_ring_label = QtWidgets.QLabel('%s:' % _("Others"))
  229. self.other_ring_label.setToolTip(
  230. _("The size of annular ring for other pads.")
  231. )
  232. self.other_ring_entry = FCDoubleSpinner()
  233. self.other_ring_entry.set_precision(self.decimals)
  234. self.other_ring_entry.set_range(0.0000, 9999.9999)
  235. self.grid1.addWidget(self.other_ring_label, 7, 0)
  236. self.grid1.addWidget(self.other_ring_entry, 7, 1)
  237. separator_line = QtWidgets.QFrame()
  238. separator_line.setFrameShape(QtWidgets.QFrame.HLine)
  239. separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
  240. grid0.addWidget(separator_line, 11, 0, 1, 2)
  241. # Proportional value
  242. self.prop_label = QtWidgets.QLabel('<b>%s</b>' % _("Proportional Diameter"))
  243. grid0.addWidget(self.prop_label, 12, 0, 1, 2)
  244. # Diameter value
  245. self.factor_entry = FCDoubleSpinner(suffix='%')
  246. self.factor_entry.set_precision(self.decimals)
  247. self.factor_entry.set_range(0.0000, 100.0000)
  248. self.factor_entry.setSingleStep(0.1)
  249. self.factor_label = QtWidgets.QLabel('%s:' % _("Value"))
  250. self.factor_label.setToolTip(
  251. _("Proportional Diameter.\n"
  252. "The drill diameter will be a fraction of the pad size.")
  253. )
  254. grid0.addWidget(self.factor_label, 13, 0)
  255. grid0.addWidget(self.factor_entry, 13, 1)
  256. separator_line3 = QtWidgets.QFrame()
  257. separator_line3.setFrameShape(QtWidgets.QFrame.HLine)
  258. separator_line3.setFrameShadow(QtWidgets.QFrame.Sunken)
  259. grid0.addWidget(separator_line3, 14, 0, 1, 2)
  260. # Buttons
  261. self.punch_object_button = QtWidgets.QPushButton(_("Punch Gerber"))
  262. self.punch_object_button.setToolTip(
  263. _("Create a Gerber object from the selected object, within\n"
  264. "the specified box.")
  265. )
  266. self.punch_object_button.setStyleSheet("""
  267. QPushButton
  268. {
  269. font-weight: bold;
  270. }
  271. """)
  272. self.layout.addWidget(self.punch_object_button)
  273. self.layout.addStretch()
  274. # ## Reset Tool
  275. self.reset_button = QtWidgets.QPushButton(_("Reset Tool"))
  276. self.reset_button.setToolTip(
  277. _("Will reset the tool parameters.")
  278. )
  279. self.reset_button.setStyleSheet("""
  280. QPushButton
  281. {
  282. font-weight: bold;
  283. }
  284. """)
  285. self.layout.addWidget(self.reset_button)
  286. self.units = self.app.defaults['units']
  287. # self.cb_items = [
  288. # self.grid1.itemAt(w).widget() for w in range(self.grid1.count())
  289. # if isinstance(self.grid1.itemAt(w).widget(), FCCheckBox)
  290. # ]
  291. self.circular_ring_entry.setEnabled(False)
  292. self.oblong_ring_entry.setEnabled(False)
  293. self.square_ring_entry.setEnabled(False)
  294. self.rectangular_ring_entry.setEnabled(False)
  295. self.other_ring_entry.setEnabled(False)
  296. self.dia_entry.setDisabled(True)
  297. self.dia_label.setDisabled(True)
  298. self.factor_label.setDisabled(True)
  299. self.factor_entry.setDisabled(True)
  300. # ## Signals
  301. self.method_punch.activated_custom.connect(self.on_method)
  302. self.reset_button.clicked.connect(self.set_tool_ui)
  303. self.circular_cb.stateChanged.connect(
  304. lambda state:
  305. self.circular_ring_entry.setDisabled(False) if state else self.circular_ring_entry.setDisabled(True)
  306. )
  307. self.oblong_cb.stateChanged.connect(
  308. lambda state:
  309. self.oblong_ring_entry.setDisabled(False) if state else self.oblong_ring_entry.setDisabled(True)
  310. )
  311. self.square_cb.stateChanged.connect(
  312. lambda state:
  313. self.square_ring_entry.setDisabled(False) if state else self.square_ring_entry.setDisabled(True)
  314. )
  315. self.rectangular_cb.stateChanged.connect(
  316. lambda state:
  317. self.rectangular_ring_entry.setDisabled(False) if state else self.rectangular_ring_entry.setDisabled(True)
  318. )
  319. self.other_cb.stateChanged.connect(
  320. lambda state:
  321. self.other_ring_entry.setDisabled(False) if state else self.other_ring_entry.setDisabled(True)
  322. )
  323. def run(self, toggle=True):
  324. self.app.report_usage("ToolPunchGerber()")
  325. if toggle:
  326. # if the splitter is hidden, display it, else hide it but only if the current widget is the same
  327. if self.app.ui.splitter.sizes()[0] == 0:
  328. self.app.ui.splitter.setSizes([1, 1])
  329. else:
  330. try:
  331. if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
  332. # if tab is populated with the tool but it does not have the focus, focus on it
  333. if not self.app.ui.notebook.currentWidget() is self.app.ui.tool_tab:
  334. # focus on Tool Tab
  335. self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
  336. else:
  337. self.app.ui.splitter.setSizes([0, 1])
  338. except AttributeError:
  339. pass
  340. else:
  341. if self.app.ui.splitter.sizes()[0] == 0:
  342. self.app.ui.splitter.setSizes([1, 1])
  343. FlatCAMTool.run(self)
  344. self.set_tool_ui()
  345. self.app.ui.notebook.setTabText(2, _("Punch Tool"))
  346. def install(self, icon=None, separator=None, **kwargs):
  347. FlatCAMTool.install(self, icon, separator, shortcut='ALT+H', **kwargs)
  348. def set_tool_ui(self):
  349. self.reset_fields()
  350. self.ui_connect()
  351. self.method_punch.set_value('exc')
  352. self.select_all_cb.set_value(True)
  353. def on_select_all(self, state):
  354. self.ui_disconnect()
  355. if state:
  356. self.circular_cb.setChecked(True)
  357. self.oblong_cb.setChecked(True)
  358. self.square_cb.setChecked(True)
  359. self.rectangular_cb.setChecked(True)
  360. self.other_cb.setChecked(True)
  361. else:
  362. self.circular_cb.setChecked(False)
  363. self.oblong_cb.setChecked(False)
  364. self.square_cb.setChecked(False)
  365. self.rectangular_cb.setChecked(False)
  366. self.other_cb.setChecked(False)
  367. self.ui_connect()
  368. def on_method(self, val):
  369. self.exc_label.setEnabled(False)
  370. self.exc_combo.setEnabled(False)
  371. self.fixed_label.setEnabled(False)
  372. self.dia_label.setEnabled(False)
  373. self.dia_entry.setEnabled(False)
  374. self.ring_frame.setEnabled(False)
  375. self.prop_label.setEnabled(False)
  376. self.factor_label.setEnabled(False)
  377. self.factor_entry.setEnabled(False)
  378. if val == 'exc':
  379. self.exc_label.setEnabled(True)
  380. self.exc_combo.setEnabled(True)
  381. elif val == 'fixed':
  382. self.fixed_label.setEnabled(True)
  383. self.dia_label.setEnabled(True)
  384. self.dia_entry.setEnabled(True)
  385. elif val == 'ring':
  386. self.ring_frame.setEnabled(True)
  387. elif val == 'prop':
  388. self.prop_label.setEnabled(True)
  389. self.factor_label.setEnabled(True)
  390. self.factor_entry.setEnabled(True)
  391. def ui_connect(self):
  392. self.select_all_cb.stateChanged.connect(self.on_select_all)
  393. def ui_disconnect(self):
  394. try:
  395. self.select_all_cb.stateChanged.disconnect()
  396. except (AttributeError, TypeError):
  397. pass
  398. def reset_fields(self):
  399. self.gerber_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
  400. self.exc_combo.setRootModelIndex(self.app.collection.index(1, 0, QtCore.QModelIndex()))
  401. self.ui_disconnect()