Loading files
// Copyright (c) 2025 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 <gtest/gtest.h>
#include <BOPAlgo_Splitter.hxx>
#include <BRep_Builder.hxx>
#include <BRepCheck_Analyzer.hxx>
#include <BRepGProp.hxx>
#include <BRep_Tool.hxx>
#include <BRepAlgoAPI_Common.hxx>
#include <BRepAlgoAPI_Fuse.hxx>
#include <BRepBuilderAPI_MakeEdge.hxx>
#include <BRepBuilderAPI_MakeFace.hxx>
#include <BRepBuilderAPI_MakeVertex.hxx>
#include <BRepBuilderAPI_MakeWire.hxx>
#include <BRepBuilderAPI_Sewing.hxx>
#include <BRepPrimAPI_MakeBox.hxx>
#include <BRepPrimAPI_MakePrism.hxx>
#include <Geom_BezierCurve.hxx>
#include <Geom_CylindricalSurface.hxx>
#include <Geom_Line.hxx>
#include <Geom2d_Line.hxx>
#include <gp_Dir2d.hxx>
#include <gp_Pln.hxx>
#include <gp_Pnt.hxx>
#include <gp_Pnt2d.hxx>
#include <gp_Vec.hxx>
#include <gp_Vec2d.hxx>
#include <GProp_GProps.hxx>
#include <NCollection_Array1.hxx>
#include <NCollection_IndexedMap.hxx>
#include <Precision.hxx>
#include <ShapeUpgrade_UnifySameDomain.hxx>
#include <TopExp.hxx>
#include <TopExp_Explorer.hxx>
#include <TopAbs_ShapeEnum.hxx>
#include <TopTools_ShapeMapHasher.hxx>
#include <TopoDS.hxx>
#include <TopoDS_Compound.hxx>
#include <TopoDS_Edge.hxx>
#include <TopoDS_Face.hxx>
#include <TopoDS_Shell.hxx>
#include <TopoDS_Wire.hxx>
#include <cmath>
#include <algorithm>
//=================================================================================================
// ShapeUpgrade_UnifySameDomain Tests
//=================================================================================================
namespace
{
struct MissingPCurveShape
{
TopoDS_Shell Shell;
TopoDS_Face ReferenceFace;
TopoDS_Edge MissingPCurveEdge;
TopoDS_Edge ValidPCurveEdge;
};
static TopoDS_Vertex makeVertex(BRep_Builder& theBuilder, const gp_Pnt& thePoint)
{
TopoDS_Vertex aVertex;
theBuilder.MakeVertex(aVertex, thePoint, Precision::Confusion());
return aVertex;
}
static TopoDS_Edge makeEdge(BRep_Builder& theBuilder,
const occ::handle<Geom_Surface>& theSurface,
const TopLoc_Location& theLocation,
const TopoDS_Vertex& theFirst,
const TopoDS_Vertex& theLast,
const bool theAddPCurve)
{
BRepBuilderAPI_MakeEdge anEdgeMaker(theFirst, theLast);
if (!anEdgeMaker.IsDone())
{
return TopoDS_Edge();
}
TopoDS_Edge anEdge = anEdgeMaker.Edge();
if (theAddPCurve)
{
const gp_Pnt& aFirstPoint = BRep_Tool::Pnt(theFirst);
const gp_Pnt& aLastPoint = BRep_Tool::Pnt(theLast);
const gp_Pnt2d aFirstPoint2d(std::atan2(aFirstPoint.Y(), aFirstPoint.X()), aFirstPoint.Z());
const gp_Pnt2d aLastPoint2d(std::atan2(aLastPoint.Y(), aLastPoint.X()), aLastPoint.Z());
const gp_Vec2d aDirection(aFirstPoint2d, aLastPoint2d);
occ::handle<Geom2d_Curve> aPCurve = new Geom2d_Line(aFirstPoint2d, gp_Dir2d(aDirection));
theBuilder.UpdateEdge(anEdge, aPCurve, theSurface, theLocation, Precision::Confusion());
theBuilder.Range(anEdge, 0.0, aDirection.Magnitude());
}
return anEdge;
}
static MissingPCurveShape makeMissingPCurveShape(BRep_Builder& theBuilder)
{
const occ::handle<Geom_CylindricalSurface> aSurface = new Geom_CylindricalSurface(gp::XOY(), 1.0);
const TopLoc_Location aLocation;
const TopoDS_Vertex aVertexA = makeVertex(theBuilder, aSurface->Value(0.0, 0.0));
const TopoDS_Vertex aVertexB = makeVertex(theBuilder, aSurface->Value(1.0, 0.0));
const TopoDS_Vertex aVertexC = makeVertex(theBuilder, aSurface->Value(1.0, 1.0));
const TopoDS_Vertex aVertexD = makeVertex(theBuilder, aSurface->Value(0.5, -1.0));
const TopoDS_Vertex aVertexE = makeVertex(theBuilder, aSurface->Value(0.5, 1.0));
const TopoDS_Edge aCommonEdge =
makeEdge(theBuilder, aSurface, aLocation, aVertexA, aVertexB, true);
const TopoDS_Edge anEdgeBC = makeEdge(theBuilder, aSurface, aLocation, aVertexB, aVertexC, false);
const TopoDS_Edge anEdgeCB = makeEdge(theBuilder, aSurface, aLocation, aVertexC, aVertexB, true);
const TopoDS_Edge anEdgeBD = makeEdge(theBuilder, aSurface, aLocation, aVertexB, aVertexD, true);
const TopoDS_Edge anEdgeDA = makeEdge(theBuilder, aSurface, aLocation, aVertexD, aVertexA, true);
const TopoDS_Edge anEdgeAE = makeEdge(theBuilder, aSurface, aLocation, aVertexA, aVertexE, true);
const TopoDS_Edge anEdgeEB = makeEdge(theBuilder, aSurface, aLocation, aVertexE, aVertexB, true);
TopoDS_Wire aSelfTouchingWire;
theBuilder.MakeWire(aSelfTouchingWire);
theBuilder.Add(aSelfTouchingWire, aCommonEdge);
theBuilder.Add(aSelfTouchingWire, anEdgeBC);
theBuilder.Add(aSelfTouchingWire, anEdgeCB);
theBuilder.Add(aSelfTouchingWire, anEdgeBD);
theBuilder.Add(aSelfTouchingWire, anEdgeDA);
aSelfTouchingWire.Closed(true);
TopoDS_Face aSelfTouchingFace;
theBuilder.MakeFace(aSelfTouchingFace, aSurface, aLocation, Precision::Confusion());
theBuilder.Add(aSelfTouchingFace, aSelfTouchingWire);
TopoDS_Edge aReversedCommonEdge = aCommonEdge;
aReversedCommonEdge.Reverse();
TopoDS_Wire aSecondWire;
theBuilder.MakeWire(aSecondWire);
theBuilder.Add(aSecondWire, aReversedCommonEdge);
theBuilder.Add(aSecondWire, anEdgeAE);
theBuilder.Add(aSecondWire, anEdgeEB);
aSecondWire.Closed(true);
TopoDS_Face aSecondFace;
theBuilder.MakeFace(aSecondFace, aSurface, aLocation, Precision::Confusion());
theBuilder.Add(aSecondFace, aSecondWire);
MissingPCurveShape aResult;
theBuilder.MakeShell(aResult.Shell);
theBuilder.Add(aResult.Shell, aSelfTouchingFace);
theBuilder.Add(aResult.Shell, aSecondFace);
aResult.ReferenceFace = aSelfTouchingFace;
aResult.MissingPCurveEdge = anEdgeBC;
aResult.ValidPCurveEdge = anEdgeBD;
return aResult;
}
static MissingPCurveShape makeMissingPCurveTransformationShape(BRep_Builder& theBuilder)
{
const occ::handle<Geom_CylindricalSurface> aSurface = new Geom_CylindricalSurface(gp::XOY(), 1.0);
const TopLoc_Location aLocation;
const TopoDS_Vertex aVertexA = makeVertex(theBuilder, aSurface->Value(0.0, 0.0));
const TopoDS_Vertex aVertexB = makeVertex(theBuilder, aSurface->Value(0.5, 0.0));
const TopoDS_Vertex aVertexC = makeVertex(theBuilder, aSurface->Value(0.5, 1.0));
const TopoDS_Vertex aVertexD = makeVertex(theBuilder, aSurface->Value(0.0, 1.0));
const TopoDS_Vertex aVertexE = makeVertex(theBuilder, aSurface->Value(1.0, 0.0));
const TopoDS_Vertex aVertexF = makeVertex(theBuilder, aSurface->Value(1.0, 1.0));
const TopoDS_Edge anEdgeAB = makeEdge(theBuilder, aSurface, aLocation, aVertexA, aVertexB, true);
const TopoDS_Edge anEdgeBC = makeEdge(theBuilder, aSurface, aLocation, aVertexB, aVertexC, true);
const TopoDS_Edge anEdgeCD = makeEdge(theBuilder, aSurface, aLocation, aVertexC, aVertexD, true);
const TopoDS_Edge anEdgeDA = makeEdge(theBuilder, aSurface, aLocation, aVertexD, aVertexA, true);
const TopoDS_Edge anEdgeBE = makeEdge(theBuilder, aSurface, aLocation, aVertexB, aVertexE, false);
const TopoDS_Edge anEdgeEF = makeEdge(theBuilder, aSurface, aLocation, aVertexE, aVertexF, true);
const TopoDS_Edge anEdgeFC = makeEdge(theBuilder, aSurface, aLocation, aVertexF, aVertexC, true);
TopoDS_Wire aLeftWire;
theBuilder.MakeWire(aLeftWire);
theBuilder.Add(aLeftWire, anEdgeAB);
theBuilder.Add(aLeftWire, anEdgeBC);
theBuilder.Add(aLeftWire, anEdgeCD);
theBuilder.Add(aLeftWire, anEdgeDA);
aLeftWire.Closed(true);
TopoDS_Edge aReversedEdgeBC = anEdgeBC;
aReversedEdgeBC.Reverse();
TopoDS_Wire aRightWire;
theBuilder.MakeWire(aRightWire);
theBuilder.Add(aRightWire, anEdgeBE);
theBuilder.Add(aRightWire, anEdgeEF);
theBuilder.Add(aRightWire, anEdgeFC);
theBuilder.Add(aRightWire, aReversedEdgeBC);
aRightWire.Closed(true);
MissingPCurveShape aResult;
theBuilder.MakeFace(aResult.ReferenceFace, aSurface, aLocation, Precision::Confusion());
theBuilder.Add(aResult.ReferenceFace, aLeftWire);
TopoDS_Face aRightFace;
theBuilder.MakeFace(aRightFace, aSurface, aLocation, Precision::Confusion());
theBuilder.Add(aRightFace, aRightWire);
theBuilder.MakeShell(aResult.Shell);
theBuilder.Add(aResult.Shell, aResult.ReferenceFace);
theBuilder.Add(aResult.Shell, aRightFace);
aResult.MissingPCurveEdge = anEdgeBE;
aResult.ValidPCurveEdge = anEdgeBC;
return aResult;
}
static bool addPlanarFacePair(BRep_Builder& theBuilder, TopoDS_Shell& theShell)
{
const occ::handle<Geom_Surface> aNullSurface;
const TopLoc_Location aLocation;
const TopoDS_Vertex aVertex1 = makeVertex(theBuilder, gp_Pnt(3.0, 0.0, 0.0));
const TopoDS_Vertex aVertex2 = makeVertex(theBuilder, gp_Pnt(4.0, 0.0, 0.0));
const TopoDS_Vertex aVertex3 = makeVertex(theBuilder, gp_Pnt(4.0, 1.0, 0.0));
const TopoDS_Vertex aVertex4 = makeVertex(theBuilder, gp_Pnt(3.0, 1.0, 0.0));
const TopoDS_Vertex aVertex5 = makeVertex(theBuilder, gp_Pnt(5.0, 0.0, 0.0));
const TopoDS_Vertex aVertex6 = makeVertex(theBuilder, gp_Pnt(5.0, 1.0, 0.0));
const TopoDS_Edge anEdge12 =
makeEdge(theBuilder, aNullSurface, aLocation, aVertex1, aVertex2, false);
const TopoDS_Edge anEdge23 =
makeEdge(theBuilder, aNullSurface, aLocation, aVertex2, aVertex3, false);
const TopoDS_Edge anEdge34 =
makeEdge(theBuilder, aNullSurface, aLocation, aVertex3, aVertex4, false);
const TopoDS_Edge anEdge41 =
makeEdge(theBuilder, aNullSurface, aLocation, aVertex4, aVertex1, false);
const TopoDS_Edge anEdge25 =
makeEdge(theBuilder, aNullSurface, aLocation, aVertex2, aVertex5, false);
const TopoDS_Edge anEdge56 =
makeEdge(theBuilder, aNullSurface, aLocation, aVertex5, aVertex6, false);
const TopoDS_Edge anEdge63 =
makeEdge(theBuilder, aNullSurface, aLocation, aVertex6, aVertex3, false);
if (anEdge12.IsNull() || anEdge23.IsNull() || anEdge34.IsNull() || anEdge41.IsNull()
|| anEdge25.IsNull() || anEdge56.IsNull() || anEdge63.IsNull())
{
return false;
}
BRepBuilderAPI_MakeWire aLeftWireMaker;
aLeftWireMaker.Add(anEdge12);
aLeftWireMaker.Add(anEdge23);
aLeftWireMaker.Add(anEdge34);
aLeftWireMaker.Add(anEdge41);
if (!aLeftWireMaker.IsDone())
{
return false;
}
TopoDS_Edge aReversedEdge23 = anEdge23;
aReversedEdge23.Reverse();
BRepBuilderAPI_MakeWire aRightWireMaker;
aRightWireMaker.Add(aReversedEdge23);
aRightWireMaker.Add(anEdge25);
aRightWireMaker.Add(anEdge56);
aRightWireMaker.Add(anEdge63);
if (!aRightWireMaker.IsDone())
{
return false;
}
const gp_Pln aPlanarSurface(gp::XOY());
BRepBuilderAPI_MakeFace aLeftFaceMaker(aPlanarSurface, aLeftWireMaker.Wire());
BRepBuilderAPI_MakeFace aRightFaceMaker(aPlanarSurface, aRightWireMaker.Wire());
if (!aLeftFaceMaker.IsDone() || !aRightFaceMaker.IsDone())
{
return false;
}
theBuilder.Add(theShell, aLeftFaceMaker.Face());
theBuilder.Add(theShell, aRightFaceMaker.Face());
return true;
}
static int countSubShapes(const TopoDS_Shape& theShape, const TopAbs_ShapeEnum theType)
{
NCollection_IndexedMap<TopoDS_Shape, TopTools_ShapeMapHasher> aMap;
for (TopExp_Explorer anExp(theShape, theType); anExp.More(); anExp.Next())
{
TopoDS_Shape aSubShape = anExp.Current();
// Match DRAW's default nbshapes command, which ignores locations.
aSubShape.Location(TopLoc_Location());
aMap.Add(aSubShape);
}
return aMap.Extent();
}
static int countFaces(const TopoDS_Shape& theShape)
{
return countSubShapes(theShape, TopAbs_FACE);
}
} // namespace
// Test for issue #925: Infinite loop when processing internal edges.
// This test verifies that UnifySameDomain properly handles internal edges
// without entering an infinite loop.
TEST(ShapeUpgrade_UnifySameDomainTest, InternalEdgesTermination_Issue925)
{
// Create a planar face with an internal wire (internal edges).
// This configuration previously caused an infinite loop in IntUnifyFaces
// because edges removed from InternalEdges were not removed from IntVEmap.
BRep_Builder aBuilder;
// Create a rectangular outer wire
gp_Pnt aP1(0, 0, 0);
gp_Pnt aP2(10, 0, 0);
gp_Pnt aP3(10, 10, 0);
gp_Pnt aP4(0, 10, 0);
BRepBuilderAPI_MakeEdge aME1(aP1, aP2);
BRepBuilderAPI_MakeEdge aME2(aP2, aP3);
BRepBuilderAPI_MakeEdge aME3(aP3, aP4);
BRepBuilderAPI_MakeEdge aME4(aP4, aP1);
BRepBuilderAPI_MakeWire aMW;
aMW.Add(aME1.Edge());
aMW.Add(aME2.Edge());
aMW.Add(aME3.Edge());
aMW.Add(aME4.Edge());
ASSERT_TRUE(aMW.IsDone()) << "Failed to create outer wire";
TopoDS_Wire anOuterWire = aMW.Wire();
// Create a face from the outer wire
gp_Pln aPlane(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1));
BRepBuilderAPI_MakeFace aMF(aPlane, anOuterWire);
ASSERT_TRUE(aMF.IsDone()) << "Failed to create face";
TopoDS_Face aFace = aMF.Face();
// Create internal edges forming an open path inside the face.
// These edges share a common vertex (aIP2) which is key to triggering
// the infinite loop bug in IntVEmap processing.
gp_Pnt aIP1(2, 2, 0);
gp_Pnt aIP2(5, 5, 0);
gp_Pnt aIP3(8, 2, 0);
BRepBuilderAPI_MakeEdge aMIE1(aIP1, aIP2);
BRepBuilderAPI_MakeEdge aMIE2(aIP2, aIP3);
ASSERT_TRUE(aMIE1.IsDone()) << "Failed to create internal edge 1";
ASSERT_TRUE(aMIE2.IsDone()) << "Failed to create internal edge 2";
// Create an internal wire from the edges
BRepBuilderAPI_MakeWire aMIW;
aMIW.Add(aMIE1.Edge());
aMIW.Add(aMIE2.Edge());
ASSERT_TRUE(aMIW.IsDone()) << "Failed to create internal wire";
TopoDS_Wire anInternalWire = aMIW.Wire();
// Set wire to INTERNAL orientation and add to face
anInternalWire.Orientation(TopAbs_INTERNAL);
aBuilder.Add(aFace, anInternalWire);
// Create a shell with this face
TopoDS_Shell aShell;
aBuilder.MakeShell(aShell);
aBuilder.Add(aShell, aFace);
// Run UnifySameDomain - before the fix, this would enter an infinite loop
// because internal edges removed from InternalEdges were not removed from IntVEmap.
ShapeUpgrade_UnifySameDomain aUnifier(aShell);
aUnifier.SetAngularTolerance(1e-6);
aUnifier.SetLinearTolerance(1e-5);
aUnifier.AllowInternalEdges(true);
// This should complete without hanging
aUnifier.Build();
// Verify the result
const TopoDS_Shape& aResult = aUnifier.Shape();
EXPECT_FALSE(aResult.IsNull()) << "UnifySameDomain result should not be null";
// Count faces in result - should have at least one face
int aFaceCount = 0;
for (TopExp_Explorer anExp(aResult, TopAbs_FACE); anExp.More(); anExp.Next())
{
aFaceCount++;
}
EXPECT_GE(aFaceCount, 1) << "Result should contain at least one face";
}
// Test UnifySameDomain with multiple coplanar faces having internal wires.
// This creates a more complex scenario where multiple faces need to be merged.
TEST(ShapeUpgrade_UnifySameDomainTest, MultipleCoplanarFacesWithInternalWires)
{
BRep_Builder aBuilder;
// Create two adjacent coplanar rectangular faces
gp_Pln aPlane(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1));
// First face: 0-5 x 0-10
gp_Pnt aP1(0, 0, 0);
gp_Pnt aP2(5, 0, 0);
gp_Pnt aP3(5, 10, 0);
gp_Pnt aP4(0, 10, 0);
BRepBuilderAPI_MakeWire aMW1;
aMW1.Add(BRepBuilderAPI_MakeEdge(aP1, aP2).Edge());
aMW1.Add(BRepBuilderAPI_MakeEdge(aP2, aP3).Edge());
aMW1.Add(BRepBuilderAPI_MakeEdge(aP3, aP4).Edge());
aMW1.Add(BRepBuilderAPI_MakeEdge(aP4, aP1).Edge());
ASSERT_TRUE(aMW1.IsDone()) << "Failed to create wire 1";
BRepBuilderAPI_MakeFace aMF1(aPlane, aMW1.Wire());
ASSERT_TRUE(aMF1.IsDone()) << "Failed to create face 1";
TopoDS_Face aFace1 = aMF1.Face();
// Second face: 5-10 x 0-10
gp_Pnt aP5(5, 0, 0);
gp_Pnt aP6(10, 0, 0);
gp_Pnt aP7(10, 10, 0);
gp_Pnt aP8(5, 10, 0);
BRepBuilderAPI_MakeWire aMW2;
aMW2.Add(BRepBuilderAPI_MakeEdge(aP5, aP6).Edge());
aMW2.Add(BRepBuilderAPI_MakeEdge(aP6, aP7).Edge());
aMW2.Add(BRepBuilderAPI_MakeEdge(aP7, aP8).Edge());
aMW2.Add(BRepBuilderAPI_MakeEdge(aP8, aP5).Edge());
ASSERT_TRUE(aMW2.IsDone()) << "Failed to create wire 2";
BRepBuilderAPI_MakeFace aMF2(aPlane, aMW2.Wire());
ASSERT_TRUE(aMF2.IsDone()) << "Failed to create face 2";
TopoDS_Face aFace2 = aMF2.Face();
// Add internal wire to first face
gp_Pnt aIP1(1, 5, 0);
gp_Pnt aIP2(4, 5, 0);
BRepBuilderAPI_MakeWire aMIW1;
aMIW1.Add(BRepBuilderAPI_MakeEdge(aIP1, aIP2).Edge());
ASSERT_TRUE(aMIW1.IsDone()) << "Failed to create internal wire 1";
TopoDS_Wire anInternalWire1 = aMIW1.Wire();
anInternalWire1.Orientation(TopAbs_INTERNAL);
aBuilder.Add(aFace1, anInternalWire1);
// Add internal wire to second face
gp_Pnt aIP3(6, 5, 0);
gp_Pnt aIP4(9, 5, 0);
BRepBuilderAPI_MakeWire aMIW2;
aMIW2.Add(BRepBuilderAPI_MakeEdge(aIP3, aIP4).Edge());
ASSERT_TRUE(aMIW2.IsDone()) << "Failed to create internal wire 2";
TopoDS_Wire anInternalWire2 = aMIW2.Wire();
anInternalWire2.Orientation(TopAbs_INTERNAL);
aBuilder.Add(aFace2, anInternalWire2);
// Sew the faces together to share common edge
BRepBuilderAPI_Sewing aSewer(1e-6);
aSewer.Add(aFace1);
aSewer.Add(aFace2);
aSewer.Perform();
TopoDS_Shape aSewedShape = aSewer.SewedShape();
ASSERT_FALSE(aSewedShape.IsNull()) << "Sewing failed";
// Run UnifySameDomain
ShapeUpgrade_UnifySameDomain aUnifier(aSewedShape);
aUnifier.SetAngularTolerance(1e-6);
aUnifier.SetLinearTolerance(1e-5);
aUnifier.AllowInternalEdges(true);
aUnifier.Build();
const TopoDS_Shape& aResult = aUnifier.Shape();
EXPECT_FALSE(aResult.IsNull()) << "UnifySameDomain result should not be null";
// After unification, we should have fewer faces (ideally 1 merged face)
int aFaceCount = 0;
for (TopExp_Explorer anExp(aResult, TopAbs_FACE); anExp.More(); anExp.Next())
{
aFaceCount++;
}
// The two coplanar faces should be merged into one
EXPECT_EQ(aFaceCount, 1) << "Coplanar faces should be merged into one";
}
// Test that UnifySameDomain handles closed internal wires correctly.
TEST(ShapeUpgrade_UnifySameDomainTest, ClosedInternalWire)
{
BRep_Builder aBuilder;
// Create a large rectangular face
gp_Pnt aP1(0, 0, 0);
gp_Pnt aP2(20, 0, 0);
gp_Pnt aP3(20, 20, 0);
gp_Pnt aP4(0, 20, 0);
BRepBuilderAPI_MakeWire aMW;
aMW.Add(BRepBuilderAPI_MakeEdge(aP1, aP2).Edge());
aMW.Add(BRepBuilderAPI_MakeEdge(aP2, aP3).Edge());
aMW.Add(BRepBuilderAPI_MakeEdge(aP3, aP4).Edge());
aMW.Add(BRepBuilderAPI_MakeEdge(aP4, aP1).Edge());
ASSERT_TRUE(aMW.IsDone()) << "Failed to create outer wire";
gp_Pln aPlane(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1));
BRepBuilderAPI_MakeFace aMF(aPlane, aMW.Wire());
ASSERT_TRUE(aMF.IsDone()) << "Failed to create face";
TopoDS_Face aFace = aMF.Face();
// Create a closed internal wire (triangle) inside the face
gp_Pnt aIP1(5, 5, 0);
gp_Pnt aIP2(15, 5, 0);
gp_Pnt aIP3(10, 15, 0);
BRepBuilderAPI_MakeWire aMIW;
aMIW.Add(BRepBuilderAPI_MakeEdge(aIP1, aIP2).Edge());
aMIW.Add(BRepBuilderAPI_MakeEdge(aIP2, aIP3).Edge());
aMIW.Add(BRepBuilderAPI_MakeEdge(aIP3, aIP1).Edge());
ASSERT_TRUE(aMIW.IsDone()) << "Failed to create internal wire";
TopoDS_Wire anInternalWire = aMIW.Wire();
anInternalWire.Orientation(TopAbs_INTERNAL);
aBuilder.Add(aFace, anInternalWire);
// Create shell
TopoDS_Shell aShell;
aBuilder.MakeShell(aShell);
aBuilder.Add(aShell, aFace);
// Run UnifySameDomain
ShapeUpgrade_UnifySameDomain aUnifier(aShell);
aUnifier.SetAngularTolerance(1e-6);
aUnifier.SetLinearTolerance(1e-5);
aUnifier.AllowInternalEdges(true);
aUnifier.Build();
const TopoDS_Shape& aResult = aUnifier.Shape();
EXPECT_FALSE(aResult.IsNull()) << "UnifySameDomain result should not be null";
}
// Test basic UnifySameDomain functionality on a box (no internal edges).
// This serves as a baseline test to ensure the algorithm works correctly.
TEST(ShapeUpgrade_UnifySameDomainTest, BasicBoxUnification)
{
// Create a simple box using two corner points
BRepPrimAPI_MakeBox aBoxMaker(gp_Pnt(0, 0, 0), gp_Pnt(10, 10, 10));
aBoxMaker.Build();
ASSERT_TRUE(aBoxMaker.IsDone()) << "Failed to create box";
TopoDS_Shape aBox = aBoxMaker.Shape();
// Run UnifySameDomain
ShapeUpgrade_UnifySameDomain aUnifier(aBox);
aUnifier.Build();
const TopoDS_Shape& aResult = aUnifier.Shape();
EXPECT_FALSE(aResult.IsNull()) << "UnifySameDomain result should not be null";
// A box should still have 6 faces after unification (no coplanar faces)
int aFaceCount = 0;
for (TopExp_Explorer anExp(aResult, TopAbs_FACE); anExp.More(); anExp.Next())
{
aFaceCount++;
}
EXPECT_EQ(aFaceCount, 6) << "Box should have 6 faces";
}
// Test that a missing pcurve neither causes a null dereference nor aborts unrelated face merging.
TEST(ShapeUpgrade_UnifySameDomainTest, MissingPCurveDoesNotAbortShellProcessing)
{
BRep_Builder aBuilder;
MissingPCurveShape aTestShape = makeMissingPCurveShape(aBuilder);
ASSERT_FALSE(aTestShape.Shell.IsNull());
ASSERT_TRUE(addPlanarFacePair(aBuilder, aTestShape.Shell));
double aFirst = 0.0;
double aLast = 0.0;
EXPECT_TRUE(
BRep_Tool::CurveOnSurface(aTestShape.MissingPCurveEdge, aTestShape.ReferenceFace, aFirst, aLast)
.IsNull());
ASSERT_FALSE(
BRep_Tool::CurveOnSurface(aTestShape.ValidPCurveEdge, aTestShape.ReferenceFace, aFirst, aLast)
.IsNull());
ShapeUpgrade_UnifySameDomain aUnifier(aTestShape.Shell, false, true, false);
aUnifier.AllowInternalEdges(true);
aUnifier.Build();
const TopoDS_Shape& aResult = aUnifier.Shape();
ASSERT_FALSE(aResult.IsNull());
EXPECT_EQ(countFaces(aResult), 3);
}
// Test that pcurve transformation skips an edge which has no source pcurve.
TEST(ShapeUpgrade_UnifySameDomainTest, MissingSourcePCurveDuringTransformation)
{
BRep_Builder aBuilder;
MissingPCurveShape aTestShape = makeMissingPCurveTransformationShape(aBuilder);
ASSERT_FALSE(aTestShape.Shell.IsNull());
double aFirst = 0.0;
double aLast = 0.0;
ASSERT_TRUE(
BRep_Tool::CurveOnSurface(aTestShape.MissingPCurveEdge, aTestShape.ReferenceFace, aFirst, aLast)
.IsNull());
ShapeUpgrade_UnifySameDomain aUnifier(aTestShape.Shell, false, true, false);
aUnifier.AllowInternalEdges(true);
aUnifier.Build();
const TopoDS_Shape& aResult = aUnifier.Shape();
ASSERT_FALSE(aResult.IsNull());
EXPECT_EQ(countFaces(aResult), 2);
}
// Migrated from tests/bugs/heal/bug29382_3. Preserve all DRAW validations:
// valid result, exact topology counts, and the maximum tolerance bound.
TEST(ShapeUpgrade_UnifySameDomainTest, OCC29382_CommonShapeHasStableTopology)
{
NCollection_Array1<gp_Pnt> aPoles(1, 5);
aPoles(1) = gp_Pnt(0.0, 0.0, 0.0);
aPoles(2) = gp_Pnt(1.0, 0.0, 0.0);
aPoles(3) = gp_Pnt(2.0, 2.0, 0.0);
aPoles(4) = gp_Pnt(0.0, 0.5, 0.0);
aPoles(5) = gp_Pnt(0.0, 0.0, 0.0);
const occ::handle<Geom_BezierCurve> aBezier = new Geom_BezierCurve(aPoles);
BRepBuilderAPI_MakeEdge aEdgeBuilder(aBezier);
ASSERT_TRUE(aEdgeBuilder.IsDone());
BRepBuilderAPI_MakeWire aWireBuilder(aEdgeBuilder.Edge());
ASSERT_TRUE(aWireBuilder.IsDone());
BRepBuilderAPI_MakeFace aFaceBuilder(aWireBuilder.Wire(), false);
ASSERT_TRUE(aFaceBuilder.IsDone());
BRepPrimAPI_MakePrism aPrismBuilder(aFaceBuilder.Face(), gp_Vec(0.0, 0.0, 1.0));
ASSERT_TRUE(aPrismBuilder.IsDone());
BRepPrimAPI_MakeBox aBoxBuilder(gp_Pnt(-0.3, -0.2, 0.0), 1.0, 0.4, 1.0);
aBoxBuilder.Build();
ASSERT_TRUE(aBoxBuilder.IsDone());
BRepAlgoAPI_Common aCommon(aPrismBuilder.Shape(), aBoxBuilder.Shape());
aCommon.Build();
ASSERT_TRUE(aCommon.IsDone());
ASSERT_FALSE(aCommon.Shape().IsNull());
ShapeUpgrade_UnifySameDomain aUnifier(aCommon.Shape(), true, true, true);
aUnifier.Build();
const TopoDS_Shape& aResult = aUnifier.Shape();
ASSERT_FALSE(aResult.IsNull());
EXPECT_TRUE(BRepCheck_Analyzer(aResult).IsValid());
EXPECT_EQ(countSubShapes(aResult, TopAbs_SOLID), 1);
EXPECT_EQ(countSubShapes(aResult, TopAbs_SHELL), 1);
EXPECT_EQ(countSubShapes(aResult, TopAbs_FACE), 5);
EXPECT_EQ(countSubShapes(aResult, TopAbs_WIRE), 5);
EXPECT_EQ(countSubShapes(aResult, TopAbs_EDGE), 9);
EXPECT_EQ(countSubShapes(aResult, TopAbs_VERTEX), 5);
double aMaxTolerance = 0.0;
for (TopExp_Explorer anExp(aResult, TopAbs_VERTEX); anExp.More(); anExp.Next())
{
aMaxTolerance = std::max(aMaxTolerance, BRep_Tool::Tolerance(TopoDS::Vertex(anExp.Current())));
}
for (TopExp_Explorer anExp(aResult, TopAbs_EDGE); anExp.More(); anExp.Next())
{
aMaxTolerance = std::max(aMaxTolerance, BRep_Tool::Tolerance(TopoDS::Edge(anExp.Current())));
}
for (TopExp_Explorer anExp(aResult, TopAbs_FACE); anExp.More(); anExp.Next())
{
aMaxTolerance = std::max(aMaxTolerance, BRep_Tool::Tolerance(TopoDS::Face(anExp.Current())));
}
EXPECT_LE(aMaxTolerance, 6.e-6);
}
// Migrated from tests/bugs/heal/bug30927. Unification must retain the Closed
// flag of every wire produced by the fuse.
TEST(ShapeUpgrade_UnifySameDomainTest, OCC30927_FusedSolidKeepsClosedWires)
{
BRepPrimAPI_MakeBox aBox1Builder(10.0, 10.0, 10.0);
BRepPrimAPI_MakeBox aBox2Builder(gp_Pnt(5.0, 0.0, -5.0), 10.0, 10.0, 20.0);
aBox1Builder.Build();
aBox2Builder.Build();
ASSERT_TRUE(aBox1Builder.IsDone());
ASSERT_TRUE(aBox2Builder.IsDone());
BRepAlgoAPI_Fuse aFuse(aBox1Builder.Shape(), aBox2Builder.Shape());
aFuse.Build();
ASSERT_TRUE(aFuse.IsDone());
ASSERT_FALSE(aFuse.Shape().IsNull());
ShapeUpgrade_UnifySameDomain aUnifier(aFuse.Shape());
aUnifier.Build();
const TopoDS_Shape& aResult = aUnifier.Shape();
ASSERT_FALSE(aResult.IsNull());
int aNbWires = 0;
for (TopExp_Explorer anExp(aResult, TopAbs_WIRE); anExp.More(); anExp.Next())
{
EXPECT_TRUE(TopoDS::Wire(anExp.Current()).Closed());
++aNbWires;
}
EXPECT_GT(aNbWires, 0);
}
// Migrated from tests/bugs/heal/bug29544_2. Two collinear edge pieces split
// by a nearby vertex must be merged back into one ten-unit edge.
TEST(ShapeUpgrade_UnifySameDomainTest, OCC29544_NearbyLinearSplitIsUnified)
{
const occ::handle<Geom_Line> aLine = new Geom_Line(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(1.0, 0.0, 0.0));
BRepBuilderAPI_MakeEdge aEdgeBuilder(aLine, 0.0, 10.0);
ASSERT_TRUE(aEdgeBuilder.IsDone());
const TopoDS_Edge aReferenceEdge = aEdgeBuilder.Edge();
const TopoDS_Vertex aSplitVertex = BRepBuilderAPI_MakeVertex(gp_Pnt(5.0, 2.0e-7, 0.0));
ASSERT_FALSE(aSplitVertex.IsNull());
BOPAlgo_Splitter aSplitter;
aSplitter.AddArgument(aReferenceEdge);
aSplitter.AddTool(aSplitVertex);
aSplitter.SetRunParallel(false);
aSplitter.Perform();
ASSERT_FALSE(aSplitter.HasErrors());
const TopoDS_Shape aSplitShape = aSplitter.Shape();
ASSERT_FALSE(aSplitShape.IsNull());
ShapeUpgrade_UnifySameDomain aUnifier(aSplitShape);
aUnifier.Build();
const TopoDS_Shape& aResult = aUnifier.Shape();
ASSERT_FALSE(aResult.IsNull());
EXPECT_TRUE(BRepCheck_Analyzer(aResult).IsValid());
EXPECT_EQ(countSubShapes(aResult, TopAbs_VERTEX), 2);
EXPECT_EQ(countSubShapes(aResult, TopAbs_EDGE), 1);
GProp_GProps aProperties;
BRepGProp::LinearProperties(aResult, aProperties);
EXPECT_NEAR(aProperties.Mass(), 10.0, Precision::Confusion());
}
f2929057eThis file is pinned to the selected release. Download the original to open it in your design tool.