From 5631e9780ff361205c28c8dcf6e253d222b39721 Mon Sep 17 00:00:00 2001 From: azv Date: Fri, 17 Mar 2017 13:27:36 +0300 Subject: [PATCH] Redesign Tangency constraint processing to set the Angle constraint for tangent features with coincident points. --- src/PythonAPI/Test/TestSketcherSetFillet.py | 6 - src/SketchPlugin/Test/TestConstraintEqual.py | 7 + src/SketchPlugin/Test/TestFillet.py | 27 +- .../Test/TestFilletInteracting.py | 26 +- .../PlaneGCSSolver_EdgeWrapper.h | 2 + .../PlaneGCSSolver/PlaneGCSSolver_Solver.cpp | 18 +- .../PlaneGCSSolver/PlaneGCSSolver_Solver.h | 6 +- .../PlaneGCSSolver/PlaneGCSSolver_Storage.cpp | 18 +- .../PlaneGCSSolver/PlaneGCSSolver_Tools.cpp | 70 ---- .../PlaneGCSSolver/PlaneGCSSolver_Tools.h | 5 - .../SketchSolver_ConstraintTangent.cpp | 341 +++++++++++++++--- .../SketchSolver_ConstraintTangent.h | 19 +- 12 files changed, 366 insertions(+), 179 deletions(-) diff --git a/src/PythonAPI/Test/TestSketcherSetFillet.py b/src/PythonAPI/Test/TestSketcherSetFillet.py index 6b2b469da..f121be4db 100644 --- a/src/PythonAPI/Test/TestSketcherSetFillet.py +++ b/src/PythonAPI/Test/TestSketcherSetFillet.py @@ -3,12 +3,6 @@ from salome.shaper import model from TestSketcher import SketcherTestCase class SketcherSetFillet(SketcherTestCase): - # TODO: Remove tearDown method to check Python dump in super-class - def tearDown(self): - model.end() - #assert(model.checkPythonDump()) - model.reset() - def test_fillet(self): l1 = self.sketch.addLine(0, 0, 0, 1) l2 = self.sketch.addLine(0, 1, 1, 1) diff --git a/src/SketchPlugin/Test/TestConstraintEqual.py b/src/SketchPlugin/Test/TestConstraintEqual.py index c8d4917b4..49bf17658 100644 --- a/src/SketchPlugin/Test/TestConstraintEqual.py +++ b/src/SketchPlugin/Test/TestConstraintEqual.py @@ -209,6 +209,13 @@ assert (math.fabs(aLine1Len - anExtLineLen) < 1.e-10) assert (math.fabs(aLine2Len - anExtLineLen) < 1.e-10) assert (model.dof(aSketchFeature) == 12) #========================================================================= +# Remove costraint to check the DOF +#========================================================================= +aSession.startOperation() +aDocument.removeFeature(aConstraintEqLen2) +aSession.finishOperation() +assert (model.dof(aSketchFeature) == 13) +#========================================================================= # End of test #========================================================================= diff --git a/src/SketchPlugin/Test/TestFillet.py b/src/SketchPlugin/Test/TestFillet.py index 39762ed77..59f353cb8 100644 --- a/src/SketchPlugin/Test/TestFillet.py +++ b/src/SketchPlugin/Test/TestFillet.py @@ -197,7 +197,17 @@ aSession.finishOperation() # Verify the objects of fillet are created #========================================================================= checkSmoothness(aSketchFeature) -assert model.dof(aSketchFeature) == 14, "PlaneGCS limitation: if you see this message, then maybe PlaneGCS has solved DoF for sketch with fillet correctly (expected DoF = 10, observed = {0}".format(model.dof(aSketchFeature)) +assert (model.dof(aSketchFeature) == 10) +#========================================================================= +# Move a line and check the fillet is correct +#========================================================================= +DELTA_X = DELTA_Y = 10. +aSession.startOperation() +aEndPoint1.setValue(aEndPoint1.x() + DELTA_X, aEndPoint1.y() + DELTA_Y) +aSession.finishOperation() +checkSmoothness(aSketchFeature) +assert (model.dof(aSketchFeature) == 10) + #========================================================================= # Create another sketch @@ -228,12 +238,21 @@ aSession.finishOperation() # Verify the objects of fillet are created #========================================================================= checkSmoothness(aSketchFeature) -assert model.dof(aSketchFeature) == 10, "PlaneGCS limitation: if you see this message, then maybe PlaneGCS has solved DoF for sketch with fillet correctly (expected DoF = 8, observed = {0}".format(model.dof(aSketchFeature)) +assert (model.dof(aSketchFeature) == 8) +#========================================================================= +# Move a line and check the fillet is correct +#========================================================================= +DELTA_X = 1. +DELTA_Y = -2. +aSession.startOperation() +aStartPoint1.setValue(aStartPoint1.x() + DELTA_X, aStartPoint1.y() + DELTA_Y) +aSession.finishOperation() +checkSmoothness(aSketchFeature) +assert (model.dof(aSketchFeature) == 8) #========================================================================= # End of test #========================================================================= # TODO: Improve Fillet test case by moving one of filleted objectes and check coincidence and tangency are correct -# TODO: Checking of Python dump has been disabled until the Fillet redesigned. -#assert(model.checkPythonDump()) +assert(model.checkPythonDump()) diff --git a/src/SketchPlugin/Test/TestFilletInteracting.py b/src/SketchPlugin/Test/TestFilletInteracting.py index 58a222b68..15b101e44 100644 --- a/src/SketchPlugin/Test/TestFilletInteracting.py +++ b/src/SketchPlugin/Test/TestFilletInteracting.py @@ -58,8 +58,7 @@ class TestFilletInteracting(unittest.TestCase): def tearDown(self): model.end() - # TODO: Revert commented line to check Python dump - #self.assertTrue(model.checkPythonDump()) + self.assertTrue(model.checkPythonDump()) model.reset() @@ -115,7 +114,6 @@ class TestFilletInteracting(unittest.TestCase): return [aFeatureA, aFeatureB] - @unittest.expectedFailure def test_fillet_two_lines(self): """ Test 1. Fillet on two connected lines """ @@ -173,7 +171,6 @@ class TestFilletInteracting(unittest.TestCase): # remove fillet for correct python dump self.myDocument.removeFeature(aFillet.feature()) - @unittest.expectedFailure def test_fillet_arc_line(self): """ Test 3. Fillet on connected arc and line """ @@ -202,7 +199,6 @@ class TestFilletInteracting(unittest.TestCase): model.testNbSubShapes(self.mySketch, GeomAPI_Shape.EDGE, [3]) model.testNbSubShapes(self.mySketch, GeomAPI_Shape.VERTEX, [6]) - @unittest.expectedFailure def test_fillet_two_arcs(self): """ Test 4. Fillet on two connected arcs """ @@ -231,7 +227,6 @@ class TestFilletInteracting(unittest.TestCase): model.testNbSubShapes(self.mySketch, GeomAPI_Shape.EDGE, [3]) model.testNbSubShapes(self.mySketch, GeomAPI_Shape.VERTEX, [6]) - @unittest.expectedFailure def test_fillet_with_horizontal_vertical(self): """ Test 5. Fillet on two connected lines in case of Horizontal or Vertical constraints applied """ @@ -267,7 +262,6 @@ class TestFilletInteracting(unittest.TestCase): model.testNbSubShapes(self.mySketch, GeomAPI_Shape.EDGE, [3]) model.testNbSubShapes(self.mySketch, GeomAPI_Shape.VERTEX, [6]) - @unittest.expectedFailure def test_fillet_with_orthogonal(self): """ Test 6. Fillet on two connected lines in case of Perpendicular constraint applied """ @@ -301,7 +295,6 @@ class TestFilletInteracting(unittest.TestCase): model.testNbSubShapes(self.mySketch, GeomAPI_Shape.EDGE, [3]) model.testNbSubShapes(self.mySketch, GeomAPI_Shape.VERTEX, [6]) - @unittest.expectedFailure def test_fillet_with_parallel(self): """ Test 7. Fillet on two connected lines in case of Parallel constraint applied """ @@ -338,7 +331,6 @@ class TestFilletInteracting(unittest.TestCase): model.testNbSubShapes(self.mySketch, GeomAPI_Shape.EDGE, [4]) model.testNbSubShapes(self.mySketch, GeomAPI_Shape.VERTEX, [8]) - @unittest.expectedFailure def test_fillet_with_equal_lines(self): """ Test 8. Fillet on two connected lines in case of Equal constraint applied """ @@ -355,7 +347,7 @@ class TestFilletInteracting(unittest.TestCase): model.do() self.checkDOF() self.mySketch.setFillet(aSketchLineA.startPoint()) - self.myDOF += 2 + self.myDOF += 2 # Equal has been removed model.do() self.checkFillet() self.checkDOF() @@ -372,7 +364,6 @@ class TestFilletInteracting(unittest.TestCase): model.testNbSubShapes(self.mySketch, GeomAPI_Shape.EDGE, [3]) model.testNbSubShapes(self.mySketch, GeomAPI_Shape.VERTEX, [6]) - @unittest.expectedFailure def test_fillet_with_equal_arcs(self): """ Test 9. Fillet on two connected arcs in case of Equal constraint applied """ @@ -389,7 +380,7 @@ class TestFilletInteracting(unittest.TestCase): model.do() self.checkDOF() self.mySketch.setFillet(aSketchArc1.endPoint()) - self.myDOF += 1 + self.myDOF += 2 # Equal has been removed model.do() self.checkFillet() self.checkDOF() @@ -406,7 +397,6 @@ class TestFilletInteracting(unittest.TestCase): model.testNbSubShapes(self.mySketch, GeomAPI_Shape.EDGE, [3]) model.testNbSubShapes(self.mySketch, GeomAPI_Shape.VERTEX, [6]) - @unittest.expectedFailure def test_fillet_with_length(self): """ Test 10. Fillet on two connected lines in case of Length constraint applied """ @@ -440,7 +430,6 @@ class TestFilletInteracting(unittest.TestCase): model.testNbSubShapes(self.mySketch, GeomAPI_Shape.EDGE, [3]) model.testNbSubShapes(self.mySketch, GeomAPI_Shape.VERTEX, [6]) - @unittest.expectedFailure def test_fillet_with_radius(self): """ Test 11. Fillet on connected arc and line in case of Radius constraint is applied to arc """ @@ -474,7 +463,6 @@ class TestFilletInteracting(unittest.TestCase): model.testNbSubShapes(self.mySketch, GeomAPI_Shape.EDGE, [3]) model.testNbSubShapes(self.mySketch, GeomAPI_Shape.VERTEX, [6]) - @unittest.expectedFailure def test_fillet_with_distance(self): """ Test 12. Fillet on two connected lines in case of Distance constraint applied """ @@ -495,7 +483,7 @@ class TestFilletInteracting(unittest.TestCase): model.do() self.checkDOF() self.mySketch.setFillet(aSketchLineA.startPoint()) - self.myDOF += 1 + self.myDOF += 2 # Distance has been removed model.do() self.checkFillet() self.checkDOF() @@ -512,7 +500,6 @@ class TestFilletInteracting(unittest.TestCase): model.testNbSubShapes(self.mySketch, GeomAPI_Shape.EDGE, [4]) model.testNbSubShapes(self.mySketch, GeomAPI_Shape.VERTEX, [8]) - @unittest.expectedFailure def test_fillet_with_fixed_point(self): """ Test 13. Fillet on two connected lines in case of Fixed constraint applied to the fillet point """ @@ -529,7 +516,7 @@ class TestFilletInteracting(unittest.TestCase): model.do() self.checkDOF() self.mySketch.setFillet(aSketchLineA.startPoint()) - self.myDOF += 1 + self.myDOF += 3 # Fixed constraint has been removed model.do() self.checkFillet() self.checkDOF() @@ -546,7 +533,6 @@ class TestFilletInteracting(unittest.TestCase): model.testNbSubShapes(self.mySketch, GeomAPI_Shape.EDGE, [3]) model.testNbSubShapes(self.mySketch, GeomAPI_Shape.VERTEX, [6]) - @unittest.expectedFailure def test_fillet_with_fixed_line(self): """ Test 14. Fillet on two connected lines in case of Fixed constraint applied to one of lines """ @@ -580,7 +566,6 @@ class TestFilletInteracting(unittest.TestCase): model.testNbSubShapes(self.mySketch, GeomAPI_Shape.EDGE, [3]) model.testNbSubShapes(self.mySketch, GeomAPI_Shape.VERTEX, [6]) - @unittest.expectedFailure def test_fillet_with_angle(self): """ Test 15. Fillet on two connected lines in case of Perpendicular constraint applied """ @@ -615,6 +600,5 @@ class TestFilletInteracting(unittest.TestCase): model.testNbSubShapes(self.mySketch, GeomAPI_Shape.VERTEX, [6]) -# TODO: Remove unittest.expectedFailure if the Tangency in PlaneGCS will be updated if __name__ == '__main__': unittest.main() diff --git a/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_EdgeWrapper.h b/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_EdgeWrapper.h index dd843e2cb..206c8a4f4 100644 --- a/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_EdgeWrapper.h +++ b/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_EdgeWrapper.h @@ -34,4 +34,6 @@ private: GCSCurvePtr myEntity; }; +typedef std::shared_ptr EdgeWrapperPtr; + #endif diff --git a/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_Solver.cpp b/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_Solver.cpp index e9be8a299..2be2d62b5 100644 --- a/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_Solver.cpp +++ b/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_Solver.cpp @@ -29,26 +29,16 @@ void PlaneGCSSolver_Solver::clear() myDOF = 0; } -void PlaneGCSSolver_Solver::addConstraint(GCSConstraintPtr theConstraint, - const SketchSolver_ConstraintType theType) +void PlaneGCSSolver_Solver::addConstraint(GCSConstraintPtr theConstraint) { myEquationSystem->addConstraint(theConstraint.get()); myConstraints[theConstraint->getTag()].insert(theConstraint); myDOF = -1; - - // Workaround: avoid tangent constraint in the list of redundant - if (theType == CONSTRAINT_TANGENT_CIRCLE_LINE || - theType == CONSTRAINT_TANGENT_CIRCLE_CIRCLE || - theType == CONSTRAINT_PT_PT_COINCIDENT || - theType == CONSTRAINT_PT_ON_CIRCLE || - theType == CONSTRAINT_PT_ON_LINE) - myConstraintIDsNotRedundant.insert(theConstraint->getTag()); } void PlaneGCSSolver_Solver::removeConstraint(ConstraintID theID) { myConstraints.erase(theID); - myConstraintIDsNotRedundant.erase(theID); if (myConstraints.empty()) { myEquationSystem->clear(); myDOF = (int)myParameters.size(); @@ -132,12 +122,6 @@ void PlaneGCSSolver_Solver::collectConflicting() myConflictingIDs.insert(aConflict.begin(), aConflict.end()); myEquationSystem->getRedundant(aConflict); - // Workaround: avoid conflicting tangent constraints - GCS::VEC_I aTemp = aConflict; - aConflict.clear(); - for (GCS::VEC_I::iterator anIt = aTemp.begin(); anIt != aTemp.end(); ++anIt) - if (myConstraintIDsNotRedundant.find(*anIt) == myConstraintIDsNotRedundant.end()) - aConflict.push_back(*anIt); myConflictingIDs.insert(aConflict.begin(), aConflict.end()); myConfCollected = true; diff --git a/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_Solver.h b/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_Solver.h index 7037b1a5e..4da9e60dc 100644 --- a/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_Solver.h +++ b/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_Solver.h @@ -32,7 +32,7 @@ public: void clear(); /// \brief Add constraint to the system of equations - void addConstraint(GCSConstraintPtr theConstraint, const SketchSolver_ConstraintType theType); + void addConstraint(GCSConstraintPtr theConstraint); /// \brief Remove constraint from the system of equations void removeConstraint(ConstraintID theID); @@ -64,10 +64,6 @@ private: GCS::VEC_pD myParameters; ///< list of unknowns ConstraintMap myConstraints; ///< list of constraints - /// IDs of constraints (coincidence, tangency) which will not be treated as conflicting - /// if they are reported as redundant - GCS::SET_I myConstraintIDsNotRedundant; - std::shared_ptr myEquationSystem; ///< set of equations for solving in FreeGCS GCS::SET_I myConflictingIDs; ///< list of IDs of conflicting constraints diff --git a/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_Storage.cpp b/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_Storage.cpp index bec4445be..472f4add7 100644 --- a/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_Storage.cpp +++ b/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_Storage.cpp @@ -31,7 +31,7 @@ static void constraintsToSolver(const ConstraintWrapperPtr& theConstraint, std::dynamic_pointer_cast(theConstraint)->constraints(); std::list::const_iterator anIt = aConstraints.begin(); for (; anIt != aConstraints.end(); ++anIt) - theSolver->addConstraint(*anIt, theConstraint->type()); + theSolver->addConstraint(*anIt); } @@ -259,9 +259,20 @@ bool PlaneGCSSolver_Storage::removeConstraint(ConstraintPtr theConstraint) std::map::iterator aFound = myConstraintMap.find(theConstraint); if (aFound != myConstraintMap.end()) { - ConstraintID anID = aFound->second->id(); + ConstraintWrapperPtr aCW = aFound->second; + ConstraintID anID = aCW->id(); + // Remove solver's constraints mySketchSolver->removeConstraint(anID); + + // Remove value if exists + const ScalarWrapperPtr& aValue = aCW->valueParameter(); + if (aValue) { + GCS::SET_pD aParToRemove; + aParToRemove.insert(aValue->scalar()); + removeParameters(aParToRemove); + } + // Remove constraint myConstraintMap.erase(aFound); @@ -295,7 +306,8 @@ void PlaneGCSSolver_Storage::removeInvalidEntities() for (; aFIter != myFeatureMap.end(); aFIter++) if (!aFIter->first->data() || !aFIter->first->data()->isValid()) { anInvalidFeatures.push_back(aFIter->first); - aDestroyer.remove(aFIter->second); + if (aFIter->second) + aDestroyer.remove(aFIter->second); // remove invalid arc std::map::iterator diff --git a/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_Tools.cpp b/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_Tools.cpp index f90aa3172..524088596 100644 --- a/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_Tools.cpp +++ b/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_Tools.cpp @@ -84,10 +84,6 @@ static ConstraintWrapperPtr std::shared_ptr theEntity1, std::shared_ptr theEntity2, std::shared_ptr theIntermed); -static ConstraintWrapperPtr - createConstraintTangent(const SketchSolver_ConstraintType& theType, - std::shared_ptr theEntity1, - std::shared_ptr theEntity2); static ConstraintWrapperPtr createConstraintMiddlePoint(std::shared_ptr thePoint, std::shared_ptr theEntity); @@ -200,15 +196,6 @@ ConstraintWrapperPtr PlaneGCSSolver_Tools::createConstraint( GCS_EDGE_WRAPPER(theEntity2), anIntermediate); break; - case CONSTRAINT_TANGENT_CIRCLE_LINE: - case CONSTRAINT_TANGENT_CIRCLE_CIRCLE: - aResult = createConstraintTangent(theType, - GCS_EDGE_WRAPPER(theEntity1), - GCS_EDGE_WRAPPER(theEntity2)); - break; - case CONSTRAINT_MULTI_TRANSLATION: - case CONSTRAINT_MULTI_ROTATION: - case CONSTRAINT_SYMMETRIC: default: break; } @@ -465,60 +452,3 @@ ConstraintWrapperPtr createConstraintEqual( aResult->setValueParameter(theIntermed); return aResult; } - -ConstraintWrapperPtr createConstraintTangent( - const SketchSolver_ConstraintType& theType, - std::shared_ptr theEntity1, - std::shared_ptr theEntity2) -{ - GCSConstraintPtr aNewConstr; - if (theType == CONSTRAINT_TANGENT_CIRCLE_LINE) { - GCSCurvePtr anEntCirc, anEntLine; - if (theEntity1->type() == ENTITY_LINE) { - anEntLine = theEntity1->entity(); - anEntCirc = theEntity2->entity(); - } else { - anEntLine = theEntity2->entity(); - anEntCirc = theEntity1->entity(); - } - - std::shared_ptr aCirc = - std::dynamic_pointer_cast(anEntCirc); - std::shared_ptr aLine = - std::dynamic_pointer_cast(anEntLine); - - aNewConstr = - GCSConstraintPtr(new GCS::ConstraintP2LDistance(aCirc->center, *aLine, aCirc->rad)); - } else { - std::shared_ptr aCirc1 = - std::dynamic_pointer_cast(theEntity1->entity()); - std::shared_ptr aCirc2 = - std::dynamic_pointer_cast(theEntity2->entity()); - - double aDX = *(aCirc1->center.x) - *(aCirc2->center.x); - double aDY = *(aCirc1->center.y) - *(aCirc2->center.y); - double aDist = sqrt(aDX * aDX + aDY * aDY); - aNewConstr = GCSConstraintPtr(new GCS::ConstraintTangentCircumf(aCirc1->center, aCirc2->center, - aCirc1->rad, aCirc2->rad, (aDist < *(aCirc1->rad) || aDist < *(aCirc2->rad)))); - } - - return ConstraintWrapperPtr(new PlaneGCSSolver_ConstraintWrapper(aNewConstr, theType)); -} - -bool PlaneGCSSolver_Tools::isArcArcTangencyInternal( - EntityWrapperPtr theArc1, EntityWrapperPtr theArc2) -{ - std::shared_ptr aCirc1 = std::dynamic_pointer_cast( - GCS_EDGE_WRAPPER(theArc1)->entity()); - std::shared_ptr aCirc2 = std::dynamic_pointer_cast( - GCS_EDGE_WRAPPER(theArc2)->entity()); - - if (!aCirc1 || !aCirc2) - return false; - - double aDX = *(aCirc1->center.x) - *(aCirc2->center.x); - double aDY = *(aCirc1->center.y) - *(aCirc2->center.y); - double aDist = sqrt(aDX * aDX + aDY * aDY); - - return (aDist < *(aCirc1->rad) || aDist < *(aCirc2->rad)); -} diff --git a/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_Tools.h b/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_Tools.h index 56dfbbdbb..db4820107 100644 --- a/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_Tools.h +++ b/src/SketchSolver/PlaneGCSSolver/PlaneGCSSolver_Tools.h @@ -54,11 +54,6 @@ namespace PlaneGCSSolver_Tools /// \brief Convert entity to line /// \return empty pointer if the entity is not a line std::shared_ptr line(FeaturePtr theFeature); - - /// \brief Check if two connected arcs have centers - /// in same direction relatively to connection point - bool isArcArcTangencyInternal(EntityWrapperPtr theArc1, - EntityWrapperPtr theArc2); }; #endif diff --git a/src/SketchSolver/SketchSolver_ConstraintTangent.cpp b/src/SketchSolver/SketchSolver_ConstraintTangent.cpp index 8a61e1a99..791de4cc2 100644 --- a/src/SketchSolver/SketchSolver_ConstraintTangent.cpp +++ b/src/SketchSolver/SketchSolver_ConstraintTangent.cpp @@ -3,7 +3,10 @@ #include #include +#include +#include #include +#include #include #include @@ -11,46 +14,75 @@ #include +#define GCS_EDGE_WRAPPER(x) std::dynamic_pointer_cast(x) -/// \brief Check whether the entities has only one shared point or less -static bool hasSingleCoincidence(FeaturePtr theFeature1, FeaturePtr theFeature2) -{ - const std::set& aRefs1 = theFeature1->data()->refsToMe(); - const std::set& aRefs2 = theFeature2->data()->refsToMe(); +/// \brief Obtain tangent features from the constraint +static void getTangentFeatures(const ConstraintPtr& theConstraint, + FeaturePtr& theFeature1, + FeaturePtr& theFeature2); - // collect all shared coincidendes - std::set aCoincidences; - std::set::const_iterator anIt; - for (anIt = aRefs1.begin(); anIt != aRefs1.end(); ++anIt) { - FeaturePtr aRef = ModelAPI_Feature::feature((*anIt)->owner()); - if (aRef && aRef->getKind() == SketchPlugin_ConstraintCoincidence::ID()) - aCoincidences.insert(aRef); - } - int aNbCoinCidentPoints = 0; - for (anIt = aRefs2.begin(); anIt != aRefs2.end(); ++anIt) { - FeaturePtr aRef = ModelAPI_Feature::feature((*anIt)->owner()); - if (aCoincidences.find(aRef) != aCoincidences.end()) - ++aNbCoinCidentPoints; - } +/// \brief Obtain all coincident constraints between features +static std::set collectCoincidences(FeaturePtr theFeature1, FeaturePtr theFeature2); - return aNbCoinCidentPoints <= 1; -} +/// \brief Check whether the entities has only one shared point or less. +/// Return list of coincident points. +static std::list coincidentPoints(FeaturePtr theFeature1, FeaturePtr theFeature2); + +/// \brief Check if two connected arcs have centers +/// in same direction relatively to connection point +static bool isArcArcTangencyInternal(EntityWrapperPtr theArc1, + EntityWrapperPtr theArc2); + +static ConstraintWrapperPtr + createArcLineTangency(EntityWrapperPtr theEntity1, + EntityWrapperPtr theEntity2, + EntityWrapperPtr theShapedPoint = EntityWrapperPtr(), + double* theAngle = 0); + +static ConstraintWrapperPtr + createArcArcTangency(EntityWrapperPtr theEntity1, + EntityWrapperPtr theEntity2, + bool theInternalTangency, + EntityWrapperPtr theSharedPoint = EntityWrapperPtr(), + double* theAngle = 0); -void SketchSolver_ConstraintTangent::getAttributes( - EntityWrapperPtr& theValue, - std::vector& theAttributes) + +void SketchSolver_ConstraintTangent::process() { - SketchSolver_Constraint::getAttributes(theValue, theAttributes); - if (!myErrorMsg.empty() || !theAttributes[2] || !theAttributes[3]) { - theAttributes.clear(); + cleanErrorMsg(); + if (!myBaseConstraint || !myStorage) { + // Not enough parameters are assigned return; } + EntityWrapperPtr aValue; + std::vector anAttributes; + SketchSolver_Constraint::getAttributes(aValue, anAttributes); + if (!myErrorMsg.empty()) + return; + + rebuild(); + if (!myErrorMsg.empty()) + return; + + myStorage->subscribeUpdates(this, PlaneGCSSolver_UpdateCoincidence::GROUP()); +} + +void SketchSolver_ConstraintTangent::rebuild() +{ + if (mySolverConstraint) + myStorage->removeConstraint(myBaseConstraint); + + mySolverConstraint = ConstraintWrapperPtr(); + mySharedPoint = AttributePtr(); + // Check the quantity of entities of each type and their order (arcs first) int aNbLines = 0; int aNbCircles = 0; - std::vector::iterator anEntIt = theAttributes.begin() + 2; - for (; anEntIt != theAttributes.end(); ++anEntIt) { + std::list::iterator anEntIt = myAttributes.begin(); + for (; anEntIt != myAttributes.end(); ++anEntIt) { + if (!(*anEntIt).get()) + continue; if ((*anEntIt)->type() == ENTITY_LINE) ++aNbLines; else if ((*anEntIt)->type() == ENTITY_ARC || (*anEntIt)->type() == ENTITY_CIRCLE) @@ -66,23 +98,38 @@ void SketchSolver_ConstraintTangent::getAttributes( } else if (aNbCircles == 2) { myType = CONSTRAINT_TANGENT_CIRCLE_CIRCLE; - isArcArcInternal = - PlaneGCSSolver_Tools::isArcArcTangencyInternal(theAttributes[2], theAttributes[3]); + isArcArcInternal = isArcArcTangencyInternal(myAttributes.front(), myAttributes.back()); } else { myErrorMsg = SketchSolver_Error::INCORRECT_ATTRIBUTE(); return; } - if (myType == CONSTRAINT_TANGENT_CIRCLE_LINE) { - AttributeRefAttrPtr aRefAttr = myBaseConstraint->refattr(SketchPlugin_Constraint::ENTITY_A()); - FeaturePtr aFeature1 = ModelAPI_Feature::feature(aRefAttr->object()); - aRefAttr = myBaseConstraint->refattr(SketchPlugin_Constraint::ENTITY_B()); - FeaturePtr aFeature2 = ModelAPI_Feature::feature(aRefAttr->object()); + FeaturePtr aFeature1, aFeature2; + getTangentFeatures(myBaseConstraint, aFeature1, aFeature2); - if (!hasSingleCoincidence(aFeature1, aFeature2)) - myErrorMsg = SketchSolver_Error::TANGENCY_FAILED(); + // check number of coincident points + std::list aCoincidentPoints = coincidentPoints(aFeature1, aFeature2); + if (myType == CONSTRAINT_TANGENT_CIRCLE_LINE && aCoincidentPoints.size() > 1) { + myErrorMsg = SketchSolver_Error::TANGENCY_FAILED(); + return; } + + EntityWrapperPtr aSharedPointEntity; + if (!aCoincidentPoints.empty()) { + mySharedPoint = aCoincidentPoints.front(); + aSharedPointEntity = myStorage->entity(mySharedPoint); + } + + if (myType == CONSTRAINT_TANGENT_CIRCLE_LINE) { + mySolverConstraint = createArcLineTangency(myAttributes.front(), myAttributes.back(), + aSharedPointEntity, &myCurveCurveAngle); + } else { + mySolverConstraint = createArcArcTangency(myAttributes.front(), myAttributes.back(), + isArcArcInternal, aSharedPointEntity, &myCurveCurveAngle); + } + + myStorage->addConstraint(myBaseConstraint, mySolverConstraint); } void SketchSolver_ConstraintTangent::adjustConstraint() @@ -93,10 +140,220 @@ void SketchSolver_ConstraintTangent::adjustConstraint() EntityWrapperPtr anEntity2 = myStorage->entity(myBaseConstraint->attribute(SketchPlugin_Constraint::ENTITY_B())); - if (isArcArcInternal != PlaneGCSSolver_Tools::isArcArcTangencyInternal(anEntity1, anEntity2)) { - // fully rebuld constraint, because it is unable to access attributes of PlaneGCS constraint - remove(); - process(); + if (isArcArcInternal != isArcArcTangencyInternal(anEntity1, anEntity2)) + rebuild(); + } +} + +void SketchSolver_ConstraintTangent::notify(const FeaturePtr& theFeature, + PlaneGCSSolver_Update* theUpdater) +{ + if (theFeature->getKind() != SketchPlugin_ConstraintCoincidence::ID()) + return; + + // base constraint may become invalid (for example, during undo) + if (!myBaseConstraint->data() || !myBaseConstraint->data()->isValid()) + return; + + FeaturePtr aTgFeat1, aTgFeat2; + getTangentFeatures(myBaseConstraint, aTgFeat1, aTgFeat2); + + bool isRebuild = false; + if (theFeature->data() && theFeature->data()->isValid()) { + // the constraint has been created + if (!mySharedPoint) { + // features has no shared point, check whether coincidence constraint binds these features) + int aNbCoincidentFeatures = 0; + for (int i = 0; i < CONSTRAINT_ATTR_SIZE; ++i) { + AttributeRefAttrPtr aRefAttr = theFeature->refattr(SketchPlugin_Constraint::ATTRIBUTE(i)); + if (!aRefAttr) + continue; + + ObjectPtr anObj; + if (aRefAttr->isObject()) + anObj = aRefAttr->object(); + else + anObj = aRefAttr->attr()->owner(); + + FeaturePtr aFeature = ModelAPI_Feature::feature(anObj); + if (aFeature == aTgFeat1 || aFeature == aTgFeat2) + ++aNbCoincidentFeatures; + } + + if (aNbCoincidentFeatures == 2) + isRebuild = true; + } + } else if (mySharedPoint) { + // The features are tangent in the shared point, but the coincidence has been removed. + // Check if the coincidence is the same. + std::list aCoincidentPoints = coincidentPoints(aTgFeat1, aTgFeat2); + isRebuild = true; + std::list::iterator anIt = aCoincidentPoints.begin(); + for (; anIt != aCoincidentPoints.end() && isRebuild; ++anIt) + if (*anIt == mySharedPoint) + isRebuild = false; // the coincidence is still exists => nothing to change + } + + if (isRebuild) + rebuild(); +} + + + + +// ================== Auxiliary functions ================================= +void getTangentFeatures(const ConstraintPtr& theConstraint, + FeaturePtr& theFeature1, + FeaturePtr& theFeature2) +{ + AttributeRefAttrPtr aRefAttr = theConstraint->refattr(SketchPlugin_Constraint::ENTITY_A()); + theFeature1 = ModelAPI_Feature::feature(aRefAttr->object()); + aRefAttr = theConstraint->refattr(SketchPlugin_Constraint::ENTITY_B()); + theFeature2 = ModelAPI_Feature::feature(aRefAttr->object()); +} + +std::set collectCoincidences(FeaturePtr theFeature1, FeaturePtr theFeature2) +{ + const std::set& aRefs1 = theFeature1->data()->refsToMe(); + const std::set& aRefs2 = theFeature2->data()->refsToMe(); + + std::set aCoincidences; + std::set::const_iterator anIt; + + // collect coincidences referred to the first feature + for (anIt = aRefs1.begin(); anIt != aRefs1.end(); ++anIt) { + FeaturePtr aRef = ModelAPI_Feature::feature((*anIt)->owner()); + if (aRef && aRef->getKind() == SketchPlugin_ConstraintCoincidence::ID()) + aCoincidences.insert(aRef); + } + + // leave only coincidences referred to the second feature + std::set aCoincidencesBetweenFeatures; + for (anIt = aRefs2.begin(); anIt != aRefs2.end(); ++anIt) { + FeaturePtr aRef = ModelAPI_Feature::feature((*anIt)->owner()); + if (aCoincidences.find(aRef) != aCoincidences.end()) + aCoincidencesBetweenFeatures.insert(aRef); + } + + return aCoincidencesBetweenFeatures; +} + +std::list coincidentPoints(FeaturePtr theFeature1, FeaturePtr theFeature2) +{ + std::set aCoincidences = collectCoincidences(theFeature1, theFeature2); + // collect points only + std::list aCoincidentPoints; + std::set::const_iterator aCIt = aCoincidences.begin(); + for (; aCIt != aCoincidences.end(); ++ aCIt) { + for (int i = 0; i < CONSTRAINT_ATTR_SIZE; ++i) { + AttributeRefAttrPtr aRefAttr = (*aCIt)->refattr(SketchPlugin_Constraint::ENTITY_A()); + if (aRefAttr && !aRefAttr->isObject()) { + aCoincidentPoints.push_back(aRefAttr->attr()); + break; + } } } + return aCoincidentPoints; +} + +bool isArcArcTangencyInternal(EntityWrapperPtr theArc1, EntityWrapperPtr theArc2) +{ + std::shared_ptr aCirc1 = std::dynamic_pointer_cast( + GCS_EDGE_WRAPPER(theArc1)->entity()); + std::shared_ptr aCirc2 = std::dynamic_pointer_cast( + GCS_EDGE_WRAPPER(theArc2)->entity()); + + if (!aCirc1 || !aCirc2) + return false; + + double aDX = *(aCirc1->center.x) - *(aCirc2->center.x); + double aDY = *(aCirc1->center.y) - *(aCirc2->center.y); + double aDist = sqrt(aDX * aDX + aDY * aDY); + + return (aDist < *(aCirc1->rad) || aDist < *(aCirc2->rad)); +} + +// sets angle to 0 or 180 degrees +static void adjustAngleBetweenCurves(const GCSCurvePtr& theCurve1, + const GCSCurvePtr& theCurve2, + const GCSPointPtr& thePoint, + double* theAngle) +{ + double anAngle = GCS::System::calculateAngleViaPoint(*theCurve1, *theCurve2, *thePoint); + // bring angle to [-pi..pi] + if (anAngle > PI) anAngle -= 2.0 * PI; + if (anAngle < -PI) anAngle += 2.0 * PI; + // set angle value according to the current angle between curves + if (fabs(anAngle) <= PI / 2.) + *theAngle = 0.0; + else + *theAngle = PI; +} + + +ConstraintWrapperPtr createArcLineTangency(EntityWrapperPtr theEntity1, + EntityWrapperPtr theEntity2, + EntityWrapperPtr theSharedPoint, + double* theAngle) +{ + EdgeWrapperPtr anEntLine, anEntCirc; + if (theEntity1->type() == ENTITY_LINE) { + anEntLine = GCS_EDGE_WRAPPER(theEntity1); + anEntCirc = GCS_EDGE_WRAPPER(theEntity2); + } else { + anEntLine = GCS_EDGE_WRAPPER(theEntity2); + anEntCirc = GCS_EDGE_WRAPPER(theEntity1); + } + + std::shared_ptr aCirc = + std::dynamic_pointer_cast(anEntCirc->entity()); + std::shared_ptr aLine = + std::dynamic_pointer_cast(anEntLine->entity()); + + GCSConstraintPtr aNewConstr; + if (theSharedPoint) { + std::shared_ptr anArc = std::dynamic_pointer_cast(aCirc); + GCSPointPtr aPoint = + std::dynamic_pointer_cast(theSharedPoint)->point(); + + adjustAngleBetweenCurves(anArc, aLine, aPoint, theAngle); + aNewConstr = + GCSConstraintPtr(new GCS::ConstraintAngleViaPoint(*anArc, *aLine, *aPoint, theAngle)); + } else { + aNewConstr = + GCSConstraintPtr(new GCS::ConstraintP2LDistance(aCirc->center, *aLine, aCirc->rad)); + } + + return ConstraintWrapperPtr( + new PlaneGCSSolver_ConstraintWrapper(aNewConstr, CONSTRAINT_TANGENT_CIRCLE_LINE)); +} + +ConstraintWrapperPtr createArcArcTangency(EntityWrapperPtr theEntity1, + EntityWrapperPtr theEntity2, + bool theInternalTangency, + EntityWrapperPtr theSharedPoint, + double* theAngle) +{ + std::shared_ptr aCirc1 = + std::dynamic_pointer_cast(GCS_EDGE_WRAPPER(theEntity1)->entity()); + std::shared_ptr aCirc2 = + std::dynamic_pointer_cast(GCS_EDGE_WRAPPER(theEntity2)->entity()); + + GCSConstraintPtr aNewConstr; + if (theSharedPoint) { + std::shared_ptr anArc1 = std::dynamic_pointer_cast(aCirc1); + std::shared_ptr anArc2 = std::dynamic_pointer_cast(aCirc2); + GCSPointPtr aPoint = + std::dynamic_pointer_cast(theSharedPoint)->point(); + + adjustAngleBetweenCurves(anArc1, anArc2, aPoint, theAngle); + aNewConstr = + GCSConstraintPtr(new GCS::ConstraintAngleViaPoint(*anArc1, *anArc2, *aPoint, theAngle)); + } else { + aNewConstr = GCSConstraintPtr(new GCS::ConstraintTangentCircumf(aCirc1->center, aCirc2->center, + aCirc1->rad, aCirc2->rad, theInternalTangency)); + } + + return ConstraintWrapperPtr( + new PlaneGCSSolver_ConstraintWrapper(aNewConstr, CONSTRAINT_TANGENT_CIRCLE_CIRCLE)); } diff --git a/src/SketchSolver/SketchSolver_ConstraintTangent.h b/src/SketchSolver/SketchSolver_ConstraintTangent.h index 96428b47f..3f8524c34 100644 --- a/src/SketchSolver/SketchSolver_ConstraintTangent.h +++ b/src/SketchSolver/SketchSolver_ConstraintTangent.h @@ -19,15 +19,20 @@ public: /// Constructor based on SketchPlugin constraint SketchSolver_ConstraintTangent(ConstraintPtr theConstraint) : SketchSolver_Constraint(theConstraint), - isArcArcInternal(false) + isArcArcInternal(false), + myCurveCurveAngle(0.0) {} + /// \brief Notify this object about the feature is changed somewhere + virtual void notify(const FeaturePtr& theFeature, + PlaneGCSSolver_Update* theUpdater); + protected: - /// \brief Generate list of attributes of constraint in order useful for constraints - /// \param[out] theValue numerical characteristic of constraint (e.g. distance) - /// \param[out] theAttributes list of attributes to be filled - virtual void getAttributes(EntityWrapperPtr& theValue, - std::vector& theAttributes); + /// \brief Converts SketchPlugin constraint to a list of solver constraints + virtual void process(); + + /// \brief Remove current constraint from the storage and build is again + void rebuild(); /// \brief This method is used in derived objects to check consistency of constraint. /// E.g. the distance between line and point may be signed. @@ -35,6 +40,8 @@ protected: private: bool isArcArcInternal; + double myCurveCurveAngle; + AttributePtr mySharedPoint; }; #endif -- 2.39.2