chiark / gitweb /
Add uppercase STL and HEX to file dialog filters for linux/MacOS
[cura.git] / Cura / cura_sf / fabmetheus_utilities / gcodec.py
1 """
2 Gcodec is a collection of utilities to decode and encode gcode.
3
4 To run gcodec, install python 2.x on your machine, which is avaliable from http://www.python.org/download/
5
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.
7
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.
9
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
13 ..
14 many lines of text
15 ..
16
17 """
18
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.
21 import __init__
22
23 from fabmetheus_utilities.vector3 import Vector3
24 from fabmetheus_utilities import archive
25 from fabmetheus_utilities import euclidean
26 import cStringIO
27 import math
28 import os
29 import sys
30 import traceback
31
32
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'
36
37
38 def addLineAndNewlineIfNecessary(line, output):
39         'Add the line and if the line does not end with a newline add a newline.'
40         output.write(line)
41         if len(line) < 1:
42                 return
43         if not line.endswith('\n'):
44                 output.write('\n')
45
46 def addLinesToCString(cString, lines):
47         'Add lines which have something to cStringIO.'
48         for line in lines:
49                 if line != '':
50                         cString.write(line + '\n')
51
52 def getArcDistance(relativeLocation, splitLine):
53         'Get arc distance.'
54         halfPlaneLineDistance = 0.5 * abs(relativeLocation.dropAxis())
55         radius = getDoubleFromCharacterSplitLine('R', splitLine)
56         if radius == None:
57                 iFloat = getDoubleFromCharacterSplitLine('I', splitLine)
58                 jFloat = getDoubleFromCharacterSplitLine('J', splitLine)
59                 radius = abs(complex(iFloat, jFloat))
60         angle = 0.0
61         if radius > 0.0:
62                 halfPlaneLineDistanceOverRadius = halfPlaneLineDistance / radius
63                 if halfPlaneLineDistance < radius:
64                         angle = 2.0 * math.asin(halfPlaneLineDistanceOverRadius)
65                 else:
66                         angle = math.pi * halfPlaneLineDistanceOverRadius
67         return abs(complex(angle * radius, relativeLocation.z))
68
69 def getDoubleAfterFirstLetter(word):
70         'Get the double value of the word after the first letter.'
71         return float(word[1 :])
72
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)])
76
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:
81                 return None
82         floatString = splitLine[indexOfCharacter][1 :]
83         try:
84                 return float(floatString)
85         except ValueError:
86                 return None
87
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:
92                 return value
93         return splitLineFloat
94
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)
98         if indexOfF > 0:
99                 return getDoubleAfterFirstLetter( splitLine[indexOfF] )
100         return feedRateMinute
101
102 def getFirstWord(splitLine):
103         'Get the first word of a split line.'
104         if len(splitLine) > 0:
105                 return splitLine[0]
106         return ''
107
108 def getFirstWordFromLine(line):
109         'Get the first word of a line.'
110         return getFirstWord(line.split())
111
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])):
116                         return lineIndex
117         return -1
118
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.'
121         if gcodeText != '':
122                 return gcodeText
123         if fileName.endswith('.gcode'):
124                 return archive.getFileText(fileName)
125         return ''
126
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()
132         for line in lines:
133                 firstWord = getFirstWordFromLine(line)
134                 if firstWord == duplicateWord:
135                         if line != oldWrittenLine:
136                                 output.write(line + '\n')
137                                 oldWrittenLine = line
138                 else:
139                         if len(line) > 0:
140                                 output.write(line + '\n')
141         return output.getvalue()
142
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:
149                         return wordIndex
150         return - 1
151
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)
160
161 def getLocationFromSplitLine(oldLocation, splitLine):
162         'Get the location from the split line.'
163         if oldLocation == None:
164                 oldLocation = Vector3()
165         return Vector3(
166                 getDoubleFromCharacterSplitLineValue('X', splitLine, oldLocation.x),
167                 getDoubleFromCharacterSplitLineValue('Y', splitLine, oldLocation.y),
168                 getDoubleFromCharacterSplitLineValue('Z', splitLine, oldLocation.z))
169
170 def getRotationBySplitLine(splitLine):
171         'Get the complex rotation from the split gcode line.'
172         return complex(splitLine[1].replace('(', '').replace(')', ''))
173
174 def getSplitLineBeforeBracketSemicolon(line):
175         'Get the split line before a bracket or semicolon.'
176         if ';' in line:
177                 line = line[: line.find(';')]
178         bracketIndex = line.find('(')
179         if bracketIndex > 0:
180                 return line[: bracketIndex].split()
181         return line.split()
182
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:
187                 return None
188         return splitLine[indexOfCharacter][1 :]
189
190 def getTagBracketedLine(tagName, value):
191         'Get line with a begin tag, value and end tag.'
192         return '(<%s> %s </%s>)' % (tagName, value, tagName)
193
194 def getTagBracketedProcedure(procedure):
195         'Get line with a begin procedure tag, procedure and end procedure tag.'
196         return getTagBracketedLine('procedureName', procedure)
197
198 def isProcedureDone(gcodeText, procedure):
199         'Determine if the procedure has been done on the gcode text.'
200         if gcodeText == '':
201                 return False
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
209                 return False
210         return gcodeText.find(getTagBracketedProcedure(procedure), 0, extruderInitializationIndex) != -1
211
212 def isProcedureDoneOrFileIsEmpty(gcodeText, procedure):
213         'Determine if the procedure has been done on the gcode text or the file is empty.'
214         if gcodeText == '':
215                 return True
216         return isProcedureDone(gcodeText, procedure)
217
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):
224                         return True
225         return False
226
227
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)
236                 for line in lines:
237                         self.parseCorner(line)
238                 return self
239
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
243
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
258
259
260 class DistanceFeedRate:
261         'A class to limit the z feed rate and round values.'
262         def __init__(self):
263                 'Initialize.'
264                 self.isAlteration = False
265                 self.decimalPlacesCarried = 3
266                 self.output = cStringIO.StringIO()
267
268         def addFlowRateLine(self, flowRate):
269                 'Add a flow rate line.'
270                 self.output.write('M108 S%s\n' % euclidean.getFourSignificantFigures(flowRate))
271
272         def addGcodeFromFeedRateThreadZ(self, feedRateMinute, thread, travelFeedRateMinute, z):
273                 'Add a thread to the output.'
274                 if len(thread) > 0:
275                         self.addGcodeMovementZWithFeedRate(travelFeedRateMinute, thread[0], z)
276                 else:
277                         print('zero length vertex positions array which was skipped over, this should never happen.')
278                 if len(thread) < 2:
279                         print('thread of only one point in addGcodeFromFeedRateThreadZ in gcodec, this should never happen.')
280                         print(thread)
281                         return
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.
286
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>)')
293
294         def addGcodeFromThreadZ(self, thread, z):
295                 'Add a thread to the output.'
296                 if len(thread) > 0:
297                         self.addGcodeMovementZ(thread[0], z)
298                 else:
299                         print('zero length vertex positions array which was skipped over, this should never happen.')
300                 if len(thread) < 2:
301                         print('thread of only one point in addGcodeFromThreadZ in gcodec, this should never happen.')
302                         print(thread)
303                         return
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.
308
309         def addGcodeMovementZ(self, point, z):
310                 'Add a movement to the output.'
311                 self.output.write(self.getLinearGcodeMovement(point, z) + '\n')
312
313         def addGcodeMovementZWithFeedRate(self, feedRateMinute, point, z):
314                 'Add a movement to the output.'
315                 self.output.write(self.getLinearGcodeMovementWithFeedRate(feedRateMinute, point, z) + '\n')
316
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)))
322
323         def addLine(self, line):
324                 'Add a line of text and a newline to the output.'
325                 if len(line) > 0:
326                         self.output.write(line + '\n')
327
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
335                 if len(line) > 0:
336                         self.output.write(line + '\n')
337
338         def addLines(self, lines):
339                 'Add lines of text to the output.'
340                 addLinesToCString(self.output, lines)
341
342         def addLinesSetAbsoluteDistanceMode(self, lines):
343                 'Add lines of text to the output and ensure the absolute mode is set.'
344                 if len(lines) < 1:
345                         return
346                 if len(lines[0]) < 1:
347                         return
348                 absoluteDistanceMode = True
349                 self.addLine('(<alteration>)')
350                 for line in lines:
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:
359                         self.addLine('G90')
360                 self.addLine('(</alteration>)')
361
362         def addParameter(self, firstWord, parameter):
363                 'Add the parameter.'
364                 self.addLine(firstWord + ' S' + euclidean.getRoundedToThreePlaces(parameter))
365
366         def addPerimeterBlock(self, loop, z):
367                 'Add the edge gcode block for the loop.'
368                 if len(loop) < 2:
369                         return
370                 if euclidean.isWiddershins(loop): # Indicate that an edge is beginning.
371                         self.addLine('(<edge> outer )')
372                 else:
373                         self.addLine('(<edge> inner )')
374                 self.addGcodeFromThreadZ(loop + [loop[0]], z)
375                 self.addLine('(</edge>)') # Indicate that an edge is beginning.
376
377         def addTagBracketedLine(self, tagName, value):
378                 'Add a begin tag, value and end tag.'
379                 self.addLine(getTagBracketedLine(tagName, value))
380
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))
384
385         def addTagBracketedProcedure(self, procedure):
386                 'Add a begin procedure tag, procedure and end procedure tag.'
387                 self.addLine(getTagBracketedProcedure(procedure))
388
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))
392
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))
396
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))
400
401         def getIsAlteration(self, line):
402                 'Determine if it is an alteration.'
403                 if self.isAlteration:
404                         self.addLineCheckAlteration(line)
405                         return True
406                 return False
407
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))
411
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)
418
419         def getLineWithFeedRate(self, feedRateMinute, line, splitLine):
420                 'Get the line with a feed rate.'
421                 return getLineWithValueString('F', line, splitLine, self.getRounded(feedRateMinute))
422
423         def getLineWithX(self, line, splitLine, x):
424                 'Get the line with an x.'
425                 return getLineWithValueString('X', line, splitLine, self.getRounded(x))
426
427         def getLineWithY(self, line, splitLine, y):
428                 'Get the line with a y.'
429                 return getLineWithValueString('Y', line, splitLine, self.getRounded(y))
430
431         def getLineWithZ(self, line, splitLine, z):
432                 'Get the line with a z.'
433                 return getLineWithValueString('Z', line, splitLine, self.getRounded(z))
434
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)
438
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])