Salome HOME
Task 2.4. Ability to modify the radius of circles and arcs of circle with the mouse
[modules/shaper.git] / src / SketchSolver / SketchSolver_Group.cpp
1 // Copyright (C) 2014-20xx CEA/DEN, EDF R&D
2
3 // File:    SketchSolver_Group.cpp
4 // Created: 27 May 2014
5 // Author:  Artem ZHIDKOV
6
7 #include "SketchSolver_Group.h"
8 #include <SketchSolver_Error.h>
9 #include <SketchSolver_Manager.h>
10
11 #include <PlaneGCSSolver_Solver.h>
12 #include <PlaneGCSSolver_Storage.h>
13 #include <PlaneGCSSolver_Tools.h>
14
15 #include <Events_InfoMessage.h>
16 #include <ModelAPI_AttributeString.h>
17 #include <ModelAPI_Events.h>
18 #include <SketchPlugin_ConstraintMirror.h>
19 #include <SketchPlugin_ConstraintRigid.h>
20 #include <SketchPlugin_MultiRotation.h>
21 #include <SketchPlugin_MultiTranslation.h>
22
23
24 static void sendMessage(const char* theMessageName)
25 {
26   std::shared_ptr<Events_Message> aMessage = std::shared_ptr<Events_Message>(
27       new Events_Message(Events_Loop::eventByName(theMessageName)));
28   Events_Loop::loop()->send(aMessage);
29 }
30
31 static void sendMessage(const char* theMessageName, const std::set<ObjectPtr>& theConflicting)
32 {
33   std::shared_ptr<ModelAPI_SolverFailedMessage> aMessage =
34       std::shared_ptr<ModelAPI_SolverFailedMessage>(
35       new ModelAPI_SolverFailedMessage(Events_Loop::eventByName(theMessageName)));
36   aMessage->setObjects(theConflicting);
37   Events_Loop::loop()->send(aMessage);
38 }
39
40 static void sendMessage(const char* theMessageName,
41                         const CompositeFeaturePtr& theSketch,
42                         const int theDOF)
43 {
44   std::shared_ptr<ModelAPI_SolverFailedMessage> aMessage =
45       std::shared_ptr<ModelAPI_SolverFailedMessage>(
46       new ModelAPI_SolverFailedMessage(Events_Loop::eventByName(theMessageName)));
47
48   std::set<ObjectPtr> anObjects;
49   anObjects.insert(theSketch);
50   aMessage->setObjects(anObjects);
51   aMessage->dof(theDOF);
52
53   Events_Loop::loop()->send(aMessage);
54 }
55
56
57
58 // ========================================================
59 // =========       SketchSolver_Group       ===============
60 // ========================================================
61
62 SketchSolver_Group::SketchSolver_Group(const CompositeFeaturePtr& theWorkplane)
63   : mySketch(theWorkplane),
64     myPrevResult(PlaneGCSSolver_Solver::STATUS_UNKNOWN),
65     myDOF(0),
66     myIsEventsBlocked(false),
67     myMultiConstraintUpdateStack(0)
68 {
69   mySketchSolver = SolverPtr(new PlaneGCSSolver_Solver);
70   myStorage = StoragePtr(new PlaneGCSSolver_Storage(mySketchSolver));
71 }
72
73 SketchSolver_Group::~SketchSolver_Group()
74 {
75   myConstraints.clear();
76   // send the message that there is no more conflicting constraints
77   if (!myConflictingConstraints.empty()) {
78     sendMessage(EVENT_SOLVER_REPAIRED, myConflictingConstraints);
79     myConflictingConstraints.clear();
80   }
81 }
82
83 // ============================================================================
84 //  Function: changeConstraint
85 //  Class:    SketchSolver_Group
86 //  Purpose:  create/update the constraint in the group
87 // ============================================================================
88 bool SketchSolver_Group::changeConstraint(
89     std::shared_ptr<SketchPlugin_Constraint> theConstraint)
90 {
91   bool isNewConstraint = myConstraints.find(theConstraint) == myConstraints.end();
92   if (isNewConstraint) {
93     // Add constraint to the current group
94     SolverConstraintPtr aConstraint = PlaneGCSSolver_Tools::createConstraint(theConstraint);
95     if (!aConstraint)
96       return false;
97     aConstraint->process(myStorage, myIsEventsBlocked);
98     if (!aConstraint->error().empty()) {
99       if (aConstraint->error() == SketchSolver_Error::NOT_INITIALIZED())
100         return false; // some attribute are not initialized yet, don't show message
101       Events_InfoMessage("SketchSolver_Group", aConstraint->error(), this).send();
102     }
103     myConstraints[theConstraint] = aConstraint;
104   }
105   else
106     myConstraints[theConstraint]->update();
107
108   // constraint is created/updated => reset stack of "multi" constraints updates
109   myMultiConstraintUpdateStack = 0;
110   return true;
111 }
112
113 bool SketchSolver_Group::updateFeature(FeaturePtr theFeature)
114 {
115   return myStorage->update(theFeature);
116 }
117
118 #ifdef SUPPORT_NEW_MOVE
119 template <class Type>
120 static SolverConstraintPtr move(StoragePtr theStorage,
121                                 SolverPtr theSketchSolver,
122                                 int theSketchDOF,
123                                 bool theEventsBlocked,
124                                 Type theFeatureOrPoint,
125                                 const std::shared_ptr<GeomAPI_Pnt2d>& theFrom,
126                                 const std::shared_ptr<GeomAPI_Pnt2d>& theTo)
127 {
128   bool isEntityExists = (theStorage->entity(theFeatureOrPoint).get() != 0);
129   if (theSketchDOF == 0 && isEntityExists) {
130     // avoid moving elements of fully constrained sketch
131     theStorage->refresh();
132     return SolverConstraintPtr();
133   }
134
135   // Create temporary Fixed constraint
136   std::shared_ptr<SketchSolver_ConstraintMovement> aConstraint =
137       PlaneGCSSolver_Tools::createMovementConstraint(theFeatureOrPoint);
138   if (aConstraint) {
139     SolverConstraintPtr(aConstraint)->process(theStorage, theEventsBlocked);
140     if (aConstraint->error().empty()) {
141       if (!theStorage->isEmpty())
142         theStorage->setNeedToResolve(true);
143
144       theSketchSolver->initialize();
145       aConstraint->startPoint(theFrom);
146       aConstraint->moveTo(theTo);
147     } else
148       theStorage->notify(aConstraint->movedFeature());
149   }
150
151   return aConstraint;
152 }
153
154 bool SketchSolver_Group::moveFeature(FeaturePtr theFeature,
155                                      const std::shared_ptr<GeomAPI_Pnt2d>& theFrom,
156                                      const std::shared_ptr<GeomAPI_Pnt2d>& theTo)
157 {
158   SolverConstraintPtr aConstraint =
159       move(myStorage, mySketchSolver, myDOF, myIsEventsBlocked, theFeature, theFrom, theTo);
160   setTemporary(aConstraint);
161   return true;
162 }
163
164 bool SketchSolver_Group::movePoint(AttributePtr theAttribute,
165                                    const std::shared_ptr<GeomAPI_Pnt2d>& theFrom,
166                                    const std::shared_ptr<GeomAPI_Pnt2d>& theTo)
167 {
168   SolverConstraintPtr aConstraint =
169       move(myStorage, mySketchSolver, myDOF, myIsEventsBlocked, theAttribute, theFrom, theTo);
170   setTemporary(aConstraint);
171   return true;
172 }
173 #else
174 bool SketchSolver_Group::moveFeature(FeaturePtr theFeature)
175 {
176   bool isFeatureExists = (myStorage->entity(theFeature).get() != 0);
177   if (myDOF == 0 && isFeatureExists) {
178     // avoid moving elements of fully constrained sketch
179     myStorage->refresh();
180     return true;
181   }
182
183   // Create temporary Fixed constraint
184   std::shared_ptr<SketchSolver_ConstraintFixed> aConstraint =
185       PlaneGCSSolver_Tools::createMovementConstraint(theFeature);
186   if (!aConstraint)
187     return false;
188   SolverConstraintPtr(aConstraint)->process(myStorage, myIsEventsBlocked);
189   if (aConstraint->error().empty()) {
190     setTemporary(aConstraint);
191     if (!myStorage->isEmpty())
192       myStorage->setNeedToResolve(true);
193
194     mySketchSolver->initialize();
195     aConstraint->moveFeature();
196   } else
197     myStorage->notify(theFeature);
198
199   return true;
200 }
201 #endif
202
203 // ============================================================================
204 //  Function: resolveConstraints
205 //  Class:    SketchSolver_Group
206 //  Purpose:  solve the set of constraints for the current group
207 // ============================================================================
208 bool SketchSolver_Group::resolveConstraints()
209 {
210   static const int MAX_STACK_SIZE = 5;
211   // check the "Multi" constraints do not drop sketch into infinite loop
212   if (myMultiConstraintUpdateStack > MAX_STACK_SIZE) {
213     myMultiConstraintUpdateStack = 0;
214     myPrevResult = PlaneGCSSolver_Solver::STATUS_FAILED;
215     // generate error message due to loop update of the sketch
216     getWorkplane()->string(SketchPlugin_Sketch::SOLVER_ERROR())
217       ->setValue(SketchSolver_Error::INFINITE_LOOP());
218     sendMessage(EVENT_SOLVER_FAILED, myConflictingConstraints);
219     return false;
220   }
221
222   bool aResolved = false;
223   bool isGroupEmpty = isEmpty() && myStorage->isEmpty();
224   if (myStorage->isNeedToResolve() &&
225       (!isGroupEmpty || !myConflictingConstraints.empty() ||
226         myPrevResult == PlaneGCSSolver_Solver::STATUS_FAILED)) {
227
228     PlaneGCSSolver_Solver::SolveStatus aResult = PlaneGCSSolver_Solver::STATUS_OK;
229     try {
230       if (!isGroupEmpty)
231         aResult = mySketchSolver->solve();
232       if (aResult == PlaneGCSSolver_Solver::STATUS_FAILED &&
233           !myTempConstraints.empty()) {
234         mySketchSolver->undo();
235         removeTemporaryConstraints();
236         aResult = mySketchSolver->solve();
237       }
238     } catch (...) {
239       getWorkplane()->string(SketchPlugin_Sketch::SOLVER_ERROR())
240         ->setValue(SketchSolver_Error::SOLVESPACE_CRASH());
241       if (myPrevResult == PlaneGCSSolver_Solver::STATUS_OK ||
242           myPrevResult == PlaneGCSSolver_Solver::STATUS_UNKNOWN) {
243         // the error message should be changed before sending the message
244         sendMessage(EVENT_SOLVER_FAILED);
245         myPrevResult = PlaneGCSSolver_Solver::STATUS_FAILED;
246       }
247       mySketchSolver->undo();
248       return false;
249     }
250     // solution succeeded, store results into correspondent attributes
251     if (aResult == PlaneGCSSolver_Solver::STATUS_OK ||
252         aResult == PlaneGCSSolver_Solver::STATUS_EMPTYSET) {
253       myStorage->setNeedToResolve(false);
254       myStorage->refresh();
255
256       // additional check that copied entities used in Mirror and other "Multi" constraints
257       // is not connected with their originals by constraints.
258       myMultiConstraintUpdateStack += 1;
259       aResolved = true;
260       if (myStorage->isNeedToResolve())
261         aResolved = resolveConstraints();
262
263       if (aResolved) {
264         myMultiConstraintUpdateStack -= 1;
265
266         if (myPrevResult != PlaneGCSSolver_Solver::STATUS_OK ||
267             myPrevResult == PlaneGCSSolver_Solver::STATUS_UNKNOWN) {
268           getWorkplane()->string(SketchPlugin_Sketch::SOLVER_ERROR())->setValue("");
269           std::set<ObjectPtr> aConflicting = myConflictingConstraints;
270           myConflictingConstraints.clear();
271           myPrevResult = PlaneGCSSolver_Solver::STATUS_OK;
272           // the error message should be changed before sending the message
273           sendMessage(EVENT_SOLVER_REPAIRED, aConflicting);
274         }
275       }
276
277       // show degrees of freedom
278       computeDoF();
279     } else {
280       mySketchSolver->undo();
281       if (!myConstraints.empty()) {
282         // the error message should be changed before sending the message
283         getWorkplane()->string(SketchPlugin_Sketch::SOLVER_ERROR())
284           ->setValue(SketchSolver_Error::CONSTRAINTS());
285         if (myPrevResult != aResult ||
286             myPrevResult == PlaneGCSSolver_Solver::STATUS_UNKNOWN ||
287             myPrevResult == PlaneGCSSolver_Solver::STATUS_FAILED) {
288           // Obtain list of conflicting constraints
289           std::set<ObjectPtr> aConflicting = myStorage->getConflictingConstraints(mySketchSolver);
290
291           if (!myConflictingConstraints.empty()) {
292             std::set<ObjectPtr>::iterator anIt = aConflicting.begin();
293             for (; anIt != aConflicting.end(); ++anIt)
294               myConflictingConstraints.erase(*anIt);
295             if (!myConflictingConstraints.empty()) {
296               // some constraints does not conflict, send corresponding message
297               sendMessage(EVENT_SOLVER_REPAIRED, myConflictingConstraints);
298             }
299           }
300           myConflictingConstraints = aConflicting;
301           if (!myConflictingConstraints.empty())
302             sendMessage(EVENT_SOLVER_FAILED, myConflictingConstraints);
303           myPrevResult = aResult;
304         }
305       }
306     }
307
308   } else if (isGroupEmpty && isWorkplaneValid())
309     computeDoF();
310   removeTemporaryConstraints();
311   myStorage->setNeedToResolve(false);
312   return aResolved;
313 }
314
315 // ============================================================================
316 //  Function: computeDoF
317 //  Class:    SketchSolver_Group
318 //  Purpose:  compute DoF of the sketch and set corresponding field
319 // ============================================================================
320 void SketchSolver_Group::computeDoF()
321 {
322   std::ostringstream aDoFMsg;
323   int aDoF = mySketchSolver->dof();
324   /// "DoF = 0" content of string value is used in PartSet by Sketch edit
325   /// If it is changed, it should be corrected also there
326   if (aDoF == 0)
327     aDoFMsg << "Sketch is fully fixed (DoF = 0)";
328   else
329     aDoFMsg << "DoF (degrees of freedom) = " << aDoF;
330   mySketch->string(SketchPlugin_Sketch::SOLVER_DOF())->setValue(aDoFMsg.str());
331
332   if (aDoF > 0 && myDOF == 0)
333     sendMessage(EVENT_SKETCH_UNDER_CONSTRAINED, mySketch, aDoF);
334   else if (aDoF == 0 && myDOF > 0)
335     sendMessage(EVENT_SKETCH_FULLY_CONSTRAINED, mySketch, aDoF);
336   else if (aDoF < 0)
337     sendMessage(EVENT_SKETCH_OVER_CONSTRAINED, mySketch, aDoF);
338
339   myDOF = aDoF;
340 }
341
342 // ============================================================================
343 //  Function: repairConsistency
344 //  Class:    SketchSolver_Group
345 //  Purpose:  search removed entities and constraints
346 // ============================================================================
347 void SketchSolver_Group::repairConsistency()
348 {
349   if (!areConstraintsValid() || !myStorage->areFeaturesValid()) {
350     // remove invalid constraints
351     std::set<ConstraintPtr> anInvalidConstraints;
352     ConstraintConstraintMap::iterator aCIter = myConstraints.begin();
353     for (; aCIter != myConstraints.end(); ++aCIter) {
354       if (!aCIter->first->data() || !aCIter->first->data()->isValid())
355         anInvalidConstraints.insert(aCIter->first);
356     }
357     std::set<ConstraintPtr>::const_iterator aRemoveIt = anInvalidConstraints.begin();
358     for (; aRemoveIt != anInvalidConstraints.end(); ++aRemoveIt)
359       removeConstraint(*aRemoveIt);
360
361     // remove invalid features
362     myStorage->removeInvalidEntities();
363
364     // show DoF
365     computeDoF();
366   }
367 }
368
369 // ============================================================================
370 //  Function: removeTemporaryConstraints
371 //  Class:    SketchSolver_Group
372 //  Purpose:  remove all transient SLVS_C_WHERE_DRAGGED constraints after
373 //            resolving the set of constraints
374 // ============================================================================
375 void SketchSolver_Group::removeTemporaryConstraints()
376 {
377   if (!myTempConstraints.empty()) {
378     mySketchSolver->removeConstraint(CID_MOVEMENT);
379
380     std::set<SolverConstraintPtr>::iterator aTmpIt = myTempConstraints.begin();
381     for (; aTmpIt != myTempConstraints.end(); ++aTmpIt)
382       (*aTmpIt)->remove();
383
384     myTempConstraints.clear();
385   }
386
387   myStorage->setNeedToResolve(false);
388 }
389
390 // ============================================================================
391 //  Function: removeConstraint
392 //  Class:    SketchSolver_Group
393 //  Purpose:  remove constraint and all unused entities
394 // ============================================================================
395 void SketchSolver_Group::removeConstraint(ConstraintPtr theConstraint)
396 {
397   ConstraintConstraintMap::iterator aCIter = myConstraints.begin();
398   for (; aCIter != myConstraints.end(); aCIter++)
399     if (aCIter->first == theConstraint) {
400       aCIter->second->remove(); // the constraint is not fully removed
401
402       // constraint is removed => reset stack of "multi" constraints updates
403       myMultiConstraintUpdateStack = 0;
404       break;
405     }
406   if (aCIter != myConstraints.end())
407     myConstraints.erase(aCIter);
408 }
409
410 // ============================================================================
411 //  Function: setTemporary
412 //  Class:    SketchSolver_Group
413 //  Purpose:  append given constraint to the group of temporary constraints
414 // ============================================================================
415 void SketchSolver_Group::setTemporary(SolverConstraintPtr theConstraint)
416 {
417   if (theConstraint)
418     myTempConstraints.insert(theConstraint);
419 }
420
421 // ============================================================================
422 //  Function: blockEvents
423 //  Class:    SketchSolver_Group
424 //  Purpose:  block or unblock events from features in this group
425 // ============================================================================
426 void SketchSolver_Group::blockEvents(bool isBlocked)
427 {
428   if (myIsEventsBlocked == isBlocked)
429     return;
430
431   // block/unblock events from the features in the storage
432   myStorage->blockEvents(isBlocked);
433
434   // block/unblock events from constraints
435   ConstraintConstraintMap::iterator aCIt = myConstraints.begin();
436   for (; aCIt != myConstraints.end(); ++aCIt)
437     aCIt->second->blockEvents(isBlocked);
438
439   myIsEventsBlocked = isBlocked;
440 }
441
442 bool SketchSolver_Group::areConstraintsValid() const
443 {
444   // Check the constraints are valid
445   ConstraintConstraintMap::const_iterator aCIter = myConstraints.begin();
446   for (; aCIter != myConstraints.end(); ++aCIter)
447     if (!aCIter->first->data() || !aCIter->first->data()->isValid())
448       return false;
449   return true;
450 }