Explorar el Código

Merged in legacy_2D (pull request #1)

Legacy 2D
Marius Stanciu hace 6 años
padre
commit
9be61d89c4

+ 234 - 51
FlatCAMApp.py

@@ -39,6 +39,8 @@ from array import array
 from ObjectCollection import *
 from FlatCAMObj import *
 from flatcamGUI.PlotCanvas import *
+from flatcamGUI.PlotCanvasLegacy import *
+
 from flatcamGUI.FlatCAMGUI import *
 from FlatCAMCommon import LoudDict
 from FlatCAMPostProc import load_postprocessors
@@ -124,6 +126,7 @@ class App(QtCore.QObject):
     version = 8.97
     version_date = "2019/09/20"
     beta = True
+    engine = '3D'
 
     # current date now
     date = str(datetime.today()).rpartition('.')[0]
@@ -440,6 +443,7 @@ class App(QtCore.QObject):
         self.defaults_form_fields = {
             # General App
             "units": self.ui.general_defaults_form.general_app_group.units_radio,
+            "global_graphic_engine": self.ui.general_defaults_form.general_app_group.ge_radio,
             "global_app_level": self.ui.general_defaults_form.general_app_group.app_level_radio,
             "global_portable": self.ui.general_defaults_form.general_app_group.portability_cb,
             "global_language": self.ui.general_defaults_form.general_app_group.language_cb,
@@ -825,6 +829,7 @@ class App(QtCore.QObject):
             "global_serial": 0,
             "global_stats": {},
             "global_tabs_detachable": True,
+            "global_graphic_engine": '3D',
             "global_app_level": 'b',
             "global_portable": False,
             "global_language": 'English',
@@ -1584,6 +1589,16 @@ class App(QtCore.QObject):
         # ###############################################
         # ############# SETUP Plot Area #################
         # ###############################################
+
+        # determine if the Legacy Graphic Engine is to be used or the OpenGL one
+        if self.defaults["global_graphic_engine"] == '3D':
+            self.is_legacy = False
+        else:
+            self.is_legacy = True
+
+        # Matplotlib axis
+        self.axes = None
+
         if show_splash:
             self.splash.showMessage(_("FlatCAM is initializing ...\n"
                                       "Canvas initialization started."),
@@ -1591,9 +1606,14 @@ class App(QtCore.QObject):
                                     color=QtGui.QColor("gray"))
         start_plot_time = time.time()   # debug
         self.plotcanvas = None
-        self.app_cursor = None
+
+        # this is a list just because when in legacy it is needed to add multiple cursors
+        # each gets deleted when the axes are deleted therefore there is a need of one for each
+        self.app_cursor = []
         self.hover_shapes = None
+
         self.on_plotcanvas_setup()
+
         end_plot_time = time.time()
         self.used_time = end_plot_time - start_plot_time
         self.log.debug("Finished Canvas initialization in %s seconds." % str(self.used_time))
@@ -1854,6 +1874,11 @@ class App(QtCore.QObject):
         # ##############################
         # ### GUI PREFERENCES SIGNALS ##
         # ##############################
+
+        self.ui.general_defaults_form.general_app_group.ge_radio.activated_custom.connect(
+            lambda: fcTranslate.restart_program(app=self)
+        )
+
         self.ui.general_options_form.general_app_group.units_radio.group_toggle_fn = self.on_toggle_units
         self.ui.general_defaults_form.general_app_group.language_apply_btn.clicked.connect(
             lambda: fcTranslate.on_language_apply_click(self, restart=True)
@@ -2455,9 +2480,17 @@ class App(QtCore.QObject):
         self.isHovering = False
         self.notHovering = True
 
+        # Event signals disconnect id holders
+        self.mp = None
+        self.mm = None
+        self.mr = None
+
         # when True, the app has to return from any thread
         self.abort_flag = False
 
+        # set the value used in the Windows Title
+        self.engine = self.ui.general_defaults_form.general_app_group.ge_radio.get_value()
+
         # ###############################################################################
         # ############# Save defaults to factory_defaults.FlatConfig file ###############
         # ############# It's done only once after install                 ###############
@@ -2727,10 +2760,11 @@ class App(QtCore.QObject):
         :param name: String that store the project path and project name
         :return: None
         """
-        self.ui.setWindowTitle('FlatCAM %s %s - %s    %s' %
+        self.ui.setWindowTitle('FlatCAM %s %s - %s - (%s)    %s' %
                                (self.version,
                                 ('BETA' if self.beta else ''),
                                 platform.architecture()[0],
+                                self.engine,
                                 name)
                                )
 
@@ -3119,12 +3153,32 @@ class App(QtCore.QObject):
                             self.collection.set_active(old_name)
                             self.collection.delete_active()
 
+                        # restore GUI to the Selected TAB
+                        # Remove anything else in the GUI
+                        self.ui.selected_scroll_area.takeWidget()
+                        # Switch notebook to Selected page
+                        self.ui.notebook.setCurrentWidget(self.ui.selected_tab)
+
                     elif isinstance(edited_obj, FlatCAMExcellon):
                         obj_type = "Excellon"
                         if cleanup is None:
                             self.exc_editor.update_fcexcellon(edited_obj)
                             self.exc_editor.update_options(edited_obj)
+
                         self.exc_editor.deactivate()
+
+                        # delete the old object (the source object) if it was an empty one
+                        if len(edited_obj.drills) == 0 and len(edited_obj.slots) == 0:
+                            old_name = edited_obj.options['name']
+                            self.collection.set_active(old_name)
+                            self.collection.delete_active()
+
+                        # restore GUI to the Selected TAB
+                        # Remove anything else in the GUI
+                        self.ui.tool_scroll_area.takeWidget()
+                        # Switch notebook to Selected page
+                        self.ui.notebook.setCurrentWidget(self.ui.selected_tab)
+
                     else:
                         self.inform.emit('[WARNING_NOTCL] %s' %
                                          _("Select a Gerber, Geometry or Excellon Object to update."))
@@ -6663,10 +6717,22 @@ class App(QtCore.QObject):
         try:
             sel_obj = self.collection.get_active()
             name = sel_obj.options["name"]
+            isPlotted = sel_obj.options["plot"]
         except AttributeError:
             self.log.debug("Nothing selected for deletion")
             return
 
+        if self.is_legacy is True:
+            # Remove plot only if the object was plotted otherwise delaxes will fail
+            if isPlotted:
+                try:
+                    # self.plotcanvas.figure.delaxes(self.collection.get_active().axes)
+                    self.plotcanvas.figure.delaxes(self.collection.get_active().shapes.axes)
+                except Exception as e:
+                    log.debug("App.delete_first_selected() --> %s" % str(e))
+
+            self.plotcanvas.auto_adjust_axes()
+
         # Remove from dictionary
         self.collection.delete_active()
 
@@ -6694,12 +6760,15 @@ class App(QtCore.QObject):
                     for obj in self.collection.get_list():
                         obj.plot()
                     self.plotcanvas.fit_view()
-                self.plotcanvas.vis_disconnect('mouse_press', self.on_set_zero_click)
+                if self.is_legacy:
+                    self.plotcanvas.graph_event_disconnect(self.mp_zc)
+                else:
+                    self.plotcanvas.graph_event_disconnect('mouse_press', self.on_set_zero_click)
 
             self.worker_task.emit({'fcn': worker_task, 'params': []})
 
         self.inform.emit(_('Click to set the origin ...'))
-        self.plotcanvas.vis_connect('mouse_press', self.on_set_zero_click)
+        self.mp_zc = self.plotcanvas.graph_event_connect('mouse_press', self.on_set_zero_click)
 
         # first disconnect it as it may have been used by something else
         try:
@@ -6711,7 +6780,12 @@ class App(QtCore.QObject):
     def on_set_zero_click(self, event):
         # this function will be available only for mouse left click
 
-        pos_canvas = self.plotcanvas.translate_coords(event.pos)
+        if self.is_legacy is False:
+            event_pos = event.pos
+        else:
+            event_pos = (event.xdata, event.ydata)
+
+        pos_canvas = self.plotcanvas.translate_coords(event_pos)
         if event.button == 1:
             if self.grid_status() == True:
                 pos = self.geo_editor.snap(pos_canvas[0], pos_canvas[1])
@@ -6748,6 +6822,10 @@ class App(QtCore.QObject):
         """
         self.report_usage("on_jump_to()")
 
+        if self.is_legacy is True:
+            self.inform.emit(_("Not available with the current Graphic Engine Legacy(2D)."))
+            return
+
         if not custom_location:
             dia_box = Dialog_box(title=_("Jump to ..."),
                                  label=_("Enter the coordinates in format X,Y:"),
@@ -6767,14 +6845,27 @@ class App(QtCore.QObject):
             location = custom_location
 
         if fit_center:
-            self.plotcanvas.fit_center(loc=location)
+            if self.is_legacy is False:
+                self.plotcanvas.fit_center(loc=location)
+            else:
+                pass
+                # self.plotcanvas.fit_view()
 
         cursor = QtGui.QCursor()
 
-        canvas_origin = self.plotcanvas.native.mapToGlobal(QtCore.QPoint(0, 0))
-        jump_loc = self.plotcanvas.translate_coords_2((location[0], location[1]))
+        if self.is_legacy is False:
+            canvas_origin = self.plotcanvas.native.mapToGlobal(QtCore.QPoint(0, 0))
+            jump_loc = self.plotcanvas.translate_coords_2((location[0], location[1]))
+            cursor.setPos(canvas_origin.x() + jump_loc[0], (canvas_origin.y() + jump_loc[1]))
+        else:
+            # the origin finding works but not mapping the location to pixels
+            canvas_origin = self.plotcanvas.native.mapToGlobal(QtCore.QPoint(0, 0))
+            x0, y0 = canvas_origin.x(), canvas_origin.y() + self.ui.right_layout.geometry().height()
+            x0, y0 = x0 + self.plotcanvas.axes.transData.transform((0, 0))[0], y0 - \
+                     self.plotcanvas.axes.transData.transform((0, 0))[1]
+            loc = self.plotcanvas.axes.transData.transform(location)
+            cursor.setPos(x0 + loc[0]/50, y0 - loc[1]/50)
 
-        cursor.setPos(canvas_origin.x() + jump_loc[0], (canvas_origin.y() + jump_loc[1]))
         self.inform.emit('[success] %s' %
                          _("Done."))
         return location
@@ -7352,7 +7443,11 @@ class App(QtCore.QObject):
 
         :return: None
         """
-        self.plotcanvas.update()           # TODO: Need update canvas?
+        if self.is_legacy is False:
+            self.plotcanvas.update()           # TODO: Need update canvas?
+        else:
+            self.plotcanvas.auto_adjust_axes()
+
         self.on_zoom_fit(None)
         self.collection.update_view()
         # self.inform.emit(_("Plots updated ..."))
@@ -7525,19 +7620,31 @@ class App(QtCore.QObject):
         """
         self.pos = []
 
+        if self.is_legacy is False:
+            event_pos = event.pos
+            if self.defaults["global_pan_button"] == '2':
+                pan_button = 2
+            else:
+                pan_button = 3
+            # Set the mouse button for panning
+            self.plotcanvas.view.camera.pan_button_setting = pan_button
+        else:
+            event_pos = (event.xdata, event.ydata)
+            # Matplotlib has the middle and right buttons mapped in reverse compared with VisPy
+            if self.defaults["global_pan_button"] == '2':
+                pan_button = 3
+            else:
+                pan_button = 2
+
         # So it can receive key presses
         self.plotcanvas.native.setFocus()
-        # Set the mouse button for panning
-        self.plotcanvas.view.camera.pan_button_setting = self.defaults['global_pan_button']
 
-        self.pos_canvas = self.plotcanvas.translate_coords(event.pos)
+        self.pos_canvas = self.plotcanvas.translate_coords(event_pos)
 
         if self.grid_status() == True:
             self.pos = self.geo_editor.snap(self.pos_canvas[0], self.pos_canvas[1])
-            self.app_cursor.enabled = True
         else:
             self.pos = (self.pos_canvas[0], self.pos_canvas[1])
-            self.app_cursor.enabled = False
 
         try:
             modifiers = QtWidgets.QApplication.keyboardModifiers()
@@ -7565,7 +7672,8 @@ class App(QtCore.QObject):
             App.log.debug("App.on_mouse_click_over_plot() --> Outside plot? --> %s" % str(e))
 
     def on_double_click_over_plot(self, event):
-        self.doubleclick = True
+        if event.button == 1:
+            self.doubleclick = True
 
     def on_mouse_move_over_plot(self, event, origin_click=None):
         """
@@ -7576,49 +7684,69 @@ class App(QtCore.QObject):
         :return: None
         """
 
+        if self.is_legacy is False:
+            event_pos = event.pos
+            if self.defaults["global_pan_button"] == '2':
+                pan_button = 2
+            else:
+                pan_button = 3
+            event_is_dragging = event.is_dragging
+        else:
+            event_pos = (event.xdata, event.ydata)
+            # Matplotlib has the middle and right buttons mapped in reverse compared with VisPy
+            if self.defaults["global_pan_button"] == '2':
+                pan_button = 3
+            else:
+                pan_button = 2
+            event_is_dragging = self.plotcanvas.is_dragging
+
         # So it can receive key presses
         self.plotcanvas.native.setFocus()
-        self.pos_jump = event.pos
+        self.pos_jump = event_pos
 
         self.ui.popMenu.mouse_is_panning = False
 
         if not origin_click:
             # if the RMB is clicked and mouse is moving over plot then 'panning_action' is True
-            if event.button == 2 and event.is_dragging == 1:
+            if event.button == pan_button and event_is_dragging == 1:
                 self.ui.popMenu.mouse_is_panning = True
                 return
 
         if self.rel_point1 is not None:
             try:  # May fail in case mouse not within axes
-                pos_canvas = self.plotcanvas.translate_coords(event.pos)
+                pos_canvas = self.plotcanvas.translate_coords(event_pos)
+
                 if self.grid_status() == True:
                     pos = self.geo_editor.snap(pos_canvas[0], pos_canvas[1])
-                    self.app_cursor.enabled = True
-                    # Update cursor
-                    self.app_cursor.set_data(np.asarray([(pos[0], pos[1])]), symbol='++', edge_color='black', size=20)
+
+                    if self.is_legacy is False:
+                        # Update cursor
+                        self.app_cursor.set_data(np.asarray([(pos[0], pos[1])]),
+                                                 symbol='++', edge_color='black', size=20)
                 else:
                     pos = (pos_canvas[0], pos_canvas[1])
-                    self.app_cursor.enabled = False
 
                 self.ui.position_label.setText("&nbsp;&nbsp;&nbsp;&nbsp;<b>X</b>: %.4f&nbsp;&nbsp;   "
                                                "<b>Y</b>: %.4f" % (pos[0], pos[1]))
 
-                dx = pos[0] - self.rel_point1[0]
-                dy = pos[1] - self.rel_point1[1]
+                dx = pos[0] - float(self.rel_point1[0])
+                dy = pos[1] - float(self.rel_point1[1])
                 self.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp;  <b>Dy</b>: "
                                                    "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (dx, dy))
                 self.mouse = [pos[0], pos[1]]
 
                 # if the mouse is moved and the LMB is clicked then the action is a selection
-                if event.is_dragging == 1 and event.button == 1:
+                if event_is_dragging == 1 and event.button == 1:
                     self.delete_selection_shape()
                     if dx < 0:
                         self.draw_moving_selection_shape(self.pos, pos, color=self.defaults['global_alt_sel_line'],
-                                                     face_color=self.defaults['global_alt_sel_fill'])
+                                                         face_color=self.defaults['global_alt_sel_fill'])
                         self.selection_type = False
-                    else:
+                    elif dx > 0:
                         self.draw_moving_selection_shape(self.pos, pos)
                         self.selection_type = True
+                    else:
+                        self.selection_type = None
 
                 # hover effect - enabled in Preferences -> General -> GUI Settings
                 if self.defaults['global_hover']:
@@ -7664,7 +7792,16 @@ class App(QtCore.QObject):
         :return:
         """
         pos = 0, 0
-        pos_canvas = self.plotcanvas.translate_coords(event.pos)
+
+        if self.is_legacy is False:
+            event_pos = event.pos
+            right_button = 2
+        else:
+            event_pos = (event.xdata, event.ydata)
+            # Matplotlib has the middle and right buttons mapped in reverse compared with VisPy
+            right_button = 3
+
+        pos_canvas = self.plotcanvas.translate_coords(event_pos)
         if self.grid_status() == True:
             pos = self.geo_editor.snap(pos_canvas[0], pos_canvas[1])
         else:
@@ -7672,7 +7809,7 @@ 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 == 2:  # right click
+        if event.button == right_button:  # right click
             if self.ui.popMenu.mouse_is_panning is False:
                 self.cursor = QtGui.QCursor()
                 self.populate_cmenu_grids()
@@ -7776,6 +7913,7 @@ class App(QtCore.QObject):
                         # add objects to the objects_under_the_click list only if the object is plotted
                         # (active and not disabled)
                         objects_under_the_click_list.append(obj.options['name'])
+
         try:
             # If there is no element in the overlapped objects list then make everyone inactive
             # because we selected "nothing"
@@ -7939,17 +8077,27 @@ class App(QtCore.QObject):
             hover_rect = hover_rect.buffer(-0.00393)
             hover_rect = hover_rect.buffer(0.00787)
 
+        # if color:
+        #     face = Color(color)
+        #     face.alpha = 0.2
+        #     outline = Color(color, alpha=0.8)
+        # else:
+        #     face = Color(self.defaults['global_sel_fill'])
+        #     face.alpha = 0.2
+        #     outline = self.defaults['global_sel_line']
+
         if color:
-            face = Color(color)
-            face.alpha = 0.2
-            outline = Color(color, alpha=0.8)
+            face = color[:-2] + str(hex(int(0.2 * 255)))[2:]
+            outline = color[:-2] + str(hex(int(0.8 * 255)))[2:]
         else:
-            face = Color(self.defaults['global_sel_fill'])
-            face.alpha = 0.2
+            face = self.defaults['global_sel_fill'][:-2] + str(hex(int(0.2 * 255)))[2:]
             outline = self.defaults['global_sel_line']
 
         self.hover_shapes.add(hover_rect, color=outline, face_color=face, update=True, layer=0, tolerance=None)
 
+        if self.is_legacy is True:
+            self.hover_shapes.redraw()
+
     def delete_selection_shape(self):
         self.move_tool.sel_shapes.clear()
         self.move_tool.sel_shapes.redraw()
@@ -7974,12 +8122,19 @@ class App(QtCore.QObject):
             sel_rect = sel_rect.buffer(-0.00393)
             sel_rect = sel_rect.buffer(0.00787)
 
+        # if color:
+        #     face = Color(color, alpha=0.2)
+        #     outline = Color(color, alpha=0.8)
+        # else:
+        #     face = Color(self.defaults['global_sel_fill'], alpha=0.2)
+        #     outline = Color(self.defaults['global_sel_line'], alpha=0.8)
+
         if color:
-            face = Color(color, alpha=0.2)
-            outline = Color(color, alpha=0.8)
+            face = color[:-2] + str(hex(int(0.2 * 255)))[2:]
+            outline = color[:-2] + str(hex(int(0.8 * 255)))[2:]
         else:
-            face = Color(self.defaults['global_sel_fill'], alpha=0.2)
-            outline = Color(self.defaults['global_sel_line'], alpha=0.8)
+            face = self.defaults['global_sel_fill'][:-2] + str(hex(int(0.2 * 255)))[2:]
+            outline = self.defaults['global_sel_line'][:-2] + str(hex(int(0.8 * 255)))[2:]
 
         self.sel_objects_list.append(self.move_tool.sel_shapes.add(sel_rect,
                                                                    color=outline,
@@ -7987,6 +8142,8 @@ class App(QtCore.QObject):
                                                                    update=True,
                                                                    layer=0,
                                                                    tolerance=None))
+        if self.is_legacy is True:
+            self.move_tool.sel_shapes.redraw()
 
     def draw_moving_selection_shape(self, old_coords, coords, **kwargs):
         """
@@ -8013,16 +8170,22 @@ class App(QtCore.QObject):
 
         x0, y0 = old_coords
         x1, y1 = coords
+
         pt1 = (x0, y0)
         pt2 = (x1, y0)
         pt3 = (x1, y1)
         pt4 = (x0, y1)
         sel_rect = Polygon([pt1, pt2, pt3, pt4])
 
-        color_t = Color(face_color)
-        color_t.alpha = face_alpha
+        # color_t = Color(face_color)
+        # color_t.alpha = face_alpha
+
+        color_t = face_color[:-2] + str(hex(int(face_alpha * 255)))[2:]
+
         self.move_tool.sel_shapes.add(sel_rect, color=color, face_color=color_t, update=True,
                                       layer=0, tolerance=None)
+        if self.is_legacy is True:
+            self.move_tool.sel_shapes.redraw()
 
     def on_file_new_click(self):
         if self.collection.get_list() and self.should_we_save:
@@ -10893,22 +11056,33 @@ class App(QtCore.QObject):
         else:
             plot_container = self.ui.right_layout
 
-        self.plotcanvas = PlotCanvas(plot_container, self)
+        if self.is_legacy is False:
+            self.plotcanvas = PlotCanvas(plot_container, self)
+        else:
+            self.plotcanvas = PlotCanvasLegacy(plot_container, self)
 
         # So it can receive key presses
         self.plotcanvas.native.setFocus()
 
-        self.plotcanvas.vis_connect('mouse_move', self.on_mouse_move_over_plot)
-        self.plotcanvas.vis_connect('mouse_press', self.on_mouse_click_over_plot)
-        self.plotcanvas.vis_connect('mouse_release', self.on_mouse_click_release_over_plot)
-        self.plotcanvas.vis_connect('mouse_double_click', self.on_double_click_over_plot)
+        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)
 
         # Keys over plot enabled
-        self.plotcanvas.vis_connect('key_press', self.ui.keyPressEvent)
+        self.kp = self.plotcanvas.graph_event_connect('key_press', self.ui.keyPressEvent)
 
         self.app_cursor = self.plotcanvas.new_cursor()
-        self.app_cursor.enabled = False
-        self.hover_shapes = ShapeCollection(parent=self.plotcanvas.view.scene, layers=1)
+        if self.ui.grid_snap_btn.isChecked():
+            self.app_cursor.enabled = True
+        else:
+            self.app_cursor.enabled = False
+
+        if self.is_legacy is False:
+            self.hover_shapes = ShapeCollection(parent=self.plotcanvas.view.scene, layers=1)
+        else:
+            # will use the default Matplotlib axes
+            self.hover_shapes = ShapeCollectionLegacy(obj=self, app=self, name='hover')
 
     def on_zoom_fit(self, event):
         """
@@ -10919,8 +11093,17 @@ class App(QtCore.QObject):
         :param event: Ignored.
         :return: None
         """
-
-        self.plotcanvas.fit_view()
+        if self.is_legacy is False:
+            self.plotcanvas.fit_view()
+        else:
+            xmin, ymin, xmax, ymax = self.collection.get_bounds()
+            width = xmax - xmin
+            height = ymax - ymin
+            xmin -= 0.05 * width
+            xmax += 0.05 * width
+            ymin -= 0.05 * height
+            ymax += 0.05 * height
+            self.plotcanvas.adjust_axes(xmin, ymin, xmax, ymax)
 
     def on_zoom_in(self):
         self.plotcanvas.zoom(1 / float(self.defaults['global_zoom_ratio']))

+ 52 - 26
FlatCAMObj.py

@@ -12,6 +12,7 @@ from datetime import datetime
 
 from flatcamGUI.ObjectUI import *
 from FlatCAMCommon import LoudDict
+from flatcamGUI.PlotCanvasLegacy import ShapeCollectionLegacy
 from camlib import *
 import itertools
 
@@ -74,10 +75,17 @@ class FlatCAMObj(QtCore.QObject):
         # store here the default data for Geometry Data
         self.default_data = {}
 
+        # 2D mode
+        # Axes must exist and be attached to canvas.
+        self.axes = None
+
         self.kind = None  # Override with proper name
 
         # self.shapes = ShapeCollection(parent=self.app.plotcanvas.view.scene)
-        self.shapes = self.app.plotcanvas.new_shape_group()
+        if self.app.is_legacy is False:
+            self.shapes = self.app.plotcanvas.new_shape_group()
+        else:
+            self.shapes = ShapeCollectionLegacy(obj=self, app=self.app)
 
         # self.mark_shapes = self.app.plotcanvas.new_shape_collection(layers=2)
         self.mark_shapes = {}
@@ -391,11 +399,12 @@ class FlatCAMObj(QtCore.QObject):
         def worker_task(app_obj):
             self.shapes.visible = value
 
-            # Not all object types has annotations
-            try:
-                self.annotation.visible = value
-            except Exception as e:
-                pass
+            if self.app.is_legacy is False:
+                # Not all object types has annotations
+                try:
+                    self.annotation.visible = value
+                except Exception as e:
+                    pass
 
         if threaded is False:
             worker_task(app_obj=self.app)
@@ -627,8 +636,13 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
             self.ui.create_buffer_button.hide()
 
         # add the shapes storage for marking apertures
-        for ap_code in self.apertures:
-            self.mark_shapes[ap_code] = self.app.plotcanvas.new_shape_collection(layers=2)
+        if self.app.is_legacy is False:
+            for ap_code in self.apertures:
+                self.mark_shapes[ap_code] = self.app.plotcanvas.new_shape_collection(layers=2)
+        else:
+            for ap_code in self.apertures:
+                self.mark_shapes[ap_code] = ShapeCollectionLegacy(obj=self, app=self.app,
+                                                                  name=self.options['name'] + str(ap_code))
 
         # set initial state of the aperture table and associated widgets
         self.on_aperture_table_visibility_change()
@@ -1342,6 +1356,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
         except TypeError:
             geometry = [geometry]
 
+        # if self.app.is_legacy is False:
         def random_color():
             color = np.random.rand(4)
             color[3] = 1
@@ -5349,7 +5364,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
 
         return factor
 
-    def plot_element(self, element, color='red', visible=None):
+    def plot_element(self, element, color='#FF0000FF', visible=None):
 
         visible = visible if visible else self.options['plot']
 
@@ -5358,8 +5373,10 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
                 self.plot_element(sub_el)
 
         except TypeError:  # Element is not iterable...
+            # if self.app.is_legacy is False:
             self.add_shape(shape=element, color=color, visible=visible, layer=0)
 
+
     def plot(self, visible=None, kind=None):
         """
         Plot the object.
@@ -5389,7 +5406,9 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
                     self.plot_element(self.solid_geometry, visible=visible)
 
             # self.plot_element(self.solid_geometry, visible=self.options['plot'])
+
             self.shapes.redraw()
+
         except (ObjectDeleted, AttributeError):
             self.shapes.clear(update=True)
 
@@ -5551,9 +5570,10 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
         # from predecessors.
         self.ser_attrs += ['options', 'kind', 'cnc_tools', 'multitool']
 
-        self.text_col = self.app.plotcanvas.new_text_collection()
-        self.text_col.enabled = True
-        self.annotation = self.app.plotcanvas.new_text_group(collection=self.text_col)
+        if self.app.is_legacy is False:
+            self.text_col = self.app.plotcanvas.new_text_collection()
+            self.text_col.enabled = True
+            self.annotation = self.app.plotcanvas.new_text_group(collection=self.text_col)
 
     def build_ui(self):
         self.ui_disconnect()
@@ -5728,8 +5748,9 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
             pass
         self.ui.annotation_cb.stateChanged.connect(self.on_annotation_change)
 
-        # set if to display text annotations
-        self.ui.annotation_cb.set_value(self.app.defaults["cncjob_annotation"])
+        if self.app.is_legacy is False:
+            # set if to display text annotations
+            self.ui.annotation_cb.set_value(self.app.defaults["cncjob_annotation"])
 
         # Show/Hide Advanced Options
         if self.app.defaults["global_app_level"] == 'b':
@@ -6178,11 +6199,12 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
 
         visible = visible if visible else self.options['plot']
 
-        if self.ui.annotation_cb.get_value() and self.ui.plot_cb.get_value():
-            self.text_col.enabled = True
-        else:
-            self.text_col.enabled = False
-        self.annotation.redraw()
+        if self.app.is_legacy is False:
+            if self.ui.annotation_cb.get_value() and self.ui.plot_cb.get_value():
+                self.text_col.enabled = True
+            else:
+                self.text_col.enabled = False
+            self.annotation.redraw()
 
         try:
             if self.multitool is False:  # single tool usage
@@ -6201,16 +6223,20 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
             self.shapes.redraw()
         except (ObjectDeleted, AttributeError):
             self.shapes.clear(update=True)
-            self.annotation.clear(update=True)
+            if self.app.is_legacy is False:
+                self.annotation.clear(update=True)
 
     def on_annotation_change(self):
-        if self.ui.annotation_cb.get_value():
-            self.text_col.enabled = True
+        if self.app.is_legacy is False:
+            if self.ui.annotation_cb.get_value():
+                self.text_col.enabled = True
+            else:
+                self.text_col.enabled = False
+            # kind = self.ui.cncplot_method_combo.get_value()
+            # self.plot(kind=kind)
+            self.annotation.redraw()
         else:
-            self.text_col.enabled = False
-        # kind = self.ui.cncplot_method_combo.get_value()
-        # self.plot(kind=kind)
-        self.annotation.redraw()
+            self.inform.emit(_("Not available with the current Graphic Engine Legacy(2D)."))
 
     def convert_units(self, units):
         log.debug("FlatCAMObj.FlatCAMECNCjob.convert_units()")

+ 4 - 2
FlatCAMTranslation.py

@@ -16,6 +16,7 @@ from PyQt5.QtCore import QSettings
 from flatcamGUI.GUIElements import log
 import gettext
 
+
 # import builtins
 #
 # if '_' not in builtins.__dict__:
@@ -154,12 +155,13 @@ def apply_language(domain, lang=None):
         return name
 
 
-def restart_program(app):
+def restart_program(app, ask=None):
     """Restarts the current program.
     Note: this function does not return. Any cleanup action (like
     saving data) must be done before calling this function.
     """
-    if app.should_we_save and app.collection.get_list():
+
+    if app.should_we_save and app.collection.get_list() or ask is True:
         msgbox = QtWidgets.QMessageBox()
         msgbox.setText(_("There are files/objects modified in FlatCAM. "
                          "\n"

+ 2 - 2
ObjectCollection.py

@@ -588,8 +588,8 @@ class ObjectCollection(QtCore.QAbstractItemModel):
         self.app.all_objects_list = self.get_list()
 
         self.endRemoveRows()
-
-        self.app.plotcanvas.redraw()
+        if self.app.is_legacy is False:
+            self.app.plotcanvas.redraw()
 
         if select_project:
             # always go to the Project Tab after object deletion as it may be done with a shortcut key

+ 35 - 0
README.md

@@ -9,9 +9,44 @@ CAD program, and create G-Code for Isolation routing.
 
 =================================================
 
+22.09.2019
+
+- fixed zoom directions legacy graphic engine (previous commit)
+- fixed display of MultiGeo geometries in legacy graphic engine
+- fixed Paint tool to work in legacy graphic engine
+- fixed CutOut Tool to work in legacy graphic engine
+- fixed display of distance labels and code optimizations in ToolPaint and NCC Tool
+- adjusted axis at startup for legacy graphic engine plotcanvas
+- when the graphic engine is changed in Edit -> Preferences -> General -> App Preferences, the application will restart
+
+21.09.2019
+
+- fixed Measuring Tool in legacy graphic engine
+- fixed Gerber plotting in legacy graphic engine
+- fixed Geometry plotting in legacy graphic engine
+- fixed CNCJob and Excellon plotting in legacy graphic engine
+- in legacy graphic engine fixed the travel vs cut lines in CNCJob objects
+- final fix for key shortcuts with modifier in legacy graphic engine
+- refactored some of the code in the legacy graphic engine
+- fixed drawing of selection box when dragging mouse on screen and the selection shape drawing on the selected objects
+- fixed the moving drawing shape in Tool Move in legacy graphic engine
+- fixed moving geometry in Tool Measurement in legacy graphic engine
+- fixed Geometry Editor to work in legacy graphic engine
+- fixed Excellon Editor to work in legacy graphic engine
+- fixed Gerber Editor to work in legacy graphic engine
+- fixed NCC tool to work in legacy graphic engine
+
 20.09.2019
 
 - final fix for the --shellvar having spaces within the assigned value; now they are retained
+- legacy graphic engine - made the mouse events work (click, release, doubleclick, dragging)
+- legacy graphic engine - made the key events work (simple or with modifiers)
+- legacy graphic engine - made the mouse cursor work (enabled/disabled, position report); snapping is not moving the cursor yet
+- made the mouse cursor snap to the grid when grid snapping is active
+- changed the axis color to the one used in the OpenGL graphic engine
+- work on ShapeCollectionLegacy
+- fixed mouse cursor to work for all objects
+- fixed event signals to work in both graphic engines: 2D and 3D
 
 19.09.2019
 

+ 9 - 1
camlib.py

@@ -36,6 +36,10 @@ from shapely.wkt import dumps as sdumps
 from shapely.geometry.base import BaseGeometry
 from shapely.geometry import shape
 
+# needed for legacy mode
+# Used for solid polygons in Matplotlib
+from descartes.patch import PolygonPatch
+
 import collections
 from collections import Iterable
 
@@ -117,7 +121,11 @@ class Geometry(object):
         self.old_disp_number = 0
         self.el_count = 0
 
-        self.temp_shapes = self.app.plotcanvas.new_shape_group()
+        if self.app.is_legacy is False:
+            self.temp_shapes = self.app.plotcanvas.new_shape_group()
+        else:
+            from flatcamGUI.PlotCanvasLegacy import ShapeCollectionLegacy
+            self.temp_shapes = ShapeCollectionLegacy(obj=self, app=self.app, name='camlib.geometry')
 
         # if geo_steps_per_circle is None:
         #     geo_steps_per_circle = int(Geometry.defaults["geo_steps_per_circle"])

+ 4 - 0
descartes/__init__.py

@@ -0,0 +1,4 @@
+"""Turn geometric objects into matplotlib patches"""
+
+from descartes.patch import PolygonPatch
+

+ 66 - 0
descartes/patch.py

@@ -0,0 +1,66 @@
+"""Paths and patches"""
+
+from matplotlib.patches import PathPatch
+from matplotlib.path import Path
+from numpy import asarray, concatenate, ones
+
+
+class Polygon(object):
+    # Adapt Shapely or GeoJSON/geo_interface polygons to a common interface
+    def __init__(self, context):
+        if hasattr(context, 'interiors'):
+            self.context = context
+        else:
+            self.context = getattr(context, '__geo_interface__', context)
+    @property
+    def geom_type(self):
+        return (getattr(self.context, 'geom_type', None)
+                or self.context['type'])
+    @property
+    def exterior(self):
+        return (getattr(self.context, 'exterior', None) 
+                or self.context['coordinates'][0])
+    @property
+    def interiors(self):
+        value = getattr(self.context, 'interiors', None)
+        if value is None:
+            value = self.context['coordinates'][1:]
+        return value
+
+
+def PolygonPath(polygon):
+    """Constructs a compound matplotlib path from a Shapely or GeoJSON-like
+    geometric object"""
+    this = Polygon(polygon)
+    assert this.geom_type == 'Polygon'
+    def coding(ob):
+        # The codes will be all "LINETO" commands, except for "MOVETO"s at the
+        # beginning of each subpath
+        n = len(getattr(ob, 'coords', None) or ob)
+        vals = ones(n, dtype=Path.code_type) * Path.LINETO
+        vals[0] = Path.MOVETO
+        return vals
+    vertices = concatenate(
+                    [asarray(this.exterior)] 
+                    + [asarray(r) for r in this.interiors])
+    codes = concatenate(
+                [coding(this.exterior)] 
+                + [coding(r) for r in this.interiors])
+    return Path(vertices, codes)
+
+
+def PolygonPatch(polygon, **kwargs):
+    """Constructs a matplotlib patch from a geometric object
+    
+    The `polygon` may be a Shapely or GeoJSON-like object with or without holes.
+    The `kwargs` are those supported by the matplotlib.patches.Polygon class
+    constructor. Returns an instance of matplotlib.patches.PathPatch.
+
+    Example (using Shapely Point and a matplotlib axes):
+
+      >>> b = Point(0, 0).buffer(1.0)
+      >>> patch = PolygonPatch(b, fc='blue', ec='blue', alpha=0.5)
+      >>> axis.add_patch(patch)
+
+    """
+    return PathPatch(PolygonPath(polygon), **kwargs)

+ 116 - 67
flatcamEditors/FlatCAMExcEditor.py

@@ -2012,8 +2012,14 @@ class FlatCAMExcEditor(QtCore.QObject):
         self.exc_obj = None
 
         # VisPy Visuals
-        self.shapes = self.app.plotcanvas.new_shape_collection(layers=1)
-        self.tool_shape = self.app.plotcanvas.new_shape_collection(layers=1)
+        if self.app.is_legacy is False:
+            self.shapes = self.app.plotcanvas.new_shape_collection(layers=1)
+            self.tool_shape = self.app.plotcanvas.new_shape_collection(layers=1)
+        else:
+            from flatcamGUI.PlotCanvasLegacy import ShapeCollectionLegacy
+            self.shapes = ShapeCollectionLegacy(obj=self, app=self.app, name='shapes_exc_editor')
+            self.tool_shape = ShapeCollectionLegacy(obj=self, app=self.app, name='tool_shapes_exc_editor')
+
         self.app.pool_recreated.connect(self.pool_recreated)
 
         # Remove from scene
@@ -2082,6 +2088,11 @@ class FlatCAMExcEditor(QtCore.QObject):
         def entry2option(option, entry):
             self.options[option] = float(entry.text())
 
+        # Event signals disconnect id holders
+        self.mp = None
+        self.mm = None
+        self.mr = None
+
         # store the status of the editor so the Delete at object level will not work until the edit is finished
         self.editor_active = False
         log.debug("Initialization of the FlatCAM Excellon Editor is finished ...")
@@ -2445,8 +2456,10 @@ class FlatCAMExcEditor(QtCore.QObject):
                 row_to_be_selected = int(key) - 1
                 self.last_tool_selected = int(key)
                 break
-
-        self.tools_table_exc.selectRow(row_to_be_selected)
+        try:
+            self.tools_table_exc.selectRow(row_to_be_selected)
+        except TypeError as e:
+            log.debug("FlatCAMExcEditor.on_tool_add() --> %s" % str(e))
 
     def on_tool_delete(self, dia=None):
         self.is_modified = True
@@ -2791,16 +2804,23 @@ class FlatCAMExcEditor(QtCore.QObject):
 
         # first connect to new, then disconnect the old handlers
         # don't ask why but if there is nothing connected I've seen issues
-        self.canvas.vis_connect('mouse_press', self.on_canvas_click)
-        self.canvas.vis_connect('mouse_move', self.on_canvas_move)
-        self.canvas.vis_connect('mouse_release', self.on_exc_click_release)
+        self.mp = self.canvas.graph_event_connect('mouse_press', self.on_canvas_click)
+        self.mm = self.canvas.graph_event_connect('mouse_move', self.on_canvas_move)
+        self.mr = self.canvas.graph_event_connect('mouse_release', self.on_exc_click_release)
 
         # make sure that the shortcuts key and mouse events will no longer be linked to the methods from FlatCAMApp
         # but those from FlatCAMGeoEditor
-        self.app.plotcanvas.vis_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
-        self.app.plotcanvas.vis_disconnect('mouse_move', self.app.on_mouse_move_over_plot)
-        self.app.plotcanvas.vis_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
-        self.app.plotcanvas.vis_disconnect('mouse_double_click', self.app.on_double_click_over_plot)
+        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)
+            self.app.plotcanvas.graph_event_disconnect('mouse_double_click', self.app.on_double_click_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.app.plotcanvas.graph_event_disconnect(self.app.mdc)
+
         self.app.collection.view.clicked.disconnect()
 
         self.app.ui.popmenu_copy.triggered.disconnect()
@@ -2819,15 +2839,22 @@ class FlatCAMExcEditor(QtCore.QObject):
         # we restore the key and mouse control to FlatCAMApp method
         # first connect to new, then disconnect the old handlers
         # don't ask why but if there is nothing connected I've seen issues
-        self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
-        self.app.plotcanvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot)
-        self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
-        self.app.plotcanvas.vis_connect('mouse_double_click', self.app.on_double_click_over_plot)
+        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.app.mdc = self.app.plotcanvas.graph_event_connect('mouse_double_click',
+                                                               self.app.on_double_click_over_plot)
         self.app.collection.view.clicked.connect(self.app.collection.on_mouse_down)
 
-        self.canvas.vis_disconnect('mouse_press', self.on_canvas_click)
-        self.canvas.vis_disconnect('mouse_move', self.on_canvas_move)
-        self.canvas.vis_disconnect('mouse_release', self.on_exc_click_release)
+        if self.app.is_legacy is False:
+            self.canvas.graph_event_disconnect('mouse_press', self.on_canvas_click)
+            self.canvas.graph_event_disconnect('mouse_move', self.on_canvas_move)
+            self.canvas.graph_event_disconnect('mouse_release', self.on_exc_click_release)
+        else:
+            self.canvas.graph_event_disconnect(self.mp)
+            self.canvas.graph_event_disconnect(self.mm)
+            self.canvas.graph_event_disconnect(self.mr)
 
         try:
             self.app.ui.popmenu_copy.triggered.disconnect(self.exc_copy_drills)
@@ -2903,6 +2930,11 @@ class FlatCAMExcEditor(QtCore.QObject):
 
         self.select_tool("drill_select")
 
+        # reset the tool table
+        self.tools_table_exc.clear()
+        self.tools_table_exc.setHorizontalHeaderLabels(['#', _('Diameter'), 'D', 'S'])
+        self.last_tool_selected = None
+
         self.set_ui()
 
         # now that we hava data, create the GUI interface and add it to the Tool Tab
@@ -3053,6 +3085,7 @@ class FlatCAMExcEditor(QtCore.QObject):
         # element[1] of the tuple is a list of coordinates (a tuple themselves)
         ordered_edited_points = sorted(zip(edited_points.keys(), edited_points.values()))
 
+
         current_tool = 0
         for tool_dia in ordered_edited_points:
             current_tool += 1
@@ -3121,26 +3154,13 @@ class FlatCAMExcEditor(QtCore.QObject):
                     self.edited_obj_name += "_1"
             else:
                 self.edited_obj_name += "_edit"
-
-        self.app.worker_task.emit({'fcn': self.new_edited_excellon,
-                                   'params': [self.edited_obj_name]})
-
         self.new_tool_offset = self.exc_obj.tool_offset
 
-        # reset the tool table
-        self.tools_table_exc.clear()
-        self.tools_table_exc.setHorizontalHeaderLabels(['#', _('Diameter'), 'D', 'S'])
-        self.last_tool_selected = None
-
-        # delete the edited Excellon object which will be replaced by a new one having the edited content of the first
-        # self.app.collection.set_active(self.exc_obj.options['name'])
-        # self.app.collection.delete_active()
-
-        # restore GUI to the Selected TAB
-        # Remove anything else in the GUI
-        self.app.ui.tool_scroll_area.takeWidget()
-        # Switch notebook to Selected page
-        self.app.ui.notebook.setCurrentWidget(self.app.ui.selected_tab)
+        self.app.worker_task.emit({'fcn': self.new_edited_excellon,
+                                   'params': [self.edited_obj_name,
+                                              self.new_drills,
+                                              self.new_slots,
+                                              self.new_tools]})
 
     def update_options(self, obj):
         try:
@@ -3157,7 +3177,7 @@ class FlatCAMExcEditor(QtCore.QObject):
             obj.options = {}
             return True
 
-    def new_edited_excellon(self, outname):
+    def new_edited_excellon(self, outname, n_drills, n_slots, n_tools):
         """
         Creates a new Excellon object for the edited Excellon. Thread-safe.
 
@@ -3170,12 +3190,17 @@ class FlatCAMExcEditor(QtCore.QObject):
         self.app.log.debug("Update the Excellon object with edited content. Source is %s" %
                            self.exc_obj.options['name'])
 
+        new_drills = n_drills
+        new_slots = n_slots
+        new_tools = n_tools
+
         # How the object should be initialized
         def obj_init(excellon_obj, app_obj):
+
             # self.progress.emit(20)
-            excellon_obj.drills = self.new_drills
-            excellon_obj.tools = self.new_tools
-            excellon_obj.slots = self.new_slots
+            excellon_obj.drills = deepcopy(new_drills)
+            excellon_obj.tools = deepcopy(new_tools)
+            excellon_obj.slots = deepcopy(new_slots)
             excellon_obj.tool_offset = self.new_tool_offset
             excellon_obj.options['name'] = outname
 
@@ -3192,15 +3217,17 @@ class FlatCAMExcEditor(QtCore.QObject):
                 app_obj.inform.emit(msg)
                 raise
                 # raise
-            excellon_obj.source_file = self.app.export_excellon(obj_name=outname, filename=None,
-                                                                local_use=excellon_obj, use_thread=False)
 
         with self.app.proc_container.new(_("Creating Excellon.")):
 
             try:
-                self.app.new_object("excellon", outname, obj_init)
+                edited_obj = self.app.new_object("excellon", outname, obj_init)
+                edited_obj.source_file = self.app.export_excellon(obj_name=edited_obj.options['name'],
+                                                                  local_use=edited_obj,
+                                                                  filename=None,
+                                                                  use_thread=False)
             except Exception as e:
-                log.error("Error on object creation: %s" % str(e))
+                log.error("Error on Edited object creation: %s" % str(e))
                 self.app.progress.emit(100)
                 return
 
@@ -3282,25 +3309,28 @@ class FlatCAMExcEditor(QtCore.QObject):
         :param event: Event object dispatched by VisPy
         :return: None
         """
+        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
 
-        self.pos = self.canvas.translate_coords(event.pos)
+        self.pos = self.canvas.translate_coords(event_pos)
 
         if self.app.grid_status() == True:
             self.pos  = self.app.geo_editor.snap(self.pos[0], self.pos[1])
-            self.app.app_cursor.enabled = True
-            # Update cursor
-            self.app.app_cursor.set_data(np.asarray([(self.pos[0], self.pos[1])]), symbol='++', edge_color='black',
-                                         size=20)
         else:
             self.pos = (self.pos[0], self.pos[1])
-            self.app.app_cursor.enabled = False
 
-        if event.button is 1:
+        if event.button == 1:
             self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp;  <b>Dy</b>: "
                                                    "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (0, 0))
 
             # Selection with left mouse button
-            if self.active_tool is not None and event.button is 1:
+            if self.active_tool is not None and event.button == 1:
                 # Dispatch event to active_tool
                 # msg = self.active_tool.click(self.app.geo_editor.snap(event.xdata, event.ydata))
                 self.active_tool.click(self.app.geo_editor.snap(self.pos[0], self.pos[1]))
@@ -3318,6 +3348,7 @@ class FlatCAMExcEditor(QtCore.QObject):
                         modifier_to_use = Qt.ControlModifier
                     else:
                         modifier_to_use = Qt.ShiftModifier
+
                     # if modifier key is pressed then we add to the selected list the current shape but if it's already
                     # in the selected list, we removed it. Therefore first click selects, second deselects.
                     if key_modifier == modifier_to_use:
@@ -3422,7 +3453,17 @@ class FlatCAMExcEditor(QtCore.QObject):
         :param event: Event object dispatched by VisPy SceneCavas
         :return: None
         """
-        pos_canvas = self.canvas.translate_coords(event.pos)
+
+        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
+
+        pos_canvas = self.canvas.translate_coords(event_pos)
 
         if self.app.grid_status() == True:
             pos = self.app.geo_editor.snap(pos_canvas[0], pos_canvas[1])
@@ -3432,7 +3473,7 @@ class FlatCAMExcEditor(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
         try:
-            if event.button == 2:  # right click
+            if event.button == right_button:  # right click
                 if self.app.ui.popMenu.mouse_is_panning is False:
                     try:
                         QtGui.QGuiApplication.restoreOverrideCursor()
@@ -3580,7 +3621,16 @@ class FlatCAMExcEditor(QtCore.QObject):
         :return: None
         """
 
-        pos = self.canvas.translate_coords(event.pos)
+        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
+
+        pos = self.canvas.translate_coords(event_pos)
         event.xdata, event.ydata = pos[0], pos[1]
 
         self.x = event.xdata
@@ -3589,7 +3639,7 @@ class FlatCAMExcEditor(QtCore.QObject):
         self.app.ui.popMenu.mouse_is_panning = False
 
         # if the RMB is clicked and mouse is moving over plot then 'panning_action' is True
-        if event.button == 2 and event.is_dragging == 1:
+        if event.button == right_button and event_is_dragging == 1:
             self.app.ui.popMenu.mouse_is_panning = True
             return
 
@@ -3605,11 +3655,9 @@ class FlatCAMExcEditor(QtCore.QObject):
         # ## Snap coordinates
         if self.app.grid_status() == True:
             x, y = self.app.geo_editor.snap(x, y)
-            self.app.app_cursor.enabled = True
-            # Update cursor
-            self.app.app_cursor.set_data(np.asarray([(x, y)]), symbol='++', edge_color='black', size=20)
-        else:
-            self.app.app_cursor.enabled = False
+            if self.app.is_legacy is False:
+                # Update cursor
+                self.app.app_cursor.set_data(np.asarray([(x, y)]), symbol='++', edge_color='black', size=20)
 
         self.snap_x = x
         self.snap_y = y
@@ -3636,7 +3684,7 @@ class FlatCAMExcEditor(QtCore.QObject):
             self.draw_utility_geometry(geo=geo)
 
         # ## Selection area on canvas section # ##
-        if event.is_dragging == 1 and event.button == 1:
+        if event_is_dragging == 1 and event.button == 1:
             # I make an exception for FCDrillAdd and FCDrillArray because clicking and dragging while making regions
             # can create strange issues. Also for FCSlot and FCSlotArray
             if isinstance(self.active_tool, FCDrillAdd) or isinstance(self.active_tool, FCDrillArray) or \
@@ -3656,8 +3704,9 @@ class FlatCAMExcEditor(QtCore.QObject):
         else:
             self.app.selection_type = None
 
-        # Update cursor
-        self.app.app_cursor.set_data(np.asarray([(x, y)]), symbol='++', edge_color='black', size=20)
+        if self.app.is_legacy is False:
+            # Update cursor
+            self.app.app_cursor.set_data(np.asarray([(x, y)]), symbol='++', edge_color='black', size=20)
 
     def on_canvas_key_release(self, event):
         self.key = None
@@ -3718,10 +3767,10 @@ class FlatCAMExcEditor(QtCore.QObject):
                     continue
 
                 if shape_plus in self.selected:
-                    self.plot_shape(geometry=shape_plus.geo, color=self.app.defaults['global_sel_draw_color'],
+                    self.plot_shape(geometry=shape_plus.geo, color=self.app.defaults['global_sel_draw_color'] + 'FF',
                                     linewidth=2)
                     continue
-                self.plot_shape(geometry=shape_plus.geo, color=self.app.defaults['global_draw_color'])
+                self.plot_shape(geometry=shape_plus.geo, color=self.app.defaults['global_draw_color'] + 'FF')
 
         # for shape in self.storage.get_objects():
         #     if shape.geo is None:  # TODO: This shouldn't have happened
@@ -3739,7 +3788,7 @@ class FlatCAMExcEditor(QtCore.QObject):
 
         self.shapes.redraw()
 
-    def plot_shape(self, geometry=None, color='black', linewidth=1):
+    def plot_shape(self, geometry=None, color='0x000000FF', linewidth=1):
         """
         Plots a geometric object or list of objects without rendering. Plotted objects
         are returned as a list. This allows for efficient/animated rendering.

+ 81 - 42
flatcamEditors/FlatCAMGeoEditor.py

@@ -3025,8 +3025,14 @@ class FlatCAMGeoEditor(QtCore.QObject):
 
         # VisPy visuals
         self.fcgeometry = None
-        self.shapes = self.app.plotcanvas.new_shape_collection(layers=1)
-        self.tool_shape = self.app.plotcanvas.new_shape_collection(layers=1)
+        if self.app.is_legacy is False:
+            self.shapes = self.app.plotcanvas.new_shape_collection(layers=1)
+            self.tool_shape = self.app.plotcanvas.new_shape_collection(layers=1)
+        else:
+            from flatcamGUI.PlotCanvasLegacy import ShapeCollectionLegacy
+            self.shapes = ShapeCollectionLegacy(obj=self, app=self.app, name='shapes_geo_editor')
+            self.tool_shape = ShapeCollectionLegacy(obj=self, app=self.app, name='tool_shapes_geo_editor')
+
         self.app.pool_recreated.connect(self.pool_recreated)
 
         # Remove from scene
@@ -3163,6 +3169,11 @@ class FlatCAMGeoEditor(QtCore.QObject):
 
         self.transform_complete.connect(self.on_transform_complete)
 
+        # Event signals disconnect id holders
+        self.mp = None
+        self.mm = None
+        self.mr = None
+
         # store the status of the editor so the Delete at object level will not work until the edit is finished
         self.editor_active = False
         log.debug("Initialization of the FlatCAM Geometry Editor is finished ...")
@@ -3271,7 +3282,6 @@ class FlatCAMGeoEditor(QtCore.QObject):
         # Disable visuals
         self.shapes.enabled = False
         self.tool_shape.enabled = False
-        self.app.app_cursor.enabled = False
 
         self.app.ui.geo_editor_menu.setDisabled(True)
         self.app.ui.geo_editor_menu.menuAction().setVisible(False)
@@ -3309,16 +3319,23 @@ class FlatCAMGeoEditor(QtCore.QObject):
 
         # first connect to new, then disconnect the old handlers
         # don't ask why but if there is nothing connected I've seen issues
-        self.canvas.vis_connect('mouse_press', self.on_canvas_click)
-        self.canvas.vis_connect('mouse_move', self.on_canvas_move)
-        self.canvas.vis_connect('mouse_release', self.on_geo_click_release)
+        self.mp = self.canvas.graph_event_connect('mouse_press', self.on_canvas_click)
+        self.mm = self.canvas.graph_event_connect('mouse_move', self.on_canvas_move)
+        self.mr = self.canvas.graph_event_connect('mouse_release', self.on_geo_click_release)
+
+        if self.app.is_legacy is False:
+            # make sure that the shortcuts key and mouse events will no longer be linked to the methods from FlatCAMApp
+            # but those from FlatCAMGeoEditor
+            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)
+        else:
 
-        # make sure that the shortcuts key and mouse events will no longer be linked to the methods from FlatCAMApp
-        # but those from FlatCAMGeoEditor
-        self.app.plotcanvas.vis_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
-        self.app.plotcanvas.vis_disconnect('mouse_move', self.app.on_mouse_move_over_plot)
-        self.app.plotcanvas.vis_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
-        self.app.plotcanvas.vis_disconnect('mouse_double_click', self.app.on_double_click_over_plot)
+            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.app.plotcanvas.graph_event_disconnect(self.app.mdc)
 
         # self.app.collection.view.clicked.disconnect()
         self.app.ui.popmenu_copy.triggered.disconnect()
@@ -3354,15 +3371,22 @@ class FlatCAMGeoEditor(QtCore.QObject):
         # we restore the key and mouse control to FlatCAMApp method
         # first connect to new, then disconnect the old handlers
         # don't ask why but if there is nothing connected I've seen issues
-        self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
-        self.app.plotcanvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot)
-        self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
-        self.app.plotcanvas.vis_connect('mouse_double_click', self.app.on_double_click_over_plot)
+        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.app.mdc = self.app.plotcanvas.graph_event_connect('mouse_double_click',
+                                                               self.app.on_double_click_over_plot)
         # self.app.collection.view.clicked.connect(self.app.collection.on_mouse_down)
 
-        self.canvas.vis_disconnect('mouse_press', self.on_canvas_click)
-        self.canvas.vis_disconnect('mouse_move', self.on_canvas_move)
-        self.canvas.vis_disconnect('mouse_release', self.on_geo_click_release)
+        if self.app.is_legacy is False:
+            self.canvas.graph_event_disconnect('mouse_press', self.on_canvas_click)
+            self.canvas.graph_event_disconnect('mouse_move', self.on_canvas_move)
+            self.canvas.graph_event_disconnect('mouse_release', self.on_geo_click_release)
+        else:
+            self.canvas.graph_event_disconnect(self.mp)
+            self.canvas.graph_event_disconnect(self.mm)
+            self.canvas.graph_event_disconnect(self.mr)
 
         try:
             self.app.ui.popmenu_copy.triggered.disconnect(lambda: self.select_tool('copy'))
@@ -3625,18 +3649,17 @@ class FlatCAMGeoEditor(QtCore.QObject):
         :param event: Event object dispatched by Matplotlib
         :return: None
         """
+        if self.app.is_legacy is False:
+            event_pos = event.pos
+        else:
+            event_pos = (event.xdata, event.ydata)
 
-        self.pos = self.canvas.translate_coords(event.pos)
+        self.pos = self.canvas.translate_coords(event_pos)
 
         if self.app.grid_status() == True:
             self.pos = self.app.geo_editor.snap(self.pos[0], self.pos[1])
-            self.app.app_cursor.enabled = True
-            # Update cursor
-            self.app.app_cursor.set_data(np.asarray([(self.pos[0], self.pos[1])]), symbol='++', edge_color='black',
-                                         size=20)
         else:
             self.pos = (self.pos[0], self.pos[1])
-            self.app.app_cursor.enabled = False
 
         if event.button == 1:
             self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp;  <b>Dy</b>: "
@@ -3649,8 +3672,9 @@ class FlatCAMGeoEditor(QtCore.QObject):
                     self.app.defaults["global_point_clipboard_format"] % (self.pos[0], self.pos[1]))
                 return
 
+
             # Selection with left mouse button
-            if self.active_tool is not None and event.button is 1:
+            if self.active_tool is not None and event.button == 1:
 
                 # Dispatch event to active_tool
                 self.active_tool.click(self.snap(self.pos[0], self.pos[1]))
@@ -3678,7 +3702,16 @@ class FlatCAMGeoEditor(QtCore.QObject):
         :param event: Event object dispatched by VisPy SceneCavas
         :return: None
         """
-        pos = self.canvas.translate_coords(event.pos)
+        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
+
+        pos = self.canvas.translate_coords(event_pos)
         event.xdata, event.ydata = pos[0], pos[1]
 
         self.x = event.xdata
@@ -3687,8 +3720,8 @@ class FlatCAMGeoEditor(QtCore.QObject):
         self.app.ui.popMenu.mouse_is_panning = False
 
         # if the RMB is clicked and mouse is moving over plot then 'panning_action' is True
-        if event.button == 2:
-            if event.is_dragging:
+        if event.button == right_button:
+            if event_is_dragging:
                 self.app.ui.popMenu.mouse_is_panning = True
                 # return
             else:
@@ -3706,11 +3739,9 @@ class FlatCAMGeoEditor(QtCore.QObject):
         # ### Snap coordinates ###
         if self.app.grid_status() == True:
             x, y = self.snap(x, y)
-            self.app.app_cursor.enabled = True
-            # Update cursor
-            self.app.app_cursor.set_data(np.asarray([(x, y)]), symbol='++', edge_color='black', size=20)
-        else:
-            self.app.app_cursor.enabled = False
+            if self.app.is_legacy is False:
+                # Update cursor
+                self.app.app_cursor.set_data(np.asarray([(x, y)]), symbol='++', edge_color='black', size=20)
 
         self.snap_x = x
         self.snap_y = y
@@ -3728,7 +3759,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
         self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp;  <b>Dy</b>: "
                                            "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (dx, dy))
 
-        if event.button == 1 and event.is_dragging and isinstance(self.active_tool, FCEraser):
+        if event.button == 1 and event_is_dragging and isinstance(self.active_tool, FCEraser):
             pass
         else:
             # ### Utility geometry (animated) ###
@@ -3740,7 +3771,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
 
         # ### Selection area on canvas section ###
         dx = pos[0] - self.pos[0]
-        if event.is_dragging and event.button == 1:
+        if event_is_dragging and event.button == 1:
             self.app.delete_selection_shape()
             if dx < 0:
                 self.app.draw_moving_selection_shape((self.pos[0], self.pos[1]), (x, y),
@@ -3754,7 +3785,16 @@ class FlatCAMGeoEditor(QtCore.QObject):
             self.app.selection_type = None
 
     def on_geo_click_release(self, event):
-        pos_canvas = self.canvas.translate_coords(event.pos)
+        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
+
+        pos_canvas = self.canvas.translate_coords(event_pos)
 
         if self.app.grid_status() == True:
             pos = self.snap(pos_canvas[0], pos_canvas[1])
@@ -3776,7 +3816,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
                     self.active_tool.click_release((self.pos[0], self.pos[1]))
                     # self.app.inform.emit(msg)
                     self.replot()
-            elif event.button == 2:  # right click
+            elif event.button == right_button:  # right click
                 if self.app.ui.popMenu.mouse_is_panning == False:
                     if self.in_action is False:
                         try:
@@ -3943,7 +3983,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
         # return [shape for shape in self.shape_buffer if shape["selected"]]
         return self.selected
 
-    def plot_shape(self, geometry=None, color='black', linewidth=1):
+    def plot_shape(self, geometry=None, color='#000000FF', linewidth=1):
         """
         Plots a geometric object or list of objects without rendering. Plotted objects
         are returned as a list. This allows for efficient/animated rendering.
@@ -3961,7 +4001,6 @@ class FlatCAMGeoEditor(QtCore.QObject):
         try:
             for geo in geometry:
                 plot_elements += self.plot_shape(geometry=geo, color=color, linewidth=linewidth)
-
         # Non-iterable
         except TypeError:
 
@@ -3999,10 +4038,10 @@ class FlatCAMGeoEditor(QtCore.QObject):
                 continue
 
             if shape in self.selected:
-                self.plot_shape(geometry=shape.geo, color=self.app.defaults['global_sel_draw_color'], linewidth=2)
+                self.plot_shape(geometry=shape.geo, color=self.app.defaults['global_sel_draw_color'] + 'FF', linewidth=2)
                 continue
 
-            self.plot_shape(geometry=shape.geo, color=self.app.defaults['global_draw_color'])
+            self.plot_shape(geometry=shape.geo, color=self.app.defaults['global_draw_color'] + "FF")
 
         for shape in self.utility:
             self.plot_shape(geometry=shape.geo, linewidth=1)

+ 97 - 59
flatcamEditors/FlatCAMGrbEditor.py

@@ -2821,12 +2821,23 @@ class FlatCAMGrbEditor(QtCore.QObject):
         self.gerber_obj_options = dict()
 
         # VisPy Visuals
-        self.shapes = self.canvas.new_shape_collection(layers=1)
-        self.tool_shape = self.canvas.new_shape_collection(layers=1)
-        self.ma_annotation = self.canvas.new_text_group()
+        if self.app.is_legacy is False:
+            self.shapes = self.canvas.new_shape_collection(layers=1)
+            self.tool_shape = self.canvas.new_shape_collection(layers=1)
+            self.ma_annotation = self.canvas.new_text_group()
+        else:
+            from flatcamGUI.PlotCanvasLegacy import ShapeCollectionLegacy
+            self.shapes = ShapeCollectionLegacy(obj=self, app=self.app, name='shapes_grb_editor')
+            self.tool_shape = ShapeCollectionLegacy(obj=self, app=self.app, name='tool_shapes_grb_editor')
+            self.ma_annotation = ShapeCollectionLegacy(obj=self, app=self.app, name='ma_anno_grb_editor')
 
         self.app.pool_recreated.connect(self.pool_recreated)
 
+        # Event signals disconnect id holders
+        self.mp = None
+        self.mm = None
+        self.mr = None
+
         # Remove from scene
         self.shapes.enabled = False
         self.tool_shape.enabled = False
@@ -3511,14 +3522,21 @@ class FlatCAMGrbEditor(QtCore.QObject):
 
         # first connect to new, then disconnect the old handlers
         # don't ask why but if there is nothing connected I've seen issues
-        self.canvas.vis_connect('mouse_press', self.on_canvas_click)
-        self.canvas.vis_connect('mouse_move', self.on_canvas_move)
-        self.canvas.vis_connect('mouse_release', self.on_grb_click_release)
-
-        self.canvas.vis_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
-        self.canvas.vis_disconnect('mouse_move', self.app.on_mouse_move_over_plot)
-        self.canvas.vis_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
-        self.canvas.vis_disconnect('mouse_double_click', self.app.on_double_click_over_plot)
+        self.mp = self.canvas.graph_event_connect('mouse_press', self.on_canvas_click)
+        self.mm = self.canvas.graph_event_connect('mouse_move', self.on_canvas_move)
+        self.mr = self.canvas.graph_event_connect('mouse_release', self.on_grb_click_release)
+
+        if self.app.is_legacy is False:
+            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)
+        else:
+            self.canvas.graph_event_disconnect(self.app.mp)
+            self.canvas.graph_event_disconnect(self.app.mm)
+            self.canvas.graph_event_disconnect(self.app.mr)
+            self.canvas.graph_event_disconnect(self.app.mdc)
+
         self.app.collection.view.clicked.disconnect()
 
         self.app.ui.popmenu_copy.triggered.disconnect()
@@ -3550,15 +3568,20 @@ class FlatCAMGrbEditor(QtCore.QObject):
         # we restore the key and mouse control to FlatCAMApp method
         # first connect to new, then disconnect the old handlers
         # don't ask why but if there is nothing connected I've seen issues
-        self.canvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
-        self.canvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot)
-        self.canvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
-        self.canvas.vis_connect('mouse_double_click', self.app.on_double_click_over_plot)
+        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.collection.view.clicked.connect(self.app.collection.on_mouse_down)
 
-        self.canvas.vis_disconnect('mouse_press', self.on_canvas_click)
-        self.canvas.vis_disconnect('mouse_move', self.on_canvas_move)
-        self.canvas.vis_disconnect('mouse_release', self.on_grb_click_release)
+        if self.app.is_legacy is False:
+            self.canvas.graph_event_disconnect('mouse_press', self.on_canvas_click)
+            self.canvas.graph_event_disconnect('mouse_move', self.on_canvas_move)
+            self.canvas.graph_event_disconnect('mouse_release', self.on_grb_click_release)
+        else:
+            self.canvas.graph_event_disconnect(self.mp)
+            self.canvas.graph_event_disconnect(self.mm)
+            self.canvas.graph_event_disconnect(self.mr)
 
         try:
             self.app.ui.popmenu_copy.triggered.disconnect(self.on_copy_button)
@@ -3654,6 +3677,12 @@ class FlatCAMGrbEditor(QtCore.QObject):
         self.deactivate_grb_editor()
         self.activate_grb_editor()
 
+        # reset the tool table
+        self.apertures_table.clear()
+
+        self.apertures_table.setHorizontalHeaderLabels(['#', _('Code'), _('Type'), _('Size'), _('Dim')])
+        self.last_aperture_selected = None
+
         # create a reference to the source object
         self.gerber_obj = orig_grb_obj
         self.gerber_obj_options = orig_grb_obj.options
@@ -3846,19 +3875,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
             new_grb_name = self.edited_obj_name + "_edit"
 
         self.app.worker_task.emit({'fcn': self.new_edited_gerber,
-                                   'params': [new_grb_name]})
-
-        # reset the tool table
-        self.apertures_table.clear()
-
-        self.apertures_table.setHorizontalHeaderLabels(['#', _('Code'), _('Type'), _('Size'), _('Dim')])
-        self.last_aperture_selected = None
-
-        # restore GUI to the Selected TAB
-        # Remove anything else in the GUI
-        self.app.ui.selected_scroll_area.takeWidget()
-        # Switch notebook to Selected page
-        self.app.ui.notebook.setCurrentWidget(self.app.ui.selected_tab)
+                                   'params': [new_grb_name, self.storage_dict]})
 
     @staticmethod
     def update_options(obj):
@@ -3876,12 +3893,13 @@ class FlatCAMGrbEditor(QtCore.QObject):
             obj.options = dict()
             return True
 
-    def new_edited_gerber(self, outname):
+    def new_edited_gerber(self, outname, aperture_storage):
         """
         Creates a new Gerber object for the edited Gerber. Thread-safe.
 
         :param outname: Name of the resulting object. None causes the name to be that of the file.
         :type outname: str
+        :param aperture_storage: a dictionary that holds all the objects geometry
         :return: None
         """
 
@@ -3889,13 +3907,14 @@ class FlatCAMGrbEditor(QtCore.QObject):
                            self.gerber_obj.options['name'].upper())
 
         out_name = outname
+        storage_dict = aperture_storage
 
         local_storage_dict = dict()
-        for aperture in self.storage_dict:
-            if 'geometry' in self.storage_dict[aperture]:
+        for aperture in storage_dict:
+            if 'geometry' in storage_dict[aperture]:
                 # add aperture only if it has geometry
-                if len(self.storage_dict[aperture]['geometry']) > 0:
-                    local_storage_dict[aperture] = deepcopy(self.storage_dict[aperture])
+                if len(storage_dict[aperture]['geometry']) > 0:
+                    local_storage_dict[aperture] = deepcopy(storage_dict[aperture])
 
         # How the object should be initialized
         def obj_init(grb_obj, app_obj):
@@ -3984,7 +4003,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
             try:
                 self.app.new_object("gerber", outname, obj_init)
             except Exception as e:
-                log.error("Error on object creation: %s" % str(e))
+                log.error("Error on Edited object creation: %s" % str(e))
                 self.app.progress.emit(100)
                 return
 
@@ -4131,20 +4150,23 @@ class FlatCAMGrbEditor(QtCore.QObject):
         :param event: Event object dispatched by VisPy
         :return: None
         """
+        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
 
-        self.pos = self.canvas.translate_coords(event.pos)
+        self.pos = self.canvas.translate_coords(event_pos)
 
         if self.app.grid_status() == True:
             self.pos = self.app.geo_editor.snap(self.pos[0], self.pos[1])
-            self.app.app_cursor.enabled = True
-            # Update cursor
-            self.app.app_cursor.set_data(np.asarray([(self.pos[0], self.pos[1])]), symbol='++', edge_color='black',
-                                         size=20)
         else:
             self.pos = (self.pos[0], self.pos[1])
-            self.app.app_cursor.enabled = False
 
-        if event.button is 1:
+        if event.button == 1:
             self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp;  <b>Dy</b>: "
                                                    "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (0, 0))
 
@@ -4195,8 +4217,16 @@ class FlatCAMGrbEditor(QtCore.QObject):
 
     def on_grb_click_release(self, event):
         self.modifiers = QtWidgets.QApplication.keyboardModifiers()
+        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
 
-        pos_canvas = self.canvas.translate_coords(event.pos)
+        pos_canvas = self.canvas.translate_coords(event_pos)
         if self.app.grid_status() == True:
             pos = self.app.geo_editor.snap(pos_canvas[0], pos_canvas[1])
         else:
@@ -4205,7 +4235,7 @@ class FlatCAMGrbEditor(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
         try:
-            if event.button == 2:  # right click
+            if event.button == right_button:  # right click
                 if self.app.ui.popMenu.mouse_is_panning is False:
                     if self.in_action is False:
                         try:
@@ -4328,8 +4358,16 @@ class FlatCAMGrbEditor(QtCore.QObject):
         :param event: Event object dispatched by VisPy SceneCavas
         :return: None
         """
+        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
 
-        pos_canvas = self.canvas.translate_coords(event.pos)
+        pos_canvas = self.canvas.translate_coords(event_pos)
         event.xdata, event.ydata = pos_canvas[0], pos_canvas[1]
 
         self.x = event.xdata
@@ -4338,7 +4376,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
         self.app.ui.popMenu.mouse_is_panning = False
 
         # if the RMB is clicked and mouse is moving over plot then 'panning_action' is True
-        if event.button == 2 and event.is_dragging == 1:
+        if event.button == right_button and event_is_dragging == 1:
             self.app.ui.popMenu.mouse_is_panning = True
             return
 
@@ -4354,11 +4392,9 @@ class FlatCAMGrbEditor(QtCore.QObject):
         # # ## Snap coordinates
         if self.app.grid_status() == True:
             x, y = self.app.geo_editor.snap(x, y)
-            self.app.app_cursor.enabled = True
-            # Update cursor
-            self.app.app_cursor.set_data(np.asarray([(x, y)]), symbol='++', edge_color='black', size=20)
-        else:
-            self.app.app_cursor.enabled = False
+            if self.app.is_legacy is False:
+                # Update cursor
+                self.app.app_cursor.set_data(np.asarray([(x, y)]), symbol='++', edge_color='black', size=20)
 
         self.snap_x = x
         self.snap_y = y
@@ -4385,7 +4421,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
             self.draw_utility_geometry(geo=geo)
 
         # # ## Selection area on canvas section # ##
-        if event.is_dragging == 1 and event.button == 1:
+        if event_is_dragging == 1 and event.button == 1:
             # I make an exception for FCRegion and FCTrack because clicking and dragging while making regions can
             # create strange issues like missing a point in a track/region
             if isinstance(self.active_tool, FCRegion) or isinstance(self.active_tool, FCTrack):
@@ -4445,11 +4481,11 @@ class FlatCAMGrbEditor(QtCore.QObject):
 
                         if elem in self.selected:
                             self.plot_shape(geometry=geometric_data,
-                                            color=self.app.defaults['global_sel_draw_color'],
+                                            color=self.app.defaults['global_sel_draw_color'] + 'FF',
                                             linewidth=2)
                         else:
                             self.plot_shape(geometry=geometric_data,
-                                            color=self.app.defaults['global_draw_color'])
+                                            color=self.app.defaults['global_draw_color'] + 'FF')
 
             if self.utility:
                 for elem in self.utility:
@@ -4459,7 +4495,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
 
             self.shapes.redraw()
 
-    def plot_shape(self, geometry=None, color='black', linewidth=1):
+    def plot_shape(self, geometry=None, color='#000000FF', linewidth=1):
         """
         Plots a geometric object or list of objects without rendering. Plotted objects
         are returned as a list. This allows for efficient/animated rendering.
@@ -4475,10 +4511,12 @@ class FlatCAMGrbEditor(QtCore.QObject):
 
         try:
             self.shapes.add(shape=geometry.geo, color=color, face_color=color, layer=0, tolerance=self.tolerance)
-        except AttributeError:
+        except AttributeError as e:
             if type(geometry) == Point:
                 return
-            self.shapes.add(shape=geometry, color=color, face_color=color+'AF', layer=0, tolerance=self.tolerance)
+            if len(color) == 9:
+                color = color[:7] + 'AF'
+            self.shapes.add(shape=geometry, color=color, face_color=color, layer=0, tolerance=self.tolerance)
 
     def start_delayed_plot(self, check_period):
         """

+ 20 - 1
flatcamGUI/FlatCAMGUI.py

@@ -12,6 +12,7 @@
 # ##########################################################
 
 from flatcamGUI.PreferencesUI import *
+from matplotlib.backend_bases import KeyEvent as mpl_key_event
 
 import gettext
 import FlatCAMTranslation as fcTranslate
@@ -2271,6 +2272,24 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
         # 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
+            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.ControlModifier
+                elif mod.lower() == 'alt':
+                    modifiers = QtCore.Qt.AltModifier
+                elif mod.lower() == 'shift':
+                    modifiers = QtCore.Qt.ShiftModifier
+                else:
+                    modifiers = QtCore.Qt.NoModifier
+                key = QtGui.QKeySequence(key_text)
+
         # events from Vispy are of type KeyEvent
         else:
             key = event.key
@@ -2494,7 +2513,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
 
                     # try to disconnect the slot from Set Origin
                     try:
-                        self.app.plotcanvas.vis_disconnect('mouse_press', self.app.on_set_zero_click)
+                        self.app.plotcanvas.graph_event_disconnect('mouse_press', self.app.on_set_zero_click)
                     except TypeError:
                         pass
                     self.app.inform.emit("")

+ 2 - 2
flatcamGUI/PlotCanvas.py

@@ -152,10 +152,10 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas):
         except Exception as e:
             pass
 
-    def vis_connect(self, event_name, callback):
+    def graph_event_connect(self, event_name, callback):
         return getattr(self.events, event_name).connect(callback)
 
-    def vis_disconnect(self, event_name, callback=None):
+    def graph_event_disconnect(self, event_name, callback=None):
         if callback is None:
             getattr(self.events, event_name).disconnect()
         else:

+ 902 - 0
flatcamGUI/PlotCanvasLegacy.py

@@ -0,0 +1,902 @@
+############################################################
+# FlatCAM: 2D Post-processing for Manufacturing            #
+# http://caram.cl/software/flatcam                         #
+# Author: Juan Pablo Caram (c)                             #
+# Date: 2/5/2014                                           #
+# MIT Licence                                              #
+############################################################
+
+from PyQt5 import QtGui, QtCore, QtWidgets
+
+# Prevent conflict with Qt5 and above.
+from matplotlib import use as mpl_use
+
+from matplotlib.figure import Figure
+from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
+from matplotlib.backends.backend_agg import FigureCanvasAgg
+from matplotlib.widgets import Cursor
+
+# needed for legacy mode
+# Used for solid polygons in Matplotlib
+from descartes.patch import PolygonPatch
+
+from shapely.geometry import Polygon, LineString, LinearRing, Point, MultiPolygon, MultiLineString
+
+import FlatCAMApp
+from copy import deepcopy
+import logging
+
+mpl_use("Qt5Agg")
+log = logging.getLogger('base')
+
+
+class CanvasCache(QtCore.QObject):
+    """
+
+    Case story #1:
+
+    1) No objects in the project.
+    2) Object is created (new_object() emits object_created(obj)).
+       on_object_created() adds (i) object to collection and emits
+       (ii) new_object_available() then calls (iii) object.plot()
+    3) object.plot() creates axes if necessary on
+       app.collection.figure. Then plots on it.
+    4) Plots on a cache-size canvas (in background).
+    5) Plot completes. Bitmap is generated.
+    6) Visible canvas is painted.
+
+    """
+
+    # Signals:
+    # A bitmap is ready to be displayed.
+    new_screen = QtCore.pyqtSignal()
+
+    def __init__(self, plotcanvas, app, dpi=50):
+
+        super(CanvasCache, self).__init__()
+
+        self.app = app
+
+        self.plotcanvas = plotcanvas
+        self.dpi = dpi
+
+        self.figure = Figure(dpi=dpi)
+
+        self.axes = self.figure.add_axes([0.0, 0.0, 1.0, 1.0], alpha=1.0)
+        self.axes.set_frame_on(False)
+        self.axes.set_xticks([])
+        self.axes.set_yticks([])
+
+        self.canvas = FigureCanvasAgg(self.figure)
+
+        self.cache = None
+
+    def run(self):
+
+        log.debug("CanvasCache Thread Started!")
+        self.plotcanvas.update_screen_request.connect(self.on_update_req)
+
+    def on_update_req(self, extents):
+        """
+        Event handler for an updated display request.
+
+        :param extents: [xmin, xmax, ymin, ymax, zoom(optional)]
+        """
+
+        # log.debug("Canvas update requested: %s" % str(extents))
+
+        # Note: This information below might be out of date. Establish
+        # a protocol regarding when to change the canvas in the main
+        # thread and when to check these values here in the background,
+        # or pass this data in the signal (safer).
+        # log.debug("Size: %s [px]" % str(self.plotcanvas.get_axes_pixelsize()))
+        # log.debug("Density: %s [units/px]" % str(self.plotcanvas.get_density()))
+
+        # Move the requested screen portion to the main thread
+        # and inform about the update:
+
+        self.new_screen.emit()
+
+        # Continue to update the cache.
+
+    # def on_new_object_available(self):
+    #
+    #     log.debug("A new object is available. Should plot it!")
+
+
+class PlotCanvasLegacy(QtCore.QObject):
+    """
+    Class handling the plotting area in the application.
+    """
+
+    # Signals:
+    # Request for new bitmap to display. The parameter
+    # is a list with [xmin, xmax, ymin, ymax, zoom(optional)]
+    update_screen_request = QtCore.pyqtSignal(list)
+    double_click = QtCore.pyqtSignal(object)
+
+    def __init__(self, container, app):
+        """
+        The constructor configures the Matplotlib figure that
+        will contain all plots, creates the base axes and connects
+        events to the plotting area.
+
+        :param container: The parent container in which to draw plots.
+        :rtype: PlotCanvas
+        """
+
+        super(PlotCanvasLegacy, self).__init__()
+
+        self.app = app
+
+        # Options
+        self.x_margin = 15  # pixels
+        self.y_margin = 25  # Pixels
+
+        # Parent container
+        self.container = container
+
+        # Plots go onto a single matplotlib.figure
+        self.figure = Figure(dpi=50)  # TODO: dpi needed?
+        self.figure.patch.set_visible(False)
+
+        # These axes show the ticks and grid. No plotting done here.
+        # New axes must have a label, otherwise mpl returns an existing one.
+        self.axes = self.figure.add_axes([0.05, 0.05, 0.9, 0.9], label="base", alpha=0.0)
+        self.axes.set_aspect(1)
+        self.axes.grid(True)
+        self.axes.axhline(color=(0.70, 0.3, 0.3), linewidth=2)
+        self.axes.axvline(color=(0.70, 0.3, 0.3), linewidth=2)
+
+        # The canvas is the top level container (FigureCanvasQTAgg)
+        self.canvas = FigureCanvas(self.figure)
+        self.canvas.setFocusPolicy(QtCore.Qt.ClickFocus)
+        self.canvas.setFocus()
+        self.native = self.canvas
+
+        self.adjust_axes(-10, -10, 100, 100)
+        # self.canvas.set_can_focus(True)  # For key press
+
+        # Attach to parent
+        # self.container.attach(self.canvas, 0, 0, 600, 400)  # TODO: Height and width are num. columns??
+        self.container.addWidget(self.canvas)  # Qt
+
+        # Copy a bitmap of the canvas for quick animation.
+        # Update every time the canvas is re-drawn.
+        self.background = self.canvas.copy_from_bbox(self.axes.bbox)
+
+        # ## Bitmap Cache
+        self.cache = CanvasCache(self, self.app)
+        self.cache_thread = QtCore.QThread()
+        self.cache.moveToThread(self.cache_thread)
+        # super(PlotCanvas, self).connect(self.cache_thread, QtCore.SIGNAL("started()"), self.cache.run)
+        self.cache_thread.started.connect(self.cache.run)
+
+        self.cache_thread.start()
+        self.cache.new_screen.connect(self.on_new_screen)
+
+        # Events
+        self.mp = self.graph_event_connect('button_press_event', self.on_mouse_press)
+        self.mr = self.graph_event_connect('button_release_event', self.on_mouse_release)
+        self.mm = self.graph_event_connect('motion_notify_event', self.on_mouse_move)
+        # self.canvas.connect('configure-event', self.auto_adjust_axes)
+        self.aaa = self.graph_event_connect('resize_event', self.auto_adjust_axes)
+        # self.canvas.add_events(Gdk.EventMask.SMOOTH_SCROLL_MASK)
+        # self.canvas.connect("scroll-event", self.on_scroll)
+        self.osc = self.graph_event_connect('scroll_event', self.on_scroll)
+        # self.graph_event_connect('key_press_event', self.on_key_down)
+        # self.graph_event_connect('key_release_event', self.on_key_up)
+        self.odr = self.graph_event_connect('draw_event', self.on_draw)
+
+        self.mouse = [0, 0]
+        self.key = None
+
+        self.pan_axes = []
+        self.panning = False
+
+        # signal is the mouse is dragging
+        self.is_dragging = False
+
+        # signal if there is a doubleclick
+        self.is_dblclk = False
+
+    def graph_event_connect(self, event_name, callback):
+        """
+        Attach an event handler to the canvas through the Matplotlib interface.
+
+        :param event_name: Name of the event
+        :type event_name: str
+        :param callback: Function to call
+        :type callback: func
+        :return: Connection id
+        :rtype: int
+        """
+        if event_name == 'mouse_move':
+            event_name = 'motion_notify_event'
+        if event_name == 'mouse_press':
+            event_name = 'button_press_event'
+        if event_name == 'mouse_release':
+            event_name = 'button_release_event'
+        if event_name == 'mouse_double_click':
+            return self.double_click.connect(callback)
+
+        if event_name == 'key_press':
+            event_name = 'key_press_event'
+
+        return self.canvas.mpl_connect(event_name, callback)
+
+    def graph_event_disconnect(self, cid):
+        """
+        Disconnect callback with the give id.
+        :param cid: Callback id.
+        :return: None
+        """
+
+        # self.double_click.disconnect(cid)
+
+        self.canvas.mpl_disconnect(cid)
+
+    def on_new_screen(self):
+        pass
+        # log.debug("Cache updated the screen!")
+
+    def new_cursor(self, axes=None):
+        # if axes is None:
+        #     c = MplCursor(axes=self.axes, color='black', linewidth=1)
+        # else:
+        #     c = MplCursor(axes=axes, color='black', linewidth=1)
+
+        c = FakeCursor()
+
+        return c
+
+    def on_key_down(self, event):
+        """
+
+        :param event:
+        :return:
+        """
+        FlatCAMApp.App.log.debug('on_key_down(): ' + str(event.key))
+        self.key = event.key
+
+    def on_key_up(self, event):
+        """
+
+        :param event:
+        :return:
+        """
+        self.key = None
+
+    def connect(self, event_name, callback):
+        """
+        Attach an event handler to the canvas through the native Qt interface.
+
+        :param event_name: Name of the event
+        :type event_name: str
+        :param callback: Function to call
+        :type callback: function
+        :return: Nothing
+        """
+        self.canvas.connect(event_name, callback)
+
+    def clear(self):
+        """
+        Clears axes and figure.
+
+        :return: None
+        """
+
+        # Clear
+        self.axes.cla()
+        try:
+            self.figure.clf()
+        except KeyError:
+            FlatCAMApp.App.log.warning("KeyError in MPL figure.clf()")
+
+        # Re-build
+        self.figure.add_axes(self.axes)
+        self.axes.set_aspect(1)
+        self.axes.grid(True)
+
+        # Re-draw
+        self.canvas.draw_idle()
+
+    def adjust_axes(self, xmin, ymin, xmax, ymax):
+        """
+        Adjusts all axes while maintaining the use of the whole canvas
+        and an aspect ratio to 1:1 between x and y axes. The parameters are an original
+        request that will be modified to fit these restrictions.
+
+        :param xmin: Requested minimum value for the X axis.
+        :type xmin: float
+        :param ymin: Requested minimum value for the Y axis.
+        :type ymin: float
+        :param xmax: Requested maximum value for the X axis.
+        :type xmax: float
+        :param ymax: Requested maximum value for the Y axis.
+        :type ymax: float
+        :return: None
+        """
+
+        # FlatCAMApp.App.log.debug("PC.adjust_axes()")
+
+        width = xmax - xmin
+        height = ymax - ymin
+        try:
+            r = width / height
+        except ZeroDivisionError:
+            FlatCAMApp.App.log.error("Height is %f" % height)
+            return
+        canvas_w, canvas_h = self.canvas.get_width_height()
+        canvas_r = float(canvas_w) / canvas_h
+        x_ratio = float(self.x_margin) / canvas_w
+        y_ratio = float(self.y_margin) / canvas_h
+
+        if r > canvas_r:
+            ycenter = (ymin + ymax) / 2.0
+            newheight = height * r / canvas_r
+            ymin = ycenter - newheight / 2.0
+            ymax = ycenter + newheight / 2.0
+        else:
+            xcenter = (xmax + xmin) / 2.0
+            newwidth = width * canvas_r / r
+            xmin = xcenter - newwidth / 2.0
+            xmax = xcenter + newwidth / 2.0
+
+        # Adjust axes
+        for ax in self.figure.get_axes():
+            if ax._label != 'base':
+                ax.set_frame_on(False)  # No frame
+                ax.set_xticks([])  # No tick
+                ax.set_yticks([])  # No ticks
+                ax.patch.set_visible(False)  # No background
+                ax.set_aspect(1)
+            ax.set_xlim((xmin, xmax))
+            ax.set_ylim((ymin, ymax))
+            ax.set_position([x_ratio, y_ratio, 1 - 2 * x_ratio, 1 - 2 * y_ratio])
+
+        # Sync re-draw to proper paint on form resize
+        self.canvas.draw()
+
+        # #### Temporary place-holder for cached update #####
+        self.update_screen_request.emit([0, 0, 0, 0, 0])
+
+    def auto_adjust_axes(self, *args):
+        """
+        Calls ``adjust_axes()`` using the extents of the base axes.
+
+        :rtype : None
+        :return: None
+        """
+
+        xmin, xmax = self.axes.get_xlim()
+        ymin, ymax = self.axes.get_ylim()
+        self.adjust_axes(xmin, ymin, xmax, ymax)
+
+    def fit_view(self):
+        self.auto_adjust_axes()
+
+    def zoom(self, factor, center=None):
+        """
+        Zooms the plot by factor around a given
+        center point. Takes care of re-drawing.
+
+        :param factor: Number by which to scale the plot.
+        :type factor: float
+        :param center: Coordinates [x, y] of the point around which to scale the plot.
+        :type center: list
+        :return: None
+        """
+
+        factor = 1 / factor
+
+        xmin, xmax = self.axes.get_xlim()
+        ymin, ymax = self.axes.get_ylim()
+        width = xmax - xmin
+        height = ymax - ymin
+
+        if center is None or center == [None, None]:
+            center = [(xmin + xmax) / 2.0, (ymin + ymax) / 2.0]
+
+        # For keeping the point at the pointer location
+        relx = (xmax - center[0]) / width
+        rely = (ymax - center[1]) / height
+
+        new_width = width / factor
+        new_height = height / factor
+
+        xmin = center[0] - new_width * (1 - relx)
+        xmax = center[0] + new_width * relx
+        ymin = center[1] - new_height * (1 - rely)
+        ymax = center[1] + new_height * rely
+
+        # Adjust axes
+        for ax in self.figure.get_axes():
+            ax.set_xlim((xmin, xmax))
+            ax.set_ylim((ymin, ymax))
+
+        # Async re-draw
+        self.canvas.draw_idle()
+
+        # #### Temporary place-holder for cached update #####
+        self.update_screen_request.emit([0, 0, 0, 0, 0])
+
+    def pan(self, x, y):
+        xmin, xmax = self.axes.get_xlim()
+        ymin, ymax = self.axes.get_ylim()
+        width = xmax - xmin
+        height = ymax - ymin
+
+        # Adjust axes
+        for ax in self.figure.get_axes():
+            ax.set_xlim((xmin + x * width, xmax + x * width))
+            ax.set_ylim((ymin + y * height, ymax + y * height))
+
+        # Re-draw
+        self.canvas.draw_idle()
+
+        # #### Temporary place-holder for cached update #####
+        self.update_screen_request.emit([0, 0, 0, 0, 0])
+
+    def new_axes(self, name):
+        """
+        Creates and returns an Axes object attached to this object's Figure.
+
+        :param name: Unique label for the axes.
+        :return: Axes attached to the figure.
+        :rtype: Axes
+        """
+
+        return self.figure.add_axes([0.05, 0.05, 0.9, 0.9], label=name)
+
+    def on_scroll(self, event):
+        """
+        Scroll event handler.
+
+        :param event: Event object containing the event information.
+        :return: None
+        """
+
+        # So it can receive key presses
+        # self.canvas.grab_focus()
+        self.canvas.setFocus()
+
+        # Event info
+        # z, direction = event.get_scroll_direction()
+
+        if self.key is None:
+
+            if event.button == 'up':
+                self.zoom(1 / 1.5, self.mouse)
+            else:
+                self.zoom(1.5, self.mouse)
+            return
+
+        if self.key == 'shift':
+
+            if event.button == 'up':
+                self.pan(0.3, 0)
+            else:
+                self.pan(-0.3, 0)
+            return
+
+        if self.key == 'control':
+
+            if event.button == 'up':
+                self.pan(0, 0.3)
+            else:
+                self.pan(0, -0.3)
+            return
+
+    def on_mouse_press(self, event):
+
+        self.is_dragging = True
+
+        # Check for middle mouse button press
+        if self.app.defaults["global_pan_button"] == '2':
+            pan_button = 3  # right button for Matplotlib
+        else:
+            pan_button = 2  # middle button for Matplotlib
+
+        if event.button == pan_button:
+            # Prepare axes for pan (using 'matplotlib' pan function)
+            self.pan_axes = []
+            for a in self.figure.get_axes():
+                if (event.x is not None and event.y is not None and a.in_axes(event) and
+                        a.get_navigate() and a.can_pan()):
+                    a.start_pan(event.x, event.y, 1)
+                    self.pan_axes.append(a)
+
+            # Set pan view flag
+            if len(self.pan_axes) > 0:
+                self.panning = True
+
+        if event.dblclick:
+            self.double_click.emit(event)
+
+    def on_mouse_release(self, event):
+
+        self.is_dragging = False
+
+        # Check for middle mouse button release to complete pan procedure
+        # Check for middle mouse button press
+        if self.app.defaults["global_pan_button"] == '2':
+            pan_button = 3  # right button for Matplotlib
+        else:
+            pan_button = 2  # middle button for Matplotlib
+
+        if event.button == pan_button:
+            for a in self.pan_axes:
+                a.end_pan()
+
+            # Clear pan flag
+            self.panning = False
+
+    def on_mouse_move(self, event):
+        """
+        Mouse movement event hadler. Stores the coordinates. Updates view on pan.
+
+        :param event: Contains information about the event.
+        :return: None
+        """
+
+        try:
+            x = float(event.xdata)
+            y = float(event.ydata)
+        except TypeError:
+            return
+
+        self.mouse = [event.xdata, event.ydata]
+
+        self.canvas.restore_region(self.background)
+
+        # Update pan view on mouse move
+        if self.panning is True:
+            # x_pan, y_pan = self.app.geo_editor.snap(event.xdata, event.ydata)
+            # self.app.app_cursor.set_data(event, (x_pan, y_pan))
+            for a in self.pan_axes:
+                a.drag_pan(1, event.key, event.x, event.y)
+
+            # Async re-draw (redraws only on thread idle state, uses timer on backend)
+            self.canvas.draw_idle()
+
+            # #### Temporary place-holder for cached update #####
+            self.update_screen_request.emit([0, 0, 0, 0, 0])
+
+        x, y = self.app.geo_editor.snap(x, y)
+        if self.app.app_cursor.enabled is True:
+            # Pointer (snapped)
+            elements = self.axes.plot(x, y, 'k+', ms=40, mew=2, animated=True)
+            for el in elements:
+                self.axes.draw_artist(el)
+
+        self.canvas.blit(self.axes.bbox)
+
+    def translate_coords(self, position):
+        """
+        This does not do much. It's just for code compatibility
+
+        :param position: Mouse event position
+        :return: Tuple with mouse position
+        """
+        return (position[0], position[1])
+
+    def on_draw(self, renderer):
+
+        # Store background on canvas redraw
+        self.background = self.canvas.copy_from_bbox(self.axes.bbox)
+
+    def get_axes_pixelsize(self):
+        """
+        Axes size in pixels.
+
+        :return: Pixel width and height
+        :rtype: tuple
+        """
+        bbox = self.axes.get_window_extent().transformed(self.figure.dpi_scale_trans.inverted())
+        width, height = bbox.width, bbox.height
+        width *= self.figure.dpi
+        height *= self.figure.dpi
+        return width, height
+
+    def get_density(self):
+        """
+        Returns unit length per pixel on horizontal
+        and vertical axes.
+
+        :return: X and Y density
+        :rtype: tuple
+        """
+        xpx, ypx = self.get_axes_pixelsize()
+
+        xmin, xmax = self.axes.get_xlim()
+        ymin, ymax = self.axes.get_ylim()
+        width = xmax - xmin
+        height = ymax - ymin
+
+        return width / xpx, height / ypx
+
+
+class FakeCursor():
+    def __init__(self):
+        self._enabled = True
+
+    @property
+    def enabled(self):
+        return True if self._enabled else False
+
+    @enabled.setter
+    def enabled(self, value):
+        self._enabled = value
+
+
+class MplCursor(Cursor):
+
+    def __init__(self, axes, color='red', linewidth=1):
+
+        super().__init__(ax=axes, useblit=True, color=color, linewidth=linewidth)
+        self._enabled = True
+
+        self.axes = axes
+        self.color = color
+        self.linewidth = linewidth
+
+        self.x = None
+        self.y = None
+
+    @property
+    def enabled(self):
+        return True if self._enabled else False
+
+    @enabled.setter
+    def enabled(self, value):
+        self._enabled = value
+        self.visible = self._enabled
+        self.canvas.draw()
+
+    def onmove(self, event):
+        pass
+
+    def set_data(self, event, pos):
+        """Internal event handler to draw the cursor when the mouse moves."""
+        self.x = pos[0]
+        self.y = pos[1]
+
+        if self.ignore(event):
+            return
+        if not self.canvas.widgetlock.available(self):
+            return
+        if event.inaxes != self.ax:
+            self.linev.set_visible(False)
+            self.lineh.set_visible(False)
+
+            if self.needclear:
+                self.canvas.draw()
+                self.needclear = False
+            return
+        self.needclear = True
+        if not self.visible:
+            return
+        self.linev.set_xdata((self.x, self.x))
+
+        self.lineh.set_ydata((self.y, self.y))
+        self.linev.set_visible(self.visible and self.vertOn)
+        self.lineh.set_visible(self.visible and self.horizOn)
+
+        self._update()
+
+
+class ShapeCollectionLegacy:
+
+    def __init__(self, obj, app, name=None):
+
+        self.obj = obj
+        self.app = app
+
+        self._shapes = dict()
+        self.shape_dict = dict()
+        self.shape_id = 0
+
+        self._color = None
+        self._face_color = None
+        self._visible = True
+        self._update = False
+        self._alpha = None
+        self._tool_tolerance = None
+        self._tooldia = None
+
+        self._obj = None
+        self._gcode_parsed = None
+
+        if name is None:
+            axes_name = self.obj.options['name']
+        else:
+            axes_name = name
+
+        # Axes must exist and be attached to canvas.
+        if axes_name not in self.app.plotcanvas.figure.axes:
+            self.axes = self.app.plotcanvas.new_axes(axes_name)
+
+    def add(self, shape=None, color=None, face_color=None, alpha=None, visible=True,
+            update=False, layer=1, tolerance=0.01, obj=None, gcode_parsed=None, tool_tolerance=None, tooldia=None):
+
+        self._color = color[:-2] if color is not None else None
+        self._face_color = face_color[:-2] if face_color is not None else None
+        self._alpha = int(face_color[-2:], 16) / 255 if face_color is not None else 0.75
+
+        if alpha is not None:
+            self._alpha = alpha
+
+        self._visible = visible
+        self._update = update
+
+        # CNCJob oject related arguments
+        self._obj = obj
+        self._gcode_parsed = gcode_parsed
+        self._tool_tolerance = tool_tolerance
+        self._tooldia = tooldia
+
+        # if self._update:
+        #     self.clear()
+
+        try:
+            for sh in shape:
+                self.shape_id += 1
+                self.shape_dict.update({
+                    'color': self._color,
+                    'face_color': self._face_color,
+                    'alpha': self._alpha,
+                    'shape': sh
+                })
+
+                self._shapes.update({
+                    self.shape_id: deepcopy(self.shape_dict)
+                })
+        except TypeError:
+            self.shape_id += 1
+            self.shape_dict.update({
+                'color': self._color,
+                'face_color': self._face_color,
+                'alpha': self._alpha,
+                'shape': shape
+            })
+
+            self._shapes.update({
+                self.shape_id: deepcopy(self.shape_dict)
+            })
+
+        return self.shape_id
+
+    def clear(self, update=None):
+        self._shapes.clear()
+        self.shape_id = 0
+
+        self.axes.cla()
+        self.app.plotcanvas.auto_adjust_axes()
+
+        if update is True:
+            self.redraw()
+
+    def redraw(self):
+        path_num = 0
+        local_shapes = deepcopy(self._shapes)
+
+        try:
+            obj_type = self.obj.kind
+        except AttributeError:
+            obj_type = 'utility'
+
+        if self._visible:
+            for element in local_shapes:
+                if obj_type == 'excellon':
+                    # Plot excellon (All polygons?)
+                    if self.obj.options["solid"] and isinstance(local_shapes[element]['shape'], Polygon):
+                        patch = PolygonPatch(local_shapes[element]['shape'],
+                                             facecolor="#C40000",
+                                             edgecolor="#750000",
+                                             alpha=local_shapes[element]['alpha'],
+                                             zorder=3)
+                        self.axes.add_patch(patch)
+                    else:
+                        x, y = local_shapes[element]['shape'].exterior.coords.xy
+                        self.axes.plot(x, y, 'r-')
+                        for ints in local_shapes[element]['shape'].interiors:
+                            x, y = ints.coords.xy
+                            self.axes.plot(x, y, 'o-')
+                elif obj_type == 'geometry':
+                    if type(local_shapes[element]['shape']) == Polygon:
+                        x, y = local_shapes[element]['shape'].exterior.coords.xy
+                        self.axes.plot(x, y, local_shapes[element]['color'], linestyle='-')
+                        for ints in local_shapes[element]['shape'].interiors:
+                            x, y = ints.coords.xy
+                            self.axes.plot(x, y, local_shapes[element]['color'], linestyle='-')
+                    elif type(local_shapes[element]['shape']) == LineString or \
+                            type(local_shapes[element]['shape']) == LinearRing:
+
+                        x, y = local_shapes[element]['shape'].coords.xy
+                        self.axes.plot(x, y, local_shapes[element]['color'], linestyle='-')
+
+                elif obj_type == 'gerber':
+                    if self.obj.options["multicolored"]:
+                        linespec = '-'
+                    else:
+                        linespec = 'k-'
+
+                    if self.obj.options["solid"]:
+                        try:
+                            patch = PolygonPatch(local_shapes[element]['shape'],
+                                                 facecolor=local_shapes[element]['face_color'],
+                                                 edgecolor=local_shapes[element]['color'],
+                                                 alpha=local_shapes[element]['alpha'],
+                                                 zorder=2)
+                            self.axes.add_patch(patch)
+                        except AssertionError:
+                            FlatCAMApp.App.log.warning("A geometry component was not a polygon:")
+                            FlatCAMApp.App.log.warning(str(element))
+                    else:
+                        x, y = local_shapes[element]['shape'].exterior.xy
+                        self.axes.plot(x, y, linespec)
+                        for ints in local_shapes[element]['shape'].interiors:
+                            x, y = ints.coords.xy
+                            self.axes.plot(x, y, linespec)
+                elif obj_type == 'cncjob':
+
+                    if local_shapes[element]['face_color'] is None:
+                        linespec = '--'
+                        linecolor = local_shapes[element]['color']
+                        # if geo['kind'][0] == 'C':
+                        #     linespec = 'k-'
+                        x, y = local_shapes[element]['shape'].coords.xy
+                        self.axes.plot(x, y, linespec, color=linecolor)
+                    else:
+                        path_num += 1
+                        if isinstance(local_shapes[element]['shape'], Polygon):
+                            self.axes.annotate(str(path_num), xy=local_shapes[element]['shape'].exterior.coords[0],
+                                               xycoords='data', fontsize=20)
+                        else:
+                            self.axes.annotate(str(path_num), xy=local_shapes[element]['shape'].coords[0],
+                                               xycoords='data', fontsize=20)
+
+                        patch = PolygonPatch(local_shapes[element]['shape'],
+                                             facecolor=local_shapes[element]['face_color'],
+                                             edgecolor=local_shapes[element]['color'],
+                                             alpha=local_shapes[element]['alpha'], zorder=2)
+                        self.axes.add_patch(patch)
+                elif obj_type == 'utility':
+                    # not a FlatCAM object, must be utility
+                    if local_shapes[element]['face_color']:
+                        try:
+                            patch = PolygonPatch(local_shapes[element]['shape'],
+                                                 facecolor=local_shapes[element]['face_color'],
+                                                 edgecolor=local_shapes[element]['color'],
+                                                 alpha=local_shapes[element]['alpha'],
+                                                 zorder=2)
+                            self.axes.add_patch(patch)
+                        except Exception as e:
+                            log.debug("ShapeCollectionLegacy.redraw() --> %s" % str(e))
+                    else:
+                        if isinstance(local_shapes[element]['shape'], Polygon):
+                            x, y = local_shapes[element]['shape'].exterior.xy
+                            self.axes.plot(x, y, local_shapes[element]['color'], linestyle='-')
+                            for ints in local_shapes[element]['shape'].interiors:
+                                x, y = ints.coords.xy
+                                self.axes.plot(x, y, local_shapes[element]['color'], linestyle='-')
+                        else:
+                            x, y = local_shapes[element]['shape'].coords.xy
+                            self.axes.plot(x, y, local_shapes[element]['color'], linestyle='-')
+
+        self.app.plotcanvas.auto_adjust_axes()
+
+    @property
+    def visible(self):
+        return self._visible
+
+    @visible.setter
+    def visible(self, value):
+        if value is False:
+            self.axes.cla()
+            self.app.plotcanvas.auto_adjust_axes()
+        else:
+            if self._visible is False:
+                self.redraw()
+        self._visible = value

+ 13 - 0
flatcamGUI/PreferencesUI.py

@@ -846,6 +846,17 @@ class GeneralAppPrefGroupUI(OptionsGroupUI):
         self.units_radio = RadioSet([{'label': _('IN'), 'value': 'IN'},
                                      {'label': _('MM'), 'value': 'MM'}])
 
+        # Graphic Engine for FlatCAM
+        self.ge_label = QtWidgets.QLabel('<b>%s:</b>' % _('Graphic Engine'))
+        self.ge_label.setToolTip(_("Choose what graphic engine to use in FlatCAM.\n"
+                                   "Legacy(2D) -> reduced functionality, slow performance but enhanced compatibility.\n"
+                                   "OpenGL(3D) -> full functionality, high performance\n"
+                                   "Some graphic cards are too old and do not work in OpenGL(3D) mode, like:\n"
+                                   "Intel HD3000 or older. In this case the plot area will be black therefore\n"
+                                   "use the Legacy(2D) mode."))
+        self.ge_radio = RadioSet([{'label': _('Legacy(2D)'), 'value': '2D'},
+                                  {'label': _('OpenGL(3D)'), 'value': '3D'}])
+
         # Application Level for FlatCAM
         self.app_level_label = QtWidgets.QLabel('<b>%s:</b>' % _('APP. LEVEL'))
         self.app_level_label.setToolTip(_("Choose the default level of usage for FlatCAM.\n"
@@ -963,6 +974,8 @@ class GeneralAppPrefGroupUI(OptionsGroupUI):
 
         # Add (label - input field) pair to the QFormLayout
         self.form_box.addRow(self.unitslabel, self.units_radio)
+        self.form_box.addRow(self.ge_label, self.ge_radio)
+        self.form_box.addRow(QtWidgets.QLabel(''))
         self.form_box.addRow(self.app_level_label, self.app_level_radio)
         self.form_box.addRow(self.portability_label, self.portability_cb)
         self.form_box.addRow(QtWidgets.QLabel(''))

+ 102 - 36
flatcamTools/ToolCutOut.py

@@ -298,6 +298,11 @@ class CutOut(FlatCAMTool):
         # if mouse is dragging set the object True
         self.mouse_is_dragging = False
 
+        # event handlers references
+        self.kp = None
+        self.mm = None
+        self.mr = None
+
         # hold the mouse position here
         self.x_pos = None
         self.y_pos = None
@@ -780,13 +785,21 @@ class CutOut(FlatCAMTool):
             self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve Geometry object"), name))
             return "Could not retrieve object: %s" % name
 
-        self.app.plotcanvas.vis_disconnect('key_press', self.app.ui.keyPressEvent)
-        self.app.plotcanvas.vis_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
-        self.app.plotcanvas.vis_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
-        self.app.plotcanvas.vis_disconnect('mouse_move', self.app.on_mouse_move_over_plot)
-        self.app.plotcanvas.vis_connect('key_press', self.on_key_press)
-        self.app.plotcanvas.vis_connect('mouse_move', self.on_mouse_move)
-        self.app.plotcanvas.vis_connect('mouse_release', self.on_mouse_click_release)
+        if self.app.is_legacy is False:
+            self.app.plotcanvas.graph_event_disconnect('key_press', self.app.ui.keyPressEvent)
+            self.app.plotcanvas.graph_event_disconnect('mouse_press', self.app.on_mouse_click_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_move', self.app.on_mouse_move_over_plot)
+        else:
+            self.app.plotcanvas.graph_event_disconnect(self.app.kp)
+            self.app.plotcanvas.graph_event_disconnect(self.app.mp)
+            self.app.plotcanvas.graph_event_disconnect(self.app.mr)
+            self.app.plotcanvas.graph_event_disconnect(self.app.mm)
+
+        self.kp = self.app.plotcanvas.graph_event_connect('key_press', self.on_key_press)
+        self.mm = self.app.plotcanvas.graph_event_connect('mouse_move', self.on_mouse_move)
+        self.mr = self.app.plotcanvas.graph_event_connect('mouse_release', self.on_mouse_click_release)
+
 
     def on_manual_cutout(self, click_pos):
         name = self.man_object_combo.currentText()
@@ -923,33 +936,46 @@ class CutOut(FlatCAMTool):
     # To be called after clicking on the plot.
     def on_mouse_click_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)
+            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
+        event_pos = (x, y)
+
         # do paint single only for left mouse clicks
         if event.button == 1:
             self.app.inform.emit(_("Making manual bridge gap..."))
-            pos = self.app.plotcanvas.translate_coords(event.pos)
-            self.on_manual_cutout(click_pos=pos)
 
-            # self.app.plotcanvas.vis_disconnect('key_press', self.on_key_press)
-            # self.app.plotcanvas.vis_disconnect('mouse_move', self.on_mouse_move)
-            # self.app.plotcanvas.vis_disconnect('mouse_release', self.on_mouse_click_release)
-            # self.app.plotcanvas.vis_connect('key_press', self.app.ui.keyPressEvent)
-            # self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
-            # self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
-            # self.app.plotcanvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot)
+            pos = self.app.plotcanvas.translate_coords(event_pos)
 
-            # self.app.geo_editor.tool_shape.clear(update=True)
-            # self.app.geo_editor.tool_shape.enabled = False
-            # self.gapFinished.emit()
+            self.on_manual_cutout(click_pos=pos)
 
         # if RMB then we exit
-        elif event.button == 2 and self.mouse_is_dragging is False:
-            self.app.plotcanvas.vis_disconnect('key_press', self.on_key_press)
-            self.app.plotcanvas.vis_disconnect('mouse_move', self.on_mouse_move)
-            self.app.plotcanvas.vis_disconnect('mouse_release', self.on_mouse_click_release)
-            self.app.plotcanvas.vis_connect('key_press', self.app.ui.keyPressEvent)
-            self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
-            self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
-            self.app.plotcanvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot)
+        elif event.button == right_button and self.mouse_is_dragging is False:
+            if self.app.is_legacy is False:
+                self.app.plotcanvas.graph_event_disconnect('key_press', self.on_key_press)
+                self.app.plotcanvas.graph_event_disconnect('mouse_move', self.on_mouse_move)
+                self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_mouse_click_release)
+            else:
+                self.app.plotcanvas.graph_event_disconnect(self.kp)
+                self.app.plotcanvas.graph_event_disconnect(self.mm)
+                self.app.plotcanvas.graph_event_disconnect(self.mr)
+
+            self.app.kp = self.app.plotcanvas.graph_event_connect('key_press', self.app.ui.keyPressEvent)
+            self.app.mp = self.app.plotcanvas.graph_event_connect('mouse_press', self.app.on_mouse_click_over_plot)
+            self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release',
+                                                                  self.app.on_mouse_click_release_over_plot)
+            self.app.mm = self.app.plotcanvas.graph_event_connect('mouse_move', self.app.on_mouse_move_over_plot)
 
             # Remove any previous utility shape
             self.app.geo_editor.tool_shape.clear(update=True)
@@ -959,10 +985,26 @@ class CutOut(FlatCAMTool):
 
         self.app.on_mouse_move_over_plot(event=event)
 
-        pos = self.canvas.translate_coords(event.pos)
+        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
+        event_pos = (x, y)
+
+        pos = self.canvas.translate_coords(event_pos)
         event.xdata, event.ydata = pos[0], pos[1]
 
-        if event.is_dragging is True:
+        if event_is_dragging is True:
             self.mouse_is_dragging = True
         else:
             self.mouse_is_dragging = False
@@ -1058,19 +1100,43 @@ class CutOut(FlatCAMTool):
         # 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
+            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.ControlModifier
+                elif mod.lower() == 'alt':
+                    modifiers = QtCore.Qt.AltModifier
+                elif mod.lower() == 'shift':
+                    modifiers = QtCore.Qt.ShiftModifier
+                else:
+                    modifiers = QtCore.Qt.NoModifier
+                key = QtGui.QKeySequence(key_text)
         # events from Vispy are of type KeyEvent
         else:
             key = event.key
 
         # Escape = Deselect All
         if key == QtCore.Qt.Key_Escape or key == 'Escape':
-            self.app.plotcanvas.vis_disconnect('key_press', self.on_key_press)
-            self.app.plotcanvas.vis_disconnect('mouse_move', self.on_mouse_move)
-            self.app.plotcanvas.vis_disconnect('mouse_release', self.on_mouse_click_release)
-            self.app.plotcanvas.vis_connect('key_press', self.app.ui.keyPressEvent)
-            self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
-            self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
-            self.app.plotcanvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot)
+            if self.app.is_legacy is False:
+                self.app.plotcanvas.graph_event_disconnect('key_press', self.on_key_press)
+                self.app.plotcanvas.graph_event_disconnect('mouse_move', self.on_mouse_move)
+                self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_mouse_click_release)
+            else:
+                self.app.plotcanvas.graph_event_disconnect(self.kp)
+                self.app.plotcanvas.graph_event_disconnect(self.mm)
+                self.app.plotcanvas.graph_event_disconnect(self.mr)
+
+            self.app.kp = self.app.plotcanvas.graph_event_connect('key_press', self.app.ui.keyPressEvent)
+            self.app.mp = self.app.plotcanvas.graph_event_connect('mouse_press', self.app.on_mouse_click_over_plot)
+            self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release',
+                                                                  self.app.on_mouse_click_release_over_plot)
+            self.app.mm = self.app.plotcanvas.graph_event_connect('mouse_move', self.app.on_mouse_move_over_plot)
 
             # Remove any previous utility shape
             self.app.geo_editor.tool_shape.clear(update=True)

+ 111 - 48
flatcamTools/ToolMeasurement.py

@@ -113,7 +113,11 @@ class Measurement(FlatCAMTool):
         self.original_call_source = 'app'
 
         # VisPy visuals
-        self.sel_shapes = ShapeCollection(parent=self.app.plotcanvas.view.scene, layers=1)
+        if self.app.is_legacy is False:
+            self.sel_shapes = ShapeCollection(parent=self.app.plotcanvas.view.scene, layers=1)
+        else:
+            from flatcamGUI.PlotCanvasLegacy import ShapeCollectionLegacy
+            self.sel_shapes = ShapeCollectionLegacy(obj=self, app=self.app, name='measurement')
 
         self.measure_btn.clicked.connect(self.activate_measure_tool)
 
@@ -178,26 +182,49 @@ class Measurement(FlatCAMTool):
 
         # we can connect the app mouse events to the measurement tool
         # NEVER DISCONNECT THOSE before connecting some other handlers; it breaks something in VisPy
-        self.canvas.vis_connect('mouse_move', self.on_mouse_move_meas)
-        self.canvas.vis_connect('mouse_release', self.on_mouse_click_release)
+        self.mm = self.canvas.graph_event_connect('mouse_move', self.on_mouse_move_meas)
+        self.mr = self.canvas.graph_event_connect('mouse_release', self.on_mouse_click_release)
 
         # we disconnect the mouse/key handlers from wherever the measurement tool was called
         if self.app.call_source == 'app':
-            self.canvas.vis_disconnect('mouse_move', self.app.on_mouse_move_over_plot)
-            self.canvas.vis_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
-            self.canvas.vis_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
+            if self.app.is_legacy is False:
+                self.canvas.graph_event_disconnect('mouse_move', self.app.on_mouse_move_over_plot)
+                self.canvas.graph_event_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
+                self.canvas.graph_event_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
+            else:
+                self.canvas.graph_event_disconnect(self.app.mm)
+                self.canvas.graph_event_disconnect(self.app.mp)
+                self.canvas.graph_event_disconnect(self.app.mr)
+
         elif self.app.call_source == 'geo_editor':
-            self.canvas.vis_disconnect('mouse_move', self.app.geo_editor.on_canvas_move)
-            self.canvas.vis_disconnect('mouse_press', self.app.geo_editor.on_canvas_click)
-            self.canvas.vis_disconnect('mouse_release', self.app.geo_editor.on_geo_click_release)
+            if self.app.is_legacy is False:
+                self.canvas.graph_event_disconnect('mouse_move', self.app.geo_editor.on_canvas_move)
+                self.canvas.graph_event_disconnect('mouse_press', self.app.geo_editor.on_canvas_click)
+                self.canvas.graph_event_disconnect('mouse_release', self.app.geo_editor.on_geo_click_release)
+            else:
+                self.canvas.graph_event_disconnect(self.app.geo_editor.mm)
+                self.canvas.graph_event_disconnect(self.app.geo_editor.mp)
+                self.canvas.graph_event_disconnect(self.app.geo_editor.mr)
+
         elif self.app.call_source == 'exc_editor':
-            self.canvas.vis_disconnect('mouse_move', self.app.exc_editor.on_canvas_move)
-            self.canvas.vis_disconnect('mouse_press', self.app.exc_editor.on_canvas_click)
-            self.canvas.vis_disconnect('mouse_release', self.app.exc_editor.on_exc_click_release)
+            if self.app.is_legacy is False:
+                self.canvas.graph_event_disconnect('mouse_move', self.app.exc_editor.on_canvas_move)
+                self.canvas.graph_event_disconnect('mouse_press', self.app.exc_editor.on_canvas_click)
+                self.canvas.graph_event_disconnect('mouse_release', self.app.exc_editor.on_exc_click_release)
+            else:
+                self.canvas.graph_event_disconnect(self.app.exc_editor.mm)
+                self.canvas.graph_event_disconnect(self.app.exc_editor.mp)
+                self.canvas.graph_event_disconnect(self.app.exc_editor.mr)
+
         elif self.app.call_source == 'grb_editor':
-            self.canvas.vis_disconnect('mouse_move', self.app.grb_editor.on_canvas_move)
-            self.canvas.vis_disconnect('mouse_press', self.app.grb_editor.on_canvas_click)
-            self.canvas.vis_disconnect('mouse_release', self.app.grb_editor.on_grb_click_release)
+            if self.app.is_legacy is False:
+                self.canvas.graph_event_disconnect('mouse_move', self.app.grb_editor.on_canvas_move)
+                self.canvas.graph_event_disconnect('mouse_press', self.app.grb_editor.on_canvas_click)
+                self.canvas.graph_event_disconnect('mouse_release', self.app.grb_editor.on_grb_click_release)
+            else:
+                self.canvas.graph_event_disconnect(self.app.grb_editor.mm)
+                self.canvas.graph_event_disconnect(self.app.grb_editor.mp)
+                self.canvas.graph_event_disconnect(self.app.grb_editor.mr)
 
         self.app.call_source = 'measurement'
 
@@ -210,25 +237,35 @@ class Measurement(FlatCAMTool):
 
         self.app.call_source = copy(self.original_call_source)
         if self.original_call_source == 'app':
-            self.canvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot)
-            self.canvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
-            self.canvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
+            self.app.mm = self.canvas.graph_event_connect('mouse_move', self.app.on_mouse_move_over_plot)
+            self.app.mp = self.canvas.graph_event_connect('mouse_press', self.app.on_mouse_click_over_plot)
+            self.app.mr = self.canvas.graph_event_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
+
         elif self.original_call_source == 'geo_editor':
-            self.canvas.vis_connect('mouse_move', self.app.geo_editor.on_canvas_move)
-            self.canvas.vis_connect('mouse_press', self.app.geo_editor.on_canvas_click)
-            self.canvas.vis_connect('mouse_release', self.app.geo_editor.on_geo_click_release)
+            self.app.geo_editor.mm = self.canvas.graph_event_connect('mouse_move', self.app.geo_editor.on_canvas_move)
+            self.app.geo_editor.mp = self.canvas.graph_event_connect('mouse_press', self.app.geo_editor.on_canvas_click)
+            self.app.geo_editor.mr = self.canvas.graph_event_connect('mouse_release',
+                                                                     self.app.geo_editor.on_geo_click_release)
+
         elif self.original_call_source == 'exc_editor':
-            self.canvas.vis_connect('mouse_move', self.app.exc_editor.on_canvas_move)
-            self.canvas.vis_connect('mouse_press', self.app.exc_editor.on_canvas_click)
-            self.canvas.vis_connect('mouse_release', self.app.exc_editor.on_exc_click_release)
+            self.app.exc_editor.mm = self.canvas.graph_event_connect('mouse_move', self.app.exc_editor.on_canvas_move)
+            self.app.exc_editor.mp = self.canvas.graph_event_connect('mouse_press', self.app.exc_editor.on_canvas_click)
+            self.app.exc_editor.mr = self.canvas.graph_event_connect('mouse_release',
+                                                                     self.app.exc_editor.on_exc_click_release)
+
         elif self.original_call_source == 'grb_editor':
-            self.canvas.vis_connect('mouse_move', self.app.grb_editor.on_canvas_move)
-            self.canvas.vis_connect('mouse_press', self.app.grb_editor.on_canvas_click)
-            self.canvas.vis_connect('mouse_release', self.app.grb_editor.on_grb_click_release)
+            self.app.grb_editor.mm = self.canvas.graph_event_connect('mouse_move', self.app.grb_editor.on_canvas_move)
+            self.app.grb_editor.mp = self.canvas.graph_event_connect('mouse_press', self.app.grb_editor.on_canvas_click)
+            self.app.grb_editor.mr = self.canvas.graph_event_connect('mouse_release',
+                                                                     self.app.grb_editor.on_grb_click_release)
 
         # disconnect the mouse/key events from functions of measurement tool
-        self.canvas.vis_disconnect('mouse_move', self.on_mouse_move_meas)
-        self.canvas.vis_disconnect('mouse_release', self.on_mouse_click_release)
+        if self.app.is_legacy is False:
+            self.canvas.graph_event_disconnect('mouse_move', self.on_mouse_move_meas)
+            self.canvas.graph_event_disconnect('mouse_release', self.on_mouse_click_release)
+        else:
+            self.canvas.graph_event_disconnect(self.mm)
+            self.canvas.graph_event_disconnect(self.mr)
 
         # self.app.ui.notebook.setTabText(2, _("Tools"))
         # self.app.ui.notebook.setCurrentWidget(self.app.ui.project_tab)
@@ -247,7 +284,13 @@ class Measurement(FlatCAMTool):
         log.debug("Measuring Tool --> mouse click release")
 
         if event.button == 1:
-            pos_canvas = self.canvas.translate_coords(event.pos)
+            if self.app.is_legacy is False:
+                event_pos = event.pos
+            else:
+                event_pos = (event.xdata, event.ydata)
+
+            pos_canvas = self.canvas.translate_coords(event_pos)
+
             # if GRID is active we need to get the snapped positions
             if self.app.grid_status() == True:
                 pos = self.app.geo_editor.snap(pos_canvas[0], pos_canvas[1])
@@ -267,8 +310,7 @@ class Measurement(FlatCAMTool):
             if len(self.points) == 1:
                 self.start_entry.set_value("(%.4f, %.4f)" % pos)
                 self.app.inform.emit(_("MEASURING: Click on the Destination point ..."))
-
-            if len(self.points) == 2:
+            elif len(self.points) == 2:
                 dx = self.points[1][0] - self.points[0][0]
                 dy = self.points[1][1] - self.points[0][1]
                 d = sqrt(dx ** 2 + dy ** 2)
@@ -280,47 +322,68 @@ class Measurement(FlatCAMTool):
                 self.distance_x_entry.set_value('%.4f' % abs(dx))
                 self.distance_y_entry.set_value('%.4f' % abs(dy))
                 self.total_distance_entry.set_value('%.4f' % abs(d))
-                self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp;  <b>Dy</b>: "
-                                                       "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (pos[0], pos[1]))
+                self.app.ui.rel_position_label.setText("<b>Dx</b>: {0:.4f}&nbsp;&nbsp;  <b>Dy</b>: "
+                                                       "{0:.4f}&nbsp;&nbsp;&nbsp;&nbsp;".format(pos[0], pos[1]))
                 self.deactivate_measure_tool()
 
     def on_mouse_move_meas(self, event):
         try:  # May fail in case mouse not within axes
-            pos_canvas = self.app.plotcanvas.translate_coords(event.pos)
+            if self.app.is_legacy is False:
+                event_pos = event.pos
+            else:
+                event_pos = (event.xdata, event.ydata)
+
+            try:
+                x = float(event_pos[0])
+                y = float(event_pos[1])
+            except TypeError:
+                return
+
+            pos_canvas = self.app.plotcanvas.translate_coords((x, y))
+
             if self.app.grid_status() == True:
                 pos = self.app.geo_editor.snap(pos_canvas[0], pos_canvas[1])
-                self.app.app_cursor.enabled = True
-                # Update cursor
-                self.app.app_cursor.set_data(np.asarray([(pos[0], pos[1])]),
-                                             symbol='++', edge_color='black', size=20)
+                if self.app.is_legacy is False:
+                    # Update cursor
+                    self.app.app_cursor.set_data(np.asarray([(pos[0], pos[1])]),
+                                                 symbol='++', edge_color='black', size=20)
             else:
                 pos = (pos_canvas[0], pos_canvas[1])
-                self.app.app_cursor.enabled = False
+
+            self.app.ui.position_label.setText("&nbsp;&nbsp;&nbsp;&nbsp;<b>X</b>: {0:.4f}&nbsp;&nbsp;   "
+                                               "<b>Y</b>: {0:.4f}".format(pos[0], pos[1]))
 
             if self.rel_point1 is not None:
-                dx = pos[0] - self.rel_point1[0]
-                dy = pos[1] - self.rel_point1[1]
+                dx = pos[0] - float(self.rel_point1[0])
+                dy = pos[1] - float(self.rel_point1[1])
             else:
                 dx = pos[0]
                 dy = pos[1]
 
-            self.app.ui.position_label.setText("&nbsp;&nbsp;&nbsp;&nbsp;<b>X</b>: %.4f&nbsp;&nbsp;   "
-                                               "<b>Y</b>: %.4f" % (pos[0], pos[1]))
-            self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp;  <b>Dy</b>: "
-                                                   "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (dx, dy))
+            self.app.ui.rel_position_label.setText("<b>Dx</b>: {0:.4f}&nbsp;&nbsp;  <b>Dy</b>: "
+                                                   "{0:.4f}&nbsp;&nbsp;&nbsp;&nbsp;".format(dx, dy))
+
             # update utility geometry
+
             if len(self.points) == 1:
                 self.utility_geometry(pos=pos)
         except Exception as e:
+            log.debug("Measurement.on_mouse_move_meas() --> %s" % str(e))
             self.app.ui.position_label.setText("")
             self.app.ui.rel_position_label.setText("")
 
     def utility_geometry(self, pos):
         # first delete old shape
         self.delete_shape()
+
         # second draw the new shape of the utility geometry
-        self.meas_line = LineString([pos, self.points[0]])
-        self.sel_shapes.add(self.meas_line, color='black', update=True, layer=0, tolerance=None)
+        meas_line = LineString([pos, self.points[0]])
+
+        color = '#00000000'
+        self.sel_shapes.add(meas_line, color=color, update=True, layer=0, tolerance=None)
+
+        if self.app.is_legacy is True:
+            self.sel_shapes.redraw()
 
     def delete_shape(self):
         self.sel_shapes.clear()

+ 61 - 23
flatcamTools/ToolMove.py

@@ -44,7 +44,11 @@ class ToolMove(FlatCAMTool):
         self.old_coords = []
 
         # VisPy visuals
-        self.sel_shapes = ShapeCollection(parent=self.app.plotcanvas.view.scene, layers=1)
+        if self.app.is_legacy is False:
+            self.sel_shapes = ShapeCollection(parent=self.app.plotcanvas.view.scene, layers=1)
+        else:
+            from flatcamGUI.PlotCanvasLegacy import ShapeCollectionLegacy
+            self.sel_shapes = ShapeCollectionLegacy(obj=self, app=self.app, name="move")
 
         self.replot_signal[list].connect(self.replot)
 
@@ -62,10 +66,16 @@ class ToolMove(FlatCAMTool):
         if self.isVisible():
             self.setVisible(False)
 
-            self.app.plotcanvas.vis_disconnect('mouse_move', self.on_move)
-            self.app.plotcanvas.vis_disconnect('mouse_press', self.on_left_click)
-            self.app.plotcanvas.vis_disconnect('key_release', self.on_key_press)
-            self.app.plotcanvas.vis_connect('key_press', self.app.ui.keyPressEvent)
+            if self.app.is_legacy is False:
+                self.app.plotcanvas.graph_event_disconnect('mouse_move', self.on_move)
+                self.app.plotcanvas.graph_event_disconnect('mouse_press', self.on_left_click)
+                self.app.plotcanvas.graph_event_disconnect('key_release', self.on_key_press)
+                self.app.plotcanvas.graph_event_connect('key_press', self.app.ui.keyPressEvent)
+            else:
+                self.app.plotcanvas.graph_event_disconnect(self.mm)
+                self.app.plotcanvas.graph_event_disconnect(self.mp)
+                self.app.plotcanvas.graph_event_disconnect(self.kr)
+                self.app.kr = self.app.plotcanvas.graph_event_connect('key_press', self.app.ui.keyPressEvent)
 
             self.clicked_move = 0
 
@@ -95,9 +105,14 @@ class ToolMove(FlatCAMTool):
         # this is necessary because right mouse click and middle mouse click
         # are used for panning on the canvas
 
+        if self.app.is_legacy is False:
+            event_pos = event.pos
+        else:
+            event_pos = (event.xdata, event.ydata)
+
         if event.button == 1:
             if self.clicked_move == 0:
-                pos_canvas = self.app.plotcanvas.translate_coords(event.pos)
+                pos_canvas = self.app.plotcanvas.translate_coords(event_pos)
 
                 # if GRID is active we need to get the snapped positions
                 if self.app.grid_status() == True:
@@ -114,7 +129,7 @@ class ToolMove(FlatCAMTool):
 
             if self.clicked_move == 1:
                 try:
-                    pos_canvas = self.app.plotcanvas.translate_coords(event.pos)
+                    pos_canvas = self.app.plotcanvas.translate_coords(event_pos)
 
                     # delete the selection bounding box
                     self.delete_shape()
@@ -174,7 +189,8 @@ class ToolMove(FlatCAMTool):
                     self.toggle()
                     return
 
-                except TypeError:
+                except TypeError as e:
+                    log.debug("ToolMove.on_left_click() --> %s" % str(e))
                     self.app.inform.emit('[ERROR_NOTCL] %s' %
                                          _('ToolMove.on_left_click() --> Error when mouse left click.'))
                     return
@@ -191,7 +207,19 @@ class ToolMove(FlatCAMTool):
         self.app.worker_task.emit({'fcn': worker_task, 'params': []})
 
     def on_move(self, event):
-        pos_canvas = self.app.plotcanvas.translate_coords(event.pos)
+
+        if self.app.is_legacy is False:
+            event_pos = event.pos
+        else:
+            event_pos = (event.xdata, event.ydata)
+
+        try:
+            x = float(event_pos[0])
+            y = float(event_pos[1])
+        except TypeError:
+            return
+
+        pos_canvas = self.app.plotcanvas.translate_coords((x, y))
 
         # if GRID is active we need to get the snapped positions
         if self.app.grid_status() == True:
@@ -228,9 +256,9 @@ class ToolMove(FlatCAMTool):
             self.toggle()
         else:
             # if we have an object selected then we can safely activate the mouse events
-            self.app.plotcanvas.vis_connect('mouse_move', self.on_move)
-            self.app.plotcanvas.vis_connect('mouse_press', self.on_left_click)
-            self.app.plotcanvas.vis_connect('key_release', self.on_key_press)
+            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)
             # first get a bounding box to fit all
             for obj in obj_list:
                 xmin, ymin, xmax, ymax = obj.bounds()
@@ -249,8 +277,12 @@ class ToolMove(FlatCAMTool):
             p2 = (xmaximal, yminimal)
             p3 = (xmaximal, ymaximal)
             p4 = (xminimal, ymaximal)
+
             self.old_coords = [p1, p2, p3, p4]
-            self.draw_shape(self.old_coords)
+            self.draw_shape(Polygon(self.old_coords))
+
+            if self.app.is_legacy is True:
+                self.sel_shapes.redraw()
 
     def update_sel_bbox(self, pos):
         self.delete_shape()
@@ -259,24 +291,30 @@ class ToolMove(FlatCAMTool):
         pt2 = (self.old_coords[1][0] + pos[0], self.old_coords[1][1] + pos[1])
         pt3 = (self.old_coords[2][0] + pos[0], self.old_coords[2][1] + pos[1])
         pt4 = (self.old_coords[3][0] + pos[0], self.old_coords[3][1] + pos[1])
+        self.draw_shape(Polygon([pt1, pt2, pt3, pt4]))
 
-        self.draw_shape([pt1, pt2, pt3, pt4])
+        if self.app.is_legacy is True:
+            self.sel_shapes.redraw()
 
     def delete_shape(self):
         self.sel_shapes.clear()
         self.sel_shapes.redraw()
 
-    def draw_shape(self, coords):
-        self.sel_rect = Polygon(coords)
+    def draw_shape(self, shape):
+
         if self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().upper() == 'MM':
-            self.sel_rect = self.sel_rect.buffer(-0.1)
-            self.sel_rect = self.sel_rect.buffer(0.2)
+            proc_shape = shape.buffer(-0.1)
+            proc_shape = proc_shape.buffer(0.2)
         else:
-            self.sel_rect = self.sel_rect.buffer(-0.00393)
-            self.sel_rect = self.sel_rect.buffer(0.00787)
+            proc_shape = shape.buffer(-0.00393)
+            proc_shape = proc_shape.buffer(0.00787)
+
+        # face = Color('blue')
+        # face.alpha = 0.2
+
+        face = '#0000FFAF' + str(hex(int(0.2 * 255)))[2:]
+        outline = '#0000FFAF'
 
-        blue_t = Color('blue')
-        blue_t.alpha = 0.2
-        self.sel_shapes.add(self.sel_rect, color='blue', face_color=blue_t, update=True, layer=0, tolerance=None)
+        self.sel_shapes.add(proc_shape, color=outline, face_color=face, update=True, layer=0, tolerance=None)
 
 # end of file

+ 174 - 153
flatcamTools/ToolNonCopperClear.py

@@ -470,10 +470,21 @@ class NonCopperClear(FlatCAMTool, Gerber):
         self.bound_obj_name = ""
         self.bound_obj = None
 
+        self.ncc_dia_list = []
+        self.iso_dia_list = []
+        self.has_offset = None
+        self.o_name = None
+        self.overlap = None
+        self.connect = None
+        self.contour = None
+        self.rest = None
+
         self.first_click = False
         self.cursor_pos = None
         self.mouse_is_dragging = False
 
+        self.mm = None
+        self.mr = None
         # store here solid_geometry when there are tool with isolation job
         self.solid_geometry = []
 
@@ -1057,27 +1068,27 @@ class NonCopperClear(FlatCAMTool, Gerber):
         self.app.report_usage(_("on_paint_button_click"))
 
         try:
-            overlap = float(self.ncc_overlap_entry.get_value())
+            self.overlap = float(self.ncc_overlap_entry.get_value())
         except ValueError:
             # try to convert comma to decimal point. if it's still not working error message and return
             try:
-                overlap = float(self.ncc_overlap_entry.get_value().replace(',', '.'))
+                self.overlap = float(self.ncc_overlap_entry.get_value().replace(',', '.'))
             except ValueError:
                 self.app.inform.emit('[ERROR_NOTCL]  %s' % _("Wrong value format entered, "
                                                              "use a number."))
                 return
 
-        if overlap >= 1 or overlap < 0:
+        if self.overlap >= 1 or self.overlap < 0:
             self.app.inform.emit('[ERROR_NOTCL] %s' % _("Overlap value must be between "
                                                         "0 (inclusive) and 1 (exclusive), "))
             return
 
-        connect = self.ncc_connect_cb.get_value()
-        contour = self.ncc_contour_cb.get_value()
+        self.connect = self.ncc_connect_cb.get_value()
+        self.contour = self.ncc_contour_cb.get_value()
 
-        has_offset = self.ncc_choice_offset_cb.isChecked()
+        self.has_offset = self.ncc_choice_offset_cb.isChecked()
 
-        rest = self.ncc_rest_cb.get_value()
+        self.rest = self.ncc_rest_cb.get_value()
 
         self.obj_name = self.object_combo.currentText()
         # Get source object.
@@ -1092,34 +1103,34 @@ class NonCopperClear(FlatCAMTool, Gerber):
             return
 
         # use the selected tools in the tool table; get diameters for non-copper clear
-        iso_dia_list = list()
+        self.iso_dia_list = list()
         # use the selected tools in the tool table; get diameters for non-copper clear
-        ncc_dia_list = list()
+        self.ncc_dia_list = list()
         if self.tools_table.selectedItems():
             for x in self.tools_table.selectedItems():
                 try:
-                    tooldia = float(self.tools_table.item(x.row(), 1).text())
+                    self.tooldia = float(self.tools_table.item(x.row(), 1).text())
                 except ValueError:
                     # try to convert comma to decimal point. if it's still not working error message and return
                     try:
-                        tooldia = float(self.tools_table.item(x.row(), 1).text().replace(',', '.'))
+                        self.tooldia = float(self.tools_table.item(x.row(), 1).text().replace(',', '.'))
                     except ValueError:
                         self.app.inform.emit('[ERROR_NOTCL] %s' % _("Wrong Tool Dia value format entered, "
                                                                     "use a number."))
                         continue
 
                 if self.tools_table.cellWidget(x.row(), 4).currentText() == 'iso_op':
-                    iso_dia_list.append(tooldia)
+                    self.iso_dia_list.append(self.tooldia)
                 else:
-                    ncc_dia_list.append(tooldia)
+                    self.ncc_dia_list.append(self.tooldia)
         else:
             self.app.inform.emit('[ERROR_NOTCL] %s' % _("No selected tools in Tool Table."))
             return
 
-        o_name = '%s_ncc' % self.obj_name
+        self.o_name = '%s_ncc' % self.obj_name
 
-        select_method = self.reference_radio.get_value()
-        if select_method == 'itself':
+        self.select_method = self.reference_radio.get_value()
+        if self.select_method == 'itself':
             self.bound_obj_name = self.object_combo.currentText()
             # Get source object.
             try:
@@ -1129,138 +1140,29 @@ class NonCopperClear(FlatCAMTool, Gerber):
                 return "Could not retrieve object: %s" % self.obj_name
 
             self.clear_copper(ncc_obj=self.ncc_obj,
-                              ncctooldia=ncc_dia_list,
-                              isotooldia=iso_dia_list,
-                              has_offset=has_offset,
-                              outname=o_name,
-                              overlap=overlap,
-                              connect=connect,
-                              contour=contour,
-                              rest=rest)
-        elif select_method == 'area':
+                              ncctooldia=self.ncc_dia_list,
+                              isotooldia=self.iso_dia_list,
+                              has_offset=self.has_offset,
+                              outname=self.o_name,
+                              overlap=self.overlap,
+                              connect=self.connect,
+                              contour=self.contour,
+                              rest=self.rest)
+        elif self.select_method == 'area':
             self.app.inform.emit('[WARNING_NOTCL] %s' % _("Click the start point of the area."))
 
-            # use the first tool in the tool table; get the diameter
-            # tooldia = float('%.4f' % float(self.tools_table.item(0, 1).text()))
-
-            # To be called after clicking on the plot.
-            def on_mouse_release(event):
-                # do clear area only for left mouse clicks
-                if event.button == 1:
-                    if self.first_click is False:
-                        self.first_click = True
-                        self.app.inform.emit('[WARNING_NOTCL] %s' % _("Click the end point of the paint area."))
-
-                        self.cursor_pos = self.app.plotcanvas.translate_coords(event.pos)
-                        if self.app.grid_status() == True:
-                            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()
-
-                        curr_pos = self.app.plotcanvas.translate_coords(event.pos)
-                        if self.app.grid_status() == True:
-                            curr_pos = self.app.geo_editor.snap(curr_pos[0], curr_pos[1])
-
-                        x0, y0 = self.cursor_pos[0], self.cursor_pos[1]
-                        x1, y1 = curr_pos[0], curr_pos[1]
-                        pt1 = (x0, y0)
-                        pt2 = (x1, y0)
-                        pt3 = (x1, y1)
-                        pt4 = (x0, y1)
-                        self.sel_rect.append(Polygon([pt1, pt2, pt3, pt4]))
-                        self.first_click = False
-                        return
-
-                        # modifiers = QtWidgets.QApplication.keyboardModifiers()
-                        #
-                        # if modifiers == QtCore.Qt.ShiftModifier:
-                        #     mod_key = 'Shift'
-                        # elif modifiers == QtCore.Qt.ControlModifier:
-                        #     mod_key = 'Control'
-                        # else:
-                        #     mod_key = None
-                        #
-                        # if mod_key == self.app.defaults["global_mselect_key"]:
-                        #     self.first_click = False
-                        #     return
-                        #
-                        # self.sel_rect = cascaded_union(self.sel_rect)
-                        # self.clear_copper(ncc_obj=self.ncc_obj,
-                        #                   sel_obj=self.bound_obj,
-                        #                   ncctooldia=ncc_dia_list,
-                        #                   isotooldia=iso_dia_list,
-                        #                   has_offset=has_offset,
-                        #                   outname=o_name,
-                        #                   overlap=overlap,
-                        #                   connect=connect,
-                        #                   contour=contour,
-                        #                   rest=rest)
-                        #
-                        # self.app.plotcanvas.vis_disconnect('mouse_release', on_mouse_release)
-                        # self.app.plotcanvas.vis_disconnect('mouse_move', on_mouse_move)
-                        #
-                        # self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
-                        # self.app.plotcanvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot)
-                        # self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
-                elif event.button == 2 and self.mouse_is_dragging == False:
-                    self.first_click = False
-
-                    self.app.plotcanvas.vis_disconnect('mouse_release', on_mouse_release)
-                    self.app.plotcanvas.vis_disconnect('mouse_move', on_mouse_move)
-
-                    self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
-                    self.app.plotcanvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot)
-                    self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
-
-                    if len(self.sel_rect) == 0:
-                        return
-
-                    self.sel_rect = cascaded_union(self.sel_rect)
-                    self.clear_copper(ncc_obj=self.ncc_obj,
-                                      sel_obj=self.bound_obj,
-                                      ncctooldia=ncc_dia_list,
-                                      isotooldia=iso_dia_list,
-                                      has_offset=has_offset,
-                                      outname=o_name,
-                                      overlap=overlap,
-                                      connect=connect,
-                                      contour=contour,
-                                      rest=rest)
-
-            # called on mouse move
-            def on_mouse_move(event):
-                curr_pos = self.app.plotcanvas.translate_coords(event.pos)
-                self.app.app_cursor.enabled = False
-
-                # detect mouse dragging motion
-                if event.is_dragging is True:
-                    self.mouse_is_dragging = True
-                else:
-                    self.mouse_is_dragging = False
+            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)
 
-                # update the cursor position
-                if self.app.grid_status() == True:
-                    self.app.app_cursor.enabled = True
-                    # 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='black', size=20)
-
-                # draw the utility geometry
-                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]),
-                                                         face_alpha=0.0)
-
-            self.app.plotcanvas.vis_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
-            self.app.plotcanvas.vis_disconnect('mouse_move', self.app.on_mouse_move_over_plot)
-            self.app.plotcanvas.vis_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
-
-            self.app.plotcanvas.vis_connect('mouse_release', on_mouse_release)
-            self.app.plotcanvas.vis_connect('mouse_move', on_mouse_move)
-        elif select_method == 'box':
+            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)
+        elif self.select_method == 'box':
             self.bound_obj_name = self.box_combo.currentText()
             # Get source object.
             try:
@@ -1271,14 +1173,133 @@ class NonCopperClear(FlatCAMTool, Gerber):
 
             self.clear_copper(ncc_obj=self.ncc_obj,
                               sel_obj=self.bound_obj,
-                              ncctooldia=ncc_dia_list,
-                              isotooldia=iso_dia_list,
-                              has_offset=has_offset,
-                              outname=o_name,
-                              overlap=overlap,
-                              connect=connect,
-                              contour=contour,
-                              rest=rest)
+                              ncctooldia=self.ncc_dia_list,
+                              isotooldia=self.iso_dia_list,
+                              has_offset=self.has_offset,
+                              outname=self.o_name,
+                              overlap=self.overlap,
+                              connect=self.connect,
+                              contour=self.contour,
+                              rest=self.rest)
+
+    # 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)
+            event_is_dragging = self.app.plotcanvas.is_dragging
+            right_button = 3
+
+        event_pos = self.app.plotcanvas.translate_coords(event_pos)
+
+        # do clear area only for left mouse clicks
+        if event.button == 1:
+            if self.first_click is False:
+                self.first_click = True
+                self.app.inform.emit('[WARNING_NOTCL] %s' % _("Click the end point of the paint area."))
+
+                self.cursor_pos = self.app.plotcanvas.translate_coords(event_pos)
+                if self.app.grid_status() == True:
+                    self.cursor_pos = self.app.geo_editor.snap(event_pos[0], event_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()
+
+                if self.app.grid_status() == True:
+                    curr_pos = self.app.geo_editor.snap(event_pos[0], event_pos[1])
+                else:
+                    curr_pos = (event_pos[0], event_pos[1])
+
+                x0, y0 = self.cursor_pos[0], self.cursor_pos[1]
+                x1, y1 = curr_pos[0], curr_pos[1]
+                pt1 = (x0, y0)
+                pt2 = (x1, y0)
+                pt3 = (x1, y1)
+                pt4 = (x0, y1)
+
+                self.sel_rect.append(Polygon([pt1, pt2, pt3, pt4]))
+                self.first_click = False
+                return
+
+        elif event.button == right_button and self.mouse_is_dragging == False:
+            self.first_click = False
+
+            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)
+            else:
+                self.app.plotcanvas.graph_event_disconnect(self.mr)
+                self.app.plotcanvas.graph_event_disconnect(self.mm)
+
+            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)
+
+            if len(self.sel_rect) == 0:
+                return
+
+            self.sel_rect = cascaded_union(self.sel_rect)
+
+            self.clear_copper(ncc_obj=self.ncc_obj,
+                              sel_obj=self.bound_obj,
+                              ncctooldia=self.ncc_dia_list,
+                              isotooldia=self.iso_dia_list,
+                              has_offset=self.has_offset,
+                              outname=self.o_name,
+                              overlap=self.overlap,
+                              connect=self.connect,
+                              contour=self.contour,
+                              rest=self.rest)
+
+    # called on mouse move
+    def on_mouse_move(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)
+            event_is_dragging = self.app.plotcanvas.is_dragging
+            right_button = 3
+
+        curr_pos = self.app.plotcanvas.translate_coords(event_pos)
+
+        # detect mouse dragging motion
+        if event_is_dragging is True:
+            self.mouse_is_dragging = True
+        else:
+            self.mouse_is_dragging = False
+
+        # update the cursor position
+        if self.app.grid_status() == True:
+            # Update cursor
+            curr_pos = self.app.geo_editor.snap(curr_pos[0], curr_pos[1])
+            if self.app.is_legacy is False:
+                self.app.app_cursor.set_data(np.asarray([(curr_pos[0], curr_pos[1])]),
+                                             symbol='++', edge_color='black', size=20)
+
+        # update the positions on status bar
+        self.app.ui.position_label.setText("&nbsp;&nbsp;&nbsp;&nbsp;<b>X</b>: %.4f&nbsp;&nbsp;   "
+                                           "<b>Y</b>: %.4f" % (curr_pos[0], curr_pos[1]))
+        if self.cursor_pos is None:
+            self.cursor_pos = (0, 0)
+
+        dx = curr_pos[0] - float(self.cursor_pos[0])
+        dy = curr_pos[1] - float(self.cursor_pos[1])
+        self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp;  <b>Dy</b>: "
+                                               "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (dx, dy))
+
+        # draw the utility geometry
+        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]))
 
     def clear_copper(self, ncc_obj,
                      sel_obj=None,

+ 191 - 149
flatcamTools/ToolPaint.py

@@ -357,6 +357,13 @@ class ToolPaint(FlatCAMTool, Gerber):
         self.bound_obj_name = ""
         self.bound_obj = None
 
+        self.tooldia_list = []
+        self.sel_rect = None
+        self.o_name = None
+        self.overlap = None
+        self.connect = None
+        self.contour = None
+
         self.units = ''
         self.paint_tools = {}
         self.tooluid = 0
@@ -364,6 +371,9 @@ class ToolPaint(FlatCAMTool, Gerber):
         self.cursor_pos = None
         self.mouse_is_dragging = False
 
+        self.mm = None
+        self.mp = None
+
         self.sel_rect = []
 
         # store here the default data for Geometry Data
@@ -916,17 +926,17 @@ class ToolPaint(FlatCAMTool, Gerber):
         self.app.inform.emit(_("Paint Tool. Reading parameters."))
 
         try:
-            overlap = float(self.paintoverlap_entry.get_value())
+            self.overlap = float(self.paintoverlap_entry.get_value())
         except ValueError:
             # try to convert comma to decimal point. if it's still not working error message and return
             try:
-                overlap = float(self.paintoverlap_entry.get_value().replace(',', '.'))
+                self.overlap = float(self.paintoverlap_entry.get_value().replace(',', '.'))
             except ValueError:
                 self.app.inform.emit('[ERROR_NOTCL] %s' %
                                      _("Wrong value format entered, use a number."))
                 return
 
-        if overlap >= 1 or overlap < 0:
+        if self.overlap >= 1 or self.overlap < 0:
             self.app.inform.emit('[ERROR_NOTCL] %s' %
                                  _("Overlap value must be between 0 (inclusive) and 1 (exclusive)"))
             return
@@ -934,9 +944,9 @@ class ToolPaint(FlatCAMTool, Gerber):
         self.app.inform.emit('[WARNING_NOTCL] %s' %
                              _("Click inside the desired polygon."))
 
-        connect = self.pathconnect_cb.get_value()
-        contour = self.paintcontour_cb.get_value()
-        select_method = self.selectmethod_combo.get_value()
+        self.connect = self.pathconnect_cb.get_value()
+        self.contour = self.paintcontour_cb.get_value()
+        self.select_method = self.selectmethod_combo.get_value()
 
         self.obj_name = self.obj_combo.currentText()
 
@@ -966,34 +976,34 @@ class ToolPaint(FlatCAMTool, Gerber):
         o_name = '%s_multitool_paint' % self.obj_name
 
         # use the selected tools in the tool table; get diameters
-        tooldia_list = list()
+        self.tooldia_list = list()
         if self.tools_table.selectedItems():
             for x in self.tools_table.selectedItems():
                 try:
-                    tooldia = float(self.tools_table.item(x.row(), 1).text())
+                    self.tooldia = float(self.tools_table.item(x.row(), 1).text())
                 except ValueError:
                     # try to convert comma to decimal point. if it's still not working error message and return
                     try:
-                        tooldia = float(self.tools_table.item(x.row(), 1).text().replace(',', '.'))
+                        self.tooldia = float(self.tools_table.item(x.row(), 1).text().replace(',', '.'))
                     except ValueError:
                         self.app.inform.emit('[ERROR_NOTCL] %s' %
                                              _("Wrong value format entered, use a number."))
                         continue
-                tooldia_list.append(tooldia)
+                self.tooldia_list.append(self.tooldia)
         else:
             self.app.inform.emit('[ERROR_NOTCL] %s' %
                                  _("No selected tools in Tool Table."))
             return
 
-        if select_method == "all":
+        if self.select_method == "all":
             self.paint_poly_all(self.paint_obj,
-                                tooldia=tooldia_list,
-                                outname=o_name,
-                                overlap=overlap,
-                                connect=connect,
-                                contour=contour)
+                                tooldia=self.tooldia_list,
+                                outname=self.o_name,
+                                overlap=self.overlap,
+                                connect=self.connect,
+                                contour=self.contour)
 
-        elif select_method == "single":
+        elif self.select_method == "single":
             self.app.inform.emit('[WARNING_NOTCL] %s' %
                                  _("Click inside the desired polygon."))
 
@@ -1005,7 +1015,10 @@ class ToolPaint(FlatCAMTool, Gerber):
                 # do paint single only for left mouse clicks
                 if event.button == 1:
                     self.app.inform.emit(_("Painting polygon..."))
-                    self.app.plotcanvas.vis_disconnect('mouse_press', doit)
+                    if self.app.is_legacy:
+                        self.app.plotcanvas.graph_event_disconnect('mouse_press', doit)
+                    else:
+                        self.app.plotcanvas.graph_event_disconnect(self.mp)
 
                     pos = self.app.plotcanvas.translate_coords(event.pos)
                     if self.app.grid_status() == True:
@@ -1013,137 +1026,40 @@ class ToolPaint(FlatCAMTool, Gerber):
 
                     self.paint_poly(self.paint_obj,
                                     inside_pt=[pos[0], pos[1]],
-                                    tooldia=tooldia_list,
-                                    overlap=overlap,
-                                    connect=connect,
-                                    contour=contour)
-                    self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
-                    self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
-
-            self.app.plotcanvas.vis_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
-            self.app.plotcanvas.vis_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
-            self.app.plotcanvas.vis_connect('mouse_press', doit)
-
-        elif select_method == "area":
+                                    tooldia=self.tooldia_list,
+                                    overlap=self.overlap,
+                                    connect=self.connect,
+                                    contour=self.contour)
+                    self.app.mp = self.app.plotcanvas.graph_event_connect('mouse_press',
+                                                                          self.app.on_mouse_click_over_plot)
+                    self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release',
+                                                                          self.app.on_mouse_click_release_over_plot)
+
+            if self.app.is_legacy is False:
+                self.app.plotcanvas.graph_event_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
+                self.app.plotcanvas.graph_event_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
+            else:
+                self.app.plotcanvas.graph_event_disconnect(self.app.mr)
+                self.app.plotcanvas.graph_event_disconnect(self.app.mp)
+            self.mp = self.app.plotcanvas.graph_event_connect('mouse_press', doit)
+
+        elif self.select_method == "area":
             self.app.inform.emit('[WARNING_NOTCL] %s' %
                                  _("Click the start point of the paint area."))
 
-            # use the first tool in the tool table; get the diameter
-            # tooldia = float('%.4f' % float(self.tools_table.item(0, 1).text()))
-
-            # To be called after clicking on the plot.
-            def on_mouse_release(event):
-                # do paint single only for left mouse clicks
-                if event.button == 1:
-                    if not self.first_click:
-                        self.first_click = True
-                        self.app.inform.emit('[WARNING_NOTCL] %s' %
-                                             _("Click the end point of the paint area."))
-
-                        self.cursor_pos = self.app.plotcanvas.translate_coords(event.pos)
-                        if self.app.grid_status() == True:
-                            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()
-
-                        curr_pos = self.app.plotcanvas.translate_coords(event.pos)
-                        if self.app.grid_status() == True:
-                            curr_pos = self.app.geo_editor.snap(curr_pos[0], curr_pos[1])
-
-                        x0, y0 = self.cursor_pos[0], self.cursor_pos[1]
-                        x1, y1 = curr_pos[0], curr_pos[1]
-                        pt1 = (x0, y0)
-                        pt2 = (x1, y0)
-                        pt3 = (x1, y1)
-                        pt4 = (x0, y1)
-                        self.sel_rect.append(Polygon([pt1, pt2, pt3, pt4]))
-                        self.first_click = False
-                        return
-                        # modifiers = QtWidgets.QApplication.keyboardModifiers()
-                        #
-                        # if modifiers == QtCore.Qt.ShiftModifier:
-                        #     mod_key = 'Shift'
-                        # elif modifiers == QtCore.Qt.ControlModifier:
-                        #     mod_key = 'Control'
-                        # else:
-                        #     mod_key = None
-                        #
-                        # if mod_key == self.app.defaults["global_mselect_key"]:
-                        #     self.first_click = False
-                        #     return
-                        #
-                        # self.sel_rect = cascaded_union(self.sel_rect)
-                        # self.paint_poly_area(obj=self.paint_obj,
-                        #                      tooldia=tooldia_list,
-                        #                      sel_obj= self.sel_rect,
-                        #                      outname=o_name,
-                        #                      overlap=overlap,
-                        #                      connect=connect,
-                        #                      contour=contour)
-                        #
-                        # self.app.plotcanvas.vis_disconnect('mouse_release', on_mouse_release)
-                        # self.app.plotcanvas.vis_disconnect('mouse_move', on_mouse_move)
-                        #
-                        # self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
-                        # self.app.plotcanvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot)
-                        # self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
-                elif event.button == 2 and self.mouse_is_dragging is False:
-                    self.first_click = False
-
-                    self.app.plotcanvas.vis_disconnect('mouse_release', on_mouse_release)
-                    self.app.plotcanvas.vis_disconnect('mouse_move', on_mouse_move)
-
-                    self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
-                    self.app.plotcanvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot)
-                    self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
-
-                    if len(self.sel_rect) == 0:
-                        return
-
-                    self.sel_rect = cascaded_union(self.sel_rect)
-                    self.paint_poly_area(obj=self.paint_obj,
-                                         tooldia=tooldia_list,
-                                         sel_obj=self.sel_rect,
-                                         outname=o_name,
-                                         overlap=overlap,
-                                         connect=connect,
-                                         contour=contour)
-
-            # called on mouse move
-            def on_mouse_move(event):
-                curr_pos = self.app.plotcanvas.translate_coords(event.pos)
-                self.app.app_cursor.enabled = False
-
-                # detect mouse dragging motion
-                if event.is_dragging is True:
-                    self.mouse_is_dragging = True
-                else:
-                    self.mouse_is_dragging = False
-
-                # update the cursor position
-                if self.app.grid_status() == True:
-                    self.app.app_cursor.enabled = True
-                    # 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='black', size=20)
-
-                # draw the utility geometry
-                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]),
-                                                         face_alpha=0.0)
-
-            self.app.plotcanvas.vis_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
-            self.app.plotcanvas.vis_disconnect('mouse_move', self.app.on_mouse_move_over_plot)
-            self.app.plotcanvas.vis_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
+            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.app.plotcanvas.vis_connect('mouse_release', on_mouse_release)
-            self.app.plotcanvas.vis_connect('mouse_move', on_mouse_move)
+            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)
 
-        elif select_method == 'ref':
+        elif self.select_method == 'ref':
             self.bound_obj_name = self.box_combo.currentText()
             # Get source object.
             try:
@@ -1156,11 +1072,137 @@ class ToolPaint(FlatCAMTool, Gerber):
 
             self.paint_poly_ref(obj=self.paint_obj,
                                 sel_obj=self.bound_obj,
-                                tooldia=tooldia_list,
-                                overlap=overlap,
-                                outname=o_name,
-                                connect=connect,
-                                contour=contour)
+                                tooldia=self.tooldia_list,
+                                overlap=self.overlap,
+                                outname=self.o_name,
+                                connect=self.connect,
+                                contour=self.contour)
+
+    # 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)
+            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
+
+        event_pos = (x, y)
+
+        # do paint single only for left mouse clicks
+        if event.button == 1:
+            if not self.first_click:
+                self.first_click = True
+                self.app.inform.emit('[WARNING_NOTCL] %s' %
+                                     _("Click the end point of the paint area."))
+
+                self.cursor_pos = self.app.plotcanvas.translate_coords(event_pos)
+                if self.app.grid_status() == True:
+                    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()
+
+                curr_pos = self.app.plotcanvas.translate_coords(event_pos)
+                if self.app.grid_status() == True:
+                    curr_pos = self.app.geo_editor.snap(curr_pos[0], curr_pos[1])
+
+                x0, y0 = self.cursor_pos[0], self.cursor_pos[1]
+                x1, y1 = curr_pos[0], curr_pos[1]
+                pt1 = (x0, y0)
+                pt2 = (x1, y0)
+                pt3 = (x1, y1)
+                pt4 = (x0, y1)
+                self.sel_rect.append(Polygon([pt1, pt2, pt3, pt4]))
+                self.first_click = False
+                return
+
+        elif event.button == right_button and self.mouse_is_dragging is False:
+            self.first_click = False
+
+            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)
+            else:
+                self.app.plotcanvas.graph_event_disconnect(self.mr)
+                self.app.plotcanvas.graph_event_disconnect(self.mm)
+
+            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)
+
+            if len(self.sel_rect) == 0:
+                return
+
+            self.sel_rect = cascaded_union(self.sel_rect)
+            self.paint_poly_area(obj=self.paint_obj,
+                                 tooldia=self.tooldia_list,
+                                 sel_obj=self.sel_rect,
+                                 outname=self.o_name,
+                                 overlap=self.overlap,
+                                 connect=self.connect,
+                                 contour=self.contour)
+
+    # called on mouse move
+    def on_mouse_move(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)
+            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() == True:
+            # Update cursor
+            curr_pos = self.app.geo_editor.snap(curr_pos[0], curr_pos[1])
+            if self.app.is_legacy is False:
+                self.app.app_cursor.set_data(np.asarray([(curr_pos[0], curr_pos[1])]),
+                                             symbol='++', edge_color='black', size=20)
+
+        # update the positions on status bar
+        self.app.ui.position_label.setText("&nbsp;&nbsp;&nbsp;&nbsp;<b>X</b>: %.4f&nbsp;&nbsp;   "
+                                           "<b>Y</b>: %.4f" % (curr_pos[0], curr_pos[1]))
+        if self.cursor_pos is None:
+            self.cursor_pos = (0, 0)
+
+        dx = curr_pos[0] - float(self.cursor_pos[0])
+        dy = curr_pos[1] - float(self.cursor_pos[1])
+        self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp;  <b>Dy</b>: "
+                                               "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (dx, dy))
+
+        # draw the utility geometry
+        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]))
 
     def paint_poly(self, obj,
                    inside_pt=None,