From 0db912d7f004c9858b06e590a895420780ae73fc Mon Sep 17 00:00:00 2001 From: azv Date: Wed, 4 Sep 2019 08:57:55 +0300 Subject: [PATCH] Task 2.11. Ability to impose a midpoint on an arc (refers issue #3002) --- src/SketchPlugin/CMakeLists.txt | 1 + src/SketchPlugin/SketchPlugin_Validators.cpp | 3 +- .../Test/TestConstraintMiddlePointOnArc.py | 252 ++++++++++++++++++ src/SketchPlugin/plugin-Sketch.xml | 6 +- .../PlaneGCSSolver/CMakeLists.txt | 2 + .../PlaneGCSSolver_AttributeBuilder.cpp | 13 + .../PlaneGCSSolver_BooleanWrapper.cpp | 25 ++ .../PlaneGCSSolver_BooleanWrapper.h | 51 ++++ .../PlaneGCSSolver/PlaneGCSSolver_Defs.h | 1 + .../PlaneGCSSolver_EdgeWrapper.cpp | 3 +- .../PlaneGCSSolver_EdgeWrapper.h | 8 + .../PlaneGCSSolver_FeatureBuilder.cpp | 30 ++- .../PlaneGCSSolver/PlaneGCSSolver_Storage.cpp | 90 ++++--- .../PlaneGCSSolver/PlaneGCSSolver_Storage.h | 5 + .../PlaneGCSSolver/PlaneGCSSolver_Tools.cpp | 42 ++- .../SketchSolver_ConstraintMiddle.cpp | 66 ++++- .../SketchSolver_ConstraintMiddle.h | 4 + src/SketchSolver/SketchSolver_Group.cpp | 5 +- src/SketchSolver/SketchSolver_Storage.h | 5 + 19 files changed, 537 insertions(+), 75 deletions(-) create mode 100644 src/SketchPlugin/Test/TestConstraintMiddlePointOnArc.py create mode 100644 src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_BooleanWrapper.cpp create mode 100644 src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_BooleanWrapper.h diff --git a/src/SketchPlugin/CMakeLists.txt b/src/SketchPlugin/CMakeLists.txt index cd9bc43fb..26cf523f2 100644 --- a/src/SketchPlugin/CMakeLists.txt +++ b/src/SketchPlugin/CMakeLists.txt @@ -212,6 +212,7 @@ ADD_UNIT_TESTS( TestConstraintHorizontalValidator.py TestConstraintLength.py TestConstraintMiddlePoint.py + TestConstraintMiddlePointOnArc.py TestConstraintParallel.py TestConstraintPerpendicular.py TestConstraintPerpendicularArcLine.py diff --git a/src/SketchPlugin/SketchPlugin_Validators.cpp b/src/SketchPlugin/SketchPlugin_Validators.cpp index a0cbf6805..fb9be5197 100644 --- a/src/SketchPlugin/SketchPlugin_Validators.cpp +++ b/src/SketchPlugin/SketchPlugin_Validators.cpp @@ -778,7 +778,8 @@ bool SketchPlugin_MiddlePointAttrValidator::isValid(const AttributePtr& theAttri if (aFeature->getKind() == SketchPlugin_Point::ID()) ++aNbPoints; - else if (aFeature->getKind() == SketchPlugin_Line::ID()) + else if (aFeature->getKind() == SketchPlugin_Line::ID() || + aFeature->getKind() == SketchPlugin_Arc::ID()) ++aNbLines; } } diff --git a/src/SketchPlugin/Test/TestConstraintMiddlePointOnArc.py b/src/SketchPlugin/Test/TestConstraintMiddlePointOnArc.py new file mode 100644 index 000000000..268893231 --- /dev/null +++ b/src/SketchPlugin/Test/TestConstraintMiddlePointOnArc.py @@ -0,0 +1,252 @@ +# Copyright (C) 2017-2019 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 middle point on an arc +""" + +import unittest +import math + +from salome.shaper import model +from GeomAPI import GeomAPI_Dir2d + +__updated__ = "2019-09-03" + +class TestMiddlePointOnArc(unittest.TestCase): + def setUp(self): + model.begin() + self.myTestPassed = True + self.myDocument = model.moduleDocument() + self.mySketch = model.addSketch(self.myDocument, model.defaultPlane("XOY")) + self.myArc = self.mySketch.addArc(50, 50, 70, 50, 50, 70, False) + self.myLine = self.mySketch.addLine(55, 60, 50, 0) + self.myDOF = 9 + model.do() + self.checkDOF() + + def tearDown(self): + if self.myTestPassed: + model.assertArcValidity(self.myArc) + self.checkMiddlePoint(self.myLine.startPoint(), self.myArc) + self.checkDOF() + model.end() + assert(model.checkPythonDump()) + + def checkDOF(self): + self.assertEqual(model.dof(self.mySketch), self.myDOF) + + def checkMiddlePoint(self, thePoint, theArc): + self.myTestPassed = False + # check point on arc + dist = thePoint.pnt().distance(theArc.center().pnt()) + NB_DIGITS = 7 - math.floor(math.log10(theArc.radius().value())) + self.assertAlmostEqual(dist, theArc.radius().value(), NB_DIGITS) + # check middle point + dirPC = GeomAPI_Dir2d(thePoint.x() - theArc.center().x(), + thePoint.y() - theArc.center().y()) + dirSC = GeomAPI_Dir2d(theArc.startPoint().x() - theArc.center().x(), + theArc.startPoint().y() - theArc.center().y()) + dirEC = GeomAPI_Dir2d(theArc.endPoint().x() - theArc.center().x(), + theArc.endPoint().y() - theArc.center().y()) + angleSP = dirSC.angle(dirPC) + anglePE = dirPC.angle(dirEC) + self.assertAlmostEqual(angleSP, anglePE) + self.assertEqual(angleSP < 0, theArc.reversed().value()) + self.myTestPassed = True + + def rotatePoint(self, thePoint, theCenter, theAngle): + dirX = thePoint.x() - theCenter.x() + dirY = thePoint.y() - theCenter.y() + newX = theCenter.x() + dirX * math.cos(theAngle) - dirY * math.sin(theAngle) + newY = theCenter.y() + dirX * math.sin(theAngle) + dirY * math.cos(theAngle) + self.mySketch.move(thePoint, newX, newY) + + def moveArc(self): + ANGLE_STEP = math.pi * 5.0 / 180.0 + ANGLE_THRESHOLD = math.pi + # move start point of the arc clockwise + fullAngle = 0.0 + while fullAngle < ANGLE_THRESHOLD: + self.rotatePoint(self.myArc.startPoint(), self.myArc.center(), -ANGLE_STEP) + model.do() + self.checkMiddlePoint(self.myLine.startPoint(), self.myArc) + fullAngle += ANGLE_STEP + # move start point of the arc conterclockwise + fullAngle = 0.0 + while fullAngle < ANGLE_THRESHOLD: + self.rotatePoint(self.myArc.startPoint(), self.myArc.center(), ANGLE_STEP) + model.do() + self.checkMiddlePoint(self.myLine.startPoint(), self.myArc) + fullAngle += ANGLE_STEP + + # move end point of the arc clockwise + fullAngle = 0.0 + while fullAngle < ANGLE_THRESHOLD: + self.rotatePoint(self.myArc.endPoint(), self.myArc.center(), -ANGLE_STEP) + model.do() + self.checkMiddlePoint(self.myLine.startPoint(), self.myArc) + fullAngle += ANGLE_STEP + # move end point of the arc conterclockwise + fullAngle = 0.0 + while fullAngle < ANGLE_THRESHOLD: + self.rotatePoint(self.myArc.endPoint(), self.myArc.center(), ANGLE_STEP) + model.do() + self.checkMiddlePoint(self.myLine.startPoint(), self.myArc) + fullAngle += ANGLE_STEP + + # move center of the arc + DELTA = [1.0, 1.0] + for i in range(0, 40): + if i == 10 or i == 30: + DELTA = [-DELTA[0], -DELTA[1]] + self.mySketch.move(self.myArc.center(), self.myArc.center().x() + DELTA[0], self.myArc.center().y() + DELTA[1]) + model.do() + self.checkMiddlePoint(self.myLine.startPoint(), self.myArc) + DELTA = [-1.0, 1.0] + for i in range(0, 40): + if i == 10 or i == 30: + DELTA = [-DELTA[0], -DELTA[1]] + self.mySketch.move(self.myArc.center(), self.myArc.center().x() + DELTA[0], self.myArc.center().y() + DELTA[1]) + model.do() + self.checkMiddlePoint(self.myLine.startPoint(), self.myArc) + + def moveLine(self): + DELTA = [1.0, 0.0] + for i in range(0, 40): + if i == 10 or i == 30: + DELTA = [-DELTA[0], -DELTA[1]] + self.mySketch.move(self.myLine.startPoint(), self.myLine.startPoint().x() + DELTA[0], self.myLine.startPoint().y() + DELTA[1]) + model.do() + self.checkMiddlePoint(self.myLine.startPoint(), self.myArc) + DELTA = [0.0, 1.0] + for i in range(0, 40): + if i == 10 or i == 30: + DELTA = [-DELTA[0], -DELTA[1]] + self.mySketch.move(self.myLine.startPoint(), self.myLine.startPoint().x() + DELTA[0], self.myLine.startPoint().y() + DELTA[1]) + model.do() + self.checkMiddlePoint(self.myLine.startPoint(), self.myArc) + + + def test_middle_point_PA(self): + """ Test 1. Set middle point constraint (point is the first argument) + """ + self.mySketch.setMiddlePoint(self.myLine.startPoint(), self.myArc.results()[1]) + self.myDOF -= 2 + model.do() + + def test_middle_point_AP(self): + """ Test 2. Set middle point constraint (point is the second argument) + """ + self.mySketch.setMiddlePoint(self.myArc.results()[1], self.myLine.startPoint()) + self.myDOF -= 2 + model.do() + + def test_coincident_middle_point(self): + """ Test 3. Set middle point constraint for the point already coincident with the arc + """ + self.mySketch.setCoincident(self.myLine.startPoint(), self.myArc.results()[1]) + model.do() + self.mySketch.setMiddlePoint(self.myLine.startPoint(), self.myArc.results()[1]) + self.myDOF -= 2 + model.do() + + def test_middle_point_coincident(self): + """ Test 4. Set concidence of the point and the arc which are already constrained with middle point + """ + self.mySketch.setMiddlePoint(self.myLine.startPoint(), self.myArc.results()[1]) + model.do() + self.mySketch.setCoincident(self.myLine.startPoint(), self.myArc.results()[1]) + self.myDOF -= 2 + model.do() + + @unittest.expectedFailure + def test_middle_point_limitation(self): + """ Test 5. Check middle point fails if the point's coordinates are equal to the arc boundary point + """ + self.myLine.startPoint().setValue(self.myArc.endPoint().pnt()) + model.do() + coincidence = self.mySketch.setCoincident(self.myLine.startPoint(), self.myArc.results()[1]) + self.mySketch.setMiddlePoint(self.myLine.startPoint(), self.myArc.results()[1]) + self.myDOF -= 2 + model.do() + # this check will fail due to the limitation of PlanGCS + self.checkMiddlePoint(self.myLine.startPoint(), self.myArc) + + def test_middle_point_move_arc(self): + """ Test 6. Set middle point constraint and move arc + """ + self.mySketch.setMiddlePoint(self.myLine.startPoint(), self.myArc.results()[1]) + self.myDOF -= 2 + model.do() + self.checkMiddlePoint(self.myLine.startPoint(), self.myArc) + self.moveArc() + + def test_middle_point_coincidence_move_arc(self): + """ Test 7. Set coincidence and middle point constraint and move arc + """ + self.mySketch.setCoincident(self.myLine.startPoint(), self.myArc.results()[1]) + model.do() + self.mySketch.setMiddlePoint(self.myLine.startPoint(), self.myArc.results()[1]) + self.myDOF -= 2 + model.do() + self.checkMiddlePoint(self.myLine.startPoint(), self.myArc) + self.moveArc() + + def test_middle_point_move_line(self): + """ Test 8. Set middle point constraint and move line + """ + self.mySketch.setMiddlePoint(self.myLine.startPoint(), self.myArc.results()[1]) + self.myDOF -= 2 + model.do() + self.checkMiddlePoint(self.myLine.startPoint(), self.myArc) + self.moveLine() + + def test_middle_point_coincidence_move_line(self): + """ Test 9. Set coincidence and middle point constraint and move line + """ + self.mySketch.setCoincident(self.myLine.startPoint(), self.myArc.results()[1]) + model.do() + self.mySketch.setMiddlePoint(self.myLine.startPoint(), self.myArc.results()[1]) + self.myDOF -= 2 + model.do() + self.checkMiddlePoint(self.myLine.startPoint(), self.myArc) + self.moveLine() + + def test_remove_middle_point(self): + """ Test 10. Set and then remove middle point constraint + """ + mp = self.mySketch.setMiddlePoint(self.myLine.startPoint(), self.myArc.results()[1]) + self.myDOF -= 2 + model.do() + model.assertArcValidity(self.myArc) + self.checkMiddlePoint(self.myLine.startPoint(), self.myArc) + self.checkDOF() + # remove middle point + self.myDocument.removeFeature(mp.feature()) + self.myDOF += 2 + model.do() + self.checkDOF() + # set flag False to avoid checking middle point constraint in tearDown() method + self.myTestPassed = False + + +if __name__ == "__main__": + test_program = unittest.main(exit=False) + assert test_program.result.wasSuccessful(), "Test failed" diff --git a/src/SketchPlugin/plugin-Sketch.xml b/src/SketchPlugin/plugin-Sketch.xml index 76871ab1b..35a8e7a76 100644 --- a/src/SketchPlugin/plugin-Sketch.xml +++ b/src/SketchPlugin/plugin-Sketch.xml @@ -657,7 +657,7 @@ - + - + - + diff --git a/src/SketchSolver/PlaneGCSSolver/CMakeLists.txt b/src/SketchSolver/PlaneGCSSolver/CMakeLists.txt index 7a961fcad..af72c4b13 100644 --- a/src/SketchSolver/PlaneGCSSolver/CMakeLists.txt +++ b/src/SketchSolver/PlaneGCSSolver/CMakeLists.txt @@ -29,6 +29,7 @@ SET(PLANEGCSSOLVER_HEADERS PlaneGCSSolver_PointWrapper.h PlaneGCSSolver_ScalarWrapper.h PlaneGCSSolver_AngleWrapper.h + PlaneGCSSolver_BooleanWrapper.h PlaneGCSSolver_Tools.h ) @@ -40,6 +41,7 @@ SET(PLANEGCSSOLVER_SOURCES PlaneGCSSolver_PointWrapper.cpp PlaneGCSSolver_ScalarWrapper.cpp PlaneGCSSolver_AngleWrapper.cpp + PlaneGCSSolver_BooleanWrapper.cpp PlaneGCSSolver_Tools.cpp ) diff --git a/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_AttributeBuilder.cpp b/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_AttributeBuilder.cpp index ea6eef78c..ca463c53d 100644 --- a/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_AttributeBuilder.cpp +++ b/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_AttributeBuilder.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -44,6 +45,16 @@ static double* createParameter(PlaneGCSSolver_Storage* theStorage) return theStorage ? theStorage->createParameter() : (new double(0)); } +static EntityWrapperPtr createBoolean(const AttributePtr& theAttribute) +{ + BooleanWrapperPtr aWrapper; + AttributeBooleanPtr aBoolean = + std::dynamic_pointer_cast(theAttribute); + if (aBoolean) + aWrapper = BooleanWrapperPtr(new PlaneGCSSolver_BooleanWrapper(aBoolean->value())); + return aWrapper; +} + static EntityWrapperPtr createScalar(const AttributePtr& theAttribute, PlaneGCSSolver_Storage* theStorage) { @@ -99,6 +110,8 @@ EntityWrapperPtr PlaneGCSSolver_AttributeBuilder::createAttribute( aResult = createPoint(theAttribute, myStorage); if (!aResult) aResult = createScalar(theAttribute, myStorage); + if (!aResult) + aResult = createBoolean(theAttribute); if (aResult && !myStorage) aResult->setExternal(true); return aResult; diff --git a/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_BooleanWrapper.cpp b/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_BooleanWrapper.cpp new file mode 100644 index 000000000..225c4344f --- /dev/null +++ b/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_BooleanWrapper.cpp @@ -0,0 +1,25 @@ +// Copyright (C) 2014-2019 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +#include + +PlaneGCSSolver_BooleanWrapper::PlaneGCSSolver_BooleanWrapper(bool theParam) + : myValue(theParam) +{ +} diff --git a/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_BooleanWrapper.h b/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_BooleanWrapper.h new file mode 100644 index 000000000..3bd1d8d2e --- /dev/null +++ b/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_BooleanWrapper.h @@ -0,0 +1,51 @@ +// Copyright (C) 2014-2019 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +#ifndef PlaneGCSSolver_BooleanWrapper_H_ +#define PlaneGCSSolver_BooleanWrapper_H_ + +#include +#include + +/** + * Wrapper providing storage for boolean values. + */ +class PlaneGCSSolver_BooleanWrapper : public PlaneGCSSolver_EntityWrapper +{ +public: + PlaneGCSSolver_BooleanWrapper(bool theValue); + + /// \brief Change value of parameter + void setValue(bool theValue) + { myValue = theValue; } + /// \brief Return value of parameter + bool value() const + { return myValue; } + + /// \brief Return type of current entity + virtual SketchSolver_EntityType type() const + { return ENTITY_BOOLEAN; } + +protected: + bool myValue; +}; + +typedef std::shared_ptr BooleanWrapperPtr; + +#endif diff --git a/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_Defs.h b/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_Defs.h index fccd4d718..e4851a295 100644 --- a/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_Defs.h +++ b/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_Defs.h @@ -44,6 +44,7 @@ const ConstraintID CID_FICTIVE = 1024; /// Types of entities enum SketchSolver_EntityType { ENTITY_UNKNOWN = 0, + ENTITY_BOOLEAN, ENTITY_SCALAR, ENTITY_ANGLE, ENTITY_POINT, diff --git a/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_EdgeWrapper.cpp b/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_EdgeWrapper.cpp index 7ec36cf23..2efea1914 100644 --- a/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_EdgeWrapper.cpp +++ b/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_EdgeWrapper.cpp @@ -21,7 +21,8 @@ #include PlaneGCSSolver_EdgeWrapper::PlaneGCSSolver_EdgeWrapper(const GCSCurvePtr theEntity) - : myEntity(theEntity) + : myEntity(theEntity), + myReversed(false) { std::shared_ptr aLine = std::dynamic_pointer_cast(myEntity); if (aLine) myType = ENTITY_LINE; diff --git a/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_EdgeWrapper.h b/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_EdgeWrapper.h index 9cd5bc2cb..e84b809c6 100644 --- a/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_EdgeWrapper.h +++ b/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_EdgeWrapper.h @@ -21,6 +21,7 @@ #define PlaneGCSSolver_EdgeWrapper_H_ #include +#include #include /** @@ -44,9 +45,16 @@ public: bool isDegenerated() const; + void setReversed(BooleanWrapperPtr theReversed) + { myReversed = theReversed; } + + bool isReversed() const + { return myReversed ? myReversed->value() : false; } + private: SketchSolver_EntityType myType; GCSCurvePtr myEntity; + BooleanWrapperPtr myReversed; // preferably used to control arc orientation }; typedef std::shared_ptr EdgeWrapperPtr; diff --git a/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_FeatureBuilder.cpp b/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_FeatureBuilder.cpp index c2965e3ee..f7687f3e7 100644 --- a/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_FeatureBuilder.cpp +++ b/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_FeatureBuilder.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -162,21 +163,25 @@ EntityWrapperPtr createArc(const AttributeEntityMap& theAttributes, PlaneGCSSolver_Storage* theStorage) { std::shared_ptr aNewArc(new GCS::Arc); + BooleanWrapperPtr isReversed = false; // Base attributes of arc (center, start and end points) AttributeEntityMap::const_iterator anIt = theAttributes.begin(); for (; anIt != theAttributes.end(); ++anIt) { std::shared_ptr aPoint = std::dynamic_pointer_cast(anIt->second); - if (!aPoint) - continue; - - if (anIt->first->id() == SketchPlugin_Arc::CENTER_ID()) - aNewArc->center = *(aPoint->point()); - else if (anIt->first->id() == SketchPlugin_Arc::START_ID()) - aNewArc->start = *(aPoint->point()); - else if (anIt->first->id() == SketchPlugin_Arc::END_ID()) - aNewArc->end = *(aPoint->point()); + if (aPoint) { + if (anIt->first->id() == SketchPlugin_Arc::CENTER_ID()) + aNewArc->center = *(aPoint->point()); + else if (anIt->first->id() == SketchPlugin_Arc::START_ID()) + aNewArc->start = *(aPoint->point()); + else if (anIt->first->id() == SketchPlugin_Arc::END_ID()) + aNewArc->end = *(aPoint->point()); + } + else { + // reversed flag + isReversed = std::dynamic_pointer_cast(anIt->second); + } } // Additional atrtributes of arc necessary for PlaneGCS solver @@ -200,7 +205,9 @@ EntityWrapperPtr createArc(const AttributeEntityMap& theAttributes, new GeomAPI_Dir2d((*aNewArc->end.x) - aCenter->x(), (*aNewArc->end.y) - aCenter->y())); *aNewArc->endAngle = OX->angle(aDir); - return EntityWrapperPtr(new PlaneGCSSolver_EdgeWrapper(aNewArc)); + EdgeWrapperPtr anArcWrapper(new PlaneGCSSolver_EdgeWrapper(aNewArc)); + anArcWrapper->setReversed(isReversed); + return anArcWrapper; } bool isAttributeApplicable(const std::string& theAttrName, const std::string& theOwnerName) @@ -208,7 +215,8 @@ bool isAttributeApplicable(const std::string& theAttrName, const std::string& th if (theOwnerName == SketchPlugin_Arc::ID()) { return theAttrName == SketchPlugin_Arc::CENTER_ID() || theAttrName == SketchPlugin_Arc::START_ID() || - theAttrName == SketchPlugin_Arc::END_ID(); + theAttrName == SketchPlugin_Arc::END_ID() || + theAttrName == SketchPlugin_Arc::REVERSED_ID(); } else if (theOwnerName == SketchPlugin_Circle::ID()) { return theAttrName == SketchPlugin_Circle::CENTER_ID() || diff --git a/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_Storage.cpp b/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_Storage.cpp index 2f98c8813..5c8cebe53 100644 --- a/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_Storage.cpp +++ b/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_Storage.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -134,6 +135,15 @@ static bool updateValues(AttributePtr& theAttribute, EntityWrapperPtr& theEntity isUpdated = updateValue(aScalar->value(), aValue); if (isUpdated) aWrapper->setValue(aValue); + } else { + AttributeBooleanPtr aBoolean = + std::dynamic_pointer_cast(theAttribute); + if (aBoolean) { + BooleanWrapperPtr aWrapper = + std::dynamic_pointer_cast(theEntity); + isUpdated = aWrapper->value() != aBoolean->value(); + aWrapper->setValue(aBoolean->value()); + } } } @@ -193,7 +203,8 @@ bool PlaneGCSSolver_Storage::update(FeaturePtr theFeature, bool theForce) std::list::iterator anAttrIt = anAttributes.begin(); for (; anAttrIt != anAttributes.end(); ++anAttrIt) if ((*anAttrIt)->attributeType() == GeomDataAPI_Point2D::typeId() || - (*anAttrIt)->attributeType() == ModelAPI_AttributeDouble::typeId()) + (*anAttrIt)->attributeType() == ModelAPI_AttributeDouble::typeId() || + (*anAttrIt)->attributeType() == ModelAPI_AttributeBoolean::typeId()) isUpdated = update(*anAttrIt) || isUpdated; // check external attribute is changed @@ -211,30 +222,6 @@ bool PlaneGCSSolver_Storage::update(FeaturePtr theFeature, bool theForce) if (sendNotify && isUpdated) notify(theFeature); - // update arc - if (aRelated && aRelated->type() == ENTITY_ARC) { - /// TODO: this code should be shared with FeatureBuilder somehow - - std::shared_ptr anEntity = - std::dynamic_pointer_cast(aRelated); - std::shared_ptr anArc = std::dynamic_pointer_cast(anEntity->entity()); - - static std::shared_ptr OX(new GeomAPI_Dir2d(1.0, 0.0)); - std::shared_ptr aCenter( - new GeomAPI_Pnt2d(*anArc->center.x, *anArc->center.y)); - std::shared_ptr aStart( - new GeomAPI_Pnt2d(*anArc->start.x, *anArc->start.y)); - - *anArc->rad = aStart->distance(aCenter); - - std::shared_ptr aDir(new GeomAPI_Dir2d(aStart->xy()->decreased(aCenter->xy()))); - *anArc->startAngle = OX->angle(aDir); - - aDir = std::shared_ptr( - new GeomAPI_Dir2d((*anArc->end.x) - aCenter->x(), (*anArc->end.y) - aCenter->y())); - *anArc->endAngle = OX->angle(aDir); - } - return isUpdated; } @@ -311,16 +298,16 @@ void PlaneGCSSolver_Storage::createArcConstraints(const EntityWrapperPtr& theArc // Additional constaints to fix arc's extra DoF (if the arc is not external): std::list anArcConstraints; - // 1. distances from center till start and end points are equal to radius - anArcConstraints.push_back(GCSConstraintPtr(new GCS::ConstraintP2PDistance( - anArc->center, anArc->start, anArc->rad))); - anArcConstraints.push_back(GCSConstraintPtr(new GCS::ConstraintP2PDistance( - anArc->center, anArc->end, anArc->rad))); - // 2. angles of start and end points should be equal to the arc angles - anArcConstraints.push_back(GCSConstraintPtr(new GCS::ConstraintP2PAngle( - anArc->center, anArc->start, anArc->startAngle))); - anArcConstraints.push_back(GCSConstraintPtr(new GCS::ConstraintP2PAngle( - anArc->center, anArc->end, anArc->endAngle))); + // constrain the start point on the arc + anArcConstraints.push_back(GCSConstraintPtr(new GCS::ConstraintCurveValue( + anArc->start, anArc->start.x, *anArc, anArc->startAngle))); + anArcConstraints.push_back(GCSConstraintPtr(new GCS::ConstraintCurveValue( + anArc->start, anArc->start.y, *anArc, anArc->startAngle))); + // constrain the end point on the arc + anArcConstraints.push_back(GCSConstraintPtr(new GCS::ConstraintCurveValue( + anArc->end, anArc->end.x, *anArc, anArc->endAngle))); + anArcConstraints.push_back(GCSConstraintPtr(new GCS::ConstraintCurveValue( + anArc->end, anArc->end.y, *anArc, anArc->endAngle))); ConstraintWrapperPtr aWrapper( new PlaneGCSSolver_ConstraintWrapper(anArcConstraints, CONSTRAINT_UNKNOWN)); @@ -340,6 +327,39 @@ void PlaneGCSSolver_Storage::removeArcConstraints(const EntityWrapperPtr& theArc } } +void PlaneGCSSolver_Storage::adjustParametrizationOfArcs() +{ + std::map::iterator anIt = myArcConstraintMap.begin(); + for (; anIt != myArcConstraintMap.end(); ++anIt) { + EdgeWrapperPtr anEdge = std::dynamic_pointer_cast(anIt->first); + std::shared_ptr anArc = std::dynamic_pointer_cast(anEdge->entity()); + // tune start angle of the arc to be in [0, 2PI] + while (*anArc->startAngle < -PI) + *anArc->startAngle += 2.0 * PI; + while (*anArc->startAngle >= PI) + *anArc->startAngle -= 2.0 * PI; + // adjust end angle of the arc + if (anEdge->isReversed()) { + while (*anArc->endAngle > *anArc->startAngle) + *anArc->endAngle -= 2.0 * PI; + while (*anArc->endAngle + 2 * PI < *anArc->startAngle) + *anArc->endAngle += 2.0 * PI; + } else { + while (*anArc->endAngle < *anArc->startAngle) + *anArc->endAngle += 2.0 * PI; + while (*anArc->endAngle > *anArc->startAngle + 2 * PI) + *anArc->endAngle -= 2.0 * PI; + } + } + + // update parameters of Middle point constraint for point on arc + std::map::iterator aCIt = myConstraintMap.begin(); + for (; aCIt != myConstraintMap.end(); ++aCIt) + if (aCIt->second->type() == CONSTRAINT_MIDDLE_POINT) { + notify(aCIt->first); + } +} + bool PlaneGCSSolver_Storage::removeConstraint(ConstraintPtr theConstraint) { diff --git a/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_Storage.h b/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_Storage.h index 3ac8e201e..107e4205d 100644 --- a/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_Storage.h +++ b/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_Storage.h @@ -90,6 +90,11 @@ public: virtual bool isEmpty() const { return SketchSolver_Storage::isEmpty() && myArcConstraintMap.empty(); } + /// \brief Make parametrization of arcs consistent. + /// Forward arcs should have the last parameter greater than the first parameter. + /// Reversed arcs should have the last parameter lesser than the first parameter. + virtual void adjustParametrizationOfArcs(); + private: /// \brief Convert feature using specified builder. EntityWrapperPtr createFeature(const FeaturePtr& theFeature, diff --git a/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_Tools.cpp b/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_Tools.cpp index 9c6cd276d..e90def0d2 100644 --- a/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_Tools.cpp +++ b/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_Tools.cpp @@ -106,7 +106,8 @@ static ConstraintWrapperPtr std::shared_ptr theIntermed); static ConstraintWrapperPtr createConstraintMiddlePoint(std::shared_ptr thePoint, - std::shared_ptr theEntity); + std::shared_ptr theEntity, + std::shared_ptr theAuxParameters); static GCS::SET_pD scalarParameters(const ScalarWrapperPtr& theScalar); static GCS::SET_pD pointParameters(const PointWrapperPtr& thePoint); @@ -191,7 +192,7 @@ ConstraintWrapperPtr PlaneGCSSolver_Tools::createConstraint( aResult = createConstraintPointOnEntity(theType, aPoint1, GCS_EDGE_WRAPPER(theEntity1)); break; case CONSTRAINT_MIDDLE_POINT: - aResult = createConstraintMiddlePoint(aPoint1, GCS_EDGE_WRAPPER(theEntity1)); + aResult = createConstraintMiddlePoint(aPoint1, GCS_EDGE_WRAPPER(theEntity1), aPoint2); break; case CONSTRAINT_PT_PT_DISTANCE: aResult = createConstraintDistancePointPoint(GCS_SCALAR_WRAPPER(theValue), aPoint1, aPoint2); @@ -354,19 +355,38 @@ ConstraintWrapperPtr createConstraintPointOnEntity( ConstraintWrapperPtr createConstraintMiddlePoint( std::shared_ptr thePoint, - std::shared_ptr theEntity) + std::shared_ptr theEntity, + std::shared_ptr theAuxParameters) { + std::list aConstrList; + GCSPointPtr aPoint = thePoint->point(); std::shared_ptr aLine = std::dynamic_pointer_cast(theEntity->entity()); - if (!aLine) - return ConstraintWrapperPtr(); - - std::list aConstrList; - aConstrList.push_back( - GCSConstraintPtr(new GCS::ConstraintPointOnPerpBisector(*aPoint, aLine->p1, aLine->p2))); - aConstrList.push_back(GCSConstraintPtr(new GCS::ConstraintPointOnLine(*aPoint, *aLine))); + if (aLine) { + aConstrList.push_back(GCSConstraintPtr(new GCS::ConstraintPointOnLine(*aPoint, *aLine))); + aConstrList.push_back( + GCSConstraintPtr(new GCS::ConstraintPointOnPerpBisector(*aPoint, aLine->p1, aLine->p2))); + } + else { + std::shared_ptr anArc = std::dynamic_pointer_cast(theEntity->entity()); + if (anArc) { + double* u = theAuxParameters->point()->x; + double* diff = theAuxParameters->point()->y; + *u = (*anArc->startAngle + *anArc->endAngle) * 0.5; + *diff = (*anArc->endAngle - *anArc->startAngle) * 0.5; + + aConstrList.push_back(GCSConstraintPtr( + new GCS::ConstraintCurveValue(*aPoint, aPoint->x, *anArc, u))); + aConstrList.push_back(GCSConstraintPtr( + new GCS::ConstraintCurveValue(*aPoint, aPoint->y, *anArc, u))); + aConstrList.push_back(GCSConstraintPtr( + new GCS::ConstraintDifference(anArc->startAngle, u, diff))); + aConstrList.push_back(GCSConstraintPtr( + new GCS::ConstraintDifference(u, anArc->endAngle, diff))); + } + } - return ConstraintWrapperPtr( + return aConstrList.empty() ? ConstraintWrapperPtr() : ConstraintWrapperPtr( new PlaneGCSSolver_ConstraintWrapper(aConstrList, CONSTRAINT_MIDDLE_POINT)); } diff --git a/src/SketchSolver/SketchSolver_ConstraintMiddle.cpp b/src/SketchSolver/SketchSolver_ConstraintMiddle.cpp index 9643b2aaf..15b8dc285 100644 --- a/src/SketchSolver/SketchSolver_ConstraintMiddle.cpp +++ b/src/SketchSolver/SketchSolver_ConstraintMiddle.cpp @@ -19,6 +19,10 @@ #include #include +#include +#include +#include +#include #include void SketchSolver_ConstraintMiddle::getAttributes( @@ -26,13 +30,55 @@ void SketchSolver_ConstraintMiddle::getAttributes( std::vector& theAttributes) { SketchSolver_Constraint::getAttributes(theValue, theAttributes); + + // create auxiliary point if middle point on arc is specified + if (theAttributes[2]->type() == ENTITY_ARC) { + std::shared_ptr aStorage = + std::dynamic_pointer_cast(myStorage); + + myOddPoint = GCSPointPtr(new GCS::Point); + myOddPoint->x = aStorage->createParameter(); + myOddPoint->y = aStorage->createParameter(); + theAttributes[1] = PointWrapperPtr(new PlaneGCSSolver_PointWrapper(myOddPoint)); + } +} + +bool SketchSolver_ConstraintMiddle::remove() +{ + if (myOddPoint) { + std::shared_ptr aStorage = + std::dynamic_pointer_cast(myStorage); + + GCS::SET_pD aParams; + aParams.insert(myOddPoint->x); + aParams.insert(myOddPoint->y); + aStorage->removeParameters(aParams); + } + return SketchSolver_ConstraintCoincidence::remove(); } void SketchSolver_ConstraintMiddle::notify(const FeaturePtr& theFeature, PlaneGCSSolver_Update* theUpdater) { - if (theFeature == myBaseConstraint && myInSolver) - return; // the constraint is already being updated + if (theFeature == myBaseConstraint && myInSolver) { + // the constraint is already being updated, + // update the middle point parameter if the constraint is "point-on-arc". + if (myOddPoint) { + EntityWrapperPtr anArcEntity = + myAttributes.front()->type() == ENTITY_ARC ? myAttributes.front() : myAttributes.back(); + EdgeWrapperPtr anArcEdge = + std::dynamic_pointer_cast(anArcEntity); + std::shared_ptr anArc; + if (anArcEdge) + anArc = std::dynamic_pointer_cast(anArcEdge->entity()); + if (anArc) { + // recalculate parameters of middle point according to arc + *myOddPoint->x = (*anArc->startAngle + *anArc->endAngle) * 0.5; + *myOddPoint->y = (*anArc->endAngle - *anArc->startAngle) * 0.5; + } + } + return; + } PlaneGCSSolver_UpdateCoincidence* anUpdater = static_cast(theUpdater); @@ -45,13 +91,8 @@ void SketchSolver_ConstraintMiddle::notify(const FeaturePtr& theFeature, // remove previously adde constraint myStorage->removeConstraint(myBaseConstraint); // merge divided constraints into single object - std::list aGCSConstraints; - std::shared_ptr aConstraint = - std::dynamic_pointer_cast(myMiddle); - aGCSConstraints.push_back(aConstraint->constraints().front()); - aConstraint = - std::dynamic_pointer_cast(mySolverConstraint); - aGCSConstraints.push_back(aConstraint->constraints().front()); + std::list aGCSConstraints = myMiddle->constraints(); + aGCSConstraints.push_front(mySolverConstraint->constraints().front()); myMiddle = ConstraintWrapperPtr(); mySolverConstraint = ConstraintWrapperPtr( @@ -72,10 +113,11 @@ void SketchSolver_ConstraintMiddle::notify(const FeaturePtr& theFeature, std::dynamic_pointer_cast(mySolverConstraint); std::list aGCSConstraints = aConstraint->constraints(); - myMiddle = ConstraintWrapperPtr( - new PlaneGCSSolver_ConstraintWrapper(aGCSConstraints.front(), CONSTRAINT_MIDDLE_POINT)); mySolverConstraint = ConstraintWrapperPtr( - new PlaneGCSSolver_ConstraintWrapper(aGCSConstraints.back(), CONSTRAINT_MIDDLE_POINT)); + new PlaneGCSSolver_ConstraintWrapper(aGCSConstraints.front(), CONSTRAINT_MIDDLE_POINT)); + aGCSConstraints.pop_front(); + myMiddle = ConstraintWrapperPtr( + new PlaneGCSSolver_ConstraintWrapper(aGCSConstraints, CONSTRAINT_MIDDLE_POINT)); // send middle constraint only myStorage->addConstraint(myBaseConstraint, myMiddle); diff --git a/src/SketchSolver/SketchSolver_ConstraintMiddle.h b/src/SketchSolver/SketchSolver_ConstraintMiddle.h index 48e11560a..3d6319785 100644 --- a/src/SketchSolver/SketchSolver_ConstraintMiddle.h +++ b/src/SketchSolver/SketchSolver_ConstraintMiddle.h @@ -38,6 +38,9 @@ public: virtual void notify(const FeaturePtr& theFeature, PlaneGCSSolver_Update* theUpdater); + /// \brief Remove constraint + virtual bool remove(); + protected: /// \brief Generate list of attributes of constraint in order useful for constraints /// \param[out] theValue numerical characteristic of constraint (e.g. distance) @@ -47,6 +50,7 @@ protected: private: ConstraintWrapperPtr myMiddle; + GCSPointPtr myOddPoint; ///< auxiliary point to adjust midpoint-on-arc }; #endif diff --git a/src/SketchSolver/SketchSolver_Group.cpp b/src/SketchSolver/SketchSolver_Group.cpp index 5a66f99b0..84d701e2b 100644 --- a/src/SketchSolver/SketchSolver_Group.cpp +++ b/src/SketchSolver/SketchSolver_Group.cpp @@ -179,6 +179,7 @@ static SolverConstraintPtr move(StoragePtr theStorage, SolverConstraintPtr(aConstraint)->process(theStorage, theEventsBlocked); if (aConstraint->error().empty()) { aConstraint->startPoint(theFrom); + theStorage->adjustParametrizationOfArcs(); theSketchSolver->initialize(); aConstraint->moveTo(theTo); theStorage->setNeedToResolve(true); @@ -236,8 +237,10 @@ bool SketchSolver_Group::resolveConstraints() PlaneGCSSolver_Solver::SolveStatus aResult = PlaneGCSSolver_Solver::STATUS_OK; try { - if (!isGroupEmpty) + if (!isGroupEmpty) { + myStorage->adjustParametrizationOfArcs(); aResult = mySketchSolver->solve(); + } if (aResult == PlaneGCSSolver_Solver::STATUS_FAILED && !myTempConstraints.empty()) { mySketchSolver->undo(); diff --git a/src/SketchSolver/SketchSolver_Storage.h b/src/SketchSolver/SketchSolver_Storage.h index 4d4831058..0757abfb7 100644 --- a/src/SketchSolver/SketchSolver_Storage.h +++ b/src/SketchSolver/SketchSolver_Storage.h @@ -150,6 +150,11 @@ public: /// \brief Notify all subscribers about update of the feature void notify(const FeaturePtr& theFeature) const; + /// \brief Make parametrization of arcs consistent. + /// Forward arcs should have the last parameter greater than the first parameter. + /// Reversed arcs should have the last parameter lesser than the first parameter. + virtual void adjustParametrizationOfArcs() = 0; + protected: /// \brief Convert result to feature or attribute if theResult is linked to center of circle/arc static void resultToFeatureOrAttribute(const ObjectPtr& theResult, -- 2.39.2