2008-09-17 17:08:36 +00:00
|
|
|
#-*- coding: UTF-8 -*-
|
|
|
|
|
|
|
|
"""
|
|
|
|
PyAssimp
|
|
|
|
|
|
|
|
This is the main-module of PyAssimp.
|
|
|
|
"""
|
|
|
|
|
2008-09-17 15:25:20 +00:00
|
|
|
import structs
|
|
|
|
import ctypes
|
|
|
|
import os
|
2008-09-18 13:17:44 +00:00
|
|
|
import helper
|
2008-09-18 13:36:34 +00:00
|
|
|
from errors import AssimpError
|
2008-09-17 15:25:20 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AssimpLib(object):
|
2008-09-18 13:36:34 +00:00
|
|
|
"""
|
|
|
|
Assimp-Singleton
|
|
|
|
"""
|
|
|
|
load, release = helper.search_library()
|
2008-09-17 15:25:20 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AssimpBase(object):
|
|
|
|
"""
|
|
|
|
Base class for all Assimp-classes.
|
|
|
|
"""
|
|
|
|
|
2008-09-23 13:11:30 +00:00
|
|
|
@staticmethod
|
|
|
|
def _load_array(data, count, cons):
|
2008-09-17 15:25:20 +00:00
|
|
|
"""
|
|
|
|
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 []
|
2008-09-23 13:11:30 +00:00
|
|
|
|
|
|
|
|
|
|
|
@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
|
2008-09-17 15:25:20 +00:00
|
|
|
|
|
|
|
|
2008-09-23 12:38:39 +00:00
|
|
|
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)
|
|
|
|
|
|
|
|
|
2008-09-19 17:52:58 +00:00
|
|
|
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!
|
2008-09-23 13:11:30 +00:00
|
|
|
Bone._load_array(bone.mWeights,
|
2008-09-19 17:52:58 +00:00
|
|
|
bone.mNumWeights,
|
|
|
|
VertexWeight)
|
|
|
|
|
|
|
|
|
2008-09-23 13:11:30 +00:00
|
|
|
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))
|
|
|
|
|
|
|
|
|
2008-09-17 15:25:20 +00:00
|
|
|
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
|
2008-09-23 13:11:30 +00:00
|
|
|
self.meshes = Scene._load_array(model.mMeshes,
|
|
|
|
model.mNumMeshes,
|
|
|
|
Scene.make_loader(Mesh))
|
2008-09-23 12:38:39 +00:00
|
|
|
|
|
|
|
#load materials
|
2008-09-23 13:11:30 +00:00
|
|
|
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))
|
2008-09-17 15:25:20 +00:00
|
|
|
|
|
|
|
|
|
|
|
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]
|
|
|
|
|
|
|
|
|
2008-09-18 13:17:44 +00:00
|
|
|
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)
|
|
|
|
|
|
|
|
|
2008-09-17 15:25:20 +00:00
|
|
|
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
|
2008-09-23 13:11:30 +00:00
|
|
|
self.vertices = Mesh._load_array(mesh.mVertices,
|
2008-09-17 15:25:20 +00:00
|
|
|
mesh.mNumVertices,
|
2008-09-18 13:17:44 +00:00
|
|
|
helper.vec2tuple)
|
2008-09-17 15:25:20 +00:00
|
|
|
|
|
|
|
#load normals
|
2008-09-23 13:11:30 +00:00
|
|
|
self.normals = Mesh._load_array(mesh.mNormals,
|
2008-09-17 15:25:20 +00:00
|
|
|
mesh.mNumVertices,
|
2008-09-18 13:17:44 +00:00
|
|
|
helper.vec2tuple)
|
2008-09-17 15:25:20 +00:00
|
|
|
|
|
|
|
#load tangents
|
2008-09-23 13:11:30 +00:00
|
|
|
self.tangents = Mesh._load_array(mesh.mTangents,
|
2008-09-17 15:25:20 +00:00
|
|
|
mesh.mNumVertices,
|
2008-09-18 13:17:44 +00:00
|
|
|
helper.vec2tuple)
|
2008-09-17 15:25:20 +00:00
|
|
|
|
|
|
|
#load bitangents
|
2008-09-23 13:11:30 +00:00
|
|
|
self.bitangents = Mesh._load_array(mesh.mBitangents,
|
2008-09-17 15:25:20 +00:00
|
|
|
mesh.mNumVertices,
|
2008-09-18 13:17:44 +00:00
|
|
|
helper.vec2tuple)
|
2008-09-17 15:25:20 +00:00
|
|
|
|
|
|
|
#vertex color sets
|
|
|
|
self.colors = self._load_colors(mesh)
|
|
|
|
|
2008-09-18 12:45:03 +00:00
|
|
|
#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
|
2008-09-18 13:17:44 +00:00
|
|
|
self.material_index = int(mesh.mMaterialIndex)
|
|
|
|
|
|
|
|
#faces
|
|
|
|
self.faces = self._load_faces(mesh)
|
2008-09-19 17:52:58 +00:00
|
|
|
|
|
|
|
#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
|
2008-09-23 13:11:30 +00:00
|
|
|
return Mesh._load_array(bones,
|
|
|
|
count,
|
|
|
|
Bone)
|
2008-09-18 13:17:44 +00:00
|
|
|
|
|
|
|
|
|
|
|
def _load_faces(self, mesh):
|
|
|
|
"""
|
|
|
|
Loads all faces.
|
|
|
|
|
|
|
|
mesh - mesh-data
|
|
|
|
|
|
|
|
result faces
|
|
|
|
"""
|
|
|
|
return [Face(mesh.mFaces[i]) for i in range(mesh.mNumFaces)]
|
2008-09-17 15:25:20 +00:00
|
|
|
|
|
|
|
|
|
|
|
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))
|
|
|
|
|
|
|
|
|
2008-09-18 12:45:03 +00:00
|
|
|
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):
|
2008-09-23 13:11:30 +00:00
|
|
|
result.append(Mesh._load_array(mesh.mTextureCoords[i],
|
2008-09-18 12:45:03 +00:00
|
|
|
mesh.mNumVertices,
|
2008-09-18 13:17:44 +00:00
|
|
|
helper.vec2tuple))
|
2008-09-18 12:45:03 +00:00
|
|
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
2008-09-17 15:25:20 +00:00
|
|
|
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)
|