From 5ae65987c0c42cddfa8196e28d86097eb63c527a Mon Sep 17 00:00:00 2001 From: Michael Dawson-Haggerty Date: Thu, 4 Sep 2014 01:04:00 -0400 Subject: [PATCH] added the ability to load from file objects with pyassimp.load, as opposed to only being able to load from paths --- port/PyAssimp/pyassimp/core.py | 101 ++++++++++++++++--------------- port/PyAssimp/pyassimp/helper.py | 88 +++++++++++++++------------ 2 files changed, 100 insertions(+), 89 deletions(-) diff --git a/port/PyAssimp/pyassimp/core.py b/port/PyAssimp/pyassimp/core.py index 4e16718ac..e51e8f5a8 100644 --- a/port/PyAssimp/pyassimp/core.py +++ b/port/PyAssimp/pyassimp/core.py @@ -10,29 +10,20 @@ import sys if sys.version_info < (2,6): raise 'pyassimp: need python 2.6 or newer' - import ctypes import os import numpy -import logging; logger = logging.getLogger("pyassimp") - -# Attach a default, null handler, to the logger. -# applications can easily get log messages from pyassimp -# by calling for instance -# >>> logging.basicConfig(level=logging.DEBUG) -# before importing pyassimp -class NullHandler(logging.Handler): - def emit(self, record): - pass -h = NullHandler() -logger.addHandler(h) +import logging +logger = logging.getLogger("pyassimp") +# attach default null handler to logger so it doesn't complain +# even if you don't attach another handler to logger +logger.addHandler(logging.NullHandler()) from . import structs from .errors import AssimpError from . import helper - assimp_structs_as_tuple = ( structs.Matrix4x4, structs.Matrix3x3, @@ -59,10 +50,9 @@ def make_tuple(ai_obj, type = None): # It is faster and more correct to have an init function for each assimp class def _init_face(aiFace): - aiFace.indices = [aiFace.mIndices[i] for i in range(aiFace.mNumIndices)] + aiFace.indices = [aiFace.mIndices[i] for i in xrange(aiFace.mNumIndices)] -assimp_struct_inits = \ - { structs.Face : _init_face } +assimp_struct_inits = { structs.Face : _init_face } def call_init(obj, caller = None): if helper.hasattr_silent(obj,'contents'): #pointer @@ -86,7 +76,7 @@ def _is_init_type(obj): def _init(self, target = None, parent = None): """ - Custom initialize() for C structs, adds safely accessable member functionality. + Custom initialize() for C structs, adds safely accessible member functionality. :param target: set the object which receive the added methods. Useful when manipulating pointers, to skip the intermediate 'contents' deferencing. @@ -214,7 +204,7 @@ class AssimpLib(object): """ Assimp-Singleton """ - load, release, dll = helper.search_library() + load, load_mem, release, dll = helper.search_library() #the loader as singleton _assimp_lib = AssimpLib() @@ -239,7 +229,6 @@ def pythonize_assimp(type, obj, scene): return meshes if type == "ADDTRANSFORMATION": - def getnode(node, name): if node.name == name: return node for child in node.children: @@ -251,51 +240,65 @@ def pythonize_assimp(type, obj, scene): raise AssimpError("Object " + str(obj) + " has no associated node!") setattr(obj, "transformation", node.transformation) - def recur_pythonize(node, scene): - """ Recursively call pythonize_assimp on + ''' + Recursively call pythonize_assimp on nodes tree to apply several post-processing to pythonize the assimp datastructures. - """ - + ''' node.meshes = pythonize_assimp("MESH", node.meshes, scene) - + for mesh in node.meshes: mesh.material = scene.materials[mesh.materialindex] for cam in scene.cameras: pythonize_assimp("ADDTRANSFORMATION", cam, scene) - #for light in scene.lights: - # pythonize_assimp("ADDTRANSFORMATION", light, scene) - for c in node.children: recur_pythonize(c, scene) - -def load(filename, processing=0): - """ - Loads the model with some specific processing parameters. - - filename - file to load model from - processing - processing parameters - - result Scene-object with model-data - - throws AssimpError - could not open file - """ - #read pure data - #from ctypes import c_char_p, c_uint - #model = _assimp_lib.load(c_char_p(filename), c_uint(processing)) - model = _assimp_lib.load(filename.encode("ascii"), processing) +def load(filename, processing=0, file_type=None): + ''' + Load a model into a scene. On failure throws AssimpError. + + Arguments + --------- + filename: Either a filename or a file object to load model from. + If a file object is passed, file_type MUST be specified + Otherwise Assimp has no idea which importer to use. + This is named 'filename' so as to not break legacy code. + processing: assimp processing parameters + file_type: string, such as 'stl' + + Returns + --------- + Scene object with model-data + ''' + + if hasattr(filename, 'read'): + ''' + This is the case where a file object has been passed to load. + It is calling the following function: + const aiScene* aiImportFileFromMemory(const char* pBuffer, + unsigned int pLength, + unsigned int pFlags, + const char* pHint) + ''' + if file_type == None: + raise AssimpError('File type must be specified when passing file objects!') + data = filename.read() + model = _assimp_lib.load_mem(data, + len(data), + processing, + file_type) + else: + # a filename string has been passed + model = _assimp_lib.load(filename.encode("ascii"), processing) + if not model: - #Uhhh, something went wrong! - raise AssimpError("could not import file: %s" % filename) - + raise AssimpError('Could not import file!') scene = _init(model.contents) - recur_pythonize(scene.rootnode, scene) - return scene def release(scene): diff --git a/port/PyAssimp/pyassimp/helper.py b/port/PyAssimp/pyassimp/helper.py index 21cefdfc5..47794298c 100644 --- a/port/PyAssimp/pyassimp/helper.py +++ b/port/PyAssimp/pyassimp/helper.py @@ -75,42 +75,50 @@ def get_bounding_box_for_node(node, bb_min, bb_max, transformation): return bb_min, bb_max - - -def try_load_functions(library,dll,candidates): - """try to functbind to aiImportFile and aiReleaseImport +def try_load_functions(library_path, dll): + ''' + Try to bind to aiImportFile and aiReleaseImport + + Arguments + --------- + library_path: path to current lib + dll: ctypes handle to library + + Returns + --------- + If unsuccessful: + None + If successful: + Tuple containing (library_path, + load function, + release function, + ctypes handle to assimp library) + ''' - library - path to current lib - dll - ctypes handle to it - candidates - receives matching candidates - - They serve as signal functions to detect assimp, - also they're currently the only functions we need. - insert (library,aiImportFile,aiReleaseImport,dll) - into 'candidates' if successful. - - """ try: - load = dll.aiImportFile - release = dll.aiReleaseImport + load = dll.aiImportFile + release = dll.aiReleaseImport + load_mem = dll.aiImportFileFromMemory except AttributeError: - #OK, this is a library, but it has not the functions we need - pass - else: - #Library found! - from .structs import Scene - load.restype = POINTER(Scene) - - candidates.append((library, load, release, dll)) - + #OK, this is a library, but it doesn't have the functions we need + return None + + # library found! + from .structs import Scene + load.restype = POINTER(Scene) + load_mem.restype = POINTER(Scene) + return (library_path, load, load_mem, release, dll) def search_library(): - """Loads the assimp-Library. + ''' + Loads the assimp library. + Throws exception AssimpError if no library_path is found - result (load-function, release-function) - exception AssimpError if no library is found - - """ + Returns: tuple, (load filename function, + release function, + dll, + load from memory function) + ''' #this path folder = os.path.dirname(__file__) @@ -121,7 +129,6 @@ def search_library(): pass candidates = [] - # test every file for curfolder in [folder]+additional_dirs: for filename in os.listdir(curfolder): @@ -132,26 +139,27 @@ def search_library(): os.path.splitext(filename)[-1].lower() not in ext_whitelist: continue - library = os.path.join(curfolder, filename) - logger.debug('Try ' + library) + library_path = os.path.join(curfolder, filename) + logger.debug('Try ' + library_path) try: - dll = ctypes.cdll.LoadLibrary(library) + dll = ctypes.cdll.LoadLibrary(library_path) except Exception as e: logger.warning(str(e)) # OK, this except is evil. But different OSs will throw different # errors. So just ignore any errors. continue + + loaded = try_load_functions(library_path, dll) + if loaded: candidates.append(loaded) - try_load_functions(library,dll,candidates) - if not candidates: - # no library found - raise AssimpError("assimp library not found") + # no library_path found + raise AssimpError("assimp library_path not found") else: - # get the newest library + # get the newest library_path candidates = map(lambda x: (os.lstat(x[0])[-2], x), candidates) res = max(candidates, key=operator.itemgetter(0))[1] - logger.debug('Using assimp library located at ' + res[0]) + logger.debug('Using assimp library_path located at ' + res[0]) # XXX: if there are 1000 dll/so files containing 'assimp' # in their name, do we have all of them in our address