Salome HOME
Merge remote-tracking branch 'remotes/origin/CHANGE_SKETCH_PLANE'
[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       theSketchSolver->initialize();
183       aConstraint->moveTo(theTo);
184       theStorage->setNeedToResolve(true);
185     } else
186       theStorage->notify(aConstraint->movedFeature());
187   }
188
189   return aConstraint;
190 }
191
192 bool SketchSolver_Group::moveFeature(FeaturePtr theFeature,
193                                      const std::shared_ptr<GeomAPI_Pnt2d>& theFrom,
194                                      const std::shared_ptr<GeomAPI_Pnt2d>& theTo)
195 {
196   SolverConstraintPtr aConstraint =
197       move(myStorage, mySketchSolver, myDOF, myIsEventsBlocked, theFeature, theFrom, theTo);
198   setTemporary(aConstraint);
199   return true;
200 }
201
202 bool SketchSolver_Group::movePoint(AttributePtr theAttribute,
203                                    const std::shared_ptr<GeomAPI_Pnt2d>& theFrom,
204                                    const std::shared_ptr<GeomAPI_Pnt2d>& theTo)
205 {
206   SolverConstraintPtr aConstraint =
207       move(myStorage, mySketchSolver, myDOF, myIsEventsBlocked, theAttribute, theFrom, theTo);
208   setTemporary(aConstraint);
209   return true;
210 }
211
212 // ============================================================================
213 //  Function: resolveConstraints
214 //  Class:    SketchSolver_Group
215 //  Purpose:  solve the set of constraints for the current group
216 // ============================================================================
217 bool SketchSolver_Group::resolveConstraints()
218 {
219   static const int MAX_STACK_SIZE = 5;
220   // check the "Multi" constraints do not drop sketch into infinite loop
221   if (myMultiConstraintUpdateStack > MAX_STACK_SIZE) {
222     myMultiConstraintUpdateStack = 0;
223     myPrevResult = PlaneGCSSolver_Solver::STATUS_FAILED;
224     // generate error message due to loop update of the sketch
225     getWorkplane()->string(SketchPlugin_Sketch::SOLVER_ERROR())
226       ->setValue(SketchSolver_Error::INFINITE_LOOP());
227     sendMessage(EVENT_SOLVER_FAILED, myConflictingConstraints);
228     return false;
229   }
230
231   bool aResolved = false;
232   bool isGroupEmpty = isEmpty() && myStorage->isEmpty();
233   if (myStorage->isNeedToResolve() &&
234       (!isGroupEmpty || !myConflictingConstraints.empty() ||
235         myPrevResult == PlaneGCSSolver_Solver::STATUS_FAILED)) {
236
237     PlaneGCSSolver_Solver::SolveStatus aResult = PlaneGCSSolver_Solver::STATUS_OK;
238     try {
239       if (!isGroupEmpty)
240         aResult = mySketchSolver->solve();
241       if (aResult == PlaneGCSSolver_Solver::STATUS_FAILED &&
242           !myTempConstraints.empty()) {
243         mySketchSolver->undo();
244         removeTemporaryConstraints();
245         aResult = mySketchSolver->solve();
246       }
247       // check degenerated geometry after constraints resolving
248       if (aResult == PlaneGCSSolver_Solver::STATUS_OK)
249         aResult = myStorage->checkDegeneratedGeometry();
250     } catch (...) {
251       getWorkplane()->string(SketchPlugin_Sketch::SOLVER_ERROR())
252         ->setValue(SketchSolver_Error::SOLVESPACE_CRASH());
253       if (myPrevResult == PlaneGCSSolver_Solver::STATUS_OK ||
254           myPrevResult == PlaneGCSSolver_Solver::STATUS_UNKNOWN) {
255         // the error message should be changed before sending the message
256         sendMessage(EVENT_SOLVER_FAILED);
257         myPrevResult = PlaneGCSSolver_Solver::STATUS_FAILED;
258       }
259       mySketchSolver->undo();
260       return false;
261     }
262     // solution succeeded, store results into correspondent attributes
263     if (aResult == PlaneGCSSolver_Solver::STATUS_OK ||
264         aResult == PlaneGCSSolver_Solver::STATUS_EMPTYSET) {
265       myStorage->setNeedToResolve(false);
266       myStorage->refresh();
267
268       // additional check that copied entities used in Mirror and other "Multi" constraints
269       // is not connected with their originals by constraints.
270       myMultiConstraintUpdateStack += 1;
271       aResolved = true;
272       if (myStorage->isNeedToResolve())
273         aResolved = resolveConstraints();
274
275       if (aResolved) {
276         myMultiConstraintUpdateStack -= 1;
277
278         if (myPrevResult != PlaneGCSSolver_Solver::STATUS_OK ||
279             myPrevResult == PlaneGCSSolver_Solver::STATUS_UNKNOWN) {
280           getWorkplane()->string(SketchPlugin_Sketch::SOLVER_ERROR())->setValue("");
281           std::set<ObjectPtr> aConflicting = myConflictingConstraints;
282           myConflictingConstraints.clear();
283           myPrevResult = PlaneGCSSolver_Solver::STATUS_OK;
284           // the error message should be changed before sending the message
285           sendMessage(EVENT_SOLVER_REPAIRED, aConflicting);
286         }
287       }
288
289       // show degrees of freedom
290       computeDoF();
291     } else {
292       mySketchSolver->undo();
293       if (!myConstraints.empty()) {
294         // the error message should be changed before sending the message
295         const std::string& aErrorMsg = aResult == PlaneGCSSolver_Solver::STATUS_DEGENERATED ?
296                                        SketchSolver_Error::DEGENERATED_GEOMETRY() :
297                                        SketchSolver_Error::CONSTRAINTS();
298         getWorkplane()->string(SketchPlugin_Sketch::SOLVER_ERROR())->setValue(aErrorMsg);
299         if (myPrevResult != aResult ||
300             myPrevResult == PlaneGCSSolver_Solver::STATUS_UNKNOWN ||
301             myPrevResult == PlaneGCSSolver_Solver::STATUS_FAILED) {
302           // Obtain list of conflicting constraints
303           std::set<ObjectPtr> aConflicting = myStorage->getConflictingConstraints(mySketchSolver);
304
305           if (!myConflictingConstraints.empty()) {
306             std::set<ObjectPtr>::iterator anIt = aConflicting.begin();
307             for (; anIt != aConflicting.end(); ++anIt)
308               myConflictingConstraints.erase(*anIt);
309             if (!myConflictingConstraints.empty()) {
310               // some constraints does not conflict, send corresponding message
311               sendMessage(EVENT_SOLVER_REPAIRED, myConflictingConstraints);
312             }
313           }
314           myConflictingConstraints = aConflicting;
315           if (!myConflictingConstraints.empty())
316             sendMessage(EVENT_SOLVER_FAILED, myConflictingConstraints);
317           myPrevResult = aResult;
318         }
319       }
320
321       // show degrees of freedom only if the degenerated geometry appears
322       if (aResult == PlaneGCSSolver_Solver::STATUS_DEGENERATED)
323         computeDoF();
324     }
325
326   }
327   else if (isGroupEmpty && isWorkplaneValid()) {
328     // clear error related to previously degenerated entities
329     if (myPrevResult == PlaneGCSSolver_Solver::STATUS_DEGENERATED) {
330       getWorkplane()->string(SketchPlugin_Sketch::SOLVER_ERROR())->setValue("");
331       myPrevResult = PlaneGCSSolver_Solver::STATUS_OK;
332       // the error message should be changed before sending the message
333       myConflictingConstraints.clear();
334       sendMessage(EVENT_SOLVER_REPAIRED, myConflictingConstraints);
335     }
336
337     computeDoF();
338   }
339   removeTemporaryConstraints();
340   myStorage->setNeedToResolve(false);
341   return aResolved;
342 }
343
344 // ============================================================================
345 //  Function: computeDoF
346 //  Class:    SketchSolver_Group
347 //  Purpose:  compute DoF of the sketch and set corresponding field
348 // ============================================================================
349 void SketchSolver_Group::computeDoF()
350 {
351   std::ostringstream aDoFMsg;
352   int aDoF = mySketchSolver->dof();
353   /// "DoF = 0" content of string value is used in PartSet by Sketch edit
354   /// If it is changed, it should be corrected also there
355   if (aDoF == 0)
356     aDoFMsg << "Sketch is fully fixed (DoF = 0)";
357   else
358     aDoFMsg << "DoF (degrees of freedom) = " << aDoF;
359   mySketch->string(SketchPlugin_Sketch::SOLVER_DOF())->setValue(aDoFMsg.str());
360
361   if (aDoF > 0 && myDOF <= 0)
362     sendMessage(EVENT_SKETCH_UNDER_CONSTRAINED, mySketch, aDoF);
363   else if (aDoF == 0 && myDOF != 0)
364     sendMessage(EVENT_SKETCH_FULLY_CONSTRAINED, mySketch, aDoF);
365   else if (aDoF < 0)
366     sendMessage(EVENT_SKETCH_OVER_CONSTRAINED, mySketch, aDoF);
367
368   myDOF = aDoF;
369 }
370
371 // ============================================================================
372 //  Function: repairConsistency
373 //  Class:    SketchSolver_Group
374 //  Purpose:  search removed entities and constraints
375 // ============================================================================
376 void SketchSolver_Group::repairConsistency()
377 {
378   if (!areConstraintsValid() || !myStorage->areFeaturesValid()) {
379     // remove invalid constraints
380     std::set<ConstraintPtr> anInvalidConstraints;
381     ConstraintConstraintMap::iterator aCIter = myConstraints.begin();
382     for (; aCIter != myConstraints.end(); ++aCIter) {
383       if (!aCIter->first->data() || !aCIter->first->data()->isValid())
384         anInvalidConstraints.insert(aCIter->first);
385     }
386     std::set<ConstraintPtr>::const_iterator aRemoveIt = anInvalidConstraints.begin();
387     for (; aRemoveIt != anInvalidConstraints.end(); ++aRemoveIt)
388       removeConstraint(*aRemoveIt);
389
390     // remove invalid features
391     myStorage->removeInvalidEntities();
392
393     // show DoF
394     computeDoF();
395   }
396 }
397
398 // ============================================================================
399 //  Function: removeTemporaryConstraints
400 //  Class:    SketchSolver_Group
401 //  Purpose:  remove all transient SLVS_C_WHERE_DRAGGED constraints after
402 //            resolving the set of constraints
403 // ============================================================================
404 void SketchSolver_Group::removeTemporaryConstraints()
405 {
406   if (!myTempConstraints.empty()) {
407     mySketchSolver->removeConstraint(CID_MOVEMENT);
408
409     std::set<SolverConstraintPtr>::iterator aTmpIt = myTempConstraints.begin();
410     for (; aTmpIt != myTempConstraints.end(); ++aTmpIt)
411       (*aTmpIt)->remove();
412
413     myTempConstraints.clear();
414   }
415
416   myStorage->setNeedToResolve(false);
417 }
418
419 // ============================================================================
420 //  Function: removeConstraint
421 //  Class:    SketchSolver_Group
422 //  Purpose:  remove constraint and all unused entities
423 // ============================================================================
424 void SketchSolver_Group::removeConstraint(ConstraintPtr theConstraint)
425 {
426   ConstraintConstraintMap::iterator aCIter = myConstraints.begin();
427   for (; aCIter != myConstraints.end(); aCIter++)
428     if (aCIter->first == theConstraint) {
429       aCIter->second->remove(); // the constraint is not fully removed
430
431       // constraint is removed => reset stack of "multi" constraints updates
432       myMultiConstraintUpdateStack = 0;
433       break;
434     }
435   if (aCIter != myConstraints.end())
436     myConstraints.erase(aCIter);
437 }
438
439 // ============================================================================
440 //  Function: setTemporary
441 //  Class:    SketchSolver_Group
442 //  Purpose:  append given constraint to the group of temporary constraints
443 // ============================================================================
444 void SketchSolver_Group::setTemporary(SolverConstraintPtr theConstraint)
445 {
446   if (theConstraint)
447     myTempConstraints.insert(theConstraint);
448 }
449
450 // ============================================================================
451 //  Function: blockEvents
452 //  Class:    SketchSolver_Group
453 //  Purpose:  block or unblock events from features in this group
454 // ============================================================================
455 void SketchSolver_Group::blockEvents(bool isBlocked)
456 {
457   if (myIsEventsBlocked == isBlocked)
458     return;
459
460   // block/unblock events from the features in the storage
461   myStorage->blockEvents(isBlocked);
462
463   // block/unblock events from constraints
464   ConstraintConstraintMap::iterator aCIt = myConstraints.begin();
465   for (; aCIt != myConstraints.end(); ++aCIt)
466     aCIt->second->blockEvents(isBlocked);
467
468   myIsEventsBlocked = isBlocked;
469 }
470
471 bool SketchSolver_Group::areConstraintsValid() const
472 {
473   // Check the constraints are valid
474   ConstraintConstraintMap::const_iterator aCIter = myConstraints.begin();
475   for (; aCIter != myConstraints.end(); ++aCIter)
476     if (!aCIter->first->data() || !aCIter->first->data()->isValid())
477       return false;
478   return true;
479 }