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