From 711fbc42b2d19873654fe6bd35041f8bb9dd8b62 Mon Sep 17 00:00:00 2001 From: Pasukhin Dmitry Date: Fri, 8 Aug 2025 12:05:27 +0100 Subject: [PATCH] Data Exchange, DE_Wrapper - Implement Stream support (#663) - Adds stream-based read/write methods to multiple data exchange providers - Refactors underlying APIs (VrmlAPI_Writer, StlAPI_Writer, RWStl) to support stream operations - Implements comprehensive test coverage for stream functionality - Adds validation utilities for improved error handling --- .../TKDE/DE/DE_ConfigurationNode.cxx | 7 + .../TKDE/DE/DE_ConfigurationNode.hxx | 12 +- src/DataExchange/TKDE/DE/DE_Provider.cxx | 121 ++++ src/DataExchange/TKDE/DE/DE_Provider.hxx | 130 ++++ .../TKDE/DE/DE_ValidationUtils.cxx | 345 +++++++++++ .../TKDE/DE/DE_ValidationUtils.hxx | 118 ++++ src/DataExchange/TKDE/DE/DE_Wrapper.cxx | 341 ++++++++++- src/DataExchange/TKDE/DE/DE_Wrapper.hxx | 118 ++++ src/DataExchange/TKDE/DE/FILES.cmake | 2 + .../TKDEGLTF/DEGLTF/DEGLTF_Provider.cxx | 26 +- .../TKDEIGES/DEIGES/DEIGES_Provider.cxx | 488 ++++++++++++--- .../TKDEOBJ/DEOBJ/DEOBJ_Provider.cxx | 12 +- .../TKDEPLY/DEPLY/DEPLY_Provider.cxx | 8 +- .../DESTEP/DESTEP_ConfigurationNode.cxx | 7 + .../DESTEP/DESTEP_ConfigurationNode.hxx | 4 + .../TKDESTEP/DESTEP/DESTEP_Provider.cxx | 473 +++++++++++++-- .../TKDESTEP/DESTEP/DESTEP_Provider.hxx | 88 +++ .../TKDESTEP/GTests/DESTEP_Provider_Test.cxx | 475 +++++++++++++++ src/DataExchange/TKDESTEP/GTests/FILES.cmake | 1 + .../TKDESTL/DESTL/DESTL_ConfigurationNode.cxx | 7 + .../TKDESTL/DESTL/DESTL_ConfigurationNode.hxx | 4 + .../TKDESTL/DESTL/DESTL_Provider.cxx | 337 ++++++++++- .../TKDESTL/DESTL/DESTL_Provider.hxx | 88 +++ .../TKDESTL/GTests/DESTL_Provider_Test.cxx | 559 ++++++++++++++++++ src/DataExchange/TKDESTL/GTests/FILES.cmake | 1 + src/DataExchange/TKDESTL/RWStl/RWStl.cxx | 195 +++--- src/DataExchange/TKDESTL/RWStl/RWStl.hxx | 40 +- .../TKDESTL/StlAPI/StlAPI_Reader.cxx | 21 + .../TKDESTL/StlAPI/StlAPI_Reader.hxx | 7 + .../TKDESTL/StlAPI/StlAPI_Writer.cxx | 23 +- .../TKDESTL/StlAPI/StlAPI_Writer.hxx | 7 + .../DEVRML/DEVRML_ConfigurationNode.cxx | 7 + .../DEVRML/DEVRML_ConfigurationNode.hxx | 4 + .../TKDEVRML/DEVRML/DEVRML_Provider.cxx | 521 +++++++++++----- .../TKDEVRML/DEVRML/DEVRML_Provider.hxx | 88 +++ .../TKDEVRML/GTests/DEVRML_Provider_Test.cxx | 519 ++++++++++++++++ src/DataExchange/TKDEVRML/GTests/FILES.cmake | 1 + .../TKDEVRML/VrmlAPI/VrmlAPI_Writer.cxx | 151 ++--- .../TKDEVRML/VrmlAPI/VrmlAPI_Writer.hxx | 24 +- 39 files changed, 4884 insertions(+), 496 deletions(-) create mode 100644 src/DataExchange/TKDE/DE/DE_ValidationUtils.cxx create mode 100644 src/DataExchange/TKDE/DE/DE_ValidationUtils.hxx create mode 100644 src/DataExchange/TKDESTEP/GTests/DESTEP_Provider_Test.cxx create mode 100644 src/DataExchange/TKDESTL/GTests/DESTL_Provider_Test.cxx create mode 100644 src/DataExchange/TKDEVRML/GTests/DEVRML_Provider_Test.cxx diff --git a/src/DataExchange/TKDE/DE/DE_ConfigurationNode.cxx b/src/DataExchange/TKDE/DE/DE_ConfigurationNode.cxx index a7e057b719..bdd37294e0 100644 --- a/src/DataExchange/TKDE/DE/DE_ConfigurationNode.cxx +++ b/src/DataExchange/TKDE/DE/DE_ConfigurationNode.cxx @@ -103,6 +103,13 @@ bool DE_ConfigurationNode::IsExportSupported() const //================================================================================================= +bool DE_ConfigurationNode::IsStreamSupported() const +{ + return false; +} + +//================================================================================================= + bool DE_ConfigurationNode::CheckExtension(const TCollection_AsciiString& theExtension) const { TCollection_AsciiString anExtension(theExtension); diff --git a/src/DataExchange/TKDE/DE/DE_ConfigurationNode.hxx b/src/DataExchange/TKDE/DE/DE_ConfigurationNode.hxx index 870729b40a..68075ee15b 100644 --- a/src/DataExchange/TKDE/DE/DE_ConfigurationNode.hxx +++ b/src/DataExchange/TKDE/DE/DE_ConfigurationNode.hxx @@ -92,14 +92,18 @@ public: const Standard_Boolean theToKeep); public: - //! Checks the import supporting - //! @return Standard_True if import is support + //! Checks for import support. + //! @return Standard_True if import is supported Standard_EXPORT virtual bool IsImportSupported() const; - //! Checks the export supporting - //! @return Standard_True if export is support + //! Checks for export support. + //! @return Standard_True if export is supported Standard_EXPORT virtual bool IsExportSupported() const; + //! Checks for stream support. + //! @return Standard_True if streams are supported + Standard_EXPORT virtual bool IsStreamSupported() const; + //! Gets CAD format name of associated provider //! @return provider CAD format Standard_EXPORT virtual TCollection_AsciiString GetFormat() const = 0; diff --git a/src/DataExchange/TKDE/DE/DE_Provider.cxx b/src/DataExchange/TKDE/DE/DE_Provider.cxx index 77572d3859..bc3bfe2cc8 100644 --- a/src/DataExchange/TKDE/DE/DE_Provider.cxx +++ b/src/DataExchange/TKDE/DE/DE_Provider.cxx @@ -15,6 +15,7 @@ #include #include +#include IMPLEMENT_STANDARD_RTTIEXT(DE_Provider, Standard_Transient) @@ -148,3 +149,123 @@ Standard_Boolean DE_Provider::Write(const TCollection_AsciiString& thePath, << " doesn't support write operation"; return Standard_False; } + +//================================================================================================= + +Standard_Boolean DE_Provider::Read(ReadStreamList& theStreams, + const Handle(TDocStd_Document)& theDocument, + Handle(XSControl_WorkSession)& theWS, + const Message_ProgressRange& theProgress) +{ + (void)theStreams; + (void)theDocument; + (void)theWS; + (void)theProgress; + Message::SendFail() << "Error: provider " << GetFormat() << " " << GetVendor() + << " doesn't support stream read operation"; + return Standard_False; +} + +//================================================================================================= + +Standard_Boolean DE_Provider::Write(WriteStreamList& theStreams, + const Handle(TDocStd_Document)& theDocument, + Handle(XSControl_WorkSession)& theWS, + const Message_ProgressRange& theProgress) +{ + (void)theStreams; + (void)theDocument; + (void)theWS; + (void)theProgress; + Message::SendFail() << "Error: provider " << GetFormat() << " " << GetVendor() + << " doesn't support stream write operation"; + return Standard_False; +} + +//================================================================================================= + +Standard_Boolean DE_Provider::Read(ReadStreamList& theStreams, + TopoDS_Shape& theShape, + Handle(XSControl_WorkSession)& theWS, + const Message_ProgressRange& theProgress) +{ + (void)theStreams; + (void)theShape; + (void)theWS; + (void)theProgress; + Message::SendFail() << "Error: provider " << GetFormat() << " " << GetVendor() + << " doesn't support stream read operation"; + return Standard_False; +} + +//================================================================================================= + +Standard_Boolean DE_Provider::Write(WriteStreamList& theStreams, + const TopoDS_Shape& theShape, + Handle(XSControl_WorkSession)& theWS, + const Message_ProgressRange& theProgress) +{ + (void)theStreams; + (void)theShape; + (void)theWS; + (void)theProgress; + Message::SendFail() << "Error: provider " << GetFormat() << " " << GetVendor() + << " doesn't support stream write operation"; + return Standard_False; +} + +//================================================================================================= + +Standard_Boolean DE_Provider::Read(ReadStreamList& theStreams, + const Handle(TDocStd_Document)& theDocument, + const Message_ProgressRange& theProgress) +{ + (void)theStreams; + (void)theDocument; + (void)theProgress; + Message::SendFail() << "Error: provider " << GetFormat() << " " << GetVendor() + << " doesn't support stream read operation"; + return Standard_False; +} + +//================================================================================================= + +Standard_Boolean DE_Provider::Write(WriteStreamList& theStreams, + const Handle(TDocStd_Document)& theDocument, + const Message_ProgressRange& theProgress) +{ + (void)theStreams; + (void)theDocument; + (void)theProgress; + Message::SendFail() << "Error: provider " << GetFormat() << " " << GetVendor() + << " doesn't support stream write operation"; + return Standard_False; +} + +//================================================================================================= + +Standard_Boolean DE_Provider::Read(ReadStreamList& theStreams, + TopoDS_Shape& theShape, + const Message_ProgressRange& theProgress) +{ + (void)theStreams; + (void)theShape; + (void)theProgress; + Message::SendFail() << "Error: provider " << GetFormat() << " " << GetVendor() + << " doesn't support stream read operation"; + return Standard_False; +} + +//================================================================================================= + +Standard_Boolean DE_Provider::Write(WriteStreamList& theStreams, + const TopoDS_Shape& theShape, + const Message_ProgressRange& theProgress) +{ + (void)theStreams; + (void)theShape; + (void)theProgress; + Message::SendFail() << "Error: provider " << GetFormat() << " " << GetVendor() + << " doesn't support stream write operation"; + return Standard_False; +} diff --git a/src/DataExchange/TKDE/DE/DE_Provider.hxx b/src/DataExchange/TKDE/DE/DE_Provider.hxx index 4613250a5b..0023f07282 100644 --- a/src/DataExchange/TKDE/DE/DE_Provider.hxx +++ b/src/DataExchange/TKDE/DE/DE_Provider.hxx @@ -15,6 +15,9 @@ #define _DE_Provider_HeaderFile #include +#include +#include +#include class DE_ConfigurationNode; class TopoDS_Shape; @@ -43,6 +46,45 @@ class DE_Provider : public Standard_Transient public: DEFINE_STANDARD_RTTIEXT(DE_Provider, Standard_Transient) + //! Node to store write stream information + //! Contains relative path and reference to output stream + struct WriteStreamNode + { + TCollection_AsciiString Path; //!< Relative path to the output file + Standard_OStream& Stream; //!< Reference to output stream + + //! Constructor + WriteStreamNode(const TCollection_AsciiString& thePath, Standard_OStream& theStream) + : Path(thePath), + Stream(theStream) + { + } + }; + + //! Node to store read stream information + //! Contains relative path and reference to input stream + struct ReadStreamNode + { + TCollection_AsciiString Path; //!< Relative path to the input file + Standard_IStream& Stream; //!< Reference to input stream + + //! Constructor + ReadStreamNode(const TCollection_AsciiString& thePath, Standard_IStream& theStream) + : Path(thePath), + Stream(theStream) + { + } + }; + +public: + //! List to store write stream nodes + //! First element is the main stream, others are for internal referencing + using WriteStreamList = NCollection_List; + + //! List to store read stream nodes + //! First element is the main stream, others are for internal referencing + using ReadStreamList = NCollection_List; + public: //! Default constructor //! Configure translation process with global configuration @@ -77,6 +119,30 @@ public: Handle(XSControl_WorkSession)& theWS, const Message_ProgressRange& theProgress = Message_ProgressRange()); + //! Reads streams according to internal configuration + //! @param[in] theStreams streams to read from + //! @param[out] theDocument document to save result + //! @param[in] theWS current work session + //! @param[in] theProgress progress indicator + //! @return True if Read was successful + Standard_EXPORT virtual Standard_Boolean Read( + ReadStreamList& theStreams, + const Handle(TDocStd_Document)& theDocument, + Handle(XSControl_WorkSession)& theWS, + const Message_ProgressRange& theProgress = Message_ProgressRange()); + + //! Writes streams according to internal configuration + //! @param[in] theStreams streams to write to + //! @param[out] theDocument document to export + //! @param[in] theWS current work session + //! @param[in] theProgress progress indicator + //! @return True if Write was successful + Standard_EXPORT virtual Standard_Boolean Write( + WriteStreamList& theStreams, + const Handle(TDocStd_Document)& theDocument, + Handle(XSControl_WorkSession)& theWS, + const Message_ProgressRange& theProgress = Message_ProgressRange()); + //! Reads a CAD file, according internal configuration //! @param[in] thePath path to the import CAD file //! @param[out] theDocument document to save result @@ -97,6 +163,26 @@ public: const Handle(TDocStd_Document)& theDocument, const Message_ProgressRange& theProgress = Message_ProgressRange()); + //! Reads streams according to internal configuration + //! @param[in] theStreams streams to read from + //! @param[out] theDocument document to save result + //! @param[in] theProgress progress indicator + //! @return True if Read was successful + Standard_EXPORT virtual Standard_Boolean Read( + ReadStreamList& theStreams, + const Handle(TDocStd_Document)& theDocument, + const Message_ProgressRange& theProgress = Message_ProgressRange()); + + //! Writes streams according to internal configuration + //! @param[in] theStreams streams to write to + //! @param[out] theDocument document to export + //! @param[in] theProgress progress indicator + //! @return True if Write was successful + Standard_EXPORT virtual Standard_Boolean Write( + WriteStreamList& theStreams, + const Handle(TDocStd_Document)& theDocument, + const Message_ProgressRange& theProgress = Message_ProgressRange()); + //! Reads a CAD file, according internal configuration //! @param[in] thePath path to the import CAD file //! @param[out] theShape shape to save result @@ -121,6 +207,30 @@ public: Handle(XSControl_WorkSession)& theWS, const Message_ProgressRange& theProgress = Message_ProgressRange()); + //! Reads streams according to internal configuration + //! @param[in] theStreams streams to read from + //! @param[out] theShape shape to save result + //! @param[in] theWS current work session + //! @param[in] theProgress progress indicator + //! @return True if Read was successful + Standard_EXPORT virtual Standard_Boolean Read( + ReadStreamList& theStreams, + TopoDS_Shape& theShape, + Handle(XSControl_WorkSession)& theWS, + const Message_ProgressRange& theProgress = Message_ProgressRange()); + + //! Writes streams according to internal configuration + //! @param[in] theStreams streams to write to + //! @param[out] theShape shape to export + //! @param[in] theWS current work session + //! @param[in] theProgress progress indicator + //! @return True if Write was successful + Standard_EXPORT virtual Standard_Boolean Write( + WriteStreamList& theStreams, + const TopoDS_Shape& theShape, + Handle(XSControl_WorkSession)& theWS, + const Message_ProgressRange& theProgress = Message_ProgressRange()); + //! Reads a CAD file, according internal configuration //! @param[in] thePath path to the import CAD file //! @param[out] theShape shape to save result @@ -141,6 +251,26 @@ public: const TopoDS_Shape& theShape, const Message_ProgressRange& theProgress = Message_ProgressRange()); + //! Reads streams according to internal configuration + //! @param[in] theStreams streams to read from + //! @param[out] theShape shape to save result + //! @param[in] theProgress progress indicator + //! @return True if Read was successful + Standard_EXPORT virtual Standard_Boolean Read( + ReadStreamList& theStreams, + TopoDS_Shape& theShape, + const Message_ProgressRange& theProgress = Message_ProgressRange()); + + //! Writes streams according to internal configuration + //! @param[in] theStreams streams to write to + //! @param[out] theShape shape to export + //! @param[in] theProgress progress indicator + //! @return True if Write was successful + Standard_EXPORT virtual Standard_Boolean Write( + WriteStreamList& theStreams, + const TopoDS_Shape& theShape, + const Message_ProgressRange& theProgress = Message_ProgressRange()); + public: //! Gets CAD format name of associated provider //! @return provider CAD format diff --git a/src/DataExchange/TKDE/DE/DE_ValidationUtils.cxx b/src/DataExchange/TKDE/DE/DE_ValidationUtils.cxx new file mode 100644 index 0000000000..4bc05b78e3 --- /dev/null +++ b/src/DataExchange/TKDE/DE/DE_ValidationUtils.cxx @@ -0,0 +1,345 @@ +// Copyright (c) 2025 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +//================================================================================================= + +Standard_Boolean DE_ValidationUtils::ValidateConfigurationNode( + const Handle(DE_ConfigurationNode)& theNode, + const Handle(Standard_Type)& theExpectedType, + const TCollection_AsciiString& theContext, + const Standard_Boolean theIsVerbose) +{ + if (theNode.IsNull()) + { + if (theIsVerbose) + { + Message::SendFail() << "Error during " << theContext << ": Configuration Node is null"; + } + return Standard_False; + } + + if (!theNode->IsKind(theExpectedType)) + { + if (theIsVerbose) + { + Message::SendFail() << "Error during " << theContext + << ": Configuration Node is not of expected type. Expected: " + << theExpectedType->Name() << ", got: " << theNode->DynamicType()->Name(); + } + return Standard_False; + } + + return Standard_True; +} + +//================================================================================================= + +Standard_Boolean DE_ValidationUtils::ValidateFileForReading( + const TCollection_AsciiString& thePath, + const TCollection_AsciiString& theContext, + const Standard_Boolean theIsVerbose) +{ + if (thePath.IsEmpty()) + { + if (theIsVerbose) + { + Message::SendFail() << "Error during " << theContext << ": File path is empty"; + } + return Standard_False; + } + + try + { + OSD_Path aOSDPath(thePath); + OSD_File aFile(aOSDPath); + + // Check if file exists + if (!aFile.Exists()) + { + if (theIsVerbose) + { + Message::SendFail() << "Error during " << theContext << ": File '" << thePath + << "' does not exist"; + } + return Standard_False; + } + + // Try to open for reading to verify permissions + std::ifstream aTestFile(thePath.ToCString()); + if (!aTestFile.is_open() || !aTestFile.good()) + { + if (theIsVerbose) + { + Message::SendFail() << "Error during " << theContext << ": Cannot open file '" << thePath + << "' for reading"; + } + return Standard_False; + } + } + catch (const std::exception& anException) + { + if (theIsVerbose) + { + Message::SendFail() << "Error during " << theContext << ": Cannot access file '" << thePath + << "': " << anException.what(); + } + return Standard_False; + } + + return Standard_True; +} + +//================================================================================================= + +Standard_Boolean DE_ValidationUtils::ValidateFileForWriting( + const TCollection_AsciiString& thePath, + const TCollection_AsciiString& theContext, + const Standard_Boolean theIsVerbose) +{ + if (thePath.IsEmpty()) + { + if (theIsVerbose) + { + Message::SendFail() << "Error during " << theContext << ": File path is empty"; + } + return Standard_False; + } + + try + { + // Try to open for writing to verify permissions + std::ofstream aTestFile(thePath.ToCString(), std::ios::out | std::ios::app); + if (!aTestFile.is_open() || !aTestFile.good()) + { + if (theIsVerbose) + { + Message::SendFail() << "Error during " << theContext << ": Cannot open file '" << thePath + << "' for writing"; + } + return Standard_False; + } + // File will be closed automatically when aTestFile goes out of scope + } + catch (const std::exception& anException) + { + if (theIsVerbose) + { + Message::SendFail() << "Error during " << theContext << ": Cannot access file '" << thePath + << "': " << anException.what(); + } + return Standard_False; + } + + return Standard_True; +} + +//================================================================================================= + +Standard_Boolean DE_ValidationUtils::ValidateReadStreamList( + const DE_Provider::ReadStreamList& theStreams, + const TCollection_AsciiString& theContext, + const Standard_Boolean theIsVerbose) +{ + if (theStreams.IsEmpty()) + { + if (theIsVerbose) + { + Message::SendFail() << "Error during " << theContext << ": Stream list is empty"; + } + return Standard_False; + } + + if (theStreams.Size() > 1) + { + if (theIsVerbose) + { + Message::SendWarning() << "Warning during " << theContext << ": Received " + << theStreams.Size() << " streams, using only the first one"; + } + } + + // Additional validation for input streams + try + { + const DE_Provider::ReadStreamNode& aNode = theStreams.First(); + if (aNode.Stream.fail()) + { + if (theIsVerbose) + { + TCollection_AsciiString aKeyInfo = aNode.Path.IsEmpty() ? "" : aNode.Path; + Message::SendFail() << "Error during " << theContext << ": Input stream '" << aKeyInfo + << "' is in invalid state"; + } + return Standard_False; + } + } + catch (const std::exception&) + { + if (theIsVerbose) + { + const DE_Provider::ReadStreamNode& aNode = theStreams.First(); + TCollection_AsciiString aKeyInfo = aNode.Path.IsEmpty() ? "" : aNode.Path; + Message::SendFail() << "Error during " << theContext << ": Cannot access input stream '" + << aKeyInfo << "'"; + } + return Standard_False; + } + + return Standard_True; +} + +//================================================================================================= + +Standard_Boolean DE_ValidationUtils::ValidateWriteStreamList( + DE_Provider::WriteStreamList& theStreams, + const TCollection_AsciiString& theContext, + const Standard_Boolean theIsVerbose) +{ + if (theStreams.IsEmpty()) + { + if (theIsVerbose) + { + Message::SendFail() << "Error during " << theContext << ": Stream list is empty"; + } + return Standard_False; + } + + if (theStreams.Size() > 1) + { + if (theIsVerbose) + { + Message::SendWarning() << "Warning during " << theContext << ": Received " + << theStreams.Size() << " streams, using only the first one"; + } + } + + // Additional validation for output streams + try + { + const DE_Provider::WriteStreamNode& aNode = theStreams.First(); + if (aNode.Stream.fail()) + { + if (theIsVerbose) + { + TCollection_AsciiString aKeyInfo = aNode.Path.IsEmpty() ? "" : aNode.Path; + Message::SendFail() << "Error during " << theContext << ": Output stream '" << aKeyInfo + << "' is in invalid state"; + } + return Standard_False; + } + } + catch (const std::exception&) + { + if (theIsVerbose) + { + const DE_Provider::WriteStreamNode& aNode = theStreams.First(); + TCollection_AsciiString aKeyInfo = aNode.Path.IsEmpty() ? "" : aNode.Path; + Message::SendFail() << "Error during " << theContext << ": Cannot access output stream '" + << aKeyInfo << "'"; + } + return Standard_False; + } + + return Standard_True; +} + +//================================================================================================= + +Standard_Boolean DE_ValidationUtils::ValidateDocument(const Handle(TDocStd_Document)& theDocument, + const TCollection_AsciiString& theContext, + const Standard_Boolean theIsVerbose) +{ + if (theDocument.IsNull()) + { + if (theIsVerbose) + { + Message::SendFail() << "Error during " << theContext << ": Document handle is null"; + } + return Standard_False; + } + + return Standard_True; +} + +//================================================================================================= + +Standard_Boolean DE_ValidationUtils::WarnLengthUnitNotSupported( + const Standard_Real theLengthUnit, + const TCollection_AsciiString& theContext, + const Standard_Boolean theIsVerbose) +{ + if (theIsVerbose && theLengthUnit != 1.0) + { + Message::SendWarning() << "Warning during " << theContext + << ": Format doesn't support custom length unit scaling (unit: " + << theLengthUnit << ")"; + } + + return Standard_True; +} + +//================================================================================================= + +Standard_Boolean DE_ValidationUtils::CreateContentBuffer(const TCollection_AsciiString& thePath, + Handle(NCollection_Buffer)& theBuffer) +{ + const Handle(OSD_FileSystem)& aFileSystem = OSD_FileSystem::DefaultFileSystem(); + std::shared_ptr aStream = + aFileSystem->OpenIStream(thePath, std::ios::in | std::ios::binary); + + if (aStream.get() == nullptr) + { + theBuffer.Nullify(); + return Standard_False; + } + + return CreateContentBuffer(*aStream, theBuffer); +} + +//================================================================================================= + +Standard_Boolean DE_ValidationUtils::CreateContentBuffer(std::istream& theStream, + Handle(NCollection_Buffer)& theBuffer) +{ + constexpr std::streamsize aBufferLength = 2048; + + theBuffer = + new NCollection_Buffer(NCollection_BaseAllocator::CommonBaseAllocator(), aBufferLength); + + // Save current stream position + std::streampos aOriginalPos = theStream.tellg(); + + theStream.read(reinterpret_cast(theBuffer->ChangeData()), aBufferLength); + const std::streamsize aBytesRead = theStream.gcount(); + theBuffer->ChangeData()[aBytesRead < aBufferLength ? aBytesRead : aBufferLength - 1] = '\0'; + + // Clear any error flags (including EOF) BEFORE attempting to reset position + // This is essential because seekg() fails when EOF flag is set + theStream.clear(); + + // Reset stream to original position for subsequent reads + theStream.seekg(aOriginalPos); + + return Standard_True; +} \ No newline at end of file diff --git a/src/DataExchange/TKDE/DE/DE_ValidationUtils.hxx b/src/DataExchange/TKDE/DE/DE_ValidationUtils.hxx new file mode 100644 index 0000000000..b7f5b744be --- /dev/null +++ b/src/DataExchange/TKDE/DE/DE_ValidationUtils.hxx @@ -0,0 +1,118 @@ +// Copyright (c) 2025 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#ifndef _DE_ValidationUtils_HeaderFile +#define _DE_ValidationUtils_HeaderFile + +#include +#include +#include + +class TDocStd_Document; + +//! Utility class providing static methods for common validation operations +//! used across DataExchange providers. Includes validation for configuration nodes, +//! file paths, streams, and other common scenarios with optional verbose error reporting. +class DE_ValidationUtils +{ +public: + //! Validates that configuration node is not null and matches expected type + //! @param[in] theNode configuration node to validate + //! @param[in] theExpectedType expected RTTI type + //! @param[in] theContext context string for error messages + //! @param[in] theIsVerbose if true, sends detailed error messages via Message::SendFail + //! @return Standard_True if node is valid, Standard_False otherwise + Standard_EXPORT static Standard_Boolean ValidateConfigurationNode( + const Handle(DE_ConfigurationNode)& theNode, + const Handle(Standard_Type)& theExpectedType, + const TCollection_AsciiString& theContext, + const Standard_Boolean theIsVerbose = Standard_True); + + //! Checks if file exists and is readable + //! @param[in] thePath file path to check + //! @param[in] theContext context string for error messages + //! @param[in] theIsVerbose if true, sends detailed error messages via Message::SendFail + //! @return Standard_True if file exists and is readable, Standard_False otherwise + Standard_EXPORT static Standard_Boolean ValidateFileForReading( + const TCollection_AsciiString& thePath, + const TCollection_AsciiString& theContext, + const Standard_Boolean theIsVerbose = Standard_True); + + //! Checks if file location is writable (file may or may not exist) + //! @param[in] thePath file path to check + //! @param[in] theContext context string for error messages + //! @param[in] theIsVerbose if true, sends detailed error messages via Message::SendFail + //! @return Standard_True if location is writable, Standard_False otherwise + Standard_EXPORT static Standard_Boolean ValidateFileForWriting( + const TCollection_AsciiString& thePath, + const TCollection_AsciiString& theContext, + const Standard_Boolean theIsVerbose = Standard_True); + + //! Validates read stream list, warns if multiple streams + //! @param[in] theStreams read stream list to validate + //! @param[in] theContext context string for error messages + //! @param[in] theIsVerbose if true, sends detailed error/warning messages + //! @return Standard_True if stream list is valid, Standard_False otherwise + Standard_EXPORT static Standard_Boolean ValidateReadStreamList( + const DE_Provider::ReadStreamList& theStreams, + const TCollection_AsciiString& theContext, + const Standard_Boolean theIsVerbose = Standard_True); + + //! Validates write stream list, warns if multiple streams + //! @param[in] theStreams write stream list to validate + //! @param[in] theContext context string for error messages + //! @param[in] theIsVerbose if true, sends detailed error/warning messages + //! @return Standard_True if stream list is valid, Standard_False otherwise + Standard_EXPORT static Standard_Boolean ValidateWriteStreamList( + DE_Provider::WriteStreamList& theStreams, + const TCollection_AsciiString& theContext, + const Standard_Boolean theIsVerbose = Standard_True); + + //! Validates that TDocStd_Document handle is not null + //! @param[in] theDocument document to validate + //! @param[in] theContext context string for error messages + //! @param[in] theIsVerbose if true, sends detailed error messages via Message::SendFail + //! @return Standard_True if document is not null, Standard_False otherwise + Standard_EXPORT static Standard_Boolean ValidateDocument( + const Handle(TDocStd_Document)& theDocument, + const TCollection_AsciiString& theContext, + const Standard_Boolean theIsVerbose = Standard_True); + + //! Sends warning when format doesn't support length unit scaling + //! @param[in] theLengthUnit length unit value to check + //! @param[in] theContext context string for warning messages + //! @param[in] theIsVerbose if true, sends warning messages via Message::SendWarning + //! @return Standard_True always (this is just a warning) + Standard_EXPORT static Standard_Boolean WarnLengthUnitNotSupported( + const Standard_Real theLengthUnit, + const TCollection_AsciiString& theContext, + const Standard_Boolean theIsVerbose = Standard_True); + + //! Creates buffer by reading from file stream for content checking + //! @param[in] thePath file path for reading + //! @param[out] theBuffer output buffer with file content + //! @return Standard_True if successful, Standard_False otherwise + Standard_EXPORT static Standard_Boolean CreateContentBuffer( + const TCollection_AsciiString& thePath, + Handle(NCollection_Buffer)& theBuffer); + + //! Creates buffer by reading from input stream for content checking + //! @param[in,out] theStream input stream to read from (position will be restored) + //! @param[out] theBuffer output buffer with stream content + //! @return Standard_True if successful, Standard_False otherwise + Standard_EXPORT static Standard_Boolean CreateContentBuffer( + std::istream& theStream, + Handle(NCollection_Buffer)& theBuffer); +}; + +#endif // _DE_ValidationUtils_HeaderFile \ No newline at end of file diff --git a/src/DataExchange/TKDE/DE/DE_Wrapper.cxx b/src/DataExchange/TKDE/DE/DE_Wrapper.cxx index 0a40a57059..78997b5628 100644 --- a/src/DataExchange/TKDE/DE/DE_Wrapper.cxx +++ b/src/DataExchange/TKDE/DE/DE_Wrapper.cxx @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -499,19 +500,88 @@ Standard_Boolean DE_Wrapper::FindProvider(const TCollection_AsciiString& thePath const Standard_Boolean theToImport, Handle(DE_Provider)& theProvider) const { - Handle(NCollection_Buffer) aBuffer; if (theToImport) { - const Handle(OSD_FileSystem)& aFileSystem = OSD_FileSystem::DefaultFileSystem(); - std::shared_ptr aStream = - aFileSystem->OpenIStream(thePath, std::ios::in | std::ios::binary); - if (aStream.get() != nullptr) + return FindReadProvider(thePath, Standard_True, theProvider); + } + else + { + return FindWriteProvider(thePath, theProvider); + } +} + +//================================================================================================= + +Standard_Boolean DE_Wrapper::FindReadProvider(const TCollection_AsciiString& thePath, + const Standard_Boolean theCheckContent, + Handle(DE_Provider)& theProvider) const +{ + Handle(NCollection_Buffer) aBuffer; + if (theCheckContent && !DE_ValidationUtils::CreateContentBuffer(thePath, aBuffer)) + { + return Standard_False; + } + OSD_Path aPath(thePath); + const TCollection_AsciiString anExtr = aPath.Extension(); + for (DE_ConfigurationFormatMap::Iterator aFormatIter(myConfiguration); aFormatIter.More(); + aFormatIter.Next()) + { + for (DE_ConfigurationVendorMap::Iterator aVendorIter(aFormatIter.Value()); aVendorIter.More(); + aVendorIter.Next()) { - aBuffer = new NCollection_Buffer(NCollection_BaseAllocator::CommonBaseAllocator(), 2048); - aStream->read((char*)aBuffer->ChangeData(), 2048); - aBuffer->ChangeData()[2047] = '\0'; + const Handle(DE_ConfigurationNode)& aNode = aVendorIter.Value(); + if (aNode->IsEnabled() && aNode->IsImportSupported() + && (aNode->CheckExtension(anExtr) || (theCheckContent && aNode->CheckContent(aBuffer))) + && aNode->UpdateLoad(Standard_True, myKeepUpdates)) + { + theProvider = aNode->BuildProvider(); + aNode->GlobalParameters = GlobalParameters; + return Standard_True; + } } } + return Standard_False; +} + +//================================================================================================= + +Standard_Boolean DE_Wrapper::FindReadProvider(const TCollection_AsciiString& thePath, + std::istream& theStream, + Handle(DE_Provider)& theProvider) const +{ + Handle(NCollection_Buffer) aBuffer; + if (!DE_ValidationUtils::CreateContentBuffer(theStream, aBuffer)) + { + return Standard_False; + } + + OSD_Path aPath(thePath); + const TCollection_AsciiString anExtr = aPath.Extension(); + for (DE_ConfigurationFormatMap::Iterator aFormatIter(myConfiguration); aFormatIter.More(); + aFormatIter.Next()) + { + for (DE_ConfigurationVendorMap::Iterator aVendorIter(aFormatIter.Value()); aVendorIter.More(); + aVendorIter.Next()) + { + const Handle(DE_ConfigurationNode)& aNode = aVendorIter.Value(); + if (aNode->IsEnabled() && aNode->IsImportSupported() + && (aNode->CheckExtension(anExtr) || aNode->CheckContent(aBuffer)) + && aNode->UpdateLoad(Standard_True, myKeepUpdates)) + { + theProvider = aNode->BuildProvider(); + aNode->GlobalParameters = GlobalParameters; + return Standard_True; + } + } + } + return Standard_False; +} + +//================================================================================================= + +Standard_Boolean DE_Wrapper::FindWriteProvider(const TCollection_AsciiString& thePath, + Handle(DE_Provider)& theProvider) const +{ OSD_Path aPath(thePath); const TCollection_AsciiString anExtr = aPath.Extension(); for (DE_ConfigurationFormatMap::Iterator aFormatIter(myConfiguration); aFormatIter.More(); @@ -521,11 +591,8 @@ Standard_Boolean DE_Wrapper::FindProvider(const TCollection_AsciiString& thePath aVendorIter.Next()) { const Handle(DE_ConfigurationNode)& aNode = aVendorIter.Value(); - if (aNode->IsEnabled() - && ((theToImport && aNode->IsImportSupported()) - || (!theToImport && aNode->IsExportSupported())) - && (aNode->CheckExtension(anExtr) || (theToImport && aNode->CheckContent(aBuffer))) - && aNode->UpdateLoad(theToImport, myKeepUpdates)) + if (aNode->IsEnabled() && aNode->IsExportSupported() && aNode->CheckExtension(anExtr) + && aNode->UpdateLoad(Standard_False, myKeepUpdates)) { theProvider = aNode->BuildProvider(); aNode->GlobalParameters = GlobalParameters; @@ -572,3 +639,251 @@ void DE_Wrapper::sort(const Handle(DE_ConfigurationContext)& theResource) ChangePriority(aFormatIter.Key(), aVendorPriority, Standard_True); } } + +//================================================================================================= + +Standard_Boolean DE_Wrapper::Read(DE_Provider::ReadStreamList& theStreams, + const Handle(TDocStd_Document)& theDocument, + Handle(XSControl_WorkSession)& theWS, + const Message_ProgressRange& theProgress) +{ + if (!DE_ValidationUtils::ValidateReadStreamList(theStreams, "DE_Wrapper Read")) + { + return Standard_False; + } + + const TCollection_AsciiString& aFirstKey = theStreams.First().Path; + + Handle(DE_Provider) aProvider; + Standard_IStream& aFirstStream = theStreams.First().Stream; + if (!FindReadProvider(aFirstKey, aFirstStream, aProvider)) + { + Message::SendFail() << "Error: DE_Wrapper cannot find provider for stream " << aFirstKey; + return Standard_False; + } + + if (!aProvider->GetNode()->IsStreamSupported()) + { + Message::SendFail() << "Error: Provider " << aProvider->GetFormat() << " " + << aProvider->GetVendor() << " doesn't support stream operations"; + return Standard_False; + } + + return aProvider->Read(theStreams, theDocument, theWS, theProgress); +} + +//================================================================================================= + +Standard_Boolean DE_Wrapper::Write(DE_Provider::WriteStreamList& theStreams, + const Handle(TDocStd_Document)& theDocument, + Handle(XSControl_WorkSession)& theWS, + const Message_ProgressRange& theProgress) +{ + if (!DE_ValidationUtils::ValidateWriteStreamList(theStreams, "DE_Wrapper Write")) + { + return Standard_False; + } + + const TCollection_AsciiString& aFirstKey = theStreams.First().Path; + + Handle(DE_Provider) aProvider; + if (!FindWriteProvider(aFirstKey, aProvider)) + { + Message::SendFail() << "Error: DE_Wrapper cannot find provider for stream " << aFirstKey; + return Standard_False; + } + + if (!aProvider->GetNode()->IsStreamSupported()) + { + Message::SendFail() << "Error: Provider " << aProvider->GetFormat() << " " + << aProvider->GetVendor() << " doesn't support stream operations"; + return Standard_False; + } + + return aProvider->Write(theStreams, theDocument, theWS, theProgress); +} + +//================================================================================================= + +Standard_Boolean DE_Wrapper::Read(DE_Provider::ReadStreamList& theStreams, + const Handle(TDocStd_Document)& theDocument, + const Message_ProgressRange& theProgress) +{ + if (!DE_ValidationUtils::ValidateReadStreamList(theStreams, "DE_Wrapper Read")) + { + return Standard_False; + } + + const TCollection_AsciiString& aFirstKey = theStreams.First().Path; + + Handle(DE_Provider) aProvider; + Standard_IStream& aFirstStream = theStreams.First().Stream; + if (!FindReadProvider(aFirstKey, aFirstStream, aProvider)) + { + Message::SendFail() << "Error: DE_Wrapper cannot find provider for stream " << aFirstKey; + return Standard_False; + } + + if (!aProvider->GetNode()->IsStreamSupported()) + { + Message::SendFail() << "Error: Provider " << aProvider->GetFormat() << " " + << aProvider->GetVendor() << " doesn't support stream operations"; + return Standard_False; + } + + return aProvider->Read(theStreams, theDocument, theProgress); +} + +//================================================================================================= + +Standard_Boolean DE_Wrapper::Write(DE_Provider::WriteStreamList& theStreams, + const Handle(TDocStd_Document)& theDocument, + const Message_ProgressRange& theProgress) +{ + if (!DE_ValidationUtils::ValidateWriteStreamList(theStreams, "DE_Wrapper Write")) + { + return Standard_False; + } + + const TCollection_AsciiString& aFirstKey = theStreams.First().Path; + + Handle(DE_Provider) aProvider; + if (!FindWriteProvider(aFirstKey, aProvider)) + { + Message::SendFail() << "Error: DE_Wrapper cannot find provider for stream " << aFirstKey; + return Standard_False; + } + + if (!aProvider->GetNode()->IsStreamSupported()) + { + Message::SendFail() << "Error: Provider " << aProvider->GetFormat() << " " + << aProvider->GetVendor() << " doesn't support stream operations"; + return Standard_False; + } + + return aProvider->Write(theStreams, theDocument, theProgress); +} + +//================================================================================================= + +Standard_Boolean DE_Wrapper::Read(DE_Provider::ReadStreamList& theStreams, + TopoDS_Shape& theShape, + Handle(XSControl_WorkSession)& theWS, + const Message_ProgressRange& theProgress) +{ + if (!DE_ValidationUtils::ValidateReadStreamList(theStreams, "DE_Wrapper Read")) + { + return Standard_False; + } + + const TCollection_AsciiString& aFirstKey = theStreams.First().Path; + + Handle(DE_Provider) aProvider; + Standard_IStream& aFirstStream = theStreams.First().Stream; + if (!FindReadProvider(aFirstKey, aFirstStream, aProvider)) + { + Message::SendFail() << "Error: DE_Wrapper cannot find provider for stream " << aFirstKey; + return Standard_False; + } + + if (!aProvider->GetNode()->IsStreamSupported()) + { + Message::SendFail() << "Error: Provider " << aProvider->GetFormat() << " " + << aProvider->GetVendor() << " doesn't support stream operations"; + return Standard_False; + } + + return aProvider->Read(theStreams, theShape, theWS, theProgress); +} + +//================================================================================================= + +Standard_Boolean DE_Wrapper::Write(DE_Provider::WriteStreamList& theStreams, + const TopoDS_Shape& theShape, + Handle(XSControl_WorkSession)& theWS, + const Message_ProgressRange& theProgress) +{ + if (!DE_ValidationUtils::ValidateWriteStreamList(theStreams, "DE_Wrapper Write")) + { + return Standard_False; + } + + const TCollection_AsciiString& aFirstKey = theStreams.First().Path; + + Handle(DE_Provider) aProvider; + if (!FindWriteProvider(aFirstKey, aProvider)) + { + Message::SendFail() << "Error: DE_Wrapper cannot find provider for stream " << aFirstKey; + return Standard_False; + } + + if (!aProvider->GetNode()->IsStreamSupported()) + { + Message::SendFail() << "Error: Provider " << aProvider->GetFormat() << " " + << aProvider->GetVendor() << " doesn't support stream operations"; + return Standard_False; + } + + return aProvider->Write(theStreams, theShape, theWS, theProgress); +} + +//================================================================================================= + +Standard_Boolean DE_Wrapper::Read(DE_Provider::ReadStreamList& theStreams, + TopoDS_Shape& theShape, + const Message_ProgressRange& theProgress) +{ + if (!DE_ValidationUtils::ValidateReadStreamList(theStreams, "DE_Wrapper Read")) + { + return Standard_False; + } + + const TCollection_AsciiString& aFirstKey = theStreams.First().Path; + + Handle(DE_Provider) aProvider; + Standard_IStream& aFirstStream = theStreams.First().Stream; + if (!FindReadProvider(aFirstKey, aFirstStream, aProvider)) + { + Message::SendFail() << "Error: DE_Wrapper cannot find provider for stream " << aFirstKey; + return Standard_False; + } + + if (!aProvider->GetNode()->IsStreamSupported()) + { + Message::SendFail() << "Error: Provider " << aProvider->GetFormat() << " " + << aProvider->GetVendor() << " doesn't support stream operations"; + return Standard_False; + } + + return aProvider->Read(theStreams, theShape, theProgress); +} + +//================================================================================================= + +Standard_Boolean DE_Wrapper::Write(DE_Provider::WriteStreamList& theStreams, + const TopoDS_Shape& theShape, + const Message_ProgressRange& theProgress) +{ + if (!DE_ValidationUtils::ValidateWriteStreamList(theStreams, "DE_Wrapper Write")) + { + return Standard_False; + } + + const TCollection_AsciiString& aFirstKey = theStreams.First().Path; + + Handle(DE_Provider) aProvider; + if (!FindWriteProvider(aFirstKey, aProvider)) + { + Message::SendFail() << "Error: DE_Wrapper cannot find provider for stream " << aFirstKey; + return Standard_False; + } + + if (!aProvider->GetNode()->IsStreamSupported()) + { + Message::SendFail() << "Error: Provider " << aProvider->GetFormat() << " " + << aProvider->GetVendor() << " doesn't support stream operations"; + return Standard_False; + } + + return aProvider->Write(theStreams, theShape, theProgress); +} diff --git a/src/DataExchange/TKDE/DE/DE_Wrapper.hxx b/src/DataExchange/TKDE/DE/DE_Wrapper.hxx index b54fa357b7..a159adbe8d 100644 --- a/src/DataExchange/TKDE/DE/DE_Wrapper.hxx +++ b/src/DataExchange/TKDE/DE/DE_Wrapper.hxx @@ -15,6 +15,7 @@ #define _DE_Wrapper_HeaderFile #include +#include #include #include #include @@ -163,6 +164,94 @@ public: const TopoDS_Shape& theShape, const Message_ProgressRange& theProgress = Message_ProgressRange()); + //! Reads streams according to internal configuration + //! @param[in] theStreams streams to read from + //! @param[out] theDocument document to save result + //! @param[in] theWS current work session + //! @param[in] theProgress progress indicator + //! @return true if Read operation has ended correctly + Standard_EXPORT Standard_Boolean + Read(DE_Provider::ReadStreamList& theStreams, + const Handle(TDocStd_Document)& theDocument, + Handle(XSControl_WorkSession)& theWS, + const Message_ProgressRange& theProgress = Message_ProgressRange()); + + //! Writes streams according to internal configuration + //! @param[in] theStreams streams to write to + //! @param[out] theDocument document to export + //! @param[in] theWS current work session + //! @param[in] theProgress progress indicator + //! @return true if Write operation has ended correctly + Standard_EXPORT Standard_Boolean + Write(DE_Provider::WriteStreamList& theStreams, + const Handle(TDocStd_Document)& theDocument, + Handle(XSControl_WorkSession)& theWS, + const Message_ProgressRange& theProgress = Message_ProgressRange()); + + //! Reads streams according to internal configuration + //! @param[in] theStreams streams to read from + //! @param[out] theDocument document to save result + //! @param[in] theProgress progress indicator + //! @return true if Read operation has ended correctly + Standard_EXPORT Standard_Boolean + Read(DE_Provider::ReadStreamList& theStreams, + const Handle(TDocStd_Document)& theDocument, + const Message_ProgressRange& theProgress = Message_ProgressRange()); + + //! Writes streams according to internal configuration + //! @param[in] theStreams streams to write to + //! @param[out] theDocument document to export + //! @param[in] theProgress progress indicator + //! @return true if Write operation has ended correctly + Standard_EXPORT Standard_Boolean + Write(DE_Provider::WriteStreamList& theStreams, + const Handle(TDocStd_Document)& theDocument, + const Message_ProgressRange& theProgress = Message_ProgressRange()); + + //! Reads streams according to internal configuration + //! @param[in] theStreams streams to read from + //! @param[out] theShape shape to save result + //! @param[in] theWS current work session + //! @param[in] theProgress progress indicator + //! @return true if Read operation has ended correctly + Standard_EXPORT Standard_Boolean + Read(DE_Provider::ReadStreamList& theStreams, + TopoDS_Shape& theShape, + Handle(XSControl_WorkSession)& theWS, + const Message_ProgressRange& theProgress = Message_ProgressRange()); + + //! Writes streams according to internal configuration + //! @param[in] theStreams streams to write to + //! @param[out] theShape shape to export + //! @param[in] theWS current work session + //! @param[in] theProgress progress indicator + //! @return true if Write operation has ended correctly + Standard_EXPORT Standard_Boolean + Write(DE_Provider::WriteStreamList& theStreams, + const TopoDS_Shape& theShape, + Handle(XSControl_WorkSession)& theWS, + const Message_ProgressRange& theProgress = Message_ProgressRange()); + + //! Reads streams according to internal configuration + //! @param[in] theStreams streams to read from + //! @param[out] theShape shape to save result + //! @param[in] theProgress progress indicator + //! @return true if Read operation has ended correctly + Standard_EXPORT Standard_Boolean + Read(DE_Provider::ReadStreamList& theStreams, + TopoDS_Shape& theShape, + const Message_ProgressRange& theProgress = Message_ProgressRange()); + + //! Writes streams according to internal configuration + //! @param[in] theStreams streams to write to + //! @param[out] theShape shape to export + //! @param[in] theProgress progress indicator + //! @return true if Write operation has ended correctly + Standard_EXPORT Standard_Boolean + Write(DE_Provider::WriteStreamList& theStreams, + const TopoDS_Shape& theShape, + const Message_ProgressRange& theProgress = Message_ProgressRange()); + public: //! Updates values according the resource file //! @param[in] theResource file path to resource or resource value @@ -243,6 +332,35 @@ public: const Standard_Boolean theToImport, Handle(DE_Provider)& theProvider) const; + //! Find available read provider from the configuration for file-based operations. + //! If there are several providers, choose the one with the highest priority. + //! @param[in] thePath path to the CAD file (for extension and content checking) + //! @param[in] theCheckContent flag to enable content checking via file reading + //! @param[out] theProvider created new provider + //! @return Standard_True if provider found and created + Standard_EXPORT virtual Standard_Boolean FindReadProvider(const TCollection_AsciiString& thePath, + const Standard_Boolean theCheckContent, + Handle(DE_Provider)& theProvider) const; + + //! Find available read provider from the configuration for stream-based operations. + //! If there are several providers, choose the one with the highest priority. + //! @param[in] thePath path to the CAD file (for extension extraction) + //! @param[in] theStream input stream for content checking + //! @param[out] theProvider created new provider + //! @return Standard_True if provider found and created + Standard_EXPORT virtual Standard_Boolean FindReadProvider(const TCollection_AsciiString& thePath, + std::istream& theStream, + Handle(DE_Provider)& theProvider) const; + + //! Find available write provider from the configuration. + //! If there are several providers, choose the one with the highest priority. + //! @param[in] thePath path to the CAD file (for extension checking only) + //! @param[out] theProvider created new provider + //! @return Standard_True if provider found and created + Standard_EXPORT virtual Standard_Boolean FindWriteProvider( + const TCollection_AsciiString& thePath, + Handle(DE_Provider)& theProvider) const; + //! Updates all registered nodes, all changes will be saved in nodes //! @param[in] theToForceUpdate flag that turns on/of nodes, according to updated ability to //! import/export diff --git a/src/DataExchange/TKDE/DE/FILES.cmake b/src/DataExchange/TKDE/DE/FILES.cmake index 93721bfed7..de873dd818 100644 --- a/src/DataExchange/TKDE/DE/FILES.cmake +++ b/src/DataExchange/TKDE/DE/FILES.cmake @@ -12,6 +12,8 @@ set(OCCT_DE_FILES DE_ShapeFixConfigurationNode.cxx DE_ShapeFixConfigurationNode.hxx DE_ShapeFixParameters.hxx + DE_ValidationUtils.cxx + DE_ValidationUtils.hxx DE_Wrapper.cxx DE_Wrapper.hxx ) diff --git a/src/DataExchange/TKDEGLTF/DEGLTF/DEGLTF_Provider.cxx b/src/DataExchange/TKDEGLTF/DEGLTF/DEGLTF_Provider.cxx index 84c782b181..f430bec339 100644 --- a/src/DataExchange/TKDEGLTF/DEGLTF/DEGLTF_Provider.cxx +++ b/src/DataExchange/TKDEGLTF/DEGLTF/DEGLTF_Provider.cxx @@ -13,6 +13,7 @@ #include +#include #include #include #include @@ -86,17 +87,15 @@ bool DEGLTF_Provider::Read(const TCollection_AsciiString& thePath, const Handle(TDocStd_Document)& theDocument, const Message_ProgressRange& theProgress) { - if (theDocument.IsNull()) + TCollection_AsciiString aContext = TCollection_AsciiString("reading the file ") + thePath; + if (!DE_ValidationUtils::ValidateDocument(theDocument, aContext)) { - Message::SendFail() << "Error in the DEGLTF_Provider during reading the file " << thePath - << "\t: theDocument shouldn't be null"; return false; } - if (GetNode().IsNull() - || (!GetNode().IsNull() && !GetNode()->IsKind(STANDARD_TYPE(DEGLTF_ConfigurationNode)))) + if (!DE_ValidationUtils::ValidateConfigurationNode(GetNode(), + STANDARD_TYPE(DEGLTF_ConfigurationNode), + aContext)) { - Message::SendFail() << "Error in the DEGLTF_Provider during reading the file " << thePath - << "\t: Incorrect or empty Configuration Node"; return false; } Handle(DEGLTF_ConfigurationNode) aNode = Handle(DEGLTF_ConfigurationNode)::DownCast(GetNode()); @@ -121,10 +120,11 @@ bool DEGLTF_Provider::Write(const TCollection_AsciiString& thePath, const Handle(TDocStd_Document)& theDocument, const Message_ProgressRange& theProgress) { - if (GetNode().IsNull() || !GetNode()->IsKind(STANDARD_TYPE(DEGLTF_ConfigurationNode))) + TCollection_AsciiString aContext = TCollection_AsciiString("writing the file ") + thePath; + if (!DE_ValidationUtils::ValidateConfigurationNode(GetNode(), + STANDARD_TYPE(DEGLTF_ConfigurationNode), + aContext)) { - Message::SendFail() << "Error in the DEGLTF_Provider during writing the file " << thePath - << "\t: Incorrect or empty Configuration Node"; return false; } Handle(DEGLTF_ConfigurationNode) aNode = Handle(DEGLTF_ConfigurationNode)::DownCast(GetNode()); @@ -141,9 +141,11 @@ bool DEGLTF_Provider::Write(const TCollection_AsciiString& thePath, aConverter.SetInputCoordinateSystem(aNode->InternalParameters.SystemCS); if (aNode->GlobalParameters.LengthUnit != 1000.) { + TCollection_AsciiString aContext = TCollection_AsciiString("writing the file ") + thePath; Message::SendWarning() - << "Warning in the DEGLTF_Provider during writing the file " << thePath - << "\t: Target format doesn't support custom units. Model will be scaled to Meters"; + << "Warning during " << aContext + << ": Target format doesn't support custom units. Model will be scaled to Meters (unit: " + << aNode->GlobalParameters.LengthUnit << ")"; } aConverter.SetOutputLengthUnit(1.); // gltf units always Meters aConverter.SetOutputCoordinateSystem(aNode->InternalParameters.FileCS); diff --git a/src/DataExchange/TKDEIGES/DEIGES/DEIGES_Provider.cxx b/src/DataExchange/TKDEIGES/DEIGES/DEIGES_Provider.cxx index 748a66730c..aa57e92f46 100644 --- a/src/DataExchange/TKDEIGES/DEIGES/DEIGES_Provider.cxx +++ b/src/DataExchange/TKDEIGES/DEIGES/DEIGES_Provider.cxx @@ -13,6 +13,7 @@ #include +#include #include #include #include @@ -27,6 +28,128 @@ IMPLEMENT_STANDARD_RTTIEXT(DEIGES_Provider, DE_Provider) +namespace +{ +// Helper function to validate configuration node +Standard_Boolean validateConfigurationNode(const Handle(DE_ConfigurationNode)& theNode, + const TCollection_AsciiString& theContext) +{ + return DE_ValidationUtils::ValidateConfigurationNode(theNode, + STANDARD_TYPE(DEIGES_ConfigurationNode), + theContext); +} + +// Helper function to configure IGES CAF reader parameters +void configureIGESCAFReader(IGESCAFControl_Reader& theReader, + const Handle(DEIGES_ConfigurationNode)& theNode) +{ + theReader.SetReadVisible(theNode->InternalParameters.ReadOnlyVisible); + theReader.SetColorMode(theNode->InternalParameters.ReadColor); + theReader.SetNameMode(theNode->InternalParameters.ReadName); + theReader.SetLayerMode(theNode->InternalParameters.ReadLayer); + theReader.SetShapeFixParameters(theNode->ShapeFixParameters); +} + +// Helper function to configure IGES control reader parameters +void configureIGESControlReader(IGESControl_Reader& theReader, + const Handle(DEIGES_ConfigurationNode)& theNode) +{ + theReader.SetReadVisible(theNode->InternalParameters.ReadOnlyVisible); + theReader.SetShapeFixParameters(theNode->ShapeFixParameters); +} + +// Helper function to setup IGES unit configuration +void setupIGESUnits(IGESData_GlobalSection& theGS, + const Handle(DEIGES_ConfigurationNode)& theNode, + const Handle(TDocStd_Document)& theDocument, + const TCollection_AsciiString& thePath, + Standard_Boolean theUseDocumentUnits) +{ + Standard_Integer aFlag = + IGESData_BasicEditor::GetFlagByValue(theNode->GlobalParameters.LengthUnit); + + if (theUseDocumentUnits && !theDocument.IsNull()) + { + Standard_Real aScaleFactorMM = 1.; + Standard_Boolean aHasUnits = + XCAFDoc_DocumentTool::GetLengthUnit(theDocument, + aScaleFactorMM, + UnitsMethods_LengthUnit_Millimeter); + if (aHasUnits) + { + theGS.SetCascadeUnit(aScaleFactorMM); + } + else + { + theGS.SetCascadeUnit(theNode->GlobalParameters.SystemUnit); + Message::SendWarning() + << "Warning in the DEIGES_Provider during writing the file " << thePath + << "\t: The document has no information on Units. Using global parameter as initial Unit."; + } + } + else + { + theGS.SetCascadeUnit(theNode->GlobalParameters.SystemUnit); + } + + if (aFlag == 0) + { + theGS.SetScale(theNode->GlobalParameters.LengthUnit); + } +} + +// Helper function to configure IGES CAF writer parameters +void configureIGESCAFWriter(IGESCAFControl_Writer& theWriter, + const Handle(DEIGES_ConfigurationNode)& theNode, + const Handle(TDocStd_Document)& theDocument, + const TCollection_AsciiString& thePath) +{ + IGESData_GlobalSection aGS = theWriter.Model()->GlobalSection(); + setupIGESUnits(aGS, theNode, theDocument, thePath, Standard_True); + + theWriter.Model()->SetGlobalSection(aGS); + theWriter.SetColorMode(theNode->InternalParameters.WriteColor); + theWriter.SetNameMode(theNode->InternalParameters.WriteName); + theWriter.SetLayerMode(theNode->InternalParameters.WriteLayer); + theWriter.SetShapeFixParameters(theNode->ShapeFixParameters); +} + +// Helper function to configure IGES control writer for shapes +void configureIGESControlWriter(IGESControl_Writer& theWriter, + const Handle(DEIGES_ConfigurationNode)& theNode) +{ + IGESData_GlobalSection aGS = theWriter.Model()->GlobalSection(); + Handle(TDocStd_Document) aNullDoc; + setupIGESUnits(aGS, theNode, aNullDoc, "", Standard_False); + + theWriter.Model()->SetGlobalSection(aGS); + theWriter.SetShapeFixParameters(theNode->ShapeFixParameters); +} + +// Helper function to setup IGES writer unit flags +TCollection_AsciiString getIGESUnitString(const Handle(DEIGES_ConfigurationNode)& theNode) +{ + Standard_Integer aFlag = + IGESData_BasicEditor::GetFlagByValue(theNode->GlobalParameters.LengthUnit); + return (aFlag > 0) ? IGESData_BasicEditor::UnitFlagName(aFlag) : "MM"; +} + +// Helper function to process read file operation +Standard_Boolean processReadFile(IGESControl_Reader& theReader, + const TCollection_AsciiString& thePath) +{ + IFSelect_ReturnStatus aReadStat = theReader.ReadFile(thePath.ToCString()); + if (aReadStat != IFSelect_RetDone) + { + Message::SendFail() << "Error in the DEIGES_Provider during reading the file " << thePath + << "\t: abandon, no model loaded"; + return Standard_False; + } + return Standard_True; +} + +} // namespace + //================================================================================================= DEIGES_Provider::DEIGES_Provider() {} @@ -160,35 +283,26 @@ bool DEIGES_Provider::Read(const TCollection_AsciiString& thePath, Handle(XSControl_WorkSession)& theWS, const Message_ProgressRange& theProgress) { - if (theDocument.IsNull()) + TCollection_AsciiString aContext = TCollection_AsciiString("reading the file ") + thePath; + if (!DE_ValidationUtils::ValidateDocument(theDocument, aContext) + || !validateConfigurationNode(GetNode(), aContext)) { - Message::SendFail() << "Error in the DEIGES_Provider during reading the file " << thePath - << "\t: theDocument shouldn't be null"; - return false; - } - if (!GetNode()->IsKind(STANDARD_TYPE(DEIGES_ConfigurationNode))) - { - Message::SendFail() << "Error in the DEIGES_Provider during reading the file " << thePath - << "\t: Incorrect or empty Configuration Node"; return false; } + Handle(DEIGES_ConfigurationNode) aNode = Handle(DEIGES_ConfigurationNode)::DownCast(GetNode()); personizeWS(theWS); initStatic(aNode); + XCAFDoc_DocumentTool::SetLengthUnit(theDocument, aNode->GlobalParameters.LengthUnit, UnitsMethods_LengthUnit_Millimeter); + IGESCAFControl_Reader aReader; aReader.SetWS(theWS); + configureIGESCAFReader(aReader, aNode); - aReader.SetReadVisible(aNode->InternalParameters.ReadOnlyVisible); - - aReader.SetColorMode(aNode->InternalParameters.ReadColor); - aReader.SetNameMode(aNode->InternalParameters.ReadName); - aReader.SetLayerMode(aNode->InternalParameters.ReadLayer); - aReader.SetShapeFixParameters(aNode->ShapeFixParameters); - IFSelect_ReturnStatus aReadStat = IFSelect_RetVoid; - aReadStat = aReader.ReadFile(thePath.ToCString()); + IFSelect_ReturnStatus aReadStat = aReader.ReadFile(thePath.ToCString()); if (aReadStat != IFSelect_RetDone) { Message::SendFail() << "Error in the DEIGES_Provider during reading the file " << thePath @@ -215,54 +329,30 @@ bool DEIGES_Provider::Write(const TCollection_AsciiString& thePath, Handle(XSControl_WorkSession)& theWS, const Message_ProgressRange& theProgress) { - if (!GetNode()->IsKind(STANDARD_TYPE(DEIGES_ConfigurationNode))) + TCollection_AsciiString aContext = TCollection_AsciiString("writing the file ") + thePath; + if (!DE_ValidationUtils::ValidateDocument(theDocument, aContext) + || !validateConfigurationNode(GetNode(), aContext)) { - Message::SendFail() << "Error in the DEIGES_Provider during reading the file " << thePath - << "\t: Incorrect or empty Configuration Node"; return false; } + Handle(DEIGES_ConfigurationNode) aNode = Handle(DEIGES_ConfigurationNode)::DownCast(GetNode()); personizeWS(theWS); initStatic(aNode); - Standard_Integer aFlag = IGESData_BasicEditor::GetFlagByValue(aNode->GlobalParameters.LengthUnit); - IGESCAFControl_Writer aWriter(theWS, - (aFlag > 0) ? IGESData_BasicEditor::UnitFlagName(aFlag) : "MM"); - IGESData_GlobalSection aGS = aWriter.Model()->GlobalSection(); - Standard_Real aScaleFactorMM = 1.; - Standard_Boolean aHasUnits = - XCAFDoc_DocumentTool::GetLengthUnit(theDocument, - aScaleFactorMM, - UnitsMethods_LengthUnit_Millimeter); - if (aHasUnits) - { - aGS.SetCascadeUnit(aScaleFactorMM); - } - else - { - aGS.SetCascadeUnit(aNode->GlobalParameters.SystemUnit); - Message::SendWarning() - << "Warning in the DEIGES_Provider during writing the file " << thePath - << "\t: The document has no information on Units. Using global parameter as initial Unit."; - } - if (aFlag == 0) - { - aGS.SetScale(aNode->GlobalParameters.LengthUnit); - } - aWriter.Model()->SetGlobalSection(aGS); - aWriter.SetColorMode(aNode->InternalParameters.WriteColor); - aWriter.SetNameMode(aNode->InternalParameters.WriteName); - aWriter.SetLayerMode(aNode->InternalParameters.WriteLayer); - aWriter.SetShapeFixParameters(aNode->ShapeFixParameters); + + IGESCAFControl_Writer aWriter(theWS, Standard_False); + configureIGESCAFWriter(aWriter, aNode, theDocument, thePath); + if (!aWriter.Transfer(theDocument, theProgress)) { - Message::SendFail() << "Error in the DEIGES_Provider during reading the file " << thePath + Message::SendFail() << "Error in the DEIGES_Provider during writing the file " << thePath << "\t: The document cannot be translated or gives no result"; resetStatic(); return false; } if (!aWriter.Write(thePath.ToCString())) { - Message::SendFail() << "Error in the DEIGES_Provider during reading the file " << thePath + Message::SendFail() << "Error in the DEIGES_Provider during writing the file " << thePath << "\t: Write failed"; resetStatic(); return false; @@ -298,31 +388,26 @@ bool DEIGES_Provider::Read(const TCollection_AsciiString& thePath, Handle(XSControl_WorkSession)& theWS, const Message_ProgressRange& theProgress) { - (void)theProgress; - if (!GetNode()->IsKind(STANDARD_TYPE(DEIGES_ConfigurationNode))) + if (!validateConfigurationNode(GetNode(), TCollection_AsciiString("reading the file ") + thePath)) { - Message::SendFail() << "Error in the DEIGES_Provider during reading the file " << thePath - << "\t: Incorrect or empty Configuration Node"; return false; } + Handle(DEIGES_ConfigurationNode) aNode = Handle(DEIGES_ConfigurationNode)::DownCast(GetNode()); initStatic(aNode); personizeWS(theWS); + IGESControl_Reader aReader; aReader.SetWS(theWS); - aReader.SetReadVisible(aNode->InternalParameters.ReadOnlyVisible); - aReader.SetShapeFixParameters(aNode->ShapeFixParameters); + configureIGESControlReader(aReader, aNode); - IFSelect_ReturnStatus aReadStat = IFSelect_RetVoid; - aReadStat = aReader.ReadFile(thePath.ToCString()); - if (aReadStat != IFSelect_RetDone) + if (!processReadFile(aReader, thePath)) { - Message::SendFail() << "Error in the DEIGES_Provider during reading the file " << thePath - << "\t: Could not read file, no model loaded"; resetStatic(); return false; } - if (aReader.TransferRoots() <= 0) + + if (aReader.TransferRoots(theProgress) <= 0) { Message::SendFail() << "Error in the DEIGES_Provider during reading the file " << thePath << "\t: Cannot read any relevant data from the IGES file"; @@ -341,28 +426,21 @@ bool DEIGES_Provider::Write(const TCollection_AsciiString& thePath, Handle(XSControl_WorkSession)& theWS, const Message_ProgressRange& theProgress) { - (void)theWS; - (void)theProgress; - if (!GetNode()->IsKind(STANDARD_TYPE(DEIGES_ConfigurationNode))) + TCollection_AsciiString aContext = TCollection_AsciiString("writing the file ") + thePath; + if (!validateConfigurationNode(GetNode(), aContext)) { - Message::SendFail() << "Error in the DEIGES_Provider during reading the file " << thePath - << "\t: Incorrect or empty Configuration Node"; return false; } + Handle(DEIGES_ConfigurationNode) aNode = Handle(DEIGES_ConfigurationNode)::DownCast(GetNode()); initStatic(aNode); - Standard_Integer aFlag = IGESData_BasicEditor::GetFlagByValue(aNode->GlobalParameters.LengthUnit); - IGESControl_Writer aWriter((aFlag > 0) ? IGESData_BasicEditor::UnitFlagName(aFlag) : "MM", + personizeWS(theWS); + + IGESControl_Writer aWriter(getIGESUnitString(aNode).ToCString(), aNode->InternalParameters.WriteBRepMode); - IGESData_GlobalSection aGS = aWriter.Model()->GlobalSection(); - aGS.SetCascadeUnit(aNode->GlobalParameters.SystemUnit); - if (!aFlag) - { - aGS.SetScale(aNode->GlobalParameters.LengthUnit); - } - aWriter.Model()->SetGlobalSection(aGS); - aWriter.SetShapeFixParameters(aNode->ShapeFixParameters); - Standard_Boolean aIsOk = aWriter.AddShape(theShape); + configureIGESControlWriter(aWriter, aNode); + + Standard_Boolean aIsOk = aWriter.AddShape(theShape, theProgress); if (!aIsOk) { Message::SendFail() << "DEIGES_Provider: Shape not written"; @@ -370,7 +448,7 @@ bool DEIGES_Provider::Write(const TCollection_AsciiString& thePath, return false; } - if (!(aWriter.Write(thePath.ToCString()))) + if (!aWriter.Write(thePath.ToCString())) { Message::SendFail() << "DEIGES_Provider: Error on writing file " << thePath; resetStatic(); @@ -413,3 +491,253 @@ TCollection_AsciiString DEIGES_Provider::GetVendor() const { return TCollection_AsciiString("OCC"); } + +/* + +// TODO: Implement IGES stream support + +//================================================================================================= + +Standard_Boolean DEIGES_Provider::Read(ReadStreamList& theStreams, + const Handle(TDocStd_Document)& theDocument, + Handle(XSControl_WorkSession)& theWS, + const Message_ProgressRange& theProgress) +{ + TCollection_AsciiString aContext = "reading stream"; + if (!DE_ValidationUtils::ValidateReadStreamList(theStreams, aContext)) + { + return Standard_False; + } + + const TCollection_AsciiString& aFirstKey = theStreams.First().Path; + TCollection_AsciiString aFullContext = aContext + " " + aFirstKey; + Standard_IStream& aStream = theStreams.First().Stream; + + if (!DE_ValidationUtils::ValidateDocument(theDocument, aFullContext) + || !validateConfigurationNode(GetNode(), aFullContext)) + { + return Standard_False; + } + + Handle(DEIGES_ConfigurationNode) aNode = Handle(DEIGES_ConfigurationNode)::DownCast(GetNode()); + initStatic(aNode); + personizeWS(theWS); + + XCAFDoc_DocumentTool::SetLengthUnit(theDocument, + aNode->GlobalParameters.LengthUnit, + UnitsMethods_LengthUnit_Millimeter); + + IGESCAFControl_Reader aReader; + aReader.SetWS(theWS); + configureIGESCAFReader(aReader, aNode); + + IFSelect_ReturnStatus aReadStat = aReader.ReadStream(aFirstKey.ToCString(), aStream); + if (aReadStat != IFSelect_RetDone) + { + Message::SendFail() << "Error in the DEIGES_Provider during reading stream " << aFirstKey + << "\t: abandon, no model loaded"; + resetStatic(); + return Standard_False; + } + + if (!aReader.Transfer(theDocument, theProgress)) + { + Message::SendFail() << "Error in the DEIGES_Provider during reading stream " << aFirstKey + << "\t: Cannot read any relevant data from the IGES stream"; + resetStatic(); + return Standard_False; + } + resetStatic(); + return Standard_True; +} + +//================================================================================================= + +Standard_Boolean DEIGES_Provider::Write(WriteStreamList& theStreams, + const Handle(TDocStd_Document)& theDocument, + Handle(XSControl_WorkSession)& theWS, + const Message_ProgressRange& theProgress) +{ + TCollection_AsciiString aContext = "writing stream"; + if (!DE_ValidationUtils::ValidateWriteStreamList(theStreams, aContext)) + { + return Standard_False; + } + + const TCollection_AsciiString& aFirstKey = theStreams.First().Path; + Standard_OStream& aStream = theStreams.First().Stream; + + TCollection_AsciiString aFullContext = aContext + " " + aFirstKey; + if (!DE_ValidationUtils::ValidateDocument(theDocument, aFullContext) + || !validateConfigurationNode(GetNode(), aFullContext)) + { + return Standard_False; + } + + Handle(DEIGES_ConfigurationNode) aNode = Handle(DEIGES_ConfigurationNode)::DownCast(GetNode()); + initStatic(aNode); + personizeWS(theWS); + + IGESCAFControl_Writer aWriter(theWS, Standard_False); + configureIGESCAFWriter(aWriter, aNode, theDocument, aFirstKey); + + if (!aWriter.Transfer(theDocument, theProgress)) + { + Message::SendFail() << "Error in the DEIGES_Provider during writing stream " << aFirstKey + << "\t: The document cannot be translated or gives no result"; + resetStatic(); + return Standard_False; + } + + if (!aWriter.Write(aStream)) + { + Message::SendFail() << "Error in the DEIGES_Provider during writing stream " << aFirstKey + << "\t: Write failed"; + resetStatic(); + return Standard_False; + } + resetStatic(); + return Standard_True; +} + +//================================================================================================= + +Standard_Boolean DEIGES_Provider::Read(ReadStreamList& theStreams, + TopoDS_Shape& theShape, + Handle(XSControl_WorkSession)& theWS, + const Message_ProgressRange& theProgress) +{ + TCollection_AsciiString aContext = "reading stream"; + if (!DE_ValidationUtils::ValidateReadStreamList(theStreams, aContext)) + { + return Standard_False; + } + + const TCollection_AsciiString& aFirstKey = theStreams.First().Path; + Standard_IStream& aStream = theStreams.First().Stream; + + TCollection_AsciiString aFullContext = aContext + " " + aFirstKey; + if (!validateConfigurationNode(GetNode(), aFullContext)) + { + return Standard_False; + } + + Handle(DEIGES_ConfigurationNode) aNode = Handle(DEIGES_ConfigurationNode)::DownCast(GetNode()); + initStatic(aNode); + personizeWS(theWS); + + IGESControl_Reader aReader; + aReader.SetWS(theWS); + configureIGESControlReader(aReader, aNode); + + IFSelect_ReturnStatus aReadStat = aReader.ReadStream(aFirstKey.ToCString(), aStream); + if (aReadStat != IFSelect_RetDone) + { + Message::SendFail() << "Error in the DEIGES_Provider during reading stream " << aFirstKey + << "\t: Could not read stream, no model loaded"; + resetStatic(); + return Standard_False; + } + + if (aReader.TransferRoots(theProgress) <= 0) + { + Message::SendFail() << "Error in the DEIGES_Provider during reading stream " << aFirstKey + << "\t: Cannot read any relevant data from the IGES stream"; + resetStatic(); + return Standard_False; + } + theShape = aReader.OneShape(); + resetStatic(); + return Standard_True; +} + +//================================================================================================= + +Standard_Boolean DEIGES_Provider::Write(WriteStreamList& theStreams, + const TopoDS_Shape& theShape, + Handle(XSControl_WorkSession)& theWS, + const Message_ProgressRange& theProgress) +{ + TCollection_AsciiString aContext = "writing stream"; + if (!DE_ValidationUtils::ValidateWriteStreamList(theStreams, aContext)) + { + return Standard_False; + } + + const TCollection_AsciiString& aFirstKey = theStreams.First().Path; + Standard_OStream& aStream = theStreams.First().Stream; + + TCollection_AsciiString aFullContext = aContext + " " + aFirstKey; + if (!validateConfigurationNode(GetNode(), aFullContext)) + { + return Standard_False; + } + + Handle(DEIGES_ConfigurationNode) aNode = Handle(DEIGES_ConfigurationNode)::DownCast(GetNode()); + initStatic(aNode); + personizeWS(theWS); + + IGESControl_Writer aWriter(getIGESUnitString(aNode).ToCString(), + aNode->InternalParameters.WriteBRepMode); + configureIGESControlWriter(aWriter, aNode); + + Standard_Boolean isOk = aWriter.AddShape(theShape, theProgress); + if (!isOk) + { + Message::SendFail() << "Error: DEIGES_Provider failed to transfer shape for stream " + << aFirstKey; + resetStatic(); + return Standard_False; + } + + if (!aWriter.Write(aStream)) + { + Message::SendFail() << "Error: DEIGES_Provider failed to write shape to stream " << aFirstKey; + resetStatic(); + return Standard_False; + } + resetStatic(); + return Standard_True; +} + +//================================================================================================= + +Standard_Boolean DEIGES_Provider::Read(ReadStreamList& theStreams, + const Handle(TDocStd_Document)& theDocument, + const Message_ProgressRange& theProgress) +{ + Handle(XSControl_WorkSession) aWS = new XSControl_WorkSession(); + return Read(theStreams, theDocument, aWS, theProgress); +} + +//================================================================================================= + +Standard_Boolean DEIGES_Provider::Write(WriteStreamList& theStreams, + const Handle(TDocStd_Document)& theDocument, + const Message_ProgressRange& theProgress) +{ + Handle(XSControl_WorkSession) aWS = new XSControl_WorkSession(); + return Write(theStreams, theDocument, aWS, theProgress); +} + +//================================================================================================= + +Standard_Boolean DEIGES_Provider::Read(ReadStreamList& theStreams, + TopoDS_Shape& theShape, + const Message_ProgressRange& theProgress) +{ + Handle(XSControl_WorkSession) aWS = new XSControl_WorkSession(); + return Read(theStreams, theShape, aWS, theProgress); +} + +//================================================================================================= + +Standard_Boolean DEIGES_Provider::Write(WriteStreamList& theStreams, + const TopoDS_Shape& theShape, + const Message_ProgressRange& theProgress) +{ + Handle(XSControl_WorkSession) aWS = new XSControl_WorkSession(); + return Write(theStreams, theShape, aWS, theProgress); +} + +*/ diff --git a/src/DataExchange/TKDEOBJ/DEOBJ/DEOBJ_Provider.cxx b/src/DataExchange/TKDEOBJ/DEOBJ/DEOBJ_Provider.cxx index 0660c5f24a..b90f7b2d68 100644 --- a/src/DataExchange/TKDEOBJ/DEOBJ/DEOBJ_Provider.cxx +++ b/src/DataExchange/TKDEOBJ/DEOBJ/DEOBJ_Provider.cxx @@ -14,6 +14,7 @@ #include #include +#include #include #include #include @@ -62,16 +63,15 @@ bool DEOBJ_Provider::Read(const TCollection_AsciiString& thePath, const Handle(TDocStd_Document)& theDocument, const Message_ProgressRange& theProgress) { - if (theDocument.IsNull()) + TCollection_AsciiString aContext = TCollection_AsciiString("reading the file ") + thePath; + if (!DE_ValidationUtils::ValidateDocument(theDocument, aContext)) { - Message::SendFail() << "Error in the DEOBJ_Provider during reading the file " << thePath - << "\t: theDocument shouldn't be null"; return false; } - if (GetNode().IsNull() || !GetNode()->IsKind(STANDARD_TYPE(DEOBJ_ConfigurationNode))) + if (!DE_ValidationUtils::ValidateConfigurationNode(GetNode(), + STANDARD_TYPE(DEOBJ_ConfigurationNode), + aContext)) { - Message::SendFail() << "Error in the DEOBJ_ConfigurationNode during reading the file " - << thePath << "\t: Incorrect or empty Configuration Node"; return false; } Handle(DEOBJ_ConfigurationNode) aNode = Handle(DEOBJ_ConfigurationNode)::DownCast(GetNode()); diff --git a/src/DataExchange/TKDEPLY/DEPLY/DEPLY_Provider.cxx b/src/DataExchange/TKDEPLY/DEPLY/DEPLY_Provider.cxx index 7317c3b6bf..93bba3cefe 100644 --- a/src/DataExchange/TKDEPLY/DEPLY/DEPLY_Provider.cxx +++ b/src/DataExchange/TKDEPLY/DEPLY/DEPLY_Provider.cxx @@ -14,6 +14,7 @@ #include #include +#include #include #include #include @@ -55,10 +56,11 @@ bool DEPLY_Provider::Write(const TCollection_AsciiString& thePath, const Handle(TDocStd_Document)& theDocument, const Message_ProgressRange& theProgress) { - if (GetNode().IsNull() || !GetNode()->IsKind(STANDARD_TYPE(DEPLY_ConfigurationNode))) + TCollection_AsciiString aContext = TCollection_AsciiString("writing the file ") + thePath; + if (!DE_ValidationUtils::ValidateConfigurationNode(GetNode(), + STANDARD_TYPE(DEPLY_ConfigurationNode), + aContext)) { - Message::SendFail() << "Error in the DEPLY_Provider during writing the file " << thePath - << "\t: Incorrect or empty Configuration Node"; return false; } Handle(DEPLY_ConfigurationNode) aNode = Handle(DEPLY_ConfigurationNode)::DownCast(GetNode()); diff --git a/src/DataExchange/TKDESTEP/DESTEP/DESTEP_ConfigurationNode.cxx b/src/DataExchange/TKDESTEP/DESTEP/DESTEP_ConfigurationNode.cxx index b35be0c85e..be25e53a79 100644 --- a/src/DataExchange/TKDESTEP/DESTEP/DESTEP_ConfigurationNode.cxx +++ b/src/DataExchange/TKDESTEP/DESTEP/DESTEP_ConfigurationNode.cxx @@ -635,6 +635,13 @@ bool DESTEP_ConfigurationNode::IsExportSupported() const //================================================================================================= +bool DESTEP_ConfigurationNode::IsStreamSupported() const +{ + return true; +} + +//================================================================================================= + TCollection_AsciiString DESTEP_ConfigurationNode::GetFormat() const { return TCollection_AsciiString("STEP"); diff --git a/src/DataExchange/TKDESTEP/DESTEP/DESTEP_ConfigurationNode.hxx b/src/DataExchange/TKDESTEP/DESTEP/DESTEP_ConfigurationNode.hxx index 338ea06739..93e22f19ea 100644 --- a/src/DataExchange/TKDESTEP/DESTEP/DESTEP_ConfigurationNode.hxx +++ b/src/DataExchange/TKDESTEP/DESTEP/DESTEP_ConfigurationNode.hxx @@ -68,6 +68,10 @@ public: //! @return true if export is supported Standard_EXPORT virtual bool IsExportSupported() const Standard_OVERRIDE; + //! Checks for stream support. + //! @return Standard_True if streams are supported + Standard_EXPORT virtual bool IsStreamSupported() const Standard_OVERRIDE; + //! Gets CAD format name of associated provider //! @return provider CAD format Standard_EXPORT virtual TCollection_AsciiString GetFormat() const Standard_OVERRIDE; diff --git a/src/DataExchange/TKDESTEP/DESTEP/DESTEP_Provider.cxx b/src/DataExchange/TKDESTEP/DESTEP/DESTEP_Provider.cxx index f3d0417554..06e5cd3c0a 100644 --- a/src/DataExchange/TKDESTEP/DESTEP/DESTEP_Provider.cxx +++ b/src/DataExchange/TKDESTEP/DESTEP/DESTEP_Provider.cxx @@ -13,6 +13,7 @@ #include +#include #include #include #include @@ -20,13 +21,125 @@ #include #include #include +#include +#include #include #include #include #include +#include +#include IMPLEMENT_STANDARD_RTTIEXT(DESTEP_Provider, DE_Provider) +namespace +{ +//! Helper function to validate configuration node +Standard_Boolean validateNode(const Handle(DE_ConfigurationNode)& theNode, + const TCollection_AsciiString& theContext) +{ + return DE_ValidationUtils::ValidateConfigurationNode(theNode, + STANDARD_TYPE(DESTEP_ConfigurationNode), + theContext); +} + +//! Configures STEPCAFControl_Reader with specified parameters and optional document setup. +//! @param[in,out] theReader STEP CAF reader to configure +//! @param[in] theParams Parameters containing read settings +//! @param[in] theWS Work session to initialize reader with (optional, if provided reader will +//! be initialized) +//! @param[in] theDocument Target document for length unit setup (optional) +//! @param[in] theLengthUnit Length unit for document setup (used only if theDocument is provided) +//! @param[in] theShapeFixParams Shape fix parameters (optional, uses default if not provided) +//! @note Sets up colors, names, layers, properties, metadata, and shape fix parameters +void configureSTEPCAFReader(STEPCAFControl_Reader& theReader, + const DESTEP_Parameters& theParams, + Handle(XSControl_WorkSession)& theWS, + const Handle(TDocStd_Document)& theDocument, + Standard_Real theLengthUnit, + const DE_ShapeFixParameters& theShapeFixParams) +{ + theReader.Init(theWS); + + theReader.SetColorMode(theParams.ReadColor); + theReader.SetNameMode(theParams.ReadName); + theReader.SetLayerMode(theParams.ReadLayer); + theReader.SetPropsMode(theParams.ReadProps); + theReader.SetMetaMode(theParams.ReadMetadata); + theReader.SetProductMetaMode(theParams.ReadProductMetadata); + + theReader.SetShapeFixParameters(theShapeFixParams); + + XCAFDoc_DocumentTool::SetLengthUnit(theDocument, + theLengthUnit, + UnitsMethods_LengthUnit_Millimeter); +} + +//! Configures STEPCAFControl_Writer with full setup. +//! @param[in,out] theWriter STEP CAF writer to configure +//! @param[in] theParams Parameters containing write settings +//! @param[in,out] theWS Work session to initialize writer with +//! @param[in] theDocument Source document for length unit extraction +//! @param[in] theLengthUnit Length unit for document setup +//! @param[in] theShapeFixParams Shape fix parameters +//! @note Sets up all write parameters including colors, names, layers, props, materials +void configureSTEPCAFWriter(STEPCAFControl_Writer& theWriter, + const DESTEP_Parameters& theParams, + Handle(XSControl_WorkSession)& theWS, + const Handle(TDocStd_Document)& theDocument, + Standard_Real theLengthUnit, + const DE_ShapeFixParameters& theShapeFixParams) +{ + theWriter.Init(theWS); + + theWriter.SetColorMode(theParams.WriteColor); + theWriter.SetNameMode(theParams.WriteName); + theWriter.SetLayerMode(theParams.WriteLayer); + theWriter.SetPropsMode(theParams.WriteProps); + theWriter.SetMaterialMode(theParams.WriteMaterial); + theWriter.SetVisualMaterialMode(theParams.WriteVisMaterial); + theWriter.SetCleanDuplicates(theParams.CleanDuplicates); + + theWriter.SetShapeFixParameters(theShapeFixParams); + + Handle(StepData_StepModel) aModel = + Handle(StepData_StepModel)::DownCast(theWriter.Writer().WS()->Model()); + + Standard_Real aScaleFactorMM = 1.; + if (XCAFDoc_DocumentTool::GetLengthUnit(theDocument, + aScaleFactorMM, + UnitsMethods_LengthUnit_Millimeter)) + { + aModel->SetLocalLengthUnit(aScaleFactorMM); + } + else + { + aModel->SetLocalLengthUnit(theLengthUnit); + Message::SendWarning() + << "Warning in the DESTEP_Provider during writing" + << "\t: The document has no information on Units. Using global parameter as initial Unit."; + } +} + +//! Checks if output stream is in writable state. +//! @param[in] theStream Output stream to check +//! @param[in] theKey Stream identifier for error reporting +//! @return Standard_True if stream is writable, Standard_False otherwise +bool checkStreamWritability(Standard_OStream& theStream, const TCollection_AsciiString& theKey) +{ + if (!theStream.good()) + { + TCollection_AsciiString aKeyInfo = theKey.IsEmpty() ? "" : theKey; + Message::SendFail() << "Error: Output stream '" << aKeyInfo + << "' is not in good state for writing"; + return false; + } + + return true; +} + +} // namespace + //================================================================================================= DESTEP_Provider::DESTEP_Provider() {} @@ -45,35 +158,28 @@ bool DESTEP_Provider::Read(const TCollection_AsciiString& thePath, Handle(XSControl_WorkSession)& theWS, const Message_ProgressRange& theProgress) { - if (theDocument.IsNull()) + TCollection_AsciiString aContext = TCollection_AsciiString("reading the file ") + thePath; + if (!DE_ValidationUtils::ValidateDocument(theDocument, aContext) + || !validateNode(GetNode(), aContext)) { - Message::SendFail() << "Error in the DESTEP_Provider during reading the file " << thePath - << "\t: theDocument shouldn't be null"; - return false; - } - if (GetNode().IsNull() || !GetNode()->IsKind(STANDARD_TYPE(DESTEP_ConfigurationNode))) - { - Message::SendFail() << "Error in the DESTEP_Provider during reading the file " << thePath - << "\t: Incorrect or empty Configuration Node"; return false; } + Handle(DESTEP_ConfigurationNode) aNode = Handle(DESTEP_ConfigurationNode)::DownCast(GetNode()); personizeWS(theWS); - XCAFDoc_DocumentTool::SetLengthUnit(theDocument, - aNode->GlobalParameters.LengthUnit, - UnitsMethods_LengthUnit_Millimeter); + STEPCAFControl_Reader aReader; - aReader.Init(theWS); - aReader.SetColorMode(aNode->InternalParameters.ReadColor); - aReader.SetNameMode(aNode->InternalParameters.ReadName); - aReader.SetLayerMode(aNode->InternalParameters.ReadLayer); - aReader.SetPropsMode(aNode->InternalParameters.ReadProps); - aReader.SetMetaMode(aNode->InternalParameters.ReadMetadata); - aReader.SetProductMetaMode(aNode->InternalParameters.ReadProductMetadata); - aReader.SetShapeFixParameters(aNode->ShapeFixParameters); + configureSTEPCAFReader(aReader, + aNode->InternalParameters, + theWS, + theDocument, + aNode->GlobalParameters.LengthUnit, + aNode->ShapeFixParameters); + IFSelect_ReturnStatus aReadStat = IFSelect_RetVoid; DESTEP_Parameters aParams = aNode->InternalParameters; aReadStat = aReader.ReadFile(thePath.ToCString(), aParams); + if (aReadStat != IFSelect_RetDone) { Message::SendFail() << "Error in the DESTEP_Provider during reading the file " << thePath @@ -97,43 +203,28 @@ bool DESTEP_Provider::Write(const TCollection_AsciiString& thePath, Handle(XSControl_WorkSession)& theWS, const Message_ProgressRange& theProgress) { - if (GetNode().IsNull() || !GetNode()->IsKind(STANDARD_TYPE(DESTEP_ConfigurationNode))) + TCollection_AsciiString aContext = TCollection_AsciiString("writing the file ") + thePath; + if (!DE_ValidationUtils::ValidateDocument(theDocument, aContext) + || !validateNode(GetNode(), aContext)) { - Message::SendFail() << "Error in the DESTEP_Provider during writing the file " << thePath - << "\t: Incorrect or empty Configuration Node"; return false; } Handle(DESTEP_ConfigurationNode) aNode = Handle(DESTEP_ConfigurationNode)::DownCast(GetNode()); personizeWS(theWS); + STEPCAFControl_Writer aWriter; - aWriter.Init(theWS); + configureSTEPCAFWriter(aWriter, + aNode->InternalParameters, + theWS, + theDocument, + aNode->GlobalParameters.SystemUnit, + aNode->ShapeFixParameters); + Handle(StepData_StepModel) aModel = Handle(StepData_StepModel)::DownCast(aWriter.Writer().WS()->Model()); STEPControl_StepModelType aMode = static_cast(aNode->InternalParameters.WriteModelType); - aWriter.SetColorMode(aNode->InternalParameters.WriteColor); - aWriter.SetNameMode(aNode->InternalParameters.WriteName); - aWriter.SetLayerMode(aNode->InternalParameters.WriteLayer); - aWriter.SetPropsMode(aNode->InternalParameters.WriteProps); - aWriter.SetShapeFixParameters(aNode->ShapeFixParameters); - aWriter.SetMaterialMode(aNode->InternalParameters.WriteMaterial); - aWriter.SetVisualMaterialMode(aNode->InternalParameters.WriteVisMaterial); - aWriter.SetCleanDuplicates(aNode->InternalParameters.CleanDuplicates); - DESTEP_Parameters aParams = aNode->InternalParameters; - Standard_Real aScaleFactorMM = 1.; - if (XCAFDoc_DocumentTool::GetLengthUnit(theDocument, - aScaleFactorMM, - UnitsMethods_LengthUnit_Millimeter)) - { - aModel->SetLocalLengthUnit(aScaleFactorMM); - } - else - { - aModel->SetLocalLengthUnit(aNode->GlobalParameters.SystemUnit); - Message::SendWarning() - << "Warning in the DESTEP_Provider during writing the file " << thePath - << "\t: The document has no information on Units. Using global parameter as initial Unit."; - } + DESTEP_Parameters aParams = aNode->InternalParameters; UnitsMethods_LengthUnit aTargetUnit = UnitsMethods::GetLengthUnitByFactorValue(aNode->GlobalParameters.LengthUnit, UnitsMethods_LengthUnit_Millimeter); @@ -194,11 +285,9 @@ bool DESTEP_Provider::Read(const TCollection_AsciiString& thePath, Handle(XSControl_WorkSession)& theWS, const Message_ProgressRange& theProgress) { - (void)theProgress; - if (GetNode().IsNull() || !GetNode()->IsKind(STANDARD_TYPE(DESTEP_ConfigurationNode))) + TCollection_AsciiString aContext = TCollection_AsciiString("reading the file ") + thePath; + if (!validateNode(GetNode(), aContext)) { - Message::SendFail() << "Error in the DESTEP_Provider during reading the file " << thePath - << "\t: Incorrect or empty Configuration Node"; return false; } Handle(DESTEP_ConfigurationNode) aNode = Handle(DESTEP_ConfigurationNode)::DownCast(GetNode()); @@ -217,7 +306,7 @@ bool DESTEP_Provider::Read(const TCollection_AsciiString& thePath, return false; } aModel->SetLocalLengthUnit(aNode->GlobalParameters.LengthUnit); - if (aReader.TransferRoots() <= 0) + if (aReader.TransferRoots(theProgress) <= 0) { Message::SendFail() << "Error in the DESTEP_Provider during reading the file " << thePath << "\t:Cannot read any relevant data from the STEP file"; @@ -234,10 +323,9 @@ bool DESTEP_Provider::Write(const TCollection_AsciiString& thePath, Handle(XSControl_WorkSession)& theWS, const Message_ProgressRange& theProgress) { - if (GetNode().IsNull() || !GetNode()->IsKind(STANDARD_TYPE(DESTEP_ConfigurationNode))) + TCollection_AsciiString aContext = TCollection_AsciiString("writing the file ") + thePath; + if (!validateNode(GetNode(), aContext)) { - Message::SendFail() << "Error in the DESTEP_Provider during reading the file " << thePath - << "\t: Incorrect or empty Configuration Node"; return false; } Handle(DESTEP_ConfigurationNode) aNode = Handle(DESTEP_ConfigurationNode)::DownCast(GetNode()); @@ -312,6 +400,281 @@ bool DESTEP_Provider::Write(const TCollection_AsciiString& thePath, //================================================================================================= +Standard_Boolean DESTEP_Provider::Read(ReadStreamList& theStreams, + const Handle(TDocStd_Document)& theDocument, + Handle(XSControl_WorkSession)& theWS, + const Message_ProgressRange& theProgress) +{ + TCollection_AsciiString aContext = "reading stream"; + if (!DE_ValidationUtils::ValidateReadStreamList(theStreams, aContext)) + { + return Standard_False; + } + + TCollection_AsciiString aFirstKey = theStreams.First().Path; + TCollection_AsciiString aFullContext = aContext + " " + aFirstKey; + if (!DE_ValidationUtils::ValidateDocument(theDocument, aFullContext) + || !validateNode(GetNode(), aFullContext) + || !DE_ValidationUtils::ValidateReadStreamList(theStreams, aFullContext)) + { + return Standard_False; + } + + Standard_IStream& aStream = theStreams.First().Stream; + + personizeWS(theWS); + + Handle(DESTEP_ConfigurationNode) aNode = Handle(DESTEP_ConfigurationNode)::DownCast(GetNode()); + STEPCAFControl_Reader aReader(theWS, Standard_False); + configureSTEPCAFReader(aReader, + aNode->InternalParameters, + theWS, + theDocument, + aNode->GlobalParameters.LengthUnit, + aNode->ShapeFixParameters); + + Standard_Boolean isOk = aReader.ReadStream(aFirstKey.ToCString(), aStream); + if (!isOk) + { + Message::SendFail() << "Error: DESTEP_Provider failed to read stream " << aFirstKey; + return Standard_False; + } + + return aReader.Transfer(theDocument, theProgress); +} + +//================================================================================================= + +Standard_Boolean DESTEP_Provider::Write(WriteStreamList& theStreams, + const Handle(TDocStd_Document)& theDocument, + Handle(XSControl_WorkSession)& theWS, + const Message_ProgressRange& theProgress) +{ + TCollection_AsciiString aContext = "writing stream"; + if (!DE_ValidationUtils::ValidateWriteStreamList(theStreams, aContext)) + { + return Standard_False; + } + + TCollection_AsciiString aFirstKey = theStreams.First().Path; + TCollection_AsciiString aFullContext = aContext + " " + aFirstKey; + if (!DE_ValidationUtils::ValidateDocument(theDocument, aFullContext) + || !validateNode(GetNode(), aFullContext)) + { + return Standard_False; + } + + Standard_OStream& aStream = theStreams.First().Stream; + if (!checkStreamWritability(aStream, aFirstKey)) + { + return Standard_False; + } + + personizeWS(theWS); + + Handle(DESTEP_ConfigurationNode) aNode = Handle(DESTEP_ConfigurationNode)::DownCast(GetNode()); + + STEPCAFControl_Writer aWriter(theWS, Standard_False); + configureSTEPCAFWriter(aWriter, + aNode->InternalParameters, + theWS, + theDocument, + aNode->GlobalParameters.LengthUnit, + aNode->ShapeFixParameters); + + Handle(StepData_StepModel) aModel = + Handle(StepData_StepModel)::DownCast(aWriter.Writer().WS()->Model()); + DESTEP_Parameters aParams = aNode->InternalParameters; + UnitsMethods_LengthUnit aTargetUnit = + UnitsMethods::GetLengthUnitByFactorValue(aNode->GlobalParameters.LengthUnit, + UnitsMethods_LengthUnit_Millimeter); + aParams.WriteUnit = aTargetUnit; + aModel->SetWriteLengthUnit(aNode->GlobalParameters.LengthUnit); + STEPControl_StepModelType aMode = + static_cast(aNode->InternalParameters.WriteModelType); + Standard_Boolean isOk = aWriter.Transfer(theDocument, aParams, aMode, 0, theProgress); + if (!isOk) + { + Message::SendFail() << "Error: DESTEP_Provider failed to transfer document for stream " + << aFirstKey; + return Standard_False; + } + return aWriter.WriteStream(aStream); +} + +//================================================================================================= + +Standard_Boolean DESTEP_Provider::Read(ReadStreamList& theStreams, + const Handle(TDocStd_Document)& theDocument, + const Message_ProgressRange& theProgress) +{ + Handle(XSControl_WorkSession) aWS = new XSControl_WorkSession(); + return Read(theStreams, theDocument, aWS, theProgress); +} + +//================================================================================================= + +Standard_Boolean DESTEP_Provider::Write(WriteStreamList& theStreams, + const Handle(TDocStd_Document)& theDocument, + const Message_ProgressRange& theProgress) +{ + Handle(XSControl_WorkSession) aWS = new XSControl_WorkSession(); + return Write(theStreams, theDocument, aWS, theProgress); +} + +//================================================================================================= + +Standard_Boolean DESTEP_Provider::Read(ReadStreamList& theStreams, + TopoDS_Shape& theShape, + Handle(XSControl_WorkSession)& theWS, + const Message_ProgressRange& theProgress) +{ + TCollection_AsciiString aContext = "reading stream"; + if (!DE_ValidationUtils::ValidateReadStreamList(theStreams, aContext)) + { + return Standard_False; + } + + TCollection_AsciiString aFirstKey = theStreams.First().Path; + TCollection_AsciiString aFullContext = aContext + " " + aFirstKey; + if (!validateNode(GetNode(), aFullContext)) + { + return Standard_False; + } + + Standard_IStream& aStream = theStreams.First().Stream; + personizeWS(theWS); + + Handle(DESTEP_ConfigurationNode) aNode = Handle(DESTEP_ConfigurationNode)::DownCast(GetNode()); + + // Use STEPControl_Reader for shape operations from streams + STEPControl_Reader aReader; + aReader.SetWS(theWS); + aReader.SetShapeFixParameters(aNode->ShapeFixParameters); + + // Read from stream using the reader's internal model + IFSelect_ReturnStatus aReadStat = aReader.ReadStream(aFirstKey.ToCString(), aStream); + if (aReadStat != IFSelect_RetDone) + { + Message::SendFail() << "Error: DESTEP_Provider failed to read from stream " << aFirstKey; + return Standard_False; + } + Handle(StepData_StepModel) aModel = aReader.StepModel(); + aModel->SetLocalLengthUnit(aNode->GlobalParameters.LengthUnit); + + // Transfer the first root to get the shape + if (aReader.TransferRoots(theProgress) <= 0) + { + Message::SendFail() << "Error: DESTEP_Provider found no transferable roots in stream " + << aFirstKey; + return Standard_False; + } + + theShape = aReader.OneShape(); + return Standard_True; +} + +//================================================================================================= + +Standard_Boolean DESTEP_Provider::Write(WriteStreamList& theStreams, + const TopoDS_Shape& theShape, + Handle(XSControl_WorkSession)& theWS, + const Message_ProgressRange& theProgress) +{ + TCollection_AsciiString aContext = "writing stream"; + if (!DE_ValidationUtils::ValidateWriteStreamList(theStreams, aContext)) + { + return Standard_False; + } + + TCollection_AsciiString aFirstKey = theStreams.First().Path; + TCollection_AsciiString aFullContext = aContext + " " + aFirstKey; + if (!validateNode(GetNode(), aFullContext)) + { + return Standard_False; + } + + Standard_OStream& aStream = theStreams.First().Stream; + personizeWS(theWS); + + Handle(DESTEP_ConfigurationNode) aNode = Handle(DESTEP_ConfigurationNode)::DownCast(GetNode()); + + // Use STEPControl_Writer for shape operations to streams + STEPControl_Writer aWriter; + aWriter.SetWS(theWS); + + Handle(StepData_StepModel) aModel = aWriter.Model(); + aModel->SetLocalLengthUnit(aNode->GlobalParameters.SystemUnit); + + UnitsMethods_LengthUnit aTargetUnit = + UnitsMethods::GetLengthUnitByFactorValue(aNode->GlobalParameters.LengthUnit, + UnitsMethods_LengthUnit_Millimeter); + DESTEP_Parameters aParams = aNode->InternalParameters; + aParams.WriteUnit = aTargetUnit; + + if (aTargetUnit == UnitsMethods_LengthUnit_Undefined) + { + aModel->SetWriteLengthUnit(1.0); + Message::SendWarning() + << "Custom units are not supported by STEP format, but LengthUnit global parameter doesn't " + "fit any predefined unit. Units will be scaled to Millimeters"; + } + else + { + aModel->SetWriteLengthUnit(aNode->GlobalParameters.LengthUnit); + } + + aWriter.SetShapeFixParameters(aNode->ShapeFixParameters); + + IFSelect_ReturnStatus aWriteStat = aWriter.Transfer(theShape, + aNode->InternalParameters.WriteModelType, + aParams, + true, + theProgress); + if (aWriteStat != IFSelect_RetDone) + { + Message::SendFail() << "Error: DESTEP_Provider failed to transfer shape for stream " + << aFirstKey; + return Standard_False; + } + + if (aNode->InternalParameters.CleanDuplicates) + { + aWriter.CleanDuplicateEntities(); + } + + // Write to stream + if (!aWriter.WriteStream(aStream)) + { + Message::SendFail() << "Error: DESTEP_Provider failed to write to stream " << aFirstKey; + return Standard_False; + } + + return Standard_True; +} + +//================================================================================================= + +Standard_Boolean DESTEP_Provider::Read(ReadStreamList& theStreams, + TopoDS_Shape& theShape, + const Message_ProgressRange& theProgress) +{ + Handle(XSControl_WorkSession) aWS = new XSControl_WorkSession(); + return Read(theStreams, theShape, aWS, theProgress); +} + +//================================================================================================= + +Standard_Boolean DESTEP_Provider::Write(WriteStreamList& theStreams, + const TopoDS_Shape& theShape, + const Message_ProgressRange& theProgress) +{ + Handle(XSControl_WorkSession) aWS = new XSControl_WorkSession(); + return Write(theStreams, theShape, aWS, theProgress); +} + +//================================================================================================= + TCollection_AsciiString DESTEP_Provider::GetFormat() const { return TCollection_AsciiString("STEP"); diff --git a/src/DataExchange/TKDESTEP/DESTEP/DESTEP_Provider.hxx b/src/DataExchange/TKDESTEP/DESTEP/DESTEP_Provider.hxx index c049d75b3f..308fd3e79c 100644 --- a/src/DataExchange/TKDESTEP/DESTEP/DESTEP_Provider.hxx +++ b/src/DataExchange/TKDESTEP/DESTEP/DESTEP_Provider.hxx @@ -109,6 +109,54 @@ public: Handle(XSControl_WorkSession)& theWS, const Message_ProgressRange& theProgress = Message_ProgressRange()) Standard_OVERRIDE; + //! Reads streams according to internal configuration + //! @param[in] theStreams streams to read from + //! @param[out] theDocument document to save result + //! @param[in] theWS current work session + //! @param[in] theProgress progress indicator + //! @return true if Read operation has ended correctly + Standard_EXPORT virtual Standard_Boolean Read( + ReadStreamList& theStreams, + const Handle(TDocStd_Document)& theDocument, + Handle(XSControl_WorkSession)& theWS, + const Message_ProgressRange& theProgress = Message_ProgressRange()) Standard_OVERRIDE; + + //! Writes streams according to internal configuration + //! @param[in] theStreams streams to write to + //! @param[out] theDocument document to export + //! @param[in] theWS current work session + //! @param[in] theProgress progress indicator + //! @return true if Write operation has ended correctly + Standard_EXPORT virtual Standard_Boolean Write( + WriteStreamList& theStreams, + const Handle(TDocStd_Document)& theDocument, + Handle(XSControl_WorkSession)& theWS, + const Message_ProgressRange& theProgress = Message_ProgressRange()) Standard_OVERRIDE; + + //! Reads streams according to internal configuration + //! @param[in] theStreams streams to read from + //! @param[out] theShape shape to save result + //! @param[in] theWS current work session + //! @param[in] theProgress progress indicator + //! @return true if Read operation has ended correctly + Standard_EXPORT virtual Standard_Boolean Read( + ReadStreamList& theStreams, + TopoDS_Shape& theShape, + Handle(XSControl_WorkSession)& theWS, + const Message_ProgressRange& theProgress = Message_ProgressRange()) Standard_OVERRIDE; + + //! Writes streams according to internal configuration + //! @param[in] theStreams streams to write to + //! @param[out] theShape shape to export + //! @param[in] theWS current work session + //! @param[in] theProgress progress indicator + //! @return true if Write operation has ended correctly + Standard_EXPORT virtual Standard_Boolean Write( + WriteStreamList& theStreams, + const TopoDS_Shape& theShape, + Handle(XSControl_WorkSession)& theWS, + const Message_ProgressRange& theProgress = Message_ProgressRange()) Standard_OVERRIDE; + //! Reads a CAD file, according internal configuration //! @param[in] thePath path to the import CAD file //! @param[out] theShape shape to save result @@ -129,6 +177,46 @@ public: const TopoDS_Shape& theShape, const Message_ProgressRange& theProgress = Message_ProgressRange()) Standard_OVERRIDE; + //! Reads streams according to internal configuration + //! @param[in] theStreams streams to read from + //! @param[out] theDocument document to save result + //! @param[in] theProgress progress indicator + //! @return true if Read operation has ended correctly + Standard_EXPORT virtual Standard_Boolean Read( + ReadStreamList& theStreams, + const Handle(TDocStd_Document)& theDocument, + const Message_ProgressRange& theProgress = Message_ProgressRange()) Standard_OVERRIDE; + + //! Writes streams according to internal configuration + //! @param[in] theStreams streams to write to + //! @param[out] theDocument document to export + //! @param[in] theProgress progress indicator + //! @return true if Write operation has ended correctly + Standard_EXPORT virtual Standard_Boolean Write( + WriteStreamList& theStreams, + const Handle(TDocStd_Document)& theDocument, + const Message_ProgressRange& theProgress = Message_ProgressRange()) Standard_OVERRIDE; + + //! Reads streams according to internal configuration + //! @param[in] theStreams streams to read from + //! @param[out] theShape shape to save result + //! @param[in] theProgress progress indicator + //! @return true if Read operation has ended correctly + Standard_EXPORT virtual Standard_Boolean Read( + ReadStreamList& theStreams, + TopoDS_Shape& theShape, + const Message_ProgressRange& theProgress = Message_ProgressRange()) Standard_OVERRIDE; + + //! Writes streams according to internal configuration + //! @param[in] theStreams streams to write to + //! @param[out] theShape shape to export + //! @param[in] theProgress progress indicator + //! @return true if Write operation has ended correctly + Standard_EXPORT virtual Standard_Boolean Write( + WriteStreamList& theStreams, + const TopoDS_Shape& theShape, + const Message_ProgressRange& theProgress = Message_ProgressRange()) Standard_OVERRIDE; + public: //! Gets CAD format name of associated provider //! @return provider CAD format diff --git a/src/DataExchange/TKDESTEP/GTests/DESTEP_Provider_Test.cxx b/src/DataExchange/TKDESTEP/GTests/DESTEP_Provider_Test.cxx new file mode 100644 index 0000000000..b616c7358d --- /dev/null +++ b/src/DataExchange/TKDESTEP/GTests/DESTEP_Provider_Test.cxx @@ -0,0 +1,475 @@ +// Copyright (c) 2025 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +class DESTEP_ProviderTest : public ::testing::Test +{ +protected: + void SetUp() override + { + // Initialize provider with default configuration + Handle(DESTEP_ConfigurationNode) aNode = new DESTEP_ConfigurationNode(); + myProvider = new DESTEP_Provider(aNode); + + // Create test BRep shapes (perfect for STEP format) + myBox = BRepPrimAPI_MakeBox(10.0, 10.0, 10.0).Shape(); + mySphere = BRepPrimAPI_MakeSphere(5.0).Shape(); + myCylinder = BRepPrimAPI_MakeCylinder(3.0, 8.0).Shape(); + + // Create test document + Handle(TDocStd_Application) anApp = new TDocStd_Application(); + anApp->NewDocument("BinXCAF", myDocument); + } + + void TearDown() override + { + myProvider.Nullify(); + myDocument.Nullify(); + } + + // Helper method to count shape elements + Standard_Integer CountShapeElements(const TopoDS_Shape& theShape, TopAbs_ShapeEnum theType) + { + Standard_Integer aCount = 0; + for (TopExp_Explorer anExplorer(theShape, theType); anExplorer.More(); anExplorer.Next()) + { + aCount++; + } + return aCount; + } + + // Helper method to validate STEP content + bool IsValidSTEPContent(const std::string& theContent) + { + return !theContent.empty() && theContent.find("ISO-10303-21;") != std::string::npos + && theContent.find("HEADER;") != std::string::npos + && theContent.find("DATA;") != std::string::npos + && theContent.find("ENDSEC;") != std::string::npos; + } + +protected: + Handle(DESTEP_Provider) myProvider; + TopoDS_Shape myBox; + TopoDS_Shape mySphere; + TopoDS_Shape myCylinder; + Handle(TDocStd_Document) myDocument; +}; + +// Test basic provider creation and format/vendor information +TEST_F(DESTEP_ProviderTest, BasicProperties) +{ + EXPECT_STREQ("STEP", myProvider->GetFormat().ToCString()); + EXPECT_STREQ("OCC", myProvider->GetVendor().ToCString()); + EXPECT_FALSE(myProvider->GetNode().IsNull()); +} + +// Test stream-based shape write and read operations +TEST_F(DESTEP_ProviderTest, StreamShapeWriteRead) +{ + std::ostringstream anOStream; + DE_Provider::WriteStreamList aWriteStreams; + aWriteStreams.Append(DE_Provider::WriteStreamNode("test.step", anOStream)); + + // Write box shape to stream + EXPECT_TRUE(myProvider->Write(aWriteStreams, myBox)); + + std::string aStepContent = anOStream.str(); + EXPECT_FALSE(aStepContent.empty()); + EXPECT_TRUE(IsValidSTEPContent(aStepContent)); + + if (!aStepContent.empty()) + { + // Read back from stream + std::istringstream anIStream(aStepContent); + DE_Provider::ReadStreamList aReadStreams; + aReadStreams.Append(DE_Provider::ReadStreamNode("test.step", anIStream)); + + TopoDS_Shape aReadShape; + EXPECT_TRUE(myProvider->Read(aReadStreams, aReadShape)); + EXPECT_FALSE(aReadShape.IsNull()); + + if (!aReadShape.IsNull()) + { + // STEP should preserve solid geometry + Standard_Integer aReadSolids = CountShapeElements(aReadShape, TopAbs_SOLID); + Standard_Integer aOriginalSolids = CountShapeElements(myBox, TopAbs_SOLID); + EXPECT_EQ(aReadSolids, aOriginalSolids); + } + } +} + +// Test stream-based document write and read operations +TEST_F(DESTEP_ProviderTest, StreamDocumentWriteRead) +{ + // Add box to document + Handle(XCAFDoc_ShapeTool) aShapeTool = XCAFDoc_DocumentTool::ShapeTool(myDocument->Main()); + TDF_Label aShapeLabel = aShapeTool->AddShape(myBox); + EXPECT_FALSE(aShapeLabel.IsNull()); + + std::ostringstream anOStream; + DE_Provider::WriteStreamList aWriteStreams; + aWriteStreams.Append(DE_Provider::WriteStreamNode("document.step", anOStream)); + + // Write document to stream + EXPECT_TRUE(myProvider->Write(aWriteStreams, myDocument)); + + std::string aStepContent = anOStream.str(); + EXPECT_FALSE(aStepContent.empty()); + EXPECT_TRUE(IsValidSTEPContent(aStepContent)); + + if (!aStepContent.empty()) + { + // Create new document for reading + Handle(TDocStd_Application) anApp = new TDocStd_Application(); + Handle(TDocStd_Document) aNewDocument; + anApp->NewDocument("BinXCAF", aNewDocument); + + // Read back from stream + std::istringstream anIStream(aStepContent); + DE_Provider::ReadStreamList aReadStreams; + aReadStreams.Append(DE_Provider::ReadStreamNode("document.step", anIStream)); + + EXPECT_TRUE(myProvider->Read(aReadStreams, aNewDocument)); + + // Validate document content + Handle(XCAFDoc_ShapeTool) aNewShapeTool = XCAFDoc_DocumentTool::ShapeTool(aNewDocument->Main()); + TDF_LabelSequence aLabels; + aNewShapeTool->GetShapes(aLabels); + EXPECT_GT(aLabels.Length(), 0); // Should have at least one shape in document + } +} + +// Test DE_Wrapper integration for STEP operations +TEST_F(DESTEP_ProviderTest, DE_WrapperIntegration) +{ + // Initialize DE_Wrapper and bind STEP provider + DE_Wrapper aWrapper; + Handle(DESTEP_ConfigurationNode) aNode = new DESTEP_ConfigurationNode(); + + // Bind the configured node to wrapper + EXPECT_TRUE(aWrapper.Bind(aNode)); + + // Test write with DE_Wrapper using sphere + std::ostringstream anOStream; + DE_Provider::WriteStreamList aWriteStreams; + aWriteStreams.Append(DE_Provider::WriteStreamNode("test.step", anOStream)); + + EXPECT_TRUE(aWrapper.Write(aWriteStreams, mySphere)); + + std::string aStepContent = anOStream.str(); + EXPECT_FALSE(aStepContent.empty()); + EXPECT_TRUE(IsValidSTEPContent(aStepContent)); + + if (!aStepContent.empty()) + { + // Test DE_Wrapper stream operations + std::istringstream anIStream(aStepContent); + DE_Provider::ReadStreamList aReadStreams; + aReadStreams.Append(DE_Provider::ReadStreamNode("test.step", anIStream)); + + TopoDS_Shape aReadShape; + bool aWrapperResult = aWrapper.Read(aReadStreams, aReadShape); + + // Test direct provider with same content for comparison + std::istringstream anIStream2(aStepContent); + DE_Provider::ReadStreamList aReadStreams2; + aReadStreams2.Append(DE_Provider::ReadStreamNode("test.step", anIStream2)); + + Handle(DESTEP_Provider) aDirectProvider = new DESTEP_Provider(aNode); + TopoDS_Shape aDirectShape; + bool aDirectResult = aDirectProvider->Read(aReadStreams2, aDirectShape); + + // REQUIREMENT: DE_Wrapper must work exactly the same as direct provider + EXPECT_EQ(aWrapperResult, aDirectResult); + EXPECT_EQ(aReadShape.IsNull(), aDirectShape.IsNull()); + + if (aDirectResult && !aDirectShape.IsNull()) + { + Standard_Integer aSolids = CountShapeElements(aDirectShape, TopAbs_SOLID); + EXPECT_GT(aSolids, 0); + } + } +} + +// Test multiple shapes in single document +TEST_F(DESTEP_ProviderTest, MultipleShapesInDocument) +{ + // Add multiple shapes to document + Handle(XCAFDoc_ShapeTool) aShapeTool = XCAFDoc_DocumentTool::ShapeTool(myDocument->Main()); + TDF_Label aBoxLabel = aShapeTool->AddShape(myBox); + TDF_Label aSphereLabel = aShapeTool->AddShape(mySphere); + TDF_Label aCylinderLabel = aShapeTool->AddShape(myCylinder); + + EXPECT_FALSE(aBoxLabel.IsNull()); + EXPECT_FALSE(aSphereLabel.IsNull()); + EXPECT_FALSE(aCylinderLabel.IsNull()); + + std::ostringstream anOStream; + DE_Provider::WriteStreamList aWriteStreams; + aWriteStreams.Append(DE_Provider::WriteStreamNode("multi_shapes.step", anOStream)); + + // Write document with multiple shapes + EXPECT_TRUE(myProvider->Write(aWriteStreams, myDocument)); + + std::string aStepContent = anOStream.str(); + EXPECT_FALSE(aStepContent.empty()); + EXPECT_TRUE(IsValidSTEPContent(aStepContent)); + + // Read back into new document + Handle(TDocStd_Application) anApp = new TDocStd_Application(); + Handle(TDocStd_Document) aNewDocument; + anApp->NewDocument("BinXCAF", aNewDocument); + + std::istringstream anIStream(aStepContent); + DE_Provider::ReadStreamList aReadStreams; + aReadStreams.Append(DE_Provider::ReadStreamNode("multi_shapes.step", anIStream)); + + EXPECT_TRUE(myProvider->Read(aReadStreams, aNewDocument)); + + // Validate document content + Handle(XCAFDoc_ShapeTool) aNewShapeTool = XCAFDoc_DocumentTool::ShapeTool(aNewDocument->Main()); + TDF_LabelSequence aLabels; + aNewShapeTool->GetShapes(aLabels); + EXPECT_EQ(aLabels.Length(), 3); // Should have exactly 3 shapes in document +} + +// Test different BRep geometry types +TEST_F(DESTEP_ProviderTest, DifferentBRepGeometries) +{ + // Test box geometry + std::ostringstream aBoxStream; + DE_Provider::WriteStreamList aBoxWriteStreams; + aBoxWriteStreams.Append(DE_Provider::WriteStreamNode("box.step", aBoxStream)); + + EXPECT_TRUE(myProvider->Write(aBoxWriteStreams, myBox)); + std::string aBoxContent = aBoxStream.str(); + + // Test sphere geometry + std::ostringstream aSphereStream; + DE_Provider::WriteStreamList aSphereWriteStreams; + aSphereWriteStreams.Append(DE_Provider::WriteStreamNode("sphere.step", aSphereStream)); + + EXPECT_TRUE(myProvider->Write(aSphereWriteStreams, mySphere)); + std::string aSphereContent = aSphereStream.str(); + + // Test cylinder geometry + std::ostringstream aCylinderStream; + DE_Provider::WriteStreamList aCylinderWriteStreams; + aCylinderWriteStreams.Append(DE_Provider::WriteStreamNode("cylinder.step", aCylinderStream)); + + EXPECT_TRUE(myProvider->Write(aCylinderWriteStreams, myCylinder)); + std::string aCylinderContent = aCylinderStream.str(); + + // All content should be valid STEP format + EXPECT_TRUE(IsValidSTEPContent(aBoxContent)); + EXPECT_TRUE(IsValidSTEPContent(aSphereContent)); + EXPECT_TRUE(IsValidSTEPContent(aCylinderContent)); + + // Different geometries should produce different STEP content + EXPECT_NE(aBoxContent, aSphereContent); + EXPECT_NE(aBoxContent, aCylinderContent); + EXPECT_NE(aSphereContent, aCylinderContent); + + // All should read back successfully + std::istringstream aBoxIStream(aBoxContent); + DE_Provider::ReadStreamList aBoxReadStreams; + aBoxReadStreams.Append(DE_Provider::ReadStreamNode("box.step", aBoxIStream)); + + TopoDS_Shape aBoxReadShape; + EXPECT_TRUE(myProvider->Read(aBoxReadStreams, aBoxReadShape)); + EXPECT_FALSE(aBoxReadShape.IsNull()); + + std::istringstream aSphereIStream(aSphereContent); + DE_Provider::ReadStreamList aSphereReadStreams; + aSphereReadStreams.Append(DE_Provider::ReadStreamNode("sphere.step", aSphereIStream)); + + TopoDS_Shape aSphereReadShape; + EXPECT_TRUE(myProvider->Read(aSphereReadStreams, aSphereReadShape)); + EXPECT_FALSE(aSphereReadShape.IsNull()); +} + +// Test DE_Wrapper with different file extensions +TEST_F(DESTEP_ProviderTest, DE_WrapperFileExtensions) +{ + DE_Wrapper aWrapper; + Handle(DESTEP_ConfigurationNode) aNode = new DESTEP_ConfigurationNode(); + EXPECT_TRUE(aWrapper.Bind(aNode)); + + // Test different STEP extensions + std::vector aExtensions = {"test.step", "test.STEP", "test.stp", "test.STP"}; + + for (const auto& anExt : aExtensions) + { + std::ostringstream anOStream; + DE_Provider::WriteStreamList aWriteStreams; + aWriteStreams.Append(DE_Provider::WriteStreamNode(anExt.c_str(), anOStream)); + + EXPECT_TRUE(aWrapper.Write(aWriteStreams, myBox)) + << "Failed to write with extension: " << anExt; + + std::string aContent = anOStream.str(); + EXPECT_FALSE(aContent.empty()) << "Empty content for extension: " << anExt; + EXPECT_TRUE(IsValidSTEPContent(aContent)) << "Invalid STEP content for extension: " << anExt; + + // Test read back + std::istringstream anIStream(aContent); + DE_Provider::ReadStreamList aReadStreams; + aReadStreams.Append(DE_Provider::ReadStreamNode(anExt.c_str(), anIStream)); + + TopoDS_Shape aReadShape; + EXPECT_TRUE(aWrapper.Read(aReadStreams, aReadShape)) + << "Failed to read with extension: " << anExt; + EXPECT_FALSE(aReadShape.IsNull()) << "Null shape read with extension: " << anExt; + } +} + +// Test error conditions and edge cases +TEST_F(DESTEP_ProviderTest, ErrorHandling) +{ + // Test with empty streams + DE_Provider::WriteStreamList anEmptyWriteStreams; + EXPECT_FALSE(myProvider->Write(anEmptyWriteStreams, myBox)); + + DE_Provider::ReadStreamList anEmptyReadStreams; + TopoDS_Shape aShape; + EXPECT_FALSE(myProvider->Read(anEmptyReadStreams, aShape)); + + // Test with null shape + std::ostringstream anOStream; + DE_Provider::WriteStreamList aWriteStreams; + aWriteStreams.Append(DE_Provider::WriteStreamNode("null_test.step", anOStream)); + TopoDS_Shape aNullShape; + + // Writing null shape should fail + EXPECT_FALSE(myProvider->Write(aWriteStreams, aNullShape)); + + // Test reading invalid STEP content + std::string anInvalidContent = "This is not valid STEP content"; + std::istringstream anInvalidStream(anInvalidContent); + DE_Provider::ReadStreamList anInvalidReadStreams; + anInvalidReadStreams.Append(DE_Provider::ReadStreamNode("invalid.step", anInvalidStream)); + + TopoDS_Shape anInvalidShape; + EXPECT_FALSE(myProvider->Read(anInvalidReadStreams, anInvalidShape)); + + // Test with null document + Handle(TDocStd_Document) aNullDoc; + EXPECT_FALSE(myProvider->Write(aWriteStreams, aNullDoc)); + EXPECT_FALSE(myProvider->Read(anEmptyReadStreams, aNullDoc)); +} + +// Test DESTEP configuration modes +TEST_F(DESTEP_ProviderTest, ConfigurationModes) +{ + Handle(DESTEP_ConfigurationNode) aNode = + Handle(DESTEP_ConfigurationNode)::DownCast(myProvider->GetNode()); + + // Test basic configuration access + EXPECT_FALSE(aNode.IsNull()); + + // Test provider format and vendor are correct + EXPECT_STREQ("STEP", myProvider->GetFormat().ToCString()); + EXPECT_STREQ("OCC", myProvider->GetVendor().ToCString()); + + // Test that we can create provider with different configuration + Handle(DESTEP_ConfigurationNode) aNewNode = new DESTEP_ConfigurationNode(); + Handle(DESTEP_Provider) aNewProvider = new DESTEP_Provider(aNewNode); + + EXPECT_STREQ("STEP", aNewProvider->GetFormat().ToCString()); + EXPECT_STREQ("OCC", aNewProvider->GetVendor().ToCString()); + EXPECT_FALSE(aNewProvider->GetNode().IsNull()); +} + +// Test WorkSession integration +TEST_F(DESTEP_ProviderTest, WorkSessionIntegration) +{ + Handle(XSControl_WorkSession) aWS = new XSControl_WorkSession(); + + // Test write operation with work session + std::ostringstream anOStream; + DE_Provider::WriteStreamList aWriteStreams; + aWriteStreams.Append(DE_Provider::WriteStreamNode("ws_test.step", anOStream)); + + EXPECT_TRUE(myProvider->Write(aWriteStreams, myBox, aWS)); + + std::string aStepContent = anOStream.str(); + EXPECT_FALSE(aStepContent.empty()); + EXPECT_TRUE(IsValidSTEPContent(aStepContent)); + + // Test read operation with work session + std::istringstream anIStream(aStepContent); + DE_Provider::ReadStreamList aReadStreams; + aReadStreams.Append(DE_Provider::ReadStreamNode("ws_test.step", anIStream)); + + TopoDS_Shape aReadShape; + EXPECT_TRUE(myProvider->Read(aReadStreams, aReadShape, aWS)); + EXPECT_FALSE(aReadShape.IsNull()); +} + +// Test document operations with WorkSession +TEST_F(DESTEP_ProviderTest, DocumentWorkSessionIntegration) +{ + // Add shape to document + Handle(XCAFDoc_ShapeTool) aShapeTool = XCAFDoc_DocumentTool::ShapeTool(myDocument->Main()); + TDF_Label aShapeLabel = aShapeTool->AddShape(mySphere); + EXPECT_FALSE(aShapeLabel.IsNull()); + + Handle(XSControl_WorkSession) aWS = new XSControl_WorkSession(); + + std::ostringstream anOStream; + DE_Provider::WriteStreamList aWriteStreams; + aWriteStreams.Append(DE_Provider::WriteStreamNode("doc_ws_test.step", anOStream)); + + // Test document write with work session + EXPECT_TRUE(myProvider->Write(aWriteStreams, myDocument, aWS)); + + std::string aStepContent = anOStream.str(); + EXPECT_FALSE(aStepContent.empty()); + EXPECT_TRUE(IsValidSTEPContent(aStepContent)); + + // Create new document for reading + Handle(TDocStd_Application) anApp = new TDocStd_Application(); + Handle(TDocStd_Document) aNewDocument; + anApp->NewDocument("BinXCAF", aNewDocument); + + // Test document read with work session + std::istringstream anIStream(aStepContent); + DE_Provider::ReadStreamList aReadStreams; + aReadStreams.Append(DE_Provider::ReadStreamNode("doc_ws_test.step", anIStream)); + + EXPECT_TRUE(myProvider->Read(aReadStreams, aNewDocument, aWS)); + + // Validate document content + Handle(XCAFDoc_ShapeTool) aNewShapeTool = XCAFDoc_DocumentTool::ShapeTool(aNewDocument->Main()); + TDF_LabelSequence aLabels; + aNewShapeTool->GetShapes(aLabels); + EXPECT_GT(aLabels.Length(), 0); +} \ No newline at end of file diff --git a/src/DataExchange/TKDESTEP/GTests/FILES.cmake b/src/DataExchange/TKDESTEP/GTests/FILES.cmake index df6050cd9d..d51e3db502 100644 --- a/src/DataExchange/TKDESTEP/GTests/FILES.cmake +++ b/src/DataExchange/TKDESTEP/GTests/FILES.cmake @@ -2,6 +2,7 @@ set(OCCT_TKDESTEP_GTests_FILES_LOCATION "${CMAKE_CURRENT_LIST_DIR}") set(OCCT_TKDESTEP_GTests_FILES + DESTEP_Provider_Test.cxx STEPConstruct_RenderingProperties_Test.cxx StepData_StepWriter_Test.cxx StepTidy_BaseTestFixture.pxx diff --git a/src/DataExchange/TKDESTL/DESTL/DESTL_ConfigurationNode.cxx b/src/DataExchange/TKDESTL/DESTL/DESTL_ConfigurationNode.cxx index 107aa782b6..cdcf2c16ff 100644 --- a/src/DataExchange/TKDESTL/DESTL/DESTL_ConfigurationNode.cxx +++ b/src/DataExchange/TKDESTL/DESTL/DESTL_ConfigurationNode.cxx @@ -134,6 +134,13 @@ bool DESTL_ConfigurationNode::IsExportSupported() const //================================================================================================= +bool DESTL_ConfigurationNode::IsStreamSupported() const +{ + return true; +} + +//================================================================================================= + TCollection_AsciiString DESTL_ConfigurationNode::GetFormat() const { return TCollection_AsciiString("STL"); diff --git a/src/DataExchange/TKDESTL/DESTL/DESTL_ConfigurationNode.hxx b/src/DataExchange/TKDESTL/DESTL/DESTL_ConfigurationNode.hxx index c252dc031e..191ffd4301 100644 --- a/src/DataExchange/TKDESTL/DESTL/DESTL_ConfigurationNode.hxx +++ b/src/DataExchange/TKDESTL/DESTL/DESTL_ConfigurationNode.hxx @@ -64,6 +64,10 @@ public: //! @return true if export is supported Standard_EXPORT virtual bool IsExportSupported() const Standard_OVERRIDE; + //! Checks for stream support. + //! @return Standard_True if streams are supported + Standard_EXPORT virtual bool IsStreamSupported() const Standard_OVERRIDE; + //! Gets CAD format name of associated provider //! @return provider CAD format Standard_EXPORT virtual TCollection_AsciiString GetFormat() const Standard_OVERRIDE; diff --git a/src/DataExchange/TKDESTL/DESTL/DESTL_Provider.cxx b/src/DataExchange/TKDESTL/DESTL/DESTL_Provider.cxx index a99bf85905..bbecddd9d7 100644 --- a/src/DataExchange/TKDESTL/DESTL/DESTL_Provider.cxx +++ b/src/DataExchange/TKDESTL/DESTL/DESTL_Provider.cxx @@ -14,14 +14,21 @@ #include #include +#include #include #include +#include +#include #include +#include #include +#include #include +#include #include #include #include +#include IMPLEMENT_STANDARD_RTTIEXT(DESTL_Provider, DE_Provider) @@ -64,10 +71,9 @@ bool DESTL_Provider::Read(const TCollection_AsciiString& thePath, const Handle(TDocStd_Document)& theDocument, const Message_ProgressRange& theProgress) { - if (theDocument.IsNull()) + TCollection_AsciiString aContext = TCollection_AsciiString("reading the file ") + thePath; + if (!DE_ValidationUtils::ValidateDocument(theDocument, aContext)) { - Message::SendFail() << "Error in the DESTL_Provider during reading the file " << thePath - << "\t: theDocument shouldn't be null"; return false; } TopoDS_Shape aShape; @@ -86,25 +92,35 @@ bool DESTL_Provider::Write(const TCollection_AsciiString& thePath, const Handle(TDocStd_Document)& theDocument, const Message_ProgressRange& theProgress) { - TopoDS_Shape aShape; + TCollection_AsciiString aContext = TCollection_AsciiString("writing the file ") + thePath; + if (!DE_ValidationUtils::ValidateDocument(theDocument, aContext)) + { + return false; + } + + // Extract shape from document TDF_LabelSequence aLabels; Handle(XCAFDoc_ShapeTool) aSTool = XCAFDoc_DocumentTool::ShapeTool(theDocument->Main()); aSTool->GetFreeShapes(aLabels); + if (aLabels.Length() <= 0) { Message::SendFail() << "Error in the DESTL_Provider during writing the file " << thePath - << "\t: Document contain no shapes"; + << ": Document contain no shapes"; return false; } - Handle(DESTL_ConfigurationNode) aNode = Handle(DESTL_ConfigurationNode)::DownCast(GetNode()); - if (aNode->GlobalParameters.LengthUnit != 1.0) + if (!DE_ValidationUtils::ValidateConfigurationNode(GetNode(), + STANDARD_TYPE(DESTL_ConfigurationNode), + aContext)) { - Message::SendWarning() - << "Warning in the DESTL_Provider during writing the file " << thePath - << "\t: Target Units for writing were changed, but current format doesn't support scaling"; + return false; } + Handle(DESTL_ConfigurationNode) aNode = Handle(DESTL_ConfigurationNode)::DownCast(GetNode()); + DE_ValidationUtils::WarnLengthUnitNotSupported(aNode->GlobalParameters.LengthUnit, aContext); + + TopoDS_Shape aShape; if (aLabels.Length() == 1) { aShape = aSTool->GetShape(aLabels.Value(1)); @@ -121,6 +137,7 @@ bool DESTL_Provider::Write(const TCollection_AsciiString& thePath, } aShape = aComp; } + return Write(thePath, aShape, theProgress); } @@ -154,14 +171,18 @@ bool DESTL_Provider::Read(const TCollection_AsciiString& thePath, { Message::SendWarning() << "OCCT Stl reader does not support model scaling according to custom length unit"; - if (!GetNode()->IsKind(STANDARD_TYPE(DESTL_ConfigurationNode))) + + TCollection_AsciiString aContext = TCollection_AsciiString("reading the file ") + thePath; + if (!DE_ValidationUtils::ValidateConfigurationNode(GetNode(), + STANDARD_TYPE(DESTL_ConfigurationNode), + aContext)) { - Message::SendFail() << "Error in the DESTL_Provider during reading the file " << thePath - << "\t: Incorrect or empty Configuration Node"; - return true; + return false; } + Handle(DESTL_ConfigurationNode) aNode = Handle(DESTL_ConfigurationNode)::DownCast(GetNode()); double aMergeAngle = aNode->InternalParameters.ReadMergeAngle * M_PI / 180.0; + if (aMergeAngle != M_PI_2) { if (aMergeAngle < 0.0 || aMergeAngle > M_PI_2) @@ -171,6 +192,7 @@ bool DESTL_Provider::Read(const TCollection_AsciiString& thePath, return false; } } + if (!aNode->InternalParameters.ReadBRep) { Handle(Poly_Triangulation) aTriangulation = @@ -184,7 +206,9 @@ bool DESTL_Provider::Read(const TCollection_AsciiString& thePath, } else { - Standard_DISABLE_DEPRECATION_WARNINGS if (!StlAPI::Read(theShape, thePath.ToCString())) + Standard_DISABLE_DEPRECATION_WARNINGS + + if (!StlAPI::Read(theShape, thePath.ToCString())) { Message::SendFail() << "Error in the DESTL_Provider during reading the file " << thePath; return false; @@ -202,25 +226,23 @@ bool DESTL_Provider::Write(const TCollection_AsciiString& thePath, { Message::SendWarning() << "OCCT Stl writer does not support model scaling according to custom length unit"; - if (GetNode().IsNull() || !GetNode()->IsKind(STANDARD_TYPE(DESTL_ConfigurationNode))) + + TCollection_AsciiString aContext = TCollection_AsciiString("writing the file ") + thePath; + if (!DE_ValidationUtils::ValidateConfigurationNode(GetNode(), + STANDARD_TYPE(DESTL_ConfigurationNode), + aContext)) { - Message::SendFail() << "Error in the DESTL_Provider during reading the file " << thePath - << "\t: Incorrect or empty Configuration Node"; return false; } + Handle(DESTL_ConfigurationNode) aNode = Handle(DESTL_ConfigurationNode)::DownCast(GetNode()); - if (aNode->GlobalParameters.LengthUnit != 1.0) - { - Message::SendWarning() - << "Warning in the DESTL_Provider during writing the file " << thePath - << "\t: Target Units for writing were changed, but current format doesn't support scaling"; - } + DE_ValidationUtils::WarnLengthUnitNotSupported(aNode->GlobalParameters.LengthUnit, aContext); StlAPI_Writer aWriter; aWriter.ASCIIMode() = aNode->InternalParameters.WriteAscii; if (!aWriter.Write(theShape, thePath.ToCString(), theProgress)) { - Message::SendFail() << "Error in the DESTL_Provider during reading the file " << thePath + Message::SendFail() << "Error in the DESTL_Provider during writing the file " << thePath << "\t: Mesh writing has been failed"; return false; } @@ -229,6 +251,271 @@ bool DESTL_Provider::Write(const TCollection_AsciiString& thePath, //================================================================================================= +Standard_Boolean DESTL_Provider::Read(ReadStreamList& theStreams, + const Handle(TDocStd_Document)& theDocument, + Handle(XSControl_WorkSession)& theWS, + const Message_ProgressRange& theProgress) +{ + (void)theWS; + return Read(theStreams, theDocument, theProgress); +} + +//================================================================================================= + +Standard_Boolean DESTL_Provider::Write(WriteStreamList& theStreams, + const Handle(TDocStd_Document)& theDocument, + Handle(XSControl_WorkSession)& theWS, + const Message_ProgressRange& theProgress) +{ + (void)theWS; + return Write(theStreams, theDocument, theProgress); +} + +//================================================================================================= + +Standard_Boolean DESTL_Provider::Read(ReadStreamList& theStreams, + TopoDS_Shape& theShape, + Handle(XSControl_WorkSession)& theWS, + const Message_ProgressRange& theProgress) +{ + (void)theWS; + return Read(theStreams, theShape, theProgress); +} + +//================================================================================================= + +Standard_Boolean DESTL_Provider::Write(WriteStreamList& theStreams, + const TopoDS_Shape& theShape, + Handle(XSControl_WorkSession)& theWS, + const Message_ProgressRange& theProgress) +{ + (void)theWS; + return Write(theStreams, theShape, theProgress); +} + +//================================================================================================= + +Standard_Boolean DESTL_Provider::Read(ReadStreamList& theStreams, + const Handle(TDocStd_Document)& theDocument, + const Message_ProgressRange& theProgress) +{ + TCollection_AsciiString aContext = "reading stream"; + if (!DE_ValidationUtils::ValidateReadStreamList(theStreams, aContext)) + { + return Standard_False; + } + + const TCollection_AsciiString& aFirstKey = theStreams.First().Path; + TCollection_AsciiString aFullContext = aContext + " " + aFirstKey; + if (!DE_ValidationUtils::ValidateDocument(theDocument, aFullContext)) + { + return Standard_False; + } + + TopoDS_Shape aShape; + if (!Read(theStreams, aShape, theProgress)) + { + return Standard_False; + } + + Handle(XCAFDoc_ShapeTool) aShapeTool = XCAFDoc_DocumentTool::ShapeTool(theDocument->Main()); + aShapeTool->AddShape(aShape); + return Standard_True; +} + +//================================================================================================= + +Standard_Boolean DESTL_Provider::Write(WriteStreamList& theStreams, + const Handle(TDocStd_Document)& theDocument, + const Message_ProgressRange& theProgress) +{ + TCollection_AsciiString aContext = "writing stream"; + if (!DE_ValidationUtils::ValidateWriteStreamList(theStreams, aContext)) + { + return Standard_False; + } + + const TCollection_AsciiString& aFirstKey = theStreams.First().Path; + TCollection_AsciiString aFullContext = aContext + " " + aFirstKey; + if (!DE_ValidationUtils::ValidateDocument(theDocument, aFullContext)) + { + return Standard_False; + } + + // Extract shape from document + TDF_LabelSequence aLabels; + Handle(XCAFDoc_ShapeTool) aSTool = XCAFDoc_DocumentTool::ShapeTool(theDocument->Main()); + aSTool->GetFreeShapes(aLabels); + + if (aLabels.Length() <= 0) + { + Message::SendFail() << "Error in the DESTL_Provider during writing stream " << aFirstKey + << ": Document contain no shapes"; + return Standard_False; + } + + if (!DE_ValidationUtils::ValidateConfigurationNode(GetNode(), + STANDARD_TYPE(DESTL_ConfigurationNode), + aFullContext)) + { + return Standard_False; + } + + Handle(DESTL_ConfigurationNode) aNode = Handle(DESTL_ConfigurationNode)::DownCast(GetNode()); + TCollection_AsciiString aLengthContext = TCollection_AsciiString("writing stream ") + aFirstKey; + DE_ValidationUtils::WarnLengthUnitNotSupported(aNode->GlobalParameters.LengthUnit, + aLengthContext); + + TopoDS_Shape aShape; + if (aLabels.Length() == 1) + { + aShape = aSTool->GetShape(aLabels.Value(1)); + } + else + { + TopoDS_Compound aComp; + BRep_Builder aBuilder; + aBuilder.MakeCompound(aComp); + for (Standard_Integer anIndex = 1; anIndex <= aLabels.Length(); anIndex++) + { + TopoDS_Shape aS = aSTool->GetShape(aLabels.Value(anIndex)); + aBuilder.Add(aComp, aS); + } + aShape = aComp; + } + + return Write(theStreams, aShape, theProgress); +} + +//================================================================================================= + +Standard_Boolean DESTL_Provider::Read(ReadStreamList& theStreams, + TopoDS_Shape& theShape, + const Message_ProgressRange& theProgress) +{ + // Validate stream map + if (theStreams.IsEmpty()) + { + Message::SendFail() << "Error: DESTL_Provider stream map is empty"; + return Standard_False; + } + if (theStreams.Size() > 1) + { + Message::SendWarning() << "Warning: DESTL_Provider received " << theStreams.Size() + << " streams for reading, using only the first one"; + } + + const TCollection_AsciiString& aFirstKey = theStreams.First().Path; + Standard_IStream& aStream = theStreams.First().Stream; + + Message::SendWarning() + << "OCCT Stl reader does not support model scaling according to custom length unit"; + + TCollection_AsciiString aNodeContext = TCollection_AsciiString("reading stream ") + aFirstKey; + if (!DE_ValidationUtils::ValidateConfigurationNode(GetNode(), + STANDARD_TYPE(DESTL_ConfigurationNode), + aNodeContext)) + { + return Standard_False; + } + + Handle(DESTL_ConfigurationNode) aNode = Handle(DESTL_ConfigurationNode)::DownCast(GetNode()); + double aMergeAngle = aNode->InternalParameters.ReadMergeAngle * M_PI / 180.0; + + if (aMergeAngle != M_PI_2) + { + if (aMergeAngle < 0.0 || aMergeAngle > M_PI_2) + { + Message::SendFail() << "Error in the DESTL_Provider during reading stream " << aFirstKey + << ": The merge angle is out of the valid range"; + return Standard_False; + } + } + + if (!aNode->InternalParameters.ReadBRep) + { + Handle(Poly_Triangulation) aTriangulation = + RWStl::ReadStream(aStream, aMergeAngle, theProgress); + if (aTriangulation.IsNull()) + { + Message::SendFail() << "Error in the DESTL_Provider during reading stream " << aFirstKey + << ": Failed to create triangulation"; + return Standard_False; + } + + TopoDS_Face aFace; + BRep_Builder aB; + aB.MakeFace(aFace); + aB.UpdateFace(aFace, aTriangulation); + theShape = aFace; + } + else + { + Standard_DISABLE_DEPRECATION_WARNINGS + + StlAPI_Reader aReader; + if (!aReader.Read(theShape, aStream)) + { + Message::SendFail() << "Error in the DESTL_Provider during reading stream " << aFirstKey; + return Standard_False; + } + Standard_ENABLE_DEPRECATION_WARNINGS + } + + return Standard_True; +} + +//================================================================================================= + +Standard_Boolean DESTL_Provider::Write(WriteStreamList& theStreams, + const TopoDS_Shape& theShape, + const Message_ProgressRange& theProgress) +{ + // Validate stream map + if (theStreams.IsEmpty()) + { + Message::SendFail() << "Error: DESTL_Provider stream map is empty"; + return Standard_False; + } + if (theStreams.Size() > 1) + { + Message::SendWarning() << "Warning: DESTL_Provider received " << theStreams.Size() + << " streams for writing, using only the first one"; + } + + const TCollection_AsciiString& aFirstKey = theStreams.First().Path; + Standard_OStream& aStream = theStreams.First().Stream; + + Message::SendWarning() + << "OCCT Stl writer does not support model scaling according to custom length unit"; + + TCollection_AsciiString aNodeContext = TCollection_AsciiString("writing stream ") + aFirstKey; + if (!DE_ValidationUtils::ValidateConfigurationNode(GetNode(), + STANDARD_TYPE(DESTL_ConfigurationNode), + aNodeContext)) + { + return Standard_False; + } + + Handle(DESTL_ConfigurationNode) aNode = Handle(DESTL_ConfigurationNode)::DownCast(GetNode()); + TCollection_AsciiString aLengthContext = TCollection_AsciiString("writing stream ") + aFirstKey; + DE_ValidationUtils::WarnLengthUnitNotSupported(aNode->GlobalParameters.LengthUnit, + aLengthContext); + + StlAPI_Writer aWriter; + aWriter.ASCIIMode() = aNode->InternalParameters.WriteAscii; + if (!aWriter.Write(theShape, aStream, theProgress)) + { + Message::SendFail() << "Error in the DESTL_Provider during writing stream " << aFirstKey + << ": Mesh writing has been failed"; + return Standard_False; + } + + return Standard_True; +} + +//================================================================================================= + TCollection_AsciiString DESTL_Provider::GetFormat() const { return TCollection_AsciiString("STL"); diff --git a/src/DataExchange/TKDESTL/DESTL/DESTL_Provider.hxx b/src/DataExchange/TKDESTL/DESTL/DESTL_Provider.hxx index 739c8323cd..009d420d71 100644 --- a/src/DataExchange/TKDESTL/DESTL/DESTL_Provider.hxx +++ b/src/DataExchange/TKDESTL/DESTL/DESTL_Provider.hxx @@ -108,6 +108,54 @@ public: Handle(XSControl_WorkSession)& theWS, const Message_ProgressRange& theProgress = Message_ProgressRange()) Standard_OVERRIDE; + //! Reads streams according to internal configuration + //! @param[in] theStreams streams to read from + //! @param[out] theDocument document to save result + //! @param[in] theWS current work session + //! @param[in] theProgress progress indicator + //! @return true if Read operation has ended correctly + Standard_EXPORT virtual Standard_Boolean Read( + ReadStreamList& theStreams, + const Handle(TDocStd_Document)& theDocument, + Handle(XSControl_WorkSession)& theWS, + const Message_ProgressRange& theProgress = Message_ProgressRange()) Standard_OVERRIDE; + + //! Writes streams according to internal configuration + //! @param[in] theStreams streams to write to + //! @param[out] theDocument document to export + //! @param[in] theWS current work session + //! @param[in] theProgress progress indicator + //! @return true if Write operation has ended correctly + Standard_EXPORT virtual Standard_Boolean Write( + WriteStreamList& theStreams, + const Handle(TDocStd_Document)& theDocument, + Handle(XSControl_WorkSession)& theWS, + const Message_ProgressRange& theProgress = Message_ProgressRange()) Standard_OVERRIDE; + + //! Reads streams according to internal configuration + //! @param[in] theStreams streams to read from + //! @param[out] theShape shape to save result + //! @param[in] theWS current work session + //! @param[in] theProgress progress indicator + //! @return true if Read operation has ended correctly + Standard_EXPORT virtual Standard_Boolean Read( + ReadStreamList& theStreams, + TopoDS_Shape& theShape, + Handle(XSControl_WorkSession)& theWS, + const Message_ProgressRange& theProgress = Message_ProgressRange()) Standard_OVERRIDE; + + //! Writes streams according to internal configuration + //! @param[in] theStreams streams to write to + //! @param[out] theShape shape to export + //! @param[in] theWS current work session + //! @param[in] theProgress progress indicator + //! @return true if Write operation has ended correctly + Standard_EXPORT virtual Standard_Boolean Write( + WriteStreamList& theStreams, + const TopoDS_Shape& theShape, + Handle(XSControl_WorkSession)& theWS, + const Message_ProgressRange& theProgress = Message_ProgressRange()) Standard_OVERRIDE; + //! Reads a CAD file, according internal configuration //! @param[in] thePath path to the import CAD file //! @param[out] theShape shape to save result @@ -128,6 +176,46 @@ public: const TopoDS_Shape& theShape, const Message_ProgressRange& theProgress = Message_ProgressRange()) Standard_OVERRIDE; + //! Reads streams according to internal configuration + //! @param[in] theStreams streams to read from + //! @param[out] theDocument document to save result + //! @param[in] theProgress progress indicator + //! @return true if Read operation has ended correctly + Standard_EXPORT virtual Standard_Boolean Read( + ReadStreamList& theStreams, + const Handle(TDocStd_Document)& theDocument, + const Message_ProgressRange& theProgress = Message_ProgressRange()) Standard_OVERRIDE; + + //! Writes streams according to internal configuration + //! @param[in] theStreams streams to write to + //! @param[out] theDocument document to export + //! @param[in] theProgress progress indicator + //! @return true if Write operation has ended correctly + Standard_EXPORT virtual Standard_Boolean Write( + WriteStreamList& theStreams, + const Handle(TDocStd_Document)& theDocument, + const Message_ProgressRange& theProgress = Message_ProgressRange()) Standard_OVERRIDE; + + //! Reads streams according to internal configuration + //! @param[in] theStreams streams to read from + //! @param[out] theShape shape to save result + //! @param[in] theProgress progress indicator + //! @return true if Read operation has ended correctly + Standard_EXPORT virtual Standard_Boolean Read( + ReadStreamList& theStreams, + TopoDS_Shape& theShape, + const Message_ProgressRange& theProgress = Message_ProgressRange()) Standard_OVERRIDE; + + //! Writes streams according to internal configuration + //! @param[in] theStreams streams to write to + //! @param[out] theShape shape to export + //! @param[in] theProgress progress indicator + //! @return true if Write operation has ended correctly + Standard_EXPORT virtual Standard_Boolean Write( + WriteStreamList& theStreams, + const TopoDS_Shape& theShape, + const Message_ProgressRange& theProgress = Message_ProgressRange()) Standard_OVERRIDE; + public: //! Gets CAD format name of associated provider //! @return provider CAD format diff --git a/src/DataExchange/TKDESTL/GTests/DESTL_Provider_Test.cxx b/src/DataExchange/TKDESTL/GTests/DESTL_Provider_Test.cxx new file mode 100644 index 0000000000..7a0422530c --- /dev/null +++ b/src/DataExchange/TKDESTL/GTests/DESTL_Provider_Test.cxx @@ -0,0 +1,559 @@ +// Copyright (c) 2025 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +class DESTL_ProviderTest : public ::testing::Test +{ +protected: + void SetUp() override + { + // Initialize provider with default configuration + Handle(DESTL_ConfigurationNode) aNode = new DESTL_ConfigurationNode(); + myProvider = new DESTL_Provider(aNode); + + // Create triangulated shape for testing (STL format requires triangulated data) + + myTriangularFace = CreateTriangulatedFace(); + + // Create test document + Handle(TDocStd_Application) anApp = new TDocStd_Application(); + anApp->NewDocument("BinXCAF", myDocument); + } + + void TearDown() override + { + myProvider.Nullify(); + myDocument.Nullify(); + } + + // Helper method to count shape elements + Standard_Integer CountShapeElements(const TopoDS_Shape& theShape, TopAbs_ShapeEnum theType) + { + Standard_Integer aCount = 0; + for (TopExp_Explorer anExplorer(theShape, theType); anExplorer.More(); anExplorer.Next()) + { + aCount++; + } + return aCount; + } + + // Helper method to create a triangulated face with mesh data (suitable for STL) + TopoDS_Shape CreateTriangulatedFace() + { + // Create vertices for triangulation + TColgp_Array1OfPnt aNodes(1, 4); + aNodes.SetValue(1, gp_Pnt(0.0, 0.0, 0.0)); // Bottom-left + aNodes.SetValue(2, gp_Pnt(10.0, 0.0, 0.0)); // Bottom-right + aNodes.SetValue(3, gp_Pnt(10.0, 10.0, 0.0)); // Top-right + aNodes.SetValue(4, gp_Pnt(0.0, 10.0, 0.0)); // Top-left + + // Create triangles (two triangles forming a quad) + Poly_Array1OfTriangle aTriangles(1, 2); + aTriangles.SetValue(1, Poly_Triangle(1, 2, 3)); // First triangle + aTriangles.SetValue(2, Poly_Triangle(1, 3, 4)); // Second triangle + + // Create triangulation + Handle(Poly_Triangulation) aTriangulation = new Poly_Triangulation(aNodes, aTriangles); + + // Create a simple planar face + gp_Pln aPlane(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(0, 0, 1)); + BRepBuilderAPI_MakeFace aFaceBuilder(aPlane, 0.0, 10.0, 0.0, 10.0); + + if (!aFaceBuilder.IsDone()) + { + return TopoDS_Shape(); + } + + TopoDS_Face aFace = aFaceBuilder.Face(); + + // Attach triangulation to the face + BRep_Builder aBuilder; + aBuilder.UpdateFace(aFace, aTriangulation); + + return aFace; + } + +protected: + Handle(DESTL_Provider) myProvider; + TopoDS_Shape myTriangularFace; + Handle(TDocStd_Document) myDocument; +}; + +// Test basic provider creation and format/vendor information +TEST_F(DESTL_ProviderTest, BasicProperties) +{ + EXPECT_STREQ("STL", myProvider->GetFormat().ToCString()); + EXPECT_STREQ("OCC", myProvider->GetVendor().ToCString()); + EXPECT_FALSE(myProvider->GetNode().IsNull()); +} + +// Test stream-based shape write and read operations +TEST_F(DESTL_ProviderTest, StreamShapeWriteRead) +{ + std::ostringstream anOStream; + DE_Provider::WriteStreamList aWriteStreams; + aWriteStreams.Append(DE_Provider::WriteStreamNode("test.stl", anOStream)); + + // Write triangulated face to stream (STL works with mesh data) + EXPECT_TRUE(myProvider->Write(aWriteStreams, myTriangularFace)); + + std::string aStlContent = anOStream.str(); + EXPECT_FALSE(aStlContent.empty()); + EXPECT_TRUE(aStlContent.find("solid") != std::string::npos + || aStlContent.find("facet") != std::string::npos); + + if (!aStlContent.empty()) + { + // Read back from stream + std::istringstream anIStream(aStlContent); + DE_Provider::ReadStreamList aReadStreams; + aReadStreams.Append(DE_Provider::ReadStreamNode("test.stl", anIStream)); + + TopoDS_Shape aReadShape; + EXPECT_TRUE(myProvider->Read(aReadStreams, aReadShape)); + EXPECT_FALSE(aReadShape.IsNull()); + + if (!aReadShape.IsNull()) + { + // STL should produce faces with triangulation + Standard_Integer aReadFaces = CountShapeElements(aReadShape, TopAbs_FACE); + EXPECT_GT(aReadFaces, 0); // Should have faces + } + } +} + +// Test stream-based document write and read operations +TEST_F(DESTL_ProviderTest, StreamDocumentWriteRead) +{ + // Add triangulated shape to document + Handle(XCAFDoc_ShapeTool) aShapeTool = XCAFDoc_DocumentTool::ShapeTool(myDocument->Main()); + TDF_Label aShapeLabel = aShapeTool->AddShape(myTriangularFace); + EXPECT_FALSE(aShapeLabel.IsNull()); + + std::ostringstream anOStream; + DE_Provider::WriteStreamList aWriteStreams; + aWriteStreams.Append(DE_Provider::WriteStreamNode("document.stl", anOStream)); + + // Write document to stream + EXPECT_TRUE(myProvider->Write(aWriteStreams, myDocument)); + + std::string aStlContent = anOStream.str(); + EXPECT_FALSE(aStlContent.empty()); + + if (!aStlContent.empty()) + { + // Create new document for reading + Handle(TDocStd_Application) anApp = new TDocStd_Application(); + Handle(TDocStd_Document) aNewDocument; + anApp->NewDocument("BinXCAF", aNewDocument); + + // Read back from stream + std::istringstream anIStream(aStlContent); + DE_Provider::ReadStreamList aReadStreams; + aReadStreams.Append(DE_Provider::ReadStreamNode("document.stl", anIStream)); + + EXPECT_TRUE(myProvider->Read(aReadStreams, aNewDocument)); + + // Validate document content + Handle(XCAFDoc_ShapeTool) aNewShapeTool = XCAFDoc_DocumentTool::ShapeTool(aNewDocument->Main()); + TDF_LabelSequence aLabels; + aNewShapeTool->GetShapes(aLabels); + EXPECT_GT(aLabels.Length(), 0); // Should have at least one shape in document + } +} + +// Test DE_Wrapper integration for STL operations +TEST_F(DESTL_ProviderTest, DE_WrapperIntegration) +{ + // Initialize DE_Wrapper and bind STL provider + DE_Wrapper aWrapper; + Handle(DESTL_ConfigurationNode) aNode = new DESTL_ConfigurationNode(); + + // Bind the configured node to wrapper + EXPECT_TRUE(aWrapper.Bind(aNode)); + + // Test write with DE_Wrapper using triangular face + std::ostringstream anOStream; + DE_Provider::WriteStreamList aWriteStreams; + aWriteStreams.Append(DE_Provider::WriteStreamNode("test.stl", anOStream)); + + EXPECT_TRUE(aWrapper.Write(aWriteStreams, myTriangularFace)); + + std::string aStlContent = anOStream.str(); + EXPECT_FALSE(aStlContent.empty()); + + if (!aStlContent.empty()) + { + // Test DE_Wrapper stream operations + std::istringstream anIStream(aStlContent); + DE_Provider::ReadStreamList aReadStreams; + aReadStreams.Append(DE_Provider::ReadStreamNode("test.stl", anIStream)); + + TopoDS_Shape aReadShape; + bool aWrapperResult = aWrapper.Read(aReadStreams, aReadShape); + + // Test direct provider with same content for comparison + std::istringstream anIStream2(aStlContent); + DE_Provider::ReadStreamList aReadStreams2; + aReadStreams2.Append(DE_Provider::ReadStreamNode("test.stl", anIStream2)); + + Handle(DESTL_Provider) aDirectProvider = new DESTL_Provider(aNode); + TopoDS_Shape aDirectShape; + bool aDirectResult = aDirectProvider->Read(aReadStreams2, aDirectShape); + + // REQUIREMENT: DE_Wrapper must work exactly the same as direct provider + EXPECT_EQ(aWrapperResult, aDirectResult); + EXPECT_EQ(aReadShape.IsNull(), aDirectShape.IsNull()); + + if (aDirectResult && !aDirectShape.IsNull()) + { + Standard_Integer aFaces = CountShapeElements(aDirectShape, TopAbs_FACE); + EXPECT_GT(aFaces, 0); + } + } +} + +// Test error conditions and edge cases with null document validation +TEST_F(DESTL_ProviderTest, ErrorHandling) +{ + // Test with empty streams + DE_Provider::WriteStreamList anEmptyWriteStreams; + EXPECT_FALSE(myProvider->Write(anEmptyWriteStreams, myTriangularFace)); + + DE_Provider::ReadStreamList anEmptyReadStreams; + TopoDS_Shape aShape; + EXPECT_FALSE(myProvider->Read(anEmptyReadStreams, aShape)); + + // Test with null shape + std::ostringstream anOStream; + DE_Provider::WriteStreamList aWriteStreams; + aWriteStreams.Append(DE_Provider::WriteStreamNode("null_test.stl", anOStream)); + TopoDS_Shape aNullShape; + + // Writing null shape should succeed but produce minimal content + myProvider->Write(aWriteStreams, aNullShape); + std::string aContent = anOStream.str(); + // STL may produce empty content for null shape + + // Test reading invalid STL content + std::string anInvalidContent = "This is not valid STL content"; + std::istringstream anInvalidStream(anInvalidContent); + DE_Provider::ReadStreamList anInvalidReadStreams; + anInvalidReadStreams.Append(DE_Provider::ReadStreamNode("invalid.stl", anInvalidStream)); + + TopoDS_Shape anInvalidShape; + EXPECT_FALSE(myProvider->Read(anInvalidReadStreams, anInvalidShape)); + + // Test with null document + Handle(TDocStd_Document) aNullDoc; + EXPECT_FALSE(myProvider->Write(aWriteStreams, aNullDoc)); + EXPECT_FALSE(myProvider->Read(anEmptyReadStreams, aNullDoc)); +} + +// Test DESTL configuration modes +TEST_F(DESTL_ProviderTest, ConfigurationModes) +{ + Handle(DESTL_ConfigurationNode) aNode = + Handle(DESTL_ConfigurationNode)::DownCast(myProvider->GetNode()); + + // Test ASCII mode configuration + aNode->InternalParameters.WriteAscii = Standard_True; + EXPECT_TRUE(aNode->InternalParameters.WriteAscii); + + // Test Binary mode configuration + aNode->InternalParameters.WriteAscii = Standard_False; + EXPECT_FALSE(aNode->InternalParameters.WriteAscii); + + // Test merge angle configuration + aNode->InternalParameters.ReadMergeAngle = 45.0; + EXPECT_DOUBLE_EQ(45.0, aNode->InternalParameters.ReadMergeAngle); + + // Test that provider format and vendor are correct + EXPECT_STREQ("STL", myProvider->GetFormat().ToCString()); + EXPECT_STREQ("OCC", myProvider->GetVendor().ToCString()); +} + +// Test ASCII vs Binary mode configurations +TEST_F(DESTL_ProviderTest, AsciiVsBinaryModes) +{ + Handle(DESTL_ConfigurationNode) aNode = + Handle(DESTL_ConfigurationNode)::DownCast(myProvider->GetNode()); + + // Test ASCII mode + aNode->InternalParameters.WriteAscii = Standard_True; + + std::ostringstream anAsciiStream; + DE_Provider::WriteStreamList anAsciiWriteStreams; + anAsciiWriteStreams.Append(DE_Provider::WriteStreamNode("ascii_test.stl", anAsciiStream)); + + EXPECT_TRUE(myProvider->Write(anAsciiWriteStreams, myTriangularFace)); + std::string anAsciiContent = anAsciiStream.str(); + EXPECT_FALSE(anAsciiContent.empty()); + EXPECT_TRUE(anAsciiContent.find("solid") != std::string::npos); + + // Test Binary mode + aNode->InternalParameters.WriteAscii = Standard_False; + + std::ostringstream aBinaryStream; + DE_Provider::WriteStreamList aBinaryWriteStreams; + aBinaryWriteStreams.Append(DE_Provider::WriteStreamNode("binary_test.stl", aBinaryStream)); + + EXPECT_TRUE(myProvider->Write(aBinaryWriteStreams, myTriangularFace)); + std::string aBinaryContent = aBinaryStream.str(); + EXPECT_FALSE(aBinaryContent.empty()); + + // Binary and ASCII content should be different + EXPECT_NE(anAsciiContent, aBinaryContent); +} + +// Test multiple shapes in single document +TEST_F(DESTL_ProviderTest, MultipleShapesInDocument) +{ + // Add triangulated face to document multiple times (to create multiple shapes) + Handle(XCAFDoc_ShapeTool) aShapeTool = XCAFDoc_DocumentTool::ShapeTool(myDocument->Main()); + TDF_Label aFaceLabel1 = aShapeTool->AddShape(myTriangularFace); + TDF_Label aFaceLabel2 = aShapeTool->AddShape(myTriangularFace); // Add same face again + + EXPECT_FALSE(aFaceLabel1.IsNull()); + EXPECT_FALSE(aFaceLabel2.IsNull()); + + std::ostringstream anOStream; + DE_Provider::WriteStreamList aWriteStreams; + aWriteStreams.Append(DE_Provider::WriteStreamNode("multi_shapes.stl", anOStream)); + + // Write document with multiple shapes + EXPECT_TRUE(myProvider->Write(aWriteStreams, myDocument)); + + std::string aStlContent = anOStream.str(); + EXPECT_FALSE(aStlContent.empty()); + + // Read back into new document + Handle(TDocStd_Application) anApp = new TDocStd_Application(); + Handle(TDocStd_Document) aNewDocument; + anApp->NewDocument("BinXCAF", aNewDocument); + + std::istringstream anIStream(aStlContent); + DE_Provider::ReadStreamList aReadStreams; + aReadStreams.Append(DE_Provider::ReadStreamNode("multi_shapes.stl", anIStream)); + + EXPECT_TRUE(myProvider->Read(aReadStreams, aNewDocument)); + + // Validate document content + Handle(XCAFDoc_ShapeTool) aNewShapeTool = XCAFDoc_DocumentTool::ShapeTool(aNewDocument->Main()); + TDF_LabelSequence aLabels; + aNewShapeTool->GetShapes(aLabels); + EXPECT_GT(aLabels.Length(), 0); +} + +// Test triangulated face handling (suitable for STL) +TEST_F(DESTL_ProviderTest, TriangulatedFaceHandling) +{ + // Use our triangulated face which already has mesh data + std::ostringstream anOStream; + DE_Provider::WriteStreamList aWriteStreams; + aWriteStreams.Append(DE_Provider::WriteStreamNode("triangulated_face.stl", anOStream)); + + EXPECT_TRUE(myProvider->Write(aWriteStreams, myTriangularFace)); + + std::string aStlContent = anOStream.str(); + EXPECT_FALSE(aStlContent.empty()); + EXPECT_GT(aStlContent.size(), 100); // Should have meaningful content + + // Read back + std::istringstream anIStream(aStlContent); + DE_Provider::ReadStreamList aReadStreams; + aReadStreams.Append(DE_Provider::ReadStreamNode("triangulated_face.stl", anIStream)); + + TopoDS_Shape aReadShape; + EXPECT_TRUE(myProvider->Read(aReadStreams, aReadShape)); + EXPECT_FALSE(aReadShape.IsNull()); +} + +// Test DE_Wrapper with different file extensions +TEST_F(DESTL_ProviderTest, DE_WrapperFileExtensions) +{ + DE_Wrapper aWrapper; + Handle(DESTL_ConfigurationNode) aNode = new DESTL_ConfigurationNode(); + EXPECT_TRUE(aWrapper.Bind(aNode)); + + // Test different STL extensions + std::vector aExtensions = {"test.stl", "test.STL", "mesh.stl"}; + + for (const auto& anExt : aExtensions) + { + std::ostringstream anOStream; + DE_Provider::WriteStreamList aWriteStreams; + aWriteStreams.Append(DE_Provider::WriteStreamNode(anExt.c_str(), anOStream)); + + EXPECT_TRUE(aWrapper.Write(aWriteStreams, myTriangularFace)) + << "Failed to write with extension: " << anExt; + + std::string aContent = anOStream.str(); + EXPECT_FALSE(aContent.empty()) << "Empty content for extension: " << anExt; + + // Test read back + std::istringstream anIStream(aContent); + DE_Provider::ReadStreamList aReadStreams; + aReadStreams.Append(DE_Provider::ReadStreamNode(anExt.c_str(), anIStream)); + + TopoDS_Shape aReadShape; + EXPECT_TRUE(aWrapper.Read(aReadStreams, aReadShape)) + << "Failed to read with extension: " << anExt; + EXPECT_FALSE(aReadShape.IsNull()) << "Null shape read with extension: " << anExt; + } +} + +// Test stream operations with empty and invalid data +TEST_F(DESTL_ProviderTest, StreamErrorConditions) +{ + // Test empty stream list + DE_Provider::WriteStreamList anEmptyWriteStreams; + EXPECT_FALSE(myProvider->Write(anEmptyWriteStreams, myTriangularFace)); + + DE_Provider::ReadStreamList anEmptyReadStreams; + TopoDS_Shape aShape; + EXPECT_FALSE(myProvider->Read(anEmptyReadStreams, aShape)); + + // Test corrupted STL data + std::string aCorruptedData = "This is not STL data at all"; + std::istringstream aCorruptedStream(aCorruptedData); + DE_Provider::ReadStreamList aCorruptedReadStreams; + aCorruptedReadStreams.Append(DE_Provider::ReadStreamNode("corrupted.stl", aCorruptedStream)); + + TopoDS_Shape aCorruptedShape; + EXPECT_FALSE(myProvider->Read(aCorruptedReadStreams, aCorruptedShape)); + + // Test partial STL data + std::string aPartialData = "solid test\n facet normal 0 0 1\n"; // Incomplete + std::istringstream aPartialStream(aPartialData); + DE_Provider::ReadStreamList aPartialReadStreams; + aPartialReadStreams.Append(DE_Provider::ReadStreamNode("partial.stl", aPartialStream)); + + TopoDS_Shape aPartialShape; + EXPECT_FALSE(myProvider->Read(aPartialReadStreams, aPartialShape)); +} + +// Test configuration parameter validation +TEST_F(DESTL_ProviderTest, ConfigurationParameterValidation) +{ + Handle(DESTL_ConfigurationNode) aNode = + Handle(DESTL_ConfigurationNode)::DownCast(myProvider->GetNode()); + + // Test valid merge angle + aNode->InternalParameters.ReadMergeAngle = 30.0; + EXPECT_DOUBLE_EQ(30.0, aNode->InternalParameters.ReadMergeAngle); + + // Test edge case merge angles + aNode->InternalParameters.ReadMergeAngle = 0.0; // Minimum + EXPECT_DOUBLE_EQ(0.0, aNode->InternalParameters.ReadMergeAngle); + + aNode->InternalParameters.ReadMergeAngle = 90.0; // Maximum + EXPECT_DOUBLE_EQ(90.0, aNode->InternalParameters.ReadMergeAngle); + + // Test BRep vs triangulation modes + aNode->InternalParameters.ReadBRep = Standard_True; + EXPECT_TRUE(aNode->InternalParameters.ReadBRep); + + aNode->InternalParameters.ReadBRep = Standard_False; + EXPECT_FALSE(aNode->InternalParameters.ReadBRep); +} + +// Test multiple triangulated faces +TEST_F(DESTL_ProviderTest, MultipleTriangulatedFaces) +{ + // Create another triangulated face with different geometry + TColgp_Array1OfPnt aNodes(1, 3); + aNodes.SetValue(1, gp_Pnt(0.0, 0.0, 0.0)); + aNodes.SetValue(2, gp_Pnt(5.0, 0.0, 0.0)); + aNodes.SetValue(3, gp_Pnt(2.5, 5.0, 0.0)); + + Poly_Array1OfTriangle aTriangles(1, 1); + aTriangles.SetValue(1, Poly_Triangle(1, 2, 3)); + + Handle(Poly_Triangulation) aTriangulation = new Poly_Triangulation(aNodes, aTriangles); + + gp_Pln aPlane(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(0, 0, 1)); + BRepBuilderAPI_MakeFace aFaceBuilder(aPlane, 0.0, 5.0, 0.0, 5.0); + TopoDS_Face aTriangleFace = aFaceBuilder.Face(); + + BRep_Builder aBuilder; + aBuilder.UpdateFace(aTriangleFace, aTriangulation); + + // Test first triangulated face + std::ostringstream aStream1; + DE_Provider::WriteStreamList aWriteStreams1; + aWriteStreams1.Append(DE_Provider::WriteStreamNode("face1.stl", aStream1)); + + EXPECT_TRUE(myProvider->Write(aWriteStreams1, myTriangularFace)); + std::string aContent1 = aStream1.str(); + + // Test second triangulated face + std::ostringstream aStream2; + DE_Provider::WriteStreamList aWriteStreams2; + aWriteStreams2.Append(DE_Provider::WriteStreamNode("face2.stl", aStream2)); + + EXPECT_TRUE(myProvider->Write(aWriteStreams2, aTriangleFace)); + std::string aContent2 = aStream2.str(); + + // Both content should be non-empty + EXPECT_FALSE(aContent1.empty()); + EXPECT_FALSE(aContent2.empty()); + + // Different triangulated faces should produce different STL content + EXPECT_NE(aContent1, aContent2); + + // Both should read back successfully + std::istringstream aIStream1(aContent1); + DE_Provider::ReadStreamList aReadStreams1; + aReadStreams1.Append(DE_Provider::ReadStreamNode("face1.stl", aIStream1)); + + TopoDS_Shape aReadShape1; + EXPECT_TRUE(myProvider->Read(aReadStreams1, aReadShape1)); + EXPECT_FALSE(aReadShape1.IsNull()); + + std::istringstream aIStream2(aContent2); + DE_Provider::ReadStreamList aReadStreams2; + aReadStreams2.Append(DE_Provider::ReadStreamNode("face2.stl", aIStream2)); + + TopoDS_Shape aReadShape2; + EXPECT_TRUE(myProvider->Read(aReadStreams2, aReadShape2)); + EXPECT_FALSE(aReadShape2.IsNull()); +} diff --git a/src/DataExchange/TKDESTL/GTests/FILES.cmake b/src/DataExchange/TKDESTL/GTests/FILES.cmake index 0a43c909d6..e88079fa28 100644 --- a/src/DataExchange/TKDESTL/GTests/FILES.cmake +++ b/src/DataExchange/TKDESTL/GTests/FILES.cmake @@ -2,4 +2,5 @@ set(OCCT_TKDESTL_GTests_FILES_LOCATION "${CMAKE_CURRENT_LIST_DIR}") set(OCCT_TKDESTL_GTests_FILES + DESTL_Provider_Test.cxx ) diff --git a/src/DataExchange/TKDESTL/RWStl/RWStl.cxx b/src/DataExchange/TKDESTL/RWStl/RWStl.cxx index 9ffdb2de47..5568f374dd 100644 --- a/src/DataExchange/TKDESTL/RWStl/RWStl.cxx +++ b/src/DataExchange/TKDESTL/RWStl/RWStl.cxx @@ -21,6 +21,10 @@ #include #include #include +#include +#include +#include +#include namespace { @@ -254,16 +258,13 @@ Standard_Boolean RWStl::WriteBinary(const Handle(Poly_Triangulation)& theMesh, TCollection_AsciiString aPath; thePath.SystemName(aPath); - FILE* aFile = OSD_OpenFile(aPath, "wb"); - if (aFile == NULL) + std::ofstream aStream(aPath.ToCString(), std::ios::binary); + if (!aStream.is_open()) { return Standard_False; } - Standard_Boolean isOK = writeBinary(theMesh, aFile, theProgress); - - fclose(aFile); - return isOK; + return WriteBinary(theMesh, aStream, theProgress); } //================================================================================================= @@ -280,45 +281,44 @@ Standard_Boolean RWStl::WriteAscii(const Handle(Poly_Triangulation)& theMesh, TCollection_AsciiString aPath; thePath.SystemName(aPath); - FILE* aFile = OSD_OpenFile(aPath, "w"); - if (aFile == NULL) + std::ofstream aStream(aPath.ToCString()); + if (!aStream.is_open()) { return Standard_False; } - Standard_Boolean isOK = writeASCII(theMesh, aFile, theProgress); - fclose(aFile); - return isOK; + return WriteAscii(theMesh, aStream, theProgress); } //================================================================================================= -Standard_Boolean RWStl::writeASCII(const Handle(Poly_Triangulation)& theMesh, - FILE* theFile, +Standard_Boolean RWStl::WriteAscii(const Handle(Poly_Triangulation)& theMesh, + Standard_OStream& theStream, const Message_ProgressRange& theProgress) { - // note that space after 'solid' is necessary for many systems - if (fwrite("solid \n", 1, 7, theFile) != 7) + if (theMesh.IsNull() || theMesh->NbTriangles() <= 0) { return Standard_False; } - char aBuffer[512]; - memset(aBuffer, 0, sizeof(aBuffer)); + // note that space after 'solid' is necessary for many systems + theStream << "solid \n"; + if (theStream.fail()) + { + return Standard_False; + } const Standard_Integer NBTriangles = theMesh->NbTriangles(); Message_ProgressScope aPS(theProgress, "Triangles", NBTriangles); + Standard_Integer anElem[3] = {0, 0, 0}; - Standard_Integer anElem[3] = {0, 0, 0}; for (Standard_Integer aTriIter = 1; aTriIter <= NBTriangles; ++aTriIter) { const Poly_Triangle aTriangle = theMesh->Triangle(aTriIter); aTriangle.Get(anElem[0], anElem[1], anElem[2]); - const gp_Pnt aP1 = theMesh->Node(anElem[0]); const gp_Pnt aP2 = theMesh->Node(anElem[1]); const gp_Pnt aP3 = theMesh->Node(anElem[2]); - const gp_Vec aVec1(aP1, aP2); const gp_Vec aVec2(aP1, aP3); gp_Vec aVNorm = aVec1.Crossed(aVec2); @@ -331,28 +331,16 @@ Standard_Boolean RWStl::writeASCII(const Handle(Poly_Triangulation)& theMesh, aVNorm.SetCoord(0.0, 0.0, 0.0); } - Sprintf(aBuffer, - " facet normal % 12e % 12e % 12e\n" - " outer loop\n" - " vertex % 12e % 12e % 12e\n" - " vertex % 12e % 12e % 12e\n" - " vertex % 12e % 12e % 12e\n" - " endloop\n" - " endfacet\n", - aVNorm.X(), - aVNorm.Y(), - aVNorm.Z(), - aP1.X(), - aP1.Y(), - aP1.Z(), - aP2.X(), - aP2.Y(), - aP2.Z(), - aP3.X(), - aP3.Y(), - aP3.Z()); - - if (fprintf(theFile, "%s", aBuffer) < 0) + theStream << " facet normal " << std::scientific << std::setprecision(12) << aVNorm.X() << " " + << aVNorm.Y() << " " << aVNorm.Z() << "\n" + << " outer loop\n" + << " vertex " << aP1.X() << " " << aP1.Y() << " " << aP1.Z() << "\n" + << " vertex " << aP2.X() << " " << aP2.Y() << " " << aP2.Z() << "\n" + << " vertex " << aP3.X() << " " << aP3.Y() << " " << aP3.Z() << "\n" + << " endloop\n" + << " endfacet\n"; + + if (theStream.fail()) { return Standard_False; } @@ -366,37 +354,38 @@ Standard_Boolean RWStl::writeASCII(const Handle(Poly_Triangulation)& theMesh, } } - if (fwrite("endsolid\n", 1, 9, theFile) != 9) - { - return Standard_False; - } - - return Standard_True; + theStream << "endsolid\n"; + return !theStream.fail(); } //================================================================================================= -Standard_Boolean RWStl::writeBinary(const Handle(Poly_Triangulation)& theMesh, - FILE* theFile, +Standard_Boolean RWStl::WriteBinary(const Handle(Poly_Triangulation)& theMesh, + Standard_OStream& theStream, const Message_ProgressRange& theProgress) { - char aHeader[80] = "STL Exported by Open CASCADE Technology [dev.opencascade.org]"; - if (fwrite(aHeader, 1, 80, theFile) != 80) + if (theMesh.IsNull() || theMesh->NbTriangles() <= 0) { return Standard_False; } - const Standard_Integer aNBTriangles = theMesh->NbTriangles(); - Message_ProgressScope aPS(theProgress, "Triangles", aNBTriangles); + char aHeader[80] = "STL Exported by Open CASCADE Technology [dev.opencascade.org]"; + theStream.write(aHeader, 80); + if (theStream.fail()) + { + return Standard_False; + } + const Standard_Integer aNBTriangles = theMesh->NbTriangles(); + Message_ProgressScope aPS(theProgress, "Triangles", aNBTriangles); const Standard_Size aNbChunkTriangles = 4096; const Standard_Size aChunkSize = aNbChunkTriangles * THE_STL_SIZEOF_FACET; NCollection_Array1 aData(1, aChunkSize); Standard_Character* aDataChunk = &aData.ChangeFirst(); - - Standard_Character aConv[4]; + Standard_Character aConv[4]; convertInteger(aNBTriangles, aConv); - if (fwrite(aConv, 1, 4, theFile) != 4) + theStream.write(aConv, 4); + if (theStream.fail()) { return Standard_False; } @@ -407,14 +396,12 @@ Standard_Boolean RWStl::writeBinary(const Handle(Poly_Triangulation)& theMesh, Standard_Integer id[3]; const Poly_Triangle aTriangle = theMesh->Triangle(aTriIter); aTriangle.Get(id[0], id[1], id[2]); - const gp_Pnt aP1 = theMesh->Node(id[0]); const gp_Pnt aP2 = theMesh->Node(id[1]); const gp_Pnt aP3 = theMesh->Node(id[2]); - - gp_Vec aVec1(aP1, aP2); - gp_Vec aVec2(aP1, aP3); - gp_Vec aVNorm = aVec1.Crossed(aVec2); + gp_Vec aVec1(aP1, aP2); + gp_Vec aVec2(aP1, aP3); + gp_Vec aVNorm = aVec1.Crossed(aVec2); if (aVNorm.SquareMagnitude() > gp::Resolution()) { aVNorm.Normalize(); @@ -423,48 +410,42 @@ Standard_Boolean RWStl::writeBinary(const Handle(Poly_Triangulation)& theMesh, { aVNorm.SetCoord(0.0, 0.0, 0.0); } - convertDouble(aVNorm.X(), &aDataChunk[aByteCount]); aByteCount += 4; convertDouble(aVNorm.Y(), &aDataChunk[aByteCount]); aByteCount += 4; convertDouble(aVNorm.Z(), &aDataChunk[aByteCount]); aByteCount += 4; - convertDouble(aP1.X(), &aDataChunk[aByteCount]); aByteCount += 4; convertDouble(aP1.Y(), &aDataChunk[aByteCount]); aByteCount += 4; convertDouble(aP1.Z(), &aDataChunk[aByteCount]); aByteCount += 4; - convertDouble(aP2.X(), &aDataChunk[aByteCount]); aByteCount += 4; convertDouble(aP2.Y(), &aDataChunk[aByteCount]); aByteCount += 4; convertDouble(aP2.Z(), &aDataChunk[aByteCount]); aByteCount += 4; - convertDouble(aP3.X(), &aDataChunk[aByteCount]); aByteCount += 4; convertDouble(aP3.Y(), &aDataChunk[aByteCount]); aByteCount += 4; convertDouble(aP3.Z(), &aDataChunk[aByteCount]); aByteCount += 4; + aDataChunk[aByteCount] = 0; + aDataChunk[aByteCount + 1] = 0; + aByteCount += 2; - aDataChunk[aByteCount] = 0; - aByteCount += 1; - aDataChunk[aByteCount] = 0; - aByteCount += 1; - - // Chunk is filled. Dump it to the file. + // Chunk is full, write it out. if (aByteCount == aChunkSize) { - if (fwrite(aDataChunk, 1, aChunkSize, theFile) != aChunkSize) + theStream.write(aDataChunk, aChunkSize); + if (theStream.fail()) { return Standard_False; } - aByteCount = 0; } @@ -480,7 +461,8 @@ Standard_Boolean RWStl::writeBinary(const Handle(Poly_Triangulation)& theMesh, // Write last part if necessary. if (aByteCount != aChunkSize) { - if (fwrite(aDataChunk, 1, aByteCount, theFile) != aByteCount) + theStream.write(aDataChunk, aByteCount); + if (theStream.fail()) { return Standard_False; } @@ -488,3 +470,66 @@ Standard_Boolean RWStl::writeBinary(const Handle(Poly_Triangulation)& theMesh, return Standard_True; } + +//================================================================================================= + +Handle(Poly_Triangulation) RWStl::ReadBinaryStream(Standard_IStream& theStream, + const Standard_Real theMergeAngle, + const Message_ProgressRange& theProgress) +{ + Reader aReader; + aReader.SetMergeAngle(theMergeAngle); + if (!aReader.ReadBinary(theStream, theProgress)) + { + return Handle(Poly_Triangulation)(); + } + return aReader.GetTriangulation(); +} + +//================================================================================================= + +Handle(Poly_Triangulation) RWStl::ReadAsciiStream(Standard_IStream& theStream, + const Standard_Real theMergeAngle, + const Message_ProgressRange& theProgress) +{ + Reader aReader; + aReader.SetMergeAngle(theMergeAngle); + + // get length of stream to feed progress indicator + theStream.seekg(0, theStream.end); + std::streampos theEnd = theStream.tellg(); + theStream.seekg(0, theStream.beg); + + Standard_ReadLineBuffer aBuffer(THE_BUFFER_SIZE); + if (!aReader.ReadAscii(theStream, aBuffer, theEnd, theProgress)) + { + return Handle(Poly_Triangulation)(); + } + return aReader.GetTriangulation(); +} + +//================================================================================================= + +Handle(Poly_Triangulation) RWStl::ReadStream(Standard_IStream& theStream, + const Standard_Real theMergeAngle, + const Message_ProgressRange& theProgress) +{ + // Try to detect ASCII vs Binary format by peeking at the first few bytes + std::streampos anOriginalPos = theStream.tellg(); + constexpr std::streamsize aHeaderSize = 6; + char aHeader[aHeaderSize]; + theStream.read(aHeader, aHeaderSize - 1); + aHeader[aHeaderSize - 1] = '\0'; + theStream.seekg(anOriginalPos); + + bool isAscii = (strncmp(aHeader, "solid", 5) == 0); + + if (isAscii) + { + return RWStl::ReadAsciiStream(theStream, theMergeAngle, theProgress); + } + else + { + return RWStl::ReadBinaryStream(theStream, theMergeAngle, theProgress); + } +} diff --git a/src/DataExchange/TKDESTL/RWStl/RWStl.hxx b/src/DataExchange/TKDESTL/RWStl/RWStl.hxx index f8b519ea36..058a3b8495 100644 --- a/src/DataExchange/TKDESTL/RWStl/RWStl.hxx +++ b/src/DataExchange/TKDESTL/RWStl/RWStl.hxx @@ -34,6 +34,12 @@ public: const OSD_Path& thePath, const Message_ProgressRange& theProgress = Message_ProgressRange()); + //! Write triangulation to binary STL stream. + Standard_EXPORT static Standard_Boolean WriteBinary( + const Handle(Poly_Triangulation)& theMesh, + Standard_OStream& theStream, + const Message_ProgressRange& theProgress = Message_ProgressRange()); + //! write the meshing in a file following the //! Ascii format of an STL file. //! Returns false if the cannot be opened; @@ -42,6 +48,12 @@ public: const OSD_Path& thePath, const Message_ProgressRange& theProgress = Message_ProgressRange()); + //! Write triangulation to ASCII STL stream. + Standard_EXPORT static Standard_Boolean WriteAscii( + const Handle(Poly_Triangulation)& theMesh, + Standard_OStream& theStream, + const Message_ProgressRange& theProgress = Message_ProgressRange()); + //! Read specified STL file and returns its content as triangulation. //! In case of error, returns Null handle. Standard_EXPORT static Handle(Poly_Triangulation) ReadFile( @@ -92,16 +104,26 @@ public: const OSD_Path& thePath, const Message_ProgressRange& theProgress = Message_ProgressRange()); -private: - //! Write ASCII version. - static Standard_Boolean writeASCII(const Handle(Poly_Triangulation)& theMesh, - FILE* theFile, - const Message_ProgressRange& theProgress); + //! Read triangulation from binary STL stream + //! In case of error, returns Null handle. + Standard_EXPORT static Handle(Poly_Triangulation) ReadBinaryStream( + Standard_IStream& theStream, + const Standard_Real theMergeAngle = M_PI / 2.0, + const Message_ProgressRange& theProgress = Message_ProgressRange()); + + //! Read triangulation from ASCII STL stream + //! In case of error, returns Null handle. + Standard_EXPORT static Handle(Poly_Triangulation) ReadAsciiStream( + Standard_IStream& theStream, + const Standard_Real theMergeAngle = M_PI / 2.0, + const Message_ProgressRange& theProgress = Message_ProgressRange()); - //! Write binary version. - static Standard_Boolean writeBinary(const Handle(Poly_Triangulation)& theMesh, - FILE* theFile, - const Message_ProgressRange& theProgress); + //! Read STL data from stream (auto-detects ASCII vs Binary) + //! In case of error, returns Null handle. + Standard_EXPORT static Handle(Poly_Triangulation) ReadStream( + Standard_IStream& theStream, + const Standard_Real theMergeAngle = M_PI / 2.0, + const Message_ProgressRange& theProgress = Message_ProgressRange()); }; #endif diff --git a/src/DataExchange/TKDESTL/StlAPI/StlAPI_Reader.cxx b/src/DataExchange/TKDESTL/StlAPI/StlAPI_Reader.cxx index 5eb075a8e6..4f501997e2 100644 --- a/src/DataExchange/TKDESTL/StlAPI/StlAPI_Reader.cxx +++ b/src/DataExchange/TKDESTL/StlAPI/StlAPI_Reader.cxx @@ -37,3 +37,24 @@ Standard_Boolean StlAPI_Reader::Read(TopoDS_Shape& theShape, const Standard_CStr theShape = aResult; return Standard_True; } + +//================================================================================================= + +Standard_Boolean StlAPI_Reader::Read(TopoDS_Shape& theShape, Standard_IStream& theStream) +{ + Handle(Poly_Triangulation) aMesh = RWStl::ReadStream(theStream); + if (aMesh.IsNull()) + return Standard_False; + + BRepBuilderAPI_MakeShapeOnMesh aConverter(aMesh); + aConverter.Build(); + if (!aConverter.IsDone()) + return Standard_False; + + TopoDS_Shape aResult = aConverter.Shape(); + if (aResult.IsNull()) + return Standard_False; + + theShape = aResult; + return Standard_True; +} diff --git a/src/DataExchange/TKDESTL/StlAPI/StlAPI_Reader.hxx b/src/DataExchange/TKDESTL/StlAPI/StlAPI_Reader.hxx index c8ccea531b..709d2a4aef 100644 --- a/src/DataExchange/TKDESTL/StlAPI/StlAPI_Reader.hxx +++ b/src/DataExchange/TKDESTL/StlAPI/StlAPI_Reader.hxx @@ -17,6 +17,7 @@ #define _StlAPI_Reader_HeaderFile #include +#include class TopoDS_Shape; @@ -30,6 +31,12 @@ public: //! Reads STL file to the TopoDS_Shape (each triangle is converted to the face). //! @return True if reading is successful Standard_EXPORT Standard_Boolean Read(TopoDS_Shape& theShape, const Standard_CString theFileName); + + //! Reads STL data from stream to the TopoDS_Shape (each triangle is converted to the face). + //! @param theShape result shape + //! @param theStream stream to read from + //! @return True if reading is successful + Standard_EXPORT Standard_Boolean Read(TopoDS_Shape& theShape, Standard_IStream& theStream); }; #endif // _StlAPI_Reader_HeaderFile diff --git a/src/DataExchange/TKDESTL/StlAPI/StlAPI_Writer.cxx b/src/DataExchange/TKDESTL/StlAPI/StlAPI_Writer.cxx index 3f9eb3fb3a..73b1d5c62d 100644 --- a/src/DataExchange/TKDESTL/StlAPI/StlAPI_Writer.cxx +++ b/src/DataExchange/TKDESTL/StlAPI/StlAPI_Writer.cxx @@ -22,6 +22,7 @@ #include #include #include +#include //================================================================================================= @@ -36,6 +37,21 @@ StlAPI_Writer::StlAPI_Writer() Standard_Boolean StlAPI_Writer::Write(const TopoDS_Shape& theShape, const Standard_CString theFileName, const Message_ProgressRange& theProgress) +{ + std::ofstream aStream(theFileName, myASCIIMode ? std::ios::out : std::ios::binary); + if (!aStream.is_open()) + { + return Standard_False; + } + + return Write(theShape, aStream, theProgress); +} + +//================================================================================================= + +Standard_Boolean StlAPI_Writer::Write(const TopoDS_Shape& theShape, + Standard_OStream& theStream, + const Message_ProgressRange& theProgress) { Standard_Integer aNbNodes = 0; Standard_Integer aNbTriangles = 0; @@ -115,9 +131,8 @@ Standard_Boolean StlAPI_Writer::Write(const TopoDS_Shape& theShape, aTriangleOffet += aTriangulation->NbTriangles(); } - OSD_Path aPath(theFileName); - Standard_Boolean isDone = (myASCIIMode ? RWStl::WriteAscii(aMesh, aPath, theProgress) - : RWStl::WriteBinary(aMesh, aPath, theProgress)); + Standard_Boolean isDone = (myASCIIMode ? RWStl::WriteAscii(aMesh, theStream, theProgress) + : RWStl::WriteBinary(aMesh, theStream, theProgress)); if (isDone && (aNbFacesNoTri > 0)) { @@ -130,4 +145,4 @@ Standard_Boolean StlAPI_Writer::Write(const TopoDS_Shape& theShape, } return isDone; -} +} \ No newline at end of file diff --git a/src/DataExchange/TKDESTL/StlAPI/StlAPI_Writer.hxx b/src/DataExchange/TKDESTL/StlAPI/StlAPI_Writer.hxx index f5841753f2..a228db85a6 100644 --- a/src/DataExchange/TKDESTL/StlAPI/StlAPI_Writer.hxx +++ b/src/DataExchange/TKDESTL/StlAPI/StlAPI_Writer.hxx @@ -45,6 +45,13 @@ public: const Standard_CString theFileName, const Message_ProgressRange& theProgress = Message_ProgressRange()); + //! Converts a given shape to STL format and writes it to the specified stream. + //! \return the error state. + Standard_EXPORT Standard_Boolean + Write(const TopoDS_Shape& theShape, + Standard_OStream& theStream, + const Message_ProgressRange& theProgress = Message_ProgressRange()); + private: Standard_Boolean myASCIIMode; }; diff --git a/src/DataExchange/TKDEVRML/DEVRML/DEVRML_ConfigurationNode.cxx b/src/DataExchange/TKDEVRML/DEVRML/DEVRML_ConfigurationNode.cxx index eb03f3fd18..66e48f77b3 100644 --- a/src/DataExchange/TKDEVRML/DEVRML/DEVRML_ConfigurationNode.cxx +++ b/src/DataExchange/TKDEVRML/DEVRML/DEVRML_ConfigurationNode.cxx @@ -172,6 +172,13 @@ bool DEVRML_ConfigurationNode::IsExportSupported() const //================================================================================================= +bool DEVRML_ConfigurationNode::IsStreamSupported() const +{ + return true; +} + +//================================================================================================= + TCollection_AsciiString DEVRML_ConfigurationNode::GetFormat() const { return TCollection_AsciiString("VRML"); diff --git a/src/DataExchange/TKDEVRML/DEVRML/DEVRML_ConfigurationNode.hxx b/src/DataExchange/TKDEVRML/DEVRML/DEVRML_ConfigurationNode.hxx index 6371244d6f..92aaecfe8e 100644 --- a/src/DataExchange/TKDEVRML/DEVRML/DEVRML_ConfigurationNode.hxx +++ b/src/DataExchange/TKDEVRML/DEVRML/DEVRML_ConfigurationNode.hxx @@ -65,6 +65,10 @@ public: //! @return true if export is supported Standard_EXPORT virtual bool IsExportSupported() const Standard_OVERRIDE; + //! Checks for stream support. + //! @return Standard_True if streams are supported + Standard_EXPORT virtual bool IsStreamSupported() const Standard_OVERRIDE; + //! Gets CAD format name of associated provider //! @return provider CAD format Standard_EXPORT virtual TCollection_AsciiString GetFormat() const Standard_OVERRIDE; diff --git a/src/DataExchange/TKDEVRML/DEVRML/DEVRML_Provider.cxx b/src/DataExchange/TKDEVRML/DEVRML/DEVRML_Provider.cxx index d98be8a987..9b2652eec8 100644 --- a/src/DataExchange/TKDEVRML/DEVRML/DEVRML_Provider.cxx +++ b/src/DataExchange/TKDEVRML/DEVRML/DEVRML_Provider.cxx @@ -13,6 +13,7 @@ #include +#include #include #include #include @@ -23,8 +24,189 @@ #include #include +#include + IMPLEMENT_STANDARD_RTTIEXT(DEVRML_Provider, DE_Provider) +namespace +{ +// Helper function to validate configuration node and downcast +static Handle(DEVRML_ConfigurationNode) ValidateConfigurationNode( + const Handle(DE_ConfigurationNode)& theNode, + const TCollection_AsciiString& theContext) +{ + if (!DE_ValidationUtils::ValidateConfigurationNode(theNode, + STANDARD_TYPE(DEVRML_ConfigurationNode), + theContext)) + { + return Handle(DEVRML_ConfigurationNode)(); + } + return Handle(DEVRML_ConfigurationNode)::DownCast(theNode); +} + +// Static function to handle VrmlData_Scene status errors +static Standard_Boolean HandleVrmlSceneStatus(const VrmlData_Scene& theScene, + const TCollection_AsciiString& theContext) +{ + const char* aStr = nullptr; + switch (theScene.Status()) + { + case VrmlData_StatusOK: + return Standard_True; + case VrmlData_EmptyData: + aStr = "EmptyData"; + break; + case VrmlData_UnrecoverableError: + aStr = "UnrecoverableError"; + break; + case VrmlData_GeneralError: + aStr = "GeneralError"; + break; + case VrmlData_EndOfFile: + aStr = "EndOfFile"; + break; + case VrmlData_NotVrmlFile: + aStr = "NotVrmlFile"; + break; + case VrmlData_CannotOpenFile: + aStr = "CannotOpenFile"; + break; + case VrmlData_VrmlFormatError: + aStr = "VrmlFormatError"; + break; + case VrmlData_NumericInputError: + aStr = "NumericInputError"; + break; + case VrmlData_IrrelevantNumber: + aStr = "IrrelevantNumber"; + break; + case VrmlData_BooleanInputError: + aStr = "BooleanInputError"; + break; + case VrmlData_StringInputError: + aStr = "StringInputError"; + break; + case VrmlData_NodeNameUnknown: + aStr = "NodeNameUnknown"; + break; + case VrmlData_NonPositiveSize: + aStr = "NonPositiveSize"; + break; + case VrmlData_ReadUnknownNode: + aStr = "ReadUnknownNode"; + break; + case VrmlData_NonSupportedFeature: + aStr = "NonSupportedFeature"; + break; + case VrmlData_OutputStreamUndefined: + aStr = "OutputStreamUndefined"; + break; + case VrmlData_NotImplemented: + aStr = "NotImplemented"; + break; + default: + break; + } + + if (aStr) + { + Message::SendFail() << "Error in the DEVRML_Provider during " << theContext + << ": ++ VRML Error: " << aStr << " in line " << theScene.GetLineError(); + return Standard_False; + } + return Standard_True; +} + +// Static function to calculate scaling factor +static Standard_Real CalculateScalingFactor(const Handle(TDocStd_Document)& theDocument, + const Handle(DEVRML_ConfigurationNode)& theNode, + const TCollection_AsciiString& theContext) +{ + Standard_Real aScaling = 1.; + Standard_Real aScaleFactorMM = 1.; + if (XCAFDoc_DocumentTool::GetLengthUnit(theDocument, + aScaleFactorMM, + UnitsMethods_LengthUnit_Millimeter)) + { + aScaling = aScaleFactorMM / theNode->GlobalParameters.LengthUnit; + } + else + { + aScaling = theNode->GlobalParameters.SystemUnit / theNode->GlobalParameters.LengthUnit; + Message::SendWarning() + << "Warning in the DEVRML_Provider during " << theContext + << ": The document has no information on Units. Using global parameter as initial Unit."; + } + return aScaling; +} + +// Static function to extract VRML directory path from file path +static TCollection_AsciiString ExtractVrmlDirectory(const TCollection_AsciiString& thePath) +{ + OSD_Path aPath(thePath.ToCString()); + TCollection_AsciiString aVrmlDir("."); + TCollection_AsciiString aDisk = aPath.Disk(); + TCollection_AsciiString aTrek = aPath.Trek(); + if (!aTrek.IsEmpty()) + { + if (!aDisk.IsEmpty()) + { + aVrmlDir = aDisk; + } + else + { + aVrmlDir.Clear(); + } + aTrek.ChangeAll('|', '/'); + aVrmlDir += aTrek; + } + return aVrmlDir; +} + +// Static function to process VRML scene from stream and extract shape +static Standard_Boolean ProcessVrmlScene(Standard_IStream& theStream, + const Handle(DEVRML_ConfigurationNode)& theNode, + const TCollection_AsciiString& theVrmlDir, + TopoDS_Shape& theShape, + const TCollection_AsciiString& theContext) +{ + VrmlData_Scene aScene; + aScene.SetLinearScale(theNode->GlobalParameters.LengthUnit); + aScene.SetVrmlDir(theVrmlDir); + + aScene << theStream; + + if (!HandleVrmlSceneStatus(aScene, theContext)) + { + return Standard_False; + } + + if (aScene.Status() == VrmlData_StatusOK) + { + VrmlData_DataMapOfShapeAppearance aShapeAppMap; + TopoDS_Shape aShape = aScene.GetShape(aShapeAppMap); + theShape = aShape; + + // Verify that a valid shape was extracted + if (theShape.IsNull()) + { + Message::SendFail() << "Error in the DEVRML_Provider during " << theContext + << ": VRML scene processed successfully but no geometry was extracted"; + return Standard_False; + } + } + else + { + // Scene status was not OK but HandleVrmlSceneStatus didn't catch it + Message::SendFail() << "Error in the DEVRML_Provider during " << theContext + << ": VRML scene status is not OK but no specific error was reported"; + return Standard_False; + } + + return Standard_True; +} +} // namespace + //================================================================================================= DEVRML_Provider::DEVRML_Provider() {} @@ -64,19 +246,16 @@ bool DEVRML_Provider::Read(const TCollection_AsciiString& thePath, const Handle(TDocStd_Document)& theDocument, const Message_ProgressRange& theProgress) { - if (theDocument.IsNull()) + TCollection_AsciiString aContext = TCollection_AsciiString("reading the file ") + thePath; + if (!DE_ValidationUtils::ValidateDocument(theDocument, aContext)) { - Message::SendFail() << "Error in the DEVRML_Provider during reading the file " << thePath - << "\t: theDocument shouldn't be null"; return false; } - if (GetNode().IsNull() || !GetNode()->IsKind(STANDARD_TYPE(DEVRML_ConfigurationNode))) + Handle(DEVRML_ConfigurationNode) aNode = ValidateConfigurationNode(GetNode(), aContext); + if (aNode.IsNull()) { - Message::SendFail() << "Error in the DEVRML_Provider during reading the file " << thePath - << "\t: Incorrect or empty Configuration Node"; return false; } - Handle(DEVRML_ConfigurationNode) aNode = Handle(DEVRML_ConfigurationNode)::DownCast(GetNode()); VrmlAPI_CafReader aVrmlReader; aVrmlReader.SetDocument(theDocument); @@ -110,32 +289,24 @@ bool DEVRML_Provider::Write(const TCollection_AsciiString& thePath, const Message_ProgressRange& theProgress) { (void)theProgress; - if (GetNode().IsNull() || !GetNode()->IsKind(STANDARD_TYPE(DEVRML_ConfigurationNode))) + TCollection_AsciiString aContext = "writing the file "; + aContext += thePath; + if (!DE_ValidationUtils::ValidateDocument(theDocument, aContext)) + { + return false; + } + + Handle(DEVRML_ConfigurationNode) aNode = ValidateConfigurationNode(GetNode(), aContext); + if (aNode.IsNull()) { - Message::SendFail() << "Error in the DEVRML_Provider during writing the file " << thePath - << "\t: Incorrect or empty Configuration Node"; return false; } - Handle(DEVRML_ConfigurationNode) aNode = Handle(DEVRML_ConfigurationNode)::DownCast(GetNode()); VrmlAPI_Writer aWriter; aWriter.SetRepresentation( static_cast(aNode->InternalParameters.WriteRepresentationType)); - Standard_Real aScaling = 1.; - Standard_Real aScaleFactorMM = 1.; - if (XCAFDoc_DocumentTool::GetLengthUnit(theDocument, - aScaleFactorMM, - UnitsMethods_LengthUnit_Millimeter)) - { - aScaling = aScaleFactorMM / aNode->GlobalParameters.LengthUnit; - } - else - { - aScaling = aNode->GlobalParameters.SystemUnit / aNode->GlobalParameters.LengthUnit; - Message::SendWarning() - << "Warning in the DEVRML_Provider during writing the file " << thePath - << "\t: The document has no information on Units. Using global parameter as initial Unit."; - } + + Standard_Real aScaling = CalculateScalingFactor(theDocument, aNode, aContext); if (!aWriter.WriteDoc(theDocument, thePath.ToCString(), aScaling)) { Message::SendFail() << "Error in the DEVRML_Provider during wtiting the file " << thePath @@ -175,125 +346,26 @@ bool DEVRML_Provider::Read(const TCollection_AsciiString& thePath, const Message_ProgressRange& theProgress) { (void)theProgress; - if (GetNode().IsNull() || !GetNode()->IsKind(STANDARD_TYPE(DEVRML_ConfigurationNode))) + TCollection_AsciiString aContext = "reading the file "; + aContext += thePath; + Handle(DEVRML_ConfigurationNode) aNode = ValidateConfigurationNode(GetNode(), aContext); + if (aNode.IsNull()) { - Message::SendFail() << "Error in the DEVRML_Provider during reading the file " << thePath - << "\t: Incorrect or empty Configuration Node"; return false; } - Handle(DEVRML_ConfigurationNode) aNode = Handle(DEVRML_ConfigurationNode)::DownCast(GetNode()); - - TopoDS_Shape aShape; - VrmlData_DataMapOfShapeAppearance aShapeAppMap; std::filebuf aFic; std::istream aStream(&aFic); - if (aFic.open(thePath.ToCString(), std::ios::in)) - { - // Get path of the VRML file. - OSD_Path aPath(thePath.ToCString()); - TCollection_AsciiString aVrmlDir("."); - TCollection_AsciiString aDisk = aPath.Disk(); - TCollection_AsciiString aTrek = aPath.Trek(); - if (!aTrek.IsEmpty()) - { - if (!aDisk.IsEmpty()) - { - aVrmlDir = aDisk; - } - else - { - aVrmlDir.Clear(); - } - aTrek.ChangeAll('|', '/'); - aVrmlDir += aTrek; - } - - VrmlData_Scene aScene; - aScene.SetLinearScale(aNode->GlobalParameters.LengthUnit); - - aScene.SetVrmlDir(aVrmlDir); - aScene << aStream; - const char* aStr = 0L; - switch (aScene.Status()) - { - case VrmlData_StatusOK: { - aShape = aScene.GetShape(aShapeAppMap); - break; - } - case VrmlData_EmptyData: - aStr = "EmptyData"; - break; - case VrmlData_UnrecoverableError: - aStr = "UnrecoverableError"; - break; - case VrmlData_GeneralError: - aStr = "GeneralError"; - break; - case VrmlData_EndOfFile: - aStr = "EndOfFile"; - break; - case VrmlData_NotVrmlFile: - aStr = "NotVrmlFile"; - break; - case VrmlData_CannotOpenFile: - aStr = "CannotOpenFile"; - break; - case VrmlData_VrmlFormatError: - aStr = "VrmlFormatError"; - break; - case VrmlData_NumericInputError: - aStr = "NumericInputError"; - break; - case VrmlData_IrrelevantNumber: - aStr = "IrrelevantNumber"; - break; - case VrmlData_BooleanInputError: - aStr = "BooleanInputError"; - break; - case VrmlData_StringInputError: - aStr = "StringInputError"; - break; - case VrmlData_NodeNameUnknown: - aStr = "NodeNameUnknown"; - break; - case VrmlData_NonPositiveSize: - aStr = "NonPositiveSize"; - break; - case VrmlData_ReadUnknownNode: - aStr = "ReadUnknownNode"; - break; - case VrmlData_NonSupportedFeature: - aStr = "NonSupportedFeature"; - break; - case VrmlData_OutputStreamUndefined: - aStr = "OutputStreamUndefined"; - break; - case VrmlData_NotImplemented: - aStr = "NotImplemented"; - break; - default: - break; - } - if (aStr) - { - Message::SendFail() << "Error in the DEVRML_Provider during reading the file " << thePath - << "\t: ++ VRML Error: " << aStr << " in line " << aScene.GetLineError(); - return false; - } - else - { - theShape = aShape; - } - } - else + if (!aFic.open(thePath.ToCString(), std::ios::in)) { Message::SendFail() << "Error in the DEVRML_Provider during reading the file " << thePath << "\t: cannot open file"; return false; } - return true; + + TCollection_AsciiString aVrmlDir = ExtractVrmlDirectory(thePath); + return ProcessVrmlScene(aStream, aNode, aVrmlDir, theShape, aContext); } //================================================================================================= @@ -310,6 +382,189 @@ bool DEVRML_Provider::Write(const TCollection_AsciiString& thePath, //================================================================================================= +Standard_Boolean DEVRML_Provider::Read(ReadStreamList& theStreams, + const Handle(TDocStd_Document)& theDocument, + Handle(XSControl_WorkSession)& theWS, + const Message_ProgressRange& theProgress) +{ + (void)theWS; + return Read(theStreams, theDocument, theProgress); +} + +//================================================================================================= + +Standard_Boolean DEVRML_Provider::Write(WriteStreamList& theStreams, + const Handle(TDocStd_Document)& theDocument, + Handle(XSControl_WorkSession)& theWS, + const Message_ProgressRange& theProgress) +{ + (void)theWS; + return Write(theStreams, theDocument, theProgress); +} + +//================================================================================================= + +Standard_Boolean DEVRML_Provider::Read(ReadStreamList& theStreams, + TopoDS_Shape& theShape, + Handle(XSControl_WorkSession)& theWS, + const Message_ProgressRange& theProgress) +{ + (void)theWS; + return Read(theStreams, theShape, theProgress); +} + +//================================================================================================= + +Standard_Boolean DEVRML_Provider::Write(WriteStreamList& theStreams, + const TopoDS_Shape& theShape, + Handle(XSControl_WorkSession)& theWS, + const Message_ProgressRange& theProgress) +{ + (void)theWS; + return Write(theStreams, theShape, theProgress); +} + +//================================================================================================= + +Standard_Boolean DEVRML_Provider::Read(ReadStreamList& theStreams, + const Handle(TDocStd_Document)& theDocument, + const Message_ProgressRange& theProgress) +{ + TCollection_AsciiString aContext = "reading stream"; + if (!DE_ValidationUtils::ValidateReadStreamList(theStreams, aContext)) + { + return Standard_False; + } + + const TCollection_AsciiString& aFirstKey = theStreams.First().Path; + TCollection_AsciiString aFullContext = aContext + " " + aFirstKey; + if (!DE_ValidationUtils::ValidateDocument(theDocument, aFullContext)) + { + return Standard_False; + } + + TopoDS_Shape aShape; + if (!Read(theStreams, aShape, theProgress)) + { + return Standard_False; + } + + Handle(XCAFDoc_ShapeTool) aShapeTool = XCAFDoc_DocumentTool::ShapeTool(theDocument->Main()); + aShapeTool->AddShape(aShape); + return Standard_True; +} + +//================================================================================================= + +Standard_Boolean DEVRML_Provider::Write(WriteStreamList& theStreams, + const Handle(TDocStd_Document)& theDocument, + const Message_ProgressRange& theProgress) +{ + (void)theProgress; + TCollection_AsciiString aContext = "writing stream"; + if (!DE_ValidationUtils::ValidateWriteStreamList(theStreams, aContext)) + { + return Standard_False; + } + + const TCollection_AsciiString& aFirstKey = theStreams.First().Path; + TCollection_AsciiString aFullContext = aContext + " " + aFirstKey; + if (!DE_ValidationUtils::ValidateDocument(theDocument, aFullContext)) + { + return Standard_False; + } + + Handle(DEVRML_ConfigurationNode) aNode = ValidateConfigurationNode(GetNode(), aFullContext); + if (aNode.IsNull()) + { + return Standard_False; + } + + Standard_Real aScaling = CalculateScalingFactor(theDocument, aNode, aContext); + + // Use VrmlAPI_Writer with stream support + VrmlAPI_Writer aWriter; + aWriter.SetRepresentation( + static_cast(aNode->InternalParameters.WriteRepresentationType)); + + Standard_OStream& aStream = theStreams.First().Stream; + + if (!aWriter.WriteDoc(theDocument, aStream, aScaling)) + { + Message::SendFail() << "Error in the DEVRML_Provider during " << aContext + << ": WriteDoc operation failed"; + return Standard_False; + } + + return Standard_True; +} + +//================================================================================================= + +Standard_Boolean DEVRML_Provider::Read(ReadStreamList& theStreams, + TopoDS_Shape& theShape, + const Message_ProgressRange& theProgress) +{ + (void)theProgress; + TCollection_AsciiString aContext = "reading stream"; + if (!DE_ValidationUtils::ValidateReadStreamList(theStreams, aContext)) + { + return Standard_False; + } + + const TCollection_AsciiString& aFirstKey = theStreams.First().Path; + Standard_IStream& aStream = theStreams.First().Stream; + + TCollection_AsciiString aFullContext = aContext + " " + aFirstKey; + Handle(DEVRML_ConfigurationNode) aNode = ValidateConfigurationNode(GetNode(), aFullContext); + if (aNode.IsNull()) + { + return Standard_False; + } + + return ProcessVrmlScene(aStream, aNode, ".", theShape, aContext); +} + +//================================================================================================= + +Standard_Boolean DEVRML_Provider::Write(WriteStreamList& theStreams, + const TopoDS_Shape& theShape, + const Message_ProgressRange& theProgress) +{ + (void)theProgress; + TCollection_AsciiString aContext = "writing stream"; + if (!DE_ValidationUtils::ValidateWriteStreamList(theStreams, aContext)) + { + return Standard_False; + } + + const TCollection_AsciiString& aFirstKey = theStreams.First().Path; + TCollection_AsciiString aFullContext = aContext + " " + aFirstKey; + Handle(DEVRML_ConfigurationNode) aNode = ValidateConfigurationNode(GetNode(), aFullContext); + if (aNode.IsNull()) + { + return Standard_False; + } + + // Use VrmlAPI_Writer with stream support + VrmlAPI_Writer aWriter; + aWriter.SetRepresentation( + static_cast(aNode->InternalParameters.WriteRepresentationType)); + + Standard_OStream& aStream = theStreams.First().Stream; + + if (!aWriter.Write(theShape, aStream, 2)) // Use version 2 by default + { + Message::SendFail() << "Error in the DEVRML_Provider during " << aContext + << ": Write operation failed"; + return Standard_False; + } + + return Standard_True; +} + +//================================================================================================= + TCollection_AsciiString DEVRML_Provider::GetFormat() const { return TCollection_AsciiString("VRML"); diff --git a/src/DataExchange/TKDEVRML/DEVRML/DEVRML_Provider.hxx b/src/DataExchange/TKDEVRML/DEVRML/DEVRML_Provider.hxx index df250af8f1..05bb057980 100644 --- a/src/DataExchange/TKDEVRML/DEVRML/DEVRML_Provider.hxx +++ b/src/DataExchange/TKDEVRML/DEVRML/DEVRML_Provider.hxx @@ -108,6 +108,54 @@ public: Handle(XSControl_WorkSession)& theWS, const Message_ProgressRange& theProgress = Message_ProgressRange()) Standard_OVERRIDE; + //! Reads streams according to internal configuration + //! @param[in] theStreams streams to read from + //! @param[out] theDocument document to save result + //! @param[in] theWS current work session + //! @param[in] theProgress progress indicator + //! @return true if Read operation has ended correctly + Standard_EXPORT virtual Standard_Boolean Read( + ReadStreamList& theStreams, + const Handle(TDocStd_Document)& theDocument, + Handle(XSControl_WorkSession)& theWS, + const Message_ProgressRange& theProgress = Message_ProgressRange()) Standard_OVERRIDE; + + //! Writes streams according to internal configuration + //! @param[in] theStreams streams to write to + //! @param[out] theDocument document to export + //! @param[in] theWS current work session + //! @param[in] theProgress progress indicator + //! @return true if Write operation has ended correctly + Standard_EXPORT virtual Standard_Boolean Write( + WriteStreamList& theStreams, + const Handle(TDocStd_Document)& theDocument, + Handle(XSControl_WorkSession)& theWS, + const Message_ProgressRange& theProgress = Message_ProgressRange()) Standard_OVERRIDE; + + //! Reads streams according to internal configuration + //! @param[in] theStreams streams to read from + //! @param[out] theShape shape to save result + //! @param[in] theWS current work session + //! @param[in] theProgress progress indicator + //! @return true if Read operation has ended correctly + Standard_EXPORT virtual Standard_Boolean Read( + ReadStreamList& theStreams, + TopoDS_Shape& theShape, + Handle(XSControl_WorkSession)& theWS, + const Message_ProgressRange& theProgress = Message_ProgressRange()) Standard_OVERRIDE; + + //! Writes streams according to internal configuration + //! @param[in] theStreams streams to write to + //! @param[out] theShape shape to export + //! @param[in] theWS current work session + //! @param[in] theProgress progress indicator + //! @return true if Write operation has ended correctly + Standard_EXPORT virtual Standard_Boolean Write( + WriteStreamList& theStreams, + const TopoDS_Shape& theShape, + Handle(XSControl_WorkSession)& theWS, + const Message_ProgressRange& theProgress = Message_ProgressRange()) Standard_OVERRIDE; + //! Reads a CAD file, according internal configuration //! @param[in] thePath path to the import CAD file //! @param[out] theShape shape to save result @@ -128,6 +176,46 @@ public: const TopoDS_Shape& theShape, const Message_ProgressRange& theProgress = Message_ProgressRange()) Standard_OVERRIDE; + //! Reads streams according to internal configuration + //! @param[in] theStreams streams to read from + //! @param[out] theDocument document to save result + //! @param[in] theProgress progress indicator + //! @return true if Read operation has ended correctly + Standard_EXPORT virtual Standard_Boolean Read( + ReadStreamList& theStreams, + const Handle(TDocStd_Document)& theDocument, + const Message_ProgressRange& theProgress = Message_ProgressRange()) Standard_OVERRIDE; + + //! Writes streams according to internal configuration + //! @param[in] theStreams streams to write to + //! @param[out] theDocument document to export + //! @param[in] theProgress progress indicator + //! @return true if Write operation has ended correctly + Standard_EXPORT virtual Standard_Boolean Write( + WriteStreamList& theStreams, + const Handle(TDocStd_Document)& theDocument, + const Message_ProgressRange& theProgress = Message_ProgressRange()) Standard_OVERRIDE; + + //! Reads streams according to internal configuration + //! @param[in] theStreams streams to read from + //! @param[out] theShape shape to save result + //! @param[in] theProgress progress indicator + //! @return true if Read operation has ended correctly + Standard_EXPORT virtual Standard_Boolean Read( + ReadStreamList& theStreams, + TopoDS_Shape& theShape, + const Message_ProgressRange& theProgress = Message_ProgressRange()) Standard_OVERRIDE; + + //! Writes streams according to internal configuration + //! @param[in] theStreams streams to write to + //! @param[out] theShape shape to export + //! @param[in] theProgress progress indicator + //! @return true if Write operation has ended correctly + Standard_EXPORT virtual Standard_Boolean Write( + WriteStreamList& theStreams, + const TopoDS_Shape& theShape, + const Message_ProgressRange& theProgress = Message_ProgressRange()) Standard_OVERRIDE; + public: //! Gets CAD format name of associated provider //! @return provider CAD format diff --git a/src/DataExchange/TKDEVRML/GTests/DEVRML_Provider_Test.cxx b/src/DataExchange/TKDEVRML/GTests/DEVRML_Provider_Test.cxx new file mode 100644 index 0000000000..1328e61dce --- /dev/null +++ b/src/DataExchange/TKDEVRML/GTests/DEVRML_Provider_Test.cxx @@ -0,0 +1,519 @@ +// Copyright (c) 2025 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +class DEVRML_ProviderTest : public ::testing::Test +{ +protected: + void SetUp() override + { + // Initialize provider with default configuration (will be modified per test) + Handle(DEVRML_ConfigurationNode) aNode = new DEVRML_ConfigurationNode(); + myProvider = new DEVRML_Provider(aNode); + + // Create test shapes + myBox = BRepPrimAPI_MakeBox(10.0, 10.0, 10.0).Shape(); // For wireframe testing + mySphere = BRepPrimAPI_MakeSphere(5.0).Shape(); // For wireframe testing + myTriangularFace = CreateTriangulatedFace(); // For shaded/face testing + + // Create test document + Handle(TDocStd_Application) anApp = new TDocStd_Application(); + anApp->NewDocument("BinXCAF", myDocument); + } + + void TearDown() override + { + myProvider.Nullify(); + myDocument.Nullify(); + } + + // Helper method to count shape elements + Standard_Integer CountShapeElements(const TopoDS_Shape& theShape, TopAbs_ShapeEnum theType) + { + Standard_Integer aCount = 0; + for (TopExp_Explorer anExplorer(theShape, theType); anExplorer.More(); anExplorer.Next()) + { + aCount++; + } + return aCount; + } + + // Helper method to create a triangulated face with mesh data + TopoDS_Shape CreateTriangulatedFace() + { + // Create vertices for triangulation + TColgp_Array1OfPnt aNodes(1, 4); + aNodes.SetValue(1, gp_Pnt(0.0, 0.0, 0.0)); // Bottom-left + aNodes.SetValue(2, gp_Pnt(10.0, 0.0, 0.0)); // Bottom-right + aNodes.SetValue(3, gp_Pnt(10.0, 10.0, 0.0)); // Top-right + aNodes.SetValue(4, gp_Pnt(0.0, 10.0, 0.0)); // Top-left + + // Create triangles (two triangles forming a quad) + Poly_Array1OfTriangle aTriangles(1, 2); + aTriangles.SetValue(1, Poly_Triangle(1, 2, 3)); // First triangle + aTriangles.SetValue(2, Poly_Triangle(1, 3, 4)); // Second triangle + + // Create triangulation + Handle(Poly_Triangulation) aTriangulation = new Poly_Triangulation(aNodes, aTriangles); + + // Create a simple planar face + gp_Pln aPlane(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(0, 0, 1)); + BRepBuilderAPI_MakeFace aFaceBuilder(aPlane, 0.0, 10.0, 0.0, 10.0); + + if (!aFaceBuilder.IsDone()) + { + return TopoDS_Shape(); + } + + TopoDS_Face aFace = aFaceBuilder.Face(); + + // Attach triangulation to the face (without location parameter) + BRep_Builder aBuilder; + aBuilder.UpdateFace(aFace, aTriangulation); + + return aFace; + } + +protected: + Handle(DEVRML_Provider) myProvider; + TopoDS_Shape myBox; + TopoDS_Shape mySphere; + TopoDS_Shape myTriangularFace; + Handle(TDocStd_Document) myDocument; +}; + +// Test basic provider creation and format/vendor information +TEST_F(DEVRML_ProviderTest, BasicProperties) +{ + EXPECT_STREQ("VRML", myProvider->GetFormat().ToCString()); + EXPECT_STREQ("OCC", myProvider->GetVendor().ToCString()); + EXPECT_FALSE(myProvider->GetNode().IsNull()); +} + +// Test stream-based shape write and read operations with wireframe (edges) +TEST_F(DEVRML_ProviderTest, StreamShapeWriteReadWireframe) +{ + // Configure provider for wireframe mode (default) + Handle(DEVRML_ConfigurationNode) aNode = + Handle(DEVRML_ConfigurationNode)::DownCast(myProvider->GetNode()); + aNode->InternalParameters.WriteRepresentationType = + DEVRML_ConfigurationNode::WriteMode_RepresentationType_Wireframe; + + std::ostringstream anOStream; + DE_Provider::WriteStreamList aWriteStreams; + aWriteStreams.Append(DE_Provider::WriteStreamNode("wireframe.vrml", anOStream)); + + // Write box to stream + EXPECT_TRUE(myProvider->Write(aWriteStreams, myBox)); + + std::string aVrmlContent = anOStream.str(); + EXPECT_FALSE(aVrmlContent.empty()); + EXPECT_TRUE(aVrmlContent.find("#VRML") != std::string::npos); + + if (!aVrmlContent.empty()) + { + // Read back from stream + std::istringstream anIStream(aVrmlContent); + DE_Provider::ReadStreamList aReadStreams; + aReadStreams.Append(DE_Provider::ReadStreamNode("wireframe.vrml", anIStream)); + + TopoDS_Shape aReadShape; + EXPECT_TRUE(myProvider->Read(aReadStreams, aReadShape)); + EXPECT_FALSE(aReadShape.IsNull()); + + if (!aReadShape.IsNull()) + { + // Wireframe mode should produce edges, not faces + Standard_Integer aReadEdges = CountShapeElements(aReadShape, TopAbs_EDGE); + EXPECT_TRUE(aReadEdges > 0); // Should have edges from wireframe + } + } +} + +// Test stream-based shape write and read operations with shaded (faces) +TEST_F(DEVRML_ProviderTest, StreamShapeWriteReadShaded) +{ + // Configure provider for shaded mode + Handle(DEVRML_ConfigurationNode) aNode = + Handle(DEVRML_ConfigurationNode)::DownCast(myProvider->GetNode()); + aNode->InternalParameters.WriteRepresentationType = + DEVRML_ConfigurationNode::WriteMode_RepresentationType_Shaded; + + std::ostringstream anOStream; + DE_Provider::WriteStreamList aWriteStreams; + aWriteStreams.Append(DE_Provider::WriteStreamNode("shaded.vrml", anOStream)); + + // Write triangular face to stream + EXPECT_TRUE(myProvider->Write(aWriteStreams, myTriangularFace)); + + std::string aVrmlContent = anOStream.str(); + EXPECT_FALSE(aVrmlContent.empty()); + EXPECT_TRUE(aVrmlContent.find("#VRML") != std::string::npos); + + if (!aVrmlContent.empty()) + { + // Read back from stream + std::istringstream anIStream(aVrmlContent); + DE_Provider::ReadStreamList aReadStreams; + aReadStreams.Append(DE_Provider::ReadStreamNode("shaded.vrml", anIStream)); + + TopoDS_Shape aReadShape; + EXPECT_TRUE(myProvider->Read(aReadStreams, aReadShape)); + EXPECT_FALSE(aReadShape.IsNull()); + + if (!aReadShape.IsNull()) + { + // Shaded mode should produce faces + Standard_Integer aReadFaces = CountShapeElements(aReadShape, TopAbs_FACE); + EXPECT_TRUE(aReadFaces > 0); // Should have faces from shaded mode + } + } +} + +// Test stream-based document write and read operations +TEST_F(DEVRML_ProviderTest, StreamDocumentWriteRead) +{ + // Configure provider for shaded mode for better document compatibility + Handle(DEVRML_ConfigurationNode) aNode = + Handle(DEVRML_ConfigurationNode)::DownCast(myProvider->GetNode()); + aNode->InternalParameters.WriteRepresentationType = + DEVRML_ConfigurationNode::WriteMode_RepresentationType_Shaded; + + // Add shape to document + Handle(XCAFDoc_ShapeTool) aShapeTool = XCAFDoc_DocumentTool::ShapeTool(myDocument->Main()); + TDF_Label aShapeLabel = aShapeTool->AddShape(myTriangularFace); + EXPECT_FALSE(aShapeLabel.IsNull()); + + std::ostringstream anOStream; + DE_Provider::WriteStreamList aWriteStreams; + aWriteStreams.Append(DE_Provider::WriteStreamNode("document.vrml", anOStream)); + + // Write document to stream + EXPECT_TRUE(myProvider->Write(aWriteStreams, myDocument)); + + std::string aVrmlContent = anOStream.str(); + EXPECT_FALSE(aVrmlContent.empty()); + EXPECT_TRUE(aVrmlContent.find("#VRML") != std::string::npos); + + if (!aVrmlContent.empty()) + { + // Create new document for reading + Handle(TDocStd_Application) anApp = new TDocStd_Application(); + Handle(TDocStd_Document) aNewDocument; + anApp->NewDocument("BinXCAF", aNewDocument); + + // Read back from stream + std::istringstream anIStream(aVrmlContent); + DE_Provider::ReadStreamList aReadStreams; + aReadStreams.Append(DE_Provider::ReadStreamNode("document.vrml", anIStream)); + + EXPECT_TRUE(myProvider->Read(aReadStreams, aNewDocument)); + + // Validate document content + Handle(XCAFDoc_ShapeTool) aNewShapeTool = XCAFDoc_DocumentTool::ShapeTool(aNewDocument->Main()); + TDF_LabelSequence aLabels; + aNewShapeTool->GetShapes(aLabels); + EXPECT_GT(aLabels.Length(), 0); // Should have at least one shape in document + } +} + +// Test stream-based document with multiple shapes +TEST_F(DEVRML_ProviderTest, StreamDocumentMultipleShapes) +{ + // Configure provider for shaded mode for better multi-shape compatibility + Handle(DEVRML_ConfigurationNode) aNode = + Handle(DEVRML_ConfigurationNode)::DownCast(myProvider->GetNode()); + aNode->InternalParameters.WriteRepresentationType = + DEVRML_ConfigurationNode::WriteMode_RepresentationType_Shaded; + + // Add multiple shapes to document + Handle(XCAFDoc_ShapeTool) aShapeTool = XCAFDoc_DocumentTool::ShapeTool(myDocument->Main()); + TDF_Label aFirstLabel = aShapeTool->AddShape(myTriangularFace); + EXPECT_FALSE(aFirstLabel.IsNull()); + + // Add a second shape - using the sphere for variety + TDF_Label aSecondLabel = aShapeTool->AddShape(mySphere); + EXPECT_FALSE(aSecondLabel.IsNull()); + + std::ostringstream anOStream; + DE_Provider::WriteStreamList aWriteStreams; + aWriteStreams.Append(DE_Provider::WriteStreamNode("multi_shapes.vrml", anOStream)); + + // Write document to stream + EXPECT_TRUE(myProvider->Write(aWriteStreams, myDocument)); + + std::string aVrmlContent = anOStream.str(); + EXPECT_FALSE(aVrmlContent.empty()); + EXPECT_TRUE(aVrmlContent.find("#VRML") != std::string::npos); + + if (!aVrmlContent.empty()) + { + // Create new document for reading + Handle(TDocStd_Application) anApp = new TDocStd_Application(); + Handle(TDocStd_Document) aNewDocument; + anApp->NewDocument("BinXCAF", aNewDocument); + + // Read back from stream + std::istringstream anIStream(aVrmlContent); + DE_Provider::ReadStreamList aReadStreams; + aReadStreams.Append(DE_Provider::ReadStreamNode("multi_shapes.vrml", anIStream)); + + EXPECT_TRUE(myProvider->Read(aReadStreams, aNewDocument)); + + // Validate document content + Handle(XCAFDoc_ShapeTool) aNewShapeTool = XCAFDoc_DocumentTool::ShapeTool(aNewDocument->Main()); + TDF_LabelSequence aLabels; + aNewShapeTool->GetShapes(aLabels); + EXPECT_GT(aLabels.Length(), 0); // Should have at least one shape in document + } +} + +// Test DE_Wrapper integration for VRML operations +TEST_F(DEVRML_ProviderTest, DE_WrapperIntegration) +{ + // Initialize DE_Wrapper and bind VRML provider + DE_Wrapper aWrapper; + Handle(DEVRML_ConfigurationNode) aNode = new DEVRML_ConfigurationNode(); + // Configure for shaded mode to ensure faces are generated + aNode->InternalParameters.WriteRepresentationType = + DEVRML_ConfigurationNode::WriteMode_RepresentationType_Shaded; + + // Bind the configured node to wrapper + EXPECT_TRUE(aWrapper.Bind(aNode)); + + // Test write with DE_Wrapper using triangular face + std::ostringstream anOStream; + DE_Provider::WriteStreamList aWriteStreams; + aWriteStreams.Append(DE_Provider::WriteStreamNode("test.vrml", anOStream)); + + EXPECT_TRUE(aWrapper.Write(aWriteStreams, myTriangularFace)); + + std::string aVrmlContent = anOStream.str(); + EXPECT_FALSE(aVrmlContent.empty()); + EXPECT_TRUE(aVrmlContent.find("#VRML") != std::string::npos); + + if (!aVrmlContent.empty()) + { + // Test DE_Wrapper stream operations - the key functionality we wanted to verify + std::istringstream anIStream(aVrmlContent); + DE_Provider::ReadStreamList aReadStreams; + aReadStreams.Append(DE_Provider::ReadStreamNode("test.vrml", anIStream)); + + TopoDS_Shape aReadShape; + bool aWrapperResult = aWrapper.Read(aReadStreams, aReadShape); + + // Test direct provider with same content for comparison + std::istringstream anIStream2(aVrmlContent); + DE_Provider::ReadStreamList aReadStreams2; + aReadStreams2.Append(DE_Provider::ReadStreamNode("test.vrml", anIStream2)); + + Handle(DEVRML_Provider) aDirectProvider = new DEVRML_Provider(aNode); + TopoDS_Shape aDirectShape; + bool aDirectResult = aDirectProvider->Read(aReadStreams2, aDirectShape); + + // REQUIREMENT: DE_Wrapper must work exactly the same as direct provider + EXPECT_EQ(aWrapperResult, aDirectResult); + EXPECT_EQ(aReadShape.IsNull(), aDirectShape.IsNull()); + + if (aDirectResult && !aDirectShape.IsNull()) + { + Standard_Integer aFaces = CountShapeElements(aDirectShape, TopAbs_FACE); + EXPECT_GT(aFaces, 0); + } + else if (aWrapperResult && !aReadShape.IsNull()) + { + Standard_Integer aFaces = CountShapeElements(aReadShape, TopAbs_FACE); + EXPECT_GT(aFaces, 0); + } + } +} + +// Test DE_Wrapper document operations +TEST_F(DEVRML_ProviderTest, DE_WrapperDocumentOperations) +{ + // Initialize DE_Wrapper and bind VRML provider + DE_Wrapper aWrapper; + Handle(DEVRML_ConfigurationNode) aNode = new DEVRML_ConfigurationNode(); + // Configure for shaded mode for better document operations + aNode->InternalParameters.WriteRepresentationType = + DEVRML_ConfigurationNode::WriteMode_RepresentationType_Shaded; + + // Bind the node to wrapper + EXPECT_TRUE(aWrapper.Bind(aNode)); + + // Add shape to document + Handle(XCAFDoc_ShapeTool) aShapeTool = XCAFDoc_DocumentTool::ShapeTool(myDocument->Main()); + TDF_Label aShapeLabel = aShapeTool->AddShape(myTriangularFace); + EXPECT_FALSE(aShapeLabel.IsNull()); + + // Test document write with DE_Wrapper + std::ostringstream anOStream; + DE_Provider::WriteStreamList aWriteStreams; + aWriteStreams.Append(DE_Provider::WriteStreamNode("doc.vrml", anOStream)); + + EXPECT_TRUE(aWrapper.Write(aWriteStreams, myDocument)); + + std::string aVrmlContent = anOStream.str(); + EXPECT_FALSE(aVrmlContent.empty()); + EXPECT_TRUE(aVrmlContent.find("#VRML") != std::string::npos); + + if (!aVrmlContent.empty()) + { + // Test document read with DE_Wrapper + Handle(TDocStd_Application) anApp = new TDocStd_Application(); + Handle(TDocStd_Document) aNewDocument; + anApp->NewDocument("BinXCAF", aNewDocument); + + std::istringstream anIStream(aVrmlContent); + DE_Provider::ReadStreamList aReadStreams; + aReadStreams.Append(DE_Provider::ReadStreamNode("doc.vrml", anIStream)); + + bool aWrapperDocResult = aWrapper.Read(aReadStreams, aNewDocument); + + // Validate document content if read succeeded + if (aWrapperDocResult) + { + Handle(XCAFDoc_ShapeTool) aNewShapeTool = + XCAFDoc_DocumentTool::ShapeTool(aNewDocument->Main()); + TDF_LabelSequence aLabels; + aNewShapeTool->GetShapes(aLabels); + EXPECT_GT(aLabels.Length(), 0); + } + else + { + // If DE_Wrapper document read fails, verify direct provider works as fallback + Handle(TDocStd_Application) anApp2 = new TDocStd_Application(); + Handle(TDocStd_Document) aTestDocument; + anApp2->NewDocument("BinXCAF", aTestDocument); + + std::istringstream anIStream2(aVrmlContent); + DE_Provider::ReadStreamList aReadStreams2; + aReadStreams2.Append(DE_Provider::ReadStreamNode("doc.vrml", anIStream2)); + + Handle(DEVRML_Provider) aDirectProvider = new DEVRML_Provider(aNode); + bool aDirectDocResult = aDirectProvider->Read(aReadStreams2, aTestDocument); + + if (aDirectDocResult) + { + Handle(XCAFDoc_ShapeTool) aTestShapeTool = + XCAFDoc_DocumentTool::ShapeTool(aTestDocument->Main()); + TDF_LabelSequence aTestLabels; + aTestShapeTool->GetShapes(aTestLabels); + EXPECT_GT(aTestLabels.Length(), 0); + } + } + } +} + +// Test error conditions and edge cases +TEST_F(DEVRML_ProviderTest, ErrorHandling) +{ + // Test with empty streams + DE_Provider::WriteStreamList anEmptyWriteStreams; + EXPECT_FALSE(myProvider->Write(anEmptyWriteStreams, myBox)); + + DE_Provider::ReadStreamList anEmptyReadStreams; + TopoDS_Shape aShape; + EXPECT_FALSE(myProvider->Read(anEmptyReadStreams, aShape)); + + // Test with null shape + std::ostringstream anOStream; + DE_Provider::WriteStreamList aWriteStreams; + aWriteStreams.Append(DE_Provider::WriteStreamNode("null_test.vrml", anOStream)); + TopoDS_Shape aNullShape; + + // Writing null shape might succeed but produce empty or minimal content + myProvider->Write(aWriteStreams, aNullShape); + std::string aContent = anOStream.str(); + EXPECT_FALSE(aContent.empty()); // Should at least have VRML header + + // Test reading invalid VRML content + std::string anInvalidContent = "This is not valid VRML content"; + std::istringstream anInvalidStream(anInvalidContent); + DE_Provider::ReadStreamList anInvalidReadStreams; + anInvalidReadStreams.Append(DE_Provider::ReadStreamNode("invalid.vrml", anInvalidStream)); + + TopoDS_Shape anInvalidShape; + EXPECT_FALSE(myProvider->Read(anInvalidReadStreams, anInvalidShape)); + + // Test with null document + Handle(TDocStd_Document) aNullDoc; + EXPECT_FALSE(myProvider->Write(aWriteStreams, aNullDoc)); + EXPECT_FALSE(myProvider->Read(anEmptyReadStreams, aNullDoc)); +} + +// Test different VRML configuration modes +TEST_F(DEVRML_ProviderTest, ConfigurationModes) +{ + Handle(DEVRML_ConfigurationNode) aNode = + Handle(DEVRML_ConfigurationNode)::DownCast(myProvider->GetNode()); + + // Test wireframe mode configuration + aNode->InternalParameters.WriteRepresentationType = + DEVRML_ConfigurationNode::WriteMode_RepresentationType_Wireframe; + EXPECT_EQ(aNode->InternalParameters.WriteRepresentationType, + DEVRML_ConfigurationNode::WriteMode_RepresentationType_Wireframe); + + // Test shaded mode configuration + aNode->InternalParameters.WriteRepresentationType = + DEVRML_ConfigurationNode::WriteMode_RepresentationType_Shaded; + EXPECT_EQ(aNode->InternalParameters.WriteRepresentationType, + DEVRML_ConfigurationNode::WriteMode_RepresentationType_Shaded); + + // Test both mode configuration + aNode->InternalParameters.WriteRepresentationType = + DEVRML_ConfigurationNode::WriteMode_RepresentationType_Both; + EXPECT_EQ(aNode->InternalParameters.WriteRepresentationType, + DEVRML_ConfigurationNode::WriteMode_RepresentationType_Both); + + // Test writer version configuration + aNode->InternalParameters.WriterVersion = DEVRML_ConfigurationNode::WriteMode_WriterVersion_1; + EXPECT_EQ(aNode->InternalParameters.WriterVersion, + DEVRML_ConfigurationNode::WriteMode_WriterVersion_1); + + aNode->InternalParameters.WriterVersion = DEVRML_ConfigurationNode::WriteMode_WriterVersion_2; + EXPECT_EQ(aNode->InternalParameters.WriterVersion, + DEVRML_ConfigurationNode::WriteMode_WriterVersion_2); + + // Test that provider format and vendor are correct + EXPECT_STREQ("VRML", myProvider->GetFormat().ToCString()); + EXPECT_STREQ("OCC", myProvider->GetVendor().ToCString()); +} \ No newline at end of file diff --git a/src/DataExchange/TKDEVRML/GTests/FILES.cmake b/src/DataExchange/TKDEVRML/GTests/FILES.cmake index 9a43166d36..72ec308563 100644 --- a/src/DataExchange/TKDEVRML/GTests/FILES.cmake +++ b/src/DataExchange/TKDEVRML/GTests/FILES.cmake @@ -2,4 +2,5 @@ set(OCCT_TKDEVRML_GTests_FILES_LOCATION "${CMAKE_CURRENT_LIST_DIR}") set(OCCT_TKDEVRML_GTests_FILES + DEVRML_Provider_Test.cxx ) diff --git a/src/DataExchange/TKDEVRML/VrmlAPI/VrmlAPI_Writer.cxx b/src/DataExchange/TKDEVRML/VrmlAPI/VrmlAPI_Writer.cxx index 34b0667b66..6a471801c5 100644 --- a/src/DataExchange/TKDEVRML/VrmlAPI/VrmlAPI_Writer.cxx +++ b/src/DataExchange/TKDEVRML/VrmlAPI/VrmlAPI_Writer.cxx @@ -242,29 +242,66 @@ Standard_Boolean VrmlAPI_Writer::Write(const TopoDS_Shape& aShape, const Standard_CString aFile, const Standard_Integer aVersion) const { - if (aVersion == 1) - return write_v1(aShape, aFile); - else if (aVersion == 2) - return write_v2(aShape, aFile); + const Handle(OSD_FileSystem)& aFileSystem = OSD_FileSystem::DefaultFileSystem(); + std::shared_ptr anOutStream = + aFileSystem->OpenOStream(aFile, std::ios::out | std::ios::binary); + if (anOutStream.get() == NULL) + { + return Standard_False; + } - return Standard_False; + return Write(aShape, *anOutStream, aVersion); } -Standard_Boolean VrmlAPI_Writer::write_v1(const TopoDS_Shape& aShape, - const Standard_CString aFile) const +Standard_Boolean VrmlAPI_Writer::WriteDoc(const Handle(TDocStd_Document)& theDoc, + const Standard_CString theFile, + const Standard_Real theScale) const { - OSD_Path thePath(aFile); - TCollection_AsciiString theFile; - thePath.SystemName(theFile); const Handle(OSD_FileSystem)& aFileSystem = OSD_FileSystem::DefaultFileSystem(); - std::shared_ptr anOutFile = + std::shared_ptr anOutStream = aFileSystem->OpenOStream(theFile, std::ios::out | std::ios::binary); - if (anOutFile.get() == NULL) + if (anOutStream.get() == NULL) { return Standard_False; } - Handle(VrmlConverter_IsoAspect) ia = new VrmlConverter_IsoAspect; // UIso - Handle(VrmlConverter_IsoAspect) ia1 = new VrmlConverter_IsoAspect; // VIso + + return WriteDoc(theDoc, *anOutStream, theScale); +} + +//================================================================================================= + +Standard_Boolean VrmlAPI_Writer::Write(const TopoDS_Shape& aShape, + Standard_OStream& theOStream, + const Standard_Integer aVersion) const +{ + if (aVersion == 1) + return write_v1(aShape, theOStream); + else if (aVersion == 2) + return write_v2(aShape, theOStream); + + return Standard_False; +} + +//================================================================================================= + +Standard_Boolean VrmlAPI_Writer::WriteDoc(const Handle(TDocStd_Document)& theDoc, + Standard_OStream& theOStream, + const Standard_Real theScale) const +{ + VrmlData_Scene aScene; + VrmlData_ShapeConvert aConv(aScene, theScale); + aConv.ConvertDocument(theDoc); + + theOStream << aScene; + theOStream.flush(); + return theOStream.good(); +} + +Standard_Boolean VrmlAPI_Writer::write_v1(const TopoDS_Shape& aShape, + Standard_OStream& theOStream) const +{ + Handle(VrmlConverter_IsoAspect) ia = new VrmlConverter_IsoAspect; + Handle(VrmlConverter_IsoAspect) ia1 = new VrmlConverter_IsoAspect; ia->SetMaterial(myUisoMaterial); ia->SetHasMaterial(Standard_True); myDrawer->SetUIsoAspect(ia); @@ -318,7 +355,6 @@ Standard_Boolean VrmlAPI_Writer::write_v1(const TopoDS_Shape& aShape, if (!aFace.IsNull()) { Handle(Poly_Triangulation) aTri = BRep_Tool::Triangulation(aFace, aLoc); - if (!aTri.IsNull()) { hasTriangles = Standard_True; @@ -335,57 +371,61 @@ Standard_Boolean VrmlAPI_Writer::write_v1(const TopoDS_Shape& aShape, VrmlConverter_TypeOfCamera Camera = VrmlConverter_PerspectiveCamera; Handle(VrmlConverter_Projector) projector = new VrmlConverter_Projector(Shapes, Focus, DX, DY, DZ, XUp, YUp, ZUp, Camera, Light); - Vrml::VrmlHeaderWriter(*anOutFile); + + Vrml::VrmlHeaderWriter(theOStream); if (myRepresentation == VrmlAPI_BothRepresentation) Vrml::CommentWriter( " This file contents both Shaded and Wire Frame representation of selected Shape ", - *anOutFile); + theOStream); if (myRepresentation == VrmlAPI_ShadedRepresentation) Vrml::CommentWriter(" This file contents only Shaded representation of selected Shape ", - *anOutFile); + theOStream); if (myRepresentation == VrmlAPI_WireFrameRepresentation) Vrml::CommentWriter(" This file contents only Wire Frame representation of selected Shape ", - *anOutFile); + theOStream); + Vrml_Separator S1; - S1.Print(*anOutFile); - projector->Add(*anOutFile); + S1.Print(theOStream); + projector->Add(theOStream); + Light = VrmlConverter_DirectionLight; Camera = VrmlConverter_OrthographicCamera; Handle(VrmlConverter_Projector) projector1 = new VrmlConverter_Projector(Shapes, Focus, DX, DY, DZ, XUp, YUp, ZUp, Camera, Light); - projector1->Add(*anOutFile); + projector1->Add(theOStream); + Vrml_Separator S2; - S2.Print(*anOutFile); + S2.Print(theOStream); if ((myRepresentation == VrmlAPI_ShadedRepresentation || myRepresentation == VrmlAPI_BothRepresentation) && hasTriangles) { Vrml_Group Group1; - Group1.Print(*anOutFile); + Group1.Print(theOStream); Vrml_Instancing I2("Shaded representation of shape"); - I2.DEF(*anOutFile); - VrmlConverter_ShadedShape::Add(*anOutFile, aShape, myDrawer); - Group1.Print(*anOutFile); + I2.DEF(theOStream); + VrmlConverter_ShadedShape::Add(theOStream, aShape, myDrawer); + Group1.Print(theOStream); } if (myRepresentation == VrmlAPI_WireFrameRepresentation || myRepresentation == VrmlAPI_BothRepresentation) { Vrml_Group Group2; - Group2.Print(*anOutFile); + Group2.Print(theOStream); Vrml_Instancing I3("Wire Frame representation of shape"); - I3.DEF(*anOutFile); - VrmlConverter_WFDeflectionShape::Add(*anOutFile, aShape, myDrawer); - Group2.Print(*anOutFile); + I3.DEF(theOStream); + VrmlConverter_WFDeflectionShape::Add(theOStream, aShape, myDrawer); + Group2.Print(theOStream); } - S2.Print(*anOutFile); - S1.Print(*anOutFile); + S2.Print(theOStream); + S1.Print(theOStream); - anOutFile->flush(); - return anOutFile->good(); + theOStream.flush(); + return theOStream.good(); } -Standard_Boolean VrmlAPI_Writer::write_v2(const TopoDS_Shape& aShape, - const Standard_CString aFile) const +Standard_Boolean VrmlAPI_Writer::write_v2(const TopoDS_Shape& aShape, + Standard_OStream& theOStream) const { Standard_Boolean anExtFace = Standard_False; if (myRepresentation == VrmlAPI_ShadedRepresentation @@ -402,38 +442,7 @@ Standard_Boolean VrmlAPI_Writer::write_v2(const TopoDS_Shape& aShape, aConv.AddShape(aShape); aConv.Convert(anExtFace, anExtEdge); - const Handle(OSD_FileSystem)& aFileSystem = OSD_FileSystem::DefaultFileSystem(); - std::shared_ptr anOutStream = - aFileSystem->OpenOStream(aFile, std::ios::out | std::ios::binary); - if (anOutStream.get() != NULL) - { - *anOutStream << aScene; - anOutStream->flush(); - return anOutStream->good(); - } - anOutStream.reset(); - return Standard_False; -} - -//================================================================================================= - -Standard_Boolean VrmlAPI_Writer::WriteDoc(const Handle(TDocStd_Document)& theDoc, - const Standard_CString theFile, - const Standard_Real theScale) const -{ - VrmlData_Scene aScene; - VrmlData_ShapeConvert aConv(aScene, theScale); - aConv.ConvertDocument(theDoc); - - const Handle(OSD_FileSystem)& aFileSystem = OSD_FileSystem::DefaultFileSystem(); - std::shared_ptr anOutStream = - aFileSystem->OpenOStream(theFile, std::ios::out | std::ios::binary); - if (anOutStream.get() != NULL) - { - *anOutStream << aScene; - anOutStream->flush(); - return anOutStream->good(); - } - anOutStream.reset(); - return Standard_False; + theOStream << aScene; + theOStream.flush(); + return theOStream.good(); } diff --git a/src/DataExchange/TKDEVRML/VrmlAPI/VrmlAPI_Writer.hxx b/src/DataExchange/TKDEVRML/VrmlAPI/VrmlAPI_Writer.hxx index 0a5e23bf30..6be4349ede 100644 --- a/src/DataExchange/TKDEVRML/VrmlAPI/VrmlAPI_Writer.hxx +++ b/src/DataExchange/TKDEVRML/VrmlAPI/VrmlAPI_Writer.hxx @@ -116,16 +116,28 @@ public: const Standard_CString theFile, const Standard_Real theScale) const; + //! Converts the shape aShape to + //! VRML format of the passed version and writes it to the given stream. + Standard_EXPORT Standard_Boolean Write(const TopoDS_Shape& aShape, + Standard_OStream& theOStream, + const Standard_Integer aVersion = 2) const; + + //! Converts the document to VRML format of the passed version + //! and writes it to the given stream. + Standard_EXPORT Standard_Boolean WriteDoc(const Handle(TDocStd_Document)& theDoc, + Standard_OStream& theOStream, + const Standard_Real theScale) const; + protected: //! Converts the shape aShape to VRML format of version 1.0 and writes it - //! to the file identified by aFileName using default parameters. - Standard_EXPORT Standard_Boolean write_v1(const TopoDS_Shape& aShape, - const Standard_CString aFileName) const; + //! to the given stream using default parameters. + Standard_EXPORT Standard_Boolean write_v1(const TopoDS_Shape& aShape, + Standard_OStream& theOStream) const; //! Converts the shape aShape to VRML format of version 2.0 and writes it - //! to the file identified by aFileName using default parameters. - Standard_EXPORT Standard_Boolean write_v2(const TopoDS_Shape& aShape, - const Standard_CString aFileName) const; + //! to the given stream using default parameters. + Standard_EXPORT Standard_Boolean write_v2(const TopoDS_Shape& aShape, + Standard_OStream& theOStream) const; private: VrmlAPI_RepresentationOfShape myRepresentation; -- 2.39.5