Modeling - Implement degenerate thin solid detection (#1231)

Refactor FillSameDomainFaces to use parent solid instead of shell for Same-Domain face mapping
This commit is contained in:
Pasukhin Dmitry
2026-04-28 12:54:13 +01:00
committed by GitHub
parent bea50c2fd8
commit 3cf18a1452
3 changed files with 152 additions and 1 deletions

View File

@@ -37,6 +37,7 @@
#include <TopTools_ShapeMapHasher.hxx>
#include <NCollection_IndexedMap.hxx>
#include <NCollection_Map.hxx>
#include <Precision.hxx>
static TopAbs_ShapeEnum TypeToExplore(const int theDim);
//

View File

@@ -16,6 +16,7 @@
// commercial license or contractual agreement.
#include <BOPAlgo_Builder.hxx>
#include <BOPAlgo_Alerts.hxx>
#include <BOPAlgo_BuilderFace.hxx>
#include <BOPAlgo_PaveFiller.hxx>
@@ -581,6 +582,54 @@ void BOPAlgo_Builder::FillSameDomainFaces(const Message_ProgressRange& theRange)
occ::handle<NCollection_BaseAllocator> aAllocator = new NCollection_IncAllocator;
// Two distinct faces of one solid cannot be Same-Domain: that would
// imply a zero-thickness interior in a single operand.
NCollection_DataMap<TopoDS_Shape, TopoDS_Shape, TopTools_ShapeMapHasher> aFaceToParent(
1,
aAllocator);
{
const int aNbSrc = myDS->NbSourceShapes();
for (int iSrc = 0; iSrc < aNbSrc; ++iSrc)
{
const BOPDS_ShapeInfo& aSI = myDS->ShapeInfo(iSrc);
if (aSI.ShapeType() != TopAbs_SOLID)
continue;
const TopoDS_Shape& aSolid = aSI.Shape();
for (TopExp_Explorer anExpF(aSolid, TopAbs_FACE); anExpF.More(); anExpF.Next())
{
const TopoDS_Shape& aF = anExpF.Current();
if (!aFaceToParent.IsBound(aF))
aFaceToParent.Bind(aF, aSolid);
}
}
NCollection_DataMap<TopoDS_Shape, TopoDS_Shape, TopTools_ShapeMapHasher> aPropagation(
1,
aAllocator);
for (NCollection_DataMap<TopoDS_Shape,
NCollection_List<TopoDS_Shape>,
TopTools_ShapeMapHasher>::Iterator anItIm(myImages);
anItIm.More();
anItIm.Next())
{
const TopoDS_Shape* pParent = aFaceToParent.Seek(anItIm.Key());
if (pParent == nullptr)
continue;
for (NCollection_List<TopoDS_Shape>::Iterator anItPiece(anItIm.Value()); anItPiece.More();
anItPiece.Next())
{
if (!aFaceToParent.IsBound(anItPiece.Value()))
aPropagation.Bind(anItPiece.Value(), *pParent);
}
}
for (NCollection_DataMap<TopoDS_Shape, TopoDS_Shape, TopTools_ShapeMapHasher>::Iterator
anItProp(aPropagation);
anItProp.More();
anItProp.Next())
{
aFaceToParent.Bind(anItProp.Key(), anItProp.Value());
}
}
// Vector to store the indices of faces for future sorting
// for making the SD face for the group from the face with
// smallest index in Data structure
@@ -689,11 +738,15 @@ void BOPAlgo_Builder::FillSameDomainFaces(const Message_ProgressRange& theRange)
{
const TopoDS_Shape& aF1 = aIt1.Value();
bool bCheckPlanar = aMFPlanar.Contains(aF1);
const TopoDS_Shape* pParent1 = aFaceToParent.Seek(aF1);
NCollection_List<TopoDS_Shape>::Iterator aIt2 = aIt1;
for (aIt2.Next(); aIt2.More(); aIt2.Next())
{
const TopoDS_Shape& aF2 = aIt2.Value();
const TopoDS_Shape& aF2 = aIt2.Value();
const TopoDS_Shape* pParent2 = aFaceToParent.Seek(aF2);
if (pParent1 != nullptr && pParent2 != nullptr && pParent1->IsSame(*pParent2))
continue;
if (bCheckPlanar && aMFPlanar.Contains(aF2))
{
// Consider planar bounded faces as Same Domain without additional check

View File

@@ -13,6 +13,9 @@
#include "BOPTest_Utilities.pxx"
#include <BRepBndLib.hxx>
#include <BRepPrimAPI_MakeHalfSpace.hxx>
//=================================================================================================
// Direct BOP Operations Tests (equivalent to bcut, bfuse, bcommon, btuc commands)
//=================================================================================================
@@ -182,4 +185,98 @@ TEST_F(BOPAlgo_ComplexOperationsTest, DirectVsTwoStepComparison)
EXPECT_NEAR(aDirectVolume, aTwoStepVolume, myTolerance)
<< "Direct and two-step operations should produce equivalent results";
}
//=================================================================================================
// Degenerate thin-tool tests
//=================================================================================================
class BOPAlgo_DegenerateToolTest : public BOPAlgo_TestBase
{
};
TEST_F(BOPAlgo_DegenerateToolTest, Cut_AxisAlignedThinTool_NearlyPreservesBoxVolume)
{
const TopoDS_Shape aBox =
BOPTest_Utilities::CreateBox(gp_Pnt(0.0, 0.0, 0.0), 100.0, 100.0, 100.0);
const TopoDS_Shape aThin =
BOPTest_Utilities::CreateBox(gp_Pnt(-500.0, 25.0, -500.0), 1500.0, 1.0e-6, 1500.0);
const TopoDS_Shape aRes = PerformDirectBOP(aBox, aThin, BOPAlgo_CUT);
const double aBoxVol = BOPTest_Utilities::GetVolume(aBox);
const double aOverlapV = 100.0 * 1.0e-6 * 100.0;
ASSERT_FALSE(aRes.IsNull());
EXPECT_NEAR(BOPTest_Utilities::GetVolume(aRes), aBoxVol - aOverlapV, 1.0e-4);
}
TEST_F(BOPAlgo_DegenerateToolTest, Fuse_AxisAlignedThinTool_AddsNonOverlappingSlice)
{
const TopoDS_Shape aBox =
BOPTest_Utilities::CreateBox(gp_Pnt(0.0, 0.0, 0.0), 100.0, 100.0, 100.0);
const TopoDS_Shape aThin =
BOPTest_Utilities::CreateBox(gp_Pnt(-500.0, 25.0, -500.0), 1500.0, 1.0e-6, 1500.0);
const TopoDS_Shape aRes = PerformDirectBOP(aBox, aThin, BOPAlgo_FUSE);
const double aBoxVol = BOPTest_Utilities::GetVolume(aBox);
const double aThinVol = 1500.0 * 1.0e-6 * 1500.0;
const double aOverlapV = 100.0 * 1.0e-6 * 100.0;
ASSERT_FALSE(aRes.IsNull());
EXPECT_NEAR(BOPTest_Utilities::GetVolume(aRes), aBoxVol + aThinVol - aOverlapV, 1.0e-4);
}
TEST_F(BOPAlgo_DegenerateToolTest, Cut_LegitimateThinSlab_NotTreatedAsEmpty)
{
const TopoDS_Shape aBox =
BOPTest_Utilities::CreateBox(gp_Pnt(0.0, 0.0, 0.0), 100.0, 100.0, 100.0);
const TopoDS_Shape aSlab =
BOPTest_Utilities::CreateBox(gp_Pnt(0.0, 0.0, 50.0), 100.0, 100.0, 1.0);
const TopoDS_Shape aRes = PerformDirectBOP(aBox, aSlab, BOPAlgo_CUT);
ASSERT_FALSE(aRes.IsNull());
EXPECT_NEAR(BOPTest_Utilities::GetVolume(aRes),
BOPTest_Utilities::GetVolume(aBox) - BOPTest_Utilities::GetVolume(aSlab),
myTolerance);
}
TEST_F(BOPAlgo_DegenerateToolTest, Common_SolidAndPlanarFace_Unaffected)
{
const TopoDS_Shape aBox =
BOPTest_Utilities::CreateBox(gp_Pnt(0.0, 0.0, 0.0), 100.0, 100.0, 100.0);
const gp_Pln aPln(gp_Pnt(0.0, 0.0, 50.0), gp_Dir(0.0, 0.0, 1.0));
BRepBuilderAPI_MakeFace aFaceMaker(aPln, -200.0, 200.0, -200.0, 200.0);
ASSERT_TRUE(aFaceMaker.IsDone());
const TopoDS_Shape aFace = aFaceMaker.Face();
const TopoDS_Shape aRes = PerformDirectBOP(aBox, aFace, BOPAlgo_COMMON);
ASSERT_FALSE(aRes.IsNull());
EXPECT_NEAR(BOPTest_Utilities::GetSurfaceArea(aRes), 1.0e4, 1.0);
}
TEST_F(BOPAlgo_DegenerateToolTest, Cut_BySemiInfinitePrism_Unaffected)
{
const TopoDS_Shape aBox = BOPTest_Utilities::CreateBox(gp_Pnt(0.0, -1.0, -1.0), 2.0, 2.0, 2.0);
const gp_Pln aPln(gp_Pnt(-0.5, 0.0, 0.0), gp_Dir(1.0, 0.0, 0.0));
BRepBuilderAPI_MakeFace aFaceMaker(aPln, -0.5, 0.5, -0.5, 0.5);
ASSERT_TRUE(aFaceMaker.IsDone());
BRepPrimAPI_MakePrism aPrismMaker(aFaceMaker.Face(), gp_Dir(1.0, 0.0, 0.0), true);
ASSERT_TRUE(aPrismMaker.IsDone());
const TopoDS_Shape aRes = PerformDirectBOP(aBox, aPrismMaker.Shape(), BOPAlgo_CUT);
ASSERT_FALSE(aRes.IsNull());
EXPECT_GT(BOPTest_Utilities::GetVolume(aRes), 1.0);
}
TEST_F(BOPAlgo_DegenerateToolTest, Common_SolidAndHalfspace_Unaffected)
{
const TopoDS_Shape aBox =
BOPTest_Utilities::CreateBox(gp_Pnt(0.0, 0.0, -30.0), 150.0, 200.0, 200.0);
const gp_Pln aPln(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(0.0, 0.0, 1.0));
BRepBuilderAPI_MakeFace aFaceMaker(aPln, -250.0, 250.0, -250.0, 250.0);
ASSERT_TRUE(aFaceMaker.IsDone());
BRepPrimAPI_MakeHalfSpace aHSMaker(aFaceMaker.Face(), gp_Pnt(0.0, 0.0, -100.0));
const TopoDS_Shape aHalfSpace = aHSMaker.Solid();
const TopoDS_Shape aRes = PerformDirectBOP(aBox, aHalfSpace, BOPAlgo_COMMON);
ASSERT_FALSE(aRes.IsNull());
EXPECT_GT(BOPTest_Utilities::GetVolume(aRes), 1.0);
}