Salome HOME
Copyright update 2022
[modules/shaper.git] / src / SketchPlugin / SketchPlugin_CurveFitting.cpp
1 // Copyright (C) 2020-2022  CEA/DEN, EDF R&D
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 <SketchPlugin_CurveFitting.h>
21
22 #include <SketchPlugin_BSpline.h>
23 #include <SketchPlugin_BSplinePeriodic.h>
24 #include <SketchPlugin_ConstraintCoincidence.h>
25 #include <SketchPlugin_MacroBSpline.h>
26 #include <SketchPlugin_Point.h>
27 #include <SketchPlugin_Tools.h>
28 #include <SketchPlugin_Sketch.h>
29
30 #include <ModelAPI_AttributeBoolean.h>
31 #include <ModelAPI_AttributeDouble.h>
32 #include <ModelAPI_AttributeDoubleArray.h>
33 #include <ModelAPI_AttributeInteger.h>
34 #include <ModelAPI_AttributeRefAttrList.h>
35 #include <ModelAPI_AttributeString.h>
36 #include <ModelAPI_Session.h>
37 #include <ModelAPI_Validator.h>
38
39 #include <Events_InfoMessage.h>
40
41 #include <GeomDataAPI_Point2D.h>
42 #include <GeomDataAPI_Point2DArray.h>
43
44 #include <GeomAlgoAPI_CurveBuilder.h>
45 #include <GeomAlgoAPI_EdgeBuilder.h>
46
47 #include <GeomAPI_BSpline.h>
48
49 static void convertTo3D(SketchPlugin_Sketch* theSketch,
50                         const AttributeRefAttrListPtr& theAttribute,
51                         bool theClosedButNotPeriodic,
52                         std::list<GeomPointPtr>& thePoints);
53
54 static GeomEdgePtr buildInterpolationCurve(SketchPlugin_Sketch* theSketch,
55                                            AttributeRefAttrListPtr thePoints,
56                                            bool thePeriodic,
57                                            bool theClosed);
58 static GeomEdgePtr buildApproximationCurve(SketchPlugin_Sketch* theSketch,
59                                            AttributeRefAttrListPtr thePoints,
60                                            double thePrecision,
61                                            bool thePeriodic,
62                                            bool theClosed);
63
64
65 SketchPlugin_CurveFitting::SketchPlugin_CurveFitting()
66   : SketchPlugin_SketchEntity()
67 {
68 }
69
70 void SketchPlugin_CurveFitting::initDerivedClassAttributes()
71 {
72   data()->addAttribute(POINTS_ID(), ModelAPI_AttributeRefAttrList::typeId());
73
74   data()->addAttribute(TYPE_ID(), ModelAPI_AttributeString::typeId());
75
76   data()->addAttribute(PRECISION_ID(), ModelAPI_AttributeDouble::typeId());
77   ModelAPI_Session::get()->validators()->registerNotObligatory(getKind(), PRECISION_ID());
78
79   data()->addAttribute(NEED_CONTROL_POLYGON_ID(), ModelAPI_AttributeBoolean::typeId());
80   ModelAPI_Session::get()->validators()->registerNotObligatory(getKind(),
81                                                                NEED_CONTROL_POLYGON_ID());
82
83   data()->addAttribute(PERIODIC_ID(), ModelAPI_AttributeBoolean::typeId());
84   data()->addAttribute(CLOSED_ID(), ModelAPI_AttributeBoolean::typeId());
85 }
86
87 void SketchPlugin_CurveFitting::execute()
88 {
89   FeaturePtr aBSpline = createBSplineFeature();
90   // create control polygon
91   AttributeBooleanPtr aNeedControlPoly = boolean(NEED_CONTROL_POLYGON_ID());
92   if (aNeedControlPoly && aNeedControlPoly->isInitialized() && aNeedControlPoly->value()) {
93     bool isPeriodic = boolean(PERIODIC_ID())->value();
94     std::list<FeaturePtr> aControlPoles;
95     SketchPlugin_MacroBSpline::createControlPolygon(aBSpline, isPeriodic, aControlPoles);
96   }
97   // constraints for the selected points
98   createConstraints(aBSpline);
99 }
100
101 FeaturePtr SketchPlugin_CurveFitting::createBSplineFeature()
102 {
103   // create transient curve if not created yet
104   if (!myTransientResult) {
105     getAISObject(AISObjectPtr());
106     if (!myTransientResult)
107       return FeaturePtr();
108   }
109
110   SketchPlugin_Sketch* aSketch = sketch();
111   if (!aSketch)
112     return FeaturePtr();
113
114   bool isPeriodic = boolean(PERIODIC_ID())->value();
115
116   FeaturePtr aBSplineFeature = aSketch->addFeature(
117       isPeriodic ? SketchPlugin_BSplinePeriodic::ID() : SketchPlugin_BSpline::ID());
118
119   // Convert transient edge to B-spline curve.
120   GeomCurvePtr aCurve = std::make_shared<GeomAPI_Curve>(myTransientResult);
121   std::shared_ptr<GeomAPI_BSpline> aBSpline = std::make_shared<GeomAPI_BSpline>(aCurve);
122
123   // Fill attributes of B-spline feature:
124   bool aWasBlocked = aBSplineFeature->data()->blockSendAttributeUpdated(true, false);
125   // 1. Degree
126   aBSplineFeature->integer(SketchPlugin_BSplineBase::DEGREE_ID())->setValue(aBSpline->degree());
127   // 2. Poles
128   std::list<GeomPointPtr> aPoles = aBSpline->poles();
129   AttributePoint2DArrayPtr aPolesAttr = std::dynamic_pointer_cast<GeomDataAPI_Point2DArray>(
130       aBSplineFeature->attribute(SketchPlugin_BSplineBase::POLES_ID()));
131   aPolesAttr->setSize((int)aPoles.size());
132   int anIndex = 0;
133   for (auto it = aPoles.begin(); it != aPoles.end(); ++it, ++anIndex)
134     aPolesAttr->setPnt(anIndex, aSketch->to2D(*it));
135   // 3. Weights
136   std::list<double> aWeights = aBSpline->weights();
137   AttributeDoubleArrayPtr aWeightsAttr =
138       aBSplineFeature->data()->realArray(SketchPlugin_BSplineBase::WEIGHTS_ID());
139   if (aWeights.empty()) {
140     aWeightsAttr->setSize((int)aPoles.size());
141     for (anIndex = 0; anIndex < (int)aPoles.size(); ++anIndex)
142       aWeightsAttr->setValue(anIndex, 1.0);
143   }
144   else {
145     aWeightsAttr->setSize((int)aWeights.size());
146     anIndex = 0;
147     for (auto it = aWeights.begin(); it != aWeights.end(); ++it, ++anIndex)
148       aWeightsAttr->setValue(anIndex, *it);
149   }
150   // 4. Knots (normalized from 0 to 1)
151   std::list<double> aKnots = aBSpline->knots();
152   AttributeDoubleArrayPtr aKnotsAttr =
153       aBSplineFeature->data()->realArray(SketchPlugin_BSplineBase::KNOTS_ID());
154   aKnotsAttr->setSize((int)aKnots.size());
155   double aFirstKnot = aKnots.front();
156   double aLastKnot = aKnots.back();
157   anIndex = 0;
158   for (auto it = aKnots.begin(); it != aKnots.end(); ++it, ++anIndex)
159     aKnotsAttr->setValue(anIndex, (*it - aFirstKnot) / (aLastKnot - aFirstKnot));
160   // 5. Multiplicities
161   std::list<int> aMults = aBSpline->mults();
162   AttributeIntArrayPtr aMultsAttr =
163       aBSplineFeature->data()->intArray(SketchPlugin_BSplineBase::MULTS_ID());
164   aMultsAttr->setSize((int)aMults.size());
165   anIndex = 0;
166   for (auto it = aMults.begin(); it != aMults.end(); ++it, ++anIndex)
167     aMultsAttr->setValue(anIndex, *it);
168
169   if (!isPeriodic) {
170     AttributePoint2DPtr aStartPoint = std::dynamic_pointer_cast<GeomDataAPI_Point2D>(
171         aBSplineFeature->attribute(SketchPlugin_BSpline::START_ID()));
172     aStartPoint->setValue(aPolesAttr->pnt(0));
173
174     AttributePoint2DPtr aEndPoint = std::dynamic_pointer_cast<GeomDataAPI_Point2D>(
175       aBSplineFeature->attribute(SketchPlugin_BSpline::END_ID()));
176     aEndPoint->setValue(aPolesAttr->pnt(aPolesAttr->size() - 1));
177   }
178
179   aBSplineFeature->boolean(SketchPlugin_SketchEntity::AUXILIARY_ID())->setValue(
180       boolean(AUXILIARY_ID())->value());
181
182   aBSplineFeature->data()->blockSendAttributeUpdated(aWasBlocked);
183
184   aBSplineFeature->execute();
185
186   return aBSplineFeature;
187 }
188
189 void SketchPlugin_CurveFitting::createConstraints(FeaturePtr theProducedFeature)
190 {
191   if (!theProducedFeature)
192     return;
193
194   SketchPlugin_Sketch* aSketch = sketch();
195   ResultPtr aResult = theProducedFeature->lastResult();
196   bool isPeriodic = boolean(PERIODIC_ID())->value();
197   bool isClosed = boolean(CLOSED_ID())->value();
198   bool isApproximation = string(TYPE_ID())->value() == TYPE_APPROXIMATION_ID();
199
200   AttributeRefAttrListPtr aPointsAttr = refattrlist(POINTS_ID());
201   std::list<std::pair<ObjectPtr, AttributePtr> > aPointsList = aPointsAttr->list();
202   std::list<std::pair<ObjectPtr, AttributePtr> >::iterator aLastIt = --aPointsList.end();
203   for (auto it = aPointsList.begin(); it != aPointsList.end(); ++it) {
204     AttributePtr anAttr = it->second;
205     if (!anAttr) {
206       // maybe the SketchPoint is selected
207       FeaturePtr aFeature = ModelAPI_Feature::feature(it->first);
208       if (aFeature && aFeature->getKind() == SketchPlugin_Point::ID())
209         anAttr = aFeature->attribute(SketchPlugin_Point::COORD_ID());
210       else
211         continue;
212     }
213
214     if (!isPeriodic && it == aPointsList.begin()) {
215       SketchPlugin_Tools::createConstraintAttrAttr(aSketch,
216           SketchPlugin_ConstraintCoincidence::ID(),
217           anAttr, theProducedFeature->attribute(SketchPlugin_BSpline::START_ID()));
218       if (isClosed) {
219         // end of B-spline curve should be coincident with the first selected point
220         SketchPlugin_Tools::createConstraintAttrAttr(aSketch,
221             SketchPlugin_ConstraintCoincidence::ID(),
222             anAttr, theProducedFeature->attribute(SketchPlugin_BSpline::END_ID()));
223       }
224     }
225     else if (!isPeriodic && !isClosed && it == aLastIt) {
226       SketchPlugin_Tools::createConstraintAttrAttr(aSketch,
227           SketchPlugin_ConstraintCoincidence::ID(),
228           anAttr, theProducedFeature->attribute(SketchPlugin_BSpline::END_ID()));
229     }
230     else if (!isApproximation) {
231       SketchPlugin_Tools::createConstraintAttrObject(aSketch,
232           SketchPlugin_ConstraintCoincidence::ID(),
233           anAttr, aResult);
234     }
235   }
236 }
237
238 bool SketchPlugin_CurveFitting::customAction(const std::string& theActionId)
239 {
240   bool isOk = true;
241   if (theActionId == REORDER_POINTS_ACTION_ID()) {
242     reorderPoints();
243   } else {
244     static const std::string MESSAGE("Error: Feature \"%1\" does not support action \"%2\".");
245     Events_InfoMessage("SketchPlugin_CurveFitting", MESSAGE)
246         .arg(getKind()).arg(theActionId).send();
247     isOk = false;
248   }
249   return isOk;
250 }
251
252
253 void SketchPlugin_CurveFitting::reorderPoints()
254 {
255   AttributeRefAttrListPtr aPointsAttr = refattrlist(POINTS_ID());
256   bool isPeriodic = boolean(PERIODIC_ID())->value();
257   bool isClosed = boolean(CLOSED_ID())->value();
258
259   std::list<GeomPointPtr> aCoordinates;
260   convertTo3D(sketch(), aPointsAttr, !isPeriodic && isClosed, aCoordinates);
261
262   // to keep mapping between points and attributes
263   std::map<GeomPointPtr, std::pair<ObjectPtr, AttributePtr> > aMap;
264   std::list<std::pair<ObjectPtr, AttributePtr> > aPointsList = aPointsAttr->list();
265
266   std::list<GeomPointPtr>::iterator aCoordIt = aCoordinates.begin();
267   std::list<std::pair<ObjectPtr, AttributePtr> >::iterator anAttrIt = aPointsList.begin();
268   for (; aCoordIt != aCoordinates.end() && anAttrIt != aPointsList.end(); ++aCoordIt, ++anAttrIt)
269     aMap[*aCoordIt] = *anAttrIt;
270
271   // reorder points
272   GeomAlgoAPI_CurveBuilder::reorderPoints(aCoordinates);
273
274   // re-compose the attribute
275   bool aWasBlocked = data()->blockSendAttributeUpdated(true);
276   aPointsAttr->clear();
277   for (aCoordIt = aCoordinates.begin(); aCoordIt != aCoordinates.end(); ++aCoordIt) {
278     const std::pair<ObjectPtr, AttributePtr>& aValue = aMap.at(*aCoordIt);
279     if (aValue.second)
280       aPointsAttr->append(aValue.second);
281     else
282       aPointsAttr->append(aValue.first);
283   }
284   data()->blockSendAttributeUpdated(aWasBlocked);
285 }
286
287 AISObjectPtr SketchPlugin_CurveFitting::getAISObject(AISObjectPtr thePrevious)
288 {
289   SketchPlugin_Sketch* aSketch = sketch();
290   if (!aSketch)
291     return AISObjectPtr();
292
293   std::string aType = string(TYPE_ID())->value();
294   if (aType == TYPE_INTERPOLATION_ID()) {
295     myTransientResult = buildInterpolationCurve(aSketch,
296                                                 refattrlist(POINTS_ID()),
297                                                 boolean(PERIODIC_ID())->value(),
298                                                 boolean(CLOSED_ID())->value());
299   }
300   else if (aType == TYPE_APPROXIMATION_ID()) {
301     myTransientResult = buildApproximationCurve(aSketch,
302                                                 refattrlist(POINTS_ID()),
303                                                 real(PRECISION_ID())->value(),
304                                                 boolean(PERIODIC_ID())->value(),
305                                                 boolean(CLOSED_ID())->value());
306   }
307   if (!myTransientResult)
308     return AISObjectPtr();
309
310   AISObjectPtr anAIS = thePrevious;
311   if (!anAIS)
312     anAIS.reset(new GeomAPI_AISObject());
313   anAIS->createShape(myTransientResult);
314
315   // Modify attributes
316   SketchPlugin_Tools::customizeFeaturePrs(anAIS, boolean(AUXILIARY_ID())->value());
317
318   return anAIS;
319 }
320
321
322 // ===============================     Auxiliary functions     ====================================
323
324 void convertTo3D(SketchPlugin_Sketch* theSketch,
325                  const AttributeRefAttrListPtr& theAttribute,
326                  bool theClosedButNotPeriodic,
327                  std::list<GeomPointPtr>& thePoints)
328 {
329   std::list<std::pair<ObjectPtr, AttributePtr> > aPointsList = theAttribute->list();
330   for (auto it = aPointsList.begin(); it != aPointsList.end(); ++it) {
331     AttributePtr anAttr = it->second;
332     if (!anAttr) {
333       // maybe the SketchPoint is selected
334       FeaturePtr aFeature = ModelAPI_Feature::feature(it->first);
335       if (aFeature && aFeature->getKind() == SketchPlugin_Point::ID())
336         anAttr = aFeature->attribute(SketchPlugin_Point::COORD_ID());
337       else {
338         thePoints.clear();
339         break;
340       }
341     }
342
343     AttributePoint2DPtr aPoint = std::dynamic_pointer_cast<GeomDataAPI_Point2D>(anAttr);
344     if (aPoint) {
345       GeomPointPtr aPnt3D = theSketch->to3D(aPoint->x(), aPoint->y());
346       thePoints.push_back(aPnt3D);
347     }
348   }
349
350   if (theClosedButNotPeriodic && !thePoints.empty() &&
351       thePoints.front()->distance(thePoints.back()) > 1.e-7)
352     thePoints.push_back(thePoints.front()); // close the curve
353 }
354
355 GeomEdgePtr buildInterpolationCurve(SketchPlugin_Sketch* theSketch,
356                                     AttributeRefAttrListPtr thePoints,
357                                     bool thePeriodic,
358                                     bool theClosed)
359 {
360   std::list<GeomPointPtr> aCoordinates;
361   convertTo3D(theSketch, thePoints, !thePeriodic && theClosed, aCoordinates);
362
363   GeomEdgePtr aResult;
364   if (aCoordinates.size() > 1) {
365     static const bool isReorder = false;
366     static GeomDirPtr aStartEndDir;
367     aResult = GeomAlgoAPI_CurveBuilder::edge(aCoordinates, thePeriodic,
368                                              isReorder, aStartEndDir, aStartEndDir);
369   }
370   return aResult;
371 }
372
373 GeomEdgePtr buildApproximationCurve(SketchPlugin_Sketch* theSketch,
374                                     AttributeRefAttrListPtr thePoints,
375                                     double thePrecision,
376                                     bool thePeriodic,
377                                     bool theClosed)
378 {
379   std::list<GeomPointPtr> aCoordinates;
380   convertTo3D(theSketch, thePoints, !thePeriodic && theClosed, aCoordinates);
381
382   GeomEdgePtr aResult;
383   if (aCoordinates.size() > 1)
384     aResult = GeomAlgoAPI_CurveBuilder::approximate(aCoordinates, thePeriodic, thePrecision);
385   return aResult;
386 }