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
This commit is contained in:
Dmitrii Kulikov
2025-11-29 16:25:18 +00:00
committed by GitHub
parent 5f40d892d2
commit 29631c61de
4 changed files with 438 additions and 109 deletions

View File

@@ -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)
{
if (aSIE.HasFlag(nF))
continue;
}
if (int nF = 0; anEdgeInfo.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;
}
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())
{
nV = nVSD;
//
// 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();
//
FillPaves(nV, anEdgeIndex, nF, aLPBOut, aPBD);
//
myDS->UpdatePaveBlock(aPBD);
}
// nV,nE,nF
//
if (aSIF.ShapeType() == TopAbs_FACE)
{
// 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);
//
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)));
//
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);
}
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)
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);
}
}
}
// 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)
{
aLPBOut.Append(aPB);
}
}
};
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();

View File

@@ -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);

View File

@@ -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 <gtest/gtest.h>
#include <BRep_Builder.hxx>
#include <BRep_Tool.hxx>
#include <BRepAlgoAPI_Fuse.hxx>
#include <BRepBuilderAPI_MakeEdge.hxx>
#include <BRepBuilderAPI_MakeFace.hxx>
#include <BRepBuilderAPI_MakeVertex.hxx>
#include <BRepBuilderAPI_MakeWire.hxx>
#include <BRepGProp.hxx>
#include <BRepOffsetAPI_ThruSections.hxx>
#include <BRepPrimAPI_MakeBox.hxx>
#include <BRepPrimAPI_MakeCone.hxx>
#include <Geom2d_Curve.hxx>
#include <Geom2d_Line.hxx>
#include <Geom_ConicalSurface.hxx>
#include <Geom_Surface.hxx>
#include <gp_Ax3.hxx>
#include <gp_Circ.hxx>
#include <gp_Pnt.hxx>
#include <GProp_GProps.hxx>
#include <Precision.hxx>
#include <ShapeFix_Shape.hxx>
#include <TopExp_Explorer.hxx>
#include <TopoDS.hxx>
#include <TopoDS_Edge.hxx>
#include <TopoDS_Face.hxx>
#include <TopoDS_Shape.hxx>
#include <TopoDS_Shell.hxx>
#include <TopoDS_Solid.hxx>
#include <TopoDS_Vertex.hxx>
#include <TopoDS_Wire.hxx>
//==================================================================================================
// 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());
}
}

View File

@@ -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
)