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_ConstraintCoincidenceInternal.h>
38 #include <SketchPlugin_ConstraintMiddle.h>
42 #define GCS_EDGE_WRAPPER(x) std::dynamic_pointer_cast<PlaneGCSSolver_EdgeWrapper>(x)
44 /// \brief Obtain tangent features from the constraint
45 static void getTangentFeatures(const ConstraintPtr& theConstraint,
46 FeaturePtr& theFeature1,
47 FeaturePtr& theFeature2);
49 /// \brief Obtain all coincident constraints between features
50 static std::set<FeaturePtr> collectCoincidences(FeaturePtr theFeature1, FeaturePtr theFeature2);
52 /// \brief Check whether the entities has only one shared point or less.
53 /// Return list of coincident points.
54 static std::set<AttributePtr> coincidentBoundaryPoints(FeaturePtr theFeature1,
55 FeaturePtr theFeature2);
57 /// \brief Collect points coincident with each of two features
58 static std::set<AttributePtr> coincidentPoints(FeaturePtr theFeature1, FeaturePtr theFeature2);
60 /// \brief Check if two connected arcs have centers
61 /// in same direction relatively to connection point
62 static bool isArcArcTangencyInternal(EntityWrapperPtr theArc1,
63 EntityWrapperPtr theArc2);
65 static ConstraintWrapperPtr
66 createArcLineTangency(EntityWrapperPtr theEntity1,
67 EntityWrapperPtr theEntity2,
68 EntityWrapperPtr theShapedPoint = EntityWrapperPtr(),
69 double* theAngle = 0);
71 static ConstraintWrapperPtr
72 createCurveCurveTangency(EntityWrapperPtr theEntity1,
73 EntityWrapperPtr theEntity2,
74 bool theInternalTangency,
75 EntityWrapperPtr theSharedPoint = EntityWrapperPtr(),
76 double* theAngle = 0);
78 static void calculateTangencyPoint(EntityWrapperPtr theCurve1, EntityWrapperPtr theCurve2,
79 GCSPointPtr& theTangencyPoint);
82 void SketchSolver_ConstraintTangent::process()
85 if (!myBaseConstraint || !myStorage) {
86 // Not enough parameters are assigned
90 EntityWrapperPtr aValue;
91 std::vector<EntityWrapperPtr> anAttributes;
92 SketchSolver_Constraint::getAttributes(aValue, anAttributes);
93 if (!myErrorMsg.empty())
97 if (!myErrorMsg.empty())
100 myStorage->subscribeUpdates(this, PlaneGCSSolver_UpdateCoincidence::GROUP());
103 void SketchSolver_ConstraintTangent::rebuild()
105 if (mySolverConstraint)
106 myStorage->removeConstraint(myBaseConstraint);
108 std::shared_ptr<PlaneGCSSolver_Storage> aStorage =
109 std::dynamic_pointer_cast<PlaneGCSSolver_Storage>(myStorage);
111 mySolverConstraint = ConstraintWrapperPtr();
112 mySharedPoint = AttributePtr();
114 GCS::SET_pD aParams = PlaneGCSSolver_Tools::parameters(myAuxPoint);
115 aStorage->removeParameters(aParams);
116 myAuxPoint = EntityWrapperPtr();
119 // Check the quantity of entities of each type and their order (arcs first)
123 std::list<EntityWrapperPtr>::iterator anEntIt = myAttributes.begin();
124 for (; anEntIt != myAttributes.end(); ++anEntIt) {
125 if (!(*anEntIt).get())
127 if ((*anEntIt)->type() == ENTITY_LINE)
129 else if ((*anEntIt)->type() == ENTITY_ARC || (*anEntIt)->type() == ENTITY_CIRCLE)
131 else if ((*anEntIt)->type() == ENTITY_ELLIPSE || (*anEntIt)->type() == ENTITY_ELLIPTIC_ARC)
135 if (aNbCircles + aNbEllipses < 1) {
136 myErrorMsg = SketchSolver_Error::INCORRECT_TANGENCY_ATTRIBUTE();
139 if (aNbLines == 1 && aNbCircles == 1) {
140 myType = CONSTRAINT_TANGENT_CIRCLE_LINE;
142 else if (aNbLines + aNbCircles + aNbEllipses == 2) {
143 myType = CONSTRAINT_TANGENT_CURVE_CURVE;
144 isArcArcInternal = isArcArcTangencyInternal(myAttributes.front(), myAttributes.back());
147 myErrorMsg = SketchSolver_Error::INCORRECT_ATTRIBUTE();
151 FeaturePtr aFeature1, aFeature2;
152 getTangentFeatures(myBaseConstraint, aFeature1, aFeature2);
154 // check number of coincident points
155 std::set<AttributePtr> aCoincidentPoints = coincidentBoundaryPoints(aFeature1, aFeature2);
156 if (myType == CONSTRAINT_TANGENT_CIRCLE_LINE && aCoincidentPoints.size() > 2) {
157 myErrorMsg = SketchSolver_Error::TANGENCY_FAILED();
161 // Try to find non-boundary points coincident with both features.
162 // It is necesasry to create tangency with ellipse
163 if (aCoincidentPoints.empty() && aNbEllipses > 0)
164 aCoincidentPoints = coincidentPoints(aFeature1, aFeature2);
166 EntityWrapperPtr aSharedPointEntity;
167 std::list<GCSConstraintPtr> anAuxConstraints;
168 if (!aCoincidentPoints.empty()) {
169 mySharedPoint = *aCoincidentPoints.begin();
170 aSharedPointEntity = myStorage->entity(mySharedPoint);
172 else if (aNbEllipses > 0) {
173 // create auxiliary point
174 GCSPointPtr aPoint(new GCS::Point);
175 aPoint->x = aStorage->createParameter();
176 aPoint->y = aStorage->createParameter();
177 calculateTangencyPoint(myAttributes.front(), myAttributes.back(), aPoint);
179 myAuxPoint.reset(new PlaneGCSSolver_PointWrapper(aPoint));
180 aSharedPointEntity = myAuxPoint;
182 // create auxiliary coincident constraints for tangency with ellipse
183 EntityWrapperPtr aDummy;
184 ConstraintWrapperPtr aCoincidence = PlaneGCSSolver_Tools::createConstraint(ConstraintPtr(),
185 CONSTRAINT_PT_ON_CURVE, aDummy, aSharedPointEntity, aDummy, myAttributes.front(), aDummy);
186 anAuxConstraints = aCoincidence->constraints();
187 aCoincidence = PlaneGCSSolver_Tools::createConstraint(ConstraintPtr(),
188 CONSTRAINT_PT_ON_CURVE, aDummy, aSharedPointEntity, aDummy, myAttributes.back(), aDummy);
189 anAuxConstraints.insert(anAuxConstraints.end(),
190 aCoincidence->constraints().begin(), aCoincidence->constraints().end());
193 if (myType == CONSTRAINT_TANGENT_CIRCLE_LINE) {
194 mySolverConstraint = createArcLineTangency(myAttributes.front(), myAttributes.back(),
195 aSharedPointEntity, &myCurveCurveAngle);
197 mySolverConstraint = createCurveCurveTangency(myAttributes.front(), myAttributes.back(),
198 isArcArcInternal, aSharedPointEntity, &myCurveCurveAngle);
201 if (!anAuxConstraints.empty()) {
202 anAuxConstraints.insert(anAuxConstraints.end(), mySolverConstraint->constraints().begin(),
203 mySolverConstraint->constraints().end());
204 mySolverConstraint->setConstraints(anAuxConstraints);
207 myStorage->addConstraint(myBaseConstraint, mySolverConstraint);
210 void SketchSolver_ConstraintTangent::adjustConstraint()
212 if (myType == CONSTRAINT_TANGENT_CURVE_CURVE) {
213 EntityWrapperPtr anEntity1 =
214 myStorage->entity(myBaseConstraint->attribute(SketchPlugin_Constraint::ENTITY_A()));
215 EntityWrapperPtr anEntity2 =
216 myStorage->entity(myBaseConstraint->attribute(SketchPlugin_Constraint::ENTITY_B()));
218 if (isArcArcInternal != isArcArcTangencyInternal(anEntity1, anEntity2))
223 void SketchSolver_ConstraintTangent::notify(const FeaturePtr& theFeature,
224 PlaneGCSSolver_Update* theUpdater)
226 if (theFeature->getKind() != SketchPlugin_ConstraintCoincidence::ID())
229 // base constraint may become invalid (for example, during undo)
230 if (!myBaseConstraint->data() || !myBaseConstraint->data()->isValid())
233 FeaturePtr aTgFeat1, aTgFeat2;
234 getTangentFeatures(myBaseConstraint, aTgFeat1, aTgFeat2);
236 bool isRebuild = false;
237 if (theFeature->data() && theFeature->data()->isValid()) {
238 // the constraint has been created
239 if (!mySharedPoint) {
240 // features has no shared point, check whether coincidence constraint binds these features)
241 int aNbCoincidentFeatures = 0;
242 for (int i = 0; i < CONSTRAINT_ATTR_SIZE; ++i) {
243 AttributeRefAttrPtr aRefAttr = theFeature->refattr(SketchPlugin_Constraint::ATTRIBUTE(i));
248 if (aRefAttr->isObject())
249 anObj = aRefAttr->object();
251 anObj = aRefAttr->attr()->owner();
253 FeaturePtr aFeature = ModelAPI_Feature::feature(anObj);
254 if (aFeature == aTgFeat1 || aFeature == aTgFeat2)
255 ++aNbCoincidentFeatures;
258 if (aNbCoincidentFeatures == 2)
265 // The features are tangent in the shared point, but the coincidence has been removed/updated.
266 // Check if the coincidence is the same.
267 std::set<AttributePtr> aCoincidentPoints = coincidentBoundaryPoints(aTgFeat1, aTgFeat2);
269 std::set<AttributePtr>::iterator anIt = aCoincidentPoints.begin();
270 for (; anIt != aCoincidentPoints.end() && isRebuild; ++anIt)
271 if (*anIt == mySharedPoint)
272 isRebuild = false; // the coincidence is still exists => nothing to change
275 // check both features have a coincident point
276 std::set<AttributePtr> aCoincidentPoints = coincidentPoints(aTgFeat1, aTgFeat2);
277 isRebuild = (bool)(myAuxPoint.get()) == (!aCoincidentPoints.empty());
288 // ================== Auxiliary functions =================================
289 void getTangentFeatures(const ConstraintPtr& theConstraint,
290 FeaturePtr& theFeature1,
291 FeaturePtr& theFeature2)
293 AttributeRefAttrPtr aRefAttr = theConstraint->refattr(SketchPlugin_Constraint::ENTITY_A());
294 theFeature1 = ModelAPI_Feature::feature(aRefAttr->object());
295 aRefAttr = theConstraint->refattr(SketchPlugin_Constraint::ENTITY_B());
296 theFeature2 = ModelAPI_Feature::feature(aRefAttr->object());
299 std::set<FeaturePtr> collectCoincidences(FeaturePtr theFeature1, FeaturePtr theFeature2)
301 const std::set<AttributePtr>& aRefs1 = theFeature1->data()->refsToMe();
302 const std::set<AttributePtr>& aRefs2 = theFeature2->data()->refsToMe();
304 std::set<FeaturePtr> aCoincidences;
305 std::set<AttributePtr>::const_iterator anIt;
307 // collect coincidences referred to the first feature
308 for (anIt = aRefs1.begin(); anIt != aRefs1.end(); ++anIt) {
309 FeaturePtr aRef = ModelAPI_Feature::feature((*anIt)->owner());
310 if (aRef && aRef->getKind() == SketchPlugin_ConstraintCoincidence::ID())
311 aCoincidences.insert(aRef);
314 // leave only coincidences referred to the second feature
315 std::set<FeaturePtr> aCoincidencesBetweenFeatures;
316 for (anIt = aRefs2.begin(); anIt != aRefs2.end(); ++anIt) {
317 FeaturePtr aRef = ModelAPI_Feature::feature((*anIt)->owner());
318 if (aCoincidences.find(aRef) != aCoincidences.end())
319 aCoincidencesBetweenFeatures.insert(aRef);
322 return aCoincidencesBetweenFeatures;
325 std::set<AttributePtr> coincidentBoundaryPoints(FeaturePtr theFeature1, FeaturePtr theFeature2)
327 std::set<FeaturePtr> aCoincidences = collectCoincidences(theFeature1, theFeature2);
328 // collect points only
329 std::set<AttributePtr> aCoincidentPoints;
330 std::set<FeaturePtr>::const_iterator aCIt = aCoincidences.begin();
331 for (; aCIt != aCoincidences.end(); ++ aCIt) {
332 AttributeRefAttrPtr aRefAttrA = (*aCIt)->refattr(SketchPlugin_Constraint::ENTITY_A());
333 AttributeRefAttrPtr aRefAttrB = (*aCIt)->refattr(SketchPlugin_Constraint::ENTITY_B());
334 if (!aRefAttrA || aRefAttrA->isObject() ||
335 !aRefAttrB || aRefAttrB->isObject())
338 AttributePtr anAttrA = aRefAttrA->attr();
339 AttributePtr anAttrB = aRefAttrB->attr();
340 if (anAttrA->id() != SketchPlugin_Arc::CENTER_ID() &&
341 anAttrA->id() != SketchPlugin_Circle::CENTER_ID() &&
342 anAttrB->id() != SketchPlugin_Arc::CENTER_ID() &&
343 anAttrB->id() != SketchPlugin_Circle::CENTER_ID()) {
344 aCoincidentPoints.insert(anAttrA);
345 aCoincidentPoints.insert(anAttrB);
348 return aCoincidentPoints;
351 static std::set<AttributePtr> refsToFeatureAndResults(FeaturePtr theFeature)
353 std::set<AttributePtr> aRefs = theFeature->data()->refsToMe();
354 const std::list<ResultPtr>& aResults = theFeature->results();
355 for (std::list<ResultPtr>::const_iterator anIt = aResults.begin();
356 anIt != aResults.end(); ++anIt) {
357 const std::set<AttributePtr>& aResRefs = (*anIt)->data()->refsToMe();
358 aRefs.insert(aResRefs.begin(), aResRefs.end());
363 // collect all points coincident with the feature
364 static std::set<AttributePtr> pointsOnFeature(FeaturePtr theFeature)
366 std::set<AttributePtr> aPoints;
368 std::set<AttributePtr> aRefs = refsToFeatureAndResults(theFeature);
369 for (std::set<AttributePtr>::const_iterator anIt = aRefs.begin(); anIt != aRefs.end(); ++anIt) {
370 FeaturePtr aRef = ModelAPI_Feature::feature((*anIt)->owner());
371 if (aRef && (aRef->getKind() == SketchPlugin_ConstraintCoincidence::ID() ||
372 aRef->getKind() == SketchPlugin_ConstraintCoincidenceInternal::ID() ||
373 aRef->getKind() == SketchPlugin_ConstraintMiddle::ID())) {
374 for (int i = 0; i < CONSTRAINT_ATTR_SIZE; ++i) {
375 AttributeRefAttrPtr aRefAttr = aRef->refattr(SketchPlugin_Constraint::ATTRIBUTE(i));
377 AttributePtr anAttr = aRefAttr->attr();
378 if (anAttr && anAttr->id() != SketchPlugin_Arc::CENTER_ID() &&
379 anAttr->id() != SketchPlugin_Circle::CENTER_ID())
380 aPoints.insert(anAttr);
388 std::set<AttributePtr> coincidentPoints(FeaturePtr theFeature1, FeaturePtr theFeature2)
390 std::set<AttributePtr> aPointsOnF1 = pointsOnFeature(theFeature1);
391 std::set<AttributePtr> aPointsOnF2 = pointsOnFeature(theFeature2);
393 std::set<AttributePtr> aCommonPoints;
394 for (std::set<AttributePtr>::iterator anIt = aPointsOnF1.begin();
395 anIt != aPointsOnF1.end(); ++anIt)
396 if (aPointsOnF2.find(*anIt) != aPointsOnF2.end())
397 aCommonPoints.insert(*anIt);
398 return aCommonPoints;
401 bool isArcArcTangencyInternal(EntityWrapperPtr theArc1, EntityWrapperPtr theArc2)
403 std::shared_ptr<GCS::Circle> aCirc1 = std::dynamic_pointer_cast<GCS::Circle>(
404 GCS_EDGE_WRAPPER(theArc1)->entity());
405 std::shared_ptr<GCS::Circle> aCirc2 = std::dynamic_pointer_cast<GCS::Circle>(
406 GCS_EDGE_WRAPPER(theArc2)->entity());
408 if (!aCirc1 || !aCirc2)
411 double aDX = *(aCirc1->center.x) - *(aCirc2->center.x);
412 double aDY = *(aCirc1->center.y) - *(aCirc2->center.y);
413 double aDist = sqrt(aDX * aDX + aDY * aDY);
415 return (aDist < *(aCirc1->rad) || aDist < *(aCirc2->rad));
418 // sets angle to 0 or 180 degrees
419 static void adjustAngleBetweenCurves(const GCSCurvePtr& theCurve1,
420 const GCSCurvePtr& theCurve2,
421 const GCSPointPtr& thePoint,
424 double anAngle = GCS::System().calculateAngleViaPoint(*theCurve1, *theCurve2, *thePoint);
425 // bring angle to [-pi..pi]
426 if (anAngle > PI) anAngle -= 2.0 * PI;
427 if (anAngle < -PI) anAngle += 2.0 * PI;
428 // set angle value according to the current angle between curves
429 if (fabs(anAngle) <= PI / 2.)
436 ConstraintWrapperPtr createArcLineTangency(EntityWrapperPtr theEntity1,
437 EntityWrapperPtr theEntity2,
438 EntityWrapperPtr theSharedPoint,
441 EdgeWrapperPtr anEntLine, anEntCirc;
442 if (theEntity1->type() == ENTITY_LINE) {
443 anEntLine = GCS_EDGE_WRAPPER(theEntity1);
444 anEntCirc = GCS_EDGE_WRAPPER(theEntity2);
446 anEntLine = GCS_EDGE_WRAPPER(theEntity2);
447 anEntCirc = GCS_EDGE_WRAPPER(theEntity1);
450 std::shared_ptr<GCS::Circle> aCirc =
451 std::dynamic_pointer_cast<GCS::Circle>(anEntCirc->entity());
452 std::shared_ptr<GCS::Arc> anArc = std::dynamic_pointer_cast<GCS::Arc>(aCirc);
454 std::shared_ptr<GCS::Line> aLine =
455 std::dynamic_pointer_cast<GCS::Line>(anEntLine->entity());
457 GCSConstraintPtr aNewConstr;
458 if (theSharedPoint && anArc) { // do not process shared point between circle and line
460 std::dynamic_pointer_cast<PlaneGCSSolver_PointWrapper>(theSharedPoint)->point();
462 adjustAngleBetweenCurves(anArc, aLine, aPoint, theAngle);
464 GCSConstraintPtr(new GCS::ConstraintAngleViaPoint(*anArc, *aLine, *aPoint, theAngle));
467 GCSConstraintPtr(new GCS::ConstraintP2LDistance(aCirc->center, *aLine, aCirc->rad));
470 return ConstraintWrapperPtr(
471 new PlaneGCSSolver_ConstraintWrapper(aNewConstr, CONSTRAINT_TANGENT_CIRCLE_LINE));
474 ConstraintWrapperPtr createCurveCurveTangency(EntityWrapperPtr theEntity1,
475 EntityWrapperPtr theEntity2,
476 bool theInternalTangency,
477 EntityWrapperPtr theSharedPoint,
480 GCSCurvePtr aCurve1 =
481 std::dynamic_pointer_cast<GCS::Curve>(GCS_EDGE_WRAPPER(theEntity1)->entity());
482 GCSCurvePtr aCurve2 =
483 std::dynamic_pointer_cast<GCS::Curve>(GCS_EDGE_WRAPPER(theEntity2)->entity());
485 GCSConstraintPtr aNewConstr;
486 if (theSharedPoint) {
488 std::dynamic_pointer_cast<PlaneGCSSolver_PointWrapper>(theSharedPoint)->point();
490 adjustAngleBetweenCurves(aCurve1, aCurve2, aPoint, theAngle);
492 GCSConstraintPtr(new GCS::ConstraintAngleViaPoint(*aCurve1, *aCurve2, *aPoint, theAngle));
494 std::shared_ptr<GCS::Circle> aCirc1 = std::dynamic_pointer_cast<GCS::Circle>(aCurve1);
495 std::shared_ptr<GCS::Circle> aCirc2 = std::dynamic_pointer_cast<GCS::Circle>(aCurve2);
496 aNewConstr = GCSConstraintPtr(new GCS::ConstraintTangentCircumf(aCirc1->center, aCirc2->center,
497 aCirc1->rad, aCirc2->rad, theInternalTangency));
500 return ConstraintWrapperPtr(
501 new PlaneGCSSolver_ConstraintWrapper(aNewConstr, CONSTRAINT_TANGENT_CURVE_CURVE));
504 void calculateTangencyPoint(EntityWrapperPtr theCurve1, EntityWrapperPtr theCurve2,
505 GCSPointPtr& theTangencyPoint)
507 std::shared_ptr<GeomAPI_Ellipse2d> anEllipse = PlaneGCSSolver_Tools::ellipse(theCurve1);
508 EntityWrapperPtr aCurve2 = theCurve2;
510 // try converting to ellipse the second curve
511 anEllipse = PlaneGCSSolver_Tools::ellipse(theCurve2);
513 return; // no one curve is ellipse
517 GeomPnt2dPtr aP1, aP2;
518 if (aCurve2->type() == ENTITY_LINE) {
519 std::shared_ptr<GeomAPI_Lin2d> aLine = PlaneGCSSolver_Tools::line(aCurve2);
520 anEllipse->distance(aLine, aP1, aP2);
522 else if (aCurve2->type() == ENTITY_ARC || aCurve2->type() == ENTITY_CIRCLE) {
523 std::shared_ptr<GeomAPI_Circ2d> aCircle = PlaneGCSSolver_Tools::circle(aCurve2);
524 anEllipse->distance(aCircle, aP1, aP2);
526 else if (aCurve2->type() == ENTITY_ELLIPSE || aCurve2->type() == ENTITY_ELLIPTIC_ARC) {
527 std::shared_ptr<GeomAPI_Ellipse2d> anEl2 = PlaneGCSSolver_Tools::ellipse(aCurve2);
528 anEllipse->distance(anEl2, aP1, aP2);
532 *theTangencyPoint->x = 0.5 * (aP1->x() + aP2->x());
533 *theTangencyPoint->y = 0.5 * (aP1->y() + aP2->y());