Files
OCCT/src/ModelingData/TKBRep/GTests/BRepGraphInc_Test.cxx
T
Pasukhin Dmitry ea3fc1f8bd Modeling, BRepGraph - Identity copy path, lock-free cache reads, per-CoEdge queries (#1306)
Identity copy path:
- copyFullGraphIdentity with PrepareForLoad() + direct slot assignment
- CopyDerivedRelationsFrom for O(K) relation vector copy
- CopyRemovedFlagsFrom for bulk removed-flag copy
- CopyShapeBindingsFrom for TShape/Original shape binding copy
- PrepareForLoad skips Clear() when storage already empty (IsEmpty fast path)
- appendRelationIdDirect for O(1) append in rebuild loop (duplicates structurally impossible)
- ChangeCompoundRefsOfNodeInternal uses TryBound for single lookup
- Counts() and ActiveCounts() accessors for Counts struct construction

Lock-free cache reads:
- EdgeEntry: packed atomic<uint8_t> (GeomStatus+IsClosed+IsComputed)
- CoEdgeSameRangeEntry: packed atomic<uint8_t> (SameRange+SameParameter+Computed)
- WireEntry: packed atomic<uint8_t> (IsClosed+IsComputed)
- ShellEntry: atomic<ClosureStatus> with Invalid sentinel
- All entries use memory_order_acquire/release pairs
- ensureSize handles lazy array growth (no myPreSized)
- Enums (GeomStatus, ClosureStatus) moved inside entry structs

Per-CoEdge queries:
- SameRange/SameParameter are per-CoEdge properties, bound to CoEdge OwnGen
- Edge class no longer has SameParameter/SameRange/IsClosed coedge overloads
- CoEdge class owns SameParameter/SameRange queries
- Test files use local edgeSameParameter/edgeSameRange helpers

Lock-free storage and registry:
- BRepGraphInc_Storage: atomic dirty flags for UID reverse indexes
- BRepGraph_LayerRegistry: atomic subscription masks for lock-free dispatch
- Ensure/EnsureCache/EnsureLayer: double-checked locking with shared fast path

Other optimizations:
- Iterator Next() fast-path: check next element validity inline
- NbFaces O(K) inline dedup with NCollection_LocalArray
- GeomAdaptor_Curve/Surface/Geom2dAdaptor_Curve for cached geometry eval
- Value() calls replaced with EvalD0() for clearer naming
- TargetItem returns value instead of pointer (no null-pointer risk)
- copyTopologyDefinitionsIdentity clears Curve3DRepId on GeomPolicy::Drop
- ensureEdgeEntry concurrency: fast-path validation + selective field merge
2026-06-14 14:15:32 +01:00

2831 lines
105 KiB
C++

// 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 <BRep_Builder.hxx>
#include <BRep_Tool.hxx>
#include <BRepBuilderAPI_MakeEdge.hxx>
#include <BRepBuilderAPI_MakeFace.hxx>
#include <BRepBuilderAPI_MakeWire.hxx>
#include <BRepBuilderAPI_Transform.hxx>
#include <BRepGraph.hxx>
#include <BRepGraphInc_Definition.hxx>
#include <BRepGraph_CacheDerivedState.hxx>
#include <BRepGraphInc_Reference.hxx>
#include <BRepGraphInc_RepId.hxx>
#include "BRepGraph_RefTestTools.hxx"
#include <BRepGraph_RefsView.hxx>
#include <BRepGraph_ReverseIterator.hxx>
#include <BRepGraph_TopoView.hxx>
#include <BRepGraph_Tool.hxx>
#include <BRepGraphInc_Populate.hxx>
#include <BRepGraphInc_Reconstruct.hxx>
#include <BRepGraphInc_Storage.hxx>
#include <BRepGraph_ShapesView.hxx>
#include <BRepGraph_Compact.hxx>
#include <BRepGraph_Deduplicate.hxx>
#include <BRepGraph_EditorView.hxx>
#include <BRepGraph_LayerTopoSupplement.hxx>
#include <BRepGraph_LayerRegistry.hxx>
#include <BRepGraph_Validate.hxx>
#include <BRepGraph_Iterator.hxx>
#include <BRepGProp.hxx>
#include <BRepPrimAPI_MakeBox.hxx>
#include <BRepPrimAPI_MakeCylinder.hxx>
#include <BRepPrimAPI_MakeSphere.hxx>
#include <Geom2d_Line.hxx>
#include <Geom_Plane.hxx>
#include <GProp_GProps.hxx>
#include <gp_Dir2d.hxx>
#include <gp_Pnt2d.hxx>
#include <Precision.hxx>
#include <TopAbs_ShapeEnum.hxx>
#include <TopExp_Explorer.hxx>
#include <TopoDS.hxx>
#include <TopoDS_Compound.hxx>
#include <TopoDS_CompSolid.hxx>
#include <TopoDS_Face.hxx>
#include <TopoDS_Edge.hxx>
#include <TopoDS_Iterator.hxx>
#include <TopoDS_Shell.hxx>
#include <TopoDS_Solid.hxx>
#include <TopoDS_Wire.hxx>
#include <BRepTools.hxx>
#include <BRepTools_WireExplorer.hxx>
#include <Standard_ProgramError.hxx>
#include <gp_Pln.hxx>
#include <NCollection_LinearVector.hxx>
#include <gtest/gtest.h>
#include <type_traits>
#include <tuple>
static_assert(std::is_same_v<BRepGraphInc::VertexDef::TypeId, BRepGraph_VertexId>);
static_assert(std::is_same_v<BRepGraphInc::FaceRef::TypeId, BRepGraph_FaceRefId>);
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 uint32_t countSubShapes(const TopoDS_Shape& theShape, TopAbs_ShapeEnum theType)
{
uint32_t aCount = 0;
for (TopExp_Explorer anExp(theShape, theType); anExp.More(); anExp.Next())
{
++aCount;
}
return aCount;
}
template <class theIteratorType>
static uint32_t countIterator(theIteratorType theIterator)
{
uint32_t aCount = 0;
for (; theIterator.More(); theIterator.Next())
{
++aCount;
}
return aCount;
}
template <class theIdType>
static bool containsId(const NCollection_LinearVector<theIdType>& theIds, const theIdType theId)
{
for (const theIdType& anId : theIds)
{
if (anId == theId)
{
return true;
}
}
return false;
}
static BRepGraph_VertexId addStorageVertex(BRepGraphInc_Storage& theStorage,
const gp_Pnt& thePoint,
const double theTolerance)
{
const BRepGraph_VertexId aVertexId = theStorage.AppendVertex();
BRepGraphInc::VertexDef& aVertex = theStorage.ChangeVertex(aVertexId);
aVertex.Point = thePoint;
aVertex.Tolerance = theTolerance;
return aVertexId;
}
static BRepGraph_EdgeId addStorageEdge(BRepGraphInc_Storage& theStorage,
const BRepGraph_VertexId theStartVertex,
const BRepGraph_VertexId theEndVertex,
const double theTolerance = 0.0)
{
const BRepGraph_EdgeId anEdgeId = theStorage.AppendEdge();
const BRepGraph_VertexRefId aStartRef = theStorage.AppendVertexRef();
const BRepGraph_VertexRefId anEndRef = theStorage.AppendVertexRef();
theStorage.ChangeVertexRef(aStartRef).ParentEdgeId = anEdgeId;
theStorage.ChangeVertexRef(aStartRef).ChildVertexId = theStartVertex;
theStorage.ChangeVertexRef(anEndRef).ParentEdgeId = anEdgeId;
theStorage.ChangeVertexRef(anEndRef).ChildVertexId = theEndVertex;
BRepGraphInc::EdgeDef& anEdge = theStorage.ChangeEdge(anEdgeId);
anEdge.StartVertexRefId = aStartRef;
anEdge.EndVertexRefId = anEndRef;
anEdge.Tolerance = theTolerance;
return anEdgeId;
}
// ============================================================
// Entity count validation
// ============================================================
TEST(BRepGraphIncTest, Box_EntityCounts_MatchDefCounts)
{
BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0);
const TopoDS_Shape& aBox = aBoxMaker.Shape();
BRepGraph aGraph;
std::ignore = BRepGraphInc_Populate::Perform(aGraph, aBox, false);
ASSERT_FALSE(aGraph.IsEmpty());
// Entity counts for a box: 8 vertices, 12 edges, 6 faces, 1 shell, 1 solid.
EXPECT_EQ(aGraph.Topo().Vertices().Nb(), 8u);
EXPECT_EQ(aGraph.Topo().Edges().Nb(), 12u);
EXPECT_EQ(aGraph.Topo().Faces().Nb(), 6u);
EXPECT_EQ(aGraph.Topo().Shells().Nb(), 1u);
EXPECT_EQ(aGraph.Topo().Solids().Nb(), 1u);
}
TEST(BRepGraphIncTest, ParityOrientationRejectsInternalExternal)
{
#ifndef No_Exception
EXPECT_THROW(
{
[[maybe_unused]] const BRepGraphInc::ParityOrientation aParity =
BRepGraphInc::ParityOrientation(TopAbs_INTERNAL);
},
Standard_ProgramError);
EXPECT_THROW(
{
[[maybe_unused]] const BRepGraphInc::ParityOrientation aParity =
BRepGraphInc::ParityOrientation(TopAbs_EXTERNAL);
},
Standard_ProgramError);
BRepGraphInc::ParityOrientation anOrientation;
EXPECT_THROW(anOrientation = TopAbs_INTERNAL, Standard_ProgramError);
EXPECT_THROW(anOrientation = TopAbs_EXTERNAL, Standard_ProgramError);
#endif
BRepGraphInc::ParityOrientation anOrientation2;
anOrientation2 = TopAbs_FORWARD;
EXPECT_EQ(TopAbs_Orientation(anOrientation2), TopAbs_FORWARD);
anOrientation2 = TopAbs_REVERSED;
EXPECT_EQ(TopAbs_Orientation(anOrientation2), TopAbs_REVERSED);
}
TEST(BRepGraphIncTest, Cylinder_EntityCounts_MatchDefCounts)
{
BRepPrimAPI_MakeCylinder aCylMaker(5.0, 15.0);
const TopoDS_Shape& aCyl = aCylMaker.Shape();
BRepGraph aGraph;
std::ignore = BRepGraphInc_Populate::Perform(aGraph, aCyl, false);
ASSERT_FALSE(aGraph.IsEmpty());
EXPECT_EQ(aGraph.Topo().Vertices().Nb(), 2u);
EXPECT_EQ(aGraph.Topo().Edges().Nb(), 3u);
EXPECT_EQ(aGraph.Topo().Wires().Nb(), 3u);
EXPECT_EQ(aGraph.Topo().Faces().Nb(), 3u);
EXPECT_EQ(aGraph.Topo().Shells().Nb(), 1u);
EXPECT_EQ(aGraph.Topo().Solids().Nb(), 1u);
}
TEST(BRepGraphIncTest, Sphere_EntityCounts_MatchDefCounts)
{
BRepPrimAPI_MakeSphere aSphMaker(8.0);
const TopoDS_Shape& aSph = aSphMaker.Shape();
BRepGraph aGraph;
std::ignore = BRepGraphInc_Populate::Perform(aGraph, aSph, false);
ASSERT_FALSE(aGraph.IsEmpty());
EXPECT_EQ(aGraph.Topo().Vertices().Nb(), 2u);
EXPECT_EQ(aGraph.Topo().Edges().Nb(), 3u);
EXPECT_EQ(aGraph.Topo().Wires().Nb(), 1u);
EXPECT_EQ(aGraph.Topo().Faces().Nb(), 1u);
EXPECT_EQ(aGraph.Topo().Shells().Nb(), 1u);
EXPECT_EQ(aGraph.Topo().Solids().Nb(), 1u);
}
TEST(BRepGraphIncTest, Storage_AppendAccess_UsesTypedIds)
{
BRepGraphInc_Storage aStorage;
const BRepGraph_VertexId aVertexId = aStorage.AppendVertex();
const BRepGraph_FaceRefId aFaceRefId = aStorage.AppendFaceRef();
const BRepGraph_FaceSurfaceRepId aSurfaceRepId = aStorage.AppendFaceSurfaceRep();
EXPECT_EQ(aVertexId.Index, 0);
EXPECT_EQ(aFaceRefId.Index, 0);
EXPECT_EQ(aSurfaceRepId.Index, 0);
EXPECT_FALSE(aStorage.IsRemoved(aVertexId));
EXPECT_FALSE(aStorage.IsRemoved(aFaceRefId));
aStorage.ChangeVertex(aVertexId).Tolerance = 1.25;
aStorage.ChangeFaceRef(aFaceRefId).Orientation = TopAbs_REVERSED;
EXPECT_DOUBLE_EQ(aStorage.Vertex(aVertexId).Tolerance, 1.25);
EXPECT_EQ(aStorage.FaceRef(aFaceRefId).Orientation, TopAbs_REVERSED);
EXPECT_TRUE(aStorage.FaceRef(aFaceRefId).Orientation.IsReversed);
aStorage.ChangeFaceRef(aFaceRefId).Orientation = TopAbs_FORWARD;
EXPECT_EQ(aStorage.FaceRef(aFaceRefId).Orientation, TopAbs_FORWARD);
EXPECT_FALSE(aStorage.FaceRef(aFaceRefId).Orientation.IsReversed);
}
TEST(BRepGraphIncTest, Storage_GenericIdDispatch_UsesTypedHelpers)
{
BRepGraphInc_Storage aStorage;
const BRepGraph_VertexId aVertexId = aStorage.AppendVertex();
const BRepGraph_FaceRefId aFaceRefId = aStorage.AppendFaceRef();
std::ignore = aStorage.AppendFaceSurfaceRep();
aStorage.ChangeFaceRef(aFaceRefId).Orientation = TopAbs_REVERSED;
EXPECT_EQ(aStorage.FaceRef(aFaceRefId).Orientation, TopAbs_REVERSED);
EXPECT_TRUE(aStorage.MarkRemoved(BRepGraph_NodeId(aVertexId)));
EXPECT_TRUE(aStorage.MarkRemovedRef(BRepGraph_RefId(aFaceRefId)));
EXPECT_TRUE(aStorage.IsRemoved(aVertexId));
EXPECT_TRUE(aStorage.IsRemoved(aFaceRefId));
EXPECT_EQ(aStorage.NbActiveVertices(), 0);
EXPECT_EQ(aStorage.NbActiveFaceRefs(), 0);
}
TEST(BRepGraphIncTest, Storage_SetRemoved_TransitionsUpdateActiveCounts)
{
BRepGraphInc_Storage aStorage;
const BRepGraph_VertexId aVertexId = aStorage.AppendVertex();
const BRepGraph_FaceRefId aFaceRefId = aStorage.AppendFaceRef();
EXPECT_EQ(aStorage.NbActiveVertices(), 1);
EXPECT_EQ(aStorage.NbActiveFaceRefs(), 1);
aStorage.SetRemoved(aVertexId, true);
aStorage.SetRemoved(aFaceRefId, true);
EXPECT_TRUE(aStorage.IsRemoved(aVertexId));
EXPECT_TRUE(aStorage.IsRemoved(aFaceRefId));
EXPECT_EQ(aStorage.NbActiveVertices(), 0);
EXPECT_EQ(aStorage.NbActiveFaceRefs(), 0);
aStorage.SetRemoved(aVertexId, true);
aStorage.SetRemoved(aFaceRefId, true);
EXPECT_EQ(aStorage.NbActiveVertices(), 0);
EXPECT_EQ(aStorage.NbActiveFaceRefs(), 0);
aStorage.SetRemoved(aVertexId, false);
aStorage.SetRemoved(aFaceRefId, false);
EXPECT_FALSE(aStorage.IsRemoved(aVertexId));
EXPECT_FALSE(aStorage.IsRemoved(aFaceRefId));
EXPECT_EQ(aStorage.NbActiveVertices(), 1);
EXPECT_EQ(aStorage.NbActiveFaceRefs(), 1);
aStorage.SetRemoved(aVertexId, false);
aStorage.SetRemoved(aFaceRefId, false);
EXPECT_EQ(aStorage.NbActiveVertices(), 1);
EXPECT_EQ(aStorage.NbActiveFaceRefs(), 1);
}
// ============================================================
// 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);
BRepGraph aGraph;
std::ignore = BRepGraphInc_Populate::Perform(aGraph, aBox, false);
ASSERT_FALSE(aGraph.IsEmpty());
TopoDS_Shape aRecon = BRepGraphInc_Reconstruct::Node(aGraph, BRepGraph_SolidId::Start());
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);
BRepGraph aGraph;
std::ignore = BRepGraphInc_Populate::Perform(aGraph, aCyl, false);
ASSERT_FALSE(aGraph.IsEmpty());
TopoDS_Shape aRecon = BRepGraphInc_Reconstruct::Node(aGraph, BRepGraph_SolidId::Start());
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);
BRepGraph aGraph;
std::ignore = BRepGraphInc_Populate::Perform(aGraph, aSph, false);
ASSERT_FALSE(aGraph.IsEmpty());
TopoDS_Shape aRecon = BRepGraphInc_Reconstruct::Node(aGraph, BRepGraph_SolidId::Start());
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);
BRepGraph aGraph;
std::ignore = BRepGraphInc_Populate::Perform(aGraph, aBox, false);
ASSERT_FALSE(aGraph.IsEmpty());
TopoDS_Shape aRecon = BRepGraphInc_Reconstruct::Node(aGraph, BRepGraph_SolidId::Start());
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);
BRepGraph aGraph;
std::ignore = BRepGraphInc_Populate::Perform(aGraph, aCyl, false);
ASSERT_FALSE(aGraph.IsEmpty());
TopoDS_Shape aRecon = BRepGraphInc_Reconstruct::Node(aGraph, BRepGraph_SolidId::Start());
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();
BRepGraph aGraph;
std::ignore = BRepGraphInc_Populate::Perform(aGraph, aBox, false);
ASSERT_FALSE(aGraph.IsEmpty());
TopoDS_Shape aRecon = BRepGraphInc_Reconstruct::Node(aGraph, BRepGraph_SolidId::Start());
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();
BRepGraph aGraph;
std::ignore = BRepGraphInc_Populate::Perform(aGraph, aCyl, false);
ASSERT_FALSE(aGraph.IsEmpty());
TopoDS_Shape aRecon = BRepGraphInc_Reconstruct::Node(aGraph, BRepGraph_SolidId::Start());
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));
}
// ============================================================
// Relation table consistency
// ============================================================
TEST(BRepGraphIncTest, Box_Relations_EdgesToWires)
{
BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0);
const TopoDS_Shape& aBox = aBoxMaker.Shape();
BRepGraph aGraph;
std::ignore = BRepGraphInc_Populate::Perform(aGraph, aBox, false);
ASSERT_FALSE(aGraph.IsEmpty());
// Every edge must appear in at least one wire.
const uint32_t aNbEdges = aGraph.Topo().Edges().Nb();
for (BRepGraph_EdgeId anEdgeId(0); anEdgeId.IsValid(aNbEdges); ++anEdgeId)
{
EXPECT_GE(countIterator(aGraph.Topo().Edges().WiresOf(anEdgeId)), 1)
<< "Edge " << anEdgeId.Index << " not in any wire";
}
}
TEST(BRepGraphIncTest, Box_Relations_EdgesToFaces)
{
BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0);
const TopoDS_Shape& aBox = aBoxMaker.Shape();
BRepGraph aGraph;
std::ignore = BRepGraphInc_Populate::Perform(aGraph, aBox, false);
ASSERT_FALSE(aGraph.IsEmpty());
// Every edge must appear in at least one face (via EdgeFaceGeom).
const uint32_t aNbEdges = aGraph.Topo().Edges().Nb();
for (BRepGraph_EdgeId anEdgeId(0); anEdgeId.IsValid(aNbEdges); ++anEdgeId)
{
// Skip degenerate edges (e.g. sphere poles with no 3D curve).
bool anIsDegenerated = false;
bool anIsClosed = false;
[[maybe_unused]] const bool isEdgeStateComputed =
BRepGraph_CacheDerivedState::ComputeEdgeProperties(aGraph,
anEdgeId,
anIsDegenerated,
anIsClosed);
if (anIsDegenerated)
{
continue;
}
EXPECT_GE(countIterator(aGraph.Topo().Edges().FacesOf(anEdgeId)), 1)
<< "Edge " << anEdgeId.Index << " not in any face";
}
}
// ============================================================
// 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();
BRepGraph aSerial;
std::ignore = BRepGraphInc_Populate::Perform(aSerial, aBox, false);
ASSERT_FALSE(aSerial.IsEmpty());
BRepGraph aParallel;
std::ignore = BRepGraphInc_Populate::Perform(aParallel, aBox, true);
ASSERT_FALSE(aParallel.IsEmpty());
EXPECT_EQ(aParallel.Topo().Vertices().Nb(), aSerial.Topo().Vertices().Nb());
EXPECT_EQ(aParallel.Topo().Edges().Nb(), aSerial.Topo().Edges().Nb());
EXPECT_EQ(aParallel.Topo().Wires().Nb(), aSerial.Topo().Wires().Nb());
EXPECT_EQ(aParallel.Topo().Faces().Nb(), aSerial.Topo().Faces().Nb());
EXPECT_EQ(aParallel.Topo().Shells().Nb(), aSerial.Topo().Shells().Nb());
EXPECT_EQ(aParallel.Topo().Solids().Nb(), aSerial.Topo().Solids().Nb());
}
// ============================================================
// Null shape handling
// ============================================================
TEST(BRepGraphIncTest, NullShape_NoEntities)
{
BRepGraph aGraph;
std::ignore = BRepGraphInc_Populate::Perform(aGraph, TopoDS_Shape(), false);
EXPECT_TRUE(aGraph.IsEmpty());
EXPECT_EQ(aGraph.Topo().Vertices().Nb(), 0);
EXPECT_EQ(aGraph.Topo().Edges().Nb(), 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());
BRepGraph aGraph;
std::ignore = BRepGraphInc_Populate::Perform(aGraph, aCompound, false);
ASSERT_FALSE(aGraph.IsEmpty());
// Two solids, two shells.
EXPECT_EQ(aGraph.Topo().Solids().Nb(), 2);
EXPECT_EQ(aGraph.Topo().Shells().Nb(), 2);
EXPECT_EQ(aGraph.Topo().Compounds().Nb(), 1);
EXPECT_EQ(aGraph.Topo().Faces().Nb(), 12);
// Round-trip reconstruct via compound.
TopoDS_Shape aRecon = BRepGraphInc_Reconstruct::Node(aGraph, BRepGraph_CompoundId::Start());
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();
BRepGraph aGraph;
std::ignore = BRepGraphInc_Populate::Perform(aGraph, aBox, false);
ASSERT_FALSE(aGraph.IsEmpty());
// A box has 12 edges, each shared by 2 faces => 24 CoEdge entries total.
// (No seam edges on a box.)
size_t aCoEdgeCount = 0;
const uint32_t aNbEdges = aGraph.Topo().Edges().Nb();
for (BRepGraph_EdgeId anEdgeId(0); anEdgeId.IsValid(aNbEdges); ++anEdgeId)
{
aCoEdgeCount += aGraph.Topo().Edges().Relations(anEdgeId).CoEdgeIds.Size();
}
EXPECT_EQ(aCoEdgeCount, 24u);
}
TEST(BRepGraphIncTest, Cylinder_HasSeamEdges)
{
BRepPrimAPI_MakeCylinder aCylMaker(5.0, 15.0);
const TopoDS_Shape& aCyl = aCylMaker.Shape();
BRepGraph aGraph;
std::ignore = BRepGraphInc_Populate::Perform(aGraph, aCyl, false);
ASSERT_FALSE(aGraph.IsEmpty());
// A cylinder has seam edges: 2 CoEdges of the same edge on the same face with
// opposite orientations. Count seam pairs once via the FORWARD half.
size_t aSeamPairCount = 0;
const uint32_t aNbEdges = aGraph.Topo().Edges().Nb();
for (BRepGraph_EdgeId anEdgeId(0); anEdgeId.IsValid(aNbEdges); ++anEdgeId)
{
const NCollection_LinearVector<BRepGraph_CoEdgeId>& aCoEdgeIds =
aGraph.Topo().Edges().Relations(anEdgeId).CoEdgeIds;
for (const BRepGraph_CoEdgeId& aCoEdgeId : aCoEdgeIds)
{
const BRepGraphInc::CoEdgeDef& aCE = aGraph.Topo().CoEdges().Definition(aCoEdgeId);
if (aCE.Orientation != TopAbs_FORWARD)
{
continue;
}
// Look for a sibling CoEdge with the same face and opposite orientation.
for (const BRepGraph_CoEdgeId& aOtherId : aCoEdgeIds)
{
if (aOtherId == aCoEdgeId)
{
continue;
}
const BRepGraphInc::CoEdgeDef& aOther = aGraph.Topo().CoEdges().Definition(aOtherId);
if (aOther.FaceId == aCE.FaceId && aOther.Orientation != aCE.Orientation)
{
++aSeamPairCount;
break;
}
}
}
}
// A cylinder has 1 seam edge on its lateral face.
EXPECT_GE(aSeamPairCount, 1u) << "Cylinder should have at least 1 seam edge pair";
}
TEST(BRepGraphIncTest, Cylinder_SeamEdge_Relations_NoDuplicateFace)
{
BRepPrimAPI_MakeCylinder aCylMaker(5.0, 15.0);
const TopoDS_Shape& aCyl = aCylMaker.Shape();
BRepGraph aGraph;
std::ignore = BRepGraphInc_Populate::Perform(aGraph, aCyl, false);
ASSERT_FALSE(aGraph.IsEmpty());
// For seam edges (two PCurve entries with the same face but opposite
// orientations), the relation tables must contain each face only once.
const uint32_t aNbEdges = aGraph.Topo().Edges().Nb();
for (BRepGraph_EdgeId anEdgeId(0); anEdgeId.IsValid(aNbEdges); ++anEdgeId)
{
NCollection_LinearVector<BRepGraph_FaceId> aSeenFaces;
for (BRepGraph_FacesOfEdge aFaceIt = aGraph.Topo().Edges().FacesOf(anEdgeId); aFaceIt.More();
aFaceIt.Next())
{
const BRepGraph_FaceId aFaceId = aFaceIt.CurrentId();
EXPECT_FALSE(containsId(aSeenFaces, aFaceId))
<< "Duplicate face " << aFaceId.Index << " in FacesOfEdge(" << anEdgeId.Index << ")";
aSeenFaces.Append(aFaceId);
}
}
}
// ============================================================
// Degenerate edge handling
// ============================================================
TEST(BRepGraphIncTest, Sphere_DegenerateEdges_Preserved)
{
BRepPrimAPI_MakeSphere aSphMaker(8.0);
const TopoDS_Shape& aSph = aSphMaker.Shape();
BRepGraph aGraph;
std::ignore = BRepGraphInc_Populate::Perform(aGraph, aSph, false);
ASSERT_FALSE(aGraph.IsEmpty());
// A sphere has degenerate edges at the poles (no 3D curve, collapsed to a point).
uint32_t aDegenerateCount = 0;
const uint32_t aNbEdges = aGraph.Topo().Edges().Nb();
for (BRepGraph_EdgeId anEdgeId(0); anEdgeId.IsValid(aNbEdges); ++anEdgeId)
{
const BRepGraphInc::EdgeDef& anEdge = aGraph.Topo().Edges().Definition(anEdgeId);
bool anIsDegenerated = false;
bool anIsClosed = false;
[[maybe_unused]] const bool isEdgeStateComputed =
BRepGraph_CacheDerivedState::ComputeEdgeProperties(aGraph,
anEdgeId,
anIsDegenerated,
anIsClosed);
if (anIsDegenerated)
{
++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(aGraph, BRepGraph_SolidId::Start());
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());
BRepGraph aGraph;
std::ignore = BRepGraphInc_Populate::Perform(aGraph, aCompound, false);
ASSERT_FALSE(aGraph.IsEmpty());
TopoDS_Shape aRecon = BRepGraphInc_Reconstruct::Node(aGraph, BRepGraph_CompoundId::Start());
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, Populate_BakesNormalChildLocationsIntoDefinitions)
{
const TopoDS_Edge anEdge = BRepBuilderAPI_MakeEdge(gp_Pnt(0.0, 0.0, 0.0), gp_Pnt(1.0, 0.0, 0.0));
gp_Trsf aTrsf;
aTrsf.SetTranslation(gp_Vec(10.0, 0.0, 0.0));
const TopLoc_Location aLoc(aTrsf);
const TopoDS_Edge aMovedEdge = TopoDS::Edge(anEdge.Moved(aLoc));
BRep_Builder aBuilder;
TopoDS_Wire aWire;
aBuilder.MakeWire(aWire);
aBuilder.Add(aWire, anEdge);
aBuilder.Add(aWire, aMovedEdge);
BRepGraph aGraph;
ASSERT_NE(BRepGraphInc_Populate::Perform(aGraph, aWire, false),
BRepGraphInc_Populate::BuildStatus::Failed);
ASSERT_FALSE(aGraph.IsEmpty());
EXPECT_EQ(aGraph.Topo().Wires().Nb(), 1u);
EXPECT_EQ(aGraph.Topo().Edges().Nb(), 2u);
EXPECT_EQ(aGraph.Topo().Vertices().Nb(), 4u);
double aMinX = RealLast();
double aMaxX = -RealLast();
for (uint32_t aVertexIdx = 0; aVertexIdx < aGraph.Topo().Vertices().Nb(); ++aVertexIdx)
{
const gp_Pnt& aPoint =
aGraph.Topo().Vertices().Definition(BRepGraph_VertexId(aVertexIdx)).Point;
aMinX = std::min(aMinX, aPoint.X());
aMaxX = std::max(aMaxX, aPoint.X());
}
EXPECT_NEAR(aMinX, 0.0, Precision::Confusion());
EXPECT_NEAR(aMaxX, 11.0, Precision::Confusion());
TopoDS_Shape aRecon = BRepGraphInc_Reconstruct::Node(aGraph, BRepGraph_WireId::Start());
ASSERT_FALSE(aRecon.IsNull());
EXPECT_EQ(countSubShapes(aRecon, TopAbs_EDGE), 2);
EXPECT_EQ(countSubShapes(aRecon, TopAbs_VERTEX), 4);
}
TEST(BRepGraphIncTest, Populate_BakesStackedNormalLocationsIntoDefinitions)
{
gp_Trsf anEdgeTrsf;
anEdgeTrsf.SetTranslation(gp_Vec(2.0, 0.0, 0.0));
const TopLoc_Location anEdgeLoc(anEdgeTrsf);
BRepBuilderAPI_MakeWire aWireBuilder;
aWireBuilder.Add(TopoDS::Edge(
BRepBuilderAPI_MakeEdge(gp_Pnt(0.0, 0.0, 0.0), gp_Pnt(1.0, 0.0, 0.0)).Edge().Moved(anEdgeLoc)));
aWireBuilder.Add(TopoDS::Edge(
BRepBuilderAPI_MakeEdge(gp_Pnt(1.0, 0.0, 0.0), gp_Pnt(1.0, 1.0, 0.0)).Edge().Moved(anEdgeLoc)));
aWireBuilder.Add(TopoDS::Edge(
BRepBuilderAPI_MakeEdge(gp_Pnt(1.0, 1.0, 0.0), gp_Pnt(0.0, 1.0, 0.0)).Edge().Moved(anEdgeLoc)));
aWireBuilder.Add(TopoDS::Edge(
BRepBuilderAPI_MakeEdge(gp_Pnt(0.0, 1.0, 0.0), gp_Pnt(0.0, 0.0, 0.0)).Edge().Moved(anEdgeLoc)));
ASSERT_TRUE(aWireBuilder.IsDone());
gp_Trsf aWireTrsf;
aWireTrsf.SetTranslation(gp_Vec(0.0, 3.0, 0.0));
const TopoDS_Wire aMovedWire =
TopoDS::Wire(aWireBuilder.Wire().Moved(TopLoc_Location(aWireTrsf)));
TopoDS_Face aFace = BRepBuilderAPI_MakeFace(aMovedWire).Face();
gp_Trsf aFaceTrsf;
aFaceTrsf.SetTranslation(gp_Vec(0.0, 0.0, 5.0));
const TopoDS_Face aMovedFace = TopoDS::Face(aFace.Moved(TopLoc_Location(aFaceTrsf)));
BRep_Builder aBuilder;
TopoDS_Shell aShell;
aBuilder.MakeShell(aShell);
aBuilder.Add(aShell, aMovedFace);
gp_Trsf aShellTrsf;
aShellTrsf.SetTranslation(gp_Vec(7.0, 0.0, 0.0));
const TopoDS_Shell aMovedShell = TopoDS::Shell(aShell.Moved(TopLoc_Location(aShellTrsf)));
TopoDS_Solid aSolid;
aBuilder.MakeSolid(aSolid);
aBuilder.Add(aSolid, aMovedShell);
gp_Trsf aSolidTrsf;
aSolidTrsf.SetTranslation(gp_Vec(0.0, 11.0, 0.0));
const TopoDS_Solid aMovedSolid = TopoDS::Solid(aSolid.Moved(TopLoc_Location(aSolidTrsf)));
BRepGraph aGraph;
ASSERT_NE(BRepGraphInc_Populate::Perform(aGraph, aMovedSolid, false),
BRepGraphInc_Populate::BuildStatus::Failed);
ASSERT_FALSE(aGraph.IsEmpty());
EXPECT_EQ(aGraph.Topo().Solids().Nb(), 1u);
EXPECT_EQ(aGraph.Topo().Shells().Nb(), 1u);
EXPECT_EQ(aGraph.Topo().Faces().Nb(), 1u);
EXPECT_EQ(aGraph.Topo().Wires().Nb(), 1u);
EXPECT_EQ(aGraph.Topo().Edges().Nb(), 4u);
const TopLoc_Location anIdentity;
for (uint32_t aRefIdx = 0; aRefIdx < aGraph.Refs().Shells().Nb(); ++aRefIdx)
{
EXPECT_TRUE(
aGraph.Refs().Gen().LocalLocation(BRepGraph_ShellRefId(aRefIdx)).IsEqual(anIdentity));
}
for (uint32_t aRefIdx = 0; aRefIdx < aGraph.Refs().Faces().Nb(); ++aRefIdx)
{
EXPECT_TRUE(
aGraph.Refs().Gen().LocalLocation(BRepGraph_FaceRefId(aRefIdx)).IsEqual(anIdentity));
}
for (uint32_t aRefIdx = 0; aRefIdx < aGraph.Refs().Wires().Nb(); ++aRefIdx)
{
EXPECT_TRUE(
aGraph.Refs().Gen().LocalLocation(BRepGraph_WireRefId(aRefIdx)).IsEqual(anIdentity));
}
for (uint32_t aRefIdx = 0; aRefIdx < aGraph.Refs().Vertices().Nb(); ++aRefIdx)
{
EXPECT_TRUE(
aGraph.Refs().Gen().LocalLocation(BRepGraph_VertexRefId(aRefIdx)).IsEqual(anIdentity));
}
for (uint32_t aRefIdx = 0; aRefIdx < aGraph.Refs().Solids().Nb(); ++aRefIdx)
{
EXPECT_TRUE(
aGraph.Refs().Gen().LocalLocation(BRepGraph_SolidRefId(aRefIdx)).IsEqual(anIdentity));
}
TopoDS_Shape aRecon = BRepGraphInc_Reconstruct::Node(aGraph, BRepGraph_SolidId::Start());
ASSERT_FALSE(aRecon.IsNull());
double aMinX = RealLast();
double aMaxX = -RealLast();
double aMinY = RealLast();
double aMaxY = -RealLast();
double aMinZ = RealLast();
double aMaxZ = -RealLast();
for (TopExp_Explorer anExp(aRecon, TopAbs_VERTEX); anExp.More(); anExp.Next())
{
const gp_Pnt aPoint = BRep_Tool::Pnt(TopoDS::Vertex(anExp.Current()));
aMinX = std::min(aMinX, aPoint.X());
aMaxX = std::max(aMaxX, aPoint.X());
aMinY = std::min(aMinY, aPoint.Y());
aMaxY = std::max(aMaxY, aPoint.Y());
aMinZ = std::min(aMinZ, aPoint.Z());
aMaxZ = std::max(aMaxZ, aPoint.Z());
}
EXPECT_NEAR(aMinX, 9.0, Precision::Confusion());
EXPECT_NEAR(aMaxX, 10.0, Precision::Confusion());
EXPECT_NEAR(aMinY, 14.0, Precision::Confusion());
EXPECT_NEAR(aMaxY, 15.0, Precision::Confusion());
EXPECT_NEAR(aMinZ, 5.0, Precision::Confusion());
EXPECT_NEAR(aMaxZ, 5.0, Precision::Confusion());
}
TEST(BRepGraphIncTest, Populate_RootCompoundLocationIsPreservedByChildRefs)
{
BRep_Builder aBuilder;
TopoDS_Compound aCompound;
aBuilder.MakeCompound(aCompound);
aBuilder.Add(aCompound, BRepBuilderAPI_MakeEdge(gp_Pnt(0.0, 0.0, 0.0), gp_Pnt(1.0, 0.0, 0.0)));
gp_Trsf aTrsf;
aTrsf.SetTranslation(gp_Vec(25.0, 0.0, 0.0));
const TopLoc_Location aLoc(aTrsf);
const TopoDS_Compound aMovedCompound = TopoDS::Compound(aCompound.Moved(aLoc));
BRepGraph aGraph;
ASSERT_NE(BRepGraphInc_Populate::Perform(aGraph, aMovedCompound, false),
BRepGraphInc_Populate::BuildStatus::Failed);
ASSERT_FALSE(aGraph.IsEmpty());
ASSERT_EQ(aGraph.Topo().Compounds().Nb(), 1u);
const NCollection_LinearVector<BRepGraph_ChildRefId>& aChildRefs =
aGraph.Topo().Compounds().Relations(BRepGraph_CompoundId::Start()).ChildRefIds;
ASSERT_EQ(aChildRefs.Size(), 1u);
const BRepGraph_ChildRefId aChildRef = aChildRefs.Value(0);
EXPECT_TRUE(aGraph.Refs().Gen().LocalLocation(aChildRef).IsEqual(aLoc));
TopoDS_Shape aRecon = BRepGraphInc_Reconstruct::Node(aGraph, BRepGraph_CompoundId::Start());
ASSERT_FALSE(aRecon.IsNull());
double aMinX = RealLast();
double aMaxX = -RealLast();
for (TopExp_Explorer anExp(aRecon, TopAbs_VERTEX); anExp.More(); anExp.Next())
{
const gp_Pnt aPoint = BRep_Tool::Pnt(TopoDS::Vertex(anExp.Current()));
aMinX = std::min(aMinX, aPoint.X());
aMaxX = std::max(aMaxX, aPoint.X());
}
EXPECT_NEAR(aMinX, 25.0, Precision::Confusion());
EXPECT_NEAR(aMaxX, 26.0, Precision::Confusion());
}
TEST(BRepGraphIncTest, Cylinder_RoundTrip_BRepDump)
{
BRepPrimAPI_MakeCylinder aCylMaker(5.0, 20.0);
const TopoDS_Shape& aCyl = aCylMaker.Shape();
BRepGraph aGraph;
std::ignore = BRepGraphInc_Populate::Perform(aGraph, aCyl, false);
ASSERT_FALSE(aGraph.IsEmpty());
TopoDS_Shape aRecon = BRepGraphInc_Reconstruct::Node(aGraph, BRepGraph_SolidId::Start());
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 << ":" << '\n';
std::cout << " ORIG: " << anOrigLine << '\n';
std::cout << " RECON: " << aReconLine << '\n';
++aDiffCount;
}
}
std::cout << "Total " << aDiffCount << " line differences. "
<< "Orig size=" << anOrigStream.str().size()
<< " Recon size=" << aReconStream.str().size() << '\n';
}
else
{
std::cout << "BRep dumps are IDENTICAL (size=" << anOrigStream.str().size() << ")" << '\n';
}
const double anOrigArea = computeArea(aCyl);
const double aReconArea = computeArea(aRecon);
std::cout << "Orig area=" << anOrigArea << " Recon area=" << aReconArea << '\n';
EXPECT_NEAR(aReconArea, anOrigArea, Precision::Confusion());
}
// Supplemental edge/face topology is no longer stored in BRepGraphInc_Storage.
// Live preservation is covered by BRepGraph_LayerTopoSupplement tests.
static TopoDS_Edge makeIncEdgeWithInternalVertex()
{
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;
}
static TopoDS_Shape wrapIncEdgeInFace(const TopoDS_Edge& theEdge)
{
occ::handle<Geom_Plane> 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;
}
static TopoDS_Face makeIncFaceWithDirectVertex()
{
BRep_Builder aBB;
TopoDS_Vertex aV0;
TopoDS_Vertex aV1;
TopoDS_Vertex aV2;
TopoDS_Vertex aV3;
aBB.MakeVertex(aV0, gp_Pnt(0, 0, 0), Precision::Confusion());
aBB.MakeVertex(aV1, gp_Pnt(10, 0, 0), Precision::Confusion());
aBB.MakeVertex(aV2, gp_Pnt(10, 10, 0), Precision::Confusion());
aBB.MakeVertex(aV3, gp_Pnt(0, 10, 0), Precision::Confusion());
TopoDS_Edge aE0;
TopoDS_Edge aE1;
TopoDS_Edge aE2;
TopoDS_Edge aE3;
aBB.MakeEdge(aE0);
aBB.MakeEdge(aE1);
aBB.MakeEdge(aE2);
aBB.MakeEdge(aE3);
aBB.Add(aE0, aV0.Oriented(TopAbs_FORWARD));
aBB.Add(aE0, aV1.Oriented(TopAbs_REVERSED));
aBB.Add(aE1, aV1.Oriented(TopAbs_FORWARD));
aBB.Add(aE1, aV2.Oriented(TopAbs_REVERSED));
aBB.Add(aE2, aV2.Oriented(TopAbs_FORWARD));
aBB.Add(aE2, aV3.Oriented(TopAbs_REVERSED));
aBB.Add(aE3, aV3.Oriented(TopAbs_FORWARD));
aBB.Add(aE3, aV0.Oriented(TopAbs_REVERSED));
TopoDS_Wire aWire;
aBB.MakeWire(aWire);
aBB.Add(aWire, aE0);
aBB.Add(aWire, aE1);
aBB.Add(aWire, aE2);
aBB.Add(aWire, aE3);
aWire.Closed(true);
TopoDS_Face aFace;
aBB.MakeFace(aFace);
aBB.Add(aFace, aWire);
TopoDS_Vertex aLooseVertex;
aBB.MakeVertex(aLooseVertex, gp_Pnt(5, 5, 0), Precision::Confusion());
aBB.Add(aFace, aLooseVertex.Oriented(TopAbs_INTERNAL));
return aFace;
}
static TopoDS_Face makeIncPlainFace()
{
return BRepBuilderAPI_MakeFace(gp_Pln(), 0.0, 10.0, 0.0, 10.0).Face();
}
static TopoDS_Shell makeIncPlainShell()
{
BRep_Builder aBB;
TopoDS_Shell aShell;
aBB.MakeShell(aShell);
aBB.Add(aShell, makeIncPlainFace());
return aShell;
}
static TopoDS_Solid makeIncPlainSolid()
{
BRep_Builder aBB;
TopoDS_Solid aSolid;
aBB.MakeSolid(aSolid);
aBB.Add(aSolid, makeIncPlainShell());
return aSolid;
}
static uint32_t countDirectChildren(const TopoDS_Shape& theShape,
const TopAbs_ShapeEnum theType,
const TopAbs_Orientation theOrientation)
{
uint32_t aCount = 0;
for (TopoDS_Iterator aChildIt(theShape, false, false); aChildIt.More(); aChildIt.Next())
{
const TopoDS_Shape& aChild = aChildIt.Value();
if (aChild.ShapeType() == theType && aChild.Orientation() == theOrientation)
{
++aCount;
}
}
return aCount;
}
TEST(BRepGraphIncTest, Populate_RegistersSupplementEdgeVerticesIntoLayer)
{
BRepGraph aGraph;
occ::handle<BRepGraph_LayerTopoSupplement> aSupplement =
aGraph.LayerRegistry().Ensure<BRepGraph_LayerTopoSupplement>();
std::ignore = BRepGraphInc_Populate::Perform(aGraph,
wrapIncEdgeInFace(makeIncEdgeWithInternalVertex()),
false);
ASSERT_FALSE(aGraph.IsEmpty());
BRepGraph_EdgeId aEdgeId;
if (aGraph.Topo().Edges().Nb() > 0)
{
aEdgeId = BRepGraph_EdgeId::Start();
}
ASSERT_TRUE(aEdgeId.IsValid());
const NCollection_LinearVector<uint64_t>& anAttached = aSupplement->AttachedTo(aEdgeId);
ASSERT_EQ(anAttached.Size(), 1);
const BRepGraph_LayerTopoSupplement::Entry* anEntry = aSupplement->FindByUid(anAttached.First());
ASSERT_NE(anEntry, nullptr);
EXPECT_EQ(anEntry->Kind, BRepGraph_LayerTopoSupplement::AttachmentKind::EdgeInternalVertex);
EXPECT_EQ(anEntry->Shape.ShapeType(), TopAbs_VERTEX);
EXPECT_EQ(anEntry->Shape.Orientation(), TopAbs_INTERNAL);
}
TEST(BRepGraphIncTest, Reconstruct_ReplaysSupplementEdgeVerticesFromLayer)
{
BRepGraph aGraph;
occ::handle<BRepGraph_LayerTopoSupplement> aSupplement =
aGraph.LayerRegistry().Ensure<BRepGraph_LayerTopoSupplement>();
std::ignore = BRepGraphInc_Populate::Perform(aGraph,
wrapIncEdgeInFace(makeIncEdgeWithInternalVertex()),
false);
ASSERT_FALSE(aGraph.IsEmpty());
TopoDS_Shape aRecon = BRepGraphInc_Reconstruct::Node(aGraph, BRepGraph_FaceId::Start());
ASSERT_FALSE(aRecon.IsNull());
bool aFoundInternal = 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)
{
aFoundInternal = true;
}
}
}
EXPECT_TRUE(aFoundInternal);
}
TEST(BRepGraphIncTest, Reconstruct_WithoutSupplementLayer_DropsSupplementEdgeVerticesButKeepsCore)
{
// No supplement layer registered - internal vertices should be dropped.
BRepGraph aGraph;
std::ignore = BRepGraphInc_Populate::Perform(aGraph,
wrapIncEdgeInFace(makeIncEdgeWithInternalVertex()),
false);
ASSERT_FALSE(aGraph.IsEmpty());
TopoDS_Shape aRecon = BRepGraphInc_Reconstruct::Node(aGraph, BRepGraph_FaceId::Start());
ASSERT_FALSE(aRecon.IsNull());
uint32_t aFoundEdges = 0;
uint32_t aFoundBoundaries = 0;
uint32_t aFoundInternal = 0;
for (TopExp_Explorer anEdgeExp(aRecon, TopAbs_EDGE); anEdgeExp.More(); anEdgeExp.Next())
{
++aFoundEdges;
for (TopoDS_Iterator aVIt(anEdgeExp.Current(), false); aVIt.More(); aVIt.Next())
{
if (aVIt.Value().ShapeType() != TopAbs_VERTEX)
{
continue;
}
if (aVIt.Value().Orientation() == TopAbs_FORWARD
|| aVIt.Value().Orientation() == TopAbs_REVERSED)
{
++aFoundBoundaries;
}
else if (aVIt.Value().Orientation() == TopAbs_INTERNAL)
{
++aFoundInternal;
}
}
}
EXPECT_EQ(aFoundEdges, 1);
EXPECT_EQ(aFoundBoundaries, 2);
EXPECT_EQ(aFoundInternal, 0);
}
TEST(BRepGraphIncTest, Reconstruct_EdgeNode_ReplaysSupplementEdgeVerticesFromLayer)
{
BRepGraph aGraph;
occ::handle<BRepGraph_LayerTopoSupplement> aSupplement =
aGraph.LayerRegistry().Ensure<BRepGraph_LayerTopoSupplement>();
std::ignore = BRepGraphInc_Populate::Perform(aGraph, makeIncEdgeWithInternalVertex(), false);
ASSERT_FALSE(aGraph.IsEmpty());
ASSERT_GT(aGraph.Topo().Edges().Nb(), 0);
const TopoDS_Shape aRecon = BRepGraphInc_Reconstruct::Node(aGraph, BRepGraph_EdgeId::Start());
ASSERT_FALSE(aRecon.IsNull());
int aFoundInternal = 0;
for (TopoDS_Iterator aVIt(aRecon, false); aVIt.More(); aVIt.Next())
{
if (aVIt.Value().ShapeType() == TopAbs_VERTEX && aVIt.Value().Orientation() == TopAbs_INTERNAL)
{
++aFoundInternal;
}
}
EXPECT_EQ(aFoundInternal, 1);
}
TEST(BRepGraphIncTest, Reconstruct_WireNode_ReplaysSupplementEdgeVerticesFromLayer)
{
BRepGraph aGraph;
occ::handle<BRepGraph_LayerTopoSupplement> aSupplement =
aGraph.LayerRegistry().Ensure<BRepGraph_LayerTopoSupplement>();
BRep_Builder aBB;
TopoDS_Wire aWire;
aBB.MakeWire(aWire);
aBB.Add(aWire, makeIncEdgeWithInternalVertex());
std::ignore = BRepGraphInc_Populate::Perform(aGraph, aWire, false);
ASSERT_FALSE(aGraph.IsEmpty());
ASSERT_GT(aGraph.Topo().Wires().Nb(), 0);
const TopoDS_Shape aRecon = BRepGraphInc_Reconstruct::Node(aGraph, BRepGraph_WireId::Start());
ASSERT_FALSE(aRecon.IsNull());
int aFoundInternal = 0;
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)
{
++aFoundInternal;
}
}
}
EXPECT_EQ(aFoundInternal, 1);
}
TEST(BRepGraphIncTest, Reconstruct_FaceNode_ReplaysSupplementFaceVerticesFromLayer)
{
BRepGraph aGraph;
occ::handle<BRepGraph_LayerTopoSupplement> aSupplement =
aGraph.LayerRegistry().Ensure<BRepGraph_LayerTopoSupplement>();
std::ignore = BRepGraphInc_Populate::Perform(aGraph, makeIncFaceWithDirectVertex(), false);
ASSERT_FALSE(aGraph.IsEmpty());
ASSERT_GT(aGraph.Topo().Faces().Nb(), 0);
const NCollection_LinearVector<uint64_t>& anAttached =
aSupplement->AttachedTo(BRepGraph_FaceId::Start());
ASSERT_EQ(anAttached.Size(), 1);
const BRepGraph_LayerTopoSupplement::Entry* anEntry = aSupplement->FindByUid(anAttached.First());
ASSERT_NE(anEntry, nullptr);
EXPECT_EQ(anEntry->Kind, BRepGraph_LayerTopoSupplement::AttachmentKind::FaceDirectVertex);
EXPECT_EQ(anEntry->Shape.ShapeType(), TopAbs_VERTEX);
EXPECT_EQ(anEntry->Shape.Orientation(), TopAbs_INTERNAL);
const TopoDS_Shape aRecon = BRepGraphInc_Reconstruct::Node(aGraph, BRepGraph_FaceId::Start());
ASSERT_FALSE(aRecon.IsNull());
int aFoundInternal = 0;
int aFoundWires = 0;
for (TopoDS_Iterator aChildIt(aRecon, false, false); aChildIt.More(); aChildIt.Next())
{
if (aChildIt.Value().ShapeType() == TopAbs_WIRE)
{
++aFoundWires;
}
else if (aChildIt.Value().ShapeType() == TopAbs_VERTEX
&& aChildIt.Value().Orientation() == TopAbs_INTERNAL)
{
++aFoundInternal;
}
}
EXPECT_EQ(aFoundWires, 1);
EXPECT_EQ(aFoundInternal, 1);
}
TEST(BRepGraphIncTest, Populate_ShellRoutesInternalFaceToSupplement)
{
BRep_Builder aBB;
TopoDS_Shell aShell;
aBB.MakeShell(aShell);
aBB.Add(aShell, makeIncPlainFace().Oriented(TopAbs_FORWARD));
aBB.Add(aShell, makeIncPlainFace().Oriented(TopAbs_INTERNAL));
BRepGraph aGraph;
occ::handle<BRepGraph_LayerTopoSupplement> aSupplement =
aGraph.LayerRegistry().Ensure<BRepGraph_LayerTopoSupplement>();
ASSERT_NO_THROW({ std::ignore = BRepGraphInc_Populate::Perform(aGraph, aShell, false); });
ASSERT_EQ(aGraph.Topo().Shells().Nb(), 1);
EXPECT_EQ(aGraph.Topo().Shells().Relations(BRepGraph_ShellId::Start()).FaceRefIds.Size(), 1);
const NCollection_LinearVector<uint64_t>& anAttached =
aSupplement->AttachedTo(BRepGraph_ShellId::Start());
ASSERT_EQ(anAttached.Size(), 1);
const BRepGraph_LayerTopoSupplement::Entry* anEntry = aSupplement->FindByUid(anAttached.First());
ASSERT_NE(anEntry, nullptr);
EXPECT_EQ(anEntry->Kind, BRepGraph_LayerTopoSupplement::AttachmentKind::ShellAuxShape);
EXPECT_EQ(anEntry->Shape.ShapeType(), TopAbs_FACE);
EXPECT_EQ(anEntry->Shape.Orientation(), TopAbs_INTERNAL);
const TopoDS_Shape aRecon = BRepGraphInc_Reconstruct::Node(aGraph, BRepGraph_ShellId::Start());
ASSERT_FALSE(aRecon.IsNull());
EXPECT_EQ(countDirectChildren(aRecon, TopAbs_FACE, TopAbs_FORWARD), 1);
EXPECT_EQ(countDirectChildren(aRecon, TopAbs_FACE, TopAbs_INTERNAL), 1);
}
TEST(BRepGraphIncTest, Populate_SolidRoutesInternalShellToSupplement)
{
BRep_Builder aBB;
TopoDS_Solid aSolid;
aBB.MakeSolid(aSolid);
aBB.Add(aSolid, makeIncPlainShell().Oriented(TopAbs_FORWARD));
aBB.Add(aSolid, makeIncPlainShell().Oriented(TopAbs_INTERNAL));
BRepGraph aGraph;
occ::handle<BRepGraph_LayerTopoSupplement> aSupplement =
aGraph.LayerRegistry().Ensure<BRepGraph_LayerTopoSupplement>();
ASSERT_NO_THROW({ std::ignore = BRepGraphInc_Populate::Perform(aGraph, aSolid, false); });
ASSERT_EQ(aGraph.Topo().Solids().Nb(), 1);
EXPECT_EQ(aGraph.Topo().Solids().Relations(BRepGraph_SolidId::Start()).ShellRefIds.Size(), 1);
const NCollection_LinearVector<uint64_t>& anAttached =
aSupplement->AttachedTo(BRepGraph_SolidId::Start());
ASSERT_EQ(anAttached.Size(), 1);
const BRepGraph_LayerTopoSupplement::Entry* anEntry = aSupplement->FindByUid(anAttached.First());
ASSERT_NE(anEntry, nullptr);
EXPECT_EQ(anEntry->Kind, BRepGraph_LayerTopoSupplement::AttachmentKind::SolidAuxShape);
EXPECT_EQ(anEntry->Shape.ShapeType(), TopAbs_SHELL);
EXPECT_EQ(anEntry->Shape.Orientation(), TopAbs_INTERNAL);
const TopoDS_Shape aRecon = BRepGraphInc_Reconstruct::Node(aGraph, BRepGraph_SolidId::Start());
ASSERT_FALSE(aRecon.IsNull());
EXPECT_EQ(countDirectChildren(aRecon, TopAbs_SHELL, TopAbs_FORWARD), 1);
EXPECT_EQ(countDirectChildren(aRecon, TopAbs_SHELL, TopAbs_INTERNAL), 1);
}
TEST(BRepGraphIncTest, Populate_CompSolidRoutesInternalSolidToSupplement)
{
BRep_Builder aBB;
TopoDS_CompSolid aCompSolid;
aBB.MakeCompSolid(aCompSolid);
aBB.Add(aCompSolid, makeIncPlainSolid().Oriented(TopAbs_FORWARD));
aBB.Add(aCompSolid, makeIncPlainSolid().Oriented(TopAbs_INTERNAL));
BRepGraph aGraph;
occ::handle<BRepGraph_LayerTopoSupplement> aSupplement =
aGraph.LayerRegistry().Ensure<BRepGraph_LayerTopoSupplement>();
ASSERT_NO_THROW({ std::ignore = BRepGraphInc_Populate::Perform(aGraph, aCompSolid, false); });
ASSERT_EQ(aGraph.Topo().CompSolids().Nb(), 1);
EXPECT_EQ(aGraph.Topo().CompSolids().Relations(BRepGraph_CompSolidId::Start()).SolidRefIds.Size(),
1);
const NCollection_LinearVector<uint64_t>& anAttached =
aSupplement->AttachedTo(BRepGraph_CompSolidId::Start());
ASSERT_EQ(anAttached.Size(), 1);
const BRepGraph_LayerTopoSupplement::Entry* anEntry = aSupplement->FindByUid(anAttached.First());
ASSERT_NE(anEntry, nullptr);
EXPECT_EQ(anEntry->Kind, BRepGraph_LayerTopoSupplement::AttachmentKind::CompSolidAuxShape);
EXPECT_EQ(anEntry->Shape.ShapeType(), TopAbs_SOLID);
EXPECT_EQ(anEntry->Shape.Orientation(), TopAbs_INTERNAL);
const TopoDS_Shape aRecon =
BRepGraphInc_Reconstruct::Node(aGraph, BRepGraph_CompSolidId::Start());
ASSERT_FALSE(aRecon.IsNull());
EXPECT_EQ(countDirectChildren(aRecon, TopAbs_SOLID, TopAbs_FORWARD), 1);
EXPECT_EQ(countDirectChildren(aRecon, TopAbs_SOLID, TopAbs_INTERNAL), 1);
}
TEST(BRepGraphIncTest, Populate_SolidInvalidOrderChildRoutesToSupplement)
{
BRep_Builder aBB;
TopoDS_Solid aSolid;
aBB.MakeSolid(aSolid);
aBB.Add(aSolid, makeIncEdgeWithInternalVertex().Oriented(TopAbs_FORWARD));
BRepGraph aGraph;
occ::handle<BRepGraph_LayerTopoSupplement> aSupplement =
aGraph.LayerRegistry().Ensure<BRepGraph_LayerTopoSupplement>();
std::ignore = BRepGraphInc_Populate::Perform(aGraph, aSolid, false);
ASSERT_EQ(aGraph.Topo().Solids().Nb(), 1);
EXPECT_EQ(aGraph.Topo().Solids().Relations(BRepGraph_SolidId::Start()).ShellRefIds.Size(), 0);
ASSERT_EQ(aSupplement->AttachedTo(BRepGraph_SolidId::Start()).Size(), 1);
const BRepGraph_LayerTopoSupplement::Entry* anEntry =
aSupplement->FindByUid(aSupplement->AttachedTo(BRepGraph_SolidId::Start()).First());
ASSERT_NE(anEntry, nullptr);
EXPECT_EQ(anEntry->Kind, BRepGraph_LayerTopoSupplement::AttachmentKind::SolidAuxShape);
EXPECT_EQ(anEntry->Shape.ShapeType(), TopAbs_EDGE);
const TopoDS_Shape aRecon = BRepGraphInc_Reconstruct::Node(aGraph, BRepGraph_SolidId::Start());
EXPECT_EQ(countDirectChildren(aRecon, TopAbs_EDGE, TopAbs_FORWARD), 1);
}
// ============================================================
// Reverse-index hardening: compound atomic children (Wire/Edge/Vertex)
// ============================================================
TEST(BRepGraphIncTest, Relations_Validate_CompoundWithAtomicWire)
{
// Build a compound that contains only a wire (no solid/shell/face).
// This exercises the Wire-child path in compound relation-table maintenance.
BRep_Builder aBB;
TopoDS_Compound aCompound;
aBB.MakeCompound(aCompound);
BRepBuilderAPI_MakeEdge aME(gp_Pnt(0, 0, 0), gp_Pnt(10, 0, 0));
ASSERT_TRUE(aME.IsDone());
TopoDS_Wire aWire;
aBB.MakeWire(aWire);
aBB.Add(aWire, aME.Edge());
aBB.Add(aCompound, aWire);
BRepGraph aGraph;
std::ignore = BRepGraphInc_Populate::Perform(aGraph, aCompound, false);
ASSERT_FALSE(aGraph.IsEmpty());
EXPECT_EQ(aGraph.Topo().Compounds().Nb(), 1u);
ASSERT_GE(aGraph.Topo().Wires().Nb(), 1u);
// Full consistency check.
EXPECT_TRUE(
BRepGraph_Validate::Perform(aGraph, BRepGraph_Validate::Options::Lightweight()).IsValid());
}
TEST(BRepGraphIncTest, Relations_Validate_CompoundWithAtomicEdge)
{
// Build a compound containing only an edge (no wire/face/solid).
BRep_Builder aBB;
TopoDS_Compound aCompound;
aBB.MakeCompound(aCompound);
BRepBuilderAPI_MakeEdge aME(gp_Pnt(0, 0, 0), gp_Pnt(5, 0, 0));
ASSERT_TRUE(aME.IsDone());
aBB.Add(aCompound, aME.Edge());
BRepGraph aGraph;
std::ignore = BRepGraphInc_Populate::Perform(aGraph, aCompound, false);
ASSERT_FALSE(aGraph.IsEmpty());
EXPECT_EQ(aGraph.Topo().Compounds().Nb(), 1u);
ASSERT_GE(aGraph.Topo().Edges().Nb(), 1u);
EXPECT_TRUE(
BRepGraph_Validate::Perform(aGraph, BRepGraph_Validate::Options::Lightweight()).IsValid());
}
TEST(BRepGraphIncTest, Relations_Validate_CompoundWithAtomicVertex)
{
// Build a compound containing only a vertex.
BRep_Builder aBB;
TopoDS_Compound aCompound;
aBB.MakeCompound(aCompound);
TopoDS_Vertex aVtx;
aBB.MakeVertex(aVtx, gp_Pnt(1, 2, 3), Precision::Confusion());
aBB.Add(aCompound, aVtx);
BRepGraph aGraph;
std::ignore = BRepGraphInc_Populate::Perform(aGraph, aCompound, false);
ASSERT_FALSE(aGraph.IsEmpty());
EXPECT_EQ(aGraph.Topo().Compounds().Nb(), 1u);
ASSERT_GE(aGraph.Topo().Vertices().Nb(), 1u);
EXPECT_TRUE(
BRepGraph_Validate::Perform(aGraph, BRepGraph_Validate::Options::Lightweight()).IsValid());
}
TEST(BRepGraphIncTest, Relations_CoEdgeToWire_IsPopulated)
{
// Every coedge in a box must carry exactly one parent wire.
BRepPrimAPI_MakeBox aBoxMaker(5.0, 5.0, 5.0);
const TopoDS_Shape& aBox = aBoxMaker.Shape();
BRepGraph aGraph;
std::ignore = BRepGraphInc_Populate::Perform(aGraph, aBox, false);
ASSERT_FALSE(aGraph.IsEmpty());
const uint32_t aNbCoEdges = aGraph.Topo().CoEdges().Nb();
ASSERT_GT(aNbCoEdges, 0);
for (BRepGraph_CoEdgeId aCoEdgeId(0); aCoEdgeId.IsValid(aNbCoEdges); ++aCoEdgeId)
{
EXPECT_TRUE(aGraph.Topo().CoEdges().Definition(aCoEdgeId).ParentWireId.IsValid())
<< "CoEdge " << aCoEdgeId.Index << " not in any wire";
}
EXPECT_TRUE(
BRepGraph_Validate::Perform(aGraph, BRepGraph_Validate::Options::Lightweight()).IsValid());
}
TEST(BRepGraphIncTest, Relations_CompSolid_IncomingMaintained_AfterBuild)
{
// Build a TopoDS_CompSolid containing two boxes and verify:
// 1. ParentSolidRefIds is populated for both solids
// 2. ValidateRelations() passes
BRep_Builder aBB;
TopoDS_CompSolid aCompSolid;
aBB.MakeCompSolid(aCompSolid);
BRepPrimAPI_MakeBox aBoxMaker1(4.0, 4.0, 4.0);
BRepPrimAPI_MakeBox aBoxMaker2(2.0, 2.0, 2.0);
aBB.Add(aCompSolid, aBoxMaker1.Shape());
aBB.Add(aCompSolid, aBoxMaker2.Shape());
BRepGraph aGraph;
std::ignore = BRepGraphInc_Populate::Perform(aGraph, aCompSolid, false);
ASSERT_FALSE(aGraph.IsEmpty());
EXPECT_EQ(aGraph.Topo().CompSolids().Nb(), 1);
ASSERT_GE(aGraph.Topo().Solids().Nb(), 2);
// Both solids must appear in the parent solid-ref relation table.
for (BRepGraph_SolidId aSolidId = BRepGraph_SolidId::Start();
aSolidId.IsValid(aGraph.Topo().Solids().Nb());
++aSolidId)
{
const NCollection_LinearVector<BRepGraph_SolidRefId>& aSolidRefs =
aGraph.Topo().Solids().Relations(aSolidId).ParentSolidRefIds;
ASSERT_EQ(aSolidRefs.Size(), 1u) << "Solid " << aSolidId.Index << " not in any CompSolid";
}
EXPECT_TRUE(
BRepGraph_Validate::Perform(aGraph, BRepGraph_Validate::Options::Lightweight()).IsValid());
}
TEST(BRepGraphIncTest, Relations_CompSolid_IncomingMaintained_AfterBuildDelta)
{
// Verify the relation build correctly indexes incoming solid refs when
// both the compsolid and solids are appended together.
BRep_Builder aBB;
TopoDS_CompSolid aCompSolid;
aBB.MakeCompSolid(aCompSolid);
BRepPrimAPI_MakeBox aBoxMaker(3.0, 3.0, 3.0);
aBB.Add(aCompSolid, aBoxMaker.Shape());
// First, build an empty storage then delta-populate.
// Simplest: just populate from scratch and check ValidateRelations.
BRepGraph aGraph;
std::ignore = BRepGraphInc_Populate::Perform(aGraph, aCompSolid, false);
ASSERT_FALSE(aGraph.IsEmpty());
EXPECT_EQ(aGraph.Topo().CompSolids().Nb(), 1);
ASSERT_GE(aGraph.Topo().Solids().Nb(), 1);
EXPECT_TRUE(
BRepGraph_Validate::Perform(aGraph, BRepGraph_Validate::Options::Lightweight()).IsValid());
const NCollection_LinearVector<BRepGraph_SolidRefId>& aSolidRefs =
aGraph.Topo().Solids().Relations(BRepGraph_SolidId::Start()).ParentSolidRefIds;
EXPECT_GE(aSolidRefs.Size(), 1u) << "Solid 0 should be indexed in CompSolid relations";
}
TEST(BRepGraphIncTest, Relations_Validate_Box_FullConsistency)
{
// Smoke test: a simple solid box must pass full relation-table validation.
BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0);
const TopoDS_Shape& aBox = aBoxMaker.Shape();
BRepGraph aGraph;
std::ignore = BRepGraphInc_Populate::Perform(aGraph, aBox, false);
ASSERT_FALSE(aGraph.IsEmpty());
EXPECT_TRUE(
BRepGraph_Validate::Perform(aGraph, BRepGraph_Validate::Options::Lightweight()).IsValid());
}
TEST(BRepGraphIncTest, Relations_Validate_Compound_FullConsistency)
{
// A compound of two boxes must pass full relation-table validation,
// covering CompoundsOfSolid, CompoundsOfShell, CompoundsOfFace.
BRep_Builder aBB;
TopoDS_Compound aCompound;
aBB.MakeCompound(aCompound);
BRepPrimAPI_MakeBox aBoxMaker1(5.0, 5.0, 5.0);
BRepPrimAPI_MakeBox aBoxMaker2(3.0, 3.0, 3.0);
aBB.Add(aCompound, aBoxMaker1.Shape());
aBB.Add(aCompound, aBoxMaker2.Shape());
BRepGraph aGraph;
std::ignore = BRepGraphInc_Populate::Perform(aGraph, aCompound, false);
ASSERT_FALSE(aGraph.IsEmpty());
EXPECT_TRUE(
BRepGraph_Validate::Perform(aGraph, BRepGraph_Validate::Options::Lightweight()).IsValid());
}
TEST(BRepGraphIncTest, Relations_AfterEditorMutations_StaysConsistent)
{
// Verify the incremental Bind/Unbind mutation path keeps the relation tables
// consistent with the forward entity / reference-entry tables across a
// sequence of RemoveWire / RemoveFace / RemoveShell mutations.
BRep_Builder aBB;
TopoDS_Compound aCompound;
aBB.MakeCompound(aCompound);
BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0);
BRepPrimAPI_MakeCylinder aCylMaker(5.0, 12.0);
aBB.Add(aCompound, aBoxMaker.Shape());
aBB.Add(aCompound, aCylMaker.Shape());
BRepGraph aGraph;
aGraph.Clear();
[[maybe_unused]] const BRepGraph::ShapesView::Result aBuildRes = aGraph.Shapes().Add(aCompound);
ASSERT_FALSE(aGraph.IsEmpty());
ASSERT_TRUE(aGraph.ValidateRelations());
// Remove an inner wire from the first face that owns more than one wire
// (or the only wire if all faces have a single wire).
for (BRepGraph_FaceIterator aFaceIt(aGraph); aFaceIt.More(); aFaceIt.Next())
{
const BRepGraph_FaceId aFaceId = aFaceIt.CurrentId();
const NCollection_LinearVector<BRepGraph_WireRefId>& aWireRefs =
BRepGraph_TestTools::WireRefsOfFace(aGraph, aFaceId);
if (aWireRefs.IsEmpty())
{
continue;
}
ASSERT_TRUE(aGraph.Editor().Faces().RemoveWire(aFaceId, aWireRefs.Value(0)));
break;
}
EXPECT_TRUE(aGraph.ValidateRelations()) << "Relation table inconsistent after RemoveWire";
// Remove the first face from the first shell.
const NCollection_LinearVector<BRepGraph_FaceRefId>& aFaceRefs =
BRepGraph_TestTools::FaceRefsOfShell(aGraph, BRepGraph_ShellId::Start());
ASSERT_GE(aFaceRefs.Size(), 1);
ASSERT_TRUE(aGraph.Editor().Shells().RemoveFace(BRepGraph_ShellId::Start(), aFaceRefs.Value(0)));
EXPECT_TRUE(aGraph.ValidateRelations()) << "Relation table inconsistent after RemoveFace";
// Remove the first shell from the first solid.
const NCollection_LinearVector<BRepGraph_ShellRefId>& aShellRefs =
BRepGraph_TestTools::ShellRefsOfSolid(aGraph, BRepGraph_SolidId::Start());
ASSERT_GE(aShellRefs.Size(), 1);
ASSERT_TRUE(
aGraph.Editor().Solids().RemoveShell(BRepGraph_SolidId::Start(), aShellRefs.Value(0)));
EXPECT_TRUE(aGraph.ValidateRelations()) << "Relation table inconsistent after RemoveShell";
EXPECT_TRUE(aGraph.Editor().ValidateMutationBoundary());
}
TEST(BRepGraphIncTest, Relations_BulkBuild_TwiceProducesEqualState)
{
// Bulk Populate must be deterministic: building the same shape twice into
// independent storages must yield byte-equal relation-table views as observed
// through the public per-entity accessors.
BRepPrimAPI_MakeBox aBoxMaker(7.0, 11.0, 13.0);
const TopoDS_Shape& aBox = aBoxMaker.Shape();
BRepGraph aGraphA;
BRepGraph aGraphB;
std::ignore = BRepGraphInc_Populate::Perform(aGraphA, aBox, false);
std::ignore = BRepGraphInc_Populate::Perform(aGraphB, aBox, false);
ASSERT_FALSE(aGraphA.IsEmpty());
ASSERT_FALSE(aGraphB.IsEmpty());
ASSERT_EQ(aGraphA.Topo().Edges().Nb(), aGraphB.Topo().Edges().Nb());
for (uint32_t anIdx = 0; anIdx < aGraphA.Topo().Edges().Nb(); ++anIdx)
{
const BRepGraph_EdgeId anEdgeId(anIdx);
BRepGraph_WiresOfEdge aWireItA = aGraphA.Topo().Edges().WiresOf(anEdgeId);
BRepGraph_WiresOfEdge aWireItB = aGraphB.Topo().Edges().WiresOf(anEdgeId);
for (; aWireItA.More() && aWireItB.More(); aWireItA.Next(), aWireItB.Next())
{
EXPECT_EQ(aWireItA.CurrentId(), aWireItB.CurrentId());
}
EXPECT_EQ(aWireItA.More(), aWireItB.More());
BRepGraph_FacesOfEdge aFaceItA = aGraphA.Topo().Edges().FacesOf(anEdgeId);
BRepGraph_FacesOfEdge aFaceItB = aGraphB.Topo().Edges().FacesOf(anEdgeId);
for (; aFaceItA.More() && aFaceItB.More(); aFaceItA.Next(), aFaceItB.Next())
{
EXPECT_EQ(aFaceItA.CurrentId(), aFaceItB.CurrentId());
}
EXPECT_EQ(aFaceItA.More(), aFaceItB.More());
}
}
TEST(BRepGraphIncTest, Relations_EdgeOpsAdd_BindsStartEndVertices)
{
// Free edge created at runtime via the Editor must show up under its endpoint
// vertices in the relation tables. Pre-fix this query returned an empty list.
BRepGraph aGraph;
aGraph.Clear();
const BRepGraph_VertexId aV0 = aGraph.Editor().Vertices().Add(gp_Pnt(0, 0, 0), 1.e-7);
const BRepGraph_VertexId aV1 = aGraph.Editor().Vertices().Add(gp_Pnt(1, 0, 0), 1.e-7);
ASSERT_TRUE(aV0.IsValid());
ASSERT_TRUE(aV1.IsValid());
const BRepGraph_EdgeId anEdge =
aGraph.Editor().Edges().Add(aV0, aV1, occ::handle<Geom_Curve>(), 0.0, 1.0, 1.e-7);
ASSERT_TRUE(anEdge.IsValid());
bool foundV0 = false, foundV1 = false;
for (const BRepGraph_EdgeId& aE : aGraph.Topo().Vertices().Edges(aV0))
{
if (aE == anEdge)
{
foundV0 = true;
}
}
for (const BRepGraph_EdgeId& aE : aGraph.Topo().Vertices().Edges(aV1))
{
if (aE == anEdge)
{
foundV1 = true;
}
}
EXPECT_TRUE(foundV0);
EXPECT_TRUE(foundV1);
EXPECT_TRUE(aGraph.ValidateRelations());
}
TEST(BRepGraphIncTest, Relations_RemoveEdge_UnbindsStartEndVertices)
{
// Symmetric to the Add test: removing the edge must drop the entries.
BRepGraph aGraph;
aGraph.Clear();
const BRepGraph_VertexId aV0 = aGraph.Editor().Vertices().Add(gp_Pnt(0, 0, 0), 1.e-7);
const BRepGraph_VertexId aV1 = aGraph.Editor().Vertices().Add(gp_Pnt(1, 0, 0), 1.e-7);
const BRepGraph_EdgeId anEdge =
aGraph.Editor().Edges().Add(aV0, aV1, occ::handle<Geom_Curve>(), 0.0, 1.0, 1.e-7);
ASSERT_TRUE(anEdge.IsValid());
aGraph.Editor().Gen().RemoveNode(BRepGraph_NodeId(anEdge));
for (const BRepGraph_EdgeId& aE : aGraph.Topo().Vertices().Edges(aV0))
{
EXPECT_NE(aE, anEdge);
}
for (const BRepGraph_EdgeId& aE : aGraph.Topo().Vertices().Edges(aV1))
{
EXPECT_NE(aE, anEdge);
}
EXPECT_TRUE(aGraph.ValidateRelations());
}
TEST(BRepGraphIncTest, Relations_SetRefChildVertexId_RebindsVertexToEdges)
{
// Rewire an edge's start-vertex ref to a different vertex and verify the
// relation moved.
BRepGraph aGraph;
aGraph.Clear();
const BRepGraph_VertexId aV0 = aGraph.Editor().Vertices().Add(gp_Pnt(0, 0, 0), 1.e-7);
const BRepGraph_VertexId aV1 = aGraph.Editor().Vertices().Add(gp_Pnt(1, 0, 0), 1.e-7);
const BRepGraph_VertexId aV2 = aGraph.Editor().Vertices().Add(gp_Pnt(2, 0, 0), 1.e-7);
const BRepGraph_EdgeId anEdge =
aGraph.Editor().Edges().Add(aV0, aV1, occ::handle<Geom_Curve>(), 0.0, 1.0, 1.e-7);
ASSERT_TRUE(anEdge.IsValid());
const BRepGraph_VertexRefId aStartRef = aGraph.Topo().Edges().Definition(anEdge).StartVertexRefId;
aGraph.Editor().Vertices().SetRefChildVertexId(aStartRef, aV2);
bool stillUnderV0 = false, foundUnderV2 = false;
for (const BRepGraph_EdgeId& aE : aGraph.Topo().Vertices().Edges(aV0))
{
if (aE == anEdge)
{
stillUnderV0 = true;
}
}
for (const BRepGraph_EdgeId& aE : aGraph.Topo().Vertices().Edges(aV2))
{
if (aE == anEdge)
{
foundUnderV2 = true;
}
}
EXPECT_FALSE(stillUnderV0);
EXPECT_TRUE(foundUnderV2);
EXPECT_TRUE(aGraph.ValidateRelations());
}
TEST(BRepGraphIncTest, Relations_BoxThroughCompact_StaysConsistent)
{
BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0);
BRepGraph aGraph;
aGraph.Clear();
[[maybe_unused]] const BRepGraph::ShapesView::Result aBuildRes =
aGraph.Shapes().Add(aBoxMaker.Shape());
ASSERT_FALSE(aGraph.IsEmpty());
ASSERT_TRUE(aGraph.Editor().Faces().RemoveWire(
BRepGraph_FaceId::Start(),
BRepGraph_TestTools::WireRefsOfFace(aGraph, BRepGraph_FaceId::Start()).Value(0)));
EXPECT_TRUE(aGraph.ValidateRelations());
[[maybe_unused]] const BRepGraph_Compact::Result aCompactRes = BRepGraph_Compact::Perform(aGraph);
EXPECT_TRUE(aGraph.ValidateRelations());
}
TEST(BRepGraphIncTest, Relations_SetRefChildWireId_RebindsWireToFaces)
{
// Add two faces; rewire face0's outer-wire ref to face1's outer wire and
// verify WireToFaces moved entries.
BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0);
BRepGraph aGraph;
aGraph.Clear();
[[maybe_unused]] const BRepGraph::ShapesView::Result aBuildRes =
aGraph.Shapes().Add(aBoxMaker.Shape());
ASSERT_FALSE(aGraph.IsEmpty());
const BRepGraph_FaceId aFace0 = BRepGraph_FaceId::Start();
const BRepGraph_FaceId aFace1(1);
ASSERT_TRUE(aFace1.IsValid(aGraph.Topo().Faces().Nb()));
const BRepGraph_WireRefId aWireRef0 =
BRepGraph_TestTools::WireRefsOfFace(aGraph, aFace0).Value(0);
const BRepGraph_WireId aOldWire = aGraph.Refs().Wires().Entry(aWireRef0).ChildWireId;
const BRepGraph_WireId aNewWire =
aGraph.Refs()
.Wires()
.Entry(BRepGraph_TestTools::WireRefsOfFace(aGraph, aFace1).Value(0))
.ChildWireId;
ASSERT_NE(aOldWire, aNewWire);
aGraph.Editor().Wires().SetRefChildWireId(aWireRef0, aNewWire);
bool oldStillBound = false, newBound = false;
for (const BRepGraph_FaceId& f :
BRepGraph_FacesOfWire(aGraph, aGraph.Topo().Wires().Relations(aOldWire).ParentWireRefIds))
{
if (f == aFace0)
{
oldStillBound = true;
}
}
for (const BRepGraph_FaceId& f :
BRepGraph_FacesOfWire(aGraph, aGraph.Topo().Wires().Relations(aNewWire).ParentWireRefIds))
{
if (f == aFace0)
{
newBound = true;
}
}
EXPECT_FALSE(oldStillBound);
EXPECT_TRUE(newBound);
for (const BRepGraph_CoEdgeId& aCoEdgeId : aGraph.Topo().Wires().Relations(aOldWire).CoEdgeIds)
{
const BRepGraphInc::CoEdgeDef& aCoEdge = aGraph.Topo().CoEdges().Definition(aCoEdgeId);
EXPECT_NE(aCoEdge.FaceId, aFace0);
for (const BRepGraph_FaceId& aFaceId : aGraph.Topo().Edges().FacesOf(aCoEdge.ChildEdgeId))
{
EXPECT_NE(aFaceId, aFace0);
}
}
EXPECT_TRUE(aGraph.ValidateRelations());
}
TEST(BRepGraphIncTest, Relations_SetRefFaceId_RebindsFaceToShells)
{
BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0);
BRepGraph aGraph;
aGraph.Clear();
[[maybe_unused]] const BRepGraph::ShapesView::Result aBuildRes =
aGraph.Shapes().Add(aBoxMaker.Shape());
ASSERT_FALSE(aGraph.IsEmpty());
const BRepGraph_ShellId aShell = BRepGraph_ShellId::Start();
const BRepGraph_FaceRefId aRef0 = BRepGraph_TestTools::FaceRefsOfShell(aGraph, aShell).Value(0);
const BRepGraph_FaceId aOldFace = aGraph.Refs().Faces().Entry(aRef0).ChildFaceId;
const BRepGraph_FaceId aNewFace(1);
ASSERT_NE(aOldFace, aNewFace);
aGraph.Editor().Faces().SetRefFaceId(aRef0, aNewFace);
// Old face still has the OTHER shell-ref pointing at it; just check the
// Old/New relation makes sense relative to this single ref.
EXPECT_TRUE(aGraph.ValidateRelations());
}
TEST(BRepGraphIncTest, Relations_SetRefChildShellId_RebindsShellToSolids)
{
BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0);
BRepGraph aGraph;
aGraph.Clear();
[[maybe_unused]] const BRepGraph::ShapesView::Result aBuildRes =
aGraph.Shapes().Add(aBoxMaker.Shape());
ASSERT_FALSE(aGraph.IsEmpty());
ASSERT_GE(aGraph.Topo().Shells().Nb(), 1);
// Box has 1 solid, 1 shell. Rewiring the lone shell-ref to itself is a no-op,
// so just verify Validate after a no-op call (proves equality short-circuit).
const BRepGraph_ShellRefId aRef0 =
BRepGraph_TestTools::ShellRefsOfSolid(aGraph, BRepGraph_SolidId::Start()).Value(0);
const BRepGraph_ShellId aShell = aGraph.Refs().Shells().Entry(aRef0).ChildShellId;
aGraph.Editor().Shells().SetRefChildShellId(aRef0, aShell);
EXPECT_TRUE(aGraph.ValidateRelations());
}
TEST(BRepGraphIncTest, Relations_SetChildEdgeIdOnCoEdge_RebindsEdgeToCoEdges)
{
// Pick a coedge that lives in a wire; redirect its child edge to a different
// existing edge and confirm Edge->CoEdges, Edge->Wires, Edge->Faces all move.
BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0);
BRepGraph aGraph;
aGraph.Clear();
[[maybe_unused]] const BRepGraph::ShapesView::Result aBuildRes =
aGraph.Shapes().Add(aBoxMaker.Shape());
ASSERT_FALSE(aGraph.IsEmpty());
ASSERT_GE(aGraph.Topo().CoEdges().Nb(), 2);
const BRepGraph_CoEdgeId aCoEdge = BRepGraph_CoEdgeId::Start();
const BRepGraphInc::CoEdgeDef& aDef = aGraph.Topo().CoEdges().Definition(aCoEdge);
const BRepGraph_EdgeId anOldEdge = aDef.ChildEdgeId;
// Pick any other valid edge as the target.
BRepGraph_EdgeId aNewEdge;
for (BRepGraph_EdgeId aE(0); aE.IsValid(aGraph.Topo().Edges().Nb()); ++aE)
{
if (aE != anOldEdge)
{
aNewEdge = aE;
break;
}
}
ASSERT_TRUE(aNewEdge.IsValid());
aGraph.Editor().CoEdges().SetChildEdgeId(aCoEdge, aNewEdge);
bool oldHasCoEdge = false, newHasCoEdge = false;
for (const BRepGraph_CoEdgeId& aC : aGraph.Topo().Edges().CoEdges(anOldEdge))
{
if (aC == aCoEdge)
{
oldHasCoEdge = true;
}
}
for (const BRepGraph_CoEdgeId& aC : aGraph.Topo().Edges().CoEdges(aNewEdge))
{
if (aC == aCoEdge)
{
newHasCoEdge = true;
}
}
EXPECT_FALSE(oldHasCoEdge);
EXPECT_TRUE(newHasCoEdge);
EXPECT_TRUE(aGraph.ValidateRelations());
}
TEST(BRepGraphIncTest, Relations_SetFaceIdOnCoEdge_LastBondCheck)
{
// Cylinder seam edges have TWO coedges on the SAME face. Dropping one
// coedge's FaceId must keep the (edge, face) pair bound via the other.
BRepPrimAPI_MakeCylinder aCylMaker(5.0, 10.0);
BRepGraph aGraph;
aGraph.Clear();
[[maybe_unused]] const BRepGraph::ShapesView::Result aBuildRes =
aGraph.Shapes().Add(aCylMaker.Shape());
ASSERT_FALSE(aGraph.IsEmpty());
// Locate a seam edge: an edge with two coedges on the same face.
BRepGraph_EdgeId aSeamEdge;
BRepGraph_FaceId aSeamFace;
BRepGraph_CoEdgeId aSeamCoEdge;
for (BRepGraph_EdgeId aE(0); aE.IsValid(aGraph.Topo().Edges().Nb()) && !aSeamEdge.IsValid(); ++aE)
{
const NCollection_LinearVector<BRepGraph_CoEdgeId>& aCEs = aGraph.Topo().Edges().CoEdges(aE);
if (aCEs.Size() < 2)
{
continue;
}
NCollection_DataMap<int, BRepGraph_CoEdgeId> aSeenFace;
for (const BRepGraph_CoEdgeId& aCE : aCEs)
{
const BRepGraphInc::CoEdgeDef& aD = aGraph.Topo().CoEdges().Definition(aCE);
if (!aD.FaceId.IsValid())
{
continue;
}
if (aSeenFace.IsBound(aD.FaceId.Index))
{
aSeamEdge = aE;
aSeamFace = aD.FaceId;
aSeamCoEdge = aCE;
break;
}
aSeenFace.Bind(aD.FaceId.Index, aCE);
}
}
ASSERT_TRUE(aSeamEdge.IsValid()) << "cylinder must have a seam edge";
aGraph.Editor().CoEdges().SetFaceId(aSeamCoEdge, BRepGraph_FaceId());
// The seam-pair partner of aSeamCoEdge still binds (aSeamEdge, aSeamFace).
bool stillBound = false;
for (const BRepGraph_FaceId& f : aGraph.Topo().Edges().FacesOf(aSeamEdge))
{
if (f == aSeamFace)
{
stillBound = true;
}
}
EXPECT_TRUE(stillBound);
EXPECT_TRUE(aGraph.ValidateRelations());
}
TEST(BRepGraphIncTest, Relations_SetFaceIdOnCoEdge_OnlyBondUnbinds)
{
// Box edges have one coedge per face. Setting that single coedge's FaceId
// to invalid MUST unbind (edge,face) from EdgeToFaces.
BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0);
BRepGraph aGraph;
aGraph.Clear();
[[maybe_unused]] const BRepGraph::ShapesView::Result aBuildRes =
aGraph.Shapes().Add(aBoxMaker.Shape());
ASSERT_FALSE(aGraph.IsEmpty());
const BRepGraph_CoEdgeId aCoEdge = BRepGraph_CoEdgeId::Start();
const BRepGraphInc::CoEdgeDef& aDef = aGraph.Topo().CoEdges().Definition(aCoEdge);
const BRepGraph_EdgeId anEdge = aDef.ChildEdgeId;
const BRepGraph_FaceId anOldFace = aDef.FaceId;
aGraph.Editor().CoEdges().SetFaceId(aCoEdge, BRepGraph_FaceId());
for (const BRepGraph_FaceId& f : aGraph.Topo().Edges().FacesOf(anEdge))
{
EXPECT_NE(f, anOldFace);
}
EXPECT_TRUE(aGraph.ValidateRelations());
}
TEST(BRepGraphIncTest, Relations_RemoveWireFromSharedWireClearsDetachedFaceContext)
{
BRepGraph aGraph;
aGraph.Clear();
const BRepGraph_VertexId aV0 = aGraph.Editor().Vertices().Add(gp_Pnt(0.0, 0.0, 0.0), 1.e-7);
const BRepGraph_VertexId aV1 = aGraph.Editor().Vertices().Add(gp_Pnt(1.0, 0.0, 0.0), 1.e-7);
const BRepGraph_EdgeId anEdge =
aGraph.Editor().Edges().Add(aV0, aV1, occ::handle<Geom_Curve>(), 0.0, 1.0, 1.e-7);
ASSERT_TRUE(anEdge.IsValid());
const BRepGraph_CoEdgeId aCoEdge = aGraph.Editor().CoEdges().Add(anEdge, TopAbs_FORWARD);
ASSERT_TRUE(aCoEdge.IsValid());
NCollection_LinearVector<BRepGraph_CoEdgeId> aCoEdges;
aCoEdges.Append(aCoEdge);
const BRepGraph_WireId aWire = aGraph.Editor().Wires().Add(aCoEdges.ToArray1());
ASSERT_TRUE(aWire.IsValid());
NCollection_LinearVector<BRepGraph_WireId> anInnerWires;
const occ::handle<Geom_Plane> aPlane = new Geom_Plane(gp_Pln());
const BRepGraph_FaceId aFace0 =
aGraph.Editor().Faces().Add(aPlane, aWire, anInnerWires.ToArray1(), 1.e-7);
const BRepGraph_FaceId aFace1 =
aGraph.Editor().Faces().Add(aPlane, aWire, anInnerWires.ToArray1(), 1.e-7);
ASSERT_TRUE(aFace0.IsValid());
ASSERT_TRUE(aFace1.IsValid());
aGraph.Editor().CoEdges().SetFaceId(aCoEdge, aFace0);
aGraph.Editor().CoEdges().SetPCurve(aCoEdge,
new Geom2d_Line(gp_Pnt2d(0.0, 0.0), gp_Dir2d(1.0, 0.0)),
0.0,
1.0);
const BRepGraph_CoEdgeCurve2DRepId aPCurveRepId =
aGraph.Topo().CoEdges().Definition(aCoEdge).Curve2DRepId;
ASSERT_TRUE(aPCurveRepId.IsValid());
const uint32_t aNbActivePCurves = aGraph.Topo().Geometry().NbActiveCoEdgeCurves2D();
ASSERT_TRUE(aGraph.ValidateRelations());
const BRepGraph_WireRefId aFace0WireRef =
aGraph.Topo().Faces().Relations(aFace0).WireRefIds.First();
ASSERT_TRUE(aFace0WireRef.IsValid());
EXPECT_TRUE(aGraph.Editor().Faces().RemoveWire(aFace0, aFace0WireRef));
EXPECT_FALSE(aGraph.Topo().CoEdges().Definition(aCoEdge).FaceId.IsValid())
<< "Removing the face's wire usage must clear coedge face context even if another face still "
"uses the same wire";
EXPECT_FALSE(aGraph.Topo().CoEdges().Definition(aCoEdge).Curve2DRepId.IsValid());
EXPECT_EQ(aGraph.Topo().Geometry().NbActiveCoEdgeCurves2D(), aNbActivePCurves - 1u);
for (const BRepGraph_FaceId& aFaceId : aGraph.Topo().Edges().FacesOf(anEdge))
{
EXPECT_NE(aFaceId, aFace0);
}
EXPECT_TRUE(aGraph.ValidateRelations());
}
TEST(BRepGraphIncTest, Relations_ValidateRejectsLivePCurveWithoutFaceContext)
{
BRepGraph aGraph;
aGraph.Clear();
const BRepGraph_VertexId aV0 = aGraph.Editor().Vertices().Add(gp_Pnt(0.0, 0.0, 0.0), 1.e-7);
const BRepGraph_VertexId aV1 = aGraph.Editor().Vertices().Add(gp_Pnt(1.0, 0.0, 0.0), 1.e-7);
const BRepGraph_EdgeId anEdge =
aGraph.Editor().Edges().Add(aV0, aV1, occ::handle<Geom_Curve>(), 0.0, 1.0, 1.e-7);
ASSERT_TRUE(anEdge.IsValid());
const BRepGraph_CoEdgeId aCoEdge = aGraph.Editor().CoEdges().Add(anEdge, TopAbs_FORWARD);
ASSERT_TRUE(aCoEdge.IsValid());
NCollection_LinearVector<BRepGraph_CoEdgeId> aCoEdges;
aCoEdges.Append(aCoEdge);
const BRepGraph_WireId aWire = aGraph.Editor().Wires().Add(aCoEdges.ToArray1());
ASSERT_TRUE(aWire.IsValid());
NCollection_LinearVector<BRepGraph_WireId> anInnerWires;
const BRepGraph_FaceId aFace =
aGraph.Editor().Faces().Add(new Geom_Plane(gp_Pln()), aWire, anInnerWires.ToArray1(), 1.e-7);
ASSERT_TRUE(aFace.IsValid());
aGraph.Editor().CoEdges().SetFaceId(aCoEdge, aFace);
aGraph.Editor().CoEdges().SetPCurve(aCoEdge,
new Geom2d_Line(gp_Pnt2d(0.0, 0.0), gp_Dir2d(1.0, 0.0)),
0.0,
1.0);
ASSERT_TRUE(aGraph.Topo().CoEdges().Definition(aCoEdge).Curve2DRepId.IsValid());
ASSERT_TRUE(aGraph.ValidateRelations());
{
BRepGraph_MutGuard<BRepGraphInc::CoEdgeDef> aMutCoEdge = aGraph.Editor().CoEdges().Mut(aCoEdge);
aMutCoEdge.Internal().FaceId = BRepGraph_FaceId();
}
EXPECT_FALSE(aGraph.ValidateRelations())
<< "A live face-scoped coedge pcurve without a face context is an orphaned relation";
}
TEST(BRepGraphIncTest, Relations_SetEndVertexRefId_RebindsVertexToEdges)
{
BRepGraph aGraph;
aGraph.Clear();
const BRepGraph_VertexId aV0 = aGraph.Editor().Vertices().Add(gp_Pnt(0, 0, 0), 1.e-7);
const BRepGraph_VertexId aV1 = aGraph.Editor().Vertices().Add(gp_Pnt(1, 0, 0), 1.e-7);
const BRepGraph_VertexId aV2 = aGraph.Editor().Vertices().Add(gp_Pnt(2, 0, 0), 1.e-7);
const BRepGraph_EdgeId anEdge =
aGraph.Editor().Edges().Add(aV0, aV1, occ::handle<Geom_Curve>(), 0.0, 1.0, 1.e-7);
const BRepGraph_EdgeId anExtra =
aGraph.Editor().Edges().Add(aV0, aV2, occ::handle<Geom_Curve>(), 0.0, 1.0, 1.e-7);
ASSERT_TRUE(anEdge.IsValid());
ASSERT_TRUE(anExtra.IsValid());
// Reusing an already owned VertexRefId is invalid: refs are single-use edge
// slots, even when both edges should point to the same vertex definition.
const BRepGraph_VertexRefId aV2Ref = aGraph.Topo().Edges().Definition(anExtra).EndVertexRefId;
const BRepGraph_VertexRefId anOldEndRef = aGraph.Topo().Edges().Definition(anEdge).EndVertexRefId;
aGraph.Editor().Edges().SetEndVertexRefId(anEdge, aV2Ref);
bool stillUnderV1 = false, foundUnderV2 = false;
for (const BRepGraph_EdgeId& aE : aGraph.Topo().Vertices().Edges(aV1))
{
if (aE == anEdge)
{
stillUnderV1 = true;
}
}
for (const BRepGraph_EdgeId& aE : aGraph.Topo().Vertices().Edges(aV2))
{
if (aE == anEdge)
{
foundUnderV2 = true;
}
}
EXPECT_TRUE(stillUnderV1);
EXPECT_FALSE(foundUnderV2);
EXPECT_EQ(aGraph.Topo().Edges().Definition(anEdge).EndVertexRefId, anOldEndRef);
EXPECT_TRUE(aGraph.ValidateRelations());
}
TEST(BRepGraphIncTest, Relations_SetterIdempotency_NoOp)
{
// Identity assignments must be no-ops and not corrupt the relation tables.
BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0);
BRepGraph aGraph;
aGraph.Clear();
[[maybe_unused]] const BRepGraph::ShapesView::Result aBuildRes =
aGraph.Shapes().Add(aBoxMaker.Shape());
ASSERT_FALSE(aGraph.IsEmpty());
const BRepGraph_CoEdgeId aCoEdge = BRepGraph_CoEdgeId::Start();
const BRepGraphInc::CoEdgeDef& aDef = aGraph.Topo().CoEdges().Definition(aCoEdge);
aGraph.Editor().CoEdges().SetChildEdgeId(aCoEdge, aDef.ChildEdgeId);
aGraph.Editor().CoEdges().SetFaceId(aCoEdge, aDef.FaceId);
EXPECT_TRUE(aGraph.ValidateRelations());
const BRepGraph_VertexRefId aVRef =
aGraph.Topo().Edges().Definition(BRepGraph_EdgeId::Start()).StartVertexRefId;
if (aVRef.IsValid())
{
const BRepGraph_VertexId aV = aGraph.Refs().Vertices().Entry(aVRef).ChildVertexId;
aGraph.Editor().Vertices().SetRefChildVertexId(aVRef, aV);
EXPECT_TRUE(aGraph.ValidateRelations());
}
}
TEST(BRepGraphIncTest, Relations_RebindVertexSkipsRemovedParentEdge)
{
BRepGraphInc_Storage aStorage;
const BRepGraph_VertexId aOldVertex = aStorage.AppendVertex();
const BRepGraph_VertexId aEndVertex = aStorage.AppendVertex();
const BRepGraph_VertexId aNewVertex = aStorage.AppendVertex();
const BRepGraph_EdgeId anEdge = aStorage.AppendEdge();
const BRepGraph_VertexRefId aStartRef = aStorage.AppendVertexRef();
const BRepGraph_VertexRefId anEndRef = aStorage.AppendVertexRef();
aStorage.ChangeVertexRef(aStartRef).ParentEdgeId = anEdge;
aStorage.ChangeVertexRef(aStartRef).ChildVertexId = aOldVertex;
aStorage.ChangeVertexRef(anEndRef).ParentEdgeId = anEdge;
aStorage.ChangeVertexRef(anEndRef).ChildVertexId = aEndVertex;
aStorage.ChangeEdge(anEdge).StartVertexRefId = aStartRef;
aStorage.ChangeEdge(anEdge).EndVertexRefId = anEndRef;
aStorage.RebuildDerivedRelations();
ASSERT_EQ(aStorage.VertexRelations(aOldVertex).EdgeIds.Size(), 1u);
ASSERT_EQ(aStorage.VertexRelations(aNewVertex).EdgeIds.Size(), 0u);
aStorage.SetRemoved(anEdge, true);
aStorage.RebindVertexEdge(aOldVertex, aNewVertex, anEdge, BRepGraph_VertexRefId());
aStorage.RebindVertexRef(aStartRef, aOldVertex, aNewVertex);
EXPECT_EQ(aStorage.VertexRef(aStartRef).ChildVertexId, aOldVertex);
EXPECT_EQ(aStorage.VertexRelations(aOldVertex).EdgeIds.Size(), 1u);
EXPECT_EQ(aStorage.VertexRelations(aNewVertex).EdgeIds.Size(), 0u);
}
TEST(BRepGraphIncTest, CanonicalizeWireCoEdgeOrderStatus_ReordersExactConnectedWire)
{
BRepGraphInc_Storage aStorage;
const BRepGraph_WireId aWireId = aStorage.AppendWire();
const BRepGraph_VertexId aVertexA = addStorageVertex(aStorage, gp_Pnt(0.0, 0.0, 0.0), 1.0e-7);
const BRepGraph_VertexId aVertexB = addStorageVertex(aStorage, gp_Pnt(1.0, 0.0, 0.0), 1.0e-7);
const BRepGraph_VertexId aVertexC = addStorageVertex(aStorage, gp_Pnt(2.0, 0.0, 0.0), 1.0e-7);
const BRepGraph_EdgeId anEdgeBC = addStorageEdge(aStorage, aVertexB, aVertexC);
const BRepGraph_EdgeId anEdgeAB = addStorageEdge(aStorage, aVertexA, aVertexB);
const BRepGraph_CoEdgeId aCoEdgeBC =
aStorage.CreateCoEdgeUse(aWireId, anEdgeBC, BRepGraph_FaceId(), TopAbs_FORWARD);
const BRepGraph_CoEdgeId aCoEdgeAB =
aStorage.CreateCoEdgeUse(aWireId, anEdgeAB, BRepGraph_FaceId(), TopAbs_FORWARD);
EXPECT_FALSE(aStorage.ValidateWireCoEdgeOrder(aWireId));
EXPECT_EQ(aStorage.CanonicalizeWireCoEdgeOrderStatus(aWireId),
BRepGraphInc_Storage::WireCoEdgeOrderStatus::Reordered);
const NCollection_LinearVector<BRepGraph_CoEdgeId>& aCoEdges =
aStorage.WireRelations(aWireId).CoEdgeIds;
ASSERT_EQ(aCoEdges.Size(), 2u);
EXPECT_EQ(aCoEdges.Value(0), aCoEdgeAB);
EXPECT_EQ(aCoEdges.Value(1), aCoEdgeBC);
EXPECT_TRUE(aStorage.ValidateWireCoEdgeOrder(aWireId));
}
TEST(BRepGraphIncTest, CanonicalizeWireCoEdgeOrderStatus_UsesVertexTolerance)
{
BRepGraphInc_Storage aStorage;
const BRepGraph_WireId aWireId = aStorage.AppendWire();
const BRepGraph_VertexId aVertexA = addStorageVertex(aStorage, gp_Pnt(0.0, 0.0, 0.0), 1.0e-7);
const BRepGraph_VertexId aVertexB = addStorageVertex(aStorage, gp_Pnt(1.0, 0.0, 0.0), 1.0e-2);
const BRepGraph_VertexId aVertexC = addStorageVertex(aStorage, gp_Pnt(1.005, 0.0, 0.0), 1.0e-2);
const BRepGraph_VertexId aVertexD = addStorageVertex(aStorage, gp_Pnt(2.0, 0.0, 0.0), 1.0e-7);
const BRepGraph_EdgeId anEdgeAB = addStorageEdge(aStorage, aVertexA, aVertexB);
const BRepGraph_EdgeId anEdgeCD = addStorageEdge(aStorage, aVertexC, aVertexD);
const BRepGraph_CoEdgeId aCoEdgeAB =
aStorage.CreateCoEdgeUse(aWireId, anEdgeAB, BRepGraph_FaceId(), TopAbs_FORWARD);
const BRepGraph_CoEdgeId aCoEdgeCD =
aStorage.CreateCoEdgeUse(aWireId, anEdgeCD, BRepGraph_FaceId(), TopAbs_FORWARD);
EXPECT_FALSE(aStorage.ValidateWireCoEdgeOrder(aWireId));
EXPECT_EQ(aStorage.CanonicalizeWireCoEdgeOrderStatus(aWireId),
BRepGraphInc_Storage::WireCoEdgeOrderStatus::ToleranceOrdered);
const NCollection_LinearVector<BRepGraph_CoEdgeId>& aCoEdges =
aStorage.WireRelations(aWireId).CoEdgeIds;
ASSERT_EQ(aCoEdges.Size(), 2u);
EXPECT_EQ(aCoEdges.Value(0), aCoEdgeAB);
EXPECT_EQ(aCoEdges.Value(1), aCoEdgeCD);
}
TEST(BRepGraphIncTest, CanonicalizeWireCoEdgeOrderStatus_PartialPreservesDisconnectedRuns)
{
BRepGraphInc_Storage aStorage;
const BRepGraph_WireId aWireId = aStorage.AppendWire();
const BRepGraph_VertexId aVertexA = addStorageVertex(aStorage, gp_Pnt(0.0, 0.0, 0.0), 1.0e-7);
const BRepGraph_VertexId aVertexB = addStorageVertex(aStorage, gp_Pnt(1.0, 0.0, 0.0), 1.0e-7);
const BRepGraph_VertexId aVertexC = addStorageVertex(aStorage, gp_Pnt(2.0, 0.0, 0.0), 1.0e-7);
const BRepGraph_VertexId aVertexX = addStorageVertex(aStorage, gp_Pnt(10.0, 0.0, 0.0), 1.0e-7);
const BRepGraph_VertexId aVertexY = addStorageVertex(aStorage, gp_Pnt(11.0, 0.0, 0.0), 1.0e-7);
const BRepGraph_EdgeId anEdgeBC = addStorageEdge(aStorage, aVertexB, aVertexC);
const BRepGraph_EdgeId anEdgeXY = addStorageEdge(aStorage, aVertexX, aVertexY);
const BRepGraph_EdgeId anEdgeAB = addStorageEdge(aStorage, aVertexA, aVertexB);
const BRepGraph_CoEdgeId aCoEdgeBC =
aStorage.CreateCoEdgeUse(aWireId, anEdgeBC, BRepGraph_FaceId(), TopAbs_FORWARD);
const BRepGraph_CoEdgeId aCoEdgeXY =
aStorage.CreateCoEdgeUse(aWireId, anEdgeXY, BRepGraph_FaceId(), TopAbs_FORWARD);
const BRepGraph_CoEdgeId aCoEdgeAB =
aStorage.CreateCoEdgeUse(aWireId, anEdgeAB, BRepGraph_FaceId(), TopAbs_FORWARD);
EXPECT_EQ(aStorage.CanonicalizeWireCoEdgeOrderStatus(aWireId),
BRepGraphInc_Storage::WireCoEdgeOrderStatus::Partial);
const NCollection_LinearVector<BRepGraph_CoEdgeId>& aCoEdges =
aStorage.WireRelations(aWireId).CoEdgeIds;
ASSERT_EQ(aCoEdges.Size(), 3u);
EXPECT_EQ(aCoEdges.Value(0), aCoEdgeAB);
EXPECT_EQ(aCoEdges.Value(1), aCoEdgeBC);
EXPECT_EQ(aCoEdges.Value(2), aCoEdgeXY);
}
// ============================================================
// CleanupRemovedReferences tests
// ============================================================
TEST(BRepGraphIncTest, CleanupRemovedRefs_AfterRemoveFace_ValidatePasses)
{
BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0);
BRepGraph aGraph;
[[maybe_unused]] const BRepGraph::ShapesView::Result aBuildRes =
aGraph.Shapes().Add(aBoxMaker.Shape());
ASSERT_FALSE(aGraph.IsEmpty());
const BRepGraph_FaceId aFaceId = BRepGraph_FaceId::Start();
ASSERT_TRUE(aFaceId.IsValid(aGraph.Topo().Faces().Nb()));
aGraph.Editor().Gen().RemoveNode(BRepGraph_NodeId(aFaceId));
aGraph.Editor().Gen().CleanupRemovedReferences();
EXPECT_TRUE(aGraph.ValidateRelations());
}
TEST(BRepGraphIncTest, CleanupRemovedRefs_AfterRemoveFace_RemovesCoEdgePCurves)
{
BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0);
BRepGraph aGraph;
[[maybe_unused]] const BRepGraph::ShapesView::Result aBuildRes =
aGraph.Shapes().Add(aBoxMaker.Shape());
ASSERT_FALSE(aGraph.IsEmpty());
const BRepGraph_FaceId aFaceId = BRepGraph_FaceId::Start();
ASSERT_TRUE(aFaceId.IsValid(aGraph.Topo().Faces().Nb()));
NCollection_LinearVector<BRepGraph_CoEdgeId> aFaceCoEdges;
NCollection_LinearVector<BRepGraph_CoEdgeCurve2DRepId> aPCurves;
for (BRepGraph_CoEdgeId aCoEdgeId(0); aCoEdgeId.IsValid(aGraph.Topo().CoEdges().Nb());
++aCoEdgeId)
{
const BRepGraphInc::CoEdgeDef& aCoEdge = aGraph.Topo().CoEdges().Definition(aCoEdgeId);
if (aCoEdge.FaceId == aFaceId && aCoEdge.Curve2DRepId.IsValid())
{
aFaceCoEdges.Append(aCoEdgeId);
aPCurves.Append(aCoEdge.Curve2DRepId);
}
}
ASSERT_FALSE(aPCurves.IsEmpty());
const uint32_t aNbActivePCurves = aGraph.Topo().Geometry().NbActiveCoEdgeCurves2D();
aGraph.Editor().Gen().RemoveNode(BRepGraph_NodeId(aFaceId));
aGraph.Editor().Gen().CleanupRemovedReferences();
for (const BRepGraph_CoEdgeId& aCoEdgeId : aFaceCoEdges)
{
EXPECT_FALSE(aGraph.Topo().CoEdges().Definition(aCoEdgeId).Curve2DRepId.IsValid());
}
for (const BRepGraph_CoEdgeCurve2DRepId& aPCurveId : aPCurves)
{
EXPECT_TRUE(BRepGraph_RepId(aPCurveId).IsRemoved(aGraph));
}
EXPECT_EQ(aGraph.Topo().Geometry().NbActiveCoEdgeCurves2D(),
aNbActivePCurves - static_cast<uint32_t>(aPCurves.Size()));
EXPECT_TRUE(aGraph.ValidateRelations());
}
TEST(BRepGraphIncTest, CleanupRemovedRefs_AfterRemoveWire_ValidatePasses)
{
BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0);
BRepGraph aGraph;
[[maybe_unused]] const BRepGraph::ShapesView::Result aBuildRes =
aGraph.Shapes().Add(aBoxMaker.Shape());
ASSERT_FALSE(aGraph.IsEmpty());
const BRepGraph_FaceId aFaceId = BRepGraph_FaceId::Start();
const BRepGraph_WireId aOuterId = BRepGraph_Tool::Face::OuterWire(aGraph, aFaceId);
ASSERT_TRUE(aOuterId.IsValid());
aGraph.Editor().Gen().RemoveNode(BRepGraph_NodeId(aOuterId));
aGraph.Editor().Gen().CleanupRemovedReferences();
const BRepGraph_Validate::Result aValidateResult = BRepGraph_Validate::Perform(aGraph);
EXPECT_TRUE(aGraph.ValidateRelations());
EXPECT_TRUE(aValidateResult.IsValid())
<< (aValidateResult.Issues.IsEmpty() ? ""
: aValidateResult.Issues.First().Description.ToCString());
}
TEST(BRepGraphIncTest, CleanupRemovedRefs_AfterRemoveVertex_ValidatePasses)
{
BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0);
BRepGraph aGraph;
[[maybe_unused]] const BRepGraph::ShapesView::Result aBuildRes =
aGraph.Shapes().Add(aBoxMaker.Shape());
ASSERT_FALSE(aGraph.IsEmpty());
const BRepGraph_EdgeId anEdgeId = BRepGraph_EdgeId::Start();
const BRepGraph_VertexRefId aStartV = BRepGraph_Tool::Edge::StartVertexId(aGraph, anEdgeId);
ASSERT_TRUE(aStartV.IsValid());
aGraph.Editor().Gen().RemoveNode(
BRepGraph_NodeId(aGraph.Refs().Vertices().Entry(aStartV).ChildVertexId));
aGraph.Editor().Gen().CleanupRemovedReferences();
EXPECT_TRUE(aGraph.ValidateRelations());
EXPECT_FALSE(aGraph.Topo().Edges().Definition(anEdgeId).StartVertexRefId.IsValid());
}
TEST(BRepGraphIncTest, CleanupRemovedRefs_AfterRemoveMultiple_ValidatePasses)
{
BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0);
BRepGraph aGraph;
[[maybe_unused]] const BRepGraph::ShapesView::Result aBuildRes =
aGraph.Shapes().Add(aBoxMaker.Shape());
ASSERT_FALSE(aGraph.IsEmpty());
const BRepGraph_FaceId aFaceId = BRepGraph_FaceId::Start();
const BRepGraph_EdgeId anEdgeId = BRepGraph_EdgeId::Start();
aGraph.Editor().Gen().RemoveNode(BRepGraph_NodeId(aFaceId));
aGraph.Editor().Gen().RemoveNode(BRepGraph_NodeId(anEdgeId));
aGraph.Editor().Gen().CleanupRemovedReferences();
const BRepGraph_Validate::Result aVal = BRepGraph_Validate::Perform(aGraph);
EXPECT_TRUE(aVal.IsValid());
}
TEST(BRepGraphIncTest, Storage_MarkRemoved_UnbindsTShapeAndOriginal)
{
BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0);
BRepGraph aGraph;
[[maybe_unused]] const BRepGraph::ShapesView::Result aBuildRes =
aGraph.Shapes().Add(aBoxMaker.Shape());
ASSERT_FALSE(aGraph.IsEmpty());
const BRepGraph_FaceId aFaceId = BRepGraph_FaceId::Start();
ASSERT_TRUE(aFaceId.IsValid(aGraph.Topo().Faces().Nb()));
ASSERT_TRUE(aGraph.Shapes().HasOriginal(aFaceId));
const TopoDS_Shape aOrigShape = aGraph.Shapes().Original(aFaceId);
ASSERT_FALSE(aOrigShape.IsNull());
ASSERT_TRUE(aGraph.Shapes().HasNode(aOrigShape));
aGraph.Editor().Gen().RemoveNode(BRepGraph_NodeId(aFaceId));
EXPECT_FALSE(aGraph.Shapes().HasNode(aOrigShape));
EXPECT_FALSE(aGraph.Shapes().HasOriginal(aFaceId));
}
// ============================================================
// Storage fix tests: EraseLast, TShape unbind, RebuildDerivedRelations
// ============================================================
TEST(BRepGraphIncTest, Storage_MarkRemoved_EraseLast_EmptyStore)
{
// S1: When all entities in a rep store are marked removed,
// EraseLast (called internally during Compact) must reclaim slots.
// Verify by removing faces and checking surface counts decrease.
BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0);
BRepGraph aGraph;
aGraph.Clear();
[[maybe_unused]] const BRepGraph::ShapesView::Result aBuildRes =
aGraph.Shapes().Add(aBoxMaker.Shape());
ASSERT_FALSE(aGraph.IsEmpty());
const uint32_t anOldNbSurf = aGraph.Topo().Geometry().NbFaceSurfaces();
ASSERT_NE(anOldNbSurf, 0);
// Remove one face to trigger surface removal during compact.
aGraph.Editor().Gen().RemoveNode(BRepGraph_NodeId(BRepGraph_FaceId::Start()));
[[maybe_unused]] const BRepGraph_Compact::Result aCompactRes = BRepGraph_Compact::Perform(aGraph);
const uint32_t aNewNbSurf = aGraph.Topo().Geometry().NbFaceSurfaces();
EXPECT_LT(aNewNbSurf, anOldNbSurf);
EXPECT_TRUE(aGraph.ValidateRelations());
const BRepGraph_FaceId aNewFace = BRepGraph_FaceId::Start();
aGraph.Editor().Faces().SetSurface(aNewFace, new Geom_Plane(gp_Pln()));
// SetSurface replaces the existing surface on the surviving face; count unchanged.
EXPECT_EQ(aGraph.Topo().Geometry().NbFaceSurfaces(), aNewNbSurf);
}
TEST(BRepGraphIncTest, Storage_MarkRemoved_UnbindsTShapeToNodeId)
{
// S2: After RemoveNode, the removed node's TShape must no longer
// resolve via Shapes().FindNode().
BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0);
const TopoDS_Shape& aBox = aBoxMaker.Shape();
BRepGraph aGraph;
aGraph.Clear();
[[maybe_unused]] const BRepGraph::ShapesView::Result aBuildRes = aGraph.Shapes().Add(aBox);
ASSERT_FALSE(aGraph.IsEmpty());
// Reconstruct the solid and verify it has an active mapping.
const TopoDS_Shape aSolid = aGraph.Shapes().Shape(BRepGraph_SolidId::Start());
ASSERT_FALSE(aSolid.IsNull());
const BRepGraph_NodeId anOldNode = aGraph.Shapes().FindNode(aSolid);
ASSERT_TRUE(anOldNode.IsValid());
EXPECT_EQ(anOldNode.NodeKind, BRepGraph_NodeId::Kind::Solid);
// Remove the solid node. ValidateRelations is not checked here
// because forward refs still point to the removed solid's sub-entities.
aGraph.Editor().Gen().RemoveNode(anOldNode);
[[maybe_unused]] const BRepGraph_Compact::Result aCompactRes = BRepGraph_Compact::Perform(aGraph);
const BRepGraph_NodeId aNodeAfter = aGraph.Shapes().FindNode(aSolid);
EXPECT_FALSE(aNodeAfter.IsValid());
EXPECT_TRUE(aGraph.ValidateRelations());
}
TEST(BRepGraphIncTest, Storage_RebuildDerivedRelations_RecountsRefStoreNbActive)
{
// RebuildDerivedRelations must not corrupt NbActive counters on ref stores
// after reference entries were marked removed.
BRepGraphInc_Storage aStorage;
// Create synthetic entities.
const BRepGraph_VertexId aVtx1 = aStorage.AppendVertex();
const BRepGraph_VertexId aVtx2 = aStorage.AppendVertex();
const BRepGraph_EdgeId anEdge = aStorage.AppendEdge();
const BRepGraph_VertexRefId aStartRef = aStorage.AppendVertexRef();
const BRepGraph_VertexRefId anEndRef = aStorage.AppendVertexRef();
aStorage.ChangeVertexRef(aStartRef).ParentEdgeId = anEdge;
aStorage.ChangeVertexRef(aStartRef).ChildVertexId = aVtx1;
aStorage.ChangeVertexRef(anEndRef).ParentEdgeId = anEdge;
aStorage.ChangeVertexRef(anEndRef).ChildVertexId = aVtx2;
aStorage.ChangeEdge(anEdge).StartVertexRefId = aStartRef;
aStorage.ChangeEdge(anEdge).EndVertexRefId = anEndRef;
aStorage.RebuildDerivedRelations();
const uint32_t anOldActiveVtxRefs = aStorage.NbActiveVertexRefs();
ASSERT_GT(anOldActiveVtxRefs, 0u);
// Mark one vertex ref removed.
EXPECT_TRUE(aStorage.MarkRemovedRef(BRepGraph_RefId(aStartRef)));
EXPECT_EQ(aStorage.NbActiveVertexRefs(), anOldActiveVtxRefs - 1u);
// Rebuilding the relation tables must preserve the NbActive count.
aStorage.RebuildDerivedRelations();
EXPECT_EQ(aStorage.NbActiveVertexRefs(), anOldActiveVtxRefs - 1u);
}
// ============================================================
// Relation table fix tests: BuildDelta, seam-edge dedup
// ============================================================
TEST(BRepGraphIncTest, Relations_BuildDelta_ExistingEdgeNewCoedge_GetsNewFace)
{
// RI1: Build two identical boxes and dedup-merge. Edges merged should
// have correct face counts in the relation tables.
BRepPrimAPI_MakeBox aBoxMaker1(10.0, 20.0, 30.0);
BRepPrimAPI_MakeBox aBoxMaker2(10.0, 20.0, 30.0);
BRep_Builder aBB;
TopoDS_Compound aCompound;
aBB.MakeCompound(aCompound);
aBB.Add(aCompound, aBoxMaker1.Shape());
aBB.Add(aCompound, aBoxMaker2.Shape());
BRepGraph aGraph;
aGraph.Clear();
[[maybe_unused]] const BRepGraph::ShapesView::Result aBuildRes = aGraph.Shapes().Add(aCompound);
ASSERT_FALSE(aGraph.IsEmpty());
ASSERT_EQ(aGraph.Topo().Solids().Nb(), 2);
BRepGraph_Deduplicate::Options anOpts;
anOpts.MergeEntitiesWhenSafe = true;
[[maybe_unused]] const BRepGraph_Deduplicate::Result aDedupRes =
BRepGraph_Deduplicate::Perform(aGraph, anOpts);
// Compact to clean up removed entities before relation tables validation.
[[maybe_unused]] const BRepGraph_Compact::Result aCompactRes = BRepGraph_Compact::Perform(aGraph);
const uint32_t aNbEdges = aGraph.Topo().Edges().Nb();
for (BRepGraph_EdgeId anEId(0); anEId.IsValid(aNbEdges); ++anEId)
{
if (anEId.IsRemoved(aGraph))
{
continue;
}
EXPECT_NE(aGraph.Topo().Edges().NbFaces(anEId), 0u);
}
EXPECT_TRUE(aGraph.ValidateRelations());
}
TEST(BRepGraphIncTest, Relations_SeamEdge_NoDuplicateEdgeToWireEntries)
{
// RI4 (appendDirect -> appendUnique): a seam edge whose two coedges
// belong to the same wire must not cause duplicate wire entries in
// myEdgeToWires.
BRepPrimAPI_MakeSphere aSphMaker(8.0);
const TopoDS_Shape& aSph = aSphMaker.Shape();
BRepGraph aGraph;
std::ignore = BRepGraphInc_Populate::Perform(aGraph, aSph, false);
ASSERT_FALSE(aGraph.IsEmpty());
const uint32_t aNbEdges = aGraph.Topo().Edges().Nb();
for (BRepGraph_EdgeId anEdgeId(0); anEdgeId.IsValid(aNbEdges); ++anEdgeId)
{
NCollection_LinearVector<BRepGraph_WireId> aSeenWires;
for (BRepGraph_WiresOfEdge aWireIt = aGraph.Topo().Edges().WiresOf(anEdgeId); aWireIt.More();
aWireIt.Next())
{
const BRepGraph_WireId aWireId = aWireIt.CurrentId();
EXPECT_FALSE(containsId(aSeenWires, aWireId))
<< "Duplicate wire " << aWireId.Index << " in WiresOfEdge(" << anEdgeId.Index << ")";
aSeenWires.Append(aWireId);
}
}
EXPECT_TRUE(
BRepGraph_Validate::Perform(aGraph, BRepGraph_Validate::Options::Lightweight()).IsValid());
}
TEST(BRepGraphIncTest, Relations_SetChildRefChildNodeId_CrossKindRebinds)
{
// Compound holding a Solid; rewire the ChildRef from the Solid to a Shell.
// CompoundsOfSolid must lose the entry, CompoundsOfShell must gain it.
BRep_Builder aBB;
TopoDS_Compound aCompound;
aBB.MakeCompound(aCompound);
BRepPrimAPI_MakeBox aBoxMaker(2.0, 2.0, 2.0);
aBB.Add(aCompound, aBoxMaker.Shape());
BRepGraph aGraph;
aGraph.Clear();
[[maybe_unused]] const BRepGraph::ShapesView::Result aBuildRes = aGraph.Shapes().Add(aCompound);
ASSERT_FALSE(aGraph.IsEmpty());
ASSERT_GE(aGraph.Topo().Compounds().Nb(), 1);
ASSERT_GE(aGraph.Topo().Solids().Nb(), 1);
ASSERT_GE(aGraph.Topo().Shells().Nb(), 1);
const BRepGraph_CompoundId aCompound0 = BRepGraph_CompoundId::Start();
const NCollection_LinearVector<BRepGraph_ChildRefId>& aChildRefs =
aGraph.Topo().Compounds().Relations(aCompound0).ChildRefIds;
ASSERT_GE(aChildRefs.Size(), 1u);
const BRepGraph_ChildRefId aChildRef = aChildRefs.Value(0);
const BRepGraph_NodeId anOldChild = aGraph.Refs().Children().Entry(aChildRef).ChildNodeId;
ASSERT_EQ(anOldChild.NodeKind, BRepGraph_NodeId::Kind::Solid);
const BRepGraph_ShellId aShell = BRepGraph_ShellId::Start();
aGraph.Editor().Gen().SetChildRefChildNodeId(aChildRef, BRepGraph_NodeId(aShell));
const BRepGraph_SolidId anOldSolid = BRepGraph_SolidId::FromNodeId(anOldChild);
bool oldStill = false, newBound = false;
for (const BRepGraph_CompoundId& c :
BRepGraph_CompoundsOfChild(aGraph,
aGraph.Topo().Gen().CompoundRefIds(BRepGraph_NodeId(anOldSolid))))
{
if (c == aCompound0)
{
oldStill = true;
}
}
for (const BRepGraph_CompoundId& c :
BRepGraph_CompoundsOfChild(aGraph,
aGraph.Topo().Gen().CompoundRefIds(BRepGraph_NodeId(aShell))))
{
if (c == aCompound0)
{
newBound = true;
}
}
EXPECT_FALSE(oldStill);
EXPECT_TRUE(newBound);
EXPECT_TRUE(aGraph.ValidateRelations());
}
TEST(BRepGraphIncTest, Relations_SetChildEdgeIdOnCoEdge_LastBondInWireCheck)
{
// Cylinder seam edge: two coedges share both face and wire. Redirecting one
// coedge's ChildEdgeId must NOT remove (oldEdge, wire) from EdgeToWires while
// the other coedge still references oldEdge in the same wire.
BRepPrimAPI_MakeCylinder aCylMaker(5.0, 10.0);
BRepGraph aGraph;
aGraph.Clear();
[[maybe_unused]] const BRepGraph::ShapesView::Result aBuildRes =
aGraph.Shapes().Add(aCylMaker.Shape());
ASSERT_FALSE(aGraph.IsEmpty());
// Find a seam edge: two coedges, same wire, same edge.
BRepGraph_EdgeId aSeamEdge;
BRepGraph_WireId aSeamWire;
BRepGraph_CoEdgeId aSeamCoEdge;
for (BRepGraph_EdgeId aE(0); aE.IsValid(aGraph.Topo().Edges().Nb()) && !aSeamEdge.IsValid(); ++aE)
{
const NCollection_LinearVector<BRepGraph_CoEdgeId>& aCEs = aGraph.Topo().Edges().CoEdges(aE);
if (aCEs.Size() < 2)
{
continue;
}
NCollection_DataMap<int, BRepGraph_CoEdgeId> aSeenWire;
for (const BRepGraph_CoEdgeId& aCE : aCEs)
{
const BRepGraph_WireId aW = aGraph.Topo().CoEdges().Wire(aCE);
if (aW.IsValid())
{
if (aSeenWire.IsBound(aW.Index))
{
aSeamEdge = aE;
aSeamWire = aW;
aSeamCoEdge = aCE;
break;
}
aSeenWire.Bind(aW.Index, aCE);
}
if (aSeamEdge.IsValid())
{
break;
}
}
}
ASSERT_TRUE(aSeamEdge.IsValid()) << "cylinder must have a wire with two coedges of same edge";
// Pick a different edge to redirect to.
BRepGraph_EdgeId aTargetEdge;
for (BRepGraph_EdgeId aE(0); aE.IsValid(aGraph.Topo().Edges().Nb()); ++aE)
{
if (aE != aSeamEdge)
{
aTargetEdge = aE;
break;
}
}
ASSERT_TRUE(aTargetEdge.IsValid());
aGraph.Editor().CoEdges().SetChildEdgeId(aSeamCoEdge, aTargetEdge);
// The OTHER coedge of aSeamWire still references aSeamEdge -> wire still bound.
bool stillBound = false;
for (const BRepGraph_WireId& aW : aGraph.Topo().Edges().WiresOf(aSeamEdge))
{
if (aW == aSeamWire)
{
stillBound = true;
}
}
EXPECT_TRUE(stillBound);
EXPECT_TRUE(aGraph.ValidateRelations());
}
TEST(BRepGraphIncTest, Relations_FaceSupplementVertex_NoPersistedRefUpdate)
{
// Face-direct supplemental vertices no longer create persisted VertexRef
// entries or relation-table bindings. Validate that the graph stays consistent.
BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0);
BRepGraph aGraph;
aGraph.Clear();
[[maybe_unused]] const BRepGraph::ShapesView::Result aBuildRes =
aGraph.Shapes().Add(aBoxMaker.Shape());
ASSERT_FALSE(aGraph.IsEmpty());
std::ignore = aGraph.Editor().Vertices().Add(gp_Pnt(7, 7, 7), 1.e-7);
EXPECT_TRUE(aGraph.ValidateRelations());
const occ::handle<BRepGraph_LayerTopoSupplement> aLayer =
aGraph.LayerRegistry().Ensure<BRepGraph_LayerTopoSupplement>();
ASSERT_FALSE(aLayer.IsNull());
// No vertex attached via Perform -> supplement layer is empty for this face.
EXPECT_EQ(aLayer->AttachedTo(BRepGraph_NodeId(BRepGraph_FaceId::Start())).Size(), 0);
}
TEST(BRepGraphIncTest, Relations_RemoveRef_UnbindsByKind)
{
// GenOps::RemoveRef must unbind the corresponding relation entry. Picks one
// FaceRef on the box's first shell; after RemoveRef the FaceToShells entry
// for the detached face no longer lists this shell.
BRepPrimAPI_MakeBox aBoxMaker(10.0, 20.0, 30.0);
BRepGraph aGraph;
aGraph.Clear();
[[maybe_unused]] const BRepGraph::ShapesView::Result aBuildRes =
aGraph.Shapes().Add(aBoxMaker.Shape());
ASSERT_FALSE(aGraph.IsEmpty());
const NCollection_LinearVector<BRepGraph_FaceRefId>& aFaceRefs =
BRepGraph_TestTools::FaceRefsOfShell(aGraph, BRepGraph_ShellId::Start());
ASSERT_GE(aFaceRefs.Size(), 1);
const BRepGraph_FaceRefId aFaceRefId = aFaceRefs.Value(0);
const BRepGraphInc::FaceRef& aRef = aGraph.Refs().Faces().Entry(aFaceRefId);
const BRepGraph_FaceId aFaceId = aRef.ChildFaceId;
ASSERT_TRUE(aGraph.Editor().Gen().RemoveRef(aFaceRefId));
for (const BRepGraph_ShellId& aShellId :
BRepGraph_ShellsOfFace(aGraph, aGraph.Topo().Faces().Relations(aFaceId).ParentFaceRefIds))
{
EXPECT_NE(aShellId, BRepGraph_ShellId::Start());
}
EXPECT_TRUE(aGraph.ValidateRelations());
}