Jelajahi Sumber

- removed the labels in status bar that display X,Y positions and replaced it with a HUD display on canvas (combo key SHIFT+H) will toggle the display of the HUD
- made the HUD work in Legacy2D mode
- fixed situation when the mouse cursor is outside of the canvas and no therefore returning None values

Marius Stanciu 5 tahun lalu
induk
melakukan
46367c433f

+ 6 - 0
CHANGELOG.md

@@ -7,6 +7,12 @@ CHANGELOG for FlatCAM beta
 
 =================================================
 
+11.05.2020
+
+- removed the labels in status bar that display X,Y positions and replaced it with a HUD display on canvas (combo key SHIFT+H) will toggle the display of the HUD
+- made the HUD work in Legacy2D mode
+- fixed situation when the mouse cursor is outside of the canvas and no therefore returning None values
+
 10.05.2020
 
 - fixed the problem with using comma as decimal separator in Grid Snap fields

+ 43 - 22
FlatCAMApp.py

@@ -285,6 +285,8 @@ class App(QtCore.QObject):
         :rtype: App
         """
 
+        super().__init__()
+
         App.log.info("FlatCAM Starting...")
 
         self.main_thread = QtWidgets.QApplication.instance().thread()
@@ -504,8 +506,6 @@ class App(QtCore.QObject):
         self.FC_light_blue = '#a5a5ffbf'
         self.FC_dark_blue = '#0000ffbf'
 
-        QtCore.QObject.__init__(self)
-
         self.ui = FlatCAMGUI(self)
 
         theme_settings = QtCore.QSettings("Open Source", "FlatCAM")
@@ -5378,14 +5378,20 @@ class App(QtCore.QObject):
                                      edge_width=self.defaults["global_cursor_width"],
                                      size=self.defaults["global_cursor_size"])
 
-        # Set the position label
-        self.ui.position_label.setText("&nbsp;&nbsp;&nbsp;&nbsp;<b>X</b>: %.4f&nbsp;&nbsp;   "
-                                       "<b>Y</b>: %.4f" % (location[0], location[1]))
         # Set the relative position label
         dx = location[0] - float(self.rel_point1[0])
         dy = location[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.ui.position_label.setText("&nbsp;&nbsp;&nbsp;&nbsp;<b>X</b>: %.4f&nbsp;&nbsp;   "
+        #                                "<b>Y</b>: %.4f" % (location[0], location[1]))
+        # # Set the position label
+        #
+        # self.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp;  <b>Dy</b>: "
+        #                                    "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (dx, dy))
+
+        units = self.defaults["units"].lower()
+        self.plotcanvas.text_hud.text = \
+            'Dx:\t{:<.4f} [{:s}]\nDy:\t{:<.4f} [{:s}]\nX:  \t{:<.4f} [{:s}]\nY:  \t{:<.4f} [{:s}]'.format(
+                dx, units, dy, units, location[0], units, location[1], units)
 
         self.inform.emit('[success] %s' % _("Done."))
         return location
@@ -5527,14 +5533,19 @@ class App(QtCore.QObject):
                                      edge_width=self.defaults["global_cursor_width"],
                                      size=self.defaults["global_cursor_size"])
 
-        # Set the position label
-        self.ui.position_label.setText("&nbsp;&nbsp;&nbsp;&nbsp;<b>X</b>: %.4f&nbsp;&nbsp;   "
-                                       "<b>Y</b>: %.4f" % (location[0], location[1]))
         # Set the relative position label
         self.dx = location[0] - float(self.rel_point1[0])
         self.dy = location[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;" % (self.dx, self.dy))
+        # Set the position label
+        # self.ui.position_label.setText("&nbsp;&nbsp;&nbsp;&nbsp;<b>X</b>: %.4f&nbsp;&nbsp;   "
+        #                                "<b>Y</b>: %.4f" % (location[0], location[1]))
+        # self.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp;  <b>Dy</b>: "
+        #                                    "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (self.dx, self.dy))
+
+        units = self.defaults["units"].lower()
+        self.plotcanvas.text_hud.text = \
+            'Dx:\t{:<.4f} [{:s}]\nDy:\t{:<.4f} [{:s}]\nX:  \t{:<.4f} [{:s}]\nY:  \t{:<.4f} [{:s}]'.format(
+                self.dx, units, self.dy, units, location[0], units, location[1], units)
 
         self.inform.emit('[success] %s' % _("Done."))
         return location
@@ -5843,8 +5854,8 @@ class App(QtCore.QObject):
         self.ui.plot_tab_area.addTab(self.ui.preferences_tab, _("Preferences"))
 
         # delete the absolute and relative position and messages in the infobar
-        self.ui.position_label.setText("")
-        self.ui.rel_position_label.setText("")
+        # self.ui.position_label.setText("")
+        # self.ui.rel_position_label.setText("")
 
         # Switch plot_area to preferences page
         self.ui.plot_tab_area.setCurrentWidget(self.ui.preferences_tab)
@@ -6738,6 +6749,9 @@ class App(QtCore.QObject):
             try:  # May fail in case mouse not within axes
                 pos_canvas = self.plotcanvas.translate_coords(event_pos)
 
+                if pos_canvas[0] is None or pos_canvas[1] is None:
+                    return
+
                 if self.grid_status():
                     pos = self.geo_editor.snap(pos_canvas[0], pos_canvas[1])
 
@@ -6749,13 +6763,19 @@ class App(QtCore.QObject):
                 else:
                     pos = (pos_canvas[0], pos_canvas[1])
 
-                self.ui.position_label.setText("&nbsp;&nbsp;&nbsp;&nbsp;<b>X</b>: %.4f&nbsp;&nbsp;   "
-                                               "<b>Y</b>: %.4f" % (pos[0], pos[1]))
-
                 self.dx = pos[0] - float(self.rel_point1[0])
                 self.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;" % (self.dx, self.dy))
+
+                # self.ui.position_label.setText("&nbsp;&nbsp;&nbsp;&nbsp;<b>X</b>: %.4f&nbsp;&nbsp;   "
+                #                                "<b>Y</b>: %.4f" % (pos[0], pos[1]))
+                # self.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp;  <b>Dy</b>: "
+                #                                    "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (self.dx, self.dy))
+
+                units = self.defaults["units"].lower()
+                self.plotcanvas.text_hud.text = \
+                    'Dx:\t{:<.4f} [{:s}]\nDy:\t{:<.4f} [{:s}]\nX:  \t{:<.4f} [{:s}]\nY:  \t{:<.4f} [{:s}]'.format(
+                        self.dx, units, self.dy, units, pos[0], units, pos[1], units)
+
                 self.mouse = [pos[0], pos[1]]
 
                 # if the mouse is moved and the LMB is clicked then the action is a selection
@@ -6804,9 +6824,10 @@ class App(QtCore.QObject):
                             # In this case poly_obj creation (see above) will fail
                             pass
 
-            except Exception:
-                self.ui.position_label.setText("")
-                self.ui.rel_position_label.setText("")
+            except Exception as e:
+                log.debug("App.on_mouse_move_over_plot() - rel_point1 is not None -> %s" % str(e))
+                # self.ui.position_label.setText("")
+                # self.ui.rel_position_label.setText("")
                 self.mouse = None
 
     def on_mouse_click_release_over_plot(self, event):

+ 9 - 4
FlatCAMCommon.py

@@ -466,15 +466,20 @@ class ExclusionAreas(QtCore.QObject):
                                          size=self.app.defaults["global_cursor_size"])
 
         # 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)
 
         self.app.dx = curr_pos[0] - float(self.cursor_pos[0])
         self.app.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;" % (self.app.dx, self.app.dy))
+        # 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]))
+        # self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp;  <b>Dy</b>: "
+        #                                        "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (self.app.dx, self.app.dy))
+
+        units = self.app.defaults["units"].lower()
+        self.app.plotcanvas.text_hud.text = \
+            'Dx:\t{:<.4f} [{:s}]\nDy:\t{:<.4f} [{:s}]\nX:  \t{:<.4f} [{:s}]\nY:  \t{:<.4f} [{:s}]'.format(
+                self.app.dx, units, self.app.dy, units, curr_pos[0], units, curr_pos[1], units)
 
         if self.obj_type == 'excellon':
             color = "#FF7400"

+ 195 - 0
Utils/vispy_example.py

@@ -0,0 +1,195 @@
+from PyQt5.QtGui import QPalette
+from PyQt5 import QtCore, QtWidgets
+
+import vispy.scene as scene
+from vispy.scene.visuals import Rectangle, Text
+from vispy.color import Color
+
+import sys
+
+
+class VisPyCanvas(scene.SceneCanvas):
+
+    def __init__(self, config=None):
+        super().__init__(config=config, keys=None)
+
+        self.unfreeze()
+        
+        # Colors used by the Scene
+        theme_color = Color('#FFFFFF')
+        tick_color = Color('#000000')
+        back_color = str(QPalette().color(QPalette.Window).name())
+        
+        # Central Widget Colors
+        self.central_widget.bgcolor = back_color
+        self.central_widget.border_color = back_color
+
+        self.grid_widget = self.central_widget.add_grid(margin=10)
+        self.grid_widget.spacing = 0
+        
+        # TOP Padding
+        top_padding = self.grid_widget.add_widget(row=0, col=0, col_span=2)
+        top_padding.height_max = 0
+
+        # RIGHT Padding
+        right_padding = self.grid_widget.add_widget(row=0, col=2, row_span=2)
+        right_padding.width_max = 0
+
+        # X Axis
+        self.xaxis = scene.AxisWidget(
+            orientation='bottom', axis_color=tick_color, text_color=tick_color,
+            font_size=8, axis_width=1,
+            anchors=['center', 'bottom']
+        )
+        self.xaxis.height_max = 30
+        self.grid_widget.add_widget(self.xaxis, row=2, col=1)
+
+        # Y Axis
+        self.yaxis = scene.AxisWidget(
+            orientation='left', axis_color=tick_color, text_color=tick_color, 
+            font_size=8, axis_width=1
+        )
+        self.yaxis.width_max = 55
+        self.grid_widget.add_widget(self.yaxis, row=1, col=0)
+
+        # View & Camera
+        self.view = self.grid_widget.add_view(row=1, col=1, border_color=tick_color,
+                                              bgcolor=theme_color)
+        self.view.camera = scene.PanZoomCamera(aspect=1, rect=(-25, -25, 150, 150))
+
+        self.xaxis.link_view(self.view)
+        self.yaxis.link_view(self.view)
+
+        self.grid = scene.GridLines(parent=self.view.scene, color='dimgray')
+        self.grid.set_gl_state(depth_test=False)
+
+        self.rect = Rectangle(center=(65,30), color=Color('#0000FF10'), border_color=Color('#0000FF10'),
+                              width=120, height=50, radius=[5, 5, 5, 5], parent=self.view)
+        self.rect.set_gl_state(depth_test=False)
+
+        self.text = Text('', parent=self.view, color='black', pos=(5, 30), method='gpu', anchor_x='left')
+        self.text.font_size = 8
+        self.text.text = 'Coordinates:\nX: %s\nY: %s' % ('0.0000', '0.0000')
+
+        self.freeze()
+
+        # self.measure_fps()
+
+
+class PlotCanvas(QtCore.QObject):
+
+    def __init__(self, container, my_app):
+        """
+        The constructor configures the VisPy 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().__init__()
+        
+        # VisPyCanvas instance
+        self.vispy_canvas = VisPyCanvas()
+        
+        self.vispy_canvas.unfreeze()
+        
+        self.my_app = my_app
+        
+        # Parent container
+        self.container = container
+        
+        # <VisPyCanvas>
+        self.vispy_canvas.create_native()
+        self.vispy_canvas.native.setParent(self.my_app.ui)
+
+        # <QtCore.QObject>
+        self.container.addWidget(self.vispy_canvas.native)
+        
+        # add two Infinite Lines to act as markers for the X,Y axis
+        self.v_line = scene.visuals.InfiniteLine(
+            pos=0, color=(0.0, 0.0, 1.0, 0.3), vertical=True, 
+            parent=self.vispy_canvas.view.scene)
+
+        self.h_line = scene.visuals.InfiniteLine(
+            pos=0, color=(0.00, 0.0, 1.0, 0.3), vertical=False, 
+            parent=self.vispy_canvas.view.scene)
+        
+        self.vispy_canvas.freeze()
+    
+    def event_connect(self, event, callback):
+        getattr(self.vispy_canvas.events, event).connect(callback)
+        
+    def event_disconnect(self, event, callback):
+        getattr(self.vispy_canvas.events, event).disconnect(callback)
+    
+    def translate_coords(self, pos):
+        """
+        Translate pixels to canvas units.
+        """
+        tr = self.vispy_canvas.grid.get_transform('canvas', 'visual')
+        return tr.map(pos)
+        
+
+class MyGui(QtWidgets.QMainWindow):
+
+    def __init__(self):
+        super().__init__()
+
+        self.setWindowTitle("VisPy Test")
+
+        # add Menubar
+        self.menu = self.menuBar()
+        self.menufile = self.menu.addMenu("File")
+        self.menuedit = self.menu.addMenu("Edit")
+        self.menufhelp = self.menu.addMenu("Help")
+
+        # add a Toolbar
+        self.file_toolbar = QtWidgets.QToolBar("File Toolbar")
+        self.addToolBar(self.file_toolbar)
+        self.button = self.file_toolbar.addAction("Open")
+
+        # add Central Widget
+        self.c_widget = QtWidgets.QWidget()
+        self.central_layout = QtWidgets.QVBoxLayout()
+        self.c_widget.setLayout(self.central_layout)
+        self.setCentralWidget(self.c_widget)
+
+        # add InfoBar
+        # self.infobar = self.statusBar()
+        # self.position_label = QtWidgets.QLabel("Position:  X: 0.0000\tY: 0.0000")
+        # self.infobar.addWidget(self.position_label)
+
+
+class MyApp(QtCore.QObject):
+
+    def __init__(self):
+        super().__init__()
+        
+        self.ui = MyGui()
+        self.plot = PlotCanvas(container=self.ui.central_layout, my_app=self)
+        
+        self.ui.show()
+        
+        self.plot.event_connect(event="mouse_move", callback=self.on_mouse_move)
+    
+    def on_mouse_move(self, event):
+        cursor_pos = event.pos
+        
+        pos_canvas = self.plot.translate_coords(cursor_pos)
+        
+        # we don't need all the info in the tuple returned by the translate_coords()
+        # only first 2 elements
+        pos_canvas = [pos_canvas[0], pos_canvas[1]]
+        # self.ui.position_label.setText("Position:  X: %.4f\tY: %.4f" % (pos_canvas[0], pos_canvas[1]))
+        # pos_text = 'Coordinates:   \nX: {:<7.4f}\nY: {:<7.4f}'.format(pos_canvas[0], pos_canvas[1])
+        pos_text = 'Coordinates:   \nX: {:<.4f}\nY: {:<.4f}'.format(pos_canvas[0], pos_canvas[1])
+        self.plot.vispy_canvas.text.text = pos_text
+
+
+if __name__ == '__main__':
+    app = QtWidgets.QApplication(sys.argv)
+
+    m_app = MyApp()
+    sys.exit(app.exec_())

+ 1 - 0
defaults.py

@@ -43,6 +43,7 @@ class FlatCAMDefaults:
 
         # General
         "global_graphic_engine": '3D',
+        "global_hud": True,
         "global_app_level": 'b',
         "global_portable": False,
         "global_language": 'English',

+ 11 - 7
flatcamEditors/FlatCAMExcEditor.py

@@ -3801,18 +3801,22 @@ class FlatCAMExcEditor(QtCore.QObject):
         self.snap_x = x
         self.snap_y = y
 
-        # update the position label in the infobar since the APP mouse event handlers are disconnected
-        self.app.ui.position_label.setText("&nbsp;&nbsp;&nbsp;&nbsp;<b>X</b>: %.4f&nbsp;&nbsp;   "
-                                           "<b>Y</b>: %.4f" % (x, y))
-
         if self.pos is None:
             self.pos = (0, 0)
         self.app.dx = x - self.pos[0]
         self.app.dy = y - self.pos[1]
 
-        # update the reference position label in the infobar since the APP mouse event handlers are disconnected
-        self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp;  <b>Dy</b>: "
-                                               "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (self.app.dx, self.app.dy))
+        # # update the position label in the infobar since the APP mouse event handlers are disconnected
+        # self.app.ui.position_label.setText("&nbsp;&nbsp;&nbsp;&nbsp;<b>X</b>: %.4f&nbsp;&nbsp;   "
+        #                                    "<b>Y</b>: %.4f" % (x, y))
+        # # update the reference position label in the infobar since the APP mouse event handlers are disconnected
+        # self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp;  <b>Dy</b>: "
+        #                                        "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (self.app.dx, self.app.dy))
+
+        units = self.app.defaults["units"].lower()
+        self.plotcanvas.text_hud.text = \
+            'Dx:\t{:<.4f} [{:s}]\nDy:\t{:<.4f} [{:s}]\nX:  \t{:<.4f} [{:s}]\nY:  \t{:<.4f} [{:s}]'.format(
+                self.app.dx, units, self.app.dy, units, x, units, y, units)
 
         # ## Utility geometry (animated)
         self.update_utility_geometry(data=(x, y))

+ 12 - 7
flatcamEditors/FlatCAMGeoEditor.py

@@ -4271,18 +4271,23 @@ class FlatCAMGeoEditor(QtCore.QObject):
         self.snap_y = y
         self.app.mouse = [x, y]
 
-        # update the position label in the infobar since the APP mouse event handlers are disconnected
-        self.app.ui.position_label.setText("&nbsp;&nbsp;&nbsp;&nbsp;<b>X</b>: %.4f&nbsp;&nbsp;   "
-                                           "<b>Y</b>: %.4f" % (x, y))
-
         if self.pos is None:
             self.pos = (0, 0)
         self.app.dx = x - self.pos[0]
         self.app.dy = y - self.pos[1]
 
-        # update the reference position label in the infobar since the APP mouse event handlers are disconnected
-        self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp;  <b>Dy</b>: "
-                                               "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (self.app.dx, self.app.dy))
+        # # update the position label in the infobar since the APP mouse event handlers are disconnected
+        # self.app.ui.position_label.setText("&nbsp;&nbsp;&nbsp;&nbsp;<b>X</b>: %.4f&nbsp;&nbsp;   "
+        #                                    "<b>Y</b>: %.4f" % (x, y))
+        #
+        # # update the reference position label in the infobar since the APP mouse event handlers are disconnected
+        # self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp;  <b>Dy</b>: "
+        #                                        "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (self.app.dx, self.app.dy))
+
+        units = self.app.defaults["units"].lower()
+        self.plotcanvas.text_hud.text = \
+            'Dx:\t{:<.4f} [{:s}]\nDy:\t{:<.4f} [{:s}]\nX:  \t{:<.4f} [{:s}]\nY:  \t{:<.4f} [{:s}]'.format(
+                self.app.dx, units, self.app.dy, units, x, units, y, units)
 
         if event.button == 1 and event_is_dragging and isinstance(self.active_tool, FCEraser):
             pass

+ 12 - 7
flatcamEditors/FlatCAMGrbEditor.py

@@ -4774,18 +4774,23 @@ class FlatCAMGrbEditor(QtCore.QObject):
 
         self.app.mouse = [x, y]
 
-        # update the position label in the infobar since the APP mouse event handlers are disconnected
-        self.app.ui.position_label.setText("&nbsp;&nbsp;&nbsp;&nbsp;<b>X</b>: %.4f&nbsp;&nbsp;   " 
-                                           "<b>Y</b>: %.4f" % (x, y))
-
         if self.pos is None:
             self.pos = (0, 0)
         self.app.dx = x - self.pos[0]
         self.app.dy = y - self.pos[1]
 
-        # update the reference position label in the infobar since the APP mouse event handlers are disconnected
-        self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp;  <b>Dy</b>: " 
-                                               "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (self.app.dx, self.app.dy))
+        # # update the position label in the infobar since the APP mouse event handlers are disconnected
+        # self.app.ui.position_label.setText("&nbsp;&nbsp;&nbsp;&nbsp;<b>X</b>: %.4f&nbsp;&nbsp;   "
+        #                                    "<b>Y</b>: %.4f" % (x, y))
+        #
+        # # update the reference position label in the infobar since the APP mouse event handlers are disconnected
+        # self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp;  <b>Dy</b>: "
+        #                                        "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (self.app.dx, self.app.dy))
+
+        units = self.app.defaults["units"].lower()
+        self.plotcanvas.text_hud.text = \
+            'Dx:\t{:<.4f} [{:s}]\nDy:\t{:<.4f} [{:s}]\nX:  \t{:<.4f} [{:s}]\nY:  \t{:<.4f} [{:s}]'.format(
+                self.app.dx, units, self.app.dy, units, x, units, y, units)
 
         self.update_utility_geometry(data=(x, y))
 

+ 16 - 11
flatcamGUI/FlatCAMGUI.py

@@ -2306,17 +2306,17 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
         self.snap_infobar_label.setPixmap(QtGui.QPixmap(self.app.resource_location + '/snap_16.png'))
         self.infobar.addWidget(self.snap_infobar_label)
 
-        self.rel_position_label = QtWidgets.QLabel(
-            "<b>Dx</b>: 0.0000&nbsp;&nbsp;   <b>Dy</b>: 0.0000&nbsp;&nbsp;&nbsp;&nbsp;")
-        self.rel_position_label.setMinimumWidth(110)
-        self.rel_position_label.setToolTip(_("Relative measurement.\nReference is last click position"))
-        self.infobar.addWidget(self.rel_position_label)
-
-        self.position_label = QtWidgets.QLabel(
-            "&nbsp;&nbsp;&nbsp;&nbsp;<b>X</b>: 0.0000&nbsp;&nbsp;   <b>Y</b>: 0.0000")
-        self.position_label.setMinimumWidth(110)
-        self.position_label.setToolTip(_("Absolute measurement.\nReference is (X=0, Y= 0) position"))
-        self.infobar.addWidget(self.position_label)
+        # self.rel_position_label = QtWidgets.QLabel(
+        #     "<b>Dx</b>: 0.0000&nbsp;&nbsp;   <b>Dy</b>: 0.0000&nbsp;&nbsp;&nbsp;&nbsp;")
+        # self.rel_position_label.setMinimumWidth(110)
+        # self.rel_position_label.setToolTip(_("Relative measurement.\nReference is last click position"))
+        # self.infobar.addWidget(self.rel_position_label)
+        #
+        # self.position_label = QtWidgets.QLabel(
+        #     "&nbsp;&nbsp;&nbsp;&nbsp;<b>X</b>: 0.0000&nbsp;&nbsp;   <b>Y</b>: 0.0000")
+        # self.position_label.setMinimumWidth(110)
+        # self.position_label.setToolTip(_("Absolute measurement.\nReference is (X=0, Y= 0) position"))
+        # self.infobar.addWidget(self.position_label)
 
         self.units_label = QtWidgets.QLabel("[in]")
         self.units_label.setMargin(2)
@@ -2993,6 +2993,11 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
                 if key == QtCore.Qt.Key_G:
                     self.app.on_toggle_axis()
 
+                # Toggle HUD (Heads-Up Display)
+                if key == QtCore.Qt.Key_H:
+                    state = False if self.app.plotcanvas.hud_enabled else True
+                    self.app.plotcanvas.on_toggle_hud(state=state)
+
                 # Locate in Object
                 if key == QtCore.Qt.Key_J:
                     self.app.on_locate(obj=self.app.collection.get_active())

+ 31 - 2
flatcamGUI/PlotCanvas.py

@@ -10,7 +10,7 @@ from PyQt5 import QtCore
 import logging
 from flatcamGUI.VisPyCanvas import VisPyCanvas, Color
 from flatcamGUI.VisPyVisuals import ShapeGroup, ShapeCollection, TextCollection, TextGroup, Cursor
-from vispy.scene.visuals import InfiniteLine, Line
+from vispy.scene.visuals import InfiniteLine, Line, Rectangle, Text
 
 import numpy as np
 from vispy.geometry import Rect
@@ -54,8 +54,12 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas):
 
         if theme == 'white':
             self.line_color = (0.3, 0.0, 0.0, 1.0)
+            self.rect_hud_color = Color('#0000FF10')
+            self.text_hud_color = 'black'
         else:
             self.line_color = (0.4, 0.4, 0.4, 1.0)
+            self.rect_hud_color = Color('#0000FF10')
+            self.text_hud_color = 'white'
 
         # workspace lines; I didn't use the rectangle because I didn't want to add another VisPy Node,
         # which might decrease performance
@@ -146,13 +150,28 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas):
         self.cursor_h_line = InfiniteLine(pos=None, color=c_color, vertical=False,
                                           parent=self.line_parent)
 
+        self.rect_hud = Rectangle(center=(90,45), color=self.rect_hud_color, border_color=self.rect_hud_color,
+                                  width=170, height=80, radius=[5, 5, 5, 5], parent=None)
+        self.rect_hud.set_gl_state(depth_test=False)
+
+        # HUD Display
+        self.hud_enabled = False
+
+        self.text_hud = Text('', color=self.text_hud_color, pos=(8, 45), method='gpu', anchor_x='left', parent=None)
+        self.text_hud.font_size = 8
+        units = self.fcapp.defaults["units"].lower()
+        self.text_hud.text = 'Dx:\t%s [%s]\nDy:\t%s [%s]\nX:  \t%s [%s]\nY:  \t%s [%s]' % \
+                             ('0.0000', units, '0.0000', units, '0.0000', units, '0.0000', units)
+
+        if self.fcapp.defaults['global_hud'] is True:
+            self.on_toggle_hud(state=True)
+
         self.shape_collections = []
 
         self.shape_collection = self.new_shape_collection()
         self.fcapp.pool_recreated.connect(self.on_pool_recreated)
         self.text_collection = self.new_text_collection()
 
-        # TODO: Should be setting to show/hide CNC job annotations (global or per object)
         self.text_collection.enabled = True
 
         self.c = None
@@ -163,6 +182,16 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas):
 
         self.graph_event_connect('mouse_wheel', self.on_mouse_scroll)
 
+    def on_toggle_hud(self, state):
+        if state:
+            self.hud_enabled = True
+            self.rect_hud.parent = self.view
+            self.text_hud.parent = self.view
+        else:
+            self.hud_enabled = False
+            self.rect_hud.parent = None
+            self.text_hud.parent = None
+
     def draw_workspace(self, workspace_size):
         """
         Draw a rectangular shape on canvas to specify our valid workspace.

+ 73 - 0
flatcamGUI/PlotCanvasLegacy.py

@@ -29,6 +29,7 @@ mpl_use("Qt5Agg")
 from matplotlib.figure import Figure
 from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
 from matplotlib.lines import Line2D
+from matplotlib.offsetbox import AnchoredText
 # from matplotlib.widgets import Cursor
 
 fcTranslate.apply_language('strings')
@@ -147,9 +148,13 @@ class PlotCanvasLegacy(QtCore.QObject):
         if self.app.defaults['global_theme'] == 'white':
             theme_color = '#FFFFFF'
             tick_color = '#000000'
+            self.rect_hud_color = '#0000FF10'
+            self.text_hud_color = '#000000'
         else:
             theme_color = '#000000'
             tick_color = '#FFFFFF'
+            self.rect_hud_color = '#0000FF10'
+            self.text_hud_color = '#000000'
 
         # workspace lines; I didn't use the rectangle because I didn't want to add another VisPy Node,
         # which might decrease performance
@@ -298,11 +303,79 @@ class PlotCanvasLegacy(QtCore.QObject):
         # signal if there is a doubleclick
         self.is_dblclk = False
 
+        self.hud_enabled = False
+        self.text_hud = self.Thud(plotcanvas=self)
+
+        # bbox_props = dict(boxstyle="round,pad=0.3", fc="blue", ec="b", lw=0)
+        # self.text_hud = self.figure.text(0, 0, "Direction", ha="left", va="center", rotation=0,
+        #                                size=15,
+        #                                bbox=bbox_props)
+
         # draw a rectangle made out of 4 lines on the canvas to serve as a hint for the work area
         # all CNC have a limited workspace
         if self.app.defaults['global_workspace'] is True:
             self.draw_workspace(workspace_size=self.app.defaults["global_workspaceT"])
 
+        if self.app.defaults['global_hud'] is True:
+            self.on_toggle_hud(state=True)
+
+    def on_toggle_hud(self, state):
+        if state:
+            self.hud_enabled = True
+            self.text_hud.add_artist()
+        else:
+            self.hud_enabled = False
+            self.text_hud.remove_artist()
+        self.canvas.draw()
+
+    class Thud(QtCore.QObject):
+        text_changed = QtCore.pyqtSignal(str)
+
+        def __init__(self, plotcanvas):
+            super().__init__()
+
+            self.p = plotcanvas
+            units = self.p.app.defaults['units']
+            self._text = 'Dx:    %s [%s]\nDy:    %s [%s]\nX:      %s [%s]\nY:      %s [%s]' % \
+                         ('0.0000', units, '0.0000', units, '0.0000', units, '0.0000', units)
+
+            self.hud_holder = AnchoredText(self._text,
+                              prop=dict(size=20), frameon=True,
+                              loc='upper left',
+                              )
+            self.hud_holder.patch.set_boxstyle("round,pad=0.,rounding_size=0.2")
+
+            self.hud_holder.patch.set_facecolor('blue')
+            self.hud_holder.patch.set_alpha(0.3)
+            self.hud_holder.patch.set_edgecolor((0, 0, 0, 0))
+
+            self.text_changed.connect(self.on_text_changed)
+
+        @property
+        def text(self):
+            return self._text
+
+        @text.setter
+        def text(self, val):
+            self.text_changed.emit(val)
+            self._text = val
+
+        def on_text_changed(self, txt):
+            try:
+                txt = txt.replace('\t', '    ')
+                self.hud_holder.txt.set_text(txt)
+                self.p.canvas.draw()
+            except Exception:
+                pass
+
+        def add_artist(self):
+            if self.hud_holder not in self.p.axes.artists:
+                self.p.axes.add_artist(self.hud_holder)
+
+        def remove_artist(self):
+            if self.hud_holder in self.p.axes.artists:
+                self.p.axes.artists.remove(self.hud_holder)
+
     def draw_workspace(self, workspace_size):
         """
         Draw a rectangular shape on canvas to specify our valid workspace.

+ 1 - 0
flatcamGUI/VisPyCanvas.py

@@ -13,6 +13,7 @@ import numpy as np
 
 import vispy.scene as scene
 from vispy.scene.cameras.base_camera import BaseCamera
+# from vispy.scene.widgets import Widget as VisPyWidget
 from vispy.color import Color
 
 import time

+ 11 - 5
flatcamTools/ToolCopperThieving.py

@@ -910,16 +910,22 @@ class ToolCopperThieving(FlatCAMTool):
                                          edge_width=self.app.defaults["global_cursor_width"],
                                          size=self.app.defaults["global_cursor_size"])
 
-        # 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)
 
         self.app.dx = curr_pos[0] - float(self.cursor_pos[0])
         self.app.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;" % (self.app.dx, self.app.dy))
+
+        # # 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]))
+        # self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp;  <b>Dy</b>: "
+        #                                        "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (self.app.dx, self.app.dy))
+
+        units = self.app.defaults["units"].lower()
+        self.plotcanvas.text_hud.text = \
+            'Dx:\t{:<.4f} [{:s}]\nDy:\t{:<.4f} [{:s}]\nX:  \t{:<.4f} [{:s}]\nY:  \t{:<.4f} [{:s}]'.format(
+                self.app.dx, units, self.app.dy, units, curr_pos[0], units, curr_pos[1], units)
 
         # draw the utility geometry
         if self.first_click:

+ 10 - 5
flatcamTools/ToolDistance.py

@@ -544,11 +544,16 @@ class Distance(FlatCAMTool):
             else:
                 pos = (pos_canvas[0], pos_canvas[1])
 
-            self.app.ui.position_label.setText(
-                "&nbsp;&nbsp;&nbsp;&nbsp;<b>X</b>: {}&nbsp;&nbsp;   <b>Y</b>: {}".format(
-                    '%.*f' % (self.decimals, pos[0]), '%.*f' % (self.decimals, pos[1])
-                )
-            )
+            # self.app.ui.position_label.setText(
+            #     "&nbsp;&nbsp;&nbsp;&nbsp;<b>X</b>: {}&nbsp;&nbsp;   <b>Y</b>: {}".format(
+            #         '%.*f' % (self.decimals, pos[0]), '%.*f' % (self.decimals, pos[1])
+            #     )
+            # )
+
+            units = self.app.defaults["units"].lower()
+            self.plotcanvas.text_hud.text = \
+                'Dx:\t{:<.4f} [{:s}]\nDy:\t{:<.4f} [{:s}]\nX:  \t{:<.4f} [{:s}]\nY:  \t{:<.4f} [{:s}]'.format(
+                    0.0000, units, 0.0000, units, pos[0], units, pos[1], units)
 
             if self.rel_point1 is not None:
                 dx = pos[0] - float(self.rel_point1[0])

+ 11 - 5
flatcamTools/ToolNCC.py

@@ -1825,16 +1825,22 @@ class NonCopperClear(FlatCAMTool, Gerber):
                                          edge_width=self.app.defaults["global_cursor_width"],
                                          size=self.app.defaults["global_cursor_size"])
 
-        # 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)
 
         self.app.dx = curr_pos[0] - float(self.cursor_pos[0])
         self.app.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;" % (self.app.dx, self.app.dy))
+
+        # # 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]))
+        # self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp;  <b>Dy</b>: "
+        #                                        "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (self.app.dx, self.app.dy))
+
+        units = self.app.defaults["units"].lower()
+        self.plotcanvas.text_hud.text = \
+            'Dx:\t{:<.4f} [{:s}]\nDy:\t{:<.4f} [{:s}]\nX:  \t{:<.4f} [{:s}]\nY:  \t{:<.4f} [{:s}]'.format(
+                self.app.dx, units, self.app.dy, units, curr_pos[0], units, curr_pos[1], units)
 
         # draw the utility geometry
         if shape_type == "square":

+ 11 - 5
flatcamTools/ToolPaint.py

@@ -1724,16 +1724,22 @@ class ToolPaint(FlatCAMTool, Gerber):
                                          edge_width=self.app.defaults["global_cursor_width"],
                                          size=self.app.defaults["global_cursor_size"])
 
-        # 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)
 
         self.app.dx = curr_pos[0] - float(self.cursor_pos[0])
         self.app.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;" % (self.app.dx, self.app.dy))
+
+        # # 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]))
+        # self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp;  <b>Dy</b>: "
+        #                                        "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (self.app.dx, self.app.dy))
+
+        units = self.app.defaults["units"].lower()
+        self.plotcanvas.text_hud.text = \
+            'Dx:\t{:<.4f} [{:s}]\nDy:\t{:<.4f} [{:s}]\nX:  \t{:<.4f} [{:s}]\nY:  \t{:<.4f} [{:s}]'.format(
+                self.app.dx, units, self.app.dy, units, curr_pos[0], units, curr_pos[1], units)
 
         # draw the utility geometry
         if shape_type == "square":