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