2 Slice engine communication.
3 This module handles all communication with the slicing engine.
5 __copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License"
22 import cStringIO as StringIO
24 from Cura.util import profile
25 from Cura.util import pluginInfo
26 from Cura.util import version
27 from Cura.util import gcodeInterpreter
29 def getEngineFilename():
31 Finds and returns the path to the current engine executable. This is OS depended.
32 :return: The full path to the engine executable.
34 if platform.system() == 'Windows':
35 if version.isDevVersion() and os.path.exists('C:/Software/Cura_SteamEngine/_bin/Release/Cura_SteamEngine.exe'):
36 return 'C:/Software/Cura_SteamEngine/_bin/Release/Cura_SteamEngine.exe'
37 return os.path.abspath(os.path.join(os.path.dirname(__file__), '../..', 'CuraEngine.exe'))
38 if hasattr(sys, 'frozen'):
39 return os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../../..', 'CuraEngine'))
40 if os.path.isfile('/usr/bin/CuraEngine'):
41 return '/usr/bin/CuraEngine'
42 if os.path.isfile('/usr/local/bin/CuraEngine'):
43 return '/usr/local/bin/CuraEngine'
44 tempPath = os.path.abspath(os.path.join(os.path.dirname(__file__), '../..', 'CuraEngine'))
45 if os.path.isdir(tempPath):
46 tempPath = os.path.join(tempPath,'CuraEngine')
49 class EngineResult(object):
51 Result from running the CuraEngine.
52 Contains the engine log, polygons retrieved from the engine, the GCode and some meta-data.
56 self._gcodeData = StringIO.StringIO()
58 self._replaceInfo = {}
60 self._printTimeSeconds = None
61 self._filamentMM = [0.0] * 4
62 self._modelHash = None
63 self._profileString = profile.getProfileString()
64 self._preferencesString = profile.getPreferencesString()
65 self._gcodeInterpreter = gcodeInterpreter.gcode()
66 self._gcodeLoadThread = None
67 self._finished = False
69 def getFilamentWeight(self, e=0):
70 #Calculates the weight of the filament in kg
71 radius = float(profile.getProfileSetting('filament_diameter')) / 2
72 volumeM3 = (self._filamentMM[e] * (math.pi * radius * radius)) / (1000*1000*1000)
73 return volumeM3 * profile.getPreferenceFloat('filament_physical_density')
75 def getFilamentCost(self, e=0):
76 cost_kg = profile.getPreferenceFloat('filament_cost_kg')
77 cost_meter = profile.getPreferenceFloat('filament_cost_meter')
78 if cost_kg > 0.0 and cost_meter > 0.0:
79 return "%.2f / %.2f" % (self.getFilamentWeight(e) * cost_kg, self._filamentMM[e] / 1000.0 * cost_meter)
81 return "%.2f" % (self.getFilamentWeight(e) * cost_kg)
82 elif cost_meter > 0.0:
83 return "%.2f" % (self._filamentMM[e] / 1000.0 * cost_meter)
86 def getPrintTime(self):
87 if self._printTimeSeconds is None:
89 if int(self._printTimeSeconds / 60 / 60) < 1:
90 return '%d minutes' % (int(self._printTimeSeconds / 60) % 60)
91 if int(self._printTimeSeconds / 60 / 60) == 1:
92 return '%d hour %d minutes' % (int(self._printTimeSeconds / 60 / 60), int(self._printTimeSeconds / 60) % 60)
93 return '%d hours %d minutes' % (int(self._printTimeSeconds / 60 / 60), int(self._printTimeSeconds / 60) % 60)
95 def getFilamentAmount(self, e=0):
96 if self._filamentMM[e] == 0.0:
98 return '%0.2f meter %0.0f gram' % (float(self._filamentMM[e]) / 1000.0, self.getFilamentWeight(e) * 1000.0)
101 return self._engineLog
104 data = self._gcodeData.getvalue()
105 if len(self._replaceInfo) > 0:
106 block0 = data[0:2048]
107 for k, v in self._replaceInfo.items():
108 v = (v + ' ' * len(k))[:len(k)]
109 block0 = block0.replace(k, v)
110 return block0 + data[2048:]
113 def setGCode(self, gcode):
114 self._gcodeData = StringIO.StringIO(gcode)
115 self._replaceInfo = {}
117 def addLog(self, line):
118 self._engineLog.append(line)
120 def setHash(self, hash):
121 self._modelHash = hash
123 def setFinished(self, result):
124 self._finished = result
126 def isFinished(self):
127 return self._finished
129 def getGCodeLayers(self, loadCallback):
130 if not self._finished:
132 if self._gcodeInterpreter.layerList is None and self._gcodeLoadThread is None:
133 self._gcodeInterpreter.progressCallback = self._gcodeInterpreterCallback
134 self._gcodeLoadThread = threading.Thread(target=lambda : self._gcodeInterpreter.load(self._gcodeData))
135 self._gcodeLoadCallback = loadCallback
136 self._gcodeLoadThread.daemon = True
137 self._gcodeLoadThread.start()
138 return self._gcodeInterpreter.layerList
140 def _gcodeInterpreterCallback(self, progress):
141 if len(self._gcodeInterpreter.layerList) % 5 == 0:
143 return self._gcodeLoadCallback(self, progress)
145 def submitInfoOnline(self):
146 if profile.getPreference('submit_slice_information') != 'True':
148 if version.isDevVersion():
151 'processor': platform.processor(),
152 'machine': platform.machine(),
153 'platform': platform.platform(),
154 'profile': self._profileString,
155 'preferences': self._preferencesString,
156 'modelhash': self._modelHash,
157 'version': version.getVersion(),
160 f = urllib2.urlopen("http://www.youmagine.com/curastats/", data = urllib.urlencode(data), timeout = 1)
166 class Engine(object):
168 Class used to communicate with the CuraEngine.
169 The CuraEngine is ran as a 2nd process and reports back information trough stderr.
170 GCode trough stdout and has a socket connection for polygon information and loading the 3D model into the engine.
172 GUI_CMD_REQUEST_MESH = 0x01
173 GUI_CMD_SEND_POLYGONS = 0x02
174 GUI_CMD_FINISH_OBJECT = 0x03
176 def __init__(self, progressCallback):
179 self._callback = progressCallback
180 self._progressSteps = ['inset', 'skin', 'export']
184 self._serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
185 self._serverPortNr = 0xC20A
188 self._serversocket.bind(('127.0.0.1', self._serverPortNr))
190 print "Failed to listen on port: %d" % (self._serverPortNr)
191 self._serverPortNr += 1
192 if self._serverPortNr > 0xFFFF:
193 print "Failed to listen on any port..."
197 thread = threading.Thread(target=self._socketListenThread)
201 def _socketListenThread(self):
202 self._serversocket.listen(1)
203 print 'Listening for engine communications on %d' % (self._serverPortNr)
206 sock, _ = self._serversocket.accept()
207 thread = threading.Thread(target=self._socketConnectionThread, args=(sock,))
210 except socket.error, e:
211 if e.errno != errno.EINTR:
214 def _socketConnectionThread(self, sock):
224 cmd = struct.unpack('@i', data)[0]
225 if cmd == self.GUI_CMD_REQUEST_MESH:
226 meshInfo = self._modelData[0]
227 self._modelData = self._modelData[1:]
228 sock.sendall(struct.pack('@i', meshInfo[0]))
229 sock.sendall(meshInfo[1].tostring())
230 elif cmd == self.GUI_CMD_SEND_POLYGONS:
231 cnt = struct.unpack('@i', sock.recv(4))[0]
232 layerNr = struct.unpack('@i', sock.recv(4))[0]
233 layerNr += layerNrOffset
234 z = struct.unpack('@i', sock.recv(4))[0]
235 z = float(z) / 1000.0
236 typeNameLen = struct.unpack('@i', sock.recv(4))[0]
237 typeName = sock.recv(typeNameLen)
238 while len(self._result._polygons) < layerNr + 1:
239 self._result._polygons.append({})
240 polygons = self._result._polygons[layerNr]
241 if typeName not in polygons:
242 polygons[typeName] = []
243 for n in xrange(0, cnt):
244 length = struct.unpack('@i', sock.recv(4))[0]
246 while len(data) < length * 8 * 2:
247 recvData = sock.recv(length * 8 * 2 - len(data))
248 if len(recvData) < 1:
251 polygon2d = numpy.array(numpy.fromstring(data, numpy.int64), numpy.float32) / 1000.0
252 polygon2d = polygon2d.reshape((len(polygon2d) / 2, 2))
253 polygon = numpy.empty((len(polygon2d), 3), numpy.float32)
254 polygon[:,:-1] = polygon2d
256 polygons[typeName].append(polygon)
257 elif cmd == self.GUI_CMD_FINISH_OBJECT:
258 layerNrOffset = len(self._result._polygons)
260 print "Unknown command on socket: %x" % (cmd)
264 self._serversocket.close()
266 def abortEngine(self):
267 if self._process is not None:
269 self._process.terminate()
272 if self._thread is not None:
277 if self._thread is not None:
283 def runEngine(self, scene):
284 if len(scene.objects()) < 1:
287 for obj in scene.objects():
288 if scene.checkPlatform(obj):
289 extruderCount = max(extruderCount, len(obj._meshList))
291 extruderCount = max(extruderCount, profile.minimalExtruderCount())
293 commandList = [getEngineFilename(), '-v', '-p']
294 for k, v in self._engineSettings(extruderCount).iteritems():
295 commandList += ['-s', '%s=%s' % (k, str(v))]
296 commandList += ['-g', '%d' % (self._serverPortNr)]
299 hash = hashlib.sha512()
300 order = scene.printOrder()
302 pos = numpy.array(profile.getMachineCenterCoords()) * 1000
305 for obj in scene.objects():
306 if scene.checkPlatform(obj):
307 oMin = obj.getMinimum()[0:2] + obj.getPosition()
308 oMax = obj.getMaximum()[0:2] + obj.getPosition()
313 objMin[0] = min(oMin[0], objMin[0])
314 objMin[1] = min(oMin[1], objMin[1])
315 objMax[0] = max(oMax[0], objMax[0])
316 objMax[1] = max(oMax[1], objMax[1])
319 pos += (objMin + objMax) / 2.0 * 1000
320 commandList += ['-s', 'posx=%d' % int(pos[0]), '-s', 'posy=%d' % int(pos[1])]
322 vertexTotal = [0] * 4
324 for obj in scene.objects():
325 if scene.checkPlatform(obj):
326 meshMax = max(meshMax, len(obj._meshList))
327 for n in xrange(0, len(obj._meshList)):
328 vertexTotal[n] += obj._meshList[n].vertexCount
330 for n in xrange(0, meshMax):
331 verts = numpy.zeros((0, 3), numpy.float32)
332 for obj in scene.objects():
333 if scene.checkPlatform(obj):
334 if n < len(obj._meshList):
335 vertexes = (numpy.matrix(obj._meshList[n].vertexes, copy = False) * numpy.matrix(obj._matrix, numpy.float32)).getA()
336 vertexes -= obj._drawOffset
337 vertexes += numpy.array([obj.getPosition()[0], obj.getPosition()[1], 0.0])
338 verts = numpy.concatenate((verts, vertexes))
339 hash.update(obj._meshList[n].vertexes.tostring())
340 engineModelData.append((vertexTotal[n], verts))
342 commandList += ['$' * meshMax]
346 obj = scene.objects()[n]
347 for mesh in obj._meshList:
348 engineModelData.append((mesh.vertexCount, mesh.vertexes))
349 hash.update(mesh.vertexes.tostring())
350 pos = obj.getPosition() * 1000
351 pos += numpy.array(profile.getMachineCenterCoords()) * 1000
352 commandList += ['-m', ','.join(map(str, obj._matrix.getA().flatten()))]
353 commandList += ['-s', 'posx=%d' % int(pos[0]), '-s', 'posy=%d' % int(pos[1])]
354 commandList += ['$' * len(obj._meshList)]
356 modelHash = hash.hexdigest()
357 if self._objCount > 0:
358 self._thread = threading.Thread(target=self._watchProcess, args=(commandList, self._thread, engineModelData, modelHash))
359 self._thread.daemon = True
362 def _watchProcess(self, commandList, oldThread, engineModelData, modelHash):
363 if oldThread is not None:
364 if self._process is not None:
365 self._process.terminate()
368 self._modelData = engineModelData
370 self._process = self._runEngineProcess(commandList)
372 traceback.print_exc()
374 if self._thread != threading.currentThread():
375 self._process.terminate()
377 self._result = EngineResult()
378 self._result.addLog('Running: %s' % (' '.join(commandList)))
379 self._result.setHash(modelHash)
382 logThread = threading.Thread(target=self._watchStderr, args=(self._process.stderr,))
383 logThread.daemon = True
386 data = self._process.stdout.read(4096)
388 self._result._gcodeData.write(data)
389 data = self._process.stdout.read(4096)
391 returnCode = self._process.wait()
394 pluginError = pluginInfo.runPostProcessingPlugins(self._result)
395 if pluginError is not None:
397 self._result.addLog(pluginError)
398 self._result.setFinished(True)
401 for line in self._result.getLog():
406 def _watchStderr(self, stderr):
408 line = stderr.readline()
411 if line.startswith('Progress:'):
412 line = line.split(':')
413 if line[1] == 'process':
415 elif line[1] in self._progressSteps:
416 progressValue = float(line[2]) / float(line[3])
417 progressValue /= len(self._progressSteps)
418 progressValue += 1.0 / len(self._progressSteps) * self._progressSteps.index(line[1])
420 progressValue /= self._objCount
421 progressValue += 1.0 / self._objCount * objectNr
423 self._callback(progressValue)
426 elif line.startswith('Print time:'):
427 self._result._printTimeSeconds = int(line.split(':')[1].strip())
428 elif line.startswith('Filament:'):
429 self._result._filamentMM[0] = int(line.split(':')[1].strip())
430 if profile.getMachineSetting('gcode_flavor') == 'UltiGCode':
431 radius = profile.getProfileSettingFloat('filament_diameter') / 2.0
432 self._result._filamentMM[0] /= (math.pi * radius * radius)
433 elif line.startswith('Filament2:'):
434 self._result._filamentMM[1] = int(line.split(':')[1].strip())
435 if profile.getMachineSetting('gcode_flavor') == 'UltiGCode':
436 radius = profile.getProfileSettingFloat('filament_diameter') / 2.0
437 self._result._filamentMM[1] /= (math.pi * radius * radius)
438 elif line.startswith('Replace:'):
439 self._result._replaceInfo[line.split(':')[1].strip()] = line.split(':')[2].strip()
441 self._result.addLog(line)
442 line = stderr.readline()
444 def _engineSettings(self, extruderCount):
446 'layerThickness': int(profile.getProfileSettingFloat('layer_height') * 1000),
447 'initialLayerThickness': int(profile.getProfileSettingFloat('bottom_thickness') * 1000) if profile.getProfileSettingFloat('bottom_thickness') > 0.0 else int(profile.getProfileSettingFloat('layer_height') * 1000),
448 'filamentDiameter': int(profile.getProfileSettingFloat('filament_diameter') * 1000),
449 'filamentFlow': int(profile.getProfileSettingFloat('filament_flow')),
450 'extrusionWidth': int(profile.calculateEdgeWidth() * 1000),
451 'layer0extrusionWidth': int(profile.calculateEdgeWidth() * 1000),
452 'insetCount': int(profile.calculateLineCount()),
453 'downSkinCount': int(profile.calculateSolidLayerCount()) if profile.getProfileSetting('solid_bottom') == 'True' else 0,
454 'upSkinCount': int(profile.calculateSolidLayerCount()) if profile.getProfileSetting('solid_top') == 'True' else 0,
455 'infillOverlap': int(profile.getProfileSettingFloat('fill_overlap')),
456 'initialSpeedupLayers': int(4),
457 'initialLayerSpeed': int(profile.getProfileSettingFloat('bottom_layer_speed')),
458 'printSpeed': int(profile.getProfileSettingFloat('print_speed')),
459 'infillSpeed': int(profile.getProfileSettingFloat('infill_speed')) if int(profile.getProfileSettingFloat('infill_speed')) > 0 else int(profile.getProfileSettingFloat('print_speed')),
460 'inset0Speed': int(profile.getProfileSettingFloat('inset0_speed')) if int(profile.getProfileSettingFloat('inset0_speed')) > 0 else int(profile.getProfileSettingFloat('print_speed')),
461 'insetXSpeed': int(profile.getProfileSettingFloat('insetx_speed')) if int(profile.getProfileSettingFloat('insetx_speed')) > 0 else int(profile.getProfileSettingFloat('print_speed')),
462 'moveSpeed': int(profile.getProfileSettingFloat('travel_speed')),
463 'fanSpeedMin': int(profile.getProfileSettingFloat('fan_speed')) if profile.getProfileSetting('fan_enabled') == 'True' else 0,
464 'fanSpeedMax': int(profile.getProfileSettingFloat('fan_speed_max')) if profile.getProfileSetting('fan_enabled') == 'True' else 0,
465 'supportAngle': int(-1) if profile.getProfileSetting('support') == 'None' else int(profile.getProfileSettingFloat('support_angle')),
466 'supportEverywhere': int(1) if profile.getProfileSetting('support') == 'Everywhere' else int(0),
467 'supportLineDistance': int(100 * profile.calculateEdgeWidth() * 1000 / profile.getProfileSettingFloat('support_fill_rate')) if profile.getProfileSettingFloat('support_fill_rate') > 0 else -1,
468 'supportXYDistance': int(1000 * profile.getProfileSettingFloat('support_xy_distance')),
469 'supportZDistance': int(1000 * profile.getProfileSettingFloat('support_z_distance')),
470 '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),
471 'retractionAmount': int(profile.getProfileSettingFloat('retraction_amount') * 1000) if profile.getProfileSetting('retraction_enable') == 'True' else 0,
472 'retractionSpeed': int(profile.getProfileSettingFloat('retraction_speed')),
473 'retractionMinimalDistance': int(profile.getProfileSettingFloat('retraction_min_travel') * 1000),
474 'retractionAmountExtruderSwitch': int(profile.getProfileSettingFloat('retraction_dual_amount') * 1000),
475 'retractionZHop': int(profile.getProfileSettingFloat('retraction_hop') * 1000),
476 'minimalExtrusionBeforeRetraction': int(profile.getProfileSettingFloat('retraction_minimal_extrusion') * 1000),
477 'enableCombing': 1 if profile.getProfileSetting('retraction_combing') == 'True' else 0,
478 'multiVolumeOverlap': int(profile.getProfileSettingFloat('overlap_dual') * 1000),
479 'objectSink': max(0, int(profile.getProfileSettingFloat('object_sink') * 1000)),
480 'minimalLayerTime': int(profile.getProfileSettingFloat('cool_min_layer_time')),
481 'minimalFeedrate': int(profile.getProfileSettingFloat('cool_min_feedrate')),
482 'coolHeadLift': 1 if profile.getProfileSetting('cool_head_lift') == 'True' else 0,
483 'startCode': profile.getAlterationFileContents('start.gcode', extruderCount),
484 'endCode': profile.getAlterationFileContents('end.gcode', extruderCount),
486 'extruderOffset[1].X': int(profile.getMachineSettingFloat('extruder_offset_x1') * 1000),
487 'extruderOffset[1].Y': int(profile.getMachineSettingFloat('extruder_offset_y1') * 1000),
488 'extruderOffset[2].X': int(profile.getMachineSettingFloat('extruder_offset_x2') * 1000),
489 'extruderOffset[2].Y': int(profile.getMachineSettingFloat('extruder_offset_y2') * 1000),
490 'extruderOffset[3].X': int(profile.getMachineSettingFloat('extruder_offset_x3') * 1000),
491 'extruderOffset[3].Y': int(profile.getMachineSettingFloat('extruder_offset_y3') * 1000),
494 fanFullHeight = int(profile.getProfileSettingFloat('fan_full_height') * 1000)
495 settings['fanFullOnLayerNr'] = (fanFullHeight - settings['initialLayerThickness'] - 1) / settings['layerThickness'] + 1
496 if settings['fanFullOnLayerNr'] < 0:
497 settings['fanFullOnLayerNr'] = 0
498 if profile.getProfileSetting('support_type') == 'Lines':
499 settings['supportType'] = 1
501 if profile.getProfileSettingFloat('fill_density') == 0:
502 settings['sparseInfillLineDistance'] = -1
503 elif profile.getProfileSettingFloat('fill_density') == 100:
504 settings['sparseInfillLineDistance'] = settings['extrusionWidth']
505 #Set the up/down skins height to 10000 if we want a 100% filled object.
506 # This gives better results then normal 100% infill as the sparse and up/down skin have some overlap.
507 settings['downSkinCount'] = 10000
508 settings['upSkinCount'] = 10000
510 settings['sparseInfillLineDistance'] = int(100 * profile.calculateEdgeWidth() * 1000 / profile.getProfileSettingFloat('fill_density'))
511 if profile.getProfileSetting('platform_adhesion') == 'Brim':
512 settings['skirtDistance'] = 0
513 settings['skirtLineCount'] = int(profile.getProfileSettingFloat('brim_line_count'))
514 elif profile.getProfileSetting('platform_adhesion') == 'Raft':
515 settings['skirtDistance'] = 0
516 settings['skirtLineCount'] = 0
517 settings['raftMargin'] = int(profile.getProfileSettingFloat('raft_margin') * 1000)
518 settings['raftLineSpacing'] = int(profile.getProfileSettingFloat('raft_line_spacing') * 1000)
519 settings['raftBaseThickness'] = int(profile.getProfileSettingFloat('raft_base_thickness') * 1000)
520 settings['raftBaseLinewidth'] = int(profile.getProfileSettingFloat('raft_base_linewidth') * 1000)
521 settings['raftInterfaceThickness'] = int(profile.getProfileSettingFloat('raft_interface_thickness') * 1000)
522 settings['raftInterfaceLinewidth'] = int(profile.getProfileSettingFloat('raft_interface_linewidth') * 1000)
523 settings['raftAirGap'] = int(profile.getProfileSettingFloat('raft_airgap') * 1000)
524 settings['raftBaseSpeed'] = int(profile.getProfileSettingFloat('bottom_layer_speed'))
525 settings['raftFanSpeed'] = 0
526 settings['raftSurfaceThickness'] = 200
527 settings['raftSurfaceLinewidth'] = int(profile.calculateEdgeWidth() * 1000)
528 settings['raftSurfaceLineSpacing'] = int(profile.calculateEdgeWidth() * 1000)
529 settings['raftSurfaceLayers'] = int(profile.getProfileSettingFloat('raft_surface_layers'))
530 settings['raftSurfaceSpeed'] = int(profile.getProfileSettingFloat('bottom_layer_speed'))
532 settings['skirtDistance'] = int(profile.getProfileSettingFloat('skirt_gap') * 1000)
533 settings['skirtLineCount'] = int(profile.getProfileSettingFloat('skirt_line_count'))
534 settings['skirtMinLength'] = int(profile.getProfileSettingFloat('skirt_minimal_length') * 1000)
536 if profile.getProfileSetting('fix_horrible_union_all_type_a') == 'True':
537 settings['fixHorrible'] |= 0x01
538 if profile.getProfileSetting('fix_horrible_union_all_type_b') == 'True':
539 settings['fixHorrible'] |= 0x02
540 if profile.getProfileSetting('fix_horrible_use_open_bits') == 'True':
541 settings['fixHorrible'] |= 0x10
542 if profile.getProfileSetting('fix_horrible_extensive_stitching') == 'True':
543 settings['fixHorrible'] |= 0x04
545 if settings['layerThickness'] <= 0:
546 settings['layerThickness'] = 1000
547 if profile.getMachineSetting('gcode_flavor') == 'UltiGCode':
548 settings['gcodeFlavor'] = 1
549 elif profile.getMachineSetting('gcode_flavor') == 'MakerBot':
550 settings['gcodeFlavor'] = 2
551 elif profile.getMachineSetting('gcode_flavor') == 'BFB':
552 settings['gcodeFlavor'] = 3
553 elif profile.getMachineSetting('gcode_flavor') == 'Mach3':
554 settings['gcodeFlavor'] = 4
555 elif profile.getMachineSetting('gcode_flavor') == 'RepRap (Volumetric)':
556 settings['gcodeFlavor'] = 5
557 if profile.getProfileSetting('spiralize') == 'True':
558 settings['spiralizeMode'] = 1
559 if profile.getProfileSetting('wipe_tower') == 'True' and extruderCount > 1:
560 settings['wipeTowerSize'] = int(math.sqrt(profile.getProfileSettingFloat('wipe_tower_volume') * 1000 * 1000 * 1000 / settings['layerThickness']))
561 if profile.getProfileSetting('ooze_shield') == 'True':
562 settings['enableOozeShield'] = 1
565 def _runEngineProcess(self, cmdList):
567 if subprocess.mswindows:
568 su = subprocess.STARTUPINFO()
569 su.dwFlags |= subprocess.STARTF_USESHOWWINDOW
570 su.wShowWindow = subprocess.SW_HIDE
571 kwargs['startupinfo'] = su
572 kwargs['creationflags'] = 0x00004000 #BELOW_NORMAL_PRIORITY_CLASS
573 return subprocess.Popen(cmdList, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs)