Salome HOME
updated copyright message
[modules/shaper.git] / src / SketchSolver / SketchSolver_ConstraintPerpendicular.cpp
1 // Copyright (C) 2014-2023  CEA, EDF
2 //
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.
7 //
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.
12 //
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
16 //
17 // See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
18 //
19
20 #include <SketchSolver_ConstraintPerpendicular.h>
21 #include <SketchSolver_Error.h>
22
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>
28
29 #include <GeomAPI_Circ2d.h>
30 #include <GeomAPI_Lin2d.h>
31 #include <GeomAPI_Pnt2d.h>
32 #include <GeomAPI_Ellipse2d.h>
33
34 #include <SketchPlugin_Arc.h>
35 #include <SketchPlugin_Circle.h>
36 #include <SketchPlugin_ConstraintCoincidence.h>
37 #include <SketchPlugin_ConstraintMiddle.h>
38
39 #include <cmath>
40
41 #define GCS_EDGE_WRAPPER(x) std::dynamic_pointer_cast<PlaneGCSSolver_EdgeWrapper>(x)
42 #define GCS_POINT_WRAPPER(x) std::dynamic_pointer_cast<PlaneGCSSolver_PointWrapper>(x)
43
44 /// \brief Obtain constrained features
45 static void getFeatures(const ConstraintPtr& theConstraint,
46                         FeaturePtr& theFeature1,
47                         FeaturePtr& theFeature2);
48
49 /// \brief Check whether the entities has only one shared point or less.
50 ///        Return list of coincident points.
51 static std::set<AttributePtr> coincidentBoundaryPoints(FeaturePtr theFeature1,
52                                                        FeaturePtr theFeature2);
53
54 /// \brief Collect points coincident with each of two features
55 static std::set<AttributePtr> coincidentPoints(FeaturePtr theFeature1, FeaturePtr theFeature2);
56
57 static void calculateIntersectionPoint(EntityWrapperPtr theCurve1, EntityWrapperPtr theCurve2,
58                                        GCSPointPtr& theIntersectionPoint);
59
60 /// sets angle to 90 or -90 degrees
61 static void adjustAngleBetweenCurves(const GCSCurvePtr& theCurve1,
62                                      const GCSCurvePtr& theCurve2,
63                                      const GCSPointPtr& thePoint,
64                                      double*            theAngle);
65
66
67 void SketchSolver_ConstraintPerpendicular::process()
68 {
69   cleanErrorMsg();
70   if (!myBaseConstraint || !myStorage) {
71     // Not enough parameters are assigned
72     return;
73   }
74
75   EntityWrapperPtr aValue;
76   std::vector<EntityWrapperPtr> anAttributes;
77   SketchSolver_Constraint::getAttributes(aValue, anAttributes);
78   if (!myErrorMsg.empty())
79     return;
80
81   rebuild();
82   if (!myErrorMsg.empty())
83     return;
84
85   myStorage->subscribeUpdates(this, PlaneGCSSolver_UpdateCoincidence::GROUP());
86 }
87
88 void SketchSolver_ConstraintPerpendicular::rebuild()
89 {
90   if (mySolverConstraint)
91     myStorage->removeConstraint(myBaseConstraint);
92
93   std::shared_ptr<PlaneGCSSolver_Storage> aStorage =
94       std::dynamic_pointer_cast<PlaneGCSSolver_Storage>(myStorage);
95
96   mySolverConstraint = ConstraintWrapperPtr();
97   mySharedPoint = AttributePtr();
98   if (myAuxPoint) {
99     GCS::SET_pD aParams = PlaneGCSSolver_Tools::parameters(myAuxPoint);
100     aStorage->removeParameters(aParams);
101     myAuxPoint = EntityWrapperPtr();
102   }
103
104   // Check the quantity of entities of each type and their order (arcs first)
105   int aNbLines = 0;
106   int aNbCircles = 0;
107   int aNbOther = 0;
108   std::list<EntityWrapperPtr>::iterator anEntIt = myAttributes.begin();
109   for (; anEntIt != myAttributes.end(); ++anEntIt) {
110     if (!(*anEntIt).get())
111       continue;
112     if ((*anEntIt)->type() == ENTITY_LINE)
113       ++aNbLines;
114     else if ((*anEntIt)->type() == ENTITY_ARC || (*anEntIt)->type() == ENTITY_CIRCLE)
115       ++aNbCircles;
116     else if ((*anEntIt)->type() != ENTITY_POINT)
117       ++aNbOther;
118   }
119
120   if (aNbLines + aNbCircles + aNbOther == 2) {
121     if (aNbOther > 0)
122       myType = CONSTRAINT_PERPENDICULAR_CURVES;
123   }
124   else {
125     myErrorMsg = SketchSolver_Error::INCORRECT_ATTRIBUTE();
126     return;
127   }
128
129   FeaturePtr aFeature1, aFeature2;
130   getFeatures(myBaseConstraint, aFeature1, aFeature2);
131
132   // check number of coincident points
133   std::set<AttributePtr> aCoincidentPoints = coincidentBoundaryPoints(aFeature1, aFeature2);
134   // Try to find non-boundary points coincident with both features.
135   // It is necesasry to create perpendicularity with ellipse
136   if (aCoincidentPoints.empty() && aNbOther > 0)
137     aCoincidentPoints = coincidentPoints(aFeature1, aFeature2);
138
139   EntityWrapperPtr aSharedPointEntity;
140   std::list<GCSConstraintPtr> anAuxConstraints;
141   if (!aCoincidentPoints.empty()) {
142     mySharedPoint = *aCoincidentPoints.begin();
143     aSharedPointEntity = myStorage->entity(mySharedPoint);
144   }
145   else if (aNbOther > 0) {
146     // create auxiliary point
147     GCSPointPtr aPoint(new GCS::Point);
148     aPoint->x = aStorage->createParameter();
149     aPoint->y = aStorage->createParameter();
150     calculateIntersectionPoint(myAttributes.front(), myAttributes.back(), aPoint);
151
152     myAuxPoint.reset(new PlaneGCSSolver_PointWrapper(aPoint));
153     aSharedPointEntity = myAuxPoint;
154
155     // create auxiliary coincident constraints for tangency with ellipse
156     EntityWrapperPtr aDummy;
157     ConstraintWrapperPtr aCoincidence = PlaneGCSSolver_Tools::createConstraint(ConstraintPtr(),
158         CONSTRAINT_PT_ON_CURVE, aDummy, aSharedPointEntity, aDummy, myAttributes.front(), aDummy);
159     anAuxConstraints = aCoincidence->constraints();
160     aCoincidence = PlaneGCSSolver_Tools::createConstraint(ConstraintPtr(),
161         CONSTRAINT_PT_ON_CURVE, aDummy, aSharedPointEntity, aDummy, myAttributes.back(), aDummy);
162     anAuxConstraints.insert(anAuxConstraints.end(),
163         aCoincidence->constraints().begin(), aCoincidence->constraints().end());
164   }
165
166   ScalarWrapperPtr anAngleWrapper;
167   if (aSharedPointEntity) {
168     adjustAngleBetweenCurves(GCS_EDGE_WRAPPER(myAttributes.front())->entity(),
169                              GCS_EDGE_WRAPPER(myAttributes.back())->entity(),
170                              GCS_POINT_WRAPPER(aSharedPointEntity)->point(), &myCurveCurveAngle);
171     anAngleWrapper.reset(new PlaneGCSSolver_ScalarWrapper(&myCurveCurveAngle));
172   }
173
174   mySolverConstraint = PlaneGCSSolver_Tools::createConstraint(myBaseConstraint, myType,
175       anAngleWrapper, aSharedPointEntity, EntityWrapperPtr(),
176       myAttributes.front(), myAttributes.back());
177
178   if (!anAuxConstraints.empty()) {
179     anAuxConstraints.insert(anAuxConstraints.end(), mySolverConstraint->constraints().begin(),
180         mySolverConstraint->constraints().end());
181     mySolverConstraint->setConstraints(anAuxConstraints);
182   }
183
184   myStorage->addConstraint(myBaseConstraint, mySolverConstraint);
185 }
186
187 void SketchSolver_ConstraintPerpendicular::notify(const FeaturePtr&      theFeature,
188                                                   PlaneGCSSolver_Update* theUpdater)
189 {
190   if (theFeature->getKind() != SketchPlugin_ConstraintCoincidence::ID())
191     return;
192
193   // base constraint may become invalid (for example, during undo)
194   if (!myBaseConstraint->data() || !myBaseConstraint->data()->isValid())
195     return;
196
197   FeaturePtr aTgFeat1, aTgFeat2;
198   getFeatures(myBaseConstraint, aTgFeat1, aTgFeat2);
199
200   bool isRebuild = false;
201   if (theFeature->data() && theFeature->data()->isValid()) {
202     // the constraint has been created
203     if (!mySharedPoint) {
204       // features has no shared point, check whether coincidence constraint binds these features)
205       int aNbCoincidentFeatures = 0;
206       for (int i = 0; i < CONSTRAINT_ATTR_SIZE; ++i) {
207         AttributeRefAttrPtr aRefAttr = theFeature->refattr(SketchPlugin_Constraint::ATTRIBUTE(i));
208         if (!aRefAttr)
209           continue;
210
211         ObjectPtr anObj;
212         if (aRefAttr->isObject())
213           anObj = aRefAttr->object();
214         else
215           anObj = aRefAttr->attr()->owner();
216
217         FeaturePtr aFeature = ModelAPI_Feature::feature(anObj);
218         if (aFeature == aTgFeat1 || aFeature == aTgFeat2)
219           ++aNbCoincidentFeatures;
220       }
221
222       if (aNbCoincidentFeatures == 2)
223         isRebuild = true;
224     }
225   }
226
227   if (!isRebuild) {
228     if (mySharedPoint) {
229       // The features are tangent in the shared point, but the coincidence has been removed/updated.
230       // Check if the coincidence is the same.
231       std::set<AttributePtr> aCoincidentPoints = coincidentBoundaryPoints(aTgFeat1, aTgFeat2);
232       isRebuild = true;
233       std::set<AttributePtr>::iterator anIt = aCoincidentPoints.begin();
234       for (; anIt != aCoincidentPoints.end() && isRebuild; ++anIt)
235         if (*anIt == mySharedPoint)
236           isRebuild = false; // the coincidence is still exists => nothing to change
237     }
238     else {
239       // check both features have a coincident point
240       std::set<AttributePtr> aCoincidentPoints = coincidentPoints(aTgFeat1, aTgFeat2);
241       isRebuild = (bool)(myAuxPoint.get()) == (!aCoincidentPoints.empty());
242     }
243   }
244
245   if (isRebuild)
246     rebuild();
247 }
248
249
250
251
252 // ==================   Auxiliary functions   =================================
253 void getFeatures(const ConstraintPtr& theConstraint,
254                  FeaturePtr&          theFeature1,
255                  FeaturePtr&          theFeature2)
256 {
257   AttributeRefAttrPtr aRefAttr = theConstraint->refattr(SketchPlugin_Constraint::ENTITY_A());
258   theFeature1 = ModelAPI_Feature::feature(aRefAttr->object());
259   aRefAttr = theConstraint->refattr(SketchPlugin_Constraint::ENTITY_B());
260   theFeature2 = ModelAPI_Feature::feature(aRefAttr->object());
261 }
262
263 static std::set<FeaturePtr> collectCoincidences(FeaturePtr theFeature1, FeaturePtr theFeature2)
264 {
265   const std::set<AttributePtr>& aRefs1 = theFeature1->data()->refsToMe();
266   const std::set<AttributePtr>& aRefs2 = theFeature2->data()->refsToMe();
267
268   std::set<FeaturePtr> aCoincidences;
269   std::set<AttributePtr>::const_iterator anIt;
270
271   // collect coincidences referred to the first feature
272   for (anIt = aRefs1.begin(); anIt != aRefs1.end(); ++anIt) {
273     FeaturePtr aRef = ModelAPI_Feature::feature((*anIt)->owner());
274     if (aRef && aRef->getKind() == SketchPlugin_ConstraintCoincidence::ID())
275       aCoincidences.insert(aRef);
276   }
277
278   // leave only coincidences referred to the second feature
279   std::set<FeaturePtr> aCoincidencesBetweenFeatures;
280   for (anIt = aRefs2.begin(); anIt != aRefs2.end(); ++anIt) {
281     FeaturePtr aRef = ModelAPI_Feature::feature((*anIt)->owner());
282     if (aCoincidences.find(aRef) != aCoincidences.end())
283       aCoincidencesBetweenFeatures.insert(aRef);
284   }
285
286   return aCoincidencesBetweenFeatures;
287 }
288
289 std::set<AttributePtr> coincidentBoundaryPoints(FeaturePtr theFeature1, FeaturePtr theFeature2)
290 {
291   std::set<FeaturePtr> aCoincidences = collectCoincidences(theFeature1, theFeature2);
292   // collect points only
293   std::set<AttributePtr> aCoincidentPoints;
294   std::set<FeaturePtr>::const_iterator aCIt = aCoincidences.begin();
295   for (; aCIt != aCoincidences.end(); ++ aCIt) {
296     AttributeRefAttrPtr aRefAttrA = (*aCIt)->refattr(SketchPlugin_Constraint::ENTITY_A());
297     AttributeRefAttrPtr aRefAttrB = (*aCIt)->refattr(SketchPlugin_Constraint::ENTITY_B());
298     if (!aRefAttrA || aRefAttrA->isObject() ||
299         !aRefAttrB || aRefAttrB->isObject())
300       continue;
301
302     AttributePtr anAttrA = aRefAttrA->attr();
303     AttributePtr anAttrB = aRefAttrB->attr();
304     if (anAttrA->id() != SketchPlugin_Arc::CENTER_ID() &&
305         anAttrA->id() != SketchPlugin_Circle::CENTER_ID() &&
306         anAttrB->id() != SketchPlugin_Arc::CENTER_ID() &&
307         anAttrB->id() != SketchPlugin_Circle::CENTER_ID()) {
308       aCoincidentPoints.insert(anAttrA);
309       aCoincidentPoints.insert(anAttrB);
310     }
311   }
312   return aCoincidentPoints;
313 }
314
315 static std::set<AttributePtr> refsToFeatureAndResults(FeaturePtr theFeature)
316 {
317   std::set<AttributePtr> aRefs = theFeature->data()->refsToMe();
318   const std::list<ResultPtr>& aResults = theFeature->results();
319   for (std::list<ResultPtr>::const_iterator anIt = aResults.begin();
320       anIt != aResults.end(); ++anIt) {
321     const std::set<AttributePtr>& aResRefs = (*anIt)->data()->refsToMe();
322     aRefs.insert(aResRefs.begin(), aResRefs.end());
323   }
324   return aRefs;
325 }
326
327 // collect all points coincident with the feature
328 static std::set<AttributePtr> pointsOnFeature(FeaturePtr theFeature)
329 {
330   std::set<AttributePtr> aPoints;
331
332   std::set<AttributePtr> aRefs = refsToFeatureAndResults(theFeature);
333   for (std::set<AttributePtr>::const_iterator anIt = aRefs.begin(); anIt != aRefs.end(); ++anIt) {
334     FeaturePtr aRef = ModelAPI_Feature::feature((*anIt)->owner());
335     if (aRef && (aRef->getKind() == SketchPlugin_ConstraintCoincidence::ID() ||
336                  aRef->getKind() == SketchPlugin_ConstraintMiddle::ID())) {
337       for (int i = 0; i < CONSTRAINT_ATTR_SIZE; ++i) {
338         AttributeRefAttrPtr aRefAttr = aRef->refattr(SketchPlugin_Constraint::ATTRIBUTE(i));
339         if (aRefAttr) {
340           AttributePtr anAttr = aRefAttr->attr();
341           if (anAttr && anAttr->id() != SketchPlugin_Arc::CENTER_ID() &&
342                         anAttr->id() != SketchPlugin_Circle::CENTER_ID())
343             aPoints.insert(anAttr);
344         }
345       }
346     }
347   }
348   return aPoints;
349 }
350
351 std::set<AttributePtr> coincidentPoints(FeaturePtr theFeature1, FeaturePtr theFeature2)
352 {
353   std::set<AttributePtr> aPointsOnF1 = pointsOnFeature(theFeature1);
354   std::set<AttributePtr> aPointsOnF2 = pointsOnFeature(theFeature2);
355
356   std::set<AttributePtr> aCommonPoints;
357   for (std::set<AttributePtr>::iterator anIt = aPointsOnF1.begin();
358        anIt != aPointsOnF1.end(); ++anIt)
359     if (aPointsOnF2.find(*anIt) != aPointsOnF2.end())
360       aCommonPoints.insert(*anIt);
361   return aCommonPoints;
362 }
363
364 void adjustAngleBetweenCurves(const GCSCurvePtr& theCurve1,
365                               const GCSCurvePtr& theCurve2,
366                               const GCSPointPtr& thePoint,
367                               double*            theAngle)
368 {
369   double anAngle = GCS::System().calculateAngleViaPoint(*theCurve1, *theCurve2, *thePoint);
370   // bring angle to [-pi..pi]
371   if (anAngle >  PI) anAngle -= 2.0 * PI;
372   if (anAngle < -PI) anAngle += 2.0 * PI;
373   // set angle value according to the current angle between curves
374   if (anAngle >= 0)
375     *theAngle = PI / 2.;
376   else
377     *theAngle = -PI / 2.;
378 }
379
380 void calculateIntersectionPoint(EntityWrapperPtr theCurve1, EntityWrapperPtr theCurve2,
381                                 GCSPointPtr& theIntersectionPoint)
382 {
383   std::shared_ptr<GeomAPI_Ellipse2d> anEllipse = PlaneGCSSolver_Tools::ellipse(theCurve1);
384   EntityWrapperPtr aCurve2 = theCurve2;
385   if (!anEllipse) {
386     // try converting to ellipse the second curve
387     anEllipse = PlaneGCSSolver_Tools::ellipse(theCurve2);
388     if (!anEllipse)
389       return; // no one curve is ellipse
390     aCurve2 = theCurve1;
391   }
392
393   GeomPnt2dPtr aP1, aP2;
394   if (aCurve2->type() == ENTITY_LINE) {
395     std::shared_ptr<GeomAPI_Lin2d> aLine = PlaneGCSSolver_Tools::line(aCurve2);
396     anEllipse->distance(aLine, aP1, aP2);
397   }
398   else if (aCurve2->type() == ENTITY_ARC || aCurve2->type() == ENTITY_CIRCLE) {
399     std::shared_ptr<GeomAPI_Circ2d> aCircle = PlaneGCSSolver_Tools::circle(aCurve2);
400     anEllipse->distance(aCircle, aP1, aP2);
401   }
402   else if (aCurve2->type() == ENTITY_ELLIPSE || aCurve2->type() == ENTITY_ELLIPTIC_ARC) {
403     std::shared_ptr<GeomAPI_Ellipse2d> anEl2 = PlaneGCSSolver_Tools::ellipse(aCurve2);
404     anEllipse->distance(anEl2, aP1, aP2);
405   }
406
407   if (aP1 && aP2) {
408     *theIntersectionPoint->x = 0.5 * (aP1->x() + aP2->x());
409     *theIntersectionPoint->y = 0.5 * (aP1->y() + aP2->y());
410   }
411 }