Quellcode durchsuchen

- Modified the Distance Tool such that the Measure button can't be clicked while measuring is in progress
- optimized selection of drills in the Excellon Editor
- fixed bugs in multiple selection in Excellon Editor
- fixed selection problems in Gerber Editor
- in Distance Tool, when run in the Excellon or Gerber Editor, added a new option to snap to center of the geometry (drill for Excellon, pad for Gerber)

Marius Stanciu vor 6 Jahren
Ursprung
Commit
7424bb917c
5 geänderte Dateien mit 295 neuen und 125 gelöschten Zeilen
  1. 3 0
      FlatCAMApp.py
  2. 8 0
      README.md
  3. 34 28
      flatcamEditors/FlatCAMExcEditor.py
  4. 30 23
      flatcamEditors/FlatCAMGrbEditor.py
  5. 220 74
      flatcamTools/ToolDistance.py

+ 3 - 0
FlatCAMApp.py

@@ -890,6 +890,9 @@ class App(QtCore.QObject):
             # Subtract Tool
             "tools_sub_close_paths": True,
 
+            # Distance Tool
+            "tools_dist_snap_center": False,
+
             # ###################################################################################
             # ################################ TOOLS 2 ##########################################
             # ###################################################################################

+ 8 - 0
README.md

@@ -9,6 +9,14 @@ CAD program, and create G-Code for Isolation routing.
 
 =================================================
 
+5.02.2020
+
+- Modified the Distance Tool such that the Measure button can't be clicked while measuring is in progress
+- optimized selection of drills in the Excellon Editor
+- fixed bugs in multiple selection in Excellon Editor
+- fixed selection problems in Gerber Editor
+- in Distance Tool, when run in the Excellon or Gerber Editor, added a new option to snap to center of the geometry (drill for Excellon, pad for Gerber)
+
 3.02.2020
 
 - modified Spinbox and DoubleSpinbox Custom UI elements such that they issue a warning status message when the typed value is out of range

+ 34 - 28
flatcamEditors/FlatCAMExcEditor.py

@@ -1334,8 +1334,8 @@ class FCDrillCopy(FCDrillMove):
 
 
 class FCDrillSelect(DrawTool):
-    def __init__(self, exc_editor_app):
-        DrawTool.__init__(self, exc_editor_app)
+    def __init__(self, draw_app):
+        DrawTool.__init__(self, draw_app)
         self.name = 'drill_select'
 
         try:
@@ -1343,7 +1343,7 @@ class FCDrillSelect(DrawTool):
         except Exception as e:
             pass
 
-        self.exc_editor_app = exc_editor_app
+        self.exc_editor_app = draw_app
         self.storage = self.exc_editor_app.storage_dict
         # self.selected = self.exc_editor_app.selected
 
@@ -1368,10 +1368,10 @@ class FCDrillSelect(DrawTool):
         else:
             mod_key = None
 
-        if mod_key == self.draw_app.app.defaults["global_mselect_key"]:
+        if mod_key == self.exc_editor_app.app.defaults["global_mselect_key"]:
             pass
         else:
-            self.exc_editor_app.selected = []
+            self.exc_editor_app.selected = list()
 
     def click_release(self, pos):
         self.exc_editor_app.tools_table_exc.clearSelection()
@@ -1379,8 +1379,10 @@ class FCDrillSelect(DrawTool):
 
         try:
             for storage in self.exc_editor_app.storage_dict:
-                for sh in self.exc_editor_app.storage_dict[storage].get_objects():
-                    self.sel_storage.insert(sh)
+                # for sh in self.exc_editor_app.storage_dict[storage].get_objects():
+                #     self.sel_storage.insert(sh)
+                _, st_closest_shape = self.exc_editor_app.storage_dict[storage].nearest(pos)
+                self.sel_storage.insert(st_closest_shape)
 
             _, closest_shape = self.sel_storage.nearest(pos)
 
@@ -1417,37 +1419,41 @@ class FCDrillSelect(DrawTool):
             else:
                 mod_key = None
 
-            if mod_key == self.draw_app.app.defaults["global_mselect_key"]:
+            if mod_key == self.exc_editor_app.app.defaults["global_mselect_key"]:
                 if closest_shape in self.exc_editor_app.selected:
                     self.exc_editor_app.selected.remove(closest_shape)
                 else:
                     self.exc_editor_app.selected.append(closest_shape)
             else:
-                self.draw_app.selected = []
-                self.draw_app.selected.append(closest_shape)
+                self.exc_editor_app.selected = list()
+                self.exc_editor_app.selected.append(closest_shape)
 
             # select the diameter of the selected shape in the tool table
             try:
-                self.draw_app.tools_table_exc.cellPressed.disconnect()
+                self.exc_editor_app.tools_table_exc.cellPressed.disconnect()
             except (TypeError, AttributeError):
                 pass
 
-            self.exc_editor_app.tools_table_exc.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection)
+            # if mod_key == self.exc_editor_app.app.defaults["global_mselect_key"]:
+            #     self.exc_editor_app.tools_table_exc.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection)
+            self.sel_tools.clear()
+
             for shape_s in self.exc_editor_app.selected:
                 for storage in self.exc_editor_app.storage_dict:
                     if shape_s in self.exc_editor_app.storage_dict[storage].get_objects():
                         self.sel_tools.add(storage)
 
+            self.exc_editor_app.tools_table_exc.clearSelection()
             for storage in self.sel_tools:
-                for k, v in self.draw_app.tool2tooldia.items():
+                for k, v in self.exc_editor_app.tool2tooldia.items():
                     if v == storage:
                         self.exc_editor_app.tools_table_exc.selectRow(int(k) - 1)
-                        self.draw_app.last_tool_selected = int(k)
+                        self.exc_editor_app.last_tool_selected = int(k)
                         break
 
-            self.exc_editor_app.tools_table_exc.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
+            # self.exc_editor_app.tools_table_exc.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
 
-            self.draw_app.tools_table_exc.cellPressed.connect(self.draw_app.on_row_selected)
+            self.exc_editor_app.tools_table_exc.cellPressed.connect(self.exc_editor_app.on_row_selected)
 
         # delete whatever is in selection storage, there is no longer need for those shapes
         self.sel_storage = FlatCAMExcEditor.make_storage()
@@ -2054,32 +2060,32 @@ class FlatCAMExcEditor(QtCore.QObject):
 
         self.in_action = False
 
-        self.storage_dict = {}
+        self.storage_dict = dict()
 
-        self.current_storage = []
+        self.current_storage = list()
 
         # build the data from the Excellon point into a dictionary
         #  {tool_dia: [geometry_in_points]}
-        self.points_edit = {}
-        self.slot_points_edit = {}
+        self.points_edit = dict()
+        self.slot_points_edit = dict()
 
-        self.sorted_diameters = []
+        self.sorted_diameters = list()
 
-        self.new_drills = []
-        self.new_tools = {}
-        self.new_slots = []
-        self.new_tool_offset = {}
+        self.new_drills = list()
+        self.new_tools = dict()
+        self.new_slots = list()
+        self.new_tool_offset = dict()
 
         # dictionary to store the tool_row and diameters in Tool_table
         # it will be updated everytime self.build_ui() is called
-        self.olddia_newdia = {}
+        self.olddia_newdia = dict()
 
-        self.tool2tooldia = {}
+        self.tool2tooldia = dict()
 
         # this will store the value for the last selected tool, for use after clicking on canvas when the selection
         # is cleared but as a side effect also the selected tool is cleared
         self.last_tool_selected = None
-        self.utility = []
+        self.utility = list()
 
         # this will flag if the Editor "tools" are launched from key shortcuts (True) or from menu toolbar (False)
         self.launched_from_shortcuts = False

+ 30 - 23
flatcamEditors/FlatCAMGrbEditor.py

@@ -2300,10 +2300,10 @@ class FCApertureSelect(DrawTool):
 
         # since FCApertureSelect tool is activated whenever a tool is exited I place here the reinitialization of the
         # bending modes using in FCRegion and FCTrack
-        self.draw_app.bend_mode = 1
+        self.grb_editor_app.bend_mode = 1
 
         # here store the selected apertures
-        self.sel_aperture = set()
+        self.sel_aperture = list()
 
         try:
             self.grb_editor_app.apertures_table.clearSelection()
@@ -2332,7 +2332,7 @@ class FCApertureSelect(DrawTool):
         else:
             mod_key = None
 
-        if mod_key == self.draw_app.app.defaults["global_mselect_key"]:
+        if mod_key == self.grb_editor_app.app.defaults["global_mselect_key"]:
             pass
         else:
             self.grb_editor_app.selected = []
@@ -2348,46 +2348,53 @@ class FCApertureSelect(DrawTool):
         else:
             mod_key = None
 
+        if mod_key != self.grb_editor_app.app.defaults["global_mselect_key"]:
+            self.grb_editor_app.selected.clear()
+            self.sel_aperture.clear()
+
         for storage in self.grb_editor_app.storage_dict:
             try:
-                for geo_el in self.grb_editor_app.storage_dict[storage]['geometry']:
-                    if 'solid' in geo_el.geo:
-                        geometric_data = geo_el.geo['solid']
+                for shape_stored in self.grb_editor_app.storage_dict[storage]['geometry']:
+                    if 'solid' in shape_stored.geo:
+                        geometric_data = shape_stored.geo['solid']
                         if Point(point).within(geometric_data):
-                            if mod_key == self.grb_editor_app.app.defaults["global_mselect_key"]:
-                                if geo_el in self.draw_app.selected:
-                                    self.draw_app.selected.remove(geo_el)
-                                    self.sel_aperture.remove(storage)
-                                else:
-                                    # add the object to the selected shapes
-                                    self.draw_app.selected.append(geo_el)
-                                    self.sel_aperture.add(storage)
+                            if shape_stored in self.grb_editor_app.selected:
+                                self.grb_editor_app.selected.remove(shape_stored)
                             else:
-                                self.draw_app.selected.append(geo_el)
-                                self.sel_aperture.add(storage)
+                                # add the object to the selected shapes
+                                self.grb_editor_app.selected.append(shape_stored)
             except KeyError:
                 pass
 
         # select the aperture in the Apertures Table that is associated with the selected shape
+        self.sel_aperture.clear()
+
+        self.grb_editor_app.apertures_table.clearSelection()
         try:
-            self.draw_app.apertures_table.cellPressed.disconnect()
+            self.grb_editor_app.apertures_table.cellPressed.disconnect()
         except Exception as e:
             log.debug("FlatCAMGrbEditor.FCApertureSelect.click_release() --> %s" % str(e))
 
-        self.grb_editor_app.apertures_table.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection)
+        for shape_s in self.grb_editor_app.selected:
+            for storage in self.grb_editor_app.storage_dict:
+                if shape_s in self.grb_editor_app.storage_dict[storage]['geometry']:
+                    self.sel_aperture.append(storage)
+
+        # self.grb_editor_app.apertures_table.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection)
         for aper in self.sel_aperture:
             for row in range(self.grb_editor_app.apertures_table.rowCount()):
                 if str(aper) == self.grb_editor_app.apertures_table.item(row, 1).text():
-                    self.grb_editor_app.apertures_table.selectRow(row)
-                    self.draw_app.last_aperture_selected = aper
-        self.grb_editor_app.apertures_table.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
+                    if not self.grb_editor_app.apertures_table.item(row, 0).isSelected():
+                        self.grb_editor_app.apertures_table.selectRow(row)
+                        self.grb_editor_app.last_aperture_selected = aper
+        # self.grb_editor_app.apertures_table.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
 
-        self.draw_app.apertures_table.cellPressed.connect(self.draw_app.on_row_selected)
+        self.grb_editor_app.apertures_table.cellPressed.connect(self.grb_editor_app.on_row_selected)
 
         return ""
 
     def clean_up(self):
-        self.draw_app.plot_all()
+        self.grb_editor_app.plot_all()
 
 
 class FCTransform(FCShapeTool):

+ 220 - 74
flatcamTools/ToolDistance.py

@@ -9,13 +9,18 @@ from PyQt5 import QtWidgets, QtCore
 
 from FlatCAMTool import FlatCAMTool
 from flatcamGUI.VisPyVisuals import *
-from flatcamGUI.GUIElements import FCEntry
+from flatcamGUI.GUIElements import FCEntry, FCButton, FCCheckBox
+
+from shapely.geometry import Point, MultiLineString, Polygon
+
+import FlatCAMTranslation as fcTranslate
+from camlib import FlatCAMRTreeStorage
+from flatcamEditors.FlatCAMGeoEditor import DrawToolShape
 
 from copy import copy
 import math
 import logging
 import gettext
-import FlatCAMTranslation as fcTranslate
 import builtins
 
 fcTranslate.apply_language('strings')
@@ -43,82 +48,101 @@ class Distance(FlatCAMTool):
         self.layout.addWidget(title_label)
 
         # ## Form Layout
-        form_layout = QtWidgets.QFormLayout()
-        self.layout.addLayout(form_layout)
+        grid0 = QtWidgets.QGridLayout()
+        grid0.setColumnStretch(0, 0)
+        grid0.setColumnStretch(1, 1)
+        self.layout.addLayout(grid0)
 
         self.units_label = QtWidgets.QLabel('%s:' % _("Units"))
         self.units_label.setToolTip(_("Those are the units in which the distance is measured."))
         self.units_value = QtWidgets.QLabel("%s" % str({'mm': _("METRIC (mm)"), 'in': _("INCH (in)")}[self.units]))
         self.units_value.setDisabled(True)
 
-        self.start_label = QtWidgets.QLabel("%s:" % _('Start Coords'))
-        self.start_label.setToolTip(_("This is measuring Start point coordinates."))
-
-        self.stop_label = QtWidgets.QLabel("%s:" % _('Stop Coords'))
-        self.stop_label.setToolTip(_("This is the measuring Stop point coordinates."))
-
-        self.distance_x_label = QtWidgets.QLabel('%s:' % _("Dx"))
-        self.distance_x_label.setToolTip(_("This is the distance measured over the X axis."))
+        grid0.addWidget(self.units_label, 0, 0)
+        grid0.addWidget(self.units_value, 0, 1)
 
-        self.distance_y_label = QtWidgets.QLabel('%s:' % _("Dy"))
-        self.distance_y_label.setToolTip(_("This is the distance measured over the Y axis."))
+        self.snap_center_cb = FCCheckBox(_("Snap to center"))
+        self.snap_center_cb.setToolTip(
+            _("Mouse cursor will snap to the center of the pad/drill\n"
+              "when it is hovering over the geometry of the pad/drill.")
+        )
+        grid0.addWidget(self.snap_center_cb, 1, 0, 1, 2)
 
-        self.angle_label = QtWidgets.QLabel('%s:' % _("Angle"))
-        self.angle_label.setToolTip(_("This is orientation angle of the measuring line."))
+        separator_line = QtWidgets.QFrame()
+        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+        grid0.addWidget(separator_line, 2, 0, 1, 2)
 
-        self.total_distance_label = QtWidgets.QLabel("<b>%s:</b>" % _('DISTANCE'))
-        self.total_distance_label.setToolTip(_("This is the point to point Euclidian distance."))
+        self.start_label = QtWidgets.QLabel("%s:" % _('Start Coords'))
+        self.start_label.setToolTip(_("This is measuring Start point coordinates."))
 
         self.start_entry = FCEntry()
         self.start_entry.setReadOnly(True)
         self.start_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
         self.start_entry.setToolTip(_("This is measuring Start point coordinates."))
 
+        grid0.addWidget(self.start_label, 3, 0)
+        grid0.addWidget(self.start_entry, 3, 1)
+
+        self.stop_label = QtWidgets.QLabel("%s:" % _('Stop Coords'))
+        self.stop_label.setToolTip(_("This is the measuring Stop point coordinates."))
+
         self.stop_entry = FCEntry()
         self.stop_entry.setReadOnly(True)
         self.stop_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
         self.stop_entry.setToolTip(_("This is the measuring Stop point coordinates."))
 
+        grid0.addWidget(self.stop_label, 4, 0)
+        grid0.addWidget(self.stop_entry, 4, 1)
+
+        self.distance_x_label = QtWidgets.QLabel('%s:' % _("Dx"))
+        self.distance_x_label.setToolTip(_("This is the distance measured over the X axis."))
+
         self.distance_x_entry = FCEntry()
         self.distance_x_entry.setReadOnly(True)
         self.distance_x_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
         self.distance_x_entry.setToolTip(_("This is the distance measured over the X axis."))
 
+        grid0.addWidget(self.distance_x_label, 5, 0)
+        grid0.addWidget(self.distance_x_entry, 5, 1)
+
+        self.distance_y_label = QtWidgets.QLabel('%s:' % _("Dy"))
+        self.distance_y_label.setToolTip(_("This is the distance measured over the Y axis."))
+
         self.distance_y_entry = FCEntry()
         self.distance_y_entry.setReadOnly(True)
         self.distance_y_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
         self.distance_y_entry.setToolTip(_("This is the distance measured over the Y axis."))
 
+        grid0.addWidget(self.distance_y_label, 6, 0)
+        grid0.addWidget(self.distance_y_entry, 6, 1)
+
+        self.angle_label = QtWidgets.QLabel('%s:' % _("Angle"))
+        self.angle_label.setToolTip(_("This is orientation angle of the measuring line."))
+
         self.angle_entry = FCEntry()
         self.angle_entry.setReadOnly(True)
         self.angle_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
         self.angle_entry.setToolTip(_("This is orientation angle of the measuring line."))
 
+        grid0.addWidget(self.angle_label, 7, 0)
+        grid0.addWidget(self.angle_entry, 7, 1)
+
+        self.total_distance_label = QtWidgets.QLabel("<b>%s:</b>" % _('DISTANCE'))
+        self.total_distance_label.setToolTip(_("This is the point to point Euclidian distance."))
+
         self.total_distance_entry = FCEntry()
         self.total_distance_entry.setReadOnly(True)
         self.total_distance_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
         self.total_distance_entry.setToolTip(_("This is the point to point Euclidian distance."))
 
-        self.measure_btn = QtWidgets.QPushButton(_("Measure"))
+        grid0.addWidget(self.total_distance_label, 8, 0)
+        grid0.addWidget(self.total_distance_entry, 8, 1)
+
+        self.measure_btn = FCButton(_("Measure"))
         # self.measure_btn.setFixedWidth(70)
         self.layout.addWidget(self.measure_btn)
 
-        form_layout.addRow(self.units_label, self.units_value)
-        form_layout.addRow(self.start_label, self.start_entry)
-        form_layout.addRow(self.stop_label, self.stop_entry)
-        form_layout.addRow(self.distance_x_label, self.distance_x_entry)
-        form_layout.addRow(self.distance_y_label, self.distance_y_entry)
-        form_layout.addRow(self.angle_label, self.angle_entry)
-        form_layout.addRow(self.total_distance_label, self.total_distance_entry)
-
-        # initial view of the layout
-        self.start_entry.set_value('(0, 0)')
-        self.stop_entry.set_value('(0, 0)')
-        self.distance_x_entry.set_value('0.0')
-        self.distance_y_entry.set_value('0.0')
-        self.angle_entry.set_value('0.0')
-        self.total_distance_entry.set_value('0.0')
-
         self.layout.addStretch()
 
         # store here the first click and second click of the measurement process
@@ -137,6 +161,15 @@ class Distance(FlatCAMTool):
         self.mm = None
         self.mr = None
 
+        # monitor if the tool was used
+        self.tool_done = False
+
+        # store the grid status here
+        self.grid_status_memory = False
+
+        # store here if the snap button was clicked
+        self.snap_toggled = None
+
         # VisPy visuals
         if self.app.is_legacy is False:
             self.sel_shapes = ShapeCollection(parent=self.app.plotcanvas.view.scene, layers=1)
@@ -145,6 +178,7 @@ class Distance(FlatCAMTool):
             self.sel_shapes = ShapeCollectionLegacy(obj=self, app=self.app, name='measurement')
 
         self.measure_btn.clicked.connect(self.activate_measure_tool)
+        self.snap_center_cb.toggled.connect(self.on_snap_toggled)
 
     def run(self, toggle=False):
         self.app.report_usage("ToolDistance()")
@@ -154,6 +188,8 @@ class Distance(FlatCAMTool):
         self.rel_point1 = None
         self.rel_point2 = None
 
+        self.tool_done = False
+
         if self.app.tool_tab_locked is True:
             return
 
@@ -177,7 +213,7 @@ class Distance(FlatCAMTool):
         # Remove anything else in the GUI
         self.app.ui.tool_scroll_area.takeWidget()
 
-        # Put ourself in the GUI
+        # Put ourselves in the GUI
         self.app.ui.tool_scroll_area.setWidget(self)
 
         # Switch notebook to tool page
@@ -195,20 +231,45 @@ class Distance(FlatCAMTool):
         self.angle_entry.set_value('0.0')
         self.total_distance_entry.set_value('0.0')
 
+        self.snap_center_cb.set_value(self.app.defaults['tools_dist_snap_center'])
+
+        # snap center works only for Gerber and Execellon Editor's
+        if self.original_call_source == 'exc_editor' or self.original_call_source == 'grb_editor':
+            self.snap_center_cb.show()
+        else:
+            self.snap_center_cb.hide()
+
         # this is a hack; seems that triggering the grid will make the visuals better
         # trigger it twice to return to the original state
         self.app.ui.grid_snap_btn.trigger()
         self.app.ui.grid_snap_btn.trigger()
 
+        if self.app.ui.grid_snap_btn.isChecked():
+            self.grid_status_memory = True
+
         log.debug("Distance Tool --> tool initialized")
 
+    def on_snap_toggled(self, state):
+        self.app.defaults['tools_dist_snap_center'] = state
+        if state:
+            # disengage the grid snapping since it will be hard to find the drills or pads on grid
+            if self.app.ui.grid_snap_btn.isChecked():
+                self.app.ui.grid_snap_btn.trigger()
+
     def activate_measure_tool(self):
         # ENABLE the Measuring TOOL
         self.active = True
 
+        # disable the measuring button
+        self.measure_btn.setDisabled(True)
+        self.measure_btn.setText('%s...' % _("Working"))
+
         self.clicked_meas = 0
         self.original_call_source = copy(self.app.call_source)
 
+        snap_center = self.app.defaults['tools_dist_snap_center']
+        self.on_snap_toggled(snap_center)
+
         self.app.inform.emit(_("MEASURING: Click on the Start point ..."))
         self.units = self.app.defaults['units'].lower()
 
@@ -267,6 +328,10 @@ class Distance(FlatCAMTool):
         self.active = False
         self.points = []
 
+        # disable the measuring button
+        self.measure_btn.setDisabled(False)
+        self.measure_btn.setText(_("Measure"))
+
         self.app.call_source = copy(self.original_call_source)
         if self.original_call_source == 'app':
             self.app.mm = self.canvas.graph_event_connect('mouse_move', self.app.on_mouse_move_over_plot)
@@ -307,8 +372,16 @@ class Distance(FlatCAMTool):
         # delete the measuring line
         self.delete_shape()
 
+        # restore the grid status
+        if (self.app.ui.grid_snap_btn.isChecked() and self.grid_status_memory is False) or \
+                (not self.app.ui.grid_snap_btn.isChecked() and self.grid_status_memory is True):
+            self.app.ui.grid_snap_btn.trigger()
+
         log.debug("Distance Tool --> exit tool")
 
+        if self.tool_done is False:
+            self.app.inform.emit('%s' % _("Distance Tool finished."))
+
     def on_mouse_click_release(self, event):
         # mouse click releases will be accepted only if the left button is clicked
         # this is necessary because right mouse click or middle mouse click
@@ -323,11 +396,71 @@ class Distance(FlatCAMTool):
 
             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])
+            if self.snap_center_cb.get_value() is False:
+                # if GRID is active we need to get the snapped positions
+                if self.app.grid_status():
+                    pos = self.app.geo_editor.snap(pos_canvas[0], pos_canvas[1])
+                else:
+                    pos = pos_canvas[0], pos_canvas[1]
             else:
-                pos = pos_canvas[0], pos_canvas[1]
+                pos = (pos_canvas[0], pos_canvas[1])
+                current_pt = Point(pos)
+                shapes_storage = self.make_storage()
+
+                if self.original_call_source == 'exc_editor':
+                    for storage in self.app.exc_editor.storage_dict:
+                        __, st_closest_shape = self.app.exc_editor.storage_dict[storage].nearest(pos)
+                        shapes_storage.insert(st_closest_shape)
+
+                    __, closest_shape = shapes_storage.nearest(pos)
+
+                    # if it's a drill
+                    if isinstance(closest_shape.geo, MultiLineString):
+                        radius = closest_shape.geo[0].length / 2.0
+                        center_pt = closest_shape.geo.centroid
+
+                        geo_buffered = center_pt.buffer(radius)
+
+                        if current_pt.within(geo_buffered):
+                            pos = (center_pt.x, center_pt.y)
+
+                    # if it's a slot
+                    elif isinstance(closest_shape.geo, Polygon):
+                        geo_buffered = closest_shape.geo.buffer(0)
+                        center_pt = geo_buffered.centroid
+
+                        if current_pt.within(geo_buffered):
+                            pos = (center_pt.x, center_pt.y)
+
+                elif self.original_call_source == 'grb_editor':
+                    clicked_pads = list()
+                    for storage in self.app.grb_editor.storage_dict:
+                        try:
+                            for shape_stored in self.app.grb_editor.storage_dict[storage]['geometry']:
+                                if 'solid' in shape_stored.geo:
+                                    geometric_data = shape_stored.geo['solid']
+                                    if Point(current_pt).within(geometric_data):
+                                        if isinstance(shape_stored.geo['follow'], Point):
+                                            clicked_pads.append(shape_stored.geo['follow'])
+                        except KeyError:
+                            pass
+
+                    if len(clicked_pads) > 1:
+                        self.tool_done = True
+                        self.deactivate_measure_tool()
+                        self.app.inform.emit('[WARNING_NOTCL] %s' % _("Pads overlapped. Aborting."))
+                        return
+
+                    pos = (clicked_pads[0].x, clicked_pads[0].y)
+
+                self.app.on_jump_to(custom_location=pos, fit_center=False)
+                # Update cursor
+                self.app.app_cursor.enabled = True
+                self.app.app_cursor.set_data(np.asarray([(pos[0], pos[1])]),
+                                             symbol='++', edge_color='#000000',
+                                             edge_width=self.app.defaults["global_cursor_width"],
+                                             size=self.app.defaults["global_cursor_size"])
+
             self.points.append(pos)
 
             # Reset here the relative coordinates so there is a new reference on the click position
@@ -340,41 +473,46 @@ class Distance(FlatCAMTool):
                 self.rel_point2 = copy(self.rel_point1)
                 self.rel_point1 = pos
 
-            if len(self.points) == 1:
-                self.start_entry.set_value("(%.*f, %.*f)" % (self.decimals, pos[0], self.decimals, pos[1]))
-                self.app.inform.emit(_("MEASURING: Click on the Destination point ..."))
-            elif len(self.points) == 2:
-                dx = self.points[1][0] - self.points[0][0]
-                dy = self.points[1][1] - self.points[0][1]
-                d = math.sqrt(dx ** 2 + dy ** 2)
-                self.stop_entry.set_value("(%.*f, %.*f)" % (self.decimals, pos[0], self.decimals, pos[1]))
-
-                self.app.inform.emit("{tx1}: {tx2} D(x) = {d_x} | D(y) = {d_y} | {tx3} = {d_z}".format(
-                    tx1=_("MEASURING"),
-                    tx2=_("Result"),
-                    tx3=_("Distance"),
-                    d_x='%*f' % (self.decimals, abs(dx)),
-                    d_y='%*f' % (self.decimals, abs(dy)),
-                    d_z='%*f' % (self.decimals, abs(d)))
-                )
+            self.calculate_distance(pos=pos)
+
+    def calculate_distance(self, pos):
+        if len(self.points) == 1:
+            self.start_entry.set_value("(%.*f, %.*f)" % (self.decimals, pos[0], self.decimals, pos[1]))
+            self.app.inform.emit(_("MEASURING: Click on the Destination point ..."))
+        elif len(self.points) == 2:
+            self.app.app_cursor.enabled = False
+            dx = self.points[1][0] - self.points[0][0]
+            dy = self.points[1][1] - self.points[0][1]
+            d = math.sqrt(dx ** 2 + dy ** 2)
+            self.stop_entry.set_value("(%.*f, %.*f)" % (self.decimals, pos[0], self.decimals, pos[1]))
+
+            self.app.inform.emit("{tx1}: {tx2} D(x) = {d_x} | D(y) = {d_y} | {tx3} = {d_z}".format(
+                tx1=_("MEASURING"),
+                tx2=_("Result"),
+                tx3=_("Distance"),
+                d_x='%*f' % (self.decimals, abs(dx)),
+                d_y='%*f' % (self.decimals, abs(dy)),
+                d_z='%*f' % (self.decimals, abs(d)))
+            )
 
-                self.distance_x_entry.set_value('%.*f' % (self.decimals, abs(dx)))
-                self.distance_y_entry.set_value('%.*f' % (self.decimals, abs(dy)))
+            self.distance_x_entry.set_value('%.*f' % (self.decimals, abs(dx)))
+            self.distance_y_entry.set_value('%.*f' % (self.decimals, abs(dy)))
 
-                if dx != 0.0:
-                    try:
-                        angle = math.degrees(math.atan(dy / dx))
-                        self.angle_entry.set_value('%.*f' % (self.decimals, angle))
-                    except Exception as e:
-                        pass
+            if dx != 0.0:
+                try:
+                    angle = math.degrees(math.atan(dy / dx))
+                    self.angle_entry.set_value('%.*f' % (self.decimals, angle))
+                except Exception:
+                    pass
 
-                self.total_distance_entry.set_value('%.*f' % (self.decimals, abs(d)))
-                self.app.ui.rel_position_label.setText(
-                    "<b>Dx</b>: {}&nbsp;&nbsp;  <b>Dy</b>: {}&nbsp;&nbsp;&nbsp;&nbsp;".format(
-                        '%.*f' % (self.decimals, pos[0]), '%.*f' % (self.decimals, pos[1])
-                    )
+            self.total_distance_entry.set_value('%.*f' % (self.decimals, abs(d)))
+            self.app.ui.rel_position_label.setText(
+                "<b>Dx</b>: {}&nbsp;&nbsp;  <b>Dy</b>: {}&nbsp;&nbsp;&nbsp;&nbsp;".format(
+                    '%.*f' % (self.decimals, pos[0]), '%.*f' % (self.decimals, pos[1])
                 )
-                self.deactivate_measure_tool()
+            )
+            self.tool_done = True
+            self.deactivate_measure_tool()
 
     def on_mouse_move_meas(self, event):
         try:  # May fail in case mouse not within axes
@@ -391,7 +529,7 @@ class Distance(FlatCAMTool):
 
             pos_canvas = self.app.plotcanvas.translate_coords((x, y))
 
-            if self.app.grid_status() == True:
+            if self.app.grid_status():
                 pos = self.app.geo_editor.snap(pos_canvas[0], pos_canvas[1])
 
                 # Update cursor
@@ -465,7 +603,15 @@ class Distance(FlatCAMTool):
         self.sel_shapes.clear()
         self.sel_shapes.redraw()
 
-    def set_meas_units(self, units):
-        self.meas.units_label.setText("[" + self.app.options["units"].lower() + "]")
+    @staticmethod
+    def make_storage():
+        # ## Shape storage.
+        storage = FlatCAMRTreeStorage()
+        storage.get_points = DrawToolShape.get_pts
+
+        return storage
+
+    # def set_meas_units(self, units):
+    #     self.meas.units_label.setText("[" + self.app.options["units"].lower() + "]")
 
 # end of file