1 // Copyright (C) 2014-2019 CEA/DEN, EDF R&D
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_ConstraintTangent.h>
21 #include <SketchSolver_Error.h>
23 #include <PlaneGCSSolver_EdgeWrapper.h>
24 #include <PlaneGCSSolver_PointWrapper.h>
25 #include <PlaneGCSSolver_Storage.h>
26 #include <PlaneGCSSolver_Tools.h>
27 #include <PlaneGCSSolver_UpdateCoincidence.h>
29 #include <GeomAPI_Circ2d.h>
30 #include <GeomAPI_Lin2d.h>
31 #include <GeomAPI_Pnt2d.h>
32 #include <GeomAPI_Ellipse2d.h>
34 #include <SketchPlugin_Arc.h>
35 #include <SketchPlugin_Circle.h>
36 #include <SketchPlugin_ConstraintCoincidence.h>
37 #include <SketchPlugin_ConstraintMiddle.h>
41 #define GCS_EDGE_WRAPPER(x) std::dynamic_pointer_cast<PlaneGCSSolver_EdgeWrapper>(x)
43 /// \brief Obtain tangent features from the constraint
44 static void getTangentFeatures(const ConstraintPtr& theConstraint,
45 FeaturePtr& theFeature1,
46 FeaturePtr& theFeature2);
48 /// \brief Obtain all coincident constraints between features
49 static std::set<FeaturePtr> collectCoincidences(FeaturePtr theFeature1, FeaturePtr theFeature2);
51 /// \brief Check whether the entities has only one shared point or less.
52 /// Return list of coincident points.
53 static std::set<AttributePtr> coincidentBoundaryPoints(FeaturePtr theFeature1,
54 FeaturePtr theFeature2);
56 /// \brief Collect points coincident with each of two features
57 static std::set<AttributePtr> coincidentPoints(FeaturePtr theFeature1, FeaturePtr theFeature2);
59 /// \brief Check if two connected arcs have centers
60 /// in same direction relatively to connection point
61 static bool isArcArcTangencyInternal(EntityWrapperPtr theArc1,
62 EntityWrapperPtr theArc2);
64 static ConstraintWrapperPtr
65 createArcLineTangency(EntityWrapperPtr theEntity1,
66 EntityWrapperPtr theEntity2,
67 EntityWrapperPtr theShapedPoint = EntityWrapperPtr(),
68 double* theAngle = 0);
70 static ConstraintWrapperPtr
71 createCurveCurveTangency(EntityWrapperPtr theEntity1,
72 EntityWrapperPtr theEntity2,
73 bool theInternalTangency,
74 EntityWrapperPtr theSharedPoint = EntityWrapperPtr(),
75 double* theAngle = 0);
77 static void calculateTangencyPoint(EntityWrapperPtr theCurve1, EntityWrapperPtr theCurve2,
78 GCSPointPtr& theTangencyPoint);
81 void SketchSolver_ConstraintTangent::process()
84 if (!myBaseConstraint || !myStorage) {
85 // Not enough parameters are assigned
89 EntityWrapperPtr aValue;
90 std::vector<EntityWrapperPtr> anAttributes;
91 SketchSolver_Constraint::getAttributes(aValue, anAttributes);
92 if (!myErrorMsg.empty())
96 if (!myErrorMsg.empty())
99 myStorage->subscribeUpdates(this, PlaneGCSSolver_UpdateCoincidence::GROUP());
102 void SketchSolver_ConstraintTangent::rebuild()
104 if (mySolverConstraint)
105 myStorage->removeConstraint(myBaseConstraint);
107 std::shared_ptr<PlaneGCSSolver_Storage> aStorage =
108 std::dynamic_pointer_cast<PlaneGCSSolver_Storage>(myStorage);
110 mySolverConstraint = ConstraintWrapperPtr();
111 mySharedPoint = AttributePtr();
113 GCS::SET_pD aParams = PlaneGCSSolver_Tools::parameters(myAuxPoint);
114 aStorage->removeParameters(aParams);
115 myAuxPoint = EntityWrapperPtr();
118 // Check the quantity of entities of each type and their order (arcs first)
122 std::list<EntityWrapperPtr>::iterator anEntIt = myAttributes.begin();
123 for (; anEntIt != myAttributes.end(); ++anEntIt) {
124 if (!(*anEntIt).get())
126 if ((*anEntIt)->type() == ENTITY_LINE)
128 else if ((*anEntIt)->type() == ENTITY_ARC || (*anEntIt)->type() == ENTITY_CIRCLE)
130 else if ((*anEntIt)->type() == ENTITY_ELLIPSE || (*anEntIt)->type() == ENTITY_ELLIPTIC_ARC)
134 if (aNbCircles + aNbEllipses < 1) {
135 myErrorMsg = SketchSolver_Error::INCORRECT_TANGENCY_ATTRIBUTE();
138 if (aNbLines == 1 && aNbCircles == 1) {
139 myType = CONSTRAINT_TANGENT_CIRCLE_LINE;
141 else if (aNbLines + aNbCircles + aNbEllipses == 2) {
142 myType = CONSTRAINT_TANGENT_CURVE_CURVE;
143 isArcArcInternal = isArcArcTangencyInternal(myAttributes.front(), myAttributes.back());
146 myErrorMsg = SketchSolver_Error::INCORRECT_ATTRIBUTE();
150 FeaturePtr aFeature1, aFeature2;
151 getTangentFeatures(myBaseConstraint, aFeature1, aFeature2);
153 // check number of coincident points
154 std::set<AttributePtr> aCoincidentPoints = coincidentBoundaryPoints(aFeature1, aFeature2);
155 if (myType == CONSTRAINT_TANGENT_CIRCLE_LINE && aCoincidentPoints.size() > 2) {
156 myErrorMsg = SketchSolver_Error::TANGENCY_FAILED();
160 // Try to find non-boundary points coincident with both features.
161 // It is necesasry to create tangency with ellipse
162 if (aCoincidentPoints.empty() && aNbEllipses > 0)
163 aCoincidentPoints = coincidentPoints(aFeature1, aFeature2);
165 EntityWrapperPtr aSharedPointEntity;
166 std::list<GCSConstraintPtr> anAuxConstraints;
167 if (!aCoincidentPoints.empty()) {
168 mySharedPoint = *aCoincidentPoints.begin();
169 aSharedPointEntity = myStorage->entity(mySharedPoint);
171 else if (aNbEllipses > 0) {
172 // create auxiliary point
173 GCSPointPtr aPoint(new GCS::Point);
174 aPoint->x = aStorage->createParameter();
175 aPoint->y = aStorage->createParameter();
176 calculateTangencyPoint(myAttributes.front(), myAttributes.back(), aPoint);
178 myAuxPoint.reset(new PlaneGCSSolver_PointWrapper(aPoint));
179 aSharedPointEntity = myAuxPoint;
181 // create auxiliary coincident constraints for tangency with ellipse
182 EntityWrapperPtr aDummy;
183 ConstraintWrapperPtr aCoincidence = PlaneGCSSolver_Tools::createConstraint(ConstraintPtr(),
184 CONSTRAINT_PT_ON_CURVE, aDummy, aSharedPointEntity, aDummy, myAttributes.front(), aDummy);
185 anAuxConstraints = aCoincidence->constraints();
186 aCoincidence = PlaneGCSSolver_Tools::createConstraint(ConstraintPtr(),
187 CONSTRAINT_PT_ON_CURVE, aDummy, aSharedPointEntity, aDummy, myAttributes.back(), aDummy);
188 anAuxConstraints.insert(anAuxConstraints.end(),
189 aCoincidence->constraints().begin(), aCoincidence->constraints().end());
192 if (myType == CONSTRAINT_TANGENT_CIRCLE_LINE) {
193 mySolverConstraint = createArcLineTangency(myAttributes.front(), myAttributes.back(),
194 aSharedPointEntity, &myCurveCurveAngle);
196 mySolverConstraint = createCurveCurveTangency(myAttributes.front(), myAttributes.back(),
197 isArcArcInternal, aSharedPointEntity, &myCurveCurveAngle);
200 if (!anAuxConstraints.empty()) {
201 anAuxConstraints.insert(anAuxConstraints.end(), mySolverConstraint->constraints().begin(),
202 mySolverConstraint->constraints().end());
203 mySolverConstraint->setConstraints(anAuxConstraints);
206 myStorage->addConstraint(myBaseConstraint, mySolverConstraint);
209 void SketchSolver_ConstraintTangent::adjustConstraint()
211 if (myType == CONSTRAINT_TANGENT_CURVE_CURVE) {
212 EntityWrapperPtr anEntity1 =
213 myStorage->entity(myBaseConstraint->attribute(SketchPlugin_Constraint::ENTITY_A()));
214 EntityWrapperPtr anEntity2 =
215 myStorage->entity(myBaseConstraint->attribute(SketchPlugin_Constraint::ENTITY_B()));
217 if (isArcArcInternal != isArcArcTangencyInternal(anEntity1, anEntity2))
222 void SketchSolver_ConstraintTangent::notify(const FeaturePtr& theFeature,
223 PlaneGCSSolver_Update* theUpdater)
225 if (theFeature->getKind() != SketchPlugin_ConstraintCoincidence::ID())
228 // base constraint may become invalid (for example, during undo)
229 if (!myBaseConstraint->data() || !myBaseConstraint->data()->isValid())
232 FeaturePtr aTgFeat1, aTgFeat2;
233 getTangentFeatures(myBaseConstraint, aTgFeat1, aTgFeat2);
235 bool isRebuild = false;
236 if (theFeature->data() && theFeature->data()->isValid()) {
237 // the constraint has been created
238 if (!mySharedPoint) {
239 // features has no shared point, check whether coincidence constraint binds these features)
240 int aNbCoincidentFeatures = 0;
241 for (int i = 0; i < CONSTRAINT_ATTR_SIZE; ++i) {
242 AttributeRefAttrPtr aRefAttr = theFeature->refattr(SketchPlugin_Constraint::ATTRIBUTE(i));
247 if (aRefAttr->isObject())
248 anObj = aRefAttr->object();
250 anObj = aRefAttr->attr()->owner();
252 FeaturePtr aFeature = ModelAPI_Feature::feature(anObj);
253 if (aFeature == aTgFeat1 || aFeature == aTgFeat2)
254 ++aNbCoincidentFeatures;
257 if (aNbCoincidentFeatures == 2)
264 // The features are tangent in the shared point, but the coincidence has been removed/updated.
265 // Check if the coincidence is the same.
266 std::set<AttributePtr> aCoincidentPoints = coincidentBoundaryPoints(aTgFeat1, aTgFeat2);
268 std::set<AttributePtr>::iterator anIt = aCoincidentPoints.begin();
269 for (; anIt != aCoincidentPoints.end() && isRebuild; ++anIt)
270 if (*anIt == mySharedPoint)
271 isRebuild = false; // the coincidence is still exists => nothing to change
274 // check both features have a coincident point
275 std::set<AttributePtr> aCoincidentPoints = coincidentPoints(aTgFeat1, aTgFeat2);
276 isRebuild = (bool)(myAuxPoint.get()) == (!aCoincidentPoints.empty());
287 // ================== Auxiliary functions =================================
288 void getTangentFeatures(const ConstraintPtr& theConstraint,
289 FeaturePtr& theFeature1,
290 FeaturePtr& theFeature2)
292 AttributeRefAttrPtr aRefAttr = theConstraint->refattr(SketchPlugin_Constraint::ENTITY_A());
293 theFeature1 = ModelAPI_Feature::feature(aRefAttr->object());
294 aRefAttr = theConstraint->refattr(SketchPlugin_Constraint::ENTITY_B());
295 theFeature2 = ModelAPI_Feature::feature(aRefAttr->object());
298 std::set<FeaturePtr> collectCoincidences(FeaturePtr theFeature1, FeaturePtr theFeature2)
300 const std::set<AttributePtr>& aRefs1 = theFeature1->data()->refsToMe();
301 const std::set<AttributePtr>& aRefs2 = theFeature2->data()->refsToMe();
303 std::set<FeaturePtr> aCoincidences;
304 std::set<AttributePtr>::const_iterator anIt;
306 // collect coincidences referred to the first feature
307 for (anIt = aRefs1.begin(); anIt != aRefs1.end(); ++anIt) {
308 FeaturePtr aRef = ModelAPI_Feature::feature((*anIt)->owner());
309 if (aRef && aRef->getKind() == SketchPlugin_ConstraintCoincidence::ID())
310 aCoincidences.insert(aRef);
313 // leave only coincidences referred to the second feature
314 std::set<FeaturePtr> aCoincidencesBetweenFeatures;
315 for (anIt = aRefs2.begin(); anIt != aRefs2.end(); ++anIt) {
316 FeaturePtr aRef = ModelAPI_Feature::feature((*anIt)->owner());
317 if (aCoincidences.find(aRef) != aCoincidences.end())
318 aCoincidencesBetweenFeatures.insert(aRef);
321 return aCoincidencesBetweenFeatures;
324 std::set<AttributePtr> coincidentBoundaryPoints(FeaturePtr theFeature1, FeaturePtr theFeature2)
326 std::set<FeaturePtr> aCoincidences = collectCoincidences(theFeature1, theFeature2);
327 // collect points only
328 std::set<AttributePtr> aCoincidentPoints;
329 std::set<FeaturePtr>::const_iterator aCIt = aCoincidences.begin();
330 for (; aCIt != aCoincidences.end(); ++ aCIt) {
331 AttributeRefAttrPtr aRefAttrA = (*aCIt)->refattr(SketchPlugin_Constraint::ENTITY_A());
332 AttributeRefAttrPtr aRefAttrB = (*aCIt)->refattr(SketchPlugin_Constraint::ENTITY_B());
333 if (!aRefAttrA || aRefAttrA->isObject() ||
334 !aRefAttrB || aRefAttrB->isObject())
337 AttributePtr anAttrA = aRefAttrA->attr();
338 AttributePtr anAttrB = aRefAttrB->attr();
339 if (anAttrA->id() != SketchPlugin_Arc::CENTER_ID() &&
340 anAttrA->id() != SketchPlugin_Circle::CENTER_ID() &&
341 anAttrB->id() != SketchPlugin_Arc::CENTER_ID() &&
342 anAttrB->id() != SketchPlugin_Circle::CENTER_ID()) {
343 aCoincidentPoints.insert(anAttrA);
344 aCoincidentPoints.insert(anAttrB);
347 return aCoincidentPoints;
350 static std::set<AttributePtr> refsToFeatureAndResults(FeaturePtr theFeature)
352 std::set<AttributePtr> aRefs = theFeature->data()->refsToMe();
353 const std::list<ResultPtr>& aResults = theFeature->results();
354 for (std::list<ResultPtr>::const_iterator anIt = aResults.begin();
355 anIt != aResults.end(); ++anIt) {
356 const std::set<AttributePtr>& aResRefs = (*anIt)->data()->refsToMe();
357 aRefs.insert(aResRefs.begin(), aResRefs.end());
362 // collect all points coincident with the feature
363 static std::set<AttributePtr> pointsOnFeature(FeaturePtr theFeature)
365 std::set<AttributePtr> aPoints;
367 std::set<AttributePtr> aRefs = refsToFeatureAndResults(theFeature);
368 for (std::set<AttributePtr>::const_iterator anIt = aRefs.begin(); anIt != aRefs.end(); ++anIt) {
369 FeaturePtr aRef = ModelAPI_Feature::feature((*anIt)->owner());
370 if (aRef && (aRef->getKind() == SketchPlugin_ConstraintCoincidence::ID() ||
371 aRef->getKind() == SketchPlugin_ConstraintMiddle::ID())) {
372 for (int i = 0; i < CONSTRAINT_ATTR_SIZE; ++i) {
373 AttributeRefAttrPtr aRefAttr = aRef->refattr(SketchPlugin_Constraint::ATTRIBUTE(i));
375 AttributePtr anAttr = aRefAttr->attr();
376 if (anAttr && anAttr->id() != SketchPlugin_Arc::CENTER_ID() &&
377 anAttr->id() != SketchPlugin_Circle::CENTER_ID())
378 aPoints.insert(anAttr);
386 std::set<AttributePtr> coincidentPoints(FeaturePtr theFeature1, FeaturePtr theFeature2)
388 std::set<AttributePtr> aPointsOnF1 = pointsOnFeature(theFeature1);
389 std::set<AttributePtr> aPointsOnF2 = pointsOnFeature(theFeature2);
391 std::set<AttributePtr> aCommonPoints;
392 for (std::set<AttributePtr>::iterator anIt = aPointsOnF1.begin();
393 anIt != aPointsOnF1.end(); ++anIt)
394 if (aPointsOnF2.find(*anIt) != aPointsOnF2.end())
395 aCommonPoints.insert(*anIt);
396 return aCommonPoints;
399 bool isArcArcTangencyInternal(EntityWrapperPtr theArc1, EntityWrapperPtr theArc2)
401 std::shared_ptr<GCS::Circle> aCirc1 = std::dynamic_pointer_cast<GCS::Circle>(
402 GCS_EDGE_WRAPPER(theArc1)->entity());
403 std::shared_ptr<GCS::Circle> aCirc2 = std::dynamic_pointer_cast<GCS::Circle>(
404 GCS_EDGE_WRAPPER(theArc2)->entity());
406 if (!aCirc1 || !aCirc2)
409 double aDX = *(aCirc1->center.x) - *(aCirc2->center.x);
410 double aDY = *(aCirc1->center.y) - *(aCirc2->center.y);
411 double aDist = sqrt(aDX * aDX + aDY * aDY);
413 return (aDist < *(aCirc1->rad) || aDist < *(aCirc2->rad));
416 // sets angle to 0 or 180 degrees
417 static void adjustAngleBetweenCurves(const GCSCurvePtr& theCurve1,
418 const GCSCurvePtr& theCurve2,
419 const GCSPointPtr& thePoint,
422 double anAngle = GCS::System().calculateAngleViaPoint(*theCurve1, *theCurve2, *thePoint);
423 // bring angle to [-pi..pi]
424 if (anAngle > PI) anAngle -= 2.0 * PI;
425 if (anAngle < -PI) anAngle += 2.0 * PI;
426 // set angle value according to the current angle between curves
427 if (fabs(anAngle) <= PI / 2.)
434 ConstraintWrapperPtr createArcLineTangency(EntityWrapperPtr theEntity1,
435 EntityWrapperPtr theEntity2,
436 EntityWrapperPtr theSharedPoint,
439 EdgeWrapperPtr anEntLine, anEntCirc;
440 if (theEntity1->type() == ENTITY_LINE) {
441 anEntLine = GCS_EDGE_WRAPPER(theEntity1);
442 anEntCirc = GCS_EDGE_WRAPPER(theEntity2);
444 anEntLine = GCS_EDGE_WRAPPER(theEntity2);
445 anEntCirc = GCS_EDGE_WRAPPER(theEntity1);
448 std::shared_ptr<GCS::Circle> aCirc =
449 std::dynamic_pointer_cast<GCS::Circle>(anEntCirc->entity());
450 std::shared_ptr<GCS::Arc> anArc = std::dynamic_pointer_cast<GCS::Arc>(aCirc);
452 std::shared_ptr<GCS::Line> aLine =
453 std::dynamic_pointer_cast<GCS::Line>(anEntLine->entity());
455 GCSConstraintPtr aNewConstr;
456 if (theSharedPoint && anArc) { // do not process shared point between circle and line
458 std::dynamic_pointer_cast<PlaneGCSSolver_PointWrapper>(theSharedPoint)->point();
460 adjustAngleBetweenCurves(anArc, aLine, aPoint, theAngle);
462 GCSConstraintPtr(new GCS::ConstraintAngleViaPoint(*anArc, *aLine, *aPoint, theAngle));
465 GCSConstraintPtr(new GCS::ConstraintP2LDistance(aCirc->center, *aLine, aCirc->rad));
468 return ConstraintWrapperPtr(
469 new PlaneGCSSolver_ConstraintWrapper(aNewConstr, CONSTRAINT_TANGENT_CIRCLE_LINE));
472 ConstraintWrapperPtr createCurveCurveTangency(EntityWrapperPtr theEntity1,
473 EntityWrapperPtr theEntity2,
474 bool theInternalTangency,
475 EntityWrapperPtr theSharedPoint,
478 GCSCurvePtr aCurve1 =
479 std::dynamic_pointer_cast<GCS::Curve>(GCS_EDGE_WRAPPER(theEntity1)->entity());
480 GCSCurvePtr aCurve2 =
481 std::dynamic_pointer_cast<GCS::Curve>(GCS_EDGE_WRAPPER(theEntity2)->entity());
483 GCSConstraintPtr aNewConstr;
484 if (theSharedPoint) {
486 std::dynamic_pointer_cast<PlaneGCSSolver_PointWrapper>(theSharedPoint)->point();
488 adjustAngleBetweenCurves(aCurve1, aCurve2, aPoint, theAngle);
490 GCSConstraintPtr(new GCS::ConstraintAngleViaPoint(*aCurve1, *aCurve2, *aPoint, theAngle));
492 std::shared_ptr<GCS::Circle> aCirc1 = std::dynamic_pointer_cast<GCS::Circle>(aCurve1);
493 std::shared_ptr<GCS::Circle> aCirc2 = std::dynamic_pointer_cast<GCS::Circle>(aCurve2);
494 aNewConstr = GCSConstraintPtr(new GCS::ConstraintTangentCircumf(aCirc1->center, aCirc2->center,
495 aCirc1->rad, aCirc2->rad, theInternalTangency));
498 return ConstraintWrapperPtr(
499 new PlaneGCSSolver_ConstraintWrapper(aNewConstr, CONSTRAINT_TANGENT_CURVE_CURVE));
502 void calculateTangencyPoint(EntityWrapperPtr theCurve1, EntityWrapperPtr theCurve2,
503 GCSPointPtr& theTangencyPoint)
505 std::shared_ptr<GeomAPI_Ellipse2d> anEllipse = PlaneGCSSolver_Tools::ellipse(theCurve1);
506 EntityWrapperPtr aCurve2 = theCurve2;
508 // try converting to ellipse the second curve
509 anEllipse = PlaneGCSSolver_Tools::ellipse(theCurve2);
511 return; // no one curve is ellipse
515 GeomPnt2dPtr aP1, aP2;
516 if (aCurve2->type() == ENTITY_LINE) {
517 std::shared_ptr<GeomAPI_Lin2d> aLine = PlaneGCSSolver_Tools::line(aCurve2);
518 anEllipse->distance(aLine, aP1, aP2);
520 else if (aCurve2->type() == ENTITY_ARC || aCurve2->type() == ENTITY_CIRCLE) {
521 std::shared_ptr<GeomAPI_Circ2d> aCircle = PlaneGCSSolver_Tools::circle(aCurve2);
522 anEllipse->distance(aCircle, aP1, aP2);
524 else if (aCurve2->type() == ENTITY_ELLIPSE || aCurve2->type() == ENTITY_ELLIPTIC_ARC) {
525 std::shared_ptr<GeomAPI_Ellipse2d> anEl2 = PlaneGCSSolver_Tools::ellipse(aCurve2);
526 anEllipse->distance(anEl2, aP1, aP2);
530 *theTangencyPoint->x = 0.5 * (aP1->x() + aP2->x());
531 *theTangencyPoint->y = 0.5 * (aP1->y() + aP2->y());