Skip to content
Snippets Groups Projects
utils.py 14.3 KiB
Newer Older
  • Learn to ignore specific revisions
  • # ##### BEGIN GPL LICENSE BLOCK #####
    #
    #  This program is free software; you can redistribute it and/or
    #  modify it under the terms of the GNU General Public License
    #  as published by the Free Software Foundation; either version 2
    #  of the License, or (at your option) any later version.
    #
    #  This program is distributed in the hope that it will be useful,
    #  but WITHOUT ANY WARRANTY; without even the implied warranty of
    #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    #  GNU General Public License for more details.
    #
    #  You should have received a copy of the GNU General Public License
    #  along with this program; if not, write to the Free Software Foundation,
    #  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
    #
    # ##### END GPL LICENSE BLOCK #####
    
    
    Martin Poirier's avatar
    Martin Poirier committed
    import sys, os, re, platform
    
    Campbell Barton's avatar
    Campbell Barton committed
    import http, http.client, http.server, socket
    import subprocess, time, hashlib
    
    
    import netrender, netrender.model
    
    
    try:
      import bpy
    except:
      bpy = None
    
    VERSION = bytes(".".join((str(n) for n in netrender.bl_info["version"])), encoding='utf8')
    
    
    Martin Poirier's avatar
    Martin Poirier committed
    try:
        system = platform.system()
    except UnicodeDecodeError:
        import sys
        system = sys.platform
    
    
    if system == "Darwin":
    
    Martin Poirier's avatar
    Martin Poirier committed
        class ConnectionContext:
            def __init__(self, timeout = None):
                self.old_timeout = socket.getdefaulttimeout()
                self.timeout = timeout
                
            def __enter__(self):
                if self.old_timeout != self.timeout:
                    socket.setdefaulttimeout(self.timeout)
            def __exit__(self, exc_type, exc_value, traceback):
                if self.old_timeout != self.timeout:
                    socket.setdefaulttimeout(self.old_timeout)
    else:
        # On sane OSes we can use the connection timeout value correctly
        class ConnectionContext:
            def __init__(self, timeout = None):
                pass
                
            def __enter__(self):
                pass
    
            def __exit__(self, exc_type, exc_value, traceback):
                pass
    
    
    if system in {'Windows', 'win32'} and platform.version() >= '5': # Error mode is only available on Win2k or higher, that's version 5
    
    Martin Poirier's avatar
    Martin Poirier committed
        import ctypes
        class NoErrorDialogContext:
            def __init__(self):
                self.val = 0
                
            def __enter__(self):
                self.val = ctypes.windll.kernel32.SetErrorMode(0x0002)
                ctypes.windll.kernel32.SetErrorMode(self.val | 0x0002)
    
            def __exit__(self, exc_type, exc_value, traceback):
                ctypes.windll.kernel32.SetErrorMode(self.val)
    else:
        class NoErrorDialogContext:
            def __init__(self):
                pass
                
            def __enter__(self):
                pass
    
            def __exit__(self, exc_type, exc_value, traceback):
                pass
    
    
    class DirectoryContext:
        def __init__(self, path):
            self.path = path
            
        def __enter__(self):
            self.curdir = os.path.abspath(os.curdir)
            os.chdir(self.path)
    
        def __exit__(self, exc_type, exc_value, traceback):
            os.chdir(self.curdir)
    
    class BreakableIncrementedSleep:
        def __init__(self, increment, default_timeout, max_timeout, break_fct):
            self.increment = increment
            self.default = default_timeout
            self.max = max_timeout
            self.current = self.default
            self.break_fct = break_fct
            
        def reset(self):
            self.current = self.default
    
        def increase(self):
            self.current = min(self.current + self.increment, self.max)
            
        def sleep(self):
            for i in range(self.current):
                time.sleep(1)
                if self.break_fct():
                    break
                
            self.increase()
    
    def responseStatus(conn):
    
    Martin Poirier's avatar
    Martin Poirier committed
        with conn.getresponse() as response:
            length = int(response.getheader("content-length", "0"))
            if length > 0:
                response.read()
            return response.status
    
    
    def reporting(report, message, errorType = None):
        if errorType:
            t = 'ERROR'
        else:
            t = 'INFO'
    
        if report:
    
    Thomas Dinges's avatar
    Thomas Dinges committed
            report({t}, message)
    
            return None
        elif errorType:
            raise errorType(message)
        else:
            return None
    
    def clientScan(report = None):
        try:
            s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
            s.settimeout(30)
    
            s.bind(('', 8000))
    
            buf, address = s.recvfrom(64)
    
            address = address[0]
            port = int(str(buf, encoding='utf8'))
    
            reporting(report, "Master server found")
    
            return (address, port)
        except socket.timeout:
            reporting(report, "No master server on network", IOError)
    
            return ("", 8000) # return default values
    
    
    def clientConnection(netsettings, report = None, scan = True, timeout = 5):
        address = netsettings.server_address
        port = netsettings.server_port
        use_ssl = netsettings.use_ssl
        
        if address== "[default]":
    
    #            calling operator from python is fucked, scene isn't in context
    
    #            if bpy:
    #                bpy.ops.render.netclientscan()
    #            else:
    
            if not scan:
                return None
    
            address, port = clientScan()
            if address == "":
                return None
    
        try:
    
            HTTPConnection = http.client.HTTPSConnection if use_ssl else http.client.HTTPConnection
    
    Martin Poirier's avatar
    Martin Poirier committed
            if platform.system() == "Darwin":
                with ConnectionContext(timeout):
    
                    conn = HTTPConnection(address, port)
    
    Martin Poirier's avatar
    Martin Poirier committed
            else:
    
                conn = HTTPConnection(address, port, timeout = timeout)
    
    
            if conn:
                if clientVerifyVersion(conn):
                    return conn
                else:
                    conn.close()
                    reporting(report, "Incorrect master version", ValueError)
        except BaseException as err:
            if report:
    
                report({'ERROR'}, str(err))
    
                return None
            else:
                print(err)
                return None
    
    def clientVerifyVersion(conn):
    
    Martin Poirier's avatar
    Martin Poirier committed
        with ConnectionContext():
            conn.request("GET", "/version")
    
        response = conn.getresponse()
    
        if response.status != http.client.OK:
            conn.close()
            return False
    
        server_version = response.read()
    
        if server_version != VERSION:
            print("Incorrect server version!")
            print("expected", str(VERSION, encoding='utf8'), "received", str(server_version, encoding='utf8'))
            return False
    
        return True
    
    def fileURL(job_id, file_index):
        return "/file_%s_%i" % (job_id, file_index)
    
    def logURL(job_id, frame_number):
        return "/log_%s_%i.log" % (job_id, frame_number)
    
    
    Martin Poirier's avatar
    Martin Poirier committed
    def resultURL(job_id):
        return "/result_%s.zip" % job_id
    
    
    def renderURL(job_id, frame_number):
        return "/render_%s_%i.exr" % (job_id, frame_number)
    
    def cancelURL(job_id):
        return "/cancel_%s" % (job_id)
    
    def hashFile(path):
        f = open(path, "rb")
        value = hashData(f.read())
        f.close()
        return value
        
    def hashData(data):
        m = hashlib.md5()
        m.update(data)
        return m.hexdigest()
    
    Martin Poirier's avatar
    Martin Poirier committed
    
    
    def verifyCreateDir(directory_path):
        original_path = directory_path
        directory_path = os.path.expanduser(directory_path)
        directory_path = os.path.expandvars(directory_path)
        if not os.path.exists(directory_path):
            try:
                os.makedirs(directory_path)
                print("Created directory:", directory_path)
                if original_path != directory_path:
                    print("Expanded from the following path:", original_path)
            except:
                print("Couldn't create directory:", directory_path)
                if original_path != directory_path:
                    print("Expanded from the following path:", original_path)
                raise
        
    
    
    Martin Poirier's avatar
    Martin Poirier committed
    def cacheName(ob, point_cache):
        name = point_cache.name
        if name == "":
            name = "".join(["%02X" % ord(c) for c in ob.name])
            
        return name
    
    def cachePath(file_path):
        path, name = os.path.split(file_path)
        root, ext = os.path.splitext(name)
        return path + os.sep + "blendcache_" + root # need an API call for that
    
    Martin Poirier's avatar
    Martin Poirier committed
    # Process dependencies of all objects with user defined functions
    
    Martin Poirier's avatar
    Martin Poirier committed
    #    pointCacheFunction(object, owner, point_cache) (owner is modifier or psys)
    
    Martin Poirier's avatar
    Martin Poirier committed
    #    fluidFunction(object, modifier, cache_path)
    #    multiresFunction(object, modifier, cache_path)
    def processObjectDependencies(pointCacheFunction, fluidFunction, multiresFunction):
        for object in bpy.data.objects:
            for modifier in object.modifiers:
                if modifier.type == 'FLUID_SIMULATION' and modifier.settings.type == "DOMAIN":
                    fluidFunction(object, modifier, bpy.path.abspath(modifier.settings.filepath))
                elif modifier.type == "CLOTH":
    
    Martin Poirier's avatar
    Martin Poirier committed
                    pointCacheFunction(object, modifier, modifier.point_cache)
    
    Martin Poirier's avatar
    Martin Poirier committed
                elif modifier.type == "SOFT_BODY":
    
    Martin Poirier's avatar
    Martin Poirier committed
                    pointCacheFunction(object, modifier, modifier.point_cache)
    
    Martin Poirier's avatar
    Martin Poirier committed
                elif modifier.type == "SMOKE" and modifier.smoke_type == "DOMAIN":
    
    Martin Poirier's avatar
    Martin Poirier committed
                    pointCacheFunction(object, modifier, modifier.domain_settings.point_cache)
    
    Martin Poirier's avatar
    Martin Poirier committed
                elif modifier.type == "MULTIRES" and modifier.is_external:
                    multiresFunction(object, modifier, bpy.path.abspath(modifier.filepath))
                elif modifier.type == "DYNAMIC_PAINT" and modifier.canvas_settings:
                    for surface in modifier.canvas_settings.canvas_surfaces:
    
    Martin Poirier's avatar
    Martin Poirier committed
                        pointCacheFunction(object, modifier, surface.point_cache)
    
    Martin Poirier's avatar
    Martin Poirier committed
        
            # particles modifier are stupid and don't contain data
            # we have to go through the object property
            for psys in object.particle_systems:
    
    Martin Poirier's avatar
    Martin Poirier committed
                pointCacheFunction(object, psys, psys.point_cache)
    
    Martin Poirier's avatar
    Martin Poirier committed
        
    
    
    Martin Poirier's avatar
    Martin Poirier committed
    def createLocalPath(rfile, prefixdirectory, prefixpath, forcelocal):
        filepath = rfile.original_path
        prefixpath = os.path.normpath(prefixpath) if prefixpath else None
        if (os.path.isabs(filepath) or
            filepath[1:3] == ":/" or filepath[1:3] == ":\\" or # Windows absolute path don't count as absolute on unix, have to handle them ourself
            filepath[:1] == "/" or filepath[:1] == "\\"): # and vice versa
    
    
            # if an absolute path, make sure path exists, if it doesn't, use relative local path
    
    Martin Poirier's avatar
    Martin Poirier committed
            finalpath = filepath
            if forcelocal or not os.path.exists(finalpath):
                path, name = os.path.split(os.path.normpath(finalpath))
                
                # Don't add signatures to cache files, relink fails otherwise
                if not name.endswith(".bphys") and not name.endswith(".bobj.gz"):
                    name, ext = os.path.splitext(name)
                    name = name + "_" + rfile.signature + ext
                
                if prefixpath and path.startswith(prefixpath):
                    suffix = ""
                    while path != prefixpath:
                        path, last = os.path.split(path)
                        suffix = os.path.join(last, suffix)
    
                    directory = os.path.join(prefixdirectory, suffix)
    
    Martin Poirier's avatar
    Martin Poirier committed
    
                    finalpath = os.path.join(directory, name)
    
                else:
    
    Martin Poirier's avatar
    Martin Poirier committed
                    finalpath = os.path.join(prefixdirectory, name)
    
        else:
    
    Martin Poirier's avatar
    Martin Poirier committed
            directory, name = os.path.split(filepath)
    
            # Don't add signatures to cache files
            if not name.endswith(".bphys") and not name.endswith(".bobj.gz"):
                name, ext = os.path.splitext(name)
                name = name + "_" + rfile.signature + ext
            
            directory = directory.replace("../")
            directory = os.path.join(prefixdirectory, directory)
    
    
    Martin Poirier's avatar
    Martin Poirier committed
    
            finalpath = os.path.join(directory, name)
    
    Martin Poirier's avatar
    Martin Poirier committed
        return finalpath
    
    
    def getResults(server_address, server_port, job_id, resolution_x, resolution_y, resolution_percentage, frame_ranges):
        if bpy.app.debug:
            print("=============================================")
            print("============= FETCHING RESULTS ==============")
    
        frame_arguments = []
        for r in frame_ranges:
            if len(r) == 2:
                frame_arguments.extend(["-s", str(r[0]), "-e", str(r[1]), "-a"])
            else:
                frame_arguments.extend(["-f", str(r[0])])
                
        filepath = os.path.join(bpy.app.tempdir, "netrender_temp.blend")
        bpy.ops.wm.save_as_mainfile(filepath=filepath, copy=True, check_existing=False)
    
        arguments = [sys.argv[0], "-b", "-noaudio", filepath, "-o", bpy.path.abspath(bpy.context.scene.render.filepath), "-P", __file__] + frame_arguments + ["--", "GetResults", server_address, str(server_port), job_id, str(resolution_x), str(resolution_y), str(resolution_percentage)]
        if bpy.app.debug:
            print("Starting subprocess:")
            print(" ".join(arguments))
    
        process = subprocess.Popen(arguments, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
        while process.poll() is None:
            stdout = process.stdout.read(1024)
            if bpy.app.debug:
                print(str(stdout, encoding='utf-8'), end="")
            
    
        # read leftovers if needed
        stdout = process.stdout.read()
        if bpy.app.debug:
            print(str(stdout, encoding='utf-8'))
        
        os.remove(filepath)
        
        if bpy.app.debug:
            print("=============================================")
        return
    
    def _getResults(server_address, server_port, job_id, resolution_x, resolution_y, resolution_percentage):
        render = bpy.context.scene.render
        
        netsettings = bpy.context.scene.network_render
    
        netsettings.server_address = server_address
        netsettings.server_port = int(server_port)
        netsettings.job_id = job_id
    
        render.engine = 'NET_RENDER'
        render.resolution_x = int(resolution_x)
        render.resolution_y = int(resolution_y)
        render.resolution_percentage = int(resolution_percentage)
    
        render.use_full_sample = False
        render.use_compositing = False
        render.use_border = False
        
    
    def getFileInfo(filepath, infos):
        process = subprocess.Popen([sys.argv[0], "-b", "-noaudio", filepath, "-P", __file__, "--", "FileInfo"] + infos, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
        stdout = bytes()
        while process.poll() is None:
            stdout += process.stdout.read(1024)
    
        # read leftovers if needed
        stdout += process.stdout.read()
    
        stdout = str(stdout, encoding="utf8")
    
        values = [eval(v[1:].strip()) for v in stdout.split("\n") if v.startswith("$")]
    
        return values
      
    
    if __name__ == "__main__":
        try:
            start = sys.argv.index("--") + 1
        except ValueError:
            start = 0
        action, *args = sys.argv[start:]
        
        if action == "FileInfo": 
            for info in args:
                print("$", eval(info))
        elif action == "GetResults":
            _getResults(args[0], args[1], args[2], args[3], args[4], args[5])