Salome HOME
Sketcher: merge errors when merging two groups (issue #1893)
[modules/shaper.git] / src / SketchSolver / SketchSolver_Group.cpp
index 950004104df5b14ffcbb08983f976bf11dcd9f02..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>
@@ -69,6 +70,15 @@ static void sendMessage(const char* theMessageName)
   Events_Loop::loop()->send(aMessage);
 }
 
+static void sendMessage(const char* theMessageName, const std::set<ObjectPtr>& theConflicting)
+{
+  std::shared_ptr<ModelAPI_SolverFailedMessage> aMessage =
+      std::shared_ptr<ModelAPI_SolverFailedMessage>(
+      new ModelAPI_SolverFailedMessage(Events_Loop::eventByName(theMessageName)));
+  aMessage->setObjects(theConflicting);
+  Events_Loop::loop()->send(aMessage);
+}
+
 
 
 // ========================================================
@@ -89,6 +99,11 @@ SketchSolver_Group::~SketchSolver_Group()
 {
   myConstraints.clear();
   GroupIndexer::REMOVE_GROUP(myID);
+  // send the message that there is no more conflicting constraints
+  if (!myConflictingConstraints.empty()) {
+    sendMessage(EVENT_SOLVER_REPAIRED, myConflictingConstraints);
+    myConflictingConstraints.clear();
+  }
 }
 
 // ============================================================================
@@ -112,7 +127,26 @@ bool SketchSolver_Group::isInteract(FeaturePtr theFeature) const
   if (isEmpty())
     return true;
   // Check interaction with the storage
-  return myStorage->isInteract(theFeature);
+  bool isInteracted = myStorage->isInteract(theFeature);
+  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()) {
+      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;
 }
 
 // ============================================================================
@@ -146,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();
@@ -173,14 +210,32 @@ 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();
   for (; aCIt != theConstraints.end(); ++aCIt) {
-    if ((aCIt->second->getType() == CONSTRAINT_MULTI_ROTATION ||
-         aCIt->second->getType() == CONSTRAINT_MULTI_TRANSLATION)
+    SketchSolver_ConstraintType aType = aCIt->second->getType();
+    if ((aType == CONSTRAINT_MULTI_ROTATION ||
+         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_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);
   }
 }
 
@@ -205,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();
 
@@ -213,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;
 }
 
 // ============================================================================
@@ -283,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();
 
@@ -296,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;
@@ -304,10 +365,10 @@ bool SketchSolver_Group::resolveConstraints()
           aResult = mySketchSolver->solve();
           if (aResult == STATUS_OK || aResult == STATUS_EMPTYSET || isLastChance)
             break;
-          // try to update parameters and resolve once again
-          ConstraintConstraintMap::iterator aConstrIt = myConstraints.begin();
-          for (; aConstrIt != myConstraints.end(); ++aConstrIt)
-            aConstrIt->second->update();
+////          // try to update parameters and resolve once again
+////          ConstraintConstraintMap::iterator aConstrIt = myConstraints.begin();
+////          for (; aConstrIt != myConstraints.end(); ++aConstrIt)
+////            aConstrIt->second->update();
           isLastChance = true;
 
           removeTemporaryConstraints();
@@ -317,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);
@@ -326,22 +388,47 @@ 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);
+        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()) {
-//      Events_Error::send(SketchSolver_Error::CONSTRAINTS(), this);
-        getWorkplane()->string(SketchPlugin_Sketch::SOLVER_ERROR())->setValue(SketchSolver_Error::CONSTRAINTS());
-        if (myPrevResult != aResult || myPrevResult == STATUS_UNKNOWN) {
-          // the error message should be changed before sending the message
-          sendMessage(EVENT_SOLVER_FAILED);
+        // 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 ||
+            myPrevResult == STATUS_FAILED) {
+          // Obtain list of conflicting constraints
+          std::set<ObjectPtr> aConflicting = myStorage->getConflictingConstraints(mySketchSolver);
+
+          if (!myConflictingConstraints.empty()) {
+            std::set<ObjectPtr>::iterator anIt = aConflicting.begin();
+            for (; anIt != aConflicting.end(); ++anIt)
+              myConflictingConstraints.erase(*anIt);
+            if (!myConflictingConstraints.empty()) {
+              // some constraints does not conflict, send corresponding message
+              sendMessage(EVENT_SOLVER_REPAIRED, myConflictingConstraints);
+            }
+          }
+          myConflictingConstraints = aConflicting;
+          if (!myConflictingConstraints.empty())
+            sendMessage(EVENT_SOLVER_FAILED, myConflictingConstraints);
           myPrevResult = aResult;
         }
       }
@@ -349,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();
@@ -389,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;
 }
 
 // ============================================================================
@@ -400,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;
@@ -412,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))) {
@@ -425,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);
         }
@@ -522,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();
+  }
 }
 
 // ============================================================================
@@ -573,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() ||
@@ -593,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;
@@ -607,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;
@@ -615,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
@@ -635,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);
+}