370 lines
12 KiB
Python
370 lines
12 KiB
Python
#-*- coding: UTF-8 -*-
|
|
|
|
"""
|
|
PyAssimp
|
|
|
|
This is the main-module of PyAssimp.
|
|
"""
|
|
|
|
import sys
|
|
if sys.version_info < (2,5):
|
|
raise 'pyassimp: need python 2.5 or newer'
|
|
|
|
|
|
import ctypes
|
|
import os
|
|
import helper
|
|
from errors import AssimpError
|
|
|
|
import structs
|
|
|
|
import logging; logger = logging.getLogger("pyassimp")
|
|
|
|
#logging.basicConfig(level=logging.DEBUG)
|
|
logging.basicConfig()
|
|
|
|
assimp_structs_as_tuple = (
|
|
structs.Matrix4x4,
|
|
structs.Matrix3x3,
|
|
structs.Vector2D,
|
|
structs.Vector3D,
|
|
structs.Color3D,
|
|
structs.Color4D,
|
|
structs.Quaternion,
|
|
structs.Plane,
|
|
structs.Texel)
|
|
|
|
def make_tuple(ai_obj):
|
|
return tuple([getattr(ai_obj, e[0]) for e in ai_obj._fields_])
|
|
|
|
def call_init(obj, caller = None):
|
|
# init children
|
|
if hasattr(obj, '_init'):
|
|
logger.debug("Init of children: ")
|
|
obj._init()
|
|
logger.debug("Back to " + str(caller))
|
|
|
|
# pointers
|
|
elif hasattr(obj, 'contents'):
|
|
if hasattr(obj.contents, '_init'):
|
|
logger.debug("Init of children (pointer): ")
|
|
obj.contents._init(target = obj)
|
|
logger.debug("Back to " + str(caller))
|
|
|
|
|
|
|
|
def _init(self, target = None):
|
|
"""
|
|
Custom initialize() for C structs, adds safely accessable member functionality.
|
|
|
|
:param target: set the object which receive the added methods. Useful when manipulating
|
|
pointers, to skip the intermediate 'contents' deferencing.
|
|
"""
|
|
if hasattr(self, '_is_init'):
|
|
logger.debug("" + str(self.__class__) + " already initialized.")
|
|
return self
|
|
self._is_init = True
|
|
|
|
if not target:
|
|
target = self
|
|
|
|
#for m in self.__class__.__dict__.keys():
|
|
|
|
|
|
for m in dir(self):
|
|
|
|
name = m[1:].lower()
|
|
|
|
#if 'normals' in name : import pdb;pdb.set_trace()
|
|
|
|
if m.startswith("_"):
|
|
continue
|
|
|
|
obj = getattr(self, m)
|
|
|
|
if m.startswith('mNum'):
|
|
if 'm' + m[4:] in dir(self):
|
|
continue # will be processed later on
|
|
else:
|
|
setattr(target, name, obj)
|
|
|
|
|
|
|
|
# Create tuples
|
|
if isinstance(obj, assimp_structs_as_tuple):
|
|
setattr(target, name, make_tuple(obj))
|
|
logger.debug(str(self) + ": Added tuple " + str(getattr(target, name)) + " as self." + name.lower())
|
|
continue
|
|
|
|
|
|
if isinstance(obj, structs.String):
|
|
setattr(target, 'name', str(obj.data))
|
|
setattr(target.__class__, '__repr__', lambda x: str(x.__class__) + "(" + x.name + ")")
|
|
setattr(target.__class__, '__str__', lambda x: x.name)
|
|
continue
|
|
|
|
|
|
if m.startswith('m'):
|
|
|
|
|
|
if hasattr(self, 'mNum' + m[1:]):
|
|
|
|
length = getattr(self, 'mNum' + m[1:])
|
|
|
|
# -> special case: properties are
|
|
# stored as a dict.
|
|
if m == 'mProperties':
|
|
setattr(target, name, _get_properties(obj, length))
|
|
continue
|
|
|
|
|
|
#import pdb;pdb.set_trace()
|
|
if not obj: # empty!
|
|
setattr(target, name, [])
|
|
logger.debug(str(self) + ": " + name + " is an empty list.")
|
|
continue
|
|
|
|
|
|
try:
|
|
if obj._type_ in assimp_structs_as_tuple:
|
|
setattr(target, name, [make_tuple(obj[i]) for i in xrange(length)])
|
|
|
|
logger.debug(str(self) + ": Added a list data (type "+ str(type(obj)) + ") as self." + name)
|
|
|
|
else:
|
|
setattr(target, name, [obj[i] for i in xrange(length)]) #TODO: maybe not necessary to recreate an array?
|
|
|
|
logger.debug(str(self) + ": Added list of " + str(obj) + " " + name + " as self." + name + " (type: " + str(type(obj)) + ")")
|
|
|
|
# initialize array elements
|
|
for e in getattr(target, name):
|
|
call_init(e, caller = self)
|
|
|
|
|
|
except IndexError:
|
|
logger.warning("in " + str(self) +" : mismatch between mNum" + name + " and the actual amount of data in m" + name + ". This may be due to version mismatch between libassimp and pyassimp. Skipping this field.")
|
|
except ValueError as e:
|
|
logger.warning(str(e))
|
|
logger.warning("in " + str(self) +" : table of " + name + " not initialized (NULL pointer). Skipping this field.")
|
|
|
|
|
|
else: # starts with 'm' but not iterable
|
|
|
|
setattr(target, name, obj)
|
|
logger.debug("Added " + name + " as self." + name + " (type: " + str(type(obj)) + ")")
|
|
|
|
if name not in ["parent"]: # do not re-initialize my parent
|
|
call_init(obj, caller = self)
|
|
|
|
|
|
|
|
if isinstance(self, structs.Mesh):
|
|
_finalize_mesh(self, target)
|
|
|
|
if isinstance(self, structs.Texture):
|
|
_finalize_texture(self, target)
|
|
|
|
|
|
return self
|
|
|
|
|
|
"""
|
|
Python magic to add the _init() function to all C struct classes.
|
|
"""
|
|
for struct in dir(structs):
|
|
if not (struct.startswith('_') or struct.startswith('c_') or struct == "Structure" or struct == "POINTER") and not isinstance(getattr(structs, struct),int):
|
|
|
|
logger.debug("Adding _init to class " + struct)
|
|
setattr(getattr(structs, struct), '_init', _init)
|
|
|
|
|
|
class AssimpLib(object):
|
|
"""
|
|
Assimp-Singleton
|
|
"""
|
|
load, release, dll = helper.search_library()
|
|
|
|
#the loader as singleton
|
|
_assimp_lib = AssimpLib()
|
|
|
|
def pythonize_assimp(type, obj, scene):
|
|
""" This method modify the Assimp data structures
|
|
to make them easier to work with in Python.
|
|
|
|
Supported operations:
|
|
- MESH: replace a list of mesh IDs by reference to these meshes
|
|
|
|
:param type: the type of modification to operate (cf above)
|
|
:param obj: the input object to modify
|
|
:param scene: a reference to the whole scene
|
|
"""
|
|
|
|
if type == "MESH":
|
|
meshes = []
|
|
for i in obj:
|
|
meshes.append(scene.meshes[i])
|
|
return meshes
|
|
|
|
|
|
def recur_pythonize(node, scene):
|
|
""" 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 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
|
|
model = _assimp_lib.load(filename, processing)
|
|
if not model:
|
|
#Uhhh, something went wrong!
|
|
raise AssimpError, ("could not import file: %s" % filename)
|
|
|
|
#logger.debug("Initializing recursively tuples")
|
|
#_init_tuples(model.contents)
|
|
|
|
logger.debug("Initializing recursively objects")
|
|
scene = model.contents._init()
|
|
|
|
recur_pythonize(scene.rootnode, scene)
|
|
|
|
return scene
|
|
|
|
def release(scene):
|
|
from ctypes import pointer
|
|
_assimp_lib.release(pointer(scene))
|
|
|
|
def _finalize_texture(tex, target):
|
|
setattr(target, "achformathint", tex.achFormatHint)
|
|
data = [make_tuple(getattr(tex, pcData)[i]) for i in xrange(tex.mWidth * tex.mHeight)]
|
|
setattr(target, "data", data)
|
|
|
|
|
|
|
|
def _finalize_mesh(mesh, target):
|
|
""" Building of meshes is a bit specific.
|
|
|
|
We override here the various datasets that can
|
|
not be process as regular fields.
|
|
|
|
For instance, the length of the normals array is
|
|
mNumVertices (no mNumNormals is available)
|
|
"""
|
|
nb_vertices = getattr(mesh, "mNumVertices")
|
|
|
|
def fill(name):
|
|
mAttr = getattr(mesh, name)
|
|
if mAttr:
|
|
data = [make_tuple(getattr(mesh, name)[i]) for i in xrange(nb_vertices)]
|
|
setattr(target, name[1:].lower(), data)
|
|
else:
|
|
setattr(target, name[1:].lower(), [])
|
|
|
|
def fillarray(name):
|
|
mAttr = getattr(mesh, name)
|
|
|
|
data = []
|
|
for index, mSubAttr in enumerate(mAttr):
|
|
if mSubAttr:
|
|
data.append([make_tuple(getattr(mesh, name)[index][i]) for i in xrange(nb_vertices)])
|
|
|
|
setattr(target, name[1:].lower(), data)
|
|
|
|
fill("mNormals")
|
|
fill("mTangents")
|
|
fill("mBitangents")
|
|
|
|
fillarray("mColors")
|
|
fillarray("mTextureCoords")
|
|
|
|
def _get_properties(properties, length):
|
|
"""
|
|
Convenience Function to get the material properties as a dict
|
|
and values in a python format.
|
|
"""
|
|
result = {}
|
|
#read all properties
|
|
for p in [properties[i] for i in xrange(length)]:
|
|
#the name
|
|
p = p.contents
|
|
key = str(p.mKey.data)
|
|
|
|
#the data
|
|
from ctypes import POINTER, cast, c_int, c_float, sizeof
|
|
if p.mType == 1:
|
|
arr = cast(p.mData, POINTER(c_float*(p.mDataLength/sizeof(c_float)) )).contents
|
|
value = [x for x in arr]
|
|
elif p.mType == 3: #string can't be an array
|
|
value = cast(p.mData, POINTER(structs.String)).contents.data
|
|
elif p.mType == 4:
|
|
arr = cast(p.mData, POINTER(c_int*(p.mDataLength/sizeof(c_int)) )).contents
|
|
value = [x for x in arr]
|
|
else:
|
|
value = p.mData[:p.mDataLength]
|
|
|
|
result[key] = value
|
|
|
|
return result
|
|
|
|
def aiGetMaterialFloatArray(material, key):
|
|
AI_SUCCESS = 0
|
|
from ctypes import byref, pointer, cast, c_float, POINTER, sizeof, c_uint
|
|
out = structs.Color4D()
|
|
max = c_uint(sizeof(structs.Color4D))
|
|
r=_assimp_lib.dll.aiGetMaterialFloatArray(pointer(material),
|
|
key[0],
|
|
key[1],
|
|
key[2],
|
|
byref(out),
|
|
byref(max))
|
|
|
|
if (r != AI_SUCCESS):
|
|
raise AssimpError("aiGetMaterialFloatArray failed!")
|
|
|
|
out._init()
|
|
return [out[i] for i in xrange(max.value)]
|
|
|
|
def aiGetMaterialString(material, key):
|
|
AI_SUCCESS = 0
|
|
from ctypes import byref, pointer, cast, c_float, POINTER, sizeof, c_uint
|
|
out = structs.String()
|
|
r=_assimp_lib.dll.aiGetMaterialString(pointer(material),
|
|
key[0],
|
|
key[1],
|
|
key[2],
|
|
byref(out))
|
|
|
|
if (r != AI_SUCCESS):
|
|
raise AssimpError("aiGetMaterialString failed!")
|
|
|
|
return str(out.data)
|
|
|
|
|
|
|
|
def decompose_matrix(matrix):
|
|
if not isinstance(matrix, structs.Matrix4x4):
|
|
raise AssimpError("pyassimp.decompose_matrix failed: Not a Matrix4x4!")
|
|
|
|
scaling = structs.Vector3D()
|
|
rotation = structs.Quaternion()
|
|
position = structs.Vector3D()
|
|
|
|
from ctypes import byref, pointer
|
|
_assimp_lib.dll.aiDecomposeMatrix(pointer(matrix), byref(scaling), byref(rotation), byref(position))
|
|
return scaling._init(), rotation._init(), position._init()
|