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