chiark / gitweb /
Merge branch 'SteamEngine' of github.com:daid/Cura into SteamEngine
[cura.git] / Cura / util / sliceEngine.py
1 __copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License"
2 import subprocess
3 import time
4 import math
5 import numpy
6 import os
7 import warnings
8 import threading
9 import traceback
10 import platform
11 import sys
12 import urllib
13 import urllib2
14 import hashlib
15
16 from Cura.util import profile
17 from Cura.util import version
18
19 def getEngineFilename():
20         if platform.system() == 'Windows':
21                 if os.path.exists('C:/Software/Cura_SteamEngine/_bin/Release/Cura_SteamEngine.exe'):
22                         return 'C:/Software/Cura_SteamEngine/_bin/Release/Cura_SteamEngine.exe'
23                 return os.path.abspath(os.path.join(os.path.dirname(__file__), '../..', 'CuraEngine.exe'))
24         if hasattr(sys, 'frozen'):
25                 return os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../../..', 'CuraEngine'))
26         return os.path.abspath(os.path.join(os.path.dirname(__file__), '../..', 'CuraEngine'))
27
28 def getTempFilename():
29         warnings.simplefilter('ignore')
30         ret = os.tempnam(None, "Cura_Tmp")
31         warnings.simplefilter('default')
32         return ret
33
34 class Slicer(object):
35         def __init__(self, progressCallback):
36                 self._process = None
37                 self._thread = None
38                 self._callback = progressCallback
39                 self._binaryStorageFilename = getTempFilename()
40                 self._exportFilename = getTempFilename()
41                 self._progressSteps = ['inset', 'skin', 'export']
42                 self._objCount = 0
43                 self._sliceLog = []
44                 self._printTimeSeconds = None
45                 self._filamentMM = None
46                 self._modelHash = None
47
48         def cleanup(self):
49                 self.abortSlicer()
50                 try:
51                         os.remove(self._binaryStorageFilename)
52                 except:
53                         pass
54                 try:
55                         os.remove(self._exportFilename)
56                 except:
57                         pass
58
59         def abortSlicer(self):
60                 if self._process is not None:
61                         try:
62                                 self._process.terminate()
63                         except:
64                                 pass
65                         self._thread.join()
66                 self._thread = None
67
68         def wait(self):
69                 if self._process is not None:
70                         self._thread.join()
71
72         def getGCodeFilename(self):
73                 return self._exportFilename
74
75         def getSliceLog(self):
76                 return self._sliceLog
77
78         def getFilamentWeight(self):
79                 #Calculates the weight of the filament in kg
80                 radius = float(profile.getProfileSetting('filament_diameter')) / 2
81                 volumeM3 = (self._filamentMM * (math.pi * radius * radius)) / (1000*1000*1000)
82                 return volumeM3 * profile.getPreferenceFloat('filament_physical_density')
83
84         def getFilamentCost(self):
85                 cost_kg = profile.getPreferenceFloat('filament_cost_kg')
86                 cost_meter = profile.getPreferenceFloat('filament_cost_meter')
87                 if cost_kg > 0.0 and cost_meter > 0.0:
88                         return "%.2f / %.2f" % (self.getFilamentWeight() * cost_kg, self._filamentMM / 1000.0 * cost_meter)
89                 elif cost_kg > 0.0:
90                         return "%.2f" % (self.getFilamentWeight() * cost_kg)
91                 elif cost_meter > 0.0:
92                         return "%.2f" % (self._filamentMM / 1000.0 * cost_meter)
93                 return None
94
95         def getPrintTime(self):
96                 if int(self._printTimeSeconds / 60 / 60) < 1:
97                         return '%d minutes' % (int(self._printTimeSeconds / 60) % 60)
98                 if int(self._printTimeSeconds / 60 / 60) == 1:
99                         return '%d hour %d minutes' % (int(self._printTimeSeconds / 60 / 60), int(self._printTimeSeconds / 60) % 60)
100                 return '%d hours %d minutes' % (int(self._printTimeSeconds / 60 / 60), int(self._printTimeSeconds / 60) % 60)
101
102         def getFilamentAmount(self):
103                 return '%0.2f meter %0.0f gram' % (float(self._filamentMM) / 1000.0, self.getFilamentWeight() * 1000.0)
104
105         def runSlicer(self, scene):
106                 extruderCount = 1
107                 for obj in scene.objects():
108                         if scene.checkPlatform(obj):
109                                 extruderCount = max(extruderCount, len(obj._meshList))
110
111                 commandList = [getEngineFilename(), '-vv']
112                 for k, v in self._engineSettings(extruderCount).iteritems():
113                         commandList += ['-s', '%s=%s' % (k, str(v))]
114                 commandList += ['-o', self._exportFilename]
115                 commandList += ['-b', self._binaryStorageFilename]
116                 self._objCount = 0
117                 with open(self._binaryStorageFilename, "wb") as f:
118                         hash = hashlib.sha512()
119                         order = scene.printOrder()
120                         if order is None:
121                                 pos = numpy.array(profile.getMachineCenterCoords()) * 1000
122                                 commandList += ['-s', 'posx=%d' % int(pos[0]), '-s', 'posy=%d' % int(pos[1])]
123
124                                 vertexTotal = 0
125                                 for obj in scene.objects():
126                                         if scene.checkPlatform(obj):
127                                                 for mesh in obj._meshList:
128                                                         vertexTotal += mesh.vertexCount
129
130                                 f.write(numpy.array([vertexTotal], numpy.int32).tostring())
131                                 for obj in scene.objects():
132                                         if scene.checkPlatform(obj):
133                                                 for mesh in obj._meshList:
134                                                         vertexes = (numpy.matrix(mesh.vertexes, copy = False) * numpy.matrix(obj._matrix, numpy.float32)).getA()
135                                                         vertexes -= obj._drawOffset
136                                                         vertexes += numpy.array([obj.getPosition()[0], obj.getPosition()[1], 0.0])
137                                                         f.write(vertexes.tostring())
138                                                         hash.update(mesh.vertexes.tostring())
139
140                                 commandList += ['#']
141                                 self._objCount = 1
142                         else:
143                                 for n in order:
144                                         obj = scene.objects()[n]
145                                         for mesh in obj._meshList:
146                                                 f.write(numpy.array([mesh.vertexCount], numpy.int32).tostring())
147                                                 s = mesh.vertexes.tostring()
148                                                 f.write(s)
149                                                 hash.update(s)
150                                         pos = obj.getPosition() * 1000
151                                         pos += numpy.array(profile.getMachineCenterCoords()) * 1000
152                                         commandList += ['-m', ','.join(map(str, obj._matrix.getA().flatten()))]
153                                         commandList += ['-s', 'posx=%d' % int(pos[0]), '-s', 'posy=%d' % int(pos[1])]
154                                         commandList += ['#' * len(obj._meshList)]
155                                         self._objCount += 1
156                         self._modelHash = hash.hexdigest()
157                 if self._objCount > 0:
158                         self._thread = threading.Thread(target=self._watchProcess, args=(commandList, self._thread))
159                         self._thread.daemon = True
160                         self._thread.start()
161
162         def _watchProcess(self, commandList, oldThread):
163                 if oldThread is not None:
164                         if self._process is not None:
165                                 self._process.terminate()
166                         oldThread.join()
167                 self._callback(-1.0, False)
168                 try:
169                         self._process = self._runSliceProcess(commandList)
170                 except OSError:
171                         traceback.print_exc()
172                         return
173                 if self._thread != threading.currentThread():
174                         self._process.terminate()
175                 self._callback(0.0, False)
176                 self._sliceLog = []
177                 self._printTimeSeconds = None
178                 self._filamentMM = None
179
180                 line = self._process.stdout.readline()
181                 objectNr = 0
182                 while len(line):
183                         line = line.strip()
184                         if line.startswith('Progress:'):
185                                 line = line.split(':')
186                                 if line[1] == 'process':
187                                         objectNr += 1
188                                 elif line[1] in self._progressSteps:
189                                         progressValue = float(line[2]) / float(line[3])
190                                         progressValue /= len(self._progressSteps)
191                                         progressValue += 1.0 / len(self._progressSteps) * self._progressSteps.index(line[1])
192
193                                         progressValue /= self._objCount
194                                         progressValue += 1.0 / self._objCount * objectNr
195                                         try:
196                                                 self._callback(progressValue, False)
197                                         except:
198                                                 pass
199                         elif line.startswith('Print time:'):
200                                 self._printTimeSeconds = int(line.split(':')[1].strip())
201                         elif line.startswith('Filament:'):
202                                 self._filamentMM = int(line.split(':')[1].strip())
203                         else:
204                                 self._sliceLog.append(line.strip())
205                         line = self._process.stdout.readline()
206                 for line in self._process.stderr:
207                         self._sliceLog.append(line.strip())
208                 returnCode = self._process.wait()
209                 try:
210                         if returnCode == 0:
211                                 pluginError = profile.runPostProcessingPlugins(self._exportFilename)
212                                 if pluginError is not None:
213                                         print pluginError
214                                         self._sliceLog.append(pluginError)
215                                 self._callback(1.0, True)
216                         else:
217                                 for line in self._sliceLog:
218                                         print line
219                                 self._callback(-1.0, False)
220                 except:
221                         pass
222                 self._process = None
223
224         def _engineSettings(self, extruderCount):
225                 settings = {
226                         'layerThickness': int(profile.getProfileSettingFloat('layer_height') * 1000),
227                         'initialLayerThickness': int(profile.getProfileSettingFloat('bottom_thickness') * 1000) if profile.getProfileSettingFloat('bottom_thickness') > 0.0 else int(profile.getProfileSettingFloat('layer_height') * 1000),
228                         'filamentDiameter': int(profile.getProfileSettingFloat('filament_diameter') * 1000),
229                         'filamentFlow': int(profile.getProfileSettingFloat('filament_flow')),
230                         'extrusionWidth': int(profile.calculateEdgeWidth() * 1000),
231                         'insetCount': int(profile.calculateLineCount()),
232                         'downSkinCount': int(profile.calculateSolidLayerCount()) if profile.getProfileSetting('solid_bottom') == 'True' else 0,
233                         'upSkinCount': int(profile.calculateSolidLayerCount()) if profile.getProfileSetting('solid_top') == 'True' else 0,
234                         'sparseInfillLineDistance': int(100 * profile.calculateEdgeWidth() * 1000 / profile.getProfileSettingFloat('fill_density')) if profile.getProfileSettingFloat('fill_density') > 0 else -1,
235                         'infillOverlap': int(profile.getProfileSettingFloat('fill_overlap')),
236                         'initialSpeedupLayers': int(4),
237                         'initialLayerSpeed': int(profile.getProfileSettingFloat('bottom_layer_speed')),
238                         'printSpeed': int(profile.getProfileSettingFloat('print_speed')),
239                         'infillSpeed': int(profile.getProfileSettingFloat('infill_speed')) if int(profile.getProfileSettingFloat('infill_speed')) > 0 else int(profile.getProfileSettingFloat('print_speed')),
240                         'moveSpeed': int(profile.getProfileSettingFloat('travel_speed')),
241                         'fanOnLayerNr': int(profile.getProfileSettingFloat('fan_layer')),
242                         'fanSpeedMin': int(profile.getProfileSettingFloat('fan_speed')) if profile.getProfileSetting('fan_enabled') == 'True' else 0,
243                         'fanSpeedMax': int(profile.getProfileSettingFloat('fan_speed_max')) if profile.getProfileSetting('fan_enabled') == 'True' else 0,
244                         'supportAngle': int(-1) if profile.getProfileSetting('support') == 'None' else int(60),
245                         'supportEverywhere': int(1) if profile.getProfileSetting('support') == 'Everywhere' else int(0),
246                         'supportLineWidth': int(profile.getProfileSettingFloat('support_rate') * profile.calculateEdgeWidth() * 1000 / 100),
247                         'retractionAmount': int(profile.getProfileSettingFloat('retraction_amount') * 1000) if profile.getProfileSetting('retraction_enable') == 'True' else 0,
248                         'retractionSpeed': int(profile.getProfileSettingFloat('retraction_speed')),
249                         'retractionAmountExtruderSwitch': int(profile.getProfileSettingFloat('retraction_dual_amount') * 1000),
250                         'multiVolumeOverlap': int(profile.getProfileSettingFloat('overlap_dual') * 1000),
251                         'objectSink': int(profile.getProfileSettingFloat('object_sink') * 1000),
252                         'minimalLayerTime': int(profile.getProfileSettingFloat('cool_min_layer_time')),
253                         'minimalFeedrate': int(profile.getProfileSettingFloat('cool_min_feedrate')),
254                         'coolHeadLift': 1 if profile.getProfileSetting('cool_head_lift') == 'True' else 0,
255                         'startCode': profile.getAlterationFileContents('start.gcode', extruderCount),
256                         'endCode': profile.getAlterationFileContents('end.gcode', extruderCount),
257
258                         'extruderOffset[1].X': int(profile.getPreferenceFloat('extruder_offset_x1') * 1000),
259                         'extruderOffset[1].Y': int(profile.getPreferenceFloat('extruder_offset_y1') * 1000),
260                         'extruderOffset[2].X': int(profile.getPreferenceFloat('extruder_offset_x2') * 1000),
261                         'extruderOffset[2].Y': int(profile.getPreferenceFloat('extruder_offset_y2') * 1000),
262                         'extruderOffset[3].X': int(profile.getPreferenceFloat('extruder_offset_x3') * 1000),
263                         'extruderOffset[3].Y': int(profile.getPreferenceFloat('extruder_offset_y3') * 1000),
264                         'fixHorrible': 0,
265                 }
266                 if profile.getProfileSetting('platform_adhesion') == 'Brim':
267                         settings['skirtDistance'] = 0
268                         settings['skirtLineCount'] = int(profile.getProfileSettingFloat('brim_line_count'))
269                 elif profile.getProfileSetting('platform_adhesion') == 'Raft':
270                         settings['skirtDistance'] = 0
271                         settings['skirtLineCount'] = 0
272                         settings['raftMargin'] = int(profile.getProfileSettingFloat('raft_margin') * 1000)
273                         settings['raftBaseThickness'] = int(profile.getProfileSettingFloat('raft_base_thickness') * 1000)
274                         settings['raftBaseLinewidth'] = int(profile.getProfileSettingFloat('raft_base_linewidth') * 1000)
275                         settings['raftInterfaceThickness'] = int(profile.getProfileSettingFloat('raft_interface_thickness') * 1000)
276                         settings['raftInterfaceLinewidth'] = int(profile.getProfileSettingFloat('raft_interface_linewidth') * 1000)
277                 else:
278                         settings['skirtDistance'] = int(profile.getProfileSettingFloat('skirt_gap') * 1000)
279                         settings['skirtLineCount'] = int(profile.getProfileSettingFloat('skirt_line_count'))
280
281                 if profile.getProfileSetting('fix_horrible_union_all_type_a') == 'True':
282                         settings['fixHorrible'] |= 0x01
283                 if profile.getProfileSetting('fix_horrible_union_all_type_b') == 'True':
284                         settings['fixHorrible'] |= 0x02
285                 if profile.getProfileSetting('fix_horrible_use_open_bits') == 'True':
286                         settings['fixHorrible'] |= 0x10
287                 if profile.getProfileSetting('fix_horrible_extensive_stitching') == 'True':
288                         settings['fixHorrible'] |= 0x04
289
290                 if settings['layerThickness'] <= 0:
291                         settings['layerThickness'] = 1000
292                 return settings
293
294         def _runSliceProcess(self, cmdList):
295                 kwargs = {}
296                 if subprocess.mswindows:
297                         su = subprocess.STARTUPINFO()
298                         su.dwFlags |= subprocess.STARTF_USESHOWWINDOW
299                         su.wShowWindow = subprocess.SW_HIDE
300                         kwargs['startupinfo'] = su
301                         kwargs['creationflags'] = 0x00004000 #BELOW_NORMAL_PRIORITY_CLASS
302                 return subprocess.Popen(cmdList, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs)
303
304         def submitSliceInfoOnline(self):
305                 if profile.getPreference('submit_slice_information') != 'True':
306                         return
307                 if version.isDevVersion():
308                         return
309                 data = {
310                         'processor': platform.processor(),
311                         'machine': platform.machine(),
312                         'platform': platform.platform(),
313                         'profile': profile.getGlobalProfileString(),
314                         'preferences': profile.getGlobalPreferencesString(),
315                         'modelhash': self._modelHash,
316                         'version': version.getVersion(),
317                 }
318                 try:
319                         f = urllib2.urlopen("http://www.youmagine.com/curastats/", data = urllib.urlencode(data), timeout = 1)
320                         f.read()
321                         f.close()
322                 except:
323                         pass