Browse Source

added the ability to load from file objects with pyassimp.load, as opposed to only being able to load from paths

Michael Dawson-Haggerty 11 years ago
parent
commit
5ae65987c0
2 changed files with 100 additions and 89 deletions
  1. 52 49
      port/PyAssimp/pyassimp/core.py
  2. 48 40
      port/PyAssimp/pyassimp/helper.py

+ 52 - 49
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):

+ 48 - 40
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