瀏覽代碼

- added a traceback report in the TCL Shell for the errors that don't allow creation of an object; useful to trace exceptions/errors
- in case that the Toolchange X,Y parameter in Selected (or in Preferences) are deleted then the app will still do the job using the current coordinates for toolchange
- fixed an issue in camlib.CNCJob where tha variable self.toolchange_xy was used for 2 different purposes which created loss of information.

Marius Stanciu 7 年之前
父節點
當前提交
7e5ce009d8
共有 10 個文件被更改,包括 193 次插入62 次删除
  1. 9 4
      FlatCAMApp.py
  2. 24 12
      FlatCAMObj.py
  3. 3 0
      README.md
  4. 41 9
      camlib.py
  5. 27 7
      postprocessors/default.py
  6. 43 20
      postprocessors/grbl_11.py
  7. 4 1
      postprocessors/grbl_laser.py
  8. 17 4
      postprocessors/line_xyz.py
  9. 15 4
      postprocessors/manual_toolchange.py
  10. 10 1
      postprocessors/marlin.py

+ 9 - 4
FlatCAMApp.py

@@ -2102,10 +2102,15 @@ class App(QtCore.QObject):
         try:
         try:
             return_value = initialize(obj, self)
             return_value = initialize(obj, self)
         except Exception as e:
         except Exception as e:
-            if str(e) == "Empty Geometry":
-                self.inform.emit("[error_notcl] Object (%s) failed because: %s" % (kind, str(e)))
-            else:
-                self.inform.emit("[error] Object (%s) failed because: %s" % (kind, str(e)))
+            msg = "[error_notcl] An internal error has ocurred. See shell.\n"
+            msg += "Object (%s) failed because: %s \n\n" % (kind, str(e))
+            msg += traceback.format_exc()
+            self.inform.emit(msg)
+
+            # if str(e) == "Empty Geometry":
+            #     self.inform.emit("[error_notcl] )
+            # else:
+            #     self.inform.emit("[error] Object (%s) failed because: %s" % (kind, str(e)))
             return "fail"
             return "fail"
 
 
         t2 = time.time()
         t2 = time.time()

+ 24 - 12
FlatCAMObj.py

@@ -1665,7 +1665,7 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
             job_obj.dwell = self.options["dwell"]
             job_obj.dwell = self.options["dwell"]
             job_obj.dwelltime = self.options["dwelltime"]
             job_obj.dwelltime = self.options["dwelltime"]
             job_obj.pp_excellon_name = pp_excellon_name
             job_obj.pp_excellon_name = pp_excellon_name
-            job_obj.toolchange_xy = "excellon"
+            job_obj.toolchange_xy_type = "excellon"
             job_obj.coords_decimals = int(self.app.defaults["cncjob_coords_decimals"])
             job_obj.coords_decimals = int(self.app.defaults["cncjob_coords_decimals"])
             job_obj.fr_decimals = int(self.app.defaults["cncjob_fr_decimals"])
             job_obj.fr_decimals = int(self.app.defaults["cncjob_fr_decimals"])
 
 
@@ -3116,7 +3116,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
 
 
                 app_obj.progress.emit(40)
                 app_obj.progress.emit(40)
 
 
-                dia_cnc_dict['gcode'] = job_obj.generate_from_geometry_2(
+                res = job_obj.generate_from_geometry_2(
                     self, tooldia=tooldia_val, offset=tool_offset, tolerance=0.0005,
                     self, tooldia=tooldia_val, offset=tool_offset, tolerance=0.0005,
                     z_cut=z_cut, z_move=z_move,
                     z_cut=z_cut, z_move=z_move,
                     feedrate=feedrate, feedrate_z=feedrate_z, feedrate_rapid=feedrate_rapid,
                     feedrate=feedrate, feedrate_z=feedrate_z, feedrate_rapid=feedrate_rapid,
@@ -3127,10 +3127,16 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
                     pp_geometry_name=pp_geometry_name,
                     pp_geometry_name=pp_geometry_name,
                     tool_no=tool_cnt)
                     tool_no=tool_cnt)
 
 
+                if res == 'fail':
+                    log.debug("FlatCAMGeometry.mtool_gen_cncjob() --> generate_from_geometry2() failed")
+                    return 'fail'
+                else:
+                    dia_cnc_dict['gcode'] = res
+
                 app_obj.progress.emit(50)
                 app_obj.progress.emit(50)
                 # tell gcode_parse from which point to start drawing the lines depending on what kind of
                 # tell gcode_parse from which point to start drawing the lines depending on what kind of
                 # object is the source of gcode
                 # object is the source of gcode
-                job_obj.toolchange_xy = "geometry"
+                job_obj.toolchange_xy_type = "geometry"
 
 
                 dia_cnc_dict['gcode_parsed'] = job_obj.gcode_parse()
                 dia_cnc_dict['gcode_parsed'] = job_obj.gcode_parse()
 
 
@@ -3283,7 +3289,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
                 app_obj.progress.emit(40)
                 app_obj.progress.emit(40)
 
 
                 tool_solid_geometry = self.tools[current_uid]['solid_geometry']
                 tool_solid_geometry = self.tools[current_uid]['solid_geometry']
-                dia_cnc_dict['gcode'] = job_obj.generate_from_multitool_geometry(
+                res = job_obj.generate_from_multitool_geometry(
                     tool_solid_geometry, tooldia=tooldia_val, offset=tool_offset,
                     tool_solid_geometry, tooldia=tooldia_val, offset=tool_offset,
                     tolerance=0.0005, z_cut=z_cut, z_move=z_move,
                     tolerance=0.0005, z_cut=z_cut, z_move=z_move,
                     feedrate=feedrate, feedrate_z=feedrate_z, feedrate_rapid=feedrate_rapid,
                     feedrate=feedrate, feedrate_z=feedrate_z, feedrate_rapid=feedrate_rapid,
@@ -3294,6 +3300,12 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
                     pp_geometry_name=pp_geometry_name,
                     pp_geometry_name=pp_geometry_name,
                     tool_no=tool_cnt)
                     tool_no=tool_cnt)
 
 
+                if res == 'fail':
+                    log.debug("FlatCAMGeometry.mtool_gen_cncjob() --> generate_from_geometry2() failed")
+                    return 'fail'
+                else:
+                    dia_cnc_dict['gcode'] = res
+
                 dia_cnc_dict['gcode_parsed'] = job_obj.gcode_parse()
                 dia_cnc_dict['gcode_parsed'] = job_obj.gcode_parse()
 
 
                 # TODO this serve for bounding box creation only; should be optimized
                 # TODO this serve for bounding box creation only; should be optimized
@@ -3301,7 +3313,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
 
 
                 # tell gcode_parse from which point to start drawing the lines depending on what kind of
                 # tell gcode_parse from which point to start drawing the lines depending on what kind of
                 # object is the source of gcode
                 # object is the source of gcode
-                job_obj.toolchange_xy = "geometry"
+                job_obj.toolchange_xy_type = "geometry"
 
 
                 app_obj.progress.emit(80)
                 app_obj.progress.emit(80)
 
 
@@ -3317,14 +3329,14 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
             def job_thread(app_obj):
             def job_thread(app_obj):
                 if self.solid_geometry:
                 if self.solid_geometry:
                     with self.app.proc_container.new("Generating CNC Code"):
                     with self.app.proc_container.new("Generating CNC Code"):
-                        app_obj.new_object("cncjob", outname, job_init_single_geometry)
-                        app_obj.inform.emit("[success]CNCjob created: %s" % outname)
-                        app_obj.progress.emit(100)
+                        if app_obj.new_object("cncjob", outname, job_init_single_geometry) != 'fail':
+                            app_obj.inform.emit("[success]CNCjob created: %s" % outname)
+                            app_obj.progress.emit(100)
                 else:
                 else:
                     with self.app.proc_container.new("Generating CNC Code"):
                     with self.app.proc_container.new("Generating CNC Code"):
-                        app_obj.new_object("cncjob", outname, job_init_multi_geometry)
-                        app_obj.inform.emit("[success]CNCjob created: %s" % outname)
-                        app_obj.progress.emit(100)
+                        if app_obj.new_object("cncjob", outname, job_init_multi_geometry) != 'fail':
+                            app_obj.inform.emit("[success]CNCjob created: %s" % outname)
+                            app_obj.progress.emit(100)
 
 
             # Create a promise with the name
             # Create a promise with the name
             self.app.collection.promise(outname)
             self.app.collection.promise(outname)
@@ -3433,7 +3445,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
             app_obj.progress.emit(50)
             app_obj.progress.emit(50)
             # tell gcode_parse from which point to start drawing the lines depending on what kind of object is the
             # tell gcode_parse from which point to start drawing the lines depending on what kind of object is the
             # source of gcode
             # source of gcode
-            job_obj.toolchange_xy = "geometry"
+            job_obj.toolchange_xy_type = "geometry"
             job_obj.gcode_parse()
             job_obj.gcode_parse()
 
 
             app_obj.progress.emit(80)
             app_obj.progress.emit(80)

+ 3 - 0
README.md

@@ -14,6 +14,9 @@ CAD program, and create G-Code for Isolation routing.
 - added a space before Y coordinate in end_code() function in some of the postprocessor files
 - added a space before Y coordinate in end_code() function in some of the postprocessor files
 - added in Calculators Tool an Electroplating Calculator.
 - added in Calculators Tool an Electroplating Calculator.
 - remade the App Menu for Editors: now they will be showed only when the respective Editor is active and hidden when the Editor is closed.
 - remade the App Menu for Editors: now they will be showed only when the respective Editor is active and hidden when the Editor is closed.
+- added a traceback report in the TCL Shell for the errors that don't allow creation of an object; useful to trace exceptions/errors
+- in case that the Toolchange X,Y parameter in Selected (or in Preferences) are deleted then the app will still do the job using the current coordinates for toolchange
+- fixed an issue in camlib.CNCJob where tha variable self.toolchange_xy was used for 2 different purposes which created loss of information.
 
 
 29.01.2019
 29.01.2019
 
 

+ 41 - 9
camlib.py

@@ -4393,6 +4393,7 @@ class CNCjob(Geometry):
         self.tooldia = tooldia
         self.tooldia = tooldia
         self.toolchangez = toolchangez
         self.toolchangez = toolchangez
         self.toolchange_xy = None
         self.toolchange_xy = None
+        self.toolchange_xy_type = None
 
 
         self.endz = endz
         self.endz = endz
         self.depthpercut = depthpercut
         self.depthpercut = depthpercut
@@ -4534,7 +4535,19 @@ class CNCjob(Geometry):
             self.z_cut = drillz
             self.z_cut = drillz
 
 
         self.toolchangez = toolchangez
         self.toolchangez = toolchangez
-        self.toolchange_xy = [float(eval(a)) for a in toolchangexy.split(",")]
+
+        try:
+            if toolchangexy == '':
+                self.toolchange_xy = None
+            else:
+                self.toolchange_xy = [float(eval(a)) for a in toolchangexy.split(",")]
+                if len(self.toolchange_xy) < 2:
+                    self.app.inform.emit("[error]The Toolchange X,Y field in Edit -> Preferences has to be "
+                                         "in the format (x, y) \nbut now there is only one value, not two. ")
+                    return 'fail'
+        except Exception as e:
+            log.debug("camlib.CNCJob.generate_from_excellon_by_tool() --> %s" % str(e))
+            pass
 
 
         self.startz = startz
         self.startz = startz
         self.endz = endz
         self.endz = endz
@@ -4583,8 +4596,8 @@ class CNCjob(Geometry):
         # Initialization
         # Initialization
         gcode = self.doformat(p.start_code)
         gcode = self.doformat(p.start_code)
         gcode += self.doformat(p.feedrate_code)
         gcode += self.doformat(p.feedrate_code)
-        gcode += self.doformat(p.lift_code, x=0, y=0)
-        gcode += self.doformat(p.startz_code, x=0, y=0)
+        gcode += self.doformat(p.lift_code, x=self.toolchange_xy[0], y=self.toolchange_xy[1])
+        gcode += self.doformat(p.startz_code, x=self.toolchange_xy[0], y=self.toolchange_xy[1])
 
 
         # Distance callback
         # Distance callback
         class CreateDistanceCallback(object):
         class CreateDistanceCallback(object):
@@ -4618,8 +4631,8 @@ class CNCjob(Geometry):
                 locations.append((point.coords.xy[0][0], point.coords.xy[1][0]))
                 locations.append((point.coords.xy[0][0], point.coords.xy[1][0]))
             return locations
             return locations
 
 
-        oldx = 0
-        oldy = 0
+        oldx = self.toolchange_xy[0]
+        oldy = self.toolchange_xy[1]
         measured_distance = 0
         measured_distance = 0
 
 
         current_platform = platform.architecture()[0]
         current_platform = platform.architecture()[0]
@@ -5067,7 +5080,19 @@ class CNCjob(Geometry):
         self.multidepth = multidepth
         self.multidepth = multidepth
 
 
         self.toolchangez = toolchangez
         self.toolchangez = toolchangez
-        self.toolchange_xy = [float(eval(a)) for a in toolchangexy.split(",")]
+
+        try:
+            if toolchangexy == '':
+                self.toolchange_xy = None
+            else:
+                self.toolchange_xy = [float(eval(a)) for a in toolchangexy.split(",")]
+                if len(self.toolchange_xy) < 2:
+                    self.app.inform.emit("[error]The Toolchange X,Y field in Edit -> Preferences has to be "
+                                         "in the format (x, y) \nbut now there is only one value, not two. ")
+                    return 'fail'
+        except Exception as e:
+            log.debug("camlib.CNCJob.generate_from_geometry_2() --> %s" % str(e))
+            pass
 
 
         self.pp_geometry_name = pp_geometry_name if pp_geometry_name else 'default'
         self.pp_geometry_name = pp_geometry_name if pp_geometry_name else 'default'
 
 
@@ -5328,10 +5353,17 @@ class CNCjob(Geometry):
 
 
         # Current path: temporary storage until tool is
         # Current path: temporary storage until tool is
         # lifted or lowered.
         # lifted or lowered.
-        if self.toolchange_xy == "excellon":
-            pos_xy = [float(eval(a)) for a in self.app.defaults["excellon_toolchangexy"].split(",")]
+        if self.toolchange_xy_type == "excellon":
+            if self.app.defaults["excellon_toolchangexy"] == '':
+                pos_xy = [0, 0]
+            else:
+                pos_xy = [float(eval(a)) for a in self.app.defaults["excellon_toolchangexy"].split(",")]
         else:
         else:
-            pos_xy = [float(eval(a)) for a in self.app.defaults["geometry_toolchangexy"].split(",")]
+            if self.app.defaults["geometry_toolchangexy"] == '':
+                pos_xy = [0, 0]
+            else:
+                pos_xy = [float(eval(a)) for a in self.app.defaults["geometry_toolchangexy"].split(",")]
+
         path = [pos_xy]
         path = [pos_xy]
         # path = [(0, 0)]
         # path = [(0, 0)]
 
 

+ 27 - 7
postprocessors/default.py

@@ -29,7 +29,12 @@ class default(FlatCAMPostProc):
 
 
         gcode += '(Z_Move: ' + str(p['z_move']) + units + ')\n'
         gcode += '(Z_Move: ' + str(p['z_move']) + units + ')\n'
         gcode += '(Z Toolchange: ' + str(p['toolchangez']) + units + ')\n'
         gcode += '(Z Toolchange: ' + str(p['toolchangez']) + units + ')\n'
-        gcode += '(X,Y Toolchange: ' + "%.4f, %.4f" % (coords_xy[0], coords_xy[1]) + units + ')\n'
+
+        if coords_xy is not None:
+            gcode += '(X,Y Toolchange: ' + "%.4f, %.4f" % (coords_xy[0], coords_xy[1]) + units + ')\n'
+        else:
+            gcode += '(X,Y Toolchange: ' + "None" + units + ')\n'
+
         gcode += '(Z Start: ' + str(p['startz']) + units + ')\n'
         gcode += '(Z Start: ' + str(p['startz']) + units + ')\n'
         gcode += '(Z End: ' + str(p['endz']) + units + ')\n'
         gcode += '(Z End: ' + str(p['endz']) + units + ')\n'
         gcode += '(Steps per circle: ' + str(p['steps_per_circle']) + ')\n'
         gcode += '(Steps per circle: ' + str(p['steps_per_circle']) + ')\n'
@@ -62,8 +67,11 @@ class default(FlatCAMPostProc):
     def toolchange_code(self, p):
     def toolchange_code(self, p):
         toolchangez = p.toolchangez
         toolchangez = p.toolchangez
         toolchangexy = p.toolchange_xy
         toolchangexy = p.toolchange_xy
-        toolchangex = toolchangexy[0]
-        toolchangey = toolchangexy[1]
+        gcode = ''
+
+        if toolchangexy is not None:
+            toolchangex = toolchangexy[0]
+            toolchangey = toolchangexy[1]
 
 
         no_drills = 1
         no_drills = 1
 
 
@@ -79,17 +87,23 @@ class default(FlatCAMPostProc):
             for i in p['options']['Tools_in_use']:
             for i in p['options']['Tools_in_use']:
                 if i[0] == p.tool:
                 if i[0] == p.tool:
                     no_drills = i[2]
                     no_drills = i[2]
-            return """G00 Z{toolchangez}
+
+            gcode = """G00 Z{toolchangez}
 T{tool}
 T{tool}
 M5
 M5
 M6
 M6
-(MSG, Change to Tool Dia = {toolC}, Total drills for current tool = {t_drills})
+(MSG, Change to Tool Dia = {toolC}, Total drills for tool T{tool} = {t_drills})
 M0""".format(toolchangez=self.coordinate_format%(p.coords_decimals, toolchangez),
 M0""".format(toolchangez=self.coordinate_format%(p.coords_decimals, toolchangez),
              tool=int(p.tool),
              tool=int(p.tool),
              t_drills=no_drills,
              t_drills=no_drills,
              toolC=toolC_formatted)
              toolC=toolC_formatted)
+
+            if toolchangexy is not None:
+                gcode += ('\n' + 'G00 X{toolchangex} Y{toolchangey}'.format(toolchangex=toolchangex,
+                                                                            toolchangey=toolchangey))
+            return gcode
         else:
         else:
-            return """G00 Z{toolchangez}
+            gcode = """G00 Z{toolchangez}
 T{tool}
 T{tool}
 M5
 M5
 M6    
 M6    
@@ -97,6 +111,10 @@ M6
 M0""".format(toolchangez=self.coordinate_format%(p.coords_decimals, toolchangez),
 M0""".format(toolchangez=self.coordinate_format%(p.coords_decimals, toolchangez),
              tool=int(p.tool),
              tool=int(p.tool),
              toolC=toolC_formatted)
              toolC=toolC_formatted)
+            if toolchangexy is not None:
+                gcode += ('\n' + 'G00 X{toolchangex} Y{toolchangey}'.format(toolchangex=toolchangex,
+                                                                            toolchangey=toolchangey))
+            return gcode
 
 
     def up_to_zero_code(self, p):
     def up_to_zero_code(self, p):
         return 'G01 Z0'
         return 'G01 Z0'
@@ -114,7 +132,9 @@ M0""".format(toolchangez=self.coordinate_format%(p.coords_decimals, toolchangez)
     def end_code(self, p):
     def end_code(self, p):
         coords_xy = p['toolchange_xy']
         coords_xy = p['toolchange_xy']
         gcode = ('G00 Z' + self.feedrate_format %(p.fr_decimals, p.endz) + "\n")
         gcode = ('G00 Z' + self.feedrate_format %(p.fr_decimals, p.endz) + "\n")
-        gcode += 'G00 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + "\n"
+
+        if coords_xy is not None:
+            gcode += 'G00 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + "\n"
         return gcode
         return gcode
 
 
     def feedrate_code(self, p):
     def feedrate_code(self, p):

+ 43 - 20
postprocessors/grbl_11.py

@@ -29,7 +29,10 @@ class grbl_11(FlatCAMPostProc):
 
 
         gcode += '(Z_Move: ' + str(p['z_move']) + units + ')\n'
         gcode += '(Z_Move: ' + str(p['z_move']) + units + ')\n'
         gcode += '(Z Toolchange: ' + str(p['toolchangez']) + units + ')\n'
         gcode += '(Z Toolchange: ' + str(p['toolchangez']) + units + ')\n'
-        gcode += '(X,Y Toolchange: ' + "%.4f, %.4f" % (coords_xy[0], coords_xy[1]) + units + ')\n'
+        if coords_xy is not None:
+            gcode += '(X,Y Toolchange: ' + "%.4f, %.4f" % (coords_xy[0], coords_xy[1]) + units + ')\n'
+        else:
+            gcode += '(X,Y Toolchange: ' + "None" + units + ')\n'
         gcode += '(Z Start: ' + str(p['startz']) + units + ')\n'
         gcode += '(Z Start: ' + str(p['startz']) + units + ')\n'
         gcode += '(Z End: ' + str(p['endz']) + units + ')\n'
         gcode += '(Z End: ' + str(p['endz']) + units + ')\n'
         gcode += '(Steps per circle: ' + str(p['steps_per_circle']) + ')\n'
         gcode += '(Steps per circle: ' + str(p['steps_per_circle']) + ')\n'
@@ -62,6 +65,12 @@ class grbl_11(FlatCAMPostProc):
 
 
     def toolchange_code(self, p):
     def toolchange_code(self, p):
         toolchangez = p.toolchangez
         toolchangez = p.toolchangez
+        toolchangexy = p.toolchange_xy
+        gcode = ''
+
+        if toolchangexy is not None:
+            toolchangex = toolchangexy[0]
+            toolchangey = toolchangexy[1]
 
 
         if int(p.tool) == 1 and p.startz is not None:
         if int(p.tool) == 1 and p.startz is not None:
             toolchangez = p.startz
             toolchangez = p.startz
@@ -71,28 +80,40 @@ class grbl_11(FlatCAMPostProc):
         else:
         else:
             toolC_formatted = format(p.toolC, '.4f')
             toolC_formatted = format(p.toolC, '.4f')
 
 
+        no_drills = 1
+
         if str(p['options']['type']) == 'Excellon':
         if str(p['options']['type']) == 'Excellon':
             for i in p['options']['Tools_in_use']:
             for i in p['options']['Tools_in_use']:
                 if i[0] == p.tool:
                 if i[0] == p.tool:
                     no_drills = i[2]
                     no_drills = i[2]
-            return """G00 Z{toolchangez}
-T{tool}
-M5
-M6
-(MSG, Change to Tool Dia = {toolC}, Total drills for current tool = {t_drills})
-M0""".format(toolchangez=self.coordinate_format%(p.coords_decimals, toolchangez),
-             tool=int(p.tool),
-             t_drills=no_drills,
-             toolC=toolC_formatted)
+
+            gcode = """G00 Z{toolchangez}
+        T{tool}
+        M5
+        M6
+        (MSG, Change to Tool Dia = {toolC}, Total drills for tool T{tool} = {t_drills})
+        M0""".format(toolchangez=self.coordinate_format % (p.coords_decimals, toolchangez),
+                     tool=int(p.tool),
+                     t_drills=no_drills,
+                     toolC=toolC_formatted)
+
+            if toolchangexy is not None:
+                gcode += ('\n' + 'G00 X{toolchangex} Y{toolchangey}'.format(toolchangex=toolchangex,
+                                                                            toolchangey=toolchangey))
+            return gcode
         else:
         else:
-            return """G00 Z{toolchangez}
-T{tool}
-M5
-M6    
-(MSG, Change to Tool Dia = {toolC})
-M0""".format(toolchangez=self.coordinate_format%(p.coords_decimals, toolchangez),
-             tool=int(p.tool),
-             toolC=toolC_formatted)
+            gcode = """G00 Z{toolchangez}
+        T{tool}
+        M5
+        M6    
+        (MSG, Change to Tool Dia = {toolC})
+        M0""".format(toolchangez=self.coordinate_format % (p.coords_decimals, toolchangez),
+                     tool=int(p.tool),
+                     toolC=toolC_formatted)
+            if toolchangexy is not None:
+                gcode += ('\n' + 'G00 X{toolchangex} Y{toolchangey}'.format(toolchangex=toolchangex,
+                                                                            toolchangey=toolchangey))
+            return gcode
 
 
     def up_to_zero_code(self, p):
     def up_to_zero_code(self, p):
         return 'G01 Z0'
         return 'G01 Z0'
@@ -110,8 +131,10 @@ M0""".format(toolchangez=self.coordinate_format%(p.coords_decimals, toolchangez)
 
 
     def end_code(self, p):
     def end_code(self, p):
         coords_xy = p['toolchange_xy']
         coords_xy = p['toolchange_xy']
-        gcode = ('G00 Z' + self.feedrate_format % (p.fr_decimals, p.endz) + "\n")
-        gcode += 'G00 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + "\n"
+        gcode = ('G00 Z' + self.feedrate_format %(p.fr_decimals, p.endz) + "\n")
+
+        if coords_xy is not None:
+            gcode += 'G00 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + "\n"
         return gcode
         return gcode
 
 
     def feedrate_code(self, p):
     def feedrate_code(self, p):

+ 4 - 1
postprocessors/grbl_laser.py

@@ -59,8 +59,11 @@ class grbl_laser(FlatCAMPostProc):
                ' F' + str(self.feedrate_format %(p.fr_decimals, p.feedrate))
                ' F' + str(self.feedrate_format %(p.fr_decimals, p.feedrate))
 
 
     def end_code(self, p):
     def end_code(self, p):
+        coords_xy = p['toolchange_xy']
         gcode = ('G00 Z' + self.feedrate_format %(p.fr_decimals, p.endz) + "\n")
         gcode = ('G00 Z' + self.feedrate_format %(p.fr_decimals, p.endz) + "\n")
-        gcode += 'G00 X0Y0'
+
+        if coords_xy is not None:
+            gcode += 'G00 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + "\n"
         return gcode
         return gcode
 
 
     def feedrate_code(self, p):
     def feedrate_code(self, p):

+ 17 - 4
postprocessors/line_xyz.py

@@ -29,7 +29,10 @@ class line_xyz(FlatCAMPostProc):
 
 
         gcode += '(Z_Move: ' + str(p['z_move']) + units + ')\n'
         gcode += '(Z_Move: ' + str(p['z_move']) + units + ')\n'
         gcode += '(Z Toolchange: ' + str(p['toolchangez']) + units + ')\n'
         gcode += '(Z Toolchange: ' + str(p['toolchangez']) + units + ')\n'
-        gcode += '(X,Y Toolchange: ' + "%.4f, %.4f" % (coords_xy[0], coords_xy[1]) + units + ')\n'
+        if coords_xy is not None:
+            gcode += '(X,Y Toolchange: ' + "%.4f, %.4f" % (coords_xy[0], coords_xy[1]) + units + ')\n'
+        else:
+            gcode += '(X,Y Toolchange: ' + "None" + units + ')\n'
         gcode += '(Z Start: ' + str(p['startz']) + units + ')\n'
         gcode += '(Z Start: ' + str(p['startz']) + units + ')\n'
         gcode += '(Z End: ' + str(p['endz']) + units + ')\n'
         gcode += '(Z End: ' + str(p['endz']) + units + ')\n'
         gcode += '(Steps per circle: ' + str(p['steps_per_circle']) + ')\n'
         gcode += '(Steps per circle: ' + str(p['steps_per_circle']) + ')\n'
@@ -71,8 +74,14 @@ class line_xyz(FlatCAMPostProc):
     def toolchange_code(self, p):
     def toolchange_code(self, p):
         toolchangez = p.toolchangez
         toolchangez = p.toolchangez
         toolchangexy = p.toolchange_xy
         toolchangexy = p.toolchange_xy
-        toolchangex = toolchangexy[0]
-        toolchangey = toolchangexy[1]
+        gcode = ''
+
+        if toolchangexy is not None:
+            toolchangex = toolchangexy[0]
+            toolchangey = toolchangexy[1]
+        else:
+            toolchangex = p.x
+            toolchangey = p.y
 
 
         no_drills = 1
         no_drills = 1
 
 
@@ -132,7 +141,11 @@ M0""".format(toolchangex=self.coordinate_format%(p.coords_decimals, toolchangex)
         return g
         return g
 
 
     def end_code(self, p):
     def end_code(self, p):
-        g = ('G00 ' + self.position_code(p)).format(**p)
+        coords_xy = p['toolchange_xy']
+        if coords_xy is not None:
+            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)
         g += ' Z' + self.coordinate_format % (p.coords_decimals, p.endz)
         g += ' Z' + self.coordinate_format % (p.coords_decimals, p.endz)
         return g
         return g
 
 

+ 15 - 4
postprocessors/manual_toolchange.py

@@ -29,7 +29,10 @@ class manual_toolchange(FlatCAMPostProc):
 
 
         gcode += '(Z_Move: ' + str(p['z_move']) + units + ')\n'
         gcode += '(Z_Move: ' + str(p['z_move']) + units + ')\n'
         gcode += '(Z Toolchange: ' + str(p['toolchangez']) + units + ')\n'
         gcode += '(Z Toolchange: ' + str(p['toolchangez']) + units + ')\n'
-        gcode += '(X,Y Toolchange: ' + "%.4f, %.4f" % (coords_xy[0], coords_xy[1]) + units + ')\n'
+        if coords_xy is not None:
+            gcode += '(X,Y Toolchange: ' + "%.4f, %.4f" % (coords_xy[0], coords_xy[1]) + units + ')\n'
+        else:
+            gcode += '(X,Y Toolchange: ' + "0.0, 0.0" + units + ')\n'
         gcode += '(Z Start: ' + str(p['startz']) + units + ')\n'
         gcode += '(Z Start: ' + str(p['startz']) + units + ')\n'
         gcode += '(Z End: ' + str(p['endz']) + units + ')\n'
         gcode += '(Z End: ' + str(p['endz']) + units + ')\n'
         gcode += '(Steps per circle: ' + str(p['steps_per_circle']) + ')\n'
         gcode += '(Steps per circle: ' + str(p['steps_per_circle']) + ')\n'
@@ -62,8 +65,13 @@ class manual_toolchange(FlatCAMPostProc):
     def toolchange_code(self, p):
     def toolchange_code(self, p):
         toolchangez = p.toolchangez
         toolchangez = p.toolchangez
         toolchangexy = p.toolchange_xy
         toolchangexy = p.toolchange_xy
-        toolchangex = toolchangexy[0]
-        toolchangey = toolchangexy[1]
+
+        if toolchangexy is not None:
+            toolchangex = toolchangexy[0]
+            toolchangey = toolchangexy[1]
+        else:
+            toolchangex = 0.0
+            toolchangey = 0.0
 
 
         no_drills = 1
         no_drills = 1
 
 
@@ -128,7 +136,10 @@ M0
     def end_code(self, p):
     def end_code(self, p):
         coords_xy = p['toolchange_xy']
         coords_xy = p['toolchange_xy']
         gcode = ('G00 Z' + self.feedrate_format %(p.fr_decimals, p.endz) + "\n")
         gcode = ('G00 Z' + self.feedrate_format %(p.fr_decimals, p.endz) + "\n")
-        gcode += 'G00 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + "\n"
+        if coords_xy is not None:
+            gcode += 'G00 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + "\n"
+        else:
+            gcode += 'G00 X0 Y0' + "\n"
         return gcode
         return gcode
 
 
     def feedrate_code(self, p):
     def feedrate_code(self, p):

+ 10 - 1
postprocessors/marlin.py

@@ -9,6 +9,7 @@ class marlin(FlatCAMPostProc):
 
 
     def start_code(self, p):
     def start_code(self, p):
         units = ' ' + str(p['units']).lower()
         units = ' ' + str(p['units']).lower()
+        coords_xy = p['toolchange_xy']
         gcode = ''
         gcode = ''
 
 
         if str(p['options']['type']) == 'Geometry':
         if str(p['options']['type']) == 'Geometry':
@@ -29,6 +30,12 @@ class marlin(FlatCAMPostProc):
 
 
         gcode += ';Z_Move: ' + str(p['z_move']) + units + '\n'
         gcode += ';Z_Move: ' + str(p['z_move']) + units + '\n'
         gcode += ';Z Toolchange: ' + str(p['toolchangez']) + units + '\n'
         gcode += ';Z Toolchange: ' + str(p['toolchangez']) + units + '\n'
+
+        if coords_xy is not None:
+            gcode += ';X,Y Toolchange: ' + "%.4f, %.4f" % (coords_xy[0], coords_xy[1]) + units + '\n'
+        else:
+            gcode += ';X,Y Toolchange: ' + "None" + units + '\n'
+
         gcode += ';Z Start: ' + str(p['startz']) + units + '\n'
         gcode += ';Z Start: ' + str(p['startz']) + units + '\n'
         gcode += ';Z End: ' + str(p['endz']) + units + '\n'
         gcode += ';Z End: ' + str(p['endz']) + units + '\n'
         gcode += ';Steps per circle: ' + str(p['steps_per_circle']) + '\n'
         gcode += ';Steps per circle: ' + str(p['steps_per_circle']) + '\n'
@@ -104,7 +111,9 @@ M0 Change to Tool Dia = {toolC}
     def end_code(self, p):
     def end_code(self, p):
         coords_xy = p['toolchange_xy']
         coords_xy = p['toolchange_xy']
         gcode = ('G0 Z' + self.feedrate_format %(p.fr_decimals, p.endz) + " " + self.feedrate_rapid_code(p) + "\n")
         gcode = ('G0 Z' + self.feedrate_format %(p.fr_decimals, p.endz) + " " + self.feedrate_rapid_code(p) + "\n")
-        gcode += 'G0 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + " " + self.feedrate_rapid_code(p) + "\n"
+
+        if coords_xy is not None:
+            gcode += 'G0 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + " " + self.feedrate_rapid_code(p) + "\n"
 
 
         return gcode
         return gcode