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