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