2 The GCodeInterpreter module generates layer information from GCode.
3 It does this by parsing the whole GCode file. On large files this can take a while and should be used from a thread.
5 __copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License"
13 import cStringIO as StringIO
15 from Cura.util import profile
17 def gcodePath(newType, pathType, layerThickness, startPoint):
19 Build a gcodePath object. This used to be objects, however, this code is timing sensitive and dictionaries proved to be faster.
21 if layerThickness <= 0.0:
23 if profile.getProfileSetting('spiralize') == 'True':
24 layerThickness = profile.getProfileSettingFloat('layer_height')
25 return {'type': newType,
27 'layerThickness': layerThickness,
28 'points': [startPoint],
33 The heavy lifting GCode parser. This is most likely the hardest working python code in Cura.
34 It parses a GCode file and stores the result in layers where each layer as paths that describe the GCode.
39 self.extrusionAmount = 0
41 self.progressCallback = None
45 if type(data) in types.StringTypes and os.path.isfile(data):
47 self._fileSize = os.stat(data).st_size
48 gcodeFile = open(data, 'r')
51 elif type(data) is list:
54 self._fileSize = len(data)
58 def calculateWeight(self):
59 #Calculates the weight of the filament in kg
60 radius = float(profile.getProfileSetting('filament_diameter')) / 2
61 volumeM3 = (self.extrusionAmount * (math.pi * radius * radius)) / (1000*1000*1000)
62 return volumeM3 * profile.getPreferenceFloat('filament_physical_density')
64 def calculateCost(self):
65 cost_kg = profile.getPreferenceFloat('filament_cost_kg')
66 cost_meter = profile.getPreferenceFloat('filament_cost_meter')
67 if cost_kg > 0.0 and cost_meter > 0.0:
68 return "%.2f / %.2f" % (self.calculateWeight() * cost_kg, self.extrusionAmount / 1000 * cost_meter)
70 return "%.2f" % (self.calculateWeight() * cost_kg)
71 elif cost_meter > 0.0:
72 return "%.2f" % (self.extrusionAmount / 1000 * cost_meter)
75 def _load(self, gcodeFile):
78 posOffset = [0.0, 0.0, 0.0]
81 extrudeAmountMultiply = 1.0
90 currentPath = gcodePath('move', pathType, layerThickness, pos)
91 currentPath['extruder'] = currentExtruder
93 currentLayer.append(currentPath)
94 for line in gcodeFile:
95 if type(line) is tuple:
98 #Parse Cura_SF comments
99 if line.startswith(';TYPE:'):
100 pathType = line[6:].strip()
103 comment = line[line.find(';')+1:].strip()
104 #Slic3r GCode comment parser
105 if comment == 'fill':
107 elif comment == 'perimeter':
108 pathType = 'WALL-INNER'
109 elif comment == 'skirt':
111 #Cura layer comments.
112 if comment.startswith('LAYER:'):
113 currentPath = gcodePath(moveType, pathType, layerThickness, currentPath['points'][-1])
115 currentPath['extruder'] = currentExtruder
116 for path in currentLayer:
117 path['points'] = numpy.array(path['points'], numpy.float32)
118 path['extrusion'] = numpy.array(path['extrusion'], numpy.float32)
119 self.layerList.append(currentLayer)
120 if self.progressCallback is not None:
121 if self.progressCallback(float(gcodeFile.tell()) / float(self._fileSize)):
122 #Abort the loading, we can safely return as the results here will be discarded
124 currentLayer = [currentPath]
125 line = line[0:line.find(';')]
127 G = getCodeInt(line, 'G')
129 if G == 0 or G == 1: #Move
130 x = getCodeFloat(line, 'X')
131 y = getCodeFloat(line, 'Y')
132 z = getCodeFloat(line, 'Z')
133 e = getCodeFloat(line, 'E')
134 #f = getCodeFloat(line, 'F')
139 pos[0] = x * scale + posOffset[0]
141 pos[1] = y * scale + posOffset[1]
143 pos[2] = z * scale + posOffset[2]
153 if absoluteE and posAbs:
162 if moveType == 'move' and oldPos[2] != pos[2]:
163 if oldPos[2] > pos[2] and abs(oldPos[2] - pos[2]) > 5.0 and pos[2] < 1.0:
165 if layerThickness == 0.0:
166 layerThickness = abs(oldPos[2] - pos[2])
167 if currentPath['type'] != moveType or currentPath['pathType'] != pathType:
168 currentPath = gcodePath(moveType, pathType, layerThickness, currentPath['points'][-1])
169 currentPath['extruder'] = currentExtruder
170 currentLayer.append(currentPath)
172 currentPath['points'].append(pos)
173 currentPath['extrusion'].append(e * extrudeAmountMultiply)
175 S = getCodeFloat(line, 'S')
176 P = getCodeFloat(line, 'P')
177 elif G == 10: #Retract
178 currentPath = gcodePath('retract', pathType, layerThickness, currentPath['points'][-1])
179 currentPath['extruder'] = currentExtruder
180 currentLayer.append(currentPath)
181 currentPath['points'].append(currentPath['points'][0])
182 elif G == 11: #Push back after retract
184 elif G == 20: #Units are inches
186 elif G == 21: #Units are mm
189 x = getCodeFloat(line, 'X')
190 y = getCodeFloat(line, 'Y')
191 z = getCodeFloat(line, 'Z')
192 center = [0.0,0.0,0.0]
193 if x is None and y is None and z is None:
203 elif G == 29: #Probe Z
205 elif G == 90: #Absolute position
207 elif G == 91: #Relative position
210 x = getCodeFloat(line, 'X')
211 y = getCodeFloat(line, 'Y')
212 z = getCodeFloat(line, 'Z')
213 e = getCodeFloat(line, 'E')
217 # posOffset[0] = pos[0] - x
219 # posOffset[1] = pos[1] - y
221 # posOffset[2] = pos[2] - z
223 print "Unknown G code:" + str(G)
225 M = getCodeInt(line, 'M')
227 if M == 0: #Message with possible wait (ignored)
229 elif M == 1: #Message with possible wait (ignored)
231 elif M == 25: #Stop SD printing
233 elif M == 80: #Enable power supply
235 elif M == 81: #Suicide/disable power supply
237 elif M == 82: #Absolute E
239 elif M == 83: #Relative E
241 elif M == 84: #Disable step drivers
243 elif M == 92: #Set steps per unit
245 elif M == 101: #Enable extruder
247 elif M == 103: #Disable extruder
249 elif M == 104: #Set temperature, no wait
251 elif M == 105: #Get temperature
253 elif M == 106: #Enable fan
255 elif M == 107: #Disable fan
257 elif M == 108: #Extruder RPM (these should not be in the final GCode, but they are)
259 elif M == 109: #Set temperature, wait
261 elif M == 110: #Reset N counter
263 elif M == 113: #Extruder PWM (these should not be in the final GCode, but they are)
265 elif M == 117: #LCD message
267 elif M == 140: #Set bed temperature
269 elif M == 190: #Set bed temperature & wait
271 elif M == 203: #Set maximum feedrate
273 elif M == 204: #Set default acceleration
275 elif M == 400: #Wait for current moves to finish
277 elif M == 221: #Extrude amount multiplier
278 s = getCodeFloat(line, 'S')
280 extrudeAmountMultiply = s / 100.0
282 print "Unknown M code:" + str(M)
284 T = getCodeInt(line, 'T')
286 if currentExtruder > 0:
287 posOffset[0] -= profile.getMachineSettingFloat('extruder_offset_x%d' % (currentExtruder))
288 posOffset[1] -= profile.getMachineSettingFloat('extruder_offset_y%d' % (currentExtruder))
290 if currentExtruder > 0:
291 posOffset[0] += profile.getMachineSettingFloat('extruder_offset_x%d' % (currentExtruder))
292 posOffset[1] += profile.getMachineSettingFloat('extruder_offset_y%d' % (currentExtruder))
294 for path in currentLayer:
295 path['points'] = numpy.array(path['points'], numpy.float32)
296 path['extrusion'] = numpy.array(path['extrusion'], numpy.float32)
297 self.layerList.append(currentLayer)
298 if self.progressCallback is not None and self._fileSize > 0:
299 self.progressCallback(float(gcodeFile.tell()) / float(self._fileSize))
301 def getCodeInt(line, code):
302 n = line.find(code) + 1
305 m = line.find(' ', n)
309 return int(line[n:m])
313 def getCodeFloat(line, code):
314 n = line.find(code) + 1
317 m = line.find(' ', n)
320 return float(line[n:])
321 return float(line[n:m])
325 if __name__ == '__main__':
327 for filename in sys.argv[1:]:
330 print time.time() - t