chiark / gitweb /
e19b1a7413ee630b5c6b0182a96227fe0c27267f
[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, pluginInfo.getPostProcessPluginConfig()))
291                 self._thread.daemon = True
292                 self._thread.start()
293
294         def _runEngine(self, scene, old_thread, pluginConfig):
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                         self._result.addLog("Slicer process returned : %d" % returnCode)
404                         if returnCode == 0:
405                                 plugin_error = pluginInfo.runPostProcessingPlugins(self._result, pluginConfig)
406                                 if plugin_error is not None:
407                                         self._result.addLog(plugin_error)
408                                 self._result.setFinished(True)
409                                 self._callback(1.0)
410                         else:
411                                 self._callback(-1.0)
412                         self._process = None
413                 except MemoryError:
414                         self._result.addLog("MemoryError")
415                         self._callback(-1.0)
416                 finally:
417                         try:
418                                 with open(os.path.join(profile.getBasePath(), 'engine.log'), "w") as f:
419                                         for line in self._result.getLog():
420                                                 f.write(line + "\n")
421                         except:
422                                 pass
423
424         def _watchStderr(self, stderr):
425                 objectNr = 0
426                 line = stderr.readline()
427                 while len(line) > 0:
428                         line = line.strip()
429                         if line.startswith('Progress:'):
430                                 line = line.split(':')
431                                 if line[1] == 'process':
432                                         objectNr += 1
433                                 elif line[1] in self._progressSteps:
434                                         progressValue = float(line[2]) / float(line[3])
435                                         progressValue /= len(self._progressSteps)
436                                         progressValue += 1.0 / len(self._progressSteps) * self._progressSteps.index(line[1])
437
438                                         progressValue /= self._objCount
439                                         progressValue += 1.0 / self._objCount * objectNr
440                                         try:
441                                                 self._callback(progressValue)
442                                         except:
443                                                 pass
444                         elif line.startswith('Print time:'):
445                                 self._result._printTimeSeconds = int(line.split(':')[1].strip())
446                         elif line.startswith('Filament:'):
447                                 self._result._filamentMM[0] = int(line.split(':')[1].strip())
448                                 if profile.getMachineSetting('gcode_flavor') == 'UltiGCode':
449                                         radius = profile.getProfileSettingFloat('filament_diameter') / 2.0
450                                         self._result._filamentMM[0] /= (math.pi * radius * radius)
451                         elif line.startswith('Filament2:'):
452                                 self._result._filamentMM[1] = int(line.split(':')[1].strip())
453                                 if profile.getMachineSetting('gcode_flavor') == 'UltiGCode':
454                                         radius = profile.getProfileSettingFloat('filament_diameter') / 2.0
455                                         self._result._filamentMM[1] /= (math.pi * radius * radius)
456                         elif line.startswith('Replace:'):
457                                 self._result._replaceInfo[line.split(':')[1].strip()] = line.split(':')[2].strip()
458                         else:
459                                 self._result.addLog(line)
460                         line = stderr.readline()
461
462         def _engineSettings(self, extruderCount):
463                 settings = {
464                         'layerThickness': int(profile.getProfileSettingFloat('layer_height') * 1000),
465                         'initialLayerThickness': int(profile.getProfileSettingFloat('bottom_thickness') * 1000) if profile.getProfileSettingFloat('bottom_thickness') > 0.0 else int(profile.getProfileSettingFloat('layer_height') * 1000),
466                         'filamentDiameter': int(profile.getProfileSettingFloat('filament_diameter') * 1000),
467                         'filamentFlow': int(profile.getProfileSettingFloat('filament_flow')),
468                         'extrusionWidth': int(profile.calculateEdgeWidth() * 1000),
469                         'layer0extrusionWidth': int(profile.calculateEdgeWidth() * profile.getProfileSettingFloat('layer0_width_factor') / 100 * 1000),
470                         'insetCount': int(profile.calculateLineCount()),
471                         'downSkinCount': int(profile.calculateSolidLayerCount()) if profile.getProfileSetting('solid_bottom') == 'True' else 0,
472                         'upSkinCount': int(profile.calculateSolidLayerCount()) if profile.getProfileSetting('solid_top') == 'True' else 0,
473                         'infillOverlap': int(profile.getProfileSettingFloat('fill_overlap')),
474                         'initialSpeedupLayers': int(4),
475                         'initialLayerSpeed': int(profile.getProfileSettingFloat('bottom_layer_speed')),
476                         'printSpeed': int(profile.getProfileSettingFloat('print_speed')),
477                         'infillSpeed': int(profile.getProfileSettingFloat('infill_speed')) if int(profile.getProfileSettingFloat('infill_speed')) > 0 else int(profile.getProfileSettingFloat('print_speed')),
478                         'inset0Speed': int(profile.getProfileSettingFloat('inset0_speed')) if int(profile.getProfileSettingFloat('inset0_speed')) > 0 else int(profile.getProfileSettingFloat('print_speed')),
479                         'insetXSpeed': int(profile.getProfileSettingFloat('insetx_speed')) if int(profile.getProfileSettingFloat('insetx_speed')) > 0 else int(profile.getProfileSettingFloat('print_speed')),
480                         'moveSpeed': int(profile.getProfileSettingFloat('travel_speed')),
481                         'fanSpeedMin': int(profile.getProfileSettingFloat('fan_speed')) if profile.getProfileSetting('fan_enabled') == 'True' else 0,
482                         'fanSpeedMax': int(profile.getProfileSettingFloat('fan_speed_max')) if profile.getProfileSetting('fan_enabled') == 'True' else 0,
483                         'supportAngle': int(-1) if profile.getProfileSetting('support') == 'None' else int(profile.getProfileSettingFloat('support_angle')),
484                         'supportEverywhere': int(1) if profile.getProfileSetting('support') == 'Everywhere' else int(0),
485                         'supportLineDistance': int(100 * profile.calculateEdgeWidth() * 1000 / profile.getProfileSettingFloat('support_fill_rate')) if profile.getProfileSettingFloat('support_fill_rate') > 0 else -1,
486                         'supportXYDistance': int(1000 * profile.getProfileSettingFloat('support_xy_distance')),
487                         'supportZDistance': int(1000 * profile.getProfileSettingFloat('support_z_distance')),
488                         '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),
489                         'retractionAmount': int(profile.getProfileSettingFloat('retraction_amount') * 1000) if profile.getProfileSetting('retraction_enable') == 'True' else 0,
490                         'retractionSpeed': int(profile.getProfileSettingFloat('retraction_speed')),
491                         'retractionMinimalDistance': int(profile.getProfileSettingFloat('retraction_min_travel') * 1000),
492                         'retractionAmountExtruderSwitch': int(profile.getProfileSettingFloat('retraction_dual_amount') * 1000),
493                         'retractionZHop': int(profile.getProfileSettingFloat('retraction_hop') * 1000),
494                         'minimalExtrusionBeforeRetraction': int(profile.getProfileSettingFloat('retraction_minimal_extrusion') * 1000),
495                         'enableCombing': 1 if profile.getProfileSetting('retraction_combing') == 'True' else 0,
496                         'multiVolumeOverlap': int(profile.getProfileSettingFloat('overlap_dual') * 1000),
497                         'objectSink': max(0, int(profile.getProfileSettingFloat('object_sink') * 1000)),
498                         'minimalLayerTime': int(profile.getProfileSettingFloat('cool_min_layer_time')),
499                         'minimalFeedrate': int(profile.getProfileSettingFloat('cool_min_feedrate')),
500                         'coolHeadLift': 1 if profile.getProfileSetting('cool_head_lift') == 'True' else 0,
501                         'startCode': profile.getAlterationFileContents('start.gcode', extruderCount),
502                         'endCode': profile.getAlterationFileContents('end.gcode', extruderCount),
503                         'preSwitchExtruderCode': profile.getAlterationFileContents('preSwitchExtruder.gcode', extruderCount),
504                         'postSwitchExtruderCode': profile.getAlterationFileContents('postSwitchExtruder.gcode', extruderCount),
505
506                         'extruderOffset[1].X': int(profile.getMachineSettingFloat('extruder_offset_x1') * 1000),
507                         'extruderOffset[1].Y': int(profile.getMachineSettingFloat('extruder_offset_y1') * 1000),
508                         'extruderOffset[2].X': int(profile.getMachineSettingFloat('extruder_offset_x2') * 1000),
509                         'extruderOffset[2].Y': int(profile.getMachineSettingFloat('extruder_offset_y2') * 1000),
510                         'extruderOffset[3].X': int(profile.getMachineSettingFloat('extruder_offset_x3') * 1000),
511                         'extruderOffset[3].Y': int(profile.getMachineSettingFloat('extruder_offset_y3') * 1000),
512                         'zOffset': int(profile.getMachineSettingFloat('extruder_z_offset') * 1000),
513                         'fixHorrible': 0,
514                 }
515                 fanFullHeight = int(profile.getProfileSettingFloat('fan_full_height') * 1000)
516                 settings['fanFullOnLayerNr'] = (fanFullHeight - settings['initialLayerThickness'] - 1) / settings['layerThickness'] + 1
517                 if settings['fanFullOnLayerNr'] < 0:
518                         settings['fanFullOnLayerNr'] = 0
519                 if profile.getProfileSetting('support_type') == 'Lines':
520                         settings['supportType'] = 1
521
522                 if profile.getProfileSettingFloat('fill_density') == 0:
523                         settings['sparseInfillLineDistance'] = -1
524                 elif profile.getProfileSettingFloat('fill_density') == 100:
525                         settings['sparseInfillLineDistance'] = settings['extrusionWidth']
526                         #Set the up/down skins height to 10000 if we want a 100% filled object.
527                         # This gives better results then normal 100% infill as the sparse and up/down skin have some overlap.
528                         settings['downSkinCount'] = 10000
529                         settings['upSkinCount'] = 10000
530                 else:
531                         settings['sparseInfillLineDistance'] = int(100 * profile.calculateEdgeWidth() * 1000 / profile.getProfileSettingFloat('fill_density'))
532                 if profile.getProfileSetting('platform_adhesion') == 'Brim':
533                         settings['skirtDistance'] = 0
534                         settings['skirtLineCount'] = int(profile.getProfileSettingFloat('brim_line_count'))
535                 elif profile.getProfileSetting('platform_adhesion') == 'Raft':
536                         settings['skirtDistance'] = 0
537                         settings['skirtLineCount'] = 0
538                         settings['raftMargin'] = int(profile.getProfileSettingFloat('raft_margin') * 1000)
539                         settings['raftLineSpacing'] = int(profile.getProfileSettingFloat('raft_line_spacing') * 1000)
540                         settings['raftBaseThickness'] = int(profile.getProfileSettingFloat('raft_base_thickness') * 1000)
541                         settings['raftBaseLinewidth'] = int(profile.getProfileSettingFloat('raft_base_linewidth') * 1000)
542                         settings['raftInterfaceThickness'] = int(profile.getProfileSettingFloat('raft_interface_thickness') * 1000)
543                         settings['raftInterfaceLinewidth'] = int(profile.getProfileSettingFloat('raft_interface_linewidth') * 1000)
544                         settings['raftInterfaceLineSpacing'] = int(profile.getProfileSettingFloat('raft_interface_linewidth') * 1000 * 2.0)
545                         settings['raftAirGapLayer0'] = int(profile.getProfileSettingFloat('raft_airgap') * 1000 + profile.getProfileSettingFloat('raft_airgap_all') * 1000)
546                         settings['raftAirGap'] = int(profile.getProfileSettingFloat('raft_airgap_all') * 1000)
547                         settings['raftBaseSpeed'] = int(profile.getProfileSettingFloat('bottom_layer_speed'))
548                         settings['raftFanSpeed'] = 0
549                         settings['raftSurfaceThickness'] = int(profile.getProfileSettingFloat('raft_surface_thickness') * 1000)
550                         settings['raftSurfaceLinewidth'] = int(profile.getProfileSettingFloat('raft_surface_linewidth') * 1000)
551                         settings['raftSurfaceLineSpacing'] = int(profile.getProfileSettingFloat('raft_surface_linewidth') * 1000)
552                         settings['raftSurfaceLayers'] = int(profile.getProfileSettingFloat('raft_surface_layers'))
553                         settings['raftSurfaceSpeed'] = int(profile.getProfileSettingFloat('bottom_layer_speed'))
554                 else:
555                         settings['skirtDistance'] = int(profile.getProfileSettingFloat('skirt_gap') * 1000)
556                         settings['skirtLineCount'] = int(profile.getProfileSettingFloat('skirt_line_count'))
557                         settings['skirtMinLength'] = int(profile.getProfileSettingFloat('skirt_minimal_length') * 1000)
558
559                 if profile.getProfileSetting('fix_horrible_union_all_type_a') == 'True':
560                         settings['fixHorrible'] |= 0x01
561                 if profile.getProfileSetting('fix_horrible_union_all_type_b') == 'True':
562                         settings['fixHorrible'] |= 0x02
563                 if profile.getProfileSetting('fix_horrible_use_open_bits') == 'True':
564                         settings['fixHorrible'] |= 0x10
565                 if profile.getProfileSetting('fix_horrible_extensive_stitching') == 'True':
566                         settings['fixHorrible'] |= 0x04
567
568                 if settings['layerThickness'] <= 0:
569                         settings['layerThickness'] = 1000
570                 if profile.getMachineSetting('gcode_flavor') == 'UltiGCode':
571                         settings['gcodeFlavor'] = 1
572                 elif profile.getMachineSetting('gcode_flavor') == 'MakerBot':
573                         settings['gcodeFlavor'] = 2
574                 elif profile.getMachineSetting('gcode_flavor') == 'BFB':
575                         settings['gcodeFlavor'] = 3
576                 elif profile.getMachineSetting('gcode_flavor') == 'Mach3':
577                         settings['gcodeFlavor'] = 4
578                 elif profile.getMachineSetting('gcode_flavor') == 'RepRap (Volumetric)':
579                         settings['gcodeFlavor'] = 5
580                 if profile.getProfileSetting('spiralize') == 'True':
581                         settings['spiralizeMode'] = 1
582                 if profile.getProfileSetting('simple_mode') == 'True':
583                         settings['simpleMode'] = 1
584                 if profile.getProfileSetting('wipe_tower') == 'True' and extruderCount > 1:
585                         settings['wipeTowerSize'] = int(math.sqrt(profile.getProfileSettingFloat('wipe_tower_volume') * 1000 * 1000 * 1000 / settings['layerThickness']))
586                 if profile.getProfileSetting('ooze_shield') == 'True':
587                         settings['enableOozeShield'] = 1
588                 return settings
589
590         def _runEngineProcess(self, cmdList):
591                 kwargs = {}
592                 if subprocess.mswindows:
593                         su = subprocess.STARTUPINFO()
594                         su.dwFlags |= subprocess.STARTF_USESHOWWINDOW
595                         su.wShowWindow = subprocess.SW_HIDE
596                         kwargs['startupinfo'] = su
597                         kwargs['creationflags'] = 0x00004000 #BELOW_NORMAL_PRIORITY_CLASS
598                 return subprocess.Popen(cmdList, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs)