70bbc16abe
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.
446 lines
17 KiB
Python
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])
|