Salome HOME
Make SHAPER STUDY fields exported in SMESH into MED file
[modules/shaper.git] / src / SketchPlugin / SketchPlugin_ConstraintAngle.cpp
1 // Copyright (C) 2014-2019  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_ConstraintAngle.h"
21 #include <SketchPlugin_Line.h>
22 #include <SketchPlugin_Tools.h>
23 #include <SketcherPrs_Tools.h>
24
25 #include <ModelAPI_AttributeDouble.h>
26 #include <ModelAPI_AttributeInteger.h>
27 #include <ModelAPI_Session.h>
28 #include <ModelAPI_Validator.h>
29
30 #include <GeomDataAPI_Point2D.h>
31
32 #include <GeomAPI_Angle2d.h>
33 #include <GeomAPI_Dir2d.h>
34 #include <GeomAPI_Lin2d.h>
35 #include <GeomAPI_Pnt2d.h>
36 #include <GeomAPI_XY.h>
37
38 #include <SketcherPrs_Factory.h>
39 #include <SketcherPrs_Tools.h>
40
41 #include <cmath>
42 #include <regex>
43 #include <sstream>
44
45 const double tolerance = 1.e-7;
46 #define PI 3.1415926535897932
47
48 /// \brief Calculate intersection point of two lines
49 static std::shared_ptr<GeomAPI_Pnt2d> intersect(FeaturePtr theLine1, FeaturePtr theLine2);
50
51
52 SketchPlugin_ConstraintAngle::SketchPlugin_ConstraintAngle()
53 {
54   myFlyoutUpdate = false;
55 }
56
57 void SketchPlugin_ConstraintAngle::initAttributes()
58 {
59   ModelAPI_ValidatorsFactory* aValidators = ModelAPI_Session::get()->validators();
60
61   data()->addAttribute(SketchPlugin_Constraint::VALUE(), ModelAPI_AttributeDouble::typeId());
62   data()->addAttribute(SketchPlugin_Constraint::ENTITY_A(), ModelAPI_AttributeRefAttr::typeId());
63   data()->addAttribute(SketchPlugin_Constraint::ENTITY_B(), ModelAPI_AttributeRefAttr::typeId());
64   data()->addAttribute(SketchPlugin_Constraint::FLYOUT_VALUE_PNT(), GeomDataAPI_Point2D::typeId());
65
66   data()->addAttribute(ANGLE_VALUE_ID(), ModelAPI_AttributeDouble::typeId());
67   data()->addAttribute(TYPE_ID(), ModelAPI_AttributeInteger::typeId());
68
69   data()->addAttribute(ANGLE_REVERSED_FIRST_LINE_ID(), ModelAPI_AttributeBoolean::typeId());
70   data()->addAttribute(ANGLE_REVERSED_SECOND_LINE_ID(), ModelAPI_AttributeBoolean::typeId());
71
72   data()->addAttribute(LOCATION_TYPE_ID(), ModelAPI_AttributeInteger::typeId());
73   aValidators->registerNotObligatory(getKind(), LOCATION_TYPE_ID());
74
75   data()->addAttribute(SELECTED_FIRST_POINT_ID(), GeomDataAPI_Point2D::typeId());
76   data()->attribute(SELECTED_FIRST_POINT_ID())->setIsArgument(false);
77   aValidators->registerNotObligatory(getKind(), SELECTED_FIRST_POINT_ID());
78
79   data()->addAttribute(SELECTED_SECOND_POINT_ID(), GeomDataAPI_Point2D::typeId());
80   data()->attribute(SELECTED_SECOND_POINT_ID())->setIsArgument(false);
81   aValidators->registerNotObligatory(getKind(), SELECTED_SECOND_POINT_ID());
82
83   if (attribute(TYPE_ID())->isInitialized())
84     myPrevAngleType = integer(TYPE_ID())->value();
85   else
86     myPrevAngleType = (int)SketcherPrs_Tools::ANGLE_DIRECT;
87 }
88
89 void SketchPlugin_ConstraintAngle::colorConfigInfo(std::string& theSection, std::string& theName,
90                                                    std::string& theDefault)
91 {
92   theSection = "Visualization";
93   theName = "sketch_dimension_color";
94   theDefault = SKETCH_DIMENSION_COLOR;
95 }
96
97 void SketchPlugin_ConstraintAngle::execute()
98 {
99   std::shared_ptr<ModelAPI_Data> aData = data();
100
101   AttributeRefAttrPtr anAttrA = aData->refattr(SketchPlugin_Constraint::ENTITY_A());
102   AttributeRefAttrPtr anAttrB = aData->refattr(SketchPlugin_Constraint::ENTITY_B());
103   if (!anAttrA->isInitialized() || !anAttrB->isInitialized())
104     return;
105
106   AttributeDoublePtr anAttrValue = real(ANGLE_VALUE_ID());
107   if (!anAttrValue->isInitialized())
108     calculateAngle();
109
110   // the value should to be computed here, not in the
111   // getAISObject in order to change the model value
112   // inside the object transaction. This is important for creating a constraint by preselection.
113   // The display of the presentation in this case happens after the transaction commit
114   std::shared_ptr<GeomDataAPI_Point2D> aFlyOutAttr = std::dynamic_pointer_cast<
115       GeomDataAPI_Point2D>(aData->attribute(SketchPlugin_Constraint::FLYOUT_VALUE_PNT()));
116   if(!aFlyOutAttr->isInitialized())
117     compute(SketchPlugin_Constraint::FLYOUT_VALUE_PNT());
118 }
119
120 AISObjectPtr SketchPlugin_ConstraintAngle::getAISObject(AISObjectPtr thePrevious)
121 {
122   if (!sketch())
123     return thePrevious;
124
125   AISObjectPtr anAIS = SketcherPrs_Factory::angleConstraint(this, sketch(),
126                                                             thePrevious);
127   if (anAIS.get() && !thePrevious.get())
128     SketchPlugin_Tools::setDimensionColor(anAIS);
129   return anAIS;
130 }
131
132 void SketchPlugin_ConstraintAngle::attributeChanged(const std::string& theID)
133 {
134   std::shared_ptr<ModelAPI_Data> aData = data();
135   if (!aData)
136     return;
137
138   if (theID == TYPE_ID())
139     updateAngleValue();
140
141   FeaturePtr aLineA = SketcherPrs_Tools::getFeatureLine(aData, ENTITY_A());
142   FeaturePtr aLineB = SketcherPrs_Tools::getFeatureLine(aData, ENTITY_B());
143   if (!aLineA || !aLineB)
144     return;
145
146   if (theID == ENTITY_A() || theID == ENTITY_B() ||
147       theID == TYPE_ID() || theID == ANGLE_VALUE_ID()) {
148     calculateAngle();
149   } else if (theID == FLYOUT_VALUE_PNT() && !myFlyoutUpdate) {
150     // Recalculate flyout point in local coordinates
151     // coordinates are calculated according to the center of shapes intersection
152     std::shared_ptr<GeomDataAPI_Point2D> aFlyoutAttr =
153       std::dynamic_pointer_cast<GeomDataAPI_Point2D>(attribute(FLYOUT_VALUE_PNT()));
154
155     std::shared_ptr<ModelAPI_Data> aData = data();
156     std::shared_ptr<GeomAPI_Ax3> aPlane = SketchPlugin_Sketch::plane(sketch());
157
158     // Intersection of lines
159     std::shared_ptr<GeomAPI_Pnt2d> anInter = intersect(aLineA, aLineB);
160     if (!anInter)
161       return;
162
163     myFlyoutUpdate = true;
164     std::shared_ptr<GeomAPI_XY> aFlyoutDir = aFlyoutAttr->pnt()->xy()->decreased(anInter->xy());
165     if (aFlyoutDir->dot(aFlyoutDir) < tolerance * tolerance)
166       aFlyoutAttr->setValue(aFlyoutAttr->x() + tolerance, aFlyoutAttr->y());
167     myFlyoutUpdate = false;
168   }
169 }
170
171 void SketchPlugin_ConstraintAngle::calculateAngle()
172 {
173   // update *_REVERSED_* flags
174   calculateAnglePosition();
175
176   std::shared_ptr<ModelAPI_Data> aData = data();
177   std::shared_ptr<GeomAPI_Ax3> aPlane = SketchPlugin_Sketch::plane(sketch());
178   FeaturePtr aLineA = SketcherPrs_Tools::getFeatureLine(aData, ENTITY_A());
179   FeaturePtr aLineB = SketcherPrs_Tools::getFeatureLine(aData, ENTITY_B());
180
181   GeomPnt2dPtr aStartA = SketcherPrs_Tools::getPoint(aLineA.get(), SketchPlugin_Line::START_ID());
182   GeomPnt2dPtr aEndA = SketcherPrs_Tools::getPoint(aLineA.get(), SketchPlugin_Line::END_ID());
183   GeomPnt2dPtr aStartB = SketcherPrs_Tools::getPoint(aLineB.get(), SketchPlugin_Line::START_ID());
184   GeomPnt2dPtr aEndB = SketcherPrs_Tools::getPoint(aLineB.get(), SketchPlugin_Line::END_ID());
185
186   std::shared_ptr<GeomAPI_Lin2d> aLine1(new GeomAPI_Lin2d(aStartA, aEndA));
187   std::shared_ptr<GeomAPI_Lin2d> aLine2(new GeomAPI_Lin2d(aStartB, aEndB));
188
189   bool isReversed1 = boolean(ANGLE_REVERSED_FIRST_LINE_ID())->value();
190   bool isReversed2 = boolean(ANGLE_REVERSED_SECOND_LINE_ID())->value();
191
192   std::shared_ptr<GeomAPI_Angle2d> anAng(
193       new GeomAPI_Angle2d(aLine1, isReversed1, aLine2, isReversed2));
194   double anAngle = anAng->angleDegree();
195
196   AttributeDoublePtr anAngleValueAttr = real(ANGLE_VALUE_ID());
197   if (!anAngleValueAttr->isInitialized())
198     anAngleValueAttr->setValue(getAngleForType(fabs(anAngle)));
199
200   anAngle /= fabs(anAngle);
201   anAngle *= getAngleForType(anAngleValueAttr->value());
202
203   // update value of the constraint to be passed to the solver
204   real(SketchPlugin_Constraint::VALUE())->setValue(anAngle);
205 }
206
207 void SketchPlugin_ConstraintAngle::calculateAnglePosition()
208 {
209   if (attribute(ANGLE_REVERSED_FIRST_LINE_ID())->isInitialized() &&
210       attribute(ANGLE_REVERSED_SECOND_LINE_ID())->isInitialized())
211     return; // already calculated
212
213   DataPtr aData = data();
214   FeaturePtr aLineA = SketcherPrs_Tools::getFeatureLine(aData, ENTITY_A());
215   FeaturePtr aLineB = SketcherPrs_Tools::getFeatureLine(aData, ENTITY_B());
216
217   GeomPnt2dPtr aStartA = SketcherPrs_Tools::getPoint(aLineA.get(), SketchPlugin_Line::START_ID());
218   GeomPnt2dPtr aEndA = SketcherPrs_Tools::getPoint(aLineA.get(), SketchPlugin_Line::END_ID());
219   GeomPnt2dPtr aStartB = SketcherPrs_Tools::getPoint(aLineB.get(), SketchPlugin_Line::START_ID());
220   GeomPnt2dPtr aEndB = SketcherPrs_Tools::getPoint(aLineB.get(), SketchPlugin_Line::END_ID());
221
222   bool isReversed1 = false;
223   bool isReversed2 = false;
224
225   GeomPnt2dPtr aSelected1 = SketcherPrs_Tools::getPoint(this, SELECTED_FIRST_POINT_ID());
226   GeomPnt2dPtr aSelected2 = SketcherPrs_Tools::getPoint(this, SELECTED_SECOND_POINT_ID());
227   if (aSelected1 && aSelected2) {
228     GeomPnt2dPtr anInterPnt = intersect(aLineA, aLineB);
229     if (!anInterPnt)
230       return;
231     std::shared_ptr<GeomAPI_XY> anInterXY = anInterPnt->xy();
232     isReversed1 = aSelected1->xy()->decreased(anInterXY)->dot(
233                   aEndA->xy()->decreased(aStartA->xy())) < -tolerance;
234     isReversed2 = aSelected2->xy()->decreased(anInterXY)->dot(
235                   aEndB->xy()->decreased(aStartB->xy())) < -tolerance;
236   }
237   else {
238     // no point is selected (document opened or Python script is loaded),
239     // calculate basing on the value
240     std::shared_ptr<GeomAPI_Angle2d> anAng(new GeomAPI_Angle2d(aStartA, aEndA, aStartB, aEndB));
241     isReversed1 = anAng->isReversed(0);
242     isReversed2 = anAng->isReversed(1);
243   }
244
245   // adjust reversed flags according to the angle type
246   AttributeIntegerPtr aTypeAttr = integer(TYPE_ID());
247   if (aTypeAttr && aTypeAttr->isInitialized() &&
248      (SketcherPrs_Tools::AngleType)(aTypeAttr->value()) == SketcherPrs_Tools::ANGLE_COMPLEMENTARY)
249     isReversed1 = !isReversed1;
250
251   boolean(ANGLE_REVERSED_FIRST_LINE_ID())->setValue(isReversed1);
252   boolean(ANGLE_REVERSED_SECOND_LINE_ID())->setValue(isReversed2);
253 }
254
255 static double angleForType(const double theAngle, const int theType)
256 {
257   double anAngle = theAngle;
258   switch ((SketcherPrs_Tools::AngleType)theType) {
259     case SketcherPrs_Tools::ANGLE_DIRECT:
260       anAngle = theAngle;
261       break;
262     case SketcherPrs_Tools::ANGLE_COMPLEMENTARY:
263       anAngle = 180.0 - theAngle;
264       break;
265     case SketcherPrs_Tools::ANGLE_BACKWARD:
266       anAngle = 360.0 - theAngle;
267       break;
268     default:
269       break;
270   }
271   return anAngle;
272 }
273
274 double SketchPlugin_ConstraintAngle::getAngleForType(double theAngle)
275 {
276   return angleForType(theAngle, integer(TYPE_ID())->value());
277 }
278
279 void SketchPlugin_ConstraintAngle::updateAngleValue()
280 {
281   AttributeIntegerPtr anAngleType = integer(TYPE_ID());
282   AttributeDoublePtr anAngleValueAttr = real(ANGLE_VALUE_ID());
283   if (anAngleValueAttr->isInitialized()) {
284     if (anAngleValueAttr->text().empty()) {
285       // calculate value related to the type twice:
286       // the first time - to return to direct angle,
287       // the second time - to apply new type
288       double aValue = angleForType(anAngleValueAttr->value(), myPrevAngleType);
289       aValue = angleForType(aValue, anAngleType->value());
290       anAngleValueAttr->setValue(aValue);
291     }
292     else {
293       // process the parametric value
294       std::string anAngleText = anAngleValueAttr->text();
295       std::regex anAngleRegex("\\s*([-+]?[0-9]*\\.?[0-9]*)\\s*([-+])\\s*\\((.*)\\)",
296                               std::regex_constants::ECMAScript);
297
298       double anAnglePrefix = 0.0;
299       static const char aSignPrefix[2] = { '-', '+' };
300       int aSignInd = 1;
301
302       std::smatch aResult;
303       if (std::regex_search(anAngleText, aResult, anAngleRegex)) {
304         anAnglePrefix = std::atof(aResult[1].str().c_str());
305         aSignInd = aResult[2].str()[0] == aSignPrefix[0] ? 0 : 1;
306         anAngleText = aResult[3].str();
307       }
308
309       if (myPrevAngleType != SketcherPrs_Tools::ANGLE_DIRECT)
310         aSignInd = 1 - aSignInd;
311       anAnglePrefix = angleForType(anAnglePrefix, myPrevAngleType);
312
313       if (anAngleType->value() != SketcherPrs_Tools::ANGLE_DIRECT)
314         aSignInd = 1 - aSignInd;
315       anAnglePrefix = angleForType(anAnglePrefix, anAngleType->value());
316
317       std::ostringstream aText;
318       bool isPrintSign = true;
319       if (fabs(anAnglePrefix) > tolerance)
320         aText << anAnglePrefix;
321       else
322         isPrintSign = aSignInd == 0;
323       if (isPrintSign)
324         aText << " " << aSignPrefix[aSignInd] << " (";
325       aText << anAngleText << (isPrintSign ? ")" : "");
326       anAngleValueAttr->setText(aText.str());
327     }
328   }
329   myPrevAngleType = anAngleType->value();
330 }
331
332 bool SketchPlugin_ConstraintAngle::compute(const std::string& theAttributeId)
333 {
334   if (theAttributeId != SketchPlugin_Constraint::FLYOUT_VALUE_PNT())
335     return false;
336   if (!sketch())
337     return false;
338
339   std::shared_ptr<GeomDataAPI_Point2D> aFlyOutAttr = std::dynamic_pointer_cast<
340                            GeomDataAPI_Point2D>(attribute(theAttributeId));
341   if (aFlyOutAttr->isInitialized() &&
342       (fabs(aFlyOutAttr->x()) >= tolerance || fabs(aFlyOutAttr->y()) >= tolerance))
343     return false;
344
345   DataPtr aData = data();
346   std::shared_ptr<GeomAPI_Ax3> aPlane = SketchPlugin_Sketch::plane(sketch());
347   FeaturePtr aLineA = SketcherPrs_Tools::getFeatureLine(aData, SketchPlugin_Constraint::ENTITY_A());
348   FeaturePtr aLineB = SketcherPrs_Tools::getFeatureLine(aData, SketchPlugin_Constraint::ENTITY_B());
349
350   if ((aLineA.get() == NULL) || (aLineB.get() == NULL))
351     return false;
352
353   // Start and end points of lines
354   GeomPnt2dPtr aStartA = SketcherPrs_Tools::getPoint(aLineA.get(), SketchPlugin_Line::START_ID());
355   GeomPnt2dPtr aEndA   = SketcherPrs_Tools::getPoint(aLineA.get(), SketchPlugin_Line::END_ID());
356   if (aStartA->distance(aEndA) < tolerance)
357     return false;
358
359   GeomPnt2dPtr aStartB = SketcherPrs_Tools::getPoint(aLineB.get(), SketchPlugin_Line::START_ID());
360   GeomPnt2dPtr aEndB   = SketcherPrs_Tools::getPoint(aLineB.get(), SketchPlugin_Line::END_ID());
361   if (aStartB->distance(aEndB) < tolerance)
362     return false;
363
364   myFlyoutUpdate = true;
365   double aX = (aStartA->x() + aEndA->x() + aStartB->x() + aEndB->x()) / 4.;
366   double aY = (aStartA->y() + aEndA->y() + aStartB->y() + aEndB->y()) / 4.;
367
368   aFlyOutAttr->setValue(aX, aY);
369   myFlyoutUpdate = false;
370
371   return true;
372 }
373
374
375 // ===============   Auxiliary functions   ==================================
376 std::shared_ptr<GeomAPI_Pnt2d> intersect(FeaturePtr theLine1, FeaturePtr theLine2)
377 {
378   // Start and end points of lines
379   const std::string& aLineStartAttr = SketchPlugin_Line::START_ID();
380   const std::string& aLineEndAttr = SketchPlugin_Line::END_ID();
381   GeomPnt2dPtr aStartA = SketcherPrs_Tools::getPoint(theLine1.get(), aLineStartAttr);
382   GeomPnt2dPtr aEndA   = SketcherPrs_Tools::getPoint(theLine1.get(), aLineEndAttr);
383   GeomPnt2dPtr aStartB = SketcherPrs_Tools::getPoint(theLine2.get(), aLineStartAttr);
384   GeomPnt2dPtr aEndB   = SketcherPrs_Tools::getPoint(theLine2.get(), aLineEndAttr);
385   if (aStartA->distance(aEndA) < tolerance || aStartB->distance(aEndB) < tolerance)
386     std::shared_ptr<GeomAPI_Pnt2d>();
387
388   // Lines and their intersection point
389   std::shared_ptr<GeomAPI_Lin2d> aLA(new GeomAPI_Lin2d(aStartA, aEndA));
390   std::shared_ptr<GeomAPI_Lin2d> aLB(new GeomAPI_Lin2d(aStartB, aEndB));
391   return aLA->intersect(aLB);
392 }