OctoPrint/Cura/cura_sf/fabmetheus_utilities/gcodec.py
Stefan Blanke 70bbc16abe Fix version detection for all Python versions
Inspecting sys.version_info using named component attributes was added in
the patch "Some fixes to start support for python3."

Named component attributes were only added to sys.version_info in Python
2.7 so their use breaks Cura for Python 2.6.

As Cura otherwise works in the still widely used Python 2.6 it seems best
to maintain this compatibility.
2012-06-22 14:37:52 +01:00

446 lines
17 KiB
Python

"""
Gcodec is a collection of utilities to decode and encode gcode.
To run gcodec, install python 2.x on your machine, which is avaliable from http://www.python.org/download/
Then in the folder which gcodec is in, type 'python' in a shell to run the python interpreter. Finally type 'from gcodec import *' to import this program.
Below is an example of gcodec use. This example is run in a terminal in the folder which contains gcodec and Screw Holder Bottom_export.gcode.
>>> from gcodec import *
>>> getFileText('Screw Holder Bottom_export.gcode')
'G90\nG21\nM103\nM105\nM106\nM110 S60.0\nM111 S30.0\nM108 S210.0\nM104 S235.0\nG1 X0.37 Y-4.07 Z1.9 F60.0\nM101\n
..
many lines of text
..
"""
from __future__ import absolute_import
#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module.
import __init__
from fabmetheus_utilities.vector3 import Vector3
from fabmetheus_utilities import archive
from fabmetheus_utilities import euclidean
import math
import os
import sys
import traceback
if sys.version_info[0] < 3:
import cStringIO
else:
import io as cStringIO
__author__ = 'Enrique Perez (perez_enrique@yahoo.com)'
__date__ = '$Date: 2008/21/04 $'
__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
def addLineAndNewlineIfNecessary(line, output):
'Add the line and if the line does not end with a newline add a newline.'
output.write(line)
if len(line) < 1:
return
if not line.endswith('\n'):
output.write('\n')
def addLinesToCString(cString, lines):
'Add lines which have something to cStringIO.'
for line in lines:
if line != '':
cString.write(line + '\n')
def getArcDistance(relativeLocation, splitLine):
'Get arc distance.'
halfPlaneLineDistance = 0.5 * abs(relativeLocation.dropAxis())
radius = getDoubleFromCharacterSplitLine('R', splitLine)
if radius == None:
iFloat = getDoubleFromCharacterSplitLine('I', splitLine)
jFloat = getDoubleFromCharacterSplitLine('J', splitLine)
radius = abs(complex(iFloat, jFloat))
angle = 0.0
if radius > 0.0:
halfPlaneLineDistanceOverRadius = halfPlaneLineDistance / radius
if halfPlaneLineDistance < radius:
angle = 2.0 * math.asin(halfPlaneLineDistanceOverRadius)
else:
angle = math.pi * halfPlaneLineDistanceOverRadius
return abs(complex(angle * radius, relativeLocation.z))
def getDoubleAfterFirstLetter(word):
'Get the double value of the word after the first letter.'
return float(word[1 :])
def getDoubleForLetter(letter, splitLine):
'Get the double value of the word after the first occurence of the letter in the split line.'
return getDoubleAfterFirstLetter(splitLine[getIndexOfStartingWithSecond(letter, splitLine)])
def getDoubleFromCharacterSplitLine(character, splitLine):
'Get the double value of the string after the first occurence of the character in the split line.'
indexOfCharacter = getIndexOfStartingWithSecond(character, splitLine)
if indexOfCharacter < 0:
return None
floatString = splitLine[indexOfCharacter][1 :]
try:
return float(floatString)
except ValueError:
return None
def getDoubleFromCharacterSplitLineValue(character, splitLine, value):
'Get the double value of the string after the first occurence of the character in the split line, if it does not exist return the value.'
splitLineFloat = getDoubleFromCharacterSplitLine(character, splitLine)
if splitLineFloat == None:
return value
return splitLineFloat
def getFeedRateMinute(feedRateMinute, splitLine):
'Get the feed rate per minute if the split line has a feed rate.'
indexOfF = getIndexOfStartingWithSecond('F', splitLine)
if indexOfF > 0:
return getDoubleAfterFirstLetter( splitLine[indexOfF] )
return feedRateMinute
def getFirstWord(splitLine):
'Get the first word of a split line.'
if len(splitLine) > 0:
return splitLine[0]
return ''
def getFirstWordFromLine(line):
'Get the first word of a line.'
return getFirstWord(line.split())
def getFirstWordIndexReverse(firstWord, lines, startIndex):
'Parse gcode in reverse order until the first word if there is one, otherwise return -1.'
for lineIndex in xrange(len(lines) - 1, startIndex - 1, -1):
if firstWord == getFirstWord(getSplitLineBeforeBracketSemicolon(lines[lineIndex])):
return lineIndex
return -1
def getGcodeFileText(fileName, gcodeText):
'Get the gcode text from a file if it the gcode text is empty and if the file is a gcode file.'
if gcodeText != '':
return gcodeText
if fileName.endswith('.gcode'):
return archive.getFileText(fileName)
return ''
def getGcodeWithoutDuplication(duplicateWord, gcodeText):
'Get gcode text without duplicate first words.'
lines = archive.getTextLines(gcodeText)
oldWrittenLine = None
output = cStringIO.StringIO()
for line in lines:
firstWord = getFirstWordFromLine(line)
if firstWord == duplicateWord:
if line != oldWrittenLine:
output.write(line + '\n')
oldWrittenLine = line
else:
if len(line) > 0:
output.write(line + '\n')
return output.getvalue()
def getIndexOfStartingWithSecond(letter, splitLine):
'Get index of the first occurence of the given letter in the split line, starting with the second word. Return - 1 if letter is not found'
for wordIndex in xrange( 1, len(splitLine) ):
word = splitLine[ wordIndex ]
firstLetter = word[0]
if firstLetter == letter:
return wordIndex
return - 1
def getLineWithValueString(character, line, splitLine, valueString):
'Get the line with a valueString.'
roundedValueString = character + valueString
indexOfValue = getIndexOfStartingWithSecond(character, splitLine)
if indexOfValue == -1:
return line + ' ' + roundedValueString
word = splitLine[indexOfValue]
return line.replace(word, roundedValueString)
def getLocationFromSplitLine(oldLocation, splitLine):
'Get the location from the split line.'
if oldLocation == None:
oldLocation = Vector3()
return Vector3(
getDoubleFromCharacterSplitLineValue('X', splitLine, oldLocation.x),
getDoubleFromCharacterSplitLineValue('Y', splitLine, oldLocation.y),
getDoubleFromCharacterSplitLineValue('Z', splitLine, oldLocation.z))
def getRotationBySplitLine(splitLine):
'Get the complex rotation from the split gcode line.'
return complex(splitLine[1].replace('(', '').replace(')', ''))
def getSplitLineBeforeBracketSemicolon(line):
'Get the split line before a bracket or semicolon.'
if ';' in line:
line = line[: line.find(';')]
bracketIndex = line.find('(')
if bracketIndex > 0:
return line[: bracketIndex].split()
return line.split()
def getStringFromCharacterSplitLine(character, splitLine):
'Get the string after the first occurence of the character in the split line.'
indexOfCharacter = getIndexOfStartingWithSecond(character, splitLine)
if indexOfCharacter < 0:
return None
return splitLine[indexOfCharacter][1 :]
def getTagBracketedLine(tagName, value):
'Get line with a begin tag, value and end tag.'
return '(<%s> %s </%s>)' % (tagName, value, tagName)
def getTagBracketedProcedure(procedure):
'Get line with a begin procedure tag, procedure and end procedure tag.'
return getTagBracketedLine('procedureName', procedure)
def isProcedureDone(gcodeText, procedure):
'Determine if the procedure has been done on the gcode text.'
if gcodeText == '':
return False
extruderInitializationIndex = gcodeText.find('(</extruderInitialization>)')
if extruderInitializationIndex == -1:
metadataBeginIndex = gcodeText.find('<metadata>')
metadataEndIndex = gcodeText.find('</metadata>')
if metadataBeginIndex != -1 and metadataEndIndex != -1:
attributeString = "procedureName='%s'" % procedure
return gcodeText.find(attributeString, metadataBeginIndex, metadataEndIndex) != -1
return False
return gcodeText.find(getTagBracketedProcedure(procedure), 0, extruderInitializationIndex) != -1
def isProcedureDoneOrFileIsEmpty(gcodeText, procedure):
'Determine if the procedure has been done on the gcode text or the file is empty.'
if gcodeText == '':
return True
return isProcedureDone(gcodeText, procedure)
def isThereAFirstWord(firstWord, lines, startIndex):
'Parse gcode until the first word if there is one.'
for lineIndex in xrange(startIndex, len(lines)):
line = lines[lineIndex]
splitLine = getSplitLineBeforeBracketSemicolon(line)
if firstWord == getFirstWord(splitLine):
return True
return False
class BoundingRectangle:
'A class to get the corners of a gcode text.'
def getFromGcodeLines(self, lines, radius):
'Parse gcode text and get the minimum and maximum corners.'
self.cornerMaximum = complex(-987654321.0, -987654321.0)
self.cornerMinimum = complex(987654321.0, 987654321.0)
self.oldLocation = None
self.cornerRadius = complex(radius, radius)
for line in lines:
self.parseCorner(line)
return self
def isPointInside(self, point):
'Determine if the point is inside the bounding rectangle.'
return point.imag >= self.cornerMinimum.imag and point.imag <= self.cornerMaximum.imag and point.real >= self.cornerMinimum.real and point.real <= self.cornerMaximum.real
def parseCorner(self, line):
'Parse a gcode line and use the location to update the bounding corners.'
splitLine = getSplitLineBeforeBracketSemicolon(line)
firstWord = getFirstWord(splitLine)
if firstWord == '(<boundaryPoint>':
locationComplex = getLocationFromSplitLine(None, splitLine).dropAxis()
self.cornerMaximum = euclidean.getMaximum(self.cornerMaximum, locationComplex)
self.cornerMinimum = euclidean.getMinimum(self.cornerMinimum, locationComplex)
elif firstWord == 'G1':
location = getLocationFromSplitLine(self.oldLocation, splitLine)
locationComplex = location.dropAxis()
self.cornerMaximum = euclidean.getMaximum(self.cornerMaximum, locationComplex + self.cornerRadius)
self.cornerMinimum = euclidean.getMinimum(self.cornerMinimum, locationComplex - self.cornerRadius)
self.oldLocation = location
class DistanceFeedRate:
'A class to limit the z feed rate and round values.'
def __init__(self):
'Initialize.'
self.isAlteration = False
self.decimalPlacesCarried = 3
self.output = cStringIO.StringIO()
def addFlowRateLine(self, flowRate):
'Add a flow rate line.'
self.output.write('M108 S%s\n' % euclidean.getFourSignificantFigures(flowRate))
def addGcodeFromFeedRateThreadZ(self, feedRateMinute, thread, travelFeedRateMinute, z):
'Add a thread to the output.'
if len(thread) > 0:
self.addGcodeMovementZWithFeedRate(travelFeedRateMinute, thread[0], z)
else:
print('zero length vertex positions array which was skipped over, this should never happen.')
if len(thread) < 2:
print('thread of only one point in addGcodeFromFeedRateThreadZ in gcodec, this should never happen.')
print(thread)
return
self.output.write('M101\n') # Turn extruder on.
for point in thread[1 :]:
self.addGcodeMovementZWithFeedRate(feedRateMinute, point, z)
self.output.write('M103\n') # Turn extruder off.
def addGcodeFromLoop(self, loop, z):
'Add the gcode loop.'
euclidean.addNestedRingBeginning(self, loop, z)
self.addPerimeterBlock(loop, z)
self.addLine('(</boundaryPerimeter>)')
self.addLine('(</nestedRing>)')
def addGcodeFromThreadZ(self, thread, z):
'Add a thread to the output.'
if len(thread) > 0:
self.addGcodeMovementZ(thread[0], z)
else:
print('zero length vertex positions array which was skipped over, this should never happen.')
if len(thread) < 2:
print('thread of only one point in addGcodeFromThreadZ in gcodec, this should never happen.')
print(thread)
return
self.output.write('M101\n') # Turn extruder on.
for point in thread[1 :]:
self.addGcodeMovementZ(point, z)
self.output.write('M103\n') # Turn extruder off.
def addGcodeMovementZ(self, point, z):
'Add a movement to the output.'
self.output.write(self.getLinearGcodeMovement(point, z) + '\n')
def addGcodeMovementZWithFeedRate(self, feedRateMinute, point, z):
'Add a movement to the output.'
self.output.write(self.getLinearGcodeMovementWithFeedRate(feedRateMinute, point, z) + '\n')
def addGcodeMovementZWithFeedRateVector3(self, feedRateMinute, vector3):
'Add a movement to the output by Vector3.'
xRounded = self.getRounded(vector3.x)
yRounded = self.getRounded(vector3.y)
self.output.write('G1 X%s Y%s Z%s F%s\n' % (xRounded, yRounded, self.getRounded(vector3.z), self.getRounded(feedRateMinute)))
def addLine(self, line):
'Add a line of text and a newline to the output.'
if len(line) > 0:
self.output.write(line + '\n')
def addLineCheckAlteration(self, line):
'Add a line of text and a newline to the output and check to see if it is an alteration line.'
firstWord = getFirstWord(getSplitLineBeforeBracketSemicolon(line))
if firstWord == '(<alteration>)':
self.isAlteration = True
elif firstWord == '(</alteration>)':
self.isAlteration = False
if len(line) > 0:
self.output.write(line + '\n')
def addLines(self, lines):
'Add lines of text to the output.'
addLinesToCString(self.output, lines)
def addLinesSetAbsoluteDistanceMode(self, lines):
'Add lines of text to the output and ensure the absolute mode is set.'
if len(lines) < 1:
return
if len(lines[0]) < 1:
return
absoluteDistanceMode = True
self.addLine('(<alteration>)')
for line in lines:
splitLine = getSplitLineBeforeBracketSemicolon(line)
firstWord = getFirstWord(splitLine)
if firstWord == 'G90':
absoluteDistanceMode = True
elif firstWord == 'G91':
absoluteDistanceMode = False
self.addLine('(<alterationDeleteThisPrefix/>)' + line)
if not absoluteDistanceMode:
self.addLine('G90')
self.addLine('(</alteration>)')
def addParameter(self, firstWord, parameter):
'Add the parameter.'
self.addLine(firstWord + ' S' + euclidean.getRoundedToThreePlaces(parameter))
def addPerimeterBlock(self, loop, z):
'Add the edge gcode block for the loop.'
if len(loop) < 2:
return
if euclidean.isWiddershins(loop): # Indicate that an edge is beginning.
self.addLine('(<edge> outer )')
else:
self.addLine('(<edge> inner )')
self.addGcodeFromThreadZ(loop + [loop[0]], z)
self.addLine('(</edge>)') # Indicate that an edge is beginning.
def addTagBracketedLine(self, tagName, value):
'Add a begin tag, value and end tag.'
self.addLine(getTagBracketedLine(tagName, value))
def addTagRoundedLine(self, tagName, value):
'Add a begin tag, rounded value and end tag.'
self.addLine('(<%s> %s </%s>)' % (tagName, self.getRounded(value), tagName))
def addTagBracketedProcedure(self, procedure):
'Add a begin procedure tag, procedure and end procedure tag.'
self.addLine(getTagBracketedProcedure(procedure))
def getBoundaryLine(self, location):
'Get boundary gcode line.'
return '(<boundaryPoint> X%s Y%s Z%s </boundaryPoint>)' % (self.getRounded(location.x), self.getRounded(location.y), self.getRounded(location.z))
def getFirstWordMovement(self, firstWord, location):
'Get the start of the arc line.'
return '%s X%s Y%s Z%s' % (firstWord, self.getRounded(location.x), self.getRounded(location.y), self.getRounded(location.z))
def getInfillBoundaryLine(self, location):
'Get infill boundary gcode line.'
return '(<infillPoint> X%s Y%s Z%s </infillPoint>)' % (self.getRounded(location.x), self.getRounded(location.y), self.getRounded(location.z))
def getIsAlteration(self, line):
'Determine if it is an alteration.'
if self.isAlteration:
self.addLineCheckAlteration(line)
return True
return False
def getLinearGcodeMovement(self, point, z):
'Get a linear gcode movement.'
return 'G1 X%s Y%s Z%s' % (self.getRounded(point.real), self.getRounded(point.imag), self.getRounded(z))
def getLinearGcodeMovementWithFeedRate(self, feedRateMinute, point, z):
'Get a z limited gcode movement.'
linearGcodeMovement = self.getLinearGcodeMovement(point, z)
if feedRateMinute == None:
return linearGcodeMovement
return linearGcodeMovement + ' F' + self.getRounded(feedRateMinute)
def getLineWithFeedRate(self, feedRateMinute, line, splitLine):
'Get the line with a feed rate.'
return getLineWithValueString('F', line, splitLine, self.getRounded(feedRateMinute))
def getLineWithX(self, line, splitLine, x):
'Get the line with an x.'
return getLineWithValueString('X', line, splitLine, self.getRounded(x))
def getLineWithY(self, line, splitLine, y):
'Get the line with a y.'
return getLineWithValueString('Y', line, splitLine, self.getRounded(y))
def getLineWithZ(self, line, splitLine, z):
'Get the line with a z.'
return getLineWithValueString('Z', line, splitLine, self.getRounded(z))
def getRounded(self, number):
'Get number rounded to the number of carried decimal places as a string.'
return euclidean.getRoundedToPlacesString(self.decimalPlacesCarried, number)
def parseSplitLine(self, firstWord, splitLine):
'Parse gcode split line and store the parameters.'
if firstWord == '(<decimalPlacesCarried>':
self.decimalPlacesCarried = int(splitLine[1])