From ea604380db3a257032fa85c63c95d22fc28dd2c1 Mon Sep 17 00:00:00 2001 From: Guillaume Seguin Date: Wed, 15 May 2013 21:49:12 +0200 Subject: [PATCH] Cleanup duration estimation Estimation duration now uses the already parsed GCode instead of reparsing it. It also computes a per layer duration estimation which can probably be used to produce better ETAs. The only difference is that it does not compute duration for Z layers changes, but it was probably super wrong already given how it is done (it only changes the estimation by than 2s over 2 hours of print and 54 layers, and the feedrate stuff mixes all the axes together). I also detected a few potential issues in the code, which are marked by FIXMEs. --- gcoder.py | 154 +++++++++++++++++++++++++----------------------------- 1 file changed, 70 insertions(+), 84 deletions(-) diff --git a/gcoder.py b/gcoder.py index 86217ce..24f43b8 100755 --- a/gcoder.py +++ b/gcoder.py @@ -19,15 +19,6 @@ import re import math import datetime -def get_coordinate_value(axis, parts): - for i in parts: - if (axis in i): - return float(i[1:]) - return None - -def hypot3d(X1, Y1, Z1, X2 = 0.0, Y2 = 0.0, Z2 = 0.0): - return math.hypot(X2-X1, math.hypot(Y2-Y1, Z2-Z1)) - gcode_parsed_args = ["x", "y", "e", "f", "z", "p"] class Line(object): @@ -47,6 +38,8 @@ class Line(object): command = None is_move = False + duration = None + def __init__(self, l): self.raw = l.lower() if ";" in self.raw: @@ -67,14 +60,17 @@ class Line(object): if code in gcode_parsed_args and len(bit) > 1: setattr(self, code, float(bit[1:])) - def __str__(self): - return self.raw + def __repr__(self): + return self.raw.upper() class Layer(object): - def __init__(self,lines): + + lines = None + duration = None + + def __init__(self, lines): self.lines = lines - - + def measure(self): xmin = float("inf") ymin = float("inf") @@ -121,9 +117,12 @@ class Layer(object): current_z = z or current_z return (xmin, xmax), (ymin, ymax), (zmin, zmax) - class GCode(object): + + lines = None + layers = None + def __init__(self,data): self.lines = [Line(l2) for l2 in (l.strip() for l in data) @@ -132,7 +131,7 @@ class GCode(object): self._create_layers() def _preprocess(self): - #checks for G20, G21, G90 and G91, sets imperial and relative flags + """Checks for G20, G21, G90 and G91, sets imperial and relative flags""" imperial = False relative = False relative_e = False @@ -155,16 +154,14 @@ class GCode(object): line.relative = relative line.relative_e = relative_e line.parse_coordinates(imperial) - + + # FIXME : looks like this needs to be tested with list Z on move def _create_layers(self): - self.layers = [] + layers = {} prev_z = None cur_z = 0 cur_lines = [] - layer_index = [] - - temp_layers = {} for line in self.lines: if line.command == "G92" and line.z != None: cur_z = line.z @@ -174,40 +171,35 @@ class GCode(object): cur_z += line.z else: cur_z = line.z - - if cur_z != prev_z: - old_lines = temp_layers.pop(prev_z,[]) - old_lines += cur_lines - temp_layers[prev_z] = old_lines - if not prev_z in layer_index: - layer_index.append(prev_z) - + if cur_z != prev_z: + old_lines = layers.get(prev_z, []) + old_lines += cur_lines + layers[prev_z] = old_lines cur_lines = [] - + cur_lines.append(line) prev_z = cur_z - - - old_lines = temp_layers.pop(prev_z,[]) - old_lines += cur_lines - temp_layers[prev_z] = old_lines - if not prev_z in layer_index: - layer_index.append(prev_z) - - layer_index.sort() - - for idx in layer_index: - cur_lines = temp_layers[idx] + old_lines = layers.pop(prev_z, []) + old_lines += cur_lines + layers[prev_z] = old_lines + + for idx in layers.keys(): + cur_lines = layers[idx] has_movement = False - for l in cur_lines: + for l in layers[idx]: if l.is_move and l.e != None: has_movement = True break - + if idx > 15: + print idx, has_movement, cur_lines if has_movement: - self.layers.append(Layer(cur_lines)) + layers[idx] = Layer(layers[idx]) + else: + del layers[idx] + + self.layers = layers def num_layers(self): return len(self.layers) @@ -220,7 +212,7 @@ class GCode(object): ymax = float("-inf") zmax = float("-inf") - for l in self.layers: + for l in self.layers.values(): (xm, xM), (ym, yM), (zm, zM) = l.measure() xmin = min(xm, xmin) xmax = max(xM, xmax) @@ -257,7 +249,7 @@ class GCode(object): return total_e - def estimate_duration(self, g): + def estimate_duration(self): lastx = lasty = lastz = laste = lastf = 0.0 x = y = z = e = f = 0.0 currenttravel = 0.0 @@ -272,58 +264,53 @@ class GCode(object): # get device caps from firmware: max speed, acceleration/axis (including extruder) # calculate the maximum move duration accounting for above ;) # self.log(".... estimating ....") - for i in g: - i = i.split(";")[0] - if "G4" in i or "G1" in i: - if "G4" in i: - parts = i.split(" ") - moveduration = get_coordinate_value("P", parts[1:]) - if moveduration is None: + zs = self.layers.keys() + zs.sort() + for z in zs: + layer = self.layers[z] + for line in layer.lines: + if line.command not in ["G4", "G1"]: + continue + if line.command == "G4": + moveduration = line.p + if not moveduration: continue else: moveduration /= 1000.0 - if "G1" in i: - parts = i.split(" ") - x = get_coordinate_value("X", parts[1:]) - if x is None: x = lastx - y = get_coordinate_value("Y", parts[1:]) - if y is None: y = lasty - z = get_coordinate_value("Z", parts[1:]) - if (z is None) or (z lastz: - layercount +=1 - #self.log("layer z: ", lastz, " will take: ", time.strftime('%H:%M:%S', time.gmtime(totalduration-layerbeginduration))) - layerbeginduration = totalduration - lastx = x lasty = y - lastz = z laste = e lastf = f - #self.log("Total Duration: " #, time.strftime('%H:%M:%S', time.gmtime(totalduration))) - return "{0:d} layers, ".format(int(layercount)) + str(datetime.timedelta(seconds = int(totalduration))) + layer.duration = totalduration - layerbeginduration + layerbeginduration = totalduration + + return "%d layers, %s" % (len(self.layers), str(datetime.timedelta(seconds = int(totalduration)))) def main(): if len(sys.argv) < 2: @@ -332,8 +319,7 @@ def main(): # d = [i.replace("\n","") for i in open(sys.argv[1])] # gcode = GCode(d) - d = list(open(sys.argv[1])) - gcode = GCode(d) + gcode = GCode(open(sys.argv[1])) gcode.measure() @@ -343,7 +329,7 @@ def main(): print "\tZ: %0.02f - %0.02f (%0.02f)" % (gcode.zmin,gcode.zmax,gcode.height) print "Filament used: %0.02fmm" % gcode.filament_length() print "Number of layers: %d" % gcode.num_layers() - print "Estimated duration (pessimistic): ", gcode.estimate_duration(d) + print "Estimated duration (pessimistic): %s" % gcode.estimate_duration() if __name__ == '__main__':