Bladeren bron

Merge pull request #2 from assimp/master

Update to Assimp Master 0973f50
RichardTea 6 jaren geleden
bovenliggende
commit
2a3626577f
76 gewijzigde bestanden met toevoegingen van 4002 en 2255 verwijderingen
  1. 1 1
      .travis.yml
  2. 12 2
      CMakeLists.txt
  3. 7 4
      INSTALL
  4. 1 0
      Readme.md
  5. 4 4
      appveyor.yml
  6. 1 1
      code/AssbinLoader.cpp
  7. 0 2
      code/AssxmlExporter.cpp
  8. 1 1
      code/BlenderDNA.h
  9. 63 92
      code/C4DImporter.cpp
  10. 6 12
      code/C4DImporter.h
  11. 113 31
      code/CMakeLists.txt
  12. 5 5
      code/COBLoader.cpp
  13. 4 1
      code/D3MFOpcPackage.cpp
  14. 44 23
      code/FBXConverter.cpp
  15. 12 8
      code/FBXConverter.h
  16. 2 2
      code/FBXExportNode.cpp
  17. 18 1
      code/FBXMaterial.cpp
  18. 10 10
      code/FindInstancesProcess.cpp
  19. 12 13
      code/IRRLoader.cpp
  20. 3 3
      code/Importer/STEPParser/STEPFileReader.cpp
  21. 3 1
      code/ImproveCacheLocality.cpp
  22. 1 4
      code/LWOMaterial.cpp
  23. 5 5
      code/MD3FileData.h
  24. 27 25
      code/MD3Loader.cpp
  25. 9 9
      code/MD5Loader.cpp
  26. 3 0
      code/ObjFileData.h
  27. 1 1
      code/ObjFileImporter.cpp
  28. 4 2
      code/ObjFileParser.cpp
  29. 1 1
      code/ObjFileParser.h
  30. 3 3
      code/PostStepRegistry.cpp
  31. 2 2
      code/SMDLoader.cpp
  32. 1 1
      code/STLExporter.cpp
  33. 19 18
      code/SortByPTypeProcess.cpp
  34. 20 20
      code/StandardShapes.cpp
  35. 46 32
      code/ValidateDataStructure.cpp
  36. 4 3
      code/glTF2Asset.h
  37. 4 4
      code/glTFAssetWriter.inl
  38. 1 1
      code/glTFExporter.cpp
  39. 4 1
      contrib/irrXML/CMakeLists.txt
  40. 9 4
      contrib/irrXML/CXMLReaderImpl.h
  41. 2 2
      contrib/irrXML/irrXML.cpp
  42. 15 9
      contrib/irrXML/irrXML.h
  43. 18 0
      contrib/zip/.gitignore
  44. 18 0
      contrib/zip/.travis.sh
  45. 15 3
      contrib/zip/.travis.yml
  46. 36 7
      contrib/zip/CMakeLists.txt
  47. 249 79
      contrib/zip/README.md
  48. 1 1
      contrib/zip/appveyor.yml
  49. 55 0
      contrib/zip/cmake/asan-wrapper
  50. 23 0
      contrib/zip/cmake/cmake_uninstall.cmake.in
  51. 517 317
      contrib/zip/src/miniz.h
  52. 782 496
      contrib/zip/src/zip.c
  53. 135 12
      contrib/zip/src/zip.h
  54. 12 0
      contrib/zip/test/CMakeLists.txt
  55. 415 61
      contrib/zip/test/test.c
  56. 104 0
      contrib/zip/test/test_miniz.c
  57. 12 0
      doc/dox.h
  58. 10 6
      include/assimp/material.h
  59. 545 547
      port/PyAssimp/pyassimp/core.py
  60. 1 1
      port/PyAssimp/pyassimp/formats.py
  61. 279 280
      port/PyAssimp/pyassimp/helper.py
  62. 1 0
      port/PyAssimp/pyassimp/postprocess.py
  63. 2 2
      port/PyAssimp/pyassimp/structs.py
  64. 3 2
      port/PyAssimp/scripts/fixed_pipeline_3d_viewer.py
  65. 3 3
      port/PyAssimp/scripts/sample.py
  66. 3 1
      scripts/BlenderImporter/genblenddna.py
  67. 9 9
      scripts/StepImporter/CppGenerator.py
  68. 2 1
      scripts/StepImporter/ExpressReader.py
  69. 2 2
      scripts/StepImporter/StepReaderGen.cpp.template
  70. 18 19
      scripts/StepImporter/StepReaderGen.h.template
  71. 2 2
      test/unit/utSTLImportExport.cpp
  72. 199 0
      test/unit/utValidateDataStructure.cpp
  73. 3 6
      tools/assimp_cmd/ImageExtractor.cpp
  74. 18 26
      tools/assimp_cmd/Info.cpp
  75. 6 7
      tools/assimp_cmd/WriteDumb.cpp
  76. 1 1
      tools/assimp_view/SceneAnimator.cpp

+ 1 - 1
.travis.yml

@@ -70,6 +70,6 @@ addons:
     project:
       name: "assimp/assimp"
     notification_email: [email protected]
-    build_command_prepend: "cmake . -DASSIMP_ENABLE_BOOST_WORKAROUND=YES"
+    build_command_prepend: "cmake ./"
     build_command: "make -j4"
     branch_pattern: coverity_scan

+ 12 - 2
CMakeLists.txt

@@ -67,7 +67,7 @@ OPTION( ASSIMP_NO_EXPORT
 )
 OPTION( ASSIMP_BUILD_ZLIB
   "Build your own zlib"
-  OFF
+  OFF  
 )
 OPTION( ASSIMP_BUILD_ASSIMP_TOOLS
   "If the supplementary tools for Assimp are built in addition to the library."
@@ -159,7 +159,7 @@ SET (PROJECT_VERSION "${ASSIMP_VERSION}")
 
 SET( ASSIMP_PACKAGE_VERSION "0" CACHE STRING "the package-specific version used for uploading the sources" )
 
-# Enable C++1 globally
+# Enable C++11 support globally
 set_property( GLOBAL PROPERTY CXX_STANDARD 11 )
 
 IF(NOT IGNORE_GIT_HASH)
@@ -353,6 +353,15 @@ IF( NOT ZLIB_FOUND )
   INCLUDE(CheckIncludeFile)
   INCLUDE(CheckTypeSize)
   INCLUDE(CheckFunctionExists)
+
+  # Explicitly turn off ASM686 and AMD64 cmake options.
+  # The AMD64 option causes a build failure on MSVC and the ASM builds seem to have problems:
+  #		https://github.com/madler/zlib/issues/41#issuecomment-125848075
+  # Also prevents these options from "polluting" the cmake options if assimp is being
+  # included as a submodule.
+  set( ASM686 FALSE CACHE INTERNAL "Override ZLIB flag to turn off assembly" FORCE )
+  set( AMD64 FALSE CACHE INTERNAL "Override ZLIB flag to turn off assembly" FORCE )
+
   # compile from sources
   ADD_SUBDIRECTORY(contrib/zlib)
   SET(ZLIB_FOUND 1)
@@ -470,6 +479,7 @@ ENDIF ( ASSIMP_BUILD_ASSIMP_TOOLS )
 IF ( ASSIMP_BUILD_SAMPLES)
   IF ( WIN32 )
     ADD_SUBDIRECTORY( samples/SimpleTexturedOpenGL/ )
+    ADD_SUBDIRECTORY( samples/SimpleTexturedDirectx11 )
   ENDIF ( WIN32 )
   ADD_SUBDIRECTORY( samples/SimpleOpenGL/ )
 ENDIF ( ASSIMP_BUILD_SAMPLES )

+ 7 - 4
INSTALL

@@ -35,13 +35,16 @@ http://www.cmake.org/.
 
 For Unix:
 
-1. cmake CMakeLists.txt -G 'Unix Makefiles'
-2. make
+1. mkdir build && cd build
+2. cmake .. -G 'Unix Makefiles'
+3. make -j4
 
 For Windows:
 1. Open a command prompt
-2. cmake CMakeLists.txt
-2. Open your default IDE and build it
+2. mkdir build
+3. cd build
+4. cmake ..
+5. cmake --build .
 
 For iOS:
 Just check the following project, which deploys a compiler toolchain for different iOS-versions: https://github.com/assimp/assimp/tree/master/port/iOS

+ 1 - 0
Readme.md

@@ -128,6 +128,7 @@ Take a look into the https://github.com/assimp/assimp/blob/master/Build.md file.
 * [Javascript (Alpha)](https://github.com/makc/assimp2json)
 * [Unity 3d Plugin](https://www.assetstore.unity3d.com/en/#!/content/91777)
 * [JVM](https://github.com/kotlin-graphics/assimp) Full jvm port (current [status](https://github.com/kotlin-graphics/assimp/wiki/Status))
+* [HAXE-Port](https://github.com/longde123/assimp-haxe) The Assimp-HAXE-port.
 
 ### Other tools ###
 [open3mod](https://github.com/acgessler/open3mod) is a powerful 3D model viewer based on Assimp's import and export abilities.

+ 4 - 4
appveyor.yml

@@ -15,8 +15,8 @@ matrix:
     
 image:
   - Visual Studio 2013
-#  - Visual Studio 2015
-#  - Visual Studio 2017
+  - Previous Visual Studio 2015
+  - Previous Visual Studio 2017
     
 platform:
   - Win32
@@ -28,8 +28,8 @@ install:
   - set PATH=C:\Ruby24-x64\bin;%PATH%
   - set CMAKE_DEFINES -DASSIMP_WERROR=ON
   - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2013" set CMAKE_GENERATOR_NAME=Visual Studio 12 2013
-  - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2015" set CMAKE_GENERATOR_NAME=Visual Studio 14 2015
-  - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2017" set CMAKE_GENERATOR_NAME=Visual Studio 15 2017
+  - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Previous Visual Studio 2015" set CMAKE_GENERATOR_NAME=Visual Studio 14 2015
+  - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Previous Visual Studio 2017" set CMAKE_GENERATOR_NAME=Visual Studio 15 2017
   - if "%platform%"=="x64" set CMAKE_GENERATOR_NAME=%CMAKE_GENERATOR_NAME% Win64
   - cmake %CMAKE_DEFINES% -G "%CMAKE_GENERATOR_NAME%"
   - set PATH=%PATH%;"C:\\Program Files (x86)\\Inno Setup 5"

+ 1 - 1
code/AssbinLoader.cpp

@@ -708,7 +708,7 @@ void AssbinImporter::InternReadFile( const std::string& pFile, aiScene* pScene,
 
         unsigned char * uncompressedData = new unsigned char[ uncompressedSize ];
 
-        int res = uncompress( uncompressedData, &uncompressedSize, compressedData, len );
+        int res = uncompress( uncompressedData, &uncompressedSize, compressedData, (uLong) len );
         if(res != Z_OK)
         {
             delete [] uncompressedData;

+ 0 - 2
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</Normals>\n");
             }
 

+ 1 - 1
code/BlenderDNA.h

@@ -416,7 +416,7 @@ template <> struct Structure :: _defaultInitializer<ErrorPolicy_Fail> {
     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");
     }
 };
 

+ 63 - 92
code/C4DImporter.cpp

@@ -2,7 +2,7 @@
 Open Asset Import Library (assimp)
 ----------------------------------------------------------------------
 
-Copyright (c) 2006-2012, assimp team
+Copyright (c) 2006-2019, assimp team
 All rights reserved.
 
 Redistribution and use of this software in source and binary forms,
@@ -68,8 +68,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 using namespace melange;
 
 // overload this function and fill in your own unique data
-void GetWriterInfo(int &id, String &appname)
-{
+void GetWriterInfo(int &id, String &appname) {
     id = 2424226;
     appname = "Open Asset Import Library";
 }
@@ -78,7 +77,10 @@ using namespace Assimp;
 using namespace Assimp::Formatter;
 
 namespace Assimp {
-    template<> const std::string LogFunctions<C4DImporter>::log_prefix = "C4D: ";
+    template<> const char* LogFunctions<C4DImporter>::Prefix() {
+        static auto prefix = "C4D: ";
+        return prefix;
+    }
 }
 
 static const aiImporterDesc desc = {
@@ -97,47 +99,44 @@ static const aiImporterDesc desc = {
 
 // ------------------------------------------------------------------------------------------------
 C4DImporter::C4DImporter()
-{}
+: BaseImporter() {
+    // empty
+}
 
 // ------------------------------------------------------------------------------------------------
-C4DImporter::~C4DImporter()
-{}
+C4DImporter::~C4DImporter() {
+    // empty
+}
 
 // ------------------------------------------------------------------------------------------------
-bool C4DImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const
-{
+bool C4DImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const {
     const std::string& extension = GetExtension(pFile);
     if (extension == "c4d") {
         return true;
-    }
-
-    else if ((!extension.length() || checkSig) && pIOHandler)   {
+    } else if ((!extension.length() || checkSig) && pIOHandler)   {
         // TODO
     }
+
     return false;
 }
 
 // ------------------------------------------------------------------------------------------------
-const aiImporterDesc* C4DImporter::GetInfo () const
-{
+const aiImporterDesc* C4DImporter::GetInfo () const {
     return &desc;
 }
 
 // ------------------------------------------------------------------------------------------------
-void C4DImporter::SetupProperties(const Importer* /*pImp*/)
-{
+void C4DImporter::SetupProperties(const Importer* /*pImp*/) {
     // nothing to be done for the moment
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // Imports the given file into the given scene structure.
-void C4DImporter::InternReadFile( const std::string& pFile,
-    aiScene* pScene, IOSystem* pIOHandler)
-{
+void C4DImporter::InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler) {
     std::unique_ptr<IOStream> file( pIOHandler->Open( pFile));
 
-    if( file.get() == NULL) {
+    if( file.get() == nullptr ) {
         ThrowException("failed to open file " + pFile);
     }
 
@@ -151,7 +150,7 @@ void C4DImporter::InternReadFile( const std::string& pFile,
 
     // open document first
     BaseDocument* doc = LoadDocument(f, SCENEFILTER_OBJECTS | SCENEFILTER_MATERIALS);
-    if(doc == NULL) {
+    if(doc == nullptr ) {
         ThrowException("failed to read document " + pFile);
     }
 
@@ -160,11 +159,10 @@ void C4DImporter::InternReadFile( const std::string& pFile,
     // first convert all materials
     ReadMaterials(doc->GetFirstMaterial());
 
-    // process C4D scenegraph recursively
+    // process C4D scene-graph recursively
     try {
         RecurseHierarchy(doc->GetFirstObject(), pScene->mRootNode);
-    }
-    catch(...) {
+    } catch(...) {
         for(aiMesh* mesh : meshes) {
             delete mesh;
         }
@@ -201,8 +199,7 @@ void C4DImporter::InternReadFile( const std::string& pFile,
 
 
 // ------------------------------------------------------------------------------------------------
-bool C4DImporter::ReadShader(aiMaterial* out, melange::BaseShader* shader)
-{
+bool C4DImporter::ReadShader(aiMaterial* out, melange::BaseShader* shader) {
     // based on Melange sample code (C4DImportExport.cpp)
     while(shader) {
         if(shader->GetType() == Xlayer) {
@@ -220,15 +217,12 @@ bool C4DImporter::ReadShader(aiMaterial* out, melange::BaseShader* shader)
             // Ignore the actual layer blending - models for real-time rendering should not
             // use them in a non-trivial way. Just try to find textures that we can apply
             // to the model.
-            while (lsl)
-            {
-                if (lsl->GetType() == TypeFolder)
-                {
+            while (lsl) {
+                if (lsl->GetType() == TypeFolder) {
                     BlendFolder* const folder = dynamic_cast<BlendFolder*>(lsl);
                     LayerShaderLayer *subLsl = dynamic_cast<LayerShaderLayer*>(folder->m_Children.GetObject(0));
 
-                    while (subLsl)
-                    {
+                    while (subLsl) {
                         if (subLsl->GetType() == TypeShader) {
                             BlendShader* const shader = dynamic_cast<BlendShader*>(subLsl);
                             if(ReadShader(out, static_cast<BaseShader*>(shader->m_pLink->GetLink()))) {
@@ -238,8 +232,7 @@ bool C4DImporter::ReadShader(aiMaterial* out, melange::BaseShader* shader)
 
                         subLsl = subLsl->GetNext();
                     }
-                }
-                else if (lsl->GetType() == TypeShader) {
+                } else if (lsl->GetType() == TypeShader) {
                     BlendShader* const shader = dynamic_cast<BlendShader*>(lsl);
                     if(ReadShader(out, static_cast<BaseShader*>(shader->m_pLink->GetLink()))) {
                         return true;
@@ -248,33 +241,27 @@ bool C4DImporter::ReadShader(aiMaterial* out, melange::BaseShader* shader)
 
                 lsl = lsl->GetNext();
             }
-        }
-        else if ( shader->GetType() == Xbitmap )
-        {
+        } else if ( shader->GetType() == Xbitmap ) {
             aiString path;
             shader->GetFileName().GetString().GetCString(path.data, MAXLEN-1);
             path.length = ::strlen(path.data);
             out->AddProperty(&path, AI_MATKEY_TEXTURE_DIFFUSE(0));
             return true;
-        }
-        else {
+        } else {
             LogWarn("ignoring shader type: " + std::string(GetObjectTypeName(shader->GetType())));
         }
         shader = shader->GetNext();
     }
+
     return false;
 }
 
-
 // ------------------------------------------------------------------------------------------------
-void C4DImporter::ReadMaterials(melange::BaseMaterial* mat)
-{
+void C4DImporter::ReadMaterials(melange::BaseMaterial* mat) {
     // based on Melange sample code
-    while (mat)
-    {
+    while (mat) {
         const String& name = mat->GetName();
-        if (mat->GetType() == Mmaterial)
-        {
+        if (mat->GetType() == Mmaterial) {
             aiMaterial* out = new aiMaterial();
             material_mapping[mat] = static_cast<unsigned int>(materials.size());
             materials.push_back(out);
@@ -286,8 +273,7 @@ void C4DImporter::ReadMaterials(melange::BaseMaterial* mat)
 
             Material& m = dynamic_cast<Material&>(*mat);
 
-            if (m.GetChannelState(CHANNEL_COLOR))
-            {
+            if (m.GetChannelState(CHANNEL_COLOR)) {
                 GeData data;
                 mat->GetParameter(MATERIAL_COLOR_COLOR, data);
                 Vector color = data.GetVector();
@@ -307,9 +293,7 @@ void C4DImporter::ReadMaterials(melange::BaseMaterial* mat)
             if(shader) {
                 ReadShader(out, shader);
             }
-        }
-        else
-        {
+        } else {
             LogWarn("ignoring plugin material: " + std::string(GetObjectTypeName(mat->GetType())));
         }
         mat = mat->GetNext();
@@ -317,14 +301,12 @@ void C4DImporter::ReadMaterials(melange::BaseMaterial* mat)
 }
 
 // ------------------------------------------------------------------------------------------------
-void C4DImporter::RecurseHierarchy(BaseObject* object, aiNode* parent)
-{
-    ai_assert(parent != NULL);
+void C4DImporter::RecurseHierarchy(BaseObject* object, aiNode* parent) {
+    ai_assert(parent != nullptr );
     std::vector<aiNode*> nodes;
 
     // based on Melange sample code
-    while (object)
-    {
+    while (object) {
         const String& name = object->GetName();
         const LONG type = object->GetType();
         const Matrix& ml = object->GetMl();
@@ -356,26 +338,20 @@ void C4DImporter::RecurseHierarchy(BaseObject* object, aiNode* parent)
         nodes.push_back(nd);
 
         GeData data;
-        if (type == Ocamera)
-        {
+        if (type == Ocamera) {
             object->GetParameter(CAMERAOBJECT_FOV, data);
             // TODO: read camera
-        }
-        else if (type == Olight)
-        {
+        } else if (type == Olight) {
             // TODO: read light
-        }
-        else if (type == Opolygon)
-        {
+        } else if (type == Opolygon) {
             aiMesh* const mesh = ReadMesh(object);
-            if(mesh != NULL) {
+            if(mesh != nullptr) {
                 nd->mNumMeshes = 1;
                 nd->mMeshes = new unsigned int[1];
                 nd->mMeshes[0] = static_cast<unsigned int>(meshes.size());
                 meshes.push_back(mesh);
             }
-        }
-        else {
+        } else {
             LogWarn("ignoring object: " + std::string(GetObjectTypeName(type)));
         }
 
@@ -389,28 +365,27 @@ void C4DImporter::RecurseHierarchy(BaseObject* object, aiNode* parent)
     std::copy(nodes.begin(), nodes.end(), parent->mChildren);
 }
 
-
 // ------------------------------------------------------------------------------------------------
-aiMesh* C4DImporter::ReadMesh(BaseObject* object)
-{
-    ai_assert(object != NULL && object->GetType() == Opolygon);
+aiMesh* C4DImporter::ReadMesh(BaseObject* object) {
+    ai_assert(object != nullptr);
+    ai_assert( object->GetType() == Opolygon );
 
     // based on Melange sample code
     PolygonObject* const polyObject = dynamic_cast<PolygonObject*>(object);
-    ai_assert(polyObject != NULL);
+    ai_assert(polyObject != nullptr);
 
     const LONG pointCount = polyObject->GetPointCount();
     const LONG polyCount = polyObject->GetPolygonCount();
     if(!polyObject || !pointCount) {
         LogWarn("ignoring mesh with zero vertices or faces");
-        return NULL;
+        return nullptr;
     }
 
     const Vector* points = polyObject->GetPointR();
-    ai_assert(points != NULL);
+    ai_assert(points != nullptr);
 
     const CPolygon* polys = polyObject->GetPolygonR();
-    ai_assert(polys != NULL);
+    ai_assert(polys != nullptr);
 
     std::unique_ptr<aiMesh> mesh(new aiMesh());
     mesh->mNumFaces = static_cast<unsigned int>(polyCount);
@@ -443,14 +418,14 @@ aiMesh* C4DImporter::ReadMesh(BaseObject* object)
 
     // check if there are normals, tangents or UVW coordinates
     BaseTag* tag = object->GetTag(Tnormal);
-    NormalTag* normals_src = NULL;
+    NormalTag* normals_src = nullptr;
     if(tag) {
         normals_src = dynamic_cast<NormalTag*>(tag);
         normals = mesh->mNormals = new aiVector3D[mesh->mNumVertices]();
     }
 
     tag = object->GetTag(Ttangent);
-    TangentTag* tangents_src = NULL;
+    TangentTag* tangents_src = nullptr;
     if(tag) {
         tangents_src = dynamic_cast<TangentTag*>(tag);
         tangents = mesh->mTangents = new aiVector3D[mesh->mNumVertices]();
@@ -458,15 +433,14 @@ aiMesh* C4DImporter::ReadMesh(BaseObject* object)
     }
 
     tag = object->GetTag(Tuvw);
-    UVWTag* uvs_src = NULL;
+    UVWTag* uvs_src = nullptr;
     if(tag) {
         uvs_src = dynamic_cast<UVWTag*>(tag);
         uvs = mesh->mTextureCoords[0] = new aiVector3D[mesh->mNumVertices]();
     }
 
     // copy vertices and extra channels over and populate faces
-    for (LONG i = 0; i < polyCount; ++i, ++face)
-    {
+    for (LONG i = 0; i < polyCount; ++i, ++face) {
         ai_assert(polys[i].a < pointCount && polys[i].a >= 0);
         const Vector& pointA = points[polys[i].a];
         verts->x = pointA.x;
@@ -489,8 +463,7 @@ aiMesh* C4DImporter::ReadMesh(BaseObject* object)
         ++verts;
 
         // TODO: do we also need to handle lines or points with similar checks?
-        if (polys[i].c != polys[i].d)
-        {
+        if (polys[i].c != polys[i].d) {
             ai_assert(polys[i].d < pointCount && polys[i].d >= 0);
 
             face->mNumIndices = 4;
@@ -500,8 +473,7 @@ aiMesh* C4DImporter::ReadMesh(BaseObject* object)
             verts->y = pointD.y;
             verts->z = pointD.z;
             ++verts;
-        }
-        else {
+        } else {
             face->mNumIndices = 3;
         }
         face->mIndices = new unsigned int[face->mNumIndices];
@@ -513,8 +485,7 @@ aiMesh* C4DImporter::ReadMesh(BaseObject* object)
         if (normals_src) {
             if(i >= normals_src->GetDataCount()) {
                 LogError("unexpected number of normals, ignoring");
-            }
-            else {
+            } else {
                 ConstNormalHandle normal_handle = normals_src->GetDataAddressR();
                 NormalStruct nor;
                 NormalTag::Get(normal_handle, i, nor);
@@ -616,26 +587,25 @@ aiMesh* C4DImporter::ReadMesh(BaseObject* object)
     }
 
     mesh->mMaterialIndex = ResolveMaterial(polyObject);
+
     return mesh.release();
 }
 
-
 // ------------------------------------------------------------------------------------------------
-unsigned int C4DImporter::ResolveMaterial(PolygonObject* obj)
-{
-    ai_assert(obj != NULL);
+unsigned int C4DImporter::ResolveMaterial(PolygonObject* obj) {
+    ai_assert(obj != nullptr);
 
     const unsigned int mat_count = static_cast<unsigned int>(materials.size());
 
     BaseTag* tag = obj->GetTag(Ttexture);
-    if(tag == NULL) {
+    if(tag == nullptr) {
         return mat_count;
     }
 
     TextureTag& ttag = dynamic_cast<TextureTag&>(*tag);
 
     BaseMaterial* const mat = ttag.GetMaterial();
-    ai_assert(mat != NULL);
+    ai_assert(mat != nullptr);
 
     const MaterialMap::const_iterator it = material_mapping.find(mat);
     if(it == material_mapping.end()) {
@@ -643,6 +613,7 @@ unsigned int C4DImporter::ResolveMaterial(PolygonObject* obj)
     }
 
     ai_assert((*it).second < mat_count);
+
     return (*it).second;
 }
 

+ 6 - 12
code/C4DImporter.h

@@ -2,7 +2,7 @@
 Open Asset Import Library (assimp)
 ----------------------------------------------------------------------
 
-Copyright (c) 2006-2012, assimp team
+Copyright (c) 2006-2019, assimp team
 All rights reserved.
 
 Redistribution and use of this software in source and binary forms,
@@ -48,6 +48,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <assimp/LogAux.h>
 
 #include <map>
+
+// Forward declarations
 struct aiNode;
 struct aiMesh;
 struct aiMaterial;
@@ -61,8 +63,7 @@ namespace melange {
     class BaseShader;
 }
 
-namespace Assimp    {
-
+namespace Assimp  {
     // TinyFormatter.h
     namespace Formatter {
         template <typename T,typename TR, typename A> class basic_formatter;
@@ -75,17 +76,10 @@ namespace Assimp    {
  *
  *  Note that Melange is not free software. */
 // -------------------------------------------------------------------------------------------
-class C4DImporter : public BaseImporter, public LogFunctions<C4DImporter>
-{
+class C4DImporter : public BaseImporter, public LogFunctions<C4DImporter> {
 public:
-
     C4DImporter();
     ~C4DImporter();
-
-
-public:
-
-    // --------------------
     bool CanRead( const std::string& pFile, IOSystem* pIOHandler,
         bool checkSig) const;
 
@@ -119,5 +113,5 @@ private:
 }; // !class C4DImporter
 
 } // end of namespace Assimp
-#endif // INCLUDED_AI_CINEMA_4D_LOADER_H
 
+#endif // INCLUDED_AI_CINEMA_4D_LOADER_H

+ 113 - 31
code/CMakeLists.txt

@@ -220,7 +220,7 @@ ENDIF ( ASSIMP_BUILD_NONFREE_C4D_IMPORTER )
 # ASSIMP_BUILD_XXX_IMPORTER to FALSE for each importer
 # if this variable is set to FALSE, the user can manually enable importers by setting
 # ASSIMP_BUILD_XXX_IMPORTER to TRUE for each importer
-OPTION(ASSIMP_BUILD_ALL_IMPORTERS_BY_DEFAULT "default value of all ASSIMP_BUILD_XXX_IMPORTER value" TRUE)
+OPTION(ASSIMP_BUILD_ALL_IMPORTERS_BY_DEFAULT "default value of all ASSIMP_BUILD_XXX_IMPORTER values" TRUE)
 
 # macro to add the CMake Option ADD_ASSIMP_IMPORTER_<name> which enables compile of loader
 # this way selective loaders can be compiled (reduces filesize + compile time)
@@ -236,19 +236,51 @@ MACRO(ADD_ASSIMP_IMPORTER name)
   IF (ASSIMP_IMPORTER_ENABLED)
     LIST(APPEND ASSIMP_LOADER_SRCS ${ARGN})
     SET(ASSIMP_IMPORTERS_ENABLED "${ASSIMP_IMPORTERS_ENABLED} ${name}")
-    SET(${name}_SRCS ${ARGN})
     SOURCE_GROUP(${name} FILES ${ARGN})
   ELSE()
     SET(${name}_SRC "")
     SET(ASSIMP_IMPORTERS_DISABLED "${ASSIMP_IMPORTERS_DISABLED} ${name}")
     add_definitions(-DASSIMP_BUILD_NO_${name}_IMPORTER)
+  ENDIF()
+ENDMACRO()
+
+# if this variable is set to TRUE, the user can manually disable exporters by setting
+# ASSIMP_BUILD_XXX_EXPORTER to FALSE for each exporter
+# if this variable is set to FALSE, the user can manually enable exporters by setting
+# ASSIMP_BUILD_XXX_EXPORTER to TRUE for each exporter
+OPTION(ASSIMP_BUILD_ALL_EXPORTERS_BY_DEFAULT "default value of all ASSIMP_BUILD_XXX_EXPORTER values" TRUE)
+
+# macro to add the CMake Option ADD_ASSIMP_IMPORTER_<name> which enables compile of loader
+# this way selective loaders can be compiled (reduces filesize + compile time)
+MACRO(ADD_ASSIMP_EXPORTER name)
+  IF (ASSIMP_NO_EXPORT)
+	set(ASSIMP_EXPORTER_ENABLED FALSE)
+  ELSEIF (ASSIMP_BUILD_ALL_EXPORTERS_BY_DEFAULT)
+    set(ASSIMP_EXPORTER_ENABLED TRUE)
+    IF (DEFINED ASSIMP_BUILD_${name}_EXPORTER AND NOT ASSIMP_BUILD_${name}_EXPORTER)
+      set(ASSIMP_EXPORTER_ENABLED FALSE)
+    ENDIF ()
+  ELSE ()
+    set(ASSIMP_EXPORTER_ENABLED ${ASSIMP_BUILD_${name}_EXPORTER})
+  ENDIF ()
+  
+  IF (ASSIMP_EXPORTER_ENABLED)
+    SET(ASSIMP_EXPORTERS_ENABLED "${ASSIMP_EXPORTERS_ENABLED} ${name}")
+	LIST(APPEND ASSIMP_EXPORTER_SRCS ${ARGN})
+    SOURCE_GROUP(${name}_EXPORTER FILES ${ARGN})
+  ELSE()
+    SET(ASSIMP_EXPORTERS_DISABLED "${ASSIMP_EXPORTERS_DISABLED} ${name}")
     add_definitions(-DASSIMP_BUILD_NO_${name}_EXPORTER)
   ENDIF()
 ENDMACRO()
 
 SET(ASSIMP_LOADER_SRCS "")
 SET(ASSIMP_IMPORTERS_ENABLED "") # list of enabled importers
-SET(ASSIMP_IMPORTERS_DISABLED "") # disabled list (used to print)
+SET(ASSIMP_IMPORTERS_DISABLED "") # disabled importers list (used to print)
+
+SET(ASSIMP_EXPORTER_SRCS "")
+SET(ASSIMP_EXPORTERS_ENABLED "") # list of enabled exporters
+SET(ASSIMP_EXPORTERS_DISABLED "") # disabled exporters list (used to print)
 
 ADD_ASSIMP_IMPORTER( AMF
   AMFImporter.hpp
@@ -265,6 +297,9 @@ ADD_ASSIMP_IMPORTER( 3DS
   3DSHelper.h
   3DSLoader.cpp
   3DSLoader.h
+)
+
+ADD_ASSIMP_EXPORTER( 3DS
   3DSExporter.h
   3DSExporter.cpp
 )
@@ -282,12 +317,15 @@ ADD_ASSIMP_IMPORTER( ASE
 )
 
 ADD_ASSIMP_IMPORTER( ASSBIN
-  AssbinExporter.h
-  AssbinExporter.cpp
   AssbinLoader.h
   AssbinLoader.cpp
 )
 
+ADD_ASSIMP_EXPORTER( ASSBIN
+  AssbinExporter.h
+  AssbinExporter.cpp
+)
+
 ADD_ASSIMP_IMPORTER( ASSXML
   AssxmlExporter.h
   AssxmlExporter.cpp
@@ -309,6 +347,9 @@ ADD_ASSIMP_IMPORTER( COLLADA
   ColladaLoader.h
   ColladaParser.cpp
   ColladaParser.h
+)
+
+ADD_ASSIMP_EXPORTER( COLLADA
   ColladaExporter.h
   ColladaExporter.cpp
 )
@@ -425,6 +466,9 @@ ADD_ASSIMP_IMPORTER( OBJ
   ObjFileParser.cpp
   ObjFileParser.h
   ObjTools.h
+)
+
+ADD_ASSIMP_EXPORTER( OBJ
   ObjExporter.h
   ObjExporter.cpp
 )
@@ -443,18 +487,24 @@ ADD_ASSIMP_IMPORTER( OGRE
 )
 
 ADD_ASSIMP_IMPORTER( OPENGEX
-  OpenGEXExporter.cpp
-  OpenGEXExporter.h
   OpenGEXImporter.cpp
   OpenGEXImporter.h
   OpenGEXStructs.h
 )
 
+ADD_ASSIMP_EXPORTER( OPENGEX
+  OpenGEXExporter.cpp
+  OpenGEXExporter.h
+)
+
 ADD_ASSIMP_IMPORTER( PLY
   PlyLoader.cpp
   PlyLoader.h
   PlyParser.cpp
   PlyParser.h
+)
+
+ADD_ASSIMP_EXPORTER( PLY
   PlyExporter.cpp
   PlyExporter.h
 )
@@ -545,13 +595,16 @@ ADD_ASSIMP_IMPORTER( FBX
   FBXDeformer.cpp
   FBXBinaryTokenizer.cpp
   FBXDocumentUtil.cpp
+  FBXCommon.h
+)
+
+ADD_ASSIMP_EXPORTER( FBX
   FBXExporter.h
   FBXExporter.cpp
   FBXExportNode.h
   FBXExportNode.cpp
   FBXExportProperty.h
   FBXExportProperty.cpp
-  FBXCommon.h
 )
 
 SET( PostProcessing_SRCS
@@ -651,6 +704,9 @@ ADD_ASSIMP_IMPORTER( SMD
 ADD_ASSIMP_IMPORTER( STL
   STLLoader.cpp
   STLLoader.h
+)
+
+ADD_ASSIMP_EXPORTER( STL
   STLExporter.h
   STLExporter.cpp
 )
@@ -671,13 +727,14 @@ ADD_ASSIMP_IMPORTER( X
   XFileImporter.h
   XFileParser.cpp
   XFileParser.h
+)
+
+ADD_ASSIMP_EXPORTER( X
   XFileExporter.h
   XFileExporter.cpp
 )
 
 ADD_ASSIMP_IMPORTER( X3D
-  X3DExporter.cpp
-  X3DExporter.hpp
   X3DImporter.cpp
   X3DImporter.hpp
   X3DImporter_Geometry2D.cpp
@@ -697,6 +754,11 @@ ADD_ASSIMP_IMPORTER( X3D
   X3DVocabulary.cpp
 )
 
+ADD_ASSIMP_EXPORTER( X3D
+  X3DExporter.cpp
+  X3DExporter.hpp
+)
+
 ADD_ASSIMP_IMPORTER( GLTF
   glTFAsset.h
   glTFAsset.inl
@@ -704,28 +766,34 @@ ADD_ASSIMP_IMPORTER( GLTF
   glTFAssetWriter.inl
   glTFImporter.cpp
   glTFImporter.h
-  glTFExporter.h
-  glTFExporter.cpp
   glTF2Asset.h
   glTF2Asset.inl
   glTF2AssetWriter.h
   glTF2AssetWriter.inl
   glTF2Importer.cpp
   glTF2Importer.h
+)
+
+ADD_ASSIMP_EXPORTER( GLTF
+  glTFExporter.h
+  glTFExporter.cpp
   glTF2Exporter.h
   glTF2Exporter.cpp
 )
 
 ADD_ASSIMP_IMPORTER( 3MF
-    D3MFImporter.h
+	D3MFImporter.h
     D3MFImporter.cpp
-    D3MFExporter.h
-    D3MFExporter.cpp
-    D3MFOpcPackage.h
+	D3MFOpcPackage.h
     D3MFOpcPackage.cpp
     3MFXmlTags.h
 )
 
+ADD_ASSIMP_EXPORTER( 3MF
+    D3MFExporter.h
+    D3MFExporter.cpp
+)
+
 ADD_ASSIMP_IMPORTER( MMD
   MMDCpp14.h
   MMDImporter.cpp
@@ -744,28 +812,32 @@ ADD_ASSIMP_IMPORTER( STEP
     Importer/StepFile/StepFileGen2.cpp
     Importer/StepFile/StepFileGen3.cpp
     Importer/StepFile/StepReaderGen.h
+)
+
+ADD_ASSIMP_EXPORTER( STEP
     StepExporter.h
     StepExporter.cpp
 )
 
-SET( Exporter_SRCS
-  Exporter.cpp
-  AssimpCExport.cpp
-  ${HEADER_PATH}/BlobIOSystem.h
-)
-SOURCE_GROUP( Exporter FILES ${Exporter_SRCS})
+if ((NOT ASSIMP_NO_EXPORT) OR (NOT ASSIMP_EXPORTERS_ENABLED STREQUAL ""))
+	SET( Exporter_SRCS
+	  Exporter.cpp
+	  AssimpCExport.cpp
+	  ${HEADER_PATH}/BlobIOSystem.h
+	)
+	SOURCE_GROUP( Exporter FILES ${Exporter_SRCS})
+endif()
 
 SET( Extra_SRCS
   MD4FileData.h
 )
 SOURCE_GROUP( Extra FILES ${Extra_SRCS})
 
-
 SET( Clipper_SRCS
   ../contrib/clipper/clipper.hpp
   ../contrib/clipper/clipper.cpp
 )
-SOURCE_GROUP( Clipper FILES ${Clipper_SRCS})
+SOURCE_GROUP( Contrib\\Clipper FILES ${Clipper_SRCS})
 
 SET( Poly2Tri_SRCS
   ../contrib/poly2tri/poly2tri/common/shapes.cc
@@ -780,7 +852,7 @@ SET( Poly2Tri_SRCS
   ../contrib/poly2tri/poly2tri/sweep/sweep_context.cc
   ../contrib/poly2tri/poly2tri/sweep/sweep_context.h
 )
-SOURCE_GROUP( Poly2Tri FILES ${Poly2Tri_SRCS})
+SOURCE_GROUP( Contrib\\Poly2Tri FILES ${Poly2Tri_SRCS})
 
 SET( unzip_SRCS
   ../contrib/unzip/crypt.h
@@ -789,7 +861,7 @@ SET( unzip_SRCS
   ../contrib/unzip/unzip.c
   ../contrib/unzip/unzip.h
 )
-SOURCE_GROUP( unzip FILES ${unzip_SRCS})
+SOURCE_GROUP(Contrib\\unzip FILES ${unzip_SRCS})
 
 SET( ziplib_SRCS
   ../contrib/zip/src/miniz.h
@@ -797,6 +869,13 @@ SET( ziplib_SRCS
   ../contrib/zip/src/zip.h
 )
 
+# TODO if cmake required version has been updated to >3.12.0, collapse this to the second case only
+if(${CMAKE_VERSION} VERSION_LESS "3.12.0")
+	add_definitions(-DMINIZ_USE_UNALIGNED_LOADS_AND_STORES=0)
+else()
+	add_compile_definitions(MINIZ_USE_UNALIGNED_LOADS_AND_STORES=0)
+endif()
+
 SOURCE_GROUP( ziplib FILES ${ziplib_SRCS} )
 
 SET ( openddl_parser_SRCS
@@ -814,7 +893,7 @@ SET ( openddl_parser_SRCS
   ../contrib/openddlparser/include/openddlparser/DDLNode.h
   ../contrib/openddlparser/include/openddlparser/Value.h
 )
-SOURCE_GROUP( openddl_parser FILES ${openddl_parser_SRCS})
+SOURCE_GROUP( Contrib\\openddl_parser FILES ${openddl_parser_SRCS})
 
 SET ( open3dgc_SRCS
   ../contrib/Open3DGC/o3dgcAdjacencyInfo.h
@@ -847,7 +926,7 @@ SET ( open3dgc_SRCS
   ../contrib/Open3DGC/o3dgcVector.h
   ../contrib/Open3DGC/o3dgcVector.inl
 )
-SOURCE_GROUP( open3dgc FILES ${open3dgc_SRCS})
+SOURCE_GROUP( Contrib\\open3dgc FILES ${open3dgc_SRCS})
 
 # Check dependencies for glTF importer with Open3DGC-compression.
 # RT-extensions is used in "contrib/Open3DGC/o3dgcTimer.h" for collecting statistics. Pointed file
@@ -887,8 +966,11 @@ else (UNZIP_FOUND)
   INCLUDE_DIRECTORIES( "../contrib/unzip/" )
 endif (UNZIP_FOUND)
 
-MESSAGE(STATUS "Enabled formats:${ASSIMP_IMPORTERS_ENABLED}")
-MESSAGE(STATUS "Disabled formats:${ASSIMP_IMPORTERS_DISABLED}")
+MESSAGE(STATUS "Enabled importer formats:${ASSIMP_IMPORTERS_ENABLED}")
+MESSAGE(STATUS "Disabled importer formats:${ASSIMP_IMPORTERS_DISABLED}")
+
+MESSAGE(STATUS "Enabled exporter formats:${ASSIMP_EXPORTERS_ENABLED}")
+MESSAGE(STATUS "Disabled exporter formats:${ASSIMP_EXPORTERS_DISABLED}")
 
 SET( assimp_src
   # Assimp Files
@@ -903,6 +985,7 @@ SET( assimp_src
 
   # Model Support
   ${ASSIMP_LOADER_SRCS}
+  ${ASSIMP_EXPORTER_SRCS}
 
   # Third-party libraries
   ${IrrXML_SRCS}
@@ -916,7 +999,6 @@ SET( assimp_src
 
   ${PUBLIC_HEADERS}
   ${COMPILER_HEADERS}
-
 )
 ADD_DEFINITIONS( -DOPENDDLPARSER_BUILD )
 

+ 5 - 5
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<unsigned int>(-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<Group>(new Group()));
+    out.nodes.push_back(std::make_shared<Group>());
     Group& msh = (Group&)(*out.nodes.back().get());
     msh = nfo;
 

+ 4 - 1
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);
         }
+
     }
 }
 

+ 44 - 23
code/FBXConverter.cpp

@@ -78,6 +78,16 @@ namespace Assimp {
 
         FBXConverter::FBXConverter(aiScene* out, const Document& doc)
             : defaultMaterialIndex()
+            , lights()
+            , cameras()
+            , textures()
+            , materials_converted()
+            , textures_converted()
+            , meshes_converted()
+            , node_anim_chain_bits()
+            , mNodeNameInstances()
+            , mNodeNames()
+            , anim_fps()
             , out(out)
             , doc(doc) {
             // animations need to be converted first since this will
@@ -410,19 +420,24 @@ namespace Assimp {
 
         void FBXConverter::GetUniqueName(const std::string &name, std::string &uniqueName)
         {
-            int i = 0;
             uniqueName = name;
-            while (mNodeNames.find(uniqueName) != mNodeNames.end())
+            int i = 0;
+            auto it = mNodeNameInstances.find(name); // duplicate node name instance count
+            if (it != mNodeNameInstances.end())
             {
-                ++i;
-                std::stringstream ext;
-                ext << name << std::setfill('0') << std::setw(3) << i;
-                uniqueName = ext.str();
+                i = it->second;
+                while (mNodeNames.find(uniqueName) != mNodeNames.end())
+                {
+                    i++;
+                    std::stringstream ext;
+                    ext << name << std::setfill('0') << std::setw(3) << i;
+                    uniqueName = ext.str();
+                }
             }
+            mNodeNameInstances[name] = i;
             mNodeNames.insert(uniqueName);
         }
 
-
         const char* FBXConverter::NameTransformationComp(TransformationComp comp) {
             switch (comp) {
             case TransformationComp_Translation:
@@ -964,7 +979,7 @@ namespace Assimp {
             {
                 if (indices[i] < 0) epcount++;
             }
-            unsigned int pcount = indices.size();
+            unsigned int pcount = static_cast<unsigned int>( indices.size() );
             unsigned int scount = out_mesh->mNumFaces = pcount - epcount;
 
             aiFace* fac = out_mesh->mFaces = new aiFace[scount]();
@@ -1712,22 +1727,22 @@ namespace Assimp {
                         if (!mesh)
                         {
                             for (const MeshMap::value_type& v : meshes_converted) {
-                                const MeshGeometry* const mesh = dynamic_cast<const MeshGeometry*> (v.first);
-                                if (!mesh) {
+                                const MeshGeometry* const meshGeom = dynamic_cast<const MeshGeometry*> (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<int>(i);
                                         break;
@@ -1835,22 +1850,22 @@ namespace Assimp {
                         if (!mesh)
                         {
                             for (const MeshMap::value_type& v : meshes_converted) {
-                                const MeshGeometry* const mesh = dynamic_cast<const MeshGeometry*> (v.first);
-                                if (!mesh) {
+                                const MeshGeometry* const meshGeom = dynamic_cast<const MeshGeometry*> (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<int>(i);
                                         break;
@@ -2041,6 +2056,12 @@ namespace Assimp {
                 CalculatedOpacity = 1.0f - ((Transparent.r + Transparent.g + Transparent.b) / 3.0f);
             }
 
+            // try to get the transparency factor
+            const float TransparencyFactor = PropertyGet<float>(props, "TransparencyFactor", ok);
+            if (ok) {
+                out_mat->AddProperty(&TransparencyFactor, 1, AI_MATKEY_TRANSPARENCYFACTOR);
+            }
+
             // use of TransparencyFactor is inconsistent.
             // Maya always stores it as 1.0,
             // so we can't use it to set AI_MATKEY_OPACITY.
@@ -2191,22 +2212,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<const MeshGeometry*>(v.first);
-                            if (!mesh) {
+                            const MeshGeometry* const meshGeom = dynamic_cast<const MeshGeometry*>(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<int>(i);
                                     break;

+ 12 - 8
code/FBXConverter.h

@@ -58,6 +58,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <assimp/texture.h>
 #include <assimp/camera.h>
 #include <assimp/StringComparison.h>
+#include <unordered_map>
+#include <unordered_set>
 
 struct aiScene;
 struct aiNode;
@@ -74,8 +76,6 @@ namespace FBX {
 
 class Document;
 
-using NodeNameCache = std::set<std::string>;
-
 /** 
  *  Convert a FBX #Document to #aiScene
  *  @param out Empty scene to be populated
@@ -419,7 +419,6 @@ private:
     void TransferDataToScene();
 
 private:
-
     // 0: not assigned yet, others: index is value - 1
     unsigned int defaultMaterialIndex;
 
@@ -429,22 +428,27 @@ private:
     std::vector<aiLight*> lights;
     std::vector<aiCamera*> cameras;
     std::vector<aiTexture*> textures;
-    
 
-    typedef std::map<const Material*, unsigned int> MaterialMap;
+    using MaterialMap = std::map<const Material*, unsigned int>;
     MaterialMap materials_converted;
 
-    typedef std::map<const Video*, unsigned int> VideoMap;
+    using VideoMap = std::map<const Video*, unsigned int>;
     VideoMap textures_converted;
 
-    typedef std::map<const Geometry*, std::vector<unsigned int> > MeshMap;
+    using MeshMap = std::map<const Geometry*, std::vector<unsigned int> >;
     MeshMap meshes_converted;
 
     // fixed node name -> which trafo chain components have animations?
-    typedef std::map<std::string, unsigned int> NodeAnimBitMap;
+    using NodeAnimBitMap = std::map<std::string, unsigned int> ;
     NodeAnimBitMap node_anim_chain_bits;
 
+    // number of nodes with the same name
+    using NodeAnimNameMap = std::unordered_map<std::string, unsigned int>;
+    NodeAnimNameMap mNodeNameInstances;
+
+    using NodeNameCache = std::unordered_set<std::string>;
     NodeNameCache mNodeNames;
+
     double anim_fps;
 
     aiScene* const out;

+ 2 - 2
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());
     // *<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());
     // *<size> {
     s.PutChar('*'); s.PutString(vsize); s.PutString(" {\n");
     // indent + 1

+ 18 - 1
code/FBXMaterial.cpp

@@ -206,6 +206,20 @@ Texture::Texture(uint64_t id, const Element& element, const Document& doc, const
 
     props = GetPropertyTable(doc,"Texture.FbxFileTexture",element,sc);
 
+    // 3DS Max and FBX SDK use "Scaling" and "Translation" instead of "ModelUVScaling" and "ModelUVTranslation". Use these properties if available.
+    bool ok;
+    const aiVector3D& scaling = PropertyGet<aiVector3D>(*props, "Scaling", ok);
+    if (ok) {
+        uvScaling.x = scaling.x;
+        uvScaling.y = scaling.y;
+    }
+
+    const aiVector3D& trans = PropertyGet<aiVector3D>(*props, "Translation", ok);
+    if (ok) {
+        uvTrans.x = trans.x;
+        uvTrans.y = trans.y;
+    }
+
     // resolve video links
     if(doc.Settings().readTextures) {
         const std::vector<const Connection*>& conns = doc.GetConnectionsByDestinationSequenced(ID());
@@ -326,8 +340,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());
         }
     }
 

+ 10 - 10
code/FindInstancesProcess.cpp

@@ -178,30 +178,30 @@ void FindInstancesProcess::Execute( aiScene* pScene)
                     // use a constant epsilon for colors and UV coordinates
                     static const float uvEpsilon = 10e-4f;
                     {
-                        unsigned int i, end = orig->GetNumUVChannels();
-                        for(i = 0; i < end; ++i) {
-                            if (!orig->mTextureCoords[i]) {
+                        unsigned int j, end = orig->GetNumUVChannels();
+                        for(j = 0; j < end; ++j) {
+                            if (!orig->mTextureCoords[j]) {
                                 continue;
                             }
-                            if(!CompareArrays(orig->mTextureCoords[i],inst->mTextureCoords[i],orig->mNumVertices,uvEpsilon)) {
+                            if(!CompareArrays(orig->mTextureCoords[j],inst->mTextureCoords[j],orig->mNumVertices,uvEpsilon)) {
                                 break;
                             }
                         }
-                        if (i != end) {
+                        if (j != end) {
                             continue;
                         }
                     }
                     {
-                        unsigned int i, end = orig->GetNumColorChannels();
-                        for(i = 0; i < end; ++i) {
-                            if (!orig->mColors[i]) {
+                        unsigned int j, end = orig->GetNumColorChannels();
+                        for(j = 0; j < end; ++j) {
+                            if (!orig->mColors[j]) {
                                 continue;
                             }
-                            if(!CompareArrays(orig->mColors[i],inst->mColors[i],orig->mNumVertices,uvEpsilon)) {
+                            if(!CompareArrays(orig->mColors[j],inst->mColors[j],orig->mNumVertices,uvEpsilon)) {
                                 break;
                             }
                         }
-                        if (i != end) {
+                        if (j != end) {
                             continue;
                         }
                     }

+ 12 - 13
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::vector<aiNode
     // XXX totally WIP - doesn't produce proper results, need to evaluate
     // whether there's any use for Irrlicht's proprietary scene format
     // outside Irrlicht ...
+    // This also applies to the above function of FindSuitableMultiple and ClampSpline which are
+    // solely used in this function
 
     if (root->animators.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<aiMaterial*, unsigned int>& 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

+ 3 - 3
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<char>  );
         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) {

+ 3 - 1
code/ImproveCacheLocality.cpp

@@ -112,7 +112,9 @@ void ImproveCacheLocalityProcess::Execute( aiScene* pScene)
         }
     }
     if (!DefaultLogger::isNullLogger()) {
-        ASSIMP_LOG_INFO_F("Cache relevant are ", numm, " meshes (", numf," faces). Average output ACMR is ", out / numf );
+        if (numf > 0) {
+            ASSIMP_LOG_INFO_F("Cache relevant are ", numm, " meshes (", numf, " faces). Average output ACMR is ", out / numf);
+        }
         ASSIMP_LOG_DEBUG("ImproveCacheLocalityProcess finished. ");
     }
 }

+ 1 - 4
code/LWOMaterial.cpp

@@ -320,13 +320,10 @@ void LWOImporter::ConvertMaterial(const LWO::Surface& surf,aiMaterial* pcMat)
 
     // opacity ... either additive or default-blended, please
     if (0.0 != surf.mAdditiveTransparency)  {
-
         const int add = aiBlendMode_Additive;
         pcMat->AddProperty(&surf.mAdditiveTransparency,1,AI_MATKEY_OPACITY);
         pcMat->AddProperty(&add,1,AI_MATKEY_BLEND_FUNC);
-    }
-
-    else if (10e10f != surf.mTransparency)  {
+    } else if (10e10f != surf.mTransparency)  {
         const int def = aiBlendMode_Default;
         const float f = 1.0f-surf.mTransparency;
         pcMat->AddProperty(&f,1,AI_MATKEY_OPACITY);

+ 5 - 5
code/MD3FileData.h

@@ -59,7 +59,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 #include <assimp/Compiler/pushpack1.h>
 
-namespace Assimp    {
+namespace Assimp {
 namespace MD3   {
 
 // to make it easier for us, we test the magic word against both "endianesses"
@@ -303,12 +303,12 @@ inline void Vec3NormalToLatLng( const aiVector3D& p_vIn, uint16_t& p_iOut )
         b = int(57.2957795f * ( std::acos( p_vIn[2] ) ) * ( 255.0f / 360.0f ));
         b &= 0xff;
 
-        ((unsigned char*)&p_iOut)[0] = b;   // longitude
-        ((unsigned char*)&p_iOut)[1] = a;   // latitude
+        ((unsigned char*)&p_iOut)[0] = (unsigned char) b;   // longitude
+        ((unsigned char*)&p_iOut)[1] = (unsigned char) a;   // latitude
     }
 }
 
-}
-}
+} // Namespace MD3
+} // Namespace Assimp
 
 #endif // !! AI_MD3FILEHELPER_H_INC

+ 27 - 25
code/MD3Loader.cpp

@@ -258,10 +258,10 @@ bool Q3Shader::LoadSkin(SkinData& fill, const std::string& pFile,IOSystem* io)
             continue;
 
         fill.textures.push_back(SkinData::TextureEntry());
-        SkinData::TextureEntry& s = fill.textures.back();
+        SkinData::TextureEntry &entry = fill.textures.back();
 
-        s.first  = ss;
-        s.second = GetNextToken(buff);
+        entry.first  = ss;
+        entry.second = GetNextToken(buff);
     }
     return true;
 }
@@ -718,9 +718,7 @@ void MD3Importer::ConvertPath(const char* texture_name, const char* header_name,
 
 // ------------------------------------------------------------------------------------------------
 // Imports the given file into the given scene structure.
-void MD3Importer::InternReadFile( const std::string& pFile,
-    aiScene* pScene, IOSystem* pIOHandler)
-{
+void MD3Importer::InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler) {
     mFile = pFile;
     mScene = pScene;
     mIOHandler = pIOHandler;
@@ -730,11 +728,13 @@ void MD3Importer::InternReadFile( const std::string& pFile,
     std::string::size_type s = mFile.find_last_of("/\\");
     if (s == std::string::npos) {
         s = 0;
+    } else {
+        ++s;
+    }
+    filename = mFile.substr(s), path = mFile.substr(0, s);
+    for (std::string::iterator it = filename.begin(); it != filename.end(); ++it) {
+        *it = static_cast<char>( tolower(*it) );
     }
-    else ++s;
-    filename = mFile.substr(s), path = mFile.substr(0,s);
-    for( std::string::iterator it = filename .begin(); it != filename.end(); ++it)
-        *it = tolower( *it);
 
     // Load multi-part model file, if necessary
     if (configHandleMP) {
@@ -905,15 +905,15 @@ void MD3Importer::InternReadFile( const std::string& pFile,
         // Now search the current shader for a record with this name (
         // excluding texture file extension)
         if (!shaders.blocks.empty()) {
+            std::string::size_type sh = convertedPath.find_last_of('.');
+            if (sh == std::string::npos) {
+                sh = convertedPath.length();
+            }
 
-            std::string::size_type s = convertedPath.find_last_of('.');
-            if (s == std::string::npos)
-                s = convertedPath.length();
-
-            const std::string without_ext = convertedPath.substr(0,s);
+            const std::string without_ext = convertedPath.substr(0,sh);
             std::list< Q3Shader::ShaderDataBlock >::const_iterator dit = std::find(shaders.blocks.begin(),shaders.blocks.end(),without_ext);
             if (dit != shaders.blocks.end()) {
-                // Hurra, wir haben einen. Tolle Sache.
+                // We made it!
                 shader = &*dit;
                 ASSIMP_LOG_INFO("Found shader record for " +without_ext );
             } else {
@@ -945,8 +945,7 @@ void MD3Importer::InternReadFile( const std::string& pFile,
             aiString szString;
             if (convertedPath.length()) {
                 szString.Set(convertedPath);
-            }
-            else    {
+            } else    {
                 ASSIMP_LOG_WARN("Texture file name has zero length. Using default name");
                 szString.Set("dummy_texture.bmp");
             }
@@ -955,8 +954,7 @@ void MD3Importer::InternReadFile( const std::string& pFile,
             // prevent transparency by default
             int no_alpha = aiTextureFlags_IgnoreAlpha;
             pcHelper->AddProperty(&no_alpha,1,AI_MATKEY_TEXFLAGS_DIFFUSE(0));
-        }
-        else {
+        } else {
             Q3Shader::ConvertShaderToMaterial(pcHelper,*shader);
         }
 
@@ -1026,7 +1024,7 @@ void MD3Importer::InternReadFile( const std::string& pFile,
             if (!shader || shader->cull == Q3Shader::CULL_CW) {
                 std::swap(pcMesh->mFaces[i].mIndices[2],pcMesh->mFaces[i].mIndices[1]);
             }
-            pcTriangles++;
+            ++pcTriangles;
         }
 
         // Go to the next surface
@@ -1042,8 +1040,9 @@ void MD3Importer::InternReadFile( const std::string& pFile,
         }
     }
 
-    if (!pScene->mNumMeshes)
+    if (!pScene->mNumMeshes) {
         throw DeadlyImportError( "MD3: File contains no valid mesh");
+    }
     pScene->mNumMaterials = iNumMaterials;
 
     // Now we need to generate an empty node graph
@@ -1057,7 +1056,6 @@ void MD3Importer::InternReadFile( const std::string& pFile,
         pScene->mRootNode->mChildren = new aiNode*[pcHeader->NUM_TAGS];
 
         for (unsigned int i = 0; i < pcHeader->NUM_TAGS; ++i, ++pcTags) {
-
             aiNode* nd = pScene->mRootNode->mChildren[i] = new aiNode();
             nd->mName.Set((const char*)pcTags->NAME);
             nd->mParent = pScene->mRootNode;
@@ -1085,8 +1083,12 @@ void MD3Importer::InternReadFile( const std::string& pFile,
         pScene->mRootNode->mMeshes[i] = i;
 
     // Now rotate the whole scene 90 degrees around the x axis to convert to internal coordinate system
-    pScene->mRootNode->mTransformation = aiMatrix4x4(1.f,0.f,0.f,0.f,
-        0.f,0.f,1.f,0.f,0.f,-1.f,0.f,0.f,0.f,0.f,0.f,1.f);
+    pScene->mRootNode->mTransformation = aiMatrix4x4(
+        1.f,0.f,0.f,0.f,
+        0.f,0.f,1.f,0.f,
+        0.f,-1.f,0.f,0.f,
+        0.f,0.f,0.f,1.f
+    );
 }
 
 #endif // !! ASSIMP_BUILD_NO_MD3_IMPORTER

+ 9 - 9
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);

+ 3 - 0
code/ObjFileData.h

@@ -281,6 +281,8 @@ struct Model {
     std::string m_strActiveGroup;
     //! Vector with generated texture coordinates
     std::vector<aiVector3D> m_TextureCoord;
+    //! Maximum dimension of texture coordinates
+    unsigned int m_TextureCoordDim;
     //! Current mesh instance
     Mesh *m_pCurrentMesh;
     //! Vector with stored meshes
@@ -296,6 +298,7 @@ struct Model {
         m_pDefaultMaterial(NULL),
         m_pGroupFaceIDs(NULL),
         m_strActiveGroup(""),
+        m_TextureCoordDim(0),
         m_pCurrentMesh(NULL)
     {
         // empty

+ 1 - 1
code/ObjFileImporter.cpp

@@ -442,7 +442,7 @@ void ObjFileImporter::createVertexArray(const ObjFile::Model* pModel,
     // Allocate buffer for texture coordinates
     if ( !pModel->m_TextureCoord.empty() && pObjMesh->m_uiUVCoordinates[0] )
     {
-        pMesh->mNumUVComponents[ 0 ] = 2;
+        pMesh->mNumUVComponents[ 0 ] = pModel->m_TextureCoordDim;
         pMesh->mTextureCoords[ 0 ] = new aiVector3D[ pMesh->mNumVertices ];
     }
 

+ 4 - 2
code/ObjFileParser.cpp

@@ -151,7 +151,8 @@ void ObjFileParser::parseFile( IOStreamBuffer<char> &streamBuffer ) {
                 } else if (*m_DataIt == 't') {
                     // read in texture coordinate ( 2D or 3D )
                     ++m_DataIt;
-                    getVector( m_pModel->m_TextureCoord );
+                    size_t dim = getVector(m_pModel->m_TextureCoord);
+                    m_pModel->m_TextureCoordDim = std::max(m_pModel->m_TextureCoordDim, (unsigned int)dim);
                 } else if (*m_DataIt == 'n') {
                     // Read in normal vector definition
                     ++m_DataIt;
@@ -296,7 +297,7 @@ size_t ObjFileParser::getNumComponentsInDataDefinition() {
     return numComponents;
 }
 
-void ObjFileParser::getVector( std::vector<aiVector3D> &point3d_array ) {
+size_t ObjFileParser::getVector( std::vector<aiVector3D> &point3d_array ) {
     size_t numComponents = getNumComponentsInDataDefinition();
     ai_real x, y, z;
     if( 2 == numComponents ) {
@@ -320,6 +321,7 @@ void ObjFileParser::getVector( std::vector<aiVector3D> &point3d_array ) {
     }
     point3d_array.push_back( aiVector3D( x, y, z ) );
     m_DataIt = skipLine<DataArrayIt>( m_DataIt, m_DataItEnd, m_uiLine );
+    return numComponents;
 }
 
 void ObjFileParser::getVector3( std::vector<aiVector3D> &point3d_array ) {

+ 1 - 1
code/ObjFileParser.h

@@ -96,7 +96,7 @@ protected:
     /// Get the number of components in a line.
     size_t getNumComponentsInDataDefinition();
     /// Stores the vector
-    void getVector( std::vector<aiVector3D> &point3d_array );
+    size_t getVector( std::vector<aiVector3D> &point3d_array );
     /// Stores the following 3d vector.
     void getVector3( std::vector<aiVector3D> &point3d_array );
     /// Stores the following homogeneous vector as a 3D vector

+ 3 - 3
code/PostStepRegistry.cpp

@@ -173,6 +173,9 @@ void GetPostProcessingStepInstanceList(std::vector< BaseProcess* >& out)
 #ifndef ASSIMP_BUILD_NO_TRANSFORMTEXCOORDS_PROCESS
     out.push_back( new TextureTransformStep());
 #endif
+#if (!defined ASSIMP_BUILD_NO_GLOBALSCALE_PROCESS)
+    out.push_back( new ScaleProcess());
+#endif
 #if (!defined ASSIMP_BUILD_NO_PRETRANSFORMVERTICES_PROCESS)
     out.push_back( new PretransformVertices());
 #endif
@@ -208,9 +211,6 @@ void GetPostProcessingStepInstanceList(std::vector< BaseProcess* >& out)
 #endif
 #if (!defined ASSIMP_BUILD_NO_GENFACENORMALS_PROCESS)
     out.push_back( new GenFaceNormalsProcess());
-#endif
-#if (!defined ASSIMP_BUILD_NO_GLOBALSCALE_PROCESS)
-    out.push_back( new ScaleProcess());
 #endif
     // .........................................................................
     // DON'T change the order of these five ..

+ 2 - 2
code/SMDLoader.cpp

@@ -486,7 +486,7 @@ void SMDImporter::CreateOutputAnimations(const std::string &pFile, IOSystem* pIO
     if (bLoadAnimationList) {
         GetAnimationFileList(pFile, pIOHandler, animFileList);
     }
-    int animCount = animFileList.size() + 1;
+    int animCount = static_cast<int>( animFileList.size() + 1u );
     pScene->mNumAnimations = 1;
     pScene->mAnimations = new aiAnimation*[animCount];
     memset(pScene->mAnimations, 0, sizeof(aiAnimation*)*animCount);
@@ -510,7 +510,7 @@ void SMDImporter::CreateOutputAnimation(int index, const std::string &name) {
         anim->mName.Set(name.c_str());
     }
     anim->mDuration = dLengthOfAnim;
-    anim->mNumChannels = asBones.size();
+    anim->mNumChannels = static_cast<unsigned int>( asBones.size() );
     anim->mTicksPerSecond = 25.0; // FIXME: is this correct?
 
     aiNodeAnim** pp = anim->mChannels = new aiNodeAnim*[anim->mNumChannels];

+ 1 - 1
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) {

+ 19 - 18
code/SortByPTypeProcess.cpp

@@ -228,36 +228,37 @@ void SortByPTypeProcess::Execute( aiScene* pScene) {
 
             out->mNumVertices = (3 == real ? numPolyVerts : out->mNumFaces * (real+1));
 
-            aiVector3D *vert(NULL), *nor(NULL), *tan(NULL), *bit(NULL);
+            aiVector3D *vert(nullptr), *nor(nullptr), *tan(nullptr), *bit(nullptr);
             aiVector3D *uv   [AI_MAX_NUMBER_OF_TEXTURECOORDS];
             aiColor4D  *cols [AI_MAX_NUMBER_OF_COLOR_SETS];
 
-            if (mesh->mVertices)
+            if (mesh->mVertices) {
                 vert = out->mVertices = new aiVector3D[out->mNumVertices];
+            }
 
-            if (mesh->mNormals)
-                nor  = out->mNormals  = new aiVector3D[out->mNumVertices];
+            if (mesh->mNormals) {
+                nor = out->mNormals = new aiVector3D[out->mNumVertices];
+            }
 
-            if (mesh->mTangents)
-            {
+            if (mesh->mTangents) {
                 tan = out->mTangents   = new aiVector3D[out->mNumVertices];
                 bit = out->mBitangents = new aiVector3D[out->mNumVertices];
             }
 
-            for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS;++i)
-            {
-                if (mesh->mTextureCoords[i])
-                    uv[i] = out->mTextureCoords[i] = new aiVector3D[out->mNumVertices];
-                else uv[i] = NULL;
+            for (unsigned int j = 0; j < AI_MAX_NUMBER_OF_TEXTURECOORDS;++j) {
+                uv[j] = nullptr;
+                if (mesh->mTextureCoords[j]) {
+                    uv[j] = out->mTextureCoords[j] = new aiVector3D[out->mNumVertices];
+                }
 
-                out->mNumUVComponents[i] = mesh->mNumUVComponents[i];
+                out->mNumUVComponents[j] = mesh->mNumUVComponents[j];
             }
 
-            for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_COLOR_SETS;++i)
-            {
-                if (mesh->mColors[i])
-                    cols[i] = out->mColors[i] = new aiColor4D[out->mNumVertices];
-                else cols[i] = NULL;
+            for (unsigned int j = 0; j < AI_MAX_NUMBER_OF_COLOR_SETS;++j) {
+                cols[j] = nullptr;
+                if (mesh->mColors[j]) {
+                    cols[j] = out->mColors[j] = new aiColor4D[out->mNumVertices];
+                }
             }
 
             typedef std::vector< aiVertexWeight > TempBoneInfo;
@@ -323,7 +324,7 @@ void SortByPTypeProcess::Execute( aiScene* pScene) {
                     in.mIndices[q] = outIdx++;
                 }
 
-                in.mIndices = NULL;
+                in.mIndices = nullptr;
                 ++outFaces;
             }
             ai_assert(outFaces == out->mFaces + out->mNumFaces);

+ 20 - 20
code/StandardShapes.cpp

@@ -127,35 +127,35 @@ aiMesh* StandardShapes::MakeMesh(const std::vector<aiVector3D>& positions,
 
     // Determine which kinds of primitives the mesh consists of
     aiMesh* out = new aiMesh();
-    switch (numIndices)
-    {
-    case 1:
-        out->mPrimitiveTypes = aiPrimitiveType_POINT;
-        break;
-    case 2:
-        out->mPrimitiveTypes = aiPrimitiveType_LINE;
-        break;
-    case 3:
-        out->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
-        break;
-    default:
-        out->mPrimitiveTypes = aiPrimitiveType_POLYGON;
-        break;
+    switch (numIndices) {
+        case 1:
+            out->mPrimitiveTypes = aiPrimitiveType_POINT;
+            break;
+        case 2:
+            out->mPrimitiveTypes = aiPrimitiveType_LINE;
+            break;
+        case 3:
+            out->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
+            break;
+        default:
+            out->mPrimitiveTypes = aiPrimitiveType_POLYGON;
+            break;
     };
 
     out->mNumFaces = (unsigned int)positions.size() / numIndices;
     out->mFaces = new aiFace[out->mNumFaces];
-    for (unsigned int i = 0, a = 0; i < out->mNumFaces;++i)
-    {
+    for (unsigned int i = 0, a = 0; i < out->mNumFaces;++i) {
         aiFace& f = out->mFaces[i];
         f.mNumIndices = numIndices;
         f.mIndices = new unsigned int[numIndices];
-        for (unsigned int i = 0; i < numIndices;++i,++a)
-            f.mIndices[i] = a;
+        for (unsigned int j = 0; i < numIndices; ++i, ++a) {
+            f.mIndices[j] = a;
+        }
     }
     out->mNumVertices = (unsigned int)positions.size();
     out->mVertices = new aiVector3D[out->mNumVertices];
     ::memcpy(out->mVertices,&positions[0],out->mNumVertices*sizeof(aiVector3D));
+
     return out;
 }
 
@@ -466,8 +466,8 @@ void StandardShapes::MakeCone(ai_real height,ai_real radius1,
 
     // Need to flip face order?
     if ( SIZE_MAX != old )  {
-        for (size_t s = old; s < positions.size();s += 3) {
-            std::swap(positions[s],positions[s+1]);
+        for (size_t p = old; p < positions.size();p += 3) {
+            std::swap(positions[p],positions[p+1]);
         }
     }
 }

+ 46 - 32
code/ValidateDataStructure.cpp

@@ -160,7 +160,7 @@ inline void ValidateDSProcess::DoValidationEx(T** parray, unsigned int size,
         {
             if (!parray[i])
             {
-                ReportError("aiScene::%s[%i] is NULL (aiScene::%s is %i)",
+                ReportError("aiScene::%s[%u] is NULL (aiScene::%s is %u)",
                     firstName,i,secondName,size);
             }
             Validate(parray[i]);
@@ -170,8 +170,8 @@ inline void ValidateDSProcess::DoValidationEx(T** parray, unsigned int size,
             {
                 if (parray[i]->mName == parray[a]->mName)
                 {
-                    this->ReportError("aiScene::%s[%i] has the same name as "
-                        "aiScene::%s[%i]",firstName, i,secondName, a);
+                	ReportError("aiScene::%s[%u] has the same name as "
+                        "aiScene::%s[%u]",firstName, i,secondName, a);
                 }
             }
         }
@@ -330,6 +330,7 @@ void ValidateDSProcess::Validate( const aiMesh* pMesh)
             {
             case 0:
                 ReportError("aiMesh::mFaces[%i].mNumIndices is 0",i);
+                break;
             case 1:
                 if (0 == (pMesh->mPrimitiveTypes & aiPrimitiveType_POINT))
                 {
@@ -422,7 +423,9 @@ void ValidateDSProcess::Validate( const aiMesh* pMesh)
         if (!abRefList[i])b = true;
     }
     abRefList.clear();
-    if (b)ReportWarning("There are unreferenced vertices");
+    if (b) {
+    	ReportWarning("There are unreferenced vertices");
+    }
 
     // texture channel 2 may not be set if channel 1 is zero ...
     {
@@ -557,7 +560,9 @@ void ValidateDSProcess::Validate( const aiAnimation* pAnimation)
             Validate(pAnimation, pAnimation->mChannels[i]);
         }
     }
-    else ReportError("aiAnimation::mNumChannels is 0. At least one node animation channel must be there.");
+    else {
+    	ReportError("aiAnimation::mNumChannels is 0. At least one node animation channel must be there.");
+    }
 
     // Animation duration is allowed to be zero in cases where the anim contains only a single key frame.
     // if (!pAnimation->mDuration)this->ReportError("aiAnimation::mDuration is zero");
@@ -577,15 +582,16 @@ void ValidateDSProcess::SearchForInvalidTextures(const aiMaterial* pMaterial,
 
     int iNumIndices = 0;
     int iIndex = -1;
-    for (unsigned int i = 0; i < pMaterial->mNumProperties;++i)
-    {
-        aiMaterialProperty* prop = pMaterial->mProperties[i];
-        if (!::strcmp(prop->mKey.data,"$tex.file") && prop->mSemantic == type)  {
+    for (unsigned int i = 0; i < pMaterial->mNumProperties;++i) {
+        aiMaterialProperty* prop = pMaterial->mProperties[ i ];
+        ai_assert(nullptr != prop);
+        if ( !::strcmp(prop->mKey.data,"$tex.file") && prop->mSemantic == static_cast<unsigned int>(type))  {
             iIndex = std::max(iIndex, (int) prop->mIndex);
             ++iNumIndices;
 
-            if (aiPTI_String != prop->mType)
-                ReportError("Material property %s is expected to be a string",prop->mKey.data);
+            if (aiPTI_String != prop->mType) {
+                ReportError("Material property %s is expected to be a string", prop->mKey.data);
+            }
         }
     }
     if (iIndex +1 != iNumIndices)   {
@@ -773,8 +779,10 @@ void ValidateDSProcess::Validate( const aiTexture* pTexture)
     }
     if (pTexture->mHeight)
     {
-        if (!pTexture->mWidth)ReportError("aiTexture::mWidth is zero "
-            "(aiTexture::mHeight is %i, uncompressed texture)",pTexture->mHeight);
+        if (!pTexture->mWidth){
+        	ReportError("aiTexture::mWidth is zero (aiTexture::mHeight is %i, uncompressed texture)",
+        			pTexture->mHeight);
+        }
     }
     else
     {
@@ -805,15 +813,15 @@ void ValidateDSProcess::Validate( const aiAnimation* pAnimation,
 {
     Validate(&pNodeAnim->mNodeName);
 
-    if (!pNodeAnim->mNumPositionKeys && !pNodeAnim->mScalingKeys && !pNodeAnim->mNumRotationKeys)
+    if (!pNodeAnim->mNumPositionKeys && !pNodeAnim->mScalingKeys && !pNodeAnim->mNumRotationKeys) {
         ReportError("Empty node animation channel");
-
+    }
     // otherwise check whether one of the keys exceeds the total duration of the animation
     if (pNodeAnim->mNumPositionKeys)
     {
         if (!pNodeAnim->mPositionKeys)
         {
-            this->ReportError("aiNodeAnim::mPositionKeys is NULL (aiNodeAnim::mNumPositionKeys is %i)",
+        	ReportError("aiNodeAnim::mPositionKeys is NULL (aiNodeAnim::mNumPositionKeys is %i)",
                 pNodeAnim->mNumPositionKeys);
         }
         double dLast = -10e10;
@@ -844,7 +852,7 @@ void ValidateDSProcess::Validate( const aiAnimation* pAnimation,
     {
         if (!pNodeAnim->mRotationKeys)
         {
-            this->ReportError("aiNodeAnim::mRotationKeys is NULL (aiNodeAnim::mNumRotationKeys is %i)",
+            ReportError("aiNodeAnim::mRotationKeys is NULL (aiNodeAnim::mNumRotationKeys is %i)",
                 pNodeAnim->mNumRotationKeys);
         }
         double dLast = -10e10;
@@ -905,19 +913,23 @@ void ValidateDSProcess::Validate( const aiAnimation* pAnimation,
 // ------------------------------------------------------------------------------------------------
 void ValidateDSProcess::Validate( const aiNode* pNode)
 {
-    if (!pNode)ReportError("A node of the scenegraph is NULL");
-    if (pNode != mScene->mRootNode && !pNode->mParent)
-        this->ReportError("A node has no valid parent (aiNode::mParent is NULL)");
-
+    if (!pNode) {
+    	ReportError("A node of the scenegraph is NULL");
+    }
+    // Validate node name string first so that it's safe to use in below expressions
     this->Validate(&pNode->mName);
+    const char* nodeName = (&pNode->mName)->C_Str();
+    if (pNode != mScene->mRootNode && !pNode->mParent){
+        ReportError("Non-root node %s lacks a valid parent (aiNode::mParent is NULL) ", nodeName);
+    }
 
     // validate all meshes
     if (pNode->mNumMeshes)
     {
         if (!pNode->mMeshes)
         {
-            ReportError("aiNode::mMeshes is NULL (aiNode::mNumMeshes is %i)",
-                pNode->mNumMeshes);
+            ReportError("aiNode::mMeshes is NULL for node %s (aiNode::mNumMeshes is %i)",
+            		  nodeName, pNode->mNumMeshes);
         }
         std::vector<bool> abHadMesh;
         abHadMesh.resize(mScene->mNumMeshes,false);
@@ -925,13 +937,13 @@ void ValidateDSProcess::Validate( const aiNode* pNode)
         {
             if (pNode->mMeshes[i] >= mScene->mNumMeshes)
             {
-                ReportError("aiNode::mMeshes[%i] is out of range (maximum is %i)",
-                    pNode->mMeshes[i],mScene->mNumMeshes-1);
+                ReportError("aiNode::mMeshes[%i] is out of range for node %s (maximum is %i)",
+                    pNode->mMeshes[i], nodeName, mScene->mNumMeshes-1);
             }
             if (abHadMesh[pNode->mMeshes[i]])
             {
-                ReportError("aiNode::mMeshes[%i] is already referenced by this node (value: %i)",
-                    i,pNode->mMeshes[i]);
+                ReportError("aiNode::mMeshes[%i] is already referenced by this node %s (value: %i)",
+                    i, nodeName, pNode->mMeshes[i]);
             }
             abHadMesh[pNode->mMeshes[i]] = true;
         }
@@ -939,8 +951,8 @@ void ValidateDSProcess::Validate( const aiNode* pNode)
     if (pNode->mNumChildren)
     {
         if (!pNode->mChildren)  {
-            ReportError("aiNode::mChildren is NULL (aiNode::mNumChildren is %i)",
-                pNode->mNumChildren);
+            ReportError("aiNode::mChildren is NULL for node %s (aiNode::mNumChildren is %i)",
+            		nodeName, pNode->mNumChildren);
         }
         for (unsigned int i = 0; i < pNode->mNumChildren;++i)   {
             Validate(pNode->mChildren[i]);
@@ -953,7 +965,7 @@ void ValidateDSProcess::Validate( const aiString* pString)
 {
     if (pString->length > MAXLEN)
     {
-        this->ReportError("aiString::length is too large (%i, maximum is %lu)",
+        ReportError("aiString::length is too large (%lu, maximum is %lu)",
             pString->length,MAXLEN);
     }
     const char* sz = pString->data;
@@ -961,12 +973,14 @@ void ValidateDSProcess::Validate( const aiString* pString)
     {
         if ('\0' == *sz)
         {
-            if (pString->length != (unsigned int)(sz-pString->data))
+            if (pString->length != (unsigned int)(sz-pString->data)) {
                 ReportError("aiString::data is invalid: the terminal zero is at a wrong offset");
+            }
             break;
         }
-        else if (sz >= &pString->data[MAXLEN])
+        else if (sz >= &pString->data[MAXLEN]) {
             ReportError("aiString::data is invalid. There is no terminal character");
+        }
         ++sz;
     }
 }

+ 4 - 3
code/glTF2Asset.h

@@ -223,7 +223,8 @@ namespace glTF2
         ComponentType_FLOAT = 5126
     };
 
-    inline unsigned int ComponentTypeSize(ComponentType t)
+    inline
+    unsigned int ComponentTypeSize(ComponentType t)
     {
         switch (t) {
             case ComponentType_SHORT:
@@ -250,7 +251,7 @@ namespace glTF2
     };
 
     //! Values for the Sampler::magFilter field
-    enum class SamplerMagFilter: unsigned int
+    enum class SamplerMagFilter : unsigned int
     {
         UNSET = 0,
         SamplerMagFilter_Nearest = 9728,
@@ -258,7 +259,7 @@ namespace glTF2
     };
 
     //! Values for the Sampler::minFilter field
-    enum class SamplerMinFilter: unsigned int
+    enum class SamplerMinFilter : unsigned int
     {
         UNSET = 0,
         SamplerMinFilter_Nearest = 9728,

+ 4 - 4
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<uint64_t>(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<uint64_t>(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<uint64_t>(ptr_ext_comp->IndicesCount), w.mAl);
+							json_comp_data.AddMember("verticesCount", static_cast<uint64_t>(ptr_ext_comp->VerticesCount), w.mAl);
 							// filling object "Open3DGC-compression"
 							Value json_o3dgc;
 

+ 1 - 1
code/glTFExporter.cpp

@@ -245,7 +245,7 @@ inline Ref<Accessor> ExportData(Asset& a, std::string& meshName, Ref<Buffer>& 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);
     }
 }
 

+ 4 - 1
contrib/irrXML/CMakeLists.txt

@@ -13,10 +13,13 @@ if ( MSVC )
   ADD_DEFINITIONS( -D_CRT_SECURE_NO_WARNINGS )
 endif ( MSVC )
 
-add_library(IrrXML STATIC ${IrrXML_SRCS})
+add_library(IrrXML ${IrrXML_SRCS})
 set(IRRXML_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}" CACHE INTERNAL "IrrXML_Include" )
 set(IRRXML_LIBRARY "IrrXML" CACHE INTERNAL "IrrXML" )
 
 install(TARGETS IrrXML
+  LIBRARY DESTINATION ${ASSIMP_LIB_INSTALL_DIR}
   ARCHIVE DESTINATION ${ASSIMP_LIB_INSTALL_DIR}
+  RUNTIME DESTINATION ${ASSIMP_BIN_INSTALL_DIR}
+  FRAMEWORK DESTINATION ${ASSIMP_LIB_INSTALL_DIR}
   COMPONENT ${LIBASSIMP_COMPONENT})

+ 9 - 4
contrib/irrXML/CXMLReaderImpl.h

@@ -10,8 +10,11 @@
 #include "irrArray.h"
 
 #include <cassert>
+#include <stdlib.h>    
+#include <cctype>
+#include <cstdint>
+//using namespace Assimp;
 
-using namespace Assimp;
 
 #ifdef _DEBUG
 #define IRR_DEBUGPRINT(x) printf((x));
@@ -162,7 +165,8 @@ public:
 			return 0;
 
 		core::stringc c = attr->Value.c_str();
-		return fast_atof(c.c_str());
+        return static_cast<float>(atof(c.c_str()));
+        //return fast_atof(c.c_str());
 	}
 
 
@@ -174,7 +178,8 @@ public:
 			return 0;
 
 		core::stringc c = attrvalue;
-		return fast_atof(c.c_str());
+        return static_cast<float>(atof(c.c_str()));
+		//return fast_atof(c.c_str());
 	}
 
 
@@ -428,7 +433,7 @@ private:
 			++P;
 
     // remove trailing whitespace, if any
-    while( isspace( P[-1]))
+    while( std::isspace( P[-1]))
       --P;
 
 		NodeName = core::string<char_type>(pBeginClose, (int)(P - pBeginClose));

+ 2 - 2
contrib/irrXML/irrXML.cpp

@@ -9,7 +9,7 @@
 #include "irrXML.h"
 #include "irrString.h"
 #include "irrArray.h"
-#include <assimp/fast_atof.h>
+//#include <assimp/fast_atof.h>
 #include "CXMLReaderImpl.h"
 
 namespace irr
@@ -18,7 +18,7 @@ namespace io
 {
 
 //! Implementation of the file read callback for ordinary files
-class CFileReadCallBack : public IFileReadCallBack
+class IRRXML_API CFileReadCallBack : public IFileReadCallBack
 {
 public:
 

+ 15 - 9
contrib/irrXML/irrXML.h

@@ -7,6 +7,12 @@
 
 #include <stdio.h>
 
+#ifdef _WIN32
+#   define IRRXML_API __declspec(dllexport)
+#else
+#   define IRRXML_API __attribute__ ((visibility("default")))
+#endif // _WIN32
+
 /** \mainpage irrXML 1.2 API documentation
  <div align="center"><img src="logobig.png" ></div>
 
@@ -409,7 +415,7 @@ namespace io
 	\return Returns a pointer to the created xml parser. This pointer should be 
 	deleted using 'delete' after no longer needed. Returns 0 if an error occured
 	and the file could not be opened. */
-	IrrXMLReader* createIrrXMLReader(const char* filename);
+    IRRXML_API IrrXMLReader* createIrrXMLReader(const char* filename);
 
 	//! Creates an instance of an UFT-8 or ASCII character xml parser.
 	/** This means that all character data will be returned in 8 bit ASCII or UTF-8. The file to read can 
@@ -421,7 +427,7 @@ namespace io
 	\return Returns a pointer to the created xml parser. This pointer should be 
 	deleted using 'delete' after no longer needed. Returns 0 if an error occured
 	and the file could not be opened. */
-	IrrXMLReader* createIrrXMLReader(FILE* file);
+    IRRXML_API IrrXMLReader* createIrrXMLReader(FILE* file);
 
 	//! Creates an instance of an UFT-8 or ASCII character xml parser. 
 	/** This means that all character data will be returned in 8 bit ASCII or UTF-8. The file to read can 
@@ -434,7 +440,7 @@ namespace io
 	 \return Returns a pointer to the created xml parser. This pointer should be 
 	 deleted using 'delete' after no longer needed. Returns 0 if an error occured
 	 and the file could not be opened. */
-	IrrXMLReader* createIrrXMLReader(IFileReadCallBack* callback);
+    IRRXML_API IrrXMLReader* createIrrXMLReader(IFileReadCallBack* callback);
 
 	//! Creates an instance of an UFT-16 xml parser. 
 	/** This means that
@@ -446,7 +452,7 @@ namespace io
 	\return Returns a pointer to the created xml parser. This pointer should be 
 	deleted using 'delete' after no longer needed. Returns 0 if an error occured
 	and the file could not be opened. */
-	IrrXMLReaderUTF16* createIrrXMLReaderUTF16(const char* filename);
+    IRRXML_API IrrXMLReaderUTF16* createIrrXMLReaderUTF16(const char* filename);
 
 	//! Creates an instance of an UFT-16 xml parser. 
 	/** This means that all character data will be returned in UTF-16. The file to read can 
@@ -458,7 +464,7 @@ namespace io
 	\return Returns a pointer to the created xml parser. This pointer should be 
 	deleted using 'delete' after no longer needed. Returns 0 if an error occured
 	and the file could not be opened. */
-	IrrXMLReaderUTF16* createIrrXMLReaderUTF16(FILE* file);
+    IRRXML_API IrrXMLReaderUTF16* createIrrXMLReaderUTF16(FILE* file);
 
 	//! Creates an instance of an UFT-16 xml parser. 
 	/** This means that all character data will be returned in UTF-16. The file to read can 
@@ -471,7 +477,7 @@ namespace io
 	\return Returns a pointer to the created xml parser. This pointer should be 
 	deleted using 'delete' after no longer needed. Returns 0 if an error occured
 	and the file could not be opened. */
-	IrrXMLReaderUTF16* createIrrXMLReaderUTF16(IFileReadCallBack* callback);
+    IRRXML_API IrrXMLReaderUTF16* createIrrXMLReaderUTF16(IFileReadCallBack* callback);
 
 
 	//! Creates an instance of an UFT-32 xml parser. 
@@ -483,7 +489,7 @@ namespace io
 	\return Returns a pointer to the created xml parser. This pointer should be 
 	deleted using 'delete' after no longer needed. Returns 0 if an error occured
 	and the file could not be opened. */
-	IrrXMLReaderUTF32* createIrrXMLReaderUTF32(const char* filename);
+    IRRXML_API IrrXMLReaderUTF32* createIrrXMLReaderUTF32(const char* filename);
 
 	//! Creates an instance of an UFT-32 xml parser. 
 	/** This means that all character data will be returned in UTF-32. The file to read can 
@@ -495,7 +501,7 @@ namespace io
 	\return Returns a pointer to the created xml parser. This pointer should be 
 	deleted using 'delete' after no longer needed. Returns 0 if an error occured
 	and the file could not be opened. */
-	IrrXMLReaderUTF32* createIrrXMLReaderUTF32(FILE* file);
+    IRRXML_API IrrXMLReaderUTF32* createIrrXMLReaderUTF32(FILE* file);
 
 	//! Creates an instance of an UFT-32 xml parser. 
 	/** This means that
@@ -509,7 +515,7 @@ namespace io
 	\return Returns a pointer to the created xml parser. This pointer should be 
 	deleted using 'delete' after no longer needed. Returns 0 if an error occured
 	and the file could not be opened. */
-	IrrXMLReaderUTF32* createIrrXMLReaderUTF32(IFileReadCallBack* callback);
+    IRRXML_API IrrXMLReaderUTF32* createIrrXMLReaderUTF32(IFileReadCallBack* callback);
 	
 
 	/*! \file irrxml.h

+ 18 - 0
contrib/zip/.gitignore

@@ -36,3 +36,21 @@
 # Temporary
 *.swp
 .DS_Store
+
+# CMake
+CMakeScripts
+*.cmake
+
+# Xcode
+*.build
+*.xcodeproj
+zip.sln
+zip.vcxproj.filters
+zip.vcxproj
+ALL_BUILD.vcxproj.filters
+ALL_BUILD.vcxproj
+CMakeFiles/
+zip.dir/
+test/test.exe.vcxproj.filters
+test/test.exe.vcxproj
+test/test.exe.dir/

+ 18 - 0
contrib/zip/.travis.sh

@@ -0,0 +1,18 @@
+#!/bin/bash
+#
+# Build script for travis-ci.org builds.
+#
+if [ $ANALYZE = "true" ] && [ "$CC" = "clang" ]; then
+    # scan-build -h
+    scan-build cmake -G "Unix Makefiles"
+    scan-build -enable-checker security.FloatLoopCounter \
+        -enable-checker security.insecureAPI.UncheckedReturn \
+        --status-bugs -v \
+        make -j 8 \
+        make -j 8 test
+else
+    cmake -DCMAKE_BUILD_TYPE=Debug -DSANITIZE_ADDRESS=On -DCMAKE_INSTALL_PREFIX=_install
+    make -j 8
+    make install
+    ASAN_OPTIONS=detect_leaks=0 LSAN_OPTIONS=verbosity=1:log_threads=1 ctest -V
+fi

+ 15 - 3
contrib/zip/.travis.yml

@@ -1,10 +1,22 @@
 language: c
+addons:
+  apt:
+    packages: &1
+    - lcov
 # Compiler selection
 compiler:
   - clang
   - gcc
+env:
+  - ANALYZE=false
+  - ANALYZE=true
 # Build steps
 script:
-  - mkdir build
-  - cd build
-  - cmake -DCMAKE_BUILD_TYPE=Debug .. && make && make test
+  - ./.travis.sh
+after_success:
+  # Creating report
+  - cmake -DENABLE_COVERAGE=ON
+  - make
+  - make test
+  # Uploading report to CodeCov
+  - bash <(curl -s https://codecov.io/bash)

+ 36 - 7
contrib/zip/CMakeLists.txt

@@ -1,18 +1,47 @@
 cmake_minimum_required(VERSION 2.8)
 project(zip)
+enable_language(C)
+set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH})
 
 if (MSVC)
-    # Use secure functions by defaualt and suppress warnings about "deprecated" functions
-    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /D _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES=1")
-    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /D _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES_COUNT=1")
-    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /D _CRT_NONSTDC_NO_WARNINGS=1 /D _CRT_SECURE_NO_WARNINGS=1")
+  # Use secure functions by defaualt and suppress warnings about "deprecated" functions
+  set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /D _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES=1")
+  set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /D _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES_COUNT=1")
+  set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /D _CRT_NONSTDC_NO_WARNINGS=1 /D _CRT_SECURE_NO_WARNINGS=1")
+elseif ("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU" OR
+        "${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR
+        "${CMAKE_C_COMPILER_ID}" STREQUAL "AppleClang")
+  set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99 -Wall -Wextra -Werror -pedantic")
 endif (MSVC)
 
 # zip
 set(SRC src/miniz.h src/zip.h src/zip.c)
-add_library(${CMAKE_PROJECT_NAME} ${SRC})
+add_library(${PROJECT_NAME} ${SRC})
+target_include_directories(${PROJECT_NAME} INTERFACE src)
 
 # test
-enable_testing()
-add_subdirectory(test)
+if (NOT CMAKE_DISABLE_TESTING)
+  enable_testing()
+  add_subdirectory(test)
+  find_package(Sanitizers)
+  add_sanitizers(${PROJECT_NAME} test.exe)
+  add_sanitizers(${PROJECT_NAME} test_miniz.exe)
+endif()
 
+install(TARGETS ${PROJECT_NAME}
+        RUNTIME DESTINATION bin
+        ARCHIVE DESTINATION lib
+        LIBRARY DESTINATION lib
+        COMPONENT library)
+install(FILES ${PROJECT_SOURCE_DIR}/src/zip.h DESTINATION include)
+
+# uninstall target (https://gitlab.kitware.com/cmake/community/wikis/FAQ#can-i-do-make-uninstall-with-cmake)
+if(NOT TARGET uninstall)
+    configure_file(
+        "${CMAKE_CURRENT_SOURCE_DIR}/cmake/cmake_uninstall.cmake.in"
+        "${CMAKE_CURRENT_BINARY_DIR}/cmake/cmake_uninstall.cmake"
+        IMMEDIATE @ONLY)
+
+    add_custom_target(uninstall
+        COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake/cmake_uninstall.cmake)
+endif()

+ 249 - 79
contrib/zip/README.md

@@ -1,12 +1,11 @@
 ### A portable (OSX/Linux/Windows), simple zip library written in C
 This is done by hacking awesome [miniz](https://code.google.com/p/miniz) library and layering functions on top of the miniz v1.15 API.
 
-[![Windows][win-badge]][win-link] [![OS X][osx-linux-badge]][osx-linux-link]
+[![Windows](https://ci.appveyor.com/api/projects/status/bph8dr3jacgmjv32/branch/master?svg=true&label=windows)](https://ci.appveyor.com/project/kuba--/zip)
+[![Linux](https://travis-ci.org/kuba--/zip.svg?branch=master&label=linux%2fosx)](https://travis-ci.org/kuba--/zip)
+[![Version](https://badge.fury.io/gh/kuba--%2Fzip.svg)](https://github.com/kuba--/zip/releases)
+[![Codecov](https://codecov.io/gh/kuba--/zip/branch/master/graph/badge.svg)](https://codecov.io/gh/kuba--/zip)
 
-[win-badge]: https://img.shields.io/appveyor/ci/kuba--/zip/master.svg?label=windows "AppVeyor build status"
-[win-link]:  https://ci.appveyor.com/project/kuba--/zip "AppVeyor build status"
-[osx-linux-badge]: https://img.shields.io/travis/kuba--/zip/master.svg?label=linux/osx "Travis CI build status"
-[osx-linux-link]:  https://travis-ci.org/kuba--/zip "Travis CI build status"
 
 # The Idea
 <img src="zip.png" name="zip" />
@@ -23,117 +22,288 @@ It was the reason, why I decided to write zip module on top of the miniz. It req
 
 * Create a new zip archive with default compression level.
 ```c
-    struct zip_t *zip = zip_open("foo.zip", ZIP_DEFAULT_COMPRESSION_LEVEL, 'w');
+struct zip_t *zip = zip_open("foo.zip", ZIP_DEFAULT_COMPRESSION_LEVEL, 'w');
+{
+    zip_entry_open(zip, "foo-1.txt");
     {
-        zip_entry_open(zip, "foo-1.txt");
-        {
-            char *buf = "Some data here...";
-            zip_entry_write(zip, buf, strlen(buf));
-        }
-        zip_entry_close(zip);
-
-        zip_entry_open(zip, "foo-2.txt");
-        {
-            // merge 3 files into one entry and compress them on-the-fly.
-            zip_entry_fwrite(zip, "foo-2.1.txt");
-            zip_entry_fwrite(zip, "foo-2.2.txt");
-            zip_entry_fwrite(zip, "foo-2.3.txt");
-        }
-        zip_entry_close(zip);
+        const char *buf = "Some data here...\0";
+        zip_entry_write(zip, buf, strlen(buf));
     }
-    zip_close(zip);
+    zip_entry_close(zip);
+
+    zip_entry_open(zip, "foo-2.txt");
+    {
+        // merge 3 files into one entry and compress them on-the-fly.
+        zip_entry_fwrite(zip, "foo-2.1.txt");
+        zip_entry_fwrite(zip, "foo-2.2.txt");
+        zip_entry_fwrite(zip, "foo-2.3.txt");
+    }
+    zip_entry_close(zip);
+}
+zip_close(zip);
 ```
 
 * Append to the existing zip archive.
 ```c
-    struct zip_t *zip = zip_open("foo.zip", ZIP_DEFAULT_COMPRESSION_LEVEL, 'a');
+struct zip_t *zip = zip_open("foo.zip", ZIP_DEFAULT_COMPRESSION_LEVEL, 'a');
+{
+    zip_entry_open(zip, "foo-3.txt");
     {
-        zip_entry_open(zip, "foo-3.txt");
-        {
-            char *buf = "Append some data here...";
-            zip_entry_write(zip, buf, strlen(buf));
-        }
-        zip_entry_close(zip);
+        const char *buf = "Append some data here...\0";
+        zip_entry_write(zip, buf, strlen(buf));
     }
-    zip_close(zip);
+    zip_entry_close(zip);
+}
+zip_close(zip);
 ```
 
 * Extract a zip archive into a folder.
 ```c
-    int on_extract_entry(const char *filename, void *arg) {
-        static int i = 0;
-        int n = *(int *)arg;
-        printf("Extracted: %s (%d of %d)\n", filename, ++i, n);
+int on_extract_entry(const char *filename, void *arg) {
+    static int i = 0;
+    int n = *(int *)arg;
+    printf("Extracted: %s (%d of %d)\n", filename, ++i, n);
+
+    return 0;
+}
+
+int arg = 2;
+zip_extract("foo.zip", "/tmp", on_extract_entry, &arg);
+```
+
+*   Extract a zip entry into memory.
+```c
+void *buf = NULL;
+size_t bufsize;
 
-        return 0;
+struct zip_t *zip = zip_open("foo.zip", 0, 'r');
+{
+    zip_entry_open(zip, "foo-1.txt");
+    {
+        zip_entry_read(zip, &buf, &bufsize);
     }
+    zip_entry_close(zip);
+}
+zip_close(zip);
 
-    int arg = 2;
-    zip_extract("foo.zip", "/tmp", on_extract_entry, &arg);
+free(buf);
 ```
 
-* Extract a zip entry into memory.
+*   Extract a zip entry into memory (no internal allocation).
 ```c
-    void *buf = NULL;
-    size_t bufsize;
+unsigned char *buf;
+size_t bufsize;
 
-    struct zip_t *zip = zip_open("foo.zip", 0, 'r');
+struct zip_t *zip = zip_open("foo.zip", 0, 'r');
+{
+    zip_entry_open(zip, "foo-1.txt");
     {
-        zip_entry_open(zip, "foo-1.txt");
-        {
-            zip_entry_read(zip, &buf, &bufsize);
-        }
-        zip_entry_close(zip);
+        bufsize = zip_entry_size(zip);
+        buf = calloc(sizeof(unsigned char), bufsize);
+
+        zip_entry_noallocread(zip, (void *)buf, bufsize);
     }
-    zip_close(zip);
+    zip_entry_close(zip);
+}
+zip_close(zip);
 
-    free(buf);
+free(buf);
 ```
 
-* Extract a zip entry into memory using callback.
+*   Extract a zip entry into memory using callback.
 ```c
-    struct buffer_t {
-        char *data;
-        size_t size;
-    };
+struct buffer_t {
+    char *data;
+    size_t size;
+};
 
-    static size_t on_extract(void *arg, unsigned long long offset, const void *data, size_t size) {
-        struct buffer_t *buf = (struct buffer_t *)arg;
-        buf->data = realloc(buf->data, buf->size + size + 1);
-        assert(NULL != buf->data);
+static size_t on_extract(void *arg, unsigned long long offset, const void *data, size_t size) {
+    struct buffer_t *buf = (struct buffer_t *)arg;
+    buf->data = realloc(buf->data, buf->size + size + 1);
+    assert(NULL != buf->data);
 
-        memcpy(&(buf->data[buf->size]), data, size);
-        buf->size += size;
-        buf->data[buf->size] = 0;
+    memcpy(&(buf->data[buf->size]), data, size);
+    buf->size += size;
+    buf->data[buf->size] = 0;
 
-        return size;
-    }
+    return size;
+}
 
-    struct buffer_t buf = {0};
-    struct zip_t *zip = zip_open("foo.zip", 0, 'r');
+struct buffer_t buf = {0};
+struct zip_t *zip = zip_open("foo.zip", 0, 'r');
+{
+    zip_entry_open(zip, "foo-1.txt");
     {
-        zip_entry_open(zip, "foo-1.txt");
-        {
-            zip_entry_extract(zip, on_extract, &buf);
-        }
-        zip_entry_close(zip);
+        zip_entry_extract(zip, on_extract, &buf);
     }
-    zip_close(zip);
+    zip_entry_close(zip);
+}
+zip_close(zip);
 
-    free(buf.data);
+free(buf.data);
 ```
 
 
-* Extract a zip entry into a file.
+*   Extract a zip entry into a file.
 ```c
-    struct zip_t *zip = zip_open("foo.zip", 0, 'r');
+struct zip_t *zip = zip_open("foo.zip", 0, 'r');
+{
+    zip_entry_open(zip, "foo-2.txt");
     {
-        zip_entry_open(zip, "foo-2.txt");
-        {
-            zip_entry_fread(zip, "foo-2.txt");
-        }
-        zip_entry_close(zip);
+        zip_entry_fread(zip, "foo-2.txt");
+    }
+    zip_entry_close(zip);
+}
+zip_close(zip);
+```
+
+*   List of all zip entries
+```c
+struct zip_t *zip = zip_open("foo.zip", 0, 'r');
+int i, n = zip_total_entries(zip);
+for (i = 0; i < n; ++i) {
+    zip_entry_openbyindex(zip, i);
+    {
+        const char *name = zip_entry_name(zip);
+        int isdir = zip_entry_isdir(zip);
+        unsigned long long size = zip_entry_size(zip);
+        unsigned int crc32 = zip_entry_crc32(zip);
+    }
+    zip_entry_close(zip);
+}
+zip_close(zip);
+```
+
+## Bindings
+Compile zip library as a dynamic library.
+```shell
+$ mkdir build
+$ cd build
+$ cmake -DBUILD_SHARED_LIBS=true ..
+$ make
+```
+
+### Go (cgo)
+```go
+package main
+
+/*
+#cgo CFLAGS: -I../src
+#cgo LDFLAGS: -L. -lzip
+#include <zip.h>
+*/
+import "C"
+import "unsafe"
+
+func main() {
+	path := C.CString("/tmp/go.zip")
+	zip := C.zip_open(path, 6, 'w')
+
+	entryname := C.CString("test")
+	C.zip_entry_open(zip, entryname)
+
+	content := "test content"
+	buf := unsafe.Pointer(C.CString(content))
+	bufsize := C.size_t(len(content))
+	C.zip_entry_write(zip, buf, bufsize)
+
+	C.zip_entry_close(zip)
+
+	C.zip_close(zip)
+}
+```
+
+### Ruby (ffi)
+Install _ffi_ gem.
+```shell
+$ gem install ffi
+```
+
+Bind in your module.
+```ruby
+require 'ffi'
+
+module Zip
+  extend FFI::Library
+  ffi_lib "./libzip.#{::FFI::Platform::LIBSUFFIX}"
+
+  attach_function :zip_open, [:string, :int, :char], :pointer
+  attach_function :zip_close, [:pointer], :void
+
+  attach_function :zip_entry_open, [:pointer, :string], :int
+  attach_function :zip_entry_close, [:pointer], :void
+  attach_function :zip_entry_write, [:pointer, :string, :int], :int
+end
+
+ptr = Zip.zip_open("/tmp/ruby.zip", 6, "w".bytes()[0])
+
+status = Zip.zip_entry_open(ptr, "test")
+
+content = "test content"
+status = Zip.zip_entry_write(ptr, content, content.size())
+
+Zip.zip_entry_close(ptr)
+Zip.zip_close(ptr)
+```
+
+### Python (cffi)
+Install _cffi_ package
+```shell
+$ pip install cffi
+```
+
+Bind in your package.
+```python
+import ctypes.util
+from cffi import FFI
+
+ffi = FFI()
+ffi.cdef("""
+    struct zip_t *zip_open(const char *zipname, int level, char mode);
+    void zip_close(struct zip_t *zip);
+
+    int zip_entry_open(struct zip_t *zip, const char *entryname);
+    int zip_entry_close(struct zip_t *zip);
+    int zip_entry_write(struct zip_t *zip, const void *buf, size_t bufsize);
+""")
+
+Zip = ffi.dlopen(ctypes.util.find_library("zip"))
+
+ptr = Zip.zip_open("/tmp/python.zip", 6, 'w')
+
+status = Zip.zip_entry_open(ptr, "test")
+
+content = "test content"
+status = Zip.zip_entry_write(ptr, content, len(content))
+
+Zip.zip_entry_close(ptr)
+Zip.zip_close(ptr)
+```
+
+### Ring
+The language comes with RingZip based on this library
+```ring
+load "ziplib.ring"
+
+new Zip {
+    setFileName("myfile.zip")
+    open("w")
+    newEntry() {
+        open("test.c")
+        writefile("test.c")
+        close()
     }
-    zip_close(zip);
+    close()
+}
+```
+
+# Contribution Rules/Coding Standards
+No need to throw away your coding style, just do your best to follow default clang-format style.
+Apply `clang-format` to the source files before commit:
+```sh
+for file in $(git ls-files | \grep -E '\.(c|h)$' | \grep -v -- '#')
+do
+    clang-format -i $file
+done
 ```
 

+ 1 - 1
contrib/zip/appveyor.yml

@@ -1,4 +1,4 @@
-version: 1.0.{build}
+version: zip-0.1.9.{build}
 build_script:
 - cmd: >-
     cd c:\projects\zip

+ 55 - 0
contrib/zip/cmake/asan-wrapper

@@ -0,0 +1,55 @@
+#!/bin/sh
+
+# The MIT License (MIT)
+#
+# Copyright (c)
+#   2013 Matthew Arsenault
+#   2015-2016 RWTH Aachen University, Federal Republic of Germany
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+# This script is a wrapper for AddressSanitizer. In some special cases you need
+# to preload AddressSanitizer to avoid error messages - e.g. if you're
+# preloading another library to your application. At the moment this script will
+# only do something, if we're running on a Linux platform. OSX might not be
+# affected.
+
+
+# Exit immediately, if platform is not Linux.
+if [ "$(uname)" != "Linux" ]
+then
+    exec $@
+fi
+
+
+# Get the used libasan of the application ($1). If a libasan was found, it will
+# be prepended to LD_PRELOAD.
+libasan=$(ldd $1 | grep libasan | sed "s/^[[:space:]]//" | cut -d' ' -f1)
+if [ -n "$libasan" ]
+then
+    if [ -n "$LD_PRELOAD" ]
+    then
+        export LD_PRELOAD="$libasan:$LD_PRELOAD"
+    else
+        export LD_PRELOAD="$libasan"
+    fi
+fi
+
+# Execute the application.
+exec $@

+ 23 - 0
contrib/zip/cmake/cmake_uninstall.cmake.in

@@ -0,0 +1,23 @@
+# copied from https://gitlab.kitware.com/cmake/community/wikis/FAQ#can-i-do-make-uninstall-with-cmake
+if(NOT EXISTS "@CMAKE_BINARY_DIR@/install_manifest.txt")
+  message(FATAL_ERROR "Cannot find install manifest: @CMAKE_BINARY_DIR@/install_manifest.txt")
+endif(NOT EXISTS "@CMAKE_BINARY_DIR@/install_manifest.txt")
+
+file(READ "@CMAKE_BINARY_DIR@/install_manifest.txt" files)
+string(REGEX REPLACE "\n" ";" files "${files}")
+foreach(file ${files})
+  message(STATUS "Uninstalling $ENV{DESTDIR}${file}")
+  if(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}")
+    exec_program(
+      "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\""
+      OUTPUT_VARIABLE rm_out
+      RETURN_VALUE rm_retval
+      )
+    if(NOT "${rm_retval}" STREQUAL 0)
+      message(FATAL_ERROR "Problem when removing $ENV{DESTDIR}${file}")
+    endif(NOT "${rm_retval}" STREQUAL 0)
+  else(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}")
+    message(STATUS "File $ENV{DESTDIR}${file} does not exist.")
+  endif(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}")
+endforeach(file)
+

File diff suppressed because it is too large
+ 517 - 317
contrib/zip/src/miniz.h


+ 782 - 496
contrib/zip/src/zip.c

@@ -7,29 +7,40 @@
  * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
  * OTHER DEALINGS IN THE SOFTWARE.
  */
-
-#include "zip.h"
-#include "miniz.h"
+#define __STDC_WANT_LIB_EXT1__ 1
 
 #include <errno.h>
 #include <sys/stat.h>
 #include <time.h>
 
-#if defined _WIN32 || defined __WIN32__
-/* Win32, DOS */
+#if defined(_WIN32) || defined(__WIN32__) || defined(_MSC_VER) ||              \
+    defined(__MINGW32__)
+/* Win32, DOS, MSVC, MSVS */
 #include <direct.h>
 
 #define MKDIR(DIRNAME) _mkdir(DIRNAME)
 #define STRCLONE(STR) ((STR) ? _strdup(STR) : NULL)
 #define HAS_DEVICE(P)                                                          \
-    ((((P)[0] >= 'A' && (P)[0] <= 'Z') || ((P)[0] >= 'a' && (P)[0] <= 'z')) && \
-     (P)[1] == ':')
+  ((((P)[0] >= 'A' && (P)[0] <= 'Z') || ((P)[0] >= 'a' && (P)[0] <= 'z')) &&   \
+   (P)[1] == ':')
 #define FILESYSTEM_PREFIX_LEN(P) (HAS_DEVICE(P) ? 2 : 0)
 #define ISSLASH(C) ((C) == '/' || (C) == '\\')
 
 #else
+
+#include <unistd.h> // needed for symlink() on BSD
+int symlink(const char *target, const char *linkpath); // needed on Linux
+
 #define MKDIR(DIRNAME) mkdir(DIRNAME, 0755)
 #define STRCLONE(STR) ((STR) ? strdup(STR) : NULL)
+
+#endif
+
+#include "miniz.h"
+#include "zip.h"
+
+#ifndef HAS_DEVICE
+#define HAS_DEVICE(P) 0
 #endif
 
 #ifndef FILESYSTEM_PREFIX_LEN
@@ -40,601 +51,876 @@
 #define ISSLASH(C) ((C) == '/')
 #endif
 
-#define CLEANUP(ptr)           \
-    do {                       \
-        if (ptr) {             \
-            free((void *)ptr); \
-            ptr = NULL;        \
-        }                      \
-    } while (0)
-
-static char *basename(const char *name) {
-    char const *p;
-    char const *base = name += FILESYSTEM_PREFIX_LEN(name);
-    int all_slashes = 1;
-
-    for (p = name; *p; p++) {
-        if (ISSLASH(*p))
-            base = p + 1;
-        else
-            all_slashes = 0;
-    }
-
-    /* If NAME is all slashes, arrange to return `/'. */
-    if (*base == '\0' && ISSLASH(*name) && all_slashes) --base;
-
-    return (char *)base;
+#define CLEANUP(ptr)                                                           \
+  do {                                                                         \
+    if (ptr) {                                                                 \
+      free((void *)ptr);                                                       \
+      ptr = NULL;                                                              \
+    }                                                                          \
+  } while (0)
+
+static const char *base_name(const char *name) {
+  char const *p;
+  char const *base = name += FILESYSTEM_PREFIX_LEN(name);
+  int all_slashes = 1;
+
+  for (p = name; *p; p++) {
+    if (ISSLASH(*p))
+      base = p + 1;
+    else
+      all_slashes = 0;
+  }
+
+  /* If NAME is all slashes, arrange to return `/'. */
+  if (*base == '\0' && ISSLASH(*name) && all_slashes)
+    --base;
+
+  return base;
 }
 
 static int mkpath(const char *path) {
-    char const *p;
-    char npath[MAX_PATH + 1] = {0};
-    int len = 0;
-
-    for (p = path; *p && len < MAX_PATH; p++) {
-        if (ISSLASH(*p) && len > 0) {
-            if (MKDIR(npath) == -1)
-                if (errno != EEXIST) return -1;
-        }
-        npath[len++] = *p;
-    }
-
-    return 0;
+  char const *p;
+  char npath[MAX_PATH + 1];
+  int len = 0;
+  int has_device = HAS_DEVICE(path);
+
+  memset(npath, 0, MAX_PATH + 1);
+
+#ifdef _WIN32
+  // only on windows fix the path
+  npath[0] = path[0];
+  npath[1] = path[1];
+  len = 2;
+#endif // _WIN32
+    
+  for (p = path + len; *p && len < MAX_PATH; p++) {
+    if (ISSLASH(*p) && ((!has_device && len > 0) || (has_device && len > 2))) {
+      if (MKDIR(npath) == -1)
+        if (errno != EEXIST)
+          return -1;
+    }
+    npath[len++] = *p;
+  }
+
+  return 0;
 }
 
-static char *strrpl(const char *str, char oldchar, char newchar) {
-    char *rpl = (char *)malloc(sizeof(char) * (1 + strlen(str)));
-    char *begin = rpl;
-    char c;
-    while((c = *str++)) {
-        if (c == oldchar) {
-            c = newchar;
-        }
-        *rpl++ = c;
+static char *strrpl(const char *str, size_t n, char oldchar, char newchar) {
+  char c;
+  size_t i;
+  char *rpl = (char *)calloc((1 + n), sizeof(char));
+  char *begin = rpl;
+  if (!rpl) {
+    return NULL;
+  }
+
+  for (i = 0; (i < n) && (c = *str++); ++i) {
+    if (c == oldchar) {
+      c = newchar;
     }
-    *rpl = '\0';
+    *rpl++ = c;
+  }
 
-    return begin;
+  return begin;
 }
 
 struct zip_entry_t {
-    int index;
-    const char *name;
-    mz_uint64 uncomp_size;
-    mz_uint64 comp_size;
-    mz_uint32 uncomp_crc32;
-    mz_uint64 offset;
-    mz_uint8 header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE];
-    mz_uint64 header_offset;
-    mz_uint16 method;
-    mz_zip_writer_add_state state;
-    tdefl_compressor comp;
+  int index;
+  char *name;
+  mz_uint64 uncomp_size;
+  mz_uint64 comp_size;
+  mz_uint32 uncomp_crc32;
+  mz_uint64 offset;
+  mz_uint8 header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE];
+  mz_uint64 header_offset;
+  mz_uint16 method;
+  mz_zip_writer_add_state state;
+  tdefl_compressor comp;
+  mz_uint32 external_attr;
+  time_t m_time;
 };
 
 struct zip_t {
-    mz_zip_archive archive;
-    mz_uint level;
-    struct zip_entry_t entry;
-    char mode;
+  mz_zip_archive archive;
+  mz_uint level;
+  struct zip_entry_t entry;
 };
 
 struct zip_t *zip_open(const char *zipname, int level, char mode) {
-    struct zip_t *zip = NULL;
-
-    if (!zipname || strlen(zipname) < 1) {
-        // zip_t archive name is empty or NULL
-        goto cleanup;
+  struct zip_t *zip = NULL;
+
+  if (!zipname || strlen(zipname) < 1) {
+    // zip_t archive name is empty or NULL
+    goto cleanup;
+  }
+
+  if (level < 0)
+    level = MZ_DEFAULT_LEVEL;
+  if ((level & 0xF) > MZ_UBER_COMPRESSION) {
+    // Wrong compression level
+    goto cleanup;
+  }
+
+  zip = (struct zip_t *)calloc((size_t)1, sizeof(struct zip_t));
+  if (!zip)
+    goto cleanup;
+
+  zip->level = (mz_uint)level;
+  switch (mode) {
+  case 'w':
+    // Create a new archive.
+    if (!mz_zip_writer_init_file(&(zip->archive), zipname, 0)) {
+      // Cannot initialize zip_archive writer
+      goto cleanup;
     }
+    break;
 
-    if (level < 0) level = MZ_DEFAULT_LEVEL;
-    if ((level & 0xF) > MZ_UBER_COMPRESSION) {
-        // Wrong compression level
-        goto cleanup;
+  case 'r':
+  case 'a':
+    if (!mz_zip_reader_init_file(
+            &(zip->archive), zipname,
+            zip->level | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY)) {
+      // An archive file does not exist or cannot initialize
+      // zip_archive reader
+      goto cleanup;
     }
-
-    zip = (struct zip_t *)calloc((size_t)1, sizeof(struct zip_t));
-    if (!zip) goto cleanup;
-
-    zip->level = level;
-    zip->mode = mode;
-    switch (mode) {
-        case 'w':
-            // Create a new archive.
-            if (!mz_zip_writer_init_file(&(zip->archive), zipname, 0)) {
-                // Cannot initialize zip_archive writer
-                goto cleanup;
-            }
-            break;
-
-        case 'r':
-        case 'a':
-            if (!mz_zip_reader_init_file(
-                    &(zip->archive), zipname,
-                    level | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY)) {
-                // An archive file does not exist or cannot initialize
-                // zip_archive reader
-                goto cleanup;
-            }
-
-            if (mode == 'a' &&
-                !mz_zip_writer_init_from_reader(&(zip->archive), zipname)) {
-                mz_zip_reader_end(&(zip->archive));
-                goto cleanup;
-            }
-
-            break;
-
-        default:
-            goto cleanup;
+    if (mode == 'a' &&
+        !mz_zip_writer_init_from_reader(&(zip->archive), zipname)) {
+      mz_zip_reader_end(&(zip->archive));
+      goto cleanup;
     }
+    break;
+
+  default:
+    goto cleanup;
+  }
 
-    return zip;
+  return zip;
 
 cleanup:
-    CLEANUP(zip);
-    return NULL;
+  CLEANUP(zip);
+  return NULL;
 }
 
 void zip_close(struct zip_t *zip) {
-    if (zip) {
-        // Always finalize, even if adding failed for some reason, so we have a
-        // valid central directory.
-        mz_zip_writer_finalize_archive(&(zip->archive));
+  if (zip) {
+    // Always finalize, even if adding failed for some reason, so we have a
+    // valid central directory.
+    mz_zip_writer_finalize_archive(&(zip->archive));
 
-        mz_zip_writer_end(&(zip->archive));
-        mz_zip_reader_end(&(zip->archive));
+    mz_zip_writer_end(&(zip->archive));
+    mz_zip_reader_end(&(zip->archive));
 
-        CLEANUP(zip);
-    }
+    CLEANUP(zip);
+  }
 }
 
 int zip_entry_open(struct zip_t *zip, const char *entryname) {
-    char *locname = NULL;
-    size_t entrylen = 0;
-    mz_zip_archive *pzip = NULL;
-    mz_uint num_alignment_padding_bytes, level;
-
-    if (!zip || !entryname) {
-        return -1;
-    }
-
-    entrylen = strlen(entryname);
-    if (entrylen < 1) {
-        return -1;
-    }
-
-    pzip = &(zip->archive);
-    /*
-      .ZIP File Format Specification Version: 6.3.3
-
-      4.4.17.1 The name of the file, with optional relative path.
-      The path stored MUST not contain a drive or
-      device letter, or a leading slash.  All slashes
-      MUST be forward slashes '/' as opposed to
-      backwards slashes '\' for compatibility with Amiga
-      and UNIX file systems etc.  If input came from standard
-      input, there is no file name field.
-    */
-    locname = strrpl(entryname, '\\', '/');
-
-    if (zip->mode == 'r') {
-        zip->entry.index = mz_zip_reader_locate_file(pzip, locname, NULL, 0);
-        CLEANUP(locname);
-        return (zip->entry.index < 0) ? -1 : 0;
-    }
-
-    zip->entry.index = zip->archive.m_total_files;
-    zip->entry.name = locname;
-    if (!zip->entry.name) {
-        // Cannot parse zip entry name
-        return -1;
-    }
+  size_t entrylen = 0;
+  mz_zip_archive *pzip = NULL;
+  mz_uint num_alignment_padding_bytes, level;
+  mz_zip_archive_file_stat stats;
+
+  if (!zip || !entryname) {
+    return -1;
+  }
+
+  entrylen = strlen(entryname);
+  if (entrylen < 1) {
+    return -1;
+  }
+
+  /*
+    .ZIP File Format Specification Version: 6.3.3
+
+    4.4.17.1 The name of the file, with optional relative path.
+    The path stored MUST not contain a drive or
+    device letter, or a leading slash.  All slashes
+    MUST be forward slashes '/' as opposed to
+    backwards slashes '\' for compatibility with Amiga
+    and UNIX file systems etc.  If input came from standard
+    input, there is no file name field.
+  */
+  zip->entry.name = strrpl(entryname, entrylen, '\\', '/');
+  if (!zip->entry.name) {
+    // Cannot parse zip entry name
+    return -1;
+  }
+
+  pzip = &(zip->archive);
+  if (pzip->m_zip_mode == MZ_ZIP_MODE_READING) {
+    zip->entry.index =
+        mz_zip_reader_locate_file(pzip, zip->entry.name, NULL, 0);
+    if (zip->entry.index < 0) {
+      goto cleanup;
+    }
+
+    if (!mz_zip_reader_file_stat(pzip, (mz_uint)zip->entry.index, &stats)) {
+      goto cleanup;
+    }
+
+    zip->entry.comp_size = stats.m_comp_size;
+    zip->entry.uncomp_size = stats.m_uncomp_size;
+    zip->entry.uncomp_crc32 = stats.m_crc32;
+    zip->entry.offset = stats.m_central_dir_ofs;
+    zip->entry.header_offset = stats.m_local_header_ofs;
+    zip->entry.method = stats.m_method;
+    zip->entry.external_attr = stats.m_external_attr;
+    zip->entry.m_time = stats.m_time;
 
-    zip->entry.comp_size = 0;
-    zip->entry.uncomp_size = 0;
-    zip->entry.uncomp_crc32 = MZ_CRC32_INIT;
-    zip->entry.offset = zip->archive.m_archive_size;
-    zip->entry.header_offset = zip->archive.m_archive_size;
-    memset(zip->entry.header, 0,
-           MZ_ZIP_LOCAL_DIR_HEADER_SIZE * sizeof(mz_uint8));
-    zip->entry.method = 0;
-
-    num_alignment_padding_bytes =
-        mz_zip_writer_compute_padding_needed_for_file_alignment(pzip);
-
-    if (!pzip->m_pState || (pzip->m_zip_mode != MZ_ZIP_MODE_WRITING)) {
-        // Wrong zip mode
-        return -1;
-    }
-    if (zip->level & MZ_ZIP_FLAG_COMPRESSED_DATA) {
-        // Wrong zip compression level
-        return -1;
-    }
-    // no zip64 support yet
-    if ((pzip->m_total_files == 0xFFFF) ||
-        ((pzip->m_archive_size + num_alignment_padding_bytes +
-          MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE +
-          entrylen) > 0xFFFFFFFF)) {
-        // No zip64 support yet
-        return -1;
-    }
-    if (!mz_zip_writer_write_zeros(
-            pzip, zip->entry.offset,
-            num_alignment_padding_bytes + sizeof(zip->entry.header))) {
-        // Cannot memset zip entry header
-        return -1;
-    }
+    return 0;
+  }
+
+  zip->entry.index = (int)zip->archive.m_total_files;
+  zip->entry.comp_size = 0;
+  zip->entry.uncomp_size = 0;
+  zip->entry.uncomp_crc32 = MZ_CRC32_INIT;
+  zip->entry.offset = zip->archive.m_archive_size;
+  zip->entry.header_offset = zip->archive.m_archive_size;
+  memset(zip->entry.header, 0, MZ_ZIP_LOCAL_DIR_HEADER_SIZE * sizeof(mz_uint8));
+  zip->entry.method = 0;
+  zip->entry.external_attr = 0;
+
+  num_alignment_padding_bytes =
+      mz_zip_writer_compute_padding_needed_for_file_alignment(pzip);
+
+  if (!pzip->m_pState || (pzip->m_zip_mode != MZ_ZIP_MODE_WRITING)) {
+    // Wrong zip mode
+    goto cleanup;
+  }
+  if (zip->level & MZ_ZIP_FLAG_COMPRESSED_DATA) {
+    // Wrong zip compression level
+    goto cleanup;
+  }
+  // no zip64 support yet
+  if ((pzip->m_total_files == 0xFFFF) ||
+      ((pzip->m_archive_size + num_alignment_padding_bytes +
+        MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE +
+        entrylen) > 0xFFFFFFFF)) {
+    // No zip64 support yet
+    goto cleanup;
+  }
+  if (!mz_zip_writer_write_zeros(pzip, zip->entry.offset,
+                                 num_alignment_padding_bytes +
+                                     sizeof(zip->entry.header))) {
+    // Cannot memset zip entry header
+    goto cleanup;
+  }
+
+  zip->entry.header_offset += num_alignment_padding_bytes;
+  if (pzip->m_file_offset_alignment) {
+    MZ_ASSERT(
+        (zip->entry.header_offset & (pzip->m_file_offset_alignment - 1)) == 0);
+  }
+  zip->entry.offset += num_alignment_padding_bytes + sizeof(zip->entry.header);
+
+  if (pzip->m_pWrite(pzip->m_pIO_opaque, zip->entry.offset, zip->entry.name,
+                     entrylen) != entrylen) {
+    // Cannot write data to zip entry
+    goto cleanup;
+  }
+
+  zip->entry.offset += entrylen;
+  level = zip->level & 0xF;
+  if (level) {
+    zip->entry.state.m_pZip = pzip;
+    zip->entry.state.m_cur_archive_file_ofs = zip->entry.offset;
+    zip->entry.state.m_comp_size = 0;
+
+    if (tdefl_init(&(zip->entry.comp), mz_zip_writer_add_put_buf_callback,
+                   &(zip->entry.state),
+                   (int)tdefl_create_comp_flags_from_zip_params(
+                       (int)level, -15, MZ_DEFAULT_STRATEGY)) !=
+        TDEFL_STATUS_OKAY) {
+      // Cannot initialize the zip compressor
+      goto cleanup;
+    }
+  }
+
+  zip->entry.m_time = time(NULL);
+
+  return 0;
 
-    zip->entry.header_offset += num_alignment_padding_bytes;
-    if (pzip->m_file_offset_alignment) {
-        MZ_ASSERT((zip->entry.header_offset &
-                   (pzip->m_file_offset_alignment - 1)) == 0);
-    }
-    zip->entry.offset +=
-        num_alignment_padding_bytes + sizeof(zip->entry.header);
+cleanup:
+  CLEANUP(zip->entry.name);
+  return -1;
+}
 
-    if (pzip->m_pWrite(pzip->m_pIO_opaque, zip->entry.offset, zip->entry.name,
-                       entrylen) != entrylen) {
-        // Cannot write data to zip entry
-        return -1;
-    }
+int zip_entry_openbyindex(struct zip_t *zip, int index) {
+  mz_zip_archive *pZip = NULL;
+  mz_zip_archive_file_stat stats;
+  mz_uint namelen;
+  const mz_uint8 *pHeader;
+  const char *pFilename;
+
+  if (!zip) {
+    // zip_t handler is not initialized
+    return -1;
+  }
+
+  pZip = &(zip->archive);
+  if (pZip->m_zip_mode != MZ_ZIP_MODE_READING) {
+    // open by index requires readonly mode
+    return -1;
+  }
+
+  if (index < 0 || (mz_uint)index >= pZip->m_total_files) {
+    // index out of range
+    return -1;
+  }
+
+  if (!(pHeader = &MZ_ZIP_ARRAY_ELEMENT(
+            &pZip->m_pState->m_central_dir, mz_uint8,
+            MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets,
+                                 mz_uint32, index)))) {
+    // cannot find header in central directory
+    return -1;
+  }
+
+  namelen = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_FILENAME_LEN_OFS);
+  pFilename = (const char *)pHeader + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE;
+
+  /*
+    .ZIP File Format Specification Version: 6.3.3
+
+    4.4.17.1 The name of the file, with optional relative path.
+    The path stored MUST not contain a drive or
+    device letter, or a leading slash.  All slashes
+    MUST be forward slashes '/' as opposed to
+    backwards slashes '\' for compatibility with Amiga
+    and UNIX file systems etc.  If input came from standard
+    input, there is no file name field.
+  */
+  zip->entry.name = strrpl(pFilename, namelen, '\\', '/');
+  if (!zip->entry.name) {
+    // local entry name is NULL
+    return -1;
+  }
+
+  if (!mz_zip_reader_file_stat(pZip, (mz_uint)index, &stats)) {
+    return -1;
+  }
+
+  zip->entry.index = index;
+  zip->entry.comp_size = stats.m_comp_size;
+  zip->entry.uncomp_size = stats.m_uncomp_size;
+  zip->entry.uncomp_crc32 = stats.m_crc32;
+  zip->entry.offset = stats.m_central_dir_ofs;
+  zip->entry.header_offset = stats.m_local_header_ofs;
+  zip->entry.method = stats.m_method;
+  zip->entry.external_attr = stats.m_external_attr;
+  zip->entry.m_time = stats.m_time;
+
+  return 0;
+}
 
-    zip->entry.offset += entrylen;
-    level = zip->level & 0xF;
-    if (level) {
-        zip->entry.state.m_pZip = pzip;
-        zip->entry.state.m_cur_archive_file_ofs = zip->entry.offset;
-        zip->entry.state.m_comp_size = 0;
-
-        if (tdefl_init(&(zip->entry.comp), mz_zip_writer_add_put_buf_callback,
-                       &(zip->entry.state),
-                       tdefl_create_comp_flags_from_zip_params(
-                           level, -15, MZ_DEFAULT_STRATEGY)) !=
-            TDEFL_STATUS_OKAY) {
-            // Cannot initialize the zip compressor
-            return -1;
-        }
-    }
+int zip_entry_close(struct zip_t *zip) {
+  mz_zip_archive *pzip = NULL;
+  mz_uint level;
+  tdefl_status done;
+  mz_uint16 entrylen;
+  mz_uint16 dos_time, dos_date;
+  int status = -1;
+
+  if (!zip) {
+    // zip_t handler is not initialized
+    goto cleanup;
+  }
+
+  pzip = &(zip->archive);
+  if (pzip->m_zip_mode == MZ_ZIP_MODE_READING) {
+    status = 0;
+    goto cleanup;
+  }
+
+  level = zip->level & 0xF;
+  if (level) {
+    done = tdefl_compress_buffer(&(zip->entry.comp), "", 0, TDEFL_FINISH);
+    if (done != TDEFL_STATUS_DONE && done != TDEFL_STATUS_OKAY) {
+      // Cannot flush compressed buffer
+      goto cleanup;
+    }
+    zip->entry.comp_size = zip->entry.state.m_comp_size;
+    zip->entry.offset = zip->entry.state.m_cur_archive_file_ofs;
+    zip->entry.method = MZ_DEFLATED;
+  }
+
+  entrylen = (mz_uint16)strlen(zip->entry.name);
+  // no zip64 support yet
+  if ((zip->entry.comp_size > 0xFFFFFFFF) || (zip->entry.offset > 0xFFFFFFFF)) {
+    // No zip64 support, yet
+    goto cleanup;
+  }
+
+  mz_zip_time_t_to_dos_time(zip->entry.m_time, &dos_time, &dos_date);
+  if (!mz_zip_writer_create_local_dir_header(
+          pzip, zip->entry.header, entrylen, 0, zip->entry.uncomp_size,
+          zip->entry.comp_size, zip->entry.uncomp_crc32, zip->entry.method, 0,
+          dos_time, dos_date)) {
+    // Cannot create zip entry header
+    goto cleanup;
+  }
+
+  if (pzip->m_pWrite(pzip->m_pIO_opaque, zip->entry.header_offset,
+                     zip->entry.header,
+                     sizeof(zip->entry.header)) != sizeof(zip->entry.header)) {
+    // Cannot write zip entry header
+    goto cleanup;
+  }
+
+  if (!mz_zip_writer_add_to_central_dir(
+          pzip, zip->entry.name, entrylen, NULL, 0, "", 0,
+          zip->entry.uncomp_size, zip->entry.comp_size, zip->entry.uncomp_crc32,
+          zip->entry.method, 0, dos_time, dos_date, zip->entry.header_offset,
+          zip->entry.external_attr)) {
+    // Cannot write to zip central dir
+    goto cleanup;
+  }
+
+  pzip->m_total_files++;
+  pzip->m_archive_size = zip->entry.offset;
+  status = 0;
 
-    return 0;
+cleanup:
+  if (zip) {
+    zip->entry.m_time = 0;
+    CLEANUP(zip->entry.name);
+  }
+  return status;
 }
 
-int zip_entry_close(struct zip_t *zip) {
-    mz_zip_archive *pzip = NULL;
-    mz_uint level;
-    tdefl_status done;
-    mz_uint16 entrylen;
-    time_t t;
-    struct tm *tm;
-    mz_uint16 dos_time, dos_date;
-    int status = -1;
-
-    if (!zip) {
-        // zip_t handler is not initialized
-        return -1;
-    }
+const char *zip_entry_name(struct zip_t *zip) {
+  if (!zip) {
+    // zip_t handler is not initialized
+    return NULL;
+  }
 
-    if (zip->mode == 'r') {
-        return 0;
-    }
+  return zip->entry.name;
+}
 
-    pzip = &(zip->archive);
-    level = zip->level & 0xF;
-    if (level) {
-        done = tdefl_compress_buffer(&(zip->entry.comp), "", 0, TDEFL_FINISH);
-        if (done != TDEFL_STATUS_DONE && done != TDEFL_STATUS_OKAY) {
-            // Cannot flush compressed buffer
-            goto cleanup;
-        }
-        zip->entry.comp_size = zip->entry.state.m_comp_size;
-        zip->entry.offset = zip->entry.state.m_cur_archive_file_ofs;
-        zip->entry.method = MZ_DEFLATED;
-    }
+int zip_entry_index(struct zip_t *zip) {
+  if (!zip) {
+    // zip_t handler is not initialized
+    return -1;
+  }
 
-    entrylen = (mz_uint16)strlen(zip->entry.name);
-    t = time(NULL);
-    tm = localtime(&t);
-    dos_time = (mz_uint16)(((tm->tm_hour) << 11) + ((tm->tm_min) << 5) +
-                           ((tm->tm_sec) >> 1));
-    dos_date = (mz_uint16)(((tm->tm_year + 1900 - 1980) << 9) +
-                           ((tm->tm_mon + 1) << 5) + tm->tm_mday);
-
-    // no zip64 support yet
-    if ((zip->entry.comp_size > 0xFFFFFFFF) ||
-        (zip->entry.offset > 0xFFFFFFFF)) {
-        // No zip64 support, yet
-        goto cleanup;
-    }
+  return zip->entry.index;
+}
 
-    if (!mz_zip_writer_create_local_dir_header(
-            pzip, zip->entry.header, entrylen, 0, zip->entry.uncomp_size,
-            zip->entry.comp_size, zip->entry.uncomp_crc32, zip->entry.method, 0,
-            dos_time, dos_date)) {
-        // Cannot create zip entry header
-        goto cleanup;
-    }
+int zip_entry_isdir(struct zip_t *zip) {
+  if (!zip) {
+    // zip_t handler is not initialized
+    return -1;
+  }
 
-    if (pzip->m_pWrite(pzip->m_pIO_opaque, zip->entry.header_offset,
-                       zip->entry.header, sizeof(zip->entry.header)) !=
-        sizeof(zip->entry.header)) {
-        // Cannot write zip entry header
-        goto cleanup;
-    }
+  if (zip->entry.index < 0) {
+    // zip entry is not opened
+    return -1;
+  }
 
-    if (!mz_zip_writer_add_to_central_dir(
-            pzip, zip->entry.name, entrylen, NULL, 0, "", 0,
-            zip->entry.uncomp_size, zip->entry.comp_size,
-            zip->entry.uncomp_crc32, zip->entry.method, 0, dos_time, dos_date,
-            zip->entry.header_offset, 0)) {
-        // Cannot write to zip central dir
-        goto cleanup;
-    }
+  return (int)mz_zip_reader_is_file_a_directory(&zip->archive,
+                                                (mz_uint)zip->entry.index);
+}
 
-    pzip->m_total_files++;
-    pzip->m_archive_size = zip->entry.offset;
-    status = 0;
+unsigned long long zip_entry_size(struct zip_t *zip) {
+  return zip ? zip->entry.uncomp_size : 0;
+}
 
-cleanup:
-    CLEANUP(zip->entry.name);
-    return status;
+unsigned int zip_entry_crc32(struct zip_t *zip) {
+  return zip ? zip->entry.uncomp_crc32 : 0;
 }
 
 int zip_entry_write(struct zip_t *zip, const void *buf, size_t bufsize) {
-    mz_uint level;
-    mz_zip_archive *pzip = NULL;
-    tdefl_status status;
+  mz_uint level;
+  mz_zip_archive *pzip = NULL;
+  tdefl_status status;
 
-    if (!zip) {
-        // zip_t handler is not initialized
-        return -1;
-    }
+  if (!zip) {
+    // zip_t handler is not initialized
+    return -1;
+  }
 
-    pzip = &(zip->archive);
-    if (buf && bufsize > 0) {
-        zip->entry.uncomp_size += bufsize;
-        zip->entry.uncomp_crc32 = (mz_uint32)mz_crc32(
-            zip->entry.uncomp_crc32, (const mz_uint8 *)buf, bufsize);
-
-        level = zip->level & 0xF;
-        if (!level) {
-            if ((pzip->m_pWrite(pzip->m_pIO_opaque, zip->entry.offset, buf,
-                                bufsize) != bufsize)) {
-                // Cannot write buffer
-                return -1;
-            }
-            zip->entry.offset += bufsize;
-            zip->entry.comp_size += bufsize;
-        } else {
-            status = tdefl_compress_buffer(&(zip->entry.comp), buf, bufsize,
-                                           TDEFL_NO_FLUSH);
-            if (status != TDEFL_STATUS_DONE && status != TDEFL_STATUS_OKAY) {
-                // Cannot compress buffer
-                return -1;
-            }
-        }
+  pzip = &(zip->archive);
+  if (buf && bufsize > 0) {
+    zip->entry.uncomp_size += bufsize;
+    zip->entry.uncomp_crc32 = (mz_uint32)mz_crc32(
+        zip->entry.uncomp_crc32, (const mz_uint8 *)buf, bufsize);
+
+    level = zip->level & 0xF;
+    if (!level) {
+      if ((pzip->m_pWrite(pzip->m_pIO_opaque, zip->entry.offset, buf,
+                          bufsize) != bufsize)) {
+        // Cannot write buffer
+        return -1;
+      }
+      zip->entry.offset += bufsize;
+      zip->entry.comp_size += bufsize;
+    } else {
+      status = tdefl_compress_buffer(&(zip->entry.comp), buf, bufsize,
+                                     TDEFL_NO_FLUSH);
+      if (status != TDEFL_STATUS_DONE && status != TDEFL_STATUS_OKAY) {
+        // Cannot compress buffer
+        return -1;
+      }
     }
+  }
 
-    return 0;
+  return 0;
 }
 
 int zip_entry_fwrite(struct zip_t *zip, const char *filename) {
-    int status = 0;
-    size_t n = 0;
-    FILE *stream = NULL;
-    mz_uint8 buf[MZ_ZIP_MAX_IO_BUF_SIZE] = {0};
-
-    if (!zip) {
-        // zip_t handler is not initialized
-        return -1;
-    }
+  int status = 0;
+  size_t n = 0;
+  FILE *stream = NULL;
+  mz_uint8 buf[MZ_ZIP_MAX_IO_BUF_SIZE];
+  struct MZ_FILE_STAT_STRUCT file_stat;
+
+  if (!zip) {
+    // zip_t handler is not initialized
+    return -1;
+  }
+
+  memset(buf, 0, MZ_ZIP_MAX_IO_BUF_SIZE);
+  memset((void *)&file_stat, 0, sizeof(struct MZ_FILE_STAT_STRUCT));
+  if (MZ_FILE_STAT(filename, &file_stat) != 0) {
+    // problem getting information - check errno
+    return -1;
+  }
+
+  if ((file_stat.st_mode & 0200) == 0) {
+    // MS-DOS read-only attribute
+    zip->entry.external_attr |= 0x01;
+  }
+  zip->entry.external_attr |= (mz_uint32)((file_stat.st_mode & 0xFFFF) << 16);
+  zip->entry.m_time = file_stat.st_mtime;
+
+#if defined(_MSC_VER)
+  if (fopen_s(&stream, filename, "rb"))
+#else
+  if (!(stream = fopen(filename, "rb")))
+#endif
+  {
+    // Cannot open filename
+    return -1;
+  }
 
-    stream = fopen(filename, "rb");
-    if (!stream) {
-        // Cannot open filename
-        return -1;
+  while ((n = fread(buf, sizeof(mz_uint8), MZ_ZIP_MAX_IO_BUF_SIZE, stream)) >
+         0) {
+    if (zip_entry_write(zip, buf, n) < 0) {
+      status = -1;
+      break;
     }
+  }
+  fclose(stream);
 
-    while ((n = fread(buf, sizeof(mz_uint8), MZ_ZIP_MAX_IO_BUF_SIZE, stream)) >
-           0) {
-        if (zip_entry_write(zip, buf, n) < 0) {
-            status = -1;
-            break;
-        }
-    }
-    fclose(stream);
+  return status;
+}
 
-    return status;
+ssize_t zip_entry_read(struct zip_t *zip, void **buf, size_t *bufsize) {
+  mz_zip_archive *pzip = NULL;
+  mz_uint idx;
+  size_t size = 0;
+
+  if (!zip) {
+    // zip_t handler is not initialized
+    return -1;
+  }
+
+  pzip = &(zip->archive);
+  if (pzip->m_zip_mode != MZ_ZIP_MODE_READING || zip->entry.index < 0) {
+    // the entry is not found or we do not have read access
+    return -1;
+  }
+
+  idx = (mz_uint)zip->entry.index;
+  if (mz_zip_reader_is_file_a_directory(pzip, idx)) {
+    // the entry is a directory
+    return -1;
+  }
+
+  *buf = mz_zip_reader_extract_to_heap(pzip, idx, &size, 0);
+  if (*buf && bufsize) {
+    *bufsize = size;
+  }
+  return size;
 }
 
-int zip_entry_read(struct zip_t *zip, void **buf, size_t *bufsize) {
-    mz_zip_archive *pzip = NULL;
-    mz_uint idx;
+ssize_t zip_entry_noallocread(struct zip_t *zip, void *buf, size_t bufsize) {
+  mz_zip_archive *pzip = NULL;
 
-    if (!zip) {
-        // zip_t handler is not initialized
-        return -1;
-    }
+  if (!zip) {
+    // zip_t handler is not initialized
+    return -1;
+  }
 
-    if (zip->mode != 'r' || zip->entry.index < 0) {
-        // the entry is not found or we do not have read access
-        return -1;
-    }
+  pzip = &(zip->archive);
+  if (pzip->m_zip_mode != MZ_ZIP_MODE_READING || zip->entry.index < 0) {
+    // the entry is not found or we do not have read access
+    return -1;
+  }
 
-    pzip = &(zip->archive);
-    idx = (mz_uint)zip->entry.index;
-    if (mz_zip_reader_is_file_a_directory(pzip, idx)) {
-        // the entry is a directory
-        return -1;
-    }
+  if (!mz_zip_reader_extract_to_mem_no_alloc(pzip, (mz_uint)zip->entry.index,
+  buf, bufsize, 0, NULL,  0)) {
+    return -1;
+  }
 
-    *buf = mz_zip_reader_extract_to_heap(pzip, idx, bufsize, 0);
-    return (*buf) ? 0 : -1;
+  return (ssize_t)zip->entry.uncomp_size;
 }
 
 int zip_entry_fread(struct zip_t *zip, const char *filename) {
-    mz_zip_archive *pzip = NULL;
-    mz_uint idx;
-
-    if (!zip) {
-        // zip_t handler is not initialized
-        return -1;
-    }
-
-    if (zip->mode != 'r' || zip->entry.index < 0) {
-        // the entry is not found or we do not have read access
-        return -1;
-    }
+  mz_zip_archive *pzip = NULL;
+  mz_uint idx;
+#if defined(_MSC_VER)
+#else
+  mz_uint32 xattr = 0;
+#endif
+  mz_zip_archive_file_stat info;
+
+  if (!zip) {
+    // zip_t handler is not initialized
+    return -1;
+  }
+
+  memset((void *)&info, 0, sizeof(mz_zip_archive_file_stat));
+  pzip = &(zip->archive);
+  if (pzip->m_zip_mode != MZ_ZIP_MODE_READING || zip->entry.index < 0) {
+    // the entry is not found or we do not have read access
+    return -1;
+  }
+
+  idx = (mz_uint)zip->entry.index;
+  if (mz_zip_reader_is_file_a_directory(pzip, idx)) {
+    // the entry is a directory
+    return -1;
+  }
+
+  if (!mz_zip_reader_extract_to_file(pzip, idx, filename, 0)) {
+    return -1;
+  }
+
+#if defined(_MSC_VER)
+#else
+  if (!mz_zip_reader_file_stat(pzip, idx, &info)) {
+    // Cannot get information about zip archive;
+    return -1;
+  }
 
-    pzip = &(zip->archive);
-    idx = (mz_uint)zip->entry.index;
-    if (mz_zip_reader_is_file_a_directory(pzip, idx)) {
-        // the entry is a directory
-        return -1;
+  xattr = (info.m_external_attr >> 16) & 0xFFFF;
+  if (xattr > 0) {
+    if (chmod(filename, (mode_t)xattr) < 0) {
+      return -1;
     }
+  }
+#endif
 
-    return (mz_zip_reader_extract_to_file(pzip, idx, filename, 0)) ? 0 : -1;
+  return 0;
 }
 
 int zip_entry_extract(struct zip_t *zip,
                       size_t (*on_extract)(void *arg, unsigned long long offset,
                                            const void *buf, size_t bufsize),
                       void *arg) {
-    mz_zip_archive *pzip = NULL;
-    mz_uint idx;
-
-    if (!zip) {
-        // zip_t handler is not initialized
-        return -1;
-    }
-
-    if (zip->mode != 'r' || zip->entry.index < 0) {
-        // the entry is not found or we do not have read access
-        return -1;
-    }
+  mz_zip_archive *pzip = NULL;
+  mz_uint idx;
+
+  if (!zip) {
+    // zip_t handler is not initialized
+    return -1;
+  }
+
+  pzip = &(zip->archive);
+  if (pzip->m_zip_mode != MZ_ZIP_MODE_READING || zip->entry.index < 0) {
+    // the entry is not found or we do not have read access
+    return -1;
+  }
+
+  idx = (mz_uint)zip->entry.index;
+  return (mz_zip_reader_extract_to_callback(pzip, idx, on_extract, arg, 0))
+             ? 0
+             : -1;
+}
 
-    pzip = &(zip->archive);
-    idx = (mz_uint)zip->entry.index;
+int zip_total_entries(struct zip_t *zip) {
+  if (!zip) {
+    // zip_t handler is not initialized
+    return -1;
+  }
 
-    return (mz_zip_reader_extract_to_callback(pzip, idx, on_extract, arg, 0))
-               ? 0
-               : -1;
+  return (int)zip->archive.m_total_files;
 }
 
 int zip_create(const char *zipname, const char *filenames[], size_t len) {
-    int status = 0;
-    size_t i;
-    mz_zip_archive zip_archive;
+  int status = 0;
+  size_t i;
+  mz_zip_archive zip_archive;
+  struct MZ_FILE_STAT_STRUCT file_stat;
+  mz_uint32 ext_attributes = 0;
 
-    if (!zipname || strlen(zipname) < 1) {
-        // zip_t archive name is empty or NULL
-        return -1;
-    }
+  if (!zipname || strlen(zipname) < 1) {
+    // zip_t archive name is empty or NULL
+    return -1;
+  }
 
-    // Create a new archive.
-    if (!memset(&(zip_archive), 0, sizeof(zip_archive))) {
-        // Cannot memset zip archive
-        return -1;
-    }
+  // Create a new archive.
+  if (!memset(&(zip_archive), 0, sizeof(zip_archive))) {
+    // Cannot memset zip archive
+    return -1;
+  }
 
-    if (!mz_zip_writer_init_file(&zip_archive, zipname, 0)) {
-        // Cannot initialize zip_archive writer
-        return -1;
-    }
+  if (!mz_zip_writer_init_file(&zip_archive, zipname, 0)) {
+    // Cannot initialize zip_archive writer
+    return -1;
+  }
 
-    for (i = 0; i < len; ++i) {
-        const char *name = filenames[i];
-        if (!name) {
-            status = -1;
-            break;
-        }
+  memset((void *)&file_stat, 0, sizeof(struct MZ_FILE_STAT_STRUCT));
 
-        if (!mz_zip_writer_add_file(&zip_archive, basename(name), name, "", 0,
-                                    ZIP_DEFAULT_COMPRESSION_LEVEL)) {
-            // Cannot add file to zip_archive
-            status = -1;
-            break;
-        }
+  for (i = 0; i < len; ++i) {
+    const char *name = filenames[i];
+    if (!name) {
+      status = -1;
+      break;
     }
 
-    mz_zip_writer_finalize_archive(&zip_archive);
-    mz_zip_writer_end(&zip_archive);
-    return status;
-}
-
-int zip_extract(const char *zipname, const char *dir,
-                int (*on_extract)(const char *filename, void *arg), void *arg) {
-    int status = -1;
-    mz_uint i, n;
-    char path[MAX_PATH + 1] = {0};
-    mz_zip_archive zip_archive;
-    mz_zip_archive_file_stat info;
-    size_t dirlen = 0;
-
-    if (!memset(&(zip_archive), 0, sizeof(zip_archive))) {
-        // Cannot memset zip archive
-        return -1;
+    if (MZ_FILE_STAT(name, &file_stat) != 0) {
+      // problem getting information - check errno
+      return -1;
     }
 
-    if (!zipname || !dir) {
-        // Cannot parse zip archive name
-        return -1;
+    if ((file_stat.st_mode & 0200) == 0) {
+      // MS-DOS read-only attribute
+      ext_attributes |= 0x01;
     }
+    ext_attributes |= (mz_uint32)((file_stat.st_mode & 0xFFFF) << 16);
 
-    dirlen = strlen(dir);
-    if (dirlen + 1 > MAX_PATH) {
-        return -1;
+    if (!mz_zip_writer_add_file(&zip_archive, base_name(name), name, "", 0,
+                                ZIP_DEFAULT_COMPRESSION_LEVEL,
+                                ext_attributes)) {
+      // Cannot add file to zip_archive
+      status = -1;
+      break;
     }
+  }
 
-    // Now try to open the archive.
-    if (!mz_zip_reader_init_file(&zip_archive, zipname, 0)) {
-        // Cannot initialize zip_archive reader
-        return -1;
-    }
+  mz_zip_writer_finalize_archive(&zip_archive);
+  mz_zip_writer_end(&zip_archive);
+  return status;
+}
 
-    strcpy(path, dir);
-    if (!ISSLASH(path[dirlen - 1])) {
-#if defined _WIN32 || defined __WIN32__
-        path[dirlen] = '\\';
+int zip_extract(const char *zipname, const char *dir,
+                int (*on_extract)(const char *filename, void *arg), void *arg) {
+  int status = -1;
+  mz_uint i, n;
+  char path[MAX_PATH + 1];
+  char symlink_to[MAX_PATH + 1];
+  mz_zip_archive zip_archive;
+  mz_zip_archive_file_stat info;
+  size_t dirlen = 0;
+  mz_uint32 xattr = 0;
+
+  memset(path, 0, sizeof(path));
+  memset(symlink_to, 0, sizeof(symlink_to));
+  if (!memset(&(zip_archive), 0, sizeof(zip_archive))) {
+    // Cannot memset zip archive
+    return -1;
+  }
+
+  if (!zipname || !dir) {
+    // Cannot parse zip archive name
+    return -1;
+  }
+
+  dirlen = strlen(dir);
+  if (dirlen + 1 > MAX_PATH) {
+    return -1;
+  }
+
+  // Now try to open the archive.
+  if (!mz_zip_reader_init_file(&zip_archive, zipname, 0)) {
+    // Cannot initialize zip_archive reader
+    return -1;
+  }
+
+  memset((void *)&info, 0, sizeof(mz_zip_archive_file_stat));
+
+#if defined(_MSC_VER)
+  strcpy_s(path, MAX_PATH, dir);
 #else
-        path[dirlen] = '/';
+  strcpy(path, dir);
 #endif
-        ++dirlen;
-    }
 
-    // Get and print information about each file in the archive.
-    n = mz_zip_reader_get_num_files(&zip_archive);
-    for (i = 0; i < n; ++i) {
-        if (!mz_zip_reader_file_stat(&zip_archive, i, &info)) {
-            // Cannot get information about zip archive;
-            goto out;
-        }
-        strncpy(&path[dirlen], info.m_filename, MAX_PATH - dirlen);
-        if (mkpath(path) < 0) {
-            // Cannot make a path
-            goto out;
+  if (!ISSLASH(path[dirlen - 1])) {
+#if defined(_WIN32) || defined(__WIN32__)
+    path[dirlen] = '\\';
+#else
+    path[dirlen] = '/';
+#endif
+    ++dirlen;
+  }
+
+  // Get and print information about each file in the archive.
+  n = mz_zip_reader_get_num_files(&zip_archive);
+  for (i = 0; i < n; ++i) {
+    if (!mz_zip_reader_file_stat(&zip_archive, i, &info)) {
+      // Cannot get information about zip archive;
+      goto out;
+    }
+#if defined(_MSC_VER)
+    strncpy_s(&path[dirlen], MAX_PATH - dirlen, info.m_filename,
+              MAX_PATH - dirlen);
+#else
+    strncpy(&path[dirlen], info.m_filename, MAX_PATH - dirlen);
+#endif
+    if (mkpath(path) < 0) {
+      // Cannot make a path
+      goto out;
+    }
+
+    if ((((info.m_version_made_by >> 8) == 3) || ((info.m_version_made_by >> 8) == 19)) // if zip is produced on Unix or macOS (3 and 19 from section 4.4.2.2 of zip standard)
+        && info.m_external_attr & (0x20 << 24)) { // and has sym link attribute (0x80 is file, 0x40 is directory)
+#if defined(_WIN32) || defined(__WIN32__) || defined(_MSC_VER) ||              \
+    defined(__MINGW32__)
+#else      
+      if (info.m_uncomp_size > MAX_PATH || !mz_zip_reader_extract_to_mem_no_alloc(&zip_archive, i, symlink_to, MAX_PATH, 0, NULL, 0)) {
+        goto out;
+      }
+      symlink_to[info.m_uncomp_size] = '\0';
+      if (symlink(symlink_to, path) != 0) {
+        goto out;
+      }
+#endif
+    } else {
+      if (!mz_zip_reader_is_file_a_directory(&zip_archive, i)) {
+        if (!mz_zip_reader_extract_to_file(&zip_archive, i, path, 0)) {
+          // Cannot extract zip archive to file
+          goto out;
         }
+      }
 
-        if (!mz_zip_reader_is_file_a_directory(&zip_archive, i)) {
-            if (!mz_zip_reader_extract_to_file(&zip_archive, i, path, 0)) {
-                // Cannot extract zip archive to file
-                goto out;
-            }
+#if defined(_MSC_VER)
+#else
+      xattr = (info.m_external_attr >> 16) & 0xFFFF;
+      if (xattr > 0) {
+        if (chmod(path, (mode_t)xattr) < 0) {
+          goto out;
         }
+      }
+#endif
+    }
 
-        if (on_extract) {
-            if (on_extract(path, arg) < 0) {
-                goto out;
-            }
-        }
+    if (on_extract) {
+      if (on_extract(path, arg) < 0) {
+        goto out;
+      }
     }
-    status = 0;
+  }
+  status = 0;
 
 out:
-    // Close the archive, freeing any resources it was using
-    if (!mz_zip_reader_end(&zip_archive)) {
-        // Cannot end zip reader
-        status = -1;
-    }
+  // Close the archive, freeing any resources it was using
+  if (!mz_zip_reader_end(&zip_archive)) {
+    // Cannot end zip reader
+    status = -1;
+  }
 
-    return status;
+  return status;
 }

+ 135 - 12
contrib/zip/src/zip.h

@@ -13,11 +13,24 @@
 #define ZIP_H
 
 #include <string.h>
+#include <sys/types.h>
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
+#if !defined(_SSIZE_T_DEFINED) && !defined(_SSIZE_T_DEFINED_) &&               \
+    !defined(_SSIZE_T) && !defined(_SSIZE_T_) && !defined(__ssize_t_defined)
+#define _SSIZE_T
+// 64-bit Windows is the only mainstream platform
+// where sizeof(long) != sizeof(void*)
+#ifdef _WIN64
+typedef long long  ssize_t;  /* byte count or error */
+#else
+typedef long  ssize_t;  /* byte count or error */
+#endif
+#endif
+
 #ifndef MAX_PATH
 #define MAX_PATH 32767 /* # chars in a path name including NULL */
 #endif
@@ -47,7 +60,7 @@ struct zip_t;
 extern struct zip_t *zip_open(const char *zipname, int level, char mode);
 
 /*
-  Closes zip archive, releases resources - always finalize.
+  Closes the zip archive, releases resources - always finalize.
 
   Args:
     zip: zip archive handler.
@@ -55,7 +68,10 @@ extern struct zip_t *zip_open(const char *zipname, int level, char mode);
 extern void zip_close(struct zip_t *zip);
 
 /*
-  Opens a new entry for writing in a zip archive.
+  Opens an entry by name in the zip archive.
+  For zip archive opened in 'w' or 'a' mode the function will append
+  a new entry. In readonly mode the function tries to locate the entry
+  in global dictionary.
 
   Args:
     zip: zip archive handler.
@@ -66,6 +82,19 @@ extern void zip_close(struct zip_t *zip);
 */
 extern int zip_entry_open(struct zip_t *zip, const char *entryname);
 
+/*
+  Opens a new entry by index in the zip archive.
+  This function is only valid if zip archive was opened in 'r' (readonly) mode.
+
+  Args:
+    zip: zip archive handler.
+    index: index in local dictionary.
+
+  Returns:
+    The return code - 0 on success, negative number (< 0) on error.
+*/
+extern int zip_entry_openbyindex(struct zip_t *zip, int index);
+
 /*
   Closes a zip entry, flushes buffer and releases resources.
 
@@ -77,6 +106,67 @@ extern int zip_entry_open(struct zip_t *zip, const char *entryname);
 */
 extern int zip_entry_close(struct zip_t *zip);
 
+/*
+  Returns a local name of the current zip entry.
+  The main difference between user's entry name and local entry name
+  is optional relative path.
+  Following .ZIP File Format Specification - the path stored MUST not contain
+  a drive or device letter, or a leading slash.
+  All slashes MUST be forward slashes '/' as opposed to backwards slashes '\'
+  for compatibility with Amiga and UNIX file systems etc.
+
+  Args:
+    zip: zip archive handler.
+
+  Returns:
+    The pointer to the current zip entry name, or NULL on error.
+*/
+extern const char *zip_entry_name(struct zip_t *zip);
+
+/*
+  Returns an index of the current zip entry.
+
+  Args:
+    zip: zip archive handler.
+
+  Returns:
+    The index on success, negative number (< 0) on error.
+*/
+extern int zip_entry_index(struct zip_t *zip);
+
+/*
+  Determines if the current zip entry is a directory entry.
+
+  Args:
+    zip: zip archive handler.
+
+  Returns:
+    The return code - 1 (true), 0 (false), negative number (< 0) on error.
+*/
+extern int zip_entry_isdir(struct zip_t *zip);
+
+/*
+  Returns an uncompressed size of the current zip entry.
+
+  Args:
+    zip: zip archive handler.
+
+  Returns:
+    The uncompressed size in bytes.
+*/
+extern unsigned long long zip_entry_size(struct zip_t *zip);
+
+/*
+  Returns CRC-32 checksum of the current zip entry.
+
+  Args:
+    zip: zip archive handler.
+
+  Returns:
+    The CRC-32 checksum.
+*/
+extern unsigned int zip_entry_crc32(struct zip_t *zip);
+
 /*
   Compresses an input buffer for the current zip entry.
 
@@ -116,9 +206,31 @@ extern int zip_entry_fwrite(struct zip_t *zip, const char *filename);
     - for large entries, please take a look at zip_entry_extract function.
 
   Returns:
-    The return code - 0 on success, negative number (< 0) on error.
+    The return code - the number of bytes actually read on success.
+    Otherwise a -1 on error.
 */
-extern int zip_entry_read(struct zip_t *zip, void **buf, size_t *bufsize);
+extern ssize_t zip_entry_read(struct zip_t *zip, void **buf, size_t *bufsize);
+
+/*
+  Extracts the current zip entry into a memory buffer using no memory
+  allocation.
+
+  Args:
+    zip: zip archive handler.
+    buf: preallocated output buffer.
+    bufsize: output buffer size (in bytes).
+
+  Note:
+    - ensure supplied output buffer is large enough.
+    - zip_entry_size function (returns uncompressed size for the current entry)
+      can be handy to estimate how big buffer is needed.
+    - for large entries, please take a look at zip_entry_extract function.
+
+  Returns:
+    The return code - the number of bytes actually read on success.
+    Otherwise a -1 on error (e.g. bufsize is not large enough).
+*/
+extern ssize_t zip_entry_noallocread(struct zip_t *zip, void *buf, size_t bufsize);
 
 /*
   Extracts the current zip entry into output file.
@@ -133,9 +245,9 @@ extern int zip_entry_read(struct zip_t *zip, void **buf, size_t *bufsize);
 extern int zip_entry_fread(struct zip_t *zip, const char *filename);
 
 /*
-   Extract the current zip entry using a callback function (on_extract).
+  Extracts the current zip entry using a callback function (on_extract).
 
-   Args:
+  Args:
     zip: zip archive handler.
     on_extract: callback function.
     arg: opaque pointer (optional argument,
@@ -144,12 +256,23 @@ extern int zip_entry_fread(struct zip_t *zip, const char *filename);
    Returns:
     The return code - 0 on success, negative number (< 0) on error.
  */
-extern int zip_entry_extract(struct zip_t *zip,
-                             size_t (*on_extract)(void *arg,
-                                                  unsigned long long offset,
-                                                  const void *data,
-                                                  size_t size),
-                             void *arg);
+extern int
+zip_entry_extract(struct zip_t *zip,
+                  size_t (*on_extract)(void *arg, unsigned long long offset,
+                                       const void *data, size_t size),
+                  void *arg);
+
+/*
+  Returns the number of all entries (files and directories) in the zip archive.
+
+  Args:
+    zip: zip archive handler.
+
+  Returns:
+    The return code - the number of entries on success,
+    negative number (< 0) on error.
+*/
+extern int zip_total_entries(struct zip_t *zip);
 
 /*
   Creates a new archive and puts files into a single zip archive.

+ 12 - 0
contrib/zip/test/CMakeLists.txt

@@ -1,7 +1,19 @@
 cmake_minimum_required(VERSION 2.8)
 
+if ("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_C_COMPILER_ID}" STREQUAL "AppleClang")
+  if(ENABLE_COVERAGE)
+    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g ")
+    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O0")
+    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fprofile-arcs")
+    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ftest-coverage")
+    set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage")
+  endif()
+endif ()
+
 # test
 include_directories(../src)
 add_executable(test.exe test.c ../src/zip.c)
+add_executable(test_miniz.exe test_miniz.c)
 
 add_test(NAME test COMMAND test.exe)
+add_test(NAME test_miniz COMMAND test_miniz.exe)

+ 415 - 61
contrib/zip/test/test.c

@@ -4,102 +4,456 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/stat.h>
 
-#define ZIPNAME "test.zip"
+#if defined(_MSC_VER) || defined(__MINGW64__) || defined(__MINGW32__)
+#define MZ_FILE_STAT_STRUCT _stat
+#define MZ_FILE_STAT _stat
+#else
+#define MZ_FILE_STAT_STRUCT stat
+#define MZ_FILE_STAT stat
+#endif
+
+#define ZIPNAME "test.zip\0"
 #define TESTDATA1 "Some test data 1...\0"
+#define CRC32DATA1 2220805626
 #define TESTDATA2 "Some test data 2...\0"
+#define CRC32DATA2 2532008468
+
+#define RFILE "4.txt\0"
+#define RMODE 0100444
+
+#define WFILE "6.txt\0"
+#define WMODE 0100666
+
+#define XFILE "7.txt\0"
+#define XMODE 0100777
+
+#define UNUSED(x) (void)x
+
+static int total_entries = 0;
 
 static void test_write(void) {
-    struct zip_t *zip = zip_open(ZIPNAME, ZIP_DEFAULT_COMPRESSION_LEVEL, 'w');
-    assert(zip != NULL);
+  struct zip_t *zip = zip_open(ZIPNAME, ZIP_DEFAULT_COMPRESSION_LEVEL, 'w');
+  assert(zip != NULL);
 
-    assert(0 == zip_entry_open(zip, "test/test-1.txt"));
-    assert(0 == zip_entry_write(zip, TESTDATA1, strlen(TESTDATA1)));
-    assert(0 == zip_entry_close(zip));
+  assert(0 == zip_entry_open(zip, "test/test-1.txt"));
+  assert(0 == zip_entry_write(zip, TESTDATA1, strlen(TESTDATA1)));
+  assert(0 == strcmp(zip_entry_name(zip), "test/test-1.txt"));
+  assert(total_entries == zip_entry_index(zip));
+  assert(strlen(TESTDATA1) == zip_entry_size(zip));
+  assert(CRC32DATA1 == zip_entry_crc32(zip));
+  ++total_entries;
+  assert(0 == zip_entry_close(zip));
 
-    zip_close(zip);
+  zip_close(zip);
 }
 
 static void test_append(void) {
-    struct zip_t *zip = zip_open(ZIPNAME, ZIP_DEFAULT_COMPRESSION_LEVEL, 'a');
-    assert(zip != NULL);
+  struct zip_t *zip = zip_open(ZIPNAME, ZIP_DEFAULT_COMPRESSION_LEVEL, 'a');
+  assert(zip != NULL);
 
-    assert(0 == zip_entry_open(zip, "test\\test-2.txt"));
-    assert(0 == zip_entry_write(zip, TESTDATA2, strlen(TESTDATA2)));
-    assert(0 == zip_entry_close(zip));
+  assert(0 == zip_entry_open(zip, "test\\test-2.txt"));
+  assert(0 == strcmp(zip_entry_name(zip), "test/test-2.txt"));
+  assert(total_entries == zip_entry_index(zip));
+  assert(0 == zip_entry_write(zip, TESTDATA2, strlen(TESTDATA2)));
+  assert(strlen(TESTDATA2) == zip_entry_size(zip));
+  assert(CRC32DATA2 == zip_entry_crc32(zip));
+
+  ++total_entries;
+  assert(0 == zip_entry_close(zip));
+
+  assert(0 == zip_entry_open(zip, "test\\empty/"));
+  assert(0 == strcmp(zip_entry_name(zip), "test/empty/"));
+  assert(0 == zip_entry_size(zip));
+  assert(0 == zip_entry_crc32(zip));
+
+  assert(total_entries == zip_entry_index(zip));
+  ++total_entries;
+  assert(0 == zip_entry_close(zip));
 
-    zip_close(zip);
+  assert(0 == zip_entry_open(zip, "empty/"));
+  assert(0 == strcmp(zip_entry_name(zip), "empty/"));
+  assert(0 == zip_entry_size(zip));
+  assert(0 == zip_entry_crc32(zip));
+
+  assert(total_entries == zip_entry_index(zip));
+  ++total_entries;
+  assert(0 == zip_entry_close(zip));
+
+  zip_close(zip);
 }
 
 static void test_read(void) {
-    char *buf = NULL;
-    size_t bufsize;
-    struct zip_t *zip = zip_open(ZIPNAME, 0, 'r');
-    assert(zip != NULL);
-
-    assert(0 == zip_entry_open(zip, "test\\test-1.txt"));
-    assert(0 == zip_entry_read(zip, (void **)&buf, &bufsize));
-    assert(bufsize == strlen(TESTDATA1));
-    assert(0 == strncmp(buf, TESTDATA1, bufsize));
-    assert(0 == zip_entry_close(zip));
-    free(buf);
-    buf = NULL;
-    bufsize = 0;
-
-    assert(0 == zip_entry_open(zip, "test/test-2.txt"));
-    assert(0 == zip_entry_read(zip, (void **)&buf, &bufsize));
-    assert(bufsize == strlen(TESTDATA2));
-    assert(0 == strncmp(buf, TESTDATA2, bufsize));
-    assert(0 == zip_entry_close(zip));
-    free(buf);
-    buf = NULL;
-    bufsize = 0;
+  char *buf = NULL;
+  ssize_t bufsize;
+  size_t buftmp;
+  struct zip_t *zip = zip_open(ZIPNAME, 0, 'r');
+  assert(zip != NULL);
+
+  assert(0 == zip_entry_open(zip, "test\\test-1.txt"));
+  assert(strlen(TESTDATA1) == zip_entry_size(zip));
+  assert(CRC32DATA1 == zip_entry_crc32(zip));
+
+  bufsize = zip_entry_read(zip, (void **)&buf, &buftmp);
+  assert(bufsize == strlen(TESTDATA1));
+  assert((size_t)bufsize == buftmp);
+  assert(0 == strncmp(buf, TESTDATA1, bufsize));
+  assert(0 == zip_entry_close(zip));
+  free(buf);
+  buf = NULL;
+  
+  assert(0 == zip_entry_open(zip, "test/test-2.txt"));
+  assert(strlen(TESTDATA2) == zip_entry_size(zip));
+  assert(CRC32DATA2 == zip_entry_crc32(zip));
+
+  bufsize = zip_entry_read(zip, (void **)&buf, NULL);
+  assert((size_t)bufsize == strlen(TESTDATA2));
+  assert(0 == strncmp(buf, TESTDATA2, (size_t)bufsize));
+  assert(0 == zip_entry_close(zip));
+  free(buf);
+  buf = NULL;
+  bufsize = 0;
+
+  assert(0 == zip_entry_open(zip, "test\\empty/"));
+  assert(0 == strcmp(zip_entry_name(zip), "test/empty/"));
+  assert(0 == zip_entry_size(zip));
+  assert(0 == zip_entry_crc32(zip));
+  assert(0 == zip_entry_close(zip));
+
+  buftmp = strlen(TESTDATA2);
+  buf = calloc(buftmp, sizeof(char));
+  assert(0 == zip_entry_open(zip, "test/test-2.txt"));
+
+  bufsize = zip_entry_noallocread(zip, (void *)buf, buftmp);
+  assert(buftmp == (size_t)bufsize);
+  assert(0 == strncmp(buf, TESTDATA2, buftmp));
+  assert(0 == zip_entry_close(zip));
+  free(buf);
+  buf = NULL;
+  
+  buftmp = strlen(TESTDATA1);
+  buf = calloc(buftmp, sizeof(char));
+  assert(0 == zip_entry_open(zip, "test/test-1.txt"));
+
+  bufsize = zip_entry_noallocread(zip, (void *)buf, buftmp);
+  assert(buftmp == (size_t)bufsize);
+  assert(0 == strncmp(buf, TESTDATA1, buftmp));
+  assert(0 == zip_entry_close(zip));
+  free(buf);
+  buf = NULL;
+  bufsize = 0;
 
-    zip_close(zip);
+  zip_close(zip);
 }
 
 struct buffer_t {
-    char *data;
-    size_t size;
+  char *data;
+  size_t size;
 };
 
 static size_t on_extract(void *arg, unsigned long long offset, const void *data,
                          size_t size) {
-    struct buffer_t *buf = (struct buffer_t *)arg;
-    buf->data = realloc(buf->data, buf->size + size + 1);
-    assert(NULL != buf->data);
+  UNUSED(offset);
 
-    memcpy(&(buf->data[buf->size]), data, size);
-    buf->size += size;
-    buf->data[buf->size] = 0;
+  struct buffer_t *buf = (struct buffer_t *)arg;
+  buf->data = realloc(buf->data, buf->size + size + 1);
+  assert(NULL != buf->data);
 
-    return size;
+  memcpy(&(buf->data[buf->size]), data, size);
+  buf->size += size;
+  buf->data[buf->size] = 0;
+
+  return size;
 }
 
 static void test_extract(void) {
-    struct buffer_t buf = {0};
+  struct buffer_t buf;
+
+  struct zip_t *zip = zip_open(ZIPNAME, 0, 'r');
+  assert(zip != NULL);
+  memset((void *)&buf, 0, sizeof(struct buffer_t));
+
+  assert(0 == zip_entry_open(zip, "test/test-1.txt"));
+  assert(0 == zip_entry_extract(zip, on_extract, &buf));
+
+  assert(buf.size == strlen(TESTDATA1));
+  assert(0 == strncmp(buf.data, TESTDATA1, buf.size));
+  assert(0 == zip_entry_close(zip));
+  free(buf.data);
+  buf.data = NULL;
+  buf.size = 0;
+
+  zip_close(zip);
+}
+
+static void test_total_entries(void) {
+  struct zip_t *zip = zip_open(ZIPNAME, 0, 'r');
+  assert(zip != NULL);
+
+  int n = zip_total_entries(zip);
+  zip_close(zip);
+
+  assert(n == total_entries);
+}
+
+static void test_entry_name(void) {
+  struct zip_t *zip = zip_open(ZIPNAME, 0, 'r');
+  assert(zip != NULL);
+
+  assert(zip_entry_name(zip) == NULL);
+
+  assert(0 == zip_entry_open(zip, "test\\test-1.txt"));
+  assert(NULL != zip_entry_name(zip));
+  assert(0 == strcmp(zip_entry_name(zip), "test/test-1.txt"));
+  assert(strlen(TESTDATA1) == zip_entry_size(zip));
+  assert(CRC32DATA1 == zip_entry_crc32(zip));
+  assert(0 == zip_entry_index(zip));
+
+  assert(0 == zip_entry_close(zip));
+
+  assert(0 == zip_entry_open(zip, "test/test-2.txt"));
+  assert(NULL != zip_entry_name(zip));
+  assert(0 == strcmp(zip_entry_name(zip), "test/test-2.txt"));
+  assert(strlen(TESTDATA2) == zip_entry_size(zip));
+  assert(CRC32DATA2 == zip_entry_crc32(zip));
+  assert(1 == zip_entry_index(zip));
+
+  assert(0 == zip_entry_close(zip));
+
+  zip_close(zip);
+}
+
+static void test_entry_index(void) {
+  struct zip_t *zip = zip_open(ZIPNAME, 0, 'r');
+  assert(zip != NULL);
+
+  assert(0 == zip_entry_open(zip, "test\\test-1.txt"));
+  assert(0 == zip_entry_index(zip));
+  assert(0 == strcmp(zip_entry_name(zip), "test/test-1.txt"));
+  assert(strlen(TESTDATA1) == zip_entry_size(zip));
+  assert(CRC32DATA1 == zip_entry_crc32(zip));
+  assert(0 == zip_entry_close(zip));
+
+  assert(0 == zip_entry_open(zip, "test/test-2.txt"));
+  assert(1 == zip_entry_index(zip));
+  assert(0 == strcmp(zip_entry_name(zip), "test/test-2.txt"));
+  assert(strlen(TESTDATA2) == zip_entry_size(zip));
+  assert(CRC32DATA2 == zip_entry_crc32(zip));
+  assert(0 == zip_entry_close(zip));
+
+  zip_close(zip);
+}
+
+static void test_entry_openbyindex(void) {
+  struct zip_t *zip = zip_open(ZIPNAME, 0, 'r');
+  assert(zip != NULL);
+
+  assert(0 == zip_entry_openbyindex(zip, 1));
+  assert(1 == zip_entry_index(zip));
+  assert(strlen(TESTDATA2) == zip_entry_size(zip));
+  assert(CRC32DATA2 == zip_entry_crc32(zip));
+  assert(0 == strcmp(zip_entry_name(zip), "test/test-2.txt"));
+  assert(0 == zip_entry_close(zip));
 
-    struct zip_t *zip = zip_open(ZIPNAME, 0, 'r');
-    assert(zip != NULL);
+  assert(0 == zip_entry_openbyindex(zip, 0));
+  assert(0 == zip_entry_index(zip));
+  assert(strlen(TESTDATA1) == zip_entry_size(zip));
+  assert(CRC32DATA1 == zip_entry_crc32(zip));
+  assert(0 == strcmp(zip_entry_name(zip), "test/test-1.txt"));
+  assert(0 == zip_entry_close(zip));
 
-    assert(0 == zip_entry_open(zip, "test/test-1.txt"));
-    assert(0 == zip_entry_extract(zip, on_extract, &buf));
+  zip_close(zip);
+}
+
+static void test_list_entries(void) {
+  struct zip_t *zip = zip_open(ZIPNAME, 0, 'r');
+  assert(zip != NULL);
 
-    assert(buf.size == strlen(TESTDATA1));
-    assert(0 == strncmp(buf.data, TESTDATA1, buf.size));
+  int i = 0, n = zip_total_entries(zip);
+  for (; i < n; ++i) {
+    assert(0 == zip_entry_openbyindex(zip, i));
+    fprintf(stdout, "[%d]: %s", i, zip_entry_name(zip));
+    if (zip_entry_isdir(zip)) {
+      fprintf(stdout, " (DIR)");
+    }
+    fprintf(stdout, "\n");
     assert(0 == zip_entry_close(zip));
-    free(buf.data);
-    buf.data = NULL;
-    buf.size = 0;
+  }
+
+  zip_close(zip);
+}
+
+static void test_fwrite(void) {
+  const char *filename = WFILE;
+  FILE *stream = NULL;
+  struct zip_t *zip = NULL;
+#if defined(_MSC_VER)
+  if (0 != fopen_s(&stream, filename, "w+"))
+#else
+  if (!(stream = fopen(filename, "w+")))
+#endif
+  {
+    // Cannot open filename
+    fprintf(stdout, "Cannot open filename\n");
+    assert(0 == -1);
+  }
+  fwrite(TESTDATA1, sizeof(char), strlen(TESTDATA1), stream);
+  assert(0 == fclose(stream));
+
+  zip = zip_open(ZIPNAME, 9, 'w');
+  assert(zip != NULL);
+  assert(0 == zip_entry_open(zip, WFILE));
+  assert(0 == zip_entry_fwrite(zip, WFILE));
+  assert(0 == zip_entry_close(zip));
+
+  zip_close(zip);
+  remove(WFILE);
+  remove(ZIPNAME);
+}
+
+static void test_exe_permissions(void) {
+#if defined(_WIN32) || defined(__WIN32__)
+#else
+  struct MZ_FILE_STAT_STRUCT file_stats;
+  const char *filenames[] = {XFILE};
+  FILE *f = fopen(XFILE, "w");
+  fclose(f);
+  chmod(XFILE, XMODE);
+
+  remove(ZIPNAME);
+
+  assert(0 == zip_create(ZIPNAME, filenames, 1));
+
+  remove(XFILE);
+
+  assert(0 == zip_extract(ZIPNAME, ".", NULL, NULL));
+
+  assert(0 == MZ_FILE_STAT(XFILE, &file_stats));
+  assert(XMODE == file_stats.st_mode);
+
+  remove(XFILE);
+  remove(ZIPNAME);
+#endif
+}
+
+static void test_read_permissions(void) {
+#if defined(_MSC_VER)
+#else
+
+  struct MZ_FILE_STAT_STRUCT file_stats;
+  const char *filenames[] = {RFILE};
+  FILE *f = fopen(RFILE, "w");
+  fclose(f);
+  chmod(RFILE, RMODE);
+
+  remove(ZIPNAME);
 
-    zip_close(zip);
+  assert(0 == zip_create(ZIPNAME, filenames, 1));
+
+  // chmod from 444 to 666 to be able delete the file on windows
+  chmod(RFILE, WMODE);
+  remove(RFILE);
+
+  assert(0 == zip_extract(ZIPNAME, ".", NULL, NULL));
+
+  assert(0 == MZ_FILE_STAT(RFILE, &file_stats));
+  assert(RMODE == file_stats.st_mode);
+
+  chmod(RFILE, WMODE);
+  remove(RFILE);
+  remove(ZIPNAME);
+#endif
+}
+
+static void test_write_permissions(void) {
+#if defined(_MSC_VER)
+#else
+
+  struct MZ_FILE_STAT_STRUCT file_stats;
+  const char *filenames[] = {WFILE};
+  FILE *f = fopen(WFILE, "w");
+  fclose(f);
+  chmod(WFILE, WMODE);
+
+  remove(ZIPNAME);
+
+  assert(0 == zip_create(ZIPNAME, filenames, 1));
+
+  remove(WFILE);
+
+  assert(0 == zip_extract(ZIPNAME, ".", NULL, NULL));
+
+  assert(0 == MZ_FILE_STAT(WFILE, &file_stats));
+  assert(WMODE == file_stats.st_mode);
+
+  remove(WFILE);
+  remove(ZIPNAME);
+#endif
+}
+
+static void test_mtime(void) {
+  struct MZ_FILE_STAT_STRUCT file_stat1, file_stat2;
+
+  const char *filename = WFILE;
+  FILE *stream = NULL;
+  struct zip_t *zip = NULL;
+#if defined(_MSC_VER)
+  if (0 != fopen_s(&stream, filename, "w+"))
+#else
+  if (!(stream = fopen(filename, "w+")))
+#endif
+  {
+    // Cannot open filename
+    fprintf(stdout, "Cannot open filename\n");
+    assert(0 == -1);
+  }
+  fwrite(TESTDATA1, sizeof(char), strlen(TESTDATA1), stream);
+  assert(0 == fclose(stream));
+
+  memset(&file_stat1, 0, sizeof(file_stat1));
+  memset(&file_stat2, 0, sizeof(file_stat2));
+  zip = zip_open(ZIPNAME, ZIP_DEFAULT_COMPRESSION_LEVEL, 'w');
+  assert(zip != NULL);
+  assert(0 == zip_entry_open(zip, filename));
+  assert(0 == zip_entry_fwrite(zip, filename));
+  assert(0 == zip_entry_close(zip));
+  zip_close(zip);
+
+  assert(0 == MZ_FILE_STAT(filename, &file_stat1));
+
+  remove(filename);
+  assert(0 == zip_extract(ZIPNAME, ".", NULL, NULL));
+  assert(0 == MZ_FILE_STAT(filename, &file_stat2));
+  fprintf(stdout, "file_stat1.st_mtime: %lu\n", file_stat1.st_mtime);
+  fprintf(stdout, "file_stat2.st_mtime: %lu\n", file_stat2.st_mtime);
+  assert(labs(file_stat1.st_mtime - file_stat2.st_mtime) <= 1);
+
+  remove(filename);
+  remove(ZIPNAME);
 }
 
 int main(int argc, char *argv[]) {
-    test_write();
-    test_append();
-    test_read();
-    test_extract();
+  UNUSED(argc);
+  UNUSED(argv);
+
+  remove(ZIPNAME);
+
+  test_write();
+  test_append();
+  test_read();
+  test_extract();
+  test_total_entries();
+  test_entry_name();
+  test_entry_index();
+  test_entry_openbyindex();
+  test_list_entries();
+  test_fwrite();
+  test_read_permissions();
+  test_write_permissions();
+  test_exe_permissions();
+  test_mtime();
 
-    return remove(ZIPNAME);
+  remove(ZIPNAME);
+  return 0;
 }

+ 104 - 0
contrib/zip/test/test_miniz.c

@@ -0,0 +1,104 @@
+// Demonstrates miniz.c's compress() and uncompress() functions
+// (same as zlib's). Public domain, May 15 2011, Rich Geldreich,
+// [email protected]. See "unlicense" statement at the end of tinfl.c.
+
+#include <miniz.h>
+#include <stdio.h>
+
+typedef unsigned char uint8;
+typedef unsigned short uint16;
+typedef unsigned int uint;
+
+// The string to compress.
+static const char *s_pStr =
+    "Good morning Dr. Chandra. This is Hal. I am ready for my first lesson."
+    "Good morning Dr. Chandra. This is Hal. I am ready for my first lesson."
+    "Good morning Dr. Chandra. This is Hal. I am ready for my first lesson."
+    "Good morning Dr. Chandra. This is Hal. I am ready for my first lesson."
+    "Good morning Dr. Chandra. This is Hal. I am ready for my first lesson."
+    "Good morning Dr. Chandra. This is Hal. I am ready for my first lesson."
+    "Good morning Dr. Chandra. This is Hal. I am ready for my first lesson.";
+
+int main(int argc, char *argv[]) {
+  uint step = 0;
+  int cmp_status;
+  uLong src_len = (uLong)strlen(s_pStr);
+  uLong cmp_len = compressBound(src_len);
+  uLong uncomp_len = src_len;
+  uint8 *pCmp, *pUncomp;
+  uint total_succeeded = 0;
+  (void)argc, (void)argv;
+
+  printf("miniz.c version: %s\n", MZ_VERSION);
+
+  do {
+    // Allocate buffers to hold compressed and uncompressed data.
+    pCmp = (mz_uint8 *)malloc((size_t)cmp_len);
+    pUncomp = (mz_uint8 *)malloc((size_t)src_len);
+    if ((!pCmp) || (!pUncomp)) {
+      printf("Out of memory!\n");
+      return EXIT_FAILURE;
+    }
+
+    // Compress the string.
+    cmp_status =
+        compress(pCmp, &cmp_len, (const unsigned char *)s_pStr, src_len);
+    if (cmp_status != Z_OK) {
+      printf("compress() failed!\n");
+      free(pCmp);
+      free(pUncomp);
+      return EXIT_FAILURE;
+    }
+
+    printf("Compressed from %u to %u bytes\n", (mz_uint32)src_len,
+           (mz_uint32)cmp_len);
+
+    if (step) {
+      // Purposely corrupt the compressed data if fuzzy testing (this is a
+      // very crude fuzzy test).
+      uint n = 1 + (rand() % 3);
+      while (n--) {
+        uint i = rand() % cmp_len;
+        pCmp[i] ^= (rand() & 0xFF);
+      }
+    }
+
+    // Decompress.
+    cmp_status = uncompress(pUncomp, &uncomp_len, pCmp, cmp_len);
+    total_succeeded += (cmp_status == Z_OK);
+
+    if (step) {
+      printf("Simple fuzzy test: step %u total_succeeded: %u\n", step,
+             total_succeeded);
+    } else {
+      if (cmp_status != Z_OK) {
+        printf("uncompress failed!\n");
+        free(pCmp);
+        free(pUncomp);
+        return EXIT_FAILURE;
+      }
+
+      printf("Decompressed from %u to %u bytes\n", (mz_uint32)cmp_len,
+             (mz_uint32)uncomp_len);
+
+      // Ensure uncompress() returned the expected data.
+      if ((uncomp_len != src_len) ||
+          (memcmp(pUncomp, s_pStr, (size_t)src_len))) {
+        printf("Decompression failed!\n");
+        free(pCmp);
+        free(pUncomp);
+        return EXIT_FAILURE;
+      }
+    }
+
+    free(pCmp);
+    free(pUncomp);
+
+    step++;
+
+    // Keep on fuzzy testing if there's a non-empty command line.
+  } while (argc >= 2);
+
+  printf("Success.\n");
+  return EXIT_SUCCESS;
+}

+ 12 - 0
doc/dox.h

@@ -1173,6 +1173,18 @@ float4 PimpMyPixel (float4 prev)
 
 @endcode
 
+@section shdacc How to access shader-code from a texture (AI_MATKEY_GLOBAL_SHADERLANG and AI_MATKEY_SHADER_VERTEX, ...)
+
+You can get assigned shader sources by using the following material keys:
+
+<li>AI_MATKEY_GLOBAL_SHADERLANG</li>To get the used shader language.
+<li>AI_MATKEY_SHADER_VERTEX</li> Assigned vertex shader code stored as a string.
+<li>AI_MATKEY_SHADER_FRAGMENT</li> Assigned fragment shader code stored as a string.
+<li>AI_MATKEY_SHADER_GEO</li> Assigned geometry shader code stored as a string.
+<li>AI_MATKEY_SHADER_TESSELATION</li> Assigned tesselation shader code stored as a string.
+<li>AI_MATKEY_SHADER_PRIMITIVE</li> Assigned primitive shader code stored as a string.
+<li>AI_MATKEY_SHADER_COMPUTE</li> Assigned compute shader code stored as a string.
+
 */
 
 

+ 10 - 6
include/assimp/material.h

@@ -198,8 +198,6 @@ enum aiTextureType
      */
     aiTextureType_NONE = 0x0,
 
-
-
     /** The texture is combined with the result of the diffuse
      *  lighting equation.
      */
@@ -278,7 +276,7 @@ enum aiTextureType
      *
      *  A texture reference that does not match any of the definitions
      *  above is considered to be 'unknown'. It is still imported,
-     *  but is excluded from any further postprocessing.
+     *  but is excluded from any further post-processing.
     */
     aiTextureType_UNKNOWN = 0xC,
 
@@ -375,7 +373,7 @@ enum aiShadingMode
 */
 enum aiTextureFlags
 {
-    /** The texture's color values have to be inverted (componentwise 1-n)
+    /** The texture's color values have to be inverted (component-wise 1-n)
      */
     aiTextureFlags_Invert = 0x1,
 
@@ -902,6 +900,7 @@ extern "C" {
 #define AI_MATKEY_ENABLE_WIREFRAME "$mat.wireframe",0,0
 #define AI_MATKEY_BLEND_FUNC "$mat.blend",0,0
 #define AI_MATKEY_OPACITY "$mat.opacity",0,0
+#define AI_MATKEY_TRANSPARENCYFACTOR "$mat.transparencyfactor",0,0
 #define AI_MATKEY_BUMPSCALING "$mat.bumpscaling",0,0
 #define AI_MATKEY_SHININESS "$mat.shininess",0,0
 #define AI_MATKEY_REFLECTIVITY "$mat.reflectivity",0,0
@@ -914,6 +913,13 @@ extern "C" {
 #define AI_MATKEY_COLOR_TRANSPARENT "$clr.transparent",0,0
 #define AI_MATKEY_COLOR_REFLECTIVE "$clr.reflective",0,0
 #define AI_MATKEY_GLOBAL_BACKGROUND_IMAGE "?bg.global",0,0
+#define AI_MATKEY_GLOBAL_SHADERLANG "?sh.lang",0,0
+#define AI_MATKEY_SHADER_VERTEX "?sh.vs",0,0
+#define AI_MATKEY_SHADER_FRAGMENT "?sh.fs",0,0
+#define AI_MATKEY_SHADER_GEO "?sh.gs",0,0
+#define AI_MATKEY_SHADER_TESSELATION "?sh.ts",0,0
+#define AI_MATKEY_SHADER_PRIMITIVE "?sh.ps",0,0
+#define AI_MATKEY_SHADER_COMPUTE "?sh.cs",0,0
 
 // ---------------------------------------------------------------------------
 // Pure key names for all texture-related properties
@@ -1457,8 +1463,6 @@ inline aiReturn aiGetMaterialInteger(const C_STRUCT aiMaterial* pMat,
 
 #endif //!__cplusplus
 
-
-
 // ---------------------------------------------------------------------------
 /** @brief Retrieve a color value from the material property table
 *

+ 545 - 547
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()
+    

+ 1 - 1
port/PyAssimp/pyassimp/formats.py

@@ -21,7 +21,7 @@ FORMATS = ["CSM",
             "STL", 
             "IRR", 
             "Q3O",
-            "Q3D"
+            "Q3D",
             "MS3D", 
             "Q3S", 
             "ZGL", 

+ 279 - 280
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

+ 1 - 0
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. 

+ 2 - 2
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

+ 3 - 2
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

+ 3 - 3
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")

+ 3 - 1
scripts/BlenderImporter/genblenddna.py

@@ -291,7 +291,9 @@ def main():
     #s += "#endif\n"
         
     output.write(templt.replace("<HERE>",s))
-        
+
+    # we got here, so no error
+    return 0
 
 if __name__ == "__main__":
     sys.exit(main())

+ 9 - 9
scripts/StepImporter/CppGenerator.py

@@ -52,8 +52,8 @@ use_ifc_template = False
 	
 input_step_template_h   = 'StepReaderGen.h.template'
 input_step_template_cpp = 'StepReaderGen.cpp.template'
-input_ifc_template_h        = 'IFCReaderGen.h.template'
-input_ifc_template_cpp      = 'IFCReaderGen.cpp.template'
+input_ifc_template_h    = 'IFCReaderGen.h.template'
+input_ifc_template_cpp  = 'IFCReaderGen.cpp.template'
 
 cpp_keywords = "class"
 
@@ -87,7 +87,7 @@ template_type = r"""
 
 template_stub_decl = '\tDECL_CONV_STUB({type});\n'
 template_schema = '\t\tSchemaEntry("{normalized_name}",&STEP::ObjectHelper<{type},{argcnt}>::Construct )\n'
-template_schema_type = '\t\tSchemaEntry("{normalized_name}",NULL )\n'
+template_schema_type = '\t\tSchemaEntry("{normalized_name}",nullptr )\n'
 template_converter = r"""
 // -----------------------------------------------------------------------------------------------------------
 template <> size_t GenericFill<{type}>(const DB& db, const LIST& params, {type}* in)
@@ -99,7 +99,7 @@ template_converter_prologue_a = '\tsize_t base = GenericFill(db,params,static_ca
 template_converter_prologue_b = '\tsize_t base = 0;\n'
 template_converter_check_argcnt = '\tif (params.GetSize() < {max_arg}) {{ throw STEP::TypeError("expected {max_arg} arguments to {name}"); }}'
 template_converter_code_per_field = r"""    do {{ // convert the '{fieldname}' argument
-        boost::shared_ptr<const DataType> arg = params[base++];{handle_unset}{convert}
+        std::shared_ptr<const DataType> arg = params[base++];{handle_unset}{convert}
     }} while(0);
 """
 template_allow_optional = r"""
@@ -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'))

+ 2 - 1
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"""

+ 2 - 2
scripts/StepImporter/StepReaderGen.cpp.template

@@ -2,7 +2,7 @@
 Open Asset Import Library (ASSIMP)
 ----------------------------------------------------------------------
 
-Copyright (c) 2006-2018, ASSIMP Development Team
+Copyright (c) 2006-2019, ASSIMP Development Team
 All rights reserved.
 
 Redistribution and use of this software in source and binary forms, 
@@ -66,7 +66,7 @@ namespace STEP {
 // -----------------------------------------------------------------------------------------------------------
 template <> size_t GenericFill<NotImplemented>(const STEP::DB& db, const LIST& params, NotImplemented* in)
 {
-	return 0;
+    return 0u;
 }
 
 

+ 18 - 19
scripts/StepImporter/StepReaderGen.h.template

@@ -2,7 +2,7 @@
 Open Asset Import Library (ASSIMP)
 ----------------------------------------------------------------------
 
-Copyright (c) 2006-2018, ASSIMP Development Team
+Copyright (c) 2006-2019, ASSIMP Development Team
 All rights reserved.
 
 Redistribution and use of this software in source and binary forms, 
@@ -47,25 +47,23 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 namespace Assimp {
 namespace StepFile {
-	using namespace STEP;
-	using namespace STEP::EXPRESS;
-	
-	
-	struct NotImplemented : public ObjectHelper<NotImplemented,0> {
-		
-	};
-	
 
-	// ******************************************************************************
-	// StepFile Custom data types
-	// ******************************************************************************
+using namespace STEP;
+using namespace STEP::EXPRESS;
 
-{types}
+struct NotImplemented : public ObjectHelper<NotImplemented,0> {
 
+};
 
-	// ******************************************************************************
-	// StepFile Entities
-	// ******************************************************************************
+// ******************************************************************************
+// StepFile Custom data types
+// ******************************************************************************
+
+{types}
+
+// ******************************************************************************
+// StepFile Entities
+// ******************************************************************************
 
 {predefs}
 {entities}
@@ -73,11 +71,12 @@ namespace StepFile {
 	void GetSchema(EXPRESS::ConversionSchema& out);
 
 } //! StepFile
+
 namespace STEP {
 
-	// ******************************************************************************
-	// Converter stubs
-	// ******************************************************************************
+// ******************************************************************************
+// Converter stubs
+// ******************************************************************************
 	
 #define DECL_CONV_STUB(type) template <> size_t GenericFill<IFC::type>(const STEP::DB& db, const EXPRESS::LIST& params, IFC::type* in)
 	

+ 2 - 2
test/unit/utSTLImportExport.cpp

@@ -152,10 +152,10 @@ TEST_F(utSTLImporterExporter, test_export_pointclouds) {
 
     auto pMesh = scene.mMeshes[0];
 
-    long numValidPoints = points.size();
+    size_t numValidPoints = points.size();
 
     pMesh->mVertices = new aiVector3D[numValidPoints];
-    pMesh->mNumVertices = numValidPoints;
+    pMesh->mNumVertices = static_cast<unsigned int>( numValidPoints );
 
     int i = 0;
     for (XYZ &p : points) {

+ 199 - 0
test/unit/utValidateDataStructure.cpp

@@ -0,0 +1,199 @@
+/*
+---------------------------------------------------------------------------
+Open Asset Import Library (assimp)
+---------------------------------------------------------------------------
+
+Copyright (c) 2006-2019, assimp team
+
+
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the following
+conditions are met:
+
+* Redistributions of source code must retain the above
+copyright notice, this list of conditions and the
+following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the
+following disclaimer in the documentation and/or other
+materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+contributors may be used to endorse or promote products
+derived from this software without specific prior
+written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+---------------------------------------------------------------------------
+*/
+#include "UnitTestPCH.h"
+
+#include <assimp/mesh.h>
+#include <assimp/scene.h>
+#include <ValidateDataStructure.h>
+
+using namespace std;
+using namespace Assimp;
+
+
+class ValidateDataStructureTest : public ::testing::Test
+{
+public:
+
+    virtual void SetUp();
+    virtual void TearDown();
+
+protected:
+
+
+    ValidateDSProcess* vds;
+    aiScene* scene;
+};
+
+// ------------------------------------------------------------------------------------------------
+void ValidateDataStructureTest::SetUp()
+{
+    // setup a dummy scene with a single node
+    scene = new aiScene();
+    scene->mRootNode = new aiNode();
+    scene->mRootNode->mName.Set("<test>");
+
+    // add some translation
+    scene->mRootNode->mTransformation.a4 = 1.f;
+    scene->mRootNode->mTransformation.b4 = 2.f;
+    scene->mRootNode->mTransformation.c4 = 3.f;
+
+    // and allocate a ScenePreprocessor to operate on the scene
+    vds = new ValidateDSProcess();
+}
+
+// ------------------------------------------------------------------------------------------------
+void ValidateDataStructureTest::TearDown()
+{
+    delete vds;
+    delete scene;
+}
+
+
+
+// ------------------------------------------------------------------------------------------------
+//Template
+//TEST_F(ScenePreprocessorTest, test)
+//{
+//}
+// TODO Conditions not yet checked:
+//132: ReportError("aiScene::%s is NULL (aiScene::%s is %i)",
+//139: ReportError("aiScene::%s[%i] is NULL (aiScene::%s is %i)",
+//156: ReportError("aiScene::%s is NULL (aiScene::%s is %i)",
+//163: ReportError("aiScene::%s[%i] is NULL (aiScene::%s is %i)",
+//173: ReportError("aiScene::%s[%i] has the same name as "
+//192: ReportError("aiScene::%s[%i] has no corresponding node in the scene graph (%s)",
+//196: ReportError("aiScene::%s[%i]: there are more than one nodes with %s as name",
+//217: ReportError("aiScene::mNumMeshes is 0. At least one mesh must be there");
+//220: ReportError("aiScene::mMeshes is non-null although there are no meshes");
+//229: ReportError("aiScene::mAnimations is non-null although there are no animations");
+//238: ReportError("aiScene::mCameras is non-null although there are no cameras");
+//247: ReportError("aiScene::mLights is non-null although there are no lights");
+//256: ReportError("aiScene::mTextures is non-null although there are no textures");
+//266: ReportError("aiScene::mNumMaterials is 0. At least one material must be there");
+//270: ReportError("aiScene::mMaterials is non-null although there are no materials");
+//281: ReportWarning("aiLight::mType is aiLightSource_UNDEFINED");
+//286: ReportWarning("aiLight::mAttenuationXXX - all are zero");
+//290: ReportError("aiLight::mAngleInnerCone is larger than aiLight::mAngleOuterCone");
+//295: ReportWarning("aiLight::mColorXXX - all are black and won't have any influence");
+//303: ReportError("aiCamera::mClipPlaneFar must be >= aiCamera::mClipPlaneNear");
+//308: ReportWarning("%f is not a valid value for aiCamera::mHorizontalFOV",pCamera->mHorizontalFOV);
+//317: ReportError("aiMesh::mMaterialIndex is invalid (value: %i maximum: %i)",
+//332: ReportError("aiMesh::mFaces[%i].mNumIndices is 0",i);
+//336: ReportError("aiMesh::mFaces[%i] is a POINT but aiMesh::mPrimitiveTypes "
+//337: "does not report the POINT flag",i);
+//343: ReportError("aiMesh::mFaces[%i] is a LINE but aiMesh::mPrimitiveTypes "
+//344: "does not report the LINE flag",i);
+//350: ReportError("aiMesh::mFaces[%i] is a TRIANGLE but aiMesh::mPrimitiveTypes "
+//351: "does not report the TRIANGLE flag",i);
+//357: this->ReportError("aiMesh::mFaces[%i] is a POLYGON but aiMesh::mPrimitiveTypes "
+//358: "does not report the POLYGON flag",i);
+//365: ReportError("aiMesh::mFaces[%i].mIndices is NULL",i);
+//370: ReportError("The mesh %s contains no vertices", pMesh->mName.C_Str());
+//374: ReportError("Mesh has too many vertices: %u, but the limit is %u",pMesh->mNumVertices,AI_MAX_VERTICES);
+//377: ReportError("Mesh has too many faces: %u, but the limit is %u",pMesh->mNumFaces,AI_MAX_FACES);
+//382: ReportError("If there are tangents, bitangent vectors must be present as well");
+//387: ReportError("Mesh %s contains no faces", pMesh->mName.C_Str());
+//398: ReportError("Face %u has too many faces: %u, but the limit is %u",i,face.mNumIndices,AI_MAX_FACE_INDICES);
+//404: ReportError("aiMesh::mFaces[%i]::mIndices[%i] is out of range",i,a);
+//412: ReportError("aiMesh::mVertices[%i] is referenced twice - second "
+//426: ReportWarning("There are unreferenced vertices");
+//439: ReportError("Texture coordinate channel %i exists "
+//453: ReportError("Vertex color channel %i is exists "
+//464: ReportError("aiMesh::mBones is NULL (aiMesh::mNumBones is %i)",
+//480: ReportError("Bone %u has too many weights: %u, but the limit is %u",i,bone->mNumWeights,AI_MAX_BONE_WEIGHTS);
+//485: ReportError("aiMesh::mBones[%i] is NULL (aiMesh::mNumBones is %i)",
+//498: ReportError("aiMesh::mBones[%i], name = \"%s\" has the same name as "
+//507: ReportWarning("aiMesh::mVertices[%i]: bone weight sum != 1.0 (sum is %f)",i,afSum[i]);
+//513: ReportError("aiMesh::mBones is non-null although there are no bones");
+//524: ReportError("aiBone::mNumWeights is zero");
+//531: ReportError("aiBone::mWeights[%i].mVertexId is out of range",i);
+//534: ReportWarning("aiBone::mWeights[%i].mWeight has an invalid value",i);
+//549: ReportError("aiAnimation::mChannels is NULL (aiAnimation::mNumChannels is %i)",
+//556: ReportError("aiAnimation::mChannels[%i] is NULL (aiAnimation::mNumChannels is %i)",
+//563: ReportError("aiAnimation::mNumChannels is 0. At least one node animation channel must be there.");
+//567: // if (!pAnimation->mDuration)this->ReportError("aiAnimation::mDuration is zero");
+//592: ReportError("Material property %s is expected to be a string",prop->mKey.data);
+//596: ReportError("%s #%i is set, but there are only %i %s textures",
+//611: ReportError("Found texture property with index %i, although there "
+//619: ReportError("Material property %s%i is expected to be an integer (size is %i)",
+//627: ReportError("Material property %s%i is expected to be 5 floats large (size is %i)",
+//635: ReportError("Material property %s%i is expected to be an integer (size is %i)",
+//656: ReportWarning("Invalid UV index: %i (key %s). Mesh %i has only %i UV channels",
+//676: ReportWarning("UV-mapped texture, but there are no UV coords");
+//690: ReportError("aiMaterial::mProperties[%i] is NULL (aiMaterial::mNumProperties is %i)",
+//694: ReportError("aiMaterial::mProperties[%i].mDataLength or "
+//702: ReportError("aiMaterial::mProperties[%i].mDataLength is "
+//707: ReportError("Missing null-terminator in string material property");
+//713: ReportError("aiMaterial::mProperties[%i].mDataLength is "
+//720: ReportError("aiMaterial::mProperties[%i].mDataLength is "
+//739: ReportWarning("A specular shading model is specified but there is no "
+//743: ReportWarning("A specular shading model is specified but the value of the "
+//752: ReportWarning("Invalid opacity value (must be 0 < opacity < 1.0)");
+//776: ReportError("aiTexture::pcData is NULL");
+//781: ReportError("aiTexture::mWidth is zero (aiTexture::mHeight is %i, uncompressed texture)",
+//788: ReportError("aiTexture::mWidth is zero (compressed texture)");
+//791: ReportWarning("aiTexture::achFormatHint must be zero-terminated");
+//794: ReportWarning("aiTexture::achFormatHint should contain a file extension "
+//804: ReportError("aiTexture::achFormatHint contains non-lowercase letters");
+//815: ReportError("Empty node animation channel");
+//822: ReportError("aiNodeAnim::mPositionKeys is NULL (aiNodeAnim::mNumPositionKeys is %i)",
+//833: ReportError("aiNodeAnim::mPositionKeys[%i].mTime (%.5f) is larger "
+//840: ReportWarning("aiNodeAnim::mPositionKeys[%i].mTime (%.5f) is smaller "
+//853: ReportError("aiNodeAnim::mRotationKeys is NULL (aiNodeAnim::mNumRotationKeys is %i)",
+//861: ReportError("aiNodeAnim::mRotationKeys[%i].mTime (%.5f) is larger "
+//868: ReportWarning("aiNodeAnim::mRotationKeys[%i].mTime (%.5f) is smaller "
+//880: ReportError("aiNodeAnim::mScalingKeys is NULL (aiNodeAnim::mNumScalingKeys is %i)",
+//888: ReportError("aiNodeAnim::mScalingKeys[%i].mTime (%.5f) is larger "
+//895: ReportWarning("aiNodeAnim::mScalingKeys[%i].mTime (%.5f) is smaller "
+//907: ReportError("A node animation channel must have at least one subtrack");
+//915: ReportError("A node of the scenegraph is NULL");
+//920: ReportError("Non-root node %s lacks a valid parent (aiNode::mParent is NULL) ",pNode->mName);
+//928: ReportError("aiNode::mMeshes is NULL for node %s (aiNode::mNumMeshes is %i)",
+//937: ReportError("aiNode::mMeshes[%i] is out of range for node %s (maximum is %i)",
+//942: ReportError("aiNode::mMeshes[%i] is already referenced by this node %s (value: %i)",
+//951: ReportError("aiNode::mChildren is NULL for node %s (aiNode::mNumChildren is %i)",
+//965: ReportError("aiString::length is too large (%i, maximum is %lu)",
+//974: ReportError("aiString::data is invalid: the terminal zero is at a wrong offset");
+//979: ReportError("aiString::data is invalid. There is no terminal character");
+}
+

+ 3 - 6
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]) : "-");

+ 18 - 26
tools/assimp_cmd/Info.cpp

@@ -51,27 +51,26 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <string>
 
 const char* AICMD_MSG_INFO_HELP_E =
-"assimp info <file> [-r] [-v]\n"
-"\tPrint basic structure of a 3D model\n"
-"\t-r,--raw: No postprocessing, do a raw import\n"
-"\t-v,--verbose: Print verbose info such as node transform data\n"
-"\t-s, --silent: Print only minimal info\n";
-
-const std::string TREE_BRANCH_ASCII = "|-";
-const std::string TREE_BRANCH_UTF8 = "\xe2\x94\x9c\xe2\x95\xb4";
-const std::string TREE_STOP_ASCII = "'-";
-const std::string TREE_STOP_UTF8 = "\xe2\x94\x94\xe2\x95\xb4";
-const std::string TREE_CONTINUE_ASCII = "| ";
-const std::string TREE_CONTINUE_UTF8 = "\xe2\x94\x82 ";
-
-// note: by default this is outputing utf-8 text.
+    "assimp info <file> [-r] [-v]\n"
+    "\tPrint basic structure of a 3D model\n"
+    "\t-r,--raw: No postprocessing, do a raw import\n"
+    "\t-v,--verbose: Print verbose info such as node transform data\n"
+    "\t-s, --silent: Print only minimal info\n";
+
+const char *TREE_BRANCH_ASCII = "|-";
+const char *TREE_BRANCH_UTF8 = "\xe2\x94\x9c\xe2\x95\xb4";
+const char *TREE_STOP_ASCII = "'-";
+const char *TREE_STOP_UTF8 = "\xe2\x94\x94\xe2\x95\xb4";
+const char *TREE_CONTINUE_ASCII = "| ";
+const char *TREE_CONTINUE_UTF8 = "\xe2\x94\x82 ";
+
+// note: by default this is using utf-8 text.
 // this is well supported on pretty much any linux terminal.
 // if this causes problems on some platform,
 // put an #ifdef to use the ascii version for that platform.
-const std::string TREE_BRANCH = TREE_BRANCH_UTF8;
-const std::string TREE_STOP = TREE_STOP_UTF8;
-const std::string TREE_CONTINUE = TREE_CONTINUE_UTF8;
-
+const char *TREE_BRANCH = TREE_BRANCH_UTF8;
+const char *TREE_STOP = TREE_STOP_UTF8;
+const char *TREE_CONTINUE = TREE_CONTINUE_UTF8;
 
 // -----------------------------------------------------------------------------------
 unsigned int CountNodes(const aiNode* root)
@@ -280,14 +279,7 @@ void PrintHierarchy(
 
 // -----------------------------------------------------------------------------------
 // Implementation of the assimp info utility to print basic file info
-int Assimp_Info (const char* const* params, unsigned int num)
-{
-	if (num < 1) {
-		printf("assimp info: Invalid number of arguments. "
-			"See \'assimp info --help\'\n");
-		return 1;
-	}
-
+int Assimp_Info (const char* const* params, unsigned int num) {
 	// --help
 	if (!strcmp( params[0],"-h")||!strcmp( params[0],"--help")||!strcmp( params[0],"-?") ) {
 		printf("%s",AICMD_MSG_INFO_HELP_E);

+ 6 - 7
tools/assimp_cmd/WriteDumb.cpp

@@ -276,9 +276,12 @@ inline uint32_t WriteBounds(const T* in, unsigned int size)
 void ChangeInteger(uint32_t ofs,uint32_t n)
 {
 	const uint32_t cur = ftell(out);
-	fseek(out,ofs,SEEK_SET);
-	fwrite(&n,4,1,out);
-	fseek(out,cur,SEEK_SET);
+    int retCode;
+    retCode = fseek(out, ofs, SEEK_SET);
+    ai_assert(0 == retCode);
+	fwrite(&n, 4, 1, out);
+    retCode = fseek(out, cur, SEEK_SET);
+    ai_assert(0 == retCode);
 }
 
 // -----------------------------------------------------------------------------------
@@ -1333,10 +1336,6 @@ int Assimp_Dump (const char* const* params, unsigned int num)
 {
 	const char* fail = "assimp dump: Invalid number of arguments. "
 			"See \'assimp dump --help\'\r\n";
-	if (num < 1) {
-		printf("%s", fail);
-		return 1;
-	}
 
 	// --help
 	if (!strcmp( params[0], "-h") || !strcmp( params[0], "--help") || !strcmp( params[0], "-?") ) {

+ 1 - 1
tools/assimp_view/SceneAnimator.cpp

@@ -90,7 +90,7 @@ void SceneAnimator::SetAnimIndex( size_t pAnimIndex) {
     delete mAnimEvaluator;  mAnimEvaluator = nullptr;
     mNodesByName.clear();
 
-    mCurrentAnimIndex = pAnimIndex;
+    mCurrentAnimIndex = static_cast<int>( pAnimIndex );
 
     // create the internal node tree. Do this even in case of invalid animation index
     // so that the transformation matrices are properly set up to mimic the current scene

Some files were not shown because too many files changed in this diff