]> SALOME platform Git repositories - modules/shaper.git/commitdiff
Salome HOME
Task #3230: Sketcher: create a curve passing through selected points or vertices...
authorArtem Zhidkov <Artem.Zhidkov@opencascade.com>
Fri, 22 May 2020 16:55:22 +0000 (19:55 +0300)
committerArtem Zhidkov <Artem.Zhidkov@opencascade.com>
Wed, 24 Jun 2020 08:57:41 +0000 (11:57 +0300)
* Implement the Curve Fitting feature.
* Implement the de Boor scheme for periodic and non-periodic B-spline evaluation.
* Implement interpolation and approximation modes of the feature.
* Add creation of the control polygon.
* Reordering points on the corresponding button click
* Python API for this feature
* User documentation

31 files changed:
src/GeomAlgoAPI/GeomAlgoAPI_CurveBuilder.cpp
src/GeomAlgoAPI/GeomAlgoAPI_CurveBuilder.h
src/ModelAPI/ModelAPI_Feature.h
src/ModuleBase/ModuleBase_WidgetOptionalBox.cpp
src/ModuleBase/ModuleBase_WidgetOptionalBox.h
src/SketchAPI/SketchAPI.i
src/SketchAPI/SketchAPI_Sketch.cpp
src/SketchAPI/SketchAPI_Sketch.h
src/SketchPlugin/CMakeLists.txt
src/SketchPlugin/SketchPlugin_CurveFitting.cpp [new file with mode: 0644]
src/SketchPlugin/SketchPlugin_CurveFitting.h [new file with mode: 0644]
src/SketchPlugin/SketchPlugin_MacroBSpline.cpp
src/SketchPlugin/SketchPlugin_MacroBSpline.h
src/SketchPlugin/SketchPlugin_Plugin.cpp
src/SketchPlugin/SketchPlugin_Validators.cpp
src/SketchPlugin/SketchPlugin_Validators.h
src/SketchPlugin/SketchPlugin_msg_en.ts
src/SketchPlugin/SketchPlugin_msg_fr.ts
src/SketchPlugin/doc/TUI_curvefitting.rst [new file with mode: 0644]
src/SketchPlugin/doc/curveFittingFeature.rst [new file with mode: 0644]
src/SketchPlugin/doc/examples/approximation.py [new file with mode: 0644]
src/SketchPlugin/doc/examples/interpolation.py [new file with mode: 0644]
src/SketchPlugin/doc/images/curvefitting.png [new file with mode: 0644]
src/SketchPlugin/doc/images/curvefitting_approximation_res.png [new file with mode: 0644]
src/SketchPlugin/doc/images/curvefitting_interpolation_res.png [new file with mode: 0644]
src/SketchPlugin/doc/images/curvefitting_panel_approximation.png [new file with mode: 0644]
src/SketchPlugin/doc/images/curvefitting_panel_interpolation.png [new file with mode: 0644]
src/SketchPlugin/icons/curvefitting.png [new file with mode: 0644]
src/SketchPlugin/plugin-Sketch.xml
src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_GeoExtensions.cpp
src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_GeoExtensions.h

index ffec79e30a7aeed5a2549ded6b41cb449e35daa4..2ec8797c6662dc0f30f3a9ab4e1d4fa817f0f316 100644 (file)
 #include <GeomAPI_Vertex.h>
 #include <GeomAPI_ShapeExplorer.h>
 
+#include <Approx_ParametrizationType.hxx>
 #include <BRepBuilderAPI_MakeEdge.hxx>
-#include <TopoDS.hxx>
-#include <TopoDS_Edge.hxx>
-#include <TopExp_Explorer.hxx>
-#include <TColgp_HArray1OfPnt.hxx>
+#include <BSplSLib.hxx>
+#include <Geom_BSplineCurve.hxx>
+#include <Geom_BSplineSurface.hxx>
 #include <GeomAPI_Interpolate.hxx>
+#include <GeomAPI_PointsToBSplineSurface.hxx>
 #include <gp_Pnt.hxx>
 #include <Precision.hxx>
+#include <TColgp_Array2OfPnt.hxx>
+#include <TColgp_HArray1OfPnt.hxx>
+#include <TopExp_Explorer.hxx>
+#include <TopoDS.hxx>
+#include <TopoDS_Edge.hxx>
 
 
-static void reorder(Handle(TColgp_HArray1OfPnt)& thePoints);
-
-//=================================================================================================
 GeomEdgePtr GeomAlgoAPI_CurveBuilder::edge(const std::list<GeomPointPtr>& thePoints,
-  const bool theIsClosed,
-  const bool theIsToReorder,
-  const GeomDirPtr& theStartTangent,
-  const GeomDirPtr& theEndTangent)
+                                           const bool thePeriodic,
+                                           const bool theIsToReorder,
+                                           const GeomDirPtr& theStartTangent,
+                                           const GeomDirPtr& theEndTangent)
 {
-  // Prepare points array
-  Handle(TColgp_HArray1OfPnt) aPoints = new TColgp_HArray1OfPnt(1, (int)thePoints.size());
-  std::list<GeomPointPtr>::const_iterator anIt = thePoints.begin();
-  for (int i = 1; anIt != thePoints.end(); anIt++, i++) {
-    GeomPointPtr aPoint = *anIt;
-    aPoints->SetValue(i, aPoint->impl<gp_Pnt>());
-  }
+  std::list<GeomPointPtr> aPointsCopy = thePoints;
 
   // If the curve to be closed - remove last point if it is too close to the first one
-  bool isClose = aPoints->First().Distance(aPoints->Last()) <= gp::Resolution();
-  if (isClose && theIsClosed) {
-    aPoints->Resize(aPoints->Lower(), aPoints->Upper() - 1, Standard_True);
+  bool isClose = aPointsCopy.front()->distance(aPointsCopy.back()) <= gp::Resolution();
+  if (isClose && thePeriodic) {
+    aPointsCopy.pop_back();
   }
 
   // Reorder points if required
   if (theIsToReorder) {
-    reorder(aPoints);
+    reorderPoints(aPointsCopy);
+  }
+
+  // Prepare points array
+  Handle(TColgp_HArray1OfPnt) aPoints = new TColgp_HArray1OfPnt(1, (int)aPointsCopy.size());
+  std::list<GeomPointPtr>::const_iterator anIt = aPointsCopy.begin();
+  for (int i = 1; anIt != aPointsCopy.end(); anIt++, i++) {
+    GeomPointPtr aPoint = *anIt;
+    aPoints->SetValue(i, aPoint->impl<gp_Pnt>());
   }
 
   // Initialize interpolator
-  GeomAPI_Interpolate anInterp(aPoints, theIsClosed, gp::Resolution());
+  GeomAPI_Interpolate anInterp(aPoints, thePeriodic, gp::Resolution());
 
   // Assign tangents if defined
   if (theStartTangent && theEndTangent) {
@@ -92,54 +97,114 @@ GeomEdgePtr GeomAlgoAPI_CurveBuilder::edge(const std::list<GeomPointPtr>& thePoi
   return aResultShape;
 }
 
-//================   Auxiliary functions   ========================================================
-void reorder(Handle(TColgp_HArray1OfPnt)& thePoints)
+GeomEdgePtr GeomAlgoAPI_CurveBuilder::approximate(const std::list<GeomPointPtr>& thePoints,
+                                                  const bool thePeriodic,
+                                                  const double thePrecision)
+{
+  // Prepare points array to be able to build a surface.
+  // This surface is based on two sets of points: the first is an original and
+  // the second is shifted along orthogonal direction.
+  // This is a workaround, because GeomAPI_PointsToBSpline algorithm cannot produce
+  // the periodic curve, but GeomAPI_PointsToBSplineSurface can.
+  TColgp_Array2OfPnt aPoints(1, (int)thePoints.size(), 1, 2);
+  gp_Pnt aPlaneBase[3]; // base points to calculate the normal direction
+  int aNbPlanePoints = 0;
+  gp_Dir aNormal;
+  std::list<GeomPointPtr>::const_iterator anIt = thePoints.begin();
+  for (int i = 1; anIt != thePoints.end(); anIt++, i++) {
+    const gp_Pnt& aPoint = (*anIt)->impl<gp_Pnt>();
+    aPoints.SetValue(i, 1, aPoint);
+    aPoints.SetValue(i, 2, aPoint);
+    if (aNbPlanePoints < 3) {
+      if (aNbPlanePoints == 0 ||
+          aPoint.SquareDistance(aPlaneBase[0]) > Precision::SquareConfusion())
+        aPlaneBase[aNbPlanePoints++] = aPoint;
+      if (aNbPlanePoints == 3) {
+        gp_Vec aVec12(aPlaneBase[0], aPlaneBase[1]);
+        gp_Vec aVec13(aPlaneBase[0], aPlaneBase[2]);
+        if (aVec12.CrossSquareMagnitude(aVec13) > Precision::SquareConfusion())
+          aNormal = gp_Dir(aVec12 ^ aVec13);
+        else
+          --aNbPlanePoints;
+      }
+    }
+  }
+  if (aNbPlanePoints < 3)
+    aNormal = gp::DZ();
+  // shifted points
+  for (int i = aPoints.LowerRow(); i <= aPoints.UpperRow(); i++)
+    aPoints.ChangeValue(i, 2).ChangeCoord() += aNormal.XYZ();
+
+  // If the curve to be closed - remove last point if it is too close to the first one
+  bool isClose = aPoints.Value(aPoints.LowerRow(), 1).Distance(
+                 aPoints.Value(aPoints.UpperRow(), 1)) <= gp::Resolution();
+  if (isClose && thePeriodic) {
+    aPoints.Resize(aPoints.LowerRow(), aPoints.UpperRow() - 1,
+                   aPoints.LowerCol(), aPoints.UpperCol(), Standard_True);
+  }
+
+  // Initialize and perform approximator
+  static const Standard_Integer DEGREE_MIN = 3;
+  static const Standard_Integer DEGREE_MAX = 8;
+  GeomAPI_PointsToBSplineSurface anApprox;
+  anApprox.Init(aPoints, Approx_ChordLength, DEGREE_MIN, DEGREE_MAX,
+                GeomAbs_C2, thePrecision, thePeriodic);
+
+  // Set result in form of edge
+  TopoDS_Edge anEdge;
+  if (anApprox.IsDone()) {
+    // build a curve along U-direction of the surface
+    Handle(Geom_BSplineSurface) aSurface = anApprox.Surface();
+    Handle(Geom_Curve) aCurve = aSurface->VIso(aSurface->VKnots().First());
+
+    anEdge = BRepBuilderAPI_MakeEdge(aCurve).Edge();
+  }
+
+  GeomEdgePtr aResultShape(new GeomAPI_Edge);
+  aResultShape->setImpl(new TopoDS_Shape(anEdge));
+
+  return aResultShape;
+}
+
+void GeomAlgoAPI_CurveBuilder::reorderPoints(std::list<GeomPointPtr>& thePoints)
 {
-  if (thePoints->Length() < 3) {
+  if (thePoints.size() < 3) {
     return;
   }
 
-  int aNbPoints = thePoints->Length();
   int aNbDup = 0;
-  gp_Pnt aPrevPnt = thePoints->Value(1);
-  for (int i = 1; i < aNbPoints; i++) {
-    gp_Pnt aPnt = thePoints->Value(i);
-    int aNearest = 0;
+  std::list<GeomPointPtr>::iterator aPIt = thePoints.begin();
+  GeomPointPtr aPrevPnt = *aPIt;
+  for (; aPIt != thePoints.end(); ++aPIt) {
+    GeomPointPtr aPnt = *aPIt;
+    std::list<GeomPointPtr>::iterator aNextIt = aPIt;
+    std::list<GeomPointPtr>::iterator aNearestIt = ++aNextIt;
     double aMinDist = RealLast();
-    for (int j = i + 1; j <= aNbPoints; j++) {
-      double aDist = aPnt.SquareDistance(thePoints->Value(j));
+    while (aNextIt != thePoints.end()) {
+      double aDist = aPnt->distance(*aNextIt);
+      if (aDist < Precision::Confusion()) {
+        // remove duplicates
+        std::list<GeomPointPtr>::iterator aRemoveIt = aNextIt++;
+        thePoints.erase(aRemoveIt);
+        continue;
+      }
       if (aDist < aMinDist && (aMinDist - aDist) > Precision::Confusion()) {
-        aNearest = j;
+        aNearestIt = aNextIt;
         aMinDist = aDist;
       }
+      ++aNextIt;
     }
-    if (aNearest > 0 && aNearest != i + 1) {
+    aNextIt = aPIt; ++aNextIt;
+    if (aNearestIt != aNextIt) {
       // Keep given order of points to use it in case of equidistant candidates
       //               .-<---<-.
       //              /         \
       // o  o  o  c  o->o->o->o->n  o  o
       //          |  |           |
       //          i i+1       nearest
-      gp_Pnt aNearestPnt = thePoints->Value(aNearest);
-      for (int j = aNearest; j > i + 1; j--) {
-        thePoints->SetValue(j, thePoints->Value(j - 1));
-      }
-      thePoints->SetValue(i + 1, aNearestPnt);
-    }
-    if (aPrevPnt.Distance(thePoints->Value(i + 1)) <= Precision::Confusion())
-      aNbDup++;
-    else
-      aPrevPnt = thePoints->Value(i + 1);
-  }
-
-  if (aNbDup > 0) {
-    Handle(TColgp_HArray1OfPnt) aTmpPoints = new TColgp_HArray1OfPnt(1, aNbPoints - aNbDup);
-    for (int j = 1, i = 1; i <= aNbPoints; i++) {
-      if (i == 1 || aPrevPnt.Distance(thePoints->Value(i)) > Precision::Confusion()) {
-        aTmpPoints->SetValue(j++, thePoints->Value(i));
-        aPrevPnt = thePoints->Value(i);
-      }
+      GeomPointPtr aPointToMove = *aNearestIt;
+      thePoints.erase(aNearestIt);
+      thePoints.insert(aNextIt, aPointToMove);
     }
-    thePoints = aTmpPoints;
   }
 }
\ No newline at end of file
index e35073b8d890c379d379fccfb0a584b4922ab915..57eaf311b2f9c1e38e556578c5fad84e0314358b 100644 (file)
 
 /// \class GeomAlgoAPI_CurveBuilder
 /// \ingroup DataAlgo
-/// \brief Allows to create interpolation curve.
+/// \brief Allows to create a curve by the list of point.
 class GeomAlgoAPI_CurveBuilder
 {
  public:
    /// \brief Creates an interpolation curve from points.
    /// \param[in] thePoints list of points.
-   /// \param[in] theIsClosed defines whether the curve to be closed.
+   /// \param[in] thePeriodic defines whether the curve to be periodic.
    /// \param[in] theIsToReorder defines whether to change the order of points to construct
    ///            the shortest curve.
    /// \param[in] theStartTangent vector tangent to the start of curve.
    /// \param[in] theEndTangent vector tangent to the end of curve.
    /// \return Interpolation curve (edge). Empty in case of error or bad input.
    GEOMALGOAPI_EXPORT static GeomEdgePtr edge(const std::list<GeomPointPtr>& thePoints,
-                                              const bool theIsClosed,
+                                              const bool thePeriodic,
                                               const bool theIsToReorder,
                                               const GeomDirPtr& theStartTangent,
                                               const GeomDirPtr& theEndTangent);
+
+   /// \brief Approximate the given points by a curve.
+   /// \param[in] thePoints list of points.
+   /// \param[in] thePeriodic defines whether the curve to be periodic.
+   /// \param[in] thePrecision how close the curve should be to the points.
+   /// \return Apporimation curve (edge). Empty in case of error or bad input.
+   GEOMALGOAPI_EXPORT static GeomEdgePtr approximate(const std::list<GeomPointPtr>& thePoints,
+                                                     const bool thePeriodic,
+                                                     const double thePrecision);
+
+   /// \brief Reoder the list of points to get a polyline of minimal length
+   GEOMALGOAPI_EXPORT static void reorderPoints(std::list<GeomPointPtr>& thePoints);
 };
 
 #endif
index 8c231608656b13658eb1334ce373d715f557adbc..77cd55b96340ddce70104ea39c99fd144abcbd02 100644 (file)
@@ -198,6 +198,11 @@ class ModelAPI_Feature : public ModelAPI_Object
   {
     return data()->refattr(theID);
   }
+  /// Returns the refattrlist attribute by the identifier
+  inline std::shared_ptr<ModelAPI_AttributeRefAttrList> refattrlist(const std::string& theID)
+  {
+    return data()->refattrlist(theID);
+  }
   /// Returns the reference attribute by the identifier
   inline std::shared_ptr<ModelAPI_AttributeReference> reference(const std::string& theID)
   {
index edfd91b3f46b2e0ad84a72c8a7f66fcfcf60f933..fd2d845fb1b3a5690952a0ae7b4c08274ccb94c5 100644 (file)
@@ -54,6 +54,7 @@ ModuleBase_WidgetOptionalBox::ModuleBase_WidgetOptionalBox(QWidget* theParent,
 
   myHaveFrame = theData->getBooleanAttribute("has_frame", true);
   myEnableOnCheck = theData->getBooleanAttribute("enable_on_check", true);
+  myAlwaysShowTitle = theData->getBooleanAttribute("show_title", false);
 
   bool isChecked = theData->getBooleanAttribute(ATTR_DEFAULT, false);
   setDefaultValue(isChecked ? "true" : "false");
@@ -201,7 +202,10 @@ void ModuleBase_WidgetOptionalBox::createControl(const OptionType& theType)
 
     myCheckBoxLayout = new QHBoxLayout(myCheckBoxFrame);
     ModuleBase_Tools::adjustMargins(myCheckBoxLayout);
-    myCheckBox = new QCheckBox(myCheckBoxFrame);
+    if (myAlwaysShowTitle)
+      myCheckBox = new QCheckBox(translate(myGroupTitle), myCheckBoxFrame);
+    else
+      myCheckBox = new QCheckBox(myCheckBoxFrame);
     myCheckBox->setChecked(getDefaultValue() == "true");
     myCheckBoxLayout->addWidget(myCheckBox);
 
index d5ecaf266e6954ede276a18740abe0e8ebe4de93..94fce5da19bab9709f1419a36fd50947dbaabe43 100644 (file)
@@ -136,6 +136,7 @@ private:
 
   bool myHaveFrame;
   bool myEnableOnCheck;
+  bool myAlwaysShowTitle;
 };
 
 #endif /* ModuleBase_WidgetOptionalBox_H_ */
index 6d113b794dc297e1eb211a20b5d2f023127bbe7e..ba30a72da6938d6d677faf9fbecc8a08e9ea12ec 100644 (file)
@@ -51,6 +51,8 @@
 %feature("kwargs") SketchAPI_Ellipse::construction;
 %feature("kwargs") SketchAPI_EllipticArc::construction;
 %feature("kwargs") SketchAPI_Sketch::addSpline;
+%feature("kwargs") SketchAPI_Sketch::addInterpolation;
+%feature("kwargs") SketchAPI_Sketch::addApproximation;
 %feature("kwargs") SketchAPI_Sketch::setAngle;
 
 // shared pointers
index 6ce7550f005e1bad1fda95aec479f5972fcd4e47..c713dfd9708cac420ed2780f3ab3c19e70223de9 100644 (file)
@@ -36,6 +36,7 @@
 #include <SketchPlugin_ConstraintPerpendicular.h>
 #include <SketchPlugin_ConstraintRadius.h>
 #include <SketchPlugin_ConstraintRigid.h>
+#include <SketchPlugin_CurveFitting.h>
 #include <SketchPlugin_Trim.h>
 #include <SketchPlugin_Split.h>
 #include <SketchPlugin_ConstraintTangent.h>
@@ -793,6 +794,68 @@ std::shared_ptr<SketchAPI_BSpline> SketchAPI_Sketch::addSpline(
   return aBSpline;
 }
 
+//--------------------------------------------------------------------------------------
+static std::shared_ptr<SketchAPI_BSpline> buildInterpolation(
+    const CompositeFeaturePtr& theSketch,
+    const FeaturePtr& theCurveFittingFeature,
+    const std::list<ModelHighAPI_RefAttr>& points,
+    const bool periodic,
+    const bool closed)
+{
+  AttributeBooleanPtr aPeriodicAttr =
+      theCurveFittingFeature->boolean(SketchPlugin_CurveFitting::PERIODIC_ID());
+  fillAttribute(periodic, aPeriodicAttr);
+  AttributeBooleanPtr aClosedAttr =
+      theCurveFittingFeature->boolean(SketchPlugin_CurveFitting::CLOSED_ID());
+  fillAttribute(closed, aClosedAttr);
+  AttributeRefAttrListPtr aPointsAttr =
+      theCurveFittingFeature->refattrlist(SketchPlugin_CurveFitting::POINTS_ID());
+  fillAttribute(points, aPointsAttr);
+  apply(); // to execute and kill the macro-feature
+
+  // find created B-spline feature
+  BSplinePtr aBSpline;
+  const std::string& aKindToFind =
+      periodic ? SketchPlugin_BSplinePeriodic::ID() : SketchPlugin_BSpline::ID();
+  int aNbSubs = theSketch->numberOfSubs();
+  for (int anIndex = aNbSubs - 1; anIndex >= 0; --anIndex) {
+    FeaturePtr aFeature = theSketch->subFeature(anIndex);
+    if (aFeature->getKind() == aKindToFind) {
+      aBSpline.reset(periodic ? new SketchAPI_BSplinePeriodic(aFeature)
+        : new SketchAPI_BSpline(aFeature));
+      aBSpline->execute();
+      break;
+    }
+  }
+  return aBSpline;
+}
+
+std::shared_ptr<SketchAPI_BSpline> SketchAPI_Sketch::addInterpolation(
+    const std::list<ModelHighAPI_RefAttr>& points,
+    const bool periodic,
+    const bool closed)
+{
+  CompositeFeaturePtr aSketch = compositeFeature();
+  FeaturePtr anInterpFeature = aSketch->addFeature(SketchPlugin_CurveFitting::ID());
+  anInterpFeature->string(SketchPlugin_CurveFitting::TYPE_ID())
+      ->setValue(SketchPlugin_CurveFitting::TYPE_INTERPOLATION_ID());
+  return buildInterpolation(aSketch, anInterpFeature, points, periodic, closed);
+}
+
+std::shared_ptr<SketchAPI_BSpline> SketchAPI_Sketch::addApproximation(
+    const std::list<ModelHighAPI_RefAttr>& points,
+    const ModelHighAPI_Double& precision,
+    const bool periodic,
+    const bool closed)
+{
+  CompositeFeaturePtr aSketch = compositeFeature();
+  FeaturePtr anInterpFeature = aSketch->addFeature(SketchPlugin_CurveFitting::ID());
+  anInterpFeature->string(SketchPlugin_CurveFitting::TYPE_ID())
+      ->setValue(SketchPlugin_CurveFitting::TYPE_APPROXIMATION_ID());
+  fillAttribute(precision, anInterpFeature->real(SketchPlugin_CurveFitting::PRECISION_ID()));
+  return buildInterpolation(aSketch, anInterpFeature, points, periodic, closed);
+}
+
 //--------------------------------------------------------------------------------------
 std::shared_ptr<SketchAPI_Projection> SketchAPI_Sketch::addProjection(
     const ModelHighAPI_Selection & theExternalFeature,
index 11b6ed91fb41b9a19df53c7d6f276bcbcd5e174c..30674f4fb64069973af9da35bf32bbead85197db 100644 (file)
 #include <SketchPlugin_Sketch.h>
 #include <SketchPlugin_SketchEntity.h>
 
+#include <ModelHighAPI_Double.h>
 #include <ModelHighAPI_Interface.h>
 #include <ModelHighAPI_Macro.h>
 #include <ModelHighAPI_Selection.h>
 //--------------------------------------------------------------------------------------
 class ModelAPI_CompositeFeature;
 class ModelAPI_Object;
-class ModelHighAPI_Double;
 class ModelHighAPI_Integer;
 class ModelHighAPI_RefAttr;
 class ModelHighAPI_Reference;
@@ -338,6 +338,21 @@ public:
       const std::list<ModelHighAPI_Integer>& multiplicities = std::list<ModelHighAPI_Integer>(),
       const bool periodic = false);
 
+  /// Add interpolation feature
+  SKETCHAPI_EXPORT
+  std::shared_ptr<SketchAPI_BSpline> addInterpolation(
+      const std::list<ModelHighAPI_RefAttr>& points,
+      const bool periodic = false,
+      const bool closed = false);
+
+  /// Add approximation feature
+  SKETCHAPI_EXPORT
+  std::shared_ptr<SketchAPI_BSpline> addApproximation(
+      const std::list<ModelHighAPI_RefAttr>& points,
+      const ModelHighAPI_Double& precision = ModelHighAPI_Double(1.e-3),
+      const bool periodic = false,
+      const bool closed = false);
+
   /// Add projection
   SKETCHAPI_EXPORT
   std::shared_ptr<SketchAPI_Projection> addProjection(
index 91056a0ed7a4b8dc107febb95c9308fd5ca86f7d..e441e066feec026086bdae7389bf09ea0837b760 100644 (file)
@@ -49,6 +49,7 @@ SET(PROJECT_HEADERS
     SketchPlugin_ConstraintRigid.h
     SketchPlugin_ConstraintTangent.h
     SketchPlugin_ConstraintVertical.h
+    SketchPlugin_CurveFitting.h
     SketchPlugin_Ellipse.h
     SketchPlugin_EllipticArc.h
     SketchPlugin_ExternalValidator.h
@@ -104,6 +105,7 @@ SET(PROJECT_SOURCES
     SketchPlugin_ConstraintRigid.cpp
     SketchPlugin_ConstraintTangent.cpp
     SketchPlugin_ConstraintVertical.cpp
+    SketchPlugin_CurveFitting.cpp
     SketchPlugin_Ellipse.cpp
     SketchPlugin_EllipticArc.cpp
     SketchPlugin_ExternalValidator.cpp
diff --git a/src/SketchPlugin/SketchPlugin_CurveFitting.cpp b/src/SketchPlugin/SketchPlugin_CurveFitting.cpp
new file mode 100644 (file)
index 0000000..9c16a6e
--- /dev/null
@@ -0,0 +1,386 @@
+// Copyright (C) 2020  CEA/DEN, EDF R&D
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+
+#include <SketchPlugin_CurveFitting.h>
+
+#include <SketchPlugin_BSpline.h>
+#include <SketchPlugin_BSplinePeriodic.h>
+#include <SketchPlugin_ConstraintCoincidence.h>
+#include <SketchPlugin_MacroBSpline.h>
+#include <SketchPlugin_Point.h>
+#include <SketchPlugin_Tools.h>
+#include <SketchPlugin_Sketch.h>
+
+#include <ModelAPI_AttributeBoolean.h>
+#include <ModelAPI_AttributeDouble.h>
+#include <ModelAPI_AttributeDoubleArray.h>
+#include <ModelAPI_AttributeInteger.h>
+#include <ModelAPI_AttributeRefAttrList.h>
+#include <ModelAPI_AttributeString.h>
+#include <ModelAPI_Session.h>
+#include <ModelAPI_Validator.h>
+
+#include <Events_InfoMessage.h>
+
+#include <GeomDataAPI_Point2D.h>
+#include <GeomDataAPI_Point2DArray.h>
+
+#include <GeomAlgoAPI_CurveBuilder.h>
+#include <GeomAlgoAPI_EdgeBuilder.h>
+
+#include <GeomAPI_BSpline.h>
+
+static void convertTo3D(SketchPlugin_Sketch* theSketch,
+                        const AttributeRefAttrListPtr& theAttribute,
+                        bool theClosedButNotPeriodic,
+                        std::list<GeomPointPtr>& thePoints);
+
+static GeomEdgePtr buildInterpolationCurve(SketchPlugin_Sketch* theSketch,
+                                           AttributeRefAttrListPtr& thePoints,
+                                           bool thePeriodic,
+                                           bool theClosed);
+static GeomEdgePtr buildApproximationCurve(SketchPlugin_Sketch* theSketch,
+                                           AttributeRefAttrListPtr& thePoints,
+                                           double thePrecision,
+                                           bool thePeriodic,
+                                           bool theClosed);
+
+
+SketchPlugin_CurveFitting::SketchPlugin_CurveFitting()
+  : SketchPlugin_SketchEntity()
+{
+}
+
+void SketchPlugin_CurveFitting::initDerivedClassAttributes()
+{
+  data()->addAttribute(POINTS_ID(), ModelAPI_AttributeRefAttrList::typeId());
+
+  data()->addAttribute(TYPE_ID(), ModelAPI_AttributeString::typeId());
+
+  data()->addAttribute(PRECISION_ID(), ModelAPI_AttributeDouble::typeId());
+  ModelAPI_Session::get()->validators()->registerNotObligatory(getKind(), PRECISION_ID());
+
+  data()->addAttribute(NEED_CONTROL_POLYGON_ID(), ModelAPI_AttributeBoolean::typeId());
+  ModelAPI_Session::get()->validators()->registerNotObligatory(getKind(),
+                                                               NEED_CONTROL_POLYGON_ID());
+
+  data()->addAttribute(PERIODIC_ID(), ModelAPI_AttributeBoolean::typeId());
+  data()->addAttribute(CLOSED_ID(), ModelAPI_AttributeBoolean::typeId());
+}
+
+void SketchPlugin_CurveFitting::execute()
+{
+  FeaturePtr aBSpline = createBSplineFeature();
+  // create control polygon
+  AttributeBooleanPtr aNeedControlPoly = boolean(NEED_CONTROL_POLYGON_ID());
+  if (aNeedControlPoly && aNeedControlPoly->isInitialized() && aNeedControlPoly->value()) {
+    bool isPeriodic = boolean(PERIODIC_ID())->value();
+    std::list<FeaturePtr> aControlPoles;
+    SketchPlugin_MacroBSpline::createControlPolygon(aBSpline, isPeriodic, aControlPoles);
+  }
+  // constraints for the selected points
+  createConstraints(aBSpline);
+}
+
+FeaturePtr SketchPlugin_CurveFitting::createBSplineFeature()
+{
+  // create transient curve if not created yet
+  if (!myTransientResult) {
+    getAISObject(AISObjectPtr());
+    if (!myTransientResult)
+      return FeaturePtr();
+  }
+
+  SketchPlugin_Sketch* aSketch = sketch();
+  if (!aSketch)
+    return FeaturePtr();
+
+  bool isPeriodic = boolean(PERIODIC_ID())->value();
+
+  FeaturePtr aBSplineFeature = aSketch->addFeature(
+      isPeriodic ? SketchPlugin_BSplinePeriodic::ID() : SketchPlugin_BSpline::ID());
+
+  // Convert transient edge to B-spline curve.
+  GeomCurvePtr aCurve = std::make_shared<GeomAPI_Curve>(myTransientResult);
+  std::shared_ptr<GeomAPI_BSpline> aBSpline = std::make_shared<GeomAPI_BSpline>(aCurve);
+
+  // Fill attributes of B-spline feature:
+  bool aWasBlocked = aBSplineFeature->data()->blockSendAttributeUpdated(true, false);
+  // 1. Degree
+  aBSplineFeature->integer(SketchPlugin_BSplineBase::DEGREE_ID())->setValue(aBSpline->degree());
+  // 2. Poles
+  std::list<GeomPointPtr> aPoles = aBSpline->poles();
+  AttributePoint2DArrayPtr aPolesAttr = std::dynamic_pointer_cast<GeomDataAPI_Point2DArray>(
+      aBSplineFeature->attribute(SketchPlugin_BSplineBase::POLES_ID()));
+  aPolesAttr->setSize((int)aPoles.size());
+  int anIndex = 0;
+  for (auto it = aPoles.begin(); it != aPoles.end(); ++it, ++anIndex)
+    aPolesAttr->setPnt(anIndex, aSketch->to2D(*it));
+  // 3. Weights
+  std::list<double> aWeights = aBSpline->weights();
+  AttributeDoubleArrayPtr aWeightsAttr =
+      aBSplineFeature->data()->realArray(SketchPlugin_BSplineBase::WEIGHTS_ID());
+  if (aWeights.empty()) {
+    aWeightsAttr->setSize((int)aPoles.size());
+    for (anIndex = 0; anIndex < (int)aPoles.size(); ++anIndex)
+      aWeightsAttr->setValue(anIndex, 1.0);
+  }
+  else {
+    aWeightsAttr->setSize((int)aWeights.size());
+    anIndex = 0;
+    for (auto it = aWeights.begin(); it != aWeights.end(); ++it, ++anIndex)
+      aWeightsAttr->setValue(anIndex, *it);
+  }
+  // 4. Knots (normalized from 0 to 1)
+  std::list<double> aKnots = aBSpline->knots();
+  AttributeDoubleArrayPtr aKnotsAttr =
+      aBSplineFeature->data()->realArray(SketchPlugin_BSplineBase::KNOTS_ID());
+  aKnotsAttr->setSize((int)aKnots.size());
+  double aFirstKnot = aKnots.front();
+  double aLastKnot = aKnots.back();
+  anIndex = 0;
+  for (auto it = aKnots.begin(); it != aKnots.end(); ++it, ++anIndex)
+    aKnotsAttr->setValue(anIndex, (*it - aFirstKnot) / (aLastKnot - aFirstKnot));
+  // 5. Multiplicities
+  std::list<int> aMults = aBSpline->mults();
+  AttributeIntArrayPtr aMultsAttr =
+      aBSplineFeature->data()->intArray(SketchPlugin_BSplineBase::MULTS_ID());
+  aMultsAttr->setSize((int)aMults.size());
+  anIndex = 0;
+  for (auto it = aMults.begin(); it != aMults.end(); ++it, ++anIndex)
+    aMultsAttr->setValue(anIndex, *it);
+
+  if (!isPeriodic) {
+    AttributePoint2DPtr aStartPoint = std::dynamic_pointer_cast<GeomDataAPI_Point2D>(
+        aBSplineFeature->attribute(SketchPlugin_BSpline::START_ID()));
+    aStartPoint->setValue(aPolesAttr->pnt(0));
+
+    AttributePoint2DPtr aEndPoint = std::dynamic_pointer_cast<GeomDataAPI_Point2D>(
+      aBSplineFeature->attribute(SketchPlugin_BSpline::END_ID()));
+    aEndPoint->setValue(aPolesAttr->pnt(aPolesAttr->size() - 1));
+  }
+
+  aBSplineFeature->boolean(SketchPlugin_SketchEntity::AUXILIARY_ID())->setValue(
+      boolean(AUXILIARY_ID())->value());
+
+  aBSplineFeature->data()->blockSendAttributeUpdated(aWasBlocked);
+
+  aBSplineFeature->execute();
+
+  return aBSplineFeature;
+}
+
+void SketchPlugin_CurveFitting::createConstraints(FeaturePtr theProducedFeature)
+{
+  if (!theProducedFeature)
+    return;
+
+  SketchPlugin_Sketch* aSketch = sketch();
+  ResultPtr aResult = theProducedFeature->lastResult();
+  bool isPeriodic = boolean(PERIODIC_ID())->value();
+  bool isClosed = boolean(CLOSED_ID())->value();
+  bool isApproximation = string(TYPE_ID())->value() == TYPE_APPROXIMATION_ID();
+
+  AttributeRefAttrListPtr aPointsAttr = refattrlist(POINTS_ID());
+  std::list<std::pair<ObjectPtr, AttributePtr> > aPointsList = aPointsAttr->list();
+  std::list<std::pair<ObjectPtr, AttributePtr> >::iterator aLastIt = --aPointsList.end();
+  for (auto it = aPointsList.begin(); it != aPointsList.end(); ++it) {
+    AttributePtr anAttr = it->second;
+    if (!anAttr) {
+      // maybe the SketchPoint is selected
+      FeaturePtr aFeature = ModelAPI_Feature::feature(it->first);
+      if (aFeature && aFeature->getKind() == SketchPlugin_Point::ID())
+        anAttr = aFeature->attribute(SketchPlugin_Point::COORD_ID());
+      else
+        continue;
+    }
+
+    if (!isPeriodic && it == aPointsList.begin()) {
+      SketchPlugin_Tools::createConstraintAttrAttr(aSketch,
+          SketchPlugin_ConstraintCoincidence::ID(),
+          anAttr, theProducedFeature->attribute(SketchPlugin_BSpline::START_ID()));
+      if (isClosed) {
+        // end of B-spline curve should be coincident with the first selected point
+        SketchPlugin_Tools::createConstraintAttrAttr(aSketch,
+            SketchPlugin_ConstraintCoincidence::ID(),
+            anAttr, theProducedFeature->attribute(SketchPlugin_BSpline::END_ID()));
+      }
+    }
+    else if (!isPeriodic && !isClosed && it == aLastIt) {
+      SketchPlugin_Tools::createConstraintAttrAttr(aSketch,
+          SketchPlugin_ConstraintCoincidence::ID(),
+          anAttr, theProducedFeature->attribute(SketchPlugin_BSpline::END_ID()));
+    }
+    else if (!isApproximation) {
+      SketchPlugin_Tools::createConstraintAttrObject(aSketch,
+          SketchPlugin_ConstraintCoincidence::ID(),
+          anAttr, aResult);
+    }
+  }
+}
+
+bool SketchPlugin_CurveFitting::customAction(const std::string& theActionId)
+{
+  bool isOk = true;
+  if (theActionId == REORDER_POINTS_ACTION_ID()) {
+    reorderPoints();
+  } else {
+    static const std::string MESSAGE("Error: Feature \"%1\" does not support action \"%2\".");
+    Events_InfoMessage("SketchPlugin_CurveFitting", MESSAGE)
+        .arg(getKind()).arg(theActionId).send();
+    isOk = false;
+  }
+  return isOk;
+}
+
+
+void SketchPlugin_CurveFitting::reorderPoints()
+{
+  AttributeRefAttrListPtr aPointsAttr = refattrlist(POINTS_ID());
+  bool isPeriodic = boolean(PERIODIC_ID())->value();
+  bool isClosed = boolean(CLOSED_ID())->value();
+
+  std::list<GeomPointPtr> aCoordinates;
+  convertTo3D(sketch(), aPointsAttr, !isPeriodic && isClosed, aCoordinates);
+
+  // to keep mapping between points and attributes
+  std::map<GeomPointPtr, std::pair<ObjectPtr, AttributePtr> > aMap;
+  std::list<std::pair<ObjectPtr, AttributePtr> > aPointsList = aPointsAttr->list();
+  bool isPointAdded = aCoordinates.size() != aPointsList.size();
+  std::list<GeomPointPtr>::iterator aCoordIt = aCoordinates.begin();
+  std::list<std::pair<ObjectPtr, AttributePtr> >::iterator anAttrIt = aPointsList.begin();
+  for (; aCoordIt != aCoordinates.end() && anAttrIt != aPointsList.end(); ++aCoordIt, ++anAttrIt)
+    aMap[*aCoordIt] = *anAttrIt;
+
+  // reorder points
+  GeomAlgoAPI_CurveBuilder::reorderPoints(aCoordinates);
+
+  // re-compose the attribute
+  bool aWasBlocked = data()->blockSendAttributeUpdated(true);
+  aPointsAttr->clear();
+  for (aCoordIt = aCoordinates.begin(); aCoordIt != aCoordinates.end(); ++aCoordIt) {
+    const std::pair<ObjectPtr, AttributePtr>& aValue = aMap.at(*aCoordIt);
+    if (aValue.second)
+      aPointsAttr->append(aValue.second);
+    else
+      aPointsAttr->append(aValue.first);
+  }
+  data()->blockSendAttributeUpdated(aWasBlocked);
+}
+
+AISObjectPtr SketchPlugin_CurveFitting::getAISObject(AISObjectPtr thePrevious)
+{
+  SketchPlugin_Sketch* aSketch = sketch();
+  if (!aSketch)
+    return AISObjectPtr();
+
+  std::string aType = string(TYPE_ID())->value();
+  if (aType == TYPE_INTERPOLATION_ID()) {
+    myTransientResult = buildInterpolationCurve(aSketch,
+                                                refattrlist(POINTS_ID()),
+                                                boolean(PERIODIC_ID())->value(),
+                                                boolean(CLOSED_ID())->value());
+  }
+  else if (aType == TYPE_APPROXIMATION_ID()) {
+    myTransientResult = buildApproximationCurve(aSketch,
+                                                refattrlist(POINTS_ID()),
+                                                real(PRECISION_ID())->value(),
+                                                boolean(PERIODIC_ID())->value(),
+                                                boolean(CLOSED_ID())->value());
+  }
+  if (!myTransientResult)
+    return AISObjectPtr();
+
+  AISObjectPtr anAIS = thePrevious;
+  if (!anAIS)
+    anAIS.reset(new GeomAPI_AISObject());
+  anAIS->createShape(myTransientResult);
+
+  // Modify attributes
+  SketchPlugin_Tools::customizeFeaturePrs(anAIS, boolean(AUXILIARY_ID())->value());
+
+  return anAIS;
+}
+
+
+// ===============================     Auxiliary functions     ====================================
+
+void convertTo3D(SketchPlugin_Sketch* theSketch,
+                 const AttributeRefAttrListPtr& theAttribute,
+                 bool theClosedButNotPeriodic,
+                 std::list<GeomPointPtr>& thePoints)
+{
+  std::list<std::pair<ObjectPtr, AttributePtr> > aPointsList = theAttribute->list();
+  for (auto it = aPointsList.begin(); it != aPointsList.end(); ++it) {
+    AttributePtr anAttr = it->second;
+    if (!anAttr) {
+      // maybe the SketchPoint is selected
+      FeaturePtr aFeature = ModelAPI_Feature::feature(it->first);
+      if (aFeature && aFeature->getKind() == SketchPlugin_Point::ID())
+        anAttr = aFeature->attribute(SketchPlugin_Point::COORD_ID());
+      else {
+        thePoints.clear();
+        break;
+      }
+    }
+
+    AttributePoint2DPtr aPoint = std::dynamic_pointer_cast<GeomDataAPI_Point2D>(anAttr);
+    if (aPoint) {
+      GeomPointPtr aPnt3D = theSketch->to3D(aPoint->x(), aPoint->y());
+      thePoints.push_back(aPnt3D);
+    }
+  }
+
+  if (theClosedButNotPeriodic && !thePoints.empty() &&
+      thePoints.front()->distance(thePoints.back()) > 1.e-7)
+    thePoints.push_back(thePoints.front()); // close the curve
+}
+
+GeomEdgePtr buildInterpolationCurve(SketchPlugin_Sketch* theSketch,
+                                    AttributeRefAttrListPtr& thePoints,
+                                    bool thePeriodic,
+                                    bool theClosed)
+{
+  std::list<GeomPointPtr> aCoordinates;
+  convertTo3D(theSketch, thePoints, !thePeriodic && theClosed, aCoordinates);
+
+  GeomEdgePtr aResult;
+  if (aCoordinates.size() > 1) {
+    static const bool isReorder = false;
+    static GeomDirPtr aStartEndDir;
+    aResult = GeomAlgoAPI_CurveBuilder::edge(aCoordinates, thePeriodic,
+                                             isReorder, aStartEndDir, aStartEndDir);
+  }
+  return aResult;
+}
+
+GeomEdgePtr buildApproximationCurve(SketchPlugin_Sketch* theSketch,
+                                    AttributeRefAttrListPtr& thePoints,
+                                    double thePrecision,
+                                    bool thePeriodic,
+                                    bool theClosed)
+{
+  std::list<GeomPointPtr> aCoordinates;
+  convertTo3D(theSketch, thePoints, !thePeriodic && theClosed, aCoordinates);
+
+  GeomEdgePtr aResult;
+  if (aCoordinates.size() > 1)
+    aResult = GeomAlgoAPI_CurveBuilder::approximate(aCoordinates, thePeriodic, thePrecision);
+  return aResult;
+}
diff --git a/src/SketchPlugin/SketchPlugin_CurveFitting.h b/src/SketchPlugin/SketchPlugin_CurveFitting.h
new file mode 100644 (file)
index 0000000..d7c8b64
--- /dev/null
@@ -0,0 +1,154 @@
+// Copyright (C) 2020  CEA/DEN, EDF R&D
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+
+#ifndef SketchPlugin_CurveFitting_H_
+#define SketchPlugin_CurveFitting_H_
+
+#include <SketchPlugin.h>
+#include <SketchPlugin_SketchEntity.h>
+
+#include <GeomAPI_IPresentable.h>
+
+class GeomAPI_Edge;
+
+/**\class SketchPlugin_CurveFitting
+ * \ingroup Plugins
+ * \brief Feature for creation of the new B-spline curve in sketch
+ *        as an interpolation or an approximation of a list of points.
+ */
+class SketchPlugin_CurveFitting : public SketchPlugin_SketchEntity,
+                                  public GeomAPI_IPresentable
+{
+public:
+  /// Interpolation macro feature kind
+  inline static const std::string& ID()
+  {
+    static const std::string ID("SketchCurveFitting");
+    return ID;
+  }
+
+  /// list of selected points
+  inline static const std::string& POINTS_ID()
+  {
+    static const std::string ID("points");
+    return ID;
+  }
+
+  /// attribute for the periodic flag
+  inline static const std::string& PERIODIC_ID()
+  {
+    static const std::string ID("periodic");
+    return ID;
+  }
+
+  /// attribute for the closed flag
+  inline static const std::string& CLOSED_ID()
+  {
+    static const std::string ID("closed");
+    return ID;
+  }
+
+  /// attribute for the flag of creation a control polygon
+  inline static const std::string& NEED_CONTROL_POLYGON_ID()
+  {
+    static const std::string ID("need_control_poly");
+    return ID;
+  }
+
+  /// attribute for the type of the operation
+  inline static const std::string& TYPE_ID()
+  {
+    static const std::string ID("type");
+    return ID;
+  }
+
+  /// value for the type of operation
+  inline static const std::string& TYPE_INTERPOLATION_ID()
+  {
+    static const std::string ID("interpolation_type");
+    return ID;
+  }
+
+  /// value for the type of operation
+  inline static const std::string& TYPE_APPROXIMATION_ID()
+  {
+    static const std::string ID("approximation_type");
+    return ID;
+  }
+
+  /// attribute for the precision of the approximation
+  inline static const std::string& PRECISION_ID()
+  {
+    static const std::string ID("precision");
+    return ID;
+  }
+
+  /// attribute for the closed flag
+  inline static const std::string& REORDER_POINTS_ACTION_ID()
+  {
+    static const std::string ID("reorder_points");
+    return ID;
+  }
+
+  /// Returns the kind of a feature
+  SKETCHPLUGIN_EXPORT virtual const std::string& getKind()
+  {
+    static std::string MY_KIND = SketchPlugin_CurveFitting::ID();
+    return MY_KIND;
+  }
+
+  /// Returns the AIS preview
+  virtual AISObjectPtr getAISObject(AISObjectPtr thePrevious);
+
+  /// Creates a new part document if needed
+  SKETCHPLUGIN_EXPORT virtual void execute();
+
+  /// Reimplemented from ModelAPI_Feature::isMacro().
+  /// \returns true
+  SKETCHPLUGIN_EXPORT virtual bool isMacro() const {return true;};
+
+  SKETCHPLUGIN_EXPORT virtual bool isPreviewNeeded() const {return false;};
+
+  /// Performs some functionality by action id.
+  /// \param[in] theAttributeId action key id.
+  /// \return false in case if action not performed.
+  SKETCHPLUGIN_EXPORT virtual bool customAction(const std::string& theActionId);
+
+  /// Use plugin manager for features creation
+  SketchPlugin_CurveFitting();
+
+protected:
+  /// \brief Initializes attributes of derived class.
+  virtual void initDerivedClassAttributes();
+
+private:
+  /// \brief Create a feature, which passes through the selected points
+  FeaturePtr createBSplineFeature();
+
+  /// \brief Create coincidence constraints between selected points and the produced curve.
+  void createConstraints(FeaturePtr theProducedFeature);
+
+  /// \brief Reorder point to compose the polyline of the minimal length
+  void reorderPoints();
+
+private:
+  std::shared_ptr<GeomAPI_Edge> myTransientResult; ///< Interpolation curve
+};
+
+#endif
index 48158f1a826c86f48da608976153abbee07f3261..3d3ab4c53e7c92616bb209c0ab0b0085f1fe84c9 100644 (file)
@@ -84,7 +84,7 @@ void SketchPlugin_MacroBSpline::execute()
 
   if (boolean(CONTROL_POLYGON_ID())->value()) {
     std::list<FeaturePtr> aControlPoles;
-    createControlPolygon(aBSpline, aControlPoles);
+    createControlPolygon(aBSpline, myIsPeriodic, aControlPoles);
     constraintsForPoles(aControlPoles);
   }
 }
@@ -147,6 +147,7 @@ FeaturePtr SketchPlugin_MacroBSpline::createBSplineFeature()
 }
 
 void SketchPlugin_MacroBSpline::createControlPolygon(FeaturePtr theBSpline,
+                                                     bool thePeriodic,
                                                      std::list<FeaturePtr>& thePoles)
 {
   AttributePoint2DArrayPtr aPoles = std::dynamic_pointer_cast<GeomDataAPI_Point2DArray>(
@@ -158,7 +159,7 @@ void SketchPlugin_MacroBSpline::createControlPolygon(FeaturePtr theBSpline,
   // segments
   for (int index = 1; index < aSize; ++index)
     createAuxiliarySegment(aPoles, index - 1, index);
-  if (myIsPeriodic) {
+  if (thePeriodic) {
     // additional segment to close the control polygon
     createAuxiliarySegment(aPoles, aSize - 1, 0);
   }
index 9ecf63d49063d2a16b0853916792e35773ac2034..1c3aca6d7d7078e3b55eebf0dfc808af6851a9f6 100644 (file)
@@ -108,7 +108,12 @@ protected:
 private:
   FeaturePtr createBSplineFeature();
 
-  void createControlPolygon(FeaturePtr theBSpline, std::list<FeaturePtr>& thePoles);
+  /// Create control polygon for the B-spline and returns the list of its poles
+  static void createControlPolygon(FeaturePtr theBSpline,
+                                   bool thePeriodic,
+                                   std::list<FeaturePtr>& thePoles);
+
+  /// Create additional coincidences if other features were selected while creating the B-spline
   void constraintsForPoles(const std::list<FeaturePtr>& thePoles);
 
   /// Create Point feature coincident with the B-spline pole
@@ -124,6 +129,7 @@ private:
                                       const int thePoleIndex1,
                                       const int thePoleIndex2 = -1);
   friend class SketchPlugin_BSplineBase;
+  friend class SketchPlugin_CurveFitting;
 
 private:
   std::list<double> myKnots;
@@ -133,9 +139,9 @@ private:
 };
 
 
-/**\class SketchPlugin_MacroBSpline
+/**\class SketchPlugin_MacroBSplinePeriodic
 * \ingroup Plugins
-* \brief Feature for creation of the new B-spline in Sketch.
+* \brief Feature for creation of the new periodic B-spline in Sketch.
 */
 class SketchPlugin_MacroBSplinePeriodic : public SketchPlugin_MacroBSpline
 {
index 3be8ffc56bdfc42eeaa8ed77142cfabdbed4923b..8ef46386dc3194e9846ce359da878efcbc473598 100644 (file)
@@ -62,6 +62,7 @@
 #include <SketchPlugin_MacroEllipticArc.h>
 #include <SketchPlugin_SketchDrawer.h>
 #include <SketchPlugin_SketchCopy.h>
+#include <SketchPlugin_CurveFitting.h>
 
 #include <SketcherPrs_Tools.h>
 
@@ -154,6 +155,8 @@ SketchPlugin_Plugin::SketchPlugin_Plugin()
                               new SketchPlugin_MultiRotationAngleValidator);
   aFactory->registerValidator("SketchPlugin_BSplineValidator",
                               new SketchPlugin_BSplineValidator);
+  aFactory->registerValidator("SketchPlugin_CurveFittingValidator",
+                              new SketchPlugin_CurveFittingValidator);
 
   // register this plugin
   ModelAPI_Session::get()->registerPlugin(this);
@@ -276,6 +279,8 @@ FeaturePtr SketchPlugin_Plugin::createFeature(std::string theFeatureID)
     return FeaturePtr(new SketchPlugin_EllipticArc);
   } else if (theFeatureID == SketchPlugin_MacroEllipticArc::ID()) {
     return FeaturePtr(new SketchPlugin_MacroEllipticArc);
+  } else if (theFeatureID == SketchPlugin_CurveFitting::ID()) {
+    return FeaturePtr(new SketchPlugin_CurveFitting);
   } else if (theFeatureID == SketchPlugin_SketchDrawer::ID()) {
     return FeaturePtr(new SketchPlugin_SketchDrawer);
   } else if (theFeatureID == SketchPlugin_SketchCopy::ID()) {
@@ -359,6 +364,7 @@ std::shared_ptr<ModelAPI_FeatureStateMessage> SketchPlugin_Plugin
       aMsg->setState(SketchPlugin_MacroEllipticArc::ID(), aHasSketchPlane);
       aMsg->setState(SketchPlugin_ConstraintDistanceHorizontal::ID(), aHasSketchPlane);
       aMsg->setState(SketchPlugin_ConstraintDistanceVertical::ID(), aHasSketchPlane);
+      aMsg->setState(SketchPlugin_CurveFitting::ID(), aHasSketchPlane);
       aMsg->setState(SketchPlugin_Offset::ID(), aHasSketchPlane);
       // SketchRectangle is a python feature, so its ID is passed just as a string
       aMsg->setState("SketchRectangle", aHasSketchPlane);
index f9107bc4f9f2f7c55c4d499195bc565abd5b2847..320315bd2e6064d3469bd4e407d9e2ddca3ac94d 100644 (file)
@@ -31,6 +31,7 @@
 #include "SketchPlugin_Ellipse.h"
 #include "SketchPlugin_EllipticArc.h"
 #include "SketchPlugin_Fillet.h"
+#include "SketchPlugin_CurveFitting.h"
 #include "SketchPlugin_Line.h"
 #include "SketchPlugin_MacroArc.h"
 #include "SketchPlugin_MacroCircle.h"
@@ -49,6 +50,7 @@
 #include <ModelAPI_AttributeDouble.h>
 #include <ModelAPI_AttributeInteger.h>
 #include <ModelAPI_AttributeRefAttr.h>
+#include <ModelAPI_AttributeRefAttrList.h>
 #include <ModelAPI_AttributeRefList.h>
 #include <ModelAPI_AttributeSelectionList.h>
 #include <ModelAPI_AttributeString.h>
@@ -1894,3 +1896,22 @@ bool SketchPlugin_BSplineValidator::isValid(const AttributePtr& theAttribute,
 
   return true;
 }
+
+bool SketchPlugin_CurveFittingValidator::isValid(const FeaturePtr& theFeature,
+                                                 const std::list<std::string>& theArguments,
+                                                 Events_InfoMessage& theError) const
+{
+  AttributeRefAttrListPtr aRefAttrList =
+      theFeature->refattrlist(SketchPlugin_CurveFitting::POINTS_ID());
+  AttributeBooleanPtr aPeriodicAttr =
+      theFeature->boolean(SketchPlugin_CurveFitting::PERIODIC_ID());
+
+  // check number of selected entities
+  int aMinNbPoints = aPeriodicAttr->value() ? 3 : 2;
+  if (aRefAttrList->size() < aMinNbPoints) {
+    theError = "Not enough points selected. Need at least %1 points.";
+    theError.arg(aMinNbPoints);
+    return false;
+  }
+  return true;
+}
index 1b20c45fdcdb45af3e4028af684cb9cd0c6e1c7f..3ef50dd3386def3ac894d2abd58995dc0df0b26d 100644 (file)
@@ -546,4 +546,20 @@ class SketchPlugin_BSplineValidator : public ModelAPI_AttributeValidator
                        Events_InfoMessage& theError) const;
 };
 
+/**\class SketchPlugin_CurveFittingValidator
+ * \ingroup Validators
+ * \brief Validator for the selected vertices for the curve fitting feature.
+ */
+class SketchPlugin_CurveFittingValidator : public ModelAPI_FeatureValidator
+{
+public:
+  //! returns true if number of selected points is greater than the minimal value
+  //! \param theAttribute the checked attribute
+  //! \param theArguments arguments of the attribute
+  //! \param theError error message
+  virtual bool isValid(const std::shared_ptr<ModelAPI_Feature>& theFeature,
+                       const std::list<std::string>& theArguments,
+                       Events_InfoMessage& theError) const;
+};
+
 #endif
index 5dd2eaa01fc945eaaaeb20aa1e60cb458fcaf672..aad453f54df1fe134f6148c492643ab31a6c634d 100644 (file)
@@ -1,6 +1,15 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!DOCTYPE TS>
 <TS version="2.0" language="en_US">
+
+  <context>
+    <name>workshop</name>
+    <message>
+      <source>Curve fitting</source>
+      <translation>Curve fitting</translation>
+    </message>
+  </context>
+
   <context>
     <name>Sketch:Model_FeatureValidator</name>
     <message>
       <translation></translation>
     </message>
   </context>
+
+  <!-- SketchCurveFitting -->
+  <context>
+    <name>SketchCurveFitting</name>
+    <message>
+      <source>Curve fitting</source>
+      <translation>Curve fitting</translation>
+    </message>
+    <message>
+      <source>Create curve passing through the points</source>
+      <translation>Create curve passing through the points</translation>
+    </message>
+    <message>
+      <source>Error: Feature "%1" does not support action "%2".</source>
+      <translation>Error: Feature "%1" does not support action "%2".</translation>
+    </message>
+  </context>
+  <context>
+    <name>SketchCurveFitting:SketchPlugin_CurveFittingValidator</name>
+    <message>
+      <source>Not enough points selected. Need at least %1 points.</source>
+      <translation>Not enough points selected. Need at least %1 points.</translation>
+    </message>
+  </context>
+  <context>
+    <name>SketchCurveFitting:points</name>
+    <message>
+      <source>Points</source>
+      <translation>Points</translation>
+    </message>
+    <message>
+      <source>Select points for curve fitting</source>
+      <translation>Select points for curve fitting</translation>
+    </message>
+    <message>
+      <source>Attribute "%1" is not initialized.</source>
+      <translation>Select points for interpolation</translation>
+    </message>
+  </context>
+  <context>
+    <name>SketchCurveFitting:periodic</name>
+    <message>
+      <source>Periodic</source>
+      <translation>Periodic</translation>
+    </message>
+    <message>
+      <source>Make curve periodic</source>
+      <translation>Make curve periodic</translation>
+    </message>
+  </context>
+  <context>
+    <name>SketchCurveFitting:closed</name>
+    <message>
+      <source>Closed</source>
+      <translation>Closed</translation>
+    </message>
+    <message>
+      <source>Make curve closed, but not periodic</source>
+      <translation>Make curve closed, but not periodic</translation>
+    </message>
+  </context>
+  <context>
+    <name>SketchCurveFitting:type</name>
+    <message>
+      <source>Interpolation</source>
+      <translation>Interpolation</translation>
+    </message>
+    <message>
+      <source>Approximation</source>
+      <translation>Approximation</translation>
+    </message>
+  </context>
+  <context>
+    <name>SketchCurveFitting:precision</name>
+    <message>
+      <source>Precision</source>
+      <translation>Precision</translation>
+    </message>
+    <message>
+      <source>Maximal distance from selected points to the curve</source>
+      <translation>Maximal distance from selected points to the curve</translation>
+    </message>
+  </context>
+  <context>
+    <name>SketchCurveFitting:need_control_poly</name>
+    <message>
+      <source>Create control polygon</source>
+      <translation>Create control polygon</translation>
+    </message>
+    <message>
+      <source>Specify if the control polygon should be created</source>
+      <translation>Specify if the control polygon should be created</translation>
+    </message>
+  </context>
+  <context>
+    <name>SketchCurveFitting:reorder_points</name>
+    <message>
+      <source>Reorder points</source>
+      <translation>Reorder points</translation>
+    </message>
+    <message>
+      <source>Sort selected points to minimize the distance heighbors</source>
+      <translation>Sort selected points to minimize the distance heighbors</translation>
+    </message>
+  </context>
+  <context>
+    <name>SketchCurveFitting:Auxiliary</name>
+    <message>
+      <source>Auxiliary</source>
+      <translation>Auxiliary</translation>
+    </message>
+    <message>
+      <source>Construction element</source>
+      <translation>Construction element</translation>
+    </message>
+  </context>
+
 </TS>
index 86f682282b786fc9a6225b2091549c988ea142b3..d509191c4cf755c625a7167ee5396ce11c702a9c 100644 (file)
       <source>Horizontal Distance</source>
       <translation>Distance horizontale</translation>
     </message>
+    <message>
+      <source>Curve fitting</source>
+      <translation>Courbe d&apos;ajustement</translation>
+    </message>
     <message>
       <source>Length</source>
       <translation>Longueur</translation>
     </message>
   </context>
 
+  <!-- SketchCurveFitting-->
+  <context>
+    <name>SketchCurveFitting</name>
+    <message>
+      <source>Curve fitting</source>
+      <translation>Courbe d&apos;ajustement</translation>
+    </message>
+    <message>
+      <source>Create curve passing through the points</source>
+      <translation>Créer une courbe passant par les points</translation>
+    </message>
+    <message>
+      <source>Error: Feature "%1" does not support action "%2".</source>
+      <translation>Erreur: la fonctionnalité "% 1" ne prend pas en charge l'action "% 2".</translation>
+    </message>
+  </context>
+  <context>
+    <name>SketchCurveFitting:SketchPlugin_CurveFittingValidator</name>
+    <message>
+      <source>Not enough points selected. Need at least %1 points.</source>
+      <translation>Pas assez de points sélectionnés. Besoin d&apos;au moins %1 points.</translation>
+    </message>
+  </context>
+  <context>
+    <name>SketchCurveFitting:points</name>
+    <message>
+      <source>Points</source>
+      <translation>Points</translation>
+    </message>
+    <message>
+      <source>Select points for curve fitting</source>
+      <translation>Sélectionner des points pour l&apos;ajustement de courbe</translation>
+    </message>
+    <message>
+      <source>Attribute "%1" is not initialized.</source>
+      <translation>Sélectionner des points pour l'ajustement de courbe</translation>
+    </message>
+  </context>
+  <context>
+    <name>SketchCurveFitting:periodic</name>
+    <message>
+      <source>Periodic</source>
+      <translation>Périodique</translation>
+    </message>
+    <message>
+      <source>Make curve periodic</source>
+      <translation>Rendre la courbe périodique</translation>
+    </message>
+  </context>
+  <context>
+    <name>SketchCurveFitting:closed</name>
+    <message>
+      <source>Closed</source>
+      <translation>Fermé</translation>
+    </message>
+    <message>
+      <source>Make curve closed, but not periodic</source>
+      <translation>Rendre la courbe fermée, mais pas périodique</translation>
+    </message>
+  </context>
+  <context>
+    <name>SketchCurveFitting:type</name>
+    <message>
+      <source>Interpolation</source>
+      <translation>Interpolation</translation>
+    </message>
+    <message>
+      <source>Approximation</source>
+      <translation>Approximation</translation>
+    </message>
+  </context>
+  <context>
+    <name>SketchCurveFitting:precision</name>
+    <message>
+      <source>Precision</source>
+      <translation>Précision</translation>
+    </message>
+    <message>
+      <source>Maximal distance from selected points to the curve</source>
+      <translation>Distance maximale entre les points sélectionnés et la courbe</translation>
+    </message>
+  </context>
+  <context>
+    <name>SketchCurveFitting:need_control_poly</name>
+    <message>
+      <source>Create control polygon</source>
+      <translation>Créer un polygone de contrôle</translation>
+    </message>
+    <message>
+      <source>Specify if the control polygon should be created</source>
+      <translation>Précisez si le polygone de contrôle doit être créé</translation>
+    </message>
+  </context>
+  <context>
+    <name>SketchCurveFitting:reorder_points</name>
+    <message>
+      <source>Reorder points</source>
+      <translation>Réorganiser les points</translation>
+    </message>
+    <message>
+      <source>Sort selected points to minimize the distance heighbors</source>
+      <translation>Trier les points sélectionnés pour minimiser la distance des voisins</translation>
+    </message>
+  </context>
+  <context>
+    <name>SketchCurveFitting:Auxiliary</name>
+    <message>
+      <source>Auxiliary</source>
+      <translation>Auxiliaire</translation>
+    </message>
+    <message>
+      <source>Construction element</source>
+      <translation>Élément de construction</translation>
+    </message>
+  </context>
+
 </TS>
diff --git a/src/SketchPlugin/doc/TUI_curvefitting.rst b/src/SketchPlugin/doc/TUI_curvefitting.rst
new file mode 100644 (file)
index 0000000..1c71914
--- /dev/null
@@ -0,0 +1,23 @@
+
+  .. _tui_create_interpolation:
+
+Create interpolation curve
+==========================
+
+.. literalinclude:: examples/interpolation.py
+    :linenos:
+    :language: python
+
+:download:`Download this script <examples/interpolation.py>`
+
+
+  .. _tui_create_approximation:
+
+Create approximation curve
+==========================
+
+.. literalinclude:: examples/approximation.py
+    :linenos:
+    :language: python
+
+:download:`Download this script <examples/approximation.py>`
diff --git a/src/SketchPlugin/doc/curveFittingFeature.rst b/src/SketchPlugin/doc/curveFittingFeature.rst
new file mode 100644 (file)
index 0000000..1137611
--- /dev/null
@@ -0,0 +1,89 @@
+.. |curvefitting.icon|    image:: images/curvefitting.png
+
+Curve Fitting
+=============
+
+The Curve Fitting is a tool to create a curve by a set of given points.
+
+To start this operation:
+
+#. select *Sketch - > Curve fitting* item in the Main Menu or
+#. click |curvefitting.icon| **Curve fitting** button in the Sketch toolbar:
+
+There are 2 algorithms for the curve creation:
+
+- **Interpolation** - the curve passes exactly through the selected points;
+- **Approximation** - curve passes near the selected points to some extent.
+
+Both algorithms have additional options:
+
+- **Periodic** - forces the created curve to be smooth periodic curve;
+- **Closed** - produces closed, but non-periodic curve. As a result, it has the same start and end points, but it may be connected non-smoothly there;
+- **Create control polygon** - if the fitting curve is a B-spline curve, this option will create it's control polygon.
+
+
+Interpolation
+"""""""""""""
+
+.. image:: images/curvefitting_panel_interpolation.png
+   :align: center
+
+Select the list of points to create a curve. The curve will pass directly through these points in the given order.
+
+Clicking the **Reorder points** button will change the order of selected points to minimize the distances between the neighbors.
+
+**TUI Command**:
+
+.. py:function:: Sketch_1.addInterpolation(points, periodic = False, closed = False)
+
+    :param list: points for the curve.
+    :param boolean: flag to make the curve periodic.
+    :param boolean: flag to make the curve closed but not periodic.
+    :return: Created B-spline curve.
+
+Result
+""""""
+
+The created B-spline curve appears in the view.
+
+.. image:: images/curvefitting_interpolation_res.png
+          :align: center
+
+.. centered::
+   Interpolation curve (non-closed, periodic and closed)
+
+**See Also** a sample TUI Script of :ref:`tui_create_interpolation` operation.
+
+
+Approximation
+"""""""""""""
+
+.. image:: images/curvefitting_panel_approximation.png
+   :align: center
+
+Select the list of points to create a curve and set the **Precision** value. The curve will pass not far than the precision of these points.
+
+Clicking the **Reorder points** button will change the order of selected points to minimize the distances between the neighbors.
+
+**TUI Command**:
+
+.. py:function:: Sketch_1.addApproximation(points, precision = 0.001, periodic = False, closed = False)
+
+    :param list: points for the curve.
+    :param double: how close the curve should pass according to the points.
+    :param boolean: flag to make the curve periodic.
+    :param boolean: flag to make the curve closed but not periodic.
+    :return: Created B-spline curve.
+
+Result
+""""""
+
+The created B-spline curve appears in the view.
+
+.. image:: images/curvefitting_approximation_res.png
+          :align: center
+
+.. centered::
+   Approximated curve (non-closed, periodic and closed)
+
+**See Also** a sample TUI Script of :ref:`tui_create_approximation` operation.
diff --git a/src/SketchPlugin/doc/examples/approximation.py b/src/SketchPlugin/doc/examples/approximation.py
new file mode 100644 (file)
index 0000000..f272558
--- /dev/null
@@ -0,0 +1,38 @@
+from salome.shaper import model
+
+model.begin()
+partSet = model.moduleDocument()
+
+### Create Part
+Part_1 = model.addPart(partSet)
+Part_1_doc = Part_1.document()
+
+### Create Sketch
+Sketch_1 = model.addSketch(Part_1_doc, model.defaultPlane("XOY"))
+
+### Create SketchPoints
+SketchPoint_1 = Sketch_1.addPoint(-70.28350515463917, 1.388316151202744)
+SketchPoint_2 = Sketch_1.addPoint(-26.89862542955327, 51.7147766323024)
+SketchPoint_3 = Sketch_1.addPoint(40.08762886597938, 32.27835051546391)
+SketchPoint_4 = Sketch_1.addPoint(66.46563573883162, -29.8487972508591)
+
+### Create approximating curve
+ApproximationPoints = [SketchPoint_1.coordinates(),
+                       SketchPoint_2.coordinates(),
+                       SketchPoint_3.coordinates(),
+                       SketchPoint_4.coordinates()]
+SketchBSpline_1 = Sketch_1.addApproximation(ApproximationPoints, precision = 30)
+Sketch_1.setCoincident(SketchPoint_1.coordinates(), SketchBSpline_1.startPoint())
+Sketch_1.setCoincident(SketchPoint_4.coordinates(), SketchBSpline_1.endPoint())
+
+### Create periodic approximating curve
+SketchBSpline_2 = Sketch_1.addApproximation(ApproximationPoints, precision = 30, periodic = True)
+
+### Create closed approximating curve
+SketchBSpline_3 = Sketch_1.addApproximation(ApproximationPoints, precision = 30, closed = True)
+Sketch_1.setCoincident(SketchPoint_1.coordinates(), SketchBSpline_3.startPoint())
+Sketch_1.setCoincident(SketchPoint_1.coordinates(), SketchBSpline_3.endPoint())
+
+model.do()
+
+model.end()
diff --git a/src/SketchPlugin/doc/examples/interpolation.py b/src/SketchPlugin/doc/examples/interpolation.py
new file mode 100644 (file)
index 0000000..fc32ac4
--- /dev/null
@@ -0,0 +1,47 @@
+from salome.shaper import model
+
+model.begin()
+partSet = model.moduleDocument()
+
+### Create Part
+Part_1 = model.addPart(partSet)
+Part_1_doc = Part_1.document()
+
+### Create Sketch
+Sketch_1 = model.addSketch(Part_1_doc, model.defaultPlane("XOY"))
+
+### Create SketchPoints
+SketchPoint_1 = Sketch_1.addPoint(-70.28350515463917, 1.388316151202744)
+SketchPoint_2 = Sketch_1.addPoint(-26.89862542955327, 51.7147766323024)
+SketchPoint_3 = Sketch_1.addPoint(40.08762886597938, 32.27835051546391)
+SketchPoint_4 = Sketch_1.addPoint(66.46563573883162, -29.8487972508591)
+
+### Create interpolation curve
+InterpolationPoints = [SketchPoint_1.coordinates(),
+                       SketchPoint_2.coordinates(),
+                       SketchPoint_3.coordinates(),
+                       SketchPoint_4.coordinates()]
+SketchBSpline_1 = Sketch_1.addInterpolation(InterpolationPoints)
+Sketch_1.setCoincident(SketchPoint_1.coordinates(), SketchBSpline_1.startPoint())
+Sketch_1.setCoincident(SketchPoint_2.coordinates(), SketchBSpline_1.result())
+Sketch_1.setCoincident(SketchPoint_3.coordinates(), SketchBSpline_1.result())
+Sketch_1.setCoincident(SketchPoint_4.coordinates(), SketchBSpline_1.endPoint())
+
+### Create periodic interpolation curve
+SketchBSpline_2 = Sketch_1.addInterpolation(InterpolationPoints, periodic = True)
+Sketch_1.setCoincident(SketchPoint_1.coordinates(), SketchBSpline_2.result())
+Sketch_1.setCoincident(SketchPoint_2.coordinates(), SketchBSpline_2.result())
+Sketch_1.setCoincident(SketchPoint_3.coordinates(), SketchBSpline_2.result())
+Sketch_1.setCoincident(SketchPoint_4.coordinates(), SketchBSpline_2.result())
+
+### Create closed interpolation curve
+SketchBSpline_3 = Sketch_1.addInterpolation(InterpolationPoints, closed = True)
+Sketch_1.setCoincident(SketchPoint_1.coordinates(), SketchBSpline_3.startPoint())
+Sketch_1.setCoincident(SketchPoint_2.coordinates(), SketchBSpline_3.result())
+Sketch_1.setCoincident(SketchPoint_3.coordinates(), SketchBSpline_3.result())
+Sketch_1.setCoincident(SketchPoint_4.coordinates(), SketchBSpline_3.result())
+Sketch_1.setCoincident(SketchPoint_1.coordinates(), SketchBSpline_3.endPoint())
+
+model.do()
+
+model.end()
diff --git a/src/SketchPlugin/doc/images/curvefitting.png b/src/SketchPlugin/doc/images/curvefitting.png
new file mode 100644 (file)
index 0000000..dea1f22
Binary files /dev/null and b/src/SketchPlugin/doc/images/curvefitting.png differ
diff --git a/src/SketchPlugin/doc/images/curvefitting_approximation_res.png b/src/SketchPlugin/doc/images/curvefitting_approximation_res.png
new file mode 100644 (file)
index 0000000..007db89
Binary files /dev/null and b/src/SketchPlugin/doc/images/curvefitting_approximation_res.png differ
diff --git a/src/SketchPlugin/doc/images/curvefitting_interpolation_res.png b/src/SketchPlugin/doc/images/curvefitting_interpolation_res.png
new file mode 100644 (file)
index 0000000..dcde3f0
Binary files /dev/null and b/src/SketchPlugin/doc/images/curvefitting_interpolation_res.png differ
diff --git a/src/SketchPlugin/doc/images/curvefitting_panel_approximation.png b/src/SketchPlugin/doc/images/curvefitting_panel_approximation.png
new file mode 100644 (file)
index 0000000..2593baf
Binary files /dev/null and b/src/SketchPlugin/doc/images/curvefitting_panel_approximation.png differ
diff --git a/src/SketchPlugin/doc/images/curvefitting_panel_interpolation.png b/src/SketchPlugin/doc/images/curvefitting_panel_interpolation.png
new file mode 100644 (file)
index 0000000..29cf328
Binary files /dev/null and b/src/SketchPlugin/doc/images/curvefitting_panel_interpolation.png differ
diff --git a/src/SketchPlugin/icons/curvefitting.png b/src/SketchPlugin/icons/curvefitting.png
new file mode 100644 (file)
index 0000000..dea1f22
Binary files /dev/null and b/src/SketchPlugin/icons/curvefitting.png differ
index 5bfeb303da55a22c536d05c8be04523b01c20494..ffc443fe681ff2de78fe6a8f3a7e4327b46048fb 100644 (file)
@@ -9,6 +9,7 @@
                 SketchBSpline SketchMacroBSpline SketchMacroBSplinePeriodic SketchBSplinePeriodic
                 SketchRectangle
                 SketchProjection
+                SketchCurveFitting
                 SketchConstraintLength SketchConstraintRadius SketchConstraintDistance SketchConstraintDistanceHorizontal SketchConstraintDistanceVertical
                 SketchConstraintParallel SketchConstraintPerpendicular
                 SketchConstraintRigid SketchConstraintHorizontal SketchConstraintVertical
                    obligatory="0"
                    change_visual_attributes="true"/>
       </feature>
+
+      <!-- Curve fitting -->
+      <feature id="SketchCurveFitting"
+               title="Curve fitting"
+               tooltip="Create curve passing through the points"
+               icon="icons/Sketch/curvefitting.png"
+               helpfile="curveFittingFeature.html">
+        <sketch_multi_selector id="points"
+                               label="Points"
+                               tooltip="Select points for curve fitting"
+                               shape_types="Vertices"
+                               use_external="true"
+                               greed="true">
+        </sketch_multi_selector>
+        <switch id="type">
+          <case id="interpolation_type" title="Interpolation"/>
+          <case id="approximation_type" title="Approximation">
+            <doublevalue_editor id="precision"
+                                label="Precision"
+                                tooltip="Maximal distance from selected points to the curve"
+                                default="1.e-3"
+                                min="1.e-7">
+              <validator id="GeomValidators_Positive" parameters="0"/>
+            </doublevalue_editor>
+          </case>
+        </switch>
+        <boolvalue id="need_control_poly"
+                   label="Create control polygon"
+                   default="true"
+                   tooltip="Specify if the control polygon should be created"/>
+        <optionalbox id="periodic"
+                     title="Periodic"
+                     tooltip="Make curve periodic"
+                     default="false"
+                     has_frame="false"
+                     enable_on_check="false"
+                     show_title="true">
+          <boolvalue id="closed"
+                     label="Closed"
+                     default="false"
+                     tooltip="Make curve closed, but not periodic"
+                     obligatory="1"/>
+        </optionalbox>
+        <boolvalue id="Auxiliary"
+                   label="Auxiliary"
+                   default="false"
+                   tooltip="Construction element"
+                   obligatory="0"
+                   change_visual_attributes="true"/>
+        <validator id="SketchPlugin_CurveFittingValidator"/>
+        <action id="reorder_points"
+                label="Reorder points"
+                tooltip="Sort selected points to minimize the distance heighbors"/>
+      </feature>
     </group>
 
     <group id="Segmentation">
index f69cd48b54ebe71c270fb2df7885fe7582747948..db330f5dd3d5038d9b6c88e881ddc226be955606 100644 (file)
 
 #include <GeomAPI_BSpline2d.h>
 #include <GeomAPI_Pnt2d.h>
-#include <GeomAPI_XY.h>
 
 #include <cmath>
 
 namespace GCS
 {
 
-DeriVector2 BSplineImpl::Value(double u, double du, double* derivparam)
-{
-  if (!isCacheValid())
-    rebuildCache();
-
-  std::shared_ptr<GeomAPI_Pnt2d> aValue;
-  std::shared_ptr<GeomAPI_XY> aDeriv;
-  myCurve->D1(u, aValue, aDeriv);
-
-  // calculate the derivative on solver's parameter
-  std::shared_ptr<GeomAPI_Pnt2d> aValueDeriv(new GeomAPI_Pnt2d(0.0, 0.0));
-  bool hasParam = false;
-  std::list<GeomPnt2dPtr> aPolesDeriv;
-  for (GCS::VEC_P::iterator anIt = poles.begin(); anIt != poles.end(); ++anIt) {
-    double x = 0.0, y = 0.0;
-    if (anIt->x == derivparam) {
-      x = 1.0;
-      hasParam = true;
-    }
-    else if (anIt->y == derivparam) {
-      y = 1.0;
-      hasParam = true;
+  static void periodicNormalization(double& theParam, double thePeriodStart, double thePeriodEnd)
+  {
+    double aPeriod = thePeriodEnd - thePeriodStart;
+    if (aPeriod > tolerance) {
+      theParam = std::max(thePeriodStart,
+                          theParam + aPeriod * std::ceil((thePeriodStart - theParam) / aPeriod));
     }
-         aPolesDeriv.push_back(GeomPnt2dPtr(new GeomAPI_Pnt2d(x, y)));
-  }
-  if (hasParam) {
-    // use non-periodic curve, because the most of B-spline coefficients are 0,
-    // thus, it is not necessary to keep original knots and multiplicities to get correct value
-    std::shared_ptr<GeomAPI_BSpline2d> aCurveDeriv(
-        new GeomAPI_BSpline2d(degree, aPolesDeriv, myCachedWeights));
-    aCurveDeriv->D0(u, aValueDeriv);
   }
 
-  return DeriVector2(aValue->x(), aValue->y(),
-                     aValueDeriv->x() + aDeriv->x() * du, aValueDeriv->y() + aDeriv->y() * du);
+
+DeriVector2 BSplineImpl::Value(double u, double du, double* derivparam)
+{
+  DeriVector2 value, deriv;
+  d1(u, derivparam, value, deriv);
+  return value.sum(GCS::DeriVector2(0., 0., deriv.x, deriv.y).mult(du));
 }
 
 DeriVector2 BSplineImpl::CalculateNormal(Point &p, double* derivparam)
 {
-  if (!isCacheValid())
-    rebuildCache();
-
   double u = 0.0;
-  if (!myCurve->parameter(GeomPnt2dPtr(new GeomAPI_Pnt2d(*p.x, *p.y)), 1e100, u))
+  if (!parameter(p, u))
     return DeriVector2();
 
-  std::shared_ptr<GeomAPI_Pnt2d> aValue;
-  std::shared_ptr<GeomAPI_XY> aDeriv;
-  myCurve->D1(u, aValue, aDeriv);
-
-  DeriVector2 norm(aDeriv->x(), aDeriv->y(), 0.0, 0.0);
-  return norm.rotate90ccw();
+  DeriVector2 value, deriv;
+  d1(u, derivparam, value, deriv);
+  return deriv.rotate90ccw();
 }
 
 BSplineImpl* BSplineImpl::Copy()
@@ -88,49 +61,120 @@ BSplineImpl* BSplineImpl::Copy()
   return new BSplineImpl(*this);
 }
 
+void BSplineImpl::d1(double theU,
+                     double* theDerivParam,
+                     GCS::DeriVector2& theValue,
+                     GCS::DeriVector2& theDerivative)
+{
+  int aSpan = spanIndex(theU);
+  std::vector<GCS::DeriVector2> aPoles;
+  std::vector<double> aWeights;
+  spanPolesAndWeights(aSpan, theDerivParam, aPoles, aWeights);
+  performDeBoor(theU, aSpan, aPoles, aWeights, theValue, theDerivative);
+}
 
-bool BSplineImpl::isCacheValid() const
+int BSplineImpl::spanIndex(double& u)
 {
-  // curve has to be initialized
-  bool isValid = myCurve.get() && !myCurve->isNull();
-
-  static const double THE_TOLERANCE = 1.e-7;
-  // compare poles
-  isValid = isValid && poles.size() == myCachedPoles.size();
-  std::list<GeomPnt2dPtr>::const_iterator aCachePIt = myCachedPoles.begin();
-  GCS::VEC_P::const_iterator aPolesIt = poles.begin();
-  for (; isValid && aPolesIt != poles.end(); ++aPolesIt, ++aCachePIt) {
-    isValid = isValid && fabs((*aCachePIt)->x() - *aPolesIt->x) < THE_TOLERANCE
-                      && fabs((*aCachePIt)->y() - *aPolesIt->y) < THE_TOLERANCE;
+  if (myFlatKnots.empty()) {
+    // fill flat knots indices
+    for (int i = 0; i < (int)mult.size(); ++i)
+      myFlatKnots.resize(myFlatKnots.size() + mult[i], *knots[i]);
+    if (periodic) {
+      // additional knots at the beginning and the end to complete periodity
+      int anExtraBegin = degree + 1 - mult.front();
+      int anExtraEnd = degree + 1 - mult.back();
+      double aPeriod = *knots.back() - *knots.front();
+      VEC_D aNewFlatKnots;
+      aNewFlatKnots.reserve(myFlatKnots.size() + (size_t)(anExtraBegin + anExtraEnd));
+      auto it = myFlatKnots.end() - mult.back() - anExtraBegin;
+      while (anExtraBegin > 0) {
+        aNewFlatKnots.push_back(*(it++) - aPeriod);
+        --anExtraBegin;
+      }
+      aNewFlatKnots.insert(aNewFlatKnots.end(), myFlatKnots.begin(), myFlatKnots.end());
+      it = myFlatKnots.begin() + mult.front();
+      while (anExtraEnd > 0) {
+        aNewFlatKnots.push_back(*(it++) + aPeriod);
+        --anExtraEnd;
+      }
+      myFlatKnots = aNewFlatKnots;
+    }
   }
 
-  // compare weights
-  isValid = isValid && weights.size() == myCachedWeights.size();
-  std::list<double>::const_iterator aCacheWIt = myCachedWeights.begin();
-  GCS::VEC_pD::const_iterator aWeightsIt = weights.begin();
-  for (; isValid && aWeightsIt != weights.end(); ++aWeightsIt, ++aCacheWIt)
-    isValid = isValid && fabs(*aCacheWIt - **aWeightsIt) < THE_TOLERANCE;
+  if (periodic)
+    periodicNormalization(u, *knots.front(), *knots.back());
+
+  int anIndex = 0;
+  for (int i = 1; i < (int)knots.size() - 1; ++i) {
+    if (u <= *knots[i])
+      break;
+    anIndex += mult[i];
+  }
+  return anIndex;
+}
 
-  return isValid;
+void BSplineImpl::spanPolesAndWeights(int theSpanIndex,
+                                      double* theDerivParam,
+                                      std::vector<GCS::DeriVector2>& thePoles,
+                                      std::vector<double>& theWeights) const
+{
+  thePoles.reserve(degree + 1);
+  theWeights.reserve(degree + 1);
+  for (int i = theSpanIndex; i <= theSpanIndex + degree; ++i) {
+    // optimization: weighted pole
+    int idx = i % (int)poles.size();
+    thePoles.push_back(GCS::DeriVector2(poles[idx], theDerivParam).mult(*weights[idx]));
+    theWeights.push_back(*weights[idx]);
+  }
+}
+
+void BSplineImpl::performDeBoor(double theU,
+                                int theSpanIndex,
+                                std::vector<GCS::DeriVector2>& thePoles,
+                                std::vector<double>& theWeights,
+                                GCS::DeriVector2& theValue,
+                                GCS::DeriVector2& theDerivative) const
+{
+  std::vector<GCS::DeriVector2> aPDeriv(thePoles.size(), DeriVector2());
+  std::vector<double> aWDeriv(theWeights.size(), 0.0);
+  for (int i = 0; i < degree; ++i) {
+    for (int j = degree; j > i; --j) {
+      double denom = (myFlatKnots[theSpanIndex + j + degree - i] -
+                      myFlatKnots[theSpanIndex + j]);
+      double a = (theU - myFlatKnots[theSpanIndex + j]) / denom;
+      aPDeriv[j] = aPDeriv[j].linCombi(a, aPDeriv[j - 1], 1.0 - a).sum(
+                   thePoles[j].subtr(thePoles[j - 1]).mult(1.0 / denom));
+      aWDeriv[j] = aWDeriv[j] * a + aWDeriv[j - 1] * (1.0 - a)
+                 + (theWeights[j] - theWeights[j - 1]) / denom;
+      thePoles[j] = thePoles[j].linCombi(a, thePoles[j - 1], 1.0 - a);
+      theWeights[j] = theWeights[j] * a + theWeights[j - 1] * (1.0 - a);
+    }
+  }
+  double w = 1 / theWeights[degree];
+  theValue = thePoles[degree].mult(w);
+  theDerivative = aPDeriv[degree].subtr(theValue.mult(aWDeriv[degree])).mult(w);
 }
 
-void BSplineImpl::rebuildCache()
+bool BSplineImpl::parameter(const Point& thePoint, double& theParam) const
 {
-  myCachedPoles.clear();
-  myCachedWeights.clear();
-  myCachedKnots.clear();
-  myCachedMultiplicities.clear();
-
-  for (GCS::VEC_P::iterator anIt = poles.begin(); anIt != poles.end(); ++anIt)
-    myCachedPoles.push_back(GeomPnt2dPtr(new GeomAPI_Pnt2d(*anIt->x, *anIt->y)));
-  for (GCS::VEC_pD::iterator anIt = weights.begin(); anIt != weights.end(); ++anIt)
-    myCachedWeights.push_back(**anIt);
-  for (GCS::VEC_pD::iterator anIt = knots.begin(); anIt != knots.end(); ++anIt)
-    myCachedKnots.push_back(**anIt);
-  myCachedMultiplicities.assign(mult.begin(), mult.end());
-
-  myCurve.reset(new GeomAPI_BSpline2d(degree, myCachedPoles, myCachedWeights,
-                                      myCachedKnots, myCachedMultiplicities, periodic));
+  std::list<GeomPnt2dPtr> aPoles;
+  std::list<double> aWeights;
+  std::list<double> aKnots;
+  std::list<int> aMults;
+
+  for (GCS::VEC_P::const_iterator anIt = poles.begin(); anIt != poles.end(); ++anIt)
+    aPoles.push_back(GeomPnt2dPtr(new GeomAPI_Pnt2d(*anIt->x, *anIt->y)));
+  for (GCS::VEC_pD::const_iterator anIt = weights.begin(); anIt != weights.end(); ++anIt)
+    aWeights.push_back(**anIt);
+  for (GCS::VEC_pD::const_iterator anIt = knots.begin(); anIt != knots.end(); ++anIt)
+    aKnots.push_back(**anIt);
+  aMults.assign(mult.begin(), mult.end());
+
+  std::shared_ptr<GeomAPI_BSpline2d> aCurve(
+      new GeomAPI_BSpline2d(degree, aPoles, aWeights, aKnots, aMults, periodic));
+
+  GeomPnt2dPtr aPoint(new GeomAPI_Pnt2d(*thePoint.x, *thePoint.y));
+  return aCurve->parameter(aPoint, 1e100, theParam);
 }
 
 } // namespace GCS
index c83e1a9ddde3eacbfb8f664161ffcc2439e44020..3b734538fa53fd76854c147b8a0153c4809fb68a 100644 (file)
 
 #include <PlaneGCSSolver_Defs.h>
 
-#include <list>
-#include <memory>
-
-class GeomAPI_BSpline2d;
-class GeomAPI_Pnt2d;
-
 namespace GCS {
   /// \brife SHAPER's implementation of B-spline curves in PlaneGCS solver
   class BSplineImpl : public BSpline
@@ -39,17 +33,32 @@ namespace GCS {
     virtual BSplineImpl* Copy();
 
   private:
-    /// Verify the cached curve satisfies to the parameters
-    bool isCacheValid() const;
-    /// Poles or weights are changed, cache curve has to be rebuilt
-    void rebuildCache();
+    /// Return the index of start knot for the given parameter.
+    /// Parameter is updated accordingly, if the B-spline curve is periodic
+    /// and the parameter is out of period.
+    int spanIndex(double& u);
+
+    /// Collect the list of poles and their weights affecting the given span
+    void spanPolesAndWeights(int theSpanIndex,
+                             double* theDerivParam,
+                             std::vector<GCS::DeriVector2>& thePoles,
+                             std::vector<double>& theWeights) const;
+
+    /// Execute De Boor algorithm to calculate B-spline curve's value
+    void performDeBoor(double theU, int theSpanIndex,
+                       std::vector<GCS::DeriVector2>& thePoles, std::vector<double>& theWeights,
+                       GCS::DeriVector2& theValue, GCS::DeriVector2& theDerivative) const;
+
+    /// Calculate the value and the first derivative for the given parameter on B-spline
+    void d1(double theU, double* theDerivParam,
+            GCS::DeriVector2& theValue, GCS::DeriVector2& theDerivative);
+
+    /// Find the parameter on B-spline corresponding to the given point
+    /// \return \c false if it is unable to calculate the parameter
+    bool parameter(const Point& thePoint, double& theParam) const;
 
   private:
-    std::shared_ptr<GeomAPI_BSpline2d> myCurve; /// cached B-spline curve
-    std::list<std::shared_ptr<GeomAPI_Pnt2d> > myCachedPoles; /// cached B-spline poles
-    std::list<double> myCachedWeights; /// cached B-spline weights
-    std::list<double> myCachedKnots; /// cached B-spline knots
-    std::list<int> myCachedMultiplicities; /// cached B-spline multiplicities
+    VEC_D myFlatKnots; /// indices of knots duplicated by multiplicity
   };
 }