From 29631c61dea9fbee570e7e76aa41e949253515eb Mon Sep 17 00:00:00 2001 From: Dmitrii Kulikov <164657232+AtheneNoctuaPt@users.noreply.github.com> Date: Sat, 29 Nov 2025 16:25:18 +0000 Subject: [PATCH] Modelling - Boolean fuse segfaults on loft (#860) - Added null checks for 2D curve handles to prevent dereferencing null geometry - Refactored `ProcessDE()` to use modern C++ idioms (auto, structured bindings, range-based iteration patterns) - Simplified `FindPaveBlocks()` using a lambda function to reduce code duplication --- .../TKBO/BOPAlgo/BOPAlgo_PaveFiller_8.cxx | 180 ++++----- .../TKBO/BOPTools/BOPTools_AlgoTools_1.cxx | 13 +- .../TKBO/GTests/BOPAlgo_PaveFiller_Test.cxx | 349 ++++++++++++++++++ .../TKBO/GTests/FILES.cmake | 1 + 4 files changed, 436 insertions(+), 107 deletions(-) create mode 100644 src/ModelingAlgorithms/TKBO/GTests/BOPAlgo_PaveFiller_Test.cxx diff --git a/src/ModelingAlgorithms/TKBO/BOPAlgo/BOPAlgo_PaveFiller_8.cxx b/src/ModelingAlgorithms/TKBO/BOPAlgo/BOPAlgo_PaveFiller_8.cxx index d28a0070f1..9b02e97ae2 100644 --- a/src/ModelingAlgorithms/TKBO/BOPAlgo/BOPAlgo_PaveFiller_8.cxx +++ b/src/ModelingAlgorithms/TKBO/BOPAlgo/BOPAlgo_PaveFiller_8.cxx @@ -53,133 +53,102 @@ static Standard_Boolean AddSplitPoint(const Handle(BOPDS_PaveBlock)& thePBD, void BOPAlgo_PaveFiller::ProcessDE(const Message_ProgressRange& theRange) { Message_ProgressScope aPSOuter(theRange, NULL, 1); - - Standard_Integer nF, aNb, nE, nV, nVSD, aNbPB; - Handle(NCollection_BaseAllocator) aAllocator; - Handle(BOPDS_PaveBlock) aPBD; - TColStd_ListIteratorOfListOfInteger aItLI; // // 1. Find degenerated edges //-----------------------------------------------------scope f // - aAllocator = NCollection_BaseAllocator::CommonBaseAllocator(); - BOPDS_ListOfPaveBlock aLPBOut(aAllocator); - // - aNb = myDS->NbSourceShapes(); - for (nE = 0; nE < aNb; ++nE) + + for (int anEdgeIndex = 0; anEdgeIndex < myDS->NbSourceShapes(); ++anEdgeIndex) { - const BOPDS_ShapeInfo& aSIE = myDS->ShapeInfo(nE); - if (aSIE.ShapeType() == TopAbs_EDGE) + const BOPDS_ShapeInfo& anEdgeInfo = myDS->ShapeInfo(anEdgeIndex); + if (anEdgeInfo.ShapeType() != TopAbs_EDGE) + { + continue; + } + + if (int nF = 0; anEdgeInfo.HasFlag(nF)) { - if (aSIE.HasFlag(nF)) + const BOPDS_ShapeInfo& aSIF = myDS->ShapeInfo(nF); + int nV = anEdgeInfo.SubShapes().First(); + if (int nVSD = 0; myDS->HasShapeSD(nV, nVSD)) { - const BOPDS_ShapeInfo& aSIF = myDS->ShapeInfo(nF); - nV = aSIE.SubShapes().First(); - if (myDS->HasShapeSD(nV, nVSD)) - { - nV = nVSD; - } - // nV,nE,nF - // - if (aSIF.ShapeType() == TopAbs_FACE) + nV = nVSD; + } + + if (aSIF.ShapeType() == TopAbs_FACE) + { + // 1. Find PaveBlocks that go through nV for nF + BOPDS_ListOfPaveBlock aLPBOut(NCollection_BaseAllocator::CommonBaseAllocator()); + FindPaveBlocks(nV, nF, aLPBOut); + if (!aLPBOut.IsEmpty()) { - // 1. Find PaveBlocks that go through nV for nF - FindPaveBlocks(nV, nF, aLPBOut); - aNbPB = aLPBOut.Extent(); - if (aNbPB) - { - // - // 2. - BOPDS_ListOfPaveBlock& aLPBD = myDS->ChangePaveBlocks(nE); - Standard_ASSERT_VOID(!aLPBD.IsEmpty(), "ListOfPaveBlock is unexpectedly empty"); - if (aLPBD.IsEmpty()) - continue; - aPBD = aLPBD.First(); - // - FillPaves(nV, nE, nF, aLPBOut, aPBD); - // - myDS->UpdatePaveBlock(aPBD); - } // - MakeSplitEdge(nE, nF); + // 2. + BOPDS_ListOfPaveBlock& aLPBD = myDS->ChangePaveBlocks(anEdgeIndex); + Standard_ASSERT_VOID(!aLPBD.IsEmpty(), "ListOfPaveBlock is unexpectedly empty"); + if (aLPBD.IsEmpty()) + continue; + Handle(BOPDS_PaveBlock) aPBD = aLPBD.First(); // - aLPBOut.Clear(); - } - if (aSIF.ShapeType() == TopAbs_EDGE) - { - Standard_Real aTol = 1.e-7; - Standard_Integer nEn; - BRep_Builder BB; - const TopoDS_Edge& aDE = (*(TopoDS_Edge*)(&myDS->Shape(nE))); - const TopoDS_Vertex& aVn = (*(TopoDS_Vertex*)(&myDS->Shape(nV))); + FillPaves(nV, anEdgeIndex, nF, aLPBOut, aPBD); // - TopoDS_Edge aE = aDE; - aE.EmptyCopy(); - BB.Add(aE, aVn); - BB.Degenerated(aE, Standard_True); - BB.UpdateEdge(aE, aTol); - BOPDS_ShapeInfo aSI; - aSI.SetShapeType(TopAbs_EDGE); - aSI.SetShape(aE); - nEn = myDS->Append(aSI); - BOPDS_ListOfPaveBlock& aLPBD = myDS->ChangePaveBlocks(nE); - aPBD = aLPBD.First(); - aPBD->SetEdge(nEn); + myDS->UpdatePaveBlock(aPBD); } + // + MakeSplitEdge(anEdgeIndex, nF); } - if (UserBreak(aPSOuter)) + if (aSIF.ShapeType() == TopAbs_EDGE) { - return; + const TopoDS_Edge& aDE = (*(TopoDS_Edge*)(&myDS->Shape(anEdgeIndex))); + const TopoDS_Vertex& aVn = (*(TopoDS_Vertex*)(&myDS->Shape(nV))); + // + TopoDS_Edge aE = aDE; + aE.EmptyCopy(); + BRep_Builder BB; + BB.Add(aE, aVn); + BB.Degenerated(aE, Standard_True); + BB.UpdateEdge(aE, Precision::Confusion()); + BOPDS_ShapeInfo aSI; + aSI.SetShapeType(TopAbs_EDGE); + aSI.SetShape(aE); + const int nEn = myDS->Append(aSI); + BOPDS_ListOfPaveBlock& aLPBD = myDS->ChangePaveBlocks(anEdgeIndex); + Handle(BOPDS_PaveBlock) aPBD = aLPBD.First(); + aPBD->SetEdge(nEn); } } + + if (UserBreak(aPSOuter)) + { + return; + } } } //================================================================================================= -void BOPAlgo_PaveFiller::FindPaveBlocks(const Standard_Integer nV, - const Standard_Integer nF, - BOPDS_ListOfPaveBlock& aLPBOut) +void BOPAlgo_PaveFiller::FindPaveBlocks(const Standard_Integer thePaveIndex, + const Standard_Integer theFaceInfoIndex, + BOPDS_ListOfPaveBlock& theFoundBlocks) { - Standard_Integer i, aNbPBOn, aNbPBIn, aNbPBSc, nV1, nV2; - // - const BOPDS_FaceInfo& aFI = myDS->ChangeFaceInfo(nF); - // In - const BOPDS_IndexedMapOfPaveBlock& aMPBIn = aFI.PaveBlocksIn(); - aNbPBIn = aMPBIn.Extent(); - for (i = 1; i <= aNbPBIn; ++i) - { - const Handle(BOPDS_PaveBlock)& aPB = aMPBIn(i); - aPB->Indices(nV1, nV2); - if (nV == nV1 || nV == nV2) - { - aLPBOut.Append(aPB); - } - } - // On - const BOPDS_IndexedMapOfPaveBlock& aMPBOn = aFI.PaveBlocksOn(); - aNbPBOn = aMPBOn.Extent(); - for (i = 1; i <= aNbPBOn; ++i) - { - const Handle(BOPDS_PaveBlock)& aPB = aMPBOn(i); - aPB->Indices(nV1, nV2); - if (nV == nV1 || nV == nV2) - { - aLPBOut.Append(aPB); - } - } - // Sections - const BOPDS_IndexedMapOfPaveBlock& aMPBSc = aFI.PaveBlocksSc(); - aNbPBSc = aMPBSc.Extent(); - for (i = 1; i <= aNbPBSc; ++i) - { - const Handle(BOPDS_PaveBlock)& aPB = aMPBSc(i); - aPB->Indices(nV1, nV2); - if (nV == nV1 || nV == nV2) + auto processPaveBlocks = [thePaveIndex, + &theFoundBlocks](const BOPDS_IndexedMapOfPaveBlock& thePaveBlocksMap) { + for (int aBlockIndex = 1; aBlockIndex <= thePaveBlocksMap.Size(); ++aBlockIndex) { - aLPBOut.Append(aPB); + const Handle(BOPDS_PaveBlock)& aPaveBlock = thePaveBlocksMap(aBlockIndex); + int nV1, nV2; + aPaveBlock->Indices(nV1, nV2); + if (thePaveIndex == nV1 || thePaveIndex == nV2) + { + theFoundBlocks.Append(aPaveBlock); + } } - } + }; + + const BOPDS_FaceInfo& aFaceInfo = myDS->ChangeFaceInfo(theFaceInfoIndex); + processPaveBlocks(aFaceInfo.PaveBlocksIn()); + processPaveBlocks(aFaceInfo.PaveBlocksOn()); + processPaveBlocks(aFaceInfo.PaveBlocksSc()); } //================================================================================================= @@ -279,6 +248,11 @@ void BOPAlgo_PaveFiller::FillPaves(const Standard_Integer nVD, // Get 2D curve Standard_Real aTD1, aTD2; Handle(Geom2d_Curve) aC2DDE = BRep_Tool::CurveOnSurface(aDE, aDF, aTD1, aTD2); + if (aC2DDE.IsNull()) + { + return; + } + // Get direction of the curve Standard_Boolean bUDir = std::abs(aC2DDE->Value(aTD1).Y() - aC2DDE->Value(aTD2).Y()) < Precision::PConfusion(); diff --git a/src/ModelingAlgorithms/TKBO/BOPTools/BOPTools_AlgoTools_1.cxx b/src/ModelingAlgorithms/TKBO/BOPTools/BOPTools_AlgoTools_1.cxx index 61039cd74a..f13072607a 100644 --- a/src/ModelingAlgorithms/TKBO/BOPTools/BOPTools_AlgoTools_1.cxx +++ b/src/ModelingAlgorithms/TKBO/BOPTools/BOPTools_AlgoTools_1.cxx @@ -678,10 +678,15 @@ void CorrectWires(const TopoDS_Face& aFx, const TopTools_IndexedMapOfShape& aMap aIt.Initialize(aLE); for (; aIt.More(); aIt.Next()) { - const TopoDS_Edge& aE = *(TopoDS_Edge*)(&aIt.Value()); - const Handle(Geom2d_Curve)& aC2D = BRep_Tool::CurveOnSurface(aE, aF, aT1, aT2); - Standard_Real aT = BRep_Tool::Parameter(aV, aE); - Standard_Boolean isClosed = Standard_False; + const TopoDS_Edge& aE = *(TopoDS_Edge*)(&aIt.Value()); + const Handle(Geom2d_Curve)& aC2D = BRep_Tool::CurveOnSurface(aE, aF, aT1, aT2); + if (aC2D.IsNull()) + { + continue; + } + + Standard_Real aT = BRep_Tool::Parameter(aV, aE); + Standard_Boolean isClosed = Standard_False; { TopoDS_Vertex aV1, aV2; TopExp::Vertices(aE, aV1, aV2); diff --git a/src/ModelingAlgorithms/TKBO/GTests/BOPAlgo_PaveFiller_Test.cxx b/src/ModelingAlgorithms/TKBO/GTests/BOPAlgo_PaveFiller_Test.cxx new file mode 100644 index 0000000000..72528bc6bf --- /dev/null +++ b/src/ModelingAlgorithms/TKBO/GTests/BOPAlgo_PaveFiller_Test.cxx @@ -0,0 +1,349 @@ +// 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//================================================================================================== +// BOPAlgo_PaveFiller Tests - Regression tests for edge cases in Boolean operations +//================================================================================================== + +class BOPAlgo_PaveFillerTest : public ::testing::Test +{ +protected: + //! Create a circular wire at given height + static TopoDS_Wire CreateCircularWire(double theRadius, double theZ) + { + gp_Circ aCircle(gp_Ax2(gp_Pnt(0, 0, theZ), gp_Dir(0, 0, 1)), theRadius); + BRepBuilderAPI_MakeEdge anEdgeMaker(aCircle); + BRepBuilderAPI_MakeWire aWireMaker(anEdgeMaker.Edge()); + return aWireMaker.Wire(); + } + + //! Create a vertex at given point + static TopoDS_Vertex CreateVertex(const gp_Pnt& thePoint) + { + BRepBuilderAPI_MakeVertex aVertexMaker(thePoint); + return aVertexMaker.Vertex(); + } + + //! Check if shape has degenerated edges without pcurves + static bool HasDegeneratedEdgesWithoutPCurves(const TopoDS_Shape& theShape) + { + for (TopExp_Explorer aFaceExp(theShape, TopAbs_FACE); aFaceExp.More(); aFaceExp.Next()) + { + const TopoDS_Face& aFace = TopoDS::Face(aFaceExp.Current()); + for (TopExp_Explorer anEdgeExp(aFace, TopAbs_EDGE); anEdgeExp.More(); anEdgeExp.Next()) + { + const TopoDS_Edge& anEdge = TopoDS::Edge(anEdgeExp.Current()); + if (BRep_Tool::Degenerated(anEdge)) + { + double aFirst, aLast; + Handle(Geom2d_Curve) aPCurve = BRep_Tool::CurveOnSurface(anEdge, aFace, aFirst, aLast); + if (aPCurve.IsNull()) + { + return true; + } + } + } + } + return false; + } + + //! Get volume of a shape + static double GetVolume(const TopoDS_Shape& theShape) + { + GProp_GProps aProps; + BRepGProp::VolumeProperties(theShape, aProps); + return aProps.Mass(); + } +}; + +// Test: Boolean fuse of loft (wire to vertex) with box +// This tests the fix for FillPaves() handling edges without 2D curves. +// A loft from a circular wire to a vertex creates a cone-like shape +// with a degenerated edge at the apex. +TEST_F(BOPAlgo_PaveFillerTest, FuseConeLoftWithBox_DegeneratedEdge) +{ + // Create a loft from a circular wire to a point (apex) + // This creates a cone-like shape with a degenerated edge at the top + TopoDS_Wire aBaseWire = CreateCircularWire(10.0, 0.0); + TopoDS_Vertex anApex = CreateVertex(gp_Pnt(0, 0, 20.0)); + + BRepOffsetAPI_ThruSections aLoftMaker(true, true); // solid, ruled + aLoftMaker.AddWire(aBaseWire); + aLoftMaker.AddVertex(anApex); + aLoftMaker.Build(); + + ASSERT_TRUE(aLoftMaker.IsDone()) << "Loft operation should succeed"; + TopoDS_Shape aLoft = aLoftMaker.Shape(); + ASSERT_FALSE(aLoft.IsNull()) << "Loft shape should not be null"; + + // Create a box that intersects the loft + BRepPrimAPI_MakeBox aBoxMaker(gp_Pnt(-5, -5, 5), 10.0, 10.0, 10.0); + TopoDS_Shape aBox = aBoxMaker.Shape(); + + // Perform Boolean fuse - this should not crash even if there are + // degenerated edges without pcurves + BRepAlgoAPI_Fuse aFuser(aLoft, aBox); + + // The operation should complete without crashing + EXPECT_TRUE(aFuser.IsDone()) << "Boolean fuse should succeed for loft with degenerated edges"; + EXPECT_FALSE(aFuser.Shape().IsNull()) << "Result shape should not be null"; + + // Verify the result has reasonable volume (both shapes contribute) + double aLoftVolume = GetVolume(aLoft); + double aBoxVolume = GetVolume(aBox); + double aResultVolume = GetVolume(aFuser.Shape()); + + // Result volume should be less than sum (intersection) but more than max (union effect) + EXPECT_GT(aResultVolume, 0.0) << "Result should have positive volume"; + EXPECT_LT(aResultVolume, aLoftVolume + aBoxVolume) + << "Result volume should be less than sum of inputs"; +} + +// Test: Boolean fuse of cone (which has degenerated edge at apex) with box +// Standard cone also has a degenerated edge at the apex +TEST_F(BOPAlgo_PaveFillerTest, FuseConeWithBox_DegeneratedEdge) +{ + // Create a cone - has degenerated edge at apex + BRepPrimAPI_MakeCone aConeMaker(10.0, 0.0, 20.0); // R1=10, R2=0 (apex), H=20 + TopoDS_Shape aCone = aConeMaker.Shape(); + ASSERT_FALSE(aCone.IsNull()) << "Cone should be created"; + + // Create a box that intersects near the apex + BRepPrimAPI_MakeBox aBoxMaker(gp_Pnt(-5, -5, 15), 10.0, 10.0, 10.0); + TopoDS_Shape aBox = aBoxMaker.Shape(); + + // Perform Boolean fuse + BRepAlgoAPI_Fuse aFuser(aCone, aBox); + + EXPECT_TRUE(aFuser.IsDone()) << "Boolean fuse of cone and box should succeed"; + EXPECT_FALSE(aFuser.Shape().IsNull()) << "Result shape should not be null"; + + double aResultVolume = GetVolume(aFuser.Shape()); + EXPECT_GT(aResultVolume, 0.0) << "Result should have positive volume"; +} + +// Test: Two lofts fused together (original OCC10006 scenario) +// This is similar to the existing test but focuses on the robustness aspect +TEST_F(BOPAlgo_PaveFillerTest, FuseTwoLofts_RobustnessCheck) +{ + // Create first loft: quadrilateral wire to quadrilateral wire + BRepBuilderAPI_MakeWire aWireMaker1; + aWireMaker1.Add(BRepBuilderAPI_MakeEdge(gp_Pnt(10, -10, 0), gp_Pnt(100, -10, 0)).Edge()); + aWireMaker1.Add(BRepBuilderAPI_MakeEdge(gp_Pnt(100, -10, 0), gp_Pnt(100, -100, 0)).Edge()); + aWireMaker1.Add(BRepBuilderAPI_MakeEdge(gp_Pnt(100, -100, 0), gp_Pnt(10, -100, 0)).Edge()); + aWireMaker1.Add(BRepBuilderAPI_MakeEdge(gp_Pnt(10, -100, 0), gp_Pnt(10, -10, 0)).Edge()); + TopoDS_Wire aBottom1 = aWireMaker1.Wire(); + + BRepBuilderAPI_MakeWire aWireMaker2; + aWireMaker2.Add(BRepBuilderAPI_MakeEdge(gp_Pnt(0, 0, 10), gp_Pnt(100, 0, 10)).Edge()); + aWireMaker2.Add(BRepBuilderAPI_MakeEdge(gp_Pnt(100, 0, 10), gp_Pnt(100, -100, 10)).Edge()); + aWireMaker2.Add(BRepBuilderAPI_MakeEdge(gp_Pnt(100, -100, 10), gp_Pnt(0, -100, 10)).Edge()); + aWireMaker2.Add(BRepBuilderAPI_MakeEdge(gp_Pnt(0, -100, 10), gp_Pnt(0, 0, 10)).Edge()); + TopoDS_Wire aTop1 = aWireMaker2.Wire(); + + BRepOffsetAPI_ThruSections aLoft1(true, true); + aLoft1.AddWire(aBottom1); + aLoft1.AddWire(aTop1); + aLoft1.Build(); + ASSERT_TRUE(aLoft1.IsDone()) << "First loft should succeed"; + + // Create second loft + BRepBuilderAPI_MakeWire aWireMaker3; + aWireMaker3.Add(BRepBuilderAPI_MakeEdge(gp_Pnt(0, 0, 10), gp_Pnt(100, 0, 10)).Edge()); + aWireMaker3.Add(BRepBuilderAPI_MakeEdge(gp_Pnt(100, 0, 10), gp_Pnt(100, -100, 10)).Edge()); + aWireMaker3.Add(BRepBuilderAPI_MakeEdge(gp_Pnt(100, -100, 10), gp_Pnt(0, -100, 10)).Edge()); + aWireMaker3.Add(BRepBuilderAPI_MakeEdge(gp_Pnt(0, -100, 10), gp_Pnt(0, 0, 10)).Edge()); + TopoDS_Wire aBottom2 = aWireMaker3.Wire(); + + BRepBuilderAPI_MakeWire aWireMaker4; + aWireMaker4.Add(BRepBuilderAPI_MakeEdge(gp_Pnt(0, 0, 250), gp_Pnt(100, 0, 250)).Edge()); + aWireMaker4.Add(BRepBuilderAPI_MakeEdge(gp_Pnt(100, 0, 250), gp_Pnt(100, -100, 250)).Edge()); + aWireMaker4.Add(BRepBuilderAPI_MakeEdge(gp_Pnt(100, -100, 250), gp_Pnt(0, -100, 250)).Edge()); + aWireMaker4.Add(BRepBuilderAPI_MakeEdge(gp_Pnt(0, -100, 250), gp_Pnt(0, 0, 250)).Edge()); + TopoDS_Wire aTop2 = aWireMaker4.Wire(); + + BRepOffsetAPI_ThruSections aLoft2(true, true); + aLoft2.AddWire(aBottom2); + aLoft2.AddWire(aTop2); + aLoft2.Build(); + ASSERT_TRUE(aLoft2.IsDone()) << "Second loft should succeed"; + + // Fuse the two lofts - this is the operation that could crash + BRepAlgoAPI_Fuse aFuser(aLoft1.Shape(), aLoft2.Shape()); + + EXPECT_TRUE(aFuser.IsDone()) << "Boolean fuse of two lofts should succeed"; + EXPECT_FALSE(aFuser.Shape().IsNull()) << "Result shape should not be null"; + + double aResultVolume = GetVolume(aFuser.Shape()); + EXPECT_GT(aResultVolume, 0.0) << "Result should have positive volume"; +} + +// Test: Create a cone with manually removed pcurve on degenerated edge +// This directly tests the null pcurve handling in FillPaves() and CorrectWires() +TEST_F(BOPAlgo_PaveFillerTest, FuseConeWithRemovedPCurve_NullPCurveHandling) +{ + // Create a standard cone - it has a degenerated edge at the apex + BRepPrimAPI_MakeCone aConeMaker(10.0, 0.0, 20.0); + TopoDS_Shape aCone = aConeMaker.Shape(); + ASSERT_FALSE(aCone.IsNull()) << "Cone should be created"; + + // Find the conical face and its degenerated edge, then rebuild without pcurve + TopoDS_Face aConicalFace; + TopoDS_Edge aDegeneratedEdge; + + for (TopExp_Explorer aFaceExp(aCone, TopAbs_FACE); aFaceExp.More(); aFaceExp.Next()) + { + const TopoDS_Face& aFace = TopoDS::Face(aFaceExp.Current()); + for (TopExp_Explorer anEdgeExp(aFace, TopAbs_EDGE); anEdgeExp.More(); anEdgeExp.Next()) + { + const TopoDS_Edge& anEdge = TopoDS::Edge(anEdgeExp.Current()); + if (BRep_Tool::Degenerated(anEdge)) + { + aConicalFace = aFace; + aDegeneratedEdge = anEdge; + break; + } + } + if (!aDegeneratedEdge.IsNull()) + break; + } + + // If we found a degenerated edge with pcurve, create a modified cone + // where the degenerated edge lacks pcurve + if (!aDegeneratedEdge.IsNull()) + { + // Get the surface of the conical face + Handle(Geom_Surface) aSurf = BRep_Tool::Surface(aConicalFace); + double aFirst = 0.0; + double aLast = 2.0 * M_PI; + TopoDS_Vertex aVertex = + TopoDS::Vertex(TopExp_Explorer(aDegeneratedEdge, TopAbs_VERTEX).Current()); + + // Create a new degenerated edge without pcurve + BRep_Builder aBuilder; + TopoDS_Edge aNewDegEdge; + aBuilder.MakeEdge(aNewDegEdge); + aBuilder.Add(aNewDegEdge, aVertex.Oriented(TopAbs_FORWARD)); + aBuilder.Add(aNewDegEdge, aVertex.Oriented(TopAbs_REVERSED)); + aBuilder.Degenerated(aNewDegEdge, true); + aBuilder.Range(aNewDegEdge, aFirst, aLast); + // NOTE: We intentionally do NOT add a pcurve here! + + // Rebuild the wire with the new degenerated edge + TopoDS_Wire aNewWire; + aBuilder.MakeWire(aNewWire); + + for (TopExp_Explorer aWireExp(aConicalFace, TopAbs_WIRE); aWireExp.More(); aWireExp.Next()) + { + const TopoDS_Wire& aWire = TopoDS::Wire(aWireExp.Current()); + for (TopExp_Explorer anEdgeExp(aWire, TopAbs_EDGE); anEdgeExp.More(); anEdgeExp.Next()) + { + const TopoDS_Edge& anEdge = TopoDS::Edge(anEdgeExp.Current()); + if (BRep_Tool::Degenerated(anEdge)) + { + aBuilder.Add(aNewWire, aNewDegEdge.Oriented(anEdge.Orientation())); + } + else + { + aBuilder.Add(aNewWire, anEdge); + } + } + break; // Only process first wire + } + + // Create new face with the modified wire + TopoDS_Face aNewFace; + aBuilder.MakeFace(aNewFace, aSurf, Precision::Confusion()); + aBuilder.Add(aNewFace, aNewWire); + + // Build a new shell/solid with the modified face + TopoDS_Shell aNewShell; + aBuilder.MakeShell(aNewShell); + + for (TopExp_Explorer aFaceExp2(aCone, TopAbs_FACE); aFaceExp2.More(); aFaceExp2.Next()) + { + const TopoDS_Face& aFace = TopoDS::Face(aFaceExp2.Current()); + if (aFace.IsSame(aConicalFace)) + { + aBuilder.Add(aNewShell, aNewFace); + } + else + { + aBuilder.Add(aNewShell, aFace); + } + } + + TopoDS_Solid aNewSolid; + aBuilder.MakeSolid(aNewSolid); + aBuilder.Add(aNewSolid, aNewShell); + + // Verify we created a shape with degenerated edge without pcurve + EXPECT_TRUE(HasDegeneratedEdgesWithoutPCurves(aNewSolid)) + << "Modified cone should have degenerated edge without pcurve"; + + // Create a box that intersects near the apex + BRepPrimAPI_MakeBox aBoxMaker(gp_Pnt(-5, -5, 15), 10.0, 10.0, 10.0); + TopoDS_Shape aBox = aBoxMaker.Shape(); + + // Perform Boolean fuse - this should NOT crash with the fix + // Without the fix, this would crash due to null pcurve dereference + BRepAlgoAPI_Fuse aFuser(aNewSolid, aBox); + + // We expect either success or graceful failure, but NOT a crash + // The operation might fail due to invalid geometry, but it should not segfault + EXPECT_NO_THROW({ + bool isDone = aFuser.IsDone(); + (void)isDone; // Suppress unused variable warning + }) << "Boolean fuse should not crash even with missing pcurve"; + } + else + { + // Fallback: just run with regular cone if we couldn't find degenerated edge + BRepPrimAPI_MakeBox aBoxMaker(gp_Pnt(-5, -5, 15), 10.0, 10.0, 10.0); + BRepAlgoAPI_Fuse aFuser(aCone, aBoxMaker.Shape()); + EXPECT_TRUE(aFuser.IsDone()); + } +} diff --git a/src/ModelingAlgorithms/TKBO/GTests/FILES.cmake b/src/ModelingAlgorithms/TKBO/GTests/FILES.cmake index 564faaccbf..2220383f92 100644 --- a/src/ModelingAlgorithms/TKBO/GTests/FILES.cmake +++ b/src/ModelingAlgorithms/TKBO/GTests/FILES.cmake @@ -7,4 +7,5 @@ set(OCCT_TKBO_GTests_FILES BRepAlgoAPI_Fuse_Test.cxx BRepAlgoAPI_Common_Test.cxx BOPAlgo_BOP_Test.cxx + BOPAlgo_PaveFiller_Test.cxx ) -- 2.39.5