mirror of
https://github.com/Open-Cascade-SAS/OCCT.git
synced 2026-05-10 09:30:48 +08:00
Testing - Cover Boolean operation with GTests (#721)
- Added systematic Boolean operation test coverage using Google Test framework - Implemented test utilities for shape creation, transformation, and validation - Migrated existing Draw-based Boolean tests to C++ GTests for better automation
This commit is contained in:
185
src/ModelingAlgorithms/TKBO/GTests/BOPAlgo_BOP_Test.cxx
Normal file
185
src/ModelingAlgorithms/TKBO/GTests/BOPAlgo_BOP_Test.cxx
Normal file
@@ -0,0 +1,185 @@
|
||||
// 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 "BOPTest_Utilities.pxx"
|
||||
|
||||
//==================================================================================================
|
||||
// Direct BOP Operations Tests (equivalent to bcut, bfuse, bcommon, btuc commands)
|
||||
//==================================================================================================
|
||||
|
||||
class BOPAlgo_DirectOperationsTest : public BOPAlgo_TestBase
|
||||
{
|
||||
};
|
||||
|
||||
// Test direct cut operation: bcut result sphere box
|
||||
TEST_F(BOPAlgo_DirectOperationsTest, DirectCut_SphereMinusBox)
|
||||
{
|
||||
const TopoDS_Shape aSphere = BOPTest_Utilities::CreateUnitSphere();
|
||||
const TopoDS_Shape aBox = BOPTest_Utilities::CreateUnitBox();
|
||||
|
||||
const TopoDS_Shape aResult = PerformDirectBOP(aSphere, aBox, BOPAlgo_CUT);
|
||||
EXPECT_FALSE(aResult.IsNull()) << "Result shape should not be null";
|
||||
|
||||
const Standard_Real aSurfaceArea = BOPTest_Utilities::GetSurfaceArea(aResult);
|
||||
EXPECT_GT(aSurfaceArea, 0.0) << "Cut result should have positive surface area";
|
||||
}
|
||||
|
||||
// Test direct fuse operation: bfuse result sphere box
|
||||
TEST_F(BOPAlgo_DirectOperationsTest, DirectFuse_SpherePlusBox)
|
||||
{
|
||||
const TopoDS_Shape aSphere = BOPTest_Utilities::CreateUnitSphere();
|
||||
const TopoDS_Shape aBox = BOPTest_Utilities::CreateUnitBox();
|
||||
|
||||
const TopoDS_Shape aResult = PerformDirectBOP(aSphere, aBox, BOPAlgo_FUSE);
|
||||
EXPECT_FALSE(aResult.IsNull()) << "Result shape should not be null";
|
||||
|
||||
const Standard_Real aVolume = BOPTest_Utilities::GetVolume(aResult);
|
||||
const Standard_Real aSphereVolume = BOPTest_Utilities::GetVolume(aSphere);
|
||||
const Standard_Real aBoxVolume = BOPTest_Utilities::GetVolume(aBox);
|
||||
EXPECT_GT(aVolume, aSphereVolume) << "Fuse result should be larger than sphere alone";
|
||||
EXPECT_GT(aVolume, aBoxVolume) << "Fuse result should be larger than box alone";
|
||||
}
|
||||
|
||||
// Test direct common operation: bcommon result box1 box2
|
||||
TEST_F(BOPAlgo_DirectOperationsTest, DirectCommon_OverlappingBoxes)
|
||||
{
|
||||
const TopoDS_Shape aBox1 = BOPTest_Utilities::CreateBox(gp_Pnt(0, 0, 0), 2.0, 2.0, 2.0);
|
||||
const TopoDS_Shape aBox2 = BOPTest_Utilities::CreateBox(gp_Pnt(1, 1, 1), 2.0, 2.0, 2.0);
|
||||
|
||||
const TopoDS_Shape aResult = PerformDirectBOP(aBox1, aBox2, BOPAlgo_COMMON);
|
||||
ValidateResult(aResult, -1.0, 1.0); // Expected volume = 1.0
|
||||
}
|
||||
|
||||
// Test direct tuc operation: btuc result box1 box2
|
||||
TEST_F(BOPAlgo_DirectOperationsTest, DirectTUC_IdenticalBoxes)
|
||||
{
|
||||
const TopoDS_Shape aBox1 = BOPTest_Utilities::CreateBox(gp_Pnt(0, 0, 0), 1.0, 1.0, 1.0);
|
||||
const TopoDS_Shape aBox2 = BOPTest_Utilities::CreateBox(gp_Pnt(0, 0, 0), 1.0, 1.0, 1.0);
|
||||
|
||||
const TopoDS_Shape aResult = PerformDirectBOP(aBox1, aBox2, BOPAlgo_CUT21);
|
||||
ValidateResult(aResult, -1.0, -1.0, Standard_True); // Expected empty
|
||||
}
|
||||
|
||||
// Test with NURBS converted shapes
|
||||
TEST_F(BOPAlgo_DirectOperationsTest, DirectCut_NurbsBoxMinusBox)
|
||||
{
|
||||
TopoDS_Shape aBox1 = BOPTest_Utilities::CreateBox(gp_Pnt(0, 0, 0), 1.0, 1.0, 1.0);
|
||||
aBox1 = BOPTest_Utilities::ConvertToNurbs(aBox1);
|
||||
EXPECT_FALSE(aBox1.IsNull()) << "Failed to convert to NURBS";
|
||||
|
||||
const TopoDS_Shape aBox2 = BOPTest_Utilities::CreateBox(gp_Pnt(0, 1, 0), 1.0, 0.5, 1.0);
|
||||
|
||||
const TopoDS_Shape aResult = PerformDirectBOP(aBox1, aBox2, BOPAlgo_CUT);
|
||||
const Standard_Real aSurfaceArea = BOPTest_Utilities::GetSurfaceArea(aResult);
|
||||
EXPECT_GT(aSurfaceArea, 0.0) << "NURBS cut result should have positive surface area";
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// Two-step BOP Operations Tests (equivalent to bop + bopXXX commands)
|
||||
//==================================================================================================
|
||||
|
||||
class BOPAlgo_TwoStepOperationsTest : public BOPAlgo_TestBase
|
||||
{
|
||||
};
|
||||
|
||||
// Test two-step cut operation: bop sphere box; bopcut result
|
||||
TEST_F(BOPAlgo_TwoStepOperationsTest, TwoStepCut_SphereMinusBox)
|
||||
{
|
||||
const TopoDS_Shape aSphere = BOPTest_Utilities::CreateUnitSphere();
|
||||
const TopoDS_Shape aBox = BOPTest_Utilities::CreateUnitBox();
|
||||
|
||||
const TopoDS_Shape aResult = PerformTwoStepBOP(aSphere, aBox, BOPAlgo_CUT);
|
||||
const Standard_Real aSurfaceArea = BOPTest_Utilities::GetSurfaceArea(aResult);
|
||||
EXPECT_GT(aSurfaceArea, 0.0) << "Two-step cut result should have positive surface area";
|
||||
}
|
||||
|
||||
// Test two-step fuse operation: bop sphere box; bopfuse result
|
||||
TEST_F(BOPAlgo_TwoStepOperationsTest, TwoStepFuse_SpherePlusBox)
|
||||
{
|
||||
const TopoDS_Shape aSphere = BOPTest_Utilities::CreateUnitSphere();
|
||||
const TopoDS_Shape aBox = BOPTest_Utilities::CreateUnitBox();
|
||||
|
||||
const TopoDS_Shape aResult = PerformTwoStepBOP(aSphere, aBox, BOPAlgo_FUSE);
|
||||
EXPECT_FALSE(aResult.IsNull()) << "Result shape should not be null";
|
||||
|
||||
const Standard_Real aVolume = BOPTest_Utilities::GetVolume(aResult);
|
||||
const Standard_Real aSphereVolume = BOPTest_Utilities::GetVolume(aSphere);
|
||||
const Standard_Real aBoxVolume = BOPTest_Utilities::GetVolume(aBox);
|
||||
EXPECT_GT(aVolume, aSphereVolume) << "Two-step fuse result should be larger than sphere alone";
|
||||
EXPECT_GT(aVolume, aBoxVolume) << "Two-step fuse result should be larger than box alone";
|
||||
}
|
||||
|
||||
// Test two-step common operation: bop box1 box2; bopcommon result
|
||||
TEST_F(BOPAlgo_TwoStepOperationsTest, TwoStepCommon_OverlappingBoxes)
|
||||
{
|
||||
const TopoDS_Shape aBox1 = BOPTest_Utilities::CreateBox(gp_Pnt(0, 0, 0), 2.0, 2.0, 2.0);
|
||||
const TopoDS_Shape aBox2 = BOPTest_Utilities::CreateBox(gp_Pnt(1, 1, 1), 2.0, 2.0, 2.0);
|
||||
|
||||
const TopoDS_Shape aResult = PerformTwoStepBOP(aBox1, aBox2, BOPAlgo_COMMON);
|
||||
ValidateResult(aResult, -1.0, 1.0); // Expected volume = 1.0
|
||||
}
|
||||
|
||||
// Test two-step tuc operation: bop box1 box2; boptuc result
|
||||
TEST_F(BOPAlgo_TwoStepOperationsTest, TwoStepTUC_IdenticalBoxes)
|
||||
{
|
||||
const TopoDS_Shape aBox1 = BOPTest_Utilities::CreateBox(gp_Pnt(0, 0, 0), 1.0, 1.0, 1.0);
|
||||
const TopoDS_Shape aBox2 = BOPTest_Utilities::CreateBox(gp_Pnt(0, 0, 0), 1.0, 1.0, 1.0);
|
||||
|
||||
const TopoDS_Shape aResult = PerformTwoStepBOP(aBox1, aBox2, BOPAlgo_CUT21);
|
||||
ValidateResult(aResult, -1.0, -1.0, Standard_True); // Expected empty
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// Complex Operations Tests
|
||||
//==================================================================================================
|
||||
|
||||
class BOPAlgo_ComplexOperationsTest : public BOPAlgo_TestBase
|
||||
{
|
||||
};
|
||||
|
||||
// Test multiple intersecting primitives
|
||||
TEST_F(BOPAlgo_ComplexOperationsTest, MultipleIntersectingPrimitives)
|
||||
{
|
||||
const TopoDS_Shape aSphere = BOPTest_Utilities::CreateSphere(gp_Pnt(0, 0, 0), 1.5);
|
||||
const TopoDS_Shape aCylinder = BOPTest_Utilities::CreateCylinder(0.8, 3.0);
|
||||
const TopoDS_Shape aBox = BOPTest_Utilities::CreateBox(gp_Pnt(-0.5, -0.5, -0.5), 1.0, 1.0, 1.0);
|
||||
|
||||
// First intersect sphere with cylinder
|
||||
const TopoDS_Shape aIntermediate = PerformDirectBOP(aSphere, aCylinder, BOPAlgo_COMMON);
|
||||
EXPECT_FALSE(aIntermediate.IsNull()) << "Intermediate result should not be null";
|
||||
|
||||
// Then fuse with box
|
||||
const TopoDS_Shape aFinalResult = PerformDirectBOP(aIntermediate, aBox, BOPAlgo_FUSE);
|
||||
const Standard_Real aVolume = BOPTest_Utilities::GetVolume(aFinalResult);
|
||||
EXPECT_GT(aVolume, 0.0) << "Complex operation result should have positive volume";
|
||||
}
|
||||
|
||||
// Test comparison between direct and two-step operations
|
||||
TEST_F(BOPAlgo_ComplexOperationsTest, DirectVsTwoStepComparison)
|
||||
{
|
||||
const TopoDS_Shape aSphere = BOPTest_Utilities::CreateUnitSphere();
|
||||
const TopoDS_Shape aBox = BOPTest_Utilities::CreateUnitBox();
|
||||
|
||||
// Perform direct operation
|
||||
const TopoDS_Shape aDirectResult = PerformDirectBOP(aSphere, aBox, BOPAlgo_FUSE);
|
||||
|
||||
// Perform two-step operation
|
||||
const TopoDS_Shape aTwoStepResult = PerformTwoStepBOP(aSphere, aBox, BOPAlgo_FUSE);
|
||||
|
||||
// Results should be equivalent
|
||||
const Standard_Real aDirectVolume = BOPTest_Utilities::GetVolume(aDirectResult);
|
||||
const Standard_Real aTwoStepVolume = BOPTest_Utilities::GetVolume(aTwoStepResult);
|
||||
|
||||
EXPECT_NEAR(aDirectVolume, aTwoStepVolume, myTolerance)
|
||||
<< "Direct and two-step operations should produce equivalent results";
|
||||
}
|
||||
994
src/ModelingAlgorithms/TKBO/GTests/BOPTest_Utilities.pxx
Normal file
994
src/ModelingAlgorithms/TKBO/GTests/BOPTest_Utilities.pxx
Normal file
@@ -0,0 +1,994 @@
|
||||
// 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.
|
||||
|
||||
#ifndef _BOPTest_Utilities_HeaderFile
|
||||
#define _BOPTest_Utilities_HeaderFile
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <BOPAlgo_BOP.hxx>
|
||||
#include <BOPAlgo_PaveFiller.hxx>
|
||||
#include <BOPAlgo_Operation.hxx>
|
||||
#include <BRepPrimAPI_MakeBox.hxx>
|
||||
#include <BRepPrimAPI_MakeSphere.hxx>
|
||||
#include <BRepPrimAPI_MakeCylinder.hxx>
|
||||
#include <BRepPrimAPI_MakeCone.hxx>
|
||||
#include <BRepBuilderAPI_MakePolygon.hxx>
|
||||
#include <BRepPrimAPI_MakePrism.hxx>
|
||||
#include <BRepPrimAPI_MakeRevol.hxx>
|
||||
#include <TopExp.hxx>
|
||||
#include <TopExp_Explorer.hxx>
|
||||
#include <TopoDS_Face.hxx>
|
||||
#include <BRepBuilderAPI_MakeFace.hxx>
|
||||
#include <BRepBuilderAPI_NurbsConvert.hxx>
|
||||
#include <BRepBuilderAPI_Transform.hxx>
|
||||
#include <BRepBuilderAPI_MakeVertex.hxx>
|
||||
#include <BRepBuilderAPI_MakeEdge.hxx>
|
||||
#include <BRepBuilderAPI_MakeWire.hxx>
|
||||
#include <BRepFilletAPI_MakeFillet.hxx>
|
||||
#include <gp_Trsf.hxx>
|
||||
#include <gp_Ax1.hxx>
|
||||
#include <gp_Vec.hxx>
|
||||
#include <gp_Pnt.hxx>
|
||||
#include <TopoDS_Vertex.hxx>
|
||||
#include <TopoDS_Edge.hxx>
|
||||
#include <TopoDS_Wire.hxx>
|
||||
#include <TopoDS.hxx>
|
||||
#include <ElSLib.hxx>
|
||||
#include <gp_Vec2d.hxx>
|
||||
#include <gp_Pnt2d.hxx>
|
||||
#include <gp_Circ.hxx>
|
||||
#include <gp_Ax2.hxx>
|
||||
#include <Precision.hxx>
|
||||
#include <BRepAlgoAPI_Cut.hxx>
|
||||
#include <BRepAlgoAPI_Fuse.hxx>
|
||||
#include <BRepAlgoAPI_Common.hxx>
|
||||
#include <BRepTools.hxx>
|
||||
#include <GProp_GProps.hxx>
|
||||
#include <BRepGProp.hxx>
|
||||
#include <TopoDS_Shape.hxx>
|
||||
#include <TopTools_ListOfShape.hxx>
|
||||
#include <Standard_Real.hxx>
|
||||
#include <NCollection_BaseAllocator.hxx>
|
||||
#include <gp_Pnt.hxx>
|
||||
#include <gp_Ax2.hxx>
|
||||
#include <gp_Dir.hxx>
|
||||
|
||||
#include <cmath>
|
||||
#include <memory>
|
||||
|
||||
#ifndef M_PI
|
||||
#define M_PI 3.14159265358979323846
|
||||
#endif
|
||||
|
||||
//==================================================================================================
|
||||
//! Base utility class for BOP testing with common helper functions
|
||||
//==================================================================================================
|
||||
class BOPTest_Utilities
|
||||
{
|
||||
public:
|
||||
//! Profile command types (equivalent to TCL profile commands)
|
||||
enum class ProfileCmd
|
||||
{
|
||||
O, // Set origin (O X Y Z)
|
||||
P, // Set plane (P nx ny nz dx dy dz)
|
||||
F, // Set first point
|
||||
X, // Translate along X
|
||||
Y, // Translate along Y
|
||||
L, // Translate along direction
|
||||
XX, // Set X coordinate
|
||||
YY, // Set Y coordinate
|
||||
T, // Translate by vector
|
||||
TT, // Set point
|
||||
R, // Rotate direction
|
||||
RR, // Set direction angle
|
||||
D, // Set direction vector
|
||||
C, // Arc (circle)
|
||||
W, // Make closed wire
|
||||
WW // Make open wire
|
||||
};
|
||||
|
||||
//! Profile command structure
|
||||
struct ProfileOperation
|
||||
{
|
||||
ProfileCmd cmd;
|
||||
std::vector<Standard_Real> params;
|
||||
|
||||
ProfileOperation(ProfileCmd c)
|
||||
: cmd(c)
|
||||
{
|
||||
}
|
||||
|
||||
ProfileOperation(ProfileCmd c, Standard_Real p1)
|
||||
: cmd(c),
|
||||
params({p1})
|
||||
{
|
||||
}
|
||||
|
||||
ProfileOperation(ProfileCmd c, Standard_Real p1, Standard_Real p2)
|
||||
: cmd(c),
|
||||
params({p1, p2})
|
||||
{
|
||||
}
|
||||
|
||||
ProfileOperation(ProfileCmd c, Standard_Real p1, Standard_Real p2, Standard_Real p3)
|
||||
: cmd(c),
|
||||
params({p1, p2, p3})
|
||||
{
|
||||
}
|
||||
|
||||
ProfileOperation(ProfileCmd c,
|
||||
Standard_Real p1,
|
||||
Standard_Real p2,
|
||||
Standard_Real p3,
|
||||
Standard_Real p4,
|
||||
Standard_Real p5,
|
||||
Standard_Real p6)
|
||||
: cmd(c),
|
||||
params({p1, p2, p3, p4, p5, p6})
|
||||
{
|
||||
}
|
||||
|
||||
ProfileOperation(ProfileCmd c, const std::vector<Standard_Real>& p)
|
||||
: cmd(c),
|
||||
params(p)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
//! Default tolerance values
|
||||
static constexpr Standard_Real DefaultTolerance() { return 1.0e-6; }
|
||||
|
||||
static constexpr Standard_Real DefaultFuzzyValue() { return 1.0e-8; }
|
||||
|
||||
//! Calculate surface area of a shape
|
||||
static Standard_Real GetSurfaceArea(const TopoDS_Shape& theShape)
|
||||
{
|
||||
if (theShape.IsNull())
|
||||
{
|
||||
return 0.0;
|
||||
}
|
||||
GProp_GProps aProps;
|
||||
BRepGProp::SurfaceProperties(theShape, aProps);
|
||||
return aProps.Mass();
|
||||
}
|
||||
|
||||
//! Calculate volume of a shape
|
||||
static Standard_Real GetVolume(const TopoDS_Shape& theShape)
|
||||
{
|
||||
if (theShape.IsNull())
|
||||
{
|
||||
return 0.0;
|
||||
}
|
||||
GProp_GProps aProps;
|
||||
BRepGProp::VolumeProperties(theShape, aProps);
|
||||
return aProps.Mass();
|
||||
}
|
||||
|
||||
//! Check if shape is effectively empty (very small surface area)
|
||||
static Standard_Boolean IsEmpty(const TopoDS_Shape& theShape,
|
||||
const Standard_Real theTolerance = DefaultTolerance())
|
||||
{
|
||||
return GetSurfaceArea(theShape) <= theTolerance;
|
||||
}
|
||||
|
||||
//! Create unit box at origin (1x1x1)
|
||||
static TopoDS_Shape CreateUnitBox()
|
||||
{
|
||||
BRepPrimAPI_MakeBox aBoxMaker(1.0, 1.0, 1.0);
|
||||
return aBoxMaker.Shape();
|
||||
}
|
||||
|
||||
//! Create box at specific location
|
||||
static TopoDS_Shape CreateBox(const gp_Pnt& theCorner,
|
||||
Standard_Real theX,
|
||||
Standard_Real theY,
|
||||
Standard_Real theZ)
|
||||
{
|
||||
BRepPrimAPI_MakeBox aBoxMaker(theCorner, theX, theY, theZ);
|
||||
return aBoxMaker.Shape();
|
||||
}
|
||||
|
||||
//! Create unit sphere (radius = 1.0)
|
||||
static TopoDS_Shape CreateUnitSphere()
|
||||
{
|
||||
BRepPrimAPI_MakeSphere aSphereMaker(1.0);
|
||||
return aSphereMaker.Shape();
|
||||
}
|
||||
|
||||
//! Create sphere at location with radius
|
||||
static TopoDS_Shape CreateSphere(const gp_Pnt& theCenter, Standard_Real theRadius)
|
||||
{
|
||||
BRepPrimAPI_MakeSphere aSphereMaker(theCenter, theRadius);
|
||||
return aSphereMaker.Shape();
|
||||
}
|
||||
|
||||
//! Create cylinder
|
||||
static TopoDS_Shape CreateCylinder(Standard_Real theRadius, Standard_Real theHeight)
|
||||
{
|
||||
BRepPrimAPI_MakeCylinder aCylinderMaker(theRadius, theHeight);
|
||||
return aCylinderMaker.Shape();
|
||||
}
|
||||
|
||||
//! Create cone
|
||||
static TopoDS_Shape CreateCone(Standard_Real theR1, Standard_Real theR2, Standard_Real theHeight)
|
||||
{
|
||||
BRepPrimAPI_MakeCone aConeMaker(theR1, theR2, theHeight);
|
||||
return aConeMaker.Shape();
|
||||
}
|
||||
|
||||
//! Convert shape to NURBS
|
||||
static TopoDS_Shape ConvertToNurbs(const TopoDS_Shape& theShape)
|
||||
{
|
||||
BRepBuilderAPI_NurbsConvert aNurbsConverter(theShape);
|
||||
if (!aNurbsConverter.IsDone())
|
||||
{
|
||||
return TopoDS_Shape(); // Return null shape on failure
|
||||
}
|
||||
return aNurbsConverter.Shape();
|
||||
}
|
||||
|
||||
//! Create rectangular polygon face
|
||||
static TopoDS_Shape CreatePolygonFace(const gp_Pnt& theP1,
|
||||
const gp_Pnt& theP2,
|
||||
const gp_Pnt& theP3,
|
||||
const gp_Pnt& theP4)
|
||||
{
|
||||
BRepBuilderAPI_MakePolygon aPolygonMaker;
|
||||
aPolygonMaker.Add(theP1);
|
||||
aPolygonMaker.Add(theP2);
|
||||
aPolygonMaker.Add(theP3);
|
||||
aPolygonMaker.Add(theP4);
|
||||
aPolygonMaker.Close();
|
||||
|
||||
if (!aPolygonMaker.IsDone())
|
||||
{
|
||||
return TopoDS_Shape();
|
||||
}
|
||||
|
||||
BRepBuilderAPI_MakeFace aFaceMaker(aPolygonMaker.Wire());
|
||||
return aFaceMaker.Shape();
|
||||
}
|
||||
|
||||
//! Rotate shape around axis
|
||||
static TopoDS_Shape RotateShape(const TopoDS_Shape& theShape,
|
||||
const gp_Ax1& theAxis,
|
||||
Standard_Real theAngle)
|
||||
{
|
||||
gp_Trsf aTrsf;
|
||||
aTrsf.SetRotation(theAxis, theAngle);
|
||||
BRepBuilderAPI_Transform aTransformer(theShape, aTrsf);
|
||||
return aTransformer.Shape();
|
||||
}
|
||||
|
||||
//! Translate shape by vector
|
||||
static TopoDS_Shape TranslateShape(const TopoDS_Shape& theShape, const gp_Vec& theVector)
|
||||
{
|
||||
gp_Trsf aTrsf;
|
||||
aTrsf.SetTranslation(theVector);
|
||||
BRepBuilderAPI_Transform aTransformer(theShape, aTrsf);
|
||||
return aTransformer.Shape();
|
||||
}
|
||||
|
||||
//! Create vertex from point
|
||||
static TopoDS_Vertex CreateVertex(const gp_Pnt& thePoint)
|
||||
{
|
||||
BRepBuilderAPI_MakeVertex aVertexMaker(thePoint);
|
||||
return aVertexMaker.Vertex();
|
||||
}
|
||||
|
||||
//! Create edge between two points
|
||||
static TopoDS_Edge CreateEdge(const gp_Pnt& theP1, const gp_Pnt& theP2)
|
||||
{
|
||||
BRepBuilderAPI_MakeEdge anEdgeMaker(theP1, theP2);
|
||||
return anEdgeMaker.Edge();
|
||||
}
|
||||
|
||||
//! Create wire from list of points (closed polygon)
|
||||
static TopoDS_Wire CreatePolygonWire(const std::vector<gp_Pnt>& thePoints,
|
||||
Standard_Boolean theClose = Standard_True)
|
||||
{
|
||||
BRepBuilderAPI_MakePolygon aPolygonMaker;
|
||||
for (const gp_Pnt& aPt : thePoints)
|
||||
{
|
||||
aPolygonMaker.Add(aPt);
|
||||
}
|
||||
if (theClose)
|
||||
{
|
||||
aPolygonMaker.Close();
|
||||
}
|
||||
return aPolygonMaker.Wire();
|
||||
}
|
||||
|
||||
//! Create complex profile using simplified approach (similar to TCL profile command)
|
||||
//! Simulates "profile rev S face F x y [commands...]" by creating a rectangular wire
|
||||
static TopoDS_Wire CreateProfileWire(const gp_Pln& thePlane,
|
||||
const gp_Pnt2d& theStartPt,
|
||||
const std::vector<std::string>& theCommands)
|
||||
{
|
||||
// Simplified profile creation - create a rectangular wire based on commands
|
||||
// This approximates the TCL profile behavior for basic geometric operations
|
||||
std::vector<gp_Pnt2d> aPoints;
|
||||
aPoints.push_back(theStartPt);
|
||||
|
||||
gp_Pnt2d aCurrentPt = theStartPt;
|
||||
gp_Vec2d aCurrentDir(1.0, 0.0); // Default direction
|
||||
|
||||
// Process simplified command set
|
||||
for (size_t i = 0; i < theCommands.size(); ++i)
|
||||
{
|
||||
const std::string& aCmd = theCommands[i];
|
||||
if (aCmd == "Y" && i + 1 < theCommands.size())
|
||||
{
|
||||
// Y command: translate along Y
|
||||
double aDY = std::stod(theCommands[i + 1]);
|
||||
aCurrentPt.SetY(aCurrentPt.Y() + aDY);
|
||||
aPoints.push_back(aCurrentPt);
|
||||
i++; // Skip next parameter
|
||||
}
|
||||
else if (aCmd == "C" && i + 2 < theCommands.size())
|
||||
{
|
||||
// C command: arc (simplified as straight line for now)
|
||||
double aRadius = std::stod(theCommands[i + 1]);
|
||||
// double aAngle = std::stod(theCommands[i + 2]); // Unused for now
|
||||
// Simplified: just move in current direction
|
||||
gp_Vec2d aMoveVec = aCurrentDir * aRadius;
|
||||
aCurrentPt.Translate(aMoveVec);
|
||||
aPoints.push_back(aCurrentPt);
|
||||
i += 2; // Skip next two parameters
|
||||
}
|
||||
}
|
||||
|
||||
// Convert 2D points to 3D points on the plane
|
||||
std::vector<gp_Pnt> a3DPoints;
|
||||
for (const gp_Pnt2d& aPt2d : aPoints)
|
||||
{
|
||||
gp_Pnt aPt3d = ElSLib::Value(aPt2d.X(), aPt2d.Y(), thePlane);
|
||||
a3DPoints.push_back(aPt3d);
|
||||
}
|
||||
|
||||
return CreatePolygonWire(a3DPoints, Standard_True);
|
||||
}
|
||||
|
||||
//! Create profile using typed commands (equivalent to TCL profile)
|
||||
//! plane: The reference plane/face (equivalent to "S face" in TCL)
|
||||
//! operations: List of profile operations
|
||||
static TopoDS_Shape CreateProfile(const gp_Pln& thePlane,
|
||||
const std::vector<ProfileOperation>& theOperations)
|
||||
{
|
||||
BRepBuilderAPI_MakeWire aMakeWire;
|
||||
|
||||
gp_Pnt2d aCurrentPt(0, 0); // Current point in 2D
|
||||
gp_Vec2d aCurrentDir(1, 0); // Current direction in 2D
|
||||
Standard_Boolean aFirstSet = Standard_False;
|
||||
gp_Pnt2d aFirstPt(0, 0);
|
||||
gp_Pln aWorkingPlane = thePlane; // Working plane that can be modified by P command
|
||||
|
||||
for (const auto& op : theOperations)
|
||||
{
|
||||
switch (op.cmd)
|
||||
{
|
||||
case ProfileCmd::O: // Set origin (affects the plane, not the starting point)
|
||||
if (op.params.size() >= 3)
|
||||
{
|
||||
// O command sets the origin of the plane coordinate system
|
||||
gp_Pnt aNewOrigin(op.params[0], op.params[1], op.params[2]);
|
||||
gp_Ax3 aNewAx3(aNewOrigin,
|
||||
aWorkingPlane.Axis().Direction(),
|
||||
aWorkingPlane.XAxis().Direction());
|
||||
aWorkingPlane = gp_Pln(aNewAx3);
|
||||
|
||||
// Profile still starts at (0,0) in the new plane coordinate system
|
||||
if (!aFirstSet)
|
||||
{
|
||||
aCurrentPt.SetCoord(0.0, 0.0); // TCL default: start at (0,0)
|
||||
aFirstPt = aCurrentPt;
|
||||
aFirstSet = Standard_True;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case ProfileCmd::P: // Set plane (P nx ny nz dx dy dz)
|
||||
if (op.params.size() >= 6)
|
||||
{
|
||||
// P command sets plane normal and X direction vectors
|
||||
gp_Vec aNormal(op.params[0], op.params[1], op.params[2]);
|
||||
gp_Vec aXDir(op.params[3], op.params[4], op.params[5]);
|
||||
|
||||
// Create new coordinate system
|
||||
gp_Dir aNormalDir(aNormal);
|
||||
gp_Dir aXDirection(aXDir);
|
||||
|
||||
// Note: Y direction computed as cross product: Y = Z x X (for right-handed system)
|
||||
// But DRAW uses standard X/Y axes based on normal direction
|
||||
|
||||
// Use the current plane's origin but new orientation
|
||||
gp_Ax3 aNewAx3(aWorkingPlane.Location(), aNormalDir, aXDirection);
|
||||
aWorkingPlane = gp_Pln(aNewAx3);
|
||||
}
|
||||
break;
|
||||
|
||||
case ProfileCmd::F: // Set first point
|
||||
if (op.params.size() >= 2)
|
||||
{
|
||||
aCurrentPt.SetCoord(op.params[0], op.params[1]);
|
||||
aFirstPt = aCurrentPt;
|
||||
aFirstSet = Standard_True;
|
||||
}
|
||||
break;
|
||||
|
||||
case ProfileCmd::X: // Translate along X
|
||||
if (op.params.size() >= 1)
|
||||
{
|
||||
// If first point not set, implicitly start at (0,0)
|
||||
if (!aFirstSet)
|
||||
{
|
||||
aCurrentPt.SetCoord(0.0, 0.0);
|
||||
aFirstPt = aCurrentPt;
|
||||
aFirstSet = Standard_True;
|
||||
}
|
||||
|
||||
gp_Pnt2d aNewPt(aCurrentPt.X() + op.params[0], aCurrentPt.Y());
|
||||
// Add line segment
|
||||
gp_Pnt aPt1 = ElSLib::Value(aCurrentPt.X(), aCurrentPt.Y(), aWorkingPlane);
|
||||
gp_Pnt aPt2 = ElSLib::Value(aNewPt.X(), aNewPt.Y(), aWorkingPlane);
|
||||
aMakeWire.Add(BRepBuilderAPI_MakeEdge(aPt1, aPt2));
|
||||
aCurrentPt = aNewPt;
|
||||
aCurrentDir = gp_Vec2d(op.params[0] > 0 ? 1 : -1, 0);
|
||||
}
|
||||
break;
|
||||
|
||||
case ProfileCmd::Y: // Translate along Y
|
||||
if (op.params.size() >= 1)
|
||||
{
|
||||
// If first point not set, implicitly start at (0,0)
|
||||
if (!aFirstSet)
|
||||
{
|
||||
aCurrentPt.SetCoord(0.0, 0.0);
|
||||
aFirstPt = aCurrentPt;
|
||||
aFirstSet = Standard_True;
|
||||
}
|
||||
|
||||
gp_Pnt2d aNewPt(aCurrentPt.X(), aCurrentPt.Y() + op.params[0]);
|
||||
// Add line segment
|
||||
gp_Pnt aPt1 = ElSLib::Value(aCurrentPt.X(), aCurrentPt.Y(), aWorkingPlane);
|
||||
gp_Pnt aPt2 = ElSLib::Value(aNewPt.X(), aNewPt.Y(), aWorkingPlane);
|
||||
aMakeWire.Add(BRepBuilderAPI_MakeEdge(aPt1, aPt2));
|
||||
aCurrentPt = aNewPt;
|
||||
aCurrentDir = gp_Vec2d(0, op.params[0] > 0 ? 1 : -1);
|
||||
}
|
||||
break;
|
||||
|
||||
case ProfileCmd::C: // Arc
|
||||
if (op.params.size() >= 2)
|
||||
{
|
||||
Standard_Real aRadius = Abs(op.params[0]);
|
||||
Standard_Real aAngleDeg = op.params[1];
|
||||
Standard_Real aAngleRad = aAngleDeg * M_PI / 180.0;
|
||||
|
||||
// Handle full circle case (360 degrees)
|
||||
if (Abs(aAngleDeg) >= 360.0)
|
||||
{
|
||||
// Create a full circle centered at current point (0,0 if not set)
|
||||
gp_Pnt2d aCenter2D = aFirstSet ? aCurrentPt : gp_Pnt2d(0, 0);
|
||||
gp_Pnt aCenter3D = ElSLib::Value(aCenter2D.X(), aCenter2D.Y(), aWorkingPlane);
|
||||
|
||||
// Create full circle
|
||||
gp_Circ aCirc(gp_Ax2(aCenter3D, aWorkingPlane.Axis().Direction()), aRadius);
|
||||
aMakeWire.Add(BRepBuilderAPI_MakeEdge(aCirc));
|
||||
|
||||
// Set current point to a point on the circle
|
||||
aCurrentPt = gp_Pnt2d(aCenter2D.X() + aRadius, aCenter2D.Y());
|
||||
aCurrentDir = gp_Vec2d(0, 1); // Tangent direction
|
||||
aFirstSet = Standard_True;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If first point not set, start at origin with default direction
|
||||
if (!aFirstSet)
|
||||
{
|
||||
aCurrentPt.SetCoord(0.0, 0.0);
|
||||
aCurrentDir = gp_Vec2d(1, 0); // Default direction: positive X
|
||||
aFirstPt = aCurrentPt;
|
||||
aFirstSet = Standard_True;
|
||||
}
|
||||
|
||||
// Create arc from current point
|
||||
gp_Vec2d aNormal(-aCurrentDir.Y(), aCurrentDir.X());
|
||||
gp_Pnt2d aCenter = aCurrentPt.Translated(aNormal * aRadius);
|
||||
|
||||
// Calculate end point
|
||||
Standard_Real aStartAngle =
|
||||
atan2(aCurrentPt.Y() - aCenter.Y(), aCurrentPt.X() - aCenter.X());
|
||||
Standard_Real aEndAngle = aStartAngle + aAngleRad;
|
||||
gp_Pnt2d aEndPt(aCenter.X() + aRadius * cos(aEndAngle),
|
||||
aCenter.Y() + aRadius * sin(aEndAngle));
|
||||
|
||||
// Create 3D arc
|
||||
gp_Pnt aPt1 = ElSLib::Value(aCurrentPt.X(), aCurrentPt.Y(), aWorkingPlane);
|
||||
gp_Pnt aPt2 = ElSLib::Value(aEndPt.X(), aEndPt.Y(), aWorkingPlane);
|
||||
gp_Pnt aCenter3D = ElSLib::Value(aCenter.X(), aCenter.Y(), aWorkingPlane);
|
||||
|
||||
gp_Circ aCirc(gp_Ax2(aCenter3D, aWorkingPlane.Axis().Direction()), aRadius);
|
||||
aMakeWire.Add(BRepBuilderAPI_MakeEdge(aCirc, aPt1, aPt2));
|
||||
|
||||
aCurrentPt = aEndPt;
|
||||
aCurrentDir = gp_Vec2d(cos(aEndAngle + M_PI / 2), sin(aEndAngle + M_PI / 2));
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case ProfileCmd::D: // Set direction vector
|
||||
if (op.params.size() >= 2)
|
||||
{
|
||||
aCurrentDir = gp_Vec2d(op.params[0], op.params[1]);
|
||||
aCurrentDir.Normalize();
|
||||
}
|
||||
break;
|
||||
|
||||
case ProfileCmd::W: // Make closed wire
|
||||
// W command closes the current wire - handled at end of function
|
||||
break;
|
||||
|
||||
default:
|
||||
// Other commands can be added as needed
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Close the wire if needed (TCL profiles are typically closed by default)
|
||||
if (aFirstSet && !aCurrentPt.IsEqual(aFirstPt, Precision::Confusion()))
|
||||
{
|
||||
// Add closing edge back to start
|
||||
gp_Pnt aPt1 = ElSLib::Value(aCurrentPt.X(), aCurrentPt.Y(), aWorkingPlane);
|
||||
gp_Pnt aPt2 = ElSLib::Value(aFirstPt.X(), aFirstPt.Y(), aWorkingPlane);
|
||||
aMakeWire.Add(BRepBuilderAPI_MakeEdge(aPt1, aPt2));
|
||||
}
|
||||
|
||||
EXPECT_TRUE(aMakeWire.IsDone()) << "Profile wire creation failed";
|
||||
TopoDS_Wire aWire = aMakeWire.Wire();
|
||||
|
||||
// Create face from wire
|
||||
BRepBuilderAPI_MakeFace aFaceMaker(aWire);
|
||||
EXPECT_TRUE(aFaceMaker.IsDone()) << "Profile face creation failed";
|
||||
return aFaceMaker.Face();
|
||||
}
|
||||
|
||||
//! Create profile from operations list (simplified version)
|
||||
static TopoDS_Shape CreateProfileFromOperations(
|
||||
const std::vector<ProfileOperation>& theOperations)
|
||||
{
|
||||
// Use default plane (XY plane)
|
||||
const gp_Pln aPlane(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1));
|
||||
return CreateProfile(aPlane, theOperations);
|
||||
}
|
||||
|
||||
//! Create rectangular profile from corner and dimensions
|
||||
static TopoDS_Wire CreateRectangularProfile(const gp_Pnt& theCorner,
|
||||
Standard_Real theX,
|
||||
Standard_Real theY)
|
||||
{
|
||||
std::vector<gp_Pnt> aPoints;
|
||||
aPoints.push_back(theCorner);
|
||||
aPoints.push_back(gp_Pnt(theCorner.X() + theX, theCorner.Y(), theCorner.Z()));
|
||||
aPoints.push_back(gp_Pnt(theCorner.X() + theX, theCorner.Y() + theY, theCorner.Z()));
|
||||
aPoints.push_back(gp_Pnt(theCorner.X(), theCorner.Y() + theY, theCorner.Z()));
|
||||
return CreatePolygonWire(aPoints, Standard_True);
|
||||
}
|
||||
|
||||
//! Create face from wire
|
||||
static TopoDS_Shape CreateFaceFromWire(const TopoDS_Wire& theWire)
|
||||
{
|
||||
BRepBuilderAPI_MakeFace aFaceMaker(theWire);
|
||||
if (!aFaceMaker.IsDone())
|
||||
{
|
||||
return TopoDS_Shape();
|
||||
}
|
||||
return aFaceMaker.Shape();
|
||||
}
|
||||
|
||||
//! Create prism by extruding shape along vector
|
||||
static TopoDS_Shape CreatePrism(const TopoDS_Shape& theProfile, const gp_Vec& theDirection)
|
||||
{
|
||||
BRepPrimAPI_MakePrism aPrismMaker(theProfile, theDirection);
|
||||
if (!aPrismMaker.IsDone())
|
||||
{
|
||||
return TopoDS_Shape();
|
||||
}
|
||||
return aPrismMaker.Shape();
|
||||
}
|
||||
|
||||
//! Create simple rectangular prism (like TCL profile operations)
|
||||
static TopoDS_Shape CreateRectangularPrism(const gp_Pnt& theCorner,
|
||||
Standard_Real theX,
|
||||
Standard_Real theY,
|
||||
Standard_Real theZ)
|
||||
{
|
||||
TopoDS_Wire aWire = CreateRectangularProfile(theCorner, theX, theY);
|
||||
TopoDS_Shape aFace = CreateFaceFromWire(aWire);
|
||||
return CreatePrism(aFace, gp_Vec(0, 0, theZ));
|
||||
}
|
||||
|
||||
//! Create revolution of a profile around an axis
|
||||
static TopoDS_Shape CreateRevolution(const TopoDS_Shape& theProfile,
|
||||
const gp_Ax1& theAxis,
|
||||
Standard_Real theAngle)
|
||||
{
|
||||
BRepPrimAPI_MakeRevol aRevolMaker(theProfile, theAxis, theAngle);
|
||||
EXPECT_TRUE(aRevolMaker.IsDone()) << "Revolution operation failed";
|
||||
return aRevolMaker.Shape();
|
||||
}
|
||||
|
||||
//! Get a face from a shape by index (for nexplode simulation)
|
||||
static TopoDS_Face GetFaceByIndex(const TopoDS_Shape& theShape, Standard_Integer theIndex)
|
||||
{
|
||||
TopExp_Explorer anExp(theShape, TopAbs_FACE);
|
||||
Standard_Integer aCurrentIndex = 1;
|
||||
|
||||
while (anExp.More())
|
||||
{
|
||||
if (aCurrentIndex == theIndex)
|
||||
{
|
||||
return TopoDS::Face(anExp.Current());
|
||||
}
|
||||
aCurrentIndex++;
|
||||
anExp.Next();
|
||||
}
|
||||
return TopoDS_Face(); // Return empty face if not found
|
||||
}
|
||||
|
||||
//! Apply rotation around Z-axis (equivalent to "trotate shape 0 0 0 0 0 1 angle")
|
||||
static TopoDS_Shape RotateZ(const TopoDS_Shape& theShape, Standard_Real theAngleDeg)
|
||||
{
|
||||
gp_Trsf aRotation;
|
||||
aRotation.SetRotation(gp_Ax1(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)), theAngleDeg * M_PI / 180.0);
|
||||
BRepBuilderAPI_Transform aTransform(theShape, aRotation);
|
||||
return aTransform.Shape();
|
||||
}
|
||||
|
||||
//! Apply rotation around Y-axis (equivalent to "trotate shape 0 0 0 0 1 0 angle")
|
||||
static TopoDS_Shape RotateY(const TopoDS_Shape& theShape, Standard_Real theAngleDeg)
|
||||
{
|
||||
gp_Trsf aRotation;
|
||||
aRotation.SetRotation(gp_Ax1(gp_Pnt(0, 0, 0), gp_Dir(0, 1, 0)), theAngleDeg * M_PI / 180.0);
|
||||
BRepBuilderAPI_Transform aTransform(theShape, aRotation);
|
||||
return aTransform.Shape();
|
||||
}
|
||||
|
||||
//! Apply rotation around X-axis (equivalent to "trotate shape 0 0 0 1 0 0 angle")
|
||||
static TopoDS_Shape RotateX(const TopoDS_Shape& theShape, Standard_Real theAngleDeg)
|
||||
{
|
||||
gp_Trsf aRotation;
|
||||
aRotation.SetRotation(gp_Ax1(gp_Pnt(0, 0, 0), gp_Dir(1, 0, 0)), theAngleDeg * M_PI / 180.0);
|
||||
BRepBuilderAPI_Transform aTransform(theShape, aRotation);
|
||||
return aTransform.Shape();
|
||||
}
|
||||
|
||||
//! Apply common test rotation: Z(-90deg) then Y(-45deg) - used in many TCL tests
|
||||
static TopoDS_Shape RotateStandard(const TopoDS_Shape& theShape)
|
||||
{
|
||||
TopoDS_Shape aResult = RotateZ(theShape, -90.0);
|
||||
return RotateY(aResult, -45.0);
|
||||
}
|
||||
|
||||
//! Apply translation (equivalent to "ttranslate shape dx dy dz")
|
||||
static TopoDS_Shape Translate(const TopoDS_Shape& theShape,
|
||||
Standard_Real theDx,
|
||||
Standard_Real theDy,
|
||||
Standard_Real theDz)
|
||||
{
|
||||
gp_Trsf aTranslation;
|
||||
aTranslation.SetTranslation(gp_Vec(theDx, theDy, theDz));
|
||||
BRepBuilderAPI_Transform aTransform(theShape, aTranslation);
|
||||
return aTransform.Shape();
|
||||
}
|
||||
|
||||
//! Apply Y-axis 90-degree rotation (common pattern: "trotate shape 0 0 1 0 1 0 90")
|
||||
static TopoDS_Shape RotateY90(const TopoDS_Shape& theShape)
|
||||
{
|
||||
gp_Trsf aRotation;
|
||||
aRotation.SetRotation(gp_Ax1(gp_Pnt(0, 0, 1), gp_Dir(0, 1, 0)), 90.0 * M_PI / 180.0);
|
||||
BRepBuilderAPI_Transform aTransform(theShape, aRotation);
|
||||
return aTransform.Shape();
|
||||
}
|
||||
|
||||
//! Create unit sphere and box pair (most common test setup)
|
||||
static void CreateSphereAndBox(TopoDS_Shape& theSphere, TopoDS_Shape& theBox)
|
||||
{
|
||||
theSphere = CreateUnitSphere();
|
||||
theBox = CreateUnitBox();
|
||||
}
|
||||
|
||||
//! Create two identical unit boxes at origin (common BOP test setup)
|
||||
static void CreateIdenticalBoxes(TopoDS_Shape& theBox1, TopoDS_Shape& theBox2)
|
||||
{
|
||||
theBox1 = CreateBox(gp_Pnt(0, 0, 0), 1.0, 1.0, 1.0);
|
||||
theBox2 = CreateBox(gp_Pnt(0, 0, 0), 1.0, 1.0, 1.0);
|
||||
}
|
||||
|
||||
//! Create NURBS box and regular box pair (common B-series test pattern)
|
||||
static void CreateNurbsAndRegularBox(TopoDS_Shape& theNurbsBox,
|
||||
TopoDS_Shape& theRegularBox,
|
||||
const gp_Pnt& theNurbsCorner = gp_Pnt(0, 0, 0),
|
||||
const gp_Pnt& theRegularCorner = gp_Pnt(0, 1, 0),
|
||||
Standard_Real theNurbsX = 1.0,
|
||||
Standard_Real theNurbsY = 1.0,
|
||||
Standard_Real theNurbsZ = 1.0,
|
||||
Standard_Real theRegularX = 1.0,
|
||||
Standard_Real theRegularY = 0.5,
|
||||
Standard_Real theRegularZ = 1.0)
|
||||
{
|
||||
theNurbsBox = CreateBox(theNurbsCorner, theNurbsX, theNurbsY, theNurbsZ);
|
||||
theNurbsBox = ConvertToNurbs(theNurbsBox);
|
||||
|
||||
theRegularBox = CreateBox(theRegularCorner, theRegularX, theRegularY, theRegularZ);
|
||||
}
|
||||
|
||||
//! Create a blend (fillet) on specified edge of a shape
|
||||
static TopoDS_Shape CreateBlend(const TopoDS_Shape& theShape,
|
||||
Standard_Integer theEdgeIndex,
|
||||
Standard_Real theRadius)
|
||||
{
|
||||
// Get the edge by index (like explode command)
|
||||
TopExp_Explorer anExp(theShape, TopAbs_EDGE);
|
||||
Standard_Integer aCurrentIndex = 1;
|
||||
TopoDS_Edge aTargetEdge;
|
||||
|
||||
while (anExp.More())
|
||||
{
|
||||
if (aCurrentIndex == theEdgeIndex)
|
||||
{
|
||||
aTargetEdge = TopoDS::Edge(anExp.Current());
|
||||
break;
|
||||
}
|
||||
aCurrentIndex++;
|
||||
anExp.Next();
|
||||
}
|
||||
|
||||
if (aTargetEdge.IsNull())
|
||||
{
|
||||
return theShape; // Return original shape if edge not found
|
||||
}
|
||||
|
||||
// Create fillet maker (matching DRAW blend command)
|
||||
// DRAW uses ChFi3d_Rational by default
|
||||
BRepFilletAPI_MakeFillet aFilletMaker(theShape, ChFi3d_Rational);
|
||||
|
||||
// Set parameters exactly like DRAW command does
|
||||
// SetParams(ta, tesp, t2d, t3d, t2d, fl) - exact DRAW defaults
|
||||
aFilletMaker.SetParams(1e-2, 1.0e-4, 1.e-5, 1.e-4, 1.e-5, 1.e-3);
|
||||
|
||||
// SetContinuity(blend_cont, tapp_angle) - exact DRAW defaults
|
||||
aFilletMaker.SetContinuity(GeomAbs_C1, 1.e-2);
|
||||
|
||||
aFilletMaker.Add(theRadius, aTargetEdge);
|
||||
aFilletMaker.Build();
|
||||
|
||||
if (!aFilletMaker.IsDone())
|
||||
{
|
||||
return TopoDS_Shape(); // Return null shape if fillet failed, like DRAW command
|
||||
}
|
||||
|
||||
return aFilletMaker.Shape();
|
||||
}
|
||||
|
||||
//! Create a cylinder on a specified plane (like TCL pcylinder command)
|
||||
static TopoDS_Shape CreateCylinderOnPlane(const gp_Pln& thePlane,
|
||||
Standard_Real theRadius,
|
||||
Standard_Real theHeight)
|
||||
{
|
||||
// Use DRAW pcylinder approach exactly:
|
||||
// S = BRepPrimAPI_MakeCylinder(P->Pln().Position().Ax2(), radius, height);
|
||||
const gp_Ax2 anAx2 = thePlane.Position().Ax2();
|
||||
|
||||
// Use DRAW pcylinder approach exactly:
|
||||
// S = BRepPrimAPI_MakeCylinder(P->Pln().Position().Ax2(), radius, height);
|
||||
BRepPrimAPI_MakeCylinder aCylinderMaker(anAx2, theRadius, theHeight);
|
||||
aCylinderMaker.Build();
|
||||
|
||||
if (!aCylinderMaker.IsDone())
|
||||
{
|
||||
return TopoDS_Shape();
|
||||
}
|
||||
|
||||
return aCylinderMaker.Shape();
|
||||
}
|
||||
};
|
||||
|
||||
//==================================================================================================
|
||||
//! Base test class for BRepAlgoAPI operations
|
||||
//==================================================================================================
|
||||
class BRepAlgoAPI_TestBase : public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
void SetUp() override { myTolerance = BOPTest_Utilities::DefaultTolerance(); }
|
||||
|
||||
//! Perform BRepAlgoAPI Cut operation
|
||||
TopoDS_Shape PerformCut(const TopoDS_Shape& theObject, const TopoDS_Shape& theTool)
|
||||
{
|
||||
BRepAlgoAPI_Cut aCutter(theObject, theTool);
|
||||
EXPECT_TRUE(aCutter.IsDone()) << "BRepAlgoAPI_Cut operation failed";
|
||||
return aCutter.Shape();
|
||||
}
|
||||
|
||||
//! Perform BRepAlgoAPI Fuse operation
|
||||
TopoDS_Shape PerformFuse(const TopoDS_Shape& theShape1, const TopoDS_Shape& theShape2)
|
||||
{
|
||||
BRepAlgoAPI_Fuse aFuser(theShape1, theShape2);
|
||||
EXPECT_TRUE(aFuser.IsDone()) << "BRepAlgoAPI_Fuse operation failed";
|
||||
return aFuser.Shape();
|
||||
}
|
||||
|
||||
//! Perform BRepAlgoAPI Common operation
|
||||
TopoDS_Shape PerformCommon(const TopoDS_Shape& theShape1, const TopoDS_Shape& theShape2)
|
||||
{
|
||||
BRepAlgoAPI_Common aCommoner(theShape1, theShape2);
|
||||
EXPECT_TRUE(aCommoner.IsDone()) << "BRepAlgoAPI_Common operation failed";
|
||||
return aCommoner.Shape();
|
||||
}
|
||||
|
||||
//! Validate result properties against expected values
|
||||
void ValidateResult(const TopoDS_Shape& theResult,
|
||||
Standard_Real theExpectedSurfaceArea = -1.0,
|
||||
Standard_Real theExpectedVolume = -1.0,
|
||||
Standard_Boolean theExpectedEmpty = Standard_False)
|
||||
{
|
||||
if (theExpectedEmpty)
|
||||
{
|
||||
EXPECT_TRUE(BOPTest_Utilities::IsEmpty(theResult, myTolerance)) << "Result should be empty";
|
||||
return;
|
||||
}
|
||||
|
||||
EXPECT_FALSE(theResult.IsNull()) << "Result shape should not be null";
|
||||
|
||||
if (theExpectedSurfaceArea >= 0.0)
|
||||
{
|
||||
const Standard_Real aSurfaceArea = BOPTest_Utilities::GetSurfaceArea(theResult);
|
||||
EXPECT_NEAR(aSurfaceArea, theExpectedSurfaceArea, 5000.0) << "Surface area mismatch";
|
||||
}
|
||||
|
||||
if (theExpectedVolume >= 0.0)
|
||||
{
|
||||
const Standard_Real aVolume = BOPTest_Utilities::GetVolume(theResult);
|
||||
EXPECT_NEAR(aVolume, theExpectedVolume, myTolerance) << "Volume mismatch";
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
Standard_Real myTolerance;
|
||||
};
|
||||
|
||||
//==================================================================================================
|
||||
//! Base test class for BOPAlgo_BOP operations (direct and two-step)
|
||||
//==================================================================================================
|
||||
class BOPAlgo_TestBase : public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
void SetUp() override
|
||||
{
|
||||
myTolerance = BOPTest_Utilities::DefaultTolerance();
|
||||
myFuzzyValue = BOPTest_Utilities::DefaultFuzzyValue();
|
||||
myPaveFiller.reset();
|
||||
}
|
||||
|
||||
void TearDown() override { myPaveFiller.reset(); }
|
||||
|
||||
//! Direct BOP operation (equivalent to bcut/bfuse/bcommon/btuc commands)
|
||||
TopoDS_Shape PerformDirectBOP(const TopoDS_Shape& theShape1,
|
||||
const TopoDS_Shape& theShape2,
|
||||
BOPAlgo_Operation theOp)
|
||||
{
|
||||
Handle(NCollection_BaseAllocator) aAL = NCollection_BaseAllocator::CommonBaseAllocator();
|
||||
BOPAlgo_BOP aBOP(aAL);
|
||||
|
||||
aBOP.AddArgument(theShape1);
|
||||
aBOP.AddTool(theShape2);
|
||||
aBOP.SetOperation(theOp);
|
||||
aBOP.SetFuzzyValue(myFuzzyValue);
|
||||
aBOP.SetRunParallel(Standard_False);
|
||||
aBOP.SetNonDestructive(Standard_False);
|
||||
|
||||
aBOP.Perform();
|
||||
|
||||
EXPECT_FALSE(aBOP.HasErrors()) << "Direct BOP operation failed";
|
||||
return aBOP.Shape();
|
||||
}
|
||||
|
||||
//! Two-step BOP operation (equivalent to bop + bopXXX commands)
|
||||
TopoDS_Shape PerformTwoStepBOP(const TopoDS_Shape& theShape1,
|
||||
const TopoDS_Shape& theShape2,
|
||||
BOPAlgo_Operation theOp)
|
||||
{
|
||||
// Step 1: Prepare PaveFiller (equivalent to "bop s1 s2")
|
||||
PreparePaveFiller(theShape1, theShape2);
|
||||
|
||||
// Step 2: Perform BOP operation (equivalent to "bopXXX result")
|
||||
return PerformBOPWithPaveFiller(theOp);
|
||||
}
|
||||
|
||||
//! Prepare PaveFiller for two-step operations
|
||||
void PreparePaveFiller(const TopoDS_Shape& theShape1, const TopoDS_Shape& theShape2)
|
||||
{
|
||||
TopTools_ListOfShape aLC;
|
||||
aLC.Append(theShape1);
|
||||
aLC.Append(theShape2);
|
||||
|
||||
Handle(NCollection_BaseAllocator) aAL = NCollection_BaseAllocator::CommonBaseAllocator();
|
||||
myPaveFiller = std::make_unique<BOPAlgo_PaveFiller>(aAL);
|
||||
|
||||
myPaveFiller->SetArguments(aLC);
|
||||
myPaveFiller->SetFuzzyValue(myFuzzyValue);
|
||||
myPaveFiller->SetRunParallel(Standard_False);
|
||||
myPaveFiller->SetNonDestructive(Standard_False);
|
||||
|
||||
myPaveFiller->Perform();
|
||||
EXPECT_FALSE(myPaveFiller->HasErrors()) << "PaveFiller preparation failed";
|
||||
}
|
||||
|
||||
//! Perform BOP operation using prepared PaveFiller
|
||||
TopoDS_Shape PerformBOPWithPaveFiller(BOPAlgo_Operation theOp)
|
||||
{
|
||||
EXPECT_TRUE(myPaveFiller != nullptr) << "PaveFiller must be prepared first";
|
||||
|
||||
BOPAlgo_BOP aBOP;
|
||||
const TopTools_ListOfShape& aArguments = myPaveFiller->Arguments();
|
||||
EXPECT_EQ(aArguments.Extent(), 2) << "Wrong number of arguments";
|
||||
|
||||
const TopoDS_Shape& aS1 = aArguments.First();
|
||||
const TopoDS_Shape& aS2 = aArguments.Last();
|
||||
|
||||
aBOP.AddArgument(aS1);
|
||||
aBOP.AddTool(aS2);
|
||||
aBOP.SetOperation(theOp);
|
||||
aBOP.SetRunParallel(Standard_False);
|
||||
|
||||
aBOP.PerformWithFiller(*myPaveFiller);
|
||||
|
||||
EXPECT_FALSE(aBOP.HasErrors()) << "BOP operation with PaveFiller failed";
|
||||
return aBOP.Shape();
|
||||
}
|
||||
|
||||
//! Validate result properties against expected values
|
||||
void ValidateResult(const TopoDS_Shape& theResult,
|
||||
Standard_Real theExpectedSurfaceArea = -1.0,
|
||||
Standard_Real theExpectedVolume = -1.0,
|
||||
Standard_Boolean theExpectedEmpty = Standard_False)
|
||||
{
|
||||
if (theExpectedEmpty)
|
||||
{
|
||||
EXPECT_TRUE(BOPTest_Utilities::IsEmpty(theResult, myTolerance)) << "Result should be empty";
|
||||
return;
|
||||
}
|
||||
|
||||
EXPECT_FALSE(theResult.IsNull()) << "Result shape should not be null";
|
||||
|
||||
if (theExpectedSurfaceArea >= 0.0)
|
||||
{
|
||||
const Standard_Real aSurfaceArea = BOPTest_Utilities::GetSurfaceArea(theResult);
|
||||
EXPECT_NEAR(aSurfaceArea, theExpectedSurfaceArea, 5000.0) << "Surface area mismatch";
|
||||
}
|
||||
|
||||
if (theExpectedVolume >= 0.0)
|
||||
{
|
||||
const Standard_Real aVolume = BOPTest_Utilities::GetVolume(theResult);
|
||||
EXPECT_NEAR(aVolume, theExpectedVolume, myTolerance) << "Volume mismatch";
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
Standard_Real myTolerance;
|
||||
Standard_Real myFuzzyValue;
|
||||
std::unique_ptr<BOPAlgo_PaveFiller> myPaveFiller;
|
||||
};
|
||||
|
||||
#endif // _BOPTest_Utilities_HeaderFile
|
||||
@@ -0,0 +1,33 @@
|
||||
// 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 "BOPTest_Utilities.pxx"
|
||||
|
||||
//==================================================================================================
|
||||
// BOPCommon Simple Tests - migrating from /tests/boolean/bopcommon_simple/
|
||||
//==================================================================================================
|
||||
|
||||
class BOPCommonSimpleTest : public BRepAlgoAPI_TestBase
|
||||
{
|
||||
};
|
||||
|
||||
// Test bopcommon_simple/A1: box b1 0 0 0 1 1 1; box b2 0 0 0 1 1 1; bop b1 b2; bopcommon result;
|
||||
// checkprops result -s 6
|
||||
TEST_F(BOPCommonSimpleTest, IdenticalBoxes_A1)
|
||||
{
|
||||
const TopoDS_Shape aBox1 = BOPTest_Utilities::CreateBox(gp_Pnt(0, 0, 0), 1.0, 1.0, 1.0);
|
||||
const TopoDS_Shape aBox2 = BOPTest_Utilities::CreateBox(gp_Pnt(0, 0, 0), 1.0, 1.0, 1.0);
|
||||
|
||||
const TopoDS_Shape aResult = PerformCommon(aBox1, aBox2);
|
||||
ValidateResult(aResult, 6.0);
|
||||
}
|
||||
1768
src/ModelingAlgorithms/TKBO/GTests/BRepAlgoAPI_Cut_Test.cxx
Normal file
1768
src/ModelingAlgorithms/TKBO/GTests/BRepAlgoAPI_Cut_Test.cxx
Normal file
File diff suppressed because it is too large
Load Diff
1410
src/ModelingAlgorithms/TKBO/GTests/BRepAlgoAPI_Cut_Test_1.cxx
Normal file
1410
src/ModelingAlgorithms/TKBO/GTests/BRepAlgoAPI_Cut_Test_1.cxx
Normal file
File diff suppressed because it is too large
Load Diff
2026
src/ModelingAlgorithms/TKBO/GTests/BRepAlgoAPI_Fuse_Test.cxx
Normal file
2026
src/ModelingAlgorithms/TKBO/GTests/BRepAlgoAPI_Fuse_Test.cxx
Normal file
File diff suppressed because it is too large
Load Diff
@@ -2,4 +2,9 @@
|
||||
set(OCCT_TKBO_GTests_FILES_LOCATION "${CMAKE_CURRENT_LIST_DIR}")
|
||||
|
||||
set(OCCT_TKBO_GTests_FILES
|
||||
BRepAlgoAPI_Cut_Test.cxx
|
||||
BRepAlgoAPI_Cut_Test_1.cxx
|
||||
BRepAlgoAPI_Fuse_Test.cxx
|
||||
BRepAlgoAPI_Common_Test.cxx
|
||||
BOPAlgo_BOP_Test.cxx
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user