From faaea2f382805c9a5eb6d1e98a9e10354f62be5d Mon Sep 17 00:00:00 2001 From: azv Date: Tue, 26 Dec 2017 12:12:47 +0300 Subject: [PATCH] Issue #2387: Sketcher conservation of constraints Keep distance and length constraints when applying Fillet in a sketch --- .../ModelHighAPI_FeatureStore.cpp | 2 +- src/SketchPlugin/SketchPlugin_Fillet.cpp | 189 +++++++++++++++--- src/SketchPlugin/SketchPlugin_Fillet.h | 11 +- .../Test/TestFilletInteracting.py | 53 +++-- 4 files changed, 212 insertions(+), 43 deletions(-) diff --git a/src/ModelHighAPI/ModelHighAPI_FeatureStore.cpp b/src/ModelHighAPI/ModelHighAPI_FeatureStore.cpp index 995cb344f..f0806abc4 100644 --- a/src/ModelHighAPI/ModelHighAPI_FeatureStore.cpp +++ b/src/ModelHighAPI/ModelHighAPI_FeatureStore.cpp @@ -334,7 +334,7 @@ std::string ModelHighAPI_FeatureStore::dumpAttr(const AttributePtr& theAttr) { } else if (aType == GeomDataAPI_Point2D::typeId()) { // do not dump flyout point for constraints as it may be changed unexpectedly if (theAttr->id() == "ConstraintFlyoutValuePnt") - return ""; + return "__notinitialized__"; AttributePoint2DPtr anAttr = std::dynamic_pointer_cast(theAttr); double aValues[2] = {anAttr->x(), anAttr->y()}; dumpArray(aResult, aValues, 2); diff --git a/src/SketchPlugin/SketchPlugin_Fillet.cpp b/src/SketchPlugin/SketchPlugin_Fillet.cpp index ab0dedbea..3916cfb35 100644 --- a/src/SketchPlugin/SketchPlugin_Fillet.cpp +++ b/src/SketchPlugin/SketchPlugin_Fillet.cpp @@ -24,6 +24,7 @@ #include "SketchPlugin_Line.h" #include "SketchPlugin_Point.h" #include "SketchPlugin_Sketch.h" +#include "SketchPlugin_ConstraintDistance.h" #include "SketchPlugin_ConstraintEqual.h" #include "SketchPlugin_ConstraintCoincidence.h" #include "SketchPlugin_ConstraintLength.h" @@ -32,6 +33,8 @@ #include "SketchPlugin_ConstraintRadius.h" #include "SketchPlugin_Tools.h" +#include +#include #include #include #include @@ -102,32 +105,30 @@ void SketchPlugin_Fillet::execute() // create feature for fillet arc FeaturePtr aFilletArc = createFilletArc(); - // Delete features with refs to points of edges. - std::shared_ptr aStartPoint1; - int aFeatInd1 = myIsReversed ? 1 : 0; - int anAttrInd1 = (myIsReversed ? 2 : 0) + (myIsNotInversed[aFeatInd1] ? 0 : 1); - aStartPoint1 = std::dynamic_pointer_cast( - myBaseFeatures[aFeatInd1]->attribute(myFeatAttributes[anAttrInd1])); - std::set aFeaturesToBeRemoved1 = - findFeaturesToRemove(myBaseFeatures[aFeatInd1], aStartPoint1); - - std::shared_ptr aStartPoint2; - int aFeatInd2 = myIsReversed ? 0 : 1; - int anAttrInd2 = (myIsReversed ? 0 : 2) + (myIsNotInversed[aFeatInd2] ? 0 : 1); - aStartPoint2 = std::dynamic_pointer_cast( - myBaseFeatures[aFeatInd2]->attribute(myFeatAttributes[anAttrInd2])); - std::set aFeaturesToBeRemoved2 = - findFeaturesToRemove(myBaseFeatures[aFeatInd2], aStartPoint2); - - aFeaturesToBeRemoved1.insert(aFeaturesToBeRemoved2.begin(), aFeaturesToBeRemoved2.end()); - ModelAPI_Tools::removeFeaturesAndReferences(aFeaturesToBeRemoved1); - Events_Loop::loop()->flush(Events_Loop::eventByName(EVENT_OBJECT_DELETED)); + // collect features referred to the edges participating in fillet + AttributePoint2DPtr aFilletPoints[2]; + int aFeatInd[2]; + int anAttrInd[2]; + std::set aFeaturesToBeRemoved; + for (int i = 0; i < 2; ++i) { + bool isFirstIndex = (i == 0); + aFeatInd[i] = myIsReversed == isFirstIndex ? 1 : 0; + anAttrInd[i] = (myIsReversed == isFirstIndex ? 2 : 0) + (myIsNotInversed[aFeatInd[i]] ? 0 : 1); + aFilletPoints[i] = std::dynamic_pointer_cast( + myBaseFeatures[aFeatInd[i]]->attribute(myFeatAttributes[anAttrInd[i]])); + std::set aRemove = + findFeaturesToRemove(myBaseFeatures[aFeatInd[i]], aFilletPoints[i]); + aFeaturesToBeRemoved.insert(aRemove.begin(), aRemove.end()); + } + + // keep "distance" constraints and remove all other references + removeReferencesButKeepDistances(aFeaturesToBeRemoved, aFilletPoints); // Update fillet edges. recalculateAttributes(aFilletArc, SketchPlugin_Arc::START_ID(), - myBaseFeatures[aFeatInd1], myFeatAttributes[anAttrInd1]); + myBaseFeatures[aFeatInd[0]], myFeatAttributes[anAttrInd[0]]); recalculateAttributes(aFilletArc, SketchPlugin_Arc::END_ID(), - myBaseFeatures[aFeatInd2], myFeatAttributes[anAttrInd2]); + myBaseFeatures[aFeatInd[1]], myFeatAttributes[anAttrInd[1]]); FeaturePtr aConstraint; @@ -135,12 +136,12 @@ void SketchPlugin_Fillet::execute() aConstraint = SketchPlugin_Tools::createConstraint(sketch(), SketchPlugin_ConstraintCoincidence::ID(), aFilletArc->attribute(SketchPlugin_Arc::START_ID()), - myBaseFeatures[aFeatInd1]->attribute(myFeatAttributes[anAttrInd1])); + myBaseFeatures[aFeatInd[0]]->attribute(myFeatAttributes[anAttrInd[0]])); ModelAPI_EventCreator::get()->sendUpdated(aConstraint, anUpdateEvent); aConstraint = SketchPlugin_Tools::createConstraint(sketch(), SketchPlugin_ConstraintCoincidence::ID(), aFilletArc->attribute(SketchPlugin_Arc::END_ID()), - myBaseFeatures[aFeatInd2]->attribute(myFeatAttributes[anAttrInd2])); + myBaseFeatures[aFeatInd[1]]->attribute(myFeatAttributes[anAttrInd[1]])); ModelAPI_EventCreator::get()->sendUpdated(aConstraint, anUpdateEvent); // Create tangent features. @@ -154,9 +155,8 @@ void SketchPlugin_Fillet::execute() } // Send events to update the sub-features by the solver. - if(isUpdateFlushed) { + if (isUpdateFlushed) Events_Loop::loop()->setFlushed(anUpdateEvent, true); - } } AISObjectPtr SketchPlugin_Fillet::getAISObject(AISObjectPtr thePrevious) @@ -326,6 +326,143 @@ FeaturePtr SketchPlugin_Fillet::createFilletArc() return aFilletArc; } +FeaturePtr SketchPlugin_Fillet::createFilletApex(const GeomPnt2dPtr& theCoordinates) +{ + FeaturePtr anApex = sketch()->addFeature(SketchPlugin_Point::ID()); + AttributePoint2DPtr aCoord = std::dynamic_pointer_cast( + anApex->attribute(SketchPlugin_Point::COORD_ID())); + aCoord->setValue(theCoordinates); + + // additional coincidence constraints + static Events_ID anUpdateEvent = Events_Loop::eventByName(EVENT_OBJECT_UPDATED); + FeaturePtr aConstraint; + for (int i = 0; i < 2; i++) { + aConstraint = SketchPlugin_Tools::createConstraint(sketch(), + SketchPlugin_ConstraintCoincidence::ID(), + aCoord, + myBaseFeatures[i]->lastResult()); + aConstraint->execute(); + ModelAPI_EventCreator::get()->sendUpdated(aConstraint, anUpdateEvent); + } + + return anApex; +} + +void SketchPlugin_Fillet::removeReferencesButKeepDistances( + std::set& theFeaturesToRemove, + const AttributePoint2DPtr theFilletPoints[2]) +{ + FeaturePtr aFilletApex; + struct Length { + AttributePtr myPoints[2]; + std::string myValueText; + double myValueDouble; + GeomPnt2dPtr myFlyoutPoint; + int myLocationType; + }; + std::list aLengthToDistance; + + std::set::iterator aFeat = theFeaturesToRemove.begin(); + while (aFeat != theFeaturesToRemove.end()) { + std::shared_ptr aDistance = + std::dynamic_pointer_cast(*aFeat); + if (aDistance) { + if (!aFilletApex) + aFilletApex = createFilletApex(theFilletPoints[0]->pnt()); + // update attributes of distance constraints + bool isUpdated = false; + for (int attrInd = 0; attrInd < CONSTRAINT_ATTR_SIZE && !isUpdated; ++attrInd) { + AttributeRefAttrPtr aRefAttr = + aDistance->refattr(SketchPlugin_Constraint::ATTRIBUTE(attrInd)); + if (aRefAttr && !aRefAttr->isObject() && + (aRefAttr->attr() == theFilletPoints[0] || aRefAttr->attr() == theFilletPoints[1])) { + aRefAttr->setAttr(aFilletApex->attribute(SketchPlugin_Point::COORD_ID())); + isUpdated = true; + } + } + // avoid distance from removing if it is updated + std::set::iterator aKeepIt = aFeat++; + if (isUpdated) + theFeaturesToRemove.erase(aKeepIt); + + } else { + std::shared_ptr aLength = + std::dynamic_pointer_cast(*aFeat); + if (aLength) { + if (!aFilletApex) + aFilletApex = createFilletApex(theFilletPoints[0]->pnt()); + // remove Length, but create new distance constraint + AttributeRefAttrPtr aRefAttr = + aLength->refattr(SketchPlugin_Constraint::ENTITY_A()); + FeaturePtr aLine = ModelAPI_Feature::feature(aRefAttr->object()); + if (aLine) { + aLengthToDistance.push_back(Length()); + Length& aNewLength = aLengthToDistance.back(); + // main attrbutes + for (int i = 0; i < 2; ++i) { + AttributePtr anAttr = aLine->attribute( + i == 0 ? SketchPlugin_Line::START_ID() : SketchPlugin_Line::END_ID()); + if (anAttr == theFilletPoints[0] || anAttr == theFilletPoints[1]) + aNewLength.myPoints[i] = aFilletApex->attribute(SketchPlugin_Point::COORD_ID()); + else + aNewLength.myPoints[i] = anAttr; + } + // value + AttributeDoublePtr aValue = aLength->real(SketchPlugin_Constraint::VALUE()); + aNewLength.myValueDouble = aValue->value(); + aNewLength.myValueText = aValue->text(); + // auxiliary attributes + AttributePoint2DPtr aFlyoutAttr = std::dynamic_pointer_cast( + aLength->attribute(SketchPlugin_ConstraintLength::FLYOUT_VALUE_PNT())); + if (aFlyoutAttr && aFlyoutAttr->isInitialized()) + aNewLength.myFlyoutPoint = aFlyoutAttr->pnt(); + AttributeIntegerPtr aLocationAttr = + aLength->integer(SketchPlugin_ConstraintLength::LOCATION_TYPE_ID()); + if (aLocationAttr && aLocationAttr->isInitialized()) + aNewLength.myLocationType = aLocationAttr->value(); + else + aNewLength.myLocationType = -1; + } + } + + ++aFeat; + } + } + + // remove references + ModelAPI_Tools::removeFeaturesAndReferences(theFeaturesToRemove); + Events_Loop::loop()->flush(Events_Loop::eventByName(EVENT_OBJECT_DELETED)); + + // restore Length constraints as point-point distances + FeaturePtr aConstraint; + std::list::iterator anIt = aLengthToDistance.begin(); + for (; anIt != aLengthToDistance.end(); ++anIt) { + aConstraint = SketchPlugin_Tools::createConstraint(sketch(), + SketchPlugin_ConstraintDistance::ID(), anIt->myPoints[0], anIt->myPoints[1]); + // set value + AttributeDoublePtr aValue = aConstraint->real(SketchPlugin_Constraint::VALUE()); + if (anIt->myValueText.empty()) + aValue->setValue(anIt->myValueDouble); + else + aValue->setText(anIt->myValueText); + // set flyout point if exists + if (anIt->myFlyoutPoint) { + AttributePoint2DPtr aFlyoutAttr = std::dynamic_pointer_cast( + aConstraint->attribute(SketchPlugin_ConstraintDistance::FLYOUT_VALUE_PNT())); + aFlyoutAttr->setValue(anIt->myFlyoutPoint); + } + // set location type if initialized + if (anIt->myLocationType >= 0) { + AttributeIntegerPtr aLocationType = + aConstraint->integer(SketchPlugin_ConstraintDistance::LOCATION_TYPE_ID()); + aLocationType->setValue(anIt->myLocationType); + } + aConstraint->execute(); + ModelAPI_EventCreator::get()->sendUpdated(aConstraint, + Events_Loop::eventByName(EVENT_OBJECT_UPDATED)); + } +} + // ========= Auxiliary functions ================= void recalculateAttributes(FeaturePtr theNewArc, const std::string& theNewArcAttribute, FeaturePtr theFeature, const std::string& theFeatureAttribute) diff --git a/src/SketchPlugin/SketchPlugin_Fillet.h b/src/SketchPlugin/SketchPlugin_Fillet.h index dfd4c4b0c..14ea9f8d6 100644 --- a/src/SketchPlugin/SketchPlugin_Fillet.h +++ b/src/SketchPlugin/SketchPlugin_Fillet.h @@ -86,7 +86,16 @@ private: /// Create new feature presenting a fillet arc and initialize its parameters FeaturePtr createFilletArc(); - void createCoincidenceWithFilletArc(); + /// Create point representing fillet apex and additional coincidences with fillet features + FeaturePtr createFilletApex(const std::shared_ptr& theCoordinates); + + /// Remove references to a feature participating in fillet, + /// but transfer "distance" constraints to features after fillet + /// \param[in, out] theFeaturesToRemove features referred to fillet edges + /// (all distances will be processed and removed from this list) + /// \param[in] theFilletPoints fillet points from participating features + void removeReferencesButKeepDistances(std::set& theFeaturesToRemove, + const AttributePoint2DPtr theFilletPoints[2]); bool findFeaturesContainingFilletPoint(std::shared_ptr theFilletPoint); diff --git a/src/SketchPlugin/Test/TestFilletInteracting.py b/src/SketchPlugin/Test/TestFilletInteracting.py index 60783ed52..0b0de7d78 100644 --- a/src/SketchPlugin/Test/TestFilletInteracting.py +++ b/src/SketchPlugin/Test/TestFilletInteracting.py @@ -27,6 +27,7 @@ from GeomAPI import * from GeomDataAPI import * from ModelAPI import * +from SketchAPI import * import math import unittest from salome.shaper import model @@ -421,7 +422,7 @@ class TestFilletInteracting(unittest.TestCase): model.do() self.checkDOF() self.mySketch.setFillet(aSketchLineA.startPoint()) - self.myDOF += 2 + self.myDOF += 1 model.do() self.checkFillet() self.checkDOF() @@ -429,9 +430,10 @@ class TestFilletInteracting(unittest.TestCase): self.collectFeatures() self.checkNbFeatures("SketchLine", 2) self.checkNbFeatures("SketchArc", 1) - self.checkNbFeatures("SketchConstraintCoincidence", 2) + self.checkNbFeatures("SketchConstraintCoincidence", 4) # Additionally 2 coincidences for apex with fillet objects self.checkNbFeatures("SketchConstraintTangent", 2) self.checkNbFeatures("SketchConstraintLength", 0) # Length constraint expected to be removed + self.checkNbFeatures("SketchConstraintDistance", 1) # Distance constraint should appear instead of Length self.checkNbFeatures("SketchFillet", 0) model.testNbSubShapes(self.mySketch, GeomAPI_Shape.FACE, [0]) @@ -474,24 +476,40 @@ class TestFilletInteracting(unittest.TestCase): def test_fillet_with_distance(self): """ Test 12. Fillet on two connected lines in case of Distance constraint applied """ - aSketchLineA = self.mySketch.addLine(10., 10., 20., 10.) - aSketchLineB = self.mySketch.addLine(10., 10., 10., 20.) - self.myDOF += 8 + aSketchLineA = self.mySketch.addLine(20, 20, 70, 20) + aSketchLineB = self.mySketch.addLine(70, 20, 70, 53.16624790355412) + aSketchLineC = self.mySketch.addLine(70, 53.16624790355412, 20, 20) + self.myDOF += 12 + model.do() self.checkDOF() - self.mySketch.setCoincident(aSketchLineA.startPoint(), aSketchLineB.startPoint()) - self.myDOF -= 2 + # coincidences + self.mySketch.setCoincident(aSketchLineA.endPoint(), aSketchLineB.startPoint()) + self.mySketch.setCoincident(aSketchLineB.endPoint(), aSketchLineC.startPoint()) + self.mySketch.setCoincident(aSketchLineA.startPoint(), aSketchLineC.endPoint()) + self.myDOF -= 6 model.do() self.checkDOF() - # third line to apply Distance constraints - aSketchLineC = self.mySketch.addLine(10., 0., 20., 5.) - self.myDOF += 4 - self.mySketch.setDistance(aSketchLineB.startPoint(), aSketchLineC.result(), 10.) - self.mySketch.setDistance(aSketchLineB.endPoint(), aSketchLineC.result(), 5.) + # other constraints + self.mySketch.setHorizontal(aSketchLineA.result()) + self.mySketch.setVertical(aSketchLineB.result()) self.myDOF -= 2 model.do() self.checkDOF() + # construction point + aProjection = self.mySketch.addProjection(model.selection("VERTEX", "Origin"), False) + aSketchPoint = SketchAPI_Point(aProjection.createdFeature()) + model.do() + # distances + self.mySketch.setLength(aSketchLineA.result(), 50) + self.mySketch.setDistance(aSketchLineA.startPoint(), aSketchLineC.startPoint(), 60, True) + self.mySketch.setHorizontalDistance(aSketchPoint.coordinates(), aSketchLineA.startPoint(), 20) + self.mySketch.setVerticalDistance(aSketchPoint.coordinates(), aSketchLineC.endPoint(), 20) + self.myDOF -= 4 + model.do() + self.checkDOF() + self.mySketch.setFillet(aSketchLineA.startPoint()) - self.myDOF += 2 # Distance has been removed + self.myDOF += 1 model.do() self.checkFillet() self.checkDOF() @@ -499,9 +517,14 @@ class TestFilletInteracting(unittest.TestCase): self.collectFeatures() self.checkNbFeatures("SketchLine", 3) self.checkNbFeatures("SketchArc", 1) - self.checkNbFeatures("SketchConstraintCoincidence", 2) + self.checkNbFeatures("SketchConstraintCoincidence", 6) # Additionally 2 coincidences for apex with fillet objects + self.checkNbFeatures("SketchConstraintHorizontal", 1) + self.checkNbFeatures("SketchConstraintVertical", 1) self.checkNbFeatures("SketchConstraintTangent", 2) - self.checkNbFeatures("SketchConstraintDistance", 1) # only one Distance should be left + self.checkNbFeatures("SketchConstraintLength", 0) # Length translated to Distance + self.checkNbFeatures("SketchConstraintDistance", 2) # Length translated to Distance + self.checkNbFeatures("SketchConstraintDistanceHorizontal", 1) + self.checkNbFeatures("SketchConstraintDistanceVertical", 1) self.checkNbFeatures("SketchFillet", 0) model.testNbSubShapes(self.mySketch, GeomAPI_Shape.FACE, [0]) -- 2.39.2