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.master
parent
9816ea7c8a
commit
ea604380db
154
gcoder.py
154
gcoder.py
|
@ -19,15 +19,6 @@ import re
|
||||||
import math
|
import math
|
||||||
import datetime
|
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"]
|
gcode_parsed_args = ["x", "y", "e", "f", "z", "p"]
|
||||||
|
|
||||||
class Line(object):
|
class Line(object):
|
||||||
|
@ -47,6 +38,8 @@ class Line(object):
|
||||||
command = None
|
command = None
|
||||||
is_move = False
|
is_move = False
|
||||||
|
|
||||||
|
duration = None
|
||||||
|
|
||||||
def __init__(self, l):
|
def __init__(self, l):
|
||||||
self.raw = l.lower()
|
self.raw = l.lower()
|
||||||
if ";" in self.raw:
|
if ";" in self.raw:
|
||||||
|
@ -67,14 +60,17 @@ class Line(object):
|
||||||
if code in gcode_parsed_args and len(bit) > 1:
|
if code in gcode_parsed_args and len(bit) > 1:
|
||||||
setattr(self, code, float(bit[1:]))
|
setattr(self, code, float(bit[1:]))
|
||||||
|
|
||||||
def __str__(self):
|
def __repr__(self):
|
||||||
return self.raw
|
return self.raw.upper()
|
||||||
|
|
||||||
class Layer(object):
|
class Layer(object):
|
||||||
def __init__(self,lines):
|
|
||||||
|
lines = None
|
||||||
|
duration = None
|
||||||
|
|
||||||
|
def __init__(self, lines):
|
||||||
self.lines = lines
|
self.lines = lines
|
||||||
|
|
||||||
|
|
||||||
def measure(self):
|
def measure(self):
|
||||||
xmin = float("inf")
|
xmin = float("inf")
|
||||||
ymin = float("inf")
|
ymin = float("inf")
|
||||||
|
@ -121,9 +117,12 @@ class Layer(object):
|
||||||
current_z = z or current_z
|
current_z = z or current_z
|
||||||
|
|
||||||
return (xmin, xmax), (ymin, ymax), (zmin, zmax)
|
return (xmin, xmax), (ymin, ymax), (zmin, zmax)
|
||||||
|
|
||||||
|
|
||||||
class GCode(object):
|
class GCode(object):
|
||||||
|
|
||||||
|
lines = None
|
||||||
|
layers = None
|
||||||
|
|
||||||
def __init__(self,data):
|
def __init__(self,data):
|
||||||
self.lines = [Line(l2) for l2 in
|
self.lines = [Line(l2) for l2 in
|
||||||
(l.strip() for l in data)
|
(l.strip() for l in data)
|
||||||
|
@ -132,7 +131,7 @@ class GCode(object):
|
||||||
self._create_layers()
|
self._create_layers()
|
||||||
|
|
||||||
def _preprocess(self):
|
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
|
imperial = False
|
||||||
relative = False
|
relative = False
|
||||||
relative_e = False
|
relative_e = False
|
||||||
|
@ -155,16 +154,14 @@ class GCode(object):
|
||||||
line.relative = relative
|
line.relative = relative
|
||||||
line.relative_e = relative_e
|
line.relative_e = relative_e
|
||||||
line.parse_coordinates(imperial)
|
line.parse_coordinates(imperial)
|
||||||
|
|
||||||
|
# FIXME : looks like this needs to be tested with list Z on move
|
||||||
def _create_layers(self):
|
def _create_layers(self):
|
||||||
self.layers = []
|
layers = {}
|
||||||
|
|
||||||
prev_z = None
|
prev_z = None
|
||||||
cur_z = 0
|
cur_z = 0
|
||||||
cur_lines = []
|
cur_lines = []
|
||||||
layer_index = []
|
|
||||||
|
|
||||||
temp_layers = {}
|
|
||||||
for line in self.lines:
|
for line in self.lines:
|
||||||
if line.command == "G92" and line.z != None:
|
if line.command == "G92" and line.z != None:
|
||||||
cur_z = line.z
|
cur_z = line.z
|
||||||
|
@ -174,40 +171,35 @@ class GCode(object):
|
||||||
cur_z += line.z
|
cur_z += line.z
|
||||||
else:
|
else:
|
||||||
cur_z = line.z
|
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:
|
if cur_z != prev_z:
|
||||||
layer_index.append(prev_z)
|
old_lines = layers.get(prev_z, [])
|
||||||
|
old_lines += cur_lines
|
||||||
|
layers[prev_z] = old_lines
|
||||||
cur_lines = []
|
cur_lines = []
|
||||||
|
|
||||||
cur_lines.append(line)
|
cur_lines.append(line)
|
||||||
prev_z = cur_z
|
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:
|
old_lines = layers.pop(prev_z, [])
|
||||||
layer_index.append(prev_z)
|
old_lines += cur_lines
|
||||||
|
layers[prev_z] = old_lines
|
||||||
layer_index.sort()
|
|
||||||
|
for idx in layers.keys():
|
||||||
for idx in layer_index:
|
cur_lines = layers[idx]
|
||||||
cur_lines = temp_layers[idx]
|
|
||||||
has_movement = False
|
has_movement = False
|
||||||
for l in cur_lines:
|
for l in layers[idx]:
|
||||||
if l.is_move and l.e != None:
|
if l.is_move and l.e != None:
|
||||||
has_movement = True
|
has_movement = True
|
||||||
break
|
break
|
||||||
|
if idx > 15:
|
||||||
|
print idx, has_movement, cur_lines
|
||||||
if has_movement:
|
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):
|
def num_layers(self):
|
||||||
return len(self.layers)
|
return len(self.layers)
|
||||||
|
@ -220,7 +212,7 @@ class GCode(object):
|
||||||
ymax = float("-inf")
|
ymax = float("-inf")
|
||||||
zmax = float("-inf")
|
zmax = float("-inf")
|
||||||
|
|
||||||
for l in self.layers:
|
for l in self.layers.values():
|
||||||
(xm, xM), (ym, yM), (zm, zM) = l.measure()
|
(xm, xM), (ym, yM), (zm, zM) = l.measure()
|
||||||
xmin = min(xm, xmin)
|
xmin = min(xm, xmin)
|
||||||
xmax = max(xM, xmax)
|
xmax = max(xM, xmax)
|
||||||
|
@ -257,7 +249,7 @@ class GCode(object):
|
||||||
|
|
||||||
return total_e
|
return total_e
|
||||||
|
|
||||||
def estimate_duration(self, g):
|
def estimate_duration(self):
|
||||||
lastx = lasty = lastz = laste = lastf = 0.0
|
lastx = lasty = lastz = laste = lastf = 0.0
|
||||||
x = y = z = e = f = 0.0
|
x = y = z = e = f = 0.0
|
||||||
currenttravel = 0.0
|
currenttravel = 0.0
|
||||||
|
@ -272,58 +264,53 @@ class GCode(object):
|
||||||
# get device caps from firmware: max speed, acceleration/axis (including extruder)
|
# get device caps from firmware: max speed, acceleration/axis (including extruder)
|
||||||
# calculate the maximum move duration accounting for above ;)
|
# calculate the maximum move duration accounting for above ;)
|
||||||
# self.log(".... estimating ....")
|
# self.log(".... estimating ....")
|
||||||
for i in g:
|
zs = self.layers.keys()
|
||||||
i = i.split(";")[0]
|
zs.sort()
|
||||||
if "G4" in i or "G1" in i:
|
for z in zs:
|
||||||
if "G4" in i:
|
layer = self.layers[z]
|
||||||
parts = i.split(" ")
|
for line in layer.lines:
|
||||||
moveduration = get_coordinate_value("P", parts[1:])
|
if line.command not in ["G4", "G1"]:
|
||||||
if moveduration is None:
|
continue
|
||||||
|
if line.command == "G4":
|
||||||
|
moveduration = line.p
|
||||||
|
if not moveduration:
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
moveduration /= 1000.0
|
moveduration /= 1000.0
|
||||||
if "G1" in i:
|
elif line.command == "G1":
|
||||||
parts = i.split(" ")
|
x = line.x if line.x != None else lastx
|
||||||
x = get_coordinate_value("X", parts[1:])
|
y = line.y if line.y != None else lasty
|
||||||
if x is None: x = lastx
|
e = line.e if line.e != None else laste
|
||||||
y = get_coordinate_value("Y", parts[1:])
|
f = line.f / 60.0 if line.f != None else lastf # mm/s vs mm/m
|
||||||
if y is None: y = lasty
|
|
||||||
z = get_coordinate_value("Z", parts[1:])
|
|
||||||
if (z is None) or (z<lastz): z = lastz # Do not increment z if it's below the previous (Lift z on move fix)
|
|
||||||
e = get_coordinate_value("E", parts[1:])
|
|
||||||
if e is None: e = laste
|
|
||||||
f = get_coordinate_value("F", parts[1:])
|
|
||||||
if f is None: f = lastf
|
|
||||||
else: f /= 60.0 # mm/s vs mm/m
|
|
||||||
|
|
||||||
# given last feedrate and current feedrate calculate the distance needed to achieve current feedrate.
|
# given last feedrate and current feedrate calculate the distance needed to achieve current feedrate.
|
||||||
# if travel is longer than req'd distance, then subtract distance to achieve full speed, and add the time it took to get there.
|
# if travel is longer than req'd distance, then subtract distance to achieve full speed, and add the time it took to get there.
|
||||||
# then calculate the time taken to complete the remaining distance
|
# then calculate the time taken to complete the remaining distance
|
||||||
|
|
||||||
currenttravel = hypot3d(x, y, z, lastx, lasty, lastz)
|
currenttravel = math.hypot(x - lastx, y - lasty)
|
||||||
distance = abs(2* ((lastf+f) * (f-lastf) * 0.5 ) / acceleration) #2x because we have to accelerate and decelerate
|
# FIXME: review this better
|
||||||
if distance <= currenttravel and ( lastf + f )!=0 and f!=0:
|
# this looks wrong : there's little chance that the feedrate we'll decelerate to is the previous feedrate
|
||||||
moveduration = 2 * distance / ( lastf + f )
|
# shouldn't we instead look at three consecutive moves ?
|
||||||
|
distance = 2 * abs(((lastf + f) * (f - lastf) * 0.5) / acceleration) # multiply by 2 because we have to accelerate and decelerate
|
||||||
|
if distance <= currenttravel and lastf + f != 0 and f != 0:
|
||||||
|
# Unsure about this formula -- iXce reviewing this code
|
||||||
|
moveduration = 2 * distance / (lastf + f)
|
||||||
currenttravel -= distance
|
currenttravel -= distance
|
||||||
moveduration += currenttravel/f
|
moveduration += currenttravel/f
|
||||||
else:
|
else:
|
||||||
moveduration = math.sqrt( 2 * distance / acceleration )
|
moveduration = math.sqrt(2 * distance / acceleration) # probably buggy : not taking actual travel into account
|
||||||
|
|
||||||
totalduration += moveduration
|
totalduration += moveduration
|
||||||
|
|
||||||
if z > lastz:
|
|
||||||
layercount +=1
|
|
||||||
#self.log("layer z: ", lastz, " will take: ", time.strftime('%H:%M:%S', time.gmtime(totalduration-layerbeginduration)))
|
|
||||||
layerbeginduration = totalduration
|
|
||||||
|
|
||||||
lastx = x
|
lastx = x
|
||||||
lasty = y
|
lasty = y
|
||||||
lastz = z
|
|
||||||
laste = e
|
laste = e
|
||||||
lastf = f
|
lastf = f
|
||||||
|
|
||||||
#self.log("Total Duration: " #, time.strftime('%H:%M:%S', time.gmtime(totalduration)))
|
layer.duration = totalduration - layerbeginduration
|
||||||
return "{0:d} layers, ".format(int(layercount)) + str(datetime.timedelta(seconds = int(totalduration)))
|
layerbeginduration = totalduration
|
||||||
|
|
||||||
|
return "%d layers, %s" % (len(self.layers), str(datetime.timedelta(seconds = int(totalduration))))
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
if len(sys.argv) < 2:
|
if len(sys.argv) < 2:
|
||||||
|
@ -332,8 +319,7 @@ def main():
|
||||||
|
|
||||||
# d = [i.replace("\n","") for i in open(sys.argv[1])]
|
# d = [i.replace("\n","") for i in open(sys.argv[1])]
|
||||||
# gcode = GCode(d)
|
# gcode = GCode(d)
|
||||||
d = list(open(sys.argv[1]))
|
gcode = GCode(open(sys.argv[1]))
|
||||||
gcode = GCode(d)
|
|
||||||
|
|
||||||
gcode.measure()
|
gcode.measure()
|
||||||
|
|
||||||
|
@ -343,7 +329,7 @@ def main():
|
||||||
print "\tZ: %0.02f - %0.02f (%0.02f)" % (gcode.zmin,gcode.zmax,gcode.height)
|
print "\tZ: %0.02f - %0.02f (%0.02f)" % (gcode.zmin,gcode.zmax,gcode.height)
|
||||||
print "Filament used: %0.02fmm" % gcode.filament_length()
|
print "Filament used: %0.02fmm" % gcode.filament_length()
|
||||||
print "Number of layers: %d" % gcode.num_layers()
|
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__':
|
if __name__ == '__main__':
|
||||||
|
|
Loading…
Reference in New Issue