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

pull/359/head
Michael Dawson-Haggerty 2014-09-04 01:04:00 -04:00
parent 7fdcb25516
commit 5ae65987c0
2 changed files with 100 additions and 89 deletions

View File

@ -10,29 +10,20 @@ import sys
if sys.version_info < (2,6): if sys.version_info < (2,6):
raise 'pyassimp: need python 2.6 or newer' raise 'pyassimp: need python 2.6 or newer'
import ctypes import ctypes
import os import os
import numpy import numpy
import logging; logger = logging.getLogger("pyassimp") import logging
logger = logging.getLogger("pyassimp")
# Attach a default, null handler, to the logger. # attach default null handler to logger so it doesn't complain
# applications can easily get log messages from pyassimp # even if you don't attach another handler to logger
# by calling for instance logger.addHandler(logging.NullHandler())
# >>> logging.basicConfig(level=logging.DEBUG)
# before importing pyassimp
class NullHandler(logging.Handler):
def emit(self, record):
pass
h = NullHandler()
logger.addHandler(h)
from . import structs from . import structs
from .errors import AssimpError from .errors import AssimpError
from . import helper from . import helper
assimp_structs_as_tuple = ( assimp_structs_as_tuple = (
structs.Matrix4x4, structs.Matrix4x4,
structs.Matrix3x3, 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 # It is faster and more correct to have an init function for each assimp class
def _init_face(aiFace): 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 = \ assimp_struct_inits = { structs.Face : _init_face }
{ structs.Face : _init_face }
def call_init(obj, caller = None): def call_init(obj, caller = None):
if helper.hasattr_silent(obj,'contents'): #pointer if helper.hasattr_silent(obj,'contents'): #pointer
@ -86,7 +76,7 @@ def _is_init_type(obj):
def _init(self, target = None, parent = None): 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 :param target: set the object which receive the added methods. Useful when manipulating
pointers, to skip the intermediate 'contents' deferencing. pointers, to skip the intermediate 'contents' deferencing.
@ -214,7 +204,7 @@ class AssimpLib(object):
""" """
Assimp-Singleton Assimp-Singleton
""" """
load, release, dll = helper.search_library() load, load_mem, release, dll = helper.search_library()
#the loader as singleton #the loader as singleton
_assimp_lib = AssimpLib() _assimp_lib = AssimpLib()
@ -239,7 +229,6 @@ def pythonize_assimp(type, obj, scene):
return meshes return meshes
if type == "ADDTRANSFORMATION": if type == "ADDTRANSFORMATION":
def getnode(node, name): def getnode(node, name):
if node.name == name: return node if node.name == name: return node
for child in node.children: for child in node.children:
@ -251,51 +240,65 @@ def pythonize_assimp(type, obj, scene):
raise AssimpError("Object " + str(obj) + " has no associated node!") raise AssimpError("Object " + str(obj) + " has no associated node!")
setattr(obj, "transformation", node.transformation) setattr(obj, "transformation", node.transformation)
def recur_pythonize(node, scene): def recur_pythonize(node, scene):
""" Recursively call pythonize_assimp on '''
Recursively call pythonize_assimp on
nodes tree to apply several post-processing to nodes tree to apply several post-processing to
pythonize the assimp datastructures. pythonize the assimp datastructures.
""" '''
node.meshes = pythonize_assimp("MESH", node.meshes, scene) node.meshes = pythonize_assimp("MESH", node.meshes, scene)
for mesh in node.meshes: for mesh in node.meshes:
mesh.material = scene.materials[mesh.materialindex] mesh.material = scene.materials[mesh.materialindex]
for cam in scene.cameras: for cam in scene.cameras:
pythonize_assimp("ADDTRANSFORMATION", cam, scene) pythonize_assimp("ADDTRANSFORMATION", cam, scene)
#for light in scene.lights:
# pythonize_assimp("ADDTRANSFORMATION", light, scene)
for c in node.children: for c in node.children:
recur_pythonize(c, scene) recur_pythonize(c, scene)
def load(filename, processing=0, file_type=None):
def load(filename, processing=0): '''
""" Load a model into a scene. On failure throws AssimpError.
Loads the model with some specific processing parameters.
Arguments
filename - file to load model from ---------
processing - processing parameters filename: Either a filename or a file object to load model from.
If a file object is passed, file_type MUST be specified
result Scene-object with model-data Otherwise Assimp has no idea which importer to use.
This is named 'filename' so as to not break legacy code.
throws AssimpError - could not open file processing: assimp processing parameters
""" file_type: string, such as 'stl'
#read pure data
#from ctypes import c_char_p, c_uint Returns
#model = _assimp_lib.load(c_char_p(filename), c_uint(processing)) ---------
model = _assimp_lib.load(filename.encode("ascii"), processing) 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: if not model:
#Uhhh, something went wrong! raise AssimpError('Could not import file!')
raise AssimpError("could not import file: %s" % filename)
scene = _init(model.contents) scene = _init(model.contents)
recur_pythonize(scene.rootnode, scene) recur_pythonize(scene.rootnode, scene)
return scene return scene
def release(scene): def release(scene):

View File

@ -75,42 +75,50 @@ def get_bounding_box_for_node(node, bb_min, bb_max, transformation):
return bb_min, bb_max return bb_min, bb_max
def try_load_functions(library_path, dll):
'''
def try_load_functions(library,dll,candidates): Try to bind to aiImportFile and aiReleaseImport
"""try to functbind 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: try:
load = dll.aiImportFile load = dll.aiImportFile
release = dll.aiReleaseImport release = dll.aiReleaseImport
load_mem = dll.aiImportFileFromMemory
except AttributeError: except AttributeError:
#OK, this is a library, but it has not the functions we need #OK, this is a library, but it doesn't have the functions we need
pass return None
else:
#Library found! # library found!
from .structs import Scene from .structs import Scene
load.restype = POINTER(Scene) load.restype = POINTER(Scene)
load_mem.restype = POINTER(Scene)
candidates.append((library, load, release, dll)) return (library_path, load, load_mem, release, dll)
def search_library(): 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) Returns: tuple, (load filename function,
exception AssimpError if no library is found release function,
dll,
""" load from memory function)
'''
#this path #this path
folder = os.path.dirname(__file__) folder = os.path.dirname(__file__)
@ -121,7 +129,6 @@ def search_library():
pass pass
candidates = [] candidates = []
# test every file # test every file
for curfolder in [folder]+additional_dirs: for curfolder in [folder]+additional_dirs:
for filename in os.listdir(curfolder): for filename in os.listdir(curfolder):
@ -132,26 +139,27 @@ def search_library():
os.path.splitext(filename)[-1].lower() not in ext_whitelist: os.path.splitext(filename)[-1].lower() not in ext_whitelist:
continue continue
library = os.path.join(curfolder, filename) library_path = os.path.join(curfolder, filename)
logger.debug('Try ' + library) logger.debug('Try ' + library_path)
try: try:
dll = ctypes.cdll.LoadLibrary(library) dll = ctypes.cdll.LoadLibrary(library_path)
except Exception as e: except Exception as e:
logger.warning(str(e)) logger.warning(str(e))
# OK, this except is evil. But different OSs will throw different # OK, this except is evil. But different OSs will throw different
# errors. So just ignore any errors. # errors. So just ignore any errors.
continue continue
loaded = try_load_functions(library_path, dll)
if loaded: candidates.append(loaded)
try_load_functions(library,dll,candidates)
if not candidates: if not candidates:
# no library found # no library_path found
raise AssimpError("assimp library not found") raise AssimpError("assimp library_path not found")
else: else:
# get the newest library # get the newest library_path
candidates = map(lambda x: (os.lstat(x[0])[-2], x), candidates) candidates = map(lambda x: (os.lstat(x[0])[-2], x), candidates)
res = max(candidates, key=operator.itemgetter(0))[1] 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' # XXX: if there are 1000 dll/so files containing 'assimp'
# in their name, do we have all of them in our address # in their name, do we have all of them in our address