From: azv Date: Thu, 23 Jan 2020 12:06:08 +0000 (+0300) Subject: Update processing of Tangency constraint. Provide unit-test. X-Git-Tag: V9_5_0a1~54^2~15 X-Git-Url: http://git.salome-platform.org/gitweb/?a=commitdiff_plain;h=1f38083fe812d086f053a46910c2cec3c8167868;p=modules%2Fshaper.git Update processing of Tangency constraint. Provide unit-test. --- diff --git a/src/GeomAPI/GeomAPI.i b/src/GeomAPI/GeomAPI.i index 29d051d85..4b264be70 100644 --- a/src/GeomAPI/GeomAPI.i +++ b/src/GeomAPI/GeomAPI.i @@ -105,17 +105,18 @@ } } -%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 std::shared_ptr shared_ptr_cast(std::shared_ptr theObject); +%template(shapeToEdge) shared_ptr_cast; + // all supported interfaces %include "GeomAPI_Interface.h" diff --git a/src/GeomAPI/GeomAPI_BSpline2d.cpp b/src/GeomAPI/GeomAPI_BSpline2d.cpp index d23633dfa..58eb68580 100644 --- a/src/GeomAPI/GeomAPI_BSpline2d.cpp +++ b/src/GeomAPI/GeomAPI_BSpline2d.cpp @@ -22,6 +22,9 @@ #include #include +#include +#include +#include #define MY_BSPLINE (*(implPtr())) @@ -156,6 +159,14 @@ std::list GeomAPI_BSpline2d::mults() const return std::list(aBSplMults.begin(), aBSplMults.end()); } +const bool GeomAPI_BSpline2d::parameter(const std::shared_ptr thePoint, + const double theTolerance, + double& theParameter) const +{ + return GeomLib_Tool::Parameter(MY_BSPLINE, thePoint->impl(), + theTolerance, theParameter) == Standard_True; +} + void GeomAPI_BSpline2d::D0(const double theU, std::shared_ptr& thePoint) { gp_Pnt2d aPnt; diff --git a/src/GeomAPI/GeomAPI_BSpline2d.h b/src/GeomAPI/GeomAPI_BSpline2d.h index 0960bd403..260577b72 100644 --- a/src/GeomAPI/GeomAPI_BSpline2d.h +++ b/src/GeomAPI/GeomAPI_BSpline2d.h @@ -60,6 +60,18 @@ public: /// Multiplicities of the knots GEOMAPI_EXPORT std::list 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 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& thePoint); diff --git a/src/GeomAPI/GeomAPI_Edge.cpp b/src/GeomAPI/GeomAPI_Edge.cpp index fdd99efb8..03868875b 100644 --- a/src/GeomAPI/GeomAPI_Edge.cpp +++ b/src/GeomAPI/GeomAPI_Edge.cpp @@ -307,13 +307,26 @@ bool GeomAPI_Edge::isEqual(const std::shared_ptr theEdge) const return true; } -// LCOV_EXCL_START +void GeomAPI_Edge::setRange(const double& theFirst, const double& theLast) +{ + TopoDS_Edge anEdge = impl(); + 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(this)->impl(); Handle(Geom_Curve) aCurve = BRep_Tool::Curve((const TopoDS_Edge&)aShape, theFirst, theLast); } +// LCOV_EXCL_START bool GeomAPI_Edge::isInPlane(std::shared_ptr thePlane) const { double aFirst, aLast; diff --git a/src/GeomAPI/GeomAPI_Edge.h b/src/GeomAPI/GeomAPI_Edge.h index b319cc26d..3e926d8d0 100644 --- a/src/GeomAPI/GeomAPI_Edge.h +++ b/src/GeomAPI/GeomAPI_Edge.h @@ -98,6 +98,10 @@ public: GEOMAPI_EXPORT bool isEqual(const std::shared_ptr 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; diff --git a/src/GeomAPI/GeomAPI_swig.h b/src/GeomAPI/GeomAPI_swig.h index 065840804..fca55e793 100644 --- a/src/GeomAPI/GeomAPI_swig.h +++ b/src/GeomAPI/GeomAPI_swig.h @@ -68,4 +68,10 @@ #include #include + template + std::shared_ptr shared_ptr_cast(std::shared_ptr theObject) + { + return std::dynamic_pointer_cast(theObject); + } + #endif /* SRC_GEOMAPI_GEOMAPI_SWIG_H_ */ diff --git a/src/ModelAPI/ModelAPI_swig.h b/src/ModelAPI/ModelAPI_swig.h index de4312c62..0c4f99e03 100644 --- a/src/ModelAPI/ModelAPI_swig.h +++ b/src/ModelAPI/ModelAPI_swig.h @@ -68,10 +68,4 @@ #include #include - template - std::shared_ptr shared_ptr_cast(std::shared_ptr theObject) - { - return std::dynamic_pointer_cast(theObject); - } - #endif /* SRC_MODELAPI_MODELAPI_SWIG_H_ */ diff --git a/src/SketchPlugin/CMakeLists.txt b/src/SketchPlugin/CMakeLists.txt index d3fbe20bf..8063b10ca 100644 --- a/src/SketchPlugin/CMakeLists.txt +++ b/src/SketchPlugin/CMakeLists.txt @@ -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 index 000000000..0937fe247 --- /dev/null +++ b/src/SketchPlugin/Test/TestConstraintTangentBSpline.py @@ -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() diff --git a/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_GeoExtensions.cpp b/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_GeoExtensions.cpp index ade6eeaa7..480dbd58d 100644 --- a/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_GeoExtensions.cpp +++ b/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_GeoExtensions.cpp @@ -18,6 +18,7 @@ // #include +#include #include #include @@ -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 aValue; + std::shared_ptr 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); diff --git a/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_GeoExtensions.h b/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_GeoExtensions.h index 9bc62e327..c83e1a9dd 100644 --- a/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_GeoExtensions.h +++ b/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_GeoExtensions.h @@ -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(); diff --git a/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_Tools.cpp b/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_Tools.cpp index 5224c8443..939b7a263 100644 --- a/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_Tools.cpp +++ b/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_Tools.cpp @@ -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 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 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); -} diff --git a/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_Tools.h b/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_Tools.h index b80f254f4..846db6d21 100644 --- a/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_Tools.h +++ b/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_Tools.h @@ -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 { diff --git a/src/SketchSolver/SketchSolver_ConstraintTangent.cpp b/src/SketchSolver/SketchSolver_ConstraintTangent.cpp index 18d95f8ec..7b4b85a34 100644 --- a/src/SketchSolver/SketchSolver_ConstraintTangent.cpp +++ b/src/SketchSolver/SketchSolver_ConstraintTangent.cpp @@ -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 aStorage = + std::dynamic_pointer_cast(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(); +} + diff --git a/src/SketchSolver/SketchSolver_ConstraintTangent.h b/src/SketchSolver/SketchSolver_ConstraintTangent.h index 57424b293..1469eced5 100644 --- a/src/SketchSolver/SketchSolver_ConstraintTangent.h +++ b/src/SketchSolver/SketchSolver_ConstraintTangent.h @@ -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