Bläddra i källkod

- added the Exclusion zones processing to Excellon GCode generation
- fixed a non frequent plotting problem for CNCJob objects made out of Excellon objects

Marius Stanciu 5 år sedan
förälder
incheckning
51c9023bbe
4 ändrade filer med 381 tillägg och 51 borttagningar
  1. 2 1
      AppGUI/MainGUI.py
  2. 5 0
      CHANGELOG.md
  3. 231 34
      Common.py
  4. 143 16
      camlib.py

+ 2 - 1
AppGUI/MainGUI.py

@@ -3404,7 +3404,8 @@ class MainGUI(QtWidgets.QMainWindow):
             elif modifiers == QtCore.Qt.NoModifier:
             elif modifiers == QtCore.Qt.NoModifier:
                 if key == QtCore.Qt.Key_Escape or key == 'Escape':
                 if key == QtCore.Qt.Key_Escape or key == 'Escape':
                     sel_obj = self.app.collection.get_active()
                     sel_obj = self.app.collection.get_active()
-                    assert sel_obj.kind == 'geometry', "Expected a Geometry Object, got %s" % type(sel_obj)
+                    assert sel_obj.kind == 'geometry' or sel_obj.kind == 'excellon', \
+                        "Expected a Geometry or Excellon Object, got %s" % type(sel_obj)
 
 
                     sel_obj.area_disconnect()
                     sel_obj.area_disconnect()
                     return
                     return

+ 5 - 0
CHANGELOG.md

@@ -7,6 +7,11 @@ CHANGELOG for FlatCAM beta
 
 
 =================================================
 =================================================
 
 
+21.05.2020
+
+- added the Exclusion zones processing to Excellon GCode generation
+- fixed a non frequent plotting problem for CNCJob objects made out of Excellon objects
+
 19.05.2020
 19.05.2020
 
 
 - updated the Italian language (translation incomplete)
 - updated the Italian language (translation incomplete)

+ 231 - 34
Common.py

@@ -12,11 +12,13 @@
 # ##########################################################
 # ##########################################################
 from PyQt5 import QtCore
 from PyQt5 import QtCore
 
 
-from shapely.geometry import Polygon, MultiPolygon
+from shapely.geometry import Polygon, MultiPolygon, Point, LineString
 
 
 from AppGUI.VisPyVisuals import ShapeCollection
 from AppGUI.VisPyVisuals import ShapeCollection
 from AppTool import AppTool
 from AppTool import AppTool
 
 
+from copy import deepcopy
+
 import numpy as np
 import numpy as np
 
 
 import gettext
 import gettext
@@ -167,8 +169,8 @@ class ExclusionAreas(QtCore.QObject):
         {
         {
             "obj_type":   string ("excellon" or "geometry")   <- self.obj_type
             "obj_type":   string ("excellon" or "geometry")   <- self.obj_type
             "shape":      Shapely polygon
             "shape":      Shapely polygon
-            "strategy":   string ("over" or "around")         <- self.strategy
-            "overz":      float                               <- self.over_z
+            "strategy":   string ("over" or "around")         <- self.strategy_button
+            "overz":      float                               <- self.over_z_button
         }
         }
         '''
         '''
         self.exclusion_areas_storage = []
         self.exclusion_areas_storage = []
@@ -178,9 +180,9 @@ class ExclusionAreas(QtCore.QObject):
         self.solid_geometry = []
         self.solid_geometry = []
         self.obj_type = None
         self.obj_type = None
 
 
-        self.shape_type = 'square'  # TODO use the self.app.defaults when made general (not in Geo object Pref UI)
-        self.over_z = 0.1
-        self.strategy = None
+        self.shape_type_button = None
+        self.over_z_button = None
+        self.strategy_button = None
         self.cnc_button = None
         self.cnc_button = None
 
 
     def on_add_area_click(self, shape_button, overz_button, strategy_radio, cnc_button, solid_geo, obj_type):
     def on_add_area_click(self, shape_button, overz_button, strategy_radio, cnc_button, solid_geo, obj_type):
@@ -188,21 +190,25 @@ class ExclusionAreas(QtCore.QObject):
 
 
         :param shape_button:    a FCButton that has the value for the shape
         :param shape_button:    a FCButton that has the value for the shape
         :param overz_button:    a FCDoubleSpinner that holds the Over Z value
         :param overz_button:    a FCDoubleSpinner that holds the Over Z value
-        :param strategy_radio:  a RadioSet button with the strategy value
+        :param strategy_radio:  a RadioSet button with the strategy_button value
         :param cnc_button:      a FCButton in Object UI that when clicked the CNCJob is created
         :param cnc_button:      a FCButton in Object UI that when clicked the CNCJob is created
                                 We have a reference here so we can change the color signifying that exclusion areas are
                                 We have a reference here so we can change the color signifying that exclusion areas are
                                 available.
                                 available.
         :param solid_geo:       reference to the object solid geometry for which we add exclusion areas
         :param solid_geo:       reference to the object solid geometry for which we add exclusion areas
-        :param obj_type:        Type of FlatCAM object that called this method
-        :type obj_type:         String: "excellon" or "geometry"
-        :return:
+        :param obj_type:        Type of FlatCAM object that called this method. String: "excellon" or "geometry"
+        :type obj_type:         str
+        :return:                None
         """
         """
         self.app.inform.emit('[WARNING_NOTCL] %s' % _("Click the start point of the area."))
         self.app.inform.emit('[WARNING_NOTCL] %s' % _("Click the start point of the area."))
         self.app.call_source = 'geometry'
         self.app.call_source = 'geometry'
 
 
-        self.shape_type = shape_button.get_value()
-        self.over_z = overz_button.get_value()
-        self.strategy = strategy_radio.get_value()
+        self.shape_type_button = shape_button
+
+        # TODO use the self.app.defaults when made general (not in Geo object Pref UI)
+        # self.shape_type_button.set_value('square')
+
+        self.over_z_button = overz_button
+        self.strategy_button = strategy_radio
         self.cnc_button = cnc_button
         self.cnc_button = cnc_button
 
 
         self.solid_geometry = solid_geo
         self.solid_geometry = solid_geo
@@ -240,11 +246,11 @@ class ExclusionAreas(QtCore.QObject):
 
 
         x1, y1 = curr_pos[0], curr_pos[1]
         x1, y1 = curr_pos[0], curr_pos[1]
 
 
-        # shape_type = self.ui.area_shape_radio.get_value()
+        # shape_type_button = self.ui.area_shape_radio.get_value()
 
 
         # do clear area only for left mouse clicks
         # do clear area only for left mouse clicks
         if event.button == 1:
         if event.button == 1:
-            if self.shape_type == "square":
+            if self.shape_type_button.get_value() == "square":
                 if self.first_click is False:
                 if self.first_click is False:
                     self.first_click = True
                     self.first_click = True
                     self.app.inform.emit('[WARNING_NOTCL] %s' % _("Click the end point of the area."))
                     self.app.inform.emit('[WARNING_NOTCL] %s' % _("Click the end point of the area."))
@@ -268,14 +274,14 @@ class ExclusionAreas(QtCore.QObject):
                     # {
                     # {
                     #     "obj_type":   string("excellon" or "geometry") < - self.obj_type
                     #     "obj_type":   string("excellon" or "geometry") < - self.obj_type
                     #     "shape":      Shapely polygon
                     #     "shape":      Shapely polygon
-                    #     "strategy":   string("over" or "around") < - self.strategy
-                    #     "overz":      float < - self.over_z
+                    #     "strategy_button":   string("over" or "around") < - self.strategy_button
+                    #     "overz":      float < - self.over_z_button
                     # }
                     # }
                     new_el = {
                     new_el = {
                         "obj_type":     self.obj_type,
                         "obj_type":     self.obj_type,
                         "shape":        new_rectangle,
                         "shape":        new_rectangle,
-                        "strategy":     self.strategy,
-                        "overz":        self.over_z
+                        "strategy":     self.strategy_button.get_value(),
+                        "overz":        self.over_z_button.get_value()
                     }
                     }
                     self.exclusion_areas_storage.append(new_el)
                     self.exclusion_areas_storage.append(new_el)
 
 
@@ -305,7 +311,7 @@ class ExclusionAreas(QtCore.QObject):
                 return ""
                 return ""
         elif event.button == right_button and self.mouse_is_dragging is False:
         elif event.button == right_button and self.mouse_is_dragging is False:
 
 
-            shape_type = self.shape_type
+            shape_type = self.shape_type_button.get_value()
 
 
             if shape_type == "square":
             if shape_type == "square":
                 self.first_click = False
                 self.first_click = False
@@ -326,17 +332,19 @@ class ExclusionAreas(QtCore.QObject):
                         pol = Polygon(self.points)
                         pol = Polygon(self.points)
                         # do not add invalid polygons even if they are drawn by utility geometry
                         # do not add invalid polygons even if they are drawn by utility geometry
                         if pol.is_valid:
                         if pol.is_valid:
-                            # {
-                            #     "obj_type":   string("excellon" or "geometry") < - self.obj_type
-                            #     "shape":      Shapely polygon
-                            #     "strategy":   string("over" or "around") < - self.strategy
-                            #     "overz":      float < - self.over_z
-                            # }
+                            """
+                            {
+                                "obj_type":   string("excellon" or "geometry") < - self.obj_type
+                                "shape":      Shapely polygon
+                                "strategy":   string("over" or "around") < - self.strategy_button
+                                "overz":      float < - self.over_z_button
+                            }
+                            """
                             new_el = {
                             new_el = {
                                 "obj_type": self.obj_type,
                                 "obj_type": self.obj_type,
                                 "shape": pol,
                                 "shape": pol,
-                                "strategy": self.strategy,
-                                "overz": self.over_z
+                                "strategy": self.strategy_button.get_value(),
+                                "overz": self.over_z_button.get_value()
                             }
                             }
                             self.exclusion_areas_storage.append(new_el)
                             self.exclusion_areas_storage.append(new_el)
 
 
@@ -382,9 +390,9 @@ class ExclusionAreas(QtCore.QObject):
             if len(self.exclusion_areas_storage) == 0:
             if len(self.exclusion_areas_storage) == 0:
                 return
                 return
 
 
-            self.app.inform.emit(
-                "[success] %s" % _("Exclusion areas added. Checking overlap with the object geometry ..."))
-
+            # since the exclusion areas should apply to all objects in the app collection, this check is limited to
+            # only the current object therefore it will not guarantee success
+            self.app.inform.emit("%s" % _("Exclusion areas added. Checking overlap with the object geometry ..."))
             for el in self.exclusion_areas_storage:
             for el in self.exclusion_areas_storage:
                 if el["shape"].intersects(MultiPolygon(self.solid_geometry)):
                 if el["shape"].intersects(MultiPolygon(self.solid_geometry)):
                     self.on_clear_area_click()
                     self.on_clear_area_click()
@@ -406,8 +414,6 @@ class ExclusionAreas(QtCore.QObject):
             )
             )
 
 
             self.e_shape_modified.emit()
             self.e_shape_modified.emit()
-            for k in self.exclusion_areas_storage:
-                print(k)
 
 
     def area_disconnect(self):
     def area_disconnect(self):
         if self.app.is_legacy is False:
         if self.app.is_legacy is False:
@@ -436,7 +442,7 @@ class ExclusionAreas(QtCore.QObject):
 
 
     # called on mouse move
     # called on mouse move
     def on_mouse_move(self, event):
     def on_mouse_move(self, event):
-        shape_type = self.shape_type
+        shape_type = self.shape_type_button.get_value()
 
 
         if self.app.is_legacy is False:
         if self.app.is_legacy is False:
             event_pos = event.pos
             event_pos = event.pos
@@ -573,3 +579,194 @@ class ExclusionAreas(QtCore.QObject):
             self.cnc_button.setToolTip('%s' % _("Generate the CNC Job object."))
             self.cnc_button.setToolTip('%s' % _("Generate the CNC Job object."))
 
 
             self.app.inform.emit('[success] %s' % _("All exclusion zones deleted."))
             self.app.inform.emit('[success] %s' % _("All exclusion zones deleted."))
+
+    def travel_coordinates(self, start_point, end_point, tooldia):
+        """
+        WIll create a path the go around the exclusion areas on the shortest path
+
+        :param start_point:     X,Y coordinates for the start point of the travel line
+        :type start_point:      tuple
+        :param end_point:       X,Y coordinates for the destination point of the travel line
+        :type end_point:        tuple
+        :param tooldia:         THe tool diameter used and which generates the travel lines
+        :type tooldia           float
+        :return:                A list of x,y tuples that describe the avoiding path
+        :rtype:                 list
+        """
+
+        ret_list = []
+
+        # Travel lines: rapids. Should not pass through Exclusion areas
+        travel_line = LineString([start_point, end_point])
+        origin_point = Point(start_point)
+
+        buffered_storage = []
+        # add a little something to the half diameter, to make sure that we really don't enter in the exclusion zones
+        buffered_distance = (tooldia / 2.0) + (0.1 if self.app.defaults['units'] == 'MM' else 0.00393701)
+
+        for area in self.exclusion_areas_storage:
+            new_area = deepcopy(area)
+            new_area['shape'] = area['shape'].buffer(buffered_distance, join_style=2)
+            buffered_storage.append(new_area)
+
+        # sort the Exclusion areas from the closest to the start_point to the farthest
+        tmp = []
+        for area in buffered_storage:
+            dist = Point(start_point).distance(area['shape'])
+            tmp.append((dist, area))
+        tmp.sort(key=lambda k: k[0])
+
+        sorted_area_storage = [k[1] for k in tmp]
+
+        # process the ordered exclusion areas list
+        for area in sorted_area_storage:
+            outline = area['shape'].exterior
+            if travel_line.intersects(outline):
+                intersection_pts = travel_line.intersection(outline)
+
+                if isinstance(intersection_pts, Point):
+                    # it's just a touch, continue
+                    continue
+
+                entry_pt = nearest_point(origin_point, intersection_pts)
+                exit_pt = farthest_point(origin_point, intersection_pts)
+
+                if area['strategy'] == 'around':
+                    full_vertex_points = [Point(x) for x in list(outline.coords)]
+
+                    # the last coordinate in outline, a LinearRing, is the closing one
+                    # therefore a duplicate of the first one; discard it
+                    vertex_points = full_vertex_points[:-1]
+
+                    # dist_from_entry = [(entry_pt.distance(vt), vertex_points.index(vt)) for vt in vertex_points]
+                    # closest_point_entry = nsmallest(1, dist_from_entry, key=lambda x: x[0])
+                    # start_idx = closest_point_entry[0][1]
+                    #
+                    # dist_from_exit = [(exit_pt.distance(vt), vertex_points.index(vt)) for vt in vertex_points]
+                    # closest_point_exit = nsmallest(1, dist_from_exit, key=lambda x: x[0])
+                    # end_idx = closest_point_exit[0][1]
+
+                    pts_line_entry = None
+                    pts_line_exit = None
+                    for i in range(len(full_vertex_points) - 1):
+                        line = LineString(
+                            [
+                                (full_vertex_points[i].x, full_vertex_points[i].y),
+                                (full_vertex_points[i + 1].x, full_vertex_points[i + 1].y)
+                            ]
+                        )
+                        if entry_pt.intersects(line) or entry_pt.almost_equals(Point(line.coords[0]), decimal=3) or \
+                                entry_pt.almost_equals(Point(line.coords[1]), decimal=3):
+                            pts_line_entry = [Point(x) for x in line.coords]
+
+                        if exit_pt.intersects(line) or exit_pt.almost_equals(Point(line.coords[0]), decimal=3) or \
+                                exit_pt.almost_equals(Point(line.coords[1]), decimal=3):
+                            pts_line_exit = [Point(x) for x in line.coords]
+
+                    closest_point_entry = nearest_point(entry_pt, pts_line_entry)
+                    start_idx = vertex_points.index(closest_point_entry)
+
+                    closest_point_exit = nearest_point(exit_pt, pts_line_exit)
+                    end_idx = vertex_points.index(closest_point_exit)
+
+                    # calculate possible paths: one clockwise the other counterclockwise on the exterior of the
+                    # exclusion area outline (Polygon.exterior)
+                    vp_len = len(vertex_points)
+                    if end_idx > start_idx:
+                        path_1 = vertex_points[start_idx:(end_idx + 1)]
+                        path_2 = [vertex_points[start_idx]]
+                        idx = start_idx
+                        for __ in range(vp_len):
+                            idx = idx - 1 if idx > 0 else (vp_len - 1)
+                            path_2.append(vertex_points[idx])
+                            if idx == end_idx:
+                                break
+                    else:
+                        path_1 = vertex_points[end_idx:(start_idx + 1)]
+                        path_2 = [vertex_points[end_idx]]
+                        idx = end_idx
+                        for __ in range(vp_len):
+                            idx = idx - 1 if idx > 0 else (vp_len - 1)
+                            path_2.append(vertex_points[idx])
+                            if idx == start_idx:
+                                break
+                        path_1.reverse()
+                        path_2.reverse()
+
+                    # choose the one with the lesser length
+                    length_path_1 = 0
+                    for i in range(len(path_1)):
+                        try:
+                            length_path_1 += path_1[i].distance(path_1[i + 1])
+                        except IndexError:
+                            pass
+
+                    length_path_2 = 0
+                    for i in range(len(path_2)):
+                        try:
+                            length_path_2 += path_2[i].distance(path_2[i + 1])
+                        except IndexError:
+                            pass
+
+                    path = path_1 if length_path_1 < length_path_2 else path_2
+
+                    # transform the list of Points into a list of Points coordinates
+                    path_coords = [[None, (p.x, p.y)] for p in path]
+                    ret_list += path_coords
+
+                else:
+                    path_coords = [[float(area['overz']), (entry_pt.x, entry_pt.y)], [None, (exit_pt.x, exit_pt.y)]]
+                    ret_list += path_coords
+
+                # create a new LineString to test again for possible other Exclusion zones
+                last_pt_in_path = path_coords[-1][1]
+                travel_line = LineString([last_pt_in_path, end_point])
+
+        ret_list.append([None, end_point])
+        return ret_list
+
+
+def farthest_point(origin, points_list):
+    """
+    Calculate the farthest Point in a list from another Point
+
+    :param origin:      Reference Point
+    :type origin:       Point
+    :param points_list: List of Points or a MultiPoint
+    :type points_list:  list
+    :return:            Farthest Point
+    :rtype:             Point
+    """
+    old_dist = 0
+    fartherst_pt = None
+
+    for pt in points_list:
+        dist = abs(origin.distance(pt))
+        if dist >= old_dist:
+            fartherst_pt = pt
+            old_dist = dist
+
+    return fartherst_pt
+
+
+def nearest_point(origin, points_list):
+    """
+    Calculate the nearest Point in a list from another Point
+
+    :param origin:      Reference Point
+    :type origin:       Point
+    :param points_list: List of Points or a MultiPoint
+    :type points_list:  list
+    :return:            Nearest Point
+    :rtype:             Point
+    """
+    old_dist = np.Inf
+    nearest_pt = None
+
+    for pt in points_list:
+        dist = abs(origin.distance(pt))
+        if dist <= old_dist:
+            nearest_pt = pt
+            old_dist = dist
+
+    return nearest_pt

+ 143 - 16
camlib.py

@@ -2630,16 +2630,17 @@ class CNCjob(Geometry):
 
 
     def generate_from_excellon_by_tool(self, exobj, tools="all", use_ui=False):
     def generate_from_excellon_by_tool(self, exobj, tools="all", use_ui=False):
         """
         """
-        Creates gcode for this object from an Excellon object
+        Creates Gcode for this object from an Excellon object
         for the specified tools.
         for the specified tools.
 
 
-        :param exobj: Excellon object to process
-        :type exobj: Excellon
-        :param tools: Comma separated tool names
-        :type: tools: str
-        :param use_ui: Bool, if True the method will use parameters set in UI
-        :return: None
-        :rtype: None
+        :param exobj:   Excellon object to process
+        :type exobj:    Excellon
+        :param tools:   Comma separated tool names
+        :type tools:    str
+        :param use_ui:  if True the method will use parameters set in UI
+        :type use_ui:   bool
+        :return:        None
+        :rtype:         None
         """
         """
 
 
         # create a local copy of the exobj.drills so it can be used for creating drill CCode geometry
         # create a local copy of the exobj.drills so it can be used for creating drill CCode geometry
@@ -2780,7 +2781,7 @@ class CNCjob(Geometry):
 
 
         self.app.inform.emit(_("Creating a list of points to drill..."))
         self.app.inform.emit(_("Creating a list of points to drill..."))
 
 
-        # Points (Group by tool)
+        # Points (Group by tool): a dictionary of shapely Point geo elements grouped by tool number
         points = {}
         points = {}
         for drill in exobj.drills:
         for drill in exobj.drills:
             if self.app.abort_flag:
             if self.app.abort_flag:
@@ -2795,6 +2796,17 @@ class CNCjob(Geometry):
 
 
         # log.debug("Found %d drills." % len(points))
         # log.debug("Found %d drills." % len(points))
 
 
+        # check if there are drill points in the exclusion areas.
+        # If we find any within the exclusion areas return 'fail'
+        for tool in points:
+            for pt in points[tool]:
+                for area in self.app.exc_areas.exclusion_areas_storage:
+                    pt_buf = pt.buffer(exobj.tools[tool]['C'] / 2.0)
+                    if pt_buf.within(area['shape']) or pt_buf.intersects(area['shape']):
+                        self.app.inform.emit("[ERROR_NOTCL] %s" % _("Failed. Drill points inside the exclusion zones."))
+                        return 'fail'
+
+        # this holds the resulting GCode
         self.gcode = []
         self.gcode = []
 
 
         self.f_plunge = self.app.defaults["excellon_f_plunge"]
         self.f_plunge = self.app.defaults["excellon_f_plunge"]
@@ -3042,7 +3054,41 @@ class CNCjob(Geometry):
                                     locx = locations[k][0]
                                     locx = locations[k][0]
                                     locy = locations[k][1]
                                     locy = locations[k][1]
 
 
-                                    gcode += self.doformat(p.rapid_code, x=locx, y=locy)
+                                    travels = self.app.exc_areas.travel_coordinates(start_point=(self.oldx, self.oldy),
+                                                                                    end_point=(locx, locy),
+                                                                                    tooldia=current_tooldia)
+                                    prev_z = None
+                                    for travel in travels:
+                                        locx = travel[1][0]
+                                        locy = travel[1][1]
+
+                                        if travel[0] is not None:
+                                            # move to next point
+                                            gcode += self.doformat(p.rapid_code, x=locx, y=locy)
+
+                                            # raise to safe Z (travel[0]) each time because safe Z may be different
+                                            self.z_move = travel[0]
+                                            gcode += self.doformat(p.lift_code, x=locx, y=locy)
+
+                                            # restore z_move
+                                            self.z_move = exobj.tools[tool]['data']['travelz']
+                                        else:
+                                            if prev_z is not None:
+                                                # move to next point
+                                                gcode += self.doformat(p.rapid_code, x=locx, y=locy)
+
+                                                # we assume that previously the z_move was altered therefore raise to
+                                                # the travel_z (z_move)
+                                                self.z_move = exobj.tools[tool]['data']['travelz']
+                                                gcode += self.doformat(p.lift_code, x=locx, y=locy)
+                                            else:
+                                                # move to next point
+                                                gcode += self.doformat(p.rapid_code, x=locx, y=locy)
+
+                                        # store prev_z
+                                        prev_z = travel[0]
+
+                                    # gcode += self.doformat(p.rapid_code, x=locx, y=locy)
 
 
                                     if self.multidepth and abs(self.z_cut) > abs(self.z_depthpercut):
                                     if self.multidepth and abs(self.z_cut) > abs(self.z_depthpercut):
                                         doc = deepcopy(self.z_cut)
                                         doc = deepcopy(self.z_cut)
@@ -3260,7 +3306,41 @@ class CNCjob(Geometry):
                                     locx = locations[k][0]
                                     locx = locations[k][0]
                                     locy = locations[k][1]
                                     locy = locations[k][1]
 
 
-                                    gcode += self.doformat(p.rapid_code, x=locx, y=locy)
+                                    travels = self.app.exc_areas.travel_coordinates(start_point=(self.oldx, self.oldy),
+                                                                                    end_point=(locx, locy),
+                                                                                    tooldia=current_tooldia)
+                                    prev_z = None
+                                    for travel in travels:
+                                        locx = travel[1][0]
+                                        locy = travel[1][1]
+
+                                        if travel[0] is not None:
+                                            # move to next point
+                                            gcode += self.doformat(p.rapid_code, x=locx, y=locy)
+
+                                            # raise to safe Z (travel[0]) each time because safe Z may be different
+                                            self.z_move = travel[0]
+                                            gcode += self.doformat(p.lift_code, x=locx, y=locy)
+
+                                            # restore z_move
+                                            self.z_move = exobj.tools[tool]['data']['travelz']
+                                        else:
+                                            if prev_z is not None:
+                                                # move to next point
+                                                gcode += self.doformat(p.rapid_code, x=locx, y=locy)
+
+                                                # we assume that previously the z_move was altered therefore raise to
+                                                # the travel_z (z_move)
+                                                self.z_move = exobj.tools[tool]['data']['travelz']
+                                                gcode += self.doformat(p.lift_code, x=locx, y=locy)
+                                            else:
+                                                # move to next point
+                                                gcode += self.doformat(p.rapid_code, x=locx, y=locy)
+
+                                        # store prev_z
+                                        prev_z = travel[0]
+
+                                    # gcode += self.doformat(p.rapid_code, x=locx, y=locy)
 
 
                                     if self.multidepth and abs(self.z_cut) > abs(self.z_depthpercut):
                                     if self.multidepth and abs(self.z_cut) > abs(self.z_depthpercut):
                                         doc = deepcopy(self.z_cut)
                                         doc = deepcopy(self.z_cut)
@@ -3429,7 +3509,41 @@ class CNCjob(Geometry):
                                 locx = point[0]
                                 locx = point[0]
                                 locy = point[1]
                                 locy = point[1]
 
 
-                                gcode += self.doformat(p.rapid_code, x=locx, y=locy)
+                                travels = self.app.exc_areas.travel_coordinates(start_point=(self.oldx, self.oldy),
+                                                                                end_point=(locx, locy),
+                                                                                tooldia=current_tooldia)
+                                prev_z = None
+                                for travel in travels:
+                                    locx = travel[1][0]
+                                    locy = travel[1][1]
+
+                                    if travel[0] is not None:
+                                        # move to next point
+                                        gcode += self.doformat(p.rapid_code, x=locx, y=locy)
+
+                                        # raise to safe Z (travel[0]) each time because safe Z may be different
+                                        self.z_move = travel[0]
+                                        gcode += self.doformat(p.lift_code, x=locx, y=locy)
+
+                                        # restore z_move
+                                        self.z_move = exobj.tools[tool]['data']['travelz']
+                                    else:
+                                        if prev_z is not None:
+                                            # move to next point
+                                            gcode += self.doformat(p.rapid_code, x=locx, y=locy)
+
+                                            # we assume that previously the z_move was altered therefore raise to
+                                            # the travel_z (z_move)
+                                            self.z_move = exobj.tools[tool]['data']['travelz']
+                                            gcode += self.doformat(p.lift_code, x=locx, y=locy)
+                                        else:
+                                            # move to next point
+                                            gcode += self.doformat(p.rapid_code, x=locx, y=locy)
+
+                                    # store prev_z
+                                    prev_z = travel[0]
+
+                                # gcode += self.doformat(p.rapid_code, x=locx, y=locy)
 
 
                                 if self.multidepth and abs(self.z_cut) > abs(self.z_depthpercut):
                                 if self.multidepth and abs(self.z_cut) > abs(self.z_depthpercut):
                                     doc = deepcopy(self.z_cut)
                                     doc = deepcopy(self.z_cut)
@@ -4827,14 +4941,27 @@ class CNCjob(Geometry):
                     # plot the geometry of Excellon objects
                     # plot the geometry of Excellon objects
                     if self.origin_kind == 'excellon':
                     if self.origin_kind == 'excellon':
                         try:
                         try:
-                            poly = Polygon(geo['geom'])
-                        except ValueError:
-                            # if the geos are travel lines it will enter into Exception
-                            poly = geo['geom'].buffer(distance=(tooldia / 1.99999999), resolution=self.steps_per_circle)
+                            if geo['kind'][0] == 'T':
+                                # if the geos are travel lines it will enter into Exception
+                                poly = geo['geom'].buffer(distance=(tooldia / 1.99999999),
+                                                          resolution=self.steps_per_circle)
+                            else:
+                                poly = Polygon(geo['geom'])
+
                             poly = poly.simplify(tool_tolerance)
                             poly = poly.simplify(tool_tolerance)
                         except Exception:
                         except Exception:
                             # deal here with unexpected plot errors due of LineStrings not valid
                             # deal here with unexpected plot errors due of LineStrings not valid
                             continue
                             continue
+
+                        # try:
+                        #     poly = Polygon(geo['geom'])
+                        # except ValueError:
+                        #     # if the geos are travel lines it will enter into Exception
+                        #     poly = geo['geom'].buffer(distance=(tooldia / 1.99999999), resolution=self.steps_per_circle)
+                        #     poly = poly.simplify(tool_tolerance)
+                        # except Exception:
+                        #     # deal here with unexpected plot errors due of LineStrings not valid
+                        #     continue
                     else:
                     else:
                         # plot the geometry of any objects other than Excellon
                         # plot the geometry of any objects other than Excellon
                         poly = geo['geom'].buffer(distance=(tooldia / 1.99999999), resolution=self.steps_per_circle)
                         poly = geo['geom'].buffer(distance=(tooldia / 1.99999999), resolution=self.steps_per_circle)