added the ability to load from file objects with pyassimp.load, as opposed to only being able to load from paths
parent
7fdcb25516
commit
5ae65987c0
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue