chiark / gitweb /
Merge branch 'SteamEngine' of github.com:daid/Cura into SteamEngine
[cura.git] / Cura / util / sliceEngine.py
1 """
2 Slice engine communication.
3 This module handles all communication with the slicing engine.
4 """
5 __copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License"
6 import subprocess
7 import time
8 import math
9 import numpy
10 import os
11 import warnings
12 import threading
13 import traceback
14 import platform
15 import sys
16 import urllib
17 import urllib2
18 import hashlib
19 import socket
20 import struct
21 import cStringIO as StringIO
22
23 from Cura.util import profile
24 from Cura.util import pluginInfo
25 from Cura.util import version
26 from Cura.util import gcodeInterpreter
27
28 def getEngineFilename():
29         """
30                 Finds and returns the path to the current engine executable. This is OS depended.
31         :return: The full path to the engine executable.
32         """
33         if platform.system() == 'Windows':
34                 if version.isDevVersion() and os.path.exists('C:/Software/Cura_SteamEngine/_bin/Release/Cura_SteamEngine.exe'):
35                         return 'C:/Software/Cura_SteamEngine/_bin/Release/Cura_SteamEngine.exe'
36                 return os.path.abspath(os.path.join(os.path.dirname(__file__), '../..', 'CuraEngine.exe'))
37         if hasattr(sys, 'frozen'):
38                 return os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../../..', 'CuraEngine'))
39         if os.path.isfile('/usr/bin/CuraEngine'):
40                 return '/usr/bin/CuraEngine'
41         if os.path.isfile('/usr/local/bin/CuraEngine'):
42                 return '/usr/local/bin/CuraEngine'
43         return os.path.abspath(os.path.join(os.path.dirname(__file__), '../..', 'CuraEngine'))
44
45 class EngineResult(object):
46         """
47         Result from running the CuraEngine.
48         Contains the engine log, polygons retrieved from the engine, the GCode and some meta-data.
49         """
50         def __init__(self):
51                 self._engineLog = []
52                 self._gcodeData = StringIO.StringIO()
53                 self._polygons = []
54                 self._replaceInfo = {}
55                 self._success = False
56                 self._printTimeSeconds = None
57                 self._filamentMM = [0.0] * 4
58                 self._modelHash = None
59                 self._profileString = profile.getProfileString()
60                 self._preferencesString = profile.getPreferencesString()
61                 self._gcodeInterpreter = gcodeInterpreter.gcode()
62                 self._gcodeLoadThread = None
63                 self._finished = False
64
65         def getFilamentWeight(self, e=0):
66                 #Calculates the weight of the filament in kg
67                 radius = float(profile.getProfileSetting('filament_diameter')) / 2
68                 volumeM3 = (self._filamentMM[e] * (math.pi * radius * radius)) / (1000*1000*1000)
69                 return volumeM3 * profile.getPreferenceFloat('filament_physical_density')
70
71         def getFilamentCost(self, e=0):
72                 cost_kg = profile.getPreferenceFloat('filament_cost_kg')
73                 cost_meter = profile.getPreferenceFloat('filament_cost_meter')
74                 if cost_kg > 0.0 and cost_meter > 0.0:
75                         return "%.2f / %.2f" % (self.getFilamentWeight(e) * cost_kg, self._filamentMM[e] / 1000.0 * cost_meter)
76                 elif cost_kg > 0.0:
77                         return "%.2f" % (self.getFilamentWeight(e) * cost_kg)
78                 elif cost_meter > 0.0:
79                         return "%.2f" % (self._filamentMM[e] / 1000.0 * cost_meter)
80                 return None
81
82         def getPrintTime(self):
83                 if int(self._printTimeSeconds / 60 / 60) < 1:
84                         return '%d minutes' % (int(self._printTimeSeconds / 60) % 60)
85                 if int(self._printTimeSeconds / 60 / 60) == 1:
86                         return '%d hour %d minutes' % (int(self._printTimeSeconds / 60 / 60), int(self._printTimeSeconds / 60) % 60)
87                 return '%d hours %d minutes' % (int(self._printTimeSeconds / 60 / 60), int(self._printTimeSeconds / 60) % 60)
88
89         def getFilamentAmount(self, e=0):
90                 if self._filamentMM[e] == 0.0:
91                         return None
92                 return '%0.2f meter %0.0f gram' % (float(self._filamentMM[e]) / 1000.0, self.getFilamentWeight(e) * 1000.0)
93
94         def getLog(self):
95                 return self._engineLog
96
97         def getGCode(self):
98                 data = self._gcodeData.getvalue()
99                 if len(self._replaceInfo) > 0:
100                         block0 = data[0:2048]
101                         for k, v in self._replaceInfo.items():
102                                 v = (v + ' ' * len(k))[:len(k)]
103                                 block0 = block0.replace(k, v)
104                         return block0 + data[2048:]
105                 return data
106
107         def setGCode(self, gcode):
108                 self._gcodeData = StringIO.StringIO(gcode)
109                 self._replaceInfo = {}
110
111         def addLog(self, line):
112                 self._engineLog.append(line)
113
114         def setHash(self, hash):
115                 self._modelHash = hash
116
117         def setFinished(self, result):
118                 self._finished = result
119
120         def isFinished(self):
121                 return self._finished
122
123         def getGCodeLayers(self, loadCallback):
124                 if not self._finished:
125                         return None
126                 if self._gcodeInterpreter.layerList is None and self._gcodeLoadThread is None:
127                         self._gcodeInterpreter.progressCallback = self._gcodeInterpreterCallback
128                         self._gcodeLoadThread = threading.Thread(target=lambda : self._gcodeInterpreter.load(self._gcodeData))
129                         self._gcodeLoadCallback = loadCallback
130                         self._gcodeLoadThread.daemon = True
131                         self._gcodeLoadThread.start()
132                 return self._gcodeInterpreter.layerList
133
134         def _gcodeInterpreterCallback(self, progress):
135                 if len(self._gcodeInterpreter.layerList) % 5 == 0:
136                         time.sleep(0.1)
137                 return self._gcodeLoadCallback(self, progress)
138
139         def submitInfoOnline(self):
140                 if profile.getPreference('submit_slice_information') != 'True':
141                         return
142                 if version.isDevVersion():
143                         return
144                 data = {
145                         'processor': platform.processor(),
146                         'machine': platform.machine(),
147                         'platform': platform.platform(),
148                         'profile': self._profileString,
149                         'preferences': self._preferencesString,
150                         'modelhash': self._modelHash,
151                         'version': version.getVersion(),
152                 }
153                 try:
154                         f = urllib2.urlopen("http://www.youmagine.com/curastats/", data = urllib.urlencode(data), timeout = 1)
155                         f.read()
156                         f.close()
157                 except:
158                         pass
159
160 class Engine(object):
161         """
162         Class used to communicate with the CuraEngine.
163         The CuraEngine is ran as a 2nd process and reports back information trough stderr.
164         GCode trough stdout and has a socket connection for polygon information and loading the 3D model into the engine.
165         """
166         GUI_CMD_REQUEST_MESH = 0x01
167         GUI_CMD_SEND_POLYGONS = 0x02
168
169         def __init__(self, progressCallback):
170                 self._process = None
171                 self._thread = None
172                 self._callback = progressCallback
173                 self._progressSteps = ['inset', 'skin', 'export']
174                 self._objCount = 0
175                 self._result = None
176
177                 self._serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
178                 self._serverPortNr = 0xC20A
179                 while True:
180                         try:
181                                 self._serversocket.bind(('127.0.0.1', self._serverPortNr))
182                         except:
183                                 print "Failed to listen on port: %d" % (self._serverPortNr)
184                                 self._serverPortNr += 1
185                                 if self._serverPortNr > 0xFFFF:
186                                         print "Failed to listen on any port..."
187                                         break
188                         else:
189                                 break
190                 print 'Listening for engine communications on %d' % (self._serverPortNr)
191                 self._serversocket.listen(1)
192                 thread = threading.Thread(target=self._socketListenThread)
193                 thread.daemon = True
194                 thread.start()
195
196         def _socketListenThread(self):
197                 while True:
198                         sock, _ = self._serversocket.accept()
199                         thread = threading.Thread(target=self._socketConnectionThread, args=(sock,))
200                         thread.daemon = True
201                         thread.start()
202
203         def _socketConnectionThread(self, sock):
204                 while True:
205                         try:
206                                 data = sock.recv(4)
207                         except:
208                                 data = ''
209                         if len(data) == 0:
210                                 sock.close()
211                                 return
212                         cmd = struct.unpack('@i', data)[0]
213                         if cmd == self.GUI_CMD_REQUEST_MESH:
214                                 meshInfo = self._modelData[0]
215                                 self._modelData = self._modelData[1:]
216                                 sock.sendall(struct.pack('@i', meshInfo[0]))
217                                 sock.sendall(meshInfo[1].tostring())
218                         elif cmd == self.GUI_CMD_SEND_POLYGONS:
219                                 cnt = struct.unpack('@i', sock.recv(4))[0]
220                                 layerNr = struct.unpack('@i', sock.recv(4))[0]
221                                 z = struct.unpack('@i', sock.recv(4))[0]
222                                 z = float(z) / 1000.0
223                                 typeNameLen = struct.unpack('@i', sock.recv(4))[0]
224                                 typeName = sock.recv(typeNameLen)
225                                 while len(self._result._polygons) < layerNr + 1:
226                                         self._result._polygons.append({})
227                                 polygons = self._result._polygons[layerNr]
228                                 if typeName not in polygons:
229                                         polygons[typeName] = []
230                                 for n in xrange(0, cnt):
231                                         length = struct.unpack('@i', sock.recv(4))[0]
232                                         data = ''
233                                         while len(data) < length * 8 * 2:
234                                                 recvData = sock.recv(length * 8 * 2 - len(data))
235                                                 if len(recvData) < 1:
236                                                         return
237                                                 data += recvData
238                                         polygon2d = numpy.array(numpy.fromstring(data, numpy.int64), numpy.float32) / 1000.0
239                                         polygon2d = polygon2d.reshape((len(polygon2d) / 2, 2))
240                                         polygon = numpy.empty((len(polygon2d), 3), numpy.float32)
241                                         polygon[:,:-1] = polygon2d
242                                         polygon[:,2] = z
243                                         polygons[typeName].append(polygon)
244                         else:
245                                 print "Unknown command on socket: %x" % (cmd)
246
247         def cleanup(self):
248                 self.abortEngine()
249                 self._serversocket.close()
250
251         def abortEngine(self):
252                 if self._process is not None:
253                         try:
254                                 self._process.terminate()
255                         except:
256                                 pass
257                 if self._thread is not None:
258                         self._thread.join()
259                 self._thread = None
260
261         def wait(self):
262                 if self._thread is not None:
263                         self._thread.join()
264
265         def getResult(self):
266                 return self._result
267
268         def runEngine(self, scene):
269                 if len(scene.objects()) < 1:
270                         return
271                 extruderCount = 1
272                 for obj in scene.objects():
273                         if scene.checkPlatform(obj):
274                                 extruderCount = max(extruderCount, len(obj._meshList))
275
276                 extruderCount = max(extruderCount, profile.minimalExtruderCount())
277
278                 commandList = [getEngineFilename(), '-v', '-p']
279                 for k, v in self._engineSettings(extruderCount).iteritems():
280                         commandList += ['-s', '%s=%s' % (k, str(v))]
281                 commandList += ['-g', '%d' % (self._serverPortNr)]
282                 self._objCount = 0
283                 engineModelData = []
284                 hash = hashlib.sha512()
285                 order = scene.printOrder()
286                 if order is None:
287                         pos = numpy.array(profile.getMachineCenterCoords()) * 1000
288                         objMin = None
289                         objMax = None
290                         for obj in scene.objects():
291                                 if scene.checkPlatform(obj):
292                                         oMin = obj.getMinimum()[0:2] + obj.getPosition()
293                                         oMax = obj.getMaximum()[0:2] + obj.getPosition()
294                                         if objMin is None:
295                                                 objMin = oMin
296                                                 objMax = oMax
297                                         else:
298                                                 objMin[0] = min(oMin[0], objMin[0])
299                                                 objMin[1] = min(oMin[1], objMin[1])
300                                                 objMax[0] = max(oMax[0], objMax[0])
301                                                 objMax[1] = max(oMax[1], objMax[1])
302                         if objMin is None:
303                                 return
304                         pos += (objMin + objMax) / 2.0 * 1000
305                         commandList += ['-s', 'posx=%d' % int(pos[0]), '-s', 'posy=%d' % int(pos[1])]
306
307                         vertexTotal = [0] * 4
308                         meshMax = 1
309                         for obj in scene.objects():
310                                 if scene.checkPlatform(obj):
311                                         meshMax = max(meshMax, len(obj._meshList))
312                                         for n in xrange(0, len(obj._meshList)):
313                                                 vertexTotal[n] += obj._meshList[n].vertexCount
314
315                         for n in xrange(0, meshMax):
316                                 verts = numpy.zeros((0, 3), numpy.float32)
317                                 for obj in scene.objects():
318                                         if scene.checkPlatform(obj):
319                                                 if n < len(obj._meshList):
320                                                         vertexes = (numpy.matrix(obj._meshList[n].vertexes, copy = False) * numpy.matrix(obj._matrix, numpy.float32)).getA()
321                                                         vertexes -= obj._drawOffset
322                                                         vertexes += numpy.array([obj.getPosition()[0], obj.getPosition()[1], 0.0])
323                                                         verts = numpy.concatenate((verts, vertexes))
324                                                         hash.update(obj._meshList[n].vertexes.tostring())
325                                 engineModelData.append((vertexTotal[n], verts))
326
327                         commandList += ['$' * meshMax]
328                         self._objCount = 1
329                 else:
330                         for n in order:
331                                 obj = scene.objects()[n]
332                                 for mesh in obj._meshList:
333                                         engineModelData.append((mesh.vertexCount, mesh.vertexes))
334                                         hash.update(mesh.vertexes.tostring())
335                                 pos = obj.getPosition() * 1000
336                                 pos += numpy.array(profile.getMachineCenterCoords()) * 1000
337                                 commandList += ['-m', ','.join(map(str, obj._matrix.getA().flatten()))]
338                                 commandList += ['-s', 'posx=%d' % int(pos[0]), '-s', 'posy=%d' % int(pos[1])]
339                                 commandList += ['$' * len(obj._meshList)]
340                                 self._objCount += 1
341                 modelHash = hash.hexdigest()
342                 if self._objCount > 0:
343                         self._modelData = engineModelData
344                         self._thread = threading.Thread(target=self._watchProcess, args=(commandList, self._thread, modelHash))
345                         self._thread.daemon = True
346                         self._thread.start()
347
348         def _watchProcess(self, commandList, oldThread, modelHash):
349                 if oldThread is not None:
350                         if self._process is not None:
351                                 self._process.terminate()
352                         oldThread.join()
353                 self._callback(-1.0)
354                 try:
355                         self._process = self._runEngineProcess(commandList)
356                 except OSError:
357                         traceback.print_exc()
358                         return
359                 if self._thread != threading.currentThread():
360                         self._process.terminate()
361
362                 self._result = EngineResult()
363                 self._result.setHash(modelHash)
364                 self._callback(0.0)
365
366                 logThread = threading.Thread(target=self._watchStderr, args=(self._process.stderr,))
367                 logThread.daemon = True
368                 logThread.start()
369
370                 data = self._process.stdout.read(4096)
371                 while len(data) > 0:
372                         self._result._gcodeData.write(data)
373                         data = self._process.stdout.read(4096)
374
375                 returnCode = self._process.wait()
376                 logThread.join()
377                 if returnCode == 0:
378                         pluginError = pluginInfo.runPostProcessingPlugins(self._result)
379                         if pluginError is not None:
380                                 print pluginError
381                                 self._result.addLog(pluginError)
382                         self._result.setFinished(True)
383                         self._callback(1.0)
384                 else:
385                         for line in self._result.getLog():
386                                 print line
387                         self._callback(-1.0)
388                 self._process = None
389
390         def _watchStderr(self, stderr):
391                 objectNr = 0
392                 line = stderr.readline()
393                 while len(line) > 0:
394                         line = line.strip()
395                         if line.startswith('Progress:'):
396                                 line = line.split(':')
397                                 if line[1] == 'process':
398                                         objectNr += 1
399                                 elif line[1] in self._progressSteps:
400                                         progressValue = float(line[2]) / float(line[3])
401                                         progressValue /= len(self._progressSteps)
402                                         progressValue += 1.0 / len(self._progressSteps) * self._progressSteps.index(line[1])
403
404                                         progressValue /= self._objCount
405                                         progressValue += 1.0 / self._objCount * objectNr
406                                         try:
407                                                 self._callback(progressValue)
408                                         except:
409                                                 pass
410                         elif line.startswith('Print time:'):
411                                 self._result._printTimeSeconds = int(line.split(':')[1].strip())
412                         elif line.startswith('Filament:'):
413                                 self._result._filamentMM[0] = int(line.split(':')[1].strip())
414                                 if profile.getMachineSetting('gcode_flavor') == 'UltiGCode':
415                                         radius = profile.getProfileSettingFloat('filament_diameter') / 2.0
416                                         self._result._filamentMM[0] /= (math.pi * radius * radius)
417                         elif line.startswith('Filament2:'):
418                                 self._result._filamentMM[1] = int(line.split(':')[1].strip())
419                                 if profile.getMachineSetting('gcode_flavor') == 'UltiGCode':
420                                         radius = profile.getProfileSettingFloat('filament_diameter') / 2.0
421                                         self._result._filamentMM[1] /= (math.pi * radius * radius)
422                         elif line.startswith('Replace:'):
423                                 self._result._replaceInfo[line.split(':')[1].strip()] = line.split(':')[2].strip()
424                         else:
425                                 self._result.addLog(line)
426                         line = stderr.readline()
427
428         def _engineSettings(self, extruderCount):
429                 settings = {
430                         'layerThickness': int(profile.getProfileSettingFloat('layer_height') * 1000),
431                         'initialLayerThickness': int(profile.getProfileSettingFloat('bottom_thickness') * 1000) if profile.getProfileSettingFloat('bottom_thickness') > 0.0 else int(profile.getProfileSettingFloat('layer_height') * 1000),
432                         'filamentDiameter': int(profile.getProfileSettingFloat('filament_diameter') * 1000),
433                         'filamentFlow': int(profile.getProfileSettingFloat('filament_flow')),
434                         'extrusionWidth': int(profile.calculateEdgeWidth() * 1000),
435                         'insetCount': int(profile.calculateLineCount()),
436                         'downSkinCount': int(profile.calculateSolidLayerCount()) if profile.getProfileSetting('solid_bottom') == 'True' else 0,
437                         'upSkinCount': int(profile.calculateSolidLayerCount()) if profile.getProfileSetting('solid_top') == 'True' else 0,
438                         'infillOverlap': int(profile.getProfileSettingFloat('fill_overlap')),
439                         'initialSpeedupLayers': int(4),
440                         'initialLayerSpeed': int(profile.getProfileSettingFloat('bottom_layer_speed')),
441                         'printSpeed': int(profile.getProfileSettingFloat('print_speed')),
442                         'infillSpeed': int(profile.getProfileSettingFloat('infill_speed')) if int(profile.getProfileSettingFloat('infill_speed')) > 0 else int(profile.getProfileSettingFloat('print_speed')),
443                         'inset0Speed': int(profile.getProfileSettingFloat('inset0_speed')) if int(profile.getProfileSettingFloat('inset0_speed')) > 0 else int(profile.getProfileSettingFloat('print_speed')),
444                         'insetXSpeed': int(profile.getProfileSettingFloat('insetx_speed')) if int(profile.getProfileSettingFloat('insetx_speed')) > 0 else int(profile.getProfileSettingFloat('print_speed')),
445                         'moveSpeed': int(profile.getProfileSettingFloat('travel_speed')),
446                         'fanSpeedMin': int(profile.getProfileSettingFloat('fan_speed')) if profile.getProfileSetting('fan_enabled') == 'True' else 0,
447                         'fanSpeedMax': int(profile.getProfileSettingFloat('fan_speed_max')) if profile.getProfileSetting('fan_enabled') == 'True' else 0,
448                         'supportAngle': int(-1) if profile.getProfileSetting('support') == 'None' else int(profile.getProfileSettingFloat('support_angle')),
449                         'supportEverywhere': int(1) if profile.getProfileSetting('support') == 'Everywhere' else int(0),
450                         'supportLineDistance': int(100 * profile.calculateEdgeWidth() * 1000 / profile.getProfileSettingFloat('support_fill_rate')) if profile.getProfileSettingFloat('support_fill_rate') > 0 else -1,
451                         'supportXYDistance': int(1000 * profile.getProfileSettingFloat('support_xy_distance')),
452                         'supportZDistance': int(1000 * profile.getProfileSettingFloat('support_z_distance')),
453                         '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),
454                         'retractionAmount': int(profile.getProfileSettingFloat('retraction_amount') * 1000) if profile.getProfileSetting('retraction_enable') == 'True' else 0,
455                         'retractionSpeed': int(profile.getProfileSettingFloat('retraction_speed')),
456                         'retractionMinimalDistance': int(profile.getProfileSettingFloat('retraction_min_travel') * 1000),
457                         'retractionAmountExtruderSwitch': int(profile.getProfileSettingFloat('retraction_dual_amount') * 1000),
458                         'retractionZHop': int(profile.getProfileSettingFloat('retraction_hop') * 1000),
459                         'minimalExtrusionBeforeRetraction': int(profile.getProfileSettingFloat('retraction_minimal_extrusion') * 1000),
460                         'enableCombing': 1 if profile.getProfileSetting('retraction_combing') == 'True' else 0,
461                         'multiVolumeOverlap': int(profile.getProfileSettingFloat('overlap_dual') * 1000),
462                         'objectSink': max(0, int(profile.getProfileSettingFloat('object_sink') * 1000)),
463                         'minimalLayerTime': int(profile.getProfileSettingFloat('cool_min_layer_time')),
464                         'minimalFeedrate': int(profile.getProfileSettingFloat('cool_min_feedrate')),
465                         'coolHeadLift': 1 if profile.getProfileSetting('cool_head_lift') == 'True' else 0,
466                         'startCode': profile.getAlterationFileContents('start.gcode', extruderCount),
467                         'endCode': profile.getAlterationFileContents('end.gcode', extruderCount),
468
469                         'extruderOffset[1].X': int(profile.getMachineSettingFloat('extruder_offset_x1') * 1000),
470                         'extruderOffset[1].Y': int(profile.getMachineSettingFloat('extruder_offset_y1') * 1000),
471                         'extruderOffset[2].X': int(profile.getMachineSettingFloat('extruder_offset_x2') * 1000),
472                         'extruderOffset[2].Y': int(profile.getMachineSettingFloat('extruder_offset_y2') * 1000),
473                         'extruderOffset[3].X': int(profile.getMachineSettingFloat('extruder_offset_x3') * 1000),
474                         'extruderOffset[3].Y': int(profile.getMachineSettingFloat('extruder_offset_y3') * 1000),
475                         'fixHorrible': 0,
476                 }
477                 fanFullHeight = int(profile.getProfileSettingFloat('fan_full_height') * 1000)
478                 settings['fanFullOnLayerNr'] = (fanFullHeight - settings['initialLayerThickness'] - 1) / settings['layerThickness'] + 1
479                 if settings['fanFullOnLayerNr'] < 0:
480                         settings['fanFullOnLayerNr'] = 0
481                 if profile.getProfileSetting('support_type') == 'Lines':
482                         settings['supportType'] = 1
483
484                 if profile.getProfileSettingFloat('fill_density') == 0:
485                         settings['sparseInfillLineDistance'] = -1
486                 elif profile.getProfileSettingFloat('fill_density') == 100:
487                         settings['sparseInfillLineDistance'] = settings['extrusionWidth']
488                         #Set the up/down skins height to 10000 if we want a 100% filled object.
489                         # This gives better results then normal 100% infill as the sparse and up/down skin have some overlap.
490                         settings['downSkinCount'] = 10000
491                         settings['upSkinCount'] = 10000
492                 else:
493                         settings['sparseInfillLineDistance'] = int(100 * profile.calculateEdgeWidth() * 1000 / profile.getProfileSettingFloat('fill_density'))
494                 if profile.getProfileSetting('platform_adhesion') == 'Brim':
495                         settings['skirtDistance'] = 0
496                         settings['skirtLineCount'] = int(profile.getProfileSettingFloat('brim_line_count'))
497                 elif profile.getProfileSetting('platform_adhesion') == 'Raft':
498                         settings['skirtDistance'] = 0
499                         settings['skirtLineCount'] = 0
500                         settings['raftMargin'] = int(profile.getProfileSettingFloat('raft_margin') * 1000)
501                         settings['raftLineSpacing'] = int(profile.getProfileSettingFloat('raft_line_spacing') * 1000)
502                         settings['raftBaseThickness'] = int(profile.getProfileSettingFloat('raft_base_thickness') * 1000)
503                         settings['raftBaseLinewidth'] = int(profile.getProfileSettingFloat('raft_base_linewidth') * 1000)
504                         settings['raftInterfaceThickness'] = int(profile.getProfileSettingFloat('raft_interface_thickness') * 1000)
505                         settings['raftInterfaceLinewidth'] = int(profile.getProfileSettingFloat('raft_interface_linewidth') * 1000)
506                 else:
507                         settings['skirtDistance'] = int(profile.getProfileSettingFloat('skirt_gap') * 1000)
508                         settings['skirtLineCount'] = int(profile.getProfileSettingFloat('skirt_line_count'))
509                         settings['skirtMinLength'] = int(profile.getProfileSettingFloat('skirt_minimal_length') * 1000)
510
511                 if profile.getProfileSetting('fix_horrible_union_all_type_a') == 'True':
512                         settings['fixHorrible'] |= 0x01
513                 if profile.getProfileSetting('fix_horrible_union_all_type_b') == 'True':
514                         settings['fixHorrible'] |= 0x02
515                 if profile.getProfileSetting('fix_horrible_use_open_bits') == 'True':
516                         settings['fixHorrible'] |= 0x10
517                 if profile.getProfileSetting('fix_horrible_extensive_stitching') == 'True':
518                         settings['fixHorrible'] |= 0x04
519
520                 if settings['layerThickness'] <= 0:
521                         settings['layerThickness'] = 1000
522                 if profile.getMachineSetting('gcode_flavor') == 'UltiGCode':
523                         settings['gcodeFlavor'] = 1
524                 if profile.getProfileSetting('spiralize') == 'True':
525                         settings['spiralizeMode'] = 1
526                 if profile.getProfileSetting('wipe_tower') == 'True' and extruderCount > 1:
527                         settings['wipeTowerSize'] = int(math.sqrt(profile.getProfileSettingFloat('wipe_tower_volume') * 1000 * 1000 * 1000 / settings['layerThickness']))
528                 if profile.getProfileSetting('ooze_shield') == 'True':
529                         settings['enableOozeShield'] = 1
530                 return settings
531
532         def _runEngineProcess(self, cmdList):
533                 kwargs = {}
534                 if subprocess.mswindows:
535                         su = subprocess.STARTUPINFO()
536                         su.dwFlags |= subprocess.STARTF_USESHOWWINDOW
537                         su.wShowWindow = subprocess.SW_HIDE
538                         kwargs['startupinfo'] = su
539                         kwargs['creationflags'] = 0x00004000 #BELOW_NORMAL_PRIORITY_CLASS
540                 return subprocess.Popen(cmdList, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs)