2
0
Эх сурвалжийг харах

Merge pull request #2217 from assimp/issue_2199

closes https://github.com/assimp/assimp/issues/2199: introduce first …
Kim Kulling 6 жил өмнө
parent
commit
3d57b86349

+ 1 - 3
code/DefaultProgressHandler.h

@@ -52,9 +52,7 @@ namespace Assimp    {
 
 
 // ------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------
 /** @brief Internal default implementation of the #ProgressHandler interface. */
 /** @brief Internal default implementation of the #ProgressHandler interface. */
-class DefaultProgressHandler
-    : public ProgressHandler    {
-
+class DefaultProgressHandler : public ProgressHandler    {
 
 
     virtual bool Update(float /*percentage*/) {
     virtual bool Update(float /*percentage*/) {
         return false;
         return false;

+ 76 - 46
code/Exporter.cpp

@@ -56,22 +56,22 @@ Here we implement only the C++ interface (Assimp::Exporter).
 
 
 #include <assimp/BlobIOSystem.h>
 #include <assimp/BlobIOSystem.h>
 #include <assimp/SceneCombiner.h>
 #include <assimp/SceneCombiner.h>
-#include "BaseProcess.h"
-#include "Importer.h" // need this for GetPostProcessingStepInstanceList()
+#include <assimp/DefaultIOSystem.h>
+#include <assimp/Exporter.hpp>
+#include <assimp/mesh.h>
+#include <assimp/postprocess.h>
+#include <assimp/scene.h>
 
 
+#include "DefaultProgressHandler.h"
+#include "BaseProcess.h"
 #include "JoinVerticesProcess.h"
 #include "JoinVerticesProcess.h"
 #include "MakeVerboseFormat.h"
 #include "MakeVerboseFormat.h"
 #include "ConvertToLHProcess.h"
 #include "ConvertToLHProcess.h"
 #include "PretransformVertices.h"
 #include "PretransformVertices.h"
 #include <assimp/Exceptional.h>
 #include <assimp/Exceptional.h>
 #include "ScenePrivate.h"
 #include "ScenePrivate.h"
-#include <memory>
 
 
-#include <assimp/DefaultIOSystem.h>
-#include <assimp/Exporter.hpp>
-#include <assimp/mesh.h>
-#include <assimp/postprocess.h>
-#include <assimp/scene.h>
+#include <memory>
 
 
 namespace Assimp {
 namespace Assimp {
 
 
@@ -188,10 +188,14 @@ Exporter::ExportFormatEntry gExporters[] =
 class ExporterPimpl {
 class ExporterPimpl {
 public:
 public:
     ExporterPimpl()
     ExporterPimpl()
-        : blob()
-        , mIOSystem(new Assimp::DefaultIOSystem())
-        , mIsDefaultIOHandler(true)
-    {
+    : blob()
+    , mIOSystem(new Assimp::DefaultIOSystem())
+    , mIsDefaultIOHandler(true)
+    , mProgressHandler( nullptr )
+    , mIsDefaultProgressHandler( true )
+    , mPostProcessingSteps()
+    , mError()
+    , mExporters() {
         GetPostProcessingStepInstanceList(mPostProcessingSteps);
         GetPostProcessingStepInstanceList(mPostProcessingSteps);
 
 
         // grab all built-in exporters
         // grab all built-in exporters
@@ -201,8 +205,7 @@ public:
         }
         }
     }
     }
 
 
-    ~ExporterPimpl()
-    {
+    ~ExporterPimpl() {
         delete blob;
         delete blob;
 
 
         // Delete all post-processing plug-ins
         // Delete all post-processing plug-ins
@@ -216,6 +219,10 @@ public:
     std::shared_ptr< Assimp::IOSystem > mIOSystem;
     std::shared_ptr< Assimp::IOSystem > mIOSystem;
     bool mIsDefaultIOHandler;
     bool mIsDefaultIOHandler;
 
 
+    /** The progress handler */
+    ProgressHandler *mProgressHandler;
+    bool mIsDefaultProgressHandler;
+
     /** Post processing steps we can apply at the imported data. */
     /** Post processing steps we can apply at the imported data. */
     std::vector< BaseProcess* > mPostProcessingSteps;
     std::vector< BaseProcess* > mPostProcessingSteps;
 
 
@@ -233,13 +240,16 @@ using namespace Assimp;
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 Exporter :: Exporter()
 Exporter :: Exporter()
 : pimpl(new ExporterPimpl()) {
 : pimpl(new ExporterPimpl()) {
-    // empty
+    pimpl->mProgressHandler = new DefaultProgressHandler();
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 Exporter::~Exporter() {
 Exporter::~Exporter() {
     FreeBlob();
     FreeBlob();
-
+    if (pimpl->mIsDefaultProgressHandler) {
+        delete pimpl->mProgressHandler;
+        pimpl->mProgressHandler = nullptr;
+    }
     delete pimpl;
     delete pimpl;
 }
 }
 
 
@@ -259,12 +269,32 @@ bool Exporter::IsDefaultIOHandler() const {
     return pimpl->mIsDefaultIOHandler;
     return pimpl->mIsDefaultIOHandler;
 }
 }
 
 
+// ------------------------------------------------------------------------------------------------
+void Exporter::SetProgressHandler(ProgressHandler* pHandler) {
+    ai_assert(nullptr != pimpl);
+
+    if ( nullptr == pHandler) {
+        // Release pointer in the possession of the caller
+        pimpl->mProgressHandler = new DefaultProgressHandler();
+        pimpl->mIsDefaultProgressHandler = true;
+        return;
+    }
+
+    if (pimpl->mProgressHandler == pHandler) {
+        return;
+    }
+
+    delete pimpl->mProgressHandler;
+    pimpl->mProgressHandler = pHandler;
+    pimpl->mIsDefaultProgressHandler = false;
+}
+
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 const aiExportDataBlob* Exporter::ExportToBlob( const aiScene* pScene, const char* pFormatId,
 const aiExportDataBlob* Exporter::ExportToBlob( const aiScene* pScene, const char* pFormatId,
                                                 unsigned int, const ExportProperties* /*pProperties*/ ) {
                                                 unsigned int, const ExportProperties* /*pProperties*/ ) {
     if (pimpl->blob) {
     if (pimpl->blob) {
         delete pimpl->blob;
         delete pimpl->blob;
-        pimpl->blob = NULL;
+        pimpl->blob = nullptr;
     }
     }
 
 
     std::shared_ptr<IOSystem> old = pimpl->mIOSystem;
     std::shared_ptr<IOSystem> old = pimpl->mIOSystem;
@@ -273,7 +303,7 @@ const aiExportDataBlob* Exporter::ExportToBlob( const aiScene* pScene, const cha
 
 
     if (AI_SUCCESS != Export(pScene,pFormatId,blobio->GetMagicFileName())) {
     if (AI_SUCCESS != Export(pScene,pFormatId,blobio->GetMagicFileName())) {
         pimpl->mIOSystem = old;
         pimpl->mIOSystem = old;
-        return NULL;
+        return nullptr;
     }
     }
 
 
     pimpl->blob = blobio->GetBlobChain();
     pimpl->blob = blobio->GetBlobChain();
@@ -295,6 +325,7 @@ bool IsVerboseFormat(const aiMesh* mesh) {
             }
             }
         }
         }
     }
     }
+
     return true;
     return true;
 }
 }
 
 
@@ -305,6 +336,7 @@ bool IsVerboseFormat(const aiScene* pScene) {
             return false;
             return false;
         }
         }
     }
     }
+
     return true;
     return true;
 }
 }
 
 
@@ -319,6 +351,8 @@ aiReturn Exporter::Export( const aiScene* pScene, const char* pFormatId, const c
     // meshes upfront.
     // meshes upfront.
     const bool is_verbose_format = !(pScene->mFlags & AI_SCENE_FLAGS_NON_VERBOSE_FORMAT) || IsVerboseFormat(pScene);
     const bool is_verbose_format = !(pScene->mFlags & AI_SCENE_FLAGS_NON_VERBOSE_FORMAT) || IsVerboseFormat(pScene);
 
 
+    pimpl->mProgressHandler->UpdateFileWrite(0, 4);
+
     pimpl->mError = "";
     pimpl->mError = "";
     for (size_t i = 0; i < pimpl->mExporters.size(); ++i) {
     for (size_t i = 0; i < pimpl->mExporters.size(); ++i) {
         const Exporter::ExportFormatEntry& exp = pimpl->mExporters[i];
         const Exporter::ExportFormatEntry& exp = pimpl->mExporters[i];
@@ -326,9 +360,11 @@ aiReturn Exporter::Export( const aiScene* pScene, const char* pFormatId, const c
             try {
             try {
                 // Always create a full copy of the scene. We might optimize this one day,
                 // Always create a full copy of the scene. We might optimize this one day,
                 // but for now it is the most pragmatic way.
                 // but for now it is the most pragmatic way.
-                aiScene* scenecopy_tmp = NULL;
+                aiScene* scenecopy_tmp = nullptr;
                 SceneCombiner::CopyScene(&scenecopy_tmp,pScene);
                 SceneCombiner::CopyScene(&scenecopy_tmp,pScene);
 
 
+                pimpl->mProgressHandler->UpdateFileWrite(1, 4);
+
                 std::unique_ptr<aiScene> scenecopy(scenecopy_tmp);
                 std::unique_ptr<aiScene> scenecopy(scenecopy_tmp);
                 const ScenePrivateData* const priv = ScenePriv(pScene);
                 const ScenePrivateData* const priv = ScenePriv(pScene);
 
 
@@ -375,6 +411,8 @@ aiReturn Exporter::Export( const aiScene* pScene, const char* pFormatId, const c
                     }
                     }
                 }
                 }
 
 
+                pimpl->mProgressHandler->UpdateFileWrite(2, 4);
+
                 if (pp) {
                 if (pp) {
                     // the three 'conversion' steps need to be executed first because all other steps rely on the standard data layout
                     // the three 'conversion' steps need to be executed first because all other steps rely on the standard data layout
                     {
                     {
@@ -418,11 +456,13 @@ aiReturn Exporter::Export( const aiScene* pScene, const char* pFormatId, const c
                         }
                         }
                     }
                     }
                     ScenePrivateData* const privOut = ScenePriv(scenecopy.get());
                     ScenePrivateData* const privOut = ScenePriv(scenecopy.get());
-                    ai_assert(privOut);
+                    ai_assert(nullptr != privOut);
 
 
                     privOut->mPPStepsApplied |= pp;
                     privOut->mPPStepsApplied |= pp;
                 }
                 }
 
 
+                pimpl->mProgressHandler->UpdateFileWrite(3, 4);
+
                 if(must_join_again) {
                 if(must_join_again) {
                     JoinVerticesProcess proc;
                     JoinVerticesProcess proc;
                     proc.Execute(scenecopy.get());
                     proc.Execute(scenecopy.get());
@@ -430,6 +470,8 @@ aiReturn Exporter::Export( const aiScene* pScene, const char* pFormatId, const c
 
 
                 ExportProperties emptyProperties;  // Never pass NULL ExportProperties so Exporters don't have to worry.
                 ExportProperties emptyProperties;  // Never pass NULL ExportProperties so Exporters don't have to worry.
                 exp.mExportFunction(pPath,pimpl->mIOSystem.get(),scenecopy.get(), pProperties ? pProperties : &emptyProperties);
                 exp.mExportFunction(pPath,pimpl->mIOSystem.get(),scenecopy.get(), pProperties ? pProperties : &emptyProperties);
+
+                pimpl->mProgressHandler->UpdateFileWrite(4, 4);
             } catch (DeadlyExportError& err) {
             } catch (DeadlyExportError& err) {
                 pimpl->mError = err.what();
                 pimpl->mError = err.what();
                 return AI_FAILURE;
                 return AI_FAILURE;
@@ -452,7 +494,7 @@ const char* Exporter::GetErrorString() const {
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 void Exporter::FreeBlob() {
 void Exporter::FreeBlob() {
     delete pimpl->blob;
     delete pimpl->blob;
-    pimpl->blob = NULL;
+    pimpl->blob = nullptr;
 
 
     pimpl->mError = "";
     pimpl->mError = "";
 }
 }
@@ -465,7 +507,7 @@ const aiExportDataBlob* Exporter::GetBlob() const {
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 const aiExportDataBlob* Exporter::GetOrphanedBlob() const {
 const aiExportDataBlob* Exporter::GetOrphanedBlob() const {
     const aiExportDataBlob* tmp = pimpl->blob;
     const aiExportDataBlob* tmp = pimpl->blob;
-    pimpl->blob = NULL;
+    pimpl->blob = nullptr;
     return tmp;
     return tmp;
 }
 }
 
 
@@ -545,75 +587,63 @@ bool ExportProperties::SetPropertyString(const char* szName, const std::string&
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Set a configuration property
 // Set a configuration property
-bool ExportProperties :: SetPropertyMatrix(const char* szName, const aiMatrix4x4& value)
-{
+bool ExportProperties::SetPropertyMatrix(const char* szName, const aiMatrix4x4& value) {
     return SetGenericProperty<aiMatrix4x4>(mMatrixProperties, szName,value);
     return SetGenericProperty<aiMatrix4x4>(mMatrixProperties, szName,value);
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Get a configuration property
 // Get a configuration property
-int ExportProperties :: GetPropertyInteger(const char* szName,
-    int iErrorReturn /*= 0xffffffff*/) const
-{
+int ExportProperties::GetPropertyInteger(const char* szName, int iErrorReturn /*= 0xffffffff*/) const {
     return GetGenericProperty<int>(mIntProperties,szName,iErrorReturn);
     return GetGenericProperty<int>(mIntProperties,szName,iErrorReturn);
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Get a configuration property
 // Get a configuration property
-ai_real ExportProperties :: GetPropertyFloat(const char* szName,
-    ai_real iErrorReturn /*= 10e10*/) const
-{
+ai_real ExportProperties::GetPropertyFloat(const char* szName, ai_real iErrorReturn /*= 10e10*/) const {
     return GetGenericProperty<ai_real>(mFloatProperties,szName,iErrorReturn);
     return GetGenericProperty<ai_real>(mFloatProperties,szName,iErrorReturn);
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Get a configuration property
 // Get a configuration property
-const std::string ExportProperties :: GetPropertyString(const char* szName,
-    const std::string& iErrorReturn /*= ""*/) const
-{
+const std::string ExportProperties::GetPropertyString(const char* szName,
+        const std::string& iErrorReturn /*= ""*/) const {
     return GetGenericProperty<std::string>(mStringProperties,szName,iErrorReturn);
     return GetGenericProperty<std::string>(mStringProperties,szName,iErrorReturn);
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Has a configuration property
 // Has a configuration property
-const aiMatrix4x4 ExportProperties :: GetPropertyMatrix(const char* szName,
-    const aiMatrix4x4& iErrorReturn /*= aiMatrix4x4()*/) const
-{
+const aiMatrix4x4 ExportProperties::GetPropertyMatrix(const char* szName,
+        const aiMatrix4x4& iErrorReturn /*= aiMatrix4x4()*/) const {
     return GetGenericProperty<aiMatrix4x4>(mMatrixProperties,szName,iErrorReturn);
     return GetGenericProperty<aiMatrix4x4>(mMatrixProperties,szName,iErrorReturn);
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Has a configuration property
 // Has a configuration property
-bool ExportProperties :: HasPropertyInteger(const char* szName) const
-{
+bool ExportProperties::HasPropertyInteger(const char* szName) const {
     return HasGenericProperty<int>(mIntProperties, szName);
     return HasGenericProperty<int>(mIntProperties, szName);
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Has a configuration property
 // Has a configuration property
-bool ExportProperties :: HasPropertyBool(const char* szName) const
-{
+bool ExportProperties::HasPropertyBool(const char* szName) const {
     return HasGenericProperty<int>(mIntProperties, szName);
     return HasGenericProperty<int>(mIntProperties, szName);
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Has a configuration property
 // Has a configuration property
-bool ExportProperties :: HasPropertyFloat(const char* szName) const
-{
+bool ExportProperties::HasPropertyFloat(const char* szName) const {
     return HasGenericProperty<ai_real>(mFloatProperties, szName);
     return HasGenericProperty<ai_real>(mFloatProperties, szName);
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Has a configuration property
 // Has a configuration property
-bool ExportProperties :: HasPropertyString(const char* szName) const
-{
+bool ExportProperties::HasPropertyString(const char* szName) const {
     return HasGenericProperty<std::string>(mStringProperties, szName);
     return HasGenericProperty<std::string>(mStringProperties, szName);
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Has a configuration property
 // Has a configuration property
-bool ExportProperties :: HasPropertyMatrix(const char* szName) const
-{
+bool ExportProperties::HasPropertyMatrix(const char* szName) const {
     return HasGenericProperty<aiMatrix4x4>(mMatrixProperties, szName);
     return HasGenericProperty<aiMatrix4x4>(mMatrixProperties, szName);
 }
 }
 
 

+ 5 - 10
code/Importer.cpp

@@ -315,22 +315,19 @@ void Importer::SetIOHandler( IOSystem* pIOHandler)
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Get the currently set IO handler
 // Get the currently set IO handler
-IOSystem* Importer::GetIOHandler() const
-{
+IOSystem* Importer::GetIOHandler() const {
     return pimpl->mIOHandler;
     return pimpl->mIOHandler;
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Check whether a custom IO handler is currently set
 // Check whether a custom IO handler is currently set
-bool Importer::IsDefaultIOHandler() const
-{
+bool Importer::IsDefaultIOHandler() const {
     return pimpl->mIsDefaultHandler;
     return pimpl->mIsDefaultHandler;
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Supplies a custom progress handler to get regular callbacks during importing
 // Supplies a custom progress handler to get regular callbacks during importing
-void Importer::SetProgressHandler ( ProgressHandler* pHandler )
-{
+void Importer::SetProgressHandler ( ProgressHandler* pHandler ) {
     ASSIMP_BEGIN_EXCEPTION_REGION();
     ASSIMP_BEGIN_EXCEPTION_REGION();
     // If the new handler is zero, allocate a default implementation.
     // If the new handler is zero, allocate a default implementation.
     if (!pHandler)
     if (!pHandler)
@@ -351,15 +348,13 @@ void Importer::SetProgressHandler ( ProgressHandler* pHandler )
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Get the currently set progress handler
 // Get the currently set progress handler
-ProgressHandler* Importer::GetProgressHandler() const
-{
+ProgressHandler* Importer::GetProgressHandler() const {
     return pimpl->mProgressHandler;
     return pimpl->mProgressHandler;
 }
 }
 
 
 // ------------------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------------------
 // Check whether a custom progress handler is currently set
 // Check whether a custom progress handler is currently set
-bool Importer::IsDefaultProgressHandler() const
-{
+bool Importer::IsDefaultProgressHandler() const {
     return pimpl->mIsDefaultProgressHandler;
     return pimpl->mIsDefaultProgressHandler;
 }
 }
 
 

+ 15 - 2
include/assimp/Exporter.hpp

@@ -57,6 +57,7 @@ namespace Assimp {
     
     
 class ExporterPimpl;
 class ExporterPimpl;
 class IOSystem;
 class IOSystem;
+class ProgressHandler;
 
 
 // ----------------------------------------------------------------------------------
 // ----------------------------------------------------------------------------------
 /** CPP-API: The Exporter class forms an C++ interface to the export functionality
 /** CPP-API: The Exporter class forms an C++ interface to the export functionality
@@ -84,8 +85,7 @@ public:
     typedef void (*fpExportFunc)(const char*, IOSystem*, const aiScene*, const ExportProperties*);
     typedef void (*fpExportFunc)(const char*, IOSystem*, const aiScene*, const ExportProperties*);
 
 
     /** Internal description of an Assimp export format option */
     /** Internal description of an Assimp export format option */
-    struct ExportFormatEntry
-    {
+    struct ExportFormatEntry {
         /// Public description structure to be returned by aiGetExportFormatDescription()
         /// Public description structure to be returned by aiGetExportFormatDescription()
         aiExportFormatDesc mDescription;
         aiExportFormatDesc mDescription;
 
 
@@ -158,6 +158,19 @@ public:
      * @return true by default */
      * @return true by default */
     bool IsDefaultIOHandler() const;
     bool IsDefaultIOHandler() const;
 
 
+    // -------------------------------------------------------------------
+    /** Supplies a custom progress handler to the exporter. This
+     *  interface exposes an #Update() callback, which is called
+     *  more or less periodically (please don't sue us if it
+     *  isn't as periodically as you'd like it to have ...).
+     *  This can be used to implement progress bars and loading
+     *  timeouts.
+     *  @param pHandler Progress callback interface. Pass nullptr to
+     *    disable progress reporting.
+     *  @note Progress handlers can be used to abort the loading
+     *    at almost any time.*/
+    void SetProgressHandler(ProgressHandler* pHandler);
+
     // -------------------------------------------------------------------
     // -------------------------------------------------------------------
     /** Exports the given scene to a chosen file format. Returns the exported
     /** Exports the given scene to a chosen file format. Returns the exported
     * data as a binary blob which you can write into a file or something.
     * data as a binary blob which you can write into a file or something.

+ 1 - 1
include/assimp/Importer.hpp

@@ -330,7 +330,7 @@ public:
 
 
     // -------------------------------------------------------------------
     // -------------------------------------------------------------------
     /** Supplies a custom progress handler to the importer. This
     /** Supplies a custom progress handler to the importer. This
-     *  interface exposes a #Update() callback, which is called
+     *  interface exposes an #Update() callback, which is called
      *  more or less periodically (please don't sue us if it
      *  more or less periodically (please don't sue us if it
      *  isn't as periodically as you'd like it to have ...).
      *  isn't as periodically as you'd like it to have ...).
      *  This can be used to implement progress bars and loading
      *  This can be used to implement progress bars and loading

+ 21 - 3
include/assimp/ProgressHandler.hpp

@@ -62,11 +62,13 @@ class ASSIMP_API ProgressHandler
 #endif
 #endif
 {
 {
 protected:
 protected:
-    /** @brief  Default constructor */
-    ProgressHandler () AI_NO_EXCEPT  {
+    /// @brief  Default constructor
+    ProgressHandler () AI_NO_EXCEPT {
+        // empty
     }
     }
+
 public:
 public:
-    /** @brief  Virtual destructor  */
+    /// @brief  Virtual destructor.
     virtual ~ProgressHandler () {
     virtual ~ProgressHandler () {
     }
     }
 
 
@@ -120,8 +122,24 @@ public:
         Update( f * 0.5f + 0.5f );
         Update( f * 0.5f + 0.5f );
     }
     }
 
 
+
+    // -------------------------------------------------------------------
+    /** @brief Progress callback for export steps.
+     *  @param numberOfSteps The number of total processing
+     *   steps
+     *  @param currentStep The index of the current post-processing
+     *   step that will run, or equal to numberOfSteps if all of
+     *   them has finished. This number is always strictly monotone
+     *   increasing, although not necessarily linearly.
+     *   */
+    virtual void UpdateFileWrite(int currentStep /*= 0*/, int numberOfSteps /*= 0*/) {
+        float f = numberOfSteps ? currentStep / (float)numberOfSteps : 1.0f;
+        Update(f * 0.5f);
+    }
 }; // !class ProgressHandler
 }; // !class ProgressHandler
+
 // ------------------------------------------------------------------------------------
 // ------------------------------------------------------------------------------------
+
 } // Namespace Assimp
 } // Namespace Assimp
 
 
 #endif // AI_PROGRESSHANDLER_H_INC
 #endif // AI_PROGRESSHANDLER_H_INC

+ 1 - 0
test/CMakeLists.txt

@@ -87,6 +87,7 @@ SET( IMPORTERS
   unit/utIFCImportExport.cpp
   unit/utIFCImportExport.cpp
   unit/utFBXImporterExporter.cpp
   unit/utFBXImporterExporter.cpp
   unit/utImporter.cpp
   unit/utImporter.cpp
+  unit/ImportExport/utExporter.cpp
   unit/ut3DImportExport.cpp
   unit/ut3DImportExport.cpp
   unit/ut3DSImportExport.cpp
   unit/ut3DSImportExport.cpp
   unit/utACImportExport.cpp
   unit/utACImportExport.cpp

+ 74 - 0
test/unit/ImportExport/utExporter.cpp

@@ -0,0 +1,74 @@
+/*
+---------------------------------------------------------------------------
+Open Asset Import Library (assimp)
+---------------------------------------------------------------------------
+
+Copyright (c) 2006-2018, 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/Exporter.hpp>
+#include <assimp/ProgressHandler.hpp>
+
+using namespace Assimp;
+
+class TestProgressHandler : public ProgressHandler {
+public:
+    TestProgressHandler() : ProgressHandler() {
+        // empty
+    }
+
+    virtual ~TestProgressHandler() {
+        // empty
+    }
+
+    bool Update(float percentage = -1.f) override {
+        return true;
+    }
+};
+
+class ExporterTest : public ::testing::Test {
+    // empty
+};
+
+TEST_F(ExporterTest, ProgressHandlerTest) {
+    Exporter exporter;
+    TestProgressHandler *ph(new TestProgressHandler);
+    exporter.SetProgressHandler(ph);
+    delete ph;
+}