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