From 1726e85a68f95e8fa5174c07482af5243bde6631 Mon Sep 17 00:00:00 2001 From: Bryan Mayland Date: Wed, 7 Aug 2013 11:01:48 -0400 Subject: [PATCH 1/4] Merge gcodeInterpreter optimizations from daid/Cura --- octoprint/gcodefiles.py | 4 +- octoprint/util/gcodeInterpreter.py | 349 ++++++++++++++++------------- 2 files changed, 199 insertions(+), 154 deletions(-) diff --git a/octoprint/gcodefiles.py b/octoprint/gcodefiles.py index 704a7b3..a31deea 100644 --- a/octoprint/gcodefiles.py +++ b/octoprint/gcodefiles.py @@ -63,8 +63,8 @@ class GcodeManager: dirty = True if gcode.extrusionAmount: analysisResult["filament"] = "%.2fm" % (gcode.extrusionAmount / 1000) - if gcode.extrusionVolume: - analysisResult["filament"] += " / %.2fcm³" % gcode.extrusionVolume + if gcode.calculateVolumeCm3(): + analysisResult["filament"] += " / %.2fcm³" % gcode.calculateVolumeCm3() dirty = True if dirty: diff --git a/octoprint/util/gcodeInterpreter.py b/octoprint/util/gcodeInterpreter.py index eebdd55..cb36f12 100644 --- a/octoprint/util/gcodeInterpreter.py +++ b/octoprint/util/gcodeInterpreter.py @@ -1,15 +1,13 @@ from __future__ import absolute_import +__copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License" import sys import math -import re import os import base64 import zlib import logging -from octoprint.util import util3d - preferences = { "extruder_offset_x1": -22.0, "extruder_offset_y1": 0.0, @@ -28,82 +26,105 @@ def getPreference(key, default=None): class AnalysisAborted(Exception): pass -class gcodePath(object): - def __init__(self, newType, pathType, layerThickness, startPoint): - self.type = newType - self.pathType = pathType - self.layerThickness = layerThickness - self.list = [startPoint] +#class gcodePath(object): +# def __init__(self, newType, pathType, layerThickness, startPoint): +# self.type = newType +# self.pathType = pathType +# self.layerThickness = layerThickness +# self.points = [startPoint] +# self.extrusion = [0.0] +def gcodePath(newType, pathType, layerThickness, startPoint): + return {'type': newType, + 'pathType': pathType, + 'layerThickness': layerThickness, + 'points': [startPoint], + 'extrusion': [0.0]} class gcode(object): def __init__(self): self._logger = logging.getLogger(__name__) - self.regMatch = {} - self.layerList = [] + self.layerList = None self.extrusionAmount = 0 - self.extrusionVolume = None self.totalMoveTimeMinute = 0 + self.filename = None self.progressCallback = None self._abort = False + self._filamentDiameter = 0 def load(self, filename): if os.path.isfile(filename): + self.filename = filename self._fileSize = os.stat(filename).st_size gcodeFile = open(filename, 'r') self._load(gcodeFile) gcodeFile.close() def loadList(self, l): + self.filename = None self._load(l) def abort(self): self._abort = True + + def calculateVolumeCm3(self): + radius = self._filamentDiameter / 2 + return (self.extrusionAmount * (math.pi * radius * radius)) / 1000 + + def calculateWeight(self): + #Calculates the weight of the filament in kg + volumeM3 = calculateVolumeCm3 /(1000*1000) + return volumeM3 * getPreference('filament_physical_density') + + def calculateCost(self): + cost_kg = getPreference('filament_cost_kg') + cost_meter = getPreference('filament_cost_meter') + if cost_kg > 0.0 and cost_meter > 0.0: + return "%.2f / %.2f" % (self.calculateWeight() * cost_kg, self.extrusionAmount / 1000 * cost_meter) + elif cost_kg > 0.0: + return "%.2f" % (self.calculateWeight() * cost_kg) + elif cost_meter > 0.0: + return "%.2f" % (self.extrusionAmount / 1000 * cost_meter) + return None def _load(self, gcodeFile): filePos = 0 - pos = util3d.Vector3() - posOffset = util3d.Vector3() + self.layerList = [] + pos = [0.0, 0.0, 0.0] + posOffset = [0.0, 0.0, 0.0] currentE = 0.0 totalExtrusion = 0.0 maxExtrusion = 0.0 currentExtruder = 0 extrudeAmountMultiply = 1.0 totalMoveTimeMinute = 0.0 - filamentDiameter = 0.0 + absoluteE = True scale = 1.0 posAbs = True - posAbsExtruder = True; - feedRate = 3600 + feedRate = 3600.0 + moveType = 'move' layerThickness = 0.1 - pathType = 'CUSTOM'; + pathType = 'CUSTOM' currentLayer = [] - unknownGcodes={} - unknownMcodes={} - currentPath = gcodePath('move', pathType, layerThickness, pos.copy()) - currentPath.list[0].e = totalExtrusion - currentPath.list[0].extrudeAmountMultiply = extrudeAmountMultiply + unknownGcodes = {} + unknownMcodes = {} + currentPath = gcodePath('move', pathType, layerThickness, pos) + currentPath['extruder'] = currentExtruder + currentLayer.append(currentPath) for line in gcodeFile: if self._abort: raise AnalysisAborted() if type(line) is tuple: line = line[0] - if self.progressCallback != None: + filePos += 1 + if self.progressCallback is not None and (filePos % 100 == 0): if isinstance(gcodeFile, (file)): - self.progressCallback(float(filePos) / float(self._fileSize)) + self.progressCallback(float(gcodeFile.tell()) / float(self._fileSize)) elif isinstance(gcodeFile, (list)): self.progressCallback(float(filePos) / float(len(gcodeFile))) - filePos += 1 - #Parse Cura_SF comments - if line.startswith(';TYPE:'): - pathType = line[6:].strip() - if pathType != "CUSTOM": - startCodeDone = True - if ';' in line: - # Slic3r GCode comment parser comment = line[line.find(';')+1:].strip() if comment == 'fill': pathType = 'FILL' @@ -112,148 +133,156 @@ class gcode(object): elif comment == 'skirt': pathType = 'SKIRT' elif comment.startswith("filament_diameter"): - filamentDiameter = float(line.split("=", 1)[1].strip()) - - # Cura Gcode comment parser - if comment.startswith('LAYER:'): + self._filamentDiameter = float(comment.split("=", 1)[1].strip()) + elif comment.startswith('TYPE:'): + pathType = comment[5:] + elif comment.startswith('LAYER:'): + currentPath = gcodePath(moveType, pathType, layerThickness, currentPath['points'][-1]) + currentPath['extruder'] = currentExtruder + #for path in currentLayer: + # path['points'] = numpy.array(path['points'], numpy.float32) + # path['extrusion'] = numpy.array(path['extrusion'], numpy.float32) self.layerList.append(currentLayer) - currentLayer = [] + currentLayer = [currentPath] elif comment.startswith("CURA_PROFILE_STRING"): curaOptions = self._parseCuraProfileString(comment) - - if "filament_diameter" in curaOptions.keys(): + if "filament_diameter" in curaOptions: try: - filamentDiameter = float(curaOptions["filament_diameter"]) + self._filamentDiameter = float(curaOptions["filament_diameter"]) except: - filamentDiameter = 0.0 + self._filamentDiameter = 0.0 line = line[0:line.find(';')] - T = self.getCodeInt(line, 'T') + + T = getCodeInt(line, 'T') if T is not None: if currentExtruder > 0: - posOffset.x -= getPreference('extruder_offset_x%d' % (currentExtruder), 0.0) - posOffset.y -= getPreference('extruder_offset_y%d' % (currentExtruder), 0.0) + posOffset[0] -= getPreference('extruder_offset_x%d' % (currentExtruder), 0.0) + posOffset[1] -= getPreference('extruder_offset_y%d' % (currentExtruder), 0.0) currentExtruder = T if currentExtruder > 0: - posOffset.x += getPreference('extruder_offset_x%d' % (currentExtruder), 0.0) - posOffset.y += getPreference('extruder_offset_y%d' % (currentExtruder), 0.0) + posOffset[0] += getPreference('extruder_offset_x%d' % (currentExtruder), 0.0) + posOffset[1] += getPreference('extruder_offset_y%d' % (currentExtruder), 0.0) - G = self.getCodeInt(line, 'G') + G = getCodeInt(line, 'G') if G is not None: if G == 0 or G == 1: #Move - x = self.getCodeFloat(line, 'X') - y = self.getCodeFloat(line, 'Y') - z = self.getCodeFloat(line, 'Z') - e = self.getCodeFloat(line, 'E') - f = self.getCodeFloat(line, 'F') - oldPos = pos.copy() - if x is not None: - if posAbs: - pos.x = x * scale + posOffset.x - else: - pos.x += x * scale - if y is not None: - if posAbs: - pos.y = y * scale + posOffset.y - else: - pos.y += y * scale - if z is not None: - if posAbs: - pos.z = z * scale + posOffset.z - else: - pos.z += z * scale + x = getCodeFloat(line, 'X') + y = getCodeFloat(line, 'Y') + 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: feedRate = f if x is not None or y is not None or z is not None: - totalMoveTimeMinute += (oldPos - pos).vsize() / feedRate + diffX = oldPos[0] - pos[0] + diffY = oldPos[1] - pos[1] + totalMoveTimeMinute += math.sqrt(diffX * diffX + diffY * diffY) / feedRate moveType = 'move' if e is not None: - if posAbsExtruder: - if e > currentE: - moveType = 'extrude' - if e < currentE: - moveType = 'retract' - totalExtrusion += e - currentE - currentE = e - else: - if e > 0: - moveType = 'extrude' - if e < 0: - moveType = 'retract' - totalExtrusion += e - currentE += e + if absoluteE: + e -= currentE + if e > 0.0: + moveType = 'extrude' + if e < 0.0: + moveType = 'retract' + totalExtrusion += e + currentE += e if totalExtrusion > maxExtrusion: maxExtrusion = totalExtrusion - if moveType == 'move' and oldPos.z != pos.z: - if oldPos.z > pos.z and abs(oldPos.z - pos.z) > 5.0 and pos.z < 1.0: - oldPos.z = 0.0 - layerThickness = abs(oldPos.z - pos.z) - if currentPath.type != moveType or currentPath.pathType != pathType: - currentPath = gcodePath(moveType, pathType, layerThickness, currentPath.list[-1]) + else: + e = 0.0 + 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 + layerThickness = abs(oldPos[2] - pos[2]) + if currentPath['type'] != moveType or currentPath['pathType'] != pathType: + currentPath = gcodePath(moveType, pathType, layerThickness, currentPath['points'][-1]) + currentPath['extruder'] = currentExtruder currentLayer.append(currentPath) - newPos = pos.copy() - newPos.e = totalExtrusion - newPos.extrudeAmountMultiply = extrudeAmountMultiply - currentPath.list.append(newPos) + + currentPath['points'].append(pos) + currentPath['extrusion'].append(e * extrudeAmountMultiply) elif G == 4: #Delay - S = self.getCodeFloat(line, 'S') + S = getCodeFloat(line, 'S') if S is not None: - totalMoveTimeMinute += S / 60 - P = self.getCodeFloat(line, 'P') + totalMoveTimeMinute += S / 60.0 + P = getCodeFloat(line, 'P') if P is not None: - totalMoveTimeMinute += P / 60 / 1000 + totalMoveTimeMinute += P / 60.0 / 1000.0 elif G == 20: #Units are inches scale = 25.4 elif G == 21: #Units are mm scale = 1.0 elif G == 28: #Home - x = self.getCodeFloat(line, 'X') - y = self.getCodeFloat(line, 'Y') - z = self.getCodeFloat(line, 'Z') - if x is None and y is None and z is None: - pos = util3d.Vector3() + x = getCodeFloat(line, 'X') + y = getCodeFloat(line, 'Y') + z = getCodeFloat(line, 'Z') + if getPreference('machine_center_is_zero') == 'True': + center = [getPreference('machine_width') / 2, getPreference('machine_depth') / 2,0.0] else: + center = [0.0,0.0,0.0] + if x is None and y is None and z is None: + pos = center + else: + pos = pos[:] if x is not None: - pos.x = 0.0 + pos[0] = center[0] if y is not None: - pos.y = 0.0 + pos[1] = center[1] if z is not None: - pos.z = 0.0 + pos[2] = center[2] elif G == 90: #Absolute position posAbs = True - posAbsExtruder = True elif G == 91: #Relative position posAbs = False - posAbsExtruder = False elif G == 92: - x = self.getCodeFloat(line, 'X') - y = self.getCodeFloat(line, 'Y') - z = self.getCodeFloat(line, 'Z') - e = self.getCodeFloat(line, 'E') + x = getCodeFloat(line, 'X') + y = getCodeFloat(line, 'Y') + z = getCodeFloat(line, 'Z') + e = getCodeFloat(line, 'E') if e is not None: currentE = e if x is not None: - posOffset.x = pos.x - x + posOffset[0] = pos[0] - x if y is not None: - posOffset.y = pos.y - y + posOffset[1] = pos[1] - y if z is not None: - posOffset.z = pos.z - z + posOffset[2] = pos[2] - z else: if G not in unknownGcodes: self._logger.info("Unknown G code: %r" % G) - unknownGcodes[G] = True + unknownGcodes[G] = True else: - M = self.getCodeInt(line, 'M') + M = getCodeInt(line, 'M') if M is not None: - if M == 1: #Message with possible wait (ignored) + if M == 0: #Message with possible wait (ignored) + pass + elif M == 1: #Message with possible wait (ignored) pass elif M == 80: #Enable power supply pass elif M == 81: #Suicide/disable power supply pass - elif M == 82: # Use absolute extruder positions - posAbsExtruder = True - elif M == 83: # Use relative extruder positions - posAbsExtruder = False + elif M == 82: #Absolute E + absoluteE = True + elif M == 83: #Relative E + absoluteE = False elif M == 84: #Disable step drivers pass elif M == 92: #Set steps per unit @@ -278,50 +307,66 @@ class gcode(object): pass elif M == 113: #Extruder PWM (these should not be in the final GCode, but they are) pass + elif M == 117: #LCD message + pass elif M == 140: #Set bed temperature pass elif M == 190: #Set bed temperature & wait pass elif M == 221: #Extrude amount multiplier - s = self.getCodeFloat(line, 'S') - if s != None: + s = getCodeFloat(line, 'S') + if s is not None: extrudeAmountMultiply = s / 100.0 else: if M not in unknownMcodes: self._logger.info("Unknown M code: %r" % M) - unknownMcodes[M] = True + unknownMcodes[M] = True + #for path in currentLayer: + # path['points'] = numpy.array(path['points'], numpy.float32) + # path['extrusion'] = numpy.array(path['extrusion'], numpy.float32) self.layerList.append(currentLayer) + if self.progressCallback is not None: + self.progressCallback(100.0) self.extrusionAmount = maxExtrusion - if filamentDiameter is not None and filamentDiameter > 0: - self.extrusionVolume = math.pi * math.pow(filamentDiameter / 2.0, 2) * maxExtrusion / 1000.0 self.totalMoveTimeMinute = totalMoveTimeMinute - - def getCodeInt(self, line, code): - if code not in self.regMatch: - self.regMatch[code] = re.compile(code + '([^\s]+)') - m = self.regMatch[code].search(line) - if m == None: - return None - try: - return int(m.group(1)) - except: - return None - - def getCodeFloat(self, line, code): - if code not in self.regMatch: - self.regMatch[code] = re.compile(code + '([^\s]+)') - m = self.regMatch[code].search(line) - if m == None: - return None - try: - return float(m.group(1)) - except: - return None + #print "Extruded a total of: %d mm of filament" % (self.extrusionAmount) + #print "Estimated print duration: %.2f minutes" % (self.totalMoveTimeMinute) def _parseCuraProfileString(self, comment): return {key: value for (key, value) in map(lambda x: x.split("=", 1), zlib.decompress(base64.b64decode(comment[len("CURA_PROFILE_STRING:"):])).split("\b"))} -if __name__ == '__main__': - for filename in sys.argv[1:]: - gcode().load(filename) +def getCodeInt(line, code): + n = line.find(code) + 1 + if n < 1: + return None + m = line.find(' ', n) + try: + if m < 0: + return int(line[n:]) + return int(line[n:m]) + except: + return None + +def getCodeFloat(line, code): + n = line.find(code) + 1 + if n < 1: + return None + m = line.find(' ', n) + try: + if m < 0: + return float(line[n:]) + return float(line[n:m]) + except: + return None + +if __name__ == '__main__': + from time import time + t = time() + for filename in sys.argv[1:]: + g = gcode() + g.load(filename) + print g.totalMoveTimeMinute + print g.extrusionAmount + print g.calculateVolumeCm3() + print time() - t From 821c0c9abb6267c967f5a4e0b96cd936228758df Mon Sep 17 00:00:00 2001 From: Bryan Mayland Date: Wed, 7 Aug 2013 11:08:08 -0400 Subject: [PATCH 2/4] Don't store layers or path points to save memory --- octoprint/util/gcodeInterpreter.py | 32 +++++++++++++++--------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/octoprint/util/gcodeInterpreter.py b/octoprint/util/gcodeInterpreter.py index cb36f12..4a52e0f 100644 --- a/octoprint/util/gcodeInterpreter.py +++ b/octoprint/util/gcodeInterpreter.py @@ -105,13 +105,13 @@ class gcode(object): moveType = 'move' layerThickness = 0.1 pathType = 'CUSTOM' - currentLayer = [] + #currentLayer = [] unknownGcodes = {} unknownMcodes = {} - currentPath = gcodePath('move', pathType, layerThickness, pos) - currentPath['extruder'] = currentExtruder + #currentPath = gcodePath('move', pathType, layerThickness, pos) + #currentPath['extruder'] = currentExtruder - currentLayer.append(currentPath) + #currentLayer.append(currentPath) for line in gcodeFile: if self._abort: raise AnalysisAborted() @@ -136,14 +136,14 @@ class gcode(object): self._filamentDiameter = float(comment.split("=", 1)[1].strip()) elif comment.startswith('TYPE:'): pathType = comment[5:] - elif comment.startswith('LAYER:'): - currentPath = gcodePath(moveType, pathType, layerThickness, currentPath['points'][-1]) - currentPath['extruder'] = currentExtruder + #elif comment.startswith('LAYER:'): + #currentPath = gcodePath(moveType, pathType, layerThickness, currentPath['points'][-1]) + #currentPath['extruder'] = currentExtruder #for path in currentLayer: # path['points'] = numpy.array(path['points'], numpy.float32) # path['extrusion'] = numpy.array(path['extrusion'], numpy.float32) - self.layerList.append(currentLayer) - currentLayer = [currentPath] + #self.layerList.append(currentLayer) + #currentLayer = [currentPath] elif comment.startswith("CURA_PROFILE_STRING"): curaOptions = self._parseCuraProfileString(comment) if "filament_diameter" in curaOptions: @@ -211,13 +211,13 @@ class gcode(object): if oldPos[2] > pos[2] and abs(oldPos[2] - pos[2]) > 5.0 and pos[2] < 1.0: oldPos[2] = 0.0 layerThickness = abs(oldPos[2] - pos[2]) - if currentPath['type'] != moveType or currentPath['pathType'] != pathType: - currentPath = gcodePath(moveType, pathType, layerThickness, currentPath['points'][-1]) - currentPath['extruder'] = currentExtruder - currentLayer.append(currentPath) + #if currentPath['type'] != moveType or currentPath['pathType'] != pathType: + # currentPath = gcodePath(moveType, pathType, layerThickness, currentPath['points'][-1]) + # currentPath['extruder'] = currentExtruder + # currentLayer.append(currentPath) - currentPath['points'].append(pos) - currentPath['extrusion'].append(e * extrudeAmountMultiply) + #currentPath['points'].append(pos) + #currentPath['extrusion'].append(e * extrudeAmountMultiply) elif G == 4: #Delay S = getCodeFloat(line, 'S') if S is not None: @@ -324,7 +324,7 @@ class gcode(object): #for path in currentLayer: # path['points'] = numpy.array(path['points'], numpy.float32) # path['extrusion'] = numpy.array(path['extrusion'], numpy.float32) - self.layerList.append(currentLayer) + #self.layerList.append(currentLayer) if self.progressCallback is not None: self.progressCallback(100.0) self.extrusionAmount = maxExtrusion From 27bc7d3d03c6c3d6e7f94695b971fc70173a8fa3 Mon Sep 17 00:00:00 2001 From: Bryan Mayland Date: Wed, 7 Aug 2013 11:23:40 -0400 Subject: [PATCH 3/4] Increase progress callback to every 1000 lines. The processor handles 2500 lines per second on a 700MHz pi so as a baseline that's still 2.5 updates per second. --- octoprint/util/gcodeInterpreter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/octoprint/util/gcodeInterpreter.py b/octoprint/util/gcodeInterpreter.py index 4a52e0f..ee55c86 100644 --- a/octoprint/util/gcodeInterpreter.py +++ b/octoprint/util/gcodeInterpreter.py @@ -118,7 +118,7 @@ class gcode(object): if type(line) is tuple: line = line[0] filePos += 1 - if self.progressCallback is not None and (filePos % 100 == 0): + if self.progressCallback is not None and (filePos % 1000 == 0): if isinstance(gcodeFile, (file)): self.progressCallback(float(gcodeFile.tell()) / float(self._fileSize)) elif isinstance(gcodeFile, (list)): From c7501ded4783496d42f15c821c6421d7249b8f9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Sun, 11 Aug 2013 17:39:45 +0200 Subject: [PATCH 4/4] Removed commented out and unused code --- octoprint/util/README | 3 +- octoprint/util/gcodeInterpreter.py | 51 +---- octoprint/util/util3d.py | 317 ----------------------------- 3 files changed, 3 insertions(+), 368 deletions(-) delete mode 100644 octoprint/util/util3d.py diff --git a/octoprint/util/README b/octoprint/util/README index 145ef86..80d6863 100644 --- a/octoprint/util/README +++ b/octoprint/util/README @@ -2,6 +2,5 @@ The code in this sub package mostly originates from the Cura project (https://gi slightly reorganized and adapted. The mapping to the original Cura source is the following: * avr_isp.* => Cura.avr_isp.* -* comm => Cura.util.machineCom +* comm => Cura.util.machineCom (highly modified now) * gcodeInterpreter => Cura.util.gcodeInterpreter -* util3d => Cura.util.util3d \ No newline at end of file diff --git a/octoprint/util/gcodeInterpreter.py b/octoprint/util/gcodeInterpreter.py index ee55c86..1621dbf 100644 --- a/octoprint/util/gcodeInterpreter.py +++ b/octoprint/util/gcodeInterpreter.py @@ -26,13 +26,6 @@ def getPreference(key, default=None): class AnalysisAborted(Exception): pass -#class gcodePath(object): -# def __init__(self, newType, pathType, layerThickness, startPoint): -# self.type = newType -# self.pathType = pathType -# self.layerThickness = layerThickness -# self.points = [startPoint] -# self.extrusion = [0.0] def gcodePath(newType, pathType, layerThickness, startPoint): return {'type': newType, 'pathType': pathType, @@ -73,7 +66,7 @@ class gcode(object): def calculateWeight(self): #Calculates the weight of the filament in kg - volumeM3 = calculateVolumeCm3 /(1000*1000) + volumeM3 = self.calculateVolumeCm3() /(1000*1000) return volumeM3 * getPreference('filament_physical_density') def calculateCost(self): @@ -96,22 +89,14 @@ class gcode(object): totalExtrusion = 0.0 maxExtrusion = 0.0 currentExtruder = 0 - extrudeAmountMultiply = 1.0 totalMoveTimeMinute = 0.0 absoluteE = True scale = 1.0 posAbs = True feedRate = 3600.0 - moveType = 'move' - layerThickness = 0.1 - pathType = 'CUSTOM' - #currentLayer = [] unknownGcodes = {} unknownMcodes = {} - #currentPath = gcodePath('move', pathType, layerThickness, pos) - #currentPath['extruder'] = currentExtruder - #currentLayer.append(currentPath) for line in gcodeFile: if self._abort: raise AnalysisAborted() @@ -126,24 +111,8 @@ class gcode(object): if ';' in line: comment = line[line.find(';')+1:].strip() - if comment == 'fill': - pathType = 'FILL' - elif comment == 'perimeter': - pathType = 'WALL-INNER' - elif comment == 'skirt': - pathType = 'SKIRT' - elif comment.startswith("filament_diameter"): + if comment.startswith("filament_diameter"): self._filamentDiameter = float(comment.split("=", 1)[1].strip()) - elif comment.startswith('TYPE:'): - pathType = comment[5:] - #elif comment.startswith('LAYER:'): - #currentPath = gcodePath(moveType, pathType, layerThickness, currentPath['points'][-1]) - #currentPath['extruder'] = currentExtruder - #for path in currentLayer: - # path['points'] = numpy.array(path['points'], numpy.float32) - # path['extrusion'] = numpy.array(path['extrusion'], numpy.float32) - #self.layerList.append(currentLayer) - #currentLayer = [currentPath] elif comment.startswith("CURA_PROFILE_STRING"): curaOptions = self._parseCuraProfileString(comment) if "filament_diameter" in curaOptions: @@ -210,14 +179,6 @@ class gcode(object): 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 - layerThickness = abs(oldPos[2] - pos[2]) - #if currentPath['type'] != moveType or currentPath['pathType'] != pathType: - # currentPath = gcodePath(moveType, pathType, layerThickness, currentPath['points'][-1]) - # currentPath['extruder'] = currentExtruder - # currentLayer.append(currentPath) - - #currentPath['points'].append(pos) - #currentPath['extrusion'].append(e * extrudeAmountMultiply) elif G == 4: #Delay S = getCodeFloat(line, 'S') if S is not None: @@ -315,22 +276,14 @@ class gcode(object): pass elif M == 221: #Extrude amount multiplier s = getCodeFloat(line, 'S') - if s is not None: - extrudeAmountMultiply = s / 100.0 else: if M not in unknownMcodes: self._logger.info("Unknown M code: %r" % M) unknownMcodes[M] = True - #for path in currentLayer: - # path['points'] = numpy.array(path['points'], numpy.float32) - # path['extrusion'] = numpy.array(path['extrusion'], numpy.float32) - #self.layerList.append(currentLayer) if self.progressCallback is not None: self.progressCallback(100.0) self.extrusionAmount = maxExtrusion self.totalMoveTimeMinute = totalMoveTimeMinute - #print "Extruded a total of: %d mm of filament" % (self.extrusionAmount) - #print "Estimated print duration: %.2f minutes" % (self.totalMoveTimeMinute) def _parseCuraProfileString(self, comment): return {key: value for (key, value) in map(lambda x: x.split("=", 1), zlib.decompress(base64.b64decode(comment[len("CURA_PROFILE_STRING:"):])).split("\b"))} diff --git a/octoprint/util/util3d.py b/octoprint/util/util3d.py deleted file mode 100644 index e599447..0000000 --- a/octoprint/util/util3d.py +++ /dev/null @@ -1,317 +0,0 @@ -from __future__ import absolute_import - -import math -import numpy - -class Vector3(object): - def __init__(self, x=0.0, y=0.0, z=0.0): - self.x = x - self.y = y - self.z = z - - def __copy__(self): - return Vector3(self.x, self.y, self.z) - - def copy(self): - return Vector3(self.x, self.y, self.z) - - def __repr__(self): - return 'V[%s, %s, %s]' % ( self.x, self.y, self.z ) - - def __add__(self, v): - return Vector3( self.x + v.x, self.y + v.y, self.z + v.z ) - - def __sub__(self, v): - return Vector3( self.x - v.x, self.y - v.y, self.z - v.z ) - - def __mul__(self, v): - return Vector3( self.x * v, self.y * v, self.z * v ) - - def __div__(self, v): - return Vector3( self.x / v, self.y / v, self.z / v ) - __truediv__ = __div__ - - def __neg__(self): - return Vector3( - self.x, - self.y, - self.z ) - - def __iadd__(self, v): - self.x += v.x - self.y += v.y - self.z += v.z - return self - - def __isub__(self, v): - self.x += v.x - self.y += v.y - self.z += v.z - return self - - def __imul__(self, v): - self.x *= v - self.y *= v - self.z *= v - return self - - def __idiv__(self, v): - self.x /= v - self.y /= v - self.z /= v - return self - - def almostEqual(self, v): - return (abs(self.x - v.x) + abs(self.y - v.y) + abs(self.z - v.z)) < 0.00001 - - def cross(self, v): - return Vector3(self.y * v.z - self.z * v.y, -self.x * v.z + self.z * v.x, self.x * v.y - self.y * v.x) - - def vsize(self): - return math.sqrt( self.x * self.x + self.y * self.y + self.z * self.z ) - - def normalize(self): - f = self.vsize() - if f != 0.0: - self.x /= f - self.y /= f - self.z /= f - - def min(self, v): - return Vector3(min(self.x, v.x), min(self.y, v.y), min(self.z, v.z)) - - def max(self, v): - return Vector3(max(self.x, v.x), max(self.y, v.y), max(self.z, v.z)) - -class AABB(object): - def __init__(self, vMin, vMax): - self.vMin = vMin - self.vMax = vMax - self.perimeter = numpy.sum(self.vMax - self.vMin) - - def combine(self, aabb): - return AABB(numpy.minimum(self.vMin, aabb.vMin), numpy.maximum(self.vMax, aabb.vMax)) - - def overlap(self, aabb): - if aabb.vMin[0] - self.vMax[0] > 0.0 or aabb.vMin[1] - self.vMax[1] > 0.0 or aabb.vMin[2] - self.vMax[2] > 0.0: - return False - if self.vMin[0] - aabb.vMax[0] > 0.0 or self.vMin[1] - aabb.vMax[1] > 0.0 or self.vMin[2] - aabb.vMax[2] > 0.0: - return False - return True - - def __repr__(self): - return "AABB:%s - %s" % (str(self.vMin), str(self.vMax)) - -class _AABBNode(object): - def __init__(self, aabb): - self.child1 = None - self.child2 = None - self.parent = None - self.height = 0 - self.aabb = aabb - - def isLeaf(self): - return self.child1 == None - -class AABBTree(object): - def __init__(self): - self.root = None - - def insert(self, aabb): - newNode = _AABBNode(aabb) - if self.root == None: - self.root = newNode - return - - node = self.root - while not node.isLeaf(): - child1 = node.child1 - child2 = node.child2 - - area = node.aabb.perimeter - combinedAABB = node.aabb.combine(aabb) - combinedArea = combinedAABB.perimeter - - cost = 2.0 * combinedArea - inheritanceCost = 2.0 * (combinedArea - area) - - if child1.isLeaf(): - cost1 = aabb.combine(child1.aabb).perimeter + inheritanceCost - else: - oldArea = child1.aabb.perimeter - newArea = aabb.combine(child1.aabb).perimeter - cost1 = (newArea - oldArea) + inheritanceCost - - if child2.isLeaf(): - cost2 = aabb.combine(child1.aabb).perimeter + inheritanceCost - else: - oldArea = child2.aabb.perimeter - newArea = aabb.combine(child2.aabb).perimeter - cost2 = (newArea - oldArea) + inheritanceCost - - if cost < cost1 and cost < cost2: - break - - if cost1 < cost2: - node = child1 - else: - node = child2 - - sibling = node - - # Create a new parent. - oldParent = sibling.parent - newParent = _AABBNode(aabb.combine(sibling.aabb)) - newParent.parent = oldParent - newParent.height = sibling.height + 1 - - if oldParent != None: - # The sibling was not the root. - if oldParent.child1 == sibling: - oldParent.child1 = newParent - else: - oldParent.child2 = newParent - - newParent.child1 = sibling - newParent.child2 = newNode - sibling.parent = newParent - newNode.parent = newParent - else: - # The sibling was the root. - newParent.child1 = sibling - newParent.child2 = newNode - sibling.parent = newParent - newNode.parent = newParent - self.root = newParent - - # Walk back up the tree fixing heights and AABBs - node = newNode.parent - while node != None: - node = self._balance(node) - - child1 = node.child1 - child2 = node.child2 - - node.height = 1 + max(child1.height, child2.height) - node.aabb = child1.aabb.combine(child2.aabb) - - node = node.parent - - def _balance(self, A): - if A.isLeaf() or A.height < 2: - return A - - B = A.child1 - C = A.child2 - - balance = C.height - B.height - - # Rotate C up - if balance > 1: - F = C.child1; - G = C.child2; - - # Swap A and C - C.child1 = A; - C.parent = A.parent; - A.parent = C; - - # A's old parent should point to C - if C.parent != None: - if C.parent.child1 == A: - C.parent.child1 = C - else: - C.parent.child2 = C - else: - self.root = C - - # Rotate - if F.height > G.height: - C.child2 = F - A.child2 = G - G.parent = A - A.aabb = B.aabb.combine(G.aabb) - C.aabb = A.aabb.combine(F.aabb) - - A.height = 1 + max(B.height, G.height) - C.height = 1 + max(A.height, F.height) - else: - C.child2 = G - A.child2 = F - F.parent = A - A.aabb = B.aabb.combine(F.aabb) - C.aabb = A.aabb.combine(G.aabb) - - A.height = 1 + max(B.height, F.height) - C.height = 1 + max(A.height, G.height) - - return C; - - # Rotate B up - if balance < -1: - D = B.child1 - E = B.child2 - - # Swap A and B - B.child1 = A - B.parent = A.parent - A.parent = B - - # A's old parent should point to B - if B.parent != None: - if B.parent.child1 == A: - B.parent.child1 = B - else: - B.parent.child2 = B - else: - self.root = B - - # Rotate - if D.height > E.height: - B.child2 = D - A.child1 = E - E.parent = A - A.aabb = C.aabb.combine(E.aabb) - B.aabb = A.aabb.combine(D.aabb) - - A.height = 1 + max(C.height, E.height) - B.height = 1 + max(A.height, D.height) - else: - B.child2 = E - A.child1 = D - D.parent = A - A.aabb = C.aabb.combine(D.aabb) - B.aabb = A.aabb.combine(E.aabb) - - A.height = 1 + max(C.height, D.height) - B.height = 1 + max(A.height, E.height) - - return B - - return A - - def query(self, aabb): - resultList = [] - if self.root != None: - self._query(self.root, aabb, resultList) - return resultList - - def _query(self, node, aabb, resultList): - if not aabb.overlap(node.aabb): - return - if node.isLeaf(): - resultList.append(node.aabb) - else: - self._query(node.child1, aabb, resultList) - self._query(node.child2, aabb, resultList) - - def __repr__(self): - s = "AABBTree:\n" - s += str(self.root.aabb) - return s - -if __name__ == '__main__': - tree = AABBTree() - tree.insert(AABB(Vector3(0,0,0), Vector3(0,0,0))) - tree.insert(AABB(Vector3(1,1,1), Vector3(1,1,1))) - tree.insert(AABB(Vector3(0.5,0.5,0.5), Vector3(0.5,0.5,0.5))) - print(tree) - print(tree.query(AABB(Vector3(0,0,0), Vector3(0,0,0)))) -