Просмотр исходного кода

- added a new parameter named 'End Move X,Y' for the Geometry and Excellon objects. Adding a tuple of coordinates in this field will control the X,Y position of the final move; not entering a value there will cause not to make an end move

Marius Stanciu 5 лет назад
Родитель
Сommit
8a6ada1984

+ 24 - 2
FlatCAMApp.py

@@ -643,6 +643,7 @@ class App(QtCore.QObject):
             "excellon_depthperpass": 0.7,
             "excellon_travelz": 2,
             "excellon_endz": 0.5,
+            "excellon_endxy": None,
             "excellon_feedrate_z": 300,
             "excellon_spindlespeed": 0,
             "excellon_dwell": False,
@@ -710,6 +711,8 @@ class App(QtCore.QObject):
             "geometry_toolchange": False,
             "geometry_toolchangez": 15.0,
             "geometry_endz": 15.0,
+            "geometry_endxy": None,
+
             "geometry_feedrate": 120,
             "geometry_feedrate_z": 60,
             "geometry_spindlespeed": 0,
@@ -1326,6 +1329,8 @@ class App(QtCore.QObject):
             "excellon_depthperpass": self.ui.excellon_defaults_form.excellon_opt_group.maxdepth_entry,
             "excellon_travelz": self.ui.excellon_defaults_form.excellon_opt_group.travelz_entry,
             "excellon_endz": self.ui.excellon_defaults_form.excellon_opt_group.endz_entry,
+            "excellon_endxy": self.ui.excellon_defaults_form.excellon_opt_group.endxy_entry,
+
             "excellon_feedrate_z": self.ui.excellon_defaults_form.excellon_opt_group.feedrate_z_entry,
             "excellon_spindlespeed": self.ui.excellon_defaults_form.excellon_opt_group.spindlespeed_entry,
             "excellon_dwell": self.ui.excellon_defaults_form.excellon_opt_group.dwell_cb,
@@ -1404,6 +1409,7 @@ class App(QtCore.QObject):
             "geometry_toolchange": self.ui.geometry_defaults_form.geometry_opt_group.toolchange_cb,
             "geometry_toolchangez": self.ui.geometry_defaults_form.geometry_opt_group.toolchangez_entry,
             "geometry_endz": self.ui.geometry_defaults_form.geometry_opt_group.endz_entry,
+            "geometry_endxy": self.ui.geometry_defaults_form.geometry_opt_group.endxy_entry,
             "geometry_depthperpass": self.ui.geometry_defaults_form.geometry_opt_group.depthperpass_entry,
             "geometry_multidepth": self.ui.geometry_defaults_form.geometry_opt_group.multidepth_cb,
 
@@ -5959,13 +5965,15 @@ class App(QtCore.QObject):
 
                       'excellon_cutz',  'excellon_travelz', "excellon_toolchangexy", 'excellon_offset',
                       'excellon_feedrate', 'excellon_feedrate_rapid', 'excellon_toolchangez',
-                      'excellon_tooldia', 'excellon_slot_tooldia', 'excellon_endz', "excellon_feedrate_probe",
+                      'excellon_tooldia', 'excellon_slot_tooldia', 'excellon_endz', 'excellon_endxy',
+                      "excellon_feedrate_probe",
                       "excellon_z_pdepth", "excellon_editor_newdia", "excellon_editor_lin_pitch",
                       "excellon_editor_slot_lin_pitch",
 
                       'geometry_cutz',  "geometry_depthperpass", 'geometry_travelz', 'geometry_feedrate',
                       'geometry_feedrate_rapid', "geometry_toolchangez", "geometry_feedrate_z",
-                      "geometry_toolchangexy", 'geometry_cnctooldia', 'geometry_endz', "geometry_z_pdepth",
+                      "geometry_toolchangexy", 'geometry_cnctooldia', 'geometry_endz', 'geometry_endxy',
+                      "geometry_z_pdepth",
                       "geometry_feedrate_probe", "geometry_startz",
 
                       'cncjob_tooldia',
@@ -6013,6 +6021,20 @@ class App(QtCore.QObject):
                     coords_xy[1] *= sfactor
                     self.defaults['geometry_toolchangexy'] = "%.*f, %.*f" % (self.decimals, coords_xy[0],
                                                                              self.decimals, coords_xy[1])
+                elif dim == 'excellon_endxy':
+                    coordinates = self.defaults["excellon_endxy"].split(",")
+                    end_coords_xy = [float(eval(a)) for a in coordinates if a != '']
+                    end_coords_xy[0] *= sfactor
+                    end_coords_xy[1] *= sfactor
+                    self.defaults['excellon_endxy'] = "%.*f, %.*f" % (self.decimals, end_coords_xy[0],
+                                                                      self.decimals, end_coords_xy[1])
+                elif dim == 'geometry_endxy':
+                    coordinates = self.defaults["geometry_endxy"].split(",")
+                    end_coords_xy = [float(eval(a)) for a in coordinates if a != '']
+                    end_coords_xy[0] *= sfactor
+                    end_coords_xy[1] *= sfactor
+                    self.defaults['geometry_endxy'] = "%.*f, %.*f" % (self.decimals, end_coords_xy[0],
+                                                                      self.decimals, end_coords_xy[1])
                 elif dim == 'geometry_cnctooldia':
                     if type(self.defaults["geometry_cnctooldia"]) == float:
                         tools_diameters = [self.defaults["geometry_cnctooldia"]]

+ 15 - 3
FlatCAMObj.py

@@ -2401,6 +2401,8 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
             "extracut": self.app.defaults["geometry_extracut"],
             "extracut_length":self.app.defaults["geometry_extracut_length"],
             "endz": 2.0,
+            "endxy": '',
+
             "startz": None,
             "offset": 0.0,
             "spindlespeed": 0,
@@ -2887,6 +2889,8 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
 
             "startz": self.ui.estartz_entry,
             "endz": self.ui.endz_entry,
+            "endxy": self.ui.endxy_entry,
+
             "offset": self.ui.offset_entry,
 
             "ppname_e": self.ui.pp_excellon_name_cb,
@@ -3744,6 +3748,7 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
             job_obj.z_toolchange = float(self.options["toolchangez"])
             job_obj.startz = float(self.options["startz"]) if self.options["startz"] else None
             job_obj.endz = float(self.options["endz"])
+            job_obj.xy_end = self.options["endxy"]
             job_obj.excellon_optimization_type = self.app.defaults["excellon_optimization_type"]
 
             tools_csv = ','.join(tools)
@@ -3987,6 +3992,8 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
             "extracut": False,
             "extracut_length": 0.1,
             "endz": 2.0,
+            "endxy": '',
+
             "startz": None,
             "toolchange": False,
             "toolchangez": 1.0,
@@ -4259,6 +4266,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
             "toolchange": self.ui.toolchangeg_cb,
             "toolchangez": self.ui.toolchangez_entry,
             "endz": self.ui.endz_entry,
+            "endxy": self.ui.endxy_entry,
             "cnctooldia": self.ui.addtool_entry
         })
 
@@ -4298,6 +4306,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
             "toolchange": None,
             "toolchangez": None,
             "endz": None,
+            "endxy": '',
             "spindlespeed": 0,
             "toolchangexy": None,
             "startz": None
@@ -5645,6 +5654,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
                 toolchangexy = tools_dict[tooluid_key]['data']["toolchangexy"]
                 startz = tools_dict[tooluid_key]['data']["startz"]
                 endz = tools_dict[tooluid_key]['data']["endz"]
+                endxy = self.options["endxy"]
                 spindlespeed = tools_dict[tooluid_key]['data']["spindlespeed"]
                 dwell = tools_dict[tooluid_key]['data']["dwell"]
                 dwelltime = tools_dict[tooluid_key]['data']["dwelltime"]
@@ -5670,7 +5680,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
                     feedrate=feedrate, feedrate_z=feedrate_z, feedrate_rapid=feedrate_rapid,
                     spindlespeed=spindlespeed, spindledir=spindledir, dwell=dwell, dwelltime=dwelltime,
                     multidepth=multidepth, depthpercut=depthpercut,
-                    extracut=extracut, extracut_length=extracut_length, startz=startz, endz=endz,
+                    extracut=extracut, extracut_length=extracut_length, startz=startz, endz=endz, endxy=endxy,
                     toolchange=toolchange, toolchangez=toolchangez, toolchangexy=toolchangexy,
                     pp_geometry_name=pp_geometry_name,
                     tool_no=tool_cnt)
@@ -5797,6 +5807,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
                 toolchangexy = tools_dict[tooluid_key]['data']["toolchangexy"]
                 startz = tools_dict[tooluid_key]['data']["startz"]
                 endz = tools_dict[tooluid_key]['data']["endz"]
+                endxy = self.options["endxy"]
                 spindlespeed = tools_dict[tooluid_key]['data']["spindlespeed"]
                 dwell = tools_dict[tooluid_key]['data']["dwell"]
                 dwelltime = tools_dict[tooluid_key]['data']["dwelltime"]
@@ -5822,7 +5833,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
                     feedrate=feedrate, feedrate_z=feedrate_z, feedrate_rapid=feedrate_rapid,
                     spindlespeed=spindlespeed, spindledir=spindledir, dwell=dwell, dwelltime=dwelltime,
                     multidepth=multidepth, depthpercut=depthpercut,
-                    extracut=extracut, extracut_length=extracut_length, startz=startz, endz=endz,
+                    extracut=extracut, extracut_length=extracut_length, startz=startz, endz=endz, endxy=endxy,
                     toolchange=toolchange, toolchangez=toolchangez, toolchangexy=toolchangexy,
                     pp_geometry_name=pp_geometry_name,
                     tool_no=tool_cnt)
@@ -5932,6 +5943,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
 
         startz = startz if startz is not None else self.options["startz"]
         endz = endz if endz is not None else float(self.options["endz"])
+        endxy = self.options["endxy"]
 
         toolchangez = toolchangez if toolchangez else float(self.options["toolchangez"])
         toolchangexy = toolchangexy if toolchangexy else self.options["toolchangexy"]
@@ -5981,7 +5993,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
                 spindlespeed=spindlespeed, dwell=dwell, dwelltime=dwelltime,
                 multidepth=multidepth, depthpercut=depthperpass,
                 toolchange=toolchange, toolchangez=toolchangez, toolchangexy=toolchangexy,
-                extracut=extracut, extracut_length=extracut_length, startz=startz, endz=endz,
+                extracut=extracut, extracut_length=extracut_length, startz=startz, endz=endz, endxy=endxy,
                 pp_geometry_name=ppname_g
             )
 

+ 1 - 0
README.md

@@ -12,6 +12,7 @@ CAD program, and create G-Code for Isolation routing.
 25.02.2020
 
 - fixed bug in Gerber parser: it tried to calculate a len() for a single element and not a list - a Gerber generated by Eagle exhibited this
+- added a new parameter named 'End Move X,Y' for the Geometry and Excellon objects. Adding a tuple of coordinates in this field will control the X,Y position of the final move; not entering a value there will cause not to make an end move
 
 20.02.2020
 

+ 24 - 3
camlib.py

@@ -2415,7 +2415,7 @@ class CNCjob(Geometry):
                  depthpercut=0.1, z_pdepth=-0.02,
                  spindlespeed=None, spindledir='CW', dwell=True, dwelltime=1000,
                  toolchangez=0.787402, toolchange_xy=[0.0, 0.0],
-                 endz=2.0,
+                 endz=2.0, endxy='',
                  segx=None,
                  segy=None,
                  steps_per_circle=None):
@@ -2447,6 +2447,7 @@ class CNCjob(Geometry):
 
         self.startz = None
         self.z_end = endz
+        self.xy_end = endxy
 
         self.multidepth = False
         self.z_depthpercut = depthpercut
@@ -2656,6 +2657,12 @@ class CNCjob(Geometry):
             log.debug("camlib.CNCJob.generate_from_excellon_by_tool() --> %s" % str(e))
             pass
 
+        self.xy_end = [float(eval(a)) for a in self.xy_end.split(",")]
+        if len(self.xy_end) < 2:
+            self.app.inform.emit('[ERROR]  %s' % _("The End Move X,Y field in Edit -> Preferences has to be "
+                                                   "in the format (x, y) but now there is only one value, not two."))
+            return 'fail'
+
         self.pp_excellon = self.app.preprocessors[self.pp_excellon_name]
         p = self.pp_excellon
 
@@ -3438,7 +3445,7 @@ class CNCjob(Geometry):
             spindlespeed=None, spindledir='CW', dwell=False, dwelltime=1.0,
             multidepth=False, depthpercut=None,
             toolchange=False, toolchangez=1.0, toolchangexy="0.0, 0.0", extracut=False, extracut_length=0.2,
-            startz=None, endz=2.0, pp_geometry_name=None, tool_no=1):
+            startz=None, endz=2.0, endxy='', pp_geometry_name=None, tool_no=1):
         """
         Algorithm to generate from multitool Geometry.
 
@@ -3472,6 +3479,7 @@ class CNCjob(Geometry):
         :param extracut_length:     Extra cut legth at the end of the path
         :param startz:
         :param endz:
+        :param endxy:
         :param pp_geometry_name:
         :param tool_no:
         :return:                    GCode - string
@@ -3511,6 +3519,12 @@ class CNCjob(Geometry):
         self.startz = float(startz) if startz is not None else None
         self.z_end = float(endz) if endz is not None else None
 
+        self.xy_end = [float(eval(a)) for a in endxy.split(",")]
+        if len(self.xy_end) < 2:
+            self.app.inform.emit('[ERROR]  %s' % _("The End Move X,Y field in Edit -> Preferences has to be "
+                                                   "in the format (x, y) but now there is only one value, not two."))
+            return 'fail'
+
         self.z_depthpercut = float(depthpercut) if depthpercut else None
         self.multidepth = multidepth
 
@@ -3747,7 +3761,7 @@ class CNCjob(Geometry):
             spindlespeed=None, spindledir='CW', dwell=False, dwelltime=None,
             multidepth=False, depthpercut=None,
             toolchange=False, toolchangez=None, toolchangexy="0.0, 0.0",
-            extracut=False, extracut_length=None, startz=None, endz=None,
+            extracut=False, extracut_length=None, startz=None, endz=None, endxy='',
             pp_geometry_name=None, tool_no=1):
         """
         Second algorithm to generate from Geometry.
@@ -3872,6 +3886,13 @@ class CNCjob(Geometry):
 
         self.startz = float(startz) if startz is not None else self.app.defaults["geometry_startz"]
         self.z_end = float(endz) if endz is not None else self.app.defaults["geometry_endz"]
+        self.xy_end = endxy if endxy != '' else self.app.defaults["geometry_endxy"]
+        self.xy_end = [float(eval(a)) for a in self.xy_end.split(",")]
+        if len(self.xy_end) < 2:
+            self.app.inform.emit('[ERROR]  %s' % _("The End Move X,Y field in Edit -> Preferences has to be "
+                                                   "in the format (x, y) but now there is only one value, not two."))
+            return 'fail'
+
         self.z_depthpercut = float(depthpercut) if depthpercut is not None else 0.0
         self.multidepth = multidepth
         self.z_toolchange = float(toolchangez) if toolchangez is not None else self.app.defaults["geometry_toolchangez"]

+ 1 - 0
flatcamEditors/FlatCAMExcEditor.py

@@ -2209,6 +2209,7 @@ class FlatCAMExcEditor(QtCore.QObject):
             "extracut": self.app.defaults["geometry_extracut"],
             "extracut_length": self.app.defaults["geometry_extracut_length"],
             "endz": self.app.defaults["excellon_endz"],
+            "endxy": self.app.defaults["excellon_endxy"],
             "startz": self.app.defaults["excellon_startz"],
             "offset": self.app.defaults["excellon_offset"],
             "spindlespeed": self.app.defaults["excellon_spindlespeed"],

+ 2 - 0
flatcamGUI/GUIElements.py

@@ -444,6 +444,8 @@ class FCEntry(QtWidgets.QLineEdit):
         decimal_digits = decimals if decimals is not None else self.decimals
         if type(val) is float:
             self.setText('%.*f' % (decimal_digits, val))
+        elif val is None:
+            self.setText('')
         else:
             self.setText(str(val))
 

+ 33 - 9
flatcamGUI/ObjectUI.py

@@ -1220,6 +1220,18 @@ class ExcellonObjectUI(ObjectUI):
         self.grid5.addWidget(self.endz_label, 11, 0)
         self.grid5.addWidget(self.endz_entry, 11, 1)
 
+        # End Move X,Y
+        endmove_xy_label = QtWidgets.QLabel('%s:' % _('End move X,Y'))
+        endmove_xy_label.setToolTip(
+            _("End move X,Y position.\n"
+              "If no value is entered then there is no move\n"
+              "on X,Y plane at the end of the job.")
+        )
+        self.endxy_entry = FCEntry()
+
+        self.grid5.addWidget(endmove_xy_label, 12, 0)
+        self.grid5.addWidget(self.endxy_entry, 12, 1)
+
         # Probe depth
         self.pdepth_label = QtWidgets.QLabel('%s:' % _("Probe Z depth"))
         self.pdepth_label.setToolTip(
@@ -1232,8 +1244,8 @@ class ExcellonObjectUI(ObjectUI):
         self.pdepth_entry.set_range(-9999.9999, 9999.9999)
         self.pdepth_entry.setSingleStep(0.1)
 
-        self.grid5.addWidget(self.pdepth_label, 12, 0)
-        self.grid5.addWidget(self.pdepth_entry, 12, 1)
+        self.grid5.addWidget(self.pdepth_label, 13, 0)
+        self.grid5.addWidget(self.pdepth_entry, 13, 1)
 
         self.pdepth_label.hide()
         self.pdepth_entry.setVisible(False)
@@ -1250,8 +1262,8 @@ class ExcellonObjectUI(ObjectUI):
         self.feedrate_probe_entry.setSingleStep(0.1)
         self.feedrate_probe_entry.setObjectName(_("e_fr_probe"))
 
-        self.grid5.addWidget(self.feedrate_probe_label, 13, 0)
-        self.grid5.addWidget(self.feedrate_probe_entry, 13, 1)
+        self.grid5.addWidget(self.feedrate_probe_label, 14, 0)
+        self.grid5.addWidget(self.feedrate_probe_entry, 14, 1)
 
         self.feedrate_probe_label.hide()
         self.feedrate_probe_entry.setVisible(False)
@@ -1265,8 +1277,8 @@ class ExcellonObjectUI(ObjectUI):
         self.pp_excellon_name_cb = FCComboBox()
         self.pp_excellon_name_cb.setFocusPolicy(QtCore.Qt.StrongFocus)
 
-        self.grid5.addWidget(pp_excellon_label, 14, 0)
-        self.grid5.addWidget(self.pp_excellon_name_cb, 14, 1)
+        self.grid5.addWidget(pp_excellon_label, 15, 0)
+        self.grid5.addWidget(self.pp_excellon_name_cb, 15, 1)
 
         # Preprocessor Geometry selection
         pp_geo_label = QtWidgets.QLabel('%s:' % _("Preprocessor G"))
@@ -1277,13 +1289,13 @@ class ExcellonObjectUI(ObjectUI):
         self.pp_geo_name_cb = FCComboBox()
         self.pp_geo_name_cb.setFocusPolicy(QtCore.Qt.StrongFocus)
 
-        self.grid5.addWidget(pp_geo_label, 15, 0)
-        self.grid5.addWidget(self.pp_geo_name_cb, 15, 1)
+        self.grid5.addWidget(pp_geo_label, 16, 0)
+        self.grid5.addWidget(self.pp_geo_name_cb, 16, 1)
 
         separator_line = QtWidgets.QFrame()
         separator_line.setFrameShape(QtWidgets.QFrame.HLine)
         separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
-        self.grid5.addWidget(separator_line, 16, 0, 1, 2)
+        self.grid5.addWidget(separator_line, 17, 0, 1, 2)
 
         # #################################################################
         # ################# GRID LAYOUT 6   ###############################
@@ -1974,6 +1986,18 @@ class GeometryObjectUI(ObjectUI):
         self.grid4.addWidget(self.endz_label, 9, 0)
         self.grid4.addWidget(self.endz_entry, 9, 1)
 
+        # End Move X,Y
+        endmove_xy_label = QtWidgets.QLabel('%s:' % _('End move X,Y'))
+        endmove_xy_label.setToolTip(
+            _("End move X,Y position.\n"
+              "If no value is entered then there is no move\n"
+              "on X,Y plane at the end of the job.")
+        )
+        self.endxy_entry = FCEntry()
+
+        self.grid4.addWidget(endmove_xy_label, 10, 0)
+        self.grid4.addWidget(self.endxy_entry, 10, 1)
+
         # preprocessor selection
         pp_label = QtWidgets.QLabel('%s:' % _("Preprocessor"))
         pp_label.setToolTip(

+ 50 - 20
flatcamGUI/PreferencesUI.py

@@ -3229,6 +3229,18 @@ class ExcellonOptPrefGroupUI(OptionsGroupUI):
         grid2.addWidget(endz_label, 8, 0)
         grid2.addWidget(self.endz_entry, 8, 1)
 
+        # End Move X,Y
+        endmove_xy_label = QtWidgets.QLabel('%s:' % _('End move X,Y'))
+        endmove_xy_label.setToolTip(
+            _("End move X,Y position.\n"
+              "If no value is entered then there is no move\n"
+              "on X,Y plane at the end of the job.")
+        )
+        self.endxy_entry = FCEntry()
+
+        grid2.addWidget(endmove_xy_label, 9, 0)
+        grid2.addWidget(self.endxy_entry, 9, 1)
+
         # Feedrate Z
         frlabel = QtWidgets.QLabel('%s:' % _('Feedrate Z'))
         frlabel.setToolTip(
@@ -3241,8 +3253,8 @@ class ExcellonOptPrefGroupUI(OptionsGroupUI):
         self.feedrate_z_entry.set_precision(self.decimals)
         self.feedrate_z_entry.set_range(0, 99999.9999)
 
-        grid2.addWidget(frlabel, 9, 0)
-        grid2.addWidget(self.feedrate_z_entry, 9, 1)
+        grid2.addWidget(frlabel, 10, 0)
+        grid2.addWidget(self.feedrate_z_entry, 10, 1)
 
         # Spindle speed
         spdlabel = QtWidgets.QLabel('%s:' % _('Spindle Speed'))
@@ -3255,8 +3267,8 @@ class ExcellonOptPrefGroupUI(OptionsGroupUI):
         self.spindlespeed_entry.set_range(0, 1000000)
         self.spindlespeed_entry.set_step(100)
 
-        grid2.addWidget(spdlabel, 10, 0)
-        grid2.addWidget(self.spindlespeed_entry, 10, 1)
+        grid2.addWidget(spdlabel, 11, 0)
+        grid2.addWidget(self.spindlespeed_entry, 11, 1)
 
         # Dwell
         self.dwell_cb = FCCheckBox('%s' % _('Enable Dwell'))
@@ -3265,7 +3277,7 @@ class ExcellonOptPrefGroupUI(OptionsGroupUI):
               "speed before cutting.")
         )
 
-        grid2.addWidget(self.dwell_cb, 11, 0, 1, 2)
+        grid2.addWidget(self.dwell_cb, 12, 0, 1, 2)
 
         # Dwell Time
         dwelltime = QtWidgets.QLabel('%s:' % _('Duration'))
@@ -3274,8 +3286,8 @@ class ExcellonOptPrefGroupUI(OptionsGroupUI):
         self.dwelltime_entry.set_precision(self.decimals)
         self.dwelltime_entry.set_range(0, 99999.9999)
 
-        grid2.addWidget(dwelltime, 12, 0)
-        grid2.addWidget(self.dwelltime_entry, 12, 1)
+        grid2.addWidget(dwelltime, 13, 0)
+        grid2.addWidget(self.dwelltime_entry, 13, 1)
 
         self.ois_dwell_exc = OptionalInputSection(self.dwell_cb, [self.dwelltime_entry])
 
@@ -3367,6 +3379,7 @@ class ExcellonAdvOptPrefGroupUI(OptionsGroupUI):
         grid1 = QtWidgets.QGridLayout()
         self.layout.addLayout(grid1)
 
+        # Offset Z
         offsetlabel = QtWidgets.QLabel('%s:' % _('Offset Z'))
         offsetlabel.setToolTip(
             _("Some drill bits (the larger ones) need to drill deeper\n"
@@ -3379,21 +3392,25 @@ class ExcellonAdvOptPrefGroupUI(OptionsGroupUI):
         grid1.addWidget(offsetlabel, 0, 0)
         grid1.addWidget(self.offset_entry, 0, 1)
 
+        # ToolChange X,Y
         toolchange_xy_label = QtWidgets.QLabel('%s:' % _('Toolchange X,Y'))
         toolchange_xy_label.setToolTip(
             _("Toolchange X,Y position.")
         )
-        grid1.addWidget(toolchange_xy_label, 1, 0)
         self.toolchangexy_entry = FCEntry()
+
+        grid1.addWidget(toolchange_xy_label, 1, 0)
         grid1.addWidget(self.toolchangexy_entry, 1, 1)
 
+        # Start Z
         startzlabel = QtWidgets.QLabel('%s:' % _('Start Z'))
         startzlabel.setToolTip(
             _("Height of the tool just after start.\n"
               "Delete the value if you don't need this feature.")
         )
-        grid1.addWidget(startzlabel, 2, 0)
         self.estartz_entry = FloatEntry()
+
+        grid1.addWidget(startzlabel, 2, 0)
         grid1.addWidget(self.estartz_entry, 2, 1)
 
         # Feedrate Rapids
@@ -4165,6 +4182,18 @@ class GeometryOptPrefGroupUI(OptionsGroupUI):
         grid1.addWidget(endz_label, 6, 0)
         grid1.addWidget(self.endz_entry, 6, 1)
 
+        # End Move X,Y
+        endmove_xy_label = QtWidgets.QLabel('%s:' % _('End move X,Y'))
+        endmove_xy_label.setToolTip(
+            _("End move X,Y position.\n"
+              "If no value is entered then there is no move\n"
+              "on X,Y plane at the end of the job.")
+        )
+        self.endxy_entry = FCEntry()
+
+        grid1.addWidget(endmove_xy_label, 7, 0)
+        grid1.addWidget(self.endxy_entry, 7, 1)
+
         # Feedrate X-Y
         frlabel = QtWidgets.QLabel('%s:' % _('Feedrate X-Y'))
         frlabel.setToolTip(
@@ -4177,8 +4206,8 @@ class GeometryOptPrefGroupUI(OptionsGroupUI):
         self.cncfeedrate_entry.setSingleStep(0.1)
         self.cncfeedrate_entry.setWrapping(True)
 
-        grid1.addWidget(frlabel, 7, 0)
-        grid1.addWidget(self.cncfeedrate_entry, 7, 1)
+        grid1.addWidget(frlabel, 8, 0)
+        grid1.addWidget(self.cncfeedrate_entry, 8, 1)
 
         # Feedrate Z (Plunge)
         frz_label = QtWidgets.QLabel('%s:' % _('Feedrate Z'))
@@ -4193,8 +4222,8 @@ class GeometryOptPrefGroupUI(OptionsGroupUI):
         self.feedrate_z_entry.setSingleStep(0.1)
         self.feedrate_z_entry.setWrapping(True)
 
-        grid1.addWidget(frz_label, 8, 0)
-        grid1.addWidget(self.feedrate_z_entry, 8, 1)
+        grid1.addWidget(frz_label, 9, 0)
+        grid1.addWidget(self.feedrate_z_entry, 9, 1)
 
         # Spindle Speed
         spdlabel = QtWidgets.QLabel('%s:' % _('Spindle speed'))
@@ -4205,12 +4234,12 @@ class GeometryOptPrefGroupUI(OptionsGroupUI):
                 "this value is the power of laser."
             )
         )
-        grid1.addWidget(spdlabel, 9, 0)
         self.cncspindlespeed_entry = FCSpinner()
         self.cncspindlespeed_entry.set_range(0, 1000000)
         self.cncspindlespeed_entry.set_step(100)
 
-        grid1.addWidget(self.cncspindlespeed_entry, 9, 1)
+        grid1.addWidget(spdlabel, 10, 0)
+        grid1.addWidget(self.cncspindlespeed_entry, 10, 1)
 
         # Dwell
         self.dwell_cb = FCCheckBox(label='%s' % _('Enable Dwell'))
@@ -4228,9 +4257,9 @@ class GeometryOptPrefGroupUI(OptionsGroupUI):
         self.dwelltime_entry.setSingleStep(0.1)
         self.dwelltime_entry.setWrapping(True)
 
-        grid1.addWidget(self.dwell_cb, 10, 0)
-        grid1.addWidget(dwelltime, 11, 0)
-        grid1.addWidget(self.dwelltime_entry, 11, 1)
+        grid1.addWidget(self.dwell_cb, 11, 0)
+        grid1.addWidget(dwelltime, 12, 0)
+        grid1.addWidget(self.dwelltime_entry, 12, 1)
 
         self.ois_dwell = OptionalInputSection(self.dwell_cb, [self.dwelltime_entry])
 
@@ -4240,10 +4269,11 @@ class GeometryOptPrefGroupUI(OptionsGroupUI):
             _("The Preprocessor file that dictates\n"
               "the Machine Code (like GCode, RML, HPGL) output.")
         )
-        grid1.addWidget(pp_label, 12, 0)
         self.pp_geometry_name_cb = FCComboBox()
         self.pp_geometry_name_cb.setFocusPolicy(Qt.StrongFocus)
-        grid1.addWidget(self.pp_geometry_name_cb, 12, 1)
+
+        grid1.addWidget(pp_label, 13, 0)
+        grid1.addWidget(self.pp_geometry_name_cb, 13, 1)
 
         self.layout.addStretch()
 

+ 2 - 0
flatcamParsers/ParseHPGL2.py

@@ -72,6 +72,8 @@ class HPGL2:
             "toolchange": self.app.defaults["geometry_toolchange"],
             "toolchangez": self.app.defaults["geometry_toolchangez"],
             "endz": self.app.defaults["geometry_endz"],
+            "endxy": self.app.defaults["geometry_endxy"],
+
             "spindlespeed": self.app.defaults["geometry_spindlespeed"],
             "toolchangexy": self.app.defaults["geometry_toolchangexy"],
             "startz": self.app.defaults["geometry_startz"],

+ 2 - 0
flatcamTools/ToolNonCopperClear.py

@@ -984,6 +984,8 @@ class NonCopperClear(FlatCAMTool, Gerber):
             "toolchange": self.app.defaults["geometry_toolchange"],
             "toolchangez": self.app.defaults["geometry_toolchangez"],
             "endz": self.app.defaults["geometry_endz"],
+            "endxy": self.app.defaults["geometry_endxy"],
+
             "spindlespeed": self.app.defaults["geometry_spindlespeed"],
             "toolchangexy": self.app.defaults["geometry_toolchangexy"],
             "startz": self.app.defaults["geometry_startz"],

+ 2 - 0
flatcamTools/ToolPaint.py

@@ -961,6 +961,8 @@ class ToolPaint(FlatCAMTool, Gerber):
             "toolchange": self.app.defaults["geometry_toolchange"],
             "toolchangez": float(self.app.defaults["geometry_toolchangez"]),
             "endz": float(self.app.defaults["geometry_endz"]),
+            "endxy": self.app.defaults["geometry_endxy"],
+
             "spindlespeed": self.app.defaults["geometry_spindlespeed"],
             "toolchangexy": self.app.defaults["geometry_toolchangexy"],
             "startz": self.app.defaults["geometry_startz"],

+ 2 - 2
preprocessors/Berta_CNC.py

@@ -227,10 +227,10 @@ M0""".format(z_toolchange=self.coordinate_format % (p.coords_decimals, z_toolcha
         return ('G01 ' + self.position_code(p)).format(**p)
 
     def end_code(self, p):
-        coords_xy = p['xy_toolchange']
+        coords_xy = p['xy_end']
         gcode = ('G00 Z' + self.feedrate_format % (p.fr_decimals, p.z_end) + "\n")
 
-        if coords_xy is not None:
+        if coords_xy != '':
             gcode += 'G00 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + "\n"
 
         gcode += '(Berta)\n'

+ 2 - 2
preprocessors/GRBL_laser.py

@@ -83,10 +83,10 @@ class GRBL_laser(FlatCAMPostProc):
                ' F' + str(self.feedrate_format % (p.fr_decimals, p.feedrate))
 
     def end_code(self, p):
-        coords_xy = p['xy_toolchange']
+        coords_xy = p['xy_end']
         gcode = ('G00 Z' + self.feedrate_format % (p.fr_decimals, p.z_end) + "\n")
 
-        if coords_xy is not None:
+        if coords_xy != '':
             gcode += 'G00 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + "\n"
         return gcode
 

+ 2 - 2
preprocessors/ISEL_CNC.py

@@ -157,10 +157,10 @@ M01""".format(tool=int(p.tool), toolC=toolC_formatted)
         return ('G01 ' + self.position_code(p)).format(**p)
 
     def end_code(self, p):
-        coords_xy = p['xy_toolchange']
+        coords_xy = p['xy_end']
         gcode = ('G00 Z' + self.feedrate_format % (p.fr_decimals, p.z_end) + "\n")
 
-        if coords_xy is not None:
+        if coords_xy != '':
             gcode += 'G00 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + "\n"
         return gcode
 

+ 2 - 2
preprocessors/Marlin.py

@@ -215,10 +215,10 @@ G0 Z{z_toolchange}
         return ('G1 ' + self.position_code(p)).format(**p) + " " + self.inline_feedrate_code(p)
 
     def end_code(self, p):
-        coords_xy = p['xy_toolchange']
+        coords_xy = p['xy_end']
         gcode = ('G0 Z' + self.feedrate_format % (p.fr_decimals, p.z_end) + " " + self.feedrate_rapid_code(p) + "\n")
 
-        if coords_xy is not None:
+        if coords_xy != '':
             gcode += 'G0 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + " " + self.feedrate_rapid_code(p) + "\n"
 
         return gcode

+ 2 - 2
preprocessors/Marlin_laser_use_FAN_pin.py

@@ -85,10 +85,10 @@ class Marlin_laser_use_FAN_pin(FlatCAMPostProc):
         return ('G1 ' + self.position_code(p)).format(**p) + " " + self.inline_feedrate_code(p)
 
     def end_code(self, p):
-        coords_xy = p['xy_toolchange']
+        coords_xy = p['xy_end']
         gcode = ('G0 Z' + self.feedrate_format % (p.fr_decimals, p.z_end) + " " + self.feedrate_rapid_code(p) + "\n")
 
-        if coords_xy is not None:
+        if coords_xy != '':
             gcode += 'G0 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + " " + self.feedrate_rapid_code(p) + "\n"
 
         return gcode

+ 2 - 2
preprocessors/Marlin_laser_use_Spindle_pin.py

@@ -86,10 +86,10 @@ class Marlin_laser_use_Spindle_pin(FlatCAMPostProc):
         return ('G1 ' + self.position_code(p)).format(**p) + " " + self.inline_feedrate_code(p)
 
     def end_code(self, p):
-        coords_xy = p['xy_toolchange']
+        coords_xy = p['xy_end']
         gcode = ('G0 Z' + self.feedrate_format % (p.fr_decimals, p.z_end) + " " + self.feedrate_rapid_code(p) + "\n")
 
-        if coords_xy is not None:
+        if coords_xy != '':
             gcode += 'G0 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + " " + self.feedrate_rapid_code(p) + "\n"
 
         return gcode

+ 2 - 2
preprocessors/Paste_1.py

@@ -122,10 +122,10 @@ G00 Z{z_toolchange}
         return ('G01 ' + self.position_code(p)).format(**p)
 
     def end_code(self, p):
-        coords_xy = [float(eval(a)) for a in p['xy_toolchange'].split(",") if a != '']
+        coords_xy = [float(eval(a)) for a in p['xy_end'].split(",") if a != '']
         gcode = ('G00 Z' + self.feedrate_format % (p.fr_decimals, float(p['z_toolchange'])) + "\n")
 
-        if coords_xy is not None:
+        if coords_xy != '':
             gcode += 'G00 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + "\n"
         return gcode
 

+ 2 - 2
preprocessors/Repetier.py

@@ -206,10 +206,10 @@ G0 Z{z_toolchange}
         return ('G1 ' + self.position_code(p)).format(**p) + " " + self.inline_feedrate_code(p)
 
     def end_code(self, p):
-        coords_xy = p['xy_toolchange']
+        coords_xy = p['xy_end']
         gcode = ('G0 Z' + self.feedrate_format % (p.fr_decimals, p.z_end) + " " + self.feedrate_rapid_code(p) + "\n")
 
-        if coords_xy is not None:
+        if coords_xy != '':
             gcode += 'G0 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + " " + self.feedrate_rapid_code(p) + "\n"
 
         return gcode

+ 2 - 2
preprocessors/Toolchange_Custom.py

@@ -173,10 +173,10 @@ M6
         return ('G01 ' + self.position_code(p)).format(**p)
 
     def end_code(self, p):
-        coords_xy = p['xy_toolchange']
+        coords_xy = p['xy_end']
         gcode = ('G00 Z' + self.feedrate_format % (p.fr_decimals, p.z_end) + "\n")
 
-        if coords_xy is not None:
+        if coords_xy != '':
             gcode += 'G00 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + "\n"
         return gcode
 

+ 2 - 2
preprocessors/Toolchange_Probe_MACH3.py

@@ -272,10 +272,10 @@ M0
         return ('G01 ' + self.position_code(p)).format(**p)
 
     def end_code(self, p):
-        coords_xy = p['xy_toolchange']
+        coords_xy = p['xy_end']
         gcode = ('G00 Z' + self.feedrate_format % (p.fr_decimals, p.z_end) + "\n")
 
-        if coords_xy is not None:
+        if coords_xy != '':
             gcode += 'G00 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + "\n"
         return gcode
 

+ 3 - 4
preprocessors/Toolchange_manual.py

@@ -235,12 +235,11 @@ M0
         return ('G01 ' + self.position_code(p)).format(**p)
 
     def end_code(self, p):
-        coords_xy = p['xy_toolchange']
+        coords_xy = p['xy_end']
         gcode = ('G00 Z' + self.feedrate_format % (p.fr_decimals, p.z_end) + "\n")
-        if coords_xy is not None:
+
+        if coords_xy != '':
             gcode += 'G00 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + "\n"
-        else:
-            gcode += 'G00 X0 Y0' + "\n"
         return gcode
 
     def feedrate_code(self, p):

+ 3 - 3
preprocessors/default.py

@@ -217,11 +217,11 @@ G00 Z{z_toolchange}
         return ('G01 ' + self.position_code(p)).format(**p)
 
     def end_code(self, p):
-        coords_xy = p['xy_toolchange']
+        end_coords_xy = p['xy_end']
         gcode = ('G00 Z' + self.feedrate_format % (p.fr_decimals, p.z_end) + "\n")
 
-        if coords_xy is not None:
-            gcode += 'G00 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + "\n"
+        if end_coords_xy != '':
+            gcode += 'G00 X{x} Y{y}'.format(x=end_coords_xy[0], y=end_coords_xy[1]) + "\n"
         return gcode
 
     def feedrate_code(self, p):

+ 2 - 2
preprocessors/grbl_11.py

@@ -218,10 +218,10 @@ G00 Z{z_toolchange}
                ' F' + str(self.feedrate_format % (p.fr_decimals, p.feedrate))
 
     def end_code(self, p):
-        coords_xy = p['xy_toolchange']
+        coords_xy = p['xy_end']
         gcode = ('G00 Z' + self.feedrate_format % (p.fr_decimals, p.z_end) + "\n")
 
-        if coords_xy is not None:
+        if coords_xy != '':
             gcode += 'G00 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + "\n"
         return gcode
 

+ 2 - 2
preprocessors/line_xyz.py

@@ -206,8 +206,8 @@ M0""".format(x_toolchange=self.coordinate_format % (p.coords_decimals, x_toolcha
         return g
 
     def end_code(self, p):
-        coords_xy = p['xy_toolchange']
-        if coords_xy is not None:
+        coords_xy = p['xy_end']
+        if coords_xy != '':
             g = 'G00 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + "\n"
         else:
             g = ('G00 ' + self.position_code(p)).format(**p)

+ 1 - 0
tclCommands/TclCommandCopperClear.py

@@ -175,6 +175,7 @@ class TclCommandCopperClear(TclCommand):
             "toolchange": self.app.defaults["geometry_toolchange"],
             "toolchangez": self.app.defaults["geometry_toolchangez"],
             "endz": self.app.defaults["geometry_endz"],
+            "endxy": self.app.defaults["geometry_endxy"],
             "spindlespeed": self.app.defaults["geometry_spindlespeed"],
             "toolchangexy": self.app.defaults["geometry_toolchangexy"],
             "startz": self.app.defaults["geometry_startz"],

+ 2 - 0
tclCommands/TclCommandPaint.py

@@ -164,6 +164,8 @@ class TclCommandPaint(TclCommand):
             "toolchange": self.app.defaults["geometry_toolchange"],
             "toolchangez": self.app.defaults["geometry_toolchangez"],
             "endz": self.app.defaults["geometry_endz"],
+            "endxy": self.app.defaults["geometry_endxy"],
+
             "spindlespeed": self.app.defaults["geometry_spindlespeed"],
             "toolchangexy": self.app.defaults["geometry_toolchangexy"],
             "startz": self.app.defaults["geometry_startz"],