import urllib
import urllib2
import hashlib
+import cStringIO as StringIO
from Cura.util import profile
from Cura.util import version
+from Cura.util import gcodeInterpreter
def getEngineFilename():
if platform.system() == 'Windows':
- if os.path.exists('C:/Software/Cura_SteamEngine/_bin/Release/Cura_SteamEngine.exe'):
+ if version.isDevVersion() and os.path.exists('C:/Software/Cura_SteamEngine/_bin/Release/Cura_SteamEngine.exe'):
return 'C:/Software/Cura_SteamEngine/_bin/Release/Cura_SteamEngine.exe'
return os.path.abspath(os.path.join(os.path.dirname(__file__), '../..', 'CuraEngine.exe'))
if hasattr(sys, 'frozen'):
warnings.simplefilter('default')
return ret
-class Slicer(object):
+class EngineResult(object):
+ def __init__(self):
+ self._engineLog = []
+ self._gcodeData = StringIO.StringIO()
+ self._polygons = []
+ self._success = False
+ self._printTimeSeconds = None
+ self._filamentMM = [0.0] * 4
+ self._modelHash = None
+ self._profileString = profile.getProfileString()
+ self._preferencesString = profile.getPreferencesString()
+ self._gcodeInterpreter = gcodeInterpreter.gcode()
+ self._gcodeLoadThread = None
+ self._finished = False
+
+ def getFilamentWeight(self, e=0):
+ #Calculates the weight of the filament in kg
+ radius = float(profile.getProfileSetting('filament_diameter')) / 2
+ volumeM3 = (self._filamentMM[e] * (math.pi * radius * radius)) / (1000*1000*1000)
+ return volumeM3 * profile.getPreferenceFloat('filament_physical_density')
+
+ def getFilamentCost(self, e=0):
+ cost_kg = profile.getPreferenceFloat('filament_cost_kg')
+ cost_meter = profile.getPreferenceFloat('filament_cost_meter')
+ if cost_kg > 0.0 and cost_meter > 0.0:
+ return "%.2f / %.2f" % (self.getFilamentWeight(e) * cost_kg, self._filamentMM[e] / 1000.0 * cost_meter)
+ elif cost_kg > 0.0:
+ return "%.2f" % (self.getFilamentWeight(e) * cost_kg)
+ elif cost_meter > 0.0:
+ return "%.2f" % (self._filamentMM[e] / 1000.0 * cost_meter)
+ return None
+
+ def getPrintTime(self):
+ if int(self._printTimeSeconds / 60 / 60) < 1:
+ return '%d minutes' % (int(self._printTimeSeconds / 60) % 60)
+ if int(self._printTimeSeconds / 60 / 60) == 1:
+ return '%d hour %d minutes' % (int(self._printTimeSeconds / 60 / 60), int(self._printTimeSeconds / 60) % 60)
+ return '%d hours %d minutes' % (int(self._printTimeSeconds / 60 / 60), int(self._printTimeSeconds / 60) % 60)
+
+ def getFilamentAmount(self, e=0):
+ if self._filamentMM[e] == 0.0:
+ return None
+ return '%0.2f meter %0.0f gram' % (float(self._filamentMM[e]) / 1000.0, self.getFilamentWeight(e) * 1000.0)
+
+ def getLog(self):
+ return self._engineLog
+
+ def getGCode(self):
+ return self._gcodeData.getvalue()
+
+ def addLog(self, line):
+ self._engineLog.append(line)
+
+ def setHash(self, hash):
+ self._modelHash = hash
+
+ def setFinished(self, result):
+ self._finished = result
+
+ def isFinished(self):
+ return self._finished
+
+ def getGCodeLayers(self):
+ if not self._finished:
+ return None
+ if self._gcodeInterpreter.layerList is None and self._gcodeLoadThread is None:
+ self._gcodeInterpreter.progressCallback = self._gcodeInterpreterCallback
+ self._gcodeLoadThread = threading.Thread(target=lambda : self._gcodeInterpreter.load(self._gcodeData))
+ self._gcodeLoadThread.daemon = True
+ self._gcodeLoadThread.start()
+ return self._gcodeInterpreter.layerList
+
+ def _gcodeInterpreterCallback(self, progress):
+ if len(self._gcodeInterpreter.layerList) % 15 == 0:
+ time.sleep(0.1)
+ return False
+
+ def submitInfoOnline(self):
+ if profile.getPreference('submit_slice_information') != 'True':
+ return
+ if version.isDevVersion():
+ return
+ data = {
+ 'processor': platform.processor(),
+ 'machine': platform.machine(),
+ 'platform': platform.platform(),
+ 'profile': self._profileString,
+ 'preferences': self._preferencesString,
+ 'modelhash': self._modelHash,
+ 'version': version.getVersion(),
+ }
+ try:
+ f = urllib2.urlopen("http://www.youmagine.com/curastats/", data = urllib.urlencode(data), timeout = 1)
+ f.read()
+ f.close()
+ except:
+ pass
+
+class Engine(object):
def __init__(self, progressCallback):
self._process = None
self._thread = None
self._callback = progressCallback
self._binaryStorageFilename = getTempFilename()
- self._exportFilename = getTempFilename()
self._progressSteps = ['inset', 'skin', 'export']
self._objCount = 0
- self._sliceLog = []
- self._printTimeSeconds = None
- self._filamentMM = None
- self._modelHash = None
- self._id = 0
+ self._result = None
def cleanup(self):
- self.abortSlicer()
+ self.abortEngine()
try:
os.remove(self._binaryStorageFilename)
except:
pass
- try:
- os.remove(self._exportFilename)
- except:
- pass
- def abortSlicer(self):
+ def abortEngine(self):
if self._process is not None:
try:
self._process.terminate()
except:
pass
+ if self._thread is not None:
self._thread.join()
self._thread = None
if self._thread is not None:
self._thread.join()
- def getGCodeFilename(self):
- return self._exportFilename
+ def getResult(self):
+ return self._result
- def getSliceLog(self):
- return self._sliceLog
-
- def getID(self):
- return self._id
-
- def getFilamentWeight(self):
- #Calculates the weight of the filament in kg
- radius = float(profile.getProfileSetting('filament_diameter')) / 2
- volumeM3 = (self._filamentMM * (math.pi * radius * radius)) / (1000*1000*1000)
- return volumeM3 * profile.getPreferenceFloat('filament_physical_density')
-
- def getFilamentCost(self):
- cost_kg = profile.getPreferenceFloat('filament_cost_kg')
- cost_meter = profile.getPreferenceFloat('filament_cost_meter')
- if cost_kg > 0.0 and cost_meter > 0.0:
- return "%.2f / %.2f" % (self.getFilamentWeight() * cost_kg, self._filamentMM / 1000.0 * cost_meter)
- elif cost_kg > 0.0:
- return "%.2f" % (self.getFilamentWeight() * cost_kg)
- elif cost_meter > 0.0:
- return "%.2f" % (self._filamentMM / 1000.0 * cost_meter)
- return None
-
- def getPrintTime(self):
- if int(self._printTimeSeconds / 60 / 60) < 1:
- return '%d minutes' % (int(self._printTimeSeconds / 60) % 60)
- if int(self._printTimeSeconds / 60 / 60) == 1:
- return '%d hour %d minutes' % (int(self._printTimeSeconds / 60 / 60), int(self._printTimeSeconds / 60) % 60)
- return '%d hours %d minutes' % (int(self._printTimeSeconds / 60 / 60), int(self._printTimeSeconds / 60) % 60)
-
- def getFilamentAmount(self):
- return '%0.2f meter %0.0f gram' % (float(self._filamentMM) / 1000.0, self.getFilamentWeight() * 1000.0)
-
- def runSlicer(self, scene):
+ def runEngine(self, scene):
+ if len(scene.objects()) < 1:
+ return
extruderCount = 1
for obj in scene.objects():
if scene.checkPlatform(obj):
extruderCount = max(extruderCount, len(obj._meshList))
- commandList = [getEngineFilename(), '-vv']
+ extruderCount = max(extruderCount, profile.minimalExtruderCount())
+
+ commandList = [getEngineFilename(), '-vvv']
for k, v in self._engineSettings(extruderCount).iteritems():
commandList += ['-s', '%s=%s' % (k, str(v))]
- commandList += ['-o', self._exportFilename]
commandList += ['-b', self._binaryStorageFilename]
self._objCount = 0
with open(self._binaryStorageFilename, "wb") as f:
order = scene.printOrder()
if order is None:
pos = numpy.array(profile.getMachineCenterCoords()) * 1000
- commandList += ['-s', 'posx=%d' % int(pos[0]), '-s', 'posy=%d' % int(pos[1])]
-
- vertexTotal = 0
+ objMin = None
+ objMax = None
for obj in scene.objects():
if scene.checkPlatform(obj):
- for mesh in obj._meshList:
- vertexTotal += mesh.vertexCount
+ oMin = obj.getMinimum()[0:2] + obj.getPosition()
+ oMax = obj.getMaximum()[0:2] + obj.getPosition()
+ if objMin is None:
+ objMin = oMin
+ objMax = oMax
+ else:
+ objMin[0] = min(oMin[0], objMin[0])
+ objMin[1] = min(oMin[1], objMin[1])
+ objMax[0] = max(oMax[0], objMax[0])
+ objMax[1] = max(oMax[1], objMax[1])
+ if objMin is None:
+ return
+ pos += (objMin + objMax) / 2.0 * 1000
+ commandList += ['-s', 'posx=%d' % int(pos[0]), '-s', 'posy=%d' % int(pos[1])]
- f.write(numpy.array([vertexTotal], numpy.int32).tostring())
+ vertexTotal = [0] * 4
+ meshMax = 1
for obj in scene.objects():
if scene.checkPlatform(obj):
- for mesh in obj._meshList:
- vertexes = (numpy.matrix(mesh.vertexes, copy = False) * numpy.matrix(obj._matrix, numpy.float32)).getA()
- vertexes -= obj._drawOffset
- vertexes += numpy.array([obj.getPosition()[0], obj.getPosition()[1], 0.0])
- f.write(vertexes.tostring())
- hash.update(mesh.vertexes.tostring())
-
- commandList += ['#']
+ meshMax = max(meshMax, len(obj._meshList))
+ for n in xrange(0, len(obj._meshList)):
+ vertexTotal[n] += obj._meshList[n].vertexCount
+
+ for n in xrange(0, meshMax):
+ f.write(numpy.array([vertexTotal[n]], numpy.int32).tostring())
+ for obj in scene.objects():
+ if scene.checkPlatform(obj):
+ if n < len(obj._meshList):
+ vertexes = (numpy.matrix(obj._meshList[n].vertexes, copy = False) * numpy.matrix(obj._matrix, numpy.float32)).getA()
+ vertexes -= obj._drawOffset
+ vertexes += numpy.array([obj.getPosition()[0], obj.getPosition()[1], 0.0])
+ f.write(vertexes.tostring())
+ hash.update(obj._meshList[n].vertexes.tostring())
+
+ commandList += ['#' * meshMax]
self._objCount = 1
else:
for n in order:
commandList += ['-s', 'posx=%d' % int(pos[0]), '-s', 'posy=%d' % int(pos[1])]
commandList += ['#' * len(obj._meshList)]
self._objCount += 1
- self._modelHash = hash.hexdigest()
+ modelHash = hash.hexdigest()
if self._objCount > 0:
- self._thread = threading.Thread(target=self._watchProcess, args=(commandList, self._thread))
+ self._thread = threading.Thread(target=self._watchProcess, args=(commandList, self._thread, modelHash))
self._thread.daemon = True
self._thread.start()
- def _watchProcess(self, commandList, oldThread):
+ def _watchProcess(self, commandList, oldThread, modelHash):
if oldThread is not None:
if self._process is not None:
self._process.terminate()
oldThread.join()
- self._id += 1
- self._callback(-1.0, False)
+ self._callback(-1.0)
try:
- self._process = self._runSliceProcess(commandList)
+ self._process = self._runEngineProcess(commandList)
except OSError:
traceback.print_exc()
return
if self._thread != threading.currentThread():
self._process.terminate()
- self._callback(0.0, False)
- self._sliceLog = []
- self._printTimeSeconds = None
- self._filamentMM = None
- line = self._process.stdout.readline()
+ self._result = EngineResult()
+ self._result.setHash(modelHash)
+ self._callback(0.0)
+
+ logThread = threading.Thread(target=self._watchStderr, args=(self._process.stderr,))
+ logThread.daemon = True
+ logThread.start()
+
+ data = self._process.stdout.read(4096)
+ while len(data) > 0:
+ self._result._gcodeData.write(data)
+ data = self._process.stdout.read(4096)
+
+ returnCode = self._process.wait()
+ logThread.join()
+ if returnCode == 0:
+ pluginError = None #profile.runPostProcessingPlugins(self._exportFilename)
+ if pluginError is not None:
+ print pluginError
+ self._result.addLog(pluginError)
+ self._result.setFinished(True)
+ self._callback(1.0)
+ else:
+ for line in self._result.getLog():
+ print line
+ self._callback(-1.0)
+ self._process = None
+
+ def _watchStderr(self, stderr):
objectNr = 0
- while len(line):
+
+ # data = stderr.read(4096)
+ # tmp = StringIO.StringIO()
+ # while len(data):
+ # tmp.write(data)
+ # data = stderr.read(4096)
+ # stderr = StringIO.StringIO(tmp.getvalue())
+
+ line = stderr.readline()
+ while len(line) > 0:
line = line.strip()
if line.startswith('Progress:'):
line = line.split(':')
progressValue /= self._objCount
progressValue += 1.0 / self._objCount * objectNr
try:
- self._callback(progressValue, False)
+ self._callback(progressValue)
except:
pass
+ elif line.startswith('Polygons:'):
+ line = line.split(':')
+ typeName = line[1]
+ layerNr = int(line[2])
+ size = int(line[3])
+ z = float(line[4])
+ while len(self._result._polygons) < layerNr + 1:
+ self._result._polygons.append({})
+ polygons = self._result._polygons[layerNr]
+ for n in xrange(0, size):
+ polygon = stderr.readline().strip()
+ if not polygon:
+ continue
+ polygon2d = numpy.fromstring(polygon, dtype=numpy.float32, sep=' ')
+ polygon2d = polygon2d.reshape((len(polygon2d) / 2, 2))
+ polygon = numpy.empty((len(polygon2d), 3), numpy.float32)
+ polygon[:,:-1] = polygon2d
+ polygon[:,2] = z
+ if typeName not in polygons:
+ polygons[typeName] = []
+ polygons[typeName].append(polygon)
elif line.startswith('Print time:'):
- self._printTimeSeconds = int(line.split(':')[1].strip())
+ self._result._printTimeSeconds = int(line.split(':')[1].strip())
elif line.startswith('Filament:'):
- self._filamentMM = int(line.split(':')[1].strip())
+ self._result._filamentMM[0] = int(line.split(':')[1].strip())
if profile.getMachineSetting('gcode_flavor') == 'UltiGCode':
radius = profile.getProfileSettingFloat('filament_diameter') / 2.0
- self._filamentMM /= (math.pi * radius * radius)
- else:
- self._sliceLog.append(line.strip())
- line = self._process.stdout.readline()
- for line in self._process.stderr:
- self._sliceLog.append(line.strip())
- returnCode = self._process.wait()
- try:
- if returnCode == 0:
- pluginError = profile.runPostProcessingPlugins(self._exportFilename)
- if pluginError is not None:
- print pluginError
- self._sliceLog.append(pluginError)
- self._callback(1.0, True)
+ self._result._filamentMM[0] /= (math.pi * radius * radius)
+ elif line.startswith('Filament2:'):
+ self._result._filamentMM[1] = int(line.split(':')[1].strip())
+ if profile.getMachineSetting('gcode_flavor') == 'UltiGCode':
+ radius = profile.getProfileSettingFloat('filament_diameter') / 2.0
+ self._result._filamentMM[1] /= (math.pi * radius * radius)
else:
- for line in self._sliceLog:
- print line
- self._callback(-1.0, False)
- except:
- pass
- self._process = None
+ self._result.addLog(line)
+ line = stderr.readline()
def _engineSettings(self, extruderCount):
settings = {
'insetCount': int(profile.calculateLineCount()),
'downSkinCount': int(profile.calculateSolidLayerCount()) if profile.getProfileSetting('solid_bottom') == 'True' else 0,
'upSkinCount': int(profile.calculateSolidLayerCount()) if profile.getProfileSetting('solid_top') == 'True' else 0,
- 'sparseInfillLineDistance': int(100 * profile.calculateEdgeWidth() * 1000 / profile.getProfileSettingFloat('fill_density')) if profile.getProfileSettingFloat('fill_density') > 0 else -1,
'infillOverlap': int(profile.getProfileSettingFloat('fill_overlap')),
'initialSpeedupLayers': int(4),
'initialLayerSpeed': int(profile.getProfileSettingFloat('bottom_layer_speed')),
'printSpeed': int(profile.getProfileSettingFloat('print_speed')),
'infillSpeed': int(profile.getProfileSettingFloat('infill_speed')) if int(profile.getProfileSettingFloat('infill_speed')) > 0 else int(profile.getProfileSettingFloat('print_speed')),
'moveSpeed': int(profile.getProfileSettingFloat('travel_speed')),
- 'fanOnLayerNr': int(profile.getProfileSettingFloat('fan_layer')),
'fanSpeedMin': int(profile.getProfileSettingFloat('fan_speed')) if profile.getProfileSetting('fan_enabled') == 'True' else 0,
'fanSpeedMax': int(profile.getProfileSettingFloat('fan_speed_max')) if profile.getProfileSetting('fan_enabled') == 'True' else 0,
- 'supportAngle': int(-1) if profile.getProfileSetting('support') == 'None' else int(60),
+ 'supportAngle': int(-1) if profile.getProfileSetting('support') == 'None' else int(profile.getProfileSettingFloat('support_angle')),
'supportEverywhere': int(1) if profile.getProfileSetting('support') == 'Everywhere' else int(0),
'supportLineDistance': int(100 * profile.calculateEdgeWidth() * 1000 / profile.getProfileSettingFloat('support_fill_rate')) if profile.getProfileSettingFloat('support_fill_rate') > 0 else -1,
'supportXYDistance': int(1000 * profile.getProfileSettingFloat('support_xy_distance')),
'supportZDistance': int(1000 * profile.getProfileSettingFloat('support_z_distance')),
- 'supportExtruder': 0 if profile.getProfileSetting('support_dual_extrusion') == 'First extruder' else (1 if profile.getProfileSetting('support_dual_extrusion') == 'Second extruder' else -1),
+ '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),
'retractionAmount': int(profile.getProfileSettingFloat('retraction_amount') * 1000) if profile.getProfileSetting('retraction_enable') == 'True' else 0,
'retractionSpeed': int(profile.getProfileSettingFloat('retraction_speed')),
'retractionMinimalDistance': int(profile.getProfileSettingFloat('retraction_min_travel') * 1000),
'extruderOffset[3].Y': int(profile.getMachineSettingFloat('extruder_offset_y3') * 1000),
'fixHorrible': 0,
}
+ fanFullHeight = int(profile.getProfileSettingFloat('fan_full_height') * 1000)
+ settings['fanFullOnLayerNr'] = (fanFullHeight - settings['initialLayerThickness'] - 1) / settings['layerThickness'] + 1
+ if settings['fanFullOnLayerNr'] < 0:
+ settings['fanFullOnLayerNr'] = 0
+
+ if profile.getProfileSettingFloat('fill_density') == 0:
+ settings['sparseInfillLineDistance'] = -1
+ elif profile.getProfileSettingFloat('fill_density') == 100:
+ settings['sparseInfillLineDistance'] = settings['extrusionWidth']
+ #Set the up/down skins height to 10000 if we want a 100% filled object.
+ # This gives better results then normal 100% infill as the sparse and up/down skin have some overlap.
+ settings['downSkinCount'] = 10000
+ settings['upSkinCount'] = 10000
+ else:
+ settings['sparseInfillLineDistance'] = int(100 * profile.calculateEdgeWidth() * 1000 / profile.getProfileSettingFloat('fill_density'))
if profile.getProfileSetting('platform_adhesion') == 'Brim':
settings['skirtDistance'] = 0
settings['skirtLineCount'] = int(profile.getProfileSettingFloat('brim_line_count'))
settings['layerThickness'] = 1000
if profile.getMachineSetting('gcode_flavor') == 'UltiGCode':
settings['gcodeFlavor'] = 1
+ if profile.getProfileSetting('spiralize') == 'True':
+ settings['spiralizeMode'] = 1
+ if profile.getProfileSetting('wipe_tower') == 'True':
+ settings['wipeTowerSize'] = int(math.sqrt(profile.getProfileSettingFloat('wipe_tower_volume') * 1000 * 1000 * 1000 / settings['layerThickness']))
+ if profile.getProfileSetting('ooze_shield') == 'True':
+ settings['enableOozeShield'] = 1
return settings
- def _runSliceProcess(self, cmdList):
+ def _runEngineProcess(self, cmdList):
kwargs = {}
if subprocess.mswindows:
su = subprocess.STARTUPINFO()
kwargs['startupinfo'] = su
kwargs['creationflags'] = 0x00004000 #BELOW_NORMAL_PRIORITY_CLASS
return subprocess.Popen(cmdList, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs)
-
- def submitSliceInfoOnline(self):
- if profile.getPreference('submit_slice_information') != 'True':
- return
- if version.isDevVersion():
- return
- data = {
- 'processor': platform.processor(),
- 'machine': platform.machine(),
- 'platform': platform.platform(),
- 'profile': profile.getProfileString(),
- 'preferences': profile.getPreferencesString(),
- 'modelhash': self._modelHash,
- 'version': version.getVersion(),
- }
- try:
- f = urllib2.urlopen("http://www.youmagine.com/curastats/", data = urllib.urlencode(data), timeout = 1)
- f.read()
- f.close()
- except:
- pass