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