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