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 <gtest/gtest.h>
#include <ShapeConstruct_ProjectCurveOnSurface.hxx>
#include <ShapeAnalysis_Surface.hxx>
#include <GC_MakeSegment.hxx>
#include <Geom2d_Curve.hxx>
#include <Geom_BSplineCurve.hxx>
#include <Geom_BSplineSurface.hxx>
#include <Geom_Circle.hxx>
#include <Geom_ConicalSurface.hxx>
#include <Geom_CylindricalSurface.hxx>
#include <Geom_Ellipse.hxx>
#include <Geom_Plane.hxx>
#include <Geom_SphericalSurface.hxx>
#include <Geom_ToroidalSurface.hxx>
#include <Geom_TrimmedCurve.hxx>
#include <GeomAPI_PointsToBSpline.hxx>
#include <gp_Ax2.hxx>
#include <gp_Circ.hxx>
#include <gp_Dir.hxx>
#include <gp_Pln.hxx>
#include <gp_Pnt.hxx>
#include <Precision.hxx>
#include <ShapeExtend.hxx>
#include <NCollection_Array1.hxx>
#include <NCollection_Array2.hxx>
#include <Standard_Integer.hxx>
//=================================================================================================
// Test fixture for ShapeConstruct_ProjectCurveOnSurface
//=================================================================================================
class ShapeConstruct_ProjectCurveOnSurfaceTest : public ::testing::Test
{
protected:
void SetUp() override
{
myProjector = new ShapeConstruct_ProjectCurveOnSurface();
myTolerance = Precision::Confusion();
}
//! Helper to create a simple B-spline curve lying on Z=0 plane
occ::handle<Geom_BSplineCurve> createPlanarBSpline()
{
NCollection_Array1<gp_Pnt> aPoles(1, 5);
aPoles(1) = gp_Pnt(0, 0, 0);
aPoles(2) = gp_Pnt(2, 3, 0);
aPoles(3) = gp_Pnt(5, 4, 0);
aPoles(4) = gp_Pnt(8, 2, 0);
aPoles(5) = gp_Pnt(10, 0, 0);
GeomAPI_PointsToBSpline aBuilder(aPoles);
return aBuilder.Curve();
}
//! Helper to create a helical B-spline on a cylinder
occ::handle<Geom_BSplineCurve> createHelicalBSpline(const double theRadius, const int theNbPoints)
{
NCollection_Array1<gp_Pnt> aPoles(1, theNbPoints);
for (int i = 1; i <= theNbPoints; ++i)
{
const double anAngle = (i - 1) * M_PI / 4.0;
const double aZ = (i - 1) * 1.0;
aPoles(i) = gp_Pnt(theRadius * cos(anAngle), theRadius * sin(anAngle), aZ);
}
GeomAPI_PointsToBSpline aBuilder(aPoles);
return aBuilder.Curve();
}
//! Verifies that pcurve correctly maps to 3D points on surface
void verifyProjection(const occ::handle<Geom_Curve>& theCurve,
const occ::handle<Geom2d_Curve>& thePCurve,
const occ::handle<ShapeAnalysis_Surface>& theSurface,
const double theTolerance)
{
const double aFirst = theCurve->FirstParameter();
const double aLast = theCurve->LastParameter();
for (double t = 0.0; t <= 1.0; t += 0.25)
{
const double aParam = aFirst + t * (aLast - aFirst);
gp_Pnt a3DPnt;
theCurve->D0(aParam, a3DPnt);
const gp_Pnt2d a2DPnt = thePCurve->Value(aParam);
gp_Pnt aSurfPnt = theSurface->Value(a2DPnt);
EXPECT_NEAR(a3DPnt.Distance(aSurfPnt), 0.0, theTolerance) << "Point mismatch at t=" << t;
}
}
occ::handle<ShapeConstruct_ProjectCurveOnSurface> myProjector;
double myTolerance;
};
//=================================================================================================
// Basic projection tests - lines and circles on analytical surfaces
//=================================================================================================
// Test: Project diagonal line on horizontal plane, verify 2D coordinates match X,Y of 3D points
TEST_F(ShapeConstruct_ProjectCurveOnSurfaceTest, LineOnPlane_A1)
{
occ::handle<Geom_Plane> aPlane = new Geom_Plane(gp_Pln(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)));
occ::handle<Geom_TrimmedCurve> aLine = GC_MakeSegment(gp_Pnt(0, 0, 0), gp_Pnt(10, 10, 0)).Value();
occ::handle<ShapeAnalysis_Surface> aSAS = new ShapeAnalysis_Surface(aPlane);
myProjector->Init(aSAS, myTolerance);
occ::handle<Geom2d_Curve> aPCurve;
const double aFirst = aLine->FirstParameter();
const double aLast = aLine->LastParameter();
const bool aResult = myProjector->Perform(aLine, aFirst, aLast, aPCurve);
ASSERT_TRUE(aResult) << "Projection should succeed";
ASSERT_FALSE(aPCurve.IsNull()) << "PCurve should not be null";
// Verify start point maps to (0,0) and end point maps to (10,10)
const gp_Pnt2d aStart = aPCurve->Value(aFirst);
const gp_Pnt2d aEnd = aPCurve->Value(aLast);
EXPECT_NEAR(aStart.X(), 0.0, 0.01);
EXPECT_NEAR(aStart.Y(), 0.0, 0.01);
EXPECT_NEAR(aEnd.X(), 10.0, 0.01);
EXPECT_NEAR(aEnd.Y(), 10.0, 0.01);
}
// Test: Project circle lying on cylinder surface, V-coordinate should be constant
TEST_F(ShapeConstruct_ProjectCurveOnSurfaceTest, CircleOnCylinder_A2)
{
const double aRadius = 5.0;
occ::handle<Geom_CylindricalSurface> aCylinder =
new Geom_CylindricalSurface(gp_Ax2(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)), aRadius);
// Circle at height Z=3 on the cylinder
gp_Circ aCirc(gp_Ax2(gp_Pnt(0, 0, 3), gp_Dir(0, 0, 1)), aRadius);
occ::handle<Geom_Circle> aCircle = new Geom_Circle(aCirc);
myProjector->Init(new ShapeAnalysis_Surface(aCylinder), myTolerance);
occ::handle<Geom2d_Curve> aPCurve;
const bool aResult = myProjector->Perform(aCircle, 0.0, 2.0 * M_PI, aPCurve);
ASSERT_TRUE(aResult) << "Projection should succeed";
ASSERT_FALSE(aPCurve.IsNull()) << "PCurve should not be null";
// On cylinder parametrization, V = Z, so circle at Z=3 should have V=3
const gp_Pnt2d aMid = aPCurve->Value(M_PI);
EXPECT_NEAR(aMid.Y(), 3.0, 0.01) << "V coordinate should be 3";
}
// Test: Project vertical line on cylinder (isoparametric case), U should be constant
TEST_F(ShapeConstruct_ProjectCurveOnSurfaceTest, IsoparametricLineOnCylinder_A3)
{
const double aRadius = 5.0;
occ::handle<Geom_CylindricalSurface> aCylinder =
new Geom_CylindricalSurface(gp_Ax2(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)), aRadius);
// Line along cylinder axis at X=radius, Y=0 (U=0 on cylinder)
occ::handle<Geom_TrimmedCurve> aLine =
GC_MakeSegment(gp_Pnt(aRadius, 0, 0), gp_Pnt(aRadius, 0, 10)).Value();
occ::handle<ShapeAnalysis_Surface> aSAS = new ShapeAnalysis_Surface(aCylinder);
myProjector->Init(aSAS, myTolerance);
occ::handle<Geom2d_Curve> aPCurve;
const double aFirst = aLine->FirstParameter();
const double aLast = aLine->LastParameter();
const bool aResult = myProjector->Perform(aLine, aFirst, aLast, aPCurve);
ASSERT_TRUE(aResult) << "Projection should succeed";
ASSERT_FALSE(aPCurve.IsNull()) << "PCurve should not be null";
// U should be constant (isoparametric curve)
const gp_Pnt2d aStart = aPCurve->Value(aFirst);
const gp_Pnt2d aEnd = aPCurve->Value(aLast);
EXPECT_NEAR(aStart.X(), aEnd.X(), 0.01) << "U should be constant for isoparametric curve";
}
//=================================================================================================
// B-Spline curve projection tests
//=================================================================================================
// Test: Project planar B-spline on plane, endpoints should match original curve
TEST_F(ShapeConstruct_ProjectCurveOnSurfaceTest, BSplineOnPlane_B1)
{
occ::handle<Geom_Plane> aPlane = new Geom_Plane(gp_Pln(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)));
occ::handle<Geom_BSplineCurve> aBSpline = createPlanarBSpline();
ASSERT_FALSE(aBSpline.IsNull());
myProjector->Init(new ShapeAnalysis_Surface(aPlane), myTolerance);
occ::handle<Geom2d_Curve> aPCurve;
const bool aResult =
myProjector->Perform(aBSpline, aBSpline->FirstParameter(), aBSpline->LastParameter(), aPCurve);
ASSERT_TRUE(aResult) << "Projection should succeed";
ASSERT_FALSE(aPCurve.IsNull()) << "PCurve should not be null";
// Verify endpoints
const gp_Pnt2d aStart = aPCurve->Value(aBSpline->FirstParameter());
const gp_Pnt2d aEnd = aPCurve->Value(aBSpline->LastParameter());
EXPECT_NEAR(aStart.X(), 0.0, 0.1);
EXPECT_NEAR(aStart.Y(), 0.0, 0.1);
EXPECT_NEAR(aEnd.X(), 10.0, 0.1);
EXPECT_NEAR(aEnd.Y(), 0.0, 0.1);
}
// Test: Project helical B-spline on cylinder, verify all sample points lie on surface
TEST_F(ShapeConstruct_ProjectCurveOnSurfaceTest, HelicalBSplineOnCylinder_B2)
{
const double aRadius = 5.0;
occ::handle<Geom_CylindricalSurface> aCylinder =
new Geom_CylindricalSurface(gp_Ax2(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)), aRadius);
occ::handle<Geom_BSplineCurve> aBSpline = createHelicalBSpline(aRadius, 9);
ASSERT_FALSE(aBSpline.IsNull());
occ::handle<ShapeAnalysis_Surface> aSAS = new ShapeAnalysis_Surface(aCylinder);
myProjector->Init(aSAS, myTolerance);
occ::handle<Geom2d_Curve> aPCurve;
const bool aResult =
myProjector->Perform(aBSpline, aBSpline->FirstParameter(), aBSpline->LastParameter(), aPCurve);
ASSERT_TRUE(aResult) << "Projection should succeed";
ASSERT_FALSE(aPCurve.IsNull()) << "PCurve should not be null";
verifyProjection(aBSpline, aPCurve, aSAS, 0.1);
}
// Test: Project high-degree B-spline with many control points
TEST_F(ShapeConstruct_ProjectCurveOnSurfaceTest, HighDegreeBSpline_B3)
{
occ::handle<Geom_Plane> aPlane = new Geom_Plane(gp_Pln(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)));
// Create B-spline with 50 control points (sinusoidal shape)
const int aNbPoles = 50;
NCollection_Array1<gp_Pnt> aPoles(1, aNbPoles);
for (int i = 1; i <= aNbPoles; ++i)
{
const double aX = (i - 1) * 0.5;
const double aY = 5.0 * sin(aX * M_PI / 10.0);
aPoles(i) = gp_Pnt(aX, aY, 0);
}
GeomAPI_PointsToBSpline aBuilder(aPoles, 3, 8);
occ::handle<Geom_BSplineCurve> aBSpline = aBuilder.Curve();
ASSERT_FALSE(aBSpline.IsNull());
occ::handle<ShapeAnalysis_Surface> aSAS = new ShapeAnalysis_Surface(aPlane);
myProjector->Init(aSAS, myTolerance);
occ::handle<Geom2d_Curve> aPCurve;
const bool aResult =
myProjector->Perform(aBSpline, aBSpline->FirstParameter(), aBSpline->LastParameter(), aPCurve);
ASSERT_TRUE(aResult) << "Projection should succeed";
ASSERT_FALSE(aPCurve.IsNull()) << "PCurve should not be null";
verifyProjection(aBSpline, aPCurve, aSAS, 0.5);
}
//=================================================================================================
// Periodic surface tests - sphere, torus
//=================================================================================================
// Test: Project equator circle on sphere
TEST_F(ShapeConstruct_ProjectCurveOnSurfaceTest, EquatorOnSphere_C1)
{
const double aRadius = 10.0;
occ::handle<Geom_SphericalSurface> aSphere =
new Geom_SphericalSurface(gp_Ax2(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)), aRadius);
gp_Circ aCirc(gp_Ax2(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)), aRadius);
occ::handle<Geom_Circle> aCircle = new Geom_Circle(aCirc);
myProjector->Init(new ShapeAnalysis_Surface(aSphere), myTolerance);
occ::handle<Geom2d_Curve> aPCurve;
const bool aResult = myProjector->Perform(aCircle, 0.0, 2.0 * M_PI, aPCurve);
ASSERT_TRUE(aResult) << "Projection should succeed";
ASSERT_FALSE(aPCurve.IsNull()) << "PCurve should not be null";
}
// Test: Project latitude circle on sphere at 45 degrees, V should be constant
TEST_F(ShapeConstruct_ProjectCurveOnSurfaceTest, LatitudeOnSphere_C2)
{
const double aRadius = 10.0;
occ::handle<Geom_SphericalSurface> aSphere =
new Geom_SphericalSurface(gp_Ax2(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)), aRadius);
// Latitude at 45 degrees
const double aLatitude = M_PI / 4.0;
const double aZ = aRadius * sin(aLatitude);
const double aCircRadius = aRadius * cos(aLatitude);
gp_Circ aCirc(gp_Ax2(gp_Pnt(0, 0, aZ), gp_Dir(0, 0, 1)), aCircRadius);
occ::handle<Geom_Circle> aCircle = new Geom_Circle(aCirc);
myProjector->Init(new ShapeAnalysis_Surface(aSphere), myTolerance);
occ::handle<Geom2d_Curve> aPCurve;
const bool aResult = myProjector->Perform(aCircle, 0.0, 2.0 * M_PI, aPCurve);
ASSERT_TRUE(aResult) << "Projection should succeed";
ASSERT_FALSE(aPCurve.IsNull()) << "PCurve should not be null";
// V should be constant for latitude circle
const gp_Pnt2d aStart = aPCurve->Value(0.0);
const gp_Pnt2d aMid = aPCurve->Value(M_PI);
EXPECT_NEAR(aStart.Y(), aMid.Y(), 0.01) << "V should be constant for latitude circle";
}
// Test: Project on toroidal surface (both U and V periodic)
TEST_F(ShapeConstruct_ProjectCurveOnSurfaceTest, CircleOnTorus_C3)
{
const double aMajorRadius = 10.0;
const double aMinorRadius = 2.0;
occ::handle<Geom_ToroidalSurface> aTorus =
new Geom_ToroidalSurface(gp_Ax2(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)), aMajorRadius, aMinorRadius);
// Circle in the minor cross-section plane at U=0
gp_Circ aCirc(gp_Ax2(gp_Pnt(aMajorRadius + aMinorRadius, 0, 0), gp_Dir(0, 1, 0)), aMinorRadius);
occ::handle<Geom_Circle> aCircle = new Geom_Circle(aCirc);
myProjector->Init(new ShapeAnalysis_Surface(aTorus), myTolerance);
occ::handle<Geom2d_Curve> aPCurve;
const bool aResult = myProjector->Perform(aCircle, 0.0, 2.0 * M_PI, aPCurve);
ASSERT_TRUE(aResult) << "Projection should succeed";
ASSERT_FALSE(aPCurve.IsNull()) << "PCurve should not be null";
}
//=================================================================================================
// Conical surface tests
//=================================================================================================
// Test: Project circle on cone at specific height
TEST_F(ShapeConstruct_ProjectCurveOnSurfaceTest, CircleOnCone_D1)
{
const double aRadius = 5.0;
const double aSemiAngle = M_PI / 6.0; // 30 degrees
occ::handle<Geom_ConicalSurface> aCone =
new Geom_ConicalSurface(gp_Ax2(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)), aSemiAngle, aRadius);
// Circle at height Z=5, radius increases with height
const double aZ = 5.0;
const double aRadiusAtZ = aRadius + aZ * tan(aSemiAngle);
gp_Circ aCirc(gp_Ax2(gp_Pnt(0, 0, aZ), gp_Dir(0, 0, 1)), aRadiusAtZ);
occ::handle<Geom_Circle> aCircle = new Geom_Circle(aCirc);
myProjector->Init(new ShapeAnalysis_Surface(aCone), myTolerance);
occ::handle<Geom2d_Curve> aPCurve;
const bool aResult = myProjector->Perform(aCircle, 0.0, 2.0 * M_PI, aPCurve);
ASSERT_TRUE(aResult) << "Projection should succeed";
ASSERT_FALSE(aPCurve.IsNull()) << "PCurve should not be null";
}
//=================================================================================================
// Trimmed curve tests
//=================================================================================================
// Test: Project 90-degree arc on plane
TEST_F(ShapeConstruct_ProjectCurveOnSurfaceTest, ArcOnPlane_E1)
{
occ::handle<Geom_Plane> aPlane = new Geom_Plane(gp_Pln(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)));
gp_Circ aCirc(gp_Ax2(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)), 5.0);
occ::handle<Geom_Circle> aFullCircle = new Geom_Circle(aCirc);
occ::handle<Geom_TrimmedCurve> aArc = new Geom_TrimmedCurve(aFullCircle, 0.0, M_PI / 2.0);
occ::handle<ShapeAnalysis_Surface> aSAS = new ShapeAnalysis_Surface(aPlane);
myProjector->Init(aSAS, myTolerance);
occ::handle<Geom2d_Curve> aPCurve;
const double aFirst = aArc->FirstParameter();
const double aLast = aArc->LastParameter();
const bool aResult = myProjector->Perform(aArc, aFirst, aLast, aPCurve);
ASSERT_TRUE(aResult) << "Projection should succeed";
ASSERT_FALSE(aPCurve.IsNull()) << "PCurve should not be null";
// Arc from (5,0) to (0,5)
const gp_Pnt2d aStart = aPCurve->Value(aFirst);
const gp_Pnt2d aEnd = aPCurve->Value(aLast);
EXPECT_NEAR(aStart.X(), 5.0, 0.01);
EXPECT_NEAR(aStart.Y(), 0.0, 0.01);
EXPECT_NEAR(aEnd.X(), 0.0, 0.01);
EXPECT_NEAR(aEnd.Y(), 5.0, 0.01);
}
// Test: Project doubly-trimmed curve (tests recursive unwrapping of trimmed curves)
TEST_F(ShapeConstruct_ProjectCurveOnSurfaceTest, DoublyTrimmedCurve_E2)
{
occ::handle<Geom_Plane> aPlane = new Geom_Plane(gp_Pln(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)));
occ::handle<Geom_BSplineCurve> aBSpline = createPlanarBSpline();
ASSERT_FALSE(aBSpline.IsNull());
// First trim: take first half
const double aFirst1 = aBSpline->FirstParameter();
const double aLast1 = aBSpline->LastParameter();
const double aMid = (aFirst1 + aLast1) / 2.0;
occ::handle<Geom_TrimmedCurve> aTrim1 = new Geom_TrimmedCurve(aBSpline, aFirst1, aMid);
// Second trim (nested): take first quarter of the first trim
const double aFirst2 = aTrim1->FirstParameter();
const double aLast2 = aTrim1->LastParameter();
const double aQuart = aFirst2 + (aLast2 - aFirst2) / 4.0;
occ::handle<Geom_TrimmedCurve> aTrim2 = new Geom_TrimmedCurve(aTrim1, aFirst2, aQuart);
myProjector->Init(new ShapeAnalysis_Surface(aPlane), myTolerance);
occ::handle<Geom2d_Curve> aPCurve;
const bool aResult =
myProjector->Perform(aTrim2, aTrim2->FirstParameter(), aTrim2->LastParameter(), aPCurve);
ASSERT_TRUE(aResult) << "Projection of doubly-trimmed curve should succeed";
ASSERT_FALSE(aPCurve.IsNull()) << "PCurve should not be null";
}
//=================================================================================================
// Ellipse projection tests
//=================================================================================================
// Test: Project ellipse on plane, verify quadrant points
TEST_F(ShapeConstruct_ProjectCurveOnSurfaceTest, EllipseOnPlane_F1)
{
occ::handle<Geom_Plane> aPlane = new Geom_Plane(gp_Pln(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)));
// Ellipse with major radius 10, minor radius 5
gp_Elips anElips(gp_Ax2(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)), 10.0, 5.0);
occ::handle<Geom_Ellipse> anEllipse = new Geom_Ellipse(anElips);
occ::handle<ShapeAnalysis_Surface> aSAS = new ShapeAnalysis_Surface(aPlane);
myProjector->Init(aSAS, myTolerance);
occ::handle<Geom2d_Curve> aPCurve;
const bool aResult = myProjector->Perform(anEllipse, 0.0, 2.0 * M_PI, aPCurve);
ASSERT_TRUE(aResult) << "Projection should succeed";
ASSERT_FALSE(aPCurve.IsNull()) << "PCurve should not be null";
// At parameter 0, ellipse is at (10, 0)
gp_Pnt2d aP0 = aPCurve->Value(0.0);
EXPECT_NEAR(aP0.X(), 10.0, 0.01);
EXPECT_NEAR(aP0.Y(), 0.0, 0.01);
// At parameter PI/2, ellipse is at (0, 5)
gp_Pnt2d aP1 = aPCurve->Value(M_PI / 2.0);
EXPECT_NEAR(aP1.X(), 0.0, 0.01);
EXPECT_NEAR(aP1.Y(), 5.0, 0.01);
}
//=================================================================================================
// API method tests - Init, SetSurface, SetPrecision
//=================================================================================================
// Test: Init with Geom_Surface directly (not wrapped in ShapeAnalysis_Surface)
TEST_F(ShapeConstruct_ProjectCurveOnSurfaceTest, InitWithGeomSurface_G1)
{
occ::handle<Geom_Plane> aPlane = new Geom_Plane(gp_Pln(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)));
occ::handle<Geom_TrimmedCurve> aLine = GC_MakeSegment(gp_Pnt(0, 0, 0), gp_Pnt(10, 10, 0)).Value();
myProjector->Init(aPlane, Precision::Confusion());
occ::handle<Geom2d_Curve> aPCurve;
const bool aResult =
myProjector->Perform(aLine, aLine->FirstParameter(), aLine->LastParameter(), aPCurve);
ASSERT_TRUE(aResult) << "Init with Geom_Surface should work";
ASSERT_FALSE(aPCurve.IsNull());
}
// Test: SetSurface + SetPrecision separately
TEST_F(ShapeConstruct_ProjectCurveOnSurfaceTest, SetSurfaceAndPrecision_G2)
{
occ::handle<Geom_Plane> aPlane = new Geom_Plane(gp_Pln(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)));
occ::handle<Geom_TrimmedCurve> aLine = GC_MakeSegment(gp_Pnt(0, 0, 0), gp_Pnt(10, 10, 0)).Value();
myProjector->SetSurface(aPlane);
myProjector->SetPrecision(Precision::Confusion());
occ::handle<Geom2d_Curve> aPCurve;
const bool aResult =
myProjector->Perform(aLine, aLine->FirstParameter(), aLine->LastParameter(), aPCurve);
ASSERT_TRUE(aResult) << "SetSurface + SetPrecision should work";
ASSERT_FALSE(aPCurve.IsNull());
}
// Test: AdjustOverDegenMode accessor returns modifiable reference
TEST_F(ShapeConstruct_ProjectCurveOnSurfaceTest, AdjustOverDegenModeAccessor_G3)
{
// Default value should be 1
EXPECT_EQ(myProjector->AdjustOverDegenMode(), 1);
// Modify through reference
myProjector->AdjustOverDegenMode() = 0;
EXPECT_EQ(myProjector->AdjustOverDegenMode(), 0);
// Restore
myProjector->AdjustOverDegenMode() = 1;
EXPECT_EQ(myProjector->AdjustOverDegenMode(), 1);
}
// Test: Status method returns meaningful status after projection
TEST_F(ShapeConstruct_ProjectCurveOnSurfaceTest, StatusMethod_G4)
{
occ::handle<Geom_Plane> aPlane = new Geom_Plane(gp_Pln(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)));
occ::handle<Geom_TrimmedCurve> aLine = GC_MakeSegment(gp_Pnt(0, 0, 0), gp_Pnt(10, 10, 0)).Value();
myProjector->Init(new ShapeAnalysis_Surface(aPlane), myTolerance);
occ::handle<Geom2d_Curve> aPCurve;
myProjector->Perform(aLine, aLine->FirstParameter(), aLine->LastParameter(), aPCurve);
// Status should indicate OK or DONE
EXPECT_TRUE(myProjector->Status(ShapeExtend_OK) || myProjector->Status(ShapeExtend_DONE))
<< "Status should indicate OK or DONE";
}
//=================================================================================================
// Tolerance and precision tests
//=================================================================================================
// Test: Projection with very tight tolerance on exact curve
TEST_F(ShapeConstruct_ProjectCurveOnSurfaceTest, TightTolerance_H1)
{
occ::handle<Geom_Plane> aPlane = new Geom_Plane(gp_Pln(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)));
occ::handle<Geom_TrimmedCurve> aLine = GC_MakeSegment(gp_Pnt(0, 0, 0), gp_Pnt(10, 10, 0)).Value();
myProjector->Init(new ShapeAnalysis_Surface(aPlane), 1e-15);
occ::handle<Geom2d_Curve> aPCurve;
const bool aResult =
myProjector->Perform(aLine, aLine->FirstParameter(), aLine->LastParameter(), aPCurve);
ASSERT_TRUE(aResult) << "Tight tolerance should work for exact curve";
ASSERT_FALSE(aPCurve.IsNull());
}
// Test: Projection with different endpoint tolerances
TEST_F(ShapeConstruct_ProjectCurveOnSurfaceTest, DifferentEndpointTolerances_H2)
{
occ::handle<Geom_Plane> aPlane = new Geom_Plane(gp_Pln(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)));
// Curve slightly off plane at end
NCollection_Array1<gp_Pnt> aPoles(1, 3);
aPoles(1) = gp_Pnt(0, 0, 0);
aPoles(2) = gp_Pnt(5, 5, 0.05);
aPoles(3) = gp_Pnt(10, 10, 0.1);
GeomAPI_PointsToBSpline aBuilder(aPoles, 2, 2);
occ::handle<Geom_BSplineCurve> aBSpline = aBuilder.Curve();
ASSERT_FALSE(aBSpline.IsNull());
myProjector->Init(new ShapeAnalysis_Surface(aPlane), myTolerance);
occ::handle<Geom2d_Curve> aPCurve;
const double aTolFirst = 0.01;
const double aTolLast = 0.2;
const bool aResult = myProjector->Perform(aBSpline,
aBSpline->FirstParameter(),
aBSpline->LastParameter(),
aPCurve,
aTolFirst,
aTolLast);
ASSERT_TRUE(aResult) << "Projection with different endpoint tolerances should succeed";
ASSERT_FALSE(aPCurve.IsNull());
}
//=================================================================================================
// Partial curve projection tests
//=================================================================================================
// Test: Project only first quarter of full circle
TEST_F(ShapeConstruct_ProjectCurveOnSurfaceTest, PartialCircle_I1)
{
occ::handle<Geom_Plane> aPlane = new Geom_Plane(gp_Pln(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)));
gp_Circ aCirc(gp_Ax2(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)), 5.0);
occ::handle<Geom_Circle> aCircle = new Geom_Circle(aCirc);
occ::handle<ShapeAnalysis_Surface> aSAS = new ShapeAnalysis_Surface(aPlane);
myProjector->Init(aSAS, myTolerance);
// Project only 0 to PI/2 (first quadrant)
occ::handle<Geom2d_Curve> aPCurve;
const bool aResult = myProjector->Perform(aCircle, 0.0, M_PI / 2.0, aPCurve);
ASSERT_TRUE(aResult) << "Partial projection should succeed";
ASSERT_FALSE(aPCurve.IsNull());
// Verify endpoints: (5,0) to (0,5)
gp_Pnt2d aStart = aPCurve->Value(0.0);
gp_Pnt2d aEnd = aPCurve->Value(M_PI / 2.0);
EXPECT_NEAR(aStart.X(), 5.0, 0.01);
EXPECT_NEAR(aStart.Y(), 0.0, 0.01);
EXPECT_NEAR(aEnd.X(), 0.0, 0.01);
EXPECT_NEAR(aEnd.Y(), 5.0, 0.01);
}
// Test: Project middle portion of B-spline
TEST_F(ShapeConstruct_ProjectCurveOnSurfaceTest, MiddlePortionBSpline_I2)
{
occ::handle<Geom_Plane> aPlane = new Geom_Plane(gp_Pln(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)));
occ::handle<Geom_BSplineCurve> aBSpline = createPlanarBSpline();
ASSERT_FALSE(aBSpline.IsNull());
myProjector->Init(new ShapeAnalysis_Surface(aPlane), myTolerance);
const double aFirst = aBSpline->FirstParameter();
const double aLast = aBSpline->LastParameter();
const double aRange = aLast - aFirst;
// Project middle 50%
occ::handle<Geom2d_Curve> aPCurve;
const bool aResult =
myProjector->Perform(aBSpline, aFirst + 0.25 * aRange, aFirst + 0.75 * aRange, aPCurve);
ASSERT_TRUE(aResult) << "Middle portion projection should succeed";
ASSERT_FALSE(aPCurve.IsNull());
}
//=================================================================================================
// Multiple projection and reinitialization tests
//=================================================================================================
// Test: Multiple projections on same surface reuse setup correctly
TEST_F(ShapeConstruct_ProjectCurveOnSurfaceTest, MultipleProjections_J1)
{
occ::handle<Geom_Plane> aPlane = new Geom_Plane(gp_Pln(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)));
occ::handle<ShapeAnalysis_Surface> aSAS = new ShapeAnalysis_Surface(aPlane);
myProjector->Init(aSAS, myTolerance);
// Project 5 different curves
for (int i = 0; i < 5; ++i)
{
const double aOffset = i * 10.0;
occ::handle<Geom_TrimmedCurve> aLine =
GC_MakeSegment(gp_Pnt(aOffset, 0, 0), gp_Pnt(aOffset + 5, 5, 0)).Value();
occ::handle<Geom2d_Curve> aPCurve;
const bool aResult =
myProjector->Perform(aLine, aLine->FirstParameter(), aLine->LastParameter(), aPCurve);
EXPECT_TRUE(aResult) << "Projection " << i << " should succeed";
EXPECT_FALSE(aPCurve.IsNull()) << "PCurve " << i << " should not be null";
}
}
// Test: Surface reinitialization between projections
TEST_F(ShapeConstruct_ProjectCurveOnSurfaceTest, SurfaceReinitialization_J2)
{
// First surface
occ::handle<Geom_Plane> aPlane1 = new Geom_Plane(gp_Pln(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)));
myProjector->Init(new ShapeAnalysis_Surface(aPlane1), myTolerance);
occ::handle<Geom_TrimmedCurve> aLine1 =
GC_MakeSegment(gp_Pnt(0, 0, 0), gp_Pnt(10, 10, 0)).Value();
occ::handle<Geom2d_Curve> aPCurve1;
myProjector->Perform(aLine1, aLine1->FirstParameter(), aLine1->LastParameter(), aPCurve1);
ASSERT_FALSE(aPCurve1.IsNull());
// Second surface (different plane)
occ::handle<Geom_Plane> aPlane2 = new Geom_Plane(gp_Pln(gp_Pnt(0, 0, 5), gp_Dir(0, 0, 1)));
myProjector->Init(new ShapeAnalysis_Surface(aPlane2), myTolerance);
occ::handle<Geom_TrimmedCurve> aLine2 =
GC_MakeSegment(gp_Pnt(0, 0, 5), gp_Pnt(10, 10, 5)).Value();
occ::handle<Geom2d_Curve> aPCurve2;
myProjector->Perform(aLine2, aLine2->FirstParameter(), aLine2->LastParameter(), aPCurve2);
ASSERT_FALSE(aPCurve2.IsNull());
}
//=================================================================================================
// Near-singularity tests (poles, degenerate regions)
//=================================================================================================
// Test: Project small circle near north pole of sphere
TEST_F(ShapeConstruct_ProjectCurveOnSurfaceTest, NearPoleOnSphere_K1)
{
const double aRadius = 10.0;
occ::handle<Geom_SphericalSurface> aSphere =
new Geom_SphericalSurface(gp_Ax2(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)), aRadius);
// Small circle very close to north pole (latitude = 89.4 degrees)
const double aLatitude = M_PI / 2.0 - 0.01;
const double aZ = aRadius * sin(aLatitude);
const double aCircRadius = aRadius * cos(aLatitude);
gp_Circ aCirc(gp_Ax2(gp_Pnt(0, 0, aZ), gp_Dir(0, 0, 1)), aCircRadius);
occ::handle<Geom_Circle> aCircle = new Geom_Circle(aCirc);
myProjector->Init(new ShapeAnalysis_Surface(aSphere), myTolerance);
occ::handle<Geom2d_Curve> aPCurve;
const bool aResult = myProjector->Perform(aCircle, 0.0, 2.0 * M_PI, aPCurve);
// Should succeed even near pole
ASSERT_TRUE(aResult) << "Projection near pole should succeed";
ASSERT_FALSE(aPCurve.IsNull());
}
//=================================================================================================
// Edge case tests
//=================================================================================================
// Test: Very short curve projection should not crash
TEST_F(ShapeConstruct_ProjectCurveOnSurfaceTest, VeryShortCurve_L1)
{
occ::handle<Geom_Plane> aPlane = new Geom_Plane(gp_Pln(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)));
// Very short line segment (1e-6 length)
occ::handle<Geom_TrimmedCurve> aLine =
GC_MakeSegment(gp_Pnt(0, 0, 0), gp_Pnt(1e-6, 0, 0)).Value();
myProjector->Init(new ShapeAnalysis_Surface(aPlane), 1e-4);
occ::handle<Geom2d_Curve> aPCurve;
// Result may vary but should not crash
(void)myProjector->Perform(aLine, aLine->FirstParameter(), aLine->LastParameter(), aPCurve);
SUCCEED() << "Short curve projection completed without crash";
}
// Test: Curve far from surface still projects
TEST_F(ShapeConstruct_ProjectCurveOnSurfaceTest, CurveFarFromSurface_L2)
{
occ::handle<Geom_Plane> aPlane = new Geom_Plane(gp_Pln(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)));
// Line at Z=100, far from plane at Z=0
occ::handle<Geom_TrimmedCurve> aLine =
GC_MakeSegment(gp_Pnt(0, 0, 100), gp_Pnt(10, 10, 100)).Value();
myProjector->Init(new ShapeAnalysis_Surface(aPlane), myTolerance);
occ::handle<Geom2d_Curve> aPCurve;
(void)myProjector->Perform(aLine, aLine->FirstParameter(), aLine->LastParameter(), aPCurve);
// Projection should still produce result (it projects onto the surface)
EXPECT_FALSE(aPCurve.IsNull()) << "PCurve should be created even for distant curve";
}
// Test: Large tolerance projection
TEST_F(ShapeConstruct_ProjectCurveOnSurfaceTest, LargeTolerance_L3)
{
occ::handle<Geom_Plane> aPlane = new Geom_Plane(gp_Pln(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)));
// Line slightly off plane
occ::handle<Geom_TrimmedCurve> aLine =
GC_MakeSegment(gp_Pnt(0, 0, 0.1), gp_Pnt(10, 10, 0.1)).Value();
myProjector->Init(new ShapeAnalysis_Surface(aPlane), 1.0);
occ::handle<Geom2d_Curve> aPCurve;
const bool aResult =
myProjector->Perform(aLine, aLine->FirstParameter(), aLine->LastParameter(), aPCurve);
ASSERT_TRUE(aResult) << "Large tolerance projection should succeed";
ASSERT_FALSE(aPCurve.IsNull());
}
//=================================================================================================
// Regression tests from bug reports
//=================================================================================================
// Test: Bug 27569 - B-spline with many knots on B-spline surface
// This test verifies that normal B-spline curves with many knots
// (but uniform spacing) project correctly without triggering false
// positive in knot spacing detection
TEST_F(ShapeConstruct_ProjectCurveOnSurfaceTest, Bug27569_ManyKnotsBSpline_M1)
{
// Create a B-spline curve with many knots by manually constructing it
// with degree 3 and 15 knots (requiring 15 + 3 + 1 - 2*(3+1) = 11 internal knots)
// For degree 3 with 15 knots (all simple): nbPoles = nbKnots + degree - 1 = 15 + 3 - 1 = 17
const int aDegree = 3;
const int aNbKnots = 15;
const int aNbPoles = aNbKnots + aDegree - 1; // = 17
NCollection_Array1<gp_Pnt> aPoles(1, aNbPoles);
for (int i = 1; i <= aNbPoles; ++i)
{
const double t = (i - 1.0) / (aNbPoles - 1.0);
const double aAngle = t * 2.0 * M_PI;
const double aRadius = 50.0 + 20.0 * cos(3.0 * aAngle);
aPoles(i) = gp_Pnt(aRadius * cos(aAngle), -30.0 * t, aRadius * sin(aAngle));
}
// Uniform knot vector from 0 to 1
NCollection_Array1<double> aKnots(1, aNbKnots);
NCollection_Array1<int> aMults(1, aNbKnots);
for (int i = 1; i <= aNbKnots; ++i)
{
aKnots(i) = (i - 1.0) / (aNbKnots - 1.0);
aMults(i) = 1;
}
// Clamp end knots
aMults(1) = aDegree + 1;
aMults(aNbKnots) = aDegree + 1;
occ::handle<Geom_BSplineCurve> aBSplineCurve =
new Geom_BSplineCurve(aPoles, aKnots, aMults, aDegree);
ASSERT_FALSE(aBSplineCurve.IsNull()) << "B-Spline curve should be created";
EXPECT_GE(aBSplineCurve->NbKnots(), 10) << "Curve should have at least 10 knots";
// Create a simple plane-like surface containing the curve
occ::handle<Geom_Plane> aPlane = new Geom_Plane(gp_Pnt(0, -15, 0), gp_Dir(0, 1, 0));
// Test projection onto plane
myProjector->Init(aPlane, Precision::Confusion());
occ::handle<Geom2d_Curve> aPCurve;
const bool aResult = myProjector->Perform(aBSplineCurve,
aBSplineCurve->FirstParameter(),
aBSplineCurve->LastParameter(),
aPCurve);
EXPECT_TRUE(aResult) << "Projection should succeed for B-spline with many knots";
EXPECT_FALSE(aPCurve.IsNull()) << "PCurve should be created";
}
// Test: Bug 27569 - Actual bug reproduction with high-degree B-spline curve on B-spline surface
// This test uses a B-spline curve of degree 7 with high multiplicities and a section of
// repeated poles (degenerate section), which is the actual problematic geometry from bug27569.
// The curve is projected onto a B-spline surface.
TEST_F(ShapeConstruct_ProjectCurveOnSurfaceTest, Bug27569_HighMultiplicityBSpline_M2)
{
// Construct a degree 7 B-spline curve with structure similar to bug27569:
// - 7 unique knots with high multiplicities: 15, 14, 14, 14, 14, 14, 15
// - Total knot count (with mults) = 15 + 14*5 + 15 = 100
// - Number of poles = 100 - 7 - 1 = 92 (approximately, depends on exact formula)
// For B-spline: sum of multiplicities = nbPoles + degree + 1
// => nbPoles = sum(mults) - degree - 1 = (15 + 14*5 + 15) - 7 - 1 = 100 - 8 = 92... but file has
// 85 Actually for non-periodic: sum(mults) = nbPoles + degree + 1
// => 15 + 14 + 14 + 14 + 14 + 14 + 15 = 100 = 85 + 7 + 1 = 93... doesn't match
// Looking at file: 85 poles, degree 7, knots with mults summing to 100
// Actually file shows: "14 85 7" meaning 14 spans(?), 85 poles, degree 7
// Let's construct a simpler problematic case:
// Degree 7 B-spline with a degenerate section (repeated identical poles)
const int aDegree = 7;
const int aNbKnots = 7;
// High multiplicities similar to bug27569 (adjusted for valid B-spline)
NCollection_Array1<int> aMults(1, aNbKnots);
aMults(1) = 8; // degree + 1 for clamped start
aMults(2) = 7;
aMults(3) = 7;
aMults(4) = 7;
aMults(5) = 7;
aMults(6) = 7;
aMults(7) = 8; // degree + 1 for clamped end
// Sum = 8 + 7*5 + 8 = 51
// nbPoles = sum(mults) - degree - 1 = 51 - 7 - 1 = 43
const int aNbPoles = 43;
// Uniform knot vector
NCollection_Array1<double> aKnots(1, aNbKnots);
for (int i = 1; i <= aNbKnots; ++i)
{
aKnots(i) = (i - 1); // Knots: 0, 1, 2, 3, 4, 5, 6
}
// Create poles with a degenerate section (repeated identical poles in the middle)
NCollection_Array1<gp_Pnt> aPoles(1, aNbPoles);
// First section: smooth curve from start
for (int i = 1; i <= 15; ++i)
{
const double t = (i - 1.0) / 14.0;
const double aX = 144.0 - t * 74.0; // Decreasing from 144 to 70
const double aY = -t * 0.0002;
const double aZ = 8.3 + t * 51.7; // Increasing from 8.3 to 60
aPoles(i) = gp_Pnt(aX, aY, aZ);
}
// Middle section: degenerate (repeated identical poles) - like in bug27569
for (int i = 16; i <= 28; ++i)
{
aPoles(i) = gp_Pnt(70.0, 0.0, 60.0); // All identical
}
// Last section: smooth curve to end
for (int i = 29; i <= aNbPoles; ++i)
{
const double t = (i - 29.0) / (aNbPoles - 29.0);
const double aX = 70.0 - t * 80.0; // Decreasing from 70 to -10
const double aY = -t * 32.0;
const double aZ = 60.0 - t * 60.0; // Decreasing from 60 to 0
aPoles(i) = gp_Pnt(aX, aY, aZ);
}
occ::handle<Geom_BSplineCurve> aBSplineCurve;
try
{
aBSplineCurve = new Geom_BSplineCurve(aPoles, aKnots, aMults, aDegree);
}
catch (Standard_Failure const&)
{
// If construction fails, the test documents the expected behavior
GTEST_SKIP() << "Could not construct B-spline with high multiplicities";
}
ASSERT_FALSE(aBSplineCurve.IsNull()) << "B-Spline curve should be created";
// Create B-spline surface (simplified version of bug27569 surface)
// 8x2 poles B-spline surface
const int aNbUPoles = 8;
const int aNbVPoles = 2;
NCollection_Array2<gp_Pnt> aSurfPoles(1, aNbUPoles, 1, aNbVPoles);
// V=0 row
aSurfPoles(1, 1) = gp_Pnt(70, 0, 60);
aSurfPoles(2, 1) = gp_Pnt(85.01, 0, 60);
aSurfPoles(3, 1) = gp_Pnt(98.87, 0, 55.07);
aSurfPoles(4, 1) = gp_Pnt(111.6, 0, 46.57);
aSurfPoles(5, 1) = gp_Pnt(123.15, 0, 35.68);
aSurfPoles(6, 1) = gp_Pnt(133.46, 0, 23.56);
aSurfPoles(7, 1) = gp_Pnt(142.46, 0, 11.32);
aSurfPoles(8, 1) = gp_Pnt(150, 0, 0);
// V=1 row (same with Y offset)
aSurfPoles(1, 2) = gp_Pnt(54.99, 0, 60);
aSurfPoles(2, 2) = gp_Pnt(41.13, 0, 55.07);
aSurfPoles(3, 2) = gp_Pnt(28.4, 0, 46.57);
aSurfPoles(4, 2) = gp_Pnt(16.85, 0, 35.68);
aSurfPoles(5, 2) = gp_Pnt(6.54, 0, 23.56);
aSurfPoles(6, 2) = gp_Pnt(-2.46, 0, 11.32);
aSurfPoles(7, 2) = gp_Pnt(-10, 0, 0);
aSurfPoles(8, 2) = gp_Pnt(-10, -35.91, 0);
NCollection_Array1<double> aUKnots(1, 2);
NCollection_Array1<double> aVKnots(1, 2);
NCollection_Array1<int> aUMults(1, 2);
NCollection_Array1<int> aVMults(1, 2);
aUKnots(1) = 0;
aUKnots(2) = 1;
aVKnots(1) = 0;
aVKnots(2) = 1;
aUMults(1) = 8;
aUMults(2) = 8;
aVMults(1) = 2;
aVMults(2) = 2;
occ::handle<Geom_BSplineSurface> aBSplineSurface;
try
{
aBSplineSurface = new Geom_BSplineSurface(aSurfPoles, aUKnots, aVKnots, aUMults, aVMults, 7, 1);
}
catch (Standard_Failure const&)
{
// Fall back to a simple plane if surface construction fails
occ::handle<Geom_Plane> aPlane = new Geom_Plane(gp_Pnt(70, 0, 30), gp_Dir(0, 1, 0));
myProjector->Init(aPlane, Precision::Confusion());
occ::handle<Geom2d_Curve> aPCurve;
const bool aResult = myProjector->Perform(aBSplineCurve,
aBSplineCurve->FirstParameter(),
aBSplineCurve->LastParameter(),
aPCurve);
// This test documents expected behavior - projection should succeed
EXPECT_TRUE(aResult) << "Projection should succeed";
EXPECT_FALSE(aPCurve.IsNull()) << "PCurve should be created";
return;
}
myProjector->Init(aBSplineSurface, Precision::Confusion());
occ::handle<Geom2d_Curve> aPCurve;
const bool aResult = myProjector->Perform(aBSplineCurve,
aBSplineCurve->FirstParameter(),
aBSplineCurve->LastParameter(),
aPCurve);
// This is the key assertion - the projection SHOULD succeed but currently may fail
// for curves with degenerate sections (repeated poles)
EXPECT_TRUE(aResult) << "Projection should succeed for B-spline with degenerate section";
EXPECT_FALSE(aPCurve.IsNull()) << "PCurve should be created";
}