|
- #-*- coding: UTF-8 -*-
-
- """
- PyAssimp
-
- This is the main-module of PyAssimp.
- """
-
- 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)
-
- from . import structs
- from .errors import AssimpError
- from . import helper
-
-
- 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, type = None):
- res = None
-
- if isinstance(ai_obj, structs.Matrix4x4):
- res = numpy.array([getattr(ai_obj, e[0]) for e in ai_obj._fields_]).reshape((4,4))
- #import pdb;pdb.set_trace()
- elif isinstance(ai_obj, structs.Matrix3x3):
- res = numpy.array([getattr(ai_obj, e[0]) for e in ai_obj._fields_]).reshape((3,3))
- else:
- res = numpy.array([getattr(ai_obj, e[0]) for e in ai_obj._fields_])
-
- return res
-
- # 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)]
-
- assimp_struct_inits = \
- { structs.Face : _init_face }
-
- def call_init(obj, caller = None):
- if helper.hasattr_silent(obj,'contents'): #pointer
- _init(obj.contents, obj, caller)
- else:
- _init(obj,parent=caller)
-
- def _is_init_type(obj):
- if helper.hasattr_silent(obj,'contents'): #pointer
- return _is_init_type(obj[0])
- # null-pointer case that arises when we reach a mesh attribute
- # like mBitangents which use mNumVertices rather than mNumBitangents
- # so it breaks the 'is iterable' check.
- # Basically:
- # FIXME!
- elif not bool(obj):
- return False
- tname = obj.__class__.__name__
- return not (tname[:2] == 'c_' or tname == 'Structure' \
- or tname == 'POINTER') and not isinstance(obj,int)
-
- def _init(self, target = None, parent = 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 not target:
- target = self
-
- dirself = dir(self)
- for m in dirself:
-
- if m.startswith("_"):
- continue
-
- if m.startswith('mNum'):
- if 'm' + m[4:] in dirself:
- continue # will be processed later on
- else:
- name = m[1:].lower()
-
- obj = getattr(self, m)
- setattr(target, name, obj)
- continue
-
- if m == 'mName':
- obj = self.mName
- target.name = str(obj.data.decode("utf-8"))
- target.__class__.__repr__ = lambda x: str(x.__class__) + "(" + x.name + ")"
- target.__class__.__str__ = lambda x: x.name
- continue
-
- name = m[1:].lower()
-
- obj = getattr(self, m)
-
- # Create tuples
- if isinstance(obj, assimp_structs_as_tuple):
- setattr(target, name, make_tuple(obj))
- logger.debug(str(self) + ": Added array " + str(getattr(target, name)) + " as self." + name.lower())
- continue
-
- if m.startswith('m'):
-
- if name == "parent":
- setattr(target, name, parent)
- logger.debug("Added a parent as self." + name)
- continue
-
- if helper.hasattr_silent(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
-
-
- if not length: # 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, numpy.array([make_tuple(obj[i]) for i in range(length)], dtype=numpy.float32))
-
- logger.debug(str(self) + ": Added an array of numpy arrays (type "+ str(type(obj)) + ") as self." + name)
-
- else:
- setattr(target, name, [obj[i] for i in range(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
- try:
- init = assimp_struct_inits[type(obj[0])]
- except KeyError:
- if _is_init_type(obj[0]):
- for e in getattr(target, name):
- call_init(e, target)
- else:
- for e in getattr(target, name):
- init(e)
-
-
- except IndexError:
- logger.error("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. Quitting now.")
- sys.exit(1)
-
- except ValueError as e:
-
- logger.error("In " + str(self) + "->" + name + ": " + str(e) + ". Quitting now.")
- if "setting an array element with a sequence" in str(e):
- logger.error("Note that pyassimp does not currently "
- "support meshes with mixed triangles "
- "and quads. Try to load your mesh with"
- " a post-processing to triangulate your"
- " faces.")
- sys.exit(1)
-
-
- else: # starts with 'm' but not iterable
-
- setattr(target, name, obj)
- logger.debug("Added " + name + " as self." + name + " (type: " + str(type(obj)) + ")")
-
- if _is_init_type(obj):
- call_init(obj, target)
-
-
-
-
- if isinstance(self, structs.Mesh):
- _finalize_mesh(self, target)
-
- if isinstance(self, structs.Texture):
- _finalize_texture(self, target)
-
-
- return self
-
- 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
- - ADDTRANSFORMATION: add a reference to an object's transformation taken from their associated node.
-
- :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
-
- if type == "ADDTRANSFORMATION":
-
- def getnode(node, name):
- if node.name == name: return node
- for child in node.children:
- n = getnode(child, name)
- if n: return n
-
- node = getnode(scene.rootnode, obj.name)
- if not node:
- raise AssimpError("Object " + str(obj) + " has no associated node!")
- setattr(obj, "transformation", node.transformation)
-
-
- 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 mesh in node.meshes:
- mesh.material = scene.materials[mesh.materialindex]
-
- 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):
- """
- 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
- #from ctypes import c_char_p, c_uint
- #model = _assimp_lib.load(c_char_p(filename), c_uint(processing))
- model = _assimp_lib.load(filename.encode("ascii"), processing)
- if not model:
- #Uhhh, something went wrong!
- raise AssimpError("could not import file: %s" % filename)
-
- scene = _init(model.contents)
-
- 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 = numpy.array([make_tuple(getattr(tex, "pcData")[i]) for i in range(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 = numpy.array([make_tuple(getattr(mesh, name)[i]) for i in range(nb_vertices)], dtype=numpy.float32)
- setattr(target, name[1:].lower(), data)
- else:
- setattr(target, name[1:].lower(), numpy.array([], dtype="float32"))
-
- 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 range(nb_vertices)])
-
- setattr(target, name[1:].lower(), numpy.array(data, dtype=numpy.float32))
-
- fill("mNormals")
- fill("mTangents")
- fill("mBitangents")
-
- fillarray("mColors")
- fillarray("mTextureCoords")
-
- # prepare faces
- faces = numpy.array([f.indices for f in target.faces], dtype=numpy.int32)
- setattr(target, 'faces', faces)
-
-
- class PropertyGetter(dict):
- def __getitem__(self, key):
- semantic = 0
- if isinstance(key, tuple):
- key, semantic = key
-
- return dict.__getitem__(self, (key, semantic))
-
- def keys(self):
- for k in dict.keys(self):
- yield k[0]
-
- def __iter__(self):
- return self.keys()
-
- def items(self):
- for k, v in dict.items(self):
- yield k[0], v
-
-
- 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 range(length)]:
- #the name
- p = p.contents
- key = (str(p.mKey.data.decode("utf-8")).split('.')[1], p.mSemantic)
-
- #the data
- from ctypes import POINTER, cast, c_int, c_float, sizeof
- if p.mType == 1:
- arr = cast(p.mData, POINTER(c_float * int(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.MaterialPropertyString)).contents.data.decode("utf-8")
- elif p.mType == 4:
- arr = cast(p.mData, POINTER(c_int * int(p.mDataLength/sizeof(c_int)) )).contents
- value = [x for x in arr]
- else:
- value = p.mData[:p.mDataLength]
-
- if len(value) == 1:
- [value] = value
-
- result[key] = value
-
- return PropertyGetter(result)
-
- 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()
|