printrun-src/printrun/gcoder.py

changeset 15
0bbb006204fc
child 46
cce0af6351f0
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/printrun-src/printrun/gcoder.py	Fri Jun 03 09:16:07 2016 +0200
@@ -0,0 +1,755 @@
+#!/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/>.
+
+import sys
+import re
+import math
+import datetime
+import logging
+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_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):
+
+    __slots__ = ('x', 'y', 'z', 'e', 'f', 'i', 'j',
+                 'raw', 'command', 'is_move',
+                 'relative', 'relative_e',
+                 'current_x', 'current_y', 'current_z', 'extruding',
+                 'current_tool',
+                 'gcview_end_vertex')
+
+    def __init__(self, l):
+        self.raw = l
+
+    def __getattr__(self, name):
+        return None
+
+class PyLightLine(object):
+
+    __slots__ = ('raw', 'command')
+
+    def __init__(self, l):
+        self.raw = l
+
+    def __getattr__(self, name):
+        return None
+
+try:
+    import gcoder_line
+    Line = gcoder_line.GLine
+    LightLine = gcoder_line.GLightLine
+except Exception, e:
+    logging.warning("Memory-efficient GCoder implementation unavailable: %s" % e)
+    Line = PyLine
+    LightLine = PyLightLine
+
+def find_specific_code(line, code):
+    exp = specific_exp % code
+    bits = [bit for bit in re.findall(exp, line.raw) if bit]
+    if not bits: return None
+    else: return float(bits[0][1:])
+
+def S(line):
+    return find_specific_code(line, "S")
+
+def P(line):
+    return find_specific_code(line, "P")
+
+def split(line):
+    split_raw = gcode_exp.findall(line.raw.lower())
+    if split_raw and split_raw[0][0] == "n":
+        del split_raw[0]
+    if not split_raw:
+        line.command = line.raw
+        line.is_move = False
+        logging.warning("raw G-Code line \"%s\" could not be parsed" % line.raw)
+        return [line.raw]
+    command = split_raw[0]
+    line.command = command[0].upper() + command[1]
+    line.is_move = line.command in move_gcodes
+    return split_raw
+
+def parse_coordinates(line, split_raw, imperial = False, force = False):
+    # Not a G-line, we don't want to parse its arguments
+    if not force and line.command[0] != "G":
+        return
+    unit_factor = 25.4 if imperial else 1
+    for bit in split_raw:
+        code = bit[0]
+        if code not in gcode_parsed_nonargs and bit[1]:
+            setattr(line, code, unit_factor * float(bit[1]))
+
+class Layer(list):
+
+    __slots__ = ("duration", "z")
+
+    def __init__(self, lines, z = None):
+        super(Layer, self).__init__(lines)
+        self.z = z
+
+class GCode(object):
+
+    line_class = Line
+
+    lines = None
+    layers = None
+    all_layers = None
+    layer_idxs = None
+    line_idxs = None
+    append_layer = None
+    append_layer_id = None
+
+    imperial = False
+    relative = False
+    relative_e = False
+    current_tool = 0
+    # Home position: current absolute position counted from machine origin
+    home_x = 0
+    home_y = 0
+    home_z = 0
+    # Current position: current absolute position counted from machine origin
+    current_x = 0
+    current_y = 0
+    current_z = 0
+    # For E this is the absolute position from machine start
+    current_e = 0
+    current_e_multi=[0]
+    total_e = 0
+    total_e_multi=[0]
+    max_e = 0
+    max_e_multi=[0]
+    # Current feedrate
+    current_f = 0
+    # Offset: current offset between the machine origin and the machine current
+    # absolute coordinate system (as shifted by G92s)
+    offset_x = 0
+    offset_y = 0
+    offset_z = 0
+    offset_e = 0
+    offset_e_multi = [0]
+
+    # Expected behavior:
+    # - G28 X => X axis is homed, offset_x <- 0, current_x <- home_x
+    # - G92 Xk => X axis does not move, so current_x does not change
+    #             and offset_x <- current_x - k,
+    # - absolute G1 Xk => X axis moves, current_x <- offset_x + k
+    # How to get...
+    # current abs X from machine origin: current_x
+    # current abs X in machine current coordinate system: current_x - offset_x
+
+    filament_length = None
+    filament_length_multi=[0]
+    duration = None
+    xmin = None
+    xmax = None
+    ymin = None
+    ymax = None
+    zmin = None
+    zmax = None
+    width = None
+    depth = None
+    height = None
+
+    est_layer_height = None
+
+    # abs_x is the current absolute X in machine current coordinate system
+    # (after the various G92 transformations) and can be used to store the
+    # absolute position of the head at a given time
+    def _get_abs_x(self):
+        return self.current_x - self.offset_x
+    abs_x = property(_get_abs_x)
+
+    def _get_abs_y(self):
+        return self.current_y - self.offset_y
+    abs_y = property(_get_abs_y)
+
+    def _get_abs_z(self):
+        return self.current_z - self.offset_z
+    abs_z = property(_get_abs_z)
+
+    def _get_abs_e(self):
+        return self.current_e - self.offset_e
+    abs_e = property(_get_abs_e)
+
+    def _get_abs_e_multi(self,i):
+        return self.current_e_multi[i] - self.offset_e_multi[i]
+    abs_e = property(_get_abs_e)
+
+    def _get_abs_pos(self):
+        return (self.abs_x, self.abs_y, self.abs_z)
+    abs_pos = property(_get_abs_pos)
+
+    def _get_current_pos(self):
+        return (self.current_x, self.current_y, self.current_z)
+    current_pos = property(_get_current_pos)
+
+    def _get_home_pos(self):
+        return (self.home_x, self.home_y, self.home_z)
+
+    def _set_home_pos(self, home_pos):
+        if home_pos:
+            self.home_x, self.home_y, self.home_z = home_pos
+    home_pos = property(_get_home_pos, _set_home_pos)
+
+    def _get_layers_count(self):
+        return len(self.all_zs)
+    layers_count = property(_get_layers_count)
+
+    def __init__(self, data = None, home_pos = None,
+                 layer_callback = None, deferred = False):
+        if not deferred:
+            self.prepare(data, home_pos, layer_callback)
+
+    def prepare(self, data = None, home_pos = None, layer_callback = None):
+        self.home_pos = home_pos
+        if data:
+            line_class = self.line_class
+            self.lines = [line_class(l2) for l2 in
+                          (l.strip() for l in data)
+                          if l2]
+            self._preprocess(build_layers = True,
+                             layer_callback = layer_callback)
+        else:
+            self.lines = []
+            self.append_layer_id = 0
+            self.append_layer = Layer([])
+            self.all_layers = [self.append_layer]
+            self.all_zs = set()
+            self.layers = {}
+            self.layer_idxs = array('I', [])
+            self.line_idxs = array('I', [])
+
+    def __len__(self):
+        return len(self.line_idxs)
+
+    def __iter__(self):
+        return self.lines.__iter__()
+
+    def prepend_to_layer(self, commands, layer_idx):
+        # Prepend commands in reverse order
+        commands = [c.strip() for c in commands[::-1] if c.strip()]
+        layer = self.all_layers[layer_idx]
+        # Find start index to append lines
+        # and end index to append new indices
+        start_index = self.layer_idxs.index(layer_idx)
+        for i in range(start_index, len(self.layer_idxs)):
+            if self.layer_idxs[i] != layer_idx:
+                end_index = i
+                break
+        else:
+            end_index = i + 1
+        end_line = self.line_idxs[end_index - 1]
+        for i, command in enumerate(commands):
+            gline = Line(command)
+            # Split to get command
+            split(gline)
+            # Force is_move to False
+            gline.is_move = False
+            # Insert gline at beginning of layer
+            layer.insert(0, gline)
+            # Insert gline at beginning of list
+            self.lines.insert(start_index, gline)
+            # Update indices arrays & global gcodes list
+            self.layer_idxs.insert(end_index + i, layer_idx)
+            self.line_idxs.insert(end_index + i, end_line + i + 1)
+        return commands[::-1]
+
+    def rewrite_layer(self, commands, layer_idx):
+        # Prepend commands in reverse order
+        commands = [c.strip() for c in commands[::-1] if c.strip()]
+        layer = self.all_layers[layer_idx]
+        # Find start index to append lines
+        # and end index to append new indices
+        start_index = self.layer_idxs.index(layer_idx)
+        for i in range(start_index, len(self.layer_idxs)):
+            if self.layer_idxs[i] != layer_idx:
+                end_index = i
+                break
+        else:
+            end_index = i + 1
+        self.layer_idxs = self.layer_idxs[:start_index] + array('I', len(commands) * [layer_idx]) + self.layer_idxs[end_index:]
+        self.line_idxs = self.line_idxs[:start_index] + array('I', range(len(commands))) + self.line_idxs[end_index:]
+        del self.lines[start_index:end_index]
+        del layer[:]
+        for i, command in enumerate(commands):
+            gline = Line(command)
+            # Split to get command
+            split(gline)
+            # Force is_move to False
+            gline.is_move = False
+            # Insert gline at beginning of layer
+            layer.insert(0, gline)
+            # Insert gline at beginning of list
+            self.lines.insert(start_index, gline)
+        return commands[::-1]
+
+    def append(self, command, store = True):
+        command = command.strip()
+        if not command:
+            return
+        gline = Line(command)
+        self._preprocess([gline])
+        if store:
+            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))
+        return gline
+
+    def _preprocess(self, lines = None, build_layers = False,
+                    layer_callback = None):
+        """Checks for imperial/relativeness settings and tool changes"""
+        if not lines:
+            lines = self.lines
+        imperial = self.imperial
+        relative = self.relative
+        relative_e = self.relative_e
+        current_tool = self.current_tool
+        current_x = self.current_x
+        current_y = self.current_y
+        current_z = self.current_z
+        offset_x = self.offset_x
+        offset_y = self.offset_y
+        offset_z = self.offset_z
+
+        # Extrusion computation
+        current_e = self.current_e
+        offset_e = self.offset_e
+        total_e = self.total_e
+        max_e = self.max_e
+
+        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]
+        max_e_multi = self.max_e_multi[current_tool]
+
+        # Store this one out of the build_layers scope for efficiency
+        cur_layer_has_extrusion = False
+
+        # Initialize layers and other global computations
+        if build_layers:
+            # Bounding box computation
+            xmin = float("inf")
+            ymin = float("inf")
+            zmin = 0
+            xmax = float("-inf")
+            ymax = float("-inf")
+            zmax = float("-inf")
+            # Also compute extrusion-only values
+            xmin_e = float("inf")
+            ymin_e = float("inf")
+            xmax_e = float("-inf")
+            ymax_e = float("-inf")
+
+            # Duration estimation
+            # TODO:
+            # 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
+            lastdx = 0
+            lastdy = 0
+            x = y = e = f = 0.0
+            currenttravel = 0.0
+            moveduration = 0.0
+            totalduration = 0.0
+            acceleration = 2000.0  # mm/s^2
+            layerbeginduration = 0.0
+
+            # Initialize layers
+            all_layers = self.all_layers = []
+            all_zs = self.all_zs = set()
+            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 = []
+
+        if self.line_class != Line:
+            get_line = lambda l: Line(l.raw)
+        else:
+            get_line = lambda l: l
+        for true_line in lines:
+            # # Parse line
+            # Use a heavy copy of the light line to preprocess
+            line = get_line(true_line)
+            split_raw = split(line)
+            if line.command:
+                # Update properties
+                if line.is_move:
+                    line.relative = relative
+                    line.relative_e = relative_e
+                    line.current_tool = current_tool
+                elif line.command == "G20":
+                    imperial = True
+                elif line.command == "G21":
+                    imperial = False
+                elif line.command == "G90":
+                    relative = False
+                    relative_e = False
+                elif line.command == "G91":
+                    relative = True
+                    relative_e = True
+                elif line.command == "M82":
+                    relative_e = False
+                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)):
+                        self.current_e_multi+=[0]
+                        self.offset_e_multi+=[0]
+                        self.total_e_multi+=[0]
+                        self.max_e_multi+=[0]
+                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]
+                max_e_multi = self.max_e_multi[current_tool]
+
+
+                if line.command[0] == "G":
+                    parse_coordinates(line, split_raw, imperial)
+
+                # Compute current position
+                if line.is_move:
+                    x = line.x
+                    y = line.y
+                    z = line.z
+
+                    if line.f is not None:
+                        self.current_f = line.f
+
+                    if line.relative:
+                        x = current_x + (x or 0)
+                        y = current_y + (y or 0)
+                        z = current_z + (z or 0)
+                    else:
+                        if x is not None: x = x + offset_x
+                        if y is not None: y = y + offset_y
+                        if z is not None: z = z + offset_z
+
+                    if x is not None: current_x = x
+                    if y is not None: current_y = y
+                    if z is not None: current_z = z
+
+                elif line.command == "G28":
+                    home_all = not any([line.x, line.y, line.z])
+                    if home_all or line.x is not None:
+                        offset_x = 0
+                        current_x = self.home_x
+                    if home_all or line.y is not None:
+                        offset_y = 0
+                        current_y = self.home_y
+                    if home_all or line.z is not None:
+                        offset_z = 0
+                        current_z = self.home_z
+
+                elif line.command == "G92":
+                    if line.x is not None: offset_x = current_x - line.x
+                    if line.y is not None: offset_y = current_y - line.y
+                    if line.z is not None: offset_z = current_z - line.z
+
+                line.current_x = current_x
+                line.current_y = current_y
+                line.current_z = current_z
+
+                # # Process extrusion
+                if line.e is not None:
+                    if line.is_move:
+                        if line.relative_e:
+                            line.extruding = line.e > 0
+                            total_e += line.e
+                            current_e += line.e
+                            total_e_multi += line.e
+                            current_e_multi += line.e
+                        else:
+                            new_e = line.e + offset_e
+                            line.extruding = new_e > current_e
+                            total_e += new_e - current_e
+                            current_e = new_e
+                            new_e_multi = line.e + offset_e_multi
+                            total_e_multi += new_e_multi - current_e_multi
+                            current_e_multi = new_e_multi
+
+                        max_e = max(max_e, total_e)
+                        max_e_multi=max(max_e_multi, total_e_multi)
+                        cur_layer_has_extrusion |= line.extruding
+                    elif line.command == "G92":
+                        offset_e = current_e - line.e
+                        offset_e_multi = current_e_multi - line.e
+
+                self.current_e_multi[current_tool]=current_e_multi
+                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
+
+                # # Create layers and perform global computations
+                if build_layers:
+                    # Update bounding box
+                    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)
+                            if line.current_y is not None:
+                                ymin_e = min(ymin_e, line.current_y)
+                                ymax_e = max(ymax_e, line.current_y)
+                        if max_e <= 0:
+                            if line.current_x is not None:
+                                xmin = min(xmin, line.current_x)
+                                xmax = max(xmax, line.current_x)
+                            if line.current_y is not None:
+                                ymin = min(ymin, line.current_y)
+                                ymax = max(ymax, line.current_y)
+
+                    # 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
+                        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
+
+                        # 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
+
+                        # FIXME: this code has been proven to be super wrong when 2
+                        # subsquent moves are in opposite directions, as requested
+                        # speed is constant but printer has to fully decellerate
+                        # and reaccelerate
+                        # 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
+                        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)
+                            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
+                        if f == lastf:
+                            moveduration = currenttravel / f if f != 0 else 0.
+                        else:
+                            # FIXME: review this better
+                            # this looks wrong : there's little chance that the feedrate we'll decelerate to is the previous feedrate
+                            # 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:
+                                moveduration = 2 * distance / (lastf + f)  # This is distance / mean(lastf, f)
+                                moveduration += (currenttravel - distance) / f
+                            else:
+                                moveduration = 2 * currenttravel / (lastf + f)  # This is currenttravel / mean(lastf, f)
+                                # FIXME: probably a little bit optimistic, but probably a much better estimate than the previous one:
+                                # moveduration = math.sqrt(2 * distance / acceleration) # probably buggy : not taking actual travel into account
+
+                        lastdx = dx
+                        lastdy = dy
+
+                        totalduration += moveduration
+
+                        lastx = x
+                        lasty = y
+                        lastz = z
+                        laste = e
+                        lastf = f
+                    elif line.command == "G4":
+                        moveduration = P(line)
+                        if moveduration:
+                            moveduration /= 1000.0
+                            totalduration += moveduration
+
+                    # FIXME : looks like this needs to be tested with "lift Z on move"
+                    if line.z is not None:
+                        if line.command == "G92":
+                            cur_z = line.z
+                        elif line.is_move:
+                            if line.relative and cur_z is not None:
+                                cur_z += line.z
+                            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 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
+
+        # Store current status
+        self.imperial = imperial
+        self.relative = relative
+        self.relative_e = relative_e
+        self.current_tool = current_tool
+        self.current_x = current_x
+        self.current_y = current_y
+        self.current_z = current_z
+        self.offset_x = offset_x
+        self.offset_y = offset_y
+        self.offset_z = offset_z
+        self.current_e = current_e
+        self.offset_e = offset_e
+        self.max_e = max_e
+        self.total_e = total_e
+        self.current_e_multi[current_tool]=current_e_multi
+        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
+
+
+        # 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)
+
+            self.append_layer_id = len(all_layers)
+            self.append_layer = Layer([])
+            self.append_layer.duration = 0
+            all_layers.append(self.append_layer)
+            self.layer_idxs = array('I', layer_idxs)
+            self.line_idxs = array('I', line_idxs)
+
+            # Compute bounding box
+            all_zs = self.all_zs.union(set([zmin])).difference(set([None]))
+            zmin = min(all_zs)
+            zmax = max(all_zs)
+
+            self.filament_length = self.max_e
+            while len(self.filament_length_multi)<len(self.max_e_multi):
+                    self.filament_length_multi+=[0]
+            for i in enumerate(self.max_e_multi):
+                self.filament_length_multi[i[0]]=i[1]
+
+
+            if self.filament_length > 0:
+                self.xmin = xmin_e if not math.isinf(xmin_e) else 0
+                self.xmax = xmax_e if not math.isinf(xmax_e) else 0
+                self.ymin = ymin_e if not math.isinf(ymin_e) else 0
+                self.ymax = ymax_e if not math.isinf(ymax_e) else 0
+            else:
+                self.xmin = xmin if not math.isinf(xmin) else 0
+                self.xmax = xmax if not math.isinf(xmax) else 0
+                self.ymin = ymin if not math.isinf(ymin) else 0
+                self.ymax = ymax if not math.isinf(ymax) else 0
+            self.zmin = zmin if not math.isinf(zmin) else 0
+            self.zmax = zmax if not math.isinf(zmax) else 0
+            self.width = self.xmax - self.xmin
+            self.depth = self.ymax - self.ymin
+            self.height = self.zmax - self.zmin
+
+            # Finalize duration
+            totaltime = datetime.timedelta(seconds = int(totalduration))
+            self.duration = totaltime
+
+    def idxs(self, i):
+        return self.layer_idxs[i], self.line_idxs[i]
+
+    def estimate_duration(self):
+        return self.layers_count, self.duration
+
+class LightGCode(GCode):
+    line_class = LightLine
+
+def main():
+    if len(sys.argv) < 2:
+        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"))
+    gcode = GCode(open(sys.argv[1], "rU"))
+
+    print "Dimensions:"
+    xdims = (gcode.xmin, gcode.xmax, gcode.width)
+    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
+    zdims = (gcode.zmin, gcode.zmax, gcode.height)
+    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]
+
+if __name__ == '__main__':
+    main()

mercurial