chiark / gitweb /
plugins: Support user configuration of default values
[cura.git] / Cura / util / gcodeInterpreter.py
1 """
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.
4 """
5 __copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License"
6
7 import sys
8 import math
9 import os
10 import time
11 import numpy
12 import types
13 import cStringIO as StringIO
14
15 from Cura.util import profile
16
17 def gcodePath(newType, pathType, layerThickness, startPoint):
18         """
19         Build a gcodePath object. This used to be objects, however, this code is timing sensitive and dictionaries proved to be faster.
20         """
21         if layerThickness <= 0.0:
22                 layerThickness = 0.01
23         if profile.getProfileSetting('spiralize') == 'True':
24                 layerThickness = profile.getProfileSettingFloat('layer_height')
25         return {'type': newType,
26                         'pathType': pathType,
27                         'layerThickness': layerThickness,
28                         'points': [startPoint],
29                         'extrusion': [0.0]}
30
31 class gcode(object):
32         """
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.
35         """
36         def __init__(self):
37                 self.regMatch = {}
38                 self.layerList = None
39                 self.extrusionAmount = 0
40                 self.filename = None
41                 self.progressCallback = None
42         
43         def load(self, data):
44                 self.filename = None
45                 if type(data) in types.StringTypes and os.path.isfile(data):
46                         self.filename = data
47                         self._fileSize = os.stat(data).st_size
48                         gcodeFile = open(data, 'r')
49                         self._load(gcodeFile)
50                         gcodeFile.close()
51                 elif type(data) is list:
52                         self._load(data)
53                 else:
54                         self._fileSize = len(data)
55                         data.seekStart()
56                         self._load(data)
57
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')
63         
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)
69                 elif cost_kg > 0.0:
70                         return "%.2f" % (self.calculateWeight() * cost_kg)
71                 elif cost_meter > 0.0:
72                         return "%.2f" % (self.extrusionAmount / 1000 * cost_meter)
73                 return None
74         
75         def _load(self, gcodeFile):
76                 self.layerList = []
77                 pos = [0.0,0.0,0.0]
78                 posOffset = [0.0, 0.0, 0.0]
79                 currentE = 0.0
80                 currentExtruder = 0
81                 extrudeAmountMultiply = 1.0
82                 absoluteE = True
83                 scale = 1.0
84                 posAbs = True
85                 feedRate = 3600.0
86                 moveType = 'move'
87                 layerThickness = 0.1
88                 pathType = 'CUSTOM'
89                 currentLayer = []
90                 currentPath = gcodePath('move', pathType, layerThickness, pos)
91                 currentPath['extruder'] = currentExtruder
92
93                 currentLayer.append(currentPath)
94                 for line in gcodeFile:
95                         if type(line) is tuple:
96                                 line = line[0]
97
98                         #Parse Cura_SF comments
99                         if line.startswith(';TYPE:'):
100                                 pathType = line[6:].strip()
101
102                         if ';' in line:
103                                 comment = line[line.find(';')+1:].strip()
104                                 #Slic3r GCode comment parser
105                                 if comment == 'fill':
106                                         pathType = 'FILL'
107                                 elif comment == 'perimeter':
108                                         pathType = 'WALL-INNER'
109                                 elif comment == 'skirt':
110                                         pathType = 'SKIRT'
111                                 #Cura layer comments.
112                                 if comment.startswith('LAYER:'):
113                                         currentPath = gcodePath(moveType, pathType, layerThickness, currentPath['points'][-1])
114                                         layerThickness = 0.0
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
123                                                         return
124                                         currentLayer = [currentPath]
125                                 line = line[0:line.find(';')]
126
127                         G = getCodeInt(line, 'G')
128                         if G is not None:
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')
135                                         oldPos = pos
136                                         pos = pos[:]
137                                         if posAbs:
138                                                 if x is not None:
139                                                         pos[0] = x * scale + posOffset[0]
140                                                 if y is not None:
141                                                         pos[1] = y * scale + posOffset[1]
142                                                 if z is not None:
143                                                         pos[2] = z * scale + posOffset[2]
144                                         else:
145                                                 if x is not None:
146                                                         pos[0] += x * scale
147                                                 if y is not None:
148                                                         pos[1] += y * scale
149                                                 if z is not None:
150                                                         pos[2] += z * scale
151                                         moveType = 'move'
152                                         if e is not None:
153                                                 if absoluteE and posAbs:
154                                                         e -= currentE
155                                                 if e > 0.0:
156                                                         moveType = 'extrude'
157                                                 if e < 0.0:
158                                                         moveType = 'retract'
159                                                 currentE += e
160                                         else:
161                                                 e = 0.0
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:
164                                                         oldPos[2] = 0.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)
171
172                                         currentPath['points'].append(pos)
173                                         currentPath['extrusion'].append(e * extrudeAmountMultiply)
174                                 elif G == 4:    #Delay
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
183                                         pass
184                                 elif G == 20:   #Units are inches
185                                         scale = 25.4
186                                 elif G == 21:   #Units are mm
187                                         scale = 1.0
188                                 elif G == 28:   #Home
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:
194                                                 pos = center
195                                         else:
196                                                 pos = pos[:]
197                                                 if x is not None:
198                                                         pos[0] = center[0]
199                                                 if y is not None:
200                                                         pos[1] = center[1]
201                                                 if z is not None:
202                                                         pos[2] = center[2]
203                                 elif G == 29:   #Probe Z
204                                         pos[2] = 0.0
205                                 elif G == 90:   #Absolute position
206                                         posAbs = True
207                                 elif G == 91:   #Relative position
208                                         posAbs = False
209                                 elif G == 92:
210                                         x = getCodeFloat(line, 'X')
211                                         y = getCodeFloat(line, 'Y')
212                                         z = getCodeFloat(line, 'Z')
213                                         e = getCodeFloat(line, 'E')
214                                         if e is not None:
215                                                 currentE = e
216                                         #if x is not None:
217                                         #       posOffset[0] = pos[0] - x
218                                         #if y is not None:
219                                         #       posOffset[1] = pos[1] - y
220                                         #if z is not None:
221                                         #       posOffset[2] = pos[2] - z
222                                 else:
223                                         print "Unknown G code:" + str(G)
224                         else:
225                                 M = getCodeInt(line, 'M')
226                                 if M is not None:
227                                         if M == 0:      #Message with possible wait (ignored)
228                                                 pass
229                                         elif M == 1:    #Message with possible wait (ignored)
230                                                 pass
231                                         elif M == 25:   #Stop SD printing
232                                                 pass
233                                         elif M == 80:   #Enable power supply
234                                                 pass
235                                         elif M == 81:   #Suicide/disable power supply
236                                                 pass
237                                         elif M == 82:   #Absolute E
238                                                 absoluteE = True
239                                         elif M == 83:   #Relative E
240                                                 absoluteE = False
241                                         elif M == 84:   #Disable step drivers
242                                                 pass
243                                         elif M == 92:   #Set steps per unit
244                                                 pass
245                                         elif M == 101:  #Enable extruder
246                                                 pass
247                                         elif M == 103:  #Disable extruder
248                                                 pass
249                                         elif M == 104:  #Set temperature, no wait
250                                                 pass
251                                         elif M == 105:  #Get temperature
252                                                 pass
253                                         elif M == 106:  #Enable fan
254                                                 pass
255                                         elif M == 107:  #Disable fan
256                                                 pass
257                                         elif M == 108:  #Extruder RPM (these should not be in the final GCode, but they are)
258                                                 pass
259                                         elif M == 109:  #Set temperature, wait
260                                                 pass
261                                         elif M == 110:  #Reset N counter
262                                                 pass
263                                         elif M == 113:  #Extruder PWM (these should not be in the final GCode, but they are)
264                                                 pass
265                                         elif M == 117:  #LCD message
266                                                 pass
267                                         elif M == 140:  #Set bed temperature
268                                                 pass
269                                         elif M == 190:  #Set bed temperature & wait
270                                                 pass
271                                         elif M == 203:  #Set maximum feedrate
272                                                 pass
273                                         elif M == 204:  #Set default acceleration
274                                                 pass
275                                         elif M == 400:  #Wait for current moves to finish
276                                                 pass
277                                         elif M == 221:  #Extrude amount multiplier
278                                                 s = getCodeFloat(line, 'S')
279                                                 if s is not None:
280                                                         extrudeAmountMultiply = s / 100.0
281                                         else:
282                                                 print "Unknown M code:" + str(M)
283                                 else:
284                                         T = getCodeInt(line, 'T')
285                                         if T is not None:
286                                                 if currentExtruder > 0:
287                                                         posOffset[0] -= profile.getMachineSettingFloat('extruder_offset_x%d' % (currentExtruder))
288                                                         posOffset[1] -= profile.getMachineSettingFloat('extruder_offset_y%d' % (currentExtruder))
289                                                 currentExtruder = T
290                                                 if currentExtruder > 0:
291                                                         posOffset[0] += profile.getMachineSettingFloat('extruder_offset_x%d' % (currentExtruder))
292                                                         posOffset[1] += profile.getMachineSettingFloat('extruder_offset_y%d' % (currentExtruder))
293
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))
300
301 def getCodeInt(line, code):
302         n = line.find(code) + 1
303         if n < 1:
304                 return None
305         m = line.find(' ', n)
306         try:
307                 if m < 0:
308                         return int(line[n:])
309                 return int(line[n:m])
310         except:
311                 return None
312
313 def getCodeFloat(line, code):
314         n = line.find(code) + 1
315         if n < 1:
316                 return None
317         m = line.find(' ', n)
318         try:
319                 if m < 0:
320                         return float(line[n:])
321                 return float(line[n:m])
322         except:
323                 return None
324
325 if __name__ == '__main__':
326         t = time.time()
327         for filename in sys.argv[1:]:
328                 g = gcode()
329                 g.load(filename)
330         print time.time() - t
331