]> OCCT Git - occt.git/commitdiff
Modelling - Boolean fuse segfaults on loft (#860)
authorDmitrii Kulikov <164657232+AtheneNoctuaPt@users.noreply.github.com>
Sat, 29 Nov 2025 16:25:18 +0000 (16:25 +0000)
committerGitHub <noreply@github.com>
Sat, 29 Nov 2025 16:25:18 +0000 (16:25 +0000)
- 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

src/ModelingAlgorithms/TKBO/BOPAlgo/BOPAlgo_PaveFiller_8.cxx
src/ModelingAlgorithms/TKBO/BOPTools/BOPTools_AlgoTools_1.cxx
src/ModelingAlgorithms/TKBO/GTests/BOPAlgo_PaveFiller_Test.cxx [new file with mode: 0644]
src/ModelingAlgorithms/TKBO/GTests/FILES.cmake

index d28a0070f13cef5dc5b2d8d35fdbac312d825616..9b02e97ae2f6f25cd98ccc5088342f2c7039e886 100644 (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)
+    {
+      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();
index 61039cd74aabbf8ad18592a9330a5e51a6fa8f60..f13072607af8b052089542ab923da4d1bc845148 100644 (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);
diff --git a/src/ModelingAlgorithms/TKBO/GTests/BOPAlgo_PaveFiller_Test.cxx b/src/ModelingAlgorithms/TKBO/GTests/BOPAlgo_PaveFiller_Test.cxx
new file mode 100644 (file)
index 0000000..72528bc
--- /dev/null
@@ -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());
+  }
+}
index 564faaccbfcc32beb4269fb7e81970d5c9060cb3..2220383f92b2fa9472d4da84d75643544ae572bc 100644 (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
 )