X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?a=blobdiff_plain;f=Cura%2Futil%2FsliceEngine.py;h=87c539fec2351a9a1f2d3760d166d0b16f40f47c;hb=e24262f503b7deb1d779dd3be7ef9edb14927a53;hp=4f96171f1de5414035775fcf7f3faa8d437f5cd0;hpb=68fc669b495ceca09df3e8e5cd489144c17533da;p=cura.git diff --git a/Cura/util/sliceEngine.py b/Cura/util/sliceEngine.py index 4f96171f..87c539fe 100644 --- a/Cura/util/sliceEngine.py +++ b/Cura/util/sliceEngine.py @@ -1,3 +1,4 @@ +__copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License" import subprocess import time import math @@ -8,17 +9,25 @@ import threading import traceback import platform import sys +import urllib +import urllib2 +import hashlib from Cura.util import profile +from Cura.util import version def getEngineFilename(): if platform.system() == 'Windows': if 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__), '../..', 'SteamEngine.exe')) + return os.path.abspath(os.path.join(os.path.dirname(__file__), '../..', 'CuraEngine.exe')) if hasattr(sys, 'frozen'): - return os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../../..', 'SteamEngine')) - return os.path.abspath(os.path.join(os.path.dirname(__file__), '../..', 'SteamEngine')) + return os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../../..', 'CuraEngine')) + if os.path.isfile('/usr/bin/CuraEngine'): + return '/usr/bin/CuraEngine' + if os.path.isfile('/usr/local/bin/CuraEngine'): + return '/usr/local/bin/CuraEngine' + return os.path.abspath(os.path.join(os.path.dirname(__file__), '../..', 'CuraEngine')) def getTempFilename(): warnings.simplefilter('ignore') @@ -37,7 +46,9 @@ class Slicer(object): self._objCount = 0 self._sliceLog = [] self._printTimeSeconds = None - self._filamentMM = None + self._filamentMM = [0.0, 0.0] + self._modelHash = None + self._id = 0 def cleanup(self): self.abortSlicer() @@ -57,6 +68,11 @@ class Slicer(object): except: pass self._thread.join() + self._thread = None + + def wait(self): + if self._thread is not None: + self._thread.join() def getGCodeFilename(self): return self._exportFilename @@ -64,43 +80,72 @@ class Slicer(object): def getSliceLog(self): return self._sliceLog - def getFilamentWeight(self): + def getID(self): + return self._id + + def getFilamentWeight(self, e=0): #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) + volumeM3 = (self._filamentMM[e] * (math.pi * radius * radius)) / (1000*1000*1000) return volumeM3 * profile.getPreferenceFloat('filament_physical_density') - def getFilamentCost(self): + 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() * cost_kg, self._filamentMM / 1000.0 * cost_meter) + return "%.2f / %.2f" % (self.getFilamentWeight(e) * cost_kg, self._filamentMM[e] / 1000.0 * cost_meter) elif cost_kg > 0.0: - return "%.2f" % (self.getFilamentWeight() * cost_kg) + return "%.2f" % (self.getFilamentWeight(e) * cost_kg) elif cost_meter > 0.0: - return "%.2f" % (self._filamentMM / 1000.0 * cost_meter) + return "%.2f" % (self._filamentMM[e] / 1000.0 * cost_meter) return None def getPrintTime(self): - return '%02d:%02d' % (int(self._printTimeSeconds / 60 / 60), int(self._printTimeSeconds / 60) % 60) + 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.2fm %0.0fgram' % (float(self._filamentMM) / 1000.0, self.getFilamentWeight() * 1000.0) + 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 runSlicer(self, scene): - self.abortSlicer() - self._callback(-1.0, False) + extruderCount = 1 + for obj in scene.objects(): + if scene.checkPlatform(obj): + extruderCount = max(extruderCount, len(obj._meshList)) + + extruderCount = max(extruderCount, profile.minimalExtruderCount()) commandList = [getEngineFilename(), '-vv'] - for k, v in self._engineSettings().iteritems(): + 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: + hash = hashlib.sha512() order = scene.printOrder() if order is None: - pos = numpy.array([profile.getPreferenceFloat('machine_width') * 1000 / 2, profile.getPreferenceFloat('machine_depth') * 1000 / 2]) + pos = numpy.array(profile.getMachineCenterCoords()) * 1000 + objMin = None + objMax = None + for obj in scene.objects(): + if scene.checkPlatform(obj): + 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]) + pos += (objMin + objMax) / 2.0 * 1000 commandList += ['-s', 'posx=%d' % int(pos[0]), '-s', 'posy=%d' % int(pos[1])] vertexTotal = 0 @@ -117,6 +162,7 @@ class Slicer(object): vertexes -= obj._drawOffset vertexes += numpy.array([obj.getPosition()[0], obj.getPosition()[1], 0.0]) f.write(vertexes.tostring()) + hash.update(mesh.vertexes.tostring()) commandList += ['#'] self._objCount = 1 @@ -125,27 +171,39 @@ class Slicer(object): obj = scene.objects()[n] for mesh in obj._meshList: f.write(numpy.array([mesh.vertexCount], numpy.int32).tostring()) - f.write(mesh.vertexes.tostring()) + s = mesh.vertexes.tostring() + f.write(s) + hash.update(s) pos = obj.getPosition() * 1000 - pos += numpy.array([profile.getPreferenceFloat('machine_width') * 1000 / 2, profile.getPreferenceFloat('machine_depth') * 1000 / 2]) + pos += numpy.array(profile.getMachineCenterCoords()) * 1000 commandList += ['-m', ','.join(map(str, obj._matrix.getA().flatten()))] commandList += ['-s', 'posx=%d' % int(pos[0]), '-s', 'posy=%d' % int(pos[1])] commandList += ['#' * len(obj._meshList)] self._objCount += 1 + self._modelHash = hash.hexdigest() if self._objCount > 0: - try: - self._process = self._runSliceProcess(commandList) - self._thread = threading.Thread(target=self._watchProcess) - self._thread.daemon = True - self._thread.start() - except OSError: - traceback.print_exc() - - def _watchProcess(self): + self._thread = threading.Thread(target=self._watchProcess, args=(commandList, self._thread)) + self._thread.daemon = True + self._thread.start() + + def _watchProcess(self, commandList, oldThread): + if oldThread is not None: + if self._process is not None: + self._process.terminate() + oldThread.join() + self._id += 1 + self._callback(-1.0, False) + try: + self._process = self._runSliceProcess(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 + self._filamentMM = [0.0, 0.0] line = self._process.stdout.readline() objectNr = 0 @@ -169,7 +227,15 @@ class Slicer(object): elif line.startswith('Print time:'): self._printTimeSeconds = int(line.split(':')[1].strip()) elif line.startswith('Filament:'): - self._filamentMM = int(line.split(':')[1].strip()) + self._filamentMM[0] = int(line.split(':')[1].strip()) + if profile.getMachineSetting('gcode_flavor') == 'UltiGCode': + radius = profile.getProfileSettingFloat('filament_diameter') / 2.0 + self._filamentMM[0] /= (math.pi * radius * radius) + elif line.startswith('Filament2:'): + self._filamentMM[1] = int(line.split(':')[1].strip()) + if profile.getMachineSetting('gcode_flavor') == 'UltiGCode': + radius = profile.getProfileSettingFloat('filament_diameter') / 2.0 + self._filamentMM[1] /= (math.pi * radius * radius) else: self._sliceLog.append(line.strip()) line = self._process.stdout.readline() @@ -178,15 +244,20 @@ class Slicer(object): returnCode = self._process.wait() try: if returnCode == 0: - profile.runPostProcessingPlugins(self._exportFilename) + pluginError = profile.runPostProcessingPlugins(self._exportFilename) + if pluginError is not None: + print pluginError + self._sliceLog.append(pluginError) self._callback(1.0, True) else: + for line in self._sliceLog: + print line self._callback(-1.0, False) except: pass self._process = None - def _engineSettings(self): + def _engineSettings(self, extruderCount): settings = { 'layerThickness': int(profile.getProfileSettingFloat('layer_height') * 1000), 'initialLayerThickness': int(profile.getProfileSettingFloat('bottom_thickness') * 1000) if profile.getProfileSettingFloat('bottom_thickness') > 0.0 else int(profile.getProfileSettingFloat('layer_height') * 1000), @@ -196,32 +267,93 @@ class Slicer(object): '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 9999999999, + '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), 'supportEverywhere': int(1) if profile.getProfileSetting('support') == 'Everywhere' else int(0), - 'retractionAmount': int(profile.getProfileSettingFloat('retraction_amount') * 1000), + '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' 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), + 'retractionAmountExtruderSwitch': int(profile.getProfileSettingFloat('retraction_dual_amount') * 1000), + 'minimalExtrusionBeforeRetraction': int(profile.getProfileSettingFloat('retraction_minimal_extrusion') * 1000), + 'enableCombing': 1 if profile.getProfileSetting('retraction_combing') == 'True' else 0, + 'multiVolumeOverlap': int(profile.getProfileSettingFloat('overlap_dual') * 1000), 'objectSink': int(profile.getProfileSettingFloat('object_sink') * 1000), 'minimalLayerTime': int(profile.getProfileSettingFloat('cool_min_layer_time')), 'minimalFeedrate': int(profile.getProfileSettingFloat('cool_min_feedrate')), 'coolHeadLift': 1 if profile.getProfileSetting('cool_head_lift') == 'True' else 0, - 'startCode': profile.getAlterationFileContents('start.gcode'), - 'endCode': profile.getAlterationFileContents('end.gcode'), + 'startCode': profile.getAlterationFileContents('start.gcode', extruderCount), + 'endCode': profile.getAlterationFileContents('end.gcode', extruderCount), + + 'extruderOffset[1].X': int(profile.getMachineSettingFloat('extruder_offset_x1') * 1000), + 'extruderOffset[1].Y': int(profile.getMachineSettingFloat('extruder_offset_y1') * 1000), + 'extruderOffset[2].X': int(profile.getMachineSettingFloat('extruder_offset_x2') * 1000), + 'extruderOffset[2].Y': int(profile.getMachineSettingFloat('extruder_offset_y2') * 1000), + 'extruderOffset[3].X': int(profile.getMachineSettingFloat('extruder_offset_x3') * 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.0 + settings['skirtDistance'] = 0 settings['skirtLineCount'] = int(profile.getProfileSettingFloat('brim_line_count')) elif profile.getProfileSetting('platform_adhesion') == 'Raft': settings['skirtDistance'] = 0 settings['skirtLineCount'] = 0 + settings['raftMargin'] = int(profile.getProfileSettingFloat('raft_margin') * 1000) + settings['raftLineSpacing'] = int(profile.getProfileSettingFloat('raft_line_spacing') * 1000) + settings['raftBaseThickness'] = int(profile.getProfileSettingFloat('raft_base_thickness') * 1000) + settings['raftBaseLinewidth'] = int(profile.getProfileSettingFloat('raft_base_linewidth') * 1000) + settings['raftInterfaceThickness'] = int(profile.getProfileSettingFloat('raft_interface_thickness') * 1000) + settings['raftInterfaceLinewidth'] = int(profile.getProfileSettingFloat('raft_interface_linewidth') * 1000) else: settings['skirtDistance'] = int(profile.getProfileSettingFloat('skirt_gap') * 1000) settings['skirtLineCount'] = int(profile.getProfileSettingFloat('skirt_line_count')) + settings['skirtMinLength'] = int(profile.getProfileSettingFloat('skirt_minimal_length') * 1000) + + if profile.getProfileSetting('fix_horrible_union_all_type_a') == 'True': + settings['fixHorrible'] |= 0x01 + if profile.getProfileSetting('fix_horrible_union_all_type_b') == 'True': + settings['fixHorrible'] |= 0x02 + if profile.getProfileSetting('fix_horrible_use_open_bits') == 'True': + settings['fixHorrible'] |= 0x10 + if profile.getProfileSetting('fix_horrible_extensive_stitching') == 'True': + settings['fixHorrible'] |= 0x04 + + if settings['layerThickness'] <= 0: + 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['enableWipeTower'] = 1 + if profile.getProfileSetting('ooze_shield') == 'True': + settings['enableOozeShield'] = 1 return settings def _runSliceProcess(self, cmdList): @@ -233,3 +365,24 @@ class Slicer(object): 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