[pyassimp] Large rewrite: more generic, easier to use
Main changes: - dynamic creation of idiomatic python fields corresponding to ASSIMP ones, - hidding of pointers, - use of numpy for transformation and mesh data storage For instance, to access the list of meshes of a children of the root node, previously we did: scene.mRootNode.contents.mChildren[1].contents.mMeshes Now, it is: scene.rootnode.children[1].meshes Arrays are now regular Python list. Also added a 'post-processing' to access directly to certain objects, and not through their index. For instance: Before: mymesh_id = scene.mRootNode.contents.mChildren[1].contents.mMeshes[2] mymesh = scene.mMeshes[mymesh_id] Now: scene.rootnode.children[1].meshes[2] Initialization of the Python wrappers is not delayed anymore: everything is done during the loading (which leads to long start time, but prevent unexpected slowing at runtime) This commit also remove several 'ad-hoc' manipulation that should not be needed anymore. While here, use Python logging when necessary.pull/8/merge^2
parent
bed0e4e83f
commit
7000ea05c5
|
@ -1 +1,369 @@
|
|||
#-*- 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()
|
||||
|
|
|
@ -1,276 +0,0 @@
|
|||
#-*- 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 structs
|
||||
import ctypes
|
||||
import os
|
||||
import helper
|
||||
from errors import AssimpError
|
||||
|
||||
class aiArray:
|
||||
"""
|
||||
A python class to 'safely' access C arrays.
|
||||
For m<Name> and mNum<Name> assimp class members.
|
||||
"""
|
||||
def __init__(self, instance, dataName, sizeName, i=None):
|
||||
self.instance = instance
|
||||
self.dataName = dataName
|
||||
self.sizeName = sizeName
|
||||
self.i = i
|
||||
self.count = 0
|
||||
|
||||
def _GetSize(self):
|
||||
return getattr(self.instance, self.sizeName)
|
||||
|
||||
def _GetData(self, index):
|
||||
if self.i != None:
|
||||
if not bool(getattr(self.instance, self.dataName)[self.i]):
|
||||
return None
|
||||
item = getattr(self.instance, self.dataName)[self.i][index]
|
||||
else:
|
||||
item = getattr(self.instance, self.dataName)[index]
|
||||
if hasattr(item, 'contents'):
|
||||
return item.contents._init()
|
||||
elif hasattr(item, '_init'):
|
||||
return item._init()
|
||||
else:
|
||||
return item
|
||||
|
||||
def next(self):
|
||||
if self.count >= self._GetSize():
|
||||
self.count = 0
|
||||
raise StopIteration
|
||||
else:
|
||||
c = self.count
|
||||
self.count += 1
|
||||
return self._GetData(c)
|
||||
|
||||
def __getitem__(self, index):
|
||||
if isinstance(index, slice):
|
||||
indices = index.indices(len(self))
|
||||
return [self.__getitem__(i) for i in range(*indices)]
|
||||
|
||||
if index < 0 or index >= self._GetSize():
|
||||
raise IndexError("aiArray index out of range")
|
||||
return self._GetData(index)
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
def __len__(self):
|
||||
return int(self._GetSize())
|
||||
|
||||
def __str__(self):
|
||||
return str([x for x in self])
|
||||
|
||||
def __repr__(self):
|
||||
return str([x for x in self])
|
||||
|
||||
class aiTuple:
|
||||
"""
|
||||
A python class to 'safely' access C structs in a python tuple fashion.
|
||||
For C structs like vectors, matrices, colors, ...
|
||||
"""
|
||||
def __init__(self, instance):
|
||||
self.instance = instance
|
||||
self.count = 0
|
||||
|
||||
def _GetSize(self):
|
||||
return len(self.instance._fields_)
|
||||
|
||||
def _GetData(self, index):
|
||||
return getattr(self.instance, self.instance._fields_[index][0])
|
||||
|
||||
def next(self):
|
||||
if self.count >= self._GetSize():
|
||||
self.count = 0
|
||||
raise StopIteration
|
||||
else:
|
||||
c = self.count
|
||||
self.count += 1
|
||||
return self._GetData(c)
|
||||
|
||||
def __getitem__(self, index):
|
||||
if isinstance(index, slice):
|
||||
indices = index.indices(len(self))
|
||||
return [self.__getitem__(i) for i in range(*indices)]
|
||||
|
||||
if index < 0 or index >= self._GetSize():
|
||||
raise IndexError("aiTuple index out of range")
|
||||
return self._GetData(index)
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
def __len__(self):
|
||||
return int(self._GetSize())
|
||||
|
||||
def __str__(self):
|
||||
return str([x for x in self])
|
||||
|
||||
def __repr__(self):
|
||||
return str([x for x in self])
|
||||
|
||||
def _init(self):
|
||||
"""
|
||||
Custom initialize() for C structs, adds safely accessable member functionality.
|
||||
"""
|
||||
if hasattr(self, '_is_init'):
|
||||
return self
|
||||
self._is_init = True
|
||||
|
||||
if str(self.__class__.__name__) == "MaterialProperty":
|
||||
self.mKey._init()
|
||||
|
||||
for m in self.__class__.__dict__.keys():
|
||||
if m.startswith('mNum'):
|
||||
name = m.split('mNum')[1]
|
||||
if 'm'+name in self.__class__.__dict__.keys():
|
||||
setattr(self.__class__, name.lower(), aiArray(self, 'm'+name , m))
|
||||
if name.lower() == "vertices":
|
||||
setattr(self.__class__, "normals", aiArray(self, 'mNormals' , m))
|
||||
setattr(self.__class__, "tangents", aiArray(self, 'mTangents' , m))
|
||||
setattr(self.__class__, "bitangets", aiArray(self, 'mBitangents' , m))
|
||||
setattr(self.__class__, "colors", [aiArray(self, 'mColors' , m, o) for o in xrange(len(self.mColors))])
|
||||
setattr(self.__class__, "texcoords", [aiArray(self, 'mTextureCoords' , m, o) for o in xrange(len(self.mColors))])
|
||||
|
||||
elif m == "x" or m == "a1" or m == "b": # Vector, matrix, quat, color
|
||||
self._tuple = aiTuple(self)
|
||||
setattr(self.__class__, '__getitem__', lambda x, y: x._tuple.__getitem__(y))
|
||||
setattr(self.__class__, '__iter__', lambda x: x._tuple)
|
||||
setattr(self.__class__, 'next', lambda x: x._tuple.next)
|
||||
setattr(self.__class__, '__repr__', lambda x: str([c for c in x]))
|
||||
break
|
||||
elif m == "data": #String
|
||||
setattr(self.__class__, '__repr__', lambda x: str(x.data))
|
||||
setattr(self.__class__, '__str__', lambda x: str(x.data))
|
||||
break
|
||||
|
||||
if hasattr(getattr(self, m), '_init'):
|
||||
getattr(self, m)._init()
|
||||
|
||||
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):
|
||||
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 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)
|
||||
|
||||
return model.contents._init()
|
||||
|
||||
def release(scene):
|
||||
from ctypes import pointer
|
||||
_assimp_lib.release(pointer(scene))
|
||||
|
||||
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 GetMaterialProperties(material):
|
||||
"""
|
||||
Convenience Function to get the material properties as a dict
|
||||
and values in a python format.
|
||||
"""
|
||||
result = {}
|
||||
#read all properties
|
||||
for p in material.properties:
|
||||
#the name
|
||||
key = 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 aiDecomposeMatrix(matrix):
|
||||
if not isinstance(matrix, structs.Matrix4x4):
|
||||
raise AssimpError("aiDecomposeMatrix failed: Not a aiMatrix4x4!")
|
||||
|
||||
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()
|
Loading…
Reference in New Issue