From 79d660f3772ee3b3433d210332a2090e93f34828 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Tue, 5 Jul 2016 11:36:07 +0200 Subject: [PATCH] Further GCODE interpreter fixes & optimizations * Introduce Vector3D type to help with coordinate back and forth calculations * Fix typo in movement calculation (Y twice instead of Y and Z) * Removed some code duplication --- src/octoprint/util/gcodeInterpreter.py | 205 +++++++++++++++++-------- 1 file changed, 141 insertions(+), 64 deletions(-) diff --git a/src/octoprint/util/gcodeInterpreter.py b/src/octoprint/util/gcodeInterpreter.py index 6bc0a967..b1b86ca4 100644 --- a/src/octoprint/util/gcodeInterpreter.py +++ b/src/octoprint/util/gcodeInterpreter.py @@ -14,6 +14,107 @@ import logging from octoprint.settings import settings +class Vector3D(object): + """ + 3D vector value + + Supports addition, subtraction and multiplication with a scalar value (float, int) as well as calculating the + length of the vector. + + Examples: + + >>> a = Vector3D(1.0, 1.0, 1.0) + >>> b = Vector3D(4.0, 4.0, 4.0) + >>> a + b == Vector3D(5.0, 5.0, 5.0) + True + >>> b - a == Vector3D(3.0, 3.0, 3.0) + True + >>> abs(a - b) == Vector3D(3.0, 3.0, 3.0) + True + >>> a * 2 == Vector3D(2.0, 2.0, 2.0) + True + >>> a * 2 == 2 * a + True + >>> a.length == math.sqrt(a.x ** 2 + a.y ** 2 + a.z ** 2) + True + >>> copied_a = Vector3D(a) + >>> a == copied_a + True + >>> copied_a.x == a.x and copied_a.y == a.y and copied_a.z == a.z + True + """ + + def __init__(self, *args, **kwargs): + self.x = kwargs.get("x", 0.0) + self.y = kwargs.get("y", 0.0) + self.z = kwargs.get("z", 0.0) + + if len(args) == 3: + self.x = args[0] + self.y = args[1] + self.z = args[2] + + elif len(args) == 1: + # copy constructor + other = args[0] + if not isinstance(other, Vector3D): + raise ValueError("Object to copy must be a Scalar3D instance") + + self.x = other.x + self.y = other.y + self.z = other.z + + @property + def length(self): + return math.sqrt(self.x * self.x + self.y * self.y + self.z * self.z) + + def __add__(self, other): + if isinstance(other, Vector3D): + return Vector3D(self.x + other.x, + self.y + other.y, + self.z + other.z) + elif isinstance(other, (tuple, list)) and len(other) == 3: + return Vector3D(self.x + other[0], + self.y + other[1], + self.z + other[2]) + else: + raise ValueError("other must be a Scalar3D instance or a list or tuple of length 3") + + def __sub__(self, other): + if isinstance(other, Vector3D): + return Vector3D(self.x - other.x, + self.y - other.y, + self.z - other.z) + elif isinstance(other, (tuple, list)) and len(other) == 3: + return Vector3D(self.x - other[0], + self.y - other[1], + self.z - other[2]) + else: + raise ValueError("other must be a Scalar3D instance or a list or tuple") + + def __mul__(self, other): + if isinstance(other, (int, float)): + return Vector3D(self.x * other, + self.y * other, + self.z * other) + else: + raise ValueError("other must be a float or int value") + + def __rmul__(self, other): + return self.__mul__(other) + + def __abs__(self): + return Vector3D(abs(self.x), abs(self.y), abs(self.z)) + + def __eq__(self, other): + if not isinstance(other, Vector3D): + return False + return self.x == other.x and self.y == other.y and self.z == other.z + + def __str__(self): + return "Scalar3D(x={}, y={}, z={}, length={})".format(self.x, self.y, self.z, self.length) + + class AnalysisAborted(Exception): pass @@ -46,8 +147,8 @@ class gcode(object): def _load(self, gcodeFile, printer_profile, throttle=None): filePos = 0 readBytes = 0 - pos = [0.0, 0.0, 0.0] - posOffset = [0.0, 0.0, 0.0] + pos = Vector3D(0.0, 0.0, 0.0) + posOffset = Vector3D(0.0, 0.0, 0.0) currentE = [0.0] totalExtrusion = [0.0] maxExtrusion = [0.0] @@ -56,10 +157,10 @@ class gcode(object): absoluteE = True scale = 1.0 posAbs = True - feedRateXY = min(printer_profile["axes"]["x"]["speed"], printer_profile["axes"]["y"]["speed"]) - if feedRateXY == 0: + feedrate = min(printer_profile["axes"]["x"]["speed"], printer_profile["axes"]["y"]["speed"]) + if feedrate == 0: # some somewhat sane default if axes speeds are insane... - feedRateXY = 2000 + feedrate = 2000 offsets = printer_profile["extruder"]["offsets"] for line in gcodeFile: @@ -117,63 +218,39 @@ class gcode(object): z = getCodeFloat(line, 'Z') e = getCodeFloat(line, 'E') f = getCodeFloat(line, 'F') - oldPos = pos - pos = pos[:] - if posAbs: - if x is not None: - pos[0] = x * scale + posOffset[0] - if y is not None: - pos[1] = y * scale + posOffset[1] - if z is not None: - pos[2] = z * scale + posOffset[2] - else: - if x is not None: - pos[0] += x * scale - if y is not None: - pos[1] += y * scale - if z is not None: - pos[2] += z * scale - if f is not None and f != 0: - feedRateXY = f - moveType = 'move' + oldPos = pos + newPos = Vector3D(x if x is not None else pos.x, + y if y is not None else pos.y, + z if z is not None else pos.z) + + if posAbs: + pos = newPos * scale + posOffset + else: + pos += newPos * scale + if f is not None and f != 0: + feedrate = f + if e is not None: if absoluteE: + # make sure e is relative e -= currentE[currentExtruder] - if e > 0.0: - moveType = 'extrude' - if e < 0.0: - moveType = 'retract' totalExtrusion[currentExtruder] += e currentE[currentExtruder] += e - if totalExtrusion[currentExtruder] > maxExtrusion[currentExtruder]: - maxExtrusion[currentExtruder] = totalExtrusion[currentExtruder] + maxExtrusion[currentExtruder] = max(maxExtrusion[currentExtruder], + totalExtrusion[currentExtruder]) else: e = 0.0 - if moveType == "move": - diffX = oldPos[0] - pos[0] - diffY = oldPos[1] - pos[1] - diffZ = oldPos[1] - pos[1] - totalMoveTimeMinute += math.sqrt(diffX * diffX + diffY * diffY + diffZ * diffZ) / feedRateXY - elif moveType == "extrude": - diffX = oldPos[0] - pos[0] - diffY = oldPos[1] - pos[1] - diffZ = oldPos[1] - pos[1] - time1 = math.sqrt(diffX * diffX + diffY * diffY + diffZ * diffZ) / feedRateXY - time2 = abs(e / feedRateXY) - totalMoveTimeMinute += max(time1, time2) - elif moveType == "retract": - diffX = oldPos[0] - pos[0] - diffY = oldPos[1] - pos[1] - diffZ = oldPos[1] - pos[1] - time1 = math.sqrt(diffX * diffX + diffY * diffY + diffZ * diffZ) / feedRateXY - time2 = abs(e / feedRateXY) - totalMoveTimeMinute += max(time1, time2) + # move time in x, y, z, will be 0 if no movement happened + moveTimeXYZ = abs((oldPos - pos).length / feedrate) + + # time needed for extruding, will be 0 if no extrusion happened + extrudeTime = abs(e / feedrate) + + # time to add is maximum of both + totalMoveTimeMinute += max(moveTimeXYZ, extrudeTime) - if moveType == 'move' and oldPos[2] != pos[2]: - if oldPos[2] > pos[2] and abs(oldPos[2] - pos[2]) > 5.0 and pos[2] < 1.0: - oldPos[2] = 0.0 elif G == 4: #Delay S = getCodeFloat(line, 'S') if S is not None: @@ -189,17 +266,17 @@ class gcode(object): x = getCodeFloat(line, 'X') y = getCodeFloat(line, 'Y') z = getCodeFloat(line, 'Z') - center = [0.0,0.0,0.0] + center = Vector3D(0.0, 0.0, 0.0) if x is None and y is None and z is None: pos = center else: - pos = pos[:] + pos = Vector3D(pos) if x is not None: - pos[0] = center[0] + pos.x = center.x if y is not None: - pos[1] = center[1] + pos.y = center.y if z is not None: - pos[2] = center[2] + pos.z = center.z elif G == 90: #Absolute position posAbs = True elif G == 91: #Relative position @@ -212,11 +289,11 @@ class gcode(object): if e is not None: currentE[currentExtruder] = e if x is not None: - posOffset[0] = pos[0] - x + posOffset.x = pos.x - x if y is not None: - posOffset[1] = pos[1] - y + posOffset.y = pos.y - y if z is not None: - posOffset[2] = pos[2] - z + posOffset.z = pos.z - z elif M is not None: if M == 82: #Absolute E @@ -228,13 +305,13 @@ class gcode(object): if T > settings().getInt(["gcodeAnalysis", "maxExtruders"]): self._logger.warn("GCODE tried to select tool %d, that looks wrong, ignoring for GCODE analysis" % T) else: - posOffset[0] -= offsets[currentExtruder][0] if currentExtruder < len(offsets) else 0 - posOffset[1] -= offsets[currentExtruder][1] if currentExtruder < len(offsets) else 0 + posOffset.x -= offsets[currentExtruder][0] if currentExtruder < len(offsets) else 0 + posOffset.y -= offsets[currentExtruder][1] if currentExtruder < len(offsets) else 0 currentExtruder = T - posOffset[0] += offsets[currentExtruder][0] if currentExtruder < len(offsets) else 0 - posOffset[1] += offsets[currentExtruder][1] if currentExtruder < len(offsets) else 0 + posOffset.x += offsets[currentExtruder][0] if currentExtruder < len(offsets) else 0 + posOffset.y += offsets[currentExtruder][1] if currentExtruder < len(offsets) else 0 if len(currentE) <= currentExtruder: for i in range(len(currentE), currentExtruder + 1):