2 Gcodec is a collection of utilities to decode and encode gcode.
4 To run gcodec, install python 2.x on your machine, which is avaliable from http://www.python.org/download/
6 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.
8 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.
10 >>> from gcodec import *
11 >>> getFileText('Screw Holder Bottom_export.gcode')
12 '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
19 from __future__ import absolute_import
20 #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.
23 from fabmetheus_utilities.vector3 import Vector3
24 from fabmetheus_utilities import archive
25 from fabmetheus_utilities import euclidean
33 __author__ = 'Enrique Perez (perez_enrique@yahoo.com)'
34 __date__ = '$Date: 2008/21/04 $'
35 __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
38 def addLineAndNewlineIfNecessary(line, output):
39 'Add the line and if the line does not end with a newline add a newline.'
43 if not line.endswith('\n'):
46 def addLinesToCString(cString, lines):
47 'Add lines which have something to cStringIO.'
50 cString.write(line + '\n')
52 def getArcDistance(relativeLocation, splitLine):
54 halfPlaneLineDistance = 0.5 * abs(relativeLocation.dropAxis())
55 radius = getDoubleFromCharacterSplitLine('R', splitLine)
57 iFloat = getDoubleFromCharacterSplitLine('I', splitLine)
58 jFloat = getDoubleFromCharacterSplitLine('J', splitLine)
59 radius = abs(complex(iFloat, jFloat))
62 halfPlaneLineDistanceOverRadius = halfPlaneLineDistance / radius
63 if halfPlaneLineDistance < radius:
64 angle = 2.0 * math.asin(halfPlaneLineDistanceOverRadius)
66 angle = math.pi * halfPlaneLineDistanceOverRadius
67 return abs(complex(angle * radius, relativeLocation.z))
69 def getDoubleAfterFirstLetter(word):
70 'Get the double value of the word after the first letter.'
71 return float(word[1 :])
73 def getDoubleForLetter(letter, splitLine):
74 'Get the double value of the word after the first occurence of the letter in the split line.'
75 return getDoubleAfterFirstLetter(splitLine[getIndexOfStartingWithSecond(letter, splitLine)])
77 def getDoubleFromCharacterSplitLine(character, splitLine):
78 'Get the double value of the string after the first occurence of the character in the split line.'
79 indexOfCharacter = getIndexOfStartingWithSecond(character, splitLine)
80 if indexOfCharacter < 0:
82 floatString = splitLine[indexOfCharacter][1 :]
84 return float(floatString)
88 def getDoubleFromCharacterSplitLineValue(character, splitLine, value):
89 '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.'
90 splitLineFloat = getDoubleFromCharacterSplitLine(character, splitLine)
91 if splitLineFloat == None:
95 def getFeedRateMinute(feedRateMinute, splitLine):
96 'Get the feed rate per minute if the split line has a feed rate.'
97 indexOfF = getIndexOfStartingWithSecond('F', splitLine)
99 return getDoubleAfterFirstLetter( splitLine[indexOfF] )
100 return feedRateMinute
102 def getFirstWord(splitLine):
103 'Get the first word of a split line.'
104 if len(splitLine) > 0:
108 def getFirstWordFromLine(line):
109 'Get the first word of a line.'
110 return getFirstWord(line.split())
112 def getFirstWordIndexReverse(firstWord, lines, startIndex):
113 'Parse gcode in reverse order until the first word if there is one, otherwise return -1.'
114 for lineIndex in xrange(len(lines) - 1, startIndex - 1, -1):
115 if firstWord == getFirstWord(getSplitLineBeforeBracketSemicolon(lines[lineIndex])):
119 def getGcodeFileText(fileName, gcodeText):
120 'Get the gcode text from a file if it the gcode text is empty and if the file is a gcode file.'
123 if fileName.endswith('.gcode'):
124 return archive.getFileText(fileName)
127 def getGcodeWithoutDuplication(duplicateWord, gcodeText):
128 'Get gcode text without duplicate first words.'
129 lines = archive.getTextLines(gcodeText)
130 oldWrittenLine = None
131 output = cStringIO.StringIO()
133 firstWord = getFirstWordFromLine(line)
134 if firstWord == duplicateWord:
135 if line != oldWrittenLine:
136 output.write(line + '\n')
137 oldWrittenLine = line
140 output.write(line + '\n')
141 return output.getvalue()
143 def getIndexOfStartingWithSecond(letter, splitLine):
144 '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'
145 for wordIndex in xrange( 1, len(splitLine) ):
146 word = splitLine[ wordIndex ]
147 firstLetter = word[0]
148 if firstLetter == letter:
152 def getLineWithValueString(character, line, splitLine, valueString):
153 'Get the line with a valueString.'
154 roundedValueString = character + valueString
155 indexOfValue = getIndexOfStartingWithSecond(character, splitLine)
156 if indexOfValue == -1:
157 return line + ' ' + roundedValueString
158 word = splitLine[indexOfValue]
159 return line.replace(word, roundedValueString)
161 def getLocationFromSplitLine(oldLocation, splitLine):
162 'Get the location from the split line.'
163 if oldLocation == None:
164 oldLocation = Vector3()
166 getDoubleFromCharacterSplitLineValue('X', splitLine, oldLocation.x),
167 getDoubleFromCharacterSplitLineValue('Y', splitLine, oldLocation.y),
168 getDoubleFromCharacterSplitLineValue('Z', splitLine, oldLocation.z))
170 def getRotationBySplitLine(splitLine):
171 'Get the complex rotation from the split gcode line.'
172 return complex(splitLine[1].replace('(', '').replace(')', ''))
174 def getSplitLineBeforeBracketSemicolon(line):
175 'Get the split line before a bracket or semicolon.'
177 line = line[: line.find(';')]
178 bracketIndex = line.find('(')
180 return line[: bracketIndex].split()
183 def getStringFromCharacterSplitLine(character, splitLine):
184 'Get the string after the first occurence of the character in the split line.'
185 indexOfCharacter = getIndexOfStartingWithSecond(character, splitLine)
186 if indexOfCharacter < 0:
188 return splitLine[indexOfCharacter][1 :]
190 def getTagBracketedLine(tagName, value):
191 'Get line with a begin tag, value and end tag.'
192 return '(<%s> %s </%s>)' % (tagName, value, tagName)
194 def getTagBracketedProcedure(procedure):
195 'Get line with a begin procedure tag, procedure and end procedure tag.'
196 return getTagBracketedLine('procedureName', procedure)
198 def isProcedureDone(gcodeText, procedure):
199 'Determine if the procedure has been done on the gcode text.'
202 extruderInitializationIndex = gcodeText.find('(</extruderInitialization>)')
203 if extruderInitializationIndex == -1:
204 metadataBeginIndex = gcodeText.find('<metadata>')
205 metadataEndIndex = gcodeText.find('</metadata>')
206 if metadataBeginIndex != -1 and metadataEndIndex != -1:
207 attributeString = "procedureName='%s'" % procedure
208 return gcodeText.find(attributeString, metadataBeginIndex, metadataEndIndex) != -1
210 return gcodeText.find(getTagBracketedProcedure(procedure), 0, extruderInitializationIndex) != -1
212 def isProcedureDoneOrFileIsEmpty(gcodeText, procedure):
213 'Determine if the procedure has been done on the gcode text or the file is empty.'
216 return isProcedureDone(gcodeText, procedure)
218 def isThereAFirstWord(firstWord, lines, startIndex):
219 'Parse gcode until the first word if there is one.'
220 for lineIndex in xrange(startIndex, len(lines)):
221 line = lines[lineIndex]
222 splitLine = getSplitLineBeforeBracketSemicolon(line)
223 if firstWord == getFirstWord(splitLine):
228 class BoundingRectangle:
229 'A class to get the corners of a gcode text.'
230 def getFromGcodeLines(self, lines, radius):
231 'Parse gcode text and get the minimum and maximum corners.'
232 self.cornerMaximum = complex(-987654321.0, -987654321.0)
233 self.cornerMinimum = complex(987654321.0, 987654321.0)
234 self.oldLocation = None
235 self.cornerRadius = complex(radius, radius)
237 self.parseCorner(line)
240 def isPointInside(self, point):
241 'Determine if the point is inside the bounding rectangle.'
242 return point.imag >= self.cornerMinimum.imag and point.imag <= self.cornerMaximum.imag and point.real >= self.cornerMinimum.real and point.real <= self.cornerMaximum.real
244 def parseCorner(self, line):
245 'Parse a gcode line and use the location to update the bounding corners.'
246 splitLine = getSplitLineBeforeBracketSemicolon(line)
247 firstWord = getFirstWord(splitLine)
248 if firstWord == '(<boundaryPoint>':
249 locationComplex = getLocationFromSplitLine(None, splitLine).dropAxis()
250 self.cornerMaximum = euclidean.getMaximum(self.cornerMaximum, locationComplex)
251 self.cornerMinimum = euclidean.getMinimum(self.cornerMinimum, locationComplex)
252 elif firstWord == 'G1':
253 location = getLocationFromSplitLine(self.oldLocation, splitLine)
254 locationComplex = location.dropAxis()
255 self.cornerMaximum = euclidean.getMaximum(self.cornerMaximum, locationComplex + self.cornerRadius)
256 self.cornerMinimum = euclidean.getMinimum(self.cornerMinimum, locationComplex - self.cornerRadius)
257 self.oldLocation = location
260 class DistanceFeedRate:
261 'A class to limit the z feed rate and round values.'
264 self.isAlteration = False
265 self.decimalPlacesCarried = 3
266 self.output = cStringIO.StringIO()
268 def addFlowRateLine(self, flowRate):
269 'Add a flow rate line.'
270 self.output.write('M108 S%s\n' % euclidean.getFourSignificantFigures(flowRate))
272 def addGcodeFromFeedRateThreadZ(self, feedRateMinute, thread, travelFeedRateMinute, z):
273 'Add a thread to the output.'
275 self.addGcodeMovementZWithFeedRate(travelFeedRateMinute, thread[0], z)
277 print('zero length vertex positions array which was skipped over, this should never happen.')
279 print('thread of only one point in addGcodeFromFeedRateThreadZ in gcodec, this should never happen.')
282 self.output.write('M101\n') # Turn extruder on.
283 for point in thread[1 :]:
284 self.addGcodeMovementZWithFeedRate(feedRateMinute, point, z)
285 self.output.write('M103\n') # Turn extruder off.
287 def addGcodeFromLoop(self, loop, z):
288 'Add the gcode loop.'
289 euclidean.addNestedRingBeginning(self, loop, z)
290 self.addPerimeterBlock(loop, z)
291 self.addLine('(</boundaryPerimeter>)')
292 self.addLine('(</nestedRing>)')
294 def addGcodeFromThreadZ(self, thread, z):
295 'Add a thread to the output.'
297 self.addGcodeMovementZ(thread[0], z)
299 print('zero length vertex positions array which was skipped over, this should never happen.')
301 print('thread of only one point in addGcodeFromThreadZ in gcodec, this should never happen.')
304 self.output.write('M101\n') # Turn extruder on.
305 for point in thread[1 :]:
306 self.addGcodeMovementZ(point, z)
307 self.output.write('M103\n') # Turn extruder off.
309 def addGcodeMovementZ(self, point, z):
310 'Add a movement to the output.'
311 self.output.write(self.getLinearGcodeMovement(point, z) + '\n')
313 def addGcodeMovementZWithFeedRate(self, feedRateMinute, point, z):
314 'Add a movement to the output.'
315 self.output.write(self.getLinearGcodeMovementWithFeedRate(feedRateMinute, point, z) + '\n')
317 def addGcodeMovementZWithFeedRateVector3(self, feedRateMinute, vector3):
318 'Add a movement to the output by Vector3.'
319 xRounded = self.getRounded(vector3.x)
320 yRounded = self.getRounded(vector3.y)
321 self.output.write('G1 X%s Y%s Z%s F%s\n' % (xRounded, yRounded, self.getRounded(vector3.z), self.getRounded(feedRateMinute)))
323 def addLine(self, line):
324 'Add a line of text and a newline to the output.'
326 self.output.write(line + '\n')
328 def addLineCheckAlteration(self, line):
329 'Add a line of text and a newline to the output and check to see if it is an alteration line.'
330 firstWord = getFirstWord(getSplitLineBeforeBracketSemicolon(line))
331 if firstWord == '(<alteration>)':
332 self.isAlteration = True
333 elif firstWord == '(</alteration>)':
334 self.isAlteration = False
336 self.output.write(line + '\n')
338 def addLines(self, lines):
339 'Add lines of text to the output.'
340 addLinesToCString(self.output, lines)
342 def addLinesSetAbsoluteDistanceMode(self, lines):
343 'Add lines of text to the output and ensure the absolute mode is set.'
346 if len(lines[0]) < 1:
348 absoluteDistanceMode = True
349 self.addLine('(<alteration>)')
351 splitLine = getSplitLineBeforeBracketSemicolon(line)
352 firstWord = getFirstWord(splitLine)
353 if firstWord == 'G90':
354 absoluteDistanceMode = True
355 elif firstWord == 'G91':
356 absoluteDistanceMode = False
357 self.addLine('(<alterationDeleteThisPrefix/>)' + line)
358 if not absoluteDistanceMode:
360 self.addLine('(</alteration>)')
362 def addParameter(self, firstWord, parameter):
364 self.addLine(firstWord + ' S' + euclidean.getRoundedToThreePlaces(parameter))
366 def addPerimeterBlock(self, loop, z):
367 'Add the edge gcode block for the loop.'
370 if euclidean.isWiddershins(loop): # Indicate that an edge is beginning.
371 self.addLine('(<edge> outer )')
373 self.addLine('(<edge> inner )')
374 self.addGcodeFromThreadZ(loop + [loop[0]], z)
375 self.addLine('(</edge>)') # Indicate that an edge is beginning.
377 def addTagBracketedLine(self, tagName, value):
378 'Add a begin tag, value and end tag.'
379 self.addLine(getTagBracketedLine(tagName, value))
381 def addTagRoundedLine(self, tagName, value):
382 'Add a begin tag, rounded value and end tag.'
383 self.addLine('(<%s> %s </%s>)' % (tagName, self.getRounded(value), tagName))
385 def addTagBracketedProcedure(self, procedure):
386 'Add a begin procedure tag, procedure and end procedure tag.'
387 self.addLine(getTagBracketedProcedure(procedure))
389 def getBoundaryLine(self, location):
390 'Get boundary gcode line.'
391 return '(<boundaryPoint> X%s Y%s Z%s </boundaryPoint>)' % (self.getRounded(location.x), self.getRounded(location.y), self.getRounded(location.z))
393 def getFirstWordMovement(self, firstWord, location):
394 'Get the start of the arc line.'
395 return '%s X%s Y%s Z%s' % (firstWord, self.getRounded(location.x), self.getRounded(location.y), self.getRounded(location.z))
397 def getInfillBoundaryLine(self, location):
398 'Get infill boundary gcode line.'
399 return '(<infillPoint> X%s Y%s Z%s </infillPoint>)' % (self.getRounded(location.x), self.getRounded(location.y), self.getRounded(location.z))
401 def getIsAlteration(self, line):
402 'Determine if it is an alteration.'
403 if self.isAlteration:
404 self.addLineCheckAlteration(line)
408 def getLinearGcodeMovement(self, point, z):
409 'Get a linear gcode movement.'
410 return 'G1 X%s Y%s Z%s' % (self.getRounded(point.real), self.getRounded(point.imag), self.getRounded(z))
412 def getLinearGcodeMovementWithFeedRate(self, feedRateMinute, point, z):
413 'Get a z limited gcode movement.'
414 linearGcodeMovement = self.getLinearGcodeMovement(point, z)
415 if feedRateMinute == None:
416 return linearGcodeMovement
417 return linearGcodeMovement + ' F' + self.getRounded(feedRateMinute)
419 def getLineWithFeedRate(self, feedRateMinute, line, splitLine):
420 'Get the line with a feed rate.'
421 return getLineWithValueString('F', line, splitLine, self.getRounded(feedRateMinute))
423 def getLineWithX(self, line, splitLine, x):
424 'Get the line with an x.'
425 return getLineWithValueString('X', line, splitLine, self.getRounded(x))
427 def getLineWithY(self, line, splitLine, y):
428 'Get the line with a y.'
429 return getLineWithValueString('Y', line, splitLine, self.getRounded(y))
431 def getLineWithZ(self, line, splitLine, z):
432 'Get the line with a z.'
433 return getLineWithValueString('Z', line, splitLine, self.getRounded(z))
435 def getRounded(self, number):
436 'Get number rounded to the number of carried decimal places as a string.'
437 return euclidean.getRoundedToPlacesString(self.decimalPlacesCarried, number)
439 def parseSplitLine(self, firstWord, splitLine):
440 'Parse gcode split line and store the parameters.'
441 if firstWord == '(<decimalPlacesCarried>':
442 self.decimalPlacesCarried = int(splitLine[1])