1 __copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License"
16 from Cura.util import profile
17 from Cura.util import version
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 if os.path.isfile('/usr/bin/CuraEngine'):
27 return '/usr/bin/CuraEngine'
28 if os.path.isfile('/usr/local/bin/CuraEngine'):
29 return '/usr/local/bin/CuraEngine'
30 return os.path.abspath(os.path.join(os.path.dirname(__file__), '../..', 'CuraEngine'))
32 def getTempFilename():
33 warnings.simplefilter('ignore')
34 ret = os.tempnam(None, "Cura_Tmp")
35 warnings.simplefilter('default')
39 def __init__(self, progressCallback):
42 self._callback = progressCallback
43 self._binaryStorageFilename = getTempFilename()
44 self._exportFilename = getTempFilename()
45 self._progressSteps = ['inset', 'skin', 'export']
48 self._printTimeSeconds = None
49 self._filamentMM = [0.0, 0.0]
50 self._modelHash = None
56 os.remove(self._binaryStorageFilename)
60 os.remove(self._exportFilename)
64 def abortSlicer(self):
65 if self._process is not None:
67 self._process.terminate()
74 if self._thread is not None:
77 def getGCodeFilename(self):
78 return self._exportFilename
80 def getSliceLog(self):
86 def getFilamentWeight(self, e=0):
87 #Calculates the weight of the filament in kg
88 radius = float(profile.getProfileSetting('filament_diameter')) / 2
89 volumeM3 = (self._filamentMM[e] * (math.pi * radius * radius)) / (1000*1000*1000)
90 return volumeM3 * profile.getPreferenceFloat('filament_physical_density')
92 def getFilamentCost(self, e=0):
93 cost_kg = profile.getPreferenceFloat('filament_cost_kg')
94 cost_meter = profile.getPreferenceFloat('filament_cost_meter')
95 if cost_kg > 0.0 and cost_meter > 0.0:
96 return "%.2f / %.2f" % (self.getFilamentWeight(e) * cost_kg, self._filamentMM[e] / 1000.0 * cost_meter)
98 return "%.2f" % (self.getFilamentWeight(e) * cost_kg)
99 elif cost_meter > 0.0:
100 return "%.2f" % (self._filamentMM[e] / 1000.0 * cost_meter)
103 def getPrintTime(self):
104 if int(self._printTimeSeconds / 60 / 60) < 1:
105 return '%d minutes' % (int(self._printTimeSeconds / 60) % 60)
106 if int(self._printTimeSeconds / 60 / 60) == 1:
107 return '%d hour %d minutes' % (int(self._printTimeSeconds / 60 / 60), int(self._printTimeSeconds / 60) % 60)
108 return '%d hours %d minutes' % (int(self._printTimeSeconds / 60 / 60), int(self._printTimeSeconds / 60) % 60)
110 def getFilamentAmount(self, e=0):
111 if self._filamentMM[e] == 0.0:
113 return '%0.2f meter %0.0f gram' % (float(self._filamentMM[e]) / 1000.0, self.getFilamentWeight(e) * 1000.0)
115 def runSlicer(self, scene):
116 if len(scene.objects()) < 1:
119 for obj in scene.objects():
120 if scene.checkPlatform(obj):
121 extruderCount = max(extruderCount, len(obj._meshList))
123 extruderCount = max(extruderCount, profile.minimalExtruderCount())
125 commandList = [getEngineFilename(), '-vv']
126 for k, v in self._engineSettings(extruderCount).iteritems():
127 commandList += ['-s', '%s=%s' % (k, str(v))]
128 commandList += ['-o', self._exportFilename]
129 commandList += ['-b', self._binaryStorageFilename]
131 with open(self._binaryStorageFilename, "wb") as f:
132 hash = hashlib.sha512()
133 order = scene.printOrder()
135 pos = numpy.array(profile.getMachineCenterCoords()) * 1000
138 for obj in scene.objects():
139 if scene.checkPlatform(obj):
140 oMin = obj.getMinimum()[0:2] + obj.getPosition()
141 oMax = obj.getMaximum()[0:2] + obj.getPosition()
146 objMin[0] = min(oMin[0], objMin[0])
147 objMin[1] = min(oMin[1], objMin[1])
148 objMax[0] = max(oMax[0], objMax[0])
149 objMax[1] = max(oMax[1], objMax[1])
150 pos += (objMin + objMax) / 2.0 * 1000
151 commandList += ['-s', 'posx=%d' % int(pos[0]), '-s', 'posy=%d' % int(pos[1])]
153 vertexTotal = [0] * 4
155 for obj in scene.objects():
156 if scene.checkPlatform(obj):
157 meshMax = max(meshMax, len(obj._meshList))
158 for n in xrange(0, len(obj._meshList)):
159 vertexTotal[n] += obj._meshList[n].vertexCount
161 for n in xrange(0, meshMax):
162 f.write(numpy.array([vertexTotal[n]], numpy.int32).tostring())
163 for obj in scene.objects():
164 if scene.checkPlatform(obj):
165 if n < len(obj._meshList):
166 vertexes = (numpy.matrix(obj._meshList[n].vertexes, copy = False) * numpy.matrix(obj._matrix, numpy.float32)).getA()
167 vertexes -= obj._drawOffset
168 vertexes += numpy.array([obj.getPosition()[0], obj.getPosition()[1], 0.0])
169 f.write(vertexes.tostring())
170 hash.update(obj._meshList[n].vertexes.tostring())
172 commandList += ['#' * meshMax]
176 obj = scene.objects()[n]
177 for mesh in obj._meshList:
178 f.write(numpy.array([mesh.vertexCount], numpy.int32).tostring())
179 s = mesh.vertexes.tostring()
182 pos = obj.getPosition() * 1000
183 pos += numpy.array(profile.getMachineCenterCoords()) * 1000
184 commandList += ['-m', ','.join(map(str, obj._matrix.getA().flatten()))]
185 commandList += ['-s', 'posx=%d' % int(pos[0]), '-s', 'posy=%d' % int(pos[1])]
186 commandList += ['#' * len(obj._meshList)]
188 self._modelHash = hash.hexdigest()
189 if self._objCount > 0:
190 self._thread = threading.Thread(target=self._watchProcess, args=(commandList, self._thread))
191 self._thread.daemon = True
194 def _watchProcess(self, commandList, oldThread):
195 if oldThread is not None:
196 if self._process is not None:
197 self._process.terminate()
200 self._callback(-1.0, False)
202 self._process = self._runSliceProcess(commandList)
204 traceback.print_exc()
206 if self._thread != threading.currentThread():
207 self._process.terminate()
208 self._callback(0.0, False)
210 self._printTimeSeconds = None
211 self._filamentMM = [0.0, 0.0]
213 line = self._process.stdout.readline()
217 if line.startswith('Progress:'):
218 line = line.split(':')
219 if line[1] == 'process':
221 elif line[1] in self._progressSteps:
222 progressValue = float(line[2]) / float(line[3])
223 progressValue /= len(self._progressSteps)
224 progressValue += 1.0 / len(self._progressSteps) * self._progressSteps.index(line[1])
226 progressValue /= self._objCount
227 progressValue += 1.0 / self._objCount * objectNr
229 self._callback(progressValue, False)
232 elif line.startswith('Print time:'):
233 self._printTimeSeconds = int(line.split(':')[1].strip())
234 elif line.startswith('Filament:'):
235 self._filamentMM[0] = int(line.split(':')[1].strip())
236 if profile.getMachineSetting('gcode_flavor') == 'UltiGCode':
237 radius = profile.getProfileSettingFloat('filament_diameter') / 2.0
238 self._filamentMM[0] /= (math.pi * radius * radius)
239 elif line.startswith('Filament2:'):
240 self._filamentMM[1] = int(line.split(':')[1].strip())
241 if profile.getMachineSetting('gcode_flavor') == 'UltiGCode':
242 radius = profile.getProfileSettingFloat('filament_diameter') / 2.0
243 self._filamentMM[1] /= (math.pi * radius * radius)
245 self._sliceLog.append(line.strip())
246 line = self._process.stdout.readline()
247 for line in self._process.stderr:
248 self._sliceLog.append(line.strip())
249 returnCode = self._process.wait()
252 pluginError = profile.runPostProcessingPlugins(self._exportFilename)
253 if pluginError is not None:
255 self._sliceLog.append(pluginError)
256 self._callback(1.0, True)
258 for line in self._sliceLog:
260 self._callback(-1.0, False)
265 def _engineSettings(self, extruderCount):
267 'layerThickness': int(profile.getProfileSettingFloat('layer_height') * 1000),
268 'initialLayerThickness': int(profile.getProfileSettingFloat('bottom_thickness') * 1000) if profile.getProfileSettingFloat('bottom_thickness') > 0.0 else int(profile.getProfileSettingFloat('layer_height') * 1000),
269 'filamentDiameter': int(profile.getProfileSettingFloat('filament_diameter') * 1000),
270 'filamentFlow': int(profile.getProfileSettingFloat('filament_flow')),
271 'extrusionWidth': int(profile.calculateEdgeWidth() * 1000),
272 'insetCount': int(profile.calculateLineCount()),
273 'downSkinCount': int(profile.calculateSolidLayerCount()) if profile.getProfileSetting('solid_bottom') == 'True' else 0,
274 'upSkinCount': int(profile.calculateSolidLayerCount()) if profile.getProfileSetting('solid_top') == 'True' else 0,
275 'infillOverlap': int(profile.getProfileSettingFloat('fill_overlap')),
276 'initialSpeedupLayers': int(4),
277 'initialLayerSpeed': int(profile.getProfileSettingFloat('bottom_layer_speed')),
278 'printSpeed': int(profile.getProfileSettingFloat('print_speed')),
279 'infillSpeed': int(profile.getProfileSettingFloat('infill_speed')) if int(profile.getProfileSettingFloat('infill_speed')) > 0 else int(profile.getProfileSettingFloat('print_speed')),
280 'moveSpeed': int(profile.getProfileSettingFloat('travel_speed')),
281 'fanSpeedMin': int(profile.getProfileSettingFloat('fan_speed')) if profile.getProfileSetting('fan_enabled') == 'True' else 0,
282 'fanSpeedMax': int(profile.getProfileSettingFloat('fan_speed_max')) if profile.getProfileSetting('fan_enabled') == 'True' else 0,
283 'supportAngle': int(-1) if profile.getProfileSetting('support') == 'None' else int(60),
284 'supportEverywhere': int(1) if profile.getProfileSetting('support') == 'Everywhere' else int(0),
285 'supportLineDistance': int(100 * profile.calculateEdgeWidth() * 1000 / profile.getProfileSettingFloat('support_fill_rate')) if profile.getProfileSettingFloat('support_fill_rate') > 0 else -1,
286 'supportXYDistance': int(1000 * profile.getProfileSettingFloat('support_xy_distance')),
287 'supportZDistance': int(1000 * profile.getProfileSettingFloat('support_z_distance')),
288 'supportExtruder': 0 if profile.getProfileSetting('support_dual_extrusion') == 'First extruder' else (1 if profile.getProfileSetting('support_dual_extrusion') == 'Second extruder' and profile.minimalExtruderCount() > 1 else -1),
289 'retractionAmount': int(profile.getProfileSettingFloat('retraction_amount') * 1000) if profile.getProfileSetting('retraction_enable') == 'True' else 0,
290 'retractionSpeed': int(profile.getProfileSettingFloat('retraction_speed')),
291 'retractionMinimalDistance': int(profile.getProfileSettingFloat('retraction_min_travel') * 1000),
292 'retractionAmountExtruderSwitch': int(profile.getProfileSettingFloat('retraction_dual_amount') * 1000),
293 'minimalExtrusionBeforeRetraction': int(profile.getProfileSettingFloat('retraction_minimal_extrusion') * 1000),
294 'enableCombing': 1 if profile.getProfileSetting('retraction_combing') == 'True' else 0,
295 'multiVolumeOverlap': int(profile.getProfileSettingFloat('overlap_dual') * 1000),
296 'objectSink': int(profile.getProfileSettingFloat('object_sink') * 1000),
297 'minimalLayerTime': int(profile.getProfileSettingFloat('cool_min_layer_time')),
298 'minimalFeedrate': int(profile.getProfileSettingFloat('cool_min_feedrate')),
299 'coolHeadLift': 1 if profile.getProfileSetting('cool_head_lift') == 'True' else 0,
300 'startCode': profile.getAlterationFileContents('start.gcode', extruderCount),
301 'endCode': profile.getAlterationFileContents('end.gcode', extruderCount),
303 'extruderOffset[1].X': int(profile.getMachineSettingFloat('extruder_offset_x1') * 1000),
304 'extruderOffset[1].Y': int(profile.getMachineSettingFloat('extruder_offset_y1') * 1000),
305 'extruderOffset[2].X': int(profile.getMachineSettingFloat('extruder_offset_x2') * 1000),
306 'extruderOffset[2].Y': int(profile.getMachineSettingFloat('extruder_offset_y2') * 1000),
307 'extruderOffset[3].X': int(profile.getMachineSettingFloat('extruder_offset_x3') * 1000),
308 'extruderOffset[3].Y': int(profile.getMachineSettingFloat('extruder_offset_y3') * 1000),
311 fanFullHeight = int(profile.getProfileSettingFloat('fan_full_height') * 1000)
312 settings['fanFullOnLayerNr'] = (fanFullHeight - settings['initialLayerThickness'] - 1) / settings['layerThickness'] + 1
313 if settings['fanFullOnLayerNr'] < 0:
314 settings['fanFullOnLayerNr'] = 0
316 if profile.getProfileSettingFloat('fill_density') == 0:
317 settings['sparseInfillLineDistance'] = -1
318 elif profile.getProfileSettingFloat('fill_density') == 100:
319 settings['sparseInfillLineDistance'] = settings['extrusionWidth']
320 #Set the up/down skins height to 10000 if we want a 100% filled object.
321 # This gives better results then normal 100% infill as the sparse and up/down skin have some overlap.
322 settings['downSkinCount'] = 10000
323 settings['upSkinCount'] = 10000
325 settings['sparseInfillLineDistance'] = int(100 * profile.calculateEdgeWidth() * 1000 / profile.getProfileSettingFloat('fill_density'))
326 if profile.getProfileSetting('platform_adhesion') == 'Brim':
327 settings['skirtDistance'] = 0
328 settings['skirtLineCount'] = int(profile.getProfileSettingFloat('brim_line_count'))
329 elif profile.getProfileSetting('platform_adhesion') == 'Raft':
330 settings['skirtDistance'] = 0
331 settings['skirtLineCount'] = 0
332 settings['raftMargin'] = int(profile.getProfileSettingFloat('raft_margin') * 1000)
333 settings['raftLineSpacing'] = int(profile.getProfileSettingFloat('raft_line_spacing') * 1000)
334 settings['raftBaseThickness'] = int(profile.getProfileSettingFloat('raft_base_thickness') * 1000)
335 settings['raftBaseLinewidth'] = int(profile.getProfileSettingFloat('raft_base_linewidth') * 1000)
336 settings['raftInterfaceThickness'] = int(profile.getProfileSettingFloat('raft_interface_thickness') * 1000)
337 settings['raftInterfaceLinewidth'] = int(profile.getProfileSettingFloat('raft_interface_linewidth') * 1000)
339 settings['skirtDistance'] = int(profile.getProfileSettingFloat('skirt_gap') * 1000)
340 settings['skirtLineCount'] = int(profile.getProfileSettingFloat('skirt_line_count'))
341 settings['skirtMinLength'] = int(profile.getProfileSettingFloat('skirt_minimal_length') * 1000)
343 if profile.getProfileSetting('fix_horrible_union_all_type_a') == 'True':
344 settings['fixHorrible'] |= 0x01
345 if profile.getProfileSetting('fix_horrible_union_all_type_b') == 'True':
346 settings['fixHorrible'] |= 0x02
347 if profile.getProfileSetting('fix_horrible_use_open_bits') == 'True':
348 settings['fixHorrible'] |= 0x10
349 if profile.getProfileSetting('fix_horrible_extensive_stitching') == 'True':
350 settings['fixHorrible'] |= 0x04
352 if settings['layerThickness'] <= 0:
353 settings['layerThickness'] = 1000
354 if profile.getMachineSetting('gcode_flavor') == 'UltiGCode':
355 settings['gcodeFlavor'] = 1
356 if profile.getProfileSetting('spiralize') == 'True':
357 settings['spiralizeMode'] = 1
358 if profile.getProfileSetting('wipe_tower') == 'True':
359 settings['enableWipeTower'] = 1
360 if profile.getProfileSetting('ooze_shield') == 'True':
361 settings['enableOozeShield'] = 1
364 def _runSliceProcess(self, cmdList):
366 if subprocess.mswindows:
367 su = subprocess.STARTUPINFO()
368 su.dwFlags |= subprocess.STARTF_USESHOWWINDOW
369 su.wShowWindow = subprocess.SW_HIDE
370 kwargs['startupinfo'] = su
371 kwargs['creationflags'] = 0x00004000 #BELOW_NORMAL_PRIORITY_CLASS
372 return subprocess.Popen(cmdList, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs)
374 def submitSliceInfoOnline(self):
375 if profile.getPreference('submit_slice_information') != 'True':
377 if version.isDevVersion():
380 'processor': platform.processor(),
381 'machine': platform.machine(),
382 'platform': platform.platform(),
383 'profile': profile.getProfileString(),
384 'preferences': profile.getPreferencesString(),
385 'modelhash': self._modelHash,
386 'version': version.getVersion(),
389 f = urllib2.urlopen("http://www.youmagine.com/curastats/", data = urllib.urlencode(data), timeout = 1)