Salome HOME
Unit tests for B-splines in the sketcher.
authorazv <azv@opencascade.com>
Mon, 20 Jan 2020 13:31:16 +0000 (16:31 +0300)
committerazv <azv@opencascade.com>
Mon, 20 Jan 2020 13:31:16 +0000 (16:31 +0300)
18 files changed:
src/GeomAPI/GeomAPI.i
src/GeomAPI/GeomAPI_BSpline2d.cpp
src/GeomAPI/GeomAPI_Edge.cpp
src/GeomAPI/GeomAPI_Edge.h
src/GeomAPI/GeomAPI_swig.h
src/GeomDataAPI/GeomDataAPI.i
src/GeomDataAPI/GeomDataAPI_swig.h
src/ModelHighAPI/ModelHighAPI.i
src/ModelHighAPI/ModelHighAPI_Dumper.cpp
src/ModelHighAPI/ModelHighAPI_Dumper.h
src/SketchAPI/SketchAPI_Sketch.cpp
src/SketchPlugin/CMakeLists.txt
src/SketchPlugin/SketchPlugin_BSpline.cpp
src/SketchPlugin/Test/TestCreateBSpline.py [new file with mode: 0644]
src/SketchPlugin/Test/TestMoveBSpline.py [new file with mode: 0644]
src/SketchPlugin/Test/TestPresentation.py
src/SketchPlugin/Test/TestProjectionBSpline.py [new file with mode: 0644]
src/SketchPlugin/Test/TestRemoveBSpline.py [new file with mode: 0644]

index 4c4110155cfe305b7d424d90bc1b9dfd97648cb4..29d051d85d1f4f73b4078387d2d0c54bc30a8076 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)
 %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"
index de07f9299e1437c93baffa05d0c254cf61268d5b..d23633dfa4ccf6474a754a771c6932f14b3c4819 100644 (file)
 #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,
@@ -34,6 +41,9 @@ static Handle_Geom2d_BSplineCurve* newBSpline2d(
     const int theDegree,
     const bool thePeriodic)
 {
+  if (theKnots.empty() || theMults.empty())
+    return newBSpline2d(thePoles, theWeights, theDegree, thePeriodic);
+
   // collect arrays of poles, weights, knots and multiplicities
   TColgp_Array1OfPnt2d aPoles(1, (int)thePoles.size());
   TColStd_Array1OfReal aWeights(1, (int)theWeights.size());
@@ -62,7 +72,7 @@ static Handle_Geom2d_BSplineCurve* newBSpline2d(
   return new Handle_Geom2d_BSplineCurve(aCurve);
 }
 
-static Handle_Geom2d_BSplineCurve* newBSpline2d(
+Handle_Geom2d_BSplineCurve* newBSpline2d(
     const std::list<std::shared_ptr<GeomAPI_Pnt2d> >& thePoles,
     const std::list<double>& theWeights,
     const int theDegree,
index c4504272b3ccbea972eb5606d69f5288aab6da0a..fdd99efb82b18e86c450512833c184739457e3f9 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>();
index fdf82ed6fb57954343676c3969fa3805791f44a1..b319cc26d783f6538920606a6c8851bead9f30f7 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();
index 8c5472e93649d10d94fffd67657c34a13ba88cc3..065840804a16944f8d56dc1bfd57d13ee0a301ea 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"
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>;
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 442e1cd2a0ed2bbcc5ca16e215aa55665085aa6c..30fb144b34a2ac9c177abcfb15c99bc4067aa1d0 100644 (file)
 
 %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;
 }
 
 
+%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 677e31be7b524cbf64ad988ca713e921cd158d88..1d655bbdfc93010dd1527512ef33200f9c9e50df 100644 (file)
@@ -1140,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)
 {
index f9114176d8377d25d4e06357b3dff4f936e8fc07..90242c3aac90f0a2fd0a2f86b9ff6a1d05d13af5 100644 (file)
@@ -42,6 +42,7 @@ class ModelAPI_Attribute;
 class ModelAPI_AttributeBoolean;
 class ModelAPI_AttributeDouble;
 class ModelAPI_AttributeDoubleArray;
+class ModelAPI_AttributeIntArray;
 class ModelAPI_AttributeInteger;
 class ModelAPI_AttributeRefAttr;
 class ModelAPI_AttributeRefAttrList;
@@ -274,6 +275,9 @@ 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);
index d1d2967b1a5bfe0a48c76092e565601ecccf7a0e..f64f93fc682f76665cea02b67dc4a809d0c227f3 100644 (file)
@@ -1219,7 +1219,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);
@@ -1238,6 +1250,8 @@ 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())
+      aMiddlePoint = middlePointOnBSpline(aFeature, theSketch);
   }
   return aMiddlePoint;
 }
@@ -1252,7 +1266,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 cf6523d41558a395882e1294e0826b202eca678b..e2ad2a0434609604d74c676f8c8683f396909462 100644 (file)
@@ -277,6 +277,7 @@ ADD_UNIT_TESTS(
   TestCreateArcByThreePoints.py
   TestCreateArcByTransversalLine.py
   TestCreateArcChangeType.py
+  TestCreateBSpline.py
   TestCreateCircleByCenterAndPassed.py
   TestCreateCircleByThreePoints.py
   TestCreateCircleChangeType.py
@@ -312,12 +313,14 @@ ADD_UNIT_TESTS(
   TestMultiTranslation.py
   TestPresentation.py
   TestProjection.py
+  TestProjectionBSpline.py
   TestProjectionEllipse.py
   TestProjectionEllipticArc.py
   TestProjectionIntoResult.py
   TestProjectionUpdate.py
   TestRectangle.py
   TestRemainingDoF.py
+  TestRemoveBSpline.py
   TestRemoveEllipse.py
   TestRemoveEllipticArc.py
   TestRemoveSketch.py
@@ -355,6 +358,7 @@ ADD_UNIT_TESTS(
 if(${SKETCHER_CHANGE_RADIUS_WHEN_MOVE})
   ADD_UNIT_TESTS(
     TestMoveArc.py
+    TestMoveBSpline.py
     TestMoveCircle.py
     TestMoveEllipse.py
     TestMoveEllipticArc.py
index e8e84177d5480547fde7db0e3f2003408c9331d7..f4aee05a9b67eef678f130a338e1f45148f7cf5d 100644 (file)
@@ -152,9 +152,14 @@ void SketchPlugin_BSpline::attributeChanged(const std::string& theID) {
 ////      fillCharacteristicPoints();
 ////    }
   }
-////  else if (theID == CENTER_ID() || theID == FIRST_FOCUS_ID() ||
-////           theID == START_POINT_ID() || theID == END_POINT_ID())
-////    fillCharacteristicPoints();
+  else 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 if (theID == REVERSED_ID() && myParamDelta == 0.0)
 ////    myParamDelta = 2.0 * PI;
 }
diff --git a/src/SketchPlugin/Test/TestCreateBSpline.py b/src/SketchPlugin/Test/TestCreateBSpline.py
new file mode 100644 (file)
index 0000000..413c84b
--- /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(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(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(self.myDegree, 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(self.myPoles, [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(self.myDegree,
+                                            self.myPolesCoordinates,
+                                            [1, 1, 1, 1, 1, 1, 0.957903314642061, 0.95790331464206, 1, 1, 1, 1, 1, 1],
+                                            [-494.543457494654, 500, 507.372773368102, 1501.91623086297],
+                                            [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(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(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(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(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/TestMoveBSpline.py b/src/SketchPlugin/Test/TestMoveBSpline.py
new file mode 100644 (file)
index 0000000..eb71bff
--- /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(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()
index b3400e70e822c20d0f326ab14451918e04fec697..2b675b4ceeaec9263cd121585b7761f00982fbe7 100644 (file)
@@ -156,3 +156,22 @@ 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()
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/TestRemoveBSpline.py b/src/SketchPlugin/Test/TestRemoveBSpline.py
new file mode 100644 (file)
index 0000000..d9f4461
--- /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, theNbEllipses, theNbInternalConstraints):
+    model.testNbSubFeatures(theSketch, "SketchPoint", theNbPoints)
+    model.testNbSubFeatures(theSketch, "SketchLine", theNbLines)
+    model.testNbSubFeatures(theSketch, "SketchBSpline", theNbEllipses)
+    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(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(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())