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(); -}