Loading files
3d097a032This file is pinned to the selected release. Download the original to open it in your design tool.
// 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 <BRepBuilderAPI_MakeEdge.hxx>
#include <BRepBuilderAPI_MakeVertex.hxx>
#include <BRepPrimAPI_MakeBox.hxx>
#include <NCollection_IndexedMap.hxx>
#include <ShapeBuild_ReShape.hxx>
#include <ShapeFix_Shape.hxx>
#include <TopAbs_ShapeEnum.hxx>
#include <TopExp.hxx>
#include <TopExp_Explorer.hxx>
#include <TopTools_ShapeMapHasher.hxx>
#include <TopoDS.hxx>
#include <TopoDS_Compound.hxx>
#include <TopoDS_Edge.hxx>
#include <TopoDS_Shape.hxx>
#include <TopoDS_Vertex.hxx>
#include <gp_Pnt.hxx>
#include <gtest/gtest.h>
namespace
{
int CountSubShapes(const TopoDS_Shape& theShape, const TopAbs_ShapeEnum theType)
{
NCollection_IndexedMap<TopoDS_Shape, TopTools_ShapeMapHasher> aMap;
TopExp::MapShapes(theShape, theType, aMap);
return aMap.Extent();
}
} // namespace
// Basic Apply() via ShapeBuild_ReShape (the subclass used throughout shape
// healing) must produce the expected vertex substitution on a compound.
TEST(ShapeBuild_ReShapeTest, Apply_PerformsVertexReplacementOnCompound)
{
const TopoDS_Vertex aV1 = BRepBuilderAPI_MakeVertex(gp_Pnt(0, 0, 0));
const TopoDS_Vertex aV2 = BRepBuilderAPI_MakeVertex(gp_Pnt(1, 0, 0));
const TopoDS_Vertex aV2Repl = BRepBuilderAPI_MakeVertex(gp_Pnt(1.5, 0, 0));
TopoDS_Compound aParent;
BRep_Builder aBuilder;
aBuilder.MakeCompound(aParent);
aBuilder.Add(aParent, aV1);
aBuilder.Add(aParent, aV2);
occ::handle<ShapeBuild_ReShape> aReShape = new ShapeBuild_ReShape;
aReShape->Replace(aV2, aV2Repl);
const TopoDS_Shape aResult = aReShape->Apply(aParent);
ASSERT_FALSE(aResult.IsNull());
bool aFoundReplacement = false;
for (TopoDS_Iterator anIt(aResult); anIt.More(); anIt.Next())
{
if (anIt.Value().TShape() == aV2Repl.TShape())
{
aFoundReplacement = true;
}
EXPECT_FALSE(anIt.Value().TShape() == aV2.TShape());
}
EXPECT_TRUE(aFoundReplacement);
}
// Structural containment through ShapeBuild_ReShape's Apply override.
// The base class has its own DFS guard, the subclass has its own applyImpl;
// both paths must survive cyclic containment without a stack overflow.
TEST(ShapeBuild_ReShapeTest, Apply_StructuralContainmentWithoutCrash)
{
const TopoDS_Vertex aV1 = BRepBuilderAPI_MakeVertex(gp_Pnt(0, 0, 0));
const TopoDS_Vertex aV2 = BRepBuilderAPI_MakeVertex(gp_Pnt(1, 0, 0));
const TopoDS_Vertex aV3 = BRepBuilderAPI_MakeVertex(gp_Pnt(2, 0, 0));
const TopoDS_Edge anE = BRepBuilderAPI_MakeEdge(aV1, aV2);
TopoDS_Compound aContainer;
BRep_Builder aBuilder;
aBuilder.MakeCompound(aContainer);
aBuilder.Add(aContainer, anE);
aBuilder.Add(aContainer, aV3);
occ::handle<ShapeBuild_ReShape> aReShape = new ShapeBuild_ReShape;
aReShape->Replace(anE, aContainer);
TopoDS_Shape aResult;
ASSERT_NO_FATAL_FAILURE(aResult = aReShape->Apply(anE));
EXPECT_FALSE(aResult.IsNull());
}
// Regression test for the originally reported symptom: running shape healing
// repeatedly on the same shape must not cause the edge count to grow.
// Before the UpdateWire/Apply fixes, repeated ShapeFix_Shape::Perform on
// shapes with shared sub-shapes produced geometrically growing edge counts
// from cascading replacement chains.
TEST(ShapeFix_ShapeStabilityTest, RepeatedPerformDoesNotMultiplyEdges)
{
BRepPrimAPI_MakeBox aMakeBox(10.0, 10.0, 10.0);
const TopoDS_Shape aBox = aMakeBox.Shape();
ASSERT_TRUE(aMakeBox.IsDone());
const int aNbEdgesInitial = CountSubShapes(aBox, TopAbs_EDGE);
const int aNbVerticesInitial = CountSubShapes(aBox, TopAbs_VERTEX);
const int aNbFacesInitial = CountSubShapes(aBox, TopAbs_FACE);
ASSERT_EQ(aNbEdgesInitial, 12);
TopoDS_Shape aCurrent = aBox;
for (int anIter = 0; anIter < 5; ++anIter)
{
occ::handle<ShapeFix_Shape> aFixer = new ShapeFix_Shape(aCurrent);
aFixer->Perform();
aCurrent = aFixer->Shape();
ASSERT_FALSE(aCurrent.IsNull()) << "iteration " << anIter;
// Counts must remain identical across every iteration - no multiplication,
// no loss of shapes on a valid input.
EXPECT_EQ(CountSubShapes(aCurrent, TopAbs_FACE), aNbFacesInitial) << "iteration " << anIter;
EXPECT_EQ(CountSubShapes(aCurrent, TopAbs_EDGE), aNbEdgesInitial) << "iteration " << anIter;
EXPECT_EQ(CountSubShapes(aCurrent, TopAbs_VERTEX), aNbVerticesInitial)
<< "iteration " << anIter;
}
}
// Diamond sharing through ShapeBuild_ReShape: two compounds share a vertex
// whose replacement is registered. Both parents must pick up the replacement
// and the DFS guard must not mistake the shared reference for a cycle.
TEST(ShapeBuild_ReShapeTest, Apply_DiamondSharedVertexInTwoCompounds)
{
const TopoDS_Vertex aShared = BRepBuilderAPI_MakeVertex(gp_Pnt(0, 0, 0));
const TopoDS_Vertex aSharedNew = BRepBuilderAPI_MakeVertex(gp_Pnt(0.1, 0, 0));
const TopoDS_Vertex anOther1 = BRepBuilderAPI_MakeVertex(gp_Pnt(1, 0, 0));
const TopoDS_Vertex anOther2 = BRepBuilderAPI_MakeVertex(gp_Pnt(2, 0, 0));
TopoDS_Compound aParentA, aParentB, aGrand;
BRep_Builder aBuilder;
aBuilder.MakeCompound(aParentA);
aBuilder.Add(aParentA, aShared);
aBuilder.Add(aParentA, anOther1);
aBuilder.MakeCompound(aParentB);
aBuilder.Add(aParentB, aShared);
aBuilder.Add(aParentB, anOther2);
aBuilder.MakeCompound(aGrand);
aBuilder.Add(aGrand, aParentA);
aBuilder.Add(aGrand, aParentB);
occ::handle<ShapeBuild_ReShape> aReShape = new ShapeBuild_ReShape;
aReShape->Replace(aShared, aSharedNew);
const TopoDS_Shape aResult = aReShape->Apply(aGrand);
ASSERT_FALSE(aResult.IsNull());
int aNbReplVerts = 0;
int aNbOriginals = 0;
for (TopExp_Explorer anExp(aResult, TopAbs_VERTEX); anExp.More(); anExp.Next())
{
if (anExp.Current().TShape() == aSharedNew.TShape())
{
++aNbReplVerts;
}
if (anExp.Current().TShape() == aShared.TShape())
{
++aNbOriginals;
}
}
EXPECT_EQ(aNbOriginals, 0) << "Original vertex must be substituted everywhere";
EXPECT_EQ(aNbReplVerts, 2) << "Replacement must appear in both parentA and parentB";
}