From 2f4a3b7e34f076e1fb3c331e70d2afcef634a2dd Mon Sep 17 00:00:00 2001
From: abv <abv@opencascade.com>
Date: Sat, 21 Sep 2019 19:24:55 +0300
Subject: [PATCH] 0030990: Foundation Classes - unexpected change in numerical
 results on bsplines after 0029769

Added reproducer (DRAW command and test case) and debug output in BSplSLib_Cache
---
 src/BSplCLib/BSplCLib_Cache.cxx           | 164 +++----------
 src/BSplCLib/BSplCLib_Cache.hxx           |  47 ++--
 src/BSplCLib/BSplCLib_CacheParams.hxx     | 106 +++++++++
 src/BSplCLib/FILES                        |   1 +
 src/BSplSLib/BSplSLib_Cache.cxx           |  35 ++-
 src/Geom2dAdaptor/Geom2dAdaptor_Curve.cxx |  14 +-
 src/GeomAdaptor/GeomAdaptor_Curve.cxx     |   8 +-
 src/QABugs/FILES                          |   1 +
 src/QABugs/QABugs.cxx                     |   1 +
 src/QABugs/QABugs.hxx                     |  22 +-
 src/QABugs/QABugs_21.cxx                  | 269 ++++++++++++++++++++++
 tests/bugs/moddata_3/bug30990             |  10 +
 12 files changed, 482 insertions(+), 196 deletions(-)
 create mode 100644 src/BSplCLib/BSplCLib_CacheParams.hxx
 create mode 100644 src/QABugs/QABugs_21.cxx
 create mode 100644 tests/bugs/moddata_3/bug30990

diff --git a/src/BSplCLib/BSplCLib_Cache.cxx b/src/BSplCLib/BSplCLib_Cache.cxx
index 59efb2f8f1..f835c9b9e3 100644
--- a/src/BSplCLib/BSplCLib_Cache.cxx
+++ b/src/BSplCLib/BSplCLib_Cache.cxx
@@ -31,159 +31,71 @@ static Standard_Real* ConvertArray(const Handle(TColStd_HArray2OfReal)& theHArra
   return (Standard_Real*) &(anArray(anArray.LowerRow(), anArray.LowerCol()));
 }
 
-
-BSplCLib_Cache::BSplCLib_Cache()
-{
-  myPolesWeights.Nullify();
-  myIsRational = Standard_False;
-  mySpanStart = 0.0;
-  mySpanLength = 0.0;
-  mySpanIndex = 0;
-  myDegree = 0;
-  myFlatKnots.Nullify();
-}
-
 BSplCLib_Cache::BSplCLib_Cache(const Standard_Integer&        theDegree,
                                const Standard_Boolean&        thePeriodic,
                                const TColStd_Array1OfReal&    theFlatKnots,
-                               const TColgp_Array1OfPnt2d&    thePoles2d,
+                               const TColgp_Array1OfPnt2d&    /* only used to distinguish from 3d variant */,
                                const TColStd_Array1OfReal*    theWeights)
+: myIsRational(theWeights != NULL),
+  myParams (theDegree, thePeriodic, theFlatKnots)
 {
-  Standard_Real aCacheParam = theFlatKnots.Value(theFlatKnots.Lower() + theDegree);
-  BuildCache(aCacheParam, theDegree, thePeriodic, 
-             theFlatKnots, thePoles2d, theWeights);
+  Standard_Integer aPWColNumber = (myIsRational ? 3 : 2);
+  myPolesWeights = new TColStd_HArray2OfReal (1, theDegree + 1, 1, aPWColNumber);
 }
 
 BSplCLib_Cache::BSplCLib_Cache(const Standard_Integer&        theDegree,
                                const Standard_Boolean&        thePeriodic,
                                const TColStd_Array1OfReal&    theFlatKnots,
-                               const TColgp_Array1OfPnt&      thePoles,
+                               const TColgp_Array1OfPnt&      /* only used to distinguish from 2d variant */,
                                const TColStd_Array1OfReal*    theWeights)
+: myIsRational(theWeights != NULL),
+  myParams (theDegree, thePeriodic, theFlatKnots)
 {
-  Standard_Real aCacheParam = theFlatKnots.Value(theFlatKnots.Lower() + theDegree);
-  BuildCache(aCacheParam, theDegree, thePeriodic, 
-             theFlatKnots, thePoles, theWeights);
+  Standard_Integer aPWColNumber = (myIsRational ? 4 : 3);
+  myPolesWeights = new TColStd_HArray2OfReal (1, theDegree + 1, 1, aPWColNumber);
 }
 
-
 Standard_Boolean BSplCLib_Cache::IsCacheValid(Standard_Real theParameter) const
 {
-  Standard_Real aNewParam = theParameter;
-  if (!myFlatKnots.IsNull())
-    PeriodicNormalization(myFlatKnots->Array1(), aNewParam);
-
-  Standard_Real aDelta = aNewParam - mySpanStart;
-  return ((aDelta >= 0.0 || mySpanIndex == mySpanIndexMin) &&
-          (aDelta < mySpanLength || mySpanIndex == mySpanIndexMax));
+  return myParams.IsCacheValid (theParameter);
 }
 
-void BSplCLib_Cache::PeriodicNormalization(const TColStd_Array1OfReal& theFlatKnots, 
-                                           Standard_Real& theParameter) const
-{
-  Standard_Real aPeriod = theFlatKnots.Value(theFlatKnots.Upper() - myDegree) - 
-                          theFlatKnots.Value(myDegree + 1) ;
-  if (theParameter < theFlatKnots.Value(myDegree + 1))
-  {
-    Standard_Real aScale = IntegerPart(
-        (theFlatKnots.Value(myDegree + 1) - theParameter) / aPeriod);
-    theParameter += aPeriod * (aScale + 1.0);
-  }
-  if (theParameter > theFlatKnots.Value(theFlatKnots.Upper() - myDegree))
-  {
-    Standard_Real aScale = IntegerPart(
-        (theParameter - theFlatKnots.Value(theFlatKnots.Upper() - myDegree)) / aPeriod);
-    theParameter -= aPeriod * (aScale + 1.0);
-  }
-}
-
-
 void BSplCLib_Cache::BuildCache(const Standard_Real&           theParameter,
-                                const Standard_Integer&        theDegree,
-                                const Standard_Boolean&        thePeriodic,
                                 const TColStd_Array1OfReal&    theFlatKnots,
                                 const TColgp_Array1OfPnt2d&    thePoles2d,
                                 const TColStd_Array1OfReal*    theWeights)
 {
   // Normalize theParameter for periodical B-splines
-  Standard_Real aNewParam = theParameter;
-  if (thePeriodic)
-  {
-    PeriodicNormalization(theFlatKnots, aNewParam);
-    myFlatKnots = new TColStd_HArray1OfReal(1, theFlatKnots.Length());
-    myFlatKnots->ChangeArray1() = theFlatKnots;
-  }
-  else if (!myFlatKnots.IsNull()) // Periodical curve became non-periodical
-    myFlatKnots.Nullify();
-
-  // Change the size of cached data if needed
-  myIsRational = (theWeights != NULL);
-  Standard_Integer aPWColNumber = myIsRational ? 3 : 2;
-  if (theDegree > myDegree)
-    myPolesWeights = new TColStd_HArray2OfReal(1, theDegree + 1, 1, aPWColNumber);
-
-  myDegree = theDegree;
-  mySpanIndex = 0;
-  BSplCLib::LocateParameter(theDegree, theFlatKnots, BSplCLib::NoMults(), 
-                            aNewParam, thePeriodic, mySpanIndex, aNewParam);
-  mySpanStart  = theFlatKnots.Value(mySpanIndex);
-  mySpanLength = theFlatKnots.Value(mySpanIndex + 1) - mySpanStart;
-  mySpanIndexMin = thePeriodic ? 0 : myDegree + 1;
-  mySpanIndexMax = theFlatKnots.Length() - 1 - theDegree;
+  Standard_Real aNewParam = myParams.PeriodicNormalization (theParameter);
+  myParams.LocateParameter (aNewParam, theFlatKnots);
 
   // Calculate new cache data
-  BSplCLib::BuildCache(mySpanStart, mySpanLength, thePeriodic, theDegree, 
-                       mySpanIndex, theFlatKnots, thePoles2d, theWeights, 
-                       myPolesWeights->ChangeArray2());
+  BSplCLib::BuildCache (myParams.SpanStart, myParams.SpanLength, myParams.IsPeriodic,
+                        myParams.Degree, myParams.SpanIndex, theFlatKnots, thePoles2d,
+                        theWeights, myPolesWeights->ChangeArray2());
 }
 
 void BSplCLib_Cache::BuildCache(const Standard_Real&           theParameter,
-                                const Standard_Integer&        theDegree,
-                                const Standard_Boolean&        thePeriodic,
                                 const TColStd_Array1OfReal&    theFlatKnots,
                                 const TColgp_Array1OfPnt&      thePoles,
                                 const TColStd_Array1OfReal*    theWeights)
 {
   // Create list of knots with repetitions and normalize theParameter for periodical B-splines
-  Standard_Real aNewParam = theParameter;
-  if (thePeriodic)
-  {
-    PeriodicNormalization(theFlatKnots, aNewParam);
-    myFlatKnots = new TColStd_HArray1OfReal(1, theFlatKnots.Length());
-    myFlatKnots->ChangeArray1() = theFlatKnots;
-  }
-  else if (!myFlatKnots.IsNull()) // Periodical curve became non-periodical
-    myFlatKnots.Nullify();
-
-  // Change the size of cached data if needed
-  myIsRational = (theWeights != NULL);
-  Standard_Integer aPWColNumber = myIsRational ? 4 : 3;
-  if (theDegree > myDegree)
-    myPolesWeights = new TColStd_HArray2OfReal(1, theDegree + 1, 1, aPWColNumber);
-
-  myDegree = theDegree;
-  mySpanIndex = 0;
-  BSplCLib::LocateParameter(theDegree, theFlatKnots, BSplCLib::NoMults(), 
-                            aNewParam, thePeriodic, mySpanIndex, aNewParam);
-  mySpanStart  = theFlatKnots.Value(mySpanIndex);
-  mySpanLength = theFlatKnots.Value(mySpanIndex + 1) - mySpanStart;
-  mySpanIndexMin = thePeriodic ? 0 : myDegree + 1;
-  mySpanIndexMax = theFlatKnots.Length() - 1 - theDegree;
+  Standard_Real aNewParam = myParams.PeriodicNormalization (theParameter);
+  myParams.LocateParameter (aNewParam, theFlatKnots);
 
   // Calculate new cache data
-  BSplCLib::BuildCache(mySpanStart, mySpanLength, thePeriodic, theDegree, 
-                       mySpanIndex, theFlatKnots, thePoles, theWeights, 
-                       myPolesWeights->ChangeArray2());
+  BSplCLib::BuildCache (myParams.SpanStart, myParams.SpanLength, myParams.IsPeriodic, 
+                        myParams.Degree, myParams.SpanIndex, theFlatKnots, thePoles,
+                        theWeights, myPolesWeights->ChangeArray2());
 }
 
-
 void BSplCLib_Cache::CalculateDerivative(const Standard_Real&    theParameter, 
                                          const Standard_Integer& theDerivative, 
                                                Standard_Real&    theDerivArray) const
 {
-  Standard_Real aNewParameter = theParameter;
-  if (!myFlatKnots.IsNull()) // B-spline is periodical
-    PeriodicNormalization(myFlatKnots->Array1(), aNewParameter);
-  aNewParameter = (aNewParameter - mySpanStart) / mySpanLength;
+  Standard_Real aNewParameter = myParams.PeriodicNormalization (theParameter);
+  aNewParameter = (aNewParameter - myParams.SpanStart) / myParams.SpanLength;
 
   Standard_Real* aPolesArray = ConvertArray(myPolesWeights);
   Standard_Integer aDimension = myPolesWeights->RowLength(); // number of columns
@@ -199,23 +111,23 @@ void BSplCLib_Cache::CalculateDerivative(const Standard_Real&    theParameter,
   // When the degree of curve is lesser than the requested derivative,
   // nullify array cells corresponding to greater derivatives
   Standard_Integer aDerivative = theDerivative;
-  if (myDegree < theDerivative)
+  if (myParams.Degree < theDerivative)
   {
-    aDerivative = myDegree;
-    for (Standard_Integer ind = myDegree * aDimension; ind < (theDerivative + 1) * aDimension; ind++)
+    aDerivative = myParams.Degree;
+    for (Standard_Integer ind = myParams.Degree * aDimension; ind < (theDerivative + 1) * aDimension; ind++)
     {
       aPntDeriv[ind] = 0.0;
       (&theDerivArray)[ind] = 0.0; // should be cleared separately, because aPntDeriv may look to another memory area
     }
   }
 
-  PLib::EvalPolynomial(aNewParameter, aDerivative, myDegree, aDimension, 
+  PLib::EvalPolynomial(aNewParameter, aDerivative, myParams.Degree, aDimension, 
                        aPolesArray[0], aPntDeriv[0]);
   // Unnormalize derivatives since those are computed normalized
   Standard_Real aFactor = 1.0;
   for (Standard_Integer deriv = 1; deriv <= aDerivative; deriv++)
   {
-    aFactor /= mySpanLength;
+    aFactor /= myParams.SpanLength;
     for (Standard_Integer ind = 0; ind < aDimension; ind++)
       aPntDeriv[aDimension * deriv + ind] *= aFactor;
   }
@@ -227,17 +139,15 @@ void BSplCLib_Cache::CalculateDerivative(const Standard_Real&    theParameter,
 
 void BSplCLib_Cache::D0(const Standard_Real& theParameter, gp_Pnt2d& thePoint) const
 {
-  Standard_Real aNewParameter = theParameter;
-  if (!myFlatKnots.IsNull()) // B-spline is periodical
-    PeriodicNormalization(myFlatKnots->Array1(), aNewParameter);
-  aNewParameter = (aNewParameter - mySpanStart) / mySpanLength;
+  Standard_Real aNewParameter = myParams.PeriodicNormalization (theParameter);
+  aNewParameter = (aNewParameter - myParams.SpanStart) / myParams.SpanLength;
 
   Standard_Real* aPolesArray = ConvertArray(myPolesWeights);
   Standard_Real aPoint[4];
   Standard_Integer aDimension = myPolesWeights->RowLength(); // number of columns
 
-  PLib::NoDerivativeEvalPolynomial(aNewParameter, myDegree,
-                                   aDimension, myDegree * aDimension,
+  PLib::NoDerivativeEvalPolynomial(aNewParameter, myParams.Degree,
+                                   aDimension, myParams.Degree * aDimension,
                                    aPolesArray[0], aPoint[0]);
 
   thePoint.SetCoord(aPoint[0], aPoint[1]);
@@ -247,17 +157,15 @@ void BSplCLib_Cache::D0(const Standard_Real& theParameter, gp_Pnt2d& thePoint) c
 
 void BSplCLib_Cache::D0(const Standard_Real& theParameter, gp_Pnt& thePoint) const
 {
-  Standard_Real aNewParameter = theParameter;
-  if (!myFlatKnots.IsNull()) // B-spline is periodical
-    PeriodicNormalization(myFlatKnots->Array1(), aNewParameter);
-  aNewParameter = (aNewParameter - mySpanStart) / mySpanLength;
+  Standard_Real aNewParameter = myParams.PeriodicNormalization (theParameter);
+  aNewParameter = (aNewParameter - myParams.SpanStart) / myParams.SpanLength;
 
   Standard_Real* aPolesArray = ConvertArray(myPolesWeights);
   Standard_Real aPoint[4];
   Standard_Integer aDimension = myPolesWeights->RowLength(); // number of columns
 
-  PLib::NoDerivativeEvalPolynomial(aNewParameter, myDegree,
-                                   aDimension, myDegree * aDimension,
+  PLib::NoDerivativeEvalPolynomial(aNewParameter, myParams.Degree,
+                                   aDimension, myParams.Degree * aDimension,
                                    aPolesArray[0], aPoint[0]);
 
   thePoint.SetCoord(aPoint[0], aPoint[1], aPoint[2]);
diff --git a/src/BSplCLib/BSplCLib_Cache.hxx b/src/BSplCLib/BSplCLib_Cache.hxx
index 74bcd14d00..a4bfd46abb 100644
--- a/src/BSplCLib/BSplCLib_Cache.hxx
+++ b/src/BSplCLib/BSplCLib_Cache.hxx
@@ -19,7 +19,6 @@
 #include <Standard_Type.hxx>
 #include <Standard_Transient.hxx>
 
-
 #include <gp_Pnt2d.hxx>
 #include <gp_Pnt.hxx>
 #include <gp_Vec2d.hxx>
@@ -31,6 +30,8 @@
 #include <TColgp_Array1OfPnt.hxx>
 #include <TColgp_Array1OfPnt2d.hxx>
 
+#include <BSplCLib_CacheParams.hxx>
+
 //! \brief A cache class for Bezier and B-spline curves.
 //!
 //! Defines all data, that can be cached on a span of a curve.
@@ -38,11 +39,10 @@
 class BSplCLib_Cache : public Standard_Transient
 {
 public:
-  //! Default constructor
-  Standard_EXPORT BSplCLib_Cache();
-  //! Constructor for caching of 2D curves
+
+  //! Constructor, prepares data structures for caching values on a 2d curve.
   //! \param theDegree     degree of the curve
-  //! \param thePeriodic   identify the curve is periodic
+  //! \param thePeriodic   identify whether the curve is periodic
   //! \param theFlatKnots  knots of Bezier/B-spline curve (with repetitions)
   //! \param thePoles2d    array of poles of 2D curve
   //! \param theWeights    array of weights of corresponding poles
@@ -51,9 +51,10 @@ public:
                                  const TColStd_Array1OfReal&    theFlatKnots,
                                  const TColgp_Array1OfPnt2d&    thePoles2d,
                                  const TColStd_Array1OfReal*    theWeights = NULL);
-  //! Constructor for caching of 3D curves
+
+  //! Constructor, prepares data structures for caching values on a 3d curve.
   //! \param theDegree     degree of the curve
-  //! \param thePeriodic   identify the curve is periodic
+  //! \param thePeriodic   identify whether the curve is periodic
   //! \param theFlatKnots  knots of Bezier/B-spline curve (with repetitions)
   //! \param thePoles      array of poles of 3D curve
   //! \param theWeights    array of weights of corresponding poles
@@ -69,27 +70,20 @@ public:
 
   //! Recomputes the cache data for 2D curves. Does not verify validity of the cache
   //! \param theParameter  the value on the knot's axis to identify the span
-  //! \param theDegree     degree of the curve
-  //! \param thePeriodic   identify the curve is periodic
   //! \param theFlatKnots  knots of Bezier/B-spline curve (with repetitions)
   //! \param thePoles2d    array of poles of 2D curve
   //! \param theWeights    array of weights of corresponding poles
   Standard_EXPORT void BuildCache(const Standard_Real&           theParameter,
-                                  const Standard_Integer&        theDegree,
-                                  const Standard_Boolean&        thePeriodic,
                                   const TColStd_Array1OfReal&    theFlatKnots,
                                   const TColgp_Array1OfPnt2d&    thePoles2d,
-                                  const TColStd_Array1OfReal*    theWeights = NULL);
+                                  const TColStd_Array1OfReal*    theWeights);
+
   //! Recomputes the cache data for 3D curves. Does not verify validity of the cache
   //! \param theParameter  the value on the knot's axis to identify the span
-  //! \param theDegree     degree of the curve
-  //! \param thePeriodic   identify the curve is periodic
   //! \param theFlatKnots  knots of Bezier/B-spline curve (with repetitions)
   //! \param thePoles      array of poles of 3D curve
   //! \param theWeights    array of weights of corresponding poles
   Standard_EXPORT void BuildCache(const Standard_Real&           theParameter,
-                                  const Standard_Integer&        theDegree,
-                                  const Standard_Boolean&        thePeriodic,
                                   const TColStd_Array1OfReal&    theFlatKnots,
                                   const TColgp_Array1OfPnt&      thePoles,
                                   const TColStd_Array1OfReal*    theWeights = NULL);
@@ -142,10 +136,6 @@ public:
   DEFINE_STANDARD_RTTIEXT(BSplCLib_Cache,Standard_Transient)
 
 protected:
-  //! Normalizes the parameter for periodical curves
-  //! \param theFlatKnots knots with repetitions
-  //! \param theParameter the value to be normalized into the knots array
-  void PeriodicNormalization(const TColStd_Array1OfReal& theFlatKnots, Standard_Real& theParameter) const;
 
   //! Fills array of derivatives in the selected point of the curve
   //! \param[in]  theParameter  parameter of the calculation
@@ -156,21 +146,18 @@ protected:
                            const Standard_Integer& theDerivative, 
                                  Standard_Real&    theDerivArray) const;
 
+  // copying is prohibited
+  BSplCLib_Cache (const BSplCLib_Cache&);
+  void operator = (const BSplCLib_Cache&);
+
 private:
-  Handle(TColStd_HArray2OfReal) myPolesWeights; ///< array of poles and weights of calculated cache
+  Standard_Boolean myIsRational;                //!< identifies the rationality of Bezier/B-spline curve
+  BSplCLib_CacheParams myParams;                //!< cache parameters
+  Handle(TColStd_HArray2OfReal) myPolesWeights; //!< array of poles and weights of calculated cache
                                                 // the array has following structure:
                                                 //       x1 y1 [z1] [w1]
                                                 //       x2 y2 [z2] [w2] etc
                                                 // for 2D-curves there is no z conponent, for non-rational curves there is no weight
-
-  Standard_Boolean              myIsRational; ///< identifies the rationality of Bezier/B-spline curve
-  Standard_Real                 mySpanStart;  ///< parameter for the first point of the span
-  Standard_Real                 mySpanLength; ///< length of the span
-  Standard_Integer              mySpanIndex;  ///< index of the span on Bezier/B-spline curve
-  Standard_Integer              mySpanIndexMin; ///< minimal index of span on Bezier/B-spline curve
-  Standard_Integer              mySpanIndexMax; ///< maximal number of spans on Bezier/B-spline curve
-  Standard_Integer              myDegree;     ///< degree of Bezier/B-spline
-  Handle(TColStd_HArray1OfReal) myFlatKnots;  ///< knots of Bezier/B-spline (used for periodic normalization of parameters, exists only for periodical splines)
 };
 
 DEFINE_STANDARD_HANDLE(BSplCLib_Cache, Standard_Transient)
diff --git a/src/BSplCLib/BSplCLib_CacheParams.hxx b/src/BSplCLib/BSplCLib_CacheParams.hxx
new file mode 100644
index 0000000000..a25094b251
--- /dev/null
+++ b/src/BSplCLib/BSplCLib_CacheParams.hxx
@@ -0,0 +1,106 @@
+// Copyright (c) 2018 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 _BSplCLib_CacheParams_Headerfile
+#define _BSplCLib_CacheParams_Headerfile
+
+#include <Standard_Real.hxx>
+#include <TColStd_Array1OfReal.hxx>
+
+#include <BSplCLib.hxx>
+
+//! Simple structure containing parameters describing parameterization
+//! of a B-spline curve or a surface in one direction (U or V),
+//! and data of the current span for its caching
+struct BSplCLib_CacheParams
+{
+  const Standard_Integer Degree;      ///< degree of Bezier/B-spline
+  const Standard_Boolean IsPeriodic;  ///< true of the B-spline is periodic
+  const Standard_Real FirstParameter; ///< first valid parameter
+  const Standard_Real LastParameter;  ///< last valid parameter
+
+  const Standard_Integer SpanIndexMin; ///< minimal index of span
+  const Standard_Integer SpanIndexMax; ///< maximal index of span
+
+  Standard_Real    SpanStart;    ///< parameter for the frst point of the span
+  Standard_Real    SpanLength;   ///< length of the span
+  Standard_Integer SpanIndex;    ///< index of the span
+
+  //! Constructor, prepares data structures for caching.
+  //! \param theDegree     degree of the B-spline (or Bezier)
+  //! \param thePeriodic   identify whether the B-spline is periodic
+  //! \param theFlatKnots  knots of Bezier / B-spline parameterization
+  BSplCLib_CacheParams (Standard_Integer theDegree, Standard_Boolean thePeriodic,
+                        const TColStd_Array1OfReal& theFlatKnots)
+  : Degree(theDegree),
+    IsPeriodic(thePeriodic),
+    FirstParameter(theFlatKnots.Value(theFlatKnots.Lower() + theDegree)),
+    LastParameter(theFlatKnots.Value(theFlatKnots.Upper() - theDegree)),
+    SpanIndexMin(theFlatKnots.Lower() + theDegree),
+    SpanIndexMax(theFlatKnots.Upper() - theDegree - 1),
+    SpanStart(0.),
+    SpanLength(0.),
+    SpanIndex(0)
+  {}
+
+  //! Normalizes the parameter for periodic B-splines
+  //! \param theParameter the value to be normalized into the knots array
+  Standard_Real PeriodicNormalization (Standard_Real theParameter) const
+  {
+    if (IsPeriodic)
+    {
+      if (theParameter < FirstParameter)
+      {
+        Standard_Real aPeriod = LastParameter - FirstParameter;
+        Standard_Real aScale = IntegerPart ((FirstParameter - theParameter) / aPeriod);
+        return theParameter + aPeriod * (aScale + 1.0);
+      }
+      if (theParameter > LastParameter)
+      {
+        Standard_Real aPeriod = LastParameter - FirstParameter;
+        Standard_Real aScale = IntegerPart ((theParameter - LastParameter) / aPeriod);
+        return theParameter - aPeriod * (aScale + 1.0);
+      }
+    }
+    return theParameter;
+  }
+
+  //! Verifies validity of the cache using flat parameter of the point
+  //! \param theParameter parameter of the point placed in the span
+  Standard_Boolean IsCacheValid (Standard_Real theParameter) const
+  {
+    Standard_Real aNewParam = PeriodicNormalization  (theParameter);
+    Standard_Real aDelta = aNewParam - SpanStart;
+    return ((aDelta >= 0.0 || SpanIndex == SpanIndexMin) &&
+            (aDelta < SpanLength || SpanIndex == SpanIndexMax));
+  }
+
+  //! Computes span for the specified parameter
+  //! \param theParameter parameter of the point placed in the span
+  //! \param theFlatKnots  knots of Bezier / B-spline parameterization
+  void LocateParameter (Standard_Real& theParameter, const TColStd_Array1OfReal& theFlatKnots)
+  {
+    SpanIndex = 0;
+    BSplCLib::LocateParameter (Degree, theFlatKnots, BSplCLib::NoMults(), 
+                               theParameter, IsPeriodic, SpanIndex, theParameter);
+    SpanStart  = theFlatKnots.Value(SpanIndex);
+    SpanLength = theFlatKnots.Value(SpanIndex + 1) - SpanStart;
+  }
+
+private:
+  // copying is prohibited
+  BSplCLib_CacheParams (const BSplCLib_CacheParams&);
+  void operator = (const BSplCLib_CacheParams&);
+};
+
+#endif
diff --git a/src/BSplCLib/FILES b/src/BSplCLib/FILES
index 2975ef6145..5aec2f5496 100755
--- a/src/BSplCLib/FILES
+++ b/src/BSplCLib/FILES
@@ -7,6 +7,7 @@ BSplCLib_3.cxx
 BSplCLib_BzSyntaxes.cxx
 BSplCLib_Cache.cxx
 BSplCLib_Cache.hxx
+BSplCLib_CacheParams.hxx
 BSplCLib_CurveComputation.gxx
 BSplCLib_EvaluatorFunction.hxx
 BSplCLib_KnotDistribution.hxx
diff --git a/src/BSplSLib/BSplSLib_Cache.cxx b/src/BSplSLib/BSplSLib_Cache.cxx
index dd9ba2eaac..2ae7c49221 100644
--- a/src/BSplSLib/BSplSLib_Cache.cxx
+++ b/src/BSplSLib/BSplSLib_Cache.cxx
@@ -75,10 +75,13 @@ Standard_Boolean BSplSLib_Cache::IsCacheValid(Standard_Real theParameterU,
 
   Standard_Real aDelta0 = aNewU - mySpanStart[0];
   Standard_Real aDelta1 = aNewV - mySpanStart[1];
-  return ((aDelta0 >= -mySpanLength[0] || mySpanIndex[0] == mySpanIndexMin[0]) &&
-          (aDelta0 < mySpanLength[0] || mySpanIndex[0] == mySpanIndexMax[0]) &&
-          (aDelta1 >= -mySpanLength[1] || mySpanIndex[1] == mySpanIndexMin[1]) &&
-          (aDelta1 < mySpanLength[1] || mySpanIndex[1] == mySpanIndexMax[1]));
+  bool isU = (aDelta0 >= -mySpanLength[0] || mySpanIndex[0] == mySpanIndexMin[0]) &&
+          (aDelta0 < mySpanLength[0] || mySpanIndex[0] == mySpanIndexMax[0]);
+  bool isV = (aDelta1 >= -mySpanLength[1] || mySpanIndex[1] == mySpanIndexMin[1]) &&
+          (aDelta1 < mySpanLength[1] || mySpanIndex[1] == mySpanIndexMax[1]);
+  std::cout << "U cache: " << theParameterU << (isU ? " valid" : " recompute") << std::endl;
+  std::cout << "V cache: " << theParameterV << (isV ? " valid" : " recompute") << std::endl;
+  return isU && isV;
 }
 
 void BSplSLib_Cache::PeriodicNormalization(const Standard_Integer& theDegree, 
@@ -145,6 +148,8 @@ void BSplSLib_Cache::BuildCache(const Standard_Real&           theParameterU,
 
   myDegree[0] = theDegreeU;
   myDegree[1] = theDegreeV;
+  int prevIndexU = mySpanIndex[0];
+  int prevIndexV = mySpanIndex[1];
   mySpanIndex[0] = mySpanIndex[1] = 0;
   BSplCLib::LocateParameter(theDegreeU, theFlatKnotsU, BSplCLib::NoMults(), aNewParamU, 
                             thePeriodicU, mySpanIndex[0], aNewParamU);
@@ -171,6 +176,12 @@ void BSplSLib_Cache::BuildCache(const Standard_Real&           theParameterU,
   mySpanIndexMin[1] = thePeriodicV ? 0 : theDegreeV + 1;
   mySpanIndexMax[1] = theFlatKnotsV.Length() - 1 - theDegreeV;
 
+  std::cout.precision (20);
+  std::cout << "BuildCache: U " << theParameterU << " -> (" << mySpanStart[0] << ", " << mySpanLength[0] << ")" << std::endl;
+  std::cout << "            V " << theParameterV << " -> (" << mySpanStart[1] << ", " << mySpanLength[1] << ")" << std::endl;
+  if (prevIndexU == mySpanIndex[0] && prevIndexV == mySpanIndex[1])
+    std::cout << "Warning: the span is the same as before: " << prevIndexU << ", " << prevIndexV << std::endl;
+
   // Calculate new cache data
   BSplSLib::BuildCache(mySpanStart[0],  mySpanStart[1], 
                        mySpanLength[0], mySpanLength[1], 
@@ -229,6 +240,10 @@ void BSplSLib_Cache::D0(const Standard_Real& theU,
   thePoint.SetCoord(aPoint[0], aPoint[1], aPoint[2]);
   if (myIsRational)
     thePoint.ChangeCoord().Divide(aPoint[3]);
+
+  std::cout.precision(20);
+  std::cout << "D0: (" << theU << ", " << theV << ") -> (" 
+                   << thePoint.X() << ", " << thePoint.Y() << ", " << thePoint.Z() << ")" << std::endl;
 }
 
 
@@ -307,6 +322,12 @@ void BSplSLib_Cache::D1(const Standard_Real& theU,
   }
   theTangentU.Multiply(anInvU);
   theTangentV.Multiply(anInvV);
+
+  std::cout.precision(20);
+  std::cout << "D1: (" << theU << ", " << theV << ") -> (" 
+                   << thePoint.X() << ", " << thePoint.Y() << ", " << thePoint.Z() << ")" << std::endl;
+  std::cout << "Nu: (" << theTangentU.X() << ", " << theTangentU.Y() << ", " << theTangentU.Z() << ")" << std::endl;
+  std::cout << "Nv: (" << theTangentV.X() << ", " << theTangentV.Y() << ", " << theTangentV.Z() << ")" << std::endl;
 }
 
 
@@ -419,5 +440,11 @@ void BSplSLib_Cache::D2(const Standard_Real& theU,
   theCurvatureU.Multiply(anInvU * anInvU);
   theCurvatureV.Multiply(anInvV * anInvV);
   theCurvatureUV.Multiply(anInvU * anInvV);
+
+  std::cout.precision(20);
+  std::cout << "D2: (" << theU << ", " << theV << ") -> (" 
+                   << thePoint.X() << ", " << thePoint.Y() << ", " << thePoint.Z() << ")" << std::endl;
+  std::cout << "Nu: (" << theTangentU.X() << ", " << theTangentU.Y() << ", " << theTangentU.Z() << ")" << std::endl;
+  std::cout << "Nv: (" << theTangentV.X() << ", " << theTangentV.Y() << ", " << theTangentV.Z() << ")" << std::endl;
 }
 
diff --git a/src/Geom2dAdaptor/Geom2dAdaptor_Curve.cxx b/src/Geom2dAdaptor/Geom2dAdaptor_Curve.cxx
index e4a30742f2..ad98572264 100644
--- a/src/Geom2dAdaptor/Geom2dAdaptor_Curve.cxx
+++ b/src/Geom2dAdaptor/Geom2dAdaptor_Curve.cxx
@@ -556,20 +556,18 @@ void Geom2dAdaptor_Curve::RebuildCache(const Standard_Real theParameter) const
     Standard_Integer aDeg = aBezier->Degree();
     TColStd_Array1OfReal aFlatKnots(BSplCLib::FlatBezierKnots(aDeg), 1, 2 * (aDeg + 1));
     if (myCurveCache.IsNull())
-      myCurveCache = new BSplCLib_Cache(aDeg, aBezier->IsPeriodic(), aFlatKnots,
-        aBezier->Poles(), aBezier->Weights());
-    myCurveCache->BuildCache(theParameter, aDeg, aBezier->IsPeriodic(), aFlatKnots,
-      aBezier->Poles(), aBezier->Weights());
+      myCurveCache = new BSplCLib_Cache (aDeg, aBezier->IsPeriodic(), aFlatKnots,
+                                         aBezier->Poles(), aBezier->Weights());
+    myCurveCache->BuildCache (theParameter, aFlatKnots, aBezier->Poles(), aBezier->Weights());
   }
   else if (myTypeCurve == GeomAbs_BSplineCurve)
   {
     // Create cache for B-spline
     if (myCurveCache.IsNull())
-      myCurveCache = new BSplCLib_Cache(myBSplineCurve->Degree(), myBSplineCurve->IsPeriodic(),
+      myCurveCache = new BSplCLib_Cache (myBSplineCurve->Degree(), myBSplineCurve->IsPeriodic(),
         myBSplineCurve->KnotSequence(), myBSplineCurve->Poles(), myBSplineCurve->Weights());
-    myCurveCache->BuildCache(theParameter, myBSplineCurve->Degree(),
-      myBSplineCurve->IsPeriodic(), myBSplineCurve->KnotSequence(),
-      myBSplineCurve->Poles(), myBSplineCurve->Weights());
+    myCurveCache->BuildCache (theParameter, myBSplineCurve->KnotSequence(),
+                              myBSplineCurve->Poles(), myBSplineCurve->Weights());
   }
 }
 
diff --git a/src/GeomAdaptor/GeomAdaptor_Curve.cxx b/src/GeomAdaptor/GeomAdaptor_Curve.cxx
index 42bf072f36..0215eefe66 100644
--- a/src/GeomAdaptor/GeomAdaptor_Curve.cxx
+++ b/src/GeomAdaptor/GeomAdaptor_Curve.cxx
@@ -539,8 +539,7 @@ void GeomAdaptor_Curve::RebuildCache(const Standard_Real theParameter) const
     if (myCurveCache.IsNull())
       myCurveCache = new BSplCLib_Cache(aDeg, aBezier->IsPeriodic(), aFlatKnots,
         aBezier->Poles(), aBezier->Weights());
-    myCurveCache->BuildCache(theParameter, aDeg, aBezier->IsPeriodic(), aFlatKnots,
-        aBezier->Poles(), aBezier->Weights());
+    myCurveCache->BuildCache (theParameter, aFlatKnots, aBezier->Poles(), aBezier->Weights());
 }
   else if (myTypeCurve == GeomAbs_BSplineCurve)
 {
@@ -548,9 +547,8 @@ void GeomAdaptor_Curve::RebuildCache(const Standard_Real theParameter) const
     if (myCurveCache.IsNull())
       myCurveCache = new BSplCLib_Cache(myBSplineCurve->Degree(), myBSplineCurve->IsPeriodic(),
         myBSplineCurve->KnotSequence(), myBSplineCurve->Poles(), myBSplineCurve->Weights());
-    myCurveCache->BuildCache(theParameter, myBSplineCurve->Degree(),
-        myBSplineCurve->IsPeriodic(), myBSplineCurve->KnotSequence(),
-        myBSplineCurve->Poles(), myBSplineCurve->Weights());
+    myCurveCache->BuildCache (theParameter, myBSplineCurve->KnotSequence(),
+                              myBSplineCurve->Poles(), myBSplineCurve->Weights());
 }
 }
 
diff --git a/src/QABugs/FILES b/src/QABugs/FILES
index 7e13b24803..0399f81a3d 100644
--- a/src/QABugs/FILES
+++ b/src/QABugs/FILES
@@ -20,5 +20,6 @@ QABugs_17.cxx
 QABugs_18.cxx
 QABugs_19.cxx
 QABugs_20.cxx
+QABugs_21.cxx
 QABugs_PresentableObject.cxx
 QABugs_PresentableObject.hxx
diff --git a/src/QABugs/QABugs.cxx b/src/QABugs/QABugs.cxx
index 9449d71000..2933d3dedb 100644
--- a/src/QABugs/QABugs.cxx
+++ b/src/QABugs/QABugs.cxx
@@ -36,6 +36,7 @@ void QABugs::Commands(Draw_Interpretor& theCommands) {
   QABugs::Commands_18(theCommands);
   QABugs::Commands_19(theCommands);
   QABugs::Commands_20(theCommands);
+  QABugs::Commands_21(theCommands);
 
   return;
 }
diff --git a/src/QABugs/QABugs.hxx b/src/QABugs/QABugs.hxx
index bfd1a989cb..2e2d8131ac 100644
--- a/src/QABugs/QABugs.hxx
+++ b/src/QABugs/QABugs.hxx
@@ -75,28 +75,8 @@ public:
 
   Standard_EXPORT static void Commands_20 (Draw_Interpretor& DI);
 
-
-
-protected:
-
-
-
-
-
-private:
-
-
-
-
-friend class QABugs_MyText;
-friend class QABugs_PresentableObject;
+  Standard_EXPORT static void Commands_21 (Draw_Interpretor& DI);
 
 };
 
-
-
-
-
-
-
 #endif // _QABugs_HeaderFile
diff --git a/src/QABugs/QABugs_21.cxx b/src/QABugs/QABugs_21.cxx
new file mode 100644
index 0000000000..c42514b277
--- /dev/null
+++ b/src/QABugs/QABugs_21.cxx
@@ -0,0 +1,269 @@
+// Created on: 2019-08-27
+// Created by: Benjamin Bihler
+// Copyright (c) 2002-2014 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 <QABugs.hxx>
+
+#include <TopoDS_Edge.hxx>
+#include <TopoDS.hxx>
+#include <Precision.hxx>
+#include <TopExp_Explorer.hxx>
+#include <BRepTools.hxx>
+#include <BRep_Builder.hxx>
+#include <BRepAdaptor_Curve.hxx>
+#include <BRepAdaptor_CompCurve.hxx>
+#include <BRepCheck_Analyzer.hxx>
+#include <BRepBuilderAPI_MakeVertex.hxx>
+#include <GeomConvert_CompCurveToBSplineCurve.hxx>
+#include <Geom_BoundedCurve.hxx>
+#include <Geom_BSplineCurve.hxx>
+#include <Geom_BSplineSurface.hxx>
+#include <Geom2d_BSplineCurve.hxx>
+#include <Geom_Surface.hxx>
+#include <Geom2dAdaptor_HCurve.hxx>
+#include <GeomAdaptor_HSurface.hxx>
+#include <Adaptor3d_CurveOnSurface.hxx>
+#include <GeomLib.hxx>
+#include <DBRep.hxx>
+
+#include <iostream>
+#include <sstream>
+#include <vector>
+
+static std::string getGenerationCode (const Handle(Geom_BSplineCurve)& curve, const std::string& variableName)
+{
+  const std::string postFix = "_" + variableName;
+
+  std::stringstream stream;
+  stream << std::setprecision(32);
+
+  stream << "TColgp_Array1OfPnt poles" << postFix << " (1, "
+    << curve->Poles().Size() << ");" << std::endl;
+  stream << std::endl;
+
+  for (int poleIndex = 1; poleIndex <= curve->Poles().Size(); ++poleIndex)
+  {
+    stream << "poles" << postFix << ".SetValue(" << poleIndex << ", gp_Pnt("
+           << curve->Poles().Value(poleIndex).X() << ", "
+           << curve->Poles().Value(poleIndex).Y() << ", "
+           << curve->Poles().Value(poleIndex).Z() << "));" << std::endl;
+  }
+
+  stream << std::endl;
+
+  stream << "TColStd_Array1OfReal knots" << postFix << " (1, "
+         << curve->Knots().Size() << ");" << std::endl;
+  stream << std::endl;
+
+  for (int knotIndex = 1; knotIndex <= curve->Knots().Size(); ++knotIndex)
+  {
+    stream << "knots" << postFix << ".SetValue(" << knotIndex << ", "
+           << curve->Knots().Value(knotIndex) << ");" << std::endl;
+  }
+
+  stream << std::endl;
+
+  stream << "TColStd_Array1OfInteger multiplicities" << postFix << " (1, "
+         << curve->Multiplicities().Size() << ");" << std::endl;
+  stream << std::endl;
+
+  for (int multiplicityIndex = 1; multiplicityIndex <= curve->Knots().Size();
+    ++multiplicityIndex)
+  {
+    stream << "multiplicities" << postFix << ".SetValue(" << multiplicityIndex
+           << ", " << curve->Multiplicities().Value(multiplicityIndex) << ");"
+           << std::endl;
+  }
+
+  stream << std::endl;
+
+  stream << "Standard_Integer degree" << postFix << " = " << curve->Degree()
+         << ";" << std::endl;
+  stream << std::endl;
+
+  stream << "const Handle(Geom_BSplineCurve) " << variableName << " = "
+         << "new Geom_BSplineCurve(poles" << postFix << ", knots" << postFix
+         << ", multiplicities" << postFix << ", degree" << postFix << ");"
+         << std::endl;
+  stream << std::endl;
+
+  return stream.str();
+}
+
+static std::string getGenerationCode (const Handle(Geom2d_BSplineCurve)& curve, const std::string& variableName)
+{
+  const std::string postFix = "_" + variableName;
+
+  std::stringstream stream;
+  stream << std::setprecision(32);
+
+  stream << "TColgp_Array1OfPnt2d poles" << postFix << " (1, "
+         << curve->Poles().Size() << ");" << std::endl;
+  stream << std::endl;
+
+  for (int poleIndex = 1; poleIndex <= curve->Poles().Size(); ++poleIndex)
+  {
+    stream << "poles" << postFix << ".SetValue(" << poleIndex << ", gp_Pnt2d("
+           << curve->Poles().Value(poleIndex).X() << ", "
+           << curve->Poles().Value(poleIndex).Y() << "));" << std::endl;
+  }
+
+  stream << std::endl;
+
+  stream << "TColStd_Array1OfReal knots" << postFix << " (1, "
+         << curve->Knots().Size() << ");" << std::endl;
+  stream << std::endl;
+
+  for (int knotIndex = 1; knotIndex <= curve->Knots().Size(); ++knotIndex)
+  {
+    stream << "knots" << postFix << ".SetValue(" << knotIndex << ", "
+           << curve->Knots().Value(knotIndex) << ");" << std::endl;
+  }
+
+  stream << std::endl;
+
+  stream << "TColStd_Array1OfInteger multiplicities" << postFix << " (1, "
+         << curve->Multiplicities().Size() << ");" << std::endl;
+  stream << std::endl;
+
+  for (int multiplicityIndex = 1; multiplicityIndex <= curve->Knots().Size();
+    ++multiplicityIndex)
+  {
+    stream << "multiplicities" << postFix << ".SetValue(" << multiplicityIndex
+           << ", " << curve->Multiplicities().Value(multiplicityIndex) << ");"
+           << std::endl;
+  }
+
+  stream << std::endl;
+
+  stream << "Standard_Integer degree" << postFix << " = " << curve->Degree()
+         << ";" << std::endl;
+  stream << std::endl;
+
+  stream << "const Handle(Geom2d_BSplineCurve) " << variableName << " = "
+         << "new Geom2d_BSplineCurve(poles" << postFix << ", knots" << postFix
+         << ", multiplicities" << postFix << ", degree" << postFix << ");"
+         << std::endl;
+  stream << std::endl;
+
+  return stream.str();
+}
+
+static Handle(Geom_BSplineCurve) createCurveOnSurface (const Handle(Geom2d_Curve)& curve, 
+                                                       const Handle(Geom_Surface)& surface)
+{
+  const Handle(Geom2dAdaptor_HCurve) curve2dAdaptor = new Geom2dAdaptor_HCurve (curve);
+  const Handle(GeomAdaptor_HSurface) surfaceAdaptor = new GeomAdaptor_HSurface (surface);
+  Adaptor3d_CurveOnSurface curveOnSurface(curve2dAdaptor, surfaceAdaptor);
+
+  Handle(Geom_BSplineCurve) result;
+  double maxDeviation;
+  double averageDeviation;
+
+  GeomLib::BuildCurve3d (Precision::Intersection(), curveOnSurface,
+                         curve->FirstParameter(), curve->LastParameter(), result,
+                         maxDeviation, averageDeviation);
+  return result;
+}
+
+static Standard_Integer OCC30990 (Draw_Interpretor& di, Standard_Integer argc, const char ** argv)
+{
+  if ( argc != 2 ) {
+    std::cerr << "Use : " << argv[0] << " face" << std::endl;
+    return 1;
+  }
+
+  TopoDS_Face face = TopoDS::Face(DBRep::Get(argv[1]));
+  if (face.IsNull())
+  {
+    di << "Error: DRAW variable " << argv[1] << " is not a face\n";
+    return 1;
+  }
+
+  TColgp_Array1OfPnt2d poles_Curve2d_8(1, 13);
+
+  poles_Curve2d_8.SetValue(1, gp_Pnt2d(42.295338664599988476311409613118, 8.4914306627985833841876228689216));
+  poles_Curve2d_8.SetValue(2, gp_Pnt2d(40.579933409132380006667517591268, 9.060165666179891275078261969611));
+  poles_Curve2d_8.SetValue(3, gp_Pnt2d(36.996940869309682398125005420297, 10.171619311379696881658674101345));
+  poles_Curve2d_8.SetValue(4, gp_Pnt2d(32.864273253771536076328629860654, 11.380660085749378040986812266055));
+  poles_Curve2d_8.SetValue(5, gp_Pnt2d(28.584295662646276525720168137923, 12.662888222064006882305875478778));
+  poles_Curve2d_8.SetValue(6, gp_Pnt2d(25.157946870720202525717468233779, 13.731919276511142413710331311449));
+  poles_Curve2d_8.SetValue(7, gp_Pnt2d(21.33459821509567078123836836312, 15.064231230188747545639671443496));
+  poles_Curve2d_8.SetValue(8, gp_Pnt2d(17.827691433660259434645922738127, 16.401397920686864750905442633666));
+  poles_Curve2d_8.SetValue(9, gp_Pnt2d(13.849273089800712810415461717639, 18.127229818602270228211637004279));
+  poles_Curve2d_8.SetValue(10, gp_Pnt2d(9.1246083432558862114092335104942, 20.568986064860464324510758160613));
+  poles_Curve2d_8.SetValue(11, gp_Pnt2d(4.4697968819325559763910860056058, 23.492000328870194181263286736794));
+  poles_Curve2d_8.SetValue(12, gp_Pnt2d(1.4141531250431809141332450963091, 25.82753110181492317565243865829));
+  poles_Curve2d_8.SetValue(13, gp_Pnt2d(7.923297741446276435447332309181e-05, 27.017815649124006682768595055677));
+
+  TColStd_Array1OfReal knots_Curve2d_8(1, 11);
+
+  knots_Curve2d_8.SetValue(1, 0);
+  knots_Curve2d_8.SetValue(2, 5.420327986370280903827278962126);
+  knots_Curve2d_8.SetValue(3, 11.254598731544540157756273401901);
+  knots_Curve2d_8.SetValue(4, 12.91707596038647309910629701335);
+  knots_Curve2d_8.SetValue(5, 18.824269472051337714901819708757);
+  knots_Curve2d_8.SetValue(6, 22.020264142726574618791346438229);
+  knots_Curve2d_8.SetValue(7, 25.06202780203105362488713581115);
+  knots_Curve2d_8.SetValue(8, 30.082445035434087543535497388802);
+  knots_Curve2d_8.SetValue(9, 35.021137482720206435260479338467);
+  knots_Curve2d_8.SetValue(10, 41.004125525221553516530548222363);
+  knots_Curve2d_8.SetValue(11, 46.545757090716385562245704932138);
+
+  TColStd_Array1OfInteger multiplicities_Curve2d_8(1, 11);
+
+  multiplicities_Curve2d_8.SetValue(1, 4);
+  multiplicities_Curve2d_8.SetValue(2, 1);
+  multiplicities_Curve2d_8.SetValue(3, 1);
+  multiplicities_Curve2d_8.SetValue(4, 1);
+  multiplicities_Curve2d_8.SetValue(5, 1);
+  multiplicities_Curve2d_8.SetValue(6, 1);
+  multiplicities_Curve2d_8.SetValue(7, 1);
+  multiplicities_Curve2d_8.SetValue(8, 1);
+  multiplicities_Curve2d_8.SetValue(9, 1);
+  multiplicities_Curve2d_8.SetValue(10, 1);
+  multiplicities_Curve2d_8.SetValue(11, 4);
+
+  Standard_Integer degree_Curve2d_8 = 3;
+
+  const Handle(Geom2d_BSplineCurve) Curve2d_8 = 
+    new Geom2d_BSplineCurve(poles_Curve2d_8, knots_Curve2d_8, multiplicities_Curve2d_8, degree_Curve2d_8);
+
+  const Handle(Geom_BSplineSurface) surface = Handle(Geom_BSplineSurface)::DownCast (BRep_Tool::Surface(face));
+  std::cout.precision(20);
+  std::cout << "U knots:" << std::endl;
+  for (int i = 1; i <= surface->NbUKnots(); i++)
+  {
+    std::cout << surface->UKnot(i) << std::endl;
+  }
+
+  const Handle(Geom_BSplineCurve) curve = createCurveOnSurface(Curve2d_8, surface);
+  if (curve.IsNull())
+  {
+    di << "Error: Curve cannot be interpolated on surface\n";
+    return 0;
+  }
+
+  di << "\n" << getGenerationCode (Curve2d_8, "Curve2d").c_str() << "\n";
+  di << "\n" << getGenerationCode (curve, "Curve").c_str() << "\n";
+
+  return 0;
+}
+
+void QABugs::Commands_21 (Draw_Interpretor& theCommands)
+{
+  const char *group = "QABugs";
+
+  theCommands.Add("OCC30990", "OCC30990 face", __FILE__, OCC30990, group);
+}
diff --git a/tests/bugs/moddata_3/bug30990 b/tests/bugs/moddata_3/bug30990
new file mode 100644
index 0000000000..438ca3879c
--- /dev/null
+++ b/tests/bugs/moddata_3/bug30990
@@ -0,0 +1,10 @@
+puts "================"
+puts "0030990: Foundation Classes - unexpected change in numerical results on bsplines after 0029769"
+puts "================"
+puts ""
+
+pload QAcommands
+
+restore [locate_data_file bug30990.brep] a
+
+set result [OCC30990 a]
-- 
2.22.0.windows.1

