Salome HOME
Fix incorrect DoF after dump-import loop (issue #1767)
[modules/shaper.git] / src / SketchSolver / SketchSolver_Manager.cpp
1 // Copyright (C) 2014-20xx CEA/DEN, EDF R&D
2
3 // File:    SketchSolver_Manager.cpp
4 // Created: 08 May 2014
5 // Author:  Artem ZHIDKOV
6
7 #include "SketchSolver_Manager.h"
8 #include "SketchSolver_Error.h"
9
10 #include <Events_Loop.h>
11
12 #include <GeomDataAPI_Point2D.h>
13
14 #include <ModelAPI_AttributeDouble.h>
15 #include <ModelAPI_AttributeRefList.h>
16 #include <ModelAPI_Data.h>
17 #include <ModelAPI_Events.h>
18 #include <ModelAPI_Object.h>
19 #include <ModelAPI_ResultConstruction.h>
20 #include <ModelAPI_Attribute.h>
21 #include <ModelAPI_AttributeInteger.h>
22 #include <ModelAPI_AttributeString.h>
23
24 #include <SketchPlugin_Constraint.h>
25 #include <SketchPlugin_ConstraintAngle.h>
26 #include <SketchPlugin_ConstraintCoincidence.h>
27 #include <SketchPlugin_ConstraintCollinear.h>
28 #include <SketchPlugin_ConstraintDistance.h>
29 #include <SketchPlugin_ConstraintEqual.h>
30 #include <SketchPlugin_ConstraintHorizontal.h>
31 #include <SketchPlugin_ConstraintLength.h>
32 #include <SketchPlugin_ConstraintMiddle.h>
33 #include <SketchPlugin_ConstraintMirror.h>
34 #include <SketchPlugin_ConstraintParallel.h>
35 #include <SketchPlugin_ConstraintPerpendicular.h>
36 #include <SketchPlugin_ConstraintRadius.h>
37 #include <SketchPlugin_ConstraintRigid.h>
38 #include <SketchPlugin_ConstraintTangent.h>
39 #include <SketchPlugin_ConstraintVertical.h>
40 #include <SketchPlugin_MultiRotation.h>
41 #include <SketchPlugin_MultiTranslation.h>
42
43 #include <SketchPlugin_Arc.h>
44 #include <SketchPlugin_Circle.h>
45 #include <SketchPlugin_Line.h>
46 #include <SketchPlugin_Point.h>
47 #include <SketchPlugin_Sketch.h>
48 #include <SketchPlugin_Feature.h>
49
50 #include <assert.h>
51 #include <list>
52 #include <set>
53 #include <memory>
54 #include <sstream>
55
56 static const Events_ID anUpdateEvent = Events_Loop::eventByName(EVENT_OBJECT_UPDATED);
57
58 // Initialization of constraint manager self pointer
59 SketchSolver_Manager* SketchSolver_Manager::mySelf = 0;
60
61 /// Global constraint manager object
62 SketchSolver_Manager* myManager = SketchSolver_Manager::instance();
63
64
65 // ========================================================
66 // ========= SketchSolver_Manager ===============
67 // ========================================================
68 SketchSolver_Manager* SketchSolver_Manager::instance()
69 {
70   if (!mySelf)
71     mySelf = new SketchSolver_Manager();
72   return mySelf;
73 }
74
75 SketchSolver_Manager::SketchSolver_Manager()
76 {
77   myGroups.clear();
78   myIsComputed = false;
79
80   // Register in event loop
81   Events_Loop::loop()->registerListener(this, Events_Loop::eventByName(EVENT_OBJECT_CREATED));
82   Events_Loop::loop()->registerListener(this, anUpdateEvent);
83   Events_Loop::loop()->registerListener(this, Events_Loop::eventByName(EVENT_OBJECT_DELETED));
84   Events_Loop::loop()->registerListener(this, Events_Loop::eventByName(EVENT_OBJECT_MOVED));
85
86   Events_Loop::loop()->registerListener(this, Events_Loop::eventByName(EVENT_SOLVER_FAILED));
87   Events_Loop::loop()->registerListener(this, Events_Loop::eventByName(EVENT_SOLVER_REPAIRED));
88   Events_Loop::loop()->registerListener(this, Events_Loop::eventByName(EVENT_SKETCH_PREPARED));
89 }
90
91 SketchSolver_Manager::~SketchSolver_Manager()
92 {
93   myGroups.clear();
94 }
95
96 void SketchSolver_Manager::setBuilder(BuilderPtr theBuilder)
97 {
98   myBuilder = theBuilder;
99 }
100
101 BuilderPtr SketchSolver_Manager::builder()
102 {
103   return myBuilder;
104 }
105
106 bool SketchSolver_Manager::groupMessages()
107 {
108   return true;
109 }
110
111 // ============================================================================
112 //  Function: processEvent
113 //  Purpose:  listen the event loop and process the message
114 // ============================================================================
115 void SketchSolver_Manager::processEvent(
116   const std::shared_ptr<Events_Message>& theMessage)
117 {
118   static const Events_ID aSketchPreparedEvent = Events_Loop::eventByName(EVENT_SKETCH_PREPARED);
119   // sketch is prepared for resolve: all the needed events are collected and must be processed by the solver
120   if (theMessage->eventID() == aSketchPreparedEvent) {
121     flushGrouped(anUpdateEvent);
122     return;
123   }
124
125   checkConflictingConstraints(theMessage);
126
127   if (myIsComputed)
128     return;
129   myIsComputed = true;
130
131   // Shows that the message has at least one feature applicable for solver
132   bool hasProperFeature = false;
133
134   if (theMessage->eventID() == Events_Loop::loop()->eventByName(EVENT_OBJECT_CREATED)
135       || theMessage->eventID() == anUpdateEvent
136       || theMessage->eventID() == Events_Loop::loop()->eventByName(EVENT_OBJECT_MOVED)) {
137     std::shared_ptr<ModelAPI_ObjectUpdatedMessage> anUpdateMsg =
138         std::dynamic_pointer_cast<ModelAPI_ObjectUpdatedMessage>(theMessage);
139     std::set<ObjectPtr> aFeatures = anUpdateMsg->objects();
140
141     bool isUpdateFlushed = stopSendUpdate();
142
143     bool isMovedEvt = theMessage->eventID()
144           == Events_Loop::loop()->eventByName(EVENT_OBJECT_MOVED);
145     if (isMovedEvt) {
146       std::set<ObjectPtr>::iterator aFeatIter;
147       for (aFeatIter = aFeatures.begin(); aFeatIter != aFeatures.end(); aFeatIter++) {
148         std::shared_ptr<SketchPlugin_Feature> aSFeature = 
149             std::dynamic_pointer_cast<SketchPlugin_Feature>(*aFeatIter);
150         if (aSFeature && moveEntity(aSFeature)) {
151           // Want to avoid recalculation of DoF too frequently.
152           // So, set the flag when the feature is really moved.
153           hasProperFeature = true;
154         }
155       }
156       if (!hasProperFeature) // in this iteration it will compute nothing, so, no problem with recursion
157         // it is important that solver flushes signal updated after processing move signal as there is
158         // optimization that relies on this update, might be found by key "optimization"
159         myIsComputed = false;
160     } else {
161       std::list<FeaturePtr> aSketchFeatures = SketchSolver_Group::selectApplicableFeatures(aFeatures);
162       std::list<FeaturePtr>::iterator aFeatIter = aSketchFeatures.begin();
163       for (; aFeatIter != aSketchFeatures.end(); ++aFeatIter) {
164         if ((*aFeatIter)->getKind() == SketchPlugin_Sketch::ID()) {
165           std::shared_ptr<ModelAPI_CompositeFeature> aSketch = 
166               std::dynamic_pointer_cast<ModelAPI_CompositeFeature>(*aFeatIter);
167           hasProperFeature = changeWorkplane(aSketch) || hasProperFeature;
168           continue;
169         }
170         std::shared_ptr<SketchPlugin_Feature> aFeature = 
171             std::dynamic_pointer_cast<SketchPlugin_Feature>(*aFeatIter);
172         if (!aFeature)
173           continue;
174         hasProperFeature = changeFeature(aFeature) || hasProperFeature;
175       }
176     }
177
178     bool needToUpdate = false;
179     // Solve the set of constraints
180     if (hasProperFeature)
181       needToUpdate = resolveConstraints();
182
183     // Features may be updated => now send events, but for all changed at once
184     if (isUpdateFlushed)
185       allowSendUpdate();
186
187     myIsComputed = false;
188
189     // send update for movement in any case
190     if (needToUpdate || isMovedEvt)
191       Events_Loop::loop()->flush(anUpdateEvent);
192
193   } else if (theMessage->eventID() == Events_Loop::loop()->eventByName(EVENT_OBJECT_DELETED)) {
194     std::shared_ptr<ModelAPI_ObjectDeletedMessage> aDeleteMsg =
195       std::dynamic_pointer_cast<ModelAPI_ObjectDeletedMessage>(theMessage);
196     const std::set<std::string>& aFeatureGroups = aDeleteMsg->groups();
197
198     // Find SketchPlugin_Sketch::ID() in groups. The constraint groups should be updated when an object removed from Sketch
199     std::set<std::string>::const_iterator aFGrIter;
200     for (aFGrIter = aFeatureGroups.begin(); aFGrIter != aFeatureGroups.end(); aFGrIter++)
201       if (aFGrIter->compare(ModelAPI_ResultConstruction::group()) == 0 ||
202         aFGrIter->compare(ModelAPI_Feature::group()) == 0)
203         break;
204
205     if (aFGrIter != aFeatureGroups.end()) {
206       hasProperFeature = true;
207       std::list<SketchSolver_Group*> aGroupsToResolve;
208       std::list<SketchSolver_Group*>::iterator aGroupIter = myGroups.begin();
209       std::list<SketchSolver_Group*> aSeparatedGroups;
210       while (aGroupIter != myGroups.end()) {
211         if (!(*aGroupIter)->isWorkplaneValid()) {  // the group should be removed
212           delete *aGroupIter;
213           std::list<SketchSolver_Group*>::iterator aRemoveIt = aGroupIter++;
214           myGroups.erase(aRemoveIt);
215           continue;
216         }
217         if (!(*aGroupIter)->isConsistent()) {  // some constraints were removed, try to split the group
218           (*aGroupIter)->splitGroup(aSeparatedGroups);
219           if (!(*aGroupIter)->getWorkplane()->string(
220               SketchPlugin_Sketch::SOLVER_ERROR())->value().empty() ||
221               (*aGroupIter)->isFailed())
222             aGroupsToResolve.push_back(*aGroupIter);
223         }
224         aGroupIter++;
225       }
226       if (aSeparatedGroups.size() > 0) {
227         myGroups.insert(myGroups.end(), aSeparatedGroups.begin(), aSeparatedGroups.end());
228         aGroupsToResolve.insert(aGroupsToResolve.end(),
229             aSeparatedGroups.begin(), aSeparatedGroups.end());
230       }
231
232       if (!aGroupsToResolve.empty())
233         resolveConstraints(aGroupsToResolve);
234     }
235     myIsComputed = false;
236   }
237
238   if (hasProperFeature)
239     degreesOfFreedom();
240 }
241
242 void SketchSolver_Manager::checkConflictingConstraints(const std::shared_ptr<Events_Message>& theMessage)
243 {
244   if (theMessage->eventID() == Events_Loop::loop()->eventByName(EVENT_SOLVER_REPAIRED)) {
245     std::shared_ptr<ModelAPI_SolverFailedMessage> aMessage =
246         std::dynamic_pointer_cast<ModelAPI_SolverFailedMessage>(theMessage);
247     std::set<ObjectPtr> aSentObjs = aMessage->objects();
248     if (!aSentObjs.empty()) {
249       // Obtain sketch where the constraints are placed.
250       // It is enough to check only one constraint.
251       CompositeFeaturePtr aSketch;
252       FeaturePtr aConstraint = ModelAPI_Feature::feature(*aSentObjs.begin());
253       std::list<SketchSolver_Group*>::const_iterator aGrIt = myGroups.begin();
254       for (; aGrIt != myGroups.end(); ++aGrIt)
255         if ((*aGrIt)->isInteract(aConstraint)) {
256           aSketch = (*aGrIt)->getWorkplane();
257           break;
258         }
259
260       // Search failed groups built on the same sketch
261       if (aSketch) {
262         for (aGrIt = myGroups.begin(); aGrIt != myGroups.end(); ++aGrIt) {
263           SketchSolver_Group* aGroup = *aGrIt;
264           if (aGroup->isBaseWorkplane(aSketch) && aGroup->isFailed() &&
265               !aGroup->isInteract(aConstraint)) {
266             // reset error message on the sketch
267             aGroup->getWorkplane()->string(SketchPlugin_Sketch::SOLVER_ERROR())->setValue(
268                 SketchSolver_Error::CONSTRAINTS());
269             break;
270           }
271         }
272       }
273     }
274   }
275 }
276
277 // ============================================================================
278 //  Function: changeWorkplane
279 //  Purpose:  update workplane by given parameters of the sketch
280 // ============================================================================
281 bool SketchSolver_Manager::changeWorkplane(CompositeFeaturePtr theSketch)
282 {
283   bool aResult = true;  // changed when a workplane wrongly updated
284   bool isUpdated = false;
285   // Try to update specified workplane in all groups
286   std::list<SketchSolver_Group*>::iterator aGroupIter;
287   for (aGroupIter = myGroups.begin(); aGroupIter != myGroups.end(); aGroupIter++)
288     if ((*aGroupIter)->isBaseWorkplane(theSketch)) {
289       isUpdated = true;
290       aResult = false;
291     }
292   // If the workplane is not updated, so this is a new workplane
293   if (!isUpdated) {
294     SketchSolver_Group* aNewGroup = new SketchSolver_Group(theSketch);
295     // Verify that the group is created successfully
296     if (!aNewGroup->isBaseWorkplane(theSketch) || !aNewGroup->isWorkplaneValid()) {
297       delete aNewGroup;
298       return false;
299     }
300     myGroups.push_back(aNewGroup);
301   }
302   return aResult || isUpdated;
303 }
304
305 // ============================================================================
306 //  Function: changeConstraintOrEntity
307 //  Purpose:  create/update the constraint or the feature and place it into appropriate group
308 // ============================================================================
309 bool SketchSolver_Manager::changeFeature(std::shared_ptr<SketchPlugin_Feature> theFeature)
310 {
311   // Search the groups which this feature touches
312   std::set<GroupID> aGroups;
313   findGroups(theFeature, aGroups);
314
315   std::shared_ptr<SketchPlugin_Constraint> aConstraint = 
316       std::dynamic_pointer_cast<SketchPlugin_Constraint>(theFeature);
317
318   // Process the groups list
319   if (aGroups.size() == 0) {
320     // There are no groups applicable for this constraint => create new one
321     // The group will be created only for constraints, not for features
322     if (!aConstraint) return false;
323     std::shared_ptr<ModelAPI_CompositeFeature> aWP = findWorkplane(aConstraint);
324     if (!aWP)
325       return false;
326     SketchSolver_Group* aGroup = new SketchSolver_Group(aWP);
327     if (!aGroup->changeConstraint(aConstraint)) {
328       delete aGroup;
329       return false;
330     }
331     myGroups.push_back(aGroup);
332     return true;
333   } else if (aGroups.size() == 1) {  // Only one group => add feature into it
334     GroupID aGroupId = *(aGroups.begin());
335     std::list<SketchSolver_Group*>::iterator aGroupIter;
336     for (aGroupIter = myGroups.begin(); aGroupIter != myGroups.end(); aGroupIter++)
337       if ((*aGroupIter)->getId() == aGroupId) {
338         // If the group is empty, the feature is not added (the constraint only)
339         if (!aConstraint && !(*aGroupIter)->isEmpty())
340           return (*aGroupIter)->updateFeature(theFeature);
341         return (*aGroupIter)->changeConstraint(aConstraint);
342       }
343   } else if (aGroups.size() > 1) {  // Several groups applicable for this feature => need to merge them
344     std::set<GroupID>::const_iterator aGroupsIter = aGroups.begin();
345
346     // Search first group
347     std::list<SketchSolver_Group*>::iterator aFirstGroupIter;
348     for (aFirstGroupIter = myGroups.begin(); aFirstGroupIter != myGroups.end(); aFirstGroupIter++)
349       if ((*aFirstGroupIter)->getId() == *aGroupsIter)
350         break;
351     if (aFirstGroupIter == myGroups.end())
352       return false;
353
354     // Append other groups to the first one
355     std::list<SketchSolver_Group*>::iterator anOtherGroupIter = aFirstGroupIter;
356     ++anOtherGroupIter;
357     for (aGroupsIter++; aGroupsIter != aGroups.end(); aGroupsIter++) {
358       for (; anOtherGroupIter != myGroups.end(); anOtherGroupIter++)
359         if ((*anOtherGroupIter)->getId() == *aGroupsIter)
360           break;
361       if (anOtherGroupIter == myGroups.end()) {  // Group disappears
362         anOtherGroupIter = aFirstGroupIter;
363         ++anOtherGroupIter;
364         continue;
365       }
366
367       (*aFirstGroupIter)->mergeGroups(**anOtherGroupIter);
368       std::list<SketchSolver_Group*>::iterator aRemoveIt = anOtherGroupIter++;
369       delete *aRemoveIt;
370       myGroups.erase(aRemoveIt);
371     }
372
373     if (aConstraint)
374       (*aFirstGroupIter)->changeConstraint(aConstraint);
375     else
376       (*aFirstGroupIter)->updateFeature(theFeature);
377     // groups are merged => need to resolve them
378     return true;
379   }
380
381   // Something goes wrong
382   return false;
383 }
384
385 // ============================================================================
386 //  Function: moveEntity
387 //  Purpose:  update element moved on the sketch, which is used by constraints
388 // ============================================================================
389 bool SketchSolver_Manager::moveEntity(std::shared_ptr<SketchPlugin_Feature> theFeature)
390 {
391   bool isMoved = false;
392   std::list<SketchSolver_Group*>::iterator aGroupIt = myGroups.begin();
393   for (; aGroupIt != myGroups.end(); aGroupIt++)
394     if (!(*aGroupIt)->isEmpty() && (*aGroupIt)->isInteract(theFeature))
395       isMoved = (*aGroupIt)->moveFeature(theFeature) || isMoved;
396
397   if (!isMoved && theFeature->getKind() == SketchPlugin_Arc::ID()) {
398     // Workaround to move arc.
399     // If the arc has not been constrained, we will push it into empty group and apply movement.
400     bool hasEmptyGroup = false;
401     for (aGroupIt = myGroups.begin(); aGroupIt != myGroups.end(); aGroupIt++)
402       if ((*aGroupIt)->isEmpty()) {
403         isMoved = (*aGroupIt)->moveFeature(theFeature) || isMoved;
404         hasEmptyGroup = true;
405       }
406     // There is no empty group, create it explicitly
407     if (!hasEmptyGroup) {
408       // find sketch containing the arc
409       CompositeFeaturePtr aWP;
410       const std::set<AttributePtr>& aRefs = theFeature->data()->refsToMe();
411       std::set<AttributePtr>::const_iterator aRefIt = aRefs.begin();
412       for (; aRefIt != aRefs.end(); ++aRefIt) {
413         FeaturePtr anOwner = ModelAPI_Feature::feature((*aRefIt)->owner());
414         if (anOwner && anOwner->getKind() == SketchPlugin_Sketch::ID()) {
415           aWP = std::dynamic_pointer_cast<ModelAPI_CompositeFeature>(anOwner);
416           break;
417         }
418       }
419       if (aWP) {
420         SketchSolver_Group* aGroup = new SketchSolver_Group(aWP);
421         isMoved = aGroup->moveFeature(theFeature) || isMoved;
422         myGroups.push_back(aGroup);
423       }
424     }
425   }
426   return isMoved;
427 }
428
429 // ============================================================================
430 //  Function: findGroups
431 //  Purpose:  search groups of entities interacting with given feature
432 // ============================================================================
433 void SketchSolver_Manager::findGroups(
434     std::shared_ptr<SketchPlugin_Feature> theFeature,
435     std::set<GroupID>& theGroupIDs) const
436 {
437   std::shared_ptr<ModelAPI_CompositeFeature> aWP = findWorkplane(theFeature);
438
439   SketchSolver_Group* anEmptyGroup = 0;  // appropriate empty group for specified constraint
440   std::list<SketchSolver_Group*>::const_iterator aGroupIter;
441   for (aGroupIter = myGroups.begin(); aGroupIter != myGroups.end(); aGroupIter++)
442     if (aWP == (*aGroupIter)->getWorkplane() && (*aGroupIter)->isInteract(theFeature)) {
443       if (!(*aGroupIter)->isEmpty())
444         theGroupIDs.insert((*aGroupIter)->getId());
445       else if (!anEmptyGroup)
446         anEmptyGroup = *aGroupIter;
447     }
448
449   // When only empty group is found, use it
450   if (anEmptyGroup && theGroupIDs.empty())
451     theGroupIDs.insert(anEmptyGroup->getId());
452 }
453
454 // ============================================================================
455 //  Function: findWorkplane
456 //  Purpose:  search workplane containing given feature
457 // ============================================================================
458 std::shared_ptr<ModelAPI_CompositeFeature> SketchSolver_Manager
459 ::findWorkplane(std::shared_ptr<SketchPlugin_Feature> theFeature) const
460 {
461   // Already verified workplanes
462   std::set<std::shared_ptr<ModelAPI_CompositeFeature> > aVerified;
463
464   std::list<SketchSolver_Group*>::const_iterator aGroupIter;
465   for (aGroupIter = myGroups.begin(); aGroupIter != myGroups.end(); aGroupIter++) {
466     std::shared_ptr<ModelAPI_CompositeFeature> aWP = (*aGroupIter)->getWorkplane();
467     if (aVerified.find(aWP) != aVerified.end())
468       continue;
469
470     DataPtr aData = aWP->data();
471     if (aData->isValid()) {
472       std::shared_ptr<ModelAPI_AttributeRefList> aWPFeatures = std::dynamic_pointer_cast<
473           ModelAPI_AttributeRefList>(aData->attribute(SketchPlugin_Sketch::FEATURES_ID()));
474       std::list<ObjectPtr> aFeaturesList = aWPFeatures->list();
475       std::list<ObjectPtr>::const_iterator anIter;
476       for (anIter = aFeaturesList.begin(); anIter != aFeaturesList.end(); anIter++)
477         if (*anIter == theFeature)
478           return aWP;  // workplane is found
479     }
480     aVerified.insert(aWP);
481   }
482
483   return std::shared_ptr<ModelAPI_CompositeFeature>();
484 }
485
486 // ============================================================================
487 //  Function: resolveConstraints
488 //  Purpose:  change entities according to available constraints
489 // ============================================================================
490 bool SketchSolver_Manager::resolveConstraints(const std::list<SketchSolver_Group*>& theGroups)
491 {
492   bool needToUpdate = false;
493   const std::list<SketchSolver_Group*>& aGroupsToResolve = theGroups.empty() ? myGroups : theGroups;
494   std::list<SketchSolver_Group*>::const_iterator aGroupIter = aGroupsToResolve.begin();
495   for (; aGroupIter != aGroupsToResolve.end(); aGroupIter++)
496     if ((*aGroupIter)->resolveConstraints())
497       needToUpdate = true;
498   return needToUpdate;
499 }
500
501
502 // Obtain points and their copies for Mirror, Multi-Rotation and Multi-Translation constraints
503 static void collectPointsAndCopies(FeaturePtr theConstraint, std::list<std::set<AttributePtr> >& thePoints)
504 {
505   typedef std::list<std::string> strlist;
506   static strlist aPointAttributes(1, SketchPlugin_Point::COORD_ID());
507   static strlist aLineAttributes;
508   if (aLineAttributes.empty()) {
509     aLineAttributes.push_back(SketchPlugin_Line::START_ID());
510     aLineAttributes.push_back(SketchPlugin_Line::END_ID());
511   };
512   static strlist aCircleAttributes(1, SketchPlugin_Circle::CENTER_ID());
513   static strlist anArcAttributes;
514   if (anArcAttributes.empty()) {
515     anArcAttributes.push_back(SketchPlugin_Arc::CENTER_ID());
516     anArcAttributes.push_back(SketchPlugin_Arc::START_ID());
517     anArcAttributes.push_back(SketchPlugin_Arc::END_ID());
518   };
519
520   static std::map<std::string, strlist> aFeatureAttributes;
521   if (aFeatureAttributes.empty()) {
522     aFeatureAttributes[SketchPlugin_Point::ID()] = aPointAttributes;
523     aFeatureAttributes[SketchPlugin_Line::ID()] = aLineAttributes;
524     aFeatureAttributes[SketchPlugin_Circle::ID()] = aCircleAttributes;
525     aFeatureAttributes[SketchPlugin_Arc::ID()] = anArcAttributes;
526   }
527
528
529   std::set<AttributePtr> aPoints;
530   if (theConstraint->getKind() == SketchPlugin_ConstraintMirror::ID()) {
531     AttributeRefListPtr aBaseRefList = theConstraint->reflist(SketchPlugin_Constraint::ENTITY_B());
532     AttributeRefListPtr aMirrRefList = theConstraint->reflist(SketchPlugin_Constraint::ENTITY_C());
533
534     std::list<ObjectPtr> aBaseList = aBaseRefList->list();
535     std::list<ObjectPtr> aMirrList = aMirrRefList->list();
536     std::list<ObjectPtr>::const_iterator aBIt, aMIt;
537     for (aBIt = aBaseList.begin(), aMIt = aMirrList.begin();
538          aBIt != aBaseList.end() && aMIt != aMirrList.end(); ++aBIt, ++aMIt) {
539       FeaturePtr aBaseFeature = ModelAPI_Feature::feature(*aBIt);
540       FeaturePtr aMirrFeature = ModelAPI_Feature::feature(*aMIt);
541
542       strlist anAttrList = aFeatureAttributes[aBaseFeature->getKind()];
543       strlist::iterator anIt = anAttrList.begin();
544       for (; anIt != anAttrList.end(); ++anIt) {
545         aPoints.clear();
546         aPoints.insert(aBaseFeature->attribute(*anIt));
547         aPoints.insert(aMirrFeature->attribute(*anIt));
548         thePoints.push_back(aPoints);
549       }
550     }
551   }
552   else { // the "Multi" constraints
553     std::string aNbObjName;
554     if (theConstraint->getKind() == SketchPlugin_MultiRotation::ID())
555       aNbObjName = SketchPlugin_MultiRotation::NUMBER_OF_OBJECTS_ID();
556     else
557       aNbObjName = SketchPlugin_MultiTranslation::NUMBER_OF_OBJECTS_ID();
558     int aNbCopies = theConstraint->integer(aNbObjName)->value();
559
560     AttributeRefListPtr aRefList = theConstraint->reflist(SketchPlugin_Constraint::ENTITY_B());
561     std::list<ObjectPtr> aFullList = aRefList->list();
562     std::list<ObjectPtr>::const_iterator anObjIt = aFullList.begin();
563     std::list<ObjectPtr>::const_iterator aCopyIt;
564     while (anObjIt != aFullList.end()) {
565       FeaturePtr aBaseFeature = ModelAPI_Feature::feature(*anObjIt);
566       strlist anAttrList = aFeatureAttributes[aBaseFeature->getKind()];
567       strlist::iterator anIt = anAttrList.begin();
568       for (; anIt != anAttrList.end(); ++anIt) {
569         aPoints.clear();
570         aCopyIt = anObjIt;
571         for (int i = 0; i < aNbCopies && aCopyIt != aFullList.end(); ++i, ++aCopyIt) {
572           FeaturePtr aFeature = ModelAPI_Feature::feature(*aCopyIt);
573           aPoints.insert(aFeature->attribute(*anIt));
574         }
575         thePoints.push_back(aPoints);
576       }
577       anObjIt = aCopyIt;
578     }
579   }
580 }
581
582
583 // returns true if the feature is external
584 static bool isExternal(const FeaturePtr& theFeature)
585 {
586   AttributeSelectionPtr anAttr = theFeature->selection(SketchPlugin_SketchEntity::EXTERNAL_ID());
587   return anAttr && anAttr->context() && !anAttr->isInvalid();
588 }
589
590 // ============================================================================
591 //  Function: degreesOfFreedom
592 //  Purpose:  calculate DoFs for each sketch
593 // ============================================================================
594 void SketchSolver_Manager::degreesOfFreedom()
595 {
596   static std::map<std::string, int> aDoFDelta; // indicates how many DoF adds or decreases a feature
597   static bool isNeedInit = true;
598   if (isNeedInit) {
599     aDoFDelta[SketchPlugin_Point::ID()] = 2;
600     aDoFDelta[SketchPlugin_Line::ID()] = 4;
601     aDoFDelta[SketchPlugin_Circle::ID()] = 3;
602     aDoFDelta[SketchPlugin_Arc::ID()] = 5;
603
604     aDoFDelta[SketchPlugin_ConstraintAngle::ID()] = -1;
605     aDoFDelta[SketchPlugin_ConstraintCollinear::ID()] = -1;
606     aDoFDelta[SketchPlugin_ConstraintDistance::ID()] = -1;
607     aDoFDelta[SketchPlugin_ConstraintEqual::ID()] = -1;
608     aDoFDelta[SketchPlugin_ConstraintHorizontal::ID()] = -1;
609     aDoFDelta[SketchPlugin_ConstraintLength::ID()] = -1;
610     aDoFDelta[SketchPlugin_ConstraintParallel::ID()] = -1;
611     aDoFDelta[SketchPlugin_ConstraintPerpendicular::ID()] = -1;
612     aDoFDelta[SketchPlugin_ConstraintRadius::ID()] = -1;
613     aDoFDelta[SketchPlugin_ConstraintTangent::ID()] = -1;
614     aDoFDelta[SketchPlugin_ConstraintVertical::ID()] = -1;
615
616     isNeedInit = false;
617   }
618
619   std::map<CompositeFeaturePtr, int> aSketchDoF;
620
621   std::list<SketchSolver_Group*>::const_iterator aGroupIt = myGroups.begin();
622   for (; aGroupIt != myGroups.end(); ++aGroupIt) {
623     CompositeFeaturePtr aSketch = (*aGroupIt)->getWorkplane();
624     bool isSketchValid = aSketch->data() && aSketch->data()->isValid();
625
626     if (isSketchValid) {
627       std::shared_ptr<GeomDataAPI_Dir> aNormal =
628           std::dynamic_pointer_cast<GeomDataAPI_Dir>(aSketch->data()->attribute(SketchPlugin_Sketch::NORM_ID()));
629       isSketchValid = aNormal && aNormal->isInitialized();
630     }
631
632     if (!isSketchValid) {
633       myDoF.erase(aSketch);
634       continue;
635     }
636
637     // check conflicting constraints in the group
638     if ((*aGroupIt)->isFailed())
639       aSketchDoF[aSketch] = -1;
640     // check the sketch is already processed
641     if (aSketchDoF.find(aSketch) != aSketchDoF.end() || aSketchDoF[aSketch] < 0)
642       continue;
643
644     std::set<AttributePtr> aCoincidentPoints;
645     std::set<AttributePtr> aFixedPoints;
646     std::map<AttributePtr, std::set<FeaturePtr> > aPointOnLine;
647     std::list<std::set<AttributePtr> > aPointsInMultiConstraints;
648     int aDoF = 0;
649     int aNbSubs = aSketch->numberOfSubs();
650     for (int i = 0; i < aNbSubs; ++i) {
651       FeaturePtr aFeature = aSketch->subFeature(i);
652       // do not change DoF for external feature
653       if (isExternal(aFeature))
654         continue;
655       // check DoF delta for invariant types
656       std::map<std::string, int>::const_iterator aFound = aDoFDelta.find(aFeature->getKind());
657       if (aFound != aDoFDelta.end()) {
658         aDoF += aFound->second;
659         continue;
660       }
661
662       // DoF delta in specific cases
663       if (aFeature->getKind() == SketchPlugin_ConstraintCoincidence::ID()) {
664         AttributePtr aCoincPoint[2] = {AttributePtr(), AttributePtr()};
665         FeaturePtr aCoincLine;
666         for (int j = 0; j < 2; ++j) {
667           AttributeRefAttrPtr aRefAttr = std::dynamic_pointer_cast<ModelAPI_AttributeRefAttr>(
668               aFeature->attribute(SketchPlugin_Constraint::ATTRIBUTE(j)));
669           if (!aRefAttr)
670             continue;
671           if (!aRefAttr->isObject())
672             aCoincPoint[j] = aRefAttr->attr();
673           else {
674             FeaturePtr anAttr = ModelAPI_Feature::feature(aRefAttr->object());
675             if (!anAttr)
676               continue;
677             if (anAttr->getKind() == SketchPlugin_Point::ID())
678               aCoincPoint[j] = anAttr->attribute(SketchPlugin_Point::COORD_ID());
679             else if (anAttr->getKind() == SketchPlugin_Line::ID())
680               aCoincLine = anAttr;
681           }
682         }
683         if (aCoincPoint[0] && aCoincPoint[1]) {
684           bool isDoFDecreased = false;
685           // point-point coincidence
686           if (aCoincidentPoints.find(aCoincPoint[0]) == aCoincidentPoints.end() ||
687               aCoincidentPoints.find(aCoincPoint[1]) == aCoincidentPoints.end()) {
688             aDoF -= 2;
689             isDoFDecreased = true;
690           }
691           // check the coincident point is used in "multi" constraints
692           std::list<std::set<AttributePtr> >::const_iterator
693               aPtIt = aPointsInMultiConstraints.begin();
694           bool isFound[2] = {false, false};
695           for (; aPtIt != aPointsInMultiConstraints.end(); ++aPtIt) {
696             if ((!isFound[0] && (isFound[0] = (aPtIt->find(aCoincPoint[0]) != aPtIt->end())))
697              || (!isFound[1] && (isFound[1] = (aPtIt->find(aCoincPoint[1]) != aPtIt->end()))))
698               aCoincidentPoints.insert(aPtIt->begin(), aPtIt->end());
699             if (isFound[0] && isFound[1])
700               break;
701           }
702           // check both points are fixed => not need to decrease DoF
703           bool isFixed[2] = { aFixedPoints.find(aCoincPoint[0]) != aFixedPoints.end(),
704                               aFixedPoints.find(aCoincPoint[1]) != aFixedPoints.end() };
705           if (isFixed[0] && isFixed[1] && isDoFDecreased)
706             aDoF += 2; // revert decrease of DoF
707           else if (isFixed[0] && !isFixed[1])
708             aFixedPoints.insert(aCoincPoint[1]);
709           else if (!isFixed[0] && isFixed[1])
710             aFixedPoints.insert(aCoincPoint[0]);
711         } else {
712           aDoF -= 1;
713           if (aCoincPoint[0] && aCoincLine) {
714             // if the point is already coincident to a line (by middle point constraint), do not decrease DoF
715             std::map<AttributePtr, std::set<FeaturePtr> >::iterator
716                 aPtFound = aPointOnLine.find(aCoincPoint[0]);
717             if (aPtFound != aPointOnLine.end() &&
718                 aPtFound->second.find(aCoincLine) != aPtFound->second.end())
719               aDoF += 1; // restore value decreased above
720             else
721               aPointOnLine[aCoincPoint[0]].insert(aCoincLine);
722           }
723         }
724         for (int j = 0; j < 2; ++j)
725           if (aCoincPoint[j])
726             aCoincidentPoints.insert(aCoincPoint[j]);
727       }
728       else if (aFeature->getKind() == SketchPlugin_ConstraintMiddle::ID()) {
729         AttributePtr aPoint;
730         FeaturePtr aLine;
731         for (int j = 0; j < 2; ++j) {
732           AttributeRefAttrPtr aRefAttr = std::dynamic_pointer_cast<ModelAPI_AttributeRefAttr>(
733               aFeature->attribute(SketchPlugin_Constraint::ATTRIBUTE(j)));
734           if (!aRefAttr)
735             continue;
736           if (aRefAttr->isObject())
737             aLine = ModelAPI_Feature::feature(aRefAttr->object());
738           else
739             aPoint = aRefAttr->attr();
740         }
741         if (aPoint && aLine) {
742           // if the point is already on the line, decrease 1 DoF, instead decrease 2 DoF
743           std::map<AttributePtr, std::set<FeaturePtr> >::iterator
744               aPtFound = aPointOnLine.find(aPoint);
745           if (aPtFound != aPointOnLine.end() &&
746               aPtFound->second.find(aLine) != aPtFound->second.end())
747             aDoF -= 1;
748           else {
749             aDoF -= 2;
750             aPointOnLine[aPoint].insert(aLine);
751           }
752         }
753       }
754       else if (aFeature->getKind() == SketchPlugin_ConstraintRigid::ID()) {
755         AttributeRefAttrPtr aRefAttr = std::dynamic_pointer_cast<ModelAPI_AttributeRefAttr>(
756             aFeature->attribute(SketchPlugin_Constraint::ENTITY_A()));
757         assert(aRefAttr);
758         std::set<AttributePtr> aPoints;
759         if (!aRefAttr->isObject()) {
760           aDoF -= 2; // attribute is a point
761           aPoints.insert(aRefAttr->attr());
762         } else {
763           FeaturePtr anAttr = ModelAPI_Feature::feature(aRefAttr->object());
764           if (anAttr) {
765             if (isExternal(anAttr))
766               continue; // feature is already fixed since it is external
767             aDoF -= aDoFDelta[anAttr->getKind()];
768             std::list<AttributePtr> aPtAttrs = anAttr->data()->attributes(GeomDataAPI_Point2D::typeId());
769             aPoints.insert(aPtAttrs.begin(), aPtAttrs.end());
770           }
771         }
772
773         // Check whether feature's points are already coincident with fixed points.
774         // In this case we need to revert decrease of DoF for these points.
775         // If the coordinates of fixed points are different, it will be processed by solver.
776         for (int k = 0; k < i; ++k) {
777           FeaturePtr aFeature = aSketch->subFeature(k);
778           if (aFeature->getKind() != SketchPlugin_ConstraintCoincidence::ID())
779             continue;
780           AttributePtr aCoincPoint[2] = {AttributePtr(), AttributePtr()};
781           for (int j = 0; j < 2; ++j) {
782             AttributeRefAttrPtr aRefAttr = std::dynamic_pointer_cast<ModelAPI_AttributeRefAttr>(
783                 aFeature->attribute(SketchPlugin_Constraint::ATTRIBUTE(j)));
784             if (!aRefAttr)
785               continue;
786             if (!aRefAttr->isObject())
787               aCoincPoint[j] = aRefAttr->attr();
788             else {
789               FeaturePtr anAttr = ModelAPI_Feature::feature(aRefAttr->object());
790               if (anAttr && anAttr->getKind() == SketchPlugin_Point::ID())
791                 aCoincPoint[j] = anAttr->attribute(SketchPlugin_Point::COORD_ID());
792             }
793           }
794           if (aCoincPoint[0] && aCoincPoint[1]) {
795             if ((aFixedPoints.find(aCoincPoint[0]) != aFixedPoints.end() &&
796                  aPoints.find(aCoincPoint[1]) != aPoints.end()) ||
797                 (aFixedPoints.find(aCoincPoint[1]) != aFixedPoints.end() &&
798                  aPoints.find(aCoincPoint[0]) != aPoints.end()))
799               aDoF += 2; // point already fixed
800           }
801         }
802         // store fixed points
803         aFixedPoints.insert(aPoints.begin(), aPoints.end());
804       }
805       else if (aFeature->getKind() == SketchPlugin_ConstraintMirror::ID() ||
806                aFeature->getKind() == SketchPlugin_MultiRotation::ID() ||
807                aFeature->getKind() == SketchPlugin_MultiTranslation::ID()) {
808         int aNbCopies = 1;
809         std::string anAttrName;
810         if (aFeature->getKind() == SketchPlugin_ConstraintMirror::ID())
811           anAttrName = SketchPlugin_Constraint::ENTITY_B();
812         else {
813           if (aFeature->getKind() == SketchPlugin_MultiRotation::ID())
814             aNbCopies = aFeature->integer(SketchPlugin_MultiRotation::NUMBER_OF_OBJECTS_ID())->value() - 1;
815           else if (aFeature->getKind() == SketchPlugin_MultiTranslation::ID())
816             aNbCopies = aFeature->integer(SketchPlugin_MultiTranslation::NUMBER_OF_OBJECTS_ID())->value() - 1;
817           anAttrName = SketchPlugin_Constraint::ENTITY_A();
818         }
819
820         AttributeRefListPtr aRefListOfShapes = std::dynamic_pointer_cast<ModelAPI_AttributeRefList>(
821             aFeature->attribute(anAttrName));
822         std::list<ObjectPtr> anObjList = aRefListOfShapes->list();
823         std::list<ObjectPtr>::const_iterator anObjIt = anObjList.begin();
824         for (; anObjIt != anObjList.end(); ++anObjIt) {
825           FeaturePtr aSub = ModelAPI_Feature::feature(*anObjIt);
826           aDoF -= aDoFDelta[aSub->getKind()] * aNbCopies;
827         }
828         // collect points and their copies for correct calculation of DoF for coincident points
829         collectPointsAndCopies(aFeature, aPointsInMultiConstraints);
830       }
831     }
832
833     aSketchDoF[aSketch] = aDoF;
834   }
835
836   // Check the degrees of freedom are changed
837   std::map<CompositeFeaturePtr, int>::const_iterator aDoFIt = aSketchDoF.begin();
838   std::map<CompositeFeaturePtr, int>::iterator aFound;
839   for (; aDoFIt != aSketchDoF.end(); ++aDoFIt) {
840     if (aDoFIt->second < 0)
841       continue; // conflicting constraints on the current sketch
842     aFound = myDoF.find(aDoFIt->first);
843     if (aFound != myDoF.end() && aFound->second == aDoFIt->second)
844       continue; // nothing is changed
845     myDoF[aDoFIt->first] = aDoFIt->second;
846     // change attribute value
847     std::ostringstream aStream;
848     if (aDoFIt->second == 0)
849       aStream << "Sketch fully fixed (DOF = " << aDoFIt->second << ")";
850     else
851       aStream << "DOF (degree of freedom) = " << aDoFIt->second;
852     aDoFIt->first->data()->string(SketchPlugin_Sketch::SOLVER_DOF())->setValue(aStream.str());
853   }
854 }
855
856 bool SketchSolver_Manager::stopSendUpdate() const
857 {
858   // to avoid redisplay of each segment on update by solver one by one in the viewer
859   bool isUpdateFlushed = Events_Loop::loop()->isFlushed(anUpdateEvent);
860   if (isUpdateFlushed) {
861     Events_Loop::loop()->setFlushed(anUpdateEvent, false);
862   }
863   return isUpdateFlushed;
864 }
865
866 void SketchSolver_Manager::allowSendUpdate() const
867 {
868   Events_Loop::loop()->setFlushed(anUpdateEvent, true);
869 }