X-Git-Url: http://git.salome-platform.org/gitweb/?a=blobdiff_plain;ds=sidebyside;f=src%2FSketchSolver%2FSketchSolver_Manager.cpp;h=80f1f98ef7349e26fcc46d12c48a5fec78a01b5e;hb=9dc1fc2ff6f4143369cdb2042a5e01e8e737c635;hp=aef46368a1251c6b4180db8607f7fc43b4049095;hpb=8f2e8789faa06405e900ee2272858a8ca898ba32;p=modules%2Fshaper.git diff --git a/src/SketchSolver/SketchSolver_Manager.cpp b/src/SketchSolver/SketchSolver_Manager.cpp index aef46368a..80f1f98ef 100644 --- a/src/SketchSolver/SketchSolver_Manager.cpp +++ b/src/SketchSolver/SketchSolver_Manager.cpp @@ -8,6 +8,9 @@ #include "SketchSolver_Error.h" #include + +#include + #include #include #include @@ -76,12 +79,13 @@ SketchSolver_Manager::SketchSolver_Manager() // Register in event loop Events_Loop::loop()->registerListener(this, Events_Loop::eventByName(EVENT_OBJECT_CREATED)); - Events_Loop::loop()->registerListener(this, Events_Loop::eventByName(EVENT_OBJECT_UPDATED)); + Events_Loop::loop()->registerListener(this, anUpdateEvent); Events_Loop::loop()->registerListener(this, Events_Loop::eventByName(EVENT_OBJECT_DELETED)); Events_Loop::loop()->registerListener(this, Events_Loop::eventByName(EVENT_OBJECT_MOVED)); Events_Loop::loop()->registerListener(this, Events_Loop::eventByName(EVENT_SOLVER_FAILED)); Events_Loop::loop()->registerListener(this, Events_Loop::eventByName(EVENT_SOLVER_REPAIRED)); + Events_Loop::loop()->registerListener(this, Events_Loop::eventByName(EVENT_SKETCH_PREPARED)); } SketchSolver_Manager::~SketchSolver_Manager() @@ -99,6 +103,11 @@ BuilderPtr SketchSolver_Manager::builder() return myBuilder; } +bool SketchSolver_Manager::groupMessages() +{ + return true; +} + // ============================================================================ // Function: processEvent // Purpose: listen the event loop and process the message @@ -106,7 +115,16 @@ BuilderPtr SketchSolver_Manager::builder() void SketchSolver_Manager::processEvent( const std::shared_ptr& theMessage) { + static const Events_ID aSketchPreparedEvent = Events_Loop::eventByName(EVENT_SKETCH_PREPARED); + // sketch is prepared for resolve: all the needed events + // are collected and must be processed by the solver + if (theMessage->eventID() == aSketchPreparedEvent) { + flushGrouped(anUpdateEvent); + return; + } + checkConflictingConstraints(theMessage); + if (myIsComputed) return; myIsComputed = true; @@ -115,7 +133,7 @@ void SketchSolver_Manager::processEvent( bool hasProperFeature = false; if (theMessage->eventID() == Events_Loop::loop()->eventByName(EVENT_OBJECT_CREATED) - || theMessage->eventID() == Events_Loop::loop()->eventByName(EVENT_OBJECT_UPDATED) + || theMessage->eventID() == anUpdateEvent || theMessage->eventID() == Events_Loop::loop()->eventByName(EVENT_OBJECT_MOVED)) { std::shared_ptr anUpdateMsg = std::dynamic_pointer_cast(theMessage); @@ -128,24 +146,31 @@ void SketchSolver_Manager::processEvent( if (isMovedEvt) { std::set::iterator aFeatIter; for (aFeatIter = aFeatures.begin(); aFeatIter != aFeatures.end(); aFeatIter++) { - std::shared_ptr aSFeature = + std::shared_ptr aSFeature = std::dynamic_pointer_cast(*aFeatIter); - if (aSFeature) { - moveEntity(aSFeature); + if (aSFeature && moveEntity(aSFeature)) { + // Want to avoid recalculation of DoF too frequently. + // So, set the flag when the feature is really moved. hasProperFeature = true; } } + if (!hasProperFeature) + // in this iteration it will compute nothing, so, no problem with recursion + // it is important that solver flushes signal updated after processing move signal as there + // is optimization that relies on this update, might be found by key "optimization" + myIsComputed = false; } else { - std::list aSketchFeatures = SketchSolver_Group::selectApplicableFeatures(aFeatures); + std::list aSketchFeatures = + SketchSolver_Group::selectApplicableFeatures(aFeatures); std::list::iterator aFeatIter = aSketchFeatures.begin(); for (; aFeatIter != aSketchFeatures.end(); ++aFeatIter) { if ((*aFeatIter)->getKind() == SketchPlugin_Sketch::ID()) { - std::shared_ptr aSketch = + std::shared_ptr aSketch = std::dynamic_pointer_cast(*aFeatIter); hasProperFeature = changeWorkplane(aSketch) || hasProperFeature; continue; } - std::shared_ptr aFeature = + std::shared_ptr aFeature = std::dynamic_pointer_cast(*aFeatIter); if (!aFeature) continue; @@ -161,6 +186,9 @@ void SketchSolver_Manager::processEvent( // Features may be updated => now send events, but for all changed at once if (isUpdateFlushed) allowSendUpdate(); + + myIsComputed = false; + // send update for movement in any case if (needToUpdate || isMovedEvt) Events_Loop::loop()->flush(anUpdateEvent); @@ -170,7 +198,8 @@ void SketchSolver_Manager::processEvent( std::dynamic_pointer_cast(theMessage); const std::set& aFeatureGroups = aDeleteMsg->groups(); - // Find SketchPlugin_Sketch::ID() in groups. The constraint groups should be updated when an object removed from Sketch + // Find SketchPlugin_Sketch::ID() in groups. + // The constraint groups should be updated when an object removed from Sketch std::set::const_iterator aFGrIter; for (aFGrIter = aFeatureGroups.begin(); aFGrIter != aFeatureGroups.end(); aFGrIter++) if (aFGrIter->compare(ModelAPI_ResultConstruction::group()) == 0 || @@ -189,10 +218,12 @@ void SketchSolver_Manager::processEvent( myGroups.erase(aRemoveIt); continue; } - if (!(*aGroupIter)->isConsistent()) { // some constraints were removed, try to split the group + if (!(*aGroupIter)->isConsistent()) { + // some constraints were removed, try to split the group (*aGroupIter)->splitGroup(aSeparatedGroups); - //if (!(*aGroupIter)->getWorkplane()->string( - // SketchPlugin_Sketch::SOLVER_ERROR())->value().empty()) + if (!(*aGroupIter)->getWorkplane()->string( + SketchPlugin_Sketch::SOLVER_ERROR())->value().empty() || + (*aGroupIter)->isFailed()) aGroupsToResolve.push_back(*aGroupIter); } aGroupIter++; @@ -206,14 +237,15 @@ void SketchSolver_Manager::processEvent( if (!aGroupsToResolve.empty()) resolveConstraints(aGroupsToResolve); } + myIsComputed = false; } if (hasProperFeature) degreesOfFreedom(); - myIsComputed = false; } -void SketchSolver_Manager::checkConflictingConstraints(const std::shared_ptr& theMessage) +void SketchSolver_Manager:: + checkConflictingConstraints(const std::shared_ptr& theMessage) { if (theMessage->eventID() == Events_Loop::loop()->eventByName(EVENT_SOLVER_REPAIRED)) { std::shared_ptr aMessage = @@ -273,7 +305,7 @@ bool SketchSolver_Manager::changeWorkplane(CompositeFeaturePtr theSketch) } myGroups.push_back(aNewGroup); } - return aResult; + return aResult || isUpdated; } // ============================================================================ @@ -286,7 +318,7 @@ bool SketchSolver_Manager::changeFeature(std::shared_ptr t std::set aGroups; findGroups(theFeature, aGroups); - std::shared_ptr aConstraint = + std::shared_ptr aConstraint = std::dynamic_pointer_cast(theFeature); // Process the groups list @@ -314,7 +346,8 @@ bool SketchSolver_Manager::changeFeature(std::shared_ptr t return (*aGroupIter)->updateFeature(theFeature); return (*aGroupIter)->changeConstraint(aConstraint); } - } else if (aGroups.size() > 1) { // Several groups applicable for this feature => need to merge them + } else if (aGroups.size() > 1) { + // Several groups applicable for this feature => need to merge them std::set::const_iterator aGroupsIter = aGroups.begin(); // Search first group @@ -360,23 +393,44 @@ bool SketchSolver_Manager::changeFeature(std::shared_ptr t // Function: moveEntity // Purpose: update element moved on the sketch, which is used by constraints // ============================================================================ -void SketchSolver_Manager::moveEntity(std::shared_ptr theFeature) +bool SketchSolver_Manager::moveEntity(std::shared_ptr theFeature) { bool isMoved = false; std::list::iterator aGroupIt = myGroups.begin(); for (; aGroupIt != myGroups.end(); aGroupIt++) - if (!(*aGroupIt)->isEmpty() && (*aGroupIt)->isInteract(theFeature)) { - (*aGroupIt)->moveFeature(theFeature); - isMoved = true; - } + if (!(*aGroupIt)->isEmpty() && (*aGroupIt)->isInteract(theFeature)) + isMoved = (*aGroupIt)->moveFeature(theFeature) || isMoved; if (!isMoved && theFeature->getKind() == SketchPlugin_Arc::ID()) { // Workaround to move arc. // If the arc has not been constrained, we will push it into empty group and apply movement. + bool hasEmptyGroup = false; for (aGroupIt = myGroups.begin(); aGroupIt != myGroups.end(); aGroupIt++) - if ((*aGroupIt)->isEmpty()) - (*aGroupIt)->moveFeature(theFeature); + if ((*aGroupIt)->isEmpty()) { + isMoved = (*aGroupIt)->moveFeature(theFeature) || isMoved; + hasEmptyGroup = true; + } + // There is no empty group, create it explicitly + if (!hasEmptyGroup) { + // find sketch containing the arc + CompositeFeaturePtr aWP; + const std::set& aRefs = theFeature->data()->refsToMe(); + std::set::const_iterator aRefIt = aRefs.begin(); + for (; aRefIt != aRefs.end(); ++aRefIt) { + FeaturePtr anOwner = ModelAPI_Feature::feature((*aRefIt)->owner()); + if (anOwner && anOwner->getKind() == SketchPlugin_Sketch::ID()) { + aWP = std::dynamic_pointer_cast(anOwner); + break; + } + } + if (aWP) { + SketchSolver_Group* aGroup = new SketchSolver_Group(aWP); + isMoved = aGroup->moveFeature(theFeature) || isMoved; + myGroups.push_back(aGroup); + } + } } + return isMoved; } // ============================================================================ @@ -443,7 +497,8 @@ std::shared_ptr SketchSolver_Manager bool SketchSolver_Manager::resolveConstraints(const std::list& theGroups) { bool needToUpdate = false; - const std::list& aGroupsToResolve = theGroups.empty() ? myGroups : theGroups; + const std::list& aGroupsToResolve = theGroups.empty() ? + myGroups : theGroups; std::list::const_iterator aGroupIter = aGroupsToResolve.begin(); for (; aGroupIter != aGroupsToResolve.end(); aGroupIter++) if ((*aGroupIter)->resolveConstraints()) @@ -451,6 +506,96 @@ bool SketchSolver_Manager::resolveConstraints(const std::list >& thePoints) +{ + typedef std::list strlist; + static strlist aPointAttributes(1, SketchPlugin_Point::COORD_ID()); + static strlist aLineAttributes; + if (aLineAttributes.empty()) { + aLineAttributes.push_back(SketchPlugin_Line::START_ID()); + aLineAttributes.push_back(SketchPlugin_Line::END_ID()); + }; + static strlist aCircleAttributes(1, SketchPlugin_Circle::CENTER_ID()); + static strlist anArcAttributes; + if (anArcAttributes.empty()) { + anArcAttributes.push_back(SketchPlugin_Arc::CENTER_ID()); + anArcAttributes.push_back(SketchPlugin_Arc::START_ID()); + anArcAttributes.push_back(SketchPlugin_Arc::END_ID()); + }; + + static std::map aFeatureAttributes; + if (aFeatureAttributes.empty()) { + aFeatureAttributes[SketchPlugin_Point::ID()] = aPointAttributes; + aFeatureAttributes[SketchPlugin_Line::ID()] = aLineAttributes; + aFeatureAttributes[SketchPlugin_Circle::ID()] = aCircleAttributes; + aFeatureAttributes[SketchPlugin_Arc::ID()] = anArcAttributes; + } + + + std::set aPoints; + if (theConstraint->getKind() == SketchPlugin_ConstraintMirror::ID()) { + AttributeRefListPtr aBaseRefList = theConstraint->reflist(SketchPlugin_Constraint::ENTITY_B()); + AttributeRefListPtr aMirrRefList = theConstraint->reflist(SketchPlugin_Constraint::ENTITY_C()); + + std::list aBaseList = aBaseRefList->list(); + std::list aMirrList = aMirrRefList->list(); + std::list::const_iterator aBIt, aMIt; + for (aBIt = aBaseList.begin(), aMIt = aMirrList.begin(); + aBIt != aBaseList.end() && aMIt != aMirrList.end(); ++aBIt, ++aMIt) { + FeaturePtr aBaseFeature = ModelAPI_Feature::feature(*aBIt); + FeaturePtr aMirrFeature = ModelAPI_Feature::feature(*aMIt); + + strlist anAttrList = aFeatureAttributes[aBaseFeature->getKind()]; + strlist::iterator anIt = anAttrList.begin(); + for (; anIt != anAttrList.end(); ++anIt) { + aPoints.clear(); + aPoints.insert(aBaseFeature->attribute(*anIt)); + aPoints.insert(aMirrFeature->attribute(*anIt)); + thePoints.push_back(aPoints); + } + } + } + else { // the "Multi" constraints + std::string aNbObjName; + if (theConstraint->getKind() == SketchPlugin_MultiRotation::ID()) + aNbObjName = SketchPlugin_MultiRotation::NUMBER_OF_OBJECTS_ID(); + else + aNbObjName = SketchPlugin_MultiTranslation::NUMBER_OF_OBJECTS_ID(); + int aNbCopies = theConstraint->integer(aNbObjName)->value(); + + AttributeRefListPtr aRefList = theConstraint->reflist(SketchPlugin_Constraint::ENTITY_B()); + std::list aFullList = aRefList->list(); + std::list::const_iterator anObjIt = aFullList.begin(); + std::list::const_iterator aCopyIt; + while (anObjIt != aFullList.end()) { + FeaturePtr aBaseFeature = ModelAPI_Feature::feature(*anObjIt); + strlist anAttrList = aFeatureAttributes[aBaseFeature->getKind()]; + strlist::iterator anIt = anAttrList.begin(); + for (; anIt != anAttrList.end(); ++anIt) { + aPoints.clear(); + aCopyIt = anObjIt; + for (int i = 0; i < aNbCopies && aCopyIt != aFullList.end(); ++i, ++aCopyIt) { + FeaturePtr aFeature = ModelAPI_Feature::feature(*aCopyIt); + aPoints.insert(aFeature->attribute(*anIt)); + } + thePoints.push_back(aPoints); + } + anObjIt = aCopyIt; + } + } +} + + +// returns true if the feature is external +static bool isExternal(const FeaturePtr& theFeature) +{ + AttributeSelectionPtr anAttr = theFeature->selection(SketchPlugin_SketchEntity::EXTERNAL_ID()); + return anAttr && anAttr->context() && !anAttr->isInvalid(); +} + // ============================================================================ // Function: degreesOfFreedom // Purpose: calculate DoFs for each sketch @@ -471,7 +616,6 @@ void SketchSolver_Manager::degreesOfFreedom() aDoFDelta[SketchPlugin_ConstraintEqual::ID()] = -1; aDoFDelta[SketchPlugin_ConstraintHorizontal::ID()] = -1; aDoFDelta[SketchPlugin_ConstraintLength::ID()] = -1; - aDoFDelta[SketchPlugin_ConstraintMiddle::ID()] = -1; aDoFDelta[SketchPlugin_ConstraintParallel::ID()] = -1; aDoFDelta[SketchPlugin_ConstraintPerpendicular::ID()] = -1; aDoFDelta[SketchPlugin_ConstraintRadius::ID()] = -1; @@ -486,7 +630,16 @@ void SketchSolver_Manager::degreesOfFreedom() std::list::const_iterator aGroupIt = myGroups.begin(); for (; aGroupIt != myGroups.end(); ++aGroupIt) { CompositeFeaturePtr aSketch = (*aGroupIt)->getWorkplane(); - if (!aSketch->data()->isValid()) { + bool isSketchValid = aSketch->data() && aSketch->data()->isValid(); + + if (isSketchValid) { + std::shared_ptr aNormal = + std::dynamic_pointer_cast( + aSketch->data()->attribute(SketchPlugin_Sketch::NORM_ID())); + isSketchValid = aNormal && aNormal->isInitialized(); + } + + if (!isSketchValid) { myDoF.erase(aSketch); continue; } @@ -499,10 +652,16 @@ void SketchSolver_Manager::degreesOfFreedom() continue; std::set aCoincidentPoints; + std::set aFixedPoints; + std::map > aPointOnLine; + std::list > aPointsInMultiConstraints; int aDoF = 0; int aNbSubs = aSketch->numberOfSubs(); for (int i = 0; i < aNbSubs; ++i) { FeaturePtr aFeature = aSketch->subFeature(i); + // do not change DoF for external feature + if (isExternal(aFeature)) + continue; // check DoF delta for invariant types std::map::const_iterator aFound = aDoFDelta.find(aFeature->getKind()); if (aFound != aDoFDelta.end()) { @@ -513,43 +672,147 @@ void SketchSolver_Manager::degreesOfFreedom() // DoF delta in specific cases if (aFeature->getKind() == SketchPlugin_ConstraintCoincidence::ID()) { AttributePtr aCoincPoint[2] = {AttributePtr(), AttributePtr()}; + FeaturePtr aCoincLine; for (int j = 0; j < 2; ++j) { AttributeRefAttrPtr aRefAttr = std::dynamic_pointer_cast( aFeature->attribute(SketchPlugin_Constraint::ATTRIBUTE(j))); if (!aRefAttr) continue; - bool isPoint = !aRefAttr->isObject(); - if (isPoint) + if (!aRefAttr->isObject()) aCoincPoint[j] = aRefAttr->attr(); else { FeaturePtr anAttr = ModelAPI_Feature::feature(aRefAttr->object()); - isPoint = anAttr && anAttr->getKind() == SketchPlugin_Point::ID(); - if (isPoint) + if (!anAttr) + continue; + if (anAttr->getKind() == SketchPlugin_Point::ID()) aCoincPoint[j] = anAttr->attribute(SketchPlugin_Point::COORD_ID()); + else if (anAttr->getKind() == SketchPlugin_Line::ID()) + aCoincLine = anAttr; } } if (aCoincPoint[0] && aCoincPoint[1]) { + bool isDoFDecreased = false; // point-point coincidence if (aCoincidentPoints.find(aCoincPoint[0]) == aCoincidentPoints.end() || - aCoincidentPoints.find(aCoincPoint[1]) == aCoincidentPoints.end()) + aCoincidentPoints.find(aCoincPoint[1]) == aCoincidentPoints.end()) { aDoF -= 2; - } else + isDoFDecreased = true; + } + // check the coincident point is used in "multi" constraints + std::list >::const_iterator + aPtIt = aPointsInMultiConstraints.begin(); + bool isFound[2] = {false, false}; + for (; aPtIt != aPointsInMultiConstraints.end(); ++aPtIt) { + if ((!isFound[0] && (isFound[0] = (aPtIt->find(aCoincPoint[0]) != aPtIt->end()))) + || (!isFound[1] && (isFound[1] = (aPtIt->find(aCoincPoint[1]) != aPtIt->end())))) + aCoincidentPoints.insert(aPtIt->begin(), aPtIt->end()); + if (isFound[0] && isFound[1]) + break; + } + // check both points are fixed => not need to decrease DoF + bool isFixed[2] = { aFixedPoints.find(aCoincPoint[0]) != aFixedPoints.end(), + aFixedPoints.find(aCoincPoint[1]) != aFixedPoints.end() }; + if (isFixed[0] && isFixed[1] && isDoFDecreased) + aDoF += 2; // revert decrease of DoF + else if (isFixed[0] && !isFixed[1]) + aFixedPoints.insert(aCoincPoint[1]); + else if (!isFixed[0] && isFixed[1]) + aFixedPoints.insert(aCoincPoint[0]); + } else { aDoF -= 1; + if (aCoincPoint[0] && aCoincLine) { + // if the point is already coincident to a line + // (by middle point constraint), do not decrease DoF + std::map >::iterator + aPtFound = aPointOnLine.find(aCoincPoint[0]); + if (aPtFound != aPointOnLine.end() && + aPtFound->second.find(aCoincLine) != aPtFound->second.end()) + aDoF += 1; // restore value decreased above + else + aPointOnLine[aCoincPoint[0]].insert(aCoincLine); + } + } for (int j = 0; j < 2; ++j) if (aCoincPoint[j]) aCoincidentPoints.insert(aCoincPoint[j]); } + else if (aFeature->getKind() == SketchPlugin_ConstraintMiddle::ID()) { + AttributePtr aPoint; + FeaturePtr aLine; + for (int j = 0; j < 2; ++j) { + AttributeRefAttrPtr aRefAttr = std::dynamic_pointer_cast( + aFeature->attribute(SketchPlugin_Constraint::ATTRIBUTE(j))); + if (!aRefAttr) + continue; + if (aRefAttr->isObject()) + aLine = ModelAPI_Feature::feature(aRefAttr->object()); + else + aPoint = aRefAttr->attr(); + } + if (aPoint && aLine) { + // if the point is already on the line, decrease 1 DoF, instead decrease 2 DoF + std::map >::iterator + aPtFound = aPointOnLine.find(aPoint); + if (aPtFound != aPointOnLine.end() && + aPtFound->second.find(aLine) != aPtFound->second.end()) + aDoF -= 1; + else { + aDoF -= 2; + aPointOnLine[aPoint].insert(aLine); + } + } + } else if (aFeature->getKind() == SketchPlugin_ConstraintRigid::ID()) { AttributeRefAttrPtr aRefAttr = std::dynamic_pointer_cast( aFeature->attribute(SketchPlugin_Constraint::ENTITY_A())); assert(aRefAttr); - if (!aRefAttr->isObject()) + std::set aPoints; + if (!aRefAttr->isObject()) { aDoF -= 2; // attribute is a point - else { + aPoints.insert(aRefAttr->attr()); + } else { FeaturePtr anAttr = ModelAPI_Feature::feature(aRefAttr->object()); - assert(anAttr); - aDoF -= aDoFDelta[anAttr->getKind()]; + if (anAttr) { + if (isExternal(anAttr)) + continue; // feature is already fixed since it is external + aDoF -= aDoFDelta[anAttr->getKind()]; + std::list aPtAttrs = + anAttr->data()->attributes(GeomDataAPI_Point2D::typeId()); + aPoints.insert(aPtAttrs.begin(), aPtAttrs.end()); + } + } + + // Check whether feature's points are already coincident with fixed points. + // In this case we need to revert decrease of DoF for these points. + // If the coordinates of fixed points are different, it will be processed by solver. + for (int k = 0; k < i; ++k) { + FeaturePtr aFeature = aSketch->subFeature(k); + if (aFeature->getKind() != SketchPlugin_ConstraintCoincidence::ID()) + continue; + AttributePtr aCoincPoint[2] = {AttributePtr(), AttributePtr()}; + for (int j = 0; j < 2; ++j) { + AttributeRefAttrPtr aRefAttr = std::dynamic_pointer_cast( + aFeature->attribute(SketchPlugin_Constraint::ATTRIBUTE(j))); + if (!aRefAttr) + continue; + if (!aRefAttr->isObject()) + aCoincPoint[j] = aRefAttr->attr(); + else { + FeaturePtr anAttr = ModelAPI_Feature::feature(aRefAttr->object()); + if (anAttr && anAttr->getKind() == SketchPlugin_Point::ID()) + aCoincPoint[j] = anAttr->attribute(SketchPlugin_Point::COORD_ID()); + } + } + if (aCoincPoint[0] && aCoincPoint[1]) { + if ((aFixedPoints.find(aCoincPoint[0]) != aFixedPoints.end() && + aPoints.find(aCoincPoint[1]) != aPoints.end()) || + (aFixedPoints.find(aCoincPoint[1]) != aFixedPoints.end() && + aPoints.find(aCoincPoint[0]) != aPoints.end())) + aDoF += 2; // point already fixed + } } + // store fixed points + aFixedPoints.insert(aPoints.begin(), aPoints.end()); } else if (aFeature->getKind() == SketchPlugin_ConstraintMirror::ID() || aFeature->getKind() == SketchPlugin_MultiRotation::ID() || @@ -560,9 +823,11 @@ void SketchSolver_Manager::degreesOfFreedom() anAttrName = SketchPlugin_Constraint::ENTITY_B(); else { if (aFeature->getKind() == SketchPlugin_MultiRotation::ID()) - aNbCopies = aFeature->integer(SketchPlugin_MultiRotation::NUMBER_OF_OBJECTS_ID())->value() - 1; + aNbCopies = + aFeature->integer(SketchPlugin_MultiRotation::NUMBER_OF_OBJECTS_ID())->value() - 1; else if (aFeature->getKind() == SketchPlugin_MultiTranslation::ID()) - aNbCopies = aFeature->integer(SketchPlugin_MultiTranslation::NUMBER_OF_OBJECTS_ID())->value() - 1; + aNbCopies = + aFeature->integer(SketchPlugin_MultiTranslation::NUMBER_OF_OBJECTS_ID())->value() - 1; anAttrName = SketchPlugin_Constraint::ENTITY_A(); } @@ -574,6 +839,8 @@ void SketchSolver_Manager::degreesOfFreedom() FeaturePtr aSub = ModelAPI_Feature::feature(*anObjIt); aDoF -= aDoFDelta[aSub->getKind()] * aNbCopies; } + // collect points and their copies for correct calculation of DoF for coincident points + collectPointsAndCopies(aFeature, aPointsInMultiConstraints); } }