chiark / gitweb /
RC1
[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                 self._thread = threading.Thread(target=self._runEngine, args=(scene, self._thread))
291                 self._thread.daemon = True
292                 self._thread.start()
293
294         def _runEngine(self, scene, old_thread):
295                 if old_thread is not None:
296                         if self._process is not None:
297                                 self._process.terminate()
298                         old_thread.join()
299                 self._callback(-1.0)
300
301                 extruderCount = 1
302                 for obj in scene.objects():
303                         if scene.checkPlatform(obj):
304                                 extruderCount = max(extruderCount, len(obj._meshList))
305
306                 extruderCount = max(extruderCount, profile.minimalExtruderCount())
307
308                 commandList = [self._engine_executable, '-v', '-p']
309                 for k, v in self._engineSettings(extruderCount).iteritems():
310                         commandList += ['-s', '%s=%s' % (k, str(v))]
311                 commandList += ['-g', '%d' % (self._serverPortNr)]
312                 self._objCount = 0
313                 engineModelData = []
314                 hash = hashlib.sha512()
315                 order = scene.printOrder()
316                 if order is None:
317                         pos = numpy.array(profile.getMachineCenterCoords()) * 1000
318                         objMin = None
319                         objMax = None
320                         for obj in scene.objects():
321                                 if scene.checkPlatform(obj):
322                                         oMin = obj.getMinimum()[0:2] + obj.getPosition()
323                                         oMax = obj.getMaximum()[0:2] + obj.getPosition()
324                                         if objMin is None:
325                                                 objMin = oMin
326                                                 objMax = oMax
327                                         else:
328                                                 objMin[0] = min(oMin[0], objMin[0])
329                                                 objMin[1] = min(oMin[1], objMin[1])
330                                                 objMax[0] = max(oMax[0], objMax[0])
331                                                 objMax[1] = max(oMax[1], objMax[1])
332                         if objMin is None:
333                                 return
334                         pos += (objMin + objMax) / 2.0 * 1000
335                         commandList += ['-s', 'posx=%d' % int(pos[0]), '-s', 'posy=%d' % int(pos[1])]
336
337                         vertexTotal = [0] * 4
338                         meshMax = 1
339                         for obj in scene.objects():
340                                 if scene.checkPlatform(obj):
341                                         meshMax = max(meshMax, len(obj._meshList))
342                                         for n in xrange(0, len(obj._meshList)):
343                                                 vertexTotal[n] += obj._meshList[n].vertexCount
344
345                         for n in xrange(0, meshMax):
346                                 verts = numpy.zeros((0, 3), numpy.float32)
347                                 for obj in scene.objects():
348                                         if scene.checkPlatform(obj):
349                                                 if n < len(obj._meshList):
350                                                         vertexes = (numpy.matrix(obj._meshList[n].vertexes, copy = False) * numpy.matrix(obj._matrix, numpy.float32)).getA()
351                                                         vertexes -= obj._drawOffset
352                                                         vertexes += numpy.array([obj.getPosition()[0], obj.getPosition()[1], 0.0])
353                                                         verts = numpy.concatenate((verts, vertexes))
354                                                         hash.update(obj._meshList[n].vertexes.tostring())
355                                 engineModelData.append((vertexTotal[n], verts))
356
357                         commandList += ['$' * meshMax]
358                         self._objCount = 1
359                 else:
360                         for n in order:
361                                 obj = scene.objects()[n]
362                                 for mesh in obj._meshList:
363                                         engineModelData.append((mesh.vertexCount, mesh.vertexes))
364                                         hash.update(mesh.vertexes.tostring())
365                                 pos = obj.getPosition() * 1000
366                                 pos += numpy.array(profile.getMachineCenterCoords()) * 1000
367                                 commandList += ['-m', ','.join(map(str, obj._matrix.getA().flatten()))]
368                                 commandList += ['-s', 'posx=%d' % int(pos[0]), '-s', 'posy=%d' % int(pos[1])]
369                                 commandList += ['$' * len(obj._meshList)]
370                                 self._objCount += 1
371                 modelHash = hash.hexdigest()
372                 if self._objCount < 1:
373                         return
374                 if self._thread != threading.currentThread():
375                         return
376
377                 self._modelData = engineModelData
378                 try:
379                         self._process = self._runEngineProcess(commandList)
380                 except OSError:
381                         traceback.print_exc()
382                         return
383
384                 self._result = EngineResult()
385                 self._result.addLog('Running: %s' % (' '.join(commandList)))
386                 self._result.setHash(modelHash)
387                 self._callback(0.0)
388
389                 logThread = threading.Thread(target=self._watchStderr, args=(self._process.stderr,))
390                 logThread.daemon = True
391                 logThread.start()
392
393                 try:
394                         data = self._process.stdout.read(4096)
395                         while len(data) > 0:
396                                 if self._thread != threading.currentThread():
397                                         self._process.terminate()
398                                 self._result._gcodeData.write(data)
399                                 data = self._process.stdout.read(4096)
400
401                         returnCode = self._process.wait()
402                         logThread.join()
403                         if returnCode == 0:
404                                 self._result.setFinished(True)
405                                 plugin_error = pluginInfo.runPostProcessingPlugins(self._result)
406                                 if plugin_error is not None:
407                                         print plugin_error
408                                         self._result.addLog(plugin_error)
409                                 self._callback(1.0)
410                         else:
411                                 for line in self._result.getLog():
412                                         print line
413                                 self._callback(-1.0)
414                         self._process = None
415                 except MemoryError:
416                         self._result.addLog("MemoryError")
417                         self._callback(-1.0)
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                         'fixHorrible': 0,
508                 }
509                 fanFullHeight = int(profile.getProfileSettingFloat('fan_full_height') * 1000)
510                 settings['fanFullOnLayerNr'] = (fanFullHeight - settings['initialLayerThickness'] - 1) / settings['layerThickness'] + 1
511                 if settings['fanFullOnLayerNr'] < 0:
512                         settings['fanFullOnLayerNr'] = 0
513                 if profile.getProfileSetting('support_type') == 'Lines':
514                         settings['supportType'] = 1
515
516                 if profile.getProfileSettingFloat('fill_density') == 0:
517                         settings['sparseInfillLineDistance'] = -1
518                 elif profile.getProfileSettingFloat('fill_density') == 100:
519                         settings['sparseInfillLineDistance'] = settings['extrusionWidth']
520                         #Set the up/down skins height to 10000 if we want a 100% filled object.
521                         # This gives better results then normal 100% infill as the sparse and up/down skin have some overlap.
522                         settings['downSkinCount'] = 10000
523                         settings['upSkinCount'] = 10000
524                 else:
525                         settings['sparseInfillLineDistance'] = int(100 * profile.calculateEdgeWidth() * 1000 / profile.getProfileSettingFloat('fill_density'))
526                 if profile.getProfileSetting('platform_adhesion') == 'Brim':
527                         settings['skirtDistance'] = 0
528                         settings['skirtLineCount'] = int(profile.getProfileSettingFloat('brim_line_count'))
529                 elif profile.getProfileSetting('platform_adhesion') == 'Raft':
530                         settings['skirtDistance'] = 0
531                         settings['skirtLineCount'] = 0
532                         settings['raftMargin'] = int(profile.getProfileSettingFloat('raft_margin') * 1000)
533                         settings['raftLineSpacing'] = int(profile.getProfileSettingFloat('raft_line_spacing') * 1000)
534                         settings['raftBaseThickness'] = int(profile.getProfileSettingFloat('raft_base_thickness') * 1000)
535                         settings['raftBaseLinewidth'] = int(profile.getProfileSettingFloat('raft_base_linewidth') * 1000)
536                         settings['raftInterfaceThickness'] = int(profile.getProfileSettingFloat('raft_interface_thickness') * 1000)
537                         settings['raftInterfaceLinewidth'] = int(profile.getProfileSettingFloat('raft_interface_linewidth') * 1000)
538                         settings['raftInterfaceLineSpacing'] = int(profile.getProfileSettingFloat('raft_interface_linewidth') * 1000 * 2.0)
539                         settings['raftAirGapLayer0'] = int(profile.getProfileSettingFloat('raft_airgap') * 1000 + profile.getProfileSettingFloat('raft_airgap_all') * 1000)
540                         settings['raftAirGap'] = int(profile.getProfileSettingFloat('raft_airgap_all') * 1000)
541                         settings['raftBaseSpeed'] = int(profile.getProfileSettingFloat('bottom_layer_speed'))
542                         settings['raftFanSpeed'] = 0
543                         settings['raftSurfaceThickness'] = int(profile.getProfileSettingFloat('raft_surface_thickness') * 1000)
544                         settings['raftSurfaceLinewidth'] = int(profile.getProfileSettingFloat('raft_surface_linewidth') * 1000)
545                         settings['raftSurfaceLineSpacing'] = int(profile.getProfileSettingFloat('raft_surface_linewidth') * 1000)
546                         settings['raftSurfaceLayers'] = int(profile.getProfileSettingFloat('raft_surface_layers'))
547                         settings['raftSurfaceSpeed'] = int(profile.getProfileSettingFloat('bottom_layer_speed'))
548                 else:
549                         settings['skirtDistance'] = int(profile.getProfileSettingFloat('skirt_gap') * 1000)
550                         settings['skirtLineCount'] = int(profile.getProfileSettingFloat('skirt_line_count'))
551                         settings['skirtMinLength'] = int(profile.getProfileSettingFloat('skirt_minimal_length') * 1000)
552
553                 if profile.getProfileSetting('fix_horrible_union_all_type_a') == 'True':
554                         settings['fixHorrible'] |= 0x01
555                 if profile.getProfileSetting('fix_horrible_union_all_type_b') == 'True':
556                         settings['fixHorrible'] |= 0x02
557                 if profile.getProfileSetting('fix_horrible_use_open_bits') == 'True':
558                         settings['fixHorrible'] |= 0x10
559                 if profile.getProfileSetting('fix_horrible_extensive_stitching') == 'True':
560                         settings['fixHorrible'] |= 0x04
561
562                 if settings['layerThickness'] <= 0:
563                         settings['layerThickness'] = 1000
564                 if profile.getMachineSetting('gcode_flavor') == 'UltiGCode':
565                         settings['gcodeFlavor'] = 1
566                 elif profile.getMachineSetting('gcode_flavor') == 'MakerBot':
567                         settings['gcodeFlavor'] = 2
568                 elif profile.getMachineSetting('gcode_flavor') == 'BFB':
569                         settings['gcodeFlavor'] = 3
570                 elif profile.getMachineSetting('gcode_flavor') == 'Mach3':
571                         settings['gcodeFlavor'] = 4
572                 elif profile.getMachineSetting('gcode_flavor') == 'RepRap (Volumetric)':
573                         settings['gcodeFlavor'] = 5
574                 if profile.getProfileSetting('spiralize') == 'True':
575                         settings['spiralizeMode'] = 1
576                 if profile.getProfileSetting('simple_mode') == 'True':
577                         settings['simpleMode'] = 1
578                 if profile.getProfileSetting('wipe_tower') == 'True' and extruderCount > 1:
579                         settings['wipeTowerSize'] = int(math.sqrt(profile.getProfileSettingFloat('wipe_tower_volume') * 1000 * 1000 * 1000 / settings['layerThickness']))
580                 if profile.getProfileSetting('ooze_shield') == 'True':
581                         settings['enableOozeShield'] = 1
582                 return settings
583
584         def _runEngineProcess(self, cmdList):
585                 kwargs = {}
586                 if subprocess.mswindows:
587                         su = subprocess.STARTUPINFO()
588                         su.dwFlags |= subprocess.STARTF_USESHOWWINDOW
589                         su.wShowWindow = subprocess.SW_HIDE
590                         kwargs['startupinfo'] = su
591                         kwargs['creationflags'] = 0x00004000 #BELOW_NORMAL_PRIORITY_CLASS
592                 return subprocess.Popen(cmdList, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs)