-// Copyright (C) 2014-20xx CEA/DEN, EDF R&D
+// Copyright (C) 2014-2019 CEA/DEN, EDF R&D
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
#include <SketchSolver_ConstraintTangent.h>
#include <SketchSolver_Error.h>
-#include <SketchSolver_Manager.h>
+
+#include <PlaneGCSSolver_EdgeWrapper.h>
+#include <PlaneGCSSolver_PointWrapper.h>
+#include <PlaneGCSSolver_Tools.h>
+#include <PlaneGCSSolver_UpdateCoincidence.h>
#include <GeomAPI_Pnt2d.h>
+#include <SketchPlugin_Arc.h>
#include <SketchPlugin_Circle.h>
+#include <SketchPlugin_ConstraintCoincidence.h>
#include <cmath>
+#define GCS_EDGE_WRAPPER(x) std::dynamic_pointer_cast<PlaneGCSSolver_EdgeWrapper>(x)
-/// \brief Check whether the entities has only one shared point
-static bool hasSingleCoincidence(EntityWrapperPtr theEntity1, EntityWrapperPtr theEntity2)
-{
- BuilderPtr aBuilder = SketchSolver_Manager::instance()->builder();
-
- const std::list<EntityWrapperPtr>& aPoints1 = theEntity1->subEntities();
- const std::list<EntityWrapperPtr>& aPoints2 = theEntity2->subEntities();
+/// \brief Obtain tangent features from the constraint
+static void getTangentFeatures(const ConstraintPtr& theConstraint,
+ FeaturePtr& theFeature1,
+ FeaturePtr& theFeature2);
- std::list<EntityWrapperPtr>::const_iterator aStartIt1 = aPoints1.begin();
- if (theEntity1->type() == ENTITY_ARC) ++aStartIt1; // skip center of arc
- std::list<EntityWrapperPtr>::const_iterator aStartIt2 = aPoints2.begin();
- if (theEntity2->type() == ENTITY_ARC) ++aStartIt2; // skip center of arc
+/// \brief Obtain all coincident constraints between features
+static std::set<FeaturePtr> collectCoincidences(FeaturePtr theFeature1, FeaturePtr theFeature2);
- int aNbCoinc = 0;
- std::list<EntityWrapperPtr>::const_iterator anIt1, anIt2;
- for (anIt1 = aStartIt1; anIt1 != aPoints1.end(); ++anIt1) {
- if ((*anIt1)->type() != ENTITY_POINT)
- continue;
- std::shared_ptr<GeomAPI_Pnt2d> aPt1 = aBuilder->point(*anIt1);
- for (anIt2 = aStartIt2; anIt2 != aPoints2.end(); ++anIt2) {
- if ((*anIt2)->type() != ENTITY_POINT)
- continue;
- std::shared_ptr<GeomAPI_Pnt2d> aPt2 = aBuilder->point(*anIt2);
- if (aPt1->distance(aPt2) < tolerance)
- ++aNbCoinc;
- }
- }
- return aNbCoinc == 1;
-}
+/// \brief Check whether the entities has only one shared point or less.
+/// Return list of coincident points.
+static std::set<AttributePtr> coincidentBoundaryPoints(FeaturePtr theFeature1,
+ FeaturePtr theFeature2);
/// \brief Check if two connected arcs have centers
/// in same direction relatively to connection point
-static bool isInternalTangency(EntityWrapperPtr theEntity1, EntityWrapperPtr theEntity2)
-{
- BuilderPtr aBuilder = SketchSolver_Manager::instance()->builder();
- return aBuilder->isArcArcTangencyInternal(theEntity1, theEntity2);
-}
+static bool isArcArcTangencyInternal(EntityWrapperPtr theArc1,
+ EntityWrapperPtr theArc2);
+static ConstraintWrapperPtr
+ createArcLineTangency(EntityWrapperPtr theEntity1,
+ EntityWrapperPtr theEntity2,
+ EntityWrapperPtr theShapedPoint = EntityWrapperPtr(),
+ double* theAngle = 0);
-void SketchSolver_ConstraintTangent::getAttributes(
- double& theValue,
- std::vector<EntityWrapperPtr>& theAttributes)
+static ConstraintWrapperPtr
+ createArcArcTangency(EntityWrapperPtr theEntity1,
+ EntityWrapperPtr theEntity2,
+ bool theInternalTangency,
+ EntityWrapperPtr theSharedPoint = EntityWrapperPtr(),
+ double* theAngle = 0);
+
+
+void SketchSolver_ConstraintTangent::process()
{
- SketchSolver_Constraint::getAttributes(theValue, theAttributes);
- if (!myErrorMsg.empty() || !theAttributes[2] || !theAttributes[3]) {
- theAttributes.clear();
+ cleanErrorMsg();
+ if (!myBaseConstraint || !myStorage) {
+ // Not enough parameters are assigned
return;
}
+ EntityWrapperPtr aValue;
+ std::vector<EntityWrapperPtr> anAttributes;
+ SketchSolver_Constraint::getAttributes(aValue, anAttributes);
+ if (!myErrorMsg.empty())
+ return;
+
+ rebuild();
+ if (!myErrorMsg.empty())
+ return;
+
+ myStorage->subscribeUpdates(this, PlaneGCSSolver_UpdateCoincidence::GROUP());
+}
+
+void SketchSolver_ConstraintTangent::rebuild()
+{
+ if (mySolverConstraint)
+ myStorage->removeConstraint(myBaseConstraint);
+
+ mySolverConstraint = ConstraintWrapperPtr();
+ mySharedPoint = AttributePtr();
+
// Check the quantity of entities of each type and their order (arcs first)
int aNbLines = 0;
- int aNbArcs = 0;
int aNbCircles = 0;
- bool isSwap = false; // whether need to swap arguments (arc goes before line)
- std::vector<EntityWrapperPtr>::iterator anEntIt = theAttributes.begin() + 2;
- for (; anEntIt != theAttributes.end(); ++anEntIt) {
+ std::list<EntityWrapperPtr>::iterator anEntIt = myAttributes.begin();
+ for (; anEntIt != myAttributes.end(); ++anEntIt) {
+ if (!(*anEntIt).get())
+ continue;
if ((*anEntIt)->type() == ENTITY_LINE)
++aNbLines;
- else if ((*anEntIt)->type() == ENTITY_ARC) {
- ++aNbArcs;
- isSwap = aNbLines > 0;
- }
- else if ((*anEntIt)->type() == ENTITY_CIRCLE) {
+ else if ((*anEntIt)->type() == ENTITY_ARC || (*anEntIt)->type() == ENTITY_CIRCLE)
++aNbCircles;
- isSwap = aNbLines > 0;
- }
}
- if (aNbArcs < 1 && aNbCircles < 1) {
+ if (aNbCircles < 1) {
myErrorMsg = SketchSolver_Error::INCORRECT_TANGENCY_ATTRIBUTE();
return;
}
- if (aNbLines == 1) {
- if (aNbArcs == 1)
- myType = CONSTRAINT_TANGENT_ARC_LINE;
- else if (aNbCircles == 1)
- myType = CONSTRAINT_TANGENT_CIRCLE_LINE;
+ if (aNbLines == 1 && aNbCircles == 1) {
+ myType = CONSTRAINT_TANGENT_CIRCLE_LINE;
}
- else if (aNbArcs == 2) {
- myType = CONSTRAINT_TANGENT_ARC_ARC;
- isArcArcInternal = isInternalTangency(theAttributes[2], theAttributes[3]);
+ else if (aNbCircles == 2) {
+ myType = CONSTRAINT_TANGENT_CIRCLE_CIRCLE;
+ isArcArcInternal = isArcArcTangencyInternal(myAttributes.front(), myAttributes.back());
}
else {
myErrorMsg = SketchSolver_Error::INCORRECT_ATTRIBUTE();
return;
}
- if (myType == CONSTRAINT_TANGENT_ARC_LINE &&
- !hasSingleCoincidence(theAttributes[2], theAttributes[3]))
+ FeaturePtr aFeature1, aFeature2;
+ getTangentFeatures(myBaseConstraint, aFeature1, aFeature2);
+
+ // check number of coincident points
+ std::set<AttributePtr> aCoincidentPoints = coincidentBoundaryPoints(aFeature1, aFeature2);
+ if (myType == CONSTRAINT_TANGENT_CIRCLE_LINE && aCoincidentPoints.size() > 2) {
myErrorMsg = SketchSolver_Error::TANGENCY_FAILED();
+ return;
+ }
+
+ EntityWrapperPtr aSharedPointEntity;
+ if (!aCoincidentPoints.empty()) {
+ mySharedPoint = *aCoincidentPoints.begin();
+ aSharedPointEntity = myStorage->entity(mySharedPoint);
+ }
- if (isSwap) {
- EntityWrapperPtr aTemp = theAttributes[2];
- theAttributes[2] = theAttributes[3];
- theAttributes[3] = aTemp;
+ if (myType == CONSTRAINT_TANGENT_CIRCLE_LINE) {
+ mySolverConstraint = createArcLineTangency(myAttributes.front(), myAttributes.back(),
+ aSharedPointEntity, &myCurveCurveAngle);
+ } else {
+ mySolverConstraint = createArcArcTangency(myAttributes.front(), myAttributes.back(),
+ isArcArcInternal, aSharedPointEntity, &myCurveCurveAngle);
}
+
+ myStorage->addConstraint(myBaseConstraint, mySolverConstraint);
}
void SketchSolver_ConstraintTangent::adjustConstraint()
{
- if (myType == CONSTRAINT_TANGENT_CIRCLE_LINE) {
- ConstraintWrapperPtr aConstraint = myStorage->constraint(myBaseConstraint).front();
- AttributePtr aCircleCenter = aConstraint->entities().front()->baseAttribute();
- if (!aCircleCenter)
- return;
- FeaturePtr aCircle = ModelAPI_Feature::feature(aCircleCenter->owner());
- AttributeDoublePtr aRadius = std::dynamic_pointer_cast<ModelAPI_AttributeDouble>(
- aCircle->attribute(SketchPlugin_Circle::RADIUS_ID()));
-
- if (fabs(aRadius->value()) == fabs(aConstraint->value()))
- return;
-
- aConstraint->setValue(aRadius->value());
-
- // Adjust the sign of constraint value
- BuilderPtr aBuilder = SketchSolver_Manager::instance()->builder();
- aBuilder->adjustConstraint(aConstraint);
- myStorage->addConstraint(myBaseConstraint, aConstraint);
- }
- else if (myType == CONSTRAINT_TANGENT_ARC_ARC) {
- ConstraintWrapperPtr aConstraint = myStorage->constraint(myBaseConstraint).front();
- if (isArcArcInternal != isInternalTangency(
- aConstraint->entities().front(), aConstraint->entities().back())) {
- // fully rebuld constraint, because it is unable to access attributes of PlaneGCS constraint
- remove();
- process();
+ if (myType == CONSTRAINT_TANGENT_CIRCLE_CIRCLE) {
+ EntityWrapperPtr anEntity1 =
+ myStorage->entity(myBaseConstraint->attribute(SketchPlugin_Constraint::ENTITY_A()));
+ EntityWrapperPtr anEntity2 =
+ myStorage->entity(myBaseConstraint->attribute(SketchPlugin_Constraint::ENTITY_B()));
+
+ if (isArcArcInternal != isArcArcTangencyInternal(anEntity1, anEntity2))
+ rebuild();
+ }
+}
+
+void SketchSolver_ConstraintTangent::notify(const FeaturePtr& theFeature,
+ PlaneGCSSolver_Update* theUpdater)
+{
+ if (theFeature->getKind() != SketchPlugin_ConstraintCoincidence::ID())
+ return;
+
+ // base constraint may become invalid (for example, during undo)
+ if (!myBaseConstraint->data() || !myBaseConstraint->data()->isValid())
+ return;
+
+ FeaturePtr aTgFeat1, aTgFeat2;
+ getTangentFeatures(myBaseConstraint, aTgFeat1, aTgFeat2);
+
+ bool isRebuild = false;
+ if (theFeature->data() && theFeature->data()->isValid()) {
+ // the constraint has been created
+ if (!mySharedPoint) {
+ // features has no shared point, check whether coincidence constraint binds these features)
+ int aNbCoincidentFeatures = 0;
+ for (int i = 0; i < CONSTRAINT_ATTR_SIZE; ++i) {
+ AttributeRefAttrPtr aRefAttr = theFeature->refattr(SketchPlugin_Constraint::ATTRIBUTE(i));
+ if (!aRefAttr)
+ continue;
+
+ ObjectPtr anObj;
+ if (aRefAttr->isObject())
+ anObj = aRefAttr->object();
+ else
+ anObj = aRefAttr->attr()->owner();
+
+ FeaturePtr aFeature = ModelAPI_Feature::feature(anObj);
+ if (aFeature == aTgFeat1 || aFeature == aTgFeat2)
+ ++aNbCoincidentFeatures;
+ }
+
+ if (aNbCoincidentFeatures == 2)
+ isRebuild = true;
+ }
+ }
+
+ if (mySharedPoint && !isRebuild) {
+ // The features are tangent in the shared point, but the coincidence has been removed/updated.
+ // Check if the coincidence is the same.
+ std::set<AttributePtr> aCoincidentPoints = coincidentBoundaryPoints(aTgFeat1, aTgFeat2);
+ isRebuild = true;
+ std::set<AttributePtr>::iterator anIt = aCoincidentPoints.begin();
+ for (; anIt != aCoincidentPoints.end() && isRebuild; ++anIt)
+ if (*anIt == mySharedPoint)
+ isRebuild = false; // the coincidence is still exists => nothing to change
+ }
+
+ if (isRebuild)
+ rebuild();
+}
+
+
+
+
+// ================== Auxiliary functions =================================
+void getTangentFeatures(const ConstraintPtr& theConstraint,
+ FeaturePtr& theFeature1,
+ FeaturePtr& theFeature2)
+{
+ AttributeRefAttrPtr aRefAttr = theConstraint->refattr(SketchPlugin_Constraint::ENTITY_A());
+ theFeature1 = ModelAPI_Feature::feature(aRefAttr->object());
+ aRefAttr = theConstraint->refattr(SketchPlugin_Constraint::ENTITY_B());
+ theFeature2 = ModelAPI_Feature::feature(aRefAttr->object());
+}
+
+std::set<FeaturePtr> collectCoincidences(FeaturePtr theFeature1, FeaturePtr theFeature2)
+{
+ const std::set<AttributePtr>& aRefs1 = theFeature1->data()->refsToMe();
+ const std::set<AttributePtr>& aRefs2 = theFeature2->data()->refsToMe();
+
+ std::set<FeaturePtr> aCoincidences;
+ std::set<AttributePtr>::const_iterator anIt;
+
+ // collect coincidences referred to the first feature
+ for (anIt = aRefs1.begin(); anIt != aRefs1.end(); ++anIt) {
+ FeaturePtr aRef = ModelAPI_Feature::feature((*anIt)->owner());
+ if (aRef && aRef->getKind() == SketchPlugin_ConstraintCoincidence::ID())
+ aCoincidences.insert(aRef);
+ }
+
+ // leave only coincidences referred to the second feature
+ std::set<FeaturePtr> aCoincidencesBetweenFeatures;
+ for (anIt = aRefs2.begin(); anIt != aRefs2.end(); ++anIt) {
+ FeaturePtr aRef = ModelAPI_Feature::feature((*anIt)->owner());
+ if (aCoincidences.find(aRef) != aCoincidences.end())
+ aCoincidencesBetweenFeatures.insert(aRef);
+ }
+
+ return aCoincidencesBetweenFeatures;
+}
+
+std::set<AttributePtr> coincidentBoundaryPoints(FeaturePtr theFeature1, FeaturePtr theFeature2)
+{
+ std::set<FeaturePtr> aCoincidences = collectCoincidences(theFeature1, theFeature2);
+ // collect points only
+ std::set<AttributePtr> aCoincidentPoints;
+ std::set<FeaturePtr>::const_iterator aCIt = aCoincidences.begin();
+ for (; aCIt != aCoincidences.end(); ++ aCIt) {
+ AttributeRefAttrPtr aRefAttrA = (*aCIt)->refattr(SketchPlugin_Constraint::ENTITY_A());
+ AttributeRefAttrPtr aRefAttrB = (*aCIt)->refattr(SketchPlugin_Constraint::ENTITY_B());
+ if (!aRefAttrA || aRefAttrA->isObject() ||
+ !aRefAttrB || aRefAttrB->isObject())
+ continue;
+
+ AttributePtr anAttrA = aRefAttrA->attr();
+ AttributePtr anAttrB = aRefAttrB->attr();
+ if (anAttrA->id() != SketchPlugin_Arc::CENTER_ID() &&
+ anAttrA->id() != SketchPlugin_Circle::CENTER_ID() &&
+ anAttrB->id() != SketchPlugin_Arc::CENTER_ID() &&
+ anAttrB->id() != SketchPlugin_Circle::CENTER_ID()) {
+ aCoincidentPoints.insert(anAttrA);
+ aCoincidentPoints.insert(anAttrB);
}
}
+ return aCoincidentPoints;
+}
+
+bool isArcArcTangencyInternal(EntityWrapperPtr theArc1, EntityWrapperPtr theArc2)
+{
+ std::shared_ptr<GCS::Circle> aCirc1 = std::dynamic_pointer_cast<GCS::Circle>(
+ GCS_EDGE_WRAPPER(theArc1)->entity());
+ std::shared_ptr<GCS::Circle> aCirc2 = std::dynamic_pointer_cast<GCS::Circle>(
+ GCS_EDGE_WRAPPER(theArc2)->entity());
+
+ if (!aCirc1 || !aCirc2)
+ return false;
+
+ double aDX = *(aCirc1->center.x) - *(aCirc2->center.x);
+ double aDY = *(aCirc1->center.y) - *(aCirc2->center.y);
+ double aDist = sqrt(aDX * aDX + aDY * aDY);
+
+ return (aDist < *(aCirc1->rad) || aDist < *(aCirc2->rad));
+}
+
+// sets angle to 0 or 180 degrees
+static void adjustAngleBetweenCurves(const GCSCurvePtr& theCurve1,
+ const GCSCurvePtr& theCurve2,
+ const GCSPointPtr& thePoint,
+ double* theAngle)
+{
+ double anAngle = GCS::System().calculateAngleViaPoint(*theCurve1, *theCurve2, *thePoint);
+ // bring angle to [-pi..pi]
+ if (anAngle > PI) anAngle -= 2.0 * PI;
+ if (anAngle < -PI) anAngle += 2.0 * PI;
+ // set angle value according to the current angle between curves
+ if (fabs(anAngle) <= PI / 2.)
+ *theAngle = 0.0;
+ else
+ *theAngle = PI;
+}
+
+
+ConstraintWrapperPtr createArcLineTangency(EntityWrapperPtr theEntity1,
+ EntityWrapperPtr theEntity2,
+ EntityWrapperPtr theSharedPoint,
+ double* theAngle)
+{
+ EdgeWrapperPtr anEntLine, anEntCirc;
+ if (theEntity1->type() == ENTITY_LINE) {
+ anEntLine = GCS_EDGE_WRAPPER(theEntity1);
+ anEntCirc = GCS_EDGE_WRAPPER(theEntity2);
+ } else {
+ anEntLine = GCS_EDGE_WRAPPER(theEntity2);
+ anEntCirc = GCS_EDGE_WRAPPER(theEntity1);
+ }
+
+ std::shared_ptr<GCS::Circle> aCirc =
+ std::dynamic_pointer_cast<GCS::Circle>(anEntCirc->entity());
+ std::shared_ptr<GCS::Arc> anArc = std::dynamic_pointer_cast<GCS::Arc>(aCirc);
+
+ std::shared_ptr<GCS::Line> aLine =
+ std::dynamic_pointer_cast<GCS::Line>(anEntLine->entity());
+
+ GCSConstraintPtr aNewConstr;
+ if (theSharedPoint && anArc) { // do not process shared point between circle and line
+ GCSPointPtr aPoint =
+ std::dynamic_pointer_cast<PlaneGCSSolver_PointWrapper>(theSharedPoint)->point();
+
+ adjustAngleBetweenCurves(anArc, aLine, aPoint, theAngle);
+ aNewConstr =
+ GCSConstraintPtr(new GCS::ConstraintAngleViaPoint(*anArc, *aLine, *aPoint, theAngle));
+ } else {
+ aNewConstr =
+ GCSConstraintPtr(new GCS::ConstraintP2LDistance(aCirc->center, *aLine, aCirc->rad));
+ }
+
+ return ConstraintWrapperPtr(
+ new PlaneGCSSolver_ConstraintWrapper(aNewConstr, CONSTRAINT_TANGENT_CIRCLE_LINE));
+}
+
+ConstraintWrapperPtr createArcArcTangency(EntityWrapperPtr theEntity1,
+ EntityWrapperPtr theEntity2,
+ bool theInternalTangency,
+ EntityWrapperPtr theSharedPoint,
+ double* theAngle)
+{
+ std::shared_ptr<GCS::Circle> aCirc1 =
+ std::dynamic_pointer_cast<GCS::Circle>(GCS_EDGE_WRAPPER(theEntity1)->entity());
+ std::shared_ptr<GCS::Circle> aCirc2 =
+ std::dynamic_pointer_cast<GCS::Circle>(GCS_EDGE_WRAPPER(theEntity2)->entity());
+
+ GCSConstraintPtr aNewConstr;
+ if (theSharedPoint) {
+ GCSPointPtr aPoint =
+ std::dynamic_pointer_cast<PlaneGCSSolver_PointWrapper>(theSharedPoint)->point();
+
+ adjustAngleBetweenCurves(aCirc1, aCirc2, aPoint, theAngle);
+ aNewConstr =
+ GCSConstraintPtr(new GCS::ConstraintAngleViaPoint(*aCirc1, *aCirc2, *aPoint, theAngle));
+ } else {
+ aNewConstr = GCSConstraintPtr(new GCS::ConstraintTangentCircumf(aCirc1->center, aCirc2->center,
+ aCirc1->rad, aCirc2->rad, theInternalTangency));
+ }
+
+ return ConstraintWrapperPtr(
+ new PlaneGCSSolver_ConstraintWrapper(aNewConstr, CONSTRAINT_TANGENT_CIRCLE_CIRCLE));
}