1 // Copyright (C) 2014-2023 CEA, EDF
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.
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.
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
17 // See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
20 #include "SketchSolver_Group.h"
21 #include <SketchSolver_Error.h>
22 #include <SketchSolver_Manager.h>
24 #include <PlaneGCSSolver_Solver.h>
25 #include <PlaneGCSSolver_Storage.h>
26 #include <PlaneGCSSolver_Tools.h>
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>
36 #include <Config_Translator.h>
39 static void sendMessage(const char* theMessageName)
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);
46 static void sendMessage(const char* theMessageName, const std::set<ObjectPtr>& theConflicting)
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);
55 static void sendMessage(const char* theMessageName,
56 const CompositeFeaturePtr& theSketch,
59 std::shared_ptr<ModelAPI_SolverFailedMessage> aMessage =
60 std::shared_ptr<ModelAPI_SolverFailedMessage>(
61 new ModelAPI_SolverFailedMessage(Events_Loop::eventByName(theMessageName)));
63 std::set<ObjectPtr> anObjects;
64 anObjects.insert(theSketch);
65 aMessage->setObjects(anObjects);
66 aMessage->dof(theDOF);
68 Events_Loop::loop()->send(aMessage);
73 // ========================================================
74 // ========= SketchSolver_Group ===============
75 // ========================================================
77 SketchSolver_Group::SketchSolver_Group(const CompositeFeaturePtr& theWorkplane)
78 : myPrevResult(PlaneGCSSolver_Solver::STATUS_UNKNOWN),
80 myIsEventsBlocked(false),
81 myMultiConstraintUpdateStack(0)
83 mySketchSolver = SolverPtr(new PlaneGCSSolver_Solver);
84 myStorage = StoragePtr(new PlaneGCSSolver_Storage(mySketchSolver));
85 updateSketch(theWorkplane);
88 SketchSolver_Group::~SketchSolver_Group()
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();
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)
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);
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();
118 myConstraints[theConstraint] = aConstraint;
121 myConstraints[theConstraint]->update();
123 // constraint is created/updated => reset stack of "multi" constraints updates
124 myMultiConstraintUpdateStack = 0;
128 bool SketchSolver_Group::updateSketch(CompositeFeaturePtr theSketch)
130 static const double THE_TOLERANCE = 1.e-7;
131 bool isChanged = theSketch != mySketch;
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()));
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);
146 mySketch = theSketch;
147 mySketchOrigin = anOrigin->pnt();
148 mySketchNormal = aNorm->dir();
149 mySketchXDir = aDirX->dir();
151 myStorage->notify(theSketch);
156 bool SketchSolver_Group::updateFeature(FeaturePtr theFeature)
158 return myStorage->update(theFeature);
161 template <class Type>
162 static SolverConstraintPtr move(StoragePtr theStorage,
163 SolverPtr theSketchSolver,
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)
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();
178 // Create temporary Fixed constraint
179 std::shared_ptr<SketchSolver_ConstraintMovement> aConstraint =
180 PlaneGCSSolver_Tools::createMovementConstraint(theFeatureOrPoint);
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);
190 theStorage->notify(aConstraint->movedFeature());
196 bool SketchSolver_Group::moveFeature(FeaturePtr theFeature,
197 const std::shared_ptr<GeomAPI_Pnt2d>& theFrom,
198 const std::shared_ptr<GeomAPI_Pnt2d>& theTo)
200 EntityWrapperPtr anEntity = myStorage->entity(theFeature);
201 SolverConstraintPtr aConstraint = move(myStorage, mySketchSolver, myDOF, myIsEventsBlocked,
202 theFeature, anEntity, theFrom, theTo);
203 setTemporary(aConstraint);
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)
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);
219 aConstraint = move(myStorage, mySketchSolver, myDOF, myIsEventsBlocked,
220 std::pair<AttributePtr, int>(theAttribute, thePointIndex), anEntity,
223 setTemporary(aConstraint);
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()
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);
246 bool aResolved = false;
247 bool isGroupEmpty = isEmpty() && myStorage->isEmpty();
248 if (myStorage->isNeedToResolve() &&
249 (!isGroupEmpty || !myConflictingConstraints.empty() ||
250 myPrevResult == PlaneGCSSolver_Solver::STATUS_FAILED)) {
252 PlaneGCSSolver_Solver::SolveStatus aResult = PlaneGCSSolver_Solver::STATUS_OK;
255 myStorage->adjustParametrizationOfArcs();
256 aResult = mySketchSolver->solve();
258 if (aResult == PlaneGCSSolver_Solver::STATUS_FAILED &&
259 !myTempConstraints.empty()) {
260 mySketchSolver->undo();
261 removeTemporaryConstraints();
262 aResult = mySketchSolver->solve();
264 // check degenerated geometry after constraints resolving
265 if (aResult == PlaneGCSSolver_Solver::STATUS_OK)
266 aResult = myStorage->checkDegeneratedGeometry();
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;
276 mySketchSolver->undo();
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();
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;
289 if (myStorage->isNeedToResolve())
290 aResolved = resolveConstraints();
293 myMultiConstraintUpdateStack -= 1;
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);
306 // show degrees of freedom
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);
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);
331 myConflictingConstraints = aConflicting;
332 if (!myConflictingConstraints.empty())
333 sendMessage(EVENT_SOLVER_FAILED, myConflictingConstraints);
334 myPrevResult = aResult;
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)
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);
357 removeTemporaryConstraints();
358 myStorage->setNeedToResolve(false);
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()
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
375 // static const std::string aMsgDoF("Sketch is fully fixed (DoF = 0)");
376 // aDoFMsg = Config_Translator::translate(aMsgContext, aMsgDoF);
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);
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);
389 std::ostringstream aStr;
391 mySketch->string(SketchPlugin_Sketch::SOLVER_DOF())->setValue(aStr.str());
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);
398 sendMessage(EVENT_SKETCH_OVER_CONSTRAINED, mySketch, aDoF);
403 // ============================================================================
404 // Function: repairConsistency
405 // Class: SketchSolver_Group
406 // Purpose: search removed entities and constraints
407 // ============================================================================
408 void SketchSolver_Group::repairConsistency()
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);
418 std::set<ConstraintPtr>::const_iterator aRemoveIt = anInvalidConstraints.begin();
419 for (; aRemoveIt != anInvalidConstraints.end(); ++aRemoveIt)
420 removeConstraint(*aRemoveIt);
422 // remove invalid features
423 myStorage->removeInvalidEntities();
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()
438 if (!myTempConstraints.empty()) {
439 mySketchSolver->removeConstraint(CID_MOVEMENT);
441 std::set<SolverConstraintPtr>::iterator aTmpIt = myTempConstraints.begin();
442 for (; aTmpIt != myTempConstraints.end(); ++aTmpIt)
445 myTempConstraints.clear();
448 myStorage->setNeedToResolve(false);
451 // ============================================================================
452 // Function: removeConstraint
453 // Class: SketchSolver_Group
454 // Purpose: remove constraint and all unused entities
455 // ============================================================================
456 void SketchSolver_Group::removeConstraint(ConstraintPtr theConstraint)
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
463 // constraint is removed => reset stack of "multi" constraints updates
464 myMultiConstraintUpdateStack = 0;
467 if (aCIter != myConstraints.end())
468 myConstraints.erase(aCIter);
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)
479 myTempConstraints.insert(theConstraint);
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)
489 if (myIsEventsBlocked == isBlocked)
492 // block/unblock events from the features in the storage
493 myStorage->blockEvents(isBlocked);
495 // block/unblock events from constraints
496 ConstraintConstraintMap::iterator aCIt = myConstraints.begin();
497 for (; aCIt != myConstraints.end(); ++aCIt)
498 aCIt->second->blockEvents(isBlocked);
500 myIsEventsBlocked = isBlocked;
503 bool SketchSolver_Group::areConstraintsValid() const
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())
513 void SketchSolver_Group::underconstrainedFeatures(std::set<ObjectPtr>& theFeatures) const
515 myStorage->getUnderconstrainedGeometry(theFeatures);