From 6d3cedc0b16fa030dfceff28aa75640b2cba33ea Mon Sep 17 00:00:00 2001 From: aramis_acg Date: Sat, 10 Nov 2012 16:01:55 +0000 Subject: [PATCH] - add missing files from the last commit git-svn-id: https://assimp.svn.sourceforge.net/svnroot/assimp/trunk@1334 67173fc5-114c-0410-ac8e-9d2fd5bffc1f --- port/PyAssimp/README.md | 74 ++++ port/PyAssimp/pyassimp/core.py | 415 +++++++++++++++++++ port/PyAssimp/pyassimp/postprocess.py | 529 +++++++++++++++++++++++++ port/PyAssimp/scripts/opengl_viewer.py | 421 ++++++++++++++++++++ port/PyAssimp/scripts/quicktest.py | 45 +++ port/PyAssimp/scripts/sample.py | 81 ++++ port/PyAssimp/setup.py | 13 + 7 files changed, 1578 insertions(+) create mode 100644 port/PyAssimp/README.md create mode 100644 port/PyAssimp/pyassimp/core.py create mode 100644 port/PyAssimp/pyassimp/postprocess.py create mode 100755 port/PyAssimp/scripts/opengl_viewer.py create mode 100755 port/PyAssimp/scripts/quicktest.py create mode 100755 port/PyAssimp/scripts/sample.py create mode 100644 port/PyAssimp/setup.py diff --git a/port/PyAssimp/README.md b/port/PyAssimp/README.md new file mode 100644 index 000000000..5bec88711 --- /dev/null +++ b/port/PyAssimp/README.md @@ -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. + diff --git a/port/PyAssimp/pyassimp/core.py b/port/PyAssimp/pyassimp/core.py new file mode 100644 index 000000000..731dcd5a3 --- /dev/null +++ b/port/PyAssimp/pyassimp/core.py @@ -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() diff --git a/port/PyAssimp/pyassimp/postprocess.py b/port/PyAssimp/pyassimp/postprocess.py new file mode 100644 index 000000000..932c7c660 --- /dev/null +++ b/port/PyAssimp/pyassimp/postprocess.py @@ -0,0 +1,529 @@ +#
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, +# #AI_CONFIG_PP_CT_MAX_SMOOTHING_ANGLE, 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 + +##
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. If this flag is not specified, no vertices are referenced by +# more than one face and no index buffer is required for rendering. +# +aiProcess_JoinIdenticalVertices = 0x2 + +##
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 + +##
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: +#