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):
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,13 +240,12 @@ 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:
@ -266,36 +254,51 @@ def recur_pythonize(node, scene):
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, file_type=None):
'''
Load a model into a scene. On failure throws AssimpError.
def load(filename, processing=0):
"""
Loads the model with some specific processing parameters.
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'
filename - file to load model from
processing - processing parameters
Returns
---------
Scene object with model-data
'''
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))
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):

View File

@ -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_path, dll):
'''
Try to bind to aiImportFile and aiReleaseImport
Arguments
---------
library_path: path to current lib
dll: ctypes handle to library
def try_load_functions(library,dll,candidates):
"""try to functbind to aiImportFile and aiReleaseImport
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_mem = dll.aiImportFileFromMemory
except AttributeError:
#OK, this is a library, but it has not the functions we need
pass
else:
#Library found!
#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)
candidates.append((library, load, release, dll))
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
try_load_functions(library,dll,candidates)
loaded = try_load_functions(library_path, dll)
if loaded: candidates.append(loaded)
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