Kaynağa Gözat

Merged in marius_stanciu/flatcam_beta/Beta (pull request #257)

Beta - new jump_to
Marius Stanciu 6 yıl önce
ebeveyn
işleme
9d76e75f19

+ 24 - 11
FlatCAMApp.py

@@ -140,8 +140,8 @@ class App(QtCore.QObject):
     # ##########################################################################
     # ################## Version and VERSION DATE ##############################
     # ##########################################################################
-    version = 8.99
-    version_date = "2019/12/15"
+    version = 8.991
+    version_date = "2019/12/30"
     beta = True
     engine = '3D'
 
@@ -237,6 +237,9 @@ class App(QtCore.QObject):
     # should be disconnected after use so it can be reused
     replot_signal = pyqtSignal(list)
 
+    # signal emitted when jumping
+    jump_signal = pyqtSignal(tuple)
+
     def __init__(self, user_defaults=True):
         """
         Starts the application.
@@ -3810,7 +3813,7 @@ class App(QtCore.QObject):
 
         if 'version' not in defaults or defaults['version'] != self.defaults['version']:
             for k, v in defaults.items():
-                if k in self.defaults:
+                if k in self.defaults and k != 'version':
                     self.defaults[k] = v
 
             # delete old factory defaults
@@ -7355,10 +7358,6 @@ 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_location = None
 
@@ -7372,17 +7371,29 @@ class App(QtCore.QObject):
             else:
                 dia_box_location = None
 
-            dia_box = Dialog_box(title=_("Jump to ..."),
-                                 label=_("Enter the coordinates in format X,Y:"),
-                                 icon=QtGui.QIcon(self.resource_location + '/jump_to16.png'),
-                                 initial_text=dia_box_location)
+            # dia_box = Dialog_box(title=_("Jump to ..."),
+            #                      label=_("Enter the coordinates in format X,Y:"),
+            #                      icon=QtGui.QIcon(self.resource_location + '/jump_to16.png'),
+            #                      initial_text=dia_box_location)
+
+            dia_box = DialogBoxRadio(title=_("Jump to ..."),
+                                     label=_("Enter the coordinates in format X,Y:"),
+                                     icon=QtGui.QIcon(self.resource_location + '/jump_to16.png'),
+                                     initial_text=dia_box_location)
 
             if dia_box.ok is True:
                 try:
                     location = eval(dia_box.location)
+
                     if not isinstance(location, tuple):
                         self.inform.emit(_("Wrong coordinates. Enter coordinates in format: X,Y"))
                         return
+
+                    if dia_box.reference == 'rel':
+                        rel_x = self.mouse[0] + location[0]
+                        rel_y = self.mouse[1] + location[1]
+                        location = (rel_x, rel_y)
+
                 except Exception:
                     return
             else:
@@ -7390,6 +7401,8 @@ class App(QtCore.QObject):
         else:
             location = custom_location
 
+        self.jump_signal.emit(location)
+
         units = self.defaults['units'].upper()
 
         if fit_center:

+ 33 - 49
FlatCAMObj.py

@@ -3745,11 +3745,11 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
             self.ui.geo_tools_table.setColumnHidden(6, False)
 
         self.set_tool_offset_visibility(selected_row)
-        self.ui_connect()
 
         # HACK: for whatever reasons the name in Selected tab is reverted to the original one after a successful rename
         # done in the collection view but only for Geometry objects. Perhaps some references remains. Should be fixed.
         self.ui.name_entry.set_value(self.options['name'])
+        self.ui_connect()
 
     def set_ui(self, ui):
         FlatCAMObj.set_ui(self, ui)
@@ -3883,7 +3883,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
         self.ui.tool_offset_entry.hide()
         self.ui.tool_offset_lbl.hide()
 
-        # used to store the state of the mpass_cb if the selected postproc for geometry is hpgl
+        # used to store the state of the mpass_cb if the selected preprocessor for geometry is hpgl
         self.old_pp_state = self.default_data['multidepth']
         self.old_toolchangeg_state = self.default_data['toolchange']
 
@@ -3934,7 +3934,6 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
         self.ui.paint_tool_button.clicked.connect(lambda: self.app.paint_tool.run(toggle=False))
         self.ui.generate_ncc_button.clicked.connect(lambda: self.app.ncclear_tool.run(toggle=False))
         self.ui.pp_geometry_name_cb.activated.connect(self.on_pp_changed)
-        self.ui.addtool_entry.returnPressed.connect(lambda: self.on_tool_add())
 
         self.ui.tipdia_entry.valueChanged.connect(self.update_cutz)
         self.ui.tipangle_entry.valueChanged.connect(self.update_cutz)
@@ -4010,6 +4009,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
 
         # I use lambda's because the connected functions have parameters that could be used in certain scenarios
         self.ui.addtool_btn.clicked.connect(lambda: self.on_tool_add())
+        self.ui.addtool_entry.returnPressed.connect(self.on_tool_add)
 
         self.ui.copytool_btn.clicked.connect(lambda: self.on_tool_copy())
         self.ui.deltool_btn.clicked.connect(lambda: self.on_tool_delete())
@@ -4062,6 +4062,11 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
         except (TypeError, AttributeError):
             pass
 
+        try:
+            self.ui.addtool_entry.returnPressed.disconnect()
+        except (TypeError, AttributeError):
+            pass
+
         try:
             self.ui.copytool_btn.clicked.disconnect()
         except (TypeError, AttributeError):
@@ -4103,59 +4108,26 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
 
         self.units = self.app.defaults['units'].upper()
 
-        # if a Tool diameter entered is a char instead a number the final message of Tool adding is changed
-        # because the Default value for Tool is used.
-        change_message = False
-
         if dia is not None:
             tooldia = dia
         else:
-            try:
-                tooldia = float(self.ui.addtool_entry.get_value())
-            except ValueError:
-                # try to convert comma to decimal point. if it's still not working error message and return
-                try:
-                    tooldia = float(self.ui.addtool_entry.get_value().replace(',', '.'))
-                except ValueError:
-                    change_message = True
-                    tooldia = float(self.options["cnctooldia"][0])
-
-            if tooldia is None:
-                self.build_ui()
-                self.app.inform.emit('[ERROR_NOTCL] %s' %
-                                     _("Please enter the desired tool diameter in Float format."))
-                return
+            tooldia = float(self.ui.addtool_entry.get_value())
 
         # construct a list of all 'tooluid' in the self.tools
-        tool_uid_list = []
-        for tooluid_key in self.tools:
-            tool_uid_item = int(tooluid_key)
-            tool_uid_list.append(tool_uid_item)
+        # tool_uid_list = list()
+        # for tooluid_key in self.tools:
+        #     tool_uid_list.append(int(tooluid_key))
+        tool_uid_list = [int(tooluid_key) for tooluid_key in self.tools]
 
         # find maximum from the temp_uid, add 1 and this is the new 'tooluid'
-        if not tool_uid_list:
-            max_uid = 0
-        else:
-            max_uid = max(tool_uid_list)
+        max_uid = max(tool_uid_list) if tool_uid_list else 0
         self.tooluid = max_uid + 1
 
         tooldia = float('%.*f' % (self.decimals, tooldia))
 
         # here we actually add the new tool; if there is no tool in the tool table we add a tool with default data
         # otherwise we add a tool with data copied from last tool
-        if not self.tools:
-            self.tools.update({
-                self.tooluid: {
-                    'tooldia': tooldia,
-                    'offset': 'Path',
-                    'offset_value': 0.0,
-                    'type': _('Rough'),
-                    'tool_type': 'C1',
-                    'data': deepcopy(self.default_data),
-                    'solid_geometry': self.solid_geometry
-                }
-            })
-        else:
+        if self.tools:
             last_data = self.tools[max_uid]['data']
             last_offset = self.tools[max_uid]['offset']
             last_offset_value = self.tools[max_uid]['offset_value']
@@ -4179,6 +4151,18 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
                     'solid_geometry': deepcopy(last_solid_geometry)
                 }
             })
+        else:
+            self.tools.update({
+                self.tooluid: {
+                    'tooldia': tooldia,
+                    'offset': 'Path',
+                    'offset_value': 0.0,
+                    'type': _('Rough'),
+                    'tool_type': 'C1',
+                    'data': deepcopy(self.default_data),
+                    'solid_geometry': self.solid_geometry
+                }
+            })
 
         self.tools[self.tooluid]['data']['name'] = self.options['name']
 
@@ -4192,12 +4176,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
             pass
         self.ser_attrs.append('tools')
 
-        if change_message is False:
-            self.app.inform.emit('[success] %s' % _("Tool added in Tool Table."))
-        else:
-            change_message = False
-            self.app.inform.emit('[WARNING_NOTCL] %s' %
-                                 _("Default Tool added. Wrong value format entered."))
+        self.app.inform.emit('[success] %s' % _("Tool added in Tool Table."))
         self.build_ui()
 
         # if there is no tool left in the Tools Table, enable the parameters GUI
@@ -5735,12 +5714,17 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
             self.tools = deepcopy(temp_tools_dict)
 
         # if there is a value in the new tool field then convert that one too
+        try:
+            self.ui.addtool_entry.returnPressed.disconnect()
+        except TypeError:
+            pass
         tooldia = self.ui.addtool_entry.get_value()
         if tooldia:
             tooldia *= factor
             tooldia = float('%.*f' % (self.decimals, tooldia))
 
             self.ui.addtool_entry.set_value(tooldia)
+        self.ui.addtool_entry.returnPressed.connect(self.on_tool_add)
 
         return factor
 

+ 9 - 0
README.md

@@ -9,6 +9,15 @@ CAD program, and create G-Code for Isolation routing.
 
 =================================================
 
+16.12.2019
+
+- in Geometry Editor added support for Jump To function such as that it works within the Editor Tools themselves. For now it works only in absolute jumps
+- modified the Jump To method such that now allows relative jump from the current mouse location
+- fixed the Defaults upgrade overwrting the new version number with the old one
+- fixed issue with clear_polygon3() - the one who makes 'lines' and fixed the NCC Tool
+- some small changes in the FlatCAMGeoemtry.on_tool_add() method
+- made sure that in Geometry Editor the self.app.mouse attribute is updated with the current mouse position (x, y)
+
 15.12.2019
 
 - fixed a bug that created a crash in special conditions; it's related to the QSettings in FlatCAMGui.py

+ 71 - 48
camlib.py

@@ -1381,9 +1381,10 @@ class Geometry(object):
                     inner_edges.append(y)
             # geoms += outer_edges + inner_edges
             for g in outer_edges + inner_edges:
-                geoms.insert(g)
-                if prog_plot:
-                    self.plot_temp_shapes(g)
+                if g and not g.is_empty:
+                    geoms.insert(g)
+                    if prog_plot:
+                        self.plot_temp_shapes(g)
 
         if prog_plot:
             self.temp_shapes.redraw()
@@ -1395,7 +1396,9 @@ class Geometry(object):
         # Optimization: Reduce lifts
         if connect:
             # log.debug("Reducing tool lifts...")
-            geoms = Geometry.paint_connect(geoms, polygon_to_clear, tooldia, steps_per_circle)
+            geoms_conn = Geometry.paint_connect(geoms, polygon_to_clear, tooldia, steps_per_circle)
+            if geoms_conn:
+                return geoms_conn
 
         return geoms
 
@@ -1419,6 +1422,9 @@ class Geometry(object):
         """
 
         # log.debug("camlib.clear_polygon3()")
+        if not isinstance(polygon, Polygon):
+            log.debug("camlib.Geometry.clear_polygon3() --> Not a Polygon but %s" % str(type(polygon)))
+            return None
 
         # ## The toolpaths
         # Index first and last points in paths
@@ -1433,41 +1439,43 @@ class Geometry(object):
         # Bounding box
         left, bot, right, top = polygon.bounds
 
-        margin_poly = polygon.buffer(-tooldia / 1.99999999, (int(steps_per_circle)))
+        try:
+            margin_poly = polygon.buffer(-tooldia / 1.99999999, (int(steps_per_circle)))
+        except Exception as e:
+            log.debug("camlib.Geometry.clear_polygon3() --> Could not buffer the Polygon")
+            return None
 
         # First line
-        y = top - tooldia / 1.99999999
-        while y > bot + tooldia / 1.999999999:
-            if self.app.abort_flag:
-                # graceful abort requested by the user
-                raise FlatCAMApp.GracefulException
+        try:
+            y = top - tooldia / 1.99999999
+            while y > bot + tooldia / 1.999999999:
+                if self.app.abort_flag:
+                    # graceful abort requested by the user
+                    raise FlatCAMApp.GracefulException
 
-            # provide the app with a way to process the GUI events when in a blocking loop
-            QtWidgets.QApplication.processEvents()
+                # provide the app with a way to process the GUI events when in a blocking loop
+                QtWidgets.QApplication.processEvents()
+
+                line = LineString([(left, y), (right, y)])
+                line = line.intersection(margin_poly)
+                lines_trimmed.append(line)
+                y -= tooldia * (1 - overlap)
+                if prog_plot:
+                    self.plot_temp_shapes(line)
+                    self.temp_shapes.redraw()
 
+            # Last line
+            y = bot + tooldia / 2
             line = LineString([(left, y), (right, y)])
             line = line.intersection(margin_poly)
-            lines_trimmed.append(line)
-            y -= tooldia * (1 - overlap)
-            if prog_plot:
-                self.plot_temp_shapes(line)
-                self.temp_shapes.redraw()
-
-        # Last line
-        y = bot + tooldia / 2
-        line = LineString([(left, y), (right, y)])
-        line = line.intersection(margin_poly)
-        for ll in line:
-            lines_trimmed.append(ll)
-            if prog_plot:
-                self.plot_temp_shapes(line)
-
-        # Combine
-        # linesgeo = unary_union(lines)
-
-        # Trim to the polygon
-        # margin_poly = polygon.buffer(-tooldia / 1.99999999, (int(steps_per_circle)))
-        # lines_trimmed = linesgeo.intersection(margin_poly)
+
+            for ll in line:
+                lines_trimmed.append(ll)
+                if prog_plot:
+                    self.plot_temp_shapes(line)
+        except Exception as e:
+            log.debug('camlib.Geometry.clear_polygon3() Processing poly --> %s' % str(e))
+            return None
 
         if prog_plot:
             self.temp_shapes.redraw()
@@ -1477,27 +1485,33 @@ class Geometry(object):
         # Add lines to storage
         try:
             for line in lines_trimmed:
-                geoms.insert(line)
+                if isinstance(line, LineString) or isinstance(line, LinearRing):
+                    geoms.insert(line)
+                else:
+                    log.debug("camlib.Geometry.clear_polygon3(). Not a line: %s" % str(type(line)))
         except TypeError:
             # in case lines_trimmed are not iterable (Linestring, LinearRing)
             geoms.insert(lines_trimmed)
 
         # Add margin (contour) to storage
         if contour:
-            if isinstance(margin_poly, Polygon):
-                geoms.insert(margin_poly.exterior)
-                if prog_plot:
-                    self.plot_temp_shapes(margin_poly.exterior)
-                for ints in margin_poly.interiors:
-                    geoms.insert(ints)
-                    if prog_plot:
-                        self.plot_temp_shapes(ints)
-            elif isinstance(margin_poly, MultiPolygon):
+            try:
                 for poly in margin_poly:
-                    geoms.insert(poly.exterior)
+                    if isinstance(poly, Polygon) and not poly.is_empty:
+                        geoms.insert(poly.exterior)
+                        if prog_plot:
+                            self.plot_temp_shapes(poly.exterior)
+                        for ints in poly.interiors:
+                            geoms.insert(ints)
+                            if prog_plot:
+                                self.plot_temp_shapes(ints)
+            except TypeError:
+                if isinstance(margin_poly, Polygon) and not margin_poly.is_empty:
+                    marg_ext = margin_poly.exterior
+                    geoms.insert(marg_ext)
                     if prog_plot:
-                        self.plot_temp_shapes(poly.exterior)
-                    for ints in poly.interiors:
+                        self.plot_temp_shapes(margin_poly.exterior)
+                    for ints in margin_poly.interiors:
                         geoms.insert(ints)
                         if prog_plot:
                             self.plot_temp_shapes(ints)
@@ -1508,7 +1522,9 @@ class Geometry(object):
         # Optimization: Reduce lifts
         if connect:
             # log.debug("Reducing tool lifts...")
-            geoms = Geometry.paint_connect(geoms, polygon, tooldia, steps_per_circle)
+            geoms_conn = Geometry.paint_connect(geoms, polygon, tooldia, steps_per_circle)
+            if geoms_conn:
+                return geoms_conn
 
         return geoms
 
@@ -1581,8 +1597,14 @@ class Geometry(object):
         optimized_paths.get_points = get_pts
         path_count = 0
         current_pt = (0, 0)
-        pt, geo = storage.nearest(current_pt)
+        try:
+            pt, geo = storage.nearest(current_pt)
+        except StopIteration:
+            log.debug("camlib.Geometry.paint_connect(). Storage empty")
+            return None
+
         storage.remove(geo)
+
         geo = LineString(geo)
         current_pt = geo.coords[-1]
         try:
@@ -1592,6 +1614,7 @@ class Geometry(object):
 
                 pt, candidate = storage.nearest(current_pt)
                 storage.remove(candidate)
+
                 candidate = LineString(candidate)
 
                 # If last point in geometry is the nearest

+ 55 - 16
flatcamEditors/FlatCAMGeoEditor.py

@@ -1880,7 +1880,10 @@ class DrawTool(object):
         return ""
 
     def on_key(self, key):
-        return None
+
+        # Jump to coords
+        if key == QtCore.Qt.Key_J or key == 'J':
+            self.draw_app.app.on_jump_to()
 
     def utility_geometry(self, data=None):
         return None
@@ -1943,6 +1946,8 @@ class FCCircle(FCShapeTool):
         self.cursor = QtGui.QCursor(QtGui.QPixmap(self.draw_app.app.resource_location + '/aero_circle_geo.png'))
         QtGui.QGuiApplication.setOverrideCursor(self.cursor)
 
+        self.draw_app.app.jump_signal.connect(lambda x: self.draw_app.update_utility_geometry(data=x))
+
         self.draw_app.app.inform.emit(_("Click on Center point ..."))
         self.steps_per_circ = self.draw_app.app.defaults["geometry_circle_steps"]
 
@@ -1979,8 +1984,10 @@ class FCCircle(FCShapeTool):
         radius = distance(p1, p2)
         self.geometry = DrawToolShape(Point(p1).buffer(radius, int(self.steps_per_circ / 4)))
         self.complete = True
-        self.draw_app.app.inform.emit('[success] %s' %
-                                      _("Done. Adding Circle completed."))
+
+        self.draw_app.app.jump_signal.disconnect()
+
+        self.draw_app.app.inform.emit('[success] %s' % _("Done. Adding Circle completed."))
 
 
 class FCArc(FCShapeTool):
@@ -1994,7 +2001,7 @@ class FCArc(FCShapeTool):
             QtGui.QGuiApplication.restoreOverrideCursor()
         except Exception:
             pass
-        self.cursor = QtGui.QCursor(QtGui.QPixmap(self.draw_app.resource_location + '/aero_arc.png'))
+        self.cursor = QtGui.QCursor(QtGui.QPixmap(self.draw_app.app.resource_location + '/aero_arc.png'))
         QtGui.QGuiApplication.setOverrideCursor(self.cursor)
 
         self.draw_app.app.inform.emit(_("Click on Center point ..."))
@@ -2010,6 +2017,8 @@ class FCArc(FCShapeTool):
         # 132 = p1, p3, p2
         self.mode = "c12"  # Center, p1, p2
 
+        self.draw_app.app.jump_signal.connect(lambda x: self.draw_app.update_utility_geometry(data=x))
+
         self.steps_per_circ = self.draw_app.app.defaults["geometry_circle_steps"]
 
     def click(self, point):
@@ -2044,6 +2053,10 @@ class FCArc(FCShapeTool):
             self.direction = 'cw' if self.direction == 'ccw' else 'ccw'
             return _('Direction: %s') % self.direction.upper()
 
+        # Jump to coords
+        if key == QtCore.Qt.Key_J or key == 'J':
+            self.draw_app.app.on_jump_to()
+
         if key == 'M' or key == QtCore.Qt.Key_M:
             # delete the possible points made before this action; we want to start anew
             self.points[:] = []
@@ -2196,8 +2209,10 @@ class FCArc(FCShapeTool):
             self.geometry = DrawToolShape(LineString(arc(center, radius, startangle, stopangle,
                                                          self.direction, self.steps_per_circ)))
         self.complete = True
-        self.draw_app.app.inform.emit('[success] %s' %
-                                      _("Done. Arc completed."))
+
+        self.draw_app.app.jump_signal.disconnect()
+
+        self.draw_app.app.inform.emit('[success] %s' % _("Done. Arc completed."))
 
 
 class FCRectangle(FCShapeTool):
@@ -2217,6 +2232,8 @@ class FCRectangle(FCShapeTool):
         self.cursor = QtGui.QCursor(QtGui.QPixmap(self.draw_app.app.resource_location + '/aero.png'))
         QtGui.QGuiApplication.setOverrideCursor(self.cursor)
 
+        self.draw_app.app.jump_signal.connect(lambda x: self.draw_app.update_utility_geometry(data=x))
+
         self.draw_app.app.inform.emit(_("Click on 1st corner ..."))
 
     def click(self, point):
@@ -2251,8 +2268,9 @@ class FCRectangle(FCShapeTool):
         # self.geometry = LinearRing([p1, (p2[0], p1[1]), p2, (p1[0], p2[1])])
         self.geometry = DrawToolShape(Polygon([p1, (p2[0], p1[1]), p2, (p1[0], p2[1])]))
         self.complete = True
-        self.draw_app.app.inform.emit('[success] %s' %
-                                      _("Done. Rectangle completed."))
+
+        self.draw_app.app.jump_signal.disconnect()
+        self.draw_app.app.inform.emit('[success] %s' % _("Done. Rectangle completed."))
 
 
 class FCPolygon(FCShapeTool):
@@ -2272,6 +2290,8 @@ class FCPolygon(FCShapeTool):
         self.cursor = QtGui.QCursor(QtGui.QPixmap(self.draw_app.app.resource_location + '/aero.png'))
         QtGui.QGuiApplication.setOverrideCursor(self.cursor)
 
+        self.draw_app.app.jump_signal.connect(lambda x: self.draw_app.update_utility_geometry(data=x))
+
         self.draw_app.app.inform.emit(_("Click on 1st corner ..."))
 
     def click(self, point):
@@ -2307,10 +2327,16 @@ class FCPolygon(FCShapeTool):
         self.geometry = DrawToolShape(Polygon(self.points))
         self.draw_app.in_action = False
         self.complete = True
-        self.draw_app.app.inform.emit('[success] %s' %
-                                      _("Done. Polygon completed."))
+
+        self.draw_app.app.jump_signal.disconnect()
+
+        self.draw_app.app.inform.emit('[success] %s' % _("Done. Polygon completed."))
 
     def on_key(self, key):
+        # Jump to coords
+        if key == QtCore.Qt.Key_J or key == 'J':
+            self.draw_app.app.on_jump_to()
+
         if key == 'Backspace' or key == QtCore.Qt.Key_Backspace:
             if len(self.points) > 0:
                 self.points = self.points[0:-1]
@@ -2336,6 +2362,8 @@ class FCPath(FCPolygon):
         self.cursor = QtGui.QCursor(QtGui.QPixmap(self.draw_app.app.resource_location + '/aero_path5.png'))
         QtGui.QGuiApplication.setOverrideCursor(self.cursor)
 
+        self.draw_app.app.jump_signal.connect(lambda x: self.draw_app.update_utility_geometry(data=x))
+
     def make(self):
         self.geometry = DrawToolShape(LineString(self.points))
         self.name = 'path'
@@ -2347,6 +2375,9 @@ class FCPath(FCPolygon):
 
         self.draw_app.in_action = False
         self.complete = True
+
+        self.draw_app.app.jump_signal.disconnect()
+
         self.draw_app.app.inform.emit('[success] %s' % _("Done. Path completed."))
 
     def utility_geometry(self, data=None):
@@ -2358,6 +2389,10 @@ class FCPath(FCPolygon):
         return None
 
     def on_key(self, key):
+        # Jump to coords
+        if key == QtCore.Qt.Key_J or key == 'J':
+            self.draw_app.app.on_jump_to()
+
         if key == 'Backspace' or key == QtCore.Qt.Key_Backspace:
             if len(self.points) > 0:
                 self.points = self.points[0:-1]
@@ -3800,6 +3835,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
 
         self.snap_x = x
         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;   "
@@ -3817,12 +3853,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
         if event.button == 1 and event_is_dragging and isinstance(self.active_tool, FCEraser):
             pass
         else:
-            # ### Utility geometry (animated) ###
-            geo = self.active_tool.utility_geometry(data=(x, y))
-            if isinstance(geo, DrawToolShape) and geo.geo is not None:
-                # Remove any previous utility shape
-                self.tool_shape.clear(update=True)
-                self.draw_utility_geometry(geo=geo)
+            self.update_utility_geometry(data=(x, y))
 
         # ### Selection area on canvas section ###
         dx = pos[0] - self.pos[0]
@@ -3839,6 +3870,14 @@ class FlatCAMGeoEditor(QtCore.QObject):
         else:
             self.app.selection_type = None
 
+    def update_utility_geometry(self, data):
+        # ### Utility geometry (animated) ###
+        geo = self.active_tool.utility_geometry(data=data)
+        if isinstance(geo, DrawToolShape) and geo.geo is not None:
+            # Remove any previous utility shape
+            self.tool_shape.clear(update=True)
+            self.draw_utility_geometry(geo=geo)
+
     def on_geo_click_release(self, event):
         if self.app.is_legacy is False:
             event_pos = event.pos

+ 10 - 7
flatcamEditors/FlatCAMGrbEditor.py

@@ -4530,13 +4530,7 @@ class FlatCAMGrbEditor(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))
 
-        # # ## Utility geometry (animated)
-        geo = self.active_tool.utility_geometry(data=(x, y))
-
-        if isinstance(geo, DrawToolShape) and geo.geo is not None:
-            # Remove any previous utility shape
-            self.tool_shape.clear(update=True)
-            self.draw_utility_geometry(geo=geo)
+        self.update_utility_geometry(data=(x, y))
 
         # # ## Selection area on canvas section # ##
         if event_is_dragging == 1 and event.button == 1:
@@ -4558,6 +4552,15 @@ class FlatCAMGrbEditor(QtCore.QObject):
         else:
             self.app.selection_type = None
 
+    def update_utility_geometry(self, data):
+        # # ## Utility geometry (animated)
+        geo = self.active_tool.utility_geometry(data=data)
+
+        if isinstance(geo, DrawToolShape) and geo.geo is not None:
+            # Remove any previous utility shape
+            self.tool_shape.clear(update=True)
+            self.draw_utility_geometry(geo=geo)
+
     def draw_utility_geometry(self, geo):
         if type(geo.geo) == list:
             for el in geo.geo:

+ 66 - 0
flatcamGUI/GUIElements.py

@@ -2230,6 +2230,72 @@ class Dialog_box(QtWidgets.QWidget):
             self.readyToEdit = True
 
 
+class DialogBoxRadio(QtWidgets.QDialog):
+    def __init__(self, title=None, label=None, icon=None, initial_text=None):
+        """
+
+        :param title: string with the window title
+        :param label: string with the message inside the dialog box
+        """
+        super(DialogBoxRadio, self).__init__()
+        if initial_text is None:
+            self.location = str((0, 0))
+        else:
+            self.location = initial_text
+
+        self.ok = False
+
+        self.setWindowIcon(icon)
+        self.setWindowTitle(str(title))
+
+        self.form = QtWidgets.QFormLayout(self)
+
+        self.form.addRow(QtWidgets.QLabel(''))
+
+        self.wdg_label = QtWidgets.QLabel('<b>%s</b>' % str(label))
+        self.form.addRow(self.wdg_label)
+
+        self.ref_label = QtWidgets.QLabel('%s:' % _("Reference"))
+        self.ref_label.setToolTip(
+            _("The reference can be:\n"
+              "- Absolute -> the reference point is point (0,0)\n"
+              "- Relative -> the reference point is the mouse position before Jump")
+        )
+        self.ref_radio = RadioSet([
+            {"label": _("Abs"), "value": "abs"},
+            {"label": _("Relative"), "value": "rel"}
+        ], orientation='horizontal', stretch=False)
+        self.ref_radio.set_value('abs')
+        self.form.addRow(self.ref_label, self.ref_radio)
+
+        self.loc_label = QtWidgets.QLabel('<b>%s:</b>' % _("Location"))
+        self.loc_label.setToolTip(
+            _("The Location value is a tuple (x,y).\n"
+              "If the reference is Absolute then the Jump will be at the position (x,y).\n"
+              "If the reference is Relative then the Jump will be at the (x,y) distance\n"
+              "from the current mouse location point.")
+        )
+        self.lineEdit = EvalEntry()
+        self.lineEdit.setText(str(self.location).replace('(', '').replace(')', ''))
+        self.form.addRow(self.loc_label, self.lineEdit)
+
+        self.button_box = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel,
+                                                     Qt.Horizontal, parent=self)
+        self.form.addRow(self.button_box)
+
+        self.button_box.accepted.connect(self.accept)
+        self.button_box.rejected.connect(self.reject)
+
+        self.readyToEdit = True
+
+        if self.exec_() == QtWidgets.QDialog.Accepted:
+            self.ok = True
+            self.location = self.lineEdit.text()
+            self.reference = self.ref_radio.get_value()
+        else:
+            self.ok = False
+
+
 class _BrowserTextEdit(QTextEdit):
 
     def __init__(self, version):

+ 44 - 25
flatcamTools/ToolNonCopperClear.py

@@ -1811,7 +1811,38 @@ class NonCopperClear(FlatCAMTool, Gerber):
                                 # graceful abort requested by the user
                                 raise FlatCAMApp.GracefulException
                             if p is not None:
+                                poly_processed = list()
                                 try:
+                                    for pol in p:
+                                        if pol is not None and isinstance(pol, Polygon):
+                                            if ncc_method == 'standard':
+                                                cp = self.clear_polygon(pol, tool,
+                                                                        self.grb_circle_steps,
+                                                                        overlap=overlap, contour=contour,
+                                                                        connect=connect,
+                                                                        prog_plot=prog_plot)
+                                            elif ncc_method == 'seed':
+                                                cp = self.clear_polygon2(pol, tool,
+                                                                         self.grb_circle_steps,
+                                                                         overlap=overlap, contour=contour,
+                                                                         connect=connect,
+                                                                         prog_plot=prog_plot)
+                                            else:
+                                                cp = self.clear_polygon3(pol, tool,
+                                                                         self.grb_circle_steps,
+                                                                         overlap=overlap, contour=contour,
+                                                                         connect=connect,
+                                                                         prog_plot=prog_plot)
+                                            if cp:
+                                                cleared_geo += list(cp.get_objects())
+                                                poly_processed.append(True)
+                                            else:
+                                                poly_processed.append(False)
+                                                log.warning("Polygon in MultiPolygon can not be cleared.")
+                                        else:
+                                            log.warning("Geo in Iterable can not be cleared beacuse it is not Polygon. "
+                                                        "It is: %s" % str(type(pol)))
+                                except TypeError:
                                     if isinstance(p, Polygon):
                                         if ncc_method == 'standard':
                                             cp = self.clear_polygon(p, tool, self.grb_circle_steps,
@@ -1827,32 +1858,20 @@ class NonCopperClear(FlatCAMTool, Gerber):
                                                                      prog_plot=prog_plot)
                                         if cp:
                                             cleared_geo += list(cp.get_objects())
-                                    elif isinstance(p, MultiPolygon):
-                                        for pol in p:
-                                            if pol is not None:
-                                                if ncc_method == 'standard':
-                                                    cp = self.clear_polygon(pol, tool,
-                                                                            self.grb_circle_steps,
-                                                                            overlap=overlap, contour=contour,
-                                                                            connect=connect,
-                                                                            prog_plot=prog_plot)
-                                                elif ncc_method == 'seed':
-                                                    cp = self.clear_polygon2(pol, tool,
-                                                                             self.grb_circle_steps,
-                                                                             overlap=overlap, contour=contour,
-                                                                             connect=connect,
-                                                                             prog_plot=prog_plot)
-                                                else:
-                                                    cp = self.clear_polygon3(pol, tool,
-                                                                             self.grb_circle_steps,
-                                                                             overlap=overlap, contour=contour,
-                                                                             connect=connect,
-                                                                             prog_plot=prog_plot)
-                                                if cp:
-                                                    cleared_geo += list(cp.get_objects())
-                                except Exception as e:
-                                    log.warning("Polygon can not be cleared. %s" % str(e))
+                                            poly_processed.append(True)
+                                        else:
+                                            poly_processed.append(False)
+                                            log.warning("Polygon can not be cleared.")
+                                    else:
+                                        log.warning("Geo can not be cleared because it is: %s" % str(type(p)))
+
+                                p_cleared = poly_processed.count(True)
+                                p_not_cleared = poly_processed.count(False)
+
+                                if p_not_cleared:
                                     app_obj.poly_not_cleared = True
+
+                                if p_cleared == 0:
                                     continue
 
                                 pol_nr += 1