diff --git a/code/AssxmlExporter.cpp b/code/AssxmlExporter.cpp index fc9a6bae5..937eebe92 100644 --- a/code/AssxmlExporter.cpp +++ b/code/AssxmlExporter.cpp @@ -555,8 +555,6 @@ void WriteDump(const aiScene* scene, IOStream* io, bool shortened) { mesh->mNormals[n].z); } } - else { - } ioprintf(io,"\t\t\n"); } diff --git a/code/BlenderDNA.h b/code/BlenderDNA.h index 5d3a4f6ea..375d0c4bf 100644 --- a/code/BlenderDNA.h +++ b/code/BlenderDNA.h @@ -416,7 +416,7 @@ template <> struct Structure :: _defaultInitializer { void operator ()(T& /*out*/,const char* = "") { // obviously, it is crucial that _DefaultInitializer is used // only from within a catch clause. - throw; + throw DeadlyImportError("Constructing BlenderDNA Structure encountered an error"); } }; diff --git a/code/COBLoader.cpp b/code/COBLoader.cpp index efb22e08b..b7e0f9728 100644 --- a/code/COBLoader.cpp +++ b/code/COBLoader.cpp @@ -144,7 +144,7 @@ void COBImporter::InternReadFile( const std::string& pFile, aiScene* pScene, IOS // check header char head[32]; stream->CopyAndAdvance(head,32); - if (strncmp(head,"Caligari ",9)) { + if (strncmp(head,"Caligari ",9) != 0) { ThrowException("Could not found magic id: `Caligari`"); } @@ -656,14 +656,14 @@ void COBImporter::ReadLght_Ascii(Scene& out, LineSplitter& splitter, const Chunk ReadFloat3Tuple_Ascii(msh.color ,&rgb); SkipSpaces(&rgb); - if (strncmp(rgb,"cone angle",10)) { + if (strncmp(rgb,"cone angle",10) != 0) { ASSIMP_LOG_WARN_F( "Expected `cone angle` entity in `color` line in `Lght` chunk ", nfo.id ); } SkipSpaces(rgb+10,&rgb); msh.angle = fast_atof(&rgb); SkipSpaces(&rgb); - if (strncmp(rgb,"inner angle",11)) { + if (strncmp(rgb,"inner angle",11) != 0) { ASSIMP_LOG_WARN_F( "Expected `inner angle` entity in `color` line in `Lght` chunk ", nfo.id); } SkipSpaces(rgb+11,&rgb); @@ -903,7 +903,7 @@ public: if(nfo.size != static_cast(-1)) { try { reader.IncPtr( static_cast< int >( nfo.size ) - reader.GetCurrentPos() + cur ); - } catch ( DeadlyImportError e ) { + } catch (const DeadlyImportError& e ) { // out of limit so correct the value reader.IncPtr( reader.GetReadLimit() ); } @@ -1214,7 +1214,7 @@ void COBImporter::ReadGrou_Binary(COB::Scene& out, StreamReaderLE& reader, const const chunk_guard cn(nfo,reader); - out.nodes.push_back(std::shared_ptr(new Group())); + out.nodes.push_back(std::make_shared()); Group& msh = (Group&)(*out.nodes.back().get()); msh = nfo; diff --git a/code/D3MFOpcPackage.cpp b/code/D3MFOpcPackage.cpp index 2545a2750..aeadba5cc 100644 --- a/code/D3MFOpcPackage.cpp +++ b/code/D3MFOpcPackage.cpp @@ -476,8 +476,11 @@ D3MFOpcPackage::D3MFOpcPackage(IOSystem* pIOHandler, const std::string& rFile) mZipArchive->Close( fileStream ); } else if( file == D3MF::XmlTag::CONTENT_TYPES_ARCHIVE) { - + ASSIMP_LOG_WARN_F("Ignored file of unsupported type CONTENT_TYPES_ARCHIVES",file); + } else { + ASSIMP_LOG_WARN_F("Ignored file of unknown type: ",file); } + } } diff --git a/code/FBXConverter.cpp b/code/FBXConverter.cpp index a654bc870..22612924c 100644 --- a/code/FBXConverter.cpp +++ b/code/FBXConverter.cpp @@ -1717,22 +1717,22 @@ namespace Assimp { if (!mesh) { for (const MeshMap::value_type& v : meshes_converted) { - const MeshGeometry* const mesh = dynamic_cast (v.first); - if (!mesh) { + const MeshGeometry* const meshGeom = dynamic_cast (v.first); + if (!meshGeom) { continue; } - const MatIndexArray& mats = mesh->GetMaterialIndices(); + const MatIndexArray& mats = meshGeom->GetMaterialIndices(); if (std::find(mats.begin(), mats.end(), matIndex) == mats.end()) { continue; } int index = -1; for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) { - if (mesh->GetTextureCoords(i).empty()) { + if (meshGeom->GetTextureCoords(i).empty()) { break; } - const std::string& name = mesh->GetTextureCoordChannelName(i); + const std::string& name = meshGeom->GetTextureCoordChannelName(i); if (name == uvSet) { index = static_cast(i); break; @@ -1840,22 +1840,22 @@ namespace Assimp { if (!mesh) { for (const MeshMap::value_type& v : meshes_converted) { - const MeshGeometry* const mesh = dynamic_cast (v.first); - if (!mesh) { + const MeshGeometry* const meshGeom = dynamic_cast (v.first); + if (!meshGeom) { continue; } - const MatIndexArray& mats = mesh->GetMaterialIndices(); + const MatIndexArray& mats = meshGeom->GetMaterialIndices(); if (std::find(mats.begin(), mats.end(), matIndex) == mats.end()) { continue; } int index = -1; for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) { - if (mesh->GetTextureCoords(i).empty()) { + if (meshGeom->GetTextureCoords(i).empty()) { break; } - const std::string& name = mesh->GetTextureCoordChannelName(i); + const std::string& name = meshGeom->GetTextureCoordChannelName(i); if (name == uvSet) { index = static_cast(i); break; @@ -2196,22 +2196,22 @@ void FBXConverter::SetShadingPropertiesRaw(aiMaterial* out_mat, const PropertyTa if (!mesh) { for (const MeshMap::value_type& v : meshes_converted) { - const MeshGeometry* const mesh = dynamic_cast(v.first); - if (!mesh) { + const MeshGeometry* const meshGeom = dynamic_cast(v.first); + if (!meshGeom) { continue; } - const MatIndexArray& mats = mesh->GetMaterialIndices(); + const MatIndexArray& mats = meshGeom->GetMaterialIndices(); if (std::find(mats.begin(), mats.end(), matIndex) == mats.end()) { continue; } int index = -1; for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) { - if (mesh->GetTextureCoords(i).empty()) { + if (meshGeom->GetTextureCoords(i).empty()) { break; } - const std::string& name = mesh->GetTextureCoordChannelName(i); + const std::string& name = meshGeom->GetTextureCoordChannelName(i); if (name == uvSet) { index = static_cast(i); break; diff --git a/code/FBXExportNode.cpp b/code/FBXExportNode.cpp index ace6a6ac2..e5215466a 100644 --- a/code/FBXExportNode.cpp +++ b/code/FBXExportNode.cpp @@ -432,7 +432,7 @@ void FBX::Node::WritePropertyNodeAscii( char buffer[32]; FBX::Node node(name); node.Begin(s, false, indent); - std::string vsize = std::to_string(v.size()); + std::string vsize = to_string(v.size()); // * { s.PutChar('*'); s.PutString(vsize); s.PutString(" {\n"); // indent + 1 @@ -468,7 +468,7 @@ void FBX::Node::WritePropertyNodeAscii( char buffer[32]; FBX::Node node(name); node.Begin(s, false, indent); - std::string vsize = std::to_string(v.size()); + std::string vsize = to_string(v.size()); // * { s.PutChar('*'); s.PutString(vsize); s.PutString(" {\n"); // indent + 1 diff --git a/code/FBXMaterial.cpp b/code/FBXMaterial.cpp index f5f6fda03..08347740f 100644 --- a/code/FBXMaterial.cpp +++ b/code/FBXMaterial.cpp @@ -326,8 +326,11 @@ Video::Video(uint64_t id, const Element& element, const Document& doc, const std content = new uint8_t[len]; ::memcpy(content, data + 5, len); } - } catch (runtime_error runtimeError) { + } catch (const runtime_error& runtimeError) + { //we don't need the content data for contents that has already been loaded + ASSIMP_LOG_DEBUG_F("Caught exception in FBXMaterial (likely because content was already loaded): ", + runtimeError.what()); } } diff --git a/code/IRRLoader.cpp b/code/IRRLoader.cpp index 6bc4b8ce4..796b73164 100644 --- a/code/IRRLoader.cpp +++ b/code/IRRLoader.cpp @@ -300,13 +300,10 @@ int ClampSpline(int idx, int size) { // ------------------------------------------------------------------------------------------------ inline void FindSuitableMultiple(int& angle) { - if (angle < 3)angle = 3; + if (angle < 3) angle = 3; else if (angle < 10) angle = 10; else if (angle < 20) angle = 20; else if (angle < 30) angle = 30; - else - { - } } // ------------------------------------------------------------------------------------------------ @@ -317,6 +314,8 @@ void IRRImporter::ComputeAnimations(Node* root, aiNode* real, std::vectoranimators.empty()) { return; @@ -674,38 +673,38 @@ void IRRImporter::GenerateGraph(Node* root,aiNode* rootOut ,aiScene* scene, // Get the loaded mesh from the scene and add it to // the list of all scenes to be attached to the // graph we're currently building - aiScene* scene = batch.GetImport(root->id); - if (!scene) { + aiScene* localScene = batch.GetImport(root->id); + if (!localScene) { ASSIMP_LOG_ERROR("IRR: Unable to load external file: " + root->meshPath); break; } - attach.push_back(AttachmentInfo(scene,rootOut)); + attach.push_back(AttachmentInfo(localScene,rootOut)); // Now combine the material we've loaded for this mesh // with the real materials we got from the file. As we // don't execute any pp-steps on the file, the numbers // should be equal. If they are not, we can impossibly // do this ... - if (root->materials.size() != (unsigned int)scene->mNumMaterials) { + if (root->materials.size() != (unsigned int)localScene->mNumMaterials) { ASSIMP_LOG_WARN("IRR: Failed to match imported materials " "with the materials found in the IRR scene file"); break; } - for (unsigned int i = 0; i < scene->mNumMaterials;++i) { + for (unsigned int i = 0; i < localScene->mNumMaterials;++i) { // Delete the old material, we don't need it anymore - delete scene->mMaterials[i]; + delete localScene->mMaterials[i]; std::pair& src = root->materials[i]; - scene->mMaterials[i] = src.first; + localScene->mMaterials[i] = src.first; } // NOTE: Each mesh should have exactly one material assigned, // but we do it in a separate loop if this behaviour changes // in future. - for (unsigned int i = 0; i < scene->mNumMeshes;++i) { + for (unsigned int i = 0; i < localScene->mNumMeshes;++i) { // Process material flags - aiMesh* mesh = scene->mMeshes[i]; + aiMesh* mesh = localScene->mMeshes[i]; // If "trans_vertex_alpha" mode is enabled, search all vertex colors diff --git a/code/Importer/STEPParser/STEPFileReader.cpp b/code/Importer/STEPParser/STEPFileReader.cpp index 1707899a1..f099d2be7 100644 --- a/code/Importer/STEPParser/STEPFileReader.cpp +++ b/code/Importer/STEPParser/STEPFileReader.cpp @@ -278,10 +278,10 @@ void STEP::ReadFile(DB& db,const EXPRESS::ConversionSchema& scheme, std::transform( type.begin(), type.end(), type.begin(), &Assimp::ToLower ); const char* sz = scheme.GetStaticStringForToken(type); if(sz) { - const std::string::size_type len = n2-n1+1; - char* const copysz = new char[len+1]; + const std::string::size_type szLen = n2-n1+1; + char* const copysz = new char[szLen+1]; std::copy(s.c_str()+n1,s.c_str()+n2+1,copysz); - copysz[len] = '\0'; + copysz[szLen] = '\0'; db.InternInsert(new LazyObject(db,id,line,sz,copysz)); } if(!has_next) { diff --git a/code/MD5Loader.cpp b/code/MD5Loader.cpp index 346b59504..38c44b515 100644 --- a/code/MD5Loader.cpp +++ b/code/MD5Loader.cpp @@ -443,10 +443,10 @@ void MD5Importer::LoadMD5MeshFile () for (MD5::VertexList::const_iterator iter = meshSrc.mVertices.begin();iter != meshSrc.mVertices.end();++iter,++pv) { for (unsigned int jub = (*iter).mFirstWeight, w = jub; w < jub + (*iter).mNumWeights;++w) { - MD5::WeightDesc& desc = meshSrc.mWeights[w]; + MD5::WeightDesc& weightDesc = meshSrc.mWeights[w]; /* FIX for some invalid exporters */ - if (!(desc.mWeight < AI_MD5_WEIGHT_EPSILON && desc.mWeight >= -AI_MD5_WEIGHT_EPSILON )) - ++piCount[desc.mBone]; + if (!(weightDesc.mWeight < AI_MD5_WEIGHT_EPSILON && weightDesc.mWeight >= -AI_MD5_WEIGHT_EPSILON )) + ++piCount[weightDesc.mBone]; } } @@ -493,20 +493,20 @@ void MD5Importer::LoadMD5MeshFile () if (w >= meshSrc.mWeights.size()) throw DeadlyImportError("MD5MESH: Invalid weight index"); - MD5::WeightDesc& desc = meshSrc.mWeights[w]; - if ( desc.mWeight < AI_MD5_WEIGHT_EPSILON && desc.mWeight >= -AI_MD5_WEIGHT_EPSILON) { + MD5::WeightDesc& weightDesc = meshSrc.mWeights[w]; + if ( weightDesc.mWeight < AI_MD5_WEIGHT_EPSILON && weightDesc.mWeight >= -AI_MD5_WEIGHT_EPSILON) { continue; } - const ai_real fNewWeight = desc.mWeight / fSum; + const ai_real fNewWeight = weightDesc.mWeight / fSum; // transform the local position into worldspace - MD5::BoneDesc& boneSrc = meshParser.mJoints[desc.mBone]; - const aiVector3D v = boneSrc.mRotationQuatConverted.Rotate (desc.vOffsetPosition); + MD5::BoneDesc& boneSrc = meshParser.mJoints[weightDesc.mBone]; + const aiVector3D v = boneSrc.mRotationQuatConverted.Rotate (weightDesc.vOffsetPosition); // use the original weight to compute the vertex position // (some MD5s seem to depend on the invalid weight values ...) - *pv += ((boneSrc.mPositionXYZ+v)* (ai_real)desc.mWeight); + *pv += ((boneSrc.mPositionXYZ+v)* (ai_real)weightDesc.mWeight); aiBone* bone = mesh->mBones[boneSrc.mMap]; *bone->mWeights++ = aiVertexWeight((unsigned int)(pv-mesh->mVertices),fNewWeight); diff --git a/code/STLExporter.cpp b/code/STLExporter.cpp index 00fced6ff..d56c42835 100644 --- a/code/STLExporter.cpp +++ b/code/STLExporter.cpp @@ -127,7 +127,7 @@ STLExporter::STLExporter(const char* _filename, const aiScene* pScene, bool expo mOutput.write((char *)&meshnum, 4); if (exportPointClouds) { - + throw DeadlyExportError("This functionality is not yet implemented for binary output."); } for(unsigned int i = 0; i < pScene->mNumMeshes; ++i) { diff --git a/code/glTFAssetWriter.inl b/code/glTFAssetWriter.inl index 259ad03f6..20afb24e7 100644 --- a/code/glTFAssetWriter.inl +++ b/code/glTFAssetWriter.inl @@ -294,17 +294,17 @@ namespace glTF { // filling object "compressedData" json_comp_data.SetObject(); json_comp_data.AddMember("buffer", ptr_ext_comp->Buffer, w.mAl); - json_comp_data.AddMember("byteOffset", ptr_ext_comp->Offset, w.mAl); + json_comp_data.AddMember("byteOffset", static_cast(ptr_ext_comp->Offset), w.mAl); json_comp_data.AddMember("componentType", 5121, w.mAl); json_comp_data.AddMember("type", "SCALAR", w.mAl); - json_comp_data.AddMember("count", ptr_ext_comp->Count, w.mAl); + json_comp_data.AddMember("count", static_cast(ptr_ext_comp->Count), w.mAl); if(ptr_ext_comp->Binary) json_comp_data.AddMember("mode", "binary", w.mAl); else json_comp_data.AddMember("mode", "ascii", w.mAl); - json_comp_data.AddMember("indicesCount", ptr_ext_comp->IndicesCount, w.mAl); - json_comp_data.AddMember("verticesCount", ptr_ext_comp->VerticesCount, w.mAl); + json_comp_data.AddMember("indicesCount", static_cast(ptr_ext_comp->IndicesCount), w.mAl); + json_comp_data.AddMember("verticesCount", static_cast(ptr_ext_comp->VerticesCount), w.mAl); // filling object "Open3DGC-compression" Value json_o3dgc; diff --git a/code/glTFExporter.cpp b/code/glTFExporter.cpp index 9daf202cb..6bf7415d3 100644 --- a/code/glTFExporter.cpp +++ b/code/glTFExporter.cpp @@ -245,7 +245,7 @@ inline Ref ExportData(Asset& a, std::string& meshName, Ref& bu namespace { void GetMatScalar(const aiMaterial* mat, float& val, const char* propName, int type, int idx) { - if (mat->Get(propName, type, idx, val) == AI_SUCCESS) {} + ai_assert(mat->Get(propName, type, idx, val) == AI_SUCCESS); } } diff --git a/port/PyAssimp/pyassimp/core.py b/port/PyAssimp/pyassimp/core.py index 25821ff9b..ca6d8629a 100644 --- a/port/PyAssimp/pyassimp/core.py +++ b/port/PyAssimp/pyassimp/core.py @@ -1,547 +1,545 @@ -""" -PyAssimp - -This is the main-module of PyAssimp. -""" - -import sys -if sys.version_info < (2,6): - raise 'pyassimp: need python 2.6 or newer' - -# xrange was renamed range in Python 3 and the original range from Python 2 was removed. -# To keep compatibility with both Python 2 and 3, xrange is set to range for version 3.0 and up. -if sys.version_info >= (3,0): - xrange = range - -import ctypes -import os - -try: import numpy -except: numpy = None - -import logging -logger = logging.getLogger("pyassimp") -# attach default null handler to logger so it doesn't complain -# even if you don't attach another handler to logger -logger.addHandler(logging.NullHandler()) - -from . import structs -from . import helper -from . import postprocess -from .errors import AssimpError - -class AssimpLib(object): - """ - Assimp-Singleton - """ - load, load_mem, export, export_blob, release, dll = helper.search_library() -_assimp_lib = AssimpLib() - -def make_tuple(ai_obj, type = None): - res = None - - #notes: - # ai_obj._fields_ = [ ("attr", c_type), ... ] - # getattr(ai_obj, e[0]).__class__ == float - - if isinstance(ai_obj, structs.Matrix4x4): - if numpy: - res = numpy.array([getattr(ai_obj, e[0]) for e in ai_obj._fields_]).reshape((4,4)) - #import pdb;pdb.set_trace() - else: - res = [getattr(ai_obj, e[0]) for e in ai_obj._fields_] - res = [res[i:i+4] for i in xrange(0,16,4)] - elif isinstance(ai_obj, structs.Matrix3x3): - if numpy: - res = numpy.array([getattr(ai_obj, e[0]) for e in ai_obj._fields_]).reshape((3,3)) - else: - res = [getattr(ai_obj, e[0]) for e in ai_obj._fields_] - res = [res[i:i+3] for i in xrange(0,9,3)] - else: - if numpy: - res = numpy.array([getattr(ai_obj, e[0]) for e in ai_obj._fields_]) - else: - res = [getattr(ai_obj, e[0]) for e in ai_obj._fields_] - - return res - -# Returns unicode object for Python 2, and str object for Python 3. -def _convert_assimp_string(assimp_string): - try: - return unicode(assimp_string.data, errors='ignore') - except: - return str(assimp_string.data, errors='ignore') - -# It is faster and more correct to have an init function for each assimp class -def _init_face(aiFace): - aiFace.indices = [aiFace.mIndices[i] for i in range(aiFace.mNumIndices)] -assimp_struct_inits = { structs.Face : _init_face } - -def call_init(obj, caller = None): - if helper.hasattr_silent(obj,'contents'): #pointer - _init(obj.contents, obj, caller) - else: - _init(obj,parent=caller) - -def _is_init_type(obj): - if helper.hasattr_silent(obj,'contents'): #pointer - return _is_init_type(obj[0]) - # null-pointer case that arises when we reach a mesh attribute - # like mBitangents which use mNumVertices rather than mNumBitangents - # so it breaks the 'is iterable' check. - # Basically: - # FIXME! - elif not bool(obj): - return False - tname = obj.__class__.__name__ - return not (tname[:2] == 'c_' or tname == 'Structure' \ - or tname == 'POINTER') and not isinstance(obj,int) - -def _init(self, target = None, parent = None): - """ - Custom initialize() for C structs, adds safely accessible member functionality. - - :param target: set the object which receive the added methods. Useful when manipulating - pointers, to skip the intermediate 'contents' deferencing. - """ - if not target: - target = self - - dirself = dir(self) - for m in dirself: - - if m.startswith("_"): - continue - - if m.startswith('mNum'): - if 'm' + m[4:] in dirself: - continue # will be processed later on - else: - name = m[1:].lower() - - obj = getattr(self, m) - setattr(target, name, obj) - continue - - if m == 'mName': - target.name = str(_convert_assimp_string(self.mName)) - target.__class__.__repr__ = lambda x: str(x.__class__) + "(" + getattr(x, 'name','') + ")" - target.__class__.__str__ = lambda x: getattr(x, 'name', '') - continue - - name = m[1:].lower() - - obj = getattr(self, m) - - # Create tuples - if isinstance(obj, structs.assimp_structs_as_tuple): - setattr(target, name, make_tuple(obj)) - logger.debug(str(self) + ": Added array " + str(getattr(target, name)) + " as self." + name.lower()) - continue - - if m.startswith('m'): - - if name == "parent": - setattr(target, name, parent) - logger.debug("Added a parent as self." + name) - continue - - if helper.hasattr_silent(self, 'mNum' + m[1:]): - - length = getattr(self, 'mNum' + m[1:]) - - # -> special case: properties are - # stored as a dict. - if m == 'mProperties': - setattr(target, name, _get_properties(obj, length)) - continue - - - if not length: # empty! - setattr(target, name, []) - logger.debug(str(self) + ": " + name + " is an empty list.") - continue - - - try: - if obj._type_ in structs.assimp_structs_as_tuple: - if numpy: - 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, [make_tuple(obj[i]) for i in range(length)]) - - logger.debug(str(self) + ": Added a list of lists (type "+ str(type(obj)) + ") as self." + name) - - else: - setattr(target, name, [obj[i] for i in range(length)]) #TODO: maybe not necessary to recreate an array? - - logger.debug(str(self) + ": Added list of " + str(obj) + " " + name + " as self." + name + " (type: " + str(type(obj)) + ")") - - # initialize array elements - try: - init = assimp_struct_inits[type(obj[0])] - except KeyError: - if _is_init_type(obj[0]): - for e in getattr(target, name): - call_init(e, target) - else: - for e in getattr(target, name): - init(e) - - - except IndexError: - logger.error("in " + str(self) +" : mismatch between mNum" + name + " and the actual amount of data in m" + name + ". This may be due to version mismatch between libassimp and pyassimp. Quitting now.") - sys.exit(1) - - except ValueError as e: - - logger.error("In " + str(self) + "->" + name + ": " + str(e) + ". Quitting now.") - if "setting an array element with a sequence" in str(e): - logger.error("Note that pyassimp does not currently " - "support meshes with mixed triangles " - "and quads. Try to load your mesh with" - " a post-processing to triangulate your" - " faces.") - raise e - - - - else: # starts with 'm' but not iterable - setattr(target, name, obj) - logger.debug("Added " + name + " as self." + name + " (type: " + str(type(obj)) + ")") - - if _is_init_type(obj): - call_init(obj, target) - - if isinstance(self, structs.Mesh): - _finalize_mesh(self, target) - - if isinstance(self, structs.Texture): - _finalize_texture(self, target) - - if isinstance(self, structs.Metadata): - _finalize_metadata(self, target) - - - return self - - -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 c in node.children: - recur_pythonize(c, scene) - -def load(filename, - file_type = None, - processing = postprocess.aiProcess_Triangulate): - ''' - Load a model into a scene. On failure throws AssimpError. - - Arguments - --------- - filename: Either a filename or a file object to load model from. - If a file object is passed, file_type MUST be specified - Otherwise Assimp has no idea which importer to use. - This is named 'filename' so as to not break legacy code. - processing: assimp postprocessing parameters. Verbose keywords are imported - from postprocessing, and the parameters can be combined bitwise to - generate the final processing value. Note that the default value will - triangulate quad faces. Example of generating other possible values: - processing = (pyassimp.postprocess.aiProcess_Triangulate | - pyassimp.postprocess.aiProcess_OptimizeMeshes) - file_type: string of file extension, such as 'stl' - - Returns - --------- - Scene object with model data - ''' - - if hasattr(filename, 'read'): - # This is the case where a file object has been passed to load. - # It is calling the following function: - # const aiScene* aiImportFileFromMemory(const char* pBuffer, - # unsigned int pLength, - # unsigned int pFlags, - # const char* pHint) - if file_type == None: - raise AssimpError('File type must be specified when passing file objects!') - data = filename.read() - model = _assimp_lib.load_mem(data, - len(data), - processing, - file_type) - else: - # a filename string has been passed - model = _assimp_lib.load(filename.encode(sys.getfilesystemencoding()), processing) - - if not model: - raise AssimpError('Could not import file!') - scene = _init(model.contents) - recur_pythonize(scene.rootnode, scene) - return scene - -def export(scene, - filename, - file_type = None, - processing = postprocess.aiProcess_Triangulate): - ''' - Export a scene. On failure throws AssimpError. - - Arguments - --------- - scene: scene to export. - filename: Filename that the scene should be exported to. - file_type: string of file exporter to use. For example "collada". - processing: assimp postprocessing parameters. Verbose keywords are imported - from postprocessing, and the parameters can be combined bitwise to - generate the final processing value. Note that the default value will - triangulate quad faces. Example of generating other possible values: - processing = (pyassimp.postprocess.aiProcess_Triangulate | - pyassimp.postprocess.aiProcess_OptimizeMeshes) - - ''' - - from ctypes import pointer - exportStatus = _assimp_lib.export(pointer(scene), file_type.encode("ascii"), filename.encode(sys.getfilesystemencoding()), processing) - - if exportStatus != 0: - raise AssimpError('Could not export scene!') - -def export_blob(scene, - file_type = None, - processing = postprocess.aiProcess_Triangulate): - ''' - Export a scene and return a blob in the correct format. On failure throws AssimpError. - - Arguments - --------- - scene: scene to export. - file_type: string of file exporter to use. For example "collada". - processing: assimp postprocessing parameters. Verbose keywords are imported - from postprocessing, and the parameters can be combined bitwise to - generate the final processing value. Note that the default value will - triangulate quad faces. Example of generating other possible values: - processing = (pyassimp.postprocess.aiProcess_Triangulate | - pyassimp.postprocess.aiProcess_OptimizeMeshes) - Returns - --------- - Pointer to structs.ExportDataBlob - ''' - from ctypes import pointer - exportBlobPtr = _assimp_lib.export_blob(pointer(scene), file_type.encode("ascii"), processing) - - if exportBlobPtr == 0: - raise AssimpError('Could not export scene to blob!') - return exportBlobPtr - -def release(scene): - from ctypes import pointer - _assimp_lib.release(pointer(scene)) - -def _finalize_texture(tex, target): - setattr(target, "achformathint", tex.achFormatHint) - if numpy: - data = numpy.array([make_tuple(getattr(tex, "pcData")[i]) for i in range(tex.mWidth * tex.mHeight)]) - else: - data = [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 numpy: - if mAttr: - data = numpy.array([make_tuple(getattr(mesh, name)[i]) for i in range(nb_vertices)], dtype=numpy.float32) - setattr(target, name[1:].lower(), data) - else: - setattr(target, name[1:].lower(), numpy.array([], dtype="float32")) - else: - if mAttr: - data = [make_tuple(getattr(mesh, name)[i]) for i in range(nb_vertices)] - 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)]) - - if numpy: - setattr(target, name[1:].lower(), numpy.array(data, dtype=numpy.float32)) - else: - setattr(target, name[1:].lower(), data) - - fill("mNormals") - fill("mTangents") - fill("mBitangents") - - fillarray("mColors") - fillarray("mTextureCoords") - - # prepare faces - if numpy: - faces = numpy.array([f.indices for f in target.faces], dtype=numpy.int32) - else: - faces = [f.indices for f in target.faces] - setattr(target, 'faces', faces) - -def _init_metadata_entry(entry): - from ctypes import POINTER, c_bool, c_int32, c_uint64, c_float, c_double, cast - - entry.type = entry.mType - if entry.type == structs.MetadataEntry.AI_BOOL: - entry.data = cast(entry.mData, POINTER(c_bool)).contents.value - elif entry.type == structs.MetadataEntry.AI_INT32: - entry.data = cast(entry.mData, POINTER(c_int32)).contents.value - elif entry.type == structs.MetadataEntry.AI_UINT64: - entry.data = cast(entry.mData, POINTER(c_uint64)).contents.value - elif entry.type == structs.MetadataEntry.AI_FLOAT: - entry.data = cast(entry.mData, POINTER(c_float)).contents.value - elif entry.type == structs.MetadataEntry.AI_DOUBLE: - entry.data = cast(entry.mData, POINTER(c_double)).contents.value - elif entry.type == structs.MetadataEntry.AI_AISTRING: - assimp_string = cast(entry.mData, POINTER(structs.String)).contents - entry.data = _convert_assimp_string(assimp_string) - elif entry.type == structs.MetadataEntry.AI_AIVECTOR3D: - assimp_vector = cast(entry.mData, POINTER(structs.Vector3D)).contents - entry.data = make_tuple(assimp_vector) - - return entry - -def _finalize_metadata(metadata, target): - """ Building the metadata object is a bit specific. - - Firstly, there are two separate arrays: one with metadata keys and one - with metadata values, and there are no corresponding mNum* attributes, - so the C arrays are not converted to Python arrays using the generic - code in the _init function. - - Secondly, a metadata entry value has to be cast according to declared - metadata entry type. - """ - length = metadata.mNumProperties - setattr(target, 'keys', [str(_convert_assimp_string(metadata.mKeys[i])) for i in range(length)]) - setattr(target, 'values', [_init_metadata_entry(metadata.mValues[i]) for i in range(length)]) - -class PropertyGetter(dict): - def __getitem__(self, key): - semantic = 0 - if isinstance(key, tuple): - key, semantic = key - - return dict.__getitem__(self, (key, semantic)) - - def keys(self): - for k in dict.keys(self): - yield k[0] - - def __iter__(self): - return self.keys() - - def items(self): - for k, v in dict.items(self): - yield k[0], v - - -def _get_properties(properties, length): - """ - Convenience Function to get the material properties as a dict - and values in a python format. - """ - result = {} - #read all properties - for p in [properties[i] for i in range(length)]: - #the name - p = p.contents - key = str(_convert_assimp_string(p.mKey)) - key = (key.split('.')[1], p.mSemantic) - - #the data - from ctypes import POINTER, cast, c_int, c_float, sizeof - if p.mType == 1: - arr = cast(p.mData, POINTER(c_float * int(p.mDataLength/sizeof(c_float)) )).contents - value = [x for x in arr] - elif p.mType == 3: #string can't be an array - value = _convert_assimp_string(cast(p.mData, POINTER(structs.MaterialPropertyString)).contents) - - elif p.mType == 4: - arr = cast(p.mData, POINTER(c_int * int(p.mDataLength/sizeof(c_int)) )).contents - value = [x for x in arr] - else: - value = p.mData[:p.mDataLength] - - if len(value) == 1: - [value] = value - - result[key] = value - - return PropertyGetter(result) - -def decompose_matrix(matrix): - if not isinstance(matrix, structs.Matrix4x4): - raise AssimpError("pyassimp.decompose_matrix failed: Not a Matrix4x4!") - - scaling = structs.Vector3D() - rotation = structs.Quaternion() - position = structs.Vector3D() - - from ctypes import byref, pointer - _assimp_lib.dll.aiDecomposeMatrix(pointer(matrix), byref(scaling), byref(rotation), byref(position)) - return scaling._init(), rotation._init(), position._init() - +""" +PyAssimp + +This is the main-module of PyAssimp. +""" + +import sys +if sys.version_info < (2,6): + raise RuntimeError('pyassimp: need python 2.6 or newer') + +# xrange was renamed range in Python 3 and the original range from Python 2 was removed. +# To keep compatibility with both Python 2 and 3, xrange is set to range for version 3.0 and up. +if sys.version_info >= (3,0): + xrange = range + + +try: import numpy +except ImportError: numpy = None +import logging +import ctypes +logger = logging.getLogger("pyassimp") +# attach default null handler to logger so it doesn't complain +# even if you don't attach another handler to logger +logger.addHandler(logging.NullHandler()) + +from . import structs +from . import helper +from . import postprocess +from .errors import AssimpError + +class AssimpLib(object): + """ + Assimp-Singleton + """ + load, load_mem, export, export_blob, release, dll = helper.search_library() +_assimp_lib = AssimpLib() + +def make_tuple(ai_obj, type = None): + res = None + + #notes: + # ai_obj._fields_ = [ ("attr", c_type), ... ] + # getattr(ai_obj, e[0]).__class__ == float + + if isinstance(ai_obj, structs.Matrix4x4): + if numpy: + res = numpy.array([getattr(ai_obj, e[0]) for e in ai_obj._fields_]).reshape((4,4)) + #import pdb;pdb.set_trace() + else: + res = [getattr(ai_obj, e[0]) for e in ai_obj._fields_] + res = [res[i:i+4] for i in xrange(0,16,4)] + elif isinstance(ai_obj, structs.Matrix3x3): + if numpy: + res = numpy.array([getattr(ai_obj, e[0]) for e in ai_obj._fields_]).reshape((3,3)) + else: + res = [getattr(ai_obj, e[0]) for e in ai_obj._fields_] + res = [res[i:i+3] for i in xrange(0,9,3)] + else: + if numpy: + res = numpy.array([getattr(ai_obj, e[0]) for e in ai_obj._fields_]) + else: + res = [getattr(ai_obj, e[0]) for e in ai_obj._fields_] + + return res + +# Returns unicode object for Python 2, and str object for Python 3. +def _convert_assimp_string(assimp_string): + if sys.version_info >= (3, 0): + return str(assimp_string.data, errors='ignore') + else: + return unicode(assimp_string.data, errors='ignore') + +# It is faster and more correct to have an init function for each assimp class +def _init_face(aiFace): + aiFace.indices = [aiFace.mIndices[i] for i in range(aiFace.mNumIndices)] +assimp_struct_inits = { structs.Face : _init_face } + +def call_init(obj, caller = None): + if helper.hasattr_silent(obj,'contents'): #pointer + _init(obj.contents, obj, caller) + else: + _init(obj,parent=caller) + +def _is_init_type(obj): + if helper.hasattr_silent(obj,'contents'): #pointer + return _is_init_type(obj[0]) + # null-pointer case that arises when we reach a mesh attribute + # like mBitangents which use mNumVertices rather than mNumBitangents + # so it breaks the 'is iterable' check. + # Basically: + # FIXME! + elif not bool(obj): + return False + tname = obj.__class__.__name__ + return not (tname[:2] == 'c_' or tname == 'Structure' \ + or tname == 'POINTER') and not isinstance(obj,int) + +def _init(self, target = None, parent = None): + """ + Custom initialize() for C structs, adds safely accessible member functionality. + + :param target: set the object which receive the added methods. Useful when manipulating + pointers, to skip the intermediate 'contents' deferencing. + """ + if not target: + target = self + + dirself = dir(self) + for m in dirself: + + if m.startswith("_"): + continue + + if m.startswith('mNum'): + if 'm' + m[4:] in dirself: + continue # will be processed later on + else: + name = m[1:].lower() + + obj = getattr(self, m) + setattr(target, name, obj) + continue + + if m == 'mName': + target.name = str(_convert_assimp_string(self.mName)) + target.__class__.__repr__ = lambda x: str(x.__class__) + "(" + getattr(x, 'name','') + ")" + target.__class__.__str__ = lambda x: getattr(x, 'name', '') + continue + + name = m[1:].lower() + + obj = getattr(self, m) + + # Create tuples + if isinstance(obj, structs.assimp_structs_as_tuple): + setattr(target, name, make_tuple(obj)) + logger.debug(str(self) + ": Added array " + str(getattr(target, name)) + " as self." + name.lower()) + continue + + if m.startswith('m'): + + if name == "parent": + setattr(target, name, parent) + logger.debug("Added a parent as self." + name) + continue + + if helper.hasattr_silent(self, 'mNum' + m[1:]): + + length = getattr(self, 'mNum' + m[1:]) + + # -> special case: properties are + # stored as a dict. + if m == 'mProperties': + setattr(target, name, _get_properties(obj, length)) + continue + + + if not length: # empty! + setattr(target, name, []) + logger.debug(str(self) + ": " + name + " is an empty list.") + continue + + + try: + if obj._type_ in structs.assimp_structs_as_tuple: + if numpy: + 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, [make_tuple(obj[i]) for i in range(length)]) + + logger.debug(str(self) + ": Added a list of lists (type "+ str(type(obj)) + ") as self." + name) + + else: + setattr(target, name, [obj[i] for i in range(length)]) #TODO: maybe not necessary to recreate an array? + + logger.debug(str(self) + ": Added list of " + str(obj) + " " + name + " as self." + name + " (type: " + str(type(obj)) + ")") + + # initialize array elements + try: + init = assimp_struct_inits[type(obj[0])] + except KeyError: + if _is_init_type(obj[0]): + for e in getattr(target, name): + call_init(e, target) + else: + for e in getattr(target, name): + init(e) + + + except IndexError: + logger.error("in " + str(self) +" : mismatch between mNum" + name + " and the actual amount of data in m" + name + ". This may be due to version mismatch between libassimp and pyassimp. Quitting now.") + sys.exit(1) + + except ValueError as e: + + logger.error("In " + str(self) + "->" + name + ": " + str(e) + ". Quitting now.") + if "setting an array element with a sequence" in str(e): + logger.error("Note that pyassimp does not currently " + "support meshes with mixed triangles " + "and quads. Try to load your mesh with" + " a post-processing to triangulate your" + " faces.") + raise e + + + + else: # starts with 'm' but not iterable + setattr(target, name, obj) + logger.debug("Added " + name + " as self." + name + " (type: " + str(type(obj)) + ")") + + if _is_init_type(obj): + call_init(obj, target) + + if isinstance(self, structs.Mesh): + _finalize_mesh(self, target) + + if isinstance(self, structs.Texture): + _finalize_texture(self, target) + + if isinstance(self, structs.Metadata): + _finalize_metadata(self, target) + + + return self + + +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 c in node.children: + recur_pythonize(c, scene) + +def load(filename, + file_type = None, + processing = postprocess.aiProcess_Triangulate): + ''' + Load a model into a scene. On failure throws AssimpError. + + Arguments + --------- + filename: Either a filename or a file object to load model from. + If a file object is passed, file_type MUST be specified + Otherwise Assimp has no idea which importer to use. + This is named 'filename' so as to not break legacy code. + processing: assimp postprocessing parameters. Verbose keywords are imported + from postprocessing, and the parameters can be combined bitwise to + generate the final processing value. Note that the default value will + triangulate quad faces. Example of generating other possible values: + processing = (pyassimp.postprocess.aiProcess_Triangulate | + pyassimp.postprocess.aiProcess_OptimizeMeshes) + file_type: string of file extension, such as 'stl' + + Returns + --------- + Scene object with model data + ''' + + if hasattr(filename, 'read'): + # This is the case where a file object has been passed to load. + # It is calling the following function: + # const aiScene* aiImportFileFromMemory(const char* pBuffer, + # unsigned int pLength, + # unsigned int pFlags, + # const char* pHint) + if file_type is None: + raise AssimpError('File type must be specified when passing file objects!') + data = filename.read() + model = _assimp_lib.load_mem(data, + len(data), + processing, + file_type) + else: + # a filename string has been passed + model = _assimp_lib.load(filename.encode(sys.getfilesystemencoding()), processing) + + if not model: + raise AssimpError('Could not import file!') + scene = _init(model.contents) + recur_pythonize(scene.rootnode, scene) + return scene + +def export(scene, + filename, + file_type = None, + processing = postprocess.aiProcess_Triangulate): + ''' + Export a scene. On failure throws AssimpError. + + Arguments + --------- + scene: scene to export. + filename: Filename that the scene should be exported to. + file_type: string of file exporter to use. For example "collada". + processing: assimp postprocessing parameters. Verbose keywords are imported + from postprocessing, and the parameters can be combined bitwise to + generate the final processing value. Note that the default value will + triangulate quad faces. Example of generating other possible values: + processing = (pyassimp.postprocess.aiProcess_Triangulate | + pyassimp.postprocess.aiProcess_OptimizeMeshes) + + ''' + + exportStatus = _assimp_lib.export(ctypes.pointer(scene), file_type.encode("ascii"), filename.encode(sys.getfilesystemencoding()), processing) + + if exportStatus != 0: + raise AssimpError('Could not export scene!') + +def export_blob(scene, + file_type = None, + processing = postprocess.aiProcess_Triangulate): + ''' + Export a scene and return a blob in the correct format. On failure throws AssimpError. + + Arguments + --------- + scene: scene to export. + file_type: string of file exporter to use. For example "collada". + processing: assimp postprocessing parameters. Verbose keywords are imported + from postprocessing, and the parameters can be combined bitwise to + generate the final processing value. Note that the default value will + triangulate quad faces. Example of generating other possible values: + processing = (pyassimp.postprocess.aiProcess_Triangulate | + pyassimp.postprocess.aiProcess_OptimizeMeshes) + Returns + --------- + Pointer to structs.ExportDataBlob + ''' + exportBlobPtr = _assimp_lib.export_blob(ctypes.pointer(scene), file_type.encode("ascii"), processing) + + if exportBlobPtr == 0: + raise AssimpError('Could not export scene to blob!') + return exportBlobPtr + +def release(scene): + _assimp_lib.release(ctypes.pointer(scene)) + +def _finalize_texture(tex, target): + setattr(target, "achformathint", tex.achFormatHint) + if numpy: + data = numpy.array([make_tuple(getattr(tex, "pcData")[i]) for i in range(tex.mWidth * tex.mHeight)]) + else: + data = [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 numpy: + if mAttr: + data = numpy.array([make_tuple(getattr(mesh, name)[i]) for i in range(nb_vertices)], dtype=numpy.float32) + setattr(target, name[1:].lower(), data) + else: + setattr(target, name[1:].lower(), numpy.array([], dtype="float32")) + else: + if mAttr: + data = [make_tuple(getattr(mesh, name)[i]) for i in range(nb_vertices)] + 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)]) + + if numpy: + setattr(target, name[1:].lower(), numpy.array(data, dtype=numpy.float32)) + else: + setattr(target, name[1:].lower(), data) + + fill("mNormals") + fill("mTangents") + fill("mBitangents") + + fillarray("mColors") + fillarray("mTextureCoords") + + # prepare faces + if numpy: + faces = numpy.array([f.indices for f in target.faces], dtype=numpy.int32) + else: + faces = [f.indices for f in target.faces] + setattr(target, 'faces', faces) + +def _init_metadata_entry(entry): + entry.type = entry.mType + if entry.type == structs.MetadataEntry.AI_BOOL: + entry.data = ctypes.cast(entry.mData, ctypes.POINTER(ctypes.c_bool)).contents.value + elif entry.type == structs.MetadataEntry.AI_INT32: + entry.data = ctypes.cast(entry.mData, ctypes.POINTER(ctypes.c_int32)).contents.value + elif entry.type == structs.MetadataEntry.AI_UINT64: + entry.data = ctypes.cast(entry.mData, ctypes.POINTER(ctypes.c_uint64)).contents.value + elif entry.type == structs.MetadataEntry.AI_FLOAT: + entry.data = ctypes.cast(entry.mData, ctypes.POINTER(ctypes.c_float)).contents.value + elif entry.type == structs.MetadataEntry.AI_DOUBLE: + entry.data = ctypes.cast(entry.mData, ctypes.POINTER(ctypes.c_double)).contents.value + elif entry.type == structs.MetadataEntry.AI_AISTRING: + assimp_string = ctypes.cast(entry.mData, ctypes.POINTER(structs.String)).contents + entry.data = _convert_assimp_string(assimp_string) + elif entry.type == structs.MetadataEntry.AI_AIVECTOR3D: + assimp_vector = ctypes.cast(entry.mData, ctypes.POINTER(structs.Vector3D)).contents + entry.data = make_tuple(assimp_vector) + + return entry + +def _finalize_metadata(metadata, target): + """ Building the metadata object is a bit specific. + + Firstly, there are two separate arrays: one with metadata keys and one + with metadata values, and there are no corresponding mNum* attributes, + so the C arrays are not converted to Python arrays using the generic + code in the _init function. + + Secondly, a metadata entry value has to be cast according to declared + metadata entry type. + """ + length = metadata.mNumProperties + setattr(target, 'keys', [str(_convert_assimp_string(metadata.mKeys[i])) for i in range(length)]) + setattr(target, 'values', [_init_metadata_entry(metadata.mValues[i]) for i in range(length)]) + +class PropertyGetter(dict): + def __getitem__(self, key): + semantic = 0 + if isinstance(key, tuple): + key, semantic = key + + return dict.__getitem__(self, (key, semantic)) + + def keys(self): + for k in dict.keys(self): + yield k[0] + + def __iter__(self): + return self.keys() + + def items(self): + for k, v in dict.items(self): + yield k[0], v + + +def _get_properties(properties, length): + """ + Convenience Function to get the material properties as a dict + and values in a python format. + """ + result = {} + #read all properties + for p in [properties[i] for i in range(length)]: + #the name + p = p.contents + key = str(_convert_assimp_string(p.mKey)) + key = (key.split('.')[1], p.mSemantic) + + #the data + if p.mType == 1: + arr = ctypes.cast(p.mData, + ctypes.POINTER(ctypes.c_float * int(p.mDataLength/ctypes.sizeof(ctypes.c_float))) + ).contents + value = [x for x in arr] + elif p.mType == 3: #string can't be an array + value = _convert_assimp_string(ctypes.cast(p.mData, ctypes.POINTER(structs.MaterialPropertyString)).contents) + + elif p.mType == 4: + arr = ctypes.cast(p.mData, + ctypes.POINTER(ctypes.c_int * int(p.mDataLength/ctypes.sizeof(ctypes.c_int))) + ).contents + value = [x for x in arr] + else: + value = p.mData[:p.mDataLength] + + if len(value) == 1: + [value] = value + + result[key] = value + + return PropertyGetter(result) + +def decompose_matrix(matrix): + if not isinstance(matrix, structs.Matrix4x4): + raise AssimpError("pyassimp.decompose_matrix failed: Not a Matrix4x4!") + + scaling = structs.Vector3D() + rotation = structs.Quaternion() + position = structs.Vector3D() + + _assimp_lib.dll.aiDecomposeMatrix(ctypes.pointer(matrix), + ctypes.byref(scaling), + ctypes.byref(rotation), + ctypes.byref(position)) + return scaling._init(), rotation._init(), position._init() + diff --git a/port/PyAssimp/pyassimp/formats.py b/port/PyAssimp/pyassimp/formats.py index baba1645c..5d454e5b7 100644 --- a/port/PyAssimp/pyassimp/formats.py +++ b/port/PyAssimp/pyassimp/formats.py @@ -21,7 +21,7 @@ FORMATS = ["CSM", "STL", "IRR", "Q3O", - "Q3D" + "Q3D", "MS3D", "Q3S", "ZGL", diff --git a/port/PyAssimp/pyassimp/helper.py b/port/PyAssimp/pyassimp/helper.py index 4e9f10e94..9042b7f9f 100644 --- a/port/PyAssimp/pyassimp/helper.py +++ b/port/PyAssimp/pyassimp/helper.py @@ -1,280 +1,279 @@ -#-*- coding: UTF-8 -*- - -""" -Some fancy helper functions. -""" - -import os -import ctypes -from ctypes import POINTER -import operator - -from distutils.sysconfig import get_python_lib -import re -import sys - -try: import numpy -except: numpy = None - -import logging;logger = logging.getLogger("pyassimp") - -from .errors import AssimpError - -additional_dirs, ext_whitelist = [],[] - -# populate search directories and lists of allowed file extensions -# depending on the platform we're running on. -if os.name=='posix': - additional_dirs.append('./') - additional_dirs.append('/usr/lib/') - additional_dirs.append('/usr/lib/x86_64-linux-gnu/') - additional_dirs.append('/usr/local/lib/') - - if 'LD_LIBRARY_PATH' in os.environ: - additional_dirs.extend([item for item in os.environ['LD_LIBRARY_PATH'].split(':') if item]) - - # check if running from anaconda. - if "conda" or "continuum" in sys.version.lower(): - cur_path = get_python_lib() - pattern = re.compile('.*\/lib\/') - conda_lib = pattern.match(cur_path).group() - logger.info("Adding Anaconda lib path:"+ conda_lib) - additional_dirs.append(conda_lib) - - # note - this won't catch libassimp.so.N.n, but - # currently there's always a symlink called - # libassimp.so in /usr/local/lib. - ext_whitelist.append('.so') - # libassimp.dylib in /usr/local/lib - ext_whitelist.append('.dylib') - -elif os.name=='nt': - ext_whitelist.append('.dll') - path_dirs = os.environ['PATH'].split(';') - additional_dirs.extend(path_dirs) - -def vec2tuple(x): - """ Converts a VECTOR3D to a Tuple """ - return (x.x, x.y, x.z) - -def transform(vector3, matrix4x4): - """ Apply a transformation matrix on a 3D vector. - - :param vector3: array with 3 elements - :param matrix4x4: 4x4 matrix - """ - if numpy: - return numpy.dot(matrix4x4, numpy.append(vector3, 1.)) - else: - m0,m1,m2,m3 = matrix4x4; x,y,z = vector3 - return [ - m0[0]*x + m0[1]*y + m0[2]*z + m0[3], - m1[0]*x + m1[1]*y + m1[2]*z + m1[3], - m2[0]*x + m2[1]*y + m2[2]*z + m2[3], - m3[0]*x + m3[1]*y + m3[2]*z + m3[3] - ] - -def _inv(matrix4x4): - m0,m1,m2,m3 = matrix4x4 - - det = m0[3]*m1[2]*m2[1]*m3[0] - m0[2]*m1[3]*m2[1]*m3[0] - \ - m0[3]*m1[1]*m2[2]*m3[0] + m0[1]*m1[3]*m2[2]*m3[0] + \ - m0[2]*m1[1]*m2[3]*m3[0] - m0[1]*m1[2]*m2[3]*m3[0] - \ - m0[3]*m1[2]*m2[0]*m3[1] + m0[2]*m1[3]*m2[0]*m3[1] + \ - m0[3]*m1[0]*m2[2]*m3[1] - m0[0]*m1[3]*m2[2]*m3[1] - \ - m0[2]*m1[0]*m2[3]*m3[1] + m0[0]*m1[2]*m2[3]*m3[1] + \ - m0[3]*m1[1]*m2[0]*m3[2] - m0[1]*m1[3]*m2[0]*m3[2] - \ - m0[3]*m1[0]*m2[1]*m3[2] + m0[0]*m1[3]*m2[1]*m3[2] + \ - m0[1]*m1[0]*m2[3]*m3[2] - m0[0]*m1[1]*m2[3]*m3[2] - \ - m0[2]*m1[1]*m2[0]*m3[3] + m0[1]*m1[2]*m2[0]*m3[3] + \ - m0[2]*m1[0]*m2[1]*m3[3] - m0[0]*m1[2]*m2[1]*m3[3] - \ - m0[1]*m1[0]*m2[2]*m3[3] + m0[0]*m1[1]*m2[2]*m3[3] - - return[[( m1[2]*m2[3]*m3[1] - m1[3]*m2[2]*m3[1] + m1[3]*m2[1]*m3[2] - m1[1]*m2[3]*m3[2] - m1[2]*m2[1]*m3[3] + m1[1]*m2[2]*m3[3]) /det, - ( m0[3]*m2[2]*m3[1] - m0[2]*m2[3]*m3[1] - m0[3]*m2[1]*m3[2] + m0[1]*m2[3]*m3[2] + m0[2]*m2[1]*m3[3] - m0[1]*m2[2]*m3[3]) /det, - ( m0[2]*m1[3]*m3[1] - m0[3]*m1[2]*m3[1] + m0[3]*m1[1]*m3[2] - m0[1]*m1[3]*m3[2] - m0[2]*m1[1]*m3[3] + m0[1]*m1[2]*m3[3]) /det, - ( m0[3]*m1[2]*m2[1] - m0[2]*m1[3]*m2[1] - m0[3]*m1[1]*m2[2] + m0[1]*m1[3]*m2[2] + m0[2]*m1[1]*m2[3] - m0[1]*m1[2]*m2[3]) /det], - [( m1[3]*m2[2]*m3[0] - m1[2]*m2[3]*m3[0] - m1[3]*m2[0]*m3[2] + m1[0]*m2[3]*m3[2] + m1[2]*m2[0]*m3[3] - m1[0]*m2[2]*m3[3]) /det, - ( m0[2]*m2[3]*m3[0] - m0[3]*m2[2]*m3[0] + m0[3]*m2[0]*m3[2] - m0[0]*m2[3]*m3[2] - m0[2]*m2[0]*m3[3] + m0[0]*m2[2]*m3[3]) /det, - ( m0[3]*m1[2]*m3[0] - m0[2]*m1[3]*m3[0] - m0[3]*m1[0]*m3[2] + m0[0]*m1[3]*m3[2] + m0[2]*m1[0]*m3[3] - m0[0]*m1[2]*m3[3]) /det, - ( m0[2]*m1[3]*m2[0] - m0[3]*m1[2]*m2[0] + m0[3]*m1[0]*m2[2] - m0[0]*m1[3]*m2[2] - m0[2]*m1[0]*m2[3] + m0[0]*m1[2]*m2[3]) /det], - [( m1[1]*m2[3]*m3[0] - m1[3]*m2[1]*m3[0] + m1[3]*m2[0]*m3[1] - m1[0]*m2[3]*m3[1] - m1[1]*m2[0]*m3[3] + m1[0]*m2[1]*m3[3]) /det, - ( m0[3]*m2[1]*m3[0] - m0[1]*m2[3]*m3[0] - m0[3]*m2[0]*m3[1] + m0[0]*m2[3]*m3[1] + m0[1]*m2[0]*m3[3] - m0[0]*m2[1]*m3[3]) /det, - ( m0[1]*m1[3]*m3[0] - m0[3]*m1[1]*m3[0] + m0[3]*m1[0]*m3[1] - m0[0]*m1[3]*m3[1] - m0[1]*m1[0]*m3[3] + m0[0]*m1[1]*m3[3]) /det, - ( m0[3]*m1[1]*m2[0] - m0[1]*m1[3]*m2[0] - m0[3]*m1[0]*m2[1] + m0[0]*m1[3]*m2[1] + m0[1]*m1[0]*m2[3] - m0[0]*m1[1]*m2[3]) /det], - [( m1[2]*m2[1]*m3[0] - m1[1]*m2[2]*m3[0] - m1[2]*m2[0]*m3[1] + m1[0]*m2[2]*m3[1] + m1[1]*m2[0]*m3[2] - m1[0]*m2[1]*m3[2]) /det, - ( m0[1]*m2[2]*m3[0] - m0[2]*m2[1]*m3[0] + m0[2]*m2[0]*m3[1] - m0[0]*m2[2]*m3[1] - m0[1]*m2[0]*m3[2] + m0[0]*m2[1]*m3[2]) /det, - ( m0[2]*m1[1]*m3[0] - m0[1]*m1[2]*m3[0] - m0[2]*m1[0]*m3[1] + m0[0]*m1[2]*m3[1] + m0[1]*m1[0]*m3[2] - m0[0]*m1[1]*m3[2]) /det, - ( m0[1]*m1[2]*m2[0] - m0[2]*m1[1]*m2[0] + m0[2]*m1[0]*m2[1] - m0[0]*m1[2]*m2[1] - m0[1]*m1[0]*m2[2] + m0[0]*m1[1]*m2[2]) /det]] - -def get_bounding_box(scene): - bb_min = [1e10, 1e10, 1e10] # x,y,z - bb_max = [-1e10, -1e10, -1e10] # x,y,z - inv = numpy.linalg.inv if numpy else _inv - return get_bounding_box_for_node(scene.rootnode, bb_min, bb_max, inv(scene.rootnode.transformation)) - -def get_bounding_box_for_node(node, bb_min, bb_max, transformation): - - if numpy: - transformation = numpy.dot(transformation, node.transformation) - else: - t0,t1,t2,t3 = transformation - T0,T1,T2,T3 = node.transformation - transformation = [ [ - t0[0]*T0[0] + t0[1]*T1[0] + t0[2]*T2[0] + t0[3]*T3[0], - t0[0]*T0[1] + t0[1]*T1[1] + t0[2]*T2[1] + t0[3]*T3[1], - t0[0]*T0[2] + t0[1]*T1[2] + t0[2]*T2[2] + t0[3]*T3[2], - t0[0]*T0[3] + t0[1]*T1[3] + t0[2]*T2[3] + t0[3]*T3[3] - ],[ - t1[0]*T0[0] + t1[1]*T1[0] + t1[2]*T2[0] + t1[3]*T3[0], - t1[0]*T0[1] + t1[1]*T1[1] + t1[2]*T2[1] + t1[3]*T3[1], - t1[0]*T0[2] + t1[1]*T1[2] + t1[2]*T2[2] + t1[3]*T3[2], - t1[0]*T0[3] + t1[1]*T1[3] + t1[2]*T2[3] + t1[3]*T3[3] - ],[ - t2[0]*T0[0] + t2[1]*T1[0] + t2[2]*T2[0] + t2[3]*T3[0], - t2[0]*T0[1] + t2[1]*T1[1] + t2[2]*T2[1] + t2[3]*T3[1], - t2[0]*T0[2] + t2[1]*T1[2] + t2[2]*T2[2] + t2[3]*T3[2], - t2[0]*T0[3] + t2[1]*T1[3] + t2[2]*T2[3] + t2[3]*T3[3] - ],[ - t3[0]*T0[0] + t3[1]*T1[0] + t3[2]*T2[0] + t3[3]*T3[0], - t3[0]*T0[1] + t3[1]*T1[1] + t3[2]*T2[1] + t3[3]*T3[1], - t3[0]*T0[2] + t3[1]*T1[2] + t3[2]*T2[2] + t3[3]*T3[2], - t3[0]*T0[3] + t3[1]*T1[3] + t3[2]*T2[3] + t3[3]*T3[3] - ] ] - - for mesh in node.meshes: - for v in mesh.vertices: - v = transform(v, transformation) - bb_min[0] = min(bb_min[0], v[0]) - bb_min[1] = min(bb_min[1], v[1]) - bb_min[2] = min(bb_min[2], v[2]) - bb_max[0] = max(bb_max[0], v[0]) - bb_max[1] = max(bb_max[1], v[1]) - bb_max[2] = max(bb_max[2], v[2]) - - - for child in node.children: - bb_min, bb_max = get_bounding_box_for_node(child, bb_min, bb_max, transformation) - - return bb_min, bb_max - -def try_load_functions(library_path, dll): - ''' - Try to bind to aiImportFile and aiReleaseImport - - Arguments - --------- - library_path: path to current lib - dll: ctypes handle to library - - Returns - --------- - If unsuccessful: - None - If successful: - Tuple containing (library_path, - load from filename function, - load from memory function, - export to filename function, - export to blob function, - release function, - ctypes handle to assimp library) - ''' - - try: - load = dll.aiImportFile - release = dll.aiReleaseImport - load_mem = dll.aiImportFileFromMemory - export = dll.aiExportScene - export2blob = dll.aiExportSceneToBlob - except AttributeError: - #OK, this is a library, but it doesn't have the functions we need - return None - - # library found! - from .structs import Scene, ExportDataBlob - load.restype = POINTER(Scene) - load_mem.restype = POINTER(Scene) - export2blob.restype = POINTER(ExportDataBlob) - return (library_path, load, load_mem, export, export2blob, release, dll) - -def search_library(): - ''' - Loads the assimp library. - Throws exception AssimpError if no library_path is found - - Returns: tuple, (load from filename function, - load from memory function, - export to filename function, - export to blob function, - release function, - dll) - ''' - #this path - folder = os.path.dirname(__file__) - - # silence 'DLL not found' message boxes on win - try: - ctypes.windll.kernel32.SetErrorMode(0x8007) - except AttributeError: - pass - - candidates = [] - # test every file - for curfolder in [folder]+additional_dirs: - if os.path.isdir(curfolder): - for filename in os.listdir(curfolder): - # our minimum requirement for candidates is that - # they should contain 'assimp' somewhere in - # their name - if filename.lower().find('assimp')==-1 : - continue - is_out=1 - for et in ext_whitelist: - if et in filename.lower(): - is_out=0 - break - if is_out: - continue - - library_path = os.path.join(curfolder, filename) - logger.debug('Try ' + library_path) - try: - dll = ctypes.cdll.LoadLibrary(library_path) - except Exception as e: - logger.warning(str(e)) - # OK, this except is evil. But different OSs will throw different - # errors. So just ignore any errors. - continue - # see if the functions we need are in the dll - loaded = try_load_functions(library_path, dll) - if loaded: candidates.append(loaded) - - if not candidates: - # no library found - raise AssimpError("assimp library not found") - else: - # get the newest library_path - candidates = map(lambda x: (os.lstat(x[0])[-2], x), candidates) - res = max(candidates, key=operator.itemgetter(0))[1] - logger.debug('Using assimp library located at ' + res[0]) - - # XXX: if there are 1000 dll/so files containing 'assimp' - # in their name, do we have all of them in our address - # space now until gc kicks in? - - # XXX: take version postfix of the .so on linux? - return res[1:] - -def hasattr_silent(object, name): - """ - Calls hasttr() with the given parameters and preserves the legacy (pre-Python 3.2) - functionality of silently catching exceptions. - - Returns the result of hasatter() or False if an exception was raised. - """ - - try: - return hasattr(object, name) - except: - return False +#-*- coding: UTF-8 -*- + +""" +Some fancy helper functions. +""" + +import os +import ctypes +import operator + +from distutils.sysconfig import get_python_lib +import re +import sys + +try: import numpy +except ImportError: numpy = None + +import logging;logger = logging.getLogger("pyassimp") + +from .errors import AssimpError + +additional_dirs, ext_whitelist = [],[] + +# populate search directories and lists of allowed file extensions +# depending on the platform we're running on. +if os.name=='posix': + additional_dirs.append('./') + additional_dirs.append('/usr/lib/') + additional_dirs.append('/usr/lib/x86_64-linux-gnu/') + additional_dirs.append('/usr/local/lib/') + + if 'LD_LIBRARY_PATH' in os.environ: + additional_dirs.extend([item for item in os.environ['LD_LIBRARY_PATH'].split(':') if item]) + + # check if running from anaconda. + if "conda" or "continuum" in sys.version.lower(): + cur_path = get_python_lib() + pattern = re.compile('.*\/lib\/') + conda_lib = pattern.match(cur_path).group() + logger.info("Adding Anaconda lib path:"+ conda_lib) + additional_dirs.append(conda_lib) + + # note - this won't catch libassimp.so.N.n, but + # currently there's always a symlink called + # libassimp.so in /usr/local/lib. + ext_whitelist.append('.so') + # libassimp.dylib in /usr/local/lib + ext_whitelist.append('.dylib') + +elif os.name=='nt': + ext_whitelist.append('.dll') + path_dirs = os.environ['PATH'].split(';') + additional_dirs.extend(path_dirs) + +def vec2tuple(x): + """ Converts a VECTOR3D to a Tuple """ + return (x.x, x.y, x.z) + +def transform(vector3, matrix4x4): + """ Apply a transformation matrix on a 3D vector. + + :param vector3: array with 3 elements + :param matrix4x4: 4x4 matrix + """ + if numpy: + return numpy.dot(matrix4x4, numpy.append(vector3, 1.)) + else: + m0,m1,m2,m3 = matrix4x4; x,y,z = vector3 + return [ + m0[0]*x + m0[1]*y + m0[2]*z + m0[3], + m1[0]*x + m1[1]*y + m1[2]*z + m1[3], + m2[0]*x + m2[1]*y + m2[2]*z + m2[3], + m3[0]*x + m3[1]*y + m3[2]*z + m3[3] + ] + +def _inv(matrix4x4): + m0,m1,m2,m3 = matrix4x4 + + det = m0[3]*m1[2]*m2[1]*m3[0] - m0[2]*m1[3]*m2[1]*m3[0] - \ + m0[3]*m1[1]*m2[2]*m3[0] + m0[1]*m1[3]*m2[2]*m3[0] + \ + m0[2]*m1[1]*m2[3]*m3[0] - m0[1]*m1[2]*m2[3]*m3[0] - \ + m0[3]*m1[2]*m2[0]*m3[1] + m0[2]*m1[3]*m2[0]*m3[1] + \ + m0[3]*m1[0]*m2[2]*m3[1] - m0[0]*m1[3]*m2[2]*m3[1] - \ + m0[2]*m1[0]*m2[3]*m3[1] + m0[0]*m1[2]*m2[3]*m3[1] + \ + m0[3]*m1[1]*m2[0]*m3[2] - m0[1]*m1[3]*m2[0]*m3[2] - \ + m0[3]*m1[0]*m2[1]*m3[2] + m0[0]*m1[3]*m2[1]*m3[2] + \ + m0[1]*m1[0]*m2[3]*m3[2] - m0[0]*m1[1]*m2[3]*m3[2] - \ + m0[2]*m1[1]*m2[0]*m3[3] + m0[1]*m1[2]*m2[0]*m3[3] + \ + m0[2]*m1[0]*m2[1]*m3[3] - m0[0]*m1[2]*m2[1]*m3[3] - \ + m0[1]*m1[0]*m2[2]*m3[3] + m0[0]*m1[1]*m2[2]*m3[3] + + return[[( m1[2]*m2[3]*m3[1] - m1[3]*m2[2]*m3[1] + m1[3]*m2[1]*m3[2] - m1[1]*m2[3]*m3[2] - m1[2]*m2[1]*m3[3] + m1[1]*m2[2]*m3[3]) /det, + ( m0[3]*m2[2]*m3[1] - m0[2]*m2[3]*m3[1] - m0[3]*m2[1]*m3[2] + m0[1]*m2[3]*m3[2] + m0[2]*m2[1]*m3[3] - m0[1]*m2[2]*m3[3]) /det, + ( m0[2]*m1[3]*m3[1] - m0[3]*m1[2]*m3[1] + m0[3]*m1[1]*m3[2] - m0[1]*m1[3]*m3[2] - m0[2]*m1[1]*m3[3] + m0[1]*m1[2]*m3[3]) /det, + ( m0[3]*m1[2]*m2[1] - m0[2]*m1[3]*m2[1] - m0[3]*m1[1]*m2[2] + m0[1]*m1[3]*m2[2] + m0[2]*m1[1]*m2[3] - m0[1]*m1[2]*m2[3]) /det], + [( m1[3]*m2[2]*m3[0] - m1[2]*m2[3]*m3[0] - m1[3]*m2[0]*m3[2] + m1[0]*m2[3]*m3[2] + m1[2]*m2[0]*m3[3] - m1[0]*m2[2]*m3[3]) /det, + ( m0[2]*m2[3]*m3[0] - m0[3]*m2[2]*m3[0] + m0[3]*m2[0]*m3[2] - m0[0]*m2[3]*m3[2] - m0[2]*m2[0]*m3[3] + m0[0]*m2[2]*m3[3]) /det, + ( m0[3]*m1[2]*m3[0] - m0[2]*m1[3]*m3[0] - m0[3]*m1[0]*m3[2] + m0[0]*m1[3]*m3[2] + m0[2]*m1[0]*m3[3] - m0[0]*m1[2]*m3[3]) /det, + ( m0[2]*m1[3]*m2[0] - m0[3]*m1[2]*m2[0] + m0[3]*m1[0]*m2[2] - m0[0]*m1[3]*m2[2] - m0[2]*m1[0]*m2[3] + m0[0]*m1[2]*m2[3]) /det], + [( m1[1]*m2[3]*m3[0] - m1[3]*m2[1]*m3[0] + m1[3]*m2[0]*m3[1] - m1[0]*m2[3]*m3[1] - m1[1]*m2[0]*m3[3] + m1[0]*m2[1]*m3[3]) /det, + ( m0[3]*m2[1]*m3[0] - m0[1]*m2[3]*m3[0] - m0[3]*m2[0]*m3[1] + m0[0]*m2[3]*m3[1] + m0[1]*m2[0]*m3[3] - m0[0]*m2[1]*m3[3]) /det, + ( m0[1]*m1[3]*m3[0] - m0[3]*m1[1]*m3[0] + m0[3]*m1[0]*m3[1] - m0[0]*m1[3]*m3[1] - m0[1]*m1[0]*m3[3] + m0[0]*m1[1]*m3[3]) /det, + ( m0[3]*m1[1]*m2[0] - m0[1]*m1[3]*m2[0] - m0[3]*m1[0]*m2[1] + m0[0]*m1[3]*m2[1] + m0[1]*m1[0]*m2[3] - m0[0]*m1[1]*m2[3]) /det], + [( m1[2]*m2[1]*m3[0] - m1[1]*m2[2]*m3[0] - m1[2]*m2[0]*m3[1] + m1[0]*m2[2]*m3[1] + m1[1]*m2[0]*m3[2] - m1[0]*m2[1]*m3[2]) /det, + ( m0[1]*m2[2]*m3[0] - m0[2]*m2[1]*m3[0] + m0[2]*m2[0]*m3[1] - m0[0]*m2[2]*m3[1] - m0[1]*m2[0]*m3[2] + m0[0]*m2[1]*m3[2]) /det, + ( m0[2]*m1[1]*m3[0] - m0[1]*m1[2]*m3[0] - m0[2]*m1[0]*m3[1] + m0[0]*m1[2]*m3[1] + m0[1]*m1[0]*m3[2] - m0[0]*m1[1]*m3[2]) /det, + ( m0[1]*m1[2]*m2[0] - m0[2]*m1[1]*m2[0] + m0[2]*m1[0]*m2[1] - m0[0]*m1[2]*m2[1] - m0[1]*m1[0]*m2[2] + m0[0]*m1[1]*m2[2]) /det]] + +def get_bounding_box(scene): + bb_min = [1e10, 1e10, 1e10] # x,y,z + bb_max = [-1e10, -1e10, -1e10] # x,y,z + inv = numpy.linalg.inv if numpy else _inv + return get_bounding_box_for_node(scene.rootnode, bb_min, bb_max, inv(scene.rootnode.transformation)) + +def get_bounding_box_for_node(node, bb_min, bb_max, transformation): + + if numpy: + transformation = numpy.dot(transformation, node.transformation) + else: + t0,t1,t2,t3 = transformation + T0,T1,T2,T3 = node.transformation + transformation = [ [ + t0[0]*T0[0] + t0[1]*T1[0] + t0[2]*T2[0] + t0[3]*T3[0], + t0[0]*T0[1] + t0[1]*T1[1] + t0[2]*T2[1] + t0[3]*T3[1], + t0[0]*T0[2] + t0[1]*T1[2] + t0[2]*T2[2] + t0[3]*T3[2], + t0[0]*T0[3] + t0[1]*T1[3] + t0[2]*T2[3] + t0[3]*T3[3] + ],[ + t1[0]*T0[0] + t1[1]*T1[0] + t1[2]*T2[0] + t1[3]*T3[0], + t1[0]*T0[1] + t1[1]*T1[1] + t1[2]*T2[1] + t1[3]*T3[1], + t1[0]*T0[2] + t1[1]*T1[2] + t1[2]*T2[2] + t1[3]*T3[2], + t1[0]*T0[3] + t1[1]*T1[3] + t1[2]*T2[3] + t1[3]*T3[3] + ],[ + t2[0]*T0[0] + t2[1]*T1[0] + t2[2]*T2[0] + t2[3]*T3[0], + t2[0]*T0[1] + t2[1]*T1[1] + t2[2]*T2[1] + t2[3]*T3[1], + t2[0]*T0[2] + t2[1]*T1[2] + t2[2]*T2[2] + t2[3]*T3[2], + t2[0]*T0[3] + t2[1]*T1[3] + t2[2]*T2[3] + t2[3]*T3[3] + ],[ + t3[0]*T0[0] + t3[1]*T1[0] + t3[2]*T2[0] + t3[3]*T3[0], + t3[0]*T0[1] + t3[1]*T1[1] + t3[2]*T2[1] + t3[3]*T3[1], + t3[0]*T0[2] + t3[1]*T1[2] + t3[2]*T2[2] + t3[3]*T3[2], + t3[0]*T0[3] + t3[1]*T1[3] + t3[2]*T2[3] + t3[3]*T3[3] + ] ] + + for mesh in node.meshes: + for v in mesh.vertices: + v = transform(v, transformation) + bb_min[0] = min(bb_min[0], v[0]) + bb_min[1] = min(bb_min[1], v[1]) + bb_min[2] = min(bb_min[2], v[2]) + bb_max[0] = max(bb_max[0], v[0]) + bb_max[1] = max(bb_max[1], v[1]) + bb_max[2] = max(bb_max[2], v[2]) + + + for child in node.children: + bb_min, bb_max = get_bounding_box_for_node(child, bb_min, bb_max, transformation) + + return bb_min, bb_max + +def try_load_functions(library_path, dll): + ''' + Try to bind to aiImportFile and aiReleaseImport + + Arguments + --------- + library_path: path to current lib + dll: ctypes handle to library + + Returns + --------- + If unsuccessful: + None + If successful: + Tuple containing (library_path, + load from filename function, + load from memory function, + export to filename function, + export to blob function, + release function, + ctypes handle to assimp library) + ''' + + try: + load = dll.aiImportFile + release = dll.aiReleaseImport + load_mem = dll.aiImportFileFromMemory + export = dll.aiExportScene + export2blob = dll.aiExportSceneToBlob + except AttributeError: + #OK, this is a library, but it doesn't have the functions we need + return None + + # library found! + from .structs import Scene, ExportDataBlob + load.restype = ctype.POINTER(Scene) + load_mem.restype = ctype.POINTER(Scene) + export2blob.restype = ctype.POINTER(ExportDataBlob) + return (library_path, load, load_mem, export, export2blob, release, dll) + +def search_library(): + ''' + Loads the assimp library. + Throws exception AssimpError if no library_path is found + + Returns: tuple, (load from filename function, + load from memory function, + export to filename function, + export to blob function, + release function, + dll) + ''' + #this path + folder = os.path.dirname(__file__) + + # silence 'DLL not found' message boxes on win + try: + ctypes.windll.kernel32.SetErrorMode(0x8007) + except AttributeError: + pass + + candidates = [] + # test every file + for curfolder in [folder]+additional_dirs: + if os.path.isdir(curfolder): + for filename in os.listdir(curfolder): + # our minimum requirement for candidates is that + # they should contain 'assimp' somewhere in + # their name + if filename.lower().find('assimp')==-1 : + continue + is_out=1 + for et in ext_whitelist: + if et in filename.lower(): + is_out=0 + break + if is_out: + continue + + library_path = os.path.join(curfolder, filename) + logger.debug('Try ' + library_path) + try: + dll = ctypes.cdll.LoadLibrary(library_path) + except Exception as e: + logger.warning(str(e)) + # OK, this except is evil. But different OSs will throw different + # errors. So just ignore any errors. + continue + # see if the functions we need are in the dll + loaded = try_load_functions(library_path, dll) + if loaded: candidates.append(loaded) + + if not candidates: + # no library found + raise AssimpError("assimp library not found") + else: + # get the newest library_path + candidates = map(lambda x: (os.lstat(x[0])[-2], x), candidates) + res = max(candidates, key=operator.itemgetter(0))[1] + logger.debug('Using assimp library located at ' + res[0]) + + # XXX: if there are 1000 dll/so files containing 'assimp' + # in their name, do we have all of them in our address + # space now until gc kicks in? + + # XXX: take version postfix of the .so on linux? + return res[1:] + +def hasattr_silent(object, name): + """ + Calls hasttr() with the given parameters and preserves the legacy (pre-Python 3.2) + functionality of silently catching exceptions. + + Returns the result of hasatter() or False if an exception was raised. + """ + + try: + return hasattr(object, name) + except AttributeError: + return False diff --git a/port/PyAssimp/pyassimp/postprocess.py b/port/PyAssimp/pyassimp/postprocess.py index 932c7c660..0c55d6798 100644 --- a/port/PyAssimp/pyassimp/postprocess.py +++ b/port/PyAssimp/pyassimp/postprocess.py @@ -435,6 +435,7 @@ aiProcess_Debone = 0x4000000 aiProcess_GenEntityMeshes = 0x100000 aiProcess_OptimizeAnimations = 0x200000 aiProcess_FixTexturePaths = 0x200000 +aiProcess_EmbedTextures = 0x10000000, ## @def aiProcess_ConvertToLeftHanded # @brief Shortcut flag for Direct3D-based applications. diff --git a/port/PyAssimp/pyassimp/structs.py b/port/PyAssimp/pyassimp/structs.py index ddfd87f8a..d478b861b 100644 --- a/port/PyAssimp/pyassimp/structs.py +++ b/port/PyAssimp/pyassimp/structs.py @@ -1,6 +1,6 @@ #-*- coding: UTF-8 -*- -from ctypes import POINTER, c_void_p, c_int, c_uint, c_char, c_float, Structure, c_char_p, c_double, c_ubyte, c_size_t, c_uint32 +from ctypes import POINTER, c_void_p, c_uint, c_char, c_float, Structure, c_char_p, c_double, c_ubyte, c_size_t, c_uint32 class Vector2D(Structure): @@ -70,7 +70,7 @@ class String(Structure): See 'types.h' for details. """ - MAXLEN = 1024 + MAXLEN = 1024 _fields_ = [ # Binary length of the string excluding the terminal 0. This is NOT the diff --git a/port/PyAssimp/scripts/fixed_pipeline_3d_viewer.py b/port/PyAssimp/scripts/fixed_pipeline_3d_viewer.py index 7f98b9004..c2f6cebb7 100755 --- a/port/PyAssimp/scripts/fixed_pipeline_3d_viewer.py +++ b/port/PyAssimp/scripts/fixed_pipeline_3d_viewer.py @@ -24,12 +24,13 @@ This sample is based on several sources, including: - ASSIMP's C++ SimpleOpenGL viewer """ -import os, sys +import sys from OpenGL.GLUT import * from OpenGL.GLU import * from OpenGL.GL import * -import logging;logger = logging.getLogger("pyassimp_opengl") +import logging +logger = logging.getLogger("pyassimp_opengl") logging.basicConfig(level=logging.INFO) import math diff --git a/port/PyAssimp/scripts/sample.py b/port/PyAssimp/scripts/sample.py index 9012eb374..3cd4b3ec4 100755 --- a/port/PyAssimp/scripts/sample.py +++ b/port/PyAssimp/scripts/sample.py @@ -5,7 +5,7 @@ This module demonstrates the functionality of PyAssimp. """ -import os, sys +import sys import logging logging.basicConfig(level=logging.INFO) @@ -50,8 +50,8 @@ def main(filename=None): print(" colors:" + str(len(mesh.colors))) tcs = mesh.texturecoords if tcs.any(): - for index, tc in enumerate(tcs): - print(" texture-coords "+ str(index) + ":" + str(len(tcs[index])) + "first3:" + str(tcs[index][:3])) + for tc_index, tc in enumerate(tcs): + print(" texture-coords "+ str(tc_index) + ":" + str(len(tcs[tc_index])) + "first3:" + str(tcs[tc_index][:3])) else: print(" no texture coordinates") diff --git a/scripts/BlenderImporter/genblenddna.py b/scripts/BlenderImporter/genblenddna.py index caa58ded7..cca595eca 100644 --- a/scripts/BlenderImporter/genblenddna.py +++ b/scripts/BlenderImporter/genblenddna.py @@ -291,7 +291,9 @@ def main(): #s += "#endif\n" output.write(templt.replace("",s)) - + + # we got here, so no error + return 0 if __name__ == "__main__": sys.exit(main()) diff --git a/scripts/StepImporter/CppGenerator.py b/scripts/StepImporter/CppGenerator.py index 13ee7642c..b9dba9902 100644 --- a/scripts/StepImporter/CppGenerator.py +++ b/scripts/StepImporter/CppGenerator.py @@ -151,11 +151,8 @@ def handle_unset_args(field,entity,schema,argnum): return n+template_allow_optional.format() def get_single_conversion(field,schema,argnum=0,classname='?'): - typen = field.type name = field.name - if field.collection: - typen = 'LIST' - return template_convert_single.format(type=typen,name=name,argnum=argnum,classname=classname,full_type=field.fullspec) + return template_convert_single.format(name=name,argnum=argnum,classname=classname,full_type=field.fullspec) def count_args_up(entity,schema): return len(entity.members) + (count_args_up(schema.entities[entity.parent],schema) if entity.parent else 0) @@ -218,7 +215,7 @@ def get_derived(e,schema): return res def get_hierarchy(e,schema): - return get_derived(e.schema)+[e.name]+get_base_classes(e,schema) + return get_derived(e, schema)+[e.name]+get_base_classes(e,schema) def sort_entity_list(schema): deps = [] @@ -300,5 +297,8 @@ def work(filename): with open(output_file_cpp,'wt') as outp: outp.write(inp.read().replace('{schema-static-table}',schema_table).replace('{converter-impl}',converters)) + # Finished without error, so return 0 + return 0 + if __name__ == "__main__": sys.exit(work(sys.argv[1] if len(sys.argv)>1 else 'schema.exp')) diff --git a/scripts/StepImporter/ExpressReader.py b/scripts/StepImporter/ExpressReader.py index 84aaadbdc..c2a39e70b 100644 --- a/scripts/StepImporter/ExpressReader.py +++ b/scripts/StepImporter/ExpressReader.py @@ -43,7 +43,8 @@ """Parse an EXPRESS file and extract basic information on all entities and data types contained""" -import sys, os, re +import sys +import re from collections import OrderedDict re_match_entity = re.compile(r""" diff --git a/tools/assimp_cmd/ImageExtractor.cpp b/tools/assimp_cmd/ImageExtractor.cpp index f9d55b8e8..a587ab434 100644 --- a/tools/assimp_cmd/ImageExtractor.cpp +++ b/tools/assimp_cmd/ImageExtractor.cpp @@ -228,7 +228,8 @@ int DoExport(const aiTexture* tx, FILE* p, const std::string& extension, // Implementation of the assimp extract utility int Assimp_Extract (const char* const* params, unsigned int num) { - const char* const invalid = "assimp extract: Invalid number of arguments. See \'assimp extract --help\'\n"; + const char* const invalid = "assimp extract: Invalid number of arguments. See \'assimp extract --help\'\n"; + // assimp extract in out [options] if (num < 1) { printf(invalid); return 1; @@ -240,11 +241,7 @@ int Assimp_Extract (const char* const* params, unsigned int num) return 0; } - // asssimp extract in out [options] - if (num < 1) { - printf(invalid); - return 1; - } + std::string in = std::string(params[0]); std::string out = (num > 1 ? std::string(params[1]) : "-");