Salome HOME
Merge branch 'master' into occ/bsplines
authorazv <azv@opencascade.com>
Wed, 29 Jan 2020 10:43:15 +0000 (13:43 +0300)
committerazv <azv@opencascade.com>
Wed, 29 Jan 2020 10:43:15 +0000 (13:43 +0300)
139 files changed:
lcov_reports.sh
src/ConstructionPlugin/ConstructionPlugin_Plugin.cpp
src/GeomAPI/CMakeLists.txt
src/GeomAPI/GeomAPI.i
src/GeomAPI/GeomAPI_BSpline.cpp [new file with mode: 0644]
src/GeomAPI/GeomAPI_BSpline.h [new file with mode: 0644]
src/GeomAPI/GeomAPI_BSpline2d.cpp [new file with mode: 0644]
src/GeomAPI/GeomAPI_BSpline2d.h [new file with mode: 0644]
src/GeomAPI/GeomAPI_Edge.cpp
src/GeomAPI/GeomAPI_Edge.h
src/GeomAPI/GeomAPI_swig.h
src/GeomAlgoAPI/GeomAlgoAPI_EdgeBuilder.cpp
src/GeomAlgoAPI/GeomAlgoAPI_EdgeBuilder.h
src/GeomData/CMakeLists.txt
src/GeomData/GeomData_Point2DArray.cpp [new file with mode: 0644]
src/GeomData/GeomData_Point2DArray.h [new file with mode: 0644]
src/GeomDataAPI/CMakeLists.txt
src/GeomDataAPI/GeomDataAPI.i
src/GeomDataAPI/GeomDataAPI_Point2DArray.cpp [new file with mode: 0644]
src/GeomDataAPI/GeomDataAPI_Point2DArray.h [new file with mode: 0644]
src/GeomDataAPI/GeomDataAPI_swig.h
src/Model/Model_AttributeRefAttrList.cpp
src/Model/Model_Data.cpp
src/ModelAPI/ModelAPI_Events.cpp
src/ModelAPI/ModelAPI_Events.h
src/ModelAPI/ModelAPI_swig.h
src/ModelHighAPI/ModelHighAPI.i
src/ModelHighAPI/ModelHighAPI_Double.cpp
src/ModelHighAPI/ModelHighAPI_Double.h
src/ModelHighAPI/ModelHighAPI_Dumper.cpp
src/ModelHighAPI/ModelHighAPI_Dumper.h
src/ModelHighAPI/ModelHighAPI_Tools.cpp
src/ModelHighAPI/ModelHighAPI_Tools.h
src/ModuleBase/ModuleBase_IWidgetCreator.cpp
src/ModuleBase/ModuleBase_IWidgetCreator.h
src/ModuleBase/ModuleBase_ModelWidget.h
src/ModuleBase/ModuleBase_WidgetCreatorFactory.cpp
src/ModuleBase/ModuleBase_WidgetCreatorFactory.h
src/ModuleBase/ModuleBase_WidgetFactory.cpp
src/PartSet/CMakeLists.txt
src/PartSet/PartSet_BSplineWidget.cpp [new file with mode: 0644]
src/PartSet/PartSet_BSplineWidget.h [new file with mode: 0644]
src/PartSet/PartSet_Module.cpp
src/PartSet/PartSet_SketcherMgr.cpp
src/PartSet/PartSet_SketcherMgr.h
src/PartSet/PartSet_Tools.cpp
src/PartSet/PartSet_Tools.h
src/PartSet/PartSet_WidgetBSplinePoints.cpp [new file with mode: 0644]
src/PartSet/PartSet_WidgetBSplinePoints.h [new file with mode: 0644]
src/PartSet/PartSet_WidgetPoint2d.cpp
src/PartSet/PartSet_WidgetPoint2d.h
src/PartSet/PartSet_msg_fr.ts
src/SketchAPI/CMakeLists.txt
src/SketchAPI/SketchAPI.i
src/SketchAPI/SketchAPI_BSpline.cpp [new file with mode: 0644]
src/SketchAPI/SketchAPI_BSpline.h [new file with mode: 0644]
src/SketchAPI/SketchAPI_Projection.cpp
src/SketchAPI/SketchAPI_Sketch.cpp
src/SketchAPI/SketchAPI_Sketch.h
src/SketchAPI/SketchAPI_SketchEntity.cpp
src/SketchAPI/SketchAPI_swig.h
src/SketchPlugin/CMakeLists.txt
src/SketchPlugin/SketchPlugin_BSpline.cpp [new file with mode: 0644]
src/SketchPlugin/SketchPlugin_BSpline.h [new file with mode: 0644]
src/SketchPlugin/SketchPlugin_BSplineBase.cpp [new file with mode: 0644]
src/SketchPlugin/SketchPlugin_BSplineBase.h [new file with mode: 0644]
src/SketchPlugin/SketchPlugin_BSplinePeriodic.cpp [new file with mode: 0644]
src/SketchPlugin/SketchPlugin_BSplinePeriodic.h [new file with mode: 0644]
src/SketchPlugin/SketchPlugin_ConstraintCoincidenceInternal.cpp
src/SketchPlugin/SketchPlugin_ConstraintCoincidenceInternal.h
src/SketchPlugin/SketchPlugin_MacroBSpline.cpp [new file with mode: 0644]
src/SketchPlugin/SketchPlugin_MacroBSpline.h [new file with mode: 0644]
src/SketchPlugin/SketchPlugin_Plugin.cpp
src/SketchPlugin/SketchPlugin_Projection.cpp
src/SketchPlugin/SketchPlugin_Projection.h
src/SketchPlugin/SketchPlugin_Validators.cpp
src/SketchPlugin/SketchPlugin_Validators.h
src/SketchPlugin/SketchPlugin_msg_fr.ts
src/SketchPlugin/Test/TestConstraintCoincidenceBSpline.py [new file with mode: 0644]
src/SketchPlugin/Test/TestConstraintTangentBSpline.py [new file with mode: 0644]
src/SketchPlugin/Test/TestCreateBSpline.py [new file with mode: 0644]
src/SketchPlugin/Test/TestCreateBSplinePeriodic.py [new file with mode: 0644]
src/SketchPlugin/Test/TestMoveBSpline.py [new file with mode: 0644]
src/SketchPlugin/Test/TestMoveBSplinePeriodic.py [new file with mode: 0644]
src/SketchPlugin/Test/TestPresentation.py
src/SketchPlugin/Test/TestProjectionBSpline.py [new file with mode: 0644]
src/SketchPlugin/Test/TestProjectionBSplinePeriodic.py [new file with mode: 0644]
src/SketchPlugin/Test/TestRemoveBSpline.py [new file with mode: 0644]
src/SketchPlugin/Test/TestRemoveBSplinePeriodic.py [new file with mode: 0644]
src/SketchPlugin/doc/SketchPlugin.rst
src/SketchPlugin/doc/TUI_bsplineFeature.rst [new file with mode: 0644]
src/SketchPlugin/doc/bsplineFeature.rst [new file with mode: 0644]
src/SketchPlugin/doc/examples/bspline.py [new file with mode: 0644]
src/SketchPlugin/doc/images/bspline.png [new file with mode: 0644]
src/SketchPlugin/doc/images/bspline_add_pole.png [new file with mode: 0644]
src/SketchPlugin/doc/images/bspline_creation_panel.png [new file with mode: 0644]
src/SketchPlugin/doc/images/bspline_modification_panel.png [new file with mode: 0644]
src/SketchPlugin/doc/images/bspline_p.png [new file with mode: 0644]
src/SketchPlugin/doc/images/bspline_periodic_result.png [new file with mode: 0644]
src/SketchPlugin/doc/images/bspline_result.png [new file with mode: 0644]
src/SketchPlugin/icons/bspline.png [new file with mode: 0644]
src/SketchPlugin/icons/bspline_p.png [new file with mode: 0644]
src/SketchPlugin/plugin-Sketch.xml
src/SketchSolver/PlaneGCSSolver/CMakeLists.txt
src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_AttributeBuilder.cpp
src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_AttributeBuilder.h
src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_BooleanWrapper.cpp
src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_BooleanWrapper.h
src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_ConstraintWrapper.h
src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_Defs.h
src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_EdgeWrapper.cpp
src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_EntityWrapper.h
src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_FeatureBuilder.cpp
src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_GeoExtensions.cpp [new file with mode: 0644]
src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_GeoExtensions.h [new file with mode: 0644]
src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_PointArrayWrapper.cpp [new file with mode: 0644]
src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_PointArrayWrapper.h [new file with mode: 0644]
src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_PointWrapper.cpp
src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_PointWrapper.h
src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_ScalarArrayWrapper.cpp [new file with mode: 0644]
src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_ScalarArrayWrapper.h [new file with mode: 0644]
src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_ScalarWrapper.cpp
src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_ScalarWrapper.h
src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_Storage.cpp
src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_Tools.cpp
src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_Tools.h
src/SketchSolver/SketchSolver_Constraint.cpp
src/SketchSolver/SketchSolver_ConstraintCoincidence.cpp
src/SketchSolver/SketchSolver_ConstraintCoincidence.h
src/SketchSolver/SketchSolver_ConstraintFixed.cpp
src/SketchSolver/SketchSolver_ConstraintMovement.cpp
src/SketchSolver/SketchSolver_ConstraintMovement.h
src/SketchSolver/SketchSolver_ConstraintTangent.cpp
src/SketchSolver/SketchSolver_ConstraintTangent.h
src/SketchSolver/SketchSolver_Group.cpp
src/SketchSolver/SketchSolver_Group.h
src/SketchSolver/SketchSolver_Manager.cpp
src/SketchSolver/SketchSolver_Manager.h
src/XGUI/XGUI_PropertyPanel.cpp

index cd094cf1528fb31670e24af8f0b45dfd6e2d4ce3..e2b48e3cade1f7c88ffacb85eeb6b63239075f04 100755 (executable)
@@ -9,8 +9,7 @@ lcov --capture --directory ${BUILD_DIR} --no-external --base-directory ${SOURCES
 # make a working copy of report
 cp -f coverage.info.noext covfile
 # remove all reports of GUI and external parts (for all the next kinds of reports)
-# RefAttrList is unused type of attribute for now
-for MASK in '*wrap*' 'moc_*' 'XAO_*' 'SketcherPrs_*' 'GeomAlgoImpl_*' 'ModuleBase_*' '*Widget*' '*Splitter*' '*RefAttrList*'; do
+for MASK in '*wrap*' 'moc_*' 'XAO_*' 'SketcherPrs_*' 'GeomAlgoImpl_*' 'ModuleBase_*' '*Widget*' '*Splitter*'; do
 lcov -r covfile ${MASK} --output-file covfile_res -q
 mv -f covfile_res covfile
 done
index 855ce055a46a7eab21f9288174b5cabc3fc68bde..7e8ff1db5d49ba611905bf13954ccf76479a6507 100644 (file)
@@ -60,6 +60,8 @@ ConstructionPlugin_Plugin::ConstructionPlugin_Plugin()
     Config_Prop::IntSpin, SKETCH_WIDTH);
   Config_PropManager::registerProp(SKETCH_TAB_NAME, "angular_tolerance", "Angular tolerance",
     Config_Prop::DblSpin, "0.04");
+  Config_PropManager::registerProp(SKETCH_TAB_NAME, "spline_weight", "Default spline weight",
+    Config_Prop::DblSpin, "1.0");
   Config_PropManager::registerProp(SKETCH_TAB_NAME, "rotate_to_plane",
     "Rotate to plane when selected", Config_Prop::Boolean, "false");
   Config_PropManager::registerProp(SKETCH_TAB_NAME, "operation_cursor",
index cade6fc2f16ff15464185570981dc706b74fd3b4..267c703a9c5f5160db59c157b4f30ccf0fe22a99 100644 (file)
@@ -25,6 +25,8 @@ INCLUDE(UnitTest)
 
 SET(PROJECT_HEADERS
     GeomAPI.h
+    GeomAPI_BSpline.h
+    GeomAPI_BSpline2d.h
     GeomAPI_Circ.h
     GeomAPI_Circ2d.h
     GeomAPI_Interface.h
@@ -71,6 +73,8 @@ SET(PROJECT_HEADERS
 )
 
 SET(PROJECT_SOURCES
+    GeomAPI_BSpline.cpp
+    GeomAPI_BSpline2d.cpp
     GeomAPI_Circ.cpp
     GeomAPI_Circ2d.cpp
     GeomAPI_Interface.cpp
index 4c4110155cfe305b7d424d90bc1b9dfd97648cb4..4b264be7067885214662a605336435f05a92b4f1 100644 (file)
@@ -42,6 +42,8 @@
 %shared_ptr(GeomAPI_Ax2)
 %shared_ptr(GeomAPI_Ax3)
 %shared_ptr(GeomAPI_Box)
+%shared_ptr(GeomAPI_BSpline)
+%shared_ptr(GeomAPI_BSpline2d)
 %shared_ptr(GeomAPI_Circ)
 %shared_ptr(GeomAPI_Circ2d)
 %shared_ptr(GeomAPI_Cone)
   }
 }
 
-%typemap(in) double & (double temp) {
-  if (PyLong_Check($input)) {
-    temp = PyLong_AsLong($input);
-    $1 = &temp;
-  }
+%typemap(in, numinputs=0) double & (double temp) {
+  $1 = &temp;
 }
 
 %typemap(argout) double & {
-  $result = PyFloat_FromDouble(*$1);
+  $result = SWIG_Python_AppendOutput($result, PyFloat_FromDouble(*$1));
 }
 
+// std::dynamic_pointer_cast
+template<class T1, class T2> std::shared_ptr<T1> shared_ptr_cast(std::shared_ptr<T2> theObject);
+%template(shapeToEdge) shared_ptr_cast<GeomAPI_Edge, GeomAPI_Shape>;
+
 
 // all supported interfaces
 %include "GeomAPI_Interface.h"
 %include "GeomAPI_Ax2.h"
 %include "GeomAPI_Ax3.h"
 %include "GeomAPI_Box.h"
+%include "GeomAPI_BSpline.h"
+%include "GeomAPI_BSpline2d.h"
 %include "GeomAPI_Circ.h"
 %include "GeomAPI_Circ2d.h"
 %include "GeomAPI_Cone.h"
diff --git a/src/GeomAPI/GeomAPI_BSpline.cpp b/src/GeomAPI/GeomAPI_BSpline.cpp
new file mode 100644 (file)
index 0000000..f730769
--- /dev/null
@@ -0,0 +1,73 @@
+// Copyright (C) 2019-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 <GeomAPI_BSpline.h>
+#include <GeomAPI_Pnt.h>
+
+#include <Geom_BSplineCurve.hxx>
+
+#define MY_BSPLINE (*(implPtr<Handle_Geom_BSplineCurve>()))
+
+GeomAPI_BSpline::GeomAPI_BSpline(const GeomCurvePtr& theCurve)
+{
+  GeomCurvePtr anUntrimmedCurve = theCurve->basisCurve();
+  Handle(Geom_Curve) aCurve = anUntrimmedCurve->impl<Handle(Geom_Curve)>();
+  Handle(Geom_BSplineCurve) aBSpl = Handle(Geom_BSplineCurve)::DownCast(aCurve);
+  if (aBSpl.IsNull())
+    throw Standard_ConstructionError("GeomAPI_BSpline: Curve is not a B-spline");
+  setImpl(new Handle_Geom_BSplineCurve(aBSpl));
+}
+
+int GeomAPI_BSpline::degree() const
+{
+  return MY_BSPLINE->Degree();
+}
+
+std::list<GeomPointPtr> GeomAPI_BSpline::poles() const
+{
+  const TColgp_Array1OfPnt& aBSplPoles = MY_BSPLINE->Poles();
+
+  std::list<GeomPointPtr> aPoles;
+  for (int anIndex = aBSplPoles.Lower(); anIndex <= aBSplPoles.Upper(); ++anIndex) {
+    const gp_Pnt& aPoint = aBSplPoles.Value(anIndex);
+    aPoles.push_back(GeomPointPtr(new GeomAPI_Pnt(aPoint.X(), aPoint.Y(), aPoint.Z())));
+  }
+  return aPoles;
+}
+
+std::list<double> GeomAPI_BSpline::weights() const
+{
+  std::list<double> aWeights;
+  const TColStd_Array1OfReal* aBSplWeights = MY_BSPLINE->Weights();
+  if (aBSplWeights)
+    aWeights.assign(aBSplWeights->begin(), aBSplWeights->end());
+  return aWeights;
+}
+
+std::list<double> GeomAPI_BSpline::knots() const
+{
+  const TColStd_Array1OfReal& aBSplKnots = MY_BSPLINE->Knots();
+  return std::list<double>(aBSplKnots.begin(), aBSplKnots.end());
+}
+
+std::list<int> GeomAPI_BSpline::mults() const
+{
+  const TColStd_Array1OfInteger& aBSplMults = MY_BSPLINE->Multiplicities();
+  return std::list<int>(aBSplMults.begin(), aBSplMults.end());
+}
diff --git a/src/GeomAPI/GeomAPI_BSpline.h b/src/GeomAPI/GeomAPI_BSpline.h
new file mode 100644 (file)
index 0000000..ae067d0
--- /dev/null
@@ -0,0 +1,60 @@
+// Copyright (C) 2019-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 GeomAPI_BSpline_H_
+#define GeomAPI_BSpline_H_
+
+#include <GeomAPI_Interface.h>
+#include <GeomAPI_Curve.h>
+
+#include <list>
+#include <memory>
+
+class GeomAPI_Pnt;
+
+/**\class GeomAPI_BSpline
+ * \ingroup DataModel
+ * \brief B-spline in 3D
+ */
+class GeomAPI_BSpline : public GeomAPI_Interface
+{
+public:
+  /// Creation of B-spline defined by a curve
+  GEOMAPI_EXPORT GeomAPI_BSpline(const GeomCurvePtr& theCurve);
+
+  /// Degree of B-spline curve
+  GEOMAPI_EXPORT int degree() const;
+
+  /// Poles of B-spline curve
+  GEOMAPI_EXPORT std::list<std::shared_ptr<GeomAPI_Pnt> > poles() const;
+
+  /// Weights of B-spline poles
+  GEOMAPI_EXPORT std::list<double> weights() const;
+
+  /// Knots of B-spline curve
+  GEOMAPI_EXPORT std::list<double> knots() const;
+
+  /// Multiplicities of B-spline knots
+  GEOMAPI_EXPORT std::list<int> mults() const;
+};
+
+//! Pointer on the object
+typedef std::shared_ptr<GeomAPI_BSpline> GeomBSplinePtr;
+
+#endif
diff --git a/src/GeomAPI/GeomAPI_BSpline2d.cpp b/src/GeomAPI/GeomAPI_BSpline2d.cpp
new file mode 100644 (file)
index 0000000..f702a27
--- /dev/null
@@ -0,0 +1,201 @@
+// Copyright (C) 2019-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 <GeomAPI_BSpline2d.h>
+#include <GeomAPI_Pnt2d.h>
+#include <GeomAPI_XY.h>
+
+#include <Geom2d_BSplineCurve.hxx>
+#include <Geom2dAPI_ProjectPointOnCurve.hxx>
+#include <GeomLib_Tool.hxx>
+#include <Precision.hxx>
+
+#define MY_BSPLINE (*(implPtr<Handle_Geom2d_BSplineCurve>()))
+
+
+static Handle_Geom2d_BSplineCurve* newBSpline2d(
+  const std::list<std::shared_ptr<GeomAPI_Pnt2d> >& thePoles,
+  const std::list<double>& theWeights,
+  const int theDegree,
+  const bool thePeriodic);
+
+
+static Handle_Geom2d_BSplineCurve* newBSpline2d(
+    const std::list<std::shared_ptr<GeomAPI_Pnt2d> >& thePoles,
+    const std::list<double>& theWeights,
+    const std::list<double>& theKnots,
+    const std::list<int>& theMults,
+    const int theDegree,
+    const bool thePeriodic)
+{
+  if (theKnots.empty() || theMults.empty())
+    return newBSpline2d(thePoles, theWeights, theDegree, thePeriodic);
+
+  int anAuxPole = 0;
+  if (thePeriodic && thePoles.front()->distance(thePoles.back()) < Precision::Confusion())
+    anAuxPole = -1;
+
+  // collect arrays of poles, weights, knots and multiplicities
+  TColgp_Array1OfPnt2d aPoles(1, (int)thePoles.size() + anAuxPole);
+  TColStd_Array1OfReal aWeights(1, (int)theWeights.size() + anAuxPole);
+  TColStd_Array1OfReal aKnots(1, (int)theKnots.size());
+  TColStd_Array1OfInteger aMults(1, (int)theMults.size());
+
+  int anIndex = 1;
+  for (std::list<GeomPnt2dPtr>::const_iterator aPIt = thePoles.begin();
+       aPIt != thePoles.end() && anIndex <= aPoles.Upper(); ++aPIt, ++anIndex)
+    aPoles.SetValue(anIndex, gp_Pnt2d((*aPIt)->x(), (*aPIt)->y()));
+  anIndex = 1;
+  for (std::list<double>::const_iterator aWIt = theWeights.begin();
+       aWIt != theWeights.end() && anIndex <= aWeights.Upper(); ++aWIt, ++anIndex)
+    aWeights.SetValue(anIndex, *aWIt);
+  anIndex = 1;
+  for (std::list<double>::const_iterator aKIt = theKnots.begin();
+       aKIt != theKnots.end(); ++aKIt, ++anIndex)
+    aKnots.SetValue(anIndex, *aKIt);
+  anIndex = 1;
+  for (std::list<int>::const_iterator aMIt = theMults.begin();
+       aMIt != theMults.end(); ++aMIt, ++anIndex)
+    aMults.SetValue(anIndex, *aMIt);
+
+  Handle(Geom2d_BSplineCurve) aCurve =
+      new Geom2d_BSplineCurve(aPoles, aWeights, aKnots, aMults, theDegree, thePeriodic);
+  return new Handle_Geom2d_BSplineCurve(aCurve);
+}
+
+Handle_Geom2d_BSplineCurve* newBSpline2d(
+    const std::list<std::shared_ptr<GeomAPI_Pnt2d> >& thePoles,
+    const std::list<double>& theWeights,
+    const int theDegree,
+    const bool thePeriodic)
+{
+  std::list<std::shared_ptr<GeomAPI_Pnt2d> > aPoles = thePoles;
+  std::list<double> aWeights = theWeights;
+  int aMult = theDegree + 1;
+  int aNbKnots = (int)thePoles.size() - theDegree + 1;
+  if (thePeriodic) {
+    if (aPoles.front()->distance(aPoles.back()) < Precision::Confusion()) {
+      aPoles.pop_back();
+      aWeights.pop_back();
+    }
+    aMult = 1;
+    aNbKnots = (int)aPoles.size() + 1;
+  }
+
+  if (aNbKnots < 2)
+    return new Handle_Geom2d_BSplineCurve();
+
+  static const double aStartParam = 0.0;
+  static const double aEndParam = 1.0;
+  double aStep = aEndParam / (aNbKnots - 1);
+  int anIndex = 1;
+  std::list<double> aKnots;
+  for (double aKnot = aStartParam; anIndex < aNbKnots; ++anIndex, aKnot += aStep)
+    aKnots.push_back(aKnot);
+  aKnots.push_back(aEndParam);
+
+  std::list<int> aMults(aNbKnots - 2, 1);
+  aMults.push_front(aMult);
+  aMults.push_back(aMult);
+
+  return newBSpline2d(aPoles, aWeights, aKnots, aMults, theDegree, thePeriodic);
+}
+
+static Handle_Geom2d_BSplineCurve* newBSpline2d(
+    const std::list<std::shared_ptr<GeomAPI_Pnt2d> >& thePoles,
+    const std::list<double>& theWeights,
+    const bool thePeriodic)
+{
+  int aDegree = 3;
+  if ((int)thePoles.size() <= aDegree)
+    aDegree = (int)thePoles.size() - 1;
+  if (aDegree <= 0)
+    return new Handle_Geom2d_BSplineCurve();
+  return newBSpline2d(thePoles, theWeights, aDegree, thePeriodic);
+}
+
+
+GeomAPI_BSpline2d::GeomAPI_BSpline2d(const std::list<std::shared_ptr<GeomAPI_Pnt2d> >& thePoles,
+                                     const std::list<double>& theWeights,
+                                     const bool thePeriodic)
+  : GeomAPI_Interface(newBSpline2d(thePoles, theWeights, thePeriodic))
+{
+  if (isNull())
+    throw Standard_ConstructionError("GeomAPI_BSpline2d: Impossible to create B-spline curve");
+}
+
+GeomAPI_BSpline2d::GeomAPI_BSpline2d(const int theDegree,
+                                     const std::list<std::shared_ptr<GeomAPI_Pnt2d> >& thePoles,
+                                     const std::list<double>& theWeights,
+                                     const std::list<double>& theKnots,
+                                     const std::list<int>& theMults,
+                                     const bool thePeriodic)
+  : GeomAPI_Interface(newBSpline2d(thePoles, theWeights, theKnots, theMults,
+                                   theDegree, thePeriodic))
+{
+  if (isNull())
+    throw Standard_ConstructionError("GeomAPI_BSpline2d: Impossible to create B-spline curve");
+}
+
+bool GeomAPI_BSpline2d::isNull() const
+{
+  return MY_BSPLINE.IsNull();
+}
+
+int GeomAPI_BSpline2d::degree() const
+{
+  return MY_BSPLINE->Degree();
+}
+
+std::list<double> GeomAPI_BSpline2d::knots() const
+{
+  const TColStd_Array1OfReal& aBSplKnots = MY_BSPLINE->Knots();
+  return std::list<double>(aBSplKnots.begin(), aBSplKnots.end());
+}
+
+std::list<int> GeomAPI_BSpline2d::mults() const
+{
+  const TColStd_Array1OfInteger& aBSplMults = MY_BSPLINE->Multiplicities();
+  return std::list<int>(aBSplMults.begin(), aBSplMults.end());
+}
+
+const bool GeomAPI_BSpline2d::parameter(const std::shared_ptr<GeomAPI_Pnt2d> thePoint,
+                                        const double theTolerance,
+                                        double& theParameter) const
+{
+  return GeomLib_Tool::Parameter(MY_BSPLINE, thePoint->impl<gp_Pnt2d>(),
+                                 theTolerance, theParameter) == Standard_True;
+}
+
+void GeomAPI_BSpline2d::D0(const double theU, std::shared_ptr<GeomAPI_Pnt2d>& thePoint)
+{
+  gp_Pnt2d aPnt;
+  MY_BSPLINE->D0(theU, aPnt);
+  thePoint.reset(new GeomAPI_Pnt2d(aPnt.X(), aPnt.Y()));
+}
+
+void GeomAPI_BSpline2d::D1(const double theU, std::shared_ptr<GeomAPI_Pnt2d>& thePoint,
+                                              std::shared_ptr<GeomAPI_XY>& theDerivative)
+{
+  gp_Pnt2d aPnt;
+  gp_Vec2d aVec;
+  MY_BSPLINE->D1(theU, aPnt, aVec);
+  thePoint.reset(new GeomAPI_Pnt2d(aPnt.X(), aPnt.Y()));
+  theDerivative.reset(new GeomAPI_XY(aVec.X(), aVec.Y()));
+}
diff --git a/src/GeomAPI/GeomAPI_BSpline2d.h b/src/GeomAPI/GeomAPI_BSpline2d.h
new file mode 100644 (file)
index 0000000..260577b
--- /dev/null
@@ -0,0 +1,83 @@
+// Copyright (C) 2019-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 GeomAPI_BSpline2d_H_
+#define GeomAPI_BSpline2d_H_
+
+#include <GeomAPI_Interface.h>
+
+#include <list>
+#include <memory>
+
+class GeomAPI_Pnt2d;
+class GeomAPI_XY;
+
+/** \class GeomAPI_BSpline2d
+ *  \ingroup DataModel
+ *  \brief B-spline curve in 2D
+ */
+class GeomAPI_BSpline2d : public GeomAPI_Interface
+{
+public:
+  /// Creation of B-spline curve defined by list of poles and weights
+  GEOMAPI_EXPORT GeomAPI_BSpline2d(const std::list<std::shared_ptr<GeomAPI_Pnt2d> >& thePoles,
+                                   const std::list<double>& theWeights,
+                                   const bool thePeriodic = false);
+
+  /// Creation of B-spline curve defined by list of poles and weights
+  GEOMAPI_EXPORT GeomAPI_BSpline2d(const int theDegree,
+                                   const std::list<std::shared_ptr<GeomAPI_Pnt2d> >& thePoles,
+                                   const std::list<double>& theWeights,
+                                   const std::list<double>& theKnots = std::list<double>(),
+                                   const std::list<int>& theMults = std::list<int>(),
+                                   const bool thePeriodic = false);
+
+  /// Returns true if curve is not initialized
+  GEOMAPI_EXPORT bool isNull() const;
+
+  /// Returns degree of the curve
+  GEOMAPI_EXPORT int degree() const;
+
+  /// Knots of the curve
+  GEOMAPI_EXPORT std::list<double> knots() const;
+
+  /// Multiplicities of the knots
+  GEOMAPI_EXPORT std::list<int> mults() const;
+
+  /// \brief Computes the parameter of a given point on a circle. The point must be
+  ///        located either on the circle itself or relatively to the latter
+  ///        at a distance less than the tolerance value. Return FALSE if the point
+  ///        is beyond the tolerance limit or if computation fails.
+  ///        Max Tolerance value is currently limited to 1.e-4
+  /// \param[in] thePoint point of origin.
+  /// \param[in] theTolerance tolerance of computation.
+  /// \param[out] theParameter resulting parameter.
+  GEOMAPI_EXPORT const bool parameter(const std::shared_ptr<GeomAPI_Pnt2d> thePoint,
+                                      const double theTolerance,
+                                      double& theParameter) const;
+
+  /// \brief Calculate point on B-spline curve accrding to the given parameter
+  GEOMAPI_EXPORT void D0(const double theU, std::shared_ptr<GeomAPI_Pnt2d>& thePoint);
+
+  /// \brief Calculate point and first derivative for B-spline curve accrding to the given parameter
+  GEOMAPI_EXPORT void D1(const double theU, std::shared_ptr<GeomAPI_Pnt2d>& thePoint,
+                                            std::shared_ptr<GeomAPI_XY>& theDerivative);
+};
+
+#endif
index c4504272b3ccbea972eb5606d69f5288aab6da0a..03868875ba53de38688ae1abddfed3e399a942e7 100644 (file)
@@ -36,6 +36,7 @@
 #include <BRep_Tool.hxx>
 #include <ElCLib.hxx>
 #include <GCPnts_UniformAbscissa.hxx>
+#include <Geom_BSplineCurve.hxx>
 #include <Geom_Curve.hxx>
 #include <Geom_Line.hxx>
 #include <Geom_Circle.hxx>
@@ -179,6 +180,18 @@ bool GeomAPI_Edge::isEllipse() const
   return false;
 }
 
+bool GeomAPI_Edge::isBSpline() const
+{
+  const TopoDS_Shape& aShape = const_cast<GeomAPI_Edge*>(this)->impl<TopoDS_Shape>();
+  double aFirst, aLast;
+  Handle(Geom_Curve) aCurve = BRep_Tool::Curve((const TopoDS_Edge&)aShape, aFirst, aLast);
+  if (aCurve.IsNull()) // degenerative edge
+    return false;
+  while (aCurve->IsKind(STANDARD_TYPE(Geom_TrimmedCurve)))
+    aCurve = Handle(Geom_TrimmedCurve)::DownCast(aCurve)->BasisCurve();
+  return aCurve->IsKind(STANDARD_TYPE(Geom_BSplineCurve));
+}
+
 std::shared_ptr<GeomAPI_Pnt> GeomAPI_Edge::firstPoint()
 {
   const TopoDS_Shape& aShape = const_cast<GeomAPI_Edge*>(this)->impl<TopoDS_Shape>();
@@ -294,13 +307,26 @@ bool GeomAPI_Edge::isEqual(const std::shared_ptr<GeomAPI_Shape> theEdge) const
   return true;
 }
 
-// LCOV_EXCL_START
+void GeomAPI_Edge::setRange(const double& theFirst, const double& theLast)
+{
+  TopoDS_Edge anEdge = impl<TopoDS_Edge>();
+  double aFirst, aLast;
+  Handle(Geom_Curve) aCurve = BRep_Tool::Curve(anEdge, aFirst, aLast);
+  double aTolerance = BRep_Tool::Tolerance(anEdge);
+  if (aCurve->DynamicType() == STANDARD_TYPE(Geom_TrimmedCurve)) {
+    aCurve = Handle(Geom_TrimmedCurve)::DownCast(aCurve)->BasisCurve();
+    BRep_Builder().UpdateEdge(anEdge, aCurve, aTolerance);
+  }
+  BRep_Builder().Range(anEdge, theFirst, theLast);
+}
+
 void GeomAPI_Edge::getRange(double& theFirst, double& theLast) const
 {
   const TopoDS_Shape& aShape = const_cast<GeomAPI_Edge*>(this)->impl<TopoDS_Shape>();
   Handle(Geom_Curve) aCurve = BRep_Tool::Curve((const TopoDS_Edge&)aShape, theFirst, theLast);
 }
 
+// LCOV_EXCL_START
 bool GeomAPI_Edge::isInPlane(std::shared_ptr<GeomAPI_Pln> thePlane) const
 {
   double aFirst, aLast;
index fdf82ed6fb57954343676c3969fa3805791f44a1..3e926d8d04e5d109cb512957bc4eeed805425c10 100644 (file)
@@ -70,6 +70,10 @@ public:
   GEOMAPI_EXPORT
   bool isEllipse() const;
 
+  /// Verifies that the edge is based on a B-spline curve
+  GEOMAPI_EXPORT
+  bool isBSpline() const;
+
   /// Returns the first vertex coordinates of the edge
   GEOMAPI_EXPORT
   std::shared_ptr<GeomAPI_Pnt> firstPoint();
@@ -94,6 +98,10 @@ public:
   GEOMAPI_EXPORT
   bool isEqual(const std::shared_ptr<GeomAPI_Shape> theEdge) const;
 
+  /// Change parametric range of the curve
+  GEOMAPI_EXPORT
+  void setRange(const double& theFirst, const double& theLast);
+
   /// Returns range of parameter on the curve
   GEOMAPI_EXPORT
   void getRange(double& theFirst, double& theLast) const;
index 8c5472e93649d10d94fffd67657c34a13ba88cc3..fca55e7932e8d8e7ae3275090816366d4d8397ce 100644 (file)
@@ -28,6 +28,8 @@
   #include "GeomAPI_Ax2.h"
   #include "GeomAPI_Ax3.h"
   #include "GeomAPI_Box.h"
+  #include "GeomAPI_BSpline.h"
+  #include "GeomAPI_BSpline2d.h"
   #include "GeomAPI_Circ.h"
   #include "GeomAPI_Circ2d.h"
   #include "GeomAPI_Cone.h"
   #include <memory>
   #include <string>
 
+  template<class T1, class T2>
+  std::shared_ptr<T1> shared_ptr_cast(std::shared_ptr<T2> theObject)
+  {
+    return std::dynamic_pointer_cast<T1>(theObject);
+  }
+
 #endif /* SRC_GEOMAPI_GEOMAPI_SWIG_H_ */
index 9bfc1a50a20364ac3d8c4af28ca2c23fb34d9bd8..3badacb17afb6aa9eab7ed363672246731558225 100644 (file)
 #include <GeomAlgoAPI_EdgeBuilder.h>
 
 #include <GeomAPI_Ax2.h>
+#include <GeomAPI_Ax3.h>
+#include <GeomAPI_BSpline2d.h>
 #include <GeomAPI_Ellipse.h>
+#include <GeomAPI_Pnt2d.h>
 
 #include <gp_Pln.hxx>
 #include <BRepBuilderAPI_MakeEdge.hxx>
 #include <TopoDS_Face.hxx>
 #include <TopoDS.hxx>
 #include <BRep_Tool.hxx>
+#include <Geom2d_BSplineCurve.hxx>
 #include <Geom_Plane.hxx>
 #include <Geom_CylindricalSurface.hxx>
 #include <Geom_RectangularTrimmedSurface.hxx>
+#include <GeomLib.hxx>
 
 #include <gp_Ax2.hxx>
 #include <gp_Circ.hxx>
@@ -266,3 +271,31 @@ std::shared_ptr<GeomAPI_Edge> GeomAlgoAPI_EdgeBuilder::ellipticArc(
   aRes->setImpl(new TopoDS_Shape(anEdge));
   return aRes;
 }
+
+GeomEdgePtr GeomAlgoAPI_EdgeBuilder::bsplineOnPlane(
+    const std::shared_ptr<GeomAPI_Ax3>& thePlane,
+    const std::list<GeomPnt2dPtr>& thePoles,
+    const std::list<double>& theWeights,
+    const std::list<double>& theKnots,
+    const std::list<int>& theMults,
+    const int theDegree,
+    const bool thePeriodic)
+{
+  std::shared_ptr<GeomAPI_BSpline2d> aBSplineCurve(
+      new GeomAPI_BSpline2d(theDegree, thePoles, theWeights, theKnots, theMults, thePeriodic));
+  return bsplineOnPlane(thePlane, aBSplineCurve);
+}
+
+GeomEdgePtr GeomAlgoAPI_EdgeBuilder::bsplineOnPlane(
+    const std::shared_ptr<GeomAPI_Ax3>& thePlane,
+    const std::shared_ptr<GeomAPI_BSpline2d>& theCurve)
+{
+  Handle(Geom_Curve) aCurve3D = GeomLib::To3d(thePlane->impl<gp_Ax3>().Ax2(),
+                                              theCurve->impl<Handle_Geom2d_BSplineCurve>());
+
+  BRepBuilderAPI_MakeEdge anEdgeBuilder(aCurve3D);
+  GeomEdgePtr aRes(new GeomAPI_Edge);
+  TopoDS_Edge anEdge = anEdgeBuilder.Edge();
+  aRes->setImpl(new TopoDS_Shape(anEdge));
+  return aRes;
+}
index 245e97f05888c0795f2d21e37dde3f242ae10e05..3b6a8c760659b80f6d09485347d7e407a575f31c 100644 (file)
 #include <GeomAPI_Lin.h>
 #include <GeomAPI_Circ.h>
 #include <memory>
+#include <vector>
+
+class GeomAPI_Ax3;
+class GeomAPI_BSpline2d;
 
 /**\class GeomAlgoAPI_EdgeBuilder
  * \ingroup DataAlgo
@@ -89,6 +93,19 @@ class GEOMALGOAPI_EXPORT GeomAlgoAPI_EdgeBuilder
       const double                        theMinorRadius,
       const std::shared_ptr<GeomAPI_Pnt>& theStart,
       const std::shared_ptr<GeomAPI_Pnt>& theEnd);
+
+  /// Creates planar B-spline edge
+  static GeomEdgePtr bsplineOnPlane(const std::shared_ptr<GeomAPI_Ax3>& thePlane,
+                                    const std::list<std::shared_ptr<GeomAPI_Pnt2d> >& thePoles,
+                                    const std::list<double>& theWeights,
+                                    const std::list<double>& theKnots,
+                                    const std::list<int>& theMults,
+                                    const int theDegree,
+                                    const bool thePeriodic);
+
+  /// Creates planar B-spline edge
+  static GeomEdgePtr bsplineOnPlane(const std::shared_ptr<GeomAPI_Ax3>& thePlane,
+                                    const std::shared_ptr<GeomAPI_BSpline2d>& theCurve);
 };
 
 #endif
index d6ef692308a1190888a64974b3f053528bf820f4..2b281396cd48ffbf6574e0eb6d342f046839072a 100644 (file)
@@ -24,12 +24,14 @@ SET(PROJECT_HEADERS
     GeomData_Point.h
     GeomData_Dir.h
     GeomData_Point2D.h
+    GeomData_Point2DArray.h
 )
 
 SET(PROJECT_SOURCES
     GeomData_Point.cpp
     GeomData_Dir.cpp
     GeomData_Point2D.cpp
+    GeomData_Point2DArray.cpp
 )
 
 SET(PROJECT_LIBRARIES
diff --git a/src/GeomData/GeomData_Point2DArray.cpp b/src/GeomData/GeomData_Point2DArray.cpp
new file mode 100644 (file)
index 0000000..198f916
--- /dev/null
@@ -0,0 +1,120 @@
+// Copyright (C) 2019-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 "GeomData_Point2DArray.h"
+
+#include <GeomAPI_Pnt2d.h>
+
+#include <ModelAPI_Data.h>
+#include <ModelAPI_Events.h>
+#include <ModelAPI_Expression.h>
+#include <ModelAPI_Feature.h>
+
+#include <cassert>
+
+GeomData_Point2DArray::GeomData_Point2DArray(TDF_Label& theLabel)
+  : myLab(theLabel)
+{
+  reinit();
+}
+
+void GeomData_Point2DArray::reinit()
+{
+  // check the attribute could be already presented in this doc (after load document)
+  myIsInitialized = myLab.FindAttribute(TDataStd_RealArray::GetID(), myArray) == Standard_True;
+}
+
+bool GeomData_Point2DArray::assign(std::shared_ptr<GeomDataAPI_Point2DArray> theOther)
+{
+  std::shared_ptr<GeomData_Point2DArray> anOther =
+      std::dynamic_pointer_cast<GeomData_Point2DArray>(theOther);
+  if (!anOther)
+    return false;
+
+  setSize(anOther->size());
+  myArray->ChangeArray(anOther->myArray->Array(), false);
+  owner()->data()->sendAttributeUpdated(this);
+
+  return true;
+}
+
+int GeomData_Point2DArray::size()
+{
+  if (myArray.IsNull() || !myArray->IsValid()) {
+    // this could be on undo and then redo creation of the attribute
+    // in result creation it may be uninitialized
+    myIsInitialized = myLab.FindAttribute(TDataStd_RealArray::GetID(), myArray) == Standard_True;
+  }
+  // checking the validity because attribute (as a field) may be presented,
+  // but without label: it is undoed
+  return (myArray.IsNull() || !myArray->IsValid()) ? 0 : myArray->Length() / 2;
+}
+
+void GeomData_Point2DArray::setSize(const int theSize)
+{
+  int aValuesSize = 2 * theSize;
+  if (myArray.IsNull() || !myArray->IsValid()) { // create array if it is not done yet
+    if (aValuesSize != 0) { // if size is zero, nothing to do (null array means there is no array)
+      myArray = TDataStd_RealArray::Set(myLab, 0, aValuesSize - 1);
+      owner()->data()->sendAttributeUpdated(this);
+    }
+  }
+  else { // reset the old array
+    if (aValuesSize) {
+      if (aValuesSize != myArray->Length()) { // old data is kept in the new array
+        Handle(TColStd_HArray1OfReal) aNewArray = new TColStd_HArray1OfReal(0, aValuesSize - 1);
+        for (int anIndex = 0; anIndex < aValuesSize && anIndex <= myArray->Upper(); ++anIndex)
+          aNewArray->SetValue(anIndex, myArray->Value(anIndex));
+        myArray->ChangeArray(aNewArray);
+        owner()->data()->sendAttributeUpdated(this);
+      }
+    }
+    else { // size is zero => array must be erased
+      if (!myArray.IsNull()) {
+        myArray.Nullify();
+        myLab.ForgetAttribute(TDataStd_RealArray::GetID());
+        owner()->data()->sendAttributeUpdated(this);
+      }
+    }
+  }
+}
+
+void GeomData_Point2DArray::setPnt(const int theIndex,
+                                   const double theX,
+                                   const double theY)
+{
+  if (myArray->Value(2 * theIndex) != theX || myArray->Value(2 * theIndex + 1) != theY) {
+    myArray->SetValue(2 * theIndex, theX);
+    myArray->SetValue(2 * theIndex + 1, theY);
+    owner()->data()->sendAttributeUpdated(this);
+  }
+}
+
+void GeomData_Point2DArray::setPnt(const int theIndex, const GeomPnt2dPtr& thePoint)
+{
+  setPnt(theIndex, thePoint->x(), thePoint->y());
+}
+
+GeomPnt2dPtr GeomData_Point2DArray::pnt(const int theIndex)
+{
+  GeomPnt2dPtr aPoint;
+  if (theIndex >= 0 && theIndex * 2 < myArray->Length())
+    aPoint.reset(new GeomAPI_Pnt2d(myArray->Value(2 * theIndex), myArray->Value(2 * theIndex + 1)));
+  return aPoint;
+}
diff --git a/src/GeomData/GeomData_Point2DArray.h b/src/GeomData/GeomData_Point2DArray.h
new file mode 100644 (file)
index 0000000..8f3f739
--- /dev/null
@@ -0,0 +1,70 @@
+// Copyright (C) 2019-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 GeomData_Point2DArray_H_
+#define GeomData_Point2DArray_H_
+
+#include "GeomData.h"
+
+#include <GeomDataAPI_Point2DArray.h>
+
+#include <TDataStd_RealArray.hxx>
+#include <TDF_Label.hxx>
+
+/** \class GeomData_Point2DArray
+ *  \ingroup DataModel
+ *  \brief Attribute that contains an array of 2D points.
+ */
+class GeomData_Point2DArray : public GeomDataAPI_Point2DArray
+{
+  TDF_Label myLab; ///< the main label of the attribute
+  Handle_TDataStd_RealArray myArray; ///< array that keeps all coordinates of the points
+
+public:
+  /// Copy values from another array
+  /// \return \c true if the copy was successful
+  GEOMDATA_EXPORT virtual bool assign(std::shared_ptr<GeomDataAPI_Point2DArray> theOther);
+
+  /// Returns the size of the array (zero means that it is empty)
+  GEOMDATA_EXPORT virtual int size();
+
+  /// Sets the new size of the array. The previous data is erased.
+  GEOMDATA_EXPORT virtual void setSize(const int theSize);
+
+  /// Defines the value of the array by index [0; size-1]
+  GEOMDATA_EXPORT virtual void setPnt(const int theIndex,
+                                      const double theX, const double theY);
+
+  /// Defines the value of the array by index [0; size-1]
+  GEOMDATA_EXPORT virtual void setPnt(const int theIndex,
+                                      const std::shared_ptr<GeomAPI_Pnt2d>& thePoint);
+
+  /// Returns the value by the index
+  GEOMDATA_EXPORT virtual std::shared_ptr<GeomAPI_Pnt2d> pnt(const int theIndex);
+
+protected:
+  /// Initializes attributes
+  GEOMDATA_EXPORT GeomData_Point2DArray(TDF_Label& theLabel);
+  /// Reinitializes the internal state of the attribute (may be needed on undo/redo, abort, etc)
+  virtual void reinit();
+
+  friend class Model_Data;
+};
+
+#endif
index ecc087c26d2dfbc9722f0fbc67ecb5d0e8fd2b28..fde2672d6975ce651a6c4ed51e2a6264f81e6289 100644 (file)
@@ -27,12 +27,14 @@ SET(PROJECT_HEADERS
     GeomDataAPI_Point.h
     GeomDataAPI_Dir.h
     GeomDataAPI_Point2D.h
+    GeomDataAPI_Point2DArray.h
 )
 
 SET(PROJECT_SOURCES
     GeomDataAPI_Point.cpp
     GeomDataAPI_Dir.cpp
     GeomDataAPI_Point2D.cpp
+    GeomDataAPI_Point2DArray.cpp
 )
 
 SET(PROJECT_LIBRARIES
index 3fd23f6c8a11db58ea4ae7ec33a7e287f1a3fd3b..1a6fd286b46e8ac208e987991ab8f352d9288c25 100644 (file)
 %shared_ptr(GeomDataAPI_Point)
 %shared_ptr(GeomDataAPI_Dir)
 %shared_ptr(GeomDataAPI_Point2D)
+%shared_ptr(GeomDataAPI_Point2DArray)
 
 // all supported interfaces
 %include "GeomDataAPI_Point.h"
 %include "GeomDataAPI_Dir.h"
 %include "GeomDataAPI_Point2D.h"
+%include "GeomDataAPI_Point2DArray.h"
 
 template<class T> std::shared_ptr<T> castTo(std::shared_ptr<ModelAPI_Attribute> theObject);
 %template(geomDataAPI_Point) castTo<GeomDataAPI_Point>;
 %template(geomDataAPI_Dir) castTo<GeomDataAPI_Dir>;
 %template(geomDataAPI_Point2D) castTo<GeomDataAPI_Point2D>;
+%template(geomDataAPI_Point2DArray) castTo<GeomDataAPI_Point2DArray>;
diff --git a/src/GeomDataAPI/GeomDataAPI_Point2DArray.cpp b/src/GeomDataAPI/GeomDataAPI_Point2DArray.cpp
new file mode 100644 (file)
index 0000000..fcd9a8e
--- /dev/null
@@ -0,0 +1,33 @@
+// Copyright (C) 2019-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 <GeomDataAPI_Point2DArray.h>
+
+std::string GeomDataAPI_Point2DArray::attributeType()
+{
+  return typeId();
+}
+
+GeomDataAPI_Point2DArray::GeomDataAPI_Point2DArray()
+{
+}
+
+GeomDataAPI_Point2DArray::~GeomDataAPI_Point2DArray()
+{
+}
diff --git a/src/GeomDataAPI/GeomDataAPI_Point2DArray.h b/src/GeomDataAPI/GeomDataAPI_Point2DArray.h
new file mode 100644 (file)
index 0000000..1976b16
--- /dev/null
@@ -0,0 +1,74 @@
+// Copyright (C) 2019-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 GeomDataAPI_Point2DArray_H_
+#define GeomDataAPI_Point2DArray_H_
+
+#include <GeomDataAPI.h>
+#include <ModelAPI_Attribute.h>
+
+class GeomAPI_Pnt2d;
+
+/**\class GeomDataAPI_Point2DArray
+ * \ingroup DataModel
+ * \brief Attribute that contains array of 2D point coordinates.
+ */
+
+class GeomDataAPI_Point2DArray : public ModelAPI_Attribute
+{
+public:
+  /// Copy values from another array
+  /// \return \c true if the copy was successful
+  GEOMDATAAPI_EXPORT virtual bool assign(std::shared_ptr<GeomDataAPI_Point2DArray> theOther) = 0;
+
+  /// Returns the size of the array (zero means that it is empty)
+  GEOMDATAAPI_EXPORT virtual int size() = 0;
+
+  /// Sets the new size of the array. The previous data is erased.
+  GEOMDATAAPI_EXPORT virtual void setSize(const int theSize) = 0;
+
+  /// Defines the value of the array by index [0; size-1]
+  GEOMDATAAPI_EXPORT virtual void setPnt(const int theIndex,
+                                         const double theX, const double theY) = 0;
+
+  /// Defines the value of the array by index [0; size-1]
+  GEOMDATAAPI_EXPORT virtual void setPnt(const int theIndex,
+                                         const std::shared_ptr<GeomAPI_Pnt2d>& thePoint) = 0;
+
+  /// Returns the value by the index
+  GEOMDATAAPI_EXPORT virtual std::shared_ptr<GeomAPI_Pnt2d> pnt(const int theIndex) = 0;
+
+  /// Returns the type of this class of attributes
+  static std::string typeId()
+  {
+    return std::string("Point2DArray");
+  }
+
+  /// Returns the type of this class of attributes, not static method
+  GEOMDATAAPI_EXPORT virtual std::string attributeType();
+
+protected:
+  /// Objects are created for features automatically
+  GEOMDATAAPI_EXPORT GeomDataAPI_Point2DArray();
+  GEOMDATAAPI_EXPORT virtual ~GeomDataAPI_Point2DArray();
+};
+
+typedef std::shared_ptr<GeomDataAPI_Point2DArray> AttributePoint2DArrayPtr;
+
+#endif
index b23de60169fc52bbaff275f51837ed215f67d7c8..9cd94e9f3229cd77e040c90643243e517e71bd3d 100644 (file)
@@ -28,6 +28,7 @@
   #include "GeomDataAPI_Point.h"
   #include "GeomDataAPI_Dir.h"
   #include "GeomDataAPI_Point2D.h"
+  #include "GeomDataAPI_Point2DArray.h"
 
   #include <memory>
   #include <string>
index db780eac7da4aa4477ff09c591ac2eceaeb68947..5ce42aef320496eaaeb042ad1a6d79f22b616b7e 100644 (file)
 
 void Model_AttributeRefAttrList::append(ObjectPtr theObject)
 {
-  std::shared_ptr<Model_Data> aData = std::dynamic_pointer_cast<Model_Data>(theObject->data());
-  myRef->Append(aData->label().Father());  // store label of the object
+  TDF_Label aLabel;
+  if (theObject) {
+    std::shared_ptr<Model_Data> aData = std::dynamic_pointer_cast<Model_Data>(theObject->data());
+    aLabel = aData->label().Father();
+  }
+
+  myRef->Append(aLabel); // store label of the object
   myIDs->Append(""); // for the object store an empty string
   // do it before the transaction finish to make just created/removed objects know dependencies
   // and reference from composite feature is removed automatically
@@ -306,7 +311,9 @@ void Model_AttributeRefAttrList::remove(const std::set<int>& theIndices)
         myIDs->Append(anIDIter.Value());
       } else { // found, so need to update the dependencies
         aOneisDeleted = true;
-        ObjectPtr anObj = aDoc->objects()->object(aRefIter.Value());
+        ObjectPtr anObj;
+        if (!aRefIter.Value().IsNull())
+          anObj = aDoc->objects()->object(aRefIter.Value());
         if (anObj.get()) {
           REMOVE_BACK_REF(anObj);
         }
index e5641930e1b1253ab05e0a83f5afbd1ab8c34137..2a25c5136913462dd8887e5249277880e80ac42a 100644 (file)
 
 #include <GeomDataAPI_Point.h>
 #include <GeomDataAPI_Point2D.h>
+#include <GeomDataAPI_Point2DArray.h>
 
 #include <GeomData_Point.h>
 #include <GeomData_Point2D.h>
+#include <GeomData_Point2DArray.h>
 #include <GeomData_Dir.h>
 #include <Events_Loop.h>
 #include <Events_InfoMessage.h>
@@ -217,7 +219,10 @@ AttributePtr Model_Data::addAttribute(
     }
     anAttribute->myIsInitialized = anAllInitialized;
     anAttr = anAttribute;
+  } else if (theAttrType == GeomData_Point2DArray::typeId()) {
+    anAttr = new GeomData_Point2DArray(anAttrLab);
   }
+
   if (anAttr) {
     aResult = std::shared_ptr<ModelAPI_Attribute>(anAttr);
     myAttrs[theID] = std::pair<AttributePtr, int>(aResult, anAttrIndex);
index f1bcd1e26707e11949f962c8a4b94f87c7236da8..92474383c6e743179f0c96e25b445fe2cfa95977 100644 (file)
@@ -335,10 +335,12 @@ void ModelAPI_ObjectMovedMessage::setMovedObject(const ObjectPtr& theMovedObject
   myMovedAttribute = AttributePtr();
 }
 
-void ModelAPI_ObjectMovedMessage::setMovedAttribute(const AttributePtr& theMovedAttribute)
+void ModelAPI_ObjectMovedMessage::setMovedAttribute(const AttributePtr& theMovedAttribute,
+                                                    const int thePointIndex)
 {
   myMovedAttribute = theMovedAttribute;
   myMovedObject = ObjectPtr();
+  myMovedPointIndex = thePointIndex;
 }
 
 void ModelAPI_ObjectMovedMessage::setOriginalPosition(double theX, double theY)
index 0b250ebd53e24493c8f0b4b93e42841d71b53aa4..02fdadb0936d97a68e6167a9aa5e4476b4364ac1 100644 (file)
@@ -486,6 +486,7 @@ class ModelAPI_ObjectMovedMessage : public Events_Message
 {
   ObjectPtr myMovedObject;
   AttributePtr myMovedAttribute;
+  int myMovedPointIndex;
 
   std::shared_ptr<GeomAPI_Pnt2d> myOriginalPosition;
   std::shared_ptr<GeomAPI_Pnt2d> myCurrentPosition;
@@ -496,7 +497,10 @@ public:
   /// Set object which is being moved (if the message already contains attribute it will be cleared)
   MODELAPI_EXPORT void setMovedObject(const ObjectPtr& theMovedObject);
   /// Set attribute which is being moved (if the message already contains object it will be cleared)
-  MODELAPI_EXPORT void setMovedAttribute(const AttributePtr& theMovedAttribute);
+  /// \param[in] theMovedAttribute moved attribute
+  /// \param[in] thePointIndex     index of the point if the moved attribute is an array of points
+  MODELAPI_EXPORT void setMovedAttribute(const AttributePtr& theMovedAttribute,
+                                         const int thePointIndex = -1);
 
   /// Return moved object
   ObjectPtr movedObject() const
@@ -504,6 +508,9 @@ public:
   /// Return moved attribute
   AttributePtr movedAttribute() const
   { return myMovedAttribute; }
+  /// Return index of the moved point
+  int movedPointIndex() const
+  { return myMovedPointIndex; }
 
   /// Set original mouse position
   MODELAPI_EXPORT void setOriginalPosition(double theX, double theY);
index de4312c62408c486f42c0d6e4831818e82c0cfd0..0c4f99e0367fe82f98ce22dd3d28139b6bf6a064 100644 (file)
   #include <memory>
   #include <string>
 
-  template<class T1, class T2>
-  std::shared_ptr<T1> shared_ptr_cast(std::shared_ptr<T2> theObject)
-  {
-    return std::dynamic_pointer_cast<T1>(theObject);
-  }
-
 #endif /* SRC_MODELAPI_MODELAPI_SWIG_H_ */
index 72971cc015fdb391b1226a44f2c303097ff74f1d..30fb144b34a2ac9c177abcfb15c99bc4067aa1d0 100644 (file)
   }
 }
 
+
+%typemap(in) const std::list<int> & (std::list<int> temp) {
+  int newmem = 0;
+  if (PySequence_Check($input)) {
+    for (Py_ssize_t i = 0; i < PySequence_Size($input); ++i) {
+      PyObject * item = PySequence_GetItem($input, i);
+      if (PyLong_Check(item)) {
+        temp.push_back((int)PyLong_AsLong(item));
+      } else {
+        PyErr_SetString(PyExc_TypeError, "argument must integet value.");
+        return NULL;
+      }
+      Py_DECREF(item);
+    }
+    $1 = &temp;
+  } else {
+    PyErr_SetString(PyExc_ValueError, "argument must be a tuple of integer values.");
+    return NULL;
+  }
+}
+
+%typecheck(SWIG_TYPECHECK_POINTER) std::list<int>, const std::list<int>& {
+  int newmem = 0;
+  if (PySequence_Check($input)) {
+    for (Py_ssize_t i = 0; i < PySequence_Size($input); ++i) {
+      PyObject * item = PySequence_GetItem($input, i);
+      if (PyLong_Check(item)) {
+        $1 = 1;
+      } else {
+        $1 = 0;
+        break;
+      }
+      Py_DECREF(item);
+    }
+  } else {
+    $1 = 0;
+  }
+}
+
+
 %typemap(in) const std::list<double> & (std::list<double> temp) {
-  double * temp_attribute;
   int newmem = 0;
   if (PyTuple_Check($input)) {
     for (Py_ssize_t i = 0; i < PyTuple_Size($input); ++i) {
 }
 
 %typecheck(SWIG_TYPECHECK_POINTER) std::list<double>, const std::list<double>& {
-  double * temp_object;
-  std::shared_ptr<ModelHighAPI_Interface> * temp_interface;
   int newmem = 0;
   if (PyTuple_Check($input)) {
     for (Py_ssize_t i = 0; i < PyTuple_Size($input); ++i) {
   }
 }
 
+
+%typemap(in) const std::list<ModelHighAPI_Double> & (std::list<ModelHighAPI_Double> temp) {
+  ModelHighAPI_Double * temp_double;
+  if (PySequence_Check($input)) {
+    for (Py_ssize_t i = 0; i < PySequence_Size($input); ++i) {
+      PyObject * item = PySequence_GetItem($input, i);
+      if (PyFloat_Check(item) || PyLong_Check(item)) {
+        temp.push_back(ModelHighAPI_Double(PyFloat_AsDouble(item)));
+      } else if (PyUnicode_Check(item)) {
+        temp.push_back(ModelHighAPI_Double(PyUnicode_AsUTF8(item)));
+      } else if ((SWIG_ConvertPtr(item, (void **)&temp_double, $1_descriptor, SWIG_POINTER_EXCEPTION)) == 0) {
+        temp.push_back(*temp_double);
+      } else {
+        PyErr_SetString(PyExc_ValueError, "argument must be a list of ModelHighAPI_Double, float, int or string.");
+        return NULL;
+      }
+      Py_DECREF(item);
+    }
+    $1 = &temp;
+  } else {
+    PyErr_SetString(PyExc_ValueError, "argument must be a list of ModelHighAPI_Double, float, int or string.");
+    return NULL;
+  }
+}
+
+%typecheck(SWIG_TYPECHECK_POINTER) std::list<ModelHighAPI_Double>, const std::list<ModelHighAPI_Double> & {
+  if (PySequence_Check($input)) {
+    $1 = 1;
+    for (Py_ssize_t i = 0; i < PySequence_Size($input) && $1; ++i) {
+      PyObject * item = PySequence_GetItem($input, i);
+      $1 = ((PyFloat_Check(item) || PyLong_Check(item) || PyUnicode_Check(item)) && !PyBool_Check(item)) ? 1 : 0;
+      Py_DECREF(item);
+    }
+  } else {
+    $1 = 0;
+  }
+}
+
+
+%typemap(in) const std::list<ModelHighAPI_Integer> & (std::list<ModelHighAPI_Integer> temp) {
+  ModelHighAPI_Integer * temp_int;
+  if (PySequence_Check($input)) {
+    for (Py_ssize_t i = 0; i < PySequence_Size($input); ++i) {
+      PyObject * item = PySequence_GetItem($input, i);
+      if (PyLong_Check(item)) {
+        temp.push_back(ModelHighAPI_Integer(PyLong_AsLong(item)));
+      } else if (PyUnicode_Check(item)) {
+        temp.push_back(ModelHighAPI_Integer(PyUnicode_AsUTF8(item)));
+      } else if ((SWIG_ConvertPtr(item, (void **)&temp_int, $1_descriptor, SWIG_POINTER_EXCEPTION)) == 0) {
+        temp.push_back(*temp_int);
+      } else {
+        PyErr_SetString(PyExc_ValueError, "argument must be a list of ModelHighAPI_Integer, int or string.");
+        return NULL;
+      }
+      Py_DECREF(item);
+    }
+    $1 = &temp;
+  } else {
+    PyErr_SetString(PyExc_ValueError, "argument must be a list of ModelHighAPI_Integer, int or string.");
+    return NULL;
+  }
+}
+
+%typecheck(SWIG_TYPECHECK_POINTER) std::list<ModelHighAPI_Integer>, const std::list<ModelHighAPI_Integer> & {
+  if (PySequence_Check($input)) {
+    $1 = 1;
+    for (Py_ssize_t i = 0; i < PySequence_Size($input) && $1; ++i) {
+      PyObject * item = PySequence_GetItem($input, i);
+      $1 = ((PyLong_Check(item) || PyUnicode_Check(item)) && !PyBool_Check(item)) ? 1 : 0;
+      Py_DECREF(item);
+    }
+  } else {
+    $1 = 0;
+  }
+}
+
+
 // all supported interfaces
 %include "ModelHighAPI_Double.h"
 %include "ModelHighAPI_Dumper.h"
index 35370c8190295fb7bcbdc02229ce90c1c06dafd6..33c52647d98ef1009250ab14075a844c7f387869 100644 (file)
@@ -46,6 +46,12 @@ ModelHighAPI_Double::~ModelHighAPI_Double()
 {
 }
 
+double ModelHighAPI_Double::value() const
+{
+  // needed for array of double, which supports no text
+  return myDouble;
+}
+
 //--------------------------------------------------------------------------------------
 void ModelHighAPI_Double::fillAttribute(
     const std::shared_ptr<ModelAPI_AttributeDouble> & theAttribute) const
index 6861a7c0a3f55b9a5898acbc3ea61556841f541b..9fe5df5369c272b49df1379bd0ba261fd5739f10 100644 (file)
@@ -60,6 +60,9 @@ public:
     const ModelHighAPI_Double & theY,
     const ModelHighAPI_Double & theZ) const;
 
+  /// Value of the attribute
+  MODELHIGHAPI_EXPORT double value() const;
+
 private:
   enum VariantType { VT_DOUBLE, VT_STRING } myVariantType;
   double myDouble;
index 9df987e6101f4b342e2a8d7c96f457d38f27448c..1d655bbdfc93010dd1527512ef33200f9c9e50df 100644 (file)
@@ -22,6 +22,7 @@
 #include <Config_PropManager.h>
 
 #include <GeomAPI_Pnt.h>
+#include <GeomAPI_Pnt2d.h>
 #include <GeomAPI_Dir.h>
 #include <GeomAPI_ShapeExplorer.h>
 #include <GeomAPI_ShapeIterator.h>
 #include <GeomDataAPI_Dir.h>
 #include <GeomDataAPI_Point.h>
 #include <GeomDataAPI_Point2D.h>
+#include <GeomDataAPI_Point2DArray.h>
 
 #include <ModelAPI_AttributeBoolean.h>
 #include <ModelAPI_AttributeDouble.h>
+#include <ModelAPI_AttributeDoubleArray.h>
 #include <ModelAPI_AttributeIntArray.h>
 #include <ModelAPI_AttributeInteger.h>
 #include <ModelAPI_AttributeRefAttr.h>
@@ -1082,6 +1085,43 @@ ModelHighAPI_Dumper& ModelHighAPI_Dumper::operator<<(
   return *this;
 }
 
+ModelHighAPI_Dumper& ModelHighAPI_Dumper::operator<<(
+  const std::shared_ptr<GeomDataAPI_Point2DArray>& thePointArray)
+{
+  static const int aThreshold = 4;
+  static bool aDumpAsIs = false;
+  static std::string aSeparator = "";
+  // if number of elements in the list if greater than a threshold,
+  // dump it in a separate line with specific name
+  int aSize = thePointArray->size();
+  if (aDumpAsIs || aSize <= aThreshold) {
+    *myDumpStorage << "[";
+    GeomPnt2dPtr aPoint = thePointArray->pnt(0);
+    *myDumpStorage << "(" << aPoint->x() << ", " << aPoint->y() << ")";
+    for (int anIndex = 1; anIndex < aSize; ++anIndex) {
+      aPoint = thePointArray->pnt(anIndex);
+      *myDumpStorage << "," << aSeparator << " (" << aPoint->x() << ", " << aPoint->y() << ")";
+    }
+    *myDumpStorage << aSeparator << "]";
+  }
+  else {
+    // name of list
+    FeaturePtr anOwner = ModelAPI_Feature::feature(thePointArray->owner());
+    std::string aListName = name(anOwner) + "_" + thePointArray->id();
+    // reserve dumped buffer and store list "as is"
+    myDumpStorage->reserveBuffer();
+    aDumpAsIs = true;
+    aSeparator = std::string("\n") + std::string(aListName.size() + 3, ' ');
+    *this << aListName << " = " << thePointArray << "\n";
+    aDumpAsIs = false;
+    aSeparator = "";
+    // append reserved data to the end of the current buffer
+    myDumpStorage->restoreReservedBuffer();
+    *myDumpStorage << aListName;
+  }
+  return *this;
+}
+
 ModelHighAPI_Dumper& ModelHighAPI_Dumper::operator<<(
     const std::shared_ptr<ModelAPI_AttributeBoolean>& theAttrBool)
 {
@@ -1100,6 +1140,20 @@ ModelHighAPI_Dumper& ModelHighAPI_Dumper::operator<<(
   return *this;
 }
 
+ModelHighAPI_Dumper& ModelHighAPI_Dumper::operator<<(
+    const std::shared_ptr<ModelAPI_AttributeIntArray>& theArray)
+{
+  *myDumpStorage << "[";
+  int aSize = theArray->size();
+  if (aSize > 0) {
+    *myDumpStorage << theArray->value(0);
+    for (int anIndex = 1; anIndex < aSize; ++anIndex)
+      *myDumpStorage << ", " << theArray->value(anIndex);
+  }
+  *myDumpStorage << "]";
+  return *this;
+}
+
 ModelHighAPI_Dumper& ModelHighAPI_Dumper::operator<<(
     const std::shared_ptr<ModelAPI_AttributeDouble>& theAttrReal)
 {
@@ -1111,6 +1165,20 @@ ModelHighAPI_Dumper& ModelHighAPI_Dumper::operator<<(
   return *this;
 }
 
+ModelHighAPI_Dumper& ModelHighAPI_Dumper::operator<<(
+  const std::shared_ptr<ModelAPI_AttributeDoubleArray>& theArray)
+{
+  *myDumpStorage << "[";
+  int aSize = theArray->size();
+  if (aSize > 0) {
+    *myDumpStorage << theArray->value(0);
+    for (int anIndex = 1; anIndex < aSize; ++anIndex)
+      *myDumpStorage << ", " << theArray->value(anIndex);
+  }
+  *myDumpStorage << "]";
+  return *this;
+}
+
 ModelHighAPI_Dumper& ModelHighAPI_Dumper::operator<<(
     const std::shared_ptr<ModelAPI_AttributeString>& theAttrStr)
 {
index 2bbc294357441c7a74ef484ec8124b9ce3b68811..90242c3aac90f0a2fd0a2f86b9ff6a1d05d13af5 100644 (file)
@@ -36,10 +36,13 @@ class GeomAPI_Dir;
 class GeomDataAPI_Dir;
 class GeomDataAPI_Point;
 class GeomDataAPI_Point2D;
+class GeomDataAPI_Point2DArray;
 
 class ModelAPI_Attribute;
 class ModelAPI_AttributeBoolean;
 class ModelAPI_AttributeDouble;
+class ModelAPI_AttributeDoubleArray;
+class ModelAPI_AttributeIntArray;
 class ModelAPI_AttributeInteger;
 class ModelAPI_AttributeRefAttr;
 class ModelAPI_AttributeRefAttrList;
@@ -262,6 +265,9 @@ public:
   /// "X, Y"
   MODELHIGHAPI_EXPORT
   ModelHighAPI_Dumper& operator<<(const std::shared_ptr<GeomDataAPI_Point2D>& thePoint);
+  /// Dump GeomDataAPI_Point2DArray as a list of 2D points
+  MODELHIGHAPI_EXPORT
+  ModelHighAPI_Dumper& operator<<(const std::shared_ptr<GeomDataAPI_Point2DArray>& thePointArray);
 
   /// Dump AttributeBoolean
   MODELHIGHAPI_EXPORT
@@ -269,9 +275,15 @@ public:
   /// Dump AttributeInteger
   MODELHIGHAPI_EXPORT
   ModelHighAPI_Dumper& operator<<(const std::shared_ptr<ModelAPI_AttributeInteger>& theAttrInt);
+  /// Dump AttributeIntArray
+  MODELHIGHAPI_EXPORT
+  ModelHighAPI_Dumper& operator<<(const std::shared_ptr<ModelAPI_AttributeIntArray>& theArray);
   /// Dump AttributeDouble
   MODELHIGHAPI_EXPORT
   ModelHighAPI_Dumper& operator<<(const std::shared_ptr<ModelAPI_AttributeDouble>& theAttrReal);
+  /// Dump AttributeDoubleArray
+  MODELHIGHAPI_EXPORT
+  ModelHighAPI_Dumper& operator<<(const std::shared_ptr<ModelAPI_AttributeDoubleArray>& theArray);
   /// Dump AttributeString
   MODELHIGHAPI_EXPORT
   ModelHighAPI_Dumper& operator<<(const std::shared_ptr<ModelAPI_AttributeString>& theAttrStr);
index 8f1938720491f21997eee2676bc0b331c92a68e9..e2b0e3cbd5d5e86213f1d00eeddc6fdd8ef4e282 100644 (file)
 #include <GeomDataAPI_Dir.h>
 #include <GeomDataAPI_Point.h>
 #include <GeomDataAPI_Point2D.h>
+#include <GeomDataAPI_Point2DArray.h>
 //--------------------------------------------------------------------------------------
 #include <ModelAPI_AttributeBoolean.h>
 #include <ModelAPI_AttributeDocRef.h>
 #include <ModelAPI_AttributeDouble.h>
+#include <ModelAPI_AttributeDoubleArray.h>
 #include <ModelAPI_AttributeIntArray.h>
 #include <ModelAPI_AttributeInteger.h>
 #include <ModelAPI_AttributeRefAttr.h>
@@ -239,6 +241,29 @@ void fillAttribute(const std::list<ModelHighAPI_Integer> & theValue,
     theAttribute->setValue(anIndex, it->intValue()); // use only values, no text support in array
 }
 
+//--------------------------------------------------------------------------------------
+void fillAttribute(const std::list<ModelHighAPI_Double> & theValue,
+                   const std::shared_ptr<ModelAPI_AttributeDoubleArray> & theAttribute)
+{
+  theAttribute->setSize(int(theValue.size()));
+
+  int anIndex = 0;
+  for (auto it = theValue.begin(); it != theValue.end(); ++it, ++anIndex)
+    theAttribute->setValue(anIndex, it->value()); // use only values, no text support in array
+}
+
+//--------------------------------------------------------------------------------------
+void fillAttribute(const std::list<std::shared_ptr<GeomAPI_Pnt2d> > & theValue,
+                   const std::shared_ptr<GeomDataAPI_Point2DArray> & theAttribute)
+{
+  theAttribute->setSize(int(theValue.size()));
+
+  int anIndex = 0;
+  for (auto it = theValue.begin(); it != theValue.end(); ++it, ++anIndex)
+    theAttribute->setPnt(anIndex, *it);
+}
+
+//--------------------------------------------------------------------------------------
 void fillAttribute(const ModelHighAPI_Double & theX,
                    const ModelHighAPI_Double & theY,
                    const ModelHighAPI_Double & theZ,
@@ -247,7 +272,6 @@ void fillAttribute(const ModelHighAPI_Double & theX,
   theX.fillAttribute(theAttribute, theX, theY, theZ);
 }
 
-
 //==================================================================================================
 GeomAPI_Shape::ShapeType shapeTypeByStr(std::string theShapeTypeStr)
 {
index d0fc586ca29eba28141ea0089ac37e382c44a8dd..e46596c3e1a0ea7191348fe68a073c08803d4bac 100644 (file)
@@ -37,9 +37,11 @@ class GeomAPI_Pnt2d;
 class GeomDataAPI_Dir;
 class GeomDataAPI_Point;
 class GeomDataAPI_Point2D;
+class GeomDataAPI_Point2DArray;
 //--------------------------------------------------------------------------------------
 class ModelAPI_AttributeBoolean;
 class ModelAPI_AttributeDouble;
+class ModelAPI_AttributeDoubleArray;
 class ModelAPI_AttributeIntArray;
 class ModelAPI_AttributeInteger;
 class ModelAPI_AttributeRefAttr;
@@ -146,6 +148,14 @@ MODELHIGHAPI_EXPORT
 void fillAttribute(const std::list<ModelHighAPI_Integer> & theValue,
                    const std::shared_ptr<ModelAPI_AttributeIntArray> & theAttribute);
 
+MODELHIGHAPI_EXPORT
+void fillAttribute(const std::list<ModelHighAPI_Double> & theValue,
+                   const std::shared_ptr<ModelAPI_AttributeDoubleArray> & theAttribute);
+
+MODELHIGHAPI_EXPORT
+void fillAttribute(const std::list<std::shared_ptr<GeomAPI_Pnt2d> > & theValue,
+                   const std::shared_ptr<GeomDataAPI_Point2DArray> & theAttribute);
+
 MODELHIGHAPI_EXPORT
 void fillAttribute(const ModelHighAPI_Double & theX,
                    const ModelHighAPI_Double & theY,
index 1e5e8ac33d104300d2501f491ce88acc3f1303fc..706533ff23c87c1159879e60d545200a7a7f48d0 100644 (file)
@@ -29,7 +29,8 @@ ModuleBase_IWidgetCreator::~ModuleBase_IWidgetCreator()
 
 QWidget* ModuleBase_IWidgetCreator::createPanelByType(const std::string& theType,
                                                       QWidget* theParent,
-                                                      const FeaturePtr& theFeature)
+                                                      const FeaturePtr& theFeature,
+                                                      Config_WidgetAPI* theWidgetApi)
 {
   return 0;
 }
index 85219c27cb044e9132d2a9a6b1d75f9ac20796e0..8ffc9a0bfa135038f10ad277478d18e57387d7a8 100644 (file)
@@ -68,10 +68,12 @@ public:
   /// \param theType a panel type
   /// \param theParent a parent widget
   /// \param theFeature a feature modified in the panel
+  /// \param theWidgetApi a low-level API for reading xml definitions of widget
   /// \return created widget or null
   virtual QWidget* createPanelByType(const std::string& theType,
                                      QWidget* theParent,
-                                     const FeaturePtr& theFeature);
+                                     const FeaturePtr& theFeature,
+                                     Config_WidgetAPI* theWidgetApi = 0);
 
   /// Create page by its type
   /// The default implementation is empty
index 6e1122da17a902afd85c27fd2d8c5aff83f2877e..f4d864d51192a7940bc344a825d361473fa562a1 100644 (file)
@@ -255,8 +255,8 @@ Q_OBJECT
   /// \param theFeature a feature object
   /// \param theToStoreValue a value about necessity to store the widget value to the feature
   /// \param isUpdateFlushed a flag if update should be flushed on store value
-  void setFeature(const FeaturePtr& theFeature, const bool theToStoreValue = false,
-                  const bool isUpdateFlushed = true);
+  virtual void setFeature(const FeaturePtr& theFeature, const bool theToStoreValue = false,
+                          const bool isUpdateFlushed = true);
 
   /// Editing mode depends on mode of current operation. This value is defined by it.
   virtual void setEditingMode(bool isEditing) { myIsEditing = isEditing; }
index 946a282a68eb28d825bc9542b1f58ca392f94fbf..b3122659346d5d5ca98be1d0400a1d07b67d51d9 100644 (file)
@@ -99,12 +99,13 @@ bool ModuleBase_WidgetCreatorFactory::hasPanelWidget(const std::string& theType)
 
 QWidget* ModuleBase_WidgetCreatorFactory::createPanelByType(const std::string& theType,
                                                             QWidget* theParent,
-                                                            const FeaturePtr& theFeature)
+                                                            const FeaturePtr& theFeature,
+                                                            Config_WidgetAPI* myWidgetApi)
 {
   QWidget* aPanel = 0;
   if (myPanelToCreator.contains(theType)) {
     WidgetCreatorPtr aCreator = myPanelToCreator[theType];
-    aPanel = aCreator->createPanelByType(theType, theParent, theFeature);
+    aPanel = aCreator->createPanelByType(theType, theParent, theFeature, myWidgetApi);
   }
   return aPanel;
 }
index 66c229a3b459466fb479f5391dc35f20bea06a4a..45cc5b5bb19fb5d6192c1b600400303fbdf987c0 100644 (file)
@@ -65,9 +65,12 @@ class MODULEBASE_EXPORT ModuleBase_WidgetCreatorFactory
   /// \param theType a type
   /// \param theParent a parent widget
   /// \param theFeature a feature to fill the panel
+  /// \param theWidgetApi the widget configuration.
+  ///                     The attribute of the model widget is obtained from XML
   /// \return a created panel or null
   QWidget* createPanelByType(const std::string& theType, QWidget* theParent,
-                             const FeaturePtr& theFeature);
+                             const FeaturePtr& theFeature,
+                             Config_WidgetAPI* theWidgetApi = 0);
 
   /// Returns true if there is a creator, which can make a page by the type
   /// \param theType a type
index 7f51a61a7cebe597e8b0cb4c1b41f1636c814238..ab7b17e103e4882e54c016ac02de634f101e2834 100644 (file)
@@ -161,8 +161,12 @@ void ModuleBase_WidgetFactory::createPanel(ModuleBase_PageBase* thePage,
   std::string aPanelName = myWidgetApi->getProperty(PROPERTY_PANEL_ID);
   if (!aPanelName.empty() && ModuleBase_WidgetCreatorFactory::get()->hasPanelWidget(aPanelName)) {
     QWidget* aPanel = ModuleBase_WidgetCreatorFactory::get()->createPanelByType(aPanelName,
-                                                               thePage->pageWidget(), theFeature);
-    thePage->addWidget(aPanel);
+        thePage->pageWidget(), theFeature, myWidgetApi);
+    ModuleBase_ModelWidget* aModelWdg = dynamic_cast<ModuleBase_ModelWidget*>(aPanel);
+    if (aModelWdg)
+      thePage->addModelWidget(aModelWdg);
+    else
+      thePage->addWidget(aPanel);
     thePage->alignToTop();
   }
 }
@@ -185,8 +189,8 @@ void ModuleBase_WidgetFactory::createWidget(ModuleBase_PageBase* thePage,
         aWidget->setVisible(false);
       }
     }
+    thePage->alignToTop();
   }
-  thePage->alignToTop();
 }
 
 void ModuleBase_WidgetFactory::getAttributeTitle(const std::string& theAttributeId,
@@ -200,6 +204,8 @@ void ModuleBase_WidgetFactory::getAttributeTitle(const std::string& theAttribute
       theTitle =
       QString::fromStdString(myWidgetApi->getProperty(CONTAINER_PAGE_NAME)).toStdString().c_str();
   }
+  else
+    theTitle = theAttributeId;
 }
 
 void ModuleBase_WidgetFactory::getGreedAttribute(std::string& theAttributeId)
index 83789b24f3b56cca7f72f5401121995d3fa231ec..9f87f6c3f8e40628488edfaea216871c781fe977 100644 (file)
@@ -59,8 +59,10 @@ SET(PROJECT_HEADERS
     PartSet_WidgetSketchLabel.h
     PartSet_CenterPrs.h
     PartSet_ExternalPointsMgr.h
-       PartSet_TreeNodes.h
-       PartSet_FieldStepPrs.h
+    PartSet_TreeNodes.h
+    PartSet_FieldStepPrs.h
+    PartSet_WidgetBSplinePoints.h
+    PartSet_BSplineWidget.h
 )
 
 SET(PROJECT_MOC_HEADERS
@@ -78,7 +80,9 @@ SET(PROJECT_MOC_HEADERS
     PartSet_WidgetShapeSelector.h
     PartSet_WidgetSketchCreator.h
     PartSet_WidgetSketchLabel.h
+    PartSet_WidgetBSplinePoints.h
     PartSet_ExternalPointsMgr.h
+    PartSet_BSplineWidget.h
 )
 
 SET(PROJECT_SOURCES
@@ -109,8 +113,10 @@ SET(PROJECT_SOURCES
     PartSet_WidgetSketchLabel.cpp
     PartSet_CenterPrs.cpp
     PartSet_ExternalPointsMgr.cpp
-       PartSet_TreeNodes.cpp
-       PartSet_FieldStepPrs.cpp
+    PartSet_TreeNodes.cpp
+    PartSet_FieldStepPrs.cpp
+    PartSet_WidgetBSplinePoints.cpp
+    PartSet_BSplineWidget.cpp
 )
 
 SET(PROJECT_RESOURCES
diff --git a/src/PartSet/PartSet_BSplineWidget.cpp b/src/PartSet/PartSet_BSplineWidget.cpp
new file mode 100644 (file)
index 0000000..9ca9744
--- /dev/null
@@ -0,0 +1,217 @@
+// Copyright (C) 2019-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 <PartSet_BSplineWidget.h>
+
+#include <SketchPlugin_BSpline.h>
+
+#include <ModuleBase_Tools.h>
+
+#include <ModelAPI_AttributeDoubleArray.h>
+
+#include <GeomDataAPI_Point2DArray.h>
+
+#include <GeomAPI_Pnt2d.h>
+
+#include <QFormLayout>
+#include <QGroupBox>
+#include <QLabel>
+#include <QVBoxLayout>
+#include <QScrollArea>
+#include <QToolButton>
+
+
+PartSet_BSplineWidget::PartSet_BSplineWidget(
+    QWidget* theParent,
+    const Config_WidgetAPI* theData)
+  : ModuleBase_ModelWidget(theParent, theData)
+{
+  QVBoxLayout* aMainLayout = new QVBoxLayout(this);
+  aMainLayout->setContentsMargins(0, 0, 0, 0);
+
+  // GroupBox to keep widgets for B-spline poles and weights
+  myPolesGroupBox = new QGroupBox(tr("Poles and weights"), this);
+  aMainLayout->addWidget(myPolesGroupBox);
+
+  QVBoxLayout* aLayout = new QVBoxLayout(myPolesGroupBox);
+  ModuleBase_Tools::adjustMargins(aLayout);
+
+  myScrollArea = new QScrollArea(myPolesGroupBox);
+  myScrollArea->setWidgetResizable(true);
+  myScrollArea->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+  myScrollArea->setFrameStyle(QFrame::NoFrame);
+  aLayout->addWidget(myScrollArea);
+
+  QWidget* aContainer = new QWidget(myScrollArea);
+  QVBoxLayout* aBoxLay = new QVBoxLayout(aContainer);
+  aBoxLay->setContentsMargins(0, 0, 0, 0);
+
+  // layout of GroupBox
+  myPolesWgt = new QWidget(aContainer);
+  QGridLayout* aGroupLayout = new QGridLayout(myPolesWgt);
+  aGroupLayout->setColumnStretch(1, 1);
+  ModuleBase_Tools::adjustMargins(aGroupLayout);
+
+  restoreValueCustom();
+  aBoxLay->addWidget(myPolesWgt);
+  aBoxLay->addStretch(1);
+  myScrollArea->setWidget(aContainer);
+}
+
+void PartSet_BSplineWidget::setFeature(const FeaturePtr& theFeature,
+                                            const bool theToStoreValue,
+                                            const bool isUpdateFlushed)
+{
+  ModuleBase_ModelWidget::setFeature(theFeature, theToStoreValue, isUpdateFlushed);
+  restoreValueCustom();
+}
+
+void PartSet_BSplineWidget::deactivate()
+{
+  ModuleBase_ModelWidget::deactivate();
+  storeValueCustom();
+}
+
+
+QList<QWidget*> PartSet_BSplineWidget::getControls() const
+{
+  QList<QWidget*> aControls;
+  aControls.append(myScrollArea);
+  return aControls;
+}
+
+void PartSet_BSplineWidget::storePolesAndWeights() const
+{
+  std::shared_ptr<ModelAPI_Data> aData = myFeature->data();
+  AttributeDoubleArrayPtr aWeightsArray = aData->realArray(SketchPlugin_BSpline::WEIGHTS_ID());
+
+  std::list<BSplinePoleWidgets>::const_iterator anIt = myPoles.begin();
+  for (int anIndex = 0; anIt != myPoles.end(); ++anIndex, ++anIt) {
+    aWeightsArray->setValue(anIndex, anIt->myWeight->value());
+  }
+}
+
+bool PartSet_BSplineWidget::storeValueCustom()
+{
+  std::shared_ptr<ModelAPI_Data> aData = myFeature->data();
+  if (!aData || !aData->isValid()) // can be on abort of sketcher element
+    return false;
+
+  AttributeDoubleArrayPtr aWeights = aData->realArray(SketchPlugin_BSpline::WEIGHTS_ID());
+
+  bool isBlocked = blockSignals(true);
+  storePolesAndWeights();
+  ModuleBase_Tools::flushUpdated(myFeature);
+  blockSignals(isBlocked);
+
+  updateObject(myFeature);
+  return true;
+}
+
+bool PartSet_BSplineWidget::restoreValueCustom()
+{
+  if (!myFeature)
+    return false;
+
+  DataPtr aData = myFeature->data();
+
+  AttributePoint2DArrayPtr aPoles = std::dynamic_pointer_cast<GeomDataAPI_Point2DArray>(
+      aData->attribute(SketchPlugin_BSpline::POLES_ID()));
+  AttributeDoubleArrayPtr aWeights = aData->realArray(SketchPlugin_BSpline::WEIGHTS_ID());
+
+  while (myPoles.size() < aPoles->size())
+    addPoleWidget();
+
+  std::list<BSplinePoleWidgets>::iterator anIt = myPoles.begin();
+  for (int anIndex = 0; anIt != myPoles.end(); ++anIt, ++anIndex) {
+    GeomPnt2dPtr aPoint = aPoles->pnt(anIndex);
+    anIt->myX->setValue(aPoint->x());
+    anIt->myY->setValue(aPoint->y());
+    bool isBlocked = anIt->myWeight->blockSignals(true);
+    anIt->myWeight->setValue(aWeights->value(anIndex));
+    anIt->myWeight->blockSignals(isBlocked);
+  }
+
+  return true;
+}
+
+void PartSet_BSplineWidget::addPoleWidget()
+{
+  QGridLayout* aGroupLay = dynamic_cast<QGridLayout*>(myPolesWgt->layout());
+  int aNbPoles = (int)myPoles.size();
+  QString aPoleStr = tr("Pole %1").arg(aNbPoles + 1);
+
+  myPoles.push_back(BSplinePoleWidgets());
+  BSplinePoleWidgets& aPole = myPoles.back();
+  aGroupLay->addWidget(createPoleWidget(aPole, aPoleStr, myPolesWgt), aNbPoles, 1);
+}
+
+QGroupBox* PartSet_BSplineWidget::createPoleWidget(BSplinePoleWidgets& thePole,
+  const QString& theName, QWidget* theParent)
+{
+  QGroupBox* aPoleGroupBox = new QGroupBox(theName, theParent);
+  QGridLayout* aPoleLay = new QGridLayout(aPoleGroupBox);
+  aPoleLay->setSpacing(0);
+  ModuleBase_Tools::zeroMargins(aPoleLay);
+
+  thePole.myX = new ModuleBase_LabelValue(aPoleGroupBox, tr("X"));
+  aPoleLay->addWidget(thePole.myX, 0, 0, 1, 3);
+  thePole.myY = new ModuleBase_LabelValue(aPoleGroupBox, tr("Y"));
+  aPoleLay->addWidget(thePole.myY, 1, 0, 1, 3);
+  thePole.myWeight = new ModuleBase_ParamSpinBox(aPoleGroupBox);
+  thePole.myWeight->setMinimum(0.0);
+
+  aPoleLay->addWidget(new QLabel(tr("Weight :"), aPoleGroupBox), 2, 0);
+  aPoleLay->addWidget(thePole.myWeight, 2, 1);
+  // we should listen textChanged signal as valueChanged do not send when text is modified
+  connect(thePole.myWeight, SIGNAL(textChanged(const QString&)),
+    this, SIGNAL(valuesChanged()));
+
+  thePole.myAddBtn = new QToolButton(aPoleGroupBox);
+  thePole.myAddBtn->setIcon(QIcon(":pictures/add.png"));
+  thePole.myAddBtn->setToolTip(tr("Add a new pole after the current"));
+  aPoleLay->addWidget(thePole.myAddBtn, 2, 2);
+  connect(thePole.myAddBtn, SIGNAL(clicked(bool)), SLOT(onAddPole()));
+
+  return aPoleGroupBox;
+}
+
+
+void PartSet_BSplineWidget::onAddPole()
+{
+  QObject* aObj = sender();
+  std::list<BSplinePoleWidgets>::const_iterator aIt;
+  int aId = 0;
+  bool aFound = false;
+  for (aIt = myPoles.cbegin(); aIt != myPoles.cend(); aIt++, aId++) {
+    if ((*aIt).myAddBtn == aObj) {
+      aFound = true;
+      break;
+    }
+  }
+  if (aFound) {
+    // add a new pole after found Id
+    std::ostringstream anActionName;
+    anActionName << SketchPlugin_BSplineBase::ADD_POLE_ACTION_ID() << "#" << aId;
+    if (feature()->customAction(anActionName.str()))
+      updateObject(feature());
+
+    restoreValueCustom();
+  }
+}
diff --git a/src/PartSet/PartSet_BSplineWidget.h b/src/PartSet/PartSet_BSplineWidget.h
new file mode 100644 (file)
index 0000000..05108ca
--- /dev/null
@@ -0,0 +1,100 @@
+// Copyright (C) 2019-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 PartSet_BSplineWidget_H
+#define PartSet_BSplineWidget_H
+
+
+#include <PartSet.h>
+
+#include <ModelAPI_Feature.h>
+
+#include <ModuleBase_LabelValue.h>
+#include <ModuleBase_ModelWidget.h>
+#include <ModuleBase_ParamSpinBox.h>
+
+class QGroupBox;
+class QScrollArea;
+class QToolButton;
+
+/** \brief Represent a content of the property panel to show/modify parameters of B-spline curve.
+ *  \ingroup GUI
+ */
+class PartSet_BSplineWidget : public ModuleBase_ModelWidget
+{
+Q_OBJECT
+public:
+  /// Constructor
+  /// \param theParent the parent object
+  /// \param theData the widget configuation. The attribute of the model widget is obtained from
+  PartSet_BSplineWidget(QWidget* theParent,
+                             const Config_WidgetAPI* theData);
+
+  virtual ~PartSet_BSplineWidget() {}
+
+  /// The methiod called when widget is deactivated
+  virtual void deactivate();
+
+  /// Returns list of widget controls
+  /// \return a control list
+  virtual QList<QWidget*> getControls() const;
+
+  /// Set feature which is processing by active operation
+  /// \param theFeature a feature object
+  /// \param theToStoreValue a value about necessity to store the widget value to the feature
+  /// \param isUpdateFlushed a flag if update should be flushed on store value
+  virtual void setFeature(const FeaturePtr& theFeature, const bool theToStoreValue = false,
+                          const bool isUpdateFlushed = true);
+
+protected:
+  /// Saves the internal parameters to the given feature
+  /// \return True in success
+  virtual bool storeValueCustom();
+
+  /// Restore value from attribute data to the widget's control
+  virtual bool restoreValueCustom();
+
+  /// Create group of widgets related to coordinates of pole and its weight
+  void addPoleWidget();
+
+  /// Update attributes of B-spline feature
+  void storePolesAndWeights() const;
+
+private slots:
+  void onAddPole();
+
+private:
+  struct BSplinePoleWidgets {
+    ModuleBase_LabelValue* myX;
+    ModuleBase_LabelValue* myY;
+    ModuleBase_ParamSpinBox* myWeight;
+    QToolButton* myAddBtn;
+  };
+
+  QGroupBox* createPoleWidget(BSplinePoleWidgets& thePole,
+                              const QString& theName,
+                              QWidget* theParent);
+
+  QWidget* myPolesWgt; ///< widget to show poles and weights of B-spline curve
+  QGroupBox* myPolesGroupBox;
+  QScrollArea* myScrollArea;
+  std::list<BSplinePoleWidgets> myPoles; ///< list of B-spline poles and their weights
+};
+
+#endif
\ No newline at end of file
index feeda8b7185350a10e14402a6d8e818779ea7187..75fa7c49c2a7f1900c932942fa1505bb8566ef49 100644 (file)
@@ -22,6 +22,7 @@
 #include "PartSet_Validators.h"
 #include "PartSet_Tools.h"
 #include "PartSet_PreviewPlanes.h"
+#include "PartSet_WidgetBSplinePoints.h"
 #include "PartSet_WidgetPoint2d.h"
 #include "PartSet_WidgetPoint2DFlyout.h"
 #include "PartSet_WidgetShapeSelector.h"
@@ -39,6 +40,7 @@
 #include "PartSet_OverconstraintListener.h"
 #include "PartSet_TreeNodes.h"
 #include "PartSet_FieldStepPrs.h"
+#include "PartSet_BSplineWidget.h"
 
 #include "PartSet_Filters.h"
 #include "PartSet_FilterInfinite.h"
@@ -804,12 +806,12 @@ bool PartSet_Module::createWidgets(const FeaturePtr& theFeature, const QString&
             return aProcessed;
         }
         const TopoDS_Shape& aTDShape = aShape->impl<TopoDS_Shape>();
-        AttributePtr anAttribute = PartSet_Tools::findAttributeBy2dPoint(anObject, aTDShape,
-                                                               mySketchMgr->activeSketch());
-        if (anAttribute.get()) {
+        std::pair<AttributePtr, int> anAttribute =
+            PartSet_Tools::findAttributeBy2dPoint(anObject, aTDShape, mySketchMgr->activeSketch());
+        if (anAttribute.first.get()) {
           ModuleBase_WidgetFactory aFactory(theXmlRepr.toStdString(), workshop());
 
-          const std::string anAttributeId = anAttribute->id();
+          const std::string anAttributeId = anAttribute.first->id();
           aFactory.createWidget(aPropertyPanel->contentWidget(), anAttributeId);
 
           theWidgets = aFactory.getModelWidgets();
@@ -915,16 +917,27 @@ ModuleBase_ModelWidget* PartSet_Module::createWidgetByType(const std::string& th
     aPointSelectorWgt->setSketcher(mySketchMgr->activeSketch());
     aWgt = aPointSelectorWgt;
   }
+  else if (theType == "sketch-bspline_selector") {
+    PartSet_WidgetBSplinePoints* aBSplineWgt =
+        new PartSet_WidgetBSplinePoints(theParent, aWorkshop, theWidgetApi);
+    aBSplineWgt->setSketch(mySketchMgr->activeSketch());
+    aWgt = aBSplineWgt;
+  }
   else if (theType == WDG_DOUBLEVALUE_EDITOR) {
     aWgt = new PartSet_WidgetEditor(theParent, aWorkshop, theWidgetApi);
   } else if (theType == "export_file_selector") {
     aWgt = new PartSet_WidgetFileSelector(theParent, aWorkshop, theWidgetApi);
   } else if (theType == "sketch_launcher") {
     aWgt = new PartSet_WidgetSketchCreator(theParent, this, theWidgetApi);
-  } else if (theType == "module_choice") {
+  }
+  else if (theType == "module_choice") {
     aWgt = new ModuleBase_WidgetChoice(theParent, theWidgetApi);
     connect(aWgt, SIGNAL(itemSelected(ModuleBase_ModelWidget*, int)),
-            this, SLOT(onChoiceChanged(ModuleBase_ModelWidget*, int)));
+      this, SLOT(onChoiceChanged(ModuleBase_ModelWidget*, int)));
+  } else if (theType == "bspline-panel") {
+    PartSet_BSplineWidget* aPanel = new PartSet_BSplineWidget(theParent, theWidgetApi);
+    //aPanel->setFeature(theFeature);
+    aWgt = aPanel;
   }
   return aWgt;
 }
@@ -1728,8 +1741,9 @@ AttributePtr PartSet_Module::findAttribute(const ObjectPtr& theObject,
 
   if (aGeomShape.get()) {
     TopoDS_Shape aTDSShape = aGeomShape->impl<TopoDS_Shape>();
-    return PartSet_Tools::findAttributeBy2dPoint(theObject, aTDSShape,
-                                                 mySketchMgr->activeSketch());
+    std::pair<AttributePtr, int> anAttrAndIndex =
+        PartSet_Tools::findAttributeBy2dPoint(theObject, aTDSShape, mySketchMgr->activeSketch());
+    return anAttrAndIndex.first;
   }
   return anAttribute;
 }
index 27f9bf8d61a593223c16b7043cdb389ed5f3d59a..c57929240857943f38c965c34d622b7ffe75466f 100644 (file)
@@ -60,6 +60,7 @@
 #include <ModuleBase_ViewerFilters.h>
 
 #include <GeomDataAPI_Point2D.h>
+#include <GeomDataAPI_Point2DArray.h>
 
 #include <GeomAPI_Shape.h>
 
 void getAttributesOrResults(const Handle(SelectMgr_EntityOwner)& theOwner,
                             const FeaturePtr& theFeature, const FeaturePtr& theSketch,
                             const ResultPtr& theResult,
-                            std::set<AttributePtr>& theSelectedAttributes,
+                            std::map<AttributePtr, int>& theSelectedAttributes,
                             std::set<ResultPtr>& theSelectedResults,
                             TopTools_MapOfShape& theShapes)
 {
@@ -156,10 +157,10 @@ void getAttributesOrResults(const Handle(SelectMgr_EntityOwner)& theOwner,
     theShapes.Add(aShape);
     TopAbs_ShapeEnum aShapeType = aShape.ShapeType();
     if (aShapeType == TopAbs_VERTEX) {
-      AttributePtr aPntAttr = PartSet_Tools::findAttributeBy2dPoint(theFeature,
-                                                                    aShape, theSketch);
-      if (aPntAttr.get() != NULL)
-        theSelectedAttributes.insert(aPntAttr);
+      std::pair<AttributePtr, int> aPntAttrIndex =
+          PartSet_Tools::findAttributeBy2dPoint(theFeature, aShape, theSketch);
+      if (aPntAttrIndex.first.get() != NULL)
+        theSelectedAttributes[aPntAttrIndex.first] = aPntAttrIndex.second;
     }
     else if (aShapeType == TopAbs_EDGE &&
              theSelectedResults.find(theResult) == theSelectedResults.end()) {
@@ -641,26 +642,26 @@ void PartSet_SketcherMgr::onMouseMoved(ModuleBase_IViewWindow* theWnd, QMouseEve
     for (; anIt != aLast; anIt++) {
       FeaturePtr aFeature = anIt.key();
 
-      std::set<AttributePtr> anAttributes = anIt.value().myAttributes;
+      std::map<AttributePtr, int> anAttributes = anIt.value().myAttributes;
       // Process selection by attribute: the priority to the attribute
       if (!anAttributes.empty()) {
-        std::set<AttributePtr>::const_iterator anAttIt = anAttributes.begin(),
+        std::map<AttributePtr, int>::const_iterator anAttIt = anAttributes.begin(),
           anAttLast = anAttributes.end();
         for (; anAttIt != anAttLast; anAttIt++) {
-          AttributePtr anAttr = *anAttIt;
+          AttributePtr anAttr = anAttIt->first;
           if (anAttr.get() == NULL)
             continue;
           std::string aAttrId = anAttr->id();
           DataPtr aData = aFeature->data();
           if (aData->isValid()) {
-            std::shared_ptr<GeomDataAPI_Point2D> aPoint =
-              std::dynamic_pointer_cast<GeomDataAPI_Point2D>(aData->attribute(aAttrId));
-            if (aPoint.get() != NULL) {
+            AttributePtr aPoint = aData->attribute(aAttrId);
+            if (aPoint->attributeType() == GeomDataAPI_Point2D::typeId() ||
+                aPoint->attributeType() == GeomDataAPI_Point2DArray::typeId()) {
               bool isImmutable = aPoint->setImmutable(true);
 
               std::shared_ptr<ModelAPI_ObjectMovedMessage> aMessage = std::shared_ptr
                 <ModelAPI_ObjectMovedMessage>(new ModelAPI_ObjectMovedMessage(this));
-              aMessage->setMovedAttribute(aPoint);
+              aMessage->setMovedAttribute(aPoint, anAttIt->second);
               aMessage->setOriginalPosition(anOriginalPosition);
               aMessage->setCurrentPosition(aCurrentPosition);
               Events_Loop::loop()->send(aMessage);
@@ -1686,7 +1687,7 @@ void PartSet_SketcherMgr::getSelectionOwners(const FeaturePtr& theFeature,
 
   FeatureToSelectionMap::const_iterator anIt = theSelection.find(theFeature);
   SelectionInfo anInfo = anIt.value();
-  std::set<AttributePtr> aSelectedAttributes = anInfo.myAttributes;
+  std::map<AttributePtr, int> aSelectedAttributes = anInfo.myAttributes;
   std::set<ResultPtr> aSelectedResults = anInfo.myResults;
 
   ModuleBase_IViewer* aViewer = theWorkshop->viewer();
@@ -1738,10 +1739,10 @@ void PartSet_SketcherMgr::getSelectionOwners(const FeaturePtr& theFeature,
       const TopoDS_Shape& aShape = anOwner->Shape();
       TopAbs_ShapeEnum aShapeType = aShape.ShapeType();
       if (aShapeType == TopAbs_VERTEX) {
-        AttributePtr aPntAttr =
+        std::pair<AttributePtr, int> aPntAttrIndex =
           PartSet_Tools::findAttributeBy2dPoint(theFeature, aShape, theSketch);
-        if (aPntAttr.get() != NULL &&
-            aSelectedAttributes.find(aPntAttr) != aSelectedAttributes.end())
+        if (aPntAttrIndex.first.get() != NULL &&
+            aSelectedAttributes.find(aPntAttrIndex.first) != aSelectedAttributes.end())
           theOwnersToSelect.Add(anOwner);
         else if (isSameShape && anInfo.myLocalSelectedShapes.Contains(aShape)) {
           theOwnersToSelect.Add(anOwner);
index 67640b0184c9fa779fa1103ac6f72c1228e7ddf8..97fb2c720cf77533f3e58e33f6975d1e4f957e38 100644 (file)
@@ -137,7 +137,8 @@ public:
   /// Struct to define selection model information to store/restore selection
   struct SelectionInfo
   {
-    std::set<AttributePtr> myAttributes; /// the selected attributes
+    /// the selected attributes and indices of points if array
+    std::map<AttributePtr, int> myAttributes;
     std::set<ResultPtr> myResults; /// the selected results
     TopoDS_Shape myFirstResultShape; /// the first shape of feature result
     TopTools_MapOfShape myLocalSelectedShapes; /// shapes of local selection
index 83476b096d03d4bd9c0f64e99b4b8c6e00367bd8..09f2e143726776d661a379a62fd906eacffb590c 100644 (file)
@@ -49,6 +49,7 @@
 #include <GeomDataAPI_Point.h>
 #include <GeomDataAPI_Dir.h>
 #include <GeomDataAPI_Point2D.h>
+#include <GeomDataAPI_Point2DArray.h>
 #include <GeomAPI_Pln.h>
 #include <GeomAPI_Pnt2d.h>
 #include <GeomAPI_Pnt.h>
@@ -443,9 +444,9 @@ GeomShapePtr PartSet_Tools::findShapeBy2DPoint(const AttributePtr& theAttribute,
               // attribute, returns the shape
               PartSet_Module* aModule = dynamic_cast<PartSet_Module*>(theWorkshop->module());
               PartSet_SketcherMgr* aSketchMgr = aModule->sketchMgr();
-              AttributePtr aPntAttr = PartSet_Tools::findAttributeBy2dPoint(anAttributeFeature,
-                                                        aBRepShape, aSketchMgr->activeSketch());
-              if (aPntAttr.get() != NULL && aPntAttr == theAttribute) {
+              std::pair<AttributePtr, int> aPntAttrIndex = PartSet_Tools::findAttributeBy2dPoint(
+                  anAttributeFeature, aBRepShape, aSketchMgr->activeSketch());
+              if (aPntAttrIndex.first.get() != NULL && aPntAttrIndex.first == theAttribute) {
                 aShape = std::shared_ptr<GeomAPI_Shape>(new GeomAPI_Shape);
                 aShape->setImpl(new TopoDS_Shape(aBRepShape));
                 break;
@@ -483,6 +484,24 @@ std::shared_ptr<GeomAPI_Pnt2d> PartSet_Tools::getPnt2d(QMouseEvent* theEvent,
   return std::shared_ptr<GeomAPI_Pnt2d>(new GeomAPI_Pnt2d(aX, anY));
 }
 
+std::shared_ptr<GeomAPI_Pnt2d> PartSet_Tools::getPnt2d(const Handle(V3d_View)& theView,
+                                                       const TopoDS_Shape& theShape,
+                                                       const FeaturePtr& theSketch)
+{
+  GeomPnt2dPtr aPoint2D;
+  if (!theShape.IsNull() && theShape.ShapeType() == TopAbs_VERTEX) {
+    const TopoDS_Vertex& aVertex = TopoDS::Vertex(theShape);
+    if (!aVertex.IsNull()) {
+      // the case when the point is taken from the existing vertex
+      gp_Pnt aPoint = BRep_Tool::Pnt(aVertex);
+      double aX, aY;
+      PartSet_Tools::convertTo2D(aPoint, theSketch, theView, aX, aY);
+      aPoint2D.reset(new GeomAPI_Pnt2d(aX, aY));
+    }
+  }
+  return aPoint2D;
+}
+
 FeaturePtr findFirstCoincidenceByData(const DataPtr& theData,
                                       std::shared_ptr<GeomAPI_Pnt2d> thePoint)
 {
@@ -630,12 +649,44 @@ std::shared_ptr<GeomAPI_Pnt2d> PartSet_Tools::getCoincedencePoint(FeaturePtr the
   return aPnt;
 }
 
-AttributePtr PartSet_Tools::findAttributeBy2dPoint(ObjectPtr theObj,
-                                                   const TopoDS_Shape theShape,
-                                                   FeaturePtr theSketch)
+class PointWrapper
+{
+public:
+  PointWrapper(AttributePtr theAttribute)
+    : myPoint(std::dynamic_pointer_cast<GeomDataAPI_Point2D>(theAttribute)),
+      myArray(std::dynamic_pointer_cast<GeomDataAPI_Point2DArray>(theAttribute))
+  {}
+
+  int size() const { return myPoint.get() ? 1 : (myArray.get() ? myArray->size() : 0); }
+
+  GeomPointPtr point(int theIndex, FeaturePtr theSketch)
+  {
+    GeomPnt2dPtr aP2d;
+    if (myPoint.get())
+      aP2d = myPoint->pnt();
+    else if (myArray.get())
+      aP2d = myArray->pnt(theIndex);
+
+    GeomPointPtr aP3d;
+    if (aP2d.get())
+      aP3d = PartSet_Tools::convertTo3D(aP2d->x(), aP2d->y(), theSketch);
+    return aP3d;
+  }
+
+  bool isArray() const { return myArray.get(); }
+
+private:
+  AttributePoint2DPtr myPoint;
+  AttributePoint2DArrayPtr myArray;
+};
+
+std::pair<AttributePtr, int> PartSet_Tools::findAttributeBy2dPoint(ObjectPtr theObj,
+                                                                   const TopoDS_Shape theShape,
+                                                                   FeaturePtr theSketch)
 {
 
   AttributePtr anAttribute;
+  int aPointIndex = -1;
   FeaturePtr aFeature = ModelAPI_Feature::feature(theObj);
   if (aFeature) {
     if (theShape.ShapeType() == TopAbs_VERTEX) {
@@ -648,29 +699,32 @@ AttributePtr PartSet_Tools::findAttributeBy2dPoint(ObjectPtr theObj,
         // find the given point in the feature attributes
         std::list<AttributePtr> anAttiributes =
           aFeature->data()->attributes(GeomDataAPI_Point2D::typeId());
+        std::list<AttributePtr> anArrays =
+          aFeature->data()->attributes(GeomDataAPI_Point2DArray::typeId());
+        anAttiributes.insert(anAttiributes.end(), anArrays.begin(), anArrays.end());
+
         std::list<AttributePtr>::const_iterator anIt = anAttiributes.begin(),
                                                 aLast = anAttiributes.end();
         double aMinDistance = 1.e-6; // searching for point with minimal distance and < 1.e-6
         for (; anIt != aLast && !anAttribute; anIt++) {
-          std::shared_ptr<GeomDataAPI_Point2D> aCurPoint =
-            std::dynamic_pointer_cast<GeomDataAPI_Point2D>(*anIt);
-          if (!aCurPoint->isInitialized())
-            continue;
-
-          std::shared_ptr<GeomAPI_Pnt> aPnt =
-            convertTo3D(aCurPoint->x(), aCurPoint->y(), theSketch);
-          if (aPnt) {
-            double aDistance = aPnt->distance(aValue);
-            if (aDistance < aMinDistance) {
-              anAttribute = aCurPoint;
-              aMinDistance = aPnt->distance(aValue);
+          PointWrapper aWrapper(*anIt);
+          for (int anIndex = 0, aSize = aWrapper.size(); anIndex < aSize; ++anIndex) {
+            std::shared_ptr<GeomAPI_Pnt> aPnt = aWrapper.point(anIndex, theSketch);
+            if (aPnt) {
+              double aDistance = aPnt->distance(aValue);
+              if (aDistance < aMinDistance) {
+                anAttribute = *anIt;
+                if (aWrapper.isArray())
+                  aPointIndex = anIndex;
+                aMinDistance = aPnt->distance(aValue);
+              }
             }
           }
         }
       }
     }
   }
-  return anAttribute;
+  return std::pair<AttributePtr, int>(anAttribute, aPointIndex);
 }
 
 void PartSet_Tools::sendSubFeaturesEvent(const CompositeFeaturePtr& theComposite,
index 250e82a19119653cb83bf5b71f2b0768026f4f68..574ecc5b4221c00a379af6b1f7e5f9b3961cf04d 100644 (file)
@@ -193,9 +193,11 @@ public:
   * \param theObj - an object
   * \param theShape - a Shape
   * \param theSketch - a Sketch to get a plane of converting to 2d
+  * \return Found attribute and index of point if the attribute is an array
   */
-  static AttributePtr findAttributeBy2dPoint(ObjectPtr theObj, const TopoDS_Shape theShape,
-                                             FeaturePtr theSketch);
+  static std::pair<AttributePtr, int> findAttributeBy2dPoint(ObjectPtr theObj,
+                                                             const TopoDS_Shape theShape,
+                                                             FeaturePtr theSketch);
 
   /**
   * Finds an attribute value in attribute reference attribute value
@@ -216,7 +218,7 @@ public:
 
   /**
   * Convertes parameters into a geom point
-  * \theEvent a Qt event to find mouse position
+  * \param theEvent a Qt event to find mouse position
   * \param theWindow view window to define eye of view
   * \param theSketch to convert 3D point coordinates into coorditates of the sketch plane
   */
@@ -224,6 +226,16 @@ public:
                                                  ModuleBase_IViewWindow* theWindow,
                                                  const FeaturePtr& theSketch);
 
+  /** Returns point 2d from selected shape
+   *  \param theView a view window
+   *  \param theShape a vertex shape
+   *  \param theX an output value of X coordinate
+   *  \param theY an output value of Y coordinate
+   */
+  static std::shared_ptr<GeomAPI_Pnt2d> getPnt2d(const Handle(V3d_View)& theView,
+                                                 const TopoDS_Shape& theShape,
+                                                 const FeaturePtr& theSketch);
+
   /**
   * Gets all references to the feature, take coincidence constraint features, get point 2d attributes
   * and compare the point value to be equal with the given. Returns the first feature, which has
diff --git a/src/PartSet/PartSet_WidgetBSplinePoints.cpp b/src/PartSet/PartSet_WidgetBSplinePoints.cpp
new file mode 100644 (file)
index 0000000..0678e82
--- /dev/null
@@ -0,0 +1,651 @@
+// Copyright (C) 2019-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 <PartSet_WidgetBSplinePoints.h>
+
+#include <PartSet_CenterPrs.h>
+#include <PartSet_ExternalObjectsMgr.h>
+#include <PartSet_Module.h>
+#include <PartSet_SketcherReentrantMgr.h>
+#include <PartSet_WidgetPoint2d.h>
+
+#include <XGUI_Tools.h>
+#include <XGUI_Workshop.h>
+#include <XGUI_Displayer.h>
+
+#include <ModuleBase_ISelection.h>
+#include <ModuleBase_IViewer.h>
+#include <ModuleBase_IViewWindow.h>
+#include <ModuleBase_LabelValue.h>
+#include <ModuleBase_Tools.h>
+#include <ModuleBase_ViewerPrs.h>
+#include <ModuleBase_WidgetValidator.h>
+#include <ModuleBase_WidgetValidated.h>
+
+#include <Config_Keywords.h>
+#include <Config_WidgetAPI.h>
+
+#include <Events_Loop.h>
+
+#include <ModelAPI_Events.h>
+#include <ModelAPI_AttributeDoubleArray.h>
+#include <ModelAPI_AttributeRefAttrList.h>
+#include <ModelAPI_CompositeFeature.h>
+
+#include <GeomDataAPI_Point2D.h>
+#include <GeomDataAPI_Point2DArray.h>
+
+#include <GeomAPI_Pnt2d.h>
+#include <GeomAPI_IPresentable.h>
+
+#include <SketchPlugin_Feature.h>
+
+#include <QGridLayout>
+#include <QGroupBox>
+#include <QMouseEvent>
+#include <QGraphicsEffect>
+#include <QScrollArea>
+
+static const double MaxCoordinate = 1e12;
+
+static bool IsPointCreated = false;
+
+
+PartSet_WidgetBSplinePoints::PartSet_WidgetBSplinePoints(QWidget* theParent,
+                                             ModuleBase_IWorkshop* theWorkshop,
+                                             const Config_WidgetAPI* theData)
+: ModuleBase_ModelWidget(theParent, theData), myWorkshop(theWorkshop),
+  myValueIsCashed(false), myIsFeatureVisibleInCash(true),
+  myXValueInCash(0), myYValueInCash(0),
+  myPointIndex(0), myFinished(false)
+{
+  myRefAttribute = theData->getProperty("reference_attribute");
+  QVBoxLayout* aMainLayout = new QVBoxLayout(this);
+  ModuleBase_Tools::zeroMargins(aMainLayout);
+
+  // the control should accept the focus, so the boolean flag is corrected to be true
+  myIsObligatory = true;
+  QString aPageName = translate(theData->getProperty(CONTAINER_PAGE_NAME));
+  myBox = new QGroupBox(aPageName, theParent);
+  myBox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+  myBox->setFlat(false);
+  aMainLayout->addWidget(myBox);
+
+  bool aAcceptVariables = theData->getBooleanAttribute(DOUBLE_WDG_ACCEPT_EXPRESSIONS, true);
+
+  // B-spline weights attribute
+  myWeightsAttr = theData->getProperty("weights");
+
+  QVBoxLayout* aLayout = new QVBoxLayout(myBox);
+  ModuleBase_Tools::adjustMargins(aLayout);
+
+  myScrollArea = new QScrollArea(myBox);
+  myScrollArea->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+  myScrollArea->setWidgetResizable(true);
+  myScrollArea->setFrameStyle(QFrame::NoFrame);
+  aLayout->addWidget(myScrollArea);
+
+  QWidget* aContainer = new QWidget(myScrollArea);
+  QVBoxLayout* aBoxLay = new QVBoxLayout(aContainer);
+  aBoxLay->setContentsMargins(0, 0, 0, 0);
+
+  myGroupBox = new QWidget(aContainer);
+  QGridLayout* aGroupLay = new QGridLayout(myGroupBox);
+  ModuleBase_Tools::adjustMargins(aGroupLay);
+  aGroupLay->setSpacing(4);
+  aGroupLay->setColumnStretch(1, 1);
+  createNextPoint();
+  aBoxLay->addWidget(myGroupBox);
+  aBoxLay->addStretch(1);
+
+  myScrollArea->setWidget(aContainer);
+
+  myWidgetValidator = new ModuleBase_WidgetValidator(this, myWorkshop);
+  myExternalObjectMgr = new PartSet_ExternalObjectsMgr(theData->getProperty("use_external"),
+                                         theData->getProperty("can_create_external"), true);
+}
+
+void PartSet_WidgetBSplinePoints::createNextPoint()
+{
+  storeCurentValue();
+
+  QGridLayout* aGroupLay = dynamic_cast<QGridLayout*>(myGroupBox->layout());
+  int row = (int)myXSpin.size();
+
+  QString aPoleStr = tr("Pole %1");
+  aPoleStr = aPoleStr.arg(myXSpin.size() + 1);
+
+  QGroupBox* aPoleGroupBox = new QGroupBox(aPoleStr, myGroupBox);
+  QGridLayout* aPoleLay = new QGridLayout(aPoleGroupBox);
+  ModuleBase_Tools::adjustMargins(aPoleLay);
+  aPoleLay->setSpacing(2);
+  aPoleLay->setColumnStretch(1, 1);
+
+  myXSpin.push_back(new ModuleBase_LabelValue(aPoleGroupBox, tr("X")));
+  aPoleLay->addWidget(myXSpin.back(), 0, 1);
+  myYSpin.push_back(new ModuleBase_LabelValue(aPoleGroupBox, tr("Y")));
+  aPoleLay->addWidget(myYSpin.back(), 1, 1);
+
+  aGroupLay->addWidget(aPoleGroupBox, row, 1);
+  IsPointCreated = true;
+}
+
+void PartSet_WidgetBSplinePoints::removeLastPoint()
+{
+  QGridLayout* aGroupLay = dynamic_cast<QGridLayout*>(myGroupBox->layout());
+  QWidget* aXSpin = myXSpin.back();
+  QWidget* aYSpin = myYSpin.back();
+  QWidget* aBox = myXSpin.back()->parentWidget();
+  myYSpin.pop_back();
+  myXSpin.pop_back();
+
+  aGroupLay->removeWidget(aXSpin);
+  aGroupLay->removeWidget(aYSpin);
+  aGroupLay->removeWidget(aBox);
+
+  aBox->deleteLater();
+
+  // update B-spline feature attributes
+  storeValueCustom();
+}
+
+bool PartSet_WidgetBSplinePoints::isValidSelectionCustom(const ModuleBase_ViewerPrsPtr& theValue)
+{
+  bool aValid = true;
+
+  PartSet_Module* aModule = dynamic_cast<PartSet_Module*>(myWorkshop->module());
+  if (aModule->sketchReentranceMgr()->isInternalEditActive())
+    return true; // when internal edit is started a new feature is created. I has not results, AIS
+
+  // the selection is not possible if the current feature has no presentation for the current
+  // attribute not in AIS not in results. If so, no object in current feature where make
+  // coincidence, so selection is not necessary
+  GeomShapePtr anAISShape;
+  GeomPresentablePtr aPrs = std::dynamic_pointer_cast<GeomAPI_IPresentable>(myFeature);
+  if (aPrs.get()) {
+    AISObjectPtr anAIS;
+    anAIS = aPrs->getAISObject(anAIS);
+    if (anAIS.get()) {
+      anAISShape = anAIS->getShape();
+    }
+  }
+  const std::list<std::shared_ptr<ModelAPI_Result> >& aResults = myFeature->results();
+  if (!anAISShape.get() && aResults.empty())
+    return true;
+
+  AttributeRefAttrListPtr aRefAttrList = attributeRefAttrList();
+  if (aRefAttrList)
+    return isValidSelectionForAttribute_(theValue, myFeature->attribute(attributeID()));
+  return true;
+}
+
+bool PartSet_WidgetBSplinePoints::isValidSelectionForAttribute_(
+                                            const ModuleBase_ViewerPrsPtr& theValue,
+                                            const AttributePtr& theAttribute)
+{
+  bool aValid = false;
+
+  // stores the current values of the widget attribute
+  bool isFlushesActived, isAttributeSetInitializedBlocked, isAttributeSendUpdatedBlocked;
+
+  AttributeRefAttrListPtr aRefAttrList = attributeRefAttrList();
+  ModuleBase_WidgetValidated::blockFeatureAttribute(aRefAttrList, myFeature, true,
+      isFlushesActived, isAttributeSetInitializedBlocked, isAttributeSendUpdatedBlocked);
+  myWidgetValidator->storeAttributeValue(aRefAttrList);
+
+  // saves the owner value to the widget attribute
+  aValid = setSelectionCustom(theValue);
+  if (aValid)
+    // checks the attribute validity
+    aValid = myWidgetValidator->isValidAttribute(theAttribute);
+
+  // restores the current values of the widget attribute
+  myWidgetValidator->restoreAttributeValue(aRefAttrList, aValid);
+  myExternalObjectMgr->removeExternal(sketch(), myFeature, myWorkshop, true);
+
+  ModuleBase_WidgetValidated::blockFeatureAttribute(aRefAttrList, myFeature, false,
+      isFlushesActived, isAttributeSetInitializedBlocked, isAttributeSendUpdatedBlocked);
+  return aValid;
+}
+
+bool PartSet_WidgetBSplinePoints::setSelectionCustom(const ModuleBase_ViewerPrsPtr& theValue)
+{
+  bool isDone = false;
+  GeomShapePtr aShape = theValue->shape();
+  if (aShape.get() && !aShape->isNull()) {
+    Handle(V3d_View) aView = myWorkshop->viewer()->activeView();
+    const TopoDS_Shape& aTDShape = aShape->impl<TopoDS_Shape>();
+    GeomPnt2dPtr aPnt = PartSet_Tools::getPnt2d(aView, aTDShape, mySketch);
+    if (aPnt) {
+      fillRefAttribute(aPnt, theValue);
+      isDone = true;
+    }
+    else if (aTDShape.ShapeType() == TopAbs_EDGE) {
+      fillRefAttribute(theValue);
+      isDone = true;
+    }
+  }
+  return isDone;
+}
+
+static void fillLabels(std::vector<ModuleBase_LabelValue*>& theLabels, const double theValue)
+{
+  for (std::vector<ModuleBase_LabelValue*>::iterator anIt = theLabels.begin();
+       anIt != theLabels.end(); ++anIt)
+    (*anIt)->setValue(theValue);
+}
+
+bool PartSet_WidgetBSplinePoints::resetCustom()
+{
+  bool aDone = false;
+  if (!isUseReset() || isComputedDefault())
+    aDone = false;
+  else {
+    if (myValueIsCashed) {
+      // if the restored value should be hidden, aDone = true to set
+      // reset state for the widget in the parent
+      aDone = restoreCurentValue();
+      emit objectUpdated();
+    }
+    else {
+      // it is important to block the spin box control in order to do not through out the
+      // locking of the validating state.
+      fillLabels(myXSpin, 0.0);
+      fillLabels(myYSpin, 0.0);
+
+      storeValueCustom();
+      aDone = true;
+    }
+  }
+  return aDone;
+}
+
+PartSet_WidgetBSplinePoints::~PartSet_WidgetBSplinePoints()
+{
+  delete myExternalObjectMgr;
+}
+
+bool PartSet_WidgetBSplinePoints::setPoint(double theX, double theY)
+{
+  if (fabs(theX) >= MaxCoordinate || fabs(theY) >= MaxCoordinate)
+    return false;
+
+  myXSpin.back()->setValue(theX);
+  myYSpin.back()->setValue(theY);
+
+  storeValue();
+  return true;
+}
+
+void PartSet_WidgetBSplinePoints::storePolesAndWeights() const
+{
+  std::shared_ptr<ModelAPI_Data> aData = myFeature->data();
+  AttributePoint2DArrayPtr aPointArray = std::dynamic_pointer_cast<GeomDataAPI_Point2DArray>(
+      aData->attribute(attributeID()));
+  AttributeDoubleArrayPtr aWeightsArray = aData->realArray(myWeightsAttr);
+
+  int aSize = (int)myXSpin.size();
+  aPointArray->setSize(aSize);
+  aWeightsArray->setSize(aSize);
+
+  std::vector<ModuleBase_LabelValue*>::const_iterator aXIt = myXSpin.begin();
+  std::vector<ModuleBase_LabelValue*>::const_iterator aYIt = myYSpin.begin();
+  for (int anIndex = 0; aXIt != myXSpin.end() && aYIt != myYSpin.end(); ++anIndex, ++aXIt, ++aYIt)
+    aPointArray->setPnt(anIndex, (*aXIt)->value(), (*aYIt)->value());
+
+  double aWeight = Config_PropManager::real(SKETCH_TAB_NAME, "spline_weight");
+  for (int anIndex = 0; anIndex < aSize; ++anIndex)
+    aWeightsArray->setValue(anIndex, aWeight);
+}
+
+bool PartSet_WidgetBSplinePoints::storeValueCustom()
+{
+  std::shared_ptr<ModelAPI_Data> aData = myFeature->data();
+  if (!aData || !aData->isValid()) // can be on abort of sketcher element
+    return false;
+  AttributePoint2DArrayPtr aPointArray = std::dynamic_pointer_cast<GeomDataAPI_Point2DArray>(
+      aData->attribute(attributeID()));
+  AttributeDoubleArrayPtr aWeightsArray = aData->realArray(myWeightsAttr);
+
+  PartSet_WidgetBSplinePoints* that = (PartSet_WidgetBSplinePoints*) this;
+  bool isBlocked = that->blockSignals(true);
+  bool isImmutable = aPointArray->setImmutable(true);
+
+  if (myFeature->isMacro()) {
+    // Moving points of macro-features has been processed directly (without solver)
+    storePolesAndWeights();
+    updateObject(myFeature);
+
+  } else {
+    if (!aPointArray->isInitialized()) {
+      storePolesAndWeights();
+    }
+
+    std::shared_ptr<ModelAPI_ObjectMovedMessage> aMessage(
+        new ModelAPI_ObjectMovedMessage(this));
+    aMessage->setMovedAttribute(aPointArray, aPointArray->size() - 1);
+    aMessage->setOriginalPosition(aPointArray->pnt(aPointArray->size() - 1));
+    aMessage->setCurrentPosition(myXSpin.back()->value(), myYSpin.back()->value());
+    Events_Loop::loop()->send(aMessage);
+  }
+
+  aPointArray->setImmutable(isImmutable);
+  that->blockSignals(isBlocked);
+
+  return true;
+}
+
+bool PartSet_WidgetBSplinePoints::restoreValueCustom()
+{
+  std::shared_ptr<ModelAPI_Data> aData = myFeature->data();
+  AttributePoint2DArrayPtr aPointArray = std::dynamic_pointer_cast<GeomDataAPI_Point2DArray>(
+      aData->attribute(attributeID()));
+  AttributeDoubleArrayPtr aWeightsArray = aData->realArray(myWeightsAttr);
+
+  if (aPointArray->isInitialized()) {
+    while (myXSpin.size() < aPointArray->size())
+      createNextPoint();
+
+    std::vector<ModuleBase_LabelValue*>::iterator aXIt = myXSpin.begin();
+    std::vector<ModuleBase_LabelValue*>::iterator aYIt = myYSpin.begin();
+    for (int anIndex = 0; aXIt != myXSpin.end() && aYIt != myYSpin.end();
+         ++anIndex, ++aXIt, ++aYIt) {
+      GeomPnt2dPtr aPoint = aPointArray->pnt(anIndex);
+      (*aXIt)->setValue(aPoint->x());
+      (*aYIt)->setValue(aPoint->y());
+    }
+  }
+  else {
+    if (myXSpin.empty())
+      createNextPoint();
+
+    myXSpin.back()->setValue(0.0);
+    myYSpin.back()->setValue(0.0);
+  }
+
+  return true;
+}
+
+static void storeArray(const std::vector<ModuleBase_LabelValue*>& theLabels,
+                       std::vector<double>& theValues)
+{
+  theValues.clear();
+  theValues.reserve(theLabels.size());
+  for (std::vector<ModuleBase_LabelValue*>::const_iterator anIt = theLabels.begin();
+       anIt != theLabels.end(); ++anIt)
+    theValues.push_back((*anIt)->value());
+}
+
+void PartSet_WidgetBSplinePoints::storeCurentValue()
+{
+  myValueIsCashed = true;
+  myIsFeatureVisibleInCash = XGUI_Displayer::isVisible(
+                       XGUI_Tools::workshop(myWorkshop)->displayer(), myFeature);
+
+  storeArray(myXSpin, myXValueInCash);
+  storeArray(myYSpin, myYValueInCash);
+}
+
+static void restoreArray(std::vector<double>& theCacheValues,
+                         std::vector<ModuleBase_LabelValue*>& theLabels)
+{
+  std::vector<double>::iterator aCIt = theCacheValues.begin();
+  std::vector<ModuleBase_LabelValue*>::iterator anIt = theLabels.begin();
+  for (; anIt != theLabels.end(); ++anIt) {
+    if (aCIt != theCacheValues.end())
+      (*anIt)->setValue(*aCIt++);
+    else
+      (*anIt)->setValue(0.0);
+  }
+  theCacheValues.clear();
+}
+
+bool PartSet_WidgetBSplinePoints::restoreCurentValue()
+{
+  bool aRestoredAndHidden = true;
+
+  bool isVisible = myIsFeatureVisibleInCash;
+
+  myValueIsCashed = false;
+  myIsFeatureVisibleInCash = true;
+  // fill the control widgets by the cashed value
+  restoreArray(myXValueInCash, myXSpin);
+  restoreArray(myYValueInCash, myYSpin);
+
+  // store value to the model
+  storeValueCustom();
+  if (isVisible) {
+    setValueState(Stored);
+    aRestoredAndHidden = false;
+  }
+  else
+    aRestoredAndHidden = true;
+
+  return aRestoredAndHidden;
+}
+
+QList<QWidget*> PartSet_WidgetBSplinePoints::getControls() const
+{
+  QList<QWidget*> aControls;
+  aControls.append(myScrollArea);
+  return aControls;
+}
+
+void PartSet_WidgetBSplinePoints::selectionModes(int& theModuleSelectionModes, QIntList& theModes)
+{
+  theModuleSelectionModes = -1;
+  theModes << TopAbs_VERTEX;
+  theModes << TopAbs_EDGE;
+}
+
+void PartSet_WidgetBSplinePoints::deactivate()
+{
+  // the value of the control should be stored to model if it was not
+  // initialized yet. It is important when we leave this control by Tab key.
+  // It should not be performed by the widget activation as the preview
+  // is visualized with default value. Line point is moved to origin.
+  AttributePtr anAttribute = myFeature->data()->attribute(attributeID());
+  if (anAttribute && !anAttribute->isInitialized())
+    storeValue();
+
+  ModuleBase_ModelWidget::deactivate();
+}
+
+void PartSet_WidgetBSplinePoints::mouseReleased(ModuleBase_IViewWindow* theWindow,
+                                                QMouseEvent* theEvent)
+{
+  // the contex menu release by the right button should not be processed by this widget
+  if (theEvent->button() != Qt::LeftButton)
+    return;
+
+  ModuleBase_ISelection* aSelection = myWorkshop->selection();
+  Handle(V3d_View) aView = theWindow->v3dView();
+
+  QList<ModuleBase_ViewerPrsPtr> aList = aSelection->getSelected(ModuleBase_ISelection::Viewer);
+  ModuleBase_ViewerPrsPtr aFirstValue =
+    aList.size() > 0 ? aList.first() : ModuleBase_ViewerPrsPtr();
+  if (!aFirstValue.get() && myPreSelected.get()) {
+    aFirstValue = myPreSelected;
+  }
+
+  TopoDS_Shape aSelectedShape;
+  ObjectPtr aSelectedObject;
+
+  // if we have selection and use it
+  if (aFirstValue.get() && isValidSelectionCustom(aFirstValue) &&
+      aFirstValue->shape().get()) { // Trihedron Axis may be selected, but shape is empty
+    GeomShapePtr aGeomShape = aFirstValue->shape();
+    aSelectedShape = aGeomShape->impl<TopoDS_Shape>();
+    aSelectedObject = aFirstValue->object();
+
+    FeaturePtr aSelectedFeature = ModelAPI_Feature::feature(aSelectedObject);
+    std::shared_ptr<SketchPlugin_Feature> aSPFeature;
+    if (aSelectedFeature.get())
+      aSPFeature = std::dynamic_pointer_cast<SketchPlugin_Feature>(aSelectedFeature);
+
+    bool isSketchExternalFeature = aSPFeature.get() && aSPFeature->isExternal();
+    if ((!aSPFeature && !aSelectedShape.IsNull()) || isSketchExternalFeature) {
+      ObjectPtr aFixedObject =
+          PartSet_Tools::findFixedObjectByExternal(aSelectedShape, aSelectedObject, mySketch);
+      if (aFixedObject)
+        aSelectedObject = aFixedObject;
+      else if (!isSketchExternalFeature) {
+        FeaturePtr aCreatedFeature;
+        aSelectedObject = PartSet_Tools::createFixedObjectByExternal(
+            aGeomShape, aSelectedObject, mySketch, false, aCreatedFeature);
+      }
+    }
+  }
+  // The selection could be a center of an external circular object
+  else if (aFirstValue.get() && (!aFirstValue->interactive().IsNull())) {
+    Handle(PartSet_CenterPrs) aAIS =
+        Handle(PartSet_CenterPrs)::DownCast(aFirstValue->interactive());
+    if (!aAIS.IsNull()) {
+      gp_Pnt aPntComp = aAIS->Component()->Pnt();
+      GeomVertexPtr aVertPtr(new GeomAPI_Vertex(aPntComp.X(), aPntComp.Y(), aPntComp.Z()));
+      aSelectedShape = aVertPtr->impl<TopoDS_Shape>();
+
+      aSelectedObject =
+          PartSet_Tools::findFixedObjectByExternal(aSelectedShape, aAIS->object(), mySketch);
+      if (!aSelectedObject.get())
+      {
+        FeaturePtr aCreatedFeature;
+        aSelectedObject = PartSet_Tools::createFixedByExternalCenter(aAIS->object(), aAIS->edge(),
+            aAIS->centerType(), mySketch, false, aCreatedFeature);
+      }
+    }
+  }
+
+  GeomPnt2dPtr aSelectedPoint = PartSet_Tools::getPnt2d(aView, aSelectedShape, mySketch);
+  if (!aSelectedPoint) {
+    aSelectedPoint = PartSet_Tools::getPnt2d(theEvent, theWindow, mySketch);
+    setValueState(Stored); // in case of edge selection, Apply state should also be updated
+  }
+  if (aSelectedObject)
+    fillRefAttribute(aSelectedObject);
+  else
+    fillRefAttribute(aSelectedPoint, aFirstValue);
+
+  // next pole of B-spline
+  createNextPoint();
+}
+
+void PartSet_WidgetBSplinePoints::mouseMoved(ModuleBase_IViewWindow* theWindow,
+                                             QMouseEvent* theEvent)
+{
+  PartSet_Module* aModule = dynamic_cast<PartSet_Module*>(myWorkshop->module());
+
+  if (myFinished || isEditingMode() || aModule->sketchReentranceMgr()->isInternalEditActive())
+    return;
+
+  gp_Pnt aPoint = PartSet_Tools::convertClickToPoint(theEvent->pos(), theWindow->v3dView());
+
+  double aX = 0, aY = 0;
+  PartSet_Tools::convertTo2D(aPoint, mySketch, theWindow->v3dView(), aX, aY);
+  if (myState != ModifiedInViewer)
+    storeCurentValue();
+  // we need to block the value state change
+  bool isBlocked = blockValueState(true);
+  setPoint(aX, aY);
+  blockValueState(isBlocked);
+  setValueState(ModifiedInViewer);
+
+  if (IsPointCreated) {
+    QPoint aPnt = myGroupBox->geometry().bottomLeft();
+    myScrollArea->ensureVisible(aPnt.x(), aPnt.y());
+    IsPointCreated = false;
+  }
+}
+
+bool PartSet_WidgetBSplinePoints::processEscape()
+{
+  bool isProcessed = !isEditingMode();
+  if (isProcessed) {
+    // remove widgets corrsponding to the last pole/weight of B-spline
+    removeLastPoint();
+    myFinished = true;
+
+    emit focusOutWidget(this);
+  }
+  return isProcessed;
+}
+
+bool PartSet_WidgetBSplinePoints::useSelectedShapes() const
+{
+  return true;
+}
+
+AttributeRefAttrListPtr PartSet_WidgetBSplinePoints::attributeRefAttrList() const
+{
+  if (myRefAttribute.empty())
+    return AttributeRefAttrListPtr();
+
+  AttributePtr anAttributeRef = feature()->attribute(myRefAttribute);
+  if (!anAttributeRef.get())
+    return AttributeRefAttrListPtr();
+
+  return std::dynamic_pointer_cast<ModelAPI_AttributeRefAttrList>(anAttributeRef);
+}
+
+void PartSet_WidgetBSplinePoints::fillRefAttribute(GeomPnt2dPtr theClickedPoint,
+                              const std::shared_ptr<ModuleBase_ViewerPrs>& theValue)
+{
+  AttributeRefAttrListPtr aRefAttrList = attributeRefAttrList();
+  if (!aRefAttrList.get())
+    return;
+
+  FeaturePtr aFeature = feature();
+  std::string anAttribute = attributeID();
+
+  if (aFeature.get()) {
+    AttributePoint2DPtr aClickedFeaturePoint =
+        PartSet_WidgetPoint2D::findFirstEqualPointInSketch(mySketch, aFeature, theClickedPoint);
+    if (aClickedFeaturePoint.get())
+      aRefAttrList->append(aClickedFeaturePoint);
+    else
+      fillRefAttribute(theValue);
+  }
+}
+
+void PartSet_WidgetBSplinePoints::fillRefAttribute(const ModuleBase_ViewerPrsPtr& theValue)
+{
+  ObjectPtr anObject;
+  if (theValue)
+    anObject = getGeomSelection(theValue);
+  fillRefAttribute(anObject);
+}
+
+void PartSet_WidgetBSplinePoints::fillRefAttribute(const ObjectPtr& theObject)
+{
+  AttributeRefAttrListPtr aRefAttrList = attributeRefAttrList();
+  if (aRefAttrList.get())
+    aRefAttrList->append(theObject);
+}
+
+ObjectPtr PartSet_WidgetBSplinePoints::getGeomSelection(const ModuleBase_ViewerPrsPtr& theValue)
+{
+  ObjectPtr anObject;
+  GeomShapePtr aShape;
+  ModuleBase_ISelection* aSelection = myWorkshop->selection();
+  anObject = aSelection->getResult(theValue);
+  aShape = aSelection->getShape(theValue);
+  myExternalObjectMgr->getGeomSelection(theValue, anObject, aShape, myWorkshop, sketch(), true);
+
+  return anObject;
+}
diff --git a/src/PartSet/PartSet_WidgetBSplinePoints.h b/src/PartSet/PartSet_WidgetBSplinePoints.h
new file mode 100644 (file)
index 0000000..8285ab9
--- /dev/null
@@ -0,0 +1,190 @@
+// Copyright (C) 2019-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 PartSet_WidgetBSplinePoints_H
+#define PartSet_WidgetBSplinePoints_H
+
+#include "PartSet.h"
+#include "PartSet_MouseProcessor.h"
+
+#include <ModuleBase_ModelWidget.h>
+
+#include <QObject>
+
+class GeomAPI_Pnt2d;
+class ModelAPI_CompositeFeature;
+class ModuleBase_LabelValue;
+class PartSet_ExternalObjectsMgr;
+class QGroupBox;
+class QScrollArea;
+
+/**\class PartSet_WidgetBSplinePoints
+ * \ingroup Modules
+ * \brief Implementation of model widget to provide widget to input a list of 2D poles
+ *        of B-spline curve in association with weights
+ * In XML can be defined as following:
+ * \code
+ * <sketch-bspline_selector id="poles" weights="weights"/>
+ * \endcode
+ */
+class PARTSET_EXPORT PartSet_WidgetBSplinePoints : public ModuleBase_ModelWidget,
+                                                   public PartSet_MouseProcessor
+{
+Q_OBJECT
+public:
+  /// Constructor
+  /// \param theParent the parent object
+  /// \param theWorkshop a current workshop
+  /// \param theData the widget configuation. The attribute of the model widget is obtained from
+  PartSet_WidgetBSplinePoints(QWidget* theParent, ModuleBase_IWorkshop* theWorkshop,
+                              const Config_WidgetAPI* theData);
+  /// Destructor
+  virtual ~PartSet_WidgetBSplinePoints();
+
+  /// Fills given container with selection modes if the widget has it
+  /// \param [out] theModuleSelectionModes module additional modes, -1 means all default modes
+  /// \param theModes [out] a container of modes
+  virtual void selectionModes(int& theModuleSelectionModes, QIntList& theModes);
+
+  /// Checks if the selection presentation is valid in widget
+  /// \param theValue a selected presentation in the view
+  /// \return a boolean value
+  virtual bool isValidSelectionCustom(const std::shared_ptr<ModuleBase_ViewerPrs>& theValue);
+
+  /// Checks all attribute validators returns valid. It tries on the given selection
+  /// to current attribute by setting the value inside and calling validators. After this,
+  /// the previous attribute value is restored.The valid/invalid value is cashed.
+  /// \param theValue a selected presentation in the view
+  /// \param theAttribute the attribute
+  /// \return a boolean value
+  bool isValidSelectionForAttribute_(const std::shared_ptr<ModuleBase_ViewerPrs>& theValue,
+                                     const std::shared_ptr<ModelAPI_Attribute>& theAttribute);
+
+  /// Fills the attribute with the value of the selected owner
+  /// \param thePrs a selected owner
+  bool setSelectionCustom(const std::shared_ptr<ModuleBase_ViewerPrs>& theValue);
+
+  /// Returns list of widget controls
+  /// \return a control list
+  virtual QList<QWidget*> getControls() const;
+
+  /// The methiod called when widget is deactivated
+  virtual void deactivate();
+
+  /// \returns the sketch instance
+  std::shared_ptr<ModelAPI_CompositeFeature> sketch() const { return mySketch; }
+
+  /// Set sketch instance
+  void setSketch(std::shared_ptr<ModelAPI_CompositeFeature> theSketch) { mySketch = theSketch; }
+
+  /// Fill the widget values by given point
+  /// \param theX the X coordinate
+  /// \param theY the Y coordinate
+  /// \returns True in case of success
+  bool setPoint(double theX, double theY);
+
+  /// Returns true if the event is processed.
+  virtual bool processEscape();
+
+  /// Returns true if the attribute can be changed using the selected shapes in the viewer
+  /// and creating a coincidence constraint to them. This control use them.
+  virtual bool useSelectedShapes() const;
+
+  /// Processing the mouse move event in the viewer
+  /// \param theWindow a view window
+  /// \param theEvent a mouse event
+  virtual void mouseMoved(ModuleBase_IViewWindow* theWindow, QMouseEvent* theEvent);
+
+  /// Processing the mouse release event in the viewer
+  /// \param theWindow a view window
+  /// \param theEvent a mouse event
+  virtual void mouseReleased(ModuleBase_IViewWindow* theWindow, QMouseEvent* theEvent);
+
+protected:
+  /// Saves the internal parameters to the given feature
+  /// \return True in success
+  virtual bool storeValueCustom();
+
+  /// Restore value from attribute data to the widget's control
+  virtual bool restoreValueCustom();
+
+  /// Store current value in cashed value
+  void storeCurentValue();
+
+  /// Restore cashed value in the model attribute
+  /// \return boolean state if the restored feature shoud be hidden
+  bool restoreCurentValue();
+
+  /// Fills the widget with default values
+  /// \return true if the widget current value is reset
+  virtual bool resetCustom();
+
+private:
+  /// Create labels for the next B-spline point
+  void createNextPoint();
+  /// Remove labels for the last B-spline point
+  void removeLastPoint();
+
+  /// Save B-spline poles and weights to corresponding attributes
+  void storePolesAndWeights() const;
+
+  /// Returns attribute reference if the key is defined in XML definition of this control
+  /// \return found attribute or null
+  std::shared_ptr<ModelAPI_AttributeRefAttrList> attributeRefAttrList() const;
+
+  void fillRefAttribute(const std::shared_ptr<ModuleBase_ViewerPrs>& theValue);
+  void fillRefAttribute(std::shared_ptr<GeomAPI_Pnt2d> theClickedPoint,
+                        const std::shared_ptr<ModuleBase_ViewerPrs>& theValue);
+  void fillRefAttribute(const ObjectPtr& theObject);
+
+  ObjectPtr getGeomSelection(const std::shared_ptr<ModuleBase_ViewerPrs>& theValue);
+
+protected:
+  ModuleBase_IWorkshop* myWorkshop; ///< workshop
+
+private:
+  QGroupBox* myBox;
+  QWidget* myGroupBox;  ///< the parent group box for all intenal widgets
+  QScrollArea* myScrollArea;
+  std::vector<ModuleBase_LabelValue*> myXSpin; ///< the label for the X coordinate
+  std::vector<ModuleBase_LabelValue*> myYSpin; ///< the label for the Y coordinate
+  PartSet_ExternalObjectsMgr* myExternalObjectMgr; ///< reference to external objects manager
+
+  /// value used as selection in mouse release method
+  std::shared_ptr<ModuleBase_ViewerPrs> myPreSelected;
+
+  /// it is important during restart operation
+  CompositeFeaturePtr mySketch;
+
+  std::string myRefAttribute; /// if not empty, coincidences are not set but attribute is filled
+
+  bool myValueIsCashed; /// boolean state if the value is cashed during value state change
+  bool myIsFeatureVisibleInCash; /// boolean value if the feature was visible when cash if filled
+  std::vector<double> myXValueInCash; /// the cashed X value during value state change
+  std::vector<double> myYValueInCash; /// the cashed Y value during value state change
+  std::vector<double> myWeightInCash; /// the cached Weight value during valude state change
+
+  std::string myWeightsAttr;
+
+  int myPointIndex; /// index of the changing point
+
+  bool myFinished; /// \c true if building the B-spline is finished (escape pressed)
+};
+
+#endif
index 8183806b9d66b41da4ca2e03093a3734821a68f8..b01f5fecddd6dfd17c5180c4a86f1b5088766f5e 100644 (file)
@@ -243,10 +243,10 @@ bool PartSet_WidgetPoint2D::setSelectionCustom(const ModuleBase_ViewerPrsPtr& th
   GeomShapePtr aShape = theValue->shape();
   if (aShape.get() && !aShape->isNull()) {
     Handle(V3d_View) aView = myWorkshop->viewer()->activeView();
-    double aX = 0, aY = 0;
     const TopoDS_Shape& aTDShape = aShape->impl<TopoDS_Shape>();
-    if (getPoint2d(aView, aTDShape, aX, aY)) {
-      fillRefAttribute(aX, aY, theValue);
+    GeomPnt2dPtr aPnt = PartSet_Tools::getPnt2d(aView, aTDShape, mySketch);
+    if (aPnt) {
+      fillRefAttribute(aPnt->x(), aPnt->y(), theValue);
       isDone = true;
     }
     else if (aTDShape.ShapeType() == TopAbs_EDGE) {
@@ -306,11 +306,11 @@ bool PartSet_WidgetPoint2D::setSelection(QList<ModuleBase_ViewerPrsPtr>& theValu
     GeomShapePtr aShape = aValue->shape();
     if (aShape.get() && !aShape->isNull()) {
       Handle(V3d_View) aView = myWorkshop->viewer()->activeView();
-      double aX = 0, aY = 0;
       const TopoDS_Shape& aTDShape = aShape->impl<TopoDS_Shape>();
-      if (getPoint2d(aView, aTDShape, aX, aY)) {
-        isDone = setPoint(aX, aY);
-        setConstraintToPoint(aX, aY, aValue);
+      GeomPnt2dPtr aPnt = PartSet_Tools::getPnt2d(aView, aTDShape, mySketch);
+      if (aPnt) {
+        isDone = setPoint(aPnt->x(), aPnt->y());
+        setConstraintToPoint(aPnt->x(), aPnt->y(), aValue);
       }
     }
   }
@@ -476,24 +476,6 @@ void PartSet_WidgetPoint2D::deactivate()
   ModuleBase_ModelWidget::deactivate();
 }
 
-bool PartSet_WidgetPoint2D::getPoint2d(const Handle(V3d_View)& theView,
-                                       const TopoDS_Shape& theShape,
-                                       double& theX, double& theY) const
-{
-  if (!theShape.IsNull()) {
-    if (theShape.ShapeType() == TopAbs_VERTEX) {
-      const TopoDS_Vertex& aVertex = TopoDS::Vertex(theShape);
-      if (!aVertex.IsNull()) {
-        // A case when point is taken from existing vertex
-        gp_Pnt aPoint = BRep_Tool::Pnt(aVertex);
-        PartSet_Tools::convertTo2D(aPoint, mySketch, theView, theX, theY);
-        return true;
-      }
-    }
-  }
-  return false;
-}
-
 bool PartSet_WidgetPoint2D::setConstraintToPoint(double theClickedX, double theClickedY,
                                   const std::shared_ptr<ModuleBase_ViewerPrs>& theValue)
 {
@@ -627,14 +609,19 @@ void PartSet_WidgetPoint2D::mouseReleased(ModuleBase_IViewWindow* theWindow, QMo
       }
     }
     if (anExternal) {
+      GeomPnt2dPtr aPnt = PartSet_Tools::getPnt2d(aView, aShape, mySketch);
       double aX = 0, aY = 0;
-      if (getPoint2d(aView, aShape, aX, aY) && isFeatureContainsPoint(myFeature, aX, aY)) {
+      if (aPnt) {
+        aX = aPnt->x();
+        aY = aPnt->y();
+      }
+      if (aPnt && isFeatureContainsPoint(myFeature, aX, aY)) {
         // do not create a constraint to the point, which already used by the feature
         // if the feature contains the point, focus is not switched
         setPoint(aX, aY);
       }
       else {
-        if (getPoint2d(aView, aShape, aX, aY))
+        if (aPnt)
           setPoint(aX, aY);
         else {
           if (aShape.ShapeType() == TopAbs_EDGE) {
@@ -674,20 +661,23 @@ void PartSet_WidgetPoint2D::mouseReleased(ModuleBase_IViewWindow* theWindow, QMo
       }
     }
     if (!anExternal) {
-      double aX = 0, aY = 0;
       bool isProcessed = false;
-      if (getPoint2d(aView, aShape, aX, aY) && isFeatureContainsPoint(myFeature, aX, aY)) {
+      GeomPnt2dPtr aPnt = PartSet_Tools::getPnt2d(aView, aShape, mySketch);
+      if (aPnt && isFeatureContainsPoint(myFeature, aPnt->x(), aPnt->y())) {
         // when the point is selected, the coordinates of the point should be set into the attribute
         // if the feature contains the point, focus is not switched
-        setPoint(aX, aY);
+        setPoint(aPnt->x(), aPnt->y());
       }
       else {
+        double aX = 0, aY = 0;
         bool anOrphanPoint = isOrphanPoint(aSelectedFeature, mySketch, aX, aY);
         // do not set a coincidence constraint in the attribute if the feature contains a point
         // with the same coordinates. It is important for line creation in order to do not set
         // the same constraints for the same points, oterwise the result line has zero length.
         bool isAuxiliaryFeature = false;
-        if (getPoint2d(aView, aShape, aX, aY)) {
+        if (aPnt) {
+          aX = aPnt->x();
+          aY = aPnt->y();
           setPoint(aX, aY);
           setConstraintToPoint(aX, aY, aFirstValue);
         }
@@ -738,11 +728,11 @@ void PartSet_WidgetPoint2D::mouseReleased(ModuleBase_IViewWindow* theWindow, QMo
       // external objects e.g. selection of trihedron axis when input end arc point
       updateObject(feature());
 
-      double aX = 0, aY = 0;
-      if (getPoint2d(aView, aShape, aX, aY)) {
+      GeomPnt2dPtr aPnt = PartSet_Tools::getPnt2d(aView, aShape, mySketch);
+      if (aPnt) {
         // do not create a constraint to the point, which already used by the feature
         // if the feature contains the point, focus is not switched
-        setPoint(aX, aY);
+        setPoint(aPnt->x(), aPnt->y());
       }
       emit vertexSelected(); // it stops the reentrant operation
       emit focusOutWidget(this);
index 515a03c4120a7856bf950eee829b15285e887757..3a062cfa486b5fddc1e4ebdba079ab31cf76561e 100644 (file)
@@ -211,14 +211,6 @@ protected:
   virtual void initializeValueByActivate();
 
  private:
-   /// Returns point 2d from selected vertex
-   /// \param theView a view window
-   /// \param theShape a vertex shape
-   /// \param theX an output value of X coordinate
-   /// \param theY an output value of Y coordinate
-   bool getPoint2d(const Handle(V3d_View)& theView, const TopoDS_Shape& theShape,
-                   double& theX, double& theY) const;
-
    /// Creates constrains of the clicked point
    /// \param theClickedX the horizontal coordnate of the point
    /// \param theClickedY the vertical coordnate of the point
@@ -230,6 +222,7 @@ protected:
    /// \return true if succed
    bool setConstraintToObject(const ObjectPtr& theObject);
 
+public:
    /// Returns if the feature is an orphan point, circle or an arc. Returns true if it
    /// has no a coincident to other lines. It processes point, circle and arc features
    /// In circle an arc features, only centers are processed, for other points, it returns
@@ -274,6 +267,7 @@ protected:
                                        const FeaturePtr& theSkipFeature,
                                        const std::shared_ptr<GeomAPI_Pnt2d>& thePoint);
 
+private:
   /// Returns attribute reference if the key is defined in XML definition of this control
   /// \return found attribute or null
   std::shared_ptr<ModelAPI_AttributeRefAttr> attributeRefAttr() const;
index aa7fa8a6f631a3dc5948faa778cf5568e1c79fcf..3a311e71ea5413a578bb1b79bcd359358eb8ed69 100644 (file)
@@ -152,4 +152,42 @@ Il sera nécessaire de créer une pièce afin d&apos;utiliser cette esquisse pou
         <translation>Groupes</translation>
     </message>
 </context>
+<context>
+    <name>PartSet_BSplineWidget</name>
+    <message>
+        <source>Poles and weights</source>
+        <translation>Poteaux et poids</translation>
+    </message>
+    <message>
+        <source>B-spline poles and weights</source>
+        <translation>Poteaux et poids B-spline</translation>
+    </message>
+    <message>
+        <source>Pole %1</source>
+        <translation>Pôle %1</translation>
+    </message>
+    <message>
+        <source>Weight :</source>
+        <translation>Poids :</translation>
+    </message>
+    <message>
+        <source>Add a new pole after the current</source>
+        <translation>Ajouter un nouveau pôle après le courant</translation>
+    </message>
+</context>
+<context>
+    <name>PartSet_WidgetBSplinePoints</name>
+    <message>
+        <source>Pole %1</source>
+        <translation>Pôle %1</translation>
+    </message>
+    <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>Spécifiez si le polygone de contrôle doit Ãªtre créé</translation>
+    </message>
+</context>
 </TS>
index 9d2b423d8a244dc27cab1286d334d11b2ec35c43..c81d857be876744e1ae073b9f30c09cd77b8b3f7 100644 (file)
@@ -22,6 +22,7 @@ INCLUDE(Common)
 SET(PROJECT_HEADERS
   SketchAPI.h
   SketchAPI_Arc.h
+  SketchAPI_BSpline.h
   SketchAPI_Circle.h
   SketchAPI_Constraint.h
   SketchAPI_ConstraintAngle.h
@@ -45,6 +46,7 @@ SET(PROJECT_HEADERS
 
 SET(PROJECT_SOURCES
   SketchAPI_Arc.cpp
+  SketchAPI_BSpline.cpp
   SketchAPI_Circle.cpp
   SketchAPI_Constraint.cpp
   SketchAPI_ConstraintAngle.cpp
index 07f596c21909c1356ccbdebbcd58ca112d354aa5..ad38d9c556d450ac0124df660f948462689a5df3 100644 (file)
 %include "std_shared_ptr.i"
 
 // function with named parameters
+%feature("kwargs") SketchAPI_BSpline::controlPoles;
+%feature("kwargs") SketchAPI_BSpline::controlPolygon;
 %feature("kwargs") SketchAPI_Ellipse::construction;
 %feature("kwargs") SketchAPI_EllipticArc::construction;
+%feature("kwargs") SketchAPI_Sketch::addSpline;
 %feature("kwargs") SketchAPI_Sketch::setAngle;
 
 // shared pointers
@@ -58,6 +61,8 @@
 %shared_ptr(SketchAPI_MacroEllipse)
 %shared_ptr(SketchAPI_EllipticArc)
 %shared_ptr(SketchAPI_MacroEllipticArc)
+%shared_ptr(SketchAPI_BSpline)
+%shared_ptr(SketchAPI_BSplinePeriodic)
 %shared_ptr(SketchAPI_Constraint)
 %shared_ptr(SketchAPI_ConstraintAngle)
 %shared_ptr(SketchAPI_IntersectionPoint)
@@ -75,6 +80,7 @@
 %template(InterfaceList) std::list<std::shared_ptr<ModelHighAPI_Interface> >;
 %template(EntityList)    std::list<std::shared_ptr<SketchAPI_SketchEntity> >;
 %template(SketchPointList) std::list<std::shared_ptr<SketchAPI_Point> >;
+%template(GeomPnt2dList) std::list<std::shared_ptr<GeomAPI_Pnt2d> >;
 // std::pair -> []
 %template(PointRefAttrPair) std::pair<std::shared_ptr<GeomAPI_Pnt2d>, ModelHighAPI_RefAttr>;
 
 // fix compilarion error: 'res*' was not declared in this scope
 %typemap(freearg) const std::pair<std::shared_ptr<GeomAPI_Pnt2d>, ModelHighAPI_RefAttr> & {}
 
+
+%typemap(in) const std::list<std::shared_ptr<GeomAPI_Pnt2d> > & (std::list<std::shared_ptr<GeomAPI_Pnt2d> > temp) {
+  std::shared_ptr<GeomAPI_Pnt2d> * temp_point = 0;
+  int newmem = 0;
+  if (PySequence_Check($input)) {
+    for (Py_ssize_t i = 0; i < PySequence_Size($input); ++i) {
+      PyObject * item = PySequence_GetItem($input, i);
+      if (PyTuple_Check(item)) {
+        if (PyTuple_Size(item) == 2) {
+          double x = (double)PyFloat_AsDouble(PySequence_GetItem(item, 0));
+          double y = (double)PyFloat_AsDouble(PySequence_GetItem(item, 1));
+          temp.push_back(std::shared_ptr<GeomAPI_Pnt2d>(new GeomAPI_Pnt2d(x, y)));
+        } else {
+          PyErr_SetString(PyExc_TypeError, "argument must a list of 2D points.");
+          return NULL;
+        }
+      } else
+      if ((SWIG_ConvertPtrAndOwn(item, (void **)&temp_point, $descriptor(std::shared_ptr<GeomAPI_Pnt2d> *), SWIG_POINTER_EXCEPTION, &newmem)) == 0) {
+        temp.push_back(*temp_point);
+        if (temp_point && (newmem & SWIG_CAST_NEW_MEMORY)) {
+          delete temp_point;
+        }
+      } else {
+        PyErr_SetString(PyExc_TypeError, "argument must a list of 2D points.");
+        return NULL;
+      }
+      Py_DECREF(item);
+    }
+    $1 = &temp;
+  } else {
+    PyErr_SetString(PyExc_ValueError, "argument must be a tuple of lists.");
+    return NULL;
+  }
+}
+
+%typecheck(SWIG_TYPECHECK_POINTER) std::list<std::shared_ptr<GeomAPI_Pnt2d> >, const std::list<std::shared_ptr<GeomAPI_Pnt2d> >& {
+  std::shared_ptr<GeomAPI_Pnt2d> * temp_point = 0;
+  int newmem = 0;
+  if (PySequence_Check($input)) {
+    for (Py_ssize_t i = 0; i < PySequence_Size($input) && $1; ++i) {
+      PyObject * item = PySequence_GetItem($input, i);
+      if (PyTuple_Check(item)) {
+        if (PyTuple_Size(item) == 2) {
+          if (PyNumber_Check(PySequence_GetItem(item, 0)) && PyNumber_Check(PySequence_GetItem(item, 1))) {
+            $1 = 1;
+          } else {
+            $1 = 0;
+          }
+        } else {
+          $1 = 0;
+        }
+      } else
+      if ((SWIG_ConvertPtrAndOwn(item, (void **)&temp_point, $descriptor(std::shared_ptr<GeomAPI_Pnt2d> *), SWIG_POINTER_EXCEPTION, &newmem)) == 0) {
+        if (temp_point) {
+          $1 = 1;
+        } else {
+          $1 = 0;
+        }
+      }
+      Py_DECREF(item);
+    }
+  } else {
+    $1 = 0;
+  }
+}
+
+// fix compilarion error: 'res*' was not declared in this scope
+%typemap(freearg) const std::list<std::shared_ptr<GeomAPI_Pnt2d> > & {}
+
+
 // all supported interfaces (the order is very important according dependencies: base class first)
 %include "SketchAPI_SketchEntity.h"
 %include "SketchAPI_Point.h"
 %include "SketchAPI_MacroEllipse.h"
 %include "SketchAPI_EllipticArc.h"
 %include "SketchAPI_MacroEllipticArc.h"
+%include "SketchAPI_BSpline.h"
 %include "SketchAPI_Projection.h"
 %include "SketchAPI_Mirror.h"
 %include "SketchAPI_Translation.h"
diff --git a/src/SketchAPI/SketchAPI_BSpline.cpp b/src/SketchAPI/SketchAPI_BSpline.cpp
new file mode 100644 (file)
index 0000000..e804d16
--- /dev/null
@@ -0,0 +1,483 @@
+// Copyright (C) 2019-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 "SketchAPI_BSpline.h"
+
+#include <GeomAPI_BSpline2d.h>
+#include <GeomAPI_Pnt2d.h>
+
+#include <GeomAlgoAPI_EdgeBuilder.h>
+
+#include <ModelHighAPI_Double.h>
+#include <ModelHighAPI_Dumper.h>
+#include <ModelHighAPI_Integer.h>
+#include <ModelHighAPI_Selection.h>
+#include <ModelHighAPI_Tools.h>
+
+#include <SketchPlugin_ConstraintCoincidenceInternal.h>
+#include <SketchPlugin_Line.h>
+#include <SketchPlugin_Point.h>
+
+#include <cmath>
+
+
+SketchAPI_BSpline::SketchAPI_BSpline(const std::shared_ptr<ModelAPI_Feature> & theFeature)
+  : SketchAPI_SketchEntity(theFeature)
+{
+  initialize();
+}
+
+SketchAPI_BSpline::SketchAPI_BSpline(const std::shared_ptr<ModelAPI_Feature>& theFeature,
+                                     bool theInitialize)
+  : SketchAPI_SketchEntity(theFeature)
+{
+  if (theInitialize)
+    initialize();
+}
+
+SketchAPI_BSpline::~SketchAPI_BSpline()
+{
+}
+
+void SketchAPI_BSpline::setByDegreePolesAndWeights(const ModelHighAPI_Integer& theDegree,
+                                                   const std::list<GeomPnt2dPtr>& thePoles,
+                                                   const std::list<ModelHighAPI_Double>& theWeights)
+{
+  std::list<ModelHighAPI_Double> aWeights;
+  if (theWeights.size() <= 1) {
+    // prepare array of equal weights
+    aWeights.assign(thePoles.size(),
+        theWeights.empty() ? ModelHighAPI_Double(1.0) : theWeights.front());
+  }
+  else
+    aWeights = theWeights;
+
+  ModelHighAPI_Integer aDegree = theDegree;
+  std::list<ModelHighAPI_Double> aKnots;
+  std::list<ModelHighAPI_Integer> aMults;
+  getDefaultParameters(thePoles, aWeights, aDegree, aKnots, aMults);
+
+  setByParameters(aDegree, thePoles, aWeights, aKnots, aMults);
+}
+
+void SketchAPI_BSpline::setByParameters(const ModelHighAPI_Integer& theDegree,
+                                        const std::list<GeomPnt2dPtr>& thePoles,
+                                        const std::list<ModelHighAPI_Double>& theWeights,
+                                        const std::list<ModelHighAPI_Double>& theKnots,
+                                        const std::list<ModelHighAPI_Integer>& theMults)
+{
+  fillAttribute(theDegree, degree());
+
+  fillAttribute(thePoles, poles());
+  if (theWeights.size() <= 1) {
+    // prepare array of equal weights
+    std::list<ModelHighAPI_Double> aWeights(thePoles.size(),
+        theWeights.empty() ? ModelHighAPI_Double(1.0) : theWeights.front());
+    fillAttribute(aWeights, weights());
+  }
+  else
+    fillAttribute(theWeights, weights());
+
+  fillAttribute(theKnots, knots());
+  fillAttribute(theMults, multiplicities());
+
+  if (feature()->getKind() != SketchPlugin_BSplinePeriodic::ID())
+    setStartAndEndPoints();
+  execute();
+}
+
+void SketchAPI_BSpline::setStartAndEndPoints()
+{
+  fillAttribute(poles()->pnt(0), startPoint());
+  fillAttribute(poles()->pnt(poles()->size() - 1), endPoint());
+}
+
+void SketchAPI_BSpline::setByExternal(const ModelHighAPI_Selection & theExternal)
+{
+  fillAttribute(theExternal, external());
+  execute();
+}
+
+static CompositeFeaturePtr sketchForFeature(FeaturePtr theFeature)
+{
+  const std::set<AttributePtr>& aRefs = theFeature->data()->refsToMe();
+  for (std::set<AttributePtr>::const_iterator anIt = aRefs.begin(); anIt != aRefs.end(); ++anIt)
+    if ((*anIt)->id() == SketchPlugin_Sketch::FEATURES_ID())
+      return std::dynamic_pointer_cast<ModelAPI_CompositeFeature>((*anIt)->owner());
+  return CompositeFeaturePtr();
+}
+
+static void createInternalConstraint(const CompositeFeaturePtr& theSketch,
+                                     const AttributePoint2DPtr& thePoint,
+                                     const AttributePoint2DArrayPtr& thePoles,
+                                     const int thePoleIndex)
+{
+  FeaturePtr aConstraint = theSketch->addFeature(SketchPlugin_ConstraintCoincidenceInternal::ID());
+  aConstraint->refattr(SketchPlugin_Constraint::ENTITY_A())->setAttr(thePoint);
+  aConstraint->refattr(SketchPlugin_Constraint::ENTITY_B())->setAttr(thePoles);
+  aConstraint->integer(SketchPlugin_ConstraintCoincidenceInternal::INDEX_ENTITY_B())
+      ->setValue(thePoleIndex);
+  aConstraint->execute();
+}
+
+static void createPole(const CompositeFeaturePtr& theSketch,
+                       const FeaturePtr& theBSpline,
+                       const AttributePoint2DArrayPtr& thePoles,
+                       const int thePoleIndex,
+                       const bool theAuxiliary,
+                       std::list<FeaturePtr>& theEntities)
+{
+  GeomPnt2dPtr aPole = thePoles->pnt(thePoleIndex);
+
+  FeaturePtr aPointFeature = theSketch->addFeature(SketchPlugin_Point::ID());
+  AttributePoint2DPtr aCoord = std::dynamic_pointer_cast<GeomDataAPI_Point2D>(
+      aPointFeature->attribute(SketchPlugin_Point::COORD_ID()));
+  aCoord->setValue(aPole);
+  aPointFeature->reference(SketchPlugin_Point::PARENT_ID())->setValue(theBSpline);
+  aPointFeature->execute();
+
+  std::ostringstream aName;
+  aName << theBSpline->name() << "_" << thePoles->id() << "_" << thePoleIndex;
+  aPointFeature->data()->setName(aName.str());
+  aPointFeature->lastResult()->data()->setName(aName.str());
+
+  aPointFeature->boolean(SketchPlugin_Point::AUXILIARY_ID())->setValue(theAuxiliary);
+
+  createInternalConstraint(theSketch, aCoord, thePoles, thePoleIndex);
+
+  theEntities.push_back(aPointFeature);
+}
+
+static void createSegment(const CompositeFeaturePtr& theSketch,
+                          const FeaturePtr& theBSpline,
+                          const AttributePoint2DArrayPtr& thePoles,
+                          const int theStartPoleIndex,
+                          const bool theAuxiliary,
+                          std::list<FeaturePtr>& theEntities)
+{
+  int aEndPoleIndex = (theStartPoleIndex + 1) % thePoles->size();
+  GeomPnt2dPtr aStartPoint = thePoles->pnt(theStartPoleIndex);
+  GeomPnt2dPtr aEndPoint = thePoles->pnt(aEndPoleIndex);
+
+  FeaturePtr aLineFeature = theSketch->addFeature(SketchPlugin_Line::ID());
+  AttributePoint2DPtr aLineStart = std::dynamic_pointer_cast<GeomDataAPI_Point2D>(
+      aLineFeature->attribute(SketchPlugin_Line::START_ID()));
+  aLineStart->setValue(aStartPoint);
+  AttributePoint2DPtr aLineEnd = std::dynamic_pointer_cast<GeomDataAPI_Point2D>(
+      aLineFeature->attribute(SketchPlugin_Line::END_ID()));
+  aLineEnd->setValue(aEndPoint);
+  aLineFeature->reference(SketchPlugin_Point::PARENT_ID())->setValue(theBSpline);
+  aLineFeature->execute();
+
+  std::ostringstream aName;
+  aName << theBSpline->name() << "_segment_" << theStartPoleIndex << "_" << aEndPoleIndex;
+  aLineFeature->data()->setName(aName.str());
+  aLineFeature->lastResult()->data()->setName(aName.str());
+
+  aLineFeature->boolean(SketchPlugin_Line::AUXILIARY_ID())->setValue(theAuxiliary);
+
+  createInternalConstraint(theSketch, aLineStart, thePoles, theStartPoleIndex);
+  createInternalConstraint(theSketch, aLineEnd, thePoles, aEndPoleIndex);
+
+  theEntities.push_back(aLineFeature);
+}
+
+static void toMapOfAuxIndices(const std::list<int>& theRegular,
+                              const std::list<int>& theAuxiliary,
+                              std::map<int, bool>& theIndices)
+{
+  for (auto it = theRegular.begin(); it != theRegular.end(); ++it)
+    theIndices[*it] = false;
+  for (auto it = theAuxiliary.begin(); it != theAuxiliary.end(); ++it)
+    theIndices[*it] = true;
+}
+
+std::list<std::shared_ptr<SketchAPI_SketchEntity> > SketchAPI_BSpline::controlPoles(
+    const std::list<int>& regular,
+    const std::list<int>& auxiliary) const
+{
+  std::map<int, bool> anAux;
+  toMapOfAuxIndices(regular, auxiliary, anAux);
+
+  std::list<FeaturePtr> anEntities;
+
+  FeaturePtr aBSpline = feature();
+  CompositeFeaturePtr aSketch = sketchForFeature(aBSpline);
+  AttributePoint2DArrayPtr aPoles = poles();
+
+  for (auto it = anAux.begin(); it != anAux.end(); ++it)
+    createPole(aSketch, aBSpline, aPoles, it->first, it->second, anEntities);
+
+  return SketchAPI_SketchEntity::wrap(anEntities);
+}
+
+std::list<std::shared_ptr<SketchAPI_SketchEntity> > SketchAPI_BSpline::controlPolygon(
+    const std::list<int>& regular,
+    const std::list<int>& auxiliary) const
+{
+  std::map<int, bool> anAux;
+  toMapOfAuxIndices(regular, auxiliary, anAux);
+
+  std::list<FeaturePtr> anEntities;
+
+  FeaturePtr aBSpline = feature();
+  CompositeFeaturePtr aSketch = sketchForFeature(aBSpline);
+  AttributePoint2DArrayPtr aPoles = poles();
+
+  for (auto it = anAux.begin(); it != anAux.end(); ++it)
+    createSegment(aSketch, aBSpline, aPoles, it->first, it->second, anEntities);
+
+  return SketchAPI_SketchEntity::wrap(anEntities);
+}
+
+
+void SketchAPI_BSpline::getDefaultParameters(
+    const std::list<std::shared_ptr<GeomAPI_Pnt2d> >& thePoles,
+    const std::list<ModelHighAPI_Double>& theWeights,
+    ModelHighAPI_Integer& theDegree,
+    std::list<ModelHighAPI_Double>& theKnots,
+    std::list<ModelHighAPI_Integer>& theMults) const
+{
+  std::shared_ptr<GeomAPI_BSpline2d> aBSplineCurve;
+  try {
+    std::list<double> aWeights;
+    for (std::list<ModelHighAPI_Double>::const_iterator it = theWeights.begin();
+         it != theWeights.end(); ++it)
+      aWeights.push_back(it->value());
+
+    bool isPeriodic = feature()->getKind() == SketchPlugin_BSplinePeriodic::ID();
+    if (theDegree.intValue() < 0)
+      aBSplineCurve.reset(new GeomAPI_BSpline2d(thePoles, aWeights, isPeriodic));
+    else {
+      aBSplineCurve.reset(new GeomAPI_BSpline2d(theDegree.intValue(), thePoles, aWeights,
+                                                std::list<double>(), std::list<int>(), isPeriodic));
+    }
+  }
+  catch (...) {
+    // cannot build a B-spline curve
+    return;
+  }
+
+  theDegree = aBSplineCurve->degree();
+  std::list<double> aKnots = aBSplineCurve->knots();
+  std::list<int> aMults = aBSplineCurve->mults();
+  theKnots.assign(aKnots.begin(), aKnots.end());
+  theMults.assign(aMults.begin(), aMults.end());
+}
+
+void SketchAPI_BSpline::checkDefaultParameters(bool& isDefaultDegree,
+                                               bool& isDefaultWeights,
+                                               bool& isDefaultKnotsMults) const
+{
+  static const double TOLERANCE = 1.e-7;
+
+  AttributePoint2DArrayPtr aPolesAttr = poles();
+  AttributeDoubleArrayPtr aWeightsAttr = weights();
+  AttributeDoubleArrayPtr aKnotsAttr = knots();
+  AttributeIntArrayPtr aMultsAttr = multiplicities();
+
+  std::list<GeomPnt2dPtr> aPoles;
+  std::list<ModelHighAPI_Double> aWeights;
+  isDefaultWeights = true;
+  for (int anIndex = 0; anIndex < aPolesAttr->size(); ++anIndex) {
+    aPoles.push_back(aPolesAttr->pnt(anIndex));
+    double aCurWeight = aWeightsAttr->value(anIndex);
+    isDefaultWeights = isDefaultWeights && fabs(aCurWeight - 1.0) < TOLERANCE;
+    aWeights.push_back(aCurWeight);
+  }
+
+  ModelHighAPI_Integer aDegree(-1);
+  std::list<ModelHighAPI_Double> aKnots;
+  std::list<ModelHighAPI_Integer> aMults;
+  getDefaultParameters(aPoles, aWeights, aDegree, aKnots, aMults);
+  isDefaultDegree = aDegree.intValue() == degree()->value();
+  if (!isDefaultDegree) {
+    // recalculate knots and multiplicities with the actual degree
+    aDegree = degree()->value();
+    getDefaultParameters(aPoles, aWeights, aDegree, aKnots, aMults);
+  }
+
+  isDefaultKnotsMults = aKnotsAttr->size() == (int)aKnots.size()
+                     && aMultsAttr->size() == (int)aMults.size();
+  if (isDefaultKnotsMults) {
+    std::list<ModelHighAPI_Double>::iterator anIt = aKnots.begin();
+    for (int anIndex = 0; isDefaultKnotsMults && anIt != aKnots.end(); ++anIt, ++anIndex)
+      isDefaultKnotsMults = fabs(anIt->value() - aKnotsAttr->value(anIndex)) < TOLERANCE;
+  }
+  if (isDefaultKnotsMults) {
+    std::list<ModelHighAPI_Integer>::iterator anIt = aMults.begin();
+    for (int anIndex = 0; isDefaultKnotsMults && anIt != aMults.end(); ++anIt, ++anIndex)
+      isDefaultKnotsMults = anIt->intValue() == aMultsAttr->value(anIndex);
+  }
+
+  isDefaultDegree = isDefaultDegree && isDefaultKnotsMults;
+  isDefaultWeights = isDefaultWeights && isDefaultKnotsMults;
+}
+
+
+static void bsplineAuxiliaryFeature(const AttributeRefAttrPtr& theReference,
+                                    FeaturePtr& thePoint,
+                                    FeaturePtr& theSegment)
+{
+  ObjectPtr anAuxObject;
+  if (theReference->isObject())
+    anAuxObject = theReference->object();
+  else
+    anAuxObject = theReference->attr()->owner();
+
+  FeaturePtr anAuxFeature = ModelAPI_Feature::feature(anAuxObject);
+  if (anAuxFeature->getKind() == SketchPlugin_Point::ID())
+    thePoint = anAuxFeature;
+  else if (anAuxFeature->getKind() == SketchPlugin_Line::ID() &&
+           theReference->attr()->id() == SketchPlugin_Line::START_ID()) {
+    // process only coincidence with start point
+    theSegment = anAuxFeature;
+  }
+}
+
+static void collectAuxiliaryFeatures(FeaturePtr theBSpline,
+                                     std::map<int, FeaturePtr>& thePoints,
+                                     std::map<int, FeaturePtr>& theSegments)
+{
+  const std::set<AttributePtr>& aRefs = theBSpline->data()->refsToMe();
+  for (std::set<AttributePtr>::const_iterator aRefIt = aRefs.begin();
+       aRefIt != aRefs.end(); ++aRefIt) {
+    FeaturePtr anOwner = ModelAPI_Feature::feature((*aRefIt)->owner());
+    if (anOwner->getKind() == SketchPlugin_ConstraintCoincidenceInternal::ID()) {
+      // process internal constraints only
+      AttributeRefAttrPtr aRefAttrA = anOwner->refattr(SketchPlugin_Constraint::ENTITY_A());
+      AttributeRefAttrPtr aRefAttrB = anOwner->refattr(SketchPlugin_Constraint::ENTITY_B());
+      AttributePtr anAttrA = aRefAttrA->attr();
+      AttributePtr anAttrB = aRefAttrB->attr();
+
+      AttributeIntegerPtr aPoleIndex;
+      FeaturePtr aPoint, aLine;
+      if (anAttrA && anAttrA->attributeType() == GeomDataAPI_Point2DArray::typeId()) {
+        aPoleIndex = anOwner->integer(SketchPlugin_ConstraintCoincidenceInternal::INDEX_ENTITY_A());
+        bsplineAuxiliaryFeature(aRefAttrB, aPoint, aLine);
+      }
+      else if (anAttrB && anAttrB->attributeType() == GeomDataAPI_Point2DArray::typeId()) {
+        aPoleIndex = anOwner->integer(SketchPlugin_ConstraintCoincidenceInternal::INDEX_ENTITY_B());
+        bsplineAuxiliaryFeature(aRefAttrA, aPoint, aLine);
+      }
+
+      if (aPoint)
+        thePoints[aPoleIndex->value()] = aPoint;
+      else if (aLine)
+        theSegments[aPoleIndex->value()] = aLine;
+    }
+  }
+}
+
+void SketchAPI_BSpline::dump(ModelHighAPI_Dumper& theDumper) const
+{
+  if (isCopy())
+    return; // no need to dump copied feature
+
+  FeaturePtr aBase = feature();
+  const std::string& aSketchName = theDumper.parentName(aBase);
+
+  AttributeSelectionPtr anExternal = aBase->selection(SketchPlugin_SketchEntity::EXTERNAL_ID());
+  if (anExternal->context()) {
+    // B-spline is external
+    theDumper << aBase << " = " << aSketchName << ".addSpline(" << anExternal << ")" << std::endl;
+  } else {
+    // check if some B-spline parameters are default and should not be dumped
+    bool isDefaultDegree, isDefaultWeights, isDefaultKnotsMults;
+    checkDefaultParameters(isDefaultDegree, isDefaultWeights, isDefaultKnotsMults);
+
+    theDumper << aBase << " = " << aSketchName << ".addSpline(";
+    if (!isDefaultDegree)
+      theDumper << "degree = " << degree() << ", ";
+    theDumper << "poles = " << poles();
+    if (!isDefaultWeights)
+      theDumper << ", weights = " << weights();
+    if (!isDefaultKnotsMults)
+      theDumper << ", knots = " << knots() << ", multiplicities = " << multiplicities();
+    if (aBase->getKind() == SketchPlugin_BSplinePeriodic::ID())
+      theDumper << ", periodic = True";
+    theDumper << ")" << std::endl;
+  }
+  // dump "auxiliary" flag if necessary
+  SketchAPI_SketchEntity::dump(theDumper);
+
+  // dump control polygon
+  std::map<int, FeaturePtr> anAuxPoles, anAuxSegments;
+  collectAuxiliaryFeatures(aBase, anAuxPoles, anAuxSegments);
+
+  if (!anAuxPoles.empty())
+    dumpControlPolygon(theDumper, aBase, "controlPoles", anAuxPoles);
+  if (!anAuxSegments.empty())
+    dumpControlPolygon(theDumper, aBase, "controlPolygon", anAuxSegments);
+}
+
+static void dumpList(ModelHighAPI_Dumper& theDumper,
+                     const std::string& theAttrName,
+                     const std::set<int>& theIndices)
+{
+  theDumper << theAttrName << " = [";
+  std::set<int>::const_iterator it = theIndices.begin();
+  theDumper << *it;
+  for (++it; it != theIndices.end(); ++it)
+    theDumper << ", " << *it;
+  theDumper << "]";
+}
+
+void SketchAPI_BSpline::dumpControlPolygon(
+    ModelHighAPI_Dumper& theDumper,
+    const FeaturePtr& theBSpline,
+    const std::string& theMethod,
+    const std::map<int, FeaturePtr>& theAuxFeatures) const
+{
+  theDumper << "[";
+  bool isFirst = true;
+  // dump features and split them to auxiliary and regular
+  std::set<int> aRegular, anAuxiliary;
+  for (std::map<int, FeaturePtr>::const_iterator it = theAuxFeatures.begin();
+       it != theAuxFeatures.end(); ++it) {
+    if (!isFirst)
+      theDumper << ", ";
+    theDumper << theDumper.name(it->second, false);
+    theDumper.doNotDumpFeature(it->second);
+    isFirst = false;
+
+    if (it->second->boolean(SketchPlugin_SketchEntity::AUXILIARY_ID())->value())
+      anAuxiliary.insert(it->first);
+    else
+      aRegular.insert(it->first);
+  }
+  theDumper << "] = " << theDumper.name(theBSpline) << "." << theMethod << "(";
+  if (!aRegular.empty()) {
+    dumpList(theDumper, "regular", aRegular);
+    if (!anAuxiliary.empty())
+      theDumper << ", ";
+  }
+  if (!anAuxiliary.empty())
+    dumpList(theDumper, "auxiliary", anAuxiliary);
+  theDumper << ")" << std::endl;
+}
+
+
+
+// =================================================================================================
+SketchAPI_BSplinePeriodic::SketchAPI_BSplinePeriodic(const FeaturePtr& theFeature)
+  : SketchAPI_BSpline(theFeature, false)
+{
+  initialize();
+}
diff --git a/src/SketchAPI/SketchAPI_BSpline.h b/src/SketchAPI/SketchAPI_BSpline.h
new file mode 100644 (file)
index 0000000..377a7b6
--- /dev/null
@@ -0,0 +1,154 @@
+// Copyright (C) 2019-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 SketchAPI_BSpline_H_
+#define SketchAPI_BSpline_H_
+
+#include "SketchAPI.h"
+#include "SketchAPI_SketchEntity.h"
+
+#include <GeomDataAPI_Point2DArray.h>
+
+#include <ModelAPI_AttributeDoubleArray.h>
+
+#include <SketchPlugin_BSpline.h>
+#include <SketchPlugin_BSplinePeriodic.h>
+
+#include <ModelHighAPI_Double.h>
+#include <ModelHighAPI_Integer.h>
+
+class ModelHighAPI_Selection;
+
+/// \class SketchAPI_BSpline
+/// \ingroup CPPHighAPI
+/// \brief Interface for BSpline feature.
+class SketchAPI_BSpline : public SketchAPI_SketchEntity
+{
+public:
+  /// Constructor without values.
+  SKETCHAPI_EXPORT
+  explicit SketchAPI_BSpline(const std::shared_ptr<ModelAPI_Feature>& theFeature);
+
+  /// Destructor.
+  SKETCHAPI_EXPORT
+  virtual ~SketchAPI_BSpline();
+
+  INTERFACE_8(SketchPlugin_BSpline::ID(),
+              poles, SketchPlugin_BSplineBase::POLES_ID(),
+              GeomDataAPI_Point2DArray, /** B-spline poles */,
+              weights, SketchPlugin_BSplineBase::WEIGHTS_ID(),
+              ModelAPI_AttributeDoubleArray, /** B-spline weights */,
+              knots, SketchPlugin_BSplineBase::KNOTS_ID(),
+              ModelAPI_AttributeDoubleArray, /** B-spline knots */,
+              multiplicities, SketchPlugin_BSplineBase::MULTS_ID(),
+              ModelAPI_AttributeIntArray, /** Knots multiplicities */,
+              degree, SketchPlugin_BSplineBase::DEGREE_ID(),
+              ModelAPI_AttributeInteger, /** B-spline degree */,
+              startPoint, SketchPlugin_BSpline::START_ID(),
+              GeomDataAPI_Point2D, /** First pole of B-spline */,
+              endPoint, SketchPlugin_BSpline::END_ID(),
+              GeomDataAPI_Point2D, /** Last pole of B-spline */,
+              external, SketchPlugin_BSplineBase::EXTERNAL_ID(),
+              ModelAPI_AttributeSelection, /** External */)
+
+  /// Set by poles and weights.
+  SKETCHAPI_EXPORT
+  void setByDegreePolesAndWeights(const ModelHighAPI_Integer& theDegree,
+                                  const std::list<std::shared_ptr<GeomAPI_Pnt2d> >& thePoles,
+                                  const std::list<ModelHighAPI_Double>& theWeights);
+
+  /// Initialize by full set of B-spline parameters.
+  SKETCHAPI_EXPORT
+  void setByParameters(const ModelHighAPI_Integer& theDegree,
+                       const std::list<std::shared_ptr<GeomAPI_Pnt2d> >& thePoles,
+                       const std::list<ModelHighAPI_Double>& theWeights,
+                       const std::list<ModelHighAPI_Double>& theKnots,
+                       const std::list<ModelHighAPI_Integer>& theMults);
+
+  /// Set by external.
+  SKETCHAPI_EXPORT
+  void setByExternal(const ModelHighAPI_Selection& theExternal);
+
+  /// Generate list of construction points coincident with B-spline poles
+  SKETCHAPI_EXPORT
+  std::list<std::shared_ptr<SketchAPI_SketchEntity> > controlPoles(
+      const std::list<int>& regular   = std::list<int>(),
+      const std::list<int>& auxiliary = std::list<int>()) const;
+
+  /// Generate control polygon for B-spline curve
+  SKETCHAPI_EXPORT
+  std::list<std::shared_ptr<SketchAPI_SketchEntity> > controlPolygon(
+      const std::list<int>& regular   = std::list<int>(),
+      const std::list<int>& auxiliary = std::list<int>()) const;
+
+  /// Dump wrapped feature
+  SKETCHAPI_EXPORT
+  virtual void dump(ModelHighAPI_Dumper& theDumper) const;
+
+protected:
+  SketchAPI_BSpline(const std::shared_ptr<ModelAPI_Feature>& theFeature, bool theInitialize);
+
+private:
+  /// Initialize start and end points of B-spline and apply internal coincidence
+  /// constraint to keep them on the corresponding pole.
+  void setStartAndEndPoints();
+
+  /// Compute default B-spline parameters (degree, knots and multiplicities)
+  /// basing on hte given poles and weights
+  void getDefaultParameters(const std::list<std::shared_ptr<GeomAPI_Pnt2d> >& thePoles,
+                            const std::list<ModelHighAPI_Double>& theWeights,
+                            ModelHighAPI_Integer& theDegree,
+                            std::list<ModelHighAPI_Double>& theKnots,
+                            std::list<ModelHighAPI_Integer>& theMults) const;
+
+  /// Check what parameters of B-spline are default
+  void checkDefaultParameters(bool& isDefaultDegree,
+                              bool& isDefaultWeights,
+                              bool& isDefaultKnotsMults) const;
+
+  void dumpControlPolygon(ModelHighAPI_Dumper& theDumper,
+                          const FeaturePtr& theBSpline,
+                          const std::string& theMethod,
+                          const std::map<int, FeaturePtr>& theAuxFeatures) const;
+};
+
+/// Pointer on B-spline object.
+typedef std::shared_ptr<SketchAPI_BSpline> BSplinePtr;
+
+
+
+/// \class SketchAPI_BSplinePeriodic
+/// \ingroup CPPHighAPI
+/// \brief Interface for BSplinePeriodic feature.
+class SketchAPI_BSplinePeriodic : public SketchAPI_BSpline
+{
+public:
+  /// Constructor without values.
+  SKETCHAPI_EXPORT
+  explicit SketchAPI_BSplinePeriodic(const std::shared_ptr<ModelAPI_Feature>& theFeature);
+
+  /// Destructor.
+  SKETCHAPI_EXPORT
+  virtual ~SketchAPI_BSplinePeriodic() {}
+
+  static std::string ID() { return SketchPlugin_BSplinePeriodic::ID(); }
+  virtual std::string getID() { return SketchPlugin_BSplinePeriodic::ID(); }
+};
+
+#endif // SketchAPI_BSpline_H_
index 4baf94150d033d352ab0027122a39bbae3822ffd..aa94fa895e1b89607d0198dafb243b84d94f2ef9 100644 (file)
 #include "SketchAPI_Projection.h"
 
 #include <SketchPlugin_Line.h>
+#include <SketchPlugin_BSpline.h>
+#include <SketchPlugin_BSplinePeriodic.h>
 #include <SketchPlugin_Circle.h>
 #include <SketchPlugin_Ellipse.h>
 #include <SketchPlugin_EllipticArc.h>
 #include <SketchPlugin_Point.h>
 
 #include <SketchAPI_Arc.h>
+#include <SketchAPI_BSpline.h>
 #include <SketchAPI_Circle.h>
 #include <SketchAPI_Ellipse.h>
 #include <SketchAPI_EllipticArc.h>
@@ -108,6 +111,10 @@ std::shared_ptr<SketchAPI_SketchEntity> SketchAPI_Projection::createdFeature() c
     anEntity.reset(new SketchAPI_Ellipse(aProjectedFeature));
   else if (aProjectedFeature->getKind() == SketchPlugin_EllipticArc::ID())
     anEntity.reset(new SketchAPI_EllipticArc(aProjectedFeature));
+  else if (aProjectedFeature->getKind() == SketchPlugin_BSpline::ID())
+    anEntity.reset(new SketchAPI_BSpline(aProjectedFeature));
+  else if (aProjectedFeature->getKind() == SketchPlugin_BSplinePeriodic::ID())
+    anEntity.reset(new SketchAPI_BSplinePeriodic(aProjectedFeature));
   else if (aProjectedFeature->getKind() == SketchPlugin_Point::ID())
     anEntity.reset(new SketchAPI_Point(aProjectedFeature));
 
index 25cac9e71a89939ce77cfa5e9be64e6421379419..a93c26e05a1f586beb5f48ff95fd9ec369ea28c7 100644 (file)
@@ -53,6 +53,7 @@
 #include <ModelHighAPI_Tools.h>
 //--------------------------------------------------------------------------------------
 #include "SketchAPI_Arc.h"
+#include "SketchAPI_BSpline.h"
 #include "SketchAPI_Circle.h"
 #include "SketchAPI_Ellipse.h"
 #include "SketchAPI_EllipticArc.h"
@@ -697,6 +698,32 @@ std::shared_ptr<SketchAPI_EllipticArc> SketchAPI_Sketch::addEllipticArc(
   return EllipticArcPtr(new SketchAPI_EllipticArc(aFeature, theExternalName));
 }
 
+//--------------------------------------------------------------------------------------
+
+std::shared_ptr<SketchAPI_BSpline> SketchAPI_Sketch::addSpline(
+    const ModelHighAPI_Selection & external,
+    const int degree,
+    const std::list<std::shared_ptr<GeomAPI_Pnt2d> >& poles,
+    const std::list<ModelHighAPI_Double>& weights,
+    const std::list<ModelHighAPI_Double>& knots,
+    const std::list<ModelHighAPI_Integer>& multiplicities,
+    const bool periodic)
+{
+  FeaturePtr aFeature = compositeFeature()->addFeature(
+    periodic ? SketchPlugin_BSplinePeriodic::ID() : SketchPlugin_BSpline::ID());
+
+  BSplinePtr aBSpline(periodic ? new SketchAPI_BSplinePeriodic(aFeature)
+                               : new SketchAPI_BSpline(aFeature));
+  if (external.variantType() != ModelHighAPI_Selection::VT_Empty)
+    aBSpline->setByExternal(external);
+  else if (knots.empty() || multiplicities.empty())
+    aBSpline->setByDegreePolesAndWeights(degree, poles, weights);
+  else
+    aBSpline->setByParameters(degree, poles, weights, knots, multiplicities);
+
+  return aBSpline;
+}
+
 //--------------------------------------------------------------------------------------
 std::shared_ptr<SketchAPI_Projection> SketchAPI_Sketch::addProjection(
     const ModelHighAPI_Selection & theExternalFeature,
@@ -1184,7 +1211,19 @@ static std::shared_ptr<GeomAPI_Pnt2d> pointOnEllipse(const FeaturePtr& theFeatur
   return aMajorAxisEnd ? aMajorAxisEnd->pnt() : std::shared_ptr<GeomAPI_Pnt2d>();
 }
 
-static std::shared_ptr<GeomAPI_Pnt2d> middlePoint(const ObjectPtr& theObject)
+static std::shared_ptr<GeomAPI_Pnt2d> middlePointOnBSpline(const FeaturePtr& theFeature,
+                                                           const CompositeFeaturePtr& theSketch)
+{
+  GeomAPI_Edge anEdge(theFeature->lastResult()->shape());
+  GeomPointPtr aMiddle = anEdge.middlePoint();
+
+  std::shared_ptr<SketchPlugin_Sketch> aSketch =
+      std::dynamic_pointer_cast<SketchPlugin_Sketch>(theSketch);
+  return aSketch->to2D(aMiddle);
+}
+
+static std::shared_ptr<GeomAPI_Pnt2d> middlePoint(const ObjectPtr& theObject,
+                                                  const CompositeFeaturePtr& theSketch)
 {
   std::shared_ptr<GeomAPI_Pnt2d> aMiddlePoint;
   FeaturePtr aFeature = ModelAPI_Feature::feature(theObject);
@@ -1203,6 +1242,9 @@ static std::shared_ptr<GeomAPI_Pnt2d> middlePoint(const ObjectPtr& theObject)
       aMiddlePoint = pointOnEllipse(aFeature);
     else if (aFeatureKind == SketchPlugin_EllipticArc::ID())
       aMiddlePoint = pointOnEllipse(aFeature, false);
+    else if (aFeatureKind == SketchPlugin_BSpline::ID() ||
+             aFeatureKind == SketchPlugin_BSplinePeriodic::ID())
+      aMiddlePoint = middlePointOnBSpline(aFeature, theSketch);
   }
   return aMiddlePoint;
 }
@@ -1217,7 +1259,7 @@ void SketchAPI_Sketch::move(const ModelHighAPI_RefAttr& theMovedEntity,
   if (aMessage->movedAttribute())
     anOriginalPosition = pointCoordinates(aMessage->movedAttribute());
   else
-    anOriginalPosition = middlePoint(aMessage->movedObject());
+    anOriginalPosition = middlePoint(aMessage->movedObject(), compositeFeature());
 
   if (!anOriginalPosition)
     return; // something has gone wrong, do not process movement
index f1787c9360267c1f533f37f541c0ff84a067b53b..ddbb1829186f83da593adb0520b1716aeed821cd 100644 (file)
@@ -30,6 +30,7 @@
 
 #include <ModelHighAPI_Interface.h>
 #include <ModelHighAPI_Macro.h>
+#include <ModelHighAPI_Selection.h>
 //--------------------------------------------------------------------------------------
 class ModelAPI_CompositeFeature;
 class ModelAPI_Object;
@@ -37,7 +38,6 @@ class ModelHighAPI_Double;
 class ModelHighAPI_Integer;
 class ModelHighAPI_RefAttr;
 class ModelHighAPI_Reference;
-class ModelHighAPI_Selection;
 class SketchAPI_Arc;
 class SketchAPI_MacroArc;
 class SketchAPI_Circle;
@@ -46,6 +46,7 @@ class SketchAPI_Ellipse;
 class SketchAPI_MacroEllipse;
 class SketchAPI_EllipticArc;
 class SketchAPI_MacroEllipticArc;
+class SketchAPI_BSpline;
 class SketchAPI_IntersectionPoint;
 class SketchAPI_Line;
 class SketchAPI_Mirror;
@@ -324,6 +325,18 @@ public:
   SKETCHAPI_EXPORT
   std::shared_ptr<SketchAPI_EllipticArc> addEllipticArc(const std::string & theExternalName);
 
+  /// Add B-spline
+  SKETCHAPI_EXPORT
+  std::shared_ptr<SketchAPI_BSpline> addSpline(
+      const ModelHighAPI_Selection & external = ModelHighAPI_Selection(),
+      const int degree = -1,
+      const std::list<std::shared_ptr<GeomAPI_Pnt2d> >& poles =
+                                          std::list<std::shared_ptr<GeomAPI_Pnt2d> >(),
+      const std::list<ModelHighAPI_Double>& weights = std::list<ModelHighAPI_Double>(),
+      const std::list<ModelHighAPI_Double>& knots = std::list<ModelHighAPI_Double>(),
+      const std::list<ModelHighAPI_Integer>& multiplicities = std::list<ModelHighAPI_Integer>(),
+      const bool periodic = false);
+
   /// Add projection
   SKETCHAPI_EXPORT
   std::shared_ptr<SketchAPI_Projection> addProjection(
index 467e04349007f9719eb375542bf144417d93f5ea..fa56e6a5c95e1a46f20955d83e6e024390f3811a 100644 (file)
@@ -19,6 +19,7 @@
 
 #include "SketchAPI_SketchEntity.h"
 #include <SketchAPI_Arc.h>
+#include <SketchAPI_BSpline.h>
 #include <SketchAPI_Circle.h>
 #include <SketchAPI_Ellipse.h>
 #include <SketchAPI_EllipticArc.h>
@@ -30,6 +31,7 @@
 #include <ModelHighAPI_Tools.h>
 
 #include <SketchPlugin_Arc.h>
+#include <SketchPlugin_BSpline.h>
 #include <SketchPlugin_Circle.h>
 #include <SketchPlugin_Ellipse.h>
 #include <SketchPlugin_IntersectionPoint.h>
@@ -103,6 +105,12 @@ SketchAPI_SketchEntity::wrap(const std::list<std::shared_ptr<ModelAPI_Feature> >
       aResult.push_back(std::shared_ptr<SketchAPI_SketchEntity>(new SketchAPI_Ellipse(*anIt)));
     else if ((*anIt)->getKind() == SketchPlugin_EllipticArc::ID())
       aResult.push_back(std::shared_ptr<SketchAPI_SketchEntity>(new SketchAPI_EllipticArc(*anIt)));
+    else if ((*anIt)->getKind() == SketchPlugin_BSpline::ID())
+      aResult.push_back(std::shared_ptr<SketchAPI_SketchEntity>(new SketchAPI_BSpline(*anIt)));
+    else if ((*anIt)->getKind() == SketchPlugin_BSplinePeriodic::ID()) {
+      aResult.push_back(
+          std::shared_ptr<SketchAPI_SketchEntity>(new SketchAPI_BSplinePeriodic(*anIt)));
+    }
     else if ((*anIt)->getKind() == SketchPlugin_Point::ID())
       aResult.push_back(std::shared_ptr<SketchAPI_SketchEntity>(new SketchAPI_Point(*anIt)));
     else if ((*anIt)->getKind() == SketchPlugin_IntersectionPoint::ID())
index 93788350bae92690c04502d068eb60227b3b250c..28370b70ce25ebc7c50275432d0d4c4d194e2abb 100644 (file)
@@ -31,6 +31,7 @@
   #include "SketchAPI_MacroEllipse.h"
   #include "SketchAPI_EllipticArc.h"
   #include "SketchAPI_MacroEllipticArc.h"
+  #include "SketchAPI_BSpline.h"
   #include "SketchAPI_Constraint.h"
   #include "SketchAPI_ConstraintAngle.h"
   #include "SketchAPI_IntersectionPoint.h"
index 10105e1ca14b746b2e1ea8fc1cfdbbc29bd64338..27965e742648c96ed226431afd726e030ca30892 100644 (file)
@@ -23,6 +23,9 @@ INCLUDE(UnitTest)
 SET(PROJECT_HEADERS
     SketchPlugin.h
     SketchPlugin_Arc.h
+    SketchPlugin_BSpline.h
+    SketchPlugin_BSplineBase.h
+    SketchPlugin_BSplinePeriodic.h
     SketchPlugin_Circle.h
     SketchPlugin_Constraint.h
     SketchPlugin_ConstraintAngle.h
@@ -54,6 +57,7 @@ SET(PROJECT_HEADERS
     SketchPlugin_Line.h
     SketchPlugin_MacroArc.h
     SketchPlugin_MacroArcReentrantMessage.h
+    SketchPlugin_MacroBSpline.h
     SketchPlugin_MacroCircle.h
     SketchPlugin_MacroEllipse.h
     SketchPlugin_MacroEllipticArc.h
@@ -63,16 +67,19 @@ SET(PROJECT_HEADERS
     SketchPlugin_Point.h
     SketchPlugin_Projection.h
     SketchPlugin_Sketch.h
+    SketchPlugin_SketchDrawer.h
     SketchPlugin_SketchEntity.h
     SketchPlugin_Split.h
     SketchPlugin_Tools.h
     SketchPlugin_Trim.h
     SketchPlugin_Validators.h
-    SketchPlugin_SketchDrawer.h
 )
 
 SET(PROJECT_SOURCES
     SketchPlugin_Arc.cpp
+    SketchPlugin_BSpline.cpp
+    SketchPlugin_BSplineBase.cpp
+    SketchPlugin_BSplinePeriodic.cpp
     SketchPlugin_Circle.cpp
     SketchPlugin_Constraint.cpp
     SketchPlugin_ConstraintAngle.cpp
@@ -102,6 +109,7 @@ SET(PROJECT_SOURCES
     SketchPlugin_IntersectionPoint.cpp
     SketchPlugin_Line.cpp
     SketchPlugin_MacroArc.cpp
+    SketchPlugin_MacroBSpline.cpp
     SketchPlugin_MacroCircle.cpp
     SketchPlugin_MacroEllipse.cpp
     SketchPlugin_MacroEllipticArc.cpp
@@ -111,12 +119,12 @@ SET(PROJECT_SOURCES
     SketchPlugin_Point.cpp
     SketchPlugin_Projection.cpp
     SketchPlugin_Sketch.cpp
+    SketchPlugin_SketchDrawer.cpp
     SketchPlugin_SketchEntity.cpp
     SketchPlugin_Split.cpp
     SketchPlugin_Tools.cpp
     SketchPlugin_Trim.cpp
     SketchPlugin_Validators.cpp
-    SketchPlugin_SketchDrawer.cpp
 )
 
 SET(PROJECT_LIBRARIES
@@ -125,6 +133,7 @@ SET(PROJECT_LIBRARIES
     GeomAlgoAPI
     ModelAPI
     ModelGeomAlgo
+    ModuleBase
     SketcherPrs
     GeomDataAPI
 )
@@ -135,14 +144,18 @@ SET(XML_RESOURCES
 )
 
 SET(TEXT_RESOURCES
-       SketchPlugin_msg_en.ts
-       SketchPlugin_msg_fr.ts
+    SketchPlugin_msg_en.ts
+    SketchPlugin_msg_fr.ts
 )
 
+# sources / moc wrappings
+QT_WRAP_MOC(PROJECT_AUTOMOC ${PROJECT_MOC_HEADERS})
+
+SOURCE_GROUP ("Generated Files" FILES ${PROJECT_AUTOMOC})
 SOURCE_GROUP ("Resource Files" FILES ${TEXT_RESOURCES})
 
 ADD_DEFINITIONS(-DSKETCHPLUGIN_EXPORTS)
-ADD_LIBRARY(SketchPlugin MODULE ${PROJECT_SOURCES} ${PROJECT_HEADERS} ${XML_RESOURCES} ${TEXT_RESOURCES})
+ADD_LIBRARY(SketchPlugin MODULE ${PROJECT_SOURCES} ${PROJECT_HEADERS} ${XML_RESOURCES} ${TEXT_RESOURCES} ${PROJECT_AUTOMOC})
 TARGET_LINK_LIBRARIES(SketchPlugin ${PROJECT_LIBRARIES})
 
 INCLUDE_DIRECTORIES(
@@ -150,10 +163,12 @@ INCLUDE_DIRECTORIES(
   ../Events
   ../ModelAPI
   ../ModelGeomAlgo
+  ../ModuleBase
   ../GeomAPI
   ../GeomAlgoAPI
   ../GeomDataAPI
   ../SketcherPrs
+  ${OpenCASCADE_INCLUDE_DIR}
 )
 
 INSTALL(TARGETS SketchPlugin DESTINATION ${SHAPER_INSTALL_PLUGIN_FILES})
@@ -222,6 +237,7 @@ ADD_UNIT_TESTS(
   TestConstraintAngleBehaviorBackward_2.py
   TestConstraintAngleEllipse.py
   TestConstraintCoincidence.py
+  TestConstraintCoincidenceBSpline.py
   TestConstraintCoincidenceEllipse.py
   TestConstraintCoincidenceEllipticArc.py
   TestConstraintCollinear.py
@@ -250,6 +266,7 @@ ADD_UNIT_TESTS(
   TestConstraintRadius.py
   TestConstraintRadiusFailure.py
   TestConstraintTangent.py
+  TestConstraintTangentBSpline.py
   TestConstraintTangentEllipse.py
   TestConstraintTangentEllipticArc.py
   TestConstraintVertical.py
@@ -258,6 +275,8 @@ ADD_UNIT_TESTS(
   TestCreateArcByThreePoints.py
   TestCreateArcByTransversalLine.py
   TestCreateArcChangeType.py
+  TestCreateBSpline.py
+  TestCreateBSplinePeriodic.py
   TestCreateCircleByCenterAndPassed.py
   TestCreateCircleByThreePoints.py
   TestCreateCircleChangeType.py
@@ -293,12 +312,16 @@ ADD_UNIT_TESTS(
   TestMultiTranslation.py
   TestPresentation.py
   TestProjection.py
+  TestProjectionBSpline.py
+  TestProjectionBSplinePeriodic.py
   TestProjectionEllipse.py
   TestProjectionEllipticArc.py
   TestProjectionIntoResult.py
   TestProjectionUpdate.py
   TestRectangle.py
   TestRemainingDoF.py
+  TestRemoveBSpline.py
+  TestRemoveBSplinePeriodic.py
   TestRemoveEllipse.py
   TestRemoveEllipticArc.py
   TestRemoveSketch.py
@@ -336,6 +359,8 @@ ADD_UNIT_TESTS(
 if(${SKETCHER_CHANGE_RADIUS_WHEN_MOVE})
   ADD_UNIT_TESTS(
     TestMoveArc.py
+    TestMoveBSpline.py
+    TestMoveBSplinePeriodic.py
     TestMoveCircle.py
     TestMoveEllipse.py
     TestMoveEllipticArc.py
diff --git a/src/SketchPlugin/SketchPlugin_BSpline.cpp b/src/SketchPlugin/SketchPlugin_BSpline.cpp
new file mode 100644 (file)
index 0000000..0183573
--- /dev/null
@@ -0,0 +1,50 @@
+// Copyright (C) 2019-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_BSpline.h>
+
+#include <GeomDataAPI_Point2D.h>
+#include <GeomDataAPI_Point2DArray.h>
+
+
+SketchPlugin_BSpline::SketchPlugin_BSpline()
+  : SketchPlugin_BSplineBase()
+{
+}
+
+void SketchPlugin_BSpline::initDerivedClassAttributes()
+{
+  data()->addAttribute(START_ID(), GeomDataAPI_Point2D::typeId());
+  data()->addAttribute(END_ID(), GeomDataAPI_Point2D::typeId());
+
+  SketchPlugin_BSplineBase::initDerivedClassAttributes();
+}
+
+void SketchPlugin_BSpline::attributeChanged(const std::string& theID) {
+  if (theID == POLES_ID()) {
+    AttributePoint2DArrayPtr aPolesArray =
+        std::dynamic_pointer_cast<GeomDataAPI_Point2DArray>(attribute(POLES_ID()));
+    std::dynamic_pointer_cast<GeomDataAPI_Point2D>(
+        attribute(START_ID()))->setValue(aPolesArray->pnt(0));
+    std::dynamic_pointer_cast<GeomDataAPI_Point2D>(
+        attribute(END_ID()))->setValue(aPolesArray->pnt(aPolesArray->size() - 1));
+  }
+  else
+    SketchPlugin_BSplineBase::attributeChanged(theID);
+}
diff --git a/src/SketchPlugin/SketchPlugin_BSpline.h b/src/SketchPlugin/SketchPlugin_BSpline.h
new file mode 100644 (file)
index 0000000..ff27cd3
--- /dev/null
@@ -0,0 +1,72 @@
+// Copyright (C) 2019-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_BSpline_H_
+#define SketchPlugin_BSpline_H_
+
+#include <SketchPlugin_BSplineBase.h>
+
+/**\class SketchPlugin_BSpline
+ * \ingroup Plugins
+ * \brief Feature for creation of the B-spline curve in the sketch.
+ */
+class SketchPlugin_BSpline : public SketchPlugin_BSplineBase
+{
+public:
+  /// B-spline feature kind
+  inline static const std::string& ID()
+  {
+    static const std::string ID("SketchBSpline");
+    return ID;
+  }
+
+  /// start point of B-spline curve
+  inline static const std::string& START_ID()
+  {
+    static const std::string ID("start_point");
+    return ID;
+  }
+  /// end point of B-spline curve
+  inline static const std::string& END_ID()
+  {
+    static const std::string ID("end_point");
+    return ID;
+  }
+
+  /// Returns the kind of a feature
+  SKETCHPLUGIN_EXPORT virtual const std::string& getKind()
+  {
+    static std::string MY_KIND = SketchPlugin_BSpline::ID();
+    return MY_KIND;
+  }
+
+  /// Called on change of any argument-attribute of this object
+  SKETCHPLUGIN_EXPORT virtual void attributeChanged(const std::string& theID);
+
+  /// Use plugin manager for features creation
+  SketchPlugin_BSpline();
+
+protected:
+  /// \brief Initializes attributes of derived class.
+  virtual void initDerivedClassAttributes();
+
+  virtual bool isPeriodic() const { return false; }
+};
+
+#endif
diff --git a/src/SketchPlugin/SketchPlugin_BSplineBase.cpp b/src/SketchPlugin/SketchPlugin_BSplineBase.cpp
new file mode 100644 (file)
index 0000000..380887a
--- /dev/null
@@ -0,0 +1,287 @@
+// Copyright (C) 2019-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_BSplineBase.h>
+
+#include <SketchPlugin_ConstraintCoincidenceInternal.h>
+#include <SketchPlugin_Line.h>
+#include <SketchPlugin_MacroBSpline.h>
+#include <SketchPlugin_Sketch.h>
+
+#include <Events_InfoMessage.h>
+
+#include <GeomAlgoAPI_EdgeBuilder.h>
+
+#include <GeomAPI_BSpline2d.h>
+#include <GeomAPI_Pnt2d.h>
+
+#include <GeomDataAPI_Point2D.h>
+#include <GeomDataAPI_Point2DArray.h>
+
+#include <ModelAPI_AttributeDouble.h>
+#include <ModelAPI_AttributeDoubleArray.h>
+#include <ModelAPI_AttributeIntArray.h>
+#include <ModelAPI_AttributeInteger.h>
+#include <ModelAPI_ResultConstruction.h>
+#include <ModelAPI_Session.h>
+#include <ModelAPI_Validator.h>
+
+
+SketchPlugin_BSplineBase::SketchPlugin_BSplineBase()
+  : SketchPlugin_SketchEntity()
+{
+}
+
+void SketchPlugin_BSplineBase::initDerivedClassAttributes()
+{
+  data()->addAttribute(POLES_ID(), GeomDataAPI_Point2DArray::typeId());
+  data()->addAttribute(WEIGHTS_ID(), ModelAPI_AttributeDoubleArray::typeId());
+  data()->addAttribute(KNOTS_ID(), ModelAPI_AttributeDoubleArray::typeId());
+  data()->addAttribute(MULTS_ID(), ModelAPI_AttributeIntArray::typeId());
+  data()->addAttribute(DEGREE_ID(), ModelAPI_AttributeInteger::typeId());
+
+  data()->addAttribute(EXTERNAL_ID(), ModelAPI_AttributeSelection::typeId());
+  ModelAPI_Session::get()->validators()->registerNotObligatory(getKind(), EXTERNAL_ID());
+}
+
+void SketchPlugin_BSplineBase::execute()
+{
+  SketchPlugin_Sketch* aSketch = sketch();
+  if(!aSketch) {
+    return;
+  }
+
+  AttributePoint2DArrayPtr aPolesArray =
+      std::dynamic_pointer_cast<GeomDataAPI_Point2DArray>(attribute(POLES_ID()));
+  AttributeDoubleArrayPtr aWeightsArray = data()->realArray(WEIGHTS_ID());
+  AttributeDoubleArrayPtr aKnotsArray = data()->realArray(KNOTS_ID());
+  AttributeIntArrayPtr aMultsArray = data()->intArray(MULTS_ID());
+  AttributeIntegerPtr aDegreeAttr = data()->integer(DEGREE_ID());
+
+  // collect poles
+  std::list<GeomPnt2dPtr> aPoles2D;
+  for (int anIndex = 0; anIndex < aPolesArray->size(); ++anIndex) {
+    GeomPnt2dPtr aPole = aPolesArray->pnt(anIndex);
+    aPoles2D.push_back(aPole);
+  }
+  // collect weights
+  std::list<double> aWeights;
+  for (int anIndex = 0; anIndex < aWeightsArray->size(); ++anIndex)
+    aWeights.push_back(aWeightsArray->value(anIndex));
+  // collect knots
+  std::list<double> aKnots;
+  for (int anIndex = 0; anIndex < aKnotsArray->size(); ++anIndex)
+    aKnots.push_back(aKnotsArray->value(anIndex));
+  // collect multiplicities
+  std::list<int> aMults;
+  for (int anIndex = 0; anIndex < aMultsArray->size(); ++anIndex)
+    aMults.push_back(aMultsArray->value(anIndex));
+
+  // create result B-spline curve
+  GeomShapePtr anEdge = GeomAlgoAPI_EdgeBuilder::bsplineOnPlane(aSketch->coordinatePlane(),
+      aPoles2D, aWeights, aKnots, aMults, aDegreeAttr->value(), isPeriodic());
+
+  ResultConstructionPtr aResult = document()->createConstruction(data(), 0);
+  aResult->setShape(anEdge);
+  aResult->setIsInHistory(false);
+  setResult(aResult, 0);
+}
+
+bool SketchPlugin_BSplineBase::isFixed() {
+  return data()->selection(EXTERNAL_ID())->context().get() != NULL;
+}
+
+void SketchPlugin_BSplineBase::attributeChanged(const std::string& theID) {
+  // the second condition for unability to move external segments anywhere
+  if (theID == EXTERNAL_ID() || isFixed()) {
+    std::shared_ptr<GeomAPI_Shape> aSelection = data()->selection(EXTERNAL_ID())->value();
+    if (!aSelection) {
+      // empty shape in selection shows that the shape is equal to context
+      ResultPtr anExtRes = selection(EXTERNAL_ID())->context();
+      if (anExtRes)
+        aSelection = anExtRes->shape();
+    }
+////    // update arguments due to the selection value
+////    if (aSelection && !aSelection->isNull() && aSelection->isEdge()) {
+////      std::shared_ptr<GeomAPI_Edge> anEdge(new GeomAPI_Edge(aSelection));
+////      std::shared_ptr<GeomAPI_Ellipse> anEllipse = anEdge->ellipse();
+////
+////      bool aWasBlocked = data()->blockSendAttributeUpdated(true);
+////      std::shared_ptr<GeomDataAPI_Point2D> aCenterAttr =
+////        std::dynamic_pointer_cast<GeomDataAPI_Point2D>(attribute(CENTER_ID()));
+////      aCenterAttr->setValue(sketch()->to2D(anEllipse->center()));
+////
+////      std::shared_ptr<GeomDataAPI_Point2D> aFocusAttr =
+////        std::dynamic_pointer_cast<GeomDataAPI_Point2D>(attribute(FIRST_FOCUS_ID()));
+////      aFocusAttr->setValue(sketch()->to2D(anEllipse->firstFocus()));
+////
+////      std::shared_ptr<GeomDataAPI_Point2D> aStartAttr =
+////        std::dynamic_pointer_cast<GeomDataAPI_Point2D>(attribute(START_POINT_ID()));
+////      aStartAttr->setValue(sketch()->to2D(anEdge->firstPoint()));
+////
+////      std::shared_ptr<GeomDataAPI_Point2D> aEndAttr =
+////        std::dynamic_pointer_cast<GeomDataAPI_Point2D>(attribute(END_POINT_ID()));
+////      aEndAttr->setValue(sketch()->to2D(anEdge->lastPoint()));
+////
+////      real(MAJOR_RADIUS_ID())->setValue(anEllipse->majorRadius());
+////      real(MINOR_RADIUS_ID())->setValue(anEllipse->minorRadius());
+////
+////      double aStartParam, aMidParam, aEndParam;
+////      anEllipse->parameter(anEdge->firstPoint(), tolerance, aStartParam);
+////      anEllipse->parameter(anEdge->middlePoint(), tolerance, aMidParam);
+////      anEllipse->parameter(anEdge->lastPoint(), tolerance, aEndParam);
+////      if (aEndParam < aStartParam)
+////        aEndParam += 2.0 * PI;
+////      if (aMidParam < aStartParam)
+////        aMidParam += 2.0 * PI;
+////      boolean(REVERSED_ID())->setValue(aMidParam > aEndParam);
+////
+////      data()->blockSendAttributeUpdated(aWasBlocked, false);
+////
+////      fillCharacteristicPoints();
+////    }
+  }
+}
+
+bool SketchPlugin_BSplineBase::customAction(const std::string& theActionId)
+{
+  // parse for the action and an index divided by '#'
+  std::string anAction;
+  int anIndex = -1;
+  size_t pos = theActionId.find('#');
+  if (pos != std::string::npos) {
+    anAction = theActionId.substr(0, pos);
+    anIndex = std::stoi(theActionId.substr(pos + 1));
+  }
+
+  if (anAction == ADD_POLE_ACTION_ID()) {
+    return addPole(anIndex);
+  }
+
+  std::string aMsg = "Error: Feature \"%1\" does not support action \"%2\".";
+  Events_InfoMessage("SketchPlugin_BSplineBase", aMsg).arg(getKind()).arg(anAction).send();
+  return false;
+}
+
+bool SketchPlugin_BSplineBase::addPole(const int theAfter)
+{
+  AttributePoint2DArrayPtr aPolesArray =
+      std::dynamic_pointer_cast<GeomDataAPI_Point2DArray>(attribute(POLES_ID()));
+  AttributeDoubleArrayPtr aWeightsArray = data()->realArray(WEIGHTS_ID());
+
+  int anAfter = (!isPeriodic() && theAfter == aPolesArray->size() - 1) ? theAfter - 1 : theAfter;
+
+  // find internal coincidences applied to the poles with greater indices
+  std::list<AttributeIntegerPtr> aCoincidentPoleIndex;
+  bool hasAuxSegment = false;
+  const std::set<AttributePtr>& aRefs = data()->refsToMe();
+  for (std::set<AttributePtr>::iterator anIt = aRefs.begin(); anIt != aRefs.end(); ++anIt) {
+    FeaturePtr aFeature = ModelAPI_Feature::feature((*anIt)->owner());
+    if (aFeature->getKind() == SketchPlugin_ConstraintCoincidenceInternal::ID()) {
+      AttributeIntegerPtr anIndex;
+      if ((*anIt)->id() == SketchPlugin_ConstraintCoincidenceInternal::ENTITY_A())
+        anIndex = aFeature->integer(SketchPlugin_ConstraintCoincidenceInternal::INDEX_ENTITY_A());
+      else if ((*anIt)->id() == SketchPlugin_ConstraintCoincidenceInternal::ENTITY_B())
+        anIndex = aFeature->integer(SketchPlugin_ConstraintCoincidenceInternal::INDEX_ENTITY_B());
+
+      if (anIndex && anIndex->isInitialized()) {
+        if (anIndex->value() > anAfter)
+          aCoincidentPoleIndex.push_back(anIndex);
+        else if (anIndex->value() == anAfter && !hasAuxSegment) {
+          // check the constrained object is a segment of the control polygon
+          const std::string& anOtherAttr =
+              (*anIt)->id() == SketchPlugin_ConstraintCoincidenceInternal::ENTITY_A() ?
+                               SketchPlugin_ConstraintCoincidenceInternal::ENTITY_B() :
+                               SketchPlugin_ConstraintCoincidenceInternal::ENTITY_A();
+          AttributeRefAttrPtr aRefAttr = aFeature->refattr(anOtherAttr);
+          if (aRefAttr && !aRefAttr->isObject() &&
+              aRefAttr->attr()->id() == SketchPlugin_Line::START_ID()) {
+            hasAuxSegment = true;
+            aCoincidentPoleIndex.push_back(anIndex);
+          }
+        }
+      }
+    }
+  }
+
+  bool aWasBlocked = data()->blockSendAttributeUpdated(true);
+
+  // add new pole and default weight
+  std::list<GeomPnt2dPtr> aPoles;
+  aPolesArray->setSize(aPolesArray->size() + 1);
+  aPolesArray->setPnt(aPolesArray->size() - 1, aPolesArray->pnt(0)); // for periodic spline
+  for (int i = aPolesArray->size() - 2; i > anAfter; --i) {
+    aPoles.push_front(aPolesArray->pnt(i));
+    aPolesArray->setPnt(i + 1, aPoles.front());
+  }
+
+  GeomPnt2dPtr aCurPole = aPolesArray->pnt(anAfter);
+  GeomPnt2dPtr aNextPole = aPolesArray->pnt(anAfter + 1);
+  aPolesArray->setPnt(anAfter + 1, (aCurPole->x() + aNextPole->x()) * 0.5,
+                                   (aCurPole->y() + aNextPole->y()) * 0.5);
+  for (int i = anAfter + 1; i >= 0; --i)
+    aPoles.push_front(aPolesArray->pnt(i));
+
+  std::list<double> aWeights;
+  for (int i = 0; i < aWeightsArray->size(); ++i) {
+    aWeights.push_back(aWeightsArray->value(i));
+    if (i == anAfter)
+      aWeights.push_back(1.0); // default weight
+  }
+  aWeightsArray->setSize(aWeightsArray->size() + 1);
+  std::list<double>::iterator aWIt = aWeights.begin();
+  for (int i = 0; i < aWeightsArray->size(); ++i, ++aWIt)
+    aWeightsArray->setValue(i, *aWIt);
+
+  // recalculate knots and multiplicities
+  std::shared_ptr<GeomAPI_BSpline2d> aBSplineCurve(
+      new GeomAPI_BSpline2d(aPoles, aWeights, isPeriodic()));
+
+  integer(DEGREE_ID())->setValue(aBSplineCurve->degree());
+
+  AttributeDoubleArrayPtr aKnotsAttr = data()->realArray(SketchPlugin_BSplineBase::KNOTS_ID());
+  std::list<double> aKnots = aBSplineCurve->knots();
+  int aSize = (int)aKnots.size();
+  aKnotsAttr->setSize(aSize);
+  std::list<double>::iterator aKIt = aKnots.begin();
+  for (int index = 0; index < aSize; ++index, ++aKIt)
+    aKnotsAttr->setValue(index, *aKIt);
+
+  AttributeIntArrayPtr aMultsAttr = data()->intArray(SketchPlugin_BSplineBase::MULTS_ID());
+  std::list<int> aMults = aBSplineCurve->mults();
+  aSize = (int)aMults.size();
+  aMultsAttr->setSize(aSize);
+  std::list<int>::iterator aMIt = aMults.begin();
+  for (int index = 0; index < aSize; ++index, ++aMIt)
+    aMultsAttr->setValue(index, *aMIt);
+
+  data()->blockSendAttributeUpdated(aWasBlocked, true);
+
+  // update indices of internal coincidences
+  for (std::list<AttributeIntegerPtr>::iterator aCIt = aCoincidentPoleIndex.begin();
+       aCIt != aCoincidentPoleIndex.end(); ++aCIt)
+    (*aCIt)->setValue((*aCIt)->value() + 1);
+
+  // create auxiliary segment and pole updating the control polygon
+  SketchPlugin_MacroBSpline::createAuxiliaryPole(aPolesArray, anAfter + 1);
+  if (hasAuxSegment)
+    SketchPlugin_MacroBSpline::createAuxiliarySegment(aPolesArray, anAfter, anAfter + 1);
+
+  return true;
+}
diff --git a/src/SketchPlugin/SketchPlugin_BSplineBase.h b/src/SketchPlugin/SketchPlugin_BSplineBase.h
new file mode 100644 (file)
index 0000000..fc75396
--- /dev/null
@@ -0,0 +1,103 @@
+// Copyright (C) 2019-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_BSplineBase_H_
+#define SketchPlugin_BSplineBase_H_
+
+#include <SketchPlugin.h>
+#include <SketchPlugin_SketchEntity.h>
+
+/**\class SketchPlugin_BSplineBase
+ * \ingroup Plugins
+ * \brief Base class for B-spline curves in the sketch.
+ */
+class SketchPlugin_BSplineBase : public SketchPlugin_SketchEntity
+{
+public:
+  /// list of B-spline poles
+  inline static const std::string& POLES_ID()
+  {
+    static const std::string ID("poles");
+    return ID;
+  }
+
+  /// list of B-spline weights
+  inline static const std::string& WEIGHTS_ID()
+  {
+    static const std::string ID("weights");
+    return ID;
+  }
+
+  /// attribute to store the degree of B-spline
+  inline static const std::string& DEGREE_ID()
+  {
+    static const std::string ID("degree");
+    return ID;
+  }
+
+  /// list of B-spline knots
+  inline static const std::string& KNOTS_ID()
+  {
+    static const std::string ID("knots");
+    return ID;
+  }
+
+  /// list of B-spline multiplicities
+  inline static const std::string& MULTS_ID()
+  {
+    static const std::string ID("multiplicities");
+    return ID;
+  }
+
+  /// name for add pole action
+  inline static const std::string& ADD_POLE_ACTION_ID()
+  {
+    static const std::string ID("AddPole");
+    return ID;
+  }
+
+  /// Returns true is sketch element is under the rigid constraint
+  SKETCHPLUGIN_EXPORT virtual bool isFixed();
+
+  /// Called on change of any argument-attribute of this object
+  SKETCHPLUGIN_EXPORT virtual void attributeChanged(const std::string& theID);
+
+  /// Creates a new part document if needed
+  SKETCHPLUGIN_EXPORT virtual void execute();
+
+  /// Updates the B-spline curve.
+  /// \param[in] theActionId action key id (in following form: Action#Index)
+  /// \return \c false in case the action not performed.
+  SKETCHPLUGIN_EXPORT virtual bool customAction(const std::string& theActionId);
+
+protected:
+  /// Called from the derived class
+  SketchPlugin_BSplineBase();
+
+  /// \brief Initializes attributes of derived class.
+  virtual void initDerivedClassAttributes();
+
+  /// \brief Return \c true if the B-spline curve is periodic
+  virtual bool isPeriodic() const = 0;
+
+  /// Add new pole after the pole with the given index
+  bool addPole(const int theAfter);
+};
+
+#endif
diff --git a/src/SketchPlugin/SketchPlugin_BSplinePeriodic.cpp b/src/SketchPlugin/SketchPlugin_BSplinePeriodic.cpp
new file mode 100644 (file)
index 0000000..eb5fa74
--- /dev/null
@@ -0,0 +1,25 @@
+// Copyright (C) 2019-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_BSplinePeriodic.h>
+
+SketchPlugin_BSplinePeriodic::SketchPlugin_BSplinePeriodic()
+  : SketchPlugin_BSplineBase()
+{
+}
diff --git a/src/SketchPlugin/SketchPlugin_BSplinePeriodic.h b/src/SketchPlugin/SketchPlugin_BSplinePeriodic.h
new file mode 100644 (file)
index 0000000..b5b4e62
--- /dev/null
@@ -0,0 +1,53 @@
+// Copyright (C) 2019-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_BSplinePeriodic_H_
+#define SketchPlugin_BSplinePeriodic_H_
+
+#include <SketchPlugin_BSplineBase.h>
+
+/**\class SketchPlugin_BSplinePeriodic
+ * \ingroup Plugins
+ * \brief Feature for creation of the periodic B-spline curve in the sketch.
+ */
+class SketchPlugin_BSplinePeriodic : public SketchPlugin_BSplineBase
+{
+public:
+  /// B-spline feature kind
+  inline static const std::string& ID()
+  {
+    static const std::string ID("SketchBSplinePeriodic");
+    return ID;
+  }
+
+  /// Returns the kind of a feature
+  SKETCHPLUGIN_EXPORT virtual const std::string& getKind()
+  {
+    static std::string MY_KIND = SketchPlugin_BSplinePeriodic::ID();
+    return MY_KIND;
+  }
+
+  /// Use plugin manager for features creation
+  SketchPlugin_BSplinePeriodic();
+
+protected:
+  virtual bool isPeriodic() const { return true; }
+};
+
+#endif
index ccab1cf931cf0b06a53dcdbdc2d9bdfd891b7cff..07d5638ad7e31790fa62d48a6ffe92499cc32029 100644 (file)
 
 #include "SketchPlugin_ConstraintCoincidenceInternal.h"
 
+#include <ModelAPI_AttributeInteger.h>
+#include <ModelAPI_Session.h>
+#include <ModelAPI_Validator.h>
+
 SketchPlugin_ConstraintCoincidenceInternal::SketchPlugin_ConstraintCoincidenceInternal()
 {
 }
@@ -26,6 +30,12 @@ SketchPlugin_ConstraintCoincidenceInternal::SketchPlugin_ConstraintCoincidenceIn
 void SketchPlugin_ConstraintCoincidenceInternal::initAttributes()
 {
   SketchPlugin_ConstraintCoincidence::initAttributes();
+
+  data()->addAttribute(INDEX_ENTITY_A(), ModelAPI_AttributeInteger::typeId());
+  ModelAPI_Session::get()->validators()->registerNotObligatory(getKind(), INDEX_ENTITY_A());
+
+  data()->addAttribute(INDEX_ENTITY_B(), ModelAPI_AttributeInteger::typeId());
+  ModelAPI_Session::get()->validators()->registerNotObligatory(getKind(), INDEX_ENTITY_B());
 }
 
 void SketchPlugin_ConstraintCoincidenceInternal::execute()
index fac0060ebb86b2963ba8aeb8100bb30a6efafdcd..d5a65f8101163da9a1f5a2699144ba2801747d34 100644 (file)
@@ -30,7 +30,7 @@
 class SketchPlugin_ConstraintCoincidenceInternal : public SketchPlugin_ConstraintCoincidence
 {
   public:
-  /// Coincidence constraint kind
+  /// \brief Coincidence constraint kind
   inline static const std::string& ID()
   {
     static const std::string MY_CONSTRAINT_COINCIDENCE_ID("SketchConstraintCoincidenceInternal");
@@ -43,7 +43,20 @@ class SketchPlugin_ConstraintCoincidenceInternal : public SketchPlugin_Constrain
     return MY_KIND;
   }
 
-  /// Returns the AIS preview
+  /// \brief Index of point in the array if the first attribute is an array
+  inline static const std::string& INDEX_ENTITY_A()
+  {
+    static const std::string MY_INDEX("ConstraintEntityA_Index");
+    return MY_INDEX;
+  }
+  /// \brief Index of point in the array if the second attribute is an array
+  inline static const std::string& INDEX_ENTITY_B()
+  {
+    static const std::string MY_INDEX("ConstraintEntityB_Index");
+    return MY_INDEX;
+  }
+
+  /// \brief Returns the AIS preview
   SKETCHPLUGIN_EXPORT virtual AISObjectPtr getAISObject(AISObjectPtr thePrevious);
 
   /// \brief Creates a new part document if needed
diff --git a/src/SketchPlugin/SketchPlugin_MacroBSpline.cpp b/src/SketchPlugin/SketchPlugin_MacroBSpline.cpp
new file mode 100644 (file)
index 0000000..4f491fe
--- /dev/null
@@ -0,0 +1,387 @@
+// Copyright (C) 2019-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_MacroBSpline.h>
+
+#include <SketchPlugin_BSpline.h>
+#include <SketchPlugin_BSplinePeriodic.h>
+#include <SketchPlugin_ConstraintCoincidenceInternal.h>
+#include <SketchPlugin_Line.h>
+#include <SketchPlugin_Point.h>
+#include <SketchPlugin_Tools.h>
+#include <SketchPlugin_Sketch.h>
+
+#include <ModelAPI_AttributeDoubleArray.h>
+#include <ModelAPI_AttributeInteger.h>
+#include <ModelAPI_AttributeRefAttrList.h>
+#include <ModelAPI_Events.h>
+#include <ModelAPI_EventReentrantMessage.h>
+#include <ModelAPI_Session.h>
+#include <ModelAPI_Validator.h>
+
+#include <GeomDataAPI_Point2DArray.h>
+
+#include <GeomAlgoAPI_CompoundBuilder.h>
+#include <GeomAlgoAPI_EdgeBuilder.h>
+#include <GeomAlgoAPI_PointBuilder.h>
+
+#include <GeomAPI_BSpline2d.h>
+
+#include <sstream>
+
+/// Create internal coincidence constraint with B-spline pole
+static void createInternalConstraint(SketchPlugin_Sketch* theSketch,
+                                     AttributePtr thePoint,
+                                     AttributePtr theBSplinePoles,
+                                     const int thePoleIndex);
+
+
+SketchPlugin_MacroBSpline::SketchPlugin_MacroBSpline()
+  : SketchPlugin_SketchEntity(),
+    myDegree(3),
+    myIsPeriodic(false)
+{
+}
+
+SketchPlugin_MacroBSpline::SketchPlugin_MacroBSpline(bool isPeriodic)
+  : SketchPlugin_SketchEntity(),
+    myDegree(3),
+    myIsPeriodic(isPeriodic)
+{
+}
+
+void SketchPlugin_MacroBSpline::initAttributes()
+{
+  data()->addAttribute(POLES_ID(), GeomDataAPI_Point2DArray::typeId());
+  data()->addAttribute(WEIGHTS_ID(), ModelAPI_AttributeDoubleArray::typeId());
+
+  data()->addAttribute(REF_POLES_ID(), ModelAPI_AttributeRefAttrList::typeId());
+  ModelAPI_Session::get()->validators()->registerNotObligatory(getKind(), REF_POLES_ID());
+
+  data()->addAttribute(CONTROL_POLYGON_ID(), ModelAPI_AttributeBoolean::typeId());
+
+  data()->addAttribute(AUXILIARY_ID(), ModelAPI_AttributeBoolean::typeId());
+}
+
+void SketchPlugin_MacroBSpline::execute()
+{
+  FeaturePtr aBSpline = createBSplineFeature();
+
+  if (boolean(CONTROL_POLYGON_ID())->value()) {
+    std::list<FeaturePtr> aControlPoles;
+    createControlPolygon(aBSpline, aControlPoles);
+    constraintsForPoles(aControlPoles);
+
+    // message to init reentrant operation
+    static Events_ID anId = ModelAPI_EventReentrantMessage::eventId();
+    ReentrantMessagePtr aMessage(new ModelAPI_EventReentrantMessage(anId, this));
+    // set here the last pole to make coincidence with the start point of the next B-spline curve
+    aMessage->setCreatedFeature(aControlPoles.back());
+    Events_Loop::loop()->send(aMessage);
+  }
+}
+
+// LCOV_EXCL_START
+std::string SketchPlugin_MacroBSpline::processEvent(
+                                              const std::shared_ptr<Events_Message>& theMessage)
+{
+  ReentrantMessagePtr aReentrantMessage =
+      std::dynamic_pointer_cast<ModelAPI_EventReentrantMessage>(theMessage);
+  if (aReentrantMessage) {
+    FeaturePtr aCreatedFeature = aReentrantMessage->createdFeature();
+    ObjectPtr anObject = aReentrantMessage->selectedObject();
+    AttributePtr anAttribute = aReentrantMessage->selectedAttribute();
+    std::shared_ptr<GeomAPI_Pnt2d> aClickedPoint = aReentrantMessage->clickedPoint();
+
+    if (aClickedPoint) {
+      // fill points list (it consists of 2 points to make editable the second one)
+      AttributePoint2DArrayPtr aPointArrayAttr =
+          std::dynamic_pointer_cast<GeomDataAPI_Point2DArray>(attribute(POLES_ID()));
+      aPointArrayAttr->setSize(2);
+      aPointArrayAttr->setPnt(0, aClickedPoint);
+      aPointArrayAttr->setPnt(1, aClickedPoint);
+
+      // fill weights
+      AttributeDoubleArrayPtr aWeightsArrayAttr = data()->realArray(WEIGHTS_ID());
+      aWeightsArrayAttr->setSize(2);
+      aWeightsArrayAttr->setValue(0, 1.0);
+      aWeightsArrayAttr->setValue(1, 1.0);
+
+      // fill reference attribute
+      AttributeRefAttrListPtr aRefAttrList =
+          std::dynamic_pointer_cast<ModelAPI_AttributeRefAttrList>(attribute(REF_POLES_ID()));
+      if (anAttribute) {
+        if (!anAttribute->owner() || !anAttribute->owner()->data()->isValid()) {
+          if (aCreatedFeature && anAttribute->id() == SketchPlugin_Point::COORD_ID())
+            anAttribute = aCreatedFeature->attribute(SketchPlugin_Point::COORD_ID());
+        }
+        aRefAttrList->append(anAttribute);
+      }
+    }
+    Events_Loop::loop()->flush(Events_Loop::eventByName(EVENT_OBJECT_UPDATED));
+  }
+  return std::string();
+}
+// LCOV_EXCL_STOP
+
+FeaturePtr SketchPlugin_MacroBSpline::createBSplineFeature()
+{
+  FeaturePtr aBSpline = sketch()->addFeature(
+      myIsPeriodic ? SketchPlugin_BSplinePeriodic::ID() : SketchPlugin_BSpline::ID());
+
+  aBSpline->integer(SketchPlugin_BSplineBase::DEGREE_ID())->setValue(myDegree);
+
+  AttributePoint2DArrayPtr aPoles = std::dynamic_pointer_cast<GeomDataAPI_Point2DArray>(
+      aBSpline->attribute(SketchPlugin_BSplineBase::POLES_ID()));
+  AttributePoint2DArrayPtr aPolesMacro =
+      std::dynamic_pointer_cast<GeomDataAPI_Point2DArray>(attribute(POLES_ID()));
+  aPoles->assign(aPolesMacro);
+
+  AttributeDoubleArrayPtr aWeights =
+      aBSpline->data()->realArray(SketchPlugin_BSplineBase::WEIGHTS_ID());
+  AttributeDoubleArrayPtr aWeightsMacro = data()->realArray(WEIGHTS_ID());
+  int aSize = aWeightsMacro->size();
+  aWeights->setSize(aSize);
+  for (int index = 0; index < aSize; ++index)
+    aWeights->setValue(index, aWeightsMacro->value(index));
+
+  AttributeDoubleArrayPtr aKnots =
+      aBSpline->data()->realArray(SketchPlugin_BSplineBase::KNOTS_ID());
+  aSize = (int)myKnots.size();
+  aKnots->setSize(aSize);
+  std::list<double>::iterator aKIt = myKnots.begin();
+  for (int index = 0; index < aSize; ++index, ++aKIt)
+    aKnots->setValue(index, *aKIt);
+
+  AttributeIntArrayPtr aMults = aBSpline->data()->intArray(SketchPlugin_BSplineBase::MULTS_ID());
+  aSize = (int)myMultiplicities.size();
+  aMults->setSize(aSize);
+  std::list<int>::iterator aMIt = myMultiplicities.begin();
+  for (int index = 0; index < aSize; ++index, ++aMIt)
+    aMults->setValue(index, *aMIt);
+
+  if (!myIsPeriodic) {
+    AttributePoint2DPtr aStartPoint = std::dynamic_pointer_cast<GeomDataAPI_Point2D>(
+        aBSpline->attribute(SketchPlugin_BSpline::START_ID()));
+    aStartPoint->setValue(aPoles->pnt(0));
+
+    AttributePoint2DPtr aEndPoint = std::dynamic_pointer_cast<GeomDataAPI_Point2D>(
+       aBSpline->attribute(SketchPlugin_BSpline::END_ID()));
+    aEndPoint->setValue(aPoles->pnt(aPoles->size() - 1));
+  }
+
+  aBSpline->boolean(SketchPlugin_SketchEntity::AUXILIARY_ID())->setValue(
+      boolean(AUXILIARY_ID())->value());
+
+  aBSpline->execute();
+
+  return aBSpline;
+}
+
+void SketchPlugin_MacroBSpline::createControlPolygon(FeaturePtr theBSpline,
+                                                     std::list<FeaturePtr>& thePoles)
+{
+  AttributePoint2DArrayPtr aPoles = std::dynamic_pointer_cast<GeomDataAPI_Point2DArray>(
+      theBSpline->attribute(SketchPlugin_BSpline::POLES_ID()));
+  int aSize = aPoles->size();
+  // poles
+  for (int index = 0; index < aSize; ++index)
+    thePoles.push_back(createAuxiliaryPole(aPoles, index));
+  // segments
+  for (int index = 1; index < aSize; ++index)
+    createAuxiliarySegment(aPoles, index - 1, index);
+  if (myIsPeriodic) {
+    // additional segment to close the control polygon
+    createAuxiliarySegment(aPoles, aSize - 1, 0);
+  }
+}
+
+void SketchPlugin_MacroBSpline::constraintsForPoles(const std::list<FeaturePtr>& thePoles)
+{
+  AttributeRefAttrListPtr aRefAttrList = data()->refattrlist(REF_POLES_ID());
+  std::list<std::pair<ObjectPtr, AttributePtr> > aList;
+  if (aRefAttrList)
+    aList = aRefAttrList->list();
+
+  SketchPlugin_Sketch* aSketch = sketch();
+
+  std::list<std::pair<ObjectPtr, AttributePtr> >::iterator aLIt = aList.begin();
+  std::list<FeaturePtr>::const_iterator aPIt = thePoles.begin();
+  for (; aLIt != aList.end() && aPIt != thePoles.end(); ++aPIt, ++aLIt) {
+    // firstly, check the attribute (in this case the object will be not empty too)
+    if (aLIt->second) {
+      SketchPlugin_Tools::createConstraintAttrAttr(aSketch,
+          SketchPlugin_ConstraintCoincidence::ID(),
+          (*aPIt)->attribute(SketchPlugin_Point::COORD_ID()), aLIt->second);
+    }
+    // now add coincidence with the result
+    else if (aLIt->first) {
+      SketchPlugin_Tools::createConstraintAttrObject(aSketch,
+          SketchPlugin_ConstraintCoincidence::ID(),
+          (*aPIt)->attribute(SketchPlugin_Point::COORD_ID()), aLIt->first);
+    }
+  }
+}
+
+AISObjectPtr SketchPlugin_MacroBSpline::getAISObject(AISObjectPtr thePrevious)
+{
+  SketchPlugin_Sketch* aSketch = sketch();
+  if (!aSketch)
+    return AISObjectPtr();
+
+  AttributePoint2DArrayPtr aPolesArray =
+      std::dynamic_pointer_cast<GeomDataAPI_Point2DArray>(attribute(POLES_ID()));
+  AttributeDoubleArrayPtr aWeightsArray = data()->realArray(WEIGHTS_ID());
+
+  if (aPolesArray->size() < 2)
+    return AISObjectPtr();
+
+  std::list<GeomShapePtr> aShapes;
+
+  // convert poles to vertices and collect weights
+  std::list<GeomPnt2dPtr> aPoles2D;
+  std::list<double> aWeights;
+  for (int anIndex = 0; anIndex < aPolesArray->size(); ++anIndex) {
+    double aWeight = aWeightsArray->value(anIndex);
+    if (aWeight < 1.e-10)
+      continue; // skip poles with zero weights
+
+    aWeights.push_back(aWeight);
+
+    GeomPnt2dPtr aPole = aPolesArray->pnt(anIndex);
+    aPoles2D.push_back(aPole);
+    GeomPointPtr aPole3D = aSketch->to3D(aPole->x(), aPole->y());
+    aShapes.push_back(GeomAlgoAPI_PointBuilder::vertex(aPole3D));
+  }
+
+  // create result non-periodic B-spline curve
+  std::shared_ptr<GeomAPI_BSpline2d> aBSplineCurve;
+  try {
+    aBSplineCurve.reset(new GeomAPI_BSpline2d(aPoles2D, aWeights, myIsPeriodic));
+  } catch (...) {
+    // cannot build a B-spline curve
+    return AISObjectPtr();
+  }
+  GeomShapePtr anEdge =
+      GeomAlgoAPI_EdgeBuilder::bsplineOnPlane(aSketch->coordinatePlane(), aBSplineCurve);
+  if (!anEdge)
+    return AISObjectPtr();
+
+  // store transient parameters of B-spline curve
+  myDegree = aBSplineCurve->degree();
+  myKnots = aBSplineCurve->knots();
+  myMultiplicities = aBSplineCurve->mults();
+
+  aShapes.push_back(anEdge);
+  GeomShapePtr aCompound = GeomAlgoAPI_CompoundBuilder::compound(aShapes);
+
+  AISObjectPtr anAIS = thePrevious;
+  if (!anAIS)
+    anAIS.reset(new GeomAPI_AISObject());
+  anAIS->createShape(aCompound);
+
+  // Modify attributes
+  SketchPlugin_Tools::customizeFeaturePrs(anAIS, boolean(AUXILIARY_ID())->value());
+
+  return anAIS;
+}
+
+
+
+// ==========================     Auxiliary functions    ===========================================
+
+FeaturePtr SketchPlugin_MacroBSpline::createAuxiliaryPole(AttributePoint2DArrayPtr theBSplinePoles,
+                                                          const int thePoleIndex)
+{
+  FeaturePtr aBSpline = ModelAPI_Feature::feature(theBSplinePoles->owner());
+
+  SketchPlugin_Sketch* aSketch =
+      std::dynamic_pointer_cast<SketchPlugin_Feature>(aBSpline)->sketch();
+
+  // create child point equal to the B-spline's pole
+  FeaturePtr aPointFeature = aSketch->addFeature(SketchPlugin_Point::ID());
+  aPointFeature->boolean(SketchPlugin_Point::AUXILIARY_ID())->setValue(true);
+  aPointFeature->reference(SketchPlugin_Point::PARENT_ID())->setValue(aBSpline);
+
+  GeomPnt2dPtr aPole = theBSplinePoles->pnt(thePoleIndex);
+
+  AttributePoint2DPtr aCoord = std::dynamic_pointer_cast<GeomDataAPI_Point2D>(
+      aPointFeature->attribute(SketchPlugin_Point::COORD_ID()));
+  aCoord->setValue(aPole);
+
+  aPointFeature->execute();
+
+  std::ostringstream aName;
+  aName << aBSpline->name() << "_" << theBSplinePoles->id() << "_" << thePoleIndex;
+  aPointFeature->data()->setName(aName.str());
+  aPointFeature->lastResult()->data()->setName(aName.str());
+
+  // internal constraint to keep position of the point
+  createInternalConstraint(aSketch, aCoord, theBSplinePoles, thePoleIndex);
+
+  return aPointFeature;
+}
+
+void SketchPlugin_MacroBSpline::createAuxiliarySegment(AttributePoint2DArrayPtr theBSplinePoles,
+                                                       const int thePoleIndex1,
+                                                       const int thePoleIndex2)
+{
+  FeaturePtr aBSpline = ModelAPI_Feature::feature(theBSplinePoles->owner());
+
+  SketchPlugin_Sketch* aSketch =
+      std::dynamic_pointer_cast<SketchPlugin_Feature>(aBSpline)->sketch();
+
+  // create child segment between B-spline poles
+  FeaturePtr aLineFeature = aSketch->addFeature(SketchPlugin_Line::ID());
+  aLineFeature->boolean(SketchPlugin_Point::AUXILIARY_ID())->setValue(true);
+  aLineFeature->reference(SketchPlugin_Point::PARENT_ID())->setValue(aBSpline);
+
+  AttributePoint2DPtr aLineStart = std::dynamic_pointer_cast<GeomDataAPI_Point2D>(
+    aLineFeature->attribute(SketchPlugin_Line::START_ID()));
+  aLineStart->setValue(theBSplinePoles->pnt(thePoleIndex1));
+
+  AttributePoint2DPtr aLineEnd = std::dynamic_pointer_cast<GeomDataAPI_Point2D>(
+    aLineFeature->attribute(SketchPlugin_Line::END_ID()));
+  aLineEnd->setValue(theBSplinePoles->pnt(thePoleIndex2));
+
+  aLineFeature->execute();
+
+  std::ostringstream aName;
+  aName << aBSpline->name() << "_segment_" << thePoleIndex1 << "_" << thePoleIndex2;
+  aLineFeature->data()->setName(aName.str());
+  aLineFeature->lastResult()->data()->setName(aName.str());
+
+  // internal constraints to keep the segment position
+  createInternalConstraint(aSketch, aLineStart, theBSplinePoles, thePoleIndex1);
+  createInternalConstraint(aSketch, aLineEnd, theBSplinePoles, thePoleIndex2);
+}
+
+void createInternalConstraint(SketchPlugin_Sketch* theSketch,
+                              AttributePtr thePoint,
+                              AttributePtr theBSplinePoles,
+                              const int thePoleIndex)
+{
+  std::shared_ptr<SketchPlugin_ConstraintCoincidenceInternal> aConstraint =
+      std::dynamic_pointer_cast<SketchPlugin_ConstraintCoincidenceInternal>(
+      theSketch->addFeature(SketchPlugin_ConstraintCoincidenceInternal::ID()));
+  aConstraint->refattr(SketchPlugin_Constraint::ENTITY_A())->setAttr(thePoint);
+  aConstraint->refattr(SketchPlugin_Constraint::ENTITY_B())->setAttr(theBSplinePoles);
+  aConstraint->integer(SketchPlugin_ConstraintCoincidenceInternal::INDEX_ENTITY_B())
+      ->setValue(thePoleIndex);
+}
diff --git a/src/SketchPlugin/SketchPlugin_MacroBSpline.h b/src/SketchPlugin/SketchPlugin_MacroBSpline.h
new file mode 100644 (file)
index 0000000..860c34f
--- /dev/null
@@ -0,0 +1,160 @@
+// Copyright (C) 2019-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_MacroBSpline_H_
+#define SketchPlugin_MacroBSpline_H_
+
+#include <ModelAPI_IReentrant.h>
+
+#include "SketchPlugin.h"
+
+#include "SketchPlugin_SketchEntity.h"
+
+#include <GeomAPI_IPresentable.h>
+
+class GeomAPI_Circ2d;
+class GeomAPI_Pnt2d;
+
+class GeomDataAPI_Point2DArray;
+
+/**\class SketchPlugin_MacroBSpline
+ * \ingroup Plugins
+ * \brief Feature for creation of the new B-spline in Sketch.
+ */
+class SketchPlugin_MacroBSpline : public SketchPlugin_SketchEntity,
+                                  public GeomAPI_IPresentable,
+                                  public ModelAPI_IReentrant
+{
+public:
+  /// B-spline macro feature kind
+  inline static const std::string& ID()
+  {
+    static const std::string ID("SketchMacroBSpline");
+    return ID;
+  }
+
+
+  /// list of B-spline poles
+  inline static const std::string& POLES_ID()
+  {
+    static const std::string ID("poles");
+    return ID;
+  }
+
+  /// list of references of B-spline poles
+  inline static const std::string& REF_POLES_ID()
+  {
+    static const std::string ID("poles_ref");
+    return ID;
+  }
+
+  /// list of B-spline weights
+  inline static const std::string& WEIGHTS_ID()
+  {
+    static const std::string ID("weights");
+    return ID;
+  }
+
+  /// flag attribute whether control polygon is need to be created
+  inline static const std::string& CONTROL_POLYGON_ID()
+  {
+    static const std::string ID("need_control_poly");
+    return ID;
+  }
+
+  /// Returns the kind of a feature
+  SKETCHPLUGIN_EXPORT virtual const std::string& getKind()
+  {
+    static std::string MY_KIND = SketchPlugin_MacroBSpline::ID();
+    return MY_KIND;
+  }
+
+  /// \brief Request for initialization of data model of the feature: adding all attributes.
+  SKETCHPLUGIN_EXPORT virtual void initAttributes();
+
+  /// 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;};
+
+  /// Apply information of the message to current object. It fills reference object,
+  /// tangent type and tangent point refence in case of tangent arc
+  virtual std::string processEvent(const std::shared_ptr<Events_Message>& theMessage);
+
+  /// Use plugin manager for features creation
+  SketchPlugin_MacroBSpline();
+
+protected:
+  SketchPlugin_MacroBSpline(bool isPeriodic);
+
+private:
+  FeaturePtr createBSplineFeature();
+
+  void createControlPolygon(FeaturePtr theBSpline, std::list<FeaturePtr>& thePoles);
+  void constraintsForPoles(const std::list<FeaturePtr>& thePoles);
+
+  /// Create Point feature coincident with the B-spline pole
+  static FeaturePtr createAuxiliaryPole(std::shared_ptr<GeomDataAPI_Point2DArray> theBSplinePoles,
+                                        const int thePoleIndex);
+  /// Create segment between consequtive B-spline poles
+  static void createAuxiliarySegment(std::shared_ptr<GeomDataAPI_Point2DArray> theBSplinePoles,
+                                     const int thePoleIndex1,
+                                     const int thePoleIndex2);
+  friend class SketchPlugin_BSplineBase;
+
+private:
+  std::list<double> myKnots;
+  std::list<int> myMultiplicities;
+  int myDegree;
+  bool myIsPeriodic;
+};
+
+
+/**\class SketchPlugin_MacroBSpline
+* \ingroup Plugins
+* \brief Feature for creation of the new B-spline in Sketch.
+*/
+class SketchPlugin_MacroBSplinePeriodic : public SketchPlugin_MacroBSpline
+{
+public:
+  /// B-spline macro feature kind
+  inline static const std::string& ID()
+  {
+    static const std::string ID("SketchMacroBSplinePeriodic");
+    return ID;
+  }
+
+  /// Returns the kind of a feature
+  SKETCHPLUGIN_EXPORT virtual const std::string& getKind()
+  {
+    return SketchPlugin_MacroBSpline::ID();
+  }
+
+  /// Use plugin manager for features creation
+  SketchPlugin_MacroBSplinePeriodic() : SketchPlugin_MacroBSpline(true) {}
+};
+
+#endif
index ac1b442c82da8b06b32c948a929aaa7427cbc7c4..db8995277246a8b5f079cea9fa7ac79e91e4e247 100644 (file)
@@ -24,6 +24,8 @@
 #include <SketchPlugin_IntersectionPoint.h>
 #include <SketchPlugin_Circle.h>
 #include <SketchPlugin_Arc.h>
+#include <SketchPlugin_BSpline.h>
+#include <SketchPlugin_BSplinePeriodic.h>
 #include <SketchPlugin_Projection.h>
 #include <SketchPlugin_ConstraintAngle.h>
 #include <SketchPlugin_ConstraintCoincidence.h>
@@ -45,6 +47,7 @@
 #include <SketchPlugin_ConstraintTangent.h>
 #include <SketchPlugin_ConstraintVertical.h>
 #include <SketchPlugin_MacroArc.h>
+#include <SketchPlugin_MacroBSpline.h>
 #include <SketchPlugin_MacroCircle.h>
 #include <SketchPlugin_MultiRotation.h>
 #include <SketchPlugin_MultiTranslation.h>
@@ -147,6 +150,8 @@ SketchPlugin_Plugin::SketchPlugin_Plugin()
                               new SketchPlugin_SketchFeatureValidator);
   aFactory->registerValidator("SketchPlugin_MultiRotationAngleValidator",
                               new SketchPlugin_MultiRotationAngleValidator);
+  aFactory->registerValidator("SketchPlugin_BSplineValidator",
+                              new SketchPlugin_BSplineValidator);
 
   // register this plugin
   ModelAPI_Session::get()->registerPlugin(this);
@@ -201,6 +206,10 @@ FeaturePtr SketchPlugin_Plugin::createFeature(std::string theFeatureID)
     return FeaturePtr(new SketchPlugin_Circle);
   } else if (theFeatureID == SketchPlugin_Arc::ID()) {
     return FeaturePtr(new SketchPlugin_Arc);
+  } else if (theFeatureID == SketchPlugin_BSpline::ID()) {
+    return FeaturePtr(new SketchPlugin_BSpline);
+  } else if (theFeatureID == SketchPlugin_BSplinePeriodic::ID()) {
+    return FeaturePtr(new SketchPlugin_BSplinePeriodic);
   } else if (theFeatureID == SketchPlugin_Projection::ID()) {
     return FeaturePtr(new SketchPlugin_Projection);
   } else if (theFeatureID == SketchPlugin_ConstraintCoincidence::ID()) {
@@ -251,6 +260,10 @@ FeaturePtr SketchPlugin_Plugin::createFeature(std::string theFeatureID)
     return FeaturePtr(new SketchPlugin_Trim);
   } else if (theFeatureID == SketchPlugin_MacroArc::ID()) {
     return FeaturePtr(new SketchPlugin_MacroArc);
+  } else if (theFeatureID == SketchPlugin_MacroBSpline::ID()) {
+    return FeaturePtr(new SketchPlugin_MacroBSpline);
+  } else if (theFeatureID == SketchPlugin_MacroBSplinePeriodic::ID()) {
+    return FeaturePtr(new SketchPlugin_MacroBSplinePeriodic);
   } else if (theFeatureID == SketchPlugin_MacroCircle::ID()) {
     return FeaturePtr(new SketchPlugin_MacroCircle);
   } else if (theFeatureID == SketchPlugin_Ellipse::ID()) {
@@ -306,6 +319,8 @@ std::shared_ptr<ModelAPI_FeatureStateMessage> SketchPlugin_Plugin
       aMsg->setState(SketchPlugin_Line::ID(), aHasSketchPlane);
       aMsg->setState(SketchPlugin_Circle::ID(), aHasSketchPlane);
       aMsg->setState(SketchPlugin_Arc::ID(), aHasSketchPlane);
+      aMsg->setState(SketchPlugin_BSpline::ID(), aHasSketchPlane);
+      aMsg->setState(SketchPlugin_BSplinePeriodic::ID(), aHasSketchPlane);
       aMsg->setState(SketchPlugin_Ellipse::ID(), aHasSketchPlane);
       aMsg->setState(SketchPlugin_EllipticArc::ID(), aHasSketchPlane);
       aMsg->setState(SketchPlugin_Projection::ID(), aHasSketchPlane);
@@ -331,6 +346,8 @@ std::shared_ptr<ModelAPI_FeatureStateMessage> SketchPlugin_Plugin
       aMsg->setState(SketchPlugin_Split::ID(), aHasSketchPlane);
       aMsg->setState(SketchPlugin_Trim::ID(), aHasSketchPlane);
       aMsg->setState(SketchPlugin_MacroArc::ID(), aHasSketchPlane);
+      aMsg->setState(SketchPlugin_MacroBSpline::ID(), aHasSketchPlane);
+      aMsg->setState(SketchPlugin_MacroBSplinePeriodic::ID(), aHasSketchPlane);
       aMsg->setState(SketchPlugin_MacroCircle::ID(), aHasSketchPlane);
       aMsg->setState(SketchPlugin_MacroEllipse::ID(), aHasSketchPlane);
       aMsg->setState(SketchPlugin_MacroEllipticArc::ID(), aHasSketchPlane);
index e04031fee6db7cee731dd447ce225900018759b9..e041299b0c5b56a159e06529fa4b329b52a20264 100644 (file)
@@ -20,6 +20,7 @@
 #include <SketchPlugin_Projection.h>
 
 #include <SketchPlugin_Arc.h>
+#include <SketchPlugin_BSpline.h>
 #include <SketchPlugin_Circle.h>
 #include <SketchPlugin_Ellipse.h>
 #include <SketchPlugin_EllipticArc.h>
@@ -31,6 +32,8 @@
 #include <ModelAPI_AttributeRefAttr.h>
 #include <ModelAPI_AttributeSelection.h>
 #include <ModelAPI_AttributeDouble.h>
+#include <ModelAPI_AttributeDoubleArray.h>
+#include <ModelAPI_AttributeInteger.h>
 #include <ModelAPI_ResultConstruction.h>
 #include <ModelAPI_Session.h>
 #include <ModelAPI_Validator.h>
@@ -39,6 +42,7 @@
 
 #include <Events_Loop.h>
 
+#include <GeomAPI_BSpline.h>
 #include <GeomAPI_Circ.h>
 #include <GeomAPI_Edge.h>
 #include <GeomAPI_Ellipse.h>
@@ -49,6 +53,7 @@
 #include <GeomAlgoAPI_EdgeBuilder.h>
 #include <GeomAlgoAPI_Projection.h>
 #include <GeomDataAPI_Point2D.h>
+#include <GeomDataAPI_Point2DArray.h>
 
 #include <cmath>
 
@@ -145,6 +150,14 @@ static const std::set<std::string>& ARC_PROJECTION()
   return aProj;
 }
 
+static const std::set<std::string>& BSPLINE_PROJECTION()
+{
+  static std::set<std::string> aProj;
+  if (aProj.empty())
+    aProj.insert(SketchPlugin_BSpline::ID());
+  return aProj;
+}
+
 
 static const std::set<std::string>& possibleProjectionTypes(GeomEdgePtr theEdge,
                                                             GeomVertexPtr theVertex)
@@ -160,6 +173,8 @@ static const std::set<std::string>& possibleProjectionTypes(GeomEdgePtr theEdge,
       else
         return ARC_PROJECTION();
     }
+    else
+      return BSPLINE_PROJECTION();
   }
   static const std::set<std::string> DUMMY;
   return DUMMY;
@@ -196,8 +211,6 @@ void SketchPlugin_Projection::computeProjection(const std::string& theID)
   // if the type of feature differs with already selected, remove it and create once again
   bool isRebuild = rebuildProjectedFeature(aProjection, aProjType);
 
-  std::shared_ptr<GeomAPI_Pln> aSketchPlane = sketch()->plane();
-
   ResultConstructionPtr aResult =
       std::dynamic_pointer_cast<ModelAPI_ResultConstruction>(lastResult());
   if (!isRebuild && aResult && aResult->shape() && theID == EXTERNAL_FEATURE_ID()) {
@@ -208,158 +221,14 @@ void SketchPlugin_Projection::computeProjection(const std::string& theID)
 
   keepCurrentFeature();
 
-  if (aVertex) {
-    std::shared_ptr<GeomAPI_Pnt> aPrjPnt = aSketchPlane->project(aVertex->point());
-    std::shared_ptr<GeomAPI_Pnt2d> aPntInSketch = sketch()->to2D(aPrjPnt);
-
-    rebuildProjectedFeature(aProjection, POINT_PROJECTION(), SketchPlugin_Point::ID());
+  bool isProjected = false;
+  if (aVertex)
+    isProjected = projectPoint(aProjection, aVertex->point());
+  else
+    isProjected = projectEdge(aProjection, anEdge);
 
-    // update coordinates of projection
-    std::dynamic_pointer_cast<GeomDataAPI_Point2D>(
-        aProjection->attribute(SketchPlugin_Point::COORD_ID()))->setValue(aPntInSketch);
-  }
-  else if (anEdge->isLine()) {
-    std::shared_ptr<GeomAPI_Pnt> aFirst = aSketchPlane->project(anEdge->firstPoint());
-    std::shared_ptr<GeomAPI_Pnt> aLast = aSketchPlane->project(anEdge->lastPoint());
-
-    std::shared_ptr<GeomAPI_Pnt2d> aFirstInSketch = sketch()->to2D(aFirst);
-    std::shared_ptr<GeomAPI_Pnt2d> aLastInSketch = sketch()->to2D(aLast);
-    if (aFirstInSketch->distance(aLastInSketch) < tolerance)
-      return; // line is semi-orthogonal to the sketch plane
-
-    rebuildProjectedFeature(aProjection, LINE_PROJECTION(), SketchPlugin_Line::ID());
-
-    // update attributes of projection
-    std::shared_ptr<GeomDataAPI_Point2D> aStartPnt = std::dynamic_pointer_cast<GeomDataAPI_Point2D>(
-        aProjection->attribute(SketchPlugin_Line::START_ID()));
-    std::shared_ptr<GeomDataAPI_Point2D> aEndPnt = std::dynamic_pointer_cast<GeomDataAPI_Point2D>(
-        aProjection->attribute(SketchPlugin_Line::END_ID()));
-    aStartPnt->setValue(aFirstInSketch);
-    aEndPnt->setValue(aLastInSketch);
-  }
-  else if (anEdge->isCircle() || anEdge->isArc() || anEdge->isEllipse()) {
-    GeomAlgoAPI_Projection aProjAlgo(aSketchPlane);
-    GeomCurvePtr aProjectedCurve = aProjAlgo.project(anEdge);
-
-    if (aProjectedCurve->isCircle()) {
-      GeomAPI_Circ aCircle(aProjectedCurve);
-      GeomPointPtr aCenter = aSketchPlane->project(aCircle.center());
-      GeomPnt2dPtr aCenterInSketch = sketch()->to2D(aCenter);
-
-      if (aProjectedCurve->isTrimmed()) {
-        // ARC is a projection
-        rebuildProjectedFeature(aProjection, ARC_PROJECTION(), SketchPlugin_Arc::ID());
-
-        GeomPointPtr aFirst = aProjectedCurve->getPoint(aProjectedCurve->startParam());
-        GeomPointPtr aLast = aProjectedCurve->getPoint(aProjectedCurve->endParam());
-        GeomPnt2dPtr aFirstInSketch = sketch()->to2D(aSketchPlane->project(aFirst));
-        GeomPnt2dPtr aLastInSketch = sketch()->to2D(aSketchPlane->project(aLast));
-
-        double aNormalsDot = aCircle.normal()->dot(aSketchPlane->direction());
-        if (fabs(fabs(aNormalsDot) - 1.0) > tolerance)
-          return; // arc is not in the plane, parallel to the sketch plane
-
-        bool isInversed = aNormalsDot < 0.;
-
-        bool aWasBlocked = aProjection->data()->blockSendAttributeUpdated(true);
-
-        // update attributes of projection
-        std::shared_ptr<GeomDataAPI_Point2D> aCenterPnt =
-            std::dynamic_pointer_cast<GeomDataAPI_Point2D>(
-            aProjection->attribute(SketchPlugin_Arc::CENTER_ID()));
-        std::shared_ptr<GeomDataAPI_Point2D> aStartPnt =
-            std::dynamic_pointer_cast<GeomDataAPI_Point2D>(
-            aProjection->attribute(SketchPlugin_Arc::START_ID()));
-        std::shared_ptr<GeomDataAPI_Point2D> aEndPnt =
-            std::dynamic_pointer_cast<GeomDataAPI_Point2D>(
-            aProjection->attribute(SketchPlugin_Arc::END_ID()));
-        aStartPnt->setValue(aFirstInSketch);
-        aEndPnt->setValue(aLastInSketch);
-        aCenterPnt->setValue(aCenterInSketch);
-        aProjection->boolean(SketchPlugin_Arc::REVERSED_ID())->setValue(isInversed);
-
-        aProjection->data()->blockSendAttributeUpdated(aWasBlocked);
-      }
-      else {
-        // CIRCLE is a projection
-        rebuildProjectedFeature(aProjection, CIRCLE_ELLIPSE_PROJECTION(),
-                                SketchPlugin_Circle::ID());
-
-        // update attributes of projection
-        std::shared_ptr<GeomDataAPI_Point2D> aCenterPnt =
-            std::dynamic_pointer_cast<GeomDataAPI_Point2D>(
-            aProjection->attribute(SketchPlugin_Circle::CENTER_ID()));
-        aCenterPnt->setValue(aCenterInSketch);
-        aProjection->real(SketchPlugin_Circle::RADIUS_ID())->setValue(aCircle.radius());
-      }
-    }
-    else if (aProjectedCurve->isEllipse()) {
-      GeomAPI_Ellipse anEllipse(aProjectedCurve);
-      GeomPointPtr aCenter = aSketchPlane->project(anEllipse.center());
-      GeomPnt2dPtr aCenterInSketch = sketch()->to2D(aCenter);
-      GeomPointPtr aFocus = aSketchPlane->project(anEllipse.firstFocus());
-      GeomPnt2dPtr aFocusInSketch = sketch()->to2D(aFocus);
-
-      if (aProjectedCurve->isTrimmed()) {
-        // ELLIPTIC ARC is a projection
-        rebuildProjectedFeature(aProjection, ARC_PROJECTION(), SketchPlugin_EllipticArc::ID());
-
-        GeomPointPtr aFirst = aProjectedCurve->getPoint(aProjectedCurve->startParam());
-        GeomPointPtr aLast = aProjectedCurve->getPoint(aProjectedCurve->endParam());
-        GeomPnt2dPtr aFirstInSketch = sketch()->to2D(aSketchPlane->project(aFirst));
-        GeomPnt2dPtr aLastInSketch = sketch()->to2D(aSketchPlane->project(aLast));
-
-        double aNormalsDot = anEllipse.normal()->dot(aSketchPlane->direction());
-        if (fabs(fabs(aNormalsDot) - 1.0) > tolerance)
-          return; // arc is not in the plane, parallel to the sketch plane
-
-        bool isInversed = aNormalsDot < 0.;
-
-        bool aWasBlocked = aProjection->data()->blockSendAttributeUpdated(true);
-
-        // update attributes of projection
-        std::shared_ptr<GeomDataAPI_Point2D> aCenterPnt =
-            std::dynamic_pointer_cast<GeomDataAPI_Point2D>(
-            aProjection->attribute(SketchPlugin_EllipticArc::CENTER_ID()));
-        std::shared_ptr<GeomDataAPI_Point2D> aFocusPnt =
-            std::dynamic_pointer_cast<GeomDataAPI_Point2D>(
-            aProjection->attribute(SketchPlugin_EllipticArc::FIRST_FOCUS_ID()));
-        std::shared_ptr<GeomDataAPI_Point2D> aStartPnt =
-            std::dynamic_pointer_cast<GeomDataAPI_Point2D>(
-            aProjection->attribute(SketchPlugin_EllipticArc::START_POINT_ID()));
-        std::shared_ptr<GeomDataAPI_Point2D> aEndPnt =
-            std::dynamic_pointer_cast<GeomDataAPI_Point2D>(
-            aProjection->attribute(SketchPlugin_EllipticArc::END_POINT_ID()));
-        aStartPnt->setValue(aFirstInSketch);
-        aEndPnt->setValue(aLastInSketch);
-        aCenterPnt->setValue(aCenterInSketch);
-        aFocusPnt->setValue(aFocusInSketch);
-        aProjection->boolean(SketchPlugin_EllipticArc::REVERSED_ID())->setValue(isInversed);
-
-        aProjection->data()->blockSendAttributeUpdated(aWasBlocked);
-      }
-      else {
-        // ELLIPSE is a projection
-        rebuildProjectedFeature(aProjection, CIRCLE_ELLIPSE_PROJECTION(),
-                                SketchPlugin_Ellipse::ID());
-
-        // update attributes of projection
-        std::shared_ptr<GeomDataAPI_Point2D> aCenterPnt =
-            std::dynamic_pointer_cast<GeomDataAPI_Point2D>(
-            aProjection->attribute(SketchPlugin_Ellipse::CENTER_ID()));
-        aCenterPnt->setValue(aCenterInSketch);
-        std::shared_ptr<GeomDataAPI_Point2D> aFocusPnt =
-            std::dynamic_pointer_cast<GeomDataAPI_Point2D>(
-            aProjection->attribute(SketchPlugin_Ellipse::FIRST_FOCUS_ID()));
-        aFocusPnt->setValue(aFocusInSketch);
-        aProjection->real(SketchPlugin_Ellipse::MINOR_RADIUS_ID())->setValue(
-            anEllipse.minorRadius());
-      }
-    }
-    else
-      return;
-  } else
-    return;
+  if (!isProjected)
+    return; // projection is not computed, stop processing
 
   aProjection->boolean(COPY_ID())->setValue(true);
   aProjection->execute();
@@ -407,3 +276,274 @@ bool SketchPlugin_Projection::rebuildProjectedFeature(
     theProjection = sketch()->addFeature(theRequestedFeature);
   return isRebuild;
 }
+
+bool SketchPlugin_Projection::projectPoint(FeaturePtr& theProjection, const GeomPointPtr& thePoint)
+{
+  std::shared_ptr<GeomAPI_Pln> aSketchPlane = sketch()->plane();
+
+  std::shared_ptr<GeomAPI_Pnt> aPrjPnt = aSketchPlane->project(thePoint);
+  std::shared_ptr<GeomAPI_Pnt2d> aPntInSketch = sketch()->to2D(aPrjPnt);
+
+  rebuildProjectedFeature(theProjection, POINT_PROJECTION(), SketchPlugin_Point::ID());
+
+  // update coordinates of projection
+  std::dynamic_pointer_cast<GeomDataAPI_Point2D>(
+      theProjection->attribute(SketchPlugin_Point::COORD_ID()))->setValue(aPntInSketch);
+  return true;
+}
+
+bool SketchPlugin_Projection::projectSegment(FeaturePtr& theProjection, const GeomEdgePtr& theEdge)
+{
+  std::shared_ptr<GeomAPI_Pln> aSketchPlane = sketch()->plane();
+
+  std::shared_ptr<GeomAPI_Pnt> aFirst = aSketchPlane->project(theEdge->firstPoint());
+  std::shared_ptr<GeomAPI_Pnt> aLast = aSketchPlane->project(theEdge->lastPoint());
+
+  std::shared_ptr<GeomAPI_Pnt2d> aFirstInSketch = sketch()->to2D(aFirst);
+  std::shared_ptr<GeomAPI_Pnt2d> aLastInSketch = sketch()->to2D(aLast);
+  if (aFirstInSketch->distance(aLastInSketch) < tolerance)
+    return false; // line is semi-orthogonal to the sketch plane
+
+  rebuildProjectedFeature(theProjection, LINE_PROJECTION(), SketchPlugin_Line::ID());
+
+  // update attributes of projection
+  std::shared_ptr<GeomDataAPI_Point2D> aStartPnt = std::dynamic_pointer_cast<GeomDataAPI_Point2D>(
+      theProjection->attribute(SketchPlugin_Line::START_ID()));
+  std::shared_ptr<GeomDataAPI_Point2D> aEndPnt = std::dynamic_pointer_cast<GeomDataAPI_Point2D>(
+      theProjection->attribute(SketchPlugin_Line::END_ID()));
+  aStartPnt->setValue(aFirstInSketch);
+  aEndPnt->setValue(aLastInSketch);
+
+  return true;
+}
+
+bool SketchPlugin_Projection::projectEdge(FeaturePtr& theProjection, const GeomEdgePtr& theEdge)
+{
+  if (theEdge->isLine())
+    return projectSegment(theProjection, theEdge);
+
+  std::shared_ptr<GeomAPI_Pln> aSketchPlane = sketch()->plane();
+
+  GeomAlgoAPI_Projection aProjAlgo(aSketchPlane);
+  GeomCurvePtr aProjectedCurve = aProjAlgo.project(theEdge);
+
+  bool isOk = false;
+  if (aProjectedCurve->isCircle()) {
+    if (aProjectedCurve->isTrimmed()) {
+      // ARC is a projection
+      isOk = fillArc(theProjection, aProjectedCurve, aSketchPlane);
+    }
+    else {
+      // CIRCLE is a projection
+      isOk = fillCircle(theProjection, aProjectedCurve, aSketchPlane);
+    }
+  }
+  else if (aProjectedCurve->isEllipse()) {
+    if (aProjectedCurve->isTrimmed()) {
+      // ELLIPTIC ARC is a projection
+      isOk = fillEllipticArc(theProjection, aProjectedCurve, aSketchPlane);
+    }
+    else {
+      // ELLIPSE is a projection
+      isOk = fillEllipse(theProjection, aProjectedCurve, aSketchPlane);
+    }
+  }
+  else
+    isOk = fillBSpline(theProjection, aProjectedCurve, aSketchPlane);
+
+  return isOk;
+}
+
+bool SketchPlugin_Projection::fillArc(FeaturePtr& theProjection,
+                                      const GeomCurvePtr& theArc,
+                                      const GeomPlanePtr& thePlane)
+{
+  rebuildProjectedFeature(theProjection, ARC_PROJECTION(), SketchPlugin_Arc::ID());
+
+  GeomAPI_Circ aCircle(theArc);
+
+  double aNormalsDot = aCircle.normal()->dot(thePlane->direction());
+  if (fabs(fabs(aNormalsDot) - 1.0) > tolerance)
+    return false; // arc is not in the plane, parallel to the sketch plane
+
+  bool isInversed = aNormalsDot < 0.;
+
+  GeomPointPtr aCenter = thePlane->project(aCircle.center());
+  GeomPnt2dPtr aCenterInSketch = sketch()->to2D(aCenter);
+
+  GeomPointPtr aFirst = theArc->getPoint(theArc->startParam());
+  GeomPnt2dPtr aFirstInSketch = sketch()->to2D(thePlane->project(aFirst));
+
+  GeomPointPtr aLast = theArc->getPoint(theArc->endParam());
+  GeomPnt2dPtr aLastInSketch = sketch()->to2D(thePlane->project(aLast));
+
+  bool aWasBlocked = theProjection->data()->blockSendAttributeUpdated(true);
+
+  // update attributes of projection
+  std::shared_ptr<GeomDataAPI_Point2D> aCenterPnt =
+      std::dynamic_pointer_cast<GeomDataAPI_Point2D>(
+      theProjection->attribute(SketchPlugin_Arc::CENTER_ID()));
+  std::shared_ptr<GeomDataAPI_Point2D> aStartPnt =
+      std::dynamic_pointer_cast<GeomDataAPI_Point2D>(
+      theProjection->attribute(SketchPlugin_Arc::START_ID()));
+  std::shared_ptr<GeomDataAPI_Point2D> aEndPnt =
+      std::dynamic_pointer_cast<GeomDataAPI_Point2D>(
+      theProjection->attribute(SketchPlugin_Arc::END_ID()));
+  aStartPnt->setValue(aFirstInSketch);
+  aEndPnt->setValue(aLastInSketch);
+  aCenterPnt->setValue(aCenterInSketch);
+  theProjection->boolean(SketchPlugin_Arc::REVERSED_ID())->setValue(isInversed);
+
+  theProjection->data()->blockSendAttributeUpdated(aWasBlocked);
+  return true;
+}
+
+bool SketchPlugin_Projection::fillCircle(FeaturePtr& theProjection,
+                                         const GeomCurvePtr& theCircle,
+                                         const GeomPlanePtr& thePlane)
+{
+  rebuildProjectedFeature(theProjection, CIRCLE_ELLIPSE_PROJECTION(), SketchPlugin_Circle::ID());
+
+  GeomAPI_Circ aCircle(theCircle);
+  GeomPointPtr aCenter = thePlane->project(aCircle.center());
+  GeomPnt2dPtr aCenterInSketch = sketch()->to2D(aCenter);
+
+  // update attributes of projection
+  std::shared_ptr<GeomDataAPI_Point2D> aCenterPnt =
+      std::dynamic_pointer_cast<GeomDataAPI_Point2D>(
+      theProjection->attribute(SketchPlugin_Circle::CENTER_ID()));
+  aCenterPnt->setValue(aCenterInSketch);
+  theProjection->real(SketchPlugin_Circle::RADIUS_ID())->setValue(aCircle.radius());
+  return true;
+}
+
+bool SketchPlugin_Projection::fillEllipse(FeaturePtr& theProjection,
+                                          const GeomCurvePtr& theEllipse,
+                                          const GeomPlanePtr& thePlane)
+{
+  rebuildProjectedFeature(theProjection, CIRCLE_ELLIPSE_PROJECTION(), SketchPlugin_Ellipse::ID());
+
+  GeomAPI_Ellipse anEllipse(theEllipse);
+  GeomPointPtr aCenter = thePlane->project(anEllipse.center());
+  GeomPnt2dPtr aCenterInSketch = sketch()->to2D(aCenter);
+  GeomPointPtr aFocus = thePlane->project(anEllipse.firstFocus());
+  GeomPnt2dPtr aFocusInSketch = sketch()->to2D(aFocus);
+
+  // update attributes of projection
+  std::shared_ptr<GeomDataAPI_Point2D> aCenterPnt =
+      std::dynamic_pointer_cast<GeomDataAPI_Point2D>(
+      theProjection->attribute(SketchPlugin_Ellipse::CENTER_ID()));
+  aCenterPnt->setValue(aCenterInSketch);
+  std::shared_ptr<GeomDataAPI_Point2D> aFocusPnt =
+      std::dynamic_pointer_cast<GeomDataAPI_Point2D>(
+      theProjection->attribute(SketchPlugin_Ellipse::FIRST_FOCUS_ID()));
+  aFocusPnt->setValue(aFocusInSketch);
+  theProjection->real(SketchPlugin_Ellipse::MINOR_RADIUS_ID())->setValue(anEllipse.minorRadius());
+  return true;
+}
+
+bool SketchPlugin_Projection::fillEllipticArc(FeaturePtr& theProjection,
+                                              const GeomCurvePtr& theEllipticArc,
+                                              const GeomPlanePtr& thePlane)
+{
+  rebuildProjectedFeature(theProjection, ARC_PROJECTION(), SketchPlugin_EllipticArc::ID());
+
+  GeomAPI_Ellipse anEllipse(theEllipticArc);
+
+  double aNormalsDot = anEllipse.normal()->dot(thePlane->direction());
+  if (fabs(fabs(aNormalsDot) - 1.0) > tolerance)
+    return false; // arc is not in the plane, parallel to the sketch plane
+
+  bool isInversed = aNormalsDot < 0.;
+
+  GeomPointPtr aCenter = thePlane->project(anEllipse.center());
+  GeomPnt2dPtr aCenterInSketch = sketch()->to2D(aCenter);
+  GeomPointPtr aFocus = thePlane->project(anEllipse.firstFocus());
+  GeomPnt2dPtr aFocusInSketch = sketch()->to2D(aFocus);
+
+  GeomPointPtr aFirst = theEllipticArc->getPoint(theEllipticArc->startParam());
+  GeomPnt2dPtr aFirstInSketch = sketch()->to2D(thePlane->project(aFirst));
+  GeomPointPtr aLast = theEllipticArc->getPoint(theEllipticArc->endParam());
+  GeomPnt2dPtr aLastInSketch = sketch()->to2D(thePlane->project(aLast));
+
+  bool aWasBlocked = theProjection->data()->blockSendAttributeUpdated(true);
+
+  // update attributes of projection
+  std::shared_ptr<GeomDataAPI_Point2D> aCenterPnt =
+      std::dynamic_pointer_cast<GeomDataAPI_Point2D>(
+      theProjection->attribute(SketchPlugin_EllipticArc::CENTER_ID()));
+  std::shared_ptr<GeomDataAPI_Point2D> aFocusPnt =
+      std::dynamic_pointer_cast<GeomDataAPI_Point2D>(
+      theProjection->attribute(SketchPlugin_EllipticArc::FIRST_FOCUS_ID()));
+  std::shared_ptr<GeomDataAPI_Point2D> aStartPnt =
+      std::dynamic_pointer_cast<GeomDataAPI_Point2D>(
+      theProjection->attribute(SketchPlugin_EllipticArc::START_POINT_ID()));
+  std::shared_ptr<GeomDataAPI_Point2D> aEndPnt =
+      std::dynamic_pointer_cast<GeomDataAPI_Point2D>(
+      theProjection->attribute(SketchPlugin_EllipticArc::END_POINT_ID()));
+  aStartPnt->setValue(aFirstInSketch);
+  aEndPnt->setValue(aLastInSketch);
+  aCenterPnt->setValue(aCenterInSketch);
+  aFocusPnt->setValue(aFocusInSketch);
+  theProjection->boolean(SketchPlugin_EllipticArc::REVERSED_ID())->setValue(isInversed);
+
+  theProjection->data()->blockSendAttributeUpdated(aWasBlocked);
+  return true;
+}
+
+bool SketchPlugin_Projection::fillBSpline(FeaturePtr& theProjection,
+                                          const GeomCurvePtr& theCurve,
+                                          const GeomPlanePtr& thePlane)
+{
+  rebuildProjectedFeature(theProjection, BSPLINE_PROJECTION(), SketchPlugin_BSpline::ID());
+
+  GeomAPI_BSpline aBSpline(theCurve);
+
+  theProjection->integer(SketchPlugin_BSpline::DEGREE_ID())->setValue(aBSpline.degree());
+
+  AttributePoint2DArrayPtr aPolesAttr = std::dynamic_pointer_cast<GeomDataAPI_Point2DArray>(
+      theProjection->attribute(SketchPlugin_BSpline::POLES_ID()));
+  std::list<GeomPointPtr> aPoles = aBSpline.poles();
+  aPolesAttr->setSize((int)aPoles.size());
+  std::list<GeomPointPtr>::iterator anIt = aPoles.begin();
+  for (int anIndex = 0; anIt != aPoles.end(); ++anIt, ++anIndex) {
+    GeomPnt2dPtr aPoleInSketch = sketch()->to2D(*anIt);
+    aPolesAttr->setPnt(anIndex, aPoleInSketch);
+  }
+
+  AttributeDoubleArrayPtr aWeightsAttr =
+      theProjection->data()->realArray(SketchPlugin_BSpline::WEIGHTS_ID());
+  std::list<double> aWeights = aBSpline.weights();
+  if (aWeights.empty()) { // rational B-spline
+    int aSize = (int)aPoles.size();
+    aWeightsAttr->setSize(aSize);
+    for (int anIndex = 0; anIndex < aSize; ++anIndex)
+      aWeightsAttr->setValue(anIndex, 1.0);
+  }
+  else { // non-rational B-spline
+    aWeightsAttr->setSize((int)aWeights.size());
+    std::list<double>::iterator anIt = aWeights.begin();
+    for (int anIndex = 0; anIt != aWeights.end(); ++anIt, ++anIndex)
+      aWeightsAttr->setValue(anIndex, *anIt);
+  }
+
+  AttributeDoubleArrayPtr aKnotsAttr =
+      theProjection->data()->realArray(SketchPlugin_BSpline::KNOTS_ID());
+  std::list<double> aKnots = aBSpline.knots();
+  int aSize = (int)aKnots.size();
+  aKnotsAttr->setSize(aSize);
+  std::list<double>::iterator aKIt = aKnots.begin();
+  for (int index = 0; index < aSize; ++index, ++aKIt)
+    aKnotsAttr->setValue(index, *aKIt);
+
+  AttributeIntArrayPtr aMultsAttr =
+      theProjection->data()->intArray(SketchPlugin_BSpline::MULTS_ID());
+  std::list<int> aMultiplicities = aBSpline.mults();
+  aSize = (int)aMultiplicities.size();
+  aMultsAttr->setSize(aSize);
+  std::list<int>::iterator aMIt = aMultiplicities.begin();
+  for (int index = 0; index < aSize; ++index, ++aMIt)
+    aMultsAttr->setValue(index, *aMIt);
+
+  return true;
+}
index 40fd6301a6933faafea9675ea4c5b3d8a6a58e5c..b820daf991e95a19bfd0dfe6fa00e84580aaed3b 100644 (file)
@@ -22,6 +22,8 @@
 
 #include "SketchPlugin_SketchEntity.h"
 
+class GeomAPI_Curve;
+
 /** \class SketchPlugin_Projection
  *  \ingroup Plugins
  *  \brief Feature for creation of external feature as a projection onto the sketch plane.
@@ -88,6 +90,34 @@ private:
   /// \brief Find projection of a feature onto sketch plane
   void computeProjection(const std::string& theID);
 
+  /// \brief Project point to the sketch plane
+  bool projectPoint(FeaturePtr& theProjection, const std::shared_ptr<GeomAPI_Pnt>& thePoint);
+  /// \brief Project segment to the sketch plane
+  bool projectSegment(FeaturePtr& theProjection, const std::shared_ptr<GeomAPI_Edge>& theEdge);
+  /// \brief Project any edge to sketch plane
+  bool projectEdge(FeaturePtr& theProjection, const std::shared_ptr<GeomAPI_Edge>& theEdge);
+
+  /// \brief Fill attributes of the Arc feature
+  bool fillArc(FeaturePtr& theProjection,
+               const std::shared_ptr<GeomAPI_Curve>& theArc,
+               const std::shared_ptr<GeomAPI_Pln>& thePlane);
+  /// \brief Fill attributes of the Circle feature
+  bool fillCircle(FeaturePtr& theProjection,
+                  const std::shared_ptr<GeomAPI_Curve>& theCircle,
+                  const std::shared_ptr<GeomAPI_Pln>& thePlane);
+  /// \brief Fill attributes of the Ellipse feature
+  bool fillEllipse(FeaturePtr& theProjection,
+                   const std::shared_ptr<GeomAPI_Curve>& theEllipse,
+                   const std::shared_ptr<GeomAPI_Pln>& thePlane);
+  /// \brief Fill attributes of the EllipticArc feature
+  bool fillEllipticArc(FeaturePtr& theProjection,
+                       const std::shared_ptr<GeomAPI_Curve>& theEllipticArc,
+                       const std::shared_ptr<GeomAPI_Pln>& thePlane);
+  /// \brief Fill attributes of the B-spline feature
+  bool fillBSpline(FeaturePtr& theProjection,
+                   const std::shared_ptr<GeomAPI_Curve>& theCurve,
+                   const std::shared_ptr<GeomAPI_Pln>& thePlane);
+
   /// \brief Delete already calculated projected feature
   ///        if the selection of the projection is changed
   /// \param[in/out] theProjection   projected feature
index 8e1bccbf6c655d459453992cfb33d4a09a6b2f5e..94236695e053cfa67cba433d1e51ca1f970354ae 100644 (file)
@@ -65,7 +65,9 @@
 #include <GeomAPI_Lin.h>
 #include <GeomAPI_Edge.h>
 #include <GeomAPI_Vertex.h>
+
 #include <GeomDataAPI_Point2D.h>
+#include <GeomDataAPI_Point2DArray.h>
 
 #include <algorithm>
 #include <cmath>
@@ -1123,38 +1125,35 @@ bool SketchPlugin_ProjectionValidator::isValid(const AttributePtr& theAttribute,
   std::shared_ptr<GeomAPI_Dir> aNormal = aPlane->direction();
   std::shared_ptr<GeomAPI_Pnt> anOrigin = aPlane->location();
 
+  bool aValid = true;
   if (anEdge->isLine()) {
     std::shared_ptr<GeomAPI_Lin> aLine = anEdge->line();
     std::shared_ptr<GeomAPI_Dir> aLineDir = aLine->direction();
     double aDot = fabs(aNormal->dot(aLineDir));
-    bool aValid = fabs(aDot - 1.0) >= tolerance * tolerance;
+    aValid = fabs(aDot - 1.0) >= tolerance * tolerance;
     if (!aValid)
       theError = "Error: Line is orthogonal to the sketch plane.";
-    return aValid;
   }
   else if (anEdge->isCircle() || anEdge->isArc()) {
     std::shared_ptr<GeomAPI_Circ> aCircle = anEdge->circle();
     std::shared_ptr<GeomAPI_Dir> aCircNormal = aCircle->normal();
     double aDot = fabs(aNormal->dot(aCircNormal));
-    bool aValid = aDot >= tolerance * tolerance;
+    aValid = aDot >= tolerance * tolerance;
     if (!aValid)
       theError.arg(anEdge->isCircle() ? "Error: Circle is orthogonal to the sketch plane."
                                       : "Error: Arc is orthogonal to the sketch plane.");
-    return aValid;
   }
   else if (anEdge->isEllipse()) {
     std::shared_ptr<GeomAPI_Ellipse> anEllipse = anEdge->ellipse();
     std::shared_ptr<GeomAPI_Dir> anEllipseNormal = anEllipse->normal();
     double aDot = fabs(aNormal->dot(anEllipseNormal));
-    bool aValid = fabs(aDot - 1.0) <= tolerance * tolerance;
+    aValid = fabs(aDot - 1.0) <= tolerance * tolerance;
     if (!aValid)
       theError.arg(anEdge->isClosed() ? "Error: Ellipse is orthogonal to the sketch plane."
                                       : "Error: Elliptic Arc is orthogonal to the sketch plane.");
-    return aValid;
   }
 
-  theError = "Error: Selected object is not supported for projection.";
-  return false;
+  return aValid;
 }
 
 
@@ -1779,3 +1778,20 @@ bool SketchPlugin_MultiRotationAngleValidator::isValid(const AttributePtr& theAt
 
   return true;
 }
+
+bool SketchPlugin_BSplineValidator::isValid(const AttributePtr& theAttribute,
+                                            const std::list<std::string>& theArguments,
+                                            Events_InfoMessage& theError) const
+{
+  AttributePoint2DArrayPtr aPolesArray =
+      std::dynamic_pointer_cast<GeomDataAPI_Point2DArray>(theAttribute);
+  if (!aPolesArray)
+    return false;
+
+  if (aPolesArray->size() < 2) {
+    theError = "Number of B-spline poles should be 2 or more";
+    return false;
+  }
+
+  return true;
+}
index b3fe8f57464ee41781dcfe9d114f9caaac735a89..1b20c45fdcdb45af3e4028af684cb9cd0c6e1c7f 100644 (file)
@@ -531,4 +531,19 @@ class SketchPlugin_MultiRotationAngleValidator : public ModelAPI_AttributeValida
                        Events_InfoMessage& theError) const;
 };
 
+/**\class SketchPlugin_BSplineValidator
+ * \ingroup Validators
+ * \brief Validator for checking poles/weights of B-spline curve.
+ */
+class SketchPlugin_BSplineValidator : public ModelAPI_AttributeValidator
+{
+  //! returns true if attribute is valid
+  //! \param theAttribute the checked attribute
+  //! \param theArguments arguments of the attribute
+  //! \param theError error message
+  virtual bool isValid(const AttributePtr& theAttribute,
+                       const std::list<std::string>& theArguments,
+                       Events_InfoMessage& theError) const;
+};
+
 #endif
index 5e21e2514e278de653799b4c23be93a89287767d..2c6c284e770bc86fb34bbe7ac68c5906aa30dc6b 100644 (file)
     </message>
   </context>
 
+  <context>
+    <name>SketchBSpline</name>
+    <message>
+        <source>Number of B-spline poles should be 2 or more</source>
+        <translation>Le nombre de pôles B-spline doit Ãªtre de 2 ou plus</translation>
+    </message>
+  </context>
+
 </TS>
diff --git a/src/SketchPlugin/Test/TestConstraintCoincidenceBSpline.py b/src/SketchPlugin/Test/TestConstraintCoincidenceBSpline.py
new file mode 100644 (file)
index 0000000..36a0426
--- /dev/null
@@ -0,0 +1,176 @@
+# Copyright (C) 2019-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
+#
+
+"""
+    Test constraint coincidence applied for B-spline curve and its sub-results
+"""
+
+import unittest
+import math
+
+from salome.shaper import model
+
+from GeomAPI import *
+from SketchAPI import *
+
+__updated__ = "2020-01-21"
+
+class TestCoincidenceBSpline(unittest.TestCase):
+  def setUp(self):
+    model.begin()
+    self.myDocument = model.moduleDocument()
+    self.mySketch = model.addSketch(self.myDocument, model.defaultPlane("XOY"))
+
+    self.myPoles = [GeomAPI_Pnt2d(-10, -30), GeomAPI_Pnt2d(20, -15), GeomAPI_Pnt2d(-10, 0), GeomAPI_Pnt2d(20, 15), GeomAPI_Pnt2d(-10, 30)]
+    self.myWeights = [1, 3, 5, 3, 1]
+    self.mySpline = self.mySketch.addSpline(poles = self.myPoles, weights = self.myWeights)
+    self.myControlPoles = self.mySpline.controlPoles(auxiliary = [0, 1, 2, 3, 4])
+    self.myControlLines = self.mySpline.controlPolygon(auxiliary = [0, 1, 2, 3])
+
+    self.myDOF = len(self.myPoles) * 2
+    self.myOrigin = self.mySketch.addPoint("Origin")
+    self.myOX = self.mySketch.addLine("OX")
+    model.do()
+    self.myExpectFailure = False
+    self.myNbPoints = len(self.myPoles) + 1
+    self.myNbLines = len(self.myPoles)
+    self.myNbBSplines = 1
+    self.myNbInternalConstraints = len(self.myPoles) * 3 - 2
+    self.myNbCoincidences = 1
+
+  def tearDown(self):
+    model.end()
+    if self.myExpectFailure:
+      assert(self.mySketch.solverError() != ""), "PlaneGCS limitation: if you see this message, then PlaneGCS has solved the set of constraints correctly"
+      model.undo()
+    else:
+      self.checkDOF()
+      model.testNbSubFeatures(self.mySketch, "SketchPoint", self.myNbPoints)
+      model.testNbSubFeatures(self.mySketch, "SketchLine", self.myNbLines)
+      model.testNbSubFeatures(self.mySketch, "SketchBSpline", self.myNbBSplines)
+      model.testNbSubFeatures(self.mySketch, "SketchConstraintCoincidenceInternal", self.myNbInternalConstraints)
+      model.testNbSubFeatures(self.mySketch, "SketchConstraintCoincidence", self.myNbCoincidences)
+
+
+  def checkDOF(self):
+    self.assertEqual(model.dof(self.mySketch), self.myDOF)
+
+  def setCoincidentWithOrigin(self, thePoint):
+    self.mySketch.setCoincident(thePoint, self.myOrigin.coordinates())
+    self.myDOF -= 2
+    model.do()
+
+  def setCoincidentWithOX(self, thePoint):
+    self.mySketch.setCoincident(thePoint, self.myOX.result())
+    self.myDOF -= 1
+    model.do()
+
+  def assertPoles(self):
+    poles = self.mySpline.poles()
+    assert(poles.size() == len(self.myPoles))
+    for index in range(0, len(self.myPoles)):
+      self.assertPoints(self.myPoles[index], poles.pnt(index))
+
+  def assertPoints(self, thePoint1, thePoint2):
+    self.assertAlmostEqual(thePoint1.x(), thePoint2.x())
+    self.assertAlmostEqual(thePoint1.y(), thePoint2.y())
+
+  def assertPointOnLine(self, thePoint, theLineStart, theLineEnd):
+    vecP = [thePoint.x() - theLineStart.x(), thePoint.y() - theLineStart.y()]
+    vecL = [theLineEnd.x() - theLineStart.x(), theLineEnd.y() - theLineStart.y()]
+    dist = math.fabs(vecP[0] * vecL[1] - vecP[1] * vecL[0]) / math.hypot(vecL[0], vecL[1])
+    self.assertAlmostEqual(dist, 0.0)
+
+  def assertPointOnSpline(self, thePoint, theSpline):
+    point = GeomAPI_Pnt(thePoint.x(), thePoint.y(), 0.0)
+    bspline = GeomAPI_Curve(theSpline.results()[-1].resultSubShapePair()[0].shape())
+    proj = bspline.project(point)
+    self.assertAlmostEqual(point.distance(proj), 0.0)
+
+
+  def test_origin_equal_start_point(self):
+    """ Test 1. Make start point of B-spline coincident with the Origin
+    """
+    self.setCoincidentWithOrigin(self.mySpline.startPoint())
+    self.myPoles[0].setX(0)
+    self.myPoles[0].setY(0)
+    self.assertPoles()
+
+  def test_origin_equal_end_point(self):
+    """ Test 2. Make end point of B-spline coincident with the Origin
+    """
+    self.setCoincidentWithOrigin(self.mySpline.endPoint())
+    self.myPoles[-1].setX(0)
+    self.myPoles[-1].setY(0)
+    self.assertPoles()
+
+  def test_origin_equal_pole(self):
+    """ Test 3. Make one of B-spline poles coincident with the Origin
+    """
+    self.setCoincidentWithOrigin(SketchAPI_Point(self.myControlPoles[1]).coordinates())
+    self.myPoles[1].setX(0)
+    self.myPoles[1].setY(0)
+    self.assertPoles()
+
+  def test_origin_on_bspline(self):
+    """ Test 4. (expected failure) Make Origin lying on the B-spline curve
+    """
+    self.mySketch.setCoincident(self.mySpline.defaultResult(), self.myOrigin.coordinates())
+    self.myDOF -= 1
+    model.do()
+    self.myExpectFailure = True
+
+  def test_point_on_bspline(self):
+    """ Test 5. Place free point on the B-spline curve
+    """
+    point = self.mySketch.addPoint(1, 0)
+    self.mySketch.setCoincident(self.myOX.defaultResult(), point.coordinates())
+    self.mySketch.setCoincident(self.mySpline.defaultResult(), point.coordinates())
+    model.do()
+    self.myNbPoints += 1
+    self.myNbCoincidences += 1
+    self.assertPointOnSpline(point.coordinates(), self.mySpline)
+
+
+  def test_start_point_on_axis(self):
+    """ Test 6. Make start point of B-spline coincident with the OX
+    """
+    self.setCoincidentWithOX(self.mySpline.startPoint())
+    self.myPoles[0].setY(0)
+    self.assertPoles()
+
+  def test_end_point_on_axis(self):
+    """ Test 7. Make end point of B-spline coincident with the OX
+    """
+    self.setCoincidentWithOX(self.mySpline.endPoint())
+    self.myPoles[-1].setY(0)
+    self.assertPoles()
+
+  def test_pole_on_axis(self):
+    """ Test 8. Make one of B-spline poles coincident with the OX
+    """
+    self.setCoincidentWithOX(SketchAPI_Point(self.myControlPoles[1]).coordinates())
+    self.myPoles[1].setY(0)
+    self.assertPoles()
+
+
+if __name__ == "__main__":
+    test_program = unittest.main(exit=False)
+    assert test_program.result.wasSuccessful(), "Test failed"
+    assert model.checkPythonDump()
diff --git a/src/SketchPlugin/Test/TestConstraintTangentBSpline.py b/src/SketchPlugin/Test/TestConstraintTangentBSpline.py
new file mode 100644 (file)
index 0000000..e3e758b
--- /dev/null
@@ -0,0 +1,479 @@
+# Copyright (C) 2019-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
+#
+
+"""
+    Test constraint "Tangent" applied to B-spline and another entity
+"""
+
+import unittest
+import math
+
+from salome.shaper import model
+
+from GeomAPI import *
+from GeomAlgoAPI import *
+from SketchAPI import *
+
+__updated__ = "2020-01-22"
+
+class TestTangentBSpline(unittest.TestCase):
+  def setUp(self):
+    model.begin()
+    self.myDocument = model.moduleDocument()
+    self.mySketch = model.addSketch(self.myDocument, model.defaultPlane("XOY"))
+    self.myPoles = [GeomAPI_Pnt2d(-10, -30), GeomAPI_Pnt2d(20, -15), GeomAPI_Pnt2d(-10, 0), GeomAPI_Pnt2d(20, 15), GeomAPI_Pnt2d(-10, 30)]
+    self.myWeights = [1, 3, 5, 3, 1]
+    self.mySpline = self.mySketch.addSpline(poles = self.myPoles, weights = self.myWeights)
+    self.myControlPoles = self.mySpline.controlPoles(auxiliary = [0, 1, 2, 3, 4])
+    self.myControlLines = self.mySpline.controlPolygon(auxiliary = [0, 1, 2, 3])
+    model.do()
+
+    self.myExpectedFailure = False
+    self.myDOF = len(self.myPoles) * 2
+    self.myNbPoints = len(self.myPoles)
+    self.myNbLines = len(self.myPoles) - 1
+    self.myNbArcs = 0
+    self.myNbCircles = 0
+    self.myNbEllipses = 0
+    self.myNbEllipticArcs = 0
+    self.myNbBSplines = 1
+    self.myNbInternals = len(self.myPoles) * 3 - 2
+    self.myNbCoincidence = 0
+    self.myNbTangency = 0
+
+  def tearDown(self):
+    model.end()
+    if self.myExpectedFailure:
+      assert(self.mySketch.solverError() != ""), "PlaneGCS limitation: if you see this message, then PlaneGCS has solved the set of constraints correctly"
+      model.undo()
+    else:
+      self.checkDOF()
+      model.testNbSubFeatures(self.mySketch, "SketchPoint", self.myNbPoints)
+      model.testNbSubFeatures(self.mySketch, "SketchLine", self.myNbLines)
+      model.testNbSubFeatures(self.mySketch, "SketchArc", self.myNbArcs)
+      model.testNbSubFeatures(self.mySketch, "SketchCircle", self.myNbCircles)
+      model.testNbSubFeatures(self.mySketch, "SketchEllipse", self.myNbEllipses)
+      model.testNbSubFeatures(self.mySketch, "SketchEllipticArc", self.myNbEllipticArcs)
+      model.testNbSubFeatures(self.mySketch, "SketchBSpline", self.myNbBSplines)
+      model.testNbSubFeatures(self.mySketch, "SketchConstraintCoincidenceInternal", self.myNbInternals)
+      model.testNbSubFeatures(self.mySketch, "SketchConstraintCoincidence", self.myNbCoincidence)
+      model.testNbSubFeatures(self.mySketch, "SketchConstraintTangent", self.myNbTangency)
+
+
+  def checkDOF(self):
+    self.assertEqual(model.dof(self.mySketch), self.myDOF)
+
+  def assertTangentFeatures(self, theFeature1, theFeature2):
+    shapes = [theFeature1.results()[-1].resultSubShapePair()[0].shape(),
+              theFeature2.results()[-1].resultSubShapePair()[0].shape()]
+    for s in shapes:
+      e = shapeToEdge(s)
+      if e.isLine() or e.isArc() or e.isEllipse():
+        params = e.getRange()
+        e.setRange(params[0] - 100, params[1] + 100)
+    # TODO (azv): complete checking the tangent curves
+
+  def assertPointLineDistance(self, thePoint, theLine, theExpectedDistance = 0):
+    dist = model.distancePointLine(thePoint, theLine)
+    self.assertAlmostEqual(dist, theExpectedDistance)
+
+  def assertTangentLineCircle(self, theLine, theCircle):
+    self.assertPointLineDistance(theCircle.center(), theLine, theCircle.radius().value())
+
+  def assertTangentLineEllipse(self, theLine, theEllipse):
+    aLine = GeomAPI_Lin2d(theLine.startPoint().pnt(), theLine.endPoint().pnt())
+    projF1 = aLine.project(theEllipse.firstFocus().pnt())
+    projF2 = aLine.project(theEllipse.secondFocus().pnt())
+
+    distF1P1 = model.distancePointPoint(theEllipse.firstFocus(), projF1)
+    distF2P2 = model.distancePointPoint(theEllipse.secondFocus(), projF2)
+
+    tgPoint = GeomAPI_Pnt2d((projF1.x() * distF2P2 + projF2.x() * distF1P1) / (distF1P1 + distF2P2), (projF1.y() * distF2P2 + projF2.y() * distF1P1) / (distF1P1 + distF2P2))
+    distF1T = model.distancePointPoint(theEllipse.firstFocus(), tgPoint)
+    distF2T = model.distancePointPoint(theEllipse.secondFocus(), tgPoint)
+    self.assertAlmostEqual(distF1T + distF2T, 2 * theEllipse.majorRadius().value())
+
+
+  def test_line_tangent(self):
+    """ Test 1. Set tangency between B-spline and a line
+    """
+    aLine = self.mySketch.addLine(10, -10, 90, 40)
+    self.myNbLines += 1
+    self.myDOF += 4
+    model.do()
+
+    self.mySketch.setTangent(self.mySpline.result(), aLine.result())
+    self.myNbTangency += 1
+    self.myDOF -= 1
+    model.do()
+
+    self.assertTangentFeatures(aLine, self.mySpline)
+
+  def test_circle_tangent(self):
+    """ Test 2. Set tangency between B-spline and a circle
+    """
+    aCircle = self.mySketch.addCircle(10, 10, 20)
+    self.myNbCircles += 1
+    self.myDOF += 3
+    model.do()
+
+    self.mySketch.setTangent(self.mySpline.result(), aCircle.defaultResult())
+    self.myNbTangency += 1
+    self.myDOF -= 1
+    model.do()
+
+    self.assertTangentFeatures(aCircle, self.mySpline)
+
+  def test_arc_tangent(self):
+    """ Test 3. Set tangency between B-spline and an arc
+    """
+    anArc = self.mySketch.addArc(10, 10, 20, 10, 10, 20, False)
+    self.myNbArcs += 1
+    self.myDOF += 5
+    model.do()
+
+    self.mySketch.setTangent(self.mySpline.result(), anArc.defaultResult())
+    self.myNbTangency += 1
+    self.myDOF -= 1
+    model.do()
+
+    self.assertTangentFeatures(anArc, self.mySpline)
+
+  def test_ellipse_tangent(self):
+    """ Test 4. Set tangency between B-spline and an ellipse
+    """
+    anEllipse = self.mySketch.addEllipse(10, 10, 20, 10, 7)
+    self.myNbEllipses += 1
+    self.myDOF += 5
+    model.do()
+
+    self.mySketch.setTangent(self.mySpline.result(), anEllipse.defaultResult())
+    self.myNbTangency += 1
+    self.myDOF -= 1
+    model.do()
+
+    self.assertTangentFeatures(anEllipse, self.mySpline)
+
+  def test_elliptic_arc_tangent(self):
+    """ Test 5. Set tangency between B-spline and an elliptic arc
+    """
+    anEllipticArc = self.mySketch.addEllipticArc(10, 10, 20, 10, 22.2065556157337, 10, 10, 17, True)
+    self.myNbEllipticArcs += 1
+    self.myDOF += 7
+    model.do()
+
+    self.mySketch.setTangent(self.mySpline.result(), anEllipticArc.defaultResult())
+    self.myNbTangency += 1
+    self.myDOF -= 1
+    model.do()
+
+    self.assertTangentFeatures(anEllipticArc, self.mySpline)
+
+  def test_spline_tangent(self):
+    """ Test 6. Set tangency between two B-spline curves
+    """
+    aSpline = self.mySketch.addSpline(poles = [(50, -20), (40, 0), (50, 20)])
+    self.myNbBSplines += 1
+    self.myDOF += aSpline.poles().size() * 2
+    model.do()
+
+    self.mySketch.setTangent(self.mySpline.result(), aSpline.result())
+    self.myNbTangency += 1
+    self.myDOF -= 1
+    model.do()
+
+    #self.assertTangentFeatures(aSpline, self.mySpline)
+    self.myExpectedFailure = True
+
+
+  def test_line_tangent_coincident_by_pole(self):
+    """ Test 7. Set tangency between B-spline and a line coincident with B-spline start point
+    """
+    aLine = self.mySketch.addLine(-15, -25, 50, 40)
+    self.myNbLines += 1
+    self.myDOF += 4
+    model.do()
+
+    self.mySketch.setCoincident(self.mySpline.startPoint(), aLine.result())
+    self.myNbCoincidence += 1
+    self.myDOF -= 1
+    model.do()
+
+    self.mySketch.setTangent(self.mySpline.result(), aLine.result())
+    self.myNbTangency += 1
+    self.myDOF -= 1
+    model.do()
+
+    self.assertPointLineDistance(self.mySpline.startPoint(), aLine)
+    self.assertTangentFeatures(aLine, self.mySpline)
+
+  def test_circle_tangent_coincident_by_pole(self):
+    """ Test 8. Set tangency between B-spline and a circle coincident with B-spline end point
+    """
+    aCircle = self.mySketch.addCircle(10, 10, 20)
+    self.myNbCircles += 1
+    self.myDOF += 3
+    model.do()
+
+    self.mySketch.setCoincident(self.mySpline.startPoint(), aCircle.defaultResult())
+    self.myNbCoincidence += 1
+    self.myDOF -= 1
+    model.do()
+
+    self.mySketch.setTangent(self.mySpline.result(), aCircle.defaultResult())
+    self.myNbTangency += 1
+    self.myDOF -= 1
+    model.do()
+
+    self.assertTangentFeatures(aCircle, self.mySpline)
+    dist = model.distancePointPoint(self.mySpline.startPoint(), aCircle.center())
+    self.assertAlmostEqual(dist, aCircle.radius().value())
+
+  def test_arc_tangent_coincident_by_pole(self):
+    """ Test 9. Set tangency between B-spline and an arc coincident with B-spline end point
+    """
+    anArc = self.mySketch.addArc(10, 10, 20, 10, 10, 20, False)
+    self.myNbArcs += 1
+    self.myDOF += 5
+    model.do()
+
+    self.mySketch.setCoincident(self.mySpline.endPoint(), anArc.defaultResult())
+    self.myNbCoincidence += 1
+    self.myDOF -= 1
+    model.do()
+
+    self.mySketch.setTangent(self.mySpline.result(), anArc.defaultResult())
+    self.myNbTangency += 1
+    self.myDOF -= 1
+    model.do()
+
+    self.assertTangentFeatures(anArc, self.mySpline)
+    dist = model.distancePointPoint(self.mySpline.endPoint(), anArc.center())
+    self.assertAlmostEqual(dist, anArc.radius().value())
+
+  def test_ellipse_tangent_coincident_by_pole(self):
+    """ Test 10. Set tangency between B-spline and an ellipse coincident with B-spline start point
+    """
+    anEllipse = self.mySketch.addEllipse(10, 10, 20, 10, 7)
+    self.myNbEllipses += 1
+    self.myDOF += 5
+    model.do()
+
+    self.mySketch.setCoincident(self.mySpline.startPoint(), anEllipse.defaultResult())
+    self.myNbCoincidence += 1
+    self.myDOF -= 1
+    model.do()
+
+    self.mySketch.setTangent(self.mySpline.result(), anEllipse.defaultResult())
+    self.myNbTangency += 1
+    self.myDOF -= 1
+    model.do()
+
+    self.assertTangentLineEllipse(SketchAPI_Line(self.myControlLines[0]), anEllipse)
+
+  def test_elliptic_arc_tangent_coincident_by_pole(self):
+    """ Test 11. Set tangency between B-spline and an elliptic arc coincident with B-spline start point
+    """
+    anEllipticArc = self.mySketch.addEllipticArc(10, 10, 20, 10, 22.2065556157337, 10, 10, 17, True)
+    self.myNbEllipticArcs += 1
+    self.myDOF += 7
+    model.do()
+
+    self.mySketch.setCoincident(self.mySpline.startPoint(), anEllipticArc.defaultResult())
+    self.myNbCoincidence += 1
+    self.myDOF -= 1
+    model.do()
+
+    self.mySketch.setTangent(self.mySpline.result(), anEllipticArc.defaultResult())
+    self.myNbTangency += 1
+    self.myDOF -= 1
+    model.do()
+
+    self.assertTangentLineEllipse(SketchAPI_Line(self.myControlLines[0]), anEllipticArc)
+
+
+  def test_line_tangent_coincident_by_boundaries(self):
+    """ Test 12. Set tangency between B-spline and a line, coincident by their start points
+    """
+    aLine = self.mySketch.addLine(10, -10, 90, 40)
+    self.myNbLines += 1
+    self.myDOF += 4
+    model.do()
+
+    self.mySketch.setCoincident(self.mySpline.startPoint(), aLine.startPoint())
+    self.myNbCoincidence += 1
+    self.myDOF -= 2
+    model.do()
+
+    self.mySketch.setTangent(self.mySpline.result(), aLine.result())
+    self.myNbTangency += 1
+    self.myDOF -= 1
+    model.do()
+
+    self.assertPointLineDistance(aLine.endPoint(), self.myControlLines[0])
+
+  def test_arc_tangent_coincident_by_boundaries(self):
+    """ Test 13. Set tangency between B-spline and an arc, coincident by their start points
+    """
+    anArc = self.mySketch.addArc(10, 10, 20, 10, 10, 20, False)
+    self.myNbArcs += 1
+    self.myDOF += 5
+    model.do()
+
+    self.mySketch.setCoincident(self.mySpline.startPoint(), anArc.startPoint())
+    self.myNbCoincidence += 1
+    self.myDOF -= 2
+    model.do()
+
+    self.mySketch.setTangent(self.mySpline.result(), anArc.defaultResult())
+    self.myNbTangency += 1
+    self.myDOF -= 1
+    model.do()
+
+    self.assertTangentLineCircle(SketchAPI_Line(self.myControlLines[0]), anArc)
+
+  def test_elliptic_arc_tangent_coincident_by_boundaries(self):
+    """ Test 14. Set tangency between B-spline and an elliptic arc, coincident by their start points
+    """
+    anEllipticArc = self.mySketch.addEllipticArc(10, -10, 20, -10, 22.2065556157337, -10, 10, 3, True)
+    self.myNbEllipticArcs += 1
+    self.myDOF += 7
+    model.do()
+
+    self.mySketch.setCoincident(self.mySpline.startPoint(), anEllipticArc.startPoint())
+    self.myNbCoincidence += 1
+    self.myDOF -= 2
+    model.do()
+
+    self.mySketch.setTangent(self.mySpline.result(), anEllipticArc.defaultResult())
+    self.myNbTangency += 1
+    self.myDOF -= 1
+    model.do()
+
+    self.assertTangentLineEllipse(SketchAPI_Line(self.myControlLines[0]), anEllipticArc)
+
+  def test_spline_tangent_coincident_by_boundaries(self):
+    """ Test 15. Set tangency between two B-spline curves coincident with B-spline start point
+    """
+    aSpline = self.mySketch.addSpline(poles = [(50, -20), (40, 0), (50, 20)])
+    self.myNbBSplines += 1
+    self.myDOF += aSpline.poles().size() * 2
+    model.do()
+
+    self.mySketch.setCoincident(self.mySpline.startPoint(), aSpline.startPoint())
+    self.myNbCoincidence += 1
+    self.myDOF -= 2
+    model.do()
+
+    self.mySketch.setTangent(self.mySpline.result(), aSpline.result())
+    self.myNbTangency += 1
+    self.myDOF -= 1
+    model.do()
+
+    #self.assertPointLineDistance(aSpline.poles()[1], self.myControlLines[0])
+    self.myExpectedFailure = True
+
+
+  def test_line_tangent_coincident_by_aux(self):
+    """ Test 16. Set tangency between B-spline and a line, coincident by their start points
+    """
+    aLine = self.mySketch.addLine(10, -10, 90, 40)
+    self.myNbLines += 1
+    self.myDOF += 4
+    model.do()
+
+    self.mySketch.setCoincident(SketchAPI_Point(self.myControlPoles[0]).coordinates(), aLine.startPoint())
+    self.myNbCoincidence += 1
+    self.myDOF -= 2
+    model.do()
+
+    self.mySketch.setTangent(self.mySpline.result(), aLine.result())
+    self.myNbTangency += 1
+    self.myDOF -= 1
+    model.do()
+
+    self.assertPointLineDistance(aLine.endPoint(), self.myControlLines[0])
+
+  def test_arc_tangent_coincident_by_aux(self):
+    """ Test 17. Set tangency between B-spline and an arc, coincident by their start points
+    """
+    anArc = self.mySketch.addArc(10, 10, 20, 10, 10, 20, False)
+    self.myNbArcs += 1
+    self.myDOF += 5
+    model.do()
+
+    self.mySketch.setCoincident(SketchAPI_Point(self.myControlPoles[0]).coordinates(), anArc.startPoint())
+    self.myNbCoincidence += 1
+    self.myDOF -= 2
+    model.do()
+
+    self.mySketch.setTangent(self.mySpline.result(), anArc.defaultResult())
+    self.myNbTangency += 1
+    self.myDOF -= 1
+    model.do()
+
+    self.assertTangentLineCircle(SketchAPI_Line(self.myControlLines[0]), anArc)
+
+  def test_elliptic_arc_tangent_coincident_by_aux(self):
+    """ Test 18. Set tangency between B-spline and an elliptic arc, coincident by their start points
+    """
+    anEllipticArc = self.mySketch.addEllipticArc(10, 10, 20, 10, 22.2065556157337, 10, 10, 17, True)
+    self.myNbEllipticArcs += 1
+    self.myDOF += 7
+    model.do()
+
+    self.mySketch.setCoincident(SketchAPI_Point(self.myControlPoles[0]).coordinates(), anEllipticArc.startPoint())
+    self.myNbCoincidence += 1
+    self.myDOF -= 2
+    model.do()
+
+    self.mySketch.setTangent(self.mySpline.result(), anEllipticArc.defaultResult())
+    self.myNbTangency += 1
+    self.myDOF -= 1
+    model.do()
+
+    self.assertTangentLineEllipse(SketchAPI_Line(self.myControlLines[0]), anEllipticArc)
+
+  def test_spline_tangent_coincident_by_aux(self):
+    """ Test 19. Set tangency between two B-spline curves coincident with B-spline start point
+    """
+    aSpline = self.mySketch.addSpline(poles = [(50, -20), (40, 0), (50, 20)])
+    self.myNbBSplines += 1
+    self.myDOF += aSpline.poles().size() * 2
+    model.do()
+
+    self.mySketch.setCoincident(SketchAPI_Point(self.myControlPoles[0]).coordinates(), aSpline.startPoint())
+    self.myNbCoincidence += 1
+    self.myDOF -= 2
+    model.do()
+
+    self.mySketch.setTangent(self.mySpline.result(), aSpline.result())
+    self.myNbTangency += 1
+    self.myDOF -= 1
+    model.do()
+
+    #self.assertPointLineDistance(aSpline.poles().pnt(1), self.myControlLines[0])
+    self.myExpectedFailure = True
+
+
+
+if __name__ == "__main__":
+    test_program = unittest.main(exit=False)
+    assert test_program.result.wasSuccessful(), "Test failed"
+    #assert model.checkPythonDump()
diff --git a/src/SketchPlugin/Test/TestCreateBSpline.py b/src/SketchPlugin/Test/TestCreateBSpline.py
new file mode 100644 (file)
index 0000000..0aa12ee
--- /dev/null
@@ -0,0 +1,195 @@
+# 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
+#
+
+"""
+    Test creation of B-spline curve
+"""
+
+import unittest
+from salome.shaper import model
+
+from GeomAPI import *
+from SketchAPI import *
+
+__updated__ = "2020-01-17"
+
+class TestBSpline(unittest.TestCase):
+  def setUp(self):
+    model.begin()
+    self.myDocument = model.moduleDocument()
+    self.mySketch = model.addSketch(self.myDocument, model.defaultPlane("XOY"))
+    self.myPoles = [GeomAPI_Pnt2d(50., 50.), GeomAPI_Pnt2d(70., 70.), GeomAPI_Pnt2d(80., 30.), GeomAPI_Pnt2d(50., 10.), GeomAPI_Pnt2d(10., -30.)]
+    self.myPolesCoordinates = [(50., 50.), (70., 70.), (80., 30.), (50., 10.), (10., -30.)]
+    self.myDegree = 3;
+    self.myDOF = 0
+    self.myNbPoints = 0
+    self.myNbLines = 0
+    self.myNbSplines = 0
+
+  def tearDown(self):
+    self.checkDOF()
+    model.end()
+    model.testNbSubFeatures(self.mySketch, "SketchPoint", self.myNbPoints)
+    model.testNbSubFeatures(self.mySketch, "SketchLine", self.myNbLines)
+    model.testNbSubFeatures(self.mySketch, "SketchBSpline", self.myNbSplines)
+
+
+  def checkDOF(self):
+    self.assertEqual(model.dof(self.mySketch), self.myDOF)
+
+
+  def test_bspline_by_coordinates(self):
+    """ Test 1. Create B-spline curve by coordinates of its poles
+    """
+    self.mySpline = self.mySketch.addSpline(poles = self.myPolesCoordinates)
+    self.myDOF += len(self.myPolesCoordinates) * 2
+    self.myNbSplines += 1
+    model.do()
+
+    assert(self.mySpline.feature())
+    assert(self.mySpline.feature().error() == "")
+    assert(self.mySpline.degree().value() == self.myDegree)
+
+  def test_bspline_by_poles(self):
+    """ Test 2. Create B-spline curve by poles
+    """
+    self.mySpline = self.mySketch.addSpline(poles = self.myPoles)
+    self.myDOF += len(self.myPoles) * 2
+    self.myNbSplines += 1
+    model.do()
+
+    assert(self.mySpline.feature())
+    assert(self.mySpline.feature().error() == "")
+    assert(self.mySpline.degree().value() == self.myDegree)
+
+  def test_bspline_by_degree_and_poles(self):
+    """ Test 3. Create B-spline curve by poles and degree
+    """
+    self.myDegree = 4
+    self.mySpline = self.mySketch.addSpline(degree = self.myDegree, poles = self.myPoles)
+    self.myDOF += len(self.myPoles) * 2
+    self.myNbSplines += 1
+    model.do()
+
+    assert(self.mySpline.feature())
+    assert(self.mySpline.feature().error() == "")
+    assert(self.mySpline.degree().value() == self.myDegree)
+
+  def test_bspline_by_poles_and_weights(self):
+    """ Test 4. Create B-spline curve by poles and weights
+    """
+    self.mySpline = self.mySketch.addSpline(poles = self.myPoles, weights = [1, 2, 3, 2, 1])
+    self.myDOF += len(self.myPoles) * 2
+    self.myNbSplines += 1
+    model.do()
+
+    assert(self.mySpline.feature())
+    assert(self.mySpline.feature().error() == "")
+    assert(self.mySpline.degree().value() == self.myDegree)
+
+  def test_bspline_by_parametric(self):
+    """ Test 5. Create B-spline curve by whole set of parameters
+    """
+    self.myDegree = 5
+    self.myPolesCoordinates = [(-79.8578274581199, 75.5284518447357),
+                               (-64.6205376770376, 62.7428476092882),
+                               (-49.3832478959552, 49.9572433738407),
+                               (-34.1459581148729, 37.1716391383932),
+                               (-18.9086683337906, 24.3860349029457),
+                               (-3.55842111132817, 11.5056481200973),
+                               (-3.37993197286247, 11.42995541724),
+                               (-3.1778022626919, 11.4565662984205),
+                               (-3.02498570721059, 11.575876223351),
+                               (8.46852511720001, 27.9903107977019),
+                               (19.8774589601206, 44.2839569245217),
+                               (31.2863928030413, 60.5776030513415),
+                               (42.6953266459619, 76.8712491781612),
+                               (54.1042604888826, 93.164895304981)
+                              ]
+    self.mySpline = self.mySketch.addSpline(degree = self.myDegree,
+                                            poles = self.myPolesCoordinates,
+                                            weights = [1, 1, 1, 1, 1, 1, 0.957903314642061, 0.95790331464206, 1, 1, 1, 1, 1, 1],
+                                            knots = [-494.543457494654, 500, 507.372773368102, 1501.91623086297],
+                                            multiplicities = [6, 4, 4, 6])
+    self.myDOF += len(self.myPolesCoordinates) * 2
+    self.myNbSplines += 1
+    model.do()
+
+    assert(self.mySpline.feature())
+    assert(self.mySpline.feature().error() == "")
+    assert(self.mySpline.degree().value() == self.myDegree)
+
+  def test_bspline_linear(self):
+    """ Test 6. Create B-spline curve by 2 poles
+    """
+    self.myDegree = 1
+    self.mySpline = self.mySketch.addSpline(poles = self.myPoles[:2])
+    self.myDOF += 4
+    self.myNbSplines += 1
+    model.do()
+
+    assert(self.mySpline.feature())
+    assert(self.mySpline.feature().error() == "")
+    assert(self.mySpline.degree().value() == self.myDegree)
+
+  def test_bspline_parabola(self):
+    """ Test 7. Create B-spline curve by 3 poles
+    """
+    self.myDegree = 2
+    self.mySpline = self.mySketch.addSpline(poles = self.myPoles[:3])
+    self.myDOF += 6
+    self.myNbSplines += 1
+    model.do()
+
+    assert(self.mySpline.feature())
+    assert(self.mySpline.feature().error() == "")
+    assert(self.mySpline.degree().value() == self.myDegree)
+
+  def test_bspline_with_poles(self):
+    """ Test 8. Create B-spline curve and points coincident with its poles
+    """
+    self.mySpline = self.mySketch.addSpline(poles = self.myPoles)
+    self.mySpline.controlPoles(regular = [0, 2], auxiliary = [1, 3])
+    self.myDOF += len(self.myPoles) * 2
+    self.myNbSplines += 1
+    self.myNbPoints += 4
+    model.do()
+
+    assert(self.mySpline.feature())
+    assert(self.mySpline.feature().error() == "")
+    assert(self.mySpline.degree().value() == self.myDegree)
+
+  def test_bspline_with_polygon(self):
+    """ Test 9. Create B-spline curve and its control polygon
+    """
+    self.mySpline = self.mySketch.addSpline(poles = self.myPoles)
+    self.mySpline.controlPolygon(regular = [0, 2], auxiliary = [1, 3])
+    self.myDOF += len(self.myPoles) * 2
+    self.myNbSplines += 1
+    self.myNbLines += 4
+    model.do()
+
+    assert(self.mySpline.feature())
+    assert(self.mySpline.feature().error() == "")
+    assert(self.mySpline.degree().value() == self.myDegree)
+
+if __name__ == "__main__":
+    test_program = unittest.main(exit=False)
+    assert test_program.result.wasSuccessful(), "Test failed"
+    assert model.checkPythonDump()
diff --git a/src/SketchPlugin/Test/TestCreateBSplinePeriodic.py b/src/SketchPlugin/Test/TestCreateBSplinePeriodic.py
new file mode 100644 (file)
index 0000000..7cec4b0
--- /dev/null
@@ -0,0 +1,184 @@
+# 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
+#
+
+"""
+    Test creation of periodic B-spline curve
+"""
+
+import unittest
+from salome.shaper import model
+
+from GeomAPI import *
+from SketchAPI import *
+
+__updated__ = "2020-01-24"
+
+class TestBSplinePeriodic(unittest.TestCase):
+  def setUp(self):
+    model.begin()
+    self.myDocument = model.moduleDocument()
+    self.mySketch = model.addSketch(self.myDocument, model.defaultPlane("XOY"))
+    self.myPoles = [GeomAPI_Pnt2d(50., 50.), GeomAPI_Pnt2d(70., 70.), GeomAPI_Pnt2d(80., 30.), GeomAPI_Pnt2d(50., 10.), GeomAPI_Pnt2d(10., -30.)]
+    self.myPolesCoordinates = [(50., 50.), (70., 70.), (80., 30.), (50., 10.), (10., -30.)]
+    self.myDegree = 3;
+    self.myDOF = 0
+    self.myNbPoints = 0
+    self.myNbLines = 0
+    self.myNbSplines = 0
+
+  def tearDown(self):
+    self.checkDOF()
+    model.end()
+    model.testNbSubFeatures(self.mySketch, "SketchPoint", self.myNbPoints)
+    model.testNbSubFeatures(self.mySketch, "SketchLine", self.myNbLines)
+    model.testNbSubFeatures(self.mySketch, "SketchBSplinePeriodic", self.myNbSplines)
+
+
+  def checkDOF(self):
+    self.assertEqual(model.dof(self.mySketch), self.myDOF)
+
+
+  def test_bspline_by_coordinates(self):
+    """ Test 1. Create B-spline curve by coordinates of its poles
+    """
+    self.mySpline = self.mySketch.addSpline(poles = self.myPolesCoordinates, periodic = True)
+    self.myDOF += len(self.myPolesCoordinates) * 2
+    self.myNbSplines += 1
+    model.do()
+
+    assert(self.mySpline.feature())
+    assert(self.mySpline.feature().error() == "")
+    assert(self.mySpline.degree().value() == self.myDegree)
+
+  def test_bspline_by_poles(self):
+    """ Test 2. Create B-spline curve by poles
+    """
+    self.mySpline = self.mySketch.addSpline(poles = self.myPoles, periodic = True)
+    self.myDOF += len(self.myPoles) * 2
+    self.myNbSplines += 1
+    model.do()
+
+    assert(self.mySpline.feature())
+    assert(self.mySpline.feature().error() == "")
+    assert(self.mySpline.degree().value() == self.myDegree)
+
+  def test_bspline_by_degree_and_poles(self):
+    """ Test 3. Create B-spline curve by poles and degree
+    """
+    self.myDegree = 4
+    self.mySpline = self.mySketch.addSpline(degree = self.myDegree, poles = self.myPoles, periodic = True)
+    self.myDOF += len(self.myPoles) * 2
+    self.myNbSplines += 1
+    model.do()
+
+    assert(self.mySpline.feature())
+    assert(self.mySpline.feature().error() == "")
+    assert(self.mySpline.degree().value() == self.myDegree)
+
+  def test_bspline_by_poles_and_weights(self):
+    """ Test 4. Create B-spline curve by poles and weights
+    """
+    self.mySpline = self.mySketch.addSpline(poles = self.myPoles, weights = [1, 2, 3, 2, 1], periodic = True)
+    self.myDOF += len(self.myPoles) * 2
+    self.myNbSplines += 1
+    model.do()
+
+    assert(self.mySpline.feature())
+    assert(self.mySpline.feature().error() == "")
+    assert(self.mySpline.degree().value() == self.myDegree)
+
+  def test_bspline_by_parametric(self):
+    """ Test 5. Create B-spline curve by whole set of parameters
+    """
+    self.myDegree = 3
+    self.myPolesCoordinates = [(-10, 0), (-20, 20), (0, 10), (20, 20),
+                               (10, 0), (20, -20), (0, -10), (-20, -20)
+                              ]
+    self.mySpline = self.mySketch.addSpline(degree = self.myDegree,
+                                            poles = self.myPolesCoordinates,
+                                            weights = [1, 1, 1, 1, 1, 1, 1, 1],
+                                            knots = [0, 1, 2, 3, 4, 5, 6, 7, 8],
+                                            multiplicities = [1, 1, 1, 1, 1, 1, 1, 1, 1],
+                                            periodic = True)
+    self.myDOF += len(self.myPolesCoordinates) * 2
+    self.myNbSplines += 1
+    model.do()
+
+    assert(self.mySpline.feature())
+    assert(self.mySpline.feature().error() == "")
+    assert(self.mySpline.degree().value() == self.myDegree)
+
+  def test_bspline_linear(self):
+    """ Test 6. Create B-spline curve by 2 poles
+    """
+    self.myDegree = 1
+    self.mySpline = self.mySketch.addSpline(poles = self.myPoles[:2], periodic = True)
+    self.myDOF += 4
+    self.myNbSplines += 1
+    model.do()
+
+    assert(self.mySpline.feature())
+    assert(self.mySpline.feature().error() == "")
+    assert(self.mySpline.degree().value() == self.myDegree)
+
+  def test_bspline_parabola(self):
+    """ Test 7. Create B-spline curve by 3 poles
+    """
+    self.myDegree = 2
+    self.mySpline = self.mySketch.addSpline(poles = self.myPoles[:3], periodic = True)
+    self.myDOF += 6
+    self.myNbSplines += 1
+    model.do()
+
+    assert(self.mySpline.feature())
+    assert(self.mySpline.feature().error() == "")
+    assert(self.mySpline.degree().value() == self.myDegree)
+
+  def test_bspline_with_poles(self):
+    """ Test 8. Create B-spline curve and points coincident with its poles
+    """
+    self.mySpline = self.mySketch.addSpline(poles = self.myPoles, periodic = True)
+    self.mySpline.controlPoles(regular = [0, 2], auxiliary = [1, 3])
+    self.myDOF += len(self.myPoles) * 2
+    self.myNbSplines += 1
+    self.myNbPoints += 4
+    model.do()
+
+    assert(self.mySpline.feature())
+    assert(self.mySpline.feature().error() == "")
+    assert(self.mySpline.degree().value() == self.myDegree)
+
+  def test_bspline_with_polygon(self):
+    """ Test 9. Create B-spline curve and its control polygon
+    """
+    self.mySpline = self.mySketch.addSpline(poles = self.myPoles, periodic = True)
+    self.mySpline.controlPolygon(regular = [0, 2], auxiliary = [1, 3])
+    self.myDOF += len(self.myPoles) * 2
+    self.myNbSplines += 1
+    self.myNbLines += 4
+    model.do()
+
+    assert(self.mySpline.feature())
+    assert(self.mySpline.feature().error() == "")
+    assert(self.mySpline.degree().value() == self.myDegree)
+
+if __name__ == "__main__":
+    test_program = unittest.main(exit=False)
+    assert test_program.result.wasSuccessful(), "Test failed"
+    assert model.checkPythonDump()
diff --git a/src/SketchPlugin/Test/TestMoveBSpline.py b/src/SketchPlugin/Test/TestMoveBSpline.py
new file mode 100644 (file)
index 0000000..3e01ac7
--- /dev/null
@@ -0,0 +1,418 @@
+# Copyright (C) 2019-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
+#
+
+"""
+    Test movement of the B-spline curve
+"""
+
+import unittest
+import math
+from GeomAPI import *
+from GeomDataAPI import geomDataAPI_Point2DArray
+from SketchAPI import *
+from salome.shaper import model
+
+__updated__ = "2020-01-20"
+
+class TestMoveBSpline(unittest.TestCase):
+  def setUp(self):
+    model.begin()
+    self.myDocument = model.moduleDocument()
+    self.mySketch = model.addSketch(self.myDocument, model.defaultPlane("XOY"))
+    self.myPoles = [GeomAPI_Pnt2d(20., 50.),
+                    GeomAPI_Pnt2d(70., 70.),
+                    GeomAPI_Pnt2d(80., 30.),
+                    GeomAPI_Pnt2d(50., 10.),
+                    GeomAPI_Pnt2d(90., -30.)]
+    self.mySpline = self.mySketch.addSpline(poles = self.myPoles)
+    self.myDOF = len(self.myPoles) * 2
+    model.do()
+    self.checkDOF()
+
+  def tearDown(self):
+    self.checkDOF()
+    model.end()
+
+  def checkDOF(self):
+    self.assertEqual(model.dof(self.mySketch), self.myDOF)
+
+  def checkPointCoordinates(self, thePoint, theCoordinates):
+    aCoord = []
+    if issubclass(type(theCoordinates), GeomAPI_Pnt2d):
+      aCoord = [theCoordinates.x(), theCoordinates.y()]
+    else:
+      aCoord = theCoordinates
+    DIGITS = 7 - math.floor(math.log10(math.hypot(aCoord[0], aCoord[1])))
+    self.assertAlmostEqual(thePoint.x(), aCoord[0], DIGITS)
+    self.assertAlmostEqual(thePoint.y(), aCoord[1], DIGITS)
+
+  def checkPoles(self, theBSpline, theCoordinates):
+    poles = theBSpline.poles()
+    for index, point in zip(range(0, len(theCoordinates)), theCoordinates):
+      self.checkPointCoordinates(poles.pnt(index), point)
+
+  def fixPoint(self, thePoint):
+    self.mySketch.setFixed(thePoint)
+    self.myDOF -= 2
+    model.do()
+    self.checkDOF()
+
+
+  def test_move_free_bspline(self):
+    """ Test 1. Movement of a free B-spline dragging the edge
+    """
+    oldPosition = GeomAPI_Edge(self.mySpline.defaultResult().shape()).middlePoint()
+    newPosition = GeomAPI_Pnt2d(120., 90.)
+    self.mySketch.move(self.mySpline.defaultResult(), newPosition)
+    model.do()
+
+    # plane is XOY, no need to project oldPosition point
+    dx = newPosition.x() - oldPosition.x()
+    dy = newPosition.y() - oldPosition.y()
+
+    newPoles = []
+    for pole in self.myPoles:
+      newPoles.append(GeomAPI_Pnt2d(pole.x() + dx, pole.y() + dy))
+    self.checkPoles(self.mySpline, newPoles)
+
+  def test_move_start_point(self):
+    """ Test 2. Movement of start point of a free B-spline curve
+    """
+    newPoles = self.myPoles
+
+    newPoles[0].setX(newPoles[0].x() - 20.)
+    newPoles[0].setY(newPoles[0].y() + 10.)
+    self.mySketch.move(self.mySpline.startPoint(), newPoles[0])
+    model.do()
+
+    self.checkPoles(self.mySpline, newPoles)
+
+  def test_move_end_point(self):
+    """ Test 3. Movement of end point of a free B-spline curve
+    """
+    newPoles = self.myPoles
+
+    newPoles[-1].setX(newPoles[-1].x() + 20.)
+    newPoles[-1].setY(newPoles[-1].y() + 10.)
+    self.mySketch.move(self.mySpline.endPoint(), newPoles[-1])
+    model.do()
+
+    self.checkPoles(self.mySpline, newPoles)
+
+
+  def test_move_bspline_with_start_point_fixed(self):
+    """ Test 4. Movement of a B-spline dragging the edge when start point is fixed
+    """
+    self.fixPoint(self.mySpline.startPoint())
+    model.do()
+
+    oldPosition = GeomAPI_Edge(self.mySpline.defaultResult().shape()).middlePoint()
+    newPosition = GeomAPI_Pnt2d(120., 90.)
+    self.mySketch.move(self.mySpline.defaultResult(), newPosition)
+    model.do()
+
+    # plane is XOY, no need to project oldPosition point
+    dx = newPosition.x() - oldPosition.x()
+    dy = newPosition.y() - oldPosition.y()
+
+    newPoles = [self.myPoles[0]]
+    for pole in self.myPoles[1:]:
+      newPoles.append(GeomAPI_Pnt2d(pole.x() + dx, pole.y() + dy))
+    self.checkPoles(self.mySpline, newPoles)
+
+  def test_move_start_point_with_start_point_fixed(self):
+    """ Test 5. Movement of start point of a free B-spline curve has no result if the first pole is fixed
+    """
+    self.fixPoint(self.mySpline.startPoint())
+    model.do()
+
+    self.mySketch.move(self.mySpline.startPoint(), self.myPoles[0].x() - 10., self.myPoles[0].y() + 10.)
+    model.do()
+
+    self.checkPoles(self.mySpline, self.myPoles)
+
+  def test_move_end_point_with_start_point_fixed(self):
+    """ Test 6. Movement of end point of a free B-spline curve when start point is fixed
+    """
+    self.fixPoint(self.mySpline.startPoint())
+    model.do()
+
+    newPoles = self.myPoles
+
+    newPoles[-1].setX(newPoles[-1].x() + 20.)
+    newPoles[-1].setY(newPoles[-1].y() + 10.)
+    self.mySketch.move(self.mySpline.endPoint(), newPoles[-1])
+    model.do()
+
+    self.checkPoles(self.mySpline, newPoles)
+
+
+  def test_move_bspline_with_end_point_fixed(self):
+    """ Test 7. Movement of a B-spline dragging the edge when end point is fixed
+    """
+    self.fixPoint(self.mySpline.endPoint())
+    model.do()
+
+    oldPosition = GeomAPI_Edge(self.mySpline.defaultResult().shape()).middlePoint()
+    newPosition = GeomAPI_Pnt2d(120., 90.)
+    self.mySketch.move(self.mySpline.defaultResult(), newPosition)
+    model.do()
+
+    # plane is XOY, no need to project oldPosition point
+    dx = newPosition.x() - oldPosition.x()
+    dy = newPosition.y() - oldPosition.y()
+
+    newPoles = []
+    for pole in self.myPoles[:-1]:
+      newPoles.append(GeomAPI_Pnt2d(pole.x() + dx, pole.y() + dy))
+    newPoles.append(self.myPoles[-1])
+    self.checkPoles(self.mySpline, newPoles)
+
+  def test_move_start_point_with_end_point_fixed(self):
+    """ Test 8. Movement of start point of a free B-spline curve when end point is fixed
+    """
+    self.fixPoint(self.mySpline.endPoint())
+    model.do()
+
+    newPoles = self.myPoles
+
+    newPoles[0].setX(newPoles[0].x() + 20.)
+    newPoles[0].setY(newPoles[0].y() + 10.)
+    self.mySketch.move(self.mySpline.startPoint(), self.myPoles[0])
+    model.do()
+
+    self.checkPoles(self.mySpline, self.myPoles)
+
+  def test_move_end_point_with_end_point_fixed(self):
+    """ Test 9. Movement of end point of a free B-spline curve has no result if the last pole is fixed
+    """
+    self.fixPoint(self.mySpline.endPoint())
+    model.do()
+
+    self.mySketch.move(self.mySpline.endPoint(), self.myPoles[-1].x() + 10., self.myPoles[-1].y() + 10.)
+    model.do()
+
+    self.checkPoles(self.mySpline, self.myPoles)
+
+
+  def test_move_fixed_bspline(self):
+    """ Test 10. Movement of a fully fixed B-spline
+    """
+    self.mySketch.setFixed(self.mySpline.defaultResult())
+    self.myDOF = 0
+    model.do()
+
+    newPosition = GeomAPI_Pnt2d(120., 90.)
+    self.mySketch.move(self.mySpline.defaultResult(), newPosition)
+    model.do()
+
+    self.checkPoles(self.mySpline, self.myPoles)
+
+  def test_move_start_point_of_fixed_bspline(self):
+    """ Test 11. Movement of start point of a fully fixed B-spline curve
+    """
+    self.mySketch.setFixed(self.mySpline.defaultResult())
+    self.myDOF = 0
+    model.do()
+
+    self.mySketch.move(self.mySpline.startPoint(), self.myPoles[0].x() + 10., self.myPoles[0].y() + 10.)
+    model.do()
+
+    self.checkPoles(self.mySpline, self.myPoles)
+
+  def test_move_end_point_of_fixed_bspline(self):
+    """ Test 12. Movement of end point of a fully fixed B-spline curve
+    """
+    self.mySketch.setFixed(self.mySpline.defaultResult())
+    self.myDOF = 0
+    model.do()
+
+    self.mySketch.move(self.mySpline.endPoint(), self.myPoles[-1].x() + 10., self.myPoles[-1].y() + 10.)
+    model.do()
+
+    self.checkPoles(self.mySpline, self.myPoles)
+
+
+  def test_move_bspline_with_fixed_pole(self):
+    """ Test 13. Movement of a B-spline curve with fixed pole
+    """
+    [Point_1, Point_2, Point_3, Point_4, Point_5] = self.mySpline.controlPoles(auxiliary = [0, 1, 2, 3, 4])
+    model.do()
+
+    self.fixPoint(Point_2.defaultResult())
+    model.do()
+
+    oldPosition = GeomAPI_Edge(self.mySpline.defaultResult().shape()).middlePoint()
+    newPosition = GeomAPI_Pnt2d(120., 90.)
+    self.mySketch.move(self.mySpline.defaultResult(), newPosition)
+    model.do()
+
+    # plane is XOY, no need to project oldPosition point
+    dx = newPosition.x() - oldPosition.x()
+    dy = newPosition.y() - oldPosition.y()
+
+    newPoles = []
+    for pole in self.myPoles:
+      newPoles.append(GeomAPI_Pnt2d(pole.x() + dx, pole.y() + dy))
+    newPoles[1].setX(newPoles[1].x() - dx)
+    newPoles[1].setY(newPoles[1].y() - dy)
+    self.checkPoles(self.mySpline, newPoles)
+
+  def test_move_start_point_with_fixed_pole(self):
+    """ Test 14. Movement of start point of a B-spline curve with fixed pole
+    """
+    [Point_1, Point_2, Point_3, Point_4, Point_5] = self.mySpline.controlPoles(auxiliary = [0, 1, 2, 3, 4])
+    model.do()
+
+    self.fixPoint(Point_2.defaultResult())
+    model.do()
+
+    newPoles = self.myPoles
+
+    newPoles[0].setX(newPoles[0].x() + 20.)
+    newPoles[0].setY(newPoles[0].y() + 10.)
+    self.mySketch.move(self.mySpline.startPoint(), newPoles[0])
+    model.do()
+
+    self.checkPoles(self.mySpline, newPoles)
+
+  def test_move_end_point_with_fixed_pole(self):
+    """ Test 15. Movement of end point of a B-spline curve with fixed pole
+    """
+    [Point_1, Point_2, Point_3, Point_4, Point_5] = self.mySpline.controlPoles(auxiliary = [0, 1, 2, 3, 4])
+    model.do()
+
+    self.fixPoint(Point_2.defaultResult())
+    model.do()
+
+    newPoles = self.myPoles
+
+    newPoles[-1].setX(newPoles[-1].x() + 20.)
+    newPoles[-1].setY(newPoles[-1].y() + 10.)
+    self.mySketch.move(self.mySpline.endPoint(), newPoles[-1])
+    model.do()
+
+    self.checkPoles(self.mySpline, newPoles)
+
+
+  def test_move_bspline_with_fixed_segment(self):
+    """ Test 16. Movement of a B-spline curve with fixed control segment
+    """
+    [Line_1, Line_2, Line_3, Line_4] = self.mySpline.controlPolygon(auxiliary = [0, 1, 2, 3])
+    model.do()
+
+    self.mySketch.setFixed(Line_1.defaultResult())
+    self.myDOF -= 4
+    model.do()
+
+    oldPosition = GeomAPI_Edge(self.mySpline.defaultResult().shape()).middlePoint()
+    newPosition = GeomAPI_Pnt2d(120., 90.)
+    self.mySketch.move(self.mySpline.defaultResult(), newPosition)
+    model.do()
+
+    # plane is XOY, no need to project oldPosition point
+    dx = newPosition.x() - oldPosition.x()
+    dy = newPosition.y() - oldPosition.y()
+
+    newPoles = [self.myPoles[0], self.myPoles[1]]
+    for pole in self.myPoles[2:]:
+      newPoles.append(GeomAPI_Pnt2d(pole.x() + dx, pole.y() + dy))
+    self.checkPoles(self.mySpline, newPoles)
+
+  def test_move_start_point_with_fixed_segment(self):
+    """ Test 17. Movement of start point of a B-spline curve with fixed control segment
+    """
+    [Line_1, Line_2, Line_3, Line_4] = self.mySpline.controlPolygon(auxiliary = [0, 1, 2, 3])
+    model.do()
+
+    self.mySketch.setFixed(Line_1.defaultResult())
+    self.myDOF -= 4
+    model.do()
+
+    self.mySketch.move(self.mySpline.startPoint(), self.myPoles[0].x() + 10., self.myPoles[0].y() - 20.)
+    model.do()
+
+    self.checkPoles(self.mySpline, self.myPoles)
+
+  def test_move_end_point_with_fixed_segment(self):
+    """ Test 18. Movement of end point of a B-spline curve with fixed control segment
+    """
+    [Line_1, Line_2, Line_3, Line_4] = self.mySpline.controlPolygon(auxiliary = [0, 1, 2, 3])
+    model.do()
+
+    self.mySketch.setFixed(Line_1.defaultResult())
+    self.myDOF -= 4
+    model.do()
+
+    newPoles = self.myPoles
+
+    newPoles[-1].setX(newPoles[-1].x() + 20.)
+    newPoles[-1].setY(newPoles[-1].y() + 10.)
+    self.mySketch.move(self.mySpline.endPoint(), newPoles[-1])
+    model.do()
+
+    self.checkPoles(self.mySpline, newPoles)
+
+
+  def test_move_pole_of_free_bspline(self):
+    """ Test 19. Movement of a pole of a B-spline curve
+    """
+    [Point_1, Point_2, Point_3, Point_4, Point_5] = self.mySpline.controlPoles(auxiliary = [0, 1, 2, 3, 4])
+    [Line_1, Line_2, Line_3, Line_4] = self.mySpline.controlPolygon(auxiliary = [0, 1, 2, 3])
+    model.do()
+
+    newPoles = self.myPoles
+
+    newPoles[2].setX(newPoles[2].x() + 20.)
+    newPoles[2].setY(newPoles[2].y() + 20.)
+    self.mySketch.move(SketchAPI_Point(Point_3).coordinates(), newPoles[2])
+    model.do()
+
+    self.checkPoles(self.mySpline, newPoles)
+
+  def test_move_segment_of_free_bspline(self):
+    """ Test 20. Movement of a control segment of a B-spline curve
+    """
+    [Point_1, Point_2, Point_3, Point_4, Point_5] = self.mySpline.controlPoles(auxiliary = [0, 1, 2, 3, 4])
+    [Line_1, Line_2, Line_3, Line_4] = self.mySpline.controlPolygon(auxiliary = [0, 1, 2, 3])
+    model.do()
+
+    oldPosition = GeomAPI_Pnt2d(0.5 * (self.myPoles[2].x() + self.myPoles[3].x()),
+                                0.5 * (self.myPoles[2].y() + self.myPoles[3].y()))
+    newPosition = GeomAPI_Pnt2d(120., 90.)
+    self.mySketch.move(SketchAPI_Line(Line_3).defaultResult(), newPosition)
+    model.do()
+
+    dx = newPosition.x() - oldPosition.x()
+    dy = newPosition.y() - oldPosition.y()
+
+    newPoles = self.myPoles
+    newPoles[2].setX(newPoles[2].x() + dx)
+    newPoles[2].setY(newPoles[2].y() + dy)
+    newPoles[3].setX(newPoles[3].x() + dx)
+    newPoles[3].setY(newPoles[3].y() + dy)
+
+    self.checkPoles(self.mySpline, newPoles)
+
+
+
+if __name__ == "__main__":
+    test_program = unittest.main(exit=False)
+    assert test_program.result.wasSuccessful(), "Test failed"
+    assert model.checkPythonDump()
diff --git a/src/SketchPlugin/Test/TestMoveBSplinePeriodic.py b/src/SketchPlugin/Test/TestMoveBSplinePeriodic.py
new file mode 100644 (file)
index 0000000..d7c702a
--- /dev/null
@@ -0,0 +1,205 @@
+# Copyright (C) 2019-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
+#
+
+"""
+    Test movement of the periodic B-spline curve
+"""
+
+import unittest
+import math
+from GeomAPI import *
+from GeomDataAPI import geomDataAPI_Point2DArray
+from SketchAPI import *
+from salome.shaper import model
+
+__updated__ = "2020-01-20"
+
+class TestMoveBSpline(unittest.TestCase):
+  def setUp(self):
+    model.begin()
+    self.myDocument = model.moduleDocument()
+    self.mySketch = model.addSketch(self.myDocument, model.defaultPlane("XOY"))
+    self.myPoles = [GeomAPI_Pnt2d(20., 50.),
+                    GeomAPI_Pnt2d(70., 70.),
+                    GeomAPI_Pnt2d(80., 30.),
+                    GeomAPI_Pnt2d(50., -10.),
+                    GeomAPI_Pnt2d(90., -30.)]
+    self.mySpline = self.mySketch.addSpline(poles = self.myPoles, periodic = True)
+    self.myDOF = len(self.myPoles) * 2
+    model.do()
+    self.checkDOF()
+
+  def tearDown(self):
+    self.checkDOF()
+    model.end()
+
+  def checkDOF(self):
+    self.assertEqual(model.dof(self.mySketch), self.myDOF)
+
+  def checkPointCoordinates(self, thePoint, theCoordinates):
+    aCoord = []
+    if issubclass(type(theCoordinates), GeomAPI_Pnt2d):
+      aCoord = [theCoordinates.x(), theCoordinates.y()]
+    else:
+      aCoord = theCoordinates
+    DIGITS = 7 - math.floor(math.log10(math.hypot(aCoord[0], aCoord[1])))
+    self.assertAlmostEqual(thePoint.x(), aCoord[0], DIGITS)
+    self.assertAlmostEqual(thePoint.y(), aCoord[1], DIGITS)
+
+  def checkPoles(self, theBSpline, theCoordinates):
+    poles = theBSpline.poles()
+    for index, point in zip(range(0, len(theCoordinates)), theCoordinates):
+      self.checkPointCoordinates(poles.pnt(index), point)
+
+  def fixPoint(self, thePoint):
+    self.mySketch.setFixed(thePoint)
+    self.myDOF -= 2
+    model.do()
+    self.checkDOF()
+
+
+  def test_move_free_bspline(self):
+    """ Test 1. Movement of a free B-spline dragging the edge
+    """
+    oldPosition = GeomAPI_Edge(self.mySpline.defaultResult().shape()).middlePoint()
+    newPosition = GeomAPI_Pnt2d(120., 90.)
+    self.mySketch.move(self.mySpline.defaultResult(), newPosition)
+    model.do()
+
+    # plane is XOY, no need to project oldPosition point
+    dx = newPosition.x() - oldPosition.x()
+    dy = newPosition.y() - oldPosition.y()
+
+    newPoles = []
+    for pole in self.myPoles:
+      newPoles.append(GeomAPI_Pnt2d(pole.x() + dx, pole.y() + dy))
+    self.checkPoles(self.mySpline, newPoles)
+
+
+  def test_move_fixed_bspline(self):
+    """ Test 2. Movement of a fully fixed B-spline
+    """
+    self.mySketch.setFixed(self.mySpline.defaultResult())
+    self.myDOF = 0
+    model.do()
+
+    newPosition = GeomAPI_Pnt2d(120., 90.)
+    self.mySketch.move(self.mySpline.defaultResult(), newPosition)
+    model.do()
+
+    self.checkPoles(self.mySpline, self.myPoles)
+
+
+  def test_move_bspline_with_fixed_pole(self):
+    """ Test 3. Movement of a B-spline curve with fixed pole
+    """
+    [Point_1, Point_2, Point_3, Point_4, Point_5] = self.mySpline.controlPoles(auxiliary = [0, 1, 2, 3, 4])
+    model.do()
+
+    self.fixPoint(Point_2.defaultResult())
+    model.do()
+
+    oldPosition = GeomAPI_Edge(self.mySpline.defaultResult().shape()).middlePoint()
+    newPosition = GeomAPI_Pnt2d(120., 90.)
+    self.mySketch.move(self.mySpline.defaultResult(), newPosition)
+    model.do()
+
+    # plane is XOY, no need to project oldPosition point
+    dx = newPosition.x() - oldPosition.x()
+    dy = newPosition.y() - oldPosition.y()
+
+    newPoles = []
+    for pole in self.myPoles:
+      newPoles.append(GeomAPI_Pnt2d(pole.x() + dx, pole.y() + dy))
+    newPoles[1].setX(newPoles[1].x() - dx)
+    newPoles[1].setY(newPoles[1].y() - dy)
+    self.checkPoles(self.mySpline, newPoles)
+
+
+  def test_move_bspline_with_fixed_segment(self):
+    """ Test 4. Movement of a B-spline curve with fixed control segment
+    """
+    [Line_1, Line_2, Line_3, Line_4, Line_5] = self.mySpline.controlPolygon(auxiliary = [0, 1, 2, 3, 4])
+    model.do()
+
+    self.mySketch.setFixed(Line_1.defaultResult())
+    self.myDOF -= 4
+    model.do()
+
+    oldPosition = GeomAPI_Edge(self.mySpline.defaultResult().shape()).middlePoint()
+    newPosition = GeomAPI_Pnt2d(120., 90.)
+    self.mySketch.move(self.mySpline.defaultResult(), newPosition)
+    model.do()
+
+    # plane is XOY, no need to project oldPosition point
+    dx = newPosition.x() - oldPosition.x()
+    dy = newPosition.y() - oldPosition.y()
+
+    newPoles = [self.myPoles[0], self.myPoles[1]]
+    for pole in self.myPoles[2:]:
+      newPoles.append(GeomAPI_Pnt2d(pole.x() + dx, pole.y() + dy))
+    self.checkPoles(self.mySpline, newPoles)
+
+
+  def test_move_pole_of_free_bspline(self):
+    """ Test 5. Movement of a pole of a B-spline curve
+    """
+    [Point_1, Point_2, Point_3, Point_4, Point_5] = self.mySpline.controlPoles(auxiliary = [0, 1, 2, 3, 4])
+    [Line_1, Line_2, Line_3, Line_4, Line_5] = self.mySpline.controlPolygon(auxiliary = [0, 1, 2, 3, 4])
+    model.do()
+
+    newPoles = self.myPoles
+
+    newPoles[2].setX(newPoles[2].x() + 20.)
+    newPoles[2].setY(newPoles[2].y() + 20.)
+    self.mySketch.move(SketchAPI_Point(Point_3).coordinates(), newPoles[2])
+    model.do()
+
+    self.checkPoles(self.mySpline, newPoles)
+
+  def test_move_segment_of_free_bspline(self):
+    """ Test 6. Movement of a control segment of a B-spline curve
+    """
+    [Point_1, Point_2, Point_3, Point_4, Point_5] = self.mySpline.controlPoles(auxiliary = [0, 1, 2, 3, 4])
+    [Line_1, Line_2, Line_3, Line_4, Line_5] = self.mySpline.controlPolygon(auxiliary = [0, 1, 2, 3, 4])
+    model.do()
+
+    oldPosition = GeomAPI_Pnt2d(0.5 * (self.myPoles[2].x() + self.myPoles[3].x()),
+                                0.5 * (self.myPoles[2].y() + self.myPoles[3].y()))
+    newPosition = GeomAPI_Pnt2d(120., 90.)
+    self.mySketch.move(SketchAPI_Line(Line_3).defaultResult(), newPosition)
+    model.do()
+
+    dx = newPosition.x() - oldPosition.x()
+    dy = newPosition.y() - oldPosition.y()
+
+    newPoles = self.myPoles
+    newPoles[2].setX(newPoles[2].x() + dx)
+    newPoles[2].setY(newPoles[2].y() + dy)
+    newPoles[3].setX(newPoles[3].x() + dx)
+    newPoles[3].setY(newPoles[3].y() + dy)
+
+    self.checkPoles(self.mySpline, newPoles)
+
+
+
+if __name__ == "__main__":
+    test_program = unittest.main(exit=False)
+    assert test_program.result.wasSuccessful(), "Test failed"
+    assert model.checkPythonDump()
index b3400e70e822c20d0f326ab14451918e04fec697..2f09005efa5edcc31a874e5dc5b4c4cedabd3e7d 100644 (file)
@@ -156,3 +156,41 @@ anEllipticArcPnt3.setValue(0, 5)
 anEllipticArcPnt4.setValue(-10, 0)
 assert(featureToPresentation(anEllipticArc).getAISObject(None) is not None)
 aSession.finishOperation()
+
+# Test presentation for MacroBSpline on low-level
+aSession.startOperation()
+aBSpline = aSketchFeature.addFeature("SketchMacroBSpline")
+aPoles = geomDataAPI_Point2DArray(aBSpline.attribute("poles"))
+aPoles.setSize(4)
+aPoles.setPnt(0, 0, 0)
+aPoles.setPnt(1, 10, 0)
+aPoles.setPnt(2, 10, 10)
+aPoles.setPnt(3, 0, 10)
+aWeights = aBSpline.data().realArray("weights")
+aWeights.setSize(4)
+aWeights.setValue(0, 1)
+aWeights.setValue(1, 2)
+aWeights.setValue(2, 2)
+aWeights.setValue(3, 1)
+aBSpline.boolean("need_control_poly").setValue(True)
+assert(featureToPresentation(aBSpline).getAISObject(None) is not None)
+aSession.finishOperation()
+
+# Test presentation for MacroBSplinePeriodic on low-level
+aSession.startOperation()
+aBSplineP = aSketchFeature.addFeature("SketchMacroBSplinePeriodic")
+aPoles = geomDataAPI_Point2DArray(aBSplineP.attribute("poles"))
+aPoles.setSize(4)
+aPoles.setPnt(0, 0, 0)
+aPoles.setPnt(1, 10, 0)
+aPoles.setPnt(2, 10, 10)
+aPoles.setPnt(3, 0, 10)
+aWeights = aBSplineP.data().realArray("weights")
+aWeights.setSize(4)
+aWeights.setValue(0, 1)
+aWeights.setValue(1, 2)
+aWeights.setValue(2, 2)
+aWeights.setValue(3, 1)
+aBSplineP.boolean("need_control_poly").setValue(True)
+assert(featureToPresentation(aBSplineP).getAISObject(None) is not None)
+aSession.finishOperation()
diff --git a/src/SketchPlugin/Test/TestProjectionBSpline.py b/src/SketchPlugin/Test/TestProjectionBSpline.py
new file mode 100644 (file)
index 0000000..d053681
--- /dev/null
@@ -0,0 +1,78 @@
+# Copyright (C) 2019-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
+#
+
+from salome.shaper import model
+
+model.begin()
+partSet = model.moduleDocument()
+Part_1 = model.addPart(partSet)
+Part_1_doc = Part_1.document()
+Cylinder_1 = model.addCylinder(Part_1_doc, model.selection("VERTEX", "PartSet/Origin"), model.selection("EDGE", "PartSet/OZ"), 5, 10, 180)
+Sketch_1 = model.addSketch(Part_1_doc, model.selection("FACE", "Cylinder_1_1/Face_5"))
+SketchCircle_1 = Sketch_1.addCircle(-0.87355746875896, 7.873567272779828, 3.095312696967586)
+model.do()
+ExtrusionCut_1 = model.addExtrusionCut(Part_1_doc, [model.selection("FACE", "Sketch_1/Face-SketchCircle_1_2r")], model.selection(), [model.selection("SOLID", "Cylinder_1_1")])
+Rotation_1 = model.addRotation(Part_1_doc, [model.selection("SOLID", "ExtrusionCut_1_1")], model.selection("EDGE", "PartSet/OX"), 45)
+Edge_1 = model.addEdge(Part_1_doc, [model.selection("EDGE", "[Rotation_1_1/MF:Rotated&Cylinder_1_1/Face_1][(Rotation_1_1/MF:Rotated&Cylinder_1_1/Face_1)(Rotation_1_1/MF:Rotated&Cylinder_1_1/Face_5)(Rotation_1_1/MF:Rotated&Cylinder_1_1/Face_4)(Rotation_1_1/MF:Rotated&Cylinder_1_1/Face_3)2]")], False)
+
+Sketch_2 = model.addSketch(Part_1_doc, model.standardPlane("XOY"))
+SketchProjection_1 = Sketch_2.addProjection(model.selection("EDGE", "Edge_1_1"), True)
+SketchBSpline_1 = SketchProjection_1.createdFeature()
+model.do()
+
+Sketch_3 = model.addSketch(Part_1_doc, model.standardPlane("XOZ"))
+SketchProjection_2 = Sketch_3.addProjection(model.selection("EDGE", "Edge_1_1"), True)
+SketchBSpline_2 = SketchProjection_2.createdFeature()
+model.do()
+
+Sketch_4 = model.addSketch(Part_1_doc, model.standardPlane("YOZ"))
+SketchProjection_3 = Sketch_4.addProjection(model.selection("EDGE", "Edge_1_1"), True)
+SketchBSpline_3 = SketchProjection_3.createdFeature()
+model.do()
+
+model.end()
+
+from GeomAPI import *
+import math
+
+TOLERANCE = 1.e-7
+
+def checkProjection(theBSpline3D, theBSpline2D, theFlags):
+    assert(theBSpline2D.isEdge() and theBSpline2D.edge().isBSpline())
+    poles2D = GeomAPI_BSpline(GeomAPI_Curve(theBSpline2D)).poles()
+    poles3D = theBSpline3D.poles()
+    assert(poles2D.size() == poles3D.size())
+    for p2d, p3d in zip(poles2D, poles3D):
+        assert(math.fabs((p2d.x() - p3d.x()) * theFlags.x()) < TOLERANCE and
+               math.fabs((p2d.y() - p3d.y()) * theFlags.y()) < TOLERANCE and
+               math.fabs((p2d.z() - p3d.z()) * theFlags.z()) < TOLERANCE)
+
+
+bspline0 = GeomAPI_BSpline(GeomAPI_Curve(Edge_1.results()[-1].resultSubShapePair()[0].shape()))
+
+bsplineShape1 = SketchBSpline_1.results()[-1].resultSubShapePair()[0].shape()
+checkProjection(bspline0, bsplineShape1, GeomAPI_Pnt(1, 1, 0))
+
+bsplineShape2 = SketchBSpline_2.results()[-1].resultSubShapePair()[0].shape()
+checkProjection(bspline0, bsplineShape2, GeomAPI_Pnt(1, 0, 1))
+
+bsplineShape3 = SketchBSpline_3.results()[-1].resultSubShapePair()[0].shape()
+checkProjection(bspline0, bsplineShape3, GeomAPI_Pnt(0, 1, 1))
+
+assert(model.checkPythonDump())
diff --git a/src/SketchPlugin/Test/TestProjectionBSplinePeriodic.py b/src/SketchPlugin/Test/TestProjectionBSplinePeriodic.py
new file mode 100644 (file)
index 0000000..2ebd6b1
--- /dev/null
@@ -0,0 +1,78 @@
+# Copyright (C) 2019-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
+#
+
+from salome.shaper import model
+
+model.begin()
+partSet = model.moduleDocument()
+Part_1 = model.addPart(partSet)
+Part_1_doc = Part_1.document()
+Cylinder_1 = model.addCylinder(Part_1_doc, model.selection("VERTEX", "PartSet/Origin"), model.selection("EDGE", "PartSet/OZ"), 5, 10, 180)
+Sketch_1 = model.addSketch(Part_1_doc, model.selection("FACE", "Cylinder_1_1/Face_5"))
+SketchCircle_1 = Sketch_1.addCircle(-0.9379111501048892, 5.54816019935757, 2.957303770750356)
+model.do()
+ExtrusionCut_1 = model.addExtrusionCut(Part_1_doc, [model.selection("FACE", "Sketch_1/Face-SketchCircle_1_2r")], model.selection(), [model.selection("SOLID", "Cylinder_1_1")])
+Rotation_1 = model.addRotation(Part_1_doc, [model.selection("SOLID", "ExtrusionCut_1_1")], model.selection("EDGE", "PartSet/OX"), 45)
+Edge_1 = model.addEdge(Part_1_doc, [model.selection("EDGE", "[Rotation_1_1/MF:Rotated&Cylinder_1_1/Face_1][Rotation_1_1/MF:Rotated&Sketch_1/SketchCircle_1_2]")], False)
+
+Sketch_2 = model.addSketch(Part_1_doc, model.standardPlane("XOY"))
+SketchProjection_1 = Sketch_2.addProjection(model.selection("EDGE", "Edge_1_1"), True)
+SketchBSpline_1 = SketchProjection_1.createdFeature()
+model.do()
+
+Sketch_3 = model.addSketch(Part_1_doc, model.standardPlane("XOZ"))
+SketchProjection_2 = Sketch_3.addProjection(model.selection("EDGE", "Edge_1_1"), True)
+SketchBSpline_2 = SketchProjection_2.createdFeature()
+model.do()
+
+Sketch_4 = model.addSketch(Part_1_doc, model.standardPlane("YOZ"))
+SketchProjection_3 = Sketch_4.addProjection(model.selection("EDGE", "Edge_1_1"), True)
+SketchBSpline_3 = SketchProjection_3.createdFeature()
+model.do()
+
+model.end()
+
+from GeomAPI import *
+import math
+
+TOLERANCE = 1.e-7
+
+def checkProjection(theBSpline3D, theBSpline2D, theFlags):
+    assert(theBSpline2D.isEdge() and theBSpline2D.edge().isBSpline())
+    poles2D = GeomAPI_BSpline(GeomAPI_Curve(theBSpline2D)).poles()
+    poles3D = theBSpline3D.poles()
+    assert(poles2D.size() == poles3D.size())
+    for p2d, p3d in zip(poles2D, poles3D):
+        assert(math.fabs((p2d.x() - p3d.x()) * theFlags.x()) < TOLERANCE and
+               math.fabs((p2d.y() - p3d.y()) * theFlags.y()) < TOLERANCE and
+               math.fabs((p2d.z() - p3d.z()) * theFlags.z()) < TOLERANCE)
+
+
+bspline0 = GeomAPI_BSpline(GeomAPI_Curve(Edge_1.results()[-1].resultSubShapePair()[0].shape()))
+
+bsplineShape1 = SketchBSpline_1.results()[-1].resultSubShapePair()[0].shape()
+checkProjection(bspline0, bsplineShape1, GeomAPI_Pnt(1, 1, 0))
+
+bsplineShape2 = SketchBSpline_2.results()[-1].resultSubShapePair()[0].shape()
+checkProjection(bspline0, bsplineShape2, GeomAPI_Pnt(1, 0, 1))
+
+bsplineShape3 = SketchBSpline_3.results()[-1].resultSubShapePair()[0].shape()
+checkProjection(bspline0, bsplineShape3, GeomAPI_Pnt(0, 1, 1))
+
+assert(model.checkPythonDump())
diff --git a/src/SketchPlugin/Test/TestRemoveBSpline.py b/src/SketchPlugin/Test/TestRemoveBSpline.py
new file mode 100644 (file)
index 0000000..05da298
--- /dev/null
@@ -0,0 +1,108 @@
+# Copyright (C) 2019-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
+#
+
+"""
+    Test removing B-spline curve and its construstion elements
+"""
+
+from salome.shaper import model
+from ModelAPI import *
+
+def assertNbSubs(theSketch, theNbPoints, theNbLines, theNbSplines, theNbInternalConstraints):
+    model.testNbSubFeatures(theSketch, "SketchPoint", theNbPoints)
+    model.testNbSubFeatures(theSketch, "SketchLine", theNbLines)
+    model.testNbSubFeatures(theSketch, "SketchBSpline", theNbSplines)
+    model.testNbSubFeatures(theSketch, "SketchConstraintCoincidenceInternal", theNbInternalConstraints)
+
+model.begin()
+partSet = model.moduleDocument()
+Part_1 = model.addPart(partSet)
+Part_1_doc = Part_1.document()
+Sketch_1 = model.addSketch(Part_1_doc, model.defaultPlane("XOY"))
+SketchBSpline_1_poles = [(-30, -10), (-15, 20), (0, -10), (15, 20), (30, -10)]
+SketchBSpline_1 = Sketch_1.addSpline(poles = SketchBSpline_1_poles)
+controlPoles = SketchBSpline_1.controlPoles(auxiliary = [0, 1, 2, 3, 4])
+controlLines = SketchBSpline_1.controlPolygon(auxiliary = [0, 1, 2, 3])
+model.do()
+model.end()
+
+DEFAULT_DOF = len(SketchBSpline_1_poles) * 2
+DEFAULT_POINTS = len(SketchBSpline_1_poles)
+DEFAULT_LINES = len(SketchBSpline_1_poles) - 1
+DEFAULT_BSPLINES = 1
+DEAFULT_INTERNALS = len(controlPoles) + len(controlLines) * 2
+
+assertNbSubs(Sketch_1, DEFAULT_POINTS, DEFAULT_LINES, DEFAULT_BSPLINES, DEAFULT_INTERNALS)
+assert(model.dof(Sketch_1) == DEFAULT_DOF)
+
+# Test 1. Remove auxiliary points one by one.
+for pnt in controlPoles:
+    model.begin()
+    removeFeaturesAndReferences(FeatureSet([pnt.feature()]))
+    model.end()
+
+    assertNbSubs(Sketch_1, DEFAULT_POINTS - 1, DEFAULT_LINES, DEFAULT_BSPLINES, DEAFULT_INTERNALS - 1)
+    assert(model.dof(Sketch_1) == DEFAULT_DOF)
+    model.undo()
+    assertNbSubs(Sketch_1, DEFAULT_POINTS, DEFAULT_LINES, DEFAULT_BSPLINES, DEAFULT_INTERNALS)
+    assert(model.dof(Sketch_1) == DEFAULT_DOF)
+
+# Test 2. Remove auxiliary lines one by one.
+for ln in controlLines:
+    model.begin()
+    removeFeaturesAndReferences(FeatureSet([ln.feature()]))
+    model.end()
+
+    assertNbSubs(Sketch_1, DEFAULT_POINTS, DEFAULT_LINES - 1, DEFAULT_BSPLINES, DEAFULT_INTERNALS - 2)
+    assert(model.dof(Sketch_1) == DEFAULT_DOF)
+    model.undo()
+    assertNbSubs(Sketch_1, DEFAULT_POINTS, DEFAULT_LINES, DEFAULT_BSPLINES, DEAFULT_INTERNALS)
+    assert(model.dof(Sketch_1) == DEFAULT_DOF)
+
+# Test 3. Remove the B-spline curve.
+model.begin()
+removeFeaturesAndReferences(FeatureSet([SketchBSpline_1.feature()]))
+model.end()
+
+assertNbSubs(Sketch_1, 0, 0, 0, 0)
+assert(model.dof(Sketch_1) == 0)
+model.undo()
+assertNbSubs(Sketch_1, DEFAULT_POINTS, DEFAULT_LINES, DEFAULT_BSPLINES, DEAFULT_INTERNALS)
+assert(model.dof(Sketch_1) == DEFAULT_DOF)
+
+# Test 4. Remove some construction elements, make non-auxiliary a couple of the rest and check the dumping.
+model.begin()
+partSet = model.moduleDocument()
+Sketch_2 = model.addSketch(Part_1_doc, model.defaultPlane("XOY"))
+SketchBSpline_2_poles = [(-30, -10), (-15, -40), (0, -10), (15, -40), (30, -10)]
+SketchBSpline_2 = Sketch_2.addSpline(poles = SketchBSpline_2_poles)
+controlPoles2 = SketchBSpline_2.controlPoles(auxiliary = [0, 1, 2, 3, 4])
+controlLines2 = SketchBSpline_2.controlPolygon(auxiliary = [0, 1, 2, 3])
+model.do()
+model.end()
+
+model.begin()
+controlPoles2[1].setAuxiliary(False)
+controlLines2[2].setAuxiliary(False)
+removeFeaturesAndReferences(FeatureSet([controlPoles2[2].feature(), controlLines2[0].feature()]))
+model.end()
+
+assertNbSubs(Sketch_2, DEFAULT_POINTS - 1, DEFAULT_LINES - 1, DEFAULT_BSPLINES, DEAFULT_INTERNALS - 3)
+
+assert(model.checkPythonDump())
diff --git a/src/SketchPlugin/Test/TestRemoveBSplinePeriodic.py b/src/SketchPlugin/Test/TestRemoveBSplinePeriodic.py
new file mode 100644 (file)
index 0000000..4e9d3eb
--- /dev/null
@@ -0,0 +1,108 @@
+# Copyright (C) 2019-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
+#
+
+"""
+    Test removing peridoc B-spline curve and its construstion elements
+"""
+
+from salome.shaper import model
+from ModelAPI import *
+
+def assertNbSubs(theSketch, theNbPoints, theNbLines, theNbSplines, theNbInternalConstraints):
+    model.testNbSubFeatures(theSketch, "SketchPoint", theNbPoints)
+    model.testNbSubFeatures(theSketch, "SketchLine", theNbLines)
+    model.testNbSubFeatures(theSketch, "SketchBSplinePeriodic", theNbSplines)
+    model.testNbSubFeatures(theSketch, "SketchConstraintCoincidenceInternal", theNbInternalConstraints)
+
+model.begin()
+partSet = model.moduleDocument()
+Part_1 = model.addPart(partSet)
+Part_1_doc = Part_1.document()
+Sketch_1 = model.addSketch(Part_1_doc, model.defaultPlane("XOY"))
+SketchBSpline_1_poles = [(-30, -10), (-15, 20), (0, -10), (15, 20), (30, -10)]
+SketchBSpline_1 = Sketch_1.addSpline(poles = SketchBSpline_1_poles, periodic = True)
+controlPoles = SketchBSpline_1.controlPoles(auxiliary = [0, 1, 2, 3, 4])
+controlLines = SketchBSpline_1.controlPolygon(auxiliary = [0, 1, 2, 3, 4])
+model.do()
+model.end()
+
+DEFAULT_DOF = len(SketchBSpline_1_poles) * 2
+DEFAULT_POINTS = len(SketchBSpline_1_poles)
+DEFAULT_LINES = len(SketchBSpline_1_poles)
+DEFAULT_BSPLINES = 1
+DEAFULT_INTERNALS = len(controlPoles) + len(controlLines) * 2
+
+assertNbSubs(Sketch_1, DEFAULT_POINTS, DEFAULT_LINES, DEFAULT_BSPLINES, DEAFULT_INTERNALS)
+assert(model.dof(Sketch_1) == DEFAULT_DOF)
+
+# Test 1. Remove auxiliary points one by one.
+for pnt in controlPoles:
+    model.begin()
+    removeFeaturesAndReferences(FeatureSet([pnt.feature()]))
+    model.end()
+
+    assertNbSubs(Sketch_1, DEFAULT_POINTS - 1, DEFAULT_LINES, DEFAULT_BSPLINES, DEAFULT_INTERNALS - 1)
+    assert(model.dof(Sketch_1) == DEFAULT_DOF)
+    model.undo()
+    assertNbSubs(Sketch_1, DEFAULT_POINTS, DEFAULT_LINES, DEFAULT_BSPLINES, DEAFULT_INTERNALS)
+    assert(model.dof(Sketch_1) == DEFAULT_DOF)
+
+# Test 2. Remove auxiliary lines one by one.
+for ln in controlLines:
+    model.begin()
+    removeFeaturesAndReferences(FeatureSet([ln.feature()]))
+    model.end()
+
+    assertNbSubs(Sketch_1, DEFAULT_POINTS, DEFAULT_LINES - 1, DEFAULT_BSPLINES, DEAFULT_INTERNALS - 2)
+    assert(model.dof(Sketch_1) == DEFAULT_DOF)
+    model.undo()
+    assertNbSubs(Sketch_1, DEFAULT_POINTS, DEFAULT_LINES, DEFAULT_BSPLINES, DEAFULT_INTERNALS)
+    assert(model.dof(Sketch_1) == DEFAULT_DOF)
+
+# Test 3. Remove the B-spline curve.
+model.begin()
+removeFeaturesAndReferences(FeatureSet([SketchBSpline_1.feature()]))
+model.end()
+
+assertNbSubs(Sketch_1, 0, 0, 0, 0)
+assert(model.dof(Sketch_1) == 0)
+model.undo()
+assertNbSubs(Sketch_1, DEFAULT_POINTS, DEFAULT_LINES, DEFAULT_BSPLINES, DEAFULT_INTERNALS)
+assert(model.dof(Sketch_1) == DEFAULT_DOF)
+
+# Test 4. Remove some construction elements, make non-auxiliary a couple of the rest and check the dumping.
+model.begin()
+partSet = model.moduleDocument()
+Sketch_2 = model.addSketch(Part_1_doc, model.defaultPlane("XOY"))
+SketchBSpline_2_poles = [(-30, -10), (-15, -40), (0, -10), (15, -40), (30, -10)]
+SketchBSpline_2 = Sketch_2.addSpline(poles = SketchBSpline_2_poles, periodic = True)
+controlPoles2 = SketchBSpline_2.controlPoles(auxiliary = [0, 1, 2, 3, 4])
+controlLines2 = SketchBSpline_2.controlPolygon(auxiliary = [0, 1, 2, 3, 4])
+model.do()
+model.end()
+
+model.begin()
+controlPoles2[1].setAuxiliary(False)
+controlLines2[2].setAuxiliary(False)
+removeFeaturesAndReferences(FeatureSet([controlPoles2[2].feature(), controlLines2[0].feature()]))
+model.end()
+
+assertNbSubs(Sketch_2, DEFAULT_POINTS - 1, DEFAULT_LINES - 1, DEFAULT_BSPLINES, DEAFULT_INTERNALS - 3)
+
+assert(model.checkPythonDump())
index c8e4b9b7df9ac66f4aed804f1a4146f731a1da51..6d2a5af6cdfc2b135adbb6e6e0c5ca79c503dad1 100644 (file)
@@ -92,6 +92,7 @@ The plug-in includes the following features for creation of 2D objects:
    arcFeature.rst
    ellipseFeature.rst
    arcEllipseFeature.rst
+   bsplineFeature.rst
 
 .. _sketch_constraints:
 
diff --git a/src/SketchPlugin/doc/TUI_bsplineFeature.rst b/src/SketchPlugin/doc/TUI_bsplineFeature.rst
new file mode 100644 (file)
index 0000000..962c33f
--- /dev/null
@@ -0,0 +1,11 @@
+
+  .. _tui_create_bspline:
+
+Create Sketch B-spline
+======================
+
+.. literalinclude:: examples/bspline.py
+    :linenos:
+    :language: python
+
+:download:`Download this script <examples/bspline.py>`
diff --git a/src/SketchPlugin/doc/bsplineFeature.rst b/src/SketchPlugin/doc/bsplineFeature.rst
new file mode 100644 (file)
index 0000000..5d8cde2
--- /dev/null
@@ -0,0 +1,81 @@
+.. |bspline.icon|    image:: images/bspline.png
+.. |bspline_p.icon|  image:: images/bspline_p.png
+.. |add_pole.icon|   image:: images/bspline_add_pole.png
+
+B-spline and periodic B-spline
+==============================
+
+The feature creates a free form spline curve in the current Sketch.
+
+To add a new B-spline to the Sketch:
+
+#. select in the Main Menu *Sketch - > B-spline* item  or
+#. click |bspline.icon| **B-spline** button in the Sketch toolbar.
+
+To add a periodic B-spline to the Sketch:
+
+#. select in the Main Menu *Sketch - > Periodic B-spline* item  or
+#. click |bspline_p.icon| **Periodic B-spline** button in the Sketch toolbar.
+
+
+Creation of B-spline curve
+""""""""""""""""""""""""""
+
+.. image:: images/bspline_creation_panel.png
+   :align: center
+
+Click in the view to specify the control polygon of B-spline curve. The curve will be shown after the second point is initialized. To stop adding new poles, click **Esc** button or **Apply** the operation.
+
+
+**TUI Command**:
+
+.. py:function:: Sketch_1.addSpline(degree, poles, weights, knots, multiplicities, periodic)
+
+    :param integer: degree of B-spline.
+    :param array: list of poles [(x1, y1), (x2, y2), ...].
+    :param array: list of weights for corresponding poles.
+    :param array: parametric knots of B-spline curve.
+    :param array: multiplicity of each knot.
+    :param boolean: True mentions that the B-spline curve is periodic.
+    :return: Result object.
+
+Each parameter is optional.
+
+Result
+""""""
+
+Created B-spline curve appears in the view.
+
+.. image:: images/bspline_result.png
+          :align: center
+
+.. centered::
+   Non-periodic B-spline created
+
+
+.. image:: images/bspline_periodic_result.png
+          :align: center
+
+.. centered::
+   Periodic B-spline created
+
+**See Also** a sample TUI Script of :ref:`tui_create_bspline` operation.
+
+
+Modification of B-spline curve
+""""""""""""""""""""""""""""""
+
+.. image:: images/bspline_modification_panel.png
+   :align: center
+
+.. centered::
+   Modification panel for B-spline curve
+
+The following options are provided to modify the already created B-spline curve:
+
+#. Change weight of each pole.
+#. Add new pole.
+
+The new pole is added after the current by pressing on the corresponding |add_pole.icon| button. The default weight for the new pole is 1. 
+
+**Note:** adding the new pole after the last for non-periodic B-spline will not change the last point of the curve to avoid modification of full sketch.
diff --git a/src/SketchPlugin/doc/examples/bspline.py b/src/SketchPlugin/doc/examples/bspline.py
new file mode 100644 (file)
index 0000000..40f5bb3
--- /dev/null
@@ -0,0 +1,18 @@
+from salome.shaper import model
+
+model.begin()
+partSet = model.moduleDocument()
+Sketch_1 = model.addSketch(partSet, model.defaultPlane("XOY"))
+
+SketchBSpline_1_poles = [(-70, -5), (-50, 30), (-40, 3), (-20, 20), (-10, -5)]
+SketchBSpline_1 = Sketch_1.addSpline(poles = SketchBSpline_1_poles, weights = [1, 2, 2, 2, 1])
+[SketchPoint_1, SketchPoint_2, SketchPoint_3, SketchPoint_4, SketchPoint_5] = SketchBSpline_1.controlPoles(auxiliary = [0, 1, 2, 3, 4])
+[SketchLine_1, SketchLine_2, SketchLine_3, SketchLine_4] = SketchBSpline_1.controlPolygon(auxiliary = [0, 1, 2, 3])
+
+SketchBSplinePeriodic_1_poles = [(10, -5), (30, 30), (40, 3), (60, 20), (70, -5)]
+SketchBSplinePeriodic_1 = Sketch_1.addSpline(poles = SketchBSplinePeriodic_1_poles, weights = [3, 2, 1, 1, 1], periodic = True)
+[SketchPoint_6, SketchPoint_7, SketchPoint_8, SketchPoint_9, SketchPoint_10] = SketchBSplinePeriodic_1.controlPoles(auxiliary = [0, 1, 2, 3, 4])
+[SketchLine_5, SketchLine_6, SketchLine_7, SketchLine_8, SketchLine_9] = SketchBSplinePeriodic_1.controlPolygon(auxiliary = [0, 1, 2, 3, 4])
+
+model.do()
+model.end()
diff --git a/src/SketchPlugin/doc/images/bspline.png b/src/SketchPlugin/doc/images/bspline.png
new file mode 100644 (file)
index 0000000..149d19e
Binary files /dev/null and b/src/SketchPlugin/doc/images/bspline.png differ
diff --git a/src/SketchPlugin/doc/images/bspline_add_pole.png b/src/SketchPlugin/doc/images/bspline_add_pole.png
new file mode 100644 (file)
index 0000000..388f8ff
Binary files /dev/null and b/src/SketchPlugin/doc/images/bspline_add_pole.png differ
diff --git a/src/SketchPlugin/doc/images/bspline_creation_panel.png b/src/SketchPlugin/doc/images/bspline_creation_panel.png
new file mode 100644 (file)
index 0000000..88acb19
Binary files /dev/null and b/src/SketchPlugin/doc/images/bspline_creation_panel.png differ
diff --git a/src/SketchPlugin/doc/images/bspline_modification_panel.png b/src/SketchPlugin/doc/images/bspline_modification_panel.png
new file mode 100644 (file)
index 0000000..4bda345
Binary files /dev/null and b/src/SketchPlugin/doc/images/bspline_modification_panel.png differ
diff --git a/src/SketchPlugin/doc/images/bspline_p.png b/src/SketchPlugin/doc/images/bspline_p.png
new file mode 100644 (file)
index 0000000..a23ade1
Binary files /dev/null and b/src/SketchPlugin/doc/images/bspline_p.png differ
diff --git a/src/SketchPlugin/doc/images/bspline_periodic_result.png b/src/SketchPlugin/doc/images/bspline_periodic_result.png
new file mode 100644 (file)
index 0000000..b02a7cc
Binary files /dev/null and b/src/SketchPlugin/doc/images/bspline_periodic_result.png differ
diff --git a/src/SketchPlugin/doc/images/bspline_result.png b/src/SketchPlugin/doc/images/bspline_result.png
new file mode 100644 (file)
index 0000000..9b954c0
Binary files /dev/null and b/src/SketchPlugin/doc/images/bspline_result.png differ
diff --git a/src/SketchPlugin/icons/bspline.png b/src/SketchPlugin/icons/bspline.png
new file mode 100644 (file)
index 0000000..149d19e
Binary files /dev/null and b/src/SketchPlugin/icons/bspline.png differ
diff --git a/src/SketchPlugin/icons/bspline_p.png b/src/SketchPlugin/icons/bspline_p.png
new file mode 100644 (file)
index 0000000..a23ade1
Binary files /dev/null and b/src/SketchPlugin/icons/bspline_p.png differ
index 61c0319a701558e3ec315ce042b906c2c1c25422..d12e746ea1616956c50296e468de8480baaec6c0 100644 (file)
@@ -6,6 +6,7 @@
         nested="SketchPoint SketchIntersectionPoint SketchLine
                 SketchCircle SketchMacroCircle SketchArc SketchMacroArc
                 SketchEllipse SketchMacroEllipse SketchEllipticArc SketchMacroEllipticArc
+                SketchBSpline SketchMacroBSpline SketchMacroBSplinePeriodic SketchBSplinePeriodic
                 SketchRectangle
                 SketchProjection
                 SketchConstraintLength SketchConstraintRadius SketchConstraintDistance SketchConstraintDistanceHorizontal SketchConstraintDistanceVertical
       </feature>
     </group>
 
+    <group id="Parametric curves">
+      <!-- SketchBSpline is a hidden feature. It is created inside SketchMacroBSpline. -->
+      <feature id="SketchBSpline"
+               title="B-spline"
+               tooltip="Create B-spline curve"
+               icon="icons/Sketch/bspline.png"
+               helpfile="bsplineFeature.html"
+               internal="1">
+        <bspline-panel id="poles"
+                       weights="weights"
+                       title="Poles and weights"
+                       tooltip="B-spline poles and weights"
+                       enable_value="enable_by_preferences">
+          <validator id="SketchPlugin_BSplineValidator"/>
+        </bspline-panel>
+        <boolvalue id="Auxiliary"
+                   label="Auxiliary"
+                   default="false"
+                   tooltip="Construction element"
+                   obligatory="0"
+                   change_visual_attributes="true"/>
+      </feature>
+
+      <!-- SketchBSplinePeriodic is a hidden feature. It is created inside SketchMacroBSplinePeriodic. -->
+      <feature id="SketchBSplinePeriodic"
+               title="Periodic B-spline"
+               tooltip="Create periodic B-spline curve"
+               icon="icons/Sketch/bspline_p.png"
+               helpfile="bsplineFeature.html"
+               internal="1">
+        <bspline-panel id="poles"
+                       weights="weights"
+                       title="Poles and weights"
+                       tooltip="B-spline poles and weights"
+                       enable_value="enable_by_preferences">
+          <validator id="SketchPlugin_BSplineValidator"/>
+        </bspline-panel>
+        <boolvalue id="Auxiliary"
+                   label="Auxiliary"
+                   default="false"
+                   tooltip="Construction element"
+                   obligatory="0"
+                   change_visual_attributes="true"/>
+      </feature>
+
+      <!-- SketchMacroBSpline -->
+      <feature id="SketchMacroBSpline"
+               title="B-spline"
+               tooltip="Create B-spline curve"
+               icon="icons/Sketch/bspline.png"
+               helpfile="bsplineFeature.html">
+        <sketch-bspline_selector id="poles"
+                                 weights="weights"
+                                 reference_attribute="poles_ref"
+                                 title="Poles and weights"
+                                 tooltip="B-spline poles and weights"
+                                 enable_value="enable_by_preferences">
+          <validator id="SketchPlugin_BSplineValidator"/>
+        </sketch-bspline_selector>
+        <boolvalue id="need_control_poly"
+                   label="Create control polygon"
+                   default="true"
+                   tooltip="Specify if the control polygon should be created"/>
+        <boolvalue id="Auxiliary"
+                   label="Auxiliary"
+                   default="false"
+                   tooltip="Construction element"
+                   obligatory="0"
+                   change_visual_attributes="true"/>
+      </feature>
+
+      <!-- SketchMacroBSplinePeriodic -->
+      <feature id="SketchMacroBSplinePeriodic"
+               title="Periodic B-spline"
+               tooltip="Create periodic B-spline curve"
+               icon="icons/Sketch/bspline_p.png"
+               helpfile="bsplineFeature.html">
+        <sketch-bspline_selector id="poles"
+                                 weights="weights"
+                                 reference_attribute="poles_ref"
+                                 title="Poles and weights"
+                                 tooltip="B-spline poles and weights"
+                                 enable_value="enable_by_preferences">
+          <validator id="SketchPlugin_BSplineValidator"/>
+        </sketch-bspline_selector>
+        <boolvalue id="need_control_poly"
+                   label="Create control polygon"
+                   default="true"
+                   tooltip="Specify if the control polygon should be created"/>
+        <boolvalue id="Auxiliary"
+                   label="Auxiliary"
+                   default="false"
+                   tooltip="Construction element"
+                   obligatory="0"
+                   change_visual_attributes="true"/>
+      </feature>
+    </group>
+
     <group id="Segmentation">
       <!--  SketchSplit  -->
       <feature id="SketchSplit" title="Split"
index fab1f0356d3f363e00fff20140a125395f3172e9..61a812a8b2dbc88e7ce32178e012b01d394a638a 100644 (file)
@@ -27,10 +27,13 @@ SET(PLANEGCSSOLVER_HEADERS
     PlaneGCSSolver_EdgeWrapper.h
     PlaneGCSSolver_EntityWrapper.h
     PlaneGCSSolver_PointWrapper.h
+    PlaneGCSSolver_PointArrayWrapper.h
     PlaneGCSSolver_ScalarWrapper.h
+    PlaneGCSSolver_ScalarArrayWrapper.h
     PlaneGCSSolver_AngleWrapper.h
     PlaneGCSSolver_BooleanWrapper.h
     PlaneGCSSolver_Tools.h
+    PlaneGCSSolver_GeoExtensions.h
 )
 
 SET(PLANEGCSSOLVER_SOURCES
@@ -39,10 +42,13 @@ SET(PLANEGCSSOLVER_SOURCES
     PlaneGCSSolver_ConstraintWrapper.cpp
     PlaneGCSSolver_EdgeWrapper.cpp
     PlaneGCSSolver_PointWrapper.cpp
+    PlaneGCSSolver_PointArrayWrapper.cpp
     PlaneGCSSolver_ScalarWrapper.cpp
+    PlaneGCSSolver_ScalarArrayWrapper.cpp
     PlaneGCSSolver_AngleWrapper.cpp
     PlaneGCSSolver_BooleanWrapper.cpp
     PlaneGCSSolver_Tools.cpp
+    PlaneGCSSolver_GeoExtensions.cpp
 )
 
 SET(PLANEGCSSOLVER_BUILDER_HEADERS
index ca463c53d70f063745d9f062bf7c250cc6810d24..a446abc6693cae4d85b920ffa00d8632c13014ef 100644 (file)
 
 #include <PlaneGCSSolver_AngleWrapper.h>
 #include <PlaneGCSSolver_AttributeBuilder.h>
+#include <PlaneGCSSolver_PointArrayWrapper.h>
 #include <PlaneGCSSolver_PointWrapper.h>
 #include <PlaneGCSSolver_ScalarWrapper.h>
+#include <PlaneGCSSolver_ScalarArrayWrapper.h>
 #include <PlaneGCSSolver_BooleanWrapper.h>
+#include <PlaneGCSSolver_Tools.h>
 
+#include <GeomAPI_Pnt2d.h>
 #include <GeomDataAPI_Point2D.h>
+#include <GeomDataAPI_Point2DArray.h>
 #include <ModelAPI_AttributeDouble.h>
+#include <ModelAPI_AttributeDoubleArray.h>
+#include <ModelAPI_AttributeInteger.h>
+#include <SketchPlugin_BSpline.h>
+#include <SketchPlugin_BSplinePeriodic.h>
 #include <SketchPlugin_ConstraintAngle.h>
 #include <SketchPlugin_MultiRotation.h>
 
@@ -58,9 +67,17 @@ static EntityWrapperPtr createBoolean(const AttributePtr& theAttribute)
 static EntityWrapperPtr createScalar(const AttributePtr&     theAttribute,
                                      PlaneGCSSolver_Storage* theStorage)
 {
-  AttributeDoublePtr aScalar = std::dynamic_pointer_cast<ModelAPI_AttributeDouble>(theAttribute);
-  if (!aScalar)
-    return EntityWrapperPtr();
+  double aValue = 0.0;
+  AttributeDoublePtr aDouble = std::dynamic_pointer_cast<ModelAPI_AttributeDouble>(theAttribute);
+  if (aDouble)
+    aValue = aDouble->isInitialized() ? aDouble->value() : 0.0;
+  else {
+    AttributeIntegerPtr anInt = std::dynamic_pointer_cast<ModelAPI_AttributeInteger>(theAttribute);
+    if (anInt)
+      aValue = anInt->isInitialized() ? anInt->value() : 0.0;
+    else
+      return EntityWrapperPtr();
+  }
 
   ScalarWrapperPtr aWrapper;
   // following attributes should be converted from degrees to radians
@@ -72,14 +89,66 @@ static EntityWrapperPtr createScalar(const AttributePtr&     theAttribute,
      (theAttribute->id() == SketchPlugin_MultiRotation::ANGLE_ID() &&
       anOwner->getKind() == SketchPlugin_MultiRotation::ID()))
     aWrapper = ScalarWrapperPtr(new PlaneGCSSolver_AngleWrapper(createParameter(theStorage)));
+  else if ((anOwner->getKind() == SketchPlugin_BSpline::ID() ||
+            anOwner->getKind() == SketchPlugin_BSplinePeriodic::ID()) &&
+           theAttribute->id() == SketchPlugin_BSplineBase::DEGREE_ID())
+    // Degree of B-spline is not processed by the solver
+    aWrapper = ScalarWrapperPtr(new PlaneGCSSolver_ScalarWrapper(createParameter(nullptr)));
   else
     aWrapper = ScalarWrapperPtr(new PlaneGCSSolver_ScalarWrapper(createParameter(theStorage)));
 
-  if (aScalar->isInitialized())
-    aWrapper->setValue(aScalar->value());
+  aWrapper->setValue(aValue);
   return aWrapper;
 }
 
+template <typename TYPE>
+static bool nonSolverAttribute(const FeaturePtr theOwner, const std::string& theAttrId)
+{
+  return theOwner->getKind() == TYPE::ID() && (theAttrId == TYPE::WEIGHTS_ID()
+      || theAttrId == TYPE::KNOTS_ID() || theAttrId == TYPE::MULTS_ID());
+}
+
+static EntityWrapperPtr createScalarArray(const AttributePtr&     theAttribute,
+                                          PlaneGCSSolver_Storage* theStorage)
+{
+  PlaneGCSSolver_Tools::AttributeArray anArray(theAttribute);
+
+  if (!anArray.isInitialized())
+    return EntityWrapperPtr();
+
+  PlaneGCSSolver_Storage* aStorage = theStorage;
+  // Weights, knots and multiplicities of B-spline curve are not processed by the solver
+  FeaturePtr anOwner = ModelAPI_Feature::feature(theAttribute->owner());
+  if (nonSolverAttribute<SketchPlugin_BSpline>(anOwner, theAttribute->id()) ||
+      nonSolverAttribute<SketchPlugin_BSplinePeriodic>(anOwner, theAttribute->id()))
+    aStorage = 0;
+
+  int aSize = anArray.size();
+  GCS::VEC_pD aParameters;
+  aParameters.reserve(aSize);
+  for (int index = 0; index < aSize; ++index) {
+    double* aParam = createParameter(aStorage);
+    *aParam = anArray.value(index);
+    aParameters.push_back(aParam);
+  }
+
+  return EntityWrapperPtr(new PlaneGCSSolver_ScalarArrayWrapper(aParameters));
+}
+
+static PointWrapperPtr createPoint(const GeomPnt2dPtr& thePoint, PlaneGCSSolver_Storage* theStorage)
+{
+  GCSPointPtr aNewPoint(new GCS::Point);
+
+  aNewPoint->x = createParameter(theStorage);
+  aNewPoint->y = createParameter(theStorage);
+  if (thePoint) {
+    *(aNewPoint->x) = thePoint->x();
+    *(aNewPoint->y) = thePoint->y();
+  }
+
+  return PointWrapperPtr(new PlaneGCSSolver_PointWrapper(aNewPoint));
+}
+
 static EntityWrapperPtr createPoint(const AttributePtr&     theAttribute,
                                     PlaneGCSSolver_Storage* theStorage)
 {
@@ -88,16 +157,29 @@ static EntityWrapperPtr createPoint(const AttributePtr&     theAttribute,
   if (!aPoint2D)
     return EntityWrapperPtr();
 
-  GCSPointPtr aNewPoint(new GCS::Point);
+  GeomPnt2dPtr aPnt;
+  if (aPoint2D->isInitialized())
+    aPnt = aPoint2D->pnt();
 
-  aNewPoint->x = createParameter(theStorage);
-  aNewPoint->y = createParameter(theStorage);
-  if (aPoint2D->isInitialized()) {
-    *(aNewPoint->x) = aPoint2D->x();
-    *(aNewPoint->y) = aPoint2D->y();
-  }
+  return createPoint(aPnt, theStorage);
+}
+
+static EntityWrapperPtr createPointArray(const AttributePtr& theAttribute,
+                                         PlaneGCSSolver_Storage* theStorage)
+{
+  std::shared_ptr<GeomDataAPI_Point2DArray> aPointArray =
+      std::dynamic_pointer_cast<GeomDataAPI_Point2DArray>(theAttribute);
+  if (!aPointArray)
+    return EntityWrapperPtr();
+
+  int aSize = aPointArray->size();
 
-  return EntityWrapperPtr(new PlaneGCSSolver_PointWrapper(aNewPoint));
+  std::vector<PointWrapperPtr> aPointWrappers;
+  aPointWrappers.reserve(aSize);
+  for (int index = 0; index < aSize; ++index)
+    aPointWrappers.push_back(createPoint(aPointArray->pnt(index), theStorage));
+
+  return EntityWrapperPtr(new PlaneGCSSolver_PointArrayWrapper(aPointWrappers));
 }
 
 EntityWrapperPtr PlaneGCSSolver_AttributeBuilder::createAttribute(
@@ -112,7 +194,76 @@ EntityWrapperPtr PlaneGCSSolver_AttributeBuilder::createAttribute(
     aResult = createScalar(theAttribute, myStorage);
   if (!aResult)
     aResult = createBoolean(theAttribute);
+  if (!aResult)
+    aResult = createPointArray(theAttribute, myStorage);
+  if (!aResult)
+    aResult = createScalarArray(theAttribute, myStorage);
   if (aResult && !myStorage)
     aResult->setExternal(true);
   return aResult;
 }
+
+bool PlaneGCSSolver_AttributeBuilder::updateAttribute(
+    AttributePtr theAttribute,
+    EntityWrapperPtr theEntity)
+{
+  bool isUpdated = false;
+  GCS::SET_pD aParamsToRemove;
+  // rebuild array if its size is changed
+  if (theEntity->type() == ENTITY_POINT_ARRAY) {
+    std::shared_ptr<PlaneGCSSolver_PointArrayWrapper> aWrapper =
+        std::dynamic_pointer_cast<PlaneGCSSolver_PointArrayWrapper>(theEntity);
+    std::shared_ptr<GeomDataAPI_Point2DArray> anAttribute =
+        std::dynamic_pointer_cast<GeomDataAPI_Point2DArray>(theAttribute);
+
+    if (aWrapper->size() != anAttribute->size()) {
+      std::vector<PointWrapperPtr> aPointsArray = aWrapper->array();
+      std::vector<PointWrapperPtr>::iterator aPos = aPointsArray.begin();
+      while (anAttribute->size() > (int)aPointsArray.size()) {
+        // add points to the middle of array
+        GeomPnt2dPtr aValue;
+        for (; aPos != aPointsArray.end(); ++aPos) {
+          aValue = anAttribute->pnt(aPos - aPointsArray.begin());
+          if (aValue->distance(PlaneGCSSolver_Tools::point(*aPos)) > tolerance)
+            break;
+        }
+        int aShift = aPos - aPointsArray.begin();
+        aPointsArray.insert(aPos, createPoint(aValue, myStorage));
+        aPos = aPointsArray.begin() + aShift;
+      }
+
+      while (anAttribute->size() < (int)aPointsArray.size()) {
+        // remove middle points
+        std::vector<PointWrapperPtr>::iterator anIt = --aPointsArray.end();
+        GCS::SET_pD aParams = PlaneGCSSolver_Tools::parameters(*anIt);
+        aParamsToRemove.insert(aParams.begin(), aParams.end());
+        aPointsArray.erase(anIt);
+      }
+
+      aWrapper->setArray(aPointsArray);
+    }
+  }
+  else if (theEntity->type() == ENTITY_SCALAR_ARRAY) {
+    std::shared_ptr<PlaneGCSSolver_ScalarArrayWrapper> aWrapper =
+        std::dynamic_pointer_cast<PlaneGCSSolver_ScalarArrayWrapper>(theEntity);
+    if (aWrapper->size() != PlaneGCSSolver_Tools::AttributeArray(theAttribute).size()) {
+      aParamsToRemove = PlaneGCSSolver_Tools::parameters(aWrapper);
+      std::shared_ptr<PlaneGCSSolver_ScalarArrayWrapper> aNewArray =
+          std::dynamic_pointer_cast<PlaneGCSSolver_ScalarArrayWrapper>(
+          createScalarArray(theAttribute, myStorage));
+      aWrapper->setArray(aNewArray->array());
+      isUpdated = true;
+    }
+  }
+
+  if (!aParamsToRemove.empty()) {
+    if (myStorage)
+      myStorage->removeParameters(aParamsToRemove);
+    else {
+      std::for_each(aParamsToRemove.begin(), aParamsToRemove.end(),
+                    [](double* theParam) { delete theParam; });
+    }
+  }
+
+  return isUpdated || theEntity->update(theAttribute);
+}
index 7d21983b30337becddb0b02252bacd8b35bce714..adc0e5720212174335716a65abd90254b7ff250e 100644 (file)
@@ -38,6 +38,10 @@ public:
   /// \return Created wrapper of the attribute applicable for specific solver
   virtual EntityWrapperPtr createAttribute(AttributePtr theAttribute);
 
+  /// \brief Update entity by the attribute values.
+  /// \return \c true if any value is updated.
+  virtual bool updateAttribute(AttributePtr theAttribute, EntityWrapperPtr theEntity);
+
   /// \brief Blank. To be defined in derived class.
   virtual EntityWrapperPtr createFeature(FeaturePtr)
   { return EntityWrapperPtr(); }
index 225c4344f73e98ce386bafce57f52bed100774a6..be2e0bf81113065e1e394a0bb7f910db40fe7402 100644 (file)
 
 #include <PlaneGCSSolver_BooleanWrapper.h>
 
+#include <ModelAPI_AttributeBoolean.h>
+
 PlaneGCSSolver_BooleanWrapper::PlaneGCSSolver_BooleanWrapper(bool theParam)
   : myValue(theParam)
 {
 }
+
+bool PlaneGCSSolver_BooleanWrapper::update(AttributePtr theAttribute)
+{
+  bool isUpdated = false;
+  AttributeBooleanPtr aBoolean =
+      std::dynamic_pointer_cast<ModelAPI_AttributeBoolean>(theAttribute);
+  if (aBoolean) {
+    isUpdated = value() != aBoolean->value();
+    setValue(aBoolean->value());
+  }
+  return isUpdated;
+}
index 3bd1d8d2ed7ed5455b909cbda6b7bee6e9692db2..667eb083dea0ae0385daa6272d71fbe12cf68a0a 100644 (file)
@@ -42,6 +42,11 @@ public:
   virtual SketchSolver_EntityType type() const
   { return ENTITY_BOOLEAN; }
 
+protected:
+  /// \brief Update entity by the values of theAttribute
+  /// \return \c true if any value of attribute is not equal to the stored in the entity
+  virtual bool update(std::shared_ptr<ModelAPI_Attribute> theAttribute);
+
 protected:
   bool myValue;
 };
index b99cc7c3b1bd00160a3d9cb2ea782555d15ca295..ab6ea78943dbf91d7076c0f795a2577e7744eef1 100644 (file)
@@ -23,6 +23,8 @@
 #include <PlaneGCSSolver_Defs.h>
 #include <PlaneGCSSolver_ScalarWrapper.h>
 
+#include <list>
+
 /**
  *  Wrapper providing operations with PlaneGCS constraints.
  */
index 0f46e7544d73920b8f3349618b3f1a6dc7c4d982..eea27bf06faccc7087337d4481a3a7b081ee49e3 100644 (file)
@@ -46,13 +46,16 @@ enum SketchSolver_EntityType {
   ENTITY_UNKNOWN = 0,
   ENTITY_BOOLEAN,
   ENTITY_SCALAR,
+  ENTITY_SCALAR_ARRAY,
   ENTITY_ANGLE,
   ENTITY_POINT,
+  ENTITY_POINT_ARRAY,
   ENTITY_LINE,
   ENTITY_CIRCLE,
   ENTITY_ARC,
   ENTITY_ELLIPSE,
-  ENTITY_ELLIPTIC_ARC
+  ENTITY_ELLIPTIC_ARC,
+  ENTITY_BSPLINE
 };
 
 /// Types of constraints
index f9683203880051e28acbc8a99d0e71ef8d4cf014..669aece33073ca8e85916c47b7fe8c0f875fcd61 100644 (file)
 #include <PlaneGCSSolver_EdgeWrapper.h>
 #include <cmath>
 
-static bool isLine(const GCSCurvePtr& theEntity)
+template <typename TYPE>
+static bool isCurve(const GCSCurvePtr& theEntity)
 {
-  return std::dynamic_pointer_cast<GCS::Line>(theEntity).get();
-}
-
-static bool isCircle(const GCSCurvePtr& theEntity)
-{
-  return std::dynamic_pointer_cast<GCS::Circle>(theEntity).get();
-}
-
-static bool isArc(const GCSCurvePtr& theEntity)
-{
-  return std::dynamic_pointer_cast<GCS::Arc>(theEntity).get();
-}
-
-static bool isEllipse(const GCSCurvePtr& theEntity)
-{
-  return std::dynamic_pointer_cast<GCS::Ellipse>(theEntity).get();
-}
-
-static bool isEllipticArc(const GCSCurvePtr& theEntity)
-{
-  return std::dynamic_pointer_cast<GCS::ArcOfEllipse>(theEntity).get();
+  return std::dynamic_pointer_cast<TYPE>(theEntity).get();
 }
 
 
 PlaneGCSSolver_EdgeWrapper::PlaneGCSSolver_EdgeWrapper(const GCSCurvePtr theEntity)
   : myEntity(theEntity)
 {
-  if (isLine(myEntity))
+  if (isCurve<GCS::Line>(myEntity))
     myType = ENTITY_LINE;
-  else if (isArc(myEntity))
+  else if (isCurve<GCS::Arc>(myEntity))
     myType = ENTITY_ARC;
-  else if (isCircle(myEntity))
+  else if (isCurve<GCS::Circle>(myEntity))
     myType = ENTITY_CIRCLE;
-  else if (isEllipticArc(myEntity))
+  else if (isCurve<GCS::ArcOfEllipse>(myEntity))
     myType = ENTITY_ELLIPTIC_ARC;
-  else if (isEllipse(myEntity))
+  else if (isCurve<GCS::Ellipse>(myEntity))
     myType = ENTITY_ELLIPSE;
+  else if (isCurve<GCS::BSpline>(myEntity))
+    myType = ENTITY_BSPLINE;
 }
 
 static double squareDistance(const GCS::Point& theP1, const GCS::Point& theP2)
index 5eceaf63c5c20f1d63906b4584d4ee401b24eabc..bc86246e588e66eb2e1137236427ac7648c670ee 100644 (file)
 
 #include <PlaneGCSSolver_Defs.h>
 
-#include <ModelAPI_Attribute.h>
-#include <ModelAPI_Feature.h>
-
-#include <list>
+#include <map>
 #include <memory>
 
+class ModelAPI_Attribute;
+
 class PlaneGCSSolver_EntityWrapper;
 typedef std::shared_ptr<PlaneGCSSolver_EntityWrapper> EntityWrapperPtr;
 
@@ -55,6 +54,14 @@ public:
   const std::map<std::string, EntityWrapperPtr>& additionalAttributes() const
   { return myAdditionalAttributes; }
 
+protected:
+  /// \brief Update entity by the values of theAttribute
+  /// \return \c true if any value of attribute is not equal to the stored in the entity
+  virtual bool update(std::shared_ptr<ModelAPI_Attribute> theAttribute)
+  { return false; }
+
+  friend class PlaneGCSSolver_AttributeBuilder;
+
 private:
   bool myExternal;
   std::map<std::string, EntityWrapperPtr> myAdditionalAttributes;
index 400319aed4bd9804a46ec5c2c118da1d8473c9d7..e797abad2342bb7f6207dc48d1e65bfcb2a8ef3c 100644 (file)
 //
 
 #include <PlaneGCSSolver_FeatureBuilder.h>
+#include <PlaneGCSSolver_BooleanWrapper.h>
 #include <PlaneGCSSolver_EdgeWrapper.h>
+#include <PlaneGCSSolver_GeoExtensions.h>
 #include <PlaneGCSSolver_PointWrapper.h>
+#include <PlaneGCSSolver_PointArrayWrapper.h>
 #include <PlaneGCSSolver_ScalarWrapper.h>
-#include <PlaneGCSSolver_BooleanWrapper.h>
+#include <PlaneGCSSolver_ScalarArrayWrapper.h>
 #include <PlaneGCSSolver_Tools.h>
 
 #include <SketchPlugin_Arc.h>
+#include <SketchPlugin_BSpline.h>
+#include <SketchPlugin_BSplinePeriodic.h>
 #include <SketchPlugin_Circle.h>
 #include <SketchPlugin_Ellipse.h>
 #include <SketchPlugin_EllipticArc.h>
@@ -36,8 +41,6 @@
 #include <GeomAPI_Pnt2d.h>
 #include <GeomAPI_XY.h>
 
-static bool isAttributeApplicable(const std::string& theAttrName,
-                                  const std::string& theOwnerName);
 
 static EntityWrapperPtr createLine(const AttributeEntityMap& theAttributes);
 static EntityWrapperPtr createCircle(const AttributeEntityMap& theAttributes);
@@ -46,6 +49,8 @@ static EntityWrapperPtr createArc(const AttributeEntityMap&    theAttributes,
 static EntityWrapperPtr createEllipse(const AttributeEntityMap& theAttributes);
 static EntityWrapperPtr createEllipticArc(const AttributeEntityMap& theAttributes,
                                           PlaneGCSSolver_Storage*   theStorage);
+template <typename TYPE>
+static EntityWrapperPtr createBSpline(const AttributeEntityMap& theAttributes);
 
 
 PlaneGCSSolver_FeatureBuilder::PlaneGCSSolver_FeatureBuilder(
@@ -64,7 +69,7 @@ EntityWrapperPtr PlaneGCSSolver_FeatureBuilder::createAttribute(
 {
   FeaturePtr anOwner = ModelAPI_Feature::feature(theAttribute->owner());
   EntityWrapperPtr anAttr;
-  if (isAttributeApplicable(theAttribute->id(), anOwner->getKind()))
+  if (PlaneGCSSolver_Tools::isAttributeApplicable(theAttribute->id(), anOwner->getKind()))
     anAttr = PlaneGCSSolver_AttributeBuilder::createAttribute(theAttribute);
   if (anAttr)
     myAttributes[theAttribute] = anAttr;
@@ -102,6 +107,11 @@ EntityWrapperPtr PlaneGCSSolver_FeatureBuilder::createFeature(FeaturePtr theFeat
   // Arc of ellipse
   else if (aFeatureKind == SketchPlugin_EllipticArc::ID())
     aResult = createEllipticArc(myAttributes, myStorage);
+  // B-spline curve
+  else if (aFeatureKind == SketchPlugin_BSpline::ID())
+    aResult = createBSpline<SketchPlugin_BSpline>(myAttributes);
+  else if (aFeatureKind == SketchPlugin_BSplinePeriodic::ID())
+    aResult = createBSpline<SketchPlugin_BSplinePeriodic>(myAttributes);
   // Point (it has low probability to be an attribute of constraint, so it is checked at the end)
   else if (aFeatureKind == SketchPlugin_Point::ID() ||
            aFeatureKind == SketchPlugin_IntersectionPoint::ID()) {
@@ -288,48 +298,57 @@ EntityWrapperPtr createEllipticArc(const AttributeEntityMap& theAttributes,
   return anEllipseWrapper;
 }
 
-bool isAttributeApplicable(const std::string& theAttrName, const std::string& theOwnerName)
+template <typename TYPE>
+EntityWrapperPtr createBSpline(const AttributeEntityMap& theAttributes)
 {
-  if (theOwnerName == SketchPlugin_Arc::ID()) {
-    return theAttrName == SketchPlugin_Arc::CENTER_ID() ||
-           theAttrName == SketchPlugin_Arc::START_ID() ||
-           theAttrName == SketchPlugin_Arc::END_ID() ||
-           theAttrName == SketchPlugin_Arc::REVERSED_ID();
-  }
-  else if (theOwnerName == SketchPlugin_Circle::ID()) {
-    return theAttrName == SketchPlugin_Circle::CENTER_ID() ||
-           theAttrName == SketchPlugin_Circle::RADIUS_ID();
-  }
-  else if (theOwnerName == SketchPlugin_Line::ID()) {
-    return theAttrName == SketchPlugin_Line::START_ID() ||
-           theAttrName == SketchPlugin_Line::END_ID();
-  }
-  else if (theOwnerName == SketchPlugin_Ellipse::ID()) {
-    return theAttrName == SketchPlugin_Ellipse::CENTER_ID() ||
-           theAttrName == SketchPlugin_Ellipse::FIRST_FOCUS_ID() ||
-           theAttrName == SketchPlugin_Ellipse::SECOND_FOCUS_ID() ||
-           theAttrName == SketchPlugin_Ellipse::MAJOR_AXIS_START_ID() ||
-           theAttrName == SketchPlugin_Ellipse::MAJOR_AXIS_END_ID() ||
-           theAttrName == SketchPlugin_Ellipse::MINOR_AXIS_START_ID() ||
-           theAttrName == SketchPlugin_Ellipse::MINOR_AXIS_END_ID() ||
-           theAttrName == SketchPlugin_Ellipse::MAJOR_RADIUS_ID() ||
-           theAttrName == SketchPlugin_Ellipse::MINOR_RADIUS_ID();
-  }
-  else if (theOwnerName == SketchPlugin_EllipticArc::ID()) {
-    return theAttrName == SketchPlugin_EllipticArc::CENTER_ID() ||
-           theAttrName == SketchPlugin_EllipticArc::FIRST_FOCUS_ID() ||
-           theAttrName == SketchPlugin_EllipticArc::SECOND_FOCUS_ID() ||
-           theAttrName == SketchPlugin_EllipticArc::MAJOR_AXIS_START_ID() ||
-           theAttrName == SketchPlugin_EllipticArc::MAJOR_AXIS_END_ID() ||
-           theAttrName == SketchPlugin_EllipticArc::MINOR_AXIS_START_ID() ||
-           theAttrName == SketchPlugin_EllipticArc::MINOR_AXIS_END_ID() ||
-           theAttrName == SketchPlugin_EllipticArc::MAJOR_RADIUS_ID() ||
-           theAttrName == SketchPlugin_EllipticArc::MINOR_RADIUS_ID() ||
-           theAttrName == SketchPlugin_EllipticArc::START_POINT_ID() ||
-           theAttrName == SketchPlugin_EllipticArc::END_POINT_ID() ||
-           theAttrName == SketchPlugin_EllipticArc::REVERSED_ID();
+  std::shared_ptr<GCS::BSplineImpl> aNewSpline(new GCS::BSplineImpl);
+
+  aNewSpline->degree = 3;
+  aNewSpline->periodic = std::is_same<TYPE, SketchPlugin_BSplinePeriodic>();
+
+  std::map<std::string, EntityWrapperPtr> anAdditionalAttributes;
+
+  AttributeEntityMap::const_iterator anIt = theAttributes.begin();
+  for (; anIt != theAttributes.end(); ++anIt) {
+    const std::string& anAttrID = anIt->first->id();
+    if (anAttrID == TYPE::POLES_ID()) {
+      PointArrayWrapperPtr anArray =
+          std::dynamic_pointer_cast<PlaneGCSSolver_PointArrayWrapper>(anIt->second);
+
+      int aSize = anArray->size();
+      aNewSpline->poles.reserve(aSize);
+      for (int anIndex = 0; anIndex < aSize; ++anIndex)
+        aNewSpline->poles.push_back(*anArray->value(anIndex)->point());
+
+      aNewSpline->start = aNewSpline->poles.front();
+      aNewSpline->end = aNewSpline->poles.back();
+    }
+    else if (anAttrID == TYPE::DEGREE_ID()) {
+      ScalarWrapperPtr aScalar =
+          std::dynamic_pointer_cast<PlaneGCSSolver_ScalarWrapper>(anIt->second);
+      aNewSpline->degree = (int)aScalar->value();
+    }
+    else if (anAttrID == SketchPlugin_BSpline::START_ID() ||
+             anAttrID == SketchPlugin_BSpline::END_ID()) {
+      anAdditionalAttributes[anAttrID] = anIt->second;
+    }
+    else {
+      ScalarArrayWrapperPtr anArray =
+          std::dynamic_pointer_cast<PlaneGCSSolver_ScalarArrayWrapper>(anIt->second);
+      if (anAttrID == TYPE::WEIGHTS_ID())
+        aNewSpline->weights = anArray->array();
+      else if (anAttrID == TYPE::KNOTS_ID())
+        aNewSpline->knots = anArray->array();
+      else if (anAttrID == TYPE::MULTS_ID()) {
+        const GCS::VEC_pD& aValues = anArray->array();
+        aNewSpline->mult.reserve(aValues.size());
+        for (GCS::VEC_pD::const_iterator anIt = aValues.begin(); anIt != aValues.end(); ++anIt)
+          aNewSpline->mult.push_back((int)(**anIt));
+      }
+    }
   }
 
-  // suppose that all remaining features are points
-  return theAttrName == SketchPlugin_Point::COORD_ID();
+  EdgeWrapperPtr aWrapper(new PlaneGCSSolver_EdgeWrapper(aNewSpline));
+  aWrapper->setAdditionalAttributes(anAdditionalAttributes);
+  return aWrapper;
 }
diff --git a/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_GeoExtensions.cpp b/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_GeoExtensions.cpp
new file mode 100644 (file)
index 0000000..480dbd5
--- /dev/null
@@ -0,0 +1,123 @@
+// Copyright (C) 2019-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 <PlaneGCSSolver_GeoExtensions.h>
+#include <PlaneGCSSolver_Tools.h>
+
+#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);
+
+  return DeriVector2(aValue->x(), aValue->y(), aDeriv->x() * du, aDeriv->y() * 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)) {
+    // Sometimes OCCT's Extrema algorithm cannot find the parameter on B-spline curve
+    // (usually, if the point is near the curve extremity).
+    // Workaround: compute distance to each boundary point
+    double aDistPS = PlaneGCSSolver_Tools::distance(p, poles.front());
+    double aDistPE = PlaneGCSSolver_Tools::distance(p, poles.back());
+    static const double THE_TOLERANCE = 1.e-6;
+    if (aDistPS < aDistPE && aDistPS < THE_TOLERANCE)
+      u = *knots.front();
+    else if (aDistPE < aDistPS && aDistPE < THE_TOLERANCE)
+      u = *knots.back();
+    else
+      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();
+}
+
+BSplineImpl* BSplineImpl::Copy()
+{
+  return new BSplineImpl(*this);
+}
+
+
+bool BSplineImpl::isCacheValid() const
+{
+  // 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;
+  }
+
+  // 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;
+
+  return isValid;
+}
+
+void BSplineImpl::rebuildCache()
+{
+  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));
+}
+
+} // namespace GCS
diff --git a/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_GeoExtensions.h b/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_GeoExtensions.h
new file mode 100644 (file)
index 0000000..c83e1a9
--- /dev/null
@@ -0,0 +1,56 @@
+// Copyright (C) 2019-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 PlaneGCSSolver_GeomExtensions_H_
+#define PlaneGCSSolver_GeomExtensions_H_
+
+#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
+  {
+  public:
+    virtual DeriVector2 Value(double u, double du, double* derivparam = 0);
+    virtual DeriVector2 CalculateNormal(Point &p, double* derivparam = 0);
+
+    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();
+
+  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
+  };
+}
+
+#endif
diff --git a/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_PointArrayWrapper.cpp b/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_PointArrayWrapper.cpp
new file mode 100644 (file)
index 0000000..a409fb4
--- /dev/null
@@ -0,0 +1,49 @@
+// Copyright (C) 2019-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 <PlaneGCSSolver_PointArrayWrapper.h>
+#include <PlaneGCSSolver_Tools.h>
+
+#include <GeomDataAPI_Point2DArray.h>
+
+#include <GeomAPI_Pnt2d.h>
+
+PlaneGCSSolver_PointArrayWrapper::PlaneGCSSolver_PointArrayWrapper(
+    const std::vector<PointWrapperPtr>& thePoints)
+  : myPoints(thePoints)
+{
+}
+
+bool PlaneGCSSolver_PointArrayWrapper::update(AttributePtr theAttribute)
+{
+  bool isUpdated = false;
+  std::shared_ptr<GeomDataAPI_Point2DArray> aPointArray =
+      std::dynamic_pointer_cast<GeomDataAPI_Point2DArray>(theAttribute);
+  if (aPointArray && aPointArray->size() == (int)myPoints.size()) {
+    std::vector<PointWrapperPtr>::iterator aPIt = myPoints.begin();
+    for (int anIndex = 0; aPIt != myPoints.end(); ++aPIt, ++anIndex) {
+      GeomPnt2dPtr aPnt = aPointArray->pnt(anIndex);
+
+      const GCSPointPtr& aGCSPoint = (*aPIt)->point();
+      isUpdated = PlaneGCSSolver_Tools::updateValue(aPnt->x(), *(aGCSPoint->x)) || isUpdated;
+      isUpdated = PlaneGCSSolver_Tools::updateValue(aPnt->y(), *(aGCSPoint->y)) || isUpdated;
+    }
+  }
+  return isUpdated;
+}
diff --git a/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_PointArrayWrapper.h b/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_PointArrayWrapper.h
new file mode 100644 (file)
index 0000000..0524be2
--- /dev/null
@@ -0,0 +1,61 @@
+// Copyright (C) 2019-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 PlaneGCSSolver_PointArrayWrapper_H_
+#define PlaneGCSSolver_PointArrayWrapper_H_
+
+#include <PlaneGCSSolver_Defs.h>
+#include <PlaneGCSSolver_PointWrapper.h>
+
+/**
+ *  Wrapper providing operations with arrays of PlaneGCS points.
+ */
+class PlaneGCSSolver_PointArrayWrapper : public PlaneGCSSolver_EntityWrapper
+{
+public:
+  PlaneGCSSolver_PointArrayWrapper(const std::vector<PointWrapperPtr>& thePoints);
+
+  /// \brief Return wrapper of PlaneGCS point
+  const PointWrapperPtr& value(const int theIndex) const
+  { return myPoints[theIndex]; }
+
+  /// \breif Size of array
+  int size() const { return (int)myPoints.size(); }
+
+  /// \brief Return array of points
+  const std::vector<PointWrapperPtr>& array() const { return myPoints; }
+  /// \breif Set points
+  void setArray(const std::vector<PointWrapperPtr>& thePoints) { myPoints = thePoints; }
+
+  /// \brief Return type of current entity
+  virtual SketchSolver_EntityType type() const
+  { return ENTITY_POINT_ARRAY; }
+
+protected:
+  /// \brief Update entity by the values of theAttribute
+  /// \return \c true if any value of attribute is not equal to the stored in the entity
+  virtual bool update(std::shared_ptr<ModelAPI_Attribute> theAttribute);
+
+private:
+  std::vector<PointWrapperPtr> myPoints;
+};
+
+typedef std::shared_ptr<PlaneGCSSolver_PointArrayWrapper> PointArrayWrapperPtr;
+
+#endif
index bfb662590ad96dd2bdfe647a1404b7518b5ff179..bda73b1977ef5ac62285794385701f4461ac83b6 100644 (file)
 //
 
 #include <PlaneGCSSolver_PointWrapper.h>
+#include <PlaneGCSSolver_Tools.h>
+
+#include <GeomDataAPI_Point2D.h>
 
 PlaneGCSSolver_PointWrapper::PlaneGCSSolver_PointWrapper(const GCSPointPtr thePoint)
   : myPoint(thePoint)
 {
 }
+
+bool PlaneGCSSolver_PointWrapper::update(AttributePtr theAttribute)
+{
+  bool isUpdated = false;
+  std::shared_ptr<GeomDataAPI_Point2D> aPoint2D =
+      std::dynamic_pointer_cast<GeomDataAPI_Point2D>(theAttribute);
+  if (aPoint2D) {
+    isUpdated = PlaneGCSSolver_Tools::updateValue(aPoint2D->x(), *(myPoint->x)) || isUpdated;
+    isUpdated = PlaneGCSSolver_Tools::updateValue(aPoint2D->y(), *(myPoint->y)) || isUpdated;
+  }
+  return isUpdated;
+}
index 3cb05e124f04505181e834b2cbbf6fc29bd1716d..a50497523bc04a1f38aeff314fc0bfde437e4b08 100644 (file)
@@ -42,6 +42,11 @@ public:
   virtual SketchSolver_EntityType type() const
   { return ENTITY_POINT; }
 
+protected:
+  /// \brief Update entity by the values of theAttribute
+  /// \return \c true if any value of attribute is not equal to the stored in the entity
+  virtual bool update(std::shared_ptr<ModelAPI_Attribute> theAttribute);
+
 private:
   GCSPointPtr myPoint;
 };
diff --git a/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_ScalarArrayWrapper.cpp b/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_ScalarArrayWrapper.cpp
new file mode 100644 (file)
index 0000000..9d56154
--- /dev/null
@@ -0,0 +1,39 @@
+// Copyright (C) 2019-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 <PlaneGCSSolver_ScalarArrayWrapper.h>
+#include <PlaneGCSSolver_Tools.h>
+
+PlaneGCSSolver_ScalarArrayWrapper::PlaneGCSSolver_ScalarArrayWrapper(const GCS::VEC_pD& theParam)
+  : myValue(theParam)
+{
+}
+
+bool PlaneGCSSolver_ScalarArrayWrapper::update(AttributePtr theAttribute)
+{
+  bool isUpdated = false;
+  PlaneGCSSolver_Tools::AttributeArray anArray(theAttribute);
+  if (anArray.isInitialized() && anArray.size() == (int)myValue.size()) {
+    for (int anIndex = 0; anIndex < anArray.size(); ++anIndex) {
+      isUpdated = PlaneGCSSolver_Tools::updateValue(anArray.value(anIndex), *(myValue[anIndex]))
+          || isUpdated;
+    }
+  }
+  return isUpdated;
+}
diff --git a/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_ScalarArrayWrapper.h b/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_ScalarArrayWrapper.h
new file mode 100644 (file)
index 0000000..6b0992e
--- /dev/null
@@ -0,0 +1,57 @@
+// Copyright (C) 2019-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 PlaneGCSSolver_ScalarArrayWrapper_H_
+#define PlaneGCSSolver_ScalarArrayWrapper_H_
+
+#include <PlaneGCSSolver_Defs.h>
+#include <PlaneGCSSolver_EntityWrapper.h>
+
+/**
+ *  Wrapper providing operations with array of PlaneGCS scalars.
+ */
+class PlaneGCSSolver_ScalarArrayWrapper : public PlaneGCSSolver_EntityWrapper
+{
+public:
+  PlaneGCSSolver_ScalarArrayWrapper(const GCS::VEC_pD& theParam);
+
+  /// \breif Size of array
+  int size() const { return (int)myValue.size(); }
+
+  /// \brief Return array of PlaneGCS parameters
+  const GCS::VEC_pD& array() const { return myValue; }
+  /// \breif Set array of parameters
+  void setArray(const GCS::VEC_pD& theParams) { myValue = theParams; }
+
+  /// \brief Return type of current entity
+  virtual SketchSolver_EntityType type() const
+  { return ENTITY_SCALAR_ARRAY; }
+
+protected:
+  /// \brief Update entity by the values of theAttribute
+  /// \return \c true if any value of attribute is not equal to the stored in the entity
+  virtual bool update(std::shared_ptr<ModelAPI_Attribute> theAttribute);
+
+protected:
+  GCS::VEC_pD myValue; ///< list of pointers to values provided by the storage
+};
+
+typedef std::shared_ptr<PlaneGCSSolver_ScalarArrayWrapper> ScalarArrayWrapperPtr;
+
+#endif
index 439bc8184c4f85bbf557ac6b55d0b138cc185fb1..399b37531a88487e34ab413c6c40c13a80a23952 100644 (file)
 //
 
 #include <PlaneGCSSolver_ScalarWrapper.h>
+#include <PlaneGCSSolver_Tools.h>
 
-#include <cmath>
+#include <ModelAPI_AttributeDouble.h>
+#include <ModelAPI_AttributeInteger.h>
 
 PlaneGCSSolver_ScalarWrapper::PlaneGCSSolver_ScalarWrapper(double *const theParam)
   : myValue(theParam)
@@ -35,3 +37,29 @@ double PlaneGCSSolver_ScalarWrapper::value() const
 {
   return *myValue;
 }
+
+bool PlaneGCSSolver_ScalarWrapper::update(AttributePtr theAttribute)
+{
+  double anAttrValue = 0.0;
+  AttributeDoublePtr aDouble =
+      std::dynamic_pointer_cast<ModelAPI_AttributeDouble>(theAttribute);
+  if (aDouble)
+    anAttrValue = aDouble->value();
+  else {
+    AttributeIntegerPtr anInt =
+        std::dynamic_pointer_cast<ModelAPI_AttributeInteger>(theAttribute);
+    if (anInt)
+      anAttrValue = anInt->value();
+    else
+      return false;
+  }
+
+  // There is possible an angular value, which is converted between degrees and radians.
+  // So, we use its value instead of using direct pointer to variable.
+  double aCurrentValue = value();
+
+  bool isUpdated = PlaneGCSSolver_Tools::updateValue(anAttrValue, aCurrentValue);
+  if (isUpdated)
+    setValue(aCurrentValue);
+  return isUpdated;
+}
index 454f5ea01159642431862ca71c6022d090ed7fa3..01238d579d2ed795920d6f5886de1f3a294fba85 100644 (file)
@@ -44,6 +44,11 @@ public:
   virtual SketchSolver_EntityType type() const
   { return ENTITY_SCALAR; }
 
+protected:
+  /// \brief Update entity by the values of theAttribute
+  /// \return \c true if any value of attribute is not equal to the stored in the entity
+  virtual bool update(std::shared_ptr<ModelAPI_Attribute> theAttribute);
+
 protected:
   double* myValue; ///< pointer to value provided by the storage
 };
index a5788f23a82f8e37224d769a52f4c91cf07bc89e..7f936524c083487d9dac8719243404b4d14ce5b3 100644 (file)
@@ -23,6 +23,8 @@
 #include <PlaneGCSSolver_ConstraintWrapper.h>
 #include <PlaneGCSSolver_EdgeWrapper.h>
 #include <PlaneGCSSolver_PointWrapper.h>
+#include <PlaneGCSSolver_PointArrayWrapper.h>
+#include <PlaneGCSSolver_ScalarArrayWrapper.h>
 #include <PlaneGCSSolver_Tools.h>
 
 #include <PlaneGCSSolver_AttributeBuilder.h>
 #include <GeomAPI_Pnt2d.h>
 #include <GeomAPI_XY.h>
 #include <GeomDataAPI_Point2D.h>
+#include <GeomDataAPI_Point2DArray.h>
+#include <ModelAPI_AttributeDoubleArray.h>
 #include <ModelAPI_AttributeRefAttr.h>
+#include <SketchPlugin_BSpline.h>
 #include <SketchPlugin_Ellipse.h>
 #include <SketchPlugin_Projection.h>
 
@@ -102,55 +107,6 @@ EntityWrapperPtr PlaneGCSSolver_Storage::createAttribute(
   return aResult;
 }
 
-/// \brief Update value
-static bool updateValue(const double& theSource, double& theDest)
-{
-  static const double aTol = 1.e4 * tolerance;
-  bool isUpdated = fabs(theSource - theDest) > aTol;
-  if (isUpdated)
-    theDest = theSource;
-  return isUpdated;
-}
-
-/// \brief Update coordinates of the point or scalar using its base attribute
-static bool updateValues(AttributePtr& theAttribute, EntityWrapperPtr& theEntity)
-{
-  bool isUpdated = false;
-
-  std::shared_ptr<GeomDataAPI_Point2D> aPoint2D =
-      std::dynamic_pointer_cast<GeomDataAPI_Point2D>(theAttribute);
-  if (aPoint2D) {
-    const GCSPointPtr& aGCSPoint =
-        std::dynamic_pointer_cast<PlaneGCSSolver_PointWrapper>(theEntity)->point();
-    isUpdated = updateValue(aPoint2D->x(), *(aGCSPoint->x)) || isUpdated;
-    isUpdated = updateValue(aPoint2D->y(), *(aGCSPoint->y)) || isUpdated;
-  } else {
-    AttributeDoublePtr aScalar =
-        std::dynamic_pointer_cast<ModelAPI_AttributeDouble>(theAttribute);
-    if (aScalar) {
-      ScalarWrapperPtr aWrapper =
-          std::dynamic_pointer_cast<PlaneGCSSolver_ScalarWrapper>(theEntity);
-      // There is possible angular value, which is converted between degrees and radians.
-      // So, we use its value instead of using direct pointer to value.
-      double aValue = aWrapper->value();
-      isUpdated = updateValue(aScalar->value(), aValue);
-      if (isUpdated)
-        aWrapper->setValue(aValue);
-    } else {
-      AttributeBooleanPtr aBoolean =
-          std::dynamic_pointer_cast<ModelAPI_AttributeBoolean>(theAttribute);
-      if (aBoolean) {
-        BooleanWrapperPtr aWrapper =
-            std::dynamic_pointer_cast<PlaneGCSSolver_BooleanWrapper>(theEntity);
-        isUpdated = aWrapper->value() != aBoolean->value();
-        aWrapper->setValue(aBoolean->value());
-      }
-    }
-  }
-
-  return isUpdated;
-}
-
 static bool hasReference(std::shared_ptr<SketchPlugin_Feature> theFeature,
                          const std::string& theFeatureKind)
 {
@@ -203,9 +159,7 @@ bool PlaneGCSSolver_Storage::update(FeaturePtr theFeature, bool theForce)
   std::list<AttributePtr> anAttributes = theFeature->data()->attributes(std::string());
   std::list<AttributePtr>::iterator anAttrIt = anAttributes.begin();
   for (; anAttrIt != anAttributes.end(); ++anAttrIt)
-    if ((*anAttrIt)->attributeType() == GeomDataAPI_Point2D::typeId() ||
-        (*anAttrIt)->attributeType() == ModelAPI_AttributeDouble::typeId() ||
-        (*anAttrIt)->attributeType() == ModelAPI_AttributeBoolean::typeId())
+    if (PlaneGCSSolver_Tools::isAttributeApplicable((*anAttrIt)->id(), theFeature->getKind()))
       isUpdated = update(*anAttrIt) || isUpdated;
 
   // check external attribute is changed
@@ -257,7 +211,8 @@ bool PlaneGCSSolver_Storage::update(AttributePtr theAttribute, bool theForce)
     return aRelated.get() != 0;
   }
 
-  bool isUpdated = updateValues(anAttribute, aRelated);
+  PlaneGCSSolver_AttributeBuilder aBuilder(aRelated->isExternal() ? 0 : this);
+  bool isUpdated = aBuilder.updateAttribute(anAttribute, aRelated);
   if (isUpdated) {
     setNeedToResolve(true);
     notify(aFeature);
@@ -413,6 +368,47 @@ static void createEllipticArcConstraints(
   constraintsToSolver(aConstraint, theSolver);
 }
 
+static void createBSplineConstraints(
+    const EntityWrapperPtr& theCurve,
+    const SolverPtr& theSolver,
+    const ConstraintID theConstraintID,
+    std::map<EntityWrapperPtr, ConstraintWrapperPtr>& theConstraints)
+{
+  // set start and end point of B-spline equal to first and last pole correspondingly
+  EdgeWrapperPtr anEdge = std::dynamic_pointer_cast<PlaneGCSSolver_EdgeWrapper>(theCurve);
+  std::shared_ptr<GCS::BSpline> aBSpline =
+      std::dynamic_pointer_cast<GCS::BSpline>(anEdge->entity());
+  if (aBSpline->periodic)
+    return; // additional constraints are not necessary
+
+  std::list<GCSConstraintPtr> aBSplineConstraints;
+
+  const std::map<std::string, EntityWrapperPtr>& anAdditional = anEdge->additionalAttributes();
+  PointWrapperPtr aStartPoint = std::dynamic_pointer_cast<PlaneGCSSolver_PointWrapper>(
+      anAdditional.at(SketchPlugin_BSpline::START_ID()));
+
+  const GCS::Point& sp = *aStartPoint->point();
+  const GCS::Point& p0 = aBSpline->poles.front();
+  aBSplineConstraints.push_back(GCSConstraintPtr(new GCS::ConstraintEqual(p0.x, sp.x)));
+  aBSplineConstraints.push_back(GCSConstraintPtr(new GCS::ConstraintEqual(p0.y, sp.y)));
+
+  PointWrapperPtr aEndPoint = std::dynamic_pointer_cast<PlaneGCSSolver_PointWrapper>(
+      anAdditional.at(SketchPlugin_BSpline::END_ID()));
+
+  const GCS::Point& ep = *aEndPoint->point();
+  const GCS::Point& pN = aBSpline->poles.back();
+  aBSplineConstraints.push_back(GCSConstraintPtr(new GCS::ConstraintEqual(pN.x, ep.x)));
+  aBSplineConstraints.push_back(GCSConstraintPtr(new GCS::ConstraintEqual(pN.y, ep.y)));
+
+  ConstraintWrapperPtr aWrapper(
+      new PlaneGCSSolver_ConstraintWrapper(aBSplineConstraints, CONSTRAINT_UNKNOWN));
+  aWrapper->setId(theConstraintID);
+  if (theSolver)
+    constraintsToSolver(aWrapper, theSolver);
+
+  theConstraints[theCurve] = aWrapper;
+}
+
 void PlaneGCSSolver_Storage::createAuxiliaryConstraints(const EntityWrapperPtr& theEntity)
 {
   if (!theEntity || theEntity->isExternal())
@@ -426,6 +422,8 @@ void PlaneGCSSolver_Storage::createAuxiliaryConstraints(const EntityWrapperPtr&
     createEllipticArcConstraints(theEntity, mySketchSolver,
                                  ++myConstraintLastID, myAuxConstraintMap);
   }
+  else if (theEntity->type() == ENTITY_BSPLINE)
+    createBSplineConstraints(theEntity, mySketchSolver, ++myConstraintLastID, myAuxConstraintMap);
 }
 
 void PlaneGCSSolver_Storage::removeAuxiliaryConstraints(const EntityWrapperPtr& theEntity)
@@ -578,6 +576,8 @@ double* PlaneGCSSolver_Storage::createParameter()
 void PlaneGCSSolver_Storage::removeParameters(const GCS::SET_pD& theParams)
 {
   mySketchSolver->removeParameters(theParams);
+  //for (GCS::SET_pD::iterator it = theParams.begin(); it != theParams.end(); ++it)
+  //  delete *it;
 }
 
 // indicates attribute containing in the external feature
@@ -626,6 +626,23 @@ void PlaneGCSSolver_Storage::refresh() const
       }
       continue;
     }
+    std::shared_ptr<GeomDataAPI_Point2DArray> aPointArray =
+        std::dynamic_pointer_cast<GeomDataAPI_Point2DArray>(anIt->first);
+    if (aPointArray) {
+      std::shared_ptr<PlaneGCSSolver_PointArrayWrapper> anArrayWrapper =
+          std::dynamic_pointer_cast<PlaneGCSSolver_PointArrayWrapper>(anIt->second);
+      int aSize = aPointArray->size();
+      for (int anIndex = 0; anIndex < aSize; ++anIndex) {
+        GeomPnt2dPtr anOriginal = aPointArray->pnt(anIndex);
+        GCSPointPtr aGCSPoint = anArrayWrapper->value(anIndex)->point();
+        if (fabs(anOriginal->x() - (*aGCSPoint->x)) > aTol ||
+            fabs(anOriginal->y() - (*aGCSPoint->y)) > aTol) {
+          aPointArray->setPnt(anIndex, *aGCSPoint->x, *aGCSPoint->y);
+          addOwnerToSet(anIt->first, anUpdatedFeatures);
+        }
+      }
+      continue;
+    }
     AttributeDoublePtr aScalar = std::dynamic_pointer_cast<ModelAPI_AttributeDouble>(anIt->first);
     if (aScalar) {
       ScalarWrapperPtr aScalarWrapper =
@@ -636,6 +653,20 @@ void PlaneGCSSolver_Storage::refresh() const
       }
       continue;
     }
+    AttributeDoubleArrayPtr aRealArray =
+        std::dynamic_pointer_cast<ModelAPI_AttributeDoubleArray>(anIt->first);
+    if (aRealArray) {
+      std::shared_ptr<PlaneGCSSolver_ScalarArrayWrapper> anArrayWrapper =
+          std::dynamic_pointer_cast<PlaneGCSSolver_ScalarArrayWrapper>(anIt->second);
+      int aSize = aRealArray->size();
+      for (int anIndex = 0; anIndex < aSize; ++anIndex) {
+        if (fabs(aRealArray->value(anIndex) - *anArrayWrapper->array()[anIndex]) > aTol) {
+          aRealArray->setValue(anIndex, *anArrayWrapper->array()[anIndex]);
+          addOwnerToSet(anIt->first, anUpdatedFeatures);
+        }
+      }
+      continue;
+    }
   }
 
   // notify listeners about features update
index a438391ab3c49a39ccdcc035f1330d23d753f2ac..e29d23018fba251e44f77bb27672d4e8e695456d 100644 (file)
@@ -19,7 +19,9 @@
 
 #include <PlaneGCSSolver_Tools.h>
 #include <PlaneGCSSolver_EdgeWrapper.h>
+#include <PlaneGCSSolver_PointArrayWrapper.h>
 #include <PlaneGCSSolver_PointWrapper.h>
+#include <PlaneGCSSolver_ScalarArrayWrapper.h>
 #include <PlaneGCSSolver_ScalarWrapper.h>
 #include <PlaneGCSSolver_ConstraintWrapper.h>
 
 #include <SketchSolver_ConstraintMultiRotation.h>
 #include <SketchSolver_ConstraintMultiTranslation.h>
 
+#include <SketchPlugin_Arc.h>
+#include <SketchPlugin_BSpline.h>
+#include <SketchPlugin_BSplinePeriodic.h>
+#include <SketchPlugin_Circle.h>
 #include <SketchPlugin_ConstraintAngle.h>
 #include <SketchPlugin_ConstraintCoincidence.h>
 #include <SketchPlugin_ConstraintCollinear.h>
 #include <SketchPlugin_ConstraintRigid.h>
 #include <SketchPlugin_ConstraintPerpendicular.h>
 #include <SketchPlugin_ConstraintTangent.h>
+#include <SketchPlugin_Ellipse.h>
+#include <SketchPlugin_EllipticArc.h>
 #include <SketchPlugin_Line.h>
 #include <SketchPlugin_MultiRotation.h>
 #include <SketchPlugin_MultiTranslation.h>
+#include <SketchPlugin_Point.h>
 
 #include <GeomAPI_Circ2d.h>
 #include <GeomAPI_Dir2d.h>
@@ -62,6 +71,9 @@
 #include <GeomAPI_Lin2d.h>
 #include <GeomAPI_Pnt2d.h>
 
+#include <ModelAPI_AttributeDoubleArray.h>
+#include <ModelAPI_AttributeIntArray.h>
+
 #include <cmath>
 
 
@@ -77,7 +89,8 @@ static ConstraintWrapperPtr
 static ConstraintWrapperPtr
   createConstraintPointOnEntity(const SketchSolver_ConstraintType& theType,
                                 std::shared_ptr<PlaneGCSSolver_PointWrapper> thePoint,
-                                std::shared_ptr<PlaneGCSSolver_EdgeWrapper> theEntity);
+                                std::shared_ptr<PlaneGCSSolver_EdgeWrapper> theEntity,
+                                std::shared_ptr<PlaneGCSSolver_ScalarWrapper> theValue);
 static ConstraintWrapperPtr
   createConstraintPointsCollinear(std::shared_ptr<PlaneGCSSolver_PointWrapper> thePoint1,
                                   std::shared_ptr<PlaneGCSSolver_PointWrapper> thePoint2,
@@ -128,15 +141,15 @@ static ConstraintWrapperPtr
                                      std::shared_ptr<PlaneGCSSolver_EdgeWrapper> theEntity2);
 
 static GCS::SET_pD scalarParameters(const ScalarWrapperPtr& theScalar);
+static GCS::SET_pD scalarArrayParameters(const EntityWrapperPtr& theArray);
 static GCS::SET_pD pointParameters(const PointWrapperPtr& thePoint);
+static GCS::SET_pD pointArrayParameters(const EntityWrapperPtr& theArray);
 static GCS::SET_pD lineParameters(const EdgeWrapperPtr& theLine);
 static GCS::SET_pD circleParameters(const EdgeWrapperPtr& theCircle);
 static GCS::SET_pD arcParameters(const EdgeWrapperPtr& theArc);
 static GCS::SET_pD ellipseParameters(const EdgeWrapperPtr& theEllipse);
 static GCS::SET_pD ellipticArcParameters(const EdgeWrapperPtr& theEllipticArc);
-
-static double distance(const GCS::Point& thePnt1, const GCS::Point& thePnt2);
-
+static GCS::SET_pD bsplineParameters(const EdgeWrapperPtr& theEdge);
 
 
 
@@ -191,6 +204,13 @@ std::shared_ptr<SketchSolver_ConstraintMovement> PlaneGCSSolver_Tools::createMov
       new SketchSolver_ConstraintMovement(theMovedAttribute));
 }
 
+std::shared_ptr<SketchSolver_ConstraintMovement> PlaneGCSSolver_Tools::createMovementConstraint(
+    const std::pair<AttributePtr, int>& theMovedPointInArray)
+{
+  return std::shared_ptr<SketchSolver_ConstraintMovement>(
+      new SketchSolver_ConstraintMovement(theMovedPointInArray.first, theMovedPointInArray.second));
+}
+
 
 
 ConstraintWrapperPtr PlaneGCSSolver_Tools::createConstraint(
@@ -214,8 +234,9 @@ ConstraintWrapperPtr PlaneGCSSolver_Tools::createConstraint(
     aResult = createConstraintCoincidence(aPoint1, aPoint2);
     break;
   case CONSTRAINT_PT_ON_CURVE:
-    aResult = anEntity1 ? createConstraintPointOnEntity(theType, aPoint1, anEntity1):
-              createConstraintPointsCollinear(aPoint1, aPoint2, GCS_POINT_WRAPPER(theEntity1));
+    aResult = anEntity1 ?
+        createConstraintPointOnEntity(theType, aPoint1, anEntity1, GCS_SCALAR_WRAPPER(theValue)) :
+        createConstraintPointsCollinear(aPoint1, aPoint2, GCS_POINT_WRAPPER(theEntity1));
     break;
   case CONSTRAINT_MIDDLE_POINT:
     aResult = createConstraintMiddlePoint(aPoint1, GCS_EDGE_WRAPPER(theEntity1), aPoint2);
@@ -389,8 +410,12 @@ GCS::SET_pD PlaneGCSSolver_Tools::parameters(const EntityWrapperPtr& theEntity)
   case ENTITY_SCALAR:
   case ENTITY_ANGLE:
     return scalarParameters(GCS_SCALAR_WRAPPER(theEntity));
+  case ENTITY_SCALAR_ARRAY:
+    return scalarArrayParameters(theEntity);
   case ENTITY_POINT:
     return pointParameters(GCS_POINT_WRAPPER(theEntity));
+  case ENTITY_POINT_ARRAY:
+    return pointArrayParameters(theEntity);
   case ENTITY_LINE:
     return lineParameters(GCS_EDGE_WRAPPER(theEntity));
   case ENTITY_CIRCLE:
@@ -401,11 +426,119 @@ GCS::SET_pD PlaneGCSSolver_Tools::parameters(const EntityWrapperPtr& theEntity)
     return ellipseParameters(GCS_EDGE_WRAPPER(theEntity));
   case ENTITY_ELLIPTIC_ARC:
     return ellipticArcParameters(GCS_EDGE_WRAPPER(theEntity));
+  case ENTITY_BSPLINE:
+    return bsplineParameters(GCS_EDGE_WRAPPER(theEntity));
   default: break;
   }
   return GCS::SET_pD();
 }
 
+bool PlaneGCSSolver_Tools::isAttributeApplicable(const std::string& theAttrName,
+                                                 const std::string& theOwnerName)
+{
+  if (theOwnerName == SketchPlugin_Arc::ID()) {
+    return theAttrName == SketchPlugin_Arc::CENTER_ID() ||
+           theAttrName == SketchPlugin_Arc::START_ID() ||
+           theAttrName == SketchPlugin_Arc::END_ID() ||
+           theAttrName == SketchPlugin_Arc::REVERSED_ID();
+  }
+  else if (theOwnerName == SketchPlugin_Circle::ID()) {
+    return theAttrName == SketchPlugin_Circle::CENTER_ID() ||
+           theAttrName == SketchPlugin_Circle::RADIUS_ID();
+  }
+  else if (theOwnerName == SketchPlugin_Line::ID()) {
+    return theAttrName == SketchPlugin_Line::START_ID() ||
+           theAttrName == SketchPlugin_Line::END_ID();
+  }
+  else if (theOwnerName == SketchPlugin_Ellipse::ID()) {
+    return theAttrName == SketchPlugin_Ellipse::CENTER_ID() ||
+           theAttrName == SketchPlugin_Ellipse::FIRST_FOCUS_ID() ||
+           theAttrName == SketchPlugin_Ellipse::SECOND_FOCUS_ID() ||
+           theAttrName == SketchPlugin_Ellipse::MAJOR_AXIS_START_ID() ||
+           theAttrName == SketchPlugin_Ellipse::MAJOR_AXIS_END_ID() ||
+           theAttrName == SketchPlugin_Ellipse::MINOR_AXIS_START_ID() ||
+           theAttrName == SketchPlugin_Ellipse::MINOR_AXIS_END_ID() ||
+           theAttrName == SketchPlugin_Ellipse::MAJOR_RADIUS_ID() ||
+           theAttrName == SketchPlugin_Ellipse::MINOR_RADIUS_ID();
+  }
+  else if (theOwnerName == SketchPlugin_EllipticArc::ID()) {
+    return theAttrName == SketchPlugin_EllipticArc::CENTER_ID() ||
+           theAttrName == SketchPlugin_EllipticArc::FIRST_FOCUS_ID() ||
+           theAttrName == SketchPlugin_EllipticArc::SECOND_FOCUS_ID() ||
+           theAttrName == SketchPlugin_EllipticArc::MAJOR_AXIS_START_ID() ||
+           theAttrName == SketchPlugin_EllipticArc::MAJOR_AXIS_END_ID() ||
+           theAttrName == SketchPlugin_EllipticArc::MINOR_AXIS_START_ID() ||
+           theAttrName == SketchPlugin_EllipticArc::MINOR_AXIS_END_ID() ||
+           theAttrName == SketchPlugin_EllipticArc::MAJOR_RADIUS_ID() ||
+           theAttrName == SketchPlugin_EllipticArc::MINOR_RADIUS_ID() ||
+           theAttrName == SketchPlugin_EllipticArc::START_POINT_ID() ||
+           theAttrName == SketchPlugin_EllipticArc::END_POINT_ID() ||
+           theAttrName == SketchPlugin_EllipticArc::REVERSED_ID();
+  }
+  else if (theOwnerName == SketchPlugin_BSpline::ID()) {
+    return theAttrName == SketchPlugin_BSpline::POLES_ID() ||
+           theAttrName == SketchPlugin_BSpline::WEIGHTS_ID() ||
+           theAttrName == SketchPlugin_BSpline::KNOTS_ID() ||
+           theAttrName == SketchPlugin_BSpline::MULTS_ID() ||
+           theAttrName == SketchPlugin_BSpline::DEGREE_ID() ||
+           theAttrName == SketchPlugin_BSpline::START_ID() ||
+           theAttrName == SketchPlugin_BSpline::END_ID();
+  }
+  else if (theOwnerName == SketchPlugin_BSplinePeriodic::ID()) {
+    return theAttrName == SketchPlugin_BSplinePeriodic::POLES_ID() ||
+           theAttrName == SketchPlugin_BSplinePeriodic::WEIGHTS_ID() ||
+           theAttrName == SketchPlugin_BSplinePeriodic::KNOTS_ID() ||
+           theAttrName == SketchPlugin_BSplinePeriodic::MULTS_ID() ||
+           theAttrName == SketchPlugin_BSplinePeriodic::DEGREE_ID();
+  }
+
+  // suppose that all remaining features are points
+  return theAttrName == SketchPlugin_Point::COORD_ID();
+}
+
+/// \brief Update value
+bool PlaneGCSSolver_Tools::updateValue(const double& theSource, double& theDest,
+                                       const double theTolerance)
+{
+  bool isUpdated = fabs(theSource - theDest) > theTolerance;
+  if (isUpdated)
+    theDest = theSource;
+  return isUpdated;
+}
+
+
+double PlaneGCSSolver_Tools::distance(const GCS::Point& thePnt1, const GCS::Point& thePnt2)
+{
+  double x = *thePnt1.x - *thePnt2.x;
+  double y = *thePnt1.y - *thePnt2.y;
+  return sqrt(x*x + y*y);
+}
+
+
+
+
+// ================   AttributeArray methods   ==========================
+PlaneGCSSolver_Tools::AttributeArray::AttributeArray(AttributePtr theAttribute)
+{
+  myDouble = std::dynamic_pointer_cast<ModelAPI_AttributeDoubleArray>(theAttribute);
+  myInteger = std::dynamic_pointer_cast<ModelAPI_AttributeIntArray>(theAttribute);
+}
+
+bool PlaneGCSSolver_Tools::AttributeArray::isInitialized() const
+{
+  return (myDouble && myDouble->isInitialized()) || (myInteger && myInteger->isInitialized());
+}
+
+int PlaneGCSSolver_Tools::AttributeArray::size() const
+{
+  return myDouble.get() ? myDouble->size() : myInteger->size();
+}
+
+double PlaneGCSSolver_Tools::AttributeArray::value(const int theIndex) const
+{
+  return myDouble.get() ? myDouble->value(theIndex) : myInteger->value(theIndex);
+}
+
 
 
 
@@ -433,7 +566,8 @@ ConstraintWrapperPtr createConstraintCoincidence(
 ConstraintWrapperPtr createConstraintPointOnEntity(
     const SketchSolver_ConstraintType& theType,
     std::shared_ptr<PlaneGCSSolver_PointWrapper> thePoint,
-    std::shared_ptr<PlaneGCSSolver_EdgeWrapper> theEntity)
+    std::shared_ptr<PlaneGCSSolver_EdgeWrapper> theEntity,
+    std::shared_ptr<PlaneGCSSolver_ScalarWrapper> theValue)
 {
   GCSConstraintPtr aNewConstr;
 
@@ -459,6 +593,14 @@ ConstraintWrapperPtr createConstraintPointOnEntity(
         new GCS::ConstraintPointOnEllipse(*(thePoint->point()), *anEllipse));
     break;
     }
+  case ENTITY_BSPLINE: {
+    std::list<GCSConstraintPtr> aConstraints;
+    aConstraints.push_back(GCSConstraintPtr(new GCS::ConstraintCurveValue(
+        *thePoint->point(), thePoint->point()->x, *theEntity->entity(), theValue->scalar())));
+    aConstraints.push_back(GCSConstraintPtr(new GCS::ConstraintCurveValue(
+        *thePoint->point(), thePoint->point()->y, *theEntity->entity(), theValue->scalar())));
+    return ConstraintWrapperPtr(new PlaneGCSSolver_ConstraintWrapper(aConstraints, theType));
+  }
   default:
     return ConstraintWrapperPtr();
   }
@@ -698,7 +840,7 @@ ConstraintWrapperPtr createConstraintEqual(
     aConstrList.push_back(GCSConstraintPtr(
         new GCS::ConstraintP2PDistance(aLine2->p1, aLine2->p2, theIntermed->scalar())));
     // update value of intermediate parameter
-    theIntermed->setValue(distance(aLine1->p1, aLine1->p2));
+    theIntermed->setValue(PlaneGCSSolver_Tools::distance(aLine1->p1, aLine1->p2));
   }
   else if (theType == CONSTRAINT_EQUAL_ELLIPSES) {
     std::shared_ptr<GCS::Ellipse> anEllipse1 =
@@ -713,7 +855,7 @@ ConstraintWrapperPtr createConstraintEqual(
     aConstrList.push_back(GCSConstraintPtr(new GCS::ConstraintP2PDistance(
         anEllipse2->center, anEllipse2->focus1, theIntermed->scalar())));
     // update value of intermediate parameter
-    theIntermed->setValue(distance(anEllipse1->center, anEllipse1->focus1));
+    theIntermed->setValue(PlaneGCSSolver_Tools::distance(anEllipse1->center, anEllipse1->focus1));
   }
   else {
     std::shared_ptr<GCS::Circle> aCirc1 =
@@ -737,6 +879,13 @@ GCS::SET_pD scalarParameters(const ScalarWrapperPtr& theScalar)
   return aParams;
 }
 
+GCS::SET_pD scalarArrayParameters(const EntityWrapperPtr& theArray)
+{
+  ScalarArrayWrapperPtr anArray =
+      std::dynamic_pointer_cast<PlaneGCSSolver_ScalarArrayWrapper>(theArray);
+  return GCS::SET_pD(anArray->array().begin(), anArray->array().end());
+}
+
 GCS::SET_pD pointParameters(const PointWrapperPtr& thePoint)
 {
   GCS::SET_pD aParams;
@@ -745,6 +894,19 @@ GCS::SET_pD pointParameters(const PointWrapperPtr& thePoint)
   return aParams;
 }
 
+GCS::SET_pD pointArrayParameters(const EntityWrapperPtr& theArray)
+{
+  GCS::SET_pD aParams;
+  PointArrayWrapperPtr aPoints =
+      std::dynamic_pointer_cast<PlaneGCSSolver_PointArrayWrapper>(theArray);
+  for (std::vector<PointWrapperPtr>::const_iterator anIt = aPoints->array().begin();
+       anIt != aPoints->array().end(); ++anIt) {
+    GCS::SET_pD aPointParams = PlaneGCSSolver_Tools::parameters(*anIt);
+    aParams.insert(aPointParams.begin(), aPointParams.end());
+  }
+  return aParams;
+}
+
 GCS::SET_pD lineParameters(const EdgeWrapperPtr& theLine)
 {
   GCS::SET_pD aParams;
@@ -806,9 +968,19 @@ GCS::SET_pD ellipticArcParameters(const EdgeWrapperPtr& theEllipticArc)
   return aParams;
 }
 
-double distance(const GCS::Point& thePnt1, const GCS::Point& thePnt2)
+GCS::SET_pD bsplineParameters(const EdgeWrapperPtr& theEdge)
 {
-  double x = *thePnt1.x - *thePnt2.x;
-  double y = *thePnt1.y - *thePnt2.y;
-  return sqrt(x*x + y*y);
+  GCS::SET_pD aParams;
+
+  std::shared_ptr<GCS::BSpline> aBSpline =
+    std::dynamic_pointer_cast<GCS::BSpline>(theEdge->entity());
+
+  for (GCS::VEC_P::iterator it = aBSpline->poles.begin(); it != aBSpline->poles.end(); ++it) {
+    aParams.insert(it->x);
+    aParams.insert(it->y);
+  }
+  for (GCS::VEC_pD::iterator it = aBSpline->weights.begin(); it != aBSpline->weights.end(); ++it)
+    aParams.insert(*it);
+
+  return aParams;
 }
index a02a9948c77a2302f86a4b7d48176b33f3d3f4ea..846db6d21ba3b3c0fdb8adbfd5748e6d89274d08 100644 (file)
@@ -29,6 +29,9 @@ class GeomAPI_Ellipse2d;
 class GeomAPI_Lin2d;
 class GeomAPI_Pnt2d;
 
+class ModelAPI_AttributeDoubleArray;
+class ModelAPI_AttributeIntArray;
+
 /** \namespace  PlaneGCSSolver_Tools
  *  \ingroup    Plugins
  *  \brief      Converter tools
@@ -45,6 +48,9 @@ namespace PlaneGCSSolver_Tools
   /// \brief Creates temporary constraint to fix the attribute after movement
   std::shared_ptr<SketchSolver_ConstraintMovement>
       createMovementConstraint(AttributePtr theMovedAttribute);
+  /// \brief Creates temporary constraint to fix the point in array after movement
+  std::shared_ptr<SketchSolver_ConstraintMovement>
+      createMovementConstraint(const std::pair<AttributePtr, int>& theMovedPointInArray);
 
   /// \brief Creates new constraint using given parameters
   /// \param theConstraint [in]  original constraint
@@ -64,6 +70,12 @@ namespace PlaneGCSSolver_Tools
                                         const EntityWrapperPtr& theEntity3 = EntityWrapperPtr(),
                                         const EntityWrapperPtr& theEntity4 = EntityWrapperPtr());
 
+  /// \brief Return \c true if the attribute is used in PlaneGCS solver
+  /// \param[in] theAttrName  name of the attribute
+  /// \param[in] theOwnerName name of the parent feature
+  bool isAttributeApplicable(const std::string& theAttrName,
+                             const std::string& theOwnerName);
+
   /// \brief Convert entity to point
   /// \return empty pointer if the entity is not a point
   std::shared_ptr<GeomAPI_Pnt2d> point(EntityWrapperPtr theEntity);
@@ -88,6 +100,30 @@ namespace PlaneGCSSolver_Tools
 
   /// brief Return list of parameters for the given entity
   GCS::SET_pD parameters(const EntityWrapperPtr& theEntity);
+
+  /// \brief Update value in theDest if theSource is differ more than theTolerance
+  /// \return \c true if the value was updated.
+  bool updateValue(const double& theSource, double& theDest,
+                   const double theTolerance = 1.e-4 * tolerance);
+
+  double distance(const GCS::Point& thePnt1, const GCS::Point& thePnt2);
+
+  /// \brief Provide an interface to access values in attribute which is an array of values
+  class AttributeArray
+  {
+  public:
+    AttributeArray(AttributePtr theAttribute);
+
+    bool isInitialized() const;
+
+    int size() const;
+
+    double value(const int theIndex) const;
+
+  private:
+    std::shared_ptr<ModelAPI_AttributeDoubleArray> myDouble;
+    std::shared_ptr<ModelAPI_AttributeIntArray> myInteger;
+  };
 };
 
 #endif
index 48c0fa5b80d63454f15ee4b12fa0a563c4b941d7..17720fdc4c3855a4a995e25075523c5510aa17d9 100644 (file)
@@ -233,7 +233,7 @@ void SketchSolver_Constraint::getAttributes(
     SketchSolver_EntityType aType = anEntity->type();
     if (aType == ENTITY_UNKNOWN)
       continue;
-    else if (aType == ENTITY_POINT)
+    else if (aType == ENTITY_POINT || aType == ENTITY_POINT_ARRAY)
       theAttributes[aPtInd++] = anEntity; // the point is created
     else { // another entity (not a point) is created
       if (aEntInd < anInitNbOfAttr)
index 4bac7dd1fda160092024b66cad9bc7fc5ed99d92..c00dcfce8d1ccd609a33d9083c1e9d74fad0d69a 100644 (file)
 
 #include <SketchSolver_ConstraintCoincidence.h>
 #include <SketchSolver_Error.h>
+#include <PlaneGCSSolver_PointArrayWrapper.h>
+#include <PlaneGCSSolver_Storage.h>
 #include <PlaneGCSSolver_Tools.h>
 #include <PlaneGCSSolver_UpdateCoincidence.h>
 
 #include <GeomDataAPI_Point2D.h>
 
+#include <ModelAPI_AttributeInteger.h>
+
 #include <SketchPlugin_Arc.h>
 #include <SketchPlugin_ConstraintCoincidenceInternal.h>
 #include <SketchPlugin_Ellipse.h>
@@ -186,6 +190,20 @@ static void processEllipticArcExtremities(SketchSolver_ConstraintType& theType,
   }
 }
 
+static void getPointFromArray(EntityWrapperPtr& theArray,
+                              const ConstraintPtr& theConstraint,
+                              const std::string& theIndexAttrId)
+{
+  if (theArray && theArray->type() == ENTITY_POINT_ARRAY) {
+    AttributeIntegerPtr anIndexAttr = theConstraint->integer(theIndexAttrId);
+    if (anIndexAttr) {
+      PointArrayWrapperPtr aPointsArray =
+          std::dynamic_pointer_cast<PlaneGCSSolver_PointArrayWrapper>(theArray);
+      theArray = aPointsArray->value(anIndexAttr->value());
+    }
+  }
+}
+
 
 void SketchSolver_ConstraintCoincidence::process()
 {
@@ -207,7 +225,7 @@ void SketchSolver_ConstraintCoincidence::process()
 
   mySolverConstraint = PlaneGCSSolver_Tools::createConstraint(
       myBaseConstraint, getType(),
-      aValue, anAttributes[0], anAttributes[1], anAttributes[2], anAttributes[3]);
+      myAuxValue, anAttributes[0], anAttributes[1], anAttributes[2], anAttributes[3]);
 
   myStorage->subscribeUpdates(this, PlaneGCSSolver_UpdateCoincidence::GROUP());
   myStorage->notify(myBaseConstraint);
@@ -218,6 +236,13 @@ bool SketchSolver_ConstraintCoincidence::remove()
   myInSolver = false;
   myFeatureExtremities[0] = EntityWrapperPtr();
   myFeatureExtremities[1] = EntityWrapperPtr();
+  if (myAuxValue) {
+    std::shared_ptr<PlaneGCSSolver_Storage> aStorage =
+        std::dynamic_pointer_cast<PlaneGCSSolver_Storage>(myStorage);
+    GCS::SET_pD aParams;
+    aParams.insert(myAuxValue->scalar());
+    aStorage->removeParameters(aParams);
+  }
   return SketchSolver_Constraint::remove();
 }
 
@@ -240,10 +265,24 @@ void SketchSolver_ConstraintCoincidence::getAttributes(
                                   theAttributes, myFeatureExtremities);
   } else if (theAttributes[2]) {
     myType = CONSTRAINT_PT_ON_CURVE;
-    // obtain extremity points of the coincident feature for further checking of multi-coincidence
-    getCoincidentFeatureExtremities(myBaseConstraint, myStorage, myFeatureExtremities);
+    // point-on-bspline requires additional parameter
+    if (theAttributes[2]->type() == ENTITY_BSPLINE) {
+      std::shared_ptr<PlaneGCSSolver_Storage> aStorage =
+          std::dynamic_pointer_cast<PlaneGCSSolver_Storage>(myStorage);
+      myAuxValue.reset(new PlaneGCSSolver_ScalarWrapper(aStorage->createParameter()));
+    }
+    else {
+      // obtain extremity points of the coincident feature for further checking of multi-coincidence
+      getCoincidentFeatureExtremities(myBaseConstraint, myStorage, myFeatureExtremities);
+    }
   } else
     myErrorMsg = SketchSolver_Error::INCORRECT_ATTRIBUTE();
+
+  // process internal coincidence with a point in the array of points
+  getPointFromArray(theAttributes[0], myBaseConstraint,
+                    SketchPlugin_ConstraintCoincidenceInternal::INDEX_ENTITY_A());
+  getPointFromArray(theAttributes[1], myBaseConstraint,
+                    SketchPlugin_ConstraintCoincidenceInternal::INDEX_ENTITY_B());
 }
 
 void SketchSolver_ConstraintCoincidence::notify(const FeaturePtr&      theFeature,
@@ -300,3 +339,18 @@ void SketchSolver_ConstraintCoincidence::notify(const FeaturePtr&      theFeatur
     }
   }
 }
+
+void SketchSolver_ConstraintCoincidence::adjustConstraint()
+{
+  if (myBaseConstraint->getKind() == SketchPlugin_ConstraintCoincidenceInternal::ID()) {
+    AttributeIntegerPtr anIndexA = myBaseConstraint->integer(
+        SketchPlugin_ConstraintCoincidenceInternal::INDEX_ENTITY_A());
+    AttributeIntegerPtr anIndexB = myBaseConstraint->integer(
+        SketchPlugin_ConstraintCoincidenceInternal::INDEX_ENTITY_B());
+    if ((anIndexA && anIndexA->isInitialized()) ||
+        (anIndexB && anIndexB->isInitialized())) {
+      remove();
+      process();
+    }
+  }
+}
index 871314b926afa2d6993b1667e725eb66158eb3d7..4ed2b821c7825e285eb503919ebe9d86eeaec565 100644 (file)
@@ -52,9 +52,14 @@ protected:
   virtual void getAttributes(EntityWrapperPtr&              theValue,
                              std::vector<EntityWrapperPtr>& theAttributes);
 
+  /// \brief This method is used in derived objects to check consistency of constraint.
+  ///        E.g. the distance between line and point may be signed.
+  virtual void adjustConstraint();
+
 protected:
   bool myInSolver; ///< shows the constraint is added to the solver
   EntityWrapperPtr myFeatureExtremities[2]; ///< extremities of a feature, a point is coincident to
+  ScalarWrapperPtr myAuxValue; ///< parameter on B-spline curve
 };
 
 #endif
index a32e1966f49a200ca6016081b1c8e071062ab166..a56004115d8a3bef5d8342994b6ba3e737c4e9a1 100644 (file)
@@ -163,6 +163,16 @@ GCS::VEC_pD toParameters(const EntityWrapperPtr& theEntity)
     aParameters.push_back(anEllArc->endAngle);
     break;
     }
+  case ENTITY_BSPLINE: {
+    std::shared_ptr<GCS::BSpline> aBSpline =
+        std::dynamic_pointer_cast<GCS::BSpline>(anEntity->entity());
+    for (GCS::VEC_P::iterator anIt = aBSpline->poles.begin();
+         anIt != aBSpline->poles.end(); ++anIt) {
+      aParameters.push_back(anIt->x);
+      aParameters.push_back(anIt->y);
+    }
+    break;
+  }
   default:
     break;
   }
index 8e38fa6b8303487f7acead82f677d97b0c9b0660..3384416c7102c4ef375157b9c3d8907f5212d953 100644 (file)
@@ -22,6 +22,7 @@
 #include <SketchSolver_Manager.h>
 
 #include <PlaneGCSSolver_EdgeWrapper.h>
+#include <PlaneGCSSolver_PointArrayWrapper.h>
 #include <PlaneGCSSolver_PointWrapper.h>
 
 #include <SketchPlugin_Arc.h>
@@ -53,12 +54,14 @@ SketchSolver_ConstraintMovement::SketchSolver_ConstraintMovement(FeaturePtr theF
 {
 }
 
-SketchSolver_ConstraintMovement::SketchSolver_ConstraintMovement(AttributePtr thePoint)
+SketchSolver_ConstraintMovement::SketchSolver_ConstraintMovement(AttributePtr theAttribute,
+                                                                 const int thePointIndex)
   : SketchSolver_ConstraintFixed(ConstraintPtr()),
-    myDraggedPoint(thePoint),
+    myDraggedAttribute(theAttribute),
+    myDraggedPointIndex(thePointIndex),
     mySimpleMove(true)
 {
-  myMovedFeature = ModelAPI_Feature::feature(thePoint->owner());
+  myMovedFeature = ModelAPI_Feature::feature(theAttribute->owner());
 }
 
 void SketchSolver_ConstraintMovement::blockEvents(bool isBlocked)
@@ -115,22 +118,27 @@ ConstraintWrapperPtr SketchSolver_ConstraintMovement::initMovement()
     return aConstraint;
   }
 
-  EntityWrapperPtr anEntity =
-      myDraggedPoint ? myStorage->entity(myDraggedPoint) : myStorage->entity(myMovedFeature);
+  EntityWrapperPtr anEntity = myDraggedAttribute ? myStorage->entity(myDraggedAttribute)
+                                                 : myStorage->entity(myMovedFeature);
   if (!anEntity) {
     myStorage->update(myMovedFeature, true);
-    anEntity =
-        myDraggedPoint ? myStorage->entity(myDraggedPoint) : myStorage->entity(myMovedFeature);
+    anEntity = myDraggedAttribute ? myStorage->entity(myDraggedAttribute)
+                                  : myStorage->entity(myMovedFeature);
     if (!anEntity)
       return aConstraint;
   }
 
-  mySimpleMove = isSimpleMove(myMovedFeature, myDraggedPoint);
+  mySimpleMove = isSimpleMove(myMovedFeature, myDraggedAttribute);
 
-  if (mySimpleMove)
+  if (mySimpleMove) {
+    if (anEntity->type() == ENTITY_POINT_ARRAY) {
+      anEntity = std::dynamic_pointer_cast<PlaneGCSSolver_PointArrayWrapper>(anEntity)
+                 ->value(myDraggedPointIndex);
+    }
     aConstraint = fixFeature(anEntity);
+  }
   else {
-    if (myDraggedPoint) // start or end point of arc has been moved
+    if (myDraggedAttribute) // start or end point of arc has been moved
       aConstraint = fixArcExtremity(anEntity);
     else if (anEntity->type() == ENTITY_CIRCLE || anEntity->type() == ENTITY_ARC) {
       // arc or circle has been moved
@@ -311,7 +319,8 @@ void SketchSolver_ConstraintMovement::moveTo(
 #ifdef CHANGE_RADIUS_WHILE_MOVE
   int aMaxSize = mySimpleMove ? (int)myFixedValues.size() : 2;
 #else
-  int aMaxSize = myMovedFeature->getKind() == SketchPlugin_Line::ID() && !myDraggedPoint ? 4 : 2;
+  int aMaxSize =
+      myMovedFeature->getKind() == SketchPlugin_Line::ID() && !myDraggedAttribute ? 4 : 2;
 #endif
   for (int i = 0; i < aMaxSize; ++i)
     myFixedValues[i] += aDelta[i % 2];
index bfbe1f1ed644f2fc442e11ef918c4cd33275ebcc..451ddb462ee41672e0b9d4cbecafb0df4dd406c9 100644 (file)
@@ -39,7 +39,7 @@ public:
   SketchSolver_ConstraintMovement(FeaturePtr theFeature);
 
   /// Creates movement constraint based on point
-  SketchSolver_ConstraintMovement(AttributePtr thePoint);
+  SketchSolver_ConstraintMovement(AttributePtr theMovedAttribute, const int thePointIndex = -1);
 
   /// \brief Set coordinates of the start point of the movement
   void startPoint(const std::shared_ptr<GeomAPI_Pnt2d>& theStartPoint);
@@ -74,7 +74,8 @@ protected:
 
 private:
   FeaturePtr       myMovedFeature; ///< fixed feature (if set, myBaseConstraint should be NULL)
-  AttributePtr     myDraggedPoint; ///< one of the feature points which has been moved
+  AttributePtr     myDraggedAttribute; ///< one of the feature points which has been moved
+  int              myDraggedPointIndex; ///< index of the point if the moved attribute is an array
   std::shared_ptr<GeomAPI_Pnt2d> myStartPoint; ///< start point of the movement
 
   bool mySimpleMove; ///< simple move, thus all parameters should be increased by movement delta
index 18d95f8ec9d5c533dcaf88caf62f091719155b7b..f3832c24521f674cbd626acfe6815bf18710d33e 100644 (file)
 #include <GeomAPI_Pnt2d.h>
 #include <GeomAPI_Ellipse2d.h>
 
+#include <ModelAPI_AttributeInteger.h>
+
 #include <SketchPlugin_Arc.h>
+#include <SketchPlugin_BSpline.h>
 #include <SketchPlugin_Circle.h>
 #include <SketchPlugin_ConstraintCoincidence.h>
 #include <SketchPlugin_ConstraintCoincidenceInternal.h>
@@ -112,14 +115,20 @@ void SketchSolver_ConstraintTangent::rebuild()
   mySharedPoint = AttributePtr();
   if (myAuxPoint) {
     GCS::SET_pD aParams = PlaneGCSSolver_Tools::parameters(myAuxPoint);
+    if (myAuxParameters[0])
+      aParams.insert(myAuxParameters[0]->scalar());
+    if (myAuxParameters[1])
+      aParams.insert(myAuxParameters[1]->scalar());
     aStorage->removeParameters(aParams);
     myAuxPoint = EntityWrapperPtr();
+    myAuxParameters[0] = myAuxParameters[1] = ScalarWrapperPtr();
   }
 
   // Check the quantity of entities of each type and their order (arcs first)
   int aNbLines = 0;
   int aNbCircles = 0;
   int aNbEllipses = 0;
+  int aNbSplines = 0;
   std::list<EntityWrapperPtr>::iterator anEntIt = myAttributes.begin();
   for (; anEntIt != myAttributes.end(); ++anEntIt) {
     if (!(*anEntIt).get())
@@ -130,16 +139,18 @@ void SketchSolver_ConstraintTangent::rebuild()
       ++aNbCircles;
     else if ((*anEntIt)->type() == ENTITY_ELLIPSE || (*anEntIt)->type() == ENTITY_ELLIPTIC_ARC)
       ++aNbEllipses;
+    else if ((*anEntIt)->type() == ENTITY_BSPLINE)
+      ++aNbSplines;
   }
 
-  if (aNbCircles + aNbEllipses < 1) {
+  if (aNbCircles + aNbEllipses + aNbSplines < 1) {
     myErrorMsg = SketchSolver_Error::INCORRECT_TANGENCY_ATTRIBUTE();
     return;
   }
   if (aNbLines == 1 && aNbCircles == 1) {
     myType = CONSTRAINT_TANGENT_CIRCLE_LINE;
   }
-  else if (aNbLines + aNbCircles + aNbEllipses == 2) {
+  else if (aNbLines + aNbCircles + aNbEllipses + aNbSplines == 2) {
     myType = CONSTRAINT_TANGENT_CURVE_CURVE;
     isArcArcInternal = isArcArcTangencyInternal(myAttributes.front(), myAttributes.back());
   }
@@ -148,20 +159,61 @@ void SketchSolver_ConstraintTangent::rebuild()
     return;
   }
 
-  FeaturePtr aFeature1, aFeature2;
-  getTangentFeatures(myBaseConstraint, aFeature1, aFeature2);
+  FeaturePtr aFeatures[2];
+  getTangentFeatures(myBaseConstraint, aFeatures[0], aFeatures[1]);
 
   // check number of coincident points
-  std::set<AttributePtr> aCoincidentPoints = coincidentBoundaryPoints(aFeature1, aFeature2);
+  std::set<AttributePtr> aCoincidentPoints = coincidentBoundaryPoints(aFeatures[0], aFeatures[1]);
   if (myType == CONSTRAINT_TANGENT_CIRCLE_LINE && aCoincidentPoints.size() > 2) {
     myErrorMsg = SketchSolver_Error::TANGENCY_FAILED();
     return;
   }
 
-  // Try to find non-boundary points coincident with both features.
-  // It is necesasry to create tangency with ellipse
-  if (aCoincidentPoints.empty() && aNbEllipses > 0)
-    aCoincidentPoints = coincidentPoints(aFeature1, aFeature2);
+  EntityWrapperPtr aTgEntities[2] = { myAttributes.front(), myAttributes.back() };
+
+  if (aCoincidentPoints.empty()) {
+    // Try to find non-boundary points coincident with both features.
+    // It is necesasry to create tangency with ellipse.
+    if (aNbEllipses > 0)
+      aCoincidentPoints = coincidentPoints(aFeatures[0], aFeatures[1]);
+  }
+  else if (aNbSplines > 0) {
+    // General approach applying tangency to B-spline leads to hang-up in PlaneGCS.
+    // So, the tangency will be applied for the construction segment instead of B-spline curve.
+    for (int i = 0; i < 2; ++i) {
+      if (aTgEntities[i]->type() == ENTITY_BSPLINE) {
+        EdgeWrapperPtr anEdge =
+            std::dynamic_pointer_cast<PlaneGCSSolver_EdgeWrapper>(aTgEntities[i]);
+        std::shared_ptr<GCS::BSpline> aBSpline =
+            std::dynamic_pointer_cast<GCS::BSpline>(anEdge->entity());
+
+        // which boundary is coincident?
+        GCS::Point aPoint1, aPoint2;
+        for (std::set<AttributePtr>::iterator aPIt = aCoincidentPoints.begin();
+             aPIt != aCoincidentPoints.end(); ++aPIt) {
+          if ((*aPIt)->owner() == aFeatures[i]) {
+            if ((*aPIt)->id() == SketchPlugin_BSpline::START_ID()) {
+              aPoint1 = aBSpline->poles[0];
+              aPoint2 = aBSpline->poles[1];
+            }
+            else if ((*aPIt)->id() == SketchPlugin_BSpline::END_ID()) {
+              aPoint1 = aBSpline->poles[aBSpline->poles.size() - 2];
+              aPoint2 = aBSpline->poles[aBSpline->poles.size() - 1];
+            }
+            break;
+          }
+        }
+
+        // substitute B-spline by its boundary segment
+        std::shared_ptr<GCS::Line> aSegment(new GCS::Line);
+        aSegment->p1 = aPoint1;
+        aSegment->p2 = aPoint2;
+        aTgEntities[i] = EdgeWrapperPtr(new PlaneGCSSolver_EdgeWrapper(aSegment));
+        --aNbSplines;
+        ++aNbLines;
+      }
+    }
+  }
 
   EntityWrapperPtr aSharedPointEntity;
   std::list<GCSConstraintPtr> anAuxConstraints;
@@ -169,32 +221,36 @@ void SketchSolver_ConstraintTangent::rebuild()
     mySharedPoint = *aCoincidentPoints.begin();
     aSharedPointEntity = myStorage->entity(mySharedPoint);
   }
-  else if (aNbEllipses > 0) {
+  else if (aNbEllipses + aNbSplines > 0) {
     // create auxiliary point
     GCSPointPtr aPoint(new GCS::Point);
     aPoint->x = aStorage->createParameter();
     aPoint->y = aStorage->createParameter();
-    calculateTangencyPoint(myAttributes.front(), myAttributes.back(), aPoint);
+    calculateTangencyPoint(aTgEntities[0], aTgEntities[1], aPoint);
 
     myAuxPoint.reset(new PlaneGCSSolver_PointWrapper(aPoint));
     aSharedPointEntity = myAuxPoint;
 
-    // create auxiliary coincident constraints for tangency with ellipse
     EntityWrapperPtr aDummy;
-    ConstraintWrapperPtr aCoincidence = PlaneGCSSolver_Tools::createConstraint(ConstraintPtr(),
-        CONSTRAINT_PT_ON_CURVE, aDummy, aSharedPointEntity, aDummy, myAttributes.front(), aDummy);
-    anAuxConstraints = aCoincidence->constraints();
-    aCoincidence = PlaneGCSSolver_Tools::createConstraint(ConstraintPtr(),
-        CONSTRAINT_PT_ON_CURVE, aDummy, aSharedPointEntity, aDummy, myAttributes.back(), aDummy);
-    anAuxConstraints.insert(anAuxConstraints.end(),
-        aCoincidence->constraints().begin(), aCoincidence->constraints().end());
+    for (int i = 0; i < 2; ++i) {
+      // create auxiliary parameters for coincidence with B-spline
+      if (aTgEntities[i]->type() == ENTITY_BSPLINE)
+        myAuxParameters[i].reset(new PlaneGCSSolver_ScalarWrapper(aStorage->createParameter()));
+
+      // create auxiliary coincident constraints for tangency with ellipse
+      ConstraintWrapperPtr aCoincidence = PlaneGCSSolver_Tools::createConstraint(ConstraintPtr(),
+          CONSTRAINT_PT_ON_CURVE, myAuxParameters[i],
+          aSharedPointEntity, aDummy, aTgEntities[i], aDummy);
+      anAuxConstraints.insert(anAuxConstraints.end(),
+          aCoincidence->constraints().begin(), aCoincidence->constraints().end());
+    }
   }
 
   if (myType == CONSTRAINT_TANGENT_CIRCLE_LINE) {
-    mySolverConstraint = createArcLineTangency(myAttributes.front(), myAttributes.back(),
+    mySolverConstraint = createArcLineTangency(aTgEntities[0], aTgEntities[1],
                                            aSharedPointEntity, &myCurveCurveAngle);
   } else {
-    mySolverConstraint = createCurveCurveTangency(myAttributes.front(), myAttributes.back(),
+    mySolverConstraint = createCurveCurveTangency(aTgEntities[0], aTgEntities[1],
                             isArcArcInternal, aSharedPointEntity, &myCurveCurveAngle);
   }
 
@@ -282,6 +338,24 @@ void SketchSolver_ConstraintTangent::notify(const FeaturePtr&      theFeature,
     rebuild();
 }
 
+bool SketchSolver_ConstraintTangent::remove()
+{
+  if (myAuxPoint) {
+    std::shared_ptr<PlaneGCSSolver_Storage> aStorage =
+        std::dynamic_pointer_cast<PlaneGCSSolver_Storage>(myStorage);
+
+    GCS::SET_pD aParams = PlaneGCSSolver_Tools::parameters(myAuxPoint);
+    if (myAuxParameters[0])
+      aParams.insert(myAuxParameters[0]->scalar());
+    if (myAuxParameters[1])
+      aParams.insert(myAuxParameters[1]->scalar());
+    aStorage->removeParameters(aParams);
+    myAuxPoint = EntityWrapperPtr();
+    myAuxParameters[0] = myAuxParameters[1] = ScalarWrapperPtr();
+  }
+  return SketchSolver_Constraint::remove();
+}
+
 
 
 
@@ -302,13 +376,22 @@ std::set<FeaturePtr> collectCoincidences(FeaturePtr theFeature1, FeaturePtr theF
   const std::set<AttributePtr>& aRefs2 = theFeature2->data()->refsToMe();
 
   std::set<FeaturePtr> aCoincidences;
+  std::map<AttributePtr, FeaturePtr> aCoincidentPoints;
   std::set<AttributePtr>::const_iterator anIt;
 
   // collect coincidences referred to the first feature
   for (anIt = aRefs1.begin(); anIt != aRefs1.end(); ++anIt) {
     FeaturePtr aRef = ModelAPI_Feature::feature((*anIt)->owner());
-    if (aRef && aRef->getKind() == SketchPlugin_ConstraintCoincidence::ID())
+    if (aRef && (aRef->getKind() == SketchPlugin_ConstraintCoincidence::ID() ||
+                 aRef->getKind() == SketchPlugin_ConstraintCoincidenceInternal::ID())) {
       aCoincidences.insert(aRef);
+      AttributeRefAttrPtr aRefAttrA = aRef->refattr(SketchPlugin_Constraint::ENTITY_A());
+      if (!aRefAttrA->isObject())
+        aCoincidentPoints[aRefAttrA->attr()] = aRef;
+      AttributeRefAttrPtr aRefAttrB = aRef->refattr(SketchPlugin_Constraint::ENTITY_B());
+      if (!aRefAttrB->isObject())
+        aCoincidentPoints[aRefAttrB->attr()] = aRef;
+    }
   }
 
   // leave only coincidences referred to the second feature
@@ -317,6 +400,20 @@ std::set<FeaturePtr> collectCoincidences(FeaturePtr theFeature1, FeaturePtr theF
     FeaturePtr aRef = ModelAPI_Feature::feature((*anIt)->owner());
     if (aCoincidences.find(aRef) != aCoincidences.end())
       aCoincidencesBetweenFeatures.insert(aRef);
+    else if (aRef && (aRef->getKind() == SketchPlugin_ConstraintCoincidence::ID() ||
+                      aRef->getKind() == SketchPlugin_ConstraintCoincidenceInternal::ID())) {
+      for (int i = 0; i < CONSTRAINT_ATTR_SIZE; ++i) {
+        AttributeRefAttrPtr aRefAttr = aRef->refattr(SketchPlugin_Constraint::ATTRIBUTE(i));
+        if (aRefAttr && !aRefAttr->isObject()) {
+          std::map<AttributePtr, FeaturePtr>::iterator aFound =
+              aCoincidentPoints.find(aRefAttr->attr());
+          if (aFound != aCoincidentPoints.end()) {
+            aCoincidencesBetweenFeatures.insert(aRef);
+            aCoincidencesBetweenFeatures.insert(aFound->second);
+          }
+        }
+      }
+    }
   }
 
   return aCoincidencesBetweenFeatures;
@@ -329,20 +426,31 @@ std::set<AttributePtr> coincidentBoundaryPoints(FeaturePtr theFeature1, FeatureP
   std::set<AttributePtr> aCoincidentPoints;
   std::set<FeaturePtr>::const_iterator aCIt = aCoincidences.begin();
   for (; aCIt != aCoincidences.end(); ++ aCIt) {
-    AttributeRefAttrPtr aRefAttrA = (*aCIt)->refattr(SketchPlugin_Constraint::ENTITY_A());
-    AttributeRefAttrPtr aRefAttrB = (*aCIt)->refattr(SketchPlugin_Constraint::ENTITY_B());
-    if (!aRefAttrA || aRefAttrA->isObject() ||
-        !aRefAttrB || aRefAttrB->isObject())
-      continue;
-
-    AttributePtr anAttrA = aRefAttrA->attr();
-    AttributePtr anAttrB = aRefAttrB->attr();
-    if (anAttrA->id() != SketchPlugin_Arc::CENTER_ID() &&
-        anAttrA->id() != SketchPlugin_Circle::CENTER_ID() &&
-        anAttrB->id() != SketchPlugin_Arc::CENTER_ID() &&
-        anAttrB->id() != SketchPlugin_Circle::CENTER_ID()) {
-      aCoincidentPoints.insert(anAttrA);
-      aCoincidentPoints.insert(anAttrB);
+    for (int i = 0; i < CONSTRAINT_ATTR_SIZE; ++i) {
+      AttributeRefAttrPtr aRefAttr = (*aCIt)->refattr(SketchPlugin_Constraint::ATTRIBUTE(i));
+      if (!aRefAttr || aRefAttr->isObject())
+        continue;
+
+      AttributePtr anAttr = aRefAttr->attr();
+      FeaturePtr anOwner = ModelAPI_Feature::feature(anAttr->owner());
+      if (anOwner == theFeature1 || anOwner == theFeature2) {
+        if (anAttr->id() == SketchPlugin_BSplineBase::POLES_ID()) {
+          AttributeIntegerPtr anIndex = (*aCIt)->integer(i == 0 ?
+              SketchPlugin_ConstraintCoincidenceInternal::INDEX_ENTITY_A() :
+              SketchPlugin_ConstraintCoincidenceInternal::INDEX_ENTITY_B());
+          if (anIndex) {
+            if (anIndex->value() == 0)
+              anAttr = anOwner->attribute(SketchPlugin_BSpline::START_ID());
+            else
+              anAttr = anOwner->attribute(SketchPlugin_BSpline::END_ID());
+            if (anAttr)
+              aCoincidentPoints.insert(anAttr);
+          }
+        }
+        else if (anAttr->id() != SketchPlugin_Arc::CENTER_ID() &&
+                 anAttr->id() != SketchPlugin_Circle::CENTER_ID())
+          aCoincidentPoints.insert(anAttr);
+      }
     }
   }
   return aCoincidentPoints;
index 57424b293492e0284afa606dc63c178c4499d1c7..1469eced5e8068b1965cc2e5985e00462e9b0e58 100644 (file)
@@ -40,6 +40,11 @@ public:
   virtual void notify(const FeaturePtr&      theFeature,
                       PlaneGCSSolver_Update* theUpdater);
 
+  /// \brief Tries to remove constraint
+  /// \return \c false, if current constraint contains another SketchPlugin constraints
+  /// (like for multiple coincidence)
+  virtual bool remove();
+
 protected:
   /// \brief Converts SketchPlugin constraint to a list of solver constraints
   virtual void process();
@@ -56,6 +61,7 @@ private:
   double myCurveCurveAngle;
   AttributePtr mySharedPoint;
   EntityWrapperPtr myAuxPoint;
+  ScalarWrapperPtr myAuxParameters[2];
 };
 
 #endif
index 96c0a29687a66e1008520dd4d398e5244df9ce18..4f1f5ee863388733c7b2e335ac606d1e2316c4a5 100644 (file)
@@ -164,10 +164,11 @@ static SolverConstraintPtr move(StoragePtr theStorage,
                                 int theSketchDOF,
                                 bool theEventsBlocked,
                                 Type theFeatureOrPoint,
+                                const EntityWrapperPtr& theSolverEntity,
                                 const std::shared_ptr<GeomAPI_Pnt2d>& theFrom,
                                 const std::shared_ptr<GeomAPI_Pnt2d>& theTo)
 {
-  bool isEntityExists = (theStorage->entity(theFeatureOrPoint).get() != 0);
+  bool isEntityExists = (theSolverEntity.get() != 0);
   if (theSketchDOF == 0 && isEntityExists) {
     // avoid moving elements of fully constrained sketch
     theStorage->refresh();
@@ -196,18 +197,29 @@ bool SketchSolver_Group::moveFeature(FeaturePtr theFeature,
                                      const std::shared_ptr<GeomAPI_Pnt2d>& theFrom,
                                      const std::shared_ptr<GeomAPI_Pnt2d>& theTo)
 {
-  SolverConstraintPtr aConstraint =
-      move(myStorage, mySketchSolver, myDOF, myIsEventsBlocked, theFeature, theFrom, theTo);
+  EntityWrapperPtr anEntity = myStorage->entity(theFeature);
+  SolverConstraintPtr aConstraint = move(myStorage, mySketchSolver, myDOF, myIsEventsBlocked,
+                                         theFeature, anEntity, theFrom, theTo);
   setTemporary(aConstraint);
   return true;
 }
 
 bool SketchSolver_Group::movePoint(AttributePtr theAttribute,
+                                   const int thePointIndex,
                                    const std::shared_ptr<GeomAPI_Pnt2d>& theFrom,
                                    const std::shared_ptr<GeomAPI_Pnt2d>& theTo)
 {
-  SolverConstraintPtr aConstraint =
-      move(myStorage, mySketchSolver, myDOF, myIsEventsBlocked, theAttribute, theFrom, theTo);
+  EntityWrapperPtr anEntity = myStorage->entity(theAttribute);
+  SolverConstraintPtr aConstraint;
+  if (thePointIndex < 0) {
+    aConstraint = move(myStorage, mySketchSolver, myDOF, myIsEventsBlocked,
+                       theAttribute, anEntity, theFrom, theTo);
+  }
+  else {
+    aConstraint = move(myStorage, mySketchSolver, myDOF, myIsEventsBlocked,
+                       std::pair<AttributePtr, int>(theAttribute, thePointIndex), anEntity,
+                       theFrom, theTo);
+  }
   setTemporary(aConstraint);
   return true;
 }
index 45407ddb8f47884f6dbc4efb5a4ede2d0a567a25..d5617155932ec56e073b7ed706b172f2641e7edf 100644 (file)
@@ -90,12 +90,14 @@ class SketchSolver_Group
                    const std::shared_ptr<GeomAPI_Pnt2d>& theTo);
   /** \brief Updates the data corresponding the specified point moved in GUI.
    *         Special kind of Fixed constraints is created.
-   *  \param[in] thePoint the attribute to be updated
-   *  \param[in] theFrom  start point of the movement
-   *  \param[in] theTo    final point of the movement
+   *  \param[in] thePointOrArray the attribute to be updated
+   *  \param[in] thePointIndex   index of moved point in array
+   *  \param[in] theFrom         start point of the movement
+   *  \param[in] theTo           destination point of the movement
    *  \return \c true, if the attribute is really moved
    */
-  bool movePoint(AttributePtr thePoint,
+  bool movePoint(AttributePtr thePointOrArray,
+                 const int thePointIndex,
                  const std::shared_ptr<GeomAPI_Pnt2d>& theFrom,
                  const std::shared_ptr<GeomAPI_Pnt2d>& theTo);
 
index a9d47533e95b12fb5cb18ad2dc632add3e8c13a5..b7b6d498036f4a8f64579d55cf8f56fb57eea235 100644 (file)
@@ -22,6 +22,7 @@
 
 #include <Events_Loop.h>
 #include <GeomDataAPI_Point2D.h>
+#include <GeomDataAPI_Point2DArray.h>
 #include <ModelAPI_Events.h>
 #include <ModelAPI_ResultConstruction.h>
 #include <ModelAPI_Session.h>
@@ -87,6 +88,19 @@ static void featuresOrderedByType(const std::set<ObjectPtr>& theOriginalFeatures
   }
 }
 
+static void setPoint(AttributePtr theAttribute,
+                     const int thePointIndex,
+                     const std::shared_ptr<GeomAPI_Pnt2d> theValue)
+{
+  AttributePoint2DPtr aPointAttr = std::dynamic_pointer_cast<GeomDataAPI_Point2D>(theAttribute);
+  AttributePoint2DArrayPtr aPointArrayAttr =
+      std::dynamic_pointer_cast<GeomDataAPI_Point2DArray>(theAttribute);
+  if (aPointAttr)
+    aPointAttr->setValue(theValue);
+  else if (aPointArrayAttr && thePointIndex >= 0)
+    aPointArrayAttr->setPnt(thePointIndex, theValue);
+}
+
 
 
 // ========================================================
@@ -180,8 +194,8 @@ void SketchSolver_Manager::processEvent(
         std::dynamic_pointer_cast<ModelAPI_ObjectMovedMessage>(theMessage);
 
     ObjectPtr aMovedObject = aMoveMsg->movedObject();
-    std::shared_ptr<GeomDataAPI_Point2D> aMovedPoint =
-        std::dynamic_pointer_cast<GeomDataAPI_Point2D>(aMoveMsg->movedAttribute());
+    AttributePtr aMovedAttribute = aMoveMsg->movedAttribute();
+    int aMovedPoint = aMoveMsg->movedPointIndex();
 
     const std::shared_ptr<GeomAPI_Pnt2d>& aFrom = aMoveMsg->originalPosition();
     const std::shared_ptr<GeomAPI_Pnt2d>& aTo = aMoveMsg->currentPosition();
@@ -192,8 +206,8 @@ void SketchSolver_Manager::processEvent(
           std::dynamic_pointer_cast<SketchPlugin_Feature>(aMovedFeature);
       if (aSketchFeature && !aSketchFeature->isMacro())
         needToResolve = moveFeature(aSketchFeature, aFrom, aTo);
-    } else if (aMovedPoint)
-      needToResolve = moveAttribute(aMovedPoint, aFrom, aTo);
+    } else if (aMovedAttribute)
+      needToResolve = moveAttribute(aMovedAttribute, aMovedPoint, aFrom, aTo);
 
   } else if (theMessage->eventID() == Events_Loop::loop()->eventByName(EVENT_OBJECT_DELETED)) {
     std::shared_ptr<ModelAPI_ObjectDeletedMessage> aDeleteMsg =
@@ -349,7 +363,8 @@ bool SketchSolver_Manager::moveFeature(
 //  Purpose:  move given attribute in appropriate group
 // ============================================================================
 bool SketchSolver_Manager::moveAttribute(
-    const std::shared_ptr<GeomDataAPI_Point2D>& theMovedAttribute,
+    const std::shared_ptr<ModelAPI_Attribute>& theMovedAttribute,
+    const int theMovedPointIndex,
     const std::shared_ptr<GeomAPI_Pnt2d>& theFrom,
     const std::shared_ptr<GeomAPI_Pnt2d>& theTo)
 {
@@ -358,7 +373,7 @@ bool SketchSolver_Manager::moveAttribute(
       std::dynamic_pointer_cast<SketchPlugin_Constraint>(anOwner);
   if (aConstraint)
   {
-    theMovedAttribute->setValue(theTo);
+    setPoint(theMovedAttribute, theMovedPointIndex, theTo);
     Events_Loop::loop()->flush(Events_Loop::eventByName(EVENT_OBJECT_UPDATED));
     return true;
   }
@@ -369,12 +384,12 @@ bool SketchSolver_Manager::moveAttribute(
   if (aSketchFeature)
     aGroup = findGroup(aSketchFeature);
   if (!aGroup) {
-    theMovedAttribute->setValue(theTo);
+    setPoint(theMovedAttribute, theMovedPointIndex, theTo);
     return false;
   }
 
   aGroup->blockEvents(true);
-  return aGroup->movePoint(theMovedAttribute, theFrom, theTo);
+  return aGroup->movePoint(theMovedAttribute, theMovedPointIndex, theFrom, theTo);
 }
 
 // ============================================================================
index 78ddaefbb26c2d99811bb51d0462d4564519ace8..33aa152a513fefc54e4ab734f9afc366505396f2 100644 (file)
@@ -85,12 +85,14 @@ protected:
                    const std::shared_ptr<GeomAPI_Pnt2d>& theToPoint);
 
   /** \brief Move feature using its moved attribute
-   *  \param[in] theMovedAttribute dragged point attribute of sketch feature
-   *  \param[in] theFromPoint      original position of the moved point
-   *  \param[in] theToPoint        prefereble position (current position of the mouse)
+   *  \param[in] theMovedAttribute  dragged point (array of points) attribute of sketch feature
+   *  \param[in] theMovedPointIndex index of dragged point in an array (-1 otherwise)
+   *  \param[in] theFromPoint       original position of the moved point
+   *  \param[in] theToPoint         prefereble position (current position of the mouse)
    *  \return \c true if the attribute owner has been changed successfully
    */
-  bool moveAttribute(const std::shared_ptr<GeomDataAPI_Point2D>& theMovedAttribute,
+  bool moveAttribute(const std::shared_ptr<ModelAPI_Attribute>& theMovedAttribute,
+                     const int theMovedPointIndex,
                      const std::shared_ptr<GeomAPI_Pnt2d>& theFromPoint,
                      const std::shared_ptr<GeomAPI_Pnt2d>& theToPoint);
 
index a3e0bf2d004f512ec1a7ebf30f67fe1839ca04c9..0099869f50e86fd6cc92f88e407822eabf49d33e 100644 (file)
@@ -225,8 +225,11 @@ void XGUI_PropertyPanel::createContentPanel(FeaturePtr theFeature)
     QString aXmlRepr = anOperation->getDescription()->xmlRepresentation();
 
     ModuleBase_WidgetFactory aFactory(aXmlRepr.toStdString(), myOperationMgr->workshop());
-    aFactory.createPanel(contentWidget(), theFeature);
-    /// Apply button should be update if the feature was modified by the panel
+    ModuleBase_PageBase* aPage = contentWidget();
+    aFactory.createPanel(aPage, theFeature);
+    // update model widgets if exist
+    setModelWidgets(aPage->modelWidgets());
+    // Apply button should be update if the feature was modified by the panel
     myOperationMgr->onValidateOperation();
   }
   ModuleBase_OperationFeature* aFeatureOp =