ToolOptimal.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625
  1. # ##########################################################
  2. # FlatCAM: 2D Post-processing for Manufacturing #
  3. # File Author: Marius Adrian Stanciu (c) #
  4. # Date: 09/27/2019 #
  5. # MIT Licence #
  6. # ##########################################################
  7. from PyQt5 import QtWidgets, QtCore, QtGui
  8. from appTool import AppTool
  9. from appGUI.GUIElements import OptionalHideInputSection, FCTextArea, FCEntry, FCSpinner, FCCheckBox, FCComboBox
  10. from camlib import grace
  11. from shapely.geometry import MultiPolygon
  12. from shapely.ops import nearest_points
  13. import numpy as np
  14. import logging
  15. import gettext
  16. import appTranslation as fcTranslate
  17. import builtins
  18. fcTranslate.apply_language('strings')
  19. if '_' not in builtins.__dict__:
  20. _ = gettext.gettext
  21. log = logging.getLogger('base')
  22. class ToolOptimal(AppTool):
  23. update_text = QtCore.pyqtSignal(list)
  24. update_sec_distances = QtCore.pyqtSignal(dict)
  25. def __init__(self, app):
  26. AppTool.__init__(self, app)
  27. self.units = self.app.defaults['units'].upper()
  28. self.decimals = self.app.decimals
  29. # #############################################################################
  30. # ######################### Tool GUI ##########################################
  31. # #############################################################################
  32. self.ui = OptimalUI(layout=self.layout, app=self.app)
  33. self.toolName = self.ui.toolName
  34. # this is the line selected in the textbox with the locations of the minimum
  35. self.selected_text = ''
  36. # this is the line selected in the textbox with the locations of the other distances found in the Gerber object
  37. self.selected_locations_text = ''
  38. # dict to hold the distances between every two elements in Gerber as keys and the actual locations where that
  39. # distances happen as values
  40. self.min_dict = {}
  41. # ############################################################################
  42. # ############################ Signals #######################################
  43. # ############################################################################
  44. self.ui.calculate_button.clicked.connect(self.find_minimum_distance)
  45. self.ui.locate_button.clicked.connect(self.on_locate_position)
  46. self.ui.update_text.connect(self.on_update_text)
  47. self.ui.locations_textb.cursorPositionChanged.connect(self.on_textbox_clicked)
  48. self.ui.locate_sec_button.clicked.connect(self.on_locate_sec_position)
  49. self.ui.update_sec_distances.connect(self.on_update_sec_distances_txt)
  50. self.ui.distances_textb.cursorPositionChanged.connect(self.on_distances_textb_clicked)
  51. self.ui.locations_sec_textb.cursorPositionChanged.connect(self.on_locations_sec_clicked)
  52. self.ui.reset_button.clicked.connect(self.set_tool_ui)
  53. def install(self, icon=None, separator=None, **kwargs):
  54. AppTool.install(self, icon, separator, shortcut='Alt+O', **kwargs)
  55. def run(self, toggle=True):
  56. self.app.defaults.report_usage("ToolOptimal()")
  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, _("Optimal Tool"))
  78. def set_tool_ui(self):
  79. self.ui.result_entry.set_value(0.0)
  80. self.ui.freq_entry.set_value('0')
  81. self.ui.precision_spinner.set_value(int(self.app.defaults["tools_opt_precision"]))
  82. self.ui.locations_textb.clear()
  83. # new cursor - select all document
  84. cursor = self.ui.locations_textb.textCursor()
  85. cursor.select(QtGui.QTextCursor.Document)
  86. # clear previous selection highlight
  87. tmp = cursor.blockFormat()
  88. tmp.clearBackground()
  89. cursor.setBlockFormat(tmp)
  90. self.ui.locations_textb.setVisible(False)
  91. self.ui.locate_button.setVisible(False)
  92. self.ui.result_entry.set_value(0.0)
  93. self.ui.freq_entry.set_value('0')
  94. self.reset_fields()
  95. def find_minimum_distance(self):
  96. self.units = self.app.defaults['units'].upper()
  97. self.decimals = int(self.ui.precision_spinner.get_value())
  98. selection_index = self.ui.gerber_object_combo.currentIndex()
  99. model_index = self.app.collection.index(selection_index, 0, self.ui.gerber_object_combo.rootModelIndex())
  100. try:
  101. fcobj = model_index.internalPointer().obj
  102. except Exception as e:
  103. log.debug("ToolOptimal.find_minimum_distance() --> %s" % str(e))
  104. self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Gerber object loaded ..."))
  105. return
  106. if fcobj.kind != 'gerber':
  107. self.app.inform.emit('[ERROR_NOTCL] %s' % _("Only Gerber objects can be evaluated."))
  108. return
  109. proc = self.app.proc_container.new(_("Working..."))
  110. def job_thread(app_obj):
  111. app_obj.inform.emit(_("Optimal Tool. Started to search for the minimum distance between copper features."))
  112. try:
  113. old_disp_number = 0
  114. pol_nr = 0
  115. app_obj.proc_container.update_view_text(' %d%%' % 0)
  116. total_geo = []
  117. for ap in list(fcobj.apertures.keys()):
  118. if 'geometry' in fcobj.apertures[ap]:
  119. app_obj.inform.emit(
  120. '%s: %s' % (_("Optimal Tool. Parsing geometry for aperture"), str(ap)))
  121. for geo_el in fcobj.apertures[ap]['geometry']:
  122. if self.app.abort_flag:
  123. # graceful abort requested by the user
  124. raise grace
  125. if 'solid' in geo_el and geo_el['solid'] is not None and geo_el['solid'].is_valid:
  126. total_geo.append(geo_el['solid'])
  127. app_obj.inform.emit(
  128. _("Optimal Tool. Creating a buffer for the object geometry."))
  129. total_geo = MultiPolygon(total_geo)
  130. total_geo = total_geo.buffer(0)
  131. try:
  132. __ = iter(total_geo)
  133. geo_len = len(total_geo)
  134. geo_len = (geo_len * (geo_len - 1)) / 2
  135. except TypeError:
  136. app_obj.inform.emit('[ERROR_NOTCL] %s' %
  137. _("The Gerber object has one Polygon as geometry.\n"
  138. "There are no distances between geometry elements to be found."))
  139. return 'fail'
  140. app_obj.inform.emit(
  141. '%s: %s' % (_("Optimal Tool. Finding the distances between each two elements. Iterations"),
  142. str(geo_len)))
  143. self.min_dict = {}
  144. idx = 1
  145. for geo in total_geo:
  146. for s_geo in total_geo[idx:]:
  147. if self.app.abort_flag:
  148. # graceful abort requested by the user
  149. raise grace
  150. # minimize the number of distances by not taking into considerations those that are too small
  151. dist = geo.distance(s_geo)
  152. dist = float('%.*f' % (self.decimals, dist))
  153. loc_1, loc_2 = nearest_points(geo, s_geo)
  154. proc_loc = (
  155. (float('%.*f' % (self.decimals, loc_1.x)), float('%.*f' % (self.decimals, loc_1.y))),
  156. (float('%.*f' % (self.decimals, loc_2.x)), float('%.*f' % (self.decimals, loc_2.y)))
  157. )
  158. if dist in self.min_dict:
  159. self.min_dict[dist].append(proc_loc)
  160. else:
  161. self.min_dict[dist] = [proc_loc]
  162. pol_nr += 1
  163. disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 100]))
  164. if old_disp_number < disp_number <= 100:
  165. app_obj.proc_container.update_view_text(' %d%%' % disp_number)
  166. old_disp_number = disp_number
  167. idx += 1
  168. app_obj.inform.emit(_("Optimal Tool. Finding the minimum distance."))
  169. min_list = list(self.min_dict.keys())
  170. min_dist = min(min_list)
  171. min_dist_string = '%.*f' % (self.decimals, float(min_dist))
  172. self.ui.result_entry.set_value(min_dist_string)
  173. freq = len(self.min_dict[min_dist])
  174. freq = '%d' % int(freq)
  175. self.ui.freq_entry.set_value(freq)
  176. min_locations = self.min_dict.pop(min_dist)
  177. self.update_text.emit(min_locations)
  178. self.update_sec_distances.emit(self.min_dict)
  179. app_obj.inform.emit('[success] %s' % _("Optimal Tool. Finished successfully."))
  180. except Exception as ee:
  181. proc.done()
  182. log.debug(str(ee))
  183. return
  184. proc.done()
  185. self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
  186. def on_locate_position(self):
  187. # cursor = self.locations_textb.textCursor()
  188. # self.selected_text = cursor.selectedText()
  189. try:
  190. if self.selected_text != '':
  191. loc = eval(self.selected_text)
  192. else:
  193. return 'fail'
  194. except Exception as e:
  195. log.debug("ToolOptimal.on_locate_position() --> first try %s" % str(e))
  196. self.app.inform.emit("[ERROR_NOTCL] The selected text is no valid location in the format "
  197. "((x0, y0), (x1, y1)).")
  198. return
  199. try:
  200. loc_1 = loc[0]
  201. loc_2 = loc[1]
  202. dx = loc_1[0] - loc_2[0]
  203. dy = loc_1[1] - loc_2[1]
  204. loc = (float('%.*f' % (self.decimals, (min(loc_1[0], loc_2[0]) + (abs(dx) / 2)))),
  205. float('%.*f' % (self.decimals, (min(loc_1[1], loc_2[1]) + (abs(dy) / 2)))))
  206. self.app.on_jump_to(custom_location=loc)
  207. except Exception as e:
  208. log.debug("ToolOptimal.on_locate_position() --> sec try %s" % str(e))
  209. return
  210. def on_update_text(self, data):
  211. txt = ''
  212. for loc in data:
  213. if loc:
  214. txt += '%s, %s\n' % (str(loc[0]), str(loc[1]))
  215. self.ui.locations_textb.setPlainText(txt)
  216. self.ui.locate_button.setDisabled(False)
  217. def on_textbox_clicked(self):
  218. # new cursor - select all document
  219. cursor = self.ui.locations_textb.textCursor()
  220. cursor.select(QtGui.QTextCursor.Document)
  221. # clear previous selection highlight
  222. tmp = cursor.blockFormat()
  223. tmp.clearBackground()
  224. cursor.setBlockFormat(tmp)
  225. # new cursor - select the current line
  226. cursor = self.ui.locations_textb.textCursor()
  227. cursor.select(QtGui.QTextCursor.LineUnderCursor)
  228. # highlight the current selected line
  229. tmp = cursor.blockFormat()
  230. tmp.setBackground(QtGui.QBrush(QtCore.Qt.yellow))
  231. cursor.setBlockFormat(tmp)
  232. self.selected_text = cursor.selectedText()
  233. def on_update_sec_distances_txt(self, data):
  234. distance_list = sorted(list(data.keys()))
  235. txt = ''
  236. for loc in distance_list:
  237. txt += '%s\n' % str(loc)
  238. self.ui.distances_textb.setPlainText(txt)
  239. self.ui.locate_sec_button.setDisabled(False)
  240. def on_distances_textb_clicked(self):
  241. # new cursor - select all document
  242. cursor = self.ui.distances_textb.textCursor()
  243. cursor.select(QtGui.QTextCursor.Document)
  244. # clear previous selection highlight
  245. tmp = cursor.blockFormat()
  246. tmp.clearBackground()
  247. cursor.setBlockFormat(tmp)
  248. # new cursor - select the current line
  249. cursor = self.ui.distances_textb.textCursor()
  250. cursor.select(QtGui.QTextCursor.LineUnderCursor)
  251. # highlight the current selected line
  252. tmp = cursor.blockFormat()
  253. tmp.setBackground(QtGui.QBrush(QtCore.Qt.yellow))
  254. cursor.setBlockFormat(tmp)
  255. distance_text = cursor.selectedText()
  256. key_in_min_dict = eval(distance_text)
  257. self.on_update_locations_text(dist=key_in_min_dict)
  258. def on_update_locations_text(self, dist):
  259. distance_list = self.min_dict[dist]
  260. txt = ''
  261. for loc in distance_list:
  262. if loc:
  263. txt += '%s, %s\n' % (str(loc[0]), str(loc[1]))
  264. self.ui.locations_sec_textb.setPlainText(txt)
  265. def on_locations_sec_clicked(self):
  266. # new cursor - select all document
  267. cursor = self.ui.locations_sec_textb.textCursor()
  268. cursor.select(QtGui.QTextCursor.Document)
  269. # clear previous selection highlight
  270. tmp = cursor.blockFormat()
  271. tmp.clearBackground()
  272. cursor.setBlockFormat(tmp)
  273. # new cursor - select the current line
  274. cursor = self.ui.locations_sec_textb.textCursor()
  275. cursor.select(QtGui.QTextCursor.LineUnderCursor)
  276. # highlight the current selected line
  277. tmp = cursor.blockFormat()
  278. tmp.setBackground(QtGui.QBrush(QtCore.Qt.yellow))
  279. cursor.setBlockFormat(tmp)
  280. self.selected_locations_text = cursor.selectedText()
  281. def on_locate_sec_position(self):
  282. try:
  283. if self.selected_locations_text != '':
  284. loc = eval(self.selected_locations_text)
  285. else:
  286. return
  287. except Exception as e:
  288. log.debug("ToolOptimal.on_locate_sec_position() --> first try %s" % str(e))
  289. self.app.inform.emit("[ERROR_NOTCL] The selected text is no valid location in the format "
  290. "((x0, y0), (x1, y1)).")
  291. return
  292. try:
  293. loc_1 = loc[0]
  294. loc_2 = loc[1]
  295. dx = loc_1[0] - loc_2[0]
  296. dy = loc_1[1] - loc_2[1]
  297. loc = (float('%.*f' % (self.decimals, (min(loc_1[0], loc_2[0]) + (abs(dx) / 2)))),
  298. float('%.*f' % (self.decimals, (min(loc_1[1], loc_2[1]) + (abs(dy) / 2)))))
  299. self.app.on_jump_to(custom_location=loc)
  300. except Exception as e:
  301. log.debug("ToolOptimal.on_locate_sec_position() --> sec try %s" % str(e))
  302. return
  303. def reset_fields(self):
  304. self.ui.gerber_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
  305. self.ui.gerber_object_combo.setCurrentIndex(0)
  306. class OptimalUI:
  307. toolName = _("Optimal Tool")
  308. def __init__(self, layout, app):
  309. self.app = app
  310. self.decimals = self.app.decimals
  311. self.layout = layout
  312. self.units = self.app.defaults['units'].upper()
  313. # ## Title
  314. title_label = QtWidgets.QLabel("%s" % self.toolName)
  315. title_label.setStyleSheet("""
  316. QLabel
  317. {
  318. font-size: 16px;
  319. font-weight: bold;
  320. }
  321. """)
  322. self.layout.addWidget(title_label)
  323. self.layout.addWidget(QtWidgets.QLabel(""))
  324. # ## Form Layout
  325. form_lay = QtWidgets.QFormLayout()
  326. self.layout.addLayout(form_lay)
  327. # ## Gerber Object to mirror
  328. self.gerber_object_combo = FCComboBox()
  329. self.gerber_object_combo.setModel(self.app.collection)
  330. self.gerber_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
  331. self.gerber_object_combo.is_last = True
  332. self.gerber_object_combo.obj_type = "Gerber"
  333. self.gerber_object_label = QtWidgets.QLabel("<b>%s:</b>" % _("GERBER"))
  334. self.gerber_object_label.setToolTip(
  335. "Gerber object for which to find the minimum distance between copper features."
  336. )
  337. form_lay.addRow(self.gerber_object_label)
  338. form_lay.addRow(self.gerber_object_combo)
  339. separator_line = QtWidgets.QFrame()
  340. separator_line.setFrameShape(QtWidgets.QFrame.HLine)
  341. separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
  342. form_lay.addRow(separator_line)
  343. # Precision = nr of decimals
  344. self.precision_label = QtWidgets.QLabel('%s:' % _("Precision"))
  345. self.precision_label.setToolTip(_("Number of decimals kept for found distances."))
  346. self.precision_spinner = FCSpinner(callback=self.confirmation_message_int)
  347. self.precision_spinner.set_range(2, 10)
  348. self.precision_spinner.setWrapping(True)
  349. form_lay.addRow(self.precision_label, self.precision_spinner)
  350. # Results Title
  351. self.title_res_label = QtWidgets.QLabel('<b>%s:</b>' % _("Minimum distance"))
  352. self.title_res_label.setToolTip(_("Display minimum distance between copper features."))
  353. form_lay.addRow(self.title_res_label)
  354. # Result value
  355. self.result_label = QtWidgets.QLabel('%s:' % _("Determined"))
  356. self.result_entry = FCEntry()
  357. self.result_entry.setReadOnly(True)
  358. self.units_lbl = QtWidgets.QLabel(self.units.lower())
  359. self.units_lbl.setDisabled(True)
  360. hlay = QtWidgets.QHBoxLayout()
  361. hlay.addWidget(self.result_entry)
  362. hlay.addWidget(self.units_lbl)
  363. form_lay.addRow(self.result_label, hlay)
  364. # Frequency of minimum encounter
  365. self.freq_label = QtWidgets.QLabel('%s:' % _("Occurring"))
  366. self.freq_label.setToolTip(_("How many times this minimum is found."))
  367. self.freq_entry = FCEntry()
  368. self.freq_entry.setReadOnly(True)
  369. form_lay.addRow(self.freq_label, self.freq_entry)
  370. # Control if to display the locations of where the minimum was found
  371. self.locations_cb = FCCheckBox(_("Minimum points coordinates"))
  372. self.locations_cb.setToolTip(_("Coordinates for points where minimum distance was found."))
  373. form_lay.addRow(self.locations_cb)
  374. # Locations where minimum was found
  375. self.locations_textb = FCTextArea(parent=self)
  376. self.locations_textb.setPlaceholderText(
  377. _("Coordinates for points where minimum distance was found.")
  378. )
  379. self.locations_textb.setReadOnly(True)
  380. stylesheet = """
  381. QTextEdit { selection-background-color:blue;
  382. selection-color:white;
  383. }
  384. """
  385. self.locations_textb.setStyleSheet(stylesheet)
  386. form_lay.addRow(self.locations_textb)
  387. # Jump button
  388. self.locate_button = QtWidgets.QPushButton(_("Jump to selected position"))
  389. self.locate_button.setToolTip(
  390. _("Select a position in the Locations text box and then\n"
  391. "click this button.")
  392. )
  393. self.locate_button.setMinimumWidth(60)
  394. self.locate_button.setDisabled(True)
  395. form_lay.addRow(self.locate_button)
  396. # Other distances in Gerber
  397. self.title_second_res_label = QtWidgets.QLabel('<b>%s:</b>' % _("Other distances"))
  398. self.title_second_res_label.setToolTip(_("Will display other distances in the Gerber file ordered from\n"
  399. "the minimum to the maximum, not including the absolute minimum."))
  400. form_lay.addRow(self.title_second_res_label)
  401. # Control if to display the locations of where the minimum was found
  402. self.sec_locations_cb = FCCheckBox(_("Other distances points coordinates"))
  403. self.sec_locations_cb.setToolTip(_("Other distances and the coordinates for points\n"
  404. "where the distance was found."))
  405. form_lay.addRow(self.sec_locations_cb)
  406. # this way I can hide/show the frame
  407. self.sec_locations_frame = QtWidgets.QFrame()
  408. self.sec_locations_frame.setContentsMargins(0, 0, 0, 0)
  409. self.layout.addWidget(self.sec_locations_frame)
  410. self.distances_box = QtWidgets.QVBoxLayout()
  411. self.distances_box.setContentsMargins(0, 0, 0, 0)
  412. self.sec_locations_frame.setLayout(self.distances_box)
  413. # Other Distances label
  414. self.distances_label = QtWidgets.QLabel('%s' % _("Gerber distances"))
  415. self.distances_label.setToolTip(_("Other distances and the coordinates for points\n"
  416. "where the distance was found."))
  417. self.distances_box.addWidget(self.distances_label)
  418. # Other distances
  419. self.distances_textb = FCTextArea(parent=self)
  420. self.distances_textb.setPlaceholderText(
  421. _("Other distances and the coordinates for points\n"
  422. "where the distance was found.")
  423. )
  424. self.distances_textb.setReadOnly(True)
  425. stylesheet = """
  426. QTextEdit { selection-background-color:blue;
  427. selection-color:white;
  428. }
  429. """
  430. self.distances_textb.setStyleSheet(stylesheet)
  431. self.distances_box.addWidget(self.distances_textb)
  432. self.distances_box.addWidget(QtWidgets.QLabel(''))
  433. # Other Locations label
  434. self.locations_label = QtWidgets.QLabel('%s' % _("Points coordinates"))
  435. self.locations_label.setToolTip(_("Other distances and the coordinates for points\n"
  436. "where the distance was found."))
  437. self.distances_box.addWidget(self.locations_label)
  438. # Locations where minimum was found
  439. self.locations_sec_textb = FCTextArea(parent=self)
  440. self.locations_sec_textb.setPlaceholderText(
  441. _("Other distances and the coordinates for points\n"
  442. "where the distance was found.")
  443. )
  444. self.locations_sec_textb.setReadOnly(True)
  445. stylesheet = """
  446. QTextEdit { selection-background-color:blue;
  447. selection-color:white;
  448. }
  449. """
  450. self.locations_sec_textb.setStyleSheet(stylesheet)
  451. self.distances_box.addWidget(self.locations_sec_textb)
  452. # Jump button
  453. self.locate_sec_button = QtWidgets.QPushButton(_("Jump to selected position"))
  454. self.locate_sec_button.setToolTip(
  455. _("Select a position in the Locations text box and then\n"
  456. "click this button.")
  457. )
  458. self.locate_sec_button.setMinimumWidth(60)
  459. self.locate_sec_button.setDisabled(True)
  460. self.distances_box.addWidget(self.locate_sec_button)
  461. # GO button
  462. self.calculate_button = QtWidgets.QPushButton(_("Find Minimum"))
  463. self.calculate_button.setToolTip(
  464. _("Calculate the minimum distance between copper features,\n"
  465. "this will allow the determination of the right tool to\n"
  466. "use for isolation or copper clearing.")
  467. )
  468. self.calculate_button.setStyleSheet("""
  469. QPushButton
  470. {
  471. font-weight: bold;
  472. }
  473. """)
  474. self.calculate_button.setMinimumWidth(60)
  475. self.layout.addWidget(self.calculate_button)
  476. self.layout.addStretch()
  477. # ## Reset Tool
  478. self.reset_button = QtWidgets.QPushButton(_("Reset Tool"))
  479. self.reset_button.setIcon(QtGui.QIcon(self.app.resource_location + '/reset32.png'))
  480. self.reset_button.setToolTip(
  481. _("Will reset the tool parameters.")
  482. )
  483. self.reset_button.setStyleSheet("""
  484. QPushButton
  485. {
  486. font-weight: bold;
  487. }
  488. """)
  489. self.layout.addWidget(self.reset_button)
  490. self.loc_ois = OptionalHideInputSection(self.locations_cb, [self.locations_textb, self.locate_button])
  491. self.sec_loc_ois = OptionalHideInputSection(self.sec_locations_cb, [self.sec_locations_frame])
  492. # #################################### FINSIHED GUI ###########################
  493. # #############################################################################
  494. def confirmation_message(self, accepted, minval, maxval):
  495. if accepted is False:
  496. self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%.*f, %.*f]' % (_("Edited value is out of range"),
  497. self.decimals,
  498. minval,
  499. self.decimals,
  500. maxval), False)
  501. else:
  502. self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False)
  503. def confirmation_message_int(self, accepted, minval, maxval):
  504. if accepted is False:
  505. self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%d, %d]' %
  506. (_("Edited value is out of range"), minval, maxval), False)
  507. else:
  508. self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False)