// 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 class BRepGraph_LayerHistoryTest : public testing::Test { protected: void SetUp() override { BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0); myGraph.Clear(); [[maybe_unused]] const BRepGraph::ShapesView::Result aBuildRes1 = myGraph.Shapes().Add(aBoxMaker.Shape()); } BRepGraph myGraph; }; static NCollection_LinearVector collectWiresOfEdge(const BRepGraph& theGraph, const BRepGraph_EdgeId theEdge) { NCollection_LinearVector aWires; for (BRepGraph_WiresOfEdge aWireIt = theGraph.Topo().Edges().WiresOf(theEdge); aWireIt.More(); aWireIt.Next()) { aWires.Append(aWireIt.CurrentId()); } return aWires; } // ============================================================ // History chain tests // ============================================================ TEST_F(BRepGraph_LayerHistoryTest, 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.Editor().Gen().ApplyModification( anEdge0, [&](BRepGraph& theGraph, BRepGraph_NodeId /*theTarget*/) -> NCollection_LinearVector { // Simulate producing a new edge node at index NbEdgeDefs. anEdge1 = BRepGraph_NodeId(BRepGraph_NodeId::Kind::Edge, theGraph.Topo().Edges().Nb()); NCollection_LinearVector aResult; aResult.Append(anEdge1); return aResult; }, "Step1"); myGraph.Editor().Gen().ApplyModification( anEdge1, [&](BRepGraph& theGraph, BRepGraph_NodeId /*theTarget*/) -> NCollection_LinearVector { anEdge2 = BRepGraph_NodeId(BRepGraph_NodeId::Kind::Edge, theGraph.Topo().Edges().Nb()); NCollection_LinearVector aResult; aResult.Append(anEdge2); return aResult; }, "Step2"); const BRepGraph_NodeId anOriginal = myGraph.LayerRegistry().Ensure()->FindOriginal(anEdge2); EXPECT_TRUE(anOriginal.IsValid()); EXPECT_EQ(anOriginal, anEdge0); } TEST_F(BRepGraph_LayerHistoryTest, FindDerived_ChainABC_ContainsBAndC) { const BRepGraph_NodeId anEdge0(BRepGraph_NodeId::Kind::Edge, 0); BRepGraph_NodeId anEdge1; BRepGraph_NodeId anEdge2; myGraph.Editor().Gen().ApplyModification( anEdge0, [&](BRepGraph& theGraph, BRepGraph_NodeId) -> NCollection_LinearVector { anEdge1 = BRepGraph_NodeId(BRepGraph_NodeId::Kind::Edge, theGraph.Topo().Edges().Nb()); NCollection_LinearVector aResult; aResult.Append(anEdge1); return aResult; }, "Step1"); myGraph.Editor().Gen().ApplyModification( anEdge1, [&](BRepGraph& theGraph, BRepGraph_NodeId) -> NCollection_LinearVector { anEdge2 = BRepGraph_NodeId(BRepGraph_NodeId::Kind::Edge, theGraph.Topo().Edges().Nb()); NCollection_LinearVector aResult; aResult.Append(anEdge2); return aResult; }, "Step2"); const NCollection_LinearVector aDerived = myGraph.LayerRegistry().Ensure()->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_LayerHistoryTest, 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.LayerRegistry().Ensure()->FindOriginal(aFace); EXPECT_TRUE(anOriginal.IsValid()); EXPECT_EQ(anOriginal, aFace); } TEST_F(BRepGraph_LayerHistoryTest, FindDerived_UnmodifiedNode_ReturnsEmpty) { const BRepGraph_NodeId aFace(BRepGraph_NodeId::Kind::Face, 0); const NCollection_LinearVector aDerived = myGraph.LayerRegistry().Ensure()->FindDerived(aFace); EXPECT_EQ(aDerived.Size(), size_t(0)); } TEST_F(BRepGraph_LayerHistoryTest, Disabled_RecordHistory_NoRecordStored) { const size_t aNbBefore = myGraph.LayerRegistry().Ensure()->NbRecords(); myGraph.LayerRegistry().Ensure()->SetEnabled(false); const BRepGraph_NodeId anEdge0(BRepGraph_NodeId::Kind::Edge, 0); NCollection_LinearVector aReplacements; aReplacements.Append(BRepGraph_NodeId(BRepGraph_NodeId::Kind::Edge, 1)); myGraph.LayerRegistry().Ensure()->Record("Disabled", anEdge0, aReplacements.ToArray1()); EXPECT_EQ(myGraph.LayerRegistry().Ensure()->NbRecords(), aNbBefore); } TEST_F(BRepGraph_LayerHistoryTest, Disabled_ApplyModification_ModifierStillRuns) { myGraph.LayerRegistry().Ensure()->SetEnabled(false); bool aModifierRan = false; const BRepGraph_NodeId anEdge0(BRepGraph_NodeId::Kind::Edge, 0); myGraph.Editor().Gen().ApplyModification( anEdge0, [&](BRepGraph&, BRepGraph_NodeId) -> NCollection_LinearVector { aModifierRan = true; return NCollection_LinearVector(); }, "DisabledOp"); EXPECT_TRUE(aModifierRan); } TEST_F(BRepGraph_LayerHistoryTest, ReEnabled_RecordsAfterReEnable) { myGraph.LayerRegistry().Ensure()->SetEnabled(false); EXPECT_FALSE(myGraph.LayerRegistry().Ensure()->IsEnabled()); myGraph.LayerRegistry().Ensure()->SetEnabled(true); EXPECT_TRUE(myGraph.LayerRegistry().Ensure()->IsEnabled()); const size_t aNbBefore = myGraph.LayerRegistry().Ensure()->NbRecords(); const BRepGraph_NodeId anEdge0(BRepGraph_NodeId::Kind::Edge, 0); NCollection_LinearVector aReplacements; aReplacements.Append(BRepGraph_NodeId(BRepGraph_NodeId::Kind::Edge, 1)); myGraph.LayerRegistry().Ensure()->Record("ReEnabled", anEdge0, aReplacements.ToArray1()); EXPECT_EQ(myGraph.LayerRegistry().Ensure()->NbRecords(), aNbBefore + 1); } TEST_F(BRepGraph_LayerHistoryTest, ApplyModification_EmptyReplacements) { const BRepGraph_NodeId anEdge0(BRepGraph_NodeId::Kind::Edge, 0); const size_t aNbBefore = myGraph.LayerRegistry().Ensure()->NbRecords(); myGraph.Editor().Gen().ApplyModification( anEdge0, [](BRepGraph&, BRepGraph_NodeId) -> NCollection_LinearVector { return NCollection_LinearVector(); }, "Delete"); EXPECT_EQ(myGraph.LayerRegistry().Ensure()->NbRecords(), aNbBefore + 1); const BRepGraph_LayerHistory::Event& aRec = myGraph.LayerRegistry().Ensure()->Record( myGraph.LayerRegistry().Ensure()->NbRecords() - 1); EXPECT_TRUE(aRec.OperationName.IsEqual("Delete")); } TEST_F(BRepGraph_LayerHistoryTest, ApplyModification_MultipleReplacements) { const BRepGraph_NodeId anEdge0(BRepGraph_NodeId::Kind::Edge, 0); BRepGraph_NodeId aNew1; BRepGraph_NodeId aNew2; myGraph.Editor().Gen().ApplyModification( anEdge0, [&](BRepGraph& theGraph, BRepGraph_NodeId) -> NCollection_LinearVector { const uint32_t aBase = theGraph.Topo().Edges().Nb(); aNew1 = BRepGraph_NodeId(BRepGraph_NodeId::Kind::Edge, aBase); aNew2 = BRepGraph_NodeId(BRepGraph_NodeId::Kind::Edge, aBase + 1); NCollection_LinearVector aResult; aResult.Append(aNew1); aResult.Append(aNew2); return aResult; }, "Split"); const NCollection_LinearVector aDerived = myGraph.LayerRegistry().Ensure()->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_LayerHistoryTest, RecordHistory_EmptyReplacements_Stored) { const size_t aNbBefore = myGraph.LayerRegistry().Ensure()->NbRecords(); const BRepGraph_NodeId anEdge0(BRepGraph_NodeId::Kind::Edge, 0); NCollection_LinearVector anEmpty; myGraph.LayerRegistry().Ensure()->Record("Erase", anEdge0, anEmpty.ToArray1()); EXPECT_EQ(myGraph.LayerRegistry().Ensure()->NbRecords(), aNbBefore + 1); } TEST_F(BRepGraph_LayerHistoryTest, 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_LinearVector aRepl; aRepl.Append(anEdge1); myGraph.LayerRegistry().Ensure()->Record("Op1", anEdge0, aRepl.ToArray1()); NCollection_LinearVector aRepl2; aRepl2.Append(anEdge2); myGraph.LayerRegistry().Ensure()->Record("Op2", anEdge1, aRepl2.ToArray1()); const size_t aNb = myGraph.LayerRegistry().Ensure()->NbRecords(); ASSERT_GE(aNb, 2); const BRepGraph_LayerHistory::Event& aRec1 = myGraph.LayerRegistry().Ensure()->Record(aNb - 2); const BRepGraph_LayerHistory::Event& aRec2 = myGraph.LayerRegistry().Ensure()->Record(aNb - 1); EXPECT_LT(aRec1.SequenceNumber, aRec2.SequenceNumber); } TEST_F(BRepGraph_LayerHistoryTest, HistoryRecord_OperationName_Stored) { const BRepGraph_NodeId anEdge0(BRepGraph_NodeId::Kind::Edge, 0); NCollection_LinearVector aRepl; aRepl.Append(BRepGraph_NodeId(BRepGraph_NodeId::Kind::Edge, 1)); myGraph.LayerRegistry().Ensure()->Record("MyCustomOp", anEdge0, aRepl.ToArray1()); const BRepGraph_LayerHistory::Event& aRec = myGraph.LayerRegistry().Ensure()->Record( myGraph.LayerRegistry().Ensure()->NbRecords() - 1); EXPECT_TRUE(aRec.OperationName.IsEqual("MyCustomOp")); } TEST_F(BRepGraph_LayerHistoryTest, NbHistoryRecords_AfterMultipleOps_Correct) { const size_t aNbBefore = myGraph.LayerRegistry().Ensure()->NbRecords(); const BRepGraph_NodeId anEdge0(BRepGraph_NodeId::Kind::Edge, 0); NCollection_LinearVector aRepl; aRepl.Append(BRepGraph_NodeId(BRepGraph_NodeId::Kind::Edge, 1)); myGraph.LayerRegistry().Ensure()->Record("A", anEdge0, aRepl.ToArray1()); myGraph.LayerRegistry().Ensure()->Record("B", anEdge0, aRepl.ToArray1()); myGraph.LayerRegistry().Ensure()->Record("C", anEdge0, aRepl.ToArray1()); EXPECT_EQ(myGraph.LayerRegistry().Ensure()->NbRecords(), aNbBefore + 3); } TEST_F(BRepGraph_LayerHistoryTest, FindOriginal_TwoApply_TransitiveTrace) { const BRepGraph_NodeId aVtx0(BRepGraph_NodeId::Kind::Vertex, 0); BRepGraph_NodeId aVtx1; BRepGraph_NodeId aVtx2; myGraph.Editor().Gen().ApplyModification( aVtx0, [&](BRepGraph& theGraph, BRepGraph_NodeId) -> NCollection_LinearVector { aVtx1 = BRepGraph_NodeId(BRepGraph_NodeId::Kind::Vertex, theGraph.Topo().Vertices().Nb()); NCollection_LinearVector aResult; aResult.Append(aVtx1); return aResult; }, "Move1"); myGraph.Editor().Gen().ApplyModification( aVtx1, [&](BRepGraph& theGraph, BRepGraph_NodeId) -> NCollection_LinearVector { aVtx2 = BRepGraph_NodeId(BRepGraph_NodeId::Kind::Vertex, theGraph.Topo().Vertices().Nb()); NCollection_LinearVector 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.LayerRegistry().Ensure()->FindOriginal(aVtx2); EXPECT_TRUE(anOriginal.IsValid()); EXPECT_EQ(anOriginal, aVtx0); // Intermediate node should also trace back to root. const BRepGraph_NodeId anOriginal1 = myGraph.LayerRegistry().Ensure()->FindOriginal(aVtx1); EXPECT_TRUE(anOriginal1.IsValid()); EXPECT_EQ(anOriginal1, aVtx0); } TEST_F(BRepGraph_LayerHistoryTest, ApplyModification_WhenModifierThrows_DoesNotRecordHistory) { const size_t aNbRecordsBefore = myGraph.LayerRegistry().Ensure()->NbRecords(); const BRepGraph_NodeId anEdge(BRepGraph_NodeId::Kind::Edge, 0); #if !defined(No_Exception) EXPECT_THROW(myGraph.Editor().Gen().ApplyModification( anEdge, [](BRepGraph&, BRepGraph_NodeId) -> NCollection_LinearVector { throw Standard_Failure("Synthetic failure"); }, "ThrowingModification"), Standard_Failure); #endif EXPECT_EQ(myGraph.LayerRegistry().Ensure()->NbRecords(), aNbRecordsBefore); } TEST_F(BRepGraph_LayerHistoryTest, SplitEdge_RewritesAllContainingWires) { ASSERT_GT(myGraph.Topo().Edges().Nb(), 0u); const BRepGraph_EdgeId anEdgeId(0); const std::pair anEdgeRange = BRepGraph_Tool::Edge::Range(myGraph, anEdgeId); const double aSplitParam = 0.5 * (anEdgeRange.first + anEdgeRange.second); const uint32_t aNbVerticesBefore = myGraph.Topo().Vertices().Nb(); const gp_Pnt aSplitPoint(1.0, 2.0, 3.0); const BRepGraph_VertexId aSplitVertex = myGraph.Editor().Vertices().Add(aSplitPoint, 1.0e-7); ASSERT_TRUE(aSplitVertex.IsValid()); EXPECT_EQ(myGraph.Topo().Vertices().Nb(), aNbVerticesBefore + 1); const NCollection_LinearVector aWireIndices = collectWiresOfEdge(myGraph, anEdgeId); ASSERT_GT(aWireIndices.Size(), 0u); const uint32_t aNbEdgesBefore = myGraph.Topo().Edges().Nb(); const uint32_t aNbActiveEdgesBefore = myGraph.Topo().Edges().NbActive(); BRepGraph_EdgeId aSubA; BRepGraph_EdgeId aSubB; myGraph.Editor().Edges().Split(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_LinearVector& aCoEdgeIds = myGraph.Topo().Wires().Relations(aWireId).CoEdgeIds; bool hasOld = false; bool hasSubA = false; bool hasSubB = false; constexpr size_t THE_NOT_FOUND = std::numeric_limits::max(); size_t aSubAOrd = THE_NOT_FOUND; size_t aSubBOrd = THE_NOT_FOUND; for (size_t anIdx = 0; anIdx < aCoEdgeIds.Size(); ++anIdx) { const BRepGraphInc::CoEdgeDef& aCoEdge = myGraph.Topo().CoEdges().Definition(aCoEdgeIds.Value(anIdx)); const BRepGraph_NodeId anId(aCoEdge.ChildEdgeId); 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_NE(aSubAOrd, THE_NOT_FOUND); EXPECT_NE(aSubBOrd, THE_NOT_FOUND); const size_t aDistance = (aSubAOrd < aSubBOrd) ? (aSubBOrd - aSubAOrd) : (aSubAOrd - aSubBOrd); EXPECT_TRUE(aDistance == 1 || aDistance + 1 == aCoEdgeIds.Size()); } } TEST_F(BRepGraph_LayerHistoryTest, SplitEdge_IgnoresRemovedCoEdgeEntries) { ASSERT_GT(myGraph.Topo().Edges().Nb(), 0u); const BRepGraph_EdgeId anEdgeId(0); const std::pair anEdgeRange = BRepGraph_Tool::Edge::Range(myGraph, anEdgeId); const double aSplitParam = 0.5 * (anEdgeRange.first + anEdgeRange.second); const NCollection_LinearVector aWireIndices = collectWiresOfEdge(myGraph, anEdgeId); ASSERT_GT(aWireIndices.Size(), 1); const BRepGraph_WireId aWireId = aWireIndices.Value(0); const NCollection_LinearVector& aWireCoEdgesBefore = myGraph.Topo().Wires().Relations(aWireId).CoEdgeIds; ASSERT_GT(aWireCoEdgesBefore.Size(), 0u); constexpr size_t THE_NOT_FOUND = std::numeric_limits::max(); BRepGraph_CoEdgeId aCoEdgeToRemove; size_t aRemovedOrd = THE_NOT_FOUND; for (size_t aRefOrd = 0; aRefOrd < aWireCoEdgesBefore.Size(); ++aRefOrd) { const BRepGraph_CoEdgeId aCoEdgeId = aWireCoEdgesBefore.Value(aRefOrd); const BRepGraphInc::CoEdgeDef& aCoEdge = myGraph.Topo().CoEdges().Definition(aCoEdgeId); if (aCoEdge.ChildEdgeId == anEdgeId) { aCoEdgeToRemove = aCoEdgeId; aRemovedOrd = aRefOrd; break; } } ASSERT_TRUE(aCoEdgeToRemove.IsValid(myGraph.Topo().CoEdges().Nb())); ASSERT_NE(aRemovedOrd, THE_NOT_FOUND); myGraph.Editor().Gen().RemoveNode(BRepGraph_NodeId(aCoEdgeToRemove)); ASSERT_TRUE(aCoEdgeToRemove.IsRemoved(myGraph)); const BRepGraph_CoEdgeId aRemovedCoEdgeId = aCoEdgeToRemove; const size_t aRemovedWireNbActiveBefore = myGraph.Topo().Wires().Relations(aWireId).CoEdgeIds.Size(); const BRepGraph_VertexId aSplitVertex = myGraph.Editor().Vertices().Add(gp_Pnt(4.0, 5.0, 6.0), 1.0e-7); ASSERT_TRUE(aSplitVertex.IsValid()); BRepGraph_EdgeId aSubA; BRepGraph_EdgeId aSubB; myGraph.Editor().Edges().Split(anEdgeId, aSplitVertex, aSplitParam, aSubA, aSubB); ASSERT_TRUE(aSubA.IsValid()); ASSERT_TRUE(aSubB.IsValid()); EXPECT_TRUE(aCoEdgeToRemove.IsRemoved(myGraph)); EXPECT_EQ(myGraph.Topo().Wires().Relations(aWireId).CoEdgeIds.Size(), aRemovedWireNbActiveBefore); const BRepGraphInc::CoEdgeDef& aRemovedCoEdgeAfter = myGraph.Topo().CoEdges().Definition(aRemovedCoEdgeId); EXPECT_EQ(aRemovedCoEdgeAfter.ChildEdgeId, anEdgeId); bool hasSubA = false; bool hasSubB = false; for (BRepGraph_CoEdgeId aCoEdgeId = BRepGraph_CoEdgeId::Start(); aCoEdgeId.IsValid(myGraph.Topo().CoEdges().Nb()); ++aCoEdgeId) { const BRepGraphInc::CoEdgeDef& aCoEdge = myGraph.Topo().CoEdges().Definition(aCoEdgeId); if (aCoEdgeId.IsRemoved(myGraph)) { continue; } const BRepGraph_NodeId anId(aCoEdge.ChildEdgeId); if (anId == aSubA) { hasSubA = true; } if (anId == aSubB) { hasSubB = true; } } EXPECT_TRUE(hasSubA); EXPECT_TRUE(hasSubB); } TEST_F(BRepGraph_LayerHistoryTest, ApplyModification_SplitEdge_RecordsBothDerivedNodes) { ASSERT_GT(myGraph.Topo().Edges().Nb(), 0u); const BRepGraph_EdgeId anEdgeId(0); const std::pair anEdgeRange = BRepGraph_Tool::Edge::Range(myGraph, anEdgeId); const double aSplitParam = 0.5 * (anEdgeRange.first + anEdgeRange.second); const BRepGraph_VertexId aSplitVertex = myGraph.Editor().Vertices().Add(gp_Pnt(4.0, 5.0, 6.0), 1.0e-7); ASSERT_TRUE(aSplitVertex.IsValid()); const size_t aNbRecordsBefore = myGraph.LayerRegistry().Ensure()->NbRecords(); myGraph.Editor().Gen().ApplyModification( anEdgeId, [&](BRepGraph& theGraph, BRepGraph_NodeId theTarget) -> NCollection_LinearVector { BRepGraph_EdgeId aSubA; BRepGraph_EdgeId aSubB; theGraph.Editor().Edges().Split(BRepGraph_EdgeId::FromNodeId(theTarget), aSplitVertex, aSplitParam, aSubA, aSubB); NCollection_LinearVector aResult; aResult.Append(aSubA); aResult.Append(aSubB); return aResult; }, "Split"); EXPECT_EQ(myGraph.LayerRegistry().Ensure()->NbRecords(), aNbRecordsBefore + 1); const NCollection_LinearVector aDerived = myGraph.LayerRegistry().Ensure()->FindDerived(anEdgeId); EXPECT_GE(aDerived.Size(), 2); } TEST_F(BRepGraph_LayerHistoryTest, FindModified_PerKindLookup_ReturnsOnlyModified) { const BRepGraph_NodeId anOrig(BRepGraph_NodeId::Kind::Edge, 0); BRepGraph_NodeId aRepl; myGraph.Editor().Gen().ApplyModification( anOrig, [&](BRepGraph& theGraph, BRepGraph_NodeId) -> NCollection_LinearVector { aRepl = BRepGraph_NodeId(BRepGraph_NodeId::Kind::Edge, theGraph.Topo().Edges().Nb()); NCollection_LinearVector aResult; aResult.Append(aRepl); return aResult; }, "Modify"); const NCollection_LinearVector* aMod = myGraph.LayerRegistry().Ensure()->FindModified(anOrig); ASSERT_NE(aMod, nullptr); EXPECT_EQ(aMod->Size(), 1); EXPECT_EQ(aMod->Value(0), aRepl); const NCollection_LinearVector* aGen = myGraph.LayerRegistry().Ensure()->FindGenerated(anOrig); EXPECT_EQ(aGen, nullptr); } TEST_F(BRepGraph_LayerHistoryTest, FindGenerated_PerKindLookup_ReturnsOnlyGenerated) { const BRepGraph_NodeId anOrig(BRepGraph_NodeId::Kind::Edge, 0); const BRepGraph_NodeId anRepl(BRepGraph_NodeId::Kind::Edge, 1); NCollection_LinearVector aReplVec; aReplVec.Append(anRepl); myGraph.LayerRegistry().Ensure()->Record( "Generate", anOrig, aReplVec.ToArray1(), BRepGraph_LayerHistory::Kind::Generated); const NCollection_LinearVector* aGen = myGraph.LayerRegistry().Ensure()->FindGenerated(anOrig); ASSERT_NE(aGen, nullptr); EXPECT_EQ(aGen->Size(), 1); EXPECT_EQ(aGen->Value(0), anRepl); const NCollection_LinearVector* aMod = myGraph.LayerRegistry().Ensure()->FindModified(anOrig); EXPECT_EQ(aMod, nullptr); } TEST_F(BRepGraph_LayerHistoryTest, RecordDeleted_MarksNodesAsDeleted) { const BRepGraph_NodeId anEdgeA(BRepGraph_NodeId::Kind::Edge, 0); const BRepGraph_NodeId anEdgeB(BRepGraph_NodeId::Kind::Edge, 1); NCollection_LinearVector aDeleted; aDeleted.Append(anEdgeA); aDeleted.Append(anEdgeB); const size_t aNbBefore = myGraph.LayerRegistry().Ensure()->NbRecords(); myGraph.LayerRegistry().Ensure()->RecordDeleted("DeleteEdges", aDeleted.ToArray1()); EXPECT_EQ(myGraph.LayerRegistry().Ensure()->NbRecords(), aNbBefore + 1); EXPECT_TRUE(myGraph.LayerRegistry().Ensure()->IsDeleted(anEdgeA)); EXPECT_TRUE(myGraph.LayerRegistry().Ensure()->IsDeleted(anEdgeB)); const NCollection_FlatMap& aDeletedSet = myGraph.LayerRegistry().Ensure()->DeletedNodes(); EXPECT_TRUE(aDeletedSet.Contains(anEdgeA)); EXPECT_TRUE(aDeletedSet.Contains(anEdgeB)); } TEST_F(BRepGraph_LayerHistoryTest, RecordDeleted_EmptyList_IsNoop) { const size_t aNbBefore = myGraph.LayerRegistry().Ensure()->NbRecords(); NCollection_LinearVector aEmpty; myGraph.LayerRegistry().Ensure()->RecordDeleted("Noop", aEmpty.ToArray1()); EXPECT_EQ(myGraph.LayerRegistry().Ensure()->NbRecords(), aNbBefore); } TEST_F(BRepGraph_LayerHistoryTest, Record_EmptyReplacements_AutoDeleted) { const BRepGraph_NodeId anOrig(BRepGraph_NodeId::Kind::Edge, 0); NCollection_LinearVector aEmpty; myGraph.LayerRegistry().Ensure()->Record( "Consumed", anOrig, aEmpty.ToArray1(), BRepGraph_LayerHistory::Kind::Generated); EXPECT_TRUE(myGraph.LayerRegistry().Ensure()->IsDeleted(anOrig)); EXPECT_EQ(myGraph.LayerRegistry().Ensure()->NbRecords(), 1); EXPECT_EQ(myGraph.LayerRegistry().Ensure()->Record(size_t(0)).RecordKind, BRepGraph_LayerHistory::Kind::Deleted); } TEST_F(BRepGraph_LayerHistoryTest, FindDerived_ModifiedAndGenerated_CollectsBoth) { const BRepGraph_NodeId anOrig(BRepGraph_NodeId::Kind::Edge, 0); BRepGraph_NodeId aModRepl; BRepGraph_NodeId aGenRepl; myGraph.Editor().Gen().ApplyModification( anOrig, [&](BRepGraph& theGraph, BRepGraph_NodeId) -> NCollection_LinearVector { aModRepl = BRepGraph_NodeId(BRepGraph_NodeId::Kind::Edge, theGraph.Topo().Edges().Nb()); NCollection_LinearVector aResult; aResult.Append(aModRepl); return aResult; }, "FirstModified"); // Record a Generated event on the Modified result. aGenRepl = BRepGraph_NodeId(BRepGraph_NodeId::Kind::Edge, 2); NCollection_LinearVector aGenRepVec; aGenRepVec.Append(aGenRepl); myGraph.LayerRegistry().Ensure()->Record( "ThenGenerated", aModRepl, aGenRepVec.ToArray1(), BRepGraph_LayerHistory::Kind::Generated); const NCollection_LinearVector aAll = myGraph.LayerRegistry().Ensure()->FindDerived(anOrig); EXPECT_GE(aAll.Size(), 1); bool aFoundGen = false; for (const BRepGraph_NodeId& aNode : aAll) { if (aNode == aGenRepl) { aFoundGen = true; } } EXPECT_TRUE(aFoundGen); // Verify the Generated record is in the per-kind map. EXPECT_NE(myGraph.LayerRegistry().Ensure()->FindGenerated(aModRepl), nullptr); EXPECT_NE(myGraph.LayerRegistry().Ensure()->FindModified(anOrig), nullptr); } TEST_F(BRepGraph_LayerHistoryTest, RecordBatch_ExplicitGeneratedKind_StoredCorrectly) { const BRepGraph_NodeId anOrig(BRepGraph_NodeId::Kind::Edge, 0); const BRepGraph_NodeId anRepl(BRepGraph_NodeId::Kind::Edge, 1); NCollection_LinearVector aOriginals; NCollection_LinearVector aReplacements; aOriginals.Append(anOrig); aReplacements.Append(anRepl); myGraph.LayerRegistry().Ensure()->RecordBatch( "BatchGen", aOriginals.ToArray1(), aReplacements.ToArray1(), TCollection_AsciiString(), BRepGraph_LayerHistory::Kind::Generated); EXPECT_EQ(myGraph.LayerRegistry().Ensure()->NbRecords(), 1); EXPECT_EQ(myGraph.LayerRegistry().Ensure()->Record(size_t(0)).RecordKind, BRepGraph_LayerHistory::Kind::Generated); EXPECT_NE(myGraph.LayerRegistry().Ensure()->FindGenerated(anOrig), nullptr); EXPECT_EQ(myGraph.LayerRegistry().Ensure()->FindModified(anOrig), nullptr); } TEST_F(BRepGraph_LayerHistoryTest, Record_ExplicitModifiedKind_DefaultMatches) { NCollection_LinearVector aRepls; aRepls.Append(BRepGraph_NodeId(BRepGraph_NodeId::Kind::Edge, 1)); const size_t aNbBefore = myGraph.LayerRegistry().Ensure()->NbRecords(); myGraph.LayerRegistry().Ensure()->Record( "ExplicitMod", BRepGraph_NodeId(BRepGraph_NodeId::Kind::Edge, 0), aRepls.ToArray1(), BRepGraph_LayerHistory::Kind::Modified); EXPECT_EQ(myGraph.LayerRegistry().Ensure()->NbRecords(), aNbBefore + 1); EXPECT_EQ(myGraph.LayerRegistry().Ensure()->Record(aNbBefore).RecordKind, BRepGraph_LayerHistory::Kind::Modified); } TEST_F(BRepGraph_LayerHistoryTest, IsDeleted_UnrelatedNode_ReturnsFalse) { const BRepGraph_NodeId aNode(BRepGraph_NodeId::Kind::Edge, 0); EXPECT_FALSE(myGraph.LayerRegistry().Ensure()->IsDeleted(aNode)); } TEST_F(BRepGraph_LayerHistoryTest, RecordReplaced_MapsImageAndMarksDeleted) { const BRepGraph_NodeId anOrig(BRepGraph_NodeId::Kind::Face, 0); const BRepGraph_NodeId aReplacement(BRepGraph_NodeId::Kind::Face, 1); myGraph.LayerRegistry().Ensure()->RecordReplaced("ReplaceFace", anOrig, aReplacement); ASSERT_EQ(myGraph.LayerRegistry().Ensure()->NbRecords(), size_t(1)); EXPECT_EQ(myGraph.LayerRegistry().Ensure()->Record(size_t(0)).RecordKind, BRepGraph_LayerHistory::Kind::Replaced); EXPECT_TRUE(myGraph.LayerRegistry().Ensure()->IsDeleted(anOrig)); const NCollection_LinearVector* aModified = myGraph.LayerRegistry().Ensure()->FindModified(anOrig); ASSERT_NE(aModified, nullptr); ASSERT_EQ(aModified->Size(), size_t(1)); EXPECT_EQ(aModified->Value(0), aReplacement); const NCollection_LinearVector* anOrigins = myGraph.LayerRegistry().Ensure()->FindOriginals(aReplacement); ASSERT_NE(anOrigins, nullptr); ASSERT_EQ(anOrigins->Size(), size_t(1)); EXPECT_EQ(anOrigins->Value(0), anOrig); } TEST_F(BRepGraph_LayerHistoryTest, StructuralReplacement_DoesNotEmitSemanticHistory) { ASSERT_GT(myGraph.Topo().Faces().Nb(), 1); myGraph.LayerRegistry().Ensure()->Clear(); const BRepGraph_FaceId aRemoved(1); const BRepGraph_FaceId aReplacement(0); myGraph.Editor().Gen().ReplaceNode(aRemoved, aReplacement); EXPECT_EQ(myGraph.LayerRegistry().Ensure()->NbRecords(), size_t(0)); EXPECT_FALSE(myGraph.LayerRegistry().Ensure()->IsDeleted(aRemoved)); EXPECT_EQ(myGraph.LayerRegistry().Ensure()->FindModified(aRemoved), nullptr); } TEST_F(BRepGraph_LayerHistoryTest, FindOriginals_DerivedWithTwoParents_ReturnsBoth) { const BRepGraph_NodeId anOrigA(BRepGraph_NodeId::Kind::Face, 0); const BRepGraph_NodeId anOrigB(BRepGraph_NodeId::Kind::Face, 1); const BRepGraph_NodeId aDerived(BRepGraph_NodeId::Kind::Edge, 0); NCollection_LinearVector aFirst; aFirst.Append(aDerived); myGraph.LayerRegistry().Ensure()->Record("FirstParent", anOrigA, aFirst.ToArray1()); NCollection_LinearVector aSecond; aSecond.Append(aDerived); myGraph.LayerRegistry().Ensure()->Record("SecondParent", anOrigB, aSecond.ToArray1()); const NCollection_LinearVector* anOrigins = myGraph.LayerRegistry().Ensure()->FindOriginals(aDerived); ASSERT_NE(anOrigins, nullptr); EXPECT_EQ(anOrigins->Size(), size_t(2)); bool aHasA = false; bool aHasB = false; for (const BRepGraph_NodeId& anOrigin : *anOrigins) { if (anOrigin == anOrigA) { aHasA = true; } if (anOrigin == anOrigB) { aHasB = true; } } EXPECT_TRUE(aHasA); EXPECT_TRUE(aHasB); } TEST_F(BRepGraph_LayerHistoryTest, RecordUid_Modified_AppendsAuditRecord) { const BRepGraph_UID anOrig(BRepGraph_NodeId::Kind::Face, 10); const BRepGraph_UID aRepl(BRepGraph_NodeId::Kind::Face, 11); NCollection_LinearVector aReplacements; aReplacements.Append(aRepl); BRepGraph_LayerHistory aHist; aHist.RecordUid("UidMod", anOrig, aReplacements.ToArray1(), BRepGraph_LayerHistory::Kind::Modified); ASSERT_EQ(aHist.NbRecords(), size_t(1)); const BRepGraph_LayerHistory::Event& aRecord = aHist.Record(size_t(0)); EXPECT_TRUE(aRecord.OperationName.IsEqual("UidMod")); EXPECT_EQ(aRecord.RecordKind, BRepGraph_LayerHistory::Kind::Modified); ASSERT_TRUE(aRecord.UidMapping.IsBound(anOrig)); ASSERT_EQ(aRecord.UidMapping.Find(anOrig).Size(), size_t(1)); EXPECT_EQ(aRecord.UidMapping.Find(anOrig).Value(0), aRepl); EXPECT_TRUE(aRecord.Mapping.IsEmpty()); EXPECT_TRUE(aHist.HasKnownInput(anOrig)); const NCollection_LinearVector* aMod = aHist.FindModified(anOrig); ASSERT_NE(aMod, nullptr); ASSERT_EQ(aMod->Size(), size_t(1)); EXPECT_EQ(aMod->Value(0), aRepl); } TEST_F(BRepGraph_LayerHistoryTest, RecordDeletedUid_AppendsAuditRecord) { const BRepGraph_UID anOrigA(BRepGraph_NodeId::Kind::Edge, 20); const BRepGraph_UID anOrigB(BRepGraph_NodeId::Kind::Edge, 21); NCollection_LinearVector aDeleted; aDeleted.Append(anOrigA); aDeleted.Append(anOrigB); BRepGraph_LayerHistory aHist; aHist.RecordDeletedUid("UidDelete", aDeleted.ToArray1()); ASSERT_EQ(aHist.NbRecords(), size_t(1)); const BRepGraph_LayerHistory::Event& aRecord = aHist.Record(size_t(0)); EXPECT_TRUE(aRecord.OperationName.IsEqual("UidDelete")); EXPECT_EQ(aRecord.RecordKind, BRepGraph_LayerHistory::Kind::Deleted); ASSERT_TRUE(aRecord.UidMapping.IsBound(anOrigA)); ASSERT_TRUE(aRecord.UidMapping.IsBound(anOrigB)); EXPECT_TRUE(aRecord.UidMapping.Find(anOrigA).IsEmpty()); EXPECT_TRUE(aRecord.UidMapping.Find(anOrigB).IsEmpty()); EXPECT_TRUE(aHist.IsDeleted(anOrigA)); EXPECT_TRUE(aHist.IsDeleted(anOrigB)); EXPECT_TRUE(aHist.HasKnownInput(anOrigA)); EXPECT_TRUE(aHist.HasKnownInput(anOrigB)); } // ============================================================ // Absorb (BRepTools_History -> BRepGraph_LayerHistory bridge) tests // ============================================================ namespace { TopoDS_Shape MakeVertexShape(const double theX, const double theY, const double theZ) { BRepBuilderAPI_MakeVertex aMaker(gp_Pnt(theX, theY, theZ)); return aMaker.Shape(); } } // namespace TEST_F(BRepGraph_LayerHistoryTest, Absorb_NullSource_NoOp) { BRepGraph_LayerHistory aHist; NCollection_DataMap anInputs; NCollection_DataMap anOutputs; const occ::handle aNullSrc; aHist.Absorb(anInputs, anOutputs, aNullSrc, "NullSrc"); EXPECT_EQ(aHist.NbRecords(), size_t(0)); } TEST_F(BRepGraph_LayerHistoryTest, Absorb_EmptyInputs_NoOp) { BRepGraph_LayerHistory aHist; NCollection_DataMap anInputs; NCollection_DataMap anOutputs; const occ::handle aSrc = new BRepTools_History(); aHist.Absorb(anInputs, anOutputs, aSrc, "EmptyInputs"); EXPECT_EQ(aHist.NbRecords(), size_t(0)); } TEST_F(BRepGraph_LayerHistoryTest, Absorb_ModifiedOnly_EmitsModifiedRecord) { const TopoDS_Shape anInShape = MakeVertexShape(0, 0, false); const TopoDS_Shape anOutShape = MakeVertexShape(1, 0, false); const BRepGraph_NodeId anInNode(BRepGraph_NodeId::Kind::Vertex, 100); const BRepGraph_NodeId anOutNode(BRepGraph_NodeId::Kind::Vertex, 101); occ::handle aSrc = new BRepTools_History(); aSrc->AddModified(anInShape, anOutShape); NCollection_DataMap anInputs; anInputs.Bind(anInShape, anInNode); NCollection_DataMap anOutputs; anOutputs.Bind(anOutShape, anOutNode); BRepGraph_LayerHistory aHist; aHist.Absorb(anInputs, anOutputs, aSrc, "Mod"); ASSERT_EQ(aHist.NbRecords(), size_t(1)); EXPECT_EQ(aHist.Record(size_t(0)).RecordKind, BRepGraph_LayerHistory::Kind::Modified); const NCollection_LinearVector* aMod = aHist.FindModified(anInNode); ASSERT_NE(aMod, nullptr); ASSERT_EQ(aMod->Size(), size_t(1)); EXPECT_EQ(aMod->Value(0), anOutNode); EXPECT_EQ(aHist.FindGenerated(anInNode), nullptr); EXPECT_FALSE(aHist.IsDeleted(anInNode)); } TEST_F(BRepGraph_LayerHistoryTest, Absorb_GeneratedOnly_EmitsGeneratedRecord) { const TopoDS_Shape anInShape = MakeVertexShape(0, 0, false); const TopoDS_Shape anOutShape = MakeVertexShape(2, 0, false); const BRepGraph_NodeId anInNode(BRepGraph_NodeId::Kind::Vertex, 200); const BRepGraph_NodeId anOutNode(BRepGraph_NodeId::Kind::Vertex, 201); occ::handle aSrc = new BRepTools_History(); aSrc->AddGenerated(anInShape, anOutShape); NCollection_DataMap anInputs; anInputs.Bind(anInShape, anInNode); NCollection_DataMap anOutputs; anOutputs.Bind(anOutShape, anOutNode); BRepGraph_LayerHistory aHist; aHist.Absorb(anInputs, anOutputs, aSrc, "Gen"); ASSERT_EQ(aHist.NbRecords(), size_t(1)); EXPECT_EQ(aHist.Record(size_t(0)).RecordKind, BRepGraph_LayerHistory::Kind::Generated); const NCollection_LinearVector* aGen = aHist.FindGenerated(anInNode); ASSERT_NE(aGen, nullptr); ASSERT_EQ(aGen->Size(), size_t(1)); EXPECT_EQ(aGen->Value(0), anOutNode); EXPECT_EQ(aHist.FindModified(anInNode), nullptr); } TEST_F(BRepGraph_LayerHistoryTest, Absorb_RemovedTakesPrecedenceOverModified) { // The OCCT bug being guarded against: a shape can show up as both // IsRemoved() and in Modified()/Generated(). Absorb must classify it // as a deletion event regardless. const TopoDS_Shape anInShape = MakeVertexShape(0, 0, false); const TopoDS_Shape aGhostOut = MakeVertexShape(3, 0, false); const BRepGraph_NodeId anInNode(BRepGraph_NodeId::Kind::Vertex, 300); const BRepGraph_NodeId aGhostNode(BRepGraph_NodeId::Kind::Vertex, 301); occ::handle aSrc = new BRepTools_History(); aSrc->AddModified(anInShape, aGhostOut); aSrc->Remove(anInShape); NCollection_DataMap anInputs; anInputs.Bind(anInShape, anInNode); NCollection_DataMap anOutputs; anOutputs.Bind(aGhostOut, aGhostNode); BRepGraph_LayerHistory aHist; aHist.Absorb(anInputs, anOutputs, aSrc, "RemovedWins"); EXPECT_TRUE(aHist.IsDeleted(anInNode)); EXPECT_EQ(aHist.FindModified(anInNode), nullptr); ASSERT_EQ(aHist.NbRecords(), size_t(1)); EXPECT_EQ(aHist.Record(size_t(0)).RecordKind, BRepGraph_LayerHistory::Kind::Deleted); } TEST_F(BRepGraph_LayerHistoryTest, Absorb_OutputMissingFromMap_DroppedSilently) { const TopoDS_Shape anInShape = MakeVertexShape(0, 0, false); const TopoDS_Shape anOutShape = MakeVertexShape(4, 0, false); const BRepGraph_NodeId anInNode(BRepGraph_NodeId::Kind::Vertex, 400); occ::handle aSrc = new BRepTools_History(); aSrc->AddModified(anInShape, anOutShape); NCollection_DataMap anInputs; anInputs.Bind(anInShape, anInNode); // Intentionally do not bind anOutShape in anOutputs. NCollection_DataMap anOutputs; BRepGraph_LayerHistory aHist; aHist.Absorb(anInputs, anOutputs, aSrc, "Drop"); EXPECT_EQ(aHist.NbRecords(), size_t(0)); EXPECT_EQ(aHist.FindModified(anInNode), nullptr); } TEST_F(BRepGraph_LayerHistoryTest, ClearReleasesHistoryAndAllowsFreshUidRecording) { BRepGraph_LayerHistory aHist; for (uint32_t anIdx = 0; anIdx < 1000; ++anIdx) { const BRepGraph_UID anOriginal(BRepGraph_NodeId::Kind::Edge, anIdx + 1); NCollection_LinearVector aReplacements(1); aReplacements.Append(BRepGraph_UID(BRepGraph_NodeId::Kind::Edge, anIdx + 1001)); aHist.RecordUid("Stress", anOriginal, aReplacements.ToArray1()); } ASSERT_EQ(aHist.NbRecords(), size_t(1000)); aHist.Clear(); ASSERT_EQ(aHist.NbRecords(), size_t(0)); const BRepGraph_UID aFreshOriginal(BRepGraph_NodeId::Kind::Face, 20); const BRepGraph_UID aFreshReplacement(BRepGraph_NodeId::Kind::Face, 21); NCollection_LinearVector aFreshReplacements(1); aFreshReplacements.Append(aFreshReplacement); aHist.RecordUid("AfterClear", aFreshOriginal, aFreshReplacements.ToArray1()); ASSERT_EQ(aHist.NbRecords(), size_t(1)); const NCollection_LinearVector* aModified = aHist.FindModified(aFreshOriginal); ASSERT_NE(aModified, nullptr); ASSERT_EQ(aModified->Size(), 1u); EXPECT_EQ(aModified->Value(0), aFreshReplacement); EXPECT_FALSE(aHist.IsDeleted(aFreshOriginal)); }