2012-10-17 18:19:51 +00:00
|
|
|
#!/usr/bin/env python
|
|
|
|
# This file is copied from GCoder.
|
|
|
|
#
|
|
|
|
# GCoder 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,
|
|
|
|
# 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.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU General Public License
|
|
|
|
# along with Printrun. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
2012-10-17 17:46:58 +00:00
|
|
|
import sys
|
|
|
|
import re
|
2012-11-08 22:06:59 +00:00
|
|
|
import math
|
2013-05-15 13:28:09 +00:00
|
|
|
import datetime
|
2012-11-08 22:06:59 +00:00
|
|
|
|
2013-05-15 13:23:46 +00:00
|
|
|
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))
|
|
|
|
|
2013-05-15 17:43:26 +00:00
|
|
|
gcode_parsed_args = ["x", "y", "e", "f", "z", "p"]
|
2013-05-15 12:45:17 +00:00
|
|
|
|
2013-05-15 17:43:26 +00:00
|
|
|
class Line(object):
|
2013-05-15 12:45:17 +00:00
|
|
|
|
2013-05-15 17:43:26 +00:00
|
|
|
x = None
|
|
|
|
y = None
|
|
|
|
z = None
|
|
|
|
e = None
|
|
|
|
f = None
|
|
|
|
|
|
|
|
relative = False
|
|
|
|
relative_e = False
|
2013-05-15 12:45:17 +00:00
|
|
|
|
2013-05-15 17:43:26 +00:00
|
|
|
raw = None
|
|
|
|
split_raw = None
|
2013-05-15 12:45:17 +00:00
|
|
|
|
2013-05-15 17:43:26 +00:00
|
|
|
command = None
|
|
|
|
is_move = False
|
2013-05-15 12:45:17 +00:00
|
|
|
|
2013-05-15 17:43:26 +00:00
|
|
|
def __init__(self, l):
|
|
|
|
self.raw = l.lower()
|
|
|
|
if ";" in self.raw:
|
|
|
|
self.raw = self.raw.split(";")[0].rstrip()
|
|
|
|
self.split_raw = self.raw.split(" ")
|
|
|
|
self.command = self.split_raw[0].upper()
|
|
|
|
self.is_move = self.command in ["G0", "G1"]
|
|
|
|
|
|
|
|
def parse_coordinates(self, imperial):
|
|
|
|
if imperial:
|
|
|
|
for bit in self.split_raw:
|
|
|
|
code = bit[0]
|
|
|
|
if code in gcode_parsed_args and len(bit) > 1:
|
|
|
|
setattr(self, code, 25.4*float(bit[1:]))
|
|
|
|
else:
|
|
|
|
for bit in self.split_raw:
|
|
|
|
code = bit[0]
|
|
|
|
if code in gcode_parsed_args and len(bit) > 1:
|
|
|
|
setattr(self, code, float(bit[1:]))
|
|
|
|
|
2013-05-15 12:45:17 +00:00
|
|
|
def __str__(self):
|
|
|
|
return self.raw
|
|
|
|
|
2012-11-08 22:06:59 +00:00
|
|
|
class Layer(object):
|
2013-05-15 12:45:17 +00:00
|
|
|
def __init__(self,lines):
|
|
|
|
self.lines = lines
|
|
|
|
|
|
|
|
|
|
|
|
def measure(self):
|
2013-05-15 13:18:36 +00:00
|
|
|
xmin = float("inf")
|
|
|
|
ymin = float("inf")
|
2013-05-15 12:45:17 +00:00
|
|
|
zmin = 0
|
2013-05-15 13:18:36 +00:00
|
|
|
xmax = float("-inf")
|
|
|
|
ymax = float("-inf")
|
|
|
|
zmax = float("-inf")
|
2013-05-15 12:45:17 +00:00
|
|
|
relative = False
|
|
|
|
relative_e = False
|
|
|
|
|
|
|
|
current_x = 0
|
|
|
|
current_y = 0
|
|
|
|
current_z = 0
|
|
|
|
|
|
|
|
for line in self.lines:
|
2013-05-15 17:43:26 +00:00
|
|
|
if line.command == "G92":
|
2013-05-15 12:45:17 +00:00
|
|
|
current_x = line.x or current_x
|
|
|
|
current_y = line.y or current_y
|
|
|
|
current_z = line.z or current_z
|
|
|
|
|
2013-05-15 17:43:26 +00:00
|
|
|
if line.is_move:
|
2013-05-15 12:45:17 +00:00
|
|
|
x = line.x
|
|
|
|
y = line.y
|
|
|
|
z = line.z
|
|
|
|
|
|
|
|
if line.relative:
|
|
|
|
x = current_x + (x or 0)
|
|
|
|
y = current_y + (y or 0)
|
|
|
|
z = current_z + (z or 0)
|
|
|
|
|
2013-05-15 13:35:48 +00:00
|
|
|
if line.e:
|
|
|
|
if x:
|
|
|
|
xmin = min(xmin, x)
|
|
|
|
xmax = max(xmax, x)
|
|
|
|
if y:
|
|
|
|
ymin = min(ymin, y)
|
|
|
|
ymax = max(ymax, y)
|
2013-05-15 12:45:17 +00:00
|
|
|
if z:
|
2013-05-15 13:35:48 +00:00
|
|
|
zmin = min(zmin, z)
|
|
|
|
zmax = max(zmax, z)
|
2013-05-15 12:45:17 +00:00
|
|
|
|
|
|
|
current_x = x or current_x
|
|
|
|
current_y = y or current_y
|
|
|
|
current_z = z or current_z
|
|
|
|
|
2013-05-15 13:35:48 +00:00
|
|
|
return (xmin, xmax), (ymin, ymax), (zmin, zmax)
|
2013-05-15 12:45:17 +00:00
|
|
|
|
2012-11-08 22:06:59 +00:00
|
|
|
|
|
|
|
class GCode(object):
|
2013-05-15 12:45:17 +00:00
|
|
|
def __init__(self,data):
|
2013-05-15 17:43:26 +00:00
|
|
|
self.lines = [Line(l2) for l2 in
|
|
|
|
(l.strip() for l in data)
|
|
|
|
if l2 and not l2.startswith(";")]
|
2013-05-15 12:45:17 +00:00
|
|
|
self._preprocess()
|
|
|
|
self._create_layers()
|
|
|
|
|
|
|
|
def _preprocess(self):
|
|
|
|
#checks for G20, G21, G90 and G91, sets imperial and relative flags
|
|
|
|
imperial = False
|
|
|
|
relative = False
|
|
|
|
relative_e = False
|
|
|
|
for line in self.lines:
|
2013-05-15 17:43:26 +00:00
|
|
|
if line.command == "G20":
|
2013-05-15 12:45:17 +00:00
|
|
|
imperial = True
|
2013-05-15 17:43:26 +00:00
|
|
|
elif line.command == "G21":
|
2013-05-15 12:45:17 +00:00
|
|
|
imperial = False
|
2013-05-15 17:43:26 +00:00
|
|
|
elif line.command == "G90":
|
2013-05-15 12:45:17 +00:00
|
|
|
relative = False
|
|
|
|
relative_e = False
|
2013-05-15 17:43:26 +00:00
|
|
|
elif line.command == "G91":
|
2013-05-15 12:45:17 +00:00
|
|
|
relative = True
|
|
|
|
relative_e = True
|
2013-05-15 17:43:26 +00:00
|
|
|
elif line.command == "M82":
|
2013-05-15 12:45:17 +00:00
|
|
|
relative_e = False
|
2013-05-15 17:43:26 +00:00
|
|
|
elif line.command == "M83":
|
2013-05-15 12:45:17 +00:00
|
|
|
relative_e = True
|
2013-05-15 17:43:26 +00:00
|
|
|
elif line.is_move:
|
2013-05-15 12:45:17 +00:00
|
|
|
line.relative = relative
|
|
|
|
line.relative_e = relative_e
|
2013-05-15 17:43:26 +00:00
|
|
|
line.parse_coordinates(imperial)
|
2013-05-15 12:45:17 +00:00
|
|
|
|
|
|
|
def _create_layers(self):
|
|
|
|
self.layers = []
|
|
|
|
|
|
|
|
prev_z = None
|
|
|
|
cur_z = 0
|
|
|
|
cur_lines = []
|
|
|
|
layer_index = []
|
|
|
|
|
|
|
|
temp_layers = {}
|
|
|
|
for line in self.lines:
|
2013-05-15 17:43:26 +00:00
|
|
|
if line.command == "G92" and line.z != None:
|
2013-05-15 12:45:17 +00:00
|
|
|
cur_z = line.z
|
2013-05-15 17:43:26 +00:00
|
|
|
elif line.is_move:
|
2013-05-15 12:45:17 +00:00
|
|
|
if line.z != None:
|
|
|
|
if line.relative:
|
|
|
|
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)
|
|
|
|
|
|
|
|
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]
|
|
|
|
has_movement = False
|
|
|
|
for l in cur_lines:
|
2013-05-15 17:43:26 +00:00
|
|
|
if l.is_move and l.e != None:
|
2013-05-15 12:45:17 +00:00
|
|
|
has_movement = True
|
|
|
|
break
|
|
|
|
|
|
|
|
if has_movement:
|
|
|
|
self.layers.append(Layer(cur_lines))
|
|
|
|
|
|
|
|
def num_layers(self):
|
|
|
|
return len(self.layers)
|
|
|
|
|
|
|
|
def measure(self):
|
2013-05-15 13:18:36 +00:00
|
|
|
xmin = float("inf")
|
|
|
|
ymin = float("inf")
|
2013-05-15 12:45:17 +00:00
|
|
|
zmin = 0
|
2013-05-15 13:18:36 +00:00
|
|
|
xmax = float("-inf")
|
|
|
|
ymax = float("-inf")
|
|
|
|
zmax = float("-inf")
|
2013-05-15 12:45:17 +00:00
|
|
|
|
|
|
|
for l in self.layers:
|
2013-05-15 17:43:26 +00:00
|
|
|
(xm, xM), (ym, yM), (zm, zM) = l.measure()
|
|
|
|
xmin = min(xm, xmin)
|
|
|
|
xmax = max(xM, xmax)
|
|
|
|
ymin = min(ym, ymin)
|
|
|
|
ymax = max(yM, ymax)
|
|
|
|
zmin = min(zm, zmin)
|
|
|
|
zmax = max(zM, zmax)
|
2013-05-15 12:45:17 +00:00
|
|
|
|
|
|
|
self.xmin = xmin
|
|
|
|
self.xmax = xmax
|
|
|
|
self.ymin = ymin
|
|
|
|
self.ymax = ymax
|
|
|
|
self.zmin = zmin
|
|
|
|
self.zmax = zmax
|
|
|
|
self.width = xmax - xmin
|
|
|
|
self.depth = ymax - ymin
|
|
|
|
self.height = zmax - zmin
|
|
|
|
|
|
|
|
def filament_length(self):
|
|
|
|
total_e = 0
|
|
|
|
cur_e = 0
|
|
|
|
|
|
|
|
for line in self.lines:
|
2013-05-15 13:24:33 +00:00
|
|
|
if line.e == None:
|
2013-05-15 13:19:23 +00:00
|
|
|
continue
|
2013-05-15 17:43:26 +00:00
|
|
|
if line.command == "G92":
|
2013-05-15 13:19:23 +00:00
|
|
|
cur_e = line.e
|
2013-05-15 17:43:26 +00:00
|
|
|
elif line.is_move:
|
2013-05-15 12:45:17 +00:00
|
|
|
if line.relative_e:
|
2013-05-15 13:19:23 +00:00
|
|
|
total_e += line.e
|
2013-05-15 12:45:17 +00:00
|
|
|
else:
|
2013-05-15 13:19:23 +00:00
|
|
|
total_e += line.e - cur_e
|
2013-05-15 12:45:17 +00:00
|
|
|
cur_e = line.e
|
2013-05-15 13:19:23 +00:00
|
|
|
|
2013-05-15 12:45:17 +00:00
|
|
|
return total_e
|
2012-10-17 17:46:58 +00:00
|
|
|
|
2013-05-15 13:23:46 +00:00
|
|
|
def estimate_duration(self, g):
|
|
|
|
lastx = lasty = lastz = laste = lastf = 0.0
|
|
|
|
x = y = z = e = f = 0.0
|
|
|
|
currenttravel = 0.0
|
|
|
|
totaltravel = 0.0
|
|
|
|
moveduration = 0.0
|
|
|
|
totalduration = 0.0
|
|
|
|
acceleration = 1500.0 #mm/s/s ASSUMING THE DEFAULT FROM SPRINTER !!!!
|
|
|
|
layerduration = 0.0
|
|
|
|
layerbeginduration = 0.0
|
|
|
|
layercount = 0
|
|
|
|
#TODO:
|
|
|
|
# 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:
|
|
|
|
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): 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.
|
|
|
|
# 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
|
|
|
|
|
|
|
|
currenttravel = hypot3d(x, y, z, lastx, lasty, lastz)
|
|
|
|
distance = abs(2* ((lastf+f) * (f-lastf) * 0.5 ) / acceleration) #2x because we have to accelerate and decelerate
|
|
|
|
if distance <= currenttravel and ( lastf + f )!=0 and f!=0:
|
|
|
|
moveduration = 2 * distance / ( lastf + f )
|
|
|
|
currenttravel -= distance
|
|
|
|
moveduration += currenttravel/f
|
|
|
|
else:
|
|
|
|
moveduration = math.sqrt( 2 * distance / acceleration )
|
|
|
|
|
|
|
|
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
|
|
|
|
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)))
|
2012-10-17 17:46:58 +00:00
|
|
|
|
|
|
|
def main():
|
2013-05-15 12:45:17 +00:00
|
|
|
if len(sys.argv) < 2:
|
|
|
|
print "usage: %s filename.gcode" % sys.argv[0]
|
|
|
|
return
|
2012-10-17 17:46:58 +00:00
|
|
|
|
2013-05-15 12:45:17 +00:00
|
|
|
# d = [i.replace("\n","") for i in open(sys.argv[1])]
|
|
|
|
# gcode = GCode(d)
|
2013-05-15 13:23:46 +00:00
|
|
|
d = list(open(sys.argv[1]))
|
|
|
|
gcode = GCode(d)
|
2013-05-15 17:43:26 +00:00
|
|
|
|
2013-05-15 12:45:17 +00:00
|
|
|
gcode.measure()
|
2012-10-17 17:46:58 +00:00
|
|
|
|
2013-05-15 12:45:17 +00:00
|
|
|
print "Dimensions:"
|
|
|
|
print "\tX: %0.02f - %0.02f (%0.02f)" % (gcode.xmin,gcode.xmax,gcode.width)
|
|
|
|
print "\tY: %0.02f - %0.02f (%0.02f)" % (gcode.ymin,gcode.ymax,gcode.depth)
|
|
|
|
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()
|
2013-05-15 13:23:46 +00:00
|
|
|
print "Estimated duration (pessimistic): ", gcode.estimate_duration(d)
|
2012-11-08 22:06:59 +00:00
|
|
|
|
2012-10-17 17:46:58 +00:00
|
|
|
|
|
|
|
if __name__ == '__main__':
|
2013-05-15 12:45:17 +00:00
|
|
|
main()
|