diff --git a/src/ModelingAlgorithms/TKBO/BOPAlgo/BOPAlgo_BOP.cxx b/src/ModelingAlgorithms/TKBO/BOPAlgo/BOPAlgo_BOP.cxx index 2da583c014..f76af78ad8 100644 --- a/src/ModelingAlgorithms/TKBO/BOPAlgo/BOPAlgo_BOP.cxx +++ b/src/ModelingAlgorithms/TKBO/BOPAlgo/BOPAlgo_BOP.cxx @@ -37,6 +37,7 @@ #include #include #include +#include static TopAbs_ShapeEnum TypeToExplore(const int theDim); // diff --git a/src/ModelingAlgorithms/TKBO/BOPAlgo/BOPAlgo_Builder_2.cxx b/src/ModelingAlgorithms/TKBO/BOPAlgo/BOPAlgo_Builder_2.cxx index 64a538eb83..fd6f9af42a 100644 --- a/src/ModelingAlgorithms/TKBO/BOPAlgo/BOPAlgo_Builder_2.cxx +++ b/src/ModelingAlgorithms/TKBO/BOPAlgo/BOPAlgo_Builder_2.cxx @@ -16,6 +16,7 @@ // commercial license or contractual agreement. #include + #include #include #include @@ -581,6 +582,54 @@ void BOPAlgo_Builder::FillSameDomainFaces(const Message_ProgressRange& theRange) occ::handle 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 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 aPropagation( + 1, + aAllocator); + for (NCollection_DataMap, + TopTools_ShapeMapHasher>::Iterator anItIm(myImages); + anItIm.More(); + anItIm.Next()) + { + const TopoDS_Shape* pParent = aFaceToParent.Seek(anItIm.Key()); + if (pParent == nullptr) + continue; + for (NCollection_List::Iterator anItPiece(anItIm.Value()); anItPiece.More(); + anItPiece.Next()) + { + if (!aFaceToParent.IsBound(anItPiece.Value())) + aPropagation.Bind(anItPiece.Value(), *pParent); + } + } + for (NCollection_DataMap::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::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 diff --git a/src/ModelingAlgorithms/TKBO/GTests/BOPAlgo_BOP_Test.cxx b/src/ModelingAlgorithms/TKBO/GTests/BOPAlgo_BOP_Test.cxx index 9843d01fdc..a20861d210 100644 --- a/src/ModelingAlgorithms/TKBO/GTests/BOPAlgo_BOP_Test.cxx +++ b/src/ModelingAlgorithms/TKBO/GTests/BOPAlgo_BOP_Test.cxx @@ -13,6 +13,9 @@ #include "BOPTest_Utilities.pxx" +#include +#include + //================================================================================================= // 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); } \ No newline at end of file