# ########################################################## # FlatCAM: 2D Post-processing for Manufacturing # # File by: Marius Adrian Stanciu (c) # # Date: 11/12/2020 # # License: MIT Licence # # ########################################################## from PyQt6 import QtWidgets, QtCore, QtGui from appTool import AppTool from appGUI.GUIElements import RadioSet, FCButton, FCComboBox, FCLabel, VerticalScrollArea, FCGridLayout from appParsers.ParseGerber import Gerber from copy import deepcopy import numpy as np from shapely.ops import unary_union from shapely.geometry import Polygon from matplotlib.backend_bases import KeyEvent as mpl_key_event import logging import gettext import appTranslation as fcTranslate import builtins fcTranslate.apply_language('strings') if '_' not in builtins.__dict__: _ = gettext.gettext log = logging.getLogger('base') class ToolFollow(AppTool, Gerber): optimal_found_sig = QtCore.pyqtSignal(float) def __init__(self, app): self.app = app self.decimals = self.app.decimals AppTool.__init__(self, app) Gerber.__init__(self, steps_per_circle=self.app.defaults["gerber_circle_steps"]) # ############################################################################# # ######################### Tool GUI ########################################## # ############################################################################# self.ui = FollowUI(layout=self.layout, app=self.app) self.pluginName = self.ui.pluginName self.connect_signals_at_init() # disconnect flags self.area_sel_disconnect_flag = False self.first_click = False self.cursor_pos = None self.mouse_is_dragging = False self.mm = None self.mp = None self.mr = None self.kp = None self.sel_rect = [] # store here the points for the "Polygon" area selection shape self.points = [] # set this as True when in middle of drawing a "Polygon" area selection shape # it is made False by first click to signify that the shape is complete self.poly_drawn = False def install(self, icon=None, separator=None, **kwargs): AppTool.install(self, icon, separator, shortcut='', **kwargs) def run(self, toggle=True): self.app.defaults.report_usage("ToolFollow()") if toggle: # if the splitter is hidden, display it if self.app.ui.splitter.sizes()[0] == 0: self.app.ui.splitter.setSizes([1, 1]) # if the Tool Tab is hidden display it, else hide it but only if the objectName is the same found_idx = None for idx in range(self.app.ui.notebook.count()): if self.app.ui.notebook.widget(idx).objectName() == "plugin_tab": found_idx = idx break # show the Tab if not found_idx: try: self.app.ui.notebook.addTab(self.app.ui.plugin_tab, _("Plugin")) except RuntimeError: self.app.ui.plugin_tab = QtWidgets.QWidget() self.app.ui.plugin_tab.setObjectName("plugin_tab") self.app.ui.plugin_tab_layout = QtWidgets.QVBoxLayout(self.app.ui.plugin_tab) self.app.ui.plugin_tab_layout.setContentsMargins(2, 2, 2, 2) self.app.ui.plugin_scroll_area = VerticalScrollArea() self.app.ui.plugin_tab_layout.addWidget(self.app.ui.plugin_scroll_area) self.app.ui.notebook.addTab(self.app.ui.plugin_tab, _("Plugin")) # focus on Tool Tab self.app.ui.notebook.setCurrentWidget(self.app.ui.plugin_tab) try: if self.app.ui.plugin_scroll_area.widget().objectName() == self.pluginName and found_idx: # if the Tool Tab is not focused, focus on it if not self.app.ui.notebook.currentWidget() is self.app.ui.plugin_tab: # focus on Tool Tab self.app.ui.notebook.setCurrentWidget(self.app.ui.plugin_tab) else: # else remove the Tool Tab self.app.ui.notebook.setCurrentWidget(self.app.ui.properties_tab) self.app.ui.notebook.removeTab(2) # if there are no objects loaded in the app then hide the Notebook widget if not self.app.collection.get_list(): self.app.ui.splitter.setSizes([0, 1]) except AttributeError: pass else: if self.app.ui.splitter.sizes()[0] == 0: self.app.ui.splitter.setSizes([1, 1]) AppTool.run(self) self.set_tool_ui() self.app.ui.notebook.setTabText(2, _("Follow")) def connect_signals_at_init(self): self.ui.level.toggled.connect(self.on_level_changed) self.ui.selectmethod_radio.activated_custom.connect(self.ui.on_selection) self.ui.generate_geometry_button.clicked.connect(self.on_generate_geometry_click) def set_tool_ui(self): self.units = self.app.defaults['units'].upper() self.clear_ui(self.layout) self.ui = FollowUI(layout=self.layout, app=self.app) self.pluginName = self.ui.pluginName self.connect_signals_at_init() self.ui.selectmethod_radio.set_value('all') # _("All") self.ui.area_shape_radio.set_value('square') self.sel_rect[:] = [] self.points = [] self.poly_drawn = False self.area_sel_disconnect_flag = False # SELECT THE CURRENT OBJECT obj = self.app.collection.get_active() if obj and obj.kind == 'gerber': obj_name = obj.options['name'] self.ui.object_combo.set_value(obj_name) # Show/Hide Advanced Options app_mode = self.app.defaults["global_app_level"] self.change_level(app_mode) def change_level(self, level): """ :param level: application level: either 'b' or 'a' :type level: str :return: """ if level == 'a': self.ui.level.setChecked(True) else: self.ui.level.setChecked(False) self.on_level_changed(self.ui.level.isChecked()) def on_level_changed(self, checked): if not checked: self.ui.level.setText('%s' % _('Beginner')) self.ui.level.setStyleSheet(""" QToolButton { color: green; } """) # Add Tool section self.ui.select_label.hide() self.ui.selectmethod_radio.hide() self.ui.param_title.hide() self.ui.separator_line.hide() else: self.ui.level.setText('%s' % _('Advanced')) self.ui.level.setStyleSheet(""" QToolButton { color: red; } """) # Add Tool section self.ui.select_label.show() self.ui.selectmethod_radio.show() self.ui.param_title.show() self.ui.separator_line.show() def on_generate_geometry_click(self): obj_name = self.ui.object_combo.currentText() # Get source object. try: obj = self.app.collection.get_by_name(obj_name) except Exception as e: self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object"), str(obj_name))) return "Could not retrieve object: %s with error: %s" % (obj_name, str(e)) if obj is None: self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Object not found"), str(obj_name))) return formatted_name = obj_name.rpartition('.')[0] if formatted_name == '': formatted_name = obj_name outname = '%s_follow' % formatted_name select_method = self.ui.selectmethod_radio.get_value() if select_method == 'all': # _("All") self.follow_all(obj, outname) else: self.app.inform.emit('[WARNING_NOTCL] %s' % _("Click the start point of the area.")) if self.app.is_legacy is False: self.app.plotcanvas.graph_event_disconnect('mouse_press', self.app.on_mouse_click_over_plot) self.app.plotcanvas.graph_event_disconnect('mouse_move', self.app.on_mouse_move_over_plot) self.app.plotcanvas.graph_event_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot) else: self.app.plotcanvas.graph_event_disconnect(self.app.mp) self.app.plotcanvas.graph_event_disconnect(self.app.mm) self.app.plotcanvas.graph_event_disconnect(self.app.mr) self.mr = self.app.plotcanvas.graph_event_connect('mouse_release', self.on_mouse_release) self.mm = self.app.plotcanvas.graph_event_connect('mouse_move', self.on_mouse_move) self.kp = self.app.plotcanvas.graph_event_connect('key_press', self.on_key_press) # disconnect flags self.area_sel_disconnect_flag = True def follow_all(self, obj, outname): def job_thread(tool_obj): tool_obj.follow_geo(obj, outname) self.app.worker_task.emit({'fcn': job_thread, 'params': [self]}) def follow_area(self): obj_name = self.ui.object_combo.currentText() # Get source object. try: obj = self.app.collection.get_by_name(obj_name) except Exception as e: self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object"), str(obj_name))) return "Could not retrieve object: %s with error: %s" % (obj_name, str(e)) if obj is None: self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Object not found"), str(self.obj_name))) return formatted_name = obj_name.rpartition('.')[0] if formatted_name == '': formatted_name = obj_name outname = '%s_follow' % formatted_name def job_thread(tool_obj): tool_obj.follow_geo_area(obj, outname) self.app.worker_task.emit({'fcn': job_thread, 'params': [self]}) def follow_geo(self, followed_obj, outname): """ Creates a geometry object "following" the gerber paths. :param followed_obj: Gerber object for which to generate the follow geometry :type followed_obj: AppObjects.FlatCAMGerber.GerberObject :param outname: Nme of the resulting Geometry object :type outname: str :return: None """ def follow_init(new_obj, app_obj): if type(app_obj.defaults["tools_mill_tooldia"]) == float: tools_list = [app_obj.defaults["tools_mill_tooldia"]] else: try: temp_tools = app_obj.defaults["tools_mill_tooldia"].split(",") tools_list = [ float(eval(dia)) for dia in temp_tools if dia != '' ] except Exception as e: log.error("ToolFollow.follow_geo -> At least one tool diameter needed. -> %s" % str(e)) return 'fail' # store here the default data for Geometry Data new_data = {} for opt_key in app_obj.options: if opt_key.find('geometry' + "_") == 0: oname = opt_key[len('geometry') + 1:] new_data[oname] = app_obj.options[opt_key] if opt_key.find('tools_') == 0: new_data[opt_key] = app_obj.options[opt_key] try: __ = iter(followed_obj.follow_geometry) except TypeError: followed_obj.follow_geometry = [followed_obj.follow_geometry] follow_geo = [ g for g in followed_obj.follow_geometry if g and not g.is_empty and g.is_valid and g.geom_type != 'Point' ] if not follow_geo: self.app.log.warning("ToolFollow.follow_geo() -> Empty Follow Geometry") return 'fail' # Propagate options new_obj.options["tools_mill_tooldia"] = app_obj.defaults["tools_mill_tooldia"] new_obj.solid_geometry = follow_geo new_obj.tools = { 1: { 'tooldia': app_obj.dec_format(float(tools_list[0]), self.decimals), 'offset': 'Path', 'offset_value': 0.0, 'type': 'Rough', 'tool_type': 'C1', 'data': deepcopy(new_data), 'solid_geometry': new_obj.solid_geometry } } ret = self.app.app_obj.new_object("geometry", outname, follow_init) if ret == 'fail': self.app.inform.emit("[ERROR_NOTCL] %s" % _("Failed to create Follow Geometry.")) else: self.app.inform.emit("[success] %s" % _("Done.")) def follow_geo_area(self, followed_obj, outname): """ Creates a geometry object "following" the gerber paths. :param followed_obj: Gerber object for which to generate the follow geometry :type followed_obj: AppObjects.FlatCAMGerber.GerberObject :param outname: Nme of the resulting Geometry object :type outname: str :return: None """ def follow_init(new_obj, app_obj): new_obj.multigeo = True if type(app_obj.defaults["tools_mill_tooldia"]) == float: tools_list = [app_obj.defaults["tools_mill_tooldia"]] else: try: temp_tools = app_obj.defaults["tools_mill_tooldia"].split(",") tools_list = [ float(eval(dia)) for dia in temp_tools if dia != '' ] except Exception as e: log.error("ToolFollow.follow_geo -> At least one tool diameter needed. -> %s" % str(e)) return 'fail' # store here the default data for Geometry Data new_data = {} for opt_key, opt_val in app_obj.options.items(): if opt_key.find('geometry' + "_") == 0: oname = opt_key[len('geometry') + 1:] new_data[oname] = app_obj.options[opt_key] if opt_key.find('tools_') == 0: new_data[opt_key] = app_obj.options[opt_key] # Propagate options new_obj.options["tools_mill_tooldia"] = app_obj.defaults["tools_mill_tooldia"] new_data["tools_mill_tooldia"] = app_obj.defaults["tools_mill_tooldia"] target_geo = unary_union(followed_obj.follow_geometry) area_follow = target_geo.intersection(deepcopy(unary_union(self.sel_rect))) self.sel_rect[:] = [] self.points = [] try: __ = iter(area_follow) except TypeError: area_follow = [area_follow] cleaned_area_follow = [g for g in area_follow if not g.is_empty and g.is_valid and g.geom_type != 'Point'] new_obj.solid_geometry = deepcopy(cleaned_area_follow) new_obj.tools = { 1: { 'tooldia': app_obj.dec_format(float(tools_list[0]), self.decimals), 'offset': 'Path', 'offset_value': 0.0, 'type': 'Rough', 'tool_type': 'C1', 'data': deepcopy(new_data), 'solid_geometry': new_obj.solid_geometry } } ret = self.app.app_obj.new_object("geometry", outname, follow_init) if ret == 'fail': self.app.inform.emit("[ERROR_NOTCL] %s" % _("Failed to create Follow Geometry.")) else: self.app.inform.emit("[success] %s" % _("Done.")) # To be called after clicking on the plot. def on_mouse_release(self, event): if self.app.is_legacy is False: event_pos = event.pos # event_is_dragging = event.is_dragging right_button = 2 else: event_pos = (event.xdata, event.ydata) right_button = 3 try: x = float(event_pos[0]) y = float(event_pos[1]) except TypeError: return event_pos = (x, y) shape_type = self.ui.area_shape_radio.get_value() curr_pos = self.app.plotcanvas.translate_coords(event_pos) if self.app.grid_status(): curr_pos = self.app.geo_editor.snap(curr_pos[0], curr_pos[1]) x1, y1 = curr_pos[0], curr_pos[1] # do paint single only for left mouse clicks if event.button == 1: if shape_type == "square": if not self.first_click: self.first_click = True self.app.inform.emit('[WARNING_NOTCL] %s' % _("Click the end point of the area.")) self.cursor_pos = self.app.plotcanvas.translate_coords(event_pos) if self.app.grid_status(): self.cursor_pos = self.app.geo_editor.snap(self.cursor_pos[0], self.cursor_pos[1]) else: self.app.inform.emit(_("Zone added. Click to start adding next zone or right click to finish.")) self.app.delete_selection_shape() x0, y0 = self.cursor_pos[0], self.cursor_pos[1] pt1 = (x0, y0) pt2 = (x1, y0) pt3 = (x1, y1) pt4 = (x0, y1) new_rectangle = Polygon([pt1, pt2, pt3, pt4]) self.sel_rect.append(new_rectangle) # add a temporary shape on canvas self.draw_tool_selection_shape(old_coords=(x0, y0), coords=(x1, y1)) self.first_click = False return else: self.points.append((x1, y1)) if len(self.points) > 1: self.poly_drawn = True self.app.inform.emit(_("Click on next Point or click right mouse button to complete ...")) return "" elif event.button == right_button and self.mouse_is_dragging is False: shape_type = self.ui.area_shape_radio.get_value() if shape_type == "square": self.first_click = False else: # if we finish to add a polygon if self.poly_drawn is True: try: # try to add the point where we last clicked if it is not already in the self.points last_pt = (x1, y1) if last_pt != self.points[-1]: self.points.append(last_pt) except IndexError: pass # we need to add a Polygon and a Polygon can be made only from at least 3 points if len(self.points) > 2: self.delete_moving_selection_shape() pol = Polygon(self.points) # do not add invalid polygons even if they are drawn by utility geometry if pol.is_valid: self.sel_rect.append(pol) self.draw_selection_shape_polygon(points=self.points) self.app.inform.emit( _("Zone added. Click to start adding next zone or right click to finish.")) self.points = [] self.poly_drawn = False return self.delete_tool_selection_shape() if self.app.is_legacy is False: self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_mouse_release) self.app.plotcanvas.graph_event_disconnect('mouse_move', self.on_mouse_move) self.app.plotcanvas.graph_event_disconnect('key_press', self.on_key_press) else: self.app.plotcanvas.graph_event_disconnect(self.mr) self.app.plotcanvas.graph_event_disconnect(self.mm) self.app.plotcanvas.graph_event_disconnect(self.kp) self.app.mp = self.app.plotcanvas.graph_event_connect('mouse_press', self.app.on_mouse_click_over_plot) self.app.mm = self.app.plotcanvas.graph_event_connect('mouse_move', self.app.on_mouse_move_over_plot) self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release', self.app.on_mouse_click_release_over_plot) # disconnect flags self.area_sel_disconnect_flag = False if len(self.sel_rect) == 0: return self.follow_area() # called on mouse move def on_mouse_move(self, event): shape_type = self.ui.area_shape_radio.get_value() if self.app.is_legacy is False: event_pos = event.pos event_is_dragging = event.is_dragging # right_button = 2 else: event_pos = (event.xdata, event.ydata) event_is_dragging = self.app.plotcanvas.is_dragging # right_button = 3 try: x = float(event_pos[0]) y = float(event_pos[1]) except TypeError: return curr_pos = self.app.plotcanvas.translate_coords((x, y)) # detect mouse dragging motion if event_is_dragging == 1: self.mouse_is_dragging = True else: self.mouse_is_dragging = False # update the cursor position if self.app.grid_status(): # Update cursor curr_pos = self.app.geo_editor.snap(curr_pos[0], curr_pos[1]) self.app.app_cursor.set_data(np.asarray([(curr_pos[0], curr_pos[1])]), symbol='++', edge_color=self.app.cursor_color_3D, edge_width=self.app.defaults["global_cursor_width"], size=self.app.defaults["global_cursor_size"]) if self.cursor_pos is None: self.cursor_pos = (0, 0) self.app.dx = curr_pos[0] - float(self.cursor_pos[0]) self.app.dy = curr_pos[1] - float(self.cursor_pos[1]) # # update the positions on status bar # self.app.ui.position_label.setText(" X: %.4f   " # "Y: %.4f " % (curr_pos[0], curr_pos[1])) # self.app.ui.rel_position_label.setText("Dx: %.4f   Dy: " # "%.4f    " % (self.app.dx, self.app.dy)) self.app.ui.update_location_labels(self.app.dx, self.app.dy, curr_pos[0], curr_pos[1]) units = self.app.defaults["units"].lower() # self.app.plotcanvas.text_hud.text = \ # 'Dx:\t{:<.4f} [{:s}]\nDy:\t{:<.4f} [{:s}]\n\nX: \t{:<.4f} [{:s}]\nY: \t{:<.4f} [{:s}]'.format( # self.app.dx, units, self.app.dy, units, curr_pos[0], units, curr_pos[1], units) self.app.plotcanvas.on_update_text_hud(self.app.dx, self.app.dy, curr_pos[0], curr_pos[1]) # draw the utility geometry if shape_type == "square": if self.first_click: self.app.delete_selection_shape() self.app.draw_moving_selection_shape(old_coords=(self.cursor_pos[0], self.cursor_pos[1]), coords=(curr_pos[0], curr_pos[1])) else: self.delete_moving_selection_shape() self.draw_moving_selection_shape_poly(points=self.points, data=(curr_pos[0], curr_pos[1])) def on_key_press(self, event): # modifiers = QtWidgets.QApplication.keyboardModifiers() # matplotlib_key_flag = False # events out of the self.app.collection view (it's about Project Tab) are of type int if type(event) is int: key = event # events from the GUI are of type QKeyEvent elif type(event) == QtGui.QKeyEvent: key = event.key() elif isinstance(event, mpl_key_event): # MatPlotLib key events are trickier to interpret than the rest # matplotlib_key_flag = True key = event.key key = QtGui.QKeySequence(key) # check for modifiers key_string = key.toString().lower() if '+' in key_string: mod, __, key_text = key_string.rpartition('+') if mod.lower() == 'ctrl': # modifiers = QtCore.Qt.KeyboardModifier.ControlModifier pass elif mod.lower() == 'alt': # modifiers = QtCore.Qt.KeyboardModifier.AltModifier pass elif mod.lower() == 'shift': # modifiers = QtCore.Qt.KeyboardModifier. pass else: # modifiers = QtCore.Qt.KeyboardModifier.NoModifier pass key = QtGui.QKeySequence(key_text) # events from Vispy are of type KeyEvent else: key = event.key if key == QtCore.Qt.Key.Key_Escape or key == 'Escape': if self.area_sel_disconnect_flag is True: try: if self.app.is_legacy is False: self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_mouse_release) self.app.plotcanvas.graph_event_disconnect('mouse_move', self.on_mouse_move) self.app.plotcanvas.graph_event_disconnect('key_press', self.on_key_press) else: self.app.plotcanvas.graph_event_disconnect(self.mr) self.app.plotcanvas.graph_event_disconnect(self.mm) self.app.plotcanvas.graph_event_disconnect(self.kp) except Exception as e: log.error("ToolFollow.on_key_press() _1 --> %s" % str(e)) self.app.mp = self.app.plotcanvas.graph_event_connect('mouse_press', self.app.on_mouse_click_over_plot) self.app.mm = self.app.plotcanvas.graph_event_connect('mouse_move', self.app.on_mouse_move_over_plot) self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release', self.app.on_mouse_click_release_over_plot) self.points = [] self.poly_drawn = False self.sel_rect[:] = [] self.delete_moving_selection_shape() self.delete_tool_selection_shape() class FollowUI: pluginName = _("Follow") def __init__(self, layout, app): self.app = app self.decimals = self.app.decimals self.layout = layout self.tools_frame = QtWidgets.QFrame() self.tools_frame.setContentsMargins(0, 0, 0, 0) self.layout.addWidget(self.tools_frame) self.tools_box = QtWidgets.QVBoxLayout() self.tools_box.setContentsMargins(0, 0, 0, 0) self.tools_frame.setLayout(self.tools_box) self.title_box = QtWidgets.QHBoxLayout() self.tools_box.addLayout(self.title_box) # ## Title title_label = FCLabel("%s" % self.pluginName) title_label.setStyleSheet(""" QLabel { font-size: 16px; font-weight: bold; } """) title_label.setToolTip( _("Create a Geometry object with\n" "toolpaths to cut through the middle of polygons.") ) self.title_box.addWidget(title_label) # App Level label self.level = QtWidgets.QToolButton() self.level.setToolTip( _( "Beginner Mode - many parameters are hidden.\n" "Advanced Mode - full control.\n" "Permanent change is done in 'Preferences' menu." ) ) self.level.setCheckable(True) self.title_box.addWidget(self.level) self.obj_combo_label = FCLabel('%s:' % _("GERBER")) self.obj_combo_label.setToolTip( _("Source object for following geometry.") ) self.tools_box.addWidget(self.obj_combo_label) # ############################################################################################################# # ################################ The object to be followed ################################################## # ############################################################################################################# self.object_combo = FCComboBox() self.object_combo.setModel(self.app.collection) self.object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex())) # self.object_combo.setCurrentIndex(1) self.object_combo.is_last = True self.tools_box.addWidget(self.object_combo) separator_line = QtWidgets.QFrame() separator_line.setFrameShape(QtWidgets.QFrame.Shape.HLine) separator_line.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken) self.tools_box.addWidget(separator_line) grid0 = FCGridLayout(v_spacing=5, h_spacing=3) grid0.setColumnStretch(0, 0) grid0.setColumnStretch(1, 1) self.tools_box.addLayout(grid0) # Parameter Title self.param_title = FCLabel("%s:" % _("Parameters")) grid0.addWidget(self.param_title, 0, 0, 1, 2) # Polygon selection self.select_label = FCLabel('%s:' % _('Selection')) self.select_label.setToolTip( _("Selection of area to be processed.\n" "- 'All Polygons' - the process will start after click.\n" "- 'Area Selection' - left mouse click to start selection of the area to be processed.") ) self.selectmethod_radio = RadioSet([{'label': _("All"), 'value': 'all'}, {'label': _("Area Selection"), 'value': 'area'}]) grid0.addWidget(self.select_label, 2, 0) grid0.addWidget(self.selectmethod_radio, 2, 1) # Area Selection shape self.area_shape_label = FCLabel('%s:' % _("Shape")) self.area_shape_label.setToolTip( _("The kind of selection shape used for area selection.") ) self.area_shape_radio = RadioSet([{'label': _("Square"), 'value': 'square'}, {'label': _("Polygon"), 'value': 'polygon'}]) grid0.addWidget(self.area_shape_label, 4, 0) grid0.addWidget(self.area_shape_radio, 4, 1) self.area_shape_label.hide() self.area_shape_radio.hide() self.separator_line = QtWidgets.QFrame() self.separator_line.setFrameShape(QtWidgets.QFrame.Shape.HLine) self.separator_line.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken) grid0.addWidget(self.separator_line, 6, 0, 1, 2) self.generate_geometry_button = FCButton("%s" % _("Generate Geometry")) self.generate_geometry_button.setIcon(QtGui.QIcon(self.app.resource_location + '/geometry32.png')) self.generate_geometry_button.setStyleSheet(""" QPushButton { font-weight: bold; } """) self.generate_geometry_button.setToolTip(_("Generate a 'Follow' geometry.\n" "This means that it will cut through\n" "the middle of the trace.")) self.tools_box.addWidget(self.generate_geometry_button) self.tools_box.addStretch() # ## Reset Tool self.reset_button = FCButton(_("Reset Tool")) self.reset_button.setIcon(QtGui.QIcon(self.app.resource_location + '/reset32.png')) self.reset_button.setToolTip( _("Will reset the tool parameters.") ) self.reset_button.setStyleSheet(""" QPushButton { font-weight: bold; } """) self.tools_box.addWidget(self.reset_button) # ############################ FINISHED GUI ################################### # ############################################################################# def on_selection(self, val): if val == 'area': # _("Area Selection") self.area_shape_label.show() self.area_shape_radio.show() else: # All self.area_shape_label.hide() self.area_shape_radio.hide() def confirmation_message(self, accepted, minval, maxval): if accepted is False: self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%.*f, %.*f]' % (_("Edited value is out of range"), self.decimals, minval, self.decimals, maxval), False) else: self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False) def confirmation_message_int(self, accepted, minval, maxval): if accepted is False: self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%d, %d]' % (_("Edited value is out of range"), minval, maxval), False) else: self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False)