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 return os.path.abspath(os.path.join(os.path.dirname(__file__), '../..', 'CuraEngine'))
28 def getTempFilename():
29 warnings.simplefilter('ignore')
30 ret = os.tempnam(None, "Cura_Tmp")
31 warnings.simplefilter('default')
35 def __init__(self, progressCallback):
38 self._callback = progressCallback
39 self._binaryStorageFilename = getTempFilename()
40 self._exportFilename = getTempFilename()
41 self._progressSteps = ['inset', 'skin', 'export']
44 self._printTimeSeconds = None
45 self._filamentMM = None
46 self._modelHash = None
51 os.remove(self._binaryStorageFilename)
55 os.remove(self._exportFilename)
59 def abortSlicer(self):
60 if self._process is not None:
62 self._process.terminate()
69 if self._process is not None:
72 def getGCodeFilename(self):
73 return self._exportFilename
75 def getSliceLog(self):
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')
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)
90 return "%.2f" % (self.getFilamentWeight() * cost_kg)
91 elif cost_meter > 0.0:
92 return "%.2f" % (self._filamentMM / 1000.0 * cost_meter)
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)
102 def getFilamentAmount(self):
103 return '%0.2f meter %0.0f gram' % (float(self._filamentMM) / 1000.0, self.getFilamentWeight() * 1000.0)
105 def runSlicer(self, scene):
107 for obj in scene.objects():
108 if scene.checkPlatform(obj):
109 extruderCount = max(extruderCount, len(obj._meshList))
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]
117 with open(self._binaryStorageFilename, "wb") as f:
118 hash = hashlib.sha512()
119 order = scene.printOrder()
121 pos = numpy.array(profile.getMachineCenterCoords()) * 1000
122 commandList += ['-s', 'posx=%d' % int(pos[0]), '-s', 'posy=%d' % int(pos[1])]
125 for obj in scene.objects():
126 if scene.checkPlatform(obj):
127 for mesh in obj._meshList:
128 vertexTotal += mesh.vertexCount
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())
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()
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)]
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
162 def _watchProcess(self, commandList, oldThread):
163 if oldThread is not None:
164 if self._process is not None:
165 self._process.terminate()
167 self._callback(-1.0, False)
169 self._process = self._runSliceProcess(commandList)
171 traceback.print_exc()
173 if self._thread != threading.currentThread():
174 self._process.terminate()
175 self._callback(0.0, False)
177 self._printTimeSeconds = None
178 self._filamentMM = None
180 line = self._process.stdout.readline()
184 if line.startswith('Progress:'):
185 line = line.split(':')
186 if line[1] == 'process':
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])
193 progressValue /= self._objCount
194 progressValue += 1.0 / self._objCount * objectNr
196 self._callback(progressValue, False)
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())
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()
211 pluginError = profile.runPostProcessingPlugins(self._exportFilename)
212 if pluginError is not None:
214 self._callback(1.0, True)
216 for line in self._sliceLog:
218 self._callback(-1.0, False)
223 def _engineSettings(self, extruderCount):
225 'layerThickness': int(profile.getProfileSettingFloat('layer_height') * 1000),
226 'initialLayerThickness': int(profile.getProfileSettingFloat('bottom_thickness') * 1000) if profile.getProfileSettingFloat('bottom_thickness') > 0.0 else int(profile.getProfileSettingFloat('layer_height') * 1000),
227 'filamentDiameter': int(profile.getProfileSettingFloat('filament_diameter') * 1000),
228 'filamentFlow': int(profile.getProfileSettingFloat('filament_flow')),
229 'extrusionWidth': int(profile.calculateEdgeWidth() * 1000),
230 'insetCount': int(profile.calculateLineCount()),
231 'downSkinCount': int(profile.calculateSolidLayerCount()) if profile.getProfileSetting('solid_bottom') == 'True' else 0,
232 'upSkinCount': int(profile.calculateSolidLayerCount()) if profile.getProfileSetting('solid_top') == 'True' else 0,
233 'sparseInfillLineDistance': int(100 * profile.calculateEdgeWidth() * 1000 / profile.getProfileSettingFloat('fill_density')) if profile.getProfileSettingFloat('fill_density') > 0 else -1,
234 'infillOverlap': int(profile.getProfileSettingFloat('fill_overlap')),
235 'initialSpeedupLayers': int(4),
236 'initialLayerSpeed': int(profile.getProfileSettingFloat('bottom_layer_speed')),
237 'printSpeed': int(profile.getProfileSettingFloat('print_speed')),
238 'infillSpeed': int(profile.getProfileSettingFloat('infill_speed')) if int(profile.getProfileSettingFloat('infill_speed')) > 0 else int(profile.getProfileSettingFloat('print_speed')),
239 'moveSpeed': int(profile.getProfileSettingFloat('travel_speed')),
240 'fanOnLayerNr': int(profile.getProfileSettingFloat('fan_layer')),
241 'fanSpeedMin': int(profile.getProfileSettingFloat('fan_speed')) if profile.getProfileSetting('fan_enabled') == 'True' else 0,
242 'fanSpeedMax': int(profile.getProfileSettingFloat('fan_speed_max')) if profile.getProfileSetting('fan_enabled') == 'True' else 0,
243 'supportAngle': int(-1) if profile.getProfileSetting('support') == 'None' else int(60),
244 'supportEverywhere': int(1) if profile.getProfileSetting('support') == 'Everywhere' else int(0),
245 'supportLineWidth': int(profile.getProfileSettingFloat('support_rate') * profile.calculateEdgeWidth() * 1000 / 100),
246 'retractionAmount': int(profile.getProfileSettingFloat('retraction_amount') * 1000) if profile.getProfileSetting('retraction_enable') == 'True' else 0,
247 'retractionSpeed': int(profile.getProfileSettingFloat('retraction_speed')),
248 'retractionAmountExtruderSwitch': int(profile.getProfileSettingFloat('retraction_dual_amount') * 1000),
249 'multiVolumeOverlap': int(profile.getProfileSettingFloat('overlap_dual') * 1000),
250 'objectSink': int(profile.getProfileSettingFloat('object_sink') * 1000),
251 'minimalLayerTime': int(profile.getProfileSettingFloat('cool_min_layer_time')),
252 'minimalFeedrate': int(profile.getProfileSettingFloat('cool_min_feedrate')),
253 'coolHeadLift': 1 if profile.getProfileSetting('cool_head_lift') == 'True' else 0,
254 'startCode': profile.getAlterationFileContents('start.gcode', extruderCount),
255 'endCode': profile.getAlterationFileContents('end.gcode', extruderCount),
257 'extruderOffset[1].X': int(profile.getPreferenceFloat('extruder_offset_x1') * 1000),
258 'extruderOffset[1].Y': int(profile.getPreferenceFloat('extruder_offset_y1') * 1000),
259 'extruderOffset[2].X': int(profile.getPreferenceFloat('extruder_offset_x2') * 1000),
260 'extruderOffset[2].Y': int(profile.getPreferenceFloat('extruder_offset_y2') * 1000),
261 'extruderOffset[3].X': int(profile.getPreferenceFloat('extruder_offset_x3') * 1000),
262 'extruderOffset[3].Y': int(profile.getPreferenceFloat('extruder_offset_y3') * 1000),
265 if profile.getProfileSetting('platform_adhesion') == 'Brim':
266 settings['skirtDistance'] = 0
267 settings['skirtLineCount'] = int(profile.getProfileSettingFloat('brim_line_count'))
268 elif profile.getProfileSetting('platform_adhesion') == 'Raft':
269 settings['skirtDistance'] = 0
270 settings['skirtLineCount'] = 0
271 settings['raftMargin'] = int(profile.getProfileSettingFloat('raft_margin') * 1000)
272 settings['raftBaseThickness'] = int(profile.getProfileSettingFloat('raft_base_thickness') * 1000)
273 settings['raftBaseLinewidth'] = int(profile.getProfileSettingFloat('raft_base_linewidth') * 1000)
274 settings['raftInterfaceThickness'] = int(profile.getProfileSettingFloat('raft_interface_thickness') * 1000)
275 settings['raftInterfaceLinewidth'] = int(profile.getProfileSettingFloat('raft_interface_linewidth') * 1000)
277 settings['skirtDistance'] = int(profile.getProfileSettingFloat('skirt_gap') * 1000)
278 settings['skirtLineCount'] = int(profile.getProfileSettingFloat('skirt_line_count'))
280 if profile.getProfileSetting('fix_horrible_union_all_type_a') == 'True':
281 settings['fixHorrible'] |= 0x01
282 if profile.getProfileSetting('fix_horrible_union_all_type_b') == 'True':
283 settings['fixHorrible'] |= 0x02
284 if profile.getProfileSetting('fix_horrible_use_open_bits') == 'True':
285 settings['fixHorrible'] |= 0x10
286 if profile.getProfileSetting('fix_horrible_extensive_stitching') == 'True':
287 settings['fixHorrible'] |= 0x04
289 if settings['layerThickness'] <= 0:
290 settings['layerThickness'] = 1000
293 def _runSliceProcess(self, cmdList):
295 if subprocess.mswindows:
296 su = subprocess.STARTUPINFO()
297 su.dwFlags |= subprocess.STARTF_USESHOWWINDOW
298 su.wShowWindow = subprocess.SW_HIDE
299 kwargs['startupinfo'] = su
300 kwargs['creationflags'] = 0x00004000 #BELOW_NORMAL_PRIORITY_CLASS
301 return subprocess.Popen(cmdList, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs)
303 def submitSliceInfoOnline(self):
304 if profile.getPreference('submit_slice_information') != 'True':
306 if version.isDevVersion():
309 'processor': platform.processor(),
310 'machine': platform.machine(),
311 'platform': platform.platform(),
312 'profile': profile.getGlobalProfileString(),
313 'preferences': profile.getGlobalPreferencesString(),
314 'modelhash': self._modelHash,
315 'version': version.getVersion(),
318 f = urllib2.urlopen("http://www.youmagine.com/curastats/", data = urllib.urlencode(data), timeout = 1)