Salome HOME
Sketcher: merge errors when merging two groups (issue #1893)
[modules/shaper.git] / src / SketchSolver / SketchSolver_Group.cpp
index c2034c1576d3f703cda07d094aedd0db8721c458..867775e50d2301b9defd228c2831157cfe4c37a8 100644 (file)
@@ -12,7 +12,7 @@
 #include <SketchSolver_Error.h>
 #include <SketchSolver_Manager.h>
 
-#include <Events_Error.h>
+#include <Events_InfoMessage.h>
 #include <Events_Loop.h>
 #include <ModelAPI_AttributeString.h>
 #include <ModelAPI_Events.h>
@@ -32,6 +32,7 @@
 #include <SketchPlugin_ConstraintPerpendicular.h>
 #include <SketchPlugin_ConstraintRadius.h>
 #include <SketchPlugin_ConstraintRigid.h>
+#include <SketchPlugin_ConstraintSplit.h>
 #include <SketchPlugin_ConstraintTangent.h>
 #include <SketchPlugin_ConstraintVertical.h>
 #include <SketchPlugin_MultiRotation.h>
@@ -130,8 +131,21 @@ bool SketchSolver_Group::isInteract(FeaturePtr theFeature) const
   ConstraintConstraintMap::const_iterator anIt = myConstraints.begin();
   for (; !isInteracted && anIt != myConstraints.end(); ++anIt)
     if (anIt->first->getKind() == SketchPlugin_MultiRotation::ID() ||
-        anIt->first->getKind() == SketchPlugin_MultiTranslation::ID())
+        anIt->first->getKind() == SketchPlugin_MultiTranslation::ID()) {
       isInteracted = anIt->second->isUsed(theFeature);
+      if (isInteracted)
+        break;
+      // if theFeature is a constraint, check its attributes
+      ConstraintPtr aConstraint = std::dynamic_pointer_cast<SketchPlugin_Constraint>(theFeature);
+      if (!aConstraint)
+        continue;
+      for (int i = 0; i < 4 && !isInteracted; ++i) {
+        AttributeRefAttrPtr aRefAttr = aConstraint->refattr(aConstraint->ATTRIBUTE(i));
+        if (!aRefAttr)
+          continue;
+        isInteracted = anIt->second->isUsed((AttributePtr)aRefAttr);
+      }
+    }
   return isInteracted;
 }
 
@@ -166,9 +180,12 @@ bool SketchSolver_Group::changeConstraint(
     if (!aConstraint->error().empty()) {
       if (aConstraint->error() == SketchSolver_Error::NOT_INITIALIZED())
         return false; // some attribute are not initialized yet, don't show message
-      Events_Error::send(aConstraint->error(), this);
+      Events_InfoMessage("SketchSolver_Group", aConstraint->error(), this).send();
     }
     myConstraints[theConstraint] = aConstraint;
+
+    if (theConstraint->getKind() == SketchPlugin_ConstraintCoincidence::ID())
+      notifyCoincidenceChanged(myConstraints[theConstraint]);
   }
   else
     myConstraints[theConstraint]->update();
@@ -193,6 +210,7 @@ bool SketchSolver_Group::changeConstraint(
   return true;
 }
 
+// Update constraints if they contain specific feature
 static void updateMultiConstraints(ConstraintConstraintMap& theConstraints, FeaturePtr theFeature)
 {
   ConstraintConstraintMap::iterator aCIt = theConstraints.begin();
@@ -202,13 +220,25 @@ static void updateMultiConstraints(ConstraintConstraintMap& theConstraints, Feat
          aType == CONSTRAINT_MULTI_TRANSLATION)
         && aCIt->second->isUsed(theFeature))
       std::dynamic_pointer_cast<SketchSolver_ConstraintMulti>(aCIt->second)->update(true);
-    else if ((aType == CONSTRAINT_TANGENT_CIRCLE_LINE ||
-              aType == CONSTRAINT_SYMMETRIC)
+    else if ((aType == CONSTRAINT_TANGENT_CIRCLE_LINE || aType == CONSTRAINT_TANGENT_ARC_ARC ||
+              aType == CONSTRAINT_SYMMETRIC || aType == CONSTRAINT_ANGLE)
              && aCIt->second->isUsed(theFeature))
       aCIt->second->update();
   }
 }
 
+// Recalculate slave features of the Multi constraints
+static void updateMultiConstraints(ConstraintConstraintMap& theConstraints)
+{
+  ConstraintConstraintMap::iterator aCIt = theConstraints.begin();
+  for (; aCIt != theConstraints.end(); ++aCIt) {
+    SketchSolver_ConstraintType aType = aCIt->second->getType();
+    if ((aType == CONSTRAINT_MULTI_ROTATION ||
+         aType == CONSTRAINT_MULTI_TRANSLATION))
+      std::dynamic_pointer_cast<SketchSolver_ConstraintMulti>(aCIt->second)->update(true);
+  }
+}
+
 bool SketchSolver_Group::updateFeature(FeaturePtr theFeature)
 {
   if (!checkFeatureValidity(theFeature))
@@ -230,7 +260,7 @@ bool SketchSolver_Group::updateFeature(FeaturePtr theFeature)
   return isUpdated;
 }
 
-void SketchSolver_Group::moveFeature(FeaturePtr theFeature)
+bool SketchSolver_Group::moveFeature(FeaturePtr theFeature)
 {
   BuilderPtr aBuilder = SketchSolver_Manager::instance()->builder();
 
@@ -238,27 +268,32 @@ void SketchSolver_Group::moveFeature(FeaturePtr theFeature)
   myStorage->blockEvents(true);
   myStorage->refresh(true);
 
+  // Secondly, search attributes of the feature in the list of the Multi constraints and update them
+  updateMultiConstraints(myConstraints, theFeature);
+
   // Then, create temporary Fixed constraint
   SolverConstraintPtr aConstraint = aBuilder->createMovementConstraint(theFeature);
   if (!aConstraint)
-    return;
+    return false;
   aConstraint->process(myStorage, getId(), getWorkplaneId());
   if (aConstraint->error().empty())
     setTemporary(aConstraint);
 
-  // Secondly, search attributes of the feature in the list of the Multi constraints and update them
-  updateMultiConstraints(myConstraints, theFeature);
-
   // Workaround to process arcs.
   // When move unconstrained arc, add temporary constraint to fix radius.
   if (theFeature->getKind() == SketchPlugin_Arc::ID()) {
+    bool hasDup = myStorage->hasDuplicatedConstraint();
     SolverConstraintPtr aFixedRadius = aBuilder->createFixedArcRadiusConstraint(theFeature);
     if (aFixedRadius) {
       aFixedRadius->process(myStorage, getId(), getWorkplaneId());
-      if (aFixedRadius->error().empty())
+      hasDup = myStorage->hasDuplicatedConstraint() && !hasDup;
+      if (aFixedRadius->error().empty() && !hasDup)
         setTemporary(aFixedRadius);
+      else
+        aFixedRadius->remove();
     }
   }
+  return true;
 }
 
 // ============================================================================
@@ -308,7 +343,8 @@ bool SketchSolver_Group::resolveConstraints()
 {
   bool aResolved = false;
   bool isGroupEmpty = isEmpty() && myStorage->isEmpty();
-  if (myStorage->isNeedToResolve() && !isGroupEmpty) {
+  if (myStorage->isNeedToResolve() &&
+      (!isGroupEmpty || !myConflictingConstraints.empty() || myPrevResult == STATUS_FAILED)) {
     if (!mySketchSolver)
       mySketchSolver = SketchSolver_Manager::instance()->builder()->createSolver();
 
@@ -321,7 +357,7 @@ bool SketchSolver_Group::resolveConstraints()
     try {
       if (myStorage->hasDuplicatedConstraint())
         aResult = STATUS_INCONSISTENT;
-      else {
+      else if (!isGroupEmpty) {
         // To avoid overconstraint situation, we will remove temporary constraints one-by-one
         // and try to find the case without overconstraint
         bool isLastChance = false;
@@ -342,7 +378,8 @@ bool SketchSolver_Group::resolveConstraints()
       }
     } catch (...) {
 //      Events_Error::send(SketchSolver_Error::SOLVESPACE_CRASH(), this);
-      getWorkplane()->string(SketchPlugin_Sketch::SOLVER_ERROR())->setValue(SketchSolver_Error::SOLVESPACE_CRASH());
+      getWorkplane()->string(SketchPlugin_Sketch::SOLVER_ERROR())
+        ->setValue(SketchSolver_Error::SOLVESPACE_CRASH());
       if (myPrevResult == STATUS_OK || myPrevResult == STATUS_UNKNOWN) {
         // the error message should be changed before sending the message
         sendMessage(EVENT_SOLVER_FAILED);
@@ -351,27 +388,36 @@ bool SketchSolver_Group::resolveConstraints()
       mySketchSolver->undo();
       return false;
     }
-    if (aResult == STATUS_OK || aResult == STATUS_EMPTYSET) {  // solution succeeded, store results into correspondent attributes
+    // solution succeeded, store results into correspondent attributes
+    if (aResult == STATUS_OK || aResult == STATUS_EMPTYSET) {
+      myStorage->setNeedToResolve(false);
       myStorage->refresh();
+      updateMultiConstraints(myConstraints);
+      // multi-constraints updated some parameters, need to store them
+      if (myStorage->isNeedToResolve())
+        resolveConstraints();
+
       if (myPrevResult != STATUS_OK || myPrevResult == STATUS_UNKNOWN) {
         getWorkplane()->string(SketchPlugin_Sketch::SOLVER_ERROR())->setValue("");
-        // the error message should be changed before sending the message
-        sendMessage(EVENT_SOLVER_REPAIRED, myConflictingConstraints);
+        std::set<ObjectPtr> aConflicting = myConflictingConstraints;
         myConflictingConstraints.clear();
         myPrevResult = STATUS_OK;
+        // the error message should be changed before sending the message
+        sendMessage(EVENT_SOLVER_REPAIRED, aConflicting);
       }
     } else {
       mySketchSolver->undo();
       if (!myConstraints.empty()) {
         // the error message should be changed before sending the message
-        getWorkplane()->string(SketchPlugin_Sketch::SOLVER_ERROR())->setValue(SketchSolver_Error::CONSTRAINTS());
-        if (myPrevResult != aResult || myPrevResult == STATUS_UNKNOWN) {
+        getWorkplane()->string(SketchPlugin_Sketch::SOLVER_ERROR())
+          ->setValue(SketchSolver_Error::CONSTRAINTS());
+        if (myPrevResult != aResult ||
+            myPrevResult == STATUS_UNKNOWN ||
+            myPrevResult == STATUS_FAILED) {
           // Obtain list of conflicting constraints
           std::set<ObjectPtr> aConflicting = myStorage->getConflictingConstraints(mySketchSolver);
 
-          if (myConflictingConstraints.empty())
-            sendMessage(EVENT_SOLVER_FAILED, aConflicting);
-          else {
+          if (!myConflictingConstraints.empty()) {
             std::set<ObjectPtr>::iterator anIt = aConflicting.begin();
             for (; anIt != aConflicting.end(); ++anIt)
               myConflictingConstraints.erase(*anIt);
@@ -381,6 +427,8 @@ bool SketchSolver_Group::resolveConstraints()
             }
           }
           myConflictingConstraints = aConflicting;
+          if (!myConflictingConstraints.empty())
+            sendMessage(EVENT_SOLVER_FAILED, myConflictingConstraints);
           myPrevResult = aResult;
         }
       }
@@ -388,14 +436,15 @@ bool SketchSolver_Group::resolveConstraints()
 
     aResolved = true;
   } else if (!isGroupEmpty) {
-    // Check there are constraints Fixed. If they exist, update parameters by stored values
+    // Check if the group contains only constraints Fixed, update parameters by stored values
+    aResolved = true;
     ConstraintConstraintMap::iterator aCIt = myConstraints.begin();
     for (; aCIt != myConstraints.end(); ++aCIt)
-      if (aCIt->first->getKind() == SketchPlugin_ConstraintRigid::ID()) {
-        aResolved = true;
+      if (aCIt->first->getKind() != SketchPlugin_ConstraintRigid::ID()) {
+        aResolved = false;
         break;
       }
-    if (aCIt != myConstraints.end())
+    if (aCIt == myConstraints.end())
       myStorage->refresh();
   }
   removeTemporaryConstraints();
@@ -428,6 +477,11 @@ void SketchSolver_Group::mergeGroups(const SketchSolver_Group& theGroup)
       continue;
     changeConstraint(aConstr);
   }
+
+  // merge previous states of groups => use the worst state,
+  // so the group after rebuilt may discard error messages if exist
+  if (theGroup.myPrevResult > myPrevResult)
+    myPrevResult = theGroup.myPrevResult;
 }
 
 // ============================================================================
@@ -439,7 +493,8 @@ void SketchSolver_Group::splitGroup(std::list<SketchSolver_Group*>& theCuts)
 {
   // New storage will be used in trimmed way to store the list of constraint interacted together.
   StoragePtr aNewStorage = SketchSolver_Manager::instance()->builder()->createStorage(getId());
-  std::list<ConstraintWrapperPtr> aDummyVec; // empty vector to avoid creation of solver's constraints
+  // empty vector to avoid creation of solver's constraints
+  std::list<ConstraintWrapperPtr> aDummyVec;
 
   // Obtain constraints, which should be separated
   std::list<ConstraintPtr> anUnusedConstraints;
@@ -451,7 +506,8 @@ void SketchSolver_Group::splitGroup(std::list<SketchSolver_Group*>& theCuts)
       anUnusedConstraints.push_back(aCIter->first);
   }
 
-  // Check the unused constraints once again, because they may become interacted with new storage since adding constraints
+  // Check the unused constraints once again,
+  // because they may become interacted with new storage since adding constraints
   std::list<ConstraintPtr>::iterator aUnuseIt = anUnusedConstraints.begin();
   while (aUnuseIt != anUnusedConstraints.end()) {
     if (aNewStorage->isInteract(FeaturePtr(*aUnuseIt))) {
@@ -464,27 +520,39 @@ void SketchSolver_Group::splitGroup(std::list<SketchSolver_Group*>& theCuts)
   }
 
   std::list<SketchSolver_Group*>::iterator aCutsIter;
-  aUnuseIt = anUnusedConstraints.begin();
-  for ( ; aUnuseIt != anUnusedConstraints.end(); ++aUnuseIt) {
-    // Remove unused constraints
+  // Remove unused constraints
+  for (aUnuseIt = anUnusedConstraints.begin(); aUnuseIt != anUnusedConstraints.end(); ++aUnuseIt)
     removeConstraint(*aUnuseIt);
-    // Try to append constraint to already existent group
-    for (aCutsIter = theCuts.begin(); aCutsIter != theCuts.end(); ++aCutsIter)
-      if ((*aCutsIter)->isInteract(*aUnuseIt)) {
-        (*aCutsIter)->changeConstraint(*aUnuseIt);
-        break;
-      }
-    if (aCutsIter == theCuts.end()) {
+
+  SketchSolver_Group* aBaseGroup;
+  for (aUnuseIt = anUnusedConstraints.begin(); aUnuseIt != anUnusedConstraints.end(); ++aUnuseIt) {
+    aBaseGroup = 0;
+    aCutsIter = theCuts.begin();
+    // Try to append constraint to the current group
+    if (isInteract(*aUnuseIt)) {
+      changeConstraint(*aUnuseIt);
+      aBaseGroup = this;
+    } else {
+      // Try to append constraint to already existent group
+      for (; aCutsIter != theCuts.end(); ++aCutsIter)
+        if ((*aCutsIter)->isInteract(*aUnuseIt)) {
+          (*aCutsIter)->changeConstraint(*aUnuseIt);
+          break;
+        }
+    }
+
+    if (aCutsIter == theCuts.end() && !aBaseGroup) {
       // Add new group
       SketchSolver_Group* aGroup = new SketchSolver_Group(mySketch);
       aGroup->changeConstraint(*aUnuseIt);
       theCuts.push_back(aGroup);
     } else {
+      if (!aBaseGroup)
+        aBaseGroup = *aCutsIter++;
       // Find other groups interacting with constraint
-      std::list<SketchSolver_Group*>::iterator aBaseGroupIt = aCutsIter;
-      for (++aCutsIter; aCutsIter != theCuts.end(); ++aCutsIter)
+      for (; aCutsIter != theCuts.end(); ++aCutsIter)
         if ((*aCutsIter)->isInteract(*aUnuseIt)) {
-          (*aBaseGroupIt)->mergeGroups(**aCutsIter);
+          aBaseGroup->mergeGroups(**aCutsIter);
           std::list<SketchSolver_Group*>::iterator aRemoveIt = aCutsIter--;
           theCuts.erase(aRemoveIt);
         }
@@ -561,10 +629,18 @@ void SketchSolver_Group::removeConstraint(ConstraintPtr theConstraint)
   for (; aCIter != myConstraints.end(); aCIter++)
     if (aCIter->first == theConstraint) {
       aCIter->second->remove(); // the constraint is not fully removed
+      if (aCIter->first->getKind() == SketchPlugin_ConstraintCoincidence::ID())
+        notifyCoincidenceChanged(aCIter->second);
       break;
     }
   if (aCIter != myConstraints.end())
     myConstraints.erase(aCIter);
+  // empty group => clear storage
+  if (myConstraints.empty()) {
+    myStorage = StoragePtr();
+    mySketchSolver = SolverPtr();
+    updateWorkplane();
+  }
 }
 
 // ============================================================================
@@ -612,7 +688,8 @@ static double featureToVal(FeaturePtr theFeature)
     AttributeRefAttrPtr anAttrB = std::dynamic_pointer_cast<ModelAPI_AttributeRefAttr>(
         aConstraint->attribute(SketchPlugin_Constraint::ENTITY_B()));
     if (anAttrA && anAttrB && (anAttrA->isObject() || anAttrB->isObject()))
-      return 2.0; // point-on-line and point-on-circle should go before points coincidence constraint
+      // point-on-line and point-on-circle should go before points coincidence constraint
+      return 2.0;
     return 2.5;
   }
   if (anID == SketchPlugin_ConstraintDistance::ID() ||
@@ -632,7 +709,7 @@ static double featureToVal(FeaturePtr theFeature)
       anID == SketchPlugin_ConstraintMirror::ID())
     return 6.0;
   if (anID == SketchPlugin_ConstraintRigid::ID())
-    return 7.0;
+    return 0.5;
   if (anID == SketchPlugin_MultiRotation::ID() ||
       anID == SketchPlugin_MultiTranslation::ID())
     return 8.0;
@@ -646,7 +723,8 @@ static bool isLess(FeaturePtr theFeature1, FeaturePtr theFeature2)
   return featureToVal(theFeature1) < featureToVal(theFeature2);
 }
 
-std::list<FeaturePtr> SketchSolver_Group::selectApplicableFeatures(const std::set<ObjectPtr>& theObjects)
+std::list<FeaturePtr> SketchSolver_Group::
+  selectApplicableFeatures(const std::set<ObjectPtr>& theObjects)
 {
   std::list<FeaturePtr> aResult;
   std::list<FeaturePtr>::iterator aResIt;
@@ -654,14 +732,16 @@ std::list<FeaturePtr> SketchSolver_Group::selectApplicableFeatures(const std::se
   std::set<ObjectPtr>::const_iterator anObjIter = theObjects.begin();
   for (; anObjIter != theObjects.end(); ++anObjIter) {
     // Operate sketch itself and SketchPlugin features only.
-    // Also, the Fillet need to be skipped, because there are several separated constraints composing it.
+    // Also, the Fillet and Split need to be skipped,
+    // because there are several separated constraints composing it.
     FeaturePtr aFeature = std::dynamic_pointer_cast<ModelAPI_Feature>(*anObjIter);
     if (!aFeature)
       continue;
-    std::shared_ptr<SketchPlugin_Feature> aSketchFeature = 
+    std::shared_ptr<SketchPlugin_Feature> aSketchFeature =
         std::dynamic_pointer_cast<SketchPlugin_Feature>(aFeature);
     if ((aFeature->getKind() != SketchPlugin_Sketch::ID() && !aSketchFeature) ||
-        aFeature->getKind() == SketchPlugin_ConstraintFillet::ID())
+        aFeature->getKind() == SketchPlugin_ConstraintFillet::ID() ||
+        aFeature->getKind() == SketchPlugin_ConstraintSplit::ID())
       continue;
 
     // Find the place where to insert a feature
@@ -674,3 +754,13 @@ std::list<FeaturePtr> SketchSolver_Group::selectApplicableFeatures(const std::se
   return aResult;
 }
 
+void SketchSolver_Group::notifyCoincidenceChanged(SolverConstraintPtr theCoincidence)
+{
+  const std::list<EntityWrapperPtr>& aCoincident = theCoincidence->attributes();
+  EntityWrapperPtr anAttr1 = aCoincident.front();
+  EntityWrapperPtr anAttr2 = aCoincident.back();
+
+  ConstraintConstraintMap::iterator anIt = myConstraints.begin();
+  for (; anIt != myConstraints.end(); ++anIt)
+    anIt->second->notifyCoincidenceChanged(anAttr1, anAttr2);
+}