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