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