chiark / gitweb /
Merge tag '15.01-RC2' into upstream
[cura.git] / Cura / util / sliceEngine.py
index a1f9bd4d36b716283fb2f580e3d91bcd5a642cba..73d018941615aee7eb1b6ceeceba8a82823fa2cd 100644 (file)
@@ -8,19 +8,18 @@ import time
 import math
 import numpy
 import os
-import warnings
 import threading
 import traceback
 import platform
-import sys
 import urllib
 import urllib2
 import hashlib
 import socket
 import struct
 import errno
-import cStringIO as StringIO
+import inspect
 
+from Cura.util.bigDataStorage import BigDataStorage
 from Cura.util import profile
 from Cura.util import pluginInfo
 from Cura.util import version
@@ -31,20 +30,24 @@ def getEngineFilename():
                Finds and returns the path to the current engine executable. This is OS depended.
        :return: The full path to the engine executable.
        """
+       base_search_path = os.path.dirname(inspect.getfile(getEngineFilename))
+       search_filename = 'CuraEngine'
        if platform.system() == 'Windows':
+               search_filename += '.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'):
-               return os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../../..', 'CuraEngine'))
+       for n in xrange(0, 10):
+               full_filename = os.path.abspath(os.path.join(base_search_path, '/'.join(['..'] * n), search_filename))
+               if os.path.isfile(full_filename):
+                       return full_filename
+               full_filename = os.path.abspath(os.path.join(base_search_path, '/'.join(['..'] * n), 'CuraEngine', search_filename))
+               if os.path.isfile(full_filename):
+                       return full_filename
        if os.path.isfile('/usr/bin/CuraEngine'):
                return '/usr/bin/CuraEngine'
        if os.path.isfile('/usr/local/bin/CuraEngine'):
                return '/usr/local/bin/CuraEngine'
-       tempPath = os.path.abspath(os.path.join(os.path.dirname(__file__), '../..', 'CuraEngine'))
-       if os.path.isdir(tempPath):
-               tempPath = os.path.join(tempPath,'CuraEngine')
-       return tempPath
+       return ''
 
 class EngineResult(object):
        """
@@ -53,7 +56,7 @@ class EngineResult(object):
        """
        def __init__(self):
                self._engineLog = []
-               self._gcodeData = StringIO.StringIO()
+               self._gcodeData = BigDataStorage()
                self._polygons = []
                self._replaceInfo = {}
                self._success = False
@@ -87,31 +90,26 @@ class EngineResult(object):
                if self._printTimeSeconds is None:
                        return ''
                if int(self._printTimeSeconds / 60 / 60) < 1:
-                       return '%d minutes' % (int(self._printTimeSeconds / 60) % 60)
+                       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)
+                       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)
+               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):
-               data = self._gcodeData.getvalue()
-               if len(self._replaceInfo) > 0:
-                       block0 = data[0:2048]
-                       for k, v in self._replaceInfo.items():
-                               v = (v + ' ' * len(k))[:len(k)]
-                               block0 = block0.replace(k, v)
-                       return block0 + data[2048:]
-               return data
+               self._gcodeData.seekStart()
+               return self._gcodeData
 
        def setGCode(self, gcode):
-               self._gcodeData = StringIO.StringIO(gcode)
+               self._gcodeData = BigDataStorage()
+               self._gcodeData.write(gcode)
                self._replaceInfo = {}
 
        def addLog(self, line):
@@ -121,6 +119,9 @@ class EngineResult(object):
                self._modelHash = hash
 
        def setFinished(self, result):
+               if result:
+                       for k, v in self._replaceInfo.items():
+                               self._gcodeData.replaceAtStart(k, v)
                self._finished = result
 
        def isFinished(self):
@@ -131,7 +132,7 @@ class EngineResult(object):
                        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 = threading.Thread(target=lambda : self._gcodeInterpreter.load(self._gcodeData.clone()))
                        self._gcodeLoadCallback = loadCallback
                        self._gcodeLoadThread.daemon = True
                        self._gcodeLoadThread.start()
@@ -155,9 +156,11 @@ class EngineResult(object):
                        'preferences': self._preferencesString,
                        'modelhash': self._modelHash,
                        'version': version.getVersion(),
+                       'printtime': self._printTimeSeconds,
+                       'filament': ','.join(map(str, self._filamentMM)),
                }
                try:
-                       f = urllib2.urlopen("https://www.youmagine.com/curastats/", data = urllib.urlencode(data), timeout = 1)
+                       f = urllib2.urlopen("https://stats.youmagine.com/curastats/slice", data = urllib.urlencode(data), timeout = 1)
                        f.read()
                        f.close()
                except:
@@ -182,19 +185,19 @@ class Engine(object):
                self._objCount = 0
                self._result = None
 
+               self._engine_executable = getEngineFilename()
                self._serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                self._serverPortNr = 0xC20A
-               while True:
+               for potential_port in xrange(0xC20A, 0xFFFF):
+                       self._serverPortNr = potential_port
                        try:
                                self._serversocket.bind(('127.0.0.1', self._serverPortNr))
-                       except:
-                               print "Failed to listen on port: %d" % (self._serverPortNr)
-                               self._serverPortNr += 1
-                               if self._serverPortNr > 0xFFFF:
-                                       print "Failed to listen on any port..."
-                                       break
-                       else:
                                break
+                       except:
+                               print("Failed to listen on port: %d" % (self._serverPortNr))
+               else:
+                       print("Failed to listen on any port, this is a fatal error")
+                       exit(10)
                thread = threading.Thread(target=self._socketListenThread)
                thread.daemon = True
                thread.start()
@@ -262,6 +265,7 @@ class Engine(object):
 
        def cleanup(self):
                self.abortEngine()
+               self.wait()
                self._serversocket.close()
 
        def abortEngine(self):
@@ -270,9 +274,6 @@ class Engine(object):
                                self._process.terminate()
                        except:
                                pass
-               if self._thread is not None:
-                       self._thread.join()
-               self._thread = None
 
        def wait(self):
                if self._thread is not None:
@@ -284,6 +285,17 @@ class Engine(object):
        def runEngine(self, scene):
                if len(scene.objects()) < 1:
                        return
+               self._thread = threading.Thread(target=self._runEngine, args=(scene, self._thread, pluginInfo.getPostProcessPluginConfig()))
+               self._thread.daemon = True
+               self._thread.start()
+
+       def _runEngine(self, scene, old_thread, pluginConfig):
+               if old_thread is not None:
+                       if self._process is not None:
+                               self._process.terminate()
+                       old_thread.join()
+               self._callback(-1.0)
+
                extruderCount = 1
                for obj in scene.objects():
                        if scene.checkPlatform(obj):
@@ -291,7 +303,7 @@ class Engine(object):
 
                extruderCount = max(extruderCount, profile.minimalExtruderCount())
 
-               commandList = [getEngineFilename(), '-v', '-p']
+               commandList = [self._engine_executable, '-v', '-p']
                for k, v in self._engineSettings(extruderCount).iteritems():
                        commandList += ['-s', '%s=%s' % (k, str(v))]
                commandList += ['-g', '%d' % (self._serverPortNr)]
@@ -355,25 +367,17 @@ class Engine(object):
                                commandList += ['$' * len(obj._meshList)]
                                self._objCount += 1
                modelHash = hash.hexdigest()
-               if self._objCount > 0:
-                       self._thread = threading.Thread(target=self._watchProcess, args=(commandList, self._thread, engineModelData, modelHash, pluginInfo.getPostProcessPluginConfig()))
-                       self._thread.daemon = True
-                       self._thread.start()
+               if self._objCount < 1:
+                       return
+               if self._thread != threading.currentThread():
+                       return
 
-       def _watchProcess(self, commandList, oldThread, engineModelData, modelHash, pluginConfig):
-               if oldThread is not None:
-                       if self._process is not None:
-                               self._process.terminate()
-                       oldThread.join()
-               self._callback(-1.0)
                self._modelData = engineModelData
                try:
                        self._process = self._runEngineProcess(commandList)
                except OSError:
                        traceback.print_exc()
                        return
-               if self._thread != threading.currentThread():
-                       self._process.terminate()
 
                self._result = EngineResult()
                self._result.addLog('Running: %s' % (' '.join(commandList)))
@@ -384,31 +388,36 @@ class Engine(object):
                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()
-               self._result.addLog("Slicer process returned : %d" % returnCode)
                try:
-                       with open(os.path.join(profile.getBasePath(), 'engine.log'), "w") as f:
-                               for line in self._result.getLog():
-                                       f.write(line + "\n")
-               except:
-                       pass
-
-               if returnCode == 0:
-                       pluginError = pluginInfo.runPostProcessingPlugins(self._result, pluginConfig)
-                       if pluginError is not None:
-                               print pluginError
-                               self._result.addLog(pluginError)
-                       self._result.setFinished(True)
-                       self._callback(1.0)
-               else:
+                       data = self._process.stdout.read(4096)
+                       while len(data) > 0:
+                               if self._thread != threading.currentThread():
+                                       self._process.terminate()
+                               self._result._gcodeData.write(data)
+                               data = self._process.stdout.read(4096)
+
+                       returnCode = self._process.wait()
+                       logThread.join()
+                       self._result.addLog("Slicer process returned : %d" % returnCode)
+                       if returnCode == 0:
+                               plugin_error = pluginInfo.runPostProcessingPlugins(self._result, pluginConfig)
+                               if plugin_error is not None:
+                                       self._result.addLog(plugin_error)
+                               self._result.setFinished(True)
+                               self._callback(1.0)
+                       else:
+                               self._callback(-1.0)
+                       self._process = None
+               except MemoryError:
+                       self._result.addLog("MemoryError")
                        self._callback(-1.0)
-               self._process = None
+               finally:
+                       try:
+                               with open(os.path.join(profile.getBasePath(), 'engine.log'), "w") as f:
+                                       for line in self._result.getLog():
+                                               f.write(line + "\n")
+                       except:
+                               pass
 
        def _watchStderr(self, stderr):
                objectNr = 0
@@ -531,12 +540,13 @@ class Engine(object):
                        settings['raftInterfaceThickness'] = int(profile.getProfileSettingFloat('raft_interface_thickness') * 1000)
                        settings['raftInterfaceLinewidth'] = int(profile.getProfileSettingFloat('raft_interface_linewidth') * 1000)
                        settings['raftInterfaceLineSpacing'] = int(profile.getProfileSettingFloat('raft_interface_linewidth') * 1000 * 2.0)
-                       settings['raftAirGapLayer0'] = int(profile.getProfileSettingFloat('raft_airgap') * 1000)
+                       settings['raftAirGapLayer0'] = int(profile.getProfileSettingFloat('raft_airgap') * 1000 + profile.getProfileSettingFloat('raft_airgap_all') * 1000)
+                       settings['raftAirGap'] = int(profile.getProfileSettingFloat('raft_airgap_all') * 1000)
                        settings['raftBaseSpeed'] = int(profile.getProfileSettingFloat('bottom_layer_speed'))
-                       settings['raftFanSpeed'] = 100
-                       settings['raftSurfaceThickness'] = settings['raftInterfaceThickness']
-                       settings['raftSurfaceLinewidth'] = int(profile.calculateEdgeWidth() * 1000)
-                       settings['raftSurfaceLineSpacing'] = int(profile.calculateEdgeWidth() * 1000 * 0.9)
+                       settings['raftFanSpeed'] = 0
+                       settings['raftSurfaceThickness'] = int(profile.getProfileSettingFloat('raft_surface_thickness') * 1000)
+                       settings['raftSurfaceLinewidth'] = int(profile.getProfileSettingFloat('raft_surface_linewidth') * 1000)
+                       settings['raftSurfaceLineSpacing'] = int(profile.getProfileSettingFloat('raft_surface_linewidth') * 1000)
                        settings['raftSurfaceLayers'] = int(profile.getProfileSettingFloat('raft_surface_layers'))
                        settings['raftSurfaceSpeed'] = int(profile.getProfileSettingFloat('bottom_layer_speed'))
                else: