printrun-src/printrun/gcoder.py

changeset 46
cce0af6351f0
parent 15
0bbb006204fc
--- a/printrun-src/printrun/gcoder.py	Tue Jan 19 20:45:09 2021 +0100
+++ b/printrun-src/printrun/gcoder.py	Wed Jan 20 10:15:13 2021 +0100
@@ -1,12 +1,13 @@
-#!/usr/bin/env python
-# This file is copied from GCoder.
+#!/usr/bin/env python3
 #
-# GCoder is free software: you can redistribute it and/or modify
+# This file is part of the Printrun suite.
+#
+# Printrun is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
 # (at your option) any later version.
 #
-# GCoder is distributed in the hope that it will be useful,
+# Printrun is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 # GNU General Public License for more details.
@@ -22,15 +23,15 @@
 from array import array
 
 gcode_parsed_args = ["x", "y", "e", "f", "z", "i", "j"]
-gcode_parsed_nonargs = ["g", "t", "m", "n"]
-to_parse = "".join(gcode_parsed_args + gcode_parsed_nonargs)
-gcode_exp = re.compile("\([^\(\)]*\)|;.*|[/\*].*\n|([%s])([-+]?[0-9]*\.?[0-9]*)" % to_parse)
+gcode_parsed_nonargs = 'gtmnd'
+to_parse = "".join(gcode_parsed_args) + gcode_parsed_nonargs
+gcode_exp = re.compile("\([^\(\)]*\)|;.*|[/\*].*\n|([%s])\s*([-+]?[0-9]*\.?[0-9]*)" % to_parse)
 gcode_strip_comment_exp = re.compile("\([^\(\)]*\)|;.*|[/\*].*\n")
 m114_exp = re.compile("\([^\(\)]*\)|[/\*].*\n|([XYZ]):?([-+]?[0-9]*\.?[0-9]*)")
 specific_exp = "(?:\([^\(\)]*\))|(?:;.*)|(?:[/\*].*\n)|(%s[-+]?[0-9]*\.?[0-9]*)"
 move_gcodes = ["G0", "G1", "G2", "G3"]
 
-class PyLine(object):
+class PyLine:
 
     __slots__ = ('x', 'y', 'z', 'e', 'f', 'i', 'j',
                  'raw', 'command', 'is_move',
@@ -45,7 +46,7 @@
     def __getattr__(self, name):
         return None
 
-class PyLightLine(object):
+class PyLightLine:
 
     __slots__ = ('raw', 'command')
 
@@ -56,10 +57,10 @@
         return None
 
 try:
-    import gcoder_line
+    from . import gcoder_line
     Line = gcoder_line.GLine
     LightLine = gcoder_line.GLightLine
-except Exception, e:
+except Exception as e:
     logging.warning("Memory-efficient GCoder implementation unavailable: %s" % e)
     Line = PyLine
     LightLine = PyLightLine
@@ -107,8 +108,9 @@
     def __init__(self, lines, z = None):
         super(Layer, self).__init__(lines)
         self.z = z
+        self.duration = 0
 
-class GCode(object):
+class GCode:
 
     line_class = Line
 
@@ -121,6 +123,7 @@
     append_layer_id = None
 
     imperial = False
+    cutting = False
     relative = False
     relative_e = False
     current_tool = 0
@@ -217,7 +220,9 @@
     layers_count = property(_get_layers_count)
 
     def __init__(self, data = None, home_pos = None,
-                 layer_callback = None, deferred = False):
+                 layer_callback = None, deferred = False,
+                 cutting_as_extrusion = False):
+        self.cutting_as_extrusion = cutting_as_extrusion
         if not deferred:
             self.prepare(data, home_pos, layer_callback)
 
@@ -240,6 +245,8 @@
             self.layer_idxs = array('I', [])
             self.line_idxs = array('I', [])
 
+    def has_index(self, i):
+        return i < len(self)
     def __len__(self):
         return len(self.line_idxs)
 
@@ -314,7 +321,7 @@
             self.lines.append(gline)
             self.append_layer.append(gline)
             self.layer_idxs.append(self.append_layer_id)
-            self.line_idxs.append(len(self.append_layer))
+            self.line_idxs.append(len(self.append_layer)-1)
         return gline
 
     def _preprocess(self, lines = None, build_layers = False,
@@ -338,6 +345,7 @@
         offset_e = self.offset_e
         total_e = self.total_e
         max_e = self.max_e
+        cutting = self.cutting
 
         current_e_multi = self.current_e_multi[current_tool]
         offset_e_multi = self.offset_e_multi[current_tool]
@@ -367,7 +375,8 @@
             # get device caps from firmware: max speed, acceleration/axis
             # (including extruder)
             # calculate the maximum move duration accounting for above ;)
-            lastx = lasty = lastz = laste = lastf = 0.0
+            lastx = lasty = lastz = None
+            laste = lastf = 0
             lastdx = 0
             lastdy = 0
             x = y = e = f = 0.0
@@ -383,15 +392,41 @@
             layer_idxs = self.layer_idxs = []
             line_idxs = self.line_idxs = []
 
-            layer_id = 0
-            layer_line = 0
 
             last_layer_z = None
             prev_z = None
-            prev_base_z = (None, None)
             cur_z = None
             cur_lines = []
 
+            def append_lines(lines, isEnd):
+                if not build_layers:
+                    return
+                nonlocal layerbeginduration, last_layer_z
+                if cur_layer_has_extrusion and prev_z != last_layer_z \
+                        or not all_layers or isEnd:
+                    layer = Layer([], prev_z)
+                    last_layer_z = prev_z
+                    finished_layer = len(all_layers)-1 if all_layers else None
+                    all_layers.append(layer)
+                else:
+                    layer = all_layers[-1]
+                    finished_layer = None
+                layer_id = len(all_layers)-1
+                layer_line = len(layer)
+                for i, ln in enumerate(lines):
+                    layer.append(ln)
+                    layer_idxs.append(layer_id)
+                    line_idxs.append(layer_line+i)
+                layer.duration += totalduration - layerbeginduration
+                layerbeginduration = totalduration
+                if layer_callback:
+                    # we finish a layer when inserting the next
+                    if finished_layer is not None:
+                        layer_callback(self, finished_layer)
+                    # notify about end layer, there will not be next
+                    if isEnd:
+                        layer_callback(self, layer_id)
+
         if self.line_class != Line:
             get_line = lambda l: Line(l.raw)
         else:
@@ -422,12 +457,20 @@
                 elif line.command == "M83":
                     relative_e = True
                 elif line.command[0] == "T":
-                    current_tool = int(line.command[1:])
-                    while(current_tool+1>len(self.current_e_multi)):
+                    try:
+                        current_tool = int(line.command[1:])
+                    except:
+                        pass #handle T? by treating it as no tool change
+                    while current_tool+1 > len(self.current_e_multi):
                         self.current_e_multi+=[0]
                         self.offset_e_multi+=[0]
                         self.total_e_multi+=[0]
                         self.max_e_multi+=[0]
+                elif line.command == "M3" or line.command == "M4":
+                    cutting = True
+                elif line.command == "M5":
+                    cutting = False
+
                 current_e_multi = self.current_e_multi[current_tool]
                 offset_e_multi = self.offset_e_multi[current_tool]
                 total_e_multi = self.total_e_multi[current_tool]
@@ -504,6 +547,8 @@
                     elif line.command == "G92":
                         offset_e = current_e - line.e
                         offset_e_multi = current_e_multi - line.e
+                if cutting and self.cutting_as_extrusion:
+                    line.extruding = True
 
                 self.current_e_multi[current_tool]=current_e_multi
                 self.offset_e_multi[current_tool]=offset_e_multi
@@ -516,11 +561,12 @@
                     if line.is_move:
                         if line.extruding:
                             if line.current_x is not None:
-                                xmin_e = min(xmin_e, line.current_x)
-                                xmax_e = max(xmax_e, line.current_x)
+                                # G0 X10 ; G1 X20 E5 results in 10..20 even as G0 is not extruding
+                                xmin_e = min(xmin_e, line.current_x, xmin_e if lastx is None else lastx)
+                                xmax_e = max(xmax_e, line.current_x, xmax_e if lastx is None else lastx)
                             if line.current_y is not None:
-                                ymin_e = min(ymin_e, line.current_y)
-                                ymax_e = max(ymax_e, line.current_y)
+                                ymin_e = min(ymin_e, line.current_y, ymin_e if lasty is None else lasty)
+                                ymax_e = max(ymax_e, line.current_y, ymax_e if lasty is None else lasty)
                         if max_e <= 0:
                             if line.current_x is not None:
                                 xmin = min(xmin, line.current_x)
@@ -531,9 +577,9 @@
 
                     # Compute duration
                     if line.command == "G0" or line.command == "G1":
-                        x = line.x if line.x is not None else lastx
-                        y = line.y if line.y is not None else lasty
-                        z = line.z if line.z is not None else lastz
+                        x = line.x if line.x is not None else (lastx or 0)
+                        y = line.y if line.y is not None else (lasty or 0)
+                        z = line.z if line.z is not None else (lastz or 0)
                         e = line.e if line.e is not None else laste
                         # mm/s vs mm/m => divide by 60
                         f = line.f / 60.0 if line.f is not None else lastf
@@ -553,15 +599,15 @@
                         # The following code tries to fix it by forcing a full
                         # reacceleration if this move is in the opposite direction
                         # of the previous one
-                        dx = x - lastx
-                        dy = y - lasty
+                        dx = x - (lastx or 0)
+                        dy = y - (lasty or 0)
                         if dx * lastdx + dy * lastdy <= 0:
                             lastf = 0
 
                         currenttravel = math.hypot(dx, dy)
                         if currenttravel == 0:
                             if line.z is not None:
-                                currenttravel = abs(line.z) if line.relative else abs(line.z - lastz)
+                                currenttravel = abs(line.z) if line.relative else abs(line.z - (lastz or 0))
                             elif line.e is not None:
                                 currenttravel = abs(line.e) if line.relative_e else abs(line.e - laste)
                         # Feedrate hasn't changed, no acceleration/decceleration planned
@@ -606,47 +652,14 @@
                             else:
                                 cur_z = line.z
 
-                    # FIXME: the logic behind this code seems to work, but it might be
-                    # broken
-                    if cur_z != prev_z:
-                        if prev_z is not None and last_layer_z is not None:
-                            offset = self.est_layer_height if self.est_layer_height else 0.01
-                            if abs(prev_z - last_layer_z) < offset:
-                                if self.est_layer_height is None:
-                                    zs = sorted([l.z for l in all_layers if l.z is not None])
-                                    heights = [round(zs[i + 1] - zs[i], 3) for i in range(len(zs) - 1)]
-                                    heights = [height for height in heights if height]
-                                    if len(heights) >= 2: self.est_layer_height = heights[1]
-                                    elif heights: self.est_layer_height = heights[0]
-                                    else: self.est_layer_height = 0.1
-                                base_z = round(prev_z - (prev_z % self.est_layer_height), 2)
-                            else:
-                                base_z = round(prev_z, 2)
-                        else:
-                            base_z = prev_z
-
-                        if base_z != prev_base_z:
-                            new_layer = Layer(cur_lines, base_z)
-                            new_layer.duration = totalduration - layerbeginduration
-                            layerbeginduration = totalduration
-                            all_layers.append(new_layer)
-                            if cur_layer_has_extrusion and prev_z not in all_zs:
-                                all_zs.add(prev_z)
-                            cur_lines = []
-                            cur_layer_has_extrusion = False
-                            layer_id += 1
-                            layer_line = 0
-                            last_layer_z = base_z
-                            if layer_callback is not None:
-                                layer_callback(self, len(all_layers) - 1)
-
-                        prev_base_z = base_z
+                    if cur_z != prev_z and cur_layer_has_extrusion:
+                        append_lines(cur_lines, False)
+                        all_zs.add(prev_z)
+                        cur_lines = []
+                        cur_layer_has_extrusion = False
 
             if build_layers:
                 cur_lines.append(true_line)
-                layer_idxs.append(layer_id)
-                line_idxs.append(layer_line)
-                layer_line += 1
                 prev_z = cur_z
             # ## Loop done
 
@@ -669,17 +682,14 @@
         self.offset_e_multi[current_tool]=offset_e_multi
         self.max_e_multi[current_tool]=max_e_multi
         self.total_e_multi[current_tool]=total_e_multi
+        self.cutting = cutting
 
 
         # Finalize layers
         if build_layers:
             if cur_lines:
-                new_layer = Layer(cur_lines, prev_z)
-                new_layer.duration = totalduration - layerbeginduration
-                layerbeginduration = totalduration
-                all_layers.append(new_layer)
-                if cur_layer_has_extrusion and prev_z not in all_zs:
-                    all_zs.add(prev_z)
+                append_lines(cur_lines, True)
+                all_zs.add(prev_z)
 
             self.append_layer_id = len(all_layers)
             self.append_layer = Layer([])
@@ -689,7 +699,7 @@
             self.line_idxs = array('I', line_idxs)
 
             # Compute bounding box
-            all_zs = self.all_zs.union(set([zmin])).difference(set([None]))
+            all_zs = self.all_zs.union({zmin}).difference({None})
             zmin = min(all_zs)
             zmax = max(all_zs)
 
@@ -731,25 +741,25 @@
 
 def main():
     if len(sys.argv) < 2:
-        print "usage: %s filename.gcode" % sys.argv[0]
+        print("usage: %s filename.gcode" % sys.argv[0])
         return
 
-    print "Line object size:", sys.getsizeof(Line("G0 X0"))
-    print "Light line object size:", sys.getsizeof(LightLine("G0 X0"))
+    print("Line object size:", sys.getsizeof(Line("G0 X0")))
+    print("Light line object size:", sys.getsizeof(LightLine("G0 X0")))
     gcode = GCode(open(sys.argv[1], "rU"))
 
-    print "Dimensions:"
+    print("Dimensions:")
     xdims = (gcode.xmin, gcode.xmax, gcode.width)
-    print "\tX: %0.02f - %0.02f (%0.02f)" % xdims
+    print("\tX: %0.02f - %0.02f (%0.02f)" % xdims)
     ydims = (gcode.ymin, gcode.ymax, gcode.depth)
-    print "\tY: %0.02f - %0.02f (%0.02f)" % ydims
+    print("\tY: %0.02f - %0.02f (%0.02f)" % ydims)
     zdims = (gcode.zmin, gcode.zmax, gcode.height)
-    print "\tZ: %0.02f - %0.02f (%0.02f)" % zdims
-    print "Filament used: %0.02fmm" % gcode.filament_length
+    print("\tZ: %0.02f - %0.02f (%0.02f)" % zdims)
+    print("Filament used: %0.02fmm" % gcode.filament_length)
     for i in enumerate(gcode.filament_length_multi):
-        print "E%d %0.02fmm" % (i[0],i[1])
-    print "Number of layers: %d" % gcode.layers_count
-    print "Estimated duration: %s" % gcode.estimate_duration()[1]
+        print("E%d %0.02fmm" % (i[0],i[1]))
+    print("Number of layers: %d" % gcode.layers_count)
+    print("Estimated duration: %s" % gcode.estimate_duration()[1])
 
 if __name__ == '__main__':
     main()

mercurial