ToolExtractDrills.py 31 KB

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