chiark / gitweb /
Merge tag '15.01-RC2' into upstream
[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.wait()
269                 self._serversocket.close()
270
271         def abortEngine(self):
272                 if self._process is not None:
273                         try:
274                                 self._process.terminate()
275                         except:
276                                 pass
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                 self._thread = threading.Thread(target=self._runEngine, args=(scene, self._thread, pluginInfo.getPostProcessPluginConfig()))
289                 self._thread.daemon = True
290                 self._thread.start()
291
292         def _runEngine(self, scene, old_thread, pluginConfig):
293                 if old_thread is not None:
294                         if self._process is not None:
295                                 self._process.terminate()
296                         old_thread.join()
297                 self._callback(-1.0)
298
299                 extruderCount = 1
300                 for obj in scene.objects():
301                         if scene.checkPlatform(obj):
302                                 extruderCount = max(extruderCount, len(obj._meshList))
303
304                 extruderCount = max(extruderCount, profile.minimalExtruderCount())
305
306                 commandList = [self._engine_executable, '-v', '-p']
307                 for k, v in self._engineSettings(extruderCount).iteritems():
308                         commandList += ['-s', '%s=%s' % (k, str(v))]
309                 commandList += ['-g', '%d' % (self._serverPortNr)]
310                 self._objCount = 0
311                 engineModelData = []
312                 hash = hashlib.sha512()
313                 order = scene.printOrder()
314                 if order is None:
315                         pos = numpy.array(profile.getMachineCenterCoords()) * 1000
316                         objMin = None
317                         objMax = None
318                         for obj in scene.objects():
319                                 if scene.checkPlatform(obj):
320                                         oMin = obj.getMinimum()[0:2] + obj.getPosition()
321                                         oMax = obj.getMaximum()[0:2] + obj.getPosition()
322                                         if objMin is None:
323                                                 objMin = oMin
324                                                 objMax = oMax
325                                         else:
326                                                 objMin[0] = min(oMin[0], objMin[0])
327                                                 objMin[1] = min(oMin[1], objMin[1])
328                                                 objMax[0] = max(oMax[0], objMax[0])
329                                                 objMax[1] = max(oMax[1], objMax[1])
330                         if objMin is None:
331                                 return
332                         pos += (objMin + objMax) / 2.0 * 1000
333                         commandList += ['-s', 'posx=%d' % int(pos[0]), '-s', 'posy=%d' % int(pos[1])]
334
335                         vertexTotal = [0] * 4
336                         meshMax = 1
337                         for obj in scene.objects():
338                                 if scene.checkPlatform(obj):
339                                         meshMax = max(meshMax, len(obj._meshList))
340                                         for n in xrange(0, len(obj._meshList)):
341                                                 vertexTotal[n] += obj._meshList[n].vertexCount
342
343                         for n in xrange(0, meshMax):
344                                 verts = numpy.zeros((0, 3), numpy.float32)
345                                 for obj in scene.objects():
346                                         if scene.checkPlatform(obj):
347                                                 if n < len(obj._meshList):
348                                                         vertexes = (numpy.matrix(obj._meshList[n].vertexes, copy = False) * numpy.matrix(obj._matrix, numpy.float32)).getA()
349                                                         vertexes -= obj._drawOffset
350                                                         vertexes += numpy.array([obj.getPosition()[0], obj.getPosition()[1], 0.0])
351                                                         verts = numpy.concatenate((verts, vertexes))
352                                                         hash.update(obj._meshList[n].vertexes.tostring())
353                                 engineModelData.append((vertexTotal[n], verts))
354
355                         commandList += ['$' * meshMax]
356                         self._objCount = 1
357                 else:
358                         for n in order:
359                                 obj = scene.objects()[n]
360                                 for mesh in obj._meshList:
361                                         engineModelData.append((mesh.vertexCount, mesh.vertexes))
362                                         hash.update(mesh.vertexes.tostring())
363                                 pos = obj.getPosition() * 1000
364                                 pos += numpy.array(profile.getMachineCenterCoords()) * 1000
365                                 commandList += ['-m', ','.join(map(str, obj._matrix.getA().flatten()))]
366                                 commandList += ['-s', 'posx=%d' % int(pos[0]), '-s', 'posy=%d' % int(pos[1])]
367                                 commandList += ['$' * len(obj._meshList)]
368                                 self._objCount += 1
369                 modelHash = hash.hexdigest()
370                 if self._objCount < 1:
371                         return
372                 if self._thread != threading.currentThread():
373                         return
374
375                 self._modelData = engineModelData
376                 try:
377                         self._process = self._runEngineProcess(commandList)
378                 except OSError:
379                         traceback.print_exc()
380                         return
381
382                 self._result = EngineResult()
383                 self._result.addLog('Running: %s' % (' '.join(commandList)))
384                 self._result.setHash(modelHash)
385                 self._callback(0.0)
386
387                 logThread = threading.Thread(target=self._watchStderr, args=(self._process.stderr,))
388                 logThread.daemon = True
389                 logThread.start()
390
391                 try:
392                         data = self._process.stdout.read(4096)
393                         while len(data) > 0:
394                                 if self._thread != threading.currentThread():
395                                         self._process.terminate()
396                                 self._result._gcodeData.write(data)
397                                 data = self._process.stdout.read(4096)
398
399                         returnCode = self._process.wait()
400                         logThread.join()
401                         self._result.addLog("Slicer process returned : %d" % returnCode)
402                         if returnCode == 0:
403                                 plugin_error = pluginInfo.runPostProcessingPlugins(self._result, pluginConfig)
404                                 if plugin_error is not None:
405                                         self._result.addLog(plugin_error)
406                                 self._result.setFinished(True)
407                                 self._callback(1.0)
408                         else:
409                                 self._callback(-1.0)
410                         self._process = None
411                 except MemoryError:
412                         self._result.addLog("MemoryError")
413                         self._callback(-1.0)
414                 finally:
415                         try:
416                                 with open(os.path.join(profile.getBasePath(), 'engine.log'), "w") as f:
417                                         for line in self._result.getLog():
418                                                 f.write(line + "\n")
419                         except:
420                                 pass
421
422         def _watchStderr(self, stderr):
423                 objectNr = 0
424                 line = stderr.readline()
425                 while len(line) > 0:
426                         line = line.strip()
427                         if line.startswith('Progress:'):
428                                 line = line.split(':')
429                                 if line[1] == 'process':
430                                         objectNr += 1
431                                 elif line[1] in self._progressSteps:
432                                         progressValue = float(line[2]) / float(line[3])
433                                         progressValue /= len(self._progressSteps)
434                                         progressValue += 1.0 / len(self._progressSteps) * self._progressSteps.index(line[1])
435
436                                         progressValue /= self._objCount
437                                         progressValue += 1.0 / self._objCount * objectNr
438                                         try:
439                                                 self._callback(progressValue)
440                                         except:
441                                                 pass
442                         elif line.startswith('Print time:'):
443                                 self._result._printTimeSeconds = int(line.split(':')[1].strip())
444                         elif line.startswith('Filament:'):
445                                 self._result._filamentMM[0] = int(line.split(':')[1].strip())
446                                 if profile.getMachineSetting('gcode_flavor') == 'UltiGCode':
447                                         radius = profile.getProfileSettingFloat('filament_diameter') / 2.0
448                                         self._result._filamentMM[0] /= (math.pi * radius * radius)
449                         elif line.startswith('Filament2:'):
450                                 self._result._filamentMM[1] = int(line.split(':')[1].strip())
451                                 if profile.getMachineSetting('gcode_flavor') == 'UltiGCode':
452                                         radius = profile.getProfileSettingFloat('filament_diameter') / 2.0
453                                         self._result._filamentMM[1] /= (math.pi * radius * radius)
454                         elif line.startswith('Replace:'):
455                                 self._result._replaceInfo[line.split(':')[1].strip()] = line.split(':')[2].strip()
456                         else:
457                                 self._result.addLog(line)
458                         line = stderr.readline()
459
460         def _engineSettings(self, extruderCount):
461                 settings = {
462                         'layerThickness': int(profile.getProfileSettingFloat('layer_height') * 1000),
463                         'initialLayerThickness': int(profile.getProfileSettingFloat('bottom_thickness') * 1000) if profile.getProfileSettingFloat('bottom_thickness') > 0.0 else int(profile.getProfileSettingFloat('layer_height') * 1000),
464                         'filamentDiameter': int(profile.getProfileSettingFloat('filament_diameter') * 1000),
465                         'filamentFlow': int(profile.getProfileSettingFloat('filament_flow')),
466                         'extrusionWidth': int(profile.calculateEdgeWidth() * 1000),
467                         'layer0extrusionWidth': int(profile.calculateEdgeWidth() * profile.getProfileSettingFloat('layer0_width_factor') / 100 * 1000),
468                         'insetCount': int(profile.calculateLineCount()),
469                         'downSkinCount': int(profile.calculateSolidLayerCount()) if profile.getProfileSetting('solid_bottom') == 'True' else 0,
470                         'upSkinCount': int(profile.calculateSolidLayerCount()) if profile.getProfileSetting('solid_top') == 'True' else 0,
471                         'infillOverlap': int(profile.getProfileSettingFloat('fill_overlap')),
472                         'initialSpeedupLayers': int(4),
473                         'initialLayerSpeed': int(profile.getProfileSettingFloat('bottom_layer_speed')),
474                         'printSpeed': int(profile.getProfileSettingFloat('print_speed')),
475                         'infillSpeed': int(profile.getProfileSettingFloat('infill_speed')) if int(profile.getProfileSettingFloat('infill_speed')) > 0 else int(profile.getProfileSettingFloat('print_speed')),
476                         'inset0Speed': int(profile.getProfileSettingFloat('inset0_speed')) if int(profile.getProfileSettingFloat('inset0_speed')) > 0 else int(profile.getProfileSettingFloat('print_speed')),
477                         'insetXSpeed': int(profile.getProfileSettingFloat('insetx_speed')) if int(profile.getProfileSettingFloat('insetx_speed')) > 0 else int(profile.getProfileSettingFloat('print_speed')),
478                         'moveSpeed': int(profile.getProfileSettingFloat('travel_speed')),
479                         'fanSpeedMin': int(profile.getProfileSettingFloat('fan_speed')) if profile.getProfileSetting('fan_enabled') == 'True' else 0,
480                         'fanSpeedMax': int(profile.getProfileSettingFloat('fan_speed_max')) if profile.getProfileSetting('fan_enabled') == 'True' else 0,
481                         'supportAngle': int(-1) if profile.getProfileSetting('support') == 'None' else int(profile.getProfileSettingFloat('support_angle')),
482                         'supportEverywhere': int(1) if profile.getProfileSetting('support') == 'Everywhere' else int(0),
483                         'supportLineDistance': int(100 * profile.calculateEdgeWidth() * 1000 / profile.getProfileSettingFloat('support_fill_rate')) if profile.getProfileSettingFloat('support_fill_rate') > 0 else -1,
484                         'supportXYDistance': int(1000 * profile.getProfileSettingFloat('support_xy_distance')),
485                         'supportZDistance': int(1000 * profile.getProfileSettingFloat('support_z_distance')),
486                         '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),
487                         'retractionAmount': int(profile.getProfileSettingFloat('retraction_amount') * 1000) if profile.getProfileSetting('retraction_enable') == 'True' else 0,
488                         'retractionSpeed': int(profile.getProfileSettingFloat('retraction_speed')),
489                         'retractionMinimalDistance': int(profile.getProfileSettingFloat('retraction_min_travel') * 1000),
490                         'retractionAmountExtruderSwitch': int(profile.getProfileSettingFloat('retraction_dual_amount') * 1000),
491                         'retractionZHop': int(profile.getProfileSettingFloat('retraction_hop') * 1000),
492                         'minimalExtrusionBeforeRetraction': int(profile.getProfileSettingFloat('retraction_minimal_extrusion') * 1000),
493                         'enableCombing': 1 if profile.getProfileSetting('retraction_combing') == 'True' else 0,
494                         'multiVolumeOverlap': int(profile.getProfileSettingFloat('overlap_dual') * 1000),
495                         'objectSink': max(0, int(profile.getProfileSettingFloat('object_sink') * 1000)),
496                         'minimalLayerTime': int(profile.getProfileSettingFloat('cool_min_layer_time')),
497                         'minimalFeedrate': int(profile.getProfileSettingFloat('cool_min_feedrate')),
498                         'coolHeadLift': 1 if profile.getProfileSetting('cool_head_lift') == 'True' else 0,
499                         'startCode': profile.getAlterationFileContents('start.gcode', extruderCount),
500                         'endCode': profile.getAlterationFileContents('end.gcode', extruderCount),
501                         'preSwitchExtruderCode': profile.getAlterationFileContents('preSwitchExtruder.gcode', extruderCount),
502                         'postSwitchExtruderCode': profile.getAlterationFileContents('postSwitchExtruder.gcode', extruderCount),
503
504                         'extruderOffset[1].X': int(profile.getMachineSettingFloat('extruder_offset_x1') * 1000),
505                         'extruderOffset[1].Y': int(profile.getMachineSettingFloat('extruder_offset_y1') * 1000),
506                         'extruderOffset[2].X': int(profile.getMachineSettingFloat('extruder_offset_x2') * 1000),
507                         'extruderOffset[2].Y': int(profile.getMachineSettingFloat('extruder_offset_y2') * 1000),
508                         'extruderOffset[3].X': int(profile.getMachineSettingFloat('extruder_offset_x3') * 1000),
509                         'extruderOffset[3].Y': int(profile.getMachineSettingFloat('extruder_offset_y3') * 1000),
510                         'zOffset': int(profile.getMachineSettingFloat('extruder_z_offset') * 1000),
511                         'fixHorrible': 0,
512                 }
513                 fanFullHeight = int(profile.getProfileSettingFloat('fan_full_height') * 1000)
514                 settings['fanFullOnLayerNr'] = (fanFullHeight - settings['initialLayerThickness'] - 1) / settings['layerThickness'] + 1
515                 if settings['fanFullOnLayerNr'] < 0:
516                         settings['fanFullOnLayerNr'] = 0
517                 if profile.getProfileSetting('support_type') == 'Lines':
518                         settings['supportType'] = 1
519
520                 if profile.getProfileSettingFloat('fill_density') == 0:
521                         settings['sparseInfillLineDistance'] = -1
522                 elif profile.getProfileSettingFloat('fill_density') == 100:
523                         settings['sparseInfillLineDistance'] = settings['extrusionWidth']
524                         #Set the up/down skins height to 10000 if we want a 100% filled object.
525                         # This gives better results then normal 100% infill as the sparse and up/down skin have some overlap.
526                         settings['downSkinCount'] = 10000
527                         settings['upSkinCount'] = 10000
528                 else:
529                         settings['sparseInfillLineDistance'] = int(100 * profile.calculateEdgeWidth() * 1000 / profile.getProfileSettingFloat('fill_density'))
530                 if profile.getProfileSetting('platform_adhesion') == 'Brim':
531                         settings['skirtDistance'] = 0
532                         settings['skirtLineCount'] = int(profile.getProfileSettingFloat('brim_line_count'))
533                 elif profile.getProfileSetting('platform_adhesion') == 'Raft':
534                         settings['skirtDistance'] = 0
535                         settings['skirtLineCount'] = 0
536                         settings['raftMargin'] = int(profile.getProfileSettingFloat('raft_margin') * 1000)
537                         settings['raftLineSpacing'] = int(profile.getProfileSettingFloat('raft_line_spacing') * 1000)
538                         settings['raftBaseThickness'] = int(profile.getProfileSettingFloat('raft_base_thickness') * 1000)
539                         settings['raftBaseLinewidth'] = int(profile.getProfileSettingFloat('raft_base_linewidth') * 1000)
540                         settings['raftInterfaceThickness'] = int(profile.getProfileSettingFloat('raft_interface_thickness') * 1000)
541                         settings['raftInterfaceLinewidth'] = int(profile.getProfileSettingFloat('raft_interface_linewidth') * 1000)
542                         settings['raftInterfaceLineSpacing'] = int(profile.getProfileSettingFloat('raft_interface_linewidth') * 1000 * 2.0)
543                         settings['raftAirGapLayer0'] = int(profile.getProfileSettingFloat('raft_airgap') * 1000 + profile.getProfileSettingFloat('raft_airgap_all') * 1000)
544                         settings['raftAirGap'] = int(profile.getProfileSettingFloat('raft_airgap_all') * 1000)
545                         settings['raftBaseSpeed'] = int(profile.getProfileSettingFloat('bottom_layer_speed'))
546                         settings['raftFanSpeed'] = 0
547                         settings['raftSurfaceThickness'] = int(profile.getProfileSettingFloat('raft_surface_thickness') * 1000)
548                         settings['raftSurfaceLinewidth'] = int(profile.getProfileSettingFloat('raft_surface_linewidth') * 1000)
549                         settings['raftSurfaceLineSpacing'] = int(profile.getProfileSettingFloat('raft_surface_linewidth') * 1000)
550                         settings['raftSurfaceLayers'] = int(profile.getProfileSettingFloat('raft_surface_layers'))
551                         settings['raftSurfaceSpeed'] = int(profile.getProfileSettingFloat('bottom_layer_speed'))
552                 else:
553                         settings['skirtDistance'] = int(profile.getProfileSettingFloat('skirt_gap') * 1000)
554                         settings['skirtLineCount'] = int(profile.getProfileSettingFloat('skirt_line_count'))
555                         settings['skirtMinLength'] = int(profile.getProfileSettingFloat('skirt_minimal_length') * 1000)
556
557                 if profile.getProfileSetting('fix_horrible_union_all_type_a') == 'True':
558                         settings['fixHorrible'] |= 0x01
559                 if profile.getProfileSetting('fix_horrible_union_all_type_b') == 'True':
560                         settings['fixHorrible'] |= 0x02
561                 if profile.getProfileSetting('fix_horrible_use_open_bits') == 'True':
562                         settings['fixHorrible'] |= 0x10
563                 if profile.getProfileSetting('fix_horrible_extensive_stitching') == 'True':
564                         settings['fixHorrible'] |= 0x04
565
566                 if settings['layerThickness'] <= 0:
567                         settings['layerThickness'] = 1000
568                 if profile.getMachineSetting('gcode_flavor') == 'UltiGCode':
569                         settings['gcodeFlavor'] = 1
570                 elif profile.getMachineSetting('gcode_flavor') == 'MakerBot':
571                         settings['gcodeFlavor'] = 2
572                 elif profile.getMachineSetting('gcode_flavor') == 'BFB':
573                         settings['gcodeFlavor'] = 3
574                 elif profile.getMachineSetting('gcode_flavor') == 'Mach3':
575                         settings['gcodeFlavor'] = 4
576                 elif profile.getMachineSetting('gcode_flavor') == 'RepRap (Volumetric)':
577                         settings['gcodeFlavor'] = 5
578                 if profile.getProfileSetting('spiralize') == 'True':
579                         settings['spiralizeMode'] = 1
580                 if profile.getProfileSetting('simple_mode') == 'True':
581                         settings['simpleMode'] = 1
582                 if profile.getProfileSetting('wipe_tower') == 'True' and extruderCount > 1:
583                         settings['wipeTowerSize'] = int(math.sqrt(profile.getProfileSettingFloat('wipe_tower_volume') * 1000 * 1000 * 1000 / settings['layerThickness']))
584                 if profile.getProfileSetting('ooze_shield') == 'True':
585                         settings['enableOozeShield'] = 1
586                 return settings
587
588         def _runEngineProcess(self, cmdList):
589                 kwargs = {}
590                 if subprocess.mswindows:
591                         su = subprocess.STARTUPINFO()
592                         su.dwFlags |= subprocess.STARTF_USESHOWWINDOW
593                         su.wShowWindow = subprocess.SW_HIDE
594                         kwargs['startupinfo'] = su
595                         kwargs['creationflags'] = 0x00004000 #BELOW_NORMAL_PRIORITY_CLASS
596                 return subprocess.Popen(cmdList, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs)