mirror of
https://github.com/Open-Cascade-SAS/OCCT.git
synced 2026-05-10 09:30:48 +08:00
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:
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
349
src/ModelingAlgorithms/TKBO/GTests/BOPAlgo_PaveFiller_Test.cxx
Normal file
349
src/ModelingAlgorithms/TKBO/GTests/BOPAlgo_PaveFiller_Test.cxx
Normal 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());
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user