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