diff --git a/FlatCAMApp.py b/FlatCAMApp.py index ca789ec1..7d89429b 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -7996,7 +7996,7 @@ class App(QtCore.QObject): except Exception as e: App.log.debug("App.on_mouse_click_over_plot() --> Outside plot? --> %s" % str(e)) - def on_double_click_over_plot(self, event): + def on_mouse_double_click_over_plot(self, event): if event.button == 1: self.doubleclick = True @@ -8207,7 +8207,12 @@ class App(QtCore.QObject): """ poly_selection = Polygon([start_pos, (end_pos[0], start_pos[1]), end_pos, (start_pos[0], end_pos[1])]) + # delete previous selection shape self.delete_selection_shape() + + # make all objects inactive + self.collection.set_all_inactive() + for obj in self.collection.get_list(): try: # select the object(s) only if it is enabled (plotted) @@ -11510,7 +11515,7 @@ class App(QtCore.QObject): self.mm = self.plotcanvas.graph_event_connect('mouse_move', self.on_mouse_move_over_plot) self.mp = self.plotcanvas.graph_event_connect('mouse_press', self.on_mouse_click_over_plot) self.mr = self.plotcanvas.graph_event_connect('mouse_release', self.on_mouse_click_release_over_plot) - self.mdc = self.plotcanvas.graph_event_connect('mouse_double_click', self.on_double_click_over_plot) + self.mdc = self.plotcanvas.graph_event_connect('mouse_double_click', self.on_mouse_double_click_over_plot) # Keys over plot enabled self.kp = self.plotcanvas.graph_event_connect('key_press', self.ui.keyPressEvent) @@ -11844,107 +11849,107 @@ class App(QtCore.QObject): self.options.update(self.defaults) self.options_write_form() - def on_options_project2app(self): - """ - Callback for Options->Transfer Options->Project=>App. Copies options - from project defaults to application defaults. + # def on_options_project2app(self): + # """ + # Callback for Options->Transfer Options->Project=>App. Copies options + # from project defaults to application defaults. + # + # :return: None + # """ + # + # self.report_usage("on_options_project2app") + # + # self.options_read_form() + # self.defaults.update(self.options) + # self.defaults_write_form() - :return: None - """ + # def on_options_project2object(self): + # """ + # Callback for Options->Transfer Options->Project=>Object. Copies options + # from project defaults to the currently selected object. + # + # :return: None + # """ + # + # self.report_usage("on_options_project2object") + # + # self.options_read_form() + # obj = self.collection.get_active() + # if obj is None: + # self.inform.emit('[WARNING_NOTCL] %s' % + # _("No object selected.")) + # return + # for option in self.options: + # if option.find(obj.kind + "_") == 0: + # oname = option[len(obj.kind) + 1:] + # obj.options[oname] = self.options[option] + # obj.to_form() # Update UI - self.report_usage("on_options_project2app") + # def on_options_object2project(self): + # """ + # Callback for Options->Transfer Options->Object=>Project. Copies options + # from the currently selected object to project defaults. + # + # :return: None + # """ + # + # self.report_usage("on_options_object2project") + # + # obj = self.collection.get_active() + # if obj is None: + # self.inform.emit('[WARNING_NOTCL] %s' % + # _("No object selected.")) + # return + # obj.read_form() + # for option in obj.options: + # if option in ['name']: # TODO: Handle this better... + # continue + # self.options[obj.kind + "_" + option] = obj.options[option] + # self.options_write_form() - self.options_read_form() - self.defaults.update(self.options) - self.defaults_write_form() + # def on_options_object2app(self): + # """ + # Callback for Options->Transfer Options->Object=>App. Copies options + # from the currently selected object to application defaults. + # + # :return: None + # """ + # + # self.report_usage("on_options_object2app") + # + # obj = self.collection.get_active() + # if obj is None: + # self.inform.emit('[WARNING_NOTCL] %s' % + # _("No object selected.")) + # return + # obj.read_form() + # for option in obj.options: + # if option in ['name']: # TODO: Handle this better... + # continue + # self.defaults[obj.kind + "_" + option] = obj.options[option] + # self.defaults_write_form() - def on_options_project2object(self): - """ - Callback for Options->Transfer Options->Project=>Object. Copies options - from project defaults to the currently selected object. - - :return: None - """ - - self.report_usage("on_options_project2object") - - self.options_read_form() - obj = self.collection.get_active() - if obj is None: - self.inform.emit('[WARNING_NOTCL] %s' % - _("No object selected.")) - return - for option in self.options: - if option.find(obj.kind + "_") == 0: - oname = option[len(obj.kind) + 1:] - obj.options[oname] = self.options[option] - obj.to_form() # Update UI - - def on_options_object2project(self): - """ - Callback for Options->Transfer Options->Object=>Project. Copies options - from the currently selected object to project defaults. - - :return: None - """ - - self.report_usage("on_options_object2project") - - obj = self.collection.get_active() - if obj is None: - self.inform.emit('[WARNING_NOTCL] %s' % - _("No object selected.")) - return - obj.read_form() - for option in obj.options: - if option in ['name']: # TODO: Handle this better... - continue - self.options[obj.kind + "_" + option] = obj.options[option] - self.options_write_form() - - def on_options_object2app(self): - """ - Callback for Options->Transfer Options->Object=>App. Copies options - from the currently selected object to application defaults. - - :return: None - """ - - self.report_usage("on_options_object2app") - - obj = self.collection.get_active() - if obj is None: - self.inform.emit('[WARNING_NOTCL] %s' % - _("No object selected.")) - return - obj.read_form() - for option in obj.options: - if option in ['name']: # TODO: Handle this better... - continue - self.defaults[obj.kind + "_" + option] = obj.options[option] - self.defaults_write_form() - - def on_options_app2object(self): - """ - Callback for Options->Transfer Options->App=>Object. Copies options - from application defaults to the currently selected object. - - :return: None - """ - - self.report_usage("on_options_app2object") - - self.defaults_read_form() - obj = self.collection.get_active() - if obj is None: - self.inform.emit('[WARNING_NOTCL] %s' % - _("No object selected.")) - return - for option in self.defaults: - if option.find(obj.kind + "_") == 0: - oname = option[len(obj.kind) + 1:] - obj.options[oname] = self.defaults[option] - obj.to_form() # Update UI + # def on_options_app2object(self): + # """ + # Callback for Options->Transfer Options->App=>Object. Copies options + # from application defaults to the currently selected object. + # + # :return: None + # """ + # + # self.report_usage("on_options_app2object") + # + # self.defaults_read_form() + # obj = self.collection.get_active() + # if obj is None: + # self.inform.emit('[WARNING_NOTCL] %s' % + # _("No object selected.")) + # return + # for option in self.defaults: + # if option.find(obj.kind + "_") == 0: + # oname = option[len(obj.kind) + 1:] + # obj.options[oname] = self.defaults[option] + # obj.to_form() # Update UI class ArgsThread(QtCore.QObject): diff --git a/README.md b/README.md index 0e1e5f14..ddcd43a9 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,8 @@ CAD program, and create G-Code for Isolation routing. - fixed the Gerber Parser convert units unnecessary usage. The only units conversion should be done when creating the new object, after the parsing - more fixes in Rules Check Tool +- optimized the Move Tool +- added support for key-based panning in 3D graphic engine. Moving the mouse wheel while pressing the CTRL key will pan up-down and while pressing SHIFT key will pan left-right 11.10.2019 diff --git a/flatcamEditors/FlatCAMExcEditor.py b/flatcamEditors/FlatCAMExcEditor.py index 4cc0ee86..d42bb429 100644 --- a/flatcamEditors/FlatCAMExcEditor.py +++ b/flatcamEditors/FlatCAMExcEditor.py @@ -2814,7 +2814,7 @@ class FlatCAMExcEditor(QtCore.QObject): 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) - self.app.plotcanvas.graph_event_disconnect('mouse_double_click', self.app.on_double_click_over_plot) + self.app.plotcanvas.graph_event_disconnect('mouse_double_click', self.app.on_mouse_double_click_over_plot) else: self.app.plotcanvas.graph_event_disconnect(self.app.mp) self.app.plotcanvas.graph_event_disconnect(self.app.mm) @@ -2844,7 +2844,7 @@ class FlatCAMExcEditor(QtCore.QObject): self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release', self.app.on_mouse_click_release_over_plot) self.app.mdc = self.app.plotcanvas.graph_event_connect('mouse_double_click', - self.app.on_double_click_over_plot) + self.app.on_mouse_double_click_over_plot) self.app.collection.view.clicked.connect(self.app.collection.on_mouse_down) if self.app.is_legacy is False: diff --git a/flatcamEditors/FlatCAMGeoEditor.py b/flatcamEditors/FlatCAMGeoEditor.py index af4ef847..743d1a71 100644 --- a/flatcamEditors/FlatCAMGeoEditor.py +++ b/flatcamEditors/FlatCAMGeoEditor.py @@ -3390,7 +3390,7 @@ class FlatCAMGeoEditor(QtCore.QObject): 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) - self.app.plotcanvas.graph_event_disconnect('mouse_double_click', self.app.on_double_click_over_plot) + self.app.plotcanvas.graph_event_disconnect('mouse_double_click', self.app.on_mouse_double_click_over_plot) else: self.app.plotcanvas.graph_event_disconnect(self.app.mp) @@ -3437,7 +3437,7 @@ class FlatCAMGeoEditor(QtCore.QObject): self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release', self.app.on_mouse_click_release_over_plot) self.app.mdc = self.app.plotcanvas.graph_event_connect('mouse_double_click', - self.app.on_double_click_over_plot) + self.app.on_mouse_double_click_over_plot) # self.app.collection.view.clicked.connect(self.app.collection.on_mouse_down) if self.app.is_legacy is False: diff --git a/flatcamEditors/FlatCAMGrbEditor.py b/flatcamEditors/FlatCAMGrbEditor.py index 313169a8..24110ea1 100644 --- a/flatcamEditors/FlatCAMGrbEditor.py +++ b/flatcamEditors/FlatCAMGrbEditor.py @@ -3582,7 +3582,7 @@ class FlatCAMGrbEditor(QtCore.QObject): self.canvas.graph_event_disconnect('mouse_press', self.app.on_mouse_click_over_plot) self.canvas.graph_event_disconnect('mouse_move', self.app.on_mouse_move_over_plot) self.canvas.graph_event_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot) - self.canvas.graph_event_disconnect('mouse_double_click', self.app.on_double_click_over_plot) + self.canvas.graph_event_disconnect('mouse_double_click', self.app.on_mouse_double_click_over_plot) else: self.canvas.graph_event_disconnect(self.app.mp) self.canvas.graph_event_disconnect(self.app.mm) @@ -3623,7 +3623,7 @@ class FlatCAMGrbEditor(QtCore.QObject): self.app.mp = self.canvas.graph_event_connect('mouse_press', self.app.on_mouse_click_over_plot) self.app.mm = self.canvas.graph_event_connect('mouse_move', self.app.on_mouse_move_over_plot) self.app.mr = self.canvas.graph_event_connect('mouse_release', self.app.on_mouse_click_release_over_plot) - self.app.mdc = self.canvas.graph_event_connect('mouse_double_click', self.app.on_double_click_over_plot) + self.app.mdc = self.canvas.graph_event_connect('mouse_double_click', self.app.on_mouse_double_click_over_plot) self.app.collection.view.clicked.connect(self.app.collection.on_mouse_down) if self.app.is_legacy is False: diff --git a/flatcamGUI/PlotCanvas.py b/flatcamGUI/PlotCanvas.py index 3355b5b2..21480b97 100644 --- a/flatcamGUI/PlotCanvas.py +++ b/flatcamGUI/PlotCanvas.py @@ -12,6 +12,7 @@ import logging from flatcamGUI.VisPyCanvas import VisPyCanvas, time, Color from flatcamGUI.VisPyVisuals import ShapeGroup, ShapeCollection, TextCollection, TextGroup, Cursor from vispy.scene.visuals import InfiniteLine, Line + import numpy as np from vispy.geometry import Rect @@ -103,6 +104,8 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas): # Keep VisPy canvas happy by letting it be "frozen" again. self.freeze() + self.graph_event_connect('mouse_wheel', self.on_mouse_scroll) + # draw a rectangle made out of 4 lines on the canvas to serve as a hint for the work area # all CNC have a limited workspace def draw_workspace(self): @@ -250,6 +253,43 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas): self.cursor_v_line.set_data(pos=pos[0], color=self.line_color) self.view.scene.update() + def on_mouse_scroll(self, event): + # key modifiers + modifiers = event.modifiers + pan_delta_x = self.fcapp.defaults["global_gridx"] + pan_delta_y = self.fcapp.defaults["global_gridy"] + curr_pos = event.pos + + # Controlled pan by mouse wheel + if 'Shift' in modifiers: + p1 = np.array(curr_pos)[:2] + + if event.delta[1] > 0: + curr_pos[0] -= pan_delta_x + else: + curr_pos[0] += pan_delta_x + p2 = np.array(curr_pos)[:2] + self.view.camera.pan(p2 - p1) + elif 'Control' in modifiers: + p1 = np.array(curr_pos)[:2] + + if event.delta[1] > 0: + curr_pos[1] += pan_delta_y + else: + curr_pos[1] -= pan_delta_y + p2 = np.array(curr_pos)[:2] + self.view.camera.pan(p2 - p1) + + + if self.fcapp.grid_status() == True: + pos_canvas = self.translate_coords(curr_pos) + pos = self.fcapp.geo_editor.snap(pos_canvas[0], pos_canvas[1]) + + # Update cursor + self.fcapp.app_cursor.set_data(np.asarray([(pos[0], pos[1])]), + symbol='++', edge_color=self.fcapp.cursor_color_3D, + size=self.fcapp.defaults["global_cursor_size"]) + def new_text_group(self, collection=None): if collection: return TextGroup(collection) diff --git a/flatcamGUI/VisPyCanvas.py b/flatcamGUI/VisPyCanvas.py index 3bbbc237..7f7540d9 100644 --- a/flatcamGUI/VisPyCanvas.py +++ b/flatcamGUI/VisPyCanvas.py @@ -133,6 +133,9 @@ class Camera(scene.PanZoomCamera): if event.handled or not self.interactive: return + # key modifiers + modifiers = event.mouse_event.modifiers + # Limit mouse move events last_event = event.last_event t = time.time() @@ -147,21 +150,21 @@ class Camera(scene.PanZoomCamera): event.handled = True return - # Scrolling + # ################### Scrolling ########################## BaseCamera.viewbox_mouse_event(self, event) if event.type == 'mouse_wheel': - center = self._scene_transform.imap(event.pos) - scale = (1 + self.zoom_factor) ** (-event.delta[1] * 30) - self.limited_zoom(scale, center) + if not modifiers: + center = self._scene_transform.imap(event.pos) + scale = (1 + self.zoom_factor) ** (-event.delta[1] * 30) + self.limited_zoom(scale, center) event.handled = True elif event.type == 'mouse_move': if event.press_event is None: return - modifiers = event.mouse_event.modifiers - + # ################ Panning ############################ # self.pan_button_setting is actually self.FlatCAM.APP.defaults['global_pan_button'] if event.button == int(self.pan_button_setting) and not modifiers: # Translate diff --git a/flatcamTools/ToolMove.py b/flatcamTools/ToolMove.py index 72d14e4d..c2a552c1 100644 --- a/flatcamTools/ToolMove.py +++ b/flatcamTools/ToolMove.py @@ -97,12 +97,16 @@ class ToolMove(FlatCAMTool): sel_obj_list = self.app.collection.get_selected() if sel_obj_list: self.app.inform.emit(_("MOVE: Click on the Start point ...")) + + # if we have an object selected then we can safely activate the mouse events + self.mm = self.app.plotcanvas.graph_event_connect('mouse_move', self.on_move) + self.mp = self.app.plotcanvas.graph_event_connect('mouse_press', self.on_left_click) + self.kr = self.app.plotcanvas.graph_event_connect('key_release', self.on_key_press) + # draw the selection box - self.draw_sel_bbox(obj_list=sel_obj_list) + self.draw_sel_bbox() else: - self.setVisible(False) - # signal that there is no command active - self.app.command_active = None + self.toggle() self.app.inform.emit('[WARNING_NOTCL] %s' % _("MOVE action cancelled. No object(s) to move.")) def on_left_click(self, event): @@ -148,8 +152,9 @@ class ToolMove(FlatCAMTool): dx = pos[0] - self.point1[0] dy = pos[1] - self.point1[1] - # move only the objects selected and plotted - obj_list = [obj for obj in self.app.collection.get_selected() if obj.options['plot']] + # move only the objects selected and plotted and visible + obj_list = [obj for obj in self.app.collection.get_selected() + if obj.options['plot'] and obj.visible is True] def job_move(app_obj): with self.app.proc_container.new(_("Moving...")) as proc: @@ -254,47 +259,40 @@ class ToolMove(FlatCAMTool): self.toggle() return - def draw_sel_bbox(self, obj_list): + def draw_sel_bbox(self): xminlist = [] yminlist = [] xmaxlist = [] ymaxlist = [] - if not obj_list: - self.app.inform.emit('[WARNING_NOTCL] %s' % _("Object(s) not selected")) - self.toggle() - else: - # if we have an object selected then we can safely activate the mouse events - self.mm = self.app.plotcanvas.graph_event_connect('mouse_move', self.on_move) - self.mp = self.app.plotcanvas.graph_event_connect('mouse_press', self.on_left_click) - self.kr = self.app.plotcanvas.graph_event_connect('key_release', self.on_key_press) + obj_list = self.app.collection.get_selected() - # first get a bounding box to fit all - for obj in obj_list: - # don't move disabled objects, move only plotted objects - if obj.options['plot']: - xmin, ymin, xmax, ymax = obj.bounds() - xminlist.append(xmin) - yminlist.append(ymin) - xmaxlist.append(xmax) - ymaxlist.append(ymax) + # first get a bounding box to fit all + for obj in obj_list: + # don't move disabled objects, move only plotted objects + if obj.options['plot']: + xmin, ymin, xmax, ymax = obj.bounds() + xminlist.append(xmin) + yminlist.append(ymin) + xmaxlist.append(xmax) + ymaxlist.append(ymax) - # get the minimum x,y and maximum x,y for all objects selected - xminimal = min(xminlist) - yminimal = min(yminlist) - xmaximal = max(xmaxlist) - ymaximal = max(ymaxlist) + # get the minimum x,y and maximum x,y for all objects selected + xminimal = min(xminlist) + yminimal = min(yminlist) + xmaximal = max(xmaxlist) + ymaximal = max(ymaxlist) - p1 = (xminimal, yminimal) - p2 = (xmaximal, yminimal) - p3 = (xmaximal, ymaximal) - p4 = (xminimal, ymaximal) + p1 = (xminimal, yminimal) + p2 = (xmaximal, yminimal) + p3 = (xmaximal, ymaximal) + p4 = (xminimal, ymaximal) - self.old_coords = [p1, p2, p3, p4] - self.draw_shape(Polygon(self.old_coords)) + self.old_coords = [p1, p2, p3, p4] + self.draw_shape(Polygon(self.old_coords)) - if self.app.is_legacy is True: - self.sel_shapes.redraw() + if self.app.is_legacy is True: + self.sel_shapes.redraw() def update_sel_bbox(self, pos): self.delete_shape()