ToolExtractDrills.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693
  1. # ##########################################################
  2. # FlatCAM: 2D Post-processing for Manufacturing #
  3. # File Author: Marius Adrian Stanciu (c) #
  4. # Date: 1/10/2020 #
  5. # MIT Licence #
  6. # ##########################################################
  7. from PyQt5 import QtWidgets, QtCore
  8. from FlatCAMTool import FlatCAMTool
  9. from flatcamGUI.GUIElements import RadioSet, FCDoubleSpinner, FCCheckBox
  10. from shapely.geometry import Point
  11. import logging
  12. import gettext
  13. import FlatCAMTranslation as fcTranslate
  14. import builtins
  15. fcTranslate.apply_language('strings')
  16. if '_' not in builtins.__dict__:
  17. _ = gettext.gettext
  18. log = logging.getLogger('base')
  19. class ToolExtractDrills(FlatCAMTool):
  20. toolName = _("Extract Drills")
  21. def __init__(self, app):
  22. FlatCAMTool.__init__(self, app)
  23. self.decimals = self.app.decimals
  24. # ## Title
  25. title_label = QtWidgets.QLabel("%s" % self.toolName)
  26. title_label.setStyleSheet("""
  27. QLabel
  28. {
  29. font-size: 16px;
  30. font-weight: bold;
  31. }
  32. """)
  33. self.layout.addWidget(title_label)
  34. self.layout.addWidget(QtWidgets.QLabel(""))
  35. # ## Grid Layout
  36. grid_lay = QtWidgets.QGridLayout()
  37. self.layout.addLayout(grid_lay)
  38. grid_lay.setColumnStretch(0, 1)
  39. grid_lay.setColumnStretch(1, 0)
  40. # ## Gerber Object
  41. self.gerber_object_combo = QtWidgets.QComboBox()
  42. self.gerber_object_combo.setModel(self.app.collection)
  43. self.gerber_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
  44. self.gerber_object_combo.setCurrentIndex(1)
  45. self.grb_label = QtWidgets.QLabel("<b>%s:</b>" % _("GERBER"))
  46. self.grb_label.setToolTip('%s.' % _("Gerber from which to extract drill holes"))
  47. # grid_lay.addRow("Bottom Layer:", self.object_combo)
  48. grid_lay.addWidget(self.grb_label, 0, 0, 1, 2)
  49. grid_lay.addWidget(self.gerber_object_combo, 1, 0, 1, 2)
  50. self.padt_label = QtWidgets.QLabel("<b>%s</b>" % _("Processed Pads Type"))
  51. self.padt_label.setToolTip(
  52. _("The type of pads shape to be processed.\n"
  53. "If the PCB has many SMD pads with rectangular pads,\n"
  54. "disable the Rectangular aperture.")
  55. )
  56. grid_lay.addWidget(self.padt_label, 2, 0, 1, 2)
  57. # Circular Aperture Selection
  58. self.circular_cb = FCCheckBox('%s' % _("Circular"))
  59. self.circular_cb.setToolTip(
  60. _("Process Circular Pads.")
  61. )
  62. grid_lay.addWidget(self.circular_cb, 3, 0, 1, 2)
  63. # Oblong Aperture Selection
  64. self.oblong_cb = FCCheckBox('%s' % _("Oblong"))
  65. self.oblong_cb.setToolTip(
  66. _("Process Oblong Pads.")
  67. )
  68. grid_lay.addWidget(self.oblong_cb, 4, 0, 1, 2)
  69. # Square Aperture Selection
  70. self.square_cb = FCCheckBox('%s' % _("Square"))
  71. self.square_cb.setToolTip(
  72. _("Process Square Pads.")
  73. )
  74. grid_lay.addWidget(self.square_cb, 5, 0, 1, 2)
  75. # Rectangular Aperture Selection
  76. self.rectangular_cb = FCCheckBox('%s' % _("Rectangular"))
  77. self.rectangular_cb.setToolTip(
  78. _("Process Rectangular Pads.")
  79. )
  80. grid_lay.addWidget(self.rectangular_cb, 6, 0, 1, 2)
  81. # Others type of Apertures Selection
  82. self.other_cb = FCCheckBox('%s' % _("Others"))
  83. self.other_cb.setToolTip(
  84. _("Process pads not in the categories above.")
  85. )
  86. grid_lay.addWidget(self.other_cb, 7, 0, 1, 2)
  87. separator_line = QtWidgets.QFrame()
  88. separator_line.setFrameShape(QtWidgets.QFrame.HLine)
  89. separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
  90. grid_lay.addWidget(separator_line, 8, 0, 1, 2)
  91. # ## Grid Layout
  92. grid1 = QtWidgets.QGridLayout()
  93. self.layout.addLayout(grid1)
  94. grid1.setColumnStretch(0, 0)
  95. grid1.setColumnStretch(1, 1)
  96. self.method_label = QtWidgets.QLabel('<b>%s</b>' % _("Method"))
  97. self.method_label.setToolTip(
  98. _("The method for processing pads. Can be:\n"
  99. "- Fixed Diameter -> all holes will have a set size\n"
  100. "- Fixed Annular Ring -> all holes will have a set annular ring\n"
  101. "- Proportional -> each hole size will be a fraction of the pad size"))
  102. grid1.addWidget(self.method_label, 2, 0, 1, 2)
  103. # ## Holes Size
  104. self.hole_size_radio = RadioSet(
  105. [
  106. {'label': _("Fixed Diameter"), 'value': 'fixed'},
  107. {'label': _("Fixed Annular Ring"), 'value': 'ring'},
  108. {'label': _("Proportional"), 'value': 'prop'}
  109. ],
  110. orientation='vertical',
  111. stretch=False)
  112. grid1.addWidget(self.hole_size_radio, 3, 0, 1, 2)
  113. # grid_lay1.addWidget(QtWidgets.QLabel(''))
  114. separator_line = QtWidgets.QFrame()
  115. separator_line.setFrameShape(QtWidgets.QFrame.HLine)
  116. separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
  117. grid1.addWidget(separator_line, 5, 0, 1, 2)
  118. # Annular Ring
  119. self.fixed_label = QtWidgets.QLabel('<b>%s</b>' % _("Fixed Diameter"))
  120. grid1.addWidget(self.fixed_label, 6, 0, 1, 2)
  121. # Diameter value
  122. self.dia_entry = FCDoubleSpinner()
  123. self.dia_entry.set_precision(self.decimals)
  124. self.dia_entry.set_range(0.0000, 9999.9999)
  125. self.dia_label = QtWidgets.QLabel('%s:' % _("Value"))
  126. self.dia_label.setToolTip(
  127. _("Fixed hole diameter.")
  128. )
  129. grid1.addWidget(self.dia_label, 8, 0)
  130. grid1.addWidget(self.dia_entry, 8, 1)
  131. separator_line = QtWidgets.QFrame()
  132. separator_line.setFrameShape(QtWidgets.QFrame.HLine)
  133. separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
  134. grid1.addWidget(separator_line, 9, 0, 1, 2)
  135. self.ring_frame = QtWidgets.QFrame()
  136. self.ring_frame.setContentsMargins(0, 0, 0, 0)
  137. self.layout.addWidget(self.ring_frame)
  138. self.ring_box = QtWidgets.QVBoxLayout()
  139. self.ring_box.setContentsMargins(0, 0, 0, 0)
  140. self.ring_frame.setLayout(self.ring_box)
  141. # ## Grid Layout
  142. grid2 = QtWidgets.QGridLayout()
  143. grid2.setColumnStretch(0, 0)
  144. grid2.setColumnStretch(1, 1)
  145. self.ring_box.addLayout(grid2)
  146. # Annular Ring value
  147. self.ring_label = QtWidgets.QLabel('<b>%s</b>' % _("Fixed Annular Ring"))
  148. self.ring_label.setToolTip(
  149. _("The size of annular ring.\n"
  150. "The copper sliver between the hole exterior\n"
  151. "and the margin of the copper pad.")
  152. )
  153. grid2.addWidget(self.ring_label, 0, 0, 1, 2)
  154. # Circular Annular Ring Value
  155. self.circular_ring_label = QtWidgets.QLabel('%s:' % _("Circular"))
  156. self.circular_ring_label.setToolTip(
  157. _("The size of annular ring for circular pads.")
  158. )
  159. self.circular_ring_entry = FCDoubleSpinner()
  160. self.circular_ring_entry.set_precision(self.decimals)
  161. self.circular_ring_entry.set_range(0.0000, 9999.9999)
  162. grid2.addWidget(self.circular_ring_label, 1, 0)
  163. grid2.addWidget(self.circular_ring_entry, 1, 1)
  164. # Oblong Annular Ring Value
  165. self.oblong_ring_label = QtWidgets.QLabel('%s:' % _("Oblong"))
  166. self.oblong_ring_label.setToolTip(
  167. _("The size of annular ring for oblong pads.")
  168. )
  169. self.oblong_ring_entry = FCDoubleSpinner()
  170. self.oblong_ring_entry.set_precision(self.decimals)
  171. self.oblong_ring_entry.set_range(0.0000, 9999.9999)
  172. grid2.addWidget(self.oblong_ring_label, 2, 0)
  173. grid2.addWidget(self.oblong_ring_entry, 2, 1)
  174. # Square Annular Ring Value
  175. self.square_ring_label = QtWidgets.QLabel('%s:' % _("Square"))
  176. self.square_ring_label.setToolTip(
  177. _("The size of annular ring for square pads.")
  178. )
  179. self.square_ring_entry = FCDoubleSpinner()
  180. self.square_ring_entry.set_precision(self.decimals)
  181. self.square_ring_entry.set_range(0.0000, 9999.9999)
  182. grid2.addWidget(self.square_ring_label, 3, 0)
  183. grid2.addWidget(self.square_ring_entry, 3, 1)
  184. # Rectangular Annular Ring Value
  185. self.rectangular_ring_label = QtWidgets.QLabel('%s:' % _("Rectangular"))
  186. self.rectangular_ring_label.setToolTip(
  187. _("The size of annular ring for rectangular pads.")
  188. )
  189. self.rectangular_ring_entry = FCDoubleSpinner()
  190. self.rectangular_ring_entry.set_precision(self.decimals)
  191. self.rectangular_ring_entry.set_range(0.0000, 9999.9999)
  192. grid2.addWidget(self.rectangular_ring_label, 4, 0)
  193. grid2.addWidget(self.rectangular_ring_entry, 4, 1)
  194. # Others Annular Ring Value
  195. self.other_ring_label = QtWidgets.QLabel('%s:' % _("Others"))
  196. self.other_ring_label.setToolTip(
  197. _("The size of annular ring for other pads.")
  198. )
  199. self.other_ring_entry = FCDoubleSpinner()
  200. self.other_ring_entry.set_precision(self.decimals)
  201. self.other_ring_entry.set_range(0.0000, 9999.9999)
  202. grid2.addWidget(self.other_ring_label, 5, 0)
  203. grid2.addWidget(self.other_ring_entry, 5, 1)
  204. grid3 = QtWidgets.QGridLayout()
  205. self.layout.addLayout(grid3)
  206. grid3.setColumnStretch(0, 0)
  207. grid3.setColumnStretch(1, 1)
  208. separator_line = QtWidgets.QFrame()
  209. separator_line.setFrameShape(QtWidgets.QFrame.HLine)
  210. separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
  211. grid3.addWidget(separator_line, 1, 0, 1, 2)
  212. # Annular Ring value
  213. self.prop_label = QtWidgets.QLabel('<b>%s</b>' % _("Proportional Diameter"))
  214. grid3.addWidget(self.prop_label, 2, 0, 1, 2)
  215. # Diameter value
  216. self.factor_entry = FCDoubleSpinner(suffix='%')
  217. self.factor_entry.set_precision(self.decimals)
  218. self.factor_entry.set_range(0.0000, 100.0000)
  219. self.factor_entry.setSingleStep(0.1)
  220. self.factor_label = QtWidgets.QLabel('%s:' % _("Value"))
  221. self.factor_label.setToolTip(
  222. _("Proportional Diameter.\n"
  223. "The hole diameter will be a fraction of the pad size.")
  224. )
  225. grid3.addWidget(self.factor_label, 3, 0)
  226. grid3.addWidget(self.factor_entry, 3, 1)
  227. # Extract drills from Gerber apertures flashes (pads)
  228. self.e_drills_button = QtWidgets.QPushButton(_("Extract Drills"))
  229. self.e_drills_button.setToolTip(
  230. _("Extract drills from a given Gerber file.")
  231. )
  232. self.e_drills_button.setStyleSheet("""
  233. QPushButton
  234. {
  235. font-weight: bold;
  236. }
  237. """)
  238. self.layout.addWidget(self.e_drills_button)
  239. self.layout.addStretch()
  240. # ## Reset Tool
  241. self.reset_button = QtWidgets.QPushButton(_("Reset Tool"))
  242. self.reset_button.setToolTip(
  243. _("Will reset the tool parameters.")
  244. )
  245. self.reset_button.setStyleSheet("""
  246. QPushButton
  247. {
  248. font-weight: bold;
  249. }
  250. """)
  251. self.layout.addWidget(self.reset_button)
  252. self.circular_ring_entry.setEnabled(False)
  253. self.oblong_ring_entry.setEnabled(False)
  254. self.square_ring_entry.setEnabled(False)
  255. self.rectangular_ring_entry.setEnabled(False)
  256. self.other_ring_entry.setEnabled(False)
  257. self.dia_entry.setDisabled(True)
  258. self.dia_label.setDisabled(True)
  259. self.factor_label.setDisabled(True)
  260. self.factor_entry.setDisabled(True)
  261. self.ring_frame.setDisabled(True)
  262. # ## Signals
  263. self.hole_size_radio.activated_custom.connect(self.on_hole_size_toggle)
  264. self.e_drills_button.clicked.connect(self.on_extract_drills_click)
  265. self.reset_button.clicked.connect(self.set_tool_ui)
  266. self.circular_cb.stateChanged.connect(
  267. lambda state:
  268. self.circular_ring_entry.setDisabled(False) if state else self.circular_ring_entry.setDisabled(True)
  269. )
  270. self.oblong_cb.stateChanged.connect(
  271. lambda state:
  272. self.oblong_ring_entry.setDisabled(False) if state else self.oblong_ring_entry.setDisabled(True)
  273. )
  274. self.square_cb.stateChanged.connect(
  275. lambda state:
  276. self.square_ring_entry.setDisabled(False) if state else self.square_ring_entry.setDisabled(True)
  277. )
  278. self.rectangular_cb.stateChanged.connect(
  279. lambda state:
  280. self.rectangular_ring_entry.setDisabled(False) if state else self.rectangular_ring_entry.setDisabled(True)
  281. )
  282. self.other_cb.stateChanged.connect(
  283. lambda state:
  284. self.other_ring_entry.setDisabled(False) if state else self.other_ring_entry.setDisabled(True)
  285. )
  286. def install(self, icon=None, separator=None, **kwargs):
  287. FlatCAMTool.install(self, icon, separator, shortcut='ALT+I', **kwargs)
  288. def run(self, toggle=True):
  289. self.app.report_usage("Extract Drills()")
  290. if toggle:
  291. # if the splitter is hidden, display it, else hide it but only if the current widget is the same
  292. if self.app.ui.splitter.sizes()[0] == 0:
  293. self.app.ui.splitter.setSizes([1, 1])
  294. else:
  295. try:
  296. if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
  297. # if tab is populated with the tool but it does not have the focus, focus on it
  298. if not self.app.ui.notebook.currentWidget() is self.app.ui.tool_tab:
  299. # focus on Tool Tab
  300. self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
  301. else:
  302. self.app.ui.splitter.setSizes([0, 1])
  303. except AttributeError:
  304. pass
  305. else:
  306. if self.app.ui.splitter.sizes()[0] == 0:
  307. self.app.ui.splitter.setSizes([1, 1])
  308. FlatCAMTool.run(self)
  309. self.set_tool_ui()
  310. self.app.ui.notebook.setTabText(2, _("Extract Drills Tool"))
  311. def set_tool_ui(self):
  312. self.reset_fields()
  313. self.hole_size_radio.set_value(self.app.defaults["tools_edrills_hole_type"])
  314. self.dia_entry.set_value(float(self.app.defaults["tools_edrills_hole_fixed_dia"]))
  315. self.circular_ring_entry.set_value(float(self.app.defaults["tools_edrills_circular_ring"]))
  316. self.oblong_ring_entry.set_value(float(self.app.defaults["tools_edrills_oblong_ring"]))
  317. self.square_ring_entry.set_value(float(self.app.defaults["tools_edrills_square_ring"]))
  318. self.rectangular_ring_entry.set_value(float(self.app.defaults["tools_edrills_rectangular_ring"]))
  319. self.other_ring_entry.set_value(float(self.app.defaults["tools_edrills_others_ring"]))
  320. self.circular_cb.set_value(self.app.defaults["tools_edrills_circular"])
  321. self.oblong_cb.set_value(self.app.defaults["tools_edrills_oblong"])
  322. self.square_cb.set_value(self.app.defaults["tools_edrills_square"])
  323. self.rectangular_cb.set_value(self.app.defaults["tools_edrills_rectangular"])
  324. self.other_cb.set_value(self.app.defaults["tools_edrills_others"])
  325. self.factor_entry.set_value(float(self.app.defaults["tools_edrills_hole_prop_factor"]))
  326. def on_extract_drills_click(self):
  327. drill_dia = self.dia_entry.get_value()
  328. circ_r_val = self.circular_ring_entry.get_value()
  329. oblong_r_val = self.oblong_ring_entry.get_value()
  330. square_r_val = self.square_ring_entry.get_value()
  331. rect_r_val = self.rectangular_ring_entry.get_value()
  332. other_r_val = self.other_ring_entry.get_value()
  333. prop_factor = self.factor_entry.get_value() / 100.0
  334. drills = list()
  335. tools = dict()
  336. selection_index = self.gerber_object_combo.currentIndex()
  337. model_index = self.app.collection.index(selection_index, 0, self.gerber_object_combo.rootModelIndex())
  338. try:
  339. fcobj = model_index.internalPointer().obj
  340. except Exception:
  341. self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Gerber object loaded ..."))
  342. return
  343. outname = fcobj.options['name'].rpartition('.')[0]
  344. mode = self.hole_size_radio.get_value()
  345. if mode == 'fixed':
  346. tools = {"1": {"C": drill_dia}}
  347. for apid, apid_value in fcobj.apertures.items():
  348. ap_type = apid_value['type']
  349. if ap_type == 'C':
  350. if self.circular_cb.get_value() is False:
  351. continue
  352. elif ap_type == 'O':
  353. if self.oblong_cb.get_value() is False:
  354. continue
  355. elif ap_type == 'R':
  356. width = float(apid_value['width'])
  357. height = float(apid_value['height'])
  358. # if the height == width (float numbers so the reason for the following)
  359. if round(width, self.decimals) == round(height, self.decimals):
  360. if self.square_cb.get_value() is False:
  361. continue
  362. else:
  363. if self.rectangular_cb.get_value() is False:
  364. continue
  365. else:
  366. if self.other_cb.get_value() is False:
  367. continue
  368. for geo_el in apid_value['geometry']:
  369. if 'follow' in geo_el and isinstance(geo_el['follow'], Point):
  370. drills.append({"point": geo_el['follow'], "tool": "1"})
  371. if 'solid_geometry' not in tools["1"]:
  372. tools["1"]['solid_geometry'] = list()
  373. else:
  374. tools["1"]['solid_geometry'].append(geo_el['follow'])
  375. if 'solid_geometry' not in tools["1"] or not tools["1"]['solid_geometry']:
  376. self.app.inform.emit('[WARNING_NOTCL] %s' % _("No drills extracted. Try different parameters."))
  377. return
  378. elif mode == 'ring':
  379. drills_found = set()
  380. for apid, apid_value in fcobj.apertures.items():
  381. ap_type = apid_value['type']
  382. dia = None
  383. if ap_type == 'C':
  384. if self.circular_cb.get_value():
  385. dia = float(apid_value['size']) - (2 * circ_r_val)
  386. elif ap_type == 'O':
  387. width = float(apid_value['width'])
  388. height = float(apid_value['height'])
  389. if self.oblong_cb.get_value():
  390. if width > height:
  391. dia = float(apid_value['height']) - (2 * oblong_r_val)
  392. else:
  393. dia = float(apid_value['width']) - (2 * oblong_r_val)
  394. elif ap_type == 'R':
  395. width = float(apid_value['width'])
  396. height = float(apid_value['height'])
  397. # if the height == width (float numbers so the reason for the following)
  398. if abs(float('%.*f' % (self.decimals, width)) - float('%.*f' % (self.decimals, height))) < \
  399. (10 ** -self.decimals):
  400. if self.square_cb.get_value():
  401. dia = float(apid_value['height']) - (2 * square_r_val)
  402. else:
  403. if self.rectangular_cb.get_value():
  404. if width > height:
  405. dia = float(apid_value['height']) - (2 * rect_r_val)
  406. else:
  407. dia = float(apid_value['width']) - (2 * rect_r_val)
  408. else:
  409. if self.other_cb.get_value():
  410. try:
  411. dia = float(apid_value['size']) - (2 * other_r_val)
  412. except KeyError:
  413. if ap_type == 'AM':
  414. pol = apid_value['geometry'][0]['solid']
  415. x0, y0, x1, y1 = pol.bounds
  416. dx = x1 - x0
  417. dy = y1 - y0
  418. if dx <= dy:
  419. dia = dx - (2 * other_r_val)
  420. else:
  421. dia = dy - (2 * other_r_val)
  422. # if dia is None then none of the above applied so we skip the following
  423. if dia is None:
  424. continue
  425. tool_in_drills = False
  426. for tool, tool_val in tools.items():
  427. if abs(float('%.*f' % (self.decimals, tool_val["C"])) - float('%.*f' % (self.decimals, dia))) < \
  428. (10 ** -self.decimals):
  429. tool_in_drills = tool
  430. if tool_in_drills is False:
  431. if tools:
  432. new_tool = max([int(t) for t in tools]) + 1
  433. tool_in_drills = str(new_tool)
  434. else:
  435. tool_in_drills = "1"
  436. for geo_el in apid_value['geometry']:
  437. if 'follow' in geo_el and isinstance(geo_el['follow'], Point):
  438. if tool_in_drills not in tools:
  439. tools[tool_in_drills] = {"C": dia}
  440. drills.append({"point": geo_el['follow'], "tool": tool_in_drills})
  441. if 'solid_geometry' not in tools[tool_in_drills]:
  442. tools[tool_in_drills]['solid_geometry'] = list()
  443. else:
  444. tools[tool_in_drills]['solid_geometry'].append(geo_el['follow'])
  445. if tool_in_drills in tools:
  446. if 'solid_geometry' not in tools[tool_in_drills] or not tools[tool_in_drills]['solid_geometry']:
  447. drills_found.add(False)
  448. else:
  449. drills_found.add(True)
  450. if True not in drills_found:
  451. self.app.inform.emit('[WARNING_NOTCL] %s' % _("No drills extracted. Try different parameters."))
  452. return
  453. else:
  454. drills_found = set()
  455. for apid, apid_value in fcobj.apertures.items():
  456. ap_type = apid_value['type']
  457. dia = None
  458. if ap_type == 'C':
  459. if self.circular_cb.get_value():
  460. dia = float(apid_value['size']) * prop_factor
  461. elif ap_type == 'O':
  462. width = float(apid_value['width'])
  463. height = float(apid_value['height'])
  464. if self.oblong_cb.get_value():
  465. if width > height:
  466. dia = float(apid_value['height']) * prop_factor
  467. else:
  468. dia = float(apid_value['width']) * prop_factor
  469. elif ap_type == 'R':
  470. width = float(apid_value['width'])
  471. height = float(apid_value['height'])
  472. # if the height == width (float numbers so the reason for the following)
  473. if abs(float('%.*f' % (self.decimals, width)) - float('%.*f' % (self.decimals, height))) < \
  474. (10 ** -self.decimals):
  475. if self.square_cb.get_value():
  476. dia = float(apid_value['height']) * prop_factor
  477. else:
  478. if self.rectangular_cb.get_value():
  479. if width > height:
  480. dia = float(apid_value['height']) * prop_factor
  481. else:
  482. dia = float(apid_value['width']) * prop_factor
  483. else:
  484. if self.other_cb.get_value():
  485. try:
  486. dia = float(apid_value['size']) * prop_factor
  487. except KeyError:
  488. if ap_type == 'AM':
  489. pol = apid_value['geometry'][0]['solid']
  490. x0, y0, x1, y1 = pol.bounds
  491. dx = x1 - x0
  492. dy = y1 - y0
  493. if dx <= dy:
  494. dia = dx * prop_factor
  495. else:
  496. dia = dy * prop_factor
  497. # if dia is None then none of the above applied so we skip the following
  498. if dia is None:
  499. continue
  500. tool_in_drills = False
  501. for tool, tool_val in tools.items():
  502. if abs(float('%.*f' % (self.decimals, tool_val["C"])) - float('%.*f' % (self.decimals, dia))) < \
  503. (10 ** -self.decimals):
  504. tool_in_drills = tool
  505. if tool_in_drills is False:
  506. if tools:
  507. new_tool = max([int(t) for t in tools]) + 1
  508. tool_in_drills = str(new_tool)
  509. else:
  510. tool_in_drills = "1"
  511. for geo_el in apid_value['geometry']:
  512. if 'follow' in geo_el and isinstance(geo_el['follow'], Point):
  513. if tool_in_drills not in tools:
  514. tools[tool_in_drills] = {"C": dia}
  515. drills.append({"point": geo_el['follow'], "tool": tool_in_drills})
  516. if 'solid_geometry' not in tools[tool_in_drills]:
  517. tools[tool_in_drills]['solid_geometry'] = list()
  518. else:
  519. tools[tool_in_drills]['solid_geometry'].append(geo_el['follow'])
  520. if tool_in_drills in tools:
  521. if 'solid_geometry' not in tools[tool_in_drills] or not tools[tool_in_drills]['solid_geometry']:
  522. drills_found.add(False)
  523. else:
  524. drills_found.add(True)
  525. if True not in drills_found:
  526. self.app.inform.emit('[WARNING_NOTCL] %s' % _("No drills extracted. Try different parameters."))
  527. return
  528. def obj_init(obj_inst, app_inst):
  529. obj_inst.tools = tools
  530. obj_inst.drills = drills
  531. obj_inst.create_geometry()
  532. obj_inst.source_file = self.app.export_excellon(obj_name=outname, local_use=obj_inst, filename=None,
  533. use_thread=False)
  534. self.app.new_object("excellon", outname, obj_init)
  535. def on_hole_size_toggle(self, val):
  536. if val == "fixed":
  537. self.fixed_label.setDisabled(False)
  538. self.dia_entry.setDisabled(False)
  539. self.dia_label.setDisabled(False)
  540. self.ring_frame.setDisabled(True)
  541. self.prop_label.setDisabled(True)
  542. self.factor_label.setDisabled(True)
  543. self.factor_entry.setDisabled(True)
  544. elif val == "ring":
  545. self.fixed_label.setDisabled(True)
  546. self.dia_entry.setDisabled(True)
  547. self.dia_label.setDisabled(True)
  548. self.ring_frame.setDisabled(False)
  549. self.prop_label.setDisabled(True)
  550. self.factor_label.setDisabled(True)
  551. self.factor_entry.setDisabled(True)
  552. elif val == "prop":
  553. self.fixed_label.setDisabled(True)
  554. self.dia_entry.setDisabled(True)
  555. self.dia_label.setDisabled(True)
  556. self.ring_frame.setDisabled(True)
  557. self.prop_label.setDisabled(False)
  558. self.factor_label.setDisabled(False)
  559. self.factor_entry.setDisabled(False)
  560. def reset_fields(self):
  561. self.gerber_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
  562. self.gerber_object_combo.setCurrentIndex(0)