Salome HOME
Issue #2387: Sketcher conservation of constraints
authorazv <azv@opencascade.com>
Tue, 26 Dec 2017 09:12:47 +0000 (12:12 +0300)
committerazv <azv@opencascade.com>
Tue, 26 Dec 2017 10:08:50 +0000 (13:08 +0300)
Keep distance and length constraints when applying Fillet in a sketch

src/ModelHighAPI/ModelHighAPI_FeatureStore.cpp
src/SketchPlugin/SketchPlugin_Fillet.cpp
src/SketchPlugin/SketchPlugin_Fillet.h
src/SketchPlugin/Test/TestFilletInteracting.py

index 995cb344f47ea8d4cd6c9fef688a929d0ac63bec..f0806abc45101a31433907385a62b9f688faf41b 100644 (file)
@@ -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<GeomDataAPI_Point2D>(theAttr);
     double aValues[2] = {anAttr->x(), anAttr->y()};
     dumpArray(aResult, aValues, 2);
index ab0dedbea5517d3032d159e993a513d760a644e0..3916cfb35e1a343b4171f63302a320a779c7d957 100644 (file)
@@ -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 <ModelAPI_AttributeDouble.h>
+#include <ModelAPI_AttributeInteger.h>
 #include <ModelAPI_AttributeRefAttr.h>
 #include <ModelAPI_Data.h>
 #include <ModelAPI_Events.h>
@@ -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<GeomDataAPI_Point2D> aStartPoint1;
-  int aFeatInd1 = myIsReversed ? 1 : 0;
-  int anAttrInd1 = (myIsReversed ? 2 : 0) + (myIsNotInversed[aFeatInd1] ? 0 : 1);
-  aStartPoint1 = std::dynamic_pointer_cast<GeomDataAPI_Point2D>(
-      myBaseFeatures[aFeatInd1]->attribute(myFeatAttributes[anAttrInd1]));
-  std::set<FeaturePtr> aFeaturesToBeRemoved1 =
-    findFeaturesToRemove(myBaseFeatures[aFeatInd1], aStartPoint1);
-
-  std::shared_ptr<GeomDataAPI_Point2D> aStartPoint2;
-  int aFeatInd2 = myIsReversed ? 0 : 1;
-  int anAttrInd2 = (myIsReversed ? 0 : 2) + (myIsNotInversed[aFeatInd2] ? 0 : 1);
-  aStartPoint2 = std::dynamic_pointer_cast<GeomDataAPI_Point2D>(
-      myBaseFeatures[aFeatInd2]->attribute(myFeatAttributes[anAttrInd2]));
-  std::set<FeaturePtr> 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<FeaturePtr> 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<GeomDataAPI_Point2D>(
+        myBaseFeatures[aFeatInd[i]]->attribute(myFeatAttributes[anAttrInd[i]]));
+    std::set<FeaturePtr> 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<GeomDataAPI_Point2D>(
+      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<FeaturePtr>& theFeaturesToRemove,
+    const AttributePoint2DPtr theFilletPoints[2])
+{
+  FeaturePtr aFilletApex;
+  struct Length {
+    AttributePtr myPoints[2];
+    std::string myValueText;
+    double myValueDouble;
+    GeomPnt2dPtr myFlyoutPoint;
+    int myLocationType;
+  };
+  std::list<Length> aLengthToDistance;
+
+  std::set<FeaturePtr>::iterator aFeat = theFeaturesToRemove.begin();
+  while (aFeat != theFeaturesToRemove.end()) {
+    std::shared_ptr<SketchPlugin_ConstraintDistance> aDistance =
+        std::dynamic_pointer_cast<SketchPlugin_ConstraintDistance>(*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<FeaturePtr>::iterator aKeepIt = aFeat++;
+      if (isUpdated)
+        theFeaturesToRemove.erase(aKeepIt);
+
+    } else {
+      std::shared_ptr<SketchPlugin_ConstraintLength> aLength =
+          std::dynamic_pointer_cast<SketchPlugin_ConstraintLength>(*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<GeomDataAPI_Point2D>(
+              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<Length>::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<GeomDataAPI_Point2D>(
+          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)
index dfd4c4b0c118cbb4f48906c15575a0725831dbba..14ea9f8d61e75f790cee965ff6fe906721882286 100644 (file)
@@ -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<GeomAPI_Pnt2d>& 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<FeaturePtr>& theFeaturesToRemove,
+                                         const AttributePoint2DPtr theFilletPoints[2]);
 
   bool findFeaturesContainingFilletPoint(std::shared_ptr<GeomDataAPI_Point2D> theFilletPoint);
 
index 60783ed527bff4b6f0af2ba9c270341a679160df..0b0de7d7845d4fcb7b5b46d0aee88ea1e796ec28 100644 (file)
@@ -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])