From 7000ea05c5e3b6df215347bd00e7c1197b85afed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9verin=20Lemaignan?= Date: Mon, 15 Oct 2012 16:18:44 +0200 Subject: [PATCH] [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. --- port/PyAssimp/pyassimp/__init__.py | 368 +++++++++++++++++++++++++++++ port/PyAssimp/pyassimp/pyassimp.py | 276 ---------------------- 2 files changed, 368 insertions(+), 276 deletions(-) delete mode 100644 port/PyAssimp/pyassimp/pyassimp.py diff --git a/port/PyAssimp/pyassimp/__init__.py b/port/PyAssimp/pyassimp/__init__.py index 229d2296c..c11bd9a09 100644 --- a/port/PyAssimp/pyassimp/__init__.py +++ b/port/PyAssimp/pyassimp/__init__.py @@ -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() diff --git a/port/PyAssimp/pyassimp/pyassimp.py b/port/PyAssimp/pyassimp/pyassimp.py deleted file mode 100644 index 36ecf22c3..000000000 --- a/port/PyAssimp/pyassimp/pyassimp.py +++ /dev/null @@ -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 and mNum 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()