Salome HOME
Update processing of Tangency constraint. Provide unit-test.
authorazv <azv@opencascade.com>
Thu, 23 Jan 2020 12:06:08 +0000 (15:06 +0300)
committerazv <azv@opencascade.com>
Thu, 23 Jan 2020 12:06:08 +0000 (15:06 +0300)
15 files changed:
src/GeomAPI/GeomAPI.i
src/GeomAPI/GeomAPI_BSpline2d.cpp
src/GeomAPI/GeomAPI_BSpline2d.h
src/GeomAPI/GeomAPI_Edge.cpp
src/GeomAPI/GeomAPI_Edge.h
src/GeomAPI/GeomAPI_swig.h
src/ModelAPI/ModelAPI_swig.h
src/SketchPlugin/CMakeLists.txt
src/SketchPlugin/Test/TestConstraintTangentBSpline.py [new file with mode: 0644]
src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_GeoExtensions.cpp
src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_GeoExtensions.h
src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_Tools.cpp
src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_Tools.h
src/SketchSolver/SketchSolver_ConstraintTangent.cpp
src/SketchSolver/SketchSolver_ConstraintTangent.h

index 29d051d85d1f4f73b4078387d2d0c54bc30a8076..4b264be7067885214662a605336435f05a92b4f1 100644 (file)
   }
 }
 
-%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"
index d23633dfa4ccf6474a754a771c6932f14b3c4819..58eb68580d5bfb585a2ac9f0e485b90a54f5cc6d 100644 (file)
@@ -22,6 +22,9 @@
 #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>()))
 
@@ -156,6 +159,14 @@ std::list<int> GeomAPI_BSpline2d::mults() const
   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;
index 0960bd403c4d877646b4ceeaf551a6dfc0ad8d95..260577b729a4443fa1bc1a9af12cd715c9b0c965 100644 (file)
@@ -60,6 +60,18 @@ public:
   /// 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);
 
index fdd99efb82b18e86c450512833c184739457e3f9..03868875ba53de38688ae1abddfed3e399a942e7 100644 (file)
@@ -307,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 b319cc26d783f6538920606a6c8851bead9f30f7..3e926d8d04e5d109cb512957bc4eeed805425c10 100644 (file)
@@ -98,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 065840804a16944f8d56dc1bfd57d13ee0a301ea..fca55e7932e8d8e7ae3275090816366d4d8397ce 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_GEOMAPI_GEOMAPI_SWIG_H_ */
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 d3fbe20bf87d4add51741cc87968a4e13f8aef4e..8063b10ca867b4fed2c0ff79012c7b5c61abe104 100644 (file)
@@ -261,6 +261,7 @@ ADD_UNIT_TESTS(
   TestConstraintRadius.py
   TestConstraintRadiusFailure.py
   TestConstraintTangent.py
+  TestConstraintTangentBSpline.py
   TestConstraintTangentEllipse.py
   TestConstraintTangentEllipticArc.py
   TestConstraintVertical.py
diff --git a/src/SketchPlugin/Test/TestConstraintTangentBSpline.py b/src/SketchPlugin/Test/TestConstraintTangentBSpline.py
new file mode 100644 (file)
index 0000000..0937fe2
--- /dev/null
@@ -0,0 +1,475 @@
+# 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(self.myPoles, 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([(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(10, -10, 90, 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(aLine.startPoint(), self.myControlLines[0])
+    self.assertPointLineDistance(aLine.endPoint(), self.myControlLines[0])
+
+  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.assertTangentLineCircle(SketchAPI_Line(self.myControlLines[0]), aCircle)
+
+  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.assertTangentLineCircle(SketchAPI_Line(self.myControlLines[-1]), anArc)
+
+  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([(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([(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()
index ade6eeaa773dd1330199f8f787eef5124cb36ab0..480dbd58d07333c444db93468748032d4b79a8b0 100644 (file)
@@ -18,6 +18,7 @@
 //
 
 #include <PlaneGCSSolver_GeoExtensions.h>
+#include <PlaneGCSSolver_Tools.h>
 
 #include <GeomAPI_BSpline2d.h>
 #include <GeomAPI_Pnt2d.h>
@@ -40,6 +41,35 @@ DeriVector2 BSplineImpl::Value(double u, double du, double* derivparam)
   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);
index 9bc62e327130e6f85db57487e848d07c37e642dd..c83e1a9ddde3eacbfb8f664161ffcc2439e44020 100644 (file)
@@ -34,6 +34,7 @@ namespace GCS {
   {
   public:
     virtual DeriVector2 Value(double u, double du, double* derivparam = 0);
+    virtual DeriVector2 CalculateNormal(Point &p, double* derivparam = 0);
 
     virtual BSplineImpl* Copy();
 
index 5224c8443ef0e02f1922bb716bea77423e2f186e..939b7a2636998d52522af328d8b0d168aeede0f5 100644 (file)
@@ -150,9 +150,6 @@ static GCS::SET_pD ellipseParameters(const EdgeWrapperPtr& theEllipse);
 static GCS::SET_pD ellipticArcParameters(const EdgeWrapperPtr& theEllipticArc);
 static GCS::SET_pD bsplineParameters(const EdgeWrapperPtr& theEdge);
 
-static double distance(const GCS::Point& thePnt1, const GCS::Point& thePnt2);
-
-
 
 
 
@@ -502,6 +499,13 @@ bool PlaneGCSSolver_Tools::updateValue(const double& theSource, double& theDest,
 }
 
 
+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);
+}
+
 
 
 
@@ -828,7 +832,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 =
@@ -843,7 +847,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 =
@@ -972,10 +976,3 @@ GCS::SET_pD bsplineParameters(const EdgeWrapperPtr& theEdge)
 
   return aParams;
 }
-
-double 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);
-}
index b80f254f4adca180223f809877e70a138ad52609..846db6d21ba3b3c0fdb8adbfd5748e6d89274d08 100644 (file)
@@ -106,6 +106,8 @@ namespace PlaneGCSSolver_Tools
   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
   {
index 18d95f8ec9d5c533dcaf88caf62f091719155b7b..7b4b85a34169d690d98a3bbdf4938ccfce14f041 100644 (file)
@@ -112,8 +112,13 @@ 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)
@@ -128,7 +133,7 @@ void SketchSolver_ConstraintTangent::rebuild()
       ++aNbLines;
     else if ((*anEntIt)->type() == ENTITY_ARC || (*anEntIt)->type() == ENTITY_CIRCLE)
       ++aNbCircles;
-    else if ((*anEntIt)->type() == ENTITY_ELLIPSE || (*anEntIt)->type() == ENTITY_ELLIPTIC_ARC)
+    else if ((*anEntIt)->type() == ENTITY_ELLIPSE || (*anEntIt)->type() == ENTITY_ELLIPTIC_ARC || (*anEntIt)->type() == ENTITY_BSPLINE)
       ++aNbEllipses;
   }
 
@@ -179,13 +184,21 @@ void SketchSolver_ConstraintTangent::rebuild()
     myAuxPoint.reset(new PlaneGCSSolver_PointWrapper(aPoint));
     aSharedPointEntity = myAuxPoint;
 
+    // create auxiliary parameters for coincidence with B-spline
+    if (myAttributes.front()->type() == ENTITY_BSPLINE)
+      myAuxParameters[0].reset(new PlaneGCSSolver_ScalarWrapper(aStorage->createParameter()));
+    if (myAttributes.back()->type() == ENTITY_BSPLINE)
+      myAuxParameters[1].reset(new PlaneGCSSolver_ScalarWrapper(aStorage->createParameter()));
+
     // 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);
+        CONSTRAINT_PT_ON_CURVE, myAuxParameters[0],
+        aSharedPointEntity, aDummy, myAttributes.front(), aDummy);
     anAuxConstraints = aCoincidence->constraints();
     aCoincidence = PlaneGCSSolver_Tools::createConstraint(ConstraintPtr(),
-        CONSTRAINT_PT_ON_CURVE, aDummy, aSharedPointEntity, aDummy, myAttributes.back(), aDummy);
+        CONSTRAINT_PT_ON_CURVE, myAuxParameters[1],
+        aSharedPointEntity, aDummy, myAttributes.back(), aDummy);
     anAuxConstraints.insert(anAuxConstraints.end(),
         aCoincidence->constraints().begin(), aCoincidence->constraints().end());
   }
@@ -282,6 +295,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();
+}
+
 
 
 
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