Loading files
3d097a032This file is pinned to the selected release. Download the original to open it in your design tool.
// 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 <BRep_Tool.hxx>
#include <BRepCheck_Analyzer.hxx>
#include <BRepPrimAPI_MakeTorus.hxx>
#include <Geom_Surface.hxx>
#include <Geom_ToroidalSurface.hxx>
#include <gp_Ax2.hxx>
#include <gp_Pnt.hxx>
#include <TopExp_Explorer.hxx>
#include <TopoDS.hxx>
#include <TopoDS_Face.hxx>
#include <TopoDS_Shape.hxx>
#include <gtest/gtest.h>
namespace
{
//! Helper function to create partial torus and check validity.
//! @param theR1 Major radius
//! @param theR2 Minor radius
//! @param theAngle1 First angle on the minor circle (V parameter)
//! @param theAngle2 Second angle on the minor circle (V parameter)
//! @param theAngle Angle around the major circle (U parameter), default 2*PI
//! @return true if the resulting shape is valid
bool createAndCheckPartialTorus(double theR1,
double theR2,
double theAngle1,
double theAngle2,
double theAngle = 2.0 * M_PI)
{
gp_Ax2 anAxis(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1));
BRepPrimAPI_MakeTorus aMakeTorus(anAxis, theR1, theR2, theAngle1, theAngle2, theAngle);
TopoDS_Shape aShape = aMakeTorus.Shape();
if (!aMakeTorus.IsDone() || aShape.IsNull())
{
return false;
}
BRepCheck_Analyzer anAnalyzer(aShape);
return anAnalyzer.IsValid();
}
} // namespace
// Test full torus creation (no angle parameters)
TEST(BRepPrimAPI_MakeTorusTest, FullTorus)
{
const double R1 = 10.0;
const double R2 = 2.0;
BRepPrimAPI_MakeTorus aMakeTorus(R1, R2);
TopoDS_Shape aShape = aMakeTorus.Shape();
ASSERT_TRUE(aMakeTorus.IsDone()) << "Full torus creation failed";
ASSERT_FALSE(aShape.IsNull()) << "Resulting shape is null";
BRepCheck_Analyzer anAnalyzer(aShape);
EXPECT_TRUE(anAnalyzer.IsValid()) << "Full torus shape is not valid";
// A full torus should have exactly 1 face
int aFaceCount = 0;
for (TopExp_Explorer anExp(aShape, TopAbs_FACE); anExp.More(); anExp.Next())
{
aFaceCount++;
}
EXPECT_EQ(aFaceCount, 1) << "Expected 1 face for full torus";
}
// Test partial torus with only U angle (slice around Z axis)
TEST(BRepPrimAPI_MakeTorusTest, PartialTorusAngleOnly)
{
const double R1 = 10.0;
const double R2 = 2.0;
const double angle = M_PI; // Half torus around Z axis
BRepPrimAPI_MakeTorus aMakeTorus(R1, R2, angle);
TopoDS_Shape aShape = aMakeTorus.Shape();
ASSERT_TRUE(aMakeTorus.IsDone()) << "Partial torus (angle only) creation failed";
ASSERT_FALSE(aShape.IsNull()) << "Resulting shape is null";
BRepCheck_Analyzer anAnalyzer(aShape);
EXPECT_TRUE(anAnalyzer.IsValid()) << "Partial torus (angle only) shape is not valid";
// Should have: 1 lateral + 2 planar (start/end sides)
int aFaceCount = 0;
for (TopExp_Explorer anExp(aShape, TopAbs_FACE); anExp.More(); anExp.Next())
{
aFaceCount++;
}
EXPECT_EQ(aFaceCount, 3) << "Expected 3 faces for partial torus with angle only";
}
// Test that the lateral face surface has correct parameterization
TEST(BRepPrimAPI_MakeTorusTest, LateralFaceParameterization)
{
const double R1 = 5.0;
const double R2 = 1.0;
BRepPrimAPI_MakeTorus aMakeTorus(R1, R2);
TopoDS_Shape aShape = aMakeTorus.Shape();
ASSERT_TRUE(aMakeTorus.IsDone());
TopExp_Explorer anExp(aShape, TopAbs_FACE);
ASSERT_TRUE(anExp.More()) << "No faces found";
const TopoDS_Face& aFace = TopoDS::Face(anExp.Current());
occ::handle<Geom_Surface> aSurface = BRep_Tool::Surface(aFace);
occ::handle<Geom_ToroidalSurface> aTorusSurf = occ::down_cast<Geom_ToroidalSurface>(aSurface);
ASSERT_FALSE(aTorusSurf.IsNull()) << "Lateral face is not a toroidal surface";
EXPECT_NEAR(aTorusSurf->MajorRadius(), R1, 1e-10);
EXPECT_NEAR(aTorusSurf->MinorRadius(), R2, 1e-10);
// At V = PI/2 (top of tube), Z should be +R2
gp_Pnt aPnt;
aTorusSurf->D0(0.0, M_PI_2, aPnt);
EXPECT_NEAR(aPnt.Z(), R2, 1e-10) << "At V=PI/2, Z should be +MinorRadius";
// At V = -PI/2 (bottom of tube), Z should be -R2
aTorusSurf->D0(0.0, -M_PI_2, aPnt);
EXPECT_NEAR(aPnt.Z(), -R2, 1e-10) << "At V=-PI/2, Z should be -MinorRadius";
}
// =============================================================================
// Tests for partial torus with V parameter range (angle1, angle2)
// These test various angle ranges to verify which configurations produce valid shapes.
// =============================================================================
// Working case: V range symmetric around 0 (-PI/2 to +PI/2)
// Bug 33133 original report - this case actually works
TEST(BRepPrimAPI_MakeTorusTest, PartialTorus_SymmetricAroundZero)
{
const double R1 = 1.0;
const double R2 = 0.1;
const double angle1 = -M_PI_2; // -90 degrees
const double angle2 = M_PI_2; // +90 degrees
const double angle = 2.094; // ~120 degrees
gp_Ax2 anAxis(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1));
BRepPrimAPI_MakeTorus aMakeTorus(anAxis, R1, R2, angle1, angle2, angle);
TopoDS_Shape aShape = aMakeTorus.Shape();
ASSERT_TRUE(aMakeTorus.IsDone()) << "Torus creation failed";
ASSERT_FALSE(aShape.IsNull()) << "Resulting shape is null";
BRepCheck_Analyzer anAnalyzer(aShape);
EXPECT_TRUE(anAnalyzer.IsValid()) << "Symmetric partial torus should be valid";
// Count faces: 1 lateral + 2 top/bottom + 2 start/end = 5
int aFaceCount = 0;
for (TopExp_Explorer anExp(aShape, TopAbs_FACE); anExp.More(); anExp.Next())
{
aFaceCount++;
}
EXPECT_EQ(aFaceCount, 5) << "Expected 5 faces for partial torus with sides";
}
// Working case: Smaller symmetric range (-45 to +45 degrees)
TEST(BRepPrimAPI_MakeTorusTest, PartialTorus_SmallSymmetricRange)
{
const double angle1 = -M_PI / 4.0; // -45 degrees
const double angle2 = M_PI / 4.0; // +45 degrees
bool isValid = createAndCheckPartialTorus(10.0, 2.0, angle1, angle2, M_PI / 4.0);
EXPECT_TRUE(isValid) << "Small symmetric range (-45 to +45) should produce valid shape";
}
// Bug 23612: V range from 90 to 270 degrees (positive values, not crossing 0)
// This test verifies the fix for inverted height ordering in BRepPrim_OneAxis
TEST(BRepPrimAPI_MakeTorusTest, PartialTorus_Bug23612)
{
const double R1 = 10.0;
const double R2 = 2.0;
const double angle1 = M_PI_2; // 90 degrees
const double angle2 = 3.0 * M_PI_2; // 270 degrees
const double angle = M_PI / 4.0; // 45 degrees
gp_Ax2 anAxis(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1));
BRepPrimAPI_MakeTorus aMakeTorus(anAxis, R1, R2, angle1, angle2, angle);
TopoDS_Shape aShape = aMakeTorus.Shape();
ASSERT_TRUE(aMakeTorus.IsDone()) << "Torus creation failed";
ASSERT_FALSE(aShape.IsNull()) << "Resulting shape is null";
BRepCheck_Analyzer anAnalyzer(aShape);
// Fixed: V range (90, 270) now produces valid shape with correct height ordering handling
EXPECT_TRUE(anAnalyzer.IsValid())
<< "Bug 23612 fixed: V range (90, 270) should produce valid shape";
}
// V range from 0 to 180 degrees (top half of minor circle)
// This is an "equal heights" case: sin(0) = sin(180) = 0
// Known limitation: Equal heights cause degenerate AxisEdge topology,
// resulting in UnorientableShape. This is separate from Bug 23612.
TEST(BRepPrimAPI_MakeTorusTest, PartialTorus_ZeroTo180)
{
const double angle1 = 0.0;
const double angle2 = M_PI; // 180 degrees
bool isValid = createAndCheckPartialTorus(10.0, 2.0, angle1, angle2, M_PI / 4.0);
// Known limitation: equal heights at endpoints cause topology issues
// TODO: This could be fixed by using a different topology when heights are equal
EXPECT_FALSE(isValid) << "Known limitation: equal heights (sin(0)=sin(180)=0) not supported";
}
// V range from -180 to 0 degrees (bottom half of minor circle)
// This is an "equal heights" case: sin(-180) = sin(0) = 0
// Known limitation: see PartialTorus_ZeroTo180
TEST(BRepPrimAPI_MakeTorusTest, PartialTorus_Minus180ToZero)
{
const double angle1 = -M_PI; // -180 degrees
const double angle2 = 0.0;
bool isValid = createAndCheckPartialTorus(10.0, 2.0, angle1, angle2, M_PI / 4.0);
// Known limitation: equal heights at endpoints cause topology issues
EXPECT_FALSE(isValid) << "Known limitation: equal heights (sin(-180)=sin(0)=0) not supported";
}
// V range from 180 to 360 degrees (bottom half of minor circle, alternate params)
// This is an "equal heights" case: sin(180) = sin(360) = 0
// Known limitation: see PartialTorus_ZeroTo180
TEST(BRepPrimAPI_MakeTorusTest, PartialTorus_180To360)
{
const double angle1 = M_PI; // 180 degrees
const double angle2 = 2 * M_PI; // 360 degrees
bool isValid = createAndCheckPartialTorus(10.0, 2.0, angle1, angle2, M_PI / 4.0);
// Known limitation: equal heights at endpoints cause topology issues
EXPECT_FALSE(isValid) << "Known limitation: equal heights (sin(180)=sin(360)=0) not supported";
}
// V range with both positive values not crossing 0 (+45 to +135 degrees)
// This is an "equal heights" case: sin(45) = sin(135) ~ 0.707
// Known limitation: see PartialTorus_ZeroTo180
TEST(BRepPrimAPI_MakeTorusTest, PartialTorus_PositiveRange)
{
const double angle1 = M_PI / 4.0; // 45 degrees
const double angle2 = 3.0 * M_PI / 4.0; // 135 degrees
bool isValid = createAndCheckPartialTorus(10.0, 2.0, angle1, angle2, M_PI / 4.0);
// Known limitation: equal heights at endpoints cause topology issues
EXPECT_FALSE(isValid) << "Known limitation: equal heights (sin(45)=sin(135)) not supported";
}
// V range with both negative values not crossing 0 (-135 to -45 degrees)
// This is an "equal heights" case: sin(-135) = sin(-45) ~ -0.707
// Known limitation: see PartialTorus_ZeroTo180
TEST(BRepPrimAPI_MakeTorusTest, PartialTorus_NegativeRange)
{
const double angle1 = -3.0 * M_PI / 4.0; // -135 degrees
const double angle2 = -M_PI / 4.0; // -45 degrees
bool isValid = createAndCheckPartialTorus(10.0, 2.0, angle1, angle2, M_PI / 4.0);
// Known limitation: equal heights at endpoints cause topology issues
EXPECT_FALSE(isValid) << "Known limitation: equal heights (sin(-135)=sin(-45)) not supported";
}
// V range asymmetric but crossing 0 (-30 to +60 degrees)
// This works because the range stays within (-PI/2, PI/2)
TEST(BRepPrimAPI_MakeTorusTest, PartialTorus_AsymmetricCrossingZero)
{
const double angle1 = -M_PI / 6.0; // -30 degrees
const double angle2 = M_PI / 3.0; // +60 degrees
bool isValid = createAndCheckPartialTorus(10.0, 2.0, angle1, angle2, M_PI / 4.0);
EXPECT_TRUE(isValid) << "Asymmetric range crossing 0 but within (-PI/2, PI/2) should be valid";
}
// Small positive range (+30 to +60 degrees)
// This works because the range stays within (-PI/2, PI/2)
TEST(BRepPrimAPI_MakeTorusTest, PartialTorus_SmallPositiveRange)
{
const double angle1 = M_PI / 6.0; // 30 degrees
const double angle2 = M_PI / 3.0; // 60 degrees
bool isValid = createAndCheckPartialTorus(10.0, 2.0, angle1, angle2, M_PI / 4.0);
EXPECT_TRUE(isValid) << "Small positive range within (-PI/2, PI/2) should be valid";
}
// =============================================================================
// Root cause analysis test
// =============================================================================
//! This test documents the root cause of bugs 23612/33133:
//! BRepPrim_OneAxis assumes MeridianValue(VMax).Y() > MeridianValue(VMin).Y(),
//! i.e., that the "top" (max V parameter) is geometrically above the "bottom".
//! For circular meridians (torus), this is only true for certain V ranges.
//!
//! The meridian circle parameterization for torus:
//! X(V) = Major + Minor * cos(V) [distance from axis]
//! Y(V) = Minor * sin(V) [height/Z coordinate]
//!
//! Working ranges: Y increases as V increases (e.g., -90 to +90 deg)
//! Failing ranges: Y decreases as V increases (e.g., 90 to 270 deg)
//! or Y is equal at VMin and VMax (e.g., 45 to 135 deg)
TEST(BRepPrimAPI_MakeTorusTest, RootCause_HeightOrderingAssumption)
{
const double R1 = 10.0;
const double R2 = 2.0;
// For a torus meridian: Y(V) = R2 * sin(V)
auto computeHeight = [R2](double angleRad) { return R2 * std::sin(angleRad); };
// Case 1: Working range (-90 to +90 deg)
// Y(-90 deg) = -2, Y(+90 deg) = +2 => Y(VMax) > Y(VMin)
{
const double vMin = -M_PI_2;
const double vMax = M_PI_2;
double yMin = computeHeight(vMin);
double yMax = computeHeight(vMax);
EXPECT_GT(yMax, yMin) << "Working case: Y(VMax) should be > Y(VMin)";
bool isValid = createAndCheckPartialTorus(R1, R2, vMin, vMax, M_PI / 4.0);
EXPECT_TRUE(isValid) << "Range with correct height ordering should produce valid shape";
}
// Case 2: Previously failing range (90 to 270 deg) - Bug 23612 (FIXED)
// Y(90 deg) = +2, Y(270 deg) = -2 => Y(VMax) < Y(VMin) - height ordering is inverted
// Fix: BRepPrim_OneAxis now detects inverted height ordering and adjusts
// edge vertex 'first' flags and wire 'reversed' flags accordingly
{
const double vMin = M_PI_2; // 90 deg
const double vMax = 3.0 * M_PI_2; // 270 deg
double yMin = computeHeight(vMin);
double yMax = computeHeight(vMax);
EXPECT_LT(yMax, yMin) << "Height ordering is inverted: Y(VMax) < Y(VMin)";
bool isValid = createAndCheckPartialTorus(R1, R2, vMin, vMax, M_PI / 4.0);
// Fixed: BRepPrim_OneAxis now correctly handles inverted height ordering
EXPECT_TRUE(isValid) << "Bug 23612 fixed: inverted height ordering now handled correctly";
}
// Case 3: Equal heights (45 to 135 deg) - Known Limitation
// Y(45 deg) ~ 1.41, Y(135 deg) ~ 1.41 => Heights are equal at endpoints
// The range covers distinct geometric regions (arc from 45 to 135 deg), but the
// resulting topology has a degenerate AxisEdge (zero length) which cannot form
// an orientable shell with the current face/wire structure.
{
const double vMin = M_PI / 4.0; // 45 deg
const double vMax = 3.0 * M_PI / 4.0; // 135 deg
double yMin = computeHeight(vMin);
double yMax = computeHeight(vMax);
EXPECT_NEAR(yMax, yMin, 1e-10) << "Heights at VMin and VMax are equal";
bool isValid = createAndCheckPartialTorus(R1, R2, vMin, vMax, M_PI / 4.0);
// Known limitation: equal heights cause degenerate AxisEdge topology
// This is a separate issue from Bug 23612 (inverted heights)
EXPECT_FALSE(isValid) << "Known limitation: equal heights not yet supported";
}
}
// Test full torus (no sides) with equal heights - should work
TEST(BRepPrimAPI_MakeTorusTest, FullTorus_EqualHeights)
{
const double R1 = 10.0;
const double R2 = 2.0;
const double angle1 = 0.0; // 0 degrees
const double angle2 = M_PI; // 180 degrees
gp_Ax2 anAxis(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1));
// Full revolution (2*PI) - no sides
BRepPrimAPI_MakeTorus aMakeTorus(anAxis, R1, R2, angle1, angle2);
TopoDS_Shape aShape = aMakeTorus.Shape();
ASSERT_TRUE(aMakeTorus.IsDone()) << "Torus creation failed";
ASSERT_FALSE(aShape.IsNull()) << "Shape is null";
BRepCheck_Analyzer anAnalyzer(aShape);
EXPECT_TRUE(anAnalyzer.IsValid()) << "Full torus (no sides) with equal heights should be valid";
}