chiark / gitweb /
Show SD card copy progress. Slightly reduce the memory usage of the GCode loading.
[cura.git] / Cura / util / gcodeInterpreter.py
1 from __future__ import absolute_import
2
3 import sys
4 import math
5 import os
6 import time
7 import numpy
8
9 from Cura.util import profile
10
11 #class gcodePath(object):
12 #       def __init__(self, newType, pathType, layerThickness, startPoint):
13 #               self.type = newType
14 #               self.pathType = pathType
15 #               self.layerThickness = layerThickness
16 #               self.points = [startPoint]
17 #               self.extrusion = [0.0]
18 def gcodePath(newType, pathType, layerThickness, startPoint):
19         return {'type': newType,
20                         'pathType': pathType,
21                         'layerThickness': layerThickness,
22                         'points': [startPoint],
23                         'extrusion': [0.0]}
24
25 class gcode(object):
26         def __init__(self):
27                 self.regMatch = {}
28                 self.layerList = []
29                 self.extrusionAmount = 0
30                 self.totalMoveTimeMinute = 0
31                 self.filename = None
32                 self.progressCallback = None
33         
34         def load(self, filename):
35                 if os.path.isfile(filename):
36                         self.filename = filename
37                         self._fileSize = os.stat(filename).st_size
38                         gcodeFile = open(filename, 'r')
39                         self._load(gcodeFile)
40                         gcodeFile.close()
41         
42         def loadList(self, l):
43                 self.filename = None
44                 self._load(l)
45         
46         def calculateWeight(self):
47                 #Calculates the weight of the filament in kg
48                 radius = float(profile.getProfileSetting('filament_diameter')) / 2
49                 volumeM3 = (self.extrusionAmount * (math.pi * radius * radius)) / (1000*1000*1000)
50                 return volumeM3 * profile.getPreferenceFloat('filament_physical_density')
51         
52         def calculateCost(self):
53                 cost_kg = profile.getPreferenceFloat('filament_cost_kg')
54                 cost_meter = profile.getPreferenceFloat('filament_cost_meter')
55                 if cost_kg > 0.0 and cost_meter > 0.0:
56                         return "%.2f / %.2f" % (self.calculateWeight() * cost_kg, self.extrusionAmount / 1000 * cost_meter)
57                 elif cost_kg > 0.0:
58                         return "%.2f" % (self.calculateWeight() * cost_kg)
59                 elif cost_meter > 0.0:
60                         return "%.2f" % (self.extrusionAmount / 1000 * cost_meter)
61                 return None
62         
63         def _load(self, gcodeFile):
64                 pos = [0.0, 0.0, 0.0]
65                 posOffset = [0.0, 0.0, 0.0]
66                 currentE = 0.0
67                 totalExtrusion = 0.0
68                 maxExtrusion = 0.0
69                 currentExtruder = 0
70                 extrudeAmountMultiply = 1.0
71                 totalMoveTimeMinute = 0.0
72                 absoluteE = True
73                 scale = 1.0
74                 posAbs = True
75                 feedRate = 3600.0
76                 layerThickness = 0.1
77                 pathType = 'CUSTOM';
78                 currentLayer = []
79                 currentPath = gcodePath('move', pathType, layerThickness, pos[:])
80                 currentPath['extruder'] = currentExtruder
81
82                 currentLayer.append(currentPath)
83                 for line in gcodeFile:
84                         if type(line) is tuple:
85                                 line = line[0]
86
87                         #Parse Cura_SF comments
88                         if line.startswith(';TYPE:'):
89                                 pathType = line[6:].strip()
90
91                         if ';' in line:
92                                 #Slic3r GCode comment parser
93                                 comment = line[line.find(';')+1:].strip()
94                                 if comment == 'fill':
95                                         pathType = 'FILL'
96                                 elif comment == 'perimeter':
97                                         pathType = 'WALL-INNER'
98                                 elif comment == 'skirt':
99                                         pathType = 'SKIRT'
100                                 if comment.startswith('LAYER:'):
101                                         currentPath = gcodePath(moveType, pathType, layerThickness, currentPath['points'][-1])
102                                         currentPath['extruder'] = currentExtruder
103                                         for path in currentLayer:
104                                                 path['points'] = numpy.array(path['points'], numpy.float32)
105                                                 path['extrusion'] = numpy.array(path['extrusion'], numpy.float32)
106                                         self.layerList.append(currentLayer)
107                                         if self.progressCallback is not None:
108                                                 if self.progressCallback(float(gcodeFile.tell()) / float(self._fileSize)):
109                                                         #Abort the loading, we can safely return as the results here will be discarded
110                                                         gcodeFile.close()
111                                                         return
112                                         currentLayer = [currentPath]
113                                 line = line[0:line.find(';')]
114                         T = getCodeInt(line, 'T')
115                         if T is not None:
116                                 if currentExtruder > 0:
117                                         posOffset[0] -= profile.getPreferenceFloat('extruder_offset_x%d' % (currentExtruder))
118                                         posOffset[1] -= profile.getPreferenceFloat('extruder_offset_y%d' % (currentExtruder))
119                                 currentExtruder = T
120                                 if currentExtruder > 0:
121                                         posOffset[0] += profile.getPreferenceFloat('extruder_offset_x%d' % (currentExtruder))
122                                         posOffset[1] += profile.getPreferenceFloat('extruder_offset_y%d' % (currentExtruder))
123                         
124                         G = getCodeInt(line, 'G')
125                         if G is not None:
126                                 if G == 0 or G == 1:    #Move
127                                         x = getCodeFloat(line, 'X')
128                                         y = getCodeFloat(line, 'Y')
129                                         z = getCodeFloat(line, 'Z')
130                                         e = getCodeFloat(line, 'E')
131                                         #f = getCodeFloat(line, 'F')
132                                         oldPos = pos[:]
133                                         if posAbs:
134                                                 if x is not None:
135                                                         pos[0] = x * scale + posOffset[0]
136                                                 if y is not None:
137                                                         pos[1] = y * scale + posOffset[1]
138                                                 if z is not None:
139                                                         pos[2] = z * scale + posOffset[2]
140                                         else:
141                                                 if x is not None:
142                                                         pos[0] += x * scale
143                                                 if y is not None:
144                                                         pos[1] += y * scale
145                                                 if z is not None:
146                                                         pos[2] += z * scale
147                                         #if f is not None:
148                                         #       feedRate = f
149                                         #if x is not None or y is not None or z is not None:
150                                         #       diffX = oldPos[0] - pos[0]
151                                         #       diffY = oldPos[1] - pos[1]
152                                         #       totalMoveTimeMinute += math.sqrt(diffX * diffX + diffY * diffY) / feedRate
153                                         moveType = 'move'
154                                         if e is not None:
155                                                 if absoluteE:
156                                                         e -= currentE
157                                                 if e > 0.0:
158                                                         moveType = 'extrude'
159                                                 if e < 0.0:
160                                                         moveType = 'retract'
161                                                 totalExtrusion += e
162                                                 currentE += e
163                                                 if totalExtrusion > maxExtrusion:
164                                                         maxExtrusion = totalExtrusion
165                                         else:
166                                                 e = 0.0
167                                         if moveType == 'move' and oldPos[2] != pos[2]:
168                                                 if oldPos[2] > pos[2] and abs(oldPos[2] - pos[2]) > 5.0 and pos[2] < 1.0:
169                                                         oldPos[2] = 0.0
170                                                 layerThickness = abs(oldPos[2] - pos[2])
171                                         if currentPath['type'] != moveType or currentPath['pathType'] != pathType:
172                                                 currentPath = gcodePath(moveType, pathType, layerThickness, currentPath['points'][-1])
173                                                 currentPath['extruder'] = currentExtruder
174                                                 currentLayer.append(currentPath)
175
176                                         currentPath['points'].append(pos[:])
177                                         currentPath['extrusion'].append(e * extrudeAmountMultiply)
178                                 elif G == 4:    #Delay
179                                         S = getCodeFloat(line, 'S')
180                                         if S is not None:
181                                                 totalMoveTimeMinute += S / 60.0
182                                         P = getCodeFloat(line, 'P')
183                                         if P is not None:
184                                                 totalMoveTimeMinute += P / 60.0 / 1000.0
185                                 elif G == 20:   #Units are inches
186                                         scale = 25.4
187                                 elif G == 21:   #Units are mm
188                                         scale = 1.0
189                                 elif G == 28:   #Home
190                                         x = getCodeFloat(line, 'X')
191                                         y = getCodeFloat(line, 'Y')
192                                         z = getCodeFloat(line, 'Z')
193                                         if x is None and y is None and z is None:
194                                                 pos = [0.0,0.0,0.0]
195                                         else:
196                                                 if x is not None:
197                                                         pos[0] = 0.0
198                                                 if y is not None:
199                                                         pos[0] = 0.0
200                                                 if z is not None:
201                                                         pos[0] = 0.0
202                                 elif G == 90:   #Absolute position
203                                         posAbs = True
204                                 elif G == 91:   #Relative position
205                                         posAbs = False
206                                 elif G == 92:
207                                         x = getCodeFloat(line, 'X')
208                                         y = getCodeFloat(line, 'Y')
209                                         z = getCodeFloat(line, 'Z')
210                                         e = getCodeFloat(line, 'E')
211                                         if e is not None:
212                                                 currentE = e
213                                         if x is not None:
214                                                 posOffset[0] = pos[0] - x
215                                         if y is not None:
216                                                 posOffset[1] = pos[1] - y
217                                         if z is not None:
218                                                 posOffset[2] = pos[2] - z
219                                 else:
220                                         print "Unknown G code:" + str(G)
221                         else:
222                                 M = getCodeInt(line, 'M')
223                                 if M is not None:
224                                         if M == 0:      #Message with possible wait (ignored)
225                                                 pass
226                                         elif M == 1:    #Message with possible wait (ignored)
227                                                 pass
228                                         elif M == 80:   #Enable power supply
229                                                 pass
230                                         elif M == 81:   #Suicide/disable power supply
231                                                 pass
232                                         elif M == 82:   #Absolute E
233                                                 absoluteE = True
234                                         elif M == 83:   #Relative E
235                                                 absoluteE = False
236                                         elif M == 84:   #Disable step drivers
237                                                 pass
238                                         elif M == 92:   #Set steps per unit
239                                                 pass
240                                         elif M == 101:  #Enable extruder
241                                                 pass
242                                         elif M == 103:  #Disable extruder
243                                                 pass
244                                         elif M == 104:  #Set temperature, no wait
245                                                 pass
246                                         elif M == 105:  #Get temperature
247                                                 pass
248                                         elif M == 106:  #Enable fan
249                                                 pass
250                                         elif M == 107:  #Disable fan
251                                                 pass
252                                         elif M == 108:  #Extruder RPM (these should not be in the final GCode, but they are)
253                                                 pass
254                                         elif M == 109:  #Set temperature, wait
255                                                 pass
256                                         elif M == 110:  #Reset N counter
257                                                 pass
258                                         elif M == 113:  #Extruder PWM (these should not be in the final GCode, but they are)
259                                                 pass
260                                         elif M == 117:  #LCD message
261                                                 pass
262                                         elif M == 140:  #Set bed temperature
263                                                 pass
264                                         elif M == 190:  #Set bed temperature & wait
265                                                 pass
266                                         elif M == 221:  #Extrude amount multiplier
267                                                 s = getCodeFloat(line, 'S')
268                                                 if s is not None:
269                                                         extrudeAmountMultiply = s / 100.0
270                                         else:
271                                                 print "Unknown M code:" + str(M)
272                 for path in currentLayer:
273                         path['points'] = numpy.array(path['points'], numpy.float32)
274                         path['extrusion'] = numpy.array(path['extrusion'], numpy.float32)
275                 self.layerList.append(currentLayer)
276                 if self.progressCallback is not None and self._fileSize > 0:
277                         self.progressCallback(float(gcodeFile.tell()) / float(self._fileSize))
278                 self.extrusionAmount = maxExtrusion
279                 self.totalMoveTimeMinute = totalMoveTimeMinute
280                 #print "Extruded a total of: %d mm of filament" % (self.extrusionAmount)
281                 #print "Estimated print duration: %.2f minutes" % (self.totalMoveTimeMinute)
282
283 def getCodeInt(line, code):
284         n = line.find(code) + 1
285         if n < 1:
286                 return None
287         m = line.find(' ', n)
288         try:
289                 if m < 0:
290                         return int(line[n:])
291                 return int(line[n:m])
292         except:
293                 return None
294
295 def getCodeFloat(line, code):
296         n = line.find(code) + 1
297         if n < 1:
298                 return None
299         m = line.find(' ', n)
300         try:
301                 if m < 0:
302                         return float(line[n:])
303                 return float(line[n:m])
304         except:
305                 return None
306
307 if __name__ == '__main__':
308         t = time.time()
309         for filename in sys.argv[1:]:
310                 g = gcode()
311                 g.load(filename)
312                 print g.totalMoveTimeMinute
313         print time.time() - t
314