ToolOptimal.py 26 KB

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