From c765cb4bd2db528202bc4cb7f8c9211316b47f6e Mon Sep 17 00:00:00 2001 From: Pasukhin Dmitry Date: Fri, 3 Apr 2026 00:51:20 +0100 Subject: [PATCH] Modeling - New BRrep Graph representation (#1166) Implement new representation of topology and BRep into OCCT. New foundation represent 2 levels: BRepGraph - public interfaces which making the topology graph representation BRepGraphInc - internal structure which represent topology as an incident tables. The foundation provides basic logic for conversion from TopoDS_Shape to BRepGraph and back. The foundation provides the iteration and exploring interfaces to travel in both directions on any level of topology. The internal id type of inc tables is 'int', which is not for long term and can be updated, better to use typed id aliases, even for the iterations, they provide all necessary operators. The access to the BRepGraph is done using multiple View classes available by methods from main class. The extensions on graphs is possible with 'Layer' and 'Cache', where first is persistent, second - temporary, Some basic operations on graph also provided for compact, analyze and copy. --- .../TKernel/GTests/FILES.cmake | 1 + .../GTests/NCollection_ForwardRange_Test.cxx | 516 ++++ .../TKernel/NCollection/FILES.cmake | 1 + .../NCollection/NCollection_ForwardRange.hxx | 292 ++ .../TKBRep/BRepGraph/BRepGraph.cxx | 863 ++++++ .../TKBRep/BRepGraph/BRepGraph.hxx | 270 ++ .../TKBRep/BRepGraph/BRepGraph_Builder.cxx | 440 +++ .../TKBRep/BRepGraph/BRepGraph_Builder.hxx | 88 + .../BRepGraph/BRepGraph_BuilderView.cxx | 2379 +++++++++++++++++ .../BRepGraph/BRepGraph_BuilderView.hxx | 472 ++++ .../TKBRep/BRepGraph/BRepGraph_CacheView.cxx | 139 + .../TKBRep/BRepGraph/BRepGraph_CacheView.hxx | 116 + .../BRepGraph/BRepGraph_ChildExplorer.cxx | 890 ++++++ .../BRepGraph/BRepGraph_ChildExplorer.hxx | 247 ++ .../TKBRep/BRepGraph/BRepGraph_Compact.cxx | 627 +++++ .../TKBRep/BRepGraph/BRepGraph_Compact.hxx | 74 + .../TKBRep/BRepGraph/BRepGraph_Copy.cxx | 629 +++++ .../TKBRep/BRepGraph/BRepGraph_Copy.hxx | 80 + .../TKBRep/BRepGraph/BRepGraph_Data.hxx | 142 + .../BRepGraph/BRepGraph_Deduplicate.cxx | 787 ++++++ .../BRepGraph/BRepGraph_Deduplicate.hxx | 84 + .../BRepGraph/BRepGraph_DeferredScope.hxx | 79 + .../BRepGraph/BRepGraph_DefsIterator.hxx | 556 ++++ .../TKBRep/BRepGraph/BRepGraph_History.cxx | 278 ++ .../TKBRep/BRepGraph/BRepGraph_History.hxx | 114 + .../BRepGraph/BRepGraph_HistoryRecord.hxx | 49 + .../TKBRep/BRepGraph/BRepGraph_Iterator.hxx | 290 ++ .../TKBRep/BRepGraph/BRepGraph_Layer.cxx | 34 + .../TKBRep/BRepGraph/BRepGraph_Layer.hxx | 119 + .../BRepGraph/BRepGraph_LayerRegistry.cxx | 170 ++ .../BRepGraph/BRepGraph_LayerRegistry.hxx | 105 + .../TKBRep/BRepGraph/BRepGraph_MutGuard.hxx | 147 + .../TKBRep/BRepGraph/BRepGraph_NodeId.hxx | 277 ++ .../BRepGraph/BRepGraph_ParallelPolicy.hxx | 90 + .../TKBRep/BRepGraph/BRepGraph_ParamLayer.cxx | 750 ++++++ .../TKBRep/BRepGraph/BRepGraph_ParamLayer.hxx | 150 ++ .../BRepGraph/BRepGraph_ParentExplorer.cxx | 1323 +++++++++ .../BRepGraph/BRepGraph_ParentExplorer.hxx | 230 ++ .../TKBRep/BRepGraph/BRepGraph_RefId.hxx | 234 ++ .../TKBRep/BRepGraph/BRepGraph_RefUID.hxx | 105 + .../BRepGraph/BRepGraph_RefsIterator.hxx | 420 +++ .../TKBRep/BRepGraph/BRepGraph_RefsView.cxx | 305 +++ .../TKBRep/BRepGraph/BRepGraph_RefsView.hxx | 297 ++ .../BRepGraph/BRepGraph_RegularityLayer.cxx | 502 ++++ .../BRepGraph/BRepGraph_RegularityLayer.hxx | 99 + .../TKBRep/BRepGraph/BRepGraph_RepId.hxx | 298 +++ .../TKBRep/BRepGraph/BRepGraph_ShapesView.cxx | 278 ++ .../TKBRep/BRepGraph/BRepGraph_ShapesView.hxx | 103 + .../TKBRep/BRepGraph/BRepGraph_Tool.cxx | 656 +++++ .../TKBRep/BRepGraph/BRepGraph_Tool.hxx | 514 ++++ .../TKBRep/BRepGraph/BRepGraph_TopoView.cxx | 1318 +++++++++ .../TKBRep/BRepGraph/BRepGraph_TopoView.hxx | 561 ++++ .../TKBRep/BRepGraph/BRepGraph_Transform.cxx | 169 ++ .../TKBRep/BRepGraph/BRepGraph_Transform.hxx | 80 + .../BRepGraph/BRepGraph_TransientCache.cxx | 473 ++++ .../BRepGraph/BRepGraph_TransientCache.hxx | 364 +++ .../TKBRep/BRepGraph/BRepGraph_UID.hxx | 123 + .../TKBRep/BRepGraph/BRepGraph_UIDsView.cxx | 309 +++ .../TKBRep/BRepGraph/BRepGraph_UIDsView.hxx | 107 + .../TKBRep/BRepGraph/BRepGraph_Validate.cxx | 1075 ++++++++ .../TKBRep/BRepGraph/BRepGraph_Validate.hxx | 165 ++ .../BRepGraph/BRepGraph_VersionStamp.cxx | 68 + .../BRepGraph/BRepGraph_VersionStamp.hxx | 190 ++ .../BRepGraph/BRepGraph_WireExplorer.hxx | 182 ++ src/ModelingData/TKBRep/BRepGraph/FILES.cmake | 64 + src/ModelingData/TKBRep/BRepGraph/README.md | 457 ++++ .../BRepGraphInc/BRepGraphInc_Definition.hxx | 297 ++ .../BRepGraphInc/BRepGraphInc_Populate.cxx | 2166 +++++++++++++++ .../BRepGraphInc/BRepGraphInc_Populate.hxx | 101 + .../BRepGraphInc/BRepGraphInc_Reconstruct.cxx | 864 ++++++ .../BRepGraphInc/BRepGraphInc_Reconstruct.hxx | 118 + .../BRepGraphInc/BRepGraphInc_Reference.hxx | 119 + .../BRepGraphInc_Representation.hxx | 90 + .../BRepGraphInc_ReverseIndex.cxx | 1267 +++++++++ .../BRepGraphInc_ReverseIndex.hxx | 499 ++++ .../BRepGraphInc/BRepGraphInc_Storage.cxx | 968 +++++++ .../BRepGraphInc/BRepGraphInc_Storage.hxx | 875 ++++++ .../BRepGraphInc/BRepGraphInc_Usage.hxx | 92 + .../TKBRep/BRepGraphInc/FILES.cmake | 16 + .../TKBRep/BRepGraphInc/README.md | 357 +++ .../TKBRep/GTests/BRepGraphInc_Test.cxx | 1137 ++++++++ .../TKBRep/GTests/BRepGraph_Assembly_Test.cxx | 978 +++++++ .../GTests/BRepGraph_Benchmark_Test.cxx | 210 ++ .../TKBRep/GTests/BRepGraph_Build_Test.cxx | 1341 ++++++++++ .../TKBRep/GTests/BRepGraph_Builder_Test.cxx | 902 +++++++ .../BRepGraph_CacheKindRegistry_Test.cxx | 61 + .../GTests/BRepGraph_ChildExplorer_Test.cxx | 841 ++++++ .../TKBRep/GTests/BRepGraph_Compact_Test.cxx | 354 +++ .../GTests/BRepGraph_Convenience_Test.cxx | 240 ++ .../TKBRep/GTests/BRepGraph_Copy_Test.cxx | 314 +++ .../GTests/BRepGraph_Deduplicate_Test.cxx | 1849 +++++++++++++ .../BRepGraph_DeferredInvalidation_Test.cxx | 336 +++ .../GTests/BRepGraph_DefsIterator_Test.cxx | 242 ++ .../GTests/BRepGraph_EdgeCases_Test.cxx | 260 ++ .../TKBRep/GTests/BRepGraph_EventBus_Test.cxx | 515 ++++ .../TKBRep/GTests/BRepGraph_Geometry_Test.cxx | 943 +++++++ .../TKBRep/GTests/BRepGraph_History_Test.cxx | 540 ++++ .../GTests/BRepGraph_MutationGen_Test.cxx | 275 ++ .../TKBRep/GTests/BRepGraph_NodeId_Test.cxx | 173 ++ .../GTests/BRepGraph_ParentExplorer_Test.cxx | 242 ++ .../TKBRep/GTests/BRepGraph_Polygon_Test.cxx | 459 ++++ .../GTests/BRepGraph_Reconstruct_Test.cxx | 610 +++++ .../TKBRep/GTests/BRepGraph_RefId_Test.cxx | 494 ++++ .../TKBRep/GTests/BRepGraph_RefTestTools.hxx | 392 +++ .../GTests/BRepGraph_RefsIterator_Test.cxx | 217 ++ .../TKBRep/GTests/BRepGraph_Sharing_Test.cxx | 333 +++ .../TKBRep/GTests/BRepGraph_Test.cxx | 2191 +++++++++++++++ .../GTests/BRepGraph_Transform_Test.cxx | 187 ++ .../TKBRep/GTests/BRepGraph_Validate_Test.cxx | 637 +++++ .../GTests/BRepGraph_VersionStamp_Test.cxx | 237 ++ .../TKBRep/GTests/BRepGraph_Views_Test.cxx | 683 +++++ src/ModelingData/TKBRep/GTests/FILES.cmake | 30 + src/ModelingData/TKBRep/PACKAGES.cmake | 2 + .../TKBRep/TopExp/TopExp_Explorer.hxx | 11 + .../TKBRep/TopoDS/TopoDS_Iterator.hxx | 13 +- .../GTests/ExtremaPC_Comparison_Test.cxx | 396 --- .../GTests/ExtremaPC_SearchMode_Test.cxx | 177 -- 117 files changed, 49491 insertions(+), 574 deletions(-) create mode 100644 src/FoundationClasses/TKernel/GTests/NCollection_ForwardRange_Test.cxx create mode 100644 src/FoundationClasses/TKernel/NCollection/NCollection_ForwardRange.hxx create mode 100644 src/ModelingData/TKBRep/BRepGraph/BRepGraph.cxx create mode 100644 src/ModelingData/TKBRep/BRepGraph/BRepGraph.hxx create mode 100644 src/ModelingData/TKBRep/BRepGraph/BRepGraph_Builder.cxx create mode 100644 src/ModelingData/TKBRep/BRepGraph/BRepGraph_Builder.hxx create mode 100644 src/ModelingData/TKBRep/BRepGraph/BRepGraph_BuilderView.cxx create mode 100644 src/ModelingData/TKBRep/BRepGraph/BRepGraph_BuilderView.hxx create mode 100644 src/ModelingData/TKBRep/BRepGraph/BRepGraph_CacheView.cxx create mode 100644 src/ModelingData/TKBRep/BRepGraph/BRepGraph_CacheView.hxx create mode 100644 src/ModelingData/TKBRep/BRepGraph/BRepGraph_ChildExplorer.cxx create mode 100644 src/ModelingData/TKBRep/BRepGraph/BRepGraph_ChildExplorer.hxx create mode 100644 src/ModelingData/TKBRep/BRepGraph/BRepGraph_Compact.cxx create mode 100644 src/ModelingData/TKBRep/BRepGraph/BRepGraph_Compact.hxx create mode 100644 src/ModelingData/TKBRep/BRepGraph/BRepGraph_Copy.cxx create mode 100644 src/ModelingData/TKBRep/BRepGraph/BRepGraph_Copy.hxx create mode 100644 src/ModelingData/TKBRep/BRepGraph/BRepGraph_Data.hxx create mode 100644 src/ModelingData/TKBRep/BRepGraph/BRepGraph_Deduplicate.cxx create mode 100644 src/ModelingData/TKBRep/BRepGraph/BRepGraph_Deduplicate.hxx create mode 100644 src/ModelingData/TKBRep/BRepGraph/BRepGraph_DeferredScope.hxx create mode 100644 src/ModelingData/TKBRep/BRepGraph/BRepGraph_DefsIterator.hxx create mode 100644 src/ModelingData/TKBRep/BRepGraph/BRepGraph_History.cxx create mode 100644 src/ModelingData/TKBRep/BRepGraph/BRepGraph_History.hxx create mode 100644 src/ModelingData/TKBRep/BRepGraph/BRepGraph_HistoryRecord.hxx create mode 100644 src/ModelingData/TKBRep/BRepGraph/BRepGraph_Iterator.hxx create mode 100644 src/ModelingData/TKBRep/BRepGraph/BRepGraph_Layer.cxx create mode 100644 src/ModelingData/TKBRep/BRepGraph/BRepGraph_Layer.hxx create mode 100644 src/ModelingData/TKBRep/BRepGraph/BRepGraph_LayerRegistry.cxx create mode 100644 src/ModelingData/TKBRep/BRepGraph/BRepGraph_LayerRegistry.hxx create mode 100644 src/ModelingData/TKBRep/BRepGraph/BRepGraph_MutGuard.hxx create mode 100644 src/ModelingData/TKBRep/BRepGraph/BRepGraph_NodeId.hxx create mode 100644 src/ModelingData/TKBRep/BRepGraph/BRepGraph_ParallelPolicy.hxx create mode 100644 src/ModelingData/TKBRep/BRepGraph/BRepGraph_ParamLayer.cxx create mode 100644 src/ModelingData/TKBRep/BRepGraph/BRepGraph_ParamLayer.hxx create mode 100644 src/ModelingData/TKBRep/BRepGraph/BRepGraph_ParentExplorer.cxx create mode 100644 src/ModelingData/TKBRep/BRepGraph/BRepGraph_ParentExplorer.hxx create mode 100644 src/ModelingData/TKBRep/BRepGraph/BRepGraph_RefId.hxx create mode 100644 src/ModelingData/TKBRep/BRepGraph/BRepGraph_RefUID.hxx create mode 100644 src/ModelingData/TKBRep/BRepGraph/BRepGraph_RefsIterator.hxx create mode 100644 src/ModelingData/TKBRep/BRepGraph/BRepGraph_RefsView.cxx create mode 100644 src/ModelingData/TKBRep/BRepGraph/BRepGraph_RefsView.hxx create mode 100644 src/ModelingData/TKBRep/BRepGraph/BRepGraph_RegularityLayer.cxx create mode 100644 src/ModelingData/TKBRep/BRepGraph/BRepGraph_RegularityLayer.hxx create mode 100644 src/ModelingData/TKBRep/BRepGraph/BRepGraph_RepId.hxx create mode 100644 src/ModelingData/TKBRep/BRepGraph/BRepGraph_ShapesView.cxx create mode 100644 src/ModelingData/TKBRep/BRepGraph/BRepGraph_ShapesView.hxx create mode 100644 src/ModelingData/TKBRep/BRepGraph/BRepGraph_Tool.cxx create mode 100644 src/ModelingData/TKBRep/BRepGraph/BRepGraph_Tool.hxx create mode 100644 src/ModelingData/TKBRep/BRepGraph/BRepGraph_TopoView.cxx create mode 100644 src/ModelingData/TKBRep/BRepGraph/BRepGraph_TopoView.hxx create mode 100644 src/ModelingData/TKBRep/BRepGraph/BRepGraph_Transform.cxx create mode 100644 src/ModelingData/TKBRep/BRepGraph/BRepGraph_Transform.hxx create mode 100644 src/ModelingData/TKBRep/BRepGraph/BRepGraph_TransientCache.cxx create mode 100644 src/ModelingData/TKBRep/BRepGraph/BRepGraph_TransientCache.hxx create mode 100644 src/ModelingData/TKBRep/BRepGraph/BRepGraph_UID.hxx create mode 100644 src/ModelingData/TKBRep/BRepGraph/BRepGraph_UIDsView.cxx create mode 100644 src/ModelingData/TKBRep/BRepGraph/BRepGraph_UIDsView.hxx create mode 100644 src/ModelingData/TKBRep/BRepGraph/BRepGraph_Validate.cxx create mode 100644 src/ModelingData/TKBRep/BRepGraph/BRepGraph_Validate.hxx create mode 100644 src/ModelingData/TKBRep/BRepGraph/BRepGraph_VersionStamp.cxx create mode 100644 src/ModelingData/TKBRep/BRepGraph/BRepGraph_VersionStamp.hxx create mode 100644 src/ModelingData/TKBRep/BRepGraph/BRepGraph_WireExplorer.hxx create mode 100644 src/ModelingData/TKBRep/BRepGraph/FILES.cmake create mode 100644 src/ModelingData/TKBRep/BRepGraph/README.md create mode 100644 src/ModelingData/TKBRep/BRepGraphInc/BRepGraphInc_Definition.hxx create mode 100644 src/ModelingData/TKBRep/BRepGraphInc/BRepGraphInc_Populate.cxx create mode 100644 src/ModelingData/TKBRep/BRepGraphInc/BRepGraphInc_Populate.hxx create mode 100644 src/ModelingData/TKBRep/BRepGraphInc/BRepGraphInc_Reconstruct.cxx create mode 100644 src/ModelingData/TKBRep/BRepGraphInc/BRepGraphInc_Reconstruct.hxx create mode 100644 src/ModelingData/TKBRep/BRepGraphInc/BRepGraphInc_Reference.hxx create mode 100644 src/ModelingData/TKBRep/BRepGraphInc/BRepGraphInc_Representation.hxx create mode 100644 src/ModelingData/TKBRep/BRepGraphInc/BRepGraphInc_ReverseIndex.cxx create mode 100644 src/ModelingData/TKBRep/BRepGraphInc/BRepGraphInc_ReverseIndex.hxx create mode 100644 src/ModelingData/TKBRep/BRepGraphInc/BRepGraphInc_Storage.cxx create mode 100644 src/ModelingData/TKBRep/BRepGraphInc/BRepGraphInc_Storage.hxx create mode 100644 src/ModelingData/TKBRep/BRepGraphInc/BRepGraphInc_Usage.hxx create mode 100644 src/ModelingData/TKBRep/BRepGraphInc/FILES.cmake create mode 100644 src/ModelingData/TKBRep/BRepGraphInc/README.md create mode 100644 src/ModelingData/TKBRep/GTests/BRepGraphInc_Test.cxx create mode 100644 src/ModelingData/TKBRep/GTests/BRepGraph_Assembly_Test.cxx create mode 100644 src/ModelingData/TKBRep/GTests/BRepGraph_Benchmark_Test.cxx create mode 100644 src/ModelingData/TKBRep/GTests/BRepGraph_Build_Test.cxx create mode 100644 src/ModelingData/TKBRep/GTests/BRepGraph_Builder_Test.cxx create mode 100644 src/ModelingData/TKBRep/GTests/BRepGraph_CacheKindRegistry_Test.cxx create mode 100644 src/ModelingData/TKBRep/GTests/BRepGraph_ChildExplorer_Test.cxx create mode 100644 src/ModelingData/TKBRep/GTests/BRepGraph_Compact_Test.cxx create mode 100644 src/ModelingData/TKBRep/GTests/BRepGraph_Convenience_Test.cxx create mode 100644 src/ModelingData/TKBRep/GTests/BRepGraph_Copy_Test.cxx create mode 100644 src/ModelingData/TKBRep/GTests/BRepGraph_Deduplicate_Test.cxx create mode 100644 src/ModelingData/TKBRep/GTests/BRepGraph_DeferredInvalidation_Test.cxx create mode 100644 src/ModelingData/TKBRep/GTests/BRepGraph_DefsIterator_Test.cxx create mode 100644 src/ModelingData/TKBRep/GTests/BRepGraph_EdgeCases_Test.cxx create mode 100644 src/ModelingData/TKBRep/GTests/BRepGraph_EventBus_Test.cxx create mode 100644 src/ModelingData/TKBRep/GTests/BRepGraph_Geometry_Test.cxx create mode 100644 src/ModelingData/TKBRep/GTests/BRepGraph_History_Test.cxx create mode 100644 src/ModelingData/TKBRep/GTests/BRepGraph_MutationGen_Test.cxx create mode 100644 src/ModelingData/TKBRep/GTests/BRepGraph_NodeId_Test.cxx create mode 100644 src/ModelingData/TKBRep/GTests/BRepGraph_ParentExplorer_Test.cxx create mode 100644 src/ModelingData/TKBRep/GTests/BRepGraph_Polygon_Test.cxx create mode 100644 src/ModelingData/TKBRep/GTests/BRepGraph_Reconstruct_Test.cxx create mode 100644 src/ModelingData/TKBRep/GTests/BRepGraph_RefId_Test.cxx create mode 100644 src/ModelingData/TKBRep/GTests/BRepGraph_RefTestTools.hxx create mode 100644 src/ModelingData/TKBRep/GTests/BRepGraph_RefsIterator_Test.cxx create mode 100644 src/ModelingData/TKBRep/GTests/BRepGraph_Sharing_Test.cxx create mode 100644 src/ModelingData/TKBRep/GTests/BRepGraph_Test.cxx create mode 100644 src/ModelingData/TKBRep/GTests/BRepGraph_Transform_Test.cxx create mode 100644 src/ModelingData/TKBRep/GTests/BRepGraph_Validate_Test.cxx create mode 100644 src/ModelingData/TKBRep/GTests/BRepGraph_VersionStamp_Test.cxx create mode 100644 src/ModelingData/TKBRep/GTests/BRepGraph_Views_Test.cxx diff --git a/src/FoundationClasses/TKernel/GTests/FILES.cmake b/src/FoundationClasses/TKernel/GTests/FILES.cmake index 617169f304..b25afbaaa1 100644 --- a/src/FoundationClasses/TKernel/GTests/FILES.cmake +++ b/src/FoundationClasses/TKernel/GTests/FILES.cmake @@ -13,6 +13,7 @@ set(OCCT_TKernel_GTests_FILES NCollection_DoubleMap_Test.cxx NCollection_FlatDataMap_Test.cxx NCollection_FlatMap_Test.cxx + NCollection_ForwardRange_Test.cxx NCollection_IndexedDataMap_Test.cxx NCollection_IndexedMap_Test.cxx NCollection_KDTree_Test.cxx diff --git a/src/FoundationClasses/TKernel/GTests/NCollection_ForwardRange_Test.cxx b/src/FoundationClasses/TKernel/GTests/NCollection_ForwardRange_Test.cxx new file mode 100644 index 0000000000..8804a09a54 --- /dev/null +++ b/src/FoundationClasses/TKernel/GTests/NCollection_ForwardRange_Test.cxx @@ -0,0 +1,516 @@ +// Copyright (c) 2026 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 + +// --------------------------------------------------------------------------- +// Mock iterators for testing SFINAE detection +// --------------------------------------------------------------------------- + +namespace +{ + +//! Mock iterator with Value() accessor (like TopoDS_Iterator). +class MockValueIterator +{ +public: + MockValueIterator(const int* theData, const int theSize) + : myData(theData), + mySize(theSize) + { + } + + bool More() const { return myIndex < mySize; } + + void Next() { ++myIndex; } + + const int& Value() const { return myData[myIndex]; } + +private: + const int* myData; + int mySize = 0; + int myIndex = 0; +}; + +//! Mock iterator with Current() accessor (like BRepGraph_Iterator). +class MockCurrentIterator +{ +public: + MockCurrentIterator(const int* theData, const int theSize) + : myData(theData), + mySize(theSize) + { + } + + bool More() const { return myIndex < mySize; } + + void Next() { ++myIndex; } + + const int& Current() const { return myData[myIndex]; } + +private: + const int* myData; + int mySize = 0; + int myIndex = 0; +}; + +//! Mock iterator with CurrentId() accessor (like BRepGraph_RefsIterator). +//! Returns by value, not by reference. +class MockCurrentIdIterator +{ +public: + MockCurrentIdIterator(const int* theData, const int theSize) + : myData(theData), + mySize(theSize) + { + } + + bool More() const { return myIndex < mySize; } + + void Next() { ++myIndex; } + + int CurrentId() const { return myData[myIndex]; } + +private: + const int* myData; + int mySize = 0; + int myIndex = 0; +}; + +//! Mock iterator with both Value() and Current() (like TopExp_Explorer). +//! SFINAE should pick Value() due to higher priority. +class MockBothValueAndCurrent +{ +public: + MockBothValueAndCurrent(const int* theData, const int theSize) + : myData(theData), + mySize(theSize) + { + } + + bool More() const { return myIndex < mySize; } + + void Next() { ++myIndex; } + + const int& Value() const { return myData[myIndex]; } + + const int& Current() const { return myData[myIndex]; } + +private: + const int* myData; + int mySize = 0; + int myIndex = 0; +}; + +//! Mock iterator with both Current() and CurrentId() (like BRepGraph_DefsIterator). +//! SFINAE should pick Current() due to higher priority. +class MockBothCurrentAndId +{ +public: + MockBothCurrentAndId(const int* theData, const int theSize) + : myData(theData), + mySize(theSize) + { + } + + bool More() const { return myIndex < mySize; } + + void Next() { ++myIndex; } + + const int& Current() const { return myData[myIndex]; } + + int CurrentId() const { return myIndex; } + +private: + const int* myData; + int mySize = 0; + int myIndex = 0; +}; + +//! Non-copyable mock iterator (like BRepGraph_ChildExplorer). +class MockNonCopyableIterator +{ +public: + MockNonCopyableIterator(const int* theData, const int theSize) + : myData(theData), + mySize(theSize) + { + } + + MockNonCopyableIterator(const MockNonCopyableIterator&) = delete; + MockNonCopyableIterator& operator=(const MockNonCopyableIterator&) = delete; + MockNonCopyableIterator(MockNonCopyableIterator&&) = default; + MockNonCopyableIterator& operator=(MockNonCopyableIterator&&) = default; + + bool More() const { return myIndex < mySize; } + + void Next() { ++myIndex; } + + int Current() const { return myData[myIndex]; } + +private: + const int* myData; + int mySize = 0; + int myIndex = 0; +}; + +//! Mock iterator with begin()/end() integrated directly (like modified BRepGraph classes). +class MockWithBeginEnd +{ +public: + MockWithBeginEnd(const int* theData, const int theSize) + : myData(theData), + mySize(theSize) + { + } + + bool More() const { return myIndex < mySize; } + + void Next() { ++myIndex; } + + const int& Current() const { return myData[myIndex]; } + + NCollection_ForwardRangeIterator begin() + { + return NCollection_ForwardRangeIterator(this); + } + + NCollection_ForwardRangeSentinel end() const { return NCollection_ForwardRangeSentinel{}; } + +private: + const int* myData; + int mySize = 0; + int myIndex = 0; +}; + +//! Struct for testing operator->. +struct Point +{ + int X; + int Y; +}; + +//! Mock iterator returning a reference to a struct. +class MockPointIterator +{ +public: + MockPointIterator(const Point* theData, const int theSize) + : myData(theData), + mySize(theSize) + { + } + + bool More() const { return myIndex < mySize; } + + void Next() { ++myIndex; } + + const Point& Value() const { return myData[myIndex]; } + +private: + const Point* myData; + int mySize = 0; + int myIndex = 0; +}; + +//! Mock iterator returning a struct by value (tests ArrowProxy). +class MockPointByValueIterator +{ +public: + MockPointByValueIterator(const Point* theData, const int theSize) + : myData(theData), + mySize(theSize) + { + } + + bool More() const { return myIndex < mySize; } + + void Next() { ++myIndex; } + + Point Current() const { return myData[myIndex]; } + +private: + const Point* myData; + int mySize = 0; + int myIndex = 0; +}; + +} // namespace + +// --------------------------------------------------------------------------- +// SFINAE detection tests +// --------------------------------------------------------------------------- + +TEST(NCollection_ForwardRangeTest, SFINAEDetection_Value) +{ + EXPECT_TRUE(NCollection_ForwardRangeDetail::HasValue::value); + EXPECT_FALSE(NCollection_ForwardRangeDetail::HasCurrent::value); + EXPECT_FALSE(NCollection_ForwardRangeDetail::HasCurrentId::value); +} + +TEST(NCollection_ForwardRangeTest, SFINAEDetection_Current) +{ + EXPECT_FALSE(NCollection_ForwardRangeDetail::HasValue::value); + EXPECT_TRUE(NCollection_ForwardRangeDetail::HasCurrent::value); + EXPECT_FALSE(NCollection_ForwardRangeDetail::HasCurrentId::value); +} + +TEST(NCollection_ForwardRangeTest, SFINAEDetection_CurrentId) +{ + EXPECT_FALSE(NCollection_ForwardRangeDetail::HasValue::value); + EXPECT_FALSE(NCollection_ForwardRangeDetail::HasCurrent::value); + EXPECT_TRUE(NCollection_ForwardRangeDetail::HasCurrentId::value); +} + +TEST(NCollection_ForwardRangeTest, SFINAEDetection_BothValueAndCurrent) +{ + EXPECT_TRUE(NCollection_ForwardRangeDetail::HasValue::value); + EXPECT_TRUE(NCollection_ForwardRangeDetail::HasCurrent::value); + // AccessorTraits should pick Value() over Current() + using Accessor = NCollection_ForwardRangeDetail::AccessorTraits; + EXPECT_TRUE((std::is_same_v)); +} + +TEST(NCollection_ForwardRangeTest, SFINAEDetection_BothCurrentAndId) +{ + EXPECT_FALSE(NCollection_ForwardRangeDetail::HasValue::value); + EXPECT_TRUE(NCollection_ForwardRangeDetail::HasCurrent::value); + EXPECT_TRUE(NCollection_ForwardRangeDetail::HasCurrentId::value); + // AccessorTraits should pick Current() over CurrentId() + using Accessor = NCollection_ForwardRangeDetail::AccessorTraits; + EXPECT_TRUE((std::is_same_v)); +} + +// --------------------------------------------------------------------------- +// Standalone range wrapper tests +// --------------------------------------------------------------------------- + +TEST(NCollection_ForwardRangeTest, StandaloneRange_ValueIterator) +{ + const int aData[] = {10, 20, 30, 40, 50}; + int aSum = 0; + int aCount = 0; + for (const int& aVal : NCollection_ForwardRange(MockValueIterator(aData, 5))) + { + aSum += aVal; + ++aCount; + } + EXPECT_EQ(aSum, 150); + EXPECT_EQ(aCount, 5); +} + +TEST(NCollection_ForwardRangeTest, StandaloneRange_CurrentIterator) +{ + const int aData[] = {1, 2, 3}; + int aSum = 0; + for (const int& aVal : NCollection_ForwardRange(MockCurrentIterator(aData, 3))) + { + aSum += aVal; + } + EXPECT_EQ(aSum, 6); +} + +TEST(NCollection_ForwardRangeTest, StandaloneRange_CurrentIdIterator_ByValue) +{ + const int aData[] = {100, 200, 300}; + int aSum = 0; + for (const int aVal : NCollection_ForwardRange(MockCurrentIdIterator(aData, 3))) + { + aSum += aVal; + } + EXPECT_EQ(aSum, 600); +} + +TEST(NCollection_ForwardRangeTest, StandaloneRange_Empty) +{ + const int aData[] = {1}; + int aCount = 0; + for (const int& aVal : NCollection_ForwardRange(MockValueIterator(aData, 0))) + { + (void)aVal; + ++aCount; + } + EXPECT_EQ(aCount, 0); +} + +TEST(NCollection_ForwardRangeTest, StandaloneRange_SingleElement) +{ + const int aData[] = {42}; + int aResult = 0; + for (const int& aVal : NCollection_ForwardRange(MockValueIterator(aData, 1))) + { + aResult = aVal; + } + EXPECT_EQ(aResult, 42); +} + +TEST(NCollection_ForwardRangeTest, StandaloneRange_NonCopyable_Move) +{ + const int aData[] = {5, 10, 15}; + int aSum = 0; + for (const int aVal : NCollection_ForwardRange(MockNonCopyableIterator(aData, 3))) + { + aSum += aVal; + } + EXPECT_EQ(aSum, 30); +} + +// --------------------------------------------------------------------------- +// In-class begin/end integration tests +// --------------------------------------------------------------------------- + +TEST(NCollection_ForwardRangeTest, InClassBeginEnd_RangeFor) +{ + const int aData[] = {7, 14, 21}; + MockWithBeginEnd anIter(aData, 3); + int aSum = 0; + for (const int& aVal : anIter) + { + aSum += aVal; + } + EXPECT_EQ(aSum, 42); +} + +TEST(NCollection_ForwardRangeTest, InClassBeginEnd_Temporary) +{ + const int aData[] = {1, 2, 3, 4}; + int aSum = 0; + for (const int& aVal : MockWithBeginEnd(aData, 4)) + { + aSum += aVal; + } + EXPECT_EQ(aSum, 10); +} + +// --------------------------------------------------------------------------- +// Iterator traits tests +// --------------------------------------------------------------------------- + +TEST(NCollection_ForwardRangeTest, IteratorTraits_ValueType) +{ + using Iter = NCollection_ForwardRangeIterator; + EXPECT_TRUE((std::is_same_v)); + EXPECT_TRUE((std::is_same_v)); + EXPECT_TRUE((std::is_same_v)); +} + +TEST(NCollection_ForwardRangeTest, IteratorTraits_ReferenceReturn) +{ + using Iter = NCollection_ForwardRangeIterator; + // Value() returns const int&, so reference should be const int& + EXPECT_TRUE((std::is_same_v)); +} + +TEST(NCollection_ForwardRangeTest, IteratorTraits_ValueReturn) +{ + using Iter = NCollection_ForwardRangeIterator; + // CurrentId() returns int by value + EXPECT_TRUE((std::is_same_v)); +} + +// --------------------------------------------------------------------------- +// operator-> tests +// --------------------------------------------------------------------------- + +TEST(NCollection_ForwardRangeTest, ArrowOperator_Reference) +{ + const Point aData[] = {{1, 2}, {3, 4}}; + NCollection_ForwardRange aRange(MockPointIterator(aData, 2)); + NCollection_ForwardRangeIterator anIt = aRange.begin(); + EXPECT_EQ(anIt->X, 1); + EXPECT_EQ(anIt->Y, 2); + ++anIt; + EXPECT_EQ(anIt->X, 3); + EXPECT_EQ(anIt->Y, 4); +} + +TEST(NCollection_ForwardRangeTest, ArrowOperator_ArrowProxy) +{ + const Point aData[] = {{10, 20}, {30, 40}}; + NCollection_ForwardRange aRange(MockPointByValueIterator(aData, 2)); + NCollection_ForwardRangeIterator anIt = aRange.begin(); + EXPECT_EQ(anIt->X, 10); + EXPECT_EQ(anIt->Y, 20); + ++anIt; + EXPECT_EQ(anIt->X, 30); + EXPECT_EQ(anIt->Y, 40); +} + +// --------------------------------------------------------------------------- +// Postfix increment test +// --------------------------------------------------------------------------- + +TEST(NCollection_ForwardRangeTest, PostfixIncrement) +{ + const int aData[] = {100, 200, 300}; + MockValueIterator anIter(aData, 3); + NCollection_ForwardRangeIterator anIt(&anIter); + NCollection_ForwardRangeIterator::PostfixProxy aProxy = anIt++; + EXPECT_EQ(*aProxy, 100); + EXPECT_EQ(*anIt, 200); +} + +// --------------------------------------------------------------------------- +// Sentinel comparison tests +// --------------------------------------------------------------------------- + +TEST(NCollection_ForwardRangeTest, SentinelComparison) +{ + const int aData[] = {1}; + MockValueIterator anIter(aData, 1); + NCollection_ForwardRangeIterator anIt(&anIter); + NCollection_ForwardRangeSentinel aSentinel; + + EXPECT_TRUE(anIt != aSentinel); + EXPECT_FALSE(anIt == aSentinel); + EXPECT_TRUE(aSentinel != anIt); + EXPECT_FALSE(aSentinel == anIt); + + ++anIt; + + EXPECT_FALSE(anIt != aSentinel); + EXPECT_TRUE(anIt == aSentinel); + EXPECT_FALSE(aSentinel != anIt); + EXPECT_TRUE(aSentinel == anIt); +} + +// --------------------------------------------------------------------------- +// Priority verification: Value() wins over Current() +// --------------------------------------------------------------------------- + +TEST(NCollection_ForwardRangeTest, Priority_ValueOverCurrent) +{ + const int aData[] = {99}; + int aResult = 0; + for (const int& aVal : NCollection_ForwardRange(MockBothValueAndCurrent(aData, 1))) + { + aResult = aVal; + } + EXPECT_EQ(aResult, 99); +} + +// --------------------------------------------------------------------------- +// Priority verification: Current() wins over CurrentId() +// --------------------------------------------------------------------------- + +TEST(NCollection_ForwardRangeTest, Priority_CurrentOverCurrentId) +{ + const int aData[] = {55, 66}; + int aSum = 0; + for (const int& aVal : NCollection_ForwardRange(MockBothCurrentAndId(aData, 2))) + { + aSum += aVal; + } + EXPECT_EQ(aSum, 121); +} diff --git a/src/FoundationClasses/TKernel/NCollection/FILES.cmake b/src/FoundationClasses/TKernel/NCollection/FILES.cmake index 60cb9ae7ac..717ad8a90c 100644 --- a/src/FoundationClasses/TKernel/NCollection/FILES.cmake +++ b/src/FoundationClasses/TKernel/NCollection/FILES.cmake @@ -31,6 +31,7 @@ set(OCCT_NCollection_FILES NCollection_EBTree.hxx NCollection_FlatDataMap.hxx NCollection_FlatMap.hxx + NCollection_ForwardRange.hxx NCollection_Haft.h NCollection_Handle.hxx NCollection_HArray1.hxx diff --git a/src/FoundationClasses/TKernel/NCollection/NCollection_ForwardRange.hxx b/src/FoundationClasses/TKernel/NCollection/NCollection_ForwardRange.hxx new file mode 100644 index 0000000000..5fa45f9e1c --- /dev/null +++ b/src/FoundationClasses/TKernel/NCollection/NCollection_ForwardRange.hxx @@ -0,0 +1,292 @@ +// Copyright (c) 2026 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 NCollection_ForwardRange_HeaderFile +#define NCollection_ForwardRange_HeaderFile + +#include +#include +#include + +//! @file NCollection_ForwardRange.hxx +//! @brief STL range-for adapter for OCCT iterators following the More()/Next() pattern. +//! +//! Provides reusable iterator/sentinel types that enable range-based for loops +//! on any OCCT iterator or explorer class with More(), Next(), and a value accessor. +//! +//! The value accessor is detected automatically via SFINAE with priority: +//! Value() > Current() > CurrentId(). +//! +//! Two usage modes: +//! 1. In-class integration: add begin()/end() methods using the provided types. +//! @code +//! NCollection_ForwardRangeIterator begin() +//! { +//! return NCollection_ForwardRangeIterator(this); +//! } +//! NCollection_ForwardRangeSentinel end() const { return {}; } +//! @endcode +//! 2. Standalone wrapper for unmodified classes: +//! @code +//! for (const TopoDS_Shape& aFace : +//! NCollection_ForwardRange(TopExp_Explorer(aShape, TopAbs_FACE))) +//! { +//! // ... +//! } +//! @endcode + +namespace NCollection_ForwardRangeDetail +{ + +//! SFINAE: detect .Value() const +template +struct HasValue : std::false_type +{ +}; + +template +struct HasValue().Value())>> : std::true_type +{ +}; + +//! SFINAE: detect .Current() const +template +struct HasCurrent : std::false_type +{ +}; + +template +struct HasCurrent().Current())>> : std::true_type +{ +}; + +//! SFINAE: detect .CurrentId() const +template +struct HasCurrentId : std::false_type +{ +}; + +template +struct HasCurrentId().CurrentId())>> : std::true_type +{ +}; + +//! Accessor dispatch trait: resolves to the correct value accessor and its return type. +//! Uses std::enable_if_t for mutually-exclusive priority selection. +template +struct AccessorTraits; + +//! Priority 1: has Value() +template +struct AccessorTraits::value>> +{ + using ReturnType = decltype(std::declval().Value()); + + static ReturnType Get(const T& theIter) { return theIter.Value(); } +}; + +//! Priority 2: has Current() but not Value() +template +struct AccessorTraits::value && HasCurrent::value>> +{ + using ReturnType = decltype(std::declval().Current()); + + static ReturnType Get(const T& theIter) { return theIter.Current(); } +}; + +//! Priority 3: has CurrentId() but not Value() or Current() +template +struct AccessorTraits< + T, + std::enable_if_t::value && !HasCurrent::value && HasCurrentId::value>> +{ + using ReturnType = decltype(std::declval().CurrentId()); + + static ReturnType Get(const T& theIter) { return theIter.CurrentId(); } +}; + +//! Proxy for operator-> when the accessor returns a value (not a reference). +template +struct ArrowProxy +{ + ValueT myValue; + + const ValueT* operator->() const { return &myValue; } +}; + +} // namespace NCollection_ForwardRangeDetail + +//! Empty sentinel type used as the end marker for range-for loops. +struct NCollection_ForwardRangeSentinel +{ +}; + +//! @brief STL input iterator that wraps an OCCT More()/Next() iterator. +//! +//! Holds a non-owning pointer to the host iterator/explorer. +//! The host must outlive this iterator (guaranteed by range-for semantics). +//! +//! @tparam HostType OCCT iterator/explorer with More(), Next(), and a value accessor. +template +class NCollection_ForwardRangeIterator +{ + using Accessor = NCollection_ForwardRangeDetail::AccessorTraits; + using RawReturn = typename Accessor::ReturnType; + +public: + using value_type = std::remove_cv_t>; + using difference_type = std::ptrdiff_t; + using iterator_category = std::input_iterator_tag; + using reference = RawReturn; + using pointer = std::conditional_t, + std::add_pointer_t>, + NCollection_ForwardRangeDetail::ArrowProxy>; + + //! Construct from a pointer to the host iterator. + explicit NCollection_ForwardRangeIterator(HostType* theHost) + : myHost(theHost) + { + } + + //! Dereference: returns the current value from the host. + RawReturn operator*() const { return Accessor::Get(*myHost); } + + //! Arrow operator: returns a pointer or proxy to the current value. + pointer operator->() const + { + if constexpr (std::is_reference_v) + { + return &Accessor::Get(*myHost); + } + else + { + return NCollection_ForwardRangeDetail::ArrowProxy{Accessor::Get(*myHost)}; + } + } + + //! Prefix increment: advances the host iterator. + NCollection_ForwardRangeIterator& operator++() + { + myHost->Next(); + return *this; + } + + //! Postfix increment: captures the current value, then advances. + //! Returns a proxy holding the old value (safe for non-copyable hosts). + struct PostfixProxy + { + value_type myValue; + + const value_type& operator*() const { return myValue; } + }; + + PostfixProxy operator++(int) + { + PostfixProxy aProxy{Accessor::Get(*myHost)}; + myHost->Next(); + return aProxy; + } + + //! Equality with sentinel: true when the host is exhausted. + friend bool operator==(const NCollection_ForwardRangeIterator& theLhs, + NCollection_ForwardRangeSentinel) + { + return !theLhs.myHost->More(); + } + + friend bool operator!=(const NCollection_ForwardRangeIterator& theLhs, + NCollection_ForwardRangeSentinel) + { + return theLhs.myHost->More(); + } + + friend bool operator==(NCollection_ForwardRangeSentinel, + const NCollection_ForwardRangeIterator& theRhs) + { + return !theRhs.myHost->More(); + } + + friend bool operator!=(NCollection_ForwardRangeSentinel, + const NCollection_ForwardRangeIterator& theRhs) + { + return theRhs.myHost->More(); + } + +private: + HostType* myHost; //!< Non-owning pointer to the host iterator. +}; + +//! @brief Standalone range wrapper for OCCT iterators. +//! +//! Owns a copy/move of the host iterator and provides begin()/end() +//! for range-based for loops. Use this when the host class does not +//! have its own begin()/end() methods. +//! +//! @code +//! for (const TopoDS_Shape& aFace : +//! NCollection_ForwardRange(TopExp_Explorer(aShape, TopAbs_FACE))) +//! { +//! // process aFace +//! } +//! @endcode +//! +//! @tparam HostType OCCT iterator/explorer with More(), Next(), and a value accessor. +template +class NCollection_ForwardRange +{ +public: + using iterator = NCollection_ForwardRangeIterator; + using const_iterator = iterator; + using sentinel = NCollection_ForwardRangeSentinel; + + //! Construct from a copyable host (lvalue). + template >> + explicit NCollection_ForwardRange(const HostType& theHost) + : myHost(theHost) + { + } + + //! Construct from an rvalue (moves the host). + explicit NCollection_ForwardRange(HostType&& theHost) + : myHost(std::move(theHost)) + { + } + + NCollection_ForwardRange(const NCollection_ForwardRange&) = delete; + NCollection_ForwardRange& operator=(const NCollection_ForwardRange&) = delete; + NCollection_ForwardRange(NCollection_ForwardRange&&) = default; + NCollection_ForwardRange& operator=(NCollection_ForwardRange&&) = default; + + //! Returns iterator to the current position of the host. + //! Const-qualified: iteration advances the internal cursor (mutable), + //! not the logical state of the range. + [[nodiscard]] iterator begin() const { return iterator(&myHost); } + + //! Returns sentinel marking the end. + [[nodiscard]] sentinel end() const { return sentinel{}; } + + //! Returns const-access iterator (same as begin). + [[nodiscard]] const_iterator cbegin() const { return const_iterator(&myHost); } + + //! Returns sentinel marking the end. + [[nodiscard]] sentinel cend() const { return sentinel{}; } + +private: + mutable HostType myHost; //!< Owned host iterator (mutable: iteration is cursor movement). +}; + +//! CTAD deduction guide: deduces HostType from the constructor argument. +template +NCollection_ForwardRange(T&&) -> NCollection_ForwardRange>; + +#endif // NCollection_ForwardRange_HeaderFile diff --git a/src/ModelingData/TKBRep/BRepGraph/BRepGraph.cxx b/src/ModelingData/TKBRep/BRepGraph/BRepGraph.cxx new file mode 100644 index 0000000000..fb9be5296d --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/BRepGraph.cxx @@ -0,0 +1,863 @@ +// Copyright (c) 2026 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 + +//================================================================================================= + +void BRepGraph::initViews() +{ + if (myData) + { + myData->myTopoView = TopoView(this); + myData->myUIDsView = UIDsView(this); + myData->myCacheView = CacheView(this); + myData->myRefsView = RefsView(this); + myData->myShapesView = ShapesView(this); + myData->myBuilderView = BuilderView(this); + } +} + +BRepGraph::BRepGraph() + : myData(std::make_unique()) +{ + initViews(); +} + +//================================================================================================= + +BRepGraph::BRepGraph(const occ::handle& theAlloc) + : myData(std::make_unique(theAlloc)) +{ + initViews(); +} + +//================================================================================================= + +BRepGraph::~BRepGraph() = default; + +//================================================================================================= + +const BRepGraph::TopoView& BRepGraph::Topo() const +{ + return myData->myTopoView; +} + +//================================================================================================= + +const BRepGraph::UIDsView& BRepGraph::UIDs() const +{ + return myData->myUIDsView; +} + +//================================================================================================= + +BRepGraph::CacheView& BRepGraph::Cache() +{ + return myData->myCacheView; +} + +//================================================================================================= + +const BRepGraph::CacheView& BRepGraph::Cache() const +{ + return myData->myCacheView; +} + +//================================================================================================= + +const BRepGraph::RefsView& BRepGraph::Refs() const +{ + return myData->myRefsView; +} + +//================================================================================================= + +const BRepGraph::ShapesView& BRepGraph::Shapes() const +{ + return myData->myShapesView; +} + +//================================================================================================= + +BRepGraph::BuilderView& BRepGraph::Builder() +{ + return myData->myBuilderView; +} + +//================================================================================================= + +const BRepGraph::BuilderView& BRepGraph::Builder() const +{ + return myData->myBuilderView; +} + +//================================================================================================= + +BRepGraph::BRepGraph(BRepGraph&& theOther) noexcept + : myData(std::move(theOther.myData)), + myLayerRegistry(std::move(theOther.myLayerRegistry)), + myTransientCache(std::move(theOther.myTransientCache)) +{ + // View objects store a back-pointer to the owning BRepGraph; after move, + // they must point to the new owner (`this`), not the moved-from object. + initViews(); +} + +//================================================================================================= + +BRepGraph& BRepGraph::operator=(BRepGraph&& theOther) noexcept +{ + if (this != &theOther) + { + myData = std::move(theOther.myData); + myLayerRegistry = std::move(theOther.myLayerRegistry); + myTransientCache = std::move(theOther.myTransientCache); + // View objects store a back-pointer to the owning BRepGraph; after move, + // they must point to the new owner (`this`), not the moved-from object. + initViews(); + } + return *this; +} + +//================================================================================================= + +void BRepGraph::Build(const TopoDS_Shape& theShape, const bool theParallel) +{ + BRepGraph_Builder::Perform(*this, theShape, theParallel); +} + +//================================================================================================= + +void BRepGraph::Build(const TopoDS_Shape& theShape, + const bool theParallel, + const BRepGraphInc_Populate::Options& theOptions) +{ + BRepGraph_Builder::Perform(*this, theShape, theParallel, theOptions); +} + +//================================================================================================= + +BRepGraph_UID BRepGraph::allocateUID(const BRepGraph_NodeId theNodeId) +{ + // Load counter before append: if Append() throws, no counter is consumed. + // Single-threaded precondition: UID allocation is only called from Builder + // methods which are externally serialized, so load-then-increment is safe. + const size_t aCounter = myData->myNextUIDCounter.load(std::memory_order_relaxed); + const uint32_t aGeneration = myData->myGeneration.load(std::memory_order_relaxed); + BRepGraph_UID aUID(theNodeId.NodeKind, aCounter, aGeneration); + myData->myIncStorage.ChangeUIDs(theNodeId.NodeKind).Append(aUID); + { + std::unique_lock aLock(myData->myUIDToNodeIdMutex); + if (myData->myUIDToNodeIdGeneration != aGeneration) + { + myData->myUIDToNodeId.Clear(); + myData->myUIDToNodeIdGeneration = aGeneration; + myData->myUIDToNodeIdDirty = false; + } + if (!myData->myUIDToNodeIdDirty) + { + myData->myUIDToNodeId.Bind(aUID, theNodeId); + } + } + myData->myNextUIDCounter.fetch_add(1, std::memory_order_relaxed); + return aUID; +} + +//================================================================================================= + +BRepGraph_RefUID BRepGraph::allocateRefUID(const BRepGraph_RefId theRefId) +{ + // Load counter before append: if Append() throws, no counter is consumed. + // Single-threaded precondition: see allocateUID() comment. + const size_t aCounter = myData->myNextUIDCounter.load(std::memory_order_relaxed); + const uint32_t aGeneration = myData->myGeneration.load(std::memory_order_relaxed); + const BRepGraph_RefUID aUID(theRefId.RefKind, aCounter, aGeneration); + myData->myIncStorage.ChangeRefUIDs(theRefId.RefKind).Append(aUID); + { + std::unique_lock aLock(myData->myRefUIDToRefIdMutex); + if (myData->myRefUIDToRefIdGeneration != aGeneration) + { + myData->myRefUIDToRefId.Clear(); + myData->myRefUIDToRefIdGeneration = aGeneration; + myData->myRefUIDToRefIdDirty = false; + } + if (!myData->myRefUIDToRefIdDirty) + { + myData->myRefUIDToRefId.Bind(aUID, theRefId); + } + } + myData->myNextUIDCounter.fetch_add(1, std::memory_order_relaxed); + return aUID; +} + +//================================================================================================= + +// mutableCache() removed - NodeCache no longer exists in BaseDef. +// Transient caches now live in BRepGraph_TransientCache. + +//================================================================================================= + +bool BRepGraph::IsDone() const +{ + return myData->myIsDone; +} + +//================================================================================================= + +const NCollection_Vector& BRepGraph::RootNodeIds() const +{ + return myData->myRootNodeIds; +} + +//================================================================================================= + +const BRepGraphInc::BaseDef* BRepGraph::topoEntity(const BRepGraph_NodeId theId) const +{ + if (!theId.IsValid()) + return nullptr; + const BRepGraphInc_Storage& aStorage = myData->myIncStorage; + switch (theId.NodeKind) + { + case BRepGraph_NodeId::Kind::Solid: { + const BRepGraph_SolidId anId(theId.Index); + return anId.IsValid(aStorage.NbSolids()) ? &aStorage.Solid(anId) : nullptr; + } + case BRepGraph_NodeId::Kind::Shell: { + const BRepGraph_ShellId anId(theId.Index); + return anId.IsValid(aStorage.NbShells()) ? &aStorage.Shell(anId) : nullptr; + } + case BRepGraph_NodeId::Kind::Face: { + const BRepGraph_FaceId anId(theId.Index); + return anId.IsValid(aStorage.NbFaces()) ? &aStorage.Face(anId) : nullptr; + } + case BRepGraph_NodeId::Kind::Wire: { + const BRepGraph_WireId anId(theId.Index); + return anId.IsValid(aStorage.NbWires()) ? &aStorage.Wire(anId) : nullptr; + } + case BRepGraph_NodeId::Kind::Edge: { + const BRepGraph_EdgeId anId(theId.Index); + return anId.IsValid(aStorage.NbEdges()) ? &aStorage.Edge(anId) : nullptr; + } + case BRepGraph_NodeId::Kind::CoEdge: { + const BRepGraph_CoEdgeId anId(theId.Index); + return anId.IsValid(aStorage.NbCoEdges()) ? &aStorage.CoEdge(anId) : nullptr; + } + case BRepGraph_NodeId::Kind::Vertex: { + const BRepGraph_VertexId anId(theId.Index); + return anId.IsValid(aStorage.NbVertices()) ? &aStorage.Vertex(anId) : nullptr; + } + case BRepGraph_NodeId::Kind::Compound: { + const BRepGraph_CompoundId anId(theId.Index); + return anId.IsValid(aStorage.NbCompounds()) ? &aStorage.Compound(anId) : nullptr; + } + case BRepGraph_NodeId::Kind::CompSolid: { + const BRepGraph_CompSolidId anId(theId.Index); + return anId.IsValid(aStorage.NbCompSolids()) ? &aStorage.CompSolid(anId) : nullptr; + } + case BRepGraph_NodeId::Kind::Product: { + const BRepGraph_ProductId anId(theId.Index); + return anId.IsValid(aStorage.NbProducts()) ? &aStorage.Product(anId) : nullptr; + } + case BRepGraph_NodeId::Kind::Occurrence: { + const BRepGraph_OccurrenceId anId(theId.Index); + return anId.IsValid(aStorage.NbOccurrences()) ? &aStorage.Occurrence(anId) : nullptr; + } + default: + return nullptr; + } +} + +//================================================================================================= + +BRepGraphInc::BaseDef* BRepGraph::changeTopoEntity(const BRepGraph_NodeId theId) +{ + if (!theId.IsValid()) + return nullptr; + BRepGraphInc_Storage& aStorage = myData->myIncStorage; + switch (theId.NodeKind) + { + case BRepGraph_NodeId::Kind::Solid: { + const BRepGraph_SolidId anId(theId.Index); + return anId.IsValid(aStorage.NbSolids()) ? &aStorage.ChangeSolid(anId) : nullptr; + } + case BRepGraph_NodeId::Kind::Shell: { + const BRepGraph_ShellId anId(theId.Index); + return anId.IsValid(aStorage.NbShells()) ? &aStorage.ChangeShell(anId) : nullptr; + } + case BRepGraph_NodeId::Kind::Face: { + const BRepGraph_FaceId anId(theId.Index); + return anId.IsValid(aStorage.NbFaces()) ? &aStorage.ChangeFace(anId) : nullptr; + } + case BRepGraph_NodeId::Kind::Wire: { + const BRepGraph_WireId anId(theId.Index); + return anId.IsValid(aStorage.NbWires()) ? &aStorage.ChangeWire(anId) : nullptr; + } + case BRepGraph_NodeId::Kind::Edge: { + const BRepGraph_EdgeId anId(theId.Index); + return anId.IsValid(aStorage.NbEdges()) ? &aStorage.ChangeEdge(anId) : nullptr; + } + case BRepGraph_NodeId::Kind::CoEdge: { + const BRepGraph_CoEdgeId anId(theId.Index); + return anId.IsValid(aStorage.NbCoEdges()) ? &aStorage.ChangeCoEdge(anId) : nullptr; + } + case BRepGraph_NodeId::Kind::Vertex: { + const BRepGraph_VertexId anId(theId.Index); + return anId.IsValid(aStorage.NbVertices()) ? &aStorage.ChangeVertex(anId) : nullptr; + } + case BRepGraph_NodeId::Kind::Compound: { + const BRepGraph_CompoundId anId(theId.Index); + return anId.IsValid(aStorage.NbCompounds()) ? &aStorage.ChangeCompound(anId) : nullptr; + } + case BRepGraph_NodeId::Kind::CompSolid: { + const BRepGraph_CompSolidId anId(theId.Index); + return anId.IsValid(aStorage.NbCompSolids()) ? &aStorage.ChangeCompSolid(anId) : nullptr; + } + case BRepGraph_NodeId::Kind::Product: { + const BRepGraph_ProductId anId(theId.Index); + return anId.IsValid(aStorage.NbProducts()) ? &aStorage.ChangeProduct(anId) : nullptr; + } + case BRepGraph_NodeId::Kind::Occurrence: { + const BRepGraph_OccurrenceId anId(theId.Index); + return anId.IsValid(aStorage.NbOccurrences()) ? &aStorage.ChangeOccurrence(anId) : nullptr; + } + default: + return nullptr; + } +} + +//================================================================================================= + +void BRepGraph::invalidateSubgraphImpl(const BRepGraph_NodeId theNode) +{ + if (!theNode.IsValid()) + return; + + using Kind = BRepGraph_NodeId::Kind; + const BRepGraphInc_Storage& aStorage = myData->myIncStorage; + + // Bounds check: ensure the node index is within the entity vector. + if (topoEntity(theNode) == nullptr) + return; + + struct StackEntry + { + BRepGraph_NodeId Node; + int Depth; + }; + + const int aNbNodes = aStorage.NbSolids() + aStorage.NbShells() + aStorage.NbFaces() + + aStorage.NbWires() + aStorage.NbEdges() + aStorage.NbVertices() + + aStorage.NbCompounds() + aStorage.NbCompSolids() + aStorage.NbProducts() + + aStorage.NbOccurrences(); + const int aMaxDepth = aNbNodes > 0 ? aNbNodes : 1; + occ::handle anAlloc = new NCollection_IncAllocator(); + NCollection_Vector aStack(64, anAlloc); + NCollection_Map aVisited(aNbNodes, anAlloc); + aStack.Append({theNode, 0}); + + const auto aPushChild = [&](const BRepGraph_NodeId theChild, const int theDepth) { + if (!theChild.IsValid()) + return; + aStack.Append({theChild, theDepth}); + }; + + while (!aStack.IsEmpty()) + { + const StackEntry aCurrent = aStack.Last(); + aStack.EraseLast(); + + if (aCurrent.Depth > aMaxDepth || !aCurrent.Node.IsValid() || !aVisited.Add(aCurrent.Node)) + continue; + + // Bounds check: skip nodes whose index is outside the entity vector. + if (topoEntity(aCurrent.Node) == nullptr) + continue; + + // Increment OwnGen + SubtreeGen so generation-based cache freshness detects the change. + BRepGraphInc::BaseDef* anEntity = changeTopoEntity(aCurrent.Node); + if (anEntity != nullptr) + { + ++anEntity->OwnGen; + ++anEntity->SubtreeGen; + } + + const int aNextDepth = aCurrent.Depth + 1; + + switch (aCurrent.Node.NodeKind) + { + case Kind::Compound: { + for (BRepGraph_DefsChildOfCompound aChildIt(*this, + BRepGraph_CompoundId(aCurrent.Node.Index)); + aChildIt.More(); + aChildIt.Next()) + { + aPushChild(aChildIt.CurrentId(), aNextDepth); + } + break; + } + case Kind::CompSolid: { + for (BRepGraph_DefsSolidOfCompSolid aChildIt(*this, + BRepGraph_CompSolidId(aCurrent.Node.Index)); + aChildIt.More(); + aChildIt.Next()) + { + aPushChild(aChildIt.CurrentId(), aNextDepth); + } + break; + } + case Kind::Solid: { + const BRepGraphInc::SolidDef& aSolidEnt = + aStorage.Solid(BRepGraph_SolidId(aCurrent.Node.Index)); + for (BRepGraph_DefsShellOfSolid aChildIt(*this, BRepGraph_SolidId(aCurrent.Node.Index)); + aChildIt.More(); + aChildIt.Next()) + { + aPushChild(aChildIt.CurrentId(), aNextDepth); + } + for (const BRepGraph_ChildRefId& aChildRefId : aSolidEnt.FreeChildRefIds) + aPushChild(aStorage.ChildRef(aChildRefId).ChildDefId, aNextDepth); + break; + } + case Kind::Shell: { + const BRepGraphInc::ShellDef& aShellEnt = + aStorage.Shell(BRepGraph_ShellId(aCurrent.Node.Index)); + for (BRepGraph_DefsFaceOfShell aChildIt(*this, BRepGraph_ShellId(aCurrent.Node.Index)); + aChildIt.More(); + aChildIt.Next()) + { + aPushChild(aChildIt.CurrentId(), aNextDepth); + } + for (const BRepGraph_ChildRefId& aChildRefId : aShellEnt.FreeChildRefIds) + aPushChild(aStorage.ChildRef(aChildRefId).ChildDefId, aNextDepth); + break; + } + case Kind::Face: { + for (BRepGraph_DefsWireOfFace aChildIt(*this, BRepGraph_FaceId(aCurrent.Node.Index)); + aChildIt.More(); + aChildIt.Next()) + { + aPushChild(aChildIt.CurrentId(), aNextDepth); + } + break; + } + case Kind::Wire: { + for (BRepGraph_DefsEdgeOfWire aChildIt(*this, BRepGraph_WireId(aCurrent.Node.Index)); + aChildIt.More(); + aChildIt.Next()) + { + aPushChild(aChildIt.CurrentId(), aNextDepth); + } + break; + } + case Kind::Edge: { + for (BRepGraph_DefsVertexOfEdge aChildIt(*this, BRepGraph_EdgeId(aCurrent.Node.Index)); + aChildIt.More(); + aChildIt.Next()) + { + aPushChild(aChildIt.CurrentId(), aNextDepth); + } + break; + } + case Kind::Product: { + const BRepGraphInc::ProductDef& aProd = + aStorage.Product(BRepGraph_ProductId(aCurrent.Node.Index)); + aPushChild(aProd.ShapeRootId, aNextDepth); + for (BRepGraph_RefsOccurrenceOfProduct aChildIt(*this, + BRepGraph_ProductId(aCurrent.Node.Index)); + aChildIt.More(); + aChildIt.Next()) + { + aPushChild(aStorage.OccurrenceRef(aChildIt.CurrentId()).OccurrenceDefId, aNextDepth); + } + break; + } + case Kind::Occurrence: { + const BRepGraphInc::OccurrenceDef& anOcc = + aStorage.Occurrence(BRepGraph_OccurrenceId(aCurrent.Node.Index)); + aPushChild(anOcc.ProductDefId, aNextDepth); + break; + } + default: + break; + } + } +} + +//================================================================================================= + +void BRepGraph::markModified(const BRepGraph_NodeId theNodeId) noexcept +{ + if (!theNodeId.IsValid()) + return; + + BRepGraphInc::BaseDef* anEntity = changeTopoEntity(theNodeId); + if (anEntity == nullptr) + return; + + markModified(theNodeId, *anEntity); +} + +//================================================================================================= + +void BRepGraph::markModified(const BRepGraph_NodeId theNodeId, + BRepGraphInc::BaseDef& theEntity) noexcept +{ + ++theEntity.OwnGen; + ++theEntity.SubtreeGen; + const uint32_t aWave = myData->myPropagationWave.fetch_add(1, std::memory_order_relaxed) + 1; + theEntity.LastPropWave = aWave; + + // In deferred mode: accumulate for batch processing. + if (myData->myDeferredMode.load(std::memory_order_relaxed)) + { + myData->myDeferredModified.Append(theNodeId); + return; + } + + // Dispatch modification event for the directly mutated node. + if (myLayerRegistry.HasModificationSubscribers()) + myLayerRegistry.DispatchNodeModified(theNodeId); + + // Propagate SubtreeGen upward to parents (mutex-free). + propagateSubtreeGen(theNodeId); +} + +//================================================================================================= + +void BRepGraph::markRefModified(const BRepGraph_RefId theRefId) noexcept +{ + if (!theRefId.IsValid()) + return; + + BRepGraphInc::BaseRef& aRef = myData->myIncStorage.ChangeBaseRef(theRefId); + markRefModified(theRefId, aRef); +} + +//================================================================================================= + +void BRepGraph::markRefModified(const BRepGraph_RefId /*theRefId*/, + BRepGraphInc::BaseRef& theRef) noexcept +{ + ++theRef.OwnGen; + myData->myPropagationWave.fetch_add(1, std::memory_order_relaxed); + + if (!theRef.ParentId.IsValid()) + return; + + markParentSubtreeGen(theRef.ParentId); +} + +//================================================================================================= + +void BRepGraph::markParentSubtreeGen(const BRepGraph_NodeId theParentId) noexcept +{ + BRepGraphInc::BaseDef* aParent = changeTopoEntity(theParentId); + if (aParent == nullptr) + return; + + const uint32_t aWave = myData->myPropagationWave.load(std::memory_order_relaxed); + + // Re-visit guard: skip if this parent was already processed in the current + // propagation wave. Prevents exponential blowup on diamond topologies. + if (aParent->LastPropWave == aWave) + return; + aParent->LastPropWave = aWave; + + ++aParent->SubtreeGen; // ONLY SubtreeGen - not OwnGen. + // NO mutex, NO shape cache UnBind, NO dispatch. + propagateSubtreeGen(theParentId); +} + +//================================================================================================= + +void BRepGraph::propagateSubtreeGen(const BRepGraph_NodeId theNodeId) noexcept +{ + const BRepGraphInc_ReverseIndex& aRevIdx = myData->myIncStorage.ReverseIndex(); + switch (theNodeId.NodeKind) + { + case BRepGraph_NodeId::Kind::Vertex: + // Vertex modifications don't propagate. + break; + case BRepGraph_NodeId::Kind::Edge: { + const NCollection_Vector* aWires = + aRevIdx.WiresOfEdge(BRepGraph_EdgeId(theNodeId.Index)); + if (aWires != nullptr) + for (const BRepGraph_WireId& aWireId : *aWires) + markParentSubtreeGen(aWireId); + break; + } + case BRepGraph_NodeId::Kind::Wire: { + const NCollection_Vector* aFaces = + aRevIdx.FacesOfWire(BRepGraph_WireId(theNodeId.Index)); + if (aFaces != nullptr) + for (const BRepGraph_FaceId& aFaceId : *aFaces) + markParentSubtreeGen(aFaceId); + break; + } + case BRepGraph_NodeId::Kind::Face: { + const NCollection_Vector* aShells = + aRevIdx.ShellsOfFace(BRepGraph_FaceId(theNodeId.Index)); + if (aShells != nullptr) + for (const BRepGraph_ShellId& aShellId : *aShells) + markParentSubtreeGen(aShellId); + break; + } + case BRepGraph_NodeId::Kind::Shell: { + const NCollection_Vector* aSolids = + aRevIdx.SolidsOfShell(BRepGraph_ShellId(theNodeId.Index)); + if (aSolids != nullptr) + for (const BRepGraph_SolidId& aSolidId : *aSolids) + markParentSubtreeGen(aSolidId); + break; + } + case BRepGraph_NodeId::Kind::Occurrence: { + // Occurrence modifications propagate to the parent product. + const BRepGraphInc::OccurrenceDef& anOccDef = + myData->myIncStorage.Occurrence(BRepGraph_OccurrenceId(theNodeId.Index)); + if (anOccDef.ParentProductDefId.IsValid()) + markParentSubtreeGen(anOccDef.ParentProductDefId); + break; + } + default: + // Solid/Compound/CompSolid/Product modifications don't propagate further. + break; + } +} + +//================================================================================================= + +void BRepGraph::markRepModified(const BRepGraph_RepId theRepId) noexcept +{ + if (!theRepId.IsValid()) + return; + + BRepGraphInc_Storage& aStorage = myData->myIncStorage; + const BRepGraph_RepId::Kind aKind = theRepId.RepKind; + const int anIdx = theRepId.Index; + + // Increment OwnGen on the representation. + switch (aKind) + { + case BRepGraph_RepId::Kind::Surface: + if (anIdx < aStorage.NbSurfaces()) + ++aStorage.ChangeSurfaceRep(BRepGraph_SurfaceRepId(anIdx)).OwnGen; + break; + case BRepGraph_RepId::Kind::Curve3D: + if (anIdx < aStorage.NbCurves3D()) + ++aStorage.ChangeCurve3DRep(BRepGraph_Curve3DRepId(anIdx)).OwnGen; + break; + case BRepGraph_RepId::Kind::Curve2D: + if (anIdx < aStorage.NbCurves2D()) + ++aStorage.ChangeCurve2DRep(BRepGraph_Curve2DRepId(anIdx)).OwnGen; + break; + case BRepGraph_RepId::Kind::Triangulation: + if (anIdx < aStorage.NbTriangulations()) + ++aStorage.ChangeTriangulationRep(BRepGraph_TriangulationRepId(anIdx)).OwnGen; + break; + case BRepGraph_RepId::Kind::Polygon3D: + if (anIdx < aStorage.NbPolygons3D()) + ++aStorage.ChangePolygon3DRep(BRepGraph_Polygon3DRepId(anIdx)).OwnGen; + break; + case BRepGraph_RepId::Kind::Polygon2D: + if (anIdx < aStorage.NbPolygons2D()) + ++aStorage.ChangePolygon2DRep(BRepGraph_Polygon2DRepId(anIdx)).OwnGen; + break; + case BRepGraph_RepId::Kind::PolygonOnTri: + if (anIdx < aStorage.NbPolygonsOnTri()) + ++aStorage.ChangePolygonOnTriRep(BRepGraph_PolygonOnTriRepId(anIdx)).OwnGen; + break; + default: + return; + } + + // Propagate mutation to owning topology nodes. + switch (aKind) + { + case BRepGraph_RepId::Kind::Surface: + for (BRepGraph_Iterator aFaceIt(*this); aFaceIt.More(); aFaceIt.Next()) + if (aFaceIt.Current().SurfaceRepId.Index == anIdx) + markModified(aFaceIt.CurrentId()); + break; + case BRepGraph_RepId::Kind::Curve3D: + for (BRepGraph_Iterator anEdgeIt(*this); anEdgeIt.More(); + anEdgeIt.Next()) + if (anEdgeIt.Current().Curve3DRepId.Index == anIdx) + markModified(anEdgeIt.CurrentId()); + break; + case BRepGraph_RepId::Kind::Curve2D: + for (BRepGraph_Iterator aCoEdgeIt(*this); aCoEdgeIt.More(); + aCoEdgeIt.Next()) + if (aCoEdgeIt.Current().Curve2DRepId.Index == anIdx) + markModified(aCoEdgeIt.CurrentId()); + break; + case BRepGraph_RepId::Kind::Triangulation: + for (BRepGraph_Iterator aFaceIt(*this); aFaceIt.More(); aFaceIt.Next()) + { + const BRepGraphInc::FaceDef& aFace = aFaceIt.Current(); + for (const BRepGraph_TriangulationRepId& aTriRepId : aFace.TriangulationRepIds) + if (aTriRepId.Index == anIdx) + { + markModified(aFaceIt.CurrentId()); + break; + } + } + break; + case BRepGraph_RepId::Kind::Polygon3D: + for (BRepGraph_Iterator anEdgeIt(*this); anEdgeIt.More(); + anEdgeIt.Next()) + if (anEdgeIt.Current().Polygon3DRepId.Index == anIdx) + markModified(anEdgeIt.CurrentId()); + break; + case BRepGraph_RepId::Kind::Polygon2D: + for (BRepGraph_Iterator aCoEdgeIt(*this); aCoEdgeIt.More(); + aCoEdgeIt.Next()) + if (aCoEdgeIt.Current().Polygon2DRepId.Index == anIdx) + markModified(aCoEdgeIt.CurrentId()); + break; + case BRepGraph_RepId::Kind::PolygonOnTri: + for (BRepGraph_Iterator aCoEdgeIt(*this); aCoEdgeIt.More(); + aCoEdgeIt.Next()) + { + const BRepGraphInc::CoEdgeDef& aCoEdge = aCoEdgeIt.Current(); + for (const BRepGraph_PolygonOnTriRepId& aPolyRepId : aCoEdge.PolygonOnTriRepIds) + { + if (aPolyRepId.Index == anIdx) + { + markModified(aCoEdgeIt.CurrentId()); + break; + } + } + } + break; + default: + break; + } +} + +//================================================================================================= + +void BRepGraph::SetAllocator(const occ::handle& theAlloc) +{ + Standard_ASSERT_VOID( + !myData->myIsDone, + "SetAllocator: must be called before Build() - existing graph state will be lost"); + + myData->myAllocator = + !theAlloc.IsNull() ? theAlloc : NCollection_BaseAllocator::CommonBaseAllocator(); + + // Recreate the entire data object with the new allocator. + myData = std::make_unique(myData->myAllocator); + initViews(); +} + +const occ::handle& BRepGraph::Allocator() const +{ + return myData->myAllocator; +} + +BRepGraph_History& BRepGraph::History() +{ + return myData->myHistoryLog; +} + +//================================================================================================= + +const BRepGraph_History& BRepGraph::History() const +{ + return myData->myHistoryLog; +} + +//================================================================================================= + +BRepGraph_LayerRegistry& BRepGraph::LayerRegistry() +{ + return myLayerRegistry; +} + +//================================================================================================= + +const BRepGraph_LayerRegistry& BRepGraph::LayerRegistry() const +{ + return myLayerRegistry; +} + +//================================================================================================= + +BRepGraphInc_Storage& BRepGraph::incStorage() +{ + return myData->myIncStorage; +} + +//================================================================================================= + +const BRepGraphInc_Storage& BRepGraph::incStorage() const +{ + return myData->myIncStorage; +} + +//================================================================================================= + +BRepGraph_Data* BRepGraph::data() +{ + return myData.get(); +} + +//================================================================================================= + +const BRepGraph_Data* BRepGraph::data() const +{ + return myData.get(); +} + +//================================================================================================= + +BRepGraph_LayerRegistry& BRepGraph::layerRegistry() +{ + return myLayerRegistry; +} + +//================================================================================================= + +const BRepGraph_LayerRegistry& BRepGraph::layerRegistry() const +{ + return myLayerRegistry; +} + +//================================================================================================= + +BRepGraph_TransientCache& BRepGraph::transientCache() +{ + return myTransientCache; +} + +//================================================================================================= + +const BRepGraph_TransientCache& BRepGraph::transientCache() const +{ + return myTransientCache; +} diff --git a/src/ModelingData/TKBRep/BRepGraph/BRepGraph.hxx b/src/ModelingData/TKBRep/BRepGraph/BRepGraph.hxx new file mode 100644 index 0000000000..1c6530e91c --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/BRepGraph.hxx @@ -0,0 +1,270 @@ +// Copyright (c) 2026 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 _BRepGraph_HeaderFile +#define _BRepGraph_HeaderFile + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include + +template +class BRepGraph_MutGuard; + +struct BRepGraph_Data; +class BRepGraph_Layer; +class NCollection_BaseAllocator; +class TCollection_AsciiString; + +class BRepGraph_Builder; +class BRepGraph_History; + +//! @brief Topology-geometry graph over TopoDS / BRep. +//! +//! Stores B-Rep topology as flat entity vectors (incidence-table model) with +//! integer cross-references, enabling cache-friendly traversal, O(1) upward +//! navigation via reverse indices, and parallel face-level geometry extraction. +//! +//! Key design concepts: +//! - **NodeId** (Kind + Index): lightweight typed address into per-kind vectors. +//! - **UID** (Kind + Counter): persistent identity surviving compaction/reorder. +//! - **RepId** (Kind + Index): separate geometry/mesh addressing (Surface, +//! Curve3D, Curve2D, Triangulation, Polygon) decoupled from topology nodes. +//! - **CoEdge**: half-edge entity owning PCurve data for each edge-face binding; +//! seam edges use paired CoEdges with opposite Sense (Parasolid convention). +//! - **Lifecycle**: Build() populates from TopoDS_Shape; Builder().Mut*() guards +//! provide RAII-scoped mutation with automatic cache invalidation and upward +//! propagation. +//! +//! Per-occurrence data (orientation, location) lives on incidence refs. +//! Definition types are aliases to BRepGraphInc entity structs. +//! +//! ## Grouped View API +//! Related methods are grouped behind lightweight view objects. +//! Include the corresponding header (e.g. BRepGraph_TopoView.hxx) to use. +//! +//! ## Thread safety +//! Const query methods are safe for concurrent reads. +//! Concurrent reads during active mutation still require external synchronization. +//! Deferred invalidation (BRepGraph_DeferredScope) batches SubtreeGen propagation; +//! concurrent Mut*() calls during deferred mode still require external serialization. +//! Build() is internally parallel when requested. +//! +//! ## UID persistence +//! UIDs use monotonic counters (not vector indices), persisting across Compact() +//! and node removal. Only Build() resets counters (new generation). +//! See BRepGraph_UID.hxx for the serialization contract. +//! +//! ## Extension model +//! Extend via BRepGraph_Layer (per-node attributes) or BRepGraph_TransientCache +//! (algorithm-computed caches). Direct storage extension is not supported. +class BRepGraph +{ +public: + DEFINE_STANDARD_ALLOC + + BRepGraph(const BRepGraph&) = delete; + BRepGraph& operator=(const BRepGraph&) = delete; + + //! Default constructor. Creates an empty graph with default allocator. + Standard_EXPORT BRepGraph(); + //! Construct with a custom allocator for internal collections. + //! @param[in] theAlloc allocator for internal collections (null uses CommonBaseAllocator) + Standard_EXPORT explicit BRepGraph(const occ::handle& theAlloc); + //! Destructor. + Standard_EXPORT ~BRepGraph(); + //! Move constructor. + Standard_EXPORT BRepGraph(BRepGraph&&) noexcept; + //! Move assignment operator. + Standard_EXPORT BRepGraph& operator=(BRepGraph&&) noexcept; + + //! Build the full graph from a TopoDS_Shape using default populate options. + //! Equivalent to passing a default-constructed BRepGraphInc_Populate::Options, + //! which enables both ExtractRegularities and ExtractVertexPointReps. + //! Use the overload with explicit BRepGraphInc_Populate::Options when the + //! caller needs control over those optional extraction passes. + Standard_EXPORT void Build(const TopoDS_Shape& theShape, const bool theParallel = false); + + //! Build the full graph with explicit populate options. + //! Use this overload when the caller must enable or disable optional + //! extraction passes such as regularity or vertex-point representations + //! through BRepGraphInc_Populate::Options::ExtractRegularities and + //! BRepGraphInc_Populate::Options::ExtractVertexPointReps. + Standard_EXPORT void Build(const TopoDS_Shape& theShape, + const bool theParallel, + const BRepGraphInc_Populate::Options& theOptions); + + //! Return true if the graph was successfully built. + [[nodiscard]] Standard_EXPORT bool IsDone() const; + + //! Return root topology NodeIds created by Build() and append operations. + //! Build() contributes the single top-level node of the input shape. + //! AppendFlattenedShape() contributes the nodes actually created by the + //! flattened append: one Vertex/Edge/Wire/Face root for direct inputs, or + //! one Face root per appended face for Shell/Solid/Compound/CompSolid inputs. + //! Returns empty vector if the graph has not been built. + [[nodiscard]] Standard_EXPORT const NCollection_Vector& RootNodeIds() const; + + //! Replace the internal allocator and re-create all storage. + Standard_EXPORT void SetAllocator(const occ::handle& theAlloc); + + //! Return the current allocator. + [[nodiscard]] Standard_EXPORT const occ::handle& Allocator() const; + +public: + //! Shared cache for edge/vertex shapes during multi-face reconstruction. + using ReconstructCache = NCollection_DataMap; + + //! @name Grouped View API + class TopoView; + class UIDsView; + class CacheView; + class RefsView; + class ShapesView; + class BuilderView; + + //! Access topology definitions, representation access, adjacency queries, + //! raw Product/Occurrence definition storage, and assembly classification. + [[nodiscard]] Standard_EXPORT const TopoView& Topo() const; + //! Access unique identifiers. + [[nodiscard]] Standard_EXPORT const UIDsView& UIDs() const; + //! Access transient cache values through the stable grouped-view API. + //! This is the only public cache interface. + [[nodiscard]] Standard_EXPORT CacheView& Cache(); + //! Access transient cache values (const, read-only Get/Has/CacheKinds). + //! This is the only public cache interface. + [[nodiscard]] Standard_EXPORT const CacheView& Cache() const; + //! Access reference entries and their UIDs. + [[nodiscard]] Standard_EXPORT const RefsView& Refs() const; + //! Access cached and fresh shape reconstruction. + [[nodiscard]] Standard_EXPORT const ShapesView& Shapes() const; + //! Access programmatic graph construction and mutation. + [[nodiscard]] Standard_EXPORT BuilderView& Builder(); + //! Const access is for BuilderView inspection only. + //! Mutation methods require non-const Builder(). + [[nodiscard]] Standard_EXPORT const BuilderView& Builder() const; + + //! Access history subsystem directly. + //! History is returned directly rather than through a lightweight view + //! because it is already a self-contained query and recording subsystem + //! with no per-view cached state. + //! @return history subsystem for tracking modifications + [[nodiscard]] Standard_EXPORT BRepGraph_History& History(); + //! Access history subsystem directly (const). + //! @return history subsystem for tracking modifications + [[nodiscard]] Standard_EXPORT const BRepGraph_History& History() const; + + //! Access registered graph layers. + //! @return layer registry for managing attribute layers + [[nodiscard]] Standard_EXPORT BRepGraph_LayerRegistry& LayerRegistry(); + //! Access registered graph layers (const). + //! @return layer registry for managing attribute layers + [[nodiscard]] Standard_EXPORT const BRepGraph_LayerRegistry& LayerRegistry() const; + +private: + friend class BRepGraph_Builder; + friend class BRepGraph_Compact; + friend class BRepGraph_Copy; + friend class BRepGraph_Transform; + template + friend class BRepGraph_MutGuard; + + //! @name Private accessors for friend classes + //! These provide controlled access to internal state for algorithms and builders. + //! @{ + + //! Access the underlying storage. + [[nodiscard]] Standard_EXPORT BRepGraphInc_Storage& incStorage(); + [[nodiscard]] Standard_EXPORT const BRepGraphInc_Storage& incStorage() const; + + //! Access the graph data structure. + [[nodiscard]] Standard_EXPORT BRepGraph_Data* data(); + [[nodiscard]] Standard_EXPORT const BRepGraph_Data* data() const; + + //! Access the layer registry. + [[nodiscard]] Standard_EXPORT BRepGraph_LayerRegistry& layerRegistry(); + [[nodiscard]] Standard_EXPORT const BRepGraph_LayerRegistry& layerRegistry() const; + + //! Access the raw transient cache for friend algorithms and builders. + [[nodiscard]] Standard_EXPORT BRepGraph_TransientCache& transientCache(); + [[nodiscard]] Standard_EXPORT const BRepGraph_TransientCache& transientCache() const; + + //! @} + + Standard_EXPORT void invalidateSubgraphImpl(const BRepGraph_NodeId theNode); + Standard_EXPORT BRepGraph_UID allocateUID(const BRepGraph_NodeId theNodeId); + Standard_EXPORT BRepGraph_RefUID allocateRefUID(const BRepGraph_RefId theRefId); + + Standard_EXPORT void markModified(const BRepGraph_NodeId theNodeId) noexcept; + Standard_EXPORT void markRefModified(const BRepGraph_RefId theRefId) noexcept; + + //! Optimized overload: skips changeTopoEntity() dispatch + //! when the caller already holds a mutable reference to the target entity. + Standard_EXPORT void markModified(const BRepGraph_NodeId theNodeId, + BRepGraphInc::BaseDef& theEntity) noexcept; + Standard_EXPORT void markRefModified(const BRepGraph_RefId theRefId, + BRepGraphInc::BaseRef& theRef) noexcept; + + //! Increment SubtreeGen on a parent node (NOT OwnGen - parent's own data didn't change). + //! Uses wave guard to prevent exponential blowup on diamond topologies. + //! Mutex-free: no shape cache clear, no dispatch. + Standard_EXPORT void markParentSubtreeGen(const BRepGraph_NodeId theParentId) noexcept; + + //! Propagate SubtreeGen upward through reverse indices via markParentSubtreeGen(). + Standard_EXPORT void propagateSubtreeGen(const BRepGraph_NodeId theNodeId) noexcept; + + //! Increment OwnGen on a representation and propagate mutation + //! to the owning topology node(s). + Standard_EXPORT void markRepModified(const BRepGraph_RepId theRepId) noexcept; + + //! Generic topology definition lookup by NodeId (const). + Standard_EXPORT const BRepGraphInc::BaseDef* topoEntity(const BRepGraph_NodeId theId) const; + + //! Generic mutable topology definition lookup by NodeId. + Standard_EXPORT BRepGraphInc::BaseDef* changeTopoEntity(const BRepGraph_NodeId theId); + + //! Initialize cached view objects to point to this graph. + Standard_EXPORT void initViews(); + + // Fields at the bottom (OCCT style) + std::unique_ptr myData; + + //! Registered layers are stored on BRepGraph, not BRepGraph_Data, to survive Compact swap. + BRepGraph_LayerRegistry myLayerRegistry; + BRepGraph_TransientCache myTransientCache; //!< Transient algorithm caches (BndBox, UVBounds) +}; + +// Included after BRepGraph is complete so the template body sees markModified(). +#include + +#endif // _BRepGraph_HeaderFile diff --git a/src/ModelingData/TKBRep/BRepGraph/BRepGraph_Builder.cxx b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_Builder.cxx new file mode 100644 index 0000000000..a9608089f1 --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_Builder.cxx @@ -0,0 +1,440 @@ +// Copyright (c) 2026 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 + +namespace +{ + +//! Generate a random Standard_GUID using MathUtils::RandomGenerator +//! seeded with std::random_device for platform entropy. +static Standard_GUID generateRandomGUID() +{ + std::random_device aRD; + MathUtils::RandomGenerator aRNG(aRD()); + // Fill 16 bytes with random data, then construct Standard_UUID field by field. + const uint64_t aRand1 = aRNG.NextInt(); + const uint64_t aRand2 = aRNG.NextInt(); + Standard_UUID aUUID; + aUUID.Data1 = static_cast(aRand1); + aUUID.Data2 = static_cast(aRand1 >> 32); + aUUID.Data3 = static_cast(aRand1 >> 48); + for (int i = 0; i < 8; ++i) + aUUID.Data4[i] = static_cast(aRand2 >> (i * 8)); + return Standard_GUID(aUUID); +} + +} // namespace + +//================================================================================================= + +void BRepGraph_Builder::populateUIDs(BRepGraph& theGraph) +{ + BRepGraphInc_Storage& aStorage = theGraph.myData->myIncStorage; + + if (!aStorage.GetIsDone()) + return; + + const int aNbVertices = aStorage.NbVertices(); + for (BRepGraph_VertexId aVertexId(0); aVertexId.IsValid(aNbVertices); ++aVertexId) + theGraph.allocateUID(aStorage.Vertex(aVertexId).Id); + const int aNbEdges = aStorage.NbEdges(); + for (BRepGraph_EdgeId anEdgeId(0); anEdgeId.IsValid(aNbEdges); ++anEdgeId) + theGraph.allocateUID(aStorage.Edge(anEdgeId).Id); + const int aNbCoEdges = aStorage.NbCoEdges(); + for (BRepGraph_CoEdgeId aCoEdgeId(0); aCoEdgeId.IsValid(aNbCoEdges); ++aCoEdgeId) + theGraph.allocateUID(aStorage.CoEdge(aCoEdgeId).Id); + const int aNbWires = aStorage.NbWires(); + for (BRepGraph_WireId aWireId(0); aWireId.IsValid(aNbWires); ++aWireId) + theGraph.allocateUID(aStorage.Wire(aWireId).Id); + const int aNbFaces = aStorage.NbFaces(); + for (BRepGraph_FaceId aFaceId(0); aFaceId.IsValid(aNbFaces); ++aFaceId) + theGraph.allocateUID(aStorage.Face(aFaceId).Id); + const int aNbShells = aStorage.NbShells(); + for (BRepGraph_ShellId aShellId(0); aShellId.IsValid(aNbShells); ++aShellId) + theGraph.allocateUID(aStorage.Shell(aShellId).Id); + const int aNbSolids = aStorage.NbSolids(); + for (BRepGraph_SolidId aSolidId(0); aSolidId.IsValid(aNbSolids); ++aSolidId) + theGraph.allocateUID(aStorage.Solid(aSolidId).Id); + const int aNbCompounds = aStorage.NbCompounds(); + for (BRepGraph_CompoundId aCompoundId(0); aCompoundId.IsValid(aNbCompounds); ++aCompoundId) + theGraph.allocateUID(aStorage.Compound(aCompoundId).Id); + const int aNbCompSolids = aStorage.NbCompSolids(); + for (BRepGraph_CompSolidId aCompSolidId(0); aCompSolidId.IsValid(aNbCompSolids); ++aCompSolidId) + theGraph.allocateUID(aStorage.CompSolid(aCompSolidId).Id); + const int aNbProducts = aStorage.NbProducts(); + for (BRepGraph_ProductId aProductId(0); aProductId.IsValid(aNbProducts); ++aProductId) + theGraph.allocateUID(aStorage.Product(aProductId).Id); + const int aNbOccurrences = aStorage.NbOccurrences(); + for (BRepGraph_OccurrenceId anOccurrenceId(0); anOccurrenceId.IsValid(aNbOccurrences); + ++anOccurrenceId) + theGraph.allocateUID(aStorage.Occurrence(anOccurrenceId).Id); + + const int aNbShellRefs = aStorage.NbShellRefs(); + for (BRepGraph_ShellRefId aShellRefId(0); aShellRefId.IsValid(aNbShellRefs); ++aShellRefId) + theGraph.allocateRefUID(aShellRefId); + const int aNbFaceRefs = aStorage.NbFaceRefs(); + for (BRepGraph_FaceRefId aFaceRefId(0); aFaceRefId.IsValid(aNbFaceRefs); ++aFaceRefId) + theGraph.allocateRefUID(aFaceRefId); + const int aNbWireRefs = aStorage.NbWireRefs(); + for (BRepGraph_WireRefId aWireRefId(0); aWireRefId.IsValid(aNbWireRefs); ++aWireRefId) + theGraph.allocateRefUID(aWireRefId); + const int aNbCoEdgeRefs = aStorage.NbCoEdgeRefs(); + for (BRepGraph_CoEdgeRefId aCoEdgeRefId(0); aCoEdgeRefId.IsValid(aNbCoEdgeRefs); ++aCoEdgeRefId) + theGraph.allocateRefUID(aCoEdgeRefId); + const int aNbVertexRefs = aStorage.NbVertexRefs(); + for (BRepGraph_VertexRefId aVertexRefId(0); aVertexRefId.IsValid(aNbVertexRefs); ++aVertexRefId) + theGraph.allocateRefUID(aVertexRefId); + const int aNbSolidRefs = aStorage.NbSolidRefs(); + for (BRepGraph_SolidRefId aSolidRefId(0); aSolidRefId.IsValid(aNbSolidRefs); ++aSolidRefId) + theGraph.allocateRefUID(aSolidRefId); + const int aNbChildRefs = aStorage.NbChildRefs(); + for (BRepGraph_ChildRefId aChildRefId(0); aChildRefId.IsValid(aNbChildRefs); ++aChildRefId) + theGraph.allocateRefUID(aChildRefId); +} + +//================================================================================================= + +void BRepGraph_Builder::Perform(BRepGraph& theGraph, + const TopoDS_Shape& theShape, + const bool theParallel) +{ + Perform(theGraph, theShape, theParallel, BRepGraphInc_Populate::Options()); +} + +//================================================================================================= + +void BRepGraph_Builder::Perform(BRepGraph& theGraph, + const TopoDS_Shape& theShape, + const bool theParallel, + const BRepGraphInc_Populate::Options& theOptions) +{ + theGraph.myData->myIncStorage.Clear(); + theGraph.myData->myHistoryLog.Clear(); + theGraph.myData->myCurrentShapes.Clear(); + theGraph.myData->myRootNodeIds.Clear(); + theGraph.myTransientCache.Clear(); + { + std::unique_lock aUIDLock(theGraph.myData->myUIDToNodeIdMutex); + theGraph.myData->myUIDToNodeId.Clear(); + theGraph.myData->myUIDToNodeIdDirty = true; + theGraph.myData->myUIDToNodeIdGeneration = theGraph.myData->myGeneration.load(); + } + { + std::unique_lock aRefUIDLock(theGraph.myData->myRefUIDToRefIdMutex); + theGraph.myData->myRefUIDToRefId.Clear(); + theGraph.myData->myRefUIDToRefIdDirty = true; + theGraph.myData->myRefUIDToRefIdGeneration = theGraph.myData->myGeneration.load(); + } + ++theGraph.myData->myGeneration; + theGraph.myData->myGraphGUID = generateRandomGUID(); + theGraph.myData->myIsDone = false; + + // Notify registered layers that graph data is being cleared. + theGraph.myLayerRegistry.ClearAll(); + + if (theShape.IsNull()) + return; + + // Temporary allocator for populate scratch data, discarded after build. + occ::handle aTmpAlloc = new NCollection_IncAllocator; + const occ::handle aParamLayer = + theGraph.LayerRegistry().FindLayer(); + const occ::handle aRegularityLayer = + theGraph.LayerRegistry().FindLayer(); + + BRepGraphInc_Populate::Perform(theGraph.myData->myIncStorage, + theShape, + theParallel, + theOptions, + aParamLayer.get(), + aRegularityLayer.get(), + aTmpAlloc); + if (!theGraph.myData->myIncStorage.GetIsDone()) + { + theGraph.myData->myIncStorage.Clear(); + return; + } + + populateUIDs(theGraph); + + // Auto-create a single root Product pointing to the top-level topology node. + // aTopologyRoot defaults to invalid (Index = -1); set only if topology exists. + { + BRepGraphInc_Storage& aStorage = theGraph.myData->myIncStorage; + BRepGraph_NodeId aTopologyRoot; // default: invalid (Index = -1) + switch (theShape.ShapeType()) + { + case TopAbs_COMPOUND: + if (aStorage.NbCompounds() > 0) + aTopologyRoot = BRepGraph_CompoundId(0); + break; + case TopAbs_COMPSOLID: + if (aStorage.NbCompSolids() > 0) + aTopologyRoot = BRepGraph_CompSolidId(0); + break; + case TopAbs_SOLID: + if (aStorage.NbSolids() > 0) + aTopologyRoot = BRepGraph_SolidId(0); + break; + case TopAbs_SHELL: + if (aStorage.NbShells() > 0) + aTopologyRoot = BRepGraph_ShellId(0); + break; + case TopAbs_FACE: + if (aStorage.NbFaces() > 0) + aTopologyRoot = BRepGraph_FaceId(0); + break; + case TopAbs_WIRE: + if (aStorage.NbWires() > 0) + aTopologyRoot = BRepGraph_WireId(0); + break; + case TopAbs_EDGE: + if (aStorage.NbEdges() > 0) + aTopologyRoot = BRepGraph_EdgeId(0); + break; + case TopAbs_VERTEX: + if (aStorage.NbVertices() > 0) + aTopologyRoot = BRepGraph_VertexId(0); + break; + default: + break; + } + + if (aTopologyRoot.IsValid()) + theGraph.myData->myRootNodeIds.Append(aTopologyRoot); + + BRepGraphInc::ProductDef& aProduct = aStorage.AppendProduct(); + const int aProductIdx = aStorage.NbProducts() - 1; + aProduct.Id = BRepGraph_ProductId(aProductIdx); + aProduct.ShapeRootId = aTopologyRoot; // invalid if no topology matched + aProduct.RootOrientation = theShape.Orientation(); + aProduct.RootLocation = theShape.Location(); + theGraph.allocateUID(aProduct.Id); + } + + theGraph.myData->myIsDone = true; + + // Pre-allocate transient cache for lock-free parallel access. + // Entity counts are now final - Reserve() sizes dense vectors so that + // Get()/Set() skip the mutex for in-range indices. + { + BRepGraphInc_Storage& aStorage = theGraph.myData->myIncStorage; + int aCounts[BRepGraph_TransientCache::THE_KIND_COUNT] = {}; + aCounts[static_cast(BRepGraph_NodeId::Kind::Vertex)] = aStorage.NbVertices(); + aCounts[static_cast(BRepGraph_NodeId::Kind::Edge)] = aStorage.NbEdges(); + aCounts[static_cast(BRepGraph_NodeId::Kind::CoEdge)] = aStorage.NbCoEdges(); + aCounts[static_cast(BRepGraph_NodeId::Kind::Wire)] = aStorage.NbWires(); + aCounts[static_cast(BRepGraph_NodeId::Kind::Face)] = aStorage.NbFaces(); + aCounts[static_cast(BRepGraph_NodeId::Kind::Shell)] = aStorage.NbShells(); + aCounts[static_cast(BRepGraph_NodeId::Kind::Solid)] = aStorage.NbSolids(); + aCounts[static_cast(BRepGraph_NodeId::Kind::Compound)] = aStorage.NbCompounds(); + aCounts[static_cast(BRepGraph_NodeId::Kind::CompSolid)] = aStorage.NbCompSolids(); + aCounts[static_cast(BRepGraph_NodeId::Kind::Product)] = aStorage.NbProducts(); + aCounts[static_cast(BRepGraph_NodeId::Kind::Occurrence)] = aStorage.NbOccurrences(); + int aReservedKindCount = BRepGraph_TransientCache::THE_DEFAULT_RESERVED_KIND_COUNT; + const int aRegisteredKindCount = BRepGraph_CacheKindRegistry::NbRegistered(); + if (aRegisteredKindCount > aReservedKindCount) + { + aReservedKindCount = aRegisteredKindCount; + } + theGraph.myTransientCache.Reserve(aReservedKindCount, aCounts); + } +} + +//================================================================================================= + +void BRepGraph_Builder::AppendFlattened(BRepGraph& theGraph, + const TopoDS_Shape& theShape, + const bool theParallel) +{ + if (theShape.IsNull()) + return; + + // Snapshot entity counts before append to allocate UIDs only for new entities. + BRepGraphInc_Storage& aStorage = theGraph.myData->myIncStorage; + const int anOldVtx = aStorage.NbVertices(); + const int anOldEdge = aStorage.NbEdges(); + const int anOldCoEdge = aStorage.NbCoEdges(); + const int anOldWire = aStorage.NbWires(); + const int anOldFace = aStorage.NbFaces(); + const int anOldShell = aStorage.NbShells(); + const int anOldSolid = aStorage.NbSolids(); + const int anOldComp = aStorage.NbCompounds(); + const int anOldCS = aStorage.NbCompSolids(); + const int anOldProduct = aStorage.NbProducts(); + const int anOldOccurrence = aStorage.NbOccurrences(); + const int anOldShellRef = aStorage.NbShellRefs(); + const int anOldFaceRef = aStorage.NbFaceRefs(); + const int anOldWireRef = aStorage.NbWireRefs(); + const int anOldCoEdgeRef = aStorage.NbCoEdgeRefs(); + const int anOldVertexRef = aStorage.NbVertexRefs(); + const int anOldSolidRef = aStorage.NbSolidRefs(); + const int anOldChildRef = aStorage.NbChildRefs(); + + occ::handle aTmpAlloc = new NCollection_IncAllocator; + const occ::handle aParamLayer = + theGraph.LayerRegistry().FindLayer(); + const occ::handle aRegularityLayer = + theGraph.LayerRegistry().FindLayer(); + NCollection_Vector aAppendedRoots(8, theGraph.Allocator()); + BRepGraphInc_Populate::AppendFlattened(aStorage, + theShape, + theParallel, + aAppendedRoots, + BRepGraphInc_Populate::Options(), + aParamLayer.get(), + aRegularityLayer.get(), + aTmpAlloc); + + if (!aStorage.GetIsDone()) + return; + + theGraph.myData->myCurrentShapes.Clear(); + + populateUIDsIncremental(theGraph, + anOldVtx, + anOldEdge, + anOldCoEdge, + anOldWire, + anOldFace, + anOldShell, + anOldSolid, + anOldComp, + anOldCS, + anOldProduct, + anOldOccurrence, + anOldShellRef, + anOldFaceRef, + anOldWireRef, + anOldCoEdgeRef, + anOldVertexRef, + anOldSolidRef, + anOldChildRef); + + for (const BRepGraph_NodeId& aRootNode : aAppendedRoots) + { + theGraph.myData->myRootNodeIds.Append(aRootNode); + } + + theGraph.myData->myIsDone = true; +} + +//================================================================================================= + +void BRepGraph_Builder::populateUIDsIncremental(BRepGraph& theGraph, + const int theOldVtx, + const int theOldEdge, + const int theOldCoEdge, + const int theOldWire, + const int theOldFace, + const int theOldShell, + const int theOldSolid, + const int theOldComp, + const int theOldCS, + const int theOldProduct, + const int theOldOccurrence, + const int theOldShellRef, + const int theOldFaceRef, + const int theOldWireRef, + const int theOldCoEdgeRef, + const int theOldVertexRef, + const int theOldSolidRef, + const int theOldChildRef) +{ + BRepGraphInc_Storage& aStorage = theGraph.myData->myIncStorage; + + if (!aStorage.GetIsDone()) + return; + + const int aNbVertices = aStorage.NbVertices(); + for (BRepGraph_VertexId aVertexId(theOldVtx); aVertexId.IsValid(aNbVertices); ++aVertexId) + theGraph.allocateUID(aStorage.Vertex(aVertexId).Id); + const int aNbEdges = aStorage.NbEdges(); + for (BRepGraph_EdgeId anEdgeId(theOldEdge); anEdgeId.IsValid(aNbEdges); ++anEdgeId) + theGraph.allocateUID(aStorage.Edge(anEdgeId).Id); + const int aNbCoEdges = aStorage.NbCoEdges(); + for (BRepGraph_CoEdgeId aCoEdgeId(theOldCoEdge); aCoEdgeId.IsValid(aNbCoEdges); ++aCoEdgeId) + theGraph.allocateUID(aStorage.CoEdge(aCoEdgeId).Id); + const int aNbWires = aStorage.NbWires(); + for (BRepGraph_WireId aWireId(theOldWire); aWireId.IsValid(aNbWires); ++aWireId) + theGraph.allocateUID(aStorage.Wire(aWireId).Id); + const int aNbFaces = aStorage.NbFaces(); + for (BRepGraph_FaceId aFaceId(theOldFace); aFaceId.IsValid(aNbFaces); ++aFaceId) + theGraph.allocateUID(aStorage.Face(aFaceId).Id); + const int aNbShells = aStorage.NbShells(); + for (BRepGraph_ShellId aShellId(theOldShell); aShellId.IsValid(aNbShells); ++aShellId) + theGraph.allocateUID(aStorage.Shell(aShellId).Id); + const int aNbSolids = aStorage.NbSolids(); + for (BRepGraph_SolidId aSolidId(theOldSolid); aSolidId.IsValid(aNbSolids); ++aSolidId) + theGraph.allocateUID(aStorage.Solid(aSolidId).Id); + const int aNbCompounds = aStorage.NbCompounds(); + for (BRepGraph_CompoundId aCompoundId(theOldComp); aCompoundId.IsValid(aNbCompounds); + ++aCompoundId) + theGraph.allocateUID(aStorage.Compound(aCompoundId).Id); + const int aNbCompSolids = aStorage.NbCompSolids(); + for (BRepGraph_CompSolidId aCompSolidId(theOldCS); aCompSolidId.IsValid(aNbCompSolids); + ++aCompSolidId) + theGraph.allocateUID(aStorage.CompSolid(aCompSolidId).Id); + const int aNbProducts = aStorage.NbProducts(); + for (BRepGraph_ProductId aProductId(theOldProduct); aProductId.IsValid(aNbProducts); ++aProductId) + theGraph.allocateUID(aStorage.Product(aProductId).Id); + const int aNbOccurrences = aStorage.NbOccurrences(); + for (BRepGraph_OccurrenceId anOccurrenceId(theOldOccurrence); + anOccurrenceId.IsValid(aNbOccurrences); + ++anOccurrenceId) + theGraph.allocateUID(aStorage.Occurrence(anOccurrenceId).Id); + + const int aNbShellRefs = aStorage.NbShellRefs(); + for (BRepGraph_ShellRefId aShellRefId(theOldShellRef); aShellRefId.IsValid(aNbShellRefs); + ++aShellRefId) + theGraph.allocateRefUID(aShellRefId); + const int aNbFaceRefs = aStorage.NbFaceRefs(); + for (BRepGraph_FaceRefId aFaceRefId(theOldFaceRef); aFaceRefId.IsValid(aNbFaceRefs); ++aFaceRefId) + theGraph.allocateRefUID(aFaceRefId); + const int aNbWireRefs = aStorage.NbWireRefs(); + for (BRepGraph_WireRefId aWireRefId(theOldWireRef); aWireRefId.IsValid(aNbWireRefs); ++aWireRefId) + theGraph.allocateRefUID(aWireRefId); + const int aNbCoEdgeRefs = aStorage.NbCoEdgeRefs(); + for (BRepGraph_CoEdgeRefId aCoEdgeRefId(theOldCoEdgeRef); aCoEdgeRefId.IsValid(aNbCoEdgeRefs); + ++aCoEdgeRefId) + theGraph.allocateRefUID(aCoEdgeRefId); + const int aNbVertexRefs = aStorage.NbVertexRefs(); + for (BRepGraph_VertexRefId aVertexRefId(theOldVertexRef); aVertexRefId.IsValid(aNbVertexRefs); + ++aVertexRefId) + theGraph.allocateRefUID(aVertexRefId); + const int aNbSolidRefs = aStorage.NbSolidRefs(); + for (BRepGraph_SolidRefId aSolidRefId(theOldSolidRef); aSolidRefId.IsValid(aNbSolidRefs); + ++aSolidRefId) + theGraph.allocateRefUID(aSolidRefId); + const int aNbChildRefs = aStorage.NbChildRefs(); + for (BRepGraph_ChildRefId aChildRefId(theOldChildRef); aChildRefId.IsValid(aNbChildRefs); + ++aChildRefId) + theGraph.allocateRefUID(aChildRefId); +} diff --git a/src/ModelingData/TKBRep/BRepGraph/BRepGraph_Builder.hxx b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_Builder.hxx new file mode 100644 index 0000000000..8cec79d222 --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_Builder.hxx @@ -0,0 +1,88 @@ +// Copyright (c) 2026 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 _BRepGraph_Builder_HeaderFile +#define _BRepGraph_Builder_HeaderFile + +#include +#include + +class BRepGraph; +class TopoDS_Shape; + +//! @brief Static helper that populates a BRepGraph from a TopoDS_Shape. +//! +//! BRepGraph_Builder extracts the build logic out of BRepGraph itself, +//! keeping the graph class focused on queries and mutation. +//! It is declared as a friend of BRepGraph to access private storage. +class BRepGraph_Builder +{ +public: + DEFINE_STANDARD_ALLOC + + //! Build the full graph from a TopoDS_Shape (clears existing data first). + //! @param[in,out] theGraph graph to populate + //! @param[in] theShape root shape + //! @param[in] theParallel if true, face-level construction runs in parallel + static Standard_EXPORT void Perform(BRepGraph& theGraph, + const TopoDS_Shape& theShape, + const bool theParallel); + + //! Build the full graph with explicit post-pass control. + static Standard_EXPORT void Perform(BRepGraph& theGraph, + const TopoDS_Shape& theShape, + const bool theParallel, + const BRepGraphInc_Populate::Options& theOptions); + + //! Append a shape to the existing graph without clearing. + //! Flattens hierarchy containers away. Solid/Shell/Compound/CompSolid inputs + //! append their contained faces as roots instead of adding container nodes. + //! Uses existing deduplication maps to avoid re-registering shared entities. + //! @param[in,out] theGraph graph to extend + //! @param[in] theShape shape to add + //! @param[in] theParallel if true, per-face geometry extraction is parallel + static Standard_EXPORT void AppendFlattened(BRepGraph& theGraph, + const TopoDS_Shape& theShape, + const bool theParallel); + +private: + //! Allocate UIDs for all incidence entities after BRepGraphInc_Populate + //! has filled the storage. + static void populateUIDs(BRepGraph& theGraph); + + //! Allocate UIDs only for entities in range [theOld, current) per kind. + //! Used by AppendFlattened() to avoid re-walking existing entities. + static void populateUIDsIncremental(BRepGraph& theGraph, + const int theOldVtx, + const int theOldEdge, + const int theOldCoEdge, + const int theOldWire, + const int theOldFace, + const int theOldShell, + const int theOldSolid, + const int theOldComp, + const int theOldCS, + const int theOldProduct, + const int theOldOccurrence, + const int theOldShellRef, + const int theOldFaceRef, + const int theOldWireRef, + const int theOldCoEdgeRef, + const int theOldVertexRef, + const int theOldSolidRef, + const int theOldChildRef); + + BRepGraph_Builder() = delete; +}; + +#endif // _BRepGraph_Builder_HeaderFile diff --git a/src/ModelingData/TKBRep/BRepGraph/BRepGraph_BuilderView.cxx b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_BuilderView.cxx new file mode 100644 index 0000000000..e221a6a79e --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_BuilderView.cxx @@ -0,0 +1,2379 @@ +// Copyright (c) 2026 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 +#include +#include + +#include + +namespace +{ + +//! Check if a child node has any active parent besides theExcludedParent. +//! Uses ParentExplorer(DirectParents) which automatically skips removed parents. +bool hasOtherActiveParent(const BRepGraph& theGraph, + const BRepGraph_NodeId theChild, + const BRepGraph_NodeId theExcludedParent) +{ + for (BRepGraph_ParentExplorer anExp(theGraph, + theChild, + BRepGraph_ParentExplorer::TraversalMode::DirectParents); + anExp.More(); + anExp.Next()) + { + if (anExp.Current().DefId != theExcludedParent) + return true; + } + return false; +} + +const char* kindName(const BRepGraph_NodeId::Kind theKind) +{ + switch (theKind) + { + case BRepGraph_NodeId::Kind::Vertex: + return "Vertices"; + case BRepGraph_NodeId::Kind::Edge: + return "Edges"; + case BRepGraph_NodeId::Kind::CoEdge: + return "CoEdges"; + case BRepGraph_NodeId::Kind::Wire: + return "Wires"; + case BRepGraph_NodeId::Kind::Face: + return "Faces"; + case BRepGraph_NodeId::Kind::Shell: + return "Shells"; + case BRepGraph_NodeId::Kind::Solid: + return "Solids"; + case BRepGraph_NodeId::Kind::Compound: + return "Compounds"; + case BRepGraph_NodeId::Kind::CompSolid: + return "CompSolids"; + case BRepGraph_NodeId::Kind::Product: + return "Products"; + case BRepGraph_NodeId::Kind::Occurrence: + return "Occurrences"; + } + return "Unknown"; +} + +//================================================================================================= + +static bool isNodeIndexInRange(const BRepGraphInc_Storage& theStorage, + const BRepGraph_NodeId theNode) +{ + if (theNode.Index < 0) + return false; + + switch (theNode.NodeKind) + { + case BRepGraph_NodeId::Kind::Vertex: + return theNode.Index < theStorage.NbVertices(); + case BRepGraph_NodeId::Kind::Edge: + return theNode.Index < theStorage.NbEdges(); + case BRepGraph_NodeId::Kind::CoEdge: + return theNode.Index < theStorage.NbCoEdges(); + case BRepGraph_NodeId::Kind::Wire: + return theNode.Index < theStorage.NbWires(); + case BRepGraph_NodeId::Kind::Face: + return theNode.Index < theStorage.NbFaces(); + case BRepGraph_NodeId::Kind::Shell: + return theNode.Index < theStorage.NbShells(); + case BRepGraph_NodeId::Kind::Solid: + return theNode.Index < theStorage.NbSolids(); + case BRepGraph_NodeId::Kind::Compound: + return theNode.Index < theStorage.NbCompounds(); + case BRepGraph_NodeId::Kind::CompSolid: + return theNode.Index < theStorage.NbCompSolids(); + case BRepGraph_NodeId::Kind::Product: + return theNode.Index < theStorage.NbProducts(); + case BRepGraph_NodeId::Kind::Occurrence: + return theNode.Index < theStorage.NbOccurrences(); + } + + return false; +} + +//================================================================================================= + +static const BRepGraphInc::BaseDef* topoEntity(const BRepGraphInc_Storage& theStorage, + const BRepGraph_NodeId theNode) +{ + if (!isNodeIndexInRange(theStorage, theNode)) + { + return nullptr; + } + + switch (theNode.NodeKind) + { + case BRepGraph_NodeId::Kind::Vertex: + return &theStorage.Vertex(BRepGraph_VertexId(theNode.Index)); + case BRepGraph_NodeId::Kind::Edge: + return &theStorage.Edge(BRepGraph_EdgeId(theNode.Index)); + case BRepGraph_NodeId::Kind::CoEdge: + return &theStorage.CoEdge(BRepGraph_CoEdgeId(theNode.Index)); + case BRepGraph_NodeId::Kind::Wire: + return &theStorage.Wire(BRepGraph_WireId(theNode.Index)); + case BRepGraph_NodeId::Kind::Face: + return &theStorage.Face(BRepGraph_FaceId(theNode.Index)); + case BRepGraph_NodeId::Kind::Shell: + return &theStorage.Shell(BRepGraph_ShellId(theNode.Index)); + case BRepGraph_NodeId::Kind::Solid: + return &theStorage.Solid(BRepGraph_SolidId(theNode.Index)); + case BRepGraph_NodeId::Kind::Compound: + return &theStorage.Compound(BRepGraph_CompoundId(theNode.Index)); + case BRepGraph_NodeId::Kind::CompSolid: + return &theStorage.CompSolid(BRepGraph_CompSolidId(theNode.Index)); + case BRepGraph_NodeId::Kind::Product: + return &theStorage.Product(BRepGraph_ProductId(theNode.Index)); + case BRepGraph_NodeId::Kind::Occurrence: + return &theStorage.Occurrence(BRepGraph_OccurrenceId(theNode.Index)); + } + + return nullptr; +} + +//================================================================================================= + +static bool isActiveNode(const BRepGraphInc_Storage& theStorage, const BRepGraph_NodeId theNode) +{ + const BRepGraphInc::BaseDef* aDef = topoEntity(theStorage, theNode); + return aDef != nullptr && !aDef->IsRemoved; +} + +//================================================================================================= + +static bool isActiveTopologyNode(const BRepGraphInc_Storage& theStorage, + const BRepGraph_NodeId theNode) +{ + return theNode.IsValid() && BRepGraph_NodeId::IsTopologyKind(theNode.NodeKind) + && isActiveNode(theStorage, theNode); +} + +//================================================================================================= + +[[maybe_unused]] static bool isRepIndexInRange(const BRepGraphInc_Storage& theStorage, + const BRepGraph_RepId theRepId) +{ + if (!theRepId.IsValid()) + { + return false; + } + + switch (theRepId.RepKind) + { + case BRepGraph_RepId::Kind::Surface: + return theRepId.IsValid(theStorage.NbSurfaces()); + case BRepGraph_RepId::Kind::Curve3D: + return theRepId.IsValid(theStorage.NbCurves3D()); + case BRepGraph_RepId::Kind::Curve2D: + return theRepId.IsValid(theStorage.NbCurves2D()); + case BRepGraph_RepId::Kind::Triangulation: + return theRepId.IsValid(theStorage.NbTriangulations()); + case BRepGraph_RepId::Kind::Polygon3D: + return theRepId.IsValid(theStorage.NbPolygons3D()); + case BRepGraph_RepId::Kind::Polygon2D: + return theRepId.IsValid(theStorage.NbPolygons2D()); + case BRepGraph_RepId::Kind::PolygonOnTri: + return theRepId.IsValid(theStorage.NbPolygonsOnTri()); + } + + return false; +} + +//================================================================================================= + +static void validateMutableNodeId([[maybe_unused]] const BRepGraphInc_Storage& theStorage, + [[maybe_unused]] const BRepGraph_NodeId theNodeId) +{ + Standard_ProgramError_Raise_if(!isNodeIndexInRange(theStorage, theNodeId), + "BRepGraph::BuilderView::Mut*(): invalid node id"); +} + +//================================================================================================= + +static void validateMutableRefId([[maybe_unused]] const BRepGraphInc_Storage& theStorage, + [[maybe_unused]] const BRepGraph_RefId theRefId) +{ + [[maybe_unused]] auto isRefIndexInRange = [](const BRepGraphInc_Storage& theStorage, + const BRepGraph_RefId theRefId) { + if (!theRefId.IsValid()) + { + return false; + } + + switch (theRefId.RefKind) + { + case BRepGraph_RefId::Kind::Shell: + return theRefId.IsValid(theStorage.NbShellRefs()); + case BRepGraph_RefId::Kind::Face: + return theRefId.IsValid(theStorage.NbFaceRefs()); + case BRepGraph_RefId::Kind::Wire: + return theRefId.IsValid(theStorage.NbWireRefs()); + case BRepGraph_RefId::Kind::CoEdge: + return theRefId.IsValid(theStorage.NbCoEdgeRefs()); + case BRepGraph_RefId::Kind::Vertex: + return theRefId.IsValid(theStorage.NbVertexRefs()); + case BRepGraph_RefId::Kind::Solid: + return theRefId.IsValid(theStorage.NbSolidRefs()); + case BRepGraph_RefId::Kind::Child: + return theRefId.IsValid(theStorage.NbChildRefs()); + case BRepGraph_RefId::Kind::Occurrence: + return theRefId.IsValid(theStorage.NbOccurrenceRefs()); + } + + return false; + }; + Standard_ProgramError_Raise_if(!isRefIndexInRange(theStorage, theRefId), + "BRepGraph::BuilderView::Mut*(): invalid reference id"); +} + +//================================================================================================= + +static void validateMutableRepId([[maybe_unused]] const BRepGraphInc_Storage& theStorage, + [[maybe_unused]] const BRepGraph_RepId theRepId) +{ + Standard_ProgramError_Raise_if(!isRepIndexInRange(theStorage, theRepId), + "BRepGraph::BuilderView::Mut*(): invalid representation id"); +} + +//================================================================================================= + +static void rebindCoEdgesForEdgeReplacement(BRepGraphInc_Storage& theStorage, + const BRepGraph_EdgeId theSourceEdgeId, + const BRepGraph_EdgeId theReplacementEdgeId) +{ + const NCollection_Vector& aCoEdgeIdxs = + theStorage.ReverseIndex().CoEdgesOfEdgeRef(theSourceEdgeId); + const int aNbCoEdges = aCoEdgeIdxs.Length(); + NCollection_LocalArray aCoEdgeSnapshot(aNbCoEdges); + for (int aCoEdgeIdx = 0; aCoEdgeIdx < aNbCoEdges; ++aCoEdgeIdx) + { + aCoEdgeSnapshot[aCoEdgeIdx] = aCoEdgeIdxs.Value(aCoEdgeIdx); + } + + for (int aCoEdgeIdx = 0; aCoEdgeIdx < aNbCoEdges; ++aCoEdgeIdx) + { + const BRepGraph_CoEdgeId aCoEdgeId = aCoEdgeSnapshot[aCoEdgeIdx]; + if (!aCoEdgeId.IsValid(theStorage.NbCoEdges())) + continue; + + BRepGraphInc::CoEdgeDef& aCoEdge = theStorage.ChangeCoEdge(aCoEdgeId); + if (aCoEdge.IsRemoved || aCoEdge.EdgeDefId != theSourceEdgeId) + continue; + + theStorage.ChangeReverseIndex().UnbindEdgeFromCoEdge(theSourceEdgeId, aCoEdgeId); + aCoEdge.EdgeDefId = theReplacementEdgeId; + theStorage.ChangeReverseIndex().BindEdgeToCoEdge(theReplacementEdgeId, aCoEdgeId); + } +} + +//================================================================================================= + +static void unbindCoEdgesOfRemovedEdge(BRepGraphInc_Storage& theStorage, + const BRepGraph_EdgeId theEdgeId) +{ + const NCollection_Vector& aCoEdgeIdxs = + theStorage.ReverseIndex().CoEdgesOfEdgeRef(theEdgeId); + const int aNbCoEdges = aCoEdgeIdxs.Length(); + NCollection_LocalArray aCoEdgeSnapshot(aNbCoEdges); + for (int aCoEdgeIdx = 0; aCoEdgeIdx < aNbCoEdges; ++aCoEdgeIdx) + { + aCoEdgeSnapshot[aCoEdgeIdx] = aCoEdgeIdxs.Value(aCoEdgeIdx); + } + + for (int aCoEdgeIdx = 0; aCoEdgeIdx < aNbCoEdges; ++aCoEdgeIdx) + { + const BRepGraph_CoEdgeId aCoEdgeId = aCoEdgeSnapshot[aCoEdgeIdx]; + if (!aCoEdgeId.IsValid(theStorage.NbCoEdges())) + continue; + + const BRepGraphInc::CoEdgeDef& aCoEdge = theStorage.CoEdge(aCoEdgeId); + if (!aCoEdge.IsRemoved && aCoEdge.EdgeDefId == theEdgeId) + theStorage.ChangeReverseIndex().UnbindEdgeFromCoEdge(theEdgeId, aCoEdgeId); + } +} + +//================================================================================================= + +template +int countIterator(const BRepGraph& theGraph) +{ + int aCount = 0; + for (BRepGraph_Iterator anIt(theGraph); anIt.More(); anIt.Next()) + ++aCount; + return aCount; +} + +int countActiveByKind(const BRepGraph& theGraph, const BRepGraph_NodeId::Kind theKind) +{ + switch (theKind) + { + case BRepGraph_NodeId::Kind::Vertex: + return countIterator(theGraph); + case BRepGraph_NodeId::Kind::Edge: + return countIterator(theGraph); + case BRepGraph_NodeId::Kind::CoEdge: + return countIterator(theGraph); + case BRepGraph_NodeId::Kind::Wire: + return countIterator(theGraph); + case BRepGraph_NodeId::Kind::Face: + return countIterator(theGraph); + case BRepGraph_NodeId::Kind::Shell: + return countIterator(theGraph); + case BRepGraph_NodeId::Kind::Solid: + return countIterator(theGraph); + case BRepGraph_NodeId::Kind::Compound: + return countIterator(theGraph); + case BRepGraph_NodeId::Kind::CompSolid: + return countIterator(theGraph); + case BRepGraph_NodeId::Kind::Product: + return countIterator(theGraph); + case BRepGraph_NodeId::Kind::Occurrence: + return countIterator(theGraph); + } + return 0; +} + +int cachedActiveByKind(const BRepGraphInc_Storage& theStorage, const BRepGraph_NodeId::Kind theKind) +{ + switch (theKind) + { + case BRepGraph_NodeId::Kind::Vertex: + return theStorage.NbActiveVertices(); + case BRepGraph_NodeId::Kind::Edge: + return theStorage.NbActiveEdges(); + case BRepGraph_NodeId::Kind::CoEdge: + return theStorage.NbActiveCoEdges(); + case BRepGraph_NodeId::Kind::Wire: + return theStorage.NbActiveWires(); + case BRepGraph_NodeId::Kind::Face: + return theStorage.NbActiveFaces(); + case BRepGraph_NodeId::Kind::Shell: + return theStorage.NbActiveShells(); + case BRepGraph_NodeId::Kind::Solid: + return theStorage.NbActiveSolids(); + case BRepGraph_NodeId::Kind::Compound: + return theStorage.NbActiveCompounds(); + case BRepGraph_NodeId::Kind::CompSolid: + return theStorage.NbActiveCompSolids(); + case BRepGraph_NodeId::Kind::Product: + return theStorage.NbActiveProducts(); + case BRepGraph_NodeId::Kind::Occurrence: + return theStorage.NbActiveOccurrences(); + } + return 0; +} + +[[maybe_unused]] const NCollection_Vector& wireCoEdgeRefIds( + const BRepGraphInc_Storage& theStorage, + const BRepGraph_WireId theWireId) +{ + return theStorage.Wire(theWireId).CoEdgeRefIds; +} + +//! Initialize a sub-edge definition produced by SplitEdge. +//! Copies shared properties from the original edge and assigns boundary vertex ref ids. +//! Vertex ref entries must already exist in storage; only their RefId indices are passed. +void initSubEdgeEntity(BRepGraphInc::EdgeDef& theSub, + const BRepGraph_Curve3DRepId theCurve3DRepId, + const double theTolerance, + const bool theSameParameter, + const BRepGraph_VertexRefId theStartVertexRefId, + const BRepGraph_VertexRefId theEndVertexRefId, + const double theParamFirst, + const double theParamLast) +{ + theSub.Curve3DRepId = theCurve3DRepId; + theSub.Tolerance = theTolerance; + theSub.SameParameter = theSameParameter; + theSub.SameRange = false; + theSub.IsDegenerate = false; + theSub.StartVertexRefId = theStartVertexRefId; + theSub.EndVertexRefId = theEndVertexRefId; + theSub.ParamFirst = theParamFirst; + theSub.ParamLast = theParamLast; +} + +//! Initialize a sub-CoEdge definition produced by SplitEdge. +void initSubCoEdgeEntity(BRepGraphInc::CoEdgeDef& theCE, + const BRepGraph_EdgeId theEdgeId, + const BRepGraph_FaceId theFaceId, + const TopAbs_Orientation theSense, + const BRepGraph_Curve2DRepId theCurve2DRepId, + const double theParamFirst, + const double theParamLast, + const GeomAbs_Shape theContinuity) +{ + theCE.EdgeDefId = theEdgeId; + theCE.FaceDefId = theFaceId; + theCE.Sense = theSense; + theCE.Curve2DRepId = theCurve2DRepId; + theCE.ParamFirst = theParamFirst; + theCE.ParamLast = theParamLast; + theCE.Continuity = theContinuity; +} + +} // namespace + +//================================================================================================= + +BRepGraph_VertexId BRepGraph::BuilderView::AddVertex(const gp_Pnt& thePoint, + const double theTolerance) +{ + BRepGraphInc::VertexDef& aVtxDef = myGraph->myData->myIncStorage.AppendVertex(); + const int aIdx = myGraph->myData->myIncStorage.NbVertices() - 1; + const BRepGraph_VertexId aVertexId(aIdx); + aVtxDef.Id = aVertexId; + aVtxDef.Point = thePoint; + aVtxDef.Tolerance = theTolerance; + myGraph->allocateUID(aVtxDef.Id); + + return aVertexId; +} + +//================================================================================================= + +BRepGraph_EdgeId BRepGraph::BuilderView::AddEdge(const BRepGraph_VertexId theStartVtx, + const BRepGraph_VertexId theEndVtx, + const occ::handle& theCurve, + const double theFirst, + const double theLast, + const double theTolerance) +{ + BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + if (theStartVtx.IsValid() && !isActiveNode(aStorage, theStartVtx)) + { + return BRepGraph_EdgeId(); + } + if (theEndVtx.IsValid() && !isActiveNode(aStorage, theEndVtx)) + { + return BRepGraph_EdgeId(); + } + + BRepGraphInc::EdgeDef& anEdgeDef = aStorage.AppendEdge(); + const int aIdx = aStorage.NbEdges() - 1; + const BRepGraph_EdgeId aEdgeId(aIdx); + anEdgeDef.Id = aEdgeId; + if (theStartVtx.IsValid()) + { + BRepGraphInc::VertexRef& aStartVtxRef = aStorage.AppendVertexRef(); + const int aStartVtxRefIdx = aStorage.NbVertexRefs() - 1; + aStartVtxRef.RefId = BRepGraph_VertexRefId(aStartVtxRefIdx); + aStartVtxRef.ParentId = anEdgeDef.Id; + aStartVtxRef.VertexDefId = theStartVtx; + aStartVtxRef.Orientation = TopAbs_FORWARD; + myGraph->allocateRefUID(aStartVtxRef.RefId); + anEdgeDef.StartVertexRefId = BRepGraph_VertexRefId(aStartVtxRefIdx); + } + if (theEndVtx.IsValid()) + { + BRepGraphInc::VertexRef& anEndVtxRef = aStorage.AppendVertexRef(); + const int anEndVtxRefIdx = aStorage.NbVertexRefs() - 1; + anEndVtxRef.RefId = BRepGraph_VertexRefId(anEndVtxRefIdx); + anEndVtxRef.ParentId = anEdgeDef.Id; + anEndVtxRef.VertexDefId = theEndVtx; + anEndVtxRef.Orientation = TopAbs_REVERSED; + myGraph->allocateRefUID(anEndVtxRef.RefId); + anEdgeDef.EndVertexRefId = BRepGraph_VertexRefId(anEndVtxRefIdx); + } + anEdgeDef.ParamFirst = theFirst; + anEdgeDef.ParamLast = theLast; + anEdgeDef.Tolerance = theTolerance; + anEdgeDef.SameParameter = true; + anEdgeDef.SameRange = true; + myGraph->allocateUID(anEdgeDef.Id); + + if (!theCurve.IsNull()) + { + BRepGraphInc::Curve3DRep& aCurveRep = aStorage.AppendCurve3DRep(); + const int aCurveRepIdx = aStorage.NbCurves3D() - 1; + aCurveRep.Id = BRepGraph_RepId::Curve3D(aCurveRepIdx); + aCurveRep.Curve = theCurve; + anEdgeDef.Curve3DRepId = BRepGraph_Curve3DRepId(aCurveRepIdx); + } + + return aEdgeId; +} + +//================================================================================================= + +BRepGraph_WireId BRepGraph::BuilderView::AddWire( + const NCollection_Vector>& theEdges) +{ + BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + for (const std::pair& anEdgeEntry : theEdges) + { + if (!isActiveNode(aStorage, anEdgeEntry.first)) + { + return BRepGraph_WireId(); + } + } + + BRepGraphInc::WireDef& aWireDef = aStorage.AppendWire(); + const int aIdx = aStorage.NbWires() - 1; + const BRepGraph_WireId aWireId(aIdx); + aWireDef.Id = aWireId; + myGraph->allocateUID(aWireDef.Id); + + for (const std::pair& anEdgeEntry : theEdges) + { + const BRepGraph_EdgeId anEdgeDefId = anEdgeEntry.first; + const TopAbs_Orientation anOri = anEdgeEntry.second; + + // Create CoEdge entity for this edge-wire binding. + BRepGraphInc::CoEdgeDef& aCoEdge = aStorage.AppendCoEdge(); + const int aCoEdgeIdx = aStorage.NbCoEdges() - 1; + aCoEdge.Id = BRepGraph_NodeId(BRepGraph_NodeId::Kind::CoEdge, aCoEdgeIdx); + aCoEdge.EdgeDefId = anEdgeDefId; + aCoEdge.Sense = anOri; + myGraph->allocateUID(aCoEdge.Id); + + // CoEdgeRef in ref-table. + BRepGraphInc::CoEdgeRef& aCoEdgeRef = aStorage.AppendCoEdgeRef(); + const int aCoEdgeRefIdx = aStorage.NbCoEdgeRefs() - 1; + aCoEdgeRef.RefId = BRepGraph_CoEdgeRefId(aCoEdgeRefIdx); + aCoEdgeRef.ParentId = aWireId; + aCoEdgeRef.CoEdgeDefId = BRepGraph_CoEdgeId(aCoEdgeIdx); + myGraph->allocateRefUID(aCoEdgeRef.RefId); + aStorage.ChangeWire(aWireId).CoEdgeRefIds.Append(BRepGraph_CoEdgeRefId(aCoEdgeRefIdx)); + + aStorage.ChangeReverseIndex().BindEdgeToWire(aCoEdge.EdgeDefId, aWireId); + aStorage.ChangeReverseIndex().BindEdgeToCoEdge(aCoEdge.EdgeDefId, + BRepGraph_CoEdgeId(aCoEdgeIdx)); + aStorage.ChangeReverseIndex().BindCoEdgeToWire(BRepGraph_CoEdgeId(aCoEdgeIdx), aWireId); + } + + // Check closure. + if (!theEdges.IsEmpty()) + { + const BRepGraph_EdgeId aFirstEdgeNodeId = theEdges.First().first; + const TopAbs_Orientation aFirstOri = theEdges.First().second; + const BRepGraph_EdgeId aLastEdgeNodeId = theEdges.Last().first; + const TopAbs_Orientation aLastOri = theEdges.Last().second; + + const BRepGraphInc::EdgeDef& aFirstEdge = aStorage.Edge(aFirstEdgeNodeId); + const BRepGraphInc::EdgeDef& aLastEdge = aStorage.Edge(aLastEdgeNodeId); + const BRepGraph_VertexRefId aFirstRefId = + (aFirstOri == TopAbs_FORWARD) ? aFirstEdge.StartVertexRefId : aFirstEdge.EndVertexRefId; + const BRepGraph_VertexRefId aLastRefId = + (aLastOri == TopAbs_FORWARD) ? aLastEdge.EndVertexRefId : aLastEdge.StartVertexRefId; + const BRepGraph_NodeId aFirstVtx = + aFirstRefId.IsValid() ? BRepGraph_VertexId(aStorage.VertexRef(aFirstRefId).VertexDefId.Index) + : BRepGraph_NodeId(); + const BRepGraph_NodeId aLastVtx = + aLastRefId.IsValid() ? BRepGraph_VertexId(aStorage.VertexRef(aLastRefId).VertexDefId.Index) + : BRepGraph_NodeId(); + + const bool aIsClosed = aFirstVtx.IsValid() && aLastVtx.IsValid() && aFirstVtx == aLastVtx; + aStorage.ChangeWire(aWireId).IsClosed = aIsClosed; + } + + return aWireId; +} + +//================================================================================================= + +BRepGraph_FaceId BRepGraph::BuilderView::AddFace( + const occ::handle& theSurface, + const BRepGraph_WireId theOuterWire, + const NCollection_Vector& theInnerWires, + const double theTolerance) +{ + BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + if (theOuterWire.IsValid() && !isActiveNode(aStorage, theOuterWire)) + { + return BRepGraph_FaceId(); + } + for (const BRepGraph_WireId& aWireDefId : theInnerWires) + { + if (aWireDefId.IsValid() && !isActiveNode(aStorage, aWireDefId)) + { + return BRepGraph_FaceId(); + } + } + + BRepGraphInc::FaceDef& aFaceDef = aStorage.AppendFace(); + const int aIdx = aStorage.NbFaces() - 1; + const BRepGraph_FaceId aFaceId(aIdx); + aFaceDef.Id = aFaceId; + aFaceDef.Tolerance = theTolerance; + myGraph->allocateUID(aFaceDef.Id); + if (!theSurface.IsNull()) + { + BRepGraphInc::SurfaceRep& aSurfRep = aStorage.AppendSurfaceRep(); + const int aSurfRepIdx = aStorage.NbSurfaces() - 1; + aSurfRep.Id = BRepGraph_RepId::Surface(aSurfRepIdx); + aSurfRep.Surface = theSurface; + aFaceDef.SurfaceRepId = BRepGraph_SurfaceRepId(aSurfRepIdx); + } + + // Link wire refs. + if (theOuterWire.IsValid()) + { + BRepGraphInc::WireRef& anOuterWireRef = aStorage.AppendWireRef(); + const int anOuterWireRefIdx = aStorage.NbWireRefs() - 1; + anOuterWireRef.RefId = BRepGraph_WireRefId(anOuterWireRefIdx); + anOuterWireRef.ParentId = aFaceId; + anOuterWireRef.WireDefId = theOuterWire; + anOuterWireRef.IsOuter = true; + myGraph->allocateRefUID(anOuterWireRef.RefId); + aStorage.ChangeFace(aFaceId).WireRefIds.Append(BRepGraph_WireRefId(anOuterWireRefIdx)); + } + + for (const BRepGraph_WireId& aWireDefId : theInnerWires) + { + if (!aWireDefId.IsValid()) + { + continue; + } + BRepGraphInc::WireRef& aWireRef = aStorage.AppendWireRef(); + const int aWireRefIdx = aStorage.NbWireRefs() - 1; + aWireRef.RefId = BRepGraph_WireRefId(aWireRefIdx); + aWireRef.ParentId = aFaceId; + aWireRef.WireDefId = aWireDefId; + aWireRef.IsOuter = false; + myGraph->allocateRefUID(aWireRef.RefId); + aStorage.ChangeFace(aFaceId).WireRefIds.Append(BRepGraph_WireRefId(aWireRefIdx)); + } + + return aFaceId; +} + +//================================================================================================= + +BRepGraph_ShellId BRepGraph::BuilderView::AddShell() +{ + BRepGraphInc::ShellDef& aShellDef = myGraph->myData->myIncStorage.AppendShell(); + const int aIdx = myGraph->myData->myIncStorage.NbShells() - 1; + const BRepGraph_ShellId aShellId(aIdx); + aShellDef.Id = aShellId; + myGraph->allocateUID(aShellDef.Id); + + return aShellId; +} + +//================================================================================================= + +BRepGraph_SolidId BRepGraph::BuilderView::AddSolid() +{ + BRepGraphInc::SolidDef& aSolidDef = myGraph->myData->myIncStorage.AppendSolid(); + const int aIdx = myGraph->myData->myIncStorage.NbSolids() - 1; + const BRepGraph_SolidId aSolidId(aIdx); + aSolidDef.Id = aSolidId; + myGraph->allocateUID(aSolidDef.Id); + + return aSolidId; +} + +//================================================================================================= + +BRepGraph_FaceRefId BRepGraph::BuilderView::AddFaceToShell(const BRepGraph_ShellId theShellEntity, + const BRepGraph_FaceId theFaceEntity, + const TopAbs_Orientation theOri) +{ + BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + if (!isActiveNode(aStorage, theShellEntity) || !isActiveNode(aStorage, theFaceEntity)) + { + return BRepGraph_FaceRefId(); + } + + // Append FaceRef to the shell definition. + BRepGraphInc::FaceRef& aFREntry = aStorage.AppendFaceRef(); + const int aFRIdx = aStorage.NbFaceRefs() - 1; + const BRepGraph_FaceRefId aFaceRefId(aFRIdx); + aFREntry.RefId = aFaceRefId; + aFREntry.ParentId = theShellEntity; + aFREntry.FaceDefId = theFaceEntity; + aFREntry.Orientation = theOri; + myGraph->allocateRefUID(aFREntry.RefId); + aStorage.ChangeShell(theShellEntity).FaceRefIds.Append(aFaceRefId); + + myGraph->markModified(theShellEntity); + return aFaceRefId; +} + +//================================================================================================= + +BRepGraph_ShellRefId BRepGraph::BuilderView::AddShellToSolid(const BRepGraph_SolidId theSolidEntity, + const BRepGraph_ShellId theShellEntity, + const TopAbs_Orientation theOri) +{ + BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + if (!isActiveNode(aStorage, theSolidEntity) || !isActiveNode(aStorage, theShellEntity)) + { + return BRepGraph_ShellRefId(); + } + + // Append ShellRef to the solid definition. + BRepGraphInc::ShellRef& aSREntry = aStorage.AppendShellRef(); + const int aSRIdx = aStorage.NbShellRefs() - 1; + const BRepGraph_ShellRefId aShellRefId(aSRIdx); + aSREntry.RefId = aShellRefId; + aSREntry.ParentId = theSolidEntity; + aSREntry.ShellDefId = theShellEntity; + aSREntry.Orientation = theOri; + myGraph->allocateRefUID(aSREntry.RefId); + aStorage.ChangeSolid(theSolidEntity).ShellRefIds.Append(aShellRefId); + + myGraph->markModified(theSolidEntity); + return aShellRefId; +} + +//================================================================================================= + +BRepGraph_CompoundId BRepGraph::BuilderView::AddCompound( + const NCollection_Vector& theChildEntities) +{ + BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + for (const BRepGraph_NodeId& aChild : theChildEntities) + { + if (!isActiveTopologyNode(aStorage, aChild)) + { + return BRepGraph_CompoundId(); + } + } + + BRepGraphInc::CompoundDef& aCompDef = aStorage.AppendCompound(); + const int aIdx = aStorage.NbCompounds() - 1; + const BRepGraph_CompoundId aCompoundId(aIdx); + aCompDef.Id = aCompoundId; + myGraph->allocateUID(aCompDef.Id); + + for (const BRepGraph_NodeId& aChild : theChildEntities) + { + BRepGraphInc::ChildRef& aCREntry = aStorage.AppendChildRef(); + const int aCRIdx = aStorage.NbChildRefs() - 1; + aCREntry.RefId = BRepGraph_ChildRefId(aCRIdx); + aCREntry.ParentId = aCompDef.Id; + aCREntry.ChildDefId = aChild; + myGraph->allocateRefUID(aCREntry.RefId); + aCompDef.ChildRefIds.Append(BRepGraph_ChildRefId(aCRIdx)); + } + + return aCompoundId; +} + +//================================================================================================= + +BRepGraph_CompSolidId BRepGraph::BuilderView::AddCompSolid( + const NCollection_Vector& theSolidEntities) +{ + BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + for (const BRepGraph_SolidId& aSolidId : theSolidEntities) + { + if (!isActiveNode(aStorage, aSolidId)) + { + return BRepGraph_CompSolidId(); + } + } + + BRepGraphInc::CompSolidDef& aCSolDef = aStorage.AppendCompSolid(); + const int aIdx = aStorage.NbCompSolids() - 1; + const BRepGraph_CompSolidId aCompSolidId(aIdx); + aCSolDef.Id = aCompSolidId; + myGraph->allocateUID(aCSolDef.Id); + + for (const BRepGraph_SolidId& aSolidId : theSolidEntities) + { + BRepGraphInc::SolidRef& aSREntry = aStorage.AppendSolidRef(); + const int aSRIdx = aStorage.NbSolidRefs() - 1; + aSREntry.RefId = BRepGraph_SolidRefId(aSRIdx); + aSREntry.ParentId = aCSolDef.Id; + aSREntry.SolidDefId = aSolidId; + myGraph->allocateRefUID(aSREntry.RefId); + aCSolDef.SolidRefIds.Append(BRepGraph_SolidRefId(aSRIdx)); + } + + return aCompSolidId; +} + +//================================================================================================= + +BRepGraph_ProductId BRepGraph::BuilderView::AddProduct(const BRepGraph_NodeId theShapeRoot) +{ + BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + if (!isActiveTopologyNode(aStorage, theShapeRoot)) + { + return BRepGraph_ProductId(); + } + + BRepGraphInc::ProductDef& aProductDef = aStorage.AppendProduct(); + const int aIdx = aStorage.NbProducts() - 1; + aProductDef.Id = BRepGraph_ProductId(aIdx); + aProductDef.ShapeRootId = theShapeRoot; + myGraph->allocateUID(aProductDef.Id); + + return BRepGraph_ProductId(aIdx); +} + +//================================================================================================= + +BRepGraph_ProductId BRepGraph::BuilderView::AddAssemblyProduct() +{ + BRepGraphInc::ProductDef& aProductDef = myGraph->myData->myIncStorage.AppendProduct(); + const int aIdx = myGraph->myData->myIncStorage.NbProducts() - 1; + aProductDef.Id = BRepGraph_ProductId(aIdx); + // ShapeRootId left invalid - this is an assembly. + myGraph->allocateUID(aProductDef.Id); + + return BRepGraph_ProductId(aIdx); +} + +//================================================================================================= + +BRepGraph_OccurrenceId BRepGraph::BuilderView::AddOccurrence( + const BRepGraph_ProductId theParentProduct, + const BRepGraph_ProductId theReferencedProduct, + const TopLoc_Location& thePlacement) +{ + // Delegate with no parent occurrence (top-level). + return AddOccurrence(theParentProduct, + theReferencedProduct, + thePlacement, + BRepGraph_OccurrenceId()); +} + +//================================================================================================= + +BRepGraph_OccurrenceId BRepGraph::BuilderView::AddOccurrence( + const BRepGraph_ProductId theParentProduct, + const BRepGraph_ProductId theReferencedProduct, + const TopLoc_Location& thePlacement, + const BRepGraph_OccurrenceId theParentOccurrence) +{ + BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + + // Validate that both arguments are active Product nodes with valid indices. + if (!isActiveNode(aStorage, theParentProduct)) + return BRepGraph_OccurrenceId(); + if (!isActiveNode(aStorage, theReferencedProduct)) + return BRepGraph_OccurrenceId(); + // Prevent self-referencing cycles in the assembly DAG. + if (theParentProduct.Index == theReferencedProduct.Index) + return BRepGraph_OccurrenceId(); + // Validate parent occurrence if provided. + // ParentOccurrenceIdx = -1 when theParentOccurrence is invalid (top-level). + if (theParentOccurrence.IsValid() && !isActiveNode(aStorage, theParentOccurrence)) + return BRepGraph_OccurrenceId(); + if (theParentOccurrence.IsValid() + && aStorage.Occurrence(theParentOccurrence).ProductDefId != theParentProduct) + { + return BRepGraph_OccurrenceId(); + } + + BRepGraphInc::OccurrenceDef& anOccDef = aStorage.AppendOccurrence(); + const int anOccIdx = aStorage.NbOccurrences() - 1; + anOccDef.Id = BRepGraph_OccurrenceId(anOccIdx); + anOccDef.ProductDefId = theReferencedProduct; + anOccDef.ParentProductDefId = theParentProduct; + anOccDef.ParentOccurrenceDefId = + theParentOccurrence.IsValid() ? theParentOccurrence : BRepGraph_OccurrenceId(); + anOccDef.Placement = thePlacement; + myGraph->allocateUID(anOccDef.Id); + + // Add OccurrenceRef to the parent product. + BRepGraphInc::OccurrenceRef& anOccRef = aStorage.AppendOccurrenceRef(); + const int anOccRefIdx = aStorage.NbOccurrenceRefs() - 1; + anOccRef.RefId = BRepGraph_OccurrenceRefId(anOccRefIdx); + anOccRef.ParentId = theParentProduct; + anOccRef.OccurrenceDefId = BRepGraph_OccurrenceId(anOccIdx); + myGraph->allocateRefUID(anOccRef.RefId); + aStorage.ChangeProduct(theParentProduct) + .OccurrenceRefIds.Append(BRepGraph_OccurrenceRefId(anOccRefIdx)); + + // Rebuild product->occurrence reverse index to keep it in sync + // after appending a new occurrence entity. + aStorage.ChangeReverseIndex().BuildProductOccurrences(aStorage.myOccurrences.Entities, + aStorage.NbProducts()); + + return BRepGraph_OccurrenceId(anOccIdx); +} + +//================================================================================================= + +void BRepGraph::BuilderView::AppendFlattenedShape(const TopoDS_Shape& theShape, + const bool theParallel) +{ + BRepGraph_Builder::AppendFlattened(*myGraph, theShape, theParallel); +} + +//================================================================================================= + +void BRepGraph::BuilderView::RemoveNode(const BRepGraph_NodeId theNode) +{ + RemoveNode(theNode, BRepGraph_NodeId()); +} + +//================================================================================================= + +void BRepGraph::BuilderView::RemoveNode(const BRepGraph_NodeId theNode, + const BRepGraph_NodeId theReplacement) +{ + if (!theNode.IsValid()) + return; + + BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + + // When removing an Edge with a replacement, reparent all CoEdges from the + // removed edge to the replacement edge. This prevents orphaned CoEdges + // that would be excluded from queries via CoEdgesOfEdge(). + if (theNode.NodeKind == BRepGraph_NodeId::Kind::Edge && theReplacement.IsValid() + && theReplacement != theNode && theReplacement.NodeKind == BRepGraph_NodeId::Kind::Edge) + { + Standard_ASSERT_RETURN(isNodeIndexInRange(aStorage, theNode), + "RemoveNode: source edge index is out of range", + Standard_VOID_RETURN); + Standard_ASSERT_RETURN(isNodeIndexInRange(aStorage, theReplacement), + "RemoveNode: replacement edge index is out of range", + Standard_VOID_RETURN); + Standard_ASSERT_RETURN(!aStorage.Edge(BRepGraph_EdgeId(theReplacement.Index)).IsRemoved, + "RemoveNode: replacement edge must be active", + Standard_VOID_RETURN); + + rebindCoEdgesForEdgeReplacement(aStorage, + BRepGraph_EdgeId::FromNodeId(theNode), + BRepGraph_EdgeId::FromNodeId(theReplacement)); + } + + switch (theNode.NodeKind) + { + case BRepGraph_NodeId::Kind::Vertex: + case BRepGraph_NodeId::Kind::Edge: + case BRepGraph_NodeId::Kind::CoEdge: + case BRepGraph_NodeId::Kind::Wire: + case BRepGraph_NodeId::Kind::Face: + case BRepGraph_NodeId::Kind::Shell: + case BRepGraph_NodeId::Kind::Solid: + case BRepGraph_NodeId::Kind::Compound: + case BRepGraph_NodeId::Kind::CompSolid: + case BRepGraph_NodeId::Kind::Product: + case BRepGraph_NodeId::Kind::Occurrence: + break; + default: + Standard_ASSERT_RETURN(false, "RemoveNode: unsupported node kind", Standard_VOID_RETURN); + } + Standard_ASSERT_RETURN(isNodeIndexInRange(aStorage, theNode), + "RemoveNode: node index is out of range", + Standard_VOID_RETURN); + if (!isActiveNode(aStorage, theNode)) + { + return; + } + + if (theNode.NodeKind == BRepGraph_NodeId::Kind::Edge) + { + // Keep reverse edge->coedge table coherent for pure removals too. + unbindCoEdgesOfRemovedEdge(aStorage, BRepGraph_EdgeId::FromNodeId(theNode)); + } + + // Mark removed on the entity (which is the sole definition store). + BRepGraphInc::BaseDef* aDef = myGraph->changeTopoEntity(theNode); + if (aDef != nullptr && !aDef->IsRemoved) + { + myGraph->myData->myIncStorage.MarkRemoved(theNode); + } + + // Increment OwnGen + SubtreeGen so generation-based cache freshness detects the removal. + BRepGraphInc::BaseDef* aRemovedDef = myGraph->changeTopoEntity(theNode); + if (aRemovedDef != nullptr) + { + ++aRemovedDef->OwnGen; + ++aRemovedDef->SubtreeGen; + } + + { + std::unique_lock aWriteLock(myGraph->myData->myCurrentShapesMutex); + myGraph->myData->myCurrentShapes.UnBind(theNode); + } + + // Notify registered layers. + myGraph->myLayerRegistry.DispatchOnNodeRemoved(theNode, theReplacement); +} + +//================================================================================================= + +void BRepGraph::BuilderView::RemoveSubgraph(const BRepGraph_NodeId theNode) +{ + // Collect and recursively remove children BEFORE marking this node as removed, + // because iterators (DefsIterator, RefsIterator) skip removed parents/children. + // Children shared with other active parents are NOT removed (ownership-aware). + // + // Product and Occurrence require special handling: + // - Product: shared ShapeRootId check across products. + // - Occurrence: child occurrence cascade + parent product ref detachment. + // All other kinds use the generic ChildExplorer/ParentExplorer cascade. + const BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + + switch (theNode.NodeKind) + { + case BRepGraph_NodeId::Kind::Product: { + if (theNode.IsValid(aStorage.NbProducts())) + { + // Snapshot occurrence indices before iterating, because RemoveSubgraph(Occurrence) + // modifies the parent's OccurrenceRefIds via swap-remove. + NCollection_Vector anOccIndices; + for (BRepGraph_RefsOccurrenceOfProduct anOccIt(*myGraph, + BRepGraph_ProductId::FromNodeId(theNode)); + anOccIt.More(); + anOccIt.Next()) + { + anOccIndices.Append(aStorage.OccurrenceRef(anOccIt.CurrentId()).OccurrenceDefId.Index); + } + for (const int anOccIdx : anOccIndices) + RemoveSubgraph(BRepGraph_OccurrenceId(anOccIdx)); + + // Cascade into part topology owned via ShapeRootId, + // but only if no other active product shares the same root. + const BRepGraphInc::ProductDef& aProd = + aStorage.Product(BRepGraph_ProductId(theNode.Index)); + if (aProd.ShapeRootId.IsValid() && !myGraph->Topo().Gen().IsRemoved(aProd.ShapeRootId)) + { + bool aIsSharedRoot = false; + for (int aProdIdx = 0; aProdIdx < aStorage.NbProducts(); ++aProdIdx) + { + if (aProdIdx == theNode.Index) + continue; + const BRepGraphInc::ProductDef& anOther = + aStorage.Product(BRepGraph_ProductId(aProdIdx)); + if (!anOther.IsRemoved && anOther.ShapeRootId == aProd.ShapeRootId) + { + aIsSharedRoot = true; + break; + } + } + if (!aIsSharedRoot) + RemoveSubgraph(aProd.ShapeRootId); + } + } + break; + } + case BRepGraph_NodeId::Kind::Occurrence: { + if (theNode.IsValid(aStorage.NbOccurrences())) + { + // Cascade into child occurrences whose ParentOccurrenceDefId points to this one. + NCollection_Vector aChildOccIndices; + for (int anOccIdx = 0; anOccIdx < aStorage.NbOccurrences(); ++anOccIdx) + { + const BRepGraphInc::OccurrenceDef& aCandidate = + aStorage.Occurrence(BRepGraph_OccurrenceId(anOccIdx)); + if (!aCandidate.IsRemoved && aCandidate.ParentOccurrenceDefId.IsValid() + && aCandidate.ParentOccurrenceDefId.Index == theNode.Index) + { + aChildOccIndices.Append(anOccIdx); + } + } + for (const int aChildIdx : aChildOccIndices) + RemoveSubgraph(BRepGraph_OccurrenceId(aChildIdx)); + + // Detach from parent product's OccurrenceRefIds. + const BRepGraphInc::OccurrenceDef& anOcc = + myGraph->myData->myIncStorage.Occurrence(BRepGraph_OccurrenceId(theNode.Index)); + if (anOcc.ParentProductDefId.IsValid() + && anOcc.ParentProductDefId.Index < aStorage.NbProducts()) + { + NCollection_Vector& aRefIds = + myGraph->myData->myIncStorage.ChangeProduct(anOcc.ParentProductDefId).OccurrenceRefIds; + for (BRepGraph_RefsOccurrenceOfProduct aRefIt(*myGraph, anOcc.ParentProductDefId); + aRefIt.More(); + aRefIt.Next()) + { + const int i = aRefIt.Index(); + BRepGraphInc::OccurrenceRef& aRef = + myGraph->myData->myIncStorage.ChangeOccurrenceRef(aRefIt.CurrentId()); + if (aRef.OccurrenceDefId.Index == theNode.Index) + { + if (!aRef.IsRemoved) + { + myGraph->myData->myIncStorage.MarkRemovedRef(aRef.RefId); + } + if (i < aRefIds.Length() - 1) + aRefIds.ChangeValue(i) = aRefIds.Value(aRefIds.Length() - 1); + aRefIds.EraseLast(); + myGraph->markModified(anOcc.ParentProductDefId); + break; + } + } + } + } + break; + } + default: { + // Generic topology cascade via ChildExplorer(DirectChildren) + ParentExplorer(DirectParents). + // Covers Compound, CompSolid, Solid, Shell, Face, Wire, CoEdge, Edge, Vertex. + NCollection_Vector aChildNodes; + for (BRepGraph_ChildExplorer anExp(*myGraph, + theNode, + BRepGraph_ChildExplorer::TraversalMode::DirectChildren); + anExp.More(); + anExp.Next()) + { + aChildNodes.Append(anExp.Current().DefId); + } + for (const BRepGraph_NodeId& aChild : aChildNodes) + { + if (!hasOtherActiveParent(*myGraph, aChild, theNode)) + RemoveSubgraph(aChild); + } + break; + } + } + + // Mark the node as removed AFTER children have been collected and processed, + // so that iterators can still see this node as a valid parent during traversal. + RemoveNode(theNode); +} + +//================================================================================================= + +void BRepGraph::BuilderView::RemoveRep(const BRepGraph_RepId theRep) +{ + if (!theRep.IsValid()) + return; + + if (myGraph->myData->myIncStorage.MarkRemovedRep(theRep)) + { + myGraph->markRepModified(theRep); + } +} + +//================================================================================================= + +BRepGraph_Curve2DRepId BRepGraph::BuilderView::CreateCurve2DRep( + const occ::handle& theCurve2d) +{ + if (theCurve2d.IsNull()) + return BRepGraph_Curve2DRepId(); + + BRepGraphInc::Curve2DRep& aRep = myGraph->myData->myIncStorage.AppendCurve2DRep(); + const int anIdx = myGraph->myData->myIncStorage.NbCurves2D() - 1; + aRep.Id = BRepGraph_RepId::Curve2D(anIdx); + aRep.Curve = theCurve2d; + return BRepGraph_Curve2DRepId(anIdx); +} + +//================================================================================================= + +void BRepGraph::BuilderView::AddPCurveToEdge(const BRepGraph_EdgeId theEdgeEntity, + const BRepGraph_FaceId theFaceEntity, + const occ::handle& theCurve2d, + const double theFirst, + const double theLast, + const TopAbs_Orientation theEdgeOrientation) +{ + BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + if (!isActiveNode(aStorage, theEdgeEntity) || !isActiveNode(aStorage, theFaceEntity) + || theCurve2d.IsNull()) + { + return; + } + + // Create CoEdge entity for the new PCurve binding. + BRepGraphInc::CoEdgeDef& aCoEdge = aStorage.AppendCoEdge(); + const int aCoEdgeIdx = aStorage.NbCoEdges() - 1; + aCoEdge.Id = BRepGraph_CoEdgeId(aCoEdgeIdx); + aCoEdge.EdgeDefId = theEdgeEntity; + aCoEdge.FaceDefId = theFaceEntity; + aCoEdge.Sense = theEdgeOrientation; + if (!theCurve2d.IsNull()) + { + BRepGraphInc::Curve2DRep& aCurve2DRep = aStorage.AppendCurve2DRep(); + const int aCurve2DRepIdx = aStorage.NbCurves2D() - 1; + aCurve2DRep.Id = BRepGraph_RepId::Curve2D(aCurve2DRepIdx); + aCurve2DRep.Curve = theCurve2d; + aCoEdge.Curve2DRepId = BRepGraph_Curve2DRepId(aCurve2DRepIdx); + } + aCoEdge.ParamFirst = theFirst; + aCoEdge.ParamLast = theLast; + + // Update reverse indices. + aStorage.ChangeReverseIndex().BindEdgeToCoEdge(theEdgeEntity, BRepGraph_CoEdgeId(aCoEdgeIdx)); + aStorage.ChangeReverseIndex().BindEdgeToFace(theEdgeEntity, theFaceEntity); + myGraph->allocateUID(aCoEdge.Id); + + myGraph->markModified(theEdgeEntity); +} + +//================================================================================================= + +void BRepGraph::BuilderView::BeginDeferredInvalidation() +{ + myGraph->myData->myDeferredMode.store(true, std::memory_order_relaxed); +} + +//================================================================================================= + +void BRepGraph::BuilderView::EndDeferredInvalidation() noexcept +{ + if (!myGraph->myData->myDeferredMode.load(std::memory_order_relaxed)) + return; + + myGraph->myData->myDeferredMode.store(false, std::memory_order_relaxed); + + NCollection_Vector& aDeferredList = myGraph->myData->myDeferredModified; + + if (aDeferredList.IsEmpty()) + return; + + // Shape cache uses SubtreeGen validation - no bulk clear needed. + // Stale entries are detected on read via StoredSubtreeGen != entity.SubtreeGen. + + // Propagate SubtreeGen upward from each directly-modified node. + // Dense per-kind visited flags for O(1) lookup without hashing overhead. + const BRepGraphInc_ReverseIndex& aRevIdx = myGraph->myData->myIncStorage.ReverseIndex(); + const BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + + // Dense visited arrays indexed by entity index per kind. + // NCollection_Array1 with bool values: O(1) checked/set by index. + static constexpr int THE_KIND_COUNT = BRepGraph_NodeId::THE_KIND_COUNT; + NCollection_Array1 aVisArrays[THE_KIND_COUNT]; + { + const int aKindCounts[THE_KIND_COUNT] = { + aStorage.NbSolids(), // Kind::Solid = 0 + aStorage.NbShells(), // Kind::Shell = 1 + aStorage.NbFaces(), // Kind::Face = 2 + aStorage.NbWires(), // Kind::Wire = 3 + aStorage.NbEdges(), // Kind::Edge = 4 + aStorage.NbVertices(), // Kind::Vertex = 5 + aStorage.NbCompounds(), // Kind::Compound = 6 + aStorage.NbCompSolids(), // Kind::CompSolid = 7 + aStorage.NbCoEdges(), // Kind::CoEdge = 8 + 0, // gap at 9 + aStorage.NbProducts(), // Kind::Product = 10 + aStorage.NbOccurrences() // Kind::Occurrence = 11 + }; + for (int aKindIdx = 0; aKindIdx < THE_KIND_COUNT; ++aKindIdx) + { + const int aCount = aKindCounts[aKindIdx]; + if (aCount > 0) + { + aVisArrays[aKindIdx].Resize(0, aCount - 1, false); + aVisArrays[aKindIdx].Init(false); + } + } + } + + // Helper lambda: check-and-set visited flag. + const auto markVisited = [&aVisArrays](const BRepGraph_NodeId theNode) -> bool { + const int aKindIdx = static_cast(theNode.NodeKind); + NCollection_Array1& aArr = aVisArrays[aKindIdx]; + if (aArr.IsEmpty() || theNode.Index > aArr.Upper()) + return false; + if (aArr.Value(theNode.Index)) + return false; + aArr.SetValue(theNode.Index, true); + return true; + }; + + // BFS-style upward propagation: process nodes front-to-back, appending + // newly discovered parents at the end. The loop index advances through + // the growing vector, so each node is visited exactly once. + NCollection_Vector aAllModified; + aAllModified.SetIncrement(aDeferredList.Length() * 2); + int aModifiedKindsMask = 0; + + // Seed with directly modified nodes. + for (const BRepGraph_NodeId& aNode : aDeferredList) + { + if (markVisited(aNode)) + { + aAllModified.Append(aNode); + aModifiedKindsMask |= BRepGraph_Layer::KindBit(aNode.NodeKind); + } + } + + // Propagate upward level by level. + // Process the list from front to back; newly appended parents will be + // reached in subsequent iterations of the same loop. + for (int i = 0; i < aAllModified.Length(); ++i) + { + const BRepGraph_NodeId aNodeId = aAllModified.Value(i); + + // Collect parent NodeIds via reverse index and increment SubtreeGen. + // NOT OwnGen - parent's own data didn't change. + // The visited set ensures each parent is incremented exactly once per flush, + // even in diamond topologies. This matches immediate-mode LastPropWave semantics. + switch (aNodeId.NodeKind) + { + case BRepGraph_NodeId::Kind::Vertex: + // Vertex modifications don't propagate in deferred mode. + break; + case BRepGraph_NodeId::Kind::Edge: { + const NCollection_Vector* aWires = + aRevIdx.WiresOfEdge(BRepGraph_EdgeId(aNodeId.Index)); + if (aWires != nullptr) + for (const BRepGraph_WireId& aWireId : *aWires) + { + const BRepGraph_NodeId aParentId = aWireId; + if (!markVisited(aParentId)) + continue; + BRepGraphInc::BaseDef* aParent = myGraph->changeTopoEntity(aParentId); + if (aParent == nullptr || aParent->IsRemoved) + continue; + ++aParent->SubtreeGen; + aAllModified.Append(aParentId); + aModifiedKindsMask |= BRepGraph_Layer::KindBit(aParentId.NodeKind); + } + break; + } + case BRepGraph_NodeId::Kind::CoEdge: + break; + case BRepGraph_NodeId::Kind::Wire: { + const NCollection_Vector* aFaces = + aRevIdx.FacesOfWire(BRepGraph_WireId(aNodeId.Index)); + if (aFaces != nullptr) + for (const BRepGraph_FaceId& aFaceId : *aFaces) + { + const BRepGraph_NodeId aParentId = aFaceId; + if (!markVisited(aParentId)) + continue; + BRepGraphInc::BaseDef* aParent = myGraph->changeTopoEntity(aParentId); + if (aParent == nullptr || aParent->IsRemoved) + continue; + ++aParent->SubtreeGen; + aAllModified.Append(aParentId); + aModifiedKindsMask |= BRepGraph_Layer::KindBit(aParentId.NodeKind); + } + break; + } + case BRepGraph_NodeId::Kind::Face: { + const NCollection_Vector* aShells = + aRevIdx.ShellsOfFace(BRepGraph_FaceId(aNodeId.Index)); + if (aShells != nullptr) + for (const BRepGraph_ShellId& aShellId : *aShells) + { + const BRepGraph_NodeId aParentId = aShellId; + if (!markVisited(aParentId)) + continue; + BRepGraphInc::BaseDef* aParent = myGraph->changeTopoEntity(aParentId); + if (aParent == nullptr || aParent->IsRemoved) + continue; + ++aParent->SubtreeGen; + aAllModified.Append(aParentId); + aModifiedKindsMask |= BRepGraph_Layer::KindBit(aParentId.NodeKind); + } + break; + } + case BRepGraph_NodeId::Kind::Shell: { + const NCollection_Vector* aSolids = + aRevIdx.SolidsOfShell(BRepGraph_ShellId(aNodeId.Index)); + if (aSolids != nullptr) + for (const BRepGraph_SolidId& aSolidId : *aSolids) + { + const BRepGraph_NodeId aParentId = aSolidId; + if (!markVisited(aParentId)) + continue; + BRepGraphInc::BaseDef* aParent = myGraph->changeTopoEntity(aParentId); + if (aParent == nullptr || aParent->IsRemoved) + continue; + ++aParent->SubtreeGen; + aAllModified.Append(aParentId); + aModifiedKindsMask |= BRepGraph_Layer::KindBit(aParentId.NodeKind); + } + break; + } + case BRepGraph_NodeId::Kind::Occurrence: { + // Occurrence modifications propagate to the parent product. + const BRepGraphInc::OccurrenceDef& anOccDef = + myGraph->myData->myIncStorage.Occurrence(BRepGraph_OccurrenceId(aNodeId.Index)); + if (anOccDef.ParentProductDefId.IsValid()) + { + const BRepGraph_NodeId aParentId = anOccDef.ParentProductDefId; + if (markVisited(aParentId)) + { + BRepGraphInc::BaseDef* aParent = myGraph->changeTopoEntity(aParentId); + if (aParent != nullptr && !aParent->IsRemoved) + { + ++aParent->SubtreeGen; + aAllModified.Append(aParentId); + aModifiedKindsMask |= BRepGraph_Layer::KindBit(aParentId.NodeKind); + } + } + } + break; + } + default: + break; + } + } + + // Dispatch batch modification event to subscribing layers. + if (myGraph->myLayerRegistry.HasModificationSubscribers() && !aAllModified.IsEmpty()) + myGraph->myLayerRegistry.DispatchNodesModified(aAllModified, aModifiedKindsMask); + + // Clear deferred list for next scope. + aDeferredList.Clear(); +} + +//================================================================================================= + +bool BRepGraph::BuilderView::IsDeferredMode() const +{ + return myGraph->myData->myDeferredMode.load(std::memory_order_relaxed); +} + +//================================================================================================= + +BRepGraph_MutGuard BRepGraph::BuilderView::MutEdge( + const BRepGraph_EdgeId theEdge) +{ + BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + validateMutableNodeId(aStorage, BRepGraph_EdgeId(theEdge.Index)); + return BRepGraph_MutGuard( + myGraph, + &aStorage.ChangeEdge(theEdge), + BRepGraph_NodeId(BRepGraph_NodeId::Kind::Edge, theEdge.Index)); +} + +//================================================================================================= + +BRepGraph_MutGuard BRepGraph::BuilderView::MutCoEdge( + const BRepGraph_CoEdgeId theCoEdge) +{ + BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + validateMutableNodeId(aStorage, BRepGraph_CoEdgeId(theCoEdge.Index)); + return BRepGraph_MutGuard( + myGraph, + &aStorage.ChangeCoEdge(theCoEdge), + BRepGraph_NodeId(BRepGraph_NodeId::Kind::CoEdge, theCoEdge.Index)); +} + +//================================================================================================= + +BRepGraph_MutGuard BRepGraph::BuilderView::MutVertex( + const BRepGraph_VertexId theVertex) +{ + BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + validateMutableNodeId(aStorage, BRepGraph_VertexId(theVertex.Index)); + return BRepGraph_MutGuard( + myGraph, + &aStorage.ChangeVertex(theVertex), + BRepGraph_NodeId(BRepGraph_NodeId::Kind::Vertex, theVertex.Index)); +} + +//================================================================================================= + +BRepGraph_MutGuard BRepGraph::BuilderView::MutWire( + const BRepGraph_WireId theWire) +{ + BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + validateMutableNodeId(aStorage, BRepGraph_WireId(theWire.Index)); + return BRepGraph_MutGuard( + myGraph, + &aStorage.ChangeWire(theWire), + BRepGraph_NodeId(BRepGraph_NodeId::Kind::Wire, theWire.Index)); +} + +//================================================================================================= + +BRepGraph_MutGuard BRepGraph::BuilderView::MutFace( + const BRepGraph_FaceId theFace) +{ + BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + validateMutableNodeId(aStorage, BRepGraph_FaceId(theFace.Index)); + return BRepGraph_MutGuard( + myGraph, + &aStorage.ChangeFace(theFace), + BRepGraph_NodeId(BRepGraph_NodeId::Kind::Face, theFace.Index)); +} + +//================================================================================================= + +BRepGraph_MutGuard BRepGraph::BuilderView::MutShell( + const BRepGraph_ShellId theShell) +{ + BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + validateMutableNodeId(aStorage, BRepGraph_ShellId(theShell.Index)); + return BRepGraph_MutGuard( + myGraph, + &aStorage.ChangeShell(theShell), + BRepGraph_NodeId(BRepGraph_NodeId::Kind::Shell, theShell.Index)); +} + +//================================================================================================= + +BRepGraph_MutGuard BRepGraph::BuilderView::MutSolid( + const BRepGraph_SolidId theSolid) +{ + BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + validateMutableNodeId(aStorage, BRepGraph_SolidId(theSolid.Index)); + return BRepGraph_MutGuard( + myGraph, + &aStorage.ChangeSolid(theSolid), + BRepGraph_NodeId(BRepGraph_NodeId::Kind::Solid, theSolid.Index)); +} + +//================================================================================================= + +BRepGraph_MutGuard BRepGraph::BuilderView::MutCompound( + const BRepGraph_CompoundId theCompound) +{ + BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + validateMutableNodeId(aStorage, BRepGraph_CompoundId(theCompound.Index)); + return BRepGraph_MutGuard( + myGraph, + &aStorage.ChangeCompound(theCompound), + BRepGraph_NodeId(BRepGraph_NodeId::Kind::Compound, theCompound.Index)); +} + +//================================================================================================= + +BRepGraph_MutGuard BRepGraph::BuilderView::MutCompSolid( + const BRepGraph_CompSolidId theCompSolid) +{ + BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + validateMutableNodeId(aStorage, BRepGraph_CompSolidId(theCompSolid.Index)); + return BRepGraph_MutGuard( + myGraph, + &aStorage.ChangeCompSolid(theCompSolid), + BRepGraph_NodeId(BRepGraph_NodeId::Kind::CompSolid, theCompSolid.Index)); +} + +//================================================================================================= + +BRepGraph_MutGuard BRepGraph::BuilderView::MutProduct( + const BRepGraph_ProductId theProduct) +{ + BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + validateMutableNodeId(aStorage, BRepGraph_ProductId(theProduct.Index)); + return BRepGraph_MutGuard( + myGraph, + &aStorage.ChangeProduct(theProduct), + BRepGraph_NodeId(BRepGraph_NodeId::Kind::Product, theProduct.Index)); +} + +//================================================================================================= + +BRepGraph_MutGuard BRepGraph::BuilderView::MutOccurrence( + const BRepGraph_OccurrenceId theOccurrence) +{ + BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + validateMutableNodeId(aStorage, BRepGraph_OccurrenceId(theOccurrence.Index)); + return BRepGraph_MutGuard( + myGraph, + &aStorage.ChangeOccurrence(theOccurrence), + BRepGraph_NodeId(BRepGraph_NodeId::Kind::Occurrence, theOccurrence.Index)); +} + +//================================================================================================= + +BRepGraph_MutGuard BRepGraph::BuilderView::MutShellRef( + const BRepGraph_ShellRefId theShellRef) +{ + BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + validateMutableRefId(aStorage, BRepGraph_ShellRefId(theShellRef.Index)); + return BRepGraph_MutGuard(myGraph, + &aStorage.ChangeShellRef(theShellRef), + BRepGraph_ShellRefId(theShellRef.Index)); +} + +//================================================================================================= + +BRepGraph_MutGuard BRepGraph::BuilderView::MutFaceRef( + const BRepGraph_FaceRefId theFaceRef) +{ + BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + validateMutableRefId(aStorage, BRepGraph_FaceRefId(theFaceRef.Index)); + return BRepGraph_MutGuard(myGraph, + &aStorage.ChangeFaceRef(theFaceRef), + BRepGraph_FaceRefId(theFaceRef.Index)); +} + +//================================================================================================= + +BRepGraph_MutGuard BRepGraph::BuilderView::MutWireRef( + const BRepGraph_WireRefId theWireRef) +{ + BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + validateMutableRefId(aStorage, BRepGraph_WireRefId(theWireRef.Index)); + return BRepGraph_MutGuard(myGraph, + &aStorage.ChangeWireRef(theWireRef), + BRepGraph_WireRefId(theWireRef.Index)); +} + +//================================================================================================= + +BRepGraph_MutGuard BRepGraph::BuilderView::MutCoEdgeRef( + const BRepGraph_CoEdgeRefId theCoEdgeRef) +{ + BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + validateMutableRefId(aStorage, BRepGraph_CoEdgeRefId(theCoEdgeRef.Index)); + return BRepGraph_MutGuard(myGraph, + &aStorage.ChangeCoEdgeRef(theCoEdgeRef), + BRepGraph_CoEdgeRefId(theCoEdgeRef.Index)); +} + +//================================================================================================= + +BRepGraph_MutGuard BRepGraph::BuilderView::MutVertexRef( + const BRepGraph_VertexRefId theVertexRef) +{ + BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + validateMutableRefId(aStorage, BRepGraph_VertexRefId(theVertexRef.Index)); + return BRepGraph_MutGuard(myGraph, + &aStorage.ChangeVertexRef(theVertexRef), + BRepGraph_VertexRefId(theVertexRef.Index)); +} + +//================================================================================================= + +BRepGraph_MutGuard BRepGraph::BuilderView::MutSolidRef( + const BRepGraph_SolidRefId theSolidRef) +{ + BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + validateMutableRefId(aStorage, BRepGraph_SolidRefId(theSolidRef.Index)); + return BRepGraph_MutGuard(myGraph, + &aStorage.ChangeSolidRef(theSolidRef), + BRepGraph_SolidRefId(theSolidRef.Index)); +} + +//================================================================================================= + +BRepGraph_MutGuard BRepGraph::BuilderView::MutChildRef( + const BRepGraph_ChildRefId theChildRef) +{ + BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + validateMutableRefId(aStorage, BRepGraph_ChildRefId(theChildRef.Index)); + return BRepGraph_MutGuard(myGraph, + &aStorage.ChangeChildRef(theChildRef), + BRepGraph_ChildRefId(theChildRef.Index)); +} + +//================================================================================================= + +BRepGraph_MutGuard BRepGraph::BuilderView::MutOccurrenceRef( + const BRepGraph_OccurrenceRefId theOccurrenceRef) +{ + BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + validateMutableRefId(aStorage, BRepGraph_OccurrenceRefId(theOccurrenceRef.Index)); + return BRepGraph_MutGuard( + myGraph, + &aStorage.ChangeOccurrenceRef(theOccurrenceRef), + BRepGraph_OccurrenceRefId(theOccurrenceRef.Index)); +} + +//================================================================================================= + +BRepGraph_MutGuard BRepGraph::BuilderView::MutSurface( + const BRepGraph_SurfaceRepId theSurface) +{ + BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + validateMutableRepId(aStorage, BRepGraph_RepId::Surface(theSurface.Index)); + return BRepGraph_MutGuard(myGraph, + &aStorage.ChangeSurfaceRep(theSurface), + BRepGraph_RepId::Surface(theSurface.Index)); +} + +//================================================================================================= + +BRepGraph_MutGuard BRepGraph::BuilderView::MutCurve3D( + const BRepGraph_Curve3DRepId theCurve) +{ + BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + validateMutableRepId(aStorage, BRepGraph_RepId::Curve3D(theCurve.Index)); + return BRepGraph_MutGuard(myGraph, + &aStorage.ChangeCurve3DRep(theCurve), + BRepGraph_RepId::Curve3D(theCurve.Index)); +} + +//================================================================================================= + +BRepGraph_MutGuard BRepGraph::BuilderView::MutCurve2D( + const BRepGraph_Curve2DRepId theCurve) +{ + BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + validateMutableRepId(aStorage, BRepGraph_RepId::Curve2D(theCurve.Index)); + return BRepGraph_MutGuard(myGraph, + &aStorage.ChangeCurve2DRep(theCurve), + BRepGraph_RepId::Curve2D(theCurve.Index)); +} + +//================================================================================================= + +BRepGraph_MutGuard BRepGraph::BuilderView::MutTriangulation( + const BRepGraph_TriangulationRepId theTriangulation) +{ + BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + validateMutableRepId(aStorage, BRepGraph_RepId::Triangulation(theTriangulation.Index)); + return BRepGraph_MutGuard( + myGraph, + &aStorage.ChangeTriangulationRep(theTriangulation), + BRepGraph_RepId::Triangulation(theTriangulation.Index)); +} + +//================================================================================================= + +BRepGraph_MutGuard BRepGraph::BuilderView::MutPolygon3D( + const BRepGraph_Polygon3DRepId thePolygon) +{ + BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + validateMutableRepId(aStorage, BRepGraph_RepId::Polygon3D(thePolygon.Index)); + return BRepGraph_MutGuard( + myGraph, + &aStorage.ChangePolygon3DRep(thePolygon), + BRepGraph_RepId::Polygon3D(thePolygon.Index)); +} + +//================================================================================================= + +BRepGraph_MutGuard BRepGraph::BuilderView::MutPolygon2D( + const BRepGraph_Polygon2DRepId thePolygon) +{ + BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + validateMutableRepId(aStorage, BRepGraph_RepId::Polygon2D(thePolygon.Index)); + return BRepGraph_MutGuard( + myGraph, + &aStorage.ChangePolygon2DRep(thePolygon), + BRepGraph_RepId::Polygon2D(thePolygon.Index)); +} + +//================================================================================================= + +BRepGraph_MutGuard BRepGraph::BuilderView::MutPolygonOnTri( + const BRepGraph_PolygonOnTriRepId thePolygon) +{ + BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + validateMutableRepId(aStorage, BRepGraph_RepId::PolygonOnTri(thePolygon.Index)); + return BRepGraph_MutGuard( + myGraph, + &aStorage.ChangePolygonOnTriRep(thePolygon), + BRepGraph_RepId::PolygonOnTri(thePolygon.Index)); +} + +//================================================================================================= + +void BRepGraph::BuilderView::applyModificationImpl( + const BRepGraph_NodeId theTarget, + NCollection_Vector&& theReplacements, + const TCollection_AsciiString& theOpLabel) +{ + myGraph->myData->myHistoryLog.Record(theOpLabel, theTarget, theReplacements); + myGraph->invalidateSubgraphImpl(theTarget); +} + +//================================================================================================= + +void BRepGraph::BuilderView::SplitEdge(const BRepGraph_EdgeId theEdgeEntity, + const BRepGraph_VertexId theSplitVertex, + const double theSplitParam, + BRepGraph_EdgeId& theSubA, + BRepGraph_EdgeId& theSubB) +{ + theSubA = BRepGraph_EdgeId(); + theSubB = BRepGraph_EdgeId(); + Standard_ASSERT_RETURN(theEdgeEntity.Index >= 0 + && theEdgeEntity.Index < myGraph->myData->myIncStorage.NbEdges(), + "SplitEdge: edge index is out of range", + Standard_VOID_RETURN); + Standard_ASSERT_RETURN(theSplitVertex.Index >= 0 + && theSplitVertex.Index < myGraph->myData->myIncStorage.NbVertices(), + "SplitEdge: split-vertex index is out of range", + Standard_VOID_RETURN); + + // Copy all data from the original EdgeDef before appending to vectors (which may reallocate). + const BRepGraphInc::EdgeDef& anOrig = myGraph->myData->myIncStorage.Edge(theEdgeEntity); + Standard_ASSERT_RETURN(!anOrig.IsRemoved, + "SplitEdge: source edge is removed", + Standard_VOID_RETURN); + Standard_ASSERT_RETURN(!anOrig.IsDegenerate, + "SplitEdge: degenerate edge cannot be split", + Standard_VOID_RETURN); + Standard_ASSERT_RETURN(anOrig.ParamFirst < theSplitParam && theSplitParam < anOrig.ParamLast, + "SplitEdge: split parameter must be inside open edge range", + Standard_VOID_RETURN); + + const BRepGraphInc_Storage& aConstStorage = myGraph->myData->myIncStorage; + const BRepGraph_Curve3DRepId aOrigCurve3DRepId = anOrig.Curve3DRepId; + const double aOrigTolerance = anOrig.Tolerance; + const bool aOrigSameParameter = anOrig.SameParameter; + const double aOrigParamFirst = anOrig.ParamFirst; + const double aOrigParamLast = anOrig.ParamLast; + const BRepGraph_VertexRefId aOrigStartVertexRefId = anOrig.StartVertexRefId; + const BRepGraph_VertexRefId aOrigEndVertexRefId = anOrig.EndVertexRefId; + const bool aOrigSameRange = anOrig.SameRange; + + // Resolve original vertex def ids through storage ref entries. + const BRepGraph_VertexId aOrigStartVertexDefId = + aOrigStartVertexRefId.IsValid() ? aConstStorage.VertexRef(aOrigStartVertexRefId).VertexDefId + : BRepGraph_VertexId(); + const BRepGraph_VertexId aOrigEndVertexDefId = + aOrigEndVertexRefId.IsValid() ? aConstStorage.VertexRef(aOrigEndVertexRefId).VertexDefId + : BRepGraph_VertexId(); + + // Copy wire indices: ReverseIdx may be rebuilt below. + const NCollection_Vector* aOrigWiresPtr = + myGraph->myData->myIncStorage.ReverseIndex().WiresOfEdge(theEdgeEntity); + const NCollection_Vector aOrigWires = + aOrigWiresPtr != nullptr ? *aOrigWiresPtr : NCollection_Vector(); + + // Allocate SubA slot. + BRepGraphInc::EdgeDef& aSubADef = myGraph->myData->myIncStorage.AppendEdge(); + const int aSubAIdx = myGraph->myData->myIncStorage.NbEdges() - 1; + aSubADef.Id = BRepGraph_EdgeId(aSubAIdx); + theSubA = BRepGraph_EdgeId(aSubAIdx); + + // Allocate SubB slot (note: Appended() may invalidate aSubADef reference - use index). + BRepGraphInc::EdgeDef& aSubBDef = myGraph->myData->myIncStorage.AppendEdge(); + const int aSubBIdx = myGraph->myData->myIncStorage.NbEdges() - 1; + aSubBDef.Id = BRepGraph_EdgeId(aSubBIdx); + theSubB = BRepGraph_EdgeId(aSubBIdx); + + // Build vertex ref entries for the split vertex (no Location since split vertex is new). + const BRepGraph_VertexId aSplitVertexDefId = theSplitVertex; + + // Create start vertex ref entry for SubA (copy from original edge's start vertex ref). + BRepGraph_VertexRefId aSubAStartRefId; + if (aOrigStartVertexRefId.IsValid()) + { + // Copy fields before append (which may reallocate and invalidate references). + const BRepGraphInc::VertexRef& aOrigStartRef = + myGraph->myData->myIncStorage.VertexRef(aOrigStartVertexRefId); + const BRepGraph_VertexId aOrigStartVertexId = aOrigStartRef.VertexDefId; + const TopAbs_Orientation aOrigStartOri = aOrigStartRef.Orientation; + const TopLoc_Location aOrigStartLoc = aOrigStartRef.LocalLocation; + + BRepGraphInc::VertexRef& aSubAStartRef = myGraph->myData->myIncStorage.AppendVertexRef(); + const int aSubAStartRefIdx = myGraph->myData->myIncStorage.NbVertexRefs() - 1; + aSubAStartRef.RefId = BRepGraph_VertexRefId(aSubAStartRefIdx); + aSubAStartRef.ParentId = BRepGraph_NodeId(BRepGraph_NodeId::Kind::Edge, aSubAIdx); + aSubAStartRef.VertexDefId = aOrigStartVertexId; + aSubAStartRef.Orientation = aOrigStartOri; + aSubAStartRef.LocalLocation = aOrigStartLoc; + myGraph->allocateRefUID(aSubAStartRef.RefId); + aSubAStartRefId = BRepGraph_VertexRefId(aSubAStartRefIdx); + } + + // Create end vertex ref entry for SubA (split vertex, REVERSED). + BRepGraph_VertexRefId aSubAEndRefId; + if (aSplitVertexDefId.IsValid()) + { + BRepGraphInc::VertexRef& aSubAEndRef = myGraph->myData->myIncStorage.AppendVertexRef(); + const int aSubAEndRefIdx = myGraph->myData->myIncStorage.NbVertexRefs() - 1; + aSubAEndRef.RefId = BRepGraph_VertexRefId(aSubAEndRefIdx); + aSubAEndRef.ParentId = BRepGraph_NodeId(BRepGraph_NodeId::Kind::Edge, aSubAIdx); + aSubAEndRef.VertexDefId = aSplitVertexDefId; + aSubAEndRef.Orientation = TopAbs_REVERSED; + myGraph->allocateRefUID(aSubAEndRef.RefId); + aSubAEndRefId = BRepGraph_VertexRefId(aSubAEndRefIdx); + } + + // Create start vertex ref entry for SubB (split vertex, FORWARD). + BRepGraph_VertexRefId aSubBStartRefId; + if (aSplitVertexDefId.IsValid()) + { + BRepGraphInc::VertexRef& aSubBStartRef = myGraph->myData->myIncStorage.AppendVertexRef(); + const int aSubBStartRefIdx = myGraph->myData->myIncStorage.NbVertexRefs() - 1; + aSubBStartRef.RefId = BRepGraph_VertexRefId(aSubBStartRefIdx); + aSubBStartRef.ParentId = BRepGraph_NodeId(BRepGraph_NodeId::Kind::Edge, aSubBIdx); + aSubBStartRef.VertexDefId = aSplitVertexDefId; + aSubBStartRef.Orientation = TopAbs_FORWARD; + myGraph->allocateRefUID(aSubBStartRef.RefId); + aSubBStartRefId = BRepGraph_VertexRefId(aSubBStartRefIdx); + } + + // Create end vertex ref entry for SubB (copy from original edge's end vertex ref). + BRepGraph_VertexRefId aSubBEndRefId; + if (aOrigEndVertexRefId.IsValid()) + { + // Copy fields before append (which may reallocate and invalidate references). + const BRepGraphInc::VertexRef& aOrigEndRef = + myGraph->myData->myIncStorage.VertexRef(aOrigEndVertexRefId); + const BRepGraph_VertexId aOrigEndVertexId = aOrigEndRef.VertexDefId; + const TopAbs_Orientation aOrigEndOri = aOrigEndRef.Orientation; + const TopLoc_Location aOrigEndLoc = aOrigEndRef.LocalLocation; + + BRepGraphInc::VertexRef& aSubBEndRef = myGraph->myData->myIncStorage.AppendVertexRef(); + const int aSubBEndRefIdx = myGraph->myData->myIncStorage.NbVertexRefs() - 1; + aSubBEndRef.RefId = BRepGraph_VertexRefId(aSubBEndRefIdx); + aSubBEndRef.ParentId = BRepGraph_NodeId(BRepGraph_NodeId::Kind::Edge, aSubBIdx); + aSubBEndRef.VertexDefId = aOrigEndVertexId; + aSubBEndRef.Orientation = aOrigEndOri; + aSubBEndRef.LocalLocation = aOrigEndLoc; + myGraph->allocateRefUID(aSubBEndRef.RefId); + aSubBEndRefId = BRepGraph_VertexRefId(aSubBEndRefIdx); + } + + // Set SubA: StartVertex -> SplitVertex, [ParamFirst, theSplitParam]. + { + BRepGraphInc::EdgeDef& aSubA = + myGraph->myData->myIncStorage.ChangeEdge(BRepGraph_EdgeId(aSubAIdx)); + initSubEdgeEntity(aSubA, + aOrigCurve3DRepId, + aOrigTolerance, + aOrigSameParameter, + aSubAStartRefId, + aSubAEndRefId, + aOrigParamFirst, + theSplitParam); + } + + // Set SubB: SplitVertex -> EndVertex, [theSplitParam, ParamLast]. + { + BRepGraphInc::EdgeDef& aSubB = + myGraph->myData->myIncStorage.ChangeEdge(BRepGraph_EdgeId(aSubBIdx)); + initSubEdgeEntity(aSubB, + aOrigCurve3DRepId, + aOrigTolerance, + aOrigSameParameter, + aSubBStartRefId, + aSubBEndRefId, + theSplitParam, + aOrigParamLast); + } + + myGraph->allocateUID(theSubA); + myGraph->allocateUID(theSubB); + + // Update incidence: wire CoEdgeRef rows and wire CoEdgeRefIds. + { + BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + + // Replace original edge's coedge with SubA+SubB coedges for each containing wire. + const NCollection_Vector* aWireIndices = + aStorage.ReverseIndex().WiresOfEdge(BRepGraph_EdgeId(theEdgeEntity.Index)); + if (aWireIndices != nullptr) + { + for (const BRepGraph_WireId& aWireId : *aWireIndices) + { + const int aWireIdx = aWireId.Index; + if (aWireIdx < 0 || aWireIdx >= aStorage.NbWires()) + continue; + for (BRepGraph_RefsCoEdgeOfWire aRefIt(*myGraph, BRepGraph_WireId(aWireIdx)); aRefIt.More(); + aRefIt.Next()) + { + const int aRefOrd = aRefIt.Index(); + const BRepGraph_CoEdgeRefId aRefId = aRefIt.CurrentId(); + const BRepGraphInc::CoEdgeRef& aRef = aStorage.CoEdgeRef(aRefId); + const int aOldCoEdgeIdx = aRef.CoEdgeDefId.Index; + if (aOldCoEdgeIdx < 0 || aOldCoEdgeIdx >= aStorage.NbCoEdges()) + continue; + + BRepGraphInc::CoEdgeDef& aOldCoEdge = + aStorage.ChangeCoEdge(BRepGraph_CoEdgeId(aOldCoEdgeIdx)); + if (aOldCoEdge.EdgeDefId == theEdgeEntity) + { + const TopAbs_Orientation aOrigOri = aOldCoEdge.Sense; + const BRepGraph_FaceId aFaceDef = aOldCoEdge.FaceDefId; + const TopLoc_Location aRefLoc = aRef.LocalLocation; + + // Replace in-place: update existing coedge to point to SubA. + aOldCoEdge.EdgeDefId = BRepGraph_EdgeId(aSubAIdx); + + // Create a new coedge for SubB and insert after SubA. + BRepGraphInc::CoEdgeDef& aSubBCoEdge = aStorage.AppendCoEdge(); + const int aSubBCoEdgeIdx = aStorage.NbCoEdges() - 1; + aSubBCoEdge.Id = BRepGraph_CoEdgeId(aSubBCoEdgeIdx); + aSubBCoEdge.EdgeDefId = BRepGraph_EdgeId(aSubBIdx); + aSubBCoEdge.Sense = aOrigOri; + aSubBCoEdge.FaceDefId = aFaceDef; + myGraph->allocateUID(aSubBCoEdge.Id); + + // Append ref-entry row for the new coedge under this wire (append-only RefId policy). + BRepGraphInc::CoEdgeRef& aSubBRef = aStorage.AppendCoEdgeRef(); + const int aSubBRefIdx = aStorage.NbCoEdgeRefs() - 1; + aSubBRef.RefId = BRepGraph_CoEdgeRefId(aSubBRefIdx); + aSubBRef.ParentId = BRepGraph_WireId(aWireIdx); + aSubBRef.CoEdgeDefId = BRepGraph_CoEdgeId(aSubBCoEdgeIdx); + aSubBRef.LocalLocation = aRefLoc; + myGraph->allocateRefUID(aSubBRef.RefId); + + // Add new CoEdgeRefId to wire entity's RefId vector right after the + // replaced slot to preserve coedge walk order. + BRepGraphInc::WireDef& aWireEnt = aStorage.ChangeWire(BRepGraph_WireId(aWireIdx)); + if (aRefOrd >= 0 && aRefOrd < aWireEnt.CoEdgeRefIds.Length()) + { + aWireEnt.CoEdgeRefIds.InsertAfter(aRefOrd, BRepGraph_CoEdgeRefId(aSubBRefIdx)); + } + else + { + aWireEnt.CoEdgeRefIds.Append(BRepGraph_CoEdgeRefId(aSubBRefIdx)); + } + + // Maintain coedge->wire reverse index for incremental queries. + aStorage.ChangeReverseIndex().BindCoEdgeToWire(BRepGraph_CoEdgeId(aSubBCoEdgeIdx), + BRepGraph_WireId(aWireIdx)); + break; + } + } + } + } + + // Mark original edge as removed in incidence. + myGraph->myData->myIncStorage.MarkRemoved(theEdgeEntity); + } + + // Split PCurve entries for each CoEdge referencing the original edge. + // Copy CoEdge data before mutation (vector may reallocate). + NCollection_Vector aOrigCoEdges; + { + const NCollection_Vector* aCoEdgeIdxs = + myGraph->myData->myIncStorage.ReverseIndex().CoEdgesOfEdge( + BRepGraph_EdgeId(theEdgeEntity.Index)); + if (aCoEdgeIdxs != nullptr) + { + for (const BRepGraph_CoEdgeId& aCoEdgeId : *aCoEdgeIdxs) + aOrigCoEdges.Append(myGraph->myData->myIncStorage.CoEdge(aCoEdgeId)); + } + } + + const double aParamRange = aOrigParamLast - aOrigParamFirst; + for (const BRepGraphInc::CoEdgeDef& aCE : aOrigCoEdges) + { + // Compute 2D split parameter. + double aPCSplit; + if (aOrigSameRange) + { + aPCSplit = theSplitParam; + } + else + { + const double aPCRange = aCE.ParamLast - aCE.ParamFirst; + if (aParamRange > 0.0) + aPCSplit = aCE.ParamFirst + ((theSplitParam - aOrigParamFirst) / aParamRange) * aPCRange; + else + aPCSplit = 0.5 * (aCE.ParamFirst + aCE.ParamLast); + } + + // Create CoEdge for SubA. + BRepGraphInc::CoEdgeDef& aCoEdgeSubA = myGraph->myData->myIncStorage.AppendCoEdge(); + const int aCoEdgeSubAIdx = myGraph->myData->myIncStorage.NbCoEdges() - 1; + aCoEdgeSubA.Id = BRepGraph_CoEdgeId(aCoEdgeSubAIdx); + initSubCoEdgeEntity(aCoEdgeSubA, + BRepGraph_EdgeId(aSubAIdx), + aCE.FaceDefId, + aCE.Sense, + aCE.Curve2DRepId, + aCE.ParamFirst, + aPCSplit, + aCE.Continuity); + myGraph->allocateUID(aCoEdgeSubA.Id); + + // Create CoEdge for SubB. + BRepGraphInc::CoEdgeDef& aCoEdgeSubB = myGraph->myData->myIncStorage.AppendCoEdge(); + const int aCoEdgeSubBIdx = myGraph->myData->myIncStorage.NbCoEdges() - 1; + aCoEdgeSubB.Id = BRepGraph_CoEdgeId(aCoEdgeSubBIdx); + initSubCoEdgeEntity(aCoEdgeSubB, + BRepGraph_EdgeId(aSubBIdx), + aCE.FaceDefId, + aCE.Sense, + aCE.Curve2DRepId, + aPCSplit, + aCE.ParamLast, + aCE.Continuity); + myGraph->allocateUID(aCoEdgeSubB.Id); + } + + // Register TopoDS shapes for sub-edges so OriginalOf() works in downstream algorithms. + if (aOrigCurve3DRepId.IsValid()) + { + const occ::handle& aOrigCurve3d = + myGraph->myData->myIncStorage.Curve3DRep(aOrigCurve3DRepId).Curve; + if (!aOrigCurve3d.IsNull()) + { + BRep_Builder aBB; + + const TopoDS_Shape aStartVShape = aOrigStartVertexDefId.IsValid() + ? myGraph->Shapes().Shape(aOrigStartVertexDefId) + : TopoDS_Shape(); + const TopoDS_Shape aSplitVShape = myGraph->Shapes().Shape(theSplitVertex); + const TopoDS_Shape aEndVShape = aOrigEndVertexDefId.IsValid() + ? myGraph->Shapes().Shape(aOrigEndVertexDefId) + : TopoDS_Shape(); + + TopoDS_Edge aSubAEdge; + aBB.MakeEdge(aSubAEdge, aOrigCurve3d, TopLoc_Location(), aOrigTolerance); + aBB.Range(aSubAEdge, aOrigParamFirst, theSplitParam); + if (!aStartVShape.IsNull()) + aBB.Add(aSubAEdge, aStartVShape.Oriented(TopAbs_FORWARD)); + if (!aSplitVShape.IsNull()) + aBB.Add(aSubAEdge, aSplitVShape.Oriented(TopAbs_REVERSED)); + myGraph->myData->myIncStorage.BindOriginal(theSubA, aSubAEdge); + + TopoDS_Edge aSubBEdge; + aBB.MakeEdge(aSubBEdge, aOrigCurve3d, TopLoc_Location(), aOrigTolerance); + aBB.Range(aSubBEdge, theSplitParam, aOrigParamLast); + if (!aSplitVShape.IsNull()) + aBB.Add(aSubBEdge, aSplitVShape.Oriented(TopAbs_FORWARD)); + if (!aEndVShape.IsNull()) + aBB.Add(aSubBEdge, aEndVShape.Oriented(TopAbs_REVERSED)); + myGraph->myData->myIncStorage.BindOriginal(theSubB, aSubBEdge); + } + } + + // Update edge-to-wire reverse index incrementally. + BRepGraphInc_ReverseIndex& aRevIdx = myGraph->myData->myIncStorage.ChangeReverseIndex(); + for (const BRepGraph_WireId& aWireId : aOrigWires) + { + aRevIdx.UnbindEdgeFromWire(BRepGraph_EdgeId(theEdgeEntity.Index), aWireId); + aRevIdx.BindEdgeToWire(BRepGraph_EdgeId(aSubAIdx), aWireId); + aRevIdx.BindEdgeToWire(BRepGraph_EdgeId(aSubBIdx), aWireId); + myGraph->markModified(aWireId); + } + + // Incremental vertex-to-edge updates: register sub-edge vertices. + { + const BRepGraphInc_Storage& aStorageRef = myGraph->myData->myIncStorage; + const BRepGraphInc::EdgeDef& aSubAEnt = aStorageRef.Edge(BRepGraph_EdgeId(aSubAIdx)); + const BRepGraphInc::EdgeDef& aSubBEnt = aStorageRef.Edge(BRepGraph_EdgeId(aSubBIdx)); + BRepGraphInc_ReverseIndex& aRevIdxMut = myGraph->myData->myIncStorage.ChangeReverseIndex(); + + // Resolve vertex def ids from the sub-edge ref entries. + if (aSubAEnt.StartVertexRefId.IsValid()) + { + const BRepGraph_VertexId aVtxId = + aStorageRef.VertexRef(aSubAEnt.StartVertexRefId).VertexDefId; + if (aVtxId.IsValid()) + aRevIdxMut.BindVertexToEdge(aVtxId, BRepGraph_EdgeId(aSubAIdx)); + } + if (aSubAEnt.EndVertexRefId.IsValid()) + { + const BRepGraph_VertexId aVtxId = aStorageRef.VertexRef(aSubAEnt.EndVertexRefId).VertexDefId; + if (aVtxId.IsValid()) + aRevIdxMut.BindVertexToEdge(aVtxId, BRepGraph_EdgeId(aSubAIdx)); + } + if (aSubBEnt.StartVertexRefId.IsValid()) + { + const BRepGraph_VertexId aVtxId = + aStorageRef.VertexRef(aSubBEnt.StartVertexRefId).VertexDefId; + if (aVtxId.IsValid()) + aRevIdxMut.BindVertexToEdge(aVtxId, BRepGraph_EdgeId(aSubBIdx)); + } + if (aSubBEnt.EndVertexRefId.IsValid()) + { + const BRepGraph_VertexId aVtxId = aStorageRef.VertexRef(aSubBEnt.EndVertexRefId).VertexDefId; + if (aVtxId.IsValid()) + aRevIdxMut.BindVertexToEdge(aVtxId, BRepGraph_EdgeId(aSubBIdx)); + } + + // Remove old edge from vertex-to-edge index. + if (aOrigStartVertexDefId.IsValid()) + aRevIdxMut.UnbindVertexFromEdge(aOrigStartVertexDefId, BRepGraph_EdgeId(theEdgeEntity.Index)); + if (aOrigEndVertexDefId.IsValid()) + aRevIdxMut.UnbindVertexFromEdge(aOrigEndVertexDefId, BRepGraph_EdgeId(theEdgeEntity.Index)); + + // Edge-to-face: derive from original edge's CoEdges (same faces apply to both sub-edges). + for (const BRepGraphInc::CoEdgeDef& aCE : aOrigCoEdges) + { + if (aCE.FaceDefId.IsValid()) + { + aRevIdxMut.BindEdgeToFace(BRepGraph_EdgeId(aSubAIdx), aCE.FaceDefId); + aRevIdxMut.BindEdgeToFace(BRepGraph_EdgeId(aSubBIdx), aCE.FaceDefId); + } + } + } + + myGraph->markModified(theEdgeEntity); + myGraph->markModified(theSubA); + myGraph->markModified(theSubB); + + Standard_ASSERT_VOID(myGraph->myData->myIncStorage.ValidateReverseIndex(), + "SplitEdge: post-mutation reverse index inconsistency"); +} + +//================================================================================================= + +void BRepGraph::BuilderView::ReplaceEdgeInWire(const BRepGraph_WireId theWireDefId, + const BRepGraph_EdgeId theOldEdgeEntity, + const BRepGraph_EdgeId theNewEdgeEntity, + const bool theReversed) +{ + Standard_ASSERT_RETURN(theWireDefId.Index >= 0 + && theWireDefId.Index < myGraph->myData->myIncStorage.NbWires(), + "ReplaceEdgeInWire: wire index is out of range", + Standard_VOID_RETURN); + Standard_ASSERT_RETURN(theOldEdgeEntity.Index >= 0 + && theOldEdgeEntity.Index < myGraph->myData->myIncStorage.NbEdges(), + "ReplaceEdgeInWire: old edge index is out of range", + Standard_VOID_RETURN); + Standard_ASSERT_RETURN(theNewEdgeEntity.Index >= 0 + && theNewEdgeEntity.Index < myGraph->myData->myIncStorage.NbEdges(), + "ReplaceEdgeInWire: new edge index is out of range", + Standard_VOID_RETURN); + Standard_ASSERT_RETURN(!myGraph->myData->myIncStorage.Edge(theNewEdgeEntity).IsRemoved, + "ReplaceEdgeInWire: replacement edge must be active", + Standard_VOID_RETURN); + + BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + + // Update incidence by scanning wire-owned coedge ref entries. + for (BRepGraph_RefsCoEdgeOfWire aRefIt(*myGraph, theWireDefId); aRefIt.More(); aRefIt.Next()) + { + const BRepGraphInc::CoEdgeRef& aRef = aStorage.CoEdgeRef(aRefIt.CurrentId()); + const int aCoEdgeEntIdx = aRef.CoEdgeDefId.Index; + if (aCoEdgeEntIdx < 0 || aCoEdgeEntIdx >= aStorage.NbCoEdges()) + continue; + + BRepGraphInc::CoEdgeDef& aCoEdge = aStorage.ChangeCoEdge(BRepGraph_CoEdgeId(aCoEdgeEntIdx)); + if (aCoEdge.EdgeDefId == theOldEdgeEntity) + { + aCoEdge.EdgeDefId = theNewEdgeEntity; + if (theReversed) + aCoEdge.Sense = TopAbs::Reverse(aCoEdge.Sense); + + // Update reverse indices incrementally. + BRepGraphInc_ReverseIndex& aRevIdx = myGraph->myData->myIncStorage.ChangeReverseIndex(); + aRevIdx.ReplaceEdgeInWireMap(theOldEdgeEntity, theNewEdgeEntity, theWireDefId); + aRevIdx.UnbindEdgeFromCoEdge(theOldEdgeEntity, BRepGraph_CoEdgeId(aCoEdgeEntIdx)); + aRevIdx.BindEdgeToCoEdge(theNewEdgeEntity, BRepGraph_CoEdgeId(aCoEdgeEntIdx)); + + // Update edge-to-face: bind new edge, unbind old edge for all faces of this wire. + // Wire-to-face mappings are built from FaceDef.WireRefs during Build() and are + // stable across edge mutations - only face-level operations modify them. + const NCollection_Vector* aFaces = aRevIdx.FacesOfWire(theWireDefId); + if (aFaces != nullptr) + { + for (const BRepGraph_FaceId& aFaceId : *aFaces) + { + aRevIdx.BindEdgeToFace(theNewEdgeEntity, aFaceId); + aRevIdx.UnbindEdgeFromFace(theOldEdgeEntity, aFaceId); + } + } + } + } + + myGraph->markModified(BRepGraph_WireId(theWireDefId.Index)); + + // Validate reverse index only when not in deferred mode. + // In deferred mode (batch sewing, parallel mutations), intermediate states + // may have temporarily stale entries; validation runs at CommitMutation(). + if (!myGraph->myData->myDeferredMode.load(std::memory_order_relaxed)) + { + Standard_ASSERT_VOID(myGraph->myData->myIncStorage.ValidateReverseIndex(), + "ReplaceEdgeInWire: post-mutation reverse index inconsistency"); + } +} + +//================================================================================================= + +void BRepGraph::BuilderView::CommitMutation() noexcept +{ + const bool isValid = ValidateMutationBoundary(); + Standard_ASSERT_VOID(isValid, "CommitMutation: mutation boundary consistency check failed"); + (void)isValid; +} + +//================================================================================================= + +bool BRepGraph::BuilderView::ValidateMutationBoundary( + NCollection_Vector* const theIssues) const +{ + bool isValid = true; + const BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + + if (!aStorage.ValidateReverseIndex()) + { + isValid = false; + if (theIssues != nullptr) + { + BoundaryIssue anIssue; + anIssue.NodeId = BRepGraph_NodeId(); + anIssue.Description = "Mutation boundary reverse index inconsistency"; + theIssues->Append(anIssue); + } + } + + constexpr BRepGraph_NodeId::Kind THE_KINDS[] = {BRepGraph_NodeId::Kind::Vertex, + BRepGraph_NodeId::Kind::Edge, + BRepGraph_NodeId::Kind::CoEdge, + BRepGraph_NodeId::Kind::Wire, + BRepGraph_NodeId::Kind::Face, + BRepGraph_NodeId::Kind::Shell, + BRepGraph_NodeId::Kind::Solid, + BRepGraph_NodeId::Kind::Compound, + BRepGraph_NodeId::Kind::CompSolid, + BRepGraph_NodeId::Kind::Product, + BRepGraph_NodeId::Kind::Occurrence}; + for (int aKindIdx = 0; aKindIdx < static_cast(sizeof(THE_KINDS) / sizeof(THE_KINDS[0])); + ++aKindIdx) + { + const BRepGraph_NodeId::Kind aKind = THE_KINDS[aKindIdx]; + const int aCachedCnt = cachedActiveByKind(aStorage, aKind); + const int anActualCnt = countActiveByKind(*myGraph, aKind); + if (aCachedCnt == anActualCnt) + continue; + + isValid = false; + if (theIssues != nullptr) + { + BoundaryIssue anIssue; + TCollection_AsciiString aDesc("Mutation boundary active count mismatch for "); + aDesc += kindName(aKind); + aDesc += ": cached="; + aDesc += TCollection_AsciiString(aCachedCnt); + aDesc += " actual="; + aDesc += TCollection_AsciiString(anActualCnt); + anIssue.NodeId = BRepGraph_NodeId(aKind, -1); + anIssue.Description = aDesc; + theIssues->Append(anIssue); + } + } + + // Validate built-in layer consistency: layers must not have bindings + // for entity kinds that have zero active entities in the graph. + const occ::handle aParamLayer = + myGraph->LayerRegistry().FindLayer(); + if (!aParamLayer.IsNull() && aParamLayer->HasBindings() && aStorage.NbVertices() == 0) + { + isValid = false; + if (theIssues != nullptr) + { + BoundaryIssue anIssue; + anIssue.NodeId = BRepGraph_NodeId(); + anIssue.Description = "ParamLayer has bindings but graph has no vertices"; + theIssues->Append(anIssue); + } + } + + const occ::handle aRegularityLayer = + myGraph->LayerRegistry().FindLayer(); + if (!aRegularityLayer.IsNull() && aRegularityLayer->HasBindings() && aStorage.NbEdges() == 0) + { + isValid = false; + if (theIssues != nullptr) + { + BoundaryIssue anIssue; + anIssue.NodeId = BRepGraph_NodeId(); + anIssue.Description = "RegularityLayer has bindings but graph has no edges"; + theIssues->Append(anIssue); + } + } + + return isValid; +} diff --git a/src/ModelingData/TKBRep/BRepGraph/BRepGraph_BuilderView.hxx b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_BuilderView.hxx new file mode 100644 index 0000000000..d39b819cb6 --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_BuilderView.hxx @@ -0,0 +1,472 @@ +// Copyright (c) 2026 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 _BRepGraph_BuilderView_HeaderFile +#define _BRepGraph_BuilderView_HeaderFile + +#include +#include +#include +#include + +#include + +class Geom_Surface; +class Geom_Curve; +class Geom2d_Curve; + +//! @brief Non-const view for programmatic graph construction and mutation. +//! +//! Provides methods to create topology definition nodes (vertices, edges, +//! wires, faces, shells, solids, compounds) and assembly nodes (products, +//! occurrences) without an existing TopoDS_Shape. Also supports incremental +//! shape appending, soft-deletion of nodes, scoped mutable definition guards +//! (RAII Mut* methods), and deferred invalidation mode for batched mutation +//! loops under external serialization. +//! Obtained via BRepGraph::Builder(). +//! +//! Contract notes: +//! - Add* methods return BRepGraph_NodeId() on invalid inputs and do not +//! partially modify the graph; call IsValid() on the returned id to check +//! success +//! - invalid inputs include wrong kind, out-of-range ids, or removed referenced +//! nodes unless a method documents stricter accepted-input rules +//! - linking methods such as AddFaceToShell() and AddShellToSolid() return an +//! invalid typed RefId on failure and otherwise keep ownership explicit in +//! the reference layer +//! - `Mut*()` accessors raise `Standard_ProgramError` for null, out-of-range, +//! or removed typed ids +class BRepGraph::BuilderView +{ +public: + //! Add a vertex definition to the graph. + //! @param[in] thePoint 3D coordinates + //! @param[in] theTolerance vertex tolerance + //! @return typed vertex definition identifier + [[nodiscard]] Standard_EXPORT BRepGraph_VertexId AddVertex(const gp_Pnt& thePoint, + const double theTolerance); + + //! Add an edge definition to the graph. + //! @param[in] theStartVtx typed start vertex definition identifier + //! @param[in] theEndVtx typed end vertex definition identifier + //! @param[in] theCurve 3D curve (may be null for degenerate edges) + //! @param[in] theFirst first curve parameter + //! @param[in] theLast last curve parameter + //! @param[in] theTolerance edge tolerance + //! @return typed edge definition identifier, or invalid if either referenced + //! vertex id is out of range or removed + [[nodiscard]] Standard_EXPORT BRepGraph_EdgeId AddEdge(const BRepGraph_VertexId theStartVtx, + const BRepGraph_VertexId theEndVtx, + const occ::handle& theCurve, + const double theFirst, + const double theLast, + const double theTolerance); + + //! Add a wire definition to the graph. + //! Each pair is (EdgeDefId, OrientationInWire). + //! @param[in] theEdges ordered edge entries + //! @return typed wire definition identifier, or invalid if any referenced + //! edge entry is invalid + [[nodiscard]] Standard_EXPORT BRepGraph_WireId + AddWire(const NCollection_Vector>& theEdges); + + //! Add a face definition to the graph. + //! @param[in] theSurface surface geometry + //! @param[in] theOuterWire typed outer wire definition identifier + //! @param[in] theInnerWires typed inner wire definition identifiers + //! @param[in] theTolerance face tolerance + //! @return typed face definition identifier, or invalid if any referenced + //! wire id is out of range or removed + [[nodiscard]] Standard_EXPORT BRepGraph_FaceId + AddFace(const occ::handle& theSurface, + const BRepGraph_WireId theOuterWire, + const NCollection_Vector& theInnerWires, + const double theTolerance); + + //! Add an empty shell definition to the graph. + //! @return typed shell definition identifier + [[nodiscard]] Standard_EXPORT BRepGraph_ShellId AddShell(); + + //! Add an empty solid definition to the graph. + //! @return typed solid definition identifier + [[nodiscard]] Standard_EXPORT BRepGraph_SolidId AddSolid(); + + //! Link a face to a shell. + //! Appends FaceRef and stores its FaceRefId in shell FaceRefIds. + //! @param[in] theShellEntity typed shell definition identifier + //! @param[in] theFaceEntity typed face definition identifier + //! @param[in] theOri orientation of the face in the shell + //! @return typed face reference identifier, or invalid if inputs are not active + Standard_EXPORT BRepGraph_FaceRefId + AddFaceToShell(const BRepGraph_ShellId theShellEntity, + const BRepGraph_FaceId theFaceEntity, + const TopAbs_Orientation theOri = TopAbs_FORWARD); + + //! Link a shell to a solid. + //! Appends ShellRef and stores its ShellRefId in solid ShellRefIds. + //! @param[in] theSolidEntity typed solid definition identifier + //! @param[in] theShellEntity typed shell definition identifier + //! @param[in] theOri orientation of the shell in the solid + //! @return typed shell reference identifier, or invalid if inputs are not active + Standard_EXPORT BRepGraph_ShellRefId + AddShellToSolid(const BRepGraph_SolidId theSolidEntity, + const BRepGraph_ShellId theShellEntity, + const TopAbs_Orientation theOri = TopAbs_FORWARD); + + //! Add a compound definition with child definitions. + //! @param[in] theChildEntities child definition NodeIds + //! @return typed compound definition identifier + [[nodiscard]] Standard_EXPORT BRepGraph_CompoundId + AddCompound(const NCollection_Vector& theChildEntities); + + //! Add a compsolid definition with child solid definitions. + //! @param[in] theSolidEntities typed child solid definition identifiers + //! @return typed compsolid definition identifier + [[nodiscard]] Standard_EXPORT BRepGraph_CompSolidId + AddCompSolid(const NCollection_Vector& theSolidEntities); + + //! Add a part product with a root shape node. + //! @param[in] theShapeRoot root topology NodeId for the part + //! @return typed product definition identifier, or invalid if the root is + //! not an active topology definition node + [[nodiscard]] Standard_EXPORT BRepGraph_ProductId AddProduct(const BRepGraph_NodeId theShapeRoot); + + //! Add an assembly product (no root shape, has child occurrences). + //! @return typed product definition identifier + [[nodiscard]] Standard_EXPORT BRepGraph_ProductId AddAssemblyProduct(); + + //! Add an occurrence linking a parent product to a referenced (child) product. + //! ParentOccurrenceIdx is set to -1 (top-level). + //! @param[in] theParentProduct typed parent assembly product identifier + //! @param[in] theReferencedProduct typed child product identifier being instantiated + //! @param[in] thePlacement local placement relative to parent + //! @return typed occurrence definition identifier, or invalid unless the + //! parent is an active assembly product and the referenced product is active + [[nodiscard]] Standard_EXPORT BRepGraph_OccurrenceId + AddOccurrence(const BRepGraph_ProductId theParentProduct, + const BRepGraph_ProductId theReferencedProduct, + const TopLoc_Location& thePlacement); + + //! Add an occurrence with an explicit parent occurrence for nested assembly chains. + //! This establishes a tree-structured placement path for unambiguous + //! GlobalLocation() / GlobalOrientation() computation even when products are shared (DAG). + //! @param[in] theParentProduct typed parent assembly product identifier + //! @param[in] theReferencedProduct typed child product identifier being instantiated + //! @param[in] thePlacement local placement relative to parent + //! @param[in] theParentOccurrence typed occurrence that placed the parent product + //! @return typed occurrence definition identifier, or invalid unless the + //! parent product, referenced product, and explicit parent occurrence + //! form a valid active assembly chain + [[nodiscard]] Standard_EXPORT BRepGraph_OccurrenceId + AddOccurrence(const BRepGraph_ProductId theParentProduct, + const BRepGraph_ProductId theReferencedProduct, + const TopLoc_Location& thePlacement, + const BRepGraph_OccurrenceId theParentOccurrence); + + //! Append a shape to the existing graph without clearing. + //! Compound/CompSolid/Solid/Shell inputs are flattened to appended face roots. + //! @param[in] theShape shape to add + //! @param[in] theParallel if true, per-face geometry extraction is parallel + Standard_EXPORT void AppendFlattenedShape(const TopoDS_Shape& theShape, + const bool theParallel = false); + + //! Create a new Curve2DRep in storage and return its typed identifier. + //! Use this when assigning a new PCurve to an existing CoEdge entity + //! via MutCoEdge() inside a larger mutation sequence. + //! For one-shot creation and binding of a face-context PCurve, use + //! AddPCurveToEdge(). + //! @param[in] theCurve2d the 2D parametric curve handle + //! @return typed Curve2DRep identifier, or invalid if the curve is null + [[nodiscard]] Standard_EXPORT BRepGraph_Curve2DRepId + CreateCurve2DRep(const occ::handle& theCurve2d); + + //! Attach a PCurve to an edge for a given face context. + //! Creates a new CoEdge entity with Curve2DRep and updates reverse indices. + //! This always appends a new CoEdge entry for the edge-face pair; callers + //! should avoid duplicate creation unless multiple bindings are intentional + //! for the modeled topology. + //! Prefer this route when the caller needs to add a face-context PCurve in + //! one operation. For editing an already identified CoEdge inside a larger + //! mutation sequence, use CreateCurve2DRep() with MutCoEdge(). + //! @param[in] theEdgeEntity typed edge definition identifier + //! @param[in] theFaceEntity typed face definition identifier + //! @param[in] theCurve2d 2D curve geometry + //! @param[in] theFirst first curve parameter + //! @param[in] theLast last curve parameter + //! @param[in] theEdgeOrientation edge orientation on the face + Standard_EXPORT void AddPCurveToEdge( + const BRepGraph_EdgeId theEdgeEntity, + const BRepGraph_FaceId theFaceEntity, + const occ::handle& theCurve2d, + const double theFirst, + const double theLast, + const TopAbs_Orientation theEdgeOrientation = TopAbs_FORWARD); + + //! Mark a node as removed (soft deletion). + //! @param[in] theNode node to remove + Standard_EXPORT void RemoveNode(const BRepGraph_NodeId theNode); + + //! Mark a node as removed with a known replacement (sewing/deduplicate). + //! For Edge nodes: all CoEdges referencing the removed edge are reparented to + //! the replacement edge (EdgeIdx updated, reverse index rebound). This prevents + //! orphaned CoEdges that would disappear from CoEdgesOfEdge() queries. + //! Layers are notified with both old and replacement NodeIds for data migration. + //! @param[in] theNode node to remove + //! @param[in] theReplacement node that replaces theNode + Standard_EXPORT void RemoveNode(const BRepGraph_NodeId theNode, + const BRepGraph_NodeId theReplacement); + + //! Mark a node and all its descendants as removed (cascading soft deletion). + //! @param[in] theNode root node to remove + Standard_EXPORT void RemoveSubgraph(const BRepGraph_NodeId theNode); + + //! Mark a representation entry as removed (soft deletion). + //! Invalid or already-removed ids are ignored. + //! Owning topology entities are marked modified so generation-based caches + //! and read helpers observe the representation as absent. + //! @param[in] theRep representation to remove + Standard_EXPORT void RemoveRep(const BRepGraph_RepId theRep); + + //! @name Deferred invalidation mode for batch mutation loops. + + //! Begin deferred invalidation mode. + //! While active, markModified() only increments OwnGen + SubtreeGen and + //! appends to the deferred list - without acquiring the shape-cache mutex + //! or propagating upward. + //! Call EndDeferredInvalidation() to batch-flush all accumulated changes. + //! Intended for batch mutation loops (SameParameter, Sewing) where many + //! entities are modified sequentially and upward propagation should be + //! deferred until all mutations are complete. + //! Prefer BRepGraph_DeferredScope RAII guard. + //! @warning Deferred mode batches invalidation only; it does NOT serialize + //! the mutation body. Callers must guarantee exclusive Builder() mutation + //! access for the whole deferred scope; direct concurrent `Mut*()` usage + //! still requires external synchronization around the surrounding batch. + Standard_EXPORT void BeginDeferredInvalidation(); + + //! End deferred invalidation mode and batch-flush: + //! propagates SubtreeGen upward for all modified entities from the deferred + //! list. Shape cache entries are validated lazily via SubtreeGen comparison. + Standard_EXPORT void EndDeferredInvalidation() noexcept; + + //! Check if deferred invalidation mode is currently active. + //! @note This is a state flag only. It does not imply mutation ownership + //! or synchronization guarantees. + [[nodiscard]] Standard_EXPORT bool IsDeferredMode() const; + + //! @name Topology editing operations. + + //! A single boundary invariant issue detected by ValidateMutationBoundary(). + struct BoundaryIssue + { + BRepGraph_NodeId NodeId; + TCollection_AsciiString Description; + }; + + //! Split a single edge definition at a vertex and 3D-curve parameter. + //! Creates two new EdgeDef slots, splits all PCurve nodes at the corresponding + //! 2D parameter, and updates every wire that contained the original edge. + //! @param[in] theEdgeEntity edge to split (must not be degenerate) + //! @param[in] theSplitVertex vertex definition at the split point (already in graph) + //! @param[in] theSplitParam parameter on the 3D curve at the split point + //! @param[out] theSubA sub-edge: StartVertex -> SplitVertex + //! @param[out] theSubB sub-edge: SplitVertex -> EndVertex + Standard_EXPORT void SplitEdge(const BRepGraph_EdgeId theEdgeEntity, + const BRepGraph_VertexId theSplitVertex, + const double theSplitParam, + BRepGraph_EdgeId& theSubA, + BRepGraph_EdgeId& theSubB); + + //! Replace one edge with another in a wire definition. + //! Updates the CoEdge's EdgeIdx to point to the new edge, adjusts orientation + //! if theReversed, and incrementally updates reverse indices. + //! @param[in] theWireDefId wire definition identifier + //! @param[in] theOldEdgeEntity edge to replace + //! @param[in] theNewEdgeEntity replacement edge + //! @param[in] theReversed if true, reverse the orientation of the replacement + Standard_EXPORT void ReplaceEdgeInWire(const BRepGraph_WireId theWireDefId, + const BRepGraph_EdgeId theOldEdgeEntity, + const BRepGraph_EdgeId theNewEdgeEntity, + const bool theReversed); + + //! Apply a modification operation and record history. + //! @param[in] theTarget node to modify + //! @param[in] theModifier callback that performs the modification and returns replacements + //! @param[in] theOpLabel human-readable operation label for history + template + void ApplyModification(const BRepGraph_NodeId theTarget, + ModifierT&& theModifier, + const TCollection_AsciiString& theOpLabel) + { + NCollection_Vector aReplacements = + std::forward(theModifier)(*myGraph, theTarget); + applyModificationImpl(theTarget, std::move(aReplacements), theOpLabel); + } + + //! Finalize a batch of mutations. + //! Validates reverse-index consistency and asserts active entity counts + //! match actual entity state. + //! Call this after manual batch mutation loops, or rely on + //! BRepGraph_DeferredScope to call it automatically at scope exit. + Standard_EXPORT void CommitMutation() noexcept; + + //! Validate lightweight mutation-boundary invariants. + //! @param[out] theIssues optional destination for detailed issues + //! @return true if no issues were found + [[nodiscard]] Standard_EXPORT bool ValidateMutationBoundary( + NCollection_Vector* const theIssues = nullptr) const; + + //! @name Scoped mutable guards (RAII). + //! Return a BRepGraph_MutGuard that defers notification to scope exit. + //! Use when modifying multiple fields on the same entity. + + //! Return scoped mutable edge definition guard. + //! @param[in] theEdge typed edge identifier + Standard_EXPORT BRepGraph_MutGuard MutEdge(const BRepGraph_EdgeId theEdge); + + //! Return scoped mutable vertex definition guard. + //! @param[in] theVertex typed vertex identifier + Standard_EXPORT BRepGraph_MutGuard MutVertex( + const BRepGraph_VertexId theVertex); + + //! Return scoped mutable wire definition guard. + //! @param[in] theWire typed wire identifier + Standard_EXPORT BRepGraph_MutGuard MutWire(const BRepGraph_WireId theWire); + + //! Return scoped mutable face definition guard. + //! @param[in] theFace typed face identifier + Standard_EXPORT BRepGraph_MutGuard MutFace(const BRepGraph_FaceId theFace); + + //! Return scoped mutable shell definition guard. + //! @param[in] theShell typed shell identifier + Standard_EXPORT BRepGraph_MutGuard MutShell( + const BRepGraph_ShellId theShell); + + //! Return scoped mutable solid definition guard. + //! @param[in] theSolid typed solid identifier + Standard_EXPORT BRepGraph_MutGuard MutSolid( + const BRepGraph_SolidId theSolid); + + //! Return scoped mutable compound definition guard. + //! @param[in] theCompound typed compound identifier + Standard_EXPORT BRepGraph_MutGuard MutCompound( + const BRepGraph_CompoundId theCompound); + + //! Return scoped mutable coedge definition guard. + //! @param[in] theCoEdge typed coedge identifier + Standard_EXPORT BRepGraph_MutGuard MutCoEdge( + const BRepGraph_CoEdgeId theCoEdge); + + //! Return scoped mutable comp-solid definition guard. + //! @param[in] theCompSolid typed comp-solid identifier + Standard_EXPORT BRepGraph_MutGuard MutCompSolid( + const BRepGraph_CompSolidId theCompSolid); + + //! Return scoped mutable product definition guard. + //! @param[in] theProduct typed product identifier + Standard_EXPORT BRepGraph_MutGuard MutProduct( + const BRepGraph_ProductId theProduct); + + //! Return scoped mutable occurrence definition guard. + //! @param[in] theOccurrence typed occurrence identifier + Standard_EXPORT BRepGraph_MutGuard MutOccurrence( + const BRepGraph_OccurrenceId theOccurrence); + + //! Return scoped mutable shell reference guard. + //! @param[in] theShellRef typed shell reference identifier + Standard_EXPORT BRepGraph_MutGuard MutShellRef( + const BRepGraph_ShellRefId theShellRef); + + //! Return scoped mutable face reference guard. + //! @param[in] theFaceRef typed face reference identifier + Standard_EXPORT BRepGraph_MutGuard MutFaceRef( + const BRepGraph_FaceRefId theFaceRef); + + //! Return scoped mutable wire reference guard. + //! @param[in] theWireRef typed wire reference identifier + Standard_EXPORT BRepGraph_MutGuard MutWireRef( + const BRepGraph_WireRefId theWireRef); + + //! Return scoped mutable coedge reference guard. + //! @param[in] theCoEdgeRef typed coedge reference identifier + Standard_EXPORT BRepGraph_MutGuard MutCoEdgeRef( + const BRepGraph_CoEdgeRefId theCoEdgeRef); + + //! Return scoped mutable vertex reference guard. + //! @param[in] theVertexRef typed vertex reference identifier + Standard_EXPORT BRepGraph_MutGuard MutVertexRef( + const BRepGraph_VertexRefId theVertexRef); + + //! Return scoped mutable solid reference guard. + //! @param[in] theSolidRef typed solid reference identifier + Standard_EXPORT BRepGraph_MutGuard MutSolidRef( + const BRepGraph_SolidRefId theSolidRef); + + //! Return scoped mutable child reference guard. + //! @param[in] theChildRef typed child reference identifier + Standard_EXPORT BRepGraph_MutGuard MutChildRef( + const BRepGraph_ChildRefId theChildRef); + + //! Return scoped mutable occurrence reference guard. + //! @param[in] theOccurrenceRef typed occurrence reference identifier + Standard_EXPORT BRepGraph_MutGuard MutOccurrenceRef( + const BRepGraph_OccurrenceRefId theOccurrenceRef); + + //! @name Representation mutation guards. + + //! Return scoped mutable surface representation guard. + //! On destruction, increments OwnGen and propagates mutation to owning Face(s). + Standard_EXPORT BRepGraph_MutGuard MutSurface( + const BRepGraph_SurfaceRepId theSurface); + + //! Return scoped mutable 3D curve representation guard. + Standard_EXPORT BRepGraph_MutGuard MutCurve3D( + const BRepGraph_Curve3DRepId theCurve); + + //! Return scoped mutable 2D curve (PCurve) representation guard. + Standard_EXPORT BRepGraph_MutGuard MutCurve2D( + const BRepGraph_Curve2DRepId theCurve); + + //! Return scoped mutable triangulation representation guard. + Standard_EXPORT BRepGraph_MutGuard MutTriangulation( + const BRepGraph_TriangulationRepId theTriangulation); + + //! Return scoped mutable 3D polygon representation guard. + Standard_EXPORT BRepGraph_MutGuard MutPolygon3D( + const BRepGraph_Polygon3DRepId thePolygon); + + //! Return scoped mutable 2D polygon representation guard. + Standard_EXPORT BRepGraph_MutGuard MutPolygon2D( + const BRepGraph_Polygon2DRepId thePolygon); + + //! Return scoped mutable polygon-on-triangulation representation guard. + Standard_EXPORT BRepGraph_MutGuard MutPolygonOnTri( + const BRepGraph_PolygonOnTriRepId thePolygon); + +private: + friend class BRepGraph; + friend struct BRepGraph_Data; + + Standard_EXPORT void applyModificationImpl(const BRepGraph_NodeId theTarget, + NCollection_Vector&& theReplacements, + const TCollection_AsciiString& theOpLabel); + + explicit BuilderView(BRepGraph* theGraph) + : myGraph(theGraph) + { + } + + BRepGraph* myGraph; +}; + +#endif // _BRepGraph_BuilderView_HeaderFile diff --git a/src/ModelingData/TKBRep/BRepGraph/BRepGraph_CacheView.cxx b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_CacheView.cxx new file mode 100644 index 0000000000..d4fa348e3d --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_CacheView.cxx @@ -0,0 +1,139 @@ +// Copyright (c) 2026 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 + +//================================================================================================= + +void BRepGraph::CacheView::Set(const BRepGraph_NodeId theNode, + const occ::handle& theKind, + const occ::handle& theValue) +{ + const BRepGraphInc::BaseDef* aDef = myGraph->topoEntity(theNode); + if (aDef == nullptr) + { + return; + } + myGraph->transientCache().Set(theNode, theKind, theValue, aDef->SubtreeGen); +} + +//================================================================================================= + +void BRepGraph::CacheView::Set(const BRepGraph_NodeId theNode, + const int theKindSlot, + const occ::handle& theValue) +{ + const BRepGraphInc::BaseDef* aDef = myGraph->topoEntity(theNode); + if (aDef == nullptr) + { + return; + } + myGraph->transientCache().Set(theNode, theKindSlot, theValue, aDef->SubtreeGen); +} + +//================================================================================================= + +occ::handle BRepGraph::CacheView::Get( + const BRepGraph_NodeId theNode, + const occ::handle& theKind) const +{ + const BRepGraphInc::BaseDef* aDef = myGraph->topoEntity(theNode); + if (aDef == nullptr) + { + return occ::handle(); + } + return myGraph->transientCache().Get(theNode, theKind, aDef->SubtreeGen); +} + +//================================================================================================= + +occ::handle BRepGraph::CacheView::Get(const BRepGraph_NodeId theNode, + const int theKindSlot) const +{ + const BRepGraphInc::BaseDef* aDef = myGraph->topoEntity(theNode); + if (aDef == nullptr) + { + return occ::handle(); + } + return myGraph->transientCache().Get(theNode, theKindSlot, aDef->SubtreeGen); +} + +//================================================================================================= + +bool BRepGraph::CacheView::Has(const BRepGraph_NodeId theNode, + const occ::handle& theKind) const +{ + return !Get(theNode, theKind).IsNull(); +} + +//================================================================================================= + +bool BRepGraph::CacheView::Has(const BRepGraph_NodeId theNode, const int theKindSlot) const +{ + return !Get(theNode, theKindSlot).IsNull(); +} + +//================================================================================================= + +bool BRepGraph::CacheView::Remove(const BRepGraph_NodeId theNode, + const occ::handle& theKind) +{ + return myGraph->transientCache().Remove(theNode, theKind); +} + +//================================================================================================= + +bool BRepGraph::CacheView::Remove(const BRepGraph_NodeId theNode, const int theKindSlot) +{ + return myGraph->transientCache().Remove(theNode, theKindSlot); +} + +//================================================================================================= + +void BRepGraph::CacheView::Invalidate(const BRepGraph_NodeId theNode, + const occ::handle& theKind) +{ + const BRepGraphInc::BaseDef* aDef = myGraph->topoEntity(theNode); + if (aDef == nullptr) + { + return; + } + + occ::handle aValue = + myGraph->transientCache().Get(theNode, theKind, aDef->SubtreeGen); + if (!aValue.IsNull()) + { + aValue->Invalidate(); + } +} + +//================================================================================================= + +void BRepGraph::CacheView::Invalidate(const BRepGraph_NodeId theNode, const int theKindSlot) +{ + occ::handle aValue = Get(theNode, theKindSlot); + if (!aValue.IsNull()) + { + aValue->Invalidate(); + } +} + +//================================================================================================= + +NCollection_Vector> BRepGraph::CacheView::CacheKinds( + const BRepGraph_NodeId theNode) const +{ + return myGraph->transientCache().CacheKinds(theNode); +} diff --git a/src/ModelingData/TKBRep/BRepGraph/BRepGraph_CacheView.hxx b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_CacheView.hxx new file mode 100644 index 0000000000..8cd71e77b1 --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_CacheView.hxx @@ -0,0 +1,116 @@ +// Copyright (c) 2026 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 _BRepGraph_CacheView_HeaderFile +#define _BRepGraph_CacheView_HeaderFile + +#include + +//! @brief Non-const view for managing transient cache values on nodes. +//! +//! This view is the stable public cache API for BRepGraph callers. +//! External code should use Cache() for all cache access. +//! Low-level storage operations such as reserve or cross-graph cache transfer +//! stay internal to graph-maintenance code through the graph's private cache access. +//! +//! Cached values are keyed by BRepGraph_CacheKind descriptors (Handle-based) +//! and stored as Handle(BRepGraph_CacheValue). Each CacheKind carries a +//! Standard_GUID for stable identity and is registered in +//! BRepGraph_CacheKindRegistry which maps GUIDs to dense runtime slot +//! indices for O(1) internal storage lookup. +//! +//! Supports set, get, remove, invalidate, and kind enumeration per node. +//! Cached data is stored centrally in BRepGraph_TransientCache with +//! generation-based freshness tracking via SubtreeGen. +//! Hot-path callers may pre-resolve a cache-kind slot once through +//! BRepGraph_CacheKindRegistry::Register() and then use slot-based overloads +//! to avoid repeated registry locking. +//! Obtained via BRepGraph::Cache(). +class BRepGraph::CacheView +{ +public: + //! Attach a cached value to a node. + //! @param[in] theNode node to attach the value to + //! @param[in] theKind cache kind descriptor identifying the slot + //! @param[in] theValue cached value to store + Standard_EXPORT void Set(const BRepGraph_NodeId theNode, + const occ::handle& theKind, + const occ::handle& theValue); + + //! Attach a cached value using a pre-resolved cache-kind slot. + Standard_EXPORT void Set(const BRepGraph_NodeId theNode, + const int theKindSlot, + const occ::handle& theValue); + + //! Retrieve a cached value from a node. + //! @param[in] theNode node to query + //! @param[in] theKind cache kind descriptor identifying the slot + //! @return cached value, or null handle if not present or stale + [[nodiscard]] Standard_EXPORT occ::handle Get( + const BRepGraph_NodeId theNode, + const occ::handle& theKind) const; + + //! Retrieve a cached value using a pre-resolved cache-kind slot. + [[nodiscard]] Standard_EXPORT occ::handle Get( + const BRepGraph_NodeId theNode, + const int theKindSlot) const; + + //! Check if a non-stale cached value exists on a node. + //! @param[in] theNode node to query + //! @param[in] theKind cache kind descriptor identifying the slot + //! @return true if a current value exists for this node and kind + [[nodiscard]] Standard_EXPORT bool Has(const BRepGraph_NodeId theNode, + const occ::handle& theKind) const; + + //! Check if a non-stale cached value exists using a pre-resolved slot. + [[nodiscard]] Standard_EXPORT bool Has(const BRepGraph_NodeId theNode, + const int theKindSlot) const; + + //! Remove a cached value from a node. + //! @param[in] theNode node to remove the value from + //! @param[in] theKind cache kind descriptor identifying the slot + //! @return true if a value was actually removed + Standard_EXPORT bool Remove(const BRepGraph_NodeId theNode, + const occ::handle& theKind); + + //! Remove a cached value using a pre-resolved cache-kind slot. + Standard_EXPORT bool Remove(const BRepGraph_NodeId theNode, const int theKindSlot); + + //! Invalidate (but do not remove) a cached value on a node. + //! @param[in] theNode node whose cache entry to invalidate + //! @param[in] theKind cache kind descriptor identifying the slot + Standard_EXPORT void Invalidate(const BRepGraph_NodeId theNode, + const occ::handle& theKind); + + //! Invalidate a cached value using a pre-resolved cache-kind slot. + Standard_EXPORT void Invalidate(const BRepGraph_NodeId theNode, const int theKindSlot); + + //! Return all cache kinds populated on a node. + //! @param[in] theNode node to query + //! @return vector of cache kind descriptors that have stored values on this node + [[nodiscard]] Standard_EXPORT NCollection_Vector> CacheKinds( + const BRepGraph_NodeId theNode) const; + +private: + friend class BRepGraph; + friend struct BRepGraph_Data; + + explicit CacheView(BRepGraph* theGraph) + : myGraph(theGraph) + { + } + + BRepGraph* myGraph; +}; + +#endif // _BRepGraph_CacheView_HeaderFile diff --git a/src/ModelingData/TKBRep/BRepGraph/BRepGraph_ChildExplorer.cxx b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_ChildExplorer.cxx new file mode 100644 index 0000000000..33d27c77e6 --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_ChildExplorer.cxx @@ -0,0 +1,890 @@ +// Copyright (c) 2026 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 + +namespace +{ +static int childExplorerKindDepth(const BRepGraph_NodeId::Kind theKind) +{ + static constexpr int THE_DEPTH[] = { + 2, // Kind::Solid=0 + 3, // Kind::Shell=1 + 4, // Kind::Face=2 + 5, // Kind::Wire=3 + 7, // Kind::Edge=4 + 8, // Kind::Vertex=5 + 0, // Kind::Compound=6 + 1, // Kind::CompSolid=7 + 6, // Kind::CoEdge=8 + 99, // gap=9 + 0, // Kind::Product=10 + 1, // Kind::Occurrence=11 + }; + return THE_DEPTH[static_cast(theKind)]; +} +} // namespace + +//================================================================================================= + +BRepGraph_ChildExplorer::BRepGraph_ChildExplorer(const BRepGraph& theGraph, + const BRepGraph_NodeId theRoot) + : BRepGraph_ChildExplorer(theGraph, + theRoot, + std::nullopt, + std::nullopt, + false, + true, + true, + TraversalMode::Recursive) +{ +} + +//================================================================================================= + +BRepGraph_ChildExplorer::BRepGraph_ChildExplorer(const BRepGraph& theGraph, + const BRepGraph_NodeId theRoot, + const TraversalMode theMode) + : BRepGraph_ChildExplorer(theGraph, + theRoot, + std::nullopt, + std::nullopt, + false, + true, + true, + theMode) +{ +} + +//================================================================================================= + +BRepGraph_ChildExplorer::BRepGraph_ChildExplorer( + const BRepGraph& theGraph, + const BRepGraph_NodeId theRoot, + const std::optional& theAvoidKind, + const bool theEmitAvoidKind, + const TraversalMode theMode) + : BRepGraph_ChildExplorer(theGraph, + theRoot, + std::nullopt, + theAvoidKind, + theEmitAvoidKind, + true, + true, + theMode) +{ +} + +//================================================================================================= + +BRepGraph_ChildExplorer::BRepGraph_ChildExplorer(const BRepGraph& theGraph, + const BRepGraph_NodeId theRoot, + BRepGraph_NodeId::Kind theTargetKind) + : BRepGraph_ChildExplorer(theGraph, + theRoot, + theTargetKind, + std::nullopt, + false, + true, + true, + TraversalMode::Recursive) +{ +} + +//================================================================================================= + +BRepGraph_ChildExplorer::BRepGraph_ChildExplorer(const BRepGraph& theGraph, + const BRepGraph_ProductId theProduct, + BRepGraph_NodeId::Kind theTargetKind) + : BRepGraph_ChildExplorer(theGraph, + BRepGraph_NodeId(theProduct), + theTargetKind, + std::nullopt, + false, + true, + true, + TraversalMode::Recursive) +{ +} + +//================================================================================================= + +BRepGraph_ChildExplorer::BRepGraph_ChildExplorer(const BRepGraph& theGraph, + const BRepGraph_NodeId theRoot, + BRepGraph_NodeId::Kind theTargetKind, + const TraversalMode theMode) + : BRepGraph_ChildExplorer(theGraph, + theRoot, + theTargetKind, + std::nullopt, + false, + true, + true, + theMode) +{ +} + +//================================================================================================= + +BRepGraph_ChildExplorer::BRepGraph_ChildExplorer( + const BRepGraph& theGraph, + const BRepGraph_NodeId theRoot, + BRepGraph_NodeId::Kind theTargetKind, + const std::optional& theAvoidKind, + const bool theEmitAvoidKind, + const TraversalMode theMode) + : BRepGraph_ChildExplorer(theGraph, + theRoot, + theTargetKind, + theAvoidKind, + theEmitAvoidKind, + true, + true, + theMode) +{ +} + +//================================================================================================= + +BRepGraph_ChildExplorer::BRepGraph_ChildExplorer(const BRepGraph& theGraph, + const BRepGraph_ProductId theProduct, + BRepGraph_NodeId::Kind theTargetKind, + const TraversalMode theMode) + : BRepGraph_ChildExplorer(theGraph, + BRepGraph_NodeId(theProduct), + theTargetKind, + std::nullopt, + false, + true, + true, + theMode) +{ +} + +//================================================================================================= + +BRepGraph_ChildExplorer::BRepGraph_ChildExplorer( + const BRepGraph& theGraph, + const BRepGraph_NodeId theRoot, + const std::optional& theTargetKind, + const std::optional& theAvoidKind, + const bool theEmitAvoidKind, + const bool theCumLoc, + const bool theCumOri, + const TraversalMode theMode) + : myGraph(&theGraph), + myRoot(theRoot), + myMode(theMode), + myTargetKind(theTargetKind), + myAvoidKind(normalizeAvoidKind(theAvoidKind, theTargetKind)), + myEmitAvoidKind(theEmitAvoidKind), + myCumLoc(theCumLoc), + myCumOri(theCumOri) +{ + startTraversal(TopLoc_Location(), TopAbs_FORWARD); +} + +//================================================================================================= + +BRepGraph_ChildExplorer::BRepGraph_ChildExplorer(const BRepGraph& theGraph, + const BRepGraph_NodeId theRoot, + BRepGraph_NodeId::Kind theTargetKind, + const bool theCumLoc, + const bool theCumOri, + const TraversalMode theMode) + : BRepGraph_ChildExplorer(theGraph, + theRoot, + theTargetKind, + std::nullopt, + false, + theCumLoc, + theCumOri, + theMode) +{ +} + +//================================================================================================= + +BRepGraph_ChildExplorer::BRepGraph_ChildExplorer(const BRepGraph& theGraph, + const BRepGraph_ProductId theProduct, + BRepGraph_NodeId::Kind theTargetKind, + const bool theCumLoc, + const bool theCumOri, + const TraversalMode theMode) + : BRepGraph_ChildExplorer(theGraph, + BRepGraph_NodeId(theProduct), + theTargetKind, + std::nullopt, + false, + theCumLoc, + theCumOri, + theMode) +{ +} + +//================================================================================================= + +BRepGraph_ChildExplorer::BRepGraph_ChildExplorer( + const BRepGraph& theGraph, + const BRepGraph_NodeId theRoot, + const std::optional& theTargetKind, + const std::optional& theAvoidKind, + const bool theEmitAvoidKind, + const TopLoc_Location& theStartLoc, + const TopAbs_Orientation theStartOri, + const TraversalMode theMode) + : myGraph(&theGraph), + myRoot(theRoot), + myMode(theMode), + myTargetKind(theTargetKind), + myAvoidKind(normalizeAvoidKind(theAvoidKind, theTargetKind)), + myEmitAvoidKind(theEmitAvoidKind) +{ + startTraversal(theStartLoc, theStartOri); +} + +//================================================================================================= + +BRepGraph_ChildExplorer::BRepGraph_ChildExplorer(const BRepGraph& theGraph, + const BRepGraph_NodeId theRoot, + BRepGraph_NodeId::Kind theTargetKind, + const TopLoc_Location& theStartLoc, + const TopAbs_Orientation theStartOri, + const TraversalMode theMode) + : BRepGraph_ChildExplorer(theGraph, + theRoot, + theTargetKind, + std::nullopt, + false, + theStartLoc, + theStartOri, + theMode) +{ +} + +//================================================================================================= + +void BRepGraph_ChildExplorer::startTraversal(const TopLoc_Location& theStartLoc, + const TopAbs_Orientation theStartOri) +{ + myStackTop = -1; + myHasMore = false; + myCurrent = BRepGraph_NodeId(); + myCurrentFrame = -1; + myLocation = theStartLoc; + myOrientation = theStartOri; + + if (!myRoot.IsValid()) + return; + + if (matchesAvoid(myRoot)) + { + const BRepGraphInc::BaseDef* aRootDef = myGraph->Topo().Gen().TopoEntity(myRoot); + if (myTargetKind.has_value() && myEmitAvoidKind && aRootDef != nullptr && !aRootDef->IsRemoved) + { + StackFrame aRootFrame; + aRootFrame.Node = myRoot; + aRootFrame.NextChildIdx = 0; + aRootFrame.StepFromParent = -1; + aRootFrame.AccLocation = theStartLoc; + aRootFrame.AccOrientation = theStartOri; + pushFrame(aRootFrame); + setCurrentFromFrame(myStackTop); + } + return; + } + + // Check if root is valid and not removed. + const BRepGraphInc::BaseDef* aBaseDef = myGraph->Topo().Gen().TopoEntity(myRoot); + if (aBaseDef == nullptr || aBaseDef->IsRemoved) + return; + + // Check if root itself matches the target kind (e.g., root=Edge, target=Edge). + if (myTargetKind.has_value() && matchesTarget(myRoot)) + { + StackFrame aRootFrame; + aRootFrame.Node = myRoot; + aRootFrame.NextChildIdx = 0; + aRootFrame.StepFromParent = -1; + aRootFrame.AccLocation = theStartLoc; + aRootFrame.AccOrientation = theStartOri; + pushFrame(aRootFrame); + setCurrentFromFrame(myStackTop); + return; + } + + // Push root frame and find first matching descendant. + StackFrame aRootFrame; + aRootFrame.Node = myRoot; + aRootFrame.NextChildIdx = 0; + aRootFrame.StepFromParent = -1; + aRootFrame.AccLocation = theStartLoc; + aRootFrame.AccOrientation = theStartOri; + pushFrame(aRootFrame); + advance(); +} + +//================================================================================================= + +void BRepGraph_ChildExplorer::Next() +{ + advance(); +} + +//================================================================================================= + +void BRepGraph_ChildExplorer::advance() +{ + using Kind = BRepGraph_NodeId::Kind; + const BRepGraph::TopoView& aDefs = myGraph->Topo(); + const BRepGraph::RefsView& aRefs = myGraph->Refs(); + + if (myCurrentFrame >= 0) + { + if (myCurrentFrame == myStackTop && !shouldDescendFromCurrent()) + { + popFrame(); + } + myCurrentFrame = -1; + } + + myHasMore = false; + + while (myStackTop >= 0) + { + StackFrame& aFrame = topFrame(); + const int aIdx = aFrame.NextChildIdx; + + // Per-kind child iteration. + // Each branch either yields a child (with step, loc, ori) and increments NextChildIdx, + // or signals exhaustion by setting aChildNode invalid and incrementing NextChildIdx past end. + BRepGraph_NodeId aChildNode; + TopLoc_Location aChildLoc = aFrame.AccLocation; + TopAbs_Orientation aChildOri = aFrame.AccOrientation; + int aStepIdx = aIdx; + + switch (aFrame.Node.NodeKind) + { + case Kind::Compound: { + const BRepGraphInc::CompoundDef& aComp = + aDefs.Compounds().Definition(BRepGraph_CompoundId(aFrame.Node.Index)); + // Skip removed refs. + int i = aIdx; + for (; i < aComp.ChildRefIds.Length(); ++i) + { + const BRepGraphInc::ChildRef& aRef = aRefs.Children().Entry(aComp.ChildRefIds.Value(i)); + if (!aRef.IsRemoved) + { + aChildNode = aRef.ChildDefId; + aStepIdx = i; + if (myCumLoc) + aChildLoc = aFrame.AccLocation * aRef.LocalLocation; + if (myCumOri) + aChildOri = TopAbs::Compose(aFrame.AccOrientation, aRef.Orientation); + break; + } + } + aFrame.NextChildIdx = i + 1; + break; + } + + case Kind::CompSolid: { + const BRepGraphInc::CompSolidDef& aCS = + aDefs.CompSolids().Definition(BRepGraph_CompSolidId(aFrame.Node.Index)); + int i = aIdx; + for (; i < aCS.SolidRefIds.Length(); ++i) + { + const BRepGraphInc::SolidRef& aRef = aRefs.Solids().Entry(aCS.SolidRefIds.Value(i)); + if (!aRef.IsRemoved) + { + aChildNode = aRef.SolidDefId; + aStepIdx = i; + if (myCumLoc) + aChildLoc = aFrame.AccLocation * aRef.LocalLocation; + if (myCumOri) + aChildOri = TopAbs::Compose(aFrame.AccOrientation, aRef.Orientation); + break; + } + } + aFrame.NextChildIdx = i + 1; + break; + } + + case Kind::Solid: { + const BRepGraphInc::SolidDef& aSolid = + aDefs.Solids().Definition(BRepGraph_SolidId(aFrame.Node.Index)); + const int aNbShells = aSolid.ShellRefIds.Length(); + const int aNbFree = aSolid.FreeChildRefIds.Length(); + int i = aIdx; + for (; i < aNbShells + aNbFree; ++i) + { + if (i < aNbShells) + { + const BRepGraphInc::ShellRef& aRef = aRefs.Shells().Entry(aSolid.ShellRefIds.Value(i)); + if (!aRef.IsRemoved) + { + aChildNode = aRef.ShellDefId; + aStepIdx = i; + if (myCumLoc) + aChildLoc = aFrame.AccLocation * aRef.LocalLocation; + if (myCumOri) + aChildOri = TopAbs::Compose(aFrame.AccOrientation, aRef.Orientation); + break; + } + } + else + { + const int aFreeIdx = i - aNbShells; + const BRepGraphInc::ChildRef& aRef = + aRefs.Children().Entry(aSolid.FreeChildRefIds.Value(aFreeIdx)); + if (!aRef.IsRemoved) + { + aChildNode = aRef.ChildDefId; + aStepIdx = i; + if (myCumLoc) + aChildLoc = aFrame.AccLocation * aRef.LocalLocation; + if (myCumOri) + aChildOri = TopAbs::Compose(aFrame.AccOrientation, aRef.Orientation); + break; + } + } + } + aFrame.NextChildIdx = i + 1; + break; + } + + case Kind::Shell: { + const BRepGraphInc::ShellDef& aShell = + aDefs.Shells().Definition(BRepGraph_ShellId(aFrame.Node.Index)); + const int aNbFaces = aShell.FaceRefIds.Length(); + const int aNbFree = aShell.FreeChildRefIds.Length(); + int i = aIdx; + for (; i < aNbFaces + aNbFree; ++i) + { + if (i < aNbFaces) + { + const BRepGraphInc::FaceRef& aRef = aRefs.Faces().Entry(aShell.FaceRefIds.Value(i)); + if (!aRef.IsRemoved) + { + aChildNode = aRef.FaceDefId; + aStepIdx = i; + if (myCumLoc) + aChildLoc = aFrame.AccLocation * aRef.LocalLocation; + if (myCumOri) + aChildOri = TopAbs::Compose(aFrame.AccOrientation, aRef.Orientation); + break; + } + } + else + { + const int aFreeIdx = i - aNbFaces; + const BRepGraphInc::ChildRef& aRef = + aRefs.Children().Entry(aShell.FreeChildRefIds.Value(aFreeIdx)); + if (!aRef.IsRemoved) + { + aChildNode = aRef.ChildDefId; + aStepIdx = i; + if (myCumLoc) + aChildLoc = aFrame.AccLocation * aRef.LocalLocation; + if (myCumOri) + aChildOri = TopAbs::Compose(aFrame.AccOrientation, aRef.Orientation); + break; + } + } + } + aFrame.NextChildIdx = i + 1; + break; + } + + case Kind::Face: { + const BRepGraphInc::FaceDef& aFace = + aDefs.Faces().Definition(BRepGraph_FaceId(aFrame.Node.Index)); + const int aNbWires = aFace.WireRefIds.Length(); + const int aNbVerts = aFace.VertexRefIds.Length(); + int i = aIdx; + for (; i < aNbWires + aNbVerts; ++i) + { + if (i < aNbWires) + { + const BRepGraphInc::WireRef& aRef = aRefs.Wires().Entry(aFace.WireRefIds.Value(i)); + if (!aRef.IsRemoved) + { + aChildNode = aRef.WireDefId; + aStepIdx = i; + if (myCumLoc) + aChildLoc = aFrame.AccLocation * aRef.LocalLocation; + if (myCumOri) + aChildOri = TopAbs::Compose(aFrame.AccOrientation, aRef.Orientation); + break; + } + } + else + { + const int aVIdx = i - aNbWires; + const BRepGraphInc::VertexRef& aVRef = + aRefs.Vertices().Entry(aFace.VertexRefIds.Value(aVIdx)); + if (!aVRef.IsRemoved) + { + aChildNode = aVRef.VertexDefId; + aStepIdx = i; + if (myCumLoc) + aChildLoc = aFrame.AccLocation * aVRef.LocalLocation; + if (myCumOri) + aChildOri = TopAbs::Compose(aFrame.AccOrientation, aVRef.Orientation); + break; + } + } + } + aFrame.NextChildIdx = i + 1; + break; + } + + case Kind::Wire: { + const BRepGraphInc::WireDef& aWire = + aDefs.Wires().Definition(BRepGraph_WireId(aFrame.Node.Index)); + int i = aIdx; + for (; i < aWire.CoEdgeRefIds.Length(); ++i) + { + const BRepGraphInc::CoEdgeRef& aRef = aRefs.CoEdges().Entry(aWire.CoEdgeRefIds.Value(i)); + if (!aRef.IsRemoved) + { + aChildNode = aRef.CoEdgeDefId; + aStepIdx = i; + if (myCumLoc) + aChildLoc = aFrame.AccLocation * aRef.LocalLocation; + // CoEdgeRef has no Orientation field. + break; + } + } + aFrame.NextChildIdx = i + 1; + break; + } + + case Kind::Edge: { + const BRepGraphInc::EdgeDef& anEdge = + aDefs.Edges().Definition(BRepGraph_EdgeId(aFrame.Node.Index)); + // Virtual concatenation: 0=Start, 1=End, 2+=Internal. + const int aNbIntern = anEdge.InternalVertexRefIds.Length(); + const int aNbTotal = 2 + aNbIntern; + int i = aIdx; + for (; i < aNbTotal; ++i) + { + BRepGraph_VertexRefId aVRefId; + if (i == 0) + aVRefId = anEdge.StartVertexRefId; + else if (i == 1) + aVRefId = anEdge.EndVertexRefId; + else + aVRefId = anEdge.InternalVertexRefIds.Value(i - 2); + + if (!aVRefId.IsValid()) + { + continue; + } + const BRepGraphInc::VertexRef& aVRef = aRefs.Vertices().Entry(aVRefId); + if (!aVRef.IsRemoved) + { + aChildNode = aVRef.VertexDefId; + aStepIdx = i; + if (myCumLoc) + aChildLoc = aFrame.AccLocation * aVRef.LocalLocation; + if (myCumOri) + aChildOri = TopAbs::Compose(aFrame.AccOrientation, aVRef.Orientation); + break; + } + } + aFrame.NextChildIdx = i + 1; + break; + } + + case Kind::CoEdge: { + // CoEdge owns exactly one Edge child. + if (aIdx == 0) + { + const BRepGraphInc::CoEdgeDef& aCoEdge = + aDefs.CoEdges().Definition(BRepGraph_CoEdgeId(aFrame.Node.Index)); + if (!aCoEdge.IsRemoved && aCoEdge.EdgeDefId.IsValid()) + { + aChildNode = aCoEdge.EdgeDefId; + aStepIdx = 0; + if (myCumOri) + aChildOri = TopAbs::Compose(aFrame.AccOrientation, aCoEdge.Sense); + } + } + aFrame.NextChildIdx = 1; + break; + } + + case Kind::Occurrence: { + // Occurrence references exactly one Product. + if (aIdx == 0) + { + const BRepGraphInc::OccurrenceDef& anOcc = + aDefs.Occurrences().Definition(BRepGraph_OccurrenceId(aFrame.Node.Index)); + if (!anOcc.IsRemoved && anOcc.ProductDefId.IsValid()) + { + aChildNode = anOcc.ProductDefId; + aStepIdx = 0; + if (myCumLoc) + aChildLoc = aFrame.AccLocation * anOcc.Placement; + } + } + aFrame.NextChildIdx = 1; + break; + } + + case Kind::Product: { + const BRepGraph_ProductId aProdId(aFrame.Node.Index); + const BRepGraphInc::ProductDef& aProd = aDefs.Products().Definition(aProdId); + if (aProd.ShapeRootId.IsValid()) + { + // Part product: single child = shape root topology node. + if (aIdx == 0) + { + aChildNode = aProd.ShapeRootId; + aStepIdx = 0; + if (myCumLoc) + aChildLoc = aFrame.AccLocation * aProd.RootLocation; + if (myCumOri) + aChildOri = TopAbs::Compose(aFrame.AccOrientation, aProd.RootOrientation); + } + aFrame.NextChildIdx = 1; + } + else + { + // Assembly product: children are occurrence instances. + const int aNbComps = myGraph->Topo().Products().NbComponents(aProdId); + if (aIdx < aNbComps) + { + const BRepGraph_OccurrenceId anOccId = + myGraph->Topo().Products().Component(aProdId, aIdx); + aChildNode = anOccId; + aStepIdx = aIdx; + } + aFrame.NextChildIdx = aIdx + 1; + } + break; + } + + default: + // Vertex and other leaf kinds - no children to iterate. + aFrame.NextChildIdx = 0; + break; + } + + // If no child was found, this frame is exhausted - backtrack. + if (!aChildNode.IsValid()) + { + popFrame(); + continue; + } + + if (matchesAvoid(aChildNode)) + { + const BRepGraphInc::BaseDef* anAvoidDef = aDefs.Gen().TopoEntity(aChildNode); + if (myEmitAvoidKind && anAvoidDef != nullptr && !anAvoidDef->IsRemoved) + { + StackFrame aChildFrame; + aChildFrame.Node = aChildNode; + aChildFrame.NextChildIdx = 0; + aChildFrame.StepFromParent = aStepIdx; + aChildFrame.AccLocation = aChildLoc; + aChildFrame.AccOrientation = aChildOri; + pushFrame(aChildFrame); + setCurrentFromFrame(myStackTop); + return; + } + continue; + } + + // Target check, or all-descendant emission when no target is configured. + if (shouldEmit(aChildNode)) + { + const BRepGraphInc::BaseDef* aPostDef = aDefs.Gen().TopoEntity(aChildNode); + if (aPostDef == nullptr || aPostDef->IsRemoved) + continue; + + StackFrame aChildFrame; + aChildFrame.Node = aChildNode; + aChildFrame.NextChildIdx = 0; + aChildFrame.StepFromParent = aStepIdx; + aChildFrame.AccLocation = aChildLoc; + aChildFrame.AccOrientation = aChildOri; + pushFrame(aChildFrame); + setCurrentFromFrame(myStackTop); + return; + } + + // Check if resolved child is valid and not removed before descending. + const BRepGraphInc::BaseDef* aBaseDef = aDefs.Gen().TopoEntity(aChildNode); + if (aBaseDef == nullptr || aBaseDef->IsRemoved) + continue; + + // Descend if this kind can contain the target. + if (myMode == TraversalMode::Recursive + && ((myTargetKind.has_value() && canContainTarget(aChildNode.NodeKind, *myTargetKind)) + || (!myTargetKind.has_value() && canHaveChildren(aChildNode.NodeKind)))) + { + StackFrame aChildFrame; + aChildFrame.Node = aChildNode; + aChildFrame.NextChildIdx = 0; + aChildFrame.StepFromParent = aStepIdx; + aChildFrame.AccLocation = aChildLoc; + aChildFrame.AccOrientation = aChildOri; + pushFrame(aChildFrame); + } + // Otherwise skip this child (loop continues to next sibling or backtrack). + } +} + +//================================================================================================= + +void BRepGraph_ChildExplorer::setCurrentFromFrame(const int theFrameIndex) +{ + myCurrentFrame = theFrameIndex; + myCurrent = myStack[theFrameIndex].Node; + myLocation = myStack[theFrameIndex].AccLocation; + myOrientation = myStack[theFrameIndex].AccOrientation; + myHasMore = true; +} + +//================================================================================================= + +bool BRepGraph_ChildExplorer::shouldDescendFromCurrent() const +{ + if (myMode != TraversalMode::Recursive || myCurrentFrame < 0) + { + return false; + } + + const BRepGraph_NodeId aNode = myStack[myCurrentFrame].Node; + return !myTargetKind.has_value() && !matchesAvoid(aNode) && canHaveChildren(aNode.NodeKind); +} + +//================================================================================================= + +std::optional BRepGraph_ChildExplorer::normalizeAvoidKind( + const std::optional& theAvoidKind, + const std::optional& theTargetKind) +{ + if (!theAvoidKind.has_value() || !theTargetKind.has_value()) + { + return theAvoidKind; + } + + return canContainTarget(*theAvoidKind, *theTargetKind) ? theAvoidKind : std::nullopt; +} + +//================================================================================================= + +bool BRepGraph_ChildExplorer::canContainTarget(const BRepGraph_NodeId::Kind theParentKind, + const BRepGraph_NodeId::Kind theTargetKind) +{ + const int aParentDepth = childExplorerKindDepth(theParentKind); + const int aTargetDepth = childExplorerKindDepth(theTargetKind); + return aParentDepth < aTargetDepth; +} + +//================================================================================================= + +bool BRepGraph_ChildExplorer::canHaveChildren(const BRepGraph_NodeId::Kind theNodeKind) +{ + return theNodeKind != BRepGraph_NodeId::Kind::Vertex; +} + +//================================================================================================= + +void BRepGraph_ChildExplorer::pushFrame(const StackFrame& theFrame) +{ + // Guard against pathological cycles (e.g., self-referencing compounds). + // A valid DFS path cannot exceed the total node count in the graph. + if (myStackTop >= myGraph->Topo().Gen().NbNodes()) + return; + + ++myStackTop; + if (static_cast(myStackTop) >= myStack.Size()) + { + const size_t aNewSize = + std::max(myStack.Size() * 2, static_cast(THE_INLINE_STACK_SIZE)); + myStack.Reallocate(aNewSize, true); + } + myStack[myStackTop] = theFrame; +} + +//================================================================================================= + +void BRepGraph_ChildExplorer::popFrame() +{ + if (myStackTop >= 0) + --myStackTop; +} + +//================================================================================================= + +TopLoc_Location BRepGraph_ChildExplorer::LocationOf(const BRepGraph_NodeId::Kind theKind) const +{ + // Scan step frames (skip root at index 0) to match "first step" contract. + const int aMaxFrame = myCurrentFrame >= 0 ? myCurrentFrame : myStackTop; + for (int i = 1; i <= aMaxFrame; ++i) + { + if (myStack[i].Node.NodeKind == theKind) + return myStack[i].AccLocation; + } + // Check the current match. + if (myHasMore && myCurrent.NodeKind == theKind) + return myLocation; + return TopLoc_Location(); +} + +//================================================================================================= + +BRepGraph_NodeId BRepGraph_ChildExplorer::NodeOf(const BRepGraph_NodeId::Kind theKind) const +{ + // Scan step frames (skip root at index 0) to match "first step" contract. + const int aMaxFrame = myCurrentFrame >= 0 ? myCurrentFrame : myStackTop; + for (int i = 1; i <= aMaxFrame; ++i) + { + if (myStack[i].Node.NodeKind == theKind) + return myStack[i].Node; + } + if (myHasMore && myCurrent.NodeKind == theKind) + return myCurrent; + return BRepGraph_NodeId(); +} + +//================================================================================================= + +TopLoc_Location BRepGraph_ChildExplorer::LocationAt(const int theLevel) const +{ + // Level 0 = after first step = frame[1]. + const int aFrameIdx = theLevel + 1; + if (myCurrentFrame >= 0 && aFrameIdx <= myCurrentFrame) + return myStack[aFrameIdx].AccLocation; + return TopLoc_Location(); +} + +//================================================================================================= + +BRepGraph_NodeId BRepGraph_ChildExplorer::NodeAt(const int theLevel) const +{ + const int aFrameIdx = theLevel + 1; + if (myCurrentFrame >= 0 && aFrameIdx <= myCurrentFrame) + return myStack[aFrameIdx].Node; + return BRepGraph_NodeId(); +} diff --git a/src/ModelingData/TKBRep/BRepGraph/BRepGraph_ChildExplorer.hxx b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_ChildExplorer.hxx new file mode 100644 index 0000000000..057eb2c6de --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_ChildExplorer.hxx @@ -0,0 +1,247 @@ +// Copyright (c) 2026 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 _BRepGraph_ChildExplorer_HeaderFile +#define _BRepGraph_ChildExplorer_HeaderFile + +#include +#include + +#include +#include + +#include +#include + +#include + +//! @brief Stack-based lazy downward hierarchy walker for BRepGraph with inline +//! location/orientation accumulation. +//! +//! Walks the graph hierarchy from a root node down to entities of a target kind, +//! yielding one occurrence at a time via a depth-first stack. Location and +//! orientation are composed incrementally during the walk, making +//! Current().Location and Current().Orientation O(1) per call. +//! +//! The traversal follows the actual graph structure transparently - every node +//! kind is visited as a distinct entity (no hidden collapses): +//! Compound -> children, CompSolid -> Solids, Solid -> Shells, +//! Shell -> Faces, Face -> Wires (+direct Vertices), Wire -> CoEdges, +//! CoEdge -> Edge, Edge -> Vertices, +//! Product(assembly) -> Occurrences, Product(part) -> ShapeRoot, +//! Occurrence -> Product. +//! +//! Unlike flat definition traversal by typed ids, BRepGraph_ChildExplorer visits +//! each occurrence. If Edge[5] is reachable through Face[0] and Face[1], +//! it is visited twice with different accumulated transforms. +//! +//! ## Traversal modes +//! - **Recursive**: depth-first walk through the full subgraph. +//! Without target kind, all descendant nodes are emitted. +//! With target kind, only matching nodes are emitted but intermediate +//! levels are traversed to reach them. +//! - **DirectChildren**: yields only the immediate children of the root. +//! No descent into grandchildren. With target kind, only children +//! matching the kind are returned. +class BRepGraph_ChildExplorer +{ +public: + DEFINE_STANDARD_ALLOC + + //! Downward traversal strategy. + enum class TraversalMode + { + Recursive, //!< Depth-first walk through the full subgraph. + DirectChildren, //!< Yields only the immediate children of the root. + }; + + Standard_EXPORT BRepGraph_ChildExplorer(const BRepGraph& theGraph, + const BRepGraph_NodeId theRoot); + + Standard_EXPORT BRepGraph_ChildExplorer(const BRepGraph& theGraph, + const BRepGraph_NodeId theRoot, + TraversalMode theMode); + + Standard_EXPORT BRepGraph_ChildExplorer(const BRepGraph& theGraph, + const BRepGraph_NodeId theRoot, + const std::optional& theAvoidKind, + bool theEmitAvoidKind, + TraversalMode theMode = TraversalMode::Recursive); + + Standard_EXPORT BRepGraph_ChildExplorer(const BRepGraph& theGraph, + const BRepGraph_NodeId theRoot, + BRepGraph_NodeId::Kind theTargetKind); + + Standard_EXPORT BRepGraph_ChildExplorer(const BRepGraph& theGraph, + const BRepGraph_NodeId theRoot, + BRepGraph_NodeId::Kind theTargetKind, + TraversalMode theMode); + + Standard_EXPORT BRepGraph_ChildExplorer(const BRepGraph& theGraph, + const BRepGraph_NodeId theRoot, + BRepGraph_NodeId::Kind theTargetKind, + const std::optional& theAvoidKind, + bool theEmitAvoidKind, + TraversalMode theMode = TraversalMode::Recursive); + + Standard_EXPORT BRepGraph_ChildExplorer(const BRepGraph& theGraph, + const BRepGraph_ProductId theProduct, + BRepGraph_NodeId::Kind theTargetKind); + + Standard_EXPORT BRepGraph_ChildExplorer(const BRepGraph& theGraph, + const BRepGraph_ProductId theProduct, + BRepGraph_NodeId::Kind theTargetKind, + TraversalMode theMode); + + Standard_EXPORT BRepGraph_ChildExplorer(const BRepGraph& theGraph, + const BRepGraph_NodeId theRoot, + BRepGraph_NodeId::Kind theTargetKind, + bool theCumLoc, + bool theCumOri, + TraversalMode theMode = TraversalMode::Recursive); + + Standard_EXPORT BRepGraph_ChildExplorer(const BRepGraph& theGraph, + const BRepGraph_ProductId theProduct, + BRepGraph_NodeId::Kind theTargetKind, + bool theCumLoc, + bool theCumOri, + TraversalMode theMode = TraversalMode::Recursive); + + Standard_EXPORT BRepGraph_ChildExplorer(const BRepGraph& theGraph, + const BRepGraph_NodeId theRoot, + BRepGraph_NodeId::Kind theTargetKind, + const TopLoc_Location& theStartLoc, + TopAbs_Orientation theStartOri, + TraversalMode theMode = TraversalMode::DirectChildren); + + [[nodiscard]] bool More() const { return myHasMore; } + + Standard_EXPORT void Next(); + + [[nodiscard]] BRepGraphInc::NodeUsage Current() const + { + return {myCurrent, myLocation, myOrientation}; + } + + [[nodiscard]] Standard_EXPORT TopLoc_Location + LocationOf(const BRepGraph_NodeId::Kind theKind) const; + + [[nodiscard]] Standard_EXPORT BRepGraph_NodeId NodeOf(const BRepGraph_NodeId::Kind theKind) const; + + [[nodiscard]] Standard_EXPORT TopLoc_Location LocationAt(const int theLevel) const; + + [[nodiscard]] Standard_EXPORT BRepGraph_NodeId NodeAt(const int theLevel) const; + + //! Returns an STL-compatible iterator for range-based for loops. + NCollection_ForwardRangeIterator begin() + { + return NCollection_ForwardRangeIterator(this); + } + + //! Returns a sentinel marking the end of iteration. + NCollection_ForwardRangeSentinel end() const { return NCollection_ForwardRangeSentinel{}; } + +private: + Standard_EXPORT BRepGraph_ChildExplorer( + const BRepGraph& theGraph, + const BRepGraph_NodeId theRoot, + const std::optional& theTargetKind, + const std::optional& theAvoidKind, + bool theEmitAvoidKind, + bool theCumLoc, + bool theCumOri, + TraversalMode theMode); + + Standard_EXPORT BRepGraph_ChildExplorer( + const BRepGraph& theGraph, + const BRepGraph_NodeId theRoot, + const std::optional& theTargetKind, + const std::optional& theAvoidKind, + bool theEmitAvoidKind, + const TopLoc_Location& theStartLoc, + TopAbs_Orientation theStartOri, + TraversalMode theMode); + + struct StackFrame + { + BRepGraph_NodeId Node; + int NextChildIdx = 0; + int StepFromParent = -1; + TopLoc_Location AccLocation; + TopAbs_Orientation AccOrientation = TopAbs_FORWARD; + }; + + void advance(); + + void startTraversal(const TopLoc_Location& theStartLoc, TopAbs_Orientation theStartOri); + + static std::optional normalizeAvoidKind( + const std::optional& theAvoidKind, + const std::optional& theTargetKind); + + static bool canContainTarget(BRepGraph_NodeId::Kind theParentKind, + BRepGraph_NodeId::Kind theTargetKind); + + static bool canHaveChildren(BRepGraph_NodeId::Kind theNodeKind); + + void setCurrentFromFrame(const int theFrameIndex); + + [[nodiscard]] bool shouldDescendFromCurrent() const; + + [[nodiscard]] bool matchesTarget(const BRepGraph_NodeId theNode) const + { + return myTargetKind.has_value() && theNode.NodeKind == *myTargetKind; + } + + [[nodiscard]] bool matchesAvoid(const BRepGraph_NodeId theNode) const + { + return myAvoidKind.has_value() && theNode.NodeKind == *myAvoidKind; + } + + [[nodiscard]] bool shouldEmit(const BRepGraph_NodeId theNode) const + { + const bool isAvoid = matchesAvoid(theNode); + const bool isFind = !myTargetKind.has_value() || theNode.NodeKind == *myTargetKind; + return myEmitAvoidKind ? (isFind || isAvoid) : (isFind && !isAvoid); + } + + void pushFrame(const StackFrame& theFrame); + + void popFrame(); + + StackFrame& topFrame() { return myStack[myStackTop]; } + + const StackFrame& topFrame() const { return myStack[myStackTop]; } + + static constexpr int THE_INLINE_STACK_SIZE = 16; + + const BRepGraph* myGraph = nullptr; + BRepGraph_NodeId myRoot; + const TraversalMode myMode; + const std::optional myTargetKind; + const std::optional myAvoidKind; + const bool myEmitAvoidKind; + bool myCumLoc = true; + bool myCumOri = true; + + NCollection_LocalArray myStack; + int myStackTop = -1; + int myCurrentFrame = -1; + + BRepGraph_NodeId myCurrent; + TopLoc_Location myLocation; + TopAbs_Orientation myOrientation = TopAbs_FORWARD; + bool myHasMore = false; +}; + +#endif // _BRepGraph_ChildExplorer_HeaderFile diff --git a/src/ModelingData/TKBRep/BRepGraph/BRepGraph_Compact.cxx b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_Compact.cxx new file mode 100644 index 0000000000..9ac66e5348 --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_Compact.cxx @@ -0,0 +1,627 @@ +// Copyright (c) 2026 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 + +namespace +{ + +//! Remap a NodeId through old->new index maps. Returns invalid NodeId if not found. +BRepGraph_NodeId remapNodeId(const BRepGraph_NodeId& theId, + const NCollection_DataMap& theVertexMap, + const NCollection_DataMap& theEdgeMap, + const NCollection_DataMap& theWireMap, + const NCollection_DataMap& theFaceMap, + const NCollection_DataMap& theShellMap, + const NCollection_DataMap& theSolidMap, + const NCollection_DataMap& theCompoundMap, + const NCollection_DataMap& theCompSolidMap) +{ + if (!theId.IsValid()) + return BRepGraph_NodeId(); + + const NCollection_DataMap* aMap = nullptr; + switch (theId.NodeKind) + { + case BRepGraph_NodeId::Kind::Vertex: + aMap = &theVertexMap; + break; + case BRepGraph_NodeId::Kind::Edge: + aMap = &theEdgeMap; + break; + case BRepGraph_NodeId::Kind::Wire: + aMap = &theWireMap; + break; + case BRepGraph_NodeId::Kind::Face: + aMap = &theFaceMap; + break; + case BRepGraph_NodeId::Kind::Shell: + aMap = &theShellMap; + break; + case BRepGraph_NodeId::Kind::Solid: + aMap = &theSolidMap; + break; + case BRepGraph_NodeId::Kind::Compound: + aMap = &theCompoundMap; + break; + case BRepGraph_NodeId::Kind::CompSolid: + aMap = &theCompSolidMap; + break; + default: + // Product/Occurrence kinds are not compacted - they reference topology + // nodes which are remapped independently. + break; + } + + if (aMap == nullptr) + return BRepGraph_NodeId(); + + const int* aNewIdx = aMap->Seek(theId.Index); + if (aNewIdx == nullptr) + return BRepGraph_NodeId(); + + return BRepGraph_NodeId(theId.NodeKind, *aNewIdx); +} + +template +int countActiveDefs(const BRepGraph& theGraph) +{ + int aCount = 0; + for (BRepGraph_Iterator anIt(theGraph); anIt.More(); anIt.Next()) + { + ++aCount; + } + return aCount; +} + +template +void bindActiveIndexMap(const BRepGraph& theGraph, NCollection_DataMap& theMap) +{ + int aNewIdx = 0; + for (BRepGraph_Iterator anIt(theGraph); anIt.More(); anIt.Next()) + { + theMap.Bind(anIt.CurrentId().Index, aNewIdx++); + } +} + +} // namespace + +//================================================================================================= + +BRepGraph_Compact::Result BRepGraph_Compact::Perform(BRepGraph& theGraph) +{ + return Perform(theGraph, Options()); +} + +//================================================================================================= + +BRepGraph_Compact::Result BRepGraph_Compact::Perform(BRepGraph& theGraph, const Options& theOptions) +{ + Result aResult; + if (!theGraph.IsDone()) + { + return aResult; + } + + // Count removed nodes per kind. + aResult.NbRemovedVertices = + theGraph.Topo().Vertices().Nb() - countActiveDefs(theGraph); + aResult.NbRemovedEdges = + theGraph.Topo().Edges().Nb() - countActiveDefs(theGraph); + aResult.NbRemovedWires = + theGraph.Topo().Wires().Nb() - countActiveDefs(theGraph); + aResult.NbRemovedFaces = + theGraph.Topo().Faces().Nb() - countActiveDefs(theGraph); + aResult.NbRemovedShells = + theGraph.Topo().Shells().Nb() - countActiveDefs(theGraph); + aResult.NbRemovedSolids = + theGraph.Topo().Solids().Nb() - countActiveDefs(theGraph); + aResult.NbRemovedCompounds = + theGraph.Topo().Compounds().Nb() - countActiveDefs(theGraph); + aResult.NbRemovedCompSolids = + theGraph.Topo().CompSolids().Nb() - countActiveDefs(theGraph); + + const int aTotalRemoved = aResult.NbRemovedVertices + aResult.NbRemovedEdges + + aResult.NbRemovedWires + aResult.NbRemovedFaces + + aResult.NbRemovedShells + aResult.NbRemovedSolids + + aResult.NbRemovedCompounds + aResult.NbRemovedCompSolids; + + aResult.NbNodesBefore = static_cast(theGraph.Topo().Gen().NbNodes()); + + // Short-circuit: nothing to compact. + if (aTotalRemoved == 0) + { + aResult.NbNodesAfter = aResult.NbNodesBefore; + return aResult; + } + + // Build old->new index maps for each node kind. + NCollection_DataMap aVertexMap, anEdgeMap, aCoEdgeMap, aWireMap, aFaceMap; + NCollection_DataMap aShellMap, aSolidMap, aCompoundMap, aCompSolidMap; + + bindActiveIndexMap(theGraph, aVertexMap); + bindActiveIndexMap(theGraph, anEdgeMap); + bindActiveIndexMap(theGraph, aWireMap); + bindActiveIndexMap(theGraph, aFaceMap); + bindActiveIndexMap(theGraph, aShellMap); + bindActiveIndexMap(theGraph, aSolidMap); + bindActiveIndexMap(theGraph, aCompoundMap); + bindActiveIndexMap(theGraph, aCompSolidMap); + + const bool wasHistoryEnabled = theGraph.History().IsEnabled(); + theGraph.History().SetEnabled(theOptions.HistoryMode); + const BRepGraph_Data* aGraphData = theGraph.data(); + const uint32_t aGeneration = aGraphData->myGeneration.load(std::memory_order_relaxed); + + // Record history before swap (remap entries for topology defs). + if (theOptions.HistoryMode) + { + for (const auto& [aOldIdx, aNewIdx] : aVertexMap.Items()) + { + if (aOldIdx != aNewIdx) + { + NCollection_Vector aRepl; + aRepl.Append(BRepGraph_VertexId(aNewIdx)); + theGraph.History().Record(TCollection_AsciiString("Compact:Remap"), + BRepGraph_VertexId(aOldIdx), + aRepl); + } + } + for (const auto& [aOldIdx, aNewIdx] : anEdgeMap.Items()) + { + if (aOldIdx != aNewIdx) + { + NCollection_Vector aRepl; + aRepl.Append(BRepGraph_EdgeId(aNewIdx)); + theGraph.History().Record(TCollection_AsciiString("Compact:Remap"), + BRepGraph_EdgeId(aOldIdx), + aRepl); + } + } + for (const auto& [aOldIdx, aNewIdx] : aFaceMap.Items()) + { + if (aOldIdx != aNewIdx) + { + NCollection_Vector aRepl; + aRepl.Append(BRepGraph_FaceId(aNewIdx)); + theGraph.History().Record(TCollection_AsciiString("Compact:Remap"), + BRepGraph_FaceId(aOldIdx), + aRepl); + } + } + for (const auto& [aOldIdx, aNewIdx] : aWireMap.Items()) + { + if (aOldIdx != aNewIdx) + { + NCollection_Vector aRepl; + aRepl.Append(BRepGraph_WireId(aNewIdx)); + theGraph.History().Record(TCollection_AsciiString("Compact:Remap"), + BRepGraph_WireId(aOldIdx), + aRepl); + } + } + for (const auto& [aOldIdx, aNewIdx] : aShellMap.Items()) + { + if (aOldIdx != aNewIdx) + { + NCollection_Vector aRepl; + aRepl.Append(BRepGraph_ShellId(aNewIdx)); + theGraph.History().Record(TCollection_AsciiString("Compact:Remap"), + BRepGraph_ShellId(aOldIdx), + aRepl); + } + } + for (const auto& [aOldIdx, aNewIdx] : aSolidMap.Items()) + { + if (aOldIdx != aNewIdx) + { + NCollection_Vector aRepl; + aRepl.Append(BRepGraph_SolidId(aNewIdx)); + theGraph.History().Record(TCollection_AsciiString("Compact:Remap"), + BRepGraph_SolidId(aOldIdx), + aRepl); + } + } + for (const auto& [aOldIdx, aNewIdx] : aCompoundMap.Items()) + { + if (aOldIdx != aNewIdx) + { + NCollection_Vector aRepl; + aRepl.Append(BRepGraph_CompoundId(aNewIdx)); + theGraph.History().Record(TCollection_AsciiString("Compact:Remap"), + BRepGraph_CompoundId(aOldIdx), + aRepl); + } + } + for (const auto& [aOldIdx, aNewIdx] : aCompSolidMap.Items()) + { + if (aOldIdx != aNewIdx) + { + NCollection_Vector aRepl; + aRepl.Append(BRepGraph_CompSolidId(aNewIdx)); + theGraph.History().Record(TCollection_AsciiString("Compact:Remap"), + BRepGraph_CompSolidId(aOldIdx), + aRepl); + } + } + } + + // Construct a fresh graph and rebuild bottom-up. + // Geometry nodes (Surface, Curve) are automatically created through AddFace/AddEdge. + BRepGraph aNewGraph; + BRepGraph_Data* aNewGraphData = aNewGraph.data(); + aNewGraphData->myGeneration.store(aGeneration, std::memory_order_relaxed); + + // Helper lambda for remapping NodeIds. + auto remapId = [&](const BRepGraph_NodeId& theId) -> BRepGraph_NodeId { + return remapNodeId(theId, + aVertexMap, + anEdgeMap, + aWireMap, + aFaceMap, + aShellMap, + aSolidMap, + aCompoundMap, + aCompSolidMap); + }; + + // Add topology defs bottom-up (Vertex -> Edge -> Wire -> Face -> Shell -> Solid). + // Vertices. + for (BRepGraph_Iterator anIt(theGraph); anIt.More(); anIt.Next()) + { + const BRepGraphInc::VertexDef& anOldVtx = anIt.Current(); + (void)aNewGraph.Builder().AddVertex(anOldVtx.Point, anOldVtx.Tolerance); + } + + // Edges. + for (BRepGraph_Iterator anIt(theGraph); anIt.More(); anIt.Next()) + { + const BRepGraph_EdgeId anOldEdgeId = anIt.CurrentId(); + const BRepGraphInc::EdgeDef& anOldEdge = anIt.Current(); + + const BRepGraph_VertexId aNewStart = + anOldEdge.StartVertexRefId.IsValid() + ? BRepGraph_VertexId::FromNodeId( + remapId(BRepGraph_Tool::Edge::StartVertex(theGraph, anOldEdgeId).VertexDefId)) + : BRepGraph_VertexId(); + const BRepGraph_VertexId aNewEnd = + anOldEdge.EndVertexRefId.IsValid() + ? BRepGraph_VertexId::FromNodeId( + remapId(BRepGraph_Tool::Edge::EndVertex(theGraph, anOldEdgeId).VertexDefId)) + : BRepGraph_VertexId(); + + // Get the curve handle if available. + const occ::handle& aCurve = BRepGraph_Tool::Edge::Curve(theGraph, anOldEdgeId); + + const BRepGraph_EdgeId aNewEdgeId = aNewGraph.Builder().AddEdge(aNewStart, + aNewEnd, + aCurve, + anOldEdge.ParamFirst, + anOldEdge.ParamLast, + anOldEdge.Tolerance); + + // Copy edge properties. + BRepGraph_MutGuard aNewEdge = aNewGraph.Builder().MutEdge(aNewEdgeId); + aNewEdge->IsDegenerate = anOldEdge.IsDegenerate; + aNewEdge->SameParameter = anOldEdge.SameParameter; + aNewEdge->SameRange = anOldEdge.SameRange; + } + + // PCurves (added after edges and faces are known). + // We need faces first, so do PCurves after face creation. + + // Wires. + for (BRepGraph_Iterator anIt(theGraph); anIt.More(); anIt.Next()) + { + const BRepGraph_WireId anOldWireId = anIt.CurrentId(); + + NCollection_Vector> aNewEntries; + NCollection_Vector anOldCoEdges; + for (BRepGraph_RefsCoEdgeOfWire aRefIt(theGraph, anOldWireId); aRefIt.More(); aRefIt.Next()) + { + const BRepGraphInc::CoEdgeRef& aCR = theGraph.Refs().CoEdges().Entry(aRefIt.CurrentId()); + const BRepGraphInc::CoEdgeDef& aCoEdge = + theGraph.Topo().CoEdges().Definition(aCR.CoEdgeDefId); + const BRepGraph_EdgeId aNewEdgeDefId = + BRepGraph_EdgeId::FromNodeId(remapId(aCoEdge.EdgeDefId)); + if (aNewEdgeDefId.IsValid()) + { + aNewEntries.Append(std::make_pair(aNewEdgeDefId, aCoEdge.Sense)); + anOldCoEdges.Append(aCR.CoEdgeDefId); + } + } + const int aNewFirstCoEdge = aNewGraph.Topo().CoEdges().Nb(); + (void)aNewGraph.Builder().AddWire(aNewEntries); + for (int aCoEdgeIdx = 0; aCoEdgeIdx < anOldCoEdges.Length(); ++aCoEdgeIdx) + { + aCoEdgeMap.Bind(anOldCoEdges.Value(aCoEdgeIdx).Index, aNewFirstCoEdge + aCoEdgeIdx); + } + } + + // Faces. + for (BRepGraph_Iterator anIt(theGraph); anIt.More(); anIt.Next()) + { + const BRepGraph_FaceId anOldFaceId = anIt.CurrentId(); + const BRepGraphInc::FaceDef& anOldFace = anIt.Current(); + + const occ::handle& aSurf = BRepGraph_Tool::Face::Surface(theGraph, anOldFaceId); + + // Find outer wire from transitional incidence entries. + BRepGraph_WireId aNewOuterWire; + NCollection_Vector aNewInnerWires; + + for (BRepGraph_RefsWireOfFace aRefIt(theGraph, anOldFaceId); aRefIt.More(); aRefIt.Next()) + { + const BRepGraphInc::WireRef& aWR = theGraph.Refs().Wires().Entry(aRefIt.CurrentId()); + const BRepGraph_WireId aRemapped = BRepGraph_WireId::FromNodeId(remapId(aWR.WireDefId)); + if (!aRemapped.IsValid()) + continue; + if (aWR.IsOuter) + { + aNewOuterWire = aRemapped; + } + else + { + aNewInnerWires.Append(aRemapped); + } + } + + (void)aNewGraph.Builder().AddFace(aSurf, aNewOuterWire, aNewInnerWires, anOldFace.Tolerance); + + // Copy triangulations from old FaceDef to new FaceDef. + BRepGraph_MutGuard aNewFace = + aNewGraph.Builder().MutFace(BRepGraph_FaceId(aFaceMap.Find(anOldFaceId.Index))); + aNewFace->TriangulationRepIds = anOldFace.TriangulationRepIds; + aNewFace->ActiveTriangulationIndex = anOldFace.ActiveTriangulationIndex; + } + + // Add PCurves to edges in the new graph via CoEdge data. + for (BRepGraph_Iterator anIt(theGraph); anIt.More(); anIt.Next()) + { + const BRepGraph_EdgeId anOldEdgeId = anIt.CurrentId(); + const int aNewEdgeIdx = anEdgeMap.Find(anOldEdgeId.Index); + + const NCollection_Vector& aCoEdgeIds = + theGraph.Topo().Edges().CoEdges(anOldEdgeId); + for (const BRepGraph_CoEdgeId& aCoEdgeId : aCoEdgeIds) + { + const BRepGraphInc::CoEdgeDef& aCoEdge = theGraph.Topo().CoEdges().Definition(aCoEdgeId); + if (!aCoEdge.Curve2DRepId.IsValid()) + continue; + + const BRepGraph_FaceId aNewFaceId = BRepGraph_FaceId::FromNodeId(remapId(aCoEdge.FaceDefId)); + if (!aNewFaceId.IsValid()) + continue; + + const occ::handle& aCompactPCurve = + BRepGraph_Tool::CoEdge::PCurve(theGraph, aCoEdgeId); + aNewGraph.Builder().AddPCurveToEdge(BRepGraph_EdgeId(aNewEdgeIdx), + aNewFaceId, + aCompactPCurve, + aCoEdge.ParamFirst, + aCoEdge.ParamLast, + aCoEdge.Sense); + } + } + + // Shells. + for (BRepGraph_Iterator anIt(theGraph); anIt.More(); anIt.Next()) + { + const BRepGraph_ShellId anOldShellId = anIt.CurrentId(); + + const BRepGraph_ShellId aNewShellId = aNewGraph.Builder().AddShell(); + + // Add faces to shell via transitional incidence entries. + for (BRepGraph_RefsFaceOfShell aRefIt(theGraph, anOldShellId); aRefIt.More(); aRefIt.Next()) + { + const BRepGraphInc::FaceRef& aFR = theGraph.Refs().Faces().Entry(aRefIt.CurrentId()); + const BRepGraph_FaceId aNewFace = BRepGraph_FaceId::FromNodeId(remapId(aFR.FaceDefId)); + if (aNewFace.IsValid()) + aNewGraph.Builder().AddFaceToShell(aNewShellId, aNewFace, aFR.Orientation); + } + } + + // Solids. + for (BRepGraph_Iterator anIt(theGraph); anIt.More(); anIt.Next()) + { + const BRepGraph_SolidId anOldSolidId = anIt.CurrentId(); + + const BRepGraph_SolidId aNewSolidId = aNewGraph.Builder().AddSolid(); + + for (BRepGraph_RefsShellOfSolid aRefIt(theGraph, anOldSolidId); aRefIt.More(); aRefIt.Next()) + { + const BRepGraphInc::ShellRef& aSR = theGraph.Refs().Shells().Entry(aRefIt.CurrentId()); + const BRepGraph_ShellId aNewShell = BRepGraph_ShellId::FromNodeId(remapId(aSR.ShellDefId)); + if (aNewShell.IsValid()) + aNewGraph.Builder().AddShellToSolid(aNewSolidId, aNewShell, aSR.Orientation); + } + } + + // Compounds. + for (BRepGraph_Iterator anIt(theGraph); anIt.More(); anIt.Next()) + { + const BRepGraph_CompoundId anOldCompoundId = anIt.CurrentId(); + + NCollection_Vector aNewChildren; + for (BRepGraph_RefsChildOfCompound aRefIt(theGraph, anOldCompoundId); aRefIt.More(); + aRefIt.Next()) + { + const BRepGraphInc::ChildRef& aCR = theGraph.Refs().Children().Entry(aRefIt.CurrentId()); + const BRepGraph_NodeId aNewChild = remapId(aCR.ChildDefId); + if (aNewChild.IsValid()) + aNewChildren.Append(aNewChild); + } + (void)aNewGraph.Builder().AddCompound(aNewChildren); + } + + // CompSolids. + for (BRepGraph_Iterator anIt(theGraph); anIt.More(); anIt.Next()) + { + const BRepGraph_CompSolidId anOldCompSolidId = anIt.CurrentId(); + + NCollection_Vector aNewSolids; + for (BRepGraph_RefsSolidOfCompSolid aRefIt(theGraph, anOldCompSolidId); aRefIt.More(); + aRefIt.Next()) + { + const BRepGraphInc::SolidRef& aSR = theGraph.Refs().Solids().Entry(aRefIt.CurrentId()); + const BRepGraph_SolidId aNewSolid = BRepGraph_SolidId::FromNodeId(remapId(aSR.SolidDefId)); + if (aNewSolid.IsValid()) + aNewSolids.Append(aNewSolid); + } + (void)aNewGraph.Builder().AddCompSolid(aNewSolids); + } + + // Rebuild reverse index from final forward incidence to guarantee compact output consistency. + aNewGraph.incStorage().BuildReverseIndex(); + + aResult.NbNodesAfter = static_cast(aNewGraph.Topo().Gen().NbNodes()); + + // Transfer per-kind UID vectors from old graph to new graph using index remap maps. + // Each new graph's UID vector[newIdx] = old graph's UID vector[oldIdx]. + auto transferUIDs = [&](const NCollection_DataMap& theMap, + const NCollection_Vector& theOldVec, + NCollection_Vector& theNewVec) { + // New vector was already populated by BuilderView during reconstruction. + // Overwrite entries that have a mapping from the old graph. + for (const auto& [anOldIdx, aNewIdx] : theMap.Items()) + { + if (anOldIdx < theOldVec.Length() && theOldVec.Value(anOldIdx).IsValid()) + { + theNewVec.ChangeValue(aNewIdx) = theOldVec.Value(anOldIdx); + } + } + }; + + transferUIDs(aVertexMap, + aGraphData->myIncStorage.UIDs(BRepGraph_NodeId::Kind::Vertex), + aNewGraphData->myIncStorage.ChangeUIDs(BRepGraph_NodeId::Kind::Vertex)); + transferUIDs(anEdgeMap, + aGraphData->myIncStorage.UIDs(BRepGraph_NodeId::Kind::Edge), + aNewGraphData->myIncStorage.ChangeUIDs(BRepGraph_NodeId::Kind::Edge)); + transferUIDs(aWireMap, + aGraphData->myIncStorage.UIDs(BRepGraph_NodeId::Kind::Wire), + aNewGraphData->myIncStorage.ChangeUIDs(BRepGraph_NodeId::Kind::Wire)); + transferUIDs(aFaceMap, + aGraphData->myIncStorage.UIDs(BRepGraph_NodeId::Kind::Face), + aNewGraphData->myIncStorage.ChangeUIDs(BRepGraph_NodeId::Kind::Face)); + transferUIDs(aShellMap, + aGraphData->myIncStorage.UIDs(BRepGraph_NodeId::Kind::Shell), + aNewGraphData->myIncStorage.ChangeUIDs(BRepGraph_NodeId::Kind::Shell)); + transferUIDs(aSolidMap, + aGraphData->myIncStorage.UIDs(BRepGraph_NodeId::Kind::Solid), + aNewGraphData->myIncStorage.ChangeUIDs(BRepGraph_NodeId::Kind::Solid)); + transferUIDs(aCompoundMap, + aGraphData->myIncStorage.UIDs(BRepGraph_NodeId::Kind::Compound), + aNewGraphData->myIncStorage.ChangeUIDs(BRepGraph_NodeId::Kind::Compound)); + transferUIDs(aCompSolidMap, + aGraphData->myIncStorage.UIDs(BRepGraph_NodeId::Kind::CompSolid), + aNewGraphData->myIncStorage.ChangeUIDs(BRepGraph_NodeId::Kind::CompSolid)); + + // Mark UID reverse index dirty so it is rebuilt from the transferred UID vectors + // on next NodeIdFrom()/Has() call. The reverse index was populated during + // AddVertex/AddEdge/etc. with intermediate UIDs that are now overwritten. + { + std::unique_lock aUIDLock(aNewGraphData->myUIDToNodeIdMutex); + aNewGraphData->myUIDToNodeIdDirty = true; + } + { + std::unique_lock aRefUIDLock(aNewGraphData->myRefUIDToRefIdMutex); + aNewGraphData->myRefUIDToRefIdDirty = true; + } + + aNewGraphData->myNextUIDCounter.store( + aGraphData->myNextUIDCounter.load(std::memory_order_relaxed), + std::memory_order_relaxed); + aNewGraphData->myGeneration.store(aGraphData->myGeneration.load(std::memory_order_relaxed), + std::memory_order_relaxed); + aNewGraphData->myIsDone = true; + + // Save layers before swap (default move would transfer empty layers from aNewGraph). + BRepGraph_LayerRegistry aSavedLayerRegistry = std::move(theGraph.layerRegistry()); + + // Swap. + theGraph = std::move(aNewGraph); + + // Restore layers and notify about index remapping. + theGraph.layerRegistry() = std::move(aSavedLayerRegistry); + // Direct transientCache() access is intentional here. + // Compact must clear stale slots keyed by old indices, then re-reserve the + // cache for the new compacted entity counts before later algorithm use. + theGraph.transientCache().Clear(); + { + BRepGraphInc_Storage& aStr = theGraph.incStorage(); + int aCounts[BRepGraph_TransientCache::THE_KIND_COUNT] = {}; + aCounts[static_cast(BRepGraph_NodeId::Kind::Vertex)] = aStr.NbVertices(); + aCounts[static_cast(BRepGraph_NodeId::Kind::Edge)] = aStr.NbEdges(); + aCounts[static_cast(BRepGraph_NodeId::Kind::CoEdge)] = aStr.NbCoEdges(); + aCounts[static_cast(BRepGraph_NodeId::Kind::Wire)] = aStr.NbWires(); + aCounts[static_cast(BRepGraph_NodeId::Kind::Face)] = aStr.NbFaces(); + aCounts[static_cast(BRepGraph_NodeId::Kind::Shell)] = aStr.NbShells(); + aCounts[static_cast(BRepGraph_NodeId::Kind::Solid)] = aStr.NbSolids(); + aCounts[static_cast(BRepGraph_NodeId::Kind::Compound)] = aStr.NbCompounds(); + aCounts[static_cast(BRepGraph_NodeId::Kind::CompSolid)] = aStr.NbCompSolids(); + aCounts[static_cast(BRepGraph_NodeId::Kind::Product)] = aStr.NbProducts(); + aCounts[static_cast(BRepGraph_NodeId::Kind::Occurrence)] = aStr.NbOccurrences(); + int aReservedKindCount = BRepGraph_TransientCache::THE_DEFAULT_RESERVED_KIND_COUNT; + const int aRegisteredKindCount = BRepGraph_CacheKindRegistry::NbRegistered(); + if (aRegisteredKindCount > aReservedKindCount) + { + aReservedKindCount = aRegisteredKindCount; + } + theGraph.transientCache().Reserve(aReservedKindCount, aCounts); + } + // Build unified remap map covering all 8 topology kinds. + NCollection_DataMap aRemapMap; + for (const auto& [aOldIdx, aNewIdx] : aVertexMap.Items()) + aRemapMap.Bind(BRepGraph_VertexId(aOldIdx), BRepGraph_VertexId(aNewIdx)); + for (const auto& [aOldIdx, aNewIdx] : anEdgeMap.Items()) + aRemapMap.Bind(BRepGraph_EdgeId(aOldIdx), BRepGraph_EdgeId(aNewIdx)); + for (const auto& [aOldIdx, aNewIdx] : aCoEdgeMap.Items()) + aRemapMap.Bind(BRepGraph_CoEdgeId(aOldIdx), BRepGraph_CoEdgeId(aNewIdx)); + for (const auto& [aOldIdx, aNewIdx] : aWireMap.Items()) + aRemapMap.Bind(BRepGraph_WireId(aOldIdx), BRepGraph_WireId(aNewIdx)); + for (const auto& [aOldIdx, aNewIdx] : aFaceMap.Items()) + aRemapMap.Bind(BRepGraph_FaceId(aOldIdx), BRepGraph_FaceId(aNewIdx)); + for (const auto& [aOldIdx, aNewIdx] : aShellMap.Items()) + aRemapMap.Bind(BRepGraph_ShellId(aOldIdx), BRepGraph_ShellId(aNewIdx)); + for (const auto& [aOldIdx, aNewIdx] : aSolidMap.Items()) + aRemapMap.Bind(BRepGraph_SolidId(aOldIdx), BRepGraph_SolidId(aNewIdx)); + for (const auto& [aOldIdx, aNewIdx] : aCompoundMap.Items()) + aRemapMap.Bind(BRepGraph_CompoundId(aOldIdx), BRepGraph_CompoundId(aNewIdx)); + for (const auto& [aOldIdx, aNewIdx] : aCompSolidMap.Items()) + aRemapMap.Bind(BRepGraph_CompSolidId(aOldIdx), BRepGraph_CompSolidId(aNewIdx)); + + theGraph.LayerRegistry().DispatchOnCompact(aRemapMap); + + theGraph.Builder().CommitMutation(); + theGraph.History().SetEnabled(wasHistoryEnabled); + return aResult; +} diff --git a/src/ModelingData/TKBRep/BRepGraph/BRepGraph_Compact.hxx b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_Compact.hxx new file mode 100644 index 0000000000..f87a1007e9 --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_Compact.hxx @@ -0,0 +1,74 @@ +// Copyright (c) 2026 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 _BRepGraph_Compact_HeaderFile +#define _BRepGraph_Compact_HeaderFile + +#include + +#include + +//! @brief Graph compaction algorithm that reclaims removed node slots. +//! +//! After deduplication or other operations that mark nodes as removed, +//! this algorithm rebuilds the graph with dense index arrays, eliminating +//! all removed nodes and reassigning indices to be contiguous. +//! +//! Strategy: rebuild-and-swap. A fresh BRepGraph is constructed from +//! non-removed nodes with remapped indices, then move-assigned into +//! the input graph. +class BRepGraph_Compact +{ +public: + DEFINE_STANDARD_ALLOC + + //! Configuration for compaction. + struct Options + { + bool HistoryMode = true; //!< Record index remapping in history. + }; + + //! Result counters for diagnostics. + struct Result + { + int NbRemovedVertices = 0; + int NbRemovedEdges = 0; + int NbRemovedWires = 0; + int NbRemovedFaces = 0; + int NbRemovedShells = 0; + int NbRemovedSolids = 0; + int NbRemovedCompounds = 0; + int NbRemovedCompSolids = 0; + int NbRemovedSurfaces = 0; + int NbRemovedCurves = 0; + int NbNodesBefore = 0; + int NbNodesAfter = 0; + }; + + //! Run compaction with default options. + //! @param[in,out] theGraph graph to compact + //! @return compaction statistics + [[nodiscard]] Standard_EXPORT static Result Perform(BRepGraph& theGraph); + + //! Run compaction with specified options. + //! @param[in,out] theGraph graph to compact + //! @param[in] theOptions compaction configuration + //! @return compaction statistics + [[nodiscard]] Standard_EXPORT static Result Perform(BRepGraph& theGraph, + const Options& theOptions); + +private: + BRepGraph_Compact() = delete; +}; + +#endif // _BRepGraph_Compact_HeaderFile diff --git a/src/ModelingData/TKBRep/BRepGraph/BRepGraph_Copy.cxx b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_Copy.cxx new file mode 100644 index 0000000000..364fa6093a --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_Copy.cxx @@ -0,0 +1,629 @@ +// Copyright (c) 2026 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 + +namespace +{ + +//! Copy geometry handle if theCopyGeom is true, otherwise return same handle. +occ::handle copySurface(const occ::handle& theSurf, bool theCopyGeom) +{ + if (theSurf.IsNull() || !theCopyGeom) + return theSurf; + return occ::down_cast(theSurf->Copy()); +} + +occ::handle copyCurve(const occ::handle& theCrv, bool theCopyGeom) +{ + if (theCrv.IsNull() || !theCopyGeom) + return theCrv; + return occ::down_cast(theCrv->Copy()); +} + +occ::handle copyPCurve(const occ::handle& theCrv, bool theCopyGeom) +{ + if (theCrv.IsNull() || !theCopyGeom) + return theCrv; + return occ::down_cast(theCrv->Copy()); +} + +} // namespace + +//================================================================================================= + +void BRepGraph_Copy::reserveTransientCache(BRepGraph& theGraph) +{ + BRepGraphInc_Storage& aStorage = theGraph.incStorage(); + int aCounts[BRepGraph_TransientCache::THE_KIND_COUNT] = {}; + aCounts[static_cast(BRepGraph_NodeId::Kind::Vertex)] = aStorage.NbVertices(); + aCounts[static_cast(BRepGraph_NodeId::Kind::Edge)] = aStorage.NbEdges(); + aCounts[static_cast(BRepGraph_NodeId::Kind::CoEdge)] = aStorage.NbCoEdges(); + aCounts[static_cast(BRepGraph_NodeId::Kind::Wire)] = aStorage.NbWires(); + aCounts[static_cast(BRepGraph_NodeId::Kind::Face)] = aStorage.NbFaces(); + aCounts[static_cast(BRepGraph_NodeId::Kind::Shell)] = aStorage.NbShells(); + aCounts[static_cast(BRepGraph_NodeId::Kind::Solid)] = aStorage.NbSolids(); + aCounts[static_cast(BRepGraph_NodeId::Kind::Compound)] = aStorage.NbCompounds(); + aCounts[static_cast(BRepGraph_NodeId::Kind::CompSolid)] = aStorage.NbCompSolids(); + aCounts[static_cast(BRepGraph_NodeId::Kind::Product)] = aStorage.NbProducts(); + aCounts[static_cast(BRepGraph_NodeId::Kind::Occurrence)] = aStorage.NbOccurrences(); + int aReservedKindCount = BRepGraph_TransientCache::THE_DEFAULT_RESERVED_KIND_COUNT; + const int aRegisteredKindCount = BRepGraph_CacheKindRegistry::NbRegistered(); + if (aRegisteredKindCount > aReservedKindCount) + { + aReservedKindCount = aRegisteredKindCount; + } + theGraph.transientCache().Reserve(aReservedKindCount, aCounts); +} + +//================================================================================================= + +void BRepGraph_Copy::transferCacheValues(const BRepGraph& theSrcGraph, + const BRepGraph_NodeId theSrcNode, + BRepGraph& theDstGraph, + const BRepGraph_NodeId theDstNode) +{ + const BRepGraph_TransientCache& aSrcCache = theSrcGraph.transientCache(); + if (!aSrcCache.HasCacheValues(theSrcNode)) + { + return; + } + + theDstGraph.transientCache().TransferCacheValues(aSrcCache, theSrcNode, theDstNode, 0); +} + +//================================================================================================= + +BRepGraph BRepGraph_Copy::Perform(const BRepGraph& theGraph, const bool theCopyGeom) +{ + BRepGraph aResult; + if (!theGraph.IsDone()) + return aResult; + + const BRepGraph::RefsView& aRefs = theGraph.Refs(); + + // Bottom-up graph rebuild via BuilderView. + // Since this is a full copy, old index == new index (identity mapping). + + // Vertices. + const int aNbVertices = theGraph.Topo().Vertices().Nb(); + for (BRepGraph_VertexId aVertexId(0); aVertexId.IsValid(aNbVertices); ++aVertexId) + { + const BRepGraphInc::VertexDef& aVtx = theGraph.Topo().Vertices().Definition(aVertexId); + (void)aResult.Builder().AddVertex(aVtx.Point, aVtx.Tolerance); + transferCacheValues(theGraph, aVertexId, aResult, aVertexId); + } + + // Edges. + const int aNbEdges = theGraph.Topo().Edges().Nb(); + for (BRepGraph_EdgeId anEdgeId(0); anEdgeId.IsValid(aNbEdges); ++anEdgeId) + { + const BRepGraphInc::EdgeDef& anEdge = theGraph.Topo().Edges().Definition(anEdgeId); + + const occ::handle& anEdgeSrcCurve = BRepGraph_Tool::Edge::Curve(theGraph, anEdgeId); + occ::handle aCurve = copyCurve(anEdgeSrcCurve, theCopyGeom); + + // Resolve vertex def ids through storage ref entries for the full copy + // (identity mapping: old index == new index). + const BRepGraph_VertexId aStartVtxId = + anEdge.StartVertexRefId.IsValid() + ? aRefs.Vertices().Entry(anEdge.StartVertexRefId).VertexDefId + : BRepGraph_VertexId(); + const BRepGraph_VertexId anEndVtxId = + anEdge.EndVertexRefId.IsValid() ? aRefs.Vertices().Entry(anEdge.EndVertexRefId).VertexDefId + : BRepGraph_VertexId(); + + (void)aResult.Builder().AddEdge(aStartVtxId, + anEndVtxId, + aCurve, + anEdge.ParamFirst, + anEdge.ParamLast, + anEdge.Tolerance); + + BRepGraph_MutGuard aNewEdge = aResult.Builder().MutEdge(anEdgeId); + aNewEdge->IsDegenerate = anEdge.IsDegenerate; + aNewEdge->SameParameter = anEdge.SameParameter; + aNewEdge->SameRange = anEdge.SameRange; + transferCacheValues(theGraph, anEdgeId, aResult, anEdgeId); + } + + // Wires. + const int aNbWires = theGraph.Topo().Wires().Nb(); + for (BRepGraph_WireId aWireId(0); aWireId.IsValid(aNbWires); ++aWireId) + { + const BRepGraphInc::WireDef& aWire = theGraph.Topo().Wires().Definition(aWireId); + NCollection_Vector> aWireEdges; + for (const BRepGraph_CoEdgeRefId& aRefId : aWire.CoEdgeRefIds) + { + const BRepGraphInc::CoEdgeRef& aCR = aRefs.CoEdges().Entry(aRefId); + if (aCR.IsRemoved || !aCR.CoEdgeDefId.IsValid(theGraph.Topo().CoEdges().Nb())) + continue; + const BRepGraphInc::CoEdgeDef& aCoEdge = + theGraph.Topo().CoEdges().Definition(aCR.CoEdgeDefId); + if (!aCoEdge.EdgeDefId.IsValid(theGraph.Topo().Edges().Nb())) + continue; + aWireEdges.Append(std::make_pair(aCoEdge.EdgeDefId, aCoEdge.Sense)); + } + (void)aResult.Builder().AddWire(aWireEdges); + transferCacheValues(theGraph, aWireId, aResult, aWireId); + } + + // Faces. + const int aNbFaces = theGraph.Topo().Faces().Nb(); + for (BRepGraph_FaceId aFaceId(0); aFaceId.IsValid(aNbFaces); ++aFaceId) + { + const BRepGraphInc::FaceDef& aFace = theGraph.Topo().Faces().Definition(aFaceId); + + const occ::handle& aFaceSrcSurf = + BRepGraph_Tool::Face::Surface(theGraph, aFaceId); + occ::handle aSurf = copySurface(aFaceSrcSurf, theCopyGeom); + + // Get outer/inner wire def NodeIds from incidence refs. + BRepGraph_WireId anOuterWire; + NCollection_Vector anInnerWires; + + for (const BRepGraph_WireRefId& aRefId : aFace.WireRefIds) + { + const BRepGraphInc::WireRef& aWR = aRefs.Wires().Entry(aRefId); + if (aWR.IsRemoved || !aWR.WireDefId.IsValid(theGraph.Topo().Wires().Nb())) + continue; + const BRepGraph_WireId aWireDefId = aWR.WireDefId; + if (aWR.IsOuter) + { + anOuterWire = aWireDefId; + } + else + { + anInnerWires.Append(aWireDefId); + } + } + + (void)aResult.Builder().AddFace(aSurf, anOuterWire, anInnerWires, aFace.Tolerance); + + BRepGraph_MutGuard aNewFace = aResult.Builder().MutFace(aFaceId); + aNewFace->NaturalRestriction = aFace.NaturalRestriction; + aNewFace->TriangulationRepIds = aFace.TriangulationRepIds; + aNewFace->ActiveTriangulationIndex = aFace.ActiveTriangulationIndex; + transferCacheValues(theGraph, aFaceId, aResult, aFaceId); + } + + // PCurves via CoEdge data (after edges and faces are created). + for (BRepGraph_EdgeId anEdgeId(0); anEdgeId.IsValid(aNbEdges); ++anEdgeId) + { + const NCollection_Vector& aCoEdgeIds = + theGraph.Topo().Edges().CoEdges(anEdgeId); + for (const BRepGraph_CoEdgeId& aCoEdgeId : aCoEdgeIds) + { + const BRepGraphInc::CoEdgeDef& aCoEdge = theGraph.Topo().CoEdges().Definition(aCoEdgeId); + if (!aCoEdge.Curve2DRepId.IsValid()) + continue; + + const occ::handle& aCoEdgeSrcPC = + BRepGraph_Tool::CoEdge::PCurve(theGraph, aCoEdgeId); + occ::handle aNewPC = copyPCurve(aCoEdgeSrcPC, theCopyGeom); + aResult.Builder().AddPCurveToEdge(anEdgeId, + aCoEdge.FaceDefId, + aNewPC, + aCoEdge.ParamFirst, + aCoEdge.ParamLast, + aCoEdge.Sense); + } + } + + // Shells. + const int aNbShells = theGraph.Topo().Shells().Nb(); + for (BRepGraph_ShellId aShellId(0); aShellId.IsValid(aNbShells); ++aShellId) + { + BRepGraph_ShellId aNewShellId = aResult.Builder().AddShell(); + + const BRepGraphInc::ShellDef& aShell = theGraph.Topo().Shells().Definition(aShellId); + transferCacheValues(theGraph, aShellId, aResult, aShellId); + + for (const BRepGraph_FaceRefId& aRefId : aShell.FaceRefIds) + { + const BRepGraphInc::FaceRef& aFR = aRefs.Faces().Entry(aRefId); + if (aFR.IsRemoved || !aFR.FaceDefId.IsValid(theGraph.Topo().Faces().Nb())) + continue; + const BRepGraph_FaceId aFaceDefId = aFR.FaceDefId; + aResult.Builder().AddFaceToShell(aNewShellId, aFaceDefId, aFR.Orientation); + } + } + + // Solids. + const int aNbSolids = theGraph.Topo().Solids().Nb(); + for (BRepGraph_SolidId aSolidId(0); aSolidId.IsValid(aNbSolids); ++aSolidId) + { + BRepGraph_SolidId aNewSolidId = aResult.Builder().AddSolid(); + + const BRepGraphInc::SolidDef& aSolid = theGraph.Topo().Solids().Definition(aSolidId); + transferCacheValues(theGraph, aSolidId, aResult, aSolidId); + + for (const BRepGraph_ShellRefId& aRefId : aSolid.ShellRefIds) + { + const BRepGraphInc::ShellRef& aSR = aRefs.Shells().Entry(aRefId); + if (aSR.IsRemoved || !aSR.ShellDefId.IsValid(theGraph.Topo().Shells().Nb())) + continue; + const BRepGraph_ShellId aShellDefId = aSR.ShellDefId; + aResult.Builder().AddShellToSolid(aNewSolidId, aShellDefId, aSR.Orientation); + } + } + + // Compounds. + const int aNbCompounds = theGraph.Topo().Compounds().Nb(); + for (BRepGraph_CompoundId aCompoundId(0); aCompoundId.IsValid(aNbCompounds); ++aCompoundId) + { + const BRepGraphInc::CompoundDef& aComp = theGraph.Topo().Compounds().Definition(aCompoundId); + NCollection_Vector aChildNodeIds; + for (const BRepGraph_ChildRefId& aRefId : aComp.ChildRefIds) + { + const BRepGraphInc::ChildRef& aCR = aRefs.Children().Entry(aRefId); + if (aCR.IsRemoved || !aCR.ChildDefId.IsValid()) + continue; + aChildNodeIds.Append(aCR.ChildDefId); + } + (void)aResult.Builder().AddCompound(aChildNodeIds); + transferCacheValues(theGraph, aCompoundId, aResult, aCompoundId); + } + + // CompSolids. + const int aNbCompSolids = theGraph.Topo().CompSolids().Nb(); + for (BRepGraph_CompSolidId aCompSolidId(0); aCompSolidId.IsValid(aNbCompSolids); ++aCompSolidId) + { + const BRepGraphInc::CompSolidDef& aCS = theGraph.Topo().CompSolids().Definition(aCompSolidId); + NCollection_Vector aSolidNodeIds; + for (const BRepGraph_SolidRefId& aRefId : aCS.SolidRefIds) + { + const BRepGraphInc::SolidRef& aSR = aRefs.Solids().Entry(aRefId); + if (aSR.IsRemoved || !aSR.SolidDefId.IsValid(theGraph.Topo().Solids().Nb())) + continue; + aSolidNodeIds.Append(aSR.SolidDefId); + } + (void)aResult.Builder().AddCompSolid(aSolidNodeIds); + transferCacheValues(theGraph, aCompSolidId, aResult, aCompSolidId); + } + + // Products. + const int aNbProducts = theGraph.incStorage().NbProducts(); + for (BRepGraph_ProductId aProductId(0); aProductId.IsValid(aNbProducts); ++aProductId) + { + const BRepGraphInc::ProductDef& aSrcProd = theGraph.incStorage().Product(aProductId); + BRepGraphInc::ProductDef& aNewProd = aResult.incStorage().AppendProduct(); + aNewProd.Id = aProductId; + aNewProd.ShapeRootId = aSrcProd.ShapeRootId; + aNewProd.RootOrientation = aSrcProd.RootOrientation; + aNewProd.RootLocation = aSrcProd.RootLocation; + aNewProd.OccurrenceRefIds = aSrcProd.OccurrenceRefIds; + } + + // Occurrences. + const int aNbOccurrences = theGraph.incStorage().NbOccurrences(); + for (BRepGraph_OccurrenceId anOccurrenceId(0); anOccurrenceId.IsValid(aNbOccurrences); + ++anOccurrenceId) + { + const BRepGraphInc::OccurrenceDef& aSrcOcc = theGraph.incStorage().Occurrence(anOccurrenceId); + BRepGraphInc::OccurrenceDef& aNewOcc = aResult.incStorage().AppendOccurrence(); + aNewOcc.Id = anOccurrenceId; + aNewOcc.ProductDefId = aSrcOcc.ProductDefId; + aNewOcc.ParentProductDefId = aSrcOcc.ParentProductDefId; + aNewOcc.ParentOccurrenceDefId = aSrcOcc.ParentOccurrenceDefId; + aNewOcc.Placement = aSrcOcc.Placement; + } + + // Phase 3: Transfer UIDs (identity mapping - direct vector copy). + auto copyUIDs = [](const NCollection_Vector& theSrc, + NCollection_Vector& theDst) { + const int aNbToCopy = std::min(theSrc.Length(), theDst.Length()); + for (int anIdx = 0; anIdx < aNbToCopy; ++anIdx) + { + if (theSrc.Value(anIdx).IsValid()) + theDst.ChangeValue(anIdx) = theSrc.Value(anIdx); + } + }; + + copyUIDs(theGraph.incStorage().UIDs(BRepGraph_NodeId::Kind::Vertex), + aResult.incStorage().ChangeUIDs(BRepGraph_NodeId::Kind::Vertex)); + copyUIDs(theGraph.incStorage().UIDs(BRepGraph_NodeId::Kind::Edge), + aResult.incStorage().ChangeUIDs(BRepGraph_NodeId::Kind::Edge)); + copyUIDs(theGraph.incStorage().UIDs(BRepGraph_NodeId::Kind::Wire), + aResult.incStorage().ChangeUIDs(BRepGraph_NodeId::Kind::Wire)); + copyUIDs(theGraph.incStorage().UIDs(BRepGraph_NodeId::Kind::Face), + aResult.incStorage().ChangeUIDs(BRepGraph_NodeId::Kind::Face)); + copyUIDs(theGraph.incStorage().UIDs(BRepGraph_NodeId::Kind::Shell), + aResult.incStorage().ChangeUIDs(BRepGraph_NodeId::Kind::Shell)); + copyUIDs(theGraph.incStorage().UIDs(BRepGraph_NodeId::Kind::Solid), + aResult.incStorage().ChangeUIDs(BRepGraph_NodeId::Kind::Solid)); + copyUIDs(theGraph.incStorage().UIDs(BRepGraph_NodeId::Kind::Compound), + aResult.incStorage().ChangeUIDs(BRepGraph_NodeId::Kind::Compound)); + copyUIDs(theGraph.incStorage().UIDs(BRepGraph_NodeId::Kind::CompSolid), + aResult.incStorage().ChangeUIDs(BRepGraph_NodeId::Kind::CompSolid)); + copyUIDs(theGraph.incStorage().UIDs(BRepGraph_NodeId::Kind::Product), + aResult.incStorage().ChangeUIDs(BRepGraph_NodeId::Kind::Product)); + copyUIDs(theGraph.incStorage().UIDs(BRepGraph_NodeId::Kind::Occurrence), + aResult.incStorage().ChangeUIDs(BRepGraph_NodeId::Kind::Occurrence)); + + aResult.data()->myNextUIDCounter.store( + theGraph.data()->myNextUIDCounter.load(std::memory_order_relaxed), + std::memory_order_relaxed); + aResult.data()->myGeneration.store(theGraph.data()->myGeneration.load(std::memory_order_relaxed), + std::memory_order_relaxed); + aResult.data()->myIsDone = true; + + // Pre-allocate transient cache for lock-free parallel access on the copied graph. + reserveTransientCache(aResult); + + return aResult; +} + +//================================================================================================= + +BRepGraph BRepGraph_Copy::CopyFace(const BRepGraph& theGraph, + const BRepGraph_FaceId theFace, + const bool theCopyGeom, + const bool theReserveCache) +{ + BRepGraph aResult; + if (!theGraph.IsDone() || theFace.Index < 0 || theFace.Index >= theGraph.Topo().Faces().Nb()) + return aResult; + + const BRepGraph::RefsView& aRefs = theGraph.Refs(); + const BRepGraphInc::FaceDef& aFaceDef = theGraph.Topo().Faces().Definition(theFace); + bool hasWires = false; + for (const BRepGraph_WireRefId& aRefId : aFaceDef.WireRefIds) + { + const BRepGraphInc::WireRef& aWR = aRefs.Wires().Entry(aRefId); + if (!aWR.IsRemoved && aWR.WireDefId.IsValid(theGraph.Topo().Wires().Nb())) + { + hasWires = true; + break; + } + } + if (!hasWires) + return aResult; + + // Use NCollection_IndexedMap to collect old indices in deterministic insertion order. + NCollection_IndexedMap aVertexSet, anEdgeSet, aWireSet; + + // Collect all edges/vertices/wires from the face's wire refs. + for (const BRepGraph_WireRefId& aWireRefId : aFaceDef.WireRefIds) + { + const BRepGraphInc::WireRef& aWR = aRefs.Wires().Entry(aWireRefId); + if (aWR.IsRemoved || !aWR.WireDefId.IsValid(theGraph.Topo().Wires().Nb())) + continue; + const int aWireDefIdx = aWR.WireDefId.Index; + aWireSet.Add(aWireDefIdx); + + const BRepGraphInc::WireDef& aWireDef = theGraph.Topo().Wires().Definition(aWR.WireDefId); + for (const BRepGraph_CoEdgeRefId& aCoEdgeRefId : aWireDef.CoEdgeRefIds) + { + const BRepGraphInc::CoEdgeRef& aCR = aRefs.CoEdges().Entry(aCoEdgeRefId); + if (aCR.IsRemoved || !aCR.CoEdgeDefId.IsValid(theGraph.Topo().CoEdges().Nb())) + continue; + const BRepGraphInc::CoEdgeDef& aCoEdge = + theGraph.Topo().CoEdges().Definition(aCR.CoEdgeDefId); + if (!aCoEdge.EdgeDefId.IsValid(theGraph.Topo().Edges().Nb())) + continue; + anEdgeSet.Add(aCoEdge.EdgeDefId.Index); + + const BRepGraphInc::EdgeDef& anEdgeDef = + theGraph.Topo().Edges().Definition(aCoEdge.EdgeDefId); + if (anEdgeDef.StartVertexRefId.IsValid()) + { + const BRepGraph_VertexId aStartVtx = + aRefs.Vertices().Entry(anEdgeDef.StartVertexRefId).VertexDefId; + if (aStartVtx.IsValid(theGraph.Topo().Vertices().Nb())) + aVertexSet.Add(aStartVtx.Index); + } + if (anEdgeDef.EndVertexRefId.IsValid()) + { + const BRepGraph_VertexId anEndVtx = + aRefs.Vertices().Entry(anEdgeDef.EndVertexRefId).VertexDefId; + if (anEndVtx.IsValid(theGraph.Topo().Vertices().Nb())) + aVertexSet.Add(anEndVtx.Index); + } + } + } + + // Build old->new index maps from the ordered sets. + NCollection_DataMap aVertexMap(aVertexSet.Extent()); + for (int anIdx = 1; anIdx <= aVertexSet.Extent(); ++anIdx) + aVertexMap.Bind(aVertexSet.FindKey(anIdx), anIdx - 1); + + NCollection_DataMap anEdgeMap(anEdgeSet.Extent()); + for (int anIdx = 1; anIdx <= anEdgeSet.Extent(); ++anIdx) + anEdgeMap.Bind(anEdgeSet.FindKey(anIdx), anIdx - 1); + + NCollection_DataMap aWireMap(aWireSet.Extent()); + for (int anIdx = 1; anIdx <= aWireSet.Extent(); ++anIdx) + aWireMap.Bind(aWireSet.FindKey(anIdx), anIdx - 1); + + // Add vertices in deterministic order. + for (int anIdx = 1; anIdx <= aVertexSet.Extent(); ++anIdx) + { + const int anOldIdx = aVertexSet.FindKey(anIdx); + const BRepGraphInc::VertexDef& aVtx = + theGraph.Topo().Vertices().Definition(BRepGraph_VertexId(anOldIdx)); + (void)aResult.Builder().AddVertex(aVtx.Point, aVtx.Tolerance); + transferCacheValues(theGraph, + BRepGraph_VertexId(anOldIdx), + aResult, + BRepGraph_VertexId(anIdx - 1)); + } + + // Add edges in deterministic order. + for (int anIdx = 1; anIdx <= anEdgeSet.Extent(); ++anIdx) + { + const int anOldIdx = anEdgeSet.FindKey(anIdx); + const BRepGraphInc::EdgeDef& anEdge = + theGraph.Topo().Edges().Definition(BRepGraph_EdgeId(anOldIdx)); + + BRepGraph_VertexId aNewStart, aNewEnd; + if (anEdge.StartVertexRefId.IsValid()) + { + const BRepGraph_VertexId aStartVtx = + aRefs.Vertices().Entry(anEdge.StartVertexRefId).VertexDefId; + if (aStartVtx.IsValid()) + { + const int* aNewVtxIdx = aVertexMap.Seek(aStartVtx.Index); + if (aNewVtxIdx != nullptr) + aNewStart = BRepGraph_VertexId(*aNewVtxIdx); + } + } + if (anEdge.EndVertexRefId.IsValid()) + { + const BRepGraph_VertexId anEndVtx = aRefs.Vertices().Entry(anEdge.EndVertexRefId).VertexDefId; + if (anEndVtx.IsValid()) + { + const int* aNewVtxIdx = aVertexMap.Seek(anEndVtx.Index); + if (aNewVtxIdx != nullptr) + aNewEnd = BRepGraph_VertexId(*aNewVtxIdx); + } + } + + const occ::handle& anEdgeSrcCurve = + BRepGraph_Tool::Edge::Curve(theGraph, BRepGraph_EdgeId(anOldIdx)); + occ::handle aCurve = copyCurve(anEdgeSrcCurve, theCopyGeom); + + const int aNewEdgeIdx = anIdx - 1; + (void)aResult.Builder() + .AddEdge(aNewStart, aNewEnd, aCurve, anEdge.ParamFirst, anEdge.ParamLast, anEdge.Tolerance); + + BRepGraph_MutGuard aNewEdge = + aResult.Builder().MutEdge(BRepGraph_EdgeId(aNewEdgeIdx)); + aNewEdge->IsDegenerate = anEdge.IsDegenerate; + aNewEdge->SameParameter = anEdge.SameParameter; + aNewEdge->SameRange = anEdge.SameRange; + transferCacheValues(theGraph, + BRepGraph_EdgeId(anOldIdx), + aResult, + BRepGraph_EdgeId(aNewEdgeIdx)); + } + + // Add wires in deterministic order. + for (int anIdx = 1; anIdx <= aWireSet.Extent(); ++anIdx) + { + const int anOldIdx = aWireSet.FindKey(anIdx); + const BRepGraphInc::WireDef& aWire = + theGraph.Topo().Wires().Definition(BRepGraph_WireId(anOldIdx)); + NCollection_Vector> aNewEntries; + for (const BRepGraph_CoEdgeRefId& aCoEdgeRefId : aWire.CoEdgeRefIds) + { + const BRepGraphInc::CoEdgeRef& aCR = aRefs.CoEdges().Entry(aCoEdgeRefId); + if (aCR.IsRemoved || !aCR.CoEdgeDefId.IsValid(theGraph.Topo().CoEdges().Nb())) + continue; + const BRepGraphInc::CoEdgeDef& aCoEdge = + theGraph.Topo().CoEdges().Definition(aCR.CoEdgeDefId); + const int* aNewEdgeIdx = anEdgeMap.Seek(aCoEdge.EdgeDefId.Index); + if (aNewEdgeIdx == nullptr) + continue; + aNewEntries.Append(std::make_pair(BRepGraph_EdgeId(*aNewEdgeIdx), aCoEdge.Sense)); + } + (void)aResult.Builder().AddWire(aNewEntries); + { + const int anOldWireIdx = aWireSet.FindKey(anIdx); + transferCacheValues(theGraph, + BRepGraph_WireId(anOldWireIdx), + aResult, + BRepGraph_WireId(anIdx - 1)); + } + } + + // Add the face. + const occ::handle& aFaceSrcSurf = BRepGraph_Tool::Face::Surface(theGraph, theFace); + occ::handle aSurf = copySurface(aFaceSrcSurf, theCopyGeom); + + BRepGraph_WireId anOuterWire; + NCollection_Vector anInnerWires; + + for (const BRepGraph_WireRefId& aRefId : aFaceDef.WireRefIds) + { + const BRepGraphInc::WireRef& aWR = aRefs.Wires().Entry(aRefId); + if (aWR.IsRemoved || !aWR.WireDefId.IsValid(theGraph.Topo().Wires().Nb())) + continue; + const int* aNewWireIdx = aWireMap.Seek(aWR.WireDefId.Index); + if (aNewWireIdx == nullptr) + continue; + if (aWR.IsOuter) + { + anOuterWire = BRepGraph_WireId(*aNewWireIdx); + } + else + { + anInnerWires.Append(BRepGraph_WireId(*aNewWireIdx)); + } + } + + (void)aResult.Builder().AddFace(aSurf, anOuterWire, anInnerWires, aFaceDef.Tolerance); + BRepGraph_MutGuard aNewFace = + aResult.Builder().MutFace(BRepGraph_FaceId(0)); + aNewFace->NaturalRestriction = aFaceDef.NaturalRestriction; + aNewFace->TriangulationRepIds = aFaceDef.TriangulationRepIds; + aNewFace->ActiveTriangulationIndex = aFaceDef.ActiveTriangulationIndex; + transferCacheValues(theGraph, theFace, aResult, BRepGraph_FaceId(0)); + + // PCurves for edges in this face via CoEdge data. + for (int anIdx = 1; anIdx <= anEdgeSet.Extent(); ++anIdx) + { + const int anOldEdgeIdx = anEdgeSet.FindKey(anIdx); + const int aNewEdgeIdx = anIdx - 1; + const NCollection_Vector& aCoEdgeIds = + theGraph.Topo().Edges().CoEdges(BRepGraph_EdgeId(anOldEdgeIdx)); + for (const BRepGraph_CoEdgeId& aCoEdgeId : aCoEdgeIds) + { + const BRepGraphInc::CoEdgeDef& aCoEdge = theGraph.Topo().CoEdges().Definition(aCoEdgeId); + // Only copy CoEdges belonging to this face. + if (aCoEdge.FaceDefId.Index != theFace.Index) + continue; + if (!aCoEdge.Curve2DRepId.IsValid()) + continue; + + const occ::handle& aCoEdgeSrcPC = + BRepGraph_Tool::CoEdge::PCurve(theGraph, aCoEdgeId); + occ::handle aNewPC = copyPCurve(aCoEdgeSrcPC, theCopyGeom); + aResult.Builder().AddPCurveToEdge(BRepGraph_EdgeId(aNewEdgeIdx), + BRepGraph_FaceId(0), + aNewPC, + aCoEdge.ParamFirst, + aCoEdge.ParamLast, + aCoEdge.Sense); + } + } + + aResult.data()->myIsDone = true; + + // Pre-allocate transient cache for lock-free parallel access on the copied graph. + // Skip for short-lived temporary graphs where cache is never queried. + if (theReserveCache) + { + reserveTransientCache(aResult); + } + + return aResult; +} diff --git a/src/ModelingData/TKBRep/BRepGraph/BRepGraph_Copy.hxx b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_Copy.hxx new file mode 100644 index 0000000000..4830efb152 --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_Copy.hxx @@ -0,0 +1,80 @@ +// Copyright (c) 2026 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 _BRepGraph_Copy_HeaderFile +#define _BRepGraph_Copy_HeaderFile + +#include +#include + +#include + +//! @brief Graph-to-graph deep copy. +//! +//! Produces a new BRepGraph from an existing one in a single bottom-up pass, +//! avoiding the 5-7 traversals of BRepTools_Modifier used by BRepBuilderAPI_Copy. +//! +//! Two modes: +//! - theCopyGeom = true (deep): geometry handles are cloned, result is fully independent. +//! - theCopyGeom = false (light): geometry handles are shared, only topology is duplicated. +//! +//! @note Unlike in-place mutation algorithms (Sewing, Deduplicate) which return a +//! Result struct with diagnostics, Copy and Transform return a BRepGraph directly +//! because they produce new graphs. Check IsDone() on the returned graph for success. +//! +//! ## Typical usage +//! @code +//! BRepGraph aGraph; +//! aGraph.Build(myShape); +//! BRepGraph aCopy = BRepGraph_Copy::Perform(aGraph); +//! TopoDS_Shape aShape = aCopy.Shapes().Shape(); +//! @endcode +class BRepGraph_Copy +{ +public: + DEFINE_STANDARD_ALLOC + + //! Copy the entire graph. + //! @param[in] theGraph a pre-built BRepGraph (must have IsDone() == true) + //! @param[in] theCopyGeom if true (default), geometry handles are deep-copied; + //! if false, geometry is shared (only topology is duplicated) + //! @return a new BRepGraph with IsDone() == true on success, + //! or an empty graph with IsDone() == false on failure + [[nodiscard]] Standard_EXPORT static BRepGraph Perform(const BRepGraph& theGraph, + const bool theCopyGeom = true); + + //! Copy a single face sub-graph. + //! @param[in] theGraph a pre-built BRepGraph + //! @param[in] theFace face definition identifier in the graph + //! @param[in] theCopyGeom if true, geometry is deep-copied + //! @param[in] theReserveCache if true (default), pre-allocates transient cache; + //! pass false for short-lived temporary graphs + //! @return a new BRepGraph containing only the specified face and its dependencies + [[nodiscard]] Standard_EXPORT static BRepGraph CopyFace(const BRepGraph& theGraph, + const BRepGraph_FaceId theFace, + const bool theCopyGeom = true, + const bool theReserveCache = true); + +private: + Standard_EXPORT static void transferCacheValues(const BRepGraph& theSrcGraph, + BRepGraph_NodeId theSrcNode, + BRepGraph& theDstGraph, + BRepGraph_NodeId theDstNode); + + //! Pre-allocate transient cache for lock-free parallel access. + Standard_EXPORT static void reserveTransientCache(BRepGraph& theGraph); + + BRepGraph_Copy() = delete; +}; + +#endif // _BRepGraph_Copy_HeaderFile diff --git a/src/ModelingData/TKBRep/BRepGraph/BRepGraph_Data.hxx b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_Data.hxx new file mode 100644 index 0000000000..65d12d7aa3 --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_Data.hxx @@ -0,0 +1,142 @@ +// Copyright (c) 2026 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 _BRepGraph_Data_HeaderFile +#define _BRepGraph_Data_HeaderFile + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include + +//! @brief Internal storage for BRepGraph (PIMPL). +//! +//! All topology definition data and UIDs live in myIncStorage. +//! Access via myIncStorage.Edges, myIncStorage.Faces, etc. +struct BRepGraph_Data +{ + occ::handle myAllocator; + + //! Incidence-table storage - sole source of truth for all topology data, + //! original shapes, TShape->NodeId mapping, and UIDs. + BRepGraphInc_Storage myIncStorage; + + //! UID system. + std::atomic myNextUIDCounter{ + 1}; //!< Starts at 1; counter=0 is BRepGraph_UID invalid sentinel. + std::atomic myGeneration{0}; + Standard_GUID myGraphGUID; //!< Random graph identity, generated at Build(). + + //! History subsystem. + BRepGraph_History myHistoryLog; + + bool myIsDone = false; + + //! Root topology NodeIds created by Build()/append operations. + //! Face-level append may contribute multiple face roots for one input shape. + NCollection_Vector myRootNodeIds; + + //! When true, markModified() only increments OwnGen + SubtreeGen and appends to + //! myDeferredModified - no mutex acquisition and no upward propagation. + std::atomic myDeferredMode{false}; + + //! Propagation wave counter. Incremented at the start of each + //! markModified() / markRefModified() call. markParentModified() + //! compares entity.LastPropWave against this to skip already-visited + //! parents in the same propagation wave (O(1) re-visit guard). + std::atomic myPropagationWave{0}; + + //! NodeIds accumulated during deferred mode. Processed by EndDeferredInvalidation(). + NCollection_Vector myDeferredModified; + + //! Gen-validated shape cache entry. + struct CachedShape + { + TopoDS_Shape Shape; + uint32_t StoredSubtreeGen = 0; + }; + + //! Thread-safe cache of reconstructed shapes with SubtreeGen validation. + mutable NCollection_DataMap myCurrentShapes; + mutable std::shared_mutex myCurrentShapesMutex; + + //! Lazy reverse lookup index for entity UIDs. + mutable NCollection_DataMap myUIDToNodeId; + mutable std::shared_mutex myUIDToNodeIdMutex; + mutable uint32_t myUIDToNodeIdGeneration = 0; + mutable bool myUIDToNodeIdDirty = true; + + //! Lazy reverse lookup index for reference UIDs. + mutable NCollection_DataMap myRefUIDToRefId; + mutable std::shared_mutex myRefUIDToRefIdMutex; + mutable uint32_t myRefUIDToRefIdGeneration = 0; + mutable bool myRefUIDToRefIdDirty = true; + + using ReconstructCache = NCollection_DataMap; + + //! Cached view objects (pointers set to owning BRepGraph in its constructor). + BRepGraph::TopoView myTopoView{nullptr}; + BRepGraph::UIDsView myUIDsView{nullptr}; + BRepGraph::CacheView myCacheView{nullptr}; + BRepGraph::RefsView myRefsView{nullptr}; + BRepGraph::ShapesView myShapesView{nullptr}; + BRepGraph::BuilderView myBuilderView{nullptr}; + + BRepGraph_Data() + : myAllocator(new NCollection_IncAllocator), + myIncStorage(myAllocator), + myCurrentShapes(1, myAllocator), + myUIDToNodeId(1, myAllocator), + myRefUIDToRefId(1, myAllocator) + { + myHistoryLog.SetAllocator(myAllocator); + } + + explicit BRepGraph_Data(const occ::handle& theAlloc) + : myAllocator(!theAlloc.IsNull() + ? theAlloc + : occ::handle(new NCollection_IncAllocator)), + myIncStorage(myAllocator), + myCurrentShapes(1, myAllocator), + myUIDToNodeId(1, myAllocator), + myRefUIDToRefId(1, myAllocator) + { + myHistoryLog.SetAllocator(myAllocator); + } +}; + +#endif // _BRepGraph_Data_HeaderFile diff --git a/src/ModelingData/TKBRep/BRepGraph/BRepGraph_Deduplicate.cxx b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_Deduplicate.cxx new file mode 100644 index 0000000000..30d85d14ca --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_Deduplicate.cxx @@ -0,0 +1,787 @@ +// Copyright (c) 2026 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 + +namespace +{ +template +void forWireCoEdgeRefEntries(const BRepGraph& theGraph, + const BRepGraph_WireId theWireId, + FuncT&& theFunc) +{ + const BRepGraphInc::WireDef& aWireEnt = theGraph.Topo().Wires().Definition(theWireId); + const BRepGraph::RefsView& aRefs = theGraph.Refs(); + for (int i = 0; i < aWireEnt.CoEdgeRefIds.Length(); ++i) + { + const BRepGraph_CoEdgeRefId aRefId = aWireEnt.CoEdgeRefIds.Value(i); + const BRepGraphInc::CoEdgeRef& aCR = aRefs.CoEdges().Entry(aRefId); + if (aCR.IsRemoved || !aCR.CoEdgeDefId.IsValid(theGraph.Topo().CoEdges().Nb())) + { + continue; + } + theFunc(aCR); + } +} + +template +void forFaceWireRefEntries(const BRepGraph& theGraph, + const BRepGraph_FaceId theFaceId, + FuncT&& theFunc) +{ + const BRepGraphInc::FaceDef& aFaceEnt = theGraph.Topo().Faces().Definition(theFaceId); + const BRepGraph::RefsView& aRefs = theGraph.Refs(); + for (int i = 0; i < aFaceEnt.WireRefIds.Length(); ++i) + { + const BRepGraph_WireRefId aRefId = aFaceEnt.WireRefIds.Value(i); + const BRepGraphInc::WireRef& aWR = aRefs.Wires().Entry(aRefId); + if (aWR.IsRemoved || !aWR.WireDefId.IsValid(theGraph.Topo().Wires().Nb())) + { + continue; + } + theFunc(aWR); + } +} +} // namespace + +//================================================================================================= + +BRepGraph_Deduplicate::Result BRepGraph_Deduplicate::Perform(BRepGraph& theGraph) +{ + return Perform(theGraph, Options()); +} + +//================================================================================================= + +BRepGraph_Deduplicate::Result BRepGraph_Deduplicate::Perform(BRepGraph& theGraph, + const Options& theOptions) +{ + Result aResult; + if (!theGraph.IsDone()) + { + return aResult; + } + BRepGraph_DeferredScope aDeferredScope(theGraph); + + const bool wasHistoryEnabled = theGraph.History().IsEnabled(); + theGraph.History().SetEnabled(theOptions.HistoryMode); + + GeomHash_SurfaceHasher aSurfHasher(theOptions.CompTolerance, theOptions.HashTolerance); + GeomHash_CurveHasher aCurveHasher(theOptions.CompTolerance, theOptions.HashTolerance); + + const occ::handle aTmpAlloc = new NCollection_IncAllocator(); + + // Deduplicate surfaces by comparing Handle pointers on FaceDefs. + // Map: surface handle -> canonical face index (first face that owns it). + NCollection_DataMap, int, GeomHash_SurfaceHasher> aSurfToCanonicalFace( + aSurfHasher, + std::max(1, theGraph.Topo().Faces().Nb() * 2), + aTmpAlloc); + // Map: face index -> canonical face index (for faces whose surface should be replaced). + NCollection_DataMap aSurfRewriteMap(std::max(1, theGraph.Topo().Faces().Nb()), + aTmpAlloc); + + for (int aFaceIdx = 0; aFaceIdx < theGraph.Topo().Faces().Nb(); ++aFaceIdx) + { + const BRepGraph_FaceId aFaceId(aFaceIdx); + if (!BRepGraph_Tool::Face::HasSurface(theGraph, aFaceId)) + { + continue; + } + + const occ::handle& aFaceSurf = BRepGraph_Tool::Face::Surface(theGraph, aFaceId); + const int* aCanonFaceIdx = aSurfToCanonicalFace.Seek(aFaceSurf); + if (aCanonFaceIdx == nullptr) + { + aSurfToCanonicalFace.Bind(aFaceSurf, aFaceIdx); + } + else if (*aCanonFaceIdx != aFaceIdx) + { + aSurfRewriteMap.Bind(aFaceIdx, *aCanonFaceIdx); + } + } + + // Deduplicate curves by comparing Handle pointers on EdgeDefs. + NCollection_DataMap, int, GeomHash_CurveHasher> aCurveToCanonicalEdge( + aCurveHasher, + std::max(1, theGraph.Topo().Edges().Nb() * 2), + aTmpAlloc); + NCollection_DataMap aCurveRewriteMap(std::max(1, theGraph.Topo().Edges().Nb()), + aTmpAlloc); + + for (int anEdgeIdx = 0; anEdgeIdx < theGraph.Topo().Edges().Nb(); ++anEdgeIdx) + { + const BRepGraph_EdgeId anEdgeId(anEdgeIdx); + if (!BRepGraph_Tool::Edge::HasCurve(theGraph, anEdgeId)) + { + continue; + } + + const occ::handle& anEdgeCurve = BRepGraph_Tool::Edge::Curve(theGraph, anEdgeId); + const int* aCanonEdgeIdx = aCurveToCanonicalEdge.Seek(anEdgeCurve); + if (aCanonEdgeIdx == nullptr) + { + aCurveToCanonicalEdge.Bind(anEdgeCurve, anEdgeIdx); + } + else if (*aCanonEdgeIdx != anEdgeIdx) + { + aCurveRewriteMap.Bind(anEdgeIdx, *aCanonEdgeIdx); + } + } + + aResult.NbCanonicalSurfaces = theGraph.Topo().Faces().Nb() - aSurfRewriteMap.Size(); + aResult.NbCanonicalCurves = theGraph.Topo().Edges().Nb() - aCurveRewriteMap.Size(); + + if (theOptions.AnalyzeOnly && !theOptions.MergeEntitiesWhenSafe) + { + theGraph.History().SetEnabled(wasHistoryEnabled); + return aResult; + } + + if (!theOptions.AnalyzeOnly) + { + // Rewrite face surfaces: replace duplicate surface handles with canonical ones. + for (NCollection_DataMap::Iterator anIt(aSurfRewriteMap); anIt.More(); anIt.Next()) + { + const int aFaceIdx = anIt.Key(); + const int aCanonFaceIdx = anIt.Value(); + BRepGraph_MutGuard aFaceDef = + theGraph.Builder().MutFace(BRepGraph_FaceId(aFaceIdx)); + const BRepGraph_SurfaceRepId aCanonSurfRepId = + theGraph.Topo().Faces().Definition(BRepGraph_FaceId(aCanonFaceIdx)).SurfaceRepId; + aFaceDef->SurfaceRepId = aCanonSurfRepId; + ++aResult.NbSurfaceRewrites; + aResult.AffectedFaces.Append(BRepGraph_FaceId(aFaceDef->Id.Index)); + + NCollection_Vector aRepl; + aRepl.Append(BRepGraph_FaceId(aCanonFaceIdx)); + theGraph.History().Record(TCollection_AsciiString("Dedup:CanonicalizeSurface"), + aFaceDef->Id, + aRepl); + ++aResult.NbHistoryRecords; + } + + // Rewrite edge curves: replace duplicate curve handles with canonical ones. + for (NCollection_DataMap::Iterator anIt(aCurveRewriteMap); anIt.More(); anIt.Next()) + { + const int anEdgeIdx = anIt.Key(); + const int aCanonEdgeIdx = anIt.Value(); + BRepGraph_MutGuard anEdgeDef = + theGraph.Builder().MutEdge(BRepGraph_EdgeId(anEdgeIdx)); + const BRepGraph_Curve3DRepId aCanonCurveRepId = + theGraph.Topo().Edges().Definition(BRepGraph_EdgeId(aCanonEdgeIdx)).Curve3DRepId; + anEdgeDef->Curve3DRepId = aCanonCurveRepId; + ++aResult.NbCurveRewrites; + aResult.AffectedEdges.Append(BRepGraph_EdgeId(anEdgeDef->Id.Index)); + + NCollection_Vector aRepl; + aRepl.Append(BRepGraph_EdgeId(aCanonEdgeIdx)); + theGraph.History().Record(TCollection_AsciiString("Dedup:CanonicalizeCurve"), + anEdgeDef->Id, + aRepl); + ++aResult.NbHistoryRecords; + } + + } // end if (!theOptions.AnalyzeOnly) for geometry rewrites + + // Definition merge phases (Vertex -> Edge -> Wire -> Face). + if (!theOptions.MergeEntitiesWhenSafe) + { + aResult.IsEntityMergeApplied = false; + theGraph.History().SetEnabled(wasHistoryEnabled); + return aResult; + } + + // Phase 1: Vertex Merging via KDTree range search. + { + const double aTol = theOptions.HashTolerance; + + // Collect active vertices: (point, graph index) pairs. + const int aNbVertices = theGraph.Topo().Vertices().Nb(); + NCollection_Vector> aActiveVertices(256, aTmpAlloc); + for (int aVtxIdx = 0; aVtxIdx < aNbVertices; ++aVtxIdx) + { + const BRepGraphInc::VertexDef& aVtx = + theGraph.Topo().Vertices().Definition(BRepGraph_VertexId(aVtxIdx)); + if (aVtx.IsRemoved) + continue; + aActiveVertices.Append( + std::make_pair(BRepGraph_Tool::Vertex::Pnt(theGraph, BRepGraph_VertexId(aVtxIdx)), + aVtxIdx)); + } + + // Build KDTree from active vertex points - O(n log n). + const int aNbActive = aActiveVertices.Length(); + NCollection_Array1 aPointsArr(0, std::max(0, aNbActive - 1)); + for (int i = 0; i < aNbActive; ++i) + aPointsArr.SetValue(i, aActiveVertices.Value(i).first); + + NCollection_KDTree aTree; + if (!aPointsArr.IsEmpty()) + aTree.Build(aPointsArr); + + // Canonical vertex map: old graph index -> canonical graph index. + NCollection_DataMap aCanonicalVertex(std::max(1, aNbVertices), aTmpAlloc); + + for (int aLocalIdx = 0; aLocalIdx < aNbActive; ++aLocalIdx) + { + const int aBaseVtxIdx = aActiveVertices.Value(aLocalIdx).second; + if (aCanonicalVertex.IsBound(aBaseVtxIdx)) + continue; + + const gp_Pnt aBaseVtxPnt = + BRepGraph_Tool::Vertex::Pnt(theGraph, BRepGraph_VertexId(aBaseVtxIdx)); + const double aBaseVtxTol = + BRepGraph_Tool::Vertex::Tolerance(theGraph, BRepGraph_VertexId(aBaseVtxIdx)); + + aTree.ForEachInRange(aBaseVtxPnt, aTol, [&](size_t theResultIdx) { + const int anArrayIdx = static_cast(theResultIdx) - 1; + if (anArrayIdx <= aLocalIdx) + return; // skip self and already-processed + + const int aCandVtxIdx = aActiveVertices.Value(anArrayIdx).second; + if (aCanonicalVertex.IsBound(aCandVtxIdx)) + return; + + const double aCandVtxTol = + BRepGraph_Tool::Vertex::Tolerance(theGraph, BRepGraph_VertexId(aCandVtxIdx)); + const double aMaxTol = std::max(aBaseVtxTol, aCandVtxTol); + if (aBaseVtxPnt.Distance( + BRepGraph_Tool::Vertex::Pnt(theGraph, BRepGraph_VertexId(aCandVtxIdx))) + <= aMaxTol) + { + aCanonicalVertex.Bind(aCandVtxIdx, aBaseVtxIdx); + } + }); + } + + if (!theOptions.AnalyzeOnly) + { + // Redirect edge vertex references and mark non-canonical vertices as removed. + for (NCollection_DataMap::Iterator anIt(aCanonicalVertex); anIt.More(); anIt.Next()) + { + const int anOldIdx = anIt.Key(); + const int aCanonicalIdx = anIt.Value(); + const BRepGraph_NodeId anOldId = BRepGraph_VertexId(anOldIdx); + const BRepGraph_NodeId aCanonId = BRepGraph_VertexId(aCanonicalIdx); + + // Update edges referencing the old vertex. + for (int anEdgeIdx = 0; anEdgeIdx < theGraph.Topo().Edges().Nb(); ++anEdgeIdx) + { + BRepGraph_MutGuard anEdge = + theGraph.Builder().MutEdge(BRepGraph_EdgeId(anEdgeIdx)); + if (anEdge->IsRemoved) + continue; + // Resolve current vertex def ids through ref entries and update them. + if (anEdge->StartVertexRefId.IsValid()) + { + BRepGraph_MutGuard aStartRef = + theGraph.Builder().MutVertexRef(anEdge->StartVertexRefId); + if (aStartRef->VertexDefId == BRepGraph_VertexId(anOldId.Index)) + aStartRef->VertexDefId = BRepGraph_VertexId(aCanonId.Index); + } + if (anEdge->EndVertexRefId.IsValid()) + { + BRepGraph_MutGuard anEndRef = + theGraph.Builder().MutVertexRef(anEdge->EndVertexRefId); + if (anEndRef->VertexDefId == BRepGraph_VertexId(anOldId.Index)) + anEndRef->VertexDefId = BRepGraph_VertexId(aCanonId.Index); + } + } + + // Mark non-canonical as removed. + theGraph.Builder().RemoveNode(anOldId, aCanonId); + + NCollection_Vector aRepl; + aRepl.Append(aCanonId); + theGraph.History().Record(TCollection_AsciiString("Dedup:MergeVertex"), anOldId, aRepl); + ++aResult.NbHistoryRecords; + ++aResult.NbMergedVertices; + } + } + else + { + aResult.NbMergedVertices = aCanonicalVertex.Size(); + } + } + + // Phase 2: Edge Merging. + { + // Key: (canonical Curve3d pointer, canonical StartVertexDefId, canonical EndVertexDefId). + struct EdgeKey + { + const Geom_Curve* CurvePtr; + int StartVtx; + int EndVtx; + + bool operator==(const EdgeKey& theOther) const + { + return CurvePtr == theOther.CurvePtr && StartVtx == theOther.StartVtx + && EndVtx == theOther.EndVtx; + } + }; + + struct EdgeKeyHasher + { + size_t operator()(const EdgeKey& theKey) const noexcept + { + size_t aCombination[3]; + aCombination[0] = std::hash{}(theKey.CurvePtr); + aCombination[1] = opencascade::hash(theKey.StartVtx); + aCombination[2] = opencascade::hash(theKey.EndVtx); + return opencascade::hashBytes(aCombination, sizeof(aCombination)); + } + + bool operator()(const EdgeKey& theA, const EdgeKey& theB) const { return theA == theB; } + }; + + NCollection_DataMap, EdgeKeyHasher> anEdgeGroups( + std::max(1, theGraph.Topo().Edges().Nb()), + aTmpAlloc); + + for (int anEdgeIdx = 0; anEdgeIdx < theGraph.Topo().Edges().Nb(); ++anEdgeIdx) + { + const BRepGraph_EdgeId anEdgeId(anEdgeIdx); + const BRepGraphInc::EdgeDef& anEdge = theGraph.Topo().Edges().Definition(anEdgeId); + if (anEdge.IsRemoved || !BRepGraph_Tool::Edge::HasCurve(theGraph, anEdgeId)) + continue; + + // Use canonical (forward) key: use raw pointer as a stable identity. + EdgeKey aKey; + aKey.CurvePtr = BRepGraph_Tool::Edge::Curve(theGraph, anEdgeId).get(); + const BRepGraph_VertexId aStartVtxId = + anEdge.StartVertexRefId.IsValid() + ? theGraph.Refs().Vertices().Entry(anEdge.StartVertexRefId).VertexDefId + : BRepGraph_VertexId(); + const BRepGraph_VertexId anEndVtxId = + anEdge.EndVertexRefId.IsValid() + ? theGraph.Refs().Vertices().Entry(anEdge.EndVertexRefId).VertexDefId + : BRepGraph_VertexId(); + aKey.StartVtx = aStartVtxId.IsValid() ? aStartVtxId.Index : -1; + aKey.EndVtx = anEndVtxId.IsValid() ? anEndVtxId.Index : -1; + + // Normalize: always use min vertex index first for undirected matching. + if (aKey.StartVtx > aKey.EndVtx) + std::swap(aKey.StartVtx, aKey.EndVtx); + + anEdgeGroups.TryBind(aKey, NCollection_Vector()); + anEdgeGroups.ChangeFind(aKey).Append(anEdgeIdx); + } + + NCollection_DataMap aCanonicalEdge(std::max(1, theGraph.Topo().Edges().Nb()), + aTmpAlloc); + + for (NCollection_DataMap, EdgeKeyHasher>::Iterator aGroupIter( + anEdgeGroups); + aGroupIter.More(); + aGroupIter.Next()) + { + const NCollection_Vector& aGroup = aGroupIter.Value(); + if (aGroup.Length() < 2) + continue; + + const int aCanonIdx = aGroup.Value(0); + const BRepGraphInc::EdgeDef& aCanonEdge = + theGraph.Topo().Edges().Definition(BRepGraph_EdgeId(aCanonIdx)); + + for (int aCandIter = 1; aCandIter < aGroup.Length(); ++aCandIter) + { + const int aCandIdx = aGroup.Value(aCandIter); + const BRepGraphInc::EdgeDef& aCandEdge = + theGraph.Topo().Edges().Definition(BRepGraph_EdgeId(aCandIdx)); + + // Compare parameter ranges within tolerance. + if (std::abs(aCanonEdge.ParamFirst - aCandEdge.ParamFirst) > theOptions.CompTolerance) + continue; + if (std::abs(aCanonEdge.ParamLast - aCandEdge.ParamLast) > theOptions.CompTolerance) + continue; + + // Check tolerance compatibility. + if (aCandEdge.Tolerance > aCanonEdge.Tolerance * 10.0) + continue; + + aCanonicalEdge.Bind(aCandIdx, aCanonIdx); + } + } + + if (!theOptions.AnalyzeOnly) + { + for (NCollection_DataMap::Iterator anIt(aCanonicalEdge); anIt.More(); anIt.Next()) + { + const int anOldIdx = anIt.Key(); + const int aCanonicalIdx = anIt.Value(); + const BRepGraph_NodeId anOldId = BRepGraph_EdgeId(anOldIdx); + const BRepGraph_NodeId aCanonId = BRepGraph_EdgeId(aCanonicalIdx); + const BRepGraph_EdgeId anOldEdgeId(anOldIdx); + const BRepGraph_EdgeId aCanonEdgeId(aCanonicalIdx); + + // Determine if the non-canonical edge is reversed relative to canonical. + const BRepGraphInc::EdgeDef& aCanonEdge = theGraph.Topo().Edges().Definition(aCanonEdgeId); + const BRepGraphInc::EdgeDef& anOldEdge = theGraph.Topo().Edges().Definition(anOldEdgeId); + // Resolve vertex def ids for reversal check. + const BRepGraph_NodeId aCanonStart = + aCanonEdge.StartVertexRefId.IsValid() + ? BRepGraph_VertexId( + theGraph.Refs().Vertices().Entry(aCanonEdge.StartVertexRefId).VertexDefId.Index) + : BRepGraph_NodeId(); + const BRepGraph_NodeId aCanonEnd = + aCanonEdge.EndVertexRefId.IsValid() + ? BRepGraph_VertexId( + theGraph.Refs().Vertices().Entry(aCanonEdge.EndVertexRefId).VertexDefId.Index) + : BRepGraph_NodeId(); + const BRepGraph_NodeId anOldStart = + anOldEdge.StartVertexRefId.IsValid() + ? BRepGraph_VertexId( + theGraph.Refs().Vertices().Entry(anOldEdge.StartVertexRefId).VertexDefId.Index) + : BRepGraph_NodeId(); + const BRepGraph_NodeId anOldEnd = + anOldEdge.EndVertexRefId.IsValid() + ? BRepGraph_VertexId( + theGraph.Refs().Vertices().Entry(anOldEdge.EndVertexRefId).VertexDefId.Index) + : BRepGraph_NodeId(); + const bool isReversed = (aCanonStart == anOldEnd && aCanonEnd == anOldStart); + + // Replace in wires. + const NCollection_Vector& aWires = + theGraph.Topo().Edges().Wires(anOldEdgeId); + for (int aWireIter = 0; aWireIter < aWires.Length(); ++aWireIter) + { + theGraph.Builder().ReplaceEdgeInWire(aWires.Value(aWireIter), + anOldEdgeId, + aCanonEdgeId, + isReversed); + } + + // Transfer PCurves via CoEdges (skip duplicates). + // When the old edge is reversed relative to canonical, invert orientation + // so duplicate detection matches correctly against the canonical's CoEdges. + const NCollection_Vector& aOldCoEdges = + theGraph.Topo().Edges().CoEdges(anOldEdgeId); + const NCollection_Vector& aCanonCoEdges = + theGraph.Topo().Edges().CoEdges(aCanonEdgeId); + for (int aCEIdx = 0; aCEIdx < aOldCoEdges.Length(); ++aCEIdx) + { + const BRepGraphInc::CoEdgeDef& aOldCE = + theGraph.Topo().CoEdges().Definition(aOldCoEdges.Value(aCEIdx)); + if (!aOldCE.Curve2DRepId.IsValid()) + continue; + + TopAbs_Orientation aTransferOri = aOldCE.Sense; + if (isReversed) + { + if (aTransferOri == TopAbs_FORWARD) + aTransferOri = TopAbs_REVERSED; + else if (aTransferOri == TopAbs_REVERSED) + aTransferOri = TopAbs_FORWARD; + } + + // Check if canonical edge already has a CoEdge for this face+orientation. + bool aAlreadyHas = false; + for (int aCanonCEIdx = 0; aCanonCEIdx < aCanonCoEdges.Length(); ++aCanonCEIdx) + { + const BRepGraphInc::CoEdgeDef& aCanonCE = + theGraph.Topo().CoEdges().Definition(aCanonCoEdges.Value(aCanonCEIdx)); + if (aCanonCE.FaceDefId == aOldCE.FaceDefId && aCanonCE.Sense == aTransferOri) + { + aAlreadyHas = true; + break; + } + } + + if (!aAlreadyHas) + { + const occ::handle& aOldPCurve = + BRepGraph_Tool::CoEdge::PCurve(theGraph, aOldCoEdges.Value(aCEIdx)); + theGraph.Builder().AddPCurveToEdge(BRepGraph_EdgeId::FromNodeId(aCanonId), + aOldCE.FaceDefId, + aOldPCurve, + aOldCE.ParamFirst, + aOldCE.ParamLast, + aTransferOri); + } + } + + theGraph.Builder().RemoveNode(anOldId, aCanonId); + + NCollection_Vector aRepl; + aRepl.Append(aCanonId); + theGraph.History().Record(TCollection_AsciiString("Dedup:MergeEdge"), anOldId, aRepl); + ++aResult.NbHistoryRecords; + ++aResult.NbMergedEdges; + } + } + else + { + aResult.NbMergedEdges = aCanonicalEdge.Size(); + } + } + + // Phase 3: Wire Merging. + { + // Hash wire by its ordered coedge sequence (edge index + sense from CoEdgeDef). + struct WireHash + { + size_t operator()(const BRepGraph_WireId theWireId, const BRepGraph& theGraph) const + { + size_t aHash = 0; + forWireCoEdgeRefEntries(theGraph, theWireId, [&](const BRepGraphInc::CoEdgeRef& aCR) { + const BRepGraphInc::CoEdgeDef& aCoEdge = + theGraph.Topo().CoEdges().Definition(aCR.CoEdgeDefId); + size_t aEntryHash[2]; + aEntryHash[0] = opencascade::hash(aCoEdge.EdgeDefId); + aEntryHash[1] = opencascade::hash(static_cast(aCoEdge.Sense)); + aHash ^= opencascade::hashBytes(aEntryHash, sizeof(aEntryHash)) + 0x9e3779b9 + + (aHash << 6) + (aHash >> 2); + }); + return aHash; + } + }; + + auto wiresEqual = [&](const BRepGraph_WireId theA, const BRepGraph_WireId theB) -> bool { + NCollection_Vector aWireACoEdges; + NCollection_Vector aWireBCoEdges; + forWireCoEdgeRefEntries(theGraph, theA, [&](const BRepGraphInc::CoEdgeRef& aCR) { + aWireACoEdges.Append(aCR.CoEdgeDefId); + }); + forWireCoEdgeRefEntries(theGraph, theB, [&](const BRepGraphInc::CoEdgeRef& aCR) { + aWireBCoEdges.Append(aCR.CoEdgeDefId); + }); + + if (aWireACoEdges.Length() != aWireBCoEdges.Length()) + return false; + for (int anIdx = 0; anIdx < aWireACoEdges.Length(); ++anIdx) + { + const BRepGraphInc::CoEdgeDef& aCoEdgeA = + theGraph.Topo().CoEdges().Definition(aWireACoEdges.Value(anIdx)); + const BRepGraphInc::CoEdgeDef& aCoEdgeB = + theGraph.Topo().CoEdges().Definition(aWireBCoEdges.Value(anIdx)); + if (aCoEdgeA.EdgeDefId != aCoEdgeB.EdgeDefId || aCoEdgeA.Sense != aCoEdgeB.Sense) + return false; + } + return true; + }; + + NCollection_DataMap> aWireHashBuckets( + std::max(1, theGraph.Topo().Wires().Nb()), + aTmpAlloc); + + WireHash aHasher; + for (int aWireIdx = 0; aWireIdx < theGraph.Topo().Wires().Nb(); ++aWireIdx) + { + const BRepGraphInc::WireDef& aWire = + theGraph.Topo().Wires().Definition(BRepGraph_WireId(aWireIdx)); + if (aWire.IsRemoved) + continue; + const size_t aH = aHasher(BRepGraph_WireId(aWireIdx), theGraph); + aWireHashBuckets.TryBind(aH, NCollection_Vector()); + aWireHashBuckets.ChangeFind(aH).Append(aWireIdx); + } + + NCollection_DataMap aCanonicalWire(std::max(1, theGraph.Topo().Wires().Nb()), + aTmpAlloc); + + for (NCollection_DataMap>::Iterator aBucketIter( + aWireHashBuckets); + aBucketIter.More(); + aBucketIter.Next()) + { + const NCollection_Vector& aBucket = aBucketIter.Value(); + for (int aBaseIdx = 0; aBaseIdx < aBucket.Length(); ++aBaseIdx) + { + const int aBaseWireIdx = aBucket.Value(aBaseIdx); + if (aCanonicalWire.IsBound(aBaseWireIdx)) + continue; + + for (int aCandIdx = aBaseIdx + 1; aCandIdx < aBucket.Length(); ++aCandIdx) + { + const int aCandWireIdx = aBucket.Value(aCandIdx); + if (aCanonicalWire.IsBound(aCandWireIdx)) + continue; + if (wiresEqual(BRepGraph_WireId(aBaseWireIdx), BRepGraph_WireId(aCandWireIdx))) + aCanonicalWire.Bind(aCandWireIdx, aBaseWireIdx); + } + } + } + + if (!theOptions.AnalyzeOnly) + { + // Mark non-canonical wires as removed. + for (NCollection_DataMap::Iterator anIt(aCanonicalWire); anIt.More(); anIt.Next()) + { + const int anOldIdx = anIt.Key(); + const int aCanonicalIdx = anIt.Value(); + const BRepGraph_NodeId anOldId = BRepGraph_WireId(anOldIdx); + const BRepGraph_NodeId aCanonId = BRepGraph_WireId(aCanonicalIdx); + + theGraph.Builder().RemoveNode(anOldId, aCanonId); + + NCollection_Vector aRepl; + aRepl.Append(aCanonId); + theGraph.History().Record(TCollection_AsciiString("Dedup:MergeWire"), anOldId, aRepl); + ++aResult.NbHistoryRecords; + ++aResult.NbMergedWires; + } + } + else + { + aResult.NbMergedWires = aCanonicalWire.Size(); + } + } + + // Phase 4: Face Merging. + { + struct FaceKey + { + const Geom_Surface* SurfPtr; + size_t WireHash; + + bool operator==(const FaceKey& theOther) const + { + return SurfPtr == theOther.SurfPtr && WireHash == theOther.WireHash; + } + }; + + struct FaceKeyHasher + { + size_t operator()(const FaceKey& theKey) const noexcept + { + size_t aCombination[2]; + aCombination[0] = std::hash{}(theKey.SurfPtr); + aCombination[1] = theKey.WireHash; + return opencascade::hashBytes(aCombination, sizeof(aCombination)); + } + + bool operator()(const FaceKey& theA, const FaceKey& theB) const { return theA == theB; } + }; + + auto faceWireHash = [&](int theFaceIdx) -> size_t { + // Collect wire def ids used by this face (via transitional incidence entries). + size_t aHash = 0; + forFaceWireRefEntries(theGraph, + BRepGraph_FaceId(theFaceIdx), + [&](const BRepGraphInc::WireRef& aWR) { + if (aWR.IsOuter) + { + aHash ^= opencascade::hash(aWR.WireDefId); + } + else + { + aHash ^= opencascade::hash(aWR.WireDefId) + 0x9e3779b9; + } + }); + return aHash; + }; + + NCollection_DataMap, FaceKeyHasher> aFaceGroups( + std::max(1, theGraph.Topo().Faces().Nb()), + aTmpAlloc); + + for (int aFaceIdx = 0; aFaceIdx < theGraph.Topo().Faces().Nb(); ++aFaceIdx) + { + const BRepGraph_FaceId aFaceId(aFaceIdx); + const BRepGraphInc::FaceDef& aFace = theGraph.Topo().Faces().Definition(aFaceId); + if (aFace.IsRemoved || !BRepGraph_Tool::Face::HasSurface(theGraph, aFaceId)) + continue; + + FaceKey aKey; + aKey.SurfPtr = BRepGraph_Tool::Face::Surface(theGraph, aFaceId).get(); + aKey.WireHash = faceWireHash(aFaceIdx); + + aFaceGroups.TryBind(aKey, NCollection_Vector()); + aFaceGroups.ChangeFind(aKey).Append(aFaceIdx); + } + + NCollection_DataMap aCanonicalFace(std::max(1, theGraph.Topo().Faces().Nb()), + aTmpAlloc); + + for (NCollection_DataMap, FaceKeyHasher>::Iterator aGroupIter( + aFaceGroups); + aGroupIter.More(); + aGroupIter.Next()) + { + const NCollection_Vector& aGroup = aGroupIter.Value(); + if (aGroup.Length() < 2) + continue; + + const int aCanonIdx = aGroup.Value(0); + const BRepGraphInc::FaceDef& aCanonFace = + theGraph.Topo().Faces().Definition(BRepGraph_FaceId(aCanonIdx)); + + for (int aCandIter = 1; aCandIter < aGroup.Length(); ++aCandIter) + { + const int aCandIdx = aGroup.Value(aCandIter); + const BRepGraphInc::FaceDef& aCandFace = + theGraph.Topo().Faces().Definition(BRepGraph_FaceId(aCandIdx)); + + // Check tolerance compatibility. + if (aCandFace.Tolerance > aCanonFace.Tolerance * 10.0) + continue; + + aCanonicalFace.Bind(aCandIdx, aCanonIdx); + } + } + + if (!theOptions.AnalyzeOnly) + { + for (NCollection_DataMap::Iterator anIt(aCanonicalFace); anIt.More(); anIt.Next()) + { + const int anOldIdx = anIt.Key(); + const int aCanonicalIdx = anIt.Value(); + const BRepGraph_NodeId anOldId = BRepGraph_FaceId(anOldIdx); + const BRepGraph_NodeId aCanonId = BRepGraph_FaceId(aCanonicalIdx); + + theGraph.Builder().RemoveNode(anOldId, aCanonId); + + NCollection_Vector aRepl; + aRepl.Append(aCanonId); + theGraph.History().Record(TCollection_AsciiString("Dedup:MergeFace"), anOldId, aRepl); + ++aResult.NbHistoryRecords; + ++aResult.NbMergedFaces; + } + } + else + { + aResult.NbMergedFaces = aCanonicalFace.Size(); + } + } + + aResult.IsEntityMergeApplied = !theOptions.AnalyzeOnly + && (aResult.NbMergedVertices > 0 || aResult.NbMergedEdges > 0 + || aResult.NbMergedWires > 0 || aResult.NbMergedFaces > 0); + + theGraph.History().SetEnabled(wasHistoryEnabled); + return aResult; +} diff --git a/src/ModelingData/TKBRep/BRepGraph/BRepGraph_Deduplicate.hxx b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_Deduplicate.hxx new file mode 100644 index 0000000000..4ec5a2f8d2 --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_Deduplicate.hxx @@ -0,0 +1,84 @@ +// Copyright (c) 2026 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 _BRepGraph_Deduplicate_HeaderFile +#define _BRepGraph_Deduplicate_HeaderFile + +#include + +#include +#include +#include +#include + +//! @brief Deep geometry deduplication algorithm over an existing BRepGraph. +//! +//! This algorithm canonicalizes deep-equal geometry references (surfaces and +//! 3D curves) using GeomHash hashers. It updates face/edge definition links to +//! canonical geometry nodes and can record lineage in graph history. +//! +//! First implementation intentionally does not merge edge/face definitions yet. +class BRepGraph_Deduplicate +{ +public: + DEFINE_STANDARD_ALLOC + + //! Configuration for graph deduplication run. + struct Options + { + bool AnalyzeOnly = false; + bool HistoryMode = true; + bool MergeEntitiesWhenSafe = false; + double CompTolerance = Precision::Angular(); + double HashTolerance = Precision::Confusion(); + }; + + //! Result counters for diagnostics and tests. + struct Result + { + int NbCanonicalSurfaces = 0; + int NbCanonicalCurves = 0; + int NbSurfaceRewrites = 0; + int NbCurveRewrites = 0; + int NbNullifiedSurfaces = 0; + int NbNullifiedCurves = 0; + int NbHistoryRecords = 0; + bool IsEntityMergeApplied = false; + + //! Topology definition merge counters (active when MergeEntitiesWhenSafe = true). + int NbMergedVertices = 0; + int NbMergedEdges = 0; + int NbMergedWires = 0; + int NbMergedFaces = 0; + + NCollection_Vector AffectedFaces; //!< Faces whose SurfNodeId changed. + NCollection_Vector AffectedEdges; //!< Edges whose CurveNodeId changed. + }; + + //! Run deduplication on a built graph. + //! @param[in,out] theGraph graph to update + //! @return dedup statistics + [[nodiscard]] Standard_EXPORT static Result Perform(BRepGraph& theGraph); + + //! Run deduplication on a built graph. + //! @param[in,out] theGraph graph to update + //! @param[in] theOptions dedup configuration + //! @return dedup statistics + [[nodiscard]] Standard_EXPORT static Result Perform(BRepGraph& theGraph, + const Options& theOptions); + +private: + BRepGraph_Deduplicate() = delete; +}; + +#endif // _BRepGraph_Deduplicate_HeaderFile diff --git a/src/ModelingData/TKBRep/BRepGraph/BRepGraph_DeferredScope.hxx b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_DeferredScope.hxx new file mode 100644 index 0000000000..43d1b34973 --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_DeferredScope.hxx @@ -0,0 +1,79 @@ +// Copyright (c) 2026 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 _BRepGraph_DeferredScope_HeaderFile +#define _BRepGraph_DeferredScope_HeaderFile + +#include +#include + +//! @brief RAII guard for batch mutation scopes with deferred invalidation. +//! +//! Activates deferred invalidation on construction and flushes it on destruction, +//! followed by CommitMutation validation. Guarantees exception-safe cleanup: +//! when this guard owns deferred mode, it is always closed and boundary checks +//! are executed at scope exit. EndDeferredInvalidation() batch-propagates +//! SubtreeGen upward, then CommitMutation() validates reverse-index consistency +//! and active-entity counts. +//! +//! Re-entrant: if deferred mode is already active (e.g., nested guard), +//! the inner guard is a no-op. Only the outermost guard flushes and commits, +//! so nested scopes do not create separate transaction or validation boundaries. +//! +//! @warning This guard batches invalidation and propagation; it is NOT a +//! transaction and does not serialize mutation bodies. Concurrent `Mut*()` +//! usage still requires external synchronization for the whole guarded scope +//! (for example, a mutex protecting exclusive Builder() access until the guard +//! is destroyed). +//! +//! Usage: +//! @code +//! { +//! BRepGraph_DeferredScope aScope(theGraph); +//! for (int i = 0; i < N; ++i) +//! { +//! // mutations +//! } +//! } // EndDeferredInvalidation + CommitMutation called here +//! @endcode +class BRepGraph_DeferredScope +{ +public: + //! Begin deferred invalidation if not already active. + explicit BRepGraph_DeferredScope(BRepGraph& theGraph) + : myGraph(theGraph), + myOwnsScope(!theGraph.Builder().IsDeferredMode()) + { + if (myOwnsScope) + myGraph.Builder().BeginDeferredInvalidation(); + } + + //! End deferred invalidation and validate reverse index + active counts. + ~BRepGraph_DeferredScope() + { + if (myOwnsScope) + { + myGraph.Builder().EndDeferredInvalidation(); + myGraph.Builder().CommitMutation(); + } + } + + BRepGraph_DeferredScope(const BRepGraph_DeferredScope&) = delete; + BRepGraph_DeferredScope& operator=(const BRepGraph_DeferredScope&) = delete; + +private: + BRepGraph& myGraph; + bool myOwnsScope; +}; + +#endif // _BRepGraph_DeferredScope_HeaderFile diff --git a/src/ModelingData/TKBRep/BRepGraph/BRepGraph_DefsIterator.hxx b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_DefsIterator.hxx new file mode 100644 index 0000000000..46dba1de6e --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_DefsIterator.hxx @@ -0,0 +1,556 @@ +// Copyright (c) 2026 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 _BRepGraph_DefsIterator_HeaderFile +#define _BRepGraph_DefsIterator_HeaderFile + +#include +#include +#include + +#include + +//! @brief Single-level typed iterators over active child definitions. +//! +//! These iterators do not recurse and do not accumulate location/orientation. +//! They provide a lightweight alternative to BRepGraph_ChildExplorer when the +//! caller only needs the direct active children of one parent definition. +//! +//! The iterator skips: +//! - removed reference entries +//! - invalid child ids +//! - removed child definitions +namespace BRepGraph_DefsIterator +{ +template +struct BaseTraits +{ + using ParentId = ParentIdT; + using RefId = RefIdT; + using ChildId = ChildIdT; + using ChildDef = ChildDefT; +}; + +template +inline const BRepGraphInc::BaseDef* childBaseDef(const BRepGraph& theGraph, + const ChildIdT theChildId) +{ + return theGraph.Topo().Gen().TopoEntity(BRepGraph_NodeId(theChildId)); +} + +inline const BRepGraphInc::BaseDef* childBaseDef(const BRepGraph& theGraph, + const BRepGraph_NodeId theChildId) +{ + return theGraph.Topo().Gen().TopoEntity(theChildId); +} + +struct ShellOfSolidTraits : public BaseTraits +{ + static bool IsParentValid(const BRepGraph& theGraph, const ParentId theParent) + { + return theParent.IsValid(theGraph.Topo().Solids().Nb()) + && !theGraph.Topo().Solids().Definition(theParent).IsRemoved; + } + + static const NCollection_Vector& RefIds(const BRepGraph& theGraph, + const ParentId theParent) + { + return theGraph.Topo().Solids().Definition(theParent).ShellRefIds; + } + + static const BRepGraphInc::ShellRef& Ref(const BRepGraph& theGraph, const RefId theRefId) + { + return theGraph.Refs().Shells().Entry(theRefId); + } + + static ChildId ChildIdOf(const BRepGraph&, const BRepGraphInc::ShellRef& theRef) + { + return theRef.ShellDefId; + } + + static const ChildDef& Child(const BRepGraph& theGraph, const ChildId theChildId) + { + return theGraph.Topo().Shells().Definition(theChildId); + } +}; + +struct FaceOfShellTraits : public BaseTraits +{ + static bool IsParentValid(const BRepGraph& theGraph, const ParentId theParent) + { + return theParent.IsValid(theGraph.Topo().Shells().Nb()) + && !theGraph.Topo().Shells().Definition(theParent).IsRemoved; + } + + static const NCollection_Vector& RefIds(const BRepGraph& theGraph, + const ParentId theParent) + { + return theGraph.Topo().Shells().Definition(theParent).FaceRefIds; + } + + static const BRepGraphInc::FaceRef& Ref(const BRepGraph& theGraph, const RefId theRefId) + { + return theGraph.Refs().Faces().Entry(theRefId); + } + + static ChildId ChildIdOf(const BRepGraph&, const BRepGraphInc::FaceRef& theRef) + { + return theRef.FaceDefId; + } + + static const ChildDef& Child(const BRepGraph& theGraph, const ChildId theChildId) + { + return theGraph.Topo().Faces().Definition(theChildId); + } +}; + +struct WireOfFaceTraits : public BaseTraits +{ + static bool IsParentValid(const BRepGraph& theGraph, const ParentId theParent) + { + return theParent.IsValid(theGraph.Topo().Faces().Nb()) + && !theGraph.Topo().Faces().Definition(theParent).IsRemoved; + } + + static const NCollection_Vector& RefIds(const BRepGraph& theGraph, + const ParentId theParent) + { + return theGraph.Topo().Faces().Definition(theParent).WireRefIds; + } + + static const BRepGraphInc::WireRef& Ref(const BRepGraph& theGraph, const RefId theRefId) + { + return theGraph.Refs().Wires().Entry(theRefId); + } + + static ChildId ChildIdOf(const BRepGraph&, const BRepGraphInc::WireRef& theRef) + { + return theRef.WireDefId; + } + + static const ChildDef& Child(const BRepGraph& theGraph, const ChildId theChildId) + { + return theGraph.Topo().Wires().Definition(theChildId); + } +}; + +struct VertexOfFaceTraits : public BaseTraits +{ + static bool IsParentValid(const BRepGraph& theGraph, const ParentId theParent) + { + return theParent.IsValid(theGraph.Topo().Faces().Nb()) + && !theGraph.Topo().Faces().Definition(theParent).IsRemoved; + } + + static const NCollection_Vector& RefIds(const BRepGraph& theGraph, + const ParentId theParent) + { + return theGraph.Topo().Faces().Definition(theParent).VertexRefIds; + } + + static const BRepGraphInc::VertexRef& Ref(const BRepGraph& theGraph, const RefId theRefId) + { + return theGraph.Refs().Vertices().Entry(theRefId); + } + + static ChildId ChildIdOf(const BRepGraph&, const BRepGraphInc::VertexRef& theRef) + { + return theRef.VertexDefId; + } + + static const ChildDef& Child(const BRepGraph& theGraph, const ChildId theChildId) + { + return theGraph.Topo().Vertices().Definition(theChildId); + } +}; + +struct CoEdgeOfWireTraits : public BaseTraits +{ + static bool IsParentValid(const BRepGraph& theGraph, const ParentId theParent) + { + return theParent.IsValid(theGraph.Topo().Wires().Nb()) + && !theGraph.Topo().Wires().Definition(theParent).IsRemoved; + } + + static const NCollection_Vector& RefIds(const BRepGraph& theGraph, + const ParentId theParent) + { + return theGraph.Topo().Wires().Definition(theParent).CoEdgeRefIds; + } + + static const BRepGraphInc::CoEdgeRef& Ref(const BRepGraph& theGraph, const RefId theRefId) + { + return theGraph.Refs().CoEdges().Entry(theRefId); + } + + static ChildId ChildIdOf(const BRepGraph&, const BRepGraphInc::CoEdgeRef& theRef) + { + return theRef.CoEdgeDefId; + } + + static const ChildDef& Child(const BRepGraph& theGraph, const ChildId theChildId) + { + return theGraph.Topo().CoEdges().Definition(theChildId); + } +}; + +struct EdgeOfWireTraits : public BaseTraits +{ + static bool IsParentValid(const BRepGraph& theGraph, const ParentId theParent) + { + return theParent.IsValid(theGraph.Topo().Wires().Nb()) + && !theGraph.Topo().Wires().Definition(theParent).IsRemoved; + } + + static const NCollection_Vector& RefIds(const BRepGraph& theGraph, + const ParentId theParent) + { + return theGraph.Topo().Wires().Definition(theParent).CoEdgeRefIds; + } + + static const BRepGraphInc::CoEdgeRef& Ref(const BRepGraph& theGraph, const RefId theRefId) + { + return theGraph.Refs().CoEdges().Entry(theRefId); + } + + static ChildId ChildIdOf(const BRepGraph& theGraph, const BRepGraphInc::CoEdgeRef& theRef) + { + const BRepGraph_CoEdgeId aCoEdgeId = theRef.CoEdgeDefId; + if (!aCoEdgeId.IsValid(theGraph.Topo().CoEdges().Nb())) + { + return ChildId(); + } + + const BRepGraphInc::CoEdgeDef& aCoEdge = theGraph.Topo().CoEdges().Definition(aCoEdgeId); + if (aCoEdge.IsRemoved) + { + return ChildId(); + } + return aCoEdge.EdgeDefId; + } + + static const ChildDef& Child(const BRepGraph& theGraph, const ChildId theChildId) + { + return theGraph.Topo().Edges().Definition(theChildId); + } +}; + +struct SolidOfCompSolidTraits : public BaseTraits +{ + static bool IsParentValid(const BRepGraph& theGraph, const ParentId theParent) + { + return theParent.IsValid(theGraph.Topo().CompSolids().Nb()) + && !theGraph.Topo().CompSolids().Definition(theParent).IsRemoved; + } + + static const NCollection_Vector& RefIds(const BRepGraph& theGraph, + const ParentId theParent) + { + return theGraph.Topo().CompSolids().Definition(theParent).SolidRefIds; + } + + static const BRepGraphInc::SolidRef& Ref(const BRepGraph& theGraph, const RefId theRefId) + { + return theGraph.Refs().Solids().Entry(theRefId); + } + + static ChildId ChildIdOf(const BRepGraph&, const BRepGraphInc::SolidRef& theRef) + { + return theRef.SolidDefId; + } + + static const ChildDef& Child(const BRepGraph& theGraph, const ChildId theChildId) + { + return theGraph.Topo().Solids().Definition(theChildId); + } +}; + +struct ChildOfCompoundTraits : public BaseTraits +{ + static bool IsParentValid(const BRepGraph& theGraph, const ParentId theParent) + { + return theParent.IsValid(theGraph.Topo().Compounds().Nb()) + && !theGraph.Topo().Compounds().Definition(theParent).IsRemoved; + } + + static const NCollection_Vector& RefIds(const BRepGraph& theGraph, + const ParentId theParent) + { + return theGraph.Topo().Compounds().Definition(theParent).ChildRefIds; + } + + static const BRepGraphInc::ChildRef& Ref(const BRepGraph& theGraph, const RefId theRefId) + { + return theGraph.Refs().Children().Entry(theRefId); + } + + static ChildId ChildIdOf(const BRepGraph&, const BRepGraphInc::ChildRef& theRef) + { + return theRef.ChildDefId; + } + + static const ChildDef& Child(const BRepGraph& theGraph, const ChildId theChildId) + { + return *theGraph.Topo().Gen().TopoEntity(theChildId); + } +}; + +struct OccurrenceOfProductTraits : public BaseTraits +{ + static bool IsParentValid(const BRepGraph& theGraph, const ParentId theParent) + { + return theParent.IsValid(theGraph.Topo().Products().Nb()) + && !theGraph.Topo().Products().Definition(theParent).IsRemoved; + } + + static const NCollection_Vector& RefIds(const BRepGraph& theGraph, + const ParentId theParent) + { + return theGraph.Topo().Products().Definition(theParent).OccurrenceRefIds; + } + + static const BRepGraphInc::OccurrenceRef& Ref(const BRepGraph& theGraph, const RefId theRefId) + { + return theGraph.Refs().Occurrences().Entry(theRefId); + } + + static ChildId ChildIdOf(const BRepGraph&, const BRepGraphInc::OccurrenceRef& theRef) + { + return theRef.OccurrenceDefId; + } + + static const ChildDef& Child(const BRepGraph& theGraph, const ChildId theChildId) + { + return theGraph.Topo().Occurrences().Definition(theChildId); + } +}; + +template +class DefsOfParent +{ +public: + using ParentId = typename TraitsT::ParentId; + using RefId = typename TraitsT::RefId; + using ChildId = typename TraitsT::ChildId; + using ChildDef = typename TraitsT::ChildDef; + + DefsOfParent(const BRepGraph& theGraph, const ParentId theParent) + : myGraph(theGraph) + { + if (!TraitsT::IsParentValid(theGraph, theParent)) + { + return; + } + + myRefIds = &TraitsT::RefIds(theGraph, theParent); + myLength = myRefIds->Length(); + skipRemoved(); + } + + [[nodiscard]] bool More() const { return myRefIds != nullptr && myIndex < myLength; } + + void Next() + { + ++myIndex; + skipRemoved(); + } + + [[nodiscard]] ChildId CurrentId() const + { + return TraitsT::ChildIdOf(myGraph, TraitsT::Ref(myGraph, myRefIds->Value(myIndex))); + } + + [[nodiscard]] const ChildDef& Current() const { return TraitsT::Child(myGraph, CurrentId()); } + + [[nodiscard]] int Index() const { return myIndex; } + + //! Returns an STL-compatible iterator for range-based for loops. + NCollection_ForwardRangeIterator begin() + { + return NCollection_ForwardRangeIterator(this); + } + + //! Returns a sentinel marking the end of iteration. + NCollection_ForwardRangeSentinel end() const { return NCollection_ForwardRangeSentinel{}; } + +private: + void skipRemoved() + { + while (myRefIds != nullptr && myIndex < myLength) + { + const auto& aRef = TraitsT::Ref(myGraph, myRefIds->Value(myIndex)); + if (!aRef.IsRemoved) + { + const BRepGraphInc::BaseDef* aChildDef = + childBaseDef(myGraph, TraitsT::ChildIdOf(myGraph, aRef)); + if (aChildDef != nullptr && !aChildDef->IsRemoved) + { + return; + } + } + ++myIndex; + } + } + + const BRepGraph& myGraph; + const NCollection_Vector* myRefIds = nullptr; + int myIndex = 0; + int myLength = 0; +}; + +//! @brief Direct active vertex children of an edge. +//! +//! Iteration order is start vertex, end vertex, then internal/external vertices. +class DefsVertexOfEdge +{ +public: + using ChildId = BRepGraph_VertexId; + using ChildDef = BRepGraphInc::VertexDef; + + DefsVertexOfEdge(const BRepGraph& theGraph, const BRepGraph_EdgeId theEdgeId) + : myGraph(theGraph) + { + if (!theEdgeId.IsValid(theGraph.Topo().Edges().Nb()) + || theGraph.Topo().Edges().Definition(theEdgeId).IsRemoved) + { + return; + } + + myEdge = &theGraph.Topo().Edges().Definition(theEdgeId); + myLength = 2 + myEdge->InternalVertexRefIds.Length(); + skipRemoved(); + } + + [[nodiscard]] bool More() const { return myEdge != nullptr && myIndex < myLength; } + + void Next() + { + ++myIndex; + skipRemoved(); + } + + [[nodiscard]] ChildId CurrentId() const + { + return myGraph.Refs().Vertices().Entry(currentRefId()).VertexDefId; + } + + [[nodiscard]] const ChildDef& Current() const + { + return myGraph.Topo().Vertices().Definition(CurrentId()); + } + + [[nodiscard]] int Index() const { return myIndex; } + + //! Returns an STL-compatible iterator for range-based for loops. + NCollection_ForwardRangeIterator begin() + { + return NCollection_ForwardRangeIterator(this); + } + + //! Returns a sentinel marking the end of iteration. + NCollection_ForwardRangeSentinel end() const { return NCollection_ForwardRangeSentinel{}; } + +private: + [[nodiscard]] BRepGraph_VertexRefId refIdAt(const int theIndex) const + { + if (theIndex == 0) + { + return myEdge->StartVertexRefId; + } + if (theIndex == 1) + { + return myEdge->EndVertexRefId; + } + return myEdge->InternalVertexRefIds.Value(theIndex - 2); + } + + [[nodiscard]] BRepGraph_VertexRefId currentRefId() const { return refIdAt(myIndex); } + + void skipRemoved() + { + while (myEdge != nullptr && myIndex < myLength) + { + const BRepGraph_VertexRefId aRefId = refIdAt(myIndex); + if (aRefId.IsValid()) + { + const BRepGraphInc::VertexRef& aRef = myGraph.Refs().Vertices().Entry(aRefId); + if (!aRef.IsRemoved) + { + const BRepGraphInc::BaseDef* aChildDef = + myGraph.Topo().Gen().TopoEntity(BRepGraph_NodeId(aRef.VertexDefId)); + if (aChildDef != nullptr && !aChildDef->IsRemoved) + { + return; + } + } + } + ++myIndex; + } + } + + const BRepGraph& myGraph; + const BRepGraphInc::EdgeDef* myEdge = nullptr; + int myIndex = 0; + int myLength = 0; +}; + +} // namespace BRepGraph_DefsIterator + +using BRepGraph_DefsShellOfSolid = + BRepGraph_DefsIterator::DefsOfParent; +using BRepGraph_DefsFaceOfShell = + BRepGraph_DefsIterator::DefsOfParent; +using BRepGraph_DefsEdgeOfWire = + BRepGraph_DefsIterator::DefsOfParent; +using BRepGraph_DefsWireOfFace = + BRepGraph_DefsIterator::DefsOfParent; +using BRepGraph_DefsVertexOfFace = + BRepGraph_DefsIterator::DefsOfParent; +using BRepGraph_DefsCoEdgeOfWire = + BRepGraph_DefsIterator::DefsOfParent; +using BRepGraph_DefsSolidOfCompSolid = + BRepGraph_DefsIterator::DefsOfParent; +using BRepGraph_DefsChildOfCompound = + BRepGraph_DefsIterator::DefsOfParent; +using BRepGraph_DefsOccurrenceOfProduct = + BRepGraph_DefsIterator::DefsOfParent; +using BRepGraph_DefsVertexOfEdge = BRepGraph_DefsIterator::DefsVertexOfEdge; + +#endif // _BRepGraph_DefsIterator_HeaderFile \ No newline at end of file diff --git a/src/ModelingData/TKBRep/BRepGraph/BRepGraph_History.cxx b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_History.cxx new file mode 100644 index 0000000000..4aa79a0624 --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_History.cxx @@ -0,0 +1,278 @@ +// Copyright (c) 2026 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 + +namespace +{ +constexpr int THE_HISTORY_RECORD_BLOCK_SIZE = 32; +constexpr int THE_HISTORY_REPLACEMENT_BLOCK_SIZE = 4; +constexpr int THE_HISTORY_FILTERED_BLOCK_SIZE = 4; +constexpr int THE_HISTORY_DERIVED_BLOCK_SIZE = 8; +constexpr int THE_HISTORY_QUEUE_BLOCK_SIZE = 32; +} // namespace + +//================================================================================================= + +void BRepGraph_History::SetAllocator(const occ::handle& theAlloc) +{ + Standard_ASSERT_VOID(myRecords.IsEmpty(), + "SetAllocator: must be called before any records are added"); + myAllocator = theAlloc; + // Reconstruct internal containers with the new allocator. + myRecords = + NCollection_Vector(THE_HISTORY_RECORD_BLOCK_SIZE, myAllocator); + myDerivedToOriginal = NCollection_DataMap(1, myAllocator); + myOriginalToDerived = + NCollection_DataMap>(1, myAllocator); +} + +//================================================================================================= + +void BRepGraph_History::Record(const TCollection_AsciiString& theOpLabel, + const BRepGraph_NodeId theOriginal, + const NCollection_Vector& theReplacements) +{ + if (!myEnabled) + { + return; + } + + // Append a new history record. + BRepGraph_HistoryRecord aRecord; + aRecord.OperationName = theOpLabel; + aRecord.SequenceNumber = myRecords.Length(); + if (!myAllocator.IsNull()) + { + aRecord.Mapping = + NCollection_DataMap>(1, myAllocator); + } + aRecord.Mapping.Bind(theOriginal, theReplacements); + myRecords.Append(std::move(aRecord)); + + // Populate the bidirectional lookup maps. + // Skip self-referencing entries (aDerived == theOriginal) to avoid overwriting + // prior chain links in the reverse map. + NCollection_Vector aFilteredReplacements(THE_HISTORY_FILTERED_BLOCK_SIZE, + myAllocator); + for (const BRepGraph_NodeId& aDerived : theReplacements) + { + if (aDerived != theOriginal) + { + myDerivedToOriginal.Bind(aDerived, theOriginal); + aFilteredReplacements.Append(aDerived); + } + } + + if (aFilteredReplacements.IsEmpty()) + return; + + if (myOriginalToDerived.IsBound(theOriginal)) + { + NCollection_Vector& aDerivedVec = myOriginalToDerived.ChangeFind(theOriginal); + for (const BRepGraph_NodeId& aDerived : aFilteredReplacements) + { + aDerivedVec.Append(aDerived); + } + } + else + { + myOriginalToDerived.Bind(theOriginal, std::move(aFilteredReplacements)); + } +} + +//================================================================================================= + +void BRepGraph_History::RecordBatch(const TCollection_AsciiString& theOpLabel, + const NCollection_Vector& theOriginals, + const NCollection_Vector& theReplacements, + const TCollection_AsciiString& theExtraInfo) +{ + Standard_ASSERT_VOID(theOriginals.Length() == theReplacements.Length(), + "RecordBatch: mismatched vector lengths"); + if (!myEnabled || theOriginals.IsEmpty()) + { + return; + } + + const int aNbPairs = theOriginals.Length(); + + // Create a single history record with all mappings. + // Pre-size the Mapping to avoid DataMap rehashing. + BRepGraph_HistoryRecord aRecord; + aRecord.OperationName = theOpLabel; + aRecord.SequenceNumber = myRecords.Length(); + if (!myAllocator.IsNull()) + { + aRecord.Mapping = + NCollection_DataMap>(aNbPairs, + myAllocator); + } + else + { + aRecord.Mapping.ReSize(aNbPairs); + } + aRecord.ExtraInfo = theExtraInfo; + + // Build mapping: each pair creates a 1-element replacement vector. + for (int i = 0; i < aNbPairs; ++i) + { + const BRepGraph_NodeId& anOriginal = theOriginals.Value(i); + const BRepGraph_NodeId& aReplacement = theReplacements.Value(i); + Standard_ASSERT_VOID(!aRecord.Mapping.IsBound(anOriginal), + "RecordBatch: duplicate original node"); + NCollection_Vector aRepVec(THE_HISTORY_REPLACEMENT_BLOCK_SIZE, myAllocator); + aRepVec.Append(aReplacement); + aRecord.Mapping.Bind(anOriginal, std::move(aRepVec)); + } + myRecords.Append(std::move(aRecord)); + + // Update bidirectional lookup maps in bulk. + // Pre-size to avoid rehashing during batch insert. + myDerivedToOriginal.ReSize(myDerivedToOriginal.Extent() + aNbPairs); + myOriginalToDerived.ReSize(myOriginalToDerived.Extent() + aNbPairs); + + for (int i = 0; i < aNbPairs; ++i) + { + const BRepGraph_NodeId& anOriginal = theOriginals.Value(i); + const BRepGraph_NodeId& aReplacement = theReplacements.Value(i); + if (aReplacement == anOriginal) + { + continue; + } + + myDerivedToOriginal.Bind(aReplacement, anOriginal); + + if (myOriginalToDerived.IsBound(anOriginal)) + { + myOriginalToDerived.ChangeFind(anOriginal).Append(aReplacement); + } + else + { + NCollection_Vector aDerVec(THE_HISTORY_REPLACEMENT_BLOCK_SIZE, myAllocator); + aDerVec.Append(aReplacement); + myOriginalToDerived.Bind(anOriginal, std::move(aDerVec)); + } + } +} + +//================================================================================================= + +BRepGraph_NodeId BRepGraph_History::FindOriginal(const BRepGraph_NodeId theModified) const +{ + // Walk the reverse map iteratively until a root node is reached. + // Limit iterations to the map extent to protect against cycles. + BRepGraph_NodeId aCurrent = theModified; + int aMaxIter = myDerivedToOriginal.Extent(); + while (myDerivedToOriginal.IsBound(aCurrent) && aMaxIter-- > 0) + { + const BRepGraph_NodeId& anOriginal = myDerivedToOriginal.Find(aCurrent); + if (anOriginal == aCurrent) + { + break; + } + aCurrent = anOriginal; + } + return aCurrent; +} + +//================================================================================================= + +NCollection_Vector BRepGraph_History::FindDerived( + const BRepGraph_NodeId theOriginal) const +{ + // Collect all transitively derived nodes using iterative BFS. + // A visited set guards against infinite loops if cycles exist in the forward map. + NCollection_Vector aResult(THE_HISTORY_DERIVED_BLOCK_SIZE); + NCollection_Vector aQueue(THE_HISTORY_QUEUE_BLOCK_SIZE); + NCollection_Map aVisited; + + aQueue.Append(theOriginal); + aVisited.Add(theOriginal); + + for (int aQueueIdx = 0; aQueueIdx < aQueue.Length(); ++aQueueIdx) + { + const BRepGraph_NodeId aNode = aQueue.Value(aQueueIdx); + if (!myOriginalToDerived.IsBound(aNode)) + { + // Leaf node: only add if it is not the initial query node itself. + if (aNode != theOriginal) + { + aResult.Append(aNode); + } + continue; + } + + const NCollection_Vector& aDirectDerived = myOriginalToDerived.Find(aNode); + for (const BRepGraph_NodeId& aDerived : aDirectDerived) + { + if (aVisited.Add(aDerived)) + { + aQueue.Append(aDerived); + } + } + } + + // If there were no transitive leaves but there are direct derived nodes, + // return the direct derived for the non-recursive case. + if (aResult.IsEmpty() && myOriginalToDerived.IsBound(theOriginal)) + { + const NCollection_Vector& aDirectDerived = + myOriginalToDerived.Find(theOriginal); + for (const BRepGraph_NodeId& aDerived : aDirectDerived) + { + aResult.Append(aDerived); + } + } + + return aResult; +} + +//================================================================================================= + +int BRepGraph_History::NbRecords() const +{ + return myRecords.Length(); +} + +//================================================================================================= + +const BRepGraph_HistoryRecord& BRepGraph_History::Record(const int theRecordIdx) const +{ + return myRecords.Value(theRecordIdx); +} + +//================================================================================================= + +void BRepGraph_History::SetEnabled(const bool theVal) +{ + myEnabled = theVal; +} + +//================================================================================================= + +bool BRepGraph_History::IsEnabled() const +{ + return myEnabled; +} + +//================================================================================================= + +void BRepGraph_History::Clear() +{ + myRecords.Clear(); + myDerivedToOriginal.Clear(); + myOriginalToDerived.Clear(); +} diff --git a/src/ModelingData/TKBRep/BRepGraph/BRepGraph_History.hxx b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_History.hxx new file mode 100644 index 0000000000..5993ea5b30 --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_History.hxx @@ -0,0 +1,114 @@ +// Copyright (c) 2026 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 _BRepGraph_History_HeaderFile +#define _BRepGraph_History_HeaderFile + +#include +#include +#include +#include +#include +#include +#include + +class BRepGraph; + +//! Extracted history subsystem for BRepGraph. +//! +//! BRepGraph_History maintains an append-only log of modification events +//! and bidirectional lookup maps (original <-> derived) for efficient +//! history queries. Recording can be toggled on/off at runtime. +class BRepGraph_History +{ + friend class BRepGraph; + +public: + DEFINE_STANDARD_ALLOC + + //! Record a modification: theOriginal was replaced by theReplacements. + //! @param[in] theOpLabel human-readable operation name + //! @param[in] theOriginal node id before the operation + //! @param[in] theReplacements node ids after the operation + Standard_EXPORT void Record(const TCollection_AsciiString& theOpLabel, + const BRepGraph_NodeId theOriginal, + const NCollection_Vector& theReplacements); + + //! Record a batch of 1-to-1 modifications in a single history event. + //! theOriginals[i] was replaced by theReplacements[i]. + //! More efficient than calling Record() in a loop: creates one HistoryRecord + //! and updates the bidirectional maps with minimal overhead. + //! @param[in] theOpLabel human-readable operation name + //! @param[in] theOriginals node ids before the operation + //! @param[in] theReplacements node ids after the operation (same length) + //! @param[in] theExtraInfo optional diagnostic info stored on the record + Standard_EXPORT void RecordBatch( + const TCollection_AsciiString& theOpLabel, + const NCollection_Vector& theOriginals, + const NCollection_Vector& theReplacements, + const TCollection_AsciiString& theExtraInfo = TCollection_AsciiString()); + + //! Walk backwards from a modified node to its original. + //! Follows the reverse map recursively until a root is reached. + //! @param[in] theModified node id to trace back + //! @return the root original node id, or theModified itself if not found + [[nodiscard]] Standard_EXPORT BRepGraph_NodeId + FindOriginal(const BRepGraph_NodeId theModified) const; + + //! Walk forwards from an original node to all derived nodes. + //! Follows the forward map recursively, collecting all leaves. + //! @param[in] theOriginal node id to trace forward + //! @return all transitively derived node ids + [[nodiscard]] Standard_EXPORT NCollection_Vector FindDerived( + const BRepGraph_NodeId theOriginal) const; + + //! Number of recorded history events. + //! @return record count + [[nodiscard]] Standard_EXPORT int NbRecords() const; + + //! Access a record by index (0-based). + //! @param[in] theRecordIdx zero-based index into the records vector + //! @return the history record at the given index + [[nodiscard]] Standard_EXPORT const BRepGraph_HistoryRecord& Record(const int theRecordIdx) const; + + //! Enable or disable history recording. + //! @param[in] theVal true to enable, false to disable + Standard_EXPORT void SetEnabled(const bool theVal); + + //! Query whether history recording is enabled. + //! @return true if recording is active + [[nodiscard]] Standard_EXPORT bool IsEnabled() const; + + //! Clear all records and lookup maps. + Standard_EXPORT void Clear(); + + //! Set the allocator for internal containers. + //! Must be called before any Record/RecordBatch calls. + //! @param[in] theAlloc allocator to use for internal maps + Standard_EXPORT void SetAllocator(const occ::handle& theAlloc); + +private: + occ::handle myAllocator; + + NCollection_Vector myRecords; + + //! Reverse map: derived node -> original node. + NCollection_DataMap myDerivedToOriginal; + + //! Forward map: original node -> vector of derived nodes. + NCollection_DataMap> myOriginalToDerived; + + bool myEnabled = true; +}; + +#endif // _BRepGraph_History_HeaderFile diff --git a/src/ModelingData/TKBRep/BRepGraph/BRepGraph_HistoryRecord.hxx b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_HistoryRecord.hxx new file mode 100644 index 0000000000..54abaa1a1e --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_HistoryRecord.hxx @@ -0,0 +1,49 @@ +// Copyright (c) 2026 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 _BRepGraph_HistoryRecord_HeaderFile +#define _BRepGraph_HistoryRecord_HeaderFile + +#include + +#include +#include +#include + +//! One atomic modification event recorded in the graph's history log. +//! +//! A HistoryRecord captures what happened during a single call to +//! BRepGraph::ApplyModification(): +//! - OperationName identifies the algorithm ("Sewing", "FilletEdge", ...). +//! - SequenceNumber provides total ordering of events. +//! - Mapping records the topological fate of each affected node: +//! original -> [replacement1, replacement2, ...] (split) +//! original -> [same_node] (modified in place) +//! original -> [] (deleted) +//! +//! The history log is append-only within a graph's lifetime. +struct BRepGraph_HistoryRecord +{ + TCollection_AsciiString OperationName; + int SequenceNumber = 0; + + //! Key: original node id before the operation. + //! Value: sequence of replacement node ids after the operation. + NCollection_DataMap> Mapping; + + //! Optional extra info for diagnostic/debugging purposes. + //! E.g., merge tolerance, canonical source index. + TCollection_AsciiString ExtraInfo; +}; + +#endif // _BRepGraph_HistoryRecord_HeaderFile diff --git a/src/ModelingData/TKBRep/BRepGraph/BRepGraph_Iterator.hxx b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_Iterator.hxx new file mode 100644 index 0000000000..c98d0944de --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_Iterator.hxx @@ -0,0 +1,290 @@ +// Copyright (c) 2026 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 _BRepGraph_Iterator_HeaderFile +#define _BRepGraph_Iterator_HeaderFile + +#include +#include + +#include + +#include +#include + +//! @brief Type-safe, allocation-free iterator over BRepGraph definition nodes. +//! +//! Provides sequential read-only access to definitions stored in BRepGraph. +//! By default nodes with IsRemoved flag are skipped; set TheFullTraverse +//! to true to include them (types without IsRemoved are unaffected). +//! +//! ## Usage +//! @code +//! for (BRepGraph_Iterator anIt(aGraph); +//! anIt.More(); anIt.Next()) +//! { +//! const BRepGraphInc::FaceDef& aFace = anIt.Current(); +//! } +//! @endcode +namespace BRepGraph_IteratorDetail +{ +//! SFINAE helper: detect whether NodeType has an IsRemoved member (BaseDef types do). +template +struct HasIsRemoved : std::false_type +{ +}; + +template +struct HasIsRemoved().IsRemoved)>> : std::true_type +{ +}; + +//! Compile-time traits mapping from definition type to typed NodeId, +//! count accessor, and definition accessor. +template +struct NodeTraits; + +template <> +struct NodeTraits +{ + using TypedId = BRepGraph_SolidId; + + static int Count(const BRepGraph& theGraph) { return theGraph.Topo().Solids().Nb(); } + + static const BRepGraphInc::SolidDef& Get(const BRepGraph& theGraph, const TypedId theId) + { + return theGraph.Topo().Solids().Definition(theId); + } +}; + +template <> +struct NodeTraits +{ + using TypedId = BRepGraph_ShellId; + + static int Count(const BRepGraph& theGraph) { return theGraph.Topo().Shells().Nb(); } + + static const BRepGraphInc::ShellDef& Get(const BRepGraph& theGraph, const TypedId theId) + { + return theGraph.Topo().Shells().Definition(theId); + } +}; + +template <> +struct NodeTraits +{ + using TypedId = BRepGraph_FaceId; + + static int Count(const BRepGraph& theGraph) { return theGraph.Topo().Faces().Nb(); } + + static const BRepGraphInc::FaceDef& Get(const BRepGraph& theGraph, const TypedId theId) + { + return theGraph.Topo().Faces().Definition(theId); + } +}; + +template <> +struct NodeTraits +{ + using TypedId = BRepGraph_WireId; + + static int Count(const BRepGraph& theGraph) { return theGraph.Topo().Wires().Nb(); } + + static const BRepGraphInc::WireDef& Get(const BRepGraph& theGraph, const TypedId theId) + { + return theGraph.Topo().Wires().Definition(theId); + } +}; + +template <> +struct NodeTraits +{ + using TypedId = BRepGraph_EdgeId; + + static int Count(const BRepGraph& theGraph) { return theGraph.Topo().Edges().Nb(); } + + static const BRepGraphInc::EdgeDef& Get(const BRepGraph& theGraph, const TypedId theId) + { + return theGraph.Topo().Edges().Definition(theId); + } +}; + +template <> +struct NodeTraits +{ + using TypedId = BRepGraph_VertexId; + + static int Count(const BRepGraph& theGraph) { return theGraph.Topo().Vertices().Nb(); } + + static const BRepGraphInc::VertexDef& Get(const BRepGraph& theGraph, const TypedId theId) + { + return theGraph.Topo().Vertices().Definition(theId); + } +}; + +template <> +struct NodeTraits +{ + using TypedId = BRepGraph_ProductId; + + static int Count(const BRepGraph& theGraph) { return theGraph.Topo().Products().Nb(); } + + static const BRepGraphInc::ProductDef& Get(const BRepGraph& theGraph, const TypedId theId) + { + return theGraph.Topo().Products().Definition(theId); + } +}; + +template <> +struct NodeTraits +{ + using TypedId = BRepGraph_OccurrenceId; + + static int Count(const BRepGraph& theGraph) { return theGraph.Topo().Occurrences().Nb(); } + + static const BRepGraphInc::OccurrenceDef& Get(const BRepGraph& theGraph, const TypedId theId) + { + return theGraph.Topo().Occurrences().Definition(theId); + } +}; + +template <> +struct NodeTraits +{ + using TypedId = BRepGraph_CoEdgeId; + + static int Count(const BRepGraph& theGraph) { return theGraph.Topo().CoEdges().Nb(); } + + static const BRepGraphInc::CoEdgeDef& Get(const BRepGraph& theGraph, const TypedId theId) + { + return theGraph.Topo().CoEdges().Definition(theId); + } +}; + +template <> +struct NodeTraits +{ + using TypedId = BRepGraph_CompoundId; + + static int Count(const BRepGraph& theGraph) { return theGraph.Topo().Compounds().Nb(); } + + static const BRepGraphInc::CompoundDef& Get(const BRepGraph& theGraph, const TypedId theId) + { + return theGraph.Topo().Compounds().Definition(theId); + } +}; + +template <> +struct NodeTraits +{ + using TypedId = BRepGraph_CompSolidId; + + static int Count(const BRepGraph& theGraph) { return theGraph.Topo().CompSolids().Nb(); } + + static const BRepGraphInc::CompSolidDef& Get(const BRepGraph& theGraph, const TypedId theId) + { + return theGraph.Topo().CompSolids().Definition(theId); + } +}; +} // namespace BRepGraph_IteratorDetail + +//! @brief Type-safe, allocation-free iterator over BRepGraph definition nodes. +//! +//! @tparam NodeType Definition struct type (e.g. BRepGraphInc::FaceDef). +//! @tparam TheFullTraverse When true, removed nodes are NOT skipped (for special cases). +template +class BRepGraph_Iterator +{ +public: + using Traits = BRepGraph_IteratorDetail::NodeTraits; + using TypedId = typename Traits::TypedId; + + BRepGraph_Iterator(const BRepGraph& theGraph) + : myGraph(theGraph), + myLength(TypedId(Traits::Count(theGraph))) + { + skipRemoved(); + } + + [[nodiscard]] bool More() const { return myCurrent < myLength; } + + void Next() + { + ++myCurrent; + skipRemoved(); + } + + [[nodiscard]] const NodeType& Current() const { return Traits::Get(myGraph, myCurrent); } + + //! Current definition index as a typed NodeId. + [[nodiscard]] TypedId CurrentId() const { return myCurrent; } + + //! Returns an STL-compatible iterator for range-based for loops. + NCollection_ForwardRangeIterator begin() + { + return NCollection_ForwardRangeIterator(this); + } + + //! Returns a sentinel marking the end of iteration. + NCollection_ForwardRangeSentinel end() const { return NCollection_ForwardRangeSentinel{}; } + +private: + //! Advance past any nodes marked as removed. + void skipRemoved() + { + if constexpr (!TheFullTraverse && BRepGraph_IteratorDetail::HasIsRemoved::value) + { + while (myCurrent < myLength && Current().IsRemoved) + ++myCurrent; + } + } + + const BRepGraph& myGraph; + TypedId myCurrent = TypedId(0); + TypedId myLength; +}; + +// --------------------------------------------------------------------------- +// Convenience type aliases (skip removed nodes default) +// --------------------------------------------------------------------------- + +using BRepGraph_SolidIterator = BRepGraph_Iterator; +using BRepGraph_ShellIterator = BRepGraph_Iterator; +using BRepGraph_FaceIterator = BRepGraph_Iterator; +using BRepGraph_WireIterator = BRepGraph_Iterator; +using BRepGraph_EdgeIterator = BRepGraph_Iterator; +using BRepGraph_VertexIterator = BRepGraph_Iterator; +using BRepGraph_CoEdgeIterator = BRepGraph_Iterator; +using BRepGraph_CompoundIterator = BRepGraph_Iterator; +using BRepGraph_CompSolidIterator = BRepGraph_Iterator; +using BRepGraph_ProductIterator = BRepGraph_Iterator; +using BRepGraph_OccurrenceIterator = BRepGraph_Iterator; + +// --------------------------------------------------------------------------- +// Full-traverse aliases (include removed/invalidated nodes use only +// in special cases such as compaction, validation, or debugging) +// --------------------------------------------------------------------------- + +using BRepGraph_FullSolidIterator = BRepGraph_Iterator; +using BRepGraph_FullShellIterator = BRepGraph_Iterator; +using BRepGraph_FullFaceIterator = BRepGraph_Iterator; +using BRepGraph_FullWireIterator = BRepGraph_Iterator; +using BRepGraph_FullEdgeIterator = BRepGraph_Iterator; +using BRepGraph_FullVertexIterator = BRepGraph_Iterator; +using BRepGraph_FullCoEdgeIterator = BRepGraph_Iterator; +using BRepGraph_FullCompoundIterator = BRepGraph_Iterator; +using BRepGraph_FullCompSolidIterator = BRepGraph_Iterator; +using BRepGraph_FullProductIterator = BRepGraph_Iterator; +using BRepGraph_FullOccurrenceIterator = BRepGraph_Iterator; + +#endif // _BRepGraph_Iterator_HeaderFile diff --git a/src/ModelingData/TKBRep/BRepGraph/BRepGraph_Layer.cxx b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_Layer.cxx new file mode 100644 index 0000000000..b81306f101 --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_Layer.cxx @@ -0,0 +1,34 @@ +// Copyright (c) 2026 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 + +IMPLEMENT_STANDARD_RTTIEXT(BRepGraph_Layer, Standard_Transient) + +//================================================================================================= + +int BRepGraph_Layer::SubscribedKinds() const +{ + return 0; +} + +//================================================================================================= + +void BRepGraph_Layer::OnNodeModified(const BRepGraph_NodeId /*theNode*/) noexcept {} + +//================================================================================================= + +void BRepGraph_Layer::OnNodesModified( + const NCollection_Vector& /*theModifiedNodes*/) noexcept +{ +} diff --git a/src/ModelingData/TKBRep/BRepGraph/BRepGraph_Layer.hxx b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_Layer.hxx new file mode 100644 index 0000000000..09341a1776 --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_Layer.hxx @@ -0,0 +1,119 @@ +// Copyright (c) 2026 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 _BRepGraph_Layer_HeaderFile +#define _BRepGraph_Layer_HeaderFile + +#include +#include +#include +#include +#include +#include + +//! @brief Abstract base class for named attribute layers. +//! +//! A layer groups per-node metadata under a unique name with lifecycle callbacks. +//! Layers are registered on BRepGraph and automatically notified when nodes are +//! removed, replaced (sewing/deduplicate), remapped (compact), or modified. +//! +//! Derived layers store domain-specific data (names, colors, materials, etc.) +//! in internal maps keyed by BRepGraph_NodeId. The lifecycle callbacks ensure +//! data consistency across all graph mutations. +//! +//! ## Modification Events +//! Layers can subscribe to modification events by overriding SubscribedKinds() +//! to return a non-zero bitmask of Kind values. When a subscribed node kind is +//! modified, OnNodeModified() (immediate mode) or OnNodesModified() (deferred +//! batch mode) is called. Layers with SubscribedKinds() == 0 (default) incur +//! zero dispatch overhead. +//! +//! ## Thread safety +//! Callback dispatch is single-threaded (called from mutation paths). +//! Layers that only provide read access can skip internal locking. +//! +//! @warning All lifecycle callbacks (OnNodeRemoved, OnCompact, InvalidateAll, +//! Clear, OnNodeModified, OnNodesModified) are declared noexcept. Derived +//! implementations that throw will cause std::terminate. This is enforced +//! by C++ language semantics for noexcept virtual overrides. +class BRepGraph_Layer : public Standard_Transient +{ +public: + //! Layer type identity (unique within a graph). + [[nodiscard]] virtual const Standard_GUID& ID() const = 0; + + //! Layer identity (unique within a graph). + [[nodiscard]] virtual const TCollection_AsciiString& Name() const = 0; + + //! Called when a node is soft-removed. + //! @param[in] theNode the removed node + //! @param[in] theReplacement if valid, the node that replaces theNode + //! (e.g., sewing edge merge, deduplicate). If invalid, pure deletion. + //! Layers should migrate data from theNode to theReplacement when valid, + //! otherwise discard or archive removed-node data. + //! Implementations must validate theReplacement before dereferencing + //! graph data through it. + //! @warning Layer callbacks must not throw. They are called from noexcept + //! notification paths (MutGuard destructors, deferred invalidation flush). + virtual void OnNodeRemoved(const BRepGraph_NodeId theNode, + const BRepGraph_NodeId theReplacement) noexcept = 0; + + //! Called after Compact with a unified old->new remap map. + //! Layer must remap all internal NodeId references using this map. + //! The map covers all node kinds (Vertex through CompSolid and future extensions). + //! Nodes absent from the map were removed during compaction - layers should + //! drop data associated with those nodes. + //! @param[in] theRemapMap maps old NodeId to new NodeId for all surviving nodes + virtual void OnCompact( + const NCollection_DataMap& theRemapMap) noexcept = 0; + + //! Mark all cached values dirty (bulk invalidation). + virtual void InvalidateAll() noexcept = 0; + + //! Clear all stored data. + virtual void Clear() noexcept = 0; + + // --- Modification event subscription --- + + //! Return a bitmask of BRepGraph_NodeId::Kind values this layer subscribes to. + //! Only modification events matching subscribed kinds are dispatched. + //! Default: 0 (no subscription - no modification events received). + //! Override to receive OnNodeModified/OnNodesModified callbacks. + //! The returned value must be constant for the lifetime of the layer. + [[nodiscard]] Standard_EXPORT virtual int SubscribedKinds() const; + + //! Called in immediate (non-deferred) mode after a single node is modified. + //! Only dispatched if the node's kind matches SubscribedKinds(). + //! Default: no-op. + //! @param[in] theNode the modified node + Standard_EXPORT virtual void OnNodeModified(const BRepGraph_NodeId theNode) noexcept; + + //! Called after EndDeferredInvalidation() with all nodes modified during + //! the deferred scope. Only dispatched if at least one modified node's kind + //! matches SubscribedKinds(). The vector may contain nodes of kinds not + //! subscribed to - layers should filter internally if needed. + //! Default: no-op. + //! @param[in] theModifiedNodes all modified, non-removed nodes + Standard_EXPORT virtual void OnNodesModified( + const NCollection_Vector& theModifiedNodes) noexcept; + + //! Convenience: return bitmask bit for a given Kind. + static int KindBit(const BRepGraph_NodeId::Kind theKind) + { + return 1 << static_cast(theKind); + } + + DEFINE_STANDARD_RTTIEXT(BRepGraph_Layer, Standard_Transient) +}; + +#endif // _BRepGraph_Layer_HeaderFile diff --git a/src/ModelingData/TKBRep/BRepGraph/BRepGraph_LayerRegistry.cxx b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_LayerRegistry.cxx new file mode 100644 index 0000000000..415186c3a5 --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_LayerRegistry.cxx @@ -0,0 +1,170 @@ +// Copyright (c) 2026 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 + +//================================================================================================= + +int BRepGraph_LayerRegistry::RegisterLayer(const occ::handle& theLayer) +{ + if (theLayer.IsNull()) + return -1; + + const Standard_GUID& aGUID = theLayer->ID(); + const int* aSlot = myGuidToSlot.Seek(aGUID); + if (aSlot != nullptr) + { + myLayers.ChangeValue(*aSlot) = theLayer; + recomputeSubscribedKindsMask(); + return *aSlot; + } + + const int aNewSlot = myLayers.Length(); + myLayers.Append(theLayer); + myGuidToSlot.Bind(aGUID, aNewSlot); + mySubscribedKindsMask |= theLayer->SubscribedKinds(); + return aNewSlot; +} + +//================================================================================================= + +void BRepGraph_LayerRegistry::UnregisterLayer(const Standard_GUID& theGUID) +{ + const int* aSlotPtr = myGuidToSlot.Seek(theGUID); + if (aSlotPtr == nullptr) + return; + + const int aSlot = *aSlotPtr; + const int aLastSlot = myLayers.Length() - 1; + if (aSlot != aLastSlot) + { + const occ::handle& aLastLayer = myLayers.Value(aLastSlot); + myLayers.ChangeValue(aSlot) = aLastLayer; + myGuidToSlot.ChangeFind(aLastLayer->ID()) = aSlot; + } + + myLayers.EraseLast(); + myGuidToSlot.UnBind(theGUID); + recomputeSubscribedKindsMask(); +} + +//================================================================================================= + +occ::handle BRepGraph_LayerRegistry::FindLayer(const Standard_GUID& theGUID) const +{ + const int* aSlot = myGuidToSlot.Seek(theGUID); + return aSlot != nullptr ? myLayers.Value(*aSlot) : occ::handle(); +} + +//================================================================================================= + +int BRepGraph_LayerRegistry::FindSlot(const Standard_GUID& theGUID) const +{ + const int* aSlot = myGuidToSlot.Seek(theGUID); + return aSlot != nullptr ? *aSlot : -1; +} + +//================================================================================================= + +const occ::handle& BRepGraph_LayerRegistry::Layer(const int theSlot) const +{ + Standard_OutOfRange_Raise_if(theSlot < 0 || theSlot >= myLayers.Length(), + "BRepGraph_LayerRegistry::Layer() - invalid slot"); + return myLayers.Value(theSlot); +} + +//================================================================================================= + +void BRepGraph_LayerRegistry::DispatchOnNodeRemoved(const BRepGraph_NodeId theNode, + const BRepGraph_NodeId theReplacement) noexcept +{ + for (const occ::handle& aLayer : myLayers) + { + aLayer->OnNodeRemoved(theNode, theReplacement); + } +} + +//================================================================================================= + +void BRepGraph_LayerRegistry::DispatchNodeModified(const BRepGraph_NodeId theNode) noexcept +{ + if (!HasModificationSubscribers()) + return; + + const int aKindBit = BRepGraph_Layer::KindBit(theNode.NodeKind); + for (const occ::handle& aLayer : myLayers) + { + if ((aLayer->SubscribedKinds() & aKindBit) != 0) + aLayer->OnNodeModified(theNode); + } +} + +//================================================================================================= + +void BRepGraph_LayerRegistry::DispatchNodesModified( + const NCollection_Vector& theModifiedNodes, + const int theModifiedKindsMask) noexcept +{ + if (!HasModificationSubscribers() || theModifiedKindsMask == 0) + return; + + for (const occ::handle& aLayer : myLayers) + { + if ((aLayer->SubscribedKinds() & theModifiedKindsMask) != 0) + aLayer->OnNodesModified(theModifiedNodes); + } +} + +//================================================================================================= + +void BRepGraph_LayerRegistry::DispatchOnCompact( + const NCollection_DataMap& theRemapMap) noexcept +{ + for (const occ::handle& aLayer : myLayers) + { + aLayer->OnCompact(theRemapMap); + } +} + +//================================================================================================= + +void BRepGraph_LayerRegistry::ClearAll() noexcept +{ + for (const occ::handle& aLayer : myLayers) + { + aLayer->Clear(); + } +} + +//================================================================================================= + +void BRepGraph_LayerRegistry::InvalidateAll() noexcept +{ + for (const occ::handle& aLayer : myLayers) + { + aLayer->InvalidateAll(); + } +} + +//================================================================================================= + +void BRepGraph_LayerRegistry::recomputeSubscribedKindsMask() +{ + mySubscribedKindsMask = 0; + for (const occ::handle& aLayer : myLayers) + { + mySubscribedKindsMask |= aLayer->SubscribedKinds(); + } +} diff --git a/src/ModelingData/TKBRep/BRepGraph/BRepGraph_LayerRegistry.hxx b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_LayerRegistry.hxx new file mode 100644 index 0000000000..d01720cd8e --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_LayerRegistry.hxx @@ -0,0 +1,105 @@ +// Copyright (c) 2026 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 _BRepGraph_LayerRegistry_HeaderFile +#define _BRepGraph_LayerRegistry_HeaderFile + +#include + +#include +#include +#include +#include + +//! @brief Dense GUID-keyed runtime registry of graph layers. +//! +//! Stores registered layers in a compact vector for O(1) slot access and a +//! GUID-to-slot map for O(1) lookup by stable public identity. +class BRepGraph_LayerRegistry +{ +public: + DEFINE_STANDARD_ALLOC + + BRepGraph_LayerRegistry() = default; + + BRepGraph_LayerRegistry(const BRepGraph_LayerRegistry&) = delete; + BRepGraph_LayerRegistry& operator=(const BRepGraph_LayerRegistry&) = delete; + + BRepGraph_LayerRegistry(BRepGraph_LayerRegistry&&) noexcept = default; + BRepGraph_LayerRegistry& operator=(BRepGraph_LayerRegistry&&) noexcept = default; + + //! Register a layer. Replaces an existing layer with the same GUID. + //! @return slot index in the internal dense vector, or -1 for null input. + Standard_EXPORT int RegisterLayer(const occ::handle& theLayer); + + //! Remove a layer by GUID. + Standard_EXPORT void UnregisterLayer(const Standard_GUID& theGUID); + + //! Find a layer by GUID. Returns null handle if not found. + [[nodiscard]] Standard_EXPORT occ::handle FindLayer( + const Standard_GUID& theGUID) const; + + //! Typed convenience lookup by layer GUID. + template + [[nodiscard]] occ::handle FindLayer() const + { + return occ::down_cast(FindLayer(T::GetID())); + } + + //! Return current slot for a GUID, or -1 if not registered. + [[nodiscard]] Standard_EXPORT int FindSlot(const Standard_GUID& theGUID) const; + + //! Return layer by slot index. + [[nodiscard]] Standard_EXPORT const occ::handle& Layer(const int theSlot) const; + + //! Number of registered layers. + [[nodiscard]] int NbLayers() const { return myLayers.Length(); } + + //! True if any registered layer subscribes to modification events. + [[nodiscard]] bool HasModificationSubscribers() const { return mySubscribedKindsMask != 0; } + + //! Bitwise OR of all registered layer subscription masks. + [[nodiscard]] int SubscribedKindsMask() const { return mySubscribedKindsMask; } + + //! Dispatch OnNodeRemoved to all registered layers. + Standard_EXPORT void DispatchOnNodeRemoved(const BRepGraph_NodeId theNode, + const BRepGraph_NodeId theReplacement) noexcept; + + //! Dispatch OnNodeModified to subscribed layers. + Standard_EXPORT void DispatchNodeModified(const BRepGraph_NodeId theNode) noexcept; + + //! Dispatch OnNodesModified to subscribed layers. + Standard_EXPORT void DispatchNodesModified( + const NCollection_Vector& theModifiedNodes, + const int theModifiedKindsMask) noexcept; + + //! Dispatch OnCompact to all registered layers. + Standard_EXPORT void DispatchOnCompact( + const NCollection_DataMap& theRemapMap) noexcept; + + //! Clear all registered layer payloads without unregistering them. + Standard_EXPORT void ClearAll() noexcept; + + //! Invalidate all registered layer payloads. + Standard_EXPORT void InvalidateAll() noexcept; + +private: + Standard_EXPORT void recomputeSubscribedKindsMask(); + +private: + NCollection_Vector> myLayers; + NCollection_DataMap myGuidToSlot; + int mySubscribedKindsMask = 0; +}; + +#endif // _BRepGraph_LayerRegistry_HeaderFile diff --git a/src/ModelingData/TKBRep/BRepGraph/BRepGraph_MutGuard.hxx b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_MutGuard.hxx new file mode 100644 index 0000000000..f37502441d --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_MutGuard.hxx @@ -0,0 +1,147 @@ +// Copyright (c) 2026 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 _BRepGraph_MutGuard_HeaderFile +#define _BRepGraph_MutGuard_HeaderFile + +#include + +#include + +//! @brief RAII guard wrapping a mutable topology definition or reference entry. +//! +//! Obtained via BRepGraph::Builder().MutEdge(), MutVertex(), MutFaceRef(), etc. +//! Provides operator-> / operator* for direct field access. +//! Calls the appropriate markModified() or markRefModified() exactly once +//! on scope exit (destruction), regardless of how many fields were modified. +//! +//! Move-only; non-copyable. After a move, the source guard +//! becomes inert and will not trigger notification. +//! +//! Compile-time dispatch selects the ID type and notification method: +//! - For types derived from BRepGraphInc::BaseDef: BRepGraph_NodeId + markModified() +//! - For types derived from BRepGraphInc::BaseRef: BRepGraph_RefId + markRefModified() +//! - For types derived from BRepGraphInc::BaseRep: BRepGraph_RepId + markRepModified() +//! +//! @warning Guarded access is scoped mutation access, not a general transaction. +//! Callers should not mix `Mut*()` and structural `Add*()` / `Remove*()` edits in +//! the same logical mutation step, and parallel mutation batches should use +//! `BRepGraph_DeferredScope`. +//! +//! @code +//! { +//! BRepGraph_MutGuard anEdge = +//! theGraph.Builder().MutEdge(BRepGraph_EdgeId(42)); +//! anEdge->Tolerance = 0.5; +//! anEdge->SameParameter = true; +//! } // markModified called once here +//! @endcode +template +class BRepGraph_MutGuard +{ + static_assert(std::is_base_of_v + || std::is_base_of_v + || std::is_base_of_v, + "BRepGraph_MutGuard: T must derive from BaseDef, BaseRef, or BaseRep"); + + //! ID type: BRepGraph_NodeId for definitions, BRepGraph_RefId for references, + //! BRepGraph_RepId for representations. + using IdType = std::conditional_t, + BRepGraph_NodeId, + std::conditional_t, + BRepGraph_RefId, + BRepGraph_RepId>>; + + //! Call the appropriate notification method on the graph. + void notify() noexcept + { + if constexpr (std::is_base_of_v) + myGraph->markModified(myId, *myEntity); + else if constexpr (std::is_base_of_v) + myGraph->markRefModified(myId, *myEntity); + else + myGraph->markRepModified(myId); + } + +public: + //! Construct a guard over a mutable entity. + //! @param[in] theGraph owning graph (used for notification on destruction) + //! @param[in] theEntity pointer to the mutable entity + //! @param[in] theId identity for notification + BRepGraph_MutGuard(BRepGraph* theGraph, T* theEntity, const IdType theId) + : myGraph(theGraph), + myEntity(theEntity), + myId(theId) + { + } + + //! Destructor: notifies the graph if the guard still owns the reference. + ~BRepGraph_MutGuard() + { + if (myGraph != nullptr) + notify(); + } + + //! Move constructor: transfers ownership; source becomes inert. + BRepGraph_MutGuard(BRepGraph_MutGuard&& theOther) noexcept + : myGraph(theOther.myGraph), + myEntity(theOther.myEntity), + myId(theOther.myId) + { + theOther.myGraph = nullptr; + theOther.myEntity = nullptr; + } + + //! Move assignment: flushes current guard, then transfers ownership. + BRepGraph_MutGuard& operator=(BRepGraph_MutGuard&& theOther) noexcept + { + if (this != &theOther) + { + if (myGraph != nullptr) + notify(); + myGraph = theOther.myGraph; + myEntity = theOther.myEntity; + myId = theOther.myId; + theOther.myGraph = nullptr; + theOther.myEntity = nullptr; + } + return *this; + } + + BRepGraph_MutGuard(const BRepGraph_MutGuard&) = delete; + BRepGraph_MutGuard& operator=(const BRepGraph_MutGuard&) = delete; + + //! Access the entity via pointer syntax. + [[nodiscard]] T* operator->() + { + Standard_ProgramError_Raise_if( + myEntity == nullptr, + "BRepGraph_MutGuard::operator->(): guard is empty or moved-from"); + return myEntity; + } + + //! Dereference to the entity. + [[nodiscard]] T& operator*() + { + Standard_ProgramError_Raise_if(myEntity == nullptr, + "BRepGraph_MutGuard::operator*(): guard is empty or moved-from"); + return *myEntity; + } + +private: + BRepGraph* myGraph; //!< Owning graph (nullptr after move). + T* myEntity; //!< Mutable entity pointer. + IdType myId; //!< Identity for notification. +}; + +#endif // _BRepGraph_MutGuard_HeaderFile diff --git a/src/ModelingData/TKBRep/BRepGraph/BRepGraph_NodeId.hxx b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_NodeId.hxx new file mode 100644 index 0000000000..22c7bddc02 --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_NodeId.hxx @@ -0,0 +1,277 @@ +// Copyright (c) 2026 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 _BRepGraph_NodeId_HeaderFile +#define _BRepGraph_NodeId_HeaderFile + +#include +#include + +#include +#include + +//! Lightweight typed index into a per-kind node vector inside BRepGraph. +//! +//! The pair (NodeKind, Index) forms a unique node identifier within one graph +//! instance. Default-constructed NodeId has Index = -1 (invalid). +//! +//! NodeId is a value type: cheap to copy, compare, hash. It carries no +//! pointer back to the owning graph; the caller is responsible for using +//! it with the correct BRepGraph instance. +struct BRepGraph_NodeId +{ + //! Enumeration of node kinds within a BRepGraph. + //! + //! Topology kinds 0-5 cover core hierarchy; Compound(6)/CompSolid(7) + //! are container kinds. Note: ordering does NOT match TopAbs_ShapeEnum. + //! Geometry kinds start at 10, leaving room for future topology extensions. + enum class Kind : int + { + Solid = 0, + Shell = 1, + Face = 2, + Wire = 3, + Edge = 4, + Vertex = 5, + Compound = 6, //!< TopoDS_Compound container + CompSolid = 7, //!< TopoDS_CompSolid container + CoEdge = 8, //!< Use of an edge on a face (owns PCurve data) + // Value 9 reserved for future topology kind extension. + Product = 10, //!< Reusable shape definition (part or assembly) + Occurrence = 11 //!< Placed instance of a product within a parent product + }; + + //! @brief Compile-time typed wrapper around BRepGraph_NodeId. + //! + //! Provides compile-time kind safety: a Typed + //! cannot be accidentally used where a Typed is expected. + //! Implicitly converts to BRepGraph_NodeId for backward compatibility. + //! + //! @tparam TheKind the BRepGraph_NodeId::Kind this typed id represents + template + struct Typed + { + int Index; + + //! Default: invalid (Index = -1). + Typed() + : Index(-1) + { + } + + //! Construct from index. + explicit Typed(const int theIdx) + : Index(theIdx) + { + Standard_ASSERT_VOID(theIdx >= -1, "index must be >= -1"); + } + + //! True if this id points to an allocated node slot. + [[nodiscard]] bool IsValid() const { return Index >= 0; } + + //! True if this id points to an allocated slot within [0, theMaxCount). + [[nodiscard]] bool IsValid(const int theMaxCount) const + { + Standard_ASSERT_RETURN(theMaxCount >= 0, "max count must be non-negative", false); + return Index >= 0 && Index < theMaxCount; + } + + //! Implicit conversion to untyped NodeId. + operator BRepGraph_NodeId() const { return BRepGraph_NodeId(TheKind, Index); } + + //! Explicit conversion from untyped NodeId. + //! Asserts that the Kind matches in debug builds. + //! @param[in] theId untyped NodeId to convert + static Typed FromNodeId(const BRepGraph_NodeId theId) + { + Standard_ASSERT_VOID(theId.NodeKind == TheKind, "NodeId kind mismatch"); + return Typed(theId.Index); + } + + bool operator==(const Typed& theOther) const { return Index == theOther.Index; } + + bool operator!=(const Typed& theOther) const { return Index != theOther.Index; } + + bool operator<(const Typed& theOther) const { return Index < theOther.Index; } + + bool operator<=(const Typed& theOther) const { return Index <= theOther.Index; } + + bool operator>(const Typed& theOther) const { return Index > theOther.Index; } + + bool operator>=(const Typed& theOther) const { return Index >= theOther.Index; } + + //! Pre-increment (++id). + Typed& operator++() + { + Standard_ASSERT_VOID(Index >= 0, "pre-increment on invalid id"); + ++Index; + return *this; + } + + //! Post-increment (id++). + Typed operator++(int) + { + Standard_ASSERT_VOID(Index >= 0, "post-increment on invalid id"); + Typed aPrev = *this; + ++Index; + return aPrev; + } + + //! Advance by offset. + [[nodiscard]] Typed operator+(const int theOffset) const { return Typed(Index + theOffset); } + + //! Retreat by offset. + [[nodiscard]] Typed operator-(const int theOffset) const { return Typed(Index - theOffset); } + + //! Comparison with untyped NodeId (checks both Kind and Index). + bool operator==(const BRepGraph_NodeId& theOther) const + { + return theOther.NodeKind == TheKind && theOther.Index == Index; + } + + bool operator!=(const BRepGraph_NodeId& theOther) const { return !(*this == theOther); } + + //! Allow reversed comparison: NodeId == Typed. + friend bool operator==(const BRepGraph_NodeId& theLhs, const Typed& theRhs) + { + return theRhs == theLhs; + } + + friend bool operator!=(const BRepGraph_NodeId& theLhs, const Typed& theRhs) + { + return theRhs != theLhs; + } + }; + + //! True if the kind is a core topology kind (Solid..CoEdge). + static bool IsTopologyKind(const Kind theKind) { return static_cast(theKind) <= 8; } + + //! True if the kind is an assembly kind (Product or Occurrence). + static bool IsAssemblyKind(const Kind theKind) + { + return theKind == Kind::Product || theKind == Kind::Occurrence; + } + + //! Total number of dense kind slots used by per-kind arrays. + //! Includes the reserved gap at enum value 9. + static constexpr int THE_KIND_COUNT = static_cast(Kind::Occurrence) + 1; + + Kind NodeKind; + int Index; + + //! Default: invalid NodeId (Index = -1). + //! NodeKind is set to Kind::Solid but is meaningless when !IsValid(). + BRepGraph_NodeId() + : NodeKind(Kind::Solid), + Index(-1) + { + } + + BRepGraph_NodeId(const Kind theKind, const int theIdx) + : NodeKind(theKind), + Index(theIdx) + { + Standard_ASSERT_VOID(theIdx >= -1, "BRepGraph_NodeId: index must be >= -1"); + } + + //! True if this id points to an allocated node slot. + [[nodiscard]] bool IsValid() const { return Index >= 0; } + + //! True if this id points to an allocated slot within [0, theMaxCount). + [[nodiscard]] bool IsValid(const int theMaxCount) const + { + Standard_ASSERT_RETURN(theMaxCount >= 0, "max count must be non-negative", false); + return Index >= 0 && Index < theMaxCount; + } + + bool operator==(const BRepGraph_NodeId& theOther) const + { + return NodeKind == theOther.NodeKind && Index == theOther.Index; + } + + bool operator!=(const BRepGraph_NodeId& theOther) const { return !(*this == theOther); } + + bool operator<(const BRepGraph_NodeId& theOther) const + { + if (NodeKind != theOther.NodeKind) + return static_cast(NodeKind) < static_cast(theOther.NodeKind); + return Index < theOther.Index; + } + + //! Pre-increment (++id). + BRepGraph_NodeId& operator++() + { + Standard_ASSERT_VOID(Index >= 0, "pre-increment on invalid id"); + ++Index; + return *this; + } + + //! Post-increment (id++). + BRepGraph_NodeId operator++(int) + { + Standard_ASSERT_VOID(Index >= 0, "post-increment on invalid id"); + BRepGraph_NodeId aPrev = *this; + ++Index; + return aPrev; + } + + //! Advance by offset. + [[nodiscard]] BRepGraph_NodeId operator+(const int theOffset) const + { + return BRepGraph_NodeId(NodeKind, Index + theOffset); + } + + //! Retreat by offset. + [[nodiscard]] BRepGraph_NodeId operator-(const int theOffset) const + { + return BRepGraph_NodeId(NodeKind, Index - theOffset); + } +}; + +//! @name Convenience type aliases for typed NodeIds. +using BRepGraph_SolidId = BRepGraph_NodeId::Typed; +using BRepGraph_ShellId = BRepGraph_NodeId::Typed; +using BRepGraph_FaceId = BRepGraph_NodeId::Typed; +using BRepGraph_WireId = BRepGraph_NodeId::Typed; +using BRepGraph_EdgeId = BRepGraph_NodeId::Typed; +using BRepGraph_VertexId = BRepGraph_NodeId::Typed; +using BRepGraph_CompoundId = BRepGraph_NodeId::Typed; +using BRepGraph_CompSolidId = BRepGraph_NodeId::Typed; +using BRepGraph_CoEdgeId = BRepGraph_NodeId::Typed; +using BRepGraph_ProductId = BRepGraph_NodeId::Typed; +using BRepGraph_OccurrenceId = BRepGraph_NodeId::Typed; + +//! std::hash specialization for BRepGraph_NodeId. +template <> +struct std::hash +{ + size_t operator()(const BRepGraph_NodeId& theId) const noexcept + { + size_t aCombination[2]; + aCombination[0] = opencascade::hash(static_cast(theId.NodeKind)); + aCombination[1] = opencascade::hash(theId.Index); + return opencascade::hashBytes(aCombination, sizeof(aCombination)); + } +}; + +//! std::hash specialization for BRepGraph_NodeId::Typed. +template +struct std::hash> +{ + size_t operator()(const BRepGraph_NodeId::Typed& theId) const noexcept + { + return std::hash{}(static_cast(theId)); + } +}; + +#endif // _BRepGraph_NodeId_HeaderFile diff --git a/src/ModelingData/TKBRep/BRepGraph/BRepGraph_ParallelPolicy.hxx b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_ParallelPolicy.hxx new file mode 100644 index 0000000000..1523f4fbf1 --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_ParallelPolicy.hxx @@ -0,0 +1,90 @@ +// Copyright (c) 2026 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 _BRepGraph_ParallelPolicy_HeaderFile +#define _BRepGraph_ParallelPolicy_HeaderFile + +#include + +#include +#include + +//! Lightweight workload-aware policy for deciding whether an internal phase +//! should actually launch parallel work when parallel mode is allowed. +//! +//! The goal is to avoid forcing every short-lived loop onto the thread pool. +//! Decisions are based on available worker capacity and on the amount of work +//! already visible to the phase, instead of on buried per-loop split sizes. +class BRepGraph_ParallelPolicy +{ +public: + //! Simple workload estimate for an execution phase. + struct Workload + { + int PrimaryItems = 0; //!< Main loop range. + int AuxiliaryItems = 0; //!< Additional independent items participating in the phase. + int InteractionCount = 0; //!< Pairwise or adjacency work discovered for the phase. + }; + + //! Return the effective logical worker count reported by OSD_Parallel. + [[nodiscard]] static int WorkerCount() + { + static const int THE_WORKER_COUNT = std::max(1, OSD_Parallel::NbLogicalProcessors()); + return THE_WORKER_COUNT; + } + + //! Check whether parallel execution is allowed and meaningful at all. + [[nodiscard]] static bool IsParallelAllowed(const bool theAllowParallel) + { + return theAllowParallel && WorkerCount() > 1; + } + + //! Decide whether the estimated workload is large enough to amortize + //! thread-pool launch and synchronization overhead. + [[nodiscard]] static bool ShouldRun(const bool theAllowParallel, + const int theWorkers, + const Workload& theWorkload) + { + if (!theAllowParallel || theWorkers <= 1) + { + return false; + } + + const int aPrimaryItems = std::max(theWorkload.PrimaryItems, 0); + if (aPrimaryItems <= 1) + { + return false; + } + + if (aPrimaryItems <= theWorkers) + { + return false; + } + + const int64_t aTotalWorkUnits = + static_cast(aPrimaryItems) + + static_cast(std::max(theWorkload.AuxiliaryItems, 0)) + + static_cast(std::max(theWorkload.InteractionCount, 0)); + const int64_t aRequiredWorkUnits = + static_cast(theWorkers) * static_cast(theWorkers); + return aTotalWorkUnits > aRequiredWorkUnits; + } + + //! Overload that queries the active worker count lazily. + [[nodiscard]] static bool ShouldRun(const bool theAllowParallel, const Workload& theWorkload) + { + return ShouldRun(theAllowParallel, WorkerCount(), theWorkload); + } +}; + +#endif // _BRepGraph_ParallelPolicy_HeaderFile diff --git a/src/ModelingData/TKBRep/BRepGraph/BRepGraph_ParamLayer.cxx b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_ParamLayer.cxx new file mode 100644 index 0000000000..b4ac066eb5 --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_ParamLayer.cxx @@ -0,0 +1,750 @@ +// Copyright (c) 2026 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 + +IMPLEMENT_STANDARD_RTTIEXT(BRepGraph_ParamLayer, BRepGraph_Layer) + +namespace +{ + +static const TCollection_AsciiString THE_LAYER_NAME("Param"); + +template +void appendUnique(NCollection_DataMap>& theMap, + const KeyT theKey, + const BRepGraph_VertexId theVertex) +{ + if (!theMap.IsBound(theKey)) + { + NCollection_Vector aVertices; + aVertices.Append(theVertex); + theMap.Bind(theKey, aVertices); + return; + } + + NCollection_Vector& aVertices = theMap.ChangeFind(theKey); + for (int anIdx = 0; anIdx < aVertices.Length(); ++anIdx) + { + if (aVertices.Value(anIdx) == theVertex) + return; + } + aVertices.Append(theVertex); +} + +template +void removeVertex(NCollection_DataMap>& theMap, + const KeyT theKey, + const BRepGraph_VertexId theVertex) noexcept +{ + if (!theMap.IsBound(theKey)) + return; + + NCollection_Vector& aVertices = theMap.ChangeFind(theKey); + for (int anIdx = 0; anIdx < aVertices.Length(); ++anIdx) + { + if (aVertices.Value(anIdx) != theVertex) + continue; + if (anIdx < aVertices.Length() - 1) + aVertices.ChangeValue(anIdx) = aVertices.Value(aVertices.Length() - 1); + aVertices.EraseLast(); + break; + } + + if (aVertices.IsEmpty()) + theMap.UnBind(theKey); +} + +static BRepGraph_VertexId remapVertex( + const NCollection_DataMap& theRemapMap, + const BRepGraph_VertexId theVertex) +{ + const BRepGraph_NodeId* aNewId = theRemapMap.Seek(BRepGraph_VertexId(theVertex.Index)); + if (aNewId == nullptr || aNewId->NodeKind != BRepGraph_NodeId::Kind::Vertex) + return BRepGraph_VertexId(); + return BRepGraph_VertexId(aNewId->Index); +} + +static BRepGraph_EdgeId remapEdge( + const NCollection_DataMap& theRemapMap, + const BRepGraph_EdgeId theEdge) +{ + const BRepGraph_NodeId* aNewId = theRemapMap.Seek(BRepGraph_EdgeId(theEdge.Index)); + if (aNewId == nullptr || aNewId->NodeKind != BRepGraph_NodeId::Kind::Edge) + return BRepGraph_EdgeId(); + return BRepGraph_EdgeId(aNewId->Index); +} + +static BRepGraph_FaceId remapFace( + const NCollection_DataMap& theRemapMap, + const BRepGraph_FaceId theFace) +{ + const BRepGraph_NodeId* aNewId = theRemapMap.Seek(BRepGraph_FaceId(theFace.Index)); + if (aNewId == nullptr || aNewId->NodeKind != BRepGraph_NodeId::Kind::Face) + return BRepGraph_FaceId(); + return BRepGraph_FaceId(aNewId->Index); +} + +static BRepGraph_CoEdgeId remapCoEdge( + const NCollection_DataMap& theRemapMap, + const BRepGraph_CoEdgeId theCoEdge) +{ + const BRepGraph_NodeId* aNewId = theRemapMap.Seek(BRepGraph_CoEdgeId(theCoEdge.Index)); + if (aNewId == nullptr || aNewId->NodeKind != BRepGraph_NodeId::Kind::CoEdge) + return BRepGraph_CoEdgeId(); + return BRepGraph_CoEdgeId(aNewId->Index); +} + +} // namespace + +//================================================================================================= + +const Standard_GUID& BRepGraph_ParamLayer::GetID() +{ + static const Standard_GUID THE_LAYER_ID("2f9b6a5c-1f2d-4a88-9c1c-7a0c16a10001"); + return THE_LAYER_ID; +} + +//================================================================================================= + +const Standard_GUID& BRepGraph_ParamLayer::ID() const +{ + return GetID(); +} + +//================================================================================================= + +const TCollection_AsciiString& BRepGraph_ParamLayer::Name() const +{ + return THE_LAYER_NAME; +} + +//================================================================================================= + +int BRepGraph_ParamLayer::SubscribedKinds() const +{ + return KindBit(BRepGraph_NodeId::Kind::Vertex) | KindBit(BRepGraph_NodeId::Kind::Edge) + | KindBit(BRepGraph_NodeId::Kind::Face) | KindBit(BRepGraph_NodeId::Kind::CoEdge); +} + +//================================================================================================= + +const BRepGraph_ParamLayer::VertexParams* BRepGraph_ParamLayer::FindVertexParams( + const BRepGraph_VertexId theVertex) const +{ + return myVertexParams.Seek(theVertex); +} + +//================================================================================================= + +bool BRepGraph_ParamLayer::FindPointOnCurve(const BRepGraph_VertexId theVertex, + const BRepGraph_EdgeId theEdge, + double* const theParameter) const +{ + const VertexParams* aParams = FindVertexParams(theVertex); + if (aParams == nullptr) + return false; + + for (const PointOnCurveEntry& anEntry : aParams->PointsOnCurve) + { + if (anEntry.EdgeDefId != theEdge) + continue; + if (theParameter != nullptr) + *theParameter = anEntry.Parameter; + return true; + } + return false; +} + +//================================================================================================= + +bool BRepGraph_ParamLayer::FindPointOnSurface(const BRepGraph_VertexId theVertex, + const BRepGraph_FaceId theFace, + gp_Pnt2d* const theUV) const +{ + const VertexParams* aParams = FindVertexParams(theVertex); + if (aParams == nullptr) + return false; + + for (const PointOnSurfaceEntry& anEntry : aParams->PointsOnSurface) + { + if (anEntry.FaceDefId != theFace) + continue; + if (theUV != nullptr) + *theUV = gp_Pnt2d(anEntry.ParameterU, anEntry.ParameterV); + return true; + } + return false; +} + +//================================================================================================= + +bool BRepGraph_ParamLayer::FindPointOnPCurve(const BRepGraph_VertexId theVertex, + const BRepGraph_CoEdgeId theCoEdge, + double* const theParameter) const +{ + const VertexParams* aParams = FindVertexParams(theVertex); + if (aParams == nullptr) + return false; + + for (const PointOnPCurveEntry& anEntry : aParams->PointsOnPCurve) + { + if (anEntry.CoEdgeDefId != theCoEdge) + continue; + if (theParameter != nullptr) + *theParameter = anEntry.Parameter; + return true; + } + return false; +} + +//================================================================================================= + +int BRepGraph_ParamLayer::NbPointsOnCurve(const BRepGraph_VertexId theVertex) const +{ + const VertexParams* aParams = FindVertexParams(theVertex); + return aParams == nullptr ? 0 : aParams->PointsOnCurve.Length(); +} + +//================================================================================================= + +int BRepGraph_ParamLayer::NbPointsOnSurface(const BRepGraph_VertexId theVertex) const +{ + const VertexParams* aParams = FindVertexParams(theVertex); + return aParams == nullptr ? 0 : aParams->PointsOnSurface.Length(); +} + +//================================================================================================= + +int BRepGraph_ParamLayer::NbPointsOnPCurve(const BRepGraph_VertexId theVertex) const +{ + const VertexParams* aParams = FindVertexParams(theVertex); + return aParams == nullptr ? 0 : aParams->PointsOnPCurve.Length(); +} + +//================================================================================================= + +BRepGraph_ParamLayer::VertexParams& BRepGraph_ParamLayer::changeVertexParams( + const BRepGraph_VertexId theVertex) +{ + if (!myVertexParams.IsBound(theVertex)) + myVertexParams.Bind(theVertex, VertexParams()); + return myVertexParams.ChangeFind(theVertex); +} + +//================================================================================================= + +void BRepGraph_ParamLayer::bindEdgeToVertex(const BRepGraph_EdgeId theEdge, + const BRepGraph_VertexId theVertex) +{ + appendUnique(myEdgeToVertices, theEdge, theVertex); +} + +//================================================================================================= + +void BRepGraph_ParamLayer::bindFaceToVertex(const BRepGraph_FaceId theFace, + const BRepGraph_VertexId theVertex) +{ + appendUnique(myFaceToVertices, theFace, theVertex); +} + +//================================================================================================= + +void BRepGraph_ParamLayer::bindCoEdgeToVertex(const BRepGraph_CoEdgeId theCoEdge, + const BRepGraph_VertexId theVertex) +{ + appendUnique(myCoEdgeToVertices, theCoEdge, theVertex); +} + +//================================================================================================= + +void BRepGraph_ParamLayer::unbindEdgeFromVertex(const BRepGraph_EdgeId theEdge, + const BRepGraph_VertexId theVertex) noexcept +{ + removeVertex(myEdgeToVertices, theEdge, theVertex); +} + +//================================================================================================= + +void BRepGraph_ParamLayer::unbindFaceFromVertex(const BRepGraph_FaceId theFace, + const BRepGraph_VertexId theVertex) noexcept +{ + removeVertex(myFaceToVertices, theFace, theVertex); +} + +//================================================================================================= + +void BRepGraph_ParamLayer::unbindCoEdgeFromVertex(const BRepGraph_CoEdgeId theCoEdge, + const BRepGraph_VertexId theVertex) noexcept +{ + removeVertex(myCoEdgeToVertices, theCoEdge, theVertex); +} + +//================================================================================================= + +void BRepGraph_ParamLayer::SetPointOnCurve(const BRepGraph_VertexId theVertex, + const BRepGraph_EdgeId theEdge, + const double theParameter) +{ + VertexParams& aParams = changeVertexParams(theVertex); + for (int anIdx = 0; anIdx < aParams.PointsOnCurve.Length(); ++anIdx) + { + PointOnCurveEntry& anEntry = aParams.PointsOnCurve.ChangeValue(anIdx); + if (anEntry.EdgeDefId != theEdge) + continue; + anEntry.Parameter = theParameter; + return; + } + + PointOnCurveEntry& anEntry = aParams.PointsOnCurve.Appended(); + anEntry.Parameter = theParameter; + anEntry.EdgeDefId = theEdge; + bindEdgeToVertex(theEdge, theVertex); +} + +//================================================================================================= + +void BRepGraph_ParamLayer::SetPointOnSurface(const BRepGraph_VertexId theVertex, + const BRepGraph_FaceId theFace, + const double theParameterU, + const double theParameterV) +{ + VertexParams& aParams = changeVertexParams(theVertex); + for (int anIdx = 0; anIdx < aParams.PointsOnSurface.Length(); ++anIdx) + { + PointOnSurfaceEntry& anEntry = aParams.PointsOnSurface.ChangeValue(anIdx); + if (anEntry.FaceDefId != theFace) + continue; + anEntry.ParameterU = theParameterU; + anEntry.ParameterV = theParameterV; + return; + } + + PointOnSurfaceEntry& anEntry = aParams.PointsOnSurface.Appended(); + anEntry.ParameterU = theParameterU; + anEntry.ParameterV = theParameterV; + anEntry.FaceDefId = theFace; + bindFaceToVertex(theFace, theVertex); +} + +//================================================================================================= + +void BRepGraph_ParamLayer::SetPointOnPCurve(const BRepGraph_VertexId theVertex, + const BRepGraph_CoEdgeId theCoEdge, + const double theParameter) +{ + VertexParams& aParams = changeVertexParams(theVertex); + for (int anIdx = 0; anIdx < aParams.PointsOnPCurve.Length(); ++anIdx) + { + PointOnPCurveEntry& anEntry = aParams.PointsOnPCurve.ChangeValue(anIdx); + if (anEntry.CoEdgeDefId != theCoEdge) + continue; + anEntry.Parameter = theParameter; + return; + } + + PointOnPCurveEntry& anEntry = aParams.PointsOnPCurve.Appended(); + anEntry.Parameter = theParameter; + anEntry.CoEdgeDefId = theCoEdge; + bindCoEdgeToVertex(theCoEdge, theVertex); +} + +//================================================================================================= + +void BRepGraph_ParamLayer::removePointOnCurve(const BRepGraph_VertexId theVertex, + const BRepGraph_EdgeId theEdge) noexcept +{ + if (!myVertexParams.IsBound(theVertex)) + return; + + VertexParams& aParams = myVertexParams.ChangeFind(theVertex); + for (int anIdx = 0; anIdx < aParams.PointsOnCurve.Length(); ++anIdx) + { + if (aParams.PointsOnCurve.Value(anIdx).EdgeDefId != theEdge) + continue; + if (anIdx < aParams.PointsOnCurve.Length() - 1) + aParams.PointsOnCurve.ChangeValue(anIdx) = + aParams.PointsOnCurve.Value(aParams.PointsOnCurve.Length() - 1); + aParams.PointsOnCurve.EraseLast(); + unbindEdgeFromVertex(theEdge, theVertex); + break; + } + + if (aParams.IsEmpty()) + myVertexParams.UnBind(theVertex); +} + +//================================================================================================= + +void BRepGraph_ParamLayer::removePointOnSurface(const BRepGraph_VertexId theVertex, + const BRepGraph_FaceId theFace) noexcept +{ + if (!myVertexParams.IsBound(theVertex)) + return; + + VertexParams& aParams = myVertexParams.ChangeFind(theVertex); + for (int anIdx = 0; anIdx < aParams.PointsOnSurface.Length(); ++anIdx) + { + if (aParams.PointsOnSurface.Value(anIdx).FaceDefId != theFace) + continue; + if (anIdx < aParams.PointsOnSurface.Length() - 1) + { + aParams.PointsOnSurface.ChangeValue(anIdx) = + aParams.PointsOnSurface.Value(aParams.PointsOnSurface.Length() - 1); + } + aParams.PointsOnSurface.EraseLast(); + unbindFaceFromVertex(theFace, theVertex); + break; + } + + if (aParams.IsEmpty()) + myVertexParams.UnBind(theVertex); +} + +//================================================================================================= + +void BRepGraph_ParamLayer::removePointOnPCurve(const BRepGraph_VertexId theVertex, + const BRepGraph_CoEdgeId theCoEdge) noexcept +{ + if (!myVertexParams.IsBound(theVertex)) + return; + + VertexParams& aParams = myVertexParams.ChangeFind(theVertex); + for (int anIdx = 0; anIdx < aParams.PointsOnPCurve.Length(); ++anIdx) + { + if (aParams.PointsOnPCurve.Value(anIdx).CoEdgeDefId != theCoEdge) + continue; + if (anIdx < aParams.PointsOnPCurve.Length() - 1) + { + aParams.PointsOnPCurve.ChangeValue(anIdx) = + aParams.PointsOnPCurve.Value(aParams.PointsOnPCurve.Length() - 1); + } + aParams.PointsOnPCurve.EraseLast(); + unbindCoEdgeFromVertex(theCoEdge, theVertex); + break; + } + + if (aParams.IsEmpty()) + myVertexParams.UnBind(theVertex); +} + +//================================================================================================= + +void BRepGraph_ParamLayer::removeVertexBindings(const BRepGraph_VertexId theVertex) noexcept +{ + const VertexParams* aParams = myVertexParams.Seek(theVertex); + if (aParams == nullptr) + return; + + for (const PointOnCurveEntry& anEntry : aParams->PointsOnCurve) + unbindEdgeFromVertex(anEntry.EdgeDefId, theVertex); + for (const PointOnSurfaceEntry& anEntry : aParams->PointsOnSurface) + unbindFaceFromVertex(anEntry.FaceDefId, theVertex); + for (const PointOnPCurveEntry& anEntry : aParams->PointsOnPCurve) + unbindCoEdgeFromVertex(anEntry.CoEdgeDefId, theVertex); + myVertexParams.UnBind(theVertex); +} + +//================================================================================================= + +void BRepGraph_ParamLayer::invalidateEdgeBindings(const BRepGraph_EdgeId theEdge) noexcept +{ + const NCollection_Vector* aVertices = myEdgeToVertices.Seek(theEdge); + if (aVertices == nullptr) + return; + + const NCollection_Vector aBoundVertices = *aVertices; + for (int anIdx = 0; anIdx < aBoundVertices.Length(); ++anIdx) + removePointOnCurve(aBoundVertices.Value(anIdx), theEdge); +} + +//================================================================================================= + +void BRepGraph_ParamLayer::invalidateFaceBindings(const BRepGraph_FaceId theFace) noexcept +{ + const NCollection_Vector* aVertices = myFaceToVertices.Seek(theFace); + if (aVertices == nullptr) + return; + + const NCollection_Vector aBoundVertices = *aVertices; + for (int anIdx = 0; anIdx < aBoundVertices.Length(); ++anIdx) + removePointOnSurface(aBoundVertices.Value(anIdx), theFace); +} + +//================================================================================================= + +void BRepGraph_ParamLayer::invalidateCoEdgeBindings(const BRepGraph_CoEdgeId theCoEdge) noexcept +{ + const NCollection_Vector* aVertices = myCoEdgeToVertices.Seek(theCoEdge); + if (aVertices == nullptr) + return; + + const NCollection_Vector aBoundVertices = *aVertices; + for (int anIdx = 0; anIdx < aBoundVertices.Length(); ++anIdx) + removePointOnPCurve(aBoundVertices.Value(anIdx), theCoEdge); +} + +//================================================================================================= + +void BRepGraph_ParamLayer::migrateVertexBindings(const BRepGraph_VertexId theOldVertex, + const BRepGraph_VertexId theNewVertex) noexcept +{ + const VertexParams* aParams = myVertexParams.Seek(theOldVertex); + if (aParams == nullptr) + return; + + const VertexParams aOldParams = *aParams; + removeVertexBindings(theOldVertex); + + for (const PointOnCurveEntry& anEntry : aOldParams.PointsOnCurve) + { + SetPointOnCurve(theNewVertex, anEntry.EdgeDefId, anEntry.Parameter); + } + for (const PointOnSurfaceEntry& anEntry : aOldParams.PointsOnSurface) + { + SetPointOnSurface(theNewVertex, anEntry.FaceDefId, anEntry.ParameterU, anEntry.ParameterV); + } + for (const PointOnPCurveEntry& anEntry : aOldParams.PointsOnPCurve) + { + SetPointOnPCurve(theNewVertex, anEntry.CoEdgeDefId, anEntry.Parameter); + } +} + +//================================================================================================= + +void BRepGraph_ParamLayer::migrateEdgeBindings(const BRepGraph_EdgeId theOldEdge, + const BRepGraph_EdgeId theNewEdge) noexcept +{ + const NCollection_Vector* aVertices = myEdgeToVertices.Seek(theOldEdge); + if (aVertices == nullptr) + return; + + const NCollection_Vector aBoundVertices = *aVertices; + for (int anIdx = 0; anIdx < aBoundVertices.Length(); ++anIdx) + { + double aParameter = 0.0; + if (!FindPointOnCurve(aBoundVertices.Value(anIdx), theOldEdge, &aParameter)) + continue; + removePointOnCurve(aBoundVertices.Value(anIdx), theOldEdge); + SetPointOnCurve(aBoundVertices.Value(anIdx), theNewEdge, aParameter); + } +} + +//================================================================================================= + +void BRepGraph_ParamLayer::migrateFaceBindings(const BRepGraph_FaceId theOldFace, + const BRepGraph_FaceId theNewFace) noexcept +{ + const NCollection_Vector* aVertices = myFaceToVertices.Seek(theOldFace); + if (aVertices == nullptr) + return; + + const NCollection_Vector aBoundVertices = *aVertices; + for (int anIdx = 0; anIdx < aBoundVertices.Length(); ++anIdx) + { + gp_Pnt2d aUV; + if (!FindPointOnSurface(aBoundVertices.Value(anIdx), theOldFace, &aUV)) + continue; + removePointOnSurface(aBoundVertices.Value(anIdx), theOldFace); + SetPointOnSurface(aBoundVertices.Value(anIdx), theNewFace, aUV.X(), aUV.Y()); + } +} + +//================================================================================================= + +void BRepGraph_ParamLayer::migrateCoEdgeBindings(const BRepGraph_CoEdgeId theOldCoEdge, + const BRepGraph_CoEdgeId theNewCoEdge) noexcept +{ + const NCollection_Vector* aVertices = myCoEdgeToVertices.Seek(theOldCoEdge); + if (aVertices == nullptr) + return; + + const NCollection_Vector aBoundVertices = *aVertices; + for (int anIdx = 0; anIdx < aBoundVertices.Length(); ++anIdx) + { + double aParameter = 0.0; + if (!FindPointOnPCurve(aBoundVertices.Value(anIdx), theOldCoEdge, &aParameter)) + continue; + removePointOnPCurve(aBoundVertices.Value(anIdx), theOldCoEdge); + SetPointOnPCurve(aBoundVertices.Value(anIdx), theNewCoEdge, aParameter); + } +} + +//================================================================================================= + +void BRepGraph_ParamLayer::OnNodeModified(const BRepGraph_NodeId theNode) noexcept +{ + switch (theNode.NodeKind) + { + case BRepGraph_NodeId::Kind::Vertex: + removeVertexBindings(BRepGraph_VertexId(theNode.Index)); + break; + case BRepGraph_NodeId::Kind::Edge: + invalidateEdgeBindings(BRepGraph_EdgeId(theNode.Index)); + break; + case BRepGraph_NodeId::Kind::Face: + invalidateFaceBindings(BRepGraph_FaceId(theNode.Index)); + break; + case BRepGraph_NodeId::Kind::CoEdge: + invalidateCoEdgeBindings(BRepGraph_CoEdgeId(theNode.Index)); + break; + default: + break; + } +} + +//================================================================================================= + +void BRepGraph_ParamLayer::OnNodesModified( + const NCollection_Vector& theModifiedNodes) noexcept +{ + for (const BRepGraph_NodeId& aModifiedNode : theModifiedNodes) + OnNodeModified(aModifiedNode); +} + +//================================================================================================= + +void BRepGraph_ParamLayer::OnNodeRemoved(const BRepGraph_NodeId theNode, + const BRepGraph_NodeId theReplacement) noexcept +{ + switch (theNode.NodeKind) + { + case BRepGraph_NodeId::Kind::Vertex: + if (theReplacement.NodeKind == BRepGraph_NodeId::Kind::Vertex && theReplacement.IsValid()) + migrateVertexBindings(BRepGraph_VertexId(theNode.Index), + BRepGraph_VertexId(theReplacement.Index)); + else + removeVertexBindings(BRepGraph_VertexId(theNode.Index)); + break; + case BRepGraph_NodeId::Kind::Edge: + if (theReplacement.NodeKind == BRepGraph_NodeId::Kind::Edge && theReplacement.IsValid()) + migrateEdgeBindings(BRepGraph_EdgeId(theNode.Index), + BRepGraph_EdgeId(theReplacement.Index)); + else + invalidateEdgeBindings(BRepGraph_EdgeId(theNode.Index)); + break; + case BRepGraph_NodeId::Kind::Face: + if (theReplacement.NodeKind == BRepGraph_NodeId::Kind::Face && theReplacement.IsValid()) + migrateFaceBindings(BRepGraph_FaceId(theNode.Index), + BRepGraph_FaceId(theReplacement.Index)); + else + invalidateFaceBindings(BRepGraph_FaceId(theNode.Index)); + break; + case BRepGraph_NodeId::Kind::CoEdge: + if (theReplacement.NodeKind == BRepGraph_NodeId::Kind::CoEdge && theReplacement.IsValid()) + { + migrateCoEdgeBindings(BRepGraph_CoEdgeId(theNode.Index), + BRepGraph_CoEdgeId(theReplacement.Index)); + } + else + { + invalidateCoEdgeBindings(BRepGraph_CoEdgeId(theNode.Index)); + } + break; + default: + break; + } +} + +//================================================================================================= + +void BRepGraph_ParamLayer::OnCompact( + const NCollection_DataMap& theRemapMap) noexcept +{ + NCollection_DataMap aNewParams; + NCollection_DataMap> aNewEdgeToVtx; + NCollection_DataMap> aNewFaceToVtx; + NCollection_DataMap> aNewCoEdgeToVtx; + + for (const auto& [aOldVertex, aOldParams] : myVertexParams.Items()) + { + const BRepGraph_VertexId aNewVertex = remapVertex(theRemapMap, aOldVertex); + if (!aNewVertex.IsValid()) + continue; + + VertexParams aNewVP; + + for (const PointOnCurveEntry& anOldEntry : aOldParams.PointsOnCurve) + { + const BRepGraph_EdgeId aNewEdge = remapEdge(theRemapMap, anOldEntry.EdgeDefId); + if (!aNewEdge.IsValid()) + continue; + PointOnCurveEntry& anEntry = aNewVP.PointsOnCurve.Appended(); + anEntry.Parameter = anOldEntry.Parameter; + anEntry.EdgeDefId = aNewEdge; + appendUnique(aNewEdgeToVtx, aNewEdge, aNewVertex); + } + for (const PointOnSurfaceEntry& anOldEntry : aOldParams.PointsOnSurface) + { + const BRepGraph_FaceId aNewFace = remapFace(theRemapMap, anOldEntry.FaceDefId); + if (!aNewFace.IsValid()) + continue; + PointOnSurfaceEntry& anEntry = aNewVP.PointsOnSurface.Appended(); + anEntry.ParameterU = anOldEntry.ParameterU; + anEntry.ParameterV = anOldEntry.ParameterV; + anEntry.FaceDefId = aNewFace; + appendUnique(aNewFaceToVtx, aNewFace, aNewVertex); + } + for (const PointOnPCurveEntry& anOldEntry : aOldParams.PointsOnPCurve) + { + const BRepGraph_CoEdgeId aNewCoEdge = remapCoEdge(theRemapMap, anOldEntry.CoEdgeDefId); + if (!aNewCoEdge.IsValid()) + continue; + PointOnPCurveEntry& anEntry = aNewVP.PointsOnPCurve.Appended(); + anEntry.Parameter = anOldEntry.Parameter; + anEntry.CoEdgeDefId = aNewCoEdge; + appendUnique(aNewCoEdgeToVtx, aNewCoEdge, aNewVertex); + } + + if (!aNewVP.IsEmpty()) + { + // Merge into existing entry if multiple old vertices remap to the same new vertex. + VertexParams* anExisting = aNewParams.ChangeSeek(aNewVertex); + if (anExisting != nullptr) + { + for (const PointOnCurveEntry& anEntry : aNewVP.PointsOnCurve) + anExisting->PointsOnCurve.Append(anEntry); + for (const PointOnSurfaceEntry& anEntry : aNewVP.PointsOnSurface) + anExisting->PointsOnSurface.Append(anEntry); + for (const PointOnPCurveEntry& anEntry : aNewVP.PointsOnPCurve) + anExisting->PointsOnPCurve.Append(anEntry); + } + else + { + aNewParams.Bind(aNewVertex, std::move(aNewVP)); + } + } + } + + myVertexParams = std::move(aNewParams); + myEdgeToVertices = std::move(aNewEdgeToVtx); + myFaceToVertices = std::move(aNewFaceToVtx); + myCoEdgeToVertices = std::move(aNewCoEdgeToVtx); +} + +//================================================================================================= + +void BRepGraph_ParamLayer::InvalidateAll() noexcept +{ + Clear(); +} + +//================================================================================================= + +void BRepGraph_ParamLayer::Clear() noexcept +{ + myVertexParams.Clear(); + myEdgeToVertices.Clear(); + myFaceToVertices.Clear(); + myCoEdgeToVertices.Clear(); +} diff --git a/src/ModelingData/TKBRep/BRepGraph/BRepGraph_ParamLayer.hxx b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_ParamLayer.hxx new file mode 100644 index 0000000000..69b23f9ef6 --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_ParamLayer.hxx @@ -0,0 +1,150 @@ +// Copyright (c) 2026 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 _BRepGraph_ParamLayer_HeaderFile +#define _BRepGraph_ParamLayer_HeaderFile + +#include + +#include +#include +#include + +//! @brief Stores vertex-on-curve, vertex-on-surface, and vertex-on-PCurve bindings. +class BRepGraph_ParamLayer : public BRepGraph_Layer +{ +public: + //! Return fixed layer type GUID. + [[nodiscard]] Standard_EXPORT static const Standard_GUID& GetID(); + + //! Return this layer type GUID. + [[nodiscard]] Standard_EXPORT const Standard_GUID& ID() const override; + + struct PointOnCurveEntry + { + double Parameter = 0.0; + BRepGraph_EdgeId EdgeDefId; + }; + + struct PointOnSurfaceEntry + { + double ParameterU = 0.0; + double ParameterV = 0.0; + BRepGraph_FaceId FaceDefId; + }; + + struct PointOnPCurveEntry + { + double Parameter = 0.0; + BRepGraph_CoEdgeId CoEdgeDefId; + }; + + struct VertexParams + { + NCollection_Vector PointsOnCurve; + NCollection_Vector PointsOnSurface; + NCollection_Vector PointsOnPCurve; + + [[nodiscard]] bool IsEmpty() const + { + return PointsOnCurve.IsEmpty() && PointsOnSurface.IsEmpty() && PointsOnPCurve.IsEmpty(); + } + }; + + Standard_EXPORT const VertexParams* FindVertexParams(const BRepGraph_VertexId theVertex) const; + + Standard_EXPORT bool FindPointOnCurve(const BRepGraph_VertexId theVertex, + const BRepGraph_EdgeId theEdge, + double* const theParameter = nullptr) const; + + Standard_EXPORT bool FindPointOnSurface(const BRepGraph_VertexId theVertex, + const BRepGraph_FaceId theFace, + gp_Pnt2d* const theUV = nullptr) const; + + Standard_EXPORT bool FindPointOnPCurve(const BRepGraph_VertexId theVertex, + const BRepGraph_CoEdgeId theCoEdge, + double* const theParameter = nullptr) const; + + Standard_EXPORT int NbPointsOnCurve(const BRepGraph_VertexId theVertex) const; + Standard_EXPORT int NbPointsOnSurface(const BRepGraph_VertexId theVertex) const; + Standard_EXPORT int NbPointsOnPCurve(const BRepGraph_VertexId theVertex) const; + + [[nodiscard]] bool HasBindings() const { return myVertexParams.Extent() != 0; } + + Standard_EXPORT void SetPointOnCurve(const BRepGraph_VertexId theVertex, + const BRepGraph_EdgeId theEdge, + const double theParameter); + + Standard_EXPORT void SetPointOnSurface(const BRepGraph_VertexId theVertex, + const BRepGraph_FaceId theFace, + const double theParameterU, + const double theParameterV); + + Standard_EXPORT void SetPointOnPCurve(const BRepGraph_VertexId theVertex, + const BRepGraph_CoEdgeId theCoEdge, + const double theParameter); + + Standard_EXPORT const TCollection_AsciiString& Name() const override; + [[nodiscard]] Standard_EXPORT int SubscribedKinds() const override; + Standard_EXPORT void OnNodeModified(const BRepGraph_NodeId theNode) noexcept override; + Standard_EXPORT void OnNodesModified( + const NCollection_Vector& theModifiedNodes) noexcept override; + Standard_EXPORT void OnNodeRemoved(const BRepGraph_NodeId theNode, + const BRepGraph_NodeId theReplacement) noexcept override; + Standard_EXPORT void OnCompact( + const NCollection_DataMap& theRemapMap) noexcept override; + Standard_EXPORT void InvalidateAll() noexcept override; + Standard_EXPORT void Clear() noexcept override; + + DEFINE_STANDARD_RTTIEXT(BRepGraph_ParamLayer, BRepGraph_Layer) + +private: + void removeVertexBindings(const BRepGraph_VertexId theVertex) noexcept; + void invalidateEdgeBindings(const BRepGraph_EdgeId theEdge) noexcept; + void invalidateFaceBindings(const BRepGraph_FaceId theFace) noexcept; + void invalidateCoEdgeBindings(const BRepGraph_CoEdgeId theCoEdge) noexcept; + void migrateVertexBindings(const BRepGraph_VertexId theOldVertex, + const BRepGraph_VertexId theNewVertex) noexcept; + void migrateEdgeBindings(const BRepGraph_EdgeId theOldEdge, + const BRepGraph_EdgeId theNewEdge) noexcept; + void migrateFaceBindings(const BRepGraph_FaceId theOldFace, + const BRepGraph_FaceId theNewFace) noexcept; + void migrateCoEdgeBindings(const BRepGraph_CoEdgeId theOldCoEdge, + const BRepGraph_CoEdgeId theNewCoEdge) noexcept; + + VertexParams& changeVertexParams(const BRepGraph_VertexId theVertex); + void bindEdgeToVertex(const BRepGraph_EdgeId theEdge, const BRepGraph_VertexId theVertex); + void bindFaceToVertex(const BRepGraph_FaceId theFace, const BRepGraph_VertexId theVertex); + void bindCoEdgeToVertex(const BRepGraph_CoEdgeId theCoEdge, const BRepGraph_VertexId theVertex); + void unbindEdgeFromVertex(const BRepGraph_EdgeId theEdge, + const BRepGraph_VertexId theVertex) noexcept; + void unbindFaceFromVertex(const BRepGraph_FaceId theFace, + const BRepGraph_VertexId theVertex) noexcept; + void unbindCoEdgeFromVertex(const BRepGraph_CoEdgeId theCoEdge, + const BRepGraph_VertexId theVertex) noexcept; + void removePointOnCurve(const BRepGraph_VertexId theVertex, + const BRepGraph_EdgeId theEdge) noexcept; + void removePointOnSurface(const BRepGraph_VertexId theVertex, + const BRepGraph_FaceId theFace) noexcept; + void removePointOnPCurve(const BRepGraph_VertexId theVertex, + const BRepGraph_CoEdgeId theCoEdge) noexcept; + +private: + NCollection_DataMap myVertexParams; + NCollection_DataMap> myEdgeToVertices; + NCollection_DataMap> myFaceToVertices; + NCollection_DataMap> + myCoEdgeToVertices; +}; + +#endif // _BRepGraph_ParamLayer_HeaderFile diff --git a/src/ModelingData/TKBRep/BRepGraph/BRepGraph_ParentExplorer.cxx b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_ParentExplorer.cxx new file mode 100644 index 0000000000..5324839c78 --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_ParentExplorer.cxx @@ -0,0 +1,1323 @@ +// Copyright (c) 2026 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 + +namespace +{ +static int parentExplorerKindDepth(const BRepGraph_NodeId::Kind theKind) +{ + static constexpr int THE_DEPTH[] = { + 2, // Kind::Solid=0 + 3, // Kind::Shell=1 + 4, // Kind::Face=2 + 5, // Kind::Wire=3 + 7, // Kind::Edge=4 + 8, // Kind::Vertex=5 + 0, // Kind::Compound=6 + 1, // Kind::CompSolid=7 + 6, // Kind::CoEdge=8 + 99, // gap=9 + 0, // Kind::Product=10 + 1, // Kind::Occurrence=11 + }; + return THE_DEPTH[static_cast(theKind)]; +} + +static BRepGraph_VertexRefId edgeVertexRefIdAt(const BRepGraphInc::EdgeDef& theEdge, + const int theRefIdx) +{ + if (theRefIdx == 0) + { + return theEdge.StartVertexRefId; + } + if (theRefIdx == 1) + { + return theEdge.EndVertexRefId; + } + + const int anInternalIdx = theRefIdx - 2; + if (anInternalIdx < 0 || anInternalIdx >= theEdge.InternalVertexRefIds.Length()) + { + return BRepGraph_VertexRefId(); + } + return theEdge.InternalVertexRefIds.Value(anInternalIdx); +} +} // namespace + +//================================================================================================= + +BRepGraph_ParentExplorer::BRepGraph_ParentExplorer(const BRepGraph& theGraph, + const BRepGraph_NodeId theNode) + : BRepGraph_ParentExplorer(theGraph, theNode, TraversalMode::Recursive) +{ +} + +//================================================================================================= + +BRepGraph_ParentExplorer::BRepGraph_ParentExplorer(const BRepGraph& theGraph, + const BRepGraph_NodeId theNode, + const TraversalMode theMode) + : myGraph(&theGraph), + myNode(theNode), + myMode(theMode), + myTargetKind(std::nullopt), + myAvoidKind(std::nullopt), + myEmitAvoidKind(false) +{ + startTraversal(); +} + +//================================================================================================= + +BRepGraph_ParentExplorer::BRepGraph_ParentExplorer( + const BRepGraph& theGraph, + const BRepGraph_NodeId theNode, + const std::optional& theAvoidKind, + const bool theEmitAvoidKind, + const TraversalMode theMode) + : myGraph(&theGraph), + myNode(theNode), + myMode(theMode), + myTargetKind(std::nullopt), + myAvoidKind(normalizeAvoidKind(theNode, std::nullopt, theAvoidKind)), + myEmitAvoidKind(theEmitAvoidKind) +{ + startTraversal(); +} + +//================================================================================================= + +BRepGraph_ParentExplorer::BRepGraph_ParentExplorer(const BRepGraph& theGraph, + const BRepGraph_NodeId theNode, + BRepGraph_NodeId::Kind theTargetKind) + : BRepGraph_ParentExplorer(theGraph, theNode, theTargetKind, TraversalMode::Recursive) +{ +} + +//================================================================================================= + +BRepGraph_ParentExplorer::BRepGraph_ParentExplorer(const BRepGraph& theGraph, + const BRepGraph_NodeId theNode, + BRepGraph_NodeId::Kind theTargetKind, + const TraversalMode theMode) + : myGraph(&theGraph), + myNode(theNode), + myMode(theMode), + myTargetKind(theTargetKind), + myAvoidKind(normalizeAvoidKind(theNode, theTargetKind, std::nullopt)), + myEmitAvoidKind(false) +{ + startTraversal(); +} + +//================================================================================================= + +BRepGraph_ParentExplorer::BRepGraph_ParentExplorer( + const BRepGraph& theGraph, + const BRepGraph_NodeId theNode, + BRepGraph_NodeId::Kind theTargetKind, + const std::optional& theAvoidKind, + const bool theEmitAvoidKind, + const TraversalMode theMode) + : myGraph(&theGraph), + myNode(theNode), + myMode(theMode), + myTargetKind(theTargetKind), + myAvoidKind(normalizeAvoidKind(theNode, theTargetKind, theAvoidKind)), + myEmitAvoidKind(theEmitAvoidKind) +{ + startTraversal(); +} + +//================================================================================================= + +void BRepGraph_ParentExplorer::startTraversal() +{ + myStackTop = -1; + myEmitIndex = -1; + myCurrentFrame = -1; + myCurrent = BRepGraph_NodeId(); + myLocation = TopLoc_Location(); + myOrientation = TopAbs_FORWARD; + myHasMore = false; + if (myNode.IsValid()) + { + StackFrame aStartFrame; + aStartFrame.Node = myNode; + pushFrame(aStartFrame); + } + advance(); +} + +//================================================================================================= + +void BRepGraph_ParentExplorer::Next() +{ + advance(); +} + +//================================================================================================= + +const TopLoc_Location& BRepGraph_ParentExplorer::LeafLocation() const +{ + static const TopLoc_Location THE_EMPTY_LOCATION; + return myHasMore ? myStack[0].AccLocation : THE_EMPTY_LOCATION; +} + +//================================================================================================= + +TopAbs_Orientation BRepGraph_ParentExplorer::LeafOrientation() const +{ + return myHasMore ? myStack[0].AccOrientation : TopAbs_FORWARD; +} + +//================================================================================================= + +bool BRepGraph_ParentExplorer::IsCurrentBranchRoot() const +{ + return myHasMore && myCurrentFrame == branchRootFrame(); +} + +//================================================================================================= + +void BRepGraph_ParentExplorer::advance() +{ + myHasMore = false; + myCurrent = BRepGraph_NodeId(); + myCurrentFrame = -1; + + if (myMode == TraversalMode::DirectParents) + { + if (myStackTop > 0) + { + popFrame(); + } + + while (myStackTop >= 0) + { + StackFrame aParentFrame; + if (!nextParentFrame(topFrame(), aParentFrame)) + { + popFrame(); + break; + } + + pushFrame(aParentFrame); + prepareCurrentBranch(); + + if (!shouldEmit(topFrame().Node)) + { + popFrame(); + continue; + } + + myCurrentFrame = myStackTop; + myCurrent = topFrame().Node; + myLocation = topFrame().AccLocation; + myOrientation = topFrame().AccOrientation; + myHasMore = true; + return; + } + return; + } + + if (myEmitIndex >= 1) + { + if (emitNextFromCurrentBranch()) + { + return; + } + backtrackAfterBranchEmission(); + } + + while (myStackTop >= 0) + { + StackFrame aParentFrame; + if (nextParentFrame(topFrame(), aParentFrame)) + { + if (matchesAvoid(aParentFrame.Node)) + { + pushFrame(aParentFrame); + prepareCurrentBranch(); + myEmitIndex = 1; + if (emitNextFromCurrentBranch()) + { + return; + } + popFrame(); + myEmitIndex = -1; + continue; + } + + pushFrame(aParentFrame); + continue; + } + + if (myStackTop > 0) + { + prepareCurrentBranch(); + myEmitIndex = 1; + if (emitNextFromCurrentBranch()) + { + return; + } + popFrame(); + myEmitIndex = -1; + continue; + } + + popFrame(); + } +} + +//================================================================================================= + +void BRepGraph_ParentExplorer::backtrackAfterBranchEmission() +{ + myEmitIndex = -1; + while (myStackTop >= 0) + { + popFrame(); + if (myStackTop < 0) + { + return; + } + + StackFrame aProbe = topFrame(); + StackFrame aNextFrame; + if (nextParentFrame(aProbe, aNextFrame)) + { + return; + } + } +} + +//================================================================================================= + +bool BRepGraph_ParentExplorer::emitNextFromCurrentBranch() +{ + while (myEmitIndex >= 1 && myEmitIndex <= myStackTop) + { + const int aFrameIdx = myEmitIndex++; + const StackFrame& aFrame = myStack[aFrameIdx]; + if (!shouldEmit(aFrame.Node)) + { + continue; + } + + myCurrentFrame = aFrameIdx; + myCurrent = aFrame.Node; + myLocation = aFrame.AccLocation; + myOrientation = aFrame.AccOrientation; + myHasMore = true; + return true; + } + return false; +} + +//================================================================================================= + +std::optional BRepGraph_ParentExplorer::normalizeAvoidKind( + const BRepGraph_NodeId theNode, + const std::optional& theTargetKind, + const std::optional& theAvoidKind) +{ + if (!theAvoidKind.has_value() || !theNode.IsValid()) + { + return theAvoidKind; + } + + if (!theTargetKind.has_value()) + { + return theAvoidKind; + } + + if (!canContainTarget(*theTargetKind, *theAvoidKind)) + { + return std::nullopt; + } + + return (theNode.NodeKind == *theAvoidKind || canContainTarget(*theAvoidKind, theNode.NodeKind)) + ? theAvoidKind + : std::nullopt; +} + +//================================================================================================= + +bool BRepGraph_ParentExplorer::canContainTarget(const BRepGraph_NodeId::Kind theParentKind, + const BRepGraph_NodeId::Kind theTargetKind) +{ + return parentExplorerKindDepth(theParentKind) < parentExplorerKindDepth(theTargetKind); +} + +//================================================================================================= + +bool BRepGraph_ParentExplorer::nextParentFrame(StackFrame& theChild, StackFrame& theParent) const +{ + using Kind = BRepGraph_NodeId::Kind; + + const BRepGraph::TopoView& aTopo = myGraph->Topo(); + + for (;;) + { + const int aParentIdx = theChild.NextParentIdx++; + + switch (theChild.Node.NodeKind) + { + case Kind::Vertex: { + const BRepGraph_VertexId aVertexId(theChild.Node.Index); + const NCollection_Vector& aEdges = aTopo.Vertices().Edges(aVertexId); + if (aParentIdx >= aEdges.Length()) + { + return false; + } + + const BRepGraph_EdgeId anEdgeId = aEdges.Value(aParentIdx); + const BRepGraphInc::EdgeDef& anEdge = aTopo.Edges().Definition(anEdgeId); + if (anEdge.IsRemoved) + { + continue; + } + + const int aStepToChild = findEdgeVertexStep(anEdgeId, aVertexId); + if (aStepToChild < 0) + { + continue; + } + + theParent.Node = anEdgeId; + theParent.NextParentIdx = 0; + theParent.StepToChild = aStepToChild; + return true; + } + + case Kind::Edge: { + const BRepGraph_EdgeId aEdgeId(theChild.Node.Index); + const NCollection_Vector& aCoEdges = aTopo.Edges().CoEdges(aEdgeId); + if (aParentIdx >= aCoEdges.Length()) + { + BRepGraph_ProductId aProductId; + if (!findNthProductWrapper(theChild.Node, aParentIdx - aCoEdges.Length(), aProductId)) + { + return false; + } + + theParent.Node = aProductId; + theParent.NextParentIdx = 0; + theParent.StepToChild = -1; + return true; + } + + const BRepGraph_CoEdgeId aCoEdgeId = aCoEdges.Value(aParentIdx); + const BRepGraphInc::CoEdgeDef& aCoEdge = aTopo.CoEdges().Definition(aCoEdgeId); + if (aCoEdge.IsRemoved) + { + continue; + } + + theParent.Node = aCoEdgeId; + theParent.NextParentIdx = 0; + theParent.StepToChild = -1; + return true; + } + + case Kind::Wire: { + const BRepGraph_WireId aWireId(theChild.Node.Index); + const NCollection_Vector& aFaces = aTopo.Wires().Faces(aWireId); + if (aParentIdx >= aFaces.Length()) + { + BRepGraph_ProductId aProductId; + if (!findNthProductWrapper(theChild.Node, aParentIdx - aFaces.Length(), aProductId)) + { + return false; + } + + theParent.Node = aProductId; + theParent.NextParentIdx = 0; + theParent.StepToChild = -1; + return true; + } + + const BRepGraph_FaceId aFaceId = aFaces.Value(aParentIdx); + const BRepGraphInc::FaceDef& aFace = aTopo.Faces().Definition(aFaceId); + if (aFace.IsRemoved) + { + continue; + } + + const int aStepToChild = findFaceChildStep(aFaceId, theChild.Node); + if (aStepToChild < 0) + { + continue; + } + + theParent.Node = aFaceId; + theParent.NextParentIdx = 0; + theParent.StepToChild = aStepToChild; + return true; + } + + case Kind::Face: { + const BRepGraph_FaceId aFaceId(theChild.Node.Index); + const NCollection_Vector& aShells = aTopo.Faces().Shells(aFaceId); + const NCollection_Vector& aCompounds = + aTopo.Faces().Compounds(aFaceId); + const int aNbShells = aShells.Length(); + const int aNbCompounds = aCompounds.Length(); + if (aParentIdx < aNbShells) + { + const BRepGraph_ShellId aShellId = aShells.Value(aParentIdx); + const BRepGraphInc::ShellDef& aShell = aTopo.Shells().Definition(aShellId); + if (aShell.IsRemoved) + { + continue; + } + + const int aStepToChild = findShellChildStep(aShellId, theChild.Node); + if (aStepToChild < 0) + { + continue; + } + + theParent.Node = aShellId; + theParent.NextParentIdx = 0; + theParent.StepToChild = aStepToChild; + return true; + } + if (aParentIdx < aNbShells + aNbCompounds) + { + const BRepGraph_CompoundId aCompoundId = aCompounds.Value(aParentIdx - aNbShells); + const BRepGraphInc::CompoundDef& aCompound = aTopo.Compounds().Definition(aCompoundId); + if (aCompound.IsRemoved) + { + continue; + } + + const int aStepToChild = findCompoundChildStep(aCompoundId, theChild.Node); + if (aStepToChild < 0) + { + continue; + } + + theParent.Node = aCompoundId; + theParent.NextParentIdx = 0; + theParent.StepToChild = aStepToChild; + return true; + } + + BRepGraph_ProductId aProductId; + if (!findNthProductWrapper(theChild.Node, + aParentIdx - aNbShells - aNbCompounds, + aProductId)) + { + return false; + } + + theParent.Node = aProductId; + theParent.NextParentIdx = 0; + theParent.StepToChild = -1; + return true; + } + + case Kind::Shell: { + const BRepGraph_ShellId aShellId = BRepGraph_ShellId(theChild.Node.Index); + const NCollection_Vector& aSolids = aTopo.Shells().Solids(aShellId); + const NCollection_Vector& aCompounds = + aTopo.Shells().Compounds(aShellId); + const int aNbSolids = aSolids.Length(); + const int aNbCompounds = aCompounds.Length(); + if (aParentIdx < aNbSolids) + { + const BRepGraph_SolidId aSolidId = aSolids.Value(aParentIdx); + const BRepGraphInc::SolidDef& aSolid = aTopo.Solids().Definition(aSolidId); + if (aSolid.IsRemoved) + { + continue; + } + + const int aStepToChild = findSolidChildStep(aSolidId, theChild.Node); + if (aStepToChild < 0) + { + continue; + } + + theParent.Node = aSolidId; + theParent.NextParentIdx = 0; + theParent.StepToChild = aStepToChild; + return true; + } + if (aParentIdx < aNbSolids + aNbCompounds) + { + const BRepGraph_CompoundId aCompoundId = aCompounds.Value(aParentIdx - aNbSolids); + const BRepGraphInc::CompoundDef& aCompound = aTopo.Compounds().Definition(aCompoundId); + if (aCompound.IsRemoved) + { + continue; + } + + const int aStepToChild = findCompoundChildStep(aCompoundId, theChild.Node); + if (aStepToChild < 0) + { + continue; + } + + theParent.Node = aCompoundId; + theParent.NextParentIdx = 0; + theParent.StepToChild = aStepToChild; + return true; + } + + BRepGraph_ProductId aProductId; + if (!findNthProductWrapper(theChild.Node, + aParentIdx - aNbSolids - aNbCompounds, + aProductId)) + { + return false; + } + + theParent.Node = aProductId; + theParent.NextParentIdx = 0; + theParent.StepToChild = -1; + return true; + } + + case Kind::Solid: { + const BRepGraph_SolidId aSolidId = BRepGraph_SolidId(theChild.Node.Index); + const NCollection_Vector& aCompSolids = + aTopo.Solids().CompSolids(aSolidId); + const NCollection_Vector& aCompounds = + aTopo.Solids().Compounds(aSolidId); + const int aNbCompSolids = aCompSolids.Length(); + const int aNbCompounds = aCompounds.Length(); + if (aParentIdx < aNbCompSolids) + { + const BRepGraph_CompSolidId aCompSolidId = aCompSolids.Value(aParentIdx); + const BRepGraphInc::CompSolidDef& aCompSolid = + aTopo.CompSolids().Definition(aCompSolidId); + if (aCompSolid.IsRemoved) + { + continue; + } + + const int aStepToChild = findCompSolidSolidStep(aCompSolidId, aSolidId); + if (aStepToChild < 0) + { + continue; + } + + theParent.Node = aCompSolidId; + theParent.NextParentIdx = 0; + theParent.StepToChild = aStepToChild; + return true; + } + if (aParentIdx < aNbCompSolids + aNbCompounds) + { + const BRepGraph_CompoundId aCompoundId = aCompounds.Value(aParentIdx - aNbCompSolids); + const BRepGraphInc::CompoundDef& aCompound = aTopo.Compounds().Definition(aCompoundId); + if (aCompound.IsRemoved) + { + continue; + } + + const int aStepToChild = findCompoundChildStep(aCompoundId, theChild.Node); + if (aStepToChild < 0) + { + continue; + } + + theParent.Node = aCompoundId; + theParent.NextParentIdx = 0; + theParent.StepToChild = aStepToChild; + return true; + } + + BRepGraph_ProductId aProductId; + if (!findNthProductWrapper(theChild.Node, + aParentIdx - aNbCompSolids - aNbCompounds, + aProductId)) + { + return false; + } + + theParent.Node = aProductId; + theParent.NextParentIdx = 0; + theParent.StepToChild = -1; + return true; + } + + case Kind::Compound: { + const BRepGraph_CompoundId aCompoundId(theChild.Node.Index); + const NCollection_Vector& aParents = + aTopo.Compounds().ParentCompounds(aCompoundId); + const int aNbParents = aParents.Length(); + if (aParentIdx < aNbParents) + { + const BRepGraph_CompoundId aParentCompoundId = aParents.Value(aParentIdx); + const BRepGraphInc::CompoundDef& aParentCompound = + aTopo.Compounds().Definition(aParentCompoundId); + if (aParentCompound.IsRemoved) + { + continue; + } + + const int aStepToChild = findCompoundChildStep(aParentCompoundId, theChild.Node); + if (aStepToChild < 0) + { + continue; + } + + theParent.Node = aParentCompoundId; + theParent.NextParentIdx = 0; + theParent.StepToChild = aStepToChild; + return true; + } + + BRepGraph_ProductId aProductId; + if (!findNthProductWrapper(theChild.Node, aParentIdx - aNbParents, aProductId)) + { + return false; + } + + theParent.Node = aProductId; + theParent.NextParentIdx = 0; + theParent.StepToChild = -1; + return true; + } + + case Kind::CompSolid: { + const BRepGraph_CompSolidId aCompSolidId(theChild.Node.Index); + const NCollection_Vector& aParents = + aTopo.CompSolids().Compounds(aCompSolidId); + const int aNbParents = aParents.Length(); + if (aParentIdx < aNbParents) + { + const BRepGraph_CompoundId aParentCompoundId = aParents.Value(aParentIdx); + const BRepGraphInc::CompoundDef& aParentCompound = + aTopo.Compounds().Definition(aParentCompoundId); + if (aParentCompound.IsRemoved) + { + continue; + } + + const int aStepToChild = findCompoundChildStep(aParentCompoundId, theChild.Node); + if (aStepToChild < 0) + { + continue; + } + + theParent.Node = aParentCompoundId; + theParent.NextParentIdx = 0; + theParent.StepToChild = aStepToChild; + return true; + } + + BRepGraph_ProductId aProductId; + if (!findNthProductWrapper(theChild.Node, aParentIdx - aNbParents, aProductId)) + { + return false; + } + + theParent.Node = aProductId; + theParent.NextParentIdx = 0; + theParent.StepToChild = -1; + return true; + } + + case Kind::CoEdge: { + const BRepGraph_CoEdgeId aCoEdgeId(theChild.Node.Index); + const NCollection_Vector& aWires = aTopo.CoEdges().Wires(aCoEdgeId); + const int aNbWires = aWires.Length(); + if (aParentIdx >= aNbWires) + { + return false; + } + + const BRepGraph_WireId aWireId = aWires.Value(aParentIdx); + const BRepGraphInc::WireDef& aWire = aTopo.Wires().Definition(aWireId); + if (aWire.IsRemoved) + { + continue; + } + + const int aStepToChild = findWireCoEdgeStep(aWireId, aCoEdgeId); + if (aStepToChild < 0) + { + continue; + } + + theParent.Node = aWireId; + theParent.NextParentIdx = 0; + theParent.StepToChild = aStepToChild; + return true; + } + + case Kind::Product: { + const BRepGraph_ProductId aProductId(theChild.Node.Index); + const NCollection_Vector& anOccurrences = + aTopo.Products().Instances(aProductId); + const int aNbOccurrences = anOccurrences.Length(); + if (aParentIdx >= aNbOccurrences) + { + return false; + } + + const BRepGraph_OccurrenceId anOccurrenceId = anOccurrences.Value(aParentIdx); + const BRepGraphInc::OccurrenceDef& anOccurrence = + aTopo.Occurrences().Definition(anOccurrenceId); + if (anOccurrence.IsRemoved) + { + continue; + } + + theParent.Node = anOccurrenceId; + theParent.NextParentIdx = 0; + theParent.StepToChild = -1; + return true; + } + + case Kind::Occurrence: { + if (aParentIdx > 0) + { + return false; + } + + const BRepGraph_OccurrenceId anOccurrenceId(theChild.Node.Index); + const BRepGraphInc::OccurrenceDef& anOccurrence = + aTopo.Occurrences().Definition(anOccurrenceId); + if (anOccurrence.IsRemoved || !anOccurrence.ParentProductDefId.IsValid()) + { + return false; + } + + const int aStepToChild = + findOccurrenceStep(anOccurrence.ParentProductDefId, anOccurrenceId); + if (aStepToChild < 0) + { + return false; + } + + theParent.Node = anOccurrence.ParentProductDefId; + theParent.NextParentIdx = 0; + theParent.StepToChild = aStepToChild; + return true; + } + + default: + return false; + } + } +} + +//================================================================================================= + +void BRepGraph_ParentExplorer::prepareCurrentBranch() +{ + if (myStackTop < 0) + { + return; + } + + myStack[myStackTop].AccLocation = TopLoc_Location(); + myStack[myStackTop].AccOrientation = TopAbs_FORWARD; + for (int aFrameIdx = myStackTop - 1; aFrameIdx >= 0; --aFrameIdx) + { + myStack[aFrameIdx].AccLocation = myStack[aFrameIdx + 1].AccLocation; + myStack[aFrameIdx].AccOrientation = myStack[aFrameIdx + 1].AccOrientation; + applyTransition(myStack[aFrameIdx + 1].Node, + myStack[aFrameIdx + 1].StepToChild, + myStack[aFrameIdx].AccLocation, + myStack[aFrameIdx].AccOrientation); + } +} + +//================================================================================================= + +void BRepGraph_ParentExplorer::applyTransition(const BRepGraph_NodeId theParent, + const int theStepToChild, + TopLoc_Location& theLocation, + TopAbs_Orientation& theOrientation) const +{ + if (theStepToChild >= 0) + { + theLocation = theLocation * stepLocation(theParent, theStepToChild); + theOrientation = TopAbs::Compose(theOrientation, stepOrientation(theParent, theStepToChild)); + return; + } + + const BRepGraph::TopoView& aTopo = myGraph->Topo(); + switch (theParent.NodeKind) + { + case BRepGraph_NodeId::Kind::Occurrence: { + const BRepGraphInc::OccurrenceDef& anOcc = + aTopo.Occurrences().Definition(BRepGraph_OccurrenceId(theParent.Index)); + if (!anOcc.IsRemoved) + { + theLocation = theLocation * anOcc.Placement; + } + return; + } + + case BRepGraph_NodeId::Kind::CoEdge: { + const BRepGraphInc::CoEdgeDef& aCoEdge = + aTopo.CoEdges().Definition(BRepGraph_CoEdgeId(theParent.Index)); + if (!aCoEdge.IsRemoved) + { + theOrientation = TopAbs::Compose(theOrientation, aCoEdge.Sense); + } + return; + } + + case BRepGraph_NodeId::Kind::Product: { + const BRepGraphInc::ProductDef& aProduct = + aTopo.Products().Definition(BRepGraph_ProductId(theParent.Index)); + if (!aProduct.IsRemoved && aProduct.ShapeRootId.IsValid()) + { + theLocation = theLocation * aProduct.RootLocation; + theOrientation = TopAbs::Compose(theOrientation, aProduct.RootOrientation); + } + return; + } + + default: + return; + } +} + +//================================================================================================= + +int BRepGraph_ParentExplorer::branchRootFrame() const +{ + if (myStackTop < 0) + { + return -1; + } + + int aFrameIdx = myStackTop; + while (aFrameIdx > 0 && myStack[aFrameIdx].StepToChild < 0) + { + --aFrameIdx; + } + return aFrameIdx; +} + +//================================================================================================= + +bool BRepGraph_ParentExplorer::findNthProductWrapper(const BRepGraph_NodeId theNode, + const int theOrdinal, + BRepGraph_ProductId& theProduct) const +{ + if (theOrdinal < 0) + { + return false; + } + + const BRepGraph::TopoView& aTopo = myGraph->Topo(); + int aCount = 0; + for (int aPass = 0; aPass < 2; ++aPass) + { + for (BRepGraph_ProductIterator aProdIt(*myGraph); aProdIt.More(); aProdIt.Next()) + { + const BRepGraph_ProductId aProductId = aProdIt.CurrentId(); + const BRepGraphInc::ProductDef& aProductDef = aProdIt.Current(); + if (aProductDef.ShapeRootId != theNode) + { + continue; + } + + const bool hasInstances = !aTopo.Products().Instances(aProductId).IsEmpty(); + if ((aPass == 0 && !hasInstances) || (aPass == 1 && hasInstances)) + { + continue; + } + + if (aCount == theOrdinal) + { + theProduct = aProductId; + return true; + } + ++aCount; + } + } + return false; +} + +//================================================================================================= + +int BRepGraph_ParentExplorer::findOccurrenceStep(const BRepGraph_ProductId theParentProduct, + const BRepGraph_OccurrenceId theOccurrence) const +{ + for (BRepGraph_RefsOccurrenceOfProduct aRefIt(*myGraph, theParentProduct); aRefIt.More(); + aRefIt.Next()) + { + const BRepGraphInc::OccurrenceRef& aRef = + myGraph->Refs().Occurrences().Entry(aRefIt.CurrentId()); + if (aRef.OccurrenceDefId == theOccurrence) + { + return aRefIt.Index(); + } + } + return -1; +} + +//================================================================================================= + +int BRepGraph_ParentExplorer::findCompoundChildStep(const BRepGraph_CompoundId theParent, + const BRepGraph_NodeId theChild) const +{ + for (BRepGraph_RefsChildOfCompound aRefIt(*myGraph, theParent); aRefIt.More(); aRefIt.Next()) + { + const BRepGraphInc::ChildRef& aRef = myGraph->Refs().Children().Entry(aRefIt.CurrentId()); + if (aRef.ChildDefId == theChild) + { + return aRefIt.Index(); + } + } + return -1; +} + +//================================================================================================= + +int BRepGraph_ParentExplorer::findCompSolidSolidStep(const BRepGraph_CompSolidId theParent, + const BRepGraph_SolidId theChild) const +{ + for (BRepGraph_RefsSolidOfCompSolid aRefIt(*myGraph, theParent); aRefIt.More(); aRefIt.Next()) + { + const BRepGraphInc::SolidRef& aRef = myGraph->Refs().Solids().Entry(aRefIt.CurrentId()); + if (aRef.SolidDefId == theChild) + { + return aRefIt.Index(); + } + } + return -1; +} + +//================================================================================================= + +int BRepGraph_ParentExplorer::findSolidChildStep(const BRepGraph_SolidId theParent, + const BRepGraph_NodeId theChild) const +{ + const BRepGraph::TopoView& aTopo = myGraph->Topo(); + const BRepGraph::RefsView& aRefs = myGraph->Refs(); + const BRepGraphInc::SolidDef& aSolid = aTopo.Solids().Definition(theParent); + if (theChild.NodeKind == BRepGraph_NodeId::Kind::Shell) + { + for (BRepGraph_RefsShellOfSolid aRefIt(*myGraph, theParent); aRefIt.More(); aRefIt.Next()) + { + const BRepGraphInc::ShellRef& aRef = aRefs.Shells().Entry(aRefIt.CurrentId()); + if (aRef.ShellDefId == BRepGraph_ShellId(theChild.Index)) + { + return aRefIt.Index(); + } + } + return -1; + } + + for (int aRefIdx = 0; aRefIdx < aSolid.FreeChildRefIds.Length(); ++aRefIdx) + { + const BRepGraphInc::ChildRef& aRef = + aRefs.Children().Entry(aSolid.FreeChildRefIds.Value(aRefIdx)); + if (!aRef.IsRemoved && aRef.ChildDefId == theChild) + { + return aSolid.ShellRefIds.Length() + aRefIdx; + } + } + return -1; +} + +//================================================================================================= + +int BRepGraph_ParentExplorer::findShellChildStep(const BRepGraph_ShellId theParent, + const BRepGraph_NodeId theChild) const +{ + const BRepGraph::TopoView& aTopo = myGraph->Topo(); + const BRepGraph::RefsView& aRefs = myGraph->Refs(); + const BRepGraphInc::ShellDef& aShell = aTopo.Shells().Definition(theParent); + if (theChild.NodeKind == BRepGraph_NodeId::Kind::Face) + { + for (BRepGraph_RefsFaceOfShell aRefIt(*myGraph, theParent); aRefIt.More(); aRefIt.Next()) + { + const BRepGraphInc::FaceRef& aRef = aRefs.Faces().Entry(aRefIt.CurrentId()); + if (aRef.FaceDefId == BRepGraph_FaceId(theChild.Index)) + { + return aRefIt.Index(); + } + } + return -1; + } + + for (int aRefIdx = 0; aRefIdx < aShell.FreeChildRefIds.Length(); ++aRefIdx) + { + const BRepGraphInc::ChildRef& aRef = + aRefs.Children().Entry(aShell.FreeChildRefIds.Value(aRefIdx)); + if (!aRef.IsRemoved && aRef.ChildDefId == theChild) + { + return aShell.FaceRefIds.Length() + aRefIdx; + } + } + return -1; +} + +//================================================================================================= + +int BRepGraph_ParentExplorer::findFaceChildStep(const BRepGraph_FaceId theParent, + const BRepGraph_NodeId theChild) const +{ + const BRepGraph::TopoView& aTopo = myGraph->Topo(); + const BRepGraph::RefsView& aRefs = myGraph->Refs(); + const BRepGraphInc::FaceDef& aFace = aTopo.Faces().Definition(theParent); + if (theChild.NodeKind == BRepGraph_NodeId::Kind::Wire) + { + for (BRepGraph_RefsWireOfFace aRefIt(*myGraph, theParent); aRefIt.More(); aRefIt.Next()) + { + const BRepGraphInc::WireRef& aRef = aRefs.Wires().Entry(aRefIt.CurrentId()); + if (aRef.WireDefId == BRepGraph_WireId(theChild.Index)) + { + return aRefIt.Index(); + } + } + return -1; + } + + if (theChild.NodeKind == BRepGraph_NodeId::Kind::Vertex) + { + for (BRepGraph_RefsVertexOfFace aRefIt(*myGraph, theParent); aRefIt.More(); aRefIt.Next()) + { + const BRepGraphInc::VertexRef& aRef = aRefs.Vertices().Entry(aRefIt.CurrentId()); + if (aRef.VertexDefId == BRepGraph_VertexId(theChild.Index)) + { + return aFace.WireRefIds.Length() + aRefIt.Index(); + } + } + } + return -1; +} + +//================================================================================================= + +int BRepGraph_ParentExplorer::findWireCoEdgeStep(const BRepGraph_WireId theParent, + const BRepGraph_CoEdgeId theChild) const +{ + for (BRepGraph_RefsCoEdgeOfWire aRefIt(*myGraph, theParent); aRefIt.More(); aRefIt.Next()) + { + const BRepGraphInc::CoEdgeRef& aRef = myGraph->Refs().CoEdges().Entry(aRefIt.CurrentId()); + if (aRef.CoEdgeDefId == theChild) + { + return aRefIt.Index(); + } + } + return -1; +} + +//================================================================================================= + +int BRepGraph_ParentExplorer::findEdgeVertexStep(const BRepGraph_EdgeId theParent, + const BRepGraph_VertexId theChild) const +{ + for (BRepGraph_RefsVertexOfEdge aRefIt(*myGraph, theParent); aRefIt.More(); aRefIt.Next()) + { + const BRepGraphInc::VertexRef& aRef = myGraph->Refs().Vertices().Entry(aRefIt.CurrentId()); + if (aRef.VertexDefId == theChild) + { + return aRefIt.Index(); + } + } + return -1; +} + +//================================================================================================= + +void BRepGraph_ParentExplorer::pushFrame(const StackFrame& theFrame) +{ + const BRepGraph::TopoView& aTopo = myGraph->Topo(); + const int aMaxDepth = aTopo.Compounds().Nb() + aTopo.CompSolids().Nb() + aTopo.Solids().Nb() + + aTopo.Shells().Nb() + aTopo.Faces().Nb() + aTopo.Wires().Nb() + + aTopo.Edges().Nb() + aTopo.Vertices().Nb() + aTopo.Products().Nb() + + aTopo.Occurrences().Nb() + aTopo.CoEdges().Nb(); + if (myStackTop >= aMaxDepth) + { + return; + } + + ++myStackTop; + if (static_cast(myStackTop) >= myStack.Size()) + { + const size_t aNewSize = + std::max(myStack.Size() * 2, static_cast(THE_INLINE_STACK_SIZE)); + myStack.Reallocate(aNewSize, true); + } + myStack[myStackTop] = theFrame; +} + +//================================================================================================= + +void BRepGraph_ParentExplorer::popFrame() +{ + if (myStackTop >= 0) + { + --myStackTop; + } +} + +//================================================================================================= + +TopLoc_Location BRepGraph_ParentExplorer::stepLocation(const BRepGraph_NodeId theParent, + const int theRefIdx) const +{ + const BRepGraph::TopoView& aTopo = myGraph->Topo(); + const BRepGraph::RefsView& aRefs = myGraph->Refs(); + + switch (theParent.NodeKind) + { + case BRepGraph_NodeId::Kind::Product: + return TopLoc_Location(); + case BRepGraph_NodeId::Kind::Compound: { + return aRefs.Children() + .Entry(aRefs.Children().IdsOf(BRepGraph_CompoundId(theParent.Index)).Value(theRefIdx)) + .LocalLocation; + } + case BRepGraph_NodeId::Kind::CompSolid: { + return aRefs.Solids() + .Entry(aRefs.Solids().IdsOf(BRepGraph_CompSolidId(theParent.Index)).Value(theRefIdx)) + .LocalLocation; + } + case BRepGraph_NodeId::Kind::Solid: { + const BRepGraphInc::SolidDef& aSolid = + aTopo.Solids().Definition(BRepGraph_SolidId(theParent.Index)); + if (theRefIdx < aSolid.ShellRefIds.Length()) + { + return aRefs.Shells().Entry(aSolid.ShellRefIds.Value(theRefIdx)).LocalLocation; + } + return aRefs.Children() + .Entry(aSolid.FreeChildRefIds.Value(theRefIdx - aSolid.ShellRefIds.Length())) + .LocalLocation; + } + case BRepGraph_NodeId::Kind::Shell: { + const BRepGraphInc::ShellDef& aShell = + aTopo.Shells().Definition(BRepGraph_ShellId(theParent.Index)); + if (theRefIdx < aShell.FaceRefIds.Length()) + { + return aRefs.Faces().Entry(aShell.FaceRefIds.Value(theRefIdx)).LocalLocation; + } + return aRefs.Children() + .Entry(aShell.FreeChildRefIds.Value(theRefIdx - aShell.FaceRefIds.Length())) + .LocalLocation; + } + case BRepGraph_NodeId::Kind::Face: { + const BRepGraphInc::FaceDef& aFace = + aTopo.Faces().Definition(BRepGraph_FaceId(theParent.Index)); + if (theRefIdx < aFace.WireRefIds.Length()) + { + return aRefs.Wires().Entry(aFace.WireRefIds.Value(theRefIdx)).LocalLocation; + } + return aRefs.Vertices() + .Entry(aFace.VertexRefIds.Value(theRefIdx - aFace.WireRefIds.Length())) + .LocalLocation; + } + case BRepGraph_NodeId::Kind::Wire: + return aRefs.CoEdges() + .Entry(aRefs.CoEdges().IdsOf(BRepGraph_WireId(theParent.Index)).Value(theRefIdx)) + .LocalLocation; + case BRepGraph_NodeId::Kind::Edge: { + const BRepGraphInc::EdgeDef& anEdge = + aTopo.Edges().Definition(BRepGraph_EdgeId(theParent.Index)); + const BRepGraph_VertexRefId aVertexRefId = edgeVertexRefIdAt(anEdge, theRefIdx); + if (!aVertexRefId.IsValid()) + { + return TopLoc_Location(); + } + return aRefs.Vertices().Entry(aVertexRefId).LocalLocation; + } + default: + return TopLoc_Location(); + } +} + +//================================================================================================= + +TopAbs_Orientation BRepGraph_ParentExplorer::stepOrientation(const BRepGraph_NodeId theParent, + const int theRefIdx) const +{ + const BRepGraph::TopoView& aTopo = myGraph->Topo(); + const BRepGraph::RefsView& aRefs = myGraph->Refs(); + + switch (theParent.NodeKind) + { + case BRepGraph_NodeId::Kind::Product: + return TopAbs_FORWARD; + case BRepGraph_NodeId::Kind::Compound: + return aRefs.Children() + .Entry(aRefs.Children().IdsOf(BRepGraph_CompoundId(theParent.Index)).Value(theRefIdx)) + .Orientation; + case BRepGraph_NodeId::Kind::CompSolid: + return aRefs.Solids() + .Entry(aRefs.Solids().IdsOf(BRepGraph_CompSolidId(theParent.Index)).Value(theRefIdx)) + .Orientation; + case BRepGraph_NodeId::Kind::Solid: { + const BRepGraphInc::SolidDef& aSolid = + aTopo.Solids().Definition(BRepGraph_SolidId(theParent.Index)); + if (theRefIdx < aSolid.ShellRefIds.Length()) + { + return aRefs.Shells().Entry(aSolid.ShellRefIds.Value(theRefIdx)).Orientation; + } + return aRefs.Children() + .Entry(aSolid.FreeChildRefIds.Value(theRefIdx - aSolid.ShellRefIds.Length())) + .Orientation; + } + case BRepGraph_NodeId::Kind::Shell: { + const BRepGraphInc::ShellDef& aShell = + aTopo.Shells().Definition(BRepGraph_ShellId(theParent.Index)); + if (theRefIdx < aShell.FaceRefIds.Length()) + { + return aRefs.Faces().Entry(aShell.FaceRefIds.Value(theRefIdx)).Orientation; + } + return aRefs.Children() + .Entry(aShell.FreeChildRefIds.Value(theRefIdx - aShell.FaceRefIds.Length())) + .Orientation; + } + case BRepGraph_NodeId::Kind::Face: { + const BRepGraphInc::FaceDef& aFace = + aTopo.Faces().Definition(BRepGraph_FaceId(theParent.Index)); + if (theRefIdx < aFace.WireRefIds.Length()) + { + return aRefs.Wires().Entry(aFace.WireRefIds.Value(theRefIdx)).Orientation; + } + return aRefs.Vertices() + .Entry(aFace.VertexRefIds.Value(theRefIdx - aFace.WireRefIds.Length())) + .Orientation; + } + case BRepGraph_NodeId::Kind::Wire: + return TopAbs_FORWARD; + case BRepGraph_NodeId::Kind::Edge: { + const BRepGraphInc::EdgeDef& anEdge = + aTopo.Edges().Definition(BRepGraph_EdgeId(theParent.Index)); + const BRepGraph_VertexRefId aVertexRefId = edgeVertexRefIdAt(anEdge, theRefIdx); + if (!aVertexRefId.IsValid()) + { + return TopAbs_FORWARD; + } + return aRefs.Vertices().Entry(aVertexRefId).Orientation; + } + default: + return TopAbs_FORWARD; + } +} \ No newline at end of file diff --git a/src/ModelingData/TKBRep/BRepGraph/BRepGraph_ParentExplorer.hxx b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_ParentExplorer.hxx new file mode 100644 index 0000000000..6f21fb252c --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_ParentExplorer.hxx @@ -0,0 +1,230 @@ +// Copyright (c) 2026 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 _BRepGraph_ParentExplorer_HeaderFile +#define _BRepGraph_ParentExplorer_HeaderFile + +#include +#include + +#include +#include +#include + +#include +#include + +#include + +//! @brief Upward occurrence-aware parent traversal for BRepGraph. +//! +//! Enumerates all ancestor nodes reachable from a starting node. +//! Traversal is path-aware: when the same definition is reached through multiple +//! occurrence paths, each path contributes its own parent sequence with its own +//! accumulated location and orientation. +//! +//! The traversal follows the actual graph structure transparently - every node +//! kind is visited as a distinct entity (no hidden collapses): +//! Vertex -> Edge, Edge -> CoEdge, CoEdge -> Wire, Wire -> Face, +//! Face -> Shell, Shell -> Solid, Solid -> CompSolid/Compound, +//! Product -> Occurrence, Occurrence -> Product (parent assembly). +//! +//! ## Traversal modes +//! - **Recursive**: walks the full ancestor chain to the graph roots. +//! Without target kind, all ancestors are emitted. +//! With target kind, only matching ancestors are emitted but intermediate +//! levels are traversed to reach them. +//! - **DirectParents**: yields only the immediate parents of the starting node. +//! No ascent into grandparents. With target kind, only parents +//! matching the kind are returned. +class BRepGraph_ParentExplorer +{ +public: + DEFINE_STANDARD_ALLOC + + //! Upward traversal strategy. + enum class TraversalMode + { + Recursive, //!< Walk the full ancestor chain to the graph roots. + DirectParents, //!< Yields only the immediate parents of the starting node. + }; + + //! Explore all parents of the starting node. + Standard_EXPORT BRepGraph_ParentExplorer(const BRepGraph& theGraph, + const BRepGraph_NodeId theNode); + + //! Explore parents of the starting node using the given traversal mode. + Standard_EXPORT BRepGraph_ParentExplorer(const BRepGraph& theGraph, + const BRepGraph_NodeId theNode, + TraversalMode theMode); + + //! Explore all parents while pruning branches at the avoid kind. + Standard_EXPORT BRepGraph_ParentExplorer( + const BRepGraph& theGraph, + const BRepGraph_NodeId theNode, + const std::optional& theAvoidKind, + bool theEmitAvoidKind, + TraversalMode theMode = TraversalMode::Recursive); + + //! Explore only parents of the given kind. + Standard_EXPORT BRepGraph_ParentExplorer(const BRepGraph& theGraph, + const BRepGraph_NodeId theNode, + BRepGraph_NodeId::Kind theTargetKind); + + //! Explore only parents of the given kind using the given traversal mode. + Standard_EXPORT BRepGraph_ParentExplorer(const BRepGraph& theGraph, + const BRepGraph_NodeId theNode, + BRepGraph_NodeId::Kind theTargetKind, + TraversalMode theMode); + + //! Explore parents of the given kind while pruning branches at the avoid kind. + Standard_EXPORT BRepGraph_ParentExplorer( + const BRepGraph& theGraph, + const BRepGraph_NodeId theNode, + BRepGraph_NodeId::Kind theTargetKind, + const std::optional& theAvoidKind, + bool theEmitAvoidKind, + TraversalMode theMode = TraversalMode::Recursive); + + //! True if another matching parent is available. + [[nodiscard]] bool More() const { return myHasMore; } + + //! Advance to the next matching parent. + Standard_EXPORT void Next(); + + //! Current matching ancestor node with accumulated location and orientation. + [[nodiscard]] BRepGraphInc::NodeUsage Current() const + { + if (myHasMore) + { + return {myCurrent, myLocation, myOrientation}; + } + return {}; + } + + //! Accumulated location at the starting node of the current branch. + [[nodiscard]] Standard_EXPORT const TopLoc_Location& LeafLocation() const; + + //! Accumulated orientation at the starting node of the current branch. + [[nodiscard]] Standard_EXPORT TopAbs_Orientation LeafOrientation() const; + + //! True if Current() is the explicit root node of the current branch. + [[nodiscard]] Standard_EXPORT bool IsCurrentBranchRoot() const; + + //! Returns an STL-compatible iterator for range-based for loops. + NCollection_ForwardRangeIterator begin() + { + return NCollection_ForwardRangeIterator(this); + } + + //! Returns a sentinel marking the end of iteration. + NCollection_ForwardRangeSentinel end() const { return NCollection_ForwardRangeSentinel{}; } + +private: + struct StackFrame + { + BRepGraph_NodeId Node; + int NextParentIdx = 0; + int StepToChild = -1; + TopLoc_Location AccLocation; + TopAbs_Orientation AccOrientation = TopAbs_FORWARD; + }; + + Standard_EXPORT void startTraversal(); + Standard_EXPORT void advance(); + Standard_EXPORT bool emitNextFromCurrentBranch(); + Standard_EXPORT void backtrackAfterBranchEmission(); + Standard_EXPORT bool nextParentFrame(StackFrame& theChild, StackFrame& theParent) const; + Standard_EXPORT void prepareCurrentBranch(); + Standard_EXPORT void applyTransition(const BRepGraph_NodeId theParent, + const int theStepToChild, + TopLoc_Location& theLocation, + TopAbs_Orientation& theOrientation) const; + + [[nodiscard]] Standard_EXPORT int branchRootFrame() const; + + Standard_EXPORT bool findNthProductWrapper(const BRepGraph_NodeId theNode, + const int theOrdinal, + BRepGraph_ProductId& theProduct) const; + + Standard_EXPORT int findOccurrenceStep(const BRepGraph_ProductId theParentProduct, + const BRepGraph_OccurrenceId theOccurrence) const; + Standard_EXPORT int findCompoundChildStep(const BRepGraph_CompoundId theParent, + const BRepGraph_NodeId theChild) const; + Standard_EXPORT int findCompSolidSolidStep(const BRepGraph_CompSolidId theParent, + const BRepGraph_SolidId theChild) const; + Standard_EXPORT int findSolidChildStep(const BRepGraph_SolidId theParent, + const BRepGraph_NodeId theChild) const; + Standard_EXPORT int findShellChildStep(const BRepGraph_ShellId theParent, + const BRepGraph_NodeId theChild) const; + Standard_EXPORT int findFaceChildStep(const BRepGraph_FaceId theParent, + const BRepGraph_NodeId theChild) const; + Standard_EXPORT int findWireCoEdgeStep(const BRepGraph_WireId theParent, + const BRepGraph_CoEdgeId theChild) const; + Standard_EXPORT int findEdgeVertexStep(const BRepGraph_EdgeId theParent, + const BRepGraph_VertexId theChild) const; + + static std::optional normalizeAvoidKind( + const BRepGraph_NodeId theNode, + const std::optional& theTargetKind, + const std::optional& theAvoidKind); + + static bool canContainTarget(BRepGraph_NodeId::Kind theParentKind, + BRepGraph_NodeId::Kind theTargetKind); + + Standard_EXPORT void pushFrame(const StackFrame& theFrame); + Standard_EXPORT void popFrame(); + + [[nodiscard]] bool matchesAvoid(const BRepGraph_NodeId theNode) const + { + return myAvoidKind.has_value() && theNode.NodeKind == *myAvoidKind; + } + + [[nodiscard]] bool shouldEmit(const BRepGraph_NodeId theNode) const + { + const bool isAvoid = matchesAvoid(theNode); + const bool isFind = !myTargetKind.has_value() || theNode.NodeKind == *myTargetKind; + return myEmitAvoidKind ? (isFind || isAvoid) : (isFind && !isAvoid); + } + + StackFrame& topFrame() { return myStack[myStackTop]; } + + const StackFrame& topFrame() const { return myStack[myStackTop]; } + + Standard_EXPORT TopLoc_Location stepLocation(const BRepGraph_NodeId theParent, + const int theRefIdx) const; + Standard_EXPORT TopAbs_Orientation stepOrientation(const BRepGraph_NodeId theParent, + const int theRefIdx) const; + +private: + static constexpr int THE_INLINE_STACK_SIZE = 16; + + const BRepGraph* myGraph = nullptr; + BRepGraph_NodeId myNode; + const TraversalMode myMode; + const std::optional myTargetKind; + const std::optional myAvoidKind; + const bool myEmitAvoidKind; + + NCollection_LocalArray myStack; + int myStackTop = -1; + int myEmitIndex = -1; + int myCurrentFrame = -1; + + BRepGraph_NodeId myCurrent; + TopLoc_Location myLocation; + TopAbs_Orientation myOrientation = TopAbs_FORWARD; + bool myHasMore = false; +}; + +#endif // _BRepGraph_ParentExplorer_HeaderFile \ No newline at end of file diff --git a/src/ModelingData/TKBRep/BRepGraph/BRepGraph_RefId.hxx b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_RefId.hxx new file mode 100644 index 0000000000..522f0e7725 --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_RefId.hxx @@ -0,0 +1,234 @@ +// Copyright (c) 2026 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 _BRepGraph_RefId_HeaderFile +#define _BRepGraph_RefId_HeaderFile + +#include +#include + +#include +#include + +//! Lightweight typed index into a per-kind reference vector inside BRepGraph. +//! +//! The pair (Kind, Index) forms a unique reference identifier within one graph +//! instance. Default-constructed RefId has Index = -1 (invalid). +struct BRepGraph_RefId +{ + //! Enumeration of supported topology reference kinds. + enum class Kind : int + { + Shell = 0, //!< Shell reference entries (usage of shell definitions) + Face = 1, //!< Face reference entries (usage of face definitions) + Wire = 2, //!< Wire reference entries (usage of wire definitions) + CoEdge = 3, //!< CoEdge reference entries (usage of coedge definitions) + Vertex = 4, //!< Vertex reference entries (usage of vertex definitions) + Solid = 5, //!< Solid reference entries (usage of solid definitions) + Child = 6, //!< Generic child references (usage of mixed node definitions) + Occurrence = 7 //!< Occurrence references (usage of occurrence definitions) + }; + + //! @brief Compile-time typed wrapper around BRepGraph_RefId. + //! + //! Provides compile-time kind safety similarly to BRepGraph_NodeId::Typed. + template + struct Typed + { + int Index; + + Typed() + : Index(-1) + { + } + + explicit Typed(const int theIdx) + : Index(theIdx) + { + Standard_ASSERT_VOID(theIdx >= -1, "index must be >= -1"); + } + + [[nodiscard]] bool IsValid() const { return Index >= 0; } + + [[nodiscard]] bool IsValid(const int theMaxCount) const + { + Standard_ASSERT_RETURN(theMaxCount >= 0, "max count must be non-negative", false); + return Index >= 0 && Index < theMaxCount; + } + + operator BRepGraph_RefId() const { return BRepGraph_RefId(TheKind, Index); } + + static Typed FromRefId(const BRepGraph_RefId theRefId) + { + Standard_ASSERT_VOID(theRefId.RefKind == TheKind, "RefId kind mismatch"); + return Typed(theRefId.Index); + } + + bool operator==(const Typed& theOther) const { return Index == theOther.Index; } + + bool operator!=(const Typed& theOther) const { return Index != theOther.Index; } + + bool operator<(const Typed& theOther) const { return Index < theOther.Index; } + + bool operator<=(const Typed& theOther) const { return Index <= theOther.Index; } + + bool operator>(const Typed& theOther) const { return Index > theOther.Index; } + + bool operator>=(const Typed& theOther) const { return Index >= theOther.Index; } + + //! Pre-increment (++id). + Typed& operator++() + { + Standard_ASSERT_VOID(Index >= 0, "pre-increment on invalid id"); + ++Index; + return *this; + } + + //! Post-increment (id++). + Typed operator++(int) + { + Standard_ASSERT_VOID(Index >= 0, "post-increment on invalid id"); + Typed aPrev = *this; + ++Index; + return aPrev; + } + + //! Advance by offset. + [[nodiscard]] Typed operator+(const int theOffset) const { return Typed(Index + theOffset); } + + //! Retreat by offset. + [[nodiscard]] Typed operator-(const int theOffset) const { return Typed(Index - theOffset); } + + bool operator==(const BRepGraph_RefId& theOther) const + { + return theOther.RefKind == TheKind && theOther.Index == Index; + } + + bool operator!=(const BRepGraph_RefId& theOther) const { return !(*this == theOther); } + + friend bool operator==(const BRepGraph_RefId& theLhs, const Typed& theRhs) + { + return theRhs == theLhs; + } + + friend bool operator!=(const BRepGraph_RefId& theLhs, const Typed& theRhs) + { + return theRhs != theLhs; + } + }; + + static bool IsTopologyRefKind(const Kind theKind) + { + return static_cast(theKind) >= static_cast(Kind::Shell) + && static_cast(theKind) <= static_cast(Kind::Child); + } + + Kind RefKind; + int Index; + + BRepGraph_RefId() + : RefKind(Kind::Shell), + Index(-1) + { + } + + BRepGraph_RefId(const Kind theKind, const int theIdx) + : RefKind(theKind), + Index(theIdx) + { + Standard_ASSERT_VOID(theIdx >= -1, "BRepGraph_RefId: index must be >= -1"); + } + + [[nodiscard]] bool IsValid() const { return Index >= 0; } + + [[nodiscard]] bool IsValid(const int theMaxCount) const + { + Standard_ASSERT_RETURN(theMaxCount >= 0, "max count must be non-negative", false); + return Index >= 0 && Index < theMaxCount; + } + + bool operator==(const BRepGraph_RefId& theOther) const + { + return RefKind == theOther.RefKind && Index == theOther.Index; + } + + bool operator!=(const BRepGraph_RefId& theOther) const { return !(*this == theOther); } + + bool operator<(const BRepGraph_RefId& theOther) const + { + if (RefKind != theOther.RefKind) + return static_cast(RefKind) < static_cast(theOther.RefKind); + return Index < theOther.Index; + } + + //! Pre-increment (++id). + BRepGraph_RefId& operator++() + { + Standard_ASSERT_VOID(Index >= 0, "pre-increment on invalid id"); + ++Index; + return *this; + } + + //! Post-increment (id++). + BRepGraph_RefId operator++(int) + { + Standard_ASSERT_VOID(Index >= 0, "post-increment on invalid id"); + BRepGraph_RefId aPrev = *this; + ++Index; + return aPrev; + } + + //! Advance by offset. + [[nodiscard]] BRepGraph_RefId operator+(const int theOffset) const + { + return BRepGraph_RefId(RefKind, Index + theOffset); + } + + //! Retreat by offset. + [[nodiscard]] BRepGraph_RefId operator-(const int theOffset) const + { + return BRepGraph_RefId(RefKind, Index - theOffset); + } +}; + +using BRepGraph_ShellRefId = BRepGraph_RefId::Typed; +using BRepGraph_FaceRefId = BRepGraph_RefId::Typed; +using BRepGraph_WireRefId = BRepGraph_RefId::Typed; +using BRepGraph_CoEdgeRefId = BRepGraph_RefId::Typed; +using BRepGraph_VertexRefId = BRepGraph_RefId::Typed; +using BRepGraph_SolidRefId = BRepGraph_RefId::Typed; +using BRepGraph_ChildRefId = BRepGraph_RefId::Typed; +using BRepGraph_OccurrenceRefId = BRepGraph_RefId::Typed; + +template <> +struct std::hash +{ + size_t operator()(const BRepGraph_RefId& theId) const noexcept + { + size_t aCombination[2]; + aCombination[0] = opencascade::hash(static_cast(theId.RefKind)); + aCombination[1] = opencascade::hash(theId.Index); + return opencascade::hashBytes(aCombination, sizeof(aCombination)); + } +}; + +template +struct std::hash> +{ + size_t operator()(const BRepGraph_RefId::Typed& theId) const noexcept + { + return std::hash{}(static_cast(theId)); + } +}; + +#endif // _BRepGraph_RefId_HeaderFile diff --git a/src/ModelingData/TKBRep/BRepGraph/BRepGraph_RefUID.hxx b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_RefUID.hxx new file mode 100644 index 0000000000..c19c57631a --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_RefUID.hxx @@ -0,0 +1,105 @@ +// Copyright (c) 2026 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 _BRepGraph_RefUID_HeaderFile +#define _BRepGraph_RefUID_HeaderFile + +#include + +#include +#include +#include + +//! Unique reference identifier within a BRepGraph. +//! +//! Identity = (RefKind, Counter). Generation is excluded from equality/hash. +//! Counter 0 is an invalid sentinel. +//! +//! ## Serialization Contract +//! +//! Entity UIDs (BRepGraph_UID) and reference UIDs (BRepGraph_RefUID) share +//! a single monotonic counter (BRepGraph_Data::myNextUIDCounter). +//! To persist a BRepGraph across sessions: +//! 1. Write: for each reference entry, serialize (RefKind, Counter, OwnGen). +//! 2. Read: reconstruct reference entries, populate RefUID vectors with +//! deserialized (RefKind, Counter) values, set myNextUIDCounter to +//! max(all_entity_counters, all_ref_counters) + 1. +//! 3. myGeneration resets to 0 on load (session-scoped). +//! 4. VersionStamps from a previous session will correctly detect staleness +//! via Generation mismatch. +struct BRepGraph_RefUID +{ + BRepGraph_RefUID() + : myCounter(0), + myKind(BRepGraph_RefId::Kind::Shell), + myGeneration(0) + { + } + + BRepGraph_RefUID(const BRepGraph_RefId::Kind theKind, + const size_t theCounter, + const uint32_t theGeneration) + : myCounter(theCounter), + myKind(theKind), + myGeneration(theGeneration) + { + Standard_ASSERT_VOID(theCounter > 0, "BRepGraph_RefUID: counter must be > 0 for valid UIDs"); + } + + static BRepGraph_RefUID Invalid() { return BRepGraph_RefUID(); } + + [[nodiscard]] bool IsValid() const { return myCounter > 0; } + + [[nodiscard]] BRepGraph_RefId::Kind Kind() const { return myKind; } + + [[nodiscard]] size_t Counter() const { return myCounter; } + + [[nodiscard]] uint32_t Generation() const { return myGeneration; } + + bool operator==(const BRepGraph_RefUID& theOther) const + { + if (myCounter == 0 || theOther.myCounter == 0) + return (myCounter == 0) == (theOther.myCounter == 0); + return myKind == theOther.myKind && myCounter == theOther.myCounter; + } + + bool operator!=(const BRepGraph_RefUID& theOther) const { return !(*this == theOther); } + + bool operator<(const BRepGraph_RefUID& theOther) const + { + if (myKind != theOther.myKind) + return static_cast(myKind) < static_cast(theOther.myKind); + return myCounter < theOther.myCounter; + } + + [[nodiscard]] size_t HashValue() const + { + size_t aCombination[2]; + aCombination[0] = opencascade::hash(static_cast(myKind)); + aCombination[1] = opencascade::hash(myCounter); + return opencascade::hashBytes(aCombination, sizeof(aCombination)); + } + +private: + size_t myCounter; + BRepGraph_RefId::Kind myKind; + uint32_t myGeneration; +}; + +template <> +struct std::hash +{ + size_t operator()(const BRepGraph_RefUID& theUID) const noexcept { return theUID.HashValue(); } +}; + +#endif // _BRepGraph_RefUID_HeaderFile diff --git a/src/ModelingData/TKBRep/BRepGraph/BRepGraph_RefsIterator.hxx b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_RefsIterator.hxx new file mode 100644 index 0000000000..5de37502c9 --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_RefsIterator.hxx @@ -0,0 +1,420 @@ +// Copyright (c) 2026 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 _BRepGraph_RefsIterator_HeaderFile +#define _BRepGraph_RefsIterator_HeaderFile + +#include +#include +#include + +#include + +//! @brief Single-level typed iterators over active child reference ids. +//! +//! These iterators provide the direct active child RefIds owned by one parent. +//! They skip removed refs, invalid targets, and refs pointing to removed child +//! definitions. +namespace BRepGraph_RefsIterator +{ +template +struct BaseTraits +{ + using ParentId = ParentIdT; + using RefId = RefIdT; +}; + +template +inline const BRepGraphInc::BaseDef* childBaseDef(const BRepGraph& theGraph, + const ChildIdT theChildId) +{ + return theGraph.Topo().Gen().TopoEntity(BRepGraph_NodeId(theChildId)); +} + +inline const BRepGraphInc::BaseDef* childBaseDef(const BRepGraph& theGraph, + const BRepGraph_NodeId theChildId) +{ + return theGraph.Topo().Gen().TopoEntity(theChildId); +} + +struct ShellOfSolidTraits : public BaseTraits +{ + static bool IsParentValid(const BRepGraph& theGraph, const ParentId theParent) + { + return theParent.IsValid(theGraph.Topo().Solids().Nb()) + && !theGraph.Topo().Solids().Definition(theParent).IsRemoved; + } + + static const NCollection_Vector& RefIds(const BRepGraph& theGraph, + const ParentId theParent) + { + return theGraph.Topo().Solids().Definition(theParent).ShellRefIds; + } + + static const BRepGraphInc::ShellRef& Ref(const BRepGraph& theGraph, const RefId theRefId) + { + return theGraph.Refs().Shells().Entry(theRefId); + } + + static BRepGraph_ShellId ChildIdOf(const BRepGraphInc::ShellRef& theRef) + { + return theRef.ShellDefId; + } +}; + +struct FaceOfShellTraits : public BaseTraits +{ + static bool IsParentValid(const BRepGraph& theGraph, const ParentId theParent) + { + return theParent.IsValid(theGraph.Topo().Shells().Nb()) + && !theGraph.Topo().Shells().Definition(theParent).IsRemoved; + } + + static const NCollection_Vector& RefIds(const BRepGraph& theGraph, + const ParentId theParent) + { + return theGraph.Topo().Shells().Definition(theParent).FaceRefIds; + } + + static const BRepGraphInc::FaceRef& Ref(const BRepGraph& theGraph, const RefId theRefId) + { + return theGraph.Refs().Faces().Entry(theRefId); + } + + static BRepGraph_FaceId ChildIdOf(const BRepGraphInc::FaceRef& theRef) + { + return theRef.FaceDefId; + } +}; + +struct WireOfFaceTraits : public BaseTraits +{ + static bool IsParentValid(const BRepGraph& theGraph, const ParentId theParent) + { + return theParent.IsValid(theGraph.Topo().Faces().Nb()) + && !theGraph.Topo().Faces().Definition(theParent).IsRemoved; + } + + static const NCollection_Vector& RefIds(const BRepGraph& theGraph, + const ParentId theParent) + { + return theGraph.Topo().Faces().Definition(theParent).WireRefIds; + } + + static const BRepGraphInc::WireRef& Ref(const BRepGraph& theGraph, const RefId theRefId) + { + return theGraph.Refs().Wires().Entry(theRefId); + } + + static BRepGraph_WireId ChildIdOf(const BRepGraphInc::WireRef& theRef) + { + return theRef.WireDefId; + } +}; + +struct VertexOfFaceTraits : public BaseTraits +{ + static bool IsParentValid(const BRepGraph& theGraph, const ParentId theParent) + { + return theParent.IsValid(theGraph.Topo().Faces().Nb()) + && !theGraph.Topo().Faces().Definition(theParent).IsRemoved; + } + + static const NCollection_Vector& RefIds(const BRepGraph& theGraph, + const ParentId theParent) + { + return theGraph.Topo().Faces().Definition(theParent).VertexRefIds; + } + + static const BRepGraphInc::VertexRef& Ref(const BRepGraph& theGraph, const RefId theRefId) + { + return theGraph.Refs().Vertices().Entry(theRefId); + } + + static BRepGraph_VertexId ChildIdOf(const BRepGraphInc::VertexRef& theRef) + { + return theRef.VertexDefId; + } +}; + +struct CoEdgeOfWireTraits : public BaseTraits +{ + static bool IsParentValid(const BRepGraph& theGraph, const ParentId theParent) + { + return theParent.IsValid(theGraph.Topo().Wires().Nb()) + && !theGraph.Topo().Wires().Definition(theParent).IsRemoved; + } + + static const NCollection_Vector& RefIds(const BRepGraph& theGraph, + const ParentId theParent) + { + return theGraph.Topo().Wires().Definition(theParent).CoEdgeRefIds; + } + + static const BRepGraphInc::CoEdgeRef& Ref(const BRepGraph& theGraph, const RefId theRefId) + { + return theGraph.Refs().CoEdges().Entry(theRefId); + } + + static BRepGraph_CoEdgeId ChildIdOf(const BRepGraphInc::CoEdgeRef& theRef) + { + return theRef.CoEdgeDefId; + } +}; + +struct SolidOfCompSolidTraits : public BaseTraits +{ + static bool IsParentValid(const BRepGraph& theGraph, const ParentId theParent) + { + return theParent.IsValid(theGraph.Topo().CompSolids().Nb()) + && !theGraph.Topo().CompSolids().Definition(theParent).IsRemoved; + } + + static const NCollection_Vector& RefIds(const BRepGraph& theGraph, + const ParentId theParent) + { + return theGraph.Topo().CompSolids().Definition(theParent).SolidRefIds; + } + + static const BRepGraphInc::SolidRef& Ref(const BRepGraph& theGraph, const RefId theRefId) + { + return theGraph.Refs().Solids().Entry(theRefId); + } + + static BRepGraph_SolidId ChildIdOf(const BRepGraphInc::SolidRef& theRef) + { + return theRef.SolidDefId; + } +}; + +struct ChildOfCompoundTraits : public BaseTraits +{ + static bool IsParentValid(const BRepGraph& theGraph, const ParentId theParent) + { + return theParent.IsValid(theGraph.Topo().Compounds().Nb()) + && !theGraph.Topo().Compounds().Definition(theParent).IsRemoved; + } + + static const NCollection_Vector& RefIds(const BRepGraph& theGraph, + const ParentId theParent) + { + return theGraph.Topo().Compounds().Definition(theParent).ChildRefIds; + } + + static const BRepGraphInc::ChildRef& Ref(const BRepGraph& theGraph, const RefId theRefId) + { + return theGraph.Refs().Children().Entry(theRefId); + } + + static BRepGraph_NodeId ChildIdOf(const BRepGraphInc::ChildRef& theRef) + { + return theRef.ChildDefId; + } +}; + +struct OccurrenceOfProductTraits : public BaseTraits +{ + static bool IsParentValid(const BRepGraph& theGraph, const ParentId theParent) + { + return theParent.IsValid(theGraph.Topo().Products().Nb()) + && !theGraph.Topo().Products().Definition(theParent).IsRemoved; + } + + static const NCollection_Vector& RefIds(const BRepGraph& theGraph, + const ParentId theParent) + { + return theGraph.Topo().Products().Definition(theParent).OccurrenceRefIds; + } + + static const BRepGraphInc::OccurrenceRef& Ref(const BRepGraph& theGraph, const RefId theRefId) + { + return theGraph.Refs().Occurrences().Entry(theRefId); + } + + static BRepGraph_OccurrenceId ChildIdOf(const BRepGraphInc::OccurrenceRef& theRef) + { + return theRef.OccurrenceDefId; + } +}; + +template +class RefsOfParent +{ +public: + using ParentId = typename TraitsT::ParentId; + using RefId = typename TraitsT::RefId; + + RefsOfParent(const BRepGraph& theGraph, const ParentId theParent) + : myGraph(theGraph) + { + if (!TraitsT::IsParentValid(theGraph, theParent)) + { + return; + } + + myRefIds = &TraitsT::RefIds(theGraph, theParent); + myLength = myRefIds->Length(); + skipRemoved(); + } + + [[nodiscard]] bool More() const { return myRefIds != nullptr && myIndex < myLength; } + + void Next() + { + ++myIndex; + skipRemoved(); + } + + [[nodiscard]] RefId CurrentId() const { return myRefIds->Value(myIndex); } + + [[nodiscard]] int Index() const { return myIndex; } + + //! Returns an STL-compatible iterator for range-based for loops. + NCollection_ForwardRangeIterator begin() + { + return NCollection_ForwardRangeIterator(this); + } + + //! Returns a sentinel marking the end of iteration. + NCollection_ForwardRangeSentinel end() const { return NCollection_ForwardRangeSentinel{}; } + +private: + void skipRemoved() + { + while (myRefIds != nullptr && myIndex < myLength) + { + const auto& aRef = TraitsT::Ref(myGraph, myRefIds->Value(myIndex)); + if (!aRef.IsRemoved) + { + const BRepGraphInc::BaseDef* aChildDef = childBaseDef(myGraph, TraitsT::ChildIdOf(aRef)); + if (aChildDef != nullptr && !aChildDef->IsRemoved) + { + return; + } + } + ++myIndex; + } + } + + const BRepGraph& myGraph; + const NCollection_Vector* myRefIds = nullptr; + int myIndex = 0; + int myLength = 0; +}; + +//! @brief Direct active vertex reference ids of an edge. +//! +//! Iteration order is start vertex, end vertex, then internal/external vertices. +class RefsVertexOfEdge +{ +public: + using RefId = BRepGraph_VertexRefId; + + RefsVertexOfEdge(const BRepGraph& theGraph, const BRepGraph_EdgeId theEdgeId) + : myGraph(theGraph) + { + if (!theEdgeId.IsValid(theGraph.Topo().Edges().Nb()) + || theGraph.Topo().Edges().Definition(theEdgeId).IsRemoved) + { + return; + } + + myEdge = &theGraph.Topo().Edges().Definition(theEdgeId); + myLength = 2 + myEdge->InternalVertexRefIds.Length(); + skipRemoved(); + } + + [[nodiscard]] bool More() const { return myEdge != nullptr && myIndex < myLength; } + + void Next() + { + ++myIndex; + skipRemoved(); + } + + [[nodiscard]] RefId CurrentId() const { return refIdAt(myIndex); } + + [[nodiscard]] int Index() const { return myIndex; } + + //! Returns an STL-compatible iterator for range-based for loops. + NCollection_ForwardRangeIterator begin() + { + return NCollection_ForwardRangeIterator(this); + } + + //! Returns a sentinel marking the end of iteration. + NCollection_ForwardRangeSentinel end() const { return NCollection_ForwardRangeSentinel{}; } + +private: + [[nodiscard]] RefId refIdAt(const int theIndex) const + { + if (theIndex == 0) + { + return myEdge->StartVertexRefId; + } + if (theIndex == 1) + { + return myEdge->EndVertexRefId; + } + return myEdge->InternalVertexRefIds.Value(theIndex - 2); + } + + void skipRemoved() + { + while (myEdge != nullptr && myIndex < myLength) + { + const RefId aRefId = refIdAt(myIndex); + if (aRefId.IsValid()) + { + const BRepGraphInc::VertexRef& aRef = myGraph.Refs().Vertices().Entry(aRefId); + if (!aRef.IsRemoved) + { + const BRepGraphInc::BaseDef* aChildDef = + myGraph.Topo().Gen().TopoEntity(BRepGraph_NodeId(aRef.VertexDefId)); + if (aChildDef != nullptr && !aChildDef->IsRemoved) + { + return; + } + } + } + ++myIndex; + } + } + + const BRepGraph& myGraph; + const BRepGraphInc::EdgeDef* myEdge = nullptr; + int myIndex = 0; + int myLength = 0; +}; + +} // namespace BRepGraph_RefsIterator + +using BRepGraph_RefsShellOfSolid = + BRepGraph_RefsIterator::RefsOfParent; +using BRepGraph_RefsFaceOfShell = + BRepGraph_RefsIterator::RefsOfParent; +using BRepGraph_RefsWireOfFace = + BRepGraph_RefsIterator::RefsOfParent; +using BRepGraph_RefsVertexOfFace = + BRepGraph_RefsIterator::RefsOfParent; +using BRepGraph_RefsCoEdgeOfWire = + BRepGraph_RefsIterator::RefsOfParent; +using BRepGraph_RefsSolidOfCompSolid = + BRepGraph_RefsIterator::RefsOfParent; +using BRepGraph_RefsChildOfCompound = + BRepGraph_RefsIterator::RefsOfParent; +using BRepGraph_RefsOccurrenceOfProduct = + BRepGraph_RefsIterator::RefsOfParent; +using BRepGraph_RefsVertexOfEdge = BRepGraph_RefsIterator::RefsVertexOfEdge; + +#endif // _BRepGraph_RefsIterator_HeaderFile \ No newline at end of file diff --git a/src/ModelingData/TKBRep/BRepGraph/BRepGraph_RefsView.cxx b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_RefsView.cxx new file mode 100644 index 0000000000..dbbb68759f --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_RefsView.cxx @@ -0,0 +1,305 @@ +// Copyright (c) 2026 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 + +namespace +{ +constexpr int THE_REFSVIEW_EDGE_VERTEX_REF_BLOCK_SIZE = 4; + +} // namespace + +//================================================================================================= + +int BRepGraph::RefsView::ShellOps::Nb() const +{ + return myGraph->myData->myIncStorage.NbShellRefs(); +} + +//================================================================================================= + +int BRepGraph::RefsView::ShellOps::NbActive() const +{ + return myGraph->myData->myIncStorage.NbActiveShellRefs(); +} + +//================================================================================================= + +int BRepGraph::RefsView::FaceOps::Nb() const +{ + return myGraph->myData->myIncStorage.NbFaceRefs(); +} + +//================================================================================================= + +int BRepGraph::RefsView::FaceOps::NbActive() const +{ + return myGraph->myData->myIncStorage.NbActiveFaceRefs(); +} + +//================================================================================================= + +int BRepGraph::RefsView::WireOps::Nb() const +{ + return myGraph->myData->myIncStorage.NbWireRefs(); +} + +//================================================================================================= + +int BRepGraph::RefsView::WireOps::NbActive() const +{ + return myGraph->myData->myIncStorage.NbActiveWireRefs(); +} + +//================================================================================================= + +int BRepGraph::RefsView::CoEdgeOps::Nb() const +{ + return myGraph->myData->myIncStorage.NbCoEdgeRefs(); +} + +//================================================================================================= + +int BRepGraph::RefsView::CoEdgeOps::NbActive() const +{ + return myGraph->myData->myIncStorage.NbActiveCoEdgeRefs(); +} + +//================================================================================================= + +int BRepGraph::RefsView::VertexOps::Nb() const +{ + return myGraph->myData->myIncStorage.NbVertexRefs(); +} + +//================================================================================================= + +int BRepGraph::RefsView::VertexOps::NbActive() const +{ + return myGraph->myData->myIncStorage.NbActiveVertexRefs(); +} + +//================================================================================================= + +int BRepGraph::RefsView::SolidOps::Nb() const +{ + return myGraph->myData->myIncStorage.NbSolidRefs(); +} + +//================================================================================================= + +int BRepGraph::RefsView::SolidOps::NbActive() const +{ + return myGraph->myData->myIncStorage.NbActiveSolidRefs(); +} + +//================================================================================================= + +int BRepGraph::RefsView::ChildOps::Nb() const +{ + return myGraph->myData->myIncStorage.NbChildRefs(); +} + +//================================================================================================= + +int BRepGraph::RefsView::ChildOps::NbActive() const +{ + return myGraph->myData->myIncStorage.NbActiveChildRefs(); +} + +//================================================================================================= + +int BRepGraph::RefsView::OccurrenceOps::Nb() const +{ + return myGraph->myData->myIncStorage.NbOccurrenceRefs(); +} + +//================================================================================================= + +int BRepGraph::RefsView::OccurrenceOps::NbActive() const +{ + return myGraph->myData->myIncStorage.NbActiveOccurrenceRefs(); +} + +//================================================================================================= + +const BRepGraphInc::ShellRef& BRepGraph::RefsView::ShellOps::Entry( + const BRepGraph_ShellRefId theRefId) const +{ + return myGraph->myData->myIncStorage.ShellRef(theRefId); +} + +//================================================================================================= + +const BRepGraphInc::FaceRef& BRepGraph::RefsView::FaceOps::Entry( + const BRepGraph_FaceRefId theRefId) const +{ + return myGraph->myData->myIncStorage.FaceRef(theRefId); +} + +//================================================================================================= + +const BRepGraphInc::WireRef& BRepGraph::RefsView::WireOps::Entry( + const BRepGraph_WireRefId theRefId) const +{ + return myGraph->myData->myIncStorage.WireRef(theRefId); +} + +//================================================================================================= + +const BRepGraphInc::CoEdgeRef& BRepGraph::RefsView::CoEdgeOps::Entry( + const BRepGraph_CoEdgeRefId theRefId) const +{ + return myGraph->myData->myIncStorage.CoEdgeRef(theRefId); +} + +//================================================================================================= + +const BRepGraphInc::VertexRef& BRepGraph::RefsView::VertexOps::Entry( + const BRepGraph_VertexRefId theRefId) const +{ + return myGraph->myData->myIncStorage.VertexRef(theRefId); +} + +//================================================================================================= + +const BRepGraphInc::SolidRef& BRepGraph::RefsView::SolidOps::Entry( + const BRepGraph_SolidRefId theRefId) const +{ + return myGraph->myData->myIncStorage.SolidRef(theRefId); +} + +//================================================================================================= + +const BRepGraphInc::ChildRef& BRepGraph::RefsView::ChildOps::Entry( + const BRepGraph_ChildRefId theRefId) const +{ + return myGraph->myData->myIncStorage.ChildRef(theRefId); +} + +//================================================================================================= + +const BRepGraphInc::OccurrenceRef& BRepGraph::RefsView::OccurrenceOps::Entry( + const BRepGraph_OccurrenceRefId theRefId) const +{ + return myGraph->myData->myIncStorage.OccurrenceRef(theRefId); +} + +//================================================================================================= + +const NCollection_Vector& BRepGraph::RefsView::FaceOps::IdsOf( + const BRepGraph_ShellId theShell) const +{ + static const NCollection_Vector anEmpty; + if (!theShell.IsValid(myGraph->myData->myIncStorage.NbShells())) + return anEmpty; + return myGraph->myData->myIncStorage.Shell(theShell).FaceRefIds; +} + +//================================================================================================= + +const NCollection_Vector& BRepGraph::RefsView::WireOps::IdsOf( + const BRepGraph_FaceId theFace) const +{ + static const NCollection_Vector anEmpty; + if (!theFace.IsValid(myGraph->myData->myIncStorage.NbFaces())) + return anEmpty; + return myGraph->myData->myIncStorage.Face(theFace).WireRefIds; +} + +//================================================================================================= + +const NCollection_Vector& BRepGraph::RefsView::CoEdgeOps::IdsOf( + const BRepGraph_WireId theWire) const +{ + static const NCollection_Vector anEmpty; + if (!theWire.IsValid(myGraph->myData->myIncStorage.NbWires())) + return anEmpty; + return myGraph->myData->myIncStorage.Wire(theWire).CoEdgeRefIds; +} + +//================================================================================================= + +const NCollection_Vector& BRepGraph::RefsView::ShellOps::IdsOf( + const BRepGraph_SolidId theSolid) const +{ + static const NCollection_Vector anEmpty; + if (!theSolid.IsValid(myGraph->myData->myIncStorage.NbSolids())) + return anEmpty; + return myGraph->myData->myIncStorage.Solid(theSolid).ShellRefIds; +} + +//================================================================================================= + +const NCollection_Vector& BRepGraph::RefsView::ChildOps::IdsOf( + const BRepGraph_CompoundId theCompound) const +{ + static const NCollection_Vector anEmpty; + if (!theCompound.IsValid(myGraph->myData->myIncStorage.NbCompounds())) + return anEmpty; + return myGraph->myData->myIncStorage.Compound(theCompound).ChildRefIds; +} + +//================================================================================================= + +const NCollection_Vector& BRepGraph::RefsView::OccurrenceOps::IdsOf( + const BRepGraph_ProductId theProduct) const +{ + static const NCollection_Vector anEmpty; + if (!theProduct.IsValid(myGraph->myData->myIncStorage.NbProducts())) + return anEmpty; + return myGraph->myData->myIncStorage.Product(theProduct).OccurrenceRefIds; +} + +//================================================================================================= + +const NCollection_Vector& BRepGraph::RefsView::SolidOps::IdsOf( + const BRepGraph_CompSolidId theCompSolid) const +{ + static const NCollection_Vector anEmpty; + if (!theCompSolid.IsValid(myGraph->myData->myIncStorage.NbCompSolids())) + return anEmpty; + return myGraph->myData->myIncStorage.CompSolid(theCompSolid).SolidRefIds; +} + +//================================================================================================= + +NCollection_Vector BRepGraph::RefsView::VertexOps::IdsOf( + const BRepGraph_EdgeId theEdge, + const occ::handle& theAllocator) const +{ + NCollection_Vector aResult(THE_REFSVIEW_EDGE_VERTEX_REF_BLOCK_SIZE, + theAllocator); + const BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + if (!theEdge.IsValid(aStorage.NbEdges())) + return aResult; + + NCollection_PackedMap aSeenRefIds; + + for (BRepGraph_RefsVertexOfEdge aRefIt(*myGraph, theEdge); aRefIt.More(); aRefIt.Next()) + { + const BRepGraph_VertexRefId aRefId = aRefIt.CurrentId(); + if (aSeenRefIds.Add(aRefId.Index)) + { + aResult.Append(aRefId); + } + } + return aResult; +} + +//================================================================================================= diff --git a/src/ModelingData/TKBRep/BRepGraph/BRepGraph_RefsView.hxx b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_RefsView.hxx new file mode 100644 index 0000000000..183533c97e --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_RefsView.hxx @@ -0,0 +1,297 @@ +// Copyright (c) 2026 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 _BRepGraph_RefsView_HeaderFile +#define _BRepGraph_RefsView_HeaderFile + +#include +#include +#include + +//! @brief Read-only view for RefId/RefUID-based reference storage. +//! +//! This view exposes reference-entry storage: +//! - typed reference entry access (Shell, Face, ...) +//! - reference counts +//! - RefUID lookup and reverse lookup through BRepGraph::UIDs() +//! - stale tracking via BRepGraph_VersionStamp through BRepGraph::UIDs() +//! +//! Identity semantics: +//! - RefId (kind + index) is graph-local and may change after Compact(). +//! Use it for in-graph traversal and short-lived mutation logic. +//! - RefUID (kind + counter + generation) is stable across index remapping +//! and intended for longer-lived identity tracking. +//! +//! ## RefsView vs TopoView naming +//! RefsView accessors take reference IDs (BRepGraph_ShellRefId, BRepGraph_FaceRefId) +//! and return reference-entry structs carrying per-use orientation and location. +//! TopoView accessors take definition IDs (BRepGraph_ShellId, BRepGraph_FaceId) +//! and return definition structs. +//! +//! ## Iterating over references +//! Reference entries are primarily traversed in parent-owned context through +//! the typed grouped IdsOf accessors. When flat iteration is needed, iterate +//! using a counted loop over the appropriate grouped Nb() or NbActive() count: +//! @code +//! const BRepGraph::RefsView& aRefs = aGraph.Refs(); +//! for (int i = 0; i < aRefs.Faces().Nb(); ++i) +//! { +//! const BRepGraphInc::FaceRef& aFR = aRefs.Faces().Entry(BRepGraph_FaceRefId(i)); +//! if (aFR.IsRemoved) +//! continue; +//! // use aFR.FaceDefId, aFR.Orientation, aFR.Location ... +//! } +//! @endcode +//! +//! To iterate refs belonging to a specific parent, use the grouped IdsOf +//! accessors: +//! @code +//! for (const BRepGraph_WireRefId& aWireRefId : aRefs.Wires().IdsOf(aFaceId)) +//! { +//! const BRepGraphInc::WireRef& aWR = aRefs.Wires().Entry(aWireRefId); +//! // ... +//! } +//! @endcode +class BRepGraph::RefsView +{ +public: + //! @brief Shell reference queries. + class ShellOps + { + public: + [[nodiscard]] Standard_EXPORT int Nb() const; + [[nodiscard]] Standard_EXPORT int NbActive() const; + [[nodiscard]] Standard_EXPORT const BRepGraphInc::ShellRef& Entry( + const BRepGraph_ShellRefId theRefId) const; + [[nodiscard]] Standard_EXPORT const NCollection_Vector& IdsOf( + const BRepGraph_SolidId theSolid) const; + + private: + friend class RefsView; + + explicit ShellOps(const BRepGraph* theGraph) + : myGraph(theGraph) + { + } + + const BRepGraph* myGraph; + }; + + //! @brief Face reference queries. + class FaceOps + { + public: + [[nodiscard]] Standard_EXPORT int Nb() const; + [[nodiscard]] Standard_EXPORT int NbActive() const; + [[nodiscard]] Standard_EXPORT const BRepGraphInc::FaceRef& Entry( + const BRepGraph_FaceRefId theRefId) const; + [[nodiscard]] Standard_EXPORT const NCollection_Vector& IdsOf( + const BRepGraph_ShellId theShell) const; + + private: + friend class RefsView; + + explicit FaceOps(const BRepGraph* theGraph) + : myGraph(theGraph) + { + } + + const BRepGraph* myGraph; + }; + + //! @brief Wire reference queries. + class WireOps + { + public: + [[nodiscard]] Standard_EXPORT int Nb() const; + [[nodiscard]] Standard_EXPORT int NbActive() const; + [[nodiscard]] Standard_EXPORT const BRepGraphInc::WireRef& Entry( + const BRepGraph_WireRefId theRefId) const; + [[nodiscard]] Standard_EXPORT const NCollection_Vector& IdsOf( + const BRepGraph_FaceId theFace) const; + + private: + friend class RefsView; + + explicit WireOps(const BRepGraph* theGraph) + : myGraph(theGraph) + { + } + + const BRepGraph* myGraph; + }; + + //! @brief Coedge reference queries. + class CoEdgeOps + { + public: + [[nodiscard]] Standard_EXPORT int Nb() const; + [[nodiscard]] Standard_EXPORT int NbActive() const; + [[nodiscard]] Standard_EXPORT const BRepGraphInc::CoEdgeRef& Entry( + const BRepGraph_CoEdgeRefId theRefId) const; + [[nodiscard]] Standard_EXPORT const NCollection_Vector& IdsOf( + const BRepGraph_WireId theWire) const; + + private: + friend class RefsView; + + explicit CoEdgeOps(const BRepGraph* theGraph) + : myGraph(theGraph) + { + } + + const BRepGraph* myGraph; + }; + + //! @brief Vertex reference queries. + class VertexOps + { + public: + [[nodiscard]] Standard_EXPORT int Nb() const; + [[nodiscard]] Standard_EXPORT int NbActive() const; + [[nodiscard]] Standard_EXPORT const BRepGraphInc::VertexRef& Entry( + const BRepGraph_VertexRefId theRefId) const; + [[nodiscard]] Standard_EXPORT NCollection_Vector IdsOf( + const BRepGraph_EdgeId theEdge, + const occ::handle& theAllocator) const; + + private: + friend class RefsView; + + explicit VertexOps(const BRepGraph* theGraph) + : myGraph(theGraph) + { + } + + const BRepGraph* myGraph; + }; + + //! @brief Solid reference queries. + class SolidOps + { + public: + [[nodiscard]] Standard_EXPORT int Nb() const; + [[nodiscard]] Standard_EXPORT int NbActive() const; + [[nodiscard]] Standard_EXPORT const BRepGraphInc::SolidRef& Entry( + const BRepGraph_SolidRefId theRefId) const; + [[nodiscard]] Standard_EXPORT const NCollection_Vector& IdsOf( + const BRepGraph_CompSolidId theCompSolid) const; + + private: + friend class RefsView; + + explicit SolidOps(const BRepGraph* theGraph) + : myGraph(theGraph) + { + } + + const BRepGraph* myGraph; + }; + + //! @brief Generic child reference queries. + class ChildOps + { + public: + [[nodiscard]] Standard_EXPORT int Nb() const; + [[nodiscard]] Standard_EXPORT int NbActive() const; + [[nodiscard]] Standard_EXPORT const BRepGraphInc::ChildRef& Entry( + const BRepGraph_ChildRefId theRefId) const; + [[nodiscard]] Standard_EXPORT const NCollection_Vector& IdsOf( + const BRepGraph_CompoundId theCompound) const; + + private: + friend class RefsView; + + explicit ChildOps(const BRepGraph* theGraph) + : myGraph(theGraph) + { + } + + const BRepGraph* myGraph; + }; + + //! @brief Occurrence reference queries. + class OccurrenceOps + { + public: + [[nodiscard]] Standard_EXPORT int Nb() const; + [[nodiscard]] Standard_EXPORT int NbActive() const; + [[nodiscard]] Standard_EXPORT const BRepGraphInc::OccurrenceRef& Entry( + const BRepGraph_OccurrenceRefId theRefId) const; + [[nodiscard]] Standard_EXPORT const NCollection_Vector& IdsOf( + const BRepGraph_ProductId theProduct) const; + + private: + friend class RefsView; + + explicit OccurrenceOps(const BRepGraph* theGraph) + : myGraph(theGraph) + { + } + + const BRepGraph* myGraph; + }; + + //! Grouped shell reference queries. + [[nodiscard]] const ShellOps& Shells() const { return myShells; } + + //! Grouped face reference queries. + [[nodiscard]] const FaceOps& Faces() const { return myFaces; } + + //! Grouped wire reference queries. + [[nodiscard]] const WireOps& Wires() const { return myWires; } + + //! Grouped coedge reference queries. + [[nodiscard]] const CoEdgeOps& CoEdges() const { return myCoEdges; } + + //! Grouped vertex reference queries. + [[nodiscard]] const VertexOps& Vertices() const { return myVertices; } + + //! Grouped solid reference queries. + [[nodiscard]] const SolidOps& Solids() const { return mySolids; } + + //! Grouped child reference queries. + [[nodiscard]] const ChildOps& Children() const { return myChildren; } + + //! Grouped occurrence reference queries. + [[nodiscard]] const OccurrenceOps& Occurrences() const { return myOccurrences; } + +private: + friend class BRepGraph; + friend struct BRepGraph_Data; + + explicit RefsView(const BRepGraph* theGraph) + : myGraph(theGraph), + myShells(theGraph), + myFaces(theGraph), + myWires(theGraph), + myCoEdges(theGraph), + myVertices(theGraph), + mySolids(theGraph), + myChildren(theGraph), + myOccurrences(theGraph) + { + } + + const BRepGraph* myGraph; + ShellOps myShells; + FaceOps myFaces; + WireOps myWires; + CoEdgeOps myCoEdges; + VertexOps myVertices; + SolidOps mySolids; + ChildOps myChildren; + OccurrenceOps myOccurrences; +}; + +#endif // _BRepGraph_RefsView_HeaderFile diff --git a/src/ModelingData/TKBRep/BRepGraph/BRepGraph_RegularityLayer.cxx b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_RegularityLayer.cxx new file mode 100644 index 0000000000..ae6915ce3e --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_RegularityLayer.cxx @@ -0,0 +1,502 @@ +// Copyright (c) 2026 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 + +IMPLEMENT_STANDARD_RTTIEXT(BRepGraph_RegularityLayer, BRepGraph_Layer) + +namespace +{ + +static const TCollection_AsciiString THE_LAYER_NAME("Regularity"); + +void appendUnique( + NCollection_DataMap>& theMap, + const BRepGraph_FaceId theFace, + const BRepGraph_EdgeId theEdge) +{ + if (!theMap.IsBound(theFace)) + { + NCollection_Vector anEdges; + anEdges.Append(theEdge); + theMap.Bind(theFace, anEdges); + return; + } + + NCollection_Vector& anEdges = theMap.ChangeFind(theFace); + for (int anIdx = 0; anIdx < anEdges.Length(); ++anIdx) + { + if (anEdges.Value(anIdx) == theEdge) + return; + } + anEdges.Append(theEdge); +} + +void removeEdge(NCollection_DataMap>& theMap, + const BRepGraph_FaceId theFace, + const BRepGraph_EdgeId theEdge) noexcept +{ + if (!theMap.IsBound(theFace)) + return; + + NCollection_Vector& anEdges = theMap.ChangeFind(theFace); + for (int anIdx = 0; anIdx < anEdges.Length(); ++anIdx) + { + if (anEdges.Value(anIdx) != theEdge) + continue; + if (anIdx < anEdges.Length() - 1) + anEdges.ChangeValue(anIdx) = anEdges.Value(anEdges.Length() - 1); + anEdges.EraseLast(); + break; + } + + if (anEdges.IsEmpty()) + theMap.UnBind(theFace); +} + +static BRepGraph_EdgeId remapEdge( + const NCollection_DataMap& theRemapMap, + const BRepGraph_EdgeId theEdge) +{ + const BRepGraph_NodeId* aNewId = theRemapMap.Seek(BRepGraph_EdgeId(theEdge.Index)); + if (aNewId == nullptr || aNewId->NodeKind != BRepGraph_NodeId::Kind::Edge) + return BRepGraph_EdgeId(); + return BRepGraph_EdgeId(aNewId->Index); +} + +static BRepGraph_FaceId remapFace( + const NCollection_DataMap& theRemapMap, + const BRepGraph_FaceId theFace) +{ + const BRepGraph_NodeId* aNewId = theRemapMap.Seek(BRepGraph_FaceId(theFace.Index)); + if (aNewId == nullptr || aNewId->NodeKind != BRepGraph_NodeId::Kind::Face) + return BRepGraph_FaceId(); + return BRepGraph_FaceId(aNewId->Index); +} + +} // namespace + +//================================================================================================= + +const Standard_GUID& BRepGraph_RegularityLayer::GetID() +{ + static const Standard_GUID THE_LAYER_ID("2f9b6a5c-1f2d-4a88-9c1c-7a0c16a10002"); + return THE_LAYER_ID; +} + +//================================================================================================= + +const Standard_GUID& BRepGraph_RegularityLayer::ID() const +{ + return GetID(); +} + +//================================================================================================= + +const TCollection_AsciiString& BRepGraph_RegularityLayer::Name() const +{ + return THE_LAYER_NAME; +} + +//================================================================================================= + +int BRepGraph_RegularityLayer::SubscribedKinds() const +{ + return KindBit(BRepGraph_NodeId::Kind::Edge) | KindBit(BRepGraph_NodeId::Kind::Face); +} + +//================================================================================================= + +const BRepGraph_RegularityLayer::EdgeRegularities* BRepGraph_RegularityLayer::FindEdgeRegularities( + const BRepGraph_EdgeId theEdge) const +{ + return myEdgeRegularities.Seek(theEdge); +} + +//================================================================================================= + +void BRepGraph_RegularityLayer::normalizeFacePair(BRepGraph_FaceId& theFace1, + BRepGraph_FaceId& theFace2) const noexcept +{ + if (theFace2.Index < theFace1.Index) + std::swap(theFace1, theFace2); +} + +//================================================================================================= + +bool BRepGraph_RegularityLayer::FindContinuity(const BRepGraph_EdgeId theEdge, + const BRepGraph_FaceId theFace1, + const BRepGraph_FaceId theFace2, + GeomAbs_Shape* const theContinuity) const +{ + BRepGraph_FaceId aFace1 = theFace1; + BRepGraph_FaceId aFace2 = theFace2; + normalizeFacePair(aFace1, aFace2); + + const EdgeRegularities* aRegularities = FindEdgeRegularities(theEdge); + if (aRegularities == nullptr) + return false; + + for (const RegularityEntry& anEntry : aRegularities->Entries) + { + if (anEntry.FaceEntity1 != aFace1 || anEntry.FaceEntity2 != aFace2) + continue; + if (theContinuity != nullptr) + *theContinuity = anEntry.Continuity; + return true; + } + return false; +} + +//================================================================================================= + +int BRepGraph_RegularityLayer::NbRegularities(const BRepGraph_EdgeId theEdge) const +{ + const EdgeRegularities* aRegularities = FindEdgeRegularities(theEdge); + return aRegularities == nullptr ? 0 : aRegularities->Entries.Length(); +} + +//================================================================================================= + +GeomAbs_Shape BRepGraph_RegularityLayer::MaxContinuity(const BRepGraph_EdgeId theEdge) const +{ + const EdgeRegularities* aRegularities = FindEdgeRegularities(theEdge); + if (aRegularities == nullptr) + return GeomAbs_C0; + + GeomAbs_Shape aMaxContinuity = GeomAbs_C0; + for (const RegularityEntry& anEntry : aRegularities->Entries) + { + if (anEntry.Continuity > aMaxContinuity) + aMaxContinuity = anEntry.Continuity; + } + return aMaxContinuity; +} + +//================================================================================================= + +BRepGraph_RegularityLayer::EdgeRegularities& BRepGraph_RegularityLayer::changeEdgeRegularities( + const BRepGraph_EdgeId theEdge) +{ + if (!myEdgeRegularities.IsBound(theEdge)) + myEdgeRegularities.Bind(theEdge, EdgeRegularities()); + return myEdgeRegularities.ChangeFind(theEdge); +} + +//================================================================================================= + +void BRepGraph_RegularityLayer::bindFaceToEdge(const BRepGraph_FaceId theFace, + const BRepGraph_EdgeId theEdge) +{ + appendUnique(myFaceToEdges, theFace, theEdge); +} + +//================================================================================================= + +void BRepGraph_RegularityLayer::unbindFaceFromEdge(const BRepGraph_FaceId theFace, + const BRepGraph_EdgeId theEdge) noexcept +{ + removeEdge(myFaceToEdges, theFace, theEdge); +} + +//================================================================================================= + +void BRepGraph_RegularityLayer::SetRegularity(const BRepGraph_EdgeId theEdge, + const BRepGraph_FaceId theFace1, + const BRepGraph_FaceId theFace2, + const GeomAbs_Shape theContinuity) +{ + BRepGraph_FaceId aFace1 = theFace1; + BRepGraph_FaceId aFace2 = theFace2; + normalizeFacePair(aFace1, aFace2); + + EdgeRegularities& aRegularities = changeEdgeRegularities(theEdge); + for (int anIdx = 0; anIdx < aRegularities.Entries.Length(); ++anIdx) + { + RegularityEntry& anEntry = aRegularities.Entries.ChangeValue(anIdx); + if (anEntry.FaceEntity1 != aFace1 || anEntry.FaceEntity2 != aFace2) + continue; + anEntry.Continuity = theContinuity; + return; + } + + RegularityEntry& anEntry = aRegularities.Entries.Appended(); + anEntry.FaceEntity1 = aFace1; + anEntry.FaceEntity2 = aFace2; + anEntry.Continuity = theContinuity; + bindFaceToEdge(aFace1, theEdge); + bindFaceToEdge(aFace2, theEdge); +} + +//================================================================================================= + +void BRepGraph_RegularityLayer::removeRegularity(const BRepGraph_EdgeId theEdge, + const BRepGraph_FaceId theFace1, + const BRepGraph_FaceId theFace2) noexcept +{ + if (!myEdgeRegularities.IsBound(theEdge)) + return; + + BRepGraph_FaceId aFace1 = theFace1; + BRepGraph_FaceId aFace2 = theFace2; + normalizeFacePair(aFace1, aFace2); + + EdgeRegularities& aRegularities = myEdgeRegularities.ChangeFind(theEdge); + bool aFound = false; + for (int anIdx = 0; anIdx < aRegularities.Entries.Length(); ++anIdx) + { + const RegularityEntry& anEntry = aRegularities.Entries.Value(anIdx); + if (anEntry.FaceEntity1 != aFace1 || anEntry.FaceEntity2 != aFace2) + continue; + if (anIdx < aRegularities.Entries.Length() - 1) + aRegularities.Entries.ChangeValue(anIdx) = + aRegularities.Entries.Value(aRegularities.Entries.Length() - 1); + aRegularities.Entries.EraseLast(); + aFound = true; + break; + } + + if (!aFound) + return; + + // Only unbind a face if no remaining entry still references it on this edge. + bool aFace1StillReferenced = false; + bool aFace2StillReferenced = false; + for (int anIdx = 0; anIdx < aRegularities.Entries.Length(); ++anIdx) + { + const RegularityEntry& anEntry = aRegularities.Entries.Value(anIdx); + if (anEntry.FaceEntity1 == aFace1 || anEntry.FaceEntity2 == aFace1) + aFace1StillReferenced = true; + if (anEntry.FaceEntity1 == aFace2 || anEntry.FaceEntity2 == aFace2) + aFace2StillReferenced = true; + } + if (!aFace1StillReferenced) + unbindFaceFromEdge(aFace1, theEdge); + if (!aFace2StillReferenced) + unbindFaceFromEdge(aFace2, theEdge); + + if (aRegularities.IsEmpty()) + myEdgeRegularities.UnBind(theEdge); +} + +//================================================================================================= + +void BRepGraph_RegularityLayer::removeEdgeBindings(const BRepGraph_EdgeId theEdge) noexcept +{ + const EdgeRegularities* aRegularities = myEdgeRegularities.Seek(theEdge); + if (aRegularities == nullptr) + return; + + for (const RegularityEntry& anEntry : aRegularities->Entries) + { + unbindFaceFromEdge(anEntry.FaceEntity1, theEdge); + unbindFaceFromEdge(anEntry.FaceEntity2, theEdge); + } + myEdgeRegularities.UnBind(theEdge); +} + +//================================================================================================= + +void BRepGraph_RegularityLayer::invalidateFaceBindings(const BRepGraph_FaceId theFace) noexcept +{ + const NCollection_Vector* anEdges = myFaceToEdges.Seek(theFace); + if (anEdges == nullptr) + return; + + const NCollection_Vector aBoundEdges = *anEdges; + for (int anIdx = 0; anIdx < aBoundEdges.Length(); ++anIdx) + { + const EdgeRegularities* aRegularities = myEdgeRegularities.Seek(aBoundEdges.Value(anIdx)); + if (aRegularities == nullptr) + continue; + const EdgeRegularities aEntries = *aRegularities; + for (int aRegIdx = 0; aRegIdx < aEntries.Entries.Length(); ++aRegIdx) + { + const RegularityEntry& anEntry = aEntries.Entries.Value(aRegIdx); + if (anEntry.FaceEntity1 == theFace || anEntry.FaceEntity2 == theFace) + removeRegularity(aBoundEdges.Value(anIdx), anEntry.FaceEntity1, anEntry.FaceEntity2); + } + } +} + +//================================================================================================= + +void BRepGraph_RegularityLayer::migrateEdgeBindings(const BRepGraph_EdgeId theOldEdge, + const BRepGraph_EdgeId theNewEdge) noexcept +{ + const EdgeRegularities* aRegularities = myEdgeRegularities.Seek(theOldEdge); + if (aRegularities == nullptr) + return; + + const EdgeRegularities anOldRegularities = *aRegularities; + removeEdgeBindings(theOldEdge); + for (const RegularityEntry& anEntry : anOldRegularities.Entries) + { + SetRegularity(theNewEdge, anEntry.FaceEntity1, anEntry.FaceEntity2, anEntry.Continuity); + } +} + +//================================================================================================= + +void BRepGraph_RegularityLayer::migrateFaceBindings(const BRepGraph_FaceId theOldFace, + const BRepGraph_FaceId theNewFace) noexcept +{ + const NCollection_Vector* anEdges = myFaceToEdges.Seek(theOldFace); + if (anEdges == nullptr) + return; + + const NCollection_Vector aBoundEdges = *anEdges; + for (int anIdx = 0; anIdx < aBoundEdges.Length(); ++anIdx) + { + const EdgeRegularities* aRegularities = myEdgeRegularities.Seek(aBoundEdges.Value(anIdx)); + if (aRegularities == nullptr) + continue; + const EdgeRegularities aEntries = *aRegularities; + for (int aRegIdx = 0; aRegIdx < aEntries.Entries.Length(); ++aRegIdx) + { + const RegularityEntry& anEntry = aEntries.Entries.Value(aRegIdx); + if (anEntry.FaceEntity1 != theOldFace && anEntry.FaceEntity2 != theOldFace) + continue; + removeRegularity(aBoundEdges.Value(anIdx), anEntry.FaceEntity1, anEntry.FaceEntity2); + const BRepGraph_FaceId aFace1 = + anEntry.FaceEntity1 == theOldFace ? theNewFace : anEntry.FaceEntity1; + const BRepGraph_FaceId aFace2 = + anEntry.FaceEntity2 == theOldFace ? theNewFace : anEntry.FaceEntity2; + SetRegularity(aBoundEdges.Value(anIdx), aFace1, aFace2, anEntry.Continuity); + } + } +} + +//================================================================================================= + +void BRepGraph_RegularityLayer::OnNodeModified(const BRepGraph_NodeId theNode) noexcept +{ + switch (theNode.NodeKind) + { + case BRepGraph_NodeId::Kind::Edge: + removeEdgeBindings(BRepGraph_EdgeId(theNode.Index)); + break; + case BRepGraph_NodeId::Kind::Face: + invalidateFaceBindings(BRepGraph_FaceId(theNode.Index)); + break; + default: + break; + } +} + +//================================================================================================= + +void BRepGraph_RegularityLayer::OnNodesModified( + const NCollection_Vector& theModifiedNodes) noexcept +{ + for (const BRepGraph_NodeId& aModifiedNode : theModifiedNodes) + OnNodeModified(aModifiedNode); +} + +//================================================================================================= + +void BRepGraph_RegularityLayer::OnNodeRemoved(const BRepGraph_NodeId theNode, + const BRepGraph_NodeId theReplacement) noexcept +{ + switch (theNode.NodeKind) + { + case BRepGraph_NodeId::Kind::Edge: + if (theReplacement.NodeKind == BRepGraph_NodeId::Kind::Edge && theReplacement.IsValid()) + migrateEdgeBindings(BRepGraph_EdgeId(theNode.Index), + BRepGraph_EdgeId(theReplacement.Index)); + else + removeEdgeBindings(BRepGraph_EdgeId(theNode.Index)); + break; + case BRepGraph_NodeId::Kind::Face: + if (theReplacement.NodeKind == BRepGraph_NodeId::Kind::Face && theReplacement.IsValid()) + migrateFaceBindings(BRepGraph_FaceId(theNode.Index), + BRepGraph_FaceId(theReplacement.Index)); + else + invalidateFaceBindings(BRepGraph_FaceId(theNode.Index)); + break; + default: + break; + } +} + +//================================================================================================= + +void BRepGraph_RegularityLayer::OnCompact( + const NCollection_DataMap& theRemapMap) noexcept +{ + NCollection_DataMap aNewEdgeRegs; + NCollection_DataMap> aNewFaceToEdges; + + for (const auto& [aOldEdge, aOldRegularities] : myEdgeRegularities.Items()) + { + const BRepGraph_EdgeId aNewEdge = remapEdge(theRemapMap, aOldEdge); + if (!aNewEdge.IsValid()) + continue; + for (const RegularityEntry& anOldEntry : aOldRegularities.Entries) + { + BRepGraph_FaceId aNewFace1 = remapFace(theRemapMap, anOldEntry.FaceEntity1); + BRepGraph_FaceId aNewFace2 = remapFace(theRemapMap, anOldEntry.FaceEntity2); + if (!aNewFace1.IsValid() || !aNewFace2.IsValid()) + continue; + if (aNewFace2.Index < aNewFace1.Index) + std::swap(aNewFace1, aNewFace2); + + if (!aNewEdgeRegs.IsBound(aNewEdge)) + aNewEdgeRegs.Bind(aNewEdge, EdgeRegularities()); + EdgeRegularities& aRegularities = aNewEdgeRegs.ChangeFind(aNewEdge); + + // Deduplicate: if same face pair already exists for this edge, update continuity. + bool aDuplicate = false; + for (int aExIdx = 0; aExIdx < aRegularities.Entries.Length(); ++aExIdx) + { + RegularityEntry& anExisting = aRegularities.Entries.ChangeValue(aExIdx); + if (anExisting.FaceEntity1 == aNewFace1 && anExisting.FaceEntity2 == aNewFace2) + { + anExisting.Continuity = anOldEntry.Continuity; + aDuplicate = true; + break; + } + } + if (aDuplicate) + continue; + + RegularityEntry& anEntry = aRegularities.Entries.Appended(); + anEntry.FaceEntity1 = aNewFace1; + anEntry.FaceEntity2 = aNewFace2; + anEntry.Continuity = anOldEntry.Continuity; + + appendUnique(aNewFaceToEdges, aNewFace1, aNewEdge); + appendUnique(aNewFaceToEdges, aNewFace2, aNewEdge); + } + } + + myEdgeRegularities = std::move(aNewEdgeRegs); + myFaceToEdges = std::move(aNewFaceToEdges); +} + +//================================================================================================= + +void BRepGraph_RegularityLayer::InvalidateAll() noexcept +{ + Clear(); +} + +//================================================================================================= + +void BRepGraph_RegularityLayer::Clear() noexcept +{ + myEdgeRegularities.Clear(); + myFaceToEdges.Clear(); +} diff --git a/src/ModelingData/TKBRep/BRepGraph/BRepGraph_RegularityLayer.hxx b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_RegularityLayer.hxx new file mode 100644 index 0000000000..c9dfd715d0 --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_RegularityLayer.hxx @@ -0,0 +1,99 @@ +// Copyright (c) 2026 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 _BRepGraph_RegularityLayer_HeaderFile +#define _BRepGraph_RegularityLayer_HeaderFile + +#include + +#include +#include +#include + +//! @brief Stores edge continuity records between adjacent face pairs. +class BRepGraph_RegularityLayer : public BRepGraph_Layer +{ +public: + //! Return fixed layer type GUID. + [[nodiscard]] Standard_EXPORT static const Standard_GUID& GetID(); + + //! Return this layer type GUID. + [[nodiscard]] Standard_EXPORT const Standard_GUID& ID() const override; + + struct RegularityEntry + { + BRepGraph_FaceId FaceEntity1; + BRepGraph_FaceId FaceEntity2; + GeomAbs_Shape Continuity = GeomAbs_C0; + }; + + struct EdgeRegularities + { + NCollection_Vector Entries; + + [[nodiscard]] bool IsEmpty() const { return Entries.IsEmpty(); } + }; + + Standard_EXPORT const EdgeRegularities* FindEdgeRegularities( + const BRepGraph_EdgeId theEdge) const; + + Standard_EXPORT bool FindContinuity(const BRepGraph_EdgeId theEdge, + const BRepGraph_FaceId theFace1, + const BRepGraph_FaceId theFace2, + GeomAbs_Shape* const theContinuity = nullptr) const; + + Standard_EXPORT int NbRegularities(const BRepGraph_EdgeId theEdge) const; + Standard_EXPORT GeomAbs_Shape MaxContinuity(const BRepGraph_EdgeId theEdge) const; + + [[nodiscard]] bool HasBindings() const { return myEdgeRegularities.Extent() != 0; } + + Standard_EXPORT void SetRegularity(const BRepGraph_EdgeId theEdge, + const BRepGraph_FaceId theFace1, + const BRepGraph_FaceId theFace2, + const GeomAbs_Shape theContinuity); + + Standard_EXPORT const TCollection_AsciiString& Name() const override; + [[nodiscard]] Standard_EXPORT int SubscribedKinds() const override; + Standard_EXPORT void OnNodeModified(const BRepGraph_NodeId theNode) noexcept override; + Standard_EXPORT void OnNodesModified( + const NCollection_Vector& theModifiedNodes) noexcept override; + Standard_EXPORT void OnNodeRemoved(const BRepGraph_NodeId theNode, + const BRepGraph_NodeId theReplacement) noexcept override; + Standard_EXPORT void OnCompact( + const NCollection_DataMap& theRemapMap) noexcept override; + Standard_EXPORT void InvalidateAll() noexcept override; + Standard_EXPORT void Clear() noexcept override; + + DEFINE_STANDARD_RTTIEXT(BRepGraph_RegularityLayer, BRepGraph_Layer) + +private: + void normalizeFacePair(BRepGraph_FaceId& theFace1, BRepGraph_FaceId& theFace2) const noexcept; + EdgeRegularities& changeEdgeRegularities(const BRepGraph_EdgeId theEdge); + void bindFaceToEdge(const BRepGraph_FaceId theFace, const BRepGraph_EdgeId theEdge); + void unbindFaceFromEdge(const BRepGraph_FaceId theFace, const BRepGraph_EdgeId theEdge) noexcept; + void removeRegularity(const BRepGraph_EdgeId theEdge, + const BRepGraph_FaceId theFace1, + const BRepGraph_FaceId theFace2) noexcept; + void removeEdgeBindings(const BRepGraph_EdgeId theEdge) noexcept; + void invalidateFaceBindings(const BRepGraph_FaceId theFace) noexcept; + void migrateEdgeBindings(const BRepGraph_EdgeId theOldEdge, + const BRepGraph_EdgeId theNewEdge) noexcept; + void migrateFaceBindings(const BRepGraph_FaceId theOldFace, + const BRepGraph_FaceId theNewFace) noexcept; + +private: + NCollection_DataMap myEdgeRegularities; + NCollection_DataMap> myFaceToEdges; +}; + +#endif // _BRepGraph_RegularityLayer_HeaderFile diff --git a/src/ModelingData/TKBRep/BRepGraph/BRepGraph_RepId.hxx b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_RepId.hxx new file mode 100644 index 0000000000..98b7e42172 --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_RepId.hxx @@ -0,0 +1,298 @@ +// Copyright (c) 2026 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 _BRepGraph_RepId_HeaderFile +#define _BRepGraph_RepId_HeaderFile + +#include +#include + +#include +#include + +//! Lightweight typed index into a per-kind representation vector inside BRepGraph. +//! +//! The pair (Kind, Index) forms a unique representation identifier within one +//! graph instance. Default-constructed RepId has Index = -1 (invalid). +//! +//! Representations are NOT topology nodes - they hold geometry or mesh data +//! referenced by topology entities. They do not participate in BFS traversal, +//! reverse index, or parent-child relationships. +//! +//! RepId is a value type: cheap to copy, compare, hash. +struct BRepGraph_RepId +{ + //! Categories of representation data. + enum class Kind : int + { + // Geometry (exact mathematical definition) + Surface = 0, //!< Geom_Surface for faces + Curve3D = 1, //!< Geom_Curve for edges + Curve2D = 2, //!< Geom2d_Curve for coedges (PCurve geometry) + + // Mesh (discrete approximation) + Triangulation = 3, //!< Poly_Triangulation for faces + Polygon3D = 4, //!< Poly_Polygon3D for edges + Polygon2D = 5, //!< Poly_Polygon2D for coedges (polygon-on-surface) + PolygonOnTri = 6, //!< Poly_PolygonOnTriangulation for coedges + + // Reserved 7-19 for future built-in types + // Custom plugin types start at 100+ + }; + + //! True if the kind is a geometry kind (Surface, Curve3D, Curve2D). + static bool IsGeometryKind(const Kind theKind) + { + return theKind == Kind::Surface || theKind == Kind::Curve3D || theKind == Kind::Curve2D; + } + + //! True if the kind is a mesh kind (Triangulation, Polygon3D, Polygon2D, PolygonOnTri). + static bool IsMeshKind(const Kind theKind) + { + return theKind == Kind::Triangulation || theKind == Kind::Polygon3D + || theKind == Kind::Polygon2D || theKind == Kind::PolygonOnTri; + } + + //! @brief Compile-time typed wrapper around BRepGraph_RepId. + //! + //! Provides compile-time kind safety: a Typed + //! cannot be accidentally used where a Typed is expected. + //! Implicitly converts to BRepGraph_RepId for backward compatibility. + //! + //! @tparam TheKind the BRepGraph_RepId::Kind this typed id represents + template + struct Typed + { + int Index; + + //! Default: invalid (Index = -1). + Typed() + : Index(-1) + { + } + + //! Construct from index. + explicit Typed(const int theIdx) + : Index(theIdx) + { + Standard_ASSERT_VOID(theIdx >= -1, "index must be >= -1"); + } + + //! True if this id points to an allocated representation slot. + [[nodiscard]] bool IsValid() const { return Index >= 0; } + + //! True if this id points to an allocated slot within [0, theMaxCount). + [[nodiscard]] bool IsValid(const int theMaxCount) const + { + Standard_ASSERT_RETURN(theMaxCount >= 0, "max count must be non-negative", false); + return Index >= 0 && Index < theMaxCount; + } + + //! Implicit conversion to untyped RepId. + operator BRepGraph_RepId() const { return BRepGraph_RepId(TheKind, Index); } + + //! Explicit conversion from untyped RepId. + //! Asserts that the Kind matches in debug builds. + //! @param[in] theId untyped RepId to convert + static Typed FromRepId(const BRepGraph_RepId theId) + { + Standard_ASSERT_VOID(theId.RepKind == TheKind, "RepId kind mismatch"); + return Typed(theId.Index); + } + + bool operator==(const Typed& theOther) const { return Index == theOther.Index; } + + bool operator!=(const Typed& theOther) const { return Index != theOther.Index; } + + bool operator<(const Typed& theOther) const { return Index < theOther.Index; } + + bool operator<=(const Typed& theOther) const { return Index <= theOther.Index; } + + bool operator>(const Typed& theOther) const { return Index > theOther.Index; } + + bool operator>=(const Typed& theOther) const { return Index >= theOther.Index; } + + //! Pre-increment (++id). + Typed& operator++() + { + Standard_ASSERT_VOID(Index >= 0, "pre-increment on invalid id"); + ++Index; + return *this; + } + + //! Post-increment (id++). + Typed operator++(int) + { + Standard_ASSERT_VOID(Index >= 0, "post-increment on invalid id"); + Typed aPrev = *this; + ++Index; + return aPrev; + } + + //! Advance by offset. + [[nodiscard]] Typed operator+(const int theOffset) const { return Typed(Index + theOffset); } + + //! Retreat by offset. + [[nodiscard]] Typed operator-(const int theOffset) const { return Typed(Index - theOffset); } + + //! Comparison with untyped RepId (checks both Kind and Index). + bool operator==(const BRepGraph_RepId& theOther) const + { + return theOther.RepKind == TheKind && theOther.Index == Index; + } + + bool operator!=(const BRepGraph_RepId& theOther) const { return !(*this == theOther); } + + //! Allow reversed comparison: RepId == Typed. + friend bool operator==(const BRepGraph_RepId& theLhs, const Typed& theRhs) + { + return theRhs == theLhs; + } + + friend bool operator!=(const BRepGraph_RepId& theLhs, const Typed& theRhs) + { + return theRhs != theLhs; + } + }; + + Kind RepKind; + int Index; + + //! Default: invalid RepId (Index = -1). + //! RepKind is set to Kind::Surface but is meaningless when !IsValid(). + BRepGraph_RepId() + : RepKind(Kind::Surface), + Index(-1) + { + } + + BRepGraph_RepId(const Kind theKind, const int theIdx) + : RepKind(theKind), + Index(theIdx) + { + } + + //! True if this id points to an allocated representation slot. + [[nodiscard]] bool IsValid() const { return Index >= 0; } + + //! True if this id points to an allocated slot within [0, theMaxCount). + [[nodiscard]] bool IsValid(const int theMaxCount) const + { + Standard_ASSERT_RETURN(theMaxCount >= 0, "max count must be non-negative", false); + return Index >= 0 && Index < theMaxCount; + } + + //! @name Static factory methods returning typed RepIds. + static Typed Surface(const int theIdx) { return Typed(theIdx); } + + static Typed Curve3D(const int theIdx) { return Typed(theIdx); } + + static Typed Curve2D(const int theIdx) { return Typed(theIdx); } + + static Typed Triangulation(const int theIdx) + { + return Typed(theIdx); + } + + static Typed Polygon3D(const int theIdx) + { + return Typed(theIdx); + } + + static Typed Polygon2D(const int theIdx) + { + return Typed(theIdx); + } + + static Typed PolygonOnTri(const int theIdx) + { + return Typed(theIdx); + } + + bool operator==(const BRepGraph_RepId& theOther) const + { + return RepKind == theOther.RepKind && Index == theOther.Index; + } + + bool operator!=(const BRepGraph_RepId& theOther) const { return !(*this == theOther); } + + bool operator<(const BRepGraph_RepId& theOther) const + { + if (RepKind != theOther.RepKind) + return static_cast(RepKind) < static_cast(theOther.RepKind); + return Index < theOther.Index; + } + + //! Pre-increment (++id). + BRepGraph_RepId& operator++() + { + Standard_ASSERT_VOID(Index >= 0, "pre-increment on invalid id"); + ++Index; + return *this; + } + + //! Post-increment (id++). + BRepGraph_RepId operator++(int) + { + Standard_ASSERT_VOID(Index >= 0, "post-increment on invalid id"); + BRepGraph_RepId aPrev = *this; + ++Index; + return aPrev; + } + + //! Advance by offset. + [[nodiscard]] BRepGraph_RepId operator+(const int theOffset) const + { + return BRepGraph_RepId(RepKind, Index + theOffset); + } + + //! Retreat by offset. + [[nodiscard]] BRepGraph_RepId operator-(const int theOffset) const + { + return BRepGraph_RepId(RepKind, Index - theOffset); + } +}; + +//! @name Convenience type aliases for typed RepIds. +using BRepGraph_SurfaceRepId = BRepGraph_RepId::Typed; +using BRepGraph_Curve3DRepId = BRepGraph_RepId::Typed; +using BRepGraph_Curve2DRepId = BRepGraph_RepId::Typed; +using BRepGraph_TriangulationRepId = BRepGraph_RepId::Typed; +using BRepGraph_Polygon3DRepId = BRepGraph_RepId::Typed; +using BRepGraph_Polygon2DRepId = BRepGraph_RepId::Typed; +using BRepGraph_PolygonOnTriRepId = BRepGraph_RepId::Typed; + +//! std::hash specialization for NCollection_DefaultHasher support. +template <> +struct std::hash +{ + size_t operator()(const BRepGraph_RepId& theId) const noexcept + { + size_t aCombination[2]; + aCombination[0] = opencascade::hash(static_cast(theId.RepKind)); + aCombination[1] = opencascade::hash(theId.Index); + return opencascade::hashBytes(aCombination, sizeof(aCombination)); + } +}; + +//! std::hash specialization for BRepGraph_RepId::Typed. +template +struct std::hash> +{ + size_t operator()(const BRepGraph_RepId::Typed& theId) const noexcept + { + return std::hash{}(static_cast(theId)); + } +}; + +#endif // _BRepGraph_RepId_HeaderFile diff --git a/src/ModelingData/TKBRep/BRepGraph/BRepGraph_ShapesView.cxx b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_ShapesView.cxx new file mode 100644 index 0000000000..6d2e6a22ce --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_ShapesView.cxx @@ -0,0 +1,278 @@ +// Copyright (c) 2026 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 + +namespace +{ +struct BRepGraph_ReconstructionContext +{ + const BRepGraph* Graph = nullptr; + const BRepGraphInc_Storage* Storage = nullptr; + const BRepGraph_ParamLayer* Params = nullptr; + const BRepGraph_RegularityLayer* Regularities = nullptr; + BRepGraphInc_Reconstruct::Cache Cache; + NCollection_Map ActiveProducts; +}; + +static TopoDS_Shape reconstructProductLocal(BRepGraph_ReconstructionContext& theContext, + const BRepGraph_ProductId theProduct); + +static TopoDS_Shape reconstructOccurrenceLocal(BRepGraph_ReconstructionContext& theContext, + const BRepGraph_OccurrenceId theOccurrence) +{ + const BRepGraphInc_Storage& aStorage = *theContext.Storage; + if (!theOccurrence.IsValid(aStorage.NbOccurrences())) + return TopoDS_Shape(); + + const BRepGraphInc::OccurrenceDef& anOccurrence = aStorage.Occurrence(theOccurrence); + if (anOccurrence.IsRemoved || !anOccurrence.ProductDefId.IsValid(aStorage.NbProducts())) + return TopoDS_Shape(); + + TopoDS_Shape aShape = reconstructProductLocal(theContext, anOccurrence.ProductDefId); + if (!aShape.IsNull() && !anOccurrence.Placement.IsIdentity()) + aShape.Move(anOccurrence.Placement); + return aShape; +} + +static TopoDS_Shape reconstructProductLocal(BRepGraph_ReconstructionContext& theContext, + const BRepGraph_ProductId theProduct) +{ + const BRepGraphInc_Storage& aStorage = *theContext.Storage; + if (!theProduct.IsValid(aStorage.NbProducts())) + return TopoDS_Shape(); + + const BRepGraph_NodeId aProductNode = BRepGraph_ProductId(theProduct.Index); + if (const TopoDS_Shape* aCached = theContext.Cache.Seek(aProductNode)) + return *aCached; + + const BRepGraphInc::ProductDef& aProduct = aStorage.Product(theProduct); + if (aProduct.IsRemoved || theContext.ActiveProducts.Contains(theProduct.Index)) + return TopoDS_Shape(); + + theContext.ActiveProducts.Add(theProduct.Index); + + TopoDS_Shape aResult; + if (aProduct.ShapeRootId.IsValid()) + { + aResult = BRepGraphInc_Reconstruct::Node(aStorage, + aProduct.ShapeRootId, + theContext.Cache, + theContext.Params, + theContext.Regularities); + if (!aResult.IsNull()) + { + if (aProduct.RootOrientation != TopAbs_FORWARD) + aResult.Compose(aProduct.RootOrientation); + if (!aProduct.RootLocation.IsIdentity()) + aResult.Move(aProduct.RootLocation); + } + } + else + { + BRep_Builder aBuilder; + TopoDS_Compound aCompound; + aBuilder.MakeCompound(aCompound); + + for (BRepGraph_RefsOccurrenceOfProduct anOccIt(*theContext.Graph, theProduct); anOccIt.More(); + anOccIt.Next()) + { + const BRepGraphInc::OccurrenceRef& anOccurrenceRef = + aStorage.OccurrenceRef(anOccIt.CurrentId()); + + TopoDS_Shape aChild = reconstructOccurrenceLocal(theContext, anOccurrenceRef.OccurrenceDefId); + if (!aChild.IsNull()) + aBuilder.Add(aCompound, aChild); + } + + aResult = aCompound; + } + + theContext.ActiveProducts.Remove(theProduct.Index); + if (!aResult.IsNull()) + theContext.Cache.Bind(aProductNode, aResult); + return aResult; +} + +static TopoDS_Shape reconstructShape(BRepGraph_ReconstructionContext& theContext, + const BRepGraph_NodeId theNode) +{ + if (!theNode.IsValid()) + return TopoDS_Shape(); + + const BRepGraphInc_Storage& aStorage = *theContext.Storage; + switch (theNode.NodeKind) + { + case BRepGraph_NodeId::Kind::Product: + return reconstructProductLocal(theContext, BRepGraph_ProductId(theNode.Index)); + case BRepGraph_NodeId::Kind::Occurrence: { + const BRepGraph_OccurrenceId anOccurrence(theNode.Index); + if (!anOccurrence.IsValid(aStorage.NbOccurrences())) + return TopoDS_Shape(); + + const BRepGraphInc::OccurrenceDef& anOccurrenceDef = aStorage.Occurrence(anOccurrence); + if (anOccurrenceDef.IsRemoved || !anOccurrenceDef.ProductDefId.IsValid(aStorage.NbProducts())) + return TopoDS_Shape(); + + TopoDS_Shape aShape = reconstructProductLocal(theContext, anOccurrenceDef.ProductDefId); + if (!aShape.IsNull()) + { + const TopLoc_Location aGlobalLocation = + theContext.Graph->Topo().Occurrences().OccurrenceLocation(anOccurrence); + if (!aGlobalLocation.IsIdentity()) + aShape.Move(aGlobalLocation); + } + return aShape; + } + default: + return BRepGraphInc_Reconstruct::Node(aStorage, + theNode, + theContext.Cache, + theContext.Params, + theContext.Regularities); + } +} + +static BRepGraph_ReconstructionContext makeReconstructionContext( + const BRepGraph* theGraph, + const BRepGraphInc_Storage& theStorage) +{ + BRepGraph_ReconstructionContext aContext; + aContext.Graph = theGraph; + aContext.Storage = &theStorage; + + const occ::handle aParamLayer = + theGraph->LayerRegistry().FindLayer(); + const occ::handle aRegularityLayer = + theGraph->LayerRegistry().FindLayer(); + aContext.Params = aParamLayer.get(); + aContext.Regularities = aRegularityLayer.get(); + return aContext; +} +} // namespace + +//================================================================================================= + +TopoDS_Shape BRepGraph::ShapesView::Shape(const BRepGraph_NodeId theNode) const +{ + if (!theNode.IsValid()) + return TopoDS_Shape(); + + // Fast path: if entity was never mutated, return the original shape. + const BRepGraphInc::BaseDef* aDef = myGraph->topoEntity(theNode); + if (aDef != nullptr && aDef->SubtreeGen == 0) + { + const TopoDS_Shape* anOrig = myGraph->myData->myIncStorage.FindOriginal(theNode); + if (anOrig != nullptr) + return *anOrig; + } + + // Check mutable cache under shared lock with SubtreeGen validation. + { + std::shared_lock aReadLock(myGraph->myData->myCurrentShapesMutex); + const BRepGraph_Data::CachedShape* aCached = myGraph->myData->myCurrentShapes.Seek(theNode); + if (aCached != nullptr && aDef != nullptr && aCached->StoredSubtreeGen == aDef->SubtreeGen) + return aCached->Shape; + } + + // Reconstruct from incidence storage / assembly facade. + BRepGraph_ReconstructionContext aContext = + makeReconstructionContext(myGraph, myGraph->myData->myIncStorage); + TopoDS_Shape aReconstructed = reconstructShape(aContext, theNode); + + // Store under exclusive lock with double-check to avoid redundant writes + // when multiple threads reconstruct the same parent node concurrently. + if (!aReconstructed.IsNull() && aDef != nullptr) + { + std::unique_lock aWriteLock(myGraph->myData->myCurrentShapesMutex); + const BRepGraph_Data::CachedShape* aExisting = myGraph->myData->myCurrentShapes.Seek(theNode); + if (aExisting != nullptr && aExisting->StoredSubtreeGen == aDef->SubtreeGen) + { + return aExisting->Shape; + } + BRepGraph_Data::CachedShape anEntry; + anEntry.Shape = aReconstructed; + anEntry.StoredSubtreeGen = aDef->SubtreeGen; + myGraph->myData->myCurrentShapes.Bind(theNode, anEntry); + if (theNode.NodeKind != BRepGraph_NodeId::Kind::Product + && theNode.NodeKind != BRepGraph_NodeId::Kind::Occurrence) + { + myGraph->myData->myIncStorage.BindTShapeToNode(aReconstructed.TShape().get(), theNode); + } + } + return aReconstructed; +} + +//================================================================================================= + +bool BRepGraph::ShapesView::HasOriginal(const BRepGraph_NodeId theNode) const +{ + return myGraph->myData->myIncStorage.HasOriginal(theNode); +} + +//================================================================================================= + +const TopoDS_Shape& BRepGraph::ShapesView::OriginalOf(const BRepGraph_NodeId theNode) const +{ + const TopoDS_Shape* aShape = myGraph->myData->myIncStorage.FindOriginal(theNode); + if (aShape == nullptr) + throw Standard_ProgramError("BRepGraph::ShapesView::OriginalOf() - no original shape."); + return *aShape; +} + +//================================================================================================= + +TopoDS_Shape BRepGraph::ShapesView::Reconstruct(const BRepGraph_NodeId theRoot) const +{ + BRepGraph_ReconstructionContext aContext = + makeReconstructionContext(myGraph, myGraph->myData->myIncStorage); + return reconstructShape(aContext, theRoot); +} + +//================================================================================================= + +//================================================================================================= + +BRepGraph_NodeId BRepGraph::ShapesView::FindNode(const TopoDS_Shape& theShape) const +{ + if (theShape.IsNull()) + return BRepGraph_NodeId(); + + const BRepGraph_NodeId* aNodeId = + myGraph->myData->myIncStorage.FindNodeByTShape(theShape.TShape().get()); + if (aNodeId != nullptr) + return *aNodeId; + return BRepGraph_NodeId(); +} + +//================================================================================================= + +bool BRepGraph::ShapesView::HasNode(const TopoDS_Shape& theShape) const +{ + if (theShape.IsNull()) + return false; + + return myGraph->myData->myIncStorage.HasTShapeBinding(theShape.TShape().get()); +} diff --git a/src/ModelingData/TKBRep/BRepGraph/BRepGraph_ShapesView.hxx b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_ShapesView.hxx new file mode 100644 index 0000000000..249d84b3d5 --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_ShapesView.hxx @@ -0,0 +1,103 @@ +// Copyright (c) 2026 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 _BRepGraph_ShapesView_HeaderFile +#define _BRepGraph_ShapesView_HeaderFile + +#include + +//! @brief Read-only view for TopoDS_Shape reconstruction from graph data. +//! +//! Reconstructs TopoDS shapes from graph nodes on demand, with caching +//! for repeated access. Topology nodes are delegated to the incidence-table +//! reconstruction backend, while Product / Occurrence nodes are assembled at +//! the facade level using product-local roots and occurrence placement chains. +//! Provides lookup from original Build()-time shapes back to their graph +//! NodeIds via TShape pointer comparison. Shape() is the stable cached public +//! route for repeated access; Reconstruct() forces a fresh rebuild with the +//! same node-kind semantics and bypasses the persistent reconstructed-shape cache. +//! Build() and Compact() clear the persistent reconstructed-shape cache. +//! Obtained via BRepGraph::Shapes(). +class BRepGraph::ShapesView +{ +public: + //! Return or reconstruct a TopoDS_Shape for a node. + //! Prefer this route for repeated public queries. + //! Returns a cached shape when available and valid; otherwise reconstructs. + //! Topology definition nodes (Vertex..CompSolid) reconstruct their topology + //! directly, without assembly wrappers. + //! Product nodes are reconstructed in product-local coordinates. + //! Occurrence nodes are reconstructed with cumulative occurrence placement. + //! @param[in] theNode node identifier + //! @return corresponding TopoDS_Shape + [[nodiscard]] Standard_EXPORT TopoDS_Shape Shape(const BRepGraph_NodeId theNode) const; + + //! Check if the node has an original shape from Build(). + //! Nodes created programmatically through Builder().Add*() do not have an + //! original Build()/AppendFlattenedShape() shape. + //! @param[in] theNode node identifier + //! @return true if an original shape exists + [[nodiscard]] Standard_EXPORT bool HasOriginal(const BRepGraph_NodeId theNode) const; + + //! Return the original TopoDS_Shape stored during Build(). + //! @param[in] theNode node identifier + //! @return reference to the exact TopoDS_Shape stored during Build() or + //! AppendFlattenedShape() + //! @exception Standard_ProgramError if no original shape exists + [[nodiscard]] Standard_EXPORT const TopoDS_Shape& OriginalOf( + const BRepGraph_NodeId theNode) const; + + //! Reconstruct a TopoDS_Shape from a graph node without using the persistent cache. + //! Use this when the caller explicitly needs a fresh rebuild instead of the + //! shared cached shape returned by Shape(). This method does not populate the + //! persistent reconstructed-shape cache. + //! Topology definition nodes reconstruct topology directly. + //! Product nodes are reconstructed in product-local coordinates. + //! Occurrence nodes are reconstructed with cumulative occurrence placement. + //! @param[in] theRoot definition node identifier + //! @return reconstructed shape + [[nodiscard]] Standard_EXPORT TopoDS_Shape Reconstruct(const BRepGraph_NodeId theRoot) const; + + //! Look up the definition NodeId for a shape from the Build() input. + //! Uses TShape pointer comparison (same semantics as IsSame()). + //! Synthetic Product / Occurrence reconstructions are not given dedicated + //! TShape bindings, so lookup is only guaranteed for Build()-time topology. + //! Programmatically created Builder().Add*() nodes can still be located by + //! UID or by direct iteration over Topo() definitions. + //! @param[in] theShape shape to look up + //! @return node identifier, or invalid NodeId if the shape is not in the graph + [[nodiscard]] Standard_EXPORT BRepGraph_NodeId FindNode(const TopoDS_Shape& theShape) const; + + //! Check if a shape is known to the graph (was part of the Build() input). + //! Uses TShape pointer comparison (same semantics as IsSame()). + //! Synthetic Product / Occurrence reconstructions are not given dedicated + //! TShape bindings, so this is only guaranteed for Build()-time topology. + //! Programmatically created Builder().Add*() nodes can still be located by + //! UID or by direct iteration over Topo() definitions. + //! @param[in] theShape shape to check + //! @return true if the shape has a corresponding definition node + [[nodiscard]] Standard_EXPORT bool HasNode(const TopoDS_Shape& theShape) const; + +private: + friend class BRepGraph; + friend struct BRepGraph_Data; + + explicit ShapesView(const BRepGraph* theGraph) + : myGraph(theGraph) + { + } + + const BRepGraph* myGraph; +}; + +#endif // _BRepGraph_ShapesView_HeaderFile diff --git a/src/ModelingData/TKBRep/BRepGraph/BRepGraph_Tool.cxx b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_Tool.cxx new file mode 100644 index 0000000000..8398374555 --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_Tool.cxx @@ -0,0 +1,656 @@ +// Copyright (c) 2026 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 + +//================================================================================================= + +gp_Pnt BRepGraph_Tool::Vertex::Pnt(const BRepGraph& theGraph, + const BRepGraphInc::VertexUsage& theRef) +{ + const gp_Pnt& aPnt = theGraph.Topo().Vertices().Definition(theRef.DefId).Point; + if (theRef.Location.IsIdentity()) + return aPnt; + return aPnt.Transformed(theRef.Location.Transformation()); +} + +//================================================================================================= + +gp_Pnt BRepGraph_Tool::Vertex::Pnt(const BRepGraph& theGraph, const BRepGraph_VertexId theVertex) +{ + return theGraph.Topo().Vertices().Definition(theVertex).Point; +} + +//================================================================================================= + +double BRepGraph_Tool::Vertex::Tolerance(const BRepGraph& theGraph, + const BRepGraph_VertexId theVertex) +{ + return theGraph.Topo().Vertices().Definition(theVertex).Tolerance; +} + +//================================================================================================= + +double BRepGraph_Tool::Vertex::Parameter(const BRepGraph& theGraph, + const BRepGraph_VertexId theVertex, + const BRepGraph_EdgeId theEdge) +{ + double aParameter = 0.0; + const occ::handle aParamLayer = + theGraph.LayerRegistry().FindLayer(); + if (!aParamLayer.IsNull() && aParamLayer->FindPointOnCurve(theVertex, theEdge, &aParameter)) + return aParameter; + throw Standard_NoSuchObject("BRepGraph_Tool::Parameter - no PointOnCurve for this edge"); +} + +//================================================================================================= + +gp_Pnt2d BRepGraph_Tool::Vertex::Parameters(const BRepGraph& theGraph, + const BRepGraph_VertexId theVertex, + const BRepGraph_FaceId theFace) +{ + gp_Pnt2d aUV; + const occ::handle aParamLayer = + theGraph.LayerRegistry().FindLayer(); + if (!aParamLayer.IsNull() && aParamLayer->FindPointOnSurface(theVertex, theFace, &aUV)) + return aUV; + throw Standard_NoSuchObject("BRepGraph_Tool::Parameters - no PointOnSurface for this face"); +} + +//================================================================================================= + +double BRepGraph_Tool::Edge::Tolerance(const BRepGraph& theGraph, const BRepGraph_EdgeId theEdge) +{ + return theGraph.Topo().Edges().Definition(theEdge).Tolerance; +} + +//================================================================================================= + +bool BRepGraph_Tool::Edge::Degenerated(const BRepGraph& theGraph, const BRepGraph_EdgeId theEdge) +{ + return theGraph.Topo().Edges().Definition(theEdge).IsDegenerate; +} + +//================================================================================================= + +bool BRepGraph_Tool::Edge::SameParameter(const BRepGraph& theGraph, const BRepGraph_EdgeId theEdge) +{ + return theGraph.Topo().Edges().Definition(theEdge).SameParameter; +} + +//================================================================================================= + +bool BRepGraph_Tool::Edge::SameRange(const BRepGraph& theGraph, const BRepGraph_EdgeId theEdge) +{ + return theGraph.Topo().Edges().Definition(theEdge).SameRange; +} + +//================================================================================================= + +std::pair BRepGraph_Tool::Edge::Range(const BRepGraph& theGraph, + const BRepGraph_EdgeId theEdge) +{ + const BRepGraphInc::EdgeDef& anEdge = theGraph.Topo().Edges().Definition(theEdge); + return {anEdge.ParamFirst, anEdge.ParamLast}; +} + +//================================================================================================= + +bool BRepGraph_Tool::Edge::HasCurve(const BRepGraph& theGraph, const BRepGraph_EdgeId theEdge) +{ + return theGraph.Topo().Edges().Curve3DRepId(theEdge).IsValid(); +} + +//================================================================================================= + +bool BRepGraph_Tool::Edge::HasPolygon3D(const BRepGraph& theGraph, const BRepGraph_EdgeId theEdge) +{ + const BRepGraphInc::EdgeDef& anEdge = theGraph.Topo().Edges().Definition(theEdge); + return anEdge.Polygon3DRepId.IsValid() + && !theGraph.Topo().Poly().Polygon3DRep(anEdge.Polygon3DRepId).IsRemoved; +} + +//================================================================================================= + +const BRepGraphInc::VertexRef& BRepGraph_Tool::Edge::StartVertex(const BRepGraph& theGraph, + const BRepGraph_EdgeId theEdge) +{ + return theGraph.Refs().Vertices().Entry( + theGraph.Topo().Edges().Definition(theEdge).StartVertexRefId); +} + +//================================================================================================= + +const BRepGraphInc::VertexRef& BRepGraph_Tool::Edge::EndVertex(const BRepGraph& theGraph, + const BRepGraph_EdgeId theEdge) +{ + return theGraph.Refs().Vertices().Entry( + theGraph.Topo().Edges().Definition(theEdge).EndVertexRefId); +} + +//================================================================================================= + +GeomAdaptor_TransformedCurve BRepGraph_Tool::Edge::CurveAdaptor( + const BRepGraph& theGraph, + const BRepGraphInc::CoEdgeUsage& theRef) +{ + const BRepGraphInc::CoEdgeDef& aCoEdge = theGraph.Topo().CoEdges().Definition(theRef.DefId); + const BRepGraphInc::EdgeDef& anEdge = theGraph.Topo().Edges().Definition(aCoEdge.EdgeDefId); + const gp_Trsf aTrsf = theRef.Location.IsIdentity() ? gp_Trsf() : theRef.Location.Transformation(); + + // Prefer 3D curve when available. + if (anEdge.Curve3DRepId.IsValid()) + { + const BRepGraphInc::Curve3DRep& aCurveRep = + theGraph.Topo().Geometry().Curve3DRep(anEdge.Curve3DRepId); + if (aCurveRep.IsRemoved) + return GeomAdaptor_TransformedCurve(); + const occ::handle& aCurve = aCurveRep.Curve; + if (!aCurve.IsNull()) + return GeomAdaptor_TransformedCurve(aCurve, anEdge.ParamFirst, anEdge.ParamLast, aTrsf); + } + + // Fallback: CurveOnSurface from PCurve + surface. + if (aCoEdge.Curve2DRepId.IsValid() && aCoEdge.FaceDefId.IsValid()) + { + const BRepGraphInc::FaceDef& aFace = + theGraph.Topo().Faces().Definition(BRepGraph_FaceId(aCoEdge.FaceDefId.Index)); + if (aFace.SurfaceRepId.IsValid()) + { + const BRepGraphInc::Curve2DRep& aPCurveRep = + theGraph.Topo().Geometry().Curve2DRep(aCoEdge.Curve2DRepId); + const BRepGraphInc::SurfaceRep& aSurfaceRep = + theGraph.Topo().Geometry().SurfaceRep(aFace.SurfaceRepId); + if (aPCurveRep.IsRemoved || aSurfaceRep.IsRemoved) + return GeomAdaptor_TransformedCurve(); + const occ::handle& aPCurve = aPCurveRep.Curve; + const occ::handle& aSurf = aSurfaceRep.Surface; + if (!aPCurve.IsNull() && !aSurf.IsNull()) + { + GeomAdaptor_TransformedCurve aResult; + aResult.SetTrsf(aTrsf); + occ::handle aHC2d = + new Geom2dAdaptor_Curve(aPCurve, aCoEdge.ParamFirst, aCoEdge.ParamLast); + occ::handle aHS = new GeomAdaptor_Surface(aSurf); + aResult.LoadCurveOnSurface(new Adaptor3d_CurveOnSurface(aHC2d, aHS)); + return aResult; + } + } + } + + return GeomAdaptor_TransformedCurve(); +} + +//================================================================================================= + +GeomAdaptor_TransformedCurve BRepGraph_Tool::Edge::CurveAdaptor(const BRepGraph& theGraph, + const BRepGraph_EdgeId theEdge) +{ + const BRepGraphInc::EdgeDef& anEdge = theGraph.Topo().Edges().Definition(theEdge); + if (!anEdge.Curve3DRepId.IsValid()) + return GeomAdaptor_TransformedCurve(); + const BRepGraphInc::Curve3DRep& aCurveRep = + theGraph.Topo().Geometry().Curve3DRep(anEdge.Curve3DRepId); + if (aCurveRep.IsRemoved) + return GeomAdaptor_TransformedCurve(); + const occ::handle& aCurve = aCurveRep.Curve; + if (aCurve.IsNull()) + return GeomAdaptor_TransformedCurve(); + return GeomAdaptor_TransformedCurve(aCurve, anEdge.ParamFirst, anEdge.ParamLast, gp_Trsf()); +} + +//================================================================================================= + +static const occ::handle THE_NULL_CURVE; + +const occ::handle& BRepGraph_Tool::Edge::Curve(const BRepGraph& theGraph, + const BRepGraph_EdgeId theEdge) +{ + const BRepGraph_Curve3DRepId aRepId = theGraph.Topo().Edges().Curve3DRepId(theEdge); + if (!aRepId.IsValid()) + return THE_NULL_CURVE; + return theGraph.Topo().Geometry().Curve3DRep(aRepId).Curve; +} + +//================================================================================================= + +occ::handle BRepGraph_Tool::Edge::Curve(const BRepGraph& theGraph, + const BRepGraphInc::CoEdgeUsage& theRef) +{ + const BRepGraphInc::CoEdgeDef& aCoEdge = theGraph.Topo().CoEdges().Definition(theRef.DefId); + const BRepGraphInc::EdgeDef& anEdge = theGraph.Topo().Edges().Definition(aCoEdge.EdgeDefId); + if (!anEdge.Curve3DRepId.IsValid()) + return occ::handle(); + const BRepGraphInc::Curve3DRep& aCurveRep = + theGraph.Topo().Geometry().Curve3DRep(anEdge.Curve3DRepId); + if (aCurveRep.IsRemoved) + return occ::handle(); + const occ::handle& aCurve = aCurveRep.Curve; + if (aCurve.IsNull() || theRef.Location.IsIdentity()) + return aCurve; + return occ::down_cast(aCurve->Transformed(theRef.Location.Transformation())); +} + +//================================================================================================= + +static const occ::handle THE_NULL_POLYGON3D; + +const occ::handle& BRepGraph_Tool::Edge::Polygon3D(const BRepGraph& theGraph, + const BRepGraph_EdgeId theEdge) +{ + const BRepGraphInc::EdgeDef& anEdge = theGraph.Topo().Edges().Definition(theEdge); + if (!anEdge.Polygon3DRepId.IsValid()) + return THE_NULL_POLYGON3D; + const BRepGraphInc::Polygon3DRep& aPolygonRep = + theGraph.Topo().Poly().Polygon3DRep(anEdge.Polygon3DRepId); + if (aPolygonRep.IsRemoved) + return THE_NULL_POLYGON3D; + return aPolygonRep.Polygon; +} + +//================================================================================================= + +double BRepGraph_Tool::Vertex::PCurveParameter(const BRepGraph& theGraph, + const BRepGraph_VertexId theVertex, + const BRepGraph_CoEdgeId theCoEdge) +{ + double aParameter = 0.0; + const occ::handle aParamLayer = + theGraph.LayerRegistry().FindLayer(); + if (!aParamLayer.IsNull() && aParamLayer->FindPointOnPCurve(theVertex, theCoEdge, &aParameter)) + return aParameter; + throw Standard_NoSuchObject("BRepGraph_Tool::PCurveParameter - no PointOnPCurve for this coedge"); +} + +//================================================================================================= + +bool BRepGraph_Tool::Edge::HasContinuity(const BRepGraph& theGraph, + const BRepGraph_EdgeId theEdge, + const BRepGraph_FaceId theFace1, + const BRepGraph_FaceId theFace2) +{ + const occ::handle aRegularityLayer = + theGraph.LayerRegistry().FindLayer(); + return !aRegularityLayer.IsNull() + && aRegularityLayer->FindContinuity(theEdge, theFace1, theFace2, nullptr); +} + +//================================================================================================= + +GeomAbs_Shape BRepGraph_Tool::Edge::Continuity(const BRepGraph& theGraph, + const BRepGraph_EdgeId theEdge, + const BRepGraph_FaceId theFace1, + const BRepGraph_FaceId theFace2) +{ + GeomAbs_Shape aContinuity = GeomAbs_C0; + const occ::handle aRegularityLayer = + theGraph.LayerRegistry().FindLayer(); + if (!aRegularityLayer.IsNull() + && aRegularityLayer->FindContinuity(theEdge, theFace1, theFace2, &aContinuity)) + return aContinuity; + return GeomAbs_C0; +} + +//================================================================================================= + +GeomAbs_Shape BRepGraph_Tool::Edge::MaxContinuity(const BRepGraph& theGraph, + const BRepGraph_EdgeId theEdge) +{ + const occ::handle aRegularityLayer = + theGraph.LayerRegistry().FindLayer(); + return !aRegularityLayer.IsNull() ? aRegularityLayer->MaxContinuity(theEdge) : GeomAbs_C0; +} + +//================================================================================================= + +Geom2dAdaptor_Curve BRepGraph_Tool::CoEdge::PCurveAdaptor(const BRepGraph& theGraph, + const BRepGraphInc::CoEdgeUsage& theRef) +{ + return PCurveAdaptor(theGraph, theRef.DefId); +} + +//================================================================================================= + +Geom2dAdaptor_Curve BRepGraph_Tool::CoEdge::PCurveAdaptor(const BRepGraph& theGraph, + const BRepGraph_CoEdgeId theCoEdge) +{ + const BRepGraphInc::CoEdgeDef& aCoEdge = theGraph.Topo().CoEdges().Definition(theCoEdge); + if (aCoEdge.Curve2DRepId.IsValid()) + { + const BRepGraphInc::Curve2DRep& aCurveRep = + theGraph.Topo().Geometry().Curve2DRep(aCoEdge.Curve2DRepId); + if (aCurveRep.IsRemoved) + return Geom2dAdaptor_Curve(); + const occ::handle& aCurve = aCurveRep.Curve; + if (!aCurve.IsNull()) + return Geom2dAdaptor_Curve(aCurve, aCoEdge.ParamFirst, aCoEdge.ParamLast); + } + + // CurveOnPlane fallback: for planar faces without stored PCurves, compute the + // PCurve by projecting the 3D curve onto the plane's parameter space. + // This mirrors BRep_Tool::CurveOnSurface's CurveOnPlane behavior. + if (aCoEdge.FaceDefId.IsValid() && aCoEdge.EdgeDefId.IsValid()) + { + const BRepGraphInc::FaceDef& aFace = theGraph.Topo().Faces().Definition(aCoEdge.FaceDefId); + if (aFace.SurfaceRepId.IsValid()) + { + const BRepGraphInc::SurfaceRep& aSurfaceRep = + theGraph.Topo().Geometry().SurfaceRep(aFace.SurfaceRepId); + if (aSurfaceRep.IsRemoved) + return Geom2dAdaptor_Curve(); + const occ::handle& aSurf = aSurfaceRep.Surface; + const occ::handle aPlane = occ::handle::DownCast(aSurf); + if (!aPlane.IsNull()) + { + const BRepGraphInc::EdgeDef& anEdge = theGraph.Topo().Edges().Definition(aCoEdge.EdgeDefId); + if (anEdge.Curve3DRepId.IsValid()) + { + const BRepGraphInc::Curve3DRep& aCurveRep = + theGraph.Topo().Geometry().Curve3DRep(anEdge.Curve3DRepId); + if (aCurveRep.IsRemoved) + return Geom2dAdaptor_Curve(); + const occ::handle& aCurve3d = aCurveRep.Curve; + if (!aCurve3d.IsNull()) + { + // Project 3D curve onto the plane to get the 2D curve. + // Uses edge's 3D param range: the projected curve shares the same + // parameterization as the 3D curve, and the CoEdge has no stored range + // (ParamFirst/ParamLast are 0.0 when Curve2DRepIdx < 0). + occ::handle aProjected = + GeomProjLib::Curve2d(aCurve3d, anEdge.ParamFirst, anEdge.ParamLast, aPlane); + if (!aProjected.IsNull()) + return Geom2dAdaptor_Curve(aProjected, anEdge.ParamFirst, anEdge.ParamLast); + } + } + } + } + } + + return Geom2dAdaptor_Curve(); +} + +//================================================================================================= + +static const occ::handle THE_NULL_PCURVE; + +const occ::handle& BRepGraph_Tool::CoEdge::PCurve(const BRepGraph& theGraph, + const BRepGraph_CoEdgeId theCoEdge) +{ + const BRepGraph_Curve2DRepId aRepId = theGraph.Topo().CoEdges().Curve2DRepId(theCoEdge); + if (!aRepId.IsValid()) + return THE_NULL_PCURVE; + return theGraph.Topo().Geometry().Curve2DRep(aRepId).Curve; +} + +//================================================================================================= + +const occ::handle& BRepGraph_Tool::CoEdge::PCurve( + const BRepGraph& theGraph, + const BRepGraphInc::CoEdgeDef& theCoEdge) +{ + if (!theCoEdge.Curve2DRepId.IsValid()) + return THE_NULL_PCURVE; + const BRepGraphInc::Curve2DRep& aCurveRep = + theGraph.Topo().Geometry().Curve2DRep(theCoEdge.Curve2DRepId); + if (aCurveRep.IsRemoved) + return THE_NULL_PCURVE; + return aCurveRep.Curve; +} + +//================================================================================================= + +bool BRepGraph_Tool::CoEdge::HasPCurve(const BRepGraph& theGraph, + const BRepGraph_CoEdgeId theCoEdge) +{ + return theGraph.Topo().CoEdges().Curve2DRepId(theCoEdge).IsValid(); +} + +//================================================================================================= + +std::pair BRepGraph_Tool::CoEdge::UVPoints(const BRepGraph& theGraph, + const BRepGraph_CoEdgeId theCoEdge) +{ + const BRepGraphInc::CoEdgeDef& aCoEdge = theGraph.Topo().CoEdges().Definition(theCoEdge); + return {aCoEdge.UV1, aCoEdge.UV2}; +} + +//================================================================================================= + +std::pair BRepGraph_Tool::CoEdge::Range(const BRepGraph& theGraph, + const BRepGraph_CoEdgeId theCoEdge) +{ + const BRepGraphInc::CoEdgeDef& aCoEdge = theGraph.Topo().CoEdges().Definition(theCoEdge); + return {aCoEdge.ParamFirst, aCoEdge.ParamLast}; +} + +//================================================================================================= + +bool BRepGraph_Tool::Edge::IsClosedOnFace(const BRepGraph& theGraph, + const BRepGraph_EdgeId theEdge, + const BRepGraph_FaceId theFace) +{ + const BRepGraphInc::CoEdgeDef* aCoEdge = theGraph.Topo().Edges().FindPCurve(theEdge, theFace); + return aCoEdge != nullptr && aCoEdge->SeamPairId.IsValid(); +} + +//================================================================================================= + +const BRepGraphInc::CoEdgeDef* BRepGraph_Tool::Edge::FindPCurve(const BRepGraph& theGraph, + const BRepGraph_EdgeId theEdge, + const BRepGraph_FaceId theFace) +{ + return theGraph.Topo().Edges().FindPCurve(theEdge, theFace); +} + +//================================================================================================= + +const BRepGraphInc::CoEdgeDef* BRepGraph_Tool::Edge::FindPCurve(const BRepGraph& theGraph, + const BRepGraph_EdgeId theEdge, + const BRepGraph_FaceId theFace, + const TopAbs_Orientation theOri) +{ + return theGraph.Topo().Edges().FindPCurve(theEdge, theFace, theOri); +} + +//================================================================================================= + +static const occ::handle THE_NULL_POLYGON2D; + +const occ::handle& BRepGraph_Tool::CoEdge::PolygonOnSurface( + const BRepGraph& theGraph, + const BRepGraph_CoEdgeId theCoEdge) +{ + const BRepGraphInc::CoEdgeDef& aCoEdge = theGraph.Topo().CoEdges().Definition(theCoEdge); + if (!aCoEdge.Polygon2DRepId.IsValid()) + return THE_NULL_POLYGON2D; + const BRepGraphInc::Polygon2DRep& aPolygonRep = + theGraph.Topo().Poly().Polygon2DRep(aCoEdge.Polygon2DRepId); + if (aPolygonRep.IsRemoved) + return THE_NULL_POLYGON2D; + return aPolygonRep.Polygon; +} + +//================================================================================================= + +bool BRepGraph_Tool::CoEdge::HasPolygonOnSurface(const BRepGraph& theGraph, + const BRepGraph_CoEdgeId theCoEdge) +{ + const BRepGraphInc::CoEdgeDef& aCoEdge = theGraph.Topo().CoEdges().Definition(theCoEdge); + return aCoEdge.Polygon2DRepId.IsValid() + && !theGraph.Topo().Poly().Polygon2DRep(aCoEdge.Polygon2DRepId).IsRemoved; +} + +//================================================================================================= + +double BRepGraph_Tool::Face::Tolerance(const BRepGraph& theGraph, const BRepGraph_FaceId theFace) +{ + return theGraph.Topo().Faces().Definition(theFace).Tolerance; +} + +//================================================================================================= + +bool BRepGraph_Tool::Face::NaturalRestriction(const BRepGraph& theGraph, + const BRepGraph_FaceId theFace) +{ + return theGraph.Topo().Faces().Definition(theFace).NaturalRestriction; +} + +//================================================================================================= + +bool BRepGraph_Tool::Face::HasSurface(const BRepGraph& theGraph, const BRepGraph_FaceId theFace) +{ + return theGraph.Topo().Faces().SurfaceRepId(theFace).IsValid(); +} + +//================================================================================================= + +bool BRepGraph_Tool::Face::HasTriangulation(const BRepGraph& theGraph, + const BRepGraph_FaceId theFace) +{ + return theGraph.Topo().Faces().ActiveTriangulationRepId(theFace).IsValid(); +} + +//================================================================================================= + +const BRepGraphInc::WireRef* BRepGraph_Tool::Face::OuterWire(const BRepGraph& theGraph, + const BRepGraph_FaceId theFace) +{ + const BRepGraph::RefsView& aRefs = theGraph.Refs(); + for (BRepGraph_RefsWireOfFace aWireIt(theGraph, theFace); aWireIt.More(); aWireIt.Next()) + { + const BRepGraphInc::WireRef& aRef = aRefs.Wires().Entry(aWireIt.CurrentId()); + if (!aRef.IsRemoved && aRef.IsOuter) + return &aRef; + } + return nullptr; +} + +//================================================================================================= + +static const occ::handle THE_NULL_SURFACE; + +const occ::handle& BRepGraph_Tool::Face::Surface(const BRepGraph& theGraph, + const BRepGraph_FaceId theFace) +{ + const BRepGraph_SurfaceRepId aRepId = theGraph.Topo().Faces().SurfaceRepId(theFace); + if (!aRepId.IsValid()) + return THE_NULL_SURFACE; + return theGraph.Topo().Geometry().SurfaceRep(aRepId).Surface; +} + +//================================================================================================= + +GeomAdaptor_TransformedSurface BRepGraph_Tool::Face::SurfaceAdaptor(const BRepGraph& theGraph, + const BRepGraph_FaceId theFace) +{ + const BRepGraph_SurfaceRepId aRepId = theGraph.Topo().Faces().SurfaceRepId(theFace); + if (!aRepId.IsValid()) + return GeomAdaptor_TransformedSurface(); + const occ::handle& aSurf = theGraph.Topo().Geometry().SurfaceRep(aRepId).Surface; + if (aSurf.IsNull()) + return GeomAdaptor_TransformedSurface(); + return GeomAdaptor_TransformedSurface(aSurf, gp_Trsf()); +} + +//================================================================================================= + +GeomAdaptor_TransformedSurface BRepGraph_Tool::Face::SurfaceAdaptor(const BRepGraph& theGraph, + const BRepGraph_FaceId theFace, + const double theUFirst, + const double theULast, + const double theVFirst, + const double theVLast) +{ + const BRepGraph_SurfaceRepId aRepId = theGraph.Topo().Faces().SurfaceRepId(theFace); + if (!aRepId.IsValid()) + return GeomAdaptor_TransformedSurface(); + const occ::handle& aSurf = theGraph.Topo().Geometry().SurfaceRep(aRepId).Surface; + if (aSurf.IsNull()) + return GeomAdaptor_TransformedSurface(); + return GeomAdaptor_TransformedSurface(aSurf, theUFirst, theULast, theVFirst, theVLast, gp_Trsf()); +} + +//================================================================================================= + +static const occ::handle THE_NULL_TRIANGULATION; + +const occ::handle& BRepGraph_Tool::Face::Triangulation( + const BRepGraph& theGraph, + const BRepGraph_FaceId theFace) +{ + const BRepGraph_TriangulationRepId aTriRepId = + theGraph.Topo().Faces().ActiveTriangulationRepId(theFace); + if (!aTriRepId.IsValid()) + return THE_NULL_TRIANGULATION; + return theGraph.Topo().Poly().TriangulationRep(aTriRepId).Triangulation; +} + +//================================================================================================= + +occ::handle BRepGraph_Tool::Edge::CurveOnSurface( + const BRepGraph& theGraph, + const BRepGraphInc::CoEdgeUsage& theRef, + const BRepGraph_FaceId theFace) +{ + const BRepGraphInc::CoEdgeDef& aCoEdge = theGraph.Topo().CoEdges().Definition(theRef.DefId); + const BRepGraphInc::FaceDef& aFace = theGraph.Topo().Faces().Definition(theFace); + + if (!aCoEdge.Curve2DRepId.IsValid() || !aFace.SurfaceRepId.IsValid()) + return occ::handle(); + + const BRepGraphInc::Curve2DRep& aPCurveRep = + theGraph.Topo().Geometry().Curve2DRep(aCoEdge.Curve2DRepId); + const BRepGraphInc::SurfaceRep& aSurfaceRep = + theGraph.Topo().Geometry().SurfaceRep(aFace.SurfaceRepId); + if (aPCurveRep.IsRemoved || aSurfaceRep.IsRemoved) + return occ::handle(); + const occ::handle& aPCurve = aPCurveRep.Curve; + const occ::handle& aSurf = aSurfaceRep.Surface; + + if (aPCurve.IsNull() || aSurf.IsNull()) + return occ::handle(); + + occ::handle aHC2d = + new Geom2dAdaptor_Curve(aPCurve, aCoEdge.ParamFirst, aCoEdge.ParamLast); + occ::handle aHS = new GeomAdaptor_Surface(aSurf); + return new Adaptor3d_CurveOnSurface(aHC2d, aHS); +} + +//================================================================================================= + +bool BRepGraph_Tool::Wire::IsClosed(const BRepGraph& theGraph, const BRepGraph_WireId theWire) +{ + return theGraph.Topo().Wires().Definition(theWire).IsClosed; +} + +//================================================================================================= + +int BRepGraph_Tool::Wire::NbCoEdges(const BRepGraph& theGraph, const BRepGraph_WireId theWire) +{ + return theGraph.Topo().Wires().Definition(theWire).CoEdgeRefIds.Length(); +} diff --git a/src/ModelingData/TKBRep/BRepGraph/BRepGraph_Tool.hxx b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_Tool.hxx new file mode 100644 index 0000000000..e0052dd931 --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_Tool.hxx @@ -0,0 +1,514 @@ +// Copyright (c) 2026 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 _BRepGraph_Tool_HeaderFile +#define _BRepGraph_Tool_HeaderFile + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +class Adaptor3d_CurveOnSurface; + +//! Centralized geometry access for BRepGraph - analogue of BRep_Tool. +//! +//! Geometry in BRepGraph is stored in the definition frame (representation +//! Location baked via applyRepresentationLocation). Instance Locations live +//! on topology Usage/Ref structs (VertexUsage, CoEdgeUsage, WireUsage, +//! FaceUsage, ShellUsage, SolidUsage, OccurrenceUsage). This class applies +//! ref Locations automatically when accessing 3D geometry. +//! Usage structs are lightweight read-only projections produced during +//! traversal, while Ref structs are stored reference entries from RefsView; +//! this API accepts whichever form naturally carries the required context for +//! the queried property. +//! +//! Methods are grouped by topology kind via nested classes: +//! BRepGraph_Tool::Vertex, Edge, CoEdge, Face, Wire. +class BRepGraph_Tool +{ +public: + using VertexUsage = BRepGraphInc::VertexUsage; + using CoEdgeUsage = BRepGraphInc::CoEdgeUsage; + using VertexRef = BRepGraphInc::VertexRef; + using WireRef = BRepGraphInc::WireRef; + using CoEdgeDef = BRepGraphInc::CoEdgeDef; + + //! @brief Vertex geometry accessors. + //! + //! Provides 3D point retrieval (with or without Location applied), + //! tolerance access, and parameter lookup for vertex-on-curve and + //! vertex-on-surface representations. + class Vertex + { + public: + //! Returns the vertex 3D point with VertexUsage Location applied. + //! @param[in] theGraph source graph + //! @param[in] theRef vertex incidence reference carrying Location + //! @return transformed 3D point + [[nodiscard]] Standard_EXPORT static gp_Pnt Pnt(const BRepGraph& theGraph, + const VertexUsage& theRef); + + //! Returns the vertex 3D point in definition frame (no Location applied). + //! @param[in] theGraph source graph + //! @param[in] theVertex typed vertex definition identifier + //! @return 3D point in definition frame + [[nodiscard]] Standard_EXPORT static gp_Pnt Pnt(const BRepGraph& theGraph, + const BRepGraph_VertexId theVertex); + + //! Returns the vertex tolerance. + //! @param[in] theGraph source graph + //! @param[in] theVertex typed vertex definition identifier + //! @return tolerance value + [[nodiscard]] Standard_EXPORT static double Tolerance(const BRepGraph& theGraph, + const BRepGraph_VertexId theVertex); + + //! Returns the vertex parameter on an edge's 3D curve. + //! @param[in] theGraph source graph + //! @param[in] theVertex typed vertex definition identifier + //! @param[in] theEdge typed edge definition identifier + //! @return curve parameter + //! @throws Standard_NoSuchObject if vertex has no PointOnCurve for this edge + [[nodiscard]] Standard_EXPORT static double Parameter(const BRepGraph& theGraph, + const BRepGraph_VertexId theVertex, + const BRepGraph_EdgeId theEdge); + + //! Returns the vertex (U,V) parameters on a face surface. + //! @param[in] theGraph source graph + //! @param[in] theVertex typed vertex definition identifier + //! @param[in] theFace typed face definition identifier + //! @return 2D point with (U,V) parameters + //! @throws Standard_NoSuchObject if vertex has no PointOnSurface for this face + [[nodiscard]] Standard_EXPORT static gp_Pnt2d Parameters(const BRepGraph& theGraph, + const BRepGraph_VertexId theVertex, + const BRepGraph_FaceId theFace); + + //! Returns the vertex parameter on a coedge's PCurve. + //! @param[in] theGraph source graph + //! @param[in] theVertex typed vertex definition identifier + //! @param[in] theCoEdge typed coedge definition identifier + //! @return PCurve parameter + //! @throws Standard_NoSuchObject if vertex has no PointOnPCurve for this coedge + [[nodiscard]] Standard_EXPORT static double PCurveParameter(const BRepGraph& theGraph, + const BRepGraph_VertexId theVertex, + const BRepGraph_CoEdgeId theCoEdge); + }; + + //! @brief Edge geometry, curve, polygon, and continuity accessors. + //! + //! Provides tolerance, degeneracy, and parameter flags; raw and + //! location-adjusted 3D curve access; polygon discretization; + //! continuity queries between adjacent faces; and PCurve lookup + //! for edge-face contexts including seam edge support. + class Edge + { + public: + //! @name Properties + + //! Returns the edge tolerance. + //! @param[in] theGraph source graph + //! @param[in] theEdge typed edge definition identifier + //! @return tolerance value + [[nodiscard]] Standard_EXPORT static double Tolerance(const BRepGraph& theGraph, + const BRepGraph_EdgeId theEdge); + + //! Returns true if the edge is degenerate (collapses to a point on surface). + //! @param[in] theGraph source graph + //! @param[in] theEdge typed edge definition identifier + //! @return true if degenerate + [[nodiscard]] Standard_EXPORT static bool Degenerated(const BRepGraph& theGraph, + const BRepGraph_EdgeId theEdge); + + //! Returns the SameParameter flag. + //! @param[in] theGraph source graph + //! @param[in] theEdge typed edge definition identifier + //! @return true if all PCurves are reparametrized to the same range as the 3D curve + [[nodiscard]] Standard_EXPORT static bool SameParameter(const BRepGraph& theGraph, + const BRepGraph_EdgeId theEdge); + + //! Returns the SameRange flag. + //! @param[in] theGraph source graph + //! @param[in] theEdge typed edge definition identifier + //! @return true if PCurve parameter range equals the 3D curve range + [[nodiscard]] Standard_EXPORT static bool SameRange(const BRepGraph& theGraph, + const BRepGraph_EdgeId theEdge); + + //! Returns the 3D curve parameter range as (first, last). + //! @param[in] theGraph source graph + //! @param[in] theEdge typed edge definition identifier + //! @return pair of (first, last) parameters + [[nodiscard]] Standard_EXPORT static std::pair Range( + const BRepGraph& theGraph, + const BRepGraph_EdgeId theEdge); + + //! Returns the start vertex reference entry (carries Location and Orientation). + //! @param[in] theGraph source graph + //! @param[in] theEdge typed edge definition identifier + //! @return const reference to the start VertexRef + [[nodiscard]] Standard_EXPORT static const VertexRef& StartVertex( + const BRepGraph& theGraph, + const BRepGraph_EdgeId theEdge); + + //! Returns the end vertex reference entry (carries Location and Orientation). + //! @param[in] theGraph source graph + //! @param[in] theEdge typed edge definition identifier + //! @return const reference to the end VertexRef + [[nodiscard]] Standard_EXPORT static const VertexRef& EndVertex(const BRepGraph& theGraph, + const BRepGraph_EdgeId theEdge); + + //! @name 3D Curve + + //! Returns true if the edge has a 3D curve representation. + //! @param[in] theGraph source graph + //! @param[in] theEdge typed edge definition identifier + //! @return true if edge has a 3D curve + [[nodiscard]] Standard_EXPORT static bool HasCurve(const BRepGraph& theGraph, + const BRepGraph_EdgeId theEdge); + + //! Returns the raw 3D curve handle (definition frame, no copy). + //! @param[in] theGraph source graph + //! @param[in] theEdge typed edge definition identifier + //! @return curve handle, or null handle if no curve + [[nodiscard]] Standard_EXPORT static const occ::handle& Curve( + const BRepGraph& theGraph, + const BRepGraph_EdgeId theEdge); + + //! Returns the transformed 3D curve handle via CoEdgeUsage (applies Location, may copy). + //! @param[in] theGraph source graph + //! @param[in] theRef coedge incidence reference carrying Location + //! @return transformed curve handle + [[nodiscard]] Standard_EXPORT static occ::handle Curve(const BRepGraph& theGraph, + const CoEdgeUsage& theRef); + + //! Returns the 3D curve adaptor in definition frame (identity Trsf). + //! @param[in] theGraph source graph + //! @param[in] theEdge typed edge definition identifier + //! @return curve adaptor, or empty adaptor if no curve + [[nodiscard]] Standard_EXPORT static GeomAdaptor_TransformedCurve CurveAdaptor( + const BRepGraph& theGraph, + const BRepGraph_EdgeId theEdge); + + //! Returns the 3D curve adaptor via CoEdgeUsage (applies edge-in-wire Location in Trsf). + //! Falls back to CurveOnSurface when no 3D curve exists. + //! @param[in] theGraph source graph + //! @param[in] theRef coedge incidence reference carrying Location + //! @return curve adaptor with Location applied + [[nodiscard]] Standard_EXPORT static GeomAdaptor_TransformedCurve CurveAdaptor( + const BRepGraph& theGraph, + const CoEdgeUsage& theRef); + + //! @name 3D Polygon + + //! Returns true if the edge has a 3D polygon discretization. + //! @param[in] theGraph source graph + //! @param[in] theEdge typed edge definition identifier + //! @return true if edge has a polygon + [[nodiscard]] Standard_EXPORT static bool HasPolygon3D(const BRepGraph& theGraph, + const BRepGraph_EdgeId theEdge); + + //! Returns the 3D polygon handle (definition frame). + //! @param[in] theGraph source graph + //! @param[in] theEdge typed edge definition identifier + //! @return polygon handle, or null handle if no polygon + [[nodiscard]] Standard_EXPORT static const occ::handle& Polygon3D( + const BRepGraph& theGraph, + const BRepGraph_EdgeId theEdge); + + //! @name Continuity + + //! Returns true if the edge has continuity info between two faces. + //! @param[in] theGraph source graph + //! @param[in] theEdge typed edge definition identifier + //! @param[in] theFace1 typed first face definition identifier + //! @param[in] theFace2 typed second face definition identifier + //! @return true if continuity is recorded + [[nodiscard]] Standard_EXPORT static bool HasContinuity(const BRepGraph& theGraph, + const BRepGraph_EdgeId theEdge, + const BRepGraph_FaceId theFace1, + const BRepGraph_FaceId theFace2); + + //! Returns the geometric continuity between two adjacent faces. + //! @param[in] theGraph source graph + //! @param[in] theEdge typed edge definition identifier + //! @param[in] theFace1 typed first face definition identifier + //! @param[in] theFace2 typed second face definition identifier + //! @return continuity order (GeomAbs_C0 if not found) + [[nodiscard]] Standard_EXPORT static GeomAbs_Shape Continuity(const BRepGraph& theGraph, + const BRepGraph_EdgeId theEdge, + const BRepGraph_FaceId theFace1, + const BRepGraph_FaceId theFace2); + + //! Returns the maximum continuity across all face pairs for this edge. + //! @param[in] theGraph source graph + //! @param[in] theEdge typed edge definition identifier + //! @return maximum continuity order + [[nodiscard]] Standard_EXPORT static GeomAbs_Shape MaxContinuity( + const BRepGraph& theGraph, + const BRepGraph_EdgeId theEdge); + + //! @name PCurve lookup (edge-face context) + + //! Returns true if the edge has two PCurves on a face (seam/closed surface). + //! @param[in] theGraph source graph + //! @param[in] theEdge typed edge definition identifier + //! @param[in] theFace typed face definition identifier + //! @return true if the edge is a seam on this face + [[nodiscard]] Standard_EXPORT static bool IsClosedOnFace(const BRepGraph& theGraph, + const BRepGraph_EdgeId theEdge, + const BRepGraph_FaceId theFace); + + //! Finds the CoEdge entity for an edge on a face. + //! @param[in] theGraph source graph + //! @param[in] theEdge typed edge definition identifier + //! @param[in] theFace typed face definition identifier + //! @return pointer to CoEdgeDef, or nullptr if not found + [[nodiscard]] Standard_EXPORT static const CoEdgeDef* FindPCurve( + const BRepGraph& theGraph, + const BRepGraph_EdgeId theEdge, + const BRepGraph_FaceId theFace); + + //! Finds the CoEdge entity with specific orientation (for seam edges). + //! @param[in] theGraph source graph + //! @param[in] theEdge typed edge definition identifier + //! @param[in] theFace typed face definition identifier + //! @param[in] theOri edge orientation on the face + //! @return pointer to CoEdgeDef, or nullptr if not found + [[nodiscard]] Standard_EXPORT static const CoEdgeDef* FindPCurve( + const BRepGraph& theGraph, + const BRepGraph_EdgeId theEdge, + const BRepGraph_FaceId theFace, + const TopAbs_Orientation theOri); + + //! @name CurveOnSurface + + //! Returns a CurveOnSurface adaptor built from a CoEdgeUsage and face. + //! @param[in] theGraph source graph + //! @param[in] theRef coedge incidence reference + //! @param[in] theFace typed face definition identifier + //! @return adaptor handle, or null if PCurve or surface is missing + [[nodiscard]] Standard_EXPORT static occ::handle CurveOnSurface( + const BRepGraph& theGraph, + const CoEdgeUsage& theRef, + const BRepGraph_FaceId theFace); + }; + + //! @brief CoEdge (half-edge) parametric curve and polygon accessors. + //! + //! Provides PCurve retrieval, adaptor construction, UV endpoint + //! access, parameter range queries, and polygon-on-surface access + //! for coedge definitions. + class CoEdge + { + public: + //! Returns true if the coedge has a PCurve representation. + //! @param[in] theGraph source graph + //! @param[in] theCoEdge typed coedge definition identifier + //! @return true if PCurve exists + [[nodiscard]] Standard_EXPORT static bool HasPCurve(const BRepGraph& theGraph, + const BRepGraph_CoEdgeId theCoEdge); + + //! Returns the raw PCurve handle by coedge identifier (no Location - UV space). + //! @param[in] theGraph source graph + //! @param[in] theCoEdge typed coedge definition identifier + //! @return curve handle, or null handle if no PCurve + [[nodiscard]] Standard_EXPORT static const occ::handle& PCurve( + const BRepGraph& theGraph, + const BRepGraph_CoEdgeId theCoEdge); + + //! Returns the raw PCurve handle from a CoEdgeDef (no Location - UV space). + //! @param[in] theGraph source graph + //! @param[in] theCoEdge coedge entity reference + //! @return curve handle, or null handle if no PCurve + [[nodiscard]] Standard_EXPORT static const occ::handle& PCurve( + const BRepGraph& theGraph, + const CoEdgeDef& theCoEdge); + + //! Returns a PCurve adaptor by coedge identifier. + //! If the coedge has a stored PCurve (Curve2DRepIdx >= 0), returns it directly. + //! Otherwise, for planar face surfaces, computes the PCurve on-the-fly by projecting + //! the edge's 3D curve onto the plane (CurveOnPlane), mirroring the behavior of + //! BRep_Tool::CurveOnSurface for planar faces without stored PCurves. + //! @param[in] theGraph source graph + //! @param[in] theCoEdge typed coedge definition identifier + //! @return 2D curve adaptor, or empty adaptor if no PCurve and surface is not planar + [[nodiscard]] Standard_EXPORT static Geom2dAdaptor_Curve PCurveAdaptor( + const BRepGraph& theGraph, + const BRepGraph_CoEdgeId theCoEdge); + + //! Returns a PCurve adaptor from a CoEdgeUsage. + //! @param[in] theGraph source graph + //! @param[in] theRef coedge incidence reference + //! @return 2D curve adaptor + [[nodiscard]] Standard_EXPORT static Geom2dAdaptor_Curve PCurveAdaptor( + const BRepGraph& theGraph, + const CoEdgeUsage& theRef); + + //! Returns the UV endpoints from a CoEdge as (UV1, UV2). + //! @param[in] theGraph source graph + //! @param[in] theCoEdge typed coedge definition identifier + //! @return pair of 2D points at parameter first and last + [[nodiscard]] Standard_EXPORT static std::pair UVPoints( + const BRepGraph& theGraph, + const BRepGraph_CoEdgeId theCoEdge); + + //! Returns the PCurve parameter range as (first, last). + //! @param[in] theGraph source graph + //! @param[in] theCoEdge typed coedge definition identifier + //! @return pair of (first, last) parameters + [[nodiscard]] Standard_EXPORT static std::pair Range( + const BRepGraph& theGraph, + const BRepGraph_CoEdgeId theCoEdge); + + //! Returns true if the coedge has a polygon-on-surface representation. + //! @param[in] theGraph source graph + //! @param[in] theCoEdge typed coedge definition identifier + //! @return true if polygon exists + [[nodiscard]] Standard_EXPORT static bool HasPolygonOnSurface( + const BRepGraph& theGraph, + const BRepGraph_CoEdgeId theCoEdge); + + //! Returns the polygon-on-surface (2D) for the coedge. + //! @param[in] theGraph source graph + //! @param[in] theCoEdge typed coedge definition identifier + //! @return polygon handle, or null handle if no polygon + [[nodiscard]] Standard_EXPORT static const occ::handle& PolygonOnSurface( + const BRepGraph& theGraph, + const BRepGraph_CoEdgeId theCoEdge); + }; + + //! @brief Face surface, triangulation, and property accessors. + //! + //! Provides tolerance, natural restriction flag, surface handle + //! and adaptor access (with optional UV bounds), active triangulation + //! retrieval, and outer wire lookup. + class Face + { + public: + //! Returns the face tolerance. + //! @param[in] theGraph source graph + //! @param[in] theFace typed face definition identifier + //! @return tolerance value + [[nodiscard]] Standard_EXPORT static double Tolerance(const BRepGraph& theGraph, + const BRepGraph_FaceId theFace); + + //! Returns the NaturalRestriction flag. + //! @param[in] theGraph source graph + //! @param[in] theFace typed face definition identifier + //! @return true if face has natural restriction + [[nodiscard]] Standard_EXPORT static bool NaturalRestriction(const BRepGraph& theGraph, + const BRepGraph_FaceId theFace); + + //! Returns true if the face has a surface representation. + //! @param[in] theGraph source graph + //! @param[in] theFace typed face definition identifier + //! @return true if surface exists + [[nodiscard]] Standard_EXPORT static bool HasSurface(const BRepGraph& theGraph, + const BRepGraph_FaceId theFace); + + //! Returns true if the face has an active triangulation. + //! @param[in] theGraph source graph + //! @param[in] theFace typed face definition identifier + //! @return true if triangulation exists + [[nodiscard]] Standard_EXPORT static bool HasTriangulation(const BRepGraph& theGraph, + const BRepGraph_FaceId theFace); + + //! Returns the outer wire reference, or nullptr if none. + //! @param[in] theGraph source graph + //! @param[in] theFace typed face definition identifier + //! @return pointer to the outer WireRef, or nullptr + [[nodiscard]] Standard_EXPORT static const WireRef* OuterWire(const BRepGraph& theGraph, + const BRepGraph_FaceId theFace); + + //! Returns the raw surface handle (definition frame, no copy). + //! @param[in] theGraph source graph + //! @param[in] theFace typed face definition identifier + //! @return surface handle, or null handle if no surface + [[nodiscard]] Standard_EXPORT static const occ::handle& Surface( + const BRepGraph& theGraph, + const BRepGraph_FaceId theFace); + + //! Returns a surface adaptor in definition frame. + //! @param[in] theGraph source graph + //! @param[in] theFace typed face definition identifier + //! @return surface adaptor, or empty adaptor if no surface + [[nodiscard]] Standard_EXPORT static GeomAdaptor_TransformedSurface SurfaceAdaptor( + const BRepGraph& theGraph, + const BRepGraph_FaceId theFace); + + //! Returns a surface adaptor with explicit UV bounds. + //! @param[in] theGraph source graph + //! @param[in] theFace typed face definition identifier + //! @param[in] theUFirst first U parameter + //! @param[in] theULast last U parameter + //! @param[in] theVFirst first V parameter + //! @param[in] theVLast last V parameter + //! @return surface adaptor with bounds, or empty adaptor if no surface + [[nodiscard]] Standard_EXPORT static GeomAdaptor_TransformedSurface SurfaceAdaptor( + const BRepGraph& theGraph, + const BRepGraph_FaceId theFace, + const double theUFirst, + const double theULast, + const double theVFirst, + const double theVLast); + + //! Returns the active triangulation for the face (definition frame). + //! @param[in] theGraph source graph + //! @param[in] theFace typed face definition identifier + //! @return triangulation handle, or null handle if none + [[nodiscard]] Standard_EXPORT static const occ::handle& Triangulation( + const BRepGraph& theGraph, + const BRepGraph_FaceId theFace); + }; + + //! @brief Wire property accessors. + //! + //! Provides wire closure and size queries. + //! For ordered edge traversal, use BRepGraphInc_WireExplorer or access + //! the WireDef::CoEdgeRefIds vector directly via TopoView. + class Wire + { + public: + //! Returns true if the wire is topologically closed. + //! @param[in] theGraph source graph + //! @param[in] theWire typed wire definition identifier + //! @return true if closed + [[nodiscard]] Standard_EXPORT static bool IsClosed(const BRepGraph& theGraph, + const BRepGraph_WireId theWire); + + //! Number of CoEdge references in the wire (i.e., edge count including orientation). + //! @param[in] theGraph source graph + //! @param[in] theWire typed wire definition identifier + //! @return number of coedge entries + [[nodiscard]] Standard_EXPORT static int NbCoEdges(const BRepGraph& theGraph, + const BRepGraph_WireId theWire); + }; + +private: + BRepGraph_Tool() = delete; +}; + +#endif // _BRepGraph_Tool_HeaderFile diff --git a/src/ModelingData/TKBRep/BRepGraph/BRepGraph_TopoView.cxx b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_TopoView.cxx new file mode 100644 index 0000000000..dc4dc0b945 --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_TopoView.cxx @@ -0,0 +1,1318 @@ +// Copyright (c) 2026 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 + +namespace +{ + +constexpr int THE_TOPOVIEW_FACE_ADJACENCY_BLOCK_SIZE = 8; +constexpr int THE_TOPOVIEW_FACE_EDGE_BLOCK_SIZE = 8; +constexpr int THE_TOPOVIEW_EDGE_VERTEX_BLOCK_SIZE = 4; +constexpr int THE_TOPOVIEW_EDGE_ADJACENCY_BLOCK_SIZE = 8; +constexpr int THE_TOPOVIEW_SAME_DOMAIN_BLOCK_SIZE = 8; +constexpr int THE_TOPOVIEW_SHARED_EDGE_BLOCK_SIZE = 4; + +//! Collect unique edge IDs reachable from a face through its wire/coedge refs. +NCollection_Vector collectFaceEdges( + const BRepGraph& theGraph, + const BRepGraph_FaceId theFace, + const occ::handle& theAllocator) +{ + NCollection_Vector aResult(THE_TOPOVIEW_FACE_EDGE_BLOCK_SIZE, theAllocator); + if (!theFace.IsValid(theGraph.Topo().Faces().Nb())) + return aResult; + + NCollection_PackedMap anEdgeSet; + for (BRepGraph_DefsWireOfFace aWireIt(theGraph, theFace); aWireIt.More(); aWireIt.Next()) + { + for (BRepGraph_DefsEdgeOfWire anEdgeIt(theGraph, aWireIt.CurrentId()); anEdgeIt.More(); + anEdgeIt.Next()) + { + const BRepGraph_EdgeId anEdgeId = anEdgeIt.CurrentId(); + if (anEdgeSet.Add(anEdgeId.Index)) + { + aResult.Append(anEdgeId); + } + } + } + return aResult; +} + +//================================================================================================= + +template +const NCollection_Vector& emptyVector() +{ + static const NCollection_Vector THE_EMPTY_VECTOR; + return THE_EMPTY_VECTOR; +} + +} // namespace + +// ========================================================================== +// Tool-like grouped helpers. +// ========================================================================== + +//================================================================================================= + +int BRepGraph::TopoView::FaceOps::Nb() const +{ + return myGraph->myData->myIncStorage.NbFaces(); +} + +//================================================================================================= + +int BRepGraph::TopoView::FaceOps::NbActive() const +{ + return myGraph->myData->myIncStorage.NbActiveFaces(); +} + +//================================================================================================= + +const BRepGraphInc::FaceDef& BRepGraph::TopoView::FaceOps::Definition( + const BRepGraph_FaceId theFace) const +{ + return myGraph->myData->myIncStorage.Face(theFace); +} + +//================================================================================================= + +const NCollection_Vector& BRepGraph::TopoView::FaceOps::Shells( + const BRepGraph_FaceId theFace) const +{ + return myGraph->myData->myIncStorage.ReverseIndex().ShellsOfFaceRef(theFace); +} + +//================================================================================================= + +const NCollection_Vector& BRepGraph::TopoView::FaceOps::Compounds( + const BRepGraph_FaceId theFace) const +{ + const NCollection_Vector* aCompounds = + myGraph->myData->myIncStorage.ReverseIndex().CompoundsOfFace(theFace); + return aCompounds != nullptr ? *aCompounds : emptyVector(); +} + +//================================================================================================= + +BRepGraph_SurfaceRepId BRepGraph::TopoView::FaceOps::SurfaceRepId( + const BRepGraph_FaceId theFace) const +{ + const BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + if (!theFace.IsValid(aStorage.NbFaces())) + { + return BRepGraph_SurfaceRepId(); + } + + const BRepGraph_SurfaceRepId aRepId = aStorage.Face(theFace).SurfaceRepId; + if (!aRepId.IsValid(aStorage.NbSurfaces()) || aStorage.SurfaceRep(aRepId).IsRemoved) + { + return BRepGraph_SurfaceRepId(); + } + return aRepId; +} + +//================================================================================================= + +BRepGraph_TriangulationRepId BRepGraph::TopoView::FaceOps::ActiveTriangulationRepId( + const BRepGraph_FaceId theFace) const +{ + const BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + if (!theFace.IsValid(aStorage.NbFaces())) + { + return BRepGraph_TriangulationRepId(); + } + + const BRepGraph_TriangulationRepId aRepId = aStorage.Face(theFace).ActiveTriangulationRepId(); + if (!aRepId.IsValid(aStorage.NbTriangulations()) || aStorage.TriangulationRep(aRepId).IsRemoved) + { + return BRepGraph_TriangulationRepId(); + } + return aRepId; +} + +//================================================================================================= + +NCollection_Vector BRepGraph::TopoView::FaceOps::SameDomain( + const BRepGraph_FaceId theFace, + const occ::handle& theAllocator) const +{ + NCollection_Vector aResult(THE_TOPOVIEW_SAME_DOMAIN_BLOCK_SIZE, theAllocator); + const BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + if (!theFace.IsValid(aStorage.NbFaces())) + { + return aResult; + } + + const BRepGraphInc::FaceDef& aFaceDef = aStorage.Face(theFace); + if (!aFaceDef.SurfaceRepId.IsValid()) + { + return aResult; + } + + for (BRepGraph_FaceIterator aFaceIt(*myGraph); aFaceIt.More(); aFaceIt.Next()) + { + const BRepGraph_FaceId anOtherFaceId = aFaceIt.CurrentId(); + const BRepGraphInc::FaceDef& anOtherFace = aFaceIt.Current(); + if (anOtherFaceId != theFace && anOtherFace.SurfaceRepId == aFaceDef.SurfaceRepId) + { + aResult.Append(anOtherFaceId); + } + } + return aResult; +} + +//================================================================================================= + +NCollection_Vector BRepGraph::TopoView::FaceOps::SharedEdges( + const BRepGraph_FaceId theFaceA, + const BRepGraph_FaceId theFaceB, + const occ::handle& theAllocator) const +{ + NCollection_Vector aResult(THE_TOPOVIEW_SHARED_EDGE_BLOCK_SIZE, theAllocator); + const BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + if (!theFaceA.IsValid(aStorage.NbFaces()) || !theFaceB.IsValid(aStorage.NbFaces())) + { + return aResult; + } + + const NCollection_Vector aFaceAEdges = + collectFaceEdges(*myGraph, theFaceA, theAllocator); + const NCollection_Vector aFaceBEdges = + collectFaceEdges(*myGraph, theFaceB, theAllocator); + NCollection_PackedMap aFaceAEdgeSet; + NCollection_PackedMap anAddedEdges; + + for (const BRepGraph_EdgeId& anEdgeId : aFaceAEdges) + { + aFaceAEdgeSet.Add(anEdgeId.Index); + } + + for (const BRepGraph_EdgeId& anEdgeId : aFaceBEdges) + { + if (aFaceAEdgeSet.Contains(anEdgeId.Index) && anAddedEdges.Add(anEdgeId.Index)) + { + aResult.Append(anEdgeId); + } + } + return aResult; +} + +//================================================================================================= + +NCollection_Vector BRepGraph::TopoView::FaceOps::Adjacent( + const BRepGraph_FaceId theFace, + const occ::handle& theAllocator) const +{ + NCollection_Vector aResult(THE_TOPOVIEW_FACE_ADJACENCY_BLOCK_SIZE, + theAllocator); + NCollection_PackedMap aFaceSet; + const BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + if (!theFace.IsValid(aStorage.NbFaces())) + { + return aResult; + } + + const NCollection_Vector anEdges = + collectFaceEdges(*myGraph, theFace, theAllocator); + const BRepGraphInc_ReverseIndex& aRevIdx = aStorage.ReverseIndex(); + for (const BRepGraph_EdgeId& anEdgeId : anEdges) + { + const NCollection_Vector* aFaces = aRevIdx.FacesOfEdge(anEdgeId); + if (aFaces == nullptr) + { + continue; + } + + for (const BRepGraph_FaceId& anAdjacentFaceId : *aFaces) + { + if (anAdjacentFaceId == theFace) + { + continue; + } + + const BRepGraphInc::FaceDef& anAdjacentFace = aStorage.Face(anAdjacentFaceId); + if (anAdjacentFace.IsRemoved) + { + continue; + } + + if (aFaceSet.Add(anAdjacentFaceId.Index)) + { + aResult.Append(anAdjacentFaceId); + } + } + } + return aResult; +} + +//================================================================================================= + +BRepGraph_WireId BRepGraph::TopoView::FaceOps::OuterWire(const BRepGraph_FaceId theFace) const +{ + const BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + if (!theFace.IsValid(aStorage.NbFaces())) + { + return BRepGraph_WireId(); + } + + for (BRepGraph_RefsWireOfFace aWireIt(*myGraph, theFace); aWireIt.More(); aWireIt.Next()) + { + const BRepGraphInc::WireRef& aWireRef = myGraph->Refs().Wires().Entry(aWireIt.CurrentId()); + if (!aWireRef.IsRemoved && aWireRef.IsOuter) + { + return aWireRef.WireDefId; + } + } + return BRepGraph_WireId(); +} + +//================================================================================================= + +int BRepGraph::TopoView::EdgeOps::Nb() const +{ + return myGraph->myData->myIncStorage.NbEdges(); +} + +//================================================================================================= + +int BRepGraph::TopoView::EdgeOps::NbActive() const +{ + return myGraph->myData->myIncStorage.NbActiveEdges(); +} + +//================================================================================================= + +const BRepGraphInc::EdgeDef& BRepGraph::TopoView::EdgeOps::Definition( + const BRepGraph_EdgeId theEdge) const +{ + return myGraph->myData->myIncStorage.Edge(theEdge); +} + +//================================================================================================= + +int BRepGraph::TopoView::EdgeOps::NbFaces(const BRepGraph_EdgeId theEdge) const +{ + return myGraph->myData->myIncStorage.ReverseIndex().NbFacesOfEdge(theEdge); +} + +//================================================================================================= + +const NCollection_Vector& BRepGraph::TopoView::EdgeOps::Wires( + const BRepGraph_EdgeId theEdge) const +{ + return myGraph->myData->myIncStorage.ReverseIndex().WiresOfEdgeRef(theEdge); +} + +//================================================================================================= + +const NCollection_Vector& BRepGraph::TopoView::EdgeOps::CoEdges( + const BRepGraph_EdgeId theEdge) const +{ + return myGraph->myData->myIncStorage.ReverseIndex().CoEdgesOfEdgeRef(theEdge); +} + +//================================================================================================= + +const NCollection_Vector& BRepGraph::TopoView::EdgeOps::Faces( + const BRepGraph_EdgeId theEdge) const +{ + return myGraph->myData->myIncStorage.ReverseIndex().FacesOfEdgeRef(theEdge); +} + +//================================================================================================= + +BRepGraph_Curve3DRepId BRepGraph::TopoView::EdgeOps::Curve3DRepId( + const BRepGraph_EdgeId theEdge) const +{ + const BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + if (!theEdge.IsValid(aStorage.NbEdges())) + { + return BRepGraph_Curve3DRepId(); + } + + const BRepGraph_Curve3DRepId aRepId = aStorage.Edge(theEdge).Curve3DRepId; + if (!aRepId.IsValid(aStorage.NbCurves3D()) || aStorage.Curve3DRep(aRepId).IsRemoved) + { + return BRepGraph_Curve3DRepId(); + } + return aRepId; +} + +//================================================================================================= + +NCollection_Vector BRepGraph::TopoView::EdgeOps::Adjacent( + const BRepGraph_EdgeId theEdge, + const occ::handle& theAllocator) const +{ + NCollection_Vector aResult(THE_TOPOVIEW_EDGE_ADJACENCY_BLOCK_SIZE, + theAllocator); + const BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + if (!theEdge.IsValid(aStorage.NbEdges())) + { + return aResult; + } + + NCollection_Vector aVertices(THE_TOPOVIEW_EDGE_VERTEX_BLOCK_SIZE, + theAllocator); + NCollection_PackedMap aSeenVertices; + for (BRepGraph_DefsVertexOfEdge aVertexIt(*myGraph, theEdge); aVertexIt.More(); aVertexIt.Next()) + { + const BRepGraph_VertexId aVertexId = aVertexIt.CurrentId(); + if (aSeenVertices.Add(aVertexId.Index)) + { + aVertices.Append(aVertexId); + } + } + + // Find adjacent edges via shared vertices. + const BRepGraphInc_ReverseIndex& aRevIdx = aStorage.ReverseIndex(); + NCollection_PackedMap anEdgeSet; + for (const BRepGraph_VertexId& aVertexId : aVertices) + { + const NCollection_Vector* anEdges = aRevIdx.EdgesOfVertex(aVertexId); + if (anEdges == nullptr) + { + continue; + } + + for (const BRepGraph_EdgeId& anAdjacentEdgeId : *anEdges) + { + if (anAdjacentEdgeId == theEdge) + { + continue; + } + + const BRepGraphInc::EdgeDef& anAdjacentEdge = aStorage.Edge(anAdjacentEdgeId); + if (anAdjacentEdge.IsRemoved) + { + continue; + } + + if (anEdgeSet.Add(anAdjacentEdgeId.Index)) + { + aResult.Append(anAdjacentEdgeId); + } + } + } + return aResult; +} + +//================================================================================================= + +bool BRepGraph::TopoView::EdgeOps::IsBoundary(const BRepGraph_EdgeId theEdge) const +{ + return NbFaces(theEdge) == 1; +} + +//================================================================================================= + +bool BRepGraph::TopoView::EdgeOps::IsManifold(const BRepGraph_EdgeId theEdge) const +{ + return NbFaces(theEdge) == 2; +} + +//================================================================================================= + +const BRepGraphInc::CoEdgeDef* BRepGraph::TopoView::EdgeOps::FindPCurve( + const BRepGraph_EdgeId theEdge, + const BRepGraph_FaceId theFace) const +{ + const BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + if (!theEdge.IsValid(aStorage.NbEdges()) || !theFace.IsValid(aStorage.NbFaces())) + { + return nullptr; + } + + const NCollection_Vector& aCoEdges = + aStorage.ReverseIndex().CoEdgesOfEdgeRef(theEdge); + for (const BRepGraph_CoEdgeId& aCoEdgeId : aCoEdges) + { + const BRepGraphInc::CoEdgeDef& aCoEdge = aStorage.CoEdge(aCoEdgeId); + if (aCoEdge.EdgeDefId == theEdge && aCoEdge.FaceDefId == theFace) + { + return &aCoEdge; + } + } + return nullptr; +} + +//================================================================================================= + +const BRepGraphInc::CoEdgeDef* BRepGraph::TopoView::EdgeOps::FindPCurve( + const BRepGraph_EdgeId theEdge, + const BRepGraph_FaceId theFace, + const TopAbs_Orientation theOrientation) const +{ + const BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + if (!theEdge.IsValid(aStorage.NbEdges()) || !theFace.IsValid(aStorage.NbFaces())) + { + return nullptr; + } + + const NCollection_Vector& aCoEdges = + aStorage.ReverseIndex().CoEdgesOfEdgeRef(theEdge); + const BRepGraphInc::CoEdgeDef* aFirstMatch = nullptr; + for (const BRepGraph_CoEdgeId& aCoEdgeId : aCoEdges) + { + const BRepGraphInc::CoEdgeDef& aCoEdge = aStorage.CoEdge(aCoEdgeId); + if (aCoEdge.EdgeDefId != theEdge || aCoEdge.FaceDefId != theFace) + { + continue; + } + if (aFirstMatch == nullptr) + { + aFirstMatch = &aCoEdge; + } + if (aCoEdge.Sense == theOrientation) + { + return &aCoEdge; + } + } + return aFirstMatch; +} + +//================================================================================================= + +int BRepGraph::TopoView::VertexOps::Nb() const +{ + return myGraph->myData->myIncStorage.NbVertices(); +} + +//================================================================================================= + +int BRepGraph::TopoView::VertexOps::NbActive() const +{ + return myGraph->myData->myIncStorage.NbActiveVertices(); +} + +//================================================================================================= + +const BRepGraphInc::VertexDef& BRepGraph::TopoView::VertexOps::Definition( + const BRepGraph_VertexId theVertex) const +{ + return myGraph->myData->myIncStorage.Vertex(theVertex); +} + +//================================================================================================= + +const NCollection_Vector& BRepGraph::TopoView::VertexOps::Edges( + const BRepGraph_VertexId theVertex) const +{ + return myGraph->myData->myIncStorage.ReverseIndex().EdgesOfVertexRef(theVertex); +} + +//================================================================================================= + +int BRepGraph::TopoView::WireOps::Nb() const +{ + return myGraph->myData->myIncStorage.NbWires(); +} + +//================================================================================================= + +int BRepGraph::TopoView::WireOps::NbActive() const +{ + return myGraph->myData->myIncStorage.NbActiveWires(); +} + +//================================================================================================= + +const BRepGraphInc::WireDef& BRepGraph::TopoView::WireOps::Definition( + const BRepGraph_WireId theWire) const +{ + return myGraph->myData->myIncStorage.Wire(theWire); +} + +//================================================================================================= + +const NCollection_Vector& BRepGraph::TopoView::WireOps::Faces( + const BRepGraph_WireId theWire) const +{ + return myGraph->myData->myIncStorage.ReverseIndex().FacesOfWireRef(theWire); +} + +//================================================================================================= + +int BRepGraph::TopoView::ShellOps::Nb() const +{ + return myGraph->myData->myIncStorage.NbShells(); +} + +//================================================================================================= + +int BRepGraph::TopoView::ShellOps::NbActive() const +{ + return myGraph->myData->myIncStorage.NbActiveShells(); +} + +//================================================================================================= + +const BRepGraphInc::ShellDef& BRepGraph::TopoView::ShellOps::Definition( + const BRepGraph_ShellId theShell) const +{ + return myGraph->myData->myIncStorage.Shell(theShell); +} + +//================================================================================================= + +const NCollection_Vector& BRepGraph::TopoView::ShellOps::Solids( + const BRepGraph_ShellId theShell) const +{ + return myGraph->myData->myIncStorage.ReverseIndex().SolidsOfShellRef(theShell); +} + +//================================================================================================= + +const NCollection_Vector& BRepGraph::TopoView::ShellOps::Compounds( + const BRepGraph_ShellId theShell) const +{ + const NCollection_Vector* aCompounds = + myGraph->myData->myIncStorage.ReverseIndex().CompoundsOfShell(theShell); + return aCompounds != nullptr ? *aCompounds : emptyVector(); +} + +//================================================================================================= + +int BRepGraph::TopoView::SolidOps::Nb() const +{ + return myGraph->myData->myIncStorage.NbSolids(); +} + +//================================================================================================= + +int BRepGraph::TopoView::SolidOps::NbActive() const +{ + return myGraph->myData->myIncStorage.NbActiveSolids(); +} + +//================================================================================================= + +const BRepGraphInc::SolidDef& BRepGraph::TopoView::SolidOps::Definition( + const BRepGraph_SolidId theSolid) const +{ + return myGraph->myData->myIncStorage.Solid(theSolid); +} + +//================================================================================================= + +const NCollection_Vector& BRepGraph::TopoView::SolidOps::CompSolids( + const BRepGraph_SolidId theSolid) const +{ + const NCollection_Vector* aCompSolids = + myGraph->myData->myIncStorage.ReverseIndex().CompSolidsOfSolid(theSolid); + return aCompSolids != nullptr ? *aCompSolids : emptyVector(); +} + +//================================================================================================= + +const NCollection_Vector& BRepGraph::TopoView::SolidOps::Compounds( + const BRepGraph_SolidId theSolid) const +{ + const NCollection_Vector* aCompounds = + myGraph->myData->myIncStorage.ReverseIndex().CompoundsOfSolid(theSolid); + return aCompounds != nullptr ? *aCompounds : emptyVector(); +} + +//================================================================================================= + +int BRepGraph::TopoView::CoEdgeOps::Nb() const +{ + return myGraph->myData->myIncStorage.NbCoEdges(); +} + +//================================================================================================= + +int BRepGraph::TopoView::CoEdgeOps::NbActive() const +{ + return myGraph->myData->myIncStorage.NbActiveCoEdges(); +} + +//================================================================================================= + +const BRepGraphInc::CoEdgeDef& BRepGraph::TopoView::CoEdgeOps::Definition( + const BRepGraph_CoEdgeId theCoEdge) const +{ + return myGraph->myData->myIncStorage.CoEdge(theCoEdge); +} + +//================================================================================================= + +const NCollection_Vector& BRepGraph::TopoView::CoEdgeOps::Wires( + const BRepGraph_CoEdgeId theCoEdge) const +{ + const NCollection_Vector* aWires = + myGraph->myData->myIncStorage.ReverseIndex().WiresOfCoEdge(theCoEdge); + return aWires != nullptr ? *aWires : emptyVector(); +} + +//================================================================================================= + +BRepGraph_EdgeId BRepGraph::TopoView::CoEdgeOps::Edge(const BRepGraph_CoEdgeId theCoEdge) const +{ + const BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + if (!theCoEdge.IsValid(aStorage.NbCoEdges())) + { + return BRepGraph_EdgeId(); + } + + const BRepGraphInc::CoEdgeDef& aCoEdge = aStorage.CoEdge(theCoEdge); + if (aCoEdge.IsRemoved || !aCoEdge.EdgeDefId.IsValid(aStorage.NbEdges())) + { + return BRepGraph_EdgeId(); + } + if (aStorage.Edge(aCoEdge.EdgeDefId).IsRemoved) + { + return BRepGraph_EdgeId(); + } + return aCoEdge.EdgeDefId; +} + +//================================================================================================= + +BRepGraph_FaceId BRepGraph::TopoView::CoEdgeOps::Face(const BRepGraph_CoEdgeId theCoEdge) const +{ + const BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + if (!theCoEdge.IsValid(aStorage.NbCoEdges())) + { + return BRepGraph_FaceId(); + } + + const BRepGraphInc::CoEdgeDef& aCoEdge = aStorage.CoEdge(theCoEdge); + if (aCoEdge.IsRemoved || !aCoEdge.FaceDefId.IsValid(aStorage.NbFaces())) + { + return BRepGraph_FaceId(); + } + if (aStorage.Face(aCoEdge.FaceDefId).IsRemoved) + { + return BRepGraph_FaceId(); + } + return aCoEdge.FaceDefId; +} + +//================================================================================================= + +BRepGraph_Curve2DRepId BRepGraph::TopoView::CoEdgeOps::Curve2DRepId( + const BRepGraph_CoEdgeId theCoEdge) const +{ + const BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + if (!theCoEdge.IsValid(aStorage.NbCoEdges())) + { + return BRepGraph_Curve2DRepId(); + } + + const BRepGraph_Curve2DRepId aRepId = aStorage.CoEdge(theCoEdge).Curve2DRepId; + if (!aRepId.IsValid(aStorage.NbCurves2D()) || aStorage.Curve2DRep(aRepId).IsRemoved) + { + return BRepGraph_Curve2DRepId(); + } + return aRepId; +} + +//================================================================================================= + +BRepGraph_CoEdgeId BRepGraph::TopoView::CoEdgeOps::SeamPair( + const BRepGraph_CoEdgeId theCoEdge) const +{ + const BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + if (!theCoEdge.IsValid(aStorage.NbCoEdges())) + { + return BRepGraph_CoEdgeId(); + } + + const BRepGraphInc::CoEdgeDef& aCoEdge = aStorage.CoEdge(theCoEdge); + if (aCoEdge.IsRemoved || !aCoEdge.SeamPairId.IsValid(aStorage.NbCoEdges())) + { + return BRepGraph_CoEdgeId(); + } + if (aStorage.CoEdge(aCoEdge.SeamPairId).IsRemoved) + { + return BRepGraph_CoEdgeId(); + } + return aCoEdge.SeamPairId; +} + +//================================================================================================= + +int BRepGraph::TopoView::CompoundOps::Nb() const +{ + return myGraph->myData->myIncStorage.NbCompounds(); +} + +//================================================================================================= + +int BRepGraph::TopoView::CompoundOps::NbActive() const +{ + return myGraph->myData->myIncStorage.NbActiveCompounds(); +} + +//================================================================================================= + +const BRepGraphInc::CompoundDef& BRepGraph::TopoView::CompoundOps::Definition( + const BRepGraph_CompoundId theCompound) const +{ + return myGraph->myData->myIncStorage.Compound(theCompound); +} + +//================================================================================================= + +const NCollection_Vector& BRepGraph::TopoView::CompoundOps::ParentCompounds( + const BRepGraph_CompoundId theCompound) const +{ + const NCollection_Vector* aCompounds = + myGraph->myData->myIncStorage.ReverseIndex().CompoundsOfCompound(theCompound); + return aCompounds != nullptr ? *aCompounds : emptyVector(); +} + +//================================================================================================= + +int BRepGraph::TopoView::CompSolidOps::Nb() const +{ + return myGraph->myData->myIncStorage.NbCompSolids(); +} + +//================================================================================================= + +int BRepGraph::TopoView::CompSolidOps::NbActive() const +{ + return myGraph->incStorage().NbActiveCompSolids(); +} + +//================================================================================================= + +const BRepGraphInc::CompSolidDef& BRepGraph::TopoView::CompSolidOps::Definition( + const BRepGraph_CompSolidId theCompSolid) const +{ + return myGraph->myData->myIncStorage.CompSolid(theCompSolid); +} + +//================================================================================================= + +const NCollection_Vector& BRepGraph::TopoView::CompSolidOps::Compounds( + const BRepGraph_CompSolidId theCompSolid) const +{ + const NCollection_Vector* aCompounds = + myGraph->myData->myIncStorage.ReverseIndex().CompoundsOfCompSolid(theCompSolid); + return aCompounds != nullptr ? *aCompounds : emptyVector(); +} + +//================================================================================================= + +int BRepGraph::TopoView::ProductOps::Nb() const +{ + return myGraph->myData->myIncStorage.NbProducts(); +} + +//================================================================================================= + +int BRepGraph::TopoView::ProductOps::NbActive() const +{ + return myGraph->myData->myIncStorage.NbActiveProducts(); +} + +//================================================================================================= + +const BRepGraphInc::ProductDef& BRepGraph::TopoView::ProductOps::Definition( + const BRepGraph_ProductId theProduct) const +{ + return myGraph->myData->myIncStorage.Product(theProduct); +} + +//================================================================================================= + +const NCollection_Vector& BRepGraph::TopoView::ProductOps::Instances( + const BRepGraph_ProductId theProduct) const +{ + const NCollection_Vector* anOccurrences = + myGraph->myData->myIncStorage.ReverseIndex().OccurrencesOfProduct(theProduct); + return anOccurrences != nullptr ? *anOccurrences : emptyVector(); +} + +//================================================================================================= + +BRepGraph_NodeId BRepGraph::TopoView::ProductOps::ShapeRoot( + const BRepGraph_ProductId theProduct) const +{ + const BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + if (!theProduct.IsValid(aStorage.NbProducts())) + { + return BRepGraph_NodeId(); + } + + const BRepGraph_NodeId aShapeRoot = aStorage.Product(theProduct).ShapeRootId; + if (!aShapeRoot.IsValid()) + { + return BRepGraph_NodeId(); + } + + const BRepGraphInc::BaseDef* aRoot = myGraph->Topo().Gen().TopoEntity(aShapeRoot); + if (aRoot == nullptr || aRoot->IsRemoved) + { + return BRepGraph_NodeId(); + } + return aShapeRoot; +} + +//================================================================================================= + +NCollection_Vector BRepGraph::TopoView::ProductOps::RootProducts( + const occ::handle& theAllocator) const +{ + constexpr int THE_ROOT_PRODUCTS_BLOCK_SIZE = 4; + NCollection_Vector aResult(THE_ROOT_PRODUCTS_BLOCK_SIZE, theAllocator); + + NCollection_Map aReferencedProducts(1, theAllocator); + for (BRepGraph_OccurrenceIterator anOccIt(*myGraph); anOccIt.More(); anOccIt.Next()) + { + const BRepGraphInc::OccurrenceDef& anOcc = anOccIt.Current(); + if (anOcc.ProductDefId.IsValid()) + { + aReferencedProducts.Add(anOcc.ProductDefId.Index); + } + } + + for (BRepGraph_ProductIterator aProdIt(*myGraph); aProdIt.More(); aProdIt.Next()) + { + if (!aReferencedProducts.Contains(aProdIt.CurrentId().Index)) + { + aResult.Append(aProdIt.CurrentId()); + } + } + return aResult; +} + +//================================================================================================= + +bool BRepGraph::TopoView::ProductOps::IsAssembly(const BRepGraph_ProductId theProduct) const +{ + const BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + if (!theProduct.IsValid(aStorage.NbProducts())) + { + return false; + } + + const BRepGraphInc::ProductDef& aProductDef = aStorage.Product(theProduct); + return !aProductDef.IsRemoved && !aProductDef.ShapeRootId.IsValid(); +} + +//================================================================================================= + +bool BRepGraph::TopoView::ProductOps::IsPart(const BRepGraph_ProductId theProduct) const +{ + const BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + if (!theProduct.IsValid(aStorage.NbProducts())) + { + return false; + } + + const BRepGraphInc::ProductDef& aProductDef = aStorage.Product(theProduct); + return !aProductDef.IsRemoved && aProductDef.ShapeRootId.IsValid(); +} + +//================================================================================================= + +BRepGraph_NodeId BRepGraph::TopoView::ProductOps::ShapeRootNode( + const BRepGraph_ProductId theProduct) const +{ + const BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + if (!theProduct.IsValid(aStorage.NbProducts())) + { + return BRepGraph_NodeId(); + } + + const BRepGraphInc::ProductDef& aProductDef = aStorage.Product(theProduct); + if (aProductDef.IsRemoved) + { + return BRepGraph_NodeId(); + } + return aProductDef.ShapeRootId; +} + +//================================================================================================= + +int BRepGraph::TopoView::ProductOps::NbComponents(const BRepGraph_ProductId theProduct) const +{ + const BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + if (!theProduct.IsValid(aStorage.NbProducts())) + { + return 0; + } + + const BRepGraphInc::ProductDef& aProductDef = aStorage.Product(theProduct); + if (aProductDef.IsRemoved) + { + return 0; + } + + int aCount = 0; + for (BRepGraph_RefsOccurrenceOfProduct anOccIt(*myGraph, theProduct); anOccIt.More(); + anOccIt.Next()) + { + const BRepGraphInc::OccurrenceRef& anOccRef = aStorage.OccurrenceRef(anOccIt.CurrentId()); + if (!aStorage.Occurrence(anOccRef.OccurrenceDefId).IsRemoved) + { + ++aCount; + } + } + return aCount; +} + +//================================================================================================= + +BRepGraph_OccurrenceId BRepGraph::TopoView::ProductOps::Component( + const BRepGraph_ProductId theProduct, + const int theComponentIdx) const +{ + const BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + if (!theProduct.IsValid(aStorage.NbProducts()) || theComponentIdx < 0) + { + return BRepGraph_OccurrenceId(); + } + + const BRepGraphInc::ProductDef& aProductDef = aStorage.Product(theProduct); + if (aProductDef.IsRemoved) + { + return BRepGraph_OccurrenceId(); + } + + int anActiveIndex = 0; + for (BRepGraph_RefsOccurrenceOfProduct anOccIt(*myGraph, theProduct); anOccIt.More(); + anOccIt.Next()) + { + const BRepGraphInc::OccurrenceRef& anOccRef = aStorage.OccurrenceRef(anOccIt.CurrentId()); + if (aStorage.Occurrence(anOccRef.OccurrenceDefId).IsRemoved) + { + continue; + } + + if (anActiveIndex == theComponentIdx) + { + return anOccRef.OccurrenceDefId; + } + ++anActiveIndex; + } + return BRepGraph_OccurrenceId(); +} + +//================================================================================================= + +int BRepGraph::TopoView::OccurrenceOps::Nb() const +{ + return myGraph->myData->myIncStorage.NbOccurrences(); +} + +//================================================================================================= + +int BRepGraph::TopoView::OccurrenceOps::NbActive() const +{ + return myGraph->myData->myIncStorage.NbActiveOccurrences(); +} + +//================================================================================================= + +const BRepGraphInc::OccurrenceDef& BRepGraph::TopoView::OccurrenceOps::Definition( + const BRepGraph_OccurrenceId theOccurrence) const +{ + return myGraph->myData->myIncStorage.Occurrence(theOccurrence); +} + +//================================================================================================= + +BRepGraph_ProductId BRepGraph::TopoView::OccurrenceOps::Product( + const BRepGraph_OccurrenceId theOccurrence) const +{ + const BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + if (!theOccurrence.IsValid(aStorage.NbOccurrences())) + { + return BRepGraph_ProductId(); + } + + const BRepGraphInc::OccurrenceDef& anOccurrence = aStorage.Occurrence(theOccurrence); + if (anOccurrence.IsRemoved || !anOccurrence.ProductDefId.IsValid(aStorage.NbProducts())) + { + return BRepGraph_ProductId(); + } + if (aStorage.Product(anOccurrence.ProductDefId).IsRemoved) + { + return BRepGraph_ProductId(); + } + return anOccurrence.ProductDefId; +} + +//================================================================================================= + +BRepGraph_ProductId BRepGraph::TopoView::OccurrenceOps::ParentProduct( + const BRepGraph_OccurrenceId theOccurrence) const +{ + const BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + if (!theOccurrence.IsValid(aStorage.NbOccurrences())) + { + return BRepGraph_ProductId(); + } + + const BRepGraphInc::OccurrenceDef& anOccurrence = aStorage.Occurrence(theOccurrence); + if (anOccurrence.IsRemoved || !anOccurrence.ParentProductDefId.IsValid(aStorage.NbProducts())) + { + return BRepGraph_ProductId(); + } + if (aStorage.Product(anOccurrence.ParentProductDefId).IsRemoved) + { + return BRepGraph_ProductId(); + } + return anOccurrence.ParentProductDefId; +} + +//================================================================================================= + +BRepGraph_OccurrenceId BRepGraph::TopoView::OccurrenceOps::ParentOccurrence( + const BRepGraph_OccurrenceId theOccurrence) const +{ + const BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + if (!theOccurrence.IsValid(aStorage.NbOccurrences())) + { + return BRepGraph_OccurrenceId(); + } + + const BRepGraphInc::OccurrenceDef& anOccurrence = aStorage.Occurrence(theOccurrence); + if (anOccurrence.IsRemoved + || !anOccurrence.ParentOccurrenceDefId.IsValid(aStorage.NbOccurrences())) + { + return BRepGraph_OccurrenceId(); + } + if (aStorage.Occurrence(anOccurrence.ParentOccurrenceDefId).IsRemoved) + { + return BRepGraph_OccurrenceId(); + } + return anOccurrence.ParentOccurrenceDefId; +} + +//================================================================================================= + +TopLoc_Location BRepGraph::TopoView::OccurrenceOps::OccurrenceLocation( + const BRepGraph_OccurrenceId theOccurrence) const +{ + const BRepGraphInc_Storage& aStorage = myGraph->myData->myIncStorage; + if (!theOccurrence.IsValid(aStorage.NbOccurrences())) + return TopLoc_Location(); + + TopLoc_Location aGlobal = aStorage.Occurrence(theOccurrence).Placement; + BRepGraph_OccurrenceId aParentOccId = aStorage.Occurrence(theOccurrence).ParentOccurrenceDefId; + + const int aNbOccurrences = aStorage.NbOccurrences(); + int aSteps = 0; + + while (aParentOccId.IsValid(aNbOccurrences) && aSteps < aNbOccurrences) + { + ++aSteps; + const BRepGraphInc::OccurrenceDef& aParentOcc = aStorage.Occurrence(aParentOccId); + if (aParentOcc.IsRemoved) + break; + aGlobal = aParentOcc.Placement * aGlobal; + aParentOccId = aParentOcc.ParentOccurrenceDefId; + } + + return aGlobal; +} + +//================================================================================================= + +int BRepGraph::TopoView::GeometryOps::NbSurfaces() const +{ + return myGraph->myData->myIncStorage.NbSurfaces(); +} + +//================================================================================================= + +int BRepGraph::TopoView::GeometryOps::NbCurves3D() const +{ + return myGraph->myData->myIncStorage.NbCurves3D(); +} + +//================================================================================================= + +int BRepGraph::TopoView::GeometryOps::NbCurves2D() const +{ + return myGraph->myData->myIncStorage.NbCurves2D(); +} + +//================================================================================================= + +int BRepGraph::TopoView::GeometryOps::NbActiveSurfaces() const +{ + return myGraph->myData->myIncStorage.NbActiveSurfaces(); +} + +//================================================================================================= + +int BRepGraph::TopoView::GeometryOps::NbActiveCurves3D() const +{ + return myGraph->myData->myIncStorage.NbActiveCurves3D(); +} + +//================================================================================================= + +int BRepGraph::TopoView::GeometryOps::NbActiveCurves2D() const +{ + return myGraph->myData->myIncStorage.NbActiveCurves2D(); +} + +//================================================================================================= + +const BRepGraphInc::SurfaceRep& BRepGraph::TopoView::GeometryOps::SurfaceRep( + const BRepGraph_SurfaceRepId theRep) const +{ + return myGraph->myData->myIncStorage.SurfaceRep(theRep); +} + +//================================================================================================= + +const BRepGraphInc::Curve3DRep& BRepGraph::TopoView::GeometryOps::Curve3DRep( + const BRepGraph_Curve3DRepId theRep) const +{ + return myGraph->myData->myIncStorage.Curve3DRep(theRep); +} + +//================================================================================================= + +const BRepGraphInc::Curve2DRep& BRepGraph::TopoView::GeometryOps::Curve2DRep( + const BRepGraph_Curve2DRepId theRep) const +{ + return myGraph->myData->myIncStorage.Curve2DRep(theRep); +} + +//================================================================================================= + +int BRepGraph::TopoView::PolyOps::NbTriangulations() const +{ + return myGraph->myData->myIncStorage.NbTriangulations(); +} + +//================================================================================================= + +int BRepGraph::TopoView::PolyOps::NbPolygons3D() const +{ + return myGraph->myData->myIncStorage.NbPolygons3D(); +} + +//================================================================================================= + +int BRepGraph::TopoView::PolyOps::NbPolygons2D() const +{ + return myGraph->myData->myIncStorage.NbPolygons2D(); +} + +//================================================================================================= + +int BRepGraph::TopoView::PolyOps::NbPolygonsOnTri() const +{ + return myGraph->myData->myIncStorage.NbPolygonsOnTri(); +} + +//================================================================================================= + +int BRepGraph::TopoView::PolyOps::NbActiveTriangulations() const +{ + return myGraph->myData->myIncStorage.NbActiveTriangulations(); +} + +//================================================================================================= + +int BRepGraph::TopoView::PolyOps::NbActivePolygons3D() const +{ + return myGraph->myData->myIncStorage.NbActivePolygons3D(); +} + +//================================================================================================= + +int BRepGraph::TopoView::PolyOps::NbActivePolygons2D() const +{ + return myGraph->myData->myIncStorage.NbActivePolygons2D(); +} + +//================================================================================================= + +int BRepGraph::TopoView::PolyOps::NbActivePolygonsOnTri() const +{ + return myGraph->myData->myIncStorage.NbActivePolygonsOnTri(); +} + +//================================================================================================= + +const BRepGraphInc::TriangulationRep& BRepGraph::TopoView::PolyOps::TriangulationRep( + const BRepGraph_TriangulationRepId theRep) const +{ + return myGraph->myData->myIncStorage.TriangulationRep(theRep); +} + +//================================================================================================= + +const BRepGraphInc::Polygon3DRep& BRepGraph::TopoView::PolyOps::Polygon3DRep( + const BRepGraph_Polygon3DRepId theRep) const +{ + return myGraph->myData->myIncStorage.Polygon3DRep(theRep); +} + +//================================================================================================= + +const BRepGraphInc::Polygon2DRep& BRepGraph::TopoView::PolyOps::Polygon2DRep( + const BRepGraph_Polygon2DRepId theRep) const +{ + return myGraph->myData->myIncStorage.Polygon2DRep(theRep); +} + +//================================================================================================= + +const BRepGraphInc::PolygonOnTriRep& BRepGraph::TopoView::PolyOps::PolygonOnTriRep( + const BRepGraph_PolygonOnTriRepId theRep) const +{ + return myGraph->myData->myIncStorage.PolygonOnTriRep(theRep); +} + +const BRepGraphInc::BaseDef* BRepGraph::TopoView::GenOps::TopoEntity( + const BRepGraph_NodeId theId) const +{ + return myGraph->topoEntity(theId); +} + +//================================================================================================= + +int BRepGraph::TopoView::GenOps::NbNodes() const +{ + const BRepGraphInc_Storage& aS = myGraph->myData->myIncStorage; + return aS.NbSolids() + aS.NbShells() + aS.NbFaces() + aS.NbWires() + aS.NbCoEdges() + aS.NbEdges() + + aS.NbVertices() + aS.NbCompounds() + aS.NbCompSolids() + aS.NbProducts() + + aS.NbOccurrences(); +} + +//================================================================================================= +bool BRepGraph::TopoView::GenOps::IsRemoved(const BRepGraph_NodeId theNode) const +{ + const BRepGraphInc::BaseDef* aDef = myGraph->topoEntity(theNode); + if (aDef == nullptr) + return false; + return aDef->IsRemoved; +} diff --git a/src/ModelingData/TKBRep/BRepGraph/BRepGraph_TopoView.hxx b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_TopoView.hxx new file mode 100644 index 0000000000..db75079e48 --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_TopoView.hxx @@ -0,0 +1,561 @@ +// Copyright (c) 2026 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 _BRepGraph_TopoView_HeaderFile +#define _BRepGraph_TopoView_HeaderFile + +#include +#include +#include +#include +#include + +class Adaptor3d_CurveOnSurface; + +//! @brief Unified read-only view over topology definitions, adjacency, and representations. +//! +//! Provides topology definition lookup, representation lookup, read-only +//! adjacency queries, and assembly classification over the incidence-table +//! model stored in BRepGraph. +//! Obtained via BRepGraph::Topo(). +//! +//! ## Soft-deletion convention +//! Per-kind count methods (Faces().Nb(), Edges().Nb(), etc.) return totals +//! including soft-removed nodes. Prefer per-kind NbActive() variants for +//! traversal and validation code that should ignore removed entities. +//! Definition accessors (Face, Edge, etc.) do not filter removed nodes - callers should check +//! IsRemoved() if needed. +//! +//! ## TopoView vs RefsView naming +//! TopoView accessors take definition IDs (BRepGraph_FaceId, BRepGraph_ShellId, etc.) +//! and return definition structs (FaceDef, ShellDef). RefsView accessors take +//! reference IDs (BRepGraph_FaceRefId, BRepGraph_ShellRefId) and return +//! reference-entry structs carrying per-use orientation and location. +//! +//! Reverse-index accessors return const references to internal vectors. The +//! reference itself is always valid; the returned vector may be empty when the +//! queried entity has no parents of that kind. +class BRepGraph::TopoView +{ +public: + //! @brief Face-oriented topology queries. + class FaceOps + { + public: + [[nodiscard]] Standard_EXPORT int Nb() const; + [[nodiscard]] Standard_EXPORT int NbActive() const; + [[nodiscard]] Standard_EXPORT const BRepGraphInc::FaceDef& Definition( + const BRepGraph_FaceId theFace) const; + [[nodiscard]] Standard_EXPORT const NCollection_Vector& Shells( + const BRepGraph_FaceId theFace) const; + [[nodiscard]] Standard_EXPORT const NCollection_Vector& Compounds( + const BRepGraph_FaceId theFace) const; + [[nodiscard]] Standard_EXPORT BRepGraph_SurfaceRepId + SurfaceRepId(const BRepGraph_FaceId theFace) const; + [[nodiscard]] Standard_EXPORT BRepGraph_TriangulationRepId + ActiveTriangulationRepId(const BRepGraph_FaceId theFace) const; + [[nodiscard]] Standard_EXPORT NCollection_Vector SameDomain( + const BRepGraph_FaceId theFace, + const occ::handle& theAllocator) const; + [[nodiscard]] Standard_EXPORT NCollection_Vector SharedEdges( + const BRepGraph_FaceId theFaceA, + const BRepGraph_FaceId theFaceB, + const occ::handle& theAllocator) const; + [[nodiscard]] Standard_EXPORT NCollection_Vector Adjacent( + const BRepGraph_FaceId theFace, + const occ::handle& theAllocator) const; + [[nodiscard]] Standard_EXPORT BRepGraph_WireId OuterWire(const BRepGraph_FaceId theFace) const; + + private: + friend class TopoView; + + explicit FaceOps(const BRepGraph* theGraph) + : myGraph(theGraph) + { + } + + const BRepGraph* myGraph; + }; + + //! @brief Edge-oriented topology queries. + class EdgeOps + { + public: + [[nodiscard]] Standard_EXPORT int Nb() const; + [[nodiscard]] Standard_EXPORT int NbActive() const; + [[nodiscard]] Standard_EXPORT const BRepGraphInc::EdgeDef& Definition( + const BRepGraph_EdgeId theEdge) const; + [[nodiscard]] Standard_EXPORT int NbFaces(const BRepGraph_EdgeId theEdge) const; + [[nodiscard]] Standard_EXPORT const NCollection_Vector& Wires( + const BRepGraph_EdgeId theEdge) const; + [[nodiscard]] Standard_EXPORT const NCollection_Vector& CoEdges( + const BRepGraph_EdgeId theEdge) const; + [[nodiscard]] Standard_EXPORT const NCollection_Vector& Faces( + const BRepGraph_EdgeId theEdge) const; + [[nodiscard]] Standard_EXPORT BRepGraph_Curve3DRepId + Curve3DRepId(const BRepGraph_EdgeId theEdge) const; + [[nodiscard]] Standard_EXPORT NCollection_Vector Adjacent( + const BRepGraph_EdgeId theEdge, + const occ::handle& theAllocator) const; + [[nodiscard]] Standard_EXPORT bool IsBoundary(const BRepGraph_EdgeId theEdge) const; + [[nodiscard]] Standard_EXPORT bool IsManifold(const BRepGraph_EdgeId theEdge) const; + [[nodiscard]] Standard_EXPORT const BRepGraphInc::CoEdgeDef* FindPCurve( + const BRepGraph_EdgeId theEdge, + const BRepGraph_FaceId theFace) const; + [[nodiscard]] Standard_EXPORT const BRepGraphInc::CoEdgeDef* FindPCurve( + const BRepGraph_EdgeId theEdge, + const BRepGraph_FaceId theFace, + const TopAbs_Orientation theOrientation) const; + + private: + friend class TopoView; + + explicit EdgeOps(const BRepGraph* theGraph) + : myGraph(theGraph) + { + } + + const BRepGraph* myGraph; + }; + + //! @brief Vertex-oriented topology queries. + class VertexOps + { + public: + [[nodiscard]] Standard_EXPORT int Nb() const; + [[nodiscard]] Standard_EXPORT int NbActive() const; + [[nodiscard]] Standard_EXPORT const BRepGraphInc::VertexDef& Definition( + const BRepGraph_VertexId theVertex) const; + [[nodiscard]] Standard_EXPORT const NCollection_Vector& Edges( + const BRepGraph_VertexId theVertex) const; + + private: + friend class TopoView; + + explicit VertexOps(const BRepGraph* theGraph) + : myGraph(theGraph) + { + } + + const BRepGraph* myGraph; + }; + + //! @brief Wire-oriented topology queries. + class WireOps + { + public: + [[nodiscard]] Standard_EXPORT int Nb() const; + [[nodiscard]] Standard_EXPORT int NbActive() const; + [[nodiscard]] Standard_EXPORT const BRepGraphInc::WireDef& Definition( + const BRepGraph_WireId theWire) const; + [[nodiscard]] Standard_EXPORT const NCollection_Vector& Faces( + const BRepGraph_WireId theWire) const; + + private: + friend class TopoView; + + explicit WireOps(const BRepGraph* theGraph) + : myGraph(theGraph) + { + } + + const BRepGraph* myGraph; + }; + + //! @brief Shell-oriented topology queries. + class ShellOps + { + public: + [[nodiscard]] Standard_EXPORT int Nb() const; + [[nodiscard]] Standard_EXPORT int NbActive() const; + [[nodiscard]] Standard_EXPORT const BRepGraphInc::ShellDef& Definition( + const BRepGraph_ShellId theShell) const; + [[nodiscard]] Standard_EXPORT const NCollection_Vector& Solids( + const BRepGraph_ShellId theShell) const; + [[nodiscard]] Standard_EXPORT const NCollection_Vector& Compounds( + const BRepGraph_ShellId theShell) const; + + private: + friend class TopoView; + + explicit ShellOps(const BRepGraph* theGraph) + : myGraph(theGraph) + { + } + + const BRepGraph* myGraph; + }; + + //! @brief Solid-oriented topology queries. + class SolidOps + { + public: + [[nodiscard]] Standard_EXPORT int Nb() const; + [[nodiscard]] Standard_EXPORT int NbActive() const; + [[nodiscard]] Standard_EXPORT const BRepGraphInc::SolidDef& Definition( + const BRepGraph_SolidId theSolid) const; + [[nodiscard]] Standard_EXPORT const NCollection_Vector& CompSolids( + const BRepGraph_SolidId theSolid) const; + [[nodiscard]] Standard_EXPORT const NCollection_Vector& Compounds( + const BRepGraph_SolidId theSolid) const; + + private: + friend class TopoView; + + explicit SolidOps(const BRepGraph* theGraph) + : myGraph(theGraph) + { + } + + const BRepGraph* myGraph; + }; + + //! @brief Coedge-oriented topology and representation queries. + class CoEdgeOps + { + public: + [[nodiscard]] Standard_EXPORT int Nb() const; + [[nodiscard]] Standard_EXPORT int NbActive() const; + [[nodiscard]] Standard_EXPORT const BRepGraphInc::CoEdgeDef& Definition( + const BRepGraph_CoEdgeId theCoEdge) const; + [[nodiscard]] Standard_EXPORT const NCollection_Vector& Wires( + const BRepGraph_CoEdgeId theCoEdge) const; + [[nodiscard]] Standard_EXPORT BRepGraph_EdgeId Edge(const BRepGraph_CoEdgeId theCoEdge) const; + [[nodiscard]] Standard_EXPORT BRepGraph_FaceId Face(const BRepGraph_CoEdgeId theCoEdge) const; + [[nodiscard]] Standard_EXPORT BRepGraph_Curve2DRepId + Curve2DRepId(const BRepGraph_CoEdgeId theCoEdge) const; + [[nodiscard]] Standard_EXPORT BRepGraph_CoEdgeId + SeamPair(const BRepGraph_CoEdgeId theCoEdge) const; + + private: + friend class TopoView; + + explicit CoEdgeOps(const BRepGraph* theGraph) + : myGraph(theGraph) + { + } + + const BRepGraph* myGraph; + }; + + //! @brief Compound-oriented topology queries. + class CompoundOps + { + public: + [[nodiscard]] Standard_EXPORT int Nb() const; + [[nodiscard]] Standard_EXPORT int NbActive() const; + [[nodiscard]] Standard_EXPORT const BRepGraphInc::CompoundDef& Definition( + const BRepGraph_CompoundId theCompound) const; + [[nodiscard]] Standard_EXPORT const NCollection_Vector& ParentCompounds( + const BRepGraph_CompoundId theCompound) const; + + private: + friend class TopoView; + + explicit CompoundOps(const BRepGraph* theGraph) + : myGraph(theGraph) + { + } + + const BRepGraph* myGraph; + }; + + //! @brief Comp-solid oriented topology queries. + class CompSolidOps + { + public: + [[nodiscard]] Standard_EXPORT int Nb() const; + [[nodiscard]] Standard_EXPORT int NbActive() const; + [[nodiscard]] Standard_EXPORT const BRepGraphInc::CompSolidDef& Definition( + const BRepGraph_CompSolidId theCompSolid) const; + [[nodiscard]] Standard_EXPORT const NCollection_Vector& Compounds( + const BRepGraph_CompSolidId theCompSolid) const; + + private: + friend class TopoView; + + explicit CompSolidOps(const BRepGraph* theGraph) + : myGraph(theGraph) + { + } + + const BRepGraph* myGraph; + }; + + //! @brief Product-oriented raw assembly queries. + class ProductOps + { + public: + [[nodiscard]] Standard_EXPORT int Nb() const; + [[nodiscard]] Standard_EXPORT int NbActive() const; + [[nodiscard]] Standard_EXPORT const BRepGraphInc::ProductDef& Definition( + const BRepGraph_ProductId theProduct) const; + [[nodiscard]] Standard_EXPORT const NCollection_Vector& Instances( + const BRepGraph_ProductId theProduct) const; + [[nodiscard]] Standard_EXPORT BRepGraph_NodeId + ShapeRoot(const BRepGraph_ProductId theProduct) const; + + //! Return typed identifiers of all root products (products not referenced by an active + //! occurrence). + //! @param[in] theAllocator allocator for internal temporaries and the result vector + [[nodiscard]] Standard_EXPORT NCollection_Vector RootProducts( + const occ::handle& theAllocator) const; + + //! True if the product is an assembly (has child occurrences, no topology root). + //! @param[in] theProduct typed product definition identifier + [[nodiscard]] Standard_EXPORT bool IsAssembly(const BRepGraph_ProductId theProduct) const; + + //! True if the product is a part (has a valid topology root). + //! @param[in] theProduct typed product definition identifier + [[nodiscard]] Standard_EXPORT bool IsPart(const BRepGraph_ProductId theProduct) const; + + //! Return the topology root NodeId for a part product. + //! For assemblies (no topology root) returns an invalid NodeId. + //! @param[in] theProduct typed product definition identifier + [[nodiscard]] Standard_EXPORT BRepGraph_NodeId + ShapeRootNode(const BRepGraph_ProductId theProduct) const; + + //! Number of active child occurrences of a product. + //! @param[in] theProduct typed product definition identifier + [[nodiscard]] Standard_EXPORT int NbComponents(const BRepGraph_ProductId theProduct) const; + + //! Return the i-th active child occurrence identifier of a product. + //! @param[in] theProduct typed product definition identifier + //! @param[in] theComponentIdx zero-based active occurrence index within the product + [[nodiscard]] Standard_EXPORT BRepGraph_OccurrenceId + Component(const BRepGraph_ProductId theProduct, const int theComponentIdx) const; + + private: + friend class TopoView; + + explicit ProductOps(const BRepGraph* theGraph) + : myGraph(theGraph) + { + } + + const BRepGraph* myGraph; + }; + + //! @brief Occurrence-oriented raw assembly queries. + class OccurrenceOps + { + public: + [[nodiscard]] Standard_EXPORT int Nb() const; + [[nodiscard]] Standard_EXPORT int NbActive() const; + [[nodiscard]] Standard_EXPORT const BRepGraphInc::OccurrenceDef& Definition( + const BRepGraph_OccurrenceId theOccurrence) const; + [[nodiscard]] Standard_EXPORT BRepGraph_ProductId + Product(const BRepGraph_OccurrenceId theOccurrence) const; + [[nodiscard]] Standard_EXPORT BRepGraph_ProductId + ParentProduct(const BRepGraph_OccurrenceId theOccurrence) const; + [[nodiscard]] Standard_EXPORT BRepGraph_OccurrenceId + ParentOccurrence(const BRepGraph_OccurrenceId theOccurrence) const; + + //! Compute the global placement of an occurrence by walking the parent chain. + //! Shared products can appear at multiple placements; the returned location is + //! specific to the supplied occurrence path through ParentOccurrenceDefId. + //! @param[in] theOccurrence typed occurrence identifier + //! @return composed TopLoc_Location from root to the occurrence + [[nodiscard]] Standard_EXPORT TopLoc_Location + OccurrenceLocation(const BRepGraph_OccurrenceId theOccurrence) const; + + private: + friend class TopoView; + + explicit OccurrenceOps(const BRepGraph* theGraph) + : myGraph(theGraph) + { + } + + const BRepGraph* myGraph; + }; + + //! @brief Generic topology and assembly count / meta queries. + class GenOps + { + public: + [[nodiscard]] Standard_EXPORT const BRepGraphInc::BaseDef* TopoEntity( + const BRepGraph_NodeId theId) const; + [[nodiscard]] Standard_EXPORT int NbNodes() const; + [[nodiscard]] Standard_EXPORT bool IsRemoved(const BRepGraph_NodeId theNode) const; + + private: + friend class TopoView; + + explicit GenOps(const BRepGraph* theGraph) + : myGraph(theGraph) + { + } + + const BRepGraph* myGraph; + }; + + //! @brief Analytic geometry representation queries. + class GeometryOps + { + public: + [[nodiscard]] Standard_EXPORT int NbSurfaces() const; + [[nodiscard]] Standard_EXPORT int NbCurves3D() const; + [[nodiscard]] Standard_EXPORT int NbCurves2D() const; + + [[nodiscard]] Standard_EXPORT int NbActiveSurfaces() const; + [[nodiscard]] Standard_EXPORT int NbActiveCurves3D() const; + [[nodiscard]] Standard_EXPORT int NbActiveCurves2D() const; + + [[nodiscard]] Standard_EXPORT const BRepGraphInc::SurfaceRep& SurfaceRep( + const BRepGraph_SurfaceRepId theRep) const; + [[nodiscard]] Standard_EXPORT const BRepGraphInc::Curve3DRep& Curve3DRep( + const BRepGraph_Curve3DRepId theRep) const; + [[nodiscard]] Standard_EXPORT const BRepGraphInc::Curve2DRep& Curve2DRep( + const BRepGraph_Curve2DRepId theRep) const; + + private: + friend class TopoView; + + explicit GeometryOps(const BRepGraph* theGraph) + : myGraph(theGraph) + { + } + + const BRepGraph* myGraph; + }; + + //! @brief Polygonal and triangulation representation queries. + class PolyOps + { + public: + [[nodiscard]] Standard_EXPORT int NbTriangulations() const; + [[nodiscard]] Standard_EXPORT int NbPolygons3D() const; + [[nodiscard]] Standard_EXPORT int NbPolygons2D() const; + [[nodiscard]] Standard_EXPORT int NbPolygonsOnTri() const; + + [[nodiscard]] Standard_EXPORT int NbActiveTriangulations() const; + [[nodiscard]] Standard_EXPORT int NbActivePolygons3D() const; + [[nodiscard]] Standard_EXPORT int NbActivePolygons2D() const; + [[nodiscard]] Standard_EXPORT int NbActivePolygonsOnTri() const; + + [[nodiscard]] Standard_EXPORT const BRepGraphInc::TriangulationRep& TriangulationRep( + const BRepGraph_TriangulationRepId theRep) const; + [[nodiscard]] Standard_EXPORT const BRepGraphInc::Polygon3DRep& Polygon3DRep( + const BRepGraph_Polygon3DRepId theRep) const; + [[nodiscard]] Standard_EXPORT const BRepGraphInc::Polygon2DRep& Polygon2DRep( + const BRepGraph_Polygon2DRepId theRep) const; + [[nodiscard]] Standard_EXPORT const BRepGraphInc::PolygonOnTriRep& PolygonOnTriRep( + const BRepGraph_PolygonOnTriRepId theRep) const; + + private: + friend class TopoView; + + explicit PolyOps(const BRepGraph* theGraph) + : myGraph(theGraph) + { + } + + const BRepGraph* myGraph; + }; + + //! Grouped face-oriented queries. + [[nodiscard]] const FaceOps& Faces() const { return myFaces; } + + //! Grouped edge-oriented queries. + [[nodiscard]] const EdgeOps& Edges() const { return myEdges; } + + //! Grouped vertex-oriented queries. + [[nodiscard]] const VertexOps& Vertices() const { return myVertices; } + + //! Grouped wire-oriented queries. + [[nodiscard]] const WireOps& Wires() const { return myWires; } + + //! Grouped shell-oriented queries. + [[nodiscard]] const ShellOps& Shells() const { return myShells; } + + //! Grouped solid-oriented queries. + [[nodiscard]] const SolidOps& Solids() const { return mySolids; } + + //! Grouped coedge-oriented queries. + [[nodiscard]] const CoEdgeOps& CoEdges() const { return myCoEdges; } + + //! Grouped compound-oriented queries. + [[nodiscard]] const CompoundOps& Compounds() const { return myCompounds; } + + //! Grouped comp-solid oriented queries. + [[nodiscard]] const CompSolidOps& CompSolids() const { return myCompSolids; } + + //! Grouped product-oriented queries. + [[nodiscard]] const ProductOps& Products() const { return myProducts; } + + //! Grouped occurrence-oriented queries. + [[nodiscard]] const OccurrenceOps& Occurrences() const { return myOccurrences; } + + //! Grouped generic topology and assembly counts / meta queries. + [[nodiscard]] const GenOps& Gen() const { return myGen; } + + //! Grouped analytic geometry representation queries. + [[nodiscard]] const GeometryOps& Geometry() const { return myGeometry; } + + //! Grouped polygonal representation queries. + [[nodiscard]] const PolyOps& Poly() const { return myPoly; } + + //! @name Representation groups + //! + //! Representations use dense 0-based indexing. Iterate through grouped accessors: + //! @code + //! for (int i = 0; i < aGraph.Topo().Geometry().NbSurfaces(); ++i) + //! { + //! const BRepGraphInc::SurfaceRep& aRep = + //! aGraph.Topo().Geometry().SurfaceRep(BRepGraph_SurfaceRepId(i)); + //! } + //! @endcode + +private: + friend class BRepGraph; + friend struct BRepGraph_Data; + friend class BRepGraph_Tool; + + explicit TopoView(const BRepGraph* theGraph) + : myGraph(theGraph), + myFaces(theGraph), + myEdges(theGraph), + myVertices(theGraph), + myWires(theGraph), + myShells(theGraph), + mySolids(theGraph), + myCoEdges(theGraph), + myCompounds(theGraph), + myCompSolids(theGraph), + myProducts(theGraph), + myOccurrences(theGraph), + myGen(theGraph), + myGeometry(theGraph), + myPoly(theGraph) + { + } + + const BRepGraph* myGraph; + FaceOps myFaces; + EdgeOps myEdges; + VertexOps myVertices; + WireOps myWires; + ShellOps myShells; + SolidOps mySolids; + CoEdgeOps myCoEdges; + CompoundOps myCompounds; + CompSolidOps myCompSolids; + ProductOps myProducts; + OccurrenceOps myOccurrences; + GenOps myGen; + GeometryOps myGeometry; + PolyOps myPoly; +}; + +#endif // _BRepGraph_TopoView_HeaderFile diff --git a/src/ModelingData/TKBRep/BRepGraph/BRepGraph_Transform.cxx b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_Transform.cxx new file mode 100644 index 0000000000..55c6a1e57e --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_Transform.cxx @@ -0,0 +1,169 @@ +// Copyright (c) 2026 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 + +namespace +{ + +//! Geometry-level transform: deep-copy geometry is already done by Copy, +//! so transform geometry handles in-place. +//! Matches BRepBuilderAPI_Transform with theCopyGeom=true. +//! Triangulations on FaceDefs are invalidated since geometry coordinates changed. +void applyGeometryTransform(BRepGraph& theGraph, const gp_Trsf& theTrsf) +{ + // Transform absolute vertex points. + for (BRepGraph_VertexIterator aVertexIt(theGraph); aVertexIt.More(); aVertexIt.Next()) + { + theGraph.Builder().MutVertex(aVertexIt.CurrentId())->Point.Transform(theTrsf); + } + + // Transform surface geometry handles directly on surface reps. + // Use visited set to avoid transforming shared handles twice. + NCollection_Map aVisitedSurfReps; + for (BRepGraph_FaceIterator aFaceIt(theGraph); aFaceIt.More(); aFaceIt.Next()) + { + const BRepGraph_FaceId aFaceId = aFaceIt.CurrentId(); + BRepGraph_MutGuard aFace = theGraph.Builder().MutFace(aFaceId); + if (BRepGraph_Tool::Face::HasSurface(theGraph, aFaceId) + && aVisitedSurfReps.Add(aFace->SurfaceRepId.Index)) + { + const occ::handle& aSurf = BRepGraph_Tool::Face::Surface(theGraph, aFaceId); + if (!aSurf.IsNull()) + aSurf->Transform(theTrsf); + } + // Invalidate triangulations - meshes are no longer valid after geometry transform. + aFace->TriangulationRepIds.Clear(); + aFace->ActiveTriangulationIndex = -1; + } + + // Transform curve geometry handles directly on curve reps. + NCollection_Map aVisitedCurveReps; + for (BRepGraph_EdgeIterator anEdgeIt(theGraph); anEdgeIt.More(); anEdgeIt.Next()) + { + const BRepGraph_EdgeId anEdgeId = anEdgeIt.CurrentId(); + BRepGraph_MutGuard anEdge = theGraph.Builder().MutEdge(anEdgeId); + if (BRepGraph_Tool::Edge::HasCurve(theGraph, anEdgeId) + && aVisitedCurveReps.Add(anEdge->Curve3DRepId.Index)) + { + const occ::handle& aCurve3d = BRepGraph_Tool::Edge::Curve(theGraph, anEdgeId); + if (!aCurve3d.IsNull()) + aCurve3d->Transform(theTrsf); + } + } + // PCurves are in UV space - they are not affected by 3D transforms. +} + +} // namespace + +//================================================================================================= + +void BRepGraph_Transform::applyLocationTransform(BRepGraph& theGraph, const gp_Trsf& theTrsf) +{ + const TopLoc_Location aLoc(theTrsf); + + // Compose the transform into root Product RootLocations. + // RootProducts() returns products not referenced by any occurrence. + // Product::RootLocation participates in location composition, + // so all descendant queries automatically include it. + const occ::handle anAllocator = new NCollection_IncAllocator(); + const NCollection_Vector aRoots = + theGraph.Topo().Products().RootProducts(anAllocator); + for (const BRepGraph_ProductId& aRootId : aRoots) + { + BRepGraph_MutGuard aProduct = theGraph.Builder().MutProduct(aRootId); + aProduct->RootLocation = aLoc * aProduct->RootLocation; + } +} + +//================================================================================================= + +BRepGraph BRepGraph_Transform::Perform(const BRepGraph& theGraph, + const gp_Trsf& theTrsf, + const bool theCopyGeom) +{ + if (!theGraph.IsDone()) + return BRepGraph(); + + // Determine if we need geometry-level modification (like BRepBuilderAPI_Transform). + const bool useGeomModif = + theCopyGeom || theTrsf.IsNegative() + || (std::abs(std::abs(theTrsf.ScaleFactor()) - 1.) > TopLoc_Location::ScalePrec()); + + if (useGeomModif) + { + // Geometry-level: deep-copy then transform geometry handles in-place. + BRepGraph aResult = BRepGraph_Copy::Perform(theGraph, true); + if (!aResult.IsDone()) + return aResult; + + applyGeometryTransform(aResult, theTrsf); + return aResult; + } + + // Root-level (location-only): light-copy, multiply transform into node locations. + // Matches BRepBuilderAPI_Transform with theCopyGeom=false (shape.Moved(trsf)). + BRepGraph aResult = BRepGraph_Copy::Perform(theGraph, false); + if (!aResult.IsDone()) + return aResult; + + applyLocationTransform(aResult, theTrsf); + return aResult; +} + +//================================================================================================= + +BRepGraph BRepGraph_Transform::TransformFace(const BRepGraph& theGraph, + const BRepGraph_FaceId theFace, + const gp_Trsf& theTrsf, + const bool theCopyGeom) +{ + if (!theGraph.IsDone() || theFace.Index < 0 || theFace.Index >= theGraph.Topo().Faces().Nb()) + return BRepGraph(); + + const bool useGeomModif = + theCopyGeom || theTrsf.IsNegative() + || (std::abs(std::abs(theTrsf.ScaleFactor()) - 1.) > TopLoc_Location::ScalePrec()); + + // Skip transient cache reservation for temporary transform graphs + // that are only used for reconstruction and then discarded. + constexpr bool THE_RESERVE_CACHE = false; + + if (useGeomModif) + { + BRepGraph aResult = BRepGraph_Copy::CopyFace(theGraph, theFace, true, THE_RESERVE_CACHE); + if (!aResult.IsDone()) + return aResult; + + applyGeometryTransform(aResult, theTrsf); + return aResult; + } + + BRepGraph aResult = BRepGraph_Copy::CopyFace(theGraph, theFace, false, THE_RESERVE_CACHE); + if (!aResult.IsDone()) + return aResult; + + applyLocationTransform(aResult, theTrsf); + return aResult; +} diff --git a/src/ModelingData/TKBRep/BRepGraph/BRepGraph_Transform.hxx b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_Transform.hxx new file mode 100644 index 0000000000..f58128cc3b --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_Transform.hxx @@ -0,0 +1,80 @@ +// Copyright (c) 2026 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 _BRepGraph_Transform_HeaderFile +#define _BRepGraph_Transform_HeaderFile + +#include +#include + +#include +#include + +//! @brief Graph-to-graph transformation. +//! +//! Produces a new BRepGraph by copying and then applying a geometric +//! transformation to vertex points and geometry node locations. +//! +//! Two modes (matching BRepBuilderAPI_Transform semantics): +//! - theCopyGeom = true (geometry-level): deep-copy geometry, transform handles +//! in-place via Geom_Surface::Transform() etc., reset locations to identity. +//! Triangulations are invalidated. +//! - theCopyGeom = false (root-level): light-copy with shared geometry, apply +//! transform via location modification only. Triangulations remain valid. +//! +//! @note Returns BRepGraph directly (not a Result struct) because this is an +//! immutable operation producing a new graph. Check IsDone() for success. +//! +//! ## Typical usage +//! @code +//! BRepGraph aGraph; +//! aGraph.Build(myShape); +//! gp_Trsf aTrsf; +//! aTrsf.SetTranslation(gp_Vec(10.0, 0.0, 0.0)); +//! BRepGraph aTransformed = BRepGraph_Transform::Perform(aGraph, aTrsf); +//! TopoDS_Shape aShape = aTransformed.Shapes().Shape(); +//! @endcode +class BRepGraph_Transform +{ +public: + DEFINE_STANDARD_ALLOC + + //! Transform the entire graph. + //! @param[in] theGraph a pre-built BRepGraph (must have IsDone() == true) + //! @param[in] theTrsf the transformation to apply + //! @param[in] theCopyGeom if true, geometry is deep-copied before transforming; + //! if false, light-copy then transform locations/points only + //! @return a new BRepGraph with the transformation applied + [[nodiscard]] Standard_EXPORT static BRepGraph Perform(const BRepGraph& theGraph, + const gp_Trsf& theTrsf, + const bool theCopyGeom = true); + + //! Transform a single face sub-graph. + //! @param[in] theGraph a pre-built BRepGraph + //! @param[in] theFace face definition identifier in the graph + //! @param[in] theTrsf the transformation to apply + //! @param[in] theCopyGeom if true, geometry is deep-copied before transforming + //! @return a new BRepGraph containing only the specified face, transformed + [[nodiscard]] Standard_EXPORT static BRepGraph TransformFace(const BRepGraph& theGraph, + const BRepGraph_FaceId theFace, + const gp_Trsf& theTrsf, + const bool theCopyGeom = true); + +private: + //! Apply location-only transform by storing per-node locations. + static void applyLocationTransform(BRepGraph& theGraph, const gp_Trsf& theTrsf); + + BRepGraph_Transform() = delete; +}; + +#endif // _BRepGraph_Transform_HeaderFile diff --git a/src/ModelingData/TKBRep/BRepGraph/BRepGraph_TransientCache.cxx b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_TransientCache.cxx new file mode 100644 index 0000000000..c589084b56 --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_TransientCache.cxx @@ -0,0 +1,473 @@ +// Copyright (c) 2026 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 + +IMPLEMENT_STANDARD_RTTIEXT(BRepGraph_CacheKind, Standard_Transient) + +namespace +{ + +struct BRepGraph_CacheKindRegistryData +{ + NCollection_DataMap GuidToSlot; + NCollection_Vector> Kinds; + std::shared_mutex Mutex; +}; + +BRepGraph_CacheKindRegistryData& cacheKindRegistryData() +{ + static BRepGraph_CacheKindRegistryData aData; + return aData; +} + +} // namespace + +//================================================================================================= + +BRepGraph_CacheKind::BRepGraph_CacheKind(const Standard_GUID& theID, + const TCollection_AsciiString& theName, + const int theNodeKindsMask) + : myID(theID), + myName(theName), + myNodeKindsMask(theNodeKindsMask) +{ +} + +//================================================================================================= + +int BRepGraph_CacheKindRegistry::Register(const occ::handle& theKind) +{ + if (theKind.IsNull()) + { + return -1; + } + + BRepGraph_CacheKindRegistryData& aData = cacheKindRegistryData(); + { + std::shared_lock aReadLock(aData.Mutex); + const int* aSlot = aData.GuidToSlot.Seek(theKind->ID()); + if (aSlot != nullptr) + { + return *aSlot; + } + } + + std::unique_lock aWriteLock(aData.Mutex); + const int* aSlot = aData.GuidToSlot.Seek(theKind->ID()); + if (aSlot != nullptr) + { + return *aSlot; + } + + const int aNewSlot = aData.Kinds.Length(); + aData.Kinds.Append(theKind); + aData.GuidToSlot.Bind(theKind->ID(), aNewSlot); + return aNewSlot; +} + +//================================================================================================= + +int BRepGraph_CacheKindRegistry::FindSlot(const Standard_GUID& theGUID) +{ + BRepGraph_CacheKindRegistryData& aData = cacheKindRegistryData(); + std::shared_lock aLock(aData.Mutex); + const int* aSlot = aData.GuidToSlot.Seek(theGUID); + return aSlot != nullptr ? *aSlot : -1; +} + +//================================================================================================= + +bool BRepGraph_CacheKindRegistry::FindSlot(const Standard_GUID& theGUID, int& theSlot) +{ + theSlot = FindSlot(theGUID); + return theSlot >= 0; +} + +//================================================================================================= + +occ::handle BRepGraph_CacheKindRegistry::FindKind(const Standard_GUID& theGUID) +{ + const int aSlot = FindSlot(theGUID); + return aSlot >= 0 ? FindKind(aSlot) : occ::handle(); +} + +//================================================================================================= + +occ::handle BRepGraph_CacheKindRegistry::FindKind(const int theSlot) +{ + BRepGraph_CacheKindRegistryData& aData = cacheKindRegistryData(); + std::shared_lock aLock(aData.Mutex); + if (theSlot < 0 || theSlot >= aData.Kinds.Length()) + { + return occ::handle(); + } + return aData.Kinds.Value(theSlot); +} + +//================================================================================================= + +bool BRepGraph_CacheKindRegistry::Contains(const Standard_GUID& theGUID) +{ + return FindSlot(theGUID) >= 0; +} + +//================================================================================================= + +bool BRepGraph_CacheKindRegistry::Contains(const int theSlot) +{ + BRepGraph_CacheKindRegistryData& aData = cacheKindRegistryData(); + std::shared_lock aLock(aData.Mutex); + return theSlot >= 0 && theSlot < aData.Kinds.Length() && !aData.Kinds.Value(theSlot).IsNull(); +} + +//================================================================================================= + +int BRepGraph_CacheKindRegistry::NbRegistered() +{ + BRepGraph_CacheKindRegistryData& aData = cacheKindRegistryData(); + std::shared_lock aLock(aData.Mutex); + return aData.Kinds.Length(); +} + +//================================================================================================= + +void BRepGraph_TransientCache::ensureKind(const int theKindSlot) +{ + if (theKindSlot >= myKinds.Length()) + { + myKinds.SetValue(theKindSlot, CacheKindSlot()); + } +} + +//================================================================================================= + +BRepGraph_TransientCache::CacheSlot& BRepGraph_TransientCache::changeSlot( + const BRepGraph_NodeId theNode, + const int theKindSlot) +{ + ensureKind(theKindSlot); + const int aKindIdx = static_cast(theNode.NodeKind); + NCollection_Vector& aVec = + myKinds.ChangeValue(theKindSlot).myNodeKinds[aKindIdx].mySlots; + if (theNode.Index >= aVec.Length()) + { + return aVec.SetValue(theNode.Index, CacheSlot()); + } + return aVec.ChangeValue(theNode.Index); +} + +//================================================================================================= + +const BRepGraph_TransientCache::CacheSlot* BRepGraph_TransientCache::seekSlot( + const BRepGraph_NodeId theNode, + const int theKindSlot) const +{ + if (theKindSlot < 0 || theKindSlot >= myKinds.Length()) + { + return nullptr; + } + + const int aKindIdx = static_cast(theNode.NodeKind); + const NCollection_Vector& aVec = + myKinds.Value(theKindSlot).myNodeKinds[aKindIdx].mySlots; + if (theNode.Index >= aVec.Length()) + { + return nullptr; + } + return &aVec.Value(theNode.Index); +} + +//================================================================================================= + +void BRepGraph_TransientCache::Reserve(const int theKindCount, const int theCounts[THE_KIND_COUNT]) +{ + std::unique_lock aLock(myMutex); + + const int aKindCount = theKindCount > 0 ? theKindCount : 0; + if (aKindCount > 0) + { + ensureKind(aKindCount - 1); + } + + for (int aKindSlot = 0; aKindSlot < aKindCount; ++aKindSlot) + { + CacheKindSlot& aKindSlotData = myKinds.ChangeValue(aKindSlot); + for (int aKindIdx = 0; aKindIdx < THE_KIND_COUNT; ++aKindIdx) + { + const int aCount = theCounts[aKindIdx]; + if (aCount > 0 && aCount > aKindSlotData.myNodeKinds[aKindIdx].mySlots.Length()) + { + aKindSlotData.myNodeKinds[aKindIdx].mySlots.SetValue(aCount - 1, CacheSlot()); + } + } + } + + myIsReserved.store(true, std::memory_order_release); +} + +//================================================================================================= + +void BRepGraph_TransientCache::Set(const BRepGraph_NodeId theNode, + const occ::handle& theKind, + const occ::handle& theValue, + const uint32_t theCurrentSubtreeGen) +{ + if (!theNode.IsValid() || theKind.IsNull()) + { + return; + } + + const int aKindSlot = BRepGraph_CacheKindRegistry::Register(theKind); + if (aKindSlot < 0) + { + return; + } + + Set(theNode, aKindSlot, theValue, theCurrentSubtreeGen); +} + +//================================================================================================= + +void BRepGraph_TransientCache::Set(const BRepGraph_NodeId theNode, + const int theKindSlot, + const occ::handle& theValue, + const uint32_t theCurrentSubtreeGen) +{ + if (!theNode.IsValid() || theKindSlot < 0) + { + return; + } + + if (myIsReserved.load(std::memory_order_acquire) && theKindSlot < myKinds.Length()) + { + const int aKindIdx = static_cast(theNode.NodeKind); + NCollection_Vector& aVec = + myKinds.ChangeValue(theKindSlot).myNodeKinds[aKindIdx].mySlots; + if (theNode.Index < aVec.Length()) + { + CacheSlot& aSlot = aVec.ChangeValue(theNode.Index); + aSlot.Value = theValue; + aSlot.StoredSubtreeGen = theCurrentSubtreeGen; + return; + } + } + + std::unique_lock aLock(myMutex); + CacheSlot& aSlot = changeSlot(theNode, theKindSlot); + aSlot.Value = theValue; + aSlot.StoredSubtreeGen = theCurrentSubtreeGen; +} + +//================================================================================================= + +occ::handle BRepGraph_TransientCache::Get( + const BRepGraph_NodeId theNode, + const occ::handle& theKind, + const uint32_t theCurrentSubtreeGen) const +{ + if (!theNode.IsValid() || theKind.IsNull()) + { + return occ::handle(); + } + + const int aKindSlot = BRepGraph_CacheKindRegistry::FindSlot(theKind->ID()); + if (aKindSlot < 0) + { + return occ::handle(); + } + + return Get(theNode, aKindSlot, theCurrentSubtreeGen); +} + +//================================================================================================= + +occ::handle BRepGraph_TransientCache::Get( + const BRepGraph_NodeId theNode, + const int theKindSlot, + const uint32_t theCurrentSubtreeGen) const +{ + if (!theNode.IsValid() || theKindSlot < 0) + { + return occ::handle(); + } + + if (myIsReserved.load(std::memory_order_acquire) && theKindSlot < myKinds.Length()) + { + const int aKindIdx = static_cast(theNode.NodeKind); + const NCollection_Vector& aVec = + myKinds.Value(theKindSlot).myNodeKinds[aKindIdx].mySlots; + if (theNode.Index < aVec.Length()) + { + const CacheSlot& aSlot = aVec.Value(theNode.Index); + if (aSlot.Value.IsNull()) + { + return occ::handle(); + } + if (aSlot.StoredSubtreeGen != theCurrentSubtreeGen) + { + return occ::handle(); + } + return aSlot.Value; + } + } + + std::shared_lock aLock(myMutex); + const CacheSlot* aSlot = seekSlot(theNode, theKindSlot); + if (aSlot == nullptr || aSlot->Value.IsNull()) + { + return occ::handle(); + } + if (aSlot->StoredSubtreeGen != theCurrentSubtreeGen) + { + return occ::handle(); + } + return aSlot->Value; +} + +//================================================================================================= + +bool BRepGraph_TransientCache::Remove(const BRepGraph_NodeId theNode, + const occ::handle& theKind) +{ + if (!theNode.IsValid() || theKind.IsNull()) + { + return false; + } + + const int aKindSlot = BRepGraph_CacheKindRegistry::FindSlot(theKind->ID()); + if (aKindSlot < 0) + { + return false; + } + + return Remove(theNode, aKindSlot); +} + +//================================================================================================= + +bool BRepGraph_TransientCache::Remove(const BRepGraph_NodeId theNode, const int theKindSlot) +{ + if (!theNode.IsValid() || theKindSlot < 0) + { + return false; + } + + std::unique_lock aLock(myMutex); + if (theKindSlot >= myKinds.Length()) + { + return false; + } + + const int aKindIdx = static_cast(theNode.NodeKind); + NCollection_Vector& aVec = + myKinds.ChangeValue(theKindSlot).myNodeKinds[aKindIdx].mySlots; + if (theNode.Index >= aVec.Length()) + { + return false; + } + + CacheSlot& aSlot = aVec.ChangeValue(theNode.Index); + if (aSlot.Value.IsNull()) + { + return false; + } + aSlot.Value.Nullify(); + aSlot.StoredSubtreeGen = 0; + return true; +} + +//================================================================================================= + +bool BRepGraph_TransientCache::HasCacheValues(const BRepGraph_NodeId theNode) const +{ + if (!theNode.IsValid()) + { + return false; + } + + for (int aKindSlot = 0; aKindSlot < myKinds.Length(); ++aKindSlot) + { + const CacheSlot* aSlot = seekSlot(theNode, aKindSlot); + if (aSlot != nullptr && !aSlot->Value.IsNull()) + { + return true; + } + } + return false; +} + +//================================================================================================= + +NCollection_Vector> BRepGraph_TransientCache::CacheKinds( + const BRepGraph_NodeId theNode) const +{ + NCollection_Vector> aKinds; + if (!theNode.IsValid()) + { + return aKinds; + } + + for (int aKindSlot = 0; aKindSlot < myKinds.Length(); ++aKindSlot) + { + const CacheSlot* aSlot = seekSlot(theNode, aKindSlot); + if (aSlot != nullptr && !aSlot->Value.IsNull()) + { + const occ::handle aKind = + BRepGraph_CacheKindRegistry::FindKind(aKindSlot); + if (!aKind.IsNull()) + { + aKinds.Append(aKind); + } + } + } + return aKinds; +} + +//================================================================================================= + +void BRepGraph_TransientCache::TransferCacheValues(const BRepGraph_TransientCache& theSrcCache, + const BRepGraph_NodeId theSrcNode, + const BRepGraph_NodeId theDstNode, + const uint32_t theDstSubtreeGen) +{ + if (!theSrcNode.IsValid() || !theDstNode.IsValid()) + { + return; + } + + for (int aKindSlot = 0; aKindSlot < theSrcCache.myKinds.Length(); ++aKindSlot) + { + const CacheSlot* aSrcSlot = theSrcCache.seekSlot(theSrcNode, aKindSlot); + if (aSrcSlot == nullptr || aSrcSlot->Value.IsNull()) + { + continue; + } + + std::unique_lock aLock(myMutex); + CacheSlot& aDstSlot = changeSlot(theDstNode, aKindSlot); + aDstSlot.Value = aSrcSlot->Value; + aDstSlot.StoredSubtreeGen = theDstSubtreeGen; + } +} + +//================================================================================================= + +void BRepGraph_TransientCache::Clear() noexcept +{ + myKinds.Clear(); + myIsReserved.store(false, std::memory_order_relaxed); +} diff --git a/src/ModelingData/TKBRep/BRepGraph/BRepGraph_TransientCache.hxx b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_TransientCache.hxx new file mode 100644 index 0000000000..cb962b2d4b --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_TransientCache.hxx @@ -0,0 +1,364 @@ +// Copyright (c) 2026 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 _BRepGraph_TransientCache_HeaderFile +#define _BRepGraph_TransientCache_HeaderFile + +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +//! @brief Descriptor of one transient cache family. +//! +//! A cache kind defines stable public identity for one class of transient, +//! recomputable per-node data such as bounding boxes or UV bounds. +//! Instances are process-global descriptors registered in +//! BRepGraph_CacheKindRegistry and referenced from graphs by dense runtime slots. +class BRepGraph_CacheKind : public Standard_Transient +{ +public: + //! Create a cache kind descriptor. + //! @param[in] theID stable public GUID identity + //! @param[in] theName display-only name + //! @param[in] theNodeKindsMask optional node-kind applicability mask; + //! 0 means "unspecified / unrestricted" + Standard_EXPORT BRepGraph_CacheKind( + const Standard_GUID& theID, + const TCollection_AsciiString& theName = TCollection_AsciiString(), + const int theNodeKindsMask = 0); + + //! Stable public identity. + [[nodiscard]] const Standard_GUID& ID() const { return myID; } + + //! Display-only metadata. + [[nodiscard]] const TCollection_AsciiString& Name() const { return myName; } + + //! Optional node-kind applicability mask. + [[nodiscard]] int NodeKindsMask() const { return myNodeKindsMask; } + + //! True if this cache kind is applicable to the given node kind. + //! Cache kinds with NodeKindsMask() == 0 are treated as unrestricted. + [[nodiscard]] bool SupportsNodeKind(const BRepGraph_NodeId::Kind theKind) const + { + return myNodeKindsMask == 0 || (myNodeKindsMask & KindBit(theKind)) != 0; + } + + //! Convenience: bitmask bit for a given node kind. + static int KindBit(const BRepGraph_NodeId::Kind theKind) + { + return 1 << static_cast(theKind); + } + + DEFINE_STANDARD_RTTIEXT(BRepGraph_CacheKind, Standard_Transient) + +private: + Standard_GUID myID; + TCollection_AsciiString myName; + int myNodeKindsMask = 0; +}; + +//! @brief Process-global registry of cache kind descriptors. +//! +//! Maps stable GUID identity to dense runtime slot index. Slot indices are an +//! internal storage detail used by BRepGraph_TransientCache for O(1) indexing. +//! The registry is shared across all BRepGraph instances in the current process, +//! so cache-kind GUIDs should be globally unique. +class BRepGraph_CacheKindRegistry +{ +public: + //! Register a cache kind descriptor. + //! Idempotent: the same GUID always yields the same slot. + //! Slot assignment is process-global and graph-instance independent. + //! @return dense runtime slot, or -1 for null input + [[nodiscard]] Standard_EXPORT static int Register( + const occ::handle& theKind); + + //! Find slot by GUID. Returns -1 if not found. + [[nodiscard]] Standard_EXPORT static int FindSlot(const Standard_GUID& theGUID); + + //! Find slot by GUID. + //! @param[out] theSlot dense runtime slot if found + //! @return true if the GUID is registered + Standard_EXPORT static bool FindSlot(const Standard_GUID& theGUID, int& theSlot); + + //! Find descriptor by GUID. + [[nodiscard]] Standard_EXPORT static occ::handle FindKind( + const Standard_GUID& theGUID); + + //! Find descriptor by slot. + [[nodiscard]] Standard_EXPORT static occ::handle FindKind(const int theSlot); + + //! Check whether a GUID is registered. + [[nodiscard]] Standard_EXPORT static bool Contains(const Standard_GUID& theGUID); + + //! Check whether a slot is registered. + [[nodiscard]] Standard_EXPORT static bool Contains(const int theSlot); + + //! Number of registered cache kinds. + [[nodiscard]] Standard_EXPORT static int NbRegistered(); + +private: + BRepGraph_CacheKindRegistry() = delete; +}; + +//! @brief Abstract base for transient per-node cache values. +//! +//! Inherits from Standard_Transient and is stored via +//! occ::handle. This uses OCCT's embedded refcount and is +//! consistent with the Handle pattern used throughout the codebase. +class BRepGraph_CacheValue : public Standard_Transient +{ +public: + //! Mark the cached value as needing recomputation. Lock-free. + void Invalidate() { myDirty.store(true, std::memory_order_release); } + + //! True if the cached value needs recomputation. + bool IsDirty() const { return myDirty.load(std::memory_order_acquire); } + + DEFINE_STANDARD_RTTI_INLINE(BRepGraph_CacheValue, Standard_Transient) + +protected: + BRepGraph_CacheValue() + : myDirty(true) + { + } + + //! Subclass calls after successful computation to clear the dirty flag. + void MarkClean() const { myDirty.store(false, std::memory_order_release); } + + //! Mutex for thread-safe Get() in subclasses. + mutable std::shared_mutex myMutex; + +private: + mutable std::atomic myDirty; +}; + +//! @brief Concrete typed wrapper for a lazily-computed per-node value. +//! +//! @tparam T cached value type (for example double). +template +class BRepGraph_TypedCacheValue : public BRepGraph_CacheValue +{ +public: + BRepGraph_TypedCacheValue() = default; + + //! Construct with an initial value (marked clean). + explicit BRepGraph_TypedCacheValue(const T& theInitial) + : myValue(theInitial) + { + MarkClean(); + } + + //! Get the cached value, computing via theComputer if dirty. + //! Thread-safe: uses the base class shared_mutex. + T Get(const std::function& theComputer) const + { + if (!IsDirty()) + { + std::shared_lock aLock(myMutex); + if (!IsDirty()) + { + return myValue; + } + } + + std::unique_lock aLock(myMutex); + if (IsDirty()) + { + myValue = theComputer(); + MarkClean(); + } + return myValue; + } + + //! Direct write - stores the value and marks clean. + void Set(const T& theValue) + { + std::unique_lock aLock(myMutex); + myValue = theValue; + MarkClean(); + } + + //! Direct read. Caller must guarantee freshness. + const T& UncheckedValue() const { return myValue; } + +private: + mutable T myValue{}; +}; + +//! @brief Centralized transient cache for algorithm-computed per-node values. +//! +//! Stores short-lived cached data (BndBox, UVBounds, etc.) in dense per-cache-kind +//! vectors indexed by entity index. O(1) access by direct indexing - no hashing. +//! +//! ## SubtreeGen-based freshness +//! Each stored slot records SubtreeGen at write time. On read, if stored +//! SubtreeGen differs from entity's current SubtreeGen the cached value is +//! considered stale - the caller decides how to handle it. +//! +//! ## Lifecycle +//! NOT a Layer. Cleared on Build() and Compact(). No OnNodeRemoved handling - +//! stale data is auto-detected by SubtreeGen mismatch. +//! +//! ## Thread safety +//! After Reserve(), Get() and Set() for in-range indices bypass the mutex +//! entirely - safe because parallel algorithms access different entity slots. +//! Out-of-range access (entities added after Build) falls back to mutex. +class BRepGraph_TransientCache +{ +public: + //! Number of Kind enum slots to cover (0..11, with gap at 9). + static constexpr int THE_KIND_COUNT = BRepGraph_NodeId::THE_KIND_COUNT; + + //! Default number of cache-kind slots reserved after Build(). + static constexpr int THE_DEFAULT_RESERVED_KIND_COUNT = 16; + + //! Per-slot storage: cached value handle + SubtreeGen stamp. + struct CacheSlot + { + occ::handle Value; + uint32_t StoredSubtreeGen = 0; + }; + + //! Store a cached value for a node and cache kind. + //! @pre Reserve() must have been called for lock-free parallel access + //! on in-range entity indices; out-of-range access falls back to mutex. + Standard_EXPORT void Set(const BRepGraph_NodeId theNode, + const occ::handle& theKind, + const occ::handle& theValue, + const uint32_t theCurrentSubtreeGen); + + //! Store a cached value using a pre-resolved kind slot index. + //! Bypasses BRepGraph_CacheKindRegistry lookup - use in hot parallel paths. + //! @param[in] theKindSlot slot from BRepGraph_CacheKindRegistry::Register() + Standard_EXPORT void Set(const BRepGraph_NodeId theNode, + const int theKindSlot, + const occ::handle& theValue, + const uint32_t theCurrentSubtreeGen); + + //! Retrieve a cached value for a node and cache kind. + //! @pre Reserve() must have been called for lock-free parallel access + //! on in-range entity indices; out-of-range access falls back to mutex. + [[nodiscard]] Standard_EXPORT occ::handle Get( + const BRepGraph_NodeId theNode, + const occ::handle& theKind, + const uint32_t theCurrentSubtreeGen) const; + + //! Retrieve a cached value using a pre-resolved kind slot index. + //! Bypasses BRepGraph_CacheKindRegistry lookup - use in hot parallel paths. + //! @param[in] theKindSlot slot from BRepGraph_CacheKindRegistry::Register() + [[nodiscard]] Standard_EXPORT occ::handle Get( + const BRepGraph_NodeId theNode, + const int theKindSlot, + const uint32_t theCurrentSubtreeGen) const; + + //! Remove a cached value for a node and cache kind. + [[nodiscard]] Standard_EXPORT bool Remove(const BRepGraph_NodeId theNode, + const occ::handle& theKind); + + //! Remove a cached value using a pre-resolved cache-kind slot. + [[nodiscard]] Standard_EXPORT bool Remove(const BRepGraph_NodeId theNode, const int theKindSlot); + + //! True if any cached values are stored for this node (any cache kind). + [[nodiscard]] Standard_EXPORT bool HasCacheValues(const BRepGraph_NodeId theNode) const; + + //! Return all registered cache kinds that have non-null entries for this node. + [[nodiscard]] Standard_EXPORT NCollection_Vector> CacheKinds( + const BRepGraph_NodeId theNode) const; + + //! Transfer all cached values from a source cache to a destination node. + Standard_EXPORT void TransferCacheValues(const BRepGraph_TransientCache& theSrcCache, + const BRepGraph_NodeId theSrcNode, + const BRepGraph_NodeId theDstNode, + const uint32_t theDstSubtreeGen); + + //! Pre-allocate storage for lock-free parallel access. + Standard_EXPORT void Reserve(const int theKindCount, const int theCounts[THE_KIND_COUNT]); + + //! True if Reserve() has been called and storage is pre-allocated. + [[nodiscard]] bool IsReserved() const noexcept + { + return myIsReserved.load(std::memory_order_acquire); + } + + //! Clear all cached data. Called on Build() and Compact(). + Standard_EXPORT void Clear() noexcept; + + //! Move constructor: transfers data, creates fresh mutex. + BRepGraph_TransientCache(BRepGraph_TransientCache&& theOther) noexcept + : myKinds(std::move(theOther.myKinds)), + myIsReserved(theOther.myIsReserved.load(std::memory_order_relaxed)) + { + theOther.myIsReserved.store(false, std::memory_order_relaxed); + } + + //! Move assignment: transfers data, mutex stays local. + BRepGraph_TransientCache& operator=(BRepGraph_TransientCache&& theOther) noexcept + { + if (this != &theOther) + { + myKinds = std::move(theOther.myKinds); + myIsReserved.store(theOther.myIsReserved.load(std::memory_order_relaxed), + std::memory_order_relaxed); + theOther.myIsReserved.store(false, std::memory_order_relaxed); + } + return *this; + } + + BRepGraph_TransientCache() = default; + BRepGraph_TransientCache(const BRepGraph_TransientCache&) = delete; + BRepGraph_TransientCache& operator=(const BRepGraph_TransientCache&) = delete; + +private: + //! Per-node-kind dense vector of cache slots. + struct NodeKindStore + { + NCollection_Vector mySlots; + }; + + //! Per-cache-kind storage: one node-kind store per entity kind. + struct CacheKindSlot + { + NodeKindStore myNodeKinds[THE_KIND_COUNT]; + }; + + //! Ensure myKinds has capacity for the given cache-kind slot. + void ensureKind(const int theKindSlot); + + //! Access slot (mutable) - grows vector if needed. + CacheSlot& changeSlot(const BRepGraph_NodeId theNode, const int theKindSlot); + + //! Access slot (const) - returns nullptr if out of range. + const CacheSlot* seekSlot(const BRepGraph_NodeId theNode, const int theKindSlot) const; + + //! Outer vector indexed by cache-kind slot. + NCollection_Vector myKinds; + + //! True after Reserve() - enables lock-free access for in-range slots. + std::atomic myIsReserved{false}; + + //! Protects structural modifications (vector growth) during concurrent access. + mutable std::shared_mutex myMutex; +}; + +#endif // _BRepGraph_TransientCache_HeaderFile diff --git a/src/ModelingData/TKBRep/BRepGraph/BRepGraph_UID.hxx b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_UID.hxx new file mode 100644 index 0000000000..dd03b5eb32 --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_UID.hxx @@ -0,0 +1,123 @@ +// Copyright (c) 2026 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 _BRepGraph_UID_HeaderFile +#define _BRepGraph_UID_HeaderFile + +#include + +#include +#include +#include + +//! Unique node identifier within a BRepGraph. +//! +//! Identity = (Kind, Counter). Two nodes of different kinds may share a +//! counter value but their UIDs are distinct. Within one kind, counter +//! values never repeat (monotonic, never resets). +//! +//! Generation is NOT part of identity; it indicates which Build() cycle +//! produced this UID (for stale-reference detection). +//! +//! Trivially copyable, cheap to pass by value. +//! +//! ## Serialization Contract +//! +//! Entity UIDs (BRepGraph_UID) and reference UIDs (BRepGraph_RefUID) share +//! a single monotonic counter (BRepGraph_Data::myNextUIDCounter). +//! To persist a BRepGraph across sessions: +//! 1. Write: for each entity, serialize (Kind, Counter, OwnGen). +//! 2. Read: reconstruct entities, populate UID vectors with deserialized +//! (Kind, Counter) values, set myNextUIDCounter to +//! max(all_entity_counters, all_ref_counters) + 1. +//! 3. myGeneration resets to 0 on load (session-scoped). +//! 4. VersionStamps from a previous session will correctly detect staleness +//! via Generation mismatch. +struct BRepGraph_UID +{ + //! Default: invalid UID (counter = 0 is the invalid sentinel). + BRepGraph_UID() + : myCounter(0), + myKind(BRepGraph_NodeId::Kind::Solid), + myGeneration(0) + { + } + + //! Construct a valid UID. Called internally by BRepGraph::allocateUID(). + //! @pre theCounter > 0 (counter = 0 is reserved as the invalid sentinel) + BRepGraph_UID(const BRepGraph_NodeId::Kind theKind, + const size_t theCounter, + const uint32_t theGeneration) + : myCounter(theCounter), + myKind(theKind), + myGeneration(theGeneration) + { + Standard_ASSERT_VOID(theCounter > 0, "BRepGraph_UID: counter must be > 0 for valid UIDs"); + } + + //! Factory: returns an explicitly invalid UID. + static BRepGraph_UID Invalid() { return BRepGraph_UID(); } + + [[nodiscard]] bool IsValid() const { return myCounter > 0; } + + [[nodiscard]] BRepGraph_NodeId::Kind Kind() const { return myKind; } + + [[nodiscard]] size_t Counter() const { return myCounter; } + + [[nodiscard]] uint32_t Generation() const { return myGeneration; } + + [[nodiscard]] bool IsTopology() const { return BRepGraph_NodeId::IsTopologyKind(myKind); } + + [[nodiscard]] bool IsAssembly() const { return BRepGraph_NodeId::IsAssemblyKind(myKind); } + + //! Equality: Identity = (Kind, Counter). Generation excluded. + //! Two invalid UIDs are equal. + bool operator==(const BRepGraph_UID& theOther) const + { + if (myCounter == 0 || theOther.myCounter == 0) + return (myCounter == 0) == (theOther.myCounter == 0); + return myKind == theOther.myKind && myCounter == theOther.myCounter; + } + + bool operator!=(const BRepGraph_UID& theOther) const { return !(*this == theOther); } + + bool operator<(const BRepGraph_UID& theOther) const + { + if (myKind != theOther.myKind) + return static_cast(myKind) < static_cast(theOther.myKind); + return myCounter < theOther.myCounter; + } + + //! Hash value: f(Kind, Counter). + [[nodiscard]] size_t HashValue() const + { + size_t aCombination[2]; + aCombination[0] = opencascade::hash(static_cast(myKind)); + aCombination[1] = opencascade::hash(myCounter); + return opencascade::hashBytes(aCombination, sizeof(aCombination)); + } + +private: + size_t myCounter; //!< 0 = invalid sentinel; valid counters start at 1. + BRepGraph_NodeId::Kind myKind; //!< Node kind. + uint32_t myGeneration; //!< Build() cycle that produced this UID. +}; + +//! std::hash specialization for NCollection_DefaultHasher support. +template <> +struct std::hash +{ + size_t operator()(const BRepGraph_UID& theUID) const noexcept { return theUID.HashValue(); } +}; + +#endif // _BRepGraph_UID_HeaderFile diff --git a/src/ModelingData/TKBRep/BRepGraph/BRepGraph_UIDsView.cxx b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_UIDsView.cxx new file mode 100644 index 0000000000..a199d11c13 --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_UIDsView.cxx @@ -0,0 +1,309 @@ +// Copyright (c) 2026 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 + +namespace +{ + +//================================================================================================= + +void appendRefUIDReverseIndex(BRepGraph_Data& theData, const BRepGraph_RefId::Kind theKind) +{ + const NCollection_Vector& aUIDs = theData.myIncStorage.RefUIDs(theKind); + const int aNbUIDs = aUIDs.Length(); + for (BRepGraph_RefId aRefId(theKind, 0); aRefId.IsValid(aNbUIDs); ++aRefId) + { + const BRepGraph_RefUID aUID = aUIDs.Value(aRefId.Index); + if (aUID.IsValid()) + { + theData.myRefUIDToRefId.Bind(aUID, aRefId); + } + } +} + +//================================================================================================= + +void appendUIDReverseIndex(BRepGraph_Data& theData, const BRepGraph_NodeId::Kind theKind) +{ + const NCollection_Vector& aUIDs = theData.myIncStorage.UIDs(theKind); + const int aNbUIDs = aUIDs.Length(); + for (BRepGraph_NodeId aNodeId(theKind, 0); aNodeId.IsValid(aNbUIDs); ++aNodeId) + { + const BRepGraph_UID aUID = aUIDs.Value(aNodeId.Index); + if (aUID.IsValid()) + { + theData.myUIDToNodeId.Bind(aUID, aNodeId); + } + } +} + +//================================================================================================= + +void ensureUIDReverseIndex(BRepGraph_Data& theData) +{ + const uint32_t aGeneration = theData.myGeneration.load(); + { + std::shared_lock aReadLock(theData.myUIDToNodeIdMutex); + if (!theData.myUIDToNodeIdDirty && theData.myUIDToNodeIdGeneration == aGeneration) + { + return; + } + } + + std::unique_lock aWriteLock(theData.myUIDToNodeIdMutex); + if (!theData.myUIDToNodeIdDirty && theData.myUIDToNodeIdGeneration == aGeneration) + { + return; + } + + theData.myUIDToNodeId.Clear(); + appendUIDReverseIndex(theData, BRepGraph_NodeId::Kind::Vertex); + appendUIDReverseIndex(theData, BRepGraph_NodeId::Kind::Edge); + appendUIDReverseIndex(theData, BRepGraph_NodeId::Kind::CoEdge); + appendUIDReverseIndex(theData, BRepGraph_NodeId::Kind::Wire); + appendUIDReverseIndex(theData, BRepGraph_NodeId::Kind::Face); + appendUIDReverseIndex(theData, BRepGraph_NodeId::Kind::Shell); + appendUIDReverseIndex(theData, BRepGraph_NodeId::Kind::Solid); + appendUIDReverseIndex(theData, BRepGraph_NodeId::Kind::Compound); + appendUIDReverseIndex(theData, BRepGraph_NodeId::Kind::CompSolid); + appendUIDReverseIndex(theData, BRepGraph_NodeId::Kind::Product); + appendUIDReverseIndex(theData, BRepGraph_NodeId::Kind::Occurrence); + theData.myUIDToNodeIdGeneration = aGeneration; + theData.myUIDToNodeIdDirty = false; +} + +//================================================================================================= + +void ensureRefUIDReverseIndex(BRepGraph_Data& theData) +{ + const uint32_t aGeneration = theData.myGeneration.load(); + { + std::shared_lock aReadLock(theData.myRefUIDToRefIdMutex); + if (!theData.myRefUIDToRefIdDirty && theData.myRefUIDToRefIdGeneration == aGeneration) + { + return; + } + } + + std::unique_lock aWriteLock(theData.myRefUIDToRefIdMutex); + if (!theData.myRefUIDToRefIdDirty && theData.myRefUIDToRefIdGeneration == aGeneration) + { + return; + } + + theData.myRefUIDToRefId.Clear(); + appendRefUIDReverseIndex(theData, BRepGraph_RefId::Kind::Shell); + appendRefUIDReverseIndex(theData, BRepGraph_RefId::Kind::Face); + appendRefUIDReverseIndex(theData, BRepGraph_RefId::Kind::Wire); + appendRefUIDReverseIndex(theData, BRepGraph_RefId::Kind::CoEdge); + appendRefUIDReverseIndex(theData, BRepGraph_RefId::Kind::Vertex); + appendRefUIDReverseIndex(theData, BRepGraph_RefId::Kind::Solid); + appendRefUIDReverseIndex(theData, BRepGraph_RefId::Kind::Child); + appendRefUIDReverseIndex(theData, BRepGraph_RefId::Kind::Occurrence); + theData.myRefUIDToRefIdGeneration = aGeneration; + theData.myRefUIDToRefIdDirty = false; +} + +} // namespace + +//================================================================================================= + +BRepGraph_UID BRepGraph::UIDsView::Of(const BRepGraph_NodeId theNode) const +{ + if (!theNode.IsValid()) + return BRepGraph_UID(); + + const NCollection_Vector& aVec = + myGraph->myData->myIncStorage.UIDs(theNode.NodeKind); + if (theNode.Index >= aVec.Length()) + return BRepGraph_UID(); + return aVec.Value(theNode.Index); +} + +//================================================================================================= + +BRepGraph_RefUID BRepGraph::UIDsView::Of(const BRepGraph_RefId theRefId) const +{ + if (!theRefId.IsValid()) + return BRepGraph_RefUID(); + + const NCollection_Vector& aVec = + myGraph->myData->myIncStorage.RefUIDs(theRefId.RefKind); + if (theRefId.Index >= aVec.Length()) + return BRepGraph_RefUID(); + return aVec.Value(theRefId.Index); +} + +//================================================================================================= + +BRepGraph_NodeId BRepGraph::UIDsView::NodeIdFrom(const BRepGraph_UID& theUID) const +{ + if (!theUID.IsValid()) + return BRepGraph_NodeId(); + if (theUID.Generation() != myGraph->myData->myGeneration.load()) + return BRepGraph_NodeId(); + + BRepGraph_Data& aData = *myGraph->myData; + ensureUIDReverseIndex(aData); + + std::shared_lock aReadLock(aData.myUIDToNodeIdMutex); + const BRepGraph_NodeId* aNodeId = aData.myUIDToNodeId.Seek(theUID); + return aNodeId != nullptr ? *aNodeId : BRepGraph_NodeId(); +} + +//================================================================================================= + +BRepGraph_RefId BRepGraph::UIDsView::RefIdFrom(const BRepGraph_RefUID& theUID) const +{ + if (!theUID.IsValid()) + return BRepGraph_RefId(); + if (theUID.Generation() != myGraph->myData->myGeneration.load()) + return BRepGraph_RefId(); + + BRepGraph_Data& aData = *myGraph->myData; + ensureRefUIDReverseIndex(aData); + + std::shared_lock aReadLock(aData.myRefUIDToRefIdMutex); + const BRepGraph_RefId* aRefId = aData.myRefUIDToRefId.Seek(theUID); + return aRefId != nullptr ? *aRefId : BRepGraph_RefId(); +} + +//================================================================================================= + +bool BRepGraph::UIDsView::Has(const BRepGraph_UID& theUID) const +{ + if (!theUID.IsValid()) + return false; + if (theUID.Generation() != myGraph->myData->myGeneration.load()) + return false; + + BRepGraph_Data& aData = *myGraph->myData; + ensureUIDReverseIndex(aData); + + std::shared_lock aReadLock(aData.myUIDToNodeIdMutex); + return aData.myUIDToNodeId.Seek(theUID) != nullptr; +} + +//================================================================================================= + +bool BRepGraph::UIDsView::Has(const BRepGraph_RefUID& theUID) const +{ + if (!theUID.IsValid()) + return false; + if (theUID.Generation() != myGraph->myData->myGeneration.load()) + return false; + + BRepGraph_Data& aData = *myGraph->myData; + ensureRefUIDReverseIndex(aData); + + std::shared_lock aReadLock(aData.myRefUIDToRefIdMutex); + return aData.myRefUIDToRefId.Seek(theUID) != nullptr; +} + +//================================================================================================= + +uint32_t BRepGraph::UIDsView::Generation() const +{ + return myGraph->myData->myGeneration.load(); +} + +//================================================================================================= + +const Standard_GUID& BRepGraph::UIDsView::GraphGUID() const +{ + return myGraph->myData->myGraphGUID; +} + +//================================================================================================= + +BRepGraph_VersionStamp BRepGraph::UIDsView::StampOf(const BRepGraph_NodeId theNode) const +{ + if (!theNode.IsValid()) + return BRepGraph_VersionStamp(); + + const NCollection_Vector& aVec = + myGraph->myData->myIncStorage.UIDs(theNode.NodeKind); + if (theNode.Index >= aVec.Length()) + return BRepGraph_VersionStamp(); + + const BRepGraph_UID aUID = aVec.Value(theNode.Index); + const BRepGraphInc::BaseDef* aDef = myGraph->topoEntity(theNode); + if (aDef == nullptr || aDef->IsRemoved) + return BRepGraph_VersionStamp(); + + return BRepGraph_VersionStamp(aUID, aDef->OwnGen, myGraph->myData->myGeneration.load()); +} + +//================================================================================================= + +BRepGraph_VersionStamp BRepGraph::UIDsView::StampOf(const BRepGraph_RefId theRefId) const +{ + if (!theRefId.IsValid()) + return BRepGraph_VersionStamp(); + + const NCollection_Vector& aUIDs = + myGraph->myData->myIncStorage.RefUIDs(theRefId.RefKind); + if (theRefId.Index >= aUIDs.Length()) + return BRepGraph_VersionStamp(); + + const BRepGraphInc::BaseRef& aBase = myGraph->myData->myIncStorage.BaseRef(theRefId); + if (aBase.IsRemoved) + return BRepGraph_VersionStamp(); + + return BRepGraph_VersionStamp(aUIDs.Value(theRefId.Index), + aBase.OwnGen, + myGraph->myData->myGeneration.load()); +} + +//================================================================================================= + +bool BRepGraph::UIDsView::IsStale(const BRepGraph_VersionStamp& theStamp) const +{ + if (!theStamp.IsValid()) + return true; + + if (theStamp.myGeneration != myGraph->myData->myGeneration.load()) + return true; + + if (theStamp.IsEntityStamp()) + { + const BRepGraph_NodeId aNodeId = NodeIdFrom(theStamp.myUID); + if (!aNodeId.IsValid()) + return true; + + const BRepGraphInc::BaseDef* aDef = myGraph->topoEntity(aNodeId); + if (aDef == nullptr || aDef->IsRemoved) + return true; + + return aDef->OwnGen != theStamp.myMutationGen; + } + + if (theStamp.IsRefStamp()) + { + const BRepGraph_RefId aRefId = RefIdFrom(theStamp.myRefUID); + if (!aRefId.IsValid()) + return true; + + const BRepGraphInc::BaseRef& aRef = myGraph->myData->myIncStorage.BaseRef(aRefId); + if (aRef.IsRemoved) + return true; + + return aRef.OwnGen != theStamp.myMutationGen; + } + + return true; +} diff --git a/src/ModelingData/TKBRep/BRepGraph/BRepGraph_UIDsView.hxx b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_UIDsView.hxx new file mode 100644 index 0000000000..999439e427 --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_UIDsView.hxx @@ -0,0 +1,107 @@ +// Copyright (c) 2026 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 _BRepGraph_UIDsView_HeaderFile +#define _BRepGraph_UIDsView_HeaderFile + +#include +#include + +class Standard_GUID; + +//! @brief Read-only view for persistent unique identifiers. +//! +//! UIDs are (Kind, Counter) pairs that persist across graph mutations +//! (Compact, node removal). Each UID is assigned exactly once and never +//! reused. Counters are monotonic and independent of vector indices, +//! so UIDs survive Compact() index remapping. Only Build() resets +//! counters (new generation). The Generation field enables stale-reference +//! detection when a graph is rebuilt. +//! Provides bidirectional NodeId/UID resolution. +//! Obtained via BRepGraph::UIDs(). +class BRepGraph::UIDsView +{ +public: + //! Return the UID assigned to a node. + //! @param[in] theNode node identifier + //! @return UID for the node, or invalid UID if theNode is out of bounds + [[nodiscard]] Standard_EXPORT BRepGraph_UID Of(const BRepGraph_NodeId theNode) const; + + //! Return the RefUID assigned to a reference. + //! @param[in] theRefId reference identifier + //! @return RefUID for the reference, or invalid RefUID if theRefId is out of bounds + [[nodiscard]] Standard_EXPORT BRepGraph_RefUID Of(const BRepGraph_RefId theRefId) const; + + //! Resolve a UID back to a NodeId using the internal reverse index. + //! @param[in] theUID unique identifier to resolve + //! @return corresponding NodeId, or invalid NodeId if not found + [[nodiscard]] Standard_EXPORT BRepGraph_NodeId NodeIdFrom(const BRepGraph_UID& theUID) const; + + //! Resolve a RefUID back to a RefId using the internal reverse index. + //! @param[in] theUID unique reference identifier to resolve + //! @return corresponding RefId, or invalid RefId if not found + [[nodiscard]] Standard_EXPORT BRepGraph_RefId RefIdFrom(const BRepGraph_RefUID& theUID) const; + + //! Check if a UID is valid and exists in this graph generation. + //! @param[in] theUID unique identifier to check + //! @return true if the UID belongs to this graph generation + [[nodiscard]] Standard_EXPORT bool Has(const BRepGraph_UID& theUID) const; + + //! Check if a RefUID is valid and exists in this graph generation. + //! @param[in] theUID unique reference identifier to check + //! @return true if the RefUID belongs to this graph generation + [[nodiscard]] Standard_EXPORT bool Has(const BRepGraph_RefUID& theUID) const; + + //! Return the current generation counter (incremented on each Build). + //! @return graph generation number + [[nodiscard]] Standard_EXPORT uint32_t Generation() const; + + //! Return the graph-level identity GUID. + //! Generated randomly at Build() time; changes on each rebuild. + //! @return reference to the graph identity GUID + [[nodiscard]] Standard_EXPORT const Standard_GUID& GraphGUID() const; + + //! Produce a version stamp for the given node. + //! Combines the node's UID with its current OwnGen and graph Generation. + //! @param[in] theNode node identifier + //! @return version stamp, or invalid stamp if theNode is invalid, removed, or out of bounds + [[nodiscard]] Standard_EXPORT BRepGraph_VersionStamp + StampOf(const BRepGraph_NodeId theNode) const; + + //! Produce a version stamp for the given reference. + //! Combines the reference's RefUID with its current OwnGen and graph Generation. + //! @param[in] theRefId reference identifier + //! @return version stamp, or invalid stamp if theRefId is invalid, removed, or out of bounds + [[nodiscard]] Standard_EXPORT BRepGraph_VersionStamp + StampOf(const BRepGraph_RefId theRefId) const; + + //! Check if a previously-taken stamp is stale. + //! A stamp is stale when the stamped node or reference has been mutated, + //! removed, or the graph was rebuilt since the stamp was taken. + //! @param[in] theStamp version stamp to check + //! @return true if the stamp no longer matches the current graph state + [[nodiscard]] Standard_EXPORT bool IsStale(const BRepGraph_VersionStamp& theStamp) const; + +private: + friend class BRepGraph; + friend struct BRepGraph_Data; + + explicit UIDsView(const BRepGraph* theGraph) + : myGraph(theGraph) + { + } + + const BRepGraph* myGraph; +}; + +#endif // _BRepGraph_UIDsView_HeaderFile diff --git a/src/ModelingData/TKBRep/BRepGraph/BRepGraph_Validate.cxx b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_Validate.cxx new file mode 100644 index 0000000000..5903d6dfea --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_Validate.cxx @@ -0,0 +1,1075 @@ +// Copyright (c) 2026 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 + +namespace +{ + +//! Check that a NodeId refers to a valid index within graph bounds. +bool isValidNodeId(const BRepGraph& theGraph, const BRepGraph_NodeId theId) +{ + if (!theId.IsValid()) + return false; + + switch (theId.NodeKind) + { + case BRepGraph_NodeId::Kind::Solid: + return theId.IsValid(theGraph.Topo().Solids().Nb()); + case BRepGraph_NodeId::Kind::Shell: + return theId.IsValid(theGraph.Topo().Shells().Nb()); + case BRepGraph_NodeId::Kind::Face: + return theId.IsValid(theGraph.Topo().Faces().Nb()); + case BRepGraph_NodeId::Kind::Wire: + return theId.IsValid(theGraph.Topo().Wires().Nb()); + case BRepGraph_NodeId::Kind::CoEdge: + return theId.IsValid(theGraph.Topo().CoEdges().Nb()); + case BRepGraph_NodeId::Kind::Edge: + return theId.IsValid(theGraph.Topo().Edges().Nb()); + case BRepGraph_NodeId::Kind::Vertex: + return theId.IsValid(theGraph.Topo().Vertices().Nb()); + case BRepGraph_NodeId::Kind::Compound: + return theId.IsValid(theGraph.Topo().Compounds().Nb()); + case BRepGraph_NodeId::Kind::CompSolid: + return theId.IsValid(theGraph.Topo().CompSolids().Nb()); + case BRepGraph_NodeId::Kind::Product: + return theId.IsValid(theGraph.Topo().Products().Nb()); + case BRepGraph_NodeId::Kind::Occurrence: + return theId.IsValid(theGraph.Topo().Occurrences().Nb()); + } + return false; +} + +//! Check if a topology def is removed. +bool isEntityRemoved(const BRepGraph& theGraph, BRepGraph_NodeId theId) +{ + const BRepGraphInc::BaseDef* aDef = theGraph.Topo().Gen().TopoEntity(theId); + return aDef != nullptr && aDef->IsRemoved; +} + +//! Convert mutator boundary issues to validator issues. +//! @param[in] theBoundaryIssues boundary issues reported by mutator +//! @param[in,out] theIssues destination validator issue vector +void appendMutationBoundaryIssues( + const NCollection_Vector& theBoundaryIssues, + NCollection_Vector& theIssues) +{ + using Issue = BRepGraph_Validate::Issue; + using Severity = BRepGraph_Validate::Severity; + for (const BRepGraph::BuilderView::BoundaryIssue& aBoundaryIssue : theBoundaryIssues) + { + theIssues.Append(Issue{Severity::Error, aBoundaryIssue.NodeId, aBoundaryIssue.Description}); + } +} + +//! Validate that all cross-reference indices (vertex, edge, wire, face, shell, +//! coedge, compound child refs) point to valid, in-bounds entity slots. +//! @param[in] theGraph source graph +//! @param[in,out] theIssues collection to append diagnostic issues +void checkCrossReferenceBounds(const BRepGraph& theGraph, + NCollection_Vector& theIssues) +{ + using Issue = BRepGraph_Validate::Issue; + using Severity = BRepGraph_Validate::Severity; + + // Check EdgeDef references. + for (BRepGraph_Iterator anEdgeIt(theGraph); anEdgeIt.More(); + anEdgeIt.Next()) + { + const BRepGraphInc::EdgeDef& anEdge = anEdgeIt.Current(); + + // Resolve vertex def ids through vertex ref entries. + if (anEdge.StartVertexRefId.IsValid()) + { + const BRepGraph_NodeId aStartVtxId = + theGraph.Refs().Vertices().Entry(anEdge.StartVertexRefId).VertexDefId; + if (aStartVtxId.IsValid() && !isValidNodeId(theGraph, aStartVtxId)) + { + theIssues.Append(Issue{Severity::Error, + anEdge.Id, + "EdgeDef.StartVertexRefId resolves to out-of-bounds VertexDefId"}); + } + } + if (anEdge.EndVertexRefId.IsValid()) + { + const BRepGraph_NodeId anEndVtxId = + theGraph.Refs().Vertices().Entry(anEdge.EndVertexRefId).VertexDefId; + if (anEndVtxId.IsValid() && !isValidNodeId(theGraph, anEndVtxId)) + { + theIssues.Append(Issue{Severity::Error, + anEdge.Id, + "EdgeDef.EndVertexRefId resolves to out-of-bounds VertexDefId"}); + } + } + } + + // Check CoEdgeDef references. + for (BRepGraph_Iterator aCoEdgeIt(theGraph); aCoEdgeIt.More(); + aCoEdgeIt.Next()) + { + const BRepGraphInc::CoEdgeDef& aCoEdge = aCoEdgeIt.Current(); + const BRepGraph_CoEdgeId aCoEdgeId = aCoEdgeIt.CurrentId(); + + if (aCoEdge.FaceDefId.IsValid() && !isValidNodeId(theGraph, aCoEdge.FaceDefId)) + { + theIssues.Append(Issue{Severity::Error, aCoEdge.Id, "CoEdgeDef.FaceDefId out of bounds"}); + } + const BRepGraph_NodeId anEdgeId = aCoEdge.EdgeDefId; + if (anEdgeId.IsValid() && !isValidNodeId(theGraph, anEdgeId)) + { + theIssues.Append(Issue{Severity::Error, aCoEdge.Id, "CoEdgeDef.EdgeDefId out of bounds"}); + } + // SeamPairId consistency. + if (aCoEdge.SeamPairId.IsValid()) + { + if (!aCoEdge.SeamPairId.IsValid(theGraph.Topo().CoEdges().Nb())) + { + theIssues.Append(Issue{Severity::Error, aCoEdge.Id, "CoEdgeDef.SeamPairId out of bounds"}); + } + else if (aCoEdge.SeamPairId.Index == aCoEdgeId.Index) + { + theIssues.Append( + Issue{Severity::Error, aCoEdge.Id, "CoEdgeDef.SeamPairId is self-reference"}); + } + else + { + const BRepGraphInc::CoEdgeDef& aPair = + theGraph.Topo().CoEdges().Definition(aCoEdge.SeamPairId); + if (aPair.SeamPairId.Index != aCoEdgeId.Index) + { + theIssues.Append( + Issue{Severity::Error, aCoEdge.Id, "CoEdgeDef.SeamPairId not bidirectional"}); + } + if (aPair.FaceDefId != aCoEdge.FaceDefId) + { + theIssues.Append( + Issue{Severity::Error, aCoEdge.Id, "CoEdgeDef seam pair has different FaceDefId"}); + } + } + } + // Rep index bounds. + if (aCoEdge.Curve2DRepId.IsValid() + && !aCoEdge.Curve2DRepId.IsValid(theGraph.Topo().Geometry().NbCurves2D())) + { + theIssues.Append(Issue{Severity::Error, aCoEdge.Id, "CoEdgeDef.Curve2DRepId out of bounds"}); + } + } + + // Surface handles are stored directly on FaceDef; no cross-reference to validate. + + // Check WireDef CoEdgeRef references. + for (BRepGraph_Iterator aWireIt(theGraph); aWireIt.More(); aWireIt.Next()) + { + const BRepGraphInc::WireDef& aWire = aWireIt.Current(); + const BRepGraph_WireId aWireId = aWireIt.CurrentId(); + + for (BRepGraph_RefsCoEdgeOfWire anIt(theGraph, aWireId); anIt.More(); anIt.Next()) + { + const BRepGraphInc::CoEdgeRef& aCR = theGraph.Refs().CoEdges().Entry(anIt.CurrentId()); + const BRepGraph_NodeId aCoEdgeDefId = aCR.CoEdgeDefId; + if (aCoEdgeDefId.IsValid() && !isValidNodeId(theGraph, aCoEdgeDefId)) + { + theIssues.Append( + Issue{Severity::Error, aWire.Id, "WireDef.CoEdgeUsage CoEdgeIdx out of bounds"}); + } + const BRepGraphInc::CoEdgeDef& aCoEdge = + theGraph.Topo().CoEdges().Definition(aCR.CoEdgeDefId); + const BRepGraph_NodeId anEdgeDefId = aCoEdge.EdgeDefId; + if (anEdgeDefId.IsValid() && !isValidNodeId(theGraph, anEdgeDefId)) + { + theIssues.Append( + Issue{Severity::Error, aWire.Id, "WireDef.CoEdgeUsage EdgeIdx out of bounds"}); + } + } + } + + // Check CompoundDef ChildRef references. + for (BRepGraph_Iterator aCompIt(theGraph); aCompIt.More(); + aCompIt.Next()) + { + const BRepGraphInc::CompoundDef& aComp = aCompIt.Current(); + const BRepGraph_CompoundId aCompoundId = aCompIt.CurrentId(); + + for (BRepGraph_RefsChildOfCompound anIt(theGraph, aCompoundId); anIt.More(); anIt.Next()) + { + const BRepGraphInc::ChildRef& aCR = theGraph.Refs().Children().Entry(anIt.CurrentId()); + const BRepGraph_NodeId aChildId = aCR.ChildDefId; + if (aChildId.IsValid() && !isValidNodeId(theGraph, aChildId)) + { + theIssues.Append(Issue{Severity::Error, aComp.Id, "CompoundDef.ChildDefId out of bounds"}); + } + } + } + + // Check CompSolidDef SolidRef references. + for (BRepGraph_Iterator aCSIt(theGraph); aCSIt.More(); aCSIt.Next()) + { + const BRepGraphInc::CompSolidDef& aCS = aCSIt.Current(); + const BRepGraph_CompSolidId aCompSolidId = aCSIt.CurrentId(); + + for (BRepGraph_RefsSolidOfCompSolid anIt(theGraph, aCompSolidId); anIt.More(); anIt.Next()) + { + const BRepGraphInc::SolidRef& aSR = theGraph.Refs().Solids().Entry(anIt.CurrentId()); + const BRepGraph_NodeId aSolidId = aSR.SolidDefId; + if (aSolidId.IsValid() && !isValidNodeId(theGraph, aSolidId)) + { + theIssues.Append(Issue{Severity::Error, aCS.Id, "CompSolidDef.SolidDefId out of bounds"}); + } + } + } + + // Check ShellDef FaceRef references. + for (BRepGraph_Iterator aShellIt(theGraph); aShellIt.More(); + aShellIt.Next()) + { + const BRepGraphInc::ShellDef& aShell = aShellIt.Current(); + const BRepGraph_ShellId aShellId = aShellIt.CurrentId(); + + for (BRepGraph_RefsFaceOfShell anIt(theGraph, aShellId); anIt.More(); anIt.Next()) + { + const BRepGraphInc::FaceRef& aFR = theGraph.Refs().Faces().Entry(anIt.CurrentId()); + const BRepGraph_NodeId aFaceDefId = aFR.FaceDefId; + if (aFaceDefId.IsValid() && !isValidNodeId(theGraph, aFaceDefId)) + { + theIssues.Append( + Issue{Severity::Error, aShell.Id, "ShellDef.FaceUsage FaceIdx out of bounds"}); + } + } + } + + // Check SolidDef ShellRef references. + for (BRepGraph_Iterator aSolidIt(theGraph); aSolidIt.More(); + aSolidIt.Next()) + { + const BRepGraphInc::SolidDef& aSolid = aSolidIt.Current(); + const BRepGraph_SolidId aSolidId = aSolidIt.CurrentId(); + + for (BRepGraph_RefsShellOfSolid anIt(theGraph, aSolidId); anIt.More(); anIt.Next()) + { + const BRepGraphInc::ShellRef& aSR = theGraph.Refs().Shells().Entry(anIt.CurrentId()); + const BRepGraph_NodeId aShellDefId = aSR.ShellDefId; + if (aShellDefId.IsValid() && !isValidNodeId(theGraph, aShellDefId)) + { + theIssues.Append( + Issue{Severity::Error, aSolid.Id, "SolidDef.ShellUsage ShellIdx out of bounds"}); + } + } + } + + // Check ProductDef references. + for (BRepGraph_Iterator aProductIt(theGraph); aProductIt.More(); + aProductIt.Next()) + { + const BRepGraphInc::ProductDef& aProduct = aProductIt.Current(); + const BRepGraph_ProductId aProdId = aProductIt.CurrentId(); + + if (aProduct.ShapeRootId.IsValid()) + { + if (!isValidNodeId(theGraph, aProduct.ShapeRootId)) + { + theIssues.Append( + Issue{Severity::Error, aProduct.Id, "ProductDef.ShapeRootId out of bounds"}); + } + else if (aProduct.ShapeRootId.NodeKind == BRepGraph_NodeId::Kind::CoEdge + || BRepGraph_NodeId::IsAssemblyKind(aProduct.ShapeRootId.NodeKind)) + { + theIssues.Append( + Issue{Severity::Error, aProduct.Id, "ProductDef.ShapeRootId has invalid node kind"}); + } + } + + for (BRepGraph_RefsOccurrenceOfProduct anOccIt(theGraph, aProdId); anOccIt.More(); + anOccIt.Next()) + { + const BRepGraph_OccurrenceId anOccId = + theGraph.Refs().Occurrences().Entry(anOccIt.CurrentId()).OccurrenceDefId; + if (anOccId.IsValid() && !isValidNodeId(theGraph, anOccId)) + { + theIssues.Append( + Issue{Severity::Error, aProduct.Id, "ProductDef.OccurrenceUsage out of bounds"}); + } + } + } + + // Check OccurrenceDef references. + for (BRepGraph_Iterator anOccIt(theGraph); anOccIt.More(); + anOccIt.Next()) + { + const BRepGraphInc::OccurrenceDef& anOcc = anOccIt.Current(); + const BRepGraph_OccurrenceId anOccId = anOccIt.CurrentId(); + + if (!anOcc.ProductDefId.IsValid() || !isValidNodeId(theGraph, anOcc.ProductDefId)) + { + theIssues.Append(Issue{Severity::Error, anOcc.Id, "OccurrenceDef.ProductDefId invalid"}); + } + if (!anOcc.ParentProductDefId.IsValid() || !isValidNodeId(theGraph, anOcc.ParentProductDefId)) + { + theIssues.Append( + Issue{Severity::Error, anOcc.Id, "OccurrenceDef.ParentProductDefId invalid"}); + } + + if (anOcc.ParentOccurrenceDefId.IsValid()) + { + if (!isValidNodeId(theGraph, anOcc.ParentOccurrenceDefId)) + { + theIssues.Append( + Issue{Severity::Error, anOcc.Id, "OccurrenceDef.ParentOccurrenceDefId invalid"}); + } + else if (anOcc.ParentOccurrenceDefId.Index == anOccId.Index) + { + theIssues.Append( + Issue{Severity::Error, anOcc.Id, "OccurrenceDef.ParentOccurrenceDefId self-reference"}); + } + } + } +} + +//! Verify that every forward incidence ref has a matching reverse-index entry +//! (edge->wires, edge->faces, vertex->edges, wire->faces, face->shells, shell->solids). +//! @param[in] theGraph source graph +//! @param[in,out] theIssues collection to append diagnostic issues +void checkReverseIndexConsistency(const BRepGraph& theGraph, + NCollection_Vector& theIssues) +{ + using Issue = BRepGraph_Validate::Issue; + using Severity = BRepGraph_Validate::Severity; + + // Build expected edge->wires mapping from CoEdgeRef scans. + NCollection_DataMap> anExpected; + for (BRepGraph_Iterator aWireIt(theGraph); aWireIt.More(); aWireIt.Next()) + { + const BRepGraph_WireId aWireId = aWireIt.CurrentId(); + + for (BRepGraph_RefsCoEdgeOfWire anIt(theGraph, aWireId); anIt.More(); anIt.Next()) + { + const BRepGraphInc::CoEdgeRef& aCR = theGraph.Refs().CoEdges().Entry(anIt.CurrentId()); + const BRepGraphInc::CoEdgeDef& aCoEdge = + theGraph.Topo().CoEdges().Definition(aCR.CoEdgeDefId); + if (!aCoEdge.EdgeDefId.IsValid()) + continue; + + if (!anExpected.IsBound(aCoEdge.EdgeDefId.Index)) + anExpected.Bind(aCoEdge.EdgeDefId.Index, NCollection_Map()); + anExpected.ChangeFind(aCoEdge.EdgeDefId.Index).Add(aWireId.Index); + } + } + + // Check that Topo WiresOfEdge matches expected. + for (BRepGraph_Iterator anEdgeIt(theGraph); anEdgeIt.More(); + anEdgeIt.Next()) + { + const BRepGraph_EdgeId anEdgeId = anEdgeIt.CurrentId(); + const BRepGraphInc::EdgeDef& anEdge = anEdgeIt.Current(); + + const NCollection_Vector& aActualWires = + theGraph.Topo().Edges().Wires(anEdgeId); + const NCollection_Map* anExpectedWires = anExpected.Seek(anEdgeId.Index); + + const int anExpectedCount = (anExpectedWires != nullptr) ? anExpectedWires->Extent() : 0; + + // Build a set from actual wires for comparison. + NCollection_Map anActualSet; + for (const BRepGraph_WireId& aWireId : aActualWires) + anActualSet.Add(aWireId.Index); + + if (anActualSet.Extent() != anExpectedCount) + { + theIssues.Append( + Issue{Severity::Error, anEdge.Id, "Reverse index ReverseIdx.WiresOfEdge size mismatch"}); + continue; + } + + if (anExpectedWires != nullptr) + { + for (const int aWireIdx : *anExpectedWires) + { + if (!anActualSet.Contains(aWireIdx)) + { + theIssues.Append(Issue{Severity::Error, + anEdge.Id, + "Reverse index ReverseIdx.WiresOfEdge missing wire entry"}); + break; + } + } + } + } +} + +//! Validate consistency of cached edge-face counts in reverse index. +//! @param[in] theGraph source graph +//! @param[in,out] theIssues collection to append diagnostic issues +void checkReverseIndexFaceCountCache(const BRepGraph& theGraph, + NCollection_Vector& theIssues) +{ + using Issue = BRepGraph_Validate::Issue; + using Severity = BRepGraph_Validate::Severity; + const BRepGraph::TopoView& aDefs = theGraph.Topo(); + + for (BRepGraph_Iterator anEdgeIt(theGraph); anEdgeIt.More(); + anEdgeIt.Next()) + { + const BRepGraph_EdgeId anEdgeId = anEdgeIt.CurrentId(); + + NCollection_Map aUniqueFaces; + const NCollection_Vector& aCoEdges = aDefs.Edges().CoEdges(anEdgeId); + for (const BRepGraph_CoEdgeId& aCoEdgeId : aCoEdges) + { + const BRepGraphInc::CoEdgeDef& aCoEdge = aDefs.CoEdges().Definition(aCoEdgeId); + if (aCoEdge.IsRemoved || !aCoEdge.FaceDefId.IsValid()) + { + continue; + } + aUniqueFaces.Add(aCoEdge.FaceDefId.Index); + } + + const int aCachedCount = aDefs.Edges().NbFaces(anEdgeId); + const int anActualCount = aUniqueFaces.Extent(); + if (aCachedCount != anActualCount) + { + TCollection_AsciiString aDesc("Reverse index face-count cache mismatch: cached="); + aDesc += TCollection_AsciiString(aCachedCount); + aDesc += " actual="; + aDesc += TCollection_AsciiString(anActualCount); + theIssues.Append(Issue{Severity::Error, anEdgeId, aDesc}); + } + } +} + +//! Check that parent-child incidence refs (shell->face, solid->shell, +//! compound->child, compsolid->solid) reference non-removed entities +//! of the expected kind. +//! @param[in] theGraph source graph +//! @param[in,out] theIssues collection to append diagnostic issues +void checkIncidenceRefConsistency(const BRepGraph& theGraph, + NCollection_Vector& theIssues) +{ + using Issue = BRepGraph_Validate::Issue; + using Severity = BRepGraph_Validate::Severity; + + // Check face->wire incidence refs. + for (BRepGraph_Iterator aFaceIt(theGraph); aFaceIt.More(); aFaceIt.Next()) + { + const BRepGraphInc::FaceDef& aFace = aFaceIt.Current(); + const BRepGraph_FaceId aFaceId = aFaceIt.CurrentId(); + + for (BRepGraph_RefsWireOfFace anIt(theGraph, aFaceId); anIt.More(); anIt.Next()) + { + const BRepGraphInc::WireRef& aWR = theGraph.Refs().Wires().Entry(anIt.CurrentId()); + const BRepGraph_NodeId aWireId = aWR.WireDefId; + if (aWireId.IsValid() && !isValidNodeId(theGraph, aWireId)) + { + theIssues.Append( + Issue{Severity::Error, aFace.Id, "FaceDef.WireUsage WireIdx out of bounds"}); + } + } + } + + // Check shell->face incidence refs. + for (BRepGraph_Iterator aShellIt(theGraph); aShellIt.More(); + aShellIt.Next()) + { + const BRepGraphInc::ShellDef& aShell = aShellIt.Current(); + const BRepGraph_ShellId aShellId = aShellIt.CurrentId(); + + for (BRepGraph_RefsFaceOfShell anIt(theGraph, aShellId); anIt.More(); anIt.Next()) + { + const BRepGraphInc::FaceRef& aFR = theGraph.Refs().Faces().Entry(anIt.CurrentId()); + const BRepGraph_NodeId aFaceId = aFR.FaceDefId; + if (aFaceId.IsValid() && isEntityRemoved(theGraph, aFaceId)) + { + theIssues.Append(Issue{Severity::Error, aShell.Id, "ShellDef references removed FaceDef"}); + } + } + } + + // Check solid->shell incidence refs. + for (BRepGraph_Iterator aSolidIt(theGraph); aSolidIt.More(); + aSolidIt.Next()) + { + const BRepGraphInc::SolidDef& aSolid = aSolidIt.Current(); + const BRepGraph_SolidId aSolidId = aSolidIt.CurrentId(); + + for (BRepGraph_RefsShellOfSolid anIt(theGraph, aSolidId); anIt.More(); anIt.Next()) + { + const BRepGraphInc::ShellRef& aSR = theGraph.Refs().Shells().Entry(anIt.CurrentId()); + const BRepGraph_NodeId aShellId = aSR.ShellDefId; + if (aShellId.IsValid() && isEntityRemoved(theGraph, aShellId)) + { + theIssues.Append(Issue{Severity::Error, aSolid.Id, "SolidDef references removed ShellDef"}); + } + } + } +} + +//! Validate that geometry representation ids (SurfaceRepId, Curve3DRepId, +//! Curve2DRepId, TriangulationRepIds, Polygon3DRepId) are in bounds and +//! reference non-null geometry handles. +//! @param[in] theGraph source graph +//! @param[in,out] theIssues collection to append diagnostic issues +void checkGeometryReferences(const BRepGraph& theGraph, + NCollection_Vector& theIssues) +{ + using Issue = BRepGraph_Validate::Issue; + using Severity = BRepGraph_Validate::Severity; + + // Check edge->curve references (handles stored directly on EdgeDef). + for (BRepGraph_Iterator anEdgeIt(theGraph); anEdgeIt.More(); + anEdgeIt.Next()) + { + const BRepGraph_EdgeId anEdgeId = anEdgeIt.CurrentId(); + const BRepGraphInc::EdgeDef& anEdge = anEdgeIt.Current(); + + if (!BRepGraph_Tool::Edge::Degenerated(theGraph, anEdgeId) + && !BRepGraph_Tool::Edge::HasCurve(theGraph, anEdgeId)) + { + theIssues.Append( + Issue{Severity::Error, anEdge.Id, "Non-degenerate EdgeDef has no Curve3D representation"}); + } + if (anEdge.Curve3DRepId.IsValid() + && !anEdge.Curve3DRepId.IsValid(theGraph.Topo().Geometry().NbCurves3D())) + { + theIssues.Append(Issue{Severity::Error, anEdge.Id, "EdgeDef.Curve3DRepId out of bounds"}); + } + if (anEdge.Polygon3DRepId.IsValid() + && !anEdge.Polygon3DRepId.IsValid(theGraph.Topo().Poly().NbPolygons3D())) + { + theIssues.Append(Issue{Severity::Error, anEdge.Id, "EdgeDef.Polygon3DRepId out of bounds"}); + } + } + + // Check face rep indices. + for (BRepGraph_Iterator aFaceIt(theGraph); aFaceIt.More(); aFaceIt.Next()) + { + const BRepGraphInc::FaceDef& aFace = aFaceIt.Current(); + if (aFace.SurfaceRepId.IsValid() + && !aFace.SurfaceRepId.IsValid(theGraph.Topo().Geometry().NbSurfaces())) + { + theIssues.Append(Issue{Severity::Error, aFace.Id, "FaceDef.SurfaceRepId out of bounds"}); + } + for (const BRepGraph_TriangulationRepId& aTriRepId : aFace.TriangulationRepIds) + { + if (aTriRepId.Index >= theGraph.Topo().Poly().NbTriangulations()) + { + theIssues.Append( + Issue{Severity::Error, aFace.Id, "FaceDef.TriangulationRepId out of bounds"}); + break; + } + } + } + + // Check CoEdge PCurve data. + for (BRepGraph_Iterator aCoEdgeIt(theGraph); aCoEdgeIt.More(); + aCoEdgeIt.Next()) + { + const BRepGraphInc::CoEdgeDef& aCoEdge = aCoEdgeIt.Current(); + const BRepGraph_CoEdgeId aCoEdgeId = aCoEdgeIt.CurrentId(); + + if (aCoEdge.FaceDefId.IsValid() && !BRepGraph_Tool::CoEdge::HasPCurve(theGraph, aCoEdgeId)) + { + theIssues.Append( + Issue{Severity::Error, aCoEdge.Id, "CoEdgeDef has no Curve2D representation"}); + } + } +} + +//! Verify that removed entities are not referenced by any active (non-removed) +//! parent entity through forward incidence refs. +//! @param[in] theGraph source graph +//! @param[in,out] theIssues collection to append diagnostic issues +void checkRemovedNodeIsolation(const BRepGraph& theGraph, + NCollection_Vector& theIssues) +{ + using Issue = BRepGraph_Validate::Issue; + using Severity = BRepGraph_Validate::Severity; + + // Non-removed edges must not reference removed vertices. + for (BRepGraph_Iterator anEdgeIt(theGraph); anEdgeIt.More(); + anEdgeIt.Next()) + { + const BRepGraphInc::EdgeDef& anEdge = anEdgeIt.Current(); + + if (anEdge.StartVertexRefId.IsValid()) + { + const BRepGraph_NodeId aStartVtxId = + theGraph.Refs().Vertices().Entry(anEdge.StartVertexRefId).VertexDefId; + if (aStartVtxId.IsValid() && isEntityRemoved(theGraph, aStartVtxId)) + { + theIssues.Append(Issue{Severity::Error, + anEdge.Id, + "Non-removed EdgeDef references removed StartVertexEntity"}); + } + } + if (anEdge.EndVertexRefId.IsValid()) + { + const BRepGraph_NodeId anEndVtxId = + theGraph.Refs().Vertices().Entry(anEdge.EndVertexRefId).VertexDefId; + if (anEndVtxId.IsValid() && isEntityRemoved(theGraph, anEndVtxId)) + { + theIssues.Append(Issue{Severity::Error, + anEdge.Id, + "Non-removed EdgeDef references removed EndVertexEntity"}); + } + } + } + + // Non-removed wires must not reference removed edges. + for (BRepGraph_Iterator aWireIt(theGraph); aWireIt.More(); aWireIt.Next()) + { + const BRepGraphInc::WireDef& aWire = aWireIt.Current(); + const BRepGraph_WireId aWireId = aWireIt.CurrentId(); + + bool hasRemovedEdge = false; + for (BRepGraph_RefsCoEdgeOfWire anIt(theGraph, aWireId); anIt.More(); anIt.Next()) + { + const BRepGraphInc::CoEdgeRef& aCR = theGraph.Refs().CoEdges().Entry(anIt.CurrentId()); + if (hasRemovedEdge) + { + break; + } + const BRepGraphInc::CoEdgeDef& aCoEdge = + theGraph.Topo().CoEdges().Definition(aCR.CoEdgeDefId); + const BRepGraph_NodeId anEdgeId = aCoEdge.EdgeDefId; + if (anEdgeId.IsValid() && isEntityRemoved(theGraph, anEdgeId)) + { + hasRemovedEdge = true; + } + } + if (hasRemovedEdge) + { + theIssues.Append( + Issue{Severity::Error, aWire.Id, "Non-removed WireDef references removed EdgeDef"}); + } + } +} + +//! Check wire edge connectivity: each coedge's end vertex must match the +//! next coedge's start vertex. Uses BRepGraph_WireExplorer for +//! order-independent traversal. +//! @param[in] theGraph source graph +//! @param[in,out] theIssues collection to append diagnostic issues +void checkWireConnectivity(const BRepGraph& theGraph, + NCollection_Vector& theIssues) +{ + using Issue = BRepGraph_Validate::Issue; + using Severity = BRepGraph_Validate::Severity; + + auto edgeLookup = [&theGraph](int theIdx) -> const BRepGraphInc::EdgeDef& { + return theGraph.Topo().Edges().Definition(BRepGraph_EdgeId(theIdx)); + }; + + for (BRepGraph_Iterator aWireIt(theGraph); aWireIt.More(); aWireIt.Next()) + { + const BRepGraphInc::WireDef& aWire = aWireIt.Current(); + const BRepGraph_WireId aWireId = aWireIt.CurrentId(); + + NCollection_Vector aWireCoEdgeRefs; + for (BRepGraph_RefsCoEdgeOfWire anIt(theGraph, aWireId); anIt.More(); anIt.Next()) + { + const BRepGraphInc::CoEdgeRef& aCRE = theGraph.Refs().CoEdges().Entry(anIt.CurrentId()); + BRepGraphInc::CoEdgeUsage aCR; + aCR.DefId = aCRE.CoEdgeDefId; + aCR.Location = aCRE.LocalLocation; + aWireCoEdgeRefs.Append(aCR); + } + + const int aNbCoEdges = aWireCoEdgeRefs.Length(); + if (aNbCoEdges < 2) + continue; + + // Validate all edge indices first. + bool aAllValid = true; + for (int anIdx = 0; anIdx < aNbCoEdges; ++anIdx) + { + const BRepGraphInc::CoEdgeUsage& aCR = aWireCoEdgeRefs.Value(anIdx); + const BRepGraphInc::CoEdgeDef& aCoEdge = theGraph.Topo().CoEdges().Definition(aCR.DefId); + const BRepGraph_NodeId anEdgeId = aCoEdge.EdgeDefId; + if (!anEdgeId.IsValid() || !isValidNodeId(theGraph, anEdgeId)) + { + aAllValid = false; + break; + } + } + if (!aAllValid) + continue; + + // Build connection-ordered sequence via WireExplorer, then check + // that all consecutive pairs in the ordered sequence are connected. + auto coedgeLookup = [&theGraph](int theIdx) -> const BRepGraphInc::CoEdgeDef& { + return theGraph.Topo().CoEdges().Definition(BRepGraph_CoEdgeId(theIdx)); + }; + auto vtxRefLookup = [&theGraph](const BRepGraph_VertexRefId theRefId) -> BRepGraph_VertexId { + return theGraph.Refs().Vertices().Entry(theRefId).VertexDefId; + }; + BRepGraph_WireExplorer anExp(aWireCoEdgeRefs, coedgeLookup, edgeLookup, vtxRefLookup); + const NCollection_Vector& anOrdered = anExp.OrderedRefs(); + + for (int anIdx = 0; anIdx < anOrdered.Length() - 1; ++anIdx) + { + const BRepGraphInc::CoEdgeUsage& aCurrCR = anOrdered.Value(anIdx); + const BRepGraphInc::CoEdgeUsage& aNextCR = anOrdered.Value(anIdx + 1); + + const BRepGraphInc::CoEdgeDef& aCurrCoEdge = + theGraph.Topo().CoEdges().Definition(aCurrCR.DefId); + const BRepGraphInc::CoEdgeDef& aNextCoEdge = + theGraph.Topo().CoEdges().Definition(aNextCR.DefId); + + const BRepGraphInc::EdgeDef& aCurrEdge = + theGraph.Topo().Edges().Definition(aCurrCoEdge.EdgeDefId); + const BRepGraphInc::EdgeDef& aNextEdge = + theGraph.Topo().Edges().Definition(aNextCoEdge.EdgeDefId); + + // Resolve oriented end vertex of current edge. + const BRepGraph_VertexRefId aCurrEndRefId = (aCurrCoEdge.Sense == TopAbs_FORWARD) + ? aCurrEdge.EndVertexRefId + : aCurrEdge.StartVertexRefId; + const BRepGraph_NodeId aCurrEnd = + aCurrEndRefId.IsValid() + ? BRepGraph_VertexId(theGraph.Refs().Vertices().Entry(aCurrEndRefId).VertexDefId.Index) + : BRepGraph_NodeId(); + + // Resolve oriented start vertex of next edge. + const BRepGraph_VertexRefId aNextStartRefId = (aNextCoEdge.Sense == TopAbs_FORWARD) + ? aNextEdge.StartVertexRefId + : aNextEdge.EndVertexRefId; + const BRepGraph_NodeId aNextStart = + aNextStartRefId.IsValid() + ? BRepGraph_VertexId(theGraph.Refs().Vertices().Entry(aNextStartRefId).VertexDefId.Index) + : BRepGraph_NodeId(); + + if (aCurrEnd.IsValid() && aNextStart.IsValid() && aCurrEnd != aNextStart) + { + TCollection_AsciiString aDesc("Wire edges not connected: edge["); + aDesc += TCollection_AsciiString(anIdx); + aDesc += "] end != edge["; + aDesc += TCollection_AsciiString(anIdx + 1); + aDesc += "] start"; + theIssues.Append(Issue{Severity::Error, aWire.Id, aDesc}); + } + } + } +} + +//! Validate that every entity's Id field matches its actual vector position: +//! Entity[i].Id must equal (Kind, i). Detects corruption from incorrect +//! Compact remapping or manual entity manipulation. +//! @param[in] theGraph source graph +//! @param[in,out] theIssues collection to append diagnostic issues +void checkDefIds(const BRepGraph& theGraph, + NCollection_Vector& theIssues) +{ + using Severity = BRepGraph_Validate::Severity; + using Issue = BRepGraph_Validate::Issue; + const BRepGraph::TopoView& aDefs = theGraph.Topo(); + + auto checkKind = [&](BRepGraph_NodeId::Kind theKind, int theNb) { + for (int anIdx = 0; anIdx < theNb; ++anIdx) + { + const BRepGraph_NodeId anExpected(theKind, anIdx); + const BRepGraphInc::BaseDef* aDef = aDefs.Gen().TopoEntity(anExpected); + if (aDef == nullptr) + { + continue; + } + if (aDef->Id != anExpected) + { + TCollection_AsciiString aDesc("Entity Id mismatch: expected ("); + aDesc += TCollection_AsciiString(static_cast(theKind)); + aDesc += ","; + aDesc += TCollection_AsciiString(anIdx); + aDesc += ") got ("; + aDesc += TCollection_AsciiString(static_cast(aDef->Id.NodeKind)); + aDesc += ","; + aDesc += TCollection_AsciiString(aDef->Id.Index); + aDesc += ")"; + theIssues.Append(Issue{Severity::Error, anExpected, aDesc}); + } + } + }; + + checkKind(BRepGraph_NodeId::Kind::Vertex, aDefs.Vertices().Nb()); + checkKind(BRepGraph_NodeId::Kind::Edge, aDefs.Edges().Nb()); + checkKind(BRepGraph_NodeId::Kind::CoEdge, aDefs.CoEdges().Nb()); + checkKind(BRepGraph_NodeId::Kind::Wire, aDefs.Wires().Nb()); + checkKind(BRepGraph_NodeId::Kind::Face, aDefs.Faces().Nb()); + checkKind(BRepGraph_NodeId::Kind::Shell, aDefs.Shells().Nb()); + checkKind(BRepGraph_NodeId::Kind::Solid, aDefs.Solids().Nb()); + checkKind(BRepGraph_NodeId::Kind::Compound, aDefs.Compounds().Nb()); + checkKind(BRepGraph_NodeId::Kind::CompSolid, aDefs.CompSolids().Nb()); + checkKind(BRepGraph_NodeId::Kind::Product, aDefs.Products().Nb()); + checkKind(BRepGraph_NodeId::Kind::Occurrence, aDefs.Occurrences().Nb()); +} + +//! Validate that NbActive*() counts match the actual number of non-removed +//! entities in each per-kind vector. +//! @param[in] theGraph source graph +//! @param[in,out] theIssues collection to append diagnostic issues +void checkActiveCounts(const BRepGraph& theGraph, + NCollection_Vector& theIssues) +{ + using Severity = BRepGraph_Validate::Severity; + using Issue = BRepGraph_Validate::Issue; + const BRepGraph::TopoView& aDefs = theGraph.Topo(); + + auto countActive = [&](BRepGraph_NodeId::Kind theKind, int theNb) -> int { + int aCount = 0; + for (int anIdx = 0; anIdx < theNb; ++anIdx) + { + const BRepGraph_NodeId anId(theKind, anIdx); + if (!isEntityRemoved(theGraph, anId)) + { + ++aCount; + } + } + return aCount; + }; + + auto verify = [&](const char* theName, int theActual, int theExpected) { + if (theActual != theExpected) + { + TCollection_AsciiString aDesc("NbActive"); + aDesc += theName; + aDesc += " mismatch: cached="; + aDesc += TCollection_AsciiString(theActual); + aDesc += " actual="; + aDesc += TCollection_AsciiString(theExpected); + theIssues.Append(Issue{Severity::Error, BRepGraph_NodeId(), aDesc}); + } + }; + + verify("Vertices", + aDefs.Vertices().NbActive(), + countActive(BRepGraph_NodeId::Kind::Vertex, aDefs.Vertices().Nb())); + verify("Edges", + aDefs.Edges().NbActive(), + countActive(BRepGraph_NodeId::Kind::Edge, aDefs.Edges().Nb())); + verify("CoEdges", + aDefs.CoEdges().NbActive(), + countActive(BRepGraph_NodeId::Kind::CoEdge, aDefs.CoEdges().Nb())); + verify("Wires", + aDefs.Wires().NbActive(), + countActive(BRepGraph_NodeId::Kind::Wire, aDefs.Wires().Nb())); + verify("Faces", + aDefs.Faces().NbActive(), + countActive(BRepGraph_NodeId::Kind::Face, aDefs.Faces().Nb())); + verify("Shells", + aDefs.Shells().NbActive(), + countActive(BRepGraph_NodeId::Kind::Shell, aDefs.Shells().Nb())); + verify("Solids", + aDefs.Solids().NbActive(), + countActive(BRepGraph_NodeId::Kind::Solid, aDefs.Solids().Nb())); + verify("Compounds", + aDefs.Compounds().NbActive(), + countActive(BRepGraph_NodeId::Kind::Compound, aDefs.Compounds().Nb())); + verify("CompSolids", + aDefs.CompSolids().NbActive(), + countActive(BRepGraph_NodeId::Kind::CompSolid, aDefs.CompSolids().Nb())); + verify("Products", + aDefs.Products().NbActive(), + countActive(BRepGraph_NodeId::Kind::Product, aDefs.Products().Nb())); + verify("Occurrences", + aDefs.Occurrences().NbActive(), + countActive(BRepGraph_NodeId::Kind::Occurrence, aDefs.Occurrences().Nb())); +} + +} // namespace + +//================================================================================================= + +BRepGraph_Validate::Result BRepGraph_Validate::Perform(const BRepGraph& theGraph) +{ + return Perform(theGraph, Options::Lightweight()); +} + +//================================================================================================= + +BRepGraph_Validate::Result BRepGraph_Validate::Perform(const BRepGraph& theGraph, + const Mode theMode) +{ + Options anOptions; + anOptions.ValidationMode = theMode; + return Perform(theGraph, anOptions); +} + +//================================================================================================= + +BRepGraph_Validate::Result BRepGraph_Validate::Perform(const BRepGraph& theGraph, + const Options& theOptions) +{ + Result aResult; + if (!theGraph.IsDone()) + { + return aResult; + } + + if (theOptions.ValidationMode == Mode::Lightweight) + { + NCollection_Vector aBoundaryIssues; + if (!theGraph.Builder().ValidateMutationBoundary(&aBoundaryIssues)) + { + appendMutationBoundaryIssues(aBoundaryIssues, aResult.Issues); + } + return aResult; + } + + NCollection_Vector aBoundaryIssues; + if (!theGraph.Builder().ValidateMutationBoundary(&aBoundaryIssues)) + { + appendMutationBoundaryIssues(aBoundaryIssues, aResult.Issues); + } + + checkCrossReferenceBounds(theGraph, aResult.Issues); + checkReverseIndexConsistency(theGraph, aResult.Issues); + checkReverseIndexFaceCountCache(theGraph, aResult.Issues); + checkIncidenceRefConsistency(theGraph, aResult.Issues); + checkGeometryReferences(theGraph, aResult.Issues); + checkRemovedNodeIsolation(theGraph, aResult.Issues); + checkWireConnectivity(theGraph, aResult.Issues); + + checkDefIds(theGraph, aResult.Issues); + checkActiveCounts(theGraph, aResult.Issues); + + // UID integrity checks: all active nodes must have a valid UID that round-trips. + const BRepGraph::TopoView& aDefs = theGraph.Topo(); + const BRepGraph::UIDsView& aUIDs = theGraph.UIDs(); + auto checkUIDKind = [&](const BRepGraph_NodeId::Kind theKind, const int theNb) { + for (int anIdx = 0; anIdx < theNb; ++anIdx) + { + const BRepGraph_NodeId aNode(theKind, anIdx); + const BRepGraphInc::BaseDef* aDef = aDefs.Gen().TopoEntity(aNode); + if (aDef == nullptr || aDef->IsRemoved) + { + continue; + } + + const BRepGraph_UID aUID = aUIDs.Of(aNode); + if (!aUID.IsValid()) + { + aResult.Issues.Append( + Issue{Severity::Error, aNode, "Node has missing or invalid UID in deep audit"}); + continue; + } + + if (!aUIDs.Has(aUID)) + { + aResult.Issues.Append(Issue{Severity::Error, + aNode, + "Node UID is not present in UIDs view for this generation"}); + continue; + } + + if (aUIDs.NodeIdFrom(aUID) != aNode) + { + aResult.Issues.Append( + Issue{Severity::Error, aNode, "Node UID does not round-trip to originating NodeId"}); + } + } + }; + + checkUIDKind(BRepGraph_NodeId::Kind::Vertex, aDefs.Vertices().Nb()); + checkUIDKind(BRepGraph_NodeId::Kind::Edge, aDefs.Edges().Nb()); + checkUIDKind(BRepGraph_NodeId::Kind::CoEdge, aDefs.CoEdges().Nb()); + checkUIDKind(BRepGraph_NodeId::Kind::Wire, aDefs.Wires().Nb()); + checkUIDKind(BRepGraph_NodeId::Kind::Face, aDefs.Faces().Nb()); + checkUIDKind(BRepGraph_NodeId::Kind::Shell, aDefs.Shells().Nb()); + checkUIDKind(BRepGraph_NodeId::Kind::Solid, aDefs.Solids().Nb()); + checkUIDKind(BRepGraph_NodeId::Kind::Compound, aDefs.Compounds().Nb()); + checkUIDKind(BRepGraph_NodeId::Kind::CompSolid, aDefs.CompSolids().Nb()); + checkUIDKind(BRepGraph_NodeId::Kind::Product, aDefs.Products().Nb()); + checkUIDKind(BRepGraph_NodeId::Kind::Occurrence, aDefs.Occurrences().Nb()); + + // Assembly DAG cycle detection: for each product, check if it can + // reach itself through its occurrence->product chain. + // Shared references (two occurrences pointing to the same child) are valid; + // only self-reachability constitutes a cycle. + for (BRepGraph_Iterator aProdIt(theGraph); aProdIt.More(); + aProdIt.Next()) + { + const BRepGraph_ProductId aProdId = aProdIt.CurrentId(); + + // BFS from this product's children; skip already-visited to avoid + // exponential blowup on DAGs. A cycle exists if we re-encounter aProdIdx. + NCollection_Map aVisited; + NCollection_Vector aQueue; + int aHead = 0; + + // Seed with direct children. + for (BRepGraph_RefsOccurrenceOfProduct anOccIt(theGraph, aProdId); anOccIt.More(); + anOccIt.Next()) + { + const BRepGraphInc::OccurrenceRef& anOccRef = + theGraph.Refs().Occurrences().Entry(anOccIt.CurrentId()); + const BRepGraphInc::OccurrenceDef& anOcc = + aDefs.Occurrences().Definition(anOccRef.OccurrenceDefId); + if (anOcc.IsRemoved || !anOcc.ProductDefId.IsValid(aDefs.Products().Nb())) + continue; + if (anOcc.ProductDefId.Index == aProdId.Index) + { + aResult.Issues.Append( + Issue{Severity::Error, + aProdId, + "Assembly cycle: Product directly references itself via occurrence"}); + break; + } + if (aVisited.Add(anOcc.ProductDefId.Index)) + aQueue.Append(anOcc.ProductDefId); + } + + bool aCycleFound = false; + while (aHead < aQueue.Length() && !aCycleFound) + { + const BRepGraph_ProductId aChildProdId = aQueue.Value(aHead); + ++aHead; + const BRepGraphInc::ProductDef& aChildProd = aDefs.Products().Definition(aChildProdId); + if (aChildProd.IsRemoved) + continue; + for (BRepGraph_RefsOccurrenceOfProduct aRefIt(theGraph, aChildProdId); aRefIt.More(); + aRefIt.Next()) + { + const BRepGraphInc::OccurrenceRef& aRef = + theGraph.Refs().Occurrences().Entry(aRefIt.CurrentId()); + const BRepGraphInc::OccurrenceDef& aOcc = + aDefs.Occurrences().Definition(aRef.OccurrenceDefId); + if (aOcc.IsRemoved || !aOcc.ProductDefId.IsValid(aDefs.Products().Nb())) + continue; + if (aOcc.ProductDefId.Index == aProdId.Index) + { + aResult.Issues.Append( + Issue{Severity::Error, + aProdId, + "Assembly cycle: Product reaches itself through occurrence chain"}); + aCycleFound = true; + break; + } + if (aVisited.Add(aOcc.ProductDefId.Index)) + aQueue.Append(aOcc.ProductDefId); + } + } + } + + return aResult; +} diff --git a/src/ModelingData/TKBRep/BRepGraph/BRepGraph_Validate.hxx b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_Validate.hxx new file mode 100644 index 0000000000..1e531f17f0 --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_Validate.hxx @@ -0,0 +1,165 @@ +// Copyright (c) 2026 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 _BRepGraph_Validate_HeaderFile +#define _BRepGraph_Validate_HeaderFile + +#include + +#include +#include +#include +#include + +//! @brief Structural invariant checker for BRepGraph. +//! +//! Read-only algorithm that verifies the graph's internal consistency: +//! cross-reference bounds, reverse index symmetry, incidence ref consistency, +//! geometry reference validity, removed-node isolation, and wire connectivity. +//! +//! Distinct from BRepGraphCheck (geometric shape validity). This class +//! checks the graph data structure itself. +//! +//! ### Validation Mode Check Matrix +//! +//! | Check | Lightweight | Audit | +//! |--------------------------------|:-----------:|:-----:| +//! | Active entity count boundary | YES | YES | +//! | Cross-reference bounds | - | YES | +//! | Reverse-index consistency | - | YES | +//! | Face-count cache consistency | - | YES | +//! | Incidence ref consistency | - | YES | +//! | Geometry representation refs | - | YES | +//! | Removed-node isolation | - | YES | +//! | Wire edge connectivity | - | YES | +//! | Entity ID positional integrity | - | YES | +//! | UID round-trip integrity | - | YES | +//! | Assembly DAG cycle detection | - | YES | +//! +//! ### Mode Guidance +//! +//! | Mode | What it checks | Cost | Recommended use | +//! |------|----------------|------|-----------------| +//! | `Lightweight` | Active entity count boundary only | Low | Hot-path release builds when the +//! graph structure is already trusted | | `Audit` | Full structural audit from cross-reference +//! bounds through assembly DAG cycle detection | Higher | Default validation mode for production +//! pipelines, test gates, and API-boundary verification | +//! +//! For production pipelines, prefer `Mode::Audit`; `Mode::Lightweight` is intended +//! for hot-path release builds where the graph structure is already trusted. +class BRepGraph_Validate +{ +public: + DEFINE_STANDARD_ALLOC + + //! Severity level for reported issues. + enum class Severity + { + Warning, + Error + }; + + //! Validation mode controlling check depth/performance trade-off. + enum class Mode + { + //! Fast boundary-oriented checks for frequent validation points. + Lightweight, + //! Full structural audit (superset of Lightweight). + Audit + }; + + //! A single structural issue found in the graph. + struct Issue + { + Severity Sev; + BRepGraph_NodeId NodeId; + TCollection_AsciiString Description; + }; + + //! Aggregated validation result. + struct Result + { + NCollection_Vector Issues; + + //! True if no Error-level issues were found. + [[nodiscard]] bool IsValid() const + { + for (const Issue& anIssue : Issues) + { + if (anIssue.Sev == Severity::Error) + return false; + } + return true; + } + + //! Count issues of a given severity. + [[nodiscard]] int NbIssues(const Severity theSev) const + { + int aCount = 0; + for (const Issue& anIssue : Issues) + { + if (anIssue.Sev == theSev) + ++aCount; + } + return aCount; + } + }; + + //! Validation options. + struct Options + { + //! Default mode for regular validation calls. + Mode ValidationMode = Mode::Lightweight; + + //! Build options for lightweight validation. + static Options Lightweight() + { + Options anOptions; + anOptions.ValidationMode = Mode::Lightweight; + return anOptions; + } + + //! Build options for full-audit validation. + static Options Audit() + { + Options anOptions; + anOptions.ValidationMode = Mode::Audit; + return anOptions; + } + }; + + //! Run default lightweight structural checks on a built graph. + //! Uses Mode::Lightweight; for full structural audit use Perform(theGraph, Mode::Audit). + //! @param[in] theGraph graph to validate (const, read-only) + //! @return validation result with all detected issues + [[nodiscard]] Standard_EXPORT static Result Perform(const BRepGraph& theGraph); + + //! Run structural checks on a built graph with explicit mode. + //! @param[in] theGraph graph to validate (const, read-only) + //! @param[in] theMode validation mode + //! @return validation result with all detected issues + [[nodiscard]] Standard_EXPORT static Result Perform(const BRepGraph& theGraph, + const Mode theMode); + + //! Run structural checks on a built graph with explicit options. + //! @param[in] theGraph graph to validate (const, read-only) + //! @param[in] theOptions validation profile/options + //! @return validation result with all detected issues + [[nodiscard]] Standard_EXPORT static Result Perform(const BRepGraph& theGraph, + const Options& theOptions); + +private: + BRepGraph_Validate() = delete; +}; + +#endif // _BRepGraph_Validate_HeaderFile diff --git a/src/ModelingData/TKBRep/BRepGraph/BRepGraph_VersionStamp.cxx b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_VersionStamp.cxx new file mode 100644 index 0000000000..8a1b8190e2 --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_VersionStamp.cxx @@ -0,0 +1,68 @@ +// Copyright (c) 2026 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 + +//================================================================================================= + +Standard_GUID BRepGraph_VersionStamp::ToGUID(const Standard_GUID& theGraphGUID) const +{ + // Pack fields into a flat byte buffer to avoid struct padding issues. + const Standard_UUID aGraphUUID = theGraphGUID.ToUUID(); + const uint8_t aDomain = static_cast(myDomain); + + size_t aCounter = 0; + int aKind = 0; + if (myDomain == Domain::Entity) + { + aCounter = myUID.Counter(); + aKind = static_cast(myUID.Kind()); + } + else if (myDomain == Domain::Ref) + { + aCounter = myRefUID.Counter(); + aKind = static_cast(myRefUID.Kind()); + } + + uint8_t aBuffer[sizeof(aGraphUUID) + sizeof(aDomain) + sizeof(aCounter) + sizeof(aKind) + + sizeof(myMutationGen) + sizeof(myGeneration)]; + size_t anOff = 0; + std::memcpy(aBuffer + anOff, &aGraphUUID, sizeof(aGraphUUID)); + anOff += sizeof(aGraphUUID); + std::memcpy(aBuffer + anOff, &aDomain, sizeof(aDomain)); + anOff += sizeof(aDomain); + std::memcpy(aBuffer + anOff, &aCounter, sizeof(aCounter)); + anOff += sizeof(aCounter); + std::memcpy(aBuffer + anOff, &aKind, sizeof(aKind)); + anOff += sizeof(aKind); + std::memcpy(aBuffer + anOff, &myMutationGen, sizeof(myMutationGen)); + anOff += sizeof(myMutationGen); + std::memcpy(aBuffer + anOff, &myGeneration, sizeof(myGeneration)); + anOff += sizeof(myGeneration); + + // Two independent hashes fill the 128-bit GUID. + const size_t aHash1 = opencascade::hashBytes(aBuffer, static_cast(anOff)); + const size_t aHalfOff = sizeof(aGraphUUID); + const size_t aHash2 = + opencascade::hashBytes(aBuffer + aHalfOff, static_cast(anOff - aHalfOff)); + + Standard_UUID aResultUUID; + static_assert(sizeof(size_t) >= 8, "Expected 64-bit size_t"); + std::memcpy(&aResultUUID, &aHash1, 8); + std::memcpy(reinterpret_cast(&aResultUUID) + 8, &aHash2, 8); + return Standard_GUID(aResultUUID); +} diff --git a/src/ModelingData/TKBRep/BRepGraph/BRepGraph_VersionStamp.hxx b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_VersionStamp.hxx new file mode 100644 index 0000000000..5f1e2efad4 --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_VersionStamp.hxx @@ -0,0 +1,190 @@ +// Copyright (c) 2026 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 _BRepGraph_VersionStamp_HeaderFile +#define _BRepGraph_VersionStamp_HeaderFile + +#include +#include +#include + +#include + +//! @brief Snapshot of an entity/ref identity and version at a point in time. +//! +//! Combines a persistent UID (entity or reference entry) with +//! OwnGen (own-data version counter) and graph Generation (Build cycle). +//! Computed on demand via BRepGraph::UIDs().StampOf(). +//! +//! Usage pattern: +//! @code +//! BRepGraph_VersionStamp aStamp = aGraph.UIDs().StampOf(aFaceId); +//! // ... later, after mutations ... +//! if (aGraph.UIDs().IsStale(aStamp)) +//! recomputeDerivedData(); +//! @endcode +//! +struct BRepGraph_VersionStamp +{ + //! Identity domain encoded in this stamp. + enum class Domain : uint8_t + { + None = 0, + Entity = 1, + Ref = 2 + }; + + BRepGraph_UID myUID; //!< Entity identity for entity-domain stamps. + BRepGraph_RefUID myRefUID; //!< Reference identity for ref-domain stamps. + uint32_t + myMutationGen; //!< OwnGen counter at snapshot time (maps to BaseDef::OwnGen / BaseRef::OwnGen). + uint32_t myGeneration; //!< Graph Build() generation at snapshot time. + Domain myDomain; //!< Active identity domain. + + //! Default constructor. Creates an invalid stamp (invalid UID, zero counters). + BRepGraph_VersionStamp() + : myUID(), + myRefUID(), + myMutationGen(0), + myGeneration(0), + myDomain(Domain::None) + { + } + + //! Construct an entity-domain stamp from components. + //! @param[in] theUID persistent entity identity + //! @param[in] theMutationGen OwnGen counter (own-data mutation counter) + //! @param[in] theGeneration graph Build() generation + BRepGraph_VersionStamp(const BRepGraph_UID& theUID, + const uint32_t theMutationGen, + const uint32_t theGeneration) + : myUID(theUID), + myRefUID(), + myMutationGen(theMutationGen), + myGeneration(theGeneration), + myDomain(Domain::Entity) + { + } + + //! Construct a reference-domain stamp from components. + //! @param[in] theRefUID persistent reference identity + //! @param[in] theMutationGen OwnGen counter (own-data mutation counter) + //! @param[in] theGeneration graph Build() generation + BRepGraph_VersionStamp(const BRepGraph_RefUID& theRefUID, + const uint32_t theMutationGen, + const uint32_t theGeneration) + : myUID(), + myRefUID(theRefUID), + myMutationGen(theMutationGen), + myGeneration(theGeneration), + myDomain(Domain::Ref) + { + } + + //! Check if the stamp has a valid identity in its domain. + [[nodiscard]] bool IsValid() const + { + if (myDomain == Domain::Entity) + return myUID.IsValid(); + if (myDomain == Domain::Ref) + return myRefUID.IsValid(); + return myUID.IsValid() || myRefUID.IsValid(); + } + + //! True when this is an entity-domain stamp. + [[nodiscard]] bool IsEntityStamp() const + { + if (myDomain == Domain::Entity) + return myUID.IsValid(); + return myDomain == Domain::None && myUID.IsValid() && !myRefUID.IsValid(); + } + + //! True when this is a reference-domain stamp. + [[nodiscard]] bool IsRefStamp() const + { + if (myDomain == Domain::Ref) + return myRefUID.IsValid(); + return myDomain == Domain::None && myRefUID.IsValid() && !myUID.IsValid(); + } + + //! Full equality: same domain, UID, OwnGen, and Generation. + //! Two invalid stamps are equal. + bool operator==(const BRepGraph_VersionStamp& theOther) const + { + if (!IsValid() && !theOther.IsValid()) + return true; + if (myDomain != theOther.myDomain) + return false; + if (myMutationGen != theOther.myMutationGen || myGeneration != theOther.myGeneration) + return false; + if (myDomain == Domain::Entity) + return myUID == theOther.myUID; + if (myDomain == Domain::Ref) + return myRefUID == theOther.myRefUID; + return myUID == theOther.myUID && myRefUID == theOther.myRefUID; + } + + bool operator!=(const BRepGraph_VersionStamp& theOther) const { return !(*this == theOther); } + + //! Check if two stamps refer to the same entity/reference regardless of version. + //! Compares active UID only, ignoring OwnGen and Generation. + //! @param[in] theOther stamp to compare with + //! @return true if both stamps have the same domain and UID + [[nodiscard]] bool IsSameNode(const BRepGraph_VersionStamp& theOther) const + { + if (myDomain != theOther.myDomain) + return false; + if (myDomain == Domain::Entity) + return myUID == theOther.myUID; + if (myDomain == Domain::Ref) + return myRefUID == theOther.myRefUID; + return myUID == theOther.myUID && myRefUID == theOther.myRefUID; + } + + //! Derive a deterministic Standard_GUID from this stamp. + //! The graph GUID is incorporated into the hash, making per-node GUIDs + //! globally unique across different graph instances. + //! One-way: cannot reconstruct stamp fields from the resulting GUID. + //! @param[in] theGraphGUID the owning graph's identity GUID + //! @return deterministic Standard_GUID derived from stamp + graph GUID + [[nodiscard]] Standard_EXPORT Standard_GUID ToGUID(const Standard_GUID& theGraphGUID) const; + + //! Compute hash value consistent with operator==. + //! @return hash combining active UID, domain, OwnGen, and Generation + [[nodiscard]] size_t HashValue() const + { + size_t aCombination[4]; + aCombination[0] = opencascade::hash(static_cast(myDomain)); + if (myDomain == Domain::Entity) + aCombination[1] = myUID.HashValue(); + else if (myDomain == Domain::Ref) + aCombination[1] = myRefUID.HashValue(); + else + aCombination[1] = opencascade::hash(0); + aCombination[2] = opencascade::hash(myMutationGen); + aCombination[3] = opencascade::hash(myGeneration); + return opencascade::hashBytes(aCombination, sizeof(aCombination)); + } +}; + +//! std::hash specialization for NCollection_DefaultHasher support. +template <> +struct std::hash +{ + size_t operator()(const BRepGraph_VersionStamp& theStamp) const noexcept + { + return theStamp.HashValue(); + } +}; + +#endif // _BRepGraph_VersionStamp_HeaderFile diff --git a/src/ModelingData/TKBRep/BRepGraph/BRepGraph_WireExplorer.hxx b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_WireExplorer.hxx new file mode 100644 index 0000000000..e7b53d75a8 --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/BRepGraph_WireExplorer.hxx @@ -0,0 +1,182 @@ +// Copyright (c) 2026 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 _BRepGraph_WireExplorer_HeaderFile +#define _BRepGraph_WireExplorer_HeaderFile + +#include +#include +#include +#include + +//! @brief Iterator for traversing wire edges in connection order using graph data. +//! +//! Reorders wire coedges by vertex adjacency: the end vertex of each edge +//! matches the start vertex of the next. This is the graph equivalent of +//! BRepTools_WireExplorer, operating on BRepGraphInc entity data. +//! +//! The coedges are reordered on construction (O(N^2) worst case for N coedges). +//! For most wires this is fast since N is small (4-8 edges typically). +//! +//! Accepts any edge/coedge accessor - works with both low-level storage adapters +//! and higher-level views (BRepGraph::TopoView). +//! +//! Usage with storage-backed accessors: +//! @code +//! auto edgeLookup = [&](const int idx) -> const BRepGraphInc::EdgeDef& { +//! return aStorage.Edge(idx); +//! }; +//! auto coedgeLookup = [&](const int idx) -> const BRepGraphInc::CoEdgeDef& { +//! return aStorage.CoEdge(idx); +//! }; +//! auto vtxRefLookup = [&](const BRepGraph_VertexRefId id) -> BRepGraph_VertexId { +//! return aStorage.VertexRef(id).VertexDefId; +//! }; +//! BRepGraph_WireExplorer anExp(aStorage.Wire(aWireIdx).CoEdgeRefs, +//! coedgeLookup, edgeLookup, vtxRefLookup); +//! @endcode +class BRepGraph_WireExplorer +{ +public: + //! Initialize the explorer, reordering coedges by vertex connectivity. + //! @param[in] theCoEdgeRefs wire's coedge reference vector + //! @param[in] theCoEdgeLookup function to get CoEdgeDef by index + //! @param[in] theEdgeLookup function to get EdgeDef by index + //! @param[in] theVtxRefLookup function to resolve VertexRefId to VertexDefId + template + BRepGraph_WireExplorer(const NCollection_Vector& theCoEdgeRefs, + const CoEdgeLookupT& theCoEdgeLookup, + const EdgeLookupT& theEdgeLookup, + const VertexRefLookupT& theVtxRefLookup) + : myCurrent(0) + { + buildOrder(theCoEdgeRefs, theCoEdgeLookup, theEdgeLookup, theVtxRefLookup); + } + + //! Returns true if there are more edges to iterate. + bool More() const { return myCurrent < myOrder.Length(); } + + //! Advance to the next edge. + void Next() { ++myCurrent; } + + //! Current coedge reference in connection order. + const BRepGraphInc::CoEdgeUsage& CurrentRef() const { return myOrder.Value(myCurrent); } + + //! Current coedge typed identifier in storage. + BRepGraph_CoEdgeId CurrentCoEdgeId() const { return CurrentRef().DefId; } + + //! Number of coedges in the ordered sequence. + int NbEdges() const { return myOrder.Length(); } + + //! Access the ordered coedge refs vector directly. + const NCollection_Vector& OrderedRefs() const { return myOrder; } + +private: + //! Resolve the oriented start vertex of an edge: for FORWARD sense returns + //! the start vertex def id, for REVERSED returns the end vertex def id. + template + static BRepGraph_NodeId orientedStartVertex(const BRepGraphInc::EdgeDef& theEdge, + const TopAbs_Orientation theSense, + const VertexRefLookupT& theVtxRefLookup) + { + const BRepGraph_VertexRefId aRefId = + (theSense == TopAbs_FORWARD) ? theEdge.StartVertexRefId : theEdge.EndVertexRefId; + if (!aRefId.IsValid()) + return BRepGraph_NodeId(); + return theVtxRefLookup(aRefId); + } + + //! Resolve the oriented end vertex of an edge: for FORWARD sense returns + //! the end vertex def id, for REVERSED returns the start vertex def id. + template + static BRepGraph_NodeId orientedEndVertex(const BRepGraphInc::EdgeDef& theEdge, + const TopAbs_Orientation theSense, + const VertexRefLookupT& theVtxRefLookup) + { + const BRepGraph_VertexRefId aRefId = + (theSense == TopAbs_FORWARD) ? theEdge.EndVertexRefId : theEdge.StartVertexRefId; + if (!aRefId.IsValid()) + return BRepGraph_NodeId(); + return theVtxRefLookup(aRefId); + } + + //! Build connection-ordered coedge sequence from CoEdgeRefs. + template + void buildOrder(const NCollection_Vector& theCoEdgeRefs, + const CoEdgeLookupT& theCoEdgeLookup, + const EdgeLookupT& theEdgeLookup, + const VertexRefLookupT& theVtxRefLookup) + { + const int aNbEdges = theCoEdgeRefs.Length(); + if (aNbEdges == 0) + return; + + // Track which coedges have been placed. + NCollection_Array1 aUsed(0, aNbEdges - 1); + aUsed.Init(false); + + // Start with the first coedge. + myOrder.Append(theCoEdgeRefs.Value(0)); + aUsed(0) = true; + + // Chain coedges by matching end-vertex to start-vertex. + for (int aPlaced = 1; aPlaced < aNbEdges; ++aPlaced) + { + const BRepGraphInc::CoEdgeUsage& aPrevRef = myOrder.Value(aPlaced - 1); + const BRepGraphInc::CoEdgeDef& aPrevCoEdge = theCoEdgeLookup(aPrevRef.DefId.Index); + const BRepGraph_NodeId aPrevEnd = + orientedEndVertex(theEdgeLookup(aPrevCoEdge.EdgeDefId.Index), + aPrevCoEdge.Sense, + theVtxRefLookup); + + bool aFound = false; + for (int i = 0; i < aNbEdges; ++i) + { + if (aUsed(i)) + continue; + const BRepGraphInc::CoEdgeUsage& aCandRef = theCoEdgeRefs.Value(i); + const BRepGraphInc::CoEdgeDef& aCandCoEdge = theCoEdgeLookup(aCandRef.DefId.Index); + const BRepGraph_NodeId aCandStart = + orientedStartVertex(theEdgeLookup(aCandCoEdge.EdgeDefId.Index), + aCandCoEdge.Sense, + theVtxRefLookup); + + if (aPrevEnd.IsValid() && aCandStart.IsValid() && aPrevEnd == aCandStart) + { + myOrder.Append(aCandRef); + aUsed(i) = true; + aFound = true; + break; + } + } + if (!aFound) + { + // No connected coedge found - append next unused (disconnected wire segment). + for (int i = 0; i < aNbEdges; ++i) + { + if (!aUsed(i)) + { + myOrder.Append(theCoEdgeRefs.Value(i)); + aUsed(i) = true; + break; + } + } + } + } + } + + NCollection_Vector myOrder; + int myCurrent; +}; + +#endif // _BRepGraph_WireExplorer_HeaderFile diff --git a/src/ModelingData/TKBRep/BRepGraph/FILES.cmake b/src/ModelingData/TKBRep/BRepGraph/FILES.cmake new file mode 100644 index 0000000000..2cdcf59f8e --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/FILES.cmake @@ -0,0 +1,64 @@ +set(OCCT_BRepGraph_FILES_LOCATION "${CMAKE_CURRENT_LIST_DIR}") + +set(OCCT_BRepGraph_FILES + BRepGraph.cxx + BRepGraph.hxx + BRepGraph_CacheView.cxx + BRepGraph_CacheView.hxx + BRepGraph_Builder.cxx + BRepGraph_Builder.hxx + BRepGraph_BuilderView.cxx + BRepGraph_BuilderView.hxx + BRepGraph_DefsIterator.hxx + BRepGraph_ChildExplorer.cxx + BRepGraph_ChildExplorer.hxx + BRepGraph_Iterator.hxx + BRepGraph_ParentExplorer.cxx + BRepGraph_ParentExplorer.hxx + BRepGraph_Data.hxx + BRepGraph_TopoView.cxx + BRepGraph_TopoView.hxx + BRepGraph_History.cxx + BRepGraph_History.hxx + BRepGraph_HistoryRecord.hxx + BRepGraph_Layer.cxx + BRepGraph_Layer.hxx + BRepGraph_LayerRegistry.cxx + BRepGraph_LayerRegistry.hxx + BRepGraph_DeferredScope.hxx + BRepGraph_MutGuard.hxx + BRepGraph_NodeId.hxx + BRepGraph_ParamLayer.cxx + BRepGraph_ParamLayer.hxx + BRepGraph_ParallelPolicy.hxx + BRepGraph_RefId.hxx + BRepGraph_RefUID.hxx + BRepGraph_RefsIterator.hxx + BRepGraph_RefsView.cxx + BRepGraph_RefsView.hxx + BRepGraph_RegularityLayer.cxx + BRepGraph_RegularityLayer.hxx + BRepGraph_RepId.hxx + BRepGraph_ShapesView.cxx + BRepGraph_ShapesView.hxx + BRepGraph_Tool.cxx + BRepGraph_Tool.hxx + BRepGraph_TransientCache.cxx + BRepGraph_TransientCache.hxx + BRepGraph_WireExplorer.hxx + BRepGraph_UID.hxx + BRepGraph_UIDsView.cxx + BRepGraph_VersionStamp.cxx + BRepGraph_VersionStamp.hxx + BRepGraph_UIDsView.hxx + BRepGraph_Deduplicate.cxx + BRepGraph_Deduplicate.hxx + BRepGraph_Compact.hxx + BRepGraph_Compact.cxx + BRepGraph_Copy.hxx + BRepGraph_Copy.cxx + BRepGraph_Transform.hxx + BRepGraph_Transform.cxx + BRepGraph_Validate.hxx + BRepGraph_Validate.cxx +) diff --git a/src/ModelingData/TKBRep/BRepGraph/README.md b/src/ModelingData/TKBRep/BRepGraph/README.md new file mode 100644 index 0000000000..f2daefec63 --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraph/README.md @@ -0,0 +1,457 @@ +# BRepGraph + +BRepGraph is a facade API over an incidence-table topology backend for TopoDS/BRep shapes. + +## Why It Exists + +BRepGraph provides a stable algorithm-facing API for: + +- adjacency and sharing queries, +- controlled topology mutation, +- shape reconstruction, +- assembly structure (products, occurrences, placement), +- history and UID tracking, +- cached analysis helpers. + +The goal is to make workflows like sewing, healing, compact, and deduplicate easier to implement and optimize. + +## Current Model (March 2026) + +The runtime model is incidence-first: + +- Source of truth: BRepGraphInc_Storage +- Topology defs in BRepGraph are aliases to incidence entities +- Orientation/location context is stored on incidence refs +- No separate runtime Usage storage layer + +See backend details in `src/ModelingData/TKBRep/BRepGraphInc/README.md`. + +## Architecture + +```mermaid +flowchart TB + A[Algorithms] --> G[BRepGraph facade] + + subgraph Views + V1[Topo / UIDs / Shapes] + V2[Cache / Builder / Refs] + V3[Paths] + end + + G --> Views + G --> D[BRepGraph_Data] + D --> S[BRepGraphInc_Storage] + D --> H[History and caches] + D --> R[Relation edge maps] + + S --> E[Entity tables] + S --> AS[Assembly tables] + S --> X[Reverse index] + S --> T[TShape to NodeId] + S --> O[Original shapes] + S --> U[UID vectors] +``` + +## Views Reference + +All queries and mutations go through lightweight view objects obtained from a `BRepGraph` instance. + +| View | Accessor | Purpose | +|------|----------|---------| +| **TopoView** | `Topo()` | Const topology definition access, representation access, adjacency queries, and raw Product/Occurrence definition storage | +| **UIDsView** | `UIDs()` | UID allocation, lookup, validity checking | +| **ShapesView** | `Shapes()` | Cached `Shape()` access, fresh `Reconstruct()`, and FindNode/HasNode reverse lookup | +| **CacheView** | `Cache()` | Stable public transient cache access (Set/Get/Has/Remove per-node cached values). Low-level reserve, transfer, and explicit generation-aware access remain on `TransientCache()` for algorithm code. | +| **BuilderView** | `Builder()` | Mutations: AddProduct, AddOccurrence, RemoveNode, RemoveSubgraph, MutGuard accessors | +| **RefsView** | `Refs()` | Reference entry access, RefUID lookup, VersionStamp for refs | +| **PathView** | `Paths()` | Assembly-aware queries (RootProducts, IsAssembly, IsPart, NbComponents, Component) and path traversal (GlobalLocation, GlobalOrientation, PathsTo, NodeLocations, CommonAncestor) | + +`TopoView` also exposes grouped node-oriented helpers for discoverable read queries: + +- `Topo().Faces()`, `Edges()`, `Vertices()`, `Wires()` +- `Topo().Shells()`, `Solids()`, `CoEdges()` +- `Topo().Compounds()`, `CompSolids()` +- `Topo().Products()`, `Occurrences()` + +Keep `Refs()` as the home for APIs returning `RefId` vectors and reference-entry payloads. + +### Non-View Helpers + +Use `BRepGraph_ChildExplorer` and `BRepGraph_ParentExplorer` directly for structural diagnostics and connected-component grouping. Lightweight one-off analysis helpers are expected to stay local to the consuming algorithm or test. + +### Direct Subsystem Accessors + +| Accessor | Purpose | +|----------|---------| +| `History()` | Mutation history subsystem (lineage records) | +| `TransientCache()` | Raw transient algorithm cache for low-level algorithms needing reserve, transfer, or explicit generation-aware access; public callers should prefer `Cache()` | +| `LayerRegistry()` | Access the GUID-keyed runtime registry of registered layers | +| `LayerRegistry().RegisterLayer(layer)` | Register a `BRepGraph_Layer` plugin explicitly | +| `LayerRegistry().FindLayer(guid)` / `LayerRegistry().FindLayer()` | Lookup a registered layer by GUID or layer type | +| `LayerRegistry().UnregisterLayer(guid)` | Remove a registered layer by GUID | + +## Main Data Concepts + +- **NodeId** (Kind + Index): lightweight typed address into per-kind node vectors +- **UID** (Kind + Counter): generation-aware persistent identity surviving compaction/reorder +- **RefId** (Kind + Index): lightweight typed address into per-kind reference entry vectors +- **RefUID** (Kind + Counter): generation-aware persistent reference identity +- **RepId** (Kind + Index): separate geometry/mesh addressing decoupled from topology nodes +- **Topology entities**: Vertex, Edge, CoEdge, Wire, Face, Shell, Solid, Compound, CompSolid +- **Assembly entities**: Product (part or assembly), Occurrence (placed instance) +- **Context refs**: VertexUsage, CoEdgeUsage, WireUsage, FaceUsage, ShellUsage, SolidUsage, ChildUsage, OccurrenceUsage +- **Reverse indices**: edge→wire, edge→face, edge→coedge, vertex→edge, wire→face, face→shell, shell→solid, product→occurrences + +## Reference Identity (RefId) + +Reference entries are the typed edges of the incidence graph. Each ref kind has its own id space, entry table, and UID counter. + +### Ref Kinds + +8 ref kinds: Shell, Face, Wire, CoEdge, Vertex, Solid, Child, Occurrence. Type-safe wrappers: `BRepGraph_ShellRefId`, `BRepGraph_FaceRefId`, `BRepGraph_WireRefId`, `BRepGraph_CoEdgeRefId`, `BRepGraph_VertexRefId`, `BRepGraph_SolidRefId`, `BRepGraph_ChildRefId`, `BRepGraph_OccurrenceRefId`. + +### BaseRef and Ref + +`BaseRef` is the common header for all reference entries: `RefId` + `ParentId` + `OwnGen` + `IsRemoved`. Concrete ref entry types (e.g. `ShellRef`, `FaceRef`) extend BaseRef with `DefId` + `Orientation` + `LocalLocation`. + +### RefUID + +`BRepGraph_RefUID` (Kind + Counter) provides persistent identity for reference entries. Counter-based and generation-aware, surviving compaction and reorder. Analogous to `BRepGraph_UID` for entities. + +### VersionStamp Support + +`BRepGraph_VersionStamp` supports the ref domain: `StampOf(refId)` and `IsStale(stamp)` enable cache invalidation for ref-dependent computations. + +### Mutation Guards + +`BRepGraph_MutGuard` is a unified RAII guard for safe mutation of both topology definitions (BaseDef hierarchy) and reference entries (BaseRef hierarchy). + +### RefsView API + +`RefsView` (via `Refs()`) provides: + +- Ref counts: `NbShellRefs`, `NbFaceRefs`, `NbWireRefs`, etc. +- Ref entry access: `Shell(id)`, `Face(id)`, etc. +- UID operations: `UIDOf(refId)`, `RefIdFrom(uid)` +- Parent-to-ref vectors: `ShellRefIdsOf(solidId)`, `FaceRefIdsOf(shellId)`, etc. + +Face outer-wire convenience is available from grouped `TopoView` helpers: +- `Topo().Faces().OuterWire(faceId)` + +## Core Pipelines + +### Build + +```mermaid +flowchart LR + I[TopoDS input] --> P1[Hierarchy traversal] + P1 --> P2[Parallel face extraction] + P2 --> P3[Sequential registration and dedup] + P3 --> P4[Post-passes] + P4 --> P5[Reverse index build] + P5 --> D[IsDone] +``` + +After topology population, `Build()` auto-creates a single root Product whose `ShapeRootId` points to the top-level topology node. This makes every BRepGraph intrinsically assembly-aware. + +### Reconstruct + +```mermaid +flowchart TD + N[Node request] --> C{Cache hit} + C -- yes --> R1[Return cached shape] + C -- no --> K[Kind dispatch] + K --> R2[Build shape] + R2 --> B[Bind cache] + B --> R1 +``` + +Use cache-enabled reconstruction paths for multi-face/shell/solid rebuilds. + +## Assembly Model + +Products and Occurrences are first-class node kinds alongside topology. + +### Node Kinds + +``` +Kind::Product = 10 // Reusable shape definition (part or assembly) +Kind::Occurrence = 11 // Placed instance of a product within a parent product +``` + +Helpers: `BRepGraph_NodeId::IsTopologyKind()`, `IsAssemblyKind()`, `Products().Definition(id)`, `Occurrences().Definition(id)`. + +### Data Model + +```mermaid +flowchart LR + subgraph Assembly DAG + RP[Root Product] + P1[Part Product] + P2[Sub-Assembly Product] + O1[Occurrence 1] + O2[Occurrence 2] + O3[Occurrence 3] + end + + RP -->|OccurrenceUsage| O1 + RP -->|OccurrenceUsage| O2 + O1 -->|ProductDefId| P1 + O2 -->|ProductDefId| P2 + P2 -->|OccurrenceUsage| O3 + O3 -->|ProductDefId| P1 +``` + +- **ProductDef**: `ShapeRootId` (topology root for parts; invalid for assemblies), `RootOrientation`, `RootLocation`, `OccurrenceRefIds` (child occurrences) +- **OccurrenceDef**: `ProductDefId` (referenced product), `ParentProductDefId` (parent assembly), `ParentOccurrenceDefId` (parent occurrence for tree-structured placement chains), `Placement` (TopLoc_Location) + +### Placement Composition + +`Paths().OccurrenceLocation(occId)` walks `ParentOccurrenceDefId` from leaf to root, composing `Placement` transforms. DAG-safe: shared products placed at multiple locations have distinct occurrence paths. + +### API Distribution + +| View | Methods | +|------|---------| +| **TopoView** | `NbProducts`, `NbOccurrences`, grouped helpers `Products()` / `Occurrences()` | +| **PathView** | `RootProducts`, `IsAssembly`, `IsPart`, `NbComponents`, `Component`, `OccurrenceLocation(occId)` | +| **BuilderView** | `AddProduct`, `AddAssemblyProduct`, `AddOccurrence` (with optional parent occurrence), `RemoveSubgraph` (cascades to child occurrences), `MutProduct(i)`, `MutOccurrence(i)` (RAII guards) | +| **Traversal** | Flat definition traversal via `BRepGraph_ProductIterator` / `BRepGraph_OccurrenceIterator` (or explicit `NbProducts()` / `NbOccurrences()` scans when storage-level access is required) | + +### Single-Shape Graph + +`Build(aBox)` creates one Product with `ShapeRootId = Solid(0)`, zero occurrences. Algorithms always see a uniform model. + +## Traversal + +BRepGraph provides a context-preserving traversal system for walking the hierarchy from any root down to entities of a target kind, producing full occurrence paths with composed locations and orientations. + +### TopologyPath + +`BRepGraph_TopologyPath` uniquely identifies one occurrence of an entity by encoding the root and a sequence of ref-index steps through the incidence hierarchy. The step model is uniform: assembly occurrences, compound containers, and topology entities are all just steps. + +### Explorer + +`BRepGraph_ChildExplorer` visits each **occurrence** of an entity kind (not definitions). If Edge[5] is reachable through Face[0] and Face[1], it is visited twice with different paths: + +```cpp +for (BRepGraph_ChildExplorer anExp(aGraph, BRepGraph_SolidId(0), + BRepGraph_NodeId::Kind::Edge); + anExp.More(); anExp.Next()) +{ + BRepGraph_NodeId anEdge = anExp.Current(); + TopLoc_Location aLoc = anExp.Location(); +} +``` + +Can also start from a Product to descend through assembly occurrences into topology. + +### PathView + +`PathView` (via `Paths()`) resolves topology paths: + +- `RootProducts()` / `IsAssembly()` / `IsPart()` / `NbComponents()` / `Component()` - assembly-aware product traversal +-- `GlobalLocation(path)` / `GlobalOrientation(path)` - composed transforms +- `ForEachPathTo(node, alloc, callback)` / `ForEachPathFromTo(root, leaf, alloc, callback)` - lazy reverse path enumeration without result-vector materialization +- `ForEachNodeLocation(node, alloc, callback)` - lazy occurrence enumeration with path, location, and orientation per branch +-- `PathsTo(node)` - all paths from any root to a given entity (reverse lookup) +-- `NodeLocations(node)` - all occurrence entries with paths, locations, orientations +-- `CommonAncestor(path1, path2)` - longest common prefix +-- `FilterByInclude` / `FilterByExclude` - path set filtering + +The vector-returning reverse lookup methods remain as convenience wrappers over the lazy enumeration layer. +- `IsAncestorOf`, `AllNodesOnPath`, `DepthOfKind` + +### Connected Components + +Disconnected topology is grouped on demand by walking from faces to their solid or shell roots with `BRepGraph_ParentExplorer`, or by descending from known roots with `BRepGraph_ChildExplorer`. There is no longer a packaged `SubGraph` container. + +## Geometry Access (BRepGraph_Tool) + +`BRepGraph_Tool` is the centralized geometry access API for BRepGraph, analogous to `BRep_Tool` for TopoDS. Nested helper classes provide typed, safe access: + +| Helper | Key Methods | +|--------|-------------| +| **Vertex** | `Pnt`, `Tolerance`, `Parameter` (on edge), `Parameters` (on surface) | +| **Edge** | `Tolerance`, `Degenerated`, `SameParameter`, `SameRange`, `Range`, `StartVertex`, `EndVertex`, `Curve`, `Polygon`, `Continuity` | +| **CoEdge** | `PCurveGeometry`, `PCurvePolygon`, `PCurveIsHandle` | +| **Face** | `Surface`, `Tolerance`, `NaturalRestriction`, `Wires`, `BndLib`, `UVBounds`, `CurveOnPlane`, `EvalD0` | +| **Wire** | `Edges` (traversal order via WireExplorer) | + +## Extensibility: Layers vs TransientCache + +`UserAttribute` naming is reserved for the future persistent metadata subsystem. + +### Layers (`BRepGraph_Layer`) + +Graph-wide metadata plugins with full lifecycle management. Graphs start with zero layers by default; +layers are added explicitly via `LayerRegistry().RegisterLayer()`. + +- **Purpose**: persistent domain metadata (colors, materials, names, layer groups) +- **Identity**: `Standard_GUID`, not display name +- **Name**: display-only metadata returned by `BRepGraph_Layer::Name()` +- **Storage**: internal maps keyed by NodeId, owned by the layer +- **Lifecycle**: `OnNodeRemoved(old, replacement)` migrates data; `OnCompact(remapMap)` remaps; `OnNodeModified`/`OnNodesModified` for mutation tracking +- **Survives mutations**: yes +- **Examples**: `BRepGraph_ParamLayer`, `BRepGraph_RegularityLayer` + +Typical workflow: + +```cpp +BRepGraph aGraph; +aGraph.LayerRegistry().RegisterLayer(new BRepGraph_ParamLayer()); +aGraph.LayerRegistry().RegisterLayer(new BRepGraph_RegularityLayer()); + +const occ::handle aParamLayer = + aGraph.LayerRegistry().FindLayer(); +``` + +### TransientCache (`BRepGraph_TransientCache`) + +Centralized per-node cache for algorithm-computed attributes. Dense graph-local storage keyed by +registered cache-kind descriptors with O(1) slot access. NOT a Layer - cleared on Build() and Compact(). + +- **Purpose**: ephemeral computed caches (bounding boxes, UV bounds, FClass2d results) +- **Identity**: cache families are described by `BRepGraph_CacheKind` with stable `Standard_GUID` identity +- **Storage**: dense `NCollection_Vector` per cache kind, then per node kind, then per entity index +- **Granularity**: one cached value per `(node, cache kind)` +- **Freshness**: SubtreeGen-validated. Each slot stores `StoredSubtreeGen`; on read, if it differs from the entity's current `SubtreeGen`, the attribute is marked dirty and recomputed lazily. +- **Thread safety**: `shared_mutex` (concurrent reads from `OSD_Parallel::For`, exclusive writes) +- **Survives mutations**: yes (stale entries detected by SubtreeGen mismatch) + +### When to Use Which + +- Data that must persist and migrate across graph mutations → **Layer** +- Computed values that can be recomputed from entity state → **TransientCache** (prefer `CacheView` / `Cache()` in public code) + +### Persistence Boundary + +- Persist the graph model: topology / assembly defs, refs, reps, UID / RefUID vectors, direct mutation freshness (`OwnGen`), and explicitly persistent layer data. +- Do **not** persist runtime acceleration state: `TransientCache`, reconstructed shape cache, reverse indices, lazy UID lookup maps, or deferred-mutation bookkeeping. +- Use `UID` / `RefUID` as persistence anchors and `NodeId` / `RefId` as runtime addresses. +- Keep occurrence-context metadata resolution out of the core storage model; add it later through `PathView` helpers or layer-side resolvers once DE layers exist. + +## Mutation Tracking and Change Propagation + +### Split-Generation Model + +Every entity (`BaseDef`) carries two generation counters: + +| Counter | Incremented when | Used for | +|---------|-----------------|----------| +| **OwnGen** | Entity's own definition fields change (tolerance, point, flags, edge list, surface, etc.) | VersionStamp persistent identity; PLM staleness detection | +| **SubtreeGen** | Entity's own data OR any descendant's data changes | TransientCache freshness; shape cache validation | + +`BaseRef` and `BaseRep` carry only `OwnGen` (no subtree). + +### Propagation + +When an entity is directly mutated via `MutGuard`: +1. `++OwnGen; ++SubtreeGen` on the mutated entity +2. `propagateSubtreeGen()` walks upward via reverse indices (Edge→Wire→Face→Shell→Solid) +3. Each parent gets `++SubtreeGen` only (NOT OwnGen - parent's own data didn't change) +4. Diamond guard (`LastPropWave`) prevents exponential blowup on shared parents + +Propagation is **mutex-free** - no locks, no shape cache clears, no layer dispatch. Cost: ~4 cycles per parent on Apple M1; actual cost depends on hardware and memory state. + +### Deferred Mode + +`BRepGraph_DeferredScope` wraps batch mutations (sewing, SameParameter, compact loops): +- During scope: `markModified()` appends to deferred list, no propagation +- At scope exit: BFS upward propagation of SubtreeGen, batch layer dispatch +- Deferred mode batches invalidation only; concurrent `Mut*()` calls still require external synchronization +- See `BRepGraph_DeferredScope` for the RAII guard wrapping `BeginDeferredInvalidation()` / `EndDeferredInvalidation()` + +### Shape Cache + +Reconstructed shapes are cached in `BRepGraph_Data::myCurrentShapes` as `CachedShape{Shape, StoredSubtreeGen}`. Validated lazily on read - if `StoredSubtreeGen != entity.SubtreeGen`, the shape is stale and reconstructed. + +### Persistent Identity (VersionStamp) + +`BRepGraph_VersionStamp` = (UID, OwnGen, Generation). `IsStale()` compares `OwnGen` - detects only direct entity changes. Parent stamps are NOT stale when children change, matching PLM-style semantics where direct parent edits and child edits are tracked separately. + +### History + +Primary mutation entry points are exposed via `Builder()` and scoped RAII guards (`BRepGraph_MutGuard`). + +Common operations: SplitEdge, ReplaceEdgeInWire, AddPCurveToEdge, relation-edge add/remove. + +History records lineage for downstream attribute transfer and diagnostics. Supports allocator propagation via `SetAllocator()`. + +## Memory Model + +BRepGraph uses a single `NCollection_IncAllocator` (bump-pointer allocator) for all internal containers: + +- All DataMaps in `BRepGraph_Data` +- All `BRepGraphInc_Storage` entity tables and UID vectors +- All `BRepGraphInc_ReverseIndex` inner vectors +- `BRepGraph_History` containers and inner vectors + +Benefits: O(1) allocation (bump-pointer), O(1) destruction (bulk page release). The allocator can be provided externally via `BRepGraph::SetAllocator()`. + +## Threading Model + +- Const query paths are designed for concurrent read access. +- Shape cache is protected by shared mutex. +- Build supports internal parallel extraction. +- Mutation must be externally serialized. +- `BeginDeferredInvalidation()` / `EndDeferredInvalidation()` reduces invalidation overhead for batch mutation loops. + +## Build Options + +`Build(theShape, theParallel)` uses default `BRepGraphInc_Populate::Options`. +Use the explicit overload when the caller needs to override optional extraction passes. + +`Build()` options: + +- `ExtractRegularities` (default true): edge continuity across face pairs. +- `ExtractVertexPointReps` (default true): vertex parameter representations on curves/surfaces. + +## Debug Validation + +`BRepGraphInc_ReverseIndex::Validate()` checks all reverse index maps against forward entity refs. Called automatically via `Standard_ASSERT_VOID` after SplitEdge and ReplaceEdgeInWire in debug builds. + +`Builder().CommitMutation()` validates reverse index + active entity counts. Called at end of Sewing, Compact, Deduplicate. + +### Validation Pipeline + +`BRepGraph_Validate` checks structural graph invariants, not geometric validity. Use `Mode::Lightweight` only for cheap boundary checks on graphs whose structure is created or mutated exclusively by internal algorithm code with already-tested invariants. For CI, integration tests, and production API boundaries, prefer `Mode::Audit`, which adds cross-reference, reverse-index, UID, and assembly-cycle checks. + +If the caller also needs geometric/topological validity of reconstructed shapes, run `BRepGraph_Validate` first for graph integrity, then run the shape-level validation stack separately. + +```cpp +const BRepGraph_Validate::Result aResult = + BRepGraph_Validate::Perform(aGraph, BRepGraph_Validate::Mode::Audit); +if (!aResult.IsValid()) +{ + // inspect aResult.Issues before continuing +} +``` + +## Practical Guidance + +1. Treat BRepGraph as API boundary and BRepGraphInc as implementation backend. +2. Treat view APIs (`Topo()`, `Refs()`, `Paths()`) as the stable read boundary; avoid direct access to `BRepGraph_Data` / `myIncStorage` outside designated backend maintenance code. +3. Keep reverse index updates consistent with forward ref changes. +4. Prefer incremental updates in mutators over full rebuilds. +5. Use profiling before adding micro-optimizations. + +## File Map + +| Category | Files | +|----------|-------| +| **Core** | `BRepGraph.hxx/.cxx`, `BRepGraph_Data.hxx`, `BRepGraph_NodeId.hxx`, `BRepGraph_UID.hxx`, `BRepGraph_RefId.hxx`, `BRepGraph_RefUID.hxx`, `BRepGraph_RepId.hxx` | +| **Views** | `BRepGraph_TopoView.hxx/.cxx`, `BRepGraph_UIDsView.hxx/.cxx`, `BRepGraph_RefsView.hxx/.cxx`, `BRepGraph_ShapesView.hxx/.cxx`, `BRepGraph_CacheView.hxx/.cxx`, `BRepGraph_BuilderView.hxx/.cxx`, `BRepGraph_PathView.hxx/.cxx` | +| **Refs** | `BRepGraph_VersionStamp.hxx/.cxx` | +| **Traversal** | `BRepGraph_ChildExplorer.hxx/.cxx`, `BRepGraph_ParentExplorer.hxx/.cxx`, `BRepGraph_TopologyPath.hxx`, `BRepGraph_PCurveContext.hxx` | +| **Geometry** | `BRepGraph_Tool.hxx/.cxx` | +| **Mutation** | `BRepGraph_MutGuard.hxx`, `BRepGraph_DeferredScope.hxx` | +| **Layers** | `BRepGraph_Layer.hxx/.cxx`, `BRepGraph_ParamLayer.hxx/.cxx`, `BRepGraph_RegularityLayer.hxx/.cxx` | +| **Transient Cache** | `BRepGraph_TransientCache.hxx/.cxx` | +| **History** | `BRepGraph_History.hxx/.cxx`, `BRepGraph_HistoryRecord.hxx` | +| **Build** | `BRepGraph_Builder.hxx/.cxx` | + +## Documentation Map + +- API facade and views: `src/ModelingData/TKBRep/BRepGraph/` +- Backend storage and pipelines: `src/ModelingData/TKBRep/BRepGraphInc/` diff --git a/src/ModelingData/TKBRep/BRepGraphInc/BRepGraphInc_Definition.hxx b/src/ModelingData/TKBRep/BRepGraphInc/BRepGraphInc_Definition.hxx new file mode 100644 index 0000000000..0749ccc7f6 --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraphInc/BRepGraphInc_Definition.hxx @@ -0,0 +1,297 @@ +// Copyright (c) 2026 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 _BRepGraphInc_Definition_HeaderFile +#define _BRepGraphInc_Definition_HeaderFile + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +//! @brief Definition structs for the incidence-table topology model. +//! +//! Each definition holds intrinsic geometry properties plus forward-direction +//! children (via RefId indices). The incidence model stores topology as flat +//! vectors of definitions (one per kind) with integer cross-references, +//! enabling cache-friendly traversal and parallel geometry extraction. +namespace BRepGraphInc +{ + +//! Helper: reinitialize a vector member with the given allocator and block size. +template +inline void InitVec(NCollection_Vector& theVec, + const occ::handle& theAlloc, + const int theBlockSize = 4) +{ + theVec = NCollection_Vector(theBlockSize, theAlloc); +} + +//! Fields shared by every entity. +struct BaseDef +{ + BRepGraph_NodeId Id; //!< Typed address (kind + per-kind index) + + //! Own-data mutation counter, incremented ONLY when the entity's own + //! definition fields change (tolerance, point, flags, etc.). + //! NOT incremented by descendant changes. + //! Used by VersionStamp for persistent identity staleness detection. + uint32_t OwnGen = 0; + + //! Subtree mutation counter, incremented when own data OR any descendant + //! data changes. Propagated upward via markParentSubtreeGen(). + //! Used by TransientCache and shape cache for hierarchical freshness. + uint32_t SubtreeGen = 0; + + //! Wave counter from the last propagation that visited this node. + //! Used as a re-visit guard in markParentSubtreeGen() to prevent + //! exponential blowup on diamond topologies. Compared against + //! BRepGraph_Data::myPropagationWave. + uint32_t LastPropWave = 0; + + bool IsRemoved = false; //!< Soft-removal flag +}; + +//! Vertex definition: 3D point + tolerance. +struct VertexDef : public BaseDef +{ + //! 3D point in definition frame (raw BRep_TVertex::Pnt, without vertex-in-edge Location). + gp_Pnt Point; + + //! Tolerance from BRep_TVertex. + double Tolerance = 0.0; + + void InitVectors(const occ::handle&) {} +}; + +//! Edge entity: parameter range, boundary vertices, flags. +//! Geometry (curve, polygon) accessed via rep indices into Storage vectors. +struct EdgeDef : public BaseDef +{ + //! Typed representation id into Storage::myCurves3D (invalid for degenerate edges). + BRepGraph_Curve3DRepId Curve3DRepId; + + //! Curve parameter range. + double ParamFirst = 0.0; + double ParamLast = 0.0; + + //! Tolerance from BRep_TEdge. + double Tolerance = 0.0; + + //! True if this edge collapses to a point on the surface. + bool IsDegenerate = false; + + //! True if all PCurves are reparametrized to the same range as the 3D curve. + bool SameParameter = false; + + //! True if the PCurve parameter range equals the 3D curve parameter range. + bool SameRange = false; + + //! True if StartVertex == EndVertex (topological loop, e.g. circle edge). + bool IsClosed = false; + + //! Boundary vertex reference ids (indices into VertexRef table). + //! For closed edges, the start and end ref entries point to the same VertexDefId. + BRepGraph_VertexRefId StartVertexRefId; + BRepGraph_VertexRefId EndVertexRefId; + + //! Additional vertex reference ids with INTERNAL or EXTERNAL orientation. + //! Edges with only FORWARD/REVERSED boundary vertices leave this empty. + NCollection_Vector InternalVertexRefIds; + + //! Typed representation id into Storage::myPolygons3D (invalid if no polygon). + BRepGraph_Polygon3DRepId Polygon3DRepId; + + //! Reinitialize inner vectors with the given allocator. + void InitVectors(const occ::handle& theAlloc) + { + InitVec(InternalVertexRefIds, theAlloc, 2); // typically 0 + } +}; + +//! CoEdge entity: use of an edge on a specific face, owns PCurve data. +//! +//! Each coedge represents one edge-face binding with its parametric curve. +//! Wires reference coedges rather than edges directly. +//! For seam edges, two coedges exist on the same face with opposite Sense, +//! linked by SeamPairIdx. +struct CoEdgeDef : public BaseDef +{ + BRepGraph_EdgeId EdgeDefId; //!< Parent edge definition id + BRepGraph_FaceId FaceDefId; //!< Face this coedge belongs to (invalid for free wires) + TopAbs_Orientation Sense = TopAbs_FORWARD; //!< Orientation relative to parent edge + + //! Typed representation id into Storage::myCurves2D (invalid for free-wire coedges). + BRepGraph_Curve2DRepId Curve2DRepId; + double ParamFirst = 0.0; + double ParamLast = 0.0; + GeomAbs_Shape Continuity = GeomAbs_C0; //!< Geometric continuity across face pairs + gp_Pnt2d UV1; //!< UV at ParamFirst + gp_Pnt2d UV2; //!< UV at ParamLast + + //! Seam pairing: typed id of the paired coedge (invalid if non-seam). + BRepGraph_CoEdgeId SeamPairId; + GeomAbs_Shape SeamContinuity = GeomAbs_C0; //!< Continuity between seam pair + + //! Typed representation id into Storage::myPolygons2D (invalid if no polygon-on-surface). + BRepGraph_Polygon2DRepId Polygon2DRepId; + + //! Typed representation ids into Storage::myPolygonsOnTri. + NCollection_Vector PolygonOnTriRepIds; + + void InitVectors(const occ::handle& theAlloc) + { + InitVec(PolygonOnTriRepIds, theAlloc, 2); + } +}; + +//! Wire entity: ordered coedge references with closure flag. +struct WireDef : public BaseDef +{ + bool IsClosed = false; + NCollection_Vector CoEdgeRefIds; //!< Ordered coedge ref indices + + void InitVectors(const occ::handle& theAlloc) + { + InitVec(CoEdgeRefIds, theAlloc, 8); // typically 3-8 coedges per wire + } +}; + +//! Face entity: surface, triangulations, wires. +struct FaceDef : public BaseDef +{ + BRepGraph_SurfaceRepId SurfaceRepId; //!< Typed id into mySurfaces + NCollection_Vector + TriangulationRepIds; //!< Typed ids into myTriangulations + int ActiveTriangulationIndex = -1; //!< Index into TriangulationRepIds array (NOT a rep id) + + //! Convenience: active triangulation rep id, or invalid. + [[nodiscard]] BRepGraph_TriangulationRepId ActiveTriangulationRepId() const + { + if (ActiveTriangulationIndex >= 0 && ActiveTriangulationIndex < TriangulationRepIds.Length()) + return TriangulationRepIds.Value(ActiveTriangulationIndex); + return BRepGraph_TriangulationRepId(); + } + + double Tolerance = 0.0; + bool NaturalRestriction = false; + + NCollection_Vector WireRefIds; //!< Wire ref indices (outer first) + + //! Direct INTERNAL/EXTERNAL vertex children (not inside wires). + //! Boundary vertices are normally reached through WireRefIds -> CoEdgeRefIds + //! -> CoEdgeDef.EdgeDefId -> EdgeDef.{StartVertexRefId, EndVertexRefId}. + //! This vector is for additional direct face-owned vertex usage. + NCollection_Vector VertexRefIds; + + void InitVectors(const occ::handle& theAlloc) + { + InitVec(TriangulationRepIds, theAlloc, 2); // typically 1 + InitVec(WireRefIds, theAlloc, 2); // typically 1-2 (outer + holes) + InitVec(VertexRefIds, theAlloc, 2); // typically 0 + } +}; + +//! Shell entity: ordered face references with local locations. +struct ShellDef : public BaseDef +{ + bool IsClosed = false; //!< True if shell forms a watertight (closed) boundary. + NCollection_Vector FaceRefIds; //!< Face ref indices + NCollection_Vector FreeChildRefIds; //!< Non-face children (wires, edges) + + void InitVectors(const occ::handle& theAlloc) + { + InitVec(FaceRefIds, theAlloc, 8); // typically 4-8 faces per shell + InitVec(FreeChildRefIds, theAlloc, 2); // typically 0 + } +}; + +//! Solid entity: ordered shell references with local locations. +struct SolidDef : public BaseDef +{ + NCollection_Vector ShellRefIds; //!< Shell ref indices + NCollection_Vector + FreeChildRefIds; //!< Non-shell children (edges, vertices) + + void InitVectors(const occ::handle& theAlloc) + { + InitVec(ShellRefIds, theAlloc, 2); // typically 1 + InitVec(FreeChildRefIds, theAlloc, 2); // typically 0 + } +}; + +//! Compound entity: heterogeneous child references. +struct CompoundDef : public BaseDef +{ + NCollection_Vector ChildRefIds; //!< Child ref indices + + void InitVectors(const occ::handle& theAlloc) + { + InitVec(ChildRefIds, theAlloc, 4); + } +}; + +//! Comp-solid entity: ordered solid references. +struct CompSolidDef : public BaseDef +{ + NCollection_Vector SolidRefIds; //!< Solid ref indices + + void InitVectors(const occ::handle& theAlloc) + { + InitVec(SolidRefIds, theAlloc, 2); + } +}; + +//! Product entity: reusable shape definition (part or assembly). +//! A part has a valid ShapeRootId pointing to the root topology node. +//! An assembly has an invalid ShapeRootId and owns child occurrences. +struct ProductDef : public BaseDef +{ + BRepGraph_NodeId ShapeRootId; //!< Root topology for parts; invalid for assemblies + TopAbs_Orientation RootOrientation = TopAbs_FORWARD; //!< Orientation of the root shape + TopLoc_Location RootLocation; //!< Location of the root shape + NCollection_Vector OccurrenceRefIds; //!< Occurrence ref indices + + void InitVectors(const occ::handle& theAlloc) + { + InitVec(OccurrenceRefIds, theAlloc, 4); + } +}; + +//! Occurrence entity: placed instance of a product within a parent product. +//! ParentOccurrenceIdx forms a tree-structured placement chain for +//! unambiguous GlobalPlacement computation even when products are shared (DAG). +struct OccurrenceDef : public BaseDef +{ + BRepGraph_ProductId ProductDefId; //!< Referenced product definition id + BRepGraph_ProductId ParentProductDefId; //!< Parent assembly product definition id + BRepGraph_OccurrenceId ParentOccurrenceDefId; //!< Parent occurrence id (invalid for top-level) + TopLoc_Location Placement; //!< Local placement relative to parent + + //! No-op: OccurrenceDef has no inner vectors to reinitialize. + //! Present for uniform DefStore::Append() logic. + void InitVectors(const occ::handle&) {} +}; + +} // namespace BRepGraphInc + +#endif // _BRepGraphInc_Definition_HeaderFile diff --git a/src/ModelingData/TKBRep/BRepGraphInc/BRepGraphInc_Populate.cxx b/src/ModelingData/TKBRep/BRepGraphInc/BRepGraphInc_Populate.cxx new file mode 100644 index 0000000000..5b0e4d2906 --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraphInc/BRepGraphInc_Populate.cxx @@ -0,0 +1,2166 @@ +// Copyright (c) 2026 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 +#include +#include +#include +#include +#include + +#include + +// Population pipeline overview: +// +// Phase 1 (sequential): Recursively traverse the TopoDS hierarchy +// (Compound -> CompSolid -> Solid -> Shell), collecting face contexts +// into a flat FaceLocalData vector. Registers container entities +// (Compound, CompSolid, Solid, Shell) with TShape deduplication. +// +// Phase 2 (parallel): Per-face geometry extraction via OSD_Parallel. +// Extracts surface, triangulations, wires, edges (with PCurves, +// polygons, vertices) into ExtractedEdge/ExtractedWire/FaceLocalData +// structs. No storage writes - thread-safe read-only access to TopoDS. +// +// Phase 3 (sequential): Register extracted data into BRepGraphInc_Storage. +// Creates Face, Wire, Edge, CoEdge, Vertex entities with TShape dedup +// and representation dedup (getOrCreate*Rep). Also runs optional +// post-passes: edge regularities (3b) and vertex point reps (3c). +// +// Phase 4 (sequential): Build reverse indices for O(1) upward navigation. + +namespace +{ + +//================================================================================================= + +void appendFaceRef(BRepGraphInc_Storage& theStorage, + const BRepGraph_ShellId theParentShellId, + const BRepGraphInc::FaceUsage& theRef) +{ + BRepGraphInc::FaceRef& anEntry = theStorage.AppendFaceRef(); + const BRepGraph_FaceRefId aRefId(theStorage.NbFaceRefs() - 1); + anEntry.RefId = aRefId; + anEntry.ParentId = BRepGraph_ShellId(theParentShellId.Index); + anEntry.FaceDefId = theRef.DefId; + anEntry.Orientation = theRef.Orientation; + anEntry.LocalLocation = theRef.Location; + theStorage.ChangeShell(theParentShellId).FaceRefIds.Append(aRefId); +} + +//================================================================================================= + +void appendWireRef(BRepGraphInc_Storage& theStorage, + const BRepGraph_FaceId theParentFaceId, + const BRepGraphInc::WireUsage& theRef) +{ + BRepGraphInc::WireRef& anEntry = theStorage.AppendWireRef(); + const BRepGraph_WireRefId aRefId(theStorage.NbWireRefs() - 1); + anEntry.RefId = aRefId; + anEntry.ParentId = BRepGraph_FaceId(theParentFaceId.Index); + anEntry.WireDefId = theRef.DefId; + anEntry.IsOuter = theRef.IsOuter; + anEntry.Orientation = theRef.Orientation; + anEntry.LocalLocation = theRef.Location; + theStorage.ChangeFace(theParentFaceId).WireRefIds.Append(aRefId); +} + +//================================================================================================= + +void appendCoEdgeRef(BRepGraphInc_Storage& theStorage, + const BRepGraph_WireId theParentWireId, + const BRepGraphInc::CoEdgeUsage& theRef) +{ + BRepGraphInc::CoEdgeRef& anEntry = theStorage.AppendCoEdgeRef(); + const BRepGraph_CoEdgeRefId aRefId(theStorage.NbCoEdgeRefs() - 1); + anEntry.RefId = aRefId; + anEntry.ParentId = BRepGraph_WireId(theParentWireId.Index); + anEntry.CoEdgeDefId = theRef.DefId; + anEntry.LocalLocation = theRef.Location; + theStorage.ChangeWire(theParentWireId).CoEdgeRefIds.Append(aRefId); +} + +//================================================================================================= + +BRepGraph_VertexRefId appendVertexRef(BRepGraphInc_Storage& theStorage, + const BRepGraph_NodeId theParentId, + const BRepGraphInc::VertexUsage& theRef) +{ + BRepGraphInc::VertexRef& anEntry = theStorage.AppendVertexRef(); + const BRepGraph_VertexRefId aRefId(theStorage.NbVertexRefs() - 1); + anEntry.RefId = aRefId; + anEntry.ParentId = theParentId; + anEntry.VertexDefId = theRef.DefId; + anEntry.Orientation = theRef.Orientation; + anEntry.LocalLocation = theRef.Location; + return aRefId; +} + +//================================================================================================= + +void appendShellRef(BRepGraphInc_Storage& theStorage, + const BRepGraph_SolidId theParentSolidId, + const BRepGraphInc::ShellUsage& theRef) +{ + BRepGraphInc::ShellRef& anEntry = theStorage.AppendShellRef(); + const BRepGraph_ShellRefId aRefId(theStorage.NbShellRefs() - 1); + anEntry.RefId = aRefId; + anEntry.ParentId = BRepGraph_SolidId(theParentSolidId.Index); + anEntry.ShellDefId = theRef.DefId; + anEntry.Orientation = theRef.Orientation; + anEntry.LocalLocation = theRef.Location; + theStorage.ChangeSolid(theParentSolidId).ShellRefIds.Append(aRefId); +} + +//================================================================================================= + +void appendSolidRef(BRepGraphInc_Storage& theStorage, + const BRepGraph_CompSolidId theParentCompSolidId, + const BRepGraphInc::SolidUsage& theRef) +{ + BRepGraphInc::SolidRef& anEntry = theStorage.AppendSolidRef(); + const BRepGraph_SolidRefId aRefId(theStorage.NbSolidRefs() - 1); + anEntry.RefId = aRefId; + anEntry.ParentId = BRepGraph_CompSolidId(theParentCompSolidId.Index); + anEntry.SolidDefId = theRef.DefId; + anEntry.Orientation = theRef.Orientation; + anEntry.LocalLocation = theRef.Location; + theStorage.ChangeCompSolid(theParentCompSolidId).SolidRefIds.Append(aRefId); +} + +//================================================================================================= + +BRepGraph_ChildRefId appendChildRef(BRepGraphInc_Storage& theStorage, + const BRepGraph_NodeId theParentId, + const BRepGraphInc::NodeUsage& theRef) +{ + BRepGraphInc::ChildRef& anEntry = theStorage.AppendChildRef(); + const BRepGraph_ChildRefId aRefId(theStorage.NbChildRefs() - 1); + anEntry.RefId = aRefId; + anEntry.ParentId = theParentId; + anEntry.ChildDefId = theRef.DefId; + anEntry.Orientation = theRef.Orientation; + anEntry.LocalLocation = theRef.Location; + if (theParentId.NodeKind == BRepGraph_NodeId::Kind::Compound) + theStorage.ChangeCompound(BRepGraph_CompoundId(theParentId.Index)).ChildRefIds.Append(aRefId); + return aRefId; +} + +//================================================================================================= + +//! Per-vertex data extracted from TopoDS in parallel phase. +struct ExtractedVertex +{ + TopoDS_Vertex Shape; + gp_Pnt Point; + double Tolerance = 0.0; +}; + +//! Internal/external vertex extracted from an edge. +struct ExtractedInternalVertex +{ + TopoDS_Vertex Shape; + gp_Pnt Point; + double Tolerance = 0.0; + TopAbs_Orientation Orientation = TopAbs_INTERNAL; +}; + +//! Per-edge data extracted from TopoDS in parallel phase. +struct ExtractedEdge +{ + TopoDS_Edge Shape; + occ::handle Curve3d; + double ParamFirst = 0.0; + double ParamLast = 0.0; + double Tolerance = 0.0; + bool IsDegenerate = false; + bool SameParameter = false; + bool SameRange = false; + ExtractedVertex StartVertex; + ExtractedVertex EndVertex; + NCollection_Vector InternalVertices; + TopAbs_Orientation OrientationInWire = TopAbs_FORWARD; + occ::handle PCurve2d; + double PCFirst = 0.0; + double PCLast = 0.0; + occ::handle PCurve2dReversed; + double PCFirstReversed = 0.0; + double PCLastReversed = 0.0; + GeomAbs_Shape PCurveContinuity = GeomAbs_C0; + gp_Pnt2d PCUV1; + gp_Pnt2d PCUV2; + GeomAbs_Shape SeamContinuity = GeomAbs_C0; + occ::handle Polygon3D; + occ::handle PolyOnSurf; + occ::handle PolyOnSurfReversed; +}; + +//! Per-wire data extracted in parallel phase. +struct ExtractedWire +{ + TopoDS_Wire Shape; + bool IsOuter = false; + NCollection_Vector Edges; +}; + +//! All data extracted from a single face. +struct FaceLocalData +{ + // Phase 1 context. + TopoDS_Face Face; + TopLoc_Location ParentGlobalLoc; + int ParentShellIdx = -1; + + // Phase 2 extracted geometry. + occ::handle Surface; + occ::handle OriginalSurface; //!< Pre-transform surface for PCurve matching + NCollection_Vector> Triangulations; + int ActiveTriangulationIndex = -1; + double Tolerance = 0.0; + TopAbs_Orientation Orientation = TopAbs_FORWARD; + bool NaturalRestriction = false; + NCollection_Vector Wires; + NCollection_Vector DirectVertices; //!< INTERNAL/EXTERNAL vertex children +}; + +//! Extract stored PCurve(s) from edge for a given face's surface. +//! Iterates BRep_TEdge::Curves() directly, avoiding BRep_Tool::CurveOnSurface +//! which can compute phantom PCurves via CurveOnPlane for planar surfaces. +//! Uses multi-pass matching: +//! Pass 1: exact (Surface, Location) match via IsCurveOnSurface(S, L) +//! Pass 2: surface-handle-only fallback for TopLoc_Location structural equality bug +//! (only when a unique CR matches the surface - prevents wrong-context selection) +//! Pass 3: original (pre-transform) surface handle match when face surface +//! was transformed via applyRepresentationLocation +//! For seam edges (IsCurveOnClosedSurface), extracts both PCurves + continuity. +//! +//! WARNING: Passes 2-3 are workarounds for TopLoc_Location structural equality +//! issues where an explicit identity datum does not compare equal to a default +//! empty identity. If the upstream TopLoc_Location comparison is fixed, passes +//! 2-3 may become redundant but should remain harmless (pass 1 will match first). +//! @param[in] theEdge edge with context orientation and location +//! @param[in] theFace face with context location +//! @param[out] thePCurve primary PCurve (or null) +//! @param[out] thePCurve2 seam PCurve (or null, only for closed surfaces) +//! @param[out] theFirst parameter range start +//! @param[out] theLast parameter range end +//! @param[out] theSeamContinuity seam continuity (GeomAbs_C0 if non-seam) +//! @param[in] theOrigSurface pre-transform surface handle for fallback matching +//! when the face's raw TFace surface differs from edge CRs +//! @return true if a stored PCurve was found +static bool extractStoredPCurves( + const TopoDS_Edge& theEdge, + const TopoDS_Face& theFace, + occ::handle& thePCurve, + occ::handle& thePCurve2, + double& theFirst, + double& theLast, + GeomAbs_Shape& theSeamContinuity, + const occ::handle& theOrigSurface = occ::handle()) +{ + TopLoc_Location aFaceLoc; + const occ::handle& aSurf = BRep_Tool::Surface(theFace, aFaceLoc); + if (aSurf.IsNull()) + return false; + const occ::handle aTEdge = occ::down_cast(theEdge.TShape()); + if (aTEdge.IsNull()) + return false; + const NCollection_List>& aCurves = aTEdge->Curves(); + const bool aReversed = (theEdge.Orientation() == TopAbs_REVERSED); + + // Expected CurveRepresentation location for this face+edge context. + // This is the same formula used by BRep_Tool::CurveOnSurface internally. + const TopLoc_Location aExpectedLoc = aFaceLoc.Predivided(theEdge.Location()); + + // Lambda to extract PCurve data from a matched CurveRepresentation. + const auto anExtractFromCR = [&](const occ::handle& theCR) -> bool { + const BRep_GCurve* aGC = static_cast(theCR.get()); + aGC->Range(theFirst, theLast); + if (aGC->IsCurveOnClosedSurface()) + { + thePCurve = aReversed ? aGC->PCurve2() : aGC->PCurve(); + thePCurve2 = aReversed ? aGC->PCurve() : aGC->PCurve2(); + theSeamContinuity = static_cast(theCR.get())->Continuity(); + } + else + { + thePCurve = aGC->PCurve(); + } + return true; + }; + + // Pass 1: exact match by (Surface, Location). Correctly distinguishes + // multiple CurveOnSurface entries for the same surface with different Locations. + for (const occ::handle& aCR : aCurves) + { + if (!aCR->IsCurveOnSurface()) + continue; + if (aCR->IsCurveOnSurface(aSurf, aExpectedLoc)) + return anExtractFromCR(aCR); + } + + // Pass 1b: match using raw TFace.Location (preserves datum pointers). + // When Pass 1 fails due to TopLoc_Location structural inequality (the composed + // face.Location() * TFace.Location() creates a chain with different structure + // than what was used during CR creation), retry with JUST TFace.Location(). + // The TFace.Location is a fixed TShape property whose datum pointers are shared + // with the CR's stored location chain, enabling structural match to succeed. + { + const TopLoc_Location& aTFaceLoc = + static_cast(theFace.TShape().get())->Location(); + if (!aTFaceLoc.IsIdentity()) + { + const TopLoc_Location aRawExpectedLoc = aTFaceLoc.Predivided(theEdge.Location()); + for (const occ::handle& aCR : aCurves) + { + if (!aCR->IsCurveOnSurface()) + continue; + if (aCR->IsCurveOnSurface(aSurf, aRawExpectedLoc)) + return anExtractFromCR(aCR); + } + } + } + + // Pass 2: fallback to surface-handle-only match. + // Handles the TopLoc_Location structural equality bug where + // an explicit identity datum does not compare equal to a default empty identity. + for (const occ::handle& aCR : aCurves) + { + if (!aCR->IsCurveOnSurface() || aCR->Surface() != aSurf) + continue; + return anExtractFromCR(aCR); + } + + // Pass 3: match by the original (pre-transform) surface handle. + // When applyRepresentationLocation creates a new surface via Transformed(), + // the edge's CRs still reference the OLD surface handle. If the face's raw + // TFace surface differs from the old surface (e.g., compound children at + // different locations), passes 1-2 fail. Here we retry with the original + // surface handle that the edge CRs actually reference. + if (!theOrigSurface.IsNull() && theOrigSurface != aSurf) + { + // Pass 3a: exact (original surface, expected location) match. + for (const occ::handle& aCR : aCurves) + { + if (!aCR->IsCurveOnSurface()) + continue; + if (aCR->IsCurveOnSurface(theOrigSurface, aExpectedLoc)) + return anExtractFromCR(aCR); + } + // Pass 3a.5: raw TFace.Location match on original surface. + { + const TopLoc_Location& aTFaceLoc = + static_cast(theFace.TShape().get())->Location(); + if (!aTFaceLoc.IsIdentity()) + { + const TopLoc_Location aRawExpectedLoc = aTFaceLoc.Predivided(theEdge.Location()); + for (const occ::handle& aCR : aCurves) + { + if (!aCR->IsCurveOnSurface()) + continue; + if (aCR->IsCurveOnSurface(theOrigSurface, aRawExpectedLoc)) + return anExtractFromCR(aCR); + } + } + } + // Pass 3b: original surface handle-only match (location structural equality fallback). + for (const occ::handle& aCR : aCurves) + { + if (!aCR->IsCurveOnSurface() || aCR->Surface() != theOrigSurface) + continue; + return anExtractFromCR(aCR); + } + } + + return false; +} + +//! Get the raw BRep_TVertex point without applying vertex Location. +//! Stores the point in the TShape-local (definition) frame, consistent +//! with how edge curves and face surfaces are stored after +//! applyRepresentationLocation. +gp_Pnt rawVertexPoint(const TopoDS_Vertex& theVertex) +{ + return static_cast(theVertex.TShape().get())->Pnt(); +} + +//! Map TopAbs_ShapeEnum to BRepGraph_NodeId::Kind. +//! Asserts on unhandled ShapeEnum (e.g., TopAbs_SHAPE). +BRepGraph_NodeId::Kind shapeTypeToNodeKind(TopAbs_ShapeEnum theType) +{ + switch (theType) + { + case TopAbs_COMPOUND: + return BRepGraph_NodeId::Kind::Compound; + case TopAbs_COMPSOLID: + return BRepGraph_NodeId::Kind::CompSolid; + case TopAbs_SOLID: + return BRepGraph_NodeId::Kind::Solid; + case TopAbs_SHELL: + return BRepGraph_NodeId::Kind::Shell; + case TopAbs_FACE: + return BRepGraph_NodeId::Kind::Face; + case TopAbs_WIRE: + return BRepGraph_NodeId::Kind::Wire; + case TopAbs_EDGE: + return BRepGraph_NodeId::Kind::Edge; + case TopAbs_VERTEX: + return BRepGraph_NodeId::Kind::Vertex; + default: + Standard_ASSERT_VOID(false, "shapeTypeToNodeKind: unhandled ShapeEnum"); + return BRepGraph_NodeId::Kind::Solid; // unreachable in practice + } +} + +//! Check if a shape's TShape is already registered in storage with the expected kind. +//! Returns the existing NodeId pointer if found, nullptr otherwise. +const BRepGraph_NodeId* findExistingNode(const BRepGraphInc_Storage& theStorage, + const TopoDS_Shape& theShape, + BRepGraph_NodeId::Kind theExpectedKind) +{ + const BRepGraph_NodeId* anExisting = theStorage.FindNodeByTShape(theShape.TShape().get()); + if (anExisting != nullptr && anExisting->NodeKind == theExpectedKind) + return anExisting; + return nullptr; +} + +//! Register a vertex entity by TShape dedup, or return the existing index. +//! @param[in,out] theStorage incidence storage +//! @param[in] theVertex original TopoDS_Vertex +//! @param[in] thePoint 3D point (pre-extracted) +//! @param[in] theTolerance vertex tolerance (pre-extracted) +//! @return index into the vertex entity vector, or -1 if theVertex is null +int registerOrReuseVertex(BRepGraphInc_Storage& theStorage, + const TopoDS_Vertex& theVertex, + const gp_Pnt& thePoint, + const double theTolerance) +{ + if (theVertex.IsNull()) + return -1; + const BRepGraph_NodeId* anExisting = + findExistingNode(theStorage, theVertex, BRepGraph_NodeId::Kind::Vertex); + if (anExisting != nullptr) + return anExisting->Index; + + BRepGraphInc::VertexDef& aVtxEnt = theStorage.AppendVertex(); + int aVtxIdx = theStorage.NbVertices() - 1; + aVtxEnt.Id = BRepGraph_NodeId(BRepGraph_NodeId::Kind::Vertex, aVtxIdx); + aVtxEnt.Point = thePoint; + aVtxEnt.Tolerance = theTolerance; + theStorage.BindTShapeToNode(theVertex.TShape().get(), aVtxEnt.Id); + theStorage.BindOriginal(aVtxEnt.Id, theVertex); + return aVtxIdx; +} + +//! Convenience overload: extracts point and tolerance from the vertex. +int registerOrReuseVertex(BRepGraphInc_Storage& theStorage, const TopoDS_Vertex& theVertex) +{ + if (theVertex.IsNull()) + return -1; + return registerOrReuseVertex(theStorage, + theVertex, + rawVertexPoint(theVertex), + BRep_Tool::Tolerance(theVertex)); +} + +//! Factor representation location from combined location and apply to geometry. +//! Returns the transformed geometry if the representation location is non-identity, +//! otherwise returns the original unchanged. +//! @tparam T Geom_Surface or Geom_Curve (must support Transformed()) +template +occ::handle applyRepresentationLocation(const occ::handle& theGeom, + const TopLoc_Location& theShapeLoc, + const TopLoc_Location& theCombinedLoc) +{ + if (theGeom.IsNull()) + return theGeom; + // Do NOT use theCombinedLoc.IsIdentity() as an early return - TopLoc_Location + // chain composition can structurally cancel to Identity (empty chain) even when + // the actual repLoc (theShapeLoc^-1 * theCombinedLoc) is non-Identity. + // This happens when the edge instance location and the CR location on TEdge + // form inverse pairs that cancel in Multiplied(). + const TopLoc_Location aRepLoc = theShapeLoc.Inverted() * theCombinedLoc; + if (aRepLoc.IsIdentity()) + return theGeom; + return occ::down_cast(theGeom->Transformed(aRepLoc.Transformation())); +} + +//! Apply representation location to a Polygon3D by transforming its nodes. +static occ::handle applyRepLocationToPolygon3D( + const occ::handle& thePolygon3D, + const TopLoc_Location& theShapeLoc, + const TopLoc_Location& theCombinedLoc) +{ + if (thePolygon3D.IsNull()) + return thePolygon3D; + const TopLoc_Location aRepLoc = theShapeLoc.Inverted() * theCombinedLoc; + if (aRepLoc.IsIdentity()) + return thePolygon3D; + + const gp_Trsf& aTrsf = aRepLoc.Transformation(); + const NCollection_Array1& aNodes = thePolygon3D->Nodes(); + NCollection_Array1 aNewNodes(aNodes.Lower(), aNodes.Upper()); + for (int aNodeIdx = aNodes.Lower(); aNodeIdx <= aNodes.Upper(); ++aNodeIdx) + aNewNodes.SetValue(aNodeIdx, aNodes.Value(aNodeIdx).Transformed(aTrsf)); + occ::handle aTransPoly; + if (thePolygon3D->HasParameters()) + aTransPoly = new Poly_Polygon3D(aNewNodes, thePolygon3D->Parameters()); + else + aTransPoly = new Poly_Polygon3D(aNewNodes); + aTransPoly->Deflection(thePolygon3D->Deflection()); + return aTransPoly; +} + +//! Deduplication maps for representation entities. +//! Keyed by raw Handle pointer - same underlying geometry object -> same rep entity. +struct RepDedup +{ + NCollection_DataMap Surfaces; + NCollection_DataMap Curves3D; + NCollection_DataMap Curves2D; + NCollection_DataMap Triangulations; + NCollection_DataMap Polygons3D; + NCollection_DataMap Polygons2D; + NCollection_DataMap PolygonsOnTri; +}; + +//! Create or reuse a SurfaceRep for the given surface handle. +int getOrCreateSurfaceRep(BRepGraphInc_Storage& theStorage, + RepDedup& theDedup, + const occ::handle& theSurface) +{ + if (theSurface.IsNull()) + return -1; + const Geom_Surface* aPtr = theSurface.get(); + const int* anExisting = theDedup.Surfaces.Seek(aPtr); + if (anExisting != nullptr) + return *anExisting; + BRepGraphInc::SurfaceRep& aRep = theStorage.AppendSurfaceRep(); + const int anIdx = theStorage.NbSurfaces() - 1; + aRep.Id = BRepGraph_RepId::Surface(anIdx); + aRep.Surface = theSurface; + theDedup.Surfaces.Bind(aPtr, anIdx); + return anIdx; +} + +//! Create or reuse a Curve3DRep for the given 3D curve handle. +int getOrCreateCurve3DRep(BRepGraphInc_Storage& theStorage, + RepDedup& theDedup, + const occ::handle& theCurve) +{ + if (theCurve.IsNull()) + return -1; + const Geom_Curve* aPtr = theCurve.get(); + const int* anExisting = theDedup.Curves3D.Seek(aPtr); + if (anExisting != nullptr) + return *anExisting; + BRepGraphInc::Curve3DRep& aRep = theStorage.AppendCurve3DRep(); + const int anIdx = theStorage.NbCurves3D() - 1; + aRep.Id = BRepGraph_RepId::Curve3D(anIdx); + aRep.Curve = theCurve; + theDedup.Curves3D.Bind(aPtr, anIdx); + return anIdx; +} + +//! Create or reuse a Curve2DRep for the given 2D curve handle. +int getOrCreateCurve2DRep(BRepGraphInc_Storage& theStorage, + RepDedup& theDedup, + const occ::handle& theCurve) +{ + if (theCurve.IsNull()) + return -1; + const Geom2d_Curve* aPtr = theCurve.get(); + const int* anExisting = theDedup.Curves2D.Seek(aPtr); + if (anExisting != nullptr) + return *anExisting; + BRepGraphInc::Curve2DRep& aRep = theStorage.AppendCurve2DRep(); + const int anIdx = theStorage.NbCurves2D() - 1; + aRep.Id = BRepGraph_RepId::Curve2D(anIdx); + aRep.Curve = theCurve; + theDedup.Curves2D.Bind(aPtr, anIdx); + return anIdx; +} + +//! Create or reuse a TriangulationRep for the given triangulation handle. +int getOrCreateTriangulationRep(BRepGraphInc_Storage& theStorage, + RepDedup& theDedup, + const occ::handle& theTriangulation) +{ + if (theTriangulation.IsNull()) + return -1; + const Poly_Triangulation* aPtr = theTriangulation.get(); + const int* anExisting = theDedup.Triangulations.Seek(aPtr); + if (anExisting != nullptr) + return *anExisting; + BRepGraphInc::TriangulationRep& aRep = theStorage.AppendTriangulationRep(); + const int anIdx = theStorage.NbTriangulations() - 1; + aRep.Id = BRepGraph_RepId::Triangulation(anIdx); + aRep.Triangulation = theTriangulation; + theDedup.Triangulations.Bind(aPtr, anIdx); + return anIdx; +} + +//! Create or reuse a Polygon3DRep for the given polygon handle. +int getOrCreatePolygon3DRep(BRepGraphInc_Storage& theStorage, + RepDedup& theDedup, + const occ::handle& thePolygon) +{ + if (thePolygon.IsNull()) + return -1; + const Poly_Polygon3D* aPtr = thePolygon.get(); + const int* anExisting = theDedup.Polygons3D.Seek(aPtr); + if (anExisting != nullptr) + return *anExisting; + BRepGraphInc::Polygon3DRep& aRep = theStorage.AppendPolygon3DRep(); + const int anIdx = theStorage.NbPolygons3D() - 1; + aRep.Id = BRepGraph_RepId::Polygon3D(anIdx); + aRep.Polygon = thePolygon; + theDedup.Polygons3D.Bind(aPtr, anIdx); + return anIdx; +} + +//! Create or reuse a Polygon2DRep for the given polygon-on-surface handle. +int getOrCreatePolygon2DRep(BRepGraphInc_Storage& theStorage, + RepDedup& theDedup, + const occ::handle& thePolygon) +{ + if (thePolygon.IsNull()) + return -1; + const Poly_Polygon2D* aPtr = thePolygon.get(); + const int* anExisting = theDedup.Polygons2D.Seek(aPtr); + if (anExisting != nullptr) + return *anExisting; + BRepGraphInc::Polygon2DRep& aRep = theStorage.AppendPolygon2DRep(); + const int anIdx = theStorage.NbPolygons2D() - 1; + aRep.Id = BRepGraph_RepId::Polygon2D(anIdx); + aRep.Polygon = thePolygon; + theDedup.Polygons2D.Bind(aPtr, anIdx); + return anIdx; +} + +//! Create or reuse a PolygonOnTriRep for the given polygon-on-triangulation handle. +//! theTriRepIdx is the global TriangulationRep index (not face-local). +int getOrCreatePolygonOnTriRep(BRepGraphInc_Storage& theStorage, + RepDedup& theDedup, + const occ::handle& thePolygon, + const int theTriRepIdx) +{ + if (thePolygon.IsNull()) + return -1; + const Poly_PolygonOnTriangulation* aPtr = thePolygon.get(); + const int* anExisting = theDedup.PolygonsOnTri.Seek(aPtr); + if (anExisting != nullptr) + return *anExisting; + BRepGraphInc::PolygonOnTriRep& aRep = theStorage.AppendPolygonOnTriRep(); + const int anIdx = theStorage.NbPolygonsOnTri() - 1; + aRep.Id = BRepGraph_RepId::PolygonOnTri(anIdx); + aRep.Polygon = thePolygon; + aRep.TriangulationRepId = BRepGraph_TriangulationRepId(theTriRepIdx); + theDedup.PolygonsOnTri.Bind(aPtr, anIdx); + return anIdx; +} + +//! Register an edge entity from pre-extracted data, with TShape dedup. +//! Creates the entity (with vertices) if new, returns the index in both cases. +int registerExtractedEdge(BRepGraphInc_Storage& theStorage, + const ExtractedEdge& theEdgeData, + RepDedup& theRepDedup) +{ + const BRepGraph_NodeId* anExisting = + findExistingNode(theStorage, theEdgeData.Shape, BRepGraph_NodeId::Kind::Edge); + if (anExisting != nullptr) + return anExisting->Index; + + BRepGraphInc::EdgeDef& anEdgeEnt = theStorage.AppendEdge(); + int anEdgeIdx = theStorage.NbEdges() - 1; + anEdgeEnt.Id = BRepGraph_NodeId(BRepGraph_NodeId::Kind::Edge, anEdgeIdx); + anEdgeEnt.Tolerance = theEdgeData.Tolerance; + anEdgeEnt.IsDegenerate = theEdgeData.IsDegenerate; + anEdgeEnt.SameParameter = theEdgeData.SameParameter; + anEdgeEnt.SameRange = theEdgeData.SameRange; + anEdgeEnt.IsClosed = theEdgeData.Shape.Closed(); + anEdgeEnt.ParamFirst = theEdgeData.ParamFirst; + anEdgeEnt.ParamLast = theEdgeData.ParamLast; + + if (!theEdgeData.Curve3d.IsNull()) + { + anEdgeEnt.Curve3DRepId = + BRepGraph_Curve3DRepId(getOrCreateCurve3DRep(theStorage, theRepDedup, theEdgeData.Curve3d)); + } + + // Vertex registration (boundary vertices stored as VertexRefId into Ref table). + // Vertices may be null for infinite edges or degenerate topology. + if (!theEdgeData.StartVertex.Shape.IsNull()) + { + BRepGraphInc::VertexUsage aVR; + aVR.DefId = BRepGraph_VertexId(registerOrReuseVertex(theStorage, + theEdgeData.StartVertex.Shape, + theEdgeData.StartVertex.Point, + theEdgeData.StartVertex.Tolerance)); + aVR.Orientation = TopAbs_FORWARD; + aVR.Location = theEdgeData.StartVertex.Shape.Location(); + anEdgeEnt.StartVertexRefId = appendVertexRef(theStorage, anEdgeEnt.Id, aVR); + } + if (!theEdgeData.EndVertex.Shape.IsNull()) + { + BRepGraphInc::VertexUsage aVR; + aVR.DefId = BRepGraph_VertexId(registerOrReuseVertex(theStorage, + theEdgeData.EndVertex.Shape, + theEdgeData.EndVertex.Point, + theEdgeData.EndVertex.Tolerance)); + aVR.Orientation = TopAbs_REVERSED; + aVR.Location = theEdgeData.EndVertex.Shape.Location(); + anEdgeEnt.EndVertexRefId = appendVertexRef(theStorage, anEdgeEnt.Id, aVR); + } + + // Register internal/external vertices. + for (const ExtractedInternalVertex& anIntVtx : theEdgeData.InternalVertices) + { + const int anIntVtxIdx = + registerOrReuseVertex(theStorage, anIntVtx.Shape, anIntVtx.Point, anIntVtx.Tolerance); + if (anIntVtxIdx >= 0) + { + BRepGraphInc::VertexUsage aVR; + aVR.DefId = BRepGraph_VertexId(anIntVtxIdx); + aVR.Orientation = anIntVtx.Orientation; + aVR.Location = anIntVtx.Shape.Location(); + anEdgeEnt.InternalVertexRefIds.Append(appendVertexRef(theStorage, anEdgeEnt.Id, aVR)); + } + } + + theStorage.BindTShapeToNode(theEdgeData.Shape.TShape().get(), anEdgeEnt.Id); + theStorage.BindOriginal(anEdgeEnt.Id, theEdgeData.Shape); + return anEdgeIdx; +} + +//! Create a NodeUsage from a child shape, resolving its index via TShape lookup. +//! Returns true if the ref was created successfully (child was found in storage). +bool makeFreeChildRef(const BRepGraphInc_Storage& theStorage, + const TopoDS_Shape& theChild, + BRepGraphInc::NodeUsage& theRef) +{ + const BRepGraph_NodeId* aChildNodeId = theStorage.FindNodeByTShape(theChild.TShape().get()); + if (aChildNodeId == nullptr) + return false; + theRef.DefId = *aChildNodeId; + theRef.Orientation = theChild.Orientation(); + theRef.Location = theChild.Location(); + return true; +} + +//! Extract first, last, and internal/external vertices from an edge. +//! Extract start (FORWARD), end (REVERSED), and internal vertices from an edge. +//! TopoDS_Iterator may yield multiple FORWARD or REVERSED vertices (rare but legal, +//! e.g., edges rebuilt by BRepTools_Modifier). In that case the *last* encountered +//! vertex of each orientation becomes the boundary vertex; earlier ones are demoted +//! to the internal list with their original orientation preserved. INTERNAL and +//! EXTERNAL vertices go directly to the internal list. +static void edgeVertices(const TopoDS_Edge& theEdge, + TopoDS_Vertex& theFirst, + TopoDS_Vertex& theLast, + NCollection_Vector& theInternal) +{ + for (TopoDS_Iterator aVIt(theEdge, false, false); aVIt.More(); aVIt.Next()) + { + if (aVIt.Value().ShapeType() != TopAbs_VERTEX) + continue; + const TopoDS_Vertex aVertex = TopoDS::Vertex(aVIt.Value()); + if (aVertex.Orientation() == TopAbs_FORWARD) + { + if (!theFirst.IsNull()) + { + // Preserve previous FORWARD vertex in internal list. + ExtractedInternalVertex& anIntVtx = theInternal.Appended(); + anIntVtx.Shape = theFirst; + anIntVtx.Point = rawVertexPoint(theFirst); + anIntVtx.Tolerance = BRep_Tool::Tolerance(theFirst); + anIntVtx.Orientation = TopAbs_FORWARD; + } + theFirst = aVertex; + } + else if (aVertex.Orientation() == TopAbs_REVERSED) + { + if (!theLast.IsNull()) + { + // Preserve previous REVERSED vertex in internal list. + ExtractedInternalVertex& anIntVtx = theInternal.Appended(); + anIntVtx.Shape = theLast; + anIntVtx.Point = rawVertexPoint(theLast); + anIntVtx.Tolerance = BRep_Tool::Tolerance(theLast); + anIntVtx.Orientation = TopAbs_REVERSED; + } + theLast = aVertex; + } + else + { + ExtractedInternalVertex& anIntVtx = theInternal.Appended(); + anIntVtx.Shape = aVertex; + anIntVtx.Point = rawVertexPoint(aVertex); + anIntVtx.Tolerance = BRep_Tool::Tolerance(aVertex); + anIntVtx.Orientation = aVertex.Orientation(); + } + } + // Note: do NOT copy theFirst<->theLast when one is null. + // Single-vertex edges (e.g., infinite edges) legitimately have only one vertex. + // Closed edges already have both FORWARD and REVERSED vertices in the iterator. +} + +//! Extract edge geometry and parametric data in a face context. +//! Fills theEdgeData with 3D curve, vertices, PCurves, and polygons. +static void extractEdgeInFace(ExtractedEdge& theEdgeData, + const TopoDS_Edge& theEdge, + const TopoDS_Face& theForwardFace, + const occ::handle& theFaceSurface, + const occ::handle& theOrigSurface) +{ + theEdgeData.Shape = theEdge; + theEdgeData.Tolerance = BRep_Tool::Tolerance(theEdge); + theEdgeData.IsDegenerate = BRep_Tool::Degenerated(theEdge); + theEdgeData.SameParameter = BRep_Tool::SameParameter(theEdge); + theEdgeData.SameRange = BRep_Tool::SameRange(theEdge); + theEdgeData.OrientationInWire = theEdge.Orientation(); + + // 3D curve with representation location applied to definition frame. + { + double aFirst = 0.0, aLast = 0.0; + TopLoc_Location aCurveCombinedLoc; + theEdgeData.Curve3d = BRep_Tool::Curve(theEdge, aCurveCombinedLoc, aFirst, aLast); + theEdgeData.ParamFirst = aFirst; + theEdgeData.ParamLast = aLast; + theEdgeData.Curve3d = applyRepresentationLocation(theEdgeData.Curve3d, + theEdge.Location(), + aCurveCombinedLoc); + } + + // Vertices: use FORWARD-oriented edge for orientation-independent extraction. + TopoDS_Vertex aVFirst, aVLast; + { + const TopoDS_Edge aFwdEdge = TopoDS::Edge(theEdge.Oriented(TopAbs_FORWARD)); + edgeVertices(aFwdEdge, aVFirst, aVLast, theEdgeData.InternalVertices); + } + + if (!aVFirst.IsNull()) + { + theEdgeData.StartVertex.Shape = aVFirst; + theEdgeData.StartVertex.Point = rawVertexPoint(aVFirst); + theEdgeData.StartVertex.Tolerance = BRep_Tool::Tolerance(aVFirst); + } + if (!aVLast.IsNull()) + { + theEdgeData.EndVertex.Shape = aVLast; + theEdgeData.EndVertex.Point = rawVertexPoint(aVLast); + theEdgeData.EndVertex.Tolerance = BRep_Tool::Tolerance(aVLast); + } + + // Extract stored PCurves directly from BRep_TEdge::Curves(), bypassing + // BRep_Tool::CurveOnSurface which can fail due to TopLoc_Location structural + // equality bug and can compute phantom PCurves via CurveOnPlane. + // Uses FORWARD-oriented edge for consistent PCurve pair ordering. + { + const TopoDS_Edge aFwdEdge = TopoDS::Edge(theEdge.Oriented(TopAbs_FORWARD)); + double aPCFirst = 0.0, aPCLast = 0.0; + GeomAbs_Shape aSeamContinuity = GeomAbs_C0; + + extractStoredPCurves(aFwdEdge, + theForwardFace, + theEdgeData.PCurve2d, + theEdgeData.PCurve2dReversed, + aPCFirst, + aPCLast, + aSeamContinuity, + theOrigSurface); + + theEdgeData.PCFirst = aPCFirst; + theEdgeData.PCLast = aPCLast; + theEdgeData.PCurveContinuity = BRep_Tool::MaxContinuity(theEdge); + theEdgeData.SeamContinuity = aSeamContinuity; + + // When the surface was transformed (TFace.Location != Identity -> theFaceSurface + // differs from the raw TFace surface), the stored CR may belong to a different face + // context using the same raw surface. Verify by calling BRep_Tool::CurveOnSurface + // which correctly handles CurveOnPlane for planar surfaces and properly composes + // face+edge locations. If BRep_Tool COMPUTED a PCurve (not stored) AND it differs + // from what we extracted, our stored-only extraction picked a CR from the wrong + // context. Discard ours so reconstruction doesn't attach the wrong PCurve. + if (!theEdgeData.PCurve2d.IsNull() && theFaceSurface != theOrigSurface) + { + double aBTFirst = 0.0, aBTLast = 0.0; + bool aBTIsStored = false; + occ::handle aBTPCurve = + BRep_Tool::CurveOnSurface(aFwdEdge, theForwardFace, aBTFirst, aBTLast, &aBTIsStored); + if (!aBTPCurve.IsNull() && !aBTIsStored && aBTPCurve.get() != theEdgeData.PCurve2d.get()) + { + // BRep_Tool computed a different PCurve (CurveOnPlane) - our stored match + // is from the wrong face context. Discard it. + theEdgeData.PCurve2d.Nullify(); + theEdgeData.PCurve2dReversed.Nullify(); + theEdgeData.PCFirst = 0.0; + theEdgeData.PCLast = 0.0; + aPCFirst = 0.0; + aPCLast = 0.0; + theEdgeData.SeamContinuity = GeomAbs_C0; + } + } + + if (!theEdgeData.PCurve2d.IsNull() && !theFaceSurface.IsNull()) + BRep_Tool::UVPoints(aFwdEdge, theForwardFace, theEdgeData.PCUV1, theEdgeData.PCUV2); + + // For seam edges, extract reversed parameter range. + // The reversed edge accesses PCurve2/PCurve (swapped), so Range gives the same values. + // Fall back to primary range if extraction fails. + if (!theEdgeData.PCurve2dReversed.IsNull()) + { + const TopoDS_Edge aRevEdge = TopoDS::Edge(theEdge.Oriented(TopAbs_REVERSED)); + occ::handle aDummyPC1, aDummyPC2; + GeomAbs_Shape aDummyCont = GeomAbs_C0; + if (!extractStoredPCurves(aRevEdge, + theForwardFace, + aDummyPC1, + aDummyPC2, + theEdgeData.PCFirstReversed, + theEdgeData.PCLastReversed, + aDummyCont)) + { + // Seam edges share the same parameter range; use primary as fallback. + theEdgeData.PCFirstReversed = aPCFirst; + theEdgeData.PCLastReversed = aPCLast; + } + } + } + + // Polygon3D with representation location applied. + { + TopLoc_Location aPoly3DLoc; + theEdgeData.Polygon3D = BRep_Tool::Polygon3D(theEdge, aPoly3DLoc); + theEdgeData.Polygon3D = + applyRepLocationToPolygon3D(theEdgeData.Polygon3D, theEdge.Location(), aPoly3DLoc); + } + + // PolygonOnSurface: use FORWARD-oriented edge for consistent ordering. + { + const TopoDS_Edge aFwdEdge = TopoDS::Edge(theEdge.Oriented(TopAbs_FORWARD)); + theEdgeData.PolyOnSurf = BRep_Tool::PolygonOnSurface(aFwdEdge, theForwardFace); + if (!theEdgeData.PolyOnSurf.IsNull()) + { + const TopoDS_Edge aRevEdge = TopoDS::Edge(theEdge.Oriented(TopAbs_REVERSED)); + occ::handle aPolyOnSurfRev = + BRep_Tool::PolygonOnSurface(aRevEdge, theForwardFace); + if (!aPolyOnSurfRev.IsNull() && aPolyOnSurfRev != theEdgeData.PolyOnSurf) + theEdgeData.PolyOnSurfReversed = aPolyOnSurfRev; + } + } +} + +//! Extract per-face geometry/topology data from TopoDS. +void extractFaceData(FaceLocalData& theData) +{ + const TopoDS_Face& aFace = theData.Face; + + // Extract surface with representation location applied to definition frame. + { + TopLoc_Location aSurfCombinedLoc; + theData.Surface = BRep_Tool::Surface(aFace, aSurfCombinedLoc); + theData.OriginalSurface = theData.Surface; // save pre-transform handle for PCurve matching + theData.Surface = applyRepresentationLocation(theData.Surface, + aFace.Location(), + aSurfCombinedLoc); + } + + // Extract triangulations. + { + TopLoc_Location aTriLoc; + const NCollection_List>& aTriList = + BRep_Tool::Triangulations(aFace, aTriLoc); + occ::handle anActiveTri; + { + TopLoc_Location aDummyLoc; + anActiveTri = BRep_Tool::Triangulation(aFace, aDummyLoc); + } + int aTriIdx = 0; + for (const occ::handle& aTri : aTriList) + { + theData.Triangulations.Append(aTri); + if (!anActiveTri.IsNull() && aTri == anActiveTri) + theData.ActiveTriangulationIndex = aTriIdx; + ++aTriIdx; + } + } + + theData.Tolerance = BRep_Tool::Tolerance(aFace); + theData.Orientation = aFace.Orientation(); + theData.NaturalRestriction = BRep_Tool::NaturalRestriction(aFace); + + const TopoDS_Face aForwardFace = TopoDS::Face(aFace.Oriented(TopAbs_FORWARD)); + const TopoDS_Wire anOuterWire = BRepTools::OuterWire(aForwardFace); + + for (TopoDS_Iterator aChildIt(aForwardFace, false, false); aChildIt.More(); aChildIt.Next()) + { + const TopoDS_Shape& aChild = aChildIt.Value(); + if (aChild.ShapeType() == TopAbs_VERTEX) + { + const TopoDS_Vertex& aVertex = TopoDS::Vertex(aChild); + ExtractedInternalVertex& aVtxData = theData.DirectVertices.Appended(); + aVtxData.Shape = aVertex; + aVtxData.Point = rawVertexPoint(aVertex); + aVtxData.Tolerance = BRep_Tool::Tolerance(aVertex); + aVtxData.Orientation = aVertex.Orientation(); + continue; + } + if (aChild.ShapeType() != TopAbs_WIRE) + continue; + const TopoDS_Wire& aWire = TopoDS::Wire(aChild); + + ExtractedWire aWireData; + aWireData.Shape = aWire; + aWireData.IsOuter = aWire.IsSame(anOuterWire); + + for (TopoDS_Iterator anEdgeIt(aWire, false, false); anEdgeIt.More(); anEdgeIt.Next()) + { + const TopoDS_Shape& anEdgeShape = anEdgeIt.Value(); + if (anEdgeShape.ShapeType() != TopAbs_EDGE) + continue; + ExtractedEdge& anEdgeData = aWireData.Edges.Appended(); + extractEdgeInFace(anEdgeData, + TopoDS::Edge(anEdgeShape), + aForwardFace, + theData.Surface, + theData.OriginalSurface); + } + + theData.Wires.Append(std::move(aWireData)); + } +} + +//! Register pre-extracted face data into incidence storage. +//! Uses unified TShapeToNodeId map and populates OriginalShapes. +void registerFaceData(BRepGraphInc_Storage& theStorage, + const NCollection_Vector& theFaceData, + RepDedup& theRepDedup) +{ + for (const FaceLocalData& aData : theFaceData) + { + const TopoDS_Face& aCurFace = aData.Face; + + // Create or reuse FaceDef. + const BRepGraph_NodeId* anExistingFace = + findExistingNode(theStorage, aCurFace, BRepGraph_NodeId::Kind::Face); + + int aFaceIdx = -1; + bool aIsNewFaceDef = false; + if (anExistingFace != nullptr) + { + aFaceIdx = anExistingFace->Index; + } + else + { + aIsNewFaceDef = true; + BRepGraphInc::FaceDef& aFace = theStorage.AppendFace(); + aFaceIdx = theStorage.NbFaces() - 1; + aFace.Id = BRepGraph_NodeId(BRepGraph_NodeId::Kind::Face, aFaceIdx); + aFace.Tolerance = aData.Tolerance; + aFace.NaturalRestriction = aData.NaturalRestriction; + aFace.SurfaceRepId = + BRepGraph_SurfaceRepId(getOrCreateSurfaceRep(theStorage, theRepDedup, aData.Surface)); + + for (const occ::handle& aTri : aData.Triangulations) + { + aFace.TriangulationRepIds.Append( + BRepGraph_TriangulationRepId(getOrCreateTriangulationRep(theStorage, theRepDedup, aTri))); + } + aFace.ActiveTriangulationIndex = aData.ActiveTriangulationIndex; + + theStorage.BindTShapeToNode(aCurFace.TShape().get(), aFace.Id); + theStorage.BindOriginal(aFace.Id, aCurFace); + } + + // Link face to parent shell (with per-instance location for shared TShapes). + if (aData.ParentShellIdx >= 0) + { + BRepGraphInc::FaceUsage aRef; + aRef.DefId = BRepGraph_FaceId(aFaceIdx); + aRef.Orientation = aData.Orientation; + aRef.Location = aCurFace.Location(); + const BRepGraph_ShellId aShellId(aData.ParentShellIdx); + appendFaceRef(theStorage, aShellId, aRef); + } + + // Pre-fetch face entity for triangulation access in edge loop. + BRepGraphInc::FaceDef& aFaceMut = theStorage.ChangeFace(BRepGraph_FaceId(aFaceIdx)); + const BRepGraphInc::FaceDef& aFaceDef = aFaceMut; + + // Process wires - only for newly created face definitions. + // Shared faces (same TShape referenced multiple times in a shell) must NOT + // duplicate wire/edge/coedge data on the single FaceDef. + if (!aIsNewFaceDef) + continue; + + for (const ExtractedWire& aWireData : aData.Wires) + { + + // Dedup wire by TShape. + const BRepGraph_NodeId* anExistingWire = + findExistingNode(theStorage, aWireData.Shape, BRepGraph_NodeId::Kind::Wire); + + int aWireIdx = -1; + bool aIsNewWireDef = false; + + if (anExistingWire != nullptr) + { + aWireIdx = anExistingWire->Index; + } + else + { + BRepGraphInc::WireDef& aWire = theStorage.AppendWire(); + aWireIdx = theStorage.NbWires() - 1; + aWire.Id = BRepGraph_NodeId(BRepGraph_NodeId::Kind::Wire, aWireIdx); + theStorage.BindTShapeToNode(aWireData.Shape.TShape().get(), aWire.Id); + theStorage.BindOriginal(aWire.Id, aWireData.Shape); + aIsNewWireDef = true; + } + + // Link wire to face. + { + BRepGraphInc::WireUsage aWireRef; + aWireRef.DefId = BRepGraph_WireId(aWireIdx); + aWireRef.Orientation = aWireData.Shape.Orientation(); + aWireRef.Location = aWireData.Shape.Location(); + aWireRef.IsOuter = aWireData.IsOuter; + const BRepGraph_FaceId aFaceId(aFaceIdx); + appendWireRef(theStorage, aFaceId, aWireRef); + } + + for (const ExtractedEdge& anEdgeData : aWireData.Edges) + { + int anEdgeIdx = registerExtractedEdge(theStorage, anEdgeData, theRepDedup); + + // Cache mutable edge reference for subsequent PCurve/Polygon appends. + // Safe: no new edges are appended within this scope. + BRepGraphInc::EdgeDef& anEdgeMut = theStorage.ChangeEdge(BRepGraph_EdgeId(anEdgeIdx)); + + // Create CoEdge entity for this edge-face binding and add CoEdgeUsage to wire. + int aFwdCoEdgeIdx = -1; + int aSeamCoEdgeIdx = -1; + if (aIsNewWireDef) + { + // Create the forward CoEdge. + BRepGraphInc::CoEdgeDef& aCoEdge = theStorage.AppendCoEdge(); + aFwdCoEdgeIdx = theStorage.NbCoEdges() - 1; + const int aCoEdgeIdx = aFwdCoEdgeIdx; + aCoEdge.Id = BRepGraph_CoEdgeId(aCoEdgeIdx); + aCoEdge.EdgeDefId = BRepGraph_EdgeId(anEdgeIdx); + aCoEdge.FaceDefId = BRepGraph_FaceId(aFaceIdx); + aCoEdge.Sense = anEdgeData.OrientationInWire; + + // Populate CoEdge with PCurve and polygon data for this face context. + if (!anEdgeData.PCurve2d.IsNull()) + { + aCoEdge.Curve2DRepId = BRepGraph_Curve2DRepId( + getOrCreateCurve2DRep(theStorage, theRepDedup, anEdgeData.PCurve2d)); + aCoEdge.ParamFirst = anEdgeData.PCFirst; + aCoEdge.ParamLast = anEdgeData.PCLast; + aCoEdge.Continuity = anEdgeData.PCurveContinuity; + aCoEdge.UV1 = anEdgeData.PCUV1; + aCoEdge.UV2 = anEdgeData.PCUV2; + } + aCoEdge.Polygon2DRepId = BRepGraph_Polygon2DRepId( + getOrCreatePolygon2DRep(theStorage, theRepDedup, anEdgeData.PolyOnSurf)); + + // Handle seam edge: create a second CoEdge for the reversed sense. + if (!anEdgeData.PCurve2dReversed.IsNull()) + { + BRepGraphInc::CoEdgeDef& aSeamCoEdge = theStorage.AppendCoEdge(); + aSeamCoEdgeIdx = theStorage.NbCoEdges() - 1; + aSeamCoEdge.Id = BRepGraph_CoEdgeId(aSeamCoEdgeIdx); + aSeamCoEdge.EdgeDefId = BRepGraph_EdgeId(anEdgeIdx); + aSeamCoEdge.FaceDefId = BRepGraph_FaceId(aFaceIdx); + aSeamCoEdge.Sense = TopAbs_REVERSED; + aSeamCoEdge.Curve2DRepId = BRepGraph_Curve2DRepId( + getOrCreateCurve2DRep(theStorage, theRepDedup, anEdgeData.PCurve2dReversed)); + aSeamCoEdge.ParamFirst = anEdgeData.PCFirstReversed; + aSeamCoEdge.ParamLast = anEdgeData.PCLastReversed; + aSeamCoEdge.Continuity = anEdgeData.PCurveContinuity; + aSeamCoEdge.SeamContinuity = anEdgeData.SeamContinuity; + aSeamCoEdge.Polygon2DRepId = BRepGraph_Polygon2DRepId( + getOrCreatePolygon2DRep(theStorage, theRepDedup, anEdgeData.PolyOnSurfReversed)); + + // Link seam pair. + // Note: aCoEdge ref may be invalidated by AppendCoEdge, re-fetch. + theStorage.ChangeCoEdge(BRepGraph_CoEdgeId(aCoEdgeIdx)).SeamPairId = + BRepGraph_CoEdgeId(aSeamCoEdgeIdx); + aSeamCoEdge.SeamPairId = BRepGraph_CoEdgeId(aCoEdgeIdx); + } + + // Add CoEdgeUsage to wire with edge-in-wire Location. + BRepGraphInc::CoEdgeUsage aCoEdgeRef; + aCoEdgeRef.DefId = BRepGraph_CoEdgeId(aCoEdgeIdx); + aCoEdgeRef.Location = anEdgeData.Shape.Location(); + const BRepGraph_WireId aWireId(aWireIdx); + appendCoEdgeRef(theStorage, aWireId, aCoEdgeRef); + + // Note: seam coedge (if any) is NOT added to wire CoEdgeRefs -- + // it shares the same wire position as the forward coedge. + // The seam pair is accessible via SeamPairId on the CoEdgeDef. + // Only the forward coedge ref is in the wire's ordered list. + } + + // Polygon3D (once per edge). + if (!anEdgeData.Polygon3D.IsNull() && !anEdgeMut.Polygon3DRepId.IsValid()) + { + anEdgeMut.Polygon3DRepId = BRepGraph_Polygon3DRepId( + getOrCreatePolygon3DRep(theStorage, theRepDedup, anEdgeData.Polygon3D)); + } + + // Polygon-on-triangulation: extract directly into CoEdge entities. + if (aFwdCoEdgeIdx >= 0) + { + TopLoc_Location aPolyTriLoc; + const bool hasSeamCoEdge = aSeamCoEdgeIdx >= 0; + TopoDS_Edge aRevEdge; + if (hasSeamCoEdge) + aRevEdge = TopoDS::Edge(anEdgeData.Shape.Reversed()); + + for (const BRepGraph_TriangulationRepId& aTriRepIdOrig : aFaceDef.TriangulationRepIds) + { + if (!aTriRepIdOrig.IsValid()) + continue; + const occ::handle& aTri = + theStorage.TriangulationRep(aTriRepIdOrig).Triangulation; + if (aTri.IsNull()) + continue; + + occ::handle aPolyOnTri = + BRep_Tool::PolygonOnTriangulation(anEdgeData.Shape, aTri, aPolyTriLoc); + if (aPolyOnTri.IsNull()) + continue; + + int aTriRepIdx = aTriRepIdOrig.Index; + if (!aPolyTriLoc.IsIdentity()) + { + const TopLoc_Location aRepLoc = anEdgeData.Shape.Location().Inverted() * aPolyTriLoc; + if (!aRepLoc.IsIdentity()) + { + occ::handle aTriCopy = aTri->Copy(); + const gp_Trsf& aTrsf = aRepLoc.Transformation(); + for (int aNodeIdx = 1; aNodeIdx <= aTriCopy->NbNodes(); ++aNodeIdx) + aTriCopy->SetNode(aNodeIdx, aTriCopy->Node(aNodeIdx).Transformed(aTrsf)); + aTriRepIdx = getOrCreateTriangulationRep(theStorage, theRepDedup, aTriCopy); + aFaceMut.TriangulationRepIds.Append(BRepGraph_TriangulationRepId(aTriRepIdx)); + } + } + + const int aPolyOnTriRepIdx = + getOrCreatePolygonOnTriRep(theStorage, theRepDedup, aPolyOnTri, aTriRepIdx); + + theStorage.ChangeCoEdge(BRepGraph_CoEdgeId(aFwdCoEdgeIdx)) + .PolygonOnTriRepIds.Append(BRepGraph_PolygonOnTriRepId(aPolyOnTriRepIdx)); + + // Seam polygon-on-triangulation. + if (hasSeamCoEdge) + { + occ::handle aPolyOnTriRev = + BRep_Tool::PolygonOnTriangulation(aRevEdge, aTri, aPolyTriLoc); + if (!aPolyOnTriRev.IsNull() && aPolyOnTriRev != aPolyOnTri) + { + const int aSeamPolyRepIdx = + getOrCreatePolygonOnTriRep(theStorage, theRepDedup, aPolyOnTriRev, aTriRepIdx); + + theStorage.ChangeCoEdge(BRepGraph_CoEdgeId(aSeamCoEdgeIdx)) + .PolygonOnTriRepIds.Append(BRepGraph_PolygonOnTriRepId(aSeamPolyRepIdx)); + } + } + } + } + } + + // Wire closure: copy directly from the original shape. + if (aIsNewWireDef) + { + theStorage.ChangeWire(BRepGraph_WireId(aWireIdx)).IsClosed = aWireData.Shape.Closed(); + } + } + + // Register direct vertex children of the face (INTERNAL/EXTERNAL). + for (const ExtractedInternalVertex& aDirVtx : aData.DirectVertices) + { + const int aVtxIdx = + registerOrReuseVertex(theStorage, aDirVtx.Shape, aDirVtx.Point, aDirVtx.Tolerance); + if (aVtxIdx >= 0) + { + BRepGraphInc::VertexUsage aVR; + aVR.DefId = BRepGraph_VertexId(aVtxIdx); + aVR.Orientation = aDirVtx.Orientation; + aVR.Location = aDirVtx.Shape.Location(); + const BRepGraph_FaceId aFaceId(aFaceIdx); + theStorage.ChangeFace(aFaceId).VertexRefIds.Append( + appendVertexRef(theStorage, BRepGraph_FaceId(aFaceId.Index), aVR)); + } + } + } +} + +//! Recursively traverse a TopoDS hierarchy, registering container entities +//! (Compound, CompSolid, Solid, Shell) and collecting face contexts into theFaceData. +//! Used by Perform() for Phase 1. +void traverseHierarchy(BRepGraphInc_Storage& theStorage, + NCollection_Vector& theFaceData, + RepDedup& theRepDedup, + const TopoDS_Shape& theCurrentShape, + const TopLoc_Location& theParentGlobalLoc) +{ + if (theCurrentShape.IsNull()) + return; + + switch (theCurrentShape.ShapeType()) + { + case TopAbs_COMPOUND: { + const TopoDS_Compound& aCompound = TopoDS::Compound(theCurrentShape); + if (findExistingNode(theStorage, aCompound, BRepGraph_NodeId::Kind::Compound)) + break; + + BRepGraphInc::CompoundDef& aCompEnt = theStorage.AppendCompound(); + int aCompIdx = theStorage.NbCompounds() - 1; + aCompEnt.Id = BRepGraph_NodeId(BRepGraph_NodeId::Kind::Compound, aCompIdx); + theStorage.BindTShapeToNode(aCompound.TShape().get(), aCompEnt.Id); + theStorage.BindOriginal(aCompEnt.Id, aCompound); + + const TopLoc_Location aGlobalLoc = theParentGlobalLoc * aCompound.Location(); + + for (TopoDS_Iterator aChildIt(aCompound, false, false); aChildIt.More(); aChildIt.Next()) + { + const TopoDS_Shape& aChild = aChildIt.Value(); + BRepGraph_NodeId::Kind aChildKind = shapeTypeToNodeKind(aChild.ShapeType()); + + traverseHierarchy(theStorage, theFaceData, theRepDedup, aChild, aGlobalLoc); + + if (aChild.ShapeType() != TopAbs_SHAPE) + { + // Resolve child index via TShape lookup (handles dedup correctly). + // Face indices are deferred (-1) because faces are registered in Phase 3; + // resolved in the Phase 3a fixup pass after registerFaceData(). + int aChildIdx = -1; + if (aChild.ShapeType() != TopAbs_FACE) + { + const BRepGraph_NodeId* aChildNodeId = + theStorage.FindNodeByTShape(aChild.TShape().get()); + if (aChildNodeId != nullptr) + aChildIdx = aChildNodeId->Index; + } + + BRepGraphInc::NodeUsage aRef; + aRef.DefId = BRepGraph_NodeId(aChildKind, aChildIdx); + aRef.Orientation = aChild.Orientation(); + aRef.Location = aChild.Location(); + const BRepGraph_CompoundId aCompId(aCompIdx); + appendChildRef(theStorage, BRepGraph_CompoundId(aCompId.Index), aRef); + } + } + break; + } + + case TopAbs_COMPSOLID: { + const TopoDS_CompSolid& aCompSolid = TopoDS::CompSolid(theCurrentShape); + if (findExistingNode(theStorage, aCompSolid, BRepGraph_NodeId::Kind::CompSolid)) + break; + + BRepGraphInc::CompSolidDef& aCSolidEnt = theStorage.AppendCompSolid(); + int aCSolidIdx = theStorage.NbCompSolids() - 1; + aCSolidEnt.Id = BRepGraph_NodeId(BRepGraph_NodeId::Kind::CompSolid, aCSolidIdx); + theStorage.BindTShapeToNode(aCompSolid.TShape().get(), aCSolidEnt.Id); + theStorage.BindOriginal(aCSolidEnt.Id, aCompSolid); + + const TopLoc_Location aGlobalLoc = theParentGlobalLoc * aCompSolid.Location(); + + for (TopoDS_Iterator aChildIt(aCompSolid, false, false); aChildIt.More(); aChildIt.Next()) + { + if (aChildIt.Value().ShapeType() != TopAbs_SOLID) + continue; + traverseHierarchy(theStorage, theFaceData, theRepDedup, aChildIt.Value(), aGlobalLoc); + + const BRepGraph_NodeId* aSolidNodeId = + theStorage.FindNodeByTShape(aChildIt.Value().TShape().get()); + if (aSolidNodeId == nullptr) + continue; + + BRepGraphInc::SolidUsage aRef; + aRef.DefId = BRepGraph_SolidId::FromNodeId(*aSolidNodeId); + aRef.Orientation = aChildIt.Value().Orientation(); + aRef.Location = aChildIt.Value().Location(); + const BRepGraph_CompSolidId aCompSolidId(aCSolidIdx); + appendSolidRef(theStorage, aCompSolidId, aRef); + } + break; + } + + case TopAbs_SOLID: { + const TopoDS_Solid& aSolid = TopoDS::Solid(theCurrentShape); + if (findExistingNode(theStorage, aSolid, BRepGraph_NodeId::Kind::Solid)) + break; + + BRepGraphInc::SolidDef& aSolidEnt = theStorage.AppendSolid(); + int aSolidIdx = theStorage.NbSolids() - 1; + aSolidEnt.Id = BRepGraph_NodeId(BRepGraph_NodeId::Kind::Solid, aSolidIdx); + theStorage.BindTShapeToNode(aSolid.TShape().get(), aSolidEnt.Id); + theStorage.BindOriginal(aSolidEnt.Id, aSolid); + + const TopLoc_Location aGlobalLoc = theParentGlobalLoc * aSolid.Location(); + + for (TopoDS_Iterator aChildIt(aSolid, false, false); aChildIt.More(); aChildIt.Next()) + { + const TopoDS_Shape& aChild = aChildIt.Value(); + traverseHierarchy(theStorage, theFaceData, theRepDedup, aChild, aGlobalLoc); + + if (aChild.ShapeType() == TopAbs_SHELL) + { + const BRepGraph_NodeId* aShellNodeId = theStorage.FindNodeByTShape(aChild.TShape().get()); + if (aShellNodeId == nullptr) + continue; + + BRepGraphInc::ShellUsage aRef; + aRef.DefId = BRepGraph_ShellId::FromNodeId(*aShellNodeId); + aRef.Orientation = aChild.Orientation(); + aRef.Location = aChild.Location(); + const BRepGraph_SolidId aSolidId(aSolidIdx); + appendShellRef(theStorage, aSolidId, aRef); + } + else if (aChild.ShapeType() == TopAbs_EDGE || aChild.ShapeType() == TopAbs_VERTEX) + { + BRepGraphInc::NodeUsage aCR; + if (makeFreeChildRef(theStorage, aChild, aCR)) + { + const BRepGraph_SolidId aSolidId(aSolidIdx); + const BRepGraph_ChildRefId aChildRefId = + appendChildRef(theStorage, BRepGraph_SolidId(aSolidId.Index), aCR); + theStorage.ChangeSolid(aSolidId).FreeChildRefIds.Append(aChildRefId); + } + } + } + break; + } + + case TopAbs_SHELL: { + const TopoDS_Shell& aShell = TopoDS::Shell(theCurrentShape); + if (findExistingNode(theStorage, aShell, BRepGraph_NodeId::Kind::Shell)) + break; + + BRepGraphInc::ShellDef& aShellEnt = theStorage.AppendShell(); + int aShellIdx = theStorage.NbShells() - 1; + aShellEnt.Id = BRepGraph_NodeId(BRepGraph_NodeId::Kind::Shell, aShellIdx); + aShellEnt.IsClosed = aShell.Closed(); + theStorage.BindTShapeToNode(aShell.TShape().get(), aShellEnt.Id); + theStorage.BindOriginal(aShellEnt.Id, aShell); + + const TopLoc_Location aGlobalLoc = theParentGlobalLoc * aShell.Location(); + + for (TopoDS_Iterator aChildIt(aShell, false, false); aChildIt.More(); aChildIt.Next()) + { + const TopoDS_Shape& aChild = aChildIt.Value(); + if (aChild.ShapeType() == TopAbs_FACE) + { + FaceLocalData& aData = theFaceData.Appended(); + aData.Face = TopoDS::Face(aChild); + aData.ParentGlobalLoc = aGlobalLoc; + aData.ParentShellIdx = aShellIdx; + } + else if (aChild.ShapeType() == TopAbs_WIRE || aChild.ShapeType() == TopAbs_EDGE) + { + traverseHierarchy(theStorage, theFaceData, theRepDedup, aChild, aGlobalLoc); + + BRepGraphInc::NodeUsage aCR; + if (makeFreeChildRef(theStorage, aChild, aCR)) + { + const BRepGraph_ShellId aShellId(aShellIdx); + const BRepGraph_ChildRefId aChildRefId = + appendChildRef(theStorage, BRepGraph_ShellId(aShellId.Index), aCR); + theStorage.ChangeShell(aShellId).FreeChildRefIds.Append(aChildRefId); + } + } + } + break; + } + + case TopAbs_FACE: { + FaceLocalData& aData = theFaceData.Appended(); + aData.Face = TopoDS::Face(theCurrentShape); + aData.ParentGlobalLoc = theParentGlobalLoc; + aData.ParentShellIdx = -1; + break; + } + + case TopAbs_WIRE: { + const TopoDS_Wire& aWire = TopoDS::Wire(theCurrentShape); + if (findExistingNode(theStorage, aWire, BRepGraph_NodeId::Kind::Wire)) + break; + + BRepGraphInc::WireDef& aWireEnt = theStorage.AppendWire(); + int aWireIdx = theStorage.NbWires() - 1; + aWireEnt.Id = BRepGraph_NodeId(BRepGraph_NodeId::Kind::Wire, aWireIdx); + aWireEnt.IsClosed = aWire.Closed(); + theStorage.BindTShapeToNode(aWire.TShape().get(), aWireEnt.Id); + theStorage.BindOriginal(aWireEnt.Id, aWire); + + for (TopoDS_Iterator anEdgeIt(aWire, false, false); anEdgeIt.More(); anEdgeIt.Next()) + { + if (anEdgeIt.Value().ShapeType() != TopAbs_EDGE) + continue; + const TopoDS_Edge& anEdge = TopoDS::Edge(anEdgeIt.Value()); + + // Recurse to create the edge entity (with dedup). + traverseHierarchy(theStorage, theFaceData, theRepDedup, anEdge, theParentGlobalLoc); + + // Resolve edge index via TShape lookup (handles dedup correctly). + const BRepGraph_NodeId* anEdgeNodeId = theStorage.FindNodeByTShape(anEdge.TShape().get()); + if (anEdgeNodeId != nullptr && anEdgeNodeId->NodeKind == BRepGraph_NodeId::Kind::Edge) + { + // Create CoEdge for free wire (no face context). + BRepGraphInc::CoEdgeDef& aCoEdge = theStorage.AppendCoEdge(); + const int aCoEdgeIdx = theStorage.NbCoEdges() - 1; + aCoEdge.Id = BRepGraph_CoEdgeId(aCoEdgeIdx); + aCoEdge.EdgeDefId = BRepGraph_EdgeId::FromNodeId(*anEdgeNodeId); + aCoEdge.Sense = anEdge.Orientation(); + // FaceDefId left invalid for free wires. + // Curve2d left null for free wires. + + BRepGraphInc::CoEdgeUsage aCoEdgeRef; + aCoEdgeRef.DefId = BRepGraph_CoEdgeId(aCoEdgeIdx); + aCoEdgeRef.Location = anEdge.Location(); + const BRepGraph_WireId aWireId(aWireIdx); + appendCoEdgeRef(theStorage, aWireId, aCoEdgeRef); + } + } + break; + } + + case TopAbs_EDGE: { + const TopoDS_Edge& anEdge = TopoDS::Edge(theCurrentShape); + if (findExistingNode(theStorage, anEdge, BRepGraph_NodeId::Kind::Edge)) + break; + + BRepGraphInc::EdgeDef& anEdgeEnt = theStorage.AppendEdge(); + int anEdgeIdx = theStorage.NbEdges() - 1; + anEdgeEnt.Id = BRepGraph_NodeId(BRepGraph_NodeId::Kind::Edge, anEdgeIdx); + anEdgeEnt.Tolerance = BRep_Tool::Tolerance(anEdge); + anEdgeEnt.IsDegenerate = BRep_Tool::Degenerated(anEdge); + anEdgeEnt.SameParameter = BRep_Tool::SameParameter(anEdge); + anEdgeEnt.SameRange = BRep_Tool::SameRange(anEdge); + anEdgeEnt.IsClosed = anEdge.Closed(); + + // Extract 3D curve with representation location applied to definition frame. + { + double aFirst = 0.0, aLast = 0.0; + TopLoc_Location aCurveCombinedLoc; + occ::handle aCurve3d = + BRep_Tool::Curve(anEdge, aCurveCombinedLoc, aFirst, aLast); + anEdgeEnt.ParamFirst = aFirst; + anEdgeEnt.ParamLast = aLast; + aCurve3d = + applyRepresentationLocation(aCurve3d, anEdge.Location(), aCurveCombinedLoc); + anEdgeEnt.Curve3DRepId = + BRepGraph_Curve3DRepId(getOrCreateCurve3DRep(theStorage, theRepDedup, aCurve3d)); + } + + // Extract vertices. + TopoDS_Vertex aVFirst, aVLast; + NCollection_Vector anInternalVerts; + edgeVertices(anEdge, aVFirst, aVLast, anInternalVerts); + + // Register vertices (using definition-frame points; Location stored on VertexRef). + // Vertices may be null for infinite edges or degenerate topology. + if (!aVFirst.IsNull()) + { + BRepGraphInc::VertexUsage aVR; + aVR.DefId = BRepGraph_VertexId(registerOrReuseVertex(theStorage, + aVFirst, + rawVertexPoint(aVFirst), + BRep_Tool::Tolerance(aVFirst))); + aVR.Orientation = TopAbs_FORWARD; + aVR.Location = aVFirst.Location(); + anEdgeEnt.StartVertexRefId = appendVertexRef(theStorage, anEdgeEnt.Id, aVR); + } + if (!aVLast.IsNull()) + { + BRepGraphInc::VertexUsage aVR; + aVR.DefId = BRepGraph_VertexId(registerOrReuseVertex(theStorage, + aVLast, + rawVertexPoint(aVLast), + BRep_Tool::Tolerance(aVLast))); + aVR.Orientation = TopAbs_REVERSED; + aVR.Location = aVLast.Location(); + anEdgeEnt.EndVertexRefId = appendVertexRef(theStorage, anEdgeEnt.Id, aVR); + } + + for (const ExtractedInternalVertex& anIntVtx : anInternalVerts) + { + const int anIntVtxIdx = + registerOrReuseVertex(theStorage, anIntVtx.Shape, anIntVtx.Point, anIntVtx.Tolerance); + if (anIntVtxIdx >= 0) + { + BRepGraphInc::VertexUsage aVR; + aVR.DefId = BRepGraph_VertexId(anIntVtxIdx); + aVR.Orientation = anIntVtx.Orientation; + aVR.Location = anIntVtx.Shape.Location(); + anEdgeEnt.InternalVertexRefIds.Append(appendVertexRef(theStorage, anEdgeEnt.Id, aVR)); + } + } + + // Polygon3D: apply representation location for consistency. + { + TopLoc_Location aPoly3DLoc; + occ::handle aPolygon3D = BRep_Tool::Polygon3D(anEdge, aPoly3DLoc); + aPolygon3D = applyRepLocationToPolygon3D(aPolygon3D, anEdge.Location(), aPoly3DLoc); + anEdgeEnt.Polygon3DRepId = + BRepGraph_Polygon3DRepId(getOrCreatePolygon3DRep(theStorage, theRepDedup, aPolygon3D)); + } + + theStorage.BindTShapeToNode(anEdge.TShape().get(), anEdgeEnt.Id); + theStorage.BindOriginal(anEdgeEnt.Id, anEdge); + break; + } + + case TopAbs_VERTEX: { + registerOrReuseVertex(theStorage, TopoDS::Vertex(theCurrentShape)); + break; + } + + default: + break; + } +} + +//! Append a root NodeId to the vector, skipping duplicates. +static void appendUniqueRootNode(NCollection_Vector& theRoots, + const BRepGraph_NodeId& theNodeId) +{ + if (!theNodeId.IsValid()) + return; + + for (const BRepGraph_NodeId& aRoot : theRoots) + { + if (aRoot == theNodeId) + return; + } + theRoots.Append(theNodeId); +} + +//! Flatten hierarchy containers away for AppendFlattened(). +//! Face roots are collected for the parallel face pipeline; standalone +//! wire/edge/vertex roots are registered directly through traverseHierarchy(). +void flattenForAppend(BRepGraphInc_Storage& theStorage, + NCollection_Vector& theFaceData, + RepDedup& theRepDedup, + const TopoDS_Shape& theCurrentShape, + const TopLoc_Location& theParentGlobalLoc, + NCollection_Vector* theAppendedRoots) +{ + if (theCurrentShape.IsNull()) + return; + + switch (theCurrentShape.ShapeType()) + { + case TopAbs_COMPOUND: + case TopAbs_COMPSOLID: + case TopAbs_SOLID: + case TopAbs_SHELL: { + for (TopoDS_Iterator aChildIt(theCurrentShape, false, false); aChildIt.More(); + aChildIt.Next()) + { + flattenForAppend(theStorage, + theFaceData, + theRepDedup, + aChildIt.Value(), + theParentGlobalLoc * theCurrentShape.Location(), + theAppendedRoots); + } + break; + } + case TopAbs_FACE: { + FaceLocalData& aData = theFaceData.Appended(); + aData.Face = TopoDS::Face(theCurrentShape); + aData.ParentGlobalLoc = theParentGlobalLoc; + aData.ParentShellIdx = -1; + break; + } + case TopAbs_WIRE: + case TopAbs_EDGE: + case TopAbs_VERTEX: { + traverseHierarchy(theStorage, theFaceData, theRepDedup, theCurrentShape, theParentGlobalLoc); + if (theAppendedRoots != nullptr) + { + const BRepGraph_NodeId* aNodeId = + theStorage.FindNodeByTShape(theCurrentShape.TShape().get()); + if (aNodeId != nullptr) + { + appendUniqueRootNode(*theAppendedRoots, *aNodeId); + } + } + break; + } + default: + break; + } +} + +//================================================================================================= + +void populateRegularityLayer(BRepGraphInc_Storage& theStorage, + BRepGraph_RegularityLayer* theRegularityLayer, + const bool theExtractRegularities, + const int theOldNbEdges, + const occ::handle& theTmpAlloc) +{ + if (theRegularityLayer == nullptr) + return; + + if (theOldNbEdges == 0) + theRegularityLayer->Clear(); + if (!theExtractRegularities) + return; + + // Surface-to-face map covers all faces (new edges may reference old faces). + NCollection_DataMap aSurfToFaceIdx(1, theTmpAlloc); + const int aNbFaces = theStorage.NbFaces(); + for (BRepGraph_FaceId aFaceId(0); aFaceId.IsValid(aNbFaces); ++aFaceId) + { + const BRepGraphInc::FaceDef& aFace = theStorage.Face(aFaceId); + const TopoDS_Shape* anOrigFace = theStorage.FindOriginal(aFace.Id); + if (anOrigFace == nullptr || anOrigFace->IsNull()) + continue; + + TopLoc_Location aLoc; + occ::handle aRawSurf = BRep_Tool::Surface(TopoDS::Face(*anOrigFace), aLoc); + if (!aRawSurf.IsNull()) + aSurfToFaceIdx.TryBind(aRawSurf.get(), aFaceId.Index); + } + + // Only process new edges in incremental mode. + const int aNbEdges = theStorage.NbEdges(); + for (BRepGraph_EdgeId anEdgeId(theOldNbEdges); anEdgeId.IsValid(aNbEdges); ++anEdgeId) + { + const BRepGraphInc::EdgeDef& anEdgeEnt = theStorage.Edge(anEdgeId); + const TopoDS_Shape* anOrigShape = theStorage.FindOriginal(anEdgeEnt.Id); + if (anOrigShape == nullptr || anOrigShape->IsNull()) + continue; + + const TopoDS_Edge& anEdge = TopoDS::Edge(*anOrigShape); + const occ::handle aTEdge = occ::down_cast(anEdge.TShape()); + if (aTEdge.IsNull()) + continue; + + for (const occ::handle& aCRep : aTEdge->Curves()) + { + if (aCRep.IsNull()) + continue; + + const occ::handle aCon2S = + occ::down_cast(aCRep); + if (aCon2S.IsNull()) + continue; + + const Geom_Surface* aSurf1Ptr = aCon2S->Surface().get(); + const Geom_Surface* aSurf2Ptr = aCon2S->Surface2().get(); + if (aSurf1Ptr == nullptr || aSurf2Ptr == nullptr) + continue; + + const int* aFaceIdx1 = aSurfToFaceIdx.Seek(aSurf1Ptr); + const int* aFaceIdx2 = aSurfToFaceIdx.Seek(aSurf2Ptr); + if (aFaceIdx1 == nullptr || aFaceIdx2 == nullptr) + continue; + + theRegularityLayer->SetRegularity(anEdgeId, + BRepGraph_FaceId(*aFaceIdx1), + BRepGraph_FaceId(*aFaceIdx2), + aCon2S->Continuity()); + } + } +} + +//================================================================================================= + +void populateParamLayer(BRepGraphInc_Storage& theStorage, + BRepGraph_ParamLayer* theParamLayer, + const bool theExtractVertexPointReps, + const int theOldNbVertices, + const occ::handle& theTmpAlloc) +{ + if (theParamLayer == nullptr) + return; + + if (theOldNbVertices == 0) + theParamLayer->Clear(); + if (!theExtractVertexPointReps) + return; + + NCollection_DataMap aCurveToEdgeDef(1, theTmpAlloc); + const int aNbEdges = theStorage.NbEdges(); + for (BRepGraph_EdgeId anEdgeId(0); anEdgeId.IsValid(aNbEdges); ++anEdgeId) + { + const BRepGraphInc::EdgeDef& anEdgeEnt = theStorage.Edge(anEdgeId); + const TopoDS_Shape* anOrigEdge = theStorage.FindOriginal(anEdgeEnt.Id); + if (anOrigEdge == nullptr || anOrigEdge->IsNull()) + continue; + + double aFirst = 0.0; + double aLast = 0.0; + TopLoc_Location aLoc; + occ::handle aRawCurve = + BRep_Tool::Curve(TopoDS::Edge(*anOrigEdge), aLoc, aFirst, aLast); + if (!aRawCurve.IsNull()) + aCurveToEdgeDef.TryBind(aRawCurve.get(), anEdgeEnt.Id); + } + + NCollection_DataMap aSurfToFaceDef(1, theTmpAlloc); + NCollection_Vector aFaceRawSurfaces(1, theTmpAlloc); + const int aNbFaces = theStorage.NbFaces(); + for (BRepGraph_FaceId aFaceId(0); aFaceId.IsValid(aNbFaces); ++aFaceId) + { + const BRepGraphInc::FaceDef& aFaceEnt = theStorage.Face(aFaceId); + const TopoDS_Shape* anOrigFace = theStorage.FindOriginal(aFaceEnt.Id); + const Geom_Surface* aRawSurfPtr = nullptr; + if (anOrigFace != nullptr && !anOrigFace->IsNull()) + { + TopLoc_Location aLoc; + occ::handle aRawSurf = BRep_Tool::Surface(TopoDS::Face(*anOrigFace), aLoc); + if (!aRawSurf.IsNull()) + { + aSurfToFaceDef.TryBind(aRawSurf.get(), aFaceEnt.Id); + aRawSurfPtr = aRawSurf.get(); + } + } + aFaceRawSurfaces.Append(aRawSurfPtr); + } + + NCollection_DataMap> aPCurveToCoEdges( + 1, + theTmpAlloc); + const int aNbCoEdges = theStorage.NbCoEdges(); + for (BRepGraph_CoEdgeId aCoEdgeId(0); aCoEdgeId.IsValid(aNbCoEdges); ++aCoEdgeId) + { + const BRepGraphInc::CoEdgeDef& aCoEdge = theStorage.CoEdge(aCoEdgeId); + if (!aCoEdge.Curve2DRepId.IsValid() || !aCoEdge.FaceDefId.IsValid(theStorage.NbFaces())) + continue; + + const occ::handle& aPCurve = theStorage.Curve2DRep(aCoEdge.Curve2DRepId).Curve; + if (aPCurve.IsNull()) + continue; + + NCollection_Vector* aCoEdges = aPCurveToCoEdges.ChangeSeek(aPCurve.get()); + if (aCoEdges == nullptr) + { + NCollection_Vector aNewCoEdges(1, theTmpAlloc); + aNewCoEdges.Append(aCoEdgeId); + aPCurveToCoEdges.Bind(aPCurve.get(), aNewCoEdges); + } + else + { + aCoEdges->Append(aCoEdgeId); + } + } + + // Only process new vertices in incremental mode. + const int aNbVertices = theStorage.NbVertices(); + for (BRepGraph_VertexId aVertexId(theOldNbVertices); aVertexId.IsValid(aNbVertices); ++aVertexId) + { + const BRepGraphInc::VertexDef& aVtxDef = theStorage.Vertex(aVertexId); + const TopoDS_Shape* aVtxShape = theStorage.FindOriginal(aVtxDef.Id); + if (aVtxShape == nullptr || aVtxShape->IsNull()) + continue; + + const TopoDS_Vertex& aVertex = TopoDS::Vertex(*aVtxShape); + const occ::handle& aTVertex = occ::down_cast(aVertex.TShape()); + if (aTVertex.IsNull()) + continue; + + for (const occ::handle& aPtRep : aTVertex->Points()) + { + if (aPtRep.IsNull()) + continue; + + if (const occ::handle aPOC = occ::down_cast(aPtRep)) + { + const BRepGraph_NodeId* anEdgeId = aCurveToEdgeDef.Seek(aPOC->Curve().get()); + if (anEdgeId != nullptr) + theParamLayer->SetPointOnCurve(aVertexId, + BRepGraph_EdgeId::FromNodeId(*anEdgeId), + aPOC->Parameter()); + } + else if (const occ::handle aPOCS = + occ::down_cast(aPtRep)) + { + const NCollection_Vector* aCandidates = + aPCurveToCoEdges.Seek(aPOCS->PCurve().get()); + if (aCandidates == nullptr) + continue; + + const Geom_Surface* aSurfacePtr = aPOCS->Surface().get(); + BRepGraph_CoEdgeId aMatchedCoEdge; + for (const BRepGraph_CoEdgeId& aCoEdgeId : *aCandidates) + { + const BRepGraphInc::CoEdgeDef& aCoEdge = theStorage.CoEdge(aCoEdgeId); + if (!aCoEdge.FaceDefId.IsValid(aFaceRawSurfaces.Length())) + continue; + if (aFaceRawSurfaces.Value(aCoEdge.FaceDefId.Index) == aSurfacePtr) + { + aMatchedCoEdge = aCoEdgeId; + break; + } + } + + if (aMatchedCoEdge.IsValid()) + theParamLayer->SetPointOnPCurve(aVertexId, aMatchedCoEdge, aPOCS->Parameter()); + } + else if (const occ::handle aPOS = + occ::down_cast(aPtRep)) + { + const BRepGraph_NodeId* aFaceId = aSurfToFaceDef.Seek(aPOS->Surface().get()); + if (aFaceId != nullptr) + { + theParamLayer->SetPointOnSurface(aVertexId, + BRepGraph_FaceId::FromNodeId(*aFaceId), + aPOS->Parameter(), + aPOS->Parameter2()); + } + } + } + } +} + +//================================================================================================= + +void populateOptionalLayers(BRepGraphInc_Storage& theStorage, + BRepGraph_ParamLayer* theParamLayer, + BRepGraph_RegularityLayer* theRegularityLayer, + const BRepGraphInc_Populate::Options& theOptions, + const int theOldNbEdges, + const int theOldNbVertices, + const occ::handle& theTmpAlloc) +{ + populateRegularityLayer(theStorage, + theRegularityLayer, + theOptions.ExtractRegularities, + theOldNbEdges, + theTmpAlloc); + populateParamLayer(theStorage, + theParamLayer, + theOptions.ExtractVertexPointReps, + theOldNbVertices, + theTmpAlloc); +} + +} // anonymous namespace + +//================================================================================================= + +void BRepGraphInc_Populate::Perform(BRepGraphInc_Storage& theStorage, + const TopoDS_Shape& theShape, + const bool theParallel, + const Options& theOptions, + BRepGraph_ParamLayer* theParamLayer, + BRepGraph_RegularityLayer* theRegularityLayer, + const occ::handle& theTmpAlloc) +{ + theStorage.Clear(); + + if (theShape.IsNull()) + return; + + // Use temporary allocator if provided, else default. + // Must NOT use the storage's persistent allocator for scratch data. + const occ::handle& aTmpAlloc = + !theTmpAlloc.IsNull() ? theTmpAlloc : NCollection_BaseAllocator::CommonBaseAllocator(); + const int aParallelWorkers = theParallel ? BRepGraph_ParallelPolicy::WorkerCount() : 1; + + // Phase 1 (sequential): Recursively explore hierarchy, collecting face contexts. + NCollection_Vector aFaceData(256, aTmpAlloc); + RepDedup aRepDedup; + + traverseHierarchy(theStorage, aFaceData, aRepDedup, theShape, TopLoc_Location()); + + // Phase 2 (parallel): Extract per-face geometry/topology. + BRepGraph_ParallelPolicy::Workload aFaceExtractWork; + aFaceExtractWork.PrimaryItems = aFaceData.Length(); + const bool isParallelFaceExtraction = + BRepGraph_ParallelPolicy::ShouldRun(theParallel, aParallelWorkers, aFaceExtractWork); + OSD_Parallel::For( + 0, + aFaceData.Length(), + [&](const int theIndex) { extractFaceData(aFaceData.ChangeValue(theIndex)); }, + !isParallelFaceExtraction); + + // Phase 3 (sequential): Register into storage with deduplication. + registerFaceData(theStorage, aFaceData, aRepDedup); + + // Phase 3a: Fix compound Face ChildRefs (face indices were unknown during Phase 1, + // resolved now after registerFaceData). All other child types were resolved + // immediately in traverseShape via FindNodeByTShape. + const int aNbCompounds = theStorage.myCompounds.Nb(); + for (BRepGraph_CompoundId aCompoundId(0); aCompoundId.IsValid(aNbCompounds); ++aCompoundId) + { + BRepGraphInc::CompoundDef& aComp = theStorage.myCompounds.Change(aCompoundId.Index); + const TopoDS_Shape* aCompOrig = theStorage.myOriginalShapes.Seek(aComp.Id); + if (aCompOrig == nullptr) + continue; + + int aRefOrd = 0; + for (TopoDS_Iterator aChildIt(*aCompOrig, false, false); aChildIt.More(); aChildIt.Next()) + { + const BRepGraph_NodeId aParentId = aComp.Id; + BRepGraphInc::ChildRef* aRef = nullptr; + int aCurrentOrd = 0; + const int aNbChildRefs = theStorage.NbChildRefs(); + for (BRepGraph_ChildRefId aChildRefId(0); aChildRefId.IsValid(aNbChildRefs); ++aChildRefId) + { + BRepGraphInc::ChildRef& aCandidate = theStorage.ChangeChildRef(aChildRefId); + if (aCandidate.ParentId != aParentId || aCandidate.IsRemoved) + continue; + if (aCurrentOrd == aRefOrd) + { + aRef = &aCandidate; + break; + } + ++aCurrentOrd; + } + + if (aRef == nullptr) + break; + + if (!aRef->ChildDefId.IsValid()) + { + const BRepGraph_NodeId* aNodeId = + theStorage.myTShapeToNodeId.Seek(aChildIt.Value().TShape().get()); + if (aNodeId != nullptr && aNodeId->NodeKind == aRef->ChildDefId.NodeKind) + aRef->ChildDefId = *aNodeId; + } + ++aRefOrd; + } + } + + populateOptionalLayers(theStorage, + theParamLayer, + theRegularityLayer, + theOptions, + 0, + 0, + aTmpAlloc); + + // Build reverse indices. + theStorage.BuildReverseIndex(); + + theStorage.myIsDone = true; +} + +//================================================================================================= + +void BRepGraphInc_Populate::AppendFlattened( + BRepGraphInc_Storage& theStorage, + const TopoDS_Shape& theShape, + const bool theParallel, + NCollection_Vector& theAppendedRoots, + const Options& theOptions, + BRepGraph_ParamLayer* theParamLayer, + BRepGraph_RegularityLayer* theRegularityLayer, + const occ::handle& theTmpAlloc) +{ + if (theShape.IsNull()) + return; + + // Use temporary allocator if provided, else default. + // Must NOT use the storage's persistent allocator for scratch data. + const occ::handle& aTmpAlloc = + !theTmpAlloc.IsNull() ? theTmpAlloc : NCollection_BaseAllocator::CommonBaseAllocator(); + const int aParallelWorkers = theParallel ? BRepGraph_ParallelPolicy::WorkerCount() : 1; + + // Snapshot entity counts before appending, for incremental updates. + const int anOldNbEdges = theStorage.NbEdges(); + const int anOldNbWires = theStorage.NbWires(); + const int anOldNbFaces = theStorage.NbFaces(); + const int anOldNbShells = theStorage.NbShells(); + const int anOldNbSolids = theStorage.NbSolids(); + const int anOldNbVertices = theStorage.NbVertices(); + + // Collect face contexts by flattening hierarchy. + NCollection_Vector aFaceData(256, aTmpAlloc); + RepDedup aRepDedup; + + flattenForAppend(theStorage, + aFaceData, + aRepDedup, + theShape, + TopLoc_Location(), + &theAppendedRoots); + + // Parallel face extraction. + BRepGraph_ParallelPolicy::Workload aFaceExtractWork; + aFaceExtractWork.PrimaryItems = aFaceData.Length(); + const bool isParallelFaceExtraction = + BRepGraph_ParallelPolicy::ShouldRun(theParallel, aParallelWorkers, aFaceExtractWork); + OSD_Parallel::For( + 0, + aFaceData.Length(), + [&](const int theIndex) { extractFaceData(aFaceData.ChangeValue(theIndex)); }, + !isParallelFaceExtraction); + + // Sequential registration (reuses existing dedup maps). + registerFaceData(theStorage, aFaceData, aRepDedup); + + for (const FaceLocalData& aFaceDataElem : aFaceData) + { + const BRepGraph_NodeId* aFaceNodeId = + theStorage.FindNodeByTShape(aFaceDataElem.Face.TShape().get()); + if (aFaceNodeId != nullptr) + { + appendUniqueRootNode(theAppendedRoots, *aFaceNodeId); + } + } + + populateOptionalLayers(theStorage, + theParamLayer, + theRegularityLayer, + theOptions, + anOldNbEdges, + anOldNbVertices, + aTmpAlloc); + + // Incrementally update reverse indices for newly appended entities only. + theStorage.BuildDeltaReverseIndex(anOldNbEdges, + anOldNbWires, + anOldNbFaces, + anOldNbShells, + anOldNbSolids); + + theStorage.myIsDone = true; +} diff --git a/src/ModelingData/TKBRep/BRepGraphInc/BRepGraphInc_Populate.hxx b/src/ModelingData/TKBRep/BRepGraphInc/BRepGraphInc_Populate.hxx new file mode 100644 index 0000000000..71b240c6b7 --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraphInc/BRepGraphInc_Populate.hxx @@ -0,0 +1,101 @@ +// Copyright (c) 2026 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 _BRepGraphInc_Populate_HeaderFile +#define _BRepGraphInc_Populate_HeaderFile + +#include + +#include +#include +#include + +class TopoDS_Shape; +class BRepGraphInc_Storage; +class BRepGraph_ParamLayer; +class BRepGraph_RegularityLayer; + +//! @brief Backend population pipeline for BRepGraphInc_Storage. +//! +//! This class is part of the BRepGraphInc backend and is intended for +//! backend maintenance, tests, and low-level infrastructure only. +//! External code should enter through BRepGraph::Build(), which owns the +//! public lifecycle, cache invalidation, and layer coordination. +//! +//! Adapted from BRepGraph_Builder, but writes to incidence-table storage +//! instead of Def/Usage two-layer storage. Entity structs carry forward +//! child references directly (no separate Usage objects). +//! +//! The population pipeline: +//! 1. Sequential hierarchy traversal (Compound/CompSolid/Solid/Shell) +//! 2. Parallel per-face geometry extraction +//! 3. Sequential registration with TShape deduplication +//! 4. Reverse index construction +class BRepGraphInc_Populate +{ +public: + DEFINE_STANDARD_ALLOC + + //! Options controlling which post-passes are executed during population. + struct Options + { + bool ExtractRegularities; //!< Phase 3b: edge regularities + bool ExtractVertexPointReps; //!< Phase 3c: vertex point representations + + Options() + : ExtractRegularities(true), + ExtractVertexPointReps(true) + { + } + }; + + //! Build backend incidence storage from a TopoDS_Shape. + //! @param[out] theStorage storage to populate (cleared first) + //! @param[in] theShape root shape + //! @param[in] theParallel if true, face-level extraction runs in parallel + //! @param[in] theOptions optional post-pass controls + static Standard_EXPORT void Perform(BRepGraphInc_Storage& theStorage, + const TopoDS_Shape& theShape, + const bool theParallel, + const Options& theOptions = Options(), + BRepGraph_ParamLayer* theParamLayer = nullptr, + BRepGraph_RegularityLayer* theRegularityLayer = nullptr, + const occ::handle& theTmpAlloc = + occ::handle()); + + //! Extend existing backend storage with additional shapes (no clear). + //! Flattens hierarchy containers away; Solid/Shell/Compound/CompSolid inputs + //! contribute appended face roots instead of container entities. + //! Recomputes the built-in metadata layers from the populated storage. + //! @param[in,out] theStorage storage to extend + //! @param[in] theShape shape to append + //! @param[in] theParallel if true, face-level extraction runs in parallel + //! @param[out] theAppendedRoots collected root NodeIds for non-container shapes + //! @param[in] theOptions optional post-pass controls + //! @param[in] theTmpAlloc optional allocator for temporary scratch data + static Standard_EXPORT void AppendFlattened( + BRepGraphInc_Storage& theStorage, + const TopoDS_Shape& theShape, + const bool theParallel, + NCollection_Vector& theAppendedRoots, + const Options& theOptions = Options(), + BRepGraph_ParamLayer* theParamLayer = nullptr, + BRepGraph_RegularityLayer* theRegularityLayer = nullptr, + const occ::handle& theTmpAlloc = + occ::handle()); + +private: + BRepGraphInc_Populate() = delete; +}; + +#endif // _BRepGraphInc_Populate_HeaderFile diff --git a/src/ModelingData/TKBRep/BRepGraphInc/BRepGraphInc_Reconstruct.cxx b/src/ModelingData/TKBRep/BRepGraphInc/BRepGraphInc_Reconstruct.cxx new file mode 100644 index 0000000000..9ef93e8d07 --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraphInc/BRepGraphInc_Reconstruct.cxx @@ -0,0 +1,864 @@ +// Copyright (c) 2026 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 + +//================================================================================================= + +static void restoreEdgeRegularities(const BRepGraph_RegularityLayer* theRegularities, + const BRepGraph_EdgeId theEdgeId, + BRepGraphInc_Reconstruct::Cache& theCache, + BRep_Builder& theBuilder, + TopoDS_Edge& theEdgeShape) +{ + if (theRegularities == nullptr) + return; + + const BRepGraph_RegularityLayer::EdgeRegularities* aRegularities = + theRegularities->FindEdgeRegularities(theEdgeId); + if (aRegularities == nullptr) + return; + + for (const BRepGraph_RegularityLayer::RegularityEntry& aRegEntry : aRegularities->Entries) + { + const TopoDS_Shape* aFaceShape1 = theCache.Seek(aRegEntry.FaceEntity1); + const TopoDS_Shape* aFaceShape2 = theCache.Seek(aRegEntry.FaceEntity2); + if (aFaceShape1 != nullptr && aFaceShape2 != nullptr) + { + theBuilder.Continuity(theEdgeShape, + TopoDS::Face(*aFaceShape1), + TopoDS::Face(*aFaceShape2), + aRegEntry.Continuity); + } + } +} + +//================================================================================================= + +static void restoreVertexPointReps(const BRepGraphInc_Storage& theStorage, + const BRepGraph_ParamLayer* theParams, + const BRepGraph_VertexId theVertexId, + BRepGraphInc_Reconstruct::Cache& theCache, + BRep_Builder& theBuilder) +{ + if (theParams == nullptr || !theVertexId.IsValid(theStorage.NbVertices())) + return; + + const BRepGraph_ParamLayer::VertexParams* aParams = theParams->FindVertexParams(theVertexId); + if (aParams == nullptr || aParams->IsEmpty()) + return; + + const TopoDS_Shape* aVtxCached = theCache.Seek(theVertexId); + if (aVtxCached == nullptr || aVtxCached->IsNull()) + return; + + const BRepGraphInc::VertexDef& aVtxDef = theStorage.Vertex(theVertexId); + TopoDS_Vertex aVtxShape = TopoDS::Vertex(*aVtxCached); + + for (const BRepGraph_ParamLayer::PointOnCurveEntry& aPOC : aParams->PointsOnCurve) + { + const TopoDS_Shape* anEdgeCached = theCache.Seek(aPOC.EdgeDefId); + if (anEdgeCached != nullptr && !anEdgeCached->IsNull()) + theBuilder.UpdateVertex(aVtxShape, + aPOC.Parameter, + TopoDS::Edge(*anEdgeCached), + aVtxDef.Tolerance); + } + + for (const BRepGraph_ParamLayer::PointOnSurfaceEntry& aPOS : aParams->PointsOnSurface) + { + const TopoDS_Shape* aFaceCached = theCache.Seek(aPOS.FaceDefId); + if (aFaceCached != nullptr && !aFaceCached->IsNull()) + { + theBuilder.UpdateVertex(aVtxShape, + aPOS.ParameterU, + aPOS.ParameterV, + TopoDS::Face(*aFaceCached), + aVtxDef.Tolerance); + } + } + + for (const BRepGraph_ParamLayer::PointOnPCurveEntry& aPOPC : aParams->PointsOnPCurve) + { + if (!aPOPC.CoEdgeDefId.IsValid(theStorage.NbCoEdges())) + continue; + const BRepGraphInc::CoEdgeDef& aCoEdge = theStorage.CoEdge(aPOPC.CoEdgeDefId); + if (!aCoEdge.Curve2DRepId.IsValid()) + continue; + + const TopoDS_Shape* anEdgeCached = theCache.Seek(aCoEdge.EdgeDefId); + const TopoDS_Shape* aFaceCached = theCache.Seek(aCoEdge.FaceDefId); + if (anEdgeCached != nullptr && !anEdgeCached->IsNull() && aFaceCached != nullptr + && !aFaceCached->IsNull()) + { + theBuilder.UpdateVertex(aVtxShape, + aPOPC.Parameter, + TopoDS::Edge(*anEdgeCached), + TopoDS::Face(*aFaceCached), + aVtxDef.Tolerance); + } + } +} + +TopoDS_Shape BRepGraphInc_Reconstruct::Node(const BRepGraphInc_Storage& theStorage, + const BRepGraph_NodeId theNode, + const BRepGraph_ParamLayer* theParams, + const BRepGraph_RegularityLayer* theRegularities) +{ + Cache aCache; + return Node(theStorage, theNode, aCache, theParams, theRegularities); +} + +//================================================================================================= + +TopoDS_Shape BRepGraphInc_Reconstruct::Node(const BRepGraphInc_Storage& theStorage, + const BRepGraph_NodeId theNode, + Cache& theCache, + const BRepGraph_ParamLayer* theParams, + const BRepGraph_RegularityLayer* theRegularities) +{ + if (!theNode.IsValid()) + return TopoDS_Shape(); + + // Check cache first. + const TopoDS_Shape* aCached = theCache.Seek(theNode); + if (aCached != nullptr) + return *aCached; + + BRep_Builder aBB; + TopoDS_Shape aResult; + + switch (theNode.NodeKind) + { + case BRepGraph_NodeId::Kind::Vertex: { + const BRepGraphInc::VertexDef& aVtx = theStorage.Vertex(BRepGraph_VertexId(theNode.Index)); + TopoDS_Vertex aNewVtx; + aBB.MakeVertex(aNewVtx, aVtx.Point, aVtx.Tolerance); + aResult = aNewVtx; + break; + } + + case BRepGraph_NodeId::Kind::Edge: { + const BRepGraphInc::EdgeDef& anEdge = theStorage.Edge(BRepGraph_EdgeId(theNode.Index)); + TopoDS_Edge aNewEdge; + if (anEdge.IsDegenerate) + { + aBB.MakeEdge(aNewEdge); + aBB.Degenerated(aNewEdge, true); + } + else if (anEdge.Curve3DRepId.IsValid()) + { + const occ::handle& aCurve3d = theStorage.Curve3DRep(anEdge.Curve3DRepId).Curve; + if (!aCurve3d.IsNull()) + aBB.MakeEdge(aNewEdge, aCurve3d, TopLoc_Location(), anEdge.Tolerance); + else + aBB.MakeEdge(aNewEdge); + } + else + { + aBB.MakeEdge(aNewEdge); + } + aBB.Range(aNewEdge, anEdge.ParamFirst, anEdge.ParamLast); + aBB.SameParameter(aNewEdge, anEdge.SameParameter); + aBB.SameRange(aNewEdge, anEdge.SameRange); + + if (anEdge.StartVertexRefId.IsValid()) + { + const BRepGraphInc::VertexRef& aStartVR = theStorage.VertexRef(anEdge.StartVertexRefId); + TopoDS_Shape aStartVtx = + Node(theStorage, aStartVR.VertexDefId, theCache, theParams, theRegularities); + if (!aStartVtx.IsNull()) + { + aStartVtx.Orientation(TopAbs_FORWARD); + if (!aStartVR.LocalLocation.IsIdentity()) + aStartVtx.Location(aStartVR.LocalLocation); + aBB.Add(aNewEdge, aStartVtx); + } + } + if (anEdge.EndVertexRefId.IsValid()) + { + const BRepGraphInc::VertexRef& anEndVR = theStorage.VertexRef(anEdge.EndVertexRefId); + TopoDS_Shape anEndVtx = + Node(theStorage, anEndVR.VertexDefId, theCache, theParams, theRegularities); + if (!anEndVtx.IsNull()) + { + anEndVtx.Orientation(TopAbs_REVERSED); + if (!anEndVR.LocalLocation.IsIdentity()) + anEndVtx.Location(anEndVR.LocalLocation); + aBB.Add(aNewEdge, anEndVtx); + } + } + for (const BRepGraph_VertexRefId& aVRefId : anEdge.InternalVertexRefIds) + { + const BRepGraphInc::VertexRef& aVR = theStorage.VertexRef(aVRefId); + TopoDS_Shape aVtx = Node(theStorage, aVR.VertexDefId, theCache, theParams, theRegularities); + if (!aVtx.IsNull()) + { + aVtx.Orientation(aVR.Orientation); + if (!aVR.LocalLocation.IsIdentity()) + aVtx.Location(aVR.LocalLocation); + aBB.Add(aNewEdge, aVtx); + } + } + // Attach Polygon3D discretization. + if (anEdge.Polygon3DRepId.IsValid()) + { + const occ::handle& aPolygon3D = + theStorage.Polygon3DRep(anEdge.Polygon3DRepId).Polygon; + if (!aPolygon3D.IsNull()) + aBB.UpdateEdge(aNewEdge, aPolygon3D, TopLoc_Location()); + } + if (anEdge.IsClosed) + aNewEdge.Closed(true); + aResult = aNewEdge; + break; + } + + case BRepGraph_NodeId::Kind::Wire: { + const BRepGraphInc::WireDef& aWire = theStorage.Wire(BRepGraph_WireId(theNode.Index)); + TopoDS_Wire aNewWire; + aBB.MakeWire(aNewWire); + for (const BRepGraph_CoEdgeRefId& aCoEdgeRefId : aWire.CoEdgeRefIds) + { + const BRepGraphInc::CoEdgeRef& aCoEdgeRef = theStorage.CoEdgeRef(aCoEdgeRefId); + if (aCoEdgeRef.IsRemoved || !aCoEdgeRef.CoEdgeDefId.IsValid(theStorage.NbCoEdges())) + continue; + const BRepGraphInc::CoEdgeDef& aCoEdge = + theStorage.CoEdge(BRepGraph_CoEdgeId(aCoEdgeRef.CoEdgeDefId.Index)); + if (aCoEdge.IsRemoved || !aCoEdge.EdgeDefId.IsValid(theStorage.NbEdges())) + continue; + TopoDS_Shape anEdge = Node(theStorage, aCoEdge.EdgeDefId, theCache); + if (!anEdge.IsNull()) + { + anEdge.Orientation(aCoEdge.Sense); + if (!aCoEdgeRef.LocalLocation.IsIdentity()) + anEdge.Location(aCoEdgeRef.LocalLocation); + aBB.Add(aNewWire, anEdge); + } + } + if (aWire.IsClosed) + aNewWire.Closed(true); + aResult = aNewWire; + break; + } + + case BRepGraph_NodeId::Kind::Face: { + aResult = FaceWithCache(theStorage, theNode.Index, theCache, theParams, theRegularities); + break; + } + + case BRepGraph_NodeId::Kind::Shell: { + const BRepGraphInc::ShellDef& aShell = theStorage.Shell(BRepGraph_ShellId(theNode.Index)); + TopoDS_Shell aNewShell; + aBB.MakeShell(aNewShell); + for (const BRepGraph_FaceRefId& aFaceRefId : aShell.FaceRefIds) + { + const BRepGraphInc::FaceRef& aRef = theStorage.FaceRef(aFaceRefId); + if (aRef.IsRemoved || !aRef.FaceDefId.IsValid(theStorage.NbFaces())) + continue; + TopoDS_Shape aFace = + FaceWithCache(theStorage, aRef.FaceDefId.Index, theCache, theParams, theRegularities); + if (!aFace.IsNull()) + { + aFace.Orientation(aRef.Orientation); + if (!aRef.LocalLocation.IsIdentity()) + aFace.Location(aRef.LocalLocation); + aBB.Add(aNewShell, aFace); + } + } + // Reconstruct free children (wires, edges) attached directly to the shell. + for (const BRepGraph_ChildRefId& aChildRefId : aShell.FreeChildRefIds) + { + const BRepGraphInc::ChildRef& aRef = theStorage.ChildRef(aChildRefId); + TopoDS_Shape aChild = + Node(theStorage, aRef.ChildDefId, theCache, theParams, theRegularities); + if (!aChild.IsNull()) + { + aChild.Orientation(aRef.Orientation); + if (!aRef.LocalLocation.IsIdentity()) + aChild.Location(aRef.LocalLocation); + aBB.Add(aNewShell, aChild); + } + } + if (aShell.IsClosed) + aNewShell.Closed(true); + aResult = aNewShell; + break; + } + + case BRepGraph_NodeId::Kind::Solid: { + const BRepGraphInc::SolidDef& aSolid = theStorage.Solid(BRepGraph_SolidId(theNode.Index)); + TopoDS_Solid aNewSolid; + aBB.MakeSolid(aNewSolid); + for (const BRepGraph_ShellRefId& aShellRefId : aSolid.ShellRefIds) + { + const BRepGraphInc::ShellRef& aShellRef = theStorage.ShellRef(aShellRefId); + if (aShellRef.IsRemoved || !aShellRef.ShellDefId.IsValid(theStorage.NbShells())) + continue; + TopoDS_Shape aShell = + Node(theStorage, aShellRef.ShellDefId, theCache, theParams, theRegularities); + if (!aShell.IsNull()) + { + aShell.Orientation(aShellRef.Orientation); + if (!aShellRef.LocalLocation.IsIdentity()) + aShell.Location(aShellRef.LocalLocation); + aBB.Add(aNewSolid, aShell); + } + } + // Free children of the solid (edges, vertices). + for (const BRepGraph_ChildRefId& aChildRefId : aSolid.FreeChildRefIds) + { + const BRepGraphInc::ChildRef& aCR = theStorage.ChildRef(aChildRefId); + TopoDS_Shape aChild = Node(theStorage, aCR.ChildDefId, theCache); + if (!aChild.IsNull()) + { + aChild.Orientation(aCR.Orientation); + if (!aCR.LocalLocation.IsIdentity()) + aChild.Location(aCR.LocalLocation); + aBB.Add(aNewSolid, aChild); + } + } + aResult = aNewSolid; + break; + } + + case BRepGraph_NodeId::Kind::Compound: { + const BRepGraphInc::CompoundDef& aComp = + theStorage.Compound(BRepGraph_CompoundId(theNode.Index)); + TopoDS_Compound aNewComp; + aBB.MakeCompound(aNewComp); + for (const BRepGraph_ChildRefId& aChildRefId : aComp.ChildRefIds) + { + const BRepGraphInc::ChildRef& aRef = theStorage.ChildRef(aChildRefId); + if (aRef.IsRemoved || !aRef.ChildDefId.IsValid()) + continue; + TopoDS_Shape aChild = + Node(theStorage, aRef.ChildDefId, theCache, theParams, theRegularities); + if (!aChild.IsNull()) + { + aChild.Orientation(aRef.Orientation); + if (!aRef.LocalLocation.IsIdentity()) + aChild.Location(aRef.LocalLocation); + aBB.Add(aNewComp, aChild); + } + } + aResult = aNewComp; + break; + } + + case BRepGraph_NodeId::Kind::CompSolid: { + const BRepGraphInc::CompSolidDef& aCS = + theStorage.CompSolid(BRepGraph_CompSolidId(theNode.Index)); + TopoDS_CompSolid aNewCS; + aBB.MakeCompSolid(aNewCS); + for (const BRepGraph_SolidRefId& aSolidRefId : aCS.SolidRefIds) + { + const BRepGraphInc::SolidRef& aRef = theStorage.SolidRef(aSolidRefId); + if (aRef.IsRemoved || !aRef.SolidDefId.IsValid(theStorage.NbSolids())) + continue; + TopoDS_Shape aSolid = + Node(theStorage, aRef.SolidDefId, theCache, theParams, theRegularities); + if (!aSolid.IsNull()) + { + aSolid.Orientation(aRef.Orientation); + if (!aRef.LocalLocation.IsIdentity()) + aSolid.Location(aRef.LocalLocation); + aBB.Add(aNewCS, aSolid); + } + } + aResult = aNewCS; + break; + } + + // CoEdge is not a standalone TopoDS shape - it is an edge-face binding. + // Product and Occurrence are assembly-level entities without direct TopoDS equivalents. + case BRepGraph_NodeId::Kind::CoEdge: + case BRepGraph_NodeId::Kind::Product: + case BRepGraph_NodeId::Kind::Occurrence: + return TopoDS_Shape(); + } + + theCache.Bind(theNode, aResult); + return aResult; +} + +//================================================================================================= + +TopoDS_Shape BRepGraphInc_Reconstruct::FaceWithCache( + const BRepGraphInc_Storage& theStorage, + const int theFaceIdx, + Cache& theCache, + const BRepGraph_ParamLayer* theParams, + const BRepGraph_RegularityLayer* theRegularities) +{ + if (theFaceIdx < 0 || theFaceIdx >= theStorage.NbFaces()) + return TopoDS_Shape(); + + // Check cache first - 1 NodeId = 1 TShape. + BRepGraph_NodeId aFaceNodeId = BRepGraph_FaceId(theFaceIdx); + const TopoDS_Shape* aCachedFace = theCache.Seek(aFaceNodeId); + if (aCachedFace != nullptr) + return *aCachedFace; + + BRep_Builder aBB; + const BRepGraphInc::FaceDef& aFace = theStorage.Face(BRepGraph_FaceId(theFaceIdx)); + + // Resolve surface from rep storage (may be null for bare topology faces). + occ::handle aFaceSurface; + if (aFace.SurfaceRepId.IsValid()) + aFaceSurface = theStorage.SurfaceRep(aFace.SurfaceRepId).Surface; + + TopoDS_Face aNewFace; + if (!aFaceSurface.IsNull()) + aBB.MakeFace(aNewFace, aFaceSurface, TopLoc_Location(), aFace.Tolerance); + else + aBB.MakeFace(aNewFace); + + // Attach triangulations. + if (!aFace.TriangulationRepIds.IsEmpty()) + { + NCollection_List> aTriList; + occ::handle anActiveTri; + for (int aTriIdx = 0; aTriIdx < aFace.TriangulationRepIds.Length(); ++aTriIdx) + { + const BRepGraph_TriangulationRepId aTriRepId = aFace.TriangulationRepIds.Value(aTriIdx); + if (!aTriRepId.IsValid()) + continue; + const occ::handle& aTri = + theStorage.TriangulationRep(aTriRepId).Triangulation; + if (!aTri.IsNull()) + { + aTriList.Append(aTri); + if (aTriIdx == aFace.ActiveTriangulationIndex) + anActiveTri = aTri; + } + } + if (!aTriList.IsEmpty()) + { + const occ::handle& aTFace = occ::down_cast(aNewFace.TShape()); + if (!aTFace.IsNull()) + aTFace->Triangulations(aTriList, anActiveTri); + } + } + + // Helper: get or build edge from cache. + const auto aGetOrBuildEdge = [&](const int theEdgeIdx) -> TopoDS_Edge { + BRepGraph_NodeId anEdgeId = BRepGraph_EdgeId(theEdgeIdx); + const TopoDS_Shape* aCached = theCache.Seek(anEdgeId); + if (aCached != nullptr) + return TopoDS::Edge(*aCached); + + const BRepGraphInc::EdgeDef& anEdge = theStorage.Edge(BRepGraph_EdgeId(theEdgeIdx)); + TopoDS_Edge aNewEdge; + if (anEdge.IsDegenerate) + { + aBB.MakeEdge(aNewEdge); + aBB.Degenerated(aNewEdge, true); + } + else if (anEdge.Curve3DRepId.IsValid()) + { + const occ::handle& aCurve3d = theStorage.Curve3DRep(anEdge.Curve3DRepId).Curve; + if (!aCurve3d.IsNull()) + aBB.MakeEdge(aNewEdge, aCurve3d, TopLoc_Location(), anEdge.Tolerance); + else + aBB.MakeEdge(aNewEdge); + } + else + { + aBB.MakeEdge(aNewEdge); + } + aBB.Range(aNewEdge, anEdge.ParamFirst, anEdge.ParamLast); + aBB.SameParameter(aNewEdge, anEdge.SameParameter); + aBB.SameRange(aNewEdge, anEdge.SameRange); + + // Vertices (also cached). + const auto aGetOrBuildVertex = [&](const int theVtxIdx) -> TopoDS_Shape { + if (theVtxIdx < 0) + return TopoDS_Shape(); + BRepGraph_NodeId aVtxId = BRepGraph_VertexId(theVtxIdx); + const TopoDS_Shape* aVtxCached = theCache.Seek(aVtxId); + if (aVtxCached != nullptr) + return *aVtxCached; + const BRepGraphInc::VertexDef& aVtx = theStorage.Vertex(BRepGraph_VertexId(theVtxIdx)); + TopoDS_Vertex aNewVtx; + aBB.MakeVertex(aNewVtx, aVtx.Point, aVtx.Tolerance); + theCache.Bind(aVtxId, aNewVtx); + return aNewVtx; + }; + + if (anEdge.StartVertexRefId.IsValid()) + { + const BRepGraphInc::VertexRef& aStartVR = theStorage.VertexRef(anEdge.StartVertexRefId); + TopoDS_Shape aStartVtx = aGetOrBuildVertex(aStartVR.VertexDefId.Index); + if (!aStartVtx.IsNull()) + { + aStartVtx.Orientation(TopAbs_FORWARD); + if (!aStartVR.LocalLocation.IsIdentity()) + aStartVtx.Location(aStartVR.LocalLocation); + aBB.Add(aNewEdge, aStartVtx); + } + } + if (anEdge.EndVertexRefId.IsValid()) + { + const BRepGraphInc::VertexRef& anEndVR = theStorage.VertexRef(anEdge.EndVertexRefId); + TopoDS_Shape anEndVtx = aGetOrBuildVertex(anEndVR.VertexDefId.Index); + if (!anEndVtx.IsNull()) + { + anEndVtx.Orientation(TopAbs_REVERSED); + if (!anEndVR.LocalLocation.IsIdentity()) + anEndVtx.Location(anEndVR.LocalLocation); + aBB.Add(aNewEdge, anEndVtx); + } + } + for (const BRepGraph_VertexRefId& aVRefId : anEdge.InternalVertexRefIds) + { + const BRepGraphInc::VertexRef& aVR = theStorage.VertexRef(aVRefId); + TopoDS_Shape aVtx = aGetOrBuildVertex(aVR.VertexDefId.Index); + if (!aVtx.IsNull()) + { + aVtx.Orientation(aVR.Orientation); + if (!aVR.LocalLocation.IsIdentity()) + aVtx.Location(aVR.LocalLocation); + aBB.Add(aNewEdge, aVtx); + } + } + + // Polygon3D. + if (anEdge.Polygon3DRepId.IsValid()) + { + const occ::handle& aPolygon3D = + theStorage.Polygon3DRep(anEdge.Polygon3DRepId).Polygon; + if (!aPolygon3D.IsNull()) + aBB.UpdateEdge(aNewEdge, aPolygon3D, TopLoc_Location()); + } + + restoreEdgeRegularities(theRegularities, BRepGraph_EdgeId(theEdgeIdx), theCache, aBB, aNewEdge); + + if (anEdge.IsClosed) + aNewEdge.Closed(true); + + theCache.Bind(anEdgeId, aNewEdge); + return aNewEdge; + }; + + // Build wires for this face. + // Wire TShape is cached (1 NodeId = 1 TShape); PCurve attachment is per-face. + // theWireLocation is the wire's LocalLocation within the face (WireUsage.LocalLocation), + // needed to compute the correct CurveRepresentation location for PCurve binding. + const auto aBuildWireForFace = [&](BRepGraph_WireId theWireId, + const TopLoc_Location& theWireLocation) -> TopoDS_Wire { + const BRepGraphInc::WireDef& aWire = theStorage.Wire(theWireId); + BRepGraph_NodeId aWireNodeId = theWireId; + + // Get or create wire TShape. + const TopoDS_Shape* aCachedWire = theCache.Seek(aWireNodeId); + TopoDS_Wire aNewWire; + if (aCachedWire != nullptr) + { + aNewWire = TopoDS::Wire(*aCachedWire); + } + else + { + aBB.MakeWire(aNewWire); + // Add edges in original storage order (preserving input shape order). + for (const BRepGraph_CoEdgeRefId& aCoEdgeRefId : aWire.CoEdgeRefIds) + { + const BRepGraphInc::CoEdgeRef& aCoEdgeRef = theStorage.CoEdgeRef(aCoEdgeRefId); + if (aCoEdgeRef.IsRemoved || !aCoEdgeRef.CoEdgeDefId.IsValid(theStorage.NbCoEdges())) + continue; + const BRepGraphInc::CoEdgeDef& aCoEdge = + theStorage.CoEdge(BRepGraph_CoEdgeId(aCoEdgeRef.CoEdgeDefId.Index)); + if (aCoEdge.IsRemoved || !aCoEdge.EdgeDefId.IsValid(theStorage.NbEdges())) + continue; + TopoDS_Edge anEdge = aGetOrBuildEdge(aCoEdge.EdgeDefId.Index); + anEdge.Orientation(aCoEdge.Sense); + if (!aCoEdgeRef.LocalLocation.IsIdentity()) + anEdge.Location(aCoEdgeRef.LocalLocation); + aBB.Add(aNewWire, anEdge); + } + theCache.Bind(aWireNodeId, aNewWire); + } + // Apply closure flag after all edges are added (BRep_Builder::Add may reset it). + if (aWire.IsClosed) + { + aNewWire.Closed(true); + } + + // Attach PCurves/polygons for THIS face context onto shared edge TShapes. + // Each CoEdge carries its PCurve directly - no need to scan edge.PCurves by face. + // The edge must temporarily carry its composed face-hierarchy location so that + // BRep_Builder::UpdateEdge computes the correct CurveRepresentation storage key. + // Without this, PCurves stored with Identity location become unfindable when + // wire/edge instance locations within the face are non-Identity. + for (const BRepGraph_CoEdgeRefId& aCoEdgeRefId : aWire.CoEdgeRefIds) + { + const BRepGraphInc::CoEdgeRef& aCoEdgeRef = theStorage.CoEdgeRef(aCoEdgeRefId); + if (aCoEdgeRef.IsRemoved || !aCoEdgeRef.CoEdgeDefId.IsValid(theStorage.NbCoEdges())) + continue; + const BRepGraphInc::CoEdgeDef& aCoEdge = + theStorage.CoEdge(BRepGraph_CoEdgeId(aCoEdgeRef.CoEdgeDefId.Index)); + if (aCoEdge.IsRemoved || !aCoEdge.EdgeDefId.IsValid(theStorage.NbEdges())) + continue; + TopoDS_Edge anEdge = aGetOrBuildEdge(aCoEdge.EdgeDefId.Index); + const BRepGraphInc::EdgeDef& anEdgeEnt = theStorage.Edge(aCoEdge.EdgeDefId); + + // Compute composed edge location within the face TShape hierarchy. + // This is wire-in-face Location * edge-in-wire Location. + const TopLoc_Location aEdgeInFaceLoc = theWireLocation * aCoEdgeRef.LocalLocation; + + // Temporarily apply composed location to the bare cached edge before UpdateEdge. + // UpdateEdge computes: stored_loc = L.Predivided(E.Location()) = L * E.Loc^-1. + // With L = Identity and E.Loc = aEdgeInFaceLoc: + // stored_loc = aEdgeInFaceLoc^-1 + // Later, BRep_Tool search computes the same loc from face/wire/edge context. OK + if (!aEdgeInFaceLoc.IsIdentity()) + anEdge.Location(aEdgeInFaceLoc); + + // Collect PCurve(s): primary from this coedge, seam from paired coedge. + occ::handle aPC1, aPC2; + double aPCFirst = 0.0, aPCLast = 0.0; + gp_Pnt2d aUV1, aUV2; + bool aHasUV = false; + GeomAbs_Shape aSeamContinuity = GeomAbs_C0; + + if (aCoEdge.Curve2DRepId.IsValid()) + { + aPC1 = theStorage.Curve2DRep(aCoEdge.Curve2DRepId).Curve; + aPCFirst = aCoEdge.ParamFirst; + aPCLast = aCoEdge.ParamLast; + aUV1 = aCoEdge.UV1; + aUV2 = aCoEdge.UV2; + aHasUV = true; + } + + // For seam edges, get the paired coedge's PCurve. + if (aCoEdge.SeamPairId.IsValid()) + { + const BRepGraphInc::CoEdgeDef& aSeamCoEdge = theStorage.CoEdge(aCoEdge.SeamPairId); + if (aSeamCoEdge.Curve2DRepId.IsValid()) + { + aPC2 = theStorage.Curve2DRep(aSeamCoEdge.Curve2DRepId).Curve; + aSeamContinuity = aSeamCoEdge.SeamContinuity; + if (aPC1.IsNull()) + { + aPCFirst = aSeamCoEdge.ParamFirst; + aPCLast = aSeamCoEdge.ParamLast; + } + } + } + + if (!aPC1.IsNull() && !aPC2.IsNull()) + { + if (aHasUV) + aBB.UpdateEdge(anEdge, + aPC1, + aPC2, + aFaceSurface, + TopLoc_Location(), + anEdgeEnt.Tolerance, + aUV1, + aUV2); + else + aBB.UpdateEdge(anEdge, aPC1, aPC2, aFaceSurface, TopLoc_Location(), anEdgeEnt.Tolerance); + aBB.Range(anEdge, aFaceSurface, TopLoc_Location(), aPCFirst, aPCLast); + + // Restore seam continuity (UpdateEdge creates CurveOnClosedSurface with C0). + if (aSeamContinuity != GeomAbs_C0) + { + // The stored CurveRepresentation location matches the edge's current location. + const TopLoc_Location aCRLoc = + aEdgeInFaceLoc.IsIdentity() ? TopLoc_Location() : aEdgeInFaceLoc.Inverted(); + const occ::handle& aTEdge = occ::down_cast(anEdge.TShape()); + if (!aTEdge.IsNull()) + { + for (occ::handle& aCR : aTEdge->ChangeCurves()) + { + if (!aCR.IsNull() && aCR->IsCurveOnClosedSurface() + && aCR->IsCurveOnSurface(aFaceSurface, aCRLoc)) + { + occ::down_cast(aCR)->Continuity(aSeamContinuity); + break; + } + } + } + } + } + else if (!aPC1.IsNull()) + { + if (aHasUV) + aBB.UpdateEdge(anEdge, + aPC1, + aFaceSurface, + TopLoc_Location(), + anEdgeEnt.Tolerance, + aUV1, + aUV2); + else + aBB.UpdateEdge(anEdge, aPC1, aFaceSurface, TopLoc_Location(), anEdgeEnt.Tolerance); + aBB.Range(anEdge, aFaceSurface, TopLoc_Location(), aPCFirst, aPCLast); + } + else if (!aPC2.IsNull()) + { + aBB.UpdateEdge(anEdge, aPC2, aFaceSurface, TopLoc_Location(), anEdgeEnt.Tolerance); + aBB.Range(anEdge, aFaceSurface, TopLoc_Location(), aPCFirst, aPCLast); + } + + // Attach PolygonOnSurface from CoEdge. + if (aCoEdge.Polygon2DRepId.IsValid()) + { + const occ::handle& aPolyOnSurf = + theStorage.Polygon2DRep(aCoEdge.Polygon2DRepId).Polygon; + if (!aPolyOnSurf.IsNull()) + aBB.UpdateEdge(anEdge, aPolyOnSurf, aFaceSurface, TopLoc_Location()); + } + + // Attach PolygonOnTriangulation from CoEdge. + for (const BRepGraph_PolygonOnTriRepId& aPolyOnTriRepId : aCoEdge.PolygonOnTriRepIds) + { + if (!aPolyOnTriRepId.IsValid()) + continue; + const BRepGraphInc::PolygonOnTriRep& aPolyOnTriRep = + theStorage.PolygonOnTriRep(aPolyOnTriRepId); + if (aPolyOnTriRep.Polygon.IsNull() || !aPolyOnTriRep.TriangulationRepId.IsValid()) + continue; + const occ::handle& aTri = + theStorage.TriangulationRep(aPolyOnTriRep.TriangulationRepId).Triangulation; + if (!aTri.IsNull()) + aBB.UpdateEdge(anEdge, aPolyOnTriRep.Polygon, aTri, TopLoc_Location()); + } + + // Reset temporary edge location after all UpdateEdge calls. + if (!aEdgeInFaceLoc.IsIdentity()) + anEdge.Location(TopLoc_Location()); + } + + return aNewWire; + }; + + // Add wires to face: outer first, then inner. + // Wire orientation must be applied before adding to face. + for (const BRepGraph_WireRefId& aWireRefId : aFace.WireRefIds) + { + const BRepGraphInc::WireRef& aWireRef = theStorage.WireRef(aWireRefId); + if (aWireRef.IsRemoved || !aWireRef.WireDefId.IsValid(theStorage.NbWires()) + || !aWireRef.IsOuter) + continue; + TopoDS_Wire aWire = aBuildWireForFace(aWireRef.WireDefId, aWireRef.LocalLocation); + aWire.Orientation(aWireRef.Orientation); + if (!aWireRef.LocalLocation.IsIdentity()) + aWire.Location(aWireRef.LocalLocation); + aBB.Add(aNewFace, aWire); + break; + } + for (const BRepGraph_WireRefId& aWireRefId : aFace.WireRefIds) + { + const BRepGraphInc::WireRef& aWireRef = theStorage.WireRef(aWireRefId); + if (aWireRef.IsRemoved || !aWireRef.WireDefId.IsValid(theStorage.NbWires()) || aWireRef.IsOuter) + continue; + TopoDS_Wire aWire = aBuildWireForFace(aWireRef.WireDefId, aWireRef.LocalLocation); + aWire.Orientation(aWireRef.Orientation); + if (!aWireRef.LocalLocation.IsIdentity()) + aWire.Location(aWireRef.LocalLocation); + aBB.Add(aNewFace, aWire); + } + + // Add direct INTERNAL/EXTERNAL vertex children. + for (const BRepGraph_VertexRefId& aVRefId : aFace.VertexRefIds) + { + if (!aVRefId.IsValid(theStorage.NbVertexRefs())) + continue; + const BRepGraphInc::VertexRef& aVR = theStorage.VertexRef(aVRefId); + if (aVR.IsRemoved || !aVR.VertexDefId.IsValid()) + continue; + const BRepGraphInc::VertexDef& aVtxEnt = theStorage.Vertex(aVR.VertexDefId); + BRepGraph_NodeId aVtxId = aVR.VertexDefId; + const TopoDS_Shape* aVtxCached = theCache.Seek(aVtxId); + TopoDS_Shape aVtxShape; + if (aVtxCached != nullptr) + { + aVtxShape = *aVtxCached; + } + else + { + TopoDS_Vertex aNewVtx; + aBB.MakeVertex(aNewVtx, aVtxEnt.Point, aVtxEnt.Tolerance); + theCache.Bind(aVtxId, aNewVtx); + aVtxShape = aNewVtx; + } + aVtxShape.Orientation(aVR.Orientation); + if (!aVR.LocalLocation.IsIdentity()) + aVtxShape.Location(aVR.LocalLocation); + aBB.Add(aNewFace, aVtxShape); + } + + // Restore vertex point representations now that all edges and this face are cached. + // UpdateVertex modifies TShape in-place, so cached vertex shapes are updated. + for (const BRepGraph_WireRefId& aWireRefId : aFace.WireRefIds) + { + const BRepGraphInc::WireRef& aWireRef = theStorage.WireRef(aWireRefId); + if (aWireRef.IsRemoved || !aWireRef.WireDefId.IsValid(theStorage.NbWires())) + continue; + const BRepGraphInc::WireDef& aWireEnt = theStorage.Wire(aWireRef.WireDefId); + for (const BRepGraph_CoEdgeRefId& aCoEdgeRefId : aWireEnt.CoEdgeRefIds) + { + const BRepGraphInc::CoEdgeRef& aCoEdgeRef = theStorage.CoEdgeRef(aCoEdgeRefId); + if (aCoEdgeRef.IsRemoved || !aCoEdgeRef.CoEdgeDefId.IsValid(theStorage.NbCoEdges())) + continue; + const BRepGraphInc::CoEdgeDef& aCoEdge = + theStorage.CoEdge(BRepGraph_CoEdgeId(aCoEdgeRef.CoEdgeDefId.Index)); + const BRepGraphInc::EdgeDef& anEdgeEnt = theStorage.Edge(aCoEdge.EdgeDefId); + if (anEdgeEnt.StartVertexRefId.IsValid()) + { + restoreVertexPointReps(theStorage, + theParams, + theStorage.VertexRef(anEdgeEnt.StartVertexRefId).VertexDefId, + theCache, + aBB); + } + if (anEdgeEnt.EndVertexRefId.IsValid()) + { + restoreVertexPointReps(theStorage, + theParams, + theStorage.VertexRef(anEdgeEnt.EndVertexRefId).VertexDefId, + theCache, + aBB); + } + } + } + + // NaturalRestriction must be set AFTER wires are added + // (BRep_Builder::Add may reset the flag). + if (aFace.NaturalRestriction) + aBB.NaturalRestriction(aNewFace, true); + + aNewFace.Orientation(TopAbs_FORWARD); + theCache.Bind(aFaceNodeId, aNewFace); + return aNewFace; +} diff --git a/src/ModelingData/TKBRep/BRepGraphInc/BRepGraphInc_Reconstruct.hxx b/src/ModelingData/TKBRep/BRepGraphInc/BRepGraphInc_Reconstruct.hxx new file mode 100644 index 0000000000..6bec4d9d60 --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraphInc/BRepGraphInc_Reconstruct.hxx @@ -0,0 +1,118 @@ +// Copyright (c) 2026 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 _BRepGraphInc_Reconstruct_HeaderFile +#define _BRepGraphInc_Reconstruct_HeaderFile + +#include +#include +#include + +#include +#include + +class BRepGraphInc_Storage; +class BRepGraph_ParamLayer; +class BRepGraph_RegularityLayer; + +//! @brief Backend reconstruction helpers over incidence-table storage. +//! +//! Converts BRepGraphInc_Storage entity data back into TopoDS shapes. +//! This class is part of the BRepGraphInc backend; external callers should +//! prefer BRepGraph::Shapes() so reconstruction stays behind the facade. +//! Supports single-node and cached multi-face reconstruction with +//! shared edge/vertex reuse via the Cache. +class BRepGraphInc_Reconstruct +{ +public: + DEFINE_STANDARD_ALLOC + + //! Per-Kind dense vector cache for O(1) shape lookup by entity index. + //! Replaces NCollection_DataMap to eliminate hash/equality overhead. + struct Cache + { + //! Number of Kind slots used by BRepGraph_NodeId dense-kind indexing. + //! Includes topology kinds, assembly kinds, and the reserved gap at kind 9. + static constexpr int THE_KIND_COUNT = BRepGraph_NodeId::THE_KIND_COUNT; + + NCollection_Vector myKinds[THE_KIND_COUNT]; + + //! Seek a cached shape. Returns nullptr if not yet cached. + const TopoDS_Shape* Seek(const BRepGraph_NodeId theNode) const + { + const int aKindIdx = static_cast(theNode.NodeKind); + if (aKindIdx < 0 || aKindIdx >= THE_KIND_COUNT) + return nullptr; + const NCollection_Vector& aVec = myKinds[aKindIdx]; + if (theNode.Index >= aVec.Length()) + return nullptr; + const TopoDS_Shape& aShape = aVec.Value(theNode.Index); + return aShape.IsNull() ? nullptr : &aShape; + } + + //! Bind a reconstructed shape to a node. Grows the vector as needed. + void Bind(const BRepGraph_NodeId theNode, const TopoDS_Shape& theShape) + { + const int aKindIdx = static_cast(theNode.NodeKind); + if (aKindIdx < 0 || aKindIdx >= THE_KIND_COUNT) + return; + NCollection_Vector& aVec = myKinds[aKindIdx]; + aVec.SetValue(theNode.Index, theShape); + } + + //! Check if a node is already cached. + bool IsBound(const BRepGraph_NodeId theNode) const { return Seek(theNode) != nullptr; } + }; + + //! Reconstruct a TopoDS_Shape from an entity node. + //! Creates a local cache internally; shared vertices/edges are not reused + //! across calls. + //! @param[in] theStorage incidence storage + //! @param[in] theNode entity node id + //! @return reconstructed shape + static Standard_EXPORT TopoDS_Shape + Node(const BRepGraphInc_Storage& theStorage, + const BRepGraph_NodeId theNode, + const BRepGraph_ParamLayer* theParams = nullptr, + const BRepGraph_RegularityLayer* theRegularities = nullptr); + + //! Reconstruct a TopoDS_Shape with a shared cache for sub-shape reuse. + //! Vertices and edges already in theCache are returned directly. + //! @param[in] theStorage incidence storage + //! @param[in] theNode entity node id + //! @param[in,out] theCache shared cache for vertex/edge/face shapes + //! @return reconstructed shape + static Standard_EXPORT TopoDS_Shape + Node(const BRepGraphInc_Storage& theStorage, + const BRepGraph_NodeId theNode, + Cache& theCache, + const BRepGraph_ParamLayer* theParams = nullptr, + const BRepGraph_RegularityLayer* theRegularities = nullptr); + + //! Reconstruct a face with shared edge/vertex cache for multi-face contexts. + //! @param[in] theStorage incidence storage + //! @param[in] theFaceIdx face entity index + //! @param[in,out] theCache shared cache for edge and vertex shapes + //! @return reconstructed face shape + static Standard_EXPORT TopoDS_Shape + FaceWithCache(const BRepGraphInc_Storage& theStorage, + const int theFaceIdx, + Cache& theCache, + const BRepGraph_ParamLayer* theParams = nullptr, + const BRepGraph_RegularityLayer* theRegularities = nullptr); + +private: + BRepGraphInc_Reconstruct() = delete; +}; + +#endif // _BRepGraphInc_Reconstruct_HeaderFile diff --git a/src/ModelingData/TKBRep/BRepGraphInc/BRepGraphInc_Reference.hxx b/src/ModelingData/TKBRep/BRepGraphInc/BRepGraphInc_Reference.hxx new file mode 100644 index 0000000000..e1c8169bf0 --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraphInc/BRepGraphInc_Reference.hxx @@ -0,0 +1,119 @@ +// Copyright (c) 2026 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 _BRepGraphInc_Reference_HeaderFile +#define _BRepGraphInc_Reference_HeaderFile + +#include +#include +#include +#include + +//! @brief Managed reference entry structs for the incidence-table storage. +//! +//! Each reference entry extends BaseRef with payload fields describing +//! how a child definition is used by its parent (orientation, location). +//! Reference entries are stored in flat per-kind vectors in BRepGraphInc_Storage +//! and support mutation tracking and soft-removal. +//! Not every definition kind has a dedicated Ref kind by design: +//! - Edge usage is represented by CoEdgeRef -> CoEdgeDef (which then targets EdgeDef) +//! - Compound children use ChildRef (heterogeneous NodeId target) +//! - Product children use OccurrenceRef (placement owned by OccurrenceDef) +//! - CompSolid children use SolidRef +//! +//! For lightweight read-only projections without lifecycle fields, see +//! BRepGraphInc_Usage.hxx (Usage structs carry only DefId + Orientation + Location). +namespace BRepGraphInc +{ + +//! Fields shared by every reference entry. +struct BaseRef +{ + BRepGraph_RefId RefId; //!< Typed address (kind + per-kind index) + BRepGraph_NodeId ParentId; //!< Parent topology node owning this reference usage + uint32_t OwnGen = 0; //!< Per-reference mutation counter + bool IsRemoved = false; //!< Soft-removal flag +}; + +//! Shell reference storage entry. +struct ShellRef : public BaseRef +{ + BRepGraph_ShellId ShellDefId; + TopAbs_Orientation Orientation = TopAbs_FORWARD; + TopLoc_Location LocalLocation; +}; + +//! Face reference storage entry. +struct FaceRef : public BaseRef +{ + BRepGraph_FaceId FaceDefId; + TopAbs_Orientation Orientation = TopAbs_FORWARD; + TopLoc_Location LocalLocation; +}; + +//! Wire reference storage entry. +struct WireRef : public BaseRef +{ + BRepGraph_WireId WireDefId; + bool IsOuter = false; + TopAbs_Orientation Orientation = TopAbs_FORWARD; + TopLoc_Location LocalLocation; +}; + +//! CoEdge reference storage entry. +//! No Orientation field: CoEdgeDef::Sense already owns the edge-on-face sense, +//! coupled with PCurve parametrization, so duplicating orientation here would +//! create a second competing source of truth. +struct CoEdgeRef : public BaseRef +{ + BRepGraph_CoEdgeId CoEdgeDefId; + TopLoc_Location LocalLocation; +}; + +//! Vertex reference storage entry. +struct VertexRef : public BaseRef +{ + BRepGraph_VertexId VertexDefId; + TopAbs_Orientation Orientation = + TopAbs_INTERNAL; //!< INTERNAL: B-Rep vertex classification convention + TopLoc_Location LocalLocation; +}; + +//! Solid reference storage entry. +struct SolidRef : public BaseRef +{ + BRepGraph_SolidId SolidDefId; + TopAbs_Orientation Orientation = TopAbs_FORWARD; + TopLoc_Location LocalLocation; +}; + +//! Child reference storage entry. +struct ChildRef : public BaseRef +{ + BRepGraph_NodeId ChildDefId; + TopAbs_Orientation Orientation = TopAbs_FORWARD; + TopLoc_Location LocalLocation; +}; + +//! Occurrence reference storage entry. +//! Unlike other ref types, OccurrenceRef omits Orientation and LocalLocation. +//! Placement is owned by OccurrenceDef::Placement; this ref is a lightweight +//! pointer from the parent Product's OccurrenceRefIds to the OccurrenceDef slot. +struct OccurrenceRef : public BaseRef +{ + BRepGraph_OccurrenceId OccurrenceDefId; +}; + +} // namespace BRepGraphInc + +#endif // _BRepGraphInc_Reference_HeaderFile diff --git a/src/ModelingData/TKBRep/BRepGraphInc/BRepGraphInc_Representation.hxx b/src/ModelingData/TKBRep/BRepGraphInc/BRepGraphInc_Representation.hxx new file mode 100644 index 0000000000..0fac059716 --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraphInc/BRepGraphInc_Representation.hxx @@ -0,0 +1,90 @@ +// Copyright (c) 2026 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 _BRepGraphInc_Representation_HeaderFile +#define _BRepGraphInc_Representation_HeaderFile + +#include + +#include +#include +#include +#include +#include +#include +#include + +//! @brief Geometry and mesh representation structs for the incidence-table model. +//! +//! Each representation struct wraps a single piece of geometry or discretization +//! data (surface, curve, triangulation, polygon) with a typed RepId address +//! and lifecycle tracking fields. Representations are stored in flat per-kind +//! vectors in BRepGraphInc_Storage and referenced from definitions by typed RepId. +namespace BRepGraphInc +{ + +//! Fields shared by every representation entity. +struct BaseRep +{ + BRepGraph_RepId Id; //!< Typed address (Kind + per-kind index) + uint32_t OwnGen = 0; //!< Per-rep mutation counter + bool IsRemoved = false; //!< Soft-removal flag +}; + +//! Surface geometry representation for faces. +struct SurfaceRep : public BaseRep +{ + occ::handle Surface; //!< The geometric surface +}; + +//! 3D curve geometry representation for edges. +struct Curve3DRep : public BaseRep +{ + occ::handle Curve; //!< The 3D curve geometry +}; + +//! 2D parametric curve (PCurve) representation for coedges. +struct Curve2DRep : public BaseRep +{ + occ::handle Curve; //!< The 2D parametric curve +}; + +//! Triangulation mesh representation for faces. +struct TriangulationRep : public BaseRep +{ + occ::handle Triangulation; //!< The mesh +}; + +//! 3D polygon discretization for edges. +struct Polygon3DRep : public BaseRep +{ + occ::handle Polygon; //!< The 3D polygon +}; + +//! 2D polygon-on-surface discretization for coedges. +struct Polygon2DRep : public BaseRep +{ + occ::handle Polygon; //!< The 2D polygon on surface parametric space +}; + +//! Polygon-on-triangulation for coedges. +//! Links a polygon to a specific triangulation rep (global index, not face-local). +struct PolygonOnTriRep : public BaseRep +{ + occ::handle Polygon; //!< Polygon indices into triangulation + BRepGraph_TriangulationRepId TriangulationRepId; //!< Typed id into myTriangulationsRep +}; + +} // namespace BRepGraphInc + +#endif // _BRepGraphInc_Representation_HeaderFile diff --git a/src/ModelingData/TKBRep/BRepGraphInc/BRepGraphInc_ReverseIndex.cxx b/src/ModelingData/TKBRep/BRepGraphInc/BRepGraphInc_ReverseIndex.cxx new file mode 100644 index 0000000000..c91c53747b --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraphInc/BRepGraphInc_ReverseIndex.cxx @@ -0,0 +1,1267 @@ +// Copyright (c) 2026 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 + +namespace +{ + +template +BRepGraph_VertexId resolveVertexDefId( + const NCollection_Vector& theVertexRefs, + const T theRefId) +{ + if (!theRefId.IsValid() || theRefId.Index < 0 || theRefId.Index >= theVertexRefs.Length()) + return BRepGraph_VertexId(); + return theVertexRefs.Value(theRefId.Index).VertexDefId; +} + +template +bool containsIndexInTable(const NCollection_Vector>& theIdx, + const int theKey, + const int theVal) +{ + if (theKey < 0 || theKey >= theIdx.Length()) + return false; + const NCollection_Vector& aVec = theIdx.Value(theKey); + for (const T& anElem : aVec) + { + if (anElem.Index == theVal) + return true; + } + return false; +} + +static bool hasActiveFaceForEdgeInCoEdges( + const NCollection_Vector& theCoEdgeIds, + const NCollection_Vector& theCoEdges, + const int theEdgeIdx, + const int theFaceIdx) +{ + for (const BRepGraph_CoEdgeId& aCoEdgeId : theCoEdgeIds) + { + const int aCoEdgeIdx = aCoEdgeId.Index; + if (aCoEdgeIdx < 0 || aCoEdgeIdx >= theCoEdges.Length()) + { + continue; + } + + const BRepGraphInc::CoEdgeDef& aCoEdge = theCoEdges.Value(aCoEdgeIdx); + if (aCoEdge.IsRemoved || !aCoEdge.EdgeDefId.IsValid() || !aCoEdge.FaceDefId.IsValid()) + { + continue; + } + if (aCoEdge.EdgeDefId.Index == theEdgeIdx && aCoEdge.FaceDefId.Index == theFaceIdx) + { + return true; + } + } + return false; +} + +} // namespace + +//================================================================================================= + +void BRepGraphInc_ReverseIndex::Clear() +{ + myEdgeToWires.Clear(); + myEdgeToFaces.Clear(); + myEdgeToCoEdges.Clear(); + myVertexToEdges.Clear(); + myWireToFaces.Clear(); + myFaceToShells.Clear(); + myShellToSolids.Clear(); + myCompoundsOfSolid.Clear(); + myCompSolidsOfSolid.Clear(); + myCompoundsOfShell.Clear(); + myCompoundsOfFace.Clear(); + myCompoundsOfCompound.Clear(); + myCompoundsOfCompSolid.Clear(); + myCoEdgeToWires.Clear(); + myEdgeFaceCount.Clear(); + myProductToOccurrences.Clear(); + myNbIndexedCoEdges = 0; +} + +//================================================================================================= + +void BRepGraphInc_ReverseIndex::Build( + const NCollection_Vector& theEdges, + const NCollection_Vector& theCoEdges, + const NCollection_Vector& theWires, + const NCollection_Vector& theFaces, + const NCollection_Vector& theShells, + const NCollection_Vector& theSolids, + const NCollection_Vector& theCompounds, + const NCollection_Vector& theCompSolids, + const NCollection_Vector& theShellRefs, + const NCollection_Vector& theFaceRefs, + const NCollection_Vector& theWireRefs, + const NCollection_Vector& theCoEdgeRefs, + const NCollection_Vector& theSolidRefs, + const NCollection_Vector& theChildRefs, + const NCollection_Vector& theVertexRefs) +{ + myNbIndexedCoEdges = 0; + + // Reconstruct outer index tables with allocator if set. + if (!myAllocator.IsNull()) + { + myEdgeToWires = TypedIndexTable(256, myAllocator); + myEdgeToFaces = TypedIndexTable(256, myAllocator); + myVertexToEdges = TypedIndexTable(256, myAllocator); + myWireToFaces = TypedIndexTable(256, myAllocator); + myFaceToShells = TypedIndexTable(256, myAllocator); + myShellToSolids = TypedIndexTable(256, myAllocator); + } + else + { + Clear(); + } + + // Helper: resolve a VertexRefId to the corresponding VertexDefId (BRepGraph_VertexId). + // Returns an invalid id if the ref id is invalid or out of range. + + // Scan edges for max vertex index to pre-size myVertexToEdges. + int aMaxVertexIdx = -1; + const int aNbEdges = theEdges.Length(); + for (BRepGraph_EdgeId anEdgeId(0); anEdgeId.IsValid(aNbEdges); ++anEdgeId) + { + const BRepGraphInc::EdgeDef& anEdge = theEdges.Value(anEdgeId.Index); + if (anEdge.IsRemoved) + continue; + const BRepGraph_VertexId aStartVtx = resolveVertexDefId(theVertexRefs, anEdge.StartVertexRefId); + const BRepGraph_VertexId anEndVtx = resolveVertexDefId(theVertexRefs, anEdge.EndVertexRefId); + if (aStartVtx.IsValid() && aStartVtx.Index > aMaxVertexIdx) + aMaxVertexIdx = aStartVtx.Index; + if (anEndVtx.IsValid() && anEndVtx.Index > aMaxVertexIdx) + aMaxVertexIdx = anEndVtx.Index; + } + + // Pre-size all outer vectors to their known key range. + // Pass allocator so inner vectors use IncAllocator for O(1) alloc/free. + preSize(myVertexToEdges, aMaxVertexIdx + 1, myAllocator); + preSize(myEdgeToWires, theEdges.Length(), myAllocator); + preSize(myEdgeToFaces, theEdges.Length(), myAllocator); + preSize(myWireToFaces, theWires.Length(), myAllocator); + preSize(myFaceToShells, theFaces.Length(), myAllocator); + preSize(myShellToSolids, theShells.Length(), myAllocator); + + // Vertex -> Edges: scan edge entities for start/end vertex indices. + // Closed edges have StartVertexRefId and EndVertexRefId resolving to the same VertexDefId, + // so skip duplicate. + for (BRepGraph_EdgeId anEdgeId(0); anEdgeId.IsValid(aNbEdges); ++anEdgeId) + { + const BRepGraphInc::EdgeDef& anEdge = theEdges.Value(anEdgeId.Index); + if (anEdge.IsRemoved) + continue; + const BRepGraph_VertexId aStartVtx = resolveVertexDefId(theVertexRefs, anEdge.StartVertexRefId); + const BRepGraph_VertexId anEndVtx = resolveVertexDefId(theVertexRefs, anEdge.EndVertexRefId); + if (aStartVtx.IsValid()) + appendDirect(myVertexToEdges, aStartVtx.Index, anEdgeId); + if (anEndVtx.IsValid() && anEndVtx != aStartVtx) + appendDirect(myVertexToEdges, anEndVtx.Index, anEdgeId); + } + + // Edge -> Wires: iterate wire entities and their CoEdgeRefIds for O(1) parent lookup. + const int aNbWires = theWires.Length(); + for (BRepGraph_WireId aWireId(0); aWireId.IsValid(aNbWires); ++aWireId) + { + const BRepGraphInc::WireDef& aWire = theWires.Value(aWireId.Index); + if (aWire.IsRemoved) + continue; + for (const BRepGraph_CoEdgeRefId& aCoEdgeRefId : aWire.CoEdgeRefIds) + { + const int aRefIdx = aCoEdgeRefId.Index; + if (aRefIdx < 0 || aRefIdx >= theCoEdgeRefs.Length()) + continue; + const BRepGraphInc::CoEdgeRef& aRef = theCoEdgeRefs.Value(aRefIdx); + if (aRef.IsRemoved || !aRef.CoEdgeDefId.IsValid() || aRef.CoEdgeDefId.Index < 0 + || aRef.CoEdgeDefId.Index >= theCoEdges.Length()) + continue; + const BRepGraphInc::CoEdgeDef& aCoEdge = theCoEdges.Value(aRef.CoEdgeDefId.Index); + if (aCoEdge.IsRemoved || !aCoEdge.EdgeDefId.IsValid()) + continue; + appendDirect(myEdgeToWires, aCoEdge.EdgeDefId.Index, aWireId); + } + } + + // Edge -> CoEdges: derive from CoEdge.EdgeDefId field. + preSize(myEdgeToCoEdges, theEdges.Length(), myAllocator); + const int aNbCoEdges = theCoEdges.Length(); + for (BRepGraph_CoEdgeId aCoEdgeId(0); aCoEdgeId.IsValid(aNbCoEdges); ++aCoEdgeId) + { + const BRepGraphInc::CoEdgeDef& aCoEdge = theCoEdges.Value(aCoEdgeId.Index); + if (aCoEdge.IsRemoved) + continue; + if (aCoEdge.EdgeDefId.IsValid()) + appendDirect(myEdgeToCoEdges, aCoEdge.EdgeDefId.Index, aCoEdgeId); + } + myNbIndexedCoEdges = theCoEdges.Length(); + + // Edge -> Faces: derive from CoEdge.FaceDefId (replaces legacy PCurve-based derivation). + // Seam edges have two CoEdges with same FaceDefId but opposite Sense. + // Deduplicate per edge using the edge->coedges index built above. + for (BRepGraph_EdgeId anEdgeId(0); anEdgeId.IsValid(aNbEdges); ++anEdgeId) + { + if (theEdges.Value(anEdgeId.Index).IsRemoved) + continue; + const NCollection_Vector* aCoEdgeIdxs = + seekVec(myEdgeToCoEdges, anEdgeId.Index); + if (aCoEdgeIdxs == nullptr) + continue; + const int aNbCE = aCoEdgeIdxs->Length(); + + // Collect face indices from coedges (stack-allocated for small counts). + NCollection_LocalArray aFaces(aNbCE); + int aNbFaces = 0; + for (int i = 0; i < aNbCE; ++i) + { + const BRepGraphInc::CoEdgeDef& aCoEdge = theCoEdges.Value(aCoEdgeIdxs->Value(i).Index); + if (aCoEdge.FaceDefId.IsValid()) + aFaces[aNbFaces++] = aCoEdge.FaceDefId.Index; + } + if (aNbFaces == 0) + continue; + + // Insertion sort (optimal for small N). + for (int i = 1; i < aNbFaces; ++i) + { + const int aKey = aFaces[i]; + int j = i - 1; + while (j >= 0 && aFaces[j] > aKey) + { + aFaces[j + 1] = aFaces[j]; + --j; + } + aFaces[j + 1] = aKey; + } + + // Append unique sorted values. + int aPrev = -1; + for (int i = 0; i < aNbFaces; ++i) + { + if (aFaces[i] != aPrev) + { + appendDirect(myEdgeToFaces, anEdgeId.Index, BRepGraph_FaceId(aFaces[i])); + aPrev = aFaces[i]; + } + } + } + + // Populate cached face counts from the edge-to-faces index. + myEdgeFaceCount.Clear(); + for (BRepGraph_EdgeId anEdgeId(0); anEdgeId.IsValid(aNbEdges); ++anEdgeId) + { + const NCollection_Vector* aFaceVec = seekVec(myEdgeToFaces, anEdgeId.Index); + myEdgeFaceCount.Append(aFaceVec != nullptr ? aFaceVec->Length() : 0); + } + + // Wire -> Faces: iterate face entities and their WireRefIds for O(1) parent lookup. + const int aNbFaces = theFaces.Length(); + for (BRepGraph_FaceId aFaceId(0); aFaceId.IsValid(aNbFaces); ++aFaceId) + { + const BRepGraphInc::FaceDef& aFace = theFaces.Value(aFaceId.Index); + if (aFace.IsRemoved) + continue; + for (const BRepGraph_WireRefId& aWireRefId : aFace.WireRefIds) + { + const int aRefIdx = aWireRefId.Index; + if (aRefIdx < 0 || aRefIdx >= theWireRefs.Length()) + continue; + const BRepGraphInc::WireRef& aRef = theWireRefs.Value(aRefIdx); + if (aRef.IsRemoved || !aRef.WireDefId.IsValid() || aRef.WireDefId.Index < 0 + || aRef.WireDefId.Index >= theWires.Length()) + continue; + if (theWires.Value(aRef.WireDefId.Index).IsRemoved) + continue; + appendDirect(myWireToFaces, aRef.WireDefId.Index, aFaceId); + } + } + + // Face -> Shells: iterate shell entities and their FaceRefIds for O(1) parent lookup. + const int aNbShells = theShells.Length(); + for (BRepGraph_ShellId aShellId(0); aShellId.IsValid(aNbShells); ++aShellId) + { + const BRepGraphInc::ShellDef& aShell = theShells.Value(aShellId.Index); + if (aShell.IsRemoved) + continue; + for (const BRepGraph_FaceRefId& aFaceRefId : aShell.FaceRefIds) + { + const int aRefIdx = aFaceRefId.Index; + if (aRefIdx < 0 || aRefIdx >= theFaceRefs.Length()) + continue; + const BRepGraphInc::FaceRef& aRef = theFaceRefs.Value(aRefIdx); + if (aRef.IsRemoved || !aRef.FaceDefId.IsValid() || aRef.FaceDefId.Index < 0 + || aRef.FaceDefId.Index >= theFaces.Length()) + continue; + if (theFaces.Value(aRef.FaceDefId.Index).IsRemoved) + continue; + appendDirect(myFaceToShells, aRef.FaceDefId.Index, aShellId); + } + } + + // Shell -> Solids: iterate solid entities and their ShellRefIds for O(1) parent lookup. + const int aNbSolids = theSolids.Length(); + for (BRepGraph_SolidId aSolidId(0); aSolidId.IsValid(aNbSolids); ++aSolidId) + { + const BRepGraphInc::SolidDef& aSolid = theSolids.Value(aSolidId.Index); + if (aSolid.IsRemoved) + continue; + for (const BRepGraph_ShellRefId& aShellRefId : aSolid.ShellRefIds) + { + const int aRefIdx = aShellRefId.Index; + if (aRefIdx < 0 || aRefIdx >= theShellRefs.Length()) + continue; + const BRepGraphInc::ShellRef& aRef = theShellRefs.Value(aRefIdx); + if (aRef.IsRemoved || !aRef.ShellDefId.IsValid() || aRef.ShellDefId.Index < 0 + || aRef.ShellDefId.Index >= theShells.Length()) + continue; + if (theShells.Value(aRef.ShellDefId.Index).IsRemoved) + continue; + appendDirect(myShellToSolids, aRef.ShellDefId.Index, aSolidId); + } + } + + // Compound -> child reverse indices: iterate compound entities and their ChildRefIds. + preSize(myCompoundsOfSolid, theSolids.Length(), myAllocator); + preSize(myCompoundsOfShell, theShells.Length(), myAllocator); + preSize(myCompoundsOfFace, theFaces.Length(), myAllocator); + preSize(myCompoundsOfCompound, theCompounds.Length(), myAllocator); + preSize(myCompoundsOfCompSolid, theCompSolids.Length(), myAllocator); + const int aNbCompounds = theCompounds.Length(); + for (BRepGraph_CompoundId aCompoundId(0); aCompoundId.IsValid(aNbCompounds); ++aCompoundId) + { + const BRepGraphInc::CompoundDef& aComp = theCompounds.Value(aCompoundId.Index); + if (aComp.IsRemoved) + continue; + for (const BRepGraph_ChildRefId& aChildRefId : aComp.ChildRefIds) + { + const int aChildRefIdx = aChildRefId.Index; + if (aChildRefIdx < 0 || aChildRefIdx >= theChildRefs.Length()) + continue; + const BRepGraphInc::ChildRef& aRef = theChildRefs.Value(aChildRefIdx); + if (aRef.IsRemoved || !aRef.ChildDefId.IsValid()) + continue; + if (aRef.ChildDefId.NodeKind == BRepGraph_NodeId::Kind::Solid && aRef.ChildDefId.Index >= 0 + && aRef.ChildDefId.Index < theSolids.Length()) + appendDirect(myCompoundsOfSolid, aRef.ChildDefId.Index, aCompoundId); + else if (aRef.ChildDefId.NodeKind == BRepGraph_NodeId::Kind::Shell + && aRef.ChildDefId.Index >= 0 && aRef.ChildDefId.Index < theShells.Length()) + appendDirect(myCompoundsOfShell, aRef.ChildDefId.Index, aCompoundId); + else if (aRef.ChildDefId.NodeKind == BRepGraph_NodeId::Kind::Face + && aRef.ChildDefId.Index >= 0 && aRef.ChildDefId.Index < theFaces.Length()) + appendDirect(myCompoundsOfFace, aRef.ChildDefId.Index, aCompoundId); + else if (aRef.ChildDefId.NodeKind == BRepGraph_NodeId::Kind::Compound + && aRef.ChildDefId.Index >= 0 && aRef.ChildDefId.Index < theCompounds.Length()) + appendDirect(myCompoundsOfCompound, aRef.ChildDefId.Index, aCompoundId); + else if (aRef.ChildDefId.NodeKind == BRepGraph_NodeId::Kind::CompSolid + && aRef.ChildDefId.Index >= 0 && aRef.ChildDefId.Index < theCompSolids.Length()) + appendDirect(myCompoundsOfCompSolid, aRef.ChildDefId.Index, aCompoundId); + } + } + + // CompSolid -> Solid reverse index: iterate comp-solid entities and their SolidRefIds. + preSize(myCompSolidsOfSolid, theSolids.Length(), myAllocator); + const int aNbCompSolids = theCompSolids.Length(); + for (BRepGraph_CompSolidId aCompSolidId(0); aCompSolidId.IsValid(aNbCompSolids); ++aCompSolidId) + { + const BRepGraphInc::CompSolidDef& aCS = theCompSolids.Value(aCompSolidId.Index); + if (aCS.IsRemoved) + continue; + for (const BRepGraph_SolidRefId& aSolidRefId : aCS.SolidRefIds) + { + const int aRefIdx = aSolidRefId.Index; + if (aRefIdx < 0 || aRefIdx >= theSolidRefs.Length()) + continue; + const BRepGraphInc::SolidRef& aRef = theSolidRefs.Value(aRefIdx); + if (aRef.IsRemoved || !aRef.SolidDefId.IsValid() || aRef.SolidDefId.Index < 0 + || aRef.SolidDefId.Index >= theSolids.Length()) + continue; + if (theSolids.Value(aRef.SolidDefId.Index).IsRemoved) + continue; + appendDirect(myCompSolidsOfSolid, aRef.SolidDefId.Index, aCompSolidId); + } + } + + // CoEdge -> Wires: iterate wire entities and their CoEdgeRefIds for O(1) parent lookup. + preSize(myCoEdgeToWires, theCoEdges.Length(), myAllocator); + for (BRepGraph_WireId aWireId(0); aWireId.IsValid(aNbWires); ++aWireId) + { + const BRepGraphInc::WireDef& aWire = theWires.Value(aWireId.Index); + if (aWire.IsRemoved) + continue; + for (const BRepGraph_CoEdgeRefId& aCoEdgeRefId : aWire.CoEdgeRefIds) + { + const int aRefIdx = aCoEdgeRefId.Index; + if (aRefIdx < 0 || aRefIdx >= theCoEdgeRefs.Length()) + continue; + const BRepGraphInc::CoEdgeRef& aRef = theCoEdgeRefs.Value(aRefIdx); + if (aRef.IsRemoved || !aRef.CoEdgeDefId.IsValid() || aRef.CoEdgeDefId.Index < 0 + || aRef.CoEdgeDefId.Index >= theCoEdges.Length()) + continue; + if (theCoEdges.Value(aRef.CoEdgeDefId.Index).IsRemoved) + continue; + appendDirect(myCoEdgeToWires, aRef.CoEdgeDefId.Index, aWireId); + } + } +} + +//================================================================================================= + +void BRepGraphInc_ReverseIndex::BuildDelta( + const NCollection_Vector& theEdges, + const NCollection_Vector& theCoEdges, + const NCollection_Vector& theWires, + const NCollection_Vector& theFaces, + const NCollection_Vector& theShells, + const NCollection_Vector& theSolids, + const NCollection_Vector& theShellRefs, + const NCollection_Vector& theFaceRefs, + const NCollection_Vector& theWireRefs, + const NCollection_Vector& theCoEdgeRefs, + const NCollection_Vector& theVertexRefs, + const int theOldNbEdges, + const int theOldNbWires, + const int theOldNbFaces, + const int theOldNbShells, + const int theOldNbSolids) +{ + // Helper: resolve a VertexRefId to the corresponding VertexDefId (BRepGraph_VertexId). + // Returns an invalid id if the ref id is invalid or out of range. + + // Scan new edges for max vertex index to possibly extend myVertexToEdges. + int aMaxVertexIdx = myVertexToEdges.Length() - 1; + const int aNbEdges = theEdges.Length(); + for (BRepGraph_EdgeId anEdgeId(theOldNbEdges); anEdgeId.IsValid(aNbEdges); ++anEdgeId) + { + const BRepGraphInc::EdgeDef& anEdge = theEdges.Value(anEdgeId.Index); + if (anEdge.IsRemoved) + continue; + const BRepGraph_VertexId aStartVtx = resolveVertexDefId(theVertexRefs, anEdge.StartVertexRefId); + const BRepGraph_VertexId anEndVtx = resolveVertexDefId(theVertexRefs, anEdge.EndVertexRefId); + if (aStartVtx.IsValid() && aStartVtx.Index > aMaxVertexIdx) + aMaxVertexIdx = aStartVtx.Index; + if (anEndVtx.IsValid() && anEndVtx.Index > aMaxVertexIdx) + aMaxVertexIdx = anEndVtx.Index; + } + + // Extend outer vectors if needed (pre-size for new key ranges). + ensureSize(myVertexToEdges, aMaxVertexIdx + 1, myAllocator); + ensureSize(myEdgeToWires, theEdges.Length(), myAllocator); + ensureSize(myEdgeToFaces, theEdges.Length(), myAllocator); + ensureSize(myEdgeToCoEdges, theEdges.Length(), myAllocator); + ensureSize(myWireToFaces, theWires.Length(), myAllocator); + ensureSize(myFaceToShells, theFaces.Length(), myAllocator); + ensureSize(myShellToSolids, theShells.Length(), myAllocator); + ensureSize(myEdgeFaceCount, theEdges.Length()); + + // Vertex -> Edges: only new edges. + for (BRepGraph_EdgeId anEdgeId(theOldNbEdges); anEdgeId.IsValid(aNbEdges); ++anEdgeId) + { + const BRepGraphInc::EdgeDef& anEdge = theEdges.Value(anEdgeId.Index); + if (anEdge.IsRemoved) + continue; + const BRepGraph_VertexId aStartVtx = resolveVertexDefId(theVertexRefs, anEdge.StartVertexRefId); + const BRepGraph_VertexId anEndVtx = resolveVertexDefId(theVertexRefs, anEdge.EndVertexRefId); + if (aStartVtx.IsValid()) + appendUnique(myVertexToEdges, aStartVtx.Index, anEdgeId); + if (anEndVtx.IsValid() && anEndVtx != aStartVtx) + appendUnique(myVertexToEdges, anEndVtx.Index, anEdgeId); + } + + // Edge -> Wires: iterate only new wire entities and their CoEdgeRefIds. + const int aNbWires = theWires.Length(); + for (BRepGraph_WireId aWireId(theOldNbWires); aWireId.IsValid(aNbWires); ++aWireId) + { + const BRepGraphInc::WireDef& aWire = theWires.Value(aWireId.Index); + if (aWire.IsRemoved) + continue; + for (const BRepGraph_CoEdgeRefId& aCoEdgeRefId : aWire.CoEdgeRefIds) + { + const int aRefIdx = aCoEdgeRefId.Index; + if (aRefIdx < 0 || aRefIdx >= theCoEdgeRefs.Length()) + continue; + const BRepGraphInc::CoEdgeRef& aRef = theCoEdgeRefs.Value(aRefIdx); + if (aRef.IsRemoved || !aRef.CoEdgeDefId.IsValid() || aRef.CoEdgeDefId.Index < 0 + || aRef.CoEdgeDefId.Index >= theCoEdges.Length()) + continue; + const BRepGraphInc::CoEdgeDef& aCoEdge = theCoEdges.Value(aRef.CoEdgeDefId.Index); + if (aCoEdge.IsRemoved || !aCoEdge.EdgeDefId.IsValid()) + continue; + appendUnique(myEdgeToWires, aCoEdge.EdgeDefId.Index, aWireId); + } + } + + // Edge -> CoEdges: scan only newly appended coedges (they may reference old edges). + int aOldNbIndexedCoEdges = myNbIndexedCoEdges; + if (aOldNbIndexedCoEdges < 0 || aOldNbIndexedCoEdges > theCoEdges.Length()) + aOldNbIndexedCoEdges = 0; + const int aNbCoEdges = theCoEdges.Length(); + for (BRepGraph_CoEdgeId aCoEdgeId(aOldNbIndexedCoEdges); aCoEdgeId.IsValid(aNbCoEdges); + ++aCoEdgeId) + { + const BRepGraphInc::CoEdgeDef& aCoEdge = theCoEdges.Value(aCoEdgeId.Index); + if (aCoEdge.IsRemoved) + continue; + if (aCoEdge.EdgeDefId.IsValid()) + appendUnique(myEdgeToCoEdges, aCoEdge.EdgeDefId.Index, aCoEdgeId); + } + myNbIndexedCoEdges = theCoEdges.Length(); + + // Edge -> Faces: derive from CoEdge.FaceDefId for new edges. + for (BRepGraph_EdgeId anEdgeId(theOldNbEdges); anEdgeId.IsValid(aNbEdges); ++anEdgeId) + { + if (theEdges.Value(anEdgeId.Index).IsRemoved) + continue; + const NCollection_Vector* aCoEdgeIdxs = + seekVec(myEdgeToCoEdges, anEdgeId.Index); + if (aCoEdgeIdxs == nullptr) + continue; + const int aNbCE = aCoEdgeIdxs->Length(); + + NCollection_LocalArray aFaces(aNbCE); + int aNbFaces = 0; + for (int i = 0; i < aNbCE; ++i) + { + const BRepGraphInc::CoEdgeDef& aCoEdge = theCoEdges.Value(aCoEdgeIdxs->Value(i).Index); + if (aCoEdge.FaceDefId.IsValid()) + aFaces[aNbFaces++] = aCoEdge.FaceDefId.Index; + } + if (aNbFaces == 0) + continue; + + for (int i = 1; i < aNbFaces; ++i) + { + const int aKey = aFaces[i]; + int j = i - 1; + while (j >= 0 && aFaces[j] > aKey) + { + aFaces[j + 1] = aFaces[j]; + --j; + } + aFaces[j + 1] = aKey; + } + + int aPrev = -1; + for (int i = 0; i < aNbFaces; ++i) + { + if (aFaces[i] != aPrev) + { + appendDirect(myEdgeToFaces, anEdgeId.Index, BRepGraph_FaceId(aFaces[i])); + aPrev = aFaces[i]; + } + } + } + + // Update cached face counts for new edges. + for (BRepGraph_EdgeId anEdgeId(theOldNbEdges); anEdgeId.IsValid(aNbEdges); ++anEdgeId) + { + const NCollection_Vector* aFaceVec = seekVec(myEdgeToFaces, anEdgeId.Index); + myEdgeFaceCount.ChangeValue(anEdgeId.Index) = (aFaceVec != nullptr ? aFaceVec->Length() : 0); + } + + // Wire -> Faces: iterate only new face entities and their WireRefIds. + const int aNbFaces = theFaces.Length(); + for (BRepGraph_FaceId aFaceId(theOldNbFaces); aFaceId.IsValid(aNbFaces); ++aFaceId) + { + const BRepGraphInc::FaceDef& aFace = theFaces.Value(aFaceId.Index); + if (aFace.IsRemoved) + continue; + for (const BRepGraph_WireRefId& aWireRefId : aFace.WireRefIds) + { + const int aRefIdx = aWireRefId.Index; + if (aRefIdx < 0 || aRefIdx >= theWireRefs.Length()) + continue; + const BRepGraphInc::WireRef& aRef = theWireRefs.Value(aRefIdx); + if (aRef.IsRemoved || !aRef.WireDefId.IsValid() || aRef.WireDefId.Index < 0 + || aRef.WireDefId.Index >= theWires.Length()) + continue; + if (theWires.Value(aRef.WireDefId.Index).IsRemoved) + continue; + appendUnique(myWireToFaces, aRef.WireDefId.Index, aFaceId); + } + } + + // Face -> Shells: iterate only new shell entities and their FaceRefIds. + const int aNbShells = theShells.Length(); + for (BRepGraph_ShellId aShellId(theOldNbShells); aShellId.IsValid(aNbShells); ++aShellId) + { + const BRepGraphInc::ShellDef& aShell = theShells.Value(aShellId.Index); + if (aShell.IsRemoved) + continue; + for (const BRepGraph_FaceRefId& aFaceRefId : aShell.FaceRefIds) + { + const int aRefIdx = aFaceRefId.Index; + if (aRefIdx < 0 || aRefIdx >= theFaceRefs.Length()) + continue; + const BRepGraphInc::FaceRef& aRef = theFaceRefs.Value(aRefIdx); + if (aRef.IsRemoved || !aRef.FaceDefId.IsValid() || aRef.FaceDefId.Index < 0 + || aRef.FaceDefId.Index >= theFaces.Length()) + continue; + if (theFaces.Value(aRef.FaceDefId.Index).IsRemoved) + continue; + appendUnique(myFaceToShells, aRef.FaceDefId.Index, aShellId); + } + } + + // Shell -> Solids: iterate only new solid entities and their ShellRefIds. + const int aNbSolids = theSolids.Length(); + for (BRepGraph_SolidId aSolidId(theOldNbSolids); aSolidId.IsValid(aNbSolids); ++aSolidId) + { + const BRepGraphInc::SolidDef& aSolid = theSolids.Value(aSolidId.Index); + if (aSolid.IsRemoved) + continue; + for (const BRepGraph_ShellRefId& aShellRefId : aSolid.ShellRefIds) + { + const int aRefIdx = aShellRefId.Index; + if (aRefIdx < 0 || aRefIdx >= theShellRefs.Length()) + continue; + const BRepGraphInc::ShellRef& aRef = theShellRefs.Value(aRefIdx); + if (aRef.IsRemoved || !aRef.ShellDefId.IsValid() || aRef.ShellDefId.Index < 0 + || aRef.ShellDefId.Index >= theShells.Length()) + continue; + if (theShells.Value(aRef.ShellDefId.Index).IsRemoved) + continue; + appendUnique(myShellToSolids, aRef.ShellDefId.Index, aSolidId); + } + } +} + +//================================================================================================= + +void BRepGraphInc_ReverseIndex::BindEdgeToWire(const BRepGraph_EdgeId theEdgeId, + const BRepGraph_WireId theWireId) +{ + appendUnique(myEdgeToWires, theEdgeId.Index, theWireId); +} + +//================================================================================================= + +void BRepGraphInc_ReverseIndex::BindCoEdgeToWire(const BRepGraph_CoEdgeId theCoEdgeId, + const BRepGraph_WireId theWireId) +{ + appendUnique(myCoEdgeToWires, theCoEdgeId.Index, theWireId); +} + +//================================================================================================= + +void BRepGraphInc_ReverseIndex::UnbindEdgeFromWire(const BRepGraph_EdgeId theEdgeId, + const BRepGraph_WireId theWireId) +{ + if (theEdgeId.Index < 0 || theEdgeId.Index >= myEdgeToWires.Length()) + return; + NCollection_Vector& aWires = myEdgeToWires.ChangeValue(theEdgeId.Index); + for (int i = 0; i < aWires.Length(); ++i) + { + if (aWires.Value(i) == theWireId) + { + if (i < aWires.Length() - 1) + aWires.ChangeValue(i) = aWires.Value(aWires.Length() - 1); + aWires.EraseLast(); + break; + } + } +} + +//================================================================================================= + +void BRepGraphInc_ReverseIndex::UnbindCoEdgeFromWire(const BRepGraph_CoEdgeId theCoEdgeId, + const BRepGraph_WireId theWireId) +{ + if (theCoEdgeId.Index < 0 || theCoEdgeId.Index >= myCoEdgeToWires.Length()) + return; + NCollection_Vector& aWires = myCoEdgeToWires.ChangeValue(theCoEdgeId.Index); + for (int i = 0; i < aWires.Length(); ++i) + { + if (aWires.Value(i) == theWireId) + { + if (i < aWires.Length() - 1) + aWires.ChangeValue(i) = aWires.Value(aWires.Length() - 1); + aWires.EraseLast(); + break; + } + } +} + +//================================================================================================= + +void BRepGraphInc_ReverseIndex::ReplaceEdgeInWireMap(const BRepGraph_EdgeId theOldEdgeId, + const BRepGraph_EdgeId theNewEdgeId, + const BRepGraph_WireId theWireId) +{ + UnbindEdgeFromWire(theOldEdgeId, theWireId); + BindEdgeToWire(theNewEdgeId, theWireId); +} + +//================================================================================================= + +void BRepGraphInc_ReverseIndex::BindVertexToEdge(const BRepGraph_VertexId theVertexId, + const BRepGraph_EdgeId theEdgeId) +{ + appendUnique(myVertexToEdges, theVertexId.Index, theEdgeId); +} + +//================================================================================================= + +void BRepGraphInc_ReverseIndex::UnbindVertexFromEdge(const BRepGraph_VertexId theVertexId, + const BRepGraph_EdgeId theEdgeId) +{ + if (theVertexId.Index < 0 || theVertexId.Index >= myVertexToEdges.Length()) + return; + NCollection_Vector& anEdges = myVertexToEdges.ChangeValue(theVertexId.Index); + for (int i = 0; i < anEdges.Length(); ++i) + { + if (anEdges.Value(i) == theEdgeId) + { + if (i < anEdges.Length() - 1) + anEdges.ChangeValue(i) = anEdges.Value(anEdges.Length() - 1); + anEdges.EraseLast(); + break; + } + } +} + +//================================================================================================= + +void BRepGraphInc_ReverseIndex::BindEdgeToCoEdge(const BRepGraph_EdgeId theEdgeId, + const BRepGraph_CoEdgeId theCoEdgeId) +{ + appendUnique(myEdgeToCoEdges, theEdgeId.Index, theCoEdgeId); +} + +//================================================================================================= + +void BRepGraphInc_ReverseIndex::UnbindEdgeFromCoEdge(const BRepGraph_EdgeId theEdgeId, + const BRepGraph_CoEdgeId theCoEdgeId) +{ + if (theEdgeId.Index < 0 || theEdgeId.Index >= myEdgeToCoEdges.Length()) + return; + NCollection_Vector& aCoEdges = myEdgeToCoEdges.ChangeValue(theEdgeId.Index); + for (int i = 0; i < aCoEdges.Length(); ++i) + { + if (aCoEdges.Value(i) == theCoEdgeId) + { + if (i < aCoEdges.Length() - 1) + aCoEdges.ChangeValue(i) = aCoEdges.Value(aCoEdges.Length() - 1); + aCoEdges.EraseLast(); + break; + } + } +} + +//================================================================================================= + +void BRepGraphInc_ReverseIndex::BindEdgeToFace(const BRepGraph_EdgeId theEdgeId, + const BRepGraph_FaceId theFaceId) +{ + // Detect new binding by checking vector size before/after appendUnique. + const int aSizeBefore = + (theEdgeId.Index < myEdgeToFaces.Length()) ? myEdgeToFaces.Value(theEdgeId.Index).Length() : 0; + appendUnique(myEdgeToFaces, theEdgeId.Index, theFaceId); + const int aSizeAfter = myEdgeToFaces.Value(theEdgeId.Index).Length(); + + if (aSizeAfter > aSizeBefore) + { + // SetValue auto-expands with 0-initialized entries. + if (theEdgeId.Index >= myEdgeFaceCount.Length()) + { + myEdgeFaceCount.SetValue(theEdgeId.Index, 1); + } + else + { + myEdgeFaceCount.ChangeValue(theEdgeId.Index) += 1; + } + } +} + +//================================================================================================= + +void BRepGraphInc_ReverseIndex::UnbindEdgeFromFace(const BRepGraph_EdgeId theEdgeId, + const BRepGraph_FaceId theFaceId) +{ + if (theEdgeId.Index < 0 || theEdgeId.Index >= myEdgeToFaces.Length()) + return; + Standard_ASSERT_VOID(myEdgeFaceCount.Length() == myEdgeToFaces.Length(), + "UnbindEdgeFromFace: myEdgeFaceCount out of sync with myEdgeToFaces"); + NCollection_Vector& aFaces = myEdgeToFaces.ChangeValue(theEdgeId.Index); + for (int i = 0; i < aFaces.Length(); ++i) + { + if (aFaces.Value(i) == theFaceId) + { + // Swap-remove: overwrite found entry with the last element, then erase the last. + // O(1) removal without shifting; adjacency list order is not significant. + if (i < aFaces.Length() - 1) + aFaces.ChangeValue(i) = aFaces.Value(aFaces.Length() - 1); + aFaces.EraseLast(); + Standard_ASSERT_VOID(myEdgeFaceCount.Value(theEdgeId.Index) > 0, + "UnbindEdgeFromFace: face count underflow"); + myEdgeFaceCount.ChangeValue(theEdgeId.Index) -= 1; + break; + } + } +} + +//================================================================================================= + +bool BRepGraphInc_ReverseIndex::Validate( + const NCollection_Vector& theEdges, + const NCollection_Vector& theCoEdges, + const NCollection_Vector& theWires, + const NCollection_Vector& theFaces, + const NCollection_Vector& theShells, + const NCollection_Vector& theSolids, + const NCollection_Vector& theShellRefs, + const NCollection_Vector& theFaceRefs, + const NCollection_Vector& theWireRefs, + const NCollection_Vector& theCoEdgeRefs, + const NCollection_Vector& theVertexRefs) const +{ + auto hasActiveWireUsageOfEdge = [&](const int theWireIdx, const int theEdgeIdx) -> bool { + if (theWireIdx < 0 || theWireIdx >= theWires.Length() || theEdgeIdx < 0 + || theEdgeIdx >= theEdges.Length()) + { + return false; + } + const BRepGraphInc::WireDef& aWire = theWires.Value(theWireIdx); + if (aWire.IsRemoved) + { + return false; + } + for (const BRepGraph_CoEdgeRefId& aCoEdgeRefId : aWire.CoEdgeRefIds) + { + const int aRefIdx = aCoEdgeRefId.Index; + if (aRefIdx < 0 || aRefIdx >= theCoEdgeRefs.Length()) + { + continue; + } + const BRepGraphInc::CoEdgeRef& aRef = theCoEdgeRefs.Value(aRefIdx); + if (aRef.IsRemoved || !aRef.CoEdgeDefId.IsValid()) + { + continue; + } + if (aRef.ParentId.NodeKind != BRepGraph_NodeId::Kind::Wire + || aRef.ParentId.Index != theWireIdx) + { + continue; + } + const int aCoEdgeIdx = aRef.CoEdgeDefId.Index; + if (aCoEdgeIdx < 0 || aCoEdgeIdx >= theCoEdges.Length()) + { + continue; + } + const BRepGraphInc::CoEdgeDef& aCoEdge = theCoEdges.Value(aCoEdgeIdx); + if (aCoEdge.IsRemoved || !aCoEdge.EdgeDefId.IsValid()) + { + continue; + } + if (aCoEdge.EdgeDefId.Index == theEdgeIdx) + { + return true; + } + } + return false; + }; + + auto hasActiveFaceRefForWire = [&](const int theWireIdx, const int theFaceIdx) -> bool { + if (theWireIdx < 0 || theWireIdx >= theWires.Length() || theFaceIdx < 0 + || theFaceIdx >= theFaces.Length()) + { + return false; + } + const BRepGraphInc::FaceDef& aFace = theFaces.Value(theFaceIdx); + if (aFace.IsRemoved) + { + return false; + } + for (const BRepGraph_WireRefId& aWireRefId : aFace.WireRefIds) + { + const int aRefIdx = aWireRefId.Index; + if (aRefIdx < 0 || aRefIdx >= theWireRefs.Length()) + { + continue; + } + const BRepGraphInc::WireRef& aRef = theWireRefs.Value(aRefIdx); + if (aRef.IsRemoved || !aRef.WireDefId.IsValid()) + { + continue; + } + if (aRef.ParentId.NodeKind != BRepGraph_NodeId::Kind::Face + || aRef.ParentId.Index != theFaceIdx) + { + continue; + } + if (aRef.WireDefId.Index == theWireIdx) + { + return true; + } + } + return false; + }; + + auto hasActiveFaceForEdge = [&](const int theEdgeIdx, const int theFaceIdx) -> bool { + if (theEdgeIdx < 0 || theEdgeIdx >= theEdges.Length() || theFaceIdx < 0 + || theFaceIdx >= theFaces.Length()) + { + return false; + } + const BRepGraphInc::EdgeDef& anEdge = theEdges.Value(theEdgeIdx); + if (anEdge.IsRemoved) + { + return false; + } + + const NCollection_Vector* aCoEdgeIds = seekVec(myEdgeToCoEdges, theEdgeIdx); + if (aCoEdgeIds == nullptr) + { + return false; + } + + return hasActiveFaceForEdgeInCoEdges(*aCoEdgeIds, theCoEdges, theEdgeIdx, theFaceIdx); + }; + + auto hasActiveFaceRef = [&](const int theShellIdx, const int theFaceIdx) -> bool { + if (theShellIdx < 0 || theShellIdx >= theShells.Length() || theFaceIdx < 0 + || theFaceIdx >= theFaces.Length()) + { + return false; + } + const BRepGraphInc::ShellDef& aShell = theShells.Value(theShellIdx); + if (aShell.IsRemoved) + { + return false; + } + for (const BRepGraph_FaceRefId& aFaceRefId : aShell.FaceRefIds) + { + const int aRefIdx = aFaceRefId.Index; + if (aRefIdx < 0 || aRefIdx >= theFaceRefs.Length()) + { + continue; + } + const BRepGraphInc::FaceRef& aRef = theFaceRefs.Value(aRefIdx); + if (aRef.IsRemoved || !aRef.FaceDefId.IsValid()) + { + continue; + } + if (aRef.ParentId.NodeKind != BRepGraph_NodeId::Kind::Shell + || aRef.ParentId.Index != theShellIdx) + { + continue; + } + if (aRef.FaceDefId.Index == theFaceIdx) + { + return true; + } + } + return false; + }; + + auto hasActiveShellRef = [&](const int theSolidIdx, const int theShellIdx) -> bool { + if (theSolidIdx < 0 || theSolidIdx >= theSolids.Length() || theShellIdx < 0 + || theShellIdx >= theShells.Length()) + { + return false; + } + const BRepGraphInc::SolidDef& aSolid = theSolids.Value(theSolidIdx); + if (aSolid.IsRemoved) + { + return false; + } + for (const BRepGraph_ShellRefId& aShellRefId : aSolid.ShellRefIds) + { + const int aRefIdx = aShellRefId.Index; + if (aRefIdx < 0 || aRefIdx >= theShellRefs.Length()) + { + continue; + } + const BRepGraphInc::ShellRef& aRef = theShellRefs.Value(aRefIdx); + if (aRef.IsRemoved || !aRef.ShellDefId.IsValid()) + { + continue; + } + if (aRef.ParentId.NodeKind != BRepGraph_NodeId::Kind::Solid + || aRef.ParentId.Index != theSolidIdx) + { + continue; + } + if (aRef.ShellDefId.Index == theShellIdx) + { + return true; + } + } + return false; + }; + + // Check: for each coedge ref entry, edge->wire reverse entry must exist. + const int aNbCoEdgeRefs = theCoEdgeRefs.Length(); + for (BRepGraph_CoEdgeRefId aCoEdgeRefId(0); aCoEdgeRefId.IsValid(aNbCoEdgeRefs); ++aCoEdgeRefId) + { + const BRepGraphInc::CoEdgeRef& aRef = theCoEdgeRefs.Value(aCoEdgeRefId.Index); + if (aRef.IsRemoved || !aRef.ParentId.IsValid() + || aRef.ParentId.NodeKind != BRepGraph_NodeId::Kind::Wire || !aRef.CoEdgeDefId.IsValid()) + continue; + if (aRef.ParentId.Index < 0 || aRef.ParentId.Index >= theWires.Length() + || aRef.CoEdgeDefId.Index < 0 || aRef.CoEdgeDefId.Index >= theCoEdges.Length()) + return false; + const BRepGraphInc::WireDef& aWire = theWires.Value(aRef.ParentId.Index); + if (aWire.IsRemoved) + continue; + const BRepGraphInc::CoEdgeDef& aCoEdge = theCoEdges.Value(aRef.CoEdgeDefId.Index); + if (aCoEdge.IsRemoved || !aCoEdge.EdgeDefId.IsValid()) + return false; + if (!containsIndexInTable(myEdgeToWires, aCoEdge.EdgeDefId.Index, aRef.ParentId.Index)) + return false; + } + + // Check: for each edge's start/end vertex, vertex->edge reverse entry must exist. + const int aNbEdges = theEdges.Length(); + for (BRepGraph_EdgeId anEdgeId(0); anEdgeId.IsValid(aNbEdges); ++anEdgeId) + { + const BRepGraphInc::EdgeDef& anEdge = theEdges.Value(anEdgeId.Index); + if (anEdge.IsRemoved) + continue; + const BRepGraph_VertexId aStartVtx = resolveVertexDefId(theVertexRefs, anEdge.StartVertexRefId); + const BRepGraph_VertexId anEndVtx = resolveVertexDefId(theVertexRefs, anEdge.EndVertexRefId); + if (aStartVtx.IsValid()) + { + if (!containsIndexInTable(myVertexToEdges, aStartVtx.Index, anEdgeId.Index)) + return false; + } + if (anEndVtx.IsValid() && anEndVtx != aStartVtx) + { + if (!containsIndexInTable(myVertexToEdges, anEndVtx.Index, anEdgeId.Index)) + return false; + } + } + + // Check: for each edge's CoEdges with valid FaceDefId, edge->face reverse entry must exist. + const int aNbCoEdges = theCoEdges.Length(); + for (BRepGraph_CoEdgeId aCoEdgeId(0); aCoEdgeId.IsValid(aNbCoEdges); ++aCoEdgeId) + { + const BRepGraphInc::CoEdgeDef& aCoEdge = theCoEdges.Value(aCoEdgeId.Index); + if (aCoEdge.IsRemoved || !aCoEdge.FaceDefId.IsValid()) + continue; + if (!containsIndexInTable(myEdgeToFaces, aCoEdge.EdgeDefId.Index, aCoEdge.FaceDefId.Index)) + return false; + } + + // Check: for each wire ref entry, wire->face reverse entry must exist. + const int aNbWireRefs = theWireRefs.Length(); + for (BRepGraph_WireRefId aWireRefId(0); aWireRefId.IsValid(aNbWireRefs); ++aWireRefId) + { + const BRepGraphInc::WireRef& aRef = theWireRefs.Value(aWireRefId.Index); + if (aRef.IsRemoved || !aRef.ParentId.IsValid() + || aRef.ParentId.NodeKind != BRepGraph_NodeId::Kind::Face || !aRef.WireDefId.IsValid()) + continue; + if (aRef.ParentId.Index < 0 || aRef.ParentId.Index >= theFaces.Length() + || aRef.WireDefId.Index < 0 || aRef.WireDefId.Index >= theWires.Length()) + return false; + const BRepGraphInc::FaceDef& aFace = theFaces.Value(aRef.ParentId.Index); + if (aFace.IsRemoved || theWires.Value(aRef.WireDefId.Index).IsRemoved) + continue; + if (!containsIndexInTable(myWireToFaces, aRef.WireDefId.Index, aRef.ParentId.Index)) + return false; + } + + // Check: for each face ref entry, face->shell reverse entry must exist. + const int aNbFaceRefs = theFaceRefs.Length(); + for (BRepGraph_FaceRefId aFaceRefId(0); aFaceRefId.IsValid(aNbFaceRefs); ++aFaceRefId) + { + const BRepGraphInc::FaceRef& aRef = theFaceRefs.Value(aFaceRefId.Index); + if (aRef.IsRemoved || !aRef.ParentId.IsValid() + || aRef.ParentId.NodeKind != BRepGraph_NodeId::Kind::Shell || !aRef.FaceDefId.IsValid()) + continue; + if (aRef.ParentId.Index < 0 || aRef.ParentId.Index >= theShells.Length() + || aRef.FaceDefId.Index < 0 || aRef.FaceDefId.Index >= theFaces.Length()) + return false; + const BRepGraphInc::ShellDef& aShell = theShells.Value(aRef.ParentId.Index); + if (aShell.IsRemoved || theFaces.Value(aRef.FaceDefId.Index).IsRemoved) + continue; + if (!containsIndexInTable(myFaceToShells, aRef.FaceDefId.Index, aRef.ParentId.Index)) + return false; + } + + // Check: for each shell ref entry, shell->solid reverse entry must exist. + const int aNbShellRefs = theShellRefs.Length(); + for (BRepGraph_ShellRefId aShellRefId(0); aShellRefId.IsValid(aNbShellRefs); ++aShellRefId) + { + const BRepGraphInc::ShellRef& aRef = theShellRefs.Value(aShellRefId.Index); + if (aRef.IsRemoved || !aRef.ParentId.IsValid() + || aRef.ParentId.NodeKind != BRepGraph_NodeId::Kind::Solid || !aRef.ShellDefId.IsValid()) + continue; + if (aRef.ParentId.Index < 0 || aRef.ParentId.Index >= theSolids.Length() + || aRef.ShellDefId.Index < 0 || aRef.ShellDefId.Index >= theShells.Length()) + return false; + const BRepGraphInc::SolidDef& aSolid = theSolids.Value(aRef.ParentId.Index); + if (aSolid.IsRemoved || theShells.Value(aRef.ShellDefId.Index).IsRemoved) + continue; + if (!containsIndexInTable(myShellToSolids, aRef.ShellDefId.Index, aRef.ParentId.Index)) + return false; + } + + // Check reverse tables for stale/extra entries not backed by active forward refs. + const int aNbEdgeToWires = myEdgeToWires.Length(); + for (BRepGraph_EdgeId anEdgeId(0); anEdgeId.IsValid(aNbEdgeToWires); ++anEdgeId) + { + const NCollection_Vector& aWires = WiresOfEdgeRef(anEdgeId); + for (const BRepGraph_WireId& aWireId2 : aWires) + { + const int aWireIdx = aWireId2.Index; + if (aWireIdx < 0 || aWireIdx >= theWires.Length()) + { + return false; + } + if (!hasActiveWireUsageOfEdge(aWireIdx, anEdgeId.Index)) + { + return false; + } + } + } + + const int aNbEdgeToCoEdges = myEdgeToCoEdges.Length(); + for (BRepGraph_EdgeId anEdgeId(0); anEdgeId.IsValid(aNbEdgeToCoEdges); ++anEdgeId) + { + const NCollection_Vector& aCoEdges = CoEdgesOfEdgeRef(anEdgeId); + for (const BRepGraph_CoEdgeId& aCoEdgeId2 : aCoEdges) + { + const int aCoEdgeIdx = aCoEdgeId2.Index; + if (aCoEdgeIdx < 0 || aCoEdgeIdx >= theCoEdges.Length()) + { + return false; + } + const BRepGraphInc::CoEdgeDef& aCoEdge = theCoEdges.Value(aCoEdgeIdx); + if (aCoEdge.IsRemoved || !aCoEdge.EdgeDefId.IsValid() + || aCoEdge.EdgeDefId.Index != anEdgeId.Index) + { + return false; + } + } + } + + const int aNbWireToFaces = myWireToFaces.Length(); + for (BRepGraph_WireId aWireId(0); aWireId.IsValid(aNbWireToFaces); ++aWireId) + { + const NCollection_Vector& aFaces = FacesOfWireRef(aWireId); + for (const BRepGraph_FaceId& aFaceId2 : aFaces) + { + const int aFaceIdx = aFaceId2.Index; + if (aFaceIdx < 0 || aFaceIdx >= theFaces.Length()) + { + return false; + } + if (!hasActiveFaceRefForWire(aWireId.Index, aFaceIdx)) + { + return false; + } + } + } + + const int aNbEdgeToFaces = myEdgeToFaces.Length(); + for (BRepGraph_EdgeId anEdgeId(0); anEdgeId.IsValid(aNbEdgeToFaces); ++anEdgeId) + { + const NCollection_Vector* aFaces = seekVec(myEdgeToFaces, anEdgeId.Index); + if (aFaces == nullptr) + { + continue; + } + for (const BRepGraph_FaceId& aFaceId2 : *aFaces) + { + const int aFaceIdx = aFaceId2.Index; + if (aFaceIdx < 0 || aFaceIdx >= theFaces.Length()) + { + return false; + } + if (!hasActiveFaceForEdge(anEdgeId.Index, aFaceIdx)) + { + return false; + } + } + } + + const int aNbFaceToShells = myFaceToShells.Length(); + for (BRepGraph_FaceId aFaceId(0); aFaceId.IsValid(aNbFaceToShells); ++aFaceId) + { + const NCollection_Vector* aShellsVec = + seekVec(myFaceToShells, aFaceId.Index); + if (aShellsVec == nullptr) + { + continue; + } + for (const BRepGraph_ShellId& aShellId2 : *aShellsVec) + { + const int aShellIdx = aShellId2.Index; + if (aShellIdx < 0 || aShellIdx >= theShells.Length()) + { + return false; + } + if (!hasActiveFaceRef(aShellIdx, aFaceId.Index)) + { + return false; + } + } + } + + const int aNbShellToSolids = myShellToSolids.Length(); + for (BRepGraph_ShellId aShellId(0); aShellId.IsValid(aNbShellToSolids); ++aShellId) + { + const NCollection_Vector* aSolidsVec = + seekVec(myShellToSolids, aShellId.Index); + if (aSolidsVec == nullptr) + { + continue; + } + for (const BRepGraph_SolidId& aSolidId2 : *aSolidsVec) + { + const int aSolidIdx = aSolidId2.Index; + if (aSolidIdx < 0 || aSolidIdx >= theSolids.Length()) + { + return false; + } + if (!hasActiveShellRef(aSolidIdx, aShellId.Index)) + { + return false; + } + } + } + + return true; +} + +//================================================================================================= + +void BRepGraphInc_ReverseIndex::BuildProductOccurrences( + const NCollection_Vector& theOccurrences, + const int theNbProducts) +{ + myProductToOccurrences.Clear(); + preSize(myProductToOccurrences, theNbProducts, myAllocator); + + const int aNbOccurrences = theOccurrences.Length(); + for (BRepGraph_OccurrenceId anOccurrenceId(0); anOccurrenceId.IsValid(aNbOccurrences); + ++anOccurrenceId) + { + const BRepGraphInc::OccurrenceDef& anOcc = theOccurrences.Value(anOccurrenceId.Index); + if (anOcc.IsRemoved) + continue; + if (anOcc.ProductDefId.IsValid()) + appendDirect(myProductToOccurrences, anOcc.ProductDefId.Index, anOccurrenceId); + } +} diff --git a/src/ModelingData/TKBRep/BRepGraphInc/BRepGraphInc_ReverseIndex.hxx b/src/ModelingData/TKBRep/BRepGraphInc/BRepGraphInc_ReverseIndex.hxx new file mode 100644 index 0000000000..c24ddd8dce --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraphInc/BRepGraphInc_ReverseIndex.hxx @@ -0,0 +1,499 @@ +// Copyright (c) 2026 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 _BRepGraphInc_ReverseIndex_HeaderFile +#define _BRepGraphInc_ReverseIndex_HeaderFile + +#include +#include +#include +#include + +namespace BRepGraphInc +{ +struct EdgeDef; +struct CoEdgeDef; +struct WireDef; +struct FaceDef; +struct ShellDef; +struct SolidDef; +struct CompoundDef; +struct CompSolidDef; +struct ProductDef; +struct OccurrenceDef; +struct ShellRef; +struct FaceRef; +struct WireRef; +struct CoEdgeRef; +struct SolidRef; +struct ChildRef; +struct VertexRef; +} // namespace BRepGraphInc + +//! @brief Backend reverse incidence indices for O(1) upward navigation. +//! +//! Built from entity and reference-entry tables after population. +//! Full Build() is used for initial construction, while builder-side +//! mutations maintain the index incrementally through targeted bind/unbind +//! operations and BuildDelta() for append workflows. +//! +//! ## Two query tiers +//! Pointer-returning methods (e.g. WiresOfEdge() -> nullptr for empty) serve +//! performance-critical backend code that avoids static-empty-vector overhead. +//! Safe-reference methods (e.g. WiresOfEdgeRef() -> static empty vector) serve +//! the public facade (TopoView delegates to Ref variants). +class BRepGraphInc_ReverseIndex +{ +public: + DEFINE_STANDARD_ALLOC + + //! Set allocator for internal index tables. + void SetAllocator(const occ::handle& theAlloc) + { + myAllocator = theAlloc; + } + + //! Clear all indices. + Standard_EXPORT void Clear(); + + //! Rebuild all reverse indices from the entity and reference-entry tables. + //! Edge-to-face index is derived from CoEdge.FaceDefId links. + //! @pre SetAllocator() must have been called (uses myAllocator for inner vectors). + //! @param[in] theEdges edge entity vector (for vertex-to-edge, edge-to-face) + //! @param[in] theCoEdges coedge entity vector (for edge-to-coedge and edge-to-face) + //! @param[in] theWires wire entity vector (parent validation for coedge refs) + //! @param[in] theFaces face entity vector (parent validation for wire refs) + //! @param[in] theShells shell entity vector (parent validation for face refs) + //! @param[in] theSolids solid entity vector (parent validation for shell refs) + //! @param[in] theCompounds compound entity vector (parent validation for child refs) + //! @param[in] theCompSolids compsolid entity vector (parent validation for solid refs) + //! @param[in] theShellRefs shell ref-entry table (solid -> shell reverse) + //! @param[in] theFaceRefs face ref-entry table (shell -> face reverse) + //! @param[in] theWireRefs wire ref-entry table (face -> wire reverse) + //! @param[in] theCoEdgeRefs coedge ref-entry table (wire -> coedge/edge reverse) + //! @param[in] theSolidRefs solid ref-entry table (compsolid -> solid reverse) + //! @param[in] theChildRefs child ref-entry table (compound child reverse) + //! @param[in] theVertexRefs vertex ref-entry table (edge vertex resolution) + Standard_EXPORT void Build(const NCollection_Vector& theEdges, + const NCollection_Vector& theCoEdges, + const NCollection_Vector& theWires, + const NCollection_Vector& theFaces, + const NCollection_Vector& theShells, + const NCollection_Vector& theSolids, + const NCollection_Vector& theCompounds, + const NCollection_Vector& theCompSolids, + const NCollection_Vector& theShellRefs, + const NCollection_Vector& theFaceRefs, + const NCollection_Vector& theWireRefs, + const NCollection_Vector& theCoEdgeRefs, + const NCollection_Vector& theSolidRefs, + const NCollection_Vector& theChildRefs, + const NCollection_Vector& theVertexRefs); + + //! Incrementally update reverse indices for entities/ref-parents appended after a previous + //! Build(). Only processes entities from the old counts to the current vector lengths and + //! reference entries whose parent was newly appended. + //! Compound/CompSolid reverse indices are not updated incrementally - + //! these containers are populated once during Build() and not mutated post-build. + //! @param[in] theOldNbEdges edge count before the append operation + //! @param[in] theOldNbWires wire count before the append operation + //! @param[in] theOldNbFaces face count before the append operation + //! @param[in] theOldNbShells shell count before the append operation + //! @param[in] theOldNbSolids solid count before the append operation + Standard_EXPORT void BuildDelta(const NCollection_Vector& theEdges, + const NCollection_Vector& theCoEdges, + const NCollection_Vector& theWires, + const NCollection_Vector& theFaces, + const NCollection_Vector& theShells, + const NCollection_Vector& theSolids, + const NCollection_Vector& theShellRefs, + const NCollection_Vector& theFaceRefs, + const NCollection_Vector& theWireRefs, + const NCollection_Vector& theCoEdgeRefs, + const NCollection_Vector& theVertexRefs, + const int theOldNbEdges, + const int theOldNbWires, + const int theOldNbFaces, + const int theOldNbShells, + const int theOldNbSolids); + + //! Build product-to-occurrences reverse index. + //! @param[in] theOccurrences occurrence entity vector + //! @param[in] theNbProducts total number of products (for pre-sizing) + Standard_EXPORT void BuildProductOccurrences( + const NCollection_Vector& theOccurrences, + const int theNbProducts); + + //! Return wire indices containing the given edge. + [[nodiscard]] const NCollection_Vector* WiresOfEdge( + const BRepGraph_EdgeId theEdgeId) const + { + return seekVec(myEdgeToWires, theEdgeId.Index); + } + + //! Return face indices containing the given edge (derived from CoEdge.FaceDefId links). + [[nodiscard]] const NCollection_Vector* FacesOfEdge( + const BRepGraph_EdgeId theEdgeId) const + { + return seekVec(myEdgeToFaces, theEdgeId.Index); + } + + //! Return coedge indices referencing the given edge. + [[nodiscard]] const NCollection_Vector* CoEdgesOfEdge( + const BRepGraph_EdgeId theEdgeId) const + { + return seekVec(myEdgeToCoEdges, theEdgeId.Index); + } + + //! Return cached face count for an edge - O(1). + //! Populated during Build() and updated incrementally by BindEdgeToFace(). + [[nodiscard]] int NbFacesOfEdge(const BRepGraph_EdgeId theEdgeId) const + { + if (theEdgeId.Index < 0 || theEdgeId.Index >= myEdgeFaceCount.Length()) + return 0; + return myEdgeFaceCount.Value(theEdgeId.Index); + } + + //! Return edge indices incident to the given vertex. + [[nodiscard]] const NCollection_Vector* EdgesOfVertex( + const BRepGraph_VertexId theVertexId) const + { + return seekVec(myVertexToEdges, theVertexId.Index); + } + + //! Return face indices containing the given wire. + [[nodiscard]] const NCollection_Vector* FacesOfWire( + const BRepGraph_WireId theWireId) const + { + return seekVec(myWireToFaces, theWireId.Index); + } + + //! Return shell indices containing the given face. + [[nodiscard]] const NCollection_Vector* ShellsOfFace( + const BRepGraph_FaceId theFaceId) const + { + return seekVec(myFaceToShells, theFaceId.Index); + } + + //! Return solid indices containing the given shell. + [[nodiscard]] const NCollection_Vector* SolidsOfShell( + const BRepGraph_ShellId theShellId) const + { + return seekVec(myShellToSolids, theShellId.Index); + } + + //! Return compound indices containing the given solid as a NodeUsage. + [[nodiscard]] const NCollection_Vector* CompoundsOfSolid( + const BRepGraph_SolidId theSolidId) const + { + return seekVec(myCompoundsOfSolid, theSolidId.Index); + } + + //! Return compsolid indices containing the given solid as a SolidUsage. + [[nodiscard]] const NCollection_Vector* CompSolidsOfSolid( + const BRepGraph_SolidId theSolidId) const + { + return seekVec(myCompSolidsOfSolid, theSolidId.Index); + } + + //! Return compound indices containing the given shell as a NodeUsage. + [[nodiscard]] const NCollection_Vector* CompoundsOfShell( + const BRepGraph_ShellId theShellId) const + { + return seekVec(myCompoundsOfShell, theShellId.Index); + } + + //! Return compound indices containing the given face as a NodeUsage. + [[nodiscard]] const NCollection_Vector* CompoundsOfFace( + const BRepGraph_FaceId theFaceId) const + { + return seekVec(myCompoundsOfFace, theFaceId.Index); + } + + //! Return compound indices containing the given compound as a NodeUsage. + [[nodiscard]] const NCollection_Vector* CompoundsOfCompound( + const BRepGraph_CompoundId theCompoundId) const + { + return seekVec(myCompoundsOfCompound, theCompoundId.Index); + } + + //! Return compound indices containing the given compsolid as a NodeUsage. + [[nodiscard]] const NCollection_Vector* CompoundsOfCompSolid( + const BRepGraph_CompSolidId theCompSolidId) const + { + return seekVec(myCompoundsOfCompSolid, theCompSolidId.Index); + } + + //! Return wire indices containing the given coedge. + [[nodiscard]] const NCollection_Vector* WiresOfCoEdge( + const BRepGraph_CoEdgeId theCoEdgeId) const + { + return seekVec(myCoEdgeToWires, theCoEdgeId.Index); + } + + //! Return occurrence indices that reference the given product. + [[nodiscard]] const NCollection_Vector* OccurrencesOfProduct( + const BRepGraph_ProductId theProductId) const + { + return seekVec(myProductToOccurrences, theProductId.Index); + } + + // --- Safe reference accessors (return empty vector instead of nullptr) --- + + //! Return wire indices containing the given edge (safe reference, never null). + [[nodiscard]] const NCollection_Vector& WiresOfEdgeRef( + const BRepGraph_EdgeId theEdgeId) const + { + return seekRef(myEdgeToWires, theEdgeId.Index); + } + + //! Return face indices containing the given edge (safe reference, never null). + [[nodiscard]] const NCollection_Vector& FacesOfEdgeRef( + const BRepGraph_EdgeId theEdgeId) const + { + return seekRef(myEdgeToFaces, theEdgeId.Index); + } + + //! Return coedge indices referencing the given edge (safe reference, never null). + [[nodiscard]] const NCollection_Vector& CoEdgesOfEdgeRef( + const BRepGraph_EdgeId theEdgeId) const + { + return seekRef(myEdgeToCoEdges, theEdgeId.Index); + } + + //! Return face indices containing the given wire (safe reference, never null). + [[nodiscard]] const NCollection_Vector& FacesOfWireRef( + const BRepGraph_WireId theWireId) const + { + return seekRef(myWireToFaces, theWireId.Index); + } + + //! Return edge indices incident to the given vertex (safe reference, never null). + [[nodiscard]] const NCollection_Vector& EdgesOfVertexRef( + const BRepGraph_VertexId theVertexId) const + { + return seekRef(myVertexToEdges, theVertexId.Index); + } + + //! Return shell indices containing the given face (safe reference, never null). + [[nodiscard]] const NCollection_Vector& ShellsOfFaceRef( + const BRepGraph_FaceId theFaceId) const + { + return seekRef(myFaceToShells, theFaceId.Index); + } + + //! Return solid indices containing the given shell (safe reference, never null). + [[nodiscard]] const NCollection_Vector& SolidsOfShellRef( + const BRepGraph_ShellId theShellId) const + { + return seekRef(myShellToSolids, theShellId.Index); + } + + //! Verify reverse index consistency against forward entity/reference-entry tables. + //! For each forward ref (e.g., wire->edge), checks that the corresponding + //! reverse entry exists (edge->wire). Intended for debug validation. + //! @return true if all forward refs have matching reverse entries + Standard_EXPORT bool Validate( + const NCollection_Vector& theEdges, + const NCollection_Vector& theCoEdges, + const NCollection_Vector& theWires, + const NCollection_Vector& theFaces, + const NCollection_Vector& theShells, + const NCollection_Vector& theSolids, + const NCollection_Vector& theShellRefs, + const NCollection_Vector& theFaceRefs, + const NCollection_Vector& theWireRefs, + const NCollection_Vector& theCoEdgeRefs, + const NCollection_Vector& theVertexRefs) const; + + // --- Incremental mutation --- + + //! Register an edge as belonging to a wire (O(1) amortized). + Standard_EXPORT void BindEdgeToWire(const BRepGraph_EdgeId theEdgeId, + const BRepGraph_WireId theWireId); + + //! Remove a wire from the edge-to-wire index for a given edge. + Standard_EXPORT void UnbindEdgeFromWire(const BRepGraph_EdgeId theEdgeId, + const BRepGraph_WireId theWireId); + + //! Replace an edge in the edge-to-wire index for a specific wire. + Standard_EXPORT void ReplaceEdgeInWireMap(const BRepGraph_EdgeId theOldEdgeId, + const BRepGraph_EdgeId theNewEdgeId, + const BRepGraph_WireId theWireId); + + //! Register a vertex as incident to an edge (O(1) amortized, deduplicates). + Standard_EXPORT void BindVertexToEdge(const BRepGraph_VertexId theVertexId, + const BRepGraph_EdgeId theEdgeId); + + //! Remove an edge from the vertex-to-edge index for a given vertex. + Standard_EXPORT void UnbindVertexFromEdge(const BRepGraph_VertexId theVertexId, + const BRepGraph_EdgeId theEdgeId); + + //! Register a coedge as referencing an edge (O(1) amortized). + Standard_EXPORT void BindEdgeToCoEdge(const BRepGraph_EdgeId theEdgeId, + const BRepGraph_CoEdgeId theCoEdgeId); + + //! Remove a coedge from the edge-to-coedge index for a given edge. + Standard_EXPORT void UnbindEdgeFromCoEdge(const BRepGraph_EdgeId theEdgeId, + const BRepGraph_CoEdgeId theCoEdgeId); + + //! Register a coedge as belonging to a wire (O(1) amortized). + Standard_EXPORT void BindCoEdgeToWire(const BRepGraph_CoEdgeId theCoEdgeId, + const BRepGraph_WireId theWireId); + + //! Remove a wire from the coedge-to-wire index for a given coedge. + Standard_EXPORT void UnbindCoEdgeFromWire(const BRepGraph_CoEdgeId theCoEdgeId, + const BRepGraph_WireId theWireId); + + //! Register an edge as belonging to a face (O(1) amortized, deduplicates). + Standard_EXPORT void BindEdgeToFace(const BRepGraph_EdgeId theEdgeId, + const BRepGraph_FaceId theFaceId); + + //! Remove a face from the edge-to-face index for a given edge. + Standard_EXPORT void UnbindEdgeFromFace(const BRepGraph_EdgeId theEdgeId, + const BRepGraph_FaceId theFaceId); + +private: + //! Dense vector type: outer index = entity key, inner vector = typed adjacency list. + template + using TypedIndexTable = NCollection_Vector>; + + //! Bounds-checked lookup returning nullptr for out-of-range or empty slots. + template + static const NCollection_Vector* seekVec(const TypedIndexTable& theIdx, const int theKey) + { + if (theKey < 0 || theKey >= theIdx.Length()) + return nullptr; + const NCollection_Vector& aVec = theIdx.Value(theKey); + return aVec.IsEmpty() ? nullptr : &aVec; + } + + //! Bounds-checked lookup returning a const reference (empty vector for missing keys). + template + static const NCollection_Vector& seekRef(const TypedIndexTable& theIdx, const int theKey) + { + const NCollection_Vector* aPtr = seekVec(theIdx, theKey); + if (aPtr != nullptr) + return *aPtr; + static const NCollection_Vector THE_EMPTY; + return THE_EMPTY; + } + + //! Ensure theIdx has at least theSize slots (pre-sizing with empty vectors). + //! If theAlloc is non-null, inner vectors are constructed with it. + template + static void ensureSize(TypedIndexTable& theIdx, + const int theSize, + const occ::handle& theAlloc = + occ::handle()) + { + if (theSize <= theIdx.Length()) + return; + + if (!theAlloc.IsNull()) + { + for (int i = theIdx.Length(); i < theSize; ++i) + { + theIdx.Append(NCollection_Vector(16, theAlloc)); + } + } + else + { + for (int i = theIdx.Length(); i < theSize; ++i) + { + theIdx.Appended(); + } + } + } + + //! Ensure theVec has at least theSize elements. + //! New elements are default-constructed (zero for scalar types). + template + static void ensureSize(NCollection_Vector& theVec, const int theSize) + { + while (theVec.Length() < theSize) + { + theVec.Appended(); + } + } + + //! Resize theIdx exactly to theSize slots (clears previous content first). + template + static void preSize(TypedIndexTable& theIdx, + const int theSize, + const occ::handle& theAlloc = + occ::handle()) + { + theIdx.Clear(); + ensureSize(theIdx, theSize, theAlloc); + } + + //! Add theVal to the vector at theKey, creating if needed. Skips duplicates. + template + static void appendUnique(TypedIndexTable& theIdx, const int theKey, const T theVal) + { + Standard_ASSERT_RETURN(theKey >= 0, "appendUnique: negative key", ); + // Grow if needed for incremental mutation after Build(). + if (theKey >= theIdx.Length()) + ensureSize(theIdx, theKey + 1); + + NCollection_Vector& aVec = theIdx.ChangeValue(theKey); + for (const T& anElem : aVec) + { + if (anElem == theVal) + return; + } + aVec.Append(theVal); + } + + //! Add theVal to the vector at theKey unconditionally (no duplicate check). + //! Used during Build() where freshly-cleared indices guarantee no duplicates. + template + static void appendDirect(TypedIndexTable& theIdx, const int theKey, const T theVal) + { + Standard_ASSERT_RETURN(theKey >= 0, "appendDirect: negative key", ); + // During Build(), outer vector is pre-sized so theKey < Length(). + // For safety, grow if somehow out of range. + if (theKey >= theIdx.Length()) + ensureSize(theIdx, theKey + 1); + + theIdx.ChangeValue(theKey).Append(theVal); + } + + occ::handle myAllocator; + + TypedIndexTable myEdgeToWires; + TypedIndexTable myEdgeToFaces; + TypedIndexTable myEdgeToCoEdges; + TypedIndexTable myVertexToEdges; + TypedIndexTable myWireToFaces; + TypedIndexTable myFaceToShells; + TypedIndexTable myShellToSolids; + TypedIndexTable myProductToOccurrences; + + TypedIndexTable myCompoundsOfSolid; //!< Solid -> parent Compound indices. + TypedIndexTable + myCompSolidsOfSolid; //!< Solid -> parent CompSolid indices. + TypedIndexTable myCompoundsOfShell; //!< Shell -> parent Compound indices. + TypedIndexTable myCompoundsOfFace; //!< Face -> parent Compound indices. + TypedIndexTable + myCompoundsOfCompound; //!< Compound -> parent Compound indices. + TypedIndexTable + myCompoundsOfCompSolid; //!< CompSolid -> parent Compound indices. + TypedIndexTable myCoEdgeToWires; //!< CoEdge -> parent Wire indices. + + NCollection_Vector myEdgeFaceCount; //!< Cached face count per edge, O(1) lookup. + int myNbIndexedCoEdges = 0; //!< Number of coedges indexed by Build()/BuildDelta(). +}; + +#endif // _BRepGraphInc_ReverseIndex_HeaderFile diff --git a/src/ModelingData/TKBRep/BRepGraphInc/BRepGraphInc_Storage.cxx b/src/ModelingData/TKBRep/BRepGraphInc/BRepGraphInc_Storage.cxx new file mode 100644 index 0000000000..49c65f1189 --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraphInc/BRepGraphInc_Storage.cxx @@ -0,0 +1,968 @@ +// Copyright (c) 2026 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 + +namespace +{ + +template +bool containsNodeIndex(const NCollection_Vector* theVec, const int theIndex) +{ + if (theVec == nullptr) + { + return false; + } + for (const T& anElem : *theVec) + { + if (anElem.Index == theIndex) + { + return true; + } + } + return false; +} + +} // namespace + +//================================================================================================= + +BRepGraphInc_Storage::BRepGraphInc_Storage(const occ::handle& theAlloc) + : myVertices(256, theAlloc), + myEdges(256, theAlloc), + myCoEdges(256, theAlloc), + myWires(256, theAlloc), + myFaces(256, theAlloc), + myShells(256, theAlloc), + mySolids(256, theAlloc), + myCompounds(256, theAlloc), + myCompSolids(256, theAlloc), + myProducts(256, theAlloc), + myOccurrences(256, theAlloc), + myShellRefs(256, theAlloc), + myFaceRefs(256, theAlloc), + myWireRefs(256, theAlloc), + myCoEdgeRefs(256, theAlloc), + myVertexRefs(256, theAlloc), + mySolidRefs(256, theAlloc), + myChildRefs(256, theAlloc), + myOccurrenceRefs(256, theAlloc), + mySurfaces(256, theAlloc), + myCurves3D(256, theAlloc), + myCurves2D(256, theAlloc), + myTriangulationsRep(256, theAlloc), + myPolygons3D(256, theAlloc), + myPolygons2D(256, theAlloc), + myPolygonsOnTri(256, theAlloc), + myTShapeToNodeId(1, theAlloc), + myOriginalShapes(1, theAlloc), + myAllocator(theAlloc.IsNull() ? NCollection_BaseAllocator::CommonBaseAllocator() : theAlloc) +{ +} + +//================================================================================================= + +const NCollection_Vector& BRepGraphInc_Storage::UIDs( + const BRepGraph_NodeId::Kind theKind) const +{ + switch (theKind) + { + case BRepGraph_NodeId::Kind::Vertex: + return myVertices.UIDs; + case BRepGraph_NodeId::Kind::Edge: + return myEdges.UIDs; + case BRepGraph_NodeId::Kind::CoEdge: + return myCoEdges.UIDs; + case BRepGraph_NodeId::Kind::Wire: + return myWires.UIDs; + case BRepGraph_NodeId::Kind::Face: + return myFaces.UIDs; + case BRepGraph_NodeId::Kind::Shell: + return myShells.UIDs; + case BRepGraph_NodeId::Kind::Solid: + return mySolids.UIDs; + case BRepGraph_NodeId::Kind::Compound: + return myCompounds.UIDs; + case BRepGraph_NodeId::Kind::CompSolid: + return myCompSolids.UIDs; + case BRepGraph_NodeId::Kind::Product: + return myProducts.UIDs; + case BRepGraph_NodeId::Kind::Occurrence: + return myOccurrences.UIDs; + default: + break; + } + Standard_ASSERT_VOID(false, "UIDs: unhandled Kind"); + static const NCollection_Vector THE_EMPTY; + return THE_EMPTY; +} + +//================================================================================================= + +NCollection_Vector& BRepGraphInc_Storage::ChangeUIDs( + const BRepGraph_NodeId::Kind theKind) +{ + switch (theKind) + { + case BRepGraph_NodeId::Kind::Vertex: + return myVertices.UIDs; + case BRepGraph_NodeId::Kind::Edge: + return myEdges.UIDs; + case BRepGraph_NodeId::Kind::CoEdge: + return myCoEdges.UIDs; + case BRepGraph_NodeId::Kind::Wire: + return myWires.UIDs; + case BRepGraph_NodeId::Kind::Face: + return myFaces.UIDs; + case BRepGraph_NodeId::Kind::Shell: + return myShells.UIDs; + case BRepGraph_NodeId::Kind::Solid: + return mySolids.UIDs; + case BRepGraph_NodeId::Kind::Compound: + return myCompounds.UIDs; + case BRepGraph_NodeId::Kind::CompSolid: + return myCompSolids.UIDs; + case BRepGraph_NodeId::Kind::Product: + return myProducts.UIDs; + case BRepGraph_NodeId::Kind::Occurrence: + return myOccurrences.UIDs; + } + Standard_ASSERT_RETURN(false, "ChangeUIDs: invalid Kind value", myVertices.UIDs); + return myVertices.UIDs; +} + +//================================================================================================= + +void BRepGraphInc_Storage::ResetAllUIDs() +{ + myVertices.UIDs.Clear(); + myEdges.UIDs.Clear(); + myCoEdges.UIDs.Clear(); + myWires.UIDs.Clear(); + myFaces.UIDs.Clear(); + myShells.UIDs.Clear(); + mySolids.UIDs.Clear(); + myCompounds.UIDs.Clear(); + myCompSolids.UIDs.Clear(); + myProducts.UIDs.Clear(); + myOccurrences.UIDs.Clear(); +} + +//================================================================================================= + +const NCollection_Vector& BRepGraphInc_Storage::RefUIDs( + const BRepGraph_RefId::Kind theKind) const +{ + switch (theKind) + { + case BRepGraph_RefId::Kind::Shell: + return myShellRefs.UIDs; + case BRepGraph_RefId::Kind::Face: + return myFaceRefs.UIDs; + case BRepGraph_RefId::Kind::Wire: + return myWireRefs.UIDs; + case BRepGraph_RefId::Kind::CoEdge: + return myCoEdgeRefs.UIDs; + case BRepGraph_RefId::Kind::Vertex: + return myVertexRefs.UIDs; + case BRepGraph_RefId::Kind::Solid: + return mySolidRefs.UIDs; + case BRepGraph_RefId::Kind::Child: + return myChildRefs.UIDs; + case BRepGraph_RefId::Kind::Occurrence: + return myOccurrenceRefs.UIDs; + default: + break; + } + Standard_ASSERT_VOID(false, "RefUIDs: unhandled Kind"); + static const NCollection_Vector THE_EMPTY; + return THE_EMPTY; +} + +//================================================================================================= + +NCollection_Vector& BRepGraphInc_Storage::ChangeRefUIDs( + const BRepGraph_RefId::Kind theKind) +{ + switch (theKind) + { + case BRepGraph_RefId::Kind::Shell: + return myShellRefs.UIDs; + case BRepGraph_RefId::Kind::Face: + return myFaceRefs.UIDs; + case BRepGraph_RefId::Kind::Wire: + return myWireRefs.UIDs; + case BRepGraph_RefId::Kind::CoEdge: + return myCoEdgeRefs.UIDs; + case BRepGraph_RefId::Kind::Vertex: + return myVertexRefs.UIDs; + case BRepGraph_RefId::Kind::Solid: + return mySolidRefs.UIDs; + case BRepGraph_RefId::Kind::Child: + return myChildRefs.UIDs; + case BRepGraph_RefId::Kind::Occurrence: + return myOccurrenceRefs.UIDs; + default: + break; + } + Standard_ASSERT_RETURN(false, "ChangeRefUIDs: invalid Kind value", myShellRefs.UIDs); + return myShellRefs.UIDs; +} + +//================================================================================================= + +void BRepGraphInc_Storage::ResetAllRefUIDs() +{ + myShellRefs.UIDs.Clear(); + myFaceRefs.UIDs.Clear(); + myWireRefs.UIDs.Clear(); + myCoEdgeRefs.UIDs.Clear(); + myVertexRefs.UIDs.Clear(); + mySolidRefs.UIDs.Clear(); + myChildRefs.UIDs.Clear(); + myOccurrenceRefs.UIDs.Clear(); +} + +//================================================================================================= + +const BRepGraphInc::BaseRef& BRepGraphInc_Storage::BaseRef(const BRepGraph_RefId theRefId) const +{ + switch (theRefId.RefKind) + { + case BRepGraph_RefId::Kind::Shell: + return myShellRefs.Get(theRefId.Index); + case BRepGraph_RefId::Kind::Face: + return myFaceRefs.Get(theRefId.Index); + case BRepGraph_RefId::Kind::Wire: + return myWireRefs.Get(theRefId.Index); + case BRepGraph_RefId::Kind::CoEdge: + return myCoEdgeRefs.Get(theRefId.Index); + case BRepGraph_RefId::Kind::Vertex: + return myVertexRefs.Get(theRefId.Index); + case BRepGraph_RefId::Kind::Solid: + return mySolidRefs.Get(theRefId.Index); + case BRepGraph_RefId::Kind::Child: + return myChildRefs.Get(theRefId.Index); + case BRepGraph_RefId::Kind::Occurrence: + return myOccurrenceRefs.Get(theRefId.Index); + } + static const BRepGraphInc::BaseRef anInvalid; + return anInvalid; +} + +//================================================================================================= + +BRepGraphInc::BaseRef& BRepGraphInc_Storage::ChangeBaseRef(const BRepGraph_RefId theRefId) +{ + switch (theRefId.RefKind) + { + case BRepGraph_RefId::Kind::Shell: + return myShellRefs.Change(theRefId.Index); + case BRepGraph_RefId::Kind::Face: + return myFaceRefs.Change(theRefId.Index); + case BRepGraph_RefId::Kind::Wire: + return myWireRefs.Change(theRefId.Index); + case BRepGraph_RefId::Kind::CoEdge: + return myCoEdgeRefs.Change(theRefId.Index); + case BRepGraph_RefId::Kind::Vertex: + return myVertexRefs.Change(theRefId.Index); + case BRepGraph_RefId::Kind::Solid: + return mySolidRefs.Change(theRefId.Index); + case BRepGraph_RefId::Kind::Child: + return myChildRefs.Change(theRefId.Index); + case BRepGraph_RefId::Kind::Occurrence: + return myOccurrenceRefs.Change(theRefId.Index); + } + static BRepGraphInc::BaseRef anInvalid; + return anInvalid; +} + +//================================================================================================= + +void BRepGraphInc_Storage::Clear() +{ + myVertices.Clear(); + myEdges.Clear(); + myCoEdges.Clear(); + myWires.Clear(); + myFaces.Clear(); + myShells.Clear(); + mySolids.Clear(); + myCompounds.Clear(); + myCompSolids.Clear(); + myProducts.Clear(); + myOccurrences.Clear(); + myShellRefs.Clear(); + myFaceRefs.Clear(); + myWireRefs.Clear(); + myCoEdgeRefs.Clear(); + myVertexRefs.Clear(); + mySolidRefs.Clear(); + myChildRefs.Clear(); + myOccurrenceRefs.Clear(); + mySurfaces.Clear(); + myCurves3D.Clear(); + myCurves2D.Clear(); + myTriangulationsRep.Clear(); + myPolygons3D.Clear(); + myPolygons2D.Clear(); + myPolygonsOnTri.Clear(); + myReverseIdx.Clear(); + myTShapeToNodeId.Clear(); + myOriginalShapes.Clear(); + myIsDone = false; +} + +//================================================================================================= + +void BRepGraphInc_Storage::DecrementActiveCount(const BRepGraph_NodeId::Kind theKind) +{ + switch (theKind) + { + case BRepGraph_NodeId::Kind::Vertex: + myVertices.DecrementActive(); + break; + case BRepGraph_NodeId::Kind::Edge: + myEdges.DecrementActive(); + break; + case BRepGraph_NodeId::Kind::CoEdge: + myCoEdges.DecrementActive(); + break; + case BRepGraph_NodeId::Kind::Wire: + myWires.DecrementActive(); + break; + case BRepGraph_NodeId::Kind::Face: + myFaces.DecrementActive(); + break; + case BRepGraph_NodeId::Kind::Shell: + myShells.DecrementActive(); + break; + case BRepGraph_NodeId::Kind::Solid: + mySolids.DecrementActive(); + break; + case BRepGraph_NodeId::Kind::Compound: + myCompounds.DecrementActive(); + break; + case BRepGraph_NodeId::Kind::CompSolid: + myCompSolids.DecrementActive(); + break; + case BRepGraph_NodeId::Kind::Product: + myProducts.DecrementActive(); + break; + case BRepGraph_NodeId::Kind::Occurrence: + myOccurrences.DecrementActive(); + break; + default: + Standard_ASSERT_VOID(false, "DecrementActiveCount: unhandled Kind"); + break; + } +} + +//================================================================================================= + +bool BRepGraphInc_Storage::MarkRemoved(const BRepGraph_NodeId theNodeId) +{ + if (!theNodeId.IsValid()) + return false; + + BRepGraphInc::BaseDef* anEnt = nullptr; + switch (theNodeId.NodeKind) + { + case BRepGraph_NodeId::Kind::Vertex: + if (theNodeId.Index >= 0 && theNodeId.Index < myVertices.Nb()) + anEnt = &myVertices.Change(theNodeId.Index); + break; + case BRepGraph_NodeId::Kind::Edge: + if (theNodeId.Index >= 0 && theNodeId.Index < myEdges.Nb()) + anEnt = &myEdges.Change(theNodeId.Index); + break; + case BRepGraph_NodeId::Kind::CoEdge: + if (theNodeId.Index >= 0 && theNodeId.Index < myCoEdges.Nb()) + anEnt = &myCoEdges.Change(theNodeId.Index); + break; + case BRepGraph_NodeId::Kind::Wire: + if (theNodeId.Index >= 0 && theNodeId.Index < myWires.Nb()) + anEnt = &myWires.Change(theNodeId.Index); + break; + case BRepGraph_NodeId::Kind::Face: + if (theNodeId.Index >= 0 && theNodeId.Index < myFaces.Nb()) + anEnt = &myFaces.Change(theNodeId.Index); + break; + case BRepGraph_NodeId::Kind::Shell: + if (theNodeId.Index >= 0 && theNodeId.Index < myShells.Nb()) + anEnt = &myShells.Change(theNodeId.Index); + break; + case BRepGraph_NodeId::Kind::Solid: + if (theNodeId.Index >= 0 && theNodeId.Index < mySolids.Nb()) + anEnt = &mySolids.Change(theNodeId.Index); + break; + case BRepGraph_NodeId::Kind::Compound: + if (theNodeId.Index >= 0 && theNodeId.Index < myCompounds.Nb()) + anEnt = &myCompounds.Change(theNodeId.Index); + break; + case BRepGraph_NodeId::Kind::CompSolid: + if (theNodeId.Index >= 0 && theNodeId.Index < myCompSolids.Nb()) + anEnt = &myCompSolids.Change(theNodeId.Index); + break; + case BRepGraph_NodeId::Kind::Product: + if (theNodeId.Index >= 0 && theNodeId.Index < myProducts.Nb()) + anEnt = &myProducts.Change(theNodeId.Index); + break; + case BRepGraph_NodeId::Kind::Occurrence: + if (theNodeId.Index >= 0 && theNodeId.Index < myOccurrences.Nb()) + anEnt = &myOccurrences.Change(theNodeId.Index); + break; + default: + return false; + } + + if (anEnt == nullptr || anEnt->IsRemoved) + return false; + + anEnt->IsRemoved = true; + DecrementActiveCount(theNodeId.NodeKind); + return true; +} + +//================================================================================================= + +bool BRepGraphInc_Storage::MarkRemovedRef(const BRepGraph_RefId theRefId) +{ + if (!theRefId.IsValid()) + return false; + + BRepGraphInc::BaseRef* aRef = nullptr; + switch (theRefId.RefKind) + { + case BRepGraph_RefId::Kind::Shell: + if (theRefId.Index >= 0 && theRefId.Index < myShellRefs.Nb()) + aRef = &myShellRefs.Change(theRefId.Index); + break; + case BRepGraph_RefId::Kind::Face: + if (theRefId.Index >= 0 && theRefId.Index < myFaceRefs.Nb()) + aRef = &myFaceRefs.Change(theRefId.Index); + break; + case BRepGraph_RefId::Kind::Wire: + if (theRefId.Index >= 0 && theRefId.Index < myWireRefs.Nb()) + aRef = &myWireRefs.Change(theRefId.Index); + break; + case BRepGraph_RefId::Kind::CoEdge: + if (theRefId.Index >= 0 && theRefId.Index < myCoEdgeRefs.Nb()) + aRef = &myCoEdgeRefs.Change(theRefId.Index); + break; + case BRepGraph_RefId::Kind::Vertex: + if (theRefId.Index >= 0 && theRefId.Index < myVertexRefs.Nb()) + aRef = &myVertexRefs.Change(theRefId.Index); + break; + case BRepGraph_RefId::Kind::Solid: + if (theRefId.Index >= 0 && theRefId.Index < mySolidRefs.Nb()) + aRef = &mySolidRefs.Change(theRefId.Index); + break; + case BRepGraph_RefId::Kind::Child: + if (theRefId.Index >= 0 && theRefId.Index < myChildRefs.Nb()) + aRef = &myChildRefs.Change(theRefId.Index); + break; + case BRepGraph_RefId::Kind::Occurrence: + if (theRefId.Index >= 0 && theRefId.Index < myOccurrenceRefs.Nb()) + aRef = &myOccurrenceRefs.Change(theRefId.Index); + break; + default: + return false; + } + + if (aRef == nullptr || aRef->IsRemoved) + return false; + + aRef->IsRemoved = true; + switch (theRefId.RefKind) + { + case BRepGraph_RefId::Kind::Shell: + myShellRefs.DecrementActive(); + break; + case BRepGraph_RefId::Kind::Face: + myFaceRefs.DecrementActive(); + break; + case BRepGraph_RefId::Kind::Wire: + myWireRefs.DecrementActive(); + break; + case BRepGraph_RefId::Kind::CoEdge: + myCoEdgeRefs.DecrementActive(); + break; + case BRepGraph_RefId::Kind::Vertex: + myVertexRefs.DecrementActive(); + break; + case BRepGraph_RefId::Kind::Solid: + mySolidRefs.DecrementActive(); + break; + case BRepGraph_RefId::Kind::Child: + myChildRefs.DecrementActive(); + break; + case BRepGraph_RefId::Kind::Occurrence: + myOccurrenceRefs.DecrementActive(); + break; + default: + return false; + } + + return true; +} + +//================================================================================================= + +bool BRepGraphInc_Storage::MarkRemovedRep(const BRepGraph_RepId theRepId) +{ + if (!theRepId.IsValid()) + return false; + + BRepGraphInc::BaseRep* aRep = nullptr; + switch (theRepId.RepKind) + { + case BRepGraph_RepId::Kind::Surface: + if (theRepId.Index >= 0 && theRepId.Index < mySurfaces.Nb()) + aRep = &mySurfaces.Change(theRepId.Index); + break; + case BRepGraph_RepId::Kind::Curve3D: + if (theRepId.Index >= 0 && theRepId.Index < myCurves3D.Nb()) + aRep = &myCurves3D.Change(theRepId.Index); + break; + case BRepGraph_RepId::Kind::Curve2D: + if (theRepId.Index >= 0 && theRepId.Index < myCurves2D.Nb()) + aRep = &myCurves2D.Change(theRepId.Index); + break; + case BRepGraph_RepId::Kind::Triangulation: + if (theRepId.Index >= 0 && theRepId.Index < myTriangulationsRep.Nb()) + aRep = &myTriangulationsRep.Change(theRepId.Index); + break; + case BRepGraph_RepId::Kind::Polygon3D: + if (theRepId.Index >= 0 && theRepId.Index < myPolygons3D.Nb()) + aRep = &myPolygons3D.Change(theRepId.Index); + break; + case BRepGraph_RepId::Kind::Polygon2D: + if (theRepId.Index >= 0 && theRepId.Index < myPolygons2D.Nb()) + aRep = &myPolygons2D.Change(theRepId.Index); + break; + case BRepGraph_RepId::Kind::PolygonOnTri: + if (theRepId.Index >= 0 && theRepId.Index < myPolygonsOnTri.Nb()) + aRep = &myPolygonsOnTri.Change(theRepId.Index); + break; + default: + return false; + } + + if (aRep == nullptr || aRep->IsRemoved) + return false; + + aRep->IsRemoved = true; + switch (theRepId.RepKind) + { + case BRepGraph_RepId::Kind::Surface: + mySurfaces.DecrementActive(); + break; + case BRepGraph_RepId::Kind::Curve3D: + myCurves3D.DecrementActive(); + break; + case BRepGraph_RepId::Kind::Curve2D: + myCurves2D.DecrementActive(); + break; + case BRepGraph_RepId::Kind::Triangulation: + myTriangulationsRep.DecrementActive(); + break; + case BRepGraph_RepId::Kind::Polygon3D: + myPolygons3D.DecrementActive(); + break; + case BRepGraph_RepId::Kind::Polygon2D: + myPolygons2D.DecrementActive(); + break; + case BRepGraph_RepId::Kind::PolygonOnTri: + myPolygonsOnTri.DecrementActive(); + break; + default: + return false; + } + + return true; +} + +//================================================================================================= + +void BRepGraphInc_Storage::BuildReverseIndex() +{ + myReverseIdx.SetAllocator(myAllocator); + myReverseIdx.Build(myEdges.Entities, + myCoEdges.Entities, + myWires.Entities, + myFaces.Entities, + myShells.Entities, + mySolids.Entities, + myCompounds.Entities, + myCompSolids.Entities, + myShellRefs.Refs, + myFaceRefs.Refs, + myWireRefs.Refs, + myCoEdgeRefs.Refs, + mySolidRefs.Refs, + myChildRefs.Refs, + myVertexRefs.Refs); + myReverseIdx.BuildProductOccurrences(myOccurrences.Entities, myProducts.Nb()); + + // Recount active entities to sync counters after Build. + // Populate may have set IsRemoved on some entities without going through RemoveNode. + myVertices.NbActive = 0; + myEdges.NbActive = 0; + myCoEdges.NbActive = 0; + myWires.NbActive = 0; + myFaces.NbActive = 0; + myShells.NbActive = 0; + mySolids.NbActive = 0; + myCompounds.NbActive = 0; + myCompSolids.NbActive = 0; + myProducts.NbActive = 0; + myOccurrences.NbActive = 0; + mySurfaces.NbActive = 0; + myCurves3D.NbActive = 0; + myCurves2D.NbActive = 0; + myTriangulationsRep.NbActive = 0; + myPolygons3D.NbActive = 0; + myPolygons2D.NbActive = 0; + myPolygonsOnTri.NbActive = 0; + const int aNbVertices = myVertices.Nb(); + for (BRepGraph_VertexId aVertexId(0); aVertexId.IsValid(aNbVertices); ++aVertexId) + if (!myVertices.Get(aVertexId.Index).IsRemoved) + ++myVertices.NbActive; + const int aNbEdges = myEdges.Nb(); + for (BRepGraph_EdgeId anEdgeId(0); anEdgeId.IsValid(aNbEdges); ++anEdgeId) + if (!myEdges.Get(anEdgeId.Index).IsRemoved) + ++myEdges.NbActive; + const int aNbCoEdges = myCoEdges.Nb(); + for (BRepGraph_CoEdgeId aCoEdgeId(0); aCoEdgeId.IsValid(aNbCoEdges); ++aCoEdgeId) + if (!myCoEdges.Get(aCoEdgeId.Index).IsRemoved) + ++myCoEdges.NbActive; + const int aNbWires = myWires.Nb(); + for (BRepGraph_WireId aWireId(0); aWireId.IsValid(aNbWires); ++aWireId) + if (!myWires.Get(aWireId.Index).IsRemoved) + ++myWires.NbActive; + const int aNbFaces = myFaces.Nb(); + for (BRepGraph_FaceId aFaceId(0); aFaceId.IsValid(aNbFaces); ++aFaceId) + if (!myFaces.Get(aFaceId.Index).IsRemoved) + ++myFaces.NbActive; + const int aNbShells = myShells.Nb(); + for (BRepGraph_ShellId aShellId(0); aShellId.IsValid(aNbShells); ++aShellId) + if (!myShells.Get(aShellId.Index).IsRemoved) + ++myShells.NbActive; + const int aNbSolids = mySolids.Nb(); + for (BRepGraph_SolidId aSolidId(0); aSolidId.IsValid(aNbSolids); ++aSolidId) + if (!mySolids.Get(aSolidId.Index).IsRemoved) + ++mySolids.NbActive; + const int aNbCompounds = myCompounds.Nb(); + for (BRepGraph_CompoundId aCompoundId(0); aCompoundId.IsValid(aNbCompounds); ++aCompoundId) + if (!myCompounds.Get(aCompoundId.Index).IsRemoved) + ++myCompounds.NbActive; + const int aNbCompSolids = myCompSolids.Nb(); + for (BRepGraph_CompSolidId aCompSolidId(0); aCompSolidId.IsValid(aNbCompSolids); ++aCompSolidId) + if (!myCompSolids.Get(aCompSolidId.Index).IsRemoved) + ++myCompSolids.NbActive; + const int aNbProducts = myProducts.Nb(); + for (BRepGraph_ProductId aProductId(0); aProductId.IsValid(aNbProducts); ++aProductId) + if (!myProducts.Get(aProductId.Index).IsRemoved) + ++myProducts.NbActive; + const int aNbOccurrences = myOccurrences.Nb(); + for (BRepGraph_OccurrenceId anOccurrenceId(0); anOccurrenceId.IsValid(aNbOccurrences); + ++anOccurrenceId) + if (!myOccurrences.Get(anOccurrenceId.Index).IsRemoved) + ++myOccurrences.NbActive; + const int aNbSurfaces = mySurfaces.Nb(); + for (BRepGraph_SurfaceRepId aSurfaceRepId(0); aSurfaceRepId.IsValid(aNbSurfaces); ++aSurfaceRepId) + if (!mySurfaces.Get(aSurfaceRepId.Index).IsRemoved) + ++mySurfaces.NbActive; + const int aNbCurves3D = myCurves3D.Nb(); + for (BRepGraph_Curve3DRepId aCurve3DRepId(0); aCurve3DRepId.IsValid(aNbCurves3D); ++aCurve3DRepId) + if (!myCurves3D.Get(aCurve3DRepId.Index).IsRemoved) + ++myCurves3D.NbActive; + const int aNbCurves2D = myCurves2D.Nb(); + for (BRepGraph_Curve2DRepId aCurve2DRepId(0); aCurve2DRepId.IsValid(aNbCurves2D); ++aCurve2DRepId) + if (!myCurves2D.Get(aCurve2DRepId.Index).IsRemoved) + ++myCurves2D.NbActive; + const int aNbTriangulations = myTriangulationsRep.Nb(); + for (BRepGraph_TriangulationRepId aTriangulationRepId(0); + aTriangulationRepId.IsValid(aNbTriangulations); + ++aTriangulationRepId) + if (!myTriangulationsRep.Get(aTriangulationRepId.Index).IsRemoved) + ++myTriangulationsRep.NbActive; + const int aNbPolygons3D = myPolygons3D.Nb(); + for (BRepGraph_Polygon3DRepId aPolygon3DRepId(0); aPolygon3DRepId.IsValid(aNbPolygons3D); + ++aPolygon3DRepId) + if (!myPolygons3D.Get(aPolygon3DRepId.Index).IsRemoved) + ++myPolygons3D.NbActive; + const int aNbPolygons2D = myPolygons2D.Nb(); + for (BRepGraph_Polygon2DRepId aPolygon2DRepId(0); aPolygon2DRepId.IsValid(aNbPolygons2D); + ++aPolygon2DRepId) + if (!myPolygons2D.Get(aPolygon2DRepId.Index).IsRemoved) + ++myPolygons2D.NbActive; + const int aNbPolygonsOnTri = myPolygonsOnTri.Nb(); + for (BRepGraph_PolygonOnTriRepId aPolygonOnTriRepId(0); + aPolygonOnTriRepId.IsValid(aNbPolygonsOnTri); + ++aPolygonOnTriRepId) + if (!myPolygonsOnTri.Get(aPolygonOnTriRepId.Index).IsRemoved) + ++myPolygonsOnTri.NbActive; +} + +//================================================================================================= + +void BRepGraphInc_Storage::BuildDeltaReverseIndex(const int theOldNbEdges, + const int theOldNbWires, + const int theOldNbFaces, + const int theOldNbShells, + const int theOldNbSolids) +{ + myReverseIdx.BuildDelta(myEdges.Entities, + myCoEdges.Entities, + myWires.Entities, + myFaces.Entities, + myShells.Entities, + mySolids.Entities, + myShellRefs.Refs, + myFaceRefs.Refs, + myWireRefs.Refs, + myCoEdgeRefs.Refs, + myVertexRefs.Refs, + theOldNbEdges, + theOldNbWires, + theOldNbFaces, + theOldNbShells, + theOldNbSolids); +} + +//================================================================================================= + +bool BRepGraphInc_Storage::ValidateReverseIndex() const +{ + if (!myReverseIdx.Validate(myEdges.Entities, + myCoEdges.Entities, + myWires.Entities, + myFaces.Entities, + myShells.Entities, + mySolids.Entities, + myShellRefs.Refs, + myFaceRefs.Refs, + myWireRefs.Refs, + myCoEdgeRefs.Refs, + myVertexRefs.Refs)) + { + return false; + } + + // Wire -> CoEdge and Edge -> CoEdge coherence via coedge ref entries. + const int aNbCoEdgeRefs = myCoEdgeRefs.Nb(); + for (BRepGraph_CoEdgeRefId aCoEdgeRefId(0); aCoEdgeRefId.IsValid(aNbCoEdgeRefs); ++aCoEdgeRefId) + { + const BRepGraphInc::CoEdgeRef& aRef = myCoEdgeRefs.Get(aCoEdgeRefId.Index); + if (aRef.IsRemoved || !aRef.ParentId.IsValid() + || aRef.ParentId.NodeKind != BRepGraph_NodeId::Kind::Wire || !aRef.CoEdgeDefId.IsValid()) + { + continue; + } + if (aRef.ParentId.Index < 0 || aRef.ParentId.Index >= myWires.Nb() || aRef.CoEdgeDefId.Index < 0 + || aRef.CoEdgeDefId.Index >= myCoEdges.Nb()) + { + return false; + } + + const BRepGraphInc::WireDef& aWire = myWires.Get(aRef.ParentId.Index); + const BRepGraphInc::CoEdgeDef& aCoEdge = myCoEdges.Get(aRef.CoEdgeDefId.Index); + if (aWire.IsRemoved || aCoEdge.IsRemoved) + { + continue; + } + if (!containsNodeIndex(myReverseIdx.WiresOfCoEdge(aRef.CoEdgeDefId), aRef.ParentId.Index)) + { + return false; + } + if (aCoEdge.EdgeDefId.IsValid() + && !containsNodeIndex(myReverseIdx.CoEdgesOfEdge(aCoEdge.EdgeDefId), + aRef.CoEdgeDefId.Index)) + { + return false; + } + } + + // Reverse Edge -> CoEdges entries must point back to active CoEdges. + const int aNbEdges = myEdges.Nb(); + for (BRepGraph_EdgeId anEdgeId(0); anEdgeId.IsValid(aNbEdges); ++anEdgeId) + { + const NCollection_Vector* aCoEdges = myReverseIdx.CoEdgesOfEdge(anEdgeId); + if (aCoEdges == nullptr) + { + continue; + } + for (const BRepGraph_CoEdgeId& aCoEdgeElem : *aCoEdges) + { + const int aRefIdx = aCoEdgeElem.Index; + if (aRefIdx < 0 || aRefIdx >= myCoEdges.Nb()) + { + return false; + } + const BRepGraphInc::CoEdgeDef& aCoEdge = myCoEdges.Get(aRefIdx); + if (aCoEdge.IsRemoved || !aCoEdge.EdgeDefId.IsValid() + || aCoEdge.EdgeDefId.Index != anEdgeId.Index) + { + return false; + } + } + } + + // Compound child reverse maps via child ref entries. + const int aNbChildRefs = myChildRefs.Nb(); + for (BRepGraph_ChildRefId aChildRefId(0); aChildRefId.IsValid(aNbChildRefs); ++aChildRefId) + { + const BRepGraphInc::ChildRef& aRef = myChildRefs.Get(aChildRefId.Index); + if (aRef.IsRemoved || !aRef.ParentId.IsValid() + || aRef.ParentId.NodeKind != BRepGraph_NodeId::Kind::Compound || !aRef.ChildDefId.IsValid()) + { + continue; + } + if (aRef.ParentId.Index < 0 || aRef.ParentId.Index >= myCompounds.Nb()) + { + return false; + } + + const BRepGraphInc::CompoundDef& aCompound = myCompounds.Get(aRef.ParentId.Index); + if (aCompound.IsRemoved) + { + continue; + } + + switch (aRef.ChildDefId.NodeKind) + { + case BRepGraph_NodeId::Kind::Solid: + if (!containsNodeIndex( + myReverseIdx.CompoundsOfSolid(BRepGraph_SolidId(aRef.ChildDefId.Index)), + aRef.ParentId.Index)) + { + return false; + } + break; + case BRepGraph_NodeId::Kind::Shell: + if (!containsNodeIndex( + myReverseIdx.CompoundsOfShell(BRepGraph_ShellId(aRef.ChildDefId.Index)), + aRef.ParentId.Index)) + { + return false; + } + break; + case BRepGraph_NodeId::Kind::Face: + if (!containsNodeIndex( + myReverseIdx.CompoundsOfFace(BRepGraph_FaceId(aRef.ChildDefId.Index)), + aRef.ParentId.Index)) + { + return false; + } + break; + case BRepGraph_NodeId::Kind::Compound: + if (!containsNodeIndex( + myReverseIdx.CompoundsOfCompound(BRepGraph_CompoundId(aRef.ChildDefId.Index)), + aRef.ParentId.Index)) + { + return false; + } + break; + case BRepGraph_NodeId::Kind::CompSolid: + if (!containsNodeIndex( + myReverseIdx.CompoundsOfCompSolid(BRepGraph_CompSolidId(aRef.ChildDefId.Index)), + aRef.ParentId.Index)) + { + return false; + } + break; + default: + break; + } + } + + // CompSolid -> Solid reverse map via solid ref entries. + const int aNbSolidRefs = mySolidRefs.Nb(); + for (BRepGraph_SolidRefId aSolidRefId(0); aSolidRefId.IsValid(aNbSolidRefs); ++aSolidRefId) + { + const BRepGraphInc::SolidRef& aRef = mySolidRefs.Get(aSolidRefId.Index); + if (aRef.IsRemoved || !aRef.ParentId.IsValid() + || aRef.ParentId.NodeKind != BRepGraph_NodeId::Kind::CompSolid + || !aRef.SolidDefId.IsValid()) + { + continue; + } + if (aRef.ParentId.Index < 0 || aRef.ParentId.Index >= myCompSolids.Nb() + || aRef.SolidDefId.Index < 0 || aRef.SolidDefId.Index >= mySolids.Nb()) + { + return false; + } + + const BRepGraphInc::CompSolidDef& aCompSolid = myCompSolids.Get(aRef.ParentId.Index); + if (aCompSolid.IsRemoved || mySolids.Get(aRef.SolidDefId.Index).IsRemoved) + { + continue; + } + if (!containsNodeIndex(myReverseIdx.CompSolidsOfSolid(aRef.SolidDefId), aRef.ParentId.Index)) + { + return false; + } + } + + // Occurrence -> Product reverse map. + const int aNbOccurrences = myOccurrences.Nb(); + for (BRepGraph_OccurrenceId anOccurrenceId(0); anOccurrenceId.IsValid(aNbOccurrences); + ++anOccurrenceId) + { + const BRepGraphInc::OccurrenceDef& anOcc = myOccurrences.Get(anOccurrenceId.Index); + if (anOcc.IsRemoved) + { + continue; + } + if (!anOcc.ProductDefId.IsValid() || anOcc.ProductDefId.Index >= myProducts.Nb()) + { + return false; + } + if (!containsNodeIndex(myReverseIdx.OccurrencesOfProduct(anOcc.ProductDefId), + anOccurrenceId.Index)) + { + return false; + } + } + + // Reverse Product -> Occurrences entries must point to active occurrences + // that reference the same product. + const int aNbProducts = myProducts.Nb(); + for (BRepGraph_ProductId aProductId(0); aProductId.IsValid(aNbProducts); ++aProductId) + { + const NCollection_Vector* anOccs = + myReverseIdx.OccurrencesOfProduct(aProductId); + if (anOccs == nullptr) + { + continue; + } + for (const BRepGraph_OccurrenceId& anOccElem : *anOccs) + { + const int anOccIdx = anOccElem.Index; + if (anOccIdx < 0 || anOccIdx >= myOccurrences.Nb()) + { + return false; + } + const BRepGraphInc::OccurrenceDef& anOcc = myOccurrences.Get(anOccIdx); + if (anOcc.IsRemoved || !anOcc.ProductDefId.IsValid() + || anOcc.ProductDefId.Index != aProductId.Index) + { + return false; + } + } + } + + return true; +} diff --git a/src/ModelingData/TKBRep/BRepGraphInc/BRepGraphInc_Storage.hxx b/src/ModelingData/TKBRep/BRepGraphInc/BRepGraphInc_Storage.hxx new file mode 100644 index 0000000000..b4a0d28e15 --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraphInc/BRepGraphInc_Storage.hxx @@ -0,0 +1,875 @@ +// Copyright (c) 2026 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 _BRepGraphInc_Storage_HeaderFile +#define _BRepGraphInc_Storage_HeaderFile + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +//! @brief Central backend storage container for the incidence-table topology model. +//! +//! Holds all entity vectors (Vertex through Occurrence), representation +//! vectors (Surface, Curve3D, Curve2D, Triangulation, Polygon), reverse +//! indices for O(1) upward navigation, TShape deduplication maps, original +//! shape bindings, and per-kind UID vectors. Provides typed accessors +//! enforcing compile-time safety for backend code. External callers should +//! normally use the BRepGraph facade rather than reaching into this storage +//! directly. BRepGraphInc_Populate has friend access for efficient bulk writes +//! during graph population. +class BRepGraph_Builder; + +class BRepGraphInc_Storage +{ +public: + DEFINE_STANDARD_ALLOC + + //! Construct with allocator for internal collections. + //! If null, uses CommonBaseAllocator. + Standard_EXPORT explicit BRepGraphInc_Storage( + const occ::handle& theAlloc = + occ::handle()); + + //! Return the allocator used for internal collections. + [[nodiscard]] const occ::handle& Allocator() const + { + return myAllocator; + } + + //! @name Count accessors (total including removed) + + [[nodiscard]] int NbVertices() const { return myVertices.Nb(); } + + [[nodiscard]] int NbEdges() const { return myEdges.Nb(); } + + [[nodiscard]] int NbCoEdges() const { return myCoEdges.Nb(); } + + [[nodiscard]] int NbWires() const { return myWires.Nb(); } + + [[nodiscard]] int NbFaces() const { return myFaces.Nb(); } + + [[nodiscard]] int NbShells() const { return myShells.Nb(); } + + [[nodiscard]] int NbSolids() const { return mySolids.Nb(); } + + [[nodiscard]] int NbCompounds() const { return myCompounds.Nb(); } + + [[nodiscard]] int NbCompSolids() const { return myCompSolids.Nb(); } + + [[nodiscard]] int NbProducts() const { return myProducts.Nb(); } + + [[nodiscard]] int NbOccurrences() const { return myOccurrences.Nb(); } + + //! @name Transitional reference count accessors + + [[nodiscard]] int NbShellRefs() const { return myShellRefs.Nb(); } + + [[nodiscard]] int NbFaceRefs() const { return myFaceRefs.Nb(); } + + [[nodiscard]] int NbWireRefs() const { return myWireRefs.Nb(); } + + [[nodiscard]] int NbCoEdgeRefs() const { return myCoEdgeRefs.Nb(); } + + [[nodiscard]] int NbVertexRefs() const { return myVertexRefs.Nb(); } + + [[nodiscard]] int NbSolidRefs() const { return mySolidRefs.Nb(); } + + [[nodiscard]] int NbChildRefs() const { return myChildRefs.Nb(); } + + [[nodiscard]] int NbOccurrenceRefs() const { return myOccurrenceRefs.Nb(); } + + //! @name Representation count accessors + + [[nodiscard]] int NbSurfaces() const { return mySurfaces.Nb(); } + + [[nodiscard]] int NbCurves3D() const { return myCurves3D.Nb(); } + + [[nodiscard]] int NbCurves2D() const { return myCurves2D.Nb(); } + + [[nodiscard]] int NbTriangulations() const { return myTriangulationsRep.Nb(); } + + [[nodiscard]] int NbPolygons3D() const { return myPolygons3D.Nb(); } + + [[nodiscard]] int NbPolygons2D() const { return myPolygons2D.Nb(); } + + [[nodiscard]] int NbPolygonsOnTri() const { return myPolygonsOnTri.Nb(); } + + //! @name Representation active count accessors + + [[nodiscard]] int NbActiveSurfaces() const { return mySurfaces.NbActive; } + + [[nodiscard]] int NbActiveCurves3D() const { return myCurves3D.NbActive; } + + [[nodiscard]] int NbActiveCurves2D() const { return myCurves2D.NbActive; } + + [[nodiscard]] int NbActiveTriangulations() const { return myTriangulationsRep.NbActive; } + + [[nodiscard]] int NbActivePolygons3D() const { return myPolygons3D.NbActive; } + + [[nodiscard]] int NbActivePolygons2D() const { return myPolygons2D.NbActive; } + + [[nodiscard]] int NbActivePolygonsOnTri() const { return myPolygonsOnTri.NbActive; } + + //! @name Active count accessors (excluding removed nodes) + + [[nodiscard]] int NbActiveVertices() const { return myVertices.NbActive; } + + [[nodiscard]] int NbActiveEdges() const { return myEdges.NbActive; } + + [[nodiscard]] int NbActiveCoEdges() const { return myCoEdges.NbActive; } + + [[nodiscard]] int NbActiveWires() const { return myWires.NbActive; } + + [[nodiscard]] int NbActiveFaces() const { return myFaces.NbActive; } + + [[nodiscard]] int NbActiveShells() const { return myShells.NbActive; } + + [[nodiscard]] int NbActiveSolids() const { return mySolids.NbActive; } + + [[nodiscard]] int NbActiveCompounds() const { return myCompounds.NbActive; } + + [[nodiscard]] int NbActiveCompSolids() const { return myCompSolids.NbActive; } + + [[nodiscard]] int NbActiveProducts() const { return myProducts.NbActive; } + + [[nodiscard]] int NbActiveOccurrences() const { return myOccurrences.NbActive; } + + //! @name Transitional reference active count accessors + + [[nodiscard]] int NbActiveShellRefs() const { return myShellRefs.NbActive; } + + [[nodiscard]] int NbActiveFaceRefs() const { return myFaceRefs.NbActive; } + + [[nodiscard]] int NbActiveWireRefs() const { return myWireRefs.NbActive; } + + [[nodiscard]] int NbActiveCoEdgeRefs() const { return myCoEdgeRefs.NbActive; } + + [[nodiscard]] int NbActiveVertexRefs() const { return myVertexRefs.NbActive; } + + [[nodiscard]] int NbActiveSolidRefs() const { return mySolidRefs.NbActive; } + + [[nodiscard]] int NbActiveChildRefs() const { return myChildRefs.NbActive; } + + [[nodiscard]] int NbActiveOccurrenceRefs() const { return myOccurrenceRefs.NbActive; } + + //! Decrement the active count for the given node kind. + void DecrementActiveCount(const BRepGraph_NodeId::Kind theKind); + + //! Mark an entity node as removed and decrement its active counter once. + //! @param[in] theNodeId typed entity id + //! @return true if the node transitioned from active to removed + Standard_EXPORT bool MarkRemoved(const BRepGraph_NodeId theNodeId); + + //! Mark a reference entry as removed and decrement its active counter once. + //! @param[in] theRefId typed reference id + //! @return true if the ref transitioned from active to removed + Standard_EXPORT bool MarkRemovedRef(const BRepGraph_RefId theRefId); + + //! Mark a representation entry as removed and decrement its active counter once. + //! @param[in] theRepId typed representation id + //! @return true if the representation transitioned from active to removed + Standard_EXPORT bool MarkRemovedRep(const BRepGraph_RepId theRepId); + + //! @name Const representation access + //! Each method returns a const reference to the representation entity at the given typed id. + + //! @param[in] theRep typed surface representation id + [[nodiscard]] const BRepGraphInc::SurfaceRep& SurfaceRep( + const BRepGraph_SurfaceRepId theRep) const + { + return mySurfaces.Get(theRep.Index); + } + + //! @param[in] theRep typed curve-3D representation id + [[nodiscard]] const BRepGraphInc::Curve3DRep& Curve3DRep( + const BRepGraph_Curve3DRepId theRep) const + { + return myCurves3D.Get(theRep.Index); + } + + //! @param[in] theRep typed curve-2D representation id + [[nodiscard]] const BRepGraphInc::Curve2DRep& Curve2DRep( + const BRepGraph_Curve2DRepId theRep) const + { + return myCurves2D.Get(theRep.Index); + } + + //! @param[in] theRep typed triangulation representation id + [[nodiscard]] const BRepGraphInc::TriangulationRep& TriangulationRep( + const BRepGraph_TriangulationRepId theRep) const + { + return myTriangulationsRep.Get(theRep.Index); + } + + //! @param[in] theRep typed polygon-3D representation id + [[nodiscard]] const BRepGraphInc::Polygon3DRep& Polygon3DRep( + const BRepGraph_Polygon3DRepId theRep) const + { + return myPolygons3D.Get(theRep.Index); + } + + //! @param[in] theRep typed polygon-2D representation id + [[nodiscard]] const BRepGraphInc::Polygon2DRep& Polygon2DRep( + const BRepGraph_Polygon2DRepId theRep) const + { + return myPolygons2D.Get(theRep.Index); + } + + //! @param[in] theRep typed polygon-on-triangulation representation id + [[nodiscard]] const BRepGraphInc::PolygonOnTriRep& PolygonOnTriRep( + const BRepGraph_PolygonOnTriRepId theRep) const + { + return myPolygonsOnTri.Get(theRep.Index); + } + + //! @name Mutable representation access + //! Each method returns a mutable reference to the representation entity at the given typed id. + + //! @param[in] theRep typed surface representation id + BRepGraphInc::SurfaceRep& ChangeSurfaceRep(const BRepGraph_SurfaceRepId theRep) + { + return mySurfaces.Change(theRep.Index); + } + + //! @param[in] theRep typed curve-3D representation id + BRepGraphInc::Curve3DRep& ChangeCurve3DRep(const BRepGraph_Curve3DRepId theRep) + { + return myCurves3D.Change(theRep.Index); + } + + //! @param[in] theRep typed curve-2D representation id + BRepGraphInc::Curve2DRep& ChangeCurve2DRep(const BRepGraph_Curve2DRepId theRep) + { + return myCurves2D.Change(theRep.Index); + } + + //! @param[in] theRep typed triangulation representation id + BRepGraphInc::TriangulationRep& ChangeTriangulationRep(const BRepGraph_TriangulationRepId theRep) + { + return myTriangulationsRep.Change(theRep.Index); + } + + //! @param[in] theRep typed polygon-3D representation id + BRepGraphInc::Polygon3DRep& ChangePolygon3DRep(const BRepGraph_Polygon3DRepId theRep) + { + return myPolygons3D.Change(theRep.Index); + } + + //! @param[in] theRep typed polygon-2D representation id + BRepGraphInc::Polygon2DRep& ChangePolygon2DRep(const BRepGraph_Polygon2DRepId theRep) + { + return myPolygons2D.Change(theRep.Index); + } + + //! @param[in] theRep typed polygon-on-triangulation representation id + BRepGraphInc::PolygonOnTriRep& ChangePolygonOnTriRep(const BRepGraph_PolygonOnTriRepId theRep) + { + return myPolygonsOnTri.Change(theRep.Index); + } + + //! @name Append representation entities + //! Each method creates a new representation entity, increments the active count, + //! and returns a mutable reference to the appended entry for initialization. + + BRepGraphInc::SurfaceRep& AppendSurfaceRep() { return mySurfaces.Append(); } + + BRepGraphInc::Curve3DRep& AppendCurve3DRep() { return myCurves3D.Append(); } + + BRepGraphInc::Curve2DRep& AppendCurve2DRep() { return myCurves2D.Append(); } + + BRepGraphInc::TriangulationRep& AppendTriangulationRep() { return myTriangulationsRep.Append(); } + + BRepGraphInc::Polygon3DRep& AppendPolygon3DRep() { return myPolygons3D.Append(); } + + BRepGraphInc::Polygon2DRep& AppendPolygon2DRep() { return myPolygons2D.Append(); } + + BRepGraphInc::PolygonOnTriRep& AppendPolygonOnTriRep() { return myPolygonsOnTri.Append(); } + + //! @name Const entity access + //! Each method returns a const reference to the entity at the given typed id. + + //! @param[in] theVertex typed vertex id + [[nodiscard]] const BRepGraphInc::VertexDef& Vertex(const BRepGraph_VertexId theVertex) const + { + return myVertices.Get(theVertex.Index); + } + + //! @param[in] theEdge typed edge id + [[nodiscard]] const BRepGraphInc::EdgeDef& Edge(const BRepGraph_EdgeId theEdge) const + { + return myEdges.Get(theEdge.Index); + } + + //! @param[in] theCoEdge typed coedge id + [[nodiscard]] const BRepGraphInc::CoEdgeDef& CoEdge(const BRepGraph_CoEdgeId theCoEdge) const + { + return myCoEdges.Get(theCoEdge.Index); + } + + //! @param[in] theWire typed wire id + [[nodiscard]] const BRepGraphInc::WireDef& Wire(const BRepGraph_WireId theWire) const + { + return myWires.Get(theWire.Index); + } + + //! @param[in] theFace typed face id + [[nodiscard]] const BRepGraphInc::FaceDef& Face(const BRepGraph_FaceId theFace) const + { + return myFaces.Get(theFace.Index); + } + + //! @param[in] theShell typed shell id + [[nodiscard]] const BRepGraphInc::ShellDef& Shell(const BRepGraph_ShellId theShell) const + { + return myShells.Get(theShell.Index); + } + + //! @param[in] theSolid typed solid id + [[nodiscard]] const BRepGraphInc::SolidDef& Solid(const BRepGraph_SolidId theSolid) const + { + return mySolids.Get(theSolid.Index); + } + + //! @param[in] theCompound typed compound id + [[nodiscard]] const BRepGraphInc::CompoundDef& Compound( + const BRepGraph_CompoundId theCompound) const + { + return myCompounds.Get(theCompound.Index); + } + + //! @param[in] theCompSolid typed comp-solid id + [[nodiscard]] const BRepGraphInc::CompSolidDef& CompSolid( + const BRepGraph_CompSolidId theCompSolid) const + { + return myCompSolids.Get(theCompSolid.Index); + } + + //! @param[in] theProduct typed product id + [[nodiscard]] const BRepGraphInc::ProductDef& Product(const BRepGraph_ProductId theProduct) const + { + return myProducts.Get(theProduct.Index); + } + + //! @param[in] theOccurrence typed occurrence id + [[nodiscard]] const BRepGraphInc::OccurrenceDef& Occurrence( + const BRepGraph_OccurrenceId theOccurrence) const + { + return myOccurrences.Get(theOccurrence.Index); + } + + //! @name Const transitional reference access + + [[nodiscard]] const BRepGraphInc::ShellRef& ShellRef(const BRepGraph_ShellRefId theRefId) const + { + return myShellRefs.Get(theRefId.Index); + } + + [[nodiscard]] const BRepGraphInc::FaceRef& FaceRef(const BRepGraph_FaceRefId theRefId) const + { + return myFaceRefs.Get(theRefId.Index); + } + + [[nodiscard]] const BRepGraphInc::WireRef& WireRef(const BRepGraph_WireRefId theRefId) const + { + return myWireRefs.Get(theRefId.Index); + } + + [[nodiscard]] const BRepGraphInc::CoEdgeRef& CoEdgeRef(const BRepGraph_CoEdgeRefId theRefId) const + { + return myCoEdgeRefs.Get(theRefId.Index); + } + + [[nodiscard]] const BRepGraphInc::VertexRef& VertexRef(const BRepGraph_VertexRefId theRefId) const + { + return myVertexRefs.Get(theRefId.Index); + } + + [[nodiscard]] const BRepGraphInc::SolidRef& SolidRef(const BRepGraph_SolidRefId theRefId) const + { + return mySolidRefs.Get(theRefId.Index); + } + + [[nodiscard]] const BRepGraphInc::ChildRef& ChildRef(const BRepGraph_ChildRefId theRefId) const + { + return myChildRefs.Get(theRefId.Index); + } + + [[nodiscard]] const BRepGraphInc::OccurrenceRef& OccurrenceRef( + const BRepGraph_OccurrenceRefId theRefId) const + { + return myOccurrenceRefs.Get(theRefId.Index); + } + + //! @name Mutable entity access + //! Each method returns a mutable reference to the entity at the given typed id. + + //! @param[in] theVertex typed vertex id + BRepGraphInc::VertexDef& ChangeVertex(const BRepGraph_VertexId theVertex) + { + return myVertices.Change(theVertex.Index); + } + + //! @param[in] theEdge typed edge id + BRepGraphInc::EdgeDef& ChangeEdge(const BRepGraph_EdgeId theEdge) + { + return myEdges.Change(theEdge.Index); + } + + //! @param[in] theCoEdge typed coedge id + BRepGraphInc::CoEdgeDef& ChangeCoEdge(const BRepGraph_CoEdgeId theCoEdge) + { + return myCoEdges.Change(theCoEdge.Index); + } + + //! @param[in] theWire typed wire id + BRepGraphInc::WireDef& ChangeWire(const BRepGraph_WireId theWire) + { + return myWires.Change(theWire.Index); + } + + //! @param[in] theFace typed face id + BRepGraphInc::FaceDef& ChangeFace(const BRepGraph_FaceId theFace) + { + return myFaces.Change(theFace.Index); + } + + //! @param[in] theShell typed shell id + BRepGraphInc::ShellDef& ChangeShell(const BRepGraph_ShellId theShell) + { + return myShells.Change(theShell.Index); + } + + //! @param[in] theSolid typed solid id + BRepGraphInc::SolidDef& ChangeSolid(const BRepGraph_SolidId theSolid) + { + return mySolids.Change(theSolid.Index); + } + + //! @param[in] theCompound typed compound id + BRepGraphInc::CompoundDef& ChangeCompound(const BRepGraph_CompoundId theCompound) + { + return myCompounds.Change(theCompound.Index); + } + + //! @param[in] theCompSolid typed comp-solid id + BRepGraphInc::CompSolidDef& ChangeCompSolid(const BRepGraph_CompSolidId theCompSolid) + { + return myCompSolids.Change(theCompSolid.Index); + } + + //! @param[in] theProduct typed product id + BRepGraphInc::ProductDef& ChangeProduct(const BRepGraph_ProductId theProduct) + { + return myProducts.Change(theProduct.Index); + } + + //! @param[in] theOccurrence typed occurrence id + BRepGraphInc::OccurrenceDef& ChangeOccurrence(const BRepGraph_OccurrenceId theOccurrence) + { + return myOccurrences.Change(theOccurrence.Index); + } + + //! @name Mutable transitional reference access + + BRepGraphInc::ShellRef& ChangeShellRef(const BRepGraph_ShellRefId theRefId) + { + return myShellRefs.Change(theRefId.Index); + } + + BRepGraphInc::FaceRef& ChangeFaceRef(const BRepGraph_FaceRefId theRefId) + { + return myFaceRefs.Change(theRefId.Index); + } + + BRepGraphInc::WireRef& ChangeWireRef(const BRepGraph_WireRefId theRefId) + { + return myWireRefs.Change(theRefId.Index); + } + + BRepGraphInc::CoEdgeRef& ChangeCoEdgeRef(const BRepGraph_CoEdgeRefId theRefId) + { + return myCoEdgeRefs.Change(theRefId.Index); + } + + BRepGraphInc::VertexRef& ChangeVertexRef(const BRepGraph_VertexRefId theRefId) + { + return myVertexRefs.Change(theRefId.Index); + } + + BRepGraphInc::SolidRef& ChangeSolidRef(const BRepGraph_SolidRefId theRefId) + { + return mySolidRefs.Change(theRefId.Index); + } + + BRepGraphInc::ChildRef& ChangeChildRef(const BRepGraph_ChildRefId theRefId) + { + return myChildRefs.Change(theRefId.Index); + } + + BRepGraphInc::OccurrenceRef& ChangeOccurrenceRef(const BRepGraph_OccurrenceRefId theRefId) + { + return myOccurrenceRefs.Change(theRefId.Index); + } + + //! @name Append entity (returns mutable ref to newly created entity) + //! Each method creates a new entity, increments the active count, + //! initializes inner vectors with the storage allocator, and returns + //! a mutable reference to the appended entry for initialization. + + BRepGraphInc::VertexDef& AppendVertex() { return myVertices.Append(myAllocator); } + + BRepGraphInc::EdgeDef& AppendEdge() { return myEdges.Append(myAllocator); } + + BRepGraphInc::CoEdgeDef& AppendCoEdge() { return myCoEdges.Append(myAllocator); } + + BRepGraphInc::WireDef& AppendWire() { return myWires.Append(myAllocator); } + + BRepGraphInc::FaceDef& AppendFace() { return myFaces.Append(myAllocator); } + + BRepGraphInc::ShellDef& AppendShell() { return myShells.Append(myAllocator); } + + BRepGraphInc::SolidDef& AppendSolid() { return mySolids.Append(myAllocator); } + + BRepGraphInc::CompoundDef& AppendCompound() { return myCompounds.Append(myAllocator); } + + BRepGraphInc::CompSolidDef& AppendCompSolid() { return myCompSolids.Append(myAllocator); } + + BRepGraphInc::ProductDef& AppendProduct() { return myProducts.Append(myAllocator); } + + BRepGraphInc::OccurrenceDef& AppendOccurrence() { return myOccurrences.Append(myAllocator); } + + //! @name Append transitional reference entries + + BRepGraphInc::ShellRef& AppendShellRef() { return myShellRefs.Append(); } + + BRepGraphInc::FaceRef& AppendFaceRef() { return myFaceRefs.Append(); } + + BRepGraphInc::WireRef& AppendWireRef() { return myWireRefs.Append(); } + + BRepGraphInc::CoEdgeRef& AppendCoEdgeRef() { return myCoEdgeRefs.Append(); } + + BRepGraphInc::VertexRef& AppendVertexRef() { return myVertexRefs.Append(); } + + BRepGraphInc::SolidRef& AppendSolidRef() { return mySolidRefs.Append(); } + + BRepGraphInc::ChildRef& AppendChildRef() { return myChildRefs.Append(); } + + BRepGraphInc::OccurrenceRef& AppendOccurrenceRef() { return myOccurrenceRefs.Append(); } + + //! @name UID access + + //! Return the per-kind UID vector for a given Kind. + [[nodiscard]] Standard_EXPORT const NCollection_Vector& UIDs( + const BRepGraph_NodeId::Kind theKind) const; + + //! Return the per-kind UID vector for a given Kind (mutable). + Standard_EXPORT NCollection_Vector& ChangeUIDs( + const BRepGraph_NodeId::Kind theKind); + + //! Clear all UID vectors (reset lengths to 0). + Standard_EXPORT void ResetAllUIDs(); + + //! Return the BaseRef portion of any ref entry by generic RefId. + //! @param[in] theRefId generic reference identifier + //! @return const reference to the BaseRef base of the ref entry + [[nodiscard]] Standard_EXPORT const BRepGraphInc::BaseRef& BaseRef( + const BRepGraph_RefId theRefId) const; + + //! Return the mutable BaseRef portion of any ref entry by generic RefId. + //! @param[in] theRefId generic reference identifier + //! @return mutable reference to the BaseRef base of the ref entry + Standard_EXPORT BRepGraphInc::BaseRef& ChangeBaseRef(const BRepGraph_RefId theRefId); + + //! @name Ref UID access + + //! Return the per-kind transitional reference UID vector. + [[nodiscard]] Standard_EXPORT const NCollection_Vector& RefUIDs( + const BRepGraph_RefId::Kind theKind) const; + + //! Return the per-kind transitional reference UID vector (mutable). + Standard_EXPORT NCollection_Vector& ChangeRefUIDs( + const BRepGraph_RefId::Kind theKind); + + //! Clear all transitional reference UID vectors. + Standard_EXPORT void ResetAllRefUIDs(); + + //! @name Reverse index + + [[nodiscard]] const BRepGraphInc_ReverseIndex& ReverseIndex() const { return myReverseIdx; } + + BRepGraphInc_ReverseIndex& ChangeReverseIndex() { return myReverseIdx; } + + //! @name TShape to NodeId map + + [[nodiscard]] const BRepGraph_NodeId* FindNodeByTShape(const TopoDS_TShape* theTShape) const + { + return myTShapeToNodeId.Seek(theTShape); + } + + [[nodiscard]] bool HasTShapeBinding(const TopoDS_TShape* theTShape) const + { + return myTShapeToNodeId.IsBound(theTShape); + } + + void BindTShapeToNode(const TopoDS_TShape* theTShape, const BRepGraph_NodeId theNodeId) + { + myTShapeToNodeId.Bind(theTShape, theNodeId); + } + + //! @name Original shapes + + [[nodiscard]] const TopoDS_Shape* FindOriginal(const BRepGraph_NodeId theNodeId) const + { + return myOriginalShapes.Seek(theNodeId); + } + + [[nodiscard]] bool HasOriginal(const BRepGraph_NodeId theNodeId) const + { + return myOriginalShapes.IsBound(theNodeId); + } + + void BindOriginal(const BRepGraph_NodeId theNodeId, const TopoDS_Shape& theShape) + { + myOriginalShapes.Bind(theNodeId, theShape); + } + + void UnBindOriginal(const BRepGraph_NodeId theNodeId) { myOriginalShapes.UnBind(theNodeId); } + + //! @name Population status + + [[nodiscard]] bool GetIsDone() const { return myIsDone; } + + void SetIsDone(const bool theVal) { myIsDone = theVal; } + + //! Clear all storage. + Standard_EXPORT void Clear(); + + //! Build reverse indices from entity and relationship tables. + //! Call after population is complete. + Standard_EXPORT void BuildReverseIndex(); + + //! Incrementally update reverse indices for entities appended after a previous Build. + //! Only processes entities from the old counts to the current vector lengths. + Standard_EXPORT void BuildDeltaReverseIndex(const int theOldNbEdges, + const int theOldNbWires, + const int theOldNbFaces, + const int theOldNbShells, + const int theOldNbSolids); + + //! Debug: verify reverse index consistency against entity tables. + //! @return true if all forward refs have matching reverse entries + Standard_EXPORT bool ValidateReverseIndex() const; + +private: + friend class BRepGraphInc_Populate; + friend class BRepGraph; + + //! @brief Template store for topology entity kinds. + //! Groups the entity vector, per-kind UID vector, and active count + //! into a single struct, eliminating repeated boilerplate. + template + struct DefStore + { + NCollection_Vector Entities; + NCollection_Vector UIDs; + int NbActive = 0; + + DefStore() = default; + + DefStore(const int theBlockSize, const occ::handle& theAlloc) + : Entities(theBlockSize, theAlloc), + UIDs(theBlockSize, theAlloc) + { + } + + int Nb() const { return Entities.Length(); } + + const EntityT& Get(const int theIdx) const { return Entities.Value(theIdx); } + + EntityT& Change(const int theIdx) { return Entities.ChangeValue(theIdx); } + + EntityT& Append(const occ::handle& theAlloc) + { + ++NbActive; + EntityT& anEntity = Entities.Appended(); + anEntity.InitVectors(theAlloc); + return anEntity; + } + + void DecrementActive() + { + Standard_ASSERT_VOID(NbActive > 0, "DefStore::DecrementActive: underflow"); + if (NbActive > 0) + --NbActive; + } + + void Clear() + { + Entities.Clear(); + UIDs.Clear(); + NbActive = 0; + } + }; + + //! @brief Template store for representation entity kinds. + //! Groups the representation vector and active count into a single struct. + template + struct RepStore + { + NCollection_Vector Entities; + int NbActive = 0; + + RepStore() = default; + + RepStore(const int theBlockSize, const occ::handle& theAlloc) + : Entities(theBlockSize, theAlloc) + { + } + + int Nb() const { return Entities.Length(); } + + const RepT& Get(const int theIdx) const { return Entities.Value(theIdx); } + + RepT& Change(const int theIdx) { return Entities.ChangeValue(theIdx); } + + RepT& Append() + { + ++NbActive; + return Entities.Appended(); + } + + void DecrementActive() + { + Standard_ASSERT_VOID(NbActive > 0, "RepStore::DecrementActive: underflow"); + if (NbActive > 0) + --NbActive; + } + + void EraseLast() + { + Standard_ASSERT_VOID(NbActive > 0, "RepStore::EraseLast: underflow"); + if (NbActive > 0) + { + Entities.EraseLast(); + --NbActive; + } + } + + void Clear() + { + Entities.Clear(); + NbActive = 0; + } + }; + + //! @brief Template store for transitional reference entry kinds. + //! Groups reference vectors and per-kind UID vectors into a single struct. + template + struct RefStore + { + NCollection_Vector Refs; + NCollection_Vector UIDs; + int NbActive = 0; + + RefStore() = default; + + RefStore(const int theBlockSize, const occ::handle& theAlloc) + : Refs(theBlockSize, theAlloc), + UIDs(theBlockSize, theAlloc) + { + } + + int Nb() const { return Refs.Length(); } + + const RefT& Get(const int theIdx) const { return Refs.Value(theIdx); } + + RefT& Change(const int theIdx) { return Refs.ChangeValue(theIdx); } + + RefT& Append() + { + ++NbActive; + return Refs.Appended(); + } + + void DecrementActive() + { + Standard_ASSERT_VOID(NbActive > 0, "RefStore::DecrementActive: underflow"); + if (NbActive > 0) + --NbActive; + } + + void Clear() + { + Refs.Clear(); + UIDs.Clear(); + NbActive = 0; + } + }; + + //! @name Topology entity stores + DefStore myVertices; + DefStore myEdges; + DefStore myCoEdges; + DefStore myWires; + DefStore myFaces; + DefStore myShells; + DefStore mySolids; + DefStore myCompounds; + DefStore myCompSolids; + DefStore myProducts; + DefStore myOccurrences; + + //! @name Transitional reference entry stores + RefStore myShellRefs; + RefStore myFaceRefs; + RefStore myWireRefs; + RefStore myCoEdgeRefs; + RefStore myVertexRefs; + RefStore mySolidRefs; + RefStore myChildRefs; + RefStore myOccurrenceRefs; + + //! @name Representation entity stores + RepStore mySurfaces; + RepStore myCurves3D; + RepStore myCurves2D; + RepStore myTriangulationsRep; + RepStore myPolygons3D; + RepStore myPolygons2D; + RepStore myPolygonsOnTri; + + BRepGraphInc_ReverseIndex myReverseIdx; + + NCollection_DataMap myTShapeToNodeId; + NCollection_DataMap myOriginalShapes; + + occ::handle myAllocator; + + bool myIsDone = false; +}; + +#endif // _BRepGraphInc_Storage_HeaderFile diff --git a/src/ModelingData/TKBRep/BRepGraphInc/BRepGraphInc_Usage.hxx b/src/ModelingData/TKBRep/BRepGraphInc/BRepGraphInc_Usage.hxx new file mode 100644 index 0000000000..81bae61b64 --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraphInc/BRepGraphInc_Usage.hxx @@ -0,0 +1,92 @@ +// Copyright (c) 2026 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 _BRepGraphInc_Usage_HeaderFile +#define _BRepGraphInc_Usage_HeaderFile + +#include +#include +#include +#include + +#include + +//! @file BRepGraphInc_Usage.hxx +//! @brief Unified lightweight container binding a node identity with placement. +//! +//! Usage is the BRepGraph analogue of TopoDS_Shape: a lightweight value type +//! that bundles a definition id with its location and orientation in context. +//! +//! Supports C++17 structured bindings (aggregate type): +//! @code +//! for (auto [aDefId, aLoc, anOri] : BRepGraph_ChildExplorer(aGraph, aRoot, Kind::Face)) +//! { +//! // ... +//! } +//! @endcode +namespace BRepGraphInc +{ + +//! @brief Unified usage container template. +//! +//! Bundles a typed definition id with location and orientation. +//! +//! @tparam TypedIdT typed definition id (e.g. BRepGraph_FaceId, BRepGraph_NodeId). +template +struct Usage +{ + TypedIdT DefId; + TopLoc_Location Location; + TopAbs_Orientation Orientation = TopAbs_FORWARD; +}; + +using VertexUsage = Usage; +using CoEdgeUsage = Usage; +using FaceUsage = Usage; +using ShellUsage = Usage; +using SolidUsage = Usage; +using OccurrenceUsage = Usage; +using CompoundUsage = Usage; +using CompSolidUsage = Usage; +using ProductUsage = Usage; + +//! NodeUsage is Usage. +//! Returned by BRepGraph_ChildExplorer and BRepGraph_ParentExplorer +//! with accumulated transforms from traversal root to the current node. +//! Implicitly convertible from any typed Usage via BRepGraph_NodeId::Typed +//! implicit conversion to BRepGraph_NodeId. +using NodeUsage = Usage; + +//! Wire usage with an additional flag indicating whether this is the outer wire. +struct WireUsage : Usage +{ + bool IsOuter = false; +}; + +} // namespace BRepGraphInc + +//! std::hash specialization for BRepGraphInc::Usage. +template +struct std::hash> +{ + size_t operator()(const BRepGraphInc::Usage& theUsage) const noexcept + { + size_t aCombination[3]; + aCombination[0] = std::hash{}(theUsage.DefId); + aCombination[1] = theUsage.Location.HashCode(); + aCombination[2] = opencascade::hash(static_cast(theUsage.Orientation)); + return opencascade::hashBytes(aCombination, sizeof(aCombination)); + } +}; + +#endif // _BRepGraphInc_Usage_HeaderFile diff --git a/src/ModelingData/TKBRep/BRepGraphInc/FILES.cmake b/src/ModelingData/TKBRep/BRepGraphInc/FILES.cmake new file mode 100644 index 0000000000..d90b2fcb30 --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraphInc/FILES.cmake @@ -0,0 +1,16 @@ +set(OCCT_BRepGraphInc_FILES_LOCATION "${CMAKE_CURRENT_LIST_DIR}") + +set(OCCT_BRepGraphInc_FILES + BRepGraphInc_Definition.hxx + BRepGraphInc_Reference.hxx + BRepGraphInc_Representation.hxx + BRepGraphInc_Usage.hxx + BRepGraphInc_Populate.cxx + BRepGraphInc_Populate.hxx + BRepGraphInc_Reconstruct.cxx + BRepGraphInc_Reconstruct.hxx + BRepGraphInc_ReverseIndex.cxx + BRepGraphInc_ReverseIndex.hxx + BRepGraphInc_Storage.cxx + BRepGraphInc_Storage.hxx +) diff --git a/src/ModelingData/TKBRep/BRepGraphInc/README.md b/src/ModelingData/TKBRep/BRepGraphInc/README.md new file mode 100644 index 0000000000..a25f92a14a --- /dev/null +++ b/src/ModelingData/TKBRep/BRepGraphInc/README.md @@ -0,0 +1,357 @@ +# BRepGraphInc + +BRepGraphInc is the incidence-table backend used by BRepGraph. + +INTERNAL USE ONLY: this package is the backend runtime model behind the +BRepGraph facade. External code should treat BRepGraphInc storage/layout APIs +as unstable implementation details unless explicitly surfaced by facade views. + +It provides the runtime source of truth for topology entities, assembly entities, context references, reverse indices, reconstruction support, and identity mapping. + +BRepGraphInc is the backend runtime model that powers BRepGraph. + +External code should normally enter through `BRepGraph::Build()`, `BRepGraph::Shapes()`, `BRepGraph::Topo()`, `BRepGraph::Refs()`, and the other facade views. A subset of `BRepGraphInc::*` structs is intentionally exposed read-only through those views; direct storage-level access (`BRepGraph_Data`, `myIncStorage`) is reserved for backend maintenance, low-level infrastructure, and focused tests. + +## What This Backend Owns + +- Topology entity tables (Vertex, Edge, CoEdge, Wire, Face, Shell, Solid, Compound, CompSolid) +- Assembly entity tables (Product, Occurrence) +- Representation entity tables (SurfaceRep, Curve3DRep, Curve2DRep, TriangulationRep, Polygon3DRep, Polygon2DRep, PolygonOnTriRep) +- Reference entry tables (ShellRef, FaceRef, WireRef, CoEdgeRef, VertexRef, SolidRef, ChildRef, OccurrenceRef) with BaseRef identity, orientation, and location +- Reverse adjacency indices (including product→occurrences) +- TShape to NodeId mapping +- Original shape map +- Per-kind UID vectors (10 entity kinds + 8 ref kinds) + +## Architecture + +```mermaid +flowchart TB + A[Algorithms] --> G[BRepGraph facade] + G --> D[BRepGraph_Data] + D --> S[BRepGraphInc_Storage] + + S --> E[Topology Entity Tables] + S --> AS[Assembly Entity Tables] + S --> RX[Reverse Index] + S --> TM[TShape to NodeId] + S --> OR[Original Shapes] + S --> UID[UID Vectors] + + P[BRepGraphInc_Populate] --> S + X[BRepGraphInc_Reconstruct] --> S +``` + +## Entity and Ref Model + +```mermaid +flowchart LR + subgraph Topology Entities + V[VertexDef] + E[EdgeDef] + CE[CoEdgeDef] + W[WireDef] + F[FaceDef] + SH[ShellDef] + SO[SolidDef] + CO[CompoundDef] + CS[CompSolidDef] + end + + subgraph Assembly Entities + PR[ProductDef] + OC[OccurrenceDef] + end + + F -->|WireUsage| W + W -->|CoEdgeUsage| CE + CE -->|EdgeDefId| E + E -->|Start/End VertexUsage| V + SH -->|FaceUsage| F + SO -->|ShellUsage| SH + CO -->|ChildUsage| SO + CO -->|ChildUsage| SH + CO -->|ChildUsage| F + CS -->|SolidUsage| SO + + PR -->|OccurrenceUsage| OC + PR -->|ShapeRootId| SO + OC -->|ProductDefId| PR + OC -.->|ParentOccurrenceDefId| OC +``` + +Notes: + +- Intrinsic data lives on entities; context data (orientation/location) lives on Ref tables +- CoEdge owns PCurve data for each edge-face binding (Weiler half-edge pattern) +- ProductDef: `ShapeRootId` (topology root for parts; invalid for assemblies), `OccurrenceRefIds` +- OccurrenceDef: `ProductDefId`, `ParentProductDefId`, `ParentOccurrenceDefId` (tree-structured placement chain), `Placement` + +## Entity Hierarchy + +```mermaid +graph TD + Product["ProductDef
ShapeRootId, RootOrientation, RootLocation"] + + Compound["CompoundDef
ChildRefIds[]"] + CompSolid["CompSolidDef
SolidRefIds[]"] + Solid["SolidDef
ShellRefIds[], FreeChildRefIds[]"] + Shell["ShellDef
IsClosed, FaceRefIds[], FreeChildRefIds[]"] + + Face["FaceDef
SurfaceRepId, TriangulationRepIds,
ActiveTriangulationIndex, WireRefIds[],
VertexRefIds[], Tolerance, NaturalRestriction
"] + + Wire["WireDef
CoEdgeRefIds[], IsClosed"] + + CoEdge["CoEdgeDef
EdgeDefId, FaceDefId, Sense,
Curve2DRepId, Polygon2DRepId,
ParamFirst/Last, UV1/UV2,
SeamPairId, SeamContinuity
"] + + Edge["EdgeDef
Curve3DRepId, Polygon3DRepId,
StartVertexRefId, EndVertexRefId,
InternalVertexRefIds[],
ParamFirst/Last, Tolerance,
SameParameter, SameRange,
IsDegenerate, IsClosed,
Regularities[]
"] + + Vertex["VertexDef
Point (def frame), Tolerance,
PointsOnCurve[],
PointsOnPCurve[],
PointsOnSurface[]
"] + + SurfRep["SurfaceRep
Geom_Surface"] + C3DRep["Curve3DRep
Geom_Curve"] + C2DRep["Curve2DRep
Geom2d_Curve"] + TriRep["TriangulationRep
Poly_Triangulation"] + + Product -->|"ShapeRootId"| Compound + Product -->|"ShapeRootId"| Solid + Compound -->|"ChildRefId"| Solid + CompSolid -->|"SolidRefId"| Solid + Solid -->|"ShellRefId"| Shell + Shell -->|"FaceRefId"| Face + Face -->|"WireRefId"| Wire + Wire -->|"CoEdgeRefId"| CoEdge + CoEdge -->|"EdgeIdx"| Edge + Edge -->|"StartVertexRefId"| Vertex + + Face -.->|"SurfaceRepId"| SurfRep + Face -.->|"TriangulationRepIds"| TriRep + Edge -.->|"Curve3DRepId"| C3DRep + CoEdge -.->|"Curve2DRepId"| C2DRep + CoEdge -.->|"SeamPairId"| CoEdge +``` + +## Reference Entry Model + +Reference entries are the typed incidence edges connecting parent entities to child definitions. Each ref kind has its own entry table and RefId space, managed by `RefStore` in Storage. + +### BaseRef + +Common header for all reference entries: + +- `RefId`: typed address (Kind + Index) into the ref entry vector +- `ParentId`: NodeId of the owning parent entity +- `OwnGen`: generation counter for change tracking (incremented on ref mutation) +- `IsRemoved`: soft-delete flag + +### Ref Types + +Concrete ref entry types extend BaseRef with context data: + +- `ShellRef`, `FaceRef`, `WireRef`, `CoEdgeRef`, `VertexRef`, `SolidRef`, `ChildRef`, `OccurrenceRef` +- Each adds: `DefId` (target entity index), `Orientation`, `LocalLocation` + +### Entity RefId Vectors + +Entities store typed RefId vectors instead of inline ref arrays: + +- **SolidDef**: `ShellRefIds[]`, `FreeChildRefIds[]` +- **ShellDef**: `FaceRefIds[]`, `FreeChildRefIds[]` +- **FaceDef**: `WireRefIds[]`, `VertexRefIds[]` +- **WireDef**: `CoEdgeRefIds[]` +- **EdgeDef**: `StartVertexRefId`, `EndVertexRefId`, `InternalVertexRefIds[]` +- **CompoundDef**: `ChildRefIds[]` +- **CompSolidDef**: `SolidRefIds[]` +- **ProductDef**: `OccurrenceRefIds[]` + +### RefStore + +`RefStore` in Storage groups per-kind ref entry vector + UID vector + active count. Provides `Get()`, `Change()`, `Append()`, `DecrementActive()` (for soft-delete tracking via `BaseRef.IsRemoved`) -- same pattern as `DefStore`. + +## Build Pipeline + +```mermaid +flowchart LR + I[TopoDS input] --> P1[Phase 1: Hierarchy traversal] + P1 --> P2[Phase 2: Parallel face extraction] + P2 --> P3[Phase 3: Sequential register and dedup] + P3 --> P3a[Phase 3a: Compound face fixup] + P3a --> P3b[Phase 3b: Edge regularities] + P3b --> P3c[Phase 3c: Vertex point reps] + P3c --> P4[Phase 4: Reverse index build] + P4 --> D[Storage IsDone] +``` + +| Phase | Mode | What happens | +|-------|------|--------------| +| **Phase 1** | Sequential | Traverse hierarchy. Create container entities (Compound, CompSolid, Solid, Shell). Collect face contexts. | +| **Phase 2** | Parallel | Extract per-face geometry: surface, PCurves, triangulations, vertices, edges. | +| **Phase 3** | Sequential | Register faces, wires, edges, CoEdges with TShape deduplication. Link faces to shells. | +| **Phase 3a** | Sequential | Resolve deferred Compound→Face ChildUsage indices via TShape lookup. | +| **Phase 3b** | Optional | Edge regularities (controlled by `Options.ExtractRegularities`). | +| **Phase 3c** | Optional | Vertex point representations (controlled by `Options.ExtractVertexPointReps`). | +| **Phase 4** | Sequential | Build reverse indices for O(1) upward navigation. | + +Backend entry point: `BRepGraphInc_Populate::Perform()`. + +For normal graph construction, use `BRepGraph::Build()` instead. The facade owns the public lifecycle, view initialization, mutation boundary behavior, and cache coordination on top of this backend pipeline. + +### Geometry: Definition-Frame Storage + +All geometry is stored in **definition frame** - the TShape-internal location is baked into the geometry, while instance locations are preserved separately in Ref structures. + +**Surface**: `S_merged = S0.Transformed(TFace.Location())` +**3D Curve**: `C_merged = C0.Transformed(TEdge.Location())` +**Vertex Point**: `BRep_TVertex::Pnt()` (raw, no Location applied) + +Formula: `repLoc = theShapeLoc⁻¹ × theCombinedLoc; if repLoc ≠ Identity: theGeom.Transformed(repLoc)` + +### PCurve Extraction + +PCurves are extracted directly from `BRep_TEdge::Curves()`, bypassing `BRep_Tool::CurveOnSurface` which can generate phantom computed PCurves via `CurveOnPlane` and has `TopLoc_Location` structural equality issues. + +Multi-pass matching in `extractStoredPCurves()`: +- **Pass 1**: exact (Surface, Location) match via `IsCurveOnSurface(S, L)` +- **Pass 2**: surface-handle-only fallback for TopLoc_Location structural equality bug +- **Pass 3**: original (pre-transform) surface handle match +- For seam edges: extracts both PCurves + continuity + +### Instance Locations + +| Ref Type | What it stores | +|---------------|---------------| +| `FaceRef.LocalLocation` | face.Location() relative to shell | +| `WireRef.LocalLocation` | wire.Location() relative to face | +| `CoEdgeRef.LocalLocation` | edge.Location() relative to wire | +| `ShellRef.LocalLocation` | shell.Location() relative to solid | +| `VertexRef.LocalLocation` | vertex.Location() relative to edge | + +### Deduplication + +- **TShape dedup**: each unique `TopoDS_TShape*` maps to one graph entity +- **Geometry rep dedup**: surfaces, curves, triangulations deduped by handle pointer in `RepDedup` maps + +## Reconstruction Pipeline + +```mermaid +flowchart TD + N[Node request] --> C{Cache hit} + C -- yes --> R1[Return cached] + C -- no --> K[Kind dispatch] + K --> B[Build TopoDS shape] + B --> BIND[Bind cache] + BIND --> R1 +``` + +Primary API: +- `Node(storage, nodeId)` - independent, local cache +- `Node(storage, nodeId, cache)` - shared cache for vertex/edge reuse +- `FaceWithCache(storage, faceIdx, cache)` - specialized face reconstruction + +### Geometry Restoration + +All geometry restored with `TopLoc_Location() = Identity` (TShape location already baked): + +```cpp +aBB.MakeFace(aNewFace, S_merged, TopLoc_Location(), tol); +aBB.MakeEdge(aNewEdge, C_merged, TopLoc_Location(), tol); +aBB.MakeVertex(aNewVtx, rawPoint, tol); +``` + +### PCurve Attachment with Location Compensation + +Edge temporarily carries composed wire+edge location for correct `BRep_Builder::UpdateEdge` storage key: + +```cpp +anEdge.Location(aEdgeInFaceLoc); // Temporarily apply +aBB.UpdateEdge(anEdge, aPC, aSurf, Identity); // Stores CR with loc⁻¹ +anEdge.Location(Identity); // Reset after attachment +``` + +### Special Cases + +- **Seam edges**: Two CoEdges with opposite Sense, linked by `SeamPairId`. Both PCurves attached via `UpdateEdge(E, PC1, PC2, S, L, tol)`. +- **Degenerate edges**: `MakeEdge()` + `Degenerated(true)`, no 3D curve. +- **IsClosed/NaturalRestriction**: Set AFTER sub-shapes are added (Add can reset flags). + +## Reverse Indices + +| Map | Purpose | +|-----|---------| +| edge → wires | Wire membership | +| edge → faces | Face adjacency (from CoEdge.FaceDefId) | +| edge → coedges | CoEdge lookup by parent edge | +| edge face count | Cached O(1) face count per edge | +| vertex → edges | Vertex incidence | +| coedge → wires | CoEdge-to-wire membership | +| wire → faces | Wire-to-face membership | +| face → shells | Face-to-shell membership | +| shell → solids | Shell-to-solid membership | +| solid → compounds | Compound parents of a solid | +| solid → compsolids | CompSolid parents of a solid | +| shell → compounds | Compound parents of a shell | +| face → compounds | Compound parents of a face | +| compound → compounds | Compound parents of a compound | +| compsolid → compounds | Compound parents of a compsolid | +| product → occurrences | Assembly references | + +## Core Invariants + +1. **Entity ID**: for each entity vector slot i: `Id.Index == i` and `Id.Kind` matches vector kind +2. **Mapping**: TShape to NodeId must resolve to existing, type-correct entity +3. **Reverse-index**: required reverse rows must exist for forward refs used by query paths +4. **Removal**: IsRemoved entities must be filtered from normal traversals +5. **Mutation boundary**: entities, reverse indices, cache invalidation, and history are coherent after each operation +6. **Assembly**: every Build produces at least one root Product; occurrence cross-references valid; self-referencing rejected; ParentOccurrenceDefId forms a tree + +## Memory and Performance + +### Typed-Id API and DefStore + +All public Storage accessors use strongly-typed ids (`BRepGraph_VertexId`, `BRepGraph_EdgeId`, `BRepGraph_FaceId`, etc.) instead of raw `int` for compile-time safety. Internally, Storage uses two template patterns: + +- **`DefStore`**: groups entity vector + per-kind UID vector + active count. Provides `Get()`, `Change()`, `Append()`, `DecrementActive()`. +- **`RepStore`**: groups representation vector + active count. Same accessor pattern, no UID vector. + +### Allocator Propagation + +All containers use the graph's `NCollection_IncAllocator` for O(1) bump-pointer allocation and bulk-free destruction: + +- **Storage**: all entity tables, UID vectors, and DataMaps receive the allocator +- **ReverseIndex**: `SetAllocator()` called before `Build()`. Inner vectors constructed with allocator via `preSize()`. + +Contract: `SetAllocator()` must be called before `Build()`/`BuildDelta()` on ReverseIndex. + +### Other Performance Notes + +- Edge-to-face reverse index uses sort-dedup (stack-allocated for typical 1-4 coedges per edge) +- `Append()` allocates UIDs incrementally (O(M) instead of O(N+M)) +- Post-passes are optional via `BRepGraphInc_Populate::Options` +- `NbFacesOfEdge()` is O(1) via cached count vector + +## TopoDS vs GraphInc Comparison (Box) + +| Item | Count | GraphInc Storage | +|------|------:|-----------------| +| Solid | 1 | `SolidDef` table | +| Shell | 1 | `ShellDef` table | +| Face | 6 | `FaceDef` table | +| Wire | 6 | `WireDef` table | +| Edge | 12 | `EdgeDef` table | +| CoEdge | 24 | `CoEdgeDef` table | +| Vertex | 8 | `VertexDef` table | +| Product | 1 (auto root) | `ProductDef` table | + +Key difference: TopoDS expresses context through shape occurrences. GraphInc keeps canonical entities and stores context on refs. + +## File Map + +| File | Purpose | +|------|---------| +| `BRepGraphInc_Definition.hxx` | Entity struct definitions | +| `BRepGraphInc_Reference.hxx` | Context reference definitions | +| `BRepGraphInc_Storage.hxx/.cxx` | Typed storage and ownership | +| `BRepGraphInc_Populate.hxx/.cxx` | TopoDS → incidence build and append | +| `BRepGraphInc_Reconstruct.hxx/.cxx` | Incidence → TopoDS reconstruction | +| `BRepGraphInc_ReverseIndex.hxx/.cxx` | Reverse adjacency services | +| `BRepGraph_WireExplorer.hxx` | Wire traversal in connection order (in BRepGraph package) | diff --git a/src/ModelingData/TKBRep/GTests/BRepGraphInc_Test.cxx b/src/ModelingData/TKBRep/GTests/BRepGraphInc_Test.cxx new file mode 100644 index 0000000000..6413c40155 --- /dev/null +++ b/src/ModelingData/TKBRep/GTests/BRepGraphInc_Test.cxx @@ -0,0 +1,1137 @@ +// Copyright (c) 2026 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 "BRepGraph_RefTestTools.hxx" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +static double computeArea(const TopoDS_Shape& theShape) +{ + GProp_GProps aProps; + BRepGProp::SurfaceProperties(theShape, aProps); + return aProps.Mass(); +} + +static double computeVolume(const TopoDS_Shape& theShape) +{ + GProp_GProps aProps; + BRepGProp::VolumeProperties(theShape, aProps); + return aProps.Mass(); +} + +static int countSubShapes(const TopoDS_Shape& theShape, TopAbs_ShapeEnum theType) +{ + int aCount = 0; + for (TopExp_Explorer anExp(theShape, theType); anExp.More(); anExp.Next()) + ++aCount; + return aCount; +} + +// ============================================================ +// Entity count validation +// ============================================================ + +TEST(BRepGraphIncTest, Box_EntityCounts_MatchDefCounts) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + // Build BRepGraph for parity checks. + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + + // Build incidence storage. + BRepGraphInc_Storage aStorage; + BRepGraphInc_Populate::Perform(aStorage, aBox, false); + ASSERT_TRUE(aStorage.GetIsDone()); + + // Entity counts must match Def counts. + EXPECT_EQ(aStorage.NbVertices(), aGraph.Topo().Vertices().Nb()); + EXPECT_EQ(aStorage.NbEdges(), aGraph.Topo().Edges().Nb()); + EXPECT_EQ(aStorage.NbWires(), aGraph.Topo().Wires().Nb()); + EXPECT_EQ(aStorage.NbFaces(), aGraph.Topo().Faces().Nb()); + EXPECT_EQ(aStorage.NbShells(), aGraph.Topo().Shells().Nb()); + EXPECT_EQ(aStorage.NbSolids(), aGraph.Topo().Solids().Nb()); +} + +TEST(BRepGraphIncTest, Cylinder_EntityCounts_MatchDefCounts) +{ + BRepPrimAPI_MakeCylinder aCylMaker(5.0, 15.0); + const TopoDS_Shape& aCyl = aCylMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aCyl); + ASSERT_TRUE(aGraph.IsDone()); + + BRepGraphInc_Storage aStorage; + BRepGraphInc_Populate::Perform(aStorage, aCyl, false); + ASSERT_TRUE(aStorage.GetIsDone()); + + EXPECT_EQ(aStorage.NbVertices(), aGraph.Topo().Vertices().Nb()); + EXPECT_EQ(aStorage.NbEdges(), aGraph.Topo().Edges().Nb()); + EXPECT_EQ(aStorage.NbWires(), aGraph.Topo().Wires().Nb()); + EXPECT_EQ(aStorage.NbFaces(), aGraph.Topo().Faces().Nb()); + EXPECT_EQ(aStorage.NbShells(), aGraph.Topo().Shells().Nb()); + EXPECT_EQ(aStorage.NbSolids(), aGraph.Topo().Solids().Nb()); +} + +TEST(BRepGraphIncTest, Sphere_EntityCounts_MatchDefCounts) +{ + BRepPrimAPI_MakeSphere aSphMaker(8.0); + const TopoDS_Shape& aSph = aSphMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aSph); + ASSERT_TRUE(aGraph.IsDone()); + + BRepGraphInc_Storage aStorage; + BRepGraphInc_Populate::Perform(aStorage, aSph, false); + ASSERT_TRUE(aStorage.GetIsDone()); + + EXPECT_EQ(aStorage.NbVertices(), aGraph.Topo().Vertices().Nb()); + EXPECT_EQ(aStorage.NbEdges(), aGraph.Topo().Edges().Nb()); + EXPECT_EQ(aStorage.NbWires(), aGraph.Topo().Wires().Nb()); + EXPECT_EQ(aStorage.NbFaces(), aGraph.Topo().Faces().Nb()); +} + +TEST(BRepGraphIncTest, Storage_MarkRemovedRep_DecrementsActiveCountsAndIsIdempotent) +{ + BRepGraphInc_Storage aStorage; + + aStorage.AppendSurfaceRep().Id = BRepGraph_SurfaceRepId(0); + aStorage.AppendCurve3DRep().Id = BRepGraph_Curve3DRepId(0); + aStorage.AppendCurve2DRep().Id = BRepGraph_Curve2DRepId(0); + aStorage.AppendTriangulationRep().Id = BRepGraph_TriangulationRepId(0); + aStorage.AppendPolygon3DRep().Id = BRepGraph_Polygon3DRepId(0); + aStorage.AppendPolygon2DRep().Id = BRepGraph_Polygon2DRepId(0); + aStorage.AppendPolygonOnTriRep().Id = BRepGraph_PolygonOnTriRepId(0); + + EXPECT_EQ(aStorage.NbActiveSurfaces(), 1); + EXPECT_EQ(aStorage.NbActiveCurves3D(), 1); + EXPECT_EQ(aStorage.NbActiveCurves2D(), 1); + EXPECT_EQ(aStorage.NbActiveTriangulations(), 1); + EXPECT_EQ(aStorage.NbActivePolygons3D(), 1); + EXPECT_EQ(aStorage.NbActivePolygons2D(), 1); + EXPECT_EQ(aStorage.NbActivePolygonsOnTri(), 1); + + EXPECT_TRUE(aStorage.MarkRemovedRep(BRepGraph_SurfaceRepId(0))); + EXPECT_TRUE(aStorage.MarkRemovedRep(BRepGraph_Curve3DRepId(0))); + EXPECT_TRUE(aStorage.MarkRemovedRep(BRepGraph_Curve2DRepId(0))); + EXPECT_TRUE(aStorage.MarkRemovedRep(BRepGraph_TriangulationRepId(0))); + EXPECT_TRUE(aStorage.MarkRemovedRep(BRepGraph_Polygon3DRepId(0))); + EXPECT_TRUE(aStorage.MarkRemovedRep(BRepGraph_Polygon2DRepId(0))); + EXPECT_TRUE(aStorage.MarkRemovedRep(BRepGraph_PolygonOnTriRepId(0))); + + EXPECT_TRUE(aStorage.SurfaceRep(BRepGraph_SurfaceRepId(0)).IsRemoved); + EXPECT_TRUE(aStorage.Curve3DRep(BRepGraph_Curve3DRepId(0)).IsRemoved); + EXPECT_TRUE(aStorage.Curve2DRep(BRepGraph_Curve2DRepId(0)).IsRemoved); + EXPECT_TRUE(aStorage.TriangulationRep(BRepGraph_TriangulationRepId(0)).IsRemoved); + EXPECT_TRUE(aStorage.Polygon3DRep(BRepGraph_Polygon3DRepId(0)).IsRemoved); + EXPECT_TRUE(aStorage.Polygon2DRep(BRepGraph_Polygon2DRepId(0)).IsRemoved); + EXPECT_TRUE(aStorage.PolygonOnTriRep(BRepGraph_PolygonOnTriRepId(0)).IsRemoved); + + EXPECT_EQ(aStorage.NbActiveSurfaces(), 0); + EXPECT_EQ(aStorage.NbActiveCurves3D(), 0); + EXPECT_EQ(aStorage.NbActiveCurves2D(), 0); + EXPECT_EQ(aStorage.NbActiveTriangulations(), 0); + EXPECT_EQ(aStorage.NbActivePolygons3D(), 0); + EXPECT_EQ(aStorage.NbActivePolygons2D(), 0); + EXPECT_EQ(aStorage.NbActivePolygonsOnTri(), 0); + + EXPECT_FALSE(aStorage.MarkRemovedRep(BRepGraph_SurfaceRepId(0))); + EXPECT_FALSE(aStorage.MarkRemovedRep(BRepGraph_Curve3DRepId(0))); + EXPECT_FALSE(aStorage.MarkRemovedRep(BRepGraph_Curve2DRepId(0))); + EXPECT_FALSE(aStorage.MarkRemovedRep(BRepGraph_TriangulationRepId(0))); + EXPECT_FALSE(aStorage.MarkRemovedRep(BRepGraph_Polygon3DRepId(0))); + EXPECT_FALSE(aStorage.MarkRemovedRep(BRepGraph_Polygon2DRepId(0))); + EXPECT_FALSE(aStorage.MarkRemovedRep(BRepGraph_PolygonOnTriRepId(0))); + EXPECT_FALSE(aStorage.MarkRemovedRep(BRepGraph_RepId())); +} + +// ============================================================ +// Round-trip: area preservation +// ============================================================ + +TEST(BRepGraphIncTest, Box_RoundTrip_AreaPreserved) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + const double anOrigArea = computeArea(aBox); + + BRepGraphInc_Storage aStorage; + BRepGraphInc_Populate::Perform(aStorage, aBox, false); + ASSERT_TRUE(aStorage.GetIsDone()); + + TopoDS_Shape aRecon = BRepGraphInc_Reconstruct::Node(aStorage, BRepGraph_SolidId(0)); + ASSERT_FALSE(aRecon.IsNull()); + + const double aReconArea = computeArea(aRecon); + EXPECT_NEAR(aReconArea, anOrigArea, Precision::Confusion()); +} + +TEST(BRepGraphIncTest, Cylinder_RoundTrip_AreaPreserved) +{ + BRepPrimAPI_MakeCylinder aCylMaker(5.0, 15.0); + const TopoDS_Shape& aCyl = aCylMaker.Shape(); + const double anOrigArea = computeArea(aCyl); + + BRepGraphInc_Storage aStorage; + BRepGraphInc_Populate::Perform(aStorage, aCyl, false); + ASSERT_TRUE(aStorage.GetIsDone()); + + TopoDS_Shape aRecon = BRepGraphInc_Reconstruct::Node(aStorage, BRepGraph_SolidId(0)); + ASSERT_FALSE(aRecon.IsNull()); + + const double aReconArea = computeArea(aRecon); + EXPECT_NEAR(aReconArea, anOrigArea, Precision::Confusion()); +} + +TEST(BRepGraphIncTest, Sphere_RoundTrip_AreaPreserved) +{ + BRepPrimAPI_MakeSphere aSphMaker(8.0); + const TopoDS_Shape& aSph = aSphMaker.Shape(); + const double anOrigArea = computeArea(aSph); + + BRepGraphInc_Storage aStorage; + BRepGraphInc_Populate::Perform(aStorage, aSph, false); + ASSERT_TRUE(aStorage.GetIsDone()); + + TopoDS_Shape aRecon = BRepGraphInc_Reconstruct::Node(aStorage, BRepGraph_SolidId(0)); + ASSERT_FALSE(aRecon.IsNull()); + + const double aReconArea = computeArea(aRecon); + EXPECT_NEAR(aReconArea, anOrigArea, Precision::Confusion()); +} + +// ============================================================ +// Round-trip: volume preservation +// ============================================================ + +TEST(BRepGraphIncTest, Box_RoundTrip_VolumePreserved) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + const double anOrigVol = computeVolume(aBox); + + BRepGraphInc_Storage aStorage; + BRepGraphInc_Populate::Perform(aStorage, aBox, false); + ASSERT_TRUE(aStorage.GetIsDone()); + + TopoDS_Shape aRecon = BRepGraphInc_Reconstruct::Node(aStorage, BRepGraph_SolidId(0)); + ASSERT_FALSE(aRecon.IsNull()); + + const double aReconVol = computeVolume(aRecon); + EXPECT_NEAR(aReconVol, anOrigVol, Precision::Confusion()); +} + +TEST(BRepGraphIncTest, Cylinder_RoundTrip_VolumePreserved) +{ + BRepPrimAPI_MakeCylinder aCylMaker(5.0, 15.0); + const TopoDS_Shape& aCyl = aCylMaker.Shape(); + const double anOrigVol = computeVolume(aCyl); + + BRepGraphInc_Storage aStorage; + BRepGraphInc_Populate::Perform(aStorage, aCyl, false); + ASSERT_TRUE(aStorage.GetIsDone()); + + TopoDS_Shape aRecon = BRepGraphInc_Reconstruct::Node(aStorage, BRepGraph_SolidId(0)); + ASSERT_FALSE(aRecon.IsNull()); + + const double aReconVol = computeVolume(aRecon); + EXPECT_NEAR(aReconVol, anOrigVol, Precision::Confusion()); +} + +// ============================================================ +// Round-trip: sub-shape counts +// ============================================================ + +TEST(BRepGraphIncTest, Box_RoundTrip_SubShapeCounts) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + BRepGraphInc_Storage aStorage; + BRepGraphInc_Populate::Perform(aStorage, aBox, false); + ASSERT_TRUE(aStorage.GetIsDone()); + + TopoDS_Shape aRecon = BRepGraphInc_Reconstruct::Node(aStorage, BRepGraph_SolidId(0)); + ASSERT_FALSE(aRecon.IsNull()); + + EXPECT_EQ(countSubShapes(aRecon, TopAbs_FACE), countSubShapes(aBox, TopAbs_FACE)); + EXPECT_EQ(countSubShapes(aRecon, TopAbs_WIRE), countSubShapes(aBox, TopAbs_WIRE)); + EXPECT_EQ(countSubShapes(aRecon, TopAbs_EDGE), countSubShapes(aBox, TopAbs_EDGE)); + EXPECT_EQ(countSubShapes(aRecon, TopAbs_VERTEX), countSubShapes(aBox, TopAbs_VERTEX)); +} + +TEST(BRepGraphIncTest, Cylinder_RoundTrip_SubShapeCounts) +{ + BRepPrimAPI_MakeCylinder aCylMaker(5.0, 15.0); + const TopoDS_Shape& aCyl = aCylMaker.Shape(); + + BRepGraphInc_Storage aStorage; + BRepGraphInc_Populate::Perform(aStorage, aCyl, false); + ASSERT_TRUE(aStorage.GetIsDone()); + + TopoDS_Shape aRecon = BRepGraphInc_Reconstruct::Node(aStorage, BRepGraph_SolidId(0)); + ASSERT_FALSE(aRecon.IsNull()); + + EXPECT_EQ(countSubShapes(aRecon, TopAbs_FACE), countSubShapes(aCyl, TopAbs_FACE)); + EXPECT_EQ(countSubShapes(aRecon, TopAbs_WIRE), countSubShapes(aCyl, TopAbs_WIRE)); + EXPECT_EQ(countSubShapes(aRecon, TopAbs_EDGE), countSubShapes(aCyl, TopAbs_EDGE)); + EXPECT_EQ(countSubShapes(aRecon, TopAbs_VERTEX), countSubShapes(aCyl, TopAbs_VERTEX)); +} + +// ============================================================ +// Reverse index consistency +// ============================================================ + +TEST(BRepGraphIncTest, Box_ReverseIndex_EdgesToWires) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + BRepGraphInc_Storage aStorage; + BRepGraphInc_Populate::Perform(aStorage, aBox, false); + ASSERT_TRUE(aStorage.GetIsDone()); + + // Every edge must appear in at least one wire. + const int aNbEdges = aStorage.NbEdges(); + for (BRepGraph_EdgeId anEdgeId(0); anEdgeId.IsValid(aNbEdges); ++anEdgeId) + { + const NCollection_Vector* aWires = + aStorage.ReverseIndex().WiresOfEdge(anEdgeId); + EXPECT_TRUE(aWires != nullptr) << "Edge " << anEdgeId.Index << " not in any wire"; + if (aWires != nullptr) + { + EXPECT_GE(aWires->Length(), 1); + } + } +} + +TEST(BRepGraphIncTest, Box_ReverseIndex_EdgesToFaces) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + BRepGraphInc_Storage aStorage; + BRepGraphInc_Populate::Perform(aStorage, aBox, false); + ASSERT_TRUE(aStorage.GetIsDone()); + + // Every edge must appear in at least one face (via EdgeFaceGeom). + const int aNbEdges = aStorage.NbEdges(); + for (BRepGraph_EdgeId anEdgeId(0); anEdgeId.IsValid(aNbEdges); ++anEdgeId) + { + if (aStorage.Edge(anEdgeId).IsDegenerate) + continue; + const NCollection_Vector* aFaces = + aStorage.ReverseIndex().FacesOfEdge(anEdgeId); + EXPECT_TRUE(aFaces != nullptr) << "Edge " << anEdgeId.Index << " not in any face"; + if (aFaces != nullptr) + { + EXPECT_GE(aFaces->Length(), 1); + } + } +} + +// ============================================================ +// Parallel population produces same results +// ============================================================ + +TEST(BRepGraphIncTest, Box_ParallelPopulate_SameEntityCounts) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + BRepGraphInc_Storage aSerial; + BRepGraphInc_Populate::Perform(aSerial, aBox, false); + ASSERT_TRUE(aSerial.GetIsDone()); + + BRepGraphInc_Storage aParallel; + BRepGraphInc_Populate::Perform(aParallel, aBox, true); + ASSERT_TRUE(aParallel.GetIsDone()); + + EXPECT_EQ(aParallel.NbVertices(), aSerial.NbVertices()); + EXPECT_EQ(aParallel.NbEdges(), aSerial.NbEdges()); + EXPECT_EQ(aParallel.NbWires(), aSerial.NbWires()); + EXPECT_EQ(aParallel.NbFaces(), aSerial.NbFaces()); + EXPECT_EQ(aParallel.NbShells(), aSerial.NbShells()); + EXPECT_EQ(aParallel.NbSolids(), aSerial.NbSolids()); +} + +// ============================================================ +// Null shape handling +// ============================================================ + +TEST(BRepGraphIncTest, NullShape_NoEntities) +{ + BRepGraphInc_Storage aStorage; + BRepGraphInc_Populate::Perform(aStorage, TopoDS_Shape(), false); + EXPECT_FALSE(aStorage.GetIsDone()); + EXPECT_EQ(aStorage.NbVertices(), 0); + EXPECT_EQ(aStorage.NbEdges(), 0); +} + +// ============================================================ +// Compound shape handling +// ============================================================ + +TEST(BRepGraphIncTest, Compound_RoundTrip_SubShapeCounts) +{ + BRep_Builder aBB; + TopoDS_Compound aCompound; + aBB.MakeCompound(aCompound); + + BRepPrimAPI_MakeBox aBoxMaker1(10.0, 10.0, 10.0); + aBB.Add(aCompound, aBoxMaker1.Shape()); + + BRepPrimAPI_MakeBox aBoxMaker2(5.0, 5.0, 5.0); + aBB.Add(aCompound, aBoxMaker2.Shape()); + + BRepGraphInc_Storage aStorage; + BRepGraphInc_Populate::Perform(aStorage, aCompound, false); + ASSERT_TRUE(aStorage.GetIsDone()); + + // Two solids, two shells. + EXPECT_EQ(aStorage.NbSolids(), 2); + EXPECT_EQ(aStorage.NbShells(), 2); + EXPECT_EQ(aStorage.NbCompounds(), 1); + EXPECT_EQ(aStorage.NbFaces(), 12); + + // Round-trip reconstruct via compound. + TopoDS_Shape aRecon = BRepGraphInc_Reconstruct::Node(aStorage, BRepGraph_CompoundId(0)); + ASSERT_FALSE(aRecon.IsNull()); + EXPECT_EQ(countSubShapes(aRecon, TopAbs_SOLID), 2); + EXPECT_EQ(countSubShapes(aRecon, TopAbs_FACE), 12); +} + +// ============================================================ +// CoEdge count consistency (PCurve data lives on CoEdgeDef) +// ============================================================ + +TEST(BRepGraphIncTest, Box_CoEdgeCount) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + BRepGraphInc_Storage aStorage; + BRepGraphInc_Populate::Perform(aStorage, aBox, false); + ASSERT_TRUE(aStorage.GetIsDone()); + + // A box has 12 edges, each shared by 2 faces => 24 CoEdge entries total. + // (No seam edges on a box.) + int aCoEdgeCount = 0; + const int aNbEdges = aStorage.NbEdges(); + for (BRepGraph_EdgeId anEdgeId(0); anEdgeId.IsValid(aNbEdges); ++anEdgeId) + { + const NCollection_Vector* aCoEdgeIdxs = + aStorage.ReverseIndex().CoEdgesOfEdge(anEdgeId); + if (aCoEdgeIdxs != nullptr) + aCoEdgeCount += aCoEdgeIdxs->Length(); + } + EXPECT_EQ(aCoEdgeCount, 24); +} + +TEST(BRepGraphIncTest, Cylinder_HasSeamEdges) +{ + BRepPrimAPI_MakeCylinder aCylMaker(5.0, 15.0); + const TopoDS_Shape& aCyl = aCylMaker.Shape(); + + BRepGraphInc_Storage aStorage; + BRepGraphInc_Populate::Perform(aStorage, aCyl, false); + ASSERT_TRUE(aStorage.GetIsDone()); + + // A cylinder has seam edges: coedges with SeamPairId valid. + int aSeamPairCount = 0; + const int aNbEdges = aStorage.NbEdges(); + for (BRepGraph_EdgeId anEdgeId(0); anEdgeId.IsValid(aNbEdges); ++anEdgeId) + { + const NCollection_Vector* aCoEdgeIdxs = + aStorage.ReverseIndex().CoEdgesOfEdge(anEdgeId); + if (aCoEdgeIdxs == nullptr) + continue; + for (const BRepGraph_CoEdgeId& aCoEdgeId : *aCoEdgeIdxs) + { + const BRepGraphInc::CoEdgeDef& aCE = aStorage.CoEdge(aCoEdgeId); + if (aCE.Sense == TopAbs_FORWARD && aCE.SeamPairId.IsValid()) + { + ++aSeamPairCount; + } + } + } + // A cylinder has 1 seam edge on its lateral face. + EXPECT_GE(aSeamPairCount, 1) << "Cylinder should have at least 1 seam edge pair"; +} + +TEST(BRepGraphIncTest, Cylinder_SeamEdge_ReverseIndex_NoDuplicateFace) +{ + BRepPrimAPI_MakeCylinder aCylMaker(5.0, 15.0); + const TopoDS_Shape& aCyl = aCylMaker.Shape(); + + BRepGraphInc_Storage aStorage; + BRepGraphInc_Populate::Perform(aStorage, aCyl, false); + ASSERT_TRUE(aStorage.GetIsDone()); + + // For seam edges (two PCurve entries with the same FaceDefId but opposite + // orientations), the reverse index must contain each face only once. + const int aNbEdges = aStorage.NbEdges(); + for (BRepGraph_EdgeId anEdgeId(0); anEdgeId.IsValid(aNbEdges); ++anEdgeId) + { + const NCollection_Vector* aFaces = + aStorage.ReverseIndex().FacesOfEdge(anEdgeId); + if (aFaces == nullptr) + continue; + for (int i = 0; i < aFaces->Length(); ++i) + { + for (int j = i + 1; j < aFaces->Length(); ++j) + { + EXPECT_NE(aFaces->Value(i), aFaces->Value(j)) + << "Duplicate face " << aFaces->Value(i).Index << " in FacesOfEdge(" << anEdgeId.Index + << ")"; + } + } + } +} + +// ============================================================ +// Degenerate edge handling +// ============================================================ + +TEST(BRepGraphIncTest, Sphere_DegenerateEdges_Preserved) +{ + BRepPrimAPI_MakeSphere aSphMaker(8.0); + const TopoDS_Shape& aSph = aSphMaker.Shape(); + + BRepGraphInc_Storage aStorage; + BRepGraphInc_Populate::Perform(aStorage, aSph, false); + ASSERT_TRUE(aStorage.GetIsDone()); + + // A sphere has degenerate edges at the poles (no 3D curve, collapsed to a point). + int aDegenerateCount = 0; + const int aNbEdges = aStorage.NbEdges(); + for (BRepGraph_EdgeId anEdgeId(0); anEdgeId.IsValid(aNbEdges); ++anEdgeId) + { + const BRepGraphInc::EdgeDef& anEdge = aStorage.Edge(anEdgeId); + if (anEdge.IsDegenerate) + { + ++aDegenerateCount; + EXPECT_FALSE(anEdge.Curve3DRepId.IsValid()) + << "Degenerate edge " << anEdgeId.Index << " should have no 3D curve"; + } + } + EXPECT_GE(aDegenerateCount, 2) << "Sphere should have at least 2 degenerate edges (poles)"; + + // Round-trip: reconstructed sphere area must still match. + TopoDS_Shape aRecon = BRepGraphInc_Reconstruct::Node(aStorage, BRepGraph_SolidId(0)); + ASSERT_FALSE(aRecon.IsNull()); + + const double anOrigArea = computeArea(aSph); + const double aReconArea = computeArea(aRecon); + EXPECT_NEAR(aReconArea, anOrigArea, Precision::Confusion()); +} + +// ============================================================ +// Location handling: compound with translated children +// ============================================================ + +TEST(BRepGraphIncTest, Compound_TranslatedChildren_VolumePreserved) +{ + BRep_Builder aBB; + TopoDS_Compound aCompound; + aBB.MakeCompound(aCompound); + + // Box at origin. + BRepPrimAPI_MakeBox aBoxMaker1(10.0, 10.0, 10.0); + aBB.Add(aCompound, aBoxMaker1.Shape()); + + // Box translated by (100, 0, 0). + gp_Trsf aTrsf; + aTrsf.SetTranslation(gp_Vec(100.0, 0.0, 0.0)); + BRepPrimAPI_MakeBox aBoxMaker2(10.0, 10.0, 10.0); + TopoDS_Shape aTranslatedBox = BRepBuilderAPI_Transform(aBoxMaker2.Shape(), aTrsf).Shape(); + aBB.Add(aCompound, aTranslatedBox); + + const double anOrigVol = computeVolume(aCompound); + EXPECT_NEAR(anOrigVol, 2000.0, Precision::Confusion()); + + BRepGraphInc_Storage aStorage; + BRepGraphInc_Populate::Perform(aStorage, aCompound, false); + ASSERT_TRUE(aStorage.GetIsDone()); + + TopoDS_Shape aRecon = BRepGraphInc_Reconstruct::Node(aStorage, BRepGraph_CompoundId(0)); + ASSERT_FALSE(aRecon.IsNull()); + + const double aReconVol = computeVolume(aRecon); + EXPECT_NEAR(aReconVol, anOrigVol, Precision::Confusion()); + EXPECT_EQ(countSubShapes(aRecon, TopAbs_SOLID), 2); + EXPECT_EQ(countSubShapes(aRecon, TopAbs_FACE), 12); +} + +TEST(BRepGraphIncTest, Cylinder_RoundTrip_BRepDump) +{ + BRepPrimAPI_MakeCylinder aCylMaker(5.0, 20.0); + const TopoDS_Shape& aCyl = aCylMaker.Shape(); + + BRepGraphInc_Storage aStorage; + BRepGraphInc_Populate::Perform(aStorage, aCyl, false); + ASSERT_TRUE(aStorage.GetIsDone()); + + TopoDS_Shape aRecon = BRepGraphInc_Reconstruct::Node(aStorage, BRepGraph_SolidId(0)); + ASSERT_FALSE(aRecon.IsNull()); + + // Dump both shapes and compare + std::ostringstream anOrigStream, aReconStream; + BRepTools::Write(aCyl, anOrigStream); + BRepTools::Write(aRecon, aReconStream); + + if (anOrigStream.str() != aReconStream.str()) + { + // Show ALL line differences + std::istringstream anOrigLines(anOrigStream.str()); + std::istringstream aReconLines(aReconStream.str()); + std::string anOrigLine, aReconLine; + int aLineNo = 0; + int aDiffCount = 0; + while (std::getline(anOrigLines, anOrigLine) && std::getline(aReconLines, aReconLine)) + { + ++aLineNo; + if (anOrigLine != aReconLine) + { + std::cout << "BRep diff at line " << aLineNo << ":" << std::endl; + std::cout << " ORIG: " << anOrigLine << std::endl; + std::cout << " RECON: " << aReconLine << std::endl; + ++aDiffCount; + } + } + std::cout << "Total " << aDiffCount << " line differences. " + << "Orig size=" << anOrigStream.str().size() + << " Recon size=" << aReconStream.str().size() << std::endl; + } + else + { + std::cout << "BRep dumps are IDENTICAL (size=" << anOrigStream.str().size() << ")" << std::endl; + } + + const double anOrigArea = computeArea(aCyl); + const double aReconArea = computeArea(aRecon); + std::cout << "Orig area=" << anOrigArea << " Recon area=" << aReconArea << std::endl; + EXPECT_NEAR(aReconArea, anOrigArea, Precision::Confusion()); +} + +// ============================================================ +// Edge internal vertices +// ============================================================ + +// Helper: create an edge with a line segment and add vertices with given orientations. +static TopoDS_Edge makeEdgeWithInternalVertex() +{ + BRep_Builder aBB; + BRepBuilderAPI_MakeEdge aMakeEdge(gp_Pnt(0, 0, 0), gp_Pnt(10, 0, 0)); + TopoDS_Edge anEdge = aMakeEdge.Edge(); + + TopoDS_Vertex anIntVtx; + aBB.MakeVertex(anIntVtx, gp_Pnt(5, 0, 0), Precision::Confusion()); + aBB.Add(anEdge, anIntVtx.Oriented(TopAbs_INTERNAL)); + return anEdge; +} + +// Helper: build a face containing the given edge (needed for graph population). +static TopoDS_Shape wrapEdgeInFace(const TopoDS_Edge& theEdge) +{ + occ::handle aPlane = new Geom_Plane(gp_Pln()); + BRep_Builder aBB; + TopoDS_Face aFace; + aBB.MakeFace(aFace, aPlane, Precision::Confusion()); + TopoDS_Wire aWire; + aBB.MakeWire(aWire); + aBB.Add(aWire, theEdge); + aBB.Add(aFace, aWire); + return aFace; +} + +TEST(BRepGraphIncTest, EdgeInternalVertex_Captured) +{ + TopoDS_Edge anEdge = makeEdgeWithInternalVertex(); + TopoDS_Shape aFace = wrapEdgeInFace(anEdge); + + BRepGraphInc_Storage aStorage; + BRepGraphInc_Populate::Perform(aStorage, aFace, false); + ASSERT_TRUE(aStorage.GetIsDone()); + ASSERT_GE(aStorage.NbEdges(), 1); + + // Find the edge entity and check InternalVertices. + bool aFound = false; + const int aNbEdges = aStorage.NbEdges(); + for (BRepGraph_EdgeId anEdgeId(0); anEdgeId.IsValid(aNbEdges); ++anEdgeId) + { + const BRepGraphInc::EdgeDef& anEdgeEnt = aStorage.Edge(anEdgeId); + if (anEdgeEnt.InternalVertexRefIds.Length() == 1) + { + aFound = true; + const BRepGraphInc::VertexRef& aIntVRef = + aStorage.VertexRef(anEdgeEnt.InternalVertexRefIds.Value(0)); + EXPECT_GE(aIntVRef.VertexDefId.Index, 0); + EXPECT_EQ(aIntVRef.Orientation, TopAbs_INTERNAL); + // Verify the vertex point. + const BRepGraph_VertexId aVtxId = aIntVRef.VertexDefId; + const BRepGraphInc::VertexDef& aVtxEnt = aStorage.Vertex(aVtxId); + EXPECT_NEAR(aVtxEnt.Point.X(), 5.0, Precision::Confusion()); + break; + } + } + EXPECT_TRUE(aFound) << "No edge with InternalVertices found"; +} + +TEST(BRepGraphIncTest, EdgeInternalVertex_RoundTrip) +{ + TopoDS_Edge anEdge = makeEdgeWithInternalVertex(); + TopoDS_Shape aFace = wrapEdgeInFace(anEdge); + + BRepGraphInc_Storage aStorage; + BRepGraphInc_Populate::Perform(aStorage, aFace, false); + ASSERT_TRUE(aStorage.GetIsDone()); + + TopoDS_Shape aRecon = BRepGraphInc_Reconstruct::Node(aStorage, BRepGraph_FaceId(0)); + ASSERT_FALSE(aRecon.IsNull()); + + // Find the edge in the reconstructed face and verify internal vertex. + bool aFoundInternal = false; + for (TopExp_Explorer anEdgeExp(aRecon, TopAbs_EDGE); anEdgeExp.More(); anEdgeExp.Next()) + { + const TopoDS_Edge& aReconEdge = TopoDS::Edge(anEdgeExp.Current()); + for (TopoDS_Iterator aVIt(aReconEdge, false); aVIt.More(); aVIt.Next()) + { + if (aVIt.Value().ShapeType() == TopAbs_VERTEX + && aVIt.Value().Orientation() == TopAbs_INTERNAL) + { + aFoundInternal = true; + const TopoDS_Vertex& aVtx = TopoDS::Vertex(aVIt.Value()); + EXPECT_NEAR(BRep_Tool::Pnt(aVtx).X(), 5.0, Precision::Confusion()); + } + } + } + EXPECT_TRUE(aFoundInternal) << "Internal vertex not found in reconstructed edge"; +} + +TEST(BRepGraphIncTest, EdgeExternalVertex_Captured) +{ + BRep_Builder aBB; + BRepBuilderAPI_MakeEdge aMakeEdge(gp_Pnt(0, 0, 0), gp_Pnt(10, 0, 0)); + TopoDS_Edge anEdge = aMakeEdge.Edge(); + + TopoDS_Vertex anExtVtx; + aBB.MakeVertex(anExtVtx, gp_Pnt(7, 0, 0), Precision::Confusion()); + aBB.Add(anEdge, anExtVtx.Oriented(TopAbs_EXTERNAL)); + + TopoDS_Shape aFace = wrapEdgeInFace(anEdge); + + BRepGraphInc_Storage aStorage; + BRepGraphInc_Populate::Perform(aStorage, aFace, false); + ASSERT_TRUE(aStorage.GetIsDone()); + + bool aFound = false; + const int aNbEdges = aStorage.NbEdges(); + for (BRepGraph_EdgeId anEdgeId(0); anEdgeId.IsValid(aNbEdges); ++anEdgeId) + { + const BRepGraphInc::EdgeDef& anEdgeEnt = aStorage.Edge(anEdgeId); + if (anEdgeEnt.InternalVertexRefIds.Length() == 1) + { + aFound = true; + EXPECT_EQ(aStorage.VertexRef(anEdgeEnt.InternalVertexRefIds.Value(0)).Orientation, + TopAbs_EXTERNAL); + break; + } + } + EXPECT_TRUE(aFound) << "No edge with EXTERNAL vertex found"; +} + +TEST(BRepGraphIncTest, EdgeNoInternalVertices_EmptyVector) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + BRepGraphInc_Storage aStorage; + BRepGraphInc_Populate::Perform(aStorage, aBox, false); + ASSERT_TRUE(aStorage.GetIsDone()); + + const int aNbEdges = aStorage.NbEdges(); + for (BRepGraph_EdgeId anEdgeId(0); anEdgeId.IsValid(aNbEdges); ++anEdgeId) + { + EXPECT_EQ(aStorage.Edge(anEdgeId).InternalVertexRefIds.Length(), 0) + << "Edge " << anEdgeId.Index << " should have no internal vertices"; + } +} + +TEST(BRepGraphIncTest, EdgeMultipleInternalVertices_AllCaptured) +{ + BRep_Builder aBB; + BRepBuilderAPI_MakeEdge aMakeEdge(gp_Pnt(0, 0, 0), gp_Pnt(10, 0, 0)); + TopoDS_Edge anEdge = aMakeEdge.Edge(); + + TopoDS_Vertex aVtx1, aVtx2; + aBB.MakeVertex(aVtx1, gp_Pnt(3, 0, 0), Precision::Confusion()); + aBB.MakeVertex(aVtx2, gp_Pnt(7, 0, 0), Precision::Confusion()); + aBB.Add(anEdge, aVtx1.Oriented(TopAbs_INTERNAL)); + aBB.Add(anEdge, aVtx2.Oriented(TopAbs_EXTERNAL)); + + TopoDS_Shape aFace = wrapEdgeInFace(anEdge); + + BRepGraphInc_Storage aStorage; + BRepGraphInc_Populate::Perform(aStorage, aFace, false); + ASSERT_TRUE(aStorage.GetIsDone()); + + bool aFound = false; + const int aNbEdges = aStorage.NbEdges(); + for (BRepGraph_EdgeId anEdgeId(0); anEdgeId.IsValid(aNbEdges); ++anEdgeId) + { + const BRepGraphInc::EdgeDef& anEdgeEnt = aStorage.Edge(anEdgeId); + if (anEdgeEnt.InternalVertexRefIds.Length() == 2) + { + aFound = true; + // Check both orientations are preserved. + bool aHasInternal = false, aHasExternal = false; + for (int j = 0; j < 2; ++j) + { + if (aStorage.VertexRef(anEdgeEnt.InternalVertexRefIds.Value(j)).Orientation + == TopAbs_INTERNAL) + aHasInternal = true; + if (aStorage.VertexRef(anEdgeEnt.InternalVertexRefIds.Value(j)).Orientation + == TopAbs_EXTERNAL) + aHasExternal = true; + } + EXPECT_TRUE(aHasInternal); + EXPECT_TRUE(aHasExternal); + break; + } + } + EXPECT_TRUE(aFound) << "No edge with 2 internal vertices found"; +} + +// ============================================================ +// Face direct vertices +// ============================================================ + +TEST(BRepGraphIncTest, FaceDirectVertex_Internal_Captured) +{ + BRep_Builder aBB; + occ::handle aPlane = new Geom_Plane(gp_Pln()); + TopoDS_Face aFace; + aBB.MakeFace(aFace, aPlane, Precision::Confusion()); + + // Add a wire so the face is valid for population. + BRepBuilderAPI_MakeEdge aME(gp_Pnt(0, 0, 0), gp_Pnt(10, 0, 0)); + TopoDS_Wire aWire; + aBB.MakeWire(aWire); + aBB.Add(aWire, aME.Edge()); + aBB.Add(aFace, aWire); + + // Add a direct vertex child with INTERNAL orientation. + TopoDS_Vertex aVtx; + aBB.MakeVertex(aVtx, gp_Pnt(5, 5, 0), Precision::Confusion()); + aBB.Add(aFace, aVtx.Oriented(TopAbs_INTERNAL)); + + BRepGraphInc_Storage aStorage; + BRepGraphInc_Populate::Perform(aStorage, aFace, false); + ASSERT_TRUE(aStorage.GetIsDone()); + ASSERT_GE(aStorage.NbFaces(), 1); + + const BRepGraphInc::FaceDef& aFaceEnt = aStorage.Face(BRepGraph_FaceId(0)); + EXPECT_EQ(aFaceEnt.VertexRefIds.Length(), 1); + if (aFaceEnt.VertexRefIds.Length() == 1) + { + const BRepGraphInc::VertexRef& aFaceVRef = aStorage.VertexRef(aFaceEnt.VertexRefIds.Value(0)); + EXPECT_GE(aFaceVRef.VertexDefId.Index, 0); + EXPECT_EQ(aFaceVRef.Orientation, TopAbs_INTERNAL); + } +} + +TEST(BRepGraphIncTest, FaceDirectVertex_RoundTrip) +{ + BRep_Builder aBB; + occ::handle aPlane = new Geom_Plane(gp_Pln()); + TopoDS_Face aFace; + aBB.MakeFace(aFace, aPlane, Precision::Confusion()); + + BRepBuilderAPI_MakeEdge aME(gp_Pnt(0, 0, 0), gp_Pnt(10, 0, 0)); + TopoDS_Wire aWire; + aBB.MakeWire(aWire); + aBB.Add(aWire, aME.Edge()); + aBB.Add(aFace, aWire); + + TopoDS_Vertex aVtx; + aBB.MakeVertex(aVtx, gp_Pnt(5, 5, 0), Precision::Confusion()); + aBB.Add(aFace, aVtx.Oriented(TopAbs_INTERNAL)); + + BRepGraphInc_Storage aStorage; + BRepGraphInc_Populate::Perform(aStorage, aFace, false); + ASSERT_TRUE(aStorage.GetIsDone()); + + TopoDS_Shape aRecon = BRepGraphInc_Reconstruct::Node(aStorage, BRepGraph_FaceId(0)); + ASSERT_FALSE(aRecon.IsNull()); + + // Verify vertex is a direct child of the face (not inside a wire). + bool aFoundDirect = false; + for (TopoDS_Iterator aIt(aRecon); aIt.More(); aIt.Next()) + { + if (aIt.Value().ShapeType() == TopAbs_VERTEX && aIt.Value().Orientation() == TopAbs_INTERNAL) + { + aFoundDirect = true; + const TopoDS_Vertex& aReconVtx = TopoDS::Vertex(aIt.Value()); + EXPECT_NEAR(BRep_Tool::Pnt(aReconVtx).X(), 5.0, Precision::Confusion()); + EXPECT_NEAR(BRep_Tool::Pnt(aReconVtx).Y(), 5.0, Precision::Confusion()); + } + } + EXPECT_TRUE(aFoundDirect) << "Direct internal vertex not found in reconstructed face"; +} + +TEST(BRepGraphIncTest, FaceExternalVertex_Captured) +{ + BRep_Builder aBB; + occ::handle aPlane = new Geom_Plane(gp_Pln()); + TopoDS_Face aFace; + aBB.MakeFace(aFace, aPlane, Precision::Confusion()); + + BRepBuilderAPI_MakeEdge aME(gp_Pnt(0, 0, 0), gp_Pnt(10, 0, 0)); + TopoDS_Wire aWire; + aBB.MakeWire(aWire); + aBB.Add(aWire, aME.Edge()); + aBB.Add(aFace, aWire); + + TopoDS_Vertex aVtx; + aBB.MakeVertex(aVtx, gp_Pnt(5, 5, 0), Precision::Confusion()); + aBB.Add(aFace, aVtx.Oriented(TopAbs_EXTERNAL)); + + BRepGraphInc_Storage aStorage; + BRepGraphInc_Populate::Perform(aStorage, aFace, false); + ASSERT_TRUE(aStorage.GetIsDone()); + ASSERT_GE(aStorage.NbFaces(), 1); + + const BRepGraphInc::FaceDef& aFaceEnt = aStorage.Face(BRepGraph_FaceId(0)); + EXPECT_EQ(aFaceEnt.VertexRefIds.Length(), 1); + if (aFaceEnt.VertexRefIds.Length() == 1) + { + EXPECT_EQ(aStorage.VertexRef(aFaceEnt.VertexRefIds.Value(0)).Orientation, TopAbs_EXTERNAL); + } +} + +TEST(BRepGraphIncTest, FaceNoDirectVertices_EmptyVector) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + BRepGraphInc_Storage aStorage; + BRepGraphInc_Populate::Perform(aStorage, aBox, false); + ASSERT_TRUE(aStorage.GetIsDone()); + + const int aNbFaces = aStorage.NbFaces(); + for (BRepGraph_FaceId aFaceId(0); aFaceId.IsValid(aNbFaces); ++aFaceId) + { + EXPECT_EQ(aStorage.Face(aFaceId).VertexRefIds.Length(), 0) + << "Face " << aFaceId.Index << " should have no direct vertex children"; + } +} + +TEST(BRepGraphIncTest, FaceWithWiresAndVertices_BothCaptured) +{ + BRep_Builder aBB; + occ::handle aPlane = new Geom_Plane(gp_Pln()); + TopoDS_Face aFace; + aBB.MakeFace(aFace, aPlane, Precision::Confusion()); + + BRepBuilderAPI_MakeEdge aME(gp_Pnt(0, 0, 0), gp_Pnt(10, 0, 0)); + TopoDS_Wire aWire; + aBB.MakeWire(aWire); + aBB.Add(aWire, aME.Edge()); + aBB.Add(aFace, aWire); + + TopoDS_Vertex aVtx; + aBB.MakeVertex(aVtx, gp_Pnt(5, 5, 0), Precision::Confusion()); + aBB.Add(aFace, aVtx.Oriented(TopAbs_INTERNAL)); + + BRepGraphInc_Storage aStorage; + BRepGraphInc_Populate::Perform(aStorage, aFace, false); + ASSERT_TRUE(aStorage.GetIsDone()); + ASSERT_GE(aStorage.NbFaces(), 1); + + const BRepGraphInc::FaceDef& aFaceEnt = aStorage.Face(BRepGraph_FaceId(0)); + EXPECT_GE(BRepGraph_TestTools::CountWireRefsOfFace(aStorage, BRepGraph_FaceId(0)), 1); + EXPECT_EQ(aFaceEnt.VertexRefIds.Length(), 1); +} + +// ============================================================ +// Integration: round-trip with internal vertices +// ============================================================ + +TEST(BRepGraphIncTest, CompoundWithInternalVertices_RoundTrip_SubShapeCounts) +{ + BRep_Builder aBB; + TopoDS_Compound aCompound; + aBB.MakeCompound(aCompound); + + // Face with a direct internal vertex. + occ::handle aPlane = new Geom_Plane(gp_Pln()); + TopoDS_Face aFace; + aBB.MakeFace(aFace, aPlane, Precision::Confusion()); + + BRepBuilderAPI_MakeEdge aME(gp_Pnt(0, 0, 0), gp_Pnt(10, 0, 0)); + TopoDS_Edge anEdge = aME.Edge(); + // Add internal vertex to the edge. + TopoDS_Vertex anEdgeIntVtx; + aBB.MakeVertex(anEdgeIntVtx, gp_Pnt(5, 0, 0), Precision::Confusion()); + aBB.Add(anEdge, anEdgeIntVtx.Oriented(TopAbs_INTERNAL)); + + TopoDS_Wire aWire; + aBB.MakeWire(aWire); + aBB.Add(aWire, anEdge); + aBB.Add(aFace, aWire); + + // Add direct vertex to the face. + TopoDS_Vertex aFaceIntVtx; + aBB.MakeVertex(aFaceIntVtx, gp_Pnt(5, 5, 0), Precision::Confusion()); + aBB.Add(aFace, aFaceIntVtx.Oriented(TopAbs_INTERNAL)); + + aBB.Add(aCompound, aFace); + + // Count original sub-shapes. + int anOrigFaces = countSubShapes(aCompound, TopAbs_FACE); + int anOrigEdges = countSubShapes(aCompound, TopAbs_EDGE); + int anOrigVertices = countSubShapes(aCompound, TopAbs_VERTEX); + + BRepGraphInc_Storage aStorage; + BRepGraphInc_Populate::Perform(aStorage, aCompound, false); + ASSERT_TRUE(aStorage.GetIsDone()); + + TopoDS_Shape aRecon = BRepGraphInc_Reconstruct::Node(aStorage, BRepGraph_CompoundId(0)); + ASSERT_FALSE(aRecon.IsNull()); + + EXPECT_EQ(countSubShapes(aRecon, TopAbs_FACE), anOrigFaces); + EXPECT_EQ(countSubShapes(aRecon, TopAbs_EDGE), anOrigEdges); + EXPECT_EQ(countSubShapes(aRecon, TopAbs_VERTEX), anOrigVertices); + + // Verify orientations of reconstructed internal vertices. + // Edge internal vertex: INTERNAL orientation on edge child. + bool aFoundEdgeIntVtx = false; + for (TopExp_Explorer anEdgeExp(aRecon, TopAbs_EDGE); anEdgeExp.More(); anEdgeExp.Next()) + { + for (TopoDS_Iterator aVIt(anEdgeExp.Current(), false); aVIt.More(); aVIt.Next()) + { + if (aVIt.Value().ShapeType() == TopAbs_VERTEX + && aVIt.Value().Orientation() == TopAbs_INTERNAL) + { + aFoundEdgeIntVtx = true; + const TopoDS_Vertex& aVtx = TopoDS::Vertex(aVIt.Value()); + EXPECT_NEAR(BRep_Tool::Pnt(aVtx).X(), 5.0, Precision::Confusion()); + EXPECT_NEAR(BRep_Tool::Pnt(aVtx).Y(), 0.0, Precision::Confusion()); + } + } + } + EXPECT_TRUE(aFoundEdgeIntVtx) << "INTERNAL vertex on edge not found after round-trip"; + + // Face direct vertex: INTERNAL orientation as direct child of face. + bool aFoundFaceIntVtx = false; + for (TopExp_Explorer aFaceExp(aRecon, TopAbs_FACE); aFaceExp.More(); aFaceExp.Next()) + { + for (TopoDS_Iterator aFIt(aFaceExp.Current()); aFIt.More(); aFIt.Next()) + { + if (aFIt.Value().ShapeType() == TopAbs_VERTEX + && aFIt.Value().Orientation() == TopAbs_INTERNAL) + { + aFoundFaceIntVtx = true; + const TopoDS_Vertex& aVtx = TopoDS::Vertex(aFIt.Value()); + EXPECT_NEAR(BRep_Tool::Pnt(aVtx).X(), 5.0, Precision::Confusion()); + EXPECT_NEAR(BRep_Tool::Pnt(aVtx).Y(), 5.0, Precision::Confusion()); + } + } + } + EXPECT_TRUE(aFoundFaceIntVtx) << "INTERNAL vertex on face not found after round-trip"; +} + +TEST(BRepGraphIncTest, ParallelBuild_InternalVertices_SameAsSequential) +{ + BRep_Builder aBB; + occ::handle aPlane = new Geom_Plane(gp_Pln()); + TopoDS_Face aFace; + aBB.MakeFace(aFace, aPlane, Precision::Confusion()); + + BRepBuilderAPI_MakeEdge aME(gp_Pnt(0, 0, 0), gp_Pnt(10, 0, 0)); + TopoDS_Edge anEdge = aME.Edge(); + TopoDS_Vertex anIntVtx; + aBB.MakeVertex(anIntVtx, gp_Pnt(5, 0, 0), Precision::Confusion()); + aBB.Add(anEdge, anIntVtx.Oriented(TopAbs_INTERNAL)); + + TopoDS_Wire aWire; + aBB.MakeWire(aWire); + aBB.Add(aWire, anEdge); + aBB.Add(aFace, aWire); + + TopoDS_Vertex aFaceVtx; + aBB.MakeVertex(aFaceVtx, gp_Pnt(5, 5, 0), Precision::Confusion()); + aBB.Add(aFace, aFaceVtx.Oriented(TopAbs_INTERNAL)); + + BRepGraphInc_Storage aSerial; + BRepGraphInc_Populate::Perform(aSerial, aFace, false); + ASSERT_TRUE(aSerial.GetIsDone()); + + BRepGraphInc_Storage aParallel; + BRepGraphInc_Populate::Perform(aParallel, aFace, true); + ASSERT_TRUE(aParallel.GetIsDone()); + + EXPECT_EQ(aParallel.NbVertices(), aSerial.NbVertices()); + EXPECT_EQ(aParallel.NbEdges(), aSerial.NbEdges()); + EXPECT_EQ(aParallel.NbFaces(), aSerial.NbFaces()); + + // Check internal vertex counts match. + const int aNbEdges = aSerial.NbEdges(); + for (BRepGraph_EdgeId anEdgeId(0); anEdgeId.IsValid(aNbEdges); ++anEdgeId) + { + EXPECT_EQ(aParallel.Edge(anEdgeId).InternalVertexRefIds.Length(), + aSerial.Edge(anEdgeId).InternalVertexRefIds.Length()) + << "Edge " << anEdgeId.Index << " internal vertex count mismatch"; + } + const int aNbFaces = aSerial.NbFaces(); + for (BRepGraph_FaceId aFaceId(0); aFaceId.IsValid(aNbFaces); ++aFaceId) + { + EXPECT_EQ(aParallel.Face(aFaceId).VertexRefIds.Length(), + aSerial.Face(aFaceId).VertexRefIds.Length()) + << "Face " << aFaceId.Index << " direct vertex count mismatch"; + } +} diff --git a/src/ModelingData/TKBRep/GTests/BRepGraph_Assembly_Test.cxx b/src/ModelingData/TKBRep/GTests/BRepGraph_Assembly_Test.cxx new file mode 100644 index 0000000000..9742d142d6 --- /dev/null +++ b/src/ModelingData/TKBRep/GTests/BRepGraph_Assembly_Test.cxx @@ -0,0 +1,978 @@ +// Copyright (c) 2026 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 + +namespace +{ +static double translationX(const TopoDS_Shape& theShape) +{ + return theShape.Location().Transformation().TranslationPart().X(); +} +} // namespace + +// ============================================================================= +// Build_SingleSolid_AutoCreatesRootProduct +// ============================================================================= + +TEST(BRepGraph_AssemblyTest, Build_SingleSolid_AutoCreatesRootProduct) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 20.0, 30.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + EXPECT_EQ(aGraph.Topo().Products().Nb(), 1); + EXPECT_EQ(aGraph.Topo().Occurrences().Nb(), 0); + + const BRepGraphInc::ProductDef& aProduct = + aGraph.Topo().Products().Definition(BRepGraph_ProductId(0)); + EXPECT_TRUE(aProduct.ShapeRootId.IsValid()); + EXPECT_EQ(aProduct.Id.NodeKind, BRepGraph_NodeId::Kind::Product); + EXPECT_EQ(aProduct.Id.Index, 0); + + // The root product should be a part (has topology root). + EXPECT_TRUE(aGraph.Topo().Products().IsPart(BRepGraph_ProductId(0))); + EXPECT_FALSE(aGraph.Topo().Products().IsAssembly(BRepGraph_ProductId(0))); +} + +// ============================================================================= +// Build_Compound_AutoCreatesRootProduct +// ============================================================================= + +TEST(BRepGraph_AssemblyTest, Build_Compound_AutoCreatesRootProduct) +{ + TopoDS_Compound aCompound; + BRep_Builder aBB; + aBB.MakeCompound(aCompound); + aBB.Add(aCompound, BRepPrimAPI_MakeBox(10.0, 10.0, 10.0).Shape()); + aBB.Add(aCompound, BRepPrimAPI_MakeSphere(5.0).Shape()); + + BRepGraph aGraph; + aGraph.Build(aCompound); + ASSERT_TRUE(aGraph.IsDone()); + + EXPECT_EQ(aGraph.Topo().Products().Nb(), 1); + EXPECT_EQ(aGraph.Topo().Occurrences().Nb(), 0); + + const BRepGraphInc::ProductDef& aProduct = + aGraph.Topo().Products().Definition(BRepGraph_ProductId(0)); + EXPECT_TRUE(aProduct.ShapeRootId.IsValid()); + EXPECT_EQ(aProduct.ShapeRootId.NodeKind, BRepGraph_NodeId::Kind::Compound); +} + +// ============================================================================= +// AddProduct_IsPart +// ============================================================================= + +TEST(BRepGraph_AssemblyTest, AddProduct_IsPart) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 10.0, 10.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + // Add a second part product. + const BRepGraph_NodeId aShapeRoot = BRepGraph_SolidId(0); + const BRepGraph_ProductId aProductId = aGraph.Builder().AddProduct(aShapeRoot); + + EXPECT_TRUE(aProductId.IsValid()); + EXPECT_TRUE(aGraph.Topo().Products().IsPart(aProductId)); + EXPECT_FALSE(aGraph.Topo().Products().IsAssembly(aProductId)); +} + +TEST(BRepGraph_AssemblyTest, AddProduct_InvalidShapeRoot_ReturnsInvalid) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 10.0, 10.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + EXPECT_FALSE(aGraph.Builder().AddProduct(BRepGraph_ProductId(0)).IsValid()); + + aGraph.Builder().RemoveNode(BRepGraph_SolidId(0)); + EXPECT_FALSE(aGraph.Builder().AddProduct(BRepGraph_SolidId(0)).IsValid()); +} + +// ============================================================================= +// AddAssemblyProduct_IsAssembly +// ============================================================================= + +TEST(BRepGraph_AssemblyTest, AddAssemblyProduct_IsAssembly) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 10.0, 10.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + const BRepGraph_ProductId aAssemblyId = aGraph.Builder().AddAssemblyProduct(); + + EXPECT_TRUE(aAssemblyId.IsValid()); + EXPECT_TRUE(aGraph.Topo().Products().IsAssembly(aAssemblyId)); + EXPECT_FALSE(aGraph.Topo().Products().IsPart(aAssemblyId)); +} + +// ============================================================================= +// AddOccurrence_LinksCorrectly +// ============================================================================= + +TEST(BRepGraph_AssemblyTest, AddOccurrence_LinksCorrectly) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 10.0, 10.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + const BRepGraph_ProductId aPartId = BRepGraph_ProductId(0); // auto-created root + const BRepGraph_ProductId aAssemblyId = aGraph.Builder().AddAssemblyProduct(); + + gp_Trsf aTrsf; + aTrsf.SetTranslation(gp_Vec(100.0, 0.0, 0.0)); + TopLoc_Location aLoc(aTrsf); + + const BRepGraph_OccurrenceId anOccId = aGraph.Builder().AddOccurrence(aAssemblyId, aPartId, aLoc); + + EXPECT_TRUE(anOccId.IsValid()); + + const BRepGraphInc::OccurrenceDef& anOcc = aGraph.Topo().Occurrences().Definition(anOccId); + EXPECT_EQ(anOcc.ProductDefId, aPartId); + EXPECT_EQ(anOcc.ParentProductDefId, aAssemblyId); + + // Check that assembly product has the occurrence in OccurrenceRefIds. + EXPECT_EQ(aGraph.Topo().Products().NbComponents(aAssemblyId), 1); + EXPECT_EQ(aGraph.Topo().Products().Component(aAssemblyId, 0), anOccId); +} + +// ============================================================================= +// DAGSharing_MultipleOccurrencesSamePart +// ============================================================================= + +TEST(BRepGraph_AssemblyTest, DAGSharing_MultipleOccurrencesSamePart) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 10.0, 10.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + const BRepGraph_ProductId aPartId = BRepGraph_ProductId(0); + const BRepGraph_ProductId aAssemblyId = aGraph.Builder().AddAssemblyProduct(); + + gp_Trsf aTrsf1; + aTrsf1.SetTranslation(gp_Vec(100.0, 0.0, 0.0)); + gp_Trsf aTrsf2; + aTrsf2.SetTranslation(gp_Vec(200.0, 0.0, 0.0)); + + const BRepGraph_OccurrenceId anOcc1 = + aGraph.Builder().AddOccurrence(aAssemblyId, aPartId, TopLoc_Location(aTrsf1)); + const BRepGraph_OccurrenceId anOcc2 = + aGraph.Builder().AddOccurrence(aAssemblyId, aPartId, TopLoc_Location(aTrsf2)); + + EXPECT_NE(anOcc1, anOcc2); + EXPECT_EQ(aGraph.Topo().Occurrences().Definition(anOcc1).ProductDefId, + aGraph.Topo().Occurrences().Definition(anOcc2).ProductDefId); + + EXPECT_EQ(aGraph.Topo().Products().NbComponents(aAssemblyId), 2); +} + +TEST(BRepGraph_AssemblyTest, AddOccurrence_ParentOccurrenceMustMatchParentProduct) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 10.0, 10.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + const BRepGraph_ProductId aPartId = BRepGraph_ProductId(0); + const BRepGraph_ProductId anAssemblyA = aGraph.Builder().AddAssemblyProduct(); + const BRepGraph_ProductId anAssemblyB = aGraph.Builder().AddAssemblyProduct(); + const BRepGraph_OccurrenceId aParentOccId = + aGraph.Builder().AddOccurrence(anAssemblyA, aPartId, TopLoc_Location()); + ASSERT_TRUE(aParentOccId.IsValid()); + + const BRepGraph_OccurrenceId anInvalidOccId = + aGraph.Builder().AddOccurrence(anAssemblyB, aPartId, TopLoc_Location(), aParentOccId); + + EXPECT_FALSE(anInvalidOccId.IsValid()); + EXPECT_EQ(aGraph.Topo().Occurrences().Nb(), 1); + EXPECT_EQ(aGraph.Topo().Products().NbComponents(anAssemblyB), 0); +} + +// ============================================================================= +// RootProducts_Query +// ============================================================================= + +TEST(BRepGraph_AssemblyTest, RootProducts_Query) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 10.0, 10.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + // Auto-created root product is the only root initially. + const occ::handle anAllocator = new NCollection_IncAllocator(); + NCollection_Vector aRoots = + aGraph.Topo().Products().RootProducts(anAllocator); + EXPECT_EQ(aRoots.Length(), 1); + EXPECT_EQ(aRoots.Value(0), BRepGraph_ProductId(0)); + + // Add an assembly and make it instantiate the part product via occurrence. + const BRepGraph_ProductId aPartId = BRepGraph_ProductId(0); + const BRepGraph_ProductId aAssemblyId = aGraph.Builder().AddAssemblyProduct(); + (void)aGraph.Builder().AddOccurrence(aAssemblyId, aPartId, TopLoc_Location()); + + // Now only the assembly (which is not referenced by any occurrence) is a root. + aRoots = aGraph.Topo().Products().RootProducts(anAllocator); + EXPECT_EQ(aRoots.Length(), 1); + EXPECT_EQ(aRoots.Value(0), aAssemblyId); +} + +// ============================================================================= +// RootProducts_ReflectsAssemblyMutation +// ============================================================================= + +TEST(BRepGraph_AssemblyTest, RootProducts_ReflectsAssemblyMutation) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 10.0, 10.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + const occ::handle anAllocator = new NCollection_IncAllocator(); + const NCollection_Vector aRootsBefore = + aGraph.Topo().Products().RootProducts(anAllocator); + ASSERT_EQ(aRootsBefore.Length(), 1); + EXPECT_EQ(aRootsBefore.Value(0), BRepGraph_ProductId(0)); + + const BRepGraph_ProductId aAssemblyId = aGraph.Builder().AddAssemblyProduct(); + (void)aGraph.Builder().AddOccurrence(aAssemblyId, BRepGraph_ProductId(0), TopLoc_Location()); + + const NCollection_Vector aRootsAfter = + aGraph.Topo().Products().RootProducts(anAllocator); + ASSERT_EQ(aRootsAfter.Length(), 1); + EXPECT_EQ(aRootsAfter.Value(0), aAssemblyId); +} + +// ============================================================================= +// RemoveOccurrence_UpdatesParent +// ============================================================================= + +TEST(BRepGraph_AssemblyTest, RemoveOccurrence_UpdatesParent) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 10.0, 10.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + const BRepGraph_ProductId aPartId = BRepGraph_ProductId(0); + const BRepGraph_ProductId aAssemblyId = aGraph.Builder().AddAssemblyProduct(); + const BRepGraph_OccurrenceId anOccId = + aGraph.Builder().AddOccurrence(aAssemblyId, aPartId, TopLoc_Location()); + + EXPECT_EQ(aGraph.Topo().Products().NbComponents(aAssemblyId), 1); + const NCollection_Vector& aBeforeRefs = + aGraph.Refs().Occurrences().IdsOf(aAssemblyId); + ASSERT_EQ(aBeforeRefs.Length(), 1); + const BRepGraph_OccurrenceRefId anOccRefId = aBeforeRefs.Value(0); + EXPECT_FALSE(aGraph.Refs().Occurrences().Entry(anOccRefId).IsRemoved); + + // Remove the occurrence - should update parent's OccurrenceRefs. + aGraph.Builder().RemoveSubgraph(anOccId); + + EXPECT_TRUE(aGraph.Topo().Gen().IsRemoved(anOccId)); + EXPECT_EQ(aGraph.Topo().Products().NbComponents(aAssemblyId), 0); + const NCollection_Vector& anAfterRefs = + aGraph.Refs().Occurrences().IdsOf(aAssemblyId); + EXPECT_EQ(anAfterRefs.Length(), 0); + EXPECT_TRUE(aGraph.Refs().Occurrences().Entry(anOccRefId).IsRemoved); +} + +// ============================================================================= +// RemoveProduct_CascadeOccurrences +// ============================================================================= + +TEST(BRepGraph_AssemblyTest, RemoveProduct_CascadeOccurrences) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 10.0, 10.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + const BRepGraph_ProductId aPartId = BRepGraph_ProductId(0); + const BRepGraph_ProductId aAssemblyId = aGraph.Builder().AddAssemblyProduct(); + const BRepGraph_OccurrenceId anOcc1 = + aGraph.Builder().AddOccurrence(aAssemblyId, aPartId, TopLoc_Location()); + const BRepGraph_OccurrenceId anOcc2 = + aGraph.Builder().AddOccurrence(aAssemblyId, aPartId, TopLoc_Location()); + + // Remove the assembly product - cascades to its child occurrences. + aGraph.Builder().RemoveSubgraph(aAssemblyId); + + EXPECT_TRUE(aGraph.Topo().Gen().IsRemoved(aAssemblyId)); + EXPECT_TRUE(aGraph.Topo().Gen().IsRemoved(anOcc1)); + EXPECT_TRUE(aGraph.Topo().Gen().IsRemoved(anOcc2)); +} + +// ============================================================================= +// RemoveProduct_CascadesToPartTopology +// ============================================================================= + +TEST(BRepGraph_AssemblyTest, RemoveProduct_CascadesToPartTopology) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 10.0, 10.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + // Part product created by Build() owns topology via ShapeRootId. + const BRepGraph_ProductId aPartId = BRepGraph_ProductId(0); + EXPECT_TRUE(aGraph.Topo().Products().IsPart(aPartId)); + + const BRepGraph_NodeId aShapeRoot = aGraph.Topo().Products().Definition(aPartId).ShapeRootId; + ASSERT_TRUE(aShapeRoot.IsValid()); + EXPECT_FALSE(aGraph.Topo().Gen().IsRemoved(aShapeRoot)); + + // Topology entities should be active before removal. + const int aNbFaces = aGraph.Topo().Faces().Nb(); + ASSERT_GT(aNbFaces, 0); + + // Remove the part product - should cascade to topology. + aGraph.Builder().RemoveSubgraph(aPartId); + + EXPECT_TRUE(aGraph.Topo().Gen().IsRemoved(aPartId)); + EXPECT_TRUE(aGraph.Topo().Gen().IsRemoved(aShapeRoot)); + + // All faces in the part should be removed. + for (int aIdx = 0; aIdx < aNbFaces; ++aIdx) + EXPECT_TRUE(aGraph.Topo().Gen().IsRemoved(BRepGraph_FaceId(aIdx))); +} + +// ============================================================================= +// RemoveOccurrence_CascadesToNestedChildren +// ============================================================================= + +TEST(BRepGraph_AssemblyTest, RemoveOccurrence_CascadesToNestedChildren) +{ + // TopAsm -> MidAsm -> LeafPart, each level via occurrences. + // Removing the mid-level occurrence should also remove the leaf occurrence. + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 10.0, 10.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + const BRepGraph_ProductId aLeafPart = BRepGraph_ProductId(0); + const BRepGraph_ProductId aMidAsm = aGraph.Builder().AddAssemblyProduct(); + const BRepGraph_ProductId aTopAsm = aGraph.Builder().AddAssemblyProduct(); + + gp_Trsf aT1, aT2; + aT1.SetTranslation(gp_Vec(10.0, 0.0, 0.0)); + aT2.SetTranslation(gp_Vec(0.0, 20.0, 0.0)); + + // TopAsm places MidAsm. + const BRepGraph_OccurrenceId anOccMid = + aGraph.Builder().AddOccurrence(aTopAsm, aMidAsm, TopLoc_Location(aT1)); + // MidAsm places LeafPart, with parent occurrence = anOccMid. + const BRepGraph_OccurrenceId anOccLeaf = + aGraph.Builder().AddOccurrence(aMidAsm, aLeafPart, TopLoc_Location(aT2), anOccMid); + + EXPECT_FALSE(aGraph.Topo().Gen().IsRemoved(anOccMid)); + EXPECT_FALSE(aGraph.Topo().Gen().IsRemoved(anOccLeaf)); + + // Verify the parent chain: leaf's ParentOccurrenceDefId == mid occurrence. + const BRepGraphInc::OccurrenceDef& aLeafDef = aGraph.Topo().Occurrences().Definition(anOccLeaf); + EXPECT_EQ(aLeafDef.ParentOccurrenceDefId.Index, anOccMid.Index); + + // Remove the mid-level occurrence - leaf should cascade. + aGraph.Builder().RemoveSubgraph(anOccMid); + + EXPECT_TRUE(aGraph.Topo().Gen().IsRemoved(anOccMid)); + EXPECT_TRUE(aGraph.Topo().Gen().IsRemoved(anOccLeaf)); + + // Products themselves should NOT be removed (only occurrences). + EXPECT_FALSE(aGraph.Topo().Gen().IsRemoved(aLeafPart)); + EXPECT_FALSE(aGraph.Topo().Gen().IsRemoved(aMidAsm)); + EXPECT_FALSE(aGraph.Topo().Gen().IsRemoved(aTopAsm)); +} + +// ============================================================================= +// MutProduct_RAII +// ============================================================================= + +TEST(BRepGraph_AssemblyTest, MutProduct_RAII) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 10.0, 10.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + { + BRepGraph_MutGuard aMutProd = + aGraph.Builder().MutProduct(BRepGraph_ProductId(0)); + // Setting ShapeRootId to a different topology node. + aMutProd->ShapeRootId = BRepGraph_SolidId(0); + } // markModified fires here + + EXPECT_GT(aGraph.Topo().Products().Definition(BRepGraph_ProductId(0)).OwnGen, 0u); +} + +// ============================================================================= +// MutOccurrence_Placement +// ============================================================================= + +TEST(BRepGraph_AssemblyTest, MutOccurrence_Placement) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 10.0, 10.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + const BRepGraph_ProductId aPartId = BRepGraph_ProductId(0); + const BRepGraph_ProductId aAssemblyId = aGraph.Builder().AddAssemblyProduct(); + const BRepGraph_OccurrenceId anOccId = + aGraph.Builder().AddOccurrence(aAssemblyId, aPartId, TopLoc_Location()); + + gp_Trsf aTrsf; + aTrsf.SetTranslation(gp_Vec(50.0, 0.0, 0.0)); + + { + BRepGraph_MutGuard aMutOcc = + aGraph.Builder().MutOccurrence(anOccId); + aMutOcc->Placement = TopLoc_Location(aTrsf); + } // markModified fires here + + EXPECT_GT(aGraph.Topo().Occurrences().Definition(anOccId).OwnGen, 0u); + const gp_Trsf& aStoredTrsf = + aGraph.Topo().Occurrences().Definition(anOccId).Placement.Transformation(); + EXPECT_NEAR(aStoredTrsf.TranslationPart().X(), 50.0, Precision::Confusion()); +} + +TEST(BRepGraph_AssemblyTest, MutInvalidAssemblyDefs_ThrowProgramError) +{ + BRepGraph aGraph; +#if !defined(No_Exception) + EXPECT_THROW((void)aGraph.Builder().MutProduct(BRepGraph_ProductId(7)), Standard_ProgramError); + EXPECT_THROW((void)aGraph.Builder().MutOccurrence(BRepGraph_OccurrenceId(7)), + Standard_ProgramError); +#endif +} + +// ============================================================================= +// GlobalPlacement_DeepNesting +// ============================================================================= + +TEST(BRepGraph_AssemblyTest, GlobalPlacement_DeepNesting) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 10.0, 10.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + const BRepGraph_ProductId aPartId = BRepGraph_ProductId(0); + + // Build: RootAssembly -> (OccSubAsm) -> SubAssembly -> (OccPart) -> Part + const BRepGraph_ProductId aSubAsmId = aGraph.Builder().AddAssemblyProduct(); + const BRepGraph_ProductId aRootAsmId = aGraph.Builder().AddAssemblyProduct(); + + gp_Trsf aTrsf1; + aTrsf1.SetTranslation(gp_Vec(100.0, 0.0, 0.0)); + gp_Trsf aTrsf2; + aTrsf2.SetTranslation(gp_Vec(0.0, 200.0, 0.0)); + + // RootAssembly places SubAssembly with aTrsf2 (top-level occurrence, no parent occ). + const BRepGraph_OccurrenceId anOccSubAsm = + aGraph.Builder().AddOccurrence(aRootAsmId, aSubAsmId, TopLoc_Location(aTrsf2)); + // SubAssembly places Part with aTrsf1, with parent occurrence = anOccSubAsm. + const BRepGraph_OccurrenceId anOccPart = + aGraph.Builder().AddOccurrence(aSubAsmId, aPartId, TopLoc_Location(aTrsf1), anOccSubAsm); + + // Global placement of the part occurrence should be aTrsf2 * aTrsf1. + // ParentOccurrenceDefId chain: anOccPart -> anOccSubAsm -> -1 (root). + TopLoc_Location aGlobal = aGraph.Topo().Occurrences().OccurrenceLocation(anOccPart); + const gp_Trsf& aGTrsf = aGlobal.Transformation(); + EXPECT_NEAR(aGTrsf.TranslationPart().X(), 100.0, Precision::Confusion()); + EXPECT_NEAR(aGTrsf.TranslationPart().Y(), 200.0, Precision::Confusion()); +} + +// ============================================================================= +// NbNodes_IncludesAssembly +// ============================================================================= + +TEST(BRepGraph_AssemblyTest, NbNodes_IncludesAssembly) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 10.0, 10.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + const size_t aNbNodesAfterBuild = aGraph.Topo().Gen().NbNodes(); + // Should include the auto-created root product. + EXPECT_GE(aNbNodesAfterBuild, 1u); + + // Add assembly + occurrence. + const BRepGraph_ProductId aAssemblyId = aGraph.Builder().AddAssemblyProduct(); + (void)aGraph.Builder().AddOccurrence(aAssemblyId, BRepGraph_ProductId(0), TopLoc_Location()); + + const size_t aNbNodesAfterAssembly = aGraph.Topo().Gen().NbNodes(); + EXPECT_EQ(aNbNodesAfterAssembly, aNbNodesAfterBuild + 2); // +1 product, +1 occurrence +} + +// ============================================================================= +// OccurrencesOfProduct_ReverseIndex +// ============================================================================= + +TEST(BRepGraph_AssemblyTest, OccurrencesOfProduct_ReverseIndex) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 10.0, 10.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + const BRepGraph_ProductId aPartId = BRepGraph_ProductId(0); + const BRepGraph_ProductId aAssemblyId = aGraph.Builder().AddAssemblyProduct(); + (void)aGraph.Builder().AddOccurrence(aAssemblyId, aPartId, TopLoc_Location()); + (void)aGraph.Builder().AddOccurrence(aAssemblyId, aPartId, TopLoc_Location()); + + // Build the product-occurrence reverse index manually. + BRepGraphInc_ReverseIndex aRevIdx; + // We test the reverse index build via Storage's exposed reverse index. + // Since BuildReverseIndex doesn't cover product/occurrences yet, + // we test through the NbComponents API. + EXPECT_EQ(aGraph.Topo().Products().NbComponents(aAssemblyId), 2); +} + +// ============================================================================= +// Product_Count +// ============================================================================= + +TEST(BRepGraph_AssemblyTest, Product_Count) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 10.0, 10.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + (void)aGraph.Builder().AddAssemblyProduct(); + + int aCount = 0; + for (BRepGraph_ProductIterator aProductIt(aGraph); aProductIt.More(); aProductIt.Next()) + { + ++aCount; + } + EXPECT_EQ(aCount, 2); // auto root + added assembly +} + +// ============================================================================= +// Occurrence_Count +// ============================================================================= + +TEST(BRepGraph_AssemblyTest, Occurrence_Count) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 10.0, 10.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + const BRepGraph_ProductId aPartId = BRepGraph_ProductId(0); + const BRepGraph_ProductId aAssemblyId = aGraph.Builder().AddAssemblyProduct(); + (void)aGraph.Builder().AddOccurrence(aAssemblyId, aPartId, TopLoc_Location()); + (void)aGraph.Builder().AddOccurrence(aAssemblyId, aPartId, TopLoc_Location()); + + int aCount = 0; + for (BRepGraph_OccurrenceIterator anOccIt(aGraph); anOccIt.More(); anOccIt.Next()) + { + ++aCount; + } + EXPECT_EQ(aCount, 2); +} + +// ============================================================================= +// NodeId_Helpers +// ============================================================================= + +TEST(BRepGraph_AssemblyTest, NodeId_Helpers) +{ + EXPECT_TRUE(BRepGraph_NodeId::IsTopologyKind(BRepGraph_NodeId::Kind::Solid)); + EXPECT_TRUE(BRepGraph_NodeId::IsTopologyKind(BRepGraph_NodeId::Kind::Vertex)); + EXPECT_TRUE(BRepGraph_NodeId::IsTopologyKind(BRepGraph_NodeId::Kind::CompSolid)); + EXPECT_FALSE(BRepGraph_NodeId::IsTopologyKind(BRepGraph_NodeId::Kind::Product)); + EXPECT_FALSE(BRepGraph_NodeId::IsTopologyKind(BRepGraph_NodeId::Kind::Occurrence)); + + EXPECT_TRUE(BRepGraph_NodeId::IsAssemblyKind(BRepGraph_NodeId::Kind::Product)); + EXPECT_TRUE(BRepGraph_NodeId::IsAssemblyKind(BRepGraph_NodeId::Kind::Occurrence)); + EXPECT_FALSE(BRepGraph_NodeId::IsAssemblyKind(BRepGraph_NodeId::Kind::Solid)); + EXPECT_FALSE(BRepGraph_NodeId::IsAssemblyKind(BRepGraph_NodeId::Kind::Face)); +} + +// ============================================================================= +// UID_IsAssembly +// ============================================================================= + +TEST(BRepGraph_AssemblyTest, UID_IsAssembly) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 10.0, 10.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + // The auto-created root product should have a UID with IsAssembly() == false + // (it's a part, but its UID Kind is Product, so IsAssembly() on UID checks the Kind). + const BRepGraph_UID aProductUID = aGraph.UIDs().Of(BRepGraph_ProductId(0)); + EXPECT_TRUE(aProductUID.IsValid()); + EXPECT_TRUE(aProductUID.IsAssembly()); + EXPECT_FALSE(aProductUID.IsTopology()); +} + +// ============================================================================= +// AddOccurrence_InvalidParent_ReturnsInvalid +// ============================================================================= + +TEST(BRepGraph_AssemblyTest, AddOccurrence_InvalidParent_ReturnsInvalid) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 10.0, 10.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + BRepGraph_OccurrenceId aResult = aGraph.Builder().AddOccurrence(BRepGraph_ProductId(999), + BRepGraph_ProductId(0), + TopLoc_Location()); + EXPECT_FALSE(aResult.IsValid()); + + // Out-of-bounds referenced product index. + aResult = aGraph.Builder().AddOccurrence(BRepGraph_ProductId(0), + BRepGraph_ProductId(999), + TopLoc_Location()); + EXPECT_FALSE(aResult.IsValid()); +} + +// ============================================================================= +// AddOccurrence_SelfReference_ReturnsInvalid +// ============================================================================= + +TEST(BRepGraph_AssemblyTest, AddOccurrence_SelfReference_ReturnsInvalid) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 10.0, 10.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + const BRepGraph_ProductId aPartId = BRepGraph_ProductId(0); + + // Self-referencing: a product cannot be an occurrence of itself. + const BRepGraph_OccurrenceId aResult = + aGraph.Builder().AddOccurrence(aPartId, aPartId, TopLoc_Location()); + EXPECT_FALSE(aResult.IsValid()); +} + +// ============================================================================= +// RootProducts_RemovedOccurrence_DoesNotAffectRoots +// ============================================================================= + +TEST(BRepGraph_AssemblyTest, RootProducts_RemovedOccurrence_DoesNotAffectRoots) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 10.0, 10.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + const BRepGraph_ProductId aPartId = BRepGraph_ProductId(0); + const BRepGraph_ProductId aAssemblyId = aGraph.Builder().AddAssemblyProduct(); + const BRepGraph_OccurrenceId anOccId = + aGraph.Builder().AddOccurrence(aAssemblyId, aPartId, TopLoc_Location()); + + // Before removal: only assembly is root (part is referenced). + const occ::handle anAllocator = new NCollection_IncAllocator(); + NCollection_Vector aRoots = + aGraph.Topo().Products().RootProducts(anAllocator); + EXPECT_EQ(aRoots.Length(), 1); + EXPECT_EQ(aRoots.Value(0), aAssemblyId); + + // Remove the occurrence - part should become a root again. + aGraph.Builder().RemoveSubgraph(anOccId); + + aRoots = aGraph.Topo().Products().RootProducts(anAllocator); + EXPECT_EQ(aRoots.Length(), 2); // both part and assembly are roots now +} + +// ============================================================================= +// GlobalPlacement_DAGSharing_DistinctPathsGiveDistinctPlacements +// ============================================================================= + +TEST(BRepGraph_AssemblyTest, GlobalPlacement_DAGSharing_DistinctPathsGiveDistinctPlacements) +{ + // Shared part placed twice under the same assembly at different locations. + // Each occurrence has its own placement chain - no ambiguity. + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 10.0, 10.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + const BRepGraph_ProductId aPartId = BRepGraph_ProductId(0); + const BRepGraph_ProductId aAssemblyId = aGraph.Builder().AddAssemblyProduct(); + + gp_Trsf aTrsf1; + aTrsf1.SetTranslation(gp_Vec(100.0, 0.0, 0.0)); + gp_Trsf aTrsf2; + aTrsf2.SetTranslation(gp_Vec(0.0, 200.0, 0.0)); + + const BRepGraph_OccurrenceId anOcc1 = + aGraph.Builder().AddOccurrence(aAssemblyId, aPartId, TopLoc_Location(aTrsf1)); + const BRepGraph_OccurrenceId anOcc2 = + aGraph.Builder().AddOccurrence(aAssemblyId, aPartId, TopLoc_Location(aTrsf2)); + + // Same part, different occurrences, different global placements. + TopLoc_Location aGlobal1 = aGraph.Topo().Occurrences().OccurrenceLocation(anOcc1); + TopLoc_Location aGlobal2 = aGraph.Topo().Occurrences().OccurrenceLocation(anOcc2); + + EXPECT_NEAR(aGlobal1.Transformation().TranslationPart().X(), 100.0, Precision::Confusion()); + EXPECT_NEAR(aGlobal1.Transformation().TranslationPart().Y(), 0.0, Precision::Confusion()); + EXPECT_NEAR(aGlobal2.Transformation().TranslationPart().X(), 0.0, Precision::Confusion()); + EXPECT_NEAR(aGlobal2.Transformation().TranslationPart().Y(), 200.0, Precision::Confusion()); +} + +// ============================================================================= +// AddOccurrence_RemovedProduct_ReturnsInvalid +// ============================================================================= + +TEST(BRepGraph_AssemblyTest, AddOccurrence_RemovedProduct_ReturnsInvalid) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 10.0, 10.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + const BRepGraph_ProductId aAssemblyId = aGraph.Builder().AddAssemblyProduct(); + // Remove the assembly. + aGraph.Builder().RemoveNode(aAssemblyId); + + // Cannot add occurrence to a removed product. + const BRepGraph_OccurrenceId aResult = + aGraph.Builder().AddOccurrence(aAssemblyId, BRepGraph_ProductId(0), TopLoc_Location()); + EXPECT_FALSE(aResult.IsValid()); + + // Cannot reference a removed product either. + const BRepGraph_ProductId aAsm2 = aGraph.Builder().AddAssemblyProduct(); + aGraph.Builder().RemoveNode(BRepGraph_ProductId(0)); + const BRepGraph_OccurrenceId aResult2 = + aGraph.Builder().AddOccurrence(aAsm2, BRepGraph_ProductId(0), TopLoc_Location()); + EXPECT_FALSE(aResult2.IsValid()); +} + +// ============================================================================= +// GlobalPlacement_ThreeLevelNesting +// ============================================================================= + +TEST(BRepGraph_AssemblyTest, GlobalPlacement_ThreeLevelNesting) +{ + // Root -> Mid -> Leaf, each with a distinct translation. + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 10.0, 10.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + const BRepGraph_ProductId aLeafPart = BRepGraph_ProductId(0); + const BRepGraph_ProductId aMidAsm = aGraph.Builder().AddAssemblyProduct(); + const BRepGraph_ProductId aRootAsm = aGraph.Builder().AddAssemblyProduct(); + const BRepGraph_ProductId aTopAsm = aGraph.Builder().AddAssemblyProduct(); + + gp_Trsf aT1, aT2, aT3; + aT1.SetTranslation(gp_Vec(1.0, 0.0, 0.0)); + aT2.SetTranslation(gp_Vec(0.0, 2.0, 0.0)); + aT3.SetTranslation(gp_Vec(0.0, 0.0, 3.0)); + + // TopAsm places RootAsm. + const BRepGraph_OccurrenceId anOccRoot = + aGraph.Builder().AddOccurrence(aTopAsm, aRootAsm, TopLoc_Location(aT3)); + // RootAsm places MidAsm, parent occ = anOccRoot. + const BRepGraph_OccurrenceId anOccMid = + aGraph.Builder().AddOccurrence(aRootAsm, aMidAsm, TopLoc_Location(aT2), anOccRoot); + // MidAsm places Leaf, parent occ = anOccMid. + const BRepGraph_OccurrenceId anOccLeaf = + aGraph.Builder().AddOccurrence(aMidAsm, aLeafPart, TopLoc_Location(aT1), anOccMid); + + // Global of leaf = T3 * T2 * T1 => (1, 2, 3). + TopLoc_Location aGlobal = aGraph.Topo().Occurrences().OccurrenceLocation(anOccLeaf); + const gp_XYZ& aTransl = aGlobal.Transformation().TranslationPart(); + EXPECT_NEAR(aTransl.X(), 1.0, Precision::Confusion()); + EXPECT_NEAR(aTransl.Y(), 2.0, Precision::Confusion()); + EXPECT_NEAR(aTransl.Z(), 3.0, Precision::Confusion()); +} + +// ============================================================================= +// ShapesView_ProductShape_ReconstructsBuiltRootTransform +// ============================================================================= + +TEST(BRepGraph_AssemblyTest, ShapesView_ProductShape_ReconstructsBuiltRootTransform) +{ + TopoDS_Shape aRootShape = BRepPrimAPI_MakeBox(10.0, 20.0, 30.0).Shape(); + + gp_Trsf aMove; + aMove.SetTranslation(gp_Vec(15.0, -7.0, 2.0)); + aRootShape.Move(TopLoc_Location(aMove)); + aRootShape.Reverse(); + + BRepGraph aGraph; + aGraph.Build(aRootShape); + ASSERT_TRUE(aGraph.IsDone()); + + const TopoDS_Shape aProductShape = aGraph.Shapes().Shape(BRepGraph_ProductId(0)); + ASSERT_FALSE(aProductShape.IsNull()); + EXPECT_EQ(aProductShape.ShapeType(), aRootShape.ShapeType()); + EXPECT_EQ(aProductShape.Orientation(), aRootShape.Orientation()); + EXPECT_EQ(aProductShape.Location(), aRootShape.Location()); + + const TopoDS_Shape aReconstructed = aGraph.Shapes().Reconstruct(BRepGraph_ProductId(0)); + ASSERT_FALSE(aReconstructed.IsNull()); + EXPECT_EQ(aReconstructed.ShapeType(), aRootShape.ShapeType()); + EXPECT_EQ(aReconstructed.Orientation(), aRootShape.Orientation()); + EXPECT_EQ(aReconstructed.Location(), aRootShape.Location()); +} + +// ============================================================================= +// ShapesView_AssemblyProduct_ReconstructsChildOccurrences +// ============================================================================= + +TEST(BRepGraph_AssemblyTest, ShapesView_AssemblyProduct_ReconstructsChildOccurrences) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 10.0, 10.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + const BRepGraph_ProductId aPartId = BRepGraph_ProductId(0); + const BRepGraph_ProductId aAssemblyId = aGraph.Builder().AddAssemblyProduct(); + + gp_Trsf aTrsf1; + aTrsf1.SetTranslation(gp_Vec(100.0, 0.0, 0.0)); + gp_Trsf aTrsf2; + aTrsf2.SetTranslation(gp_Vec(200.0, 0.0, 0.0)); + + ASSERT_TRUE( + aGraph.Builder().AddOccurrence(aAssemblyId, aPartId, TopLoc_Location(aTrsf1)).IsValid()); + ASSERT_TRUE( + aGraph.Builder().AddOccurrence(aAssemblyId, aPartId, TopLoc_Location(aTrsf2)).IsValid()); + + const TopoDS_Shape aAssemblyShape = aGraph.Shapes().Shape(aAssemblyId); + ASSERT_FALSE(aAssemblyShape.IsNull()); + EXPECT_EQ(aAssemblyShape.ShapeType(), TopAbs_COMPOUND); + EXPECT_EQ(aAssemblyShape.NbChildren(), 2); + + TopoDS_Iterator anIt(aAssemblyShape); + ASSERT_TRUE(anIt.More()); + const TopoDS_Shape aFirstChild = anIt.Value(); + anIt.Next(); + ASSERT_TRUE(anIt.More()); + const TopoDS_Shape aSecondChild = anIt.Value(); + anIt.Next(); + EXPECT_FALSE(anIt.More()); + + EXPECT_TRUE(aFirstChild.IsPartner(aSecondChild)); + EXPECT_NEAR(translationX(aFirstChild), 100.0, Precision::Confusion()); + EXPECT_NEAR(translationX(aSecondChild), 200.0, Precision::Confusion()); +} + +// ============================================================================= +// ShapesView_OccurrenceShape_UsesGlobalPlacementChain +// ============================================================================= + +TEST(BRepGraph_AssemblyTest, ShapesView_OccurrenceShape_UsesGlobalPlacementChain) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 10.0, 10.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + const BRepGraph_ProductId aPartId = BRepGraph_ProductId(0); + const BRepGraph_ProductId aSubAssembly = aGraph.Builder().AddAssemblyProduct(); + const BRepGraph_ProductId aRootAssembly = aGraph.Builder().AddAssemblyProduct(); + + gp_Trsf aParentTrsf; + aParentTrsf.SetTranslation(gp_Vec(10.0, 0.0, 0.0)); + const BRepGraph_OccurrenceId aParentOccurrence = + aGraph.Builder().AddOccurrence(aRootAssembly, aSubAssembly, TopLoc_Location(aParentTrsf)); + ASSERT_TRUE(aParentOccurrence.IsValid()); + + gp_Trsf aChildTrsf; + aChildTrsf.SetTranslation(gp_Vec(20.0, 0.0, 0.0)); + const BRepGraph_OccurrenceId aChildOccurrence = + aGraph.Builder().AddOccurrence(aSubAssembly, + aPartId, + TopLoc_Location(aChildTrsf), + aParentOccurrence); + ASSERT_TRUE(aChildOccurrence.IsValid()); + + const TopoDS_Shape aSubAssemblyShape = aGraph.Shapes().Shape(aSubAssembly); + ASSERT_FALSE(aSubAssemblyShape.IsNull()); + EXPECT_EQ(aSubAssemblyShape.NbChildren(), 1); + + TopoDS_Iterator aSubIt(aSubAssemblyShape); + ASSERT_TRUE(aSubIt.More()); + EXPECT_NEAR(translationX(aSubIt.Value()), 20.0, Precision::Confusion()); + + const TopoDS_Shape aOccurrenceShape = aGraph.Shapes().Shape(aChildOccurrence); + ASSERT_FALSE(aOccurrenceShape.IsNull()); + EXPECT_NEAR(translationX(aOccurrenceShape), 30.0, Precision::Confusion()); + + const TopoDS_Shape aReconstructed = aGraph.Shapes().Reconstruct(aChildOccurrence); + ASSERT_FALSE(aReconstructed.IsNull()); + EXPECT_NEAR(translationX(aReconstructed), 30.0, Precision::Confusion()); +} + +// ============================================================================= +// OccurrencesOfProduct_ViaReverseIndex +// ============================================================================= + +TEST(BRepGraph_AssemblyTest, OccurrencesOfProduct_ViaReverseIndex) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 10.0, 10.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + const BRepGraph_ProductId aPartId = BRepGraph_ProductId(0); + const BRepGraph_ProductId aAsmId = aGraph.Builder().AddAssemblyProduct(); + (void)aGraph.Builder().AddOccurrence(aAsmId, aPartId, TopLoc_Location()); + (void)aGraph.Builder().AddOccurrence(aAsmId, aPartId, TopLoc_Location()); + + // Rebuild reverse index to populate product->occurrences. + // (BuildReverseIndex is called during Build, but not after Builder mutations.) + // Access via DefsView which uses forward OccurrenceRefs. + EXPECT_EQ(aGraph.Topo().Products().NbComponents(aAsmId), 2); + + // The auto-created root product is not referenced by any occurrence. + // (It IS the part being referenced, so OccurrencesOfProduct should have entries.) + EXPECT_EQ(aGraph.Topo().Products().NbComponents(aPartId), 0); // part has no child occurrences +} + +// ============================================================================= +// GlobalPlacement_CircularParentOccurrence_Terminates +// ============================================================================= + +TEST(BRepGraph_AssemblyTest, GlobalPlacement_CircularParentOccurrence_Terminates) +{ + // Manually create a circular ParentOccurrenceDefId via MutRef to simulate + // a malformed graph. GlobalPlacement must terminate (THE_MAX_OCCURRENCE_DEPTH guard). + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 10.0, 10.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + const BRepGraph_ProductId aPartId = BRepGraph_ProductId(0); + const BRepGraph_ProductId aAsmId = aGraph.Builder().AddAssemblyProduct(); + + gp_Trsf aTrsf; + aTrsf.SetTranslation(gp_Vec(1.0, 0.0, 0.0)); + + const BRepGraph_OccurrenceId anOcc1 = + aGraph.Builder().AddOccurrence(aAsmId, aPartId, TopLoc_Location(aTrsf)); + const BRepGraph_OccurrenceId anOcc2 = + aGraph.Builder().AddOccurrence(aAsmId, aPartId, TopLoc_Location(aTrsf), anOcc1); + + // Inject circular reference: occ1.ParentOccurrenceDefId = occ2 (creates cycle). + { + BRepGraph_MutGuard aMut = aGraph.Builder().MutOccurrence(anOcc1); + aMut->ParentOccurrenceDefId = anOcc2; + } + + // GlobalPlacement must terminate despite the cycle (depth guard). + TopLoc_Location aGlobal = aGraph.Topo().Occurrences().OccurrenceLocation(anOcc2); + // We don't check the value - just that it doesn't hang. + (void)aGlobal; +} diff --git a/src/ModelingData/TKBRep/GTests/BRepGraph_Benchmark_Test.cxx b/src/ModelingData/TKBRep/GTests/BRepGraph_Benchmark_Test.cxx new file mode 100644 index 0000000000..a307d4b40d --- /dev/null +++ b/src/ModelingData/TKBRep/GTests/BRepGraph_Benchmark_Test.cxx @@ -0,0 +1,210 @@ +// Copyright (c) 2026 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 + +namespace +{ + +constexpr int THE_WARMUP_ITERS = 2; +constexpr int THE_MEASURE_ITERS = 20; + +TopoDS_Compound makeFaceCloud(int theNbFaces) +{ + BRep_Builder aBuilder; + TopoDS_Compound aCompound; + aBuilder.MakeCompound(aCompound); + + int aNbAdded = 0; + int aBoxIdx = 0; + while (aNbAdded < theNbFaces) + { + const double aSize = 10.0 + static_cast(aBoxIdx % 7); + BRepPrimAPI_MakeBox aBoxMaker(aSize, aSize + 1.0, aSize + 2.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + for (TopExp_Explorer aFaceExp(aBox, TopAbs_FACE); aFaceExp.More() && aNbAdded < theNbFaces; + aFaceExp.Next()) + { + BRepBuilderAPI_Copy aCopy(aFaceExp.Current(), true); + aBuilder.Add(aCompound, aCopy.Shape()); + ++aNbAdded; + } + + ++aBoxIdx; + } + + return aCompound; +} + +template +double runBenchmark(const char* theLabel, Func theFunc) +{ + for (int aWarmupIter = 0; aWarmupIter < THE_WARMUP_ITERS; ++aWarmupIter) + { + theFunc(); + } + + double aTotal = 0.0; + for (int anIter = 0; anIter < THE_MEASURE_ITERS; ++anIter) + { + const std::chrono::steady_clock::time_point aStart = std::chrono::steady_clock::now(); + theFunc(); + const std::chrono::steady_clock::time_point anEnd = std::chrono::steady_clock::now(); + aTotal += std::chrono::duration(anEnd - aStart).count(); + } + + const double anAvg = aTotal / static_cast(THE_MEASURE_ITERS); + std::cout << "[ PERF ] " << theLabel << ": avg " << anAvg << " s over " << THE_MEASURE_ITERS + << " iters" << std::endl; + return anAvg; +} + +} // namespace + +TEST(BRepGraph_BenchmarkTest, Smoke_BuildReconstructAndAdjacency) +{ + const TopoDS_Compound aFaces = makeFaceCloud(120); + + BRepGraph aGraph; + aGraph.Build(aFaces); + ASSERT_TRUE(aGraph.IsDone()); + ASSERT_GT(aGraph.Topo().Faces().Nb(), 0); + + const BRepGraph_NodeId aFaceNodeId(BRepGraph_NodeId::Kind::Face, 0); + const TopoDS_Shape aFaceShape = aGraph.Shapes().Reconstruct(aFaceNodeId); + EXPECT_FALSE(aFaceShape.IsNull()); + + const BRepGraph_FaceId aFaceId(0); + const NCollection_Vector anAdj = + aGraph.Topo().Faces().Adjacent(aFaceId, aGraph.Allocator()); + EXPECT_GE(anAdj.Length(), 0); +} + +TEST(BRepGraph_BenchmarkTest, DISABLED_Build_100Faces) +{ + const TopoDS_Compound aFaces = makeFaceCloud(100); + const double aAvg = runBenchmark("Build 100 faces", [&]() { + BRepGraph aGraph; + aGraph.Build(aFaces); + EXPECT_TRUE(aGraph.IsDone()); + }); + EXPECT_GT(aAvg, 0.0); +} + +TEST(BRepGraph_BenchmarkTest, DISABLED_Build_1000Faces) +{ + const TopoDS_Compound aFaces = makeFaceCloud(1000); + const double aAvg = runBenchmark("Build 1000 faces", [&]() { + BRepGraph aGraph; + aGraph.Build(aFaces); + EXPECT_TRUE(aGraph.IsDone()); + }); + EXPECT_GT(aAvg, 0.0); +} + +TEST(BRepGraph_BenchmarkTest, DISABLED_Build_10000Faces) +{ + const TopoDS_Compound aFaces = makeFaceCloud(10000); + const double aAvg = runBenchmark("Build 10000 faces", [&]() { + BRepGraph aGraph; + aGraph.Build(aFaces); + EXPECT_TRUE(aGraph.IsDone()); + }); + EXPECT_GT(aAvg, 0.0); +} + +TEST(BRepGraph_BenchmarkTest, DISABLED_Build_1000Faces_Parallel) +{ + const TopoDS_Compound aFaces = makeFaceCloud(1000); + const double aAvg = runBenchmark("Build 1000 faces parallel", [&]() { + BRepGraph aGraph; + aGraph.Build(aFaces, true); + EXPECT_TRUE(aGraph.IsDone()); + }); + EXPECT_GT(aAvg, 0.0); +} + +TEST(BRepGraph_BenchmarkTest, DISABLED_Build_10000Faces_Parallel) +{ + const TopoDS_Compound aFaces = makeFaceCloud(10000); + const double aAvg = runBenchmark("Build 10000 faces parallel", [&]() { + BRepGraph aGraph; + aGraph.Build(aFaces, true); + EXPECT_TRUE(aGraph.IsDone()); + }); + EXPECT_GT(aAvg, 0.0); +} + +TEST(BRepGraph_BenchmarkTest, DISABLED_Reconstruct_RoundTrip) +{ + const TopoDS_Compound aFaces = makeFaceCloud(10000); + BRepGraph aGraph; + aGraph.Build(aFaces); + ASSERT_TRUE(aGraph.IsDone()); + + const int aNbFaces = aGraph.Topo().Faces().Nb(); + ASSERT_GT(aNbFaces, 0); + + const double aAvg = runBenchmark("Reconstruct 10000 faces", [&]() { + for (BRepGraph_FaceIterator aFaceIt(aGraph); aFaceIt.More(); aFaceIt.Next()) + { + const TopoDS_Shape aShape = + aGraph.Shapes().Reconstruct(BRepGraph_NodeId(aFaceIt.CurrentId())); + EXPECT_FALSE(aShape.IsNull()); + } + }); + + const double aPerFace = aAvg / static_cast(aNbFaces); + std::cout << "[ PERF ] Reconstruct per-face avg: " << aPerFace << " s" << std::endl; + EXPECT_GT(aAvg, 0.0); +} + +TEST(BRepGraph_BenchmarkTest, DISABLED_SpatialQuery_Throughput) +{ + const TopoDS_Compound aFaces = makeFaceCloud(10000); + BRepGraph aGraph; + aGraph.Build(aFaces); + ASSERT_TRUE(aGraph.IsDone()); + + const int aNbFaces = aGraph.Topo().Faces().Nb(); + ASSERT_GT(aNbFaces, 0); + + const double aAvg = runBenchmark("SpatialQuery 10000 faces", [&]() { + for (BRepGraph_FaceIterator aFaceIt(aGraph); aFaceIt.More(); aFaceIt.Next()) + { + const NCollection_Vector anAdj = + aGraph.Topo().Faces().Adjacent(aFaceIt.CurrentId(), aGraph.Allocator()); + EXPECT_GE(anAdj.Length(), 0); + } + }); + + const double aPerQuery = aAvg / static_cast(aNbFaces); + std::cout << "[ PERF ] SpatialQuery per-face avg: " << aPerQuery << " s" << std::endl; + EXPECT_GT(aAvg, 0.0); +} diff --git a/src/ModelingData/TKBRep/GTests/BRepGraph_Build_Test.cxx b/src/ModelingData/TKBRep/GTests/BRepGraph_Build_Test.cxx new file mode 100644 index 0000000000..b425ee8d76 --- /dev/null +++ b/src/ModelingData/TKBRep/GTests/BRepGraph_Build_Test.cxx @@ -0,0 +1,1341 @@ +// Copyright (c) 2026 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +// ============================================================================= +// Helper: count unique shapes of a given type via TopExp::MapShapes +// ============================================================================= + +static int countUnique(const TopoDS_Shape& theShape, TopAbs_ShapeEnum theType) +{ + NCollection_IndexedMap aMap; + TopExp::MapShapes(theShape, theType, aMap); + return aMap.Extent(); +} + +static void registerStandardLayers(BRepGraph& theGraph) +{ + theGraph.LayerRegistry().RegisterLayer(new BRepGraph_ParamLayer()); + theGraph.LayerRegistry().RegisterLayer(new BRepGraph_RegularityLayer()); +} + +static TopoDS_Shape makeStandaloneWire() +{ + BRepBuilderAPI_MakeEdge aMkEdge(gp_Pnt(0.0, 0.0, 0.0), gp_Pnt(10.0, 0.0, 0.0)); + return BRepBuilderAPI_MakeWire(TopoDS::Edge(aMkEdge.Shape())).Shape(); +} + +// ============================================================================= +// Sphere tests +// ============================================================================= + +TEST(BRepGraph_BuildTest, Sphere_IsDone) +{ + BRepPrimAPI_MakeSphere aMaker(10.0); + const TopoDS_Shape aShape = aMaker.Shape(); + ASSERT_TRUE(aMaker.IsDone()); + + BRepGraph aGraph; + aGraph.Build(aShape); + EXPECT_TRUE(aGraph.IsDone()); +} + +TEST(BRepGraph_BuildTest, Sphere_DefCounts_MatchTopExp) +{ + BRepPrimAPI_MakeSphere aMaker(10.0); + const TopoDS_Shape aShape = aMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aShape); + ASSERT_TRUE(aGraph.IsDone()); + + EXPECT_EQ(aGraph.Topo().Solids().Nb(), countUnique(aShape, TopAbs_SOLID)); + EXPECT_EQ(aGraph.Topo().Shells().Nb(), countUnique(aShape, TopAbs_SHELL)); + EXPECT_EQ(aGraph.Topo().Faces().Nb(), countUnique(aShape, TopAbs_FACE)); + EXPECT_EQ(aGraph.Topo().Wires().Nb(), countUnique(aShape, TopAbs_WIRE)); + EXPECT_EQ(aGraph.Topo().Edges().Nb(), countUnique(aShape, TopAbs_EDGE)); + EXPECT_EQ(aGraph.Topo().Vertices().Nb(), countUnique(aShape, TopAbs_VERTEX)); +} + +TEST(BRepGraph_BuildTest, Sphere_SurfaceType) +{ + BRepPrimAPI_MakeSphere aMaker(5.0); + const TopoDS_Shape aShape = aMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aShape); + ASSERT_TRUE(aGraph.IsDone()); + ASSERT_GE(aGraph.Topo().Faces().Nb(), 1); + + bool aHasSpherical = false; + for (BRepGraph_FaceIterator aFaceIt(aGraph); aFaceIt.More(); aFaceIt.Next()) + { + const occ::handle& aSurf = + BRepGraph_Tool::Face::Surface(aGraph, aFaceIt.CurrentId()); + if (!aSurf.IsNull() && aSurf->DynamicType() == STANDARD_TYPE(Geom_SphericalSurface)) + { + aHasSpherical = true; + } + } + EXPECT_TRUE(aHasSpherical); +} + +TEST(BRepGraph_BuildTest, Sphere_HasDegenerateEdges) +{ + BRepPrimAPI_MakeSphere aMaker(8.0); + const TopoDS_Shape aShape = aMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aShape); + ASSERT_TRUE(aGraph.IsDone()); + + // A sphere has degenerate edges at poles. + int aDegCount = 0; + for (BRepGraph_EdgeIterator anEdgeIt(aGraph); anEdgeIt.More(); anEdgeIt.Next()) + { + if (BRepGraph_Tool::Edge::Degenerated(aGraph, anEdgeIt.CurrentId())) + { + ++aDegCount; + } + } + EXPECT_GE(aDegCount, 1); +} + +// ============================================================================= +// Cylinder tests +// ============================================================================= + +TEST(BRepGraph_BuildTest, Cylinder_IsDone) +{ + BRepPrimAPI_MakeCylinder aMaker(5.0, 20.0); + const TopoDS_Shape aShape = aMaker.Shape(); + ASSERT_TRUE(aMaker.IsDone()); + + BRepGraph aGraph; + aGraph.Build(aShape); + EXPECT_TRUE(aGraph.IsDone()); +} + +TEST(BRepGraph_BuildTest, Cylinder_DefCounts_MatchTopExp) +{ + BRepPrimAPI_MakeCylinder aMaker(5.0, 20.0); + const TopoDS_Shape aShape = aMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aShape); + ASSERT_TRUE(aGraph.IsDone()); + + EXPECT_EQ(aGraph.Topo().Faces().Nb(), countUnique(aShape, TopAbs_FACE)); + EXPECT_EQ(aGraph.Topo().Edges().Nb(), countUnique(aShape, TopAbs_EDGE)); + EXPECT_EQ(aGraph.Topo().Vertices().Nb(), countUnique(aShape, TopAbs_VERTEX)); +} + +TEST(BRepGraph_BuildTest, Cylinder_SurfaceType) +{ + BRepPrimAPI_MakeCylinder aMaker(5.0, 20.0); + const TopoDS_Shape aShape = aMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aShape); + ASSERT_TRUE(aGraph.IsDone()); + + bool aHasCylindrical = false; + for (BRepGraph_FaceIterator aFaceIt(aGraph); aFaceIt.More(); aFaceIt.Next()) + { + const occ::handle& aSurf = + BRepGraph_Tool::Face::Surface(aGraph, aFaceIt.CurrentId()); + if (!aSurf.IsNull() && aSurf->DynamicType() == STANDARD_TYPE(Geom_CylindricalSurface)) + { + aHasCylindrical = true; + } + } + EXPECT_TRUE(aHasCylindrical); +} + +// ============================================================================= +// Cone tests +// ============================================================================= + +TEST(BRepGraph_BuildTest, Cone_IsDone) +{ + BRepPrimAPI_MakeCone aMaker(10.0, 0.0, 15.0); + const TopoDS_Shape aShape = aMaker.Shape(); + ASSERT_TRUE(aMaker.IsDone()); + + BRepGraph aGraph; + aGraph.Build(aShape); + EXPECT_TRUE(aGraph.IsDone()); +} + +TEST(BRepGraph_BuildTest, Cone_DefCounts_MatchTopExp) +{ + BRepPrimAPI_MakeCone aMaker(10.0, 0.0, 15.0); + const TopoDS_Shape aShape = aMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aShape); + ASSERT_TRUE(aGraph.IsDone()); + + EXPECT_EQ(aGraph.Topo().Faces().Nb(), countUnique(aShape, TopAbs_FACE)); + EXPECT_EQ(aGraph.Topo().Edges().Nb(), countUnique(aShape, TopAbs_EDGE)); + EXPECT_EQ(aGraph.Topo().Vertices().Nb(), countUnique(aShape, TopAbs_VERTEX)); +} + +TEST(BRepGraph_BuildTest, Cone_SurfaceType) +{ + BRepPrimAPI_MakeCone aMaker(10.0, 5.0, 15.0); + const TopoDS_Shape aShape = aMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aShape); + ASSERT_TRUE(aGraph.IsDone()); + + bool aHasConical = false; + for (BRepGraph_FaceIterator aFaceIt(aGraph); aFaceIt.More(); aFaceIt.Next()) + { + const occ::handle& aSurf = + BRepGraph_Tool::Face::Surface(aGraph, aFaceIt.CurrentId()); + if (!aSurf.IsNull() && aSurf->DynamicType() == STANDARD_TYPE(Geom_ConicalSurface)) + { + aHasConical = true; + } + } + EXPECT_TRUE(aHasConical); +} + +TEST(BRepGraph_BuildTest, Cone_HasDegenerateEdge) +{ + // Cone with top radius 0 => apex degenerate edge. + BRepPrimAPI_MakeCone aMaker(10.0, 0.0, 15.0); + const TopoDS_Shape aShape = aMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aShape); + ASSERT_TRUE(aGraph.IsDone()); + + int aDegCount = 0; + for (BRepGraph_EdgeIterator anEdgeIt(aGraph); anEdgeIt.More(); anEdgeIt.Next()) + { + if (BRepGraph_Tool::Edge::Degenerated(aGraph, anEdgeIt.CurrentId())) + { + ++aDegCount; + } + } + EXPECT_GE(aDegCount, 1); +} + +// ============================================================================= +// Torus tests +// ============================================================================= + +TEST(BRepGraph_BuildTest, Torus_IsDone) +{ + BRepPrimAPI_MakeTorus aMaker(20.0, 5.0); + const TopoDS_Shape aShape = aMaker.Shape(); + ASSERT_TRUE(aMaker.IsDone()); + + BRepGraph aGraph; + aGraph.Build(aShape); + EXPECT_TRUE(aGraph.IsDone()); +} + +TEST(BRepGraph_BuildTest, Torus_DefCounts_MatchTopExp) +{ + BRepPrimAPI_MakeTorus aMaker(20.0, 5.0); + const TopoDS_Shape aShape = aMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aShape); + ASSERT_TRUE(aGraph.IsDone()); + + EXPECT_EQ(aGraph.Topo().Faces().Nb(), countUnique(aShape, TopAbs_FACE)); + EXPECT_EQ(aGraph.Topo().Edges().Nb(), countUnique(aShape, TopAbs_EDGE)); + EXPECT_EQ(aGraph.Topo().Vertices().Nb(), countUnique(aShape, TopAbs_VERTEX)); +} + +TEST(BRepGraph_BuildTest, Torus_SurfaceType) +{ + BRepPrimAPI_MakeTorus aMaker(20.0, 5.0); + const TopoDS_Shape aShape = aMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aShape); + ASSERT_TRUE(aGraph.IsDone()); + + bool aHasToroidal = false; + for (BRepGraph_FaceIterator aFaceIt(aGraph); aFaceIt.More(); aFaceIt.Next()) + { + const occ::handle& aSurf = + BRepGraph_Tool::Face::Surface(aGraph, aFaceIt.CurrentId()); + if (!aSurf.IsNull() && aSurf->DynamicType() == STANDARD_TYPE(Geom_ToroidalSurface)) + { + aHasToroidal = true; + } + } + EXPECT_TRUE(aHasToroidal); +} + +// ============================================================================= +// Wedge tests +// ============================================================================= + +TEST(BRepGraph_BuildTest, Wedge_IsDone) +{ + BRepPrimAPI_MakeWedge aMaker(10.0, 10.0, 10.0, 5.0); + const TopoDS_Shape aShape = aMaker.Shape(); + ASSERT_TRUE(aMaker.IsDone()); + + BRepGraph aGraph; + aGraph.Build(aShape); + EXPECT_TRUE(aGraph.IsDone()); +} + +TEST(BRepGraph_BuildTest, Wedge_DefCounts_MatchTopExp) +{ + BRepPrimAPI_MakeWedge aMaker(10.0, 10.0, 10.0, 5.0); + const TopoDS_Shape aShape = aMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aShape); + ASSERT_TRUE(aGraph.IsDone()); + + EXPECT_EQ(aGraph.Topo().Solids().Nb(), countUnique(aShape, TopAbs_SOLID)); + EXPECT_EQ(aGraph.Topo().Shells().Nb(), countUnique(aShape, TopAbs_SHELL)); + EXPECT_EQ(aGraph.Topo().Faces().Nb(), countUnique(aShape, TopAbs_FACE)); + EXPECT_EQ(aGraph.Topo().Wires().Nb(), countUnique(aShape, TopAbs_WIRE)); + EXPECT_EQ(aGraph.Topo().Edges().Nb(), countUnique(aShape, TopAbs_EDGE)); + EXPECT_EQ(aGraph.Topo().Vertices().Nb(), countUnique(aShape, TopAbs_VERTEX)); +} + +TEST(BRepGraph_BuildTest, Wedge_AllPlanarSurfaces) +{ + BRepPrimAPI_MakeWedge aMaker(10.0, 10.0, 10.0, 5.0); + const TopoDS_Shape aShape = aMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aShape); + ASSERT_TRUE(aGraph.IsDone()); + + for (BRepGraph_FaceIterator aFaceIt(aGraph); aFaceIt.More(); aFaceIt.Next()) + { + const occ::handle& aSurf = + BRepGraph_Tool::Face::Surface(aGraph, aFaceIt.CurrentId()); + ASSERT_FALSE(aSurf.IsNull()); + EXPECT_TRUE(aSurf->DynamicType() == STANDARD_TYPE(Geom_Plane)) + << "Face " << aFaceIt.CurrentId().Index << " surface is not a Geom_Plane"; + } +} + +// ============================================================================= +// Compound builds +// ============================================================================= + +TEST(BRepGraph_BuildTest, Compound_TwoPrimitives_IsDone) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 10.0, 10.0); + const TopoDS_Shape aBox = aBoxMaker.Shape(); + + BRepPrimAPI_MakeSphere aSphereMaker(5.0); + const TopoDS_Shape aSphere = aSphereMaker.Shape(); + + BRep_Builder aBuilder; + TopoDS_Compound aCompound; + aBuilder.MakeCompound(aCompound); + aBuilder.Add(aCompound, aBox); + aBuilder.Add(aCompound, aSphere); + + BRepGraph aGraph; + aGraph.Build(aCompound); + EXPECT_TRUE(aGraph.IsDone()); +} + +TEST(BRepGraph_BuildTest, Compound_TwoPrimitives_DefCountsAddUp) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 10.0, 10.0); + const TopoDS_Shape aBox = aBoxMaker.Shape(); + + BRepPrimAPI_MakeSphere aSphereMaker(5.0); + const TopoDS_Shape aSphere = aSphereMaker.Shape(); + + BRep_Builder aBuilder; + TopoDS_Compound aCompound; + aBuilder.MakeCompound(aCompound); + aBuilder.Add(aCompound, aBox); + aBuilder.Add(aCompound, aSphere); + + BRepGraph aGraph; + aGraph.Build(aCompound); + ASSERT_TRUE(aGraph.IsDone()); + + EXPECT_EQ(aGraph.Topo().Faces().Nb(), countUnique(aCompound, TopAbs_FACE)); + EXPECT_EQ(aGraph.Topo().Edges().Nb(), countUnique(aCompound, TopAbs_EDGE)); + EXPECT_EQ(aGraph.Topo().Vertices().Nb(), countUnique(aCompound, TopAbs_VERTEX)); +} + +TEST(BRepGraph_BuildTest, Compound_ThreeBoxes_DefCounts) +{ + BRep_Builder aBuilder; + TopoDS_Compound aCompound; + aBuilder.MakeCompound(aCompound); + + for (int anIdx = 0; anIdx < 3; ++anIdx) + { + BRepPrimAPI_MakeBox aMaker(5.0 * (anIdx + 1), 10.0, 10.0); + const TopoDS_Shape aBox = aMaker.Shape(); + aBuilder.Add(aCompound, aBox); + } + + BRepGraph aGraph; + aGraph.Build(aCompound); + ASSERT_TRUE(aGraph.IsDone()); + + EXPECT_EQ(aGraph.Topo().Solids().Nb(), countUnique(aCompound, TopAbs_SOLID)); + EXPECT_EQ(aGraph.Topo().Faces().Nb(), countUnique(aCompound, TopAbs_FACE)); + EXPECT_EQ(aGraph.Topo().Edges().Nb(), countUnique(aCompound, TopAbs_EDGE)); +} + +TEST(BRepGraph_BuildTest, Compound_Nested_DefCounts) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 10.0, 10.0); + const TopoDS_Shape aBox = aBoxMaker.Shape(); + + BRepPrimAPI_MakeCylinder aCylMaker(3.0, 10.0); + const TopoDS_Shape aCyl = aCylMaker.Shape(); + + BRep_Builder aBuilder; + TopoDS_Compound anInner; + aBuilder.MakeCompound(anInner); + aBuilder.Add(anInner, aBox); + + TopoDS_Compound anOuter; + aBuilder.MakeCompound(anOuter); + aBuilder.Add(anOuter, anInner); + aBuilder.Add(anOuter, aCyl); + + BRepGraph aGraph; + aGraph.Build(anOuter); + ASSERT_TRUE(aGraph.IsDone()); + + EXPECT_EQ(aGraph.Topo().Faces().Nb(), countUnique(anOuter, TopAbs_FACE)); + EXPECT_EQ(aGraph.Topo().Edges().Nb(), countUnique(anOuter, TopAbs_EDGE)); + EXPECT_EQ(aGraph.Topo().Vertices().Nb(), countUnique(anOuter, TopAbs_VERTEX)); +} + +// ============================================================================= +// Minimal shapes +// ============================================================================= + +TEST(BRepGraph_BuildTest, SinglePlanarFace_IsDone) +{ + gp_Pln aPln; + BRepBuilderAPI_MakeFace aFaceMaker(aPln); + const TopoDS_Shape aShape = aFaceMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aShape); + EXPECT_TRUE(aGraph.IsDone()); +} + +TEST(BRepGraph_BuildTest, SinglePlanarFace_Counts) +{ + gp_Pln aPln; + BRepBuilderAPI_MakeFace aFaceMaker(aPln); + const TopoDS_Shape aShape = aFaceMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aShape); + ASSERT_TRUE(aGraph.IsDone()); + + EXPECT_EQ(aGraph.Topo().Faces().Nb(), 1); + EXPECT_EQ(aGraph.Topo().Solids().Nb(), 0); + EXPECT_EQ(aGraph.Topo().Shells().Nb(), 0); + EXPECT_GE(aGraph.Topo().Faces().Nb(), 1); + + // Verify surface is a plane. + ASSERT_TRUE(BRepGraph_Tool::Face::HasSurface(aGraph, BRepGraph_FaceId(0))); + const occ::handle& aSurf = + BRepGraph_Tool::Face::Surface(aGraph, BRepGraph_FaceId(0)); + ASSERT_FALSE(aSurf.IsNull()); + EXPECT_TRUE(aSurf->DynamicType() == STANDARD_TYPE(Geom_Plane)); +} + +TEST(BRepGraph_BuildTest, SingleEdge_HandlesGracefully) +{ + BRepBuilderAPI_MakeEdge anEdgeMaker(gp_Pnt(0, 0, 0), gp_Pnt(1, 0, 0)); + ASSERT_TRUE(anEdgeMaker.IsDone()); + const TopoDS_Shape aShape = anEdgeMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aShape); + + // BRepGraph is face-level; standalone edges may produce zero counts. + // Verify it does not crash and returns consistent state. + EXPECT_EQ(aGraph.Topo().Faces().Nb(), 0); + EXPECT_EQ(aGraph.Topo().Solids().Nb(), 0); +} + +TEST(BRepGraph_BuildTest, SingleVertex_HandlesGracefully) +{ + BRepBuilderAPI_MakeVertex aVertexMaker(gp_Pnt(1.0, 2.0, 3.0)); + const TopoDS_Shape aShape = aVertexMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aShape); + + // BRepGraph is face-level; standalone vertices may produce zero counts. + EXPECT_EQ(aGraph.Topo().Faces().Nb(), 0); + EXPECT_EQ(aGraph.Topo().Edges().Nb(), 0); +} + +// ============================================================================= +// Box cross-validation with TopExp +// ============================================================================= + +TEST(BRepGraph_BuildTest, Box_FaceDefCount_MatchesTopExp) +{ + BRepPrimAPI_MakeBox aMaker(10.0, 20.0, 30.0); + const TopoDS_Shape aBox = aMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + + EXPECT_EQ(aGraph.Topo().Faces().Nb(), countUnique(aBox, TopAbs_FACE)); + EXPECT_EQ(aGraph.Topo().Faces().Nb(), 6); +} + +TEST(BRepGraph_BuildTest, Box_EdgeDefCount_MatchesTopExp) +{ + BRepPrimAPI_MakeBox aMaker(10.0, 20.0, 30.0); + const TopoDS_Shape aBox = aMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + + EXPECT_EQ(aGraph.Topo().Edges().Nb(), countUnique(aBox, TopAbs_EDGE)); + EXPECT_EQ(aGraph.Topo().Edges().Nb(), 12); +} + +TEST(BRepGraph_BuildTest, Box_VertexDefCount_MatchesTopExp) +{ + BRepPrimAPI_MakeBox aMaker(10.0, 20.0, 30.0); + const TopoDS_Shape aBox = aMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + + EXPECT_EQ(aGraph.Topo().Vertices().Nb(), countUnique(aBox, TopAbs_VERTEX)); + EXPECT_EQ(aGraph.Topo().Vertices().Nb(), 8); +} + +TEST(BRepGraph_BuildTest, Box_VertexPoints_MatchBRepTool) +{ + BRepPrimAPI_MakeBox aMaker(10.0, 20.0, 30.0); + const TopoDS_Shape aBox = aMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + + // Collect all vertex points from TopExp. + NCollection_IndexedMap aVertexMap; + TopExp::MapShapes(aBox, TopAbs_VERTEX, aVertexMap); + + ASSERT_EQ(aGraph.Topo().Vertices().Nb(), aVertexMap.Extent()); + + // For each graph vertex, verify that a matching TopExp vertex exists. + for (BRepGraph_VertexIterator aVertexIt(aGraph); aVertexIt.More(); aVertexIt.Next()) + { + const BRepGraph_VertexId aVertexId = aVertexIt.CurrentId(); + const gp_Pnt aGraphPnt = BRepGraph_Tool::Vertex::Pnt(aGraph, aVertexId); + bool aFound = false; + for (int aMapIdx = 1; aMapIdx <= aVertexMap.Extent(); ++aMapIdx) + { + const TopoDS_Vertex& aVertex = TopoDS::Vertex(aVertexMap(aMapIdx)); + const gp_Pnt aTopExpPnt = BRep_Tool::Pnt(aVertex); + if (aGraphPnt.Distance(aTopExpPnt) < Precision::Confusion()) + { + aFound = true; + break; + } + } + EXPECT_TRUE(aFound) << "Graph vertex " << aVertexId.Index << " at (" << aGraphPnt.X() << ", " + << aGraphPnt.Y() << ", " << aGraphPnt.Z() + << ") has no matching TopExp vertex"; + } +} + +TEST(BRepGraph_BuildTest, Box_FaceTolerances_MatchBRepTool) +{ + BRepPrimAPI_MakeBox aMaker(10.0, 20.0, 30.0); + const TopoDS_Shape aBox = aMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + + NCollection_IndexedMap aFaceMap; + TopExp::MapShapes(aBox, TopAbs_FACE, aFaceMap); + + // Verify that each graph face tolerance appears in the TopExp set. + for (BRepGraph_FaceIterator aFaceIt(aGraph); aFaceIt.More(); aFaceIt.Next()) + { + const BRepGraph_FaceId aFaceId = aFaceIt.CurrentId(); + const double aGraphTol = BRepGraph_Tool::Face::Tolerance(aGraph, aFaceId); + bool aFound = false; + for (int aMapIdx = 1; aMapIdx <= aFaceMap.Extent(); ++aMapIdx) + { + const TopoDS_Face& aFace = TopoDS::Face(aFaceMap(aMapIdx)); + const double aTopExpTol = BRep_Tool::Tolerance(aFace); + if (std::abs(aGraphTol - aTopExpTol) < Precision::Confusion()) + { + aFound = true; + break; + } + } + EXPECT_TRUE(aFound) << "Graph face " << aFaceId.Index << " tolerance " << aGraphTol + << " has no matching TopExp face tolerance"; + } +} + +TEST(BRepGraph_BuildTest, Box_EdgeTolerances_MatchBRepTool) +{ + BRepPrimAPI_MakeBox aMaker(10.0, 20.0, 30.0); + const TopoDS_Shape aBox = aMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + + NCollection_IndexedMap anEdgeMap; + TopExp::MapShapes(aBox, TopAbs_EDGE, anEdgeMap); + + for (BRepGraph_EdgeIterator anEdgeIt(aGraph); anEdgeIt.More(); anEdgeIt.Next()) + { + const BRepGraph_EdgeId anEdgeId = anEdgeIt.CurrentId(); + const double aGraphTol = BRepGraph_Tool::Edge::Tolerance(aGraph, anEdgeId); + bool aFound = false; + for (int aMapIdx = 1; aMapIdx <= anEdgeMap.Extent(); ++aMapIdx) + { + const TopoDS_Edge& anEdge = TopoDS::Edge(anEdgeMap(aMapIdx)); + const double aTopExpTol = BRep_Tool::Tolerance(anEdge); + if (std::abs(aGraphTol - aTopExpTol) < Precision::Confusion()) + { + aFound = true; + break; + } + } + EXPECT_TRUE(aFound) << "Graph edge " << anEdgeId.Index << " tolerance " << aGraphTol + << " has no matching TopExp edge tolerance"; + } +} + +TEST(BRepGraph_BuildTest, Box_AllSurfacesArePlanes) +{ + BRepPrimAPI_MakeBox aMaker(10.0, 20.0, 30.0); + const TopoDS_Shape aBox = aMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + + ASSERT_EQ(aGraph.Topo().Faces().Nb(), 6); + for (BRepGraph_FaceIterator aFaceIt(aGraph); aFaceIt.More(); aFaceIt.Next()) + { + const occ::handle& aSurf = + BRepGraph_Tool::Face::Surface(aGraph, aFaceIt.CurrentId()); + ASSERT_FALSE(aSurf.IsNull()); + EXPECT_TRUE(aSurf->DynamicType() == STANDARD_TYPE(Geom_Plane)) + << "Face " << aFaceIt.CurrentId().Index << " surface is not Geom_Plane"; + } +} + +TEST(BRepGraph_BuildTest, Box_NoDegenerateEdges) +{ + BRepPrimAPI_MakeBox aMaker(10.0, 20.0, 30.0); + const TopoDS_Shape aBox = aMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + + for (BRepGraph_EdgeIterator anEdgeIt(aGraph); anEdgeIt.More(); anEdgeIt.Next()) + { + EXPECT_FALSE(BRepGraph_Tool::Edge::Degenerated(aGraph, anEdgeIt.CurrentId())) + << "Box edge " << anEdgeIt.CurrentId().Index << " is degenerate unexpectedly"; + } +} + +TEST(BRepGraph_BuildTest, Box_EdgeVertexDefsAreValid) +{ + BRepPrimAPI_MakeBox aMaker(10.0, 20.0, 30.0); + const TopoDS_Shape aBox = aMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + + for (BRepGraph_EdgeIterator anEdgeIt(aGraph); anEdgeIt.More(); anEdgeIt.Next()) + { + const BRepGraph_EdgeId anEdgeId = anEdgeIt.CurrentId(); + const BRepGraphInc::VertexRef& aStartRef = BRepGraph_Tool::Edge::StartVertex(aGraph, anEdgeId); + const BRepGraphInc::VertexRef& anEndRef = BRepGraph_Tool::Edge::EndVertex(aGraph, anEdgeId); + EXPECT_TRUE(aStartRef.VertexDefId.IsValid()) + << "Edge " << anEdgeId.Index << " has invalid start vertex"; + EXPECT_TRUE(anEndRef.VertexDefId.IsValid()) + << "Edge " << anEdgeId.Index << " has invalid end vertex"; + EXPECT_EQ(BRepGraph_NodeId(aStartRef.VertexDefId).NodeKind, BRepGraph_NodeId::Kind::Vertex); + EXPECT_EQ(BRepGraph_NodeId(anEndRef.VertexDefId).NodeKind, BRepGraph_NodeId::Kind::Vertex); + } +} + +TEST(BRepGraph_BuildTest, Box_FaceSurfacesAreValid) +{ + BRepPrimAPI_MakeBox aMaker(10.0, 20.0, 30.0); + const TopoDS_Shape aBox = aMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + + for (BRepGraph_FaceIterator aFaceIt(aGraph); aFaceIt.More(); aFaceIt.Next()) + { + EXPECT_TRUE(BRepGraph_Tool::Face::HasSurface(aGraph, aFaceIt.CurrentId())) + << "Face " << aFaceIt.CurrentId().Index << " has no surface rep"; + } +} + +TEST(BRepGraph_BuildTest, Box_EdgeParamRange_IsNonDegenerate) +{ + BRepPrimAPI_MakeBox aMaker(10.0, 20.0, 30.0); + const TopoDS_Shape aBox = aMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + + for (BRepGraph_EdgeIterator anEdgeIt(aGraph); anEdgeIt.More(); anEdgeIt.Next()) + { + const BRepGraph_EdgeId anEdgeId = anEdgeIt.CurrentId(); + const BRepGraphInc::EdgeDef& anEdge = anEdgeIt.Current(); + EXPECT_LT(anEdge.ParamFirst, anEdge.ParamLast) + << "Edge " << anEdgeId.Index << " has invalid parameter range [" << anEdge.ParamFirst << ", " + << anEdge.ParamLast << "]"; + } +} + +TEST(BRepGraph_BuildTest, AppendFlattenedShape_OnEmptyGraph_BuildsFlattenedGraph) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + BRepGraph aGraph; + aGraph.Builder().AppendFlattenedShape(aBox); + + EXPECT_TRUE(aGraph.IsDone()); + EXPECT_EQ(aGraph.Topo().Solids().Nb(), 0); + EXPECT_EQ(aGraph.Topo().Shells().Nb(), 0); + EXPECT_EQ(aGraph.Topo().Faces().Nb(), 6); + + const NCollection_Vector& aRoots = aGraph.RootNodeIds(); + ASSERT_EQ(aRoots.Length(), 6); + for (int anIdx = 0; anIdx < aRoots.Length(); ++anIdx) + { + EXPECT_EQ(aRoots.Value(anIdx).NodeKind, BRepGraph_NodeId::Kind::Face); + const BRepGraph_FaceId aFaceId(anIdx); + EXPECT_EQ(aRoots.Value(anIdx), aFaceId); + } +} + +TEST(BRepGraph_BuildTest, AppendFlattenedShape_SameFaceTwice_DedupsDefinition) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + TopExp_Explorer anExp(aBox, TopAbs_FACE); + ASSERT_TRUE(anExp.More()); + const TopoDS_Face aFace = TopoDS::Face(anExp.Current()); + + BRepGraph aGraph; + aGraph.Builder().AppendFlattenedShape(aFace); + aGraph.Builder().AppendFlattenedShape(aFace); + + ASSERT_TRUE(aGraph.IsDone()); + // Same TShape appended twice: definition is deduplicated. + ASSERT_EQ(aGraph.Topo().Faces().Nb(), 1); +} + +TEST(BRepGraph_BuildTest, AppendFlattenedShape_AfterBuild_DoesNotCreateNewSolidDefs) +{ + BRepPrimAPI_MakeBox aBox1Maker(10.0, 20.0, 30.0); + BRepPrimAPI_MakeBox aBox2Maker(15.0, 25.0, 35.0); + + BRepGraph aGraph; + aGraph.Build(aBox1Maker.Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + const int aNbSolidsBefore = aGraph.Topo().Solids().Nb(); + const int aNbFacesBefore = aGraph.Topo().Faces().Nb(); + + aGraph.Builder().AppendFlattenedShape(aBox2Maker.Shape()); + + EXPECT_EQ(aGraph.Topo().Solids().Nb(), aNbSolidsBefore); + EXPECT_EQ(aGraph.Topo().Faces().Nb(), aNbFacesBefore + 6); + EXPECT_EQ(aGraph.RootNodeIds().Length(), 7); +} + +TEST(BRepGraph_BuildTest, AppendFlattenedShape_AppendedFaceHasNoParentShell) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + TopExp_Explorer anExp(aBox, TopAbs_FACE); + ASSERT_TRUE(anExp.More()); + const TopoDS_Face aFace = TopoDS::Face(anExp.Current()); + + BRepGraph aGraph; + aGraph.Builder().AppendFlattenedShape(aFace); + + ASSERT_EQ(aGraph.Topo().Faces().Nb(), 1); + // Appended face should not be part of any shell. + EXPECT_EQ(aGraph.Topo().Shells().Nb(), 0); +} + +TEST(BRepGraph_BuildTest, AppendFlattenedShape_PreservesExistingUIDs) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + ASSERT_GT(aGraph.Topo().Edges().Nb(), 0); + + // Record UID of the first edge before append. + const BRepGraph_UID anOrigUID = aGraph.UIDs().Of(BRepGraph_EdgeId(0)); + ASSERT_TRUE(anOrigUID.IsValid()); + + // Append a sphere. + BRepPrimAPI_MakeSphere aSphereMaker(5.0); + const TopoDS_Shape& aSphere = aSphereMaker.Shape(); + aGraph.Builder().AppendFlattenedShape(aSphere); + ASSERT_TRUE(aGraph.IsDone()); + + // Verify original edge UID is unchanged. + const BRepGraph_UID aPostUID = aGraph.UIDs().Of(BRepGraph_EdgeId(0)); + EXPECT_EQ(anOrigUID, aPostUID); +} + +TEST(BRepGraph_BuildTest, AppendFlattenedShape_StandaloneVertex_AppendsIntoNonEmptyGraph) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 20.0, 30.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + const int aNbVerticesBefore = aGraph.Topo().Vertices().Nb(); + const int aNbRootsBefore = aGraph.RootNodeIds().Length(); + const TopoDS_Shape aVertexShape = BRepBuilderAPI_MakeVertex(gp_Pnt(100.0, 0.0, 0.0)).Shape(); + + aGraph.Builder().AppendFlattenedShape(aVertexShape); + + ASSERT_TRUE(aGraph.IsDone()); + EXPECT_EQ(aGraph.Topo().Vertices().Nb(), aNbVerticesBefore + 1); + ASSERT_EQ(aGraph.RootNodeIds().Length(), aNbRootsBefore + 1); + EXPECT_EQ(aGraph.RootNodeIds().Last().NodeKind, BRepGraph_NodeId::Kind::Vertex); + EXPECT_TRUE(aGraph.Shapes().FindNode(aVertexShape).IsValid()); + EXPECT_FALSE(aGraph.Shapes().Shape(aGraph.RootNodeIds().Last()).IsNull()); +} + +TEST(BRepGraph_BuildTest, AppendFlattenedShape_StandaloneEdge_AppendsIntoNonEmptyGraph) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 20.0, 30.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + const int aNbEdgesBefore = aGraph.Topo().Edges().Nb(); + const int aNbRootsBefore = aGraph.RootNodeIds().Length(); + const TopoDS_Shape anEdgeShape = + BRepBuilderAPI_MakeEdge(gp_Pnt(100.0, 0.0, 0.0), gp_Pnt(120.0, 0.0, 0.0)).Shape(); + + aGraph.Builder().AppendFlattenedShape(anEdgeShape); + + ASSERT_TRUE(aGraph.IsDone()); + EXPECT_EQ(aGraph.Topo().Edges().Nb(), aNbEdgesBefore + 1); + ASSERT_EQ(aGraph.RootNodeIds().Length(), aNbRootsBefore + 1); + EXPECT_EQ(aGraph.RootNodeIds().Last().NodeKind, BRepGraph_NodeId::Kind::Edge); + EXPECT_TRUE(aGraph.Shapes().FindNode(anEdgeShape).IsValid()); + EXPECT_FALSE(aGraph.Shapes().Shape(aGraph.RootNodeIds().Last()).IsNull()); +} + +TEST(BRepGraph_BuildTest, AppendFlattenedShape_StandaloneWire_AppendsIntoNonEmptyGraph) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 20.0, 30.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + const int aNbWiresBefore = aGraph.Topo().Wires().Nb(); + const int aNbRootsBefore = aGraph.RootNodeIds().Length(); + const TopoDS_Shape aWireShape = makeStandaloneWire(); + + aGraph.Builder().AppendFlattenedShape(aWireShape); + + ASSERT_TRUE(aGraph.IsDone()); + EXPECT_EQ(aGraph.Topo().Wires().Nb(), aNbWiresBefore + 1); + ASSERT_EQ(aGraph.RootNodeIds().Length(), aNbRootsBefore + 1); + EXPECT_EQ(aGraph.RootNodeIds().Last().NodeKind, BRepGraph_NodeId::Kind::Wire); + EXPECT_TRUE(aGraph.Shapes().FindNode(aWireShape).IsValid()); + EXPECT_FALSE(aGraph.Shapes().Shape(aGraph.RootNodeIds().Last()).IsNull()); +} + +TEST(BRepGraph_BuildTest, AppendFlattenedShape_CompoundWithStandaloneShapes) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 20.0, 30.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + BRep_Builder aBuilder; + TopoDS_Compound aCompound; + aBuilder.MakeCompound(aCompound); + aBuilder.Add(aCompound, BRepBuilderAPI_MakeVertex(gp_Pnt(50.0, 0.0, 0.0)).Shape()); + aBuilder.Add(aCompound, + BRepBuilderAPI_MakeEdge(gp_Pnt(0.0, 0.0, 0.0), gp_Pnt(10.0, 0.0, 0.0)).Shape()); + + const int aNbRootsBefore = aGraph.RootNodeIds().Length(); + aGraph.Builder().AppendFlattenedShape(aCompound); + ASSERT_TRUE(aGraph.IsDone()); + EXPECT_GT(aGraph.RootNodeIds().Length(), aNbRootsBefore); +} + +TEST(BRepGraph_BuildTest, Build_WithoutPostPasses_BasicQueriesWork) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + BRepGraphInc_Populate::Options anOpts; + anOpts.ExtractRegularities = false; + anOpts.ExtractVertexPointReps = false; + + BRepGraph aGraph; + registerStandardLayers(aGraph); + aGraph.Build(aBox, false, anOpts); + ASSERT_TRUE(aGraph.IsDone()); + const occ::handle aParamLayer = + aGraph.LayerRegistry().FindLayer(); + const occ::handle aRegularityLayer = + aGraph.LayerRegistry().FindLayer(); + ASSERT_FALSE(aParamLayer.IsNull()); + ASSERT_FALSE(aRegularityLayer.IsNull()); + + EXPECT_EQ(aGraph.Topo().Faces().Nb(), 6); + EXPECT_EQ(aGraph.Topo().Edges().Nb(), 12); + EXPECT_EQ(aGraph.Topo().Vertices().Nb(), 8); + + // Regularities should be empty. + for (BRepGraph_EdgeIterator anEdgeIt(aGraph); anEdgeIt.More(); anEdgeIt.Next()) + { + EXPECT_EQ(aRegularityLayer->NbRegularities(anEdgeIt.CurrentId()), 0); + } + + // Vertex point reps should be empty. + for (BRepGraph_VertexIterator aVertexIt(aGraph); aVertexIt.More(); aVertexIt.Next()) + { + EXPECT_EQ(aParamLayer->NbPointsOnCurve(aVertexIt.CurrentId()), 0); + EXPECT_EQ(aParamLayer->NbPointsOnSurface(aVertexIt.CurrentId()), 0); + EXPECT_EQ(aParamLayer->NbPointsOnPCurve(aVertexIt.CurrentId()), 0); + } +} + +TEST(BRepGraph_BuildTest, ParamLayer_EdgeMutation_InvalidatesVertexBindings) +{ + BRepGraph aGraph; + registerStandardLayers(aGraph); + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 20.0, 30.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + const occ::handle aParamLayer = + aGraph.LayerRegistry().FindLayer(); + ASSERT_FALSE(aParamLayer.IsNull()); + + ASSERT_GT(aGraph.Topo().Vertices().Nb(), 0); + ASSERT_GT(aGraph.Topo().Edges().Nb(), 0); + + const BRepGraph_VertexId aVertexId(0); + const BRepGraph_EdgeId anEdgeId(0); + aParamLayer->SetPointOnCurve(aVertexId, anEdgeId, 1.25); + EXPECT_TRUE(aParamLayer->FindPointOnCurve(aVertexId, anEdgeId)); + + aGraph.Builder().MutEdge(anEdgeId)->Tolerance += 0.01; + + EXPECT_FALSE(aParamLayer->FindPointOnCurve(aVertexId, anEdgeId)); +} + +TEST(BRepGraph_BuildTest, ParamLayer_FaceMutation_InvalidatesVertexBindings) +{ + BRepGraph aGraph; + registerStandardLayers(aGraph); + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 20.0, 30.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + const occ::handle aParamLayer = + aGraph.LayerRegistry().FindLayer(); + ASSERT_FALSE(aParamLayer.IsNull()); + + ASSERT_GT(aGraph.Topo().Vertices().Nb(), 0); + ASSERT_GT(aGraph.Topo().Faces().Nb(), 0); + + const BRepGraph_VertexId aVertexId(0); + const BRepGraph_FaceId aFaceId(0); + aParamLayer->SetPointOnSurface(aVertexId, aFaceId, 0.5, 0.75); + EXPECT_TRUE(aParamLayer->FindPointOnSurface(aVertexId, aFaceId)); + + { + BRepGraph_MutGuard aFace = aGraph.Builder().MutFace(aFaceId); + aFace->NaturalRestriction = !aFace->NaturalRestriction; + } + + EXPECT_FALSE(aParamLayer->FindPointOnSurface(aVertexId, aFaceId)); +} + +TEST(BRepGraph_BuildTest, ParamLayer_CoEdgeMutation_InvalidatesPCurveBindings) +{ + BRepGraph aGraph; + registerStandardLayers(aGraph); + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 20.0, 30.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + const occ::handle aParamLayer = + aGraph.LayerRegistry().FindLayer(); + ASSERT_FALSE(aParamLayer.IsNull()); + + ASSERT_GT(aGraph.Topo().Vertices().Nb(), 0); + ASSERT_GT(aGraph.Topo().CoEdges().Nb(), 0); + + const BRepGraph_VertexId aVertexId(0); + const BRepGraph_CoEdgeId aCoEdgeId(0); + aParamLayer->SetPointOnPCurve(aVertexId, aCoEdgeId, 2.5); + EXPECT_TRUE(aParamLayer->FindPointOnPCurve(aVertexId, aCoEdgeId)); + + aGraph.Builder().MutCoEdge(aCoEdgeId)->ParamFirst += 0.01; + + EXPECT_FALSE(aParamLayer->FindPointOnPCurve(aVertexId, aCoEdgeId)); +} + +TEST(BRepGraph_BuildTest, RegularityLayer_EdgeMutation_InvalidatesBindings) +{ + BRepGraph aGraph; + registerStandardLayers(aGraph); + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 20.0, 30.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + const occ::handle aRegularityLayer = + aGraph.LayerRegistry().FindLayer(); + ASSERT_FALSE(aRegularityLayer.IsNull()); + + BRepGraph_EdgeId anEdgeId; + BRepGraph_RegularityLayer::RegularityEntry aRegularity; + bool hasBinding = false; + for (BRepGraph_EdgeIterator anEdgeIt(aGraph); anEdgeIt.More() && !hasBinding; anEdgeIt.Next()) + { + anEdgeId = anEdgeIt.CurrentId(); + const NCollection_Vector& aCoEdges = + aGraph.Topo().Edges().CoEdges(anEdgeId); + BRepGraph_FaceId aFace1; + BRepGraph_FaceId aFace2; + for (const BRepGraph_CoEdgeId& aCoEdgeId : aCoEdges) + { + const BRepGraph_FaceId aFace = aGraph.Topo().CoEdges().Definition(aCoEdgeId).FaceDefId; + if (!aFace.IsValid()) + continue; + if (!aFace1.IsValid()) + aFace1 = aFace; + else if (aFace != aFace1) + { + aFace2 = aFace; + break; + } + } + if (!aFace1.IsValid() || !aFace2.IsValid()) + continue; + aRegularityLayer->SetRegularity(anEdgeId, aFace1, aFace2, GeomAbs_C1); + aRegularity.FaceEntity1 = aFace1; + aRegularity.FaceEntity2 = aFace2; + aRegularity.Continuity = GeomAbs_C1; + hasBinding = true; + } + ASSERT_TRUE(hasBinding); + EXPECT_TRUE( + aRegularityLayer->FindContinuity(anEdgeId, aRegularity.FaceEntity1, aRegularity.FaceEntity2)); + + aGraph.Builder().MutEdge(anEdgeId)->Tolerance += 0.01; + + EXPECT_FALSE( + aRegularityLayer->FindContinuity(anEdgeId, aRegularity.FaceEntity1, aRegularity.FaceEntity2)); + EXPECT_EQ(aRegularityLayer->NbRegularities(anEdgeId), 0); +} + +TEST(BRepGraph_BuildTest, RegularityLayer_FaceMutation_InvalidatesBindings) +{ + BRepGraph aGraph; + registerStandardLayers(aGraph); + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 20.0, 30.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + const occ::handle aRegularityLayer = + aGraph.LayerRegistry().FindLayer(); + ASSERT_FALSE(aRegularityLayer.IsNull()); + + BRepGraph_EdgeId anEdgeId; + BRepGraph_RegularityLayer::RegularityEntry aRegularity; + bool hasBinding = false; + for (BRepGraph_EdgeIterator anEdgeIt(aGraph); anEdgeIt.More() && !hasBinding; anEdgeIt.Next()) + { + anEdgeId = anEdgeIt.CurrentId(); + const NCollection_Vector& aCoEdges = + aGraph.Topo().Edges().CoEdges(anEdgeId); + BRepGraph_FaceId aFace1; + BRepGraph_FaceId aFace2; + for (const BRepGraph_CoEdgeId& aCoEdgeId : aCoEdges) + { + const BRepGraph_FaceId aFace = aGraph.Topo().CoEdges().Definition(aCoEdgeId).FaceDefId; + if (!aFace.IsValid()) + continue; + if (!aFace1.IsValid()) + aFace1 = aFace; + else if (aFace != aFace1) + { + aFace2 = aFace; + break; + } + } + if (!aFace1.IsValid() || !aFace2.IsValid()) + continue; + aRegularityLayer->SetRegularity(anEdgeId, aFace1, aFace2, GeomAbs_C1); + aRegularity.FaceEntity1 = aFace1; + aRegularity.FaceEntity2 = aFace2; + aRegularity.Continuity = GeomAbs_C1; + hasBinding = true; + } + ASSERT_TRUE(hasBinding); + EXPECT_TRUE( + aRegularityLayer->FindContinuity(anEdgeId, aRegularity.FaceEntity1, aRegularity.FaceEntity2)); + + { + BRepGraph_MutGuard aFace = + aGraph.Builder().MutFace(aRegularity.FaceEntity1); + aFace->NaturalRestriction = !aFace->NaturalRestriction; + } + + EXPECT_FALSE( + aRegularityLayer->FindContinuity(anEdgeId, aRegularity.FaceEntity1, aRegularity.FaceEntity2)); +} + +TEST(BRepGraph_BuildTest, RegularityLayer_RemoveRegularity_SharedFaceRetained) +{ + BRepGraph_RegularityLayer aLayer; + const BRepGraph_EdgeId anEdgeId(0); + const BRepGraph_FaceId aFace1(0); + const BRepGraph_FaceId aFace2(1); + const BRepGraph_FaceId aFace3(2); + + aLayer.SetRegularity(anEdgeId, aFace1, aFace2, GeomAbs_C1); + aLayer.SetRegularity(anEdgeId, aFace1, aFace3, GeomAbs_G1); + EXPECT_EQ(aLayer.NbRegularities(anEdgeId), 2); + + // Remove (F1,F2) - F1 is shared by the surviving (F1,F3) entry. + aLayer.OnNodeModified(BRepGraph_FaceId(aFace2.Index)); + + // (F1,F3) must survive; F1 must still be tracked. + EXPECT_TRUE(aLayer.FindContinuity(anEdgeId, aFace1, aFace3)); + EXPECT_EQ(aLayer.NbRegularities(anEdgeId), 1); + + // Modifying F1 should still invalidate the remaining entry. + aLayer.OnNodeModified(BRepGraph_FaceId(aFace1.Index)); + EXPECT_EQ(aLayer.NbRegularities(anEdgeId), 0); +} + +TEST(BRepGraph_BuildTest, RegularityLayer_RemoveRegularity_NoMatch_NoEffect) +{ + BRepGraph_RegularityLayer aLayer; + const BRepGraph_EdgeId anEdgeId(0); + const BRepGraph_FaceId aFace1(0); + const BRepGraph_FaceId aFace2(1); + const BRepGraph_FaceId aFace3(2); + + aLayer.SetRegularity(anEdgeId, aFace1, aFace2, GeomAbs_C1); + EXPECT_EQ(aLayer.NbRegularities(anEdgeId), 1); + + // Modifying F3 (not referenced) should have no effect. + aLayer.OnNodeModified(BRepGraph_FaceId(aFace3.Index)); + EXPECT_EQ(aLayer.NbRegularities(anEdgeId), 1); + EXPECT_TRUE(aLayer.FindContinuity(anEdgeId, aFace1, aFace2)); +} + +TEST(BRepGraph_BuildTest, ParamLayer_OnCompact_RemapsNodeIds) +{ + BRepGraph_ParamLayer aLayer; + const BRepGraph_VertexId aVtx0(0); + const BRepGraph_VertexId aVtx1(1); + const BRepGraph_EdgeId anEdge0(0); + const BRepGraph_EdgeId anEdge1(1); + const BRepGraph_FaceId aFace0(0); + const BRepGraph_CoEdgeId aCoEdge0(0); + + aLayer.SetPointOnCurve(aVtx0, anEdge0, 1.0); + aLayer.SetPointOnCurve(aVtx1, anEdge1, 2.0); + aLayer.SetPointOnSurface(aVtx0, aFace0, 0.5, 0.75); + aLayer.SetPointOnPCurve(aVtx1, aCoEdge0, 3.0); + + // Remap: vtx0->vtx0, vtx1 dropped; edge0->edge0, edge1 dropped; face0->face0; coedge0 dropped. + NCollection_DataMap aRemapMap; + aRemapMap.Bind(BRepGraph_VertexId(0), BRepGraph_VertexId(0)); + aRemapMap.Bind(BRepGraph_EdgeId(0), BRepGraph_EdgeId(0)); + aRemapMap.Bind(BRepGraph_FaceId(0), BRepGraph_FaceId(0)); + + aLayer.OnCompact(aRemapMap); + + // Vtx0 on edge0 should survive. + double aParam = 0.0; + EXPECT_TRUE(aLayer.FindPointOnCurve(aVtx0, anEdge0, &aParam)); + EXPECT_NEAR(aParam, 1.0, 1e-15); + + // Vtx0 on face0 should survive. + gp_Pnt2d aUV; + EXPECT_TRUE(aLayer.FindPointOnSurface(aVtx0, aFace0, &aUV)); + EXPECT_NEAR(aUV.X(), 0.5, 1e-15); + EXPECT_NEAR(aUV.Y(), 0.75, 1e-15); + + // Vtx1 was dropped. + EXPECT_FALSE(aLayer.FindPointOnCurve(aVtx1, anEdge1)); + EXPECT_FALSE(aLayer.FindPointOnPCurve(aVtx1, aCoEdge0)); +} + +TEST(BRepGraph_BuildTest, RegularityLayer_OnCompact_RemapsNodeIds) +{ + BRepGraph_RegularityLayer aLayer; + const BRepGraph_EdgeId anEdge0(0); + const BRepGraph_EdgeId anEdge1(1); + const BRepGraph_FaceId aFace0(0); + const BRepGraph_FaceId aFace1(1); + const BRepGraph_FaceId aFace2(2); + + aLayer.SetRegularity(anEdge0, aFace0, aFace1, GeomAbs_C1); + aLayer.SetRegularity(anEdge1, aFace1, aFace2, GeomAbs_G1); + + // Remap: edge0->edge0, edge1 dropped; face0->face0, face1->face1, face2 dropped. + NCollection_DataMap aRemapMap; + aRemapMap.Bind(BRepGraph_EdgeId(0), BRepGraph_EdgeId(0)); + aRemapMap.Bind(BRepGraph_FaceId(0), BRepGraph_FaceId(0)); + aRemapMap.Bind(BRepGraph_FaceId(1), BRepGraph_FaceId(1)); + + aLayer.OnCompact(aRemapMap); + + // Edge0 (F0,F1) should survive. + GeomAbs_Shape aContinuity = GeomAbs_C0; + EXPECT_TRUE(aLayer.FindContinuity(anEdge0, aFace0, aFace1, &aContinuity)); + EXPECT_EQ(aContinuity, GeomAbs_C1); + + // Edge1 was dropped. + EXPECT_EQ(aLayer.NbRegularities(anEdge1), 0); +} + +TEST(BRepGraph_BuildTest, RootNodeIds_Box_ReturnsSolid) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + + // Build() from a solid should produce exactly one root node. + const NCollection_Vector& aRoots = aGraph.RootNodeIds(); + ASSERT_EQ(aRoots.Length(), 1); + + // The root's kind should match the input shape type (Solid for a box). + const BRepGraph_NodeId aRoot = aRoots.Value(0); + EXPECT_TRUE(aRoot.IsValid()); + EXPECT_EQ(aRoot.NodeKind, BRepGraph_NodeId::Kind::Solid); +} + +TEST(BRepGraph_BuildTest, RootNodeIds_AppendFlattenedShape_ReturnsTwoRoots) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + ASSERT_EQ(aGraph.RootNodeIds().Length(), 1); + + // Append a second shape (a single face from another box). + BRepPrimAPI_MakeBox aBox2Maker(5.0, 5.0, 5.0); + TopExp_Explorer anExp(aBox2Maker.Shape(), TopAbs_FACE); + ASSERT_TRUE(anExp.More()); + const TopoDS_Face aFace = TopoDS::Face(anExp.Current()); + + aGraph.Builder().AppendFlattenedShape(aFace); + ASSERT_TRUE(aGraph.IsDone()); + + // After appending, there should be two root nodes. + const NCollection_Vector& aRoots = aGraph.RootNodeIds(); + EXPECT_EQ(aRoots.Length(), 2); + + // First root remains a Solid; second root is a Face from face-level append. + EXPECT_EQ(aRoots.Value(0).NodeKind, BRepGraph_NodeId::Kind::Solid); + EXPECT_EQ(aRoots.Value(1).NodeKind, BRepGraph_NodeId::Kind::Face); + + // Both roots should be valid. + EXPECT_TRUE(aRoots.Value(0).IsValid()); + EXPECT_TRUE(aRoots.Value(1).IsValid()); +} diff --git a/src/ModelingData/TKBRep/GTests/BRepGraph_Builder_Test.cxx b/src/ModelingData/TKBRep/GTests/BRepGraph_Builder_Test.cxx new file mode 100644 index 0000000000..d44a309f86 --- /dev/null +++ b/src/ModelingData/TKBRep/GTests/BRepGraph_Builder_Test.cxx @@ -0,0 +1,902 @@ +// Copyright (c) 2026 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 "BRepGraph_RefTestTools.hxx" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +// ============================================================ +// Task 2A: Programmatic Node Addition API +// ============================================================ + +TEST(BRepGraph_BuilderTest, AddVertex_ReturnsValidId) +{ + BRepGraph aGraph; + BRepGraph_VertexId aVtxId = aGraph.Builder().AddVertex(gp_Pnt(1.0, 2.0, 3.0), 0.001); + EXPECT_TRUE(aVtxId.IsValid()); + EXPECT_EQ(aVtxId.Index, 0); + + const BRepGraphInc::VertexDef& aVtxDef = + aGraph.Topo().Vertices().Definition(BRepGraph_VertexId(0)); + EXPECT_NEAR(aVtxDef.Point.X(), 1.0, Precision::Confusion()); + EXPECT_NEAR(aVtxDef.Point.Y(), 2.0, Precision::Confusion()); + EXPECT_NEAR(aVtxDef.Point.Z(), 3.0, Precision::Confusion()); + EXPECT_NEAR(aVtxDef.Tolerance, 0.001, 1e-10); +} + +TEST(BRepGraph_BuilderTest, AddEdge_WithCurve) +{ + BRepGraph aGraph; + BRepGraph_VertexId aV1 = aGraph.Builder().AddVertex(gp_Pnt(0.0, 0.0, 0.0), 0.001); + BRepGraph_VertexId aV2 = aGraph.Builder().AddVertex(gp_Pnt(10.0, 0.0, 0.0), 0.001); + + occ::handle aLine = new Geom_Line(gp_Lin(gp_Pnt(0, 0, 0), gp_Dir(1, 0, 0))); + BRepGraph_EdgeId anEdgeId = aGraph.Builder().AddEdge(aV1, aV2, aLine, 0.0, 10.0, 0.001); + + EXPECT_TRUE(anEdgeId.IsValid()); + + const BRepGraphInc::EdgeDef& anEdgeDef = aGraph.Topo().Edges().Definition(BRepGraph_EdgeId(0)); + EXPECT_EQ(BRepGraph_Tool::Edge::StartVertex(aGraph, BRepGraph_EdgeId(0)).VertexDefId, aV1); + EXPECT_EQ(BRepGraph_Tool::Edge::EndVertex(aGraph, BRepGraph_EdgeId(0)).VertexDefId, aV2); + EXPECT_TRUE(anEdgeDef.Curve3DRepId.IsValid()); + EXPECT_NEAR(anEdgeDef.ParamFirst, 0.0, 1e-10); + EXPECT_NEAR(anEdgeDef.ParamLast, 10.0, 1e-10); +} + +TEST(BRepGraph_BuilderTest, AddWire_ClosedRectangle) +{ + BRepGraph aGraph; + BRepGraph_VertexId aV0 = aGraph.Builder().AddVertex(gp_Pnt(0, 0, 0), 0.001); + BRepGraph_VertexId aV1 = aGraph.Builder().AddVertex(gp_Pnt(10, 0, 0), 0.001); + BRepGraph_VertexId aV2 = aGraph.Builder().AddVertex(gp_Pnt(10, 10, 0), 0.001); + BRepGraph_VertexId aV3 = aGraph.Builder().AddVertex(gp_Pnt(0, 10, 0), 0.001); + + occ::handle aL0 = new Geom_Line(gp_Pnt(0, 0, 0), gp_Dir(1, 0, 0)); + occ::handle aL1 = new Geom_Line(gp_Pnt(10, 0, 0), gp_Dir(0, 1, 0)); + occ::handle aL2 = new Geom_Line(gp_Pnt(10, 10, 0), gp_Dir(-1, 0, 0)); + occ::handle aL3 = new Geom_Line(gp_Pnt(0, 10, 0), gp_Dir(0, -1, 0)); + + BRepGraph_EdgeId aE0 = aGraph.Builder().AddEdge(aV0, aV1, aL0, 0.0, 10.0, 0.001); + BRepGraph_EdgeId aE1 = aGraph.Builder().AddEdge(aV1, aV2, aL1, 0.0, 10.0, 0.001); + BRepGraph_EdgeId aE2 = aGraph.Builder().AddEdge(aV2, aV3, aL2, 0.0, 10.0, 0.001); + BRepGraph_EdgeId aE3 = aGraph.Builder().AddEdge(aV3, aV0, aL3, 0.0, 10.0, 0.001); + + NCollection_Vector> aEdges; + aEdges.Append({aE0, TopAbs_FORWARD}); + aEdges.Append({aE1, TopAbs_FORWARD}); + aEdges.Append({aE2, TopAbs_FORWARD}); + aEdges.Append({aE3, TopAbs_FORWARD}); + + BRepGraph_WireId aWireId = aGraph.Builder().AddWire(aEdges); + EXPECT_TRUE(aWireId.IsValid()); + + EXPECT_EQ(BRepGraph_TestTools::CountCoEdgeRefsOfWire(aGraph, BRepGraph_WireId(0)), 4); + const BRepGraphInc::WireDef& aWireDef = aGraph.Topo().Wires().Definition(BRepGraph_WireId(0)); + EXPECT_TRUE(aWireDef.IsClosed); +} + +TEST(BRepGraph_BuilderTest, AddFace_WithSurface) +{ + BRepGraph aGraph; + + // Build a simple rectangular face programmatically. + BRepGraph_VertexId aV0 = aGraph.Builder().AddVertex(gp_Pnt(0, 0, 0), 0.001); + BRepGraph_VertexId aV1 = aGraph.Builder().AddVertex(gp_Pnt(10, 0, 0), 0.001); + BRepGraph_VertexId aV2 = aGraph.Builder().AddVertex(gp_Pnt(10, 10, 0), 0.001); + BRepGraph_VertexId aV3 = aGraph.Builder().AddVertex(gp_Pnt(0, 10, 0), 0.001); + + occ::handle aL0 = new Geom_Line(gp_Pnt(0, 0, 0), gp_Dir(1, 0, 0)); + occ::handle aL1 = new Geom_Line(gp_Pnt(10, 0, 0), gp_Dir(0, 1, 0)); + occ::handle aL2 = new Geom_Line(gp_Pnt(10, 10, 0), gp_Dir(-1, 0, 0)); + occ::handle aL3 = new Geom_Line(gp_Pnt(0, 10, 0), gp_Dir(0, -1, 0)); + + BRepGraph_EdgeId aE0 = aGraph.Builder().AddEdge(aV0, aV1, aL0, 0.0, 10.0, 0.001); + BRepGraph_EdgeId aE1 = aGraph.Builder().AddEdge(aV1, aV2, aL1, 0.0, 10.0, 0.001); + BRepGraph_EdgeId aE2 = aGraph.Builder().AddEdge(aV2, aV3, aL2, 0.0, 10.0, 0.001); + BRepGraph_EdgeId aE3 = aGraph.Builder().AddEdge(aV3, aV0, aL3, 0.0, 10.0, 0.001); + + NCollection_Vector> aEdges; + aEdges.Append({aE0, TopAbs_FORWARD}); + aEdges.Append({aE1, TopAbs_FORWARD}); + aEdges.Append({aE2, TopAbs_FORWARD}); + aEdges.Append({aE3, TopAbs_FORWARD}); + + BRepGraph_WireId aWireId = aGraph.Builder().AddWire(aEdges); + + occ::handle aPlane = new Geom_Plane(gp_Pln()); + NCollection_Vector aInnerWires; + BRepGraph_FaceId aFaceId = aGraph.Builder().AddFace(aPlane, aWireId, aInnerWires, 0.001); + + EXPECT_TRUE(aFaceId.IsValid()); + EXPECT_EQ(aGraph.Topo().Faces().Nb(), 1); + EXPECT_EQ(aGraph.Topo().Faces().Nb(), 1); + + const BRepGraphInc::FaceDef& aFaceDef = aGraph.Topo().Faces().Definition(BRepGraph_FaceId(0)); + EXPECT_TRUE(aFaceDef.SurfaceRepId.IsValid()); +} + +TEST(BRepGraph_BuilderTest, AddEdge_InvalidVertex_ReturnsInvalidAndDoesNotAppend) +{ + BRepGraph aGraph; + BRepGraph_VertexId aVertexId = aGraph.Builder().AddVertex(gp_Pnt(0.0, 0.0, 0.0), 0.001); + + const BRepGraph_EdgeId anEdgeId = aGraph.Builder().AddEdge(aVertexId, + BRepGraph_VertexId(42), + occ::handle(), + 0.0, + 1.0, + 0.001); + + EXPECT_FALSE(anEdgeId.IsValid()); + EXPECT_EQ(aGraph.Topo().Edges().Nb(), 0); +} + +TEST(BRepGraph_BuilderTest, AddWire_InvalidEdge_ReturnsInvalidAndDoesNotAppend) +{ + BRepGraph aGraph; + + NCollection_Vector> anEdges; + anEdges.Append({BRepGraph_EdgeId(17), TopAbs_FORWARD}); + + const BRepGraph_WireId aWireId = aGraph.Builder().AddWire(anEdges); + EXPECT_FALSE(aWireId.IsValid()); + EXPECT_EQ(aGraph.Topo().Wires().Nb(), 0); + EXPECT_EQ(aGraph.Topo().CoEdges().Nb(), 0); +} + +TEST(BRepGraph_BuilderTest, AddFace_InvalidOuterWire_ReturnsInvalidAndDoesNotAppend) +{ + BRepGraph aGraph; + + occ::handle aPlane = new Geom_Plane(gp_Pln()); + NCollection_Vector anInnerWires; + const BRepGraph_FaceId aFaceId = + aGraph.Builder().AddFace(aPlane, BRepGraph_WireId(9), anInnerWires, 0.001); + + EXPECT_FALSE(aFaceId.IsValid()); + EXPECT_EQ(aGraph.Topo().Faces().Nb(), 0); +} + +TEST(BRepGraph_BuilderTest, AddShellAndSolid) +{ + BRepGraph aGraph; + BRepGraph_ShellId aShellId = aGraph.Builder().AddShell(); + EXPECT_TRUE(aShellId.IsValid()); + + BRepGraph_SolidId aSolidId = aGraph.Builder().AddSolid(); + EXPECT_TRUE(aSolidId.IsValid()); +} + +// ============================================================ +// Task 2B: Incremental Build (AppendFlattenedShape) +// ============================================================ + +TEST(BRepGraph_BuilderTest, AppendTwoBoxFaces) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + // Get two faces from the box. + TopExp_Explorer anExp(aBox, TopAbs_FACE); + ASSERT_TRUE(anExp.More()); + TopoDS_Shape aFace1 = anExp.Current(); + BRepBuilderAPI_Copy aCopy1(aFace1, true); + anExp.Next(); + ASSERT_TRUE(anExp.More()); + TopoDS_Shape aFace2 = anExp.Current(); + BRepBuilderAPI_Copy aCopy2(aFace2, true); + + BRepGraph aGraph; + aGraph.Build(aCopy1.Shape()); + ASSERT_TRUE(aGraph.IsDone()); + EXPECT_EQ(aGraph.Topo().Faces().Nb(), 1); + + // Append second face. + aGraph.Builder().AppendFlattenedShape(aCopy2.Shape()); + EXPECT_EQ(aGraph.Topo().Faces().Nb(), 2); + EXPECT_TRUE(aGraph.IsDone()); +} + +// ============================================================ +// Task 2C: Soft Node Removal +// ============================================================ + +TEST(BRepGraph_BuilderTest, RemoveVertex_IsRemoved) +{ + BRepGraph aGraph; + BRepGraph_VertexId aVtxId = aGraph.Builder().AddVertex(gp_Pnt(1.0, 2.0, 3.0), 0.001); + EXPECT_FALSE(aGraph.Topo().Gen().IsRemoved(aVtxId)); + + aGraph.Builder().RemoveNode(aVtxId); + EXPECT_TRUE(aGraph.Topo().Gen().IsRemoved(aVtxId)); +} + +TEST(BRepGraph_BuilderTest, RemoveFaceFromBox) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + ASSERT_EQ(aGraph.Topo().Faces().Nb(), 6); + + BRepGraph_FaceId aFaceId(0); + EXPECT_FALSE(aGraph.Topo().Gen().IsRemoved(aFaceId)); + + aGraph.Builder().RemoveNode(aFaceId); + EXPECT_TRUE(aGraph.Topo().Gen().IsRemoved(aFaceId)); + + // Other faces should not be removed. + for (int aFaceIdx = 1; aFaceIdx < aGraph.Topo().Faces().Nb(); ++aFaceIdx) + { + EXPECT_FALSE( + aGraph.Topo().Gen().IsRemoved(BRepGraph_NodeId(BRepGraph_NodeId::Kind::Face, aFaceIdx))); + } +} + +TEST(BRepGraph_BuilderTest, RemoveInvalidNode_NoError) +{ + BRepGraph aGraph; + BRepGraph_NodeId anInvalidId; + EXPECT_FALSE(aGraph.Topo().Gen().IsRemoved(anInvalidId)); + aGraph.Builder().RemoveNode(anInvalidId); // Should not crash. +} + +TEST(BRepGraph_BuilderTest, RemoveAlreadyRemovedNode_NoError) +{ + BRepGraph aGraph; + + const BRepGraph_VertexId aVertexId = aGraph.Builder().AddVertex(gp_Pnt(1.0, 2.0, 3.0), 0.001); + ASSERT_TRUE(aVertexId.IsValid()); + + aGraph.Builder().RemoveNode(aVertexId); + EXPECT_TRUE(aGraph.Topo().Gen().IsRemoved(aVertexId)); + + EXPECT_NO_THROW(aGraph.Builder().RemoveNode(aVertexId)); + EXPECT_TRUE(aGraph.Topo().Gen().IsRemoved(aVertexId)); +} + +// ============================================================ +// Item 1: Complete Construction API (Shell/Solid linking) +// ============================================================ + +TEST(BRepGraph_BuilderTest, AddFaceToShell_CreatesUsage) +{ + BRepGraph aGraph; + + // Build a face programmatically. + BRepGraph_VertexId aV0 = aGraph.Builder().AddVertex(gp_Pnt(0, 0, 0), 0.001); + BRepGraph_VertexId aV1 = aGraph.Builder().AddVertex(gp_Pnt(10, 0, 0), 0.001); + BRepGraph_VertexId aV2 = aGraph.Builder().AddVertex(gp_Pnt(10, 10, 0), 0.001); + BRepGraph_VertexId aV3 = aGraph.Builder().AddVertex(gp_Pnt(0, 10, 0), 0.001); + + occ::handle aL0 = new Geom_Line(gp_Pnt(0, 0, 0), gp_Dir(1, 0, 0)); + occ::handle aL1 = new Geom_Line(gp_Pnt(10, 0, 0), gp_Dir(0, 1, 0)); + occ::handle aL2 = new Geom_Line(gp_Pnt(10, 10, 0), gp_Dir(-1, 0, 0)); + occ::handle aL3 = new Geom_Line(gp_Pnt(0, 10, 0), gp_Dir(0, -1, 0)); + + BRepGraph_EdgeId aE0 = aGraph.Builder().AddEdge(aV0, aV1, aL0, 0.0, 10.0, 0.001); + BRepGraph_EdgeId aE1 = aGraph.Builder().AddEdge(aV1, aV2, aL1, 0.0, 10.0, 0.001); + BRepGraph_EdgeId aE2 = aGraph.Builder().AddEdge(aV2, aV3, aL2, 0.0, 10.0, 0.001); + BRepGraph_EdgeId aE3 = aGraph.Builder().AddEdge(aV3, aV0, aL3, 0.0, 10.0, 0.001); + + NCollection_Vector> aEdges; + aEdges.Append({aE0, TopAbs_FORWARD}); + aEdges.Append({aE1, TopAbs_FORWARD}); + aEdges.Append({aE2, TopAbs_FORWARD}); + aEdges.Append({aE3, TopAbs_FORWARD}); + + BRepGraph_WireId aWireId = aGraph.Builder().AddWire(aEdges); + occ::handle aPlane = new Geom_Plane(gp_Pln()); + NCollection_Vector aInnerWires; + BRepGraph_FaceId aFaceId = aGraph.Builder().AddFace(aPlane, aWireId, aInnerWires, 0.001); + + // Create shell and link face to it. + BRepGraph_ShellId aShellId = aGraph.Builder().AddShell(); + const BRepGraph_FaceRefId aFaceRefId = aGraph.Builder().AddFaceToShell(aShellId, aFaceId); + EXPECT_TRUE(aFaceRefId.IsValid()); + + EXPECT_EQ(BRepGraph_TestTools::CountFaceRefsOfShell(aGraph, BRepGraph_ShellId(0)), 1); +} + +TEST(BRepGraph_BuilderTest, AddFaceToShell_InvalidNodes_NoMutation) +{ + BRepGraph aGraph; + + const BRepGraph_ShellId aShellId = aGraph.Builder().AddShell(); + const BRepGraph_FaceRefId aFaceRefId = + aGraph.Builder().AddFaceToShell(aShellId, BRepGraph_FaceId(4)); + EXPECT_FALSE(aFaceRefId.IsValid()); + + EXPECT_EQ(BRepGraph_TestTools::CountFaceRefsOfShell(aGraph, BRepGraph_ShellId(aShellId.Index)), + 0); +} + +TEST(BRepGraph_BuilderTest, AddShellToSolid_CreatesUsage) +{ + BRepGraph aGraph; + + BRepGraph_ShellId aShellId = aGraph.Builder().AddShell(); + BRepGraph_SolidId aSolidId = aGraph.Builder().AddSolid(); + + const BRepGraph_ShellRefId aShellRefId = aGraph.Builder().AddShellToSolid(aSolidId, aShellId); + EXPECT_TRUE(aShellRefId.IsValid()); + + EXPECT_EQ(BRepGraph_TestTools::CountShellRefsOfSolid(aGraph, BRepGraph_SolidId(0)), 1); +} + +TEST(BRepGraph_BuilderTest, MutInvalidTopologyDefs_ThrowProgramError) +{ + BRepGraph aGraph; +#if !defined(No_Exception) + EXPECT_THROW((void)aGraph.Builder().MutVertex(BRepGraph_VertexId(7)), Standard_ProgramError); + EXPECT_THROW((void)aGraph.Builder().MutEdge(BRepGraph_EdgeId(7)), Standard_ProgramError); + EXPECT_THROW((void)aGraph.Builder().MutWire(BRepGraph_WireId(7)), Standard_ProgramError); + EXPECT_THROW((void)aGraph.Builder().MutFace(BRepGraph_FaceId(7)), Standard_ProgramError); + EXPECT_THROW((void)aGraph.Builder().MutShell(BRepGraph_ShellId(7)), Standard_ProgramError); + EXPECT_THROW((void)aGraph.Builder().MutSolid(BRepGraph_SolidId(7)), Standard_ProgramError); + EXPECT_THROW((void)aGraph.Builder().MutCoEdge(BRepGraph_CoEdgeId(7)), Standard_ProgramError); +#endif +} + +TEST(BRepGraph_BuilderTest, AddCompound_WithChildren) +{ + BRepGraph aGraph; + + BRepGraph_SolidId aSolid1 = aGraph.Builder().AddSolid(); + BRepGraph_SolidId aSolid2 = aGraph.Builder().AddSolid(); + + NCollection_Vector aChildren; + aChildren.Append(aSolid1); + aChildren.Append(aSolid2); + + BRepGraph_CompoundId aCompId = aGraph.Builder().AddCompound(aChildren); + EXPECT_TRUE(aCompId.IsValid()); + EXPECT_EQ(aGraph.Topo().Compounds().Nb(), 1); + + EXPECT_EQ(BRepGraph_TestTools::CountChildRefsOfParent(aGraph, BRepGraph_CompoundId(0)), 2); +} + +TEST(BRepGraph_BuilderTest, AddCompSolid_WithSolids) +{ + BRepGraph aGraph; + + BRepGraph_SolidId aSolid1 = aGraph.Builder().AddSolid(); + BRepGraph_SolidId aSolid2 = aGraph.Builder().AddSolid(); + + NCollection_Vector aSolids; + aSolids.Append(aSolid1); + aSolids.Append(aSolid2); + + BRepGraph_CompSolidId aCSolId = aGraph.Builder().AddCompSolid(aSolids); + EXPECT_TRUE(aCSolId.IsValid()); + EXPECT_EQ(aGraph.Topo().CompSolids().Nb(), 1); + + const BRepGraphInc::CompSolidDef& aCSolDef = + aGraph.Topo().CompSolids().Definition(BRepGraph_CompSolidId(0)); + (void)aCSolDef; + EXPECT_EQ(BRepGraph_TestTools::CountSolidRefsOfCompSolid(aGraph, BRepGraph_CompSolidId(0)), 2); +} + +TEST(BRepGraph_BuilderTest, FullSolid_ProgrammaticConstruction) +{ + BRepGraph aGraph; + + // Build a single-face shell solid. + BRepGraph_VertexId aV0 = aGraph.Builder().AddVertex(gp_Pnt(0, 0, 0), 0.001); + BRepGraph_VertexId aV1 = aGraph.Builder().AddVertex(gp_Pnt(10, 0, 0), 0.001); + BRepGraph_VertexId aV2 = aGraph.Builder().AddVertex(gp_Pnt(10, 10, 0), 0.001); + BRepGraph_VertexId aV3 = aGraph.Builder().AddVertex(gp_Pnt(0, 10, 0), 0.001); + + occ::handle aL0 = new Geom_Line(gp_Pnt(0, 0, 0), gp_Dir(1, 0, 0)); + occ::handle aL1 = new Geom_Line(gp_Pnt(10, 0, 0), gp_Dir(0, 1, 0)); + occ::handle aL2 = new Geom_Line(gp_Pnt(10, 10, 0), gp_Dir(-1, 0, 0)); + occ::handle aL3 = new Geom_Line(gp_Pnt(0, 10, 0), gp_Dir(0, -1, 0)); + + BRepGraph_EdgeId aE0 = aGraph.Builder().AddEdge(aV0, aV1, aL0, 0.0, 10.0, 0.001); + BRepGraph_EdgeId aE1 = aGraph.Builder().AddEdge(aV1, aV2, aL1, 0.0, 10.0, 0.001); + BRepGraph_EdgeId aE2 = aGraph.Builder().AddEdge(aV2, aV3, aL2, 0.0, 10.0, 0.001); + BRepGraph_EdgeId aE3 = aGraph.Builder().AddEdge(aV3, aV0, aL3, 0.0, 10.0, 0.001); + + NCollection_Vector> aEdges; + aEdges.Append({aE0, TopAbs_FORWARD}); + aEdges.Append({aE1, TopAbs_FORWARD}); + aEdges.Append({aE2, TopAbs_FORWARD}); + aEdges.Append({aE3, TopAbs_FORWARD}); + + BRepGraph_WireId aWireId = aGraph.Builder().AddWire(aEdges); + occ::handle aPlane = new Geom_Plane(gp_Pln()); + NCollection_Vector aInnerWires; + BRepGraph_FaceId aFaceId = aGraph.Builder().AddFace(aPlane, aWireId, aInnerWires, 0.001); + + BRepGraph_ShellId aShellId = aGraph.Builder().AddShell(); + aGraph.Builder().AddFaceToShell(aShellId, aFaceId); + + BRepGraph_SolidId aSolidId = aGraph.Builder().AddSolid(); + aGraph.Builder().AddShellToSolid(aSolidId, aShellId); + + // Verify the hierarchy. + EXPECT_EQ(aGraph.Topo().Solids().Nb(), 1); + EXPECT_EQ(aGraph.Topo().Shells().Nb(), 1); + EXPECT_EQ(aGraph.Topo().Faces().Nb(), 1); + + EXPECT_EQ(BRepGraph_TestTools::CountShellRefsOfSolid(aGraph, BRepGraph_SolidId(0)), 1); +} + +// ============================================================ +// Item 2: Mutable Access for All Def Types +// ============================================================ + +TEST(BRepGraph_BuilderTest, MutableFaceDefinition_ChangesTolerance) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + ASSERT_GT(aGraph.Topo().Faces().Nb(), 0); + + const double anOrigTol = BRepGraph_Tool::Face::Tolerance(aGraph, BRepGraph_FaceId(0)); + { + BRepGraph_MutGuard aFaceDef = + aGraph.Builder().MutFace(BRepGraph_FaceId(0)); + aFaceDef->Tolerance = 0.5; + } + EXPECT_NEAR(BRepGraph_Tool::Face::Tolerance(aGraph, BRepGraph_FaceId(0)), 0.5, 1e-10); + EXPECT_GT(aGraph.Topo().Faces().Definition(BRepGraph_FaceId(0)).OwnGen, 0u); + (void)anOrigTol; +} + +TEST(BRepGraph_BuilderTest, MutableShellDefinition) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + ASSERT_GT(aGraph.Topo().Shells().Nb(), 0); + + { + BRepGraph_MutGuard aShellDef = + aGraph.Builder().MutShell(BRepGraph_ShellId(0)); + } + EXPECT_GT(aGraph.Topo().Shells().Definition(BRepGraph_ShellId(0)).OwnGen, 0u); +} + +TEST(BRepGraph_BuilderTest, MutableSolidDefinition) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + ASSERT_GT(aGraph.Topo().Solids().Nb(), 0); + + { + BRepGraph_MutGuard aSolidDef = + aGraph.Builder().MutSolid(BRepGraph_SolidId(0)); + } + EXPECT_GT(aGraph.Topo().Solids().Definition(BRepGraph_SolidId(0)).OwnGen, 0u); +} + +TEST(BRepGraph_BuilderTest, MutableCompoundDefinition) +{ + BRepGraph aGraph; + NCollection_Vector aChildren; + (void)aGraph.Builder().AddCompound(aChildren); + ASSERT_EQ(aGraph.Topo().Compounds().Nb(), 1); + + { + BRepGraph_MutGuard aCompDef = + aGraph.Builder().MutCompound(BRepGraph_CompoundId(0)); + } + EXPECT_GT(aGraph.Topo().Compounds().Definition(BRepGraph_CompoundId(0)).OwnGen, 0u); +} + +TEST(BRepGraph_BuilderTest, MutableCompSolidDefinition) +{ + BRepGraph aGraph; + NCollection_Vector aSolids; + (void)aGraph.Builder().AddCompSolid(aSolids); + ASSERT_EQ(aGraph.Topo().CompSolids().Nb(), 1); + + { + BRepGraph_MutGuard aCSolDef = + aGraph.Builder().MutCompSolid(BRepGraph_CompSolidId(0)); + } + EXPECT_GT(aGraph.Topo().CompSolids().Definition(BRepGraph_CompSolidId(0)).OwnGen, 0u); +} + +// ============================================================ +// Item 3: Definition Traversal Skips Removed Nodes +// ============================================================ + +TEST(BRepGraph_BuilderTest, SkipsRemovedFaces) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + ASSERT_EQ(aGraph.Topo().Faces().Nb(), 6); + + // Remove 2 faces. + aGraph.Builder().RemoveNode(BRepGraph_NodeId(BRepGraph_NodeId::Kind::Face, 0)); + aGraph.Builder().RemoveNode(BRepGraph_NodeId(BRepGraph_NodeId::Kind::Face, 3)); + + int aCount = 0; + for (BRepGraph_FaceIterator aFaceIt(aGraph); aFaceIt.More(); aFaceIt.Next()) + { + const BRepGraphInc::FaceDef& aFaceDef = aFaceIt.Current(); + EXPECT_FALSE(aFaceDef.IsRemoved); + ++aCount; + } + EXPECT_EQ(aCount, 4); +} + +TEST(BRepGraph_BuilderTest, SkipsRemovedEdges) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + const int aNbEdges = aGraph.Topo().Edges().Nb(); + ASSERT_GT(aNbEdges, 0); + + aGraph.Builder().RemoveNode(BRepGraph_NodeId(BRepGraph_NodeId::Kind::Edge, 0)); + + int aCount = 0; + for (BRepGraph_EdgeIterator anEdgeIt(aGraph); anEdgeIt.More(); anEdgeIt.Next()) + { + const BRepGraphInc::EdgeDef& anEdgeDef = anEdgeIt.Current(); + EXPECT_FALSE(anEdgeDef.IsRemoved); + ++aCount; + } + EXPECT_EQ(aCount, aNbEdges - 1); +} + +TEST(BRepGraph_BuilderTest, SkipsFirstNode) +{ + BRepGraph aGraph; + (void)aGraph.Builder().AddVertex(gp_Pnt(0, 0, 0), 0.001); + (void)aGraph.Builder().AddVertex(gp_Pnt(1, 0, 0), 0.001); + (void)aGraph.Builder().AddVertex(gp_Pnt(2, 0, 0), 0.001); + + // Remove the first vertex. + aGraph.Builder().RemoveNode(BRepGraph_NodeId(BRepGraph_NodeId::Kind::Vertex, 0)); + + int aCount = 0; + for (BRepGraph_VertexIterator aVertexIt(aGraph); aVertexIt.More(); aVertexIt.Next()) + { + const BRepGraphInc::VertexDef& aVertexDef = aVertexIt.Current(); + EXPECT_FALSE(aVertexDef.IsRemoved); + ++aCount; + } + EXPECT_EQ(aCount, 2); +} + +// ============================================================ +// Item 4: Cascading Soft Removal +// ============================================================ + +TEST(BRepGraph_BuilderTest, RemoveFace_RemovesWiresAndEdges) +{ + BRepGraph aGraph; + + BRepGraph_VertexId aV0 = aGraph.Builder().AddVertex(gp_Pnt(0, 0, 0), 0.001); + BRepGraph_VertexId aV1 = aGraph.Builder().AddVertex(gp_Pnt(10, 0, 0), 0.001); + BRepGraph_VertexId aV2 = aGraph.Builder().AddVertex(gp_Pnt(10, 10, 0), 0.001); + BRepGraph_VertexId aV3 = aGraph.Builder().AddVertex(gp_Pnt(0, 10, 0), 0.001); + + occ::handle aL0 = new Geom_Line(gp_Pnt(0, 0, 0), gp_Dir(1, 0, 0)); + occ::handle aL1 = new Geom_Line(gp_Pnt(10, 0, 0), gp_Dir(0, 1, 0)); + occ::handle aL2 = new Geom_Line(gp_Pnt(10, 10, 0), gp_Dir(-1, 0, 0)); + occ::handle aL3 = new Geom_Line(gp_Pnt(0, 10, 0), gp_Dir(0, -1, 0)); + + BRepGraph_EdgeId aE0 = aGraph.Builder().AddEdge(aV0, aV1, aL0, 0.0, 10.0, 0.001); + BRepGraph_EdgeId aE1 = aGraph.Builder().AddEdge(aV1, aV2, aL1, 0.0, 10.0, 0.001); + BRepGraph_EdgeId aE2 = aGraph.Builder().AddEdge(aV2, aV3, aL2, 0.0, 10.0, 0.001); + BRepGraph_EdgeId aE3 = aGraph.Builder().AddEdge(aV3, aV0, aL3, 0.0, 10.0, 0.001); + + NCollection_Vector> aEdges; + aEdges.Append({aE0, TopAbs_FORWARD}); + aEdges.Append({aE1, TopAbs_FORWARD}); + aEdges.Append({aE2, TopAbs_FORWARD}); + aEdges.Append({aE3, TopAbs_FORWARD}); + + BRepGraph_WireId aWireId = aGraph.Builder().AddWire(aEdges); + occ::handle aPlane = new Geom_Plane(gp_Pln()); + NCollection_Vector aInnerWires; + BRepGraph_FaceId aFaceId = aGraph.Builder().AddFace(aPlane, aWireId, aInnerWires, 0.001); + + // Remove the face subgraph. + aGraph.Builder().RemoveSubgraph(aFaceId); + + EXPECT_TRUE(aGraph.Topo().Gen().IsRemoved(aFaceId)); + EXPECT_TRUE(aGraph.Topo().Gen().IsRemoved(aWireId)); + EXPECT_TRUE(aGraph.Topo().Gen().IsRemoved(aE0)); + EXPECT_TRUE(aGraph.Topo().Gen().IsRemoved(aE1)); + EXPECT_TRUE(aGraph.Topo().Gen().IsRemoved(aE2)); + EXPECT_TRUE(aGraph.Topo().Gen().IsRemoved(aE3)); + EXPECT_TRUE(aGraph.Topo().Gen().IsRemoved(aV0)); + EXPECT_TRUE(aGraph.Topo().Gen().IsRemoved(aV1)); + EXPECT_TRUE(aGraph.Topo().Gen().IsRemoved(aV2)); + EXPECT_TRUE(aGraph.Topo().Gen().IsRemoved(aV3)); +} + +TEST(BRepGraph_BuilderTest, RemoveSolid_CascadesToFaces) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + + BRepGraph_SolidId aSolidId(0); + aGraph.Builder().RemoveSubgraph(aSolidId); + + EXPECT_TRUE(aGraph.Topo().Gen().IsRemoved(aSolidId)); + + // All shells should be removed. + for (int aIdx = 0; aIdx < aGraph.Topo().Shells().Nb(); ++aIdx) + EXPECT_TRUE( + aGraph.Topo().Gen().IsRemoved(BRepGraph_NodeId(BRepGraph_NodeId::Kind::Shell, aIdx))); + + // All faces should be removed. + for (int aIdx = 0; aIdx < aGraph.Topo().Faces().Nb(); ++aIdx) + EXPECT_TRUE( + aGraph.Topo().Gen().IsRemoved(BRepGraph_NodeId(BRepGraph_NodeId::Kind::Face, aIdx))); +} + +TEST(BRepGraph_BuilderTest, RemoveSubgraph_SharedFace_PreservesSharedEdgesAndVertices) +{ + // Build a box (6 faces). Remove one face. Shared edges and vertices must + // remain active because other faces still reference them. + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + + const int aNbFaces = aGraph.Topo().Faces().Nb(); + const int aNbEdges = aGraph.Topo().Edges().Nb(); + const int aNbVertices = aGraph.Topo().Vertices().Nb(); + ASSERT_EQ(aNbFaces, 6); + ASSERT_EQ(aNbEdges, 12); + ASSERT_EQ(aNbVertices, 8); + + // Remove face 0. + const BRepGraph_FaceId aRemovedFace(0); + aGraph.Builder().RemoveSubgraph(aRemovedFace); + + // The removed face must be removed. + EXPECT_TRUE(aGraph.Topo().Gen().IsRemoved(aRemovedFace)); + + // Other faces must remain active. + for (int aIdx = 1; aIdx < aNbFaces; ++aIdx) + EXPECT_FALSE(aGraph.Topo().Gen().IsRemoved(BRepGraph_FaceId(aIdx))); + + // All 12 edges of a box are shared by exactly 2 faces. + // Removing 1 face leaves each shared edge with at least 1 active parent face. + // Therefore all edges must remain active. + for (int aIdx = 0; aIdx < aNbEdges; ++aIdx) + EXPECT_FALSE(aGraph.Topo().Gen().IsRemoved(BRepGraph_EdgeId(aIdx))); + + // All 8 vertices of a box are shared by 3 edges. All edges are still active, + // so all vertices must remain active. + for (int aIdx = 0; aIdx < aNbVertices; ++aIdx) + EXPECT_FALSE(aGraph.Topo().Gen().IsRemoved(BRepGraph_VertexId(aIdx))); +} + +// ============================================================ +// Item 5: Edge Adjacency Queries +// ============================================================ + +TEST(BRepGraph_BuilderTest, FacesOfEdge_BoxSharedEdge) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + + // Every edge in a box is shared by exactly 2 faces. + for (int anEdgeIdx = 0; anEdgeIdx < aGraph.Topo().Edges().Nb(); ++anEdgeIdx) + { + BRepGraph_EdgeId anEdgeId(anEdgeIdx); + NCollection_Vector aFaces = aGraph.Topo().Edges().Faces(anEdgeId); + EXPECT_EQ(aFaces.Length(), 2) << "Edge " << anEdgeIdx << " has " << aFaces.Length() << " faces"; + } +} + +TEST(BRepGraph_BuilderTest, SharedEdges_AdjacentBoxFaces) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + ASSERT_EQ(aGraph.Topo().Faces().Nb(), 6); + + // Count total shared edge pairs across all face pairs. + int aSharingPairs = 0; + for (int aFaceA = 0; aFaceA < aGraph.Topo().Faces().Nb(); ++aFaceA) + { + for (int aFaceB = aFaceA + 1; aFaceB < aGraph.Topo().Faces().Nb(); ++aFaceB) + { + NCollection_Vector aShared = + aGraph.Topo().Faces().SharedEdges(BRepGraph_FaceId(aFaceA), + BRepGraph_FaceId(aFaceB), + aGraph.Allocator()); + if (!aShared.IsEmpty()) + ++aSharingPairs; + } + } + // A box has 12 edges, each shared by 2 faces, so 12 sharing pairs. + EXPECT_EQ(aSharingPairs, 12); +} + +TEST(BRepGraph_BuilderTest, AdjacentFaces_BoxFace) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + ASSERT_EQ(aGraph.Topo().Faces().Nb(), 6); + + // Each face of a box is adjacent to 4 other faces. + for (int aFaceIdx = 0; aFaceIdx < aGraph.Topo().Faces().Nb(); ++aFaceIdx) + { + BRepGraph_FaceId aFaceId(aFaceIdx); + NCollection_Vector aAdj = + aGraph.Topo().Faces().Adjacent(aFaceId, aGraph.Allocator()); + EXPECT_EQ(aAdj.Length(), 4) << "Face " << aFaceIdx << " has " << aAdj.Length() + << " adjacent faces"; + } +} + +TEST(BRepGraph_BuilderTest, FacesOfEdge_NoFaces_Programmatic) +{ + BRepGraph aGraph; + BRepGraph_VertexId aV0 = aGraph.Builder().AddVertex(gp_Pnt(0, 0, 0), 0.001); + BRepGraph_VertexId aV1 = aGraph.Builder().AddVertex(gp_Pnt(10, 0, 0), 0.001); + + occ::handle aLine = new Geom_Line(gp_Pnt(0, 0, 0), gp_Dir(1, 0, 0)); + BRepGraph_EdgeId anEdgeId = aGraph.Builder().AddEdge(aV0, aV1, aLine, 0.0, 10.0, 0.001); + + // Edge not in any face => empty result. + const NCollection_Vector& aFaces = aGraph.Topo().Edges().Faces(anEdgeId); + EXPECT_EQ(aFaces.Length(), 0); +} + +// ============ Topology adjacency methods ============ + +TEST(BRepGraph_BuilderTest, EdgesOfFace_Box_HasEdges) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10, 20, 30).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + // Each box face has 4 edges (rectangular loop). + int aNbEdges = 0; + for (BRepGraph_ChildExplorer anExp(aGraph, BRepGraph_FaceId(0), BRepGraph_NodeId::Kind::Edge); + anExp.More(); + anExp.Next()) + ++aNbEdges; + EXPECT_EQ(aNbEdges, 4); +} + +TEST(BRepGraph_BuilderTest, VerticesOfEdge_Box_HasTwoVertices) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10, 20, 30).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + int aNbVertices = 0; + for (BRepGraph_ChildExplorer anExp(aGraph, BRepGraph_EdgeId(0), BRepGraph_NodeId::Kind::Vertex); + anExp.More(); + anExp.Next()) + ++aNbVertices; + EXPECT_EQ(aNbVertices, 2); +} + +TEST(BRepGraph_BuilderTest, EdgesOfVertex_Box_ThreeEdges) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10, 20, 30).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + // Each box corner vertex is shared by 3 edges. + const NCollection_Vector& aEdges = + aGraph.Topo().Vertices().Edges(BRepGraph_VertexId(0)); + EXPECT_EQ(aEdges.Length(), 3); +} + +TEST(BRepGraph_BuilderTest, AdjacentEdges_Box_SharedVertex) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10, 20, 30).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + // Box edge shares 2 vertices, each with 3 incident edges. + // Adjacent = (3 - 1) + (3 - 1) - overlap = at least 4 adjacent edges. + NCollection_Vector aAdj = + aGraph.Topo().Edges().Adjacent(BRepGraph_EdgeId(0), aGraph.Allocator()); + EXPECT_GE(aAdj.Length(), 4); +} + +TEST(BRepGraph_BuilderTest, NbFacesOfEdge_Box_TwoFaces) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10, 20, 30).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + // Every box edge is shared by exactly 2 faces (manifold). + EXPECT_EQ(aGraph.Topo().Edges().NbFaces(BRepGraph_EdgeId(0)), 2); +} + +TEST(BRepGraph_BuilderTest, IsManifoldEdge_Box_True) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10, 20, 30).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + EXPECT_TRUE(aGraph.Topo().Edges().IsManifold(BRepGraph_EdgeId(0))); + EXPECT_FALSE(aGraph.Topo().Edges().IsBoundary(BRepGraph_EdgeId(0))); +} + +TEST(BRepGraph_BuilderTest, InvalidInput_ReturnsEmpty) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10, 20, 30).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + // Out-of-range typed ids return empty results. + EXPECT_EQ(aGraph.Topo().Vertices().Edges(BRepGraph_VertexId(999)).Length(), 0); + EXPECT_EQ(aGraph.Topo().Edges().Adjacent(BRepGraph_EdgeId(999), aGraph.Allocator()).Length(), 0); +} diff --git a/src/ModelingData/TKBRep/GTests/BRepGraph_CacheKindRegistry_Test.cxx b/src/ModelingData/TKBRep/GTests/BRepGraph_CacheKindRegistry_Test.cxx new file mode 100644 index 0000000000..b717b76ccd --- /dev/null +++ b/src/ModelingData/TKBRep/GTests/BRepGraph_CacheKindRegistry_Test.cxx @@ -0,0 +1,61 @@ +// Copyright (c) 2026 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 + +TEST(BRepGraph_CacheKindRegistryTest, Register_SameGUID_SameSlot) +{ + const Standard_GUID aGUID("a1b2c3d4-1111-2222-3333-444455556666"); + const occ::handle aKind1 = new BRepGraph_CacheKind(aGUID, "SameGUID"); + const occ::handle aKind2 = new BRepGraph_CacheKind(aGUID, "SameGUID"); + const int aSlot1 = BRepGraph_CacheKindRegistry::Register(aKind1); + const int aSlot2 = BRepGraph_CacheKindRegistry::Register(aKind2); + EXPECT_EQ(aSlot1, aSlot2); +} + +TEST(BRepGraph_CacheKindRegistryTest, Register_DifferentGUID_DifferentSlot) +{ + const occ::handle aKind1 = + new BRepGraph_CacheKind(Standard_GUID("b1b2c3d4-aaaa-bbbb-cccc-ddddeeee0001"), "Kind1"); + const occ::handle aKind2 = + new BRepGraph_CacheKind(Standard_GUID("b1b2c3d4-aaaa-bbbb-cccc-ddddeeee0002"), "Kind2"); + const int aSlot1 = BRepGraph_CacheKindRegistry::Register(aKind1); + const int aSlot2 = BRepGraph_CacheKindRegistry::Register(aKind2); + EXPECT_NE(aSlot1, aSlot2); +} + +TEST(BRepGraph_CacheKindRegistryTest, FindSlot_ByGUID_ReturnsCorrectSlot) +{ + const occ::handle aKind = + new BRepGraph_CacheKind(Standard_GUID("c1c2c3c4-1111-2222-3333-aabbccddeeff"), "FindByGUID"); + const int aExpectedSlot = BRepGraph_CacheKindRegistry::Register(aKind); + + int aFoundSlot = -1; + const bool aOk = BRepGraph_CacheKindRegistry::FindSlot(aKind->ID(), aFoundSlot); + EXPECT_TRUE(aOk); + EXPECT_EQ(aFoundSlot, aExpectedSlot); +} + +TEST(BRepGraph_CacheKindRegistryTest, FindKind_BySlot_ReturnsCorrectDescriptor) +{ + const occ::handle aKind = + new BRepGraph_CacheKind(Standard_GUID("d1d2d3d4-5555-6666-7777-888899990000"), "FindBySlot"); + const int aSlot = BRepGraph_CacheKindRegistry::Register(aKind); + + const occ::handle aFound = BRepGraph_CacheKindRegistry::FindKind(aSlot); + ASSERT_FALSE(aFound.IsNull()); + EXPECT_TRUE(aFound->ID() == aKind->ID()); + EXPECT_TRUE(aFound->Name().IsEqual("FindBySlot")); +} diff --git a/src/ModelingData/TKBRep/GTests/BRepGraph_ChildExplorer_Test.cxx b/src/ModelingData/TKBRep/GTests/BRepGraph_ChildExplorer_Test.cxx new file mode 100644 index 0000000000..7f9b07d894 --- /dev/null +++ b/src/ModelingData/TKBRep/GTests/BRepGraph_ChildExplorer_Test.cxx @@ -0,0 +1,841 @@ +// Copyright (c) 2026 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 + +namespace +{ +static BRepGraph_ChildExplorer makeDirectChildExplorer(const BRepGraph& theGraph, + const BRepGraph_NodeId theRoot, + BRepGraph_NodeId::Kind theTargetKind) +{ + return BRepGraph_ChildExplorer(theGraph, + theRoot, + theTargetKind, + BRepGraph_ChildExplorer::TraversalMode::DirectChildren); +} + +static BRepGraph_ChildExplorer makeDirectChildExplorer(const BRepGraph& theGraph, + const BRepGraph_NodeId theRoot, + BRepGraph_NodeId::Kind theTargetKind, + const TopLoc_Location& theLoc, + const TopAbs_Orientation theOri) +{ + return BRepGraph_ChildExplorer(theGraph, + theRoot, + theTargetKind, + theLoc, + theOri, + BRepGraph_ChildExplorer::TraversalMode::DirectChildren); +} +} // namespace + +// --- Explorer tests --- + +TEST(BRepGraph_ChildExplorerTest, Box_EdgeOccurrences_Count24) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10, 20, 30).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + int aCount = 0; + for (BRepGraph_ChildExplorer anExp(aGraph, BRepGraph_SolidId(0), BRepGraph_NodeId::Kind::Edge); + anExp.More(); + anExp.Next()) + ++aCount; + // 12 edges x 2 faces each = 24 edge occurrences. + EXPECT_EQ(aCount, 24); +} + +TEST(BRepGraph_ChildExplorerTest, Box_FaceOccurrences_Count6) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10, 20, 30).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + int aFaceCount = 0; + for (BRepGraph_ChildExplorer anExp(aGraph, BRepGraph_SolidId(0), BRepGraph_NodeId::Kind::Face); + anExp.More(); + anExp.Next()) + ++aFaceCount; + EXPECT_EQ(aFaceCount, 6); +} + +TEST(BRepGraph_ChildExplorerTest, Box_VertexOccurrences) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10, 20, 30).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + // Count unique vertices. + int aCount = 0; + NCollection_Map aVisited; + for (BRepGraph_ChildExplorer anExp(aGraph, BRepGraph_SolidId(0), BRepGraph_NodeId::Kind::Vertex); + anExp.More(); + anExp.Next()) + { + aVisited.Add(anExp.Current().DefId.Index); + ++aCount; + } + EXPECT_GT(aCount, 0); + EXPECT_EQ(aVisited.Extent(), 8); +} + +TEST(BRepGraph_ChildExplorerTest, Face_EdgeOccurrences_4) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10, 20, 30).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + int aCount = 0; + for (BRepGraph_ChildExplorer anExp(aGraph, BRepGraph_FaceId(0), BRepGraph_NodeId::Kind::Edge); + anExp.More(); + anExp.Next()) + ++aCount; + EXPECT_EQ(aCount, 4); +} + +TEST(BRepGraph_ChildExplorerTest, InvalidRoot_Empty) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10, 20, 30).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + BRepGraph_ChildExplorer anExp(aGraph, BRepGraph_NodeId(), BRepGraph_NodeId::Kind::Edge); + EXPECT_FALSE(anExp.More()); +} + +TEST(BRepGraph_ChildExplorerTest, RootEqualsTarget_ReturnsSelf) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10, 20, 30).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + BRepGraph_ChildExplorer anExp(aGraph, BRepGraph_FaceId(0), BRepGraph_NodeId::Kind::Face); + ASSERT_TRUE(anExp.More()); + EXPECT_EQ(anExp.Current().DefId, BRepGraph_FaceId(0)); + anExp.Next(); + EXPECT_FALSE(anExp.More()); +} + +TEST(BRepGraph_ChildExplorerTest, AvoidKind_Shell_SkipsContainedFaces) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10, 20, 30).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + BRepGraph_ChildExplorer anExp(aGraph, + BRepGraph_SolidId(0), + BRepGraph_NodeId::Kind::Face, + BRepGraph_NodeId::Kind::Shell, + false); + EXPECT_FALSE(anExp.More()); +} + +TEST(BRepGraph_ChildExplorerTest, AvoidKind_EmitBoundary_ReturnsFacesInsteadOfEdges) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10, 20, 30).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + int aFaceCount = 0; + for (BRepGraph_ChildExplorer anExp(aGraph, + BRepGraph_SolidId(0), + BRepGraph_NodeId::Kind::Edge, + BRepGraph_NodeId::Kind::Face, + true); + anExp.More(); + anExp.Next()) + { + EXPECT_EQ(anExp.Current().DefId.NodeKind, BRepGraph_NodeId::Kind::Face); + ++aFaceCount; + } + EXPECT_EQ(aFaceCount, 6); +} + +TEST(BRepGraph_ChildExplorerTest, AvoidKind_SameAsTarget_IsIgnored) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10, 20, 30).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + int aFaceCount = 0; + for (BRepGraph_ChildExplorer anExp(aGraph, + BRepGraph_SolidId(0), + BRepGraph_NodeId::Kind::Face, + BRepGraph_NodeId::Kind::Face, + false); + anExp.More(); + anExp.Next()) + { + EXPECT_EQ(anExp.Current().DefId.NodeKind, BRepGraph_NodeId::Kind::Face); + ++aFaceCount; + } + EXPECT_EQ(aFaceCount, 6); +} + +TEST(BRepGraph_ChildExplorerTest, AllDescendants_Recursive_YieldsAllKinds) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10, 20, 30).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + int aShellCount = 0; + int aFaceCount = 0; + int aWireCount = 0; + int aCoEdgeCount = 0; + int anEdgeCount = 0; + int aVertexCount = 0; + for (BRepGraph_ChildExplorer anExp(aGraph, BRepGraph_SolidId(0)); anExp.More(); anExp.Next()) + { + switch (anExp.Current().DefId.NodeKind) + { + case BRepGraph_NodeId::Kind::Shell: + ++aShellCount; + break; + case BRepGraph_NodeId::Kind::Face: + ++aFaceCount; + break; + case BRepGraph_NodeId::Kind::Wire: + ++aWireCount; + break; + case BRepGraph_NodeId::Kind::CoEdge: + ++aCoEdgeCount; + break; + case BRepGraph_NodeId::Kind::Edge: + ++anEdgeCount; + break; + case BRepGraph_NodeId::Kind::Vertex: + ++aVertexCount; + break; + default: + FAIL() << "Unexpected kind in unfiltered child traversal"; + break; + } + } + + EXPECT_EQ(aShellCount, 1); + EXPECT_EQ(aFaceCount, 6); + EXPECT_EQ(aWireCount, 6); + EXPECT_EQ(aCoEdgeCount, 24); // 4 CoEdges per face, 6 faces + EXPECT_EQ(anEdgeCount, 24); // Each CoEdge leads to one Edge (occurrence-based) + EXPECT_EQ(aVertexCount, 48); // Each Edge visited with 2 vertices +} + +TEST(BRepGraph_ChildExplorerTest, AllDescendants_AvoidFaceBoundary_StopsBelowFaces) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10, 20, 30).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + int aShellCount = 0; + int aFaceCount = 0; + for (BRepGraph_ChildExplorer anExp(aGraph, + BRepGraph_SolidId(0), + BRepGraph_NodeId::Kind::Face, + true); + anExp.More(); + anExp.Next()) + { + if (anExp.Current().DefId.NodeKind == BRepGraph_NodeId::Kind::Shell) + { + ++aShellCount; + continue; + } + + EXPECT_EQ(anExp.Current().DefId.NodeKind, BRepGraph_NodeId::Kind::Face); + ++aFaceCount; + } + + EXPECT_EQ(aShellCount, 1); + EXPECT_EQ(aFaceCount, 6); +} + +// --- Cached location/orientation tests --- + +TEST(BRepGraph_ChildExplorerTest, NoCumLoc_IdentityLocation) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10, 20, 30).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + for (BRepGraph_ChildExplorer + anExp(aGraph, BRepGraph_SolidId(0), BRepGraph_NodeId::Kind::Edge, false, true); + anExp.More(); + anExp.Next()) + { + EXPECT_TRUE(anExp.Current().Location.IsIdentity()); + } +} + +TEST(BRepGraph_ChildExplorerTest, NoCumOri_ForwardOrientation) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10, 20, 30).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + for (BRepGraph_ChildExplorer + anExp(aGraph, BRepGraph_SolidId(0), BRepGraph_NodeId::Kind::Edge, true, false); + anExp.More(); + anExp.Next()) + { + EXPECT_EQ(anExp.Current().Orientation, TopAbs_FORWARD); + } +} + +// --- Path-based composition tests --- + +TEST(BRepGraph_ChildExplorerTest, GlobalLocation_Box_Identity) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10, 20, 30).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + // All paths in a simple box should compose to identity. + BRepGraph_ChildExplorer anExp(aGraph, BRepGraph_SolidId(0), BRepGraph_NodeId::Kind::Edge); + for (; anExp.More(); anExp.Next()) + { + EXPECT_TRUE(anExp.Current().Location.IsIdentity()); + } +} + +TEST(BRepGraph_ChildExplorerTest, GlobalOrientation_BoxEdges_ForwardOrReversed) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10, 20, 30).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + BRepGraph_ChildExplorer anExp(aGraph, BRepGraph_SolidId(0), BRepGraph_NodeId::Kind::Edge); + for (; anExp.More(); anExp.Next()) + { + TopAbs_Orientation anOri = anExp.Current().Orientation; + EXPECT_TRUE(anOri == TopAbs_FORWARD || anOri == TopAbs_REVERSED); + } +} + +// --- Compound tests --- + +TEST(BRepGraph_ChildExplorerTest, Compound_FaceCount) +{ + // Build a compound of two boxes. + TopoDS_Compound aComp; + BRep_Builder aBB; + aBB.MakeCompound(aComp); + aBB.Add(aComp, BRepPrimAPI_MakeBox(10, 10, 10).Shape()); + aBB.Add(aComp, BRepPrimAPI_MakeBox(20, 20, 20).Shape()); + + BRepGraph aGraph; + aGraph.Build(aComp); + ASSERT_TRUE(aGraph.IsDone()); + + int aCount = 0; + for (BRepGraph_ChildExplorer anExp(aGraph, BRepGraph_CompoundId(0), BRepGraph_NodeId::Kind::Face); + anExp.More(); + anExp.Next()) + ++aCount; + EXPECT_EQ(aCount, 12); // 6 + 6 faces +} + +// --- Explorer convenience tests --- + +TEST(BRepGraph_ChildExplorerTest, NodeOf_Kind_Face) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10, 20, 30).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + BRepGraph_ChildExplorer anExp(aGraph, BRepGraph_SolidId(0), BRepGraph_NodeId::Kind::Edge); + ASSERT_TRUE(anExp.More()); + + BRepGraph_NodeId aFace = anExp.NodeOf(BRepGraph_NodeId::Kind::Face); + EXPECT_TRUE(aFace.IsValid()); + EXPECT_EQ(aFace.NodeKind, BRepGraph_NodeId::Kind::Face); +} + +// --- Depth guard and re-initialization tests --- + +TEST(BRepGraph_ChildExplorerTest, DeepCompound_NoStackOverflow) +{ + // Build a deeply nested compound: Compound(Compound(Compound(...(Box)...))). + TopoDS_Shape aInner = BRepPrimAPI_MakeBox(1, 1, 1).Shape(); + constexpr int THE_NESTING_DEPTH = 100; + for (int i = 0; i < THE_NESTING_DEPTH; ++i) + { + TopoDS_Compound aComp; + BRep_Builder aBB; + aBB.MakeCompound(aComp); + aBB.Add(aComp, aInner); + aInner = aComp; + } + BRepGraph aGraph; + aGraph.Build(aInner); + ASSERT_TRUE(aGraph.IsDone()); + + // Should not crash (stack overflow) and should find the box's faces. + int aCount = 0; + for (BRepGraph_ChildExplorer anExp(aGraph, BRepGraph_CompoundId(0), BRepGraph_NodeId::Kind::Face); + anExp.More(); + anExp.Next()) + ++aCount; + EXPECT_EQ(aCount, 6); +} + +TEST(BRepGraph_ChildExplorerTest, Recreate_ResetAndReexplore) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10, 20, 30).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + int aFaceCount = 0; + BRepGraph_ChildExplorer anExp(aGraph, BRepGraph_SolidId(0), BRepGraph_NodeId::Kind::Face); + for (; anExp.More(); anExp.Next()) + ++aFaceCount; + EXPECT_EQ(aFaceCount, 6); + + // Recreate targeting edges. + int aEdgeCount = 0; + for (BRepGraph_ChildExplorer anEdgeExp(aGraph, + BRepGraph_SolidId(0), + BRepGraph_NodeId::Kind::Edge); + anEdgeExp.More(); + anEdgeExp.Next()) + ++aEdgeCount; + EXPECT_EQ(aEdgeCount, 24); +} + +// ============ Regression tests: CoEdge/Occurrence target reachability ============ + +TEST(BRepGraph_ChildExplorerTest, CoEdgeTarget_Reachable) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10, 20, 30).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + // CoEdge target from Solid must find all coedges (24 edge occurrences = 24 coedges). + int aCount = 0; + for (BRepGraph_ChildExplorer anExp(aGraph, BRepGraph_SolidId(0), BRepGraph_NodeId::Kind::CoEdge); + anExp.More(); + anExp.Next()) + { + EXPECT_EQ(anExp.Current().DefId.NodeKind, BRepGraph_NodeId::Kind::CoEdge); + ++aCount; + } + EXPECT_EQ(aCount, 24); +} + +TEST(BRepGraph_ChildExplorerTest, CoEdgeTarget_FromFace_Count4) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10, 20, 30).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + int aCount = 0; + for (BRepGraph_ChildExplorer anExp(aGraph, BRepGraph_FaceId(0), BRepGraph_NodeId::Kind::CoEdge); + anExp.More(); + anExp.Next()) + { + EXPECT_EQ(anExp.Current().DefId.NodeKind, BRepGraph_NodeId::Kind::CoEdge); + ++aCount; + } + EXPECT_EQ(aCount, 4); +} + +TEST(BRepGraph_ChildExplorerTest, DirectChildren_ShellFaces_CountAndOrder) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10, 20, 30).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + const BRepGraph_ShellId aShellId(0); + const NCollection_Vector& aFaceRefIds = + aGraph.Refs().Faces().IdsOf(aShellId); + + NCollection_Vector anExpectedFaceIds; + for (const BRepGraph_FaceRefId& aFaceRefId : aFaceRefIds) + { + const BRepGraphInc::FaceRef& aRef = aGraph.Refs().Faces().Entry(aFaceRefId); + if (!aRef.IsRemoved) + anExpectedFaceIds.Append(aRef.FaceDefId.Index); + } + + NCollection_Vector anActualFaceIds; + for (BRepGraph_ChildExplorer anIt = + makeDirectChildExplorer(aGraph, aShellId, BRepGraph_NodeId::Kind::Face); + anIt.More(); + anIt.Next()) + { + ASSERT_EQ(anIt.Current().DefId.NodeKind, BRepGraph_NodeId::Kind::Face); + anActualFaceIds.Append(anIt.Current().DefId.Index); + } + + ASSERT_EQ(anActualFaceIds.Length(), anExpectedFaceIds.Length()); + for (int i = 0; i < anExpectedFaceIds.Length(); ++i) + EXPECT_EQ(anActualFaceIds.Value(i), anExpectedFaceIds.Value(i)); +} + +TEST(BRepGraph_ChildExplorerTest, DirectChildren_RemovedFaceRef_IsSkipped) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10, 20, 30).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + const BRepGraph_ShellId aShellId(0); + const NCollection_Vector& aFaceRefIds = + aGraph.Refs().Faces().IdsOf(aShellId); + ASSERT_GT(aFaceRefIds.Length(), 0); + + const BRepGraph_FaceRefId aRemovedRef = aFaceRefIds.Value(0); + const BRepGraph_FaceId aRemovedFaceId = aGraph.Refs().Faces().Entry(aRemovedRef).FaceDefId; + + { + BRepGraph_MutGuard aFaceRef = aGraph.Builder().MutFaceRef(aRemovedRef); + aFaceRef->IsRemoved = true; + } + + int aCount = 0; + for (BRepGraph_ChildExplorer anIt = + makeDirectChildExplorer(aGraph, aShellId, BRepGraph_NodeId::Kind::Face); + anIt.More(); + anIt.Next()) + { + EXPECT_NE(anIt.Current().DefId, BRepGraph_NodeId(aRemovedFaceId)); + ++aCount; + } + + EXPECT_EQ(aCount, aFaceRefIds.Length() - 1); +} + +TEST(BRepGraph_ChildExplorerTest, DirectChildren_WireChildren_AreCoEdges) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10, 20, 30).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + const BRepGraph_FaceId aFaceId(0); + const NCollection_Vector& aWireRefs = aGraph.Refs().Wires().IdsOf(aFaceId); + ASSERT_GT(aWireRefs.Length(), 0); + + const BRepGraph_WireId aWireId = aGraph.Refs().Wires().Entry(aWireRefs.Value(0)).WireDefId; + const NCollection_Vector& aCoEdgeRefs = + aGraph.Refs().CoEdges().IdsOf(aWireId); + + // Wire's direct children are CoEdges (no 1:1 collapse). + int aCount = 0; + for (BRepGraph_ChildExplorer anIt = + makeDirectChildExplorer(aGraph, aWireId, BRepGraph_NodeId::Kind::CoEdge); + anIt.More(); + anIt.Next()) + { + EXPECT_EQ(anIt.Current().DefId.NodeKind, BRepGraph_NodeId::Kind::CoEdge); + ++aCount; + } + + EXPECT_EQ(aCount, aCoEdgeRefs.Length()); +} + +TEST(BRepGraph_ChildExplorerTest, DirectChildren_CompoundChildren_Basic) +{ + TopoDS_Compound aComp; + BRep_Builder aBuilder; + aBuilder.MakeCompound(aComp); + aBuilder.Add(aComp, BRepPrimAPI_MakeBox(10, 10, 10).Shape()); + aBuilder.Add(aComp, BRepPrimAPI_MakeBox(20, 20, 20).Shape()); + + BRepGraph aGraph; + aGraph.Build(aComp); + ASSERT_TRUE(aGraph.IsDone()); + + int aCount = 0; + for (BRepGraph_ChildExplorer anIt = + makeDirectChildExplorer(aGraph, BRepGraph_CompoundId(0), BRepGraph_NodeId::Kind::Solid); + anIt.More(); + anIt.Next()) + { + EXPECT_TRUE(BRepGraph_NodeId::IsTopologyKind(anIt.Current().DefId.NodeKind)); + ++aCount; + } + + EXPECT_EQ(aCount, 2); +} + +TEST(BRepGraph_ChildExplorerTest, DirectChildren_ChainedTraversal_ParityWithRecursiveTraversal) +{ + gp_Trsf aTrsf; + aTrsf.SetTranslation(gp_Vec(100.0, 0.0, 0.0)); + + TopoDS_Shape aBox = BRepPrimAPI_MakeBox(10, 20, 30).Shape(); + aBox.Move(TopLoc_Location(aTrsf)); + + TopoDS_Compound aComp; + BRep_Builder aBuilder; + aBuilder.MakeCompound(aComp); + aBuilder.Add(aComp, aBox); + + BRepGraph aGraph; + aGraph.Build(aComp); + ASSERT_TRUE(aGraph.IsDone()); + + NCollection_DataMap aExpectedLoc; + NCollection_DataMap aExpectedOri; + for (BRepGraph_ChildExplorer anExp(aGraph, BRepGraph_CompoundId(0), BRepGraph_NodeId::Kind::Face); + anExp.More(); + anExp.Next()) + { + const int aFaceIdx = anExp.Current().DefId.Index; + if (!aExpectedLoc.IsBound(aFaceIdx)) + { + aExpectedLoc.Bind(aFaceIdx, anExp.Current().Location); + aExpectedOri.Bind(aFaceIdx, anExp.Current().Orientation); + } + } + + int aVisited = 0; + for (BRepGraph_ChildExplorer aSolidIt = + makeDirectChildExplorer(aGraph, BRepGraph_CompoundId(0), BRepGraph_NodeId::Kind::Solid); + aSolidIt.More(); + aSolidIt.Next()) + { + const BRepGraphInc::NodeUsage aSolidUsage = aSolidIt.Current(); + for (BRepGraph_ChildExplorer aShellIt = makeDirectChildExplorer(aGraph, + aSolidUsage.DefId, + BRepGraph_NodeId::Kind::Shell, + aSolidUsage.Location, + aSolidUsage.Orientation); + aShellIt.More(); + aShellIt.Next()) + { + const BRepGraphInc::NodeUsage aShellUsage = aShellIt.Current(); + for (BRepGraph_ChildExplorer aFaceIt = makeDirectChildExplorer(aGraph, + aShellUsage.DefId, + BRepGraph_NodeId::Kind::Face, + aShellUsage.Location, + aShellUsage.Orientation); + aFaceIt.More(); + aFaceIt.Next()) + { + const BRepGraphInc::NodeUsage aFaceUsage = aFaceIt.Current(); + ASSERT_EQ(aFaceUsage.DefId.NodeKind, BRepGraph_NodeId::Kind::Face); + const int aFaceIdx = aFaceUsage.DefId.Index; + ASSERT_TRUE(aExpectedLoc.IsBound(aFaceIdx)); + EXPECT_TRUE(aFaceUsage.Location.IsEqual(aExpectedLoc.Find(aFaceIdx))); + EXPECT_EQ(aFaceUsage.Orientation, aExpectedOri.Find(aFaceIdx)); + ++aVisited; + } + } + } + + EXPECT_EQ(aVisited, aExpectedLoc.Extent()); +} + +TEST(BRepGraph_ChildExplorerTest, Recursive_SharedProduct_ChildrenHaveDistinctContexts) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10, 20, 30).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + const BRepGraph_ProductId aPart = aGraph.Builder().AddProduct(BRepGraph_SolidId(0)); + ASSERT_TRUE(aPart.IsValid()); + const BRepGraph_ProductId anAssembly = aGraph.Builder().AddAssemblyProduct(); + ASSERT_TRUE(anAssembly.IsValid()); + + gp_Trsf aT1; + aT1.SetTranslation(gp_Vec(10.0, 0.0, 0.0)); + gp_Trsf aT2; + aT2.SetTranslation(gp_Vec(20.0, 0.0, 0.0)); + + const BRepGraph_OccurrenceId anOcc1 = + aGraph.Builder().AddOccurrence(anAssembly, aPart, TopLoc_Location(aT1)); + const BRepGraph_OccurrenceId anOcc2 = + aGraph.Builder().AddOccurrence(anAssembly, aPart, TopLoc_Location(aT2)); + ASSERT_TRUE(anOcc1.IsValid()); + ASSERT_TRUE(anOcc2.IsValid()); + + // Recursive traversal through Assembly->Occurrence->Product(part)->Solid. + int aCount = 0; + TopLoc_Location aLoc1; + TopLoc_Location aLoc2; + for (BRepGraph_ChildExplorer anIt(aGraph, anAssembly, BRepGraph_NodeId::Kind::Solid); anIt.More(); + anIt.Next()) + { + ASSERT_EQ(anIt.Current().DefId, BRepGraph_NodeId(BRepGraph_SolidId(0))); + if (aCount == 0) + aLoc1 = anIt.Current().Location; + else if (aCount == 1) + aLoc2 = anIt.Current().Location; + ++aCount; + } + + ASSERT_EQ(aCount, 2); + EXPECT_FALSE(aLoc1.IsEqual(aLoc2)); +} + +TEST(BRepGraph_ChildExplorerTest, Recursive_ProductPartRootContext_ComposedWithOccurrence) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10, 20, 30).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + const BRepGraph_ProductId aPart = aGraph.Builder().AddProduct(BRepGraph_SolidId(0)); + ASSERT_TRUE(aPart.IsValid()); + const BRepGraph_ProductId anAssembly = aGraph.Builder().AddAssemblyProduct(); + ASSERT_TRUE(anAssembly.IsValid()); + + gp_Trsf aOccTrsf; + aOccTrsf.SetTranslation(gp_Vec(10.0, 0.0, 0.0)); + const BRepGraph_OccurrenceId anOcc = + aGraph.Builder().AddOccurrence(anAssembly, aPart, TopLoc_Location(aOccTrsf)); + ASSERT_TRUE(anOcc.IsValid()); + + gp_Trsf aRootTrsf; + aRootTrsf.SetTranslation(gp_Vec(0.0, 20.0, 0.0)); + { + BRepGraph_MutGuard aMutPart = aGraph.Builder().MutProduct(aPart); + aMutPart->RootLocation = TopLoc_Location(aRootTrsf); + aMutPart->RootOrientation = TopAbs_REVERSED; + } + + // Recursive traversal through Assembly->Occurrence->Product(part)->Solid. + BRepGraph_ChildExplorer anIt(aGraph, anAssembly, BRepGraph_NodeId::Kind::Solid); + ASSERT_TRUE(anIt.More()); + EXPECT_EQ(anIt.Current().DefId, BRepGraph_NodeId(BRepGraph_SolidId(0))); + + const BRepGraphInc::NodeUsage aUsage = anIt.Current(); + const TopLoc_Location anExpectedLoc = TopLoc_Location(aOccTrsf) * TopLoc_Location(aRootTrsf); + const gp_Trsf& anActualTrsf = aUsage.Location.Transformation(); + const gp_Trsf& anExpectedTrsf = anExpectedLoc.Transformation(); + EXPECT_NEAR(anActualTrsf.TranslationPart().X(), + anExpectedTrsf.TranslationPart().X(), + Precision::Confusion()); + EXPECT_NEAR(anActualTrsf.TranslationPart().Y(), + anExpectedTrsf.TranslationPart().Y(), + Precision::Confusion()); + EXPECT_NEAR(anActualTrsf.TranslationPart().Z(), + anExpectedTrsf.TranslationPart().Z(), + Precision::Confusion()); + EXPECT_EQ(aUsage.Orientation, TopAbs_REVERSED); + + anIt.Next(); + EXPECT_FALSE(anIt.More()); +} + +TEST(BRepGraph_ChildExplorerTest, DirectChildren_HighFanout_DirectChildrenComplete) +{ + TopoDS_Compound aComp; + BRep_Builder aBuilder; + aBuilder.MakeCompound(aComp); + + constexpr int THE_NB_CHILDREN = 80; + for (int i = 0; i < THE_NB_CHILDREN; ++i) + aBuilder.Add(aComp, BRepPrimAPI_MakeBox(1.0 + i, 2.0, 3.0).Shape()); + + BRepGraph aGraph; + aGraph.Build(aComp); + ASSERT_TRUE(aGraph.IsDone()); + + int aCount = 0; + for (BRepGraph_ChildExplorer anIt = + makeDirectChildExplorer(aGraph, BRepGraph_CompoundId(0), BRepGraph_NodeId::Kind::Solid); + anIt.More(); + anIt.Next()) + ++aCount; + + EXPECT_EQ(aCount, THE_NB_CHILDREN); +} + +TEST(BRepGraph_ChildExplorerTest, HighFanout_CompletesAllChildren) +{ + // Build a compound with many children to verify no premature traversal termination. + TopoDS_Compound aComp; + BRep_Builder aBB; + aBB.MakeCompound(aComp); + constexpr int THE_NB_CHILDREN = 500; + for (int i = 0; i < THE_NB_CHILDREN; ++i) + aBB.Add(aComp, BRepPrimAPI_MakeBox(1, 1, 1).Shape()); + + BRepGraph aGraph; + aGraph.Build(aComp); + ASSERT_TRUE(aGraph.IsDone()); + + int aFaceCount = 0; + for (BRepGraph_ChildExplorer anExp(aGraph, BRepGraph_CompoundId(0), BRepGraph_NodeId::Kind::Face); + anExp.More(); + anExp.Next()) + ++aFaceCount; + // Each box has 6 faces. + EXPECT_EQ(aFaceCount, THE_NB_CHILDREN * 6); +} + +TEST(BRepGraph_ChildExplorerTest, StructuredBindings_NodeUsage) +{ + gp_Trsf aTrsf; + aTrsf.SetTranslation(gp_Vec(100.0, 0.0, 0.0)); + + TopoDS_Shape aBox = BRepPrimAPI_MakeBox(10, 20, 30).Shape(); + aBox.Move(TopLoc_Location(aTrsf)); + + TopoDS_Compound aComp; + BRep_Builder aBuilder; + aBuilder.MakeCompound(aComp); + aBuilder.Add(aComp, aBox); + + BRepGraph aGraph; + aGraph.Build(aComp); + ASSERT_TRUE(aGraph.IsDone()); + + int aCount = 0; + for (BRepGraph_ChildExplorer anExp(aGraph, BRepGraph_CompoundId(0), BRepGraph_NodeId::Kind::Face); + anExp.More(); + anExp.Next()) + { + const auto [aNodeId, aLoc, anOri] = anExp.Current(); + EXPECT_EQ(aNodeId.NodeKind, BRepGraph_NodeId::Kind::Face); + EXPECT_FALSE(aLoc.IsIdentity()); + EXPECT_TRUE(anOri == TopAbs_FORWARD || anOri == TopAbs_REVERSED); + ++aCount; + } + EXPECT_EQ(aCount, 6); +} + +TEST(BRepGraph_ChildExplorerTest, RangeFor_NodeUsage) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10, 20, 30).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + int aCount = 0; + for (const BRepGraphInc::NodeUsage& aUsage : + BRepGraph_ChildExplorer(aGraph, BRepGraph_SolidId(0), BRepGraph_NodeId::Kind::Face)) + { + EXPECT_EQ(aUsage.DefId.NodeKind, BRepGraph_NodeId::Kind::Face); + ++aCount; + } + EXPECT_EQ(aCount, 6); +} diff --git a/src/ModelingData/TKBRep/GTests/BRepGraph_Compact_Test.cxx b/src/ModelingData/TKBRep/GTests/BRepGraph_Compact_Test.cxx new file mode 100644 index 0000000000..07f188c150 --- /dev/null +++ b/src/ModelingData/TKBRep/GTests/BRepGraph_Compact_Test.cxx @@ -0,0 +1,354 @@ +// Copyright (c) 2026 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 + +namespace +{ + +TopoDS_Compound makeTwoCopiedFaces() +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + TopExp_Explorer anExp(aBox, TopAbs_FACE); + const TopoDS_Shape aFace = anExp.Current(); + + BRepBuilderAPI_Copy aCopy1(aFace, true); + BRepBuilderAPI_Copy aCopy2(aFace, true); + + BRep_Builder aBuilder; + TopoDS_Compound aCompound; + aBuilder.MakeCompound(aCompound); + aBuilder.Add(aCompound, aCopy1.Shape()); + aBuilder.Add(aCompound, aCopy2.Shape()); + return aCompound; +} + +int countHistoryRecordsByOp(const BRepGraph& theGraph, const TCollection_AsciiString& theOp) +{ + int aCount = 0; + for (int aRecIdx = 0; aRecIdx < theGraph.History().NbRecords(); ++aRecIdx) + { + if (theGraph.History().Record(aRecIdx).OperationName == theOp) + ++aCount; + } + return aCount; +} + +} // namespace + +TEST(BRepGraph_CompactTest, NoRemovedNodes_Noop) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + + const int aNbVerticesBefore = aGraph.Topo().Vertices().Nb(); + const int aNbEdgesBefore = aGraph.Topo().Edges().Nb(); + const int aNbFacesBefore = aGraph.Topo().Faces().Nb(); + + const BRepGraph_Compact::Result aRes = BRepGraph_Compact::Perform(aGraph); + + EXPECT_EQ(aRes.NbRemovedVertices, 0); + EXPECT_EQ(aRes.NbRemovedEdges, 0); + EXPECT_EQ(aRes.NbRemovedFaces, 0); + EXPECT_EQ(aRes.NbNodesBefore, aRes.NbNodesAfter); + EXPECT_EQ(aGraph.Topo().Vertices().Nb(), aNbVerticesBefore); + EXPECT_EQ(aGraph.Topo().Edges().Nb(), aNbEdgesBefore); + EXPECT_EQ(aGraph.Topo().Faces().Nb(), aNbFacesBefore); +} + +TEST(BRepGraph_CompactTest, AfterDeduplicate_RemovesNodes) +{ + BRepGraph aGraph; + aGraph.Build(makeTwoCopiedFaces()); + ASSERT_TRUE(aGraph.IsDone()); + + // Run geometry dedup which replaces duplicate surface/curve handles directly. + (void)BRepGraph_Deduplicate::Perform(aGraph); + + BRepGraph_Compact::Options anOpts; + anOpts.HistoryMode = false; + const BRepGraph_Compact::Result aRes = BRepGraph_Compact::Perform(aGraph, anOpts); + + // No separate geometry nodes exist, so NbRemovedSurfaces/NbRemovedCurves is always 0. + EXPECT_EQ(aRes.NbRemovedSurfaces, 0); + EXPECT_EQ(aRes.NbRemovedCurves, 0); + // Without MergeEntitiesWhenSafe, topology node count does not change either. + EXPECT_EQ(aRes.NbNodesAfter, aRes.NbNodesBefore); +} + +TEST(BRepGraph_CompactTest, IndexDensity_NoGaps) +{ + BRepGraph aGraph; + aGraph.Build(makeTwoCopiedFaces()); + ASSERT_TRUE(aGraph.IsDone()); + + (void)BRepGraph_Deduplicate::Perform(aGraph); + (void)BRepGraph_Compact::Perform(aGraph); + + // After compaction, there should be no removed defs. + for (int anIdx = 0; anIdx < aGraph.Topo().Vertices().Nb(); ++anIdx) + EXPECT_FALSE(aGraph.Topo().Vertices().Definition(BRepGraph_VertexId(anIdx)).IsRemoved); + for (int anIdx = 0; anIdx < aGraph.Topo().Edges().Nb(); ++anIdx) + EXPECT_FALSE(aGraph.Topo().Edges().Definition(BRepGraph_EdgeId(anIdx)).IsRemoved); + for (int anIdx = 0; anIdx < aGraph.Topo().Faces().Nb(); ++anIdx) + EXPECT_FALSE(aGraph.Topo().Faces().Definition(BRepGraph_FaceId(anIdx)).IsRemoved); + for (int anIdx = 0; anIdx < aGraph.Topo().Wires().Nb(); ++anIdx) + EXPECT_FALSE(aGraph.Topo().Wires().Definition(BRepGraph_WireId(anIdx)).IsRemoved); +} + +TEST(BRepGraph_CompactTest, CrossReferences_Valid) +{ + BRepGraph aGraph; + aGraph.Build(makeTwoCopiedFaces()); + ASSERT_TRUE(aGraph.IsDone()); + + (void)BRepGraph_Deduplicate::Perform(aGraph); + (void)BRepGraph_Compact::Perform(aGraph); + + const BRepGraph_Validate::Result aValResult = BRepGraph_Validate::Perform(aGraph); + EXPECT_TRUE(aValResult.IsValid()); +} + +TEST(BRepGraph_CompactTest, HistoryMode_RecordsMapping) +{ + BRepGraph aGraph; + aGraph.Build(makeTwoCopiedFaces()); + ASSERT_TRUE(aGraph.IsDone()); + + (void)BRepGraph_Deduplicate::Perform(aGraph); + + BRepGraph_Compact::Options anOpts; + anOpts.HistoryMode = true; + (void)BRepGraph_Compact::Perform(aGraph, anOpts); + + const int aNbRemapRecords = + countHistoryRecordsByOp(aGraph, TCollection_AsciiString("Compact:Remap")); + // There should be history records if any remapping occurred. + // After geometry dedup only, topology indices don't change, so remap may be 0. + // This test just verifies the mechanism works without crashing. + EXPECT_GE(aNbRemapRecords, 0); +} + +TEST(BRepGraph_CompactTest, FullPipeline_Deduplicate_Compact_Validate) +{ + BRepGraph aGraph; + aGraph.Build(makeTwoCopiedFaces()); + ASSERT_TRUE(aGraph.IsDone()); + + // Full dedup (replaces duplicate handles directly on defs). + (void)BRepGraph_Deduplicate::Perform(aGraph); + + // Compact. Without MergeEntitiesWhenSafe, no topology nodes are removed, + // so NbNodesAfter == NbNodesBefore. + const BRepGraph_Compact::Result aCompactRes = BRepGraph_Compact::Perform(aGraph); + EXPECT_EQ(aCompactRes.NbNodesAfter, aCompactRes.NbNodesBefore); + + // Validate. + const BRepGraph_Validate::Result aValResult = BRepGraph_Validate::Perform(aGraph); + EXPECT_TRUE(aValResult.IsValid()); +} + +TEST(BRepGraph_CompactTest, Compact_PreservesTopologyUIDs) +{ + BRepGraph aGraph; + aGraph.Build(makeTwoCopiedFaces()); + ASSERT_TRUE(aGraph.IsDone()); + + // Collect the set of all original topology UIDs before dedup+compact. + // Helper: record UIDs for all defs of a given kind. + auto collectUIDs = [&](BRepGraph_NodeId::Kind theKind, int theCount) { + NCollection_Map aMap; + for (int anIdx = 0; anIdx < theCount; ++anIdx) + { + const BRepGraph_UID aUID = aGraph.UIDs().Of(BRepGraph_NodeId(theKind, anIdx)); + EXPECT_TRUE(aUID.IsValid()); + aMap.Add(aUID); + } + return aMap; + }; + + const NCollection_Map anOrigVertexUIDs = + collectUIDs(BRepGraph_NodeId::Kind::Vertex, aGraph.Topo().Vertices().Nb()); + const NCollection_Map anOrigEdgeUIDs = + collectUIDs(BRepGraph_NodeId::Kind::Edge, aGraph.Topo().Edges().Nb()); + const NCollection_Map anOrigWireUIDs = + collectUIDs(BRepGraph_NodeId::Kind::Wire, aGraph.Topo().Wires().Nb()); + const NCollection_Map anOrigFaceUIDs = + collectUIDs(BRepGraph_NodeId::Kind::Face, aGraph.Topo().Faces().Nb()); + const NCollection_Map anOrigShellUIDs = + collectUIDs(BRepGraph_NodeId::Kind::Shell, aGraph.Topo().Shells().Nb()); + const NCollection_Map anOrigSolidUIDs = + collectUIDs(BRepGraph_NodeId::Kind::Solid, aGraph.Topo().Solids().Nb()); + const NCollection_Map anOrigCompoundUIDs = + collectUIDs(BRepGraph_NodeId::Kind::Compound, aGraph.Topo().Compounds().Nb()); + const NCollection_Map anOrigCompSolidUIDs = + collectUIDs(BRepGraph_NodeId::Kind::CompSolid, aGraph.Topo().CompSolids().Nb()); + // Geometry is now stored inline on defs; no separate geometry UIDs to collect. + + const uint32_t aGenBefore = aGraph.UIDs().Generation(); + + // Run dedup + compact. + (void)BRepGraph_Deduplicate::Perform(aGraph); + (void)BRepGraph_Compact::Perform(aGraph); + + // Generation must be preserved across compact. + EXPECT_EQ(aGraph.UIDs().Generation(), aGenBefore); + + // Helper: verify surviving defs of a kind retain original UIDs. + auto verifyUIDs = [&](BRepGraph_NodeId::Kind theKind, + int theCount, + const NCollection_Map& theOriginals, + const char* theLabel) { + for (int anIdx = 0; anIdx < theCount; ++anIdx) + { + const BRepGraph_NodeId aNewId(theKind, anIdx); + const BRepGraph_UID aNewUID = aGraph.UIDs().Of(aNewId); + EXPECT_TRUE(aNewUID.IsValid()) << theLabel << " " << anIdx << " lost UID after compact"; + EXPECT_TRUE(theOriginals.Contains(aNewUID)) + << theLabel << " " << anIdx << " has UID not present before compact"; + EXPECT_EQ(aGraph.UIDs().NodeIdFrom(aNewUID), aNewId); + EXPECT_TRUE(aGraph.UIDs().Has(aNewUID)); + } + }; + + verifyUIDs(BRepGraph_NodeId::Kind::Vertex, + aGraph.Topo().Vertices().Nb(), + anOrigVertexUIDs, + "Vertex"); + verifyUIDs(BRepGraph_NodeId::Kind::Edge, aGraph.Topo().Edges().Nb(), anOrigEdgeUIDs, "Edge"); + verifyUIDs(BRepGraph_NodeId::Kind::Wire, aGraph.Topo().Wires().Nb(), anOrigWireUIDs, "Wire"); + verifyUIDs(BRepGraph_NodeId::Kind::Face, aGraph.Topo().Faces().Nb(), anOrigFaceUIDs, "Face"); + verifyUIDs(BRepGraph_NodeId::Kind::Shell, aGraph.Topo().Shells().Nb(), anOrigShellUIDs, "Shell"); + verifyUIDs(BRepGraph_NodeId::Kind::Solid, aGraph.Topo().Solids().Nb(), anOrigSolidUIDs, "Solid"); + verifyUIDs(BRepGraph_NodeId::Kind::Compound, + aGraph.Topo().Compounds().Nb(), + anOrigCompoundUIDs, + "Compound"); + verifyUIDs(BRepGraph_NodeId::Kind::CompSolid, + aGraph.Topo().CompSolids().Nb(), + anOrigCompSolidUIDs, + "CompSolid"); + + // Geometry is now stored inline on defs; no separate geometry UIDs to verify. +} + +TEST(BRepGraph_CompactTest, OwnGen_SurvivesCompact) +{ + constexpr double THE_MUTATED_EDGE_TOLERANCE = 0.2; + constexpr uint32_t THE_EXPECTED_OWN_GEN = 2; + + BRepGraph aGraph; + aGraph.Build(makeTwoCopiedFaces()); + ASSERT_TRUE(aGraph.IsDone()); + + // Mutate edge 0 twice so OwnGen == THE_EXPECTED_OWN_GEN. + aGraph.Builder().MutEdge(BRepGraph_EdgeId(0))->Tolerance = 0.1; + aGraph.Builder().MutEdge(BRepGraph_EdgeId(0))->Tolerance = THE_MUTATED_EDGE_TOLERANCE; + ASSERT_EQ(aGraph.Topo().Edges().Definition(BRepGraph_EdgeId(0)).OwnGen, THE_EXPECTED_OWN_GEN); + + // Run dedup + compact. + (void)BRepGraph_Deduplicate::Perform(aGraph); + (void)BRepGraph_Compact::Perform(aGraph); + + // Edge 0 may have been remapped. Find the edge that carries the mutated + // tolerance and verify both the tolerance value and OwnGen are preserved. + bool aFound = false; + for (int anIdx = 0; anIdx < aGraph.Topo().Edges().Nb(); ++anIdx) + { + const BRepGraphInc::EdgeDef& anEdge = aGraph.Topo().Edges().Definition(BRepGraph_EdgeId(anIdx)); + if (std::abs(anEdge.Tolerance - THE_MUTATED_EDGE_TOLERANCE) < Precision::Confusion()) + { + EXPECT_EQ(anEdge.OwnGen, THE_EXPECTED_OWN_GEN) + << "Edge " << anIdx << " has mutated tolerance but wrong OwnGen"; + aFound = true; + break; + } + } + EXPECT_TRUE(aFound) << "No edge with mutated tolerance found after compact"; +} + +TEST(BRepGraph_CompactTest, UIDRoundTrip_AfterCompaction) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + ASSERT_GE(aGraph.Topo().Faces().Nb(), 3); + ASSERT_GE(aGraph.Topo().Edges().Nb(), 3); + + // Record UIDs for a few face and edge nodes. + const BRepGraph_UID aFaceUID0 = aGraph.UIDs().Of(BRepGraph_FaceId(0)); + const BRepGraph_UID aFaceUID1 = aGraph.UIDs().Of(BRepGraph_FaceId(1)); + const BRepGraph_UID aFaceUID2 = aGraph.UIDs().Of(BRepGraph_FaceId(2)); + const BRepGraph_UID anEdgeUID0 = aGraph.UIDs().Of(BRepGraph_EdgeId(0)); + const BRepGraph_UID anEdgeUID1 = aGraph.UIDs().Of(BRepGraph_EdgeId(1)); + ASSERT_TRUE(aFaceUID0.IsValid()); + ASSERT_TRUE(aFaceUID1.IsValid()); + ASSERT_TRUE(aFaceUID2.IsValid()); + ASSERT_TRUE(anEdgeUID0.IsValid()); + ASSERT_TRUE(anEdgeUID1.IsValid()); + + // Record the UID of the face to be removed. + const BRepGraph_UID aRemovedFaceUID = aGraph.UIDs().Of(BRepGraph_FaceId(2)); + + // Remove one face. + aGraph.Builder().RemoveNode(BRepGraph_FaceId(2)); + + // Run compaction. + const BRepGraph_Compact::Result aRes = BRepGraph_Compact::Perform(aGraph); + EXPECT_GE(aRes.NbRemovedFaces, 1); + + // Surviving UIDs should resolve to valid NodeIds. + const BRepGraph_NodeId aFace0After = aGraph.UIDs().NodeIdFrom(aFaceUID0); + const BRepGraph_NodeId aFace1After = aGraph.UIDs().NodeIdFrom(aFaceUID1); + const BRepGraph_NodeId anEdge0After = aGraph.UIDs().NodeIdFrom(anEdgeUID0); + const BRepGraph_NodeId anEdge1After = aGraph.UIDs().NodeIdFrom(anEdgeUID1); + EXPECT_TRUE(aFace0After.IsValid()) << "Face 0 UID lost after compaction"; + EXPECT_TRUE(aFace1After.IsValid()) << "Face 1 UID lost after compaction"; + EXPECT_TRUE(anEdge0After.IsValid()) << "Edge 0 UID lost after compaction"; + EXPECT_TRUE(anEdge1After.IsValid()) << "Edge 1 UID lost after compaction"; + + // The removed face's UID should no longer resolve to a valid NodeId. + const BRepGraph_NodeId aRemovedAfter = aGraph.UIDs().NodeIdFrom(aRemovedFaceUID); + EXPECT_FALSE(aRemovedAfter.IsValid()) << "Removed face UID still resolves after compaction"; +} diff --git a/src/ModelingData/TKBRep/GTests/BRepGraph_Convenience_Test.cxx b/src/ModelingData/TKBRep/GTests/BRepGraph_Convenience_Test.cxx new file mode 100644 index 0000000000..2abc2c358f --- /dev/null +++ b/src/ModelingData/TKBRep/GTests/BRepGraph_Convenience_Test.cxx @@ -0,0 +1,240 @@ +// Copyright (c) 2026 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 + +class BRepGraph_ConvenienceTest : public testing::Test +{ +protected: + void SetUp() override + { + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + myGraph.Build(aBox); + } + + BRepGraph myGraph; +}; + +// ---------- Part A: NodeId Static Factories ---------- + +TEST_F(BRepGraph_ConvenienceTest, NodeId_Factories_CorrectKindAndIndex) +{ + const BRepGraph_NodeId aSolid = BRepGraph_SolidId(3); + EXPECT_EQ(aSolid.NodeKind, BRepGraph_NodeId::Kind::Solid); + EXPECT_EQ(aSolid.Index, 3); + + const BRepGraph_NodeId aShell = BRepGraph_ShellId(5); + EXPECT_EQ(aShell.NodeKind, BRepGraph_NodeId::Kind::Shell); + EXPECT_EQ(aShell.Index, 5); + + const BRepGraph_NodeId aFace = BRepGraph_FaceId(0); + EXPECT_EQ(aFace.NodeKind, BRepGraph_NodeId::Kind::Face); + EXPECT_EQ(aFace.Index, 0); + + const BRepGraph_NodeId aWire = BRepGraph_WireId(2); + EXPECT_EQ(aWire.NodeKind, BRepGraph_NodeId::Kind::Wire); + EXPECT_EQ(aWire.Index, 2); + + const BRepGraph_NodeId anEdge = BRepGraph_EdgeId(7); + EXPECT_EQ(anEdge.NodeKind, BRepGraph_NodeId::Kind::Edge); + EXPECT_EQ(anEdge.Index, 7); + + const BRepGraph_NodeId aVertex = BRepGraph_VertexId(1); + EXPECT_EQ(aVertex.NodeKind, BRepGraph_NodeId::Kind::Vertex); + EXPECT_EQ(aVertex.Index, 1); + + const BRepGraph_NodeId aCompound = BRepGraph_CompoundId(0); + EXPECT_EQ(aCompound.NodeKind, BRepGraph_NodeId::Kind::Compound); + EXPECT_EQ(aCompound.Index, 0); + + const BRepGraph_NodeId aCompSolid = BRepGraph_CompSolidId(0); + EXPECT_EQ(aCompSolid.NodeKind, BRepGraph_NodeId::Kind::CompSolid); + EXPECT_EQ(aCompSolid.Index, 0); +} + +TEST_F(BRepGraph_ConvenienceTest, NodeId_Factories_EqualToConstructor) +{ + EXPECT_EQ(BRepGraph_FaceId(3), BRepGraph_NodeId(BRepGraph_NodeId::Kind::Face, 3)); + EXPECT_EQ(BRepGraph_EdgeId(0), BRepGraph_NodeId(BRepGraph_NodeId::Kind::Edge, 0)); +} + +// ---------- Part B: EdgeDef Vertex Access via BRepGraph_Tool ---------- + +TEST_F(BRepGraph_ConvenienceTest, EdgeDef_StartVertex_Valid) +{ + ASSERT_GT(myGraph.Topo().Edges().Nb(), 0); + const BRepGraph_EdgeId anEdgeId(0); + const BRepGraphInc::VertexRef& aStart = BRepGraph_Tool::Edge::StartVertex(myGraph, anEdgeId); + EXPECT_TRUE(aStart.VertexDefId.IsValid()); +} + +TEST_F(BRepGraph_ConvenienceTest, EdgeDef_EndVertex_Valid) +{ + ASSERT_GT(myGraph.Topo().Edges().Nb(), 0); + const BRepGraph_EdgeId anEdgeId(0); + const BRepGraphInc::VertexRef& anEnd = BRepGraph_Tool::Edge::EndVertex(myGraph, anEdgeId); + EXPECT_TRUE(anEnd.VertexDefId.IsValid()); +} + +TEST_F(BRepGraph_ConvenienceTest, EdgeDef_StartEnd_DifferForNonClosed) +{ + ASSERT_GT(myGraph.Topo().Edges().Nb(), 0); + const BRepGraph_EdgeId anEdgeId(0); + const BRepGraphInc::EdgeDef& anEdge = myGraph.Topo().Edges().Definition(anEdgeId); + if (!anEdge.IsClosed) + { + const BRepGraph_VertexId aStartId = + BRepGraph_Tool::Edge::StartVertex(myGraph, anEdgeId).VertexDefId; + const BRepGraph_VertexId anEndId = + BRepGraph_Tool::Edge::EndVertex(myGraph, anEdgeId).VertexDefId; + EXPECT_NE(aStartId, anEndId); + } +} + +TEST_F(BRepGraph_ConvenienceTest, EdgeDef_RefIds_AreValid) +{ + ASSERT_GT(myGraph.Topo().Edges().Nb(), 0); + const BRepGraph_EdgeId anEdgeId(0); + const BRepGraphInc::EdgeDef& anEdge = myGraph.Topo().Edges().Definition(anEdgeId); + EXPECT_TRUE(anEdge.StartVertexRefId.IsValid()); + EXPECT_TRUE(anEdge.EndVertexRefId.IsValid()); +} + +// ---------- Part D: FaceDef::Surface ---------- + +TEST_F(BRepGraph_ConvenienceTest, FaceSurface_Valid) +{ + const BRepGraph::TopoView aDefs = myGraph.Topo(); + ASSERT_GT(aDefs.Faces().Nb(), 0); + EXPECT_TRUE(aDefs.Faces().Definition(BRepGraph_FaceId(0)).SurfaceRepId.IsValid()); +} + +TEST_F(BRepGraph_ConvenienceTest, FaceSurface_AllBoxFaces) +{ + for (BRepGraph_FaceIterator aFaceIt(myGraph); aFaceIt.More(); aFaceIt.Next()) + { + EXPECT_TRUE(aFaceIt.Current().SurfaceRepId.IsValid()) + << "Face " << aFaceIt.CurrentId().Index << " has no surface"; + } +} + +// ---------- Part E: DefsView::FindPCurve ---------- + +TEST_F(BRepGraph_ConvenienceTest, FindPCurve_ValidPair) +{ + // Find an edge/face pair that has a PCurve. + for (BRepGraph_FaceIterator aFaceIt(myGraph); aFaceIt.More(); aFaceIt.Next()) + { + const BRepGraph_FaceId aFaceId = aFaceIt.CurrentId(); + + for (BRepGraph_EdgeIterator anEdgeIt(myGraph); anEdgeIt.More(); anEdgeIt.Next()) + { + const BRepGraphInc::CoEdgeDef* aPCurve = + BRepGraph_Tool::Edge::FindPCurve(myGraph, anEdgeIt.CurrentId(), aFaceId); + if (aPCurve != nullptr) + { + EXPECT_TRUE(aPCurve->Curve2DRepId.IsValid()); + return; + } + } + } +} + +TEST_F(BRepGraph_ConvenienceTest, FindPCurve_InvalidPair_ReturnsNull) +{ + EXPECT_EQ(BRepGraph_Tool::Edge::FindPCurve(myGraph, BRepGraph_EdgeId(0), BRepGraph_FaceId(9999)), + nullptr); +} + +// ---------- Part F: RefsView::FaceRefIdsOf ---------- + +TEST_F(BRepGraph_ConvenienceTest, ShellFaceRefs_Box_SixFaces) +{ + const BRepGraph::RefsView& aRefs = myGraph.Refs(); + ASSERT_EQ(myGraph.Topo().Shells().Nb(), 1); + EXPECT_EQ(aRefs.Faces().IdsOf(BRepGraph_ShellId(0)).Length(), 6); +} + +TEST_F(BRepGraph_ConvenienceTest, ShellFaceRefs_AllValid) +{ + const BRepGraph::RefsView& aRefs = myGraph.Refs(); + const NCollection_Vector& aFaceRefIds = + aRefs.Faces().IdsOf(BRepGraph_ShellId(0)); + for (int aFaceIter = 0; aFaceIter < aFaceRefIds.Length(); ++aFaceIter) + { + const BRepGraphInc::FaceRef& aFaceRef = aRefs.Faces().Entry(aFaceRefIds.Value(aFaceIter)); + EXPECT_TRUE(aFaceRef.FaceDefId.IsValid()) << "Shell face ref " << aFaceIter; + } +} + +TEST_F(BRepGraph_ConvenienceTest, ShellFaceRefs_InvalidShell_Empty) +{ + const BRepGraph::RefsView& aRefs = myGraph.Refs(); + EXPECT_EQ(aRefs.Faces().IdsOf(BRepGraph_ShellId(100)).Length(), 0); +} + +// ---------- Integration: Cylinder with seam edge ---------- + +TEST_F(BRepGraph_ConvenienceTest, FindPCurve_WithOrientation_SeamEdge) +{ + BRepPrimAPI_MakeCylinder aCylMaker(5.0, 10.0); + const TopoDS_Shape& aCyl = aCylMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aCyl); + ASSERT_TRUE(aGraph.IsDone()); + + const BRepGraph::TopoView aDefs = aGraph.Topo(); + + // Look for seam edges (coedge with SeamPairId valid). + for (BRepGraph_EdgeIterator anEdgeIt(aGraph); anEdgeIt.More(); anEdgeIt.Next()) + { + const NCollection_Vector& aCoEdgeIdxs = + aDefs.Edges().CoEdges(anEdgeIt.CurrentId()); + + for (const BRepGraph_CoEdgeId& aCoEdgeId : aCoEdgeIdxs) + { + const BRepGraphInc::CoEdgeDef& aCE = aDefs.CoEdges().Definition(aCoEdgeId); + if (!aCE.SeamPairId.IsValid()) + continue; + + // Found seam edge - verify FindPCurve returns distinct entries for each orientation. + const BRepGraph_FaceId aFaceDefId = aCE.FaceDefId; + const BRepGraphInc::CoEdgeDef* aPCF = + BRepGraph_Tool::Edge::FindPCurve(aGraph, anEdgeIt.CurrentId(), aFaceDefId, TopAbs_FORWARD); + const BRepGraphInc::CoEdgeDef* aPCR = + BRepGraph_Tool::Edge::FindPCurve(aGraph, anEdgeIt.CurrentId(), aFaceDefId, TopAbs_REVERSED); + EXPECT_NE(aPCF, nullptr); + EXPECT_NE(aPCR, nullptr); + if (aPCF != nullptr && aPCR != nullptr) + { + EXPECT_NE(aPCF, aPCR); + } + return; + } + } +} diff --git a/src/ModelingData/TKBRep/GTests/BRepGraph_Copy_Test.cxx b/src/ModelingData/TKBRep/GTests/BRepGraph_Copy_Test.cxx new file mode 100644 index 0000000000..a28ad6db73 --- /dev/null +++ b/src/ModelingData/TKBRep/GTests/BRepGraph_Copy_Test.cxx @@ -0,0 +1,314 @@ +// Copyright (c) 2026 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 + +TEST(BRepGraph_CopyTest, CopyBox_FaceCount) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + + BRepGraph aCopyGraph = BRepGraph_Copy::Perform(aGraph, true); + ASSERT_TRUE(aCopyGraph.IsDone()); + EXPECT_EQ(aCopyGraph.Topo().Faces().Nb(), 6); + EXPECT_EQ(aCopyGraph.Topo().Faces().Nb(), aGraph.Topo().Faces().Nb()); + + // Verify reconstructed shape has correct face count. + TopoDS_Shape aCopy = aCopyGraph.Shapes().Reconstruct(BRepGraph_SolidId(0)); + ASSERT_FALSE(aCopy.IsNull()); + + int aNbFaces = 0; + for (TopExp_Explorer anExp(aCopy, TopAbs_FACE); anExp.More(); anExp.Next()) + ++aNbFaces; + EXPECT_EQ(aNbFaces, 6); +} + +TEST(BRepGraph_CopyTest, CopyBox_AreaPreserved) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + GProp_GProps aOrigProps; + BRepGProp::SurfaceProperties(aBox, aOrigProps); + const double anOrigArea = aOrigProps.Mass(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + + BRepGraph aCopyGraph = BRepGraph_Copy::Perform(aGraph, true); + ASSERT_TRUE(aCopyGraph.IsDone()); + + TopoDS_Shape aCopy = aCopyGraph.Shapes().Reconstruct(BRepGraph_SolidId(0)); + ASSERT_FALSE(aCopy.IsNull()); + + // Sum face areas since result may be a compound. + double aCopyArea = 0.0; + for (TopExp_Explorer anExp(aCopy, TopAbs_FACE); anExp.More(); anExp.Next()) + { + GProp_GProps aProps; + BRepGProp::SurfaceProperties(anExp.Current(), aProps); + aCopyArea += std::abs(aProps.Mass()); + } + + EXPECT_NEAR(aCopyArea, anOrigArea, anOrigArea * 0.01); +} + +TEST(BRepGraph_CopyTest, CopyBox_GeometryIsIndependent) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + + BRepGraph aCopyGraph = BRepGraph_Copy::Perform(aGraph, true); + ASSERT_TRUE(aCopyGraph.IsDone()); + + // Deep copy: surface handles must be different objects. + ASSERT_GT(aGraph.Topo().Faces().Nb(), 0); + ASSERT_GT(aCopyGraph.Topo().Faces().Nb(), 0); + EXPECT_NE(BRepGraph_Tool::Face::Surface(aCopyGraph, BRepGraph_FaceId(0)).get(), + BRepGraph_Tool::Face::Surface(aGraph, BRepGraph_FaceId(0)).get()); +} + +TEST(BRepGraph_CopyTest, CopyBox_SharedGeometry) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + + // theCopyGeom = false: geometry is shared. + BRepGraph aCopyGraph = BRepGraph_Copy::Perform(aGraph, false); + ASSERT_TRUE(aCopyGraph.IsDone()); + EXPECT_EQ(aCopyGraph.Topo().Faces().Nb(), 6); + + // Light copy: surface handles must be the same objects. + ASSERT_GT(aGraph.Topo().Faces().Nb(), 0); + ASSERT_GT(aCopyGraph.Topo().Faces().Nb(), 0); + EXPECT_EQ(BRepGraph_Tool::Face::Surface(aCopyGraph, BRepGraph_FaceId(0)).get(), + BRepGraph_Tool::Face::Surface(aGraph, BRepGraph_FaceId(0)).get()); +} + +TEST(BRepGraph_CopyTest, CopyCylinder_FaceCount) +{ + BRepPrimAPI_MakeCylinder aCylMaker(5.0, 20.0); + const TopoDS_Shape& aCyl = aCylMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aCyl); + ASSERT_TRUE(aGraph.IsDone()); + + BRepGraph aCopyGraph = BRepGraph_Copy::Perform(aGraph, true); + ASSERT_TRUE(aCopyGraph.IsDone()); + EXPECT_EQ(aCopyGraph.Topo().Faces().Nb(), aGraph.Topo().Faces().Nb()); +} + +TEST(BRepGraph_CopyTest, CopySingleFace) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + ASSERT_GT(aGraph.Topo().Faces().Nb(), 0); + + BRepGraph aCopyGraph = BRepGraph_Copy::CopyFace(aGraph, BRepGraph_FaceId(0), true); + ASSERT_TRUE(aCopyGraph.IsDone()); + EXPECT_EQ(aCopyGraph.Topo().Faces().Nb(), 1); + + // A box face has 1 wire with 4 edges and 4 vertices. + EXPECT_GT(aCopyGraph.Topo().Vertices().Nb(), 0); + EXPECT_GT(aCopyGraph.Topo().Edges().Nb(), 0); + EXPECT_GT(aCopyGraph.Topo().Wires().Nb(), 0); + + // No shells/solids in a single-face sub-graph. + EXPECT_EQ(aCopyGraph.Topo().Shells().Nb(), 0); + EXPECT_EQ(aCopyGraph.Topo().Solids().Nb(), 0); + + TopoDS_Shape aCopiedFace = aCopyGraph.Shapes().Reconstruct(BRepGraph_FaceId(0)); + ASSERT_FALSE(aCopiedFace.IsNull()); + EXPECT_EQ(aCopiedFace.ShapeType(), TopAbs_FACE); + + GProp_GProps aProps; + BRepGProp::SurfaceProperties(aCopiedFace, aProps); + EXPECT_GT(std::abs(aProps.Mass()), 1.0); +} + +TEST(BRepGraph_CopyTest, CopyFacesOnly_Compound) +{ + // Build graph from individual faces (no shells/solids). + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + BRep_Builder aBB; + TopoDS_Compound aCompound; + aBB.MakeCompound(aCompound); + for (TopExp_Explorer anExp(aBox, TopAbs_FACE); anExp.More(); anExp.Next()) + { + BRepBuilderAPI_Copy aCopier(anExp.Current(), true); + aBB.Add(aCompound, aCopier.Shape()); + } + + BRepGraph aGraph; + aGraph.Build(aCompound); + ASSERT_TRUE(aGraph.IsDone()); + ASSERT_EQ(aGraph.Topo().Faces().Nb(), 6); + ASSERT_EQ(aGraph.Topo().Solids().Nb(), 0); + ASSERT_EQ(aGraph.Topo().Shells().Nb(), 0); + + BRepGraph aCopyGraph = BRepGraph_Copy::Perform(aGraph, true); + ASSERT_TRUE(aCopyGraph.IsDone()); + EXPECT_EQ(aCopyGraph.Topo().Faces().Nb(), 6); + EXPECT_EQ(aCopyGraph.Topo().Solids().Nb(), 0); + EXPECT_EQ(aCopyGraph.Topo().Shells().Nb(), 0); +} + +// ============================================================ +// SameParameter / SameRange round-trip +// ============================================================ + +TEST(BRepGraph_CopyTest, CopyBox_SameParameter_Preserved) +{ + const TopoDS_Shape aBox = BRepPrimAPI_MakeBox(10.0, 20.0, 30.0).Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + + BRepGraph aCopyGraph = BRepGraph_Copy::Perform(aGraph, true); + ASSERT_TRUE(aCopyGraph.IsDone()); + + // All edges in the copied graph must preserve SameParameter = true. + for (int anIdx = 0; anIdx < aCopyGraph.Topo().Edges().Nb(); ++anIdx) + { + const BRepGraphInc::EdgeDef& anEdge = + aCopyGraph.Topo().Edges().Definition(BRepGraph_EdgeId(anIdx)); + EXPECT_TRUE(anEdge.SameParameter) << "Copied edge " << anIdx << " lost SameParameter flag"; + EXPECT_TRUE(anEdge.SameRange) << "Copied edge " << anIdx << " lost SameRange flag"; + } +} + +// ============================================================ +// Regularity (BRep_CurveOn2Surfaces) preserved via EncodeRegularity +// ============================================================ + +TEST(BRepGraph_CopyTest, FusedBoxes_Regularity_AreaPreserved) +{ + // Fuse two adjacent boxes to produce a shape with regularity edges. + // Verify that the copy preserves area (geometry integrity). + TopoDS_Shape aBox1 = BRepPrimAPI_MakeBox(10.0, 10.0, 10.0).Shape(); + TopoDS_Shape aBox2 = BRepPrimAPI_MakeBox(gp_Pnt(10.0, 0.0, 0.0), 10.0, 10.0, 10.0).Shape(); + BRepAlgoAPI_Fuse aFuser(aBox1, aBox2); + ASSERT_TRUE(aFuser.IsDone()); + const TopoDS_Shape& aFused = aFuser.Shape(); + + GProp_GProps aOrigProps; + BRepGProp::SurfaceProperties(aFused, aOrigProps); + const double anOrigArea = aOrigProps.Mass(); + + BRepGraph aGraph; + aGraph.Build(aFused); + ASSERT_TRUE(aGraph.IsDone()); + + BRepGraph aCopyGraph = BRepGraph_Copy::Perform(aGraph, true); + ASSERT_TRUE(aCopyGraph.IsDone()); + + // Verify node counts match. + EXPECT_EQ(aCopyGraph.Topo().Faces().Nb(), aGraph.Topo().Faces().Nb()); + EXPECT_EQ(aCopyGraph.Topo().Edges().Nb(), aGraph.Topo().Edges().Nb()); + + // Verify area is preserved by summing individual face areas. + double aCopyArea = 0.0; + for (int aFaceIdx = 0; aFaceIdx < aCopyGraph.Topo().Faces().Nb(); ++aFaceIdx) + { + TopoDS_Shape aFace = aCopyGraph.Shapes().Reconstruct(BRepGraph_FaceId(aFaceIdx)); + GProp_GProps aProps; + BRepGProp::SurfaceProperties(aFace, aProps); + aCopyArea += std::abs(aProps.Mass()); + } + EXPECT_NEAR(aCopyArea, anOrigArea, anOrigArea * 0.01); +} + +// ============================================================ +// UID preservation +// ============================================================ + +TEST(BRepGraph_CopyTest, CopyBox_UIDsPreserved) +{ + const TopoDS_Shape aBox = BRepPrimAPI_MakeBox(10.0, 20.0, 30.0).Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + + BRepGraph aCopyGraph = BRepGraph_Copy::Perform(aGraph, true); + ASSERT_TRUE(aCopyGraph.IsDone()); + + // Helper to check UIDs for a given node kind. + auto checkUIDs = [&](BRepGraph_NodeId::Kind theKind, int theCount, const char* theLabel) { + for (int anIdx = 0; anIdx < theCount; ++anIdx) + { + BRepGraph_NodeId aNodeId(theKind, anIdx); + BRepGraph_UID anOrigUID = aGraph.UIDs().Of(aNodeId); + BRepGraph_UID aCopyUID = aCopyGraph.UIDs().Of(aNodeId); + EXPECT_EQ(aCopyUID.IsValid(), anOrigUID.IsValid()) + << "UID validity mismatch at " << theLabel << " " << anIdx; + if (anOrigUID.IsValid()) + { + EXPECT_EQ(aCopyUID, anOrigUID) << "UID mismatch at " << theLabel << " " << anIdx; + } + } + }; + + // Verify UIDs for all topology and geometry node types. + checkUIDs(BRepGraph_NodeId::Kind::Vertex, aGraph.Topo().Vertices().Nb(), "vertex"); + checkUIDs(BRepGraph_NodeId::Kind::Edge, aGraph.Topo().Edges().Nb(), "edge"); + checkUIDs(BRepGraph_NodeId::Kind::Wire, aGraph.Topo().Wires().Nb(), "wire"); + checkUIDs(BRepGraph_NodeId::Kind::Face, aGraph.Topo().Faces().Nb(), "face"); + checkUIDs(BRepGraph_NodeId::Kind::Shell, aGraph.Topo().Shells().Nb(), "shell"); + checkUIDs(BRepGraph_NodeId::Kind::Solid, aGraph.Topo().Solids().Nb(), "solid"); + // Geometry is now stored inline on face/edge defs; no separate geometry UIDs to check. +} diff --git a/src/ModelingData/TKBRep/GTests/BRepGraph_Deduplicate_Test.cxx b/src/ModelingData/TKBRep/GTests/BRepGraph_Deduplicate_Test.cxx new file mode 100644 index 0000000000..8476f707d4 --- /dev/null +++ b/src/ModelingData/TKBRep/GTests/BRepGraph_Deduplicate_Test.cxx @@ -0,0 +1,1849 @@ +// Copyright (c) 2026 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 +#include +#include +#include +#include + +#include + +namespace +{ + +//================================================================================================= + +TopoDS_Compound makeTwoCopiedIdenticalFaces() +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + TopExp_Explorer anExp(aBox, TopAbs_FACE); + const TopoDS_Shape aFace = anExp.Current(); + + BRepBuilderAPI_Copy aCopy1(aFace, true); + BRepBuilderAPI_Copy aCopy2(aFace, true); + + BRep_Builder aBuilder; + TopoDS_Compound aCompound; + aBuilder.MakeCompound(aCompound); + aBuilder.Add(aCompound, aCopy1.Shape()); + aBuilder.Add(aCompound, aCopy2.Shape()); + return aCompound; +} + +//================================================================================================= + +int nbUniqueFaceSurfaceDefs(const BRepGraph& theGraph) +{ + NCollection_Map aSurfSet; + for (int aFaceIdx = 0; aFaceIdx < theGraph.Topo().Faces().Nb(); ++aFaceIdx) + { + const occ::handle& aSurf = + BRepGraph_Tool::Face::Surface(theGraph, BRepGraph_FaceId(aFaceIdx)); + if (!aSurf.IsNull()) + { + aSurfSet.Add(aSurf.get()); + } + } + return aSurfSet.Extent(); +} + +//================================================================================================= + +int nbUniqueEdgeCurveDefs(const BRepGraph& theGraph) +{ + NCollection_Map aCurveSet; + for (int anEdgeIdx = 0; anEdgeIdx < theGraph.Topo().Edges().Nb(); ++anEdgeIdx) + { + const occ::handle& aCurve = + BRepGraph_Tool::Edge::Curve(theGraph, BRepGraph_EdgeId(anEdgeIdx)); + if (!aCurve.IsNull()) + { + aCurveSet.Add(aCurve.get()); + } + } + return aCurveSet.Extent(); +} + +//================================================================================================= + +int nbPCurveEntries(const BRepGraph& theGraph) +{ + int aCount = 0; + for (int anEdgeIdx = 0; anEdgeIdx < theGraph.Topo().Edges().Nb(); ++anEdgeIdx) + { + if (theGraph.Topo().Edges().Definition(BRepGraph_EdgeId(anEdgeIdx)).IsRemoved) + continue; + const NCollection_Vector& aCoEdgeIdxs = + theGraph.Topo().Edges().CoEdges(BRepGraph_EdgeId(anEdgeIdx)); + for (int i = 0; i < aCoEdgeIdxs.Length(); ++i) + { + const BRepGraphInc::CoEdgeDef& aCE = + theGraph.Topo().CoEdges().Definition(aCoEdgeIdxs.Value(i)); + if (aCE.FaceDefId.IsValid()) + ++aCount; + } + } + return aCount; +} + +//================================================================================================= + +int countHistoryRecordsByOp(const BRepGraph& theGraph, const TCollection_AsciiString& theOp) +{ + int aCount = 0; + for (int aRecIdx = 0; aRecIdx < theGraph.History().NbRecords(); ++aRecIdx) + { + if (theGraph.History().Record(aRecIdx).OperationName == theOp) + { + ++aCount; + } + } + return aCount; +} + +//================================================================================================= + +TopoDS_Compound makeNCopiedIdenticalFaces(const int theCount) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + TopExp_Explorer anExp(aBox, TopAbs_FACE); + const TopoDS_Shape aFace = anExp.Current(); + + BRep_Builder aBuilder; + TopoDS_Compound aCompound; + aBuilder.MakeCompound(aCompound); + for (int anIdx = 0; anIdx < theCount; ++anIdx) + { + BRepBuilderAPI_Copy aCopy(aFace, true); + aBuilder.Add(aCompound, aCopy.Shape()); + } + return aCompound; +} + +TopoDS_Compound makeMixedCompound() +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + TopExp_Explorer anExp(aBox, TopAbs_FACE); + const TopoDS_Shape aBoxFace = anExp.Current(); + + BRepPrimAPI_MakeCylinder aCylMaker(5.0, 15.0); + const TopoDS_Shape& aCyl = aCylMaker.Shape(); + + TopExp_Explorer aCylExp(aCyl, TopAbs_FACE); + const TopoDS_Shape aCylFace = aCylExp.Current(); + + BRep_Builder aBuilder; + TopoDS_Compound aCompound; + aBuilder.MakeCompound(aCompound); + + BRepBuilderAPI_Copy aCopy1(aBoxFace, true); + BRepBuilderAPI_Copy aCopy2(aBoxFace, true); + aBuilder.Add(aCompound, aCopy1.Shape()); + aBuilder.Add(aCompound, aCopy2.Shape()); + aBuilder.Add(aCompound, aCylFace); + return aCompound; +} + +TopoDS_Compound makeNestedCompound() +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + TopExp_Explorer anExp(aBox, TopAbs_FACE); + const TopoDS_Shape aFace = anExp.Current(); + + BRep_Builder aBuilder; + + // Inner compound with 2 copies. + TopoDS_Compound anInner; + aBuilder.MakeCompound(anInner); + BRepBuilderAPI_Copy aCopy1(aFace, true); + BRepBuilderAPI_Copy aCopy2(aFace, true); + aBuilder.Add(anInner, aCopy1.Shape()); + aBuilder.Add(anInner, aCopy2.Shape()); + + // Outer compound wrapping inner + one more copy. + TopoDS_Compound anOuter; + aBuilder.MakeCompound(anOuter); + aBuilder.Add(anOuter, anInner); + BRepBuilderAPI_Copy aCopy3(aFace, true); + aBuilder.Add(anOuter, aCopy3.Shape()); + return anOuter; +} + +TopoDS_Compound makeThreeDistinctPrimitives() +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + BRepPrimAPI_MakeSphere aSphereMaker(5.0); + BRepPrimAPI_MakeCone aConeMaker(3.0, 1.0, 8.0); + + BRep_Builder aBuilder; + TopoDS_Compound aCompound; + aBuilder.MakeCompound(aCompound); + aBuilder.Add(aCompound, aBoxMaker.Shape()); + aBuilder.Add(aCompound, aSphereMaker.Shape()); + aBuilder.Add(aCompound, aConeMaker.Shape()); + return aCompound; +} + +TopoDS_Compound makeTwoIdenticalBoxes() +{ + BRepPrimAPI_MakeBox aBoxMaker1(10.0, 20.0, 30.0); + BRepPrimAPI_MakeBox aBoxMaker2(10.0, 20.0, 30.0); + + BRep_Builder aBuilder; + TopoDS_Compound aCompound; + aBuilder.MakeCompound(aCompound); + aBuilder.Add(aCompound, aBoxMaker1.Shape()); + aBuilder.Add(aCompound, aBoxMaker2.Shape()); + return aCompound; +} + +int nbUniquePCurveNodes(const BRepGraph& theGraph) +{ + int aCount = 0; + for (int anEdgeIdx = 0; anEdgeIdx < theGraph.Topo().Edges().Nb(); ++anEdgeIdx) + { + const NCollection_Vector& aCoEdgeIdxs = + theGraph.Topo().Edges().CoEdges(BRepGraph_EdgeId(anEdgeIdx)); + aCount += aCoEdgeIdxs.Length(); + } + return aCount; +} + +int addDuplicatePCurvesToAllEdges(BRepGraph& theGraph) +{ + int aDupCount = 0; + for (int anEdgeIdx = 0; anEdgeIdx < theGraph.Topo().Edges().Nb(); ++anEdgeIdx) + { + const NCollection_Vector& aCoEdgeIdxs = + theGraph.Topo().Edges().CoEdges(BRepGraph_EdgeId(anEdgeIdx)); + if (aCoEdgeIdxs.IsEmpty()) + { + continue; + } + + const BRepGraphInc::CoEdgeDef& aCE = theGraph.Topo().CoEdges().Definition(aCoEdgeIdxs.Value(0)); + if (!aCE.Curve2DRepId.IsValid()) + { + continue; + } + + const occ::handle& aDupPCurve = BRepGraph_Tool::CoEdge::PCurve(theGraph, aCE); + theGraph.Builder().AddPCurveToEdge(BRepGraph_EdgeId(anEdgeIdx), + aCE.FaceDefId, + aDupPCurve, + aCE.ParamFirst, + aCE.ParamLast, + aCE.Sense); + ++aDupCount; + } + return aDupCount; +} + +} // namespace + +TEST(BRepGraph_DeduplicateTest, AnalyzeOnly_DoesNotRewrite) +{ + BRepGraph aGraph; + aGraph.Build(makeTwoCopiedIdenticalFaces()); + ASSERT_TRUE(aGraph.IsDone()); + + ASSERT_EQ(nbUniqueFaceSurfaceDefs(aGraph), 2); + + BRepGraph_Deduplicate::Options anOpts; + anOpts.AnalyzeOnly = true; + + const BRepGraph_Deduplicate::Result aRes = BRepGraph_Deduplicate::Perform(aGraph, anOpts); + + EXPECT_EQ(aRes.NbSurfaceRewrites, 0); + EXPECT_EQ(nbUniqueFaceSurfaceDefs(aGraph), 2); +} + +TEST(BRepGraph_DeduplicateTest, AnalyzeOnly_ReportsCanonicalCandidates) +{ + BRepGraph aGraph; + aGraph.Build(makeTwoCopiedIdenticalFaces()); + ASSERT_TRUE(aGraph.IsDone()); + + BRepGraph_Deduplicate::Options anOpts; + anOpts.AnalyzeOnly = true; + + const BRepGraph_Deduplicate::Result aRes = BRepGraph_Deduplicate::Perform(aGraph, anOpts); + // TwoCopiedFaces: 2 surfaces -> 1 canonical, 8 curves -> 4 canonical, 8 PCurves -> 8 canonical. + EXPECT_EQ(aRes.NbCanonicalSurfaces, 1); + EXPECT_EQ(aRes.NbCanonicalCurves, 4); + EXPECT_EQ(aRes.NbSurfaceRewrites, 0); + EXPECT_EQ(aRes.NbCurveRewrites, 0); +} + +TEST(BRepGraph_DeduplicateTest, CanonicalizeSurfaces_RewritesAndRecordsHistory) +{ + BRepGraph aGraph; + aGraph.Build(makeTwoCopiedIdenticalFaces()); + ASSERT_TRUE(aGraph.IsDone()); + + ASSERT_EQ(nbUniqueFaceSurfaceDefs(aGraph), 2); + + BRepGraph_Deduplicate::Options anOpts; + anOpts.AnalyzeOnly = false; + anOpts.HistoryMode = true; + + const int aHistoryBefore = aGraph.History().NbRecords(); + const BRepGraph_Deduplicate::Result aRes = BRepGraph_Deduplicate::Perform(aGraph, anOpts); + + EXPECT_EQ(aRes.NbSurfaceRewrites, 1); + EXPECT_EQ(nbUniqueFaceSurfaceDefs(aGraph), 1); + // 1 surface canonicalize + 4 curve canonicalizes = 5 history records. + EXPECT_EQ(aGraph.History().NbRecords(), aHistoryBefore + 5); +} + +TEST(BRepGraph_DeduplicateTest, CanonicalizeCurves_RewritesAndReducesUnique) +{ + BRepGraph aGraph; + aGraph.Build(makeTwoCopiedIdenticalFaces()); + ASSERT_TRUE(aGraph.IsDone()); + + ASSERT_EQ(nbUniqueEdgeCurveDefs(aGraph), 8); + + BRepGraph_Deduplicate::Options anOpts; + anOpts.AnalyzeOnly = false; + + const BRepGraph_Deduplicate::Result aRes = BRepGraph_Deduplicate::Perform(aGraph, anOpts); + EXPECT_EQ(aRes.NbCurveRewrites, 4); + EXPECT_EQ(nbUniqueEdgeCurveDefs(aGraph), 4); +} + +TEST(BRepGraph_DeduplicateTest, HistoryModeOff_DoesNotAddHistory) +{ + BRepGraph aGraph; + aGraph.Build(makeTwoCopiedIdenticalFaces()); + ASSERT_TRUE(aGraph.IsDone()); + + ASSERT_EQ(aGraph.History().NbRecords(), 0); + + BRepGraph_Deduplicate::Options anOpts; + anOpts.HistoryMode = false; + + const BRepGraph_Deduplicate::Result aRes = BRepGraph_Deduplicate::Perform(aGraph, anOpts); + EXPECT_EQ(aRes.NbSurfaceRewrites, 1); + EXPECT_EQ(aRes.NbCurveRewrites, 4); + EXPECT_EQ(aGraph.History().NbRecords(), 0); +} + +TEST(BRepGraph_DeduplicateTest, RestoresHistoryEnabledFlag) +{ + BRepGraph aGraph; + aGraph.Build(makeTwoCopiedIdenticalFaces()); + ASSERT_TRUE(aGraph.IsDone()); + + aGraph.History().SetEnabled(false); + ASSERT_FALSE(aGraph.History().IsEnabled()); + + BRepGraph_Deduplicate::Options anOpts; + anOpts.HistoryMode = true; + (void)BRepGraph_Deduplicate::Perform(aGraph, anOpts); + + EXPECT_FALSE(aGraph.History().IsEnabled()); +} + +TEST(BRepGraph_DeduplicateTest, DefaultOverload_PerformWorks) +{ + BRepGraph aGraph; + aGraph.Build(makeTwoCopiedIdenticalFaces()); + ASSERT_TRUE(aGraph.IsDone()); + + const BRepGraph_Deduplicate::Result aRes = BRepGraph_Deduplicate::Perform(aGraph); + EXPECT_EQ(aRes.NbCanonicalSurfaces, 1); + EXPECT_EQ(aRes.NbSurfaceRewrites, 1); + EXPECT_EQ(aRes.NbCurveRewrites, 4); + // 1 surface canonicalize + 4 curve canonicalizes = 5 history records. + EXPECT_EQ(aRes.NbHistoryRecords, 5); + EXPECT_FALSE(aRes.IsEntityMergeApplied); +} + +TEST(BRepGraph_DeduplicateTest, SingleFace_NoSurfaceRewrite) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + TopExp_Explorer anExp(aBox, TopAbs_FACE); + ASSERT_TRUE(anExp.More()); + + BRepGraph aGraph; + aGraph.Build(anExp.Current()); + ASSERT_TRUE(aGraph.IsDone()); + + const BRepGraph_Deduplicate::Result aRes = BRepGraph_Deduplicate::Perform(aGraph); + EXPECT_EQ(aRes.NbSurfaceRewrites, 0); +} + +// PCurve dedup test removed - PCurves are now inline on EdgeDef, not separate graph nodes. + +TEST(BRepGraph_DeduplicateTest, NotDoneGraph_ReturnsEmptyResult) +{ + BRepGraph aGraph; + // Do not call Build() - graph is not done. + ASSERT_FALSE(aGraph.IsDone()); + + const BRepGraph_Deduplicate::Result aRes = BRepGraph_Deduplicate::Perform(aGraph); + EXPECT_EQ(aRes.NbCanonicalSurfaces, 0); + EXPECT_EQ(aRes.NbCanonicalCurves, 0); + EXPECT_EQ(aRes.NbSurfaceRewrites, 0); + EXPECT_EQ(aRes.NbCurveRewrites, 0); + EXPECT_EQ(aRes.NbNullifiedSurfaces, 0); + EXPECT_EQ(aRes.NbNullifiedCurves, 0); + EXPECT_EQ(aRes.NbHistoryRecords, 0); + EXPECT_FALSE(aRes.IsEntityMergeApplied); +} + +TEST(BRepGraph_DeduplicateTest, Idempotent_SecondRunNoRewrites) +{ + BRepGraph aGraph; + aGraph.Build(makeTwoCopiedIdenticalFaces()); + ASSERT_TRUE(aGraph.IsDone()); + + const BRepGraph_Deduplicate::Result aRes1 = BRepGraph_Deduplicate::Perform(aGraph); + ASSERT_EQ(aRes1.NbSurfaceRewrites, 1); + ASSERT_EQ(aRes1.NbCurveRewrites, 4); + + // After first dedup, all faces share the same surface pointer, all duplicate + // edges share the same curve pointer. A second run still detects the same + // canonical mapping via GeomHash, so rewrite counts may be non-zero (no-op + // at pointer level). Verify the unique pointer count is unchanged. + const int aUniqueSurfsBefore = nbUniqueFaceSurfaceDefs(aGraph); + const int aUniqueCurvesBefore = nbUniqueEdgeCurveDefs(aGraph); + + const BRepGraph_Deduplicate::Result aRes2 = BRepGraph_Deduplicate::Perform(aGraph); + + EXPECT_EQ(nbUniqueFaceSurfaceDefs(aGraph), aUniqueSurfsBefore); + EXPECT_EQ(nbUniqueEdgeCurveDefs(aGraph), aUniqueCurvesBefore); +} + +TEST(BRepGraph_DeduplicateTest, FullBox_AllSurfacesUnique) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + + // A box has 6 faces with 6 distinct Geom_Plane instances (different origins/normals). + // No surface dedup should occur - all 6 are canonical. + const BRepGraph_Deduplicate::Result aRes = BRepGraph_Deduplicate::Perform(aGraph); + EXPECT_EQ(aRes.NbCanonicalSurfaces, 6); + EXPECT_EQ(aRes.NbSurfaceRewrites, 0); +} + +TEST(BRepGraph_DeduplicateTest, ResultCountersConsistency) +{ + BRepGraph aGraph; + aGraph.Build(makeTwoCopiedIdenticalFaces()); + ASSERT_TRUE(aGraph.IsDone()); + + // TwoCopiedFaces: 2 surfaces, 8 curves, 8 PCurves. + ASSERT_EQ(aGraph.Topo().Faces().Nb(), 2); + ASSERT_EQ(aGraph.Topo().Edges().Nb(), 8); + ASSERT_EQ(nbPCurveEntries(aGraph), 8); + + BRepGraph_Deduplicate::Options anOpts; + anOpts.AnalyzeOnly = true; + + const BRepGraph_Deduplicate::Result aRes = BRepGraph_Deduplicate::Perform(aGraph, anOpts); + + // Canonical + duplicates == total for each geometry type. + EXPECT_EQ(aRes.NbCanonicalSurfaces, 1); + EXPECT_EQ(aRes.NbCanonicalSurfaces + (aGraph.Topo().Faces().Nb() - aRes.NbCanonicalSurfaces), + aGraph.Topo().Faces().Nb()); + EXPECT_EQ(aRes.NbCanonicalCurves, 4); + EXPECT_EQ(aRes.NbCanonicalCurves + (aGraph.Topo().Edges().Nb() - aRes.NbCanonicalCurves), + aGraph.Topo().Edges().Nb()); +} + +TEST(BRepGraph_DeduplicateTest, EmptyCompound_NoRewrites) +{ + BRep_Builder aBuilder; + TopoDS_Compound aCompound; + aBuilder.MakeCompound(aCompound); + + BRepGraph aGraph; + aGraph.Build(aCompound); + ASSERT_TRUE(aGraph.IsDone()); + + const BRepGraph_Deduplicate::Result aRes = BRepGraph_Deduplicate::Perform(aGraph); + EXPECT_EQ(aRes.NbSurfaceRewrites, 0); + EXPECT_EQ(aRes.NbCurveRewrites, 0); +} + +TEST(BRepGraph_DeduplicateTest, MultipleCopies_NWayDedup) +{ + const int aNbCopies = 4; + BRepGraph aGraph; + aGraph.Build(makeNCopiedIdenticalFaces(aNbCopies)); + ASSERT_TRUE(aGraph.IsDone()); + + ASSERT_EQ(aGraph.Topo().Faces().Nb(), aNbCopies); + + const BRepGraph_Deduplicate::Result aRes = BRepGraph_Deduplicate::Perform(aGraph); + EXPECT_EQ(aRes.NbCanonicalSurfaces, 1); + EXPECT_EQ(aRes.NbSurfaceRewrites, aNbCopies - 1); + EXPECT_EQ(nbUniqueFaceSurfaceDefs(aGraph), 1); +} + +TEST(BRepGraph_DeduplicateTest, MixedGeometry_OnlyIdenticalDeduped) +{ + BRepGraph aGraph; + aGraph.Build(makeMixedCompound()); + ASSERT_TRUE(aGraph.IsDone()); + + // 2 box face copies + 1 cylinder face = 3 faces, 3 surfaces, 11 curves. + ASSERT_EQ(aGraph.Topo().Faces().Nb(), 3); + ASSERT_EQ(aGraph.Topo().Faces().Nb(), 3); + ASSERT_EQ(aGraph.Topo().Edges().Nb(), 11); + + const BRepGraph_Deduplicate::Result aRes = BRepGraph_Deduplicate::Perform(aGraph); + // Only the two identical box faces should be deduped; cylinder face is untouched. + EXPECT_EQ(aRes.NbCanonicalSurfaces, 2); + EXPECT_EQ(aRes.NbCanonicalCurves, 7); + EXPECT_EQ(aRes.NbSurfaceRewrites, 1); + EXPECT_EQ(aRes.NbCurveRewrites, 4); + // 7 canonicalize + 1 nullify surface + 4 nullify curves + 2 nullify PCurves = 14. + EXPECT_GT(aRes.NbHistoryRecords, 0); + EXPECT_EQ(nbUniqueFaceSurfaceDefs(aGraph), 2); + EXPECT_EQ(nbUniqueEdgeCurveDefs(aGraph), 7); +} + +TEST(BRepGraph_DeduplicateTest, AfterDedup_AllCopiedFacesShareCanonicalSurface) +{ + const int aNbCopies = 3; + BRepGraph aGraph; + aGraph.Build(makeNCopiedIdenticalFaces(aNbCopies)); + ASSERT_TRUE(aGraph.IsDone()); + + (void)BRepGraph_Deduplicate::Perform(aGraph); + + // All face defs should share the same Surface handle. + NCollection_Map aSurfIds; + for (int aFaceIdx = 0; aFaceIdx < aGraph.Topo().Faces().Nb(); ++aFaceIdx) + { + const occ::handle& aSurf = + BRepGraph_Tool::Face::Surface(aGraph, BRepGraph_FaceId(aFaceIdx)); + if (!aSurf.IsNull()) + { + aSurfIds.Add(aSurf.get()); + } + } + EXPECT_EQ(aSurfIds.Extent(), 1); +} + +TEST(BRepGraph_DeduplicateTest, HistoryRecordNames_MatchExpectedOps) +{ + BRepGraph aGraph; + aGraph.Build(makeTwoCopiedIdenticalFaces()); + ASSERT_TRUE(aGraph.IsDone()); + + BRepGraph_Deduplicate::Options anOpts; + anOpts.HistoryMode = true; + + const BRepGraph_Deduplicate::Result aRes = BRepGraph_Deduplicate::Perform(aGraph, anOpts); + // 1 surface canonicalize + 4 curve canonicalizes = 5 history records. + ASSERT_EQ(aRes.NbHistoryRecords, 5); + + // TwoCopiedFaces: 1 surface + 4 curve canonicalizations (no orphan nullification). + const int aNbSurfOps = + countHistoryRecordsByOp(aGraph, TCollection_AsciiString("Dedup:CanonicalizeSurface")); + const int aNbCurveOps = + countHistoryRecordsByOp(aGraph, TCollection_AsciiString("Dedup:CanonicalizeCurve")); + + EXPECT_EQ(aNbSurfOps, 1); + EXPECT_EQ(aNbCurveOps, 4); + EXPECT_EQ(aNbSurfOps + aNbCurveOps, 5); +} + +TEST(BRepGraph_DeduplicateTest, AnalyzeOnly_CurveAndPCurveCountsReported) +{ + BRepGraph aGraph; + aGraph.Build(makeTwoCopiedIdenticalFaces()); + ASSERT_TRUE(aGraph.IsDone()); + + BRepGraph_Deduplicate::Options anOpts; + anOpts.AnalyzeOnly = true; + + const BRepGraph_Deduplicate::Result aRes = BRepGraph_Deduplicate::Perform(aGraph, anOpts); + + // TwoCopiedFaces: 2 geom surfaces, 8 curves, 8 PCurves. + EXPECT_EQ(aRes.NbCanonicalSurfaces, 1); + EXPECT_EQ(aRes.NbCanonicalCurves, 4); + + // But no rewrites. + EXPECT_EQ(aRes.NbSurfaceRewrites, 0); + EXPECT_EQ(aRes.NbCurveRewrites, 0); +} + +// --------------------------------------------------------------------------- +// Tolerance and Options tests +// --------------------------------------------------------------------------- + +TEST(BRepGraph_DeduplicateTest, DefaultOptionsStruct_HasExpectedDefaults) +{ + BRepGraph_Deduplicate::Options anOpts; + EXPECT_FALSE(anOpts.AnalyzeOnly); + EXPECT_TRUE(anOpts.HistoryMode); + EXPECT_FALSE(anOpts.MergeEntitiesWhenSafe); + EXPECT_NEAR(anOpts.CompTolerance, Precision::Angular(), 1e-20); + EXPECT_NEAR(anOpts.HashTolerance, Precision::Confusion(), 1e-20); +} + +TEST(BRepGraph_DeduplicateTest, DefaultResultStruct_AllZeroed) +{ + BRepGraph_Deduplicate::Result aRes; + EXPECT_EQ(aRes.NbCanonicalSurfaces, 0); + EXPECT_EQ(aRes.NbCanonicalCurves, 0); + EXPECT_EQ(aRes.NbSurfaceRewrites, 0); + EXPECT_EQ(aRes.NbCurveRewrites, 0); + EXPECT_EQ(aRes.NbNullifiedSurfaces, 0); + EXPECT_EQ(aRes.NbNullifiedCurves, 0); + EXPECT_EQ(aRes.NbHistoryRecords, 0); + EXPECT_FALSE(aRes.IsEntityMergeApplied); +} + +TEST(BRepGraph_DeduplicateTest, MergeDefsWhenSafe_MergesVertices) +{ + BRepGraph aGraph; + aGraph.Build(makeTwoCopiedIdenticalFaces()); + ASSERT_TRUE(aGraph.IsDone()); + + const int aNbVerticesBefore = aGraph.Topo().Vertices().Nb(); + + BRepGraph_Deduplicate::Options anOpts; + anOpts.MergeEntitiesWhenSafe = true; + + const BRepGraph_Deduplicate::Result aRes = BRepGraph_Deduplicate::Perform(aGraph, anOpts); + // Two copied faces share identical vertex positions -> vertices should merge. + EXPECT_GT(aRes.NbMergedVertices, 0); + EXPECT_TRUE(aRes.IsEntityMergeApplied); + (void)aNbVerticesBefore; +} + +// --------------------------------------------------------------------------- +// Nested and complex compound tests +// --------------------------------------------------------------------------- + +TEST(BRepGraph_DeduplicateTest, NestedCompound_AllCopiesDeduped) +{ + BRepGraph aGraph; + aGraph.Build(makeNestedCompound()); + ASSERT_TRUE(aGraph.IsDone()); + + // 3 copies of the same face across nested compounds: 3 surfaces, 12 curves. + ASSERT_EQ(aGraph.Topo().Faces().Nb(), 3); + ASSERT_EQ(aGraph.Topo().Edges().Nb(), 12); + ASSERT_EQ(aGraph.Topo().Faces().Nb(), 3); + ASSERT_EQ(aGraph.Topo().Edges().Nb(), 12); + + const BRepGraph_Deduplicate::Result aRes = BRepGraph_Deduplicate::Perform(aGraph); + EXPECT_EQ(aRes.NbCanonicalSurfaces, 1); + EXPECT_EQ(aRes.NbCanonicalCurves, 4); + EXPECT_EQ(aRes.NbSurfaceRewrites, 2); + EXPECT_EQ(aRes.NbCurveRewrites, 8); + // 10 canonicalize + 2 nullify surfaces + 8 nullify curves = 20. + EXPECT_GT(aRes.NbHistoryRecords, 0); + EXPECT_EQ(nbUniqueFaceSurfaceDefs(aGraph), 1); + EXPECT_EQ(nbUniqueEdgeCurveDefs(aGraph), 4); +} + +TEST(BRepGraph_DeduplicateTest, ThreeDistinctPrimitives_MinimalDedup) +{ + BRepGraph aGraph; + aGraph.Build(makeThreeDistinctPrimitives()); + ASSERT_TRUE(aGraph.IsDone()); + + // Box(6 faces) + Sphere(1 face) + Cone(3 faces) = 10 faces, 18 edges. + ASSERT_EQ(aGraph.Topo().Faces().Nb(), 10); + ASSERT_EQ(aGraph.Topo().Edges().Nb(), 18); + + const BRepGraph_Deduplicate::Result aRes = BRepGraph_Deduplicate::Perform(aGraph); + // 9 canonical surfaces (1 surface matches between cone bottom and sphere/box plane). + EXPECT_EQ(aRes.NbCanonicalSurfaces, 9); + EXPECT_EQ(aRes.NbSurfaceRewrites, 1); + EXPECT_EQ(nbUniqueFaceSurfaceDefs(aGraph), 9); + // Some curve dedup may occur between the three primitives (shared circle/line geometry). + EXPECT_LE(nbUniqueEdgeCurveDefs(aGraph), 18); +} + +TEST(BRepGraph_DeduplicateTest, TwoIdenticalBoxes_SurfacesAndCurvesDeduped) +{ + BRepGraph aGraph; + aGraph.Build(makeTwoIdenticalBoxes()); + ASSERT_TRUE(aGraph.IsDone()); + + // Two identical boxes: 12 faces, 24 edges, 12 geom surfaces, 24 geom curves. + ASSERT_EQ(aGraph.Topo().Faces().Nb(), 12); + ASSERT_EQ(aGraph.Topo().Edges().Nb(), 24); + ASSERT_EQ(aGraph.Topo().Faces().Nb(), 12); + ASSERT_EQ(aGraph.Topo().Edges().Nb(), 24); + + const BRepGraph_Deduplicate::Result aRes = BRepGraph_Deduplicate::Perform(aGraph); + EXPECT_EQ(aRes.NbCanonicalSurfaces, 6); + EXPECT_EQ(aRes.NbCanonicalCurves, 12); + EXPECT_EQ(aRes.NbSurfaceRewrites, 6); + EXPECT_EQ(aRes.NbCurveRewrites, 12); + // 18 canonicalize + 6 nullify surfaces + 12 nullify curves = 36. + EXPECT_GT(aRes.NbHistoryRecords, 0); + EXPECT_EQ(nbUniqueFaceSurfaceDefs(aGraph), 6); + EXPECT_EQ(nbUniqueEdgeCurveDefs(aGraph), 12); +} + +// --------------------------------------------------------------------------- +// Curve-focused tests +// --------------------------------------------------------------------------- + +TEST(BRepGraph_DeduplicateTest, CurveRewriteCount_MatchesDuplicateEdgeCurves) +{ + BRepGraph aGraph; + aGraph.Build(makeTwoCopiedIdenticalFaces()); + ASSERT_TRUE(aGraph.IsDone()); + + // TwoCopiedFaces: 8 geom curves, 4 canonical -> 4 duplicates. + ASSERT_EQ(aGraph.Topo().Edges().Nb(), 8); + + BRepGraph_Deduplicate::Options anAnalyze; + anAnalyze.AnalyzeOnly = true; + const BRepGraph_Deduplicate::Result anAnalysis = + BRepGraph_Deduplicate::Perform(aGraph, anAnalyze); + EXPECT_EQ(anAnalysis.NbCanonicalCurves, 4); + + const BRepGraph_Deduplicate::Result aRes = BRepGraph_Deduplicate::Perform(aGraph); + // 4 duplicate curve nodes -> exactly 4 edge curve rewrites. + EXPECT_EQ(aRes.NbCurveRewrites, 4); +} + +TEST(BRepGraph_DeduplicateTest, AfterDedup_AllCopiedEdgesShareCanonicalCurve) +{ + BRepGraph aGraph; + aGraph.Build(makeTwoCopiedIdenticalFaces()); + ASSERT_TRUE(aGraph.IsDone()); + + ASSERT_EQ(nbUniqueEdgeCurveDefs(aGraph), 8); + + (void)BRepGraph_Deduplicate::Perform(aGraph); + + // After dedup: 8 unique curves reduced to 4 canonical. + EXPECT_EQ(nbUniqueEdgeCurveDefs(aGraph), 4); +} + +// --------------------------------------------------------------------------- +// PCurve tests +// --------------------------------------------------------------------------- + +TEST(BRepGraph_DeduplicateTest, MultiplePCurveDups_AllEdgesDeduped) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + TopExp_Explorer anExp(aBoxMaker.Shape(), TopAbs_FACE); + BRepBuilderAPI_Copy aCopy(anExp.Current(), true); + + BRepGraph aGraph; + aGraph.Build(aCopy.Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + const int aDupCount = addDuplicatePCurvesToAllEdges(aGraph); + ASSERT_GT(aDupCount, 0); + + const BRepGraph_Deduplicate::Result aRes = BRepGraph_Deduplicate::Perform(aGraph); +} + +TEST(BRepGraph_DeduplicateTest, PCurveDup_AnalyzeOnly_CountsButNoRewrite) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + TopExp_Explorer anExp(aBoxMaker.Shape(), TopAbs_FACE); + BRepBuilderAPI_Copy aCopy(anExp.Current(), true); + + BRepGraph aGraph; + aGraph.Build(aCopy.Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + const int aDupCount = addDuplicatePCurvesToAllEdges(aGraph); + ASSERT_GT(aDupCount, 0); + + const int aUniqueBefore = nbUniquePCurveNodes(aGraph); + + BRepGraph_Deduplicate::Options anOpts; + anOpts.AnalyzeOnly = true; + + const BRepGraph_Deduplicate::Result aRes = BRepGraph_Deduplicate::Perform(aGraph, anOpts); + EXPECT_EQ(nbUniquePCurveNodes(aGraph), aUniqueBefore); + // Canonical count should be less than total PCurves. +} + +TEST(BRepGraph_DeduplicateTest, NoPCurveDuplicates_ZeroPCurveRewrites) +{ + // A single face copy has no PCurve duplicates. + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + TopExp_Explorer anExp(aBoxMaker.Shape(), TopAbs_FACE); + BRepBuilderAPI_Copy aCopy(anExp.Current(), true); + + BRepGraph aGraph; + aGraph.Build(aCopy.Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + const BRepGraph_Deduplicate::Result aRes = BRepGraph_Deduplicate::Perform(aGraph); +} + +// --------------------------------------------------------------------------- +// History tracing tests +// --------------------------------------------------------------------------- + +TEST(BRepGraph_DeduplicateTest, HistoryFindOriginal_TracesBackToCanonical) +{ + BRepGraph aGraph; + aGraph.Build(makeTwoCopiedIdenticalFaces()); + ASSERT_TRUE(aGraph.IsDone()); + + BRepGraph_Deduplicate::Options anOpts; + anOpts.HistoryMode = true; + + const BRepGraph_Deduplicate::Result aRes = BRepGraph_Deduplicate::Perform(aGraph, anOpts); + ASSERT_EQ(aRes.NbHistoryRecords, 5); + + // For each history record, FindOriginal on the replacement should trace back. + for (int aRecIdx = 0; aRecIdx < aGraph.History().NbRecords(); ++aRecIdx) + { + const BRepGraph_HistoryRecord& aRec = aGraph.History().Record(aRecIdx); + for (NCollection_DataMap>::Iterator + aMapIter(aRec.Mapping); + aMapIter.More(); + aMapIter.Next()) + { + const BRepGraph_NodeId& anOriginal = aMapIter.Key(); + const NCollection_Vector& aReplacements = aMapIter.Value(); + for (int aReplIdx = 0; aReplIdx < aReplacements.Length(); ++aReplIdx) + { + const BRepGraph_NodeId aTraced = + aGraph.History().FindOriginal(aReplacements.Value(aReplIdx)); + // FindOriginal should eventually reach a root - the canonical node. + EXPECT_TRUE(aTraced.IsValid()); + // The original node from the record should match one of the trace results. + EXPECT_TRUE(anOriginal.IsValid()); + } + } + } +} + +TEST(BRepGraph_DeduplicateTest, HistoryFindDerived_ContainsCanonicalNode) +{ + BRepGraph aGraph; + aGraph.Build(makeTwoCopiedIdenticalFaces()); + ASSERT_TRUE(aGraph.IsDone()); + + BRepGraph_Deduplicate::Options anOpts; + anOpts.HistoryMode = true; + + const BRepGraph_Deduplicate::Result aRes = BRepGraph_Deduplicate::Perform(aGraph, anOpts); + // 1 surface canonicalize + 4 curve canonicalizes = 5 history records. + ASSERT_EQ(aRes.NbHistoryRecords, 5); + + // For each history record, FindDerived on the original should contain the replacements. + // All records are canonicalize records with 1 replacement (no nullify records). + int aNbCanonMappings = 0; + for (int aRecIdx = 0; aRecIdx < aGraph.History().NbRecords(); ++aRecIdx) + { + const BRepGraph_HistoryRecord& aRec = aGraph.History().Record(aRecIdx); + for (NCollection_DataMap>::Iterator + aMapIter(aRec.Mapping); + aMapIter.More(); + aMapIter.Next()) + { + const BRepGraph_NodeId& anOriginal = aMapIter.Key(); + const NCollection_Vector aDerived = + aGraph.History().FindDerived(anOriginal); + EXPECT_EQ(aDerived.Length(), 1); + ++aNbCanonMappings; + } + } + EXPECT_EQ(aNbCanonMappings, 5); +} + +TEST(BRepGraph_DeduplicateTest, HistoryRecordSequenceNumbers_AreMonotonic) +{ + BRepGraph aGraph; + aGraph.Build(makeTwoCopiedIdenticalFaces()); + ASSERT_TRUE(aGraph.IsDone()); + + BRepGraph_Deduplicate::Options anOpts; + anOpts.HistoryMode = true; + + (void)BRepGraph_Deduplicate::Perform(aGraph, anOpts); + + int aPrevSeq = -1; + for (int aRecIdx = 0; aRecIdx < aGraph.History().NbRecords(); ++aRecIdx) + { + const BRepGraph_HistoryRecord& aRec = aGraph.History().Record(aRecIdx); + EXPECT_GT(aRec.SequenceNumber, aPrevSeq); + aPrevSeq = aRec.SequenceNumber; + } +} + +TEST(BRepGraph_DeduplicateTest, HistoryOff_NbRecordsUnchanged) +{ + BRepGraph aGraph; + aGraph.Build(makeTwoCopiedIdenticalFaces()); + ASSERT_TRUE(aGraph.IsDone()); + + // Run once with history to get 5 records (1 surface + 4 curve canonicalizes). + BRepGraph_Deduplicate::Options anOpts1; + anOpts1.HistoryMode = true; + (void)BRepGraph_Deduplicate::Perform(aGraph, anOpts1); + EXPECT_EQ(aGraph.History().NbRecords(), 5); + + // Fresh graph, run with history off - no records should be added. + BRepGraph aGraph2; + aGraph2.Build(makeTwoCopiedIdenticalFaces()); + ASSERT_TRUE(aGraph2.IsDone()); + + BRepGraph_Deduplicate::Options anOpts2; + anOpts2.HistoryMode = false; + + const BRepGraph_Deduplicate::Result aRes = BRepGraph_Deduplicate::Perform(aGraph2, anOpts2); + EXPECT_EQ(aRes.NbSurfaceRewrites, 1); + EXPECT_EQ(aRes.NbCurveRewrites, 4); + EXPECT_EQ(aGraph2.History().NbRecords(), 0); +} + +// --------------------------------------------------------------------------- +// Canonical node validity tests +// --------------------------------------------------------------------------- + +TEST(BRepGraph_DeduplicateTest, AfterDedup_AllSurfacesValid) +{ + BRepGraph aGraph; + aGraph.Build(makeTwoCopiedIdenticalFaces()); + ASSERT_TRUE(aGraph.IsDone()); + + (void)BRepGraph_Deduplicate::Perform(aGraph); + + for (int aFaceIdx = 0; aFaceIdx < aGraph.Topo().Faces().Nb(); ++aFaceIdx) + { + const BRepGraphInc::FaceDef& aFaceDef = + aGraph.Topo().Faces().Definition(BRepGraph_FaceId(aFaceIdx)); + EXPECT_TRUE(aFaceDef.SurfaceRepId.IsValid()) + << "Face " << aFaceIdx << " has null Surface after dedup"; + } +} + +TEST(BRepGraph_DeduplicateTest, AfterDedup_AllCurve3dsValid) +{ + BRepGraph aGraph; + aGraph.Build(makeTwoCopiedIdenticalFaces()); + ASSERT_TRUE(aGraph.IsDone()); + + (void)BRepGraph_Deduplicate::Perform(aGraph); + + for (int anEdgeIdx = 0; anEdgeIdx < aGraph.Topo().Edges().Nb(); ++anEdgeIdx) + { + const BRepGraphInc::EdgeDef& anEdgeDef = + aGraph.Topo().Edges().Definition(BRepGraph_EdgeId(anEdgeIdx)); + if (!anEdgeDef.IsDegenerate) + { + EXPECT_TRUE(anEdgeDef.Curve3DRepId.IsValid()) + << "Edge " << anEdgeIdx << " has null Curve3d after dedup"; + } + } +} + +TEST(BRepGraph_DeduplicateTest, AfterDedup_AllInlinePCurvesHaveCurve2d) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + TopExp_Explorer anExp(aBoxMaker.Shape(), TopAbs_FACE); + BRepBuilderAPI_Copy aCopy(anExp.Current(), true); + + BRepGraph aGraph; + aGraph.Build(aCopy.Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + (void)addDuplicatePCurvesToAllEdges(aGraph); + (void)BRepGraph_Deduplicate::Perform(aGraph); + + for (int anEdgeIdx = 0; anEdgeIdx < aGraph.Topo().Edges().Nb(); ++anEdgeIdx) + { + const NCollection_Vector& aCoEdgeIdxs = + aGraph.Topo().Edges().CoEdges(BRepGraph_EdgeId(anEdgeIdx)); + for (int aCEIter = 0; aCEIter < aCoEdgeIdxs.Length(); ++aCEIter) + { + const BRepGraphInc::CoEdgeDef& aCE = + aGraph.Topo().CoEdges().Definition(aCoEdgeIdxs.Value(aCEIter)); + EXPECT_TRUE(aCE.Curve2DRepId.IsValid()) + << "Edge " << anEdgeIdx << " CoEdge " << aCEIter << " has null Curve2d after dedup"; + } + } +} + +TEST(BRepGraph_DeduplicateTest, AfterDedup_CanonicalSurfaceGeomNotNull) +{ + BRepGraph aGraph; + aGraph.Build(makeNCopiedIdenticalFaces(4)); + ASSERT_TRUE(aGraph.IsDone()); + + (void)BRepGraph_Deduplicate::Perform(aGraph); + + // All face defs should have a non-null surface after dedup. + for (int aFaceIdx = 0; aFaceIdx < aGraph.Topo().Faces().Nb(); ++aFaceIdx) + { + const BRepGraphInc::FaceDef& aFaceDef = + aGraph.Topo().Faces().Definition(BRepGraph_FaceId(aFaceIdx)); + EXPECT_TRUE(aFaceDef.SurfaceRepId.IsValid()) + << "Face " << aFaceIdx << " has null Surface after dedup"; + } +} + +TEST(BRepGraph_DeduplicateTest, AfterDedup_CanonicalCurveGeomNotNull) +{ + BRepGraph aGraph; + aGraph.Build(makeTwoCopiedIdenticalFaces()); + ASSERT_TRUE(aGraph.IsDone()); + + (void)BRepGraph_Deduplicate::Perform(aGraph); + + for (int anEdgeIdx = 0; anEdgeIdx < aGraph.Topo().Edges().Nb(); ++anEdgeIdx) + { + const BRepGraphInc::EdgeDef& anEdgeDef = + aGraph.Topo().Edges().Definition(BRepGraph_EdgeId(anEdgeIdx)); + if (!anEdgeDef.IsDegenerate) + { + EXPECT_TRUE(anEdgeDef.Curve3DRepId.IsValid()) + << "Edge " << anEdgeIdx << " has null Curve3d after dedup"; + } + } +} + +// --------------------------------------------------------------------------- +// Parallel build + dedup tests +// --------------------------------------------------------------------------- + +TEST(BRepGraph_DeduplicateTest, ParallelBuild_SameResultAsSequential) +{ + const TopoDS_Compound aCompound = makeTwoCopiedIdenticalFaces(); + + BRepGraph aGraphSeq; + aGraphSeq.Build(aCompound, false); + ASSERT_TRUE(aGraphSeq.IsDone()); + + BRepGraph aGraphPar; + aGraphPar.Build(aCompound, true); + ASSERT_TRUE(aGraphPar.IsDone()); + + const BRepGraph_Deduplicate::Result aResSeq = BRepGraph_Deduplicate::Perform(aGraphSeq); + const BRepGraph_Deduplicate::Result aResPar = BRepGraph_Deduplicate::Perform(aGraphPar); + + // Both should produce identical results: 1 canonical surface, 4 canonical curves. + EXPECT_EQ(aResSeq.NbCanonicalSurfaces, 1); + EXPECT_EQ(aResPar.NbCanonicalSurfaces, 1); + EXPECT_EQ(aResSeq.NbCanonicalCurves, 4); + EXPECT_EQ(aResPar.NbCanonicalCurves, 4); + EXPECT_EQ(aResSeq.NbSurfaceRewrites, 1); + EXPECT_EQ(aResPar.NbSurfaceRewrites, 1); + EXPECT_EQ(aResSeq.NbCurveRewrites, 4); + EXPECT_EQ(aResPar.NbCurveRewrites, 4); +} + +// --------------------------------------------------------------------------- +// Scale / many copies tests +// --------------------------------------------------------------------------- + +TEST(BRepGraph_DeduplicateTest, TenCopies_AllDeduplicatedToOneSurface) +{ + const int aNbCopies = 10; + BRepGraph aGraph; + aGraph.Build(makeNCopiedIdenticalFaces(aNbCopies)); + ASSERT_TRUE(aGraph.IsDone()); + + ASSERT_EQ(aGraph.Topo().Faces().Nb(), aNbCopies); + + const BRepGraph_Deduplicate::Result aRes = BRepGraph_Deduplicate::Perform(aGraph); + EXPECT_EQ(aRes.NbCanonicalSurfaces, 1); + EXPECT_EQ(aRes.NbSurfaceRewrites, aNbCopies - 1); + EXPECT_EQ(nbUniqueFaceSurfaceDefs(aGraph), 1); +} + +TEST(BRepGraph_DeduplicateTest, TwoCopies_CurveCanonicalCountLessThanTotal) +{ + BRepGraph aGraph; + aGraph.Build(makeTwoCopiedIdenticalFaces()); + ASSERT_TRUE(aGraph.IsDone()); + + // 8 geom curves, 4 canonical. + ASSERT_EQ(aGraph.Topo().Edges().Nb(), 8); + + BRepGraph_Deduplicate::Options anOpts; + anOpts.AnalyzeOnly = true; + const BRepGraph_Deduplicate::Result aRes = BRepGraph_Deduplicate::Perform(aGraph, anOpts); + + EXPECT_EQ(aRes.NbCanonicalCurves, 4); +} + +// --------------------------------------------------------------------------- +// Idempotency on various shapes +// --------------------------------------------------------------------------- + +TEST(BRepGraph_DeduplicateTest, Idempotent_MixedCompound_SurfacesAndCurves) +{ + BRepGraph aGraph; + aGraph.Build(makeMixedCompound()); + ASSERT_TRUE(aGraph.IsDone()); + + const BRepGraph_Deduplicate::Result aRes1 = BRepGraph_Deduplicate::Perform(aGraph); + ASSERT_EQ(aRes1.NbSurfaceRewrites, 1); + ASSERT_EQ(aRes1.NbCurveRewrites, 4); + + // After first dedup, unique pointer counts are stable. + const int aUniqueSurfs = nbUniqueFaceSurfaceDefs(aGraph); + const int aUniqueCurves = nbUniqueEdgeCurveDefs(aGraph); + + const BRepGraph_Deduplicate::Result aRes2 = BRepGraph_Deduplicate::Perform(aGraph); + + // Unique pointer counts unchanged after second run. + EXPECT_EQ(nbUniqueFaceSurfaceDefs(aGraph), aUniqueSurfs); + EXPECT_EQ(nbUniqueEdgeCurveDefs(aGraph), aUniqueCurves); +} + +TEST(BRepGraph_DeduplicateTest, Idempotent_TwoIdenticalBoxes) +{ + BRepGraph aGraph; + aGraph.Build(makeTwoIdenticalBoxes()); + ASSERT_TRUE(aGraph.IsDone()); + + const BRepGraph_Deduplicate::Result aRes1 = BRepGraph_Deduplicate::Perform(aGraph); + ASSERT_EQ(aRes1.NbSurfaceRewrites, 6); + ASSERT_EQ(aRes1.NbCurveRewrites, 12); + + // After first dedup, unique pointer counts are stable. + const int aUniqueSurfs = nbUniqueFaceSurfaceDefs(aGraph); + const int aUniqueCurves = nbUniqueEdgeCurveDefs(aGraph); + + const BRepGraph_Deduplicate::Result aRes2 = BRepGraph_Deduplicate::Perform(aGraph); + + // Unique pointer counts unchanged after second run. + EXPECT_EQ(nbUniqueFaceSurfaceDefs(aGraph), aUniqueSurfs); + EXPECT_EQ(nbUniqueEdgeCurveDefs(aGraph), aUniqueCurves); +} + +TEST(BRepGraph_DeduplicateTest, DISABLED_PCurveDedup_RewritesReduceUniquePCurveNodes) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + TopExp_Explorer anExp(aBoxMaker.Shape(), TopAbs_FACE); + BRepBuilderAPI_Copy aCopy(anExp.Current(), true); + + BRepGraph aGraph; + aGraph.Build(aCopy.Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + (void)addDuplicatePCurvesToAllEdges(aGraph); + + const int aUniqueBefore = nbUniquePCurveNodes(aGraph); + + const BRepGraph_Deduplicate::Result aRes = BRepGraph_Deduplicate::Perform(aGraph); + + // After dedup, fewer unique PCurve node IDs are referenced. + EXPECT_LT(nbUniquePCurveNodes(aGraph), aUniqueBefore); +} + +// --------------------------------------------------------------------------- +// History enabled flag restoration tests +// --------------------------------------------------------------------------- + +TEST(BRepGraph_DeduplicateTest, RestoresHistoryFlag_WhenHistoryModeOff) +{ + BRepGraph aGraph; + aGraph.Build(makeTwoCopiedIdenticalFaces()); + ASSERT_TRUE(aGraph.IsDone()); + + aGraph.History().SetEnabled(true); + ASSERT_TRUE(aGraph.History().IsEnabled()); + + BRepGraph_Deduplicate::Options anOpts; + anOpts.HistoryMode = false; + (void)BRepGraph_Deduplicate::Perform(aGraph, anOpts); + + // Should be restored to the original value (true). + EXPECT_TRUE(aGraph.History().IsEnabled()); +} + +TEST(BRepGraph_DeduplicateTest, RestoresHistoryFlag_AnalyzeOnlyPath) +{ + BRepGraph aGraph; + aGraph.Build(makeTwoCopiedIdenticalFaces()); + ASSERT_TRUE(aGraph.IsDone()); + + aGraph.History().SetEnabled(true); + + BRepGraph_Deduplicate::Options anOpts; + anOpts.AnalyzeOnly = true; + anOpts.HistoryMode = false; + (void)BRepGraph_Deduplicate::Perform(aGraph, anOpts); + + // Restored even when exiting through the AnalyzeOnly early-return. + EXPECT_TRUE(aGraph.History().IsEnabled()); +} + +// --------------------------------------------------------------------------- +// Geometry count consistency invariants +// --------------------------------------------------------------------------- + +TEST(BRepGraph_DeduplicateTest, GeomCountsUnchanged_AfterDedup) +{ + BRepGraph aGraph; + aGraph.Build(makeTwoCopiedIdenticalFaces()); + ASSERT_TRUE(aGraph.IsDone()); + + // TwoCopiedFaces: 2 surfaces, 8 curves, 8 PCurves. + ASSERT_EQ(aGraph.Topo().Faces().Nb(), 2); + ASSERT_EQ(aGraph.Topo().Edges().Nb(), 8); + ASSERT_EQ(nbPCurveEntries(aGraph), 8); + + (void)BRepGraph_Deduplicate::Perform(aGraph); + + // Dedup rewrites references, but does not remove geometry nodes. + EXPECT_EQ(aGraph.Topo().Faces().Nb(), 2); + EXPECT_EQ(aGraph.Topo().Edges().Nb(), 8); + EXPECT_EQ(nbPCurveEntries(aGraph), 8); +} + +TEST(BRepGraph_DeduplicateTest, DefCountsUnchanged_AfterDedup) +{ + BRepGraph aGraph; + aGraph.Build(makeTwoCopiedIdenticalFaces()); + ASSERT_TRUE(aGraph.IsDone()); + + // TwoCopiedFaces: 2 face defs, 8 edge defs. + ASSERT_EQ(aGraph.Topo().Faces().Nb(), 2); + ASSERT_EQ(aGraph.Topo().Edges().Nb(), 8); + + (void)BRepGraph_Deduplicate::Perform(aGraph); + + // Dedup never removes or adds defs. + EXPECT_EQ(aGraph.Topo().Faces().Nb(), 2); + EXPECT_EQ(aGraph.Topo().Edges().Nb(), 8); +} + +TEST(BRepGraph_DeduplicateTest, PCurveEntryCount_UnchangedAfterDedup) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + TopExp_Explorer anExp(aBoxMaker.Shape(), TopAbs_FACE); + BRepBuilderAPI_Copy aCopy(anExp.Current(), true); + + BRepGraph aGraph; + aGraph.Build(aCopy.Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + (void)addDuplicatePCurvesToAllEdges(aGraph); + + const int aPCEntryCount = nbPCurveEntries(aGraph); + + (void)BRepGraph_Deduplicate::Perform(aGraph); + + // Dedup rewrites PCurve node IDs but doesn't add or remove PCurveEntries. + EXPECT_EQ(nbPCurveEntries(aGraph), aPCEntryCount); +} + +// --------------------------------------------------------------------------- +// Sphere and cylinder specific tests +// --------------------------------------------------------------------------- + +TEST(BRepGraph_DeduplicateTest, TwoCopiedSphereFaces_Deduped) +{ + BRepPrimAPI_MakeSphere aSphereMaker(10.0); + const TopoDS_Shape& aSphere = aSphereMaker.Shape(); + + TopExp_Explorer anExp(aSphere, TopAbs_FACE); + ASSERT_TRUE(anExp.More()); + const TopoDS_Shape aFace = anExp.Current(); + + BRepBuilderAPI_Copy aCopy1(aFace, true); + BRepBuilderAPI_Copy aCopy2(aFace, true); + + BRep_Builder aBuilder; + TopoDS_Compound aCompound; + aBuilder.MakeCompound(aCompound); + aBuilder.Add(aCompound, aCopy1.Shape()); + aBuilder.Add(aCompound, aCopy2.Shape()); + + BRepGraph aGraph; + aGraph.Build(aCompound); + ASSERT_TRUE(aGraph.IsDone()); + + // Two sphere face copies: 2 faces, 6 edges. + ASSERT_EQ(aGraph.Topo().Faces().Nb(), 2); + ASSERT_EQ(aGraph.Topo().Edges().Nb(), 6); + + const BRepGraph_Deduplicate::Result aRes = BRepGraph_Deduplicate::Perform(aGraph); + EXPECT_EQ(aRes.NbCanonicalSurfaces, 1); + EXPECT_EQ(aRes.NbSurfaceRewrites, 1); + // After dedup, both sphere faces share the same surface pointer. + EXPECT_EQ(BRepGraph_Tool::Face::Surface(aGraph, BRepGraph_FaceId(0)).get(), + BRepGraph_Tool::Face::Surface(aGraph, BRepGraph_FaceId(1)).get()); + EXPECT_GT(aRes.NbHistoryRecords, 0); +} + +TEST(BRepGraph_DeduplicateTest, TwoCopiedCylinderFaces_Deduped) +{ + BRepPrimAPI_MakeCylinder aCylMaker(5.0, 20.0); + const TopoDS_Shape& aCyl = aCylMaker.Shape(); + + TopExp_Explorer anExp(aCyl, TopAbs_FACE); + ASSERT_TRUE(anExp.More()); + const TopoDS_Shape aFace = anExp.Current(); + + BRepBuilderAPI_Copy aCopy1(aFace, true); + BRepBuilderAPI_Copy aCopy2(aFace, true); + + BRep_Builder aBuilder; + TopoDS_Compound aCompound; + aBuilder.MakeCompound(aCompound); + aBuilder.Add(aCompound, aCopy1.Shape()); + aBuilder.Add(aCompound, aCopy2.Shape()); + + BRepGraph aGraph; + aGraph.Build(aCompound); + ASSERT_TRUE(aGraph.IsDone()); + + // Two cylinder face copies: 2 faces, 6 edges, 2 surfaces, 6 curves, 12 PCurves. + ASSERT_EQ(aGraph.Topo().Faces().Nb(), 2); + ASSERT_EQ(aGraph.Topo().Faces().Nb(), 2); + ASSERT_EQ(aGraph.Topo().Edges().Nb(), 6); + + const BRepGraph_Deduplicate::Result aRes = BRepGraph_Deduplicate::Perform(aGraph); + EXPECT_EQ(aRes.NbCanonicalSurfaces, 1); + EXPECT_EQ(aRes.NbCanonicalCurves, 3); + EXPECT_EQ(aRes.NbSurfaceRewrites, 1); + EXPECT_EQ(aRes.NbCurveRewrites, 3); + // 8 canonicalize + 1 nullify surface + 3 nullify curves + 4 nullify PCurves = 16. + EXPECT_GT(aRes.NbHistoryRecords, 0); +} + +TEST(BRepGraph_DeduplicateTest, DifferentSizedCylinders_NotDeduped) +{ + BRepPrimAPI_MakeCylinder aCylMaker1(5.0, 20.0); + BRepPrimAPI_MakeCylinder aCylMaker2(10.0, 20.0); + + TopExp_Explorer anExp1(aCylMaker1.Shape(), TopAbs_FACE); + TopExp_Explorer anExp2(aCylMaker2.Shape(), TopAbs_FACE); + ASSERT_TRUE(anExp1.More()); + ASSERT_TRUE(anExp2.More()); + + BRep_Builder aBuilder; + TopoDS_Compound aCompound; + aBuilder.MakeCompound(aCompound); + aBuilder.Add(aCompound, anExp1.Current()); + aBuilder.Add(aCompound, anExp2.Current()); + + BRepGraph aGraph; + aGraph.Build(aCompound); + ASSERT_TRUE(aGraph.IsDone()); + + // Two distinct cylinder faces: 2 surfaces, 6 curves. + ASSERT_EQ(aGraph.Topo().Faces().Nb(), 2); + ASSERT_EQ(aGraph.Topo().Faces().Nb(), 2); + ASSERT_EQ(aGraph.Topo().Edges().Nb(), 6); + + const BRepGraph_Deduplicate::Result aRes = BRepGraph_Deduplicate::Perform(aGraph); + // Different radii -> different cylindrical surfaces -> no surface/curve dedup. + EXPECT_EQ(aRes.NbCanonicalSurfaces, 2); + EXPECT_EQ(aRes.NbCanonicalCurves, 6); + EXPECT_EQ(aRes.NbSurfaceRewrites, 0); + EXPECT_EQ(aRes.NbCurveRewrites, 0); + // No PCurve dedup (inline now), so zero history records for different-sized cylinders. + EXPECT_EQ(aRes.NbHistoryRecords, 0); +} + +// --------------------------------------------------------------------------- +// Back-reference and orphan nullification tests +// --------------------------------------------------------------------------- + +TEST(BRepGraph_DeduplicateTest, BackRefs_SurfaceRewrite_UpdatesFaceDefUsers) +{ + BRepGraph aGraph; + aGraph.Build(makeTwoCopiedIdenticalFaces()); + ASSERT_TRUE(aGraph.IsDone()); + + // Before dedup: each face has its own surface handle. + ASSERT_EQ(aGraph.Topo().Faces().Nb(), 2); + EXPECT_NE(BRepGraph_Tool::Face::Surface(aGraph, BRepGraph_FaceId(0)).get(), + BRepGraph_Tool::Face::Surface(aGraph, BRepGraph_FaceId(1)).get()); + + (void)BRepGraph_Deduplicate::Perform(aGraph); + + // After dedup: both faces share the same canonical surface pointer. + EXPECT_EQ(BRepGraph_Tool::Face::Surface(aGraph, BRepGraph_FaceId(0)).get(), + BRepGraph_Tool::Face::Surface(aGraph, BRepGraph_FaceId(1)).get()); +} + +TEST(BRepGraph_DeduplicateTest, BackRefs_CurveRewrite_UpdatesEdgeDefUsers) +{ + BRepGraph aGraph; + aGraph.Build(makeTwoCopiedIdenticalFaces()); + ASSERT_TRUE(aGraph.IsDone()); + + // Before dedup: 8 edges, each with its own curve handle. + ASSERT_EQ(aGraph.Topo().Edges().Nb(), 8); + + (void)BRepGraph_Deduplicate::Perform(aGraph); + + // After dedup: edges with identical curves should share the same pointer. + // Count distinct curve pointers across all edges. + NCollection_Map aDistinctCurves; + for (int anEdgeIdx = 0; anEdgeIdx < aGraph.Topo().Edges().Nb(); ++anEdgeIdx) + { + const occ::handle& aCurve = + BRepGraph_Tool::Edge::Curve(aGraph, BRepGraph_EdgeId(anEdgeIdx)); + if (!aCurve.IsNull()) + aDistinctCurves.Add(aCurve.get()); + } + // After dedup, 4 canonical curves should remain (from 8 originally). + EXPECT_EQ(aDistinctCurves.Extent(), 4); +} + +TEST(BRepGraph_DeduplicateTest, FacesOnSurface_AfterDedup_ReturnsCorrectDefs) +{ + BRepGraph aGraph; + aGraph.Build(makeTwoCopiedIdenticalFaces()); + ASSERT_TRUE(aGraph.IsDone()); + + (void)BRepGraph_Deduplicate::Perform(aGraph); + + // After dedup, all face defs point to the same canonical surface. + ASSERT_EQ(aGraph.Topo().Faces().Nb(), 2); + EXPECT_TRUE(BRepGraph_Tool::Face::HasSurface(aGraph, BRepGraph_FaceId(0))); + EXPECT_TRUE(BRepGraph_Tool::Face::HasSurface(aGraph, BRepGraph_FaceId(1))); + EXPECT_EQ(BRepGraph_Tool::Face::Surface(aGraph, BRepGraph_FaceId(0)).get(), + BRepGraph_Tool::Face::Surface(aGraph, BRepGraph_FaceId(1)).get()); +} + +TEST(BRepGraph_DeduplicateTest, Nullify_OrphanedSurface_HandleIsNull) +{ + BRepGraph aGraph; + aGraph.Build(makeTwoCopiedIdenticalFaces()); + ASSERT_TRUE(aGraph.IsDone()); + + const BRepGraph_Deduplicate::Result aRes = BRepGraph_Deduplicate::Perform(aGraph); + // No orphan nullification since geometry is stored inline on defs. + EXPECT_EQ(aRes.NbNullifiedSurfaces, 0); + + // After dedup, both faces share the same canonical surface; all surfaces non-null. + for (int aFaceIdx = 0; aFaceIdx < aGraph.Topo().Faces().Nb(); ++aFaceIdx) + { + EXPECT_TRUE(BRepGraph_Tool::Face::HasSurface(aGraph, BRepGraph_FaceId(aFaceIdx))) + << "Face " << aFaceIdx << " has null Surface after dedup"; + } +} + +TEST(BRepGraph_DeduplicateTest, Nullify_OrphanedCurve_HandleIsNull) +{ + BRepGraph aGraph; + aGraph.Build(makeTwoCopiedIdenticalFaces()); + ASSERT_TRUE(aGraph.IsDone()); + + const BRepGraph_Deduplicate::Result aRes = BRepGraph_Deduplicate::Perform(aGraph); + // No orphan nullification since geometry is stored inline on defs. + EXPECT_EQ(aRes.NbNullifiedCurves, 0); + + // After dedup, all non-degenerate edges should have non-null Curve3d; + // duplicate edges now share the canonical curve pointer. + for (int anEdgeIdx = 0; anEdgeIdx < aGraph.Topo().Edges().Nb(); ++anEdgeIdx) + { + if (!BRepGraph_Tool::Edge::Degenerated(aGraph, BRepGraph_EdgeId(anEdgeIdx))) + { + EXPECT_TRUE(BRepGraph_Tool::Edge::HasCurve(aGraph, BRepGraph_EdgeId(anEdgeIdx))) + << "Edge " << anEdgeIdx << " has null Curve3d after dedup"; + } + } + // Verify unique curve count decreased. + EXPECT_EQ(nbUniqueEdgeCurveDefs(aGraph), 4); +} + +TEST(BRepGraph_DeduplicateTest, Nullify_OrphanedPCurve_HandleIsNull) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + TopExp_Explorer anExp(aBoxMaker.Shape(), TopAbs_FACE); + BRepBuilderAPI_Copy aCopy(anExp.Current(), true); + + BRepGraph aGraph; + aGraph.Build(aCopy.Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + const int aDupCount = addDuplicatePCurvesToAllEdges(aGraph); + ASSERT_GT(aDupCount, 0); + + const BRepGraph_Deduplicate::Result aRes = BRepGraph_Deduplicate::Perform(aGraph); + + // After dedup, total PCurve entries may have changed. + // Verify the number of pcurve rewrites is positive (dedup happened). +} + +TEST(BRepGraph_DeduplicateTest, AnalyzeOnly_NoBackRefChangesOrNullification) +{ + BRepGraph aGraph; + aGraph.Build(makeTwoCopiedIdenticalFaces()); + ASSERT_TRUE(aGraph.IsDone()); + + // Snapshot surface/curve pointers before. + NCollection_Vector aSurfPtrs; + for (int aFaceIdx = 0; aFaceIdx < aGraph.Topo().Faces().Nb(); ++aFaceIdx) + aSurfPtrs.Append(BRepGraph_Tool::Face::Surface(aGraph, BRepGraph_FaceId(aFaceIdx)).get()); + NCollection_Vector aCurvePtrs; + for (int anEdgeIdx = 0; anEdgeIdx < aGraph.Topo().Edges().Nb(); ++anEdgeIdx) + aCurvePtrs.Append(BRepGraph_Tool::Edge::Curve(aGraph, BRepGraph_EdgeId(anEdgeIdx)).get()); + + BRepGraph_Deduplicate::Options anOpts; + anOpts.AnalyzeOnly = true; + + const BRepGraph_Deduplicate::Result aRes = BRepGraph_Deduplicate::Perform(aGraph, anOpts); + EXPECT_EQ(aRes.NbNullifiedSurfaces, 0); + EXPECT_EQ(aRes.NbNullifiedCurves, 0); + + // Surface/curve pointers unchanged (analyze only, no rewriting). + for (int aFaceIdx = 0; aFaceIdx < aGraph.Topo().Faces().Nb(); ++aFaceIdx) + { + EXPECT_EQ(BRepGraph_Tool::Face::Surface(aGraph, BRepGraph_FaceId(aFaceIdx)).get(), + aSurfPtrs.Value(aFaceIdx)); + } + for (int anEdgeIdx = 0; anEdgeIdx < aGraph.Topo().Edges().Nb(); ++anEdgeIdx) + { + EXPECT_EQ(BRepGraph_Tool::Edge::Curve(aGraph, BRepGraph_EdgeId(anEdgeIdx)).get(), + aCurvePtrs.Value(anEdgeIdx)); + } + + // All handles still non-null. + for (int aFaceIdx = 0; aFaceIdx < aGraph.Topo().Faces().Nb(); ++aFaceIdx) + { + EXPECT_TRUE(BRepGraph_Tool::Face::HasSurface(aGraph, BRepGraph_FaceId(aFaceIdx))); + } + for (int anEdgeIdx = 0; anEdgeIdx < aGraph.Topo().Edges().Nb(); ++anEdgeIdx) + { + if (!BRepGraph_Tool::Edge::Degenerated(aGraph, BRepGraph_EdgeId(anEdgeIdx))) + { + EXPECT_TRUE(BRepGraph_Tool::Edge::HasCurve(aGraph, BRepGraph_EdgeId(anEdgeIdx))); + } + } +} + +// --------------------------------------------------------------------------- +// Round-trip tests: Build -> Dedup -> Reconstruct -> Build verifies geometry sharing +// --------------------------------------------------------------------------- + +TEST(BRepGraph_DeduplicateTest, RoundTrip_TwoCopiedFaces_FewerSurfaces) +{ + const TopoDS_Compound aCompound = makeTwoCopiedIdenticalFaces(); + + // First graph: build and dedup. + BRepGraph aGraph1; + aGraph1.Build(aCompound); + ASSERT_TRUE(aGraph1.IsDone()); + ASSERT_EQ(aGraph1.Topo().Faces().Nb(), 2); + + (void)BRepGraph_Deduplicate::Perform(aGraph1); + ASSERT_EQ(nbUniqueFaceSurfaceDefs(aGraph1), 1); + + // Reconstruct each face individually and assemble into a compound. + BRep_Builder aBB; + TopoDS_Compound aReconstructed; + aBB.MakeCompound(aReconstructed); + for (int aFaceIdx = 0; aFaceIdx < aGraph1.Topo().Faces().Nb(); ++aFaceIdx) + { + const TopoDS_Shape aFace = aGraph1.Shapes().Reconstruct(BRepGraph_FaceId(aFaceIdx)); + ASSERT_FALSE(aFace.IsNull()); + aBB.Add(aReconstructed, aFace); + } + + // Second graph: build from reconstructed shape. + BRepGraph aGraph2; + aGraph2.Build(aReconstructed); + ASSERT_TRUE(aGraph2.IsDone()); + + // Face defs count stays 2 (topology defs, not geometry nodes). + // After round-trip, unique surface pointer count should be 1 + // because both faces share the same surface handle from dedup. + EXPECT_EQ(aGraph2.Topo().Faces().Nb(), 2); + EXPECT_EQ(nbUniqueFaceSurfaceDefs(aGraph2), 1); +} + +TEST(BRepGraph_DeduplicateTest, RoundTrip_TwoCopiedFaces_FewerCurves) +{ + const TopoDS_Compound aCompound = makeTwoCopiedIdenticalFaces(); + + BRepGraph aGraph1; + aGraph1.Build(aCompound); + ASSERT_TRUE(aGraph1.IsDone()); + ASSERT_EQ(aGraph1.Topo().Edges().Nb(), 8); + + (void)BRepGraph_Deduplicate::Perform(aGraph1); + ASSERT_EQ(nbUniqueEdgeCurveDefs(aGraph1), 4); + + // Reconstruct each face individually. + BRep_Builder aBB; + TopoDS_Compound aReconstructed; + aBB.MakeCompound(aReconstructed); + for (int aFaceIdx = 0; aFaceIdx < aGraph1.Topo().Faces().Nb(); ++aFaceIdx) + { + const TopoDS_Shape aFace = aGraph1.Shapes().Reconstruct(BRepGraph_FaceId(aFaceIdx)); + ASSERT_FALSE(aFace.IsNull()); + aBB.Add(aReconstructed, aFace); + } + + BRepGraph aGraph2; + aGraph2.Build(aReconstructed); + ASSERT_TRUE(aGraph2.IsDone()); + + // Edge defs count stays 8 (topology defs, not geometry nodes). + // After round-trip, unique curve pointer count should be 4 + // because duplicate edges share the same curve handle from dedup. + EXPECT_EQ(aGraph2.Topo().Edges().Nb(), 8); + EXPECT_EQ(nbUniqueEdgeCurveDefs(aGraph2), 4); +} + +TEST(BRepGraph_DeduplicateTest, Build_SharedTFace_OneSurfaceNode) +{ + // Create a box and extract two faces that share the same TFace via Same(). + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + TopExp_Explorer anExp(aBox, TopAbs_FACE); + ASSERT_TRUE(anExp.More()); + const TopoDS_Face aFace1 = TopoDS::Face(anExp.Current()); + + // Create a compound with the same face referenced twice (same TShape). + BRep_Builder aBuilder; + TopoDS_Compound aCompound; + aBuilder.MakeCompound(aCompound); + aBuilder.Add(aCompound, aFace1); + aBuilder.Add(aCompound, aFace1); + + BRepGraph aGraph; + aGraph.Build(aCompound); + ASSERT_TRUE(aGraph.IsDone()); + + // Both face usages share the same TFace -> same raw surface pointer -> one surface node. + EXPECT_EQ(aGraph.Topo().Faces().Nb(), 1); +} + +TEST(BRepGraph_DeduplicateTest, RoundTrip_TwoBoxes_GeomReduction) +{ + const TopoDS_Compound aCompound = makeTwoIdenticalBoxes(); + + BRepGraph aGraph1; + aGraph1.Build(aCompound); + ASSERT_TRUE(aGraph1.IsDone()); + + const int aSurfsBefore = aGraph1.Topo().Faces().Nb(); + const int aCurvesBefore = aGraph1.Topo().Edges().Nb(); + ASSERT_EQ(aSurfsBefore, 12); + ASSERT_EQ(aCurvesBefore, 24); + + (void)BRepGraph_Deduplicate::Perform(aGraph1); + + // Force reconstruction from deduped graph. + BRepGraph_NodeId aRootId(BRepGraph_NodeId::Kind::Compound, 0); + const TopoDS_Shape aReconstructed = aGraph1.Shapes().Reconstruct(aRootId); + ASSERT_FALSE(aReconstructed.IsNull()); + + // Build second graph. + BRepGraph aGraph2; + aGraph2.Build(aReconstructed); + ASSERT_TRUE(aGraph2.IsDone()); + + // Face/edge def counts stay the same (topology defs, not geometry nodes). + EXPECT_EQ(aGraph2.Topo().Faces().Nb(), aSurfsBefore); + EXPECT_EQ(aGraph2.Topo().Edges().Nb(), aCurvesBefore); + + // After dedup, unique geometry pointer counts should reflect deduplication: + // 6 canonical surfaces and 12 canonical curves. + EXPECT_EQ(nbUniqueFaceSurfaceDefs(aGraph2), 6); + EXPECT_EQ(nbUniqueEdgeCurveDefs(aGraph2), 12); +} + +// --------------------------------------------------------------------------- +// Topology definition merge tests (MergeEntitiesWhenSafe = true) +// --------------------------------------------------------------------------- + +TEST(BRepGraph_DeduplicateTest, MergeVertices_SharedVerticesReduced) +{ + BRepGraph aGraph; + aGraph.Build(makeTwoCopiedIdenticalFaces()); + ASSERT_TRUE(aGraph.IsDone()); + + const int aNbVerticesBefore = aGraph.Topo().Vertices().Nb(); + + BRepGraph_Deduplicate::Options anOpts; + anOpts.MergeEntitiesWhenSafe = true; + + const BRepGraph_Deduplicate::Result aRes = BRepGraph_Deduplicate::Perform(aGraph, anOpts); + // Two copied faces -> shared vertex positions should merge. + EXPECT_GT(aRes.NbMergedVertices, 0); + + // Count non-removed vertices. + int aNbActiveVertices = 0; + for (int anIdx = 0; anIdx < aGraph.Topo().Vertices().Nb(); ++anIdx) + { + if (!aGraph.Topo().Vertices().Definition(BRepGraph_VertexId(anIdx)).IsRemoved) + ++aNbActiveVertices; + } + EXPECT_LT(aNbActiveVertices, aNbVerticesBefore); +} + +TEST(BRepGraph_DeduplicateTest, MergeEdges_SharedEdgesReduced) +{ + BRepGraph aGraph; + aGraph.Build(makeTwoCopiedIdenticalFaces()); + ASSERT_TRUE(aGraph.IsDone()); + + BRepGraph_Deduplicate::Options anOpts; + anOpts.MergeEntitiesWhenSafe = true; + + const BRepGraph_Deduplicate::Result aRes = BRepGraph_Deduplicate::Perform(aGraph, anOpts); + EXPECT_GT(aRes.NbMergedEdges, 0); +} + +TEST(BRepGraph_DeduplicateTest, MergeWires_IdenticalWiresMerged) +{ + BRepGraph aGraph; + aGraph.Build(makeTwoCopiedIdenticalFaces()); + ASSERT_TRUE(aGraph.IsDone()); + + BRepGraph_Deduplicate::Options anOpts; + anOpts.MergeEntitiesWhenSafe = true; + + const BRepGraph_Deduplicate::Result aRes = BRepGraph_Deduplicate::Perform(aGraph, anOpts); + // After vertex and edge merge, identical wires should also merge. + EXPECT_GE(aRes.NbMergedWires, 0); +} + +TEST(BRepGraph_DeduplicateTest, MergeFaces_IdenticalFacesMerged) +{ + BRepGraph aGraph; + aGraph.Build(makeTwoCopiedIdenticalFaces()); + ASSERT_TRUE(aGraph.IsDone()); + + BRepGraph_Deduplicate::Options anOpts; + anOpts.MergeEntitiesWhenSafe = true; + + const BRepGraph_Deduplicate::Result aRes = BRepGraph_Deduplicate::Perform(aGraph, anOpts); + // After lower-level merges, faces with same surface+wires should merge. + EXPECT_GE(aRes.NbMergedFaces, 0); +} + +TEST(BRepGraph_DeduplicateTest, MergeDefsWhenSafe_False_NoMerge) +{ + BRepGraph aGraph; + aGraph.Build(makeTwoCopiedIdenticalFaces()); + ASSERT_TRUE(aGraph.IsDone()); + + // Default: MergeEntitiesWhenSafe = false. + const BRepGraph_Deduplicate::Result aRes = BRepGraph_Deduplicate::Perform(aGraph); + EXPECT_EQ(aRes.NbMergedVertices, 0); + EXPECT_EQ(aRes.NbMergedEdges, 0); + EXPECT_EQ(aRes.NbMergedWires, 0); + EXPECT_EQ(aRes.NbMergedFaces, 0); + EXPECT_FALSE(aRes.IsEntityMergeApplied); +} + +TEST(BRepGraph_DeduplicateTest, AnalyzeOnly_MergeDefsWhenSafe_CountsOnly) +{ + BRepGraph aGraph; + aGraph.Build(makeTwoCopiedIdenticalFaces()); + ASSERT_TRUE(aGraph.IsDone()); + + const int aNbVerticesBefore = aGraph.Topo().Vertices().Nb(); + + BRepGraph_Deduplicate::Options anOpts; + anOpts.MergeEntitiesWhenSafe = true; + anOpts.AnalyzeOnly = true; + + const BRepGraph_Deduplicate::Result aRes = BRepGraph_Deduplicate::Perform(aGraph, anOpts); + // Counts should be reported. + EXPECT_GT(aRes.NbMergedVertices, 0); + // But no mutation: all vertices still present. + EXPECT_EQ(aGraph.Topo().Vertices().Nb(), aNbVerticesBefore); + EXPECT_FALSE(aRes.IsEntityMergeApplied); +} + +TEST(BRepGraph_DeduplicateTest, HistoryRecords_MergePhases) +{ + BRepGraph aGraph; + aGraph.Build(makeTwoCopiedIdenticalFaces()); + ASSERT_TRUE(aGraph.IsDone()); + + BRepGraph_Deduplicate::Options anOpts; + anOpts.MergeEntitiesWhenSafe = true; + anOpts.HistoryMode = true; + + const BRepGraph_Deduplicate::Result aRes = BRepGraph_Deduplicate::Perform(aGraph, anOpts); + + const int aNbVertexMerge = + countHistoryRecordsByOp(aGraph, TCollection_AsciiString("Dedup:MergeVertex")); + const int aNbEdgeMerge = + countHistoryRecordsByOp(aGraph, TCollection_AsciiString("Dedup:MergeEdge")); + + EXPECT_EQ(aNbVertexMerge, aRes.NbMergedVertices); + EXPECT_EQ(aNbEdgeMerge, aRes.NbMergedEdges); +} + +TEST(BRepGraph_DeduplicateTest, AfterMerge_Validate_NoIssues) +{ + BRepGraph aGraph; + aGraph.Build(makeTwoCopiedIdenticalFaces()); + ASSERT_TRUE(aGraph.IsDone()); + + BRepGraph_Deduplicate::Options anOpts; + anOpts.MergeEntitiesWhenSafe = true; + + (void)BRepGraph_Deduplicate::Perform(aGraph, anOpts); + + // After merge + compact, graph should be structurally valid. + // Merge alone may leave stale back-references on geometry nodes + // that are cleaned up by compaction. + (void)BRepGraph_Compact::Perform(aGraph); + + const BRepGraph_Validate::Result aValResult = BRepGraph_Validate::Perform(aGraph); + EXPECT_TRUE(aValResult.IsValid()); +} diff --git a/src/ModelingData/TKBRep/GTests/BRepGraph_DeferredInvalidation_Test.cxx b/src/ModelingData/TKBRep/GTests/BRepGraph_DeferredInvalidation_Test.cxx new file mode 100644 index 0000000000..9c3af55dde --- /dev/null +++ b/src/ModelingData/TKBRep/GTests/BRepGraph_DeferredInvalidation_Test.cxx @@ -0,0 +1,336 @@ +// Copyright (c) 2026 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 "BRepGraph_RefTestTools.hxx" +#include +#include +#include +#include +#include + +#include + +#include + +class BRepGraph_DeferredInvalidationTest : public testing::Test +{ +protected: + void SetUp() override + { + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + myGraph.Build(aBox); + ASSERT_TRUE(myGraph.IsDone()); + } + + BRepGraph myGraph; +}; + +TEST_F(BRepGraph_DeferredInvalidationTest, DeferredMode_EdgeMutation_IncrementsOwnGen) +{ + EXPECT_EQ(myGraph.Topo().Edges().Definition(BRepGraph_EdgeId(0)).OwnGen, 0u); + + myGraph.Builder().BeginDeferredInvalidation(); + myGraph.Builder().MutEdge(BRepGraph_EdgeId(0))->Tolerance = 0.5; + // In deferred mode, the entity's OwnGen is incremented. + EXPECT_GT(myGraph.Topo().Edges().Definition(BRepGraph_EdgeId(0)).OwnGen, 0u); + myGraph.Builder().EndDeferredInvalidation(); +} + +TEST_F(BRepGraph_DeferredInvalidationTest, DeferredMode_PropagatesUpOnFlush) +{ + myGraph.Builder().BeginDeferredInvalidation(); + myGraph.Builder().MutEdge(BRepGraph_EdgeId(0))->Tolerance = 0.5; + + // During deferred mode: edge is mutated, but parent wire/face are NOT yet. + const NCollection_Vector& aWires = + myGraph.Topo().Edges().Wires(BRepGraph_EdgeId(0)); + ASSERT_GT(aWires.Length(), 0); + EXPECT_EQ(myGraph.Topo().Wires().Definition(aWires.Value(0)).SubtreeGen, 0u); + + myGraph.Builder().EndDeferredInvalidation(); + + // After flush: wire and face SubtreeGen should be propagated. + EXPECT_GT(myGraph.Topo().Wires().Definition(aWires.Value(0)).SubtreeGen, 0u); + + // Check propagation to face. + for (BRepGraph_FaceIterator aFaceIt(myGraph); aFaceIt.More(); aFaceIt.Next()) + { + if (BRepGraph_TestTools::FaceUsesWire(myGraph, aFaceIt.CurrentId(), aWires.Value(0))) + { + EXPECT_GT(aFaceIt.Current().SubtreeGen, 0u); + break; + } + } + + // Check propagation to shell and solid. + EXPECT_GT(myGraph.Topo().Shells().Definition(BRepGraph_ShellId(0)).SubtreeGen, 0u); + EXPECT_GT(myGraph.Topo().Solids().Definition(BRepGraph_SolidId(0)).SubtreeGen, 0u); +} + +TEST_F(BRepGraph_DeferredInvalidationTest, DeferredMode_DirectFaceMutation_PropagatesUp) +{ + myGraph.Builder().BeginDeferredInvalidation(); + myGraph.Builder().MutFace(BRepGraph_FaceId(0)); + + // Face was directly mutated: OwnGen incremented. + EXPECT_GT(myGraph.Topo().Faces().Definition(BRepGraph_FaceId(0)).OwnGen, 0u); + // Shell not yet propagated during deferred mode. + EXPECT_EQ(myGraph.Topo().Shells().Definition(BRepGraph_ShellId(0)).SubtreeGen, 0u); + + myGraph.Builder().EndDeferredInvalidation(); + + // After flush: shell and solid SubtreeGen should be propagated. + EXPECT_GT(myGraph.Topo().Shells().Definition(BRepGraph_ShellId(0)).SubtreeGen, 0u); + EXPECT_GT(myGraph.Topo().Solids().Definition(BRepGraph_SolidId(0)).SubtreeGen, 0u); +} + +TEST_F(BRepGraph_DeferredInvalidationTest, DeferredMode_DirectShellMutation_PropagatesUp) +{ + myGraph.Builder().BeginDeferredInvalidation(); + myGraph.Builder().MutShell(BRepGraph_ShellId(0)); + + // Shell was directly mutated: OwnGen incremented. + EXPECT_GT(myGraph.Topo().Shells().Definition(BRepGraph_ShellId(0)).OwnGen, 0u); + // Solid not yet propagated during deferred mode. + EXPECT_EQ(myGraph.Topo().Solids().Definition(BRepGraph_SolidId(0)).SubtreeGen, 0u); + + myGraph.Builder().EndDeferredInvalidation(); + + // After flush: solid SubtreeGen should be propagated. + EXPECT_GT(myGraph.Topo().Solids().Definition(BRepGraph_SolidId(0)).SubtreeGen, 0u); +} + +TEST_F(BRepGraph_DeferredInvalidationTest, DeferredMode_MultipleEdges_BatchPropagation) +{ + myGraph.Builder().BeginDeferredInvalidation(); + + for (BRepGraph_EdgeIterator anEdgeIt(myGraph); anEdgeIt.More(); anEdgeIt.Next()) + { + myGraph.Builder().MutEdge(anEdgeIt.CurrentId())->Tolerance = 0.1; + } + + // During deferred mode: all edges mutated, but no parent propagation yet. + for (BRepGraph_WireIterator aWireIt(myGraph); aWireIt.More(); aWireIt.Next()) + { + EXPECT_EQ(aWireIt.Current().SubtreeGen, 0u); + } + + myGraph.Builder().EndDeferredInvalidation(); + + // After flush: all wires, faces, shells, solids should have SubtreeGen propagated. + for (BRepGraph_WireIterator aWireIt(myGraph); aWireIt.More(); aWireIt.Next()) + { + EXPECT_GT(aWireIt.Current().SubtreeGen, 0u); + } + for (BRepGraph_FaceIterator aFaceIt(myGraph); aFaceIt.More(); aFaceIt.Next()) + { + EXPECT_GT(aFaceIt.Current().SubtreeGen, 0u); + } + EXPECT_GT(myGraph.Topo().Shells().Definition(BRepGraph_ShellId(0)).SubtreeGen, 0u); + EXPECT_GT(myGraph.Topo().Solids().Definition(BRepGraph_SolidId(0)).SubtreeGen, 0u); +} + +TEST_F(BRepGraph_DeferredInvalidationTest, DeferredMode_ReconstructAfterFlush_Succeeds) +{ + // Modify an edge in deferred mode and verify reconstruction still works. + myGraph.Builder().BeginDeferredInvalidation(); + myGraph.Builder().MutEdge(BRepGraph_EdgeId(0))->Tolerance = 0.5; + myGraph.Builder().EndDeferredInvalidation(); + + // Reconstruction should succeed (shape cache was cleared on flush). + const BRepGraph_NodeId aSolidId = BRepGraph_SolidId(0); + TopoDS_Shape aShape; + EXPECT_NO_THROW(aShape = myGraph.Shapes().Reconstruct(aSolidId)); + EXPECT_FALSE(aShape.IsNull()); +} + +TEST_F(BRepGraph_DeferredInvalidationTest, DeferredMode_ParallelMutation_WithExternalSync) +{ + const int aNbEdges = myGraph.Topo().Edges().Nb(); + ASSERT_GT(aNbEdges, 1); + + // Deferred mode is NOT internally thread-safe. Parallel callers must + // provide external synchronization around each MutGuard usage. + std::mutex aMutex; + myGraph.Builder().BeginDeferredInvalidation(); + OSD_Parallel::For( + 0, + aNbEdges, + [&](int theIdx) { + std::lock_guard aLock(aMutex); + myGraph.Builder().MutEdge(BRepGraph_EdgeId(theIdx))->Tolerance = 0.1 + theIdx * 0.01; + }, + false); + myGraph.Builder().EndDeferredInvalidation(); + + // All edges should be mutated (directly: OwnGen). + for (BRepGraph_EdgeIterator anEdgeIt(myGraph); anEdgeIt.More(); anEdgeIt.Next()) + { + EXPECT_GT(anEdgeIt.Current().OwnGen, 0u); + EXPECT_NEAR(anEdgeIt.Current().Tolerance, + 0.1 + anEdgeIt.CurrentId().Index * 0.01, + Precision::Confusion()); + } + + // Propagation should have happened on flush (parents: SubtreeGen). + EXPECT_GT(myGraph.Topo().Shells().Definition(BRepGraph_ShellId(0)).SubtreeGen, 0u); + EXPECT_GT(myGraph.Topo().Solids().Definition(BRepGraph_SolidId(0)).SubtreeGen, 0u); +} + +TEST_F(BRepGraph_DeferredInvalidationTest, DeferredMode_NoMutations_FlushIsSafe) +{ + // Begin/End with no mutations in between should be a no-op. + myGraph.Builder().BeginDeferredInvalidation(); + myGraph.Builder().EndDeferredInvalidation(); + + // Nothing should be mutated. + EXPECT_EQ(myGraph.Topo().Edges().Definition(BRepGraph_EdgeId(0)).OwnGen, 0u); + EXPECT_EQ(myGraph.Topo().Wires().Definition(BRepGraph_WireId(0)).OwnGen, 0u); + EXPECT_EQ(myGraph.Topo().Faces().Definition(BRepGraph_FaceId(0)).OwnGen, 0u); + EXPECT_EQ(myGraph.Topo().Shells().Definition(BRepGraph_ShellId(0)).OwnGen, 0u); + EXPECT_EQ(myGraph.Topo().Solids().Definition(BRepGraph_SolidId(0)).OwnGen, 0u); + EXPECT_EQ(myGraph.Topo().Edges().Definition(BRepGraph_EdgeId(0)).SubtreeGen, 0u); + EXPECT_EQ(myGraph.Topo().Wires().Definition(BRepGraph_WireId(0)).SubtreeGen, 0u); + EXPECT_EQ(myGraph.Topo().Faces().Definition(BRepGraph_FaceId(0)).SubtreeGen, 0u); + EXPECT_EQ(myGraph.Topo().Shells().Definition(BRepGraph_ShellId(0)).SubtreeGen, 0u); + EXPECT_EQ(myGraph.Topo().Solids().Definition(BRepGraph_SolidId(0)).SubtreeGen, 0u); +} + +TEST_F(BRepGraph_DeferredInvalidationTest, EndWithoutBegin_IsIdempotent) +{ + // Calling End without Begin should be a safe no-op. + EXPECT_NO_THROW(myGraph.Builder().EndDeferredInvalidation()); + + // Nothing should be mutated or cleared. + EXPECT_EQ(myGraph.Topo().Edges().Definition(BRepGraph_EdgeId(0)).OwnGen, 0u); + EXPECT_EQ(myGraph.Topo().Solids().Definition(BRepGraph_SolidId(0)).OwnGen, 0u); +} + +TEST_F(BRepGraph_DeferredInvalidationTest, DeferredScope_NestedGuards_FlushOnlyOnOuterDestruction) +{ + const NCollection_Vector& aWires = + myGraph.Topo().Edges().Wires(BRepGraph_EdgeId(0)); + ASSERT_GT(aWires.Length(), 0); + const BRepGraph_WireId aWireId = aWires.Value(0); + + { + BRepGraph_DeferredScope anOuterScope(myGraph); + EXPECT_TRUE(myGraph.Builder().IsDeferredMode()); + + { + BRepGraph_DeferredScope anInnerScope(myGraph); + EXPECT_TRUE(myGraph.Builder().IsDeferredMode()); + myGraph.Builder().MutEdge(BRepGraph_EdgeId(0))->Tolerance = 0.5; + } + + EXPECT_TRUE(myGraph.Builder().IsDeferredMode()); + EXPECT_EQ(myGraph.Topo().Wires().Definition(aWireId).SubtreeGen, 0u); + } + + EXPECT_FALSE(myGraph.Builder().IsDeferredMode()); + EXPECT_GT(myGraph.Topo().Wires().Definition(aWireId).SubtreeGen, 0u); +} + +TEST_F(BRepGraph_DeferredInvalidationTest, DeferredMode_DoubleEnd_IsIdempotent) +{ + myGraph.Builder().BeginDeferredInvalidation(); + myGraph.Builder().MutEdge(BRepGraph_EdgeId(0))->Tolerance = 0.5; + myGraph.Builder().EndDeferredInvalidation(); + + // Second End should be a safe no-op. + EXPECT_NO_THROW(myGraph.Builder().EndDeferredInvalidation()); + EXPECT_GT(myGraph.Topo().Edges().Definition(BRepGraph_EdgeId(0)).OwnGen, 0u); +} + +TEST_F(BRepGraph_DeferredInvalidationTest, DeferredMode_SameEdgeMutatedTwice) +{ + myGraph.Builder().BeginDeferredInvalidation(); + myGraph.Builder().MutEdge(BRepGraph_EdgeId(0))->Tolerance = 0.1; + myGraph.Builder().MutEdge(BRepGraph_EdgeId(0))->Tolerance = 0.5; + myGraph.Builder().EndDeferredInvalidation(); + + // Last write wins. + EXPECT_NEAR(myGraph.Topo().Edges().Definition(BRepGraph_EdgeId(0)).Tolerance, + 0.5, + Precision::Confusion()); + EXPECT_GT(myGraph.Topo().Edges().Definition(BRepGraph_EdgeId(0)).OwnGen, 0u); + EXPECT_GT(myGraph.Topo().Shells().Definition(BRepGraph_ShellId(0)).SubtreeGen, 0u); + EXPECT_GT(myGraph.Topo().Solids().Definition(BRepGraph_SolidId(0)).SubtreeGen, 0u); +} + +TEST_F(BRepGraph_DeferredInvalidationTest, DeferredMode_DirectWireMutation_PropagatesUp) +{ + myGraph.Builder().BeginDeferredInvalidation(); + myGraph.Builder().MutWire(BRepGraph_WireId(0)); + + // Wire was directly mutated: OwnGen incremented. + EXPECT_GT(myGraph.Topo().Wires().Definition(BRepGraph_WireId(0)).OwnGen, 0u); + // Face not yet propagated during deferred mode. + EXPECT_EQ(myGraph.Topo().Faces().Definition(BRepGraph_FaceId(0)).SubtreeGen, 0u); + + myGraph.Builder().EndDeferredInvalidation(); + + // After flush: face, shell, solid SubtreeGen should be propagated. + bool aFacePropagated = false; + for (BRepGraph_FaceIterator aFaceIt(myGraph); aFaceIt.More(); aFaceIt.Next()) + { + if (BRepGraph_TestTools::FaceUsesWire(myGraph, aFaceIt.CurrentId(), BRepGraph_WireId(0)) + && aFaceIt.Current().SubtreeGen > 0u) + { + aFacePropagated = true; + break; + } + } + EXPECT_TRUE(aFacePropagated); + EXPECT_GT(myGraph.Topo().Shells().Definition(BRepGraph_ShellId(0)).SubtreeGen, 0u); + EXPECT_GT(myGraph.Topo().Solids().Definition(BRepGraph_SolidId(0)).SubtreeGen, 0u); +} + +TEST_F(BRepGraph_DeferredInvalidationTest, + DeferredMode_OccurrenceMutation_PropagatesSubtreeGenToProduct) +{ + // Build an assembly: root product + child occurrence referencing it. + const BRepGraph_ProductId aPartId = BRepGraph_ProductId(0); + const BRepGraph_ProductId aAssemblyId = myGraph.Builder().AddAssemblyProduct(); + const BRepGraph_OccurrenceId anOccId = + myGraph.Builder().AddOccurrence(aAssemblyId, aPartId, TopLoc_Location()); + ASSERT_TRUE(anOccId.IsValid()); + + // Verify parent product starts clean. + EXPECT_EQ(myGraph.Topo().Products().Definition(aAssemblyId).SubtreeGen, 0u); + + // Mutate occurrence placement in deferred mode. + myGraph.Builder().BeginDeferredInvalidation(); + { + gp_Trsf aTrsf; + aTrsf.SetTranslation(gp_Vec(100.0, 0.0, 0.0)); + myGraph.Builder().MutOccurrence(anOccId)->Placement = TopLoc_Location(aTrsf); + } + + // During deferred mode: occurrence OwnGen incremented, but parent product NOT yet. + EXPECT_GT(myGraph.Topo().Occurrences().Definition(anOccId).OwnGen, 0u); + EXPECT_EQ(myGraph.Topo().Products().Definition(aAssemblyId).SubtreeGen, 0u); + + myGraph.Builder().EndDeferredInvalidation(); + + // After flush: parent assembly product must have SubtreeGen incremented exactly once. + EXPECT_EQ(myGraph.Topo().Products().Definition(aAssemblyId).SubtreeGen, 1u); + // Parent's OwnGen must remain 0 - its own data didn't change. + EXPECT_EQ(myGraph.Topo().Products().Definition(aAssemblyId).OwnGen, 0u); +} diff --git a/src/ModelingData/TKBRep/GTests/BRepGraph_DefsIterator_Test.cxx b/src/ModelingData/TKBRep/GTests/BRepGraph_DefsIterator_Test.cxx new file mode 100644 index 0000000000..c49800ce7b --- /dev/null +++ b/src/ModelingData/TKBRep/GTests/BRepGraph_DefsIterator_Test.cxx @@ -0,0 +1,242 @@ +// Copyright (c) 2026 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 + +namespace +{ +template +static int countIterator(IteratorT theIterator) +{ + int aCount = 0; + for (; theIterator.More(); theIterator.Next()) + { + ++aCount; + } + return aCount; +} + +static TopoDS_Edge makeEdgeWithInternalVertex() +{ + BRep_Builder aBuilder; + BRepBuilderAPI_MakeEdge aMakeEdge(gp_Pnt(0, 0, 0), gp_Pnt(10, 0, 0)); + TopoDS_Edge anEdge = aMakeEdge.Edge(); + + TopoDS_Vertex anInternalVertex; + aBuilder.MakeVertex(anInternalVertex, gp_Pnt(5, 0, 0), Precision::Confusion()); + aBuilder.Add(anEdge, anInternalVertex.Oriented(TopAbs_INTERNAL)); + return anEdge; +} + +static TopoDS_Face makeFaceWithDirectVertex() +{ + BRep_Builder aBuilder; + const occ::handle aPlane = new Geom_Plane(gp_Pln()); + TopoDS_Face aFace; + aBuilder.MakeFace(aFace, aPlane, Precision::Confusion()); + + BRepBuilderAPI_MakeEdge aMakeEdge(gp_Pnt(0, 0, 0), gp_Pnt(10, 0, 0)); + TopoDS_Wire aWire; + aBuilder.MakeWire(aWire); + aBuilder.Add(aWire, aMakeEdge.Edge()); + aBuilder.Add(aFace, aWire); + + TopoDS_Vertex aDirectVertex; + aBuilder.MakeVertex(aDirectVertex, gp_Pnt(5, 5, 0), Precision::Confusion()); + aBuilder.Add(aFace, aDirectVertex.Oriented(TopAbs_INTERNAL)); + return aFace; +} + +static TopoDS_Face wrapEdgeInFace(const TopoDS_Edge& theEdge) +{ + BRep_Builder aBuilder; + const occ::handle aPlane = new Geom_Plane(gp_Pln()); + TopoDS_Face aFace; + aBuilder.MakeFace(aFace, aPlane, Precision::Confusion()); + TopoDS_Wire aWire; + aBuilder.MakeWire(aWire); + aBuilder.Add(aWire, theEdge); + aBuilder.Add(aFace, aWire); + return aFace; +} +} // namespace + +class BRepGraph_DefsIteratorTest : public testing::Test +{ +protected: + void SetUp() override + { + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + myGraph.Build(aBoxMaker.Shape()); + } + + BRepGraph myGraph; +}; + +TEST_F(BRepGraph_DefsIteratorTest, BoxHierarchy_TraversesSingleLevelChildren) +{ + ASSERT_EQ(countIterator(BRepGraph_DefsShellOfSolid(myGraph, BRepGraph_SolidId(0))), 1); + ASSERT_EQ(countIterator(BRepGraph_DefsFaceOfShell(myGraph, BRepGraph_ShellId(0))), 6); + ASSERT_EQ(countIterator(BRepGraph_DefsWireOfFace(myGraph, BRepGraph_FaceId(0))), 1); + ASSERT_EQ(countIterator(BRepGraph_DefsEdgeOfWire(myGraph, BRepGraph_WireId(0))), 4); + ASSERT_EQ(countIterator(BRepGraph_DefsCoEdgeOfWire(myGraph, BRepGraph_WireId(0))), 4); + ASSERT_EQ(countIterator(BRepGraph_DefsVertexOfEdge(myGraph, BRepGraph_EdgeId(0))), 2); +} + +TEST_F(BRepGraph_DefsIteratorTest, EdgeOfWire_YieldsEdgeDefinitions) +{ + BRepGraph_DefsEdgeOfWire anIt(myGraph, BRepGraph_WireId(0)); + ASSERT_TRUE(anIt.More()); + EXPECT_TRUE(anIt.CurrentId().IsValid(myGraph.Topo().Edges().Nb())); + EXPECT_TRUE(anIt.Current().StartVertexRefId.IsValid()); + EXPECT_TRUE(anIt.Current().EndVertexRefId.IsValid()); +} + +TEST_F(BRepGraph_DefsIteratorTest, CoEdgeOfWire_YieldsCoEdgeDefinitions) +{ + BRepGraph_DefsCoEdgeOfWire anIt(myGraph, BRepGraph_WireId(0)); + ASSERT_TRUE(anIt.More()); + EXPECT_TRUE(anIt.CurrentId().IsValid(myGraph.Topo().CoEdges().Nb())); + EXPECT_TRUE(anIt.Current().EdgeDefId.IsValid(myGraph.Topo().Edges().Nb())); +} + +TEST(BRepGraph_DefsIteratorTestStandalone, VertexOfEdge_IncludesInternalVertices) +{ + BRepGraph aGraph; + aGraph.Build(wrapEdgeInFace(makeEdgeWithInternalVertex())); + + BRepGraph_EdgeId aEdgeWithInternal; + for (BRepGraph_EdgeIterator anEdgeIt(aGraph); anEdgeIt.More(); anEdgeIt.Next()) + { + if (anEdgeIt.Current().InternalVertexRefIds.Length() == 1) + { + aEdgeWithInternal = anEdgeIt.CurrentId(); + break; + } + } + + ASSERT_TRUE(aEdgeWithInternal.IsValid()); + + bool aFoundSplitVertex = false; + int aCount = 0; + for (BRepGraph_DefsVertexOfEdge anIt(aGraph, aEdgeWithInternal); anIt.More(); anIt.Next()) + { + ++aCount; + const gp_Pnt& aPoint = anIt.Current().Point; + if (std::abs(aPoint.X() - 5.0) <= Precision::Confusion()) + { + aFoundSplitVertex = true; + } + } + + EXPECT_EQ(aCount, 3); + EXPECT_TRUE(aFoundSplitVertex); +} + +TEST(BRepGraph_DefsIteratorTestStandalone, VertexOfFace_EnumeratesDirectVertices) +{ + BRepGraph aGraph; + aGraph.Build(makeFaceWithDirectVertex()); + + ASSERT_EQ(countIterator(BRepGraph_DefsWireOfFace(aGraph, BRepGraph_FaceId(0))), 1); + ASSERT_EQ(countIterator(BRepGraph_DefsVertexOfFace(aGraph, BRepGraph_FaceId(0))), 1); + + BRepGraph_DefsVertexOfFace anIt(aGraph, BRepGraph_FaceId(0)); + ASSERT_TRUE(anIt.More()); + EXPECT_NEAR(anIt.Current().Point.X(), 5.0, Precision::Confusion()); + EXPECT_NEAR(anIt.Current().Point.Y(), 5.0, Precision::Confusion()); +} + +TEST_F(BRepGraph_DefsIteratorTest, ChildOfCompound_EnumeratesHeterogeneousChildren) +{ + const BRepGraph_VertexId aLooseVertex = myGraph.Builder().AddVertex(gp_Pnt(1.0, 2.0, 3.0), 0.01); + ASSERT_TRUE(aLooseVertex.IsValid()); + + NCollection_Vector aChildren; + aChildren.Append(BRepGraph_SolidId(0)); + aChildren.Append(aLooseVertex); + const BRepGraph_CompoundId aCompound = myGraph.Builder().AddCompound(aChildren); + + ASSERT_TRUE(aCompound.IsValid()); + BRepGraph_DefsChildOfCompound anIt(myGraph, aCompound); + ASSERT_TRUE(anIt.More()); + EXPECT_EQ(anIt.CurrentId().NodeKind, BRepGraph_NodeId::Kind::Solid); + anIt.Next(); + ASSERT_TRUE(anIt.More()); + EXPECT_EQ(anIt.CurrentId().NodeKind, BRepGraph_NodeId::Kind::Vertex); +} + +TEST_F(BRepGraph_DefsIteratorTest, SolidOfCompSolid_EnumeratesDirectSolids) +{ + const BRepGraph_SolidId aSolidA = myGraph.Builder().AddSolid(); + const BRepGraph_SolidId aSolidB = myGraph.Builder().AddSolid(); + ASSERT_TRUE(aSolidA.IsValid()); + ASSERT_TRUE(aSolidB.IsValid()); + + NCollection_Vector aSolids; + aSolids.Append(aSolidA); + aSolids.Append(aSolidB); + const BRepGraph_CompSolidId aCompSolid = myGraph.Builder().AddCompSolid(aSolids); + + ASSERT_TRUE(aCompSolid.IsValid()); + EXPECT_EQ(countIterator(BRepGraph_DefsSolidOfCompSolid(myGraph, aCompSolid)), 2); +} + +TEST_F(BRepGraph_DefsIteratorTest, OccurrenceOfProduct_EnumeratesDirectOccurrences) +{ + const BRepGraph_ProductId aPart = myGraph.Builder().AddProduct(BRepGraph_SolidId(0)); + const BRepGraph_ProductId anAssembly = myGraph.Builder().AddAssemblyProduct(); + ASSERT_TRUE(aPart.IsValid()); + ASSERT_TRUE(anAssembly.IsValid()); + + EXPECT_TRUE(myGraph.Builder().AddOccurrence(anAssembly, aPart, TopLoc_Location()).IsValid()); + EXPECT_TRUE(myGraph.Builder().AddOccurrence(anAssembly, aPart, TopLoc_Location()).IsValid()); + + EXPECT_EQ(countIterator(BRepGraph_DefsOccurrenceOfProduct(myGraph, anAssembly)), 2); +} + +TEST_F(BRepGraph_DefsIteratorTest, RemovedWireRef_IsSkipped) +{ + const NCollection_Vector& aWireRefs = + myGraph.Refs().Wires().IdsOf(BRepGraph_FaceId(0)); + ASSERT_EQ(aWireRefs.Length(), 1); + + { + BRepGraph_MutGuard aWireRef = + myGraph.Builder().MutWireRef(aWireRefs.Value(0)); + aWireRef->IsRemoved = true; + } + + EXPECT_EQ(countIterator(BRepGraph_DefsWireOfFace(myGraph, BRepGraph_FaceId(0))), 0); +} \ No newline at end of file diff --git a/src/ModelingData/TKBRep/GTests/BRepGraph_EdgeCases_Test.cxx b/src/ModelingData/TKBRep/GTests/BRepGraph_EdgeCases_Test.cxx new file mode 100644 index 0000000000..82c11f4a5e --- /dev/null +++ b/src/ModelingData/TKBRep/GTests/BRepGraph_EdgeCases_Test.cxx @@ -0,0 +1,260 @@ +// Copyright (c) 2026 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 + +// ============================================================ +// Null / Empty / Invalid input tests +// ============================================================ + +TEST(BRepGraph_EdgeCasesTest, Build_NullShape_IsDoneFalse) +{ + BRepGraph aGraph; + TopoDS_Shape aNullShape; + aGraph.Build(aNullShape); + EXPECT_FALSE(aGraph.IsDone()); +} + +TEST(BRepGraph_EdgeCasesTest, Build_EmptyCompound_IsDoneZeroCounts) +{ + BRepGraph aGraph; + BRep_Builder aBuilder; + TopoDS_Compound aCompound; + aBuilder.MakeCompound(aCompound); + aGraph.Build(aCompound); + + // Whether IsDone is true or false for an empty compound is implementation-defined; + // the key invariant is that all definition counts are zero. + EXPECT_EQ(aGraph.Topo().Solids().Nb(), 0); + EXPECT_EQ(aGraph.Topo().Shells().Nb(), 0); + EXPECT_EQ(aGraph.Topo().Faces().Nb(), 0); + EXPECT_EQ(aGraph.Topo().Wires().Nb(), 0); + EXPECT_EQ(aGraph.Topo().Edges().Nb(), 0); + EXPECT_EQ(aGraph.Topo().Vertices().Nb(), 0); + EXPECT_EQ(aGraph.Topo().Faces().Nb(), 0); + EXPECT_EQ(aGraph.Topo().Edges().Nb(), 0); +} + +TEST(BRepGraph_EdgeCasesTest, Shape_InvalidNodeId_ReturnsNull) +{ + BRepGraph aGraph; + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + aGraph.Build(aBoxMaker.Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + const BRepGraph_NodeId anInvalidId; // default: Index = -1 + const TopoDS_Shape aShape = aGraph.Shapes().Shape(anInvalidId); + EXPECT_TRUE(aShape.IsNull()); +} + +TEST(BRepGraph_EdgeCasesTest, ReconstructShape_InvalidNodeId_ReturnsNull) +{ + BRepGraph aGraph; + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + aGraph.Build(aBoxMaker.Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + const BRepGraph_NodeId anInvalidId; + const TopoDS_Shape aShape = aGraph.Shapes().Reconstruct(anInvalidId); + EXPECT_TRUE(aShape.IsNull()); +} + +TEST(BRepGraph_EdgeCasesTest, TopoEntity_InvalidNodeId_ReturnsNull) +{ + BRepGraph aGraph; + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + aGraph.Build(aBoxMaker.Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + const BRepGraph_NodeId anInvalidId; + const BRepGraphInc::BaseDef* aDef = aGraph.Topo().Gen().TopoEntity(anInvalidId); + EXPECT_EQ(aDef, nullptr); +} + +TEST(BRepGraph_EdgeCasesTest, NbNodes_BeforeBuild_ReturnsZero) +{ + BRepGraph aGraph; + EXPECT_EQ(aGraph.Topo().Gen().NbNodes(), 0u); +} + +// ============================================================ +// Rebuild behavior tests +// ============================================================ + +TEST(BRepGraph_EdgeCasesTest, Build_TwiceOnSameGraph_GenerationIncrements) +{ + BRepGraph aGraph; + + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape aBox = aBoxMaker.Shape(); + + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + const uint32_t aFirstGen = aGraph.UIDs().Generation(); + + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + const uint32_t aSecondGen = aGraph.UIDs().Generation(); + + EXPECT_GT(aSecondGen, aFirstGen); +} + +TEST(BRepGraph_EdgeCasesTest, Build_TwiceOnSameGraph_OldUIDsInvalidated) +{ + BRepGraph aGraph; + + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape aBox = aBoxMaker.Shape(); + + // First build: collect total UID counter used. + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + const uint32_t aFirstGen = aGraph.UIDs().Generation(); + + // Record total node count from first build. + const size_t aTotalFirstBuild = aGraph.Topo().Gen().NbNodes(); + ASSERT_GT(aTotalFirstBuild, 0u); + + // Second build. + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + const uint32_t aSecondGen = aGraph.UIDs().Generation(); + EXPECT_NE(aFirstGen, aSecondGen); + + // After rebuild, UIDs on the new nodes carry the new generation. + const BRepGraph_NodeId aSolidId(BRepGraph_NodeId::Kind::Solid, 0); + const BRepGraph_UID aUID = aGraph.UIDs().Of(aSolidId); + EXPECT_TRUE(aUID.IsValid()); + EXPECT_EQ(aUID.Generation(), aSecondGen); +} + +TEST(BRepGraph_EdgeCasesTest, Build_TwiceOnSameGraph_CountsResetCorrectly) +{ + BRepGraph aGraph; + + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape aBox = aBoxMaker.Shape(); + + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + + const int aSolids1 = aGraph.Topo().Solids().Nb(); + const int aShells1 = aGraph.Topo().Shells().Nb(); + const int aFaces1 = aGraph.Topo().Faces().Nb(); + const int aWires1 = aGraph.Topo().Wires().Nb(); + const int aEdges1 = aGraph.Topo().Edges().Nb(); + const int aVerts1 = aGraph.Topo().Vertices().Nb(); + const int aSurfs1 = aGraph.Topo().Faces().Nb(); + const int aCurves1 = aGraph.Topo().Edges().Nb(); + + // Rebuild with same shape. + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + + EXPECT_EQ(aGraph.Topo().Solids().Nb(), aSolids1); + EXPECT_EQ(aGraph.Topo().Shells().Nb(), aShells1); + EXPECT_EQ(aGraph.Topo().Faces().Nb(), aFaces1); + EXPECT_EQ(aGraph.Topo().Wires().Nb(), aWires1); + EXPECT_EQ(aGraph.Topo().Edges().Nb(), aEdges1); + EXPECT_EQ(aGraph.Topo().Vertices().Nb(), aVerts1); + EXPECT_EQ(aGraph.Topo().Faces().Nb(), aSurfs1); + EXPECT_EQ(aGraph.Topo().Edges().Nb(), aCurves1); +} + +// ============================================================ +// UID edge cases +// ============================================================ + +TEST(BRepGraph_EdgeCasesTest, UID_AlwaysEnabled_AfterBuild) +{ + BRepGraph aGraph; + + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + aGraph.Build(aBoxMaker.Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + const BRepGraph_NodeId aSolidId(BRepGraph_NodeId::Kind::Solid, 0); + const BRepGraph_UID aUID = aGraph.UIDs().Of(aSolidId); + EXPECT_TRUE(aUID.IsValid()); +} + +// ============================================================ +// Parallel build equivalence tests +// ============================================================ + +TEST(BRepGraph_EdgeCasesTest, ParallelBuild_Sphere_SameAsSequential) +{ + BRepPrimAPI_MakeSphere aSphereMaker(50.0); + const TopoDS_Shape aSphere = aSphereMaker.Shape(); + + BRepGraph aSeqGraph; + aSeqGraph.Build(aSphere, false); + ASSERT_TRUE(aSeqGraph.IsDone()); + + BRepGraph aParGraph; + aParGraph.Build(aSphere, true); + ASSERT_TRUE(aParGraph.IsDone()); + + EXPECT_EQ(aParGraph.Topo().Solids().Nb(), aSeqGraph.Topo().Solids().Nb()); + EXPECT_EQ(aParGraph.Topo().Shells().Nb(), aSeqGraph.Topo().Shells().Nb()); + EXPECT_EQ(aParGraph.Topo().Faces().Nb(), aSeqGraph.Topo().Faces().Nb()); + EXPECT_EQ(aParGraph.Topo().Wires().Nb(), aSeqGraph.Topo().Wires().Nb()); + EXPECT_EQ(aParGraph.Topo().Edges().Nb(), aSeqGraph.Topo().Edges().Nb()); + EXPECT_EQ(aParGraph.Topo().Vertices().Nb(), aSeqGraph.Topo().Vertices().Nb()); + EXPECT_EQ(aParGraph.Topo().Faces().Nb(), aSeqGraph.Topo().Faces().Nb()); + EXPECT_EQ(aParGraph.Topo().Edges().Nb(), aSeqGraph.Topo().Edges().Nb()); + EXPECT_EQ(aParGraph.Topo().Gen().NbNodes(), aSeqGraph.Topo().Gen().NbNodes()); +} + +TEST(BRepGraph_EdgeCasesTest, ParallelBuild_Compound_SameAsSequential) +{ + // Build a compound of three boxes at different sizes. + BRep_Builder aBuilder; + TopoDS_Compound aCompound; + aBuilder.MakeCompound(aCompound); + + BRepPrimAPI_MakeBox aBox1(10.0, 10.0, 10.0); + BRepPrimAPI_MakeBox aBox2(20.0, 30.0, 40.0); + BRepPrimAPI_MakeBox aBox3(5.0, 15.0, 25.0); + aBuilder.Add(aCompound, aBox1.Shape()); + aBuilder.Add(aCompound, aBox2.Shape()); + aBuilder.Add(aCompound, aBox3.Shape()); + + BRepGraph aSeqGraph; + aSeqGraph.Build(aCompound, false); + ASSERT_TRUE(aSeqGraph.IsDone()); + + BRepGraph aParGraph; + aParGraph.Build(aCompound, true); + ASSERT_TRUE(aParGraph.IsDone()); + + EXPECT_EQ(aParGraph.Topo().Solids().Nb(), aSeqGraph.Topo().Solids().Nb()); + EXPECT_EQ(aParGraph.Topo().Shells().Nb(), aSeqGraph.Topo().Shells().Nb()); + EXPECT_EQ(aParGraph.Topo().Faces().Nb(), aSeqGraph.Topo().Faces().Nb()); + EXPECT_EQ(aParGraph.Topo().Wires().Nb(), aSeqGraph.Topo().Wires().Nb()); + EXPECT_EQ(aParGraph.Topo().Edges().Nb(), aSeqGraph.Topo().Edges().Nb()); + EXPECT_EQ(aParGraph.Topo().Vertices().Nb(), aSeqGraph.Topo().Vertices().Nb()); + EXPECT_EQ(aParGraph.Topo().Faces().Nb(), aSeqGraph.Topo().Faces().Nb()); + EXPECT_EQ(aParGraph.Topo().Edges().Nb(), aSeqGraph.Topo().Edges().Nb()); + EXPECT_EQ(aParGraph.Topo().Gen().NbNodes(), aSeqGraph.Topo().Gen().NbNodes()); +} diff --git a/src/ModelingData/TKBRep/GTests/BRepGraph_EventBus_Test.cxx b/src/ModelingData/TKBRep/GTests/BRepGraph_EventBus_Test.cxx new file mode 100644 index 0000000000..c33d058327 --- /dev/null +++ b/src/ModelingData/TKBRep/GTests/BRepGraph_EventBus_Test.cxx @@ -0,0 +1,515 @@ +// Copyright (c) 2026 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 + +// Test layer that records modification events. +class BRepGraph_ModTrackingLayer : public BRepGraph_Layer +{ +public: + BRepGraph_ModTrackingLayer( + const TCollection_AsciiString& theName, + const int theSubscribedKinds, + const Standard_GUID& theId = Standard_GUID("2f9b6a5c-1f2d-4a88-9c1c-7a0c16a10004")) + : myName(theName), + mySubscribedKinds(theSubscribedKinds), + myId(theId) + { + } + + const Standard_GUID& ID() const override { return myId; } + + const TCollection_AsciiString& Name() const override { return myName; } + + int SubscribedKinds() const override { return mySubscribedKinds; } + + void OnNodeModified(const BRepGraph_NodeId theNode) noexcept override + { + myImmediateEvents.Append(theNode); + } + + void OnNodesModified(const NCollection_Vector& theNodes) noexcept override + { + myBatchEvents = theNodes; + ++myBatchCallCount; + } + + void OnNodeRemoved(const BRepGraph_NodeId theNode, + const BRepGraph_NodeId theReplacement) noexcept override + { + myLastRemovedNode = theNode; + myLastReplacement = theReplacement; + ++myRemoveCallCount; + } + + void OnCompact( + const NCollection_DataMap& theRemapMap) noexcept override + { + myLastRemapMap.Clear(); + for (const auto& [aOldNode, aNewNode] : theRemapMap.Items()) + { + myLastRemapMap.Bind(aOldNode, aNewNode); + } + ++myCompactCallCount; + } + + void InvalidateAll() noexcept override {} + + void Clear() noexcept override + { + myImmediateEvents.Clear(); + myBatchEvents.Clear(); + myBatchCallCount = 0; + myRemoveCallCount = 0; + myCompactCallCount = 0; + myLastRemovedNode = BRepGraph_NodeId(); + myLastReplacement = BRepGraph_NodeId(); + myLastRemapMap.Clear(); + } + + bool HasImmediateEventFor(BRepGraph_NodeId theNode) const + { + for (const BRepGraph_NodeId& anEvent : myImmediateEvents) + if (anEvent == theNode) + return true; + return false; + } + + bool HasBatchEventFor(BRepGraph_NodeId theNode) const + { + for (const BRepGraph_NodeId& anEvent : myBatchEvents) + if (anEvent == theNode) + return true; + return false; + } + + int CountImmediateEventsOfKind(BRepGraph_NodeId::Kind theKind) const + { + int aCount = 0; + for (const BRepGraph_NodeId& anEvent : myImmediateEvents) + if (anEvent.NodeKind == theKind) + ++aCount; + return aCount; + } + + NCollection_Vector myImmediateEvents; + NCollection_Vector myBatchEvents; + int myBatchCallCount = 0; + int myRemoveCallCount = 0; + int myCompactCallCount = 0; + BRepGraph_NodeId myLastRemovedNode; + BRepGraph_NodeId myLastReplacement; + NCollection_DataMap myLastRemapMap; + + DEFINE_STANDARD_RTTIEXT(BRepGraph_ModTrackingLayer, BRepGraph_Layer) + +private: + TCollection_AsciiString myName; + int mySubscribedKinds; + Standard_GUID myId; +}; + +IMPLEMENT_STANDARD_RTTIEXT(BRepGraph_ModTrackingLayer, BRepGraph_Layer) + +// Minimal layer with default SubscribedKinds() behavior from base class. +class BRepGraph_DefaultLayer : public BRepGraph_Layer +{ +public: + const Standard_GUID& ID() const override + { + static const Standard_GUID THE_ID("2f9b6a5c-1f2d-4a88-9c1c-7a0c16a10007"); + return THE_ID; + } + + const TCollection_AsciiString& Name() const override + { + static const TCollection_AsciiString THE_NAME("DefaultLayer"); + return THE_NAME; + } + + void OnNodeRemoved(const BRepGraph_NodeId, const BRepGraph_NodeId) noexcept override {} + + void OnCompact(const NCollection_DataMap&) noexcept override + { + } + + void InvalidateAll() noexcept override {} + + void Clear() noexcept override {} + + DEFINE_STANDARD_RTTIEXT(BRepGraph_DefaultLayer, BRepGraph_Layer) +}; + +IMPLEMENT_STANDARD_RTTIEXT(BRepGraph_DefaultLayer, BRepGraph_Layer) + +class BRepGraph_EventBusTest : public testing::Test +{ +protected: + void SetUp() override + { + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + myGraph.Build(aBox); + ASSERT_TRUE(myGraph.IsDone()); + } + + BRepGraph myGraph; +}; + +TEST_F(BRepGraph_EventBusTest, ZeroCost_NoSubscribers) +{ + // Mutate edge without any subscribing layer - verify no crash. + { + BRepGraph_MutGuard aMut = myGraph.Builder().MutEdge(BRepGraph_EdgeId(0)); + aMut->Tolerance = 0.5; + } + EXPECT_GT(myGraph.Topo().Edges().Definition(BRepGraph_EdgeId(0)).OwnGen, 0u); +} + +TEST_F(BRepGraph_EventBusTest, ImmediateMode_SingleEdge) +{ + const int aAllKinds = BRepGraph_Layer::KindBit(BRepGraph_NodeId::Kind::Edge) + | BRepGraph_Layer::KindBit(BRepGraph_NodeId::Kind::Wire) + | BRepGraph_Layer::KindBit(BRepGraph_NodeId::Kind::Face) + | BRepGraph_Layer::KindBit(BRepGraph_NodeId::Kind::Shell) + | BRepGraph_Layer::KindBit(BRepGraph_NodeId::Kind::Solid); + occ::handle aLayer = + new BRepGraph_ModTrackingLayer("Tracker", aAllKinds); + myGraph.LayerRegistry().RegisterLayer(aLayer); + + { + BRepGraph_MutGuard aMut = myGraph.Builder().MutEdge(BRepGraph_EdgeId(0)); + aMut->Tolerance = 0.5; + } + + // Edge(0) should have an immediate event. + EXPECT_TRUE(aLayer->HasImmediateEventFor(BRepGraph_EdgeId(0))); + // At least one event total (edge + propagated parents). + EXPECT_GT(aLayer->myImmediateEvents.Length(), 0); +} + +TEST_F(BRepGraph_EventBusTest, ImmediateMode_UpwardPropagation) +{ + const int aAllKinds = BRepGraph_Layer::KindBit(BRepGraph_NodeId::Kind::Edge) + | BRepGraph_Layer::KindBit(BRepGraph_NodeId::Kind::Wire) + | BRepGraph_Layer::KindBit(BRepGraph_NodeId::Kind::Face) + | BRepGraph_Layer::KindBit(BRepGraph_NodeId::Kind::Shell) + | BRepGraph_Layer::KindBit(BRepGraph_NodeId::Kind::Solid); + occ::handle aLayer = + new BRepGraph_ModTrackingLayer("Tracker", aAllKinds); + myGraph.LayerRegistry().RegisterLayer(aLayer); + + { + BRepGraph_MutGuard aMut = myGraph.Builder().MutEdge(BRepGraph_EdgeId(0)); + aMut->Tolerance = 0.5; + } + + // Only directly mutated node gets immediate dispatch. + // Parents get SubtreeGen incremented but NO dispatch (mutex-free propagation). + EXPECT_GT(aLayer->CountImmediateEventsOfKind(BRepGraph_NodeId::Kind::Edge), 0); + EXPECT_EQ(aLayer->CountImmediateEventsOfKind(BRepGraph_NodeId::Kind::Wire), 0); + EXPECT_EQ(aLayer->CountImmediateEventsOfKind(BRepGraph_NodeId::Kind::Face), 0); + EXPECT_EQ(aLayer->CountImmediateEventsOfKind(BRepGraph_NodeId::Kind::Shell), 0); + EXPECT_EQ(aLayer->CountImmediateEventsOfKind(BRepGraph_NodeId::Kind::Solid), 0); + // Verify SubtreeGen was propagated upward despite no dispatch. + EXPECT_GT(myGraph.Topo().Wires().Definition(BRepGraph_WireId(0)).SubtreeGen, 0u); + EXPECT_GT(myGraph.Topo().Faces().Definition(BRepGraph_FaceId(0)).SubtreeGen, 0u); +} + +TEST_F(BRepGraph_EventBusTest, ImmediateMode_KindFilter) +{ + // Subscribe only to Face. + occ::handle aLayer = + new BRepGraph_ModTrackingLayer("FaceOnly", + BRepGraph_Layer::KindBit(BRepGraph_NodeId::Kind::Face)); + myGraph.LayerRegistry().RegisterLayer(aLayer); + + { + BRepGraph_MutGuard aMut = myGraph.Builder().MutEdge(BRepGraph_EdgeId(0)); + aMut->Tolerance = 0.5; + } + + // No face dispatch from upward propagation (mutex-free SubtreeGen only). + // Edge is directly mutated but Face-only subscription filters it out. + EXPECT_EQ(aLayer->CountImmediateEventsOfKind(BRepGraph_NodeId::Kind::Edge), 0); + EXPECT_EQ(aLayer->CountImmediateEventsOfKind(BRepGraph_NodeId::Kind::Face), 0); + // But SubtreeGen was propagated. + EXPECT_GT(myGraph.Topo().Faces().Definition(BRepGraph_FaceId(0)).SubtreeGen, 0u); +} + +TEST_F(BRepGraph_EventBusTest, DeferredMode_BatchDispatch) +{ + const int aAllKinds = BRepGraph_Layer::KindBit(BRepGraph_NodeId::Kind::Edge) + | BRepGraph_Layer::KindBit(BRepGraph_NodeId::Kind::Wire) + | BRepGraph_Layer::KindBit(BRepGraph_NodeId::Kind::Face) + | BRepGraph_Layer::KindBit(BRepGraph_NodeId::Kind::Shell) + | BRepGraph_Layer::KindBit(BRepGraph_NodeId::Kind::Solid); + occ::handle aLayer = + new BRepGraph_ModTrackingLayer("Tracker", aAllKinds); + myGraph.LayerRegistry().RegisterLayer(aLayer); + + myGraph.Builder().BeginDeferredInvalidation(); + myGraph.Builder().MutEdge(BRepGraph_EdgeId(0))->Tolerance = 0.5; + myGraph.Builder().MutEdge(BRepGraph_EdgeId(1))->Tolerance = 0.6; + myGraph.Builder().MutEdge(BRepGraph_EdgeId(2))->Tolerance = 0.7; + myGraph.Builder().EndDeferredInvalidation(); + + // OnNodesModified called exactly once. + EXPECT_EQ(aLayer->myBatchCallCount, 1); + // Batch contains at least the 3 edges + propagated parents. + EXPECT_GE(aLayer->myBatchEvents.Length(), 3); + EXPECT_TRUE(aLayer->HasBatchEventFor(BRepGraph_EdgeId(0))); + EXPECT_TRUE(aLayer->HasBatchEventFor(BRepGraph_EdgeId(1))); + EXPECT_TRUE(aLayer->HasBatchEventFor(BRepGraph_EdgeId(2))); +} + +TEST_F(BRepGraph_EventBusTest, DeferredMode_NoImmediateDispatch) +{ + const int aEdgeBit = BRepGraph_Layer::KindBit(BRepGraph_NodeId::Kind::Edge); + occ::handle aLayer = + new BRepGraph_ModTrackingLayer("Tracker", aEdgeBit); + myGraph.LayerRegistry().RegisterLayer(aLayer); + + myGraph.Builder().BeginDeferredInvalidation(); + myGraph.Builder().MutEdge(BRepGraph_EdgeId(0))->Tolerance = 0.5; + + // During deferred mode: OnNodeModified must NOT be called. + EXPECT_EQ(aLayer->myImmediateEvents.Length(), 0); + + myGraph.Builder().EndDeferredInvalidation(); + + // Immediate events still empty - only batch was dispatched. + EXPECT_EQ(aLayer->myImmediateEvents.Length(), 0); + EXPECT_EQ(aLayer->myBatchCallCount, 1); +} + +TEST_F(BRepGraph_EventBusTest, UnregisterLayer_FlagUpdate) +{ + const int aEdgeBit = BRepGraph_Layer::KindBit(BRepGraph_NodeId::Kind::Edge); + occ::handle aLayer = + new BRepGraph_ModTrackingLayer("Tracker", aEdgeBit); + myGraph.LayerRegistry().RegisterLayer(aLayer); + + // Mutate - should dispatch. + { + BRepGraph_MutGuard aMut = myGraph.Builder().MutEdge(BRepGraph_EdgeId(0)); + aMut->Tolerance = 0.5; + } + EXPECT_GT(aLayer->myImmediateEvents.Length(), 0); + + // Unregister and clear. + myGraph.LayerRegistry().UnregisterLayer(aLayer->ID()); + aLayer->Clear(); + + // Mutate again - should NOT dispatch (layer unregistered). + { + BRepGraph_MutGuard aMut = myGraph.Builder().MutEdge(BRepGraph_EdgeId(1)); + aMut->Tolerance = 0.6; + } + EXPECT_EQ(aLayer->myImmediateEvents.Length(), 0); +} + +TEST_F(BRepGraph_EventBusTest, FindLayer_ByGuid_ReturnsRegisteredLayer) +{ + const int aEdgeBit = BRepGraph_Layer::KindBit(BRepGraph_NodeId::Kind::Edge); + occ::handle aLayer = + new BRepGraph_ModTrackingLayer("Tracker", aEdgeBit); + + myGraph.LayerRegistry().RegisterLayer(aLayer); + + EXPECT_EQ(myGraph.LayerRegistry().FindLayer(aLayer->ID()), aLayer); + EXPECT_GE(myGraph.LayerRegistry().FindSlot(aLayer->ID()), 0); +} + +TEST_F(BRepGraph_EventBusTest, OnNodeRemoved_DispatchesReplacement) +{ + occ::handle aLayer = new BRepGraph_ModTrackingLayer("Tracker", 0); + myGraph.LayerRegistry().RegisterLayer(aLayer); + + const BRepGraph_NodeId anOldEdge = BRepGraph_EdgeId(0); + const BRepGraph_NodeId aNewEdge = BRepGraph_EdgeId(1); + myGraph.Builder().RemoveNode(anOldEdge, aNewEdge); + + EXPECT_EQ(aLayer->myRemoveCallCount, 1); + EXPECT_EQ(aLayer->myLastRemovedNode, anOldEdge); + EXPECT_EQ(aLayer->myLastReplacement, aNewEdge); +} + +TEST_F(BRepGraph_EventBusTest, OnNodeRemoved_DispatchesInvalidReplacementForPureDeletion) +{ + occ::handle aLayer = new BRepGraph_ModTrackingLayer("Tracker", 0); + myGraph.LayerRegistry().RegisterLayer(aLayer); + + const BRepGraph_NodeId aFaceId = BRepGraph_FaceId(0); + myGraph.Builder().RemoveNode(aFaceId); + + EXPECT_EQ(aLayer->myRemoveCallCount, 1); + EXPECT_EQ(aLayer->myLastRemovedNode, aFaceId); + EXPECT_FALSE(aLayer->myLastReplacement.IsValid()); +} + +TEST_F(BRepGraph_EventBusTest, OnCompact_DispatchesRemapToRegisteredLayers) +{ + occ::handle aLayer = new BRepGraph_ModTrackingLayer("Tracker", 0); + myGraph.LayerRegistry().RegisterLayer(aLayer); + + myGraph.Builder().RemoveNode(BRepGraph_FaceId(0)); + const int aNbFacesBefore = myGraph.Topo().Faces().Nb(); + + const BRepGraph_Compact::Result aResult = BRepGraph_Compact::Perform(myGraph); + (void)aResult; + + EXPECT_EQ(aLayer->myCompactCallCount, 1); + EXPECT_EQ(myGraph.Topo().Faces().Nb(), aNbFacesBefore - 1); + + const BRepGraph_NodeId* aNewFaceId = aLayer->myLastRemapMap.Seek(BRepGraph_FaceId(1)); + ASSERT_NE(aNewFaceId, nullptr); + EXPECT_EQ(aNewFaceId->NodeKind, BRepGraph_NodeId::Kind::Face); + EXPECT_EQ(aNewFaceId->Index, 0); + EXPECT_EQ(aLayer->myLastRemapMap.Seek(BRepGraph_FaceId(0)), nullptr); +} + +TEST_F(BRepGraph_EventBusTest, MultipleSubscribers) +{ + occ::handle aEdgeLayer = + new BRepGraph_ModTrackingLayer("EdgeTracker", + BRepGraph_Layer::KindBit(BRepGraph_NodeId::Kind::Edge), + Standard_GUID("2f9b6a5c-1f2d-4a88-9c1c-7a0c16a10005")); + occ::handle aFaceLayer = + new BRepGraph_ModTrackingLayer("FaceTracker", + BRepGraph_Layer::KindBit(BRepGraph_NodeId::Kind::Face), + Standard_GUID("2f9b6a5c-1f2d-4a88-9c1c-7a0c16a10006")); + myGraph.LayerRegistry().RegisterLayer(aEdgeLayer); + myGraph.LayerRegistry().RegisterLayer(aFaceLayer); + + { + BRepGraph_MutGuard aMut = myGraph.Builder().MutEdge(BRepGraph_EdgeId(0)); + aMut->Tolerance = 0.5; + } + + // Edge layer gets edge events (directly mutated), no face events. + EXPECT_GT(aEdgeLayer->CountImmediateEventsOfKind(BRepGraph_NodeId::Kind::Edge), 0); + EXPECT_EQ(aEdgeLayer->CountImmediateEventsOfKind(BRepGraph_NodeId::Kind::Face), 0); + + // Face layer gets NO events - parents don't get immediate dispatch. + EXPECT_EQ(aFaceLayer->CountImmediateEventsOfKind(BRepGraph_NodeId::Kind::Edge), 0); + EXPECT_EQ(aFaceLayer->CountImmediateEventsOfKind(BRepGraph_NodeId::Kind::Face), 0); +} + +TEST_F(BRepGraph_EventBusTest, DefaultSubscribedKinds_Zero) +{ + occ::handle aDefaultLayer = new BRepGraph_DefaultLayer; + myGraph.LayerRegistry().RegisterLayer(aDefaultLayer); + + // SubscribedKinds() == 0 in BRepGraph_Layer base default implementation. + EXPECT_EQ(aDefaultLayer->SubscribedKinds(), 0); + + // Mutate - a layer with default SubscribedKinds() should not receive modification events. + // This just verifies the default no-subscription path remains a no-op. + { + BRepGraph_MutGuard aMut = myGraph.Builder().MutEdge(BRepGraph_EdgeId(0)); + aMut->Tolerance = 0.5; + } + EXPECT_GT(myGraph.Topo().Edges().Definition(BRepGraph_EdgeId(0)).OwnGen, 0u); +} + +TEST_F(BRepGraph_EventBusTest, DeferredScope_DispatchesOnDestruction) +{ + const int aEdgeBit = BRepGraph_Layer::KindBit(BRepGraph_NodeId::Kind::Edge); + occ::handle aLayer = + new BRepGraph_ModTrackingLayer("Tracker", aEdgeBit); + myGraph.LayerRegistry().RegisterLayer(aLayer); + + { + BRepGraph_DeferredScope aScope(myGraph); + myGraph.Builder().MutEdge(BRepGraph_EdgeId(0))->Tolerance = 0.5; + + // During guard scope: no batch dispatch yet. + EXPECT_EQ(aLayer->myBatchCallCount, 0); + } + + // After guard destruction: batch dispatched. + EXPECT_EQ(aLayer->myBatchCallCount, 1); + EXPECT_TRUE(aLayer->HasBatchEventFor(BRepGraph_EdgeId(0))); +} + +TEST_F(BRepGraph_EventBusTest, DeferredMode_NoModifications_NoDispatch) +{ + const int aEdgeBit = BRepGraph_Layer::KindBit(BRepGraph_NodeId::Kind::Edge); + occ::handle aLayer = + new BRepGraph_ModTrackingLayer("Tracker", aEdgeBit); + myGraph.LayerRegistry().RegisterLayer(aLayer); + + myGraph.Builder().BeginDeferredInvalidation(); + // No mutations. + myGraph.Builder().EndDeferredInvalidation(); + + EXPECT_EQ(aLayer->myBatchCallCount, 0); + EXPECT_EQ(aLayer->myBatchEvents.Length(), 0); +} + +TEST_F(BRepGraph_EventBusTest, KindBit_Helpers) +{ + // Each kind produces a distinct single-bit value derived from the Kind enum. + using Kind = BRepGraph_NodeId::Kind; + EXPECT_EQ(BRepGraph_Layer::KindBit(Kind::Solid), 1 << static_cast(Kind::Solid)); + EXPECT_EQ(BRepGraph_Layer::KindBit(Kind::Shell), 1 << static_cast(Kind::Shell)); + EXPECT_EQ(BRepGraph_Layer::KindBit(Kind::Face), 1 << static_cast(Kind::Face)); + EXPECT_EQ(BRepGraph_Layer::KindBit(Kind::Wire), 1 << static_cast(Kind::Wire)); + EXPECT_EQ(BRepGraph_Layer::KindBit(Kind::Edge), 1 << static_cast(Kind::Edge)); + EXPECT_EQ(BRepGraph_Layer::KindBit(Kind::Vertex), 1 << static_cast(Kind::Vertex)); + EXPECT_EQ(BRepGraph_Layer::KindBit(Kind::Compound), 1 << static_cast(Kind::Compound)); + EXPECT_EQ(BRepGraph_Layer::KindBit(Kind::CompSolid), 1 << static_cast(Kind::CompSolid)); + + // All kind bits are distinct (no collisions). + const int aAll = BRepGraph_Layer::KindBit(Kind::Solid) | BRepGraph_Layer::KindBit(Kind::Shell) + | BRepGraph_Layer::KindBit(Kind::Face) | BRepGraph_Layer::KindBit(Kind::Wire) + | BRepGraph_Layer::KindBit(Kind::Edge) | BRepGraph_Layer::KindBit(Kind::Vertex) + | BRepGraph_Layer::KindBit(Kind::Compound) + | BRepGraph_Layer::KindBit(Kind::CompSolid); + // 8 distinct bits set. + int aBitCount = 0; + for (int v = aAll; v != 0; v >>= 1) + aBitCount += (v & 1); + EXPECT_EQ(aBitCount, 8); +} + +TEST_F(BRepGraph_EventBusTest, OverlappingSubscription_EdgeAndFace) +{ + // Layer subscribes to both Edge and Face - should receive events for both kinds. + const int aEdgeFace = BRepGraph_Layer::KindBit(BRepGraph_NodeId::Kind::Edge) + | BRepGraph_Layer::KindBit(BRepGraph_NodeId::Kind::Face); + occ::handle aLayer = + new BRepGraph_ModTrackingLayer("EdgeFace", aEdgeFace); + myGraph.LayerRegistry().RegisterLayer(aLayer); + + { + BRepGraph_MutGuard aMut = myGraph.Builder().MutEdge(BRepGraph_EdgeId(0)); + aMut->Tolerance = 0.5; + } + + // Edge events from direct mutation; NO face events (no parent dispatch). + EXPECT_GT(aLayer->CountImmediateEventsOfKind(BRepGraph_NodeId::Kind::Edge), 0); + EXPECT_EQ(aLayer->CountImmediateEventsOfKind(BRepGraph_NodeId::Kind::Face), 0); + EXPECT_EQ(aLayer->CountImmediateEventsOfKind(BRepGraph_NodeId::Kind::Wire), 0); +} diff --git a/src/ModelingData/TKBRep/GTests/BRepGraph_Geometry_Test.cxx b/src/ModelingData/TKBRep/GTests/BRepGraph_Geometry_Test.cxx new file mode 100644 index 0000000000..c147b80aed --- /dev/null +++ b/src/ModelingData/TKBRep/GTests/BRepGraph_Geometry_Test.cxx @@ -0,0 +1,943 @@ +// Copyright (c) 2026 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 + +namespace +{ + +static int componentKey(const BRepGraph_NodeId theNode) +{ + return theNode.Index * BRepGraph_NodeId::THE_KIND_COUNT + static_cast(theNode.NodeKind); +} + +static BRepGraph_NodeId componentRootOfFace(const BRepGraph& theGraph, + const BRepGraph_FaceId theFaceId) +{ + for (BRepGraph_ParentExplorer aSolidExp(theGraph, theFaceId, BRepGraph_NodeId::Kind::Solid); + aSolidExp.More(); + aSolidExp.Next()) + { + return aSolidExp.Current().DefId; + } + + for (BRepGraph_ParentExplorer aShellExp(theGraph, theFaceId, BRepGraph_NodeId::Kind::Shell); + aShellExp.More(); + aShellExp.Next()) + { + return aShellExp.Current().DefId; + } + + return theFaceId; +} + +static NCollection_DataMap faceCountsByComponent(const BRepGraph& theGraph) +{ + NCollection_DataMap aCounts; + for (BRepGraph_FaceIterator aFaceIt(theGraph); aFaceIt.More(); aFaceIt.Next()) + { + const int aKey = componentKey(componentRootOfFace(theGraph, aFaceIt.CurrentId())); + if (!aCounts.IsBound(aKey)) + { + aCounts.Bind(aKey, 0); + } + aCounts.ChangeFind(aKey) += 1; + } + return aCounts; +} + +} // namespace + +// ============================================================ +// Geometry navigation tests +// ============================================================ + +TEST(BRepGraph_GeometryTest, Sphere_AllFaces_SameSurface) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeSphere(15.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + // All face defs of a sphere share the same surface handle. + ASSERT_GE(aGraph.Topo().Faces().Nb(), 1); + ASSERT_TRUE(BRepGraph_Tool::Face::HasSurface(aGraph, BRepGraph_FaceId(0))); + const occ::handle& aFirstSurf = + BRepGraph_Tool::Face::Surface(aGraph, BRepGraph_FaceId(0)); + EXPECT_FALSE(aFirstSurf.IsNull()); + for (BRepGraph_FaceIterator aFaceIt(aGraph); aFaceIt.More(); aFaceIt.Next()) + { + const BRepGraph_FaceId aFaceId = aFaceIt.CurrentId(); + if (aFaceId.Index == 0) + { + continue; + } + ASSERT_TRUE(BRepGraph_Tool::Face::HasSurface(aGraph, aFaceId)); + EXPECT_EQ(BRepGraph_Tool::Face::Surface(aGraph, aFaceId).get(), aFirstSurf.get()); + } +} + +TEST(BRepGraph_GeometryTest, Sphere_AllFacesShareSurface) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeSphere(15.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + // All faces of a sphere share the same surface pointer. + ASSERT_TRUE(BRepGraph_Tool::Face::HasSurface(aGraph, BRepGraph_FaceId(0))); + const occ::handle& aFirstSurf = + BRepGraph_Tool::Face::Surface(aGraph, BRepGraph_FaceId(0)); + EXPECT_FALSE(aFirstSurf.IsNull()); + int aSameCount = 0; + for (BRepGraph_FaceIterator aFaceIt(aGraph); aFaceIt.More(); aFaceIt.Next()) + { + const BRepGraph_FaceId aFaceId = aFaceIt.CurrentId(); + if (BRepGraph_Tool::Face::HasSurface(aGraph, aFaceId) + && BRepGraph_Tool::Face::Surface(aGraph, aFaceId).get() == aFirstSurf.get()) + ++aSameCount; + } + EXPECT_EQ(aSameCount, aGraph.Topo().Faces().Nb()); +} + +TEST(BRepGraph_GeometryTest, Box_Curve3d_ValidForAll12Edges) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 20.0, 30.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + EXPECT_EQ(aGraph.Topo().Edges().Nb(), 12); + + for (BRepGraph_EdgeIterator anEdgeIt(aGraph); anEdgeIt.More(); anEdgeIt.Next()) + { + const BRepGraph_EdgeId anEdgeId = anEdgeIt.CurrentId(); + EXPECT_TRUE(BRepGraph_Tool::Edge::HasCurve(aGraph, anEdgeId)) + << "Edge def " << anEdgeId.Index << " has no valid curve"; + } +} + +TEST(BRepGraph_GeometryTest, Box_AllEdgesHaveCurve3d) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 20.0, 30.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + for (BRepGraph_EdgeIterator anEdgeIt(aGraph); anEdgeIt.More(); anEdgeIt.Next()) + { + const BRepGraph_EdgeId anEdgeId = anEdgeIt.CurrentId(); + EXPECT_TRUE(BRepGraph_Tool::Edge::HasCurve(aGraph, anEdgeId)) + << "Edge " << anEdgeId.Index << " has no curve"; + } +} + +TEST(BRepGraph_GeometryTest, Box_FindPCurve_AllEdgeFacePairs_Valid) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 20.0, 30.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + int aPCurveCount = 0; + for (BRepGraph_EdgeIterator anEdgeIt(aGraph); anEdgeIt.More(); anEdgeIt.Next()) + { + const BRepGraph_EdgeId anEdgeId = anEdgeIt.CurrentId(); + const NCollection_Vector& aCoEdgeIdxs = + aGraph.Topo().Edges().CoEdges(anEdgeId); + for (const BRepGraph_CoEdgeId& aCoEdgeId : aCoEdgeIdxs) + { + const BRepGraphInc::CoEdgeDef& aCE = aGraph.Topo().CoEdges().Definition(aCoEdgeId); + const BRepGraphInc::CoEdgeDef* aPCurveEntry = + BRepGraph_Tool::Edge::FindPCurve(aGraph, anEdgeId, BRepGraph_FaceId(aCE.FaceDefId.Index)); + EXPECT_NE(aPCurveEntry, nullptr); + ++aPCurveCount; + } + } + EXPECT_GT(aPCurveCount, 0); +} + +TEST(BRepGraph_GeometryTest, CoEdge_FaceDefIdValid) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 20.0, 30.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + for (BRepGraph_EdgeIterator anEdgeIt(aGraph); anEdgeIt.More(); anEdgeIt.Next()) + { + const NCollection_Vector& aCoEdgeIdxs = + aGraph.Topo().Edges().CoEdges(anEdgeIt.CurrentId()); + for (int j = 0; j < aCoEdgeIdxs.Length(); ++j) + { + const BRepGraphInc::CoEdgeDef& aCE = aGraph.Topo().CoEdges().Definition(aCoEdgeIdxs.Value(j)); + EXPECT_TRUE(aCE.FaceDefId.IsValid()) + << "Edge " << anEdgeIt.CurrentId().Index << " CoEdge " << j << " has invalid FaceDefId"; + EXPECT_EQ(BRepGraph_NodeId(aCE.FaceDefId).NodeKind, BRepGraph_NodeId::Kind::Face); + } + } +} + +TEST(BRepGraph_GeometryTest, CoEdge_ParamRange_NonZero) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 20.0, 30.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + int aCoEdgeCount = 0; + for (BRepGraph_EdgeIterator anEdgeIt(aGraph); anEdgeIt.More(); anEdgeIt.Next()) + { + const NCollection_Vector& aCoEdgeIdxs = + aGraph.Topo().Edges().CoEdges(anEdgeIt.CurrentId()); + for (int j = 0; j < aCoEdgeIdxs.Length(); ++j) + { + const BRepGraphInc::CoEdgeDef& aCE = aGraph.Topo().CoEdges().Definition(aCoEdgeIdxs.Value(j)); + const double aRange = aCE.ParamLast - aCE.ParamFirst; + EXPECT_GT(std::abs(aRange), Precision::PConfusion()) + << "Edge " << anEdgeIt.CurrentId().Index << " CoEdge " << j << " has zero parameter range"; + ++aCoEdgeCount; + } + } + EXPECT_GT(aCoEdgeCount, 0); +} + +TEST(BRepGraph_GeometryTest, CoEdge_Continuity_Valid) +{ + const TopoDS_Shape aShape = BRepPrimAPI_MakeCylinder(10.0, 20.0).Shape(); + + BRepGraph aGraph; + aGraph.Build(aShape); + ASSERT_TRUE(aGraph.IsDone()); + + bool hasNonC0Continuity = false; + for (BRepGraph_EdgeIterator anEdgeIt(aGraph); anEdgeIt.More(); anEdgeIt.Next()) + { + const NCollection_Vector& aCoEdgeIdxs = + aGraph.Topo().Edges().CoEdges(anEdgeIt.CurrentId()); + for (int j = 0; j < aCoEdgeIdxs.Length(); ++j) + { + const BRepGraphInc::CoEdgeDef& aCE = aGraph.Topo().CoEdges().Definition(aCoEdgeIdxs.Value(j)); + if (aCE.Continuity > GeomAbs_C0) + { + hasNonC0Continuity = true; + } + } + } + + EXPECT_TRUE(hasNonC0Continuity); +} + +TEST(BRepGraph_GeometryTest, FaceDef_Surface_IsNotNull) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 20.0, 30.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + for (BRepGraph_FaceIterator aFaceIt(aGraph); aFaceIt.More(); aFaceIt.Next()) + { + EXPECT_TRUE(BRepGraph_Tool::Face::HasSurface(aGraph, aFaceIt.CurrentId())) + << "Face " << aFaceIt.CurrentId().Index << " has null Surface handle"; + } +} + +TEST(BRepGraph_GeometryTest, EdgeDef_Curve3d_IsNotNull) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 20.0, 30.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + for (BRepGraph_EdgeIterator anEdgeIt(aGraph); anEdgeIt.More(); anEdgeIt.Next()) + { + EXPECT_TRUE(BRepGraph_Tool::Edge::HasCurve(aGraph, anEdgeIt.CurrentId())) + << "Edge " << anEdgeIt.CurrentId().Index << " has null Curve3d handle"; + } +} + +TEST(BRepGraph_GeometryTest, SameDomainFaces_SimpleBox_Empty) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 20.0, 30.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + // For a simple box each face has a unique surface, so SameDomainFaces is empty. + for (BRepGraph_FaceIterator aFaceIt(aGraph); aFaceIt.More(); aFaceIt.Next()) + { + const BRepGraph_FaceId aFaceDefId = aFaceIt.CurrentId(); + const NCollection_Vector aSameDomain = + aGraph.Topo().Faces().SameDomain(aFaceDefId, aGraph.Allocator()); + EXPECT_EQ(aSameDomain.Length(), 0) + << "Face def " << aFaceDefId.Index << " has unexpected same-domain faces"; + } +} + +TEST(BRepGraph_GeometryTest, CompoundWithMovedChild_SharedSolidDef) +{ + const TopoDS_Shape aBox = BRepPrimAPI_MakeBox(10.0, 10.0, 10.0).Shape(); + + gp_Trsf aTrsf; + aTrsf.SetTranslation(gp_Vec(100.0, 0.0, 0.0)); + const TopoDS_Shape aMoved = aBox.Moved(TopLoc_Location(aTrsf)); + + TopoDS_Compound aCompound; + BRep_Builder aBuilder; + aBuilder.MakeCompound(aCompound); + aBuilder.Add(aCompound, aBox); + aBuilder.Add(aCompound, aMoved); + + BRepGraph aGraph; + aGraph.Build(aCompound); + ASSERT_TRUE(aGraph.IsDone()); + + // Moved() preserves TShape - one solid definition, two compound ChildRefs. + EXPECT_EQ(aGraph.Topo().Solids().Nb(), 1); + // Verify the graph was built successfully. + EXPECT_TRUE(aGraph.IsDone()); +} + +TEST(BRepGraph_GeometryTest, FaceDef_Triangulation_NullForAnalyticNoCrash) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 20.0, 30.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + // Analytical box faces should have null triangulation (no mesh computed). + // Simply verify access does not crash. + for (BRepGraph_FaceIterator aFaceIt(aGraph); aFaceIt.More(); aFaceIt.Next()) + { + const bool hasActiveTri = BRepGraph_Tool::Face::HasTriangulation(aGraph, aFaceIt.CurrentId()); + EXPECT_FALSE(hasActiveTri) << "Face " << aFaceIt.CurrentId().Index + << " unexpectedly has a triangulation"; + } +} + +// ============================================================ +// Definition traversal tests +// ============================================================ + +TEST(BRepGraph_GeometryTest, SolidDef_CountMatchesNb) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 10.0, 10.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + int aCount = 0; + for (BRepGraph_SolidIterator aSolidIt(aGraph); aSolidIt.More(); aSolidIt.Next()) + { + (void)aSolidIt.Current(); + ++aCount; + } + EXPECT_EQ(aCount, aGraph.Topo().Solids().Nb()); +} + +TEST(BRepGraph_GeometryTest, ShellDef_CountMatchesNb) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 10.0, 10.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + int aCount = 0; + for (BRepGraph_ShellIterator aShellIt(aGraph); aShellIt.More(); aShellIt.Next()) + { + (void)aShellIt.Current(); + ++aCount; + } + EXPECT_EQ(aCount, aGraph.Topo().Shells().Nb()); +} + +TEST(BRepGraph_GeometryTest, FaceDef_CountMatchesNb) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 10.0, 10.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + int aCount = 0; + for (BRepGraph_FaceIterator aFaceIt(aGraph); aFaceIt.More(); aFaceIt.Next()) + { + (void)aFaceIt.Current(); + ++aCount; + } + EXPECT_EQ(aCount, aGraph.Topo().Faces().Nb()); +} + +TEST(BRepGraph_GeometryTest, WireDef_CountMatchesNb) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 10.0, 10.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + int aCount = 0; + for (BRepGraph_WireIterator aWireIt(aGraph); aWireIt.More(); aWireIt.Next()) + { + (void)aWireIt.Current(); + ++aCount; + } + EXPECT_EQ(aCount, aGraph.Topo().Wires().Nb()); +} + +TEST(BRepGraph_GeometryTest, EdgeDef_CountMatchesNb) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 10.0, 10.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + int aCount = 0; + for (BRepGraph_EdgeIterator anEdgeIt(aGraph); anEdgeIt.More(); anEdgeIt.Next()) + { + (void)anEdgeIt.Current(); + ++aCount; + } + EXPECT_EQ(aCount, aGraph.Topo().Edges().Nb()); +} + +TEST(BRepGraph_GeometryTest, VertexDef_CountMatchesNb) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 10.0, 10.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + int aCount = 0; + for (BRepGraph_VertexIterator aVertexIt(aGraph); aVertexIt.More(); aVertexIt.Next()) + { + (void)aVertexIt.Current(); + ++aCount; + } + EXPECT_EQ(aCount, aGraph.Topo().Vertices().Nb()); +} + +TEST(BRepGraph_GeometryTest, FaceDef_CountViaIterator_MatchesNb) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 10.0, 10.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + int aCount = 0; + for (BRepGraph_FaceIterator aFaceIt(aGraph); aFaceIt.More(); aFaceIt.Next()) + { + (void)aFaceIt.Current(); + ++aCount; + } + EXPECT_EQ(aCount, aGraph.Topo().Faces().Nb()); +} + +TEST(BRepGraph_GeometryTest, FaceDef_AllSurfacesNonNull) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 10.0, 10.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + int aCount = 0; + for (BRepGraph_FaceIterator aFaceIt(aGraph); aFaceIt.More(); aFaceIt.Next()) + { + EXPECT_TRUE(BRepGraph_Tool::Face::HasSurface(aGraph, aFaceIt.CurrentId())); + ++aCount; + } + EXPECT_EQ(aCount, aGraph.Topo().Faces().Nb()); +} + +TEST(BRepGraph_GeometryTest, EdgeDef_AllCurves3dNonNull) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 10.0, 10.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + int aCount = 0; + for (BRepGraph_EdgeIterator anEdgeIt(aGraph); anEdgeIt.More(); anEdgeIt.Next()) + { + if (!BRepGraph_Tool::Edge::Degenerated(aGraph, anEdgeIt.CurrentId())) + { + EXPECT_TRUE(BRepGraph_Tool::Edge::HasCurve(aGraph, anEdgeIt.CurrentId())); + } + ++aCount; + } + EXPECT_EQ(aCount, aGraph.Topo().Edges().Nb()); +} + +TEST(BRepGraph_GeometryTest, AllCoEdgesHaveCurve2d) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 10.0, 10.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + int aCount = 0; + for (BRepGraph_EdgeIterator anEdgeIt(aGraph); anEdgeIt.More(); anEdgeIt.Next()) + { + const NCollection_Vector& aCoEdgeIdxs = + aGraph.Topo().Edges().CoEdges(anEdgeIt.CurrentId()); + for (const BRepGraph_CoEdgeId& aCoEdgeId : aCoEdgeIdxs) + { + EXPECT_TRUE(BRepGraph_Tool::CoEdge::HasPCurve(aGraph, aCoEdgeId)); + ++aCount; + } + } + EXPECT_GT(aCount, 0); +} + +// ============================================================ +// Connected component grouping via ParentExplorer +// ============================================================ + +TEST(BRepGraph_GeometryTest, ConnectedComponents_SingleBox_OneComponent) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 10.0, 10.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + const NCollection_DataMap aFaceCounts = faceCountsByComponent(aGraph); + ASSERT_EQ(aFaceCounts.Extent(), 1); + int aComponentFaces = 0; + for (const auto& [aComponent, aCount] : aFaceCounts.Items()) + { + aComponentFaces = aCount; + } + EXPECT_EQ(aComponentFaces, 6); +} + +TEST(BRepGraph_GeometryTest, ConnectedComponents_TwoBoxCompound_TwoComponents) +{ + const TopoDS_Shape aBox1 = BRepPrimAPI_MakeBox(10.0, 10.0, 10.0).Shape(); + const TopoDS_Shape aBox2 = + BRepPrimAPI_MakeBox(gp_Pnt(50.0, 50.0, 50.0), 60.0, 60.0, 60.0).Shape(); + + TopoDS_Compound aCompound; + BRep_Builder aBuilder; + aBuilder.MakeCompound(aCompound); + aBuilder.Add(aCompound, aBox1); + aBuilder.Add(aCompound, aBox2); + + BRepGraph aGraph; + aGraph.Build(aCompound); + ASSERT_TRUE(aGraph.IsDone()); + + const NCollection_DataMap aFaceCounts = faceCountsByComponent(aGraph); + EXPECT_EQ(aFaceCounts.Extent(), 2); +} + +TEST(BRepGraph_GeometryTest, ConnectedComponents_TwoBoxCompound_FacesGroupedPerRoot) +{ + const TopoDS_Shape aBox1 = BRepPrimAPI_MakeBox(10.0, 10.0, 10.0).Shape(); + const TopoDS_Shape aBox2 = + BRepPrimAPI_MakeBox(gp_Pnt(50.0, 50.0, 50.0), 60.0, 60.0, 60.0).Shape(); + + TopoDS_Compound aCompound; + BRep_Builder aBuilder; + aBuilder.MakeCompound(aCompound); + aBuilder.Add(aCompound, aBox1); + aBuilder.Add(aCompound, aBox2); + + BRepGraph aGraph; + aGraph.Build(aCompound); + ASSERT_TRUE(aGraph.IsDone()); + + const NCollection_DataMap aFaceCounts = faceCountsByComponent(aGraph); + ASSERT_EQ(aFaceCounts.Extent(), 2); + for (const auto& [aComponent, aCount] : aFaceCounts.Items()) + { + EXPECT_EQ(aCount, 6); + } +} + +TEST(BRepGraph_GeometryTest, ConnectedComponents_TwoBoxCompound_CoverAllFaces) +{ + const TopoDS_Shape aBox1 = BRepPrimAPI_MakeBox(10.0, 10.0, 10.0).Shape(); + const TopoDS_Shape aBox2 = + BRepPrimAPI_MakeBox(gp_Pnt(50.0, 50.0, 50.0), 60.0, 60.0, 60.0).Shape(); + + TopoDS_Compound aCompound; + BRep_Builder aBuilder; + aBuilder.MakeCompound(aCompound); + aBuilder.Add(aCompound, aBox1); + aBuilder.Add(aCompound, aBox2); + + BRepGraph aGraph; + aGraph.Build(aCompound); + ASSERT_TRUE(aGraph.IsDone()); + + const NCollection_DataMap aFaceCounts = faceCountsByComponent(aGraph); + int aTotalFaces = 0; + for (const auto& [aComponent, aCount] : aFaceCounts.Items()) + { + aTotalFaces += aCount; + } + + EXPECT_EQ(aTotalFaces, aGraph.Topo().Faces().Nb()); +} + +// ============================================================ +// SameParameter / SameRange round-trip tests +// ============================================================ + +TEST(BRepGraph_GeometryTest, Box_EdgeDef_SameParameter_IsSet) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 20.0, 30.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + ASSERT_GT(aGraph.Topo().Edges().Nb(), 0); + + // Box edges are well-formed; SameParameter should be true for all. + for (BRepGraph_EdgeIterator anEdgeIt(aGraph); anEdgeIt.More(); anEdgeIt.Next()) + { + EXPECT_TRUE(BRepGraph_Tool::Edge::SameParameter(aGraph, anEdgeIt.CurrentId())) + << "Edge def " << anEdgeIt.CurrentId().Index << " has SameParameter=false"; + } +} + +TEST(BRepGraph_GeometryTest, Box_EdgeDef_SameRange_IsSet) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 20.0, 30.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + ASSERT_GT(aGraph.Topo().Edges().Nb(), 0); + + for (BRepGraph_EdgeIterator anEdgeIt(aGraph); anEdgeIt.More(); anEdgeIt.Next()) + { + EXPECT_TRUE(BRepGraph_Tool::Edge::SameRange(aGraph, anEdgeIt.CurrentId())) + << "Edge def " << anEdgeIt.CurrentId().Index << " has SameRange=false"; + } +} + +// ============================================================ +// Seam edge PCurve validation tests +// ============================================================ + +TEST(BRepGraph_GeometryTest, Cylinder_SeamEdge_HasTwoCoEdges) +{ + // A cylinder has a seam edge on its lateral face. + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeCylinder(5.0, 20.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + // Find a seam edge via CoEdge SeamPairId. + bool aFoundSeam = false; + for (BRepGraph_EdgeIterator anEdgeIt(aGraph); anEdgeIt.More() && !aFoundSeam; anEdgeIt.Next()) + { + const NCollection_Vector& aCoEdgeIdxs = + aGraph.Topo().Edges().CoEdges(anEdgeIt.CurrentId()); + for (const BRepGraph_CoEdgeId& aCoEdgeId : aCoEdgeIdxs) + { + const BRepGraphInc::CoEdgeDef& aCE = aGraph.Topo().CoEdges().Definition(aCoEdgeId); + if (aCE.SeamPairId.IsValid()) + { + // Verify the paired coedge has opposite orientation. + const BRepGraphInc::CoEdgeDef& aPair = aGraph.Topo().CoEdges().Definition(aCE.SeamPairId); + EXPECT_NE(aCE.Sense, aPair.Sense) << "Seam coedges should have opposite orientations"; + EXPECT_EQ(aCE.FaceDefId, aPair.FaceDefId) << "Seam coedges should share the same face"; + aFoundSeam = true; + break; + } + } + } + EXPECT_TRUE(aFoundSeam) << "No seam edge found in cylinder"; +} + +TEST(BRepGraph_GeometryTest, Cylinder_SeamEdge_FindPCurve_WithOrientation) +{ + // Verify FindPCurve(edge, face, orientation) returns different entries for FORWARD vs REVERSED. + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeCylinder(5.0, 20.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + for (BRepGraph_EdgeIterator anEdgeIt(aGraph); anEdgeIt.More(); anEdgeIt.Next()) + { + const BRepGraph_EdgeId anEdgeId = anEdgeIt.CurrentId(); + const NCollection_Vector& aCoEdgeIdxs = + aGraph.Topo().Edges().CoEdges(anEdgeId); + + for (const BRepGraph_CoEdgeId& aCoEdgeId : aCoEdgeIdxs) + { + const BRepGraphInc::CoEdgeDef& aCE = aGraph.Topo().CoEdges().Definition(aCoEdgeId); + if (!aCE.SeamPairId.IsValid()) + continue; + + // This is a seam edge - test oriented overload. + const BRepGraph_FaceId aFaceId(aCE.FaceDefId.Index); + const BRepGraphInc::CoEdgeDef* aPC_Fwd = + BRepGraph_Tool::Edge::FindPCurve(aGraph, anEdgeId, aFaceId, TopAbs_FORWARD); + const BRepGraphInc::CoEdgeDef* aPC_Rev = + BRepGraph_Tool::Edge::FindPCurve(aGraph, anEdgeId, aFaceId, TopAbs_REVERSED); + + EXPECT_NE(aPC_Fwd, nullptr) << "FindPCurve FORWARD returned null for seam edge"; + EXPECT_NE(aPC_Rev, nullptr) << "FindPCurve REVERSED returned null for seam edge"; + EXPECT_NE(aPC_Fwd, aPC_Rev) << "FORWARD and REVERSED PCurves should be different entries"; + return; // one seam is enough + } + } + GTEST_SKIP() << "No seam edge found; test inconclusive"; +} + +TEST(BRepGraph_GeometryTest, Box_FindPCurve_MatchesToolOverload) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 20.0, 30.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + for (BRepGraph_EdgeIterator anEdgeIt(aGraph); anEdgeIt.More(); anEdgeIt.Next()) + { + const BRepGraph_EdgeId anEdgeId = anEdgeIt.CurrentId(); + const NCollection_Vector& aCoEdgeIdxs = + aGraph.Topo().Edges().CoEdges(anEdgeId); + + for (const BRepGraph_CoEdgeId& aCoEdgeId : aCoEdgeIdxs) + { + const BRepGraphInc::CoEdgeDef& aCE = aGraph.Topo().CoEdges().Definition(aCoEdgeId); + const BRepGraphInc::CoEdgeDef* aFromDefs = + BRepGraph_Tool::Edge::FindPCurve(aGraph, anEdgeId, aCE.FaceDefId, aCE.Sense); + const BRepGraphInc::CoEdgeDef* aFromTool = + BRepGraph_Tool::Edge::FindPCurve(aGraph, + anEdgeId, + BRepGraph_FaceId(aCE.FaceDefId.Index), + aCE.Sense); + + EXPECT_EQ(aFromDefs, aFromTool); + EXPECT_NE(aFromDefs, nullptr); + } + } +} + +TEST(BRepGraph_GeometryTest, Cylinder_SeamEdge_FindPCurve_DistinguishesOrientation) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeCylinder(5.0, 20.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + for (BRepGraph_EdgeIterator anEdgeIt(aGraph); anEdgeIt.More(); anEdgeIt.Next()) + { + const NCollection_Vector& aCoEdgeIdxs = + aGraph.Topo().Edges().CoEdges(anEdgeIt.CurrentId()); + + for (const BRepGraph_CoEdgeId& aCoEdgeId : aCoEdgeIdxs) + { + const BRepGraphInc::CoEdgeDef& aCE = aGraph.Topo().CoEdges().Definition(aCoEdgeId); + if (!aCE.SeamPairId.IsValid()) + continue; + + // Seam edge: same face, two orientations. + const BRepGraph_FaceId aFaceId = aCE.FaceDefId; + const BRepGraph_EdgeId anEdgeId = anEdgeIt.CurrentId(); + + const BRepGraphInc::CoEdgeDef* aPCFwd = + BRepGraph_Tool::Edge::FindPCurve(aGraph, anEdgeId, aFaceId, TopAbs_FORWARD); + const BRepGraphInc::CoEdgeDef* aPCRev = + BRepGraph_Tool::Edge::FindPCurve(aGraph, anEdgeId, aFaceId, TopAbs_REVERSED); + + EXPECT_NE(aPCFwd, nullptr); + EXPECT_NE(aPCRev, nullptr); + EXPECT_NE(aPCFwd, aPCRev); + return; + } + } + GTEST_SKIP() << "No seam edge found; test inconclusive"; +} + +// ============================================================ +// Representation entity layer tests +// ============================================================ + +TEST(BRepGraph_GeometryTest, Box_RepCounts_MatchTopology) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 20.0, 30.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + EXPECT_GT(aGraph.Topo().Geometry().NbSurfaces(), 0); + EXPECT_GT(aGraph.Topo().Geometry().NbCurves3D(), 0); + EXPECT_GT(aGraph.Topo().Geometry().NbCurves2D(), 0); + + // Every face has a valid surface. + for (BRepGraph_FaceIterator aFaceIt(aGraph); aFaceIt.More(); aFaceIt.Next()) + { + EXPECT_TRUE(BRepGraph_Tool::Face::HasSurface(aGraph, aFaceIt.CurrentId())) + << "Face " << aFaceIt.CurrentId().Index << " has no surface"; + EXPECT_FALSE(BRepGraph_Tool::Face::Surface(aGraph, aFaceIt.CurrentId()).IsNull()); + } + + // Every non-degenerate edge has a valid 3D curve. + for (BRepGraph_EdgeIterator anEdgeIt(aGraph); anEdgeIt.More(); anEdgeIt.Next()) + { + if (!BRepGraph_Tool::Edge::Degenerated(aGraph, anEdgeIt.CurrentId()) + && BRepGraph_Tool::Edge::HasCurve(aGraph, anEdgeIt.CurrentId())) + { + EXPECT_FALSE(BRepGraph_Tool::Edge::Curve(aGraph, anEdgeIt.CurrentId()).IsNull()); + } + } + + // Every coedge with a PCurve has a valid Curve2DRepId. + for (BRepGraph_CoEdgeIterator aCoEdgeIt(aGraph); aCoEdgeIt.More(); aCoEdgeIt.Next()) + { + const BRepGraphInc::CoEdgeDef& aCoEdge = aCoEdgeIt.Current(); + if (aCoEdge.Curve2DRepId.IsValid()) + { + const occ::handle& aPCurve = + BRepGraph_Tool::CoEdge::PCurve(aGraph, aCoEdgeIt.CurrentId()); + EXPECT_FALSE(aPCurve.IsNull()); + } + } +} + +TEST(BRepGraph_GeometryTest, Sphere_SurfaceDedup_SharedHandle) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeSphere(15.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + // All faces of a sphere share the same TShape -> same entity -> same surface. + if (aGraph.Topo().Faces().Nb() > 1) + { + const Geom_Surface* aFirstSurfPtr = + BRepGraph_Tool::Face::Surface(aGraph, BRepGraph_FaceId(0)).get(); + for (BRepGraph_FaceId aFaceId(1); aFaceId.IsValid(aGraph.Topo().Faces().Nb()); ++aFaceId) + { + EXPECT_EQ(BRepGraph_Tool::Face::Surface(aGraph, aFaceId).get(), aFirstSurfPtr) + << "Faces sharing same surface should share the same surface pointer"; + } + } +} + +TEST(BRepGraph_GeometryTest, Cylinder_TriangulationReps_Populated) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeCylinder(5.0, 10.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + for (BRepGraph_FaceIterator aFaceIt(aGraph); aFaceIt.More(); aFaceIt.Next()) + { + const BRepGraphInc::FaceDef& aFace = aFaceIt.Current(); + if (!aFace.TriangulationRepIds.IsEmpty()) + { + for (const BRepGraph_TriangulationRepId& aTriRepId : aFace.TriangulationRepIds) + { + EXPECT_TRUE(aTriRepId.IsValid()); + } + // Verify active triangulation is non-null via BRepGraph_Tool. + if (BRepGraph_Tool::Face::HasTriangulation(aGraph, aFaceIt.CurrentId())) + { + EXPECT_FALSE(BRepGraph_Tool::Face::Triangulation(aGraph, aFaceIt.CurrentId()).IsNull()); + } + } + } +} + +TEST(BRepGraph_GeometryTest, RepId_FactoryMethods) +{ + const BRepGraph_SurfaceRepId aSurfId = BRepGraph_RepId::Surface(42); + EXPECT_EQ(BRepGraph_RepId(aSurfId).RepKind, BRepGraph_RepId::Kind::Surface); + EXPECT_EQ(aSurfId.Index, 42); + EXPECT_TRUE(aSurfId.IsValid()); + + const BRepGraph_Curve3DRepId aCurve3DId = BRepGraph_RepId::Curve3D(7); + EXPECT_EQ(BRepGraph_RepId(aCurve3DId).RepKind, BRepGraph_RepId::Kind::Curve3D); + EXPECT_EQ(aCurve3DId.Index, 7); + + const BRepGraph_RepId aDefaultId; + EXPECT_FALSE(aDefaultId.IsValid()); + + EXPECT_EQ(aSurfId, BRepGraph_RepId::Surface(42)); + EXPECT_NE(BRepGraph_RepId(aSurfId), BRepGraph_RepId(aCurve3DId)); +} + +TEST(BRepGraph_GeometryTest, RepId_UntypedArithmetic_PreservesKindAndIndex) +{ + BRepGraph_RepId aRepId(BRepGraph_RepId::Kind::Curve3D, 5); + + const BRepGraph_RepId aPrev = aRepId++; + EXPECT_EQ(aPrev.RepKind, BRepGraph_RepId::Kind::Curve3D); + EXPECT_EQ(aPrev.Index, 5); + EXPECT_EQ(aRepId.Index, 6); + + ++aRepId; + EXPECT_EQ(aRepId.Index, 7); + + const BRepGraph_RepId anAdvanced = aRepId + 2; + EXPECT_EQ(anAdvanced.RepKind, BRepGraph_RepId::Kind::Curve3D); + EXPECT_EQ(anAdvanced.Index, 9); + + const BRepGraph_RepId aRetreated = anAdvanced - 4; + EXPECT_EQ(aRetreated.RepKind, BRepGraph_RepId::Kind::Curve3D); + EXPECT_EQ(aRetreated.Index, 5); +} + +TEST(BRepGraph_GeometryTest, Compound_TwoBoxes_SurfaceDedup) +{ + TopoDS_Shape aBox1 = BRepPrimAPI_MakeBox(10.0, 10.0, 10.0).Shape(); + TopoDS_Shape aBox2 = BRepPrimAPI_MakeBox(20.0, 20.0, 20.0).Shape(); + + BRep_Builder aBuilder; + TopoDS_Compound aCompound; + aBuilder.MakeCompound(aCompound); + aBuilder.Add(aCompound, aBox1); + aBuilder.Add(aCompound, aBox2); + + BRepGraph aGraph; + aGraph.Build(aCompound); + ASSERT_TRUE(aGraph.IsDone()); + + EXPECT_EQ(aGraph.Topo().Faces().Nb(), 12); + EXPECT_GT(aGraph.Topo().Geometry().NbSurfaces(), 0); + EXPECT_LE(aGraph.Topo().Geometry().NbSurfaces(), 12); + + for (BRepGraph_FaceIterator aFaceIt(aGraph); aFaceIt.More(); aFaceIt.Next()) + { + EXPECT_TRUE(BRepGraph_Tool::Face::HasSurface(aGraph, aFaceIt.CurrentId())); + } + for (BRepGraph_EdgeIterator anEdgeIt(aGraph); anEdgeIt.More(); anEdgeIt.Next()) + { + if (!BRepGraph_Tool::Edge::Degenerated(aGraph, anEdgeIt.CurrentId()) + && BRepGraph_Tool::Edge::HasCurve(aGraph, anEdgeIt.CurrentId())) + { + EXPECT_FALSE(BRepGraph_Tool::Edge::Curve(aGraph, anEdgeIt.CurrentId()).IsNull()); + } + } +} + +TEST(BRepGraph_GeometryTest, Box_Polygon2DRep_MatchesInline) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 20.0, 30.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + // Every coedge with a Polygon2DRepId has a valid polygon rep. + for (BRepGraph_CoEdgeIterator aCoEdgeIt(aGraph); aCoEdgeIt.More(); aCoEdgeIt.Next()) + { + const BRepGraphInc::CoEdgeDef& aCoEdge = aCoEdgeIt.Current(); + if (aCoEdge.Polygon2DRepId.IsValid()) + { + const occ::handle& aPoly = + BRepGraph_Tool::CoEdge::PolygonOnSurface(aGraph, aCoEdgeIt.CurrentId()); + EXPECT_FALSE(aPoly.IsNull()) + << "CoEdge " << aCoEdgeIt.CurrentId().Index << " has Polygon2DRepId but null polygon"; + } + // PolygonOnTriRepIds should have valid rep entries. + for (const BRepGraph_PolygonOnTriRepId& aPolyOnTriRepId : aCoEdge.PolygonOnTriRepIds) + { + EXPECT_TRUE(aPolyOnTriRepId.IsValid()); + } + } +} diff --git a/src/ModelingData/TKBRep/GTests/BRepGraph_History_Test.cxx b/src/ModelingData/TKBRep/GTests/BRepGraph_History_Test.cxx new file mode 100644 index 0000000000..9f3b1394c8 --- /dev/null +++ b/src/ModelingData/TKBRep/GTests/BRepGraph_History_Test.cxx @@ -0,0 +1,540 @@ +// Copyright (c) 2026 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 "BRepGraph_RefTestTools.hxx" +#include +#include +#include +#include +#include + +#include + +class BRepGraph_HistoryTest : public testing::Test +{ +protected: + void SetUp() override + { + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + myGraph.Build(aBoxMaker.Shape()); + } + + BRepGraph myGraph; +}; + +// ============================================================ +// History chain tests +// ============================================================ + +TEST_F(BRepGraph_HistoryTest, FindOriginal_ChainABC_ReturnsA) +{ + // Build a chain: edge0 -> edge1 -> edge2 via two ApplyModification calls. + const BRepGraph_NodeId anEdge0(BRepGraph_NodeId::Kind::Edge, 0); + BRepGraph_NodeId anEdge1; + BRepGraph_NodeId anEdge2; + + myGraph.Builder().ApplyModification( + anEdge0, + [&](BRepGraph& theGraph, + BRepGraph_NodeId /*theTarget*/) -> NCollection_Vector { + // Simulate producing a new edge node at index NbEdgeDefs. + anEdge1 = BRepGraph_NodeId(BRepGraph_NodeId::Kind::Edge, theGraph.Topo().Edges().Nb()); + NCollection_Vector aResult; + aResult.Append(anEdge1); + return aResult; + }, + "Step1"); + + myGraph.Builder().ApplyModification( + anEdge1, + [&](BRepGraph& theGraph, + BRepGraph_NodeId /*theTarget*/) -> NCollection_Vector { + anEdge2 = BRepGraph_NodeId(BRepGraph_NodeId::Kind::Edge, theGraph.Topo().Edges().Nb()); + NCollection_Vector aResult; + aResult.Append(anEdge2); + return aResult; + }, + "Step2"); + + const BRepGraph_NodeId anOriginal = myGraph.History().FindOriginal(anEdge2); + EXPECT_TRUE(anOriginal.IsValid()); + EXPECT_EQ(anOriginal, anEdge0); +} + +TEST_F(BRepGraph_HistoryTest, FindDerived_ChainABC_ContainsBAndC) +{ + const BRepGraph_NodeId anEdge0(BRepGraph_NodeId::Kind::Edge, 0); + BRepGraph_NodeId anEdge1; + BRepGraph_NodeId anEdge2; + + myGraph.Builder().ApplyModification( + anEdge0, + [&](BRepGraph& theGraph, BRepGraph_NodeId) -> NCollection_Vector { + anEdge1 = BRepGraph_NodeId(BRepGraph_NodeId::Kind::Edge, theGraph.Topo().Edges().Nb()); + NCollection_Vector aResult; + aResult.Append(anEdge1); + return aResult; + }, + "Step1"); + + myGraph.Builder().ApplyModification( + anEdge1, + [&](BRepGraph& theGraph, BRepGraph_NodeId) -> NCollection_Vector { + anEdge2 = BRepGraph_NodeId(BRepGraph_NodeId::Kind::Edge, theGraph.Topo().Edges().Nb()); + NCollection_Vector aResult; + aResult.Append(anEdge2); + return aResult; + }, + "Step2"); + + const NCollection_Vector aDerived = myGraph.History().FindDerived(anEdge0); + bool hasEdge1 = false; + bool hasEdge2 = false; + for (const BRepGraph_NodeId& aDerivedId : aDerived) + { + if (aDerivedId == anEdge1) + hasEdge1 = true; + if (aDerivedId == anEdge2) + hasEdge2 = true; + } + EXPECT_TRUE(hasEdge1); + EXPECT_TRUE(hasEdge2); +} + +TEST_F(BRepGraph_HistoryTest, FindOriginal_UnmodifiedNode_ReturnsSelf) +{ + // FindOriginal returns the node itself when it is not derived from anything. + const BRepGraph_NodeId aFace(BRepGraph_NodeId::Kind::Face, 0); + const BRepGraph_NodeId anOriginal = myGraph.History().FindOriginal(aFace); + EXPECT_TRUE(anOriginal.IsValid()); + EXPECT_EQ(anOriginal, aFace); +} + +TEST_F(BRepGraph_HistoryTest, FindDerived_UnmodifiedNode_ReturnsEmpty) +{ + const BRepGraph_NodeId aFace(BRepGraph_NodeId::Kind::Face, 0); + const NCollection_Vector aDerived = myGraph.History().FindDerived(aFace); + EXPECT_EQ(aDerived.Length(), 0); +} + +TEST_F(BRepGraph_HistoryTest, Disabled_RecordHistory_NoRecordStored) +{ + const int aNbBefore = myGraph.History().NbRecords(); + myGraph.History().SetEnabled(false); + + const BRepGraph_NodeId anEdge0(BRepGraph_NodeId::Kind::Edge, 0); + NCollection_Vector aReplacements; + aReplacements.Append(BRepGraph_NodeId(BRepGraph_NodeId::Kind::Edge, 1)); + myGraph.History().Record("Disabled", anEdge0, aReplacements); + + EXPECT_EQ(myGraph.History().NbRecords(), aNbBefore); +} + +TEST_F(BRepGraph_HistoryTest, Disabled_ApplyModification_ModifierStillRuns) +{ + myGraph.History().SetEnabled(false); + bool aModifierRan = false; + + const BRepGraph_NodeId anEdge0(BRepGraph_NodeId::Kind::Edge, 0); + myGraph.Builder().ApplyModification( + anEdge0, + [&](BRepGraph&, BRepGraph_NodeId) -> NCollection_Vector { + aModifierRan = true; + return NCollection_Vector(); + }, + "DisabledOp"); + + EXPECT_TRUE(aModifierRan); +} + +TEST_F(BRepGraph_HistoryTest, ReEnabled_RecordsAfterReEnable) +{ + myGraph.History().SetEnabled(false); + EXPECT_FALSE(myGraph.History().IsEnabled()); + + myGraph.History().SetEnabled(true); + EXPECT_TRUE(myGraph.History().IsEnabled()); + + const int aNbBefore = myGraph.History().NbRecords(); + const BRepGraph_NodeId anEdge0(BRepGraph_NodeId::Kind::Edge, 0); + NCollection_Vector aReplacements; + aReplacements.Append(BRepGraph_NodeId(BRepGraph_NodeId::Kind::Edge, 1)); + myGraph.History().Record("ReEnabled", anEdge0, aReplacements); + + EXPECT_EQ(myGraph.History().NbRecords(), aNbBefore + 1); +} + +TEST_F(BRepGraph_HistoryTest, ApplyModification_EmptyReplacements) +{ + const BRepGraph_NodeId anEdge0(BRepGraph_NodeId::Kind::Edge, 0); + const int aNbBefore = myGraph.History().NbRecords(); + + myGraph.Builder().ApplyModification( + anEdge0, + [](BRepGraph&, BRepGraph_NodeId) -> NCollection_Vector { + return NCollection_Vector(); + }, + "Delete"); + + EXPECT_EQ(myGraph.History().NbRecords(), aNbBefore + 1); + const BRepGraph_HistoryRecord& aRec = myGraph.History().Record(myGraph.History().NbRecords() - 1); + EXPECT_TRUE(aRec.OperationName.IsEqual("Delete")); +} + +TEST_F(BRepGraph_HistoryTest, ApplyModification_MultipleReplacements) +{ + const BRepGraph_NodeId anEdge0(BRepGraph_NodeId::Kind::Edge, 0); + BRepGraph_NodeId aNew1; + BRepGraph_NodeId aNew2; + + myGraph.Builder().ApplyModification( + anEdge0, + [&](BRepGraph& theGraph, BRepGraph_NodeId) -> NCollection_Vector { + const int aBase = theGraph.Topo().Edges().Nb(); + aNew1 = BRepGraph_NodeId(BRepGraph_NodeId::Kind::Edge, aBase); + aNew2 = BRepGraph_NodeId(BRepGraph_NodeId::Kind::Edge, aBase + 1); + NCollection_Vector aResult; + aResult.Append(aNew1); + aResult.Append(aNew2); + return aResult; + }, + "Split"); + + const NCollection_Vector aDerived = myGraph.History().FindDerived(anEdge0); + bool hasNew1 = false; + bool hasNew2 = false; + for (const BRepGraph_NodeId& aDerivedId : aDerived) + { + if (aDerivedId == aNew1) + hasNew1 = true; + if (aDerivedId == aNew2) + hasNew2 = true; + } + EXPECT_TRUE(hasNew1); + EXPECT_TRUE(hasNew2); +} + +TEST_F(BRepGraph_HistoryTest, RecordHistory_EmptyReplacements_Stored) +{ + const int aNbBefore = myGraph.History().NbRecords(); + const BRepGraph_NodeId anEdge0(BRepGraph_NodeId::Kind::Edge, 0); + NCollection_Vector anEmpty; + myGraph.History().Record("Erase", anEdge0, anEmpty); + + EXPECT_EQ(myGraph.History().NbRecords(), aNbBefore + 1); +} + +TEST_F(BRepGraph_HistoryTest, HistoryRecord_SequenceNumber_Monotonic) +{ + const BRepGraph_NodeId anEdge0(BRepGraph_NodeId::Kind::Edge, 0); + const BRepGraph_NodeId anEdge1(BRepGraph_NodeId::Kind::Edge, 1); + const BRepGraph_NodeId anEdge2(BRepGraph_NodeId::Kind::Edge, 2); + + NCollection_Vector aRepl; + aRepl.Append(anEdge1); + myGraph.History().Record("Op1", anEdge0, aRepl); + + NCollection_Vector aRepl2; + aRepl2.Append(anEdge2); + myGraph.History().Record("Op2", anEdge1, aRepl2); + + const int aNb = myGraph.History().NbRecords(); + ASSERT_GE(aNb, 2); + const BRepGraph_HistoryRecord& aRec1 = myGraph.History().Record(aNb - 2); + const BRepGraph_HistoryRecord& aRec2 = myGraph.History().Record(aNb - 1); + EXPECT_LT(aRec1.SequenceNumber, aRec2.SequenceNumber); +} + +TEST_F(BRepGraph_HistoryTest, HistoryRecord_OperationName_Stored) +{ + const BRepGraph_NodeId anEdge0(BRepGraph_NodeId::Kind::Edge, 0); + NCollection_Vector aRepl; + aRepl.Append(BRepGraph_NodeId(BRepGraph_NodeId::Kind::Edge, 1)); + myGraph.History().Record("MyCustomOp", anEdge0, aRepl); + + const BRepGraph_HistoryRecord& aRec = myGraph.History().Record(myGraph.History().NbRecords() - 1); + EXPECT_TRUE(aRec.OperationName.IsEqual("MyCustomOp")); +} + +TEST_F(BRepGraph_HistoryTest, NbHistoryRecords_AfterMultipleOps_Correct) +{ + const int aNbBefore = myGraph.History().NbRecords(); + + const BRepGraph_NodeId anEdge0(BRepGraph_NodeId::Kind::Edge, 0); + NCollection_Vector aRepl; + aRepl.Append(BRepGraph_NodeId(BRepGraph_NodeId::Kind::Edge, 1)); + + myGraph.History().Record("A", anEdge0, aRepl); + myGraph.History().Record("B", anEdge0, aRepl); + myGraph.History().Record("C", anEdge0, aRepl); + + EXPECT_EQ(myGraph.History().NbRecords(), aNbBefore + 3); +} + +TEST_F(BRepGraph_HistoryTest, FindOriginal_TwoApply_TransitiveTrace) +{ + const BRepGraph_NodeId aVtx0(BRepGraph_NodeId::Kind::Vertex, 0); + BRepGraph_NodeId aVtx1; + BRepGraph_NodeId aVtx2; + + myGraph.Builder().ApplyModification( + aVtx0, + [&](BRepGraph& theGraph, BRepGraph_NodeId) -> NCollection_Vector { + aVtx1 = BRepGraph_NodeId(BRepGraph_NodeId::Kind::Vertex, theGraph.Topo().Vertices().Nb()); + NCollection_Vector aResult; + aResult.Append(aVtx1); + return aResult; + }, + "Move1"); + + myGraph.Builder().ApplyModification( + aVtx1, + [&](BRepGraph& theGraph, BRepGraph_NodeId) -> NCollection_Vector { + aVtx2 = BRepGraph_NodeId(BRepGraph_NodeId::Kind::Vertex, theGraph.Topo().Vertices().Nb()); + NCollection_Vector aResult; + aResult.Append(aVtx2); + return aResult; + }, + "Move2"); + + // FindOriginal from the end of a 2-step chain should reach the root. + const BRepGraph_NodeId anOriginal = myGraph.History().FindOriginal(aVtx2); + EXPECT_TRUE(anOriginal.IsValid()); + EXPECT_EQ(anOriginal, aVtx0); + + // Intermediate node should also trace back to root. + const BRepGraph_NodeId anOriginal1 = myGraph.History().FindOriginal(aVtx1); + EXPECT_TRUE(anOriginal1.IsValid()); + EXPECT_EQ(anOriginal1, aVtx0); +} + +TEST_F(BRepGraph_HistoryTest, ApplyModification_WhenModifierThrows_DoesNotRecordHistory) +{ + const int aNbRecordsBefore = myGraph.History().NbRecords(); + + const BRepGraph_NodeId anEdge(BRepGraph_NodeId::Kind::Edge, 0); + +#if !defined(No_Exception) + EXPECT_THROW(myGraph.Builder().ApplyModification( + anEdge, + [](BRepGraph&, BRepGraph_NodeId) -> NCollection_Vector { + throw Standard_Failure("Synthetic failure"); + }, + "ThrowingModification"), + Standard_Failure); +#endif + + EXPECT_EQ(myGraph.History().NbRecords(), aNbRecordsBefore); +} + +TEST_F(BRepGraph_HistoryTest, SplitEdge_RewritesAllContainingWires) +{ + ASSERT_GT(myGraph.Topo().Edges().Nb(), 0); + + const BRepGraph_EdgeId anEdgeId(0); + const BRepGraphInc::EdgeDef& anEdgeDef = + myGraph.Topo().Edges().Definition(BRepGraph_EdgeId(anEdgeId.Index)); + + const double aSplitParam = 0.5 * (anEdgeDef.ParamFirst + anEdgeDef.ParamLast); + + const int aNbVerticesBefore = myGraph.Topo().Vertices().Nb(); + const gp_Pnt aSplitPoint(1.0, 2.0, 3.0); + const BRepGraph_VertexId aSplitVertex = myGraph.Builder().AddVertex(aSplitPoint, 1.0e-7); + + ASSERT_TRUE(aSplitVertex.IsValid()); + EXPECT_EQ(myGraph.Topo().Vertices().Nb(), aNbVerticesBefore + 1); + + const NCollection_Vector& aWireIndices = + myGraph.Topo().Edges().Wires(BRepGraph_EdgeId(anEdgeId.Index)); + ASSERT_GT(aWireIndices.Length(), 0); + + const int aNbEdgesBefore = myGraph.Topo().Edges().Nb(); + const int aNbActiveEdgesBefore = myGraph.Topo().Edges().NbActive(); + + BRepGraph_EdgeId aSubA; + BRepGraph_EdgeId aSubB; + + myGraph.Builder().SplitEdge(anEdgeId, aSplitVertex, aSplitParam, aSubA, aSubB); + + ASSERT_TRUE(aSubA.IsValid()); + ASSERT_TRUE(aSubB.IsValid()); + EXPECT_EQ(myGraph.Topo().Edges().Nb(), aNbEdgesBefore + 2); + EXPECT_EQ(myGraph.Topo().Edges().NbActive(), aNbActiveEdgesBefore + 1); + + for (const BRepGraph_WireId& aWireId : aWireIndices) + { + const NCollection_Vector aCoEdgeRefs = + BRepGraph_TestTools::CoEdgeRefsOfWire(myGraph, aWireId); + + bool hasOld = false; + bool hasSubA = false; + bool hasSubB = false; + int aSubAOrd = -1; + int aSubBOrd = -1; + + for (int anIdx = 0; anIdx < aCoEdgeRefs.Length(); ++anIdx) + { + const BRepGraphInc::CoEdgeRef& aCR = myGraph.Refs().CoEdges().Entry(aCoEdgeRefs.Value(anIdx)); + const BRepGraphInc::CoEdgeDef& aCoEdge = myGraph.Topo().CoEdges().Definition(aCR.CoEdgeDefId); + const BRepGraph_NodeId anId(aCoEdge.EdgeDefId); + if (anId == anEdgeId) + { + hasOld = true; + } + else if (anId == aSubA) + { + hasSubA = true; + aSubAOrd = anIdx; + } + else if (anId == aSubB) + { + hasSubB = true; + aSubBOrd = anIdx; + } + } + + EXPECT_FALSE(hasOld); + EXPECT_TRUE(hasSubA); + EXPECT_TRUE(hasSubB); + EXPECT_GE(aSubAOrd, 0); + EXPECT_GE(aSubBOrd, 0); + EXPECT_EQ(aSubBOrd, aSubAOrd + 1); + } +} + +TEST_F(BRepGraph_HistoryTest, SplitEdge_IgnoresRemovedCoEdgeRefEntries) +{ + ASSERT_GT(myGraph.Topo().Edges().Nb(), 0); + + const BRepGraph_EdgeId anEdgeId(0); + const BRepGraphInc::EdgeDef& anEdgeDef = + myGraph.Topo().Edges().Definition(BRepGraph_EdgeId(anEdgeId.Index)); + const double aSplitParam = 0.5 * (anEdgeDef.ParamFirst + anEdgeDef.ParamLast); + + const NCollection_Vector& aWireIndices = + myGraph.Topo().Edges().Wires(BRepGraph_EdgeId(anEdgeId.Index)); + ASSERT_GT(aWireIndices.Length(), 1); + + const BRepGraph_WireId aWireId = aWireIndices.Value(0); + const NCollection_Vector aWireRefsBefore = + BRepGraph_TestTools::CoEdgeRefsOfWire(myGraph, aWireId); + ASSERT_GT(aWireRefsBefore.Length(), 0); + + BRepGraph_CoEdgeRefId aRefToRemove; + int aRemovedOrd = -1; + for (int aRefOrd = 0; aRefOrd < aWireRefsBefore.Length(); ++aRefOrd) + { + const BRepGraph_CoEdgeRefId aRefId = aWireRefsBefore.Value(aRefOrd); + const BRepGraphInc::CoEdgeRef& aRef = myGraph.Refs().CoEdges().Entry(aRefId); + const BRepGraphInc::CoEdgeDef& aCoEdge = myGraph.Topo().CoEdges().Definition(aRef.CoEdgeDefId); + if (aCoEdge.EdgeDefId == BRepGraph_EdgeId(anEdgeId.Index)) + { + aRefToRemove = aRefId; + aRemovedOrd = aRefOrd; + break; + } + } + ASSERT_TRUE(aRefToRemove.IsValid(myGraph.Refs().CoEdges().Nb())); + ASSERT_GE(aRemovedOrd, 0); + + { + BRepGraph_MutGuard aMut = myGraph.Builder().MutCoEdgeRef(aRefToRemove); + aMut->IsRemoved = true; + } + ASSERT_TRUE(myGraph.Refs().CoEdges().Entry(aRefToRemove).IsRemoved); + const BRepGraph_CoEdgeId aRemovedCoEdgeId = + myGraph.Refs().CoEdges().Entry(aRefToRemove).CoEdgeDefId; + + const int aRemovedWireNbActiveBefore = + BRepGraph_TestTools::CountCoEdgeRefsOfWire(myGraph, aWireId); + + const BRepGraph_VertexId aSplitVertex = + myGraph.Builder().AddVertex(gp_Pnt(4.0, 5.0, 6.0), 1.0e-7); + ASSERT_TRUE(aSplitVertex.IsValid()); + + BRepGraph_EdgeId aSubA; + BRepGraph_EdgeId aSubB; + myGraph.Builder().SplitEdge(anEdgeId, aSplitVertex, aSplitParam, aSubA, aSubB); + ASSERT_TRUE(aSubA.IsValid()); + ASSERT_TRUE(aSubB.IsValid()); + + EXPECT_TRUE(myGraph.Refs().CoEdges().Entry(aRefToRemove).IsRemoved); + EXPECT_EQ(BRepGraph_TestTools::CountCoEdgeRefsOfWire(myGraph, aWireId), + aRemovedWireNbActiveBefore); + const BRepGraphInc::CoEdgeDef& aRemovedCoEdgeAfter = + myGraph.Topo().CoEdges().Definition(aRemovedCoEdgeId); + EXPECT_EQ(aRemovedCoEdgeAfter.EdgeDefId, BRepGraph_EdgeId(anEdgeId.Index)); + + bool hasSubA = false; + bool hasSubB = false; + const int aNbCoEdgeRefs = myGraph.Refs().CoEdges().Nb(); + for (BRepGraph_CoEdgeRefId aRefId(0); aRefId.IsValid(aNbCoEdgeRefs); ++aRefId) + { + const BRepGraphInc::CoEdgeRef& aRef = myGraph.Refs().CoEdges().Entry(aRefId); + if (aRef.IsRemoved || !aRef.CoEdgeDefId.IsValid(myGraph.Topo().CoEdges().Nb())) + continue; + const BRepGraph_NodeId anId(myGraph.Topo().CoEdges().Definition(aRef.CoEdgeDefId).EdgeDefId); + if (anId == aSubA) + hasSubA = true; + if (anId == aSubB) + hasSubB = true; + } + EXPECT_TRUE(hasSubA); + EXPECT_TRUE(hasSubB); +} + +TEST_F(BRepGraph_HistoryTest, ApplyModification_SplitEdge_RecordsBothDerivedNodes) +{ + ASSERT_GT(myGraph.Topo().Edges().Nb(), 0); + + const BRepGraph_EdgeId anEdgeId(0); + const BRepGraphInc::EdgeDef& anEdgeDef = + myGraph.Topo().Edges().Definition(BRepGraph_EdgeId(anEdgeId.Index)); + const double aSplitParam = 0.5 * (anEdgeDef.ParamFirst + anEdgeDef.ParamLast); + + const BRepGraph_VertexId aSplitVertex = + myGraph.Builder().AddVertex(gp_Pnt(4.0, 5.0, 6.0), 1.0e-7); + ASSERT_TRUE(aSplitVertex.IsValid()); + + const int aNbRecordsBefore = myGraph.History().NbRecords(); + + myGraph.Builder().ApplyModification( + anEdgeId, + [&](BRepGraph& theGraph, BRepGraph_NodeId theTarget) -> NCollection_Vector { + BRepGraph_EdgeId aSubA; + BRepGraph_EdgeId aSubB; + theGraph.Builder().SplitEdge(BRepGraph_EdgeId::FromNodeId(theTarget), + aSplitVertex, + aSplitParam, + aSubA, + aSubB); + + NCollection_Vector aResult; + aResult.Append(aSubA); + aResult.Append(aSubB); + return aResult; + }, + "SplitEdge"); + + EXPECT_EQ(myGraph.History().NbRecords(), aNbRecordsBefore + 1); + + const NCollection_Vector aDerived = myGraph.History().FindDerived(anEdgeId); + EXPECT_GE(aDerived.Length(), 2); +} diff --git a/src/ModelingData/TKBRep/GTests/BRepGraph_MutationGen_Test.cxx b/src/ModelingData/TKBRep/GTests/BRepGraph_MutationGen_Test.cxx new file mode 100644 index 0000000000..2af434dffc --- /dev/null +++ b/src/ModelingData/TKBRep/GTests/BRepGraph_MutationGen_Test.cxx @@ -0,0 +1,275 @@ +// Copyright (c) 2026 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 + +class BRepGraph_MutationGenTest : public testing::Test +{ +protected: + void SetUp() override + { + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + myGraph.Build(aBox); + ASSERT_TRUE(myGraph.IsDone()); + } + + BRepGraph myGraph; +}; + +TEST_F(BRepGraph_MutationGenTest, OwnGen_IncrementedOnMutation) +{ + EXPECT_EQ(myGraph.Topo().Edges().Definition(BRepGraph_EdgeId(0)).OwnGen, 0u); + EXPECT_EQ(myGraph.Topo().Edges().Definition(BRepGraph_EdgeId(0)).SubtreeGen, 0u); + + myGraph.Builder().MutEdge(BRepGraph_EdgeId(0))->Tolerance = 0.5; + + EXPECT_EQ(myGraph.Topo().Edges().Definition(BRepGraph_EdgeId(0)).OwnGen, 1u); + EXPECT_EQ(myGraph.Topo().Edges().Definition(BRepGraph_EdgeId(0)).SubtreeGen, 1u); +} + +TEST_F(BRepGraph_MutationGenTest, OwnGen_MultipleIncrements) +{ + myGraph.Builder().MutEdge(BRepGraph_EdgeId(0))->Tolerance = 0.1; + myGraph.Builder().MutEdge(BRepGraph_EdgeId(0))->Tolerance = 0.2; + + EXPECT_EQ(myGraph.Topo().Edges().Definition(BRepGraph_EdgeId(0)).OwnGen, 2u); +} + +TEST_F(BRepGraph_MutationGenTest, OwnGen_DeferredMode) +{ + myGraph.Builder().BeginDeferredInvalidation(); + myGraph.Builder().MutEdge(BRepGraph_EdgeId(0))->Tolerance = 0.5; + + // OwnGen is incremented even in deferred mode. + EXPECT_EQ(myGraph.Topo().Edges().Definition(BRepGraph_EdgeId(0)).OwnGen, 1u); + + myGraph.Builder().EndDeferredInvalidation(); + + // Still 1 after flush - flush doesn't re-increment. + EXPECT_EQ(myGraph.Topo().Edges().Definition(BRepGraph_EdgeId(0)).OwnGen, 1u); +} + +TEST_F(BRepGraph_MutationGenTest, SubtreeGen_PropagatedParent_Incremented) +{ + // Mutate an edge - parent wire/face/shell/solid get SubtreeGen incremented + // via propagation, enabling generation-based cache freshness on parents. + // Parent OwnGen must NOT change (only the edge itself was directly mutated). + myGraph.Builder().MutEdge(BRepGraph_EdgeId(0))->Tolerance = 0.5; + + EXPECT_EQ(myGraph.Topo().Edges().Definition(BRepGraph_EdgeId(0)).OwnGen, 1u); + EXPECT_EQ(myGraph.Topo().Edges().Definition(BRepGraph_EdgeId(0)).SubtreeGen, 1u); + + // At least one parent in each level must have SubtreeGen incremented, + // but OwnGen must remain 0 (parent's own data was not touched). + bool aAnyWireSubtreeIncremented = false; + for (BRepGraph_WireIterator aWireIt(myGraph); aWireIt.More(); aWireIt.Next()) + { + if (aWireIt.Current().SubtreeGen > 0u) + aAnyWireSubtreeIncremented = true; + } + EXPECT_TRUE(aAnyWireSubtreeIncremented); + + // Verify parent OwnGen did NOT change (split behavior). + for (BRepGraph_WireIterator aWireIt(myGraph); aWireIt.More(); aWireIt.Next()) + EXPECT_EQ(aWireIt.Current().OwnGen, 0u); + + bool aAnyFaceSubtreeIncremented = false; + for (BRepGraph_FaceIterator aFaceIt(myGraph); aFaceIt.More(); aFaceIt.Next()) + { + if (aFaceIt.Current().SubtreeGen > 0u) + aAnyFaceSubtreeIncremented = true; + } + EXPECT_TRUE(aAnyFaceSubtreeIncremented); + + for (BRepGraph_FaceIterator aFaceIt(myGraph); aFaceIt.More(); aFaceIt.Next()) + EXPECT_EQ(aFaceIt.Current().OwnGen, 0u); + + bool aAnyShellSubtreeIncremented = false; + for (BRepGraph_ShellIterator aShellIt(myGraph); aShellIt.More(); aShellIt.Next()) + { + if (aShellIt.Current().SubtreeGen > 0u) + aAnyShellSubtreeIncremented = true; + } + EXPECT_TRUE(aAnyShellSubtreeIncremented); + + for (BRepGraph_ShellIterator aShellIt(myGraph); aShellIt.More(); aShellIt.Next()) + EXPECT_EQ(aShellIt.Current().OwnGen, 0u); + + bool aAnySolidSubtreeIncremented = false; + for (BRepGraph_SolidIterator aSolidIt(myGraph); aSolidIt.More(); aSolidIt.Next()) + { + if (aSolidIt.Current().SubtreeGen > 0u) + aAnySolidSubtreeIncremented = true; + } + EXPECT_TRUE(aAnySolidSubtreeIncremented); + + for (BRepGraph_SolidIterator aSolidIt(myGraph); aSolidIt.More(); aSolidIt.Next()) + EXPECT_EQ(aSolidIt.Current().OwnGen, 0u); +} + +TEST_F(BRepGraph_MutationGenTest, SubtreeGen_DeferredPropagatedParent_Incremented) +{ + // Store baselines before mutation. + const uint32_t aEdgeOwnGenBefore = myGraph.Topo().Edges().Definition(BRepGraph_EdgeId(0)).OwnGen; + NCollection_Vector aWireSubtreeGensBefore; + for (BRepGraph_WireIterator aWireIt(myGraph); aWireIt.More(); aWireIt.Next()) + aWireSubtreeGensBefore.Append(aWireIt.Current().SubtreeGen); + NCollection_Vector aFaceSubtreeGensBefore; + for (BRepGraph_FaceIterator aFaceIt(myGraph); aFaceIt.More(); aFaceIt.Next()) + aFaceSubtreeGensBefore.Append(aFaceIt.Current().SubtreeGen); + + // Deferred mutation + flush. + myGraph.Builder().BeginDeferredInvalidation(); + myGraph.Builder().MutEdge(BRepGraph_EdgeId(0))->Tolerance = 0.5; + myGraph.Builder().EndDeferredInvalidation(); + + // Directly mutated edge: OwnGen incremented by exactly 1. + EXPECT_EQ(myGraph.Topo().Edges().Definition(BRepGraph_EdgeId(0)).OwnGen, aEdgeOwnGenBefore + 1); + + // At least one parent wire must have SubtreeGen incremented vs its baseline. + bool aAnyWireSubtreeIncremented = false; + for (BRepGraph_WireIterator aWireIt(myGraph); aWireIt.More(); aWireIt.Next()) + if (aWireIt.Current().SubtreeGen > aWireSubtreeGensBefore.Value(aWireIt.CurrentId().Index)) + aAnyWireSubtreeIncremented = true; + EXPECT_TRUE(aAnyWireSubtreeIncremented); + + // At least one parent face must have SubtreeGen incremented vs its baseline. + bool aAnyFaceSubtreeIncremented = false; + for (BRepGraph_FaceIterator aFaceIt(myGraph); aFaceIt.More(); aFaceIt.Next()) + if (aFaceIt.Current().SubtreeGen > aFaceSubtreeGensBefore.Value(aFaceIt.CurrentId().Index)) + aAnyFaceSubtreeIncremented = true; + EXPECT_TRUE(aAnyFaceSubtreeIncremented); +} + +TEST_F(BRepGraph_MutationGenTest, RepMutation_SurfacePropagatesSubtreeGenToFace) +{ + const BRepGraph_FaceId aFaceId(0); + const BRepGraph_SurfaceRepId aSurfId = myGraph.Topo().Faces().Definition(aFaceId).SurfaceRepId; + ASSERT_TRUE(aSurfId.IsValid()); + EXPECT_EQ(myGraph.Topo().Faces().Definition(aFaceId).OwnGen, 0u); + EXPECT_EQ(myGraph.Topo().Faces().Definition(aFaceId).SubtreeGen, 0u); + + { + BRepGraph_MutGuard aGuard = myGraph.Builder().MutSurface(aSurfId); + (void)aGuard; + } + + // Surface is the face's own geometry - rep mutation IS an own-data change. + EXPECT_GT(myGraph.Topo().Faces().Definition(aFaceId).OwnGen, 0u); + EXPECT_GT(myGraph.Topo().Faces().Definition(aFaceId).SubtreeGen, 0u); +} + +TEST_F(BRepGraph_MutationGenTest, RepMutation_Curve3DPropagatesSubtreeGenToEdge) +{ + const BRepGraph_EdgeId anEdgeId(0); + const BRepGraph_Curve3DRepId aCurveId = myGraph.Topo().Edges().Definition(anEdgeId).Curve3DRepId; + if (!aCurveId.IsValid()) + return; // Skip degenerate edges without 3D curves. + + EXPECT_EQ(myGraph.Topo().Edges().Definition(anEdgeId).OwnGen, 0u); + EXPECT_EQ(myGraph.Topo().Edges().Definition(anEdgeId).SubtreeGen, 0u); + + { + BRepGraph_MutGuard aGuard = myGraph.Builder().MutCurve3D(aCurveId); + (void)aGuard; + } + + // Curve3D is the edge's own geometry - rep mutation IS an own-data change. + EXPECT_GT(myGraph.Topo().Edges().Definition(anEdgeId).OwnGen, 0u); + EXPECT_GT(myGraph.Topo().Edges().Definition(anEdgeId).SubtreeGen, 0u); +} + +TEST_F(BRepGraph_MutationGenTest, RepMutation_Curve2DPropagatesSubtreeGenToCoEdge) +{ + // Find a coedge with a valid PCurve. + for (BRepGraph_CoEdgeIterator aCoEdgeIt(myGraph); aCoEdgeIt.More(); aCoEdgeIt.Next()) + { + const BRepGraph_Curve2DRepId aCurveId = aCoEdgeIt.Current().Curve2DRepId; + if (!aCurveId.IsValid()) + continue; + + const BRepGraph_CoEdgeId aCoEdgeId = aCoEdgeIt.CurrentId(); + EXPECT_EQ(aCoEdgeIt.Current().OwnGen, 0u); + EXPECT_EQ(aCoEdgeIt.Current().SubtreeGen, 0u); + + { + BRepGraph_MutGuard aGuard = myGraph.Builder().MutCurve2D(aCurveId); + (void)aGuard; + } + + // Curve2D is the coedge's own geometry - rep mutation IS an own-data change. + EXPECT_GT(myGraph.Topo().CoEdges().Definition(aCoEdgeId).OwnGen, 0u); + EXPECT_GT(myGraph.Topo().CoEdges().Definition(aCoEdgeId).SubtreeGen, 0u); + return; + } +} + +TEST_F(BRepGraph_MutationGenTest, RepMutation_TriangulationPropagatesSubtreeGenToFace) +{ + // Find a face with a valid triangulation. + for (BRepGraph_FaceIterator aFaceIt(myGraph); aFaceIt.More(); aFaceIt.Next()) + { + const BRepGraphInc::FaceDef& aFace = aFaceIt.Current(); + if (aFace.TriangulationRepIds.IsEmpty()) + continue; + + const BRepGraph_FaceId aFaceId = aFaceIt.CurrentId(); + const BRepGraph_TriangulationRepId aTriId = aFace.TriangulationRepIds.Value(0); + EXPECT_EQ(aFace.OwnGen, 0u); + EXPECT_EQ(aFace.SubtreeGen, 0u); + + { + BRepGraph_MutGuard aGuard = + myGraph.Builder().MutTriangulation(aTriId); + (void)aGuard; + } + + // Triangulation is the face's own mesh - rep mutation IS an own-data change. + EXPECT_GT(myGraph.Topo().Faces().Definition(aFaceId).OwnGen, 0u); + EXPECT_GT(myGraph.Topo().Faces().Definition(aFaceId).SubtreeGen, 0u); + return; + } +} + +TEST_F(BRepGraph_MutationGenTest, RepMutation_Polygon3DPropagatesSubtreeGenToEdge) +{ + // Find an edge with a valid Polygon3D. + for (BRepGraph_EdgeIterator anEdgeIt(myGraph); anEdgeIt.More(); anEdgeIt.Next()) + { + const BRepGraph_Polygon3DRepId aPolyId = anEdgeIt.Current().Polygon3DRepId; + if (!aPolyId.IsValid()) + continue; + + const BRepGraph_EdgeId anEdgeId = anEdgeIt.CurrentId(); + EXPECT_EQ(anEdgeIt.Current().OwnGen, 0u); + EXPECT_EQ(anEdgeIt.Current().SubtreeGen, 0u); + + { + BRepGraph_MutGuard aGuard = + myGraph.Builder().MutPolygon3D(aPolyId); + (void)aGuard; + } + + // Polygon3D is the edge's own mesh - rep mutation IS an own-data change. + EXPECT_GT(myGraph.Topo().Edges().Definition(anEdgeId).OwnGen, 0u); + EXPECT_GT(myGraph.Topo().Edges().Definition(anEdgeId).SubtreeGen, 0u); + return; + } +} diff --git a/src/ModelingData/TKBRep/GTests/BRepGraph_NodeId_Test.cxx b/src/ModelingData/TKBRep/GTests/BRepGraph_NodeId_Test.cxx new file mode 100644 index 0000000000..1ee4ec41aa --- /dev/null +++ b/src/ModelingData/TKBRep/GTests/BRepGraph_NodeId_Test.cxx @@ -0,0 +1,173 @@ +// Copyright (c) 2026 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 + +// ============ TypedNodeId tests ============ + +TEST(BRepGraph_NodeIdTest, Construction_DefaultInvalid) +{ + BRepGraph_FaceId aFace; + EXPECT_FALSE(aFace.IsValid()); + EXPECT_EQ(aFace.Index, -1); +} + +TEST(BRepGraph_NodeIdTest, Construction_FromIndex) +{ + BRepGraph_FaceId aFace(5); + EXPECT_TRUE(aFace.IsValid()); + EXPECT_EQ(aFace.Index, 5); +} + +TEST(BRepGraph_NodeIdTest, TypedAliases_ConstructFromIndex) +{ + BRepGraph_FaceId aFace = BRepGraph_FaceId(3); + EXPECT_TRUE(aFace.IsValid()); + EXPECT_EQ(aFace.Index, 3); + + BRepGraph_EdgeId anEdge = BRepGraph_EdgeId(7); + EXPECT_EQ(anEdge.Index, 7); +} + +TEST(BRepGraph_NodeIdTest, ImplicitConversion_ToNodeId) +{ + BRepGraph_FaceId aFace(5); + + // Implicit conversion to BRepGraph_NodeId. + BRepGraph_NodeId aNodeId = aFace; + EXPECT_EQ(aNodeId.NodeKind, BRepGraph_NodeId::Kind::Face); + EXPECT_EQ(aNodeId.Index, 5); +} + +TEST(BRepGraph_NodeIdTest, ImplicitConversion_PassToFunction) +{ + // Typed ids work with existing APIs that take BRepGraph_NodeId. + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10, 20, 30).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + BRepGraph_FaceId aFace(0); + // AdjacentFaces takes BRepGraph_FaceId - typed id works directly. + NCollection_Vector aAdj = + aGraph.Topo().Faces().Adjacent(aFace, aGraph.Allocator()); + EXPECT_GT(aAdj.Length(), 0); +} + +TEST(BRepGraph_NodeIdTest, FromNodeId_CorrectKind) +{ + BRepGraph_NodeId aNodeId = BRepGraph_NodeId(BRepGraph_NodeId::Kind::Edge, 3); + BRepGraph_EdgeId anEdge = BRepGraph_EdgeId::FromNodeId(aNodeId); + EXPECT_EQ(anEdge.Index, 3); +} + +TEST(BRepGraph_NodeIdTest, Comparison_TypedVsTyped) +{ + BRepGraph_FaceId aFace1(3); + BRepGraph_FaceId aFace2(3); + BRepGraph_FaceId aFace3(5); + + EXPECT_EQ(aFace1, aFace2); + EXPECT_NE(aFace1, aFace3); +} + +TEST(BRepGraph_NodeIdTest, Comparison_TypedVsNodeId) +{ + BRepGraph_FaceId aFace(3); + BRepGraph_NodeId aMatch(BRepGraph_NodeId::Kind::Face, 3); + BRepGraph_NodeId aDiffKind(BRepGraph_NodeId::Kind::Edge, 3); + BRepGraph_NodeId aDiffIdx(BRepGraph_NodeId::Kind::Face, 5); + + EXPECT_EQ(aFace, aMatch); + EXPECT_EQ(aMatch, aFace); + EXPECT_NE(aFace, aDiffKind); + EXPECT_NE(aFace, aDiffIdx); +} + +TEST(BRepGraph_NodeIdTest, Hash_ConsistentWithNodeId) +{ + BRepGraph_FaceId aFace(5); + BRepGraph_NodeId aNodeId = aFace; + + std::hash aTypedHash; + std::hash aNodeHash; + + EXPECT_EQ(aTypedHash(aFace), aNodeHash(aNodeId)); +} + +TEST(BRepGraph_NodeIdTest, UntypedArithmetic_PreservesKindAndIndex) +{ + BRepGraph_NodeId aNodeId(BRepGraph_NodeId::Kind::Face, 2); + + const BRepGraph_NodeId aPrev = aNodeId++; + EXPECT_EQ(aPrev.NodeKind, BRepGraph_NodeId::Kind::Face); + EXPECT_EQ(aPrev.Index, 2); + EXPECT_EQ(aNodeId.Index, 3); + + ++aNodeId; + EXPECT_EQ(aNodeId.Index, 4); + + const BRepGraph_NodeId anAdvanced = aNodeId + 3; + EXPECT_EQ(anAdvanced.NodeKind, BRepGraph_NodeId::Kind::Face); + EXPECT_EQ(anAdvanced.Index, 7); + + const BRepGraph_NodeId aRetreated = anAdvanced - 5; + EXPECT_EQ(aRetreated.NodeKind, BRepGraph_NodeId::Kind::Face); + EXPECT_EQ(aRetreated.Index, 2); +} + +TEST(BRepGraph_NodeIdTest, TypedArithmetic_PreservesKindAndIndex) +{ + BRepGraph_FaceId aFaceId(2); + + const BRepGraph_FaceId aPrev = aFaceId++; + EXPECT_EQ(aPrev.Index, 2); + EXPECT_EQ(aFaceId.Index, 3); + + ++aFaceId; + EXPECT_EQ(aFaceId.Index, 4); + + const BRepGraph_FaceId anAdvanced = aFaceId + 3; + EXPECT_EQ(anAdvanced.Index, 7); + + const BRepGraph_FaceId aRetreated = anAdvanced - 5; + EXPECT_EQ(aRetreated.Index, 2); + + // Verify kind is preserved through implicit conversion. + const BRepGraph_NodeId aNodeId = anAdvanced; + EXPECT_EQ(aNodeId.NodeKind, BRepGraph_NodeId::Kind::Face); + EXPECT_EQ(aNodeId.Index, 7); +} + +TEST(BRepGraph_NodeIdTest, TypedArithmetic_IndexZeroBoundary) +{ + BRepGraph_EdgeId anEdge(0); + EXPECT_TRUE(anEdge.IsValid()); + + // Increment from zero. + ++anEdge; + EXPECT_EQ(anEdge.Index, 1); + + // Retreat back to zero still valid. + const BRepGraph_EdgeId aZero = anEdge - 1; + EXPECT_EQ(aZero.Index, 0); + EXPECT_TRUE(aZero.IsValid()); + + // Subtract to -1 produces invalid id (allowed by constructor). + const BRepGraph_EdgeId anInvalid = aZero - 1; + EXPECT_EQ(anInvalid.Index, -1); + EXPECT_FALSE(anInvalid.IsValid()); +} diff --git a/src/ModelingData/TKBRep/GTests/BRepGraph_ParentExplorer_Test.cxx b/src/ModelingData/TKBRep/GTests/BRepGraph_ParentExplorer_Test.cxx new file mode 100644 index 0000000000..d55fe5ca5a --- /dev/null +++ b/src/ModelingData/TKBRep/GTests/BRepGraph_ParentExplorer_Test.cxx @@ -0,0 +1,242 @@ +// Copyright (c) 2026 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 + +TEST(BRepGraph_ParentExplorerTest, FaceParents_All_CountAndOrder) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10, 20, 30).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + BRepGraph_ParentExplorer anExp(aGraph, BRepGraph_FaceId(0)); + ASSERT_TRUE(anExp.More()); + EXPECT_EQ(anExp.Current().DefId, BRepGraph_NodeId(BRepGraph_ShellId(0))); + + anExp.Next(); + ASSERT_TRUE(anExp.More()); + EXPECT_EQ(anExp.Current().DefId, BRepGraph_NodeId(BRepGraph_SolidId(0))); + + anExp.Next(); + ASSERT_TRUE(anExp.More()); + EXPECT_EQ(anExp.Current().DefId.NodeKind, BRepGraph_NodeId::Kind::Product); + + anExp.Next(); + EXPECT_FALSE(anExp.More()); +} + +TEST(BRepGraph_ParentExplorerTest, FaceParents_TypedSolid_OneResult) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10, 20, 30).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + BRepGraph_ParentExplorer anExp(aGraph, BRepGraph_FaceId(0), BRepGraph_NodeId::Kind::Solid); + ASSERT_TRUE(anExp.More()); + EXPECT_EQ(anExp.Current().DefId, BRepGraph_NodeId(BRepGraph_SolidId(0))); + + anExp.Next(); + EXPECT_FALSE(anExp.More()); +} + +TEST(BRepGraph_ParentExplorerTest, FaceParents_DirectParents_StopsAtImmediateShell) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10, 20, 30).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + BRepGraph_ParentExplorer anExp(aGraph, + BRepGraph_FaceId(0), + BRepGraph_ParentExplorer::TraversalMode::DirectParents); + ASSERT_TRUE(anExp.More()); + EXPECT_EQ(anExp.Current().DefId, BRepGraph_NodeId(BRepGraph_ShellId(0))); + + anExp.Next(); + EXPECT_FALSE(anExp.More()); +} + +TEST(BRepGraph_ParentExplorerTest, AvoidKind_Solid_PrunesProducts) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10, 20, 30).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + BRepGraph_ParentExplorer anExp(aGraph, + BRepGraph_FaceId(0), + BRepGraph_NodeId::Kind::Product, + BRepGraph_NodeId::Kind::Solid, + false); + EXPECT_FALSE(anExp.More()); +} + +TEST(BRepGraph_ParentExplorerTest, AvoidKind_EmitBoundary_ReturnsSolidInsteadOfProducts) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10, 20, 30).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + BRepGraph_ParentExplorer anExp(aGraph, + BRepGraph_FaceId(0), + BRepGraph_NodeId::Kind::Product, + BRepGraph_NodeId::Kind::Solid, + true); + ASSERT_TRUE(anExp.More()); + EXPECT_EQ(anExp.Current().DefId, BRepGraph_NodeId(BRepGraph_SolidId(0))); + + anExp.Next(); + EXPECT_FALSE(anExp.More()); +} + +TEST(BRepGraph_ParentExplorerTest, AvoidKind_SameAsTarget_IsIgnored) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10, 20, 30).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + BRepGraph_ParentExplorer anExp(aGraph, + BRepGraph_FaceId(0), + BRepGraph_NodeId::Kind::Solid, + BRepGraph_NodeId::Kind::Solid, + false); + ASSERT_TRUE(anExp.More()); + EXPECT_EQ(anExp.Current().DefId, BRepGraph_NodeId(BRepGraph_SolidId(0))); + + anExp.Next(); + EXPECT_FALSE(anExp.More()); +} + +TEST(BRepGraph_ParentExplorerTest, AllParents_AvoidSolid_PrunesProducts) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10, 20, 30).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + BRepGraph_ParentExplorer anExp(aGraph, BRepGraph_FaceId(0), BRepGraph_NodeId::Kind::Solid, false); + ASSERT_TRUE(anExp.More()); + EXPECT_EQ(anExp.Current().DefId, BRepGraph_NodeId(BRepGraph_ShellId(0))); + + anExp.Next(); + EXPECT_FALSE(anExp.More()); +} + +TEST(BRepGraph_ParentExplorerTest, AllParents_AvoidSolidEmitBoundary_ReturnsShellAndSolid) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10, 20, 30).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + BRepGraph_ParentExplorer anExp(aGraph, BRepGraph_FaceId(0), BRepGraph_NodeId::Kind::Solid, true); + ASSERT_TRUE(anExp.More()); + EXPECT_EQ(anExp.Current().DefId, BRepGraph_NodeId(BRepGraph_ShellId(0))); + + anExp.Next(); + ASSERT_TRUE(anExp.More()); + EXPECT_EQ(anExp.Current().DefId, BRepGraph_NodeId(BRepGraph_SolidId(0))); + + anExp.Next(); + EXPECT_FALSE(anExp.More()); +} + +TEST(BRepGraph_ParentExplorerTest, SharedProduct_ProductParentsKeepDistinctContexts) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10, 20, 30).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + const BRepGraph_ProductId aPart = aGraph.Builder().AddProduct(BRepGraph_SolidId(0)); + const BRepGraph_ProductId anAssembly = aGraph.Builder().AddAssemblyProduct(); + ASSERT_TRUE(aPart.IsValid()); + ASSERT_TRUE(anAssembly.IsValid()); + + gp_Trsf aT1; + aT1.SetTranslation(gp_Vec(10.0, 0.0, 0.0)); + gp_Trsf aT2; + aT2.SetTranslation(gp_Vec(25.0, 0.0, 0.0)); + ASSERT_TRUE(aGraph.Builder().AddOccurrence(anAssembly, aPart, TopLoc_Location(aT1)).IsValid()); + ASSERT_TRUE(aGraph.Builder().AddOccurrence(anAssembly, aPart, TopLoc_Location(aT2)).IsValid()); + + int aPartCount = 0; + TopLoc_Location aLoc1; + TopLoc_Location aLoc2; + for (BRepGraph_ParentExplorer anExp(aGraph, + BRepGraph_SolidId(0), + BRepGraph_NodeId::Kind::Product); + anExp.More(); + anExp.Next()) + { + if (anExp.Current().DefId != BRepGraph_NodeId(aPart)) + { + continue; + } + if (aPartCount == 0) + { + aLoc1 = anExp.Current().Location; + } + else if (aPartCount == 1) + { + aLoc2 = anExp.Current().Location; + } + ++aPartCount; + } + + ASSERT_EQ(aPartCount, 2); + EXPECT_FALSE(aLoc1.IsEqual(aLoc2)); +} + +TEST(BRepGraph_ParentExplorerTest, CoEdgeParents_ImmediateWireIsVisible) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10, 20, 30).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + const NCollection_Vector& aWireRefIds = + aGraph.Refs().Wires().IdsOf(BRepGraph_FaceId(0)); + ASSERT_GT(aWireRefIds.Length(), 0); + const BRepGraph_WireId aWireId = aGraph.Refs().Wires().Entry(aWireRefIds.Value(0)).WireDefId; + + const NCollection_Vector& aCoEdgeRefIds = + aGraph.Refs().CoEdges().IdsOf(aWireId); + ASSERT_GT(aCoEdgeRefIds.Length(), 0); + const BRepGraph_CoEdgeId aCoEdgeId = + aGraph.Refs().CoEdges().Entry(aCoEdgeRefIds.Value(0)).CoEdgeDefId; + + BRepGraph_ParentExplorer anExp(aGraph, aCoEdgeId); + ASSERT_TRUE(anExp.More()); + EXPECT_EQ(anExp.Current().DefId, BRepGraph_NodeId(aWireId)); + + anExp.Next(); + ASSERT_TRUE(anExp.More()); + EXPECT_EQ(anExp.Current().DefId, BRepGraph_NodeId(BRepGraph_FaceId(0))); +} + +TEST(BRepGraph_ParentExplorerTest, ProductRoot_HasNoParents) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10, 20, 30).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + const BRepGraph_ProductId aRootProduct = aGraph.Builder().AddAssemblyProduct(); + ASSERT_TRUE(aRootProduct.IsValid()); + + BRepGraph_ParentExplorer anExp(aGraph, aRootProduct); + EXPECT_FALSE(anExp.More()); +} \ No newline at end of file diff --git a/src/ModelingData/TKBRep/GTests/BRepGraph_Polygon_Test.cxx b/src/ModelingData/TKBRep/GTests/BRepGraph_Polygon_Test.cxx new file mode 100644 index 0000000000..719029ff93 --- /dev/null +++ b/src/ModelingData/TKBRep/GTests/BRepGraph_Polygon_Test.cxx @@ -0,0 +1,459 @@ +// Copyright (c) 2026 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 + +static void registerStandardLayers(BRepGraph& theGraph) +{ + theGraph.LayerRegistry().RegisterLayer(new BRepGraph_ParamLayer()); + theGraph.LayerRegistry().RegisterLayer(new BRepGraph_RegularityLayer()); +} + +// ============================================================ +// Multi-Triangulation roundtrip +// ============================================================ + +TEST(BRepGraph_PolygonTest, MultiTriangulation_Roundtrip_PreservesAll) +{ + // Build a meshed box. + TopoDS_Shape aBox = BRepPrimAPI_MakeBox(10., 20., 30.).Shape(); + BRepMesh_IncrementalMesh aMesher(aBox, 0.5); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + + // Verify triangulations were captured on face definitions. + bool aHasTriangulations = false; + for (BRepGraph_FaceIterator aFaceIt(aGraph); aFaceIt.More(); aFaceIt.Next()) + { + const BRepGraphInc::FaceDef& aFaceDef = aFaceIt.Current(); + if (!aFaceDef.TriangulationRepIds.IsEmpty()) + { + aHasTriangulations = true; + EXPECT_GE(aFaceDef.ActiveTriangulationIndex, 0) + << "Active triangulation index should be valid for meshed face"; + EXPECT_LT(aFaceDef.ActiveTriangulationIndex, aFaceDef.TriangulationRepIds.Length()); + const BRepGraph_TriangulationRepId anActiveRepId = aFaceDef.ActiveTriangulationRepId(); + EXPECT_TRUE(anActiveRepId.IsValid()); + EXPECT_FALSE(BRepGraph_Tool::Face::Triangulation(aGraph, aFaceIt.CurrentId()).IsNull()); + } + } + EXPECT_TRUE(aHasTriangulations) << "Meshed box should have triangulations"; + + // Reconstruct and verify triangulations are preserved. + for (BRepGraph_FaceIterator aFaceIt(aGraph); aFaceIt.More(); aFaceIt.Next()) + { + const BRepGraphInc::FaceDef& aFaceDef = aFaceIt.Current(); + TopoDS_Shape aReconFace = aGraph.Shapes().Reconstruct(aFaceDef.Id); + ASSERT_FALSE(aReconFace.IsNull()); + + TopLoc_Location aLoc; + const occ::handle aReconTri = + BRep_Tool::Triangulation(TopoDS::Face(aReconFace), aLoc); + EXPECT_FALSE(aReconTri.IsNull()) + << "Reconstructed face " << aFaceIt.CurrentId().Index << " should have triangulation"; + } +} + +// ============================================================ +// Polygon3D roundtrip +// ============================================================ + +TEST(BRepGraph_PolygonTest, Polygon3D_Captured_WhenPresent) +{ + // Polygon3D is not produced by BRepMesh_IncrementalMesh, so a bare box won't have them. + // Verify the graph correctly reports no Polygon3D for unmeshed/standard-meshed edges, + // and that the data model is structurally sound. + TopoDS_Shape aBox = BRepPrimAPI_MakeBox(10., 20., 30.).Shape(); + BRepMesh_IncrementalMesh aMesher(aBox, 0.5); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + + // Count Polygon3D on edges - matches what BRep_Tool reports for the original shape. + int aNbPoly3DGraph = 0; + int aNbPoly3DOrig = 0; + for (BRepGraph_EdgeIterator anEdgeIt(aGraph); anEdgeIt.More(); anEdgeIt.Next()) + { + if (BRepGraph_Tool::Edge::HasPolygon3D(aGraph, anEdgeIt.CurrentId())) + ++aNbPoly3DGraph; + } + for (TopExp_Explorer anExp(aBox, TopAbs_EDGE); anExp.More(); anExp.Next()) + { + TopLoc_Location aLoc; + if (!BRep_Tool::Polygon3D(TopoDS::Edge(anExp.Current()), aLoc).IsNull()) + ++aNbPoly3DOrig; + } + EXPECT_EQ(aNbPoly3DGraph, aNbPoly3DOrig) + << "Graph Polygon3D count should match BRep_Tool::Polygon3D count"; + + // Verify Polygon3D roundtrip if present. + for (BRepGraph_EdgeIterator anEdgeIt(aGraph); anEdgeIt.More(); anEdgeIt.Next()) + { + if (!BRepGraph_Tool::Edge::HasPolygon3D(aGraph, anEdgeIt.CurrentId())) + continue; + const BRepGraphInc::EdgeDef& anEdge = anEdgeIt.Current(); + TopoDS_Shape aReconEdge = aGraph.Shapes().Reconstruct(anEdge.Id); + ASSERT_FALSE(aReconEdge.IsNull()); + TopLoc_Location aPolyLoc; + occ::handle aPoly = BRep_Tool::Polygon3D(TopoDS::Edge(aReconEdge), aPolyLoc); + EXPECT_FALSE(aPoly.IsNull()) << "Reconstructed edge " << anEdgeIt.CurrentId().Index + << " should have Polygon3D"; + } +} + +// ============================================================ +// PolygonOnTriangulation roundtrip +// ============================================================ + +TEST(BRepGraph_PolygonTest, PolyOnTri_Captured_AfterMesh) +{ + TopoDS_Shape aBox = BRepPrimAPI_MakeBox(10., 20., 30.).Shape(); + BRepMesh_IncrementalMesh aMesher(aBox, 0.5); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + + // Count PolygonOnTriangulation entries on coedges. + int aNbPolyOnTri = 0; + for (BRepGraph_EdgeIterator anEdgeIt(aGraph); anEdgeIt.More(); anEdgeIt.Next()) + { + const NCollection_Vector& aCoEdgeIdxs = + aGraph.Topo().Edges().CoEdges(anEdgeIt.CurrentId()); + for (const BRepGraph_CoEdgeId& aCoEdgeId : aCoEdgeIdxs) + { + const BRepGraphInc::CoEdgeDef& aCE = aGraph.Topo().CoEdges().Definition(aCoEdgeId); + aNbPolyOnTri += aCE.PolygonOnTriRepIds.Length(); + } + } + EXPECT_GT(aNbPolyOnTri, 0) << "Meshed box edges should have PolygonOnTriangulation"; + + // Verify PolyOnTri entries have valid context references. + for (BRepGraph_EdgeIterator anEdgeIt(aGraph); anEdgeIt.More(); anEdgeIt.Next()) + { + const NCollection_Vector& aCoEdgeIdxs = + aGraph.Topo().Edges().CoEdges(anEdgeIt.CurrentId()); + for (const BRepGraph_CoEdgeId& aCoEdgeId : aCoEdgeIdxs) + { + const BRepGraphInc::CoEdgeDef& aCE = aGraph.Topo().CoEdges().Definition(aCoEdgeId); + for (const BRepGraph_PolygonOnTriRepId& aPolyOnTriRepId : aCE.PolygonOnTriRepIds) + { + EXPECT_TRUE(aPolyOnTriRepId.IsValid()); + } + EXPECT_TRUE(aCE.FaceDefId.IsValid()); + } + } +} + +// ============================================================ +// PolygonOnTriangulation reconstruct roundtrip +// ============================================================ + +TEST(BRepGraph_PolygonTest, PolyOnTri_Roundtrip_PreservedOnReconstruct) +{ + TopoDS_Shape aBox = BRepPrimAPI_MakeBox(10., 20., 30.).Shape(); + BRepMesh_IncrementalMesh aMesher(aBox, 0.5); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + + // Reconstruct solid and verify polygon-on-triangulation is re-attached. + BRepGraph_NodeId aSolidDefId = BRepGraph_SolidId(0); + TopoDS_Shape aReconSolid = aGraph.Shapes().Reconstruct(aSolidDefId); + ASSERT_FALSE(aReconSolid.IsNull()); + + int aNbReconPolyOnTri = 0; + for (TopExp_Explorer anEdgeExp(aReconSolid, TopAbs_EDGE); anEdgeExp.More(); anEdgeExp.Next()) + { + const TopoDS_Edge& anEdge = TopoDS::Edge(anEdgeExp.Current()); + for (TopExp_Explorer aFaceExp(aReconSolid, TopAbs_FACE); aFaceExp.More(); aFaceExp.Next()) + { + const TopoDS_Face& aFace = TopoDS::Face(aFaceExp.Current()); + TopLoc_Location aTriLoc; + occ::handle aTri = BRep_Tool::Triangulation(aFace, aTriLoc); + if (aTri.IsNull()) + continue; + TopLoc_Location aPolyLoc; + occ::handle aPolyOnTri = + BRep_Tool::PolygonOnTriangulation(anEdge, aTri, aPolyLoc); + if (!aPolyOnTri.IsNull()) + ++aNbReconPolyOnTri; + } + } + + EXPECT_GT(aNbReconPolyOnTri, 0) + << "Reconstructed solid should have PolygonOnTriangulation on edges"; +} + +// ============================================================ +// UV Points on PCurves +// ============================================================ + +TEST(BRepGraph_PolygonTest, UVPoints_Captured_OnPCurves) +{ + TopoDS_Shape aBox = BRepPrimAPI_MakeBox(10., 20., 30.).Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + + // At least some CoEdge entries should have non-origin UV points. + int aNbNonOriginUV = 0; + for (BRepGraph_EdgeIterator anEdgeIt(aGraph); anEdgeIt.More(); anEdgeIt.Next()) + { + const NCollection_Vector& aCoEdgeIdxs = + aGraph.Topo().Edges().CoEdges(anEdgeIt.CurrentId()); + for (const BRepGraph_CoEdgeId& aCoEdgeId : aCoEdgeIdxs) + { + const BRepGraphInc::CoEdgeDef& aCE = aGraph.Topo().CoEdges().Definition(aCoEdgeId); + if (aCE.UV1.Distance(gp_Pnt2d(0, 0)) > Precision::Confusion() + || aCE.UV2.Distance(gp_Pnt2d(0, 0)) > Precision::Confusion()) + { + ++aNbNonOriginUV; + } + } + } + EXPECT_GT(aNbNonOriginUV, 0) << "Box CoEdges should have non-origin UV points"; +} + +// ============================================================ +// Vertex Point Representations +// ============================================================ + +TEST(BRepGraph_PolygonTest, VertexPointRepresentations_StructurallyValid) +{ + // Populate-time extraction reads explicit BRep point representations from vertices. + // Add one representation of each kind so the layer is exercised deterministically. + TopoDS_Shape aShape = BRepPrimAPI_MakeCylinder(5., 10.).Shape(); + BRep_Builder aBuilder; + + bool hasPointOnCurve = false; + for (TopExp_Explorer anEdgeExp(aShape, TopAbs_EDGE); anEdgeExp.More() && !hasPointOnCurve; + anEdgeExp.Next()) + { + const TopoDS_Edge& anEdge = TopoDS::Edge(anEdgeExp.Current()); + double aFirst = 0.0; + double aLast = 0.0; + TopLoc_Location aLoc; + if (BRep_Tool::Curve(anEdge, aLoc, aFirst, aLast).IsNull()) + continue; + + for (TopoDS_Iterator aVtxIt(anEdge, false, false); aVtxIt.More(); aVtxIt.Next()) + { + const TopoDS_Vertex& aVertex = TopoDS::Vertex(aVtxIt.Value()); + const double aParameter = aVertex.Orientation() == TopAbs_REVERSED ? aLast : aFirst; + aBuilder.UpdateVertex(aVertex, aParameter, anEdge, BRep_Tool::Tolerance(aVertex)); + hasPointOnCurve = true; + break; + } + } + + bool hasPointOnSurface = false; + for (TopExp_Explorer aFaceExp(aShape, TopAbs_FACE); aFaceExp.More() && !hasPointOnSurface; + aFaceExp.Next()) + { + const TopoDS_Face& aFace = TopoDS::Face(aFaceExp.Current()); + for (TopExp_Explorer aVtxExp(aFace, TopAbs_VERTEX); aVtxExp.More(); aVtxExp.Next()) + { + const TopoDS_Vertex& aVertex = TopoDS::Vertex(aVtxExp.Current()); + const gp_Pnt2d aUV = BRep_Tool::Parameters(aVertex, aFace); + aBuilder.UpdateVertex(aVertex, aUV.X(), aUV.Y(), aFace, BRep_Tool::Tolerance(aVertex)); + hasPointOnSurface = true; + break; + } + } + + bool hasPointOnPCurve = false; + for (TopExp_Explorer aFaceExp(aShape, TopAbs_FACE); aFaceExp.More() && !hasPointOnPCurve; + aFaceExp.Next()) + { + const TopoDS_Face& aFace = TopoDS::Face(aFaceExp.Current()); + for (TopExp_Explorer anEdgeExp(aFace, TopAbs_EDGE); anEdgeExp.More() && !hasPointOnPCurve; + anEdgeExp.Next()) + { + const TopoDS_Edge& anEdge = TopoDS::Edge(anEdgeExp.Current()); + double aFirst = 0.0; + double aLast = 0.0; + occ::handle aPCurve = BRep_Tool::CurveOnSurface(anEdge, aFace, aFirst, aLast); + if (aPCurve.IsNull()) + continue; + + aBuilder.UpdateEdge(anEdge, aPCurve, aFace, BRep_Tool::Tolerance(anEdge)); + aBuilder.Range(anEdge, aFace, aFirst, aLast); + + for (TopoDS_Iterator aVtxIt(anEdge, false, false); aVtxIt.More(); aVtxIt.Next()) + { + const TopoDS_Vertex& aVertex = TopoDS::Vertex(aVtxIt.Value()); + const double aParameter = aVertex.Orientation() == TopAbs_REVERSED ? aLast : aFirst; + aBuilder.UpdateVertex(aVertex, aParameter, anEdge, aFace, BRep_Tool::Tolerance(aVertex)); + hasPointOnPCurve = true; + break; + } + } + } + + ASSERT_TRUE(hasPointOnCurve); + ASSERT_TRUE(hasPointOnSurface); + ASSERT_TRUE(hasPointOnPCurve); + + BRepGraph aGraph; + registerStandardLayers(aGraph); + aGraph.Build(aShape); + ASSERT_TRUE(aGraph.IsDone()); + const occ::handle aParamLayer = + aGraph.LayerRegistry().FindLayer(); + ASSERT_FALSE(aParamLayer.IsNull()); + + // Count all extracted vertex point representations. + int aNbPointsOnCurve = 0; + int aNbPointsOnSurface = 0; + int aNbPointsOnPCurve = 0; + for (BRepGraph_VertexIterator aVertexIt(aGraph); aVertexIt.More(); aVertexIt.Next()) + { + const BRepGraph_VertexId aVertexId = aVertexIt.CurrentId(); + const BRepGraph_ParamLayer::VertexParams* aParams = aParamLayer->FindVertexParams(aVertexId); + if (aParams == nullptr) + continue; + aNbPointsOnCurve += aParams->PointsOnCurve.Length(); + aNbPointsOnSurface += aParams->PointsOnSurface.Length(); + aNbPointsOnPCurve += aParams->PointsOnPCurve.Length(); + + // Validate that any captured entries have valid def references. + for (const BRepGraph_ParamLayer::PointOnCurveEntry& anEntry : aParams->PointsOnCurve) + EXPECT_TRUE(anEntry.EdgeDefId.IsValid()); + for (const BRepGraph_ParamLayer::PointOnSurfaceEntry& anEntry : aParams->PointsOnSurface) + EXPECT_TRUE(anEntry.FaceDefId.IsValid()); + for (const BRepGraph_ParamLayer::PointOnPCurveEntry& anEntry : aParams->PointsOnPCurve) + EXPECT_TRUE(anEntry.CoEdgeDefId.IsValid()); + } + + EXPECT_GT(aNbPointsOnSurface, 0); + EXPECT_GT(aNbPointsOnCurve + aNbPointsOnSurface + aNbPointsOnPCurve, 0); +} + +// ============================================================ +// Edge Regularity +// ============================================================ + +TEST(BRepGraph_PolygonTest, EdgeRegularity_MatchesOriginal) +{ + // Cylinder has smooth edges between lateral face and caps. + // BRep stores regularity as BRep_CurveOn2Surfaces entries. + TopoDS_Shape aCyl = BRepPrimAPI_MakeCylinder(5., 10.).Shape(); + + // Count original regularities via BRep_Tool::Continuity. + int aNbOrigReg = 0; + for (TopExp_Explorer anEdgeExp(aCyl, TopAbs_EDGE); anEdgeExp.More(); anEdgeExp.Next()) + { + const TopoDS_Edge& anEdge = TopoDS::Edge(anEdgeExp.Current()); + const occ::handle& aTEdge = occ::down_cast(anEdge.TShape()); + if (aTEdge.IsNull()) + continue; + for (const occ::handle& aCRep : aTEdge->Curves()) + { + if (!occ::down_cast(aCRep).IsNull()) + ++aNbOrigReg; + } + } + + BRepGraph aGraph; + registerStandardLayers(aGraph); + aGraph.Build(aCyl); + ASSERT_TRUE(aGraph.IsDone()); + const occ::handle aRegularityLayer = + aGraph.LayerRegistry().FindLayer(); + ASSERT_FALSE(aRegularityLayer.IsNull()); + + // Count captured regularity entries. + int aNbGraphReg = 0; + for (BRepGraph_EdgeIterator anEdgeIt(aGraph); anEdgeIt.More(); anEdgeIt.Next()) + { + aNbGraphReg += aRegularityLayer->NbRegularities(anEdgeIt.CurrentId()); + } + EXPECT_EQ(aNbGraphReg, aNbOrigReg) + << "Graph regularity count should match BRep_CurveOn2Surfaces count"; +} + +// ============================================================ +// Seam edge PolygonOnTriangulation +// ============================================================ + +TEST(BRepGraph_PolygonTest, SeamEdge_PolyOnTri_TwoEntries) +{ + // Cylinder has seam edges with two PolygonOnTriangulation entries. + TopoDS_Shape aCyl = BRepPrimAPI_MakeCylinder(5., 10.).Shape(); + BRepMesh_IncrementalMesh aMesher(aCyl, 0.1); + + BRepGraph aGraph; + aGraph.Build(aCyl); + ASSERT_TRUE(aGraph.IsDone()); + + // Find an edge with two PolyOnTri entries for the same face (seam edge pattern). + bool aFoundSeam = false; + for (BRepGraph_EdgeIterator anEdgeIt(aGraph); anEdgeIt.More() && !aFoundSeam; anEdgeIt.Next()) + { + const NCollection_Vector& aCoEdgeIdxs = + aGraph.Topo().Edges().CoEdges(anEdgeIt.CurrentId()); + // Count PolyOnTri entries per face via coedges. + NCollection_DataMap aFaceCounts; + for (const BRepGraph_CoEdgeId& aCoEdgeId : aCoEdgeIdxs) + { + const BRepGraphInc::CoEdgeDef& aCE = aGraph.Topo().CoEdges().Definition(aCoEdgeId); + const int aFaceIdx = aCE.FaceDefId.Index; + if (!aFaceCounts.IsBound(aFaceIdx)) + aFaceCounts.Bind(aFaceIdx, 0); + aFaceCounts.ChangeFind(aFaceIdx) += 1; + } + for (const auto& [aFaceIdx, aCount] : aFaceCounts.Items()) + { + if (aCount >= 2) + { + aFoundSeam = true; + break; + } + } + if (aFoundSeam) + break; + } + // Seam edges on cylinder lateral face should produce two PolyOnTri entries. + EXPECT_TRUE(aFoundSeam) << "Meshed cylinder should have seam edge with two PolyOnTri entries"; +} diff --git a/src/ModelingData/TKBRep/GTests/BRepGraph_Reconstruct_Test.cxx b/src/ModelingData/TKBRep/GTests/BRepGraph_Reconstruct_Test.cxx new file mode 100644 index 0000000000..89ece2d918 --- /dev/null +++ b/src/ModelingData/TKBRep/GTests/BRepGraph_Reconstruct_Test.cxx @@ -0,0 +1,610 @@ +// Copyright (c) 2026 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 "BRepGraph_RefTestTools.hxx" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +// ============================================================ +// Helper: compute surface area of a shape +// ============================================================ +static double computeArea(const TopoDS_Shape& theShape) +{ + GProp_GProps aProps; + BRepGProp::SurfaceProperties(theShape, aProps); + return aProps.Mass(); +} + +// ============================================================ +// Helper: compute volume of a shape +// ============================================================ +static double computeVolume(const TopoDS_Shape& theShape) +{ + GProp_GProps aProps; + BRepGProp::VolumeProperties(theShape, aProps); + return aProps.Mass(); +} + +// ============================================================ +// Helper: count sub-shapes of a given type +// ============================================================ +static int countSubShapes(const TopoDS_Shape& theShape, TopAbs_ShapeEnum theType) +{ + int aCount = 0; + for (TopExp_Explorer anExp(theShape, theType); anExp.More(); anExp.Next()) + { + ++aCount; + } + return aCount; +} + +// ============================================================ +// Area/volume fidelity per primitive +// ============================================================ + +TEST(BRepGraph_ReconstructTest, Box_Area_Preserved) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + const double anOrigArea = computeArea(aBox); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + + TopoDS_Shape aRecon = + aGraph.Shapes().Reconstruct(BRepGraph_NodeId(BRepGraph_NodeId::Kind::Solid, 0)); + const double aReconArea = computeArea(aRecon); + + EXPECT_NEAR(aReconArea, anOrigArea, anOrigArea * 0.01); +} + +TEST(BRepGraph_ReconstructTest, Box_Volume_Preserved) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + const double anOrigVol = computeVolume(aBox); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + + TopoDS_Shape aRecon = + aGraph.Shapes().Reconstruct(BRepGraph_NodeId(BRepGraph_NodeId::Kind::Solid, 0)); + const double aReconVol = computeVolume(aRecon); + + EXPECT_NEAR(aReconVol, anOrigVol, anOrigVol * 0.01); +} + +TEST(BRepGraph_ReconstructTest, Sphere_Area_Preserved) +{ + BRepPrimAPI_MakeSphere aSphereMaker(15.0); + const TopoDS_Shape& aSphere = aSphereMaker.Shape(); + const double anOrigArea = computeArea(aSphere); + + BRepGraph aGraph; + aGraph.Build(aSphere); + ASSERT_TRUE(aGraph.IsDone()); + + TopoDS_Shape aRecon = + aGraph.Shapes().Reconstruct(BRepGraph_NodeId(BRepGraph_NodeId::Kind::Solid, 0)); + const double aReconArea = computeArea(aRecon); + + EXPECT_NEAR(aReconArea, anOrigArea, anOrigArea * 0.01); +} + +TEST(BRepGraph_ReconstructTest, Sphere_Volume_Preserved) +{ + BRepPrimAPI_MakeSphere aSphereMaker(15.0); + const TopoDS_Shape& aSphere = aSphereMaker.Shape(); + const double anOrigVol = computeVolume(aSphere); + + BRepGraph aGraph; + aGraph.Build(aSphere); + ASSERT_TRUE(aGraph.IsDone()); + + TopoDS_Shape aRecon = + aGraph.Shapes().Reconstruct(BRepGraph_NodeId(BRepGraph_NodeId::Kind::Solid, 0)); + const double aReconVol = computeVolume(aRecon); + + EXPECT_NEAR(aReconVol, anOrigVol, anOrigVol * 0.01); +} + +TEST(BRepGraph_ReconstructTest, Cylinder_Area_Preserved) +{ + BRepPrimAPI_MakeCylinder aCylMaker(10.0, 20.0); + const TopoDS_Shape& aCyl = aCylMaker.Shape(); + const double anOrigArea = computeArea(aCyl); + + BRepGraph aGraph; + aGraph.Build(aCyl); + ASSERT_TRUE(aGraph.IsDone()); + + TopoDS_Shape aRecon = + aGraph.Shapes().Reconstruct(BRepGraph_NodeId(BRepGraph_NodeId::Kind::Solid, 0)); + const double aReconArea = computeArea(aRecon); + + EXPECT_NEAR(aReconArea, anOrigArea, anOrigArea * 0.01); +} + +TEST(BRepGraph_ReconstructTest, Cylinder_Volume_Preserved) +{ + BRepPrimAPI_MakeCylinder aCylMaker(10.0, 20.0); + const TopoDS_Shape& aCyl = aCylMaker.Shape(); + const double anOrigVol = computeVolume(aCyl); + + BRepGraph aGraph; + aGraph.Build(aCyl); + ASSERT_TRUE(aGraph.IsDone()); + + TopoDS_Shape aRecon = + aGraph.Shapes().Reconstruct(BRepGraph_NodeId(BRepGraph_NodeId::Kind::Solid, 0)); + const double aReconVol = computeVolume(aRecon); + + EXPECT_NEAR(aReconVol, anOrigVol, anOrigVol * 0.01); +} + +// ============================================================ +// Per-kind reconstruction +// ============================================================ + +TEST(BRepGraph_ReconstructTest, Shell_FaceCount_MatchesOriginal) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + const int anOrigFaceCount = countSubShapes(aBox, TopAbs_FACE); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + + TopoDS_Shape aReconShell = + aGraph.Shapes().Reconstruct(BRepGraph_NodeId(BRepGraph_NodeId::Kind::Shell, 0)); + const int aReconFaceCount = countSubShapes(aReconShell, TopAbs_FACE); + + EXPECT_EQ(aReconFaceCount, anOrigFaceCount); +} + +TEST(BRepGraph_ReconstructTest, Wire_EdgeCount_FourPerBoxFace) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + + // Each wire of a box face should have exactly 4 edges. + for (BRepGraph_WireIterator aWireIt(aGraph); aWireIt.More(); aWireIt.Next()) + { + TopoDS_Shape aReconWire = aGraph.Shapes().Reconstruct(BRepGraph_NodeId(aWireIt.CurrentId())); + const int anEdgeCount = countSubShapes(aReconWire, TopAbs_EDGE); + EXPECT_EQ(anEdgeCount, 4) << "Wire " << aWireIt.CurrentId().Index << " has " << anEdgeCount + << " edges"; + } +} + +TEST(BRepGraph_ReconstructTest, Edge_HasCurve_NonNull) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + + for (BRepGraph_EdgeIterator anEdgeIt(aGraph); anEdgeIt.More(); anEdgeIt.Next()) + { + if (BRepGraph_Tool::Edge::Degenerated(aGraph, anEdgeIt.CurrentId())) + continue; + + TopoDS_Shape aReconEdge = aGraph.Shapes().Reconstruct(BRepGraph_NodeId(anEdgeIt.CurrentId())); + TopLoc_Location aLoc; + double aFirst = 0.0; + double aLast = 0.0; + occ::handle aCurve = + BRep_Tool::Curve(TopoDS::Edge(aReconEdge), aLoc, aFirst, aLast); + EXPECT_FALSE(aCurve.IsNull()) << "Edge " << anEdgeIt.CurrentId().Index << " has null curve"; + } +} + +TEST(BRepGraph_ReconstructTest, Edge_ParameterRange_Preserved) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + + for (BRepGraph_EdgeIterator anEdgeIt(aGraph); anEdgeIt.More(); anEdgeIt.Next()) + { + if (BRepGraph_Tool::Edge::Degenerated(aGraph, anEdgeIt.CurrentId())) + continue; + + TopoDS_Shape aReconEdge = aGraph.Shapes().Reconstruct(BRepGraph_NodeId(anEdgeIt.CurrentId())); + TopLoc_Location aLoc; + double aFirst = 0.0; + double aLast = 0.0; + BRep_Tool::Curve(TopoDS::Edge(aReconEdge), aLoc, aFirst, aLast); + + const auto [aDefFirst, aDefLast] = BRepGraph_Tool::Edge::Range(aGraph, anEdgeIt.CurrentId()); + EXPECT_NEAR(aFirst, aDefFirst, Precision::Confusion()) + << "Edge " << anEdgeIt.CurrentId().Index << " ParamFirst mismatch"; + EXPECT_NEAR(aLast, aDefLast, Precision::Confusion()) + << "Edge " << anEdgeIt.CurrentId().Index << " ParamLast mismatch"; + } +} + +TEST(BRepGraph_ReconstructTest, Vertex_Point_MatchesDefPoint) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + + for (BRepGraph_VertexIterator aVertexIt(aGraph); aVertexIt.More(); aVertexIt.Next()) + { + const gp_Pnt aDefPt = BRepGraph_Tool::Vertex::Pnt(aGraph, aVertexIt.CurrentId()); + + TopoDS_Shape aReconVtx = aGraph.Shapes().Reconstruct(BRepGraph_NodeId(aVertexIt.CurrentId())); + const gp_Pnt aReconPt = BRep_Tool::Pnt(TopoDS::Vertex(aReconVtx)); + + EXPECT_NEAR(aReconPt.X(), aDefPt.X(), Precision::Confusion()) + << "Vertex " << aVertexIt.CurrentId().Index << " X mismatch"; + EXPECT_NEAR(aReconPt.Y(), aDefPt.Y(), Precision::Confusion()) + << "Vertex " << aVertexIt.CurrentId().Index << " Y mismatch"; + EXPECT_NEAR(aReconPt.Z(), aDefPt.Z(), Precision::Confusion()) + << "Vertex " << aVertexIt.CurrentId().Index << " Z mismatch"; + } +} + +TEST(BRepGraph_ReconstructTest, Face_PCurvesPresent_OnAllEdges) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + + for (BRepGraph_FaceIterator aFaceIt(aGraph); aFaceIt.More(); aFaceIt.Next()) + { + TopoDS_Shape aReconFace = aGraph.Shapes().Reconstruct(aFaceIt.CurrentId()); + ASSERT_FALSE(aReconFace.IsNull()) + << "ReconstructFace returned null for face " << aFaceIt.CurrentId().Index; + + const TopoDS_Face& aFace = TopoDS::Face(aReconFace); + for (TopExp_Explorer anEdgeExp(aFace, TopAbs_EDGE); anEdgeExp.More(); anEdgeExp.Next()) + { + const TopoDS_Edge& anEdge = TopoDS::Edge(anEdgeExp.Current()); + if (BRep_Tool::Degenerated(anEdge)) + continue; + + double aFirst = 0.0; + double aLast = 0.0; + occ::handle aPCurve = BRep_Tool::CurveOnSurface(anEdge, aFace, aFirst, aLast); + EXPECT_FALSE(aPCurve.IsNull()) + << "Edge on face " << aFaceIt.CurrentId().Index << " is missing a pcurve"; + } + } +} + +TEST(BRepGraph_ReconstructTest, Face_OrientationPreserved) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + + // Verify that reconstructed faces have valid orientations matching ref entries. + ASSERT_EQ(aGraph.Topo().Shells().Nb(), 1); + const NCollection_Vector aFaceRefs = + BRepGraph_TestTools::FaceRefsOfShell(aGraph, BRepGraph_ShellId(0)); + for (const BRepGraph_FaceRefId& aFaceRefId : aFaceRefs) + { + const BRepGraphInc::FaceRef& aFaceRef = aGraph.Refs().Faces().Entry(aFaceRefId); + const TopAbs_Orientation anExpectedOri = aFaceRef.Orientation; + + TopoDS_Shape aReconFace = aGraph.Shapes().Reconstruct(aFaceRef.FaceDefId); + ASSERT_FALSE(aReconFace.IsNull()) + << "ReconstructFace returned null for face " << aFaceRef.FaceDefId.Index; + + // The reconstructed face from the def should be valid. + EXPECT_EQ(aReconFace.ShapeType(), TopAbs_FACE); + (void)anExpectedOri; // Orientation is now stored in the incidence ref, not usage. + } +} + +// ============================================================ +// Shape() vs ReconstructShape() consistency +// ============================================================ + +TEST(BRepGraph_ReconstructTest, Shape_UnmodifiedGraph_SameAsOriginalOf) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + + // For an unmodified graph, Shape(id) should be the same TShape as OriginalOf(id). + BRepGraph_SolidId aSolidId(0); + ASSERT_TRUE(aGraph.Shapes().HasOriginal(aSolidId)); + + TopoDS_Shape aShapeResult = aGraph.Shapes().Shape(aSolidId); + const TopoDS_Shape& anOriginal = aGraph.Shapes().OriginalOf(aSolidId); + EXPECT_TRUE(aShapeResult.IsSame(anOriginal)); +} + +TEST(BRepGraph_ReconstructTest, HasOriginal_BuildFace_ReturnsTrue) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + + EXPECT_TRUE(aGraph.Shapes().HasOriginal(BRepGraph_FaceId(0))); +} + +TEST(BRepGraph_ReconstructTest, OriginalOf_Face_IsSameAsBuildInputFace) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + TopExp_Explorer anExp(aBox, TopAbs_FACE); + ASSERT_TRUE(anExp.More()); + const TopoDS_Shape aFirstFace = anExp.Current(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + + EXPECT_TRUE(aGraph.Shapes().OriginalOf(BRepGraph_FaceId(0)).IsSame(aFirstFace)); +} + +TEST(BRepGraph_ReconstructTest, HasOriginal_ManualVertex_ReturnsFalse) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + + const BRepGraph_VertexId aVertexId = aGraph.Builder().AddVertex(gp_Pnt(42.0, 0.0, 0.0), 0.001); + ASSERT_TRUE(aVertexId.IsValid()); + + EXPECT_FALSE(aGraph.Shapes().HasOriginal(aVertexId)); +} + +TEST(BRepGraph_ReconstructTest, FindNode_OriginalFace_RoundTrip) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + + const BRepGraph_NodeId aFaceId = BRepGraph_FaceId(0); + const TopoDS_Shape& anOriginalFace = aGraph.Shapes().OriginalOf(aFaceId); + EXPECT_EQ(aGraph.Shapes().FindNode(anOriginalFace), aFaceId); +} + +TEST(BRepGraph_ReconstructTest, Reconstruct_Face_ValidShape) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + ASSERT_GT(aGraph.Topo().Faces().Nb(), 0); + + TopoDS_Shape aRecon = aGraph.Shapes().Reconstruct(BRepGraph_FaceId(0)); + EXPECT_FALSE(aRecon.IsNull()); + EXPECT_EQ(aRecon.ShapeType(), TopAbs_FACE); +} + +TEST(BRepGraph_ReconstructTest, Reconstruct_Edge_ValidShape) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + ASSERT_GT(aGraph.Topo().Edges().Nb(), 0); + + // Find a non-degenerate edge. + for (BRepGraph_EdgeIterator anEdgeIt(aGraph); anEdgeIt.More(); anEdgeIt.Next()) + { + if (BRepGraph_Tool::Edge::Degenerated(aGraph, anEdgeIt.CurrentId())) + continue; + + TopoDS_Shape aRecon = aGraph.Shapes().Reconstruct(BRepGraph_NodeId(anEdgeIt.CurrentId())); + EXPECT_FALSE(aRecon.IsNull()); + EXPECT_EQ(aRecon.ShapeType(), TopAbs_EDGE); + return; + } + FAIL() << "No non-degenerate edge found"; +} + +TEST(BRepGraph_ReconstructTest, Reconstruct_Vertex_CorrectPoint) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + ASSERT_GT(aGraph.Topo().Vertices().Nb(), 0); + + const gp_Pnt anExpectedPt = BRepGraph_Tool::Vertex::Pnt(aGraph, BRepGraph_VertexId(0)); + + TopoDS_Shape aRecon = + aGraph.Shapes().Reconstruct(BRepGraph_NodeId(BRepGraph_NodeId::Kind::Vertex, 0)); + ASSERT_FALSE(aRecon.IsNull()); + ASSERT_EQ(aRecon.ShapeType(), TopAbs_VERTEX); + + const gp_Pnt aReconPt = BRep_Tool::Pnt(TopoDS::Vertex(aRecon)); + EXPECT_NEAR(aReconPt.Distance(anExpectedPt), 0.0, Precision::Confusion()); +} + +// ============================================================ +// After mutation +// ============================================================ + +TEST(BRepGraph_ReconstructTest, AfterVertexMutation_ModifiedFlagAndPointChanged) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + + // Find a vertex belonging to face 0 and move it significantly. + const BRepGraph_WireId anOuterWire = + BRepGraph_TestTools::OuterWireOfFace(aGraph, BRepGraph_FaceId(0)); + ASSERT_TRUE(anOuterWire.IsValid()); + + const NCollection_Vector aCoEdgeRefs = + BRepGraph_TestTools::CoEdgeRefsOfWire(aGraph, anOuterWire); + ASSERT_GT(aCoEdgeRefs.Length(), 0); + + const BRepGraphInc::CoEdgeRef& aFirstCR = aGraph.Refs().CoEdges().Entry(aCoEdgeRefs.First()); + const BRepGraphInc::CoEdgeDef& aFirstCoEdge = + aGraph.Topo().CoEdges().Definition(aFirstCR.CoEdgeDefId); + const int aVertIdx = + BRepGraph_Tool::Edge::StartVertex(aGraph, BRepGraph_EdgeId(aFirstCoEdge.EdgeDefId)) + .VertexDefId.Index; + ASSERT_GE(aVertIdx, 0); + + // Mutate: move vertex by 5 units in Z. + const gp_Pnt anOldPt = BRepGraph_Tool::Vertex::Pnt(aGraph, BRepGraph_VertexId(aVertIdx)); + { + BRepGraph_MutGuard aMutVtx = + aGraph.Builder().MutVertex(BRepGraph_VertexId(aVertIdx)); + aMutVtx->Point = gp_Pnt(anOldPt.X(), anOldPt.Y(), anOldPt.Z() + 5.0); + } + + // Verify the OwnGen is incremented on the vertex def (directly mutated). + EXPECT_GT(aGraph.Topo().Vertices().Definition(BRepGraph_VertexId(aVertIdx)).OwnGen, 0u) + << "Vertex def should have OwnGen > 0 after mutation"; + + // Verify the graph VertexDef.Point has actually changed. + const gp_Pnt aNewPt = BRepGraph_Tool::Vertex::Pnt(aGraph, BRepGraph_VertexId(aVertIdx)); + EXPECT_GT(anOldPt.Distance(aNewPt), Precision::Confusion()) + << "VertexDef.Point should differ after mutation"; +} + +TEST(BRepGraph_ReconstructTest, AfterToleranceMutation_NewTShape) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + + BRepGraph_EdgeId anEdgeId(0); + TopoDS_Shape aShapeBefore = aGraph.Shapes().Shape(anEdgeId); + + // Mutate tolerance. + { + BRepGraph_MutGuard aMutEdge = + aGraph.Builder().MutEdge(BRepGraph_EdgeId(0)); + aMutEdge->Tolerance = aMutEdge->Tolerance + 1.0; + } + + // After mutation, Shape() should return a reconstructed shape with a different TShape. + TopoDS_Shape aShapeAfter = aGraph.Shapes().Shape(anEdgeId); + EXPECT_FALSE(aShapeAfter.IsNull()); + EXPECT_FALSE(aShapeAfter.IsSame(aShapeBefore)) + << "Shape() should return a new TShape after tolerance mutation"; +} + +TEST(BRepGraph_ReconstructTest, CompoundRoot_TwoSolids_Preserved) +{ + BRepPrimAPI_MakeBox aBoxMaker1(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox1 = aBoxMaker1.Shape(); + BRepPrimAPI_MakeBox aBoxMaker2(5.0, 10.0, 15.0); + const TopoDS_Shape& aBox2 = aBoxMaker2.Shape(); + + BRep_Builder aBuilder; + TopoDS_Compound aCompound; + aBuilder.MakeCompound(aCompound); + aBuilder.Add(aCompound, aBox1); + aBuilder.Add(aCompound, aBox2); + + BRepGraph aGraph; + aGraph.Build(aCompound); + ASSERT_TRUE(aGraph.IsDone()); + ASSERT_EQ(aGraph.Topo().Solids().Nb(), 2); + + // Reconstruct each solid and verify volumes match originals. + const double anOrigVol1 = computeVolume(aBox1); + const double anOrigVol2 = computeVolume(aBox2); + + TopoDS_Shape aRecon1 = + aGraph.Shapes().Reconstruct(BRepGraph_NodeId(BRepGraph_NodeId::Kind::Solid, 0)); + TopoDS_Shape aRecon2 = + aGraph.Shapes().Reconstruct(BRepGraph_NodeId(BRepGraph_NodeId::Kind::Solid, 1)); + + const double aReconVol1 = computeVolume(aRecon1); + const double aReconVol2 = computeVolume(aRecon2); + + // The two volumes should match the originals (order may vary, so check both assignments). + const bool isDirectMatch = (std::abs(aReconVol1 - anOrigVol1) < anOrigVol1 * 0.01) + && (std::abs(aReconVol2 - anOrigVol2) < anOrigVol2 * 0.01); + const bool isSwappedMatch = (std::abs(aReconVol1 - anOrigVol2) < anOrigVol2 * 0.01) + && (std::abs(aReconVol2 - anOrigVol1) < anOrigVol1 * 0.01); + + EXPECT_TRUE(isDirectMatch || isSwappedMatch) + << "Reconstructed solid volumes do not match originals: " << aReconVol1 << ", " << aReconVol2 + << " vs " << anOrigVol1 << ", " << anOrigVol2; +} diff --git a/src/ModelingData/TKBRep/GTests/BRepGraph_RefId_Test.cxx b/src/ModelingData/TKBRep/GTests/BRepGraph_RefId_Test.cxx new file mode 100644 index 0000000000..96ff6d4a5e --- /dev/null +++ b/src/ModelingData/TKBRep/GTests/BRepGraph_RefId_Test.cxx @@ -0,0 +1,494 @@ +// Copyright (c) 2026 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 "BRepGraph_RefTestTools.hxx" +#include +#include + +#include + +namespace +{ + +int countInlineFaceRefs(const BRepGraph& theGraph) +{ + int aNb = 0; + for (BRepGraph_ShellIterator aShellIt(theGraph); aShellIt.More(); aShellIt.Next()) + aNb += BRepGraph_TestTools::CountFaceRefsOfShell(theGraph, aShellIt.CurrentId()); + return aNb; +} + +int countInlineWireRefs(const BRepGraph& theGraph) +{ + int aNb = 0; + for (BRepGraph_FaceIterator aFaceIt(theGraph); aFaceIt.More(); aFaceIt.Next()) + aNb += BRepGraph_TestTools::CountWireRefsOfFace(theGraph, aFaceIt.CurrentId()); + return aNb; +} + +int countInlineCoEdgeRefs(const BRepGraph& theGraph) +{ + int aNb = 0; + for (BRepGraph_WireIterator aWireIt(theGraph); aWireIt.More(); aWireIt.Next()) + aNb += BRepGraph_TestTools::CountCoEdgeRefsOfWire(theGraph, aWireIt.CurrentId()); + return aNb; +} + +int countInlineVertexRefs(const BRepGraph& theGraph) +{ + int aNb = 0; + for (BRepGraph_EdgeIterator anEdgeIt(theGraph); anEdgeIt.More(); anEdgeIt.Next()) + { + const BRepGraphInc::EdgeDef& anEdge = anEdgeIt.Current(); + if (anEdge.StartVertexRefId.IsValid()) + ++aNb; + if (anEdge.EndVertexRefId.IsValid()) + ++aNb; + aNb += anEdge.InternalVertexRefIds.Length(); + } + for (BRepGraph_FaceIterator aFaceIt(theGraph); aFaceIt.More(); aFaceIt.Next()) + aNb += aFaceIt.Current().VertexRefIds.Length(); + return aNb; +} + +int countInlineShellRefs(const BRepGraph& theGraph) +{ + int aNb = 0; + for (BRepGraph_SolidIterator aSolidIt(theGraph); aSolidIt.More(); aSolidIt.Next()) + aNb += BRepGraph_TestTools::CountShellRefsOfSolid(theGraph, aSolidIt.CurrentId()); + return aNb; +} + +int countInlineSolidRefs(const BRepGraph& theGraph) +{ + int aNb = 0; + for (BRepGraph_CompSolidIterator aCSIt(theGraph); aCSIt.More(); aCSIt.Next()) + aNb += BRepGraph_TestTools::CountSolidRefsOfCompSolid(theGraph, aCSIt.CurrentId()); + return aNb; +} + +int countInlineChildRefs(const BRepGraph& theGraph) +{ + int aNb = 0; + for (BRepGraph_CompoundIterator aCompIt(theGraph); aCompIt.More(); aCompIt.Next()) + aNb += BRepGraph_TestTools::CountChildRefsOfParent(theGraph, aCompIt.CurrentId()); + for (BRepGraph_ShellIterator aShellIt(theGraph); aShellIt.More(); aShellIt.Next()) + aNb += BRepGraph_TestTools::CountChildRefsOfParent(theGraph, aShellIt.CurrentId()); + for (BRepGraph_SolidIterator aSolidIt(theGraph); aSolidIt.More(); aSolidIt.Next()) + aNb += BRepGraph_TestTools::CountChildRefsOfParent(theGraph, aSolidIt.CurrentId()); + return aNb; +} + +} // namespace + +TEST(BRepGraph_RefIdTest, DefaultRefId_IsInvalid) +{ + const BRepGraph_RefId aRefId; + EXPECT_FALSE(aRefId.IsValid()); +} + +TEST(BRepGraph_RefIdTest, TypedRefId_ConvertsToUntyped) +{ + const BRepGraph_FaceRefId aFaceRefId(3); + const BRepGraph_RefId anUntyped = aFaceRefId; + EXPECT_EQ(anUntyped.RefKind, BRepGraph_RefId::Kind::Face); + EXPECT_EQ(anUntyped.Index, 3); +} + +TEST(BRepGraph_RefIdTest, UntypedArithmetic_PreservesKindAndIndex) +{ + BRepGraph_RefId aRefId(BRepGraph_RefId::Kind::Face, 1); + + const BRepGraph_RefId aPrev = aRefId++; + EXPECT_EQ(aPrev.RefKind, BRepGraph_RefId::Kind::Face); + EXPECT_EQ(aPrev.Index, 1); + EXPECT_EQ(aRefId.Index, 2); + + ++aRefId; + EXPECT_EQ(aRefId.Index, 3); + + const BRepGraph_RefId anAdvanced = aRefId + 4; + EXPECT_EQ(anAdvanced.RefKind, BRepGraph_RefId::Kind::Face); + EXPECT_EQ(anAdvanced.Index, 7); + + const BRepGraph_RefId aRetreated = anAdvanced - 6; + EXPECT_EQ(aRetreated.RefKind, BRepGraph_RefId::Kind::Face); + EXPECT_EQ(aRetreated.Index, 1); +} + +TEST(BRepGraph_RefIdTest, TypedArithmetic_PreservesKindAndIndex) +{ + BRepGraph_FaceRefId aFaceRefId(1); + + const BRepGraph_FaceRefId aPrev = aFaceRefId++; + EXPECT_EQ(aPrev.Index, 1); + EXPECT_EQ(aFaceRefId.Index, 2); + + ++aFaceRefId; + EXPECT_EQ(aFaceRefId.Index, 3); + + const BRepGraph_FaceRefId anAdvanced = aFaceRefId + 4; + EXPECT_EQ(anAdvanced.Index, 7); + + const BRepGraph_FaceRefId aRetreated = anAdvanced - 6; + EXPECT_EQ(aRetreated.Index, 1); + + // Verify kind is preserved through implicit conversion. + const BRepGraph_RefId aRefId = anAdvanced; + EXPECT_EQ(aRefId.RefKind, BRepGraph_RefId::Kind::Face); + EXPECT_EQ(aRefId.Index, 7); +} + +TEST(BRepGraph_RefIdTest, TypedArithmetic_IndexZeroBoundary) +{ + BRepGraph_CoEdgeRefId aCoEdge(0); + EXPECT_TRUE(aCoEdge.IsValid()); + + // Increment from zero. + ++aCoEdge; + EXPECT_EQ(aCoEdge.Index, 1); + + // Retreat back to zero still valid. + const BRepGraph_CoEdgeRefId aZero = aCoEdge - 1; + EXPECT_EQ(aZero.Index, 0); + EXPECT_TRUE(aZero.IsValid()); + + // Subtract to -1 produces invalid id (allowed by constructor). + const BRepGraph_CoEdgeRefId anInvalid = aZero - 1; + EXPECT_EQ(anInvalid.Index, -1); + EXPECT_FALSE(anInvalid.IsValid()); +} + +TEST(BRepGraph_RefIdTest, RefUID_Default_IsInvalid) +{ + const BRepGraph_RefUID aRefUID; + EXPECT_FALSE(aRefUID.IsValid()); +} + +TEST(BRepGraph_RefIdTest, RefUID_Equality_IgnoresGeneration) +{ + const BRepGraph_RefUID aUIDV1(BRepGraph_RefId::Kind::Face, 42, 1); + const BRepGraph_RefUID aUIDV2(BRepGraph_RefId::Kind::Face, 42, 999); + EXPECT_EQ(aUIDV1, aUIDV2); +} + +TEST(BRepGraph_RefIdTest, RefsView_AfterBuild_HasFaceRefs) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 20.0, 30.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + const int aFaceRefCount = aGraph.Refs().Faces().Nb(); + EXPECT_GE(aFaceRefCount, 0); + + if (aFaceRefCount > 0) + { + const BRepGraph_FaceRefId aFaceRefId(0); + const BRepGraphInc::FaceRef& anEntry = aGraph.Refs().Faces().Entry(aFaceRefId); + (void)anEntry; + EXPECT_TRUE(aFaceRefId.IsValid(aFaceRefCount)); + } + else + { + EXPECT_FALSE(aGraph.UIDs().Of(BRepGraph_FaceRefId(0)).IsValid()); + } +} + +TEST(BRepGraph_RefIdTest, RefDomain_StampGUIDGeneration_IfSupported) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 20.0, 30.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + if (aGraph.Refs().Faces().Nb() <= 0) + { + GTEST_SKIP() << "Ref-domain entries are not populated at this phase"; + } + + const BRepGraph_VersionStamp aStamp = aGraph.UIDs().StampOf(BRepGraph_FaceRefId(0)); + if (!aStamp.IsValid()) + { + GTEST_SKIP() << "Ref-domain stamping/GUID generation not wired yet"; + } + + EXPECT_TRUE(aStamp.IsRefStamp()); + + const Standard_GUID& aGraphGUID = aGraph.UIDs().GraphGUID(); + const Standard_GUID aGUID1 = aStamp.ToGUID(aGraphGUID); + const Standard_GUID aGUID2 = aStamp.ToGUID(aGraphGUID); + EXPECT_EQ(aGUID1, aGUID2); +} + +TEST(BRepGraph_RefIdTest, RefsView_AfterBuild_CountsMatchInlineStorage) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 20.0, 30.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + EXPECT_EQ(aGraph.Refs().Faces().Nb(), countInlineFaceRefs(aGraph)); + EXPECT_EQ(aGraph.Refs().Wires().Nb(), countInlineWireRefs(aGraph)); + EXPECT_EQ(aGraph.Refs().CoEdges().Nb(), countInlineCoEdgeRefs(aGraph)); + EXPECT_EQ(aGraph.Refs().Vertices().Nb(), countInlineVertexRefs(aGraph)); + EXPECT_EQ(aGraph.Refs().Shells().Nb(), countInlineShellRefs(aGraph)); + EXPECT_EQ(aGraph.Refs().Solids().Nb(), countInlineSolidRefs(aGraph)); + EXPECT_EQ(aGraph.Refs().Children().Nb(), countInlineChildRefs(aGraph)); +} + +TEST(BRepGraph_RefIdTest, RefsView_AfterBuild_UIDRoundtripAndParentKinds) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 20.0, 30.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + const int aNbFaceRefs = aGraph.Refs().Faces().Nb(); + for (BRepGraph_FaceRefId aFaceRefId(0); aFaceRefId.IsValid(aNbFaceRefs); ++aFaceRefId) + { + const BRepGraph_RefId aRefId = aFaceRefId; + const BRepGraph_RefUID aUID = aGraph.UIDs().Of(aRefId); + const BRepGraph_VersionStamp aStamp = aGraph.UIDs().StampOf(aRefId); + EXPECT_TRUE(aUID.IsValid()); + EXPECT_EQ(aGraph.UIDs().RefIdFrom(aUID), aRefId); + EXPECT_TRUE(aGraph.UIDs().Has(aUID)); + EXPECT_TRUE(aStamp.IsValid()); + EXPECT_TRUE(aStamp.IsRefStamp()); + EXPECT_FALSE(aGraph.UIDs().IsStale(aStamp)); + + const BRepGraphInc::FaceRef& anEntry = aGraph.Refs().Faces().Entry(aFaceRefId); + EXPECT_EQ(anEntry.RefId, aRefId); + EXPECT_EQ(anEntry.ParentId.NodeKind, BRepGraph_NodeId::Kind::Shell); + EXPECT_TRUE(anEntry.FaceDefId.IsValid(aGraph.Topo().Faces().Nb())); + } + + const int aNbWireRefs = aGraph.Refs().Wires().Nb(); + for (BRepGraph_WireRefId aWireRefId(0); aWireRefId.IsValid(aNbWireRefs); ++aWireRefId) + { + const BRepGraph_RefId aRefId = aWireRefId; + const BRepGraph_RefUID aUID = aGraph.UIDs().Of(aRefId); + const BRepGraphInc::WireRef& anEntry = aGraph.Refs().Wires().Entry(aWireRefId); + EXPECT_TRUE(aUID.IsValid()); + EXPECT_EQ(aGraph.UIDs().RefIdFrom(aUID), aRefId); + EXPECT_EQ(anEntry.RefId, aRefId); + EXPECT_EQ(anEntry.ParentId.NodeKind, BRepGraph_NodeId::Kind::Face); + EXPECT_TRUE(anEntry.WireDefId.IsValid(aGraph.Topo().Wires().Nb())); + } + + const int aNbCoEdgeRefs = aGraph.Refs().CoEdges().Nb(); + for (BRepGraph_CoEdgeRefId aCoEdgeRefId(0); aCoEdgeRefId.IsValid(aNbCoEdgeRefs); ++aCoEdgeRefId) + { + const BRepGraph_RefId aRefId = aCoEdgeRefId; + const BRepGraph_RefUID aUID = aGraph.UIDs().Of(aRefId); + const BRepGraphInc::CoEdgeRef& anEntry = aGraph.Refs().CoEdges().Entry(aCoEdgeRefId); + EXPECT_TRUE(aUID.IsValid()); + EXPECT_EQ(aGraph.UIDs().RefIdFrom(aUID), aRefId); + EXPECT_EQ(anEntry.RefId, aRefId); + EXPECT_EQ(anEntry.ParentId.NodeKind, BRepGraph_NodeId::Kind::Wire); + EXPECT_TRUE(anEntry.CoEdgeDefId.IsValid(aGraph.Topo().CoEdges().Nb())); + } + + const int aNbShellRefs = aGraph.Refs().Shells().Nb(); + for (BRepGraph_ShellRefId aShellRefId(0); aShellRefId.IsValid(aNbShellRefs); ++aShellRefId) + { + const BRepGraph_RefId aRefId = aShellRefId; + const BRepGraph_RefUID aUID = aGraph.UIDs().Of(aRefId); + const BRepGraphInc::ShellRef& anEntry = aGraph.Refs().Shells().Entry(aShellRefId); + EXPECT_TRUE(aUID.IsValid()); + EXPECT_EQ(aGraph.UIDs().RefIdFrom(aUID), aRefId); + EXPECT_EQ(anEntry.RefId, aRefId); + EXPECT_EQ(anEntry.ParentId.NodeKind, BRepGraph_NodeId::Kind::Solid); + EXPECT_TRUE(anEntry.ShellDefId.IsValid(aGraph.Topo().Shells().Nb())); + } + + const int aNbVertexRefs = aGraph.Refs().Vertices().Nb(); + for (BRepGraph_VertexRefId aVertexRefId(0); aVertexRefId.IsValid(aNbVertexRefs); ++aVertexRefId) + { + const BRepGraph_RefId aRefId = aVertexRefId; + const BRepGraph_RefUID aUID = aGraph.UIDs().Of(aRefId); + const BRepGraphInc::VertexRef& anEntry = aGraph.Refs().Vertices().Entry(aVertexRefId); + EXPECT_TRUE(aUID.IsValid()); + EXPECT_EQ(aGraph.UIDs().RefIdFrom(aUID), aRefId); + EXPECT_EQ(anEntry.RefId, aRefId); + EXPECT_TRUE(anEntry.ParentId.NodeKind == BRepGraph_NodeId::Kind::Edge + || anEntry.ParentId.NodeKind == BRepGraph_NodeId::Kind::Face); + EXPECT_TRUE(anEntry.VertexDefId.IsValid(aGraph.Topo().Vertices().Nb())); + } + + const int aNbSolidRefs = aGraph.Refs().Solids().Nb(); + for (BRepGraph_SolidRefId aSolidRefId(0); aSolidRefId.IsValid(aNbSolidRefs); ++aSolidRefId) + { + const BRepGraph_RefId aRefId = aSolidRefId; + const BRepGraph_RefUID aUID = aGraph.UIDs().Of(aRefId); + const BRepGraphInc::SolidRef& anEntry = aGraph.Refs().Solids().Entry(aSolidRefId); + EXPECT_TRUE(aUID.IsValid()); + EXPECT_EQ(aGraph.UIDs().RefIdFrom(aUID), aRefId); + EXPECT_EQ(anEntry.RefId, aRefId); + EXPECT_EQ(anEntry.ParentId.NodeKind, BRepGraph_NodeId::Kind::CompSolid); + EXPECT_TRUE(anEntry.SolidDefId.IsValid(aGraph.Topo().Solids().Nb())); + } + + const int aNbChildRefs = aGraph.Refs().Children().Nb(); + for (BRepGraph_ChildRefId aChildRefId(0); aChildRefId.IsValid(aNbChildRefs); ++aChildRefId) + { + const BRepGraph_RefId aRefId = aChildRefId; + const BRepGraph_RefUID aUID = aGraph.UIDs().Of(aRefId); + const BRepGraphInc::ChildRef& anEntry = aGraph.Refs().Children().Entry(aChildRefId); + EXPECT_TRUE(aUID.IsValid()); + EXPECT_EQ(aGraph.UIDs().RefIdFrom(aUID), aRefId); + EXPECT_EQ(anEntry.RefId, aRefId); + EXPECT_TRUE(anEntry.ParentId.NodeKind == BRepGraph_NodeId::Kind::Compound + || anEntry.ParentId.NodeKind == BRepGraph_NodeId::Kind::Shell + || anEntry.ParentId.NodeKind == BRepGraph_NodeId::Kind::Solid); + EXPECT_TRUE(anEntry.ChildDefId.IsValid()); + } +} + +TEST(BRepGraph_RefIdTest, RefUIDReverseLookupStaysCurrentAfterProgrammaticAdd) +{ + BRepGraph aGraph; + + const BRepGraph_VertexId aV0 = aGraph.Builder().AddVertex(gp_Pnt(0.0, 0.0, 0.0), 0.001); + const BRepGraph_VertexId aV1 = aGraph.Builder().AddVertex(gp_Pnt(1.0, 0.0, 0.0), 0.001); + (void)aGraph.Builder().AddEdge(aV0, aV1, occ::handle(), 0.0, 1.0, 0.001); + + const BRepGraph_RefId aFirstRefId = BRepGraph_VertexRefId(0); + const BRepGraph_RefUID aFirstUID = aGraph.UIDs().Of(aFirstRefId); + ASSERT_TRUE(aFirstUID.IsValid()); + ASSERT_EQ(aGraph.UIDs().RefIdFrom(aFirstUID), aFirstRefId); + + const BRepGraph_VertexId aV2 = aGraph.Builder().AddVertex(gp_Pnt(2.0, 0.0, 0.0), 0.001); + const BRepGraph_VertexId aV3 = aGraph.Builder().AddVertex(gp_Pnt(3.0, 0.0, 0.0), 0.001); + (void)aGraph.Builder().AddEdge(aV2, aV3, occ::handle(), 0.0, 1.0, 0.001); + + const BRepGraph_RefId aSecondRefId = BRepGraph_VertexRefId(2); + const BRepGraph_RefUID aSecondUID = aGraph.UIDs().Of(aSecondRefId); + ASSERT_TRUE(aSecondUID.IsValid()); + + EXPECT_EQ(aGraph.UIDs().RefIdFrom(aFirstUID), aFirstRefId); + EXPECT_EQ(aGraph.UIDs().RefIdFrom(aSecondUID), aSecondRefId); + EXPECT_TRUE(aGraph.UIDs().Has(aFirstUID)); + EXPECT_TRUE(aGraph.UIDs().Has(aSecondUID)); +} + +TEST(BRepGraph_RefIdTest, StaleRefUID_HasReturnsFalseAndLookupBecomesInvalidAfterRebuild) +{ + BRepPrimAPI_MakeBox aBoxMaker1(10.0, 20.0, 30.0); + BRepPrimAPI_MakeBox aBoxMaker2(11.0, 21.0, 31.0); + + BRepGraph aGraph; + aGraph.Build(aBoxMaker1.Shape()); + ASSERT_TRUE(aGraph.IsDone()); + ASSERT_GT(aGraph.Refs().Faces().Nb(), 0); + + const BRepGraph_RefUID anOldUID = aGraph.UIDs().Of(BRepGraph_FaceRefId(0)); + ASSERT_TRUE(anOldUID.IsValid()); + ASSERT_TRUE(aGraph.UIDs().Has(anOldUID)); + + aGraph.Build(aBoxMaker2.Shape()); + + EXPECT_FALSE(aGraph.UIDs().Has(anOldUID)); + EXPECT_FALSE(aGraph.UIDs().RefIdFrom(anOldUID).IsValid()); +} + +TEST(BRepGraph_RefIdTest, MutFaceRef_UpdatesRefStampAndParentModifiedFlag) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 20.0, 30.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + if (aGraph.Refs().Faces().Nb() <= 0) + { + GTEST_SKIP() << "No face references available"; + } + + const BRepGraph_FaceRefId aFaceRefId(0); + const BRepGraphInc::FaceRef& aBeforeEntry = aGraph.Refs().Faces().Entry(aFaceRefId); + const BRepGraph_VersionStamp aBeforeStamp = aGraph.UIDs().StampOf(aFaceRefId); + ASSERT_TRUE(aBeforeEntry.ParentId.IsValid()); + ASSERT_TRUE(aBeforeStamp.IsValid()); + ASSERT_TRUE(aBeforeStamp.IsRefStamp()); + const uint32_t aBeforeOwnGen = aBeforeEntry.OwnGen; + const TopAbs_Orientation anBeforeOri = aBeforeEntry.Orientation; + + { + BRepGraph_MutGuard aMut = aGraph.Builder().MutFaceRef(aFaceRefId); + aMut->Orientation = (anBeforeOri == TopAbs_FORWARD) ? TopAbs_REVERSED : TopAbs_FORWARD; + } + + const BRepGraphInc::FaceRef& aAfterEntry = aGraph.Refs().Faces().Entry(aFaceRefId); + EXPECT_NE(aAfterEntry.Orientation, anBeforeOri); + EXPECT_GT(aAfterEntry.OwnGen, aBeforeOwnGen); + EXPECT_TRUE(aGraph.UIDs().IsStale(aBeforeStamp)); + + const BRepGraphInc::BaseDef* aParentDef = aGraph.Topo().Gen().TopoEntity(aAfterEntry.ParentId); + ASSERT_NE(aParentDef, nullptr); + EXPECT_GT(aParentDef->SubtreeGen, 0u); +} + +TEST(BRepGraph_RefIdTest, MutFaceRef_MarkRemoved_PersistsAndInvalidatesStamp) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 20.0, 30.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + if (aGraph.Refs().Faces().Nb() <= 0) + { + GTEST_SKIP() << "No face references available"; + } + + const BRepGraph_FaceRefId aFaceRefId(0); + const BRepGraph_VersionStamp aBeforeStamp = aGraph.UIDs().StampOf(aFaceRefId); + ASSERT_TRUE(aBeforeStamp.IsValid()); + ASSERT_TRUE(aBeforeStamp.IsRefStamp()); + + { + BRepGraph_MutGuard aMut = aGraph.Builder().MutFaceRef(aFaceRefId); + aMut->IsRemoved = true; + } + + const BRepGraphInc::FaceRef& aAfterEntry = aGraph.Refs().Faces().Entry(aFaceRefId); + EXPECT_TRUE(aAfterEntry.IsRemoved); + EXPECT_TRUE(aGraph.UIDs().IsStale(aBeforeStamp)); + EXPECT_FALSE(aGraph.UIDs().StampOf(aFaceRefId).IsValid()); +} + +TEST(BRepGraph_RefIdTest, ChildRefs_CompoundEntriesAreValid) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 20.0, 30.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + const BRepGraph::RefsView& aRefs = aGraph.Refs(); + + for (BRepGraph_CompoundIterator aCompIt(aGraph); aCompIt.More(); aCompIt.Next()) + { + const NCollection_Vector aChildRefs = + BRepGraph_TestTools::ChildRefsOfParent(aGraph, aCompIt.CurrentId()); + for (const BRepGraph_ChildRefId& aChildRefId : aChildRefs) + { + const BRepGraphInc::ChildRef& aRef = aRefs.Children().Entry(aChildRefId); + EXPECT_EQ(aRef.ParentId, aCompIt.CurrentId()); + EXPECT_TRUE(aRef.ChildDefId.IsValid()); + EXPECT_FALSE(aRef.IsRemoved); + } + } +} diff --git a/src/ModelingData/TKBRep/GTests/BRepGraph_RefTestTools.hxx b/src/ModelingData/TKBRep/GTests/BRepGraph_RefTestTools.hxx new file mode 100644 index 0000000000..840d5368b2 --- /dev/null +++ b/src/ModelingData/TKBRep/GTests/BRepGraph_RefTestTools.hxx @@ -0,0 +1,392 @@ +// Copyright (c) 2026 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 _BRepGraph_RefTestTools_HeaderFile +#define _BRepGraph_RefTestTools_HeaderFile + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace BRepGraph_TestTools +{ + +//================================================================================================= +inline NCollection_Vector CoEdgeRefsOfWire(const BRepGraph& theGraph, + const BRepGraph_WireId theWireId) +{ + NCollection_Vector aRefIds; + const BRepGraph::RefsView& aRefs = theGraph.Refs(); + const BRepGraph_NodeId aParentNode = BRepGraph_WireId(theWireId.Index); + const int aNbCoEdgeRefs = aRefs.CoEdges().Nb(); + for (BRepGraph_CoEdgeRefId aRefId(0); aRefId.IsValid(aNbCoEdgeRefs); ++aRefId) + { + const BRepGraphInc::CoEdgeRef& aRef = aRefs.CoEdges().Entry(aRefId); + if (aRef.ParentId == aParentNode && !aRef.IsRemoved) + aRefIds.Append(aRefId); + } + return aRefIds; +} + +//================================================================================================= + +inline int CountCoEdgeRefsOfWire(const BRepGraph& theGraph, const BRepGraph_WireId theWireId) +{ + return CoEdgeRefsOfWire(theGraph, theWireId).Length(); +} + +//================================================================================================= + +inline NCollection_Vector WireRefsOfFace(const BRepGraph& theGraph, + const BRepGraph_FaceId theFaceId) +{ + NCollection_Vector aRefIds; + const BRepGraph::RefsView& aRefs = theGraph.Refs(); + const BRepGraph_NodeId aParentNode = BRepGraph_FaceId(theFaceId.Index); + const int aNbWireRefs = aRefs.Wires().Nb(); + for (BRepGraph_WireRefId aRefId(0); aRefId.IsValid(aNbWireRefs); ++aRefId) + { + const BRepGraphInc::WireRef& aRef = aRefs.Wires().Entry(aRefId); + if (aRef.ParentId == aParentNode && !aRef.IsRemoved) + aRefIds.Append(aRefId); + } + return aRefIds; +} + +//================================================================================================= + +inline int CountWireRefsOfFace(const BRepGraph& theGraph, const BRepGraph_FaceId theFaceId) +{ + return WireRefsOfFace(theGraph, theFaceId).Length(); +} + +//================================================================================================= + +inline bool FaceUsesWire(const BRepGraph& theGraph, + const BRepGraph_FaceId theFaceId, + const BRepGraph_WireId theWireId) +{ + const BRepGraph::RefsView& aRefs = theGraph.Refs(); + const NCollection_Vector aWireRefs = WireRefsOfFace(theGraph, theFaceId); + for (const BRepGraph_WireRefId& aWireRefId : aWireRefs) + { + if (aRefs.Wires().Entry(aWireRefId).WireDefId == theWireId) + return true; + } + return false; +} + +//================================================================================================= + +inline NCollection_Vector FaceRefsOfShell(const BRepGraph& theGraph, + const BRepGraph_ShellId theShellId) +{ + NCollection_Vector aRefIds; + const BRepGraph::RefsView& aRefs = theGraph.Refs(); + const BRepGraph_NodeId aParentNode = BRepGraph_ShellId(theShellId.Index); + const int aNbFaceRefs = aRefs.Faces().Nb(); + for (BRepGraph_FaceRefId aRefId(0); aRefId.IsValid(aNbFaceRefs); ++aRefId) + { + const BRepGraphInc::FaceRef& aRef = aRefs.Faces().Entry(aRefId); + if (aRef.ParentId == aParentNode && !aRef.IsRemoved) + aRefIds.Append(aRefId); + } + return aRefIds; +} + +//================================================================================================= + +inline int CountFaceRefsOfShell(const BRepGraph& theGraph, const BRepGraph_ShellId theShellId) +{ + return FaceRefsOfShell(theGraph, theShellId).Length(); +} + +//================================================================================================= + +inline NCollection_Vector ShellRefsOfSolid(const BRepGraph& theGraph, + const BRepGraph_SolidId theSolidId) +{ + NCollection_Vector aRefIds; + const BRepGraph::RefsView& aRefs = theGraph.Refs(); + const BRepGraph_NodeId aParentNode = BRepGraph_SolidId(theSolidId.Index); + const int aNbShellRefs = aRefs.Shells().Nb(); + for (BRepGraph_ShellRefId aRefId(0); aRefId.IsValid(aNbShellRefs); ++aRefId) + { + const BRepGraphInc::ShellRef& aRef = aRefs.Shells().Entry(aRefId); + if (aRef.ParentId == aParentNode && !aRef.IsRemoved) + aRefIds.Append(aRefId); + } + return aRefIds; +} + +//================================================================================================= + +inline int CountShellRefsOfSolid(const BRepGraph& theGraph, const BRepGraph_SolidId theSolidId) +{ + return ShellRefsOfSolid(theGraph, theSolidId).Length(); +} + +//================================================================================================= + +inline NCollection_Vector SolidRefsOfCompSolid( + const BRepGraph& theGraph, + const BRepGraph_CompSolidId theCompSolidId) +{ + NCollection_Vector aRefIds; + const BRepGraph::RefsView& aRefs = theGraph.Refs(); + const BRepGraph_NodeId aParentNode = BRepGraph_CompSolidId(theCompSolidId.Index); + const int aNbSolidRefs = aRefs.Solids().Nb(); + for (BRepGraph_SolidRefId aRefId(0); aRefId.IsValid(aNbSolidRefs); ++aRefId) + { + const BRepGraphInc::SolidRef& aRef = aRefs.Solids().Entry(aRefId); + if (aRef.ParentId == aParentNode && !aRef.IsRemoved) + aRefIds.Append(aRefId); + } + return aRefIds; +} + +//================================================================================================= + +inline int CountSolidRefsOfCompSolid(const BRepGraph& theGraph, + const BRepGraph_CompSolidId theCompSolidId) +{ + return SolidRefsOfCompSolid(theGraph, theCompSolidId).Length(); +} + +//================================================================================================= + +inline NCollection_Vector ChildRefsOfParent( + const BRepGraph& theGraph, + const BRepGraph_NodeId theParentId) +{ + NCollection_Vector aRefIds; + const BRepGraph::RefsView& aRefs = theGraph.Refs(); + const int aNbChildRefs = aRefs.Children().Nb(); + for (BRepGraph_ChildRefId aRefId(0); aRefId.IsValid(aNbChildRefs); ++aRefId) + { + const BRepGraphInc::ChildRef& aRef = aRefs.Children().Entry(aRefId); + if (aRef.ParentId == theParentId && !aRef.IsRemoved) + aRefIds.Append(aRefId); + } + return aRefIds; +} + +//================================================================================================= + +inline int CountChildRefsOfParent(const BRepGraph& theGraph, const BRepGraph_NodeId theParentId) +{ + return ChildRefsOfParent(theGraph, theParentId).Length(); +} + +//================================================================================================= + +inline BRepGraph_WireId OuterWireOfFace(const BRepGraph& theGraph, const BRepGraph_FaceId theFaceId) +{ + return theGraph.Topo().Faces().OuterWire(theFaceId); +} + +//================================================================================================= + +inline NCollection_Vector CoEdgeRefsOfWire( + const BRepGraphInc_Storage& theStorage, + const BRepGraph_WireId theWireId) +{ + NCollection_Vector aRefIds; + const BRepGraph_NodeId aParentNode = BRepGraph_WireId(theWireId.Index); + const int aNbCoEdgeRefs = theStorage.NbCoEdgeRefs(); + for (BRepGraph_CoEdgeRefId aRefId(0); aRefId.IsValid(aNbCoEdgeRefs); ++aRefId) + { + const BRepGraphInc::CoEdgeRef& aRef = theStorage.CoEdgeRef(aRefId); + if (aRef.ParentId == aParentNode && !aRef.IsRemoved) + aRefIds.Append(aRefId); + } + return aRefIds; +} + +//================================================================================================= + +inline int CountCoEdgeRefsOfWire(const BRepGraphInc_Storage& theStorage, + const BRepGraph_WireId theWireId) +{ + return CoEdgeRefsOfWire(theStorage, theWireId).Length(); +} + +//================================================================================================= + +inline NCollection_Vector WireRefsOfFace( + const BRepGraphInc_Storage& theStorage, + const BRepGraph_FaceId theFaceId) +{ + NCollection_Vector aRefIds; + const BRepGraph_NodeId aParentNode = BRepGraph_FaceId(theFaceId.Index); + const int aNbWireRefs = theStorage.NbWireRefs(); + for (BRepGraph_WireRefId aRefId(0); aRefId.IsValid(aNbWireRefs); ++aRefId) + { + const BRepGraphInc::WireRef& aRef = theStorage.WireRef(aRefId); + if (aRef.ParentId == aParentNode && !aRef.IsRemoved) + aRefIds.Append(aRefId); + } + return aRefIds; +} + +//================================================================================================= + +inline int CountWireRefsOfFace(const BRepGraphInc_Storage& theStorage, + const BRepGraph_FaceId theFaceId) +{ + return WireRefsOfFace(theStorage, theFaceId).Length(); +} + +//================================================================================================= + +inline bool FaceUsesWire(const BRepGraphInc_Storage& theStorage, + const BRepGraph_FaceId theFaceId, + const BRepGraph_WireId theWireId) +{ + const NCollection_Vector aWireRefs = WireRefsOfFace(theStorage, theFaceId); + for (const BRepGraph_WireRefId& aWireRefId : aWireRefs) + { + const BRepGraphInc::WireRef& aWireRef = theStorage.WireRef(aWireRefId); + if (aWireRef.WireDefId == theWireId) + return true; + } + return false; +} + +//================================================================================================= + +inline NCollection_Vector FaceRefsOfShell( + const BRepGraphInc_Storage& theStorage, + const BRepGraph_ShellId theShellId) +{ + NCollection_Vector aRefIds; + const BRepGraph_NodeId aParentNode = BRepGraph_ShellId(theShellId.Index); + const int aNbFaceRefs = theStorage.NbFaceRefs(); + for (BRepGraph_FaceRefId aRefId(0); aRefId.IsValid(aNbFaceRefs); ++aRefId) + { + const BRepGraphInc::FaceRef& aRef = theStorage.FaceRef(aRefId); + if (aRef.ParentId == aParentNode && !aRef.IsRemoved) + aRefIds.Append(aRefId); + } + return aRefIds; +} + +//================================================================================================= + +inline int CountFaceRefsOfShell(const BRepGraphInc_Storage& theStorage, + const BRepGraph_ShellId theShellId) +{ + return FaceRefsOfShell(theStorage, theShellId).Length(); +} + +//================================================================================================= + +inline NCollection_Vector ShellRefsOfSolid( + const BRepGraphInc_Storage& theStorage, + const BRepGraph_SolidId theSolidId) +{ + NCollection_Vector aRefIds; + const BRepGraph_NodeId aParentNode = BRepGraph_SolidId(theSolidId.Index); + const int aNbShellRefs = theStorage.NbShellRefs(); + for (BRepGraph_ShellRefId aRefId(0); aRefId.IsValid(aNbShellRefs); ++aRefId) + { + const BRepGraphInc::ShellRef& aRef = theStorage.ShellRef(aRefId); + if (aRef.ParentId == aParentNode && !aRef.IsRemoved) + aRefIds.Append(aRefId); + } + return aRefIds; +} + +//================================================================================================= + +inline int CountShellRefsOfSolid(const BRepGraphInc_Storage& theStorage, + const BRepGraph_SolidId theSolidId) +{ + return ShellRefsOfSolid(theStorage, theSolidId).Length(); +} + +//================================================================================================= + +inline NCollection_Vector SolidRefsOfCompSolid( + const BRepGraphInc_Storage& theStorage, + const BRepGraph_CompSolidId theCompSolidId) +{ + NCollection_Vector aRefIds; + const BRepGraph_NodeId aParentNode = BRepGraph_CompSolidId(theCompSolidId.Index); + const int aNbSolidRefs = theStorage.NbSolidRefs(); + for (BRepGraph_SolidRefId aRefId(0); aRefId.IsValid(aNbSolidRefs); ++aRefId) + { + const BRepGraphInc::SolidRef& aRef = theStorage.SolidRef(aRefId); + if (aRef.ParentId == aParentNode && !aRef.IsRemoved) + aRefIds.Append(aRefId); + } + return aRefIds; +} + +//================================================================================================= + +inline int CountSolidRefsOfCompSolid(const BRepGraphInc_Storage& theStorage, + const BRepGraph_CompSolidId theCompSolidId) +{ + return SolidRefsOfCompSolid(theStorage, theCompSolidId).Length(); +} + +//================================================================================================= + +inline NCollection_Vector ChildRefsOfParent( + const BRepGraphInc_Storage& theStorage, + const BRepGraph_NodeId theParentId) +{ + NCollection_Vector aRefIds; + const int aNbChildRefs = theStorage.NbChildRefs(); + for (BRepGraph_ChildRefId aRefId(0); aRefId.IsValid(aNbChildRefs); ++aRefId) + { + const BRepGraphInc::ChildRef& aRef = theStorage.ChildRef(aRefId); + if (aRef.ParentId == theParentId && !aRef.IsRemoved) + aRefIds.Append(aRefId); + } + return aRefIds; +} + +//================================================================================================= + +inline int CountChildRefsOfParent(const BRepGraphInc_Storage& theStorage, + const BRepGraph_NodeId theParentId) +{ + return ChildRefsOfParent(theStorage, theParentId).Length(); +} + +//================================================================================================= + +inline BRepGraph_WireId OuterWireOfFace(const BRepGraphInc_Storage& theStorage, + const BRepGraph_FaceId theFaceId) +{ + const NCollection_Vector aWireRefs = WireRefsOfFace(theStorage, theFaceId); + for (const BRepGraph_WireRefId& aWireRefId : aWireRefs) + { + const BRepGraphInc::WireRef& aWireRef = theStorage.WireRef(aWireRefId); + if (aWireRef.IsOuter) + return aWireRef.WireDefId; + } + return BRepGraph_WireId(); +} + +} // namespace BRepGraph_TestTools + +#endif // _BRepGraph_RefTestTools_HeaderFile diff --git a/src/ModelingData/TKBRep/GTests/BRepGraph_RefsIterator_Test.cxx b/src/ModelingData/TKBRep/GTests/BRepGraph_RefsIterator_Test.cxx new file mode 100644 index 0000000000..2572ae7770 --- /dev/null +++ b/src/ModelingData/TKBRep/GTests/BRepGraph_RefsIterator_Test.cxx @@ -0,0 +1,217 @@ +// Copyright (c) 2026 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 + +namespace +{ +template +static int countIterator(IteratorT theIterator) +{ + int aCount = 0; + for (; theIterator.More(); theIterator.Next()) + { + ++aCount; + } + return aCount; +} + +static TopoDS_Edge makeEdgeWithInternalVertex() +{ + BRep_Builder aBuilder; + BRepBuilderAPI_MakeEdge aMakeEdge(gp_Pnt(0, 0, 0), gp_Pnt(10, 0, 0)); + TopoDS_Edge anEdge = aMakeEdge.Edge(); + + TopoDS_Vertex anInternalVertex; + aBuilder.MakeVertex(anInternalVertex, gp_Pnt(5, 0, 0), Precision::Confusion()); + aBuilder.Add(anEdge, anInternalVertex.Oriented(TopAbs_INTERNAL)); + return anEdge; +} + +static TopoDS_Face makeFaceWithDirectVertex() +{ + BRep_Builder aBuilder; + const occ::handle aPlane = new Geom_Plane(gp_Pln()); + TopoDS_Face aFace; + aBuilder.MakeFace(aFace, aPlane, Precision::Confusion()); + + BRepBuilderAPI_MakeEdge aMakeEdge(gp_Pnt(0, 0, 0), gp_Pnt(10, 0, 0)); + TopoDS_Wire aWire; + aBuilder.MakeWire(aWire); + aBuilder.Add(aWire, aMakeEdge.Edge()); + aBuilder.Add(aFace, aWire); + + TopoDS_Vertex aDirectVertex; + aBuilder.MakeVertex(aDirectVertex, gp_Pnt(5, 5, 0), Precision::Confusion()); + aBuilder.Add(aFace, aDirectVertex.Oriented(TopAbs_INTERNAL)); + return aFace; +} + +static TopoDS_Face wrapEdgeInFace(const TopoDS_Edge& theEdge) +{ + BRep_Builder aBuilder; + const occ::handle aPlane = new Geom_Plane(gp_Pln()); + TopoDS_Face aFace; + aBuilder.MakeFace(aFace, aPlane, Precision::Confusion()); + TopoDS_Wire aWire; + aBuilder.MakeWire(aWire); + aBuilder.Add(aWire, theEdge); + aBuilder.Add(aFace, aWire); + return aFace; +} +} // namespace + +class BRepGraph_RefsIteratorTest : public testing::Test +{ +protected: + void SetUp() override + { + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + myGraph.Build(aBoxMaker.Shape()); + } + + BRepGraph myGraph; +}; + +TEST_F(BRepGraph_RefsIteratorTest, BoxHierarchy_YieldsReferenceIds) +{ + EXPECT_EQ(countIterator(BRepGraph_RefsShellOfSolid(myGraph, BRepGraph_SolidId(0))), 1); + EXPECT_EQ(countIterator(BRepGraph_RefsFaceOfShell(myGraph, BRepGraph_ShellId(0))), 6); + EXPECT_EQ(countIterator(BRepGraph_RefsWireOfFace(myGraph, BRepGraph_FaceId(0))), 1); + EXPECT_EQ(countIterator(BRepGraph_RefsCoEdgeOfWire(myGraph, BRepGraph_WireId(0))), 4); + EXPECT_EQ(countIterator(BRepGraph_RefsVertexOfEdge(myGraph, BRepGraph_EdgeId(0))), 2); +} + +TEST_F(BRepGraph_RefsIteratorTest, CurrentId_ResolvesToExpectedEntry) +{ + BRepGraph_RefsWireOfFace anIt(myGraph, BRepGraph_FaceId(0)); + ASSERT_TRUE(anIt.More()); + + const BRepGraphInc::WireRef& aWireRef = myGraph.Refs().Wires().Entry(anIt.CurrentId()); + EXPECT_TRUE(aWireRef.WireDefId.IsValid(myGraph.Topo().Wires().Nb())); +} + +TEST(BRepGraph_RefsIteratorTestStandalone, VertexOfEdge_ExposesInternalVertexRef) +{ + BRepGraph aGraph; + aGraph.Build(wrapEdgeInFace(makeEdgeWithInternalVertex())); + + BRepGraph_EdgeId aEdgeWithInternal; + for (BRepGraph_EdgeIterator anEdgeIt(aGraph); anEdgeIt.More(); anEdgeIt.Next()) + { + if (anEdgeIt.Current().InternalVertexRefIds.Length() == 1) + { + aEdgeWithInternal = anEdgeIt.CurrentId(); + break; + } + } + + ASSERT_TRUE(aEdgeWithInternal.IsValid()); + + bool aFoundInternal = false; + int aCount = 0; + for (BRepGraph_RefsVertexOfEdge anIt(aGraph, aEdgeWithInternal); anIt.More(); anIt.Next()) + { + ++aCount; + const BRepGraphInc::VertexRef& aVertexRef = aGraph.Refs().Vertices().Entry(anIt.CurrentId()); + if (aVertexRef.Orientation == TopAbs_INTERNAL) + { + aFoundInternal = true; + } + } + + EXPECT_EQ(aCount, 3); + EXPECT_TRUE(aFoundInternal); +} + +TEST(BRepGraph_RefsIteratorTestStandalone, VertexOfFace_ExposesDirectVertexRef) +{ + BRepGraph aGraph; + aGraph.Build(makeFaceWithDirectVertex()); + + BRepGraph_RefsVertexOfFace anIt(aGraph, BRepGraph_FaceId(0)); + ASSERT_TRUE(anIt.More()); + + const BRepGraphInc::VertexRef& aVertexRef = aGraph.Refs().Vertices().Entry(anIt.CurrentId()); + EXPECT_EQ(aVertexRef.Orientation, TopAbs_INTERNAL); + EXPECT_TRUE(aVertexRef.VertexDefId.IsValid(aGraph.Topo().Vertices().Nb())); +} + +TEST_F(BRepGraph_RefsIteratorTest, ChildOfCompound_EnumeratesChildRefs) +{ + const BRepGraph_VertexId aLooseVertex = myGraph.Builder().AddVertex(gp_Pnt(1.0, 2.0, 3.0), 0.01); + ASSERT_TRUE(aLooseVertex.IsValid()); + + NCollection_Vector aChildren; + aChildren.Append(BRepGraph_SolidId(0)); + aChildren.Append(aLooseVertex); + const BRepGraph_CompoundId aCompound = myGraph.Builder().AddCompound(aChildren); + + ASSERT_TRUE(aCompound.IsValid()); + BRepGraph_RefsChildOfCompound anIt(myGraph, aCompound); + ASSERT_TRUE(anIt.More()); + EXPECT_EQ(myGraph.Refs().Children().Entry(anIt.CurrentId()).ChildDefId.NodeKind, + BRepGraph_NodeId::Kind::Solid); + anIt.Next(); + ASSERT_TRUE(anIt.More()); + EXPECT_EQ(myGraph.Refs().Children().Entry(anIt.CurrentId()).ChildDefId.NodeKind, + BRepGraph_NodeId::Kind::Vertex); +} + +TEST_F(BRepGraph_RefsIteratorTest, OccurrenceOfProduct_EnumeratesOccurrenceRefs) +{ + const BRepGraph_ProductId aPart = myGraph.Builder().AddProduct(BRepGraph_SolidId(0)); + const BRepGraph_ProductId anAssembly = myGraph.Builder().AddAssemblyProduct(); + ASSERT_TRUE(aPart.IsValid()); + ASSERT_TRUE(anAssembly.IsValid()); + + EXPECT_TRUE(myGraph.Builder().AddOccurrence(anAssembly, aPart, TopLoc_Location()).IsValid()); + EXPECT_TRUE(myGraph.Builder().AddOccurrence(anAssembly, aPart, TopLoc_Location()).IsValid()); + + EXPECT_EQ(countIterator(BRepGraph_RefsOccurrenceOfProduct(myGraph, anAssembly)), 2); +} + +TEST_F(BRepGraph_RefsIteratorTest, RemovedWireRef_IsSkipped) +{ + const NCollection_Vector& aWireRefs = + myGraph.Refs().Wires().IdsOf(BRepGraph_FaceId(0)); + ASSERT_EQ(aWireRefs.Length(), 1); + + { + BRepGraph_MutGuard aWireRef = + myGraph.Builder().MutWireRef(aWireRefs.Value(0)); + aWireRef->IsRemoved = true; + } + + EXPECT_EQ(countIterator(BRepGraph_RefsWireOfFace(myGraph, BRepGraph_FaceId(0))), 0); +} \ No newline at end of file diff --git a/src/ModelingData/TKBRep/GTests/BRepGraph_Sharing_Test.cxx b/src/ModelingData/TKBRep/GTests/BRepGraph_Sharing_Test.cxx new file mode 100644 index 0000000000..4d0807a873 --- /dev/null +++ b/src/ModelingData/TKBRep/GTests/BRepGraph_Sharing_Test.cxx @@ -0,0 +1,333 @@ +// Copyright (c) 2026 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 "BRepGraph_RefTestTools.hxx" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +// ========================================================================= +// Fixture: builds a box 10x20x30 and its BRepGraph +// ========================================================================= + +class BRepGraph_SharingTest : public testing::Test +{ +protected: + void SetUp() override + { + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + myGraph.Build(aBox); + } + + BRepGraph myGraph; +}; + +// ========================================================================= +// Edge sharing via incidence: each box edge appears in 2 wires (faces) +// ========================================================================= + +TEST_F(BRepGraph_SharingTest, EdgeDef_EachSharedByTwoFaces) +{ + ASSERT_TRUE(myGraph.IsDone()); + EXPECT_EQ(myGraph.Topo().Edges().Nb(), 12); + // In a box, each edge is shared by exactly 2 faces. + for (BRepGraph_EdgeIterator anEdgeIt(myGraph); anEdgeIt.More(); anEdgeIt.Next()) + { + const BRepGraph_EdgeId anEdgeId = anEdgeIt.CurrentId(); + const int aFaceCount = myGraph.Topo().Edges().NbFaces(anEdgeId); + EXPECT_EQ(aFaceCount, 2) << "Edge def " << anEdgeId.Index + << " expected to be shared by 2 faces, got " << aFaceCount; + } +} + +TEST_F(BRepGraph_SharingTest, FaceDef_EachHasValidSurface) +{ + ASSERT_TRUE(myGraph.IsDone()); + EXPECT_EQ(myGraph.Topo().Faces().Nb(), 6); + for (BRepGraph_FaceIterator aFaceIt(myGraph); aFaceIt.More(); aFaceIt.Next()) + { + const BRepGraph_FaceId aFaceId = aFaceIt.CurrentId(); + const BRepGraphInc::FaceDef& aDef = aFaceIt.Current(); + EXPECT_TRUE(aDef.SurfaceRepId.IsValid()) + << "Face def " << aFaceId.Index << " has no surface rep"; + } +} + +TEST_F(BRepGraph_SharingTest, SolidDef_HasOneShellRef) +{ + ASSERT_TRUE(myGraph.IsDone()); + EXPECT_EQ(myGraph.Topo().Solids().Nb(), 1); + EXPECT_EQ(BRepGraph_TestTools::CountShellRefsOfSolid(myGraph, BRepGraph_SolidId(0)), 1); +} + +TEST_F(BRepGraph_SharingTest, ShellDef_HasSixFaceRefs) +{ + ASSERT_TRUE(myGraph.IsDone()); + EXPECT_EQ(myGraph.Topo().Shells().Nb(), 1); + EXPECT_EQ(BRepGraph_TestTools::CountFaceRefsOfShell(myGraph, BRepGraph_ShellId(0)), 6); +} + +// ========================================================================= +// Containment hierarchy +// ========================================================================= + +TEST_F(BRepGraph_SharingTest, SolidDef_ContainsOneShellRef) +{ + ASSERT_TRUE(myGraph.IsDone()); + EXPECT_EQ(myGraph.Topo().Solids().Nb(), 1); + EXPECT_EQ(BRepGraph_TestTools::CountShellRefsOfSolid(myGraph, BRepGraph_SolidId(0)), 1); +} + +TEST_F(BRepGraph_SharingTest, ShellDef_ContainsSixFaceRefs) +{ + ASSERT_TRUE(myGraph.IsDone()); + EXPECT_EQ(myGraph.Topo().Shells().Nb(), 1); + EXPECT_EQ(BRepGraph_TestTools::CountFaceRefsOfShell(myGraph, BRepGraph_ShellId(0)), 6); +} + +TEST_F(BRepGraph_SharingTest, FaceDef_OuterWireIdx_Valid) +{ + ASSERT_TRUE(myGraph.IsDone()); + for (BRepGraph_FaceIterator aFaceIt(myGraph); aFaceIt.More(); aFaceIt.Next()) + { + const BRepGraph_FaceId aFaceId = aFaceIt.CurrentId(); + const BRepGraph_WireId anOuterWire = BRepGraph_TestTools::OuterWireOfFace(myGraph, aFaceId); + EXPECT_TRUE(anOuterWire.IsValid()) << "Face def " << aFaceId.Index << " has no outer wire"; + } +} + +TEST_F(BRepGraph_SharingTest, WireDef_CoEdgeRefsCount_FourPerBoxFace) +{ + ASSERT_TRUE(myGraph.IsDone()); + for (BRepGraph_WireIterator aWireIt(myGraph); aWireIt.More(); aWireIt.Next()) + { + const BRepGraph_WireId aWireId = aWireIt.CurrentId(); + const int aNbCoEdgeRefs = BRepGraph_TestTools::CountCoEdgeRefsOfWire(myGraph, aWireId); + EXPECT_GT(aNbCoEdgeRefs, 0) << "Wire def " << aWireId.Index << " has no coedge refs"; + // Box face wires have 4 edges + EXPECT_EQ(aNbCoEdgeRefs, 4) << "Wire def " << aWireId.Index + << " expected 4 coedge refs for box face"; + } +} + +TEST_F(BRepGraph_SharingTest, EdgeDef_VertexDefs_BothValid) +{ + ASSERT_TRUE(myGraph.IsDone()); + for (BRepGraph_EdgeIterator anEdgeIt(myGraph); anEdgeIt.More(); anEdgeIt.Next()) + { + const BRepGraph_EdgeId anEdgeId = anEdgeIt.CurrentId(); + EXPECT_TRUE(BRepGraph_Tool::Edge::StartVertex(myGraph, anEdgeId).VertexDefId.IsValid()) + << "Edge def " << anEdgeId.Index << " has invalid start vertex def"; + EXPECT_TRUE(BRepGraph_Tool::Edge::EndVertex(myGraph, anEdgeId).VertexDefId.IsValid()) + << "Edge def " << anEdgeId.Index << " has invalid end vertex def"; + } +} + +// ========================================================================= +// Orientation via incidence refs +// ========================================================================= + +TEST_F(BRepGraph_SharingTest, SharedEdge_IncidenceRefs_DifferentOrientation) +{ + ASSERT_TRUE(myGraph.IsDone()); + // In a box, shared edges between adjacent faces have coedges on + // different face definitions. Check that at least some edges have + // coedges referencing more than one face. + int aMultiFaceEdgeCount = 0; + for (BRepGraph_EdgeIterator anEdgeIt(myGraph); anEdgeIt.More(); anEdgeIt.Next()) + { + const BRepGraph_EdgeId anEdgeId = anEdgeIt.CurrentId(); + const NCollection_Vector& aCoEdgeIdxs = + myGraph.Topo().Edges().CoEdges(anEdgeId); + if (aCoEdgeIdxs.Length() < 2) + continue; + // Check if coedges reference different faces. + const BRepGraph_NodeId aFace0 = + myGraph.Topo().CoEdges().Definition(aCoEdgeIdxs.Value(0)).FaceDefId; + for (int aCEI = 1; aCEI < aCoEdgeIdxs.Length(); ++aCEI) + { + if (myGraph.Topo().CoEdges().Definition(aCoEdgeIdxs.Value(aCEI)).FaceDefId != aFace0) + { + ++aMultiFaceEdgeCount; + break; + } + } + } + EXPECT_GT(aMultiFaceEdgeCount, 0) + << "Expected at least some shared edges with coedges on different faces"; +} + +TEST_F(BRepGraph_SharingTest, NonClosedEdge_StartEnd_Different) +{ + ASSERT_TRUE(myGraph.IsDone()); + for (BRepGraph_EdgeIterator anEdgeIt(myGraph); anEdgeIt.More(); anEdgeIt.Next()) + { + const BRepGraph_EdgeId anEdgeId = anEdgeIt.CurrentId(); + const BRepGraphInc::EdgeDef& aDef = anEdgeIt.Current(); + if (aDef.IsDegenerate) + continue; + // Box edges are not closed, so start and end vertex defs must differ + const BRepGraph_VertexId aStartVtx = + BRepGraph_Tool::Edge::StartVertex(myGraph, anEdgeId).VertexDefId; + const BRepGraph_VertexId anEndVtx = + BRepGraph_Tool::Edge::EndVertex(myGraph, anEdgeId).VertexDefId; + EXPECT_NE(aStartVtx, anEndVtx) << "Non-degenerate edge def " << anEdgeId.Index + << " has identical start and end vertex def ids"; + } +} + +TEST_F(BRepGraph_SharingTest, VertexDef_Points_MatchExpectedBoxCorners) +{ + ASSERT_TRUE(myGraph.IsDone()); + // For a simple 10x20x30 box, all 8 vertex points should be valid. + EXPECT_EQ(myGraph.Topo().Vertices().Nb(), 8); + for (BRepGraph_VertexIterator aVertexIt(myGraph); aVertexIt.More(); aVertexIt.Next()) + { + const BRepGraphInc::VertexDef& aDef = aVertexIt.Current(); + // Verify coordinates are within the box bounds. + EXPECT_GE(aDef.Point.X(), -Precision::Confusion()); + EXPECT_LE(aDef.Point.X(), 10.0 + Precision::Confusion()); + EXPECT_GE(aDef.Point.Y(), -Precision::Confusion()); + EXPECT_LE(aDef.Point.Y(), 20.0 + Precision::Confusion()); + EXPECT_GE(aDef.Point.Z(), -Precision::Confusion()); + EXPECT_LE(aDef.Point.Z(), 30.0 + Precision::Confusion()); + } +} + +// ========================================================================= +// TShape sharing in compounds (standalone tests) +// ========================================================================= + +TEST_F(BRepGraph_SharingTest, CompoundTwoIdenticalBoxes) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + BRep_Builder aBuilder; + TopoDS_Compound aCompound; + aBuilder.MakeCompound(aCompound); + aBuilder.Add(aCompound, aBox); + aBuilder.Add(aCompound, aBox); + + BRepGraph aGraph; + aGraph.Build(aCompound); + ASSERT_TRUE(aGraph.IsDone()); + + // Same TShape added twice to compound: definition is shared (1 solid def), + // compound has 2 ChildRefs pointing to the same solid index. + EXPECT_EQ(aGraph.Topo().Solids().Nb(), 1); + + // All defs are shared (same TShape), counts stay at single-box levels. + EXPECT_EQ(aGraph.Topo().Faces().Nb(), 6); + EXPECT_EQ(aGraph.Topo().Edges().Nb(), 12); + EXPECT_EQ(aGraph.Topo().Vertices().Nb(), 8); + + // Compound has 2 child references to the same solid. + const NCollection_Vector aChildRefs = + BRepGraph_TestTools::ChildRefsOfParent(aGraph, BRepGraph_CompoundId(0)); + ASSERT_EQ(aChildRefs.Length(), 2); + EXPECT_EQ(aGraph.Refs().Children().Entry(aChildRefs.Value(0)).ChildDefId.Index, + aGraph.Refs().Children().Entry(aChildRefs.Value(1)).ChildDefId.Index); +} + +TEST_F(BRepGraph_SharingTest, CompoundTwoDistinctBoxes) +{ + BRepPrimAPI_MakeBox aBoxMaker1(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox1 = aBoxMaker1.Shape(); + + BRepPrimAPI_MakeBox aBoxMaker2(5.0, 15.0, 25.0); + const TopoDS_Shape& aBox2 = aBoxMaker2.Shape(); + + BRep_Builder aBuilder; + TopoDS_Compound aCompound; + aBuilder.MakeCompound(aCompound); + aBuilder.Add(aCompound, aBox1); + aBuilder.Add(aCompound, aBox2); + + BRepGraph aGraph; + aGraph.Build(aCompound); + ASSERT_TRUE(aGraph.IsDone()); + + // Two different TShapes: no sharing, definitions are independent + EXPECT_EQ(aGraph.Topo().Solids().Nb(), 2); + EXPECT_EQ(aGraph.Topo().Faces().Nb(), 12); + EXPECT_EQ(aGraph.Topo().Edges().Nb(), 24); + EXPECT_EQ(aGraph.Topo().Vertices().Nb(), 16); +} + +TEST_F(BRepGraph_SharingTest, CompoundWithLocation_MoreUsagesThanDefs) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + gp_Trsf aTrsf; + aTrsf.SetTranslation(gp_Vec(100.0, 0.0, 0.0)); + TopoDS_Shape aMovedBox = aBox.Moved(TopLoc_Location(aTrsf)); + + BRep_Builder aBuilder; + TopoDS_Compound aCompound; + aBuilder.MakeCompound(aCompound); + aBuilder.Add(aCompound, aBox); + aBuilder.Add(aCompound, aMovedBox); + + BRepGraph aGraph; + aGraph.Build(aCompound); + ASSERT_TRUE(aGraph.IsDone()); + + // Same TShape with different locations: defs are shared. + EXPECT_EQ(aGraph.Topo().Faces().Nb(), 6); + EXPECT_EQ(aGraph.Topo().Edges().Nb(), 12); + EXPECT_EQ(aGraph.Topo().Vertices().Nb(), 8); +} + +TEST_F(BRepGraph_SharingTest, TranslatedCopy_SameTShape_SharedDefs) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + gp_Trsf aTrsf; + aTrsf.SetTranslation(gp_Vec(50.0, 50.0, 50.0)); + TopoDS_Shape aCopy = aBox.Moved(TopLoc_Location(aTrsf)); + + BRep_Builder aBuilder; + TopoDS_Compound aCompound; + aBuilder.MakeCompound(aCompound); + aBuilder.Add(aCompound, aBox); + aBuilder.Add(aCompound, aCopy); + + BRepGraph aGraph; + aGraph.Build(aCompound); + ASSERT_TRUE(aGraph.IsDone()); + + // Moved() preserves TShape, so all definitions are shared (1 solid def). + // Compound has 2 ChildRefs with different locations. + EXPECT_EQ(aGraph.Topo().Solids().Nb(), 1); + + // Face/edge/vertex defs are shared (same TShape). + EXPECT_EQ(aGraph.Topo().Faces().Nb(), 6); + EXPECT_EQ(aGraph.Topo().Edges().Nb(), 12); + EXPECT_EQ(aGraph.Topo().Vertices().Nb(), 8); +} diff --git a/src/ModelingData/TKBRep/GTests/BRepGraph_Test.cxx b/src/ModelingData/TKBRep/GTests/BRepGraph_Test.cxx new file mode 100644 index 0000000000..fbe39c3651 --- /dev/null +++ b/src/ModelingData/TKBRep/GTests/BRepGraph_Test.cxx @@ -0,0 +1,2191 @@ +// Copyright (c) 2026 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 "BRepGraph_RefTestTools.hxx" +#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 + +#include + +namespace +{ +static_assert(!std::is_convertible_v); +static_assert(!std::is_constructible_v); +static_assert(std::is_convertible_v); +static_assert(!std::is_convertible_v); +static_assert(!std::is_constructible_v); +static_assert(std::is_convertible_v); + +const occ::handle& testDoubleAttrKind() +{ + static const occ::handle THE_KIND = + new BRepGraph_CacheKind(Standard_GUID("2f9b6a5c-1f2d-4a88-9c1c-7a0c16a10021"), + "TestDoubleAttr"); + return THE_KIND; +} + +const occ::handle& testIntAttrKind() +{ + static const occ::handle THE_KIND = + new BRepGraph_CacheKind(Standard_GUID("2f9b6a5c-1f2d-4a88-9c1c-7a0c16a10022"), "TestIntAttr"); + return THE_KIND; +} + +const occ::handle& testAuxAttrKind() +{ + static const occ::handle THE_KIND = + new BRepGraph_CacheKind(Standard_GUID("2f9b6a5c-1f2d-4a88-9c1c-7a0c16a10023"), "TestAuxAttr"); + return THE_KIND; +} + +static int componentKey(const BRepGraph_NodeId theNode) +{ + return theNode.Index * BRepGraph_NodeId::THE_KIND_COUNT + static_cast(theNode.NodeKind); +} + +static NCollection_Vector collectFreeEdges(const BRepGraph& theGraph) +{ + NCollection_Vector aResult(16); + for (BRepGraph_EdgeIterator anEdgeIt(theGraph); anEdgeIt.More(); anEdgeIt.Next()) + { + const BRepGraph_EdgeId anEdgeId = anEdgeIt.CurrentId(); + const BRepGraphInc::EdgeDef& anEdge = anEdgeIt.Current(); + if (anEdge.IsDegenerate) + { + continue; + } + + NCollection_Map aOwningFaces; + for (BRepGraph_ParentExplorer aFaceExp(theGraph, anEdgeId, BRepGraph_NodeId::Kind::Face); + aFaceExp.More(); + aFaceExp.Next()) + { + aOwningFaces.Add(aFaceExp.Current().DefId.Index); + if (aOwningFaces.Extent() > 1) + { + break; + } + } + + if (aOwningFaces.Extent() == 1) + { + aResult.Append(anEdgeId); + } + } + return aResult; +} + +static BRepGraph_NodeId componentRootOfFace(const BRepGraph& theGraph, + const BRepGraph_FaceId theFaceId) +{ + for (BRepGraph_ParentExplorer aSolidExp(theGraph, theFaceId, BRepGraph_NodeId::Kind::Solid); + aSolidExp.More(); + aSolidExp.Next()) + { + return aSolidExp.Current().DefId; + } + + for (BRepGraph_ParentExplorer aShellExp(theGraph, theFaceId, BRepGraph_NodeId::Kind::Shell); + aShellExp.More(); + aShellExp.Next()) + { + return aShellExp.Current().DefId; + } + + return theFaceId; +} + +static int countFaceComponents(const BRepGraph& theGraph) +{ + NCollection_Map aRoots; + for (BRepGraph_FaceIterator aFaceIt(theGraph); aFaceIt.More(); aFaceIt.Next()) + { + aRoots.Add(componentKey(componentRootOfFace(theGraph, aFaceIt.CurrentId()))); + } + return aRoots.Extent(); +} + +struct ReverseIndexInputData +{ + NCollection_Vector Edges; + NCollection_Vector CoEdges; + NCollection_Vector Wires; + NCollection_Vector Faces; + NCollection_Vector Shells; + NCollection_Vector Solids; + NCollection_Vector Compounds; + NCollection_Vector CompSolids; + NCollection_Vector ShellRefs; + NCollection_Vector FaceRefs; + NCollection_Vector WireRefs; + NCollection_Vector CoEdgeRefs; + NCollection_Vector SolidRefs; + NCollection_Vector ChildRefs; + NCollection_Vector VertexRefs; +}; + +static ReverseIndexInputData buildReverseIndexBaseInput() +{ + ReverseIndexInputData aData; + + BRepGraphInc::EdgeDef& anEdge = aData.Edges.Appended(); + anEdge.InitVectors(occ::handle()); + anEdge.Id = BRepGraph_EdgeId(0); + + // Create vertex ref entries for start and end vertices. + BRepGraphInc::VertexRef& aStartVRef0 = aData.VertexRefs.Appended(); + aStartVRef0.ParentId = BRepGraph_EdgeId(0); + aStartVRef0.VertexDefId = BRepGraph_VertexId(0); + aStartVRef0.Orientation = TopAbs_FORWARD; + anEdge.StartVertexRefId = BRepGraph_VertexRefId(aData.VertexRefs.Length() - 1); + + BRepGraphInc::VertexRef& anEndVRef0 = aData.VertexRefs.Appended(); + anEndVRef0.ParentId = BRepGraph_EdgeId(0); + anEndVRef0.VertexDefId = BRepGraph_VertexId(1); + anEndVRef0.Orientation = TopAbs_REVERSED; + anEdge.EndVertexRefId = BRepGraph_VertexRefId(aData.VertexRefs.Length() - 1); + + BRepGraphInc::CoEdgeDef& aCoEdge = aData.CoEdges.Appended(); + aCoEdge.InitVectors(occ::handle()); + aCoEdge.Id = BRepGraph_CoEdgeId(0); + aCoEdge.EdgeDefId = BRepGraph_EdgeId(0); + aCoEdge.FaceDefId = BRepGraph_FaceId(0); + + BRepGraphInc::WireDef& aWire = aData.Wires.Appended(); + aWire.InitVectors(occ::handle()); + aWire.Id = BRepGraph_WireId(0); + + BRepGraphInc::CoEdgeRef& aCoEdgeRef = aData.CoEdgeRefs.Appended(); + aCoEdgeRef.ParentId = BRepGraph_WireId(0); + aCoEdgeRef.CoEdgeDefId = BRepGraph_CoEdgeId(0); + aWire.CoEdgeRefIds.Append(BRepGraph_CoEdgeRefId(0)); + + BRepGraphInc::FaceDef& aFace = aData.Faces.Appended(); + aFace.InitVectors(occ::handle()); + aFace.Id = BRepGraph_FaceId(0); + + BRepGraphInc::WireRef& aWireRef = aData.WireRefs.Appended(); + aWireRef.ParentId = BRepGraph_FaceId(0); + aWireRef.WireDefId = BRepGraph_WireId(0); + aWireRef.IsOuter = true; + aFace.WireRefIds.Append(BRepGraph_WireRefId(0)); + + BRepGraphInc::ShellDef& aShell = aData.Shells.Appended(); + aShell.InitVectors(occ::handle()); + aShell.Id = BRepGraph_ShellId(0); + + BRepGraphInc::FaceRef& aFaceRef = aData.FaceRefs.Appended(); + aFaceRef.ParentId = BRepGraph_ShellId(0); + aFaceRef.FaceDefId = BRepGraph_FaceId(0); + aShell.FaceRefIds.Append(BRepGraph_FaceRefId(0)); + + BRepGraphInc::SolidDef& aSolid = aData.Solids.Appended(); + aSolid.InitVectors(occ::handle()); + aSolid.Id = BRepGraph_SolidId(0); + + BRepGraphInc::ShellRef& aShellRef = aData.ShellRefs.Appended(); + aShellRef.ParentId = BRepGraph_SolidId(0); + aShellRef.ShellDefId = BRepGraph_ShellId(0); + aSolid.ShellRefIds.Append(BRepGraph_ShellRefId(0)); + + return aData; +} + +static void appendReverseIndexDeltaInput(ReverseIndexInputData& theData) +{ + // Active edge/wire/face/shell/solid chain. + BRepGraphInc::EdgeDef& anEdge = theData.Edges.Appended(); + anEdge.InitVectors(occ::handle()); + anEdge.Id = BRepGraph_EdgeId(1); + + BRepGraphInc::VertexRef& aStartVRef1 = theData.VertexRefs.Appended(); + aStartVRef1.ParentId = BRepGraph_EdgeId(1); + aStartVRef1.VertexDefId = BRepGraph_VertexId(2); + aStartVRef1.Orientation = TopAbs_FORWARD; + anEdge.StartVertexRefId = BRepGraph_VertexRefId(theData.VertexRefs.Length() - 1); + + BRepGraphInc::VertexRef& anEndVRef1 = theData.VertexRefs.Appended(); + anEndVRef1.ParentId = BRepGraph_EdgeId(1); + anEndVRef1.VertexDefId = BRepGraph_VertexId(3); + anEndVRef1.Orientation = TopAbs_REVERSED; + anEdge.EndVertexRefId = BRepGraph_VertexRefId(theData.VertexRefs.Length() - 1); + + BRepGraphInc::CoEdgeDef& aCoEdge = theData.CoEdges.Appended(); + aCoEdge.InitVectors(occ::handle()); + aCoEdge.Id = BRepGraph_CoEdgeId(1); + aCoEdge.EdgeDefId = BRepGraph_EdgeId(1); + aCoEdge.FaceDefId = BRepGraph_FaceId(1); + + BRepGraphInc::WireDef& aWire = theData.Wires.Appended(); + aWire.InitVectors(occ::handle()); + aWire.Id = BRepGraph_WireId(1); + + BRepGraphInc::CoEdgeRef& aCoEdgeRef = theData.CoEdgeRefs.Appended(); + aCoEdgeRef.ParentId = BRepGraph_WireId(1); + aCoEdgeRef.CoEdgeDefId = BRepGraph_CoEdgeId(1); + aWire.CoEdgeRefIds.Append(BRepGraph_CoEdgeRefId(theData.CoEdgeRefs.Length() - 1)); + + BRepGraphInc::FaceDef& aFace = theData.Faces.Appended(); + aFace.InitVectors(occ::handle()); + aFace.Id = BRepGraph_FaceId(1); + + BRepGraphInc::WireRef& aWireRef = theData.WireRefs.Appended(); + aWireRef.ParentId = BRepGraph_FaceId(1); + aWireRef.WireDefId = BRepGraph_WireId(1); + aWireRef.IsOuter = true; + aFace.WireRefIds.Append(BRepGraph_WireRefId(theData.WireRefs.Length() - 1)); + + BRepGraphInc::ShellDef& aShell = theData.Shells.Appended(); + aShell.InitVectors(occ::handle()); + aShell.Id = BRepGraph_ShellId(1); + + BRepGraphInc::FaceRef& aFaceRef = theData.FaceRefs.Appended(); + aFaceRef.ParentId = BRepGraph_ShellId(1); + aFaceRef.FaceDefId = BRepGraph_FaceId(1); + aShell.FaceRefIds.Append(BRepGraph_FaceRefId(theData.FaceRefs.Length() - 1)); + + BRepGraphInc::SolidDef& aSolid = theData.Solids.Appended(); + aSolid.InitVectors(occ::handle()); + aSolid.Id = BRepGraph_SolidId(1); + + BRepGraphInc::ShellRef& aShellRef = theData.ShellRefs.Appended(); + aShellRef.ParentId = BRepGraph_SolidId(1); + aShellRef.ShellDefId = BRepGraph_ShellId(1); + aSolid.ShellRefIds.Append(BRepGraph_ShellRefId(theData.ShellRefs.Length() - 1)); + + // Removed entities to ensure BuildDelta skips them. + BRepGraphInc::EdgeDef& aRemovedEdge = theData.Edges.Appended(); + aRemovedEdge.InitVectors(occ::handle()); + aRemovedEdge.Id = BRepGraph_EdgeId(2); + aRemovedEdge.IsRemoved = true; + + BRepGraphInc::VertexRef& aRemovedStartVRef = theData.VertexRefs.Appended(); + aRemovedStartVRef.ParentId = BRepGraph_EdgeId(2); + aRemovedStartVRef.VertexDefId = BRepGraph_VertexId(10); + aRemovedStartVRef.Orientation = TopAbs_FORWARD; + aRemovedEdge.StartVertexRefId = BRepGraph_VertexRefId(theData.VertexRefs.Length() - 1); + + BRepGraphInc::VertexRef& aRemovedEndVRef = theData.VertexRefs.Appended(); + aRemovedEndVRef.ParentId = BRepGraph_EdgeId(2); + aRemovedEndVRef.VertexDefId = BRepGraph_VertexId(11); + aRemovedEndVRef.Orientation = TopAbs_REVERSED; + aRemovedEdge.EndVertexRefId = BRepGraph_VertexRefId(theData.VertexRefs.Length() - 1); + + BRepGraphInc::CoEdgeDef& aRemovedCoEdge = theData.CoEdges.Appended(); + aRemovedCoEdge.InitVectors(occ::handle()); + aRemovedCoEdge.Id = BRepGraph_CoEdgeId(2); + aRemovedCoEdge.IsRemoved = true; + aRemovedCoEdge.EdgeDefId = BRepGraph_EdgeId(2); + aRemovedCoEdge.FaceDefId = BRepGraph_FaceId(2); + + BRepGraphInc::WireDef& aRemovedWire = theData.Wires.Appended(); + aRemovedWire.InitVectors(occ::handle()); + aRemovedWire.Id = BRepGraph_WireId(2); + aRemovedWire.IsRemoved = true; + + BRepGraphInc::CoEdgeRef& aRemovedCoEdgeRef = theData.CoEdgeRefs.Appended(); + aRemovedCoEdgeRef.ParentId = BRepGraph_WireId(2); + aRemovedCoEdgeRef.CoEdgeDefId = BRepGraph_CoEdgeId(2); + aRemovedCoEdgeRef.IsRemoved = true; + + BRepGraphInc::FaceDef& aRemovedFace = theData.Faces.Appended(); + aRemovedFace.InitVectors(occ::handle()); + aRemovedFace.Id = BRepGraph_FaceId(2); + aRemovedFace.IsRemoved = true; + + BRepGraphInc::WireRef& aRemovedWireRef = theData.WireRefs.Appended(); + aRemovedWireRef.ParentId = BRepGraph_FaceId(2); + aRemovedWireRef.WireDefId = BRepGraph_WireId(2); + aRemovedWireRef.IsRemoved = true; + + BRepGraphInc::ShellDef& aRemovedShell = theData.Shells.Appended(); + aRemovedShell.InitVectors(occ::handle()); + aRemovedShell.Id = BRepGraph_ShellId(2); + aRemovedShell.IsRemoved = true; + + BRepGraphInc::FaceRef& aRemovedFaceRef = theData.FaceRefs.Appended(); + aRemovedFaceRef.ParentId = BRepGraph_ShellId(2); + aRemovedFaceRef.FaceDefId = BRepGraph_FaceId(2); + aRemovedFaceRef.IsRemoved = true; + + BRepGraphInc::SolidDef& aRemovedSolid = theData.Solids.Appended(); + aRemovedSolid.InitVectors(occ::handle()); + aRemovedSolid.Id = BRepGraph_SolidId(2); + aRemovedSolid.IsRemoved = true; + + BRepGraphInc::ShellRef& aRemovedShellRef = theData.ShellRefs.Appended(); + aRemovedShellRef.ParentId = BRepGraph_SolidId(2); + aRemovedShellRef.ShellDefId = BRepGraph_ShellId(2); + aRemovedShellRef.IsRemoved = true; +} + +static void verifyBuildDeltaScenario(const occ::handle& theAllocator) +{ + ReverseIndexInputData aData = buildReverseIndexBaseInput(); + + BRepGraphInc_ReverseIndex aRevIdx; + aRevIdx.SetAllocator(theAllocator); + aRevIdx.Build(aData.Edges, + aData.CoEdges, + aData.Wires, + aData.Faces, + aData.Shells, + aData.Solids, + aData.Compounds, + aData.CompSolids, + aData.ShellRefs, + aData.FaceRefs, + aData.WireRefs, + aData.CoEdgeRefs, + aData.SolidRefs, + aData.ChildRefs, + aData.VertexRefs); + + EXPECT_TRUE(aRevIdx.Validate(aData.Edges, + aData.CoEdges, + aData.Wires, + aData.Faces, + aData.Shells, + aData.Solids, + aData.ShellRefs, + aData.FaceRefs, + aData.WireRefs, + aData.CoEdgeRefs, + aData.VertexRefs)); + + const NCollection_Vector* aBaseWires = aRevIdx.WiresOfEdge(BRepGraph_EdgeId(0)); + ASSERT_NE(aBaseWires, nullptr); + ASSERT_EQ(aBaseWires->Length(), 1); + EXPECT_EQ(aBaseWires->Value(0), BRepGraph_WireId(0)); + + const int anOldNbEdges = aData.Edges.Length(); + const int anOldNbWires = aData.Wires.Length(); + const int anOldNbFaces = aData.Faces.Length(); + const int anOldNbShells = aData.Shells.Length(); + const int anOldNbSolids = aData.Solids.Length(); + + appendReverseIndexDeltaInput(aData); + + aRevIdx.BuildDelta(aData.Edges, + aData.CoEdges, + aData.Wires, + aData.Faces, + aData.Shells, + aData.Solids, + aData.ShellRefs, + aData.FaceRefs, + aData.WireRefs, + aData.CoEdgeRefs, + aData.VertexRefs, + anOldNbEdges, + anOldNbWires, + anOldNbFaces, + anOldNbShells, + anOldNbSolids); + + EXPECT_TRUE(aRevIdx.Validate(aData.Edges, + aData.CoEdges, + aData.Wires, + aData.Faces, + aData.Shells, + aData.Solids, + aData.ShellRefs, + aData.FaceRefs, + aData.WireRefs, + aData.CoEdgeRefs, + aData.VertexRefs)); + + const NCollection_Vector* aActiveWires = + aRevIdx.WiresOfEdge(BRepGraph_EdgeId(1)); + ASSERT_NE(aActiveWires, nullptr); + ASSERT_EQ(aActiveWires->Length(), 1); + EXPECT_EQ(aActiveWires->Value(0), BRepGraph_WireId(1)); + + const NCollection_Vector* aActiveFaces = + aRevIdx.FacesOfWire(BRepGraph_WireId(1)); + ASSERT_NE(aActiveFaces, nullptr); + ASSERT_EQ(aActiveFaces->Length(), 1); + EXPECT_EQ(aActiveFaces->Value(0), BRepGraph_FaceId(1)); + + const NCollection_Vector* anActiveShells = + aRevIdx.ShellsOfFace(BRepGraph_FaceId(1)); + ASSERT_NE(anActiveShells, nullptr); + ASSERT_EQ(anActiveShells->Length(), 1); + EXPECT_EQ(anActiveShells->Value(0), BRepGraph_ShellId(1)); + + const NCollection_Vector* anActiveSolids = + aRevIdx.SolidsOfShell(BRepGraph_ShellId(1)); + ASSERT_NE(anActiveSolids, nullptr); + ASSERT_EQ(anActiveSolids->Length(), 1); + EXPECT_EQ(anActiveSolids->Value(0), BRepGraph_SolidId(1)); + + EXPECT_EQ(aRevIdx.NbFacesOfEdge(BRepGraph_EdgeId(1)), 1); + + EXPECT_EQ(aRevIdx.WiresOfEdge(BRepGraph_EdgeId(2)), nullptr); + EXPECT_EQ(aRevIdx.EdgesOfVertex(BRepGraph_VertexId(10)), nullptr); + EXPECT_EQ(aRevIdx.NbFacesOfEdge(BRepGraph_EdgeId(2)), 0); +} +} // namespace + +class BRepGraphTest : public testing::Test +{ +protected: + void SetUp() override + { + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + myGraph.Build(aBox); + } + + BRepGraph myGraph; +}; + +TEST_F(BRepGraphTest, FaceCountMatchesFacesVector_AfterBindUnbindSequence) +{ + ReverseIndexInputData aData = buildReverseIndexBaseInput(); + + // Add a second active face for bind/unbind sequence checks. + BRepGraphInc::FaceDef& aFace1 = aData.Faces.Appended(); + aFace1.InitVectors(occ::handle()); + aFace1.Id = BRepGraph_FaceId(1); + + BRepGraphInc_ReverseIndex aRevIdx; + aRevIdx.Build(aData.Edges, + aData.CoEdges, + aData.Wires, + aData.Faces, + aData.Shells, + aData.Solids, + aData.Compounds, + aData.CompSolids, + aData.ShellRefs, + aData.FaceRefs, + aData.WireRefs, + aData.CoEdgeRefs, + aData.SolidRefs, + aData.ChildRefs, + aData.VertexRefs); + + auto expectCountsMatch = [&]() { + for (int anEdgeIdx = 0; anEdgeIdx < aData.Edges.Length(); ++anEdgeIdx) + { + const NCollection_Vector* aFaces = + aRevIdx.FacesOfEdge(BRepGraph_EdgeId(anEdgeIdx)); + const int aExpectedCount = (aFaces == nullptr) ? 0 : aFaces->Length(); + EXPECT_EQ(aRevIdx.NbFacesOfEdge(BRepGraph_EdgeId(anEdgeIdx)), aExpectedCount) + << "Edge " << anEdgeIdx << " face-count cache mismatch"; + } + }; + + expectCountsMatch(); + + // Duplicate bind should be idempotent. + aRevIdx.BindEdgeToFace(BRepGraph_EdgeId(0), BRepGraph_FaceId(0)); + expectCountsMatch(); + + // Bind/unbind/rebind sequence must keep cached count consistent. + aRevIdx.BindEdgeToFace(BRepGraph_EdgeId(0), BRepGraph_FaceId(1)); + expectCountsMatch(); + aRevIdx.UnbindEdgeFromFace(BRepGraph_EdgeId(0), BRepGraph_FaceId(0)); + expectCountsMatch(); + aRevIdx.BindEdgeToFace(BRepGraph_EdgeId(0), BRepGraph_FaceId(0)); + expectCountsMatch(); + aRevIdx.UnbindEdgeFromFace(BRepGraph_EdgeId(0), BRepGraph_FaceId(1)); + expectCountsMatch(); +} + +TEST_F(BRepGraphTest, ReverseIndexValidate_DetectsStaleEdgeWireMapping) +{ + ReverseIndexInputData aData = buildReverseIndexBaseInput(); + + BRepGraphInc_ReverseIndex aRevIdx; + aRevIdx.Build(aData.Edges, + aData.CoEdges, + aData.Wires, + aData.Faces, + aData.Shells, + aData.Solids, + aData.Compounds, + aData.CompSolids, + aData.ShellRefs, + aData.FaceRefs, + aData.WireRefs, + aData.CoEdgeRefs, + aData.SolidRefs, + aData.ChildRefs, + aData.VertexRefs); + + ASSERT_TRUE(aRevIdx.Validate(aData.Edges, + aData.CoEdges, + aData.Wires, + aData.Faces, + aData.Shells, + aData.Solids, + aData.ShellRefs, + aData.FaceRefs, + aData.WireRefs, + aData.CoEdgeRefs, + aData.VertexRefs)); + + // Inject stale reverse entry (edge 0 -> wire 1) with no matching forward coedge ref. + BRepGraphInc::WireDef& aWire1 = aData.Wires.Appended(); + aWire1.InitVectors(occ::handle()); + aWire1.Id = BRepGraph_WireId(1); + + aRevIdx.BindEdgeToWire(BRepGraph_EdgeId(0), BRepGraph_WireId(1)); + + EXPECT_FALSE(aRevIdx.Validate(aData.Edges, + aData.CoEdges, + aData.Wires, + aData.Faces, + aData.Shells, + aData.Solids, + aData.ShellRefs, + aData.FaceRefs, + aData.WireRefs, + aData.CoEdgeRefs, + aData.VertexRefs)); +} + +TEST_F(BRepGraphTest, Build_SimpleBox_IsDone) +{ + EXPECT_TRUE(myGraph.IsDone()); +} + +TEST_F(BRepGraphTest, BuildDelta_ValidateAndSkipRemoved_NullAllocator) +{ + verifyBuildDeltaScenario(occ::handle()); +} + +TEST_F(BRepGraphTest, BuildDelta_ValidateAndSkipRemoved_WithAllocator) +{ + const occ::handle anAllocator = new NCollection_IncAllocator(); + verifyBuildDeltaScenario(anAllocator); +} + +TEST_F(BRepGraphTest, Build_SimpleBox_CorrectCounts) +{ + EXPECT_EQ(myGraph.Topo().Solids().Nb(), 1); + EXPECT_EQ(myGraph.Topo().Shells().Nb(), 1); + EXPECT_EQ(myGraph.Topo().Faces().Nb(), 6); + EXPECT_EQ(myGraph.Topo().Wires().Nb(), 6); + EXPECT_EQ(myGraph.Topo().Edges().Nb(), 12); + EXPECT_EQ(myGraph.Topo().Vertices().Nb(), 8); +} + +TEST_F(BRepGraphTest, Build_SimpleBox_SurfaceCount) +{ + EXPECT_EQ(myGraph.Topo().Faces().Nb(), 6); +} + +TEST_F(BRepGraphTest, Face_Surface_IsValid) +{ + for (BRepGraph_FaceIterator aFaceIt(myGraph); aFaceIt.More(); aFaceIt.Next()) + { + EXPECT_TRUE(BRepGraph_Tool::Face::HasSurface(myGraph, aFaceIt.CurrentId())) + << "Face " << aFaceIt.CurrentId().Index << " has no surface rep"; + } +} + +TEST_F(BRepGraphTest, Edge_CurveAndVertices_AreValid) +{ + for (BRepGraph_EdgeIterator anEdgeIt(myGraph); anEdgeIt.More(); anEdgeIt.Next()) + { + const BRepGraph_EdgeId anEdgeId = anEdgeIt.CurrentId(); + if (!BRepGraph_Tool::Edge::Degenerated(myGraph, anEdgeId)) + { + EXPECT_TRUE(BRepGraph_Tool::Edge::HasCurve(myGraph, anEdgeId)) + << "Edge " << anEdgeId.Index << " has no Curve3D rep"; + } + EXPECT_TRUE(BRepGraph_Tool::Edge::StartVertex(myGraph, anEdgeId).VertexDefId.IsValid()) + << "Edge " << anEdgeId.Index << " has invalid StartVertexId"; + EXPECT_TRUE(BRepGraph_Tool::Edge::EndVertex(myGraph, anEdgeId).VertexDefId.IsValid()) + << "Edge " << anEdgeId.Index << " has invalid EndVertexId"; + } +} + +TEST_F(BRepGraphTest, Wire_OuterWire_Exists) +{ + for (BRepGraph_FaceIterator aFaceIt(myGraph); aFaceIt.More(); aFaceIt.Next()) + { + const BRepGraph_WireId anOuterWire = + BRepGraph_TestTools::OuterWireOfFace(myGraph, aFaceIt.CurrentId()); + EXPECT_TRUE(anOuterWire.IsValid()) + << "Face " << aFaceIt.CurrentId().Index << " has no outer wire"; + } +} + +TEST_F(BRepGraphTest, FaceDef_HasValidSurface) +{ + for (BRepGraph_FaceIterator aFaceIt(myGraph); aFaceIt.More(); aFaceIt.Next()) + { + EXPECT_TRUE(BRepGraph_Tool::Face::HasSurface(myGraph, aFaceIt.CurrentId())) + << "Face " << aFaceIt.CurrentId().Index << " has no surface rep"; + } +} + +TEST_F(BRepGraphTest, FindPCurve_ValidPair) +{ + for (BRepGraph_FaceIterator aFaceIt(myGraph); aFaceIt.More(); aFaceIt.Next()) + { + const BRepGraph_WireId anOuterWire = + BRepGraph_TestTools::OuterWireOfFace(myGraph, aFaceIt.CurrentId()); + if (!anOuterWire.IsValid()) + continue; + const NCollection_Vector aCoEdgeRefs = + BRepGraph_TestTools::CoEdgeRefsOfWire(myGraph, anOuterWire); + for (const BRepGraph_CoEdgeRefId& aCoEdgeRefId : aCoEdgeRefs) + { + const BRepGraphInc::CoEdgeRef& aCR = myGraph.Refs().CoEdges().Entry(aCoEdgeRefId); + const BRepGraphInc::CoEdgeDef& aCoEdge = myGraph.Topo().CoEdges().Definition(aCR.CoEdgeDefId); + if (BRepGraph_Tool::Edge::Degenerated(myGraph, BRepGraph_EdgeId(aCoEdge.EdgeDefId))) + continue; + const BRepGraphInc::CoEdgeDef* aPCurveEntry = + BRepGraph_Tool::Edge::FindPCurve(myGraph, + BRepGraph_EdgeId(aCoEdge.EdgeDefId), + aFaceIt.CurrentId()); + EXPECT_NE(aPCurveEntry, nullptr) << "Missing PCurve for edge " << aCoEdge.EdgeDefId.Index + << " on face " << aFaceIt.CurrentId().Index; + } + } +} + +TEST_F(BRepGraphTest, UID_Unique) +{ + NCollection_Map aUIDSet; + for (BRepGraph_SolidIterator aSolidIt(myGraph); aSolidIt.More(); aSolidIt.Next()) + EXPECT_TRUE(aUIDSet.Add(myGraph.UIDs().Of(BRepGraph_NodeId(aSolidIt.CurrentId())))); + for (BRepGraph_ShellIterator aShellIt(myGraph); aShellIt.More(); aShellIt.Next()) + EXPECT_TRUE(aUIDSet.Add(myGraph.UIDs().Of(BRepGraph_NodeId(aShellIt.CurrentId())))); + for (BRepGraph_FaceIterator aFaceIt(myGraph); aFaceIt.More(); aFaceIt.Next()) + EXPECT_TRUE(aUIDSet.Add(myGraph.UIDs().Of(BRepGraph_NodeId(aFaceIt.CurrentId())))); + for (BRepGraph_WireIterator aWireIt(myGraph); aWireIt.More(); aWireIt.Next()) + EXPECT_TRUE(aUIDSet.Add(myGraph.UIDs().Of(BRepGraph_NodeId(aWireIt.CurrentId())))); + for (BRepGraph_EdgeIterator anEdgeIt(myGraph); anEdgeIt.More(); anEdgeIt.Next()) + EXPECT_TRUE(aUIDSet.Add(myGraph.UIDs().Of(BRepGraph_NodeId(anEdgeIt.CurrentId())))); + for (BRepGraph_VertexIterator aVertexIt(myGraph); aVertexIt.More(); aVertexIt.Next()) + EXPECT_TRUE(aUIDSet.Add(myGraph.UIDs().Of(BRepGraph_NodeId(aVertexIt.CurrentId())))); + // Surface/Curve geometry UIDs are no longer separate nodes; geometry is stored inline on defs. +} + +TEST_F(BRepGraphTest, UID_NodeIdRoundTrip) +{ + for (BRepGraph_FaceIterator aFaceIt(myGraph); aFaceIt.More(); aFaceIt.Next()) + { + BRepGraph_NodeId aFaceId = BRepGraph_NodeId(aFaceIt.CurrentId()); + const BRepGraph_UID& aUID = myGraph.UIDs().Of(aFaceId); + BRepGraph_NodeId aResolved = myGraph.UIDs().NodeIdFrom(aUID); + EXPECT_EQ(aResolved, aFaceId) << "Round trip failed for face " << aFaceIt.CurrentId().Index; + } +} + +TEST_F(BRepGraphTest, SameDomainFaces_Box_Empty) +{ + for (BRepGraph_FaceIterator aFaceIt(myGraph); aFaceIt.More(); aFaceIt.Next()) + { + BRepGraph_FaceId aFaceId = aFaceIt.CurrentId(); + NCollection_Vector aSameDomain = + myGraph.Topo().Faces().SameDomain(aFaceId, myGraph.Allocator()); + EXPECT_EQ(aSameDomain.Length(), 0) + << "Box face " << aFaceId.Index << " should have no same-domain faces"; + } +} + +TEST_F(BRepGraphTest, Decompose_TwoSeparateFaces) +{ + BRepPrimAPI_MakeBox aBox1(10.0, 10.0, 10.0); + BRepPrimAPI_MakeBox aBox2(20.0, 20.0, 20.0); + + TopExp_Explorer anExp1(aBox1.Shape(), TopAbs_FACE); + TopExp_Explorer anExp2(aBox2.Shape(), TopAbs_FACE); + const TopoDS_Face& aFace1 = TopoDS::Face(anExp1.Current()); + const TopoDS_Face& aFace2 = TopoDS::Face(anExp2.Current()); + + BRep_Builder aBuilder; + TopoDS_Compound aCompound; + aBuilder.MakeCompound(aCompound); + aBuilder.Add(aCompound, aFace1); + aBuilder.Add(aCompound, aFace2); + + BRepGraph aGraph; + aGraph.Build(aCompound); + ASSERT_TRUE(aGraph.IsDone()); + EXPECT_EQ(aGraph.Topo().Faces().Nb(), 2); + + EXPECT_EQ(countFaceComponents(aGraph), 2); +} + +TEST_F(BRepGraphTest, UserAttribute_SetGet) +{ + BRepGraph_NodeId aFaceId(BRepGraph_NodeId::Kind::Face, 0); + + Handle(BRepGraph_TypedCacheValue) anAttr = new BRepGraph_TypedCacheValue(3.14); + myGraph.Cache().Set(aFaceId, testDoubleAttrKind(), anAttr); + + occ::handle aRetrieved = myGraph.Cache().Get(aFaceId, testDoubleAttrKind()); + ASSERT_FALSE(aRetrieved.IsNull()); + + Handle(BRepGraph_TypedCacheValue) aTyped = + Handle(BRepGraph_TypedCacheValue)::DownCast(aRetrieved); + ASSERT_FALSE(aTyped.IsNull()); + EXPECT_NEAR(aTyped->UncheckedValue(), 3.14, 1.0e-10); +} + +TEST_F(BRepGraphTest, ReBuild_UIDMonotonic) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + const uint32_t aGen1 = aGraph.UIDs().Generation(); + + // Access a UID from the first build to verify it works. + ASSERT_GT(aGraph.Topo().Faces().Nb(), 0); + const BRepGraph_NodeId aFirstFace(BRepGraph_NodeId::Kind::Face, 0); + const BRepGraph_UID aFirstUID = aGraph.UIDs().Of(aFirstFace); + EXPECT_TRUE(aFirstUID.IsValid()); + EXPECT_EQ(aFirstUID.Generation(), aGen1); + + aGraph.Build(aBox); + const uint32_t aGen2 = aGraph.UIDs().Generation(); + + EXPECT_GT(aGen2, aGen1); + + // Verify all face UIDs in second build are valid and have new generation. + for (BRepGraph_FaceIterator aFaceIt(aGraph); aFaceIt.More(); aFaceIt.Next()) + { + BRepGraph_NodeId aNodeId(aFaceIt.CurrentId()); + BRepGraph_UID aUID = aGraph.UIDs().Of(aNodeId); + EXPECT_TRUE(aUID.IsValid()) << "Face " << aFaceIt.CurrentId().Index + << " should have a valid UID"; + EXPECT_EQ(aUID.Generation(), aGen2) + << "Face " << aFaceIt.CurrentId().Index << " UID should have new generation"; + } + + // First build's UID should no longer be valid in the new generation. + EXPECT_FALSE(aGraph.UIDs().Has(aFirstUID)); +} + +TEST_F(BRepGraphTest, DetectMissingPCurves_ValidBox_Empty) +{ + NCollection_Vector> aMissing(16); + for (BRepGraph_FaceIterator aFaceIt(myGraph); aFaceIt.More(); aFaceIt.Next()) + { + const BRepGraph_FaceId aFaceId = aFaceIt.CurrentId(); + for (BRepGraph_ChildExplorer anEdgeExp(myGraph, aFaceId, BRepGraph_NodeId::Kind::Edge); + anEdgeExp.More(); + anEdgeExp.Next()) + { + const BRepGraph_EdgeId anEdgeId = BRepGraph_EdgeId::FromNodeId(anEdgeExp.Current().DefId); + const BRepGraphInc::EdgeDef& anEdge = myGraph.Topo().Edges().Definition(anEdgeId); + if (anEdge.IsDegenerate) + { + continue; + } + + if (BRepGraph_Tool::Edge::FindPCurve(myGraph, anEdgeId, aFaceId) == nullptr) + { + aMissing.Append(std::make_pair(anEdgeId, aFaceId)); + } + } + } + EXPECT_EQ(aMissing.Length(), 0); +} + +TEST_F(BRepGraphTest, DetectDegenerateWires_ValidBox_Empty) +{ + NCollection_Vector aDegenerate(16); + for (BRepGraph_WireIterator aWireIt(myGraph); aWireIt.More(); aWireIt.Next()) + { + const BRepGraph_WireId aWireId = aWireIt.CurrentId(); + int aNbEdges = 0; + for (BRepGraph_ChildExplorer anEdgeExp(myGraph, aWireId, BRepGraph_NodeId::Kind::Edge); + anEdgeExp.More(); + anEdgeExp.Next()) + { + ++aNbEdges; + } + + if (aNbEdges < 2) + { + aDegenerate.Append(aWireId); + continue; + } + + bool isOuterWire = false; + for (BRepGraph_ParentExplorer aFaceExp(myGraph, aWireId, BRepGraph_NodeId::Kind::Face); + aFaceExp.More(); + aFaceExp.Next()) + { + const BRepGraph_FaceId aFaceId = BRepGraph_FaceId::FromNodeId(aFaceExp.Current().DefId); + if (myGraph.Topo().Faces().OuterWire(aFaceId) == aWireId) + { + isOuterWire = true; + break; + } + } + + if (isOuterWire && !aWireIt.Current().IsClosed) + { + aDegenerate.Append(aWireId); + } + } + EXPECT_EQ(aDegenerate.Length(), 0); +} + +TEST_F(BRepGraphTest, MutableEdge_ModifyTolerance) +{ + double anOrigTol = BRepGraph_Tool::Edge::Tolerance(myGraph, BRepGraph_EdgeId(0)); + BRepGraph_MutGuard anEdge = myGraph.Builder().MutEdge(BRepGraph_EdgeId(0)); + anEdge->Tolerance = anOrigTol * 2.0; + EXPECT_NEAR(BRepGraph_Tool::Edge::Tolerance(myGraph, BRepGraph_EdgeId(0)), + anOrigTol * 2.0, + 1.0e-15); +} + +TEST_F(BRepGraphTest, NbFacesOfEdge_SharedEdge) +{ + // In a box, each non-degenerate edge is shared by exactly 2 faces. + for (BRepGraph_EdgeIterator anEdgeIt(myGraph); anEdgeIt.More(); anEdgeIt.Next()) + { + if (!BRepGraph_Tool::Edge::Degenerated(myGraph, anEdgeIt.CurrentId())) + { + const int aCount = myGraph.Topo().Edges().NbFaces(anEdgeIt.CurrentId()); + EXPECT_EQ(aCount, 2) << "Edge " << anEdgeIt.CurrentId().Index + << " should be shared by 2 faces"; + } + } +} + +TEST_F(BRepGraphTest, FreeEdges_ClosedBox_Empty) +{ + NCollection_Vector aFree = collectFreeEdges(myGraph); + EXPECT_EQ(aFree.Length(), 0); +} + +TEST_F(BRepGraphTest, RecordHistory_BasicEntry) +{ + int aBefore = myGraph.History().NbRecords(); + BRepGraph_NodeId anEdge0(BRepGraph_NodeId::Kind::Edge, 0); + BRepGraph_NodeId anEdge1(BRepGraph_NodeId::Kind::Edge, 1); + NCollection_Vector aRepl; + aRepl.Append(anEdge1); + myGraph.History().Record("TestOp", anEdge0, aRepl); + EXPECT_EQ(myGraph.History().NbRecords(), aBefore + 1); + EXPECT_TRUE(myGraph.History().Record(aBefore).OperationName.IsEqual("TestOp")); +} + +TEST_F(BRepGraphTest, ReplaceEdgeInWire_Substitution) +{ + // Get the first wire and its first edge via incidence refs. + const NCollection_Vector aCoEdgeRefs = + BRepGraph_TestTools::CoEdgeRefsOfWire(myGraph, BRepGraph_WireId(0)); + ASSERT_GE(aCoEdgeRefs.Length(), 1); + const BRepGraphInc::CoEdgeRef& aOldCR = myGraph.Refs().CoEdges().Entry(aCoEdgeRefs.Value(0)); + const BRepGraphInc::CoEdgeDef& anOldCoEdge = + myGraph.Topo().CoEdges().Definition(aOldCR.CoEdgeDefId); + const BRepGraph_EdgeId anOldEdgeId = anOldCoEdge.EdgeDefId; + + // Pick a different edge to substitute. + const BRepGraph_EdgeId aNewEdgeId((anOldEdgeId.Index + 1) % myGraph.Topo().Edges().Nb()); + + myGraph.Builder().ReplaceEdgeInWire(BRepGraph_WireId(0), anOldEdgeId, aNewEdgeId, false); + + // Verify the substitution via the updated incidence refs. + const NCollection_Vector aCoEdgeRefsAfter = + BRepGraph_TestTools::CoEdgeRefsOfWire(myGraph, BRepGraph_WireId(0)); + ASSERT_GE(aCoEdgeRefsAfter.Length(), 1); + const BRepGraphInc::CoEdgeRef& aNewCR = myGraph.Refs().CoEdges().Entry(aCoEdgeRefsAfter.Value(0)); + const BRepGraphInc::CoEdgeDef& aNewCoEdge = + myGraph.Topo().CoEdges().Definition(aNewCR.CoEdgeDefId); + EXPECT_EQ(aNewCoEdge.EdgeDefId.Index, aNewEdgeId.Index); +} + +TEST_F(BRepGraphTest, ParallelBuild_SameAsSequential) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + BRepGraph aSeqGraph; + aSeqGraph.Build(aBox, false); + ASSERT_TRUE(aSeqGraph.IsDone()); + + BRepGraph aParGraph; + aParGraph.Build(aBox, true); + ASSERT_TRUE(aParGraph.IsDone()); + + EXPECT_EQ(aParGraph.Topo().Solids().Nb(), aSeqGraph.Topo().Solids().Nb()); + EXPECT_EQ(aParGraph.Topo().Shells().Nb(), aSeqGraph.Topo().Shells().Nb()); + EXPECT_EQ(aParGraph.Topo().Faces().Nb(), aSeqGraph.Topo().Faces().Nb()); + EXPECT_EQ(aParGraph.Topo().Wires().Nb(), aSeqGraph.Topo().Wires().Nb()); + EXPECT_EQ(aParGraph.Topo().Edges().Nb(), aSeqGraph.Topo().Edges().Nb()); + EXPECT_EQ(aParGraph.Topo().Vertices().Nb(), aSeqGraph.Topo().Vertices().Nb()); + EXPECT_EQ(aParGraph.Topo().Faces().Nb(), aSeqGraph.Topo().Faces().Nb()); + EXPECT_EQ(aParGraph.Topo().Edges().Nb(), aSeqGraph.Topo().Edges().Nb()); +} + +TEST_F(BRepGraphTest, ParallelBuild_CompoundOfFaces) +{ + BRepPrimAPI_MakeBox aBox1(10.0, 10.0, 10.0); + BRepPrimAPI_MakeBox aBox2(20.0, 20.0, 20.0); + + BRep_Builder aBuilder; + TopoDS_Compound aCompound; + aBuilder.MakeCompound(aCompound); + + for (TopExp_Explorer anExp(aBox1.Shape(), TopAbs_FACE); anExp.More(); anExp.Next()) + aBuilder.Add(aCompound, anExp.Current()); + for (TopExp_Explorer anExp(aBox2.Shape(), TopAbs_FACE); anExp.More(); anExp.Next()) + aBuilder.Add(aCompound, anExp.Current()); + + BRepGraph aGraph; + aGraph.Build(aCompound, true); + ASSERT_TRUE(aGraph.IsDone()); + EXPECT_EQ(aGraph.Topo().Faces().Nb(), 12); +} + +// =================================================================== +// Group 1: Shape Round-Trip +// =================================================================== + +TEST_F(BRepGraphTest, ReconstructFace_EachBoxFace_SameSubShapeCounts) +{ + for (BRepGraph_FaceIterator aFaceIt(myGraph); aFaceIt.More(); aFaceIt.Next()) + { + BRepGraph_NodeId aFaceId = BRepGraph_NodeId(aFaceIt.CurrentId()); + const TopoDS_Shape& anOrigFace = myGraph.Shapes().OriginalOf(aFaceId); + const TopoDS_Shape aReconstructed = myGraph.Shapes().Reconstruct(aFaceIt.CurrentId()); + + NCollection_IndexedMap anOrigVerts, anOrigEdges, + anOrigWires; + TopExp::MapShapes(anOrigFace, TopAbs_VERTEX, anOrigVerts); + TopExp::MapShapes(anOrigFace, TopAbs_EDGE, anOrigEdges); + TopExp::MapShapes(anOrigFace, TopAbs_WIRE, anOrigWires); + + NCollection_IndexedMap aReconVerts, aReconEdges, + aReconWires; + TopExp::MapShapes(aReconstructed, TopAbs_VERTEX, aReconVerts); + TopExp::MapShapes(aReconstructed, TopAbs_EDGE, aReconEdges); + TopExp::MapShapes(aReconstructed, TopAbs_WIRE, aReconWires); + + EXPECT_EQ(aReconVerts.Extent(), anOrigVerts.Extent()) + << "Vertex count mismatch for face " << aFaceIt.CurrentId().Index; + EXPECT_EQ(aReconEdges.Extent(), anOrigEdges.Extent()) + << "Edge count mismatch for face " << aFaceIt.CurrentId().Index; + EXPECT_EQ(aReconWires.Extent(), anOrigWires.Extent()) + << "Wire count mismatch for face " << aFaceIt.CurrentId().Index; + + // Verify same surface handle. + const TopoDS_Face& anOrigF = TopoDS::Face(anOrigFace); + const TopoDS_Face& aReconF = TopoDS::Face(aReconstructed); + TopLoc_Location aLoc1, aLoc2; + const occ::handle& aSurf1 = BRep_Tool::Surface(anOrigF, aLoc1); + const occ::handle& aSurf2 = BRep_Tool::Surface(aReconF, aLoc2); + EXPECT_EQ(aSurf1.get(), aSurf2.get()) + << "Surface handle differs for face " << aFaceIt.CurrentId().Index; + } +} + +TEST_F(BRepGraphTest, ReconstructFace_AfterEdgeReplace_ContainsNewEdge) +{ + const NCollection_Vector aCoEdgeRefs = + BRepGraph_TestTools::CoEdgeRefsOfWire(myGraph, BRepGraph_WireId(0)); + ASSERT_GE(aCoEdgeRefs.Length(), 1); + const BRepGraphInc::CoEdgeRef& aCR0 = myGraph.Refs().CoEdges().Entry(aCoEdgeRefs.Value(0)); + const BRepGraph_EdgeId anOldEdgeId = + myGraph.Topo().CoEdges().Definition(aCR0.CoEdgeDefId).EdgeDefId; + + // Pick a different edge. + const int aNewIdx = (anOldEdgeId.Index + 1) % myGraph.Topo().Edges().Nb(); + const BRepGraph_EdgeId aNewEdgeId(aNewIdx); + + // Get 3D curve handles from graph for old/new edges. + occ::handle aNewCurve = + BRepGraph_Tool::Edge::Curve(myGraph, BRepGraph_EdgeId(aNewIdx)); + occ::handle anOldCurve = + BRepGraph_Tool::Edge::Curve(myGraph, BRepGraph_EdgeId(anOldEdgeId.Index)); + + myGraph.Builder().ReplaceEdgeInWire(BRepGraph_WireId(0), anOldEdgeId, aNewEdgeId, false); + + // Reconstruct face 0 (the face owning wire 0). + const int aFaceIdx = + BRepGraph_TestTools::FaceUsesWire(myGraph, BRepGraph_FaceId(0), BRepGraph_WireId(0)) ? 0 : -1; + ASSERT_GE(aFaceIdx, 0); + const TopoDS_Shape aReconstructed = myGraph.Shapes().Reconstruct(BRepGraph_FaceId(aFaceIdx)); + + // Check via 3D curve handle identity (reconstructed edges have new TShapes). + bool isNewFound = false; + bool isOldFound = false; + for (TopExp_Explorer anExp(aReconstructed, TopAbs_EDGE); anExp.More(); anExp.Next()) + { + TopLoc_Location aLoc; + double aFirst, aLast; + occ::handle aCurve = + BRep_Tool::Curve(TopoDS::Edge(anExp.Current()), aLoc, aFirst, aLast); + if (!aCurve.IsNull() && !aNewCurve.IsNull() && aCurve.get() == aNewCurve.get()) + isNewFound = true; + if (!aCurve.IsNull() && !anOldCurve.IsNull() && aCurve.get() == anOldCurve.get()) + isOldFound = true; + } + EXPECT_TRUE(isNewFound) << "New edge curve not found in reconstructed face"; + EXPECT_FALSE(isOldFound) << "Old edge curve still present in reconstructed face"; +} + +TEST_F(BRepGraphTest, ReconstructShape_SolidRoot_SameFaceCount) +{ + BRepGraph_NodeId aSolidId(BRepGraph_NodeId::Kind::Solid, 0); + const TopoDS_Shape aReconstructed = myGraph.Shapes().Reconstruct(aSolidId); + + int aFaceCount = 0; + for (TopExp_Explorer anExp(aReconstructed, TopAbs_FACE); anExp.More(); anExp.Next()) + ++aFaceCount; + EXPECT_EQ(aFaceCount, 6); +} + +TEST_F(BRepGraphTest, ReconstructShape_FaceRoot_ReturnsSameShape) +{ + BRepGraph_NodeId aFaceId(BRepGraph_NodeId::Kind::Face, 0); + const TopoDS_Shape aReconstructed = myGraph.Shapes().Reconstruct(aFaceId); + const TopoDS_Shape& anOriginal = myGraph.Shapes().OriginalOf(aFaceId); + + // Reconstructed face should have the same surface handle. + const TopoDS_Face& anOrigF = TopoDS::Face(anOriginal); + const TopoDS_Face& aReconF = TopoDS::Face(aReconstructed); + TopLoc_Location aLoc1, aLoc2; + const occ::handle& aSurf1 = BRep_Tool::Surface(anOrigF, aLoc1); + const occ::handle& aSurf2 = BRep_Tool::Surface(aReconF, aLoc2); + EXPECT_EQ(aSurf1.get(), aSurf2.get()); +} + +TEST_F(BRepGraphTest, Shape_Unmodified_ReturnsSameShape) +{ + BRepGraph_NodeId aFaceId(BRepGraph_NodeId::Kind::Face, 0); + TopoDS_Shape aShape = myGraph.Shapes().Shape(aFaceId); + const TopoDS_Shape& anOrig = myGraph.Shapes().OriginalOf(aFaceId); + EXPECT_TRUE(aShape.IsSame(anOrig)); +} + +TEST_F(BRepGraphTest, Shape_AfterReplaceEdge_DiffersFromOriginal) +{ + const NCollection_Vector aCoEdgeRefs = + BRepGraph_TestTools::CoEdgeRefsOfWire(myGraph, BRepGraph_WireId(0)); + ASSERT_GE(aCoEdgeRefs.Length(), 1); + const BRepGraphInc::CoEdgeRef& aCR0 = myGraph.Refs().CoEdges().Entry(aCoEdgeRefs.Value(0)); + const BRepGraph_EdgeId anOldEdgeId = + myGraph.Topo().CoEdges().Definition(aCR0.CoEdgeDefId).EdgeDefId; + const int aNewIdx = (anOldEdgeId.Index + 1) % myGraph.Topo().Edges().Nb(); + const BRepGraph_EdgeId aNewEdgeId(aNewIdx); + + myGraph.Builder().ReplaceEdgeInWire(BRepGraph_WireId(0), anOldEdgeId, aNewEdgeId, false); + + // Find the face that owns wire 0. + int aFaceDefIdx = -1; + for (BRepGraph_FaceIterator aFaceIt(myGraph); aFaceIt.More(); aFaceIt.Next()) + { + if (BRepGraph_TestTools::FaceUsesWire(myGraph, aFaceIt.CurrentId(), BRepGraph_WireId(0))) + { + aFaceDefIdx = aFaceIt.CurrentId().Index; + break; + } + } + ASSERT_GE(aFaceDefIdx, 0); + BRepGraph_NodeId aFaceId(BRepGraph_NodeId::Kind::Face, aFaceDefIdx); + TopoDS_Shape aShape = myGraph.Shapes().Shape(aFaceId); + const TopoDS_Shape& anOrig = myGraph.Shapes().OriginalOf(aFaceId); + EXPECT_FALSE(aShape.IsSame(anOrig)); +} + +TEST_F(BRepGraphTest, Shape_WireKind_Valid) +{ + BRepGraph_NodeId aWireId(BRepGraph_NodeId::Kind::Wire, 0); + TopoDS_Shape aShape = myGraph.Shapes().Shape(aWireId); + EXPECT_FALSE(aShape.IsNull()); + EXPECT_EQ(aShape.ShapeType(), TopAbs_WIRE); +} + +TEST_F(BRepGraphTest, Shape_EdgeKind_Valid) +{ + BRepGraph_NodeId anEdgeId(BRepGraph_NodeId::Kind::Edge, 0); + TopoDS_Shape aShape = myGraph.Shapes().Shape(anEdgeId); + EXPECT_FALSE(aShape.IsNull()); + EXPECT_EQ(aShape.ShapeType(), TopAbs_EDGE); +} + +TEST_F(BRepGraphTest, Shape_VertexKind_Valid) +{ + BRepGraph_NodeId aVtxId(BRepGraph_NodeId::Kind::Vertex, 0); + TopoDS_Shape aShape = myGraph.Shapes().Shape(aVtxId); + EXPECT_FALSE(aShape.IsNull()); + EXPECT_EQ(aShape.ShapeType(), TopAbs_VERTEX); +} + +TEST_F(BRepGraphTest, OwnGen_MutableEdge_PropagatesSubtreeGenUp) +{ + EXPECT_EQ(myGraph.Topo().Edges().Definition(BRepGraph_EdgeId(0)).OwnGen, 0u); + + myGraph.Builder().MutEdge(BRepGraph_EdgeId(0)); + + // Edge was directly mutated: OwnGen incremented. + EXPECT_GT(myGraph.Topo().Edges().Definition(BRepGraph_EdgeId(0)).OwnGen, 0u); + + // Check propagation up to parent wire and face (SubtreeGen). + const BRepGraphInc::EdgeDef& anEdge = myGraph.Topo().Edges().Definition(BRepGraph_EdgeId(0)); + if (anEdge.Id.IsValid()) + { + // Find a wire containing this edge. + const NCollection_Vector& aWires = + myGraph.Topo().Edges().Wires(BRepGraph_EdgeId(0)); + if (aWires.Length() > 0) + { + EXPECT_GT(myGraph.Topo().Wires().Definition(aWires.Value(0)).SubtreeGen, 0u); + // Check propagation to owning face. + for (BRepGraph_FaceIterator aFaceIt(myGraph); aFaceIt.More(); aFaceIt.Next()) + { + if (BRepGraph_TestTools::FaceUsesWire(myGraph, aFaceIt.CurrentId(), aWires.Value(0))) + { + EXPECT_GT(myGraph.Topo().Faces().Definition(aFaceIt.CurrentId()).SubtreeGen, 0u); + break; + } + } + } + } +} + +TEST_F(BRepGraphTest, ReconstructShape_WireKind_NoThrow) +{ + BRepGraph_NodeId aWireId(BRepGraph_NodeId::Kind::Wire, 0); + TopoDS_Shape aShape; + EXPECT_NO_THROW(aShape = myGraph.Shapes().Reconstruct(aWireId)); + EXPECT_FALSE(aShape.IsNull()); +} + +TEST_F(BRepGraphTest, HasOriginalShape_AfterBuild_True) +{ + for (BRepGraph_FaceIterator aFaceIt(myGraph); aFaceIt.More(); aFaceIt.Next()) + { + BRepGraph_NodeId aFaceId(aFaceIt.CurrentId()); + EXPECT_TRUE(myGraph.Shapes().HasOriginal(aFaceId)) + << "Face " << aFaceIt.CurrentId().Index << " should have original shape after Build()"; + } + for (BRepGraph_EdgeIterator anEdgeIt(myGraph); anEdgeIt.More(); anEdgeIt.Next()) + { + BRepGraph_NodeId anEdgeId(anEdgeIt.CurrentId()); + EXPECT_TRUE(myGraph.Shapes().HasOriginal(anEdgeId)) + << "Edge " << anEdgeIt.CurrentId().Index << " should have original shape after Build()"; + } +} + +TEST_F(BRepGraphTest, Shape_CachedOnSecondCall) +{ + BRepGraph_NodeId aVtxId(BRepGraph_NodeId::Kind::Vertex, 0); + TopoDS_Shape aFirst = myGraph.Shapes().Shape(aVtxId); + TopoDS_Shape aSecond = myGraph.Shapes().Shape(aVtxId); + EXPECT_TRUE(aFirst.IsSame(aSecond)); +} + +TEST_F(BRepGraphTest, Shape_InvalidatedAfterMutation) +{ + BRepGraph_NodeId anEdgeId(BRepGraph_NodeId::Kind::Edge, 0); + TopoDS_Shape aBefore = myGraph.Shapes().Shape(anEdgeId); + EXPECT_FALSE(aBefore.IsNull()); + + myGraph.Builder().MutEdge(BRepGraph_EdgeId(0))->Tolerance = 0.123; + TopoDS_Shape anAfter = myGraph.Shapes().Shape(anEdgeId); + EXPECT_FALSE(anAfter.IsNull()); + + // After mutation, Shape() reconstructs a new edge - different TShape. + EXPECT_FALSE(aBefore.IsSame(anAfter)); +} + +TEST_F(BRepGraphTest, DefaultBuild_AssignsValidUIDs) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + + BRepGraph aGraph; + aGraph.Build(aBoxMaker.Shape()); + + ASSERT_TRUE(aGraph.IsDone()); + ASSERT_GT(aGraph.Topo().Faces().Nb(), 0); + + const BRepGraph_NodeId aFaceId(BRepGraph_NodeId::Kind::Face, 0); + const BRepGraph_UID aUID = aGraph.UIDs().Of(aFaceId); + + EXPECT_TRUE(aUID.IsValid()); + EXPECT_TRUE(aGraph.UIDs().Has(aUID)); + EXPECT_EQ(aGraph.UIDs().NodeIdFrom(aUID), aFaceId); +} + +TEST_F(BRepGraphTest, UIDsGeneration_IncrementsAcrossBuilds) +{ + BRepPrimAPI_MakeBox aBoxMaker1(10.0, 20.0, 30.0); + BRepPrimAPI_MakeBox aBoxMaker2(11.0, 21.0, 31.0); + + BRepGraph aGraph; + aGraph.Build(aBoxMaker1.Shape()); + const uint32_t aGeneration1 = aGraph.UIDs().Generation(); + + aGraph.Build(aBoxMaker2.Shape()); + const uint32_t aGeneration2 = aGraph.UIDs().Generation(); + + EXPECT_GT(aGeneration1, 0u); + EXPECT_EQ(aGeneration2, aGeneration1 + 1); +} + +TEST_F(BRepGraphTest, StaleUID_HasReturnsFalseAfterRebuild) +{ + BRepPrimAPI_MakeBox aBoxMaker1(10.0, 20.0, 30.0); + BRepPrimAPI_MakeBox aBoxMaker2(11.0, 21.0, 31.0); + + BRepGraph aGraph; + aGraph.Build(aBoxMaker1.Shape()); + ASSERT_GT(aGraph.Topo().Faces().Nb(), 0); + const BRepGraph_UID anOldUID = + aGraph.UIDs().Of(BRepGraph_NodeId(BRepGraph_NodeId::Kind::Face, 0)); + ASSERT_TRUE(anOldUID.IsValid()); + ASSERT_TRUE(aGraph.UIDs().Has(anOldUID)); + + aGraph.Build(aBoxMaker2.Shape()); + + EXPECT_FALSE(aGraph.UIDs().Has(anOldUID)); + EXPECT_FALSE(aGraph.UIDs().NodeIdFrom(anOldUID).IsValid()); +} + +TEST(BRepGraph_UIDsViewTest, ReverseLookupStaysCurrentAfterProgrammaticAdd) +{ + BRepGraph aGraph; + + const BRepGraph_VertexId aFirstVertex = aGraph.Builder().AddVertex(gp_Pnt(0.0, 0.0, 0.0), 0.001); + const BRepGraph_UID aFirstUID = aGraph.UIDs().Of(aFirstVertex); + ASSERT_TRUE(aFirstUID.IsValid()); + ASSERT_EQ(aGraph.UIDs().NodeIdFrom(aFirstUID), aFirstVertex); + + const BRepGraph_VertexId aSecondVertex = aGraph.Builder().AddVertex(gp_Pnt(1.0, 0.0, 0.0), 0.001); + const BRepGraph_UID aSecondUID = aGraph.UIDs().Of(aSecondVertex); + ASSERT_TRUE(aSecondUID.IsValid()); + + EXPECT_EQ(aGraph.UIDs().NodeIdFrom(aFirstUID), aFirstVertex); + EXPECT_EQ(aGraph.UIDs().NodeIdFrom(aSecondUID), aSecondVertex); + EXPECT_TRUE(aGraph.UIDs().Has(aFirstUID)); + EXPECT_TRUE(aGraph.UIDs().Has(aSecondUID)); +} + +// =================================================================== +// Group 2: History Tracking +// =================================================================== + +TEST_F(BRepGraphTest, RecordHistory_MultipleRecords_SequenceNumbers) +{ + const int aBefore = myGraph.History().NbRecords(); + + BRepGraph_NodeId anEdge0(BRepGraph_NodeId::Kind::Edge, 0); + BRepGraph_NodeId anEdge1(BRepGraph_NodeId::Kind::Edge, 1); + NCollection_Vector aRepl; + aRepl.Append(anEdge1); + + myGraph.History().Record("OpA", anEdge0, aRepl); + myGraph.History().Record("OpB", anEdge0, aRepl); + myGraph.History().Record("OpC", anEdge0, aRepl); + + EXPECT_EQ(myGraph.History().NbRecords(), aBefore + 3); + + // Check monotonically increasing sequence numbers. + for (int anIdx = aBefore + 1; anIdx < aBefore + 3; ++anIdx) + { + EXPECT_GT(myGraph.History().Record(anIdx).SequenceNumber, + myGraph.History().Record(anIdx - 1).SequenceNumber) + << "SequenceNumber not monotonically increasing at index " << anIdx; + } + + EXPECT_TRUE(myGraph.History().Record(aBefore).OperationName.IsEqual("OpA")); + EXPECT_TRUE(myGraph.History().Record(aBefore + 1).OperationName.IsEqual("OpB")); + EXPECT_TRUE(myGraph.History().Record(aBefore + 2).OperationName.IsEqual("OpC")); +} + +TEST_F(BRepGraphTest, FindOriginal_SingleHop_ReturnsSource) +{ + BRepGraph_NodeId anEdge0(BRepGraph_NodeId::Kind::Edge, 0); + BRepGraph_NodeId anEdge1(BRepGraph_NodeId::Kind::Edge, 1); + + auto aModifier = [&](BRepGraph& /*theGraph*/, BRepGraph_NodeId /*theTarget*/) { + NCollection_Vector aResult; + aResult.Append(anEdge1); + return aResult; + }; + + myGraph.Builder().ApplyModification(anEdge0, aModifier, "TestHop"); + + BRepGraph_NodeId anOriginal = myGraph.History().FindOriginal(anEdge1); + EXPECT_EQ(anOriginal, anEdge0); +} + +TEST_F(BRepGraphTest, FindDerived_SingleHop_ContainsTarget) +{ + BRepGraph_NodeId anEdge0(BRepGraph_NodeId::Kind::Edge, 0); + BRepGraph_NodeId anEdge1(BRepGraph_NodeId::Kind::Edge, 1); + + auto aModifier = [&](BRepGraph& /*theGraph*/, BRepGraph_NodeId /*theTarget*/) { + NCollection_Vector aResult; + aResult.Append(anEdge1); + return aResult; + }; + + myGraph.Builder().ApplyModification(anEdge0, aModifier, "TestHop"); + + NCollection_Vector aDerived = myGraph.History().FindDerived(anEdge0); + bool isFound = false; + for (const BRepGraph_NodeId& aDerivedId : aDerived) + { + if (aDerivedId == anEdge1) + { + isFound = true; + break; + } + } + EXPECT_TRUE(isFound) << "Edge1 not found in FindDerived(Edge0)"; +} + +TEST_F(BRepGraphTest, ApplyModification_MultiStepChain_FindOriginalTracesBack) +{ + BRepGraph_NodeId anEdge0(BRepGraph_NodeId::Kind::Edge, 0); + BRepGraph_NodeId anEdge1(BRepGraph_NodeId::Kind::Edge, 1); + BRepGraph_NodeId anEdge2(BRepGraph_NodeId::Kind::Edge, 2); + + // Step 1: edge0 -> edge1 + auto aModifier1 = [&](BRepGraph& /*theGraph*/, BRepGraph_NodeId /*theTarget*/) { + NCollection_Vector aResult; + aResult.Append(anEdge1); + return aResult; + }; + myGraph.Builder().ApplyModification(anEdge0, aModifier1, "Step1"); + + // Step 2: edge1 -> edge2 + auto aModifier2 = [&](BRepGraph& /*theGraph*/, BRepGraph_NodeId /*theTarget*/) { + NCollection_Vector aResult; + aResult.Append(anEdge2); + return aResult; + }; + myGraph.Builder().ApplyModification(anEdge1, aModifier2, "Step2"); + + // FindOriginal from edge2 should trace back to edge0. + BRepGraph_NodeId anOriginal = myGraph.History().FindOriginal(anEdge2); + EXPECT_EQ(anOriginal, anEdge0); + + // FindDerived from edge0 returns leaf-only transitive descendants. + // edge1 is an intermediate (it has further derived edge2), so only edge2 is returned. + NCollection_Vector aDerived = myGraph.History().FindDerived(anEdge0); + bool isEdge2Found = false; + for (const BRepGraph_NodeId& aDerivedId : aDerived) + { + if (aDerivedId == anEdge2) + isEdge2Found = true; + } + EXPECT_TRUE(isEdge2Found) << "Edge2 not found in FindDerived(Edge0)"; + + // edge1 can be found by querying FindDerived on the intermediate step. + NCollection_Vector aDerived1 = myGraph.History().FindDerived(anEdge1); + bool isEdge2FromEdge1 = false; + for (const BRepGraph_NodeId& aDerivedId : aDerived1) + { + if (aDerivedId == anEdge2) + isEdge2FromEdge1 = true; + } + EXPECT_TRUE(isEdge2FromEdge1) << "Edge2 not found in FindDerived(Edge1)"; +} + +// =================================================================== +// Group 3: Mutation APIs +// =================================================================== + +TEST_F(BRepGraphTest, AddPCurveToEdge_NewPCurve_RetrievableViaFindPCurve) +{ + BRepGraph_EdgeId anEdgeId(0); + BRepGraph_FaceId aFaceId(0); + + occ::handle aCurve2d = new Geom2d_Line(gp_Pnt2d(0.0, 0.0), gp_Dir2d(1.0, 0.0)); + myGraph.Builder().AddPCurveToEdge(anEdgeId, aFaceId, aCurve2d, 0.0, 1.0); + + const BRepGraphInc::CoEdgeDef* aRetrieved = + BRepGraph_Tool::Edge::FindPCurve(myGraph, anEdgeId, aFaceId); + EXPECT_NE(aRetrieved, nullptr); + if (aRetrieved != nullptr) + { + EXPECT_TRUE(aRetrieved->Curve2DRepId.IsValid()); + } +} + +TEST_F(BRepGraphTest, ReplaceEdgeInWire_Reversed_OrientationFlipped) +{ + const NCollection_Vector aCoEdgeRefs = + BRepGraph_TestTools::CoEdgeRefsOfWire(myGraph, BRepGraph_WireId(0)); + ASSERT_GE(aCoEdgeRefs.Length(), 1); + + const BRepGraphInc::CoEdgeRef& anOrigCR = myGraph.Refs().CoEdges().Entry(aCoEdgeRefs.Value(0)); + const BRepGraphInc::CoEdgeDef& anOrigCoEdge = + myGraph.Topo().CoEdges().Definition(anOrigCR.CoEdgeDefId); + const BRepGraph_EdgeId anOldEdgeId = anOrigCoEdge.EdgeDefId; + TopAbs_Orientation anOrigOrientation = anOrigCoEdge.Sense; + + // Pick a different edge. + const int aNewIdx = (anOldEdgeId.Index + 1) % myGraph.Topo().Edges().Nb(); + const BRepGraph_EdgeId aNewEdgeId(aNewIdx); + + myGraph.Builder().ReplaceEdgeInWire(BRepGraph_WireId(0), anOldEdgeId, aNewEdgeId, true); + + const NCollection_Vector aCoEdgeRefsAfter = + BRepGraph_TestTools::CoEdgeRefsOfWire(myGraph, BRepGraph_WireId(0)); + ASSERT_GE(aCoEdgeRefsAfter.Length(), 1); + const BRepGraphInc::CoEdgeRef& aNewCR = myGraph.Refs().CoEdges().Entry(aCoEdgeRefsAfter.Value(0)); + const BRepGraphInc::CoEdgeDef& aNewCoEdge = + myGraph.Topo().CoEdges().Definition(aNewCR.CoEdgeDefId); + EXPECT_EQ(aNewCoEdge.EdgeDefId.Index, aNewEdgeId.Index); + + // Orientation should be flipped relative to original. + TopAbs_Orientation anExpected = + (anOrigOrientation == TopAbs_FORWARD) ? TopAbs_REVERSED : TopAbs_FORWARD; + EXPECT_EQ(aNewCoEdge.Sense, anExpected); +} + +TEST_F(BRepGraphTest, ReplaceEdgeInWire_UpdatesEdgeToCoEdgeReverseIndex) +{ + const NCollection_Vector aCoEdgeRefs = + BRepGraph_TestTools::CoEdgeRefsOfWire(myGraph, BRepGraph_WireId(0)); + ASSERT_GE(aCoEdgeRefs.Length(), 1); + + const BRepGraphInc::CoEdgeRef& aRef = myGraph.Refs().CoEdges().Entry(aCoEdgeRefs.Value(0)); + const BRepGraph_CoEdgeId aCoEdgeId = aRef.CoEdgeDefId; + const BRepGraph_EdgeId anOldEdgeId = myGraph.Topo().CoEdges().Definition(aCoEdgeId).EdgeDefId; + const BRepGraph_EdgeId aNewEdgeId((anOldEdgeId.Index + 1) % myGraph.Topo().Edges().Nb()); + + auto hasCoEdge = [&](const BRepGraph_EdgeId theEdgeId) { + const NCollection_Vector& aCoEdges = + myGraph.Topo().Edges().CoEdges(theEdgeId); + for (const BRepGraph_CoEdgeId& aCoEdge : aCoEdges) + { + if (aCoEdge == aCoEdgeId) + { + return true; + } + } + return false; + }; + + ASSERT_TRUE(hasCoEdge(anOldEdgeId)); + + myGraph.Builder().ReplaceEdgeInWire(BRepGraph_WireId(0), anOldEdgeId, aNewEdgeId, false); + + EXPECT_FALSE(hasCoEdge(anOldEdgeId)); + EXPECT_TRUE(hasCoEdge(aNewEdgeId)); +} + +TEST_F(BRepGraphTest, RemoveNode_EdgeWithReplacement_ReparentsAllCoEdges) +{ + const NCollection_Vector aCoEdgeRefs = + BRepGraph_TestTools::CoEdgeRefsOfWire(myGraph, BRepGraph_WireId(0)); + ASSERT_GE(aCoEdgeRefs.Length(), 1); + + const BRepGraphInc::CoEdgeRef& aRef = myGraph.Refs().CoEdges().Entry(aCoEdgeRefs.Value(0)); + const BRepGraph_EdgeId anOldEdgeId = + myGraph.Topo().CoEdges().Definition(aRef.CoEdgeDefId).EdgeDefId; + const BRepGraph_EdgeId aNewEdgeId((anOldEdgeId.Index + 1) % myGraph.Topo().Edges().Nb()); + + myGraph.Builder().RemoveNode(BRepGraph_EdgeId(anOldEdgeId.Index), + BRepGraph_EdgeId(aNewEdgeId.Index)); + + const NCollection_Vector& anOldCoEdges = + myGraph.Topo().Edges().CoEdges(anOldEdgeId); + EXPECT_EQ(anOldCoEdges.Length(), 0); + + const NCollection_Vector& aNewCoEdges = + myGraph.Topo().Edges().CoEdges(aNewEdgeId); + EXPECT_GT(aNewCoEdges.Length(), 0); + for (const BRepGraph_CoEdgeId& aNewCoEdgeId : aNewCoEdges) + { + const BRepGraphInc::CoEdgeDef& aCoEdge = myGraph.Topo().CoEdges().Definition(aNewCoEdgeId); + EXPECT_EQ(aCoEdge.EdgeDefId, aNewEdgeId); + } +} + +TEST_F(BRepGraphTest, MutableVertex_ChangePoint_Verified) +{ + BRepGraph_MutGuard aMutVert = + myGraph.Builder().MutVertex(BRepGraph_VertexId(0)); + aMutVert->Point = gp_Pnt(99.0, 99.0, 99.0); + + const BRepGraphInc::VertexDef& aVert = + myGraph.Topo().Vertices().Definition(BRepGraph_VertexId(0)); + EXPECT_NEAR(aVert.Point.X(), 99.0, Precision::Confusion()); + EXPECT_NEAR(aVert.Point.Y(), 99.0, Precision::Confusion()); + EXPECT_NEAR(aVert.Point.Z(), 99.0, Precision::Confusion()); +} + +// =================================================================== +// Group 4: Geometric Queries +// =================================================================== + +TEST_F(BRepGraphTest, EdgeDef_HasValidCurve3d) +{ + for (BRepGraph_EdgeIterator anEdgeIt(myGraph); anEdgeIt.More(); anEdgeIt.Next()) + { + if (BRepGraph_Tool::Edge::Degenerated(myGraph, anEdgeIt.CurrentId())) + continue; + + EXPECT_TRUE(BRepGraph_Tool::Edge::HasCurve(myGraph, anEdgeIt.CurrentId())) + << "Edge " << anEdgeIt.CurrentId().Index << " has no Curve3D rep"; + } +} + +// =================================================================== +// Group 7: Detection Methods +// =================================================================== + +TEST_F(BRepGraphTest, FreeEdges_SingleFace_AllEdgesFree) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + TopExp_Explorer anExp(aBoxMaker.Shape(), TopAbs_FACE); + const TopoDS_Face& aFace = TopoDS::Face(anExp.Current()); + + BRepGraph aGraph; + aGraph.Build(aFace); + ASSERT_TRUE(aGraph.IsDone()); + + NCollection_Vector aFreeEdges = collectFreeEdges(aGraph); + EXPECT_EQ(aFreeEdges.Length(), 4); +} + +TEST_F(BRepGraphTest, Decompose_ThreeDisconnectedFaces_ThreeComponents) +{ + BRepPrimAPI_MakeBox aBox1(10.0, 10.0, 10.0); + BRepPrimAPI_MakeBox aBox2(20.0, 20.0, 20.0); + BRepPrimAPI_MakeBox aBox3(30.0, 30.0, 30.0); + + TopExp_Explorer anExp1(aBox1.Shape(), TopAbs_FACE); + TopExp_Explorer anExp2(aBox2.Shape(), TopAbs_FACE); + TopExp_Explorer anExp3(aBox3.Shape(), TopAbs_FACE); + + BRep_Builder aBuilder; + TopoDS_Compound aCompound; + aBuilder.MakeCompound(aCompound); + aBuilder.Add(aCompound, anExp1.Current()); + aBuilder.Add(aCompound, anExp2.Current()); + aBuilder.Add(aCompound, anExp3.Current()); + + BRepGraph aGraph; + aGraph.Build(aCompound); + ASSERT_TRUE(aGraph.IsDone()); + EXPECT_EQ(aGraph.Topo().Faces().Nb(), 3); + + EXPECT_EQ(countFaceComponents(aGraph), 3); +} + +TEST_F(BRepGraphTest, DetectToleranceConflicts_ManualConflict_Detected) +{ + // Find two edges that share the same Curve3d handle. + bool isConflictSetUp = false; + const int aNbEdges = myGraph.Topo().Edges().Nb(); + for (BRepGraph_EdgeId anEdgeId(0); anEdgeId.IsValid(aNbEdges) && !isConflictSetUp; ++anEdgeId) + { + if (BRepGraph_Tool::Edge::Degenerated(myGraph, anEdgeId) + || !BRepGraph_Tool::Edge::HasCurve(myGraph, anEdgeId)) + continue; + + for (BRepGraph_EdgeId anOtherId(anEdgeId.Index + 1); anOtherId.IsValid(aNbEdges); ++anOtherId) + { + if (BRepGraph_Tool::Edge::Degenerated(myGraph, anOtherId) + || !BRepGraph_Tool::Edge::HasCurve(myGraph, anOtherId)) + continue; + if (BRepGraph_Tool::Edge::Curve(myGraph, anEdgeId).get() + != BRepGraph_Tool::Edge::Curve(myGraph, anOtherId).get()) + continue; + + // Set very different tolerances on two edges sharing the same curve. + myGraph.Builder().MutEdge(anEdgeId)->Tolerance = 0.001; + myGraph.Builder().MutEdge(anOtherId)->Tolerance = 1.0; + + isConflictSetUp = true; + break; + } + } + + if (isConflictSetUp) + { + NCollection_Vector aConflicts(16); + NCollection_Map aConflictKeys; + for (BRepGraph_EdgeIterator anEdgeIt(myGraph); anEdgeIt.More(); anEdgeIt.Next()) + { + const BRepGraph_EdgeId anEdgeId = anEdgeIt.CurrentId(); + if (BRepGraph_Tool::Edge::Degenerated(myGraph, anEdgeId) + || !BRepGraph_Tool::Edge::HasCurve(myGraph, anEdgeId)) + { + continue; + } + + const occ::handle& aCurve = BRepGraph_Tool::Edge::Curve(myGraph, anEdgeId); + double aMinTol = myGraph.Topo().Edges().Definition(anEdgeId).Tolerance; + double aMaxTol = aMinTol; + NCollection_Vector aCurveEdges(4); + aCurveEdges.Append(anEdgeId); + for (BRepGraph_EdgeIterator anOtherIt(myGraph); anOtherIt.More(); anOtherIt.Next()) + { + const BRepGraph_EdgeId anOtherId = anOtherIt.CurrentId(); + if (anOtherId == anEdgeId || BRepGraph_Tool::Edge::Degenerated(myGraph, anOtherId) + || !BRepGraph_Tool::Edge::HasCurve(myGraph, anOtherId) + || BRepGraph_Tool::Edge::Curve(myGraph, anOtherId).get() != aCurve.get()) + { + continue; + } + + const double aTol = myGraph.Topo().Edges().Definition(anOtherId).Tolerance; + aMinTol = std::min(aMinTol, aTol); + aMaxTol = std::max(aMaxTol, aTol); + aCurveEdges.Append(anOtherId); + } + + if (aCurveEdges.Length() > 1 && aMaxTol - aMinTol > 0.5) + { + for (const BRepGraph_EdgeId& aConflictId : aCurveEdges) + { + if (aConflictKeys.Add(aConflictId.Index)) + { + aConflicts.Append(aConflictId); + } + } + } + } + EXPECT_GE(aConflicts.Length(), 1); + } +} + +// =================================================================== +// Group 8: User Attributes & Error Cases +// =================================================================== + +TEST_F(BRepGraphTest, RemoveUserAttribute_AfterSet_ReturnsNull) +{ + BRepGraph_NodeId aFaceId(BRepGraph_NodeId::Kind::Face, 0); + occ::handle anAttr = new BRepGraph_TypedCacheValue(42); + + myGraph.Cache().Set(aFaceId, testIntAttrKind(), anAttr); + ASSERT_FALSE(myGraph.Cache().Get(aFaceId, testIntAttrKind()).IsNull()); + + myGraph.Cache().Remove(aFaceId, testIntAttrKind()); + EXPECT_TRUE(myGraph.Cache().Get(aFaceId, testIntAttrKind()).IsNull()); +} + +TEST_F(BRepGraphTest, Build_EmptyCompound_IsDoneZeroCounts) +{ + BRep_Builder aBuilder; + TopoDS_Compound aCompound; + aBuilder.MakeCompound(aCompound); + + BRepGraph aGraph; + aGraph.Build(aCompound); + EXPECT_TRUE(aGraph.IsDone()); + EXPECT_EQ(aGraph.Topo().Solids().Nb(), 0); + EXPECT_EQ(aGraph.Topo().Shells().Nb(), 0); + EXPECT_EQ(aGraph.Topo().Faces().Nb(), 0); + EXPECT_EQ(aGraph.Topo().Wires().Nb(), 0); + EXPECT_EQ(aGraph.Topo().Edges().Nb(), 0); + EXPECT_EQ(aGraph.Topo().Vertices().Nb(), 0); +} + +TEST_F(BRepGraphTest, TopoNode_GenericLookup_MatchesTypedAccess) +{ + BRepGraph_NodeId aFaceId(BRepGraph_NodeId::Kind::Face, 0); + const BRepGraphInc::BaseDef* aBase = myGraph.Topo().Gen().TopoEntity(aFaceId); + ASSERT_NE(aBase, nullptr); + EXPECT_EQ(aBase->Id, myGraph.Topo().Faces().Definition(BRepGraph_FaceId(0)).Id); + + // Invalid node id should return nullptr. + BRepGraph_NodeId anInvalidId; + const BRepGraphInc::BaseDef* anInvalidBase = myGraph.Topo().Gen().TopoEntity(anInvalidId); + EXPECT_EQ(anInvalidBase, nullptr); +} + +// =================================================================== +// Group 9: Node Counts and Identity +// =================================================================== + +TEST_F(BRepGraphTest, NbNodes_Box_TotalCount) +{ + // NbNodes should equal sum of all per-kind counts (topology + assembly). + size_t anExpected = static_cast(myGraph.Topo().Solids().Nb()) + + myGraph.Topo().Shells().Nb() + myGraph.Topo().Faces().Nb() + + myGraph.Topo().Wires().Nb() + myGraph.Topo().CoEdges().Nb() + + myGraph.Topo().Edges().Nb() + myGraph.Topo().Vertices().Nb() + + myGraph.Topo().Compounds().Nb() + myGraph.Topo().CompSolids().Nb() + + myGraph.Topo().Products().Nb() + myGraph.Topo().Occurrences().Nb(); + EXPECT_EQ(myGraph.Topo().Gen().NbNodes(), anExpected); +} + +TEST_F(BRepGraphTest, HasUID_ValidFace_ReturnsTrue) +{ + const BRepGraph_UID& aUID = myGraph.UIDs().Of(BRepGraph_NodeId(BRepGraph_NodeId::Kind::Face, 0)); + EXPECT_TRUE(myGraph.UIDs().Has(aUID)); +} + +TEST_F(BRepGraphTest, HasUID_InvalidUID_ReturnsFalse) +{ + BRepGraph_UID anInvalidUID = BRepGraph_UID::Invalid(); + EXPECT_FALSE(myGraph.UIDs().Has(anInvalidUID)); +} + +TEST_F(BRepGraphTest, NodeIdFromUID_InvalidUID_ReturnsInvalid) +{ + BRepGraph_UID anInvalidUID = BRepGraph_UID::Invalid(); + BRepGraph_NodeId aResolved = myGraph.UIDs().NodeIdFrom(anInvalidUID); + EXPECT_FALSE(aResolved.IsValid()); +} + +TEST_F(BRepGraphTest, Allocator_DefaultConstructor_NotNull) +{ + EXPECT_FALSE(myGraph.Allocator().IsNull()); +} + +TEST_F(BRepGraphTest, Build_WithCustomAllocator_IsDone) +{ + occ::handle anAlloc = NCollection_BaseAllocator::CommonBaseAllocator(); + BRepGraph aGraph(anAlloc); + + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + aGraph.Build(aBoxMaker.Shape()); + ASSERT_TRUE(aGraph.IsDone()); + EXPECT_EQ(aGraph.Topo().Faces().Nb(), 6); + EXPECT_FALSE(aGraph.Allocator().IsNull()); +} + +// =================================================================== +// Group 10: Topology Structure +// =================================================================== + +TEST_F(BRepGraphTest, Wire_IsClosed_BoxOuterWires) +{ + // All outer wires of a box face should be closed. + for (BRepGraph_FaceIterator aFaceIt(myGraph); aFaceIt.More(); aFaceIt.Next()) + { + const BRepGraph_WireId anOuterWire = + BRepGraph_TestTools::OuterWireOfFace(myGraph, aFaceIt.CurrentId()); + ASSERT_TRUE(anOuterWire.IsValid()); + const BRepGraphInc::WireDef& aWireDef = myGraph.Topo().Wires().Definition(anOuterWire); + EXPECT_TRUE(aWireDef.IsClosed) + << "Outer wire of face " << aFaceIt.CurrentId().Index << " should be closed"; + } +} + +TEST_F(BRepGraphTest, Face_InnerWireRefs_BoxHasNone) +{ + // Box faces have no holes, so non-outer WireRefs should be empty. + for (BRepGraph_FaceIterator aFaceIt(myGraph); aFaceIt.More(); aFaceIt.Next()) + { + const NCollection_Vector aWireRefs = + BRepGraph_TestTools::WireRefsOfFace(myGraph, aFaceIt.CurrentId()); + int aNonOuterCount = 0; + for (const BRepGraph_WireRefId& aWireRefId : aWireRefs) + { + if (!myGraph.Refs().Wires().Entry(aWireRefId).IsOuter) + ++aNonOuterCount; + } + EXPECT_EQ(aNonOuterCount, 0) << "Box face " << aFaceIt.CurrentId().Index + << " should have no inner wires"; + } +} + +TEST_F(BRepGraphTest, Face_Orientation_ValidValue) +{ + // Verify face orientations in the shell's incidence refs. + ASSERT_EQ(myGraph.Topo().Shells().Nb(), 1); + const NCollection_Vector aFaceRefs = + BRepGraph_TestTools::FaceRefsOfShell(myGraph, BRepGraph_ShellId(0)); + for (int aRefIdx = 0; aRefIdx < aFaceRefs.Length(); ++aRefIdx) + { + const BRepGraphInc::FaceRef& aFaceRef = myGraph.Refs().Faces().Entry(aFaceRefs.Value(aRefIdx)); + TopAbs_Orientation anOri = aFaceRef.Orientation; + EXPECT_TRUE(anOri == TopAbs_FORWARD || anOri == TopAbs_REVERSED) + << "Face ref " << aRefIdx << " has unexpected orientation " << anOri; + } +} + +TEST_F(BRepGraphTest, Shell_ContainsSixFaces) +{ + ASSERT_EQ(myGraph.Topo().Shells().Nb(), 1); + EXPECT_EQ(BRepGraph_TestTools::CountFaceRefsOfShell(myGraph, BRepGraph_ShellId(0)), 6); +} + +TEST_F(BRepGraphTest, Solid_ContainsOneShell) +{ + ASSERT_EQ(myGraph.Topo().Solids().Nb(), 1); + EXPECT_EQ(BRepGraph_TestTools::CountShellRefsOfSolid(myGraph, BRepGraph_SolidId(0)), 1); +} + +TEST_F(BRepGraphTest, Edge_ParamRange_ValidBounds) +{ + for (BRepGraph_EdgeIterator anEdgeIt(myGraph); anEdgeIt.More(); anEdgeIt.Next()) + { + if (BRepGraph_Tool::Edge::Degenerated(myGraph, anEdgeIt.CurrentId())) + continue; + const auto [aFirst, aLast] = BRepGraph_Tool::Edge::Range(myGraph, anEdgeIt.CurrentId()); + EXPECT_LT(aFirst, aLast) << "Edge " << anEdgeIt.CurrentId().Index + << " has invalid parameter range [" << aFirst << ", " << aLast << "]"; + } +} + +TEST_F(BRepGraphTest, Vertex_TolerancePositive) +{ + for (BRepGraph_VertexIterator aVertexIt(myGraph); aVertexIt.More(); aVertexIt.Next()) + { + EXPECT_GT(BRepGraph_Tool::Vertex::Tolerance(myGraph, aVertexIt.CurrentId()), 0.0) + << "Vertex " << aVertexIt.CurrentId().Index << " has non-positive tolerance"; + } +} + +TEST_F(BRepGraphTest, Edge_TolerancePositive) +{ + for (BRepGraph_EdgeIterator anEdgeIt(myGraph); anEdgeIt.More(); anEdgeIt.Next()) + { + EXPECT_GT(BRepGraph_Tool::Edge::Tolerance(myGraph, anEdgeIt.CurrentId()), 0.0) + << "Edge " << anEdgeIt.CurrentId().Index << " has non-positive tolerance"; + } +} + +TEST_F(BRepGraphTest, Face_ToleranceNonNegative) +{ + for (BRepGraph_FaceIterator aFaceIt(myGraph); aFaceIt.More(); aFaceIt.Next()) + { + EXPECT_GE(BRepGraph_Tool::Face::Tolerance(myGraph, aFaceIt.CurrentId()), 0.0) + << "Face " << aFaceIt.CurrentId().Index << " has negative tolerance"; + } +} + +// =================================================================== +// Group 13: Mutation (Extended) +// =================================================================== + +TEST_F(BRepGraphTest, MutableWireDef_ModifyClosure_Verified) +{ + BRepGraph_MutGuard aMutWD = myGraph.Builder().MutWire(BRepGraph_WireId(0)); + bool anOrigClosed = aMutWD->IsClosed; + aMutWD->IsClosed = !anOrigClosed; + + EXPECT_EQ(myGraph.Topo().Wires().Definition(BRepGraph_WireId(0)).IsClosed, !anOrigClosed); + + // Restore original state. + myGraph.Builder().MutWire(BRepGraph_WireId(0))->IsClosed = anOrigClosed; + EXPECT_EQ(myGraph.Topo().Wires().Definition(BRepGraph_WireId(0)).IsClosed, anOrigClosed); +} + +TEST_F(BRepGraphTest, MultipleUserAttributes_SameNode_Independent) +{ + BRepGraph_NodeId aFaceId(BRepGraph_NodeId::Kind::Face, 0); + + Handle(BRepGraph_TypedCacheValue) anAttr1 = new BRepGraph_TypedCacheValue(100); + Handle(BRepGraph_TypedCacheValue) anAttr2 = new BRepGraph_TypedCacheValue(2.718); + + myGraph.Cache().Set(aFaceId, testIntAttrKind(), anAttr1); + myGraph.Cache().Set(aFaceId, testAuxAttrKind(), anAttr2); + + Handle(BRepGraph_TypedCacheValue) aRetrieved1 = + Handle(BRepGraph_TypedCacheValue)::DownCast( + myGraph.Cache().Get(aFaceId, testIntAttrKind())); + Handle(BRepGraph_TypedCacheValue) aRetrieved2 = + Handle(BRepGraph_TypedCacheValue)::DownCast( + myGraph.Cache().Get(aFaceId, testAuxAttrKind())); + + ASSERT_FALSE(aRetrieved1.IsNull()); + ASSERT_FALSE(aRetrieved2.IsNull()); + EXPECT_EQ(aRetrieved1->UncheckedValue(), 100); + EXPECT_NEAR(aRetrieved2->UncheckedValue(), 2.718, 1.0e-10); + + // Remove one; the other should remain. + myGraph.Cache().Remove(aFaceId, testIntAttrKind()); + EXPECT_TRUE(myGraph.Cache().Get(aFaceId, testIntAttrKind()).IsNull()); + EXPECT_FALSE(myGraph.Cache().Get(aFaceId, testAuxAttrKind()).IsNull()); +} + +TEST_F(BRepGraphTest, InvalidateUserAttribute_SpecificKey) +{ + BRepGraph_NodeId aFaceId(BRepGraph_NodeId::Kind::Face, 0); + Handle(BRepGraph_TypedCacheValue) anAttr = new BRepGraph_TypedCacheValue(42); + myGraph.Cache().Set(aFaceId, testIntAttrKind(), anAttr); + + // Invalidate should not remove, just mark dirty. + myGraph.Cache().Invalidate(aFaceId, testIntAttrKind()); + + occ::handle aRetrieved = myGraph.Cache().Get(aFaceId, testIntAttrKind()); + EXPECT_FALSE(aRetrieved.IsNull()); // still present +} + +TEST_F(BRepGraphTest, UserAttribute_OnEdgeNode) +{ + BRepGraph_NodeId anEdgeId(BRepGraph_NodeId::Kind::Edge, 0); + Handle(BRepGraph_TypedCacheValue) anAttr = new BRepGraph_TypedCacheValue(1.5); + + myGraph.Cache().Set(anEdgeId, testDoubleAttrKind(), anAttr); + + Handle(BRepGraph_TypedCacheValue) aRetrieved = + Handle(BRepGraph_TypedCacheValue)::DownCast( + myGraph.Cache().Get(anEdgeId, testDoubleAttrKind())); + ASSERT_FALSE(aRetrieved.IsNull()); + EXPECT_NEAR(aRetrieved->UncheckedValue(), 1.5, 1.0e-10); +} + +// =================================================================== +// Group 14: Build From Different Root Shapes +// =================================================================== + +TEST_F(BRepGraphTest, Build_SingleFace_CorrectCounts) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + TopExp_Explorer anExp(aBoxMaker.Shape(), TopAbs_FACE); + const TopoDS_Face& aFace = TopoDS::Face(anExp.Current()); + + BRepGraph aGraph; + aGraph.Build(aFace); + ASSERT_TRUE(aGraph.IsDone()); + EXPECT_EQ(aGraph.Topo().Solids().Nb(), 0); + EXPECT_EQ(aGraph.Topo().Shells().Nb(), 0); + EXPECT_EQ(aGraph.Topo().Faces().Nb(), 1); + EXPECT_EQ(aGraph.Topo().Wires().Nb(), 1); + EXPECT_EQ(aGraph.Topo().Edges().Nb(), 4); + EXPECT_EQ(aGraph.Topo().Vertices().Nb(), 4); +} + +TEST_F(BRepGraphTest, Build_Shell_CorrectCounts) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + TopExp_Explorer anExp(aBoxMaker.Shape(), TopAbs_SHELL); + ASSERT_TRUE(anExp.More()); + + BRepGraph aGraph; + aGraph.Build(anExp.Current()); + ASSERT_TRUE(aGraph.IsDone()); + EXPECT_EQ(aGraph.Topo().Solids().Nb(), 0); + EXPECT_EQ(aGraph.Topo().Shells().Nb(), 1); + EXPECT_EQ(aGraph.Topo().Faces().Nb(), 6); + EXPECT_EQ(aGraph.Topo().Edges().Nb(), 12); + EXPECT_EQ(aGraph.Topo().Vertices().Nb(), 8); +} + +TEST_F(BRepGraphTest, Build_CompoundOfTwoSolids) +{ + BRepPrimAPI_MakeBox aBox1(10.0, 10.0, 10.0); + BRepPrimAPI_MakeBox aBox2(20.0, 20.0, 20.0); + + BRep_Builder aBuilder; + TopoDS_Compound aCompound; + aBuilder.MakeCompound(aCompound); + aBuilder.Add(aCompound, aBox1.Shape()); + aBuilder.Add(aCompound, aBox2.Shape()); + + BRepGraph aGraph; + aGraph.Build(aCompound); + ASSERT_TRUE(aGraph.IsDone()); + EXPECT_EQ(aGraph.Topo().Solids().Nb(), 2); + EXPECT_EQ(aGraph.Topo().Shells().Nb(), 2); + EXPECT_EQ(aGraph.Topo().Faces().Nb(), 12); +} + +TEST_F(BRepGraphTest, ReconstructShape_ShellRoot_SameFaceCount) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + TopExp_Explorer anExp(aBoxMaker.Shape(), TopAbs_SHELL); + ASSERT_TRUE(anExp.More()); + + BRepGraph aGraph; + aGraph.Build(anExp.Current()); + ASSERT_TRUE(aGraph.IsDone()); + ASSERT_EQ(aGraph.Topo().Shells().Nb(), 1); + + BRepGraph_NodeId aShellId(BRepGraph_NodeId::Kind::Shell, 0); + const TopoDS_Shape aReconstructed = aGraph.Shapes().Reconstruct(aShellId); + + int aFaceCount = 0; + for (TopExp_Explorer aFaceExp(aReconstructed, TopAbs_FACE); aFaceExp.More(); aFaceExp.Next()) + ++aFaceCount; + EXPECT_EQ(aFaceCount, 6); +} + +// =================================================================== +// Group 15: UID Properties +// =================================================================== + +TEST_F(BRepGraphTest, UID_FaceIsTopology) +{ + EXPECT_TRUE(myGraph.UIDs().Of(BRepGraph_NodeId(BRepGraph_NodeId::Kind::Face, 0)).IsTopology()); +} + +TEST_F(BRepGraphTest, UID_AllTopoNodesHaveValidUIDs) +{ + for (BRepGraph_FaceIterator aFaceIt(myGraph); aFaceIt.More(); aFaceIt.Next()) + { + EXPECT_TRUE(myGraph.UIDs().Of(BRepGraph_NodeId(aFaceIt.CurrentId())).IsValid()) + << "Face " << aFaceIt.CurrentId().Index << " has invalid UID"; + } + for (BRepGraph_EdgeIterator anEdgeIt(myGraph); anEdgeIt.More(); anEdgeIt.Next()) + { + EXPECT_TRUE(myGraph.UIDs().Of(BRepGraph_NodeId(anEdgeIt.CurrentId())).IsValid()) + << "Edge " << anEdgeIt.CurrentId().Index << " has invalid UID"; + } +} + +TEST_F(BRepGraphTest, Wire_OuterWireIdx_MatchesFaceDef) +{ + for (BRepGraph_FaceIterator aFaceIt(myGraph); aFaceIt.More(); aFaceIt.Next()) + { + const BRepGraph_WireId anOuterWire = + BRepGraph_TestTools::OuterWireOfFace(myGraph, aFaceIt.CurrentId()); + EXPECT_TRUE(anOuterWire.IsValid()) + << "Face " << aFaceIt.CurrentId().Index << " has no outer wire"; + if (!anOuterWire.IsValid()) + continue; + EXPECT_LT(anOuterWire.Index, myGraph.Topo().Wires().Nb()) + << "Face " << aFaceIt.CurrentId().Index << " outer wire index out of range"; + } +} + +TEST_F(BRepGraphTest, Wire_CoEdgeRefs_FourEdgesPerBoxFace) +{ + for (BRepGraph_FaceIterator aFaceIt(myGraph); aFaceIt.More(); aFaceIt.Next()) + { + const BRepGraph_WireId anOuterWire = + BRepGraph_TestTools::OuterWireOfFace(myGraph, aFaceIt.CurrentId()); + ASSERT_TRUE(anOuterWire.IsValid()); + EXPECT_EQ(BRepGraph_TestTools::CountCoEdgeRefsOfWire(myGraph, anOuterWire), 4) + << "Box face " << aFaceIt.CurrentId().Index << " should have 4 coedge refs in its outer wire"; + } +} + +// =================================================================== +// Group: History Enabled Flag +// =================================================================== + +TEST_F(BRepGraphTest, SetHistoryEnabled_DefaultTrue) +{ + EXPECT_TRUE(myGraph.History().IsEnabled()); +} + +TEST_F(BRepGraphTest, SetHistoryEnabled_DisableAndQuery) +{ + myGraph.History().SetEnabled(false); + EXPECT_FALSE(myGraph.History().IsEnabled()); + myGraph.History().SetEnabled(true); + EXPECT_TRUE(myGraph.History().IsEnabled()); +} + +TEST_F(BRepGraphTest, RecordHistory_Disabled_NoRecordAdded) +{ + const int aBefore = myGraph.History().NbRecords(); + + myGraph.History().SetEnabled(false); + + BRepGraph_NodeId anEdge0(BRepGraph_NodeId::Kind::Edge, 0); + BRepGraph_NodeId anEdge1(BRepGraph_NodeId::Kind::Edge, 1); + NCollection_Vector aRepl; + aRepl.Append(anEdge1); + myGraph.History().Record("ShouldNotRecord", anEdge0, aRepl); + + EXPECT_EQ(myGraph.History().NbRecords(), aBefore); +} + +TEST_F(BRepGraphTest, RecordHistory_ReEnabled_RecordsAgain) +{ + myGraph.History().SetEnabled(false); + + const int aBefore = myGraph.History().NbRecords(); + + BRepGraph_NodeId anEdge0(BRepGraph_NodeId::Kind::Edge, 0); + BRepGraph_NodeId anEdge1(BRepGraph_NodeId::Kind::Edge, 1); + NCollection_Vector aRepl; + aRepl.Append(anEdge1); + myGraph.History().Record("Skipped", anEdge0, aRepl); + EXPECT_EQ(myGraph.History().NbRecords(), aBefore); + + myGraph.History().SetEnabled(true); + myGraph.History().Record("Recorded", anEdge0, aRepl); + EXPECT_EQ(myGraph.History().NbRecords(), aBefore + 1); + EXPECT_TRUE(myGraph.History().Record(aBefore).OperationName.IsEqual("Recorded")); +} + +TEST_F(BRepGraphTest, ApplyModification_HistoryDisabled_NoHistoryNoDerivedEdges) +{ + myGraph.History().SetEnabled(false); + + const int aNbHistBefore = myGraph.History().NbRecords(); + + BRepGraph_NodeId anEdge0(BRepGraph_NodeId::Kind::Edge, 0); + BRepGraph_NodeId anEdge1(BRepGraph_NodeId::Kind::Edge, 1); + + auto aModifier = [&](BRepGraph& /*theGraph*/, BRepGraph_NodeId /*theTarget*/) { + NCollection_Vector aResult; + aResult.Append(anEdge1); + return aResult; + }; + + myGraph.Builder().ApplyModification(anEdge0, aModifier, "NoHistory"); + + // No history records should be added. + EXPECT_EQ(myGraph.History().NbRecords(), aNbHistBefore); +} + +TEST_F(BRepGraphTest, ApplyModification_HistoryDisabled_ModifierStillRuns) +{ + myGraph.History().SetEnabled(false); + + bool isModifierCalled = false; + BRepGraph_NodeId anEdge0(BRepGraph_NodeId::Kind::Edge, 0); + + auto aModifier = [&](BRepGraph& /*theGraph*/, BRepGraph_NodeId /*theTarget*/) { + isModifierCalled = true; + NCollection_Vector aResult; + aResult.Append(anEdge0); + return aResult; + }; + + myGraph.Builder().ApplyModification(anEdge0, aModifier, "CheckModifier"); + EXPECT_TRUE(isModifierCalled); +} diff --git a/src/ModelingData/TKBRep/GTests/BRepGraph_Transform_Test.cxx b/src/ModelingData/TKBRep/GTests/BRepGraph_Transform_Test.cxx new file mode 100644 index 0000000000..4d9d5d96ba --- /dev/null +++ b/src/ModelingData/TKBRep/GTests/BRepGraph_Transform_Test.cxx @@ -0,0 +1,187 @@ +// Copyright (c) 2026 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 + +TEST(BRepGraph_TransformTest, TranslateBox_FaceCount) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + + gp_Trsf aTrsf; + aTrsf.SetTranslation(gp_Vec(100.0, 200.0, 300.0)); + + BRepGraph aResultGraph = BRepGraph_Transform::Perform(aGraph, aTrsf, true); + ASSERT_TRUE(aResultGraph.IsDone()); + EXPECT_EQ(aResultGraph.Topo().Faces().Nb(), 6); + EXPECT_EQ(aResultGraph.Topo().Faces().Nb(), aGraph.Topo().Faces().Nb()); +} + +TEST(BRepGraph_TransformTest, TranslateBox_AreaPreserved) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + GProp_GProps aOrigProps; + BRepGProp::SurfaceProperties(aBox, aOrigProps); + const double anOrigArea = aOrigProps.Mass(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + + gp_Trsf aTrsf; + aTrsf.SetTranslation(gp_Vec(50.0, 0.0, 0.0)); + + BRepGraph aResultGraph = BRepGraph_Transform::Perform(aGraph, aTrsf, true); + ASSERT_TRUE(aResultGraph.IsDone()); + + // Verify area is preserved by summing individual face areas. + double aTransArea = 0.0; + for (int aFaceIdx = 0; aFaceIdx < aResultGraph.Topo().Faces().Nb(); ++aFaceIdx) + { + TopoDS_Shape aFace = aResultGraph.Shapes().Reconstruct(BRepGraph_FaceId(aFaceIdx)); + GProp_GProps aProps; + BRepGProp::SurfaceProperties(aFace, aProps); + aTransArea += std::abs(aProps.Mass()); + } + + EXPECT_NEAR(aTransArea, anOrigArea, anOrigArea * 0.01); +} + +TEST(BRepGraph_TransformTest, TranslateBox_VertexPointsShifted) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + ASSERT_GT(aGraph.Topo().Vertices().Nb(), 0); + + const double aDx = 100.0, aDy = 200.0, aDz = 300.0; + gp_Trsf aTrsf; + aTrsf.SetTranslation(gp_Vec(aDx, aDy, aDz)); + + BRepGraph aResultGraph = BRepGraph_Transform::Perform(aGraph, aTrsf, true); + ASSERT_TRUE(aResultGraph.IsDone()); + ASSERT_EQ(aResultGraph.Topo().Vertices().Nb(), aGraph.Topo().Vertices().Nb()); + + // Verify that all vertices have been shifted. + for (int anIdx = 0; anIdx < aGraph.Topo().Vertices().Nb(); ++anIdx) + { + const gp_Pnt anOrigPt = BRepGraph_Tool::Vertex::Pnt(aGraph, BRepGraph_VertexId(anIdx)); + const gp_Pnt aTransPt = BRepGraph_Tool::Vertex::Pnt(aResultGraph, BRepGraph_VertexId(anIdx)); + EXPECT_NEAR(aTransPt.X(), anOrigPt.X() + aDx, Precision::Confusion()) + << "Vertex " << anIdx << " X mismatch"; + EXPECT_NEAR(aTransPt.Y(), anOrigPt.Y() + aDy, Precision::Confusion()) + << "Vertex " << anIdx << " Y mismatch"; + EXPECT_NEAR(aTransPt.Z(), anOrigPt.Z() + aDz, Precision::Confusion()) + << "Vertex " << anIdx << " Z mismatch"; + } +} + +TEST(BRepGraph_TransformTest, LocationOnly_NoCopyGeom) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + ASSERT_GT(aGraph.Topo().Vertices().Nb(), 0); + + const double aDx = 50.0; + gp_Trsf aTrsf; + aTrsf.SetTranslation(gp_Vec(aDx, 0.0, 0.0)); + + // theCopyGeom = false: location-only, no geometry modification. + BRepGraph aResultGraph = BRepGraph_Transform::Perform(aGraph, aTrsf, false); + ASSERT_TRUE(aResultGraph.IsDone()); + EXPECT_EQ(aResultGraph.Topo().Faces().Nb(), 6); + EXPECT_EQ(aResultGraph.Topo().Vertices().Nb(), aGraph.Topo().Vertices().Nb()); + + // Vertex definition points must NOT be modified (location-only mode). + for (int anIdx = 0; anIdx < aGraph.Topo().Vertices().Nb(); ++anIdx) + { + const gp_Pnt anOrigPt = BRepGraph_Tool::Vertex::Pnt(aGraph, BRepGraph_VertexId(anIdx)); + const gp_Pnt aGraphPt = BRepGraph_Tool::Vertex::Pnt(aResultGraph, BRepGraph_VertexId(anIdx)); + EXPECT_NEAR(aGraphPt.X(), anOrigPt.X(), Precision::Confusion()) + << "Vertex " << anIdx << " point should not be modified"; + } + + // Verify the transform is stored on Product::RootLocation. + ASSERT_GT(aResultGraph.Topo().Products().Nb(), 0); + const TopLoc_Location& aRootLoc = + aResultGraph.Topo().Products().Definition(BRepGraph_ProductId(0)).RootLocation; + EXPECT_FALSE(aRootLoc.IsIdentity()); + const gp_Trsf aProductTrsf = aRootLoc.Transformation(); + EXPECT_NEAR(aProductTrsf.Value(1, 4), aDx, Precision::Confusion()); + EXPECT_NEAR(aProductTrsf.Value(2, 4), 0.0, Precision::Confusion()); + EXPECT_NEAR(aProductTrsf.Value(3, 4), 0.0, Precision::Confusion()); + + // Verify that reconstructed solid + RootLocation produces correct geometry. + ASSERT_GT(aResultGraph.Topo().Solids().Nb(), 0); + TopoDS_Shape aTransSolid = aResultGraph.Shapes().Reconstruct(BRepGraph_SolidId(0)); + ASSERT_FALSE(aTransSolid.IsNull()); + aTransSolid.Location(aRootLoc); + + GProp_GProps aOrigProps; + BRepGProp::SurfaceProperties(aBox, aOrigProps); + + GProp_GProps aTransProps; + BRepGProp::SurfaceProperties(aTransSolid, aTransProps); + EXPECT_NEAR(aTransProps.CentreOfMass().X(), aOrigProps.CentreOfMass().X() + aDx, 1.0); +} + +TEST(BRepGraph_TransformTest, TransformSingleFace) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + ASSERT_GT(aGraph.Topo().Faces().Nb(), 0); + + gp_Trsf aTrsf; + aTrsf.SetTranslation(gp_Vec(10.0, 20.0, 30.0)); + + BRepGraph aResultGraph = + BRepGraph_Transform::TransformFace(aGraph, BRepGraph_FaceId(0), aTrsf, true); + ASSERT_TRUE(aResultGraph.IsDone()); + EXPECT_EQ(aResultGraph.Topo().Faces().Nb(), 1); +} diff --git a/src/ModelingData/TKBRep/GTests/BRepGraph_Validate_Test.cxx b/src/ModelingData/TKBRep/GTests/BRepGraph_Validate_Test.cxx new file mode 100644 index 0000000000..e33c8ed886 --- /dev/null +++ b/src/ModelingData/TKBRep/GTests/BRepGraph_Validate_Test.cxx @@ -0,0 +1,637 @@ +// Copyright (c) 2026 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 + +namespace +{ + +//================================================================================================= + +NCollection_Vector coEdgeRefsOfWire(const BRepGraph& theGraph, + const BRepGraph_WireId theWireId) +{ + NCollection_Vector aRefIds; + const BRepGraph_NodeId aParentNode = BRepGraph_WireId(theWireId.Index); + const BRepGraph::RefsView& aRefs = theGraph.Refs(); + for (int aRefIdx = 0; aRefIdx < aRefs.CoEdges().Nb(); ++aRefIdx) + { + const BRepGraph_CoEdgeRefId aRefId(aRefIdx); + const BRepGraphInc::CoEdgeRef& aRef = aRefs.CoEdges().Entry(aRefId); + if (aRef.ParentId == aParentNode && !aRef.IsRemoved) + aRefIds.Append(aRefId); + } + return aRefIds; +} + +} // namespace + +TEST(BRepGraph_ValidateTest, CleanGraph_NoIssues) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + + const BRepGraph_Validate::Result aResult = BRepGraph_Validate::Perform(aGraph); + EXPECT_TRUE(aResult.IsValid()); + EXPECT_EQ(aResult.NbIssues(BRepGraph_Validate::Severity::Error), 0); + EXPECT_EQ(aResult.NbIssues(BRepGraph_Validate::Severity::Warning), 0); + + const BRepGraph_Validate::Options anAuditOpts = BRepGraph_Validate::Options::Audit(); + const BRepGraph_Validate::Result anAuditResult = BRepGraph_Validate::Perform(aGraph, anAuditOpts); + EXPECT_TRUE(anAuditResult.IsValid()); + EXPECT_EQ(anAuditResult.NbIssues(BRepGraph_Validate::Severity::Error), 0); + + const BRepGraph_Validate::Options aLightOpts = BRepGraph_Validate::Options::Lightweight(); + const BRepGraph_Validate::Result aLightResult = BRepGraph_Validate::Perform(aGraph, aLightOpts); + EXPECT_TRUE(aLightResult.IsValid()); + EXPECT_EQ(aLightResult.NbIssues(BRepGraph_Validate::Severity::Error), 0); +} + +TEST(BRepGraph_ValidateTest, AfterGeomDeduplicate_NoIssues) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + TopExp_Explorer anExp(aBox, TopAbs_FACE); + const TopoDS_Shape aFace = anExp.Current(); + + BRepBuilderAPI_Copy aCopy1(aFace, true); + BRepBuilderAPI_Copy aCopy2(aFace, true); + + BRep_Builder aBuilder; + TopoDS_Compound aCompound; + aBuilder.MakeCompound(aCompound); + aBuilder.Add(aCompound, aCopy1.Shape()); + aBuilder.Add(aCompound, aCopy2.Shape()); + + BRepGraph aGraph; + aGraph.Build(aCompound); + ASSERT_TRUE(aGraph.IsDone()); + + (void)BRepGraph_Deduplicate::Perform(aGraph); + + const BRepGraph_Validate::Result aResult = BRepGraph_Validate::Perform(aGraph); + EXPECT_TRUE(aResult.IsValid()); + EXPECT_EQ(aResult.NbIssues(BRepGraph_Validate::Severity::Error), 0); +} + +TEST(BRepGraph_ValidateTest, DetectsRemovedNodeReference) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + ASSERT_GT(aGraph.Topo().Vertices().Nb(), 0); + + // Find an edge that has a valid start vertex and remove it. + int aVtxToRemove = -1; + for (int anEdgeIdx = 0; anEdgeIdx < aGraph.Topo().Edges().Nb(); ++anEdgeIdx) + { + const BRepGraph_EdgeId anEdgeId(anEdgeIdx); + const BRepGraphInc::VertexRef& aStartRef = BRepGraph_Tool::Edge::StartVertex(aGraph, anEdgeId); + if (aStartRef.VertexDefId.IsValid()) + { + aVtxToRemove = aStartRef.VertexDefId.Index; + break; + } + } + ASSERT_GE(aVtxToRemove, 0); + + // Remove the vertex without fixing edges referencing it. + aGraph.Builder().RemoveNode(BRepGraph_VertexId(aVtxToRemove)); + + const BRepGraph_Validate::Result aDefaultResult = BRepGraph_Validate::Perform(aGraph); + EXPECT_TRUE(aDefaultResult.IsValid()); + + const BRepGraph_Validate::Result anAuditResult = + BRepGraph_Validate::Perform(aGraph, BRepGraph_Validate::Options::Audit()); + EXPECT_FALSE(anAuditResult.IsValid()); + EXPECT_GT(anAuditResult.NbIssues(BRepGraph_Validate::Severity::Error), 0); +} + +TEST(BRepGraph_ValidateTest, WireConnectivity_DisconnectedEdges) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + ASSERT_GT(aGraph.Topo().Wires().Nb(), 0); + + // Corrupt a wire by swapping its vertex reference to break connectivity. + // Find a wire with at least 2 edges. + int aTargetWire = -1; + for (int aWireIdx = 0; aWireIdx < aGraph.Topo().Wires().Nb(); ++aWireIdx) + { + if (coEdgeRefsOfWire(aGraph, BRepGraph_WireId(aWireIdx)).Length() >= 2) + { + aTargetWire = aWireIdx; + break; + } + } + ASSERT_GE(aTargetWire, 0); + + // Get the first edge in the wire and corrupt its end vertex. + const NCollection_Vector aWireRefIds = + coEdgeRefsOfWire(aGraph, BRepGraph_WireId(aTargetWire)); + ASSERT_GE(aWireRefIds.Length(), 1); + const BRepGraphInc::CoEdgeRef& aFirstCR = aGraph.Refs().CoEdges().Entry(aWireRefIds.Value(0)); + const BRepGraphInc::CoEdgeDef& aFirstCoEdge = + aGraph.Topo().CoEdges().Definition(BRepGraph_CoEdgeId(aFirstCR.CoEdgeDefId)); + const BRepGraph_NodeId aFirstEdgeId(aFirstCoEdge.EdgeDefId); + ASSERT_TRUE(aFirstEdgeId.IsValid()); + + BRepGraph_MutGuard aFirstEdge = + aGraph.Builder().MutEdge(BRepGraph_EdgeId(aFirstEdgeId.Index)); + + // Find a vertex different from the current end vertex. + const BRepGraph_VertexId anOrigEndVtx = + aGraph.Refs().Vertices().Entry(aFirstEdge->EndVertexRefId).VertexDefId; + const BRepGraph_VertexId anOrigStartVtx = + aGraph.Refs().Vertices().Entry(aFirstEdge->StartVertexRefId).VertexDefId; + const BRepGraph_NodeId anOrigEnd = BRepGraph_NodeId(anOrigEndVtx); + for (int aVtxIdx = 0; aVtxIdx < aGraph.Topo().Vertices().Nb(); ++aVtxIdx) + { + if (BRepGraph_VertexId(aVtxIdx) != anOrigEnd + && BRepGraph_VertexId(aVtxIdx) != BRepGraph_NodeId(anOrigStartVtx)) + { + BRepGraph_MutGuard aMutEndRef = + aGraph.Builder().MutVertexRef(aFirstEdge->EndVertexRefId); + aMutEndRef->VertexDefId = BRepGraph_VertexId(aVtxIdx); + break; + } + } + + ASSERT_NE(aGraph.Refs().Vertices().Entry(aFirstEdge->EndVertexRefId).VertexDefId, anOrigEndVtx); + + const BRepGraph_Validate::Result aResult = + BRepGraph_Validate::Perform(aGraph, BRepGraph_Validate::Options::Audit()); + EXPECT_FALSE(aResult.IsValid()); + + // Check that at least one connectivity error was found. + bool aFoundConnectivity = false; + for (int anIdx = 0; anIdx < aResult.Issues.Length(); ++anIdx) + { + const BRepGraph_Validate::Issue& anIssue = aResult.Issues.Value(anIdx); + if (anIssue.Sev == BRepGraph_Validate::Severity::Error) + { + TCollection_AsciiString aDesc = anIssue.Description; + if (aDesc.Search("Wire edges not connected") > 0) + { + aFoundConnectivity = true; + break; + } + } + } + EXPECT_TRUE(aFoundConnectivity); +} + +TEST(BRepGraph_ValidateTest, BoundsCheck_InvalidIndex) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + ASSERT_GT(aGraph.Topo().Edges().Nb(), 0); + + // Corrupt edge's Curve3d to null. + BRepGraph_MutGuard anEdge = aGraph.Builder().MutEdge(BRepGraph_EdgeId(0)); + anEdge->Curve3DRepId = BRepGraph_Curve3DRepId(); + + const BRepGraph_Validate::Result aResult = + BRepGraph_Validate::Perform(aGraph, BRepGraph_Validate::Options::Audit()); + EXPECT_FALSE(aResult.IsValid()); + EXPECT_GT(aResult.NbIssues(BRepGraph_Validate::Severity::Error), 0); +} + +TEST(BRepGraph_ValidateTest, AfterSplitEdge_ProducesSubEdges) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + const int anOrigEdgeCount = aGraph.Topo().Edges().Nb(); + + // Find a non-degenerate edge with valid vertices to split. + BRepGraph_EdgeId anEdgeId; + double aSplitParam = 0.0; + for (int i = 0; i < aGraph.Topo().Edges().Nb(); ++i) + { + const BRepGraph_EdgeId anCandEdgeId(i); + const BRepGraphInc::EdgeDef& anEdgeDef = aGraph.Topo().Edges().Definition(anCandEdgeId); + if (!anEdgeDef.IsDegenerate && anEdgeDef.Curve3DRepId.IsValid() + && BRepGraph_Tool::Edge::StartVertex(aGraph, anCandEdgeId).VertexDefId.IsValid() + && BRepGraph_Tool::Edge::EndVertex(aGraph, anCandEdgeId).VertexDefId.IsValid()) + { + anEdgeId = BRepGraph_EdgeId(i); + aSplitParam = 0.5 * (anEdgeDef.ParamFirst + anEdgeDef.ParamLast); + break; + } + } + ASSERT_TRUE(anEdgeId.IsValid()); + + // Create a split vertex at the midpoint. + gp_Pnt aMidPt; + BRepGraph_Tool::Edge::Curve(aGraph, anEdgeId)->D0(aSplitParam, aMidPt); + BRepGraph_VertexId aSplitVtx = + aGraph.Builder().AddVertex(aMidPt, BRepGraph_Tool::Edge::Tolerance(aGraph, anEdgeId)); + + BRepGraph_EdgeId aSubA, aSubB; + aGraph.Builder().SplitEdge(anEdgeId, aSplitVtx, aSplitParam, aSubA, aSubB); + + // Two new sub-edges should have been created. + EXPECT_EQ(aGraph.Topo().Edges().Nb(), anOrigEdgeCount + 2); + EXPECT_TRUE(aSubA.IsValid()); + EXPECT_TRUE(aSubB.IsValid()); + + // Original edge should be marked removed. + EXPECT_TRUE(aGraph.Topo().Edges().Definition(anEdgeId).IsRemoved); +} + +TEST(BRepGraph_ValidateTest, CorruptedPCurve_FaceDefIdOutOfBounds) +{ + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + ASSERT_GT(aGraph.Topo().Edges().Nb(), 0); + + // Corrupt a CoEdge's FaceDefId to an out-of-range value. + ASSERT_GT(aGraph.Topo().CoEdges().Nb(), 0); + BRepGraph_MutGuard aCoEdgeDef = + aGraph.Builder().MutCoEdge(BRepGraph_CoEdgeId(0)); + aCoEdgeDef->FaceDefId = BRepGraph_FaceId(aGraph.Topo().Faces().Nb() + 999); + + const BRepGraph_Validate::Result aDefaultResult = BRepGraph_Validate::Perform(aGraph); + EXPECT_FALSE(aDefaultResult.IsValid()); + + const BRepGraph_Validate::Result aLightResult = + BRepGraph_Validate::Perform(aGraph, BRepGraph_Validate::Options::Lightweight()); + EXPECT_FALSE(aLightResult.IsValid()); + + const BRepGraph_Validate::Result anAuditResult = + BRepGraph_Validate::Perform(aGraph, BRepGraph_Validate::Options::Audit()); + EXPECT_FALSE(anAuditResult.IsValid()); +} + +TEST(BRepGraph_ValidateTest, LightweightAndAudit_DetectActiveCountDrift) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 20.0, 30.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + + const int aNbActiveFacesBefore = aGraph.Topo().Faces().NbActive(); + ASSERT_GT(aNbActiveFacesBefore, 0); + + // Intentionally bypass RemoveNode() to simulate counter drift bug class. + BRepGraph_MutGuard aFaceDef = + aGraph.Builder().MutFace(BRepGraph_FaceId(0)); + aFaceDef->IsRemoved = true; + + const BRepGraph_Validate::Result aLightResult = + BRepGraph_Validate::Perform(aGraph, BRepGraph_Validate::Options::Lightweight()); + EXPECT_FALSE(aLightResult.IsValid()); + EXPECT_GT(aLightResult.NbIssues(BRepGraph_Validate::Severity::Error), 0); + + bool aFoundBoundaryActiveCountMismatch = false; + for (int anIdx = 0; anIdx < aLightResult.Issues.Length(); ++anIdx) + { + const TCollection_AsciiString& aDesc = aLightResult.Issues.Value(anIdx).Description; + if (aDesc.Search("Mutation boundary active count mismatch for Faces") > 0) + { + aFoundBoundaryActiveCountMismatch = true; + break; + } + } + EXPECT_TRUE(aFoundBoundaryActiveCountMismatch); + + const BRepGraph_Validate::Result anAuditResult = + BRepGraph_Validate::Perform(aGraph, BRepGraph_Validate::Options::Audit()); + EXPECT_FALSE(anAuditResult.IsValid()); + EXPECT_GT(anAuditResult.NbIssues(BRepGraph_Validate::Severity::Error), 0); + + bool aFoundActiveCountMismatch = false; + for (int anIdx = 0; anIdx < anAuditResult.Issues.Length(); ++anIdx) + { + const TCollection_AsciiString& aDesc = anAuditResult.Issues.Value(anIdx).Description; + if (aDesc.Search("NbActiveFaces mismatch") > 0) + { + aFoundActiveCountMismatch = true; + break; + } + } + EXPECT_TRUE(aFoundActiveCountMismatch); + EXPECT_EQ(aGraph.Topo().Faces().NbActive(), aNbActiveFacesBefore); +} + +TEST(BRepGraph_ValidateTest, DeepDetectsIdDriftButLightweightSkipsIt) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 20.0, 30.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + ASSERT_GT(aGraph.Topo().Faces().Nb(), 0); + + BRepGraph_MutGuard aFaceDef = + aGraph.Builder().MutFace(BRepGraph_FaceId(0)); + aFaceDef->Id = BRepGraph_FaceId(42); + + const BRepGraph_Validate::Result aLightResult = + BRepGraph_Validate::Perform(aGraph, BRepGraph_Validate::Options::Lightweight()); + EXPECT_TRUE(aLightResult.IsValid()); + + const BRepGraph_Validate::Result anAuditResult = + BRepGraph_Validate::Perform(aGraph, BRepGraph_Validate::Options::Audit()); + EXPECT_FALSE(anAuditResult.IsValid()); + + const BRepGraph_Validate::Result aDefaultResult = BRepGraph_Validate::Perform(aGraph); + EXPECT_TRUE(aDefaultResult.IsValid()); +} + +TEST(BRepGraph_ValidateTest, Audit_ValidatesCoEdgeUIDsFromBuilderWireCreation) +{ + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 20.0, 30.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + ASSERT_GT(aGraph.Topo().Edges().Nb(), 0); + + NCollection_Vector> anEdges; + anEdges.Append(std::make_pair(BRepGraph_EdgeId(0), TopAbs_FORWARD)); + const BRepGraph_WireId aWireId = aGraph.Builder().AddWire(anEdges); + ASSERT_TRUE(aWireId.IsValid()); + + const NCollection_Vector aWireRefIds = coEdgeRefsOfWire(aGraph, aWireId); + ASSERT_EQ(aWireRefIds.Length(), 1); + const BRepGraph_NodeId aCoEdgeId = + BRepGraph_CoEdgeId(aGraph.Refs().CoEdges().Entry(aWireRefIds.Value(0)).CoEdgeDefId.Index); + EXPECT_TRUE(aGraph.UIDs().Of(aCoEdgeId).IsValid()); + + const BRepGraph_Validate::Result anAuditResult = + BRepGraph_Validate::Perform(aGraph, BRepGraph_Validate::Options::Audit()); + if (!anAuditResult.IsValid()) + { + for (int anIdx = 0; anIdx < anAuditResult.Issues.Length(); ++anIdx) + { + const BRepGraph_Validate::Issue& anIssue = anAuditResult.Issues.Value(anIdx); + ADD_FAILURE() << "Issue[" << anIdx << "] kind=" << static_cast(anIssue.NodeId.NodeKind) + << " idx=" << anIssue.NodeId.Index + << " desc=" << anIssue.Description.ToCString(); + } + } + EXPECT_TRUE(anAuditResult.IsValid()); + EXPECT_EQ(anAuditResult.NbIssues(BRepGraph_Validate::Severity::Error), 0); +} + +// --------------------------------------------------------------------------- +// Assembly validation tests +// --------------------------------------------------------------------------- + +TEST(BRepGraph_ValidateTest, AssemblyGraph_ValidProduct_NoIssuesInAudit) +{ + // Build a box; Build() auto-creates a root part product. + const TopoDS_Shape aBox = BRepPrimAPI_MakeBox(10.0, 20.0, 30.0).Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + ASSERT_GE(aGraph.Topo().Products().Nb(), 1); + + // Identify the auto-created part product. + const occ::handle anAllocator = new NCollection_IncAllocator(); + const NCollection_Vector aInitialRootProducts = + aGraph.Topo().Products().RootProducts(anAllocator); + ASSERT_GT(aInitialRootProducts.Length(), 0); + const BRepGraph_ProductId aPartProduct = aInitialRootProducts.Value(0); + ASSERT_TRUE(aGraph.Topo().Products().IsPart(aPartProduct)); + + // Explicitly create an assembly product and add two occurrences of the part. + const BRepGraph_ProductId aAssemblyProduct = aGraph.Builder().AddAssemblyProduct(); + ASSERT_TRUE(aAssemblyProduct.IsValid()); + + gp_Trsf aTrsf; + aTrsf.SetTranslation(gp_Vec(20.0, 0.0, 0.0)); + const BRepGraph_OccurrenceId anOcc1 = + aGraph.Builder().AddOccurrence(aAssemblyProduct, aPartProduct, TopLoc_Location()); + const BRepGraph_OccurrenceId anOcc2 = + aGraph.Builder().AddOccurrence(aAssemblyProduct, aPartProduct, TopLoc_Location(aTrsf)); + ASSERT_TRUE(anOcc1.IsValid()); + ASSERT_TRUE(anOcc2.IsValid()); + + // Verify assembly structure. + EXPECT_TRUE(aGraph.Topo().Products().IsAssembly(aAssemblyProduct)); + EXPECT_EQ(aGraph.Topo().Products().NbComponents(aAssemblyProduct), 2); + + // Rebuild reverse index after assembly modifications. + aGraph.Builder().CommitMutation(); + + // Audit should pass on the explicitly constructed assembly. + const BRepGraph_Validate::Result aAuditResult = + BRepGraph_Validate::Perform(aGraph, BRepGraph_Validate::Options::Audit()); + if (!aAuditResult.IsValid()) + { + for (int anIdx = 0; anIdx < aAuditResult.Issues.Length(); ++anIdx) + { + const BRepGraph_Validate::Issue& anIssue = aAuditResult.Issues.Value(anIdx); + ADD_FAILURE() << "Issue[" << anIdx << "] kind=" << static_cast(anIssue.NodeId.NodeKind) + << " idx=" << anIssue.NodeId.Index + << " desc=" << anIssue.Description.ToCString(); + } + } + EXPECT_TRUE(aAuditResult.IsValid()); +} + +TEST(BRepGraph_ValidateTest, AssemblyGraph_CorruptedProductShapeRootId_DetectedByAudit) +{ + const TopoDS_Shape aBox = BRepPrimAPI_MakeBox(10.0, 20.0, 30.0).Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + ASSERT_GE(aGraph.Topo().Products().Nb(), 1); + + // Corrupt ShapeRootId to an out-of-bounds value. + BRepGraph_MutGuard aProduct = + aGraph.Builder().MutProduct(BRepGraph_ProductId(0)); + aProduct->ShapeRootId = BRepGraph_SolidId(aGraph.Topo().Solids().Nb() + 1); + + const BRepGraph_Validate::Result aAuditResult = + BRepGraph_Validate::Perform(aGraph, BRepGraph_Validate::Options::Audit()); + EXPECT_FALSE(aAuditResult.IsValid()) + << "Product with out-of-bounds ShapeRootId should be detected by audit."; + EXPECT_GT(aAuditResult.NbIssues(BRepGraph_Validate::Severity::Error), 0); + + // Verify the specific error message. + bool aFoundExpectedError = false; + for (int anIdx = 0; anIdx < aAuditResult.Issues.Length(); ++anIdx) + { + const BRepGraph_Validate::Issue& anIssue = aAuditResult.Issues.Value(anIdx); + if (anIssue.Sev == BRepGraph_Validate::Severity::Error + && anIssue.Description.Search("ShapeRootId out of bounds") > 0) + { + aFoundExpectedError = true; + break; + } + } + EXPECT_TRUE(aFoundExpectedError) << "Audit should report 'ProductDef.ShapeRootId out of bounds'."; +} + +TEST(BRepGraph_ValidateTest, AssemblyGraph_CorruptedOccurrenceProductDefId_DetectedByAudit) +{ + const TopoDS_Shape aBox = BRepPrimAPI_MakeBox(10.0, 20.0, 30.0).Shape(); + + BRepGraph aGraph; + aGraph.Build(aBox); + ASSERT_TRUE(aGraph.IsDone()); + + // Create an assembly with one occurrence. + const BRepGraph_ProductId aRootAssembly = aGraph.Builder().AddAssemblyProduct(); + ASSERT_TRUE(aRootAssembly.IsValid()); + + const occ::handle anAllocator = new NCollection_IncAllocator(); + const NCollection_Vector aRootProducts = + aGraph.Topo().Products().RootProducts(anAllocator); + BRepGraph_ProductId aPartId; + for (int i = 0; i < aRootProducts.Length(); ++i) + { + const BRepGraph_ProductId aProductId = aRootProducts.Value(i); + if (aGraph.Topo().Products().IsPart(aProductId)) + { + aPartId = aRootProducts.Value(i); + break; + } + } + ASSERT_TRUE(aPartId.IsValid()); + + const BRepGraph_OccurrenceId anOccId = + aGraph.Builder().AddOccurrence(aRootAssembly, aPartId, TopLoc_Location()); + ASSERT_TRUE(anOccId.IsValid()); + + // Corrupt the occurrence's ProductDefId to an invalid index. + BRepGraph_MutGuard anOccDef = + aGraph.Builder().MutOccurrence(anOccId); + anOccDef->ProductDefId = BRepGraph_ProductId(aGraph.Topo().Products().Nb() + 1); + + const BRepGraph_Validate::Result aAuditResult = + BRepGraph_Validate::Perform(aGraph, BRepGraph_Validate::Options::Audit()); + EXPECT_FALSE(aAuditResult.IsValid()) + << "Occurrence with out-of-bounds ProductDefId should be detected by audit."; + EXPECT_GT(aAuditResult.NbIssues(BRepGraph_Validate::Severity::Error), 0); + + // Verify the specific error message. + bool aFoundExpectedError = false; + for (int anIdx = 0; anIdx < aAuditResult.Issues.Length(); ++anIdx) + { + const BRepGraph_Validate::Issue& anIssue = aAuditResult.Issues.Value(anIdx); + if (anIssue.Sev == BRepGraph_Validate::Severity::Error + && anIssue.Description.Search("ProductDefId invalid") > 0) + { + aFoundExpectedError = true; + break; + } + } + EXPECT_TRUE(aFoundExpectedError) << "Audit should report 'OccurrenceDef.ProductDefId invalid'."; +} + +TEST(BRepGraph_ValidateTest, LightweightVsAudit_RemovedVertexReference_Differential) +{ + // Verifies that removed-node isolation is an Audit-only check. + // RemoveNode(vertex) correctly updates active counts (Lightweight passes) + // but leaves edges referencing the removed vertex (Audit detects). + BRepGraph aGraph; + aGraph.Build(BRepPrimAPI_MakeBox(10.0, 20.0, 30.0).Shape()); + ASSERT_TRUE(aGraph.IsDone()); + ASSERT_GT(aGraph.Topo().Vertices().Nb(), 0); + + // Find a vertex referenced by at least one edge. + int aVtxToRemove = -1; + for (int anEdgeIdx = 0; anEdgeIdx < aGraph.Topo().Edges().Nb(); ++anEdgeIdx) + { + const BRepGraph_EdgeId anEdgeId(anEdgeIdx); + const BRepGraphInc::VertexRef& aStartRef = BRepGraph_Tool::Edge::StartVertex(aGraph, anEdgeId); + if (aStartRef.VertexDefId.IsValid()) + { + aVtxToRemove = aStartRef.VertexDefId.Index; + break; + } + } + ASSERT_GE(aVtxToRemove, 0) << "Need a vertex referenced by an edge."; + + // Remove the vertex. RemoveNode correctly decrements active count + // but does NOT fix edges that still reference it. + aGraph.Builder().RemoveNode(BRepGraph_VertexId(aVtxToRemove)); + + // Lightweight only checks active counts - should pass. + const BRepGraph_Validate::Result aLightResult = + BRepGraph_Validate::Perform(aGraph, BRepGraph_Validate::Options::Lightweight()); + EXPECT_TRUE(aLightResult.IsValid()) << "Lightweight should not check removed-node isolation."; + + // Audit runs checkRemovedNodeIsolation - should detect the dangling reference. + const BRepGraph_Validate::Result aAuditResult = + BRepGraph_Validate::Perform(aGraph, BRepGraph_Validate::Options::Audit()); + EXPECT_FALSE(aAuditResult.IsValid()) << "Audit should detect edges referencing a removed vertex."; + EXPECT_GT(aAuditResult.NbIssues(BRepGraph_Validate::Severity::Error), 0); + + // Verify the specific error message. + bool aFoundExpectedError = false; + for (int anIdx = 0; anIdx < aAuditResult.Issues.Length(); ++anIdx) + { + const BRepGraph_Validate::Issue& anIssue = aAuditResult.Issues.Value(anIdx); + if (anIssue.Sev == BRepGraph_Validate::Severity::Error + && anIssue.Description.Search("references removed") > 0) + { + aFoundExpectedError = true; + break; + } + } + EXPECT_TRUE(aFoundExpectedError) + << "Audit should report 'Non-removed EdgeDef references removed StartVertexEntity'."; +} diff --git a/src/ModelingData/TKBRep/GTests/BRepGraph_VersionStamp_Test.cxx b/src/ModelingData/TKBRep/GTests/BRepGraph_VersionStamp_Test.cxx new file mode 100644 index 0000000000..68f96b8d54 --- /dev/null +++ b/src/ModelingData/TKBRep/GTests/BRepGraph_VersionStamp_Test.cxx @@ -0,0 +1,237 @@ +// Copyright (c) 2026 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 + +class BRepGraph_VersionStampTest : public testing::Test +{ +protected: + void SetUp() override + { + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + myGraph.Build(aBox); + ASSERT_TRUE(myGraph.IsDone()); + } + + BRepGraph myGraph; +}; + +// --- VersionStamp struct tests --- + +TEST(BRepGraph_VersionStampBasicTest, DefaultStamp_IsInvalid) +{ + const BRepGraph_VersionStamp aStamp; + EXPECT_FALSE(aStamp.IsValid()); +} + +TEST_F(BRepGraph_VersionStampTest, StampOf_ValidNode_ReturnsValidStamp) +{ + const BRepGraph_FaceId aFaceId(0); + ASSERT_TRUE(aFaceId.IsValid(myGraph.Topo().Faces().Nb())); + + const BRepGraph_VersionStamp aStamp = myGraph.UIDs().StampOf(aFaceId); + EXPECT_TRUE(aStamp.IsValid()); + EXPECT_TRUE(aStamp.myUID.IsValid()); + EXPECT_EQ(aStamp.myMutationGen, 0u); + EXPECT_EQ(aStamp.myGeneration, myGraph.UIDs().Generation()); +} + +TEST_F(BRepGraph_VersionStampTest, StampOf_InvalidNode_ReturnsInvalid) +{ + const BRepGraph_VersionStamp aStamp = myGraph.UIDs().StampOf(BRepGraph_NodeId()); + EXPECT_FALSE(aStamp.IsValid()); +} + +// --- IsStale tests --- + +TEST_F(BRepGraph_VersionStampTest, IsStale_UnmutatedNode_ReturnsFalse) +{ + const BRepGraph_VersionStamp aStamp = myGraph.UIDs().StampOf(BRepGraph_FaceId(0)); + EXPECT_FALSE(myGraph.UIDs().IsStale(aStamp)); +} + +TEST_F(BRepGraph_VersionStampTest, IsStale_MutatedNode_ReturnsTrue) +{ + const BRepGraph_VersionStamp aStamp = myGraph.UIDs().StampOf(BRepGraph_FaceId(0)); + + // Mutate the face. + myGraph.Builder().MutFace(BRepGraph_FaceId(0))->NaturalRestriction = true; + + EXPECT_TRUE(myGraph.UIDs().IsStale(aStamp)); +} + +TEST_F(BRepGraph_VersionStampTest, IsStale_RemovedNode_ReturnsTrue) +{ + const BRepGraph_VersionStamp aStamp = myGraph.UIDs().StampOf(BRepGraph_FaceId(0)); + + myGraph.Builder().RemoveNode(BRepGraph_FaceId(0)); + + EXPECT_TRUE(myGraph.UIDs().IsStale(aStamp)); +} + +TEST_F(BRepGraph_VersionStampTest, IsStale_DifferentGeneration_ReturnsTrue) +{ + const BRepGraph_VersionStamp aStamp = myGraph.UIDs().StampOf(BRepGraph_FaceId(0)); + + // Rebuild the graph - generation changes. + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + myGraph.Build(aBoxMaker.Shape()); + ASSERT_TRUE(myGraph.IsDone()); + + EXPECT_TRUE(myGraph.UIDs().IsStale(aStamp)); +} + +TEST_F(BRepGraph_VersionStampTest, IsStale_DeferredMode_TracksCorrectly) +{ + const BRepGraph_VersionStamp aStamp = myGraph.UIDs().StampOf(BRepGraph_EdgeId(0)); + + // Mutate in deferred mode. + myGraph.Builder().BeginDeferredInvalidation(); + myGraph.Builder().MutEdge(BRepGraph_EdgeId(0))->Tolerance = 0.5; + myGraph.Builder().EndDeferredInvalidation(); + + EXPECT_TRUE(myGraph.UIDs().IsStale(aStamp)); +} + +TEST_F(BRepGraph_VersionStampTest, IsStale_InvalidStamp_ReturnsTrue) +{ + const BRepGraph_VersionStamp aStamp; + EXPECT_TRUE(myGraph.UIDs().IsStale(aStamp)); +} + +// --- Identity and version comparison --- + +TEST_F(BRepGraph_VersionStampTest, StampIdentity_SameNode_Equal) +{ + const BRepGraph_VersionStamp aStamp1 = myGraph.UIDs().StampOf(BRepGraph_FaceId(0)); + const BRepGraph_VersionStamp aStamp2 = myGraph.UIDs().StampOf(BRepGraph_FaceId(0)); + EXPECT_EQ(aStamp1, aStamp2); +} + +TEST_F(BRepGraph_VersionStampTest, StampIdentity_DifferentNodes_NotEqual) +{ + const BRepGraph_VersionStamp aStamp1 = myGraph.UIDs().StampOf(BRepGraph_FaceId(0)); + const BRepGraph_VersionStamp aStamp2 = myGraph.UIDs().StampOf(BRepGraph_FaceId(1)); + EXPECT_NE(aStamp1, aStamp2); +} + +TEST_F(BRepGraph_VersionStampTest, IsSameNode_SameVersion_ReturnsTrue) +{ + const BRepGraph_VersionStamp aStamp1 = myGraph.UIDs().StampOf(BRepGraph_FaceId(0)); + const BRepGraph_VersionStamp aStamp2 = myGraph.UIDs().StampOf(BRepGraph_FaceId(0)); + EXPECT_TRUE(aStamp1.IsSameNode(aStamp2)); +} + +TEST_F(BRepGraph_VersionStampTest, IsSameNode_DifferentVersion_StillSameNode) +{ + const BRepGraph_VersionStamp aStampBefore = myGraph.UIDs().StampOf(BRepGraph_FaceId(0)); + + myGraph.Builder().MutFace(BRepGraph_FaceId(0))->NaturalRestriction = true; + + const BRepGraph_VersionStamp aStampAfter = myGraph.UIDs().StampOf(BRepGraph_FaceId(0)); + + // Full equality fails (different MutationGen). + EXPECT_NE(aStampBefore, aStampAfter); + // But they refer to the same node (same UID). + EXPECT_TRUE(aStampBefore.IsSameNode(aStampAfter)); +} + +TEST_F(BRepGraph_VersionStampTest, StampOf_AssemblyNodes_WorksForProductsAndOccurrences) +{ + // Box graph auto-creates a root Product. + ASSERT_GT(myGraph.Topo().Products().Nb(), 0); + + const BRepGraph_VersionStamp aProdStamp = myGraph.UIDs().StampOf(BRepGraph_ProductId(0)); + EXPECT_TRUE(aProdStamp.IsValid()); + EXPECT_FALSE(myGraph.UIDs().IsStale(aProdStamp)); +} + +// --- Graph GUID tests --- + +TEST_F(BRepGraph_VersionStampTest, GraphGUID_AfterBuild_IsValid) +{ + const Standard_GUID& aGUID = myGraph.UIDs().GraphGUID(); + // A random GUID should not be all zeros. + const Standard_GUID aZero; + EXPECT_NE(aGUID, aZero); +} + +TEST_F(BRepGraph_VersionStampTest, GraphGUID_Rebuild_Changes) +{ + const Standard_GUID aGUID1 = myGraph.UIDs().GraphGUID(); + + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + myGraph.Build(aBoxMaker.Shape()); + ASSERT_TRUE(myGraph.IsDone()); + + const Standard_GUID aGUID2 = myGraph.UIDs().GraphGUID(); + // Two random GUIDs should differ (probability of collision is negligible). + EXPECT_NE(aGUID1, aGUID2); +} + +// --- ToGUID tests --- + +TEST_F(BRepGraph_VersionStampTest, ToGUID_Deterministic_SameInputSameOutput) +{ + const BRepGraph_VersionStamp aStamp = myGraph.UIDs().StampOf(BRepGraph_FaceId(0)); + const Standard_GUID& aGraph = myGraph.UIDs().GraphGUID(); + const Standard_GUID aGUID1 = aStamp.ToGUID(aGraph); + const Standard_GUID aGUID2 = aStamp.ToGUID(aGraph); + EXPECT_EQ(aGUID1, aGUID2); +} + +TEST_F(BRepGraph_VersionStampTest, ToGUID_DifferentMutationGen_DifferentGUID) +{ + const BRepGraph_VersionStamp aStampBefore = myGraph.UIDs().StampOf(BRepGraph_FaceId(0)); + const Standard_GUID& aGraph = myGraph.UIDs().GraphGUID(); + const Standard_GUID aGUIDBefore = aStampBefore.ToGUID(aGraph); + + myGraph.Builder().MutFace(BRepGraph_FaceId(0))->NaturalRestriction = true; + + const BRepGraph_VersionStamp aStampAfter = myGraph.UIDs().StampOf(BRepGraph_FaceId(0)); + const Standard_GUID aGUIDAfter = aStampAfter.ToGUID(aGraph); + + EXPECT_NE(aGUIDBefore, aGUIDAfter); +} + +TEST_F(BRepGraph_VersionStampTest, ToGUID_DifferentGraphGUID_DifferentGUID) +{ + const BRepGraph_VersionStamp aStamp = myGraph.UIDs().StampOf(BRepGraph_FaceId(0)); + + const Standard_GUID aGraphA("a1b2c3d4-e5f6-7890-abcd-ef0123456789"); + const Standard_GUID aGraphB("11223344-5566-7788-99aa-bbccddeeff00"); + + const Standard_GUID aGUIDA = aStamp.ToGUID(aGraphA); + const Standard_GUID aGUIDB = aStamp.ToGUID(aGraphB); + + EXPECT_NE(aGUIDA, aGUIDB); +} + +TEST_F(BRepGraph_VersionStampTest, ToGUID_DifferentNodes_DifferentGUID) +{ + const Standard_GUID& aGraph = myGraph.UIDs().GraphGUID(); + + const BRepGraph_VersionStamp aStamp0 = myGraph.UIDs().StampOf(BRepGraph_FaceId(0)); + const BRepGraph_VersionStamp aStamp1 = myGraph.UIDs().StampOf(BRepGraph_FaceId(1)); + + EXPECT_NE(aStamp0.ToGUID(aGraph), aStamp1.ToGUID(aGraph)); +} diff --git a/src/ModelingData/TKBRep/GTests/BRepGraph_Views_Test.cxx b/src/ModelingData/TKBRep/GTests/BRepGraph_Views_Test.cxx new file mode 100644 index 0000000000..fb9ede4f11 --- /dev/null +++ b/src/ModelingData/TKBRep/GTests/BRepGraph_Views_Test.cxx @@ -0,0 +1,683 @@ +// Copyright (c) 2026 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 + +namespace +{ +//! Concrete subclass for testing cache values. +class TestCacheValue : public BRepGraph_CacheValue +{ +public: + DEFINE_STANDARD_RTTI_INLINE(TestCacheValue, BRepGraph_CacheValue) + TestCacheValue() = default; +}; + +const occ::handle& testUserAttrKind() +{ + static const occ::handle THE_KIND = + new BRepGraph_CacheKind(Standard_GUID("2f9b6a5c-1f2d-4a88-9c1c-7a0c16a10020"), "ViewsTestAttr"); + return THE_KIND; +} + +template +static int countActiveRefs(const NCollection_Vector& theRefIds, + const theRefFn& theRefAccess) +{ + int aCount = 0; + for (const theRefIdType& aRefId : theRefIds) + { + if (!theRefAccess(aRefId).IsRemoved) + { + ++aCount; + } + } + return aCount; +} + +static int countActiveNodes(const BRepGraph& theGraph, + const BRepGraph_NodeId::Kind theKind, + const int theUpperBound) +{ + int aCount = 0; + for (int anIdx = 0; anIdx < theUpperBound; ++anIdx) + { + if (!theGraph.Topo().Gen().IsRemoved(BRepGraph_NodeId(theKind, anIdx))) + { + ++aCount; + } + } + return aCount; +} +} // namespace + +class BRepGraph_ViewsTest : public testing::Test +{ +protected: + void SetUp() override + { + BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); + const TopoDS_Shape& aBox = aBoxMaker.Shape(); + myGraph.Build(aBox); + } + + BRepGraph myGraph; +}; + +// ---------- DefsView ---------- + +TEST_F(BRepGraph_ViewsTest, DefsView_NbFaces) +{ + EXPECT_EQ(myGraph.Topo().Faces().Nb(), 6); +} + +TEST_F(BRepGraph_ViewsTest, DefsView_NbSolids) +{ + EXPECT_EQ(myGraph.Topo().Solids().Nb(), 1); +} + +TEST_F(BRepGraph_ViewsTest, DefsView_NbShells) +{ + EXPECT_EQ(myGraph.Topo().Shells().Nb(), 1); +} + +TEST_F(BRepGraph_ViewsTest, DefsView_NbWires) +{ + EXPECT_EQ(myGraph.Topo().Wires().Nb(), 6); +} + +TEST_F(BRepGraph_ViewsTest, DefsView_NbEdges) +{ + EXPECT_EQ(myGraph.Topo().Edges().Nb(), 12); +} + +TEST_F(BRepGraph_ViewsTest, DefsView_NbVertices) +{ + EXPECT_EQ(myGraph.Topo().Vertices().Nb(), 8); +} + +TEST_F(BRepGraph_ViewsTest, DefsView_ActiveCounts_MatchStorageState) +{ + EXPECT_EQ( + countActiveNodes(myGraph, BRepGraph_NodeId::Kind::Vertex, myGraph.Topo().Vertices().Nb()), + myGraph.Topo().Vertices().NbActive()); + EXPECT_EQ(countActiveNodes(myGraph, BRepGraph_NodeId::Kind::Edge, myGraph.Topo().Edges().Nb()), + myGraph.Topo().Edges().NbActive()); + EXPECT_EQ( + countActiveNodes(myGraph, BRepGraph_NodeId::Kind::CoEdge, myGraph.Topo().CoEdges().Nb()), + myGraph.Topo().CoEdges().NbActive()); + EXPECT_EQ(countActiveNodes(myGraph, BRepGraph_NodeId::Kind::Wire, myGraph.Topo().Wires().Nb()), + myGraph.Topo().Wires().NbActive()); + EXPECT_EQ(countActiveNodes(myGraph, BRepGraph_NodeId::Kind::Face, myGraph.Topo().Faces().Nb()), + myGraph.Topo().Faces().NbActive()); + EXPECT_EQ(countActiveNodes(myGraph, BRepGraph_NodeId::Kind::Shell, myGraph.Topo().Shells().Nb()), + myGraph.Topo().Shells().NbActive()); + EXPECT_EQ(countActiveNodes(myGraph, BRepGraph_NodeId::Kind::Solid, myGraph.Topo().Solids().Nb()), + myGraph.Topo().Solids().NbActive()); + EXPECT_EQ( + countActiveNodes(myGraph, BRepGraph_NodeId::Kind::Compound, myGraph.Topo().Compounds().Nb()), + myGraph.Topo().Compounds().NbActive()); + EXPECT_EQ( + countActiveNodes(myGraph, BRepGraph_NodeId::Kind::CompSolid, myGraph.Topo().CompSolids().Nb()), + myGraph.Topo().CompSolids().NbActive()); + EXPECT_EQ( + countActiveNodes(myGraph, BRepGraph_NodeId::Kind::Product, myGraph.Topo().Products().Nb()), + myGraph.Topo().Products().NbActive()); + EXPECT_EQ(countActiveNodes(myGraph, + BRepGraph_NodeId::Kind::Occurrence, + myGraph.Topo().Occurrences().Nb()), + myGraph.Topo().Occurrences().NbActive()); +} + +TEST_F(BRepGraph_ViewsTest, DefsView_NbActiveFaces_ExcludeRemoved) +{ + const int aFacesBefore = myGraph.Topo().Faces().NbActive(); + myGraph.Builder().RemoveNode(BRepGraph_FaceId(0)); + + EXPECT_EQ(myGraph.Topo().Faces().NbActive(), aFacesBefore - 1); + EXPECT_TRUE(myGraph.Topo().Gen().IsRemoved(BRepGraph_FaceId(0))); +} + +TEST_F(BRepGraph_ViewsTest, DefsView_FaceAccessor_Valid) +{ + for (BRepGraph_FaceIterator aFaceIt(myGraph); aFaceIt.More(); aFaceIt.Next()) + { + EXPECT_TRUE(aFaceIt.Current().Id.IsValid()) + << "Face " << aFaceIt.CurrentId().Index << " has invalid Id"; + } +} + +TEST_F(BRepGraph_ViewsTest, DefsView_TopoEntity_Valid) +{ + BRepGraph_FaceId aFaceId(0); + const BRepGraphInc::BaseDef* aBase = myGraph.Topo().Gen().TopoEntity(aFaceId); + ASSERT_NE(aBase, nullptr); + EXPECT_EQ(aBase->Id, myGraph.Topo().Faces().Definition(BRepGraph_FaceId(0)).Id); +} + +TEST_F(BRepGraph_ViewsTest, DefsView_NbNodes_Positive) +{ + EXPECT_GT(myGraph.Topo().Gen().NbNodes(), 0u); +} + +// ---------- DefsView Geometry ---------- + +TEST_F(BRepGraph_ViewsTest, DefsView_FaceSurface_NonNull) +{ + for (BRepGraph_FaceIterator aFaceIt(myGraph); aFaceIt.More(); aFaceIt.Next()) + { + EXPECT_TRUE(BRepGraph_Tool::Face::HasSurface(myGraph, aFaceIt.CurrentId())) + << "Face " << aFaceIt.CurrentId().Index << " has no surface representation"; + } +} + +TEST_F(BRepGraph_ViewsTest, DefsView_EdgeCurve3d_NonNull) +{ + for (BRepGraph_EdgeIterator anEdgeIt(myGraph); anEdgeIt.More(); anEdgeIt.Next()) + { + EXPECT_TRUE(BRepGraph_Tool::Edge::HasCurve(myGraph, anEdgeIt.CurrentId())) + << "Edge " << anEdgeIt.CurrentId().Index << " has no Curve3D representation"; + } +} + +TEST_F(BRepGraph_ViewsTest, DefsView_FindPCurve_NoCrash) +{ + // FindPCurve may or may not return a non-null pointer for an arbitrary edge/face pair. + // Just verify it does not crash. + (void)BRepGraph_Tool::Edge::FindPCurve(myGraph, BRepGraph_EdgeId(0), BRepGraph_FaceId(0)); +} + +TEST_F(BRepGraph_ViewsTest, DefsView_RepIdConvenienceAccessors_RoundTrip) +{ + const BRepGraph_FaceId aFaceId(0); + const BRepGraph_EdgeId anEdgeId(0); + + EXPECT_EQ(myGraph.Topo().Faces().SurfaceRepId(aFaceId), + myGraph.Topo().Faces().Definition(aFaceId).SurfaceRepId); + EXPECT_EQ(myGraph.Topo().Faces().ActiveTriangulationRepId(aFaceId), + myGraph.Topo().Faces().Definition(aFaceId).ActiveTriangulationRepId()); + EXPECT_EQ(myGraph.Topo().Edges().Curve3DRepId(anEdgeId), + myGraph.Topo().Edges().Definition(anEdgeId).Curve3DRepId); + + const NCollection_Vector& aCoEdges = myGraph.Topo().Edges().CoEdges(anEdgeId); + ASSERT_GT(aCoEdges.Length(), 0); + const BRepGraph_CoEdgeId aCoEdgeId = aCoEdges.Value(0); + EXPECT_EQ(myGraph.Topo().CoEdges().Curve2DRepId(aCoEdgeId), + myGraph.Topo().CoEdges().Definition(aCoEdgeId).Curve2DRepId); +} + +TEST_F(BRepGraph_ViewsTest, DefsView_RepIdConvenienceAccessors_InvalidInput) +{ + const BRepGraph_FaceId aFaceOut(myGraph.Topo().Faces().Nb()); + const BRepGraph_EdgeId anEdgeOut(myGraph.Topo().Edges().Nb()); + const BRepGraph_CoEdgeId aCoEdgeOut(myGraph.Topo().CoEdges().Nb()); + + EXPECT_FALSE(myGraph.Topo().Faces().SurfaceRepId(aFaceOut).IsValid()); + EXPECT_FALSE(myGraph.Topo().Faces().ActiveTriangulationRepId(aFaceOut).IsValid()); + EXPECT_FALSE(myGraph.Topo().Edges().Curve3DRepId(anEdgeOut).IsValid()); + EXPECT_FALSE(myGraph.Topo().CoEdges().Curve2DRepId(aCoEdgeOut).IsValid()); +} + +// ---------- UIDsView ---------- + +TEST_F(BRepGraph_ViewsTest, UIDsView_Of_Valid) +{ + BRepGraph_FaceId aFaceId(0); + const BRepGraph_UID& aUID = myGraph.UIDs().Of(aFaceId); + EXPECT_TRUE(aUID.IsValid()); +} + +TEST_F(BRepGraph_ViewsTest, UIDsView_Generation_Positive) +{ + EXPECT_GT(myGraph.UIDs().Generation(), 0u); +} + +TEST_F(BRepGraph_ViewsTest, UIDsView_NodeIdFrom_RoundTrip) +{ + const BRepGraph_FaceId aFaceId(0); + const BRepGraph_UID aUID = myGraph.UIDs().Of(aFaceId); + ASSERT_TRUE(aUID.IsValid()); + EXPECT_EQ(myGraph.UIDs().NodeIdFrom(aUID), BRepGraph_NodeId(aFaceId)); +} + +TEST_F(BRepGraph_ViewsTest, UIDsView_NodeIdFrom_MultipleRoundTrip) +{ + NCollection_Vector aUIDs; + const BRepGraph_UID aFaceUID = myGraph.UIDs().Of(BRepGraph_FaceId(0)); + const BRepGraph_UID anEdgeUID = myGraph.UIDs().Of(BRepGraph_EdgeId(0)); + ASSERT_TRUE(aFaceUID.IsValid()); + ASSERT_TRUE(anEdgeUID.IsValid()); + + aUIDs.Append(aFaceUID); + aUIDs.Append(anEdgeUID); + + ASSERT_EQ(aUIDs.Length(), 2); + EXPECT_EQ(myGraph.UIDs().NodeIdFrom(aUIDs.Value(0)), BRepGraph_NodeId(BRepGraph_FaceId(0))); + EXPECT_EQ(myGraph.UIDs().NodeIdFrom(aUIDs.Value(1)), BRepGraph_NodeId(BRepGraph_EdgeId(0))); +} + +TEST_F(BRepGraph_ViewsTest, UIDsView_NodeIdFrom_InvalidAndWrongGeneration) +{ + NCollection_Vector aUIDs; + aUIDs.Append(BRepGraph_UID()); + aUIDs.Append(BRepGraph_UID(BRepGraph_NodeId::Kind::Face, 1, myGraph.UIDs().Generation() + 1)); + + ASSERT_EQ(aUIDs.Length(), 2); + EXPECT_FALSE(myGraph.UIDs().NodeIdFrom(aUIDs.Value(0)).IsValid()); + EXPECT_FALSE(myGraph.UIDs().NodeIdFrom(aUIDs.Value(1)).IsValid()); +} + +// ---------- Topology adjacency ---------- + +TEST_F(BRepGraph_ViewsTest, SpatialView_AdjacentFaces_FourPerBoxFace) +{ + BRepGraph_FaceId aFaceId(0); + NCollection_Vector aResult = + myGraph.Topo().Faces().Adjacent(aFaceId, myGraph.Allocator()); + EXPECT_EQ(aResult.Length(), 4); +} + +TEST_F(BRepGraph_ViewsTest, SpatialView_FacesOfEdge_TwoPerBoxEdge) +{ + BRepGraph_EdgeId anEdgeId(0); + const NCollection_Vector& aResult = myGraph.Topo().Edges().Faces(anEdgeId); + EXPECT_EQ(aResult.Length(), 2); +} + +TEST_F(BRepGraph_ViewsTest, SpatialView_OutParam_Parity) +{ + const BRepGraph_FaceId aFaceId(0); + const BRepGraph_EdgeId anEdgeId(0); + const BRepGraph_VertexId aVertexId(0); + const occ::handle& anAllocator = myGraph.Allocator(); + + const NCollection_Vector aAdjacentByValue = + myGraph.Topo().Faces().Adjacent(aFaceId, anAllocator); + EXPECT_EQ(aAdjacentByValue.Length(), 4); + + const NCollection_Vector anAdjEdgesByValue = + myGraph.Topo().Edges().Adjacent(anEdgeId, anAllocator); + EXPECT_GE(anAdjEdgesByValue.Length(), 4); +} + +TEST_F(BRepGraph_ViewsTest, TopoView_GroupedFaceOps_Parity) +{ + const BRepGraph_FaceId aFaceId(0); + const occ::handle& anAllocator = myGraph.Allocator(); + + EXPECT_EQ(myGraph.Topo().Faces().Definition(aFaceId).Id, BRepGraph_NodeId(aFaceId)); + EXPECT_EQ(myGraph.Topo().Faces().SurfaceRepId(aFaceId), + myGraph.Topo().Faces().Definition(aFaceId).SurfaceRepId); + EXPECT_EQ(myGraph.Topo().Faces().ActiveTriangulationRepId(aFaceId), + myGraph.Topo().Faces().Definition(aFaceId).ActiveTriangulationRepId()); + EXPECT_EQ(myGraph.Topo().Faces().OuterWire(aFaceId), BRepGraph_WireId(0)); + + EXPECT_EQ(myGraph.Topo().Faces().Adjacent(aFaceId, anAllocator).Length(), 4); +} + +TEST_F(BRepGraph_ViewsTest, TopoView_GroupedEdgeAndVertexOps_Parity) +{ + const BRepGraph_EdgeId anEdgeId(0); + const BRepGraph_VertexId aVertexId(0); + const occ::handle& anAllocator = myGraph.Allocator(); + + EXPECT_EQ(myGraph.Topo().Edges().Definition(anEdgeId).Id, BRepGraph_NodeId(anEdgeId)); + EXPECT_EQ(myGraph.Topo().Edges().NbFaces(anEdgeId), 2); + EXPECT_EQ(myGraph.Topo().Edges().Curve3DRepId(anEdgeId), + myGraph.Topo().Edges().Definition(anEdgeId).Curve3DRepId); + EXPECT_FALSE(myGraph.Topo().Edges().IsBoundary(anEdgeId)); + EXPECT_TRUE(myGraph.Topo().Edges().IsManifold(anEdgeId)); + EXPECT_GE(myGraph.Topo().Edges().Wires(anEdgeId).Length(), 1); + EXPECT_GE(myGraph.Topo().Edges().CoEdges(anEdgeId).Length(), 1); + EXPECT_EQ(myGraph.Topo().Edges().Faces(anEdgeId).Length(), 2); + EXPECT_GE(myGraph.Topo().Edges().Adjacent(anEdgeId, anAllocator).Length(), 4); + + EXPECT_EQ(myGraph.Topo().Vertices().Definition(aVertexId).Id, BRepGraph_NodeId(aVertexId)); + EXPECT_GE(myGraph.Topo().Vertices().Edges(aVertexId).Length(), 1); +} + +TEST_F(BRepGraph_ViewsTest, TopoView_GroupedCoEdgeOps_Parity) +{ + const BRepGraph_CoEdgeId aCoEdgeId(0); + const BRepGraphInc::CoEdgeDef& aCoEdge = myGraph.Topo().CoEdges().Definition(aCoEdgeId); + + EXPECT_EQ(myGraph.Topo().CoEdges().Definition(aCoEdgeId).Id, aCoEdge.Id); + EXPECT_EQ(myGraph.Topo().CoEdges().Definition(aCoEdgeId).EdgeDefId, aCoEdge.EdgeDefId); + EXPECT_EQ(myGraph.Topo().CoEdges().Definition(aCoEdgeId).FaceDefId, aCoEdge.FaceDefId); + EXPECT_EQ(myGraph.Topo().CoEdges().Curve2DRepId(aCoEdgeId), + myGraph.Topo().CoEdges().Definition(aCoEdgeId).Curve2DRepId); + EXPECT_EQ(myGraph.Topo().CoEdges().Definition(aCoEdgeId).SeamPairId, aCoEdge.SeamPairId); +} + +TEST_F(BRepGraph_ViewsTest, TopoView_GroupedProductAndOccurrenceOps_Parity) +{ + const BRepGraph_ProductId aPartProduct = + myGraph.Builder().AddProduct(BRepGraph_NodeId(BRepGraph_SolidId(0))); + const BRepGraph_ProductId aSubAssembly = myGraph.Builder().AddAssemblyProduct(); + const BRepGraph_ProductId aRootAssembly = myGraph.Builder().AddAssemblyProduct(); + ASSERT_TRUE(aPartProduct.IsValid()); + ASSERT_TRUE(aSubAssembly.IsValid()); + ASSERT_TRUE(aRootAssembly.IsValid()); + + const BRepGraph_OccurrenceId aSubOccurrence = + myGraph.Builder().AddOccurrence(aRootAssembly, aSubAssembly, TopLoc_Location()); + const BRepGraph_OccurrenceId aPartOccurrence = + myGraph.Builder().AddOccurrence(aSubAssembly, aPartProduct, TopLoc_Location(), aSubOccurrence); + ASSERT_TRUE(aSubOccurrence.IsValid()); + ASSERT_TRUE(aPartOccurrence.IsValid()); + + EXPECT_EQ(myGraph.Topo().Products().Definition(aPartProduct).Id, BRepGraph_NodeId(aPartProduct)); + EXPECT_EQ(myGraph.Topo().Products().Definition(aPartProduct).ShapeRootId, + BRepGraph_NodeId(BRepGraph_SolidId(0))); + EXPECT_FALSE(myGraph.Topo().Products().Definition(aRootAssembly).ShapeRootId.IsValid()); + EXPECT_EQ(myGraph.Refs().Occurrences().IdsOf(aRootAssembly).Length(), 1); + EXPECT_EQ(myGraph.Refs().Occurrences().IdsOf(aSubAssembly).Length(), 1); + + EXPECT_EQ(myGraph.Topo().Occurrences().Definition(aPartOccurrence).Id, + BRepGraph_NodeId(aPartOccurrence)); + EXPECT_EQ(myGraph.Topo().Occurrences().Definition(aPartOccurrence).ProductDefId, aPartProduct); + EXPECT_EQ(myGraph.Topo().Occurrences().Definition(aPartOccurrence).ParentProductDefId, + aSubAssembly); + EXPECT_EQ(myGraph.Topo().Occurrences().Definition(aPartOccurrence).ParentOccurrenceDefId, + aSubOccurrence); + + const NCollection_Vector& aOccurrenceRefs = + myGraph.Refs().Occurrences().IdsOf(aSubAssembly); + ASSERT_EQ(aOccurrenceRefs.Length(), 1); + { + BRepGraph_MutGuard anOccurrenceRef = + myGraph.Builder().MutOccurrenceRef(aOccurrenceRefs.Value(0)); + anOccurrenceRef->IsRemoved = true; + } + + EXPECT_EQ(myGraph.Topo().Products().NbComponents(aSubAssembly), 0); +} + +TEST_F(BRepGraph_ViewsTest, SpatialView_OutParam_ClearAndInvalid) +{ + const occ::handle& anAllocator = myGraph.Allocator(); + + const NCollection_Vector aFaceResult = + myGraph.Topo().Faces().Adjacent(BRepGraph_FaceId(0), anAllocator); + EXPECT_EQ(aFaceResult.Length(), 4); + EXPECT_EQ(myGraph.Topo().Faces().Adjacent(BRepGraph_FaceId(999), anAllocator).Length(), 0); + + const NCollection_Vector anAdjEdgeResult = + myGraph.Topo().Edges().Adjacent(BRepGraph_EdgeId(0), anAllocator); + EXPECT_GE(anAdjEdgeResult.Length(), 4); + EXPECT_EQ(myGraph.Topo().Edges().Adjacent(BRepGraph_EdgeId(999), anAllocator).Length(), 0); +} + +// ---------- CacheView ---------- + +TEST_F(BRepGraph_ViewsTest, AttrsView_SetGet_RoundTrip) +{ + BRepGraph_FaceId aFaceId(0); + occ::handle anAttr = new TestCacheValue(); + myGraph.Cache().Set(aFaceId, testUserAttrKind(), anAttr); + occ::handle aRetrieved = myGraph.Cache().Get(aFaceId, testUserAttrKind()); + EXPECT_EQ(aRetrieved, anAttr); + EXPECT_TRUE(myGraph.Cache().Has(aFaceId, testUserAttrKind())); +} + +TEST_F(BRepGraph_ViewsTest, AttrsView_Remove_Works) +{ + BRepGraph_FaceId aFaceId(0); + occ::handle anAttr = new TestCacheValue(); + myGraph.Cache().Set(aFaceId, testUserAttrKind(), anAttr); + EXPECT_TRUE(myGraph.Cache().Remove(aFaceId, testUserAttrKind())); + EXPECT_FALSE(myGraph.Cache().Has(aFaceId, testUserAttrKind())); + EXPECT_TRUE(myGraph.Cache().Get(aFaceId, testUserAttrKind()).IsNull()); +} + +TEST_F(BRepGraph_ViewsTest, AttrsView_CacheKinds_ReportsStoredKind) +{ + BRepGraph_FaceId aFaceId(0); + occ::handle anAttr = new TestCacheValue(); + myGraph.Cache().Set(aFaceId, testUserAttrKind(), anAttr); + + const NCollection_Vector> aKinds = + myGraph.Cache().CacheKinds(aFaceId); + + ASSERT_EQ(aKinds.Length(), 1); + ASSERT_FALSE(aKinds.Value(0).IsNull()); + EXPECT_EQ(aKinds.Value(0)->ID(), testUserAttrKind()->ID()); + EXPECT_EQ(myGraph.Cache().Get(aFaceId, testUserAttrKind()), anAttr); +} + +TEST_F(BRepGraph_ViewsTest, AttrsView_MutFace_InvalidatesEntry) +{ + BRepGraph_FaceId aFaceId(0); + occ::handle anAttr = new TestCacheValue(); + myGraph.Cache().Set(aFaceId, testUserAttrKind(), anAttr); + ASSERT_TRUE(myGraph.Cache().Has(aFaceId, testUserAttrKind())); + + { + BRepGraph_MutGuard aFace = + myGraph.Builder().MutFace(BRepGraph_FaceId(0)); + aFace->Tolerance += 0.1; + } + + EXPECT_FALSE(myGraph.Cache().Has(aFaceId, testUserAttrKind())); + EXPECT_TRUE(myGraph.Cache().Get(aFaceId, testUserAttrKind()).IsNull()); +} + +TEST_F(BRepGraph_ViewsTest, AttrsView_RemoveNode_InvalidatesEntry) +{ + BRepGraph_FaceId aFaceId(0); + occ::handle anAttr = new TestCacheValue(); + myGraph.Cache().Set(aFaceId, testUserAttrKind(), anAttr); + ASSERT_TRUE(myGraph.Cache().Has(aFaceId, testUserAttrKind())); + + myGraph.Builder().RemoveNode(aFaceId); + + EXPECT_TRUE(myGraph.Topo().Gen().IsRemoved(aFaceId)); + EXPECT_FALSE(myGraph.Cache().Has(aFaceId, testUserAttrKind())); +} + +TEST_F(BRepGraph_ViewsTest, RefsView_ActiveCounts_MatchFreshBuild) +{ + EXPECT_EQ(myGraph.Refs().Shells().NbActive(), myGraph.Refs().Shells().Nb()); + EXPECT_EQ(myGraph.Refs().Faces().NbActive(), myGraph.Refs().Faces().Nb()); + EXPECT_EQ(myGraph.Refs().Wires().NbActive(), myGraph.Refs().Wires().Nb()); + EXPECT_EQ(myGraph.Refs().CoEdges().NbActive(), myGraph.Refs().CoEdges().Nb()); + EXPECT_EQ(myGraph.Refs().Vertices().NbActive(), myGraph.Refs().Vertices().Nb()); + EXPECT_EQ(myGraph.Refs().Solids().NbActive(), myGraph.Refs().Solids().Nb()); + EXPECT_EQ(myGraph.Refs().Children().NbActive(), myGraph.Refs().Children().Nb()); + EXPECT_EQ(myGraph.Refs().Occurrences().NbActive(), myGraph.Refs().Occurrences().Nb()); +} + +TEST_F(BRepGraph_ViewsTest, RefsView_RefIdsOf_MatchFreshBuild) +{ + const BRepGraph_ShellId aShellId(0); + const BRepGraph_FaceId aFaceId(0); + const BRepGraph_WireId aWireId(0); + const BRepGraph_SolidId aSolidId(0); + + EXPECT_EQ( + countActiveRefs(myGraph.Refs().Faces().IdsOf(aShellId), + [this](const BRepGraph_FaceRefId theRefId) -> const BRepGraphInc::FaceRef& { + return myGraph.Refs().Faces().Entry(theRefId); + }), + myGraph.Refs().Faces().IdsOf(aShellId).Length()); + EXPECT_EQ( + countActiveRefs(myGraph.Refs().Wires().IdsOf(aFaceId), + [this](const BRepGraph_WireRefId theRefId) -> const BRepGraphInc::WireRef& { + return myGraph.Refs().Wires().Entry(theRefId); + }), + myGraph.Refs().Wires().IdsOf(aFaceId).Length()); + EXPECT_EQ( + countActiveRefs(myGraph.Refs().CoEdges().IdsOf(aWireId), + [this](const BRepGraph_CoEdgeRefId theRefId) -> const BRepGraphInc::CoEdgeRef& { + return myGraph.Refs().CoEdges().Entry(theRefId); + }), + myGraph.Refs().CoEdges().IdsOf(aWireId).Length()); + EXPECT_EQ( + countActiveRefs(myGraph.Refs().Shells().IdsOf(aSolidId), + [this](const BRepGraph_ShellRefId theRefId) -> const BRepGraphInc::ShellRef& { + return myGraph.Refs().Shells().Entry(theRefId); + }), + myGraph.Refs().Shells().IdsOf(aSolidId).Length()); +} + +TEST_F(BRepGraph_ViewsTest, RefsView_FaceRefIdsOf_LocalFilteringHandlesRemoved) +{ + const BRepGraph_ShellId aShellId(0); + const NCollection_Vector& aFaceRefs = myGraph.Refs().Faces().IdsOf(aShellId); + ASSERT_GT(aFaceRefs.Length(), 0); + + { + BRepGraph_MutGuard aFaceRef = + myGraph.Builder().MutFaceRef(aFaceRefs.Value(0)); + aFaceRef->IsRemoved = true; + } + + EXPECT_EQ( + countActiveRefs(myGraph.Refs().Faces().IdsOf(aShellId), + [this](const BRepGraph_FaceRefId theRefId) -> const BRepGraphInc::FaceRef& { + return myGraph.Refs().Faces().Entry(theRefId); + }), + aFaceRefs.Length() - 1); +} + +TEST_F(BRepGraph_ViewsTest, RefsView_VertexRefIdsOfEdge_ContainsBoundaryVertices) +{ + const occ::handle& anAllocator = myGraph.Allocator(); + const NCollection_Vector aVertexRefs = + myGraph.Refs().Vertices().IdsOf(BRepGraph_EdgeId(0), anAllocator); + + EXPECT_GE(aVertexRefs.Length(), 2); + for (const BRepGraph_VertexRefId& aVertexRefId : aVertexRefs) + { + const BRepGraphInc::VertexRef& aRef = myGraph.Refs().Vertices().Entry(aVertexRefId); + EXPECT_FALSE(aRef.IsRemoved); + EXPECT_TRUE(aRef.VertexDefId.IsValid(myGraph.Topo().Vertices().Nb())); + } +} + +// ---------- ShapesView ---------- + +TEST_F(BRepGraph_ViewsTest, ShapesView_Shape_NonNull) +{ + BRepGraph_FaceId aFaceId(0); + EXPECT_FALSE(myGraph.Shapes().Shape(aFaceId).IsNull()); +} + +TEST_F(BRepGraph_ViewsTest, ShapesView_HasOriginal_True) +{ + BRepGraph_FaceId aFaceId(0); + EXPECT_TRUE(myGraph.Shapes().HasOriginal(aFaceId)); +} + +// ---------- MutView ---------- + +TEST_F(BRepGraph_ViewsTest, MutView_EdgeDef_IncrementsOwnGen) +{ + { + BRepGraph_MutGuard anEdge = + myGraph.Builder().MutEdge(BRepGraph_EdgeId(0)); + } + EXPECT_GT(myGraph.Topo().Edges().Definition(BRepGraph_EdgeId(0)).OwnGen, 0u); +} + +// ---------- BuilderView ---------- + +TEST_F(BRepGraph_ViewsTest, BuilderView_AddVertex_Works) +{ + const int aNbBefore = myGraph.Topo().Vertices().Nb(); + BRepGraph_VertexId aVtx = myGraph.Builder().AddVertex(gp_Pnt(1, 2, 3), 0.001); + EXPECT_TRUE(aVtx.IsValid()); + EXPECT_EQ(myGraph.Topo().Vertices().Nb(), aNbBefore + 1); +} + +TEST_F(BRepGraph_ViewsTest, BuilderView_IsRemoved_False) +{ + BRepGraph_FaceId aFaceId(0); + EXPECT_FALSE(myGraph.Topo().Gen().IsRemoved(aFaceId)); +} + +TEST_F(BRepGraph_ViewsTest, BuilderView_RemoveRep_Surface_HidesSurfaceQueries) +{ + const BRepGraph_FaceId aFaceId(0); + const BRepGraph_SurfaceRepId aSurfaceRepId = myGraph.Topo().Faces().SurfaceRepId(aFaceId); + ASSERT_TRUE(aSurfaceRepId.IsValid()); + ASSERT_TRUE(BRepGraph_Tool::Face::HasSurface(myGraph, aFaceId)); + + myGraph.Builder().RemoveRep(aSurfaceRepId); + + EXPECT_TRUE(myGraph.Topo().Geometry().SurfaceRep(aSurfaceRepId).IsRemoved); + EXPECT_FALSE(myGraph.Topo().Faces().SurfaceRepId(aFaceId).IsValid()); + EXPECT_FALSE(BRepGraph_Tool::Face::HasSurface(myGraph, aFaceId)); + EXPECT_TRUE(BRepGraph_Tool::Face::Surface(myGraph, aFaceId).IsNull()); +} + +TEST_F(BRepGraph_ViewsTest, BuilderView_RemoveRep_CurveAndPCurve_HideCurveQueries) +{ + const BRepGraph_EdgeId anEdgeId(0); + const BRepGraph_Curve3DRepId aCurve3DRepId = myGraph.Topo().Edges().Curve3DRepId(anEdgeId); + ASSERT_TRUE(aCurve3DRepId.IsValid()); + ASSERT_TRUE(BRepGraph_Tool::Edge::HasCurve(myGraph, anEdgeId)); + + myGraph.Builder().RemoveRep(aCurve3DRepId); + + EXPECT_TRUE(myGraph.Topo().Geometry().Curve3DRep(aCurve3DRepId).IsRemoved); + EXPECT_FALSE(myGraph.Topo().Edges().Curve3DRepId(anEdgeId).IsValid()); + EXPECT_FALSE(BRepGraph_Tool::Edge::HasCurve(myGraph, anEdgeId)); + EXPECT_TRUE(BRepGraph_Tool::Edge::Curve(myGraph, anEdgeId).IsNull()); + + const NCollection_Vector& aCoEdges = myGraph.Topo().Edges().CoEdges(anEdgeId); + ASSERT_GT(aCoEdges.Length(), 0); + const BRepGraph_CoEdgeId aCoEdgeId = aCoEdges.Value(0); + const BRepGraph_Curve2DRepId aCurve2DRepId = myGraph.Topo().CoEdges().Curve2DRepId(aCoEdgeId); + ASSERT_TRUE(aCurve2DRepId.IsValid()); + ASSERT_TRUE(BRepGraph_Tool::CoEdge::HasPCurve(myGraph, aCoEdgeId)); + + myGraph.Builder().RemoveRep(aCurve2DRepId); + + EXPECT_TRUE(myGraph.Topo().Geometry().Curve2DRep(aCurve2DRepId).IsRemoved); + EXPECT_FALSE(myGraph.Topo().CoEdges().Curve2DRepId(aCoEdgeId).IsValid()); + EXPECT_FALSE(BRepGraph_Tool::CoEdge::HasPCurve(myGraph, aCoEdgeId)); + EXPECT_TRUE(BRepGraph_Tool::CoEdge::PCurve(myGraph, aCoEdgeId).IsNull()); +} + +// ---------- History() accessor ---------- + +TEST_F(BRepGraph_ViewsTest, History_ConstAccessor) +{ + const BRepGraph& aConstGraph = myGraph; + EXPECT_TRUE(aConstGraph.History().IsEnabled()); +} + +TEST_F(BRepGraph_ViewsTest, History_MutableAccessor) +{ + myGraph.History().SetEnabled(false); + EXPECT_FALSE(myGraph.History().IsEnabled()); + myGraph.History().SetEnabled(true); + EXPECT_TRUE(myGraph.History().IsEnabled()); +} diff --git a/src/ModelingData/TKBRep/GTests/FILES.cmake b/src/ModelingData/TKBRep/GTests/FILES.cmake index 21626778fd..4ab52d1905 100644 --- a/src/ModelingData/TKBRep/GTests/FILES.cmake +++ b/src/ModelingData/TKBRep/GTests/FILES.cmake @@ -4,6 +4,36 @@ set(OCCT_TKBRep_GTests_FILES_LOCATION "${CMAKE_CURRENT_LIST_DIR}") set(OCCT_TKBRep_GTests_FILES BRep_Tool_Test.cxx BRepAdaptor_CompCurve_Test.cxx + BRepGraph_CacheKindRegistry_Test.cxx + BRepGraph_Assembly_Test.cxx + BRepGraph_DefsIterator_Test.cxx + BRepGraphInc_Test.cxx + BRepGraph_Builder_Test.cxx + BRepGraph_NodeId_Test.cxx + BRepGraph_RefId_Test.cxx + BRepGraph_RefsIterator_Test.cxx + BRepGraph_Benchmark_Test.cxx + BRepGraph_Build_Test.cxx + BRepGraph_DeferredInvalidation_Test.cxx + BRepGraph_MutationGen_Test.cxx + BRepGraph_Convenience_Test.cxx + BRepGraph_EdgeCases_Test.cxx + BRepGraph_ChildExplorer_Test.cxx + BRepGraph_ParentExplorer_Test.cxx + BRepGraph_EventBus_Test.cxx + BRepGraph_Geometry_Test.cxx + BRepGraph_History_Test.cxx + BRepGraph_Polygon_Test.cxx + BRepGraph_Reconstruct_Test.cxx + BRepGraph_Sharing_Test.cxx + BRepGraph_Test.cxx + BRepGraph_VersionStamp_Test.cxx + BRepGraph_Views_Test.cxx + BRepGraph_Compact_Test.cxx + BRepGraph_Copy_Test.cxx + BRepGraph_Transform_Test.cxx + BRepGraph_Validate_Test.cxx + BRepGraph_Deduplicate_Test.cxx TopExp_Test.cxx TopoDS_Builder_Test.cxx TopoDS_Edge_Test.cxx diff --git a/src/ModelingData/TKBRep/PACKAGES.cmake b/src/ModelingData/TKBRep/PACKAGES.cmake index 8189ac33ed..c666e7d3be 100644 --- a/src/ModelingData/TKBRep/PACKAGES.cmake +++ b/src/ModelingData/TKBRep/PACKAGES.cmake @@ -8,4 +8,6 @@ set(OCCT_TKBRep_LIST_OF_PACKAGES BRepAdaptor BRepTools BinTools + BRepGraph + BRepGraphInc ) diff --git a/src/ModelingData/TKBRep/TopExp/TopExp_Explorer.hxx b/src/ModelingData/TKBRep/TopExp/TopExp_Explorer.hxx index d153473363..ae73a5cf03 100644 --- a/src/ModelingData/TKBRep/TopExp/TopExp_Explorer.hxx +++ b/src/ModelingData/TKBRep/TopExp/TopExp_Explorer.hxx @@ -17,6 +17,7 @@ #ifndef _TopExp_Explorer_HeaderFile #define _TopExp_Explorer_HeaderFile +#include #include #include #include @@ -138,6 +139,16 @@ public: //! Destructor. Standard_EXPORT ~TopExp_Explorer(); + //! Returns an STL-compatible iterator for range-based for loops. + //! @warning Do not call Next() or Init() externally during range-for iteration. + NCollection_ForwardRangeIterator begin() + { + return NCollection_ForwardRangeIterator(this); + } + + //! Returns a sentinel marking the end of iteration. + NCollection_ForwardRangeSentinel end() const { return NCollection_ForwardRangeSentinel{}; } + private: //! Push a new iterator onto the stack (placement new on first use, assign on reuse). void pushIterator(TopoDS_Iterator&& theIter); diff --git a/src/ModelingData/TKBRep/TopoDS/TopoDS_Iterator.hxx b/src/ModelingData/TKBRep/TopoDS/TopoDS_Iterator.hxx index ac832f988f..6df07bbabd 100644 --- a/src/ModelingData/TKBRep/TopoDS/TopoDS_Iterator.hxx +++ b/src/ModelingData/TKBRep/TopoDS/TopoDS_Iterator.hxx @@ -17,8 +17,9 @@ #ifndef _TopoDS_Iterator_HeaderFile #define _TopoDS_Iterator_HeaderFile -#include +#include #include +#include #include #include #include @@ -83,6 +84,16 @@ public: return myShape; } + //! Returns an STL-compatible iterator for range-based for loops. + //! @warning Do not call Next() or Initialize() externally during range-for iteration. + NCollection_ForwardRangeIterator begin() + { + return NCollection_ForwardRangeIterator(this); + } + + //! Returns a sentinel marking the end of iteration. + NCollection_ForwardRangeSentinel end() const { return NCollection_ForwardRangeSentinel{}; } + private: //! Updates myShape from the current iterator position. void updateCurrentShape(); diff --git a/src/ModelingData/TKGeomBase/GTests/ExtremaPC_Comparison_Test.cxx b/src/ModelingData/TKGeomBase/GTests/ExtremaPC_Comparison_Test.cxx index f04f00c127..6f895495ba 100644 --- a/src/ModelingData/TKGeomBase/GTests/ExtremaPC_Comparison_Test.cxx +++ b/src/ModelingData/TKGeomBase/GTests/ExtremaPC_Comparison_Test.cxx @@ -581,319 +581,6 @@ TEST_F(ExtremaPC_ComparisonTest, BSpline_RandomPoints) << " tests had worse results"; } -//================================================================================================== -// Performance comparison tests -//================================================================================================== - -TEST_F(ExtremaPC_ComparisonTest, Performance_Circle) -{ - occ::handle aGeomCircle = - new Geom_Circle(gp_Ax2(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)), 10.0); - GeomAdaptor_Curve anAdaptor(aGeomCircle); - - std::mt19937 aGen(11111); - std::uniform_real_distribution<> aDist(-30.0, 30.0); - - const int aNumIterations = 1000; - - // Generate random points - std::vector aPoints; - aPoints.reserve(aNumIterations); - for (int i = 0; i < aNumIterations; ++i) - { - aPoints.emplace_back(aDist(aGen), aDist(aGen), aDist(aGen)); - } - - // Time new implementation - auto aStartNew = std::chrono::high_resolution_clock::now(); - for (const auto& aPt : aPoints) - { - ExtremaPC_Curve anExtPC(anAdaptor); - const ExtremaPC::Result& aResult = anExtPC.Perform(aPt, THE_TOL); - (void)aResult; - } - auto aEndNew = std::chrono::high_resolution_clock::now(); - auto aDurationNew = std::chrono::duration_cast(aEndNew - aStartNew); - - // Time old implementation - auto aStartOld = std::chrono::high_resolution_clock::now(); - for (const auto& aPt : aPoints) - { - Extrema_ExtPC anOldExtPC(aPt, anAdaptor); - (void)anOldExtPC; - } - auto aEndOld = std::chrono::high_resolution_clock::now(); - auto aDurationOld = std::chrono::duration_cast(aEndOld - aStartOld); - - std::cout << "[ ] Performance_Circle:" << std::endl; - std::cout << "[ ] New: " << aDurationNew.count() << " us (" << aNumIterations - << " iterations)" << std::endl; - std::cout << "[ ] Old: " << aDurationOld.count() << " us (" << aNumIterations - << " iterations)" << std::endl; - std::cout << "[ ] Ratio (Old/New): " - << static_cast(aDurationOld.count()) / aDurationNew.count() << std::endl; - - // We don't fail on performance, just report - SUCCEED(); -} - -TEST_F(ExtremaPC_ComparisonTest, Performance_BSpline) -{ - // Create a larger BSpline with 20 control points for meaningful benchmark - // For degree 3 with 20 poles: n_knots = n_poles - degree + 1 = 18 - // End knots have multiplicity = degree + 1 = 4, interior knots have multiplicity 1 - // Total: 4 + 16*1 + 4 = 24 = n_poles + degree + 1 OK - constexpr int aNbPoles = 20; - constexpr int aDegree = 3; - constexpr int aNbKnots = aNbPoles - aDegree + 1; // 18 knots - - NCollection_Array1 aPoles(1, aNbPoles); - for (int i = 1; i <= aNbPoles; ++i) - { - double t = (i - 1.0) / (aNbPoles - 1.0); - aPoles(i) = gp_Pnt(t * 10.0, std::sin(t * M_PI * 4) * 3.0, std::cos(t * M_PI * 3) * 2.0); - } - - NCollection_Array1 aKnots(1, aNbKnots); - NCollection_Array1 aMults(1, aNbKnots); - for (int i = 1; i <= aNbKnots; ++i) - { - aKnots(i) = (i - 1.0) / (aNbKnots - 1.0); - // End knots have multiplicity = degree + 1, interior knots have multiplicity 1 - aMults(i) = (i == 1 || i == aNbKnots) ? aDegree + 1 : 1; - } - - occ::handle aBSpline = new Geom_BSplineCurve(aPoles, aKnots, aMults, aDegree); - GeomAdaptor_Curve anAdaptor(aBSpline); - - std::mt19937 aGen(22222); - std::uniform_real_distribution<> aDist(-2.0, 12.0); - - const int aNumIterations = 10000; - - std::vector aPoints; - aPoints.reserve(aNumIterations); - for (int i = 0; i < aNumIterations; ++i) - { - aPoints.emplace_back(aDist(aGen), aDist(aGen), aDist(aGen)); - } - - // Time new implementation - NO CACHING (create new evaluator each time) - auto aStartNewNoCaching = std::chrono::high_resolution_clock::now(); - for (const auto& aPt : aPoints) - { - ExtremaPC_Curve anExtPC(anAdaptor); - const ExtremaPC::Result& aResult = anExtPC.Perform(aPt, THE_TOL); - (void)aResult; - } - auto aEndNewNoCaching = std::chrono::high_resolution_clock::now(); - auto aDurationNewNoCaching = - std::chrono::duration_cast(aEndNewNoCaching - aStartNewNoCaching); - - // Time new implementation - WITH CACHING (reuse evaluator) - ExtremaPC_Curve anExtPCCached(anAdaptor); - auto aStartNewCached = std::chrono::high_resolution_clock::now(); - for (const auto& aPt : aPoints) - { - const ExtremaPC::Result& aResult = anExtPCCached.Perform(aPt, THE_TOL); - (void)aResult; - } - auto aEndNewCached = std::chrono::high_resolution_clock::now(); - auto aDurationNewCached = - std::chrono::duration_cast(aEndNewCached - aStartNewCached); - - // Time old implementation - NO CACHING (create new for each point) - auto aStartOldNoCaching = std::chrono::high_resolution_clock::now(); - for (const auto& aPt : aPoints) - { - Extrema_ExtPC anOldExtPC(aPt, anAdaptor); - (void)anOldExtPC; - } - auto aEndOldNoCaching = std::chrono::high_resolution_clock::now(); - auto aDurationOldNoCaching = - std::chrono::duration_cast(aEndOldNoCaching - aStartOldNoCaching); - - // Time old implementation - WITH CACHING (Initialize once, Perform per point) - Extrema_ExtPC anOldExtPCCached; - anOldExtPCCached.Initialize(anAdaptor, anAdaptor.FirstParameter(), anAdaptor.LastParameter()); - auto aStartOldCached = std::chrono::high_resolution_clock::now(); - for (const auto& aPt : aPoints) - { - anOldExtPCCached.Perform(aPt); - } - auto aEndOldCached = std::chrono::high_resolution_clock::now(); - auto aDurationOldCached = - std::chrono::duration_cast(aEndOldCached - aStartOldCached); - - std::cout << "[ ] Performance_BSpline (" << aNumIterations - << " iterations, 20-pole curve):" << std::endl; - std::cout << "[ ] Old (no caching): " << aDurationOldNoCaching.count() << " us" - << " (" << aDurationOldNoCaching.count() / aNumIterations << " us/query)" << std::endl; - std::cout << "[ ] Old (with caching): " << aDurationOldCached.count() << " us" - << " (" << aDurationOldCached.count() / aNumIterations << " us/query)" << std::endl; - std::cout << "[ ] New (no caching): " << aDurationNewNoCaching.count() << " us" - << " (" << aDurationNewNoCaching.count() / aNumIterations << " us/query)" << std::endl; - std::cout << "[ ] New (with caching): " << aDurationNewCached.count() << " us" - << " (" << aDurationNewCached.count() / aNumIterations << " us/query)" << std::endl; - std::cout << "[ ] Old caching benefit: " << std::fixed << std::setprecision(2) - << static_cast(aDurationOldNoCaching.count()) / aDurationOldCached.count() - << "x" << std::endl; - std::cout << "[ ] New caching benefit: " << std::fixed << std::setprecision(2) - << static_cast(aDurationNewNoCaching.count()) / aDurationNewCached.count() - << "x" << std::endl; - std::cout << "[ ] New vs Old (cached): " << std::fixed << std::setprecision(2) - << static_cast(aDurationOldCached.count()) / aDurationNewCached.count() << "x" - << std::endl; - - SUCCEED(); -} - -TEST_F(ExtremaPC_ComparisonTest, Performance_Ellipse) -{ - occ::handle aGeomEllipse = - new Geom_Ellipse(gp_Ax2(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)), 20.0, 10.0); - GeomAdaptor_Curve anAdaptor(aGeomEllipse); - - std::mt19937 aGen(33333); - std::uniform_real_distribution<> aDist(-40.0, 40.0); - - const int aNumIterations = 10000; - - std::vector aPoints; - aPoints.reserve(aNumIterations); - for (int i = 0; i < aNumIterations; ++i) - { - aPoints.emplace_back(aDist(aGen), aDist(aGen), aDist(aGen)); - } - - // Time new implementation - auto aStartNew = std::chrono::high_resolution_clock::now(); - for (const auto& aPt : aPoints) - { - ExtremaPC_Curve anExtPC(anAdaptor); - const ExtremaPC::Result& aResult = anExtPC.Perform(aPt, THE_TOL); - (void)aResult; - } - auto aEndNew = std::chrono::high_resolution_clock::now(); - auto aDurationNew = std::chrono::duration_cast(aEndNew - aStartNew); - - // Time old implementation - auto aStartOld = std::chrono::high_resolution_clock::now(); - for (const auto& aPt : aPoints) - { - Extrema_ExtPC anOldExtPC(aPt, anAdaptor); - (void)anOldExtPC; - } - auto aEndOld = std::chrono::high_resolution_clock::now(); - auto aDurationOld = std::chrono::duration_cast(aEndOld - aStartOld); - - std::cout << "[ ] Performance_Ellipse (" << aNumIterations - << " iterations):" << std::endl; - std::cout << "[ ] Old Extrema_ExtPC: " << aDurationOld.count() << " us" - << " (" << aDurationOld.count() / aNumIterations << " us/query)" << std::endl; - std::cout << "[ ] New ExtremaPC: " << aDurationNew.count() << " us" - << " (" << aDurationNew.count() / aNumIterations << " us/query)" << std::endl; - std::cout << "[ ] Speedup: " << std::fixed << std::setprecision(2) - << static_cast(aDurationOld.count()) / aDurationNew.count() << "x" << std::endl; - - SUCCEED(); -} - -TEST_F(ExtremaPC_ComparisonTest, Performance_Bezier_Caching) -{ - // Create a high-degree Bezier curve (degree 15) - NCollection_Array1 aPoles(1, 16); - for (int i = 1; i <= 16; ++i) - { - double t = (i - 1.0) / 15.0; - aPoles(i) = gp_Pnt(t * 10.0, std::sin(t * M_PI * 3) * 4.0, std::cos(t * M_PI * 2) * 3.0); - } - - occ::handle aBezier = new Geom_BezierCurve(aPoles); - GeomAdaptor_Curve anAdaptor(aBezier); - - std::mt19937 aGen(44444); - std::uniform_real_distribution<> aDist(-2.0, 12.0); - - const int aNumIterations = 10000; - - std::vector aPoints; - aPoints.reserve(aNumIterations); - for (int i = 0; i < aNumIterations; ++i) - { - aPoints.emplace_back(aDist(aGen), aDist(aGen), aDist(aGen)); - } - - // Time new implementation - NO CACHING (create new evaluator each time) - auto aStartNewNoCaching = std::chrono::high_resolution_clock::now(); - for (const auto& aPt : aPoints) - { - ExtremaPC_Curve anExtPC(anAdaptor); - const ExtremaPC::Result& aResult = anExtPC.Perform(aPt, THE_TOL); - (void)aResult; - } - auto aEndNewNoCaching = std::chrono::high_resolution_clock::now(); - auto aDurationNewNoCaching = - std::chrono::duration_cast(aEndNewNoCaching - aStartNewNoCaching); - - // Time new implementation - WITH CACHING (reuse evaluator) - ExtremaPC_Curve anExtPCCached(anAdaptor); - auto aStartNewCached = std::chrono::high_resolution_clock::now(); - for (const auto& aPt : aPoints) - { - const ExtremaPC::Result& aResult = anExtPCCached.Perform(aPt, THE_TOL); - (void)aResult; - } - auto aEndNewCached = std::chrono::high_resolution_clock::now(); - auto aDurationNewCached = - std::chrono::duration_cast(aEndNewCached - aStartNewCached); - - // Time old implementation - NO CACHING (create new for each point) - auto aStartOldNoCaching = std::chrono::high_resolution_clock::now(); - for (const auto& aPt : aPoints) - { - Extrema_ExtPC anOldExtPC(aPt, anAdaptor); - (void)anOldExtPC; - } - auto aEndOldNoCaching = std::chrono::high_resolution_clock::now(); - auto aDurationOldNoCaching = - std::chrono::duration_cast(aEndOldNoCaching - aStartOldNoCaching); - - // Time old implementation - WITH CACHING (Initialize once, Perform per point) - Extrema_ExtPC anOldExtPCCached; - anOldExtPCCached.Initialize(anAdaptor, anAdaptor.FirstParameter(), anAdaptor.LastParameter()); - auto aStartOldCached = std::chrono::high_resolution_clock::now(); - for (const auto& aPt : aPoints) - { - anOldExtPCCached.Perform(aPt); - } - auto aEndOldCached = std::chrono::high_resolution_clock::now(); - auto aDurationOldCached = - std::chrono::duration_cast(aEndOldCached - aStartOldCached); - - std::cout << "[ ] Performance_Bezier_Caching (" << aNumIterations - << " iterations, degree-15 curve):" << std::endl; - std::cout << "[ ] Old (no caching): " << aDurationOldNoCaching.count() << " us" - << " (" << aDurationOldNoCaching.count() / aNumIterations << " us/query)" << std::endl; - std::cout << "[ ] Old (with caching): " << aDurationOldCached.count() << " us" - << " (" << aDurationOldCached.count() / aNumIterations << " us/query)" << std::endl; - std::cout << "[ ] New (no caching): " << aDurationNewNoCaching.count() << " us" - << " (" << aDurationNewNoCaching.count() / aNumIterations << " us/query)" << std::endl; - std::cout << "[ ] New (with caching): " << aDurationNewCached.count() << " us" - << " (" << aDurationNewCached.count() / aNumIterations << " us/query)" << std::endl; - std::cout << "[ ] Old caching benefit: " << std::fixed << std::setprecision(2) - << static_cast(aDurationOldNoCaching.count()) / aDurationOldCached.count() - << "x" << std::endl; - std::cout << "[ ] New caching benefit: " << std::fixed << std::setprecision(2) - << static_cast(aDurationNewNoCaching.count()) / aDurationNewCached.count() - << "x" << std::endl; - std::cout << "[ ] New vs Old (cached): " << std::fixed << std::setprecision(2) - << static_cast(aDurationOldCached.count()) / aDurationNewCached.count() << "x" - << std::endl; - - SUCCEED(); -} - //================================================================================================== // Edge case comparison tests //================================================================================================== @@ -949,86 +636,3 @@ TEST_F(ExtremaPC_ComparisonTest, PointOnCurve_BSpline) EXPECT_LT(std::sqrt(aNewResult.MinSquareDistance()), THE_TOL); } - -//================================================================================================== -// Offset curve performance tests (D2 is expensive for offset curves) -//================================================================================================== - -TEST_F(ExtremaPC_ComparisonTest, Performance_OffsetBSpline) -{ - // Create a BSpline base curve with 10 control points - constexpr int aNbPoles = 10; - constexpr int aDegree = 3; - constexpr int aNbKnots = aNbPoles - aDegree + 1; // 8 knots - - NCollection_Array1 aPoles(1, aNbPoles); - for (int i = 1; i <= aNbPoles; ++i) - { - double t = (i - 1.0) / (aNbPoles - 1.0); - aPoles(i) = gp_Pnt(t * 10.0, std::sin(t * M_PI * 2) * 3.0, - 0.0); // Planar curve for offset - } - - NCollection_Array1 aKnots(1, aNbKnots); - NCollection_Array1 aMults(1, aNbKnots); - for (int i = 1; i <= aNbKnots; ++i) - { - aKnots(i) = (i - 1.0) / (aNbKnots - 1.0); - aMults(i) = (i == 1 || i == aNbKnots) ? aDegree + 1 : 1; - } - - occ::handle aBSpline = new Geom_BSplineCurve(aPoles, aKnots, aMults, aDegree); - - // Create offset curve with offset = 2.0 - occ::handle anOffsetCurve = - new Geom_OffsetCurve(aBSpline, 2.0, gp_Dir(0, 0, 1)); - GeomAdaptor_Curve anAdaptor(anOffsetCurve); - - std::mt19937 aGen(55555); - std::uniform_real_distribution<> aDist(-2.0, 12.0); - - const int aNumIterations = 5000; - - std::vector aPoints; - aPoints.reserve(aNumIterations); - for (int i = 0; i < aNumIterations; ++i) - { - aPoints.emplace_back(aDist(aGen), aDist(aGen), aDist(aGen)); - } - - // Time new implementation - WITH CACHING (reuse evaluator) - ExtremaPC_Curve anExtPCCached(anAdaptor); - auto aStartNewCached = std::chrono::high_resolution_clock::now(); - for (const auto& aPt : aPoints) - { - const ExtremaPC::Result& aResult = anExtPCCached.Perform(aPt, THE_TOL); - (void)aResult; - } - auto aEndNewCached = std::chrono::high_resolution_clock::now(); - auto aDurationNewCached = - std::chrono::duration_cast(aEndNewCached - aStartNewCached); - - // Time old implementation - WITH CACHING (Initialize once, Perform per point) - Extrema_ExtPC anOldExtPCCached; - anOldExtPCCached.Initialize(anAdaptor, anAdaptor.FirstParameter(), anAdaptor.LastParameter()); - auto aStartOldCached = std::chrono::high_resolution_clock::now(); - for (const auto& aPt : aPoints) - { - anOldExtPCCached.Perform(aPt); - } - auto aEndOldCached = std::chrono::high_resolution_clock::now(); - auto aDurationOldCached = - std::chrono::duration_cast(aEndOldCached - aStartOldCached); - - std::cout << "[ ] Performance_OffsetBSpline (" << aNumIterations - << " iterations, 10-pole base curve):" << std::endl; - std::cout << "[ ] Old (with caching): " << aDurationOldCached.count() << " us" - << " (" << aDurationOldCached.count() / aNumIterations << " us/query)" << std::endl; - std::cout << "[ ] New (with caching): " << aDurationNewCached.count() << " us" - << " (" << aDurationNewCached.count() / aNumIterations << " us/query)" << std::endl; - std::cout << "[ ] New vs Old (cached): " << std::fixed << std::setprecision(2) - << static_cast(aDurationOldCached.count()) / aDurationNewCached.count() << "x" - << std::endl; - - SUCCEED(); -} diff --git a/src/ModelingData/TKGeomBase/GTests/ExtremaPC_SearchMode_Test.cxx b/src/ModelingData/TKGeomBase/GTests/ExtremaPC_SearchMode_Test.cxx index 318fb35932..83d0ae318d 100644 --- a/src/ModelingData/TKGeomBase/GTests/ExtremaPC_SearchMode_Test.cxx +++ b/src/ModelingData/TKGeomBase/GTests/ExtremaPC_SearchMode_Test.cxx @@ -1002,180 +1002,3 @@ TEST_F(ExtremaPC_SearchModeTest, BSpline_RandomPoints_MinMode) << aTestCount << ")"; } } - -//================================================================================================== -// Performance comparison for SearchMode -//================================================================================================== - -TEST_F(ExtremaPC_SearchModeTest, Performance_MinVsMinMax_Circle) -{ - occ::handle aGeomCircle = - new Geom_Circle(gp_Ax2(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)), 10.0); - GeomAdaptor_Curve anAdaptor(aGeomCircle); - - gp_Pnt aPoint(15.0, 8.0, 0.0); - - constexpr int aNbIterations = 5000; - - // Time MinMax mode - ExtremaPC_Curve anExtPCMinMax(anAdaptor); - - auto aStartMinMax = std::chrono::high_resolution_clock::now(); - for (int i = 0; i < aNbIterations; ++i) - { - (void)anExtPCMinMax.Perform(aPoint, THE_TOL, ExtremaPC::SearchMode::MinMax); - } - auto aEndMinMax = std::chrono::high_resolution_clock::now(); - auto aDurationMinMax = - std::chrono::duration_cast(aEndMinMax - aStartMinMax).count(); - - // Time Min mode - ExtremaPC_Curve anExtPCMin(anAdaptor); - - auto aStartMin = std::chrono::high_resolution_clock::now(); - for (int i = 0; i < aNbIterations; ++i) - { - (void)anExtPCMin.Perform(aPoint, THE_TOL, ExtremaPC::SearchMode::Min); - } - auto aEndMin = std::chrono::high_resolution_clock::now(); - auto aDurationMin = - std::chrono::duration_cast(aEndMin - aStartMin).count(); - - std::cout << " [PERF] Circle Min vs MinMax (" << aNbIterations << " iterations):" << std::endl; - std::cout << " Min mode: " << aDurationMin << " us" << std::endl; - std::cout << " MinMax mode: " << aDurationMinMax << " us" << std::endl; - std::cout << " Ratio (MinMax/Min): " << (double)aDurationMinMax / aDurationMin << std::endl; - - SUCCEED(); -} - -TEST_F(ExtremaPC_SearchModeTest, Performance_MinVsMinMax_BSpline) -{ - NCollection_Array1 aPoles(1, 4); - aPoles(1) = gp_Pnt(0, 0, 0); - aPoles(2) = gp_Pnt(1, 2, 0); - aPoles(3) = gp_Pnt(3, 2, 0); - aPoles(4) = gp_Pnt(4, 0, 0); - - NCollection_Array1 aKnots(1, 2); - aKnots(1) = 0.0; - aKnots(2) = 1.0; - - NCollection_Array1 aMults(1, 2); - aMults(1) = 4; - aMults(2) = 4; - - occ::handle aBSpline = new Geom_BSplineCurve(aPoles, aKnots, aMults, 3); - GeomAdaptor_Curve anAdaptor(aBSpline); - - gp_Pnt aPoint(2.0, 1.5, 0.0); - - constexpr int aNbIterations = 1000; - - // Time MinMax mode - ExtremaPC_Curve anExtPCMinMax(anAdaptor); - - auto aStartMinMax = std::chrono::high_resolution_clock::now(); - for (int i = 0; i < aNbIterations; ++i) - { - (void)anExtPCMinMax.Perform(aPoint, THE_TOL, ExtremaPC::SearchMode::MinMax); - } - auto aEndMinMax = std::chrono::high_resolution_clock::now(); - auto aDurationMinMax = - std::chrono::duration_cast(aEndMinMax - aStartMinMax).count(); - - // Time Min mode - ExtremaPC_Curve anExtPCMin(anAdaptor); - - auto aStartMin = std::chrono::high_resolution_clock::now(); - for (int i = 0; i < aNbIterations; ++i) - { - (void)anExtPCMin.Perform(aPoint, THE_TOL, ExtremaPC::SearchMode::Min); - } - auto aEndMin = std::chrono::high_resolution_clock::now(); - auto aDurationMin = - std::chrono::duration_cast(aEndMin - aStartMin).count(); - - std::cout << " [PERF] BSpline Min vs MinMax (" << aNbIterations << " iterations):" << std::endl; - std::cout << " Min mode: " << aDurationMin << " us" << std::endl; - std::cout << " MinMax mode: " << aDurationMinMax << " us" << std::endl; - std::cout << " Ratio (MinMax/Min): " << (double)aDurationMinMax / aDurationMin << std::endl; - - SUCCEED(); -} - -//================================================================================================== -// Early termination efficiency test -//================================================================================================== - -TEST_F(ExtremaPC_SearchModeTest, EarlyTermination_LargeBezier_MinMode) -{ - // Create a high-degree Bezier curve to test early termination benefit - constexpr int aNbPoles = 10; - NCollection_Array1 aPoles(1, aNbPoles); - - // Create a wavy curve with multiple local extrema - for (int i = 1; i <= aNbPoles; ++i) - { - double t = static_cast(i - 1) / (aNbPoles - 1); - double x = t * 20.0; - double y = 3.0 * std::sin(t * 4.0 * M_PI); - aPoles(i) = gp_Pnt(x, y, 0); - } - - occ::handle aBezier = new Geom_BezierCurve(aPoles); - GeomAdaptor_Curve anAdaptor(aBezier); - - // Test point that will have multiple candidate extrema - gp_Pnt aPoint(10.0, 5.0, 0.0); - - constexpr int aNbIterations = 500; - - // Time MinMax mode (no early termination) - ExtremaPC_Curve anExtPCMinMax(anAdaptor); - - auto aStartMinMax = std::chrono::high_resolution_clock::now(); - for (int i = 0; i < aNbIterations; ++i) - { - (void)anExtPCMinMax.Perform(aPoint, THE_TOL, ExtremaPC::SearchMode::MinMax); - } - auto aEndMinMax = std::chrono::high_resolution_clock::now(); - auto aDurationMinMax = - std::chrono::duration_cast(aEndMinMax - aStartMinMax).count(); - - // Time Min mode (with early termination) - ExtremaPC_Curve anExtPCMin(anAdaptor); - - auto aStartMin = std::chrono::high_resolution_clock::now(); - for (int i = 0; i < aNbIterations; ++i) - { - (void)anExtPCMin.Perform(aPoint, THE_TOL, ExtremaPC::SearchMode::Min); - } - auto aEndMin = std::chrono::high_resolution_clock::now(); - auto aDurationMin = - std::chrono::duration_cast(aEndMin - aStartMin).count(); - - // Verify correctness - Min mode should find the same minimum - const ExtremaPC::Result& aMinMaxRes = - anExtPCMinMax.Perform(aPoint, THE_TOL, ExtremaPC::SearchMode::MinMax); - const ExtremaPC::Result& aMinRes = - anExtPCMin.Perform(aPoint, THE_TOL, ExtremaPC::SearchMode::Min); - - ASSERT_TRUE(aMinMaxRes.IsDone()); - ASSERT_TRUE(aMinRes.IsDone()); - - double aMinMaxMinDist = std::sqrt(aMinMaxRes.MinSquareDistance()); - double aMinModeDist = std::sqrt(aMinRes.MinSquareDistance()); - - // Both should find the same minimum distance - EXPECT_NEAR(aMinMaxMinDist, aMinModeDist, 0.01); - - std::cout << " [PERF] Large Bezier Early Termination (" << aNbIterations - << " iterations):" << std::endl; - std::cout << " Min mode: " << aDurationMin << " us" << std::endl; - std::cout << " MinMax mode: " << aDurationMinMax << " us" << std::endl; - std::cout << " Ratio (MinMax/Min): " << (double)aDurationMinMax / aDurationMin << std::endl; - std::cout << " Min distance: " << aMinModeDist << std::endl; - - SUCCEED(); -}