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