Shape Healing - Revert BSpline check for ShapeConstruct_ProjectCurveOnSurface (#894)

- Updated isBSplineCurveInvalid to check for uneven parameterization speed and determine the need for ProjLib usage.
- Enhanced the logic for computing parameterization speed across knot intervals.
- Simplified the handling of B-spline curves with problematic knot spacing by directly utilizing ProjLib for projection.
- Improved overall clarity and maintainability of the code by removing redundant checks and streamlining parameter handling.
This commit is contained in:
Pasukhin Dmitry
2025-12-08 12:39:17 +00:00
committed by GitHub
parent 3b8185bafb
commit 57fcedf49c

View File

@@ -45,6 +45,7 @@
#include <IntCurve_IntConicConic.hxx> #include <IntCurve_IntConicConic.hxx>
#include <IntRes2d_Domain.hxx> #include <IntRes2d_Domain.hxx>
#include <NCollection_IncAllocator.hxx> #include <NCollection_IncAllocator.hxx>
#include <NCollection_Sequence.hxx>
#include <NCollection_Vector.hxx> #include <NCollection_Vector.hxx>
#include <Precision.hxx> #include <Precision.hxx>
#include <ProjLib_ProjectedCurve.hxx> #include <ProjLib_ProjectedCurve.hxx>
@@ -404,14 +405,15 @@ Standard_Boolean fixPeriodicityTroubles(gp_Pnt2d* thePnt,
//================================================================================================= //=================================================================================================
//! Checks if B-spline curve has problematic knot spacing that could cause issues. //! Checks if B-spline curve has uneven parameterization requiring special handling.
//! Detects cases where knot intervals are extremely small relative to the median //! Computes the ratio of maximum to minimum parameterization speed across knot intervals.
//! interval or the working parameter range, or where the curve has degenerate sections. //! If this ratio exceeds a threshold, the curve should be projected using ProjLib instead
//! of the standard approximation approach.
//! @param[in] theCurve the curve to check (may be trimmed) //! @param[in] theCurve the curve to check (may be trimmed)
//! @param[in] theFirst the first parameter of the working range //! @param[in] theFirst the first parameter of the working range
//! @param[in] theLast the last parameter of the working range //! @param[in] theLast the last parameter of the working range
//! @param[out] theBSpline the extracted B-spline curve (if any) //! @param[out] theBSpline the extracted B-spline curve (if any)
//! @return true if the curve has problematic characteristics //! @return true if the curve has uneven parameterization requiring ProjLib
Standard_Boolean isBSplineCurveInvalid(const Handle(Geom_Curve)& theCurve, Standard_Boolean isBSplineCurveInvalid(const Handle(Geom_Curve)& theCurve,
const Standard_Real theFirst, const Standard_Real theFirst,
const Standard_Real theLast, const Standard_Real theLast,
@@ -421,212 +423,80 @@ Standard_Boolean isBSplineCurveInvalid(const Handle(Geom_Curve)& theCurve,
if (theBSpline.IsNull()) if (theBSpline.IsNull())
return Standard_False; return Standard_False;
try // Compute parametrization speed on each knot interval inside [theFirst, theLast].
// If quotient = (MaxSpeed / MinSpeed) >= aMaxQuotientCoeff then use PerformByProjLib.
Standard_Real aFirstParam = theFirst;
Standard_Real aLastParam = theLast;
// Find first knot index
Standard_Integer anIdx = 1;
for (; anIdx <= theBSpline->NbKnots() && aFirstParam < theLast; anIdx++)
{ {
OCC_CATCH_SIGNALS if (theBSpline->Knot(anIdx) > theFirst)
const TColStd_Array1OfReal& aKnots = theBSpline->Knots();
if (aKnots.Length() < 10)
return Standard_False;
NCollection_Vector<Standard_Real> anIntervals;
for (Standard_Integer i = aKnots.Lower() + 1; i <= aKnots.Upper(); i++)
{ {
const Standard_Real anInterval = aKnots(i) - aKnots(i - 1);
if (anInterval > Precision::Confusion())
{
anIntervals.Append(anInterval);
}
}
if (anIntervals.IsEmpty())
return Standard_True;
std::sort(anIntervals.begin(), anIntervals.end());
Standard_Real aMedianInterval;
const Standard_Integer aMid = anIntervals.Length() / 2;
if (anIntervals.Length() % 2 == 0)
{
aMedianInterval = (anIntervals[aMid - 1] + anIntervals[aMid]) / 2.0;
}
else
{
aMedianInterval = anIntervals[aMid];
}
Standard_Real aTolerance = aMedianInterval * 1e-3;
aTolerance = Max(aTolerance, Precision::Confusion() * 100);
for (Standard_Integer i = aKnots.Lower() + 1; i <= aKnots.Upper(); i++)
{
const Standard_Real anInterval = aKnots(i) - aKnots(i - 1);
if (anInterval > Precision::Confusion() && anInterval < aTolerance)
{
return Standard_True;
}
}
const Standard_Real aWorkingRange = theLast - theFirst;
if (aWorkingRange > Precision::Confusion())
{
const Standard_Real aRelativeTolerance = aWorkingRange * 1e-6;
for (Standard_Integer i = aKnots.Lower() + 1; i <= aKnots.Upper(); i++)
{
const Standard_Real anInterval = aKnots(i) - aKnots(i - 1);
if (aKnots(i - 1) >= theFirst - Precision::Confusion()
&& aKnots(i) <= theLast + Precision::Confusion() && anInterval > Precision::Confusion()
&& anInterval < aRelativeTolerance)
{
return Standard_True;
}
}
}
return Standard_False;
}
catch (Standard_Failure const&)
{
return Standard_True;
}
}
//=================================================================================================
//! Rebuilds B-spline curve by adjusting problematic knot spacing while preserving poles.
//! Only modifies the knot vector to fix degenerate parametrization; the curve geometry
//! (poles, weights, degree, multiplicities) remains unchanged.
//! @param[in] theBSpline the B-spline curve to rebuild
//! @return the rebuilt B-spline curve, or null if cannot be fixed by knot adjustment
Handle(Geom_BSplineCurve) rebuildBSpline(const Handle(Geom_BSplineCurve)& theBSpline)
{
if (theBSpline.IsNull())
return nullptr;
try
{
OCC_CATCH_SIGNALS
const Standard_Integer aDegree = theBSpline->Degree();
const TColgp_Array1OfPnt& aPoles = theBSpline->Poles();
const TColStd_Array1OfReal& anOriginalKnots = theBSpline->Knots();
const TColStd_Array1OfInteger& aMults = theBSpline->Multiplicities();
const Standard_Integer aNbKnots = anOriginalKnots.Length();
if (aNbKnots < 2)
return nullptr;
// Check for problematic knot spacing
NCollection_Vector<Standard_Real> anIntervals;
for (Standard_Integer i = anOriginalKnots.Lower() + 1; i <= anOriginalKnots.Upper(); i++)
{
const Standard_Real anInterval = anOriginalKnots(i) - anOriginalKnots(i - 1);
if (anInterval > Precision::Confusion())
{
anIntervals.Append(anInterval);
}
}
if (anIntervals.IsEmpty())
return nullptr;
std::sort(anIntervals.begin(), anIntervals.end());
Standard_Real aMedianInterval;
const Standard_Integer aMid = anIntervals.Length() / 2;
if (anIntervals.Length() % 2 == 0)
{
aMedianInterval = (anIntervals[aMid - 1] + anIntervals[aMid]) / 2.0;
}
else
{
aMedianInterval = anIntervals[aMid];
}
Standard_Real aTotalNormalInterval = 0.0;
Standard_Integer aNormalCount = 0;
const Standard_Integer aQuarter = anIntervals.Length() / 4;
for (Standard_Integer i = aQuarter; i < anIntervals.Length(); i++)
{
aTotalNormalInterval += anIntervals[i];
aNormalCount++;
}
Standard_Real anAverageNormalInterval =
(aNormalCount > 0) ? (aTotalNormalInterval / aNormalCount) : aMedianInterval;
Standard_Real aTolerance = aMedianInterval * 0.1;
aTolerance = Max(aTolerance, Precision::Confusion() * 100);
Standard_Boolean hasProblematicKnots = Standard_False;
for (Standard_Integer i = anOriginalKnots.Lower() + 1; i <= anOriginalKnots.Upper(); i++)
{
const Standard_Real anInterval = anOriginalKnots(i) - anOriginalKnots(i - 1);
if (anInterval > Precision::Confusion() && anInterval < aTolerance)
{
hasProblematicKnots = Standard_True;
break; break;
} }
} }
if (!hasProblematicKnots) GeomAdaptor_Curve aC3DAdaptor(theCurve);
return theBSpline; Standard_Real aMinParSpeed = Precision::Infinite();
NCollection_Sequence<Standard_Real> aKnotCoeffs;
Standard_Real aMinSpacing = Max(anAverageNormalInterval * 0.5, aMedianInterval * 0.3); for (; anIdx <= theBSpline->NbKnots() && aFirstParam < theLast; anIdx++)
aMinSpacing = Max(aMinSpacing, Precision::Confusion() * 10000);
TColStd_Array1OfReal aNewKnots(anOriginalKnots.Lower(), anOriginalKnots.Upper());
aNewKnots(anOriginalKnots.Lower()) = anOriginalKnots(anOriginalKnots.Lower());
for (Standard_Integer i = anOriginalKnots.Lower() + 1; i <= anOriginalKnots.Upper(); i++)
{ {
const Standard_Real aPrevKnot = aNewKnots(i - 1); // Fill current knot interval
const Standard_Real aCurrKnot = anOriginalKnots(i); aLastParam = std::min(theLast, theBSpline->Knot(anIdx));
const Standard_Real anOriginalInterval = aCurrKnot - anOriginalKnots(i - 1); Standard_Integer aNbIntPnts = THE_NCONTROL;
if (anOriginalInterval > Precision::Confusion() && anOriginalInterval < aTolerance) // Adapt number of inner points according to the length of the interval
// to avoid a lot of calculations on small range of parameters.
if (anIdx > 1)
{ {
aNewKnots(i) = aPrevKnot + Max(aMinSpacing, anOriginalInterval * 2.0); const Standard_Real aLenThres = 1.e-2;
} const Standard_Real aLenRatio =
else (aLastParam - aFirstParam) / (theBSpline->Knot(anIdx) - theBSpline->Knot(anIdx - 1));
if (aLenRatio < aLenThres)
{ {
aNewKnots(i) = Max(aCurrKnot, aPrevKnot + aMinSpacing); aNbIntPnts = Standard_Integer(aLenRatio / aLenThres * aNbIntPnts);
if (aNbIntPnts < 2)
aNbIntPnts = 2;
} }
} }
const Standard_Real aOriginalRange = Standard_Real aStep = (aLastParam - aFirstParam) / (aNbIntPnts - 1);
anOriginalKnots(anOriginalKnots.Upper()) - anOriginalKnots(anOriginalKnots.Lower()); gp_Pnt p3d1, p3d2;
const Standard_Real aNewRange = aNewKnots(aNewKnots.Upper()) - aNewKnots(aNewKnots.Lower());
if (aNewRange > aOriginalRange * 1.5) // Start filling from first point
{ aC3DAdaptor.D0(aFirstParam, p3d1);
const Standard_Real aFirstKnot = anOriginalKnots(anOriginalKnots.Lower());
const Standard_Real aLastKnot = anOriginalKnots(anOriginalKnots.Upper());
for (Standard_Integer i = anOriginalKnots.Lower(); i <= anOriginalKnots.Upper(); i++) Standard_Real aLength3d = 0.0;
for (Standard_Integer anIntIdx = 1; anIntIdx < aNbIntPnts; anIntIdx++)
{ {
const Standard_Real t = Standard_Real aParam = aFirstParam + aStep * anIntIdx;
Standard_Real(i - anOriginalKnots.Lower()) / Standard_Real(aNbKnots - 1); aC3DAdaptor.D0(aParam, p3d2);
aNewKnots(i) = aFirstKnot + t * (aLastKnot - aFirstKnot); const Standard_Real aDist = p3d2.Distance(p3d1);
}
aLength3d += aDist;
p3d1 = p3d2;
aMinParSpeed = std::min(aMinParSpeed, aDist / aStep);
} }
Handle(Geom_BSplineCurve) aNewBSpline; const Standard_Real aCoeff = aLength3d / (aLastParam - aFirstParam);
if (theBSpline->IsRational()) if (std::abs(aCoeff) > gp::Resolution())
{ aKnotCoeffs.Append(aCoeff);
const TColStd_Array1OfReal* aWeights = theBSpline->Weights(); aFirstParam = aLastParam;
aNewBSpline = new Geom_BSplineCurve(aPoles, *aWeights, aNewKnots, aMults, aDegree);
}
else
{
aNewBSpline = new Geom_BSplineCurve(aPoles, aNewKnots, aMults, aDegree);
} }
return aNewBSpline; Standard_Real anEvenlyCoeff = 0;
} if (aKnotCoeffs.Size() > 0)
catch (Standard_Failure const&)
{ {
return nullptr; anEvenlyCoeff = *std::max_element(aKnotCoeffs.begin(), aKnotCoeffs.end())
/ *std::min_element(aKnotCoeffs.begin(), aKnotCoeffs.end());
} }
const Standard_Real aMaxQuotientCoeff = 1500.0;
return (anEvenlyCoeff > aMaxQuotientCoeff && aMinParSpeed > Precision::Confusion());
} }
//================================================================================================= //=================================================================================================
@@ -840,39 +710,22 @@ Standard_Boolean ShapeConstruct_ProjectCurveOnSurface::Perform(const Handle(Geom
return Standard_True; return Standard_True;
} }
// Handle problematic B-spline curves // Handle problematic B-spline curves with uneven parameterization
Handle(Geom_Curve) aCurve = theC3D;
Standard_Real aFirst = theFirst;
Standard_Real aLast = theLast;
Handle(Geom_BSplineCurve) aBSpline; Handle(Geom_BSplineCurve) aBSpline;
if (isBSplineCurveInvalid(theC3D, theFirst, theLast, aBSpline))
if (isBSplineCurveInvalid(aCurve, aFirst, aLast, aBSpline))
{ {
// Work with a copy to preserve the original geometry // Use ProjLib for curves with highly uneven parameterization speed
Handle(Geom_BSplineCurve) aSegmented = Handle(Geom_BSplineCurve)::DownCast(aBSpline->Copy()); if (PerformByProjLib(theC3D, theFirst, theLast, theC2D))
aSegmented->Segment(aFirst, aLast, Precision::PConfusion());
Handle(Geom_BSplineCurve) aNewBSpline = rebuildBSpline(aSegmented);
if (aNewBSpline.IsNull())
{
if (PerformByProjLib(aSegmented, aFirst, aLast, theC2D))
{ {
return Status(ShapeExtend_DONE); return Status(ShapeExtend_DONE);
} }
} }
else
{
aFirst = aNewBSpline->FirstParameter();
aLast = aNewBSpline->LastParameter();
aCurve = aNewBSpline;
}
}
// Generate curve points // Generate curve points
ArrayOfPnt aPoints; ArrayOfPnt aPoints;
ArrayOfReal aParams; ArrayOfReal aParams;
const Standard_Integer aNbPnt = const Standard_Integer aNbPnt =
generateCurvePoints(aCurve, aFirst, aLast, THE_NCONTROL, aPoints, aParams); generateCurvePoints(theC3D, theFirst, theLast, THE_NCONTROL, aPoints, aParams);
// Approximate pcurve // Approximate pcurve
ArrayOfPnt2d aPoints2d(1, aNbPnt); ArrayOfPnt2d aPoints2d(1, aNbPnt);
@@ -881,7 +734,7 @@ Standard_Boolean ShapeConstruct_ProjectCurveOnSurface::Perform(const Handle(Geom
aPoints2d.SetValue(i, gp_Pnt2d(0., 0.)); aPoints2d.SetValue(i, gp_Pnt2d(0., 0.));
} }
approxPCurve(aNbPnt, aCurve, theTolFirst, theTolLast, aPoints, aParams, aPoints2d, theC2D); approxPCurve(aNbPnt, theC3D, theTolFirst, theTolLast, aPoints, aParams, aPoints2d, theC2D);
const Standard_Integer aNbPini = aPoints.Length(); const Standard_Integer aNbPini = aPoints.Length();
if (!theC2D.IsNull()) if (!theC2D.IsNull())