diff --git a/CHANGELOG.md b/CHANGELOG.md index f82676ba..39824f35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,9 @@ CHANGELOG for FlatCAM beta - fixed a typo in the Object UI - in 2Sided Plugin advanced mode fixed the bounds calculation: if no object is selected on canvas then the object selected in Source Object is used - in 2Sided Plugin added a new typ of alignment drills: manual. This mode will no longer add pairs of drill holes mirrored against reference but only add in place drill holes +- in 2Sided Plugin clicking LMB and also pressing CTRL+Shift will add the click coordinates to the Drll Alignment Coordinates +- added support for all Plugins to handle the LMB click release event without connect/reconnect of the mouse events +- code refactoring in app_Main file 3.10.2021 diff --git a/appPlugins/ToolDblSided.py b/appPlugins/ToolDblSided.py index 4a4925ce..ec9c226a 100644 --- a/appPlugins/ToolDblSided.py +++ b/appPlugins/ToolDblSided.py @@ -118,7 +118,6 @@ class DblSidedTool(AppTool): self.ui.object_type_combo.currentIndexChanged.connect(self.on_object_type) self.ui.add_point_button.clicked.connect(self.on_point_add) - self.ui.add_drill_point_button.clicked.connect(self.on_drill_add) self.ui.delete_drill_point_button.clicked.connect(self.on_drill_delete_last) self.ui.box_type_radio.activated_custom.connect(self.on_combo_box_type) @@ -433,21 +432,35 @@ class DblSidedTool(AppTool): self.app.delete_selection_shape() self.ui.axis_location.set_value('point') - # set the reference point for mirror self.ui.point_entry.set_value(center_pt_coords) + self.on_exit() self.app.inform.emit('[success] %s' % _("Mirror reference point set.")) + break elif event.button == right_button and self.app.event_is_dragging is False: self.on_exit(cancelled=True) + def on_mouse_plugin_click_release(self): + modifiers = QtWidgets.QApplication.keyboardModifiers() + # if modifiers == QtCore.Qt.KeyboardModifier.ShiftModifier: + # clip_val = self.app.clipboard.text() + # self.ui.point_entry.set_value(clip_val) + if modifiers == QtCore.Qt.KeyboardModifier.ControlModifier | QtCore.Qt.KeyboardModifier.ShiftModifier: + clip_val = self.app.clipboard.text().replace("[", '').replace("]", '') + self.ui.alignment_holes.set_value(clip_val) + else: + return + self.drill_values = clip_val + def on_exit(self, cancelled=None): self.app.call_source = "app" self.app.ui.notebook.setDisabled(False) + self.disconnect_events() + if cancelled is True: self.app.delete_selection_shape() - self.disconnect_events() self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled by user request.")) def disconnect_events(self): @@ -506,11 +519,6 @@ class DblSidedTool(AppTool): (self.decimals, self.app.pos[0], self.decimals, self.app.pos[1]) self.ui.point_entry.set_value(val) - def on_drill_add(self): - self.drill_values += (self.app.defaults["global_point_clipboard_format"] % - (self.decimals, self.app.pos[0], self.decimals, self.app.pos[1])) + ',' - self.ui.alignment_holes.set_value(self.drill_values) - def on_drill_delete_last(self): drill_values_without_last_tupple = self.drill_values.rpartition('(')[0] self.drill_values = drill_values_without_last_tupple @@ -897,13 +905,6 @@ class DsidedUI: "The (x, y) coordinates are captured by pressing SHIFT key\n" "and left mouse button click on canvas or you can enter the coordinates manually.") ) - # self.add_point_button.setStyleSheet(""" - # QPushButton - # { - # font-weight: bold; - # } - # """) - # self.add_point_button.setMinimumWidth(60) pr_hlay.addWidget(self.point_entry, stretch=1) pr_hlay.addWidget(self.add_point_button) @@ -1072,41 +1073,21 @@ class DsidedUI: "- one drill in mirror position over the axis selected above in the 'Align Axis'.") ) + grid4.addWidget(self.ah_label, 8, 0, 1, 2) + self.alignment_holes = NumericalEvalTupleEntry(border_color='#0069A9') self.alignment_holes.setPlaceholderText(_("Drill coordinates")) - grid4.addWidget(self.ah_label, 8, 0, 1, 2) - grid4.addWidget(self.alignment_holes, 9, 0, 1, 2) - - self.add_drill_point_button = FCButton(_("Add")) - self.add_drill_point_button.setIcon(QtGui.QIcon(self.app.resource_location + '/plus16.png')) - self.add_drill_point_button.setToolTip( - _("Add alignment drill holes coordinates in the format: (x1, y1), (x2, y2), ... \n" - "on one side of the alignment axis.\n\n" - "The coordinates set can be obtained:\n" - "- press SHIFT key and left mouse clicking on canvas. Then click Add.\n" - "- press SHIFT key and left mouse clicking on canvas. Then Ctrl+V in the field.\n" - "- press SHIFT key and left mouse clicking on canvas. Then RMB click in the field and click Paste.\n" - "- by entering the coords manually in the format: (x1, y1), (x2, y2), ...") - ) - # self.add_drill_point_button.setStyleSheet(""" - # QPushButton - # { - # font-weight: bold; - # } - # """) - - self.delete_drill_point_button = FCButton(_("Delete Last")) + self.delete_drill_point_button = QtWidgets.QToolButton() self.delete_drill_point_button.setIcon(QtGui.QIcon(self.app.resource_location + '/trash32.png')) self.delete_drill_point_button.setToolTip( _("Delete the last coordinates tuple in the list.") ) - drill_hlay = QtWidgets.QHBoxLayout() - drill_hlay.addWidget(self.add_drill_point_button) - drill_hlay.addWidget(self.delete_drill_point_button) - - grid4.addLayout(drill_hlay, 10, 0, 1, 2) + hlay = QtWidgets.QHBoxLayout() + hlay.addWidget(self.alignment_holes) + hlay.addWidget(self.delete_drill_point_button) + grid4.addLayout(hlay, 9, 0, 1, 2) FCGridLayout.set_common_column_size([obj_grid, grid_bounds, grid_mirror, grid_box_ref, grid4], 0) diff --git a/app_Main.py b/app_Main.py index ef747968..777112f9 100644 --- a/app_Main.py +++ b/app_Main.py @@ -1231,6 +1231,9 @@ class App(QtCore.QObject): self.corners_tool = None self.etch_tool = None + # when this list will get populated will contain a list of references to all the Plugins in this APp + self.app_plugins = [] + # always install tools only after the shell is initialized because the self.inform.emit() depends on shell try: self.install_tools() @@ -1988,6 +1991,46 @@ class App(QtCore.QObject): self.pcb_wizard_tool.install(icon=QtGui.QIcon(self.resource_location + '/drill32.png'), pos=self.ui.menufileimport) + # create a list of plugins references + self.app_plugins = [ + self.dblsidedtool, + self.distance_tool, + self.distance_min_tool, + self.panelize_tool, + self.film_tool, + self.paste_tool, + self.calculator_tool, + self.rules_tool, + self.sub_tool, + self.move_tool, + + self.cutout_tool, + self.ncclear_tool, + self.paint_tool, + self.isolation_tool, + self.follow_tool, + self.drilling_tool, + self.milling_tool, + self.levelling_tool, + + self.optimal_tool, + self.transform_tool, + self.report_tool, + self.pdf_tool, + self.image_tool, + self.pcb_wizard_tool, + self.cal_exc_tool, + self.qrcode_tool, + self.copper_thieving_tool, + self.fiducial_tool, + self.extract_tool, + self.align_objects_tool, + self.punch_tool, + self.invert_tool, + self.corners_tool, + self.etch_tool + ] + self.log.debug("Tools are installed.") def remove_tools(self): @@ -2012,10 +2055,17 @@ class App(QtCore.QObject): self.log.debug("init_tools()") # delete the data currently in the Tools Tab and the Tab itself - widget = QtWidgets.QTabWidget.widget(self.ui.notebook, 2) + found_idx = None + for tab_idx in range(self.ui.notebook.count()): + if self.ui.notebook.widget(tab_idx).objectName() == "plugin_tab": + found_idx = tab_idx + print(found_idx) + break + remove_idx = found_idx if found_idx else 2 + widget = QtWidgets.QTabWidget.widget(self.ui.notebook, remove_idx) if widget is not None: widget.deleteLater() - self.ui.notebook.removeTab(2) + self.ui.notebook.removeTab(remove_idx) # rebuild the Tools Tab # self.ui.plugin_tab = QtWidgets.QWidget() @@ -2677,7 +2727,7 @@ class App(QtCore.QObject): break if found_idx: self.ui.notebook.setCurrentWidget(self.ui.properties_tab) - self.ui.notebook.removeTab(2) + self.ui.notebook.removeTab(found_idx) if edited_obj.kind == 'geometry': obj_type = "Geometry" @@ -6701,7 +6751,7 @@ class App(QtCore.QObject): break if found_idx: self.ui.notebook.setCurrentWidget(self.ui.properties_tab) - self.ui.notebook.removeTab(2) + self.ui.notebook.removeTab(found_idx) # HACK: the content was removed but let's create it again self.ui.plugin_tab = QtWidgets.QWidget() @@ -6759,7 +6809,7 @@ class App(QtCore.QObject): self.app_obj.object_changed.emit(obj) self.inform.emit('[success] %s.' % _("Flip on Y axis done")) except Exception as e: - self.app.inform.emit('[ERROR_NOTCL] %s: %s.' % (_("Action was not executed"), str(e))) + self.inform.emit('[ERROR_NOTCL] %s: %s.' % (_("Action was not executed"), str(e))) return def on_flipx(self): @@ -6805,7 +6855,7 @@ class App(QtCore.QObject): self.app_obj.object_changed.emit(obj) self.inform.emit('[success] %s.' % _("Flip on X axis done")) except Exception as e: - self.app.inform.emit('[ERROR_NOTCL] %s: %s.' % (_("Action was not executed"), str(e))) + self.inform.emit('[ERROR_NOTCL] %s: %s.' % (_("Action was not executed"), str(e))) return def on_rotate(self, silent=False, preset=None): @@ -7310,90 +7360,143 @@ class App(QtCore.QObject): # if the released mouse button was RMB then test if it was a panning motion or not, if not it was a context # canvas menu if event.button == right_button and self.ui.popMenu.mouse_is_panning is False: # right click - self.ui.popMenu.mouse_is_panning = False - - if self.inhibit_context_menu is False: - self.cursor = QtGui.QCursor() - self.populate_cmenu_grids() - self.ui.popMenu.popup(self.cursor.pos()) + self.on_mouse_context_menu() # if the released mouse button was LMB then test if we had a right-to-left selection or a left-to-right # selection and then select a type of selection ("enclosing" or "touching") if event.button == 1: # left click - modifiers = QtWidgets.QApplication.keyboardModifiers() - # If the SHIFT key is pressed when LMB is clicked then the coordinates are copied to clipboard - if modifiers == QtCore.Qt.KeyboardModifier.ShiftModifier: - # do not auto open the Project Tab - self.click_noproject = True + key_modifier = QtWidgets.QApplication.keyboardModifiers() + shift_modifier_key = QtCore.Qt.KeyboardModifier.ShiftModifier + ctrl_modifier_key = QtCore.Qt.KeyboardModifier.ControlModifier + ctrl_shift_modifier_key = ctrl_modifier_key | shift_modifier_key - self.clipboard.setText( - self.defaults["global_point_clipboard_format"] % - (self.decimals, self.pos[0], self.decimals, self.pos[1]) - ) - self.inform.emit('[success] %s' % _("Coordinates copied to clipboard.")) + if key_modifier == shift_modifier_key or key_modifier == ctrl_shift_modifier_key: + self.on_mouse_and_key_modifiers(position=self.pos, modifiers=key_modifier) + self.on_mouse_plugin_click_release() return + else: + self.on_mouse_plugin_click_release() - # the object selection on canvas does not work for App Tools or for Editors + # the object selection on canvas will not work for App Tools or for Editors if self.call_source != 'app': return if self.doubleclick is True: - self.doubleclick = False - if self.collection.get_selected(): - self.ui.notebook.setCurrentWidget(self.ui.properties_tab) - if self.ui.splitter.sizes()[0] == 0: - self.ui.splitter.setSizes([1, 1]) - try: - # delete the selection shape(S) as it may be in the way - self.delete_selection_shape() - self.delete_hover_shape() - except Exception as e: - self.log.error( - "FlatCAMApp.on_mouse_click_release_over_plot() double click --> Error: %s" % str(e)) - return + self.on_mouse_double_click() + return + + # WORKAROUND for LEGACY MODE + if self.is_legacy is True: + # if there is no move on canvas then we have no dragging selection + if self.dx == 0 or self.dy == 0: + self.selection_type = None + + if self.selection_type is not None: + try: + self.selection_area_handler(self.pos, pos, self.selection_type) + self.selection_type = None + except Exception as e: + self.log.error( + "FlatCAMApp.on_mouse_click_release_over_plot() select area --> Error: %s" % str(e)) + return + + if key_modifier == shift_modifier_key: + mod_key = 'Shift' + elif key_modifier == ctrl_modifier_key: + mod_key = 'Control' else: - # WORKAROUND for LEGACY MODE - if self.is_legacy is True: - # if there is no move on canvas then we have no dragging selection - if self.dx == 0 or self.dy == 0: - self.selection_type = None + mod_key = None - if self.selection_type is not None: - try: - self.selection_area_handler(self.pos, pos, self.selection_type) - self.selection_type = None - except Exception as e: - self.log.error( - "FlatCAMApp.on_mouse_click_release_over_plot() select area --> Error: %s" % str(e)) - return - else: - key_modifier = QtWidgets.QApplication.keyboardModifiers() - if key_modifier == QtCore.Qt.KeyboardModifier.ShiftModifier: - mod_key = 'Shift' - elif key_modifier == QtCore.Qt.KeyboardModifier.ControlModifier: - mod_key = 'Control' + try: + if self.command_active is None: + if mod_key == self.defaults["global_mselect_key"]: + # If the modifier key is pressed when the LMB is clicked then if the object is selected it will + # deselect, and if it's not selected then it will be selected + self.select_objects(key='multisel') else: - mod_key = None + # If there is no active command (self.command_active is None) then we check if + # we clicked on a object by checking the bounding limits against mouse click position + self.select_objects() - try: - if self.command_active is None: - # If the CTRL key is pressed when the LMB is clicked then if the object is selected it will - # deselect, and if it's not selected then it will be selected - # If there is no active command (self.command_active is None) then we check if we clicked - # on a object by checking the bounding limits against mouse click position - if mod_key == self.defaults["global_mselect_key"]: - self.select_objects(key='multisel') - else: - # If there is no active command (self.command_active is None) then we check if - # we clicked on a object by checking the bounding limits against mouse click position - self.select_objects() + self.delete_hover_shape() + except Exception as e: + self.log.error( + "FlatCAMApp.on_mouse_click_release_over_plot() select click --> Error: %s" % str(e)) + return - self.delete_hover_shape() - except Exception as e: - self.log.error( - "FlatCAMApp.on_mouse_click_release_over_plot() select click --> Error: %s" % str(e)) - return + def on_mouse_double_click(self): + """ + Called when mouse double clicking on canvas. + + :return: + """ + self.doubleclick = False + if self.collection.get_selected(): + self.ui.notebook.setCurrentWidget(self.ui.properties_tab) + if self.ui.splitter.sizes()[0] == 0: + self.ui.splitter.setSizes([1, 1]) + try: + # delete the selection shape(S) as it may be in the way + self.delete_selection_shape() + self.delete_hover_shape() + except Exception as e: + self.log.error( + "FlatCAMApp.on_mouse_click_release_over_plot() double click --> Error: %s" % str(e)) + + def on_mouse_and_key_modifiers(self, position, modifiers): + """ + Called when the mouse is left-clicked on canvas and simultaneously a key modifier + (Ctrl, AAlt, Shift) is pressed. + + :param position: A tupple made of the clicked position x, y coordinates + :param modifiers: Key modifiers (Ctrl, Alt, Shift or a combination of them) + :return: + """ + + # If the SHIFT key is pressed when LMB is clicked then the coordinates are copied to clipboard + if modifiers == QtCore.Qt.KeyboardModifier.ShiftModifier: + # do not auto open the Project Tab + self.click_noproject = True + + self.clipboard.setText( + self.defaults["global_point_clipboard_format"] % + (self.decimals,position[0], self.decimals, position[1]) + ) + self.inform.emit('[success] %s' % _("Coordinates copied to clipboard.")) + elif modifiers == QtCore.Qt.KeyboardModifier.ControlModifier | QtCore.Qt.KeyboardModifier.ShiftModifier: + try: + old_clipb = eval(self.clipboard.text()) + except Exception as err: + # self.log.error("App.on_mouse_and_key_modifiers() --> %s" % str(err)) + old_clipb = None + + clip_pos_val = ( + self.dec_format(position[0], self.decimals), + self.dec_format(position[1], self.decimals) + ) + clip_text = "(%s, %s)" % (str(clip_pos_val[0]), str(clip_pos_val[1])) + + if old_clipb is None or old_clipb == '': + self.clipboard.setText(clip_text) + else: + if isinstance(old_clipb, list): + old_clipb.append(clip_pos_val) + else: + old_clipb = [old_clipb, clip_pos_val] + self.clipboard.setText(str(old_clipb)) + self.inform.emit('[success] %s' % _("Coordinates copied to clipboard.")) + + def on_mouse_context_menu(self): + """ + Display a context menu when mouse right-clicking on canvas. + + :return: + """ + if self.inhibit_context_menu is False: + self.cursor = QtGui.QCursor() + self.populate_cmenu_grids() + self.ui.popMenu.popup(self.cursor.pos()) def selection_area_handler(self, start_pos, end_pos, sel_type): """ @@ -7613,6 +7716,25 @@ class App(QtCore.QObject): tx=_("selected")) ) + def on_mouse_plugin_click_release(self): + """ + Handle specific tasks in the Plugins for the mouse click release + + :return: + """ + + if self.ui.notebook.currentWidget().objectName() == "plugin_tab": + tab_idx = self.ui.notebook.currentIndex() + for plugin in self.app_plugins: + # execute this only for the current active plugin + if self.ui.notebook.tabText(tab_idx) == plugin.pluginName: + try: + plugin.on_mouse_plugin_click_release() + except AttributeError: + # not all plugins have this implemented + # print("This does not have it", self.ui.notebook.tabText(tab_idx)) + pass + def delete_hover_shape(self): self.hover_shapes.clear() self.hover_shapes.redraw()