- add missing files from the last commit
git-svn-id: https://assimp.svn.sourceforge.net/svnroot/assimp/trunk@1334 67173fc5-114c-0410-ac8e-9d2fd5bffc1fpull/11/merge
parent
0313e5b5b4
commit
6d3cedc0b1
|
@ -0,0 +1,74 @@
|
|||
PyAssimp Readme
|
||||
===============
|
||||
|
||||
-- a simple Python wrapper for Assimp using ctypes to access
|
||||
the library. Requires Python >= 2.6.
|
||||
|
||||
Python 3 support is mostly here, but not well tested.
|
||||
|
||||
Note that pyassimp is not complete. Many ASSIMP features are missing. In
|
||||
particular, only loading of models is currently supported (no export).
|
||||
|
||||
USAGE
|
||||
-----
|
||||
|
||||
To get started with pyAssimp, examine the sample.py script in scripts/, which
|
||||
illustrates the basic usage. All Assimp data structures are wrapped using
|
||||
ctypes. All the data+length fields in Assimp's data structures (such as
|
||||
'aiMesh::mNumVertices','aiMesh::mVertices') are replaced by simple python
|
||||
lists, so you can call len() on them to get their respective size and access
|
||||
members using [].
|
||||
|
||||
For example, to load a file named 'hello.3ds' and print the first
|
||||
vertex of the first mesh, you would do (proper error handling
|
||||
substituted by assertions ...):
|
||||
|
||||
```python
|
||||
|
||||
from pyassimp.core import *
|
||||
scene = load('hello.3ds')
|
||||
|
||||
assert len(scene.meshes)
|
||||
mesh = scene.meshes[0]
|
||||
|
||||
assert len(mesh.vertices)
|
||||
print(mesh.vertices[0])
|
||||
|
||||
# don't forget this one, or you will leak!
|
||||
release(scene)
|
||||
|
||||
```
|
||||
|
||||
Another example to list the 'top nodes' in a
|
||||
scene:
|
||||
|
||||
```python
|
||||
|
||||
from pyassimp.core import *
|
||||
scene = load('hello.3ds')
|
||||
|
||||
for c in scene.rootnode.children:
|
||||
print(str(c))
|
||||
|
||||
release(scene)
|
||||
|
||||
```
|
||||
|
||||
INSTALL
|
||||
-------
|
||||
|
||||
Install pyassimp by running:
|
||||
|
||||
> python setup.py install
|
||||
|
||||
PyAssimp requires a assimp dynamic library (DLL on windows,
|
||||
so on linux :-) in order to work. The default search directories
|
||||
are:
|
||||
|
||||
- the current directory
|
||||
- on linux additionally: /usr/lib and /usr/local/lib
|
||||
|
||||
To build that library, refer to the Assimp master INSTALL
|
||||
instructions. To look in more places, edit ./pyassimp/helper.py.
|
||||
There's an 'additional_dirs' list waiting for your entries.
|
||||
|
|
@ -0,0 +1,415 @@
|
|||
#-*- 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
|
||||
|
||||
def call_init(obj, caller = None):
|
||||
# init children
|
||||
if hasattr(obj, '_init'):
|
||||
obj._init(parent = caller)
|
||||
|
||||
# pointers
|
||||
elif hasattr(obj, 'contents'):
|
||||
if hasattr(obj.contents, '_init'):
|
||||
obj.contents._init(target = obj, parent = caller)
|
||||
|
||||
|
||||
|
||||
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 hasattr(self, '_is_init'):
|
||||
return self
|
||||
self._is_init = True
|
||||
|
||||
if not target:
|
||||
target = self
|
||||
|
||||
for m in dir(self):
|
||||
|
||||
name = m[1:].lower()
|
||||
|
||||
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 array " + 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 name == "parent":
|
||||
setattr(target, name, parent)
|
||||
logger.debug("Added a parent as self." + name)
|
||||
continue
|
||||
|
||||
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
|
||||
|
||||
|
||||
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
|
||||
for e in getattr(target, name):
|
||||
call_init(e, caller = target)
|
||||
|
||||
|
||||
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)) + ")")
|
||||
|
||||
call_init(obj, caller = target)
|
||||
|
||||
|
||||
|
||||
|
||||
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):
|
||||
|
||||
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
|
||||
- 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 = 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 = 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(), [])
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
#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 = numpy.array([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 * int(p.mDataLength/sizeof(c_int)) )).contents
|
||||
value = numpy.array([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 range(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()
|
|
@ -0,0 +1,529 @@
|
|||
# <hr>Calculates the tangents and bitangents for the imported meshes.
|
||||
#
|
||||
# Does nothing if a mesh does not have normals. You might want this post
|
||||
# processing step to be executed if you plan to use tangent space calculations
|
||||
# such as normal mapping applied to the meshes. There's a config setting,
|
||||
# <tt>#AI_CONFIG_PP_CT_MAX_SMOOTHING_ANGLE<tt>, which allows you to specify
|
||||
# a maximum smoothing angle for the algorithm. However, usually you'll
|
||||
# want to leave it at the default value.
|
||||
#
|
||||
aiProcess_CalcTangentSpace = 0x1
|
||||
|
||||
## <hr>Identifies and joins identical vertex data sets within all
|
||||
# imported meshes.
|
||||
#
|
||||
# After this step is run, each mesh contains unique vertices,
|
||||
# so a vertex may be used by multiple faces. You usually want
|
||||
# to use this post processing step. If your application deals with
|
||||
# indexed geometry, this step is compulsory or you'll just waste rendering
|
||||
# time. <b>If this flag is not specified<b>, no vertices are referenced by
|
||||
# more than one face and <b>no index buffer is required<b> for rendering.
|
||||
#
|
||||
aiProcess_JoinIdenticalVertices = 0x2
|
||||
|
||||
## <hr>Converts all the imported data to a left-handed coordinate space.
|
||||
#
|
||||
# By default the data is returned in a right-handed coordinate space (which
|
||||
# OpenGL prefers). In this space, +X points to the right,
|
||||
# +Z points towards the viewer, and +Y points upwards. In the DirectX
|
||||
# coordinate space +X points to the right, +Y points upwards, and +Z points
|
||||
# away from the viewer.
|
||||
#
|
||||
# You'll probably want to consider this flag if you use Direct3D for
|
||||
# rendering. The #aiProcess_ConvertToLeftHanded flag supersedes this
|
||||
# setting and bundles all conversions typically required for D3D-based
|
||||
# applications.
|
||||
#
|
||||
aiProcess_MakeLeftHanded = 0x4
|
||||
|
||||
## <hr>Triangulates all faces of all meshes.
|
||||
#
|
||||
# By default the imported mesh data might contain faces with more than 3
|
||||
# indices. For rendering you'll usually want all faces to be triangles.
|
||||
# This post processing step splits up faces with more than 3 indices into
|
||||
# triangles. Line and point primitives are #not# modified! If you want
|
||||
# 'triangles only' with no other kinds of primitives, try the following
|
||||
# solution:
|
||||
# <ul>
|
||||
# <li>Specify both #aiProcess_Triangulate and #aiProcess_SortByPType <li>
|
||||
# <li>Ignore all point and line meshes when you process assimp's output<li>
|
||||
# <ul>
|
||||
#
|
||||
aiProcess_Triangulate = 0x8
|
||||
|
||||
## <hr>Removes some parts of the data structure (animations, materials,
|
||||
# light sources, cameras, textures, vertex components).
|
||||
#
|
||||
# The components to be removed are specified in a separate
|
||||
# configuration option, <tt>#AI_CONFIG_PP_RVC_FLAGS<tt>. This is quite useful
|
||||
# if you don't need all parts of the output structure. Vertex colors
|
||||
# are rarely used today for example... Calling this step to remove unneeded
|
||||
# data from the pipeline as early as possible results in increased
|
||||
# performance and a more optimized output data structure.
|
||||
# This step is also useful if you want to force Assimp to recompute
|
||||
# normals or tangents. The corresponding steps don't recompute them if
|
||||
# they're already there (loaded from the source asset). By using this
|
||||
# step you can make sure they are NOT there.
|
||||
#
|
||||
# This flag is a poor one, mainly because its purpose is usually
|
||||
# misunderstood. Consider the following case: a 3D model has been exported
|
||||
# from a CAD app, and it has per-face vertex colors. Vertex positions can't be
|
||||
# shared, thus the #aiProcess_JoinIdenticalVertices step fails to
|
||||
# optimize the data because of these nasty little vertex colors.
|
||||
# Most apps don't even process them, so it's all for nothing. By using
|
||||
# this step, unneeded components are excluded as early as possible
|
||||
# thus opening more room for internal optimizations.
|
||||
#
|
||||
aiProcess_RemoveComponent = 0x10
|
||||
|
||||
## <hr>Generates normals for all faces of all meshes.
|
||||
#
|
||||
# This is ignored if normals are already there at the time this flag
|
||||
# is evaluated. Model importers try to load them from the source file, so
|
||||
# they're usually already there. Face normals are shared between all points
|
||||
# of a single face, so a single point can have multiple normals, which
|
||||
# forces the library to duplicate vertices in some cases.
|
||||
# #aiProcess_JoinIdenticalVertices is #senseless# then.
|
||||
#
|
||||
# This flag may not be specified together with #aiProcess_GenSmoothNormals.
|
||||
#
|
||||
aiProcess_GenNormals = 0x20
|
||||
|
||||
## <hr>Generates smooth normals for all vertices in the mesh.
|
||||
#
|
||||
# This is ignored if normals are already there at the time this flag
|
||||
# is evaluated. Model importers try to load them from the source file, so
|
||||
# they're usually already there.
|
||||
#
|
||||
# This flag may not be specified together with
|
||||
# #aiProcess_GenNormals. There's a configuration option,
|
||||
# <tt>#AI_CONFIG_PP_GSN_MAX_SMOOTHING_ANGLE<tt> which allows you to specify
|
||||
# an angle maximum for the normal smoothing algorithm. Normals exceeding
|
||||
# this limit are not smoothed, resulting in a 'hard' seam between two faces.
|
||||
# Using a decent angle here (e.g. 80 degrees) results in very good visual
|
||||
# appearance.
|
||||
#
|
||||
aiProcess_GenSmoothNormals = 0x40
|
||||
|
||||
## <hr>Splits large meshes into smaller sub-meshes.
|
||||
#
|
||||
# This is quite useful for real-time rendering, where the number of triangles
|
||||
# which can be maximally processed in a single draw-call is limited
|
||||
# by the video driverhardware. The maximum vertex buffer is usually limited
|
||||
# too. Both requirements can be met with this step: you may specify both a
|
||||
# triangle and vertex limit for a single mesh.
|
||||
#
|
||||
# The split limits can (and should!) be set through the
|
||||
# <tt>#AI_CONFIG_PP_SLM_VERTEX_LIMIT<tt> and <tt>#AI_CONFIG_PP_SLM_TRIANGLE_LIMIT<tt>
|
||||
# settings. The default values are <tt>#AI_SLM_DEFAULT_MAX_VERTICES<tt> and
|
||||
# <tt>#AI_SLM_DEFAULT_MAX_TRIANGLES<tt>.
|
||||
#
|
||||
# Note that splitting is generally a time-consuming task, but only if there's
|
||||
# something to split. The use of this step is recommended for most users.
|
||||
#
|
||||
aiProcess_SplitLargeMeshes = 0x80
|
||||
|
||||
## <hr>Removes the node graph and pre-transforms all vertices with
|
||||
# the local transformation matrices of their nodes.
|
||||
#
|
||||
# The output scene still contains nodes, however there is only a
|
||||
# root node with children, each one referencing only one mesh,
|
||||
# and each mesh referencing one material. For rendering, you can
|
||||
# simply render all meshes in order - you don't need to pay
|
||||
# attention to local transformations and the node hierarchy.
|
||||
# Animations are removed during this step.
|
||||
# This step is intended for applications without a scenegraph.
|
||||
# The step CAN cause some problems: if e.g. a mesh of the asset
|
||||
# contains normals and another, using the same material index, does not,
|
||||
# they will be brought together, but the first meshes's part of
|
||||
# the normal list is zeroed. However, these artifacts are rare.
|
||||
# @note The <tt>#AI_CONFIG_PP_PTV_NORMALIZE<tt> configuration property
|
||||
# can be set to normalize the scene's spatial dimension to the -1...1
|
||||
# range.
|
||||
#
|
||||
aiProcess_PreTransformVertices = 0x100
|
||||
|
||||
## <hr>Limits the number of bones simultaneously affecting a single vertex
|
||||
# to a maximum value.
|
||||
#
|
||||
# If any vertex is affected by more than the maximum number of bones, the least
|
||||
# important vertex weights are removed and the remaining vertex weights are
|
||||
# renormalized so that the weights still sum up to 1.
|
||||
# The default bone weight limit is 4 (defined as <tt>#AI_LMW_MAX_WEIGHTS<tt> in
|
||||
# config.h), but you can use the <tt>#AI_CONFIG_PP_LBW_MAX_WEIGHTS<tt> setting to
|
||||
# supply your own limit to the post processing step.
|
||||
#
|
||||
# If you intend to perform the skinning in hardware, this post processing
|
||||
# step might be of interest to you.
|
||||
#
|
||||
aiProcess_LimitBoneWeights = 0x200
|
||||
|
||||
## <hr>Validates the imported scene data structure.
|
||||
# This makes sure that all indices are valid, all animations and
|
||||
# bones are linked correctly, all material references are correct .. etc.
|
||||
#
|
||||
# It is recommended that you capture Assimp's log output if you use this flag,
|
||||
# so you can easily find out what's wrong if a file fails the
|
||||
# validation. The validator is quite strict and will find #all#
|
||||
# inconsistencies in the data structure... It is recommended that plugin
|
||||
# developers use it to debug their loaders. There are two types of
|
||||
# validation failures:
|
||||
# <ul>
|
||||
# <li>Error: There's something wrong with the imported data. Further
|
||||
# postprocessing is not possible and the data is not usable at all.
|
||||
# The import fails. #Importer::GetErrorString() or #aiGetErrorString()
|
||||
# carry the error message around.<li>
|
||||
# <li>Warning: There are some minor issues (e.g. 1000000 animation
|
||||
# keyframes with the same time), but further postprocessing and use
|
||||
# of the data structure is still safe. Warning details are written
|
||||
# to the log file, <tt>#AI_SCENE_FLAGS_VALIDATION_WARNING<tt> is set
|
||||
# in #aiScene::mFlags<li>
|
||||
# <ul>
|
||||
#
|
||||
# This post-processing step is not time-consuming. Its use is not
|
||||
# compulsory, but recommended.
|
||||
#
|
||||
aiProcess_ValidateDataStructure = 0x400
|
||||
|
||||
## <hr>Reorders triangles for better vertex cache locality.
|
||||
#
|
||||
# The step tries to improve the ACMR (average post-transform vertex cache
|
||||
# miss ratio) for all meshes. The implementation runs in O(n) and is
|
||||
# roughly based on the 'tipsify' algorithm (see <a href="
|
||||
# http:www.cs.princeton.edugfxpubsSander_2007_%3ETRtipsy.pdf">this
|
||||
# paper<a>).
|
||||
#
|
||||
# If you intend to render huge models in hardware, this step might
|
||||
# be of interest to you. The <tt>#AI_CONFIG_PP_ICL_PTCACHE_SIZE<tt>config
|
||||
# setting can be used to fine-tune the cache optimization.
|
||||
#
|
||||
aiProcess_ImproveCacheLocality = 0x800
|
||||
|
||||
## <hr>Searches for redundantunreferenced materials and removes them.
|
||||
#
|
||||
# This is especially useful in combination with the
|
||||
# #aiProcess_PretransformVertices and #aiProcess_OptimizeMeshes flags.
|
||||
# Both join small meshes with equal characteristics, but they can't do
|
||||
# their work if two meshes have different materials. Because several
|
||||
# material settings are lost during Assimp's import filters,
|
||||
# (and because many exporters don't check for redundant materials), huge
|
||||
# models often have materials which are are defined several times with
|
||||
# exactly the same settings.
|
||||
#
|
||||
# Several material settings not contributing to the final appearance of
|
||||
# a surface are ignored in all comparisons (e.g. the material name).
|
||||
# So, if you're passing additional information through the
|
||||
# content pipeline (probably using #magic# material names), don't
|
||||
# specify this flag. Alternatively take a look at the
|
||||
# <tt>#AI_CONFIG_PP_RRM_EXCLUDE_LIST<tt> setting.
|
||||
#
|
||||
aiProcess_RemoveRedundantMaterials = 0x1000
|
||||
|
||||
## <hr>This step tries to determine which meshes have normal vectors
|
||||
# that are facing inwards and inverts them.
|
||||
#
|
||||
# The algorithm is simple but effective:
|
||||
# the bounding box of all vertices + their normals is compared against
|
||||
# the volume of the bounding box of all vertices without their normals.
|
||||
# This works well for most objects, problems might occur with planar
|
||||
# surfaces. However, the step tries to filter such cases.
|
||||
# The step inverts all in-facing normals. Generally it is recommended
|
||||
# to enable this step, although the result is not always correct.
|
||||
#
|
||||
aiProcess_FixInfacingNormals = 0x2000
|
||||
|
||||
## <hr>This step splits meshes with more than one primitive type in
|
||||
# homogeneous sub-meshes.
|
||||
#
|
||||
# The step is executed after the triangulation step. After the step
|
||||
# returns, just one bit is set in aiMesh::mPrimitiveTypes. This is
|
||||
# especially useful for real-time rendering where point and line
|
||||
# primitives are often ignored or rendered separately.
|
||||
# You can use the <tt>#AI_CONFIG_PP_SBP_REMOVE<tt> option to specify which
|
||||
# primitive types you need. This can be used to easily exclude
|
||||
# lines and points, which are rarely used, from the import.
|
||||
#
|
||||
aiProcess_SortByPType = 0x8000
|
||||
|
||||
## <hr>This step searches all meshes for degenerate primitives and
|
||||
# converts them to proper lines or points.
|
||||
#
|
||||
# A face is 'degenerate' if one or more of its points are identical.
|
||||
# To have the degenerate stuff not only detected and collapsed but
|
||||
# removed, try one of the following procedures:
|
||||
# <br><b>1.<b> (if you support lines and points for rendering but don't
|
||||
# want the degenerates)<br>
|
||||
# <ul>
|
||||
# <li>Specify the #aiProcess_FindDegenerates flag.
|
||||
# <li>
|
||||
# <li>Set the <tt>AI_CONFIG_PP_FD_REMOVE<tt> option to 1. This will
|
||||
# cause the step to remove degenerate triangles from the import
|
||||
# as soon as they're detected. They won't pass any further
|
||||
# pipeline steps.
|
||||
# <li>
|
||||
# <ul>
|
||||
# <br><b>2.<b>(if you don't support lines and points at all)<br>
|
||||
# <ul>
|
||||
# <li>Specify the #aiProcess_FindDegenerates flag.
|
||||
# <li>
|
||||
# <li>Specify the #aiProcess_SortByPType flag. This moves line and
|
||||
# point primitives to separate meshes.
|
||||
# <li>
|
||||
# <li>Set the <tt>AI_CONFIG_PP_SBP_REMOVE<tt> option to
|
||||
# @code aiPrimitiveType_POINTS | aiPrimitiveType_LINES
|
||||
# @endcode to cause SortByPType to reject point
|
||||
# and line meshes from the scene.
|
||||
# <li>
|
||||
# <ul>
|
||||
# @note Degenerate polygons are not necessarily evil and that's why
|
||||
# they're not removed by default. There are several file formats which
|
||||
# don't support lines or points, and some exporters bypass the
|
||||
# format specification and write them as degenerate triangles instead.
|
||||
#
|
||||
aiProcess_FindDegenerates = 0x10000
|
||||
|
||||
## <hr>This step searches all meshes for invalid data, such as zeroed
|
||||
# normal vectors or invalid UV coords and removesfixes them. This is
|
||||
# intended to get rid of some common exporter errors.
|
||||
#
|
||||
# This is especially useful for normals. If they are invalid, and
|
||||
# the step recognizes this, they will be removed and can later
|
||||
# be recomputed, i.e. by the #aiProcess_GenSmoothNormals flag.<br>
|
||||
# The step will also remove meshes that are infinitely small and reduce
|
||||
# animation tracks consisting of hundreds if redundant keys to a single
|
||||
# key. The <tt>AI_CONFIG_PP_FID_ANIM_ACCURACY<tt> config property decides
|
||||
# the accuracy of the check for duplicate animation tracks.
|
||||
#
|
||||
aiProcess_FindInvalidData = 0x20000
|
||||
|
||||
## <hr>This step converts non-UV mappings (such as spherical or
|
||||
# cylindrical mapping) to proper texture coordinate channels.
|
||||
#
|
||||
# Most applications will support UV mapping only, so you will
|
||||
# probably want to specify this step in every case. Note that Assimp is not
|
||||
# always able to match the original mapping implementation of the
|
||||
# 3D app which produced a model perfectly. It's always better to let the
|
||||
# modelling app compute the UV channels - 3ds max, Maya, Blender,
|
||||
# LightWave, and Modo do this for example.
|
||||
#
|
||||
# @note If this step is not requested, you'll need to process the
|
||||
# <tt>#AI_MATKEY_MAPPING<tt> material property in order to display all assets
|
||||
# properly.
|
||||
#
|
||||
aiProcess_GenUVCoords = 0x40000
|
||||
|
||||
## <hr>This step applies per-texture UV transformations and bakes
|
||||
# them into stand-alone vtexture coordinate channels.
|
||||
#
|
||||
# UV transformations are specified per-texture - see the
|
||||
# <tt>#AI_MATKEY_UVTRANSFORM<tt> material key for more information.
|
||||
# This step processes all textures with
|
||||
# transformed input UV coordinates and generates a new (pre-transformed) UV channel
|
||||
# which replaces the old channel. Most applications won't support UV
|
||||
# transformations, so you will probably want to specify this step.
|
||||
#
|
||||
# @note UV transformations are usually implemented in real-time apps by
|
||||
# transforming texture coordinates at vertex shader stage with a 3x3
|
||||
# (homogenous) transformation matrix.
|
||||
#
|
||||
aiProcess_TransformUVCoords = 0x80000
|
||||
|
||||
## <hr>This step searches for duplicate meshes and replaces them
|
||||
# with references to the first mesh.
|
||||
#
|
||||
# This step takes a while, so don't use it if speed is a concern.
|
||||
# Its main purpose is to workaround the fact that many export
|
||||
# file formats don't support instanced meshes, so exporters need to
|
||||
# duplicate meshes. This step removes the duplicates again. Please
|
||||
# note that Assimp does not currently support per-node material
|
||||
# assignment to meshes, which means that identical meshes with
|
||||
# different materials are currently #not# joined, although this is
|
||||
# planned for future versions.
|
||||
#
|
||||
aiProcess_FindInstances = 0x100000
|
||||
|
||||
## <hr>A postprocessing step to reduce the number of meshes.
|
||||
#
|
||||
# This will, in fact, reduce the number of draw calls.
|
||||
#
|
||||
# This is a very effective optimization and is recommended to be used
|
||||
# together with #aiProcess_OptimizeGraph, if possible. The flag is fully
|
||||
# compatible with both #aiProcess_SplitLargeMeshes and #aiProcess_SortByPType.
|
||||
#
|
||||
aiProcess_OptimizeMeshes = 0x200000
|
||||
|
||||
|
||||
## <hr>A postprocessing step to optimize the scene hierarchy.
|
||||
#
|
||||
# Nodes without animations, bones, lights or cameras assigned are
|
||||
# collapsed and joined.
|
||||
#
|
||||
# Node names can be lost during this step. If you use special 'tag nodes'
|
||||
# to pass additional information through your content pipeline, use the
|
||||
# <tt>#AI_CONFIG_PP_OG_EXCLUDE_LIST<tt> setting to specify a list of node
|
||||
# names you want to be kept. Nodes matching one of the names in this list won't
|
||||
# be touched or modified.
|
||||
#
|
||||
# Use this flag with caution. Most simple files will be collapsed to a
|
||||
# single node, so complex hierarchies are usually completely lost. This is not
|
||||
# useful for editor environments, but probably a very effective
|
||||
# optimization if you just want to get the model data, convert it to your
|
||||
# own format, and render it as fast as possible.
|
||||
#
|
||||
# This flag is designed to be used with #aiProcess_OptimizeMeshes for best
|
||||
# results.
|
||||
#
|
||||
# @note 'Crappy' scenes with thousands of extremely small meshes packed
|
||||
# in deeply nested nodes exist for almost all file formats.
|
||||
# #aiProcess_OptimizeMeshes in combination with #aiProcess_OptimizeGraph
|
||||
# usually fixes them all and makes them renderable.
|
||||
#
|
||||
aiProcess_OptimizeGraph = 0x400000
|
||||
|
||||
## <hr>This step flips all UV coordinates along the y-axis and adjusts
|
||||
# material settings and bitangents accordingly.
|
||||
#
|
||||
# <b>Output UV coordinate system:<b>
|
||||
# @code
|
||||
# 0y|0y ---------- 1x|0y
|
||||
# | |
|
||||
# | |
|
||||
# | |
|
||||
# 0x|1y ---------- 1x|1y
|
||||
# @endcode
|
||||
#
|
||||
# You'll probably want to consider this flag if you use Direct3D for
|
||||
# rendering. The #aiProcess_ConvertToLeftHanded flag supersedes this
|
||||
# setting and bundles all conversions typically required for D3D-based
|
||||
# applications.
|
||||
#
|
||||
aiProcess_FlipUVs = 0x800000
|
||||
|
||||
## <hr>This step adjusts the output face winding order to be CW.
|
||||
#
|
||||
# The default face winding order is counter clockwise (CCW).
|
||||
#
|
||||
# <b>Output face order:<b>
|
||||
# @code
|
||||
# x2
|
||||
#
|
||||
# x0
|
||||
# x1
|
||||
# @endcode
|
||||
#
|
||||
aiProcess_FlipWindingOrder = 0x1000000
|
||||
|
||||
## <hr>This step splits meshes with many bones into sub-meshes so that each
|
||||
# su-bmesh has fewer or as many bones as a given limit.
|
||||
#
|
||||
aiProcess_SplitByBoneCount = 0x2000000
|
||||
|
||||
## <hr>This step removes bones losslessly or according to some threshold.
|
||||
#
|
||||
# In some cases (i.e. formats that require it) exporters are forced to
|
||||
# assign dummy bone weights to otherwise static meshes assigned to
|
||||
# animated meshes. Full, weight-based skinning is expensive while
|
||||
# animating nodes is extremely cheap, so this step is offered to clean up
|
||||
# the data in that regard.
|
||||
#
|
||||
# Use <tt>#AI_CONFIG_PP_DB_THRESHOLD<tt> to control this.
|
||||
# Use <tt>#AI_CONFIG_PP_DB_ALL_OR_NONE<tt> if you want bones removed if and
|
||||
# only if all bones within the scene qualify for removal.
|
||||
#
|
||||
aiProcess_Debone = 0x4000000
|
||||
|
||||
aiProcess_GenEntityMeshes = 0x100000
|
||||
aiProcess_OptimizeAnimations = 0x200000
|
||||
aiProcess_FixTexturePaths = 0x200000
|
||||
|
||||
## @def aiProcess_ConvertToLeftHanded
|
||||
# @brief Shortcut flag for Direct3D-based applications.
|
||||
#
|
||||
# Supersedes the #aiProcess_MakeLeftHanded and #aiProcess_FlipUVs and
|
||||
# #aiProcess_FlipWindingOrder flags.
|
||||
# The output data matches Direct3D's conventions: left-handed geometry, upper-left
|
||||
# origin for UV coordinates and finally clockwise face order, suitable for CCW culling.
|
||||
#
|
||||
# @deprecated
|
||||
#
|
||||
aiProcess_ConvertToLeftHanded = ( \
|
||||
aiProcess_MakeLeftHanded | \
|
||||
aiProcess_FlipUVs | \
|
||||
aiProcess_FlipWindingOrder | \
|
||||
0 )
|
||||
|
||||
|
||||
## @def aiProcessPreset_TargetRealtimeUse_Fast
|
||||
# @brief Default postprocess configuration optimizing the data for real-time rendering.
|
||||
#
|
||||
# Applications would want to use this preset to load models on end-user PCs,
|
||||
# maybe for direct use in game.
|
||||
#
|
||||
# If you're using DirectX, don't forget to combine this value with
|
||||
# the #aiProcess_ConvertToLeftHanded step. If you don't support UV transformations
|
||||
# in your application apply the #aiProcess_TransformUVCoords step, too.
|
||||
# @note Please take the time to read the docs for the steps enabled by this preset.
|
||||
# Some of them offer further configurable properties, while some of them might not be of
|
||||
# use for you so it might be better to not specify them.
|
||||
#
|
||||
aiProcessPreset_TargetRealtime_Fast = ( \
|
||||
aiProcess_CalcTangentSpace | \
|
||||
aiProcess_GenNormals | \
|
||||
aiProcess_JoinIdenticalVertices | \
|
||||
aiProcess_Triangulate | \
|
||||
aiProcess_GenUVCoords | \
|
||||
aiProcess_SortByPType | \
|
||||
0 )
|
||||
|
||||
## @def aiProcessPreset_TargetRealtime_Quality
|
||||
# @brief Default postprocess configuration optimizing the data for real-time rendering.
|
||||
#
|
||||
# Unlike #aiProcessPreset_TargetRealtime_Fast, this configuration
|
||||
# performs some extra optimizations to improve rendering speed and
|
||||
# to minimize memory usage. It could be a good choice for a level editor
|
||||
# environment where import speed is not so important.
|
||||
#
|
||||
# If you're using DirectX, don't forget to combine this value with
|
||||
# the #aiProcess_ConvertToLeftHanded step. If you don't support UV transformations
|
||||
# in your application apply the #aiProcess_TransformUVCoords step, too.
|
||||
# @note Please take the time to read the docs for the steps enabled by this preset.
|
||||
# Some of them offer further configurable properties, while some of them might not be
|
||||
# of use for you so it might be better to not specify them.
|
||||
#
|
||||
aiProcessPreset_TargetRealtime_Quality = ( \
|
||||
aiProcess_CalcTangentSpace | \
|
||||
aiProcess_GenSmoothNormals | \
|
||||
aiProcess_JoinIdenticalVertices | \
|
||||
aiProcess_ImproveCacheLocality | \
|
||||
aiProcess_LimitBoneWeights | \
|
||||
aiProcess_RemoveRedundantMaterials | \
|
||||
aiProcess_SplitLargeMeshes | \
|
||||
aiProcess_Triangulate | \
|
||||
aiProcess_GenUVCoords | \
|
||||
aiProcess_SortByPType | \
|
||||
aiProcess_FindDegenerates | \
|
||||
aiProcess_FindInvalidData | \
|
||||
0 )
|
||||
|
||||
## @def aiProcessPreset_TargetRealtime_MaxQuality
|
||||
# @brief Default postprocess configuration optimizing the data for real-time rendering.
|
||||
#
|
||||
# This preset enables almost every optimization step to achieve perfectly
|
||||
# optimized data. It's your choice for level editor environments where import speed
|
||||
# is not important.
|
||||
#
|
||||
# If you're using DirectX, don't forget to combine this value with
|
||||
# the #aiProcess_ConvertToLeftHanded step. If you don't support UV transformations
|
||||
# in your application, apply the #aiProcess_TransformUVCoords step, too.
|
||||
# @note Please take the time to read the docs for the steps enabled by this preset.
|
||||
# Some of them offer further configurable properties, while some of them might not be
|
||||
# of use for you so it might be better to not specify them.
|
||||
#
|
||||
aiProcessPreset_TargetRealtime_MaxQuality = ( \
|
||||
aiProcessPreset_TargetRealtime_Quality | \
|
||||
aiProcess_FindInstances | \
|
||||
aiProcess_ValidateDataStructure | \
|
||||
aiProcess_OptimizeMeshes | \
|
||||
0 )
|
||||
|
||||
|
|
@ -0,0 +1,421 @@
|
|||
#!/usr/bin/env python
|
||||
#-*- coding: UTF-8 -*-
|
||||
|
||||
""" This program demonstrate the use of pyassimp to render
|
||||
objects in OpenGL.
|
||||
|
||||
It loads a 3D model with ASSIMP and display it.
|
||||
|
||||
Materials are supported but textures are currently ignored.
|
||||
|
||||
Half-working keyboard + mouse navigation is supported.
|
||||
|
||||
This sample is based on several sources, including:
|
||||
- http://www.lighthouse3d.com/tutorials
|
||||
- http://www.songho.ca/opengl/gl_transform.html
|
||||
- http://code.activestate.com/recipes/325391/
|
||||
- ASSIMP's C++ SimpleOpenGL viewer
|
||||
"""
|
||||
|
||||
import os, sys
|
||||
from OpenGL.GLUT import *
|
||||
from OpenGL.GLU import *
|
||||
from OpenGL.GL import *
|
||||
from OpenGL.arrays import ArrayDatatype
|
||||
|
||||
import logging;logger = logging.getLogger("assimp_opengl")
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
import math
|
||||
import numpy
|
||||
|
||||
from pyassimp import core as pyassimp
|
||||
from pyassimp.postprocess import *
|
||||
from pyassimp.helper import *
|
||||
|
||||
|
||||
name = 'pyassimp OpenGL viewer'
|
||||
height = 600
|
||||
width = 900
|
||||
|
||||
class GLRenderer():
|
||||
def __init__(self):
|
||||
self.scene = None
|
||||
|
||||
self.drot = 0.0
|
||||
self.dp = 0.0
|
||||
|
||||
self.angle = 0.0
|
||||
self.x = 1.0
|
||||
self.z = 3.0
|
||||
self.lx = 0.0
|
||||
self.lz = 0.0
|
||||
self.using_fixed_cam = False
|
||||
self.current_cam_index = 0
|
||||
|
||||
self.x_origin = -1 # x position of the mouse when pressing left btn
|
||||
|
||||
# for FPS calculation
|
||||
self.prev_time = 0
|
||||
self.prev_fps_time = 0
|
||||
self.frames = 0
|
||||
|
||||
def prepare_gl_buffers(self, mesh):
|
||||
|
||||
mesh.gl = {}
|
||||
|
||||
# Fill the buffer for vertex positions
|
||||
mesh.gl["vertices"] = glGenBuffers(1)
|
||||
glBindBuffer(GL_ARRAY_BUFFER, mesh.gl["vertices"])
|
||||
glBufferData(GL_ARRAY_BUFFER,
|
||||
mesh.vertices,
|
||||
GL_STATIC_DRAW)
|
||||
|
||||
# Fill the buffer for normals
|
||||
mesh.gl["normals"] = glGenBuffers(1)
|
||||
glBindBuffer(GL_ARRAY_BUFFER, mesh.gl["normals"])
|
||||
glBufferData(GL_ARRAY_BUFFER,
|
||||
mesh.normals,
|
||||
GL_STATIC_DRAW)
|
||||
|
||||
|
||||
# Fill the buffer for vertex positions
|
||||
mesh.gl["triangles"] = glGenBuffers(1)
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh.gl["triangles"])
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER,
|
||||
mesh.faces,
|
||||
GL_STATIC_DRAW)
|
||||
|
||||
# Unbind buffers
|
||||
glBindBuffer(GL_ARRAY_BUFFER,0)
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,0)
|
||||
|
||||
|
||||
def load_dae(self, path, postprocess = None):
|
||||
logger.info("Loading model:" + path + "...")
|
||||
|
||||
if postprocess:
|
||||
self.scene = pyassimp.load(path, postprocess)
|
||||
else:
|
||||
self.scene = pyassimp.load(path)
|
||||
logger.info("Done.")
|
||||
|
||||
scene = self.scene
|
||||
#log some statistics
|
||||
logger.info(" meshes: %d" % len(scene.meshes))
|
||||
logger.info(" total faces: %d" % sum([len(mesh.faces) for mesh in scene.meshes]))
|
||||
logger.info(" materials: %d" % len(scene.materials))
|
||||
self.bb_min, self.bb_max = get_bounding_box(self.scene)
|
||||
logger.info(" bounding box:" + str(self.bb_min) + " - " + str(self.bb_max))
|
||||
|
||||
self.scene_center = [(a + b) / 2. for a, b in zip(self.bb_min, self.bb_max)]
|
||||
|
||||
for index, mesh in enumerate(scene.meshes):
|
||||
self.prepare_gl_buffers(mesh)
|
||||
|
||||
# Finally release the model
|
||||
pyassimp.release(scene)
|
||||
|
||||
def cycle_cameras(self):
|
||||
self.current_cam_index
|
||||
if not self.scene.cameras:
|
||||
return None
|
||||
self.current_cam_index = (self.current_cam_index + 1) % len(self.scene.cameras)
|
||||
cam = self.scene.cameras[self.current_cam_index]
|
||||
logger.info("Switched to camera " + str(cam))
|
||||
return cam
|
||||
|
||||
def set_default_camera(self):
|
||||
|
||||
if not self.using_fixed_cam:
|
||||
glLoadIdentity()
|
||||
gluLookAt(self.x ,1., self.z, # pos
|
||||
self.x + self.lx - 1.0, 1., self.z + self.lz - 3.0, # look at
|
||||
0.,1.,0.) # up vector
|
||||
|
||||
|
||||
def set_camera(self, camera):
|
||||
|
||||
if not camera:
|
||||
return
|
||||
|
||||
self.using_fixed_cam = True
|
||||
|
||||
znear = camera.clipplanenear
|
||||
zfar = camera.clipplanefar
|
||||
aspect = camera.aspect
|
||||
fov = camera.horizontalfov
|
||||
|
||||
glMatrixMode(GL_PROJECTION)
|
||||
glLoadIdentity()
|
||||
|
||||
# Compute gl frustrum
|
||||
tangent = math.tan(fov/2.)
|
||||
h = znear * tangent
|
||||
w = h * aspect
|
||||
|
||||
# params: left, right, bottom, top, near, far
|
||||
glFrustum(-w, w, -h, h, znear, zfar)
|
||||
# equivalent to:
|
||||
#gluPerspective(fov * 180/math.pi, aspect, znear, zfar)
|
||||
|
||||
glMatrixMode(GL_MODELVIEW)
|
||||
glLoadIdentity()
|
||||
|
||||
cam = transform(camera.position, camera.transformation)
|
||||
at = transform(camera.lookat, camera.transformation)
|
||||
gluLookAt(cam[0], cam[2], -cam[1],
|
||||
at[0], at[2], -at[1],
|
||||
0, 1, 0)
|
||||
|
||||
def fit_scene(self, restore = False):
|
||||
""" Compute a scale factor and a translation to fit and center
|
||||
the whole geometry on the screen.
|
||||
"""
|
||||
|
||||
x_max = self.bb_max[0] - self.bb_min[0]
|
||||
y_max = self.bb_max[1] - self.bb_min[1]
|
||||
tmp = max(x_max, y_max)
|
||||
z_max = self.bb_max[2] - self.bb_min[2]
|
||||
tmp = max(z_max, tmp)
|
||||
|
||||
if not restore:
|
||||
tmp = 1. / tmp
|
||||
|
||||
logger.info("Scaling the scene by %.03f" % tmp)
|
||||
glScalef(tmp, tmp, tmp)
|
||||
|
||||
# center the model
|
||||
direction = -1 if not restore else 1
|
||||
glTranslatef( direction * self.scene_center[0],
|
||||
direction * self.scene_center[1],
|
||||
direction * self.scene_center[2] )
|
||||
|
||||
return x_max, y_max, z_max
|
||||
|
||||
def apply_material(self, mat):
|
||||
""" Apply an OpenGL, using one OpenGL list per material to cache
|
||||
the operation.
|
||||
"""
|
||||
|
||||
if not hasattr(mat, "gl_mat"): # evaluate once the mat properties, and cache the values in a glDisplayList.
|
||||
|
||||
diffuse = mat.properties.get("$clr.diffuse", numpy.array([0.8, 0.8, 0.8, 1.0]))
|
||||
specular = mat.properties.get("$clr.specular", numpy.array([0., 0., 0., 1.0]))
|
||||
ambient = mat.properties.get("$clr.ambient", numpy.array([0.2, 0.2, 0.2, 1.0]))
|
||||
emissive = mat.properties.get("$clr.emissive", numpy.array([0., 0., 0., 1.0]))
|
||||
shininess = min(mat.properties.get("$mat.shininess", 1.0), 128)
|
||||
wireframe = mat.properties.get("$mat.wireframe", 0)
|
||||
twosided = mat.properties.get("$mat.twosided", 1)
|
||||
|
||||
from OpenGL.raw import GL
|
||||
setattr(mat, "gl_mat", GL.GLuint(0))
|
||||
mat.gl_mat = glGenLists(1)
|
||||
glNewList(mat.gl_mat, GL_COMPILE)
|
||||
|
||||
glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, diffuse)
|
||||
glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, specular)
|
||||
glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, ambient)
|
||||
glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, emissive)
|
||||
glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, shininess)
|
||||
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE if wireframe else GL_FILL)
|
||||
glDisable(GL_CULL_FACE) if twosided else glEnable(GL_CULL_FACE)
|
||||
|
||||
glEndList()
|
||||
|
||||
glCallList(mat.gl_mat)
|
||||
|
||||
|
||||
|
||||
def do_motion(self):
|
||||
|
||||
gl_time = glutGet(GLUT_ELAPSED_TIME)
|
||||
|
||||
# Compute the new position of the camera and set it
|
||||
self.x += self.dp * self.lx * 0.01 * (gl_time-self.prev_time)
|
||||
self.z += self.dp * self.lz * 0.01 * (gl_time-self.prev_time)
|
||||
self.angle += self.drot * 0.1 * (gl_time-self.prev_time)
|
||||
self.lx = math.sin(self.angle)
|
||||
self.lz = -math.cos(self.angle)
|
||||
self.set_default_camera()
|
||||
|
||||
self.prev_time = gl_time
|
||||
|
||||
# Compute FPS
|
||||
self.frames += 1
|
||||
if gl_time - self.prev_fps_time >= 1000:
|
||||
current_fps = self.frames * 1000 / (gl_time - self.prev_fps_time)
|
||||
logger.info('%.0f fps' % current_fps)
|
||||
self.frames = 0
|
||||
self.prev_fps_time = gl_time
|
||||
|
||||
glutPostRedisplay()
|
||||
|
||||
def recursive_render(self, node):
|
||||
""" Main recursive rendering method.
|
||||
"""
|
||||
|
||||
# save model matrix and apply node transformation
|
||||
glPushMatrix()
|
||||
m = node.transformation.transpose() # OpenGL row major
|
||||
glMultMatrixf(m)
|
||||
|
||||
for mesh in node.meshes:
|
||||
self.apply_material(mesh.material)
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, mesh.gl["vertices"])
|
||||
glEnableClientState(GL_VERTEX_ARRAY)
|
||||
glVertexPointer(3, GL_FLOAT, 0, None)
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, mesh.gl["normals"])
|
||||
glEnableClientState(GL_NORMAL_ARRAY)
|
||||
glNormalPointer(GL_FLOAT, 0, None)
|
||||
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh.gl["triangles"])
|
||||
glDrawElements(GL_TRIANGLES,len(mesh.faces) * 3, GL_UNSIGNED_INT, None)
|
||||
|
||||
glDisableClientState(GL_VERTEX_ARRAY)
|
||||
glDisableClientState(GL_NORMAL_ARRAY)
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0)
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)
|
||||
|
||||
for child in node.children:
|
||||
self.recursive_render(child)
|
||||
|
||||
glPopMatrix()
|
||||
|
||||
|
||||
def display(self):
|
||||
""" GLUT callback to redraw OpenGL surface
|
||||
"""
|
||||
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
|
||||
|
||||
self.recursive_render(self.scene.rootnode)
|
||||
|
||||
glutSwapBuffers()
|
||||
self.do_motion()
|
||||
return
|
||||
|
||||
####################################################################
|
||||
## GLUT keyboard and mouse callbacks ##
|
||||
####################################################################
|
||||
def onkeypress(self, key, x, y):
|
||||
if key == 'c':
|
||||
self.fit_scene(restore = True)
|
||||
self.set_camera(self.cycle_cameras())
|
||||
if key == 'q':
|
||||
sys.exit(0)
|
||||
|
||||
def onspecialkeypress(self, key, x, y):
|
||||
|
||||
fraction = 0.05
|
||||
|
||||
if key == GLUT_KEY_UP:
|
||||
self.dp = 0.5
|
||||
if key == GLUT_KEY_DOWN:
|
||||
self.dp = -0.5
|
||||
if key == GLUT_KEY_LEFT:
|
||||
self.drot = -0.01
|
||||
if key == GLUT_KEY_RIGHT:
|
||||
self.drot = 0.01
|
||||
|
||||
def onspecialkeyrelease(self, key, x, y):
|
||||
|
||||
if key == GLUT_KEY_UP:
|
||||
self.dp = 0.
|
||||
if key == GLUT_KEY_DOWN:
|
||||
self.dp = 0.
|
||||
if key == GLUT_KEY_LEFT:
|
||||
self.drot = 0.0
|
||||
if key == GLUT_KEY_RIGHT:
|
||||
self.drot = 0.0
|
||||
|
||||
def onclick(self, button, state, x, y):
|
||||
if button == GLUT_LEFT_BUTTON:
|
||||
if state == GLUT_UP:
|
||||
self.drot = 0
|
||||
self.x_origin = -1
|
||||
else: # GLUT_DOWN
|
||||
self.x_origin = x
|
||||
|
||||
def onmousemove(self, x, y):
|
||||
if self.x_origin >= 0:
|
||||
self.drot = (x - self.x_origin) * 0.001
|
||||
|
||||
def render(self, filename=None, fullscreen = False, autofit = True, postprocess = None):
|
||||
"""
|
||||
|
||||
:param autofit: if true, scale the scene to fit the whole geometry
|
||||
in the viewport.
|
||||
"""
|
||||
|
||||
# First initialize the openGL context
|
||||
glutInit(sys.argv)
|
||||
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH)
|
||||
if not fullscreen:
|
||||
glutInitWindowSize(width, height)
|
||||
glutCreateWindow(name)
|
||||
else:
|
||||
glutGameModeString("1024x768")
|
||||
if glutGameModeGet(GLUT_GAME_MODE_POSSIBLE):
|
||||
glutEnterGameMode()
|
||||
else:
|
||||
print("Fullscreen mode not available!")
|
||||
sys.exit(1)
|
||||
|
||||
self.load_dae(filename, postprocess = postprocess)
|
||||
|
||||
glClearColor(0.1,0.1,0.1,1.)
|
||||
#glShadeModel(GL_SMOOTH)
|
||||
|
||||
glEnable(GL_LIGHTING)
|
||||
|
||||
glEnable(GL_CULL_FACE)
|
||||
glEnable(GL_DEPTH_TEST)
|
||||
|
||||
#lightZeroPosition = [10.,4.,10.,1.]
|
||||
#lightZeroColor = [0.8,1.0,0.8,1.0] #green tinged
|
||||
#glLightfv(GL_LIGHT0, GL_POSITION, lightZeroPosition)
|
||||
#glLightfv(GL_LIGHT0, GL_DIFFUSE, lightZeroColor)
|
||||
#glLightf(GL_LIGHT0, GL_CONSTANT_ATTENUATION, 0.1)
|
||||
#glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, 0.05)
|
||||
glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE)
|
||||
glEnable(GL_NORMALIZE)
|
||||
glEnable(GL_LIGHT0)
|
||||
|
||||
glutDisplayFunc(self.display)
|
||||
|
||||
|
||||
glMatrixMode(GL_PROJECTION)
|
||||
glLoadIdentity()
|
||||
gluPerspective(35.0, width/float(height) , 0.10, 100.0)
|
||||
glMatrixMode(GL_MODELVIEW)
|
||||
self.set_default_camera()
|
||||
|
||||
if autofit:
|
||||
# scale the whole asset to fit into our view frustum·
|
||||
self.fit_scene()
|
||||
|
||||
glPushMatrix()
|
||||
|
||||
# Register GLUT callbacks for keyboard and mouse
|
||||
glutKeyboardFunc(self.onkeypress)
|
||||
glutSpecialFunc(self.onspecialkeypress)
|
||||
glutIgnoreKeyRepeat(1)
|
||||
glutSpecialUpFunc(self.onspecialkeyrelease)
|
||||
|
||||
glutMouseFunc(self.onclick)
|
||||
glutMotionFunc(self.onmousemove)
|
||||
|
||||
glutMainLoop()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if not len(sys.argv) > 1:
|
||||
print("Usage: " + __file__ + " <model>")
|
||||
sys.exit(0)
|
||||
|
||||
glrender = GLRenderer()
|
||||
glrender.render(sys.argv[1], fullscreen = False, postprocess = aiProcessPreset_TargetRealtime_MaxQuality)
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
#!/usr/bin/env python
|
||||
#-*- coding: UTF-8 -*-
|
||||
|
||||
"""
|
||||
This module uses the sample.py script to load all test models it finds.
|
||||
|
||||
Note: this is not an exhaustive test suite, it does not check the
|
||||
data structures in detail. It just verifies whether basic
|
||||
loading and querying of 3d models using pyassimp works.
|
||||
"""
|
||||
|
||||
|
||||
import sys,os
|
||||
import sample
|
||||
from pyassimp import pyassimp,errors
|
||||
|
||||
# paths to be walkd recursively
|
||||
basepaths = [os.path.join('..','..','test','models'), os.path.join('..','..','test','models-nonbsd')]
|
||||
|
||||
# file extensions to be considered
|
||||
extensions = ['.3ds','.x','.lwo','.obj','.md5mesh','.dxf','.ply','.stl','.dae','.md5anim','.lws','.irrmesh','.nff','.off','.blend']
|
||||
|
||||
def run_tests():
|
||||
ok,err = 0,0
|
||||
for path in basepaths:
|
||||
for root, dirs, files in os.walk(path):
|
||||
for afile in files:
|
||||
base,ext = os.path.splitext(afile)
|
||||
if ext in extensions:
|
||||
try:
|
||||
sample.main(os.path.join(root,afile))
|
||||
ok += 1
|
||||
except errors.AssimpError as error:
|
||||
# assimp error is fine, this is a controlled case
|
||||
print error
|
||||
err += 1
|
||||
print '** Loaded %s models, got controlled errors for %s files' % (ok,err)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
run_tests()
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
#!/usr/bin/env python
|
||||
#-*- coding: UTF-8 -*-
|
||||
|
||||
"""
|
||||
This module demonstrates the functionality of PyAssimp.
|
||||
"""
|
||||
|
||||
import os, sys
|
||||
import logging
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
|
||||
from pyassimp import core as pyassimp
|
||||
|
||||
def recur_node(node,level = 0):
|
||||
print(" " + "\t" * level + "- " + str(node))
|
||||
for child in node.children:
|
||||
recur_node(child, level + 1)
|
||||
|
||||
|
||||
def main(filename=None):
|
||||
|
||||
scene = pyassimp.load(filename)
|
||||
|
||||
#the model we load
|
||||
print("MODEL:" + filename)
|
||||
print
|
||||
|
||||
#write some statistics
|
||||
print("SCENE:")
|
||||
print(" meshes:" + str(len(scene.meshes)))
|
||||
print(" materials:" + str(len(scene.materials)))
|
||||
print(" textures:" + str(len(scene.textures)))
|
||||
print
|
||||
|
||||
print("NODES:")
|
||||
recur_node(scene.rootnode)
|
||||
|
||||
print
|
||||
print("MESHES:")
|
||||
for index, mesh in enumerate(scene.meshes):
|
||||
print(" MESH" + str(index+1))
|
||||
print(" material id:" + str(mesh.materialindex+1))
|
||||
print(" vertices:" + str(len(mesh.vertices)))
|
||||
print(" first 3 verts:\n" + str(mesh.vertices[:3]))
|
||||
if mesh.normals.any():
|
||||
print(" first 3 normals:\n" + str(mesh.normals[:3]))
|
||||
else:
|
||||
print(" no normals")
|
||||
print(" colors:" + str(len(mesh.colors)))
|
||||
tcs = mesh.texturecoords
|
||||
if tcs:
|
||||
for index, tc in enumerate(tcs):
|
||||
print(" texture-coords "+ str(index) + ":" + str(len(tcs[index])) + "first3:" + str(tcs[index][:3]))
|
||||
|
||||
else:
|
||||
print(" no texture coordinates")
|
||||
print(" uv-component-count:" + str(len(mesh.numuvcomponents)))
|
||||
print(" faces:" + str(len(mesh.faces)) + " -> first:\n" + str(mesh.faces[:3]))
|
||||
print(" bones:" + str(len(mesh.bones)) + " -> first:" + str([str(b) for b in mesh.bones[:3]]))
|
||||
print
|
||||
|
||||
print("MATERIALS:")
|
||||
for index, material in enumerate(scene.materials):
|
||||
print(" MATERIAL (id:" + str(index+1) + ")")
|
||||
for key, value in material.properties.items():
|
||||
print(" %s: %s" % (key, value))
|
||||
print
|
||||
|
||||
print("TEXTURES:")
|
||||
for index, texture in enumerate(scene.textures):
|
||||
print(" TEXTURE" + str(index+1))
|
||||
print(" width:" + str(texture.width))
|
||||
print(" height:" + str(texture.height))
|
||||
print(" hint:" + str(texture.achformathint))
|
||||
print(" data (size):" + str(len(texture.data)))
|
||||
|
||||
# Finally release the model
|
||||
pyassimp.release(scene)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(sys.argv[1] if len(sys.argv)>1 else None)
|
|
@ -0,0 +1,13 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
from distutils.core import setup
|
||||
|
||||
setup(name='pyassimp',
|
||||
version='0.1',
|
||||
license='ISC',
|
||||
description='Python bindings for the Open Asset Import Library (ASSIMP)',
|
||||
url='http://assimp.sourceforge.net/',
|
||||
packages=['pyassimp'],
|
||||
data_files=[('share/pyassimp', ['README.md']),
|
||||
('share/examples/pyassimp', ['scripts/' + f for f in os.listdir('scripts/')])]
|
||||
)
|
Loading…
Reference in New Issue