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