#-*- coding: UTF-8 -*- """ PyAssimp This is the main-module of PyAssimp. """ import structs import ctypes import os import helper from errors import AssimpError class AssimpLib(object): """ Assimp-Singleton """ load, release = helper.search_library() class AssimpBase(object): """ Base class for all Assimp-classes. """ @staticmethod def _load_array(data, count, cons): """ Loads a whole array out of data, and constructs a new object. If data is NULL, an empty list will be returned. data - pointer to array count - size of the array cons - constructor result array data """ if data: return [cons(data[i]) for i in range(count)] else: return [] @staticmethod def make_loader(function): """ Creates a loader function for "_load_array". function - function to be applied to the content of an element """ def loader(x): return function(x.contents) return loader class Material(object): """ A Material. """ def __init__(self, material): """ Converts the raw material data to a material. """ self.properties = self._load_properties(material.mProperties, material.mNumProperties) def _load_properties(self, data, size): """ Loads all properties of this mateiral. data - properties size - elements in properties """ result = {} #read all properties for i in range(size): p = data[i].contents #the name key = p.mKey.data #the data value = p.mData[:p.mDataLength] result[key] = str(value) return result def __repr__(self): return repr(self.properties) def __str__(self): return str(self.properties) class Matrix(AssimpBase): """ Assimp 4x4-matrix """ def __init__(self, matrix): """ Copies matrix data to this structure. matrix - raw matrix data """ m = matrix self.data = [ [m.a1, m.a2, m.a3, m.a4], [m.b1, m.b2, m.b3, m.b4], [m.c1, m.c2, m.c3, m.c4], [m.d1, m.d2, m.d3, m.d4], ] def __getitem__(self, index): """ Returns an item out of the matrix data. Use (row, column) to access data directly or an natural number n to access the n-th row. index - matrix index result element or row """ try: #tuple as index? x, y = index return data[x][y] except TypeError: #index as index return data[index] def __setitem__(self, index, value): """ Sets an item of the matrix data. Use (row, column) to access data directly or an natural number n to access the n-th row. index - matrix index value - new value """ try: #tuple as index? x, y = index data[x][y] = value except TypeError: #index as index data[index] = value class VertexWeight(AssimpBase): """ Weight for vertices. """ def __init__(self, weight): """ Copies vertex weights to this structure. weight - new weight """ #corresponding vertex id self.vertex = weight.mVertexId #my weight self.weight = weight.mWeight class Bone(AssimpBase): """ Single bone of a mesh. A bone has a name by which it can be found in the frame hierarchy and by which it can be addressed by animations. """ def __init__(self, bone): """ Converts an ASSIMP-bone to a PyAssimp-bone. """ #the name is easy self.name = str(bone.mName) #matrix that transforms from mesh space to bone space in bind pose self.matrix = Matrix(bone.mOffsetMatrix) #and of course the weights! Bone._load_array(bone.mWeights, bone.mNumWeights, VertexWeight) class Texture(AssimpBase): """ Texture included in the model. """ def __init__(self, texture): """ Convertes the raw data to a texture. texture - raw data """ #dimensions self.width = texture.mWidth self.height = texture.mHeight #format hint self.hint = texture.achFormatHint #load data self.data = self._load_data(texture) def _load_data(self, texture): """ Loads the texture data. texture - the texture result texture data in (red, green, blue, alpha) """ if self.height == 0: #compressed data size = self.width else: size = self.width * self.height #load! return Texture._load_array(texture.pcData, size, lambda x: (x.r, x.g, x.b, x.a)) class Scene(AssimpBase): """ The root structure of the imported data. Everything that was imported from the given file can be accessed from here. """ #possible flags FLAGS = {1 : "AI_SCENE_FLAGS_ANIM_SKELETON_ONLY"} def __init__(self, model): """ Converts the model-data to a real scene model - the raw model-data """ #process data self._load(model) def _load(self, model): """ Converts model from raw-data to fancy data! model - pointer to data """ #store scene flags self.flags = model.flags #load mesh-data self.meshes = Scene._load_array(model.mMeshes, model.mNumMeshes, Scene.make_loader(Mesh)) #load materials self.materials = Scene._load_array(model.mMaterials, model.mNumMaterials, Scene.make_loader(Material)) #load textures self.textures = Scene._load_array(model.mTextures, model.mNumTextures, Scene.make_loader(Texture)) def list_flags(self): """ Returns a list of all used flags. result list of flags """ return [name for (key, value) in Scene.FLAGS.iteritems() if (key & self.flags)>0] class Face(AssimpBase): """ A single face in a mesh, referring to multiple vertices. If the number of indices is 3, the face is a triangle, for more than 3 it is a polygon. Point and line primitives are rarely used and are NOT supported. However, a load could pass them as degenerated triangles. """ def __init__(self, face): """ Loads a face from raw-data. """ self.indices = [face.mIndices[i] for i in range(face.mNumIndices)] def __repr__(self): return str(self.indices) class Mesh(AssimpBase): """ A mesh represents a geometry or model with a single material. It usually consists of a number of vertices and a series of primitives/faces referencing the vertices. In addition there might be a series of bones, each of them addressing a number of vertices with a certain weight. Vertex data is presented in channels with each channel containing a single per-vertex information such as a set of texture coords or a normal vector. If a data pointer is non-null, the corresponding data stream is present. A Mesh uses only a single material which is referenced by a material ID. """ def __init__(self, mesh): """ Loads mesh from raw-data. """ #process data self._load(mesh) def _load(self, mesh): """ Loads mesh-data from raw data mesh - raw mesh-data """ #load vertices self.vertices = Mesh._load_array(mesh.mVertices, mesh.mNumVertices, helper.vec2tuple) #load normals self.normals = Mesh._load_array(mesh.mNormals, mesh.mNumVertices, helper.vec2tuple) #load tangents self.tangents = Mesh._load_array(mesh.mTangents, mesh.mNumVertices, helper.vec2tuple) #load bitangents self.bitangents = Mesh._load_array(mesh.mBitangents, mesh.mNumVertices, helper.vec2tuple) #vertex color sets self.colors = self._load_colors(mesh) #number of coordinates per uv-channel self.uvsize = self._load_uv_component_count(mesh) #number of uv channels self.texcoords = self._load_texture_coords(mesh) #the used material self.material_index = int(mesh.mMaterialIndex) #faces self.faces = self._load_faces(mesh) #bones self.bones = self._load_bones(mesh) def _load_bones(self, mesh): """ Loads bones of this mesh. mesh - mesh-data result bones """ count = mesh.mNumBones if count==0: #no bones return [] #read bones bones = mesh.mBones.contents return Mesh._load_array(bones, count, Bone) def _load_faces(self, mesh): """ Loads all faces. mesh - mesh-data result faces """ return [Face(mesh.mFaces[i]) for i in range(mesh.mNumFaces)] def _load_uv_component_count(self, mesh): """ Loads the number of components for a given UV channel. mesh - mesh-data result (count channel 1, count channel 2, ...) """ return tuple(mesh.mNumUVComponents[i] for i in range(structs.MESH.AI_MAX_NUMBER_OF_TEXTURECOORDS)) def _load_texture_coords(self, mesh): """ Loads texture coordinates. mesh - mesh-data result texture coordinates """ result = [] for i in range(structs.MESH.AI_MAX_NUMBER_OF_TEXTURECOORDS): result.append(Mesh._load_array(mesh.mTextureCoords[i], mesh.mNumVertices, helper.vec2tuple)) return result def _load_colors(self, mesh): """ Loads color sets. mesh - mesh with color sets result all color sets """ result = [] #for all possible sets for i in range(structs.MESH.AI_MAX_NUMBER_OF_COLOR_SETS): #try this set x = mesh.mColors[i] if x: channel = [] #read data for al vertices! for j in range(mesh.mNumVertices): c = x[j] channel.append((c.r, c.g, c.b, c.a)) result.append(channel) return result #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) try: #create scene return Scene(model.contents) finally: #forget raw data _assimp_lib.release(model)