Skip to content

Commit

Permalink
Add support for older OpenCASCADE versions (#1760)
Browse files Browse the repository at this point in the history
* Add support for older OpenCASCADE versions

* Fix MSVC build

* Update feature support detection
  • Loading branch information
oitel authored Nov 1, 2023
1 parent a109dcb commit f6e31fa
Showing 1 changed file with 190 additions and 90 deletions.
280 changes: 190 additions & 90 deletions source/MRMesh/MRMeshLoadStep.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#include "MRMeshLoadStep.h"
#ifndef MRMESH_NO_OPENCASCADE
#include "MRIOFormatsRegistry.h"
#include "MRMesh.h"
#include "MRMeshBuilder.h"
#include "MRObjectMesh.h"
Expand All @@ -15,7 +14,7 @@
#pragma warning( disable: 5054 )
#pragma warning( disable: 5220 )
#if _MSC_VER >= 1937 // Visual Studio 2022 version 17.7
#pragma warning( disable: 5267 ) //definition of implicit copy constructor is deprecated because it has a user-provided destructor
#pragma warning( disable: 5267 ) // definition of implicit copy constructor is deprecated because it has a user-provided destructor
#endif
#include <opencascade/BRep_Tool.hxx>
#include <opencascade/BRepMesh_IncrementalMesh.hxx>
Expand All @@ -31,28 +30,25 @@
#include <opencascade/TopoDS.hxx>
#pragma warning( pop )

#if ( OCC_VERSION_MAJOR < 7 ) || ( OCC_VERSION_MAJOR == 7 && OCC_VERSION_MINOR < 5 )
namespace MR::MeshLoad
{

Expected<std::shared_ptr<Object>, std::string> fromSceneStepFile( const std::filesystem::path& path, const MeshLoadSettings& settings /*= {}*/ )
{
// TODO: support OpenCASCADE 7.3
return unexpected( "Unsupported: outdated OpenCASCADE version" );
}

Expected<std::shared_ptr<Object>, std::string> fromSceneStepFile( std::istream& in, const MeshLoadSettings& settings /*= {}*/ )
{
return unexpected( "Unsupported: outdated OpenCASCADE version" );
}

} // namespace MR::MeshLoad
#else
namespace
{

using namespace MR;

// updated BRepMesh API support
// https://dev.opencascade.org/doc/overview/html/occt__upgrade.html#upgrade_740_changed_api_of_brepmesh
#define MODERN_BREPMESH_SUPPORTED ( ( ( OCC_VERSION_MAJOR > 7 ) || ( OCC_VERSION_MAJOR == 7 && OCC_VERSION_MINOR >= 4 ) ) )
// updated Message interface support
// https://dev.opencascade.org/doc/overview/html/occt__upgrade.html#upgrade_750_message_messenger
// https://dev.opencascade.org/doc/overview/html/occt__upgrade.html#upgrade_750_message_printer
#define MODERN_MESSAGE_SUPPORTED ( ( OCC_VERSION_MAJOR > 7 ) || ( OCC_VERSION_MAJOR == 7 && OCC_VERSION_MINOR >= 5 ) )
// reading STEP data from streams support
// https://www.opencascade.com/open-cascade-technology-7-5-0-released/
#define STEP_READSTREAM_SUPPORTED ( ( ( OCC_VERSION_MAJOR > 7 ) || ( OCC_VERSION_MAJOR == 7 && OCC_VERSION_MINOR >= 5 ) ) )
// reading STEP data fixed
#define STEP_READER_FIXED ( ( ( OCC_VERSION_MAJOR > 7 ) || ( OCC_VERSION_MAJOR == 7 && OCC_VERSION_MINOR >= 7 ) ) )

#if MODERN_MESSAGE_SUPPORTED
/// spdlog adaptor for OpenCASCADE logging system
class SpdlogPrinter final : public Message_Printer
{
Expand Down Expand Up @@ -103,6 +99,9 @@ class MessageHandler
};

MessageHandler messageHandler;
#else
#pragma message( "Log redirecting is currently unsupported for OpenCASCADE versions prior to 7.4" )
#endif

struct FeatureData
{
Expand All @@ -124,7 +123,11 @@ Mesh loadSolid( const TopoDS_Shape& solid )
MR_TIMER

// TODO: expose parameters
#if MODERN_BREPMESH_SUPPORTED
IMeshTools_Parameters parameters;
#else
BRepMesh_FastDiscret::Parameters parameters;
#endif
parameters.Angle = 0.1;
parameters.Deflection = 0.5;
parameters.Relative = false;
Expand Down Expand Up @@ -206,97 +209,113 @@ Mesh loadSolid( const TopoDS_Shape& solid )
return Mesh::fromPointTriples( triples, true );
}

std::mutex cOpenCascadeMutex = {};
#if STEP_READER_FIXED
// read STEP file without any work-arounds
VoidOrErrStr readStepData( STEPControl_Reader& reader, std::istream& in, const ProgressCallback& )
{
auto ret = reader.ReadStream( "STEP file", in );
if ( ret != IFSelect_RetDone )
return unexpected( "Failed to read STEP model" );
return {};
}
#elif STEP_READSTREAM_SUPPORTED
// read STEP file with the 'save-load' work-around
// some STEP models are loaded broken in OpenCASCADE prior to 7.7
VoidOrErrStr readStepData( STEPControl_Reader& reader, std::istream& in, const ProgressCallback& cb )
{
STEPControl_Reader auxReader;
auto ret = auxReader.ReadStream( "STEP file", in );
if ( ret != IFSelect_RetDone )
return unexpected( "Failed to read STEP model" );

// some STEP model are loaded broken in OpenCASCADE prior to 7.7
#define REQUIRE_MODEL_REPAIR ( ( OCC_VERSION_MAJOR < 7 ) || ( OCC_VERSION_MAJOR == 7 && OCC_VERSION_MINOR < 7 ) )
if ( !reportProgress( cb, 0.50f ) )
return unexpected( std::string( "Loading canceled" ) );

}
const auto model = auxReader.StepModel();
const auto protocol = Handle( StepData_Protocol )::DownCast( model->Protocol() );

namespace MR::MeshLoad
{
StepData_StepWriter sw( model );
sw.SendModel( protocol );

Expected<std::shared_ptr<Object>, std::string> fromSceneStepFile( const std::filesystem::path& path, const MeshLoadSettings& settings /*= {}*/ )
{
std::ifstream in( path, std::ifstream::binary );
if ( !in )
return unexpected( std::string( "Cannot open file for reading " ) + utf8string( path ) );
std::stringstream buffer;
if ( !sw.Print( buffer ) )
return unexpected( "Failed to repair STEP model" );

auto result = fromSceneStepFile( in, settings );
if ( !result )
return addFileNameInError( result, path );
if ( !reportProgress( cb, 0.65f ) )
return unexpected( std::string( "Loading canceled" ) );

if ( auto& obj = *result )
{
if ( obj->name().empty() )
obj->setName( utf8string( path.stem() ) );
}
buffer.seekp( 0, std::ios::beg );
ret = reader.ReadStream( "STEP file", buffer );
if ( ret != IFSelect_RetDone )
return unexpected( "Failed to read STEP model" );

return result;
return {};
}

Expected<std::shared_ptr<Object>, std::string> fromSceneStepFile( std::istream& in, const MeshLoadSettings& settings /*= {}*/ )
#else
// read STEP file with the 'save-load' work-around
// loading from a file stream is not supported in OpenCASCADE prior to 7.5
std::filesystem::path getStepTemporaryDirectory()
{
MR_TIMER
const auto path = std::filesystem::temp_directory_path() / "MeshLib_MeshLoadStep";
if ( !std::filesystem::exists( path ) )
std::filesystem::create_directory( path );
return path;
}

// NOTE: OpenCASCADE STEP reader is NOT thread-safe
std::unique_lock lock( cOpenCascadeMutex );
VoidOrErrStr readStepData( STEPControl_Reader& reader, const std::filesystem::path& path, const ProgressCallback& cb )
{
STEPControl_Reader auxReader;
auto ret = auxReader.ReadFile( path.c_str() );
if ( ret != IFSelect_RetDone )
return unexpected( "Failed to read STEP model" );

#if REQUIRE_MODEL_REPAIR
// repair (?) STEP model using read and write operations
// TODO: repair the model directly
std::stringstream buffer;
{
STEPControl_Reader reader;
const auto ret = reader.ReadStream( "STEP file", in );
if ( ret != IFSelect_RetDone )
return unexpected( "Failed to read STEP model" );
if ( !reportProgress( cb, 0.50f ) )
return unexpected( std::string( "Loading canceled" ) );

if ( !reportProgress( settings.callback, 0.15f ) )
return unexpected( std::string( "Loading canceled" ) );
const auto model = auxReader.StepModel();
const auto protocol = Handle( StepData_Protocol )::DownCast( model->Protocol() );

const auto model = reader.StepModel();
const auto protocol = Handle( StepData_Protocol )::DownCast( model->Protocol() );
StepData_StepWriter sw( model );
sw.SendModel( protocol );

StepData_StepWriter sw( model );
sw.SendModel( protocol );
if ( !sw.Print( buffer ) )
const auto auxFilePath = getStepTemporaryDirectory() / "auxFile.step";
{
std::ofstream ofs( auxFilePath );
if ( !sw.Print( ofs ) )
return unexpected( "Failed to repair STEP model" );

if ( !reportProgress( settings.callback, 0.2f ) )
return unexpected( std::string( "Loading canceled" ) );
}
buffer.seekp( 0, std::ios::beg );
auto& input = buffer;
#else
auto& input = in;
#endif

std::deque<TopoDS_Shape> shapes;
{
STEPControl_Reader reader;
if ( !reportProgress( cb, 0.65f ) )
return unexpected( std::string( "Loading canceled" ) );

const auto ret = reader.ReadStream( "STEP file", input );
if ( ret != IFSelect_RetDone )
return unexpected( "Failed to read STEP model" );
ret = reader.ReadFile( auxFilePath.c_str() );
if ( ret != IFSelect_RetDone )
return unexpected( "Failed to read STEP model" );

if ( !reportProgress( settings.callback, 0.3f ) )
return unexpected( std::string( "Loading canceled" ) );
std::filesystem::remove( auxFilePath );

const auto cb1 = subprogress( settings.callback, 0.30f, 0.74f );
const auto rootCount = reader.NbRootsForTransfer();
for ( auto i = 1; i <= rootCount; ++i )
{
reader.TransferRoot( i );
if ( !reportProgress( cb1, ( float )i / ( float )rootCount ) )
return unexpected( std::string( "Loading canceled" ) );
}
if ( !reportProgress( settings.callback, 0.9f ) )
return unexpected( std::string( "Loading canceled" ) );
return {};
}
#endif

for ( auto i = 1; i <= reader.NbShapes(); ++i )
shapes.emplace_back( reader.Shape( i ) );
Expected<std::shared_ptr<Object>, std::string> stepModelToScene( STEPControl_Reader& reader, const MeshLoadSettings&, const ProgressCallback& cb )
{
MR_TIMER

const auto cb1 = subprogress( cb, 0.30f, 0.74f );
const auto rootCount = reader.NbRootsForTransfer();
for ( auto i = 1; i <= rootCount; ++i )
{
reader.TransferRoot( i );
if ( !reportProgress( cb1, ( float )i / ( float )rootCount ) )
return unexpected( std::string( "Loading canceled" ) );
}
if ( !reportProgress( cb, 0.9f ) )
return unexpected( std::string( "Loading canceled" ) );

std::deque<TopoDS_Shape> shapes;
for ( auto i = 1; i <= reader.NbShapes(); ++i )
shapes.emplace_back( reader.Shape( i ) );

// TODO: preserve shape-solid hierarchy? (not sure about actual shape count in real models)
std::deque<TopoDS_Shape> solids;
Expand All @@ -319,12 +338,11 @@ Expected<std::shared_ptr<Object>, std::string> fromSceneStepFile( std::istream&
}
else
{
auto cb2 = subprogress( settings.callback, 0.90f, 1.0f );

auto result = std::make_shared<ObjectMesh>();
// create empty parent mesh
result->setMesh( std::make_shared<Mesh>() );

auto cb2 = subprogress( cb, 0.90f, 1.0f );
std::vector<std::shared_ptr<Object>> children( solids.size() );
const bool normalFinished = ParallelFor( size_t( 0 ), solids.size(), [&] ( size_t i )
{
Expand All @@ -344,6 +362,88 @@ Expected<std::shared_ptr<Object>, std::string> fromSceneStepFile( std::istream&
}
}

std::mutex cOpenCascadeMutex = {};
#if !STEP_READSTREAM_SUPPORTED
std::mutex cOpenCascadeTempFileMutex = {};
#endif

}

#if STEP_READSTREAM_SUPPORTED
namespace MR::MeshLoad
{

Expected<std::shared_ptr<Object>, std::string> fromSceneStepFile( const std::filesystem::path& path, const MeshLoadSettings& settings )
{
std::ifstream in( path, std::ifstream::binary );
if ( !in )
return unexpected( std::string( "Cannot open file for reading " ) + utf8string( path ) );

auto result = fromSceneStepFile( in, settings );
if ( !result )
return addFileNameInError( result, path );

if ( auto& obj = *result )
{
if ( obj->name().empty() )
obj->setName( utf8string( path.stem() ) );
}

return result;
}

Expected<std::shared_ptr<Object>, std::string> fromSceneStepFile( std::istream& in, const MeshLoadSettings& settings )
{
MR_TIMER

// NOTE: OpenCASCADE STEP reader is NOT thread-safe
std::unique_lock lock( cOpenCascadeMutex );

const auto cb1 = subprogress( settings.callback, 0.00f, 0.90f );
STEPControl_Reader reader;
const auto ret = readStepData( reader, in, cb1 );
if ( !ret.has_value() )
return unexpected( ret.error() );

const auto cb2 = subprogress( settings.callback, 0.90f, 1.00f );
return stepModelToScene( reader, settings, cb2 );
}

} // namespace MR::MeshLoad
#else
namespace MR::MeshLoad
{

Expected<std::shared_ptr<Object>, std::string> fromSceneStepFile( std::istream& in, const MeshLoadSettings& settings )
{
std::unique_lock lock( cOpenCascadeTempFileMutex );
const auto tempFileName = getStepTemporaryDirectory() / "tempFile.step";
{
std::ofstream ofs( tempFileName, std::ios::binary );
if ( !ofs )
return unexpected( std::string( "Cannot open buffer file" ) );
ofs << in.rdbuf();
}
return fromSceneStepFile( tempFileName, settings );
}

Expected<std::shared_ptr<Object>, std::string> fromSceneStepFile( const std::filesystem::path& path, const MeshLoadSettings& settings )
{
MR_TIMER

// NOTE: OpenCASCADE STEP reader is NOT thread-safe
std::unique_lock lock( cOpenCascadeMutex );

const auto cb1 = subprogress( settings.callback, 0.00f, 0.90f );
STEPControl_Reader reader;
const auto ret = readStepData( reader, path, cb1 );
if ( !ret.has_value() )
return unexpected( ret.error() );

const auto cb2 = subprogress( settings.callback, 0.90f, 1.00f );
return stepModelToScene( reader, settings, cb2 );
}

} // namespace MR::MeshLoad
#endif
#endif

0 comments on commit f6e31fa

Please sign in to comment.