]> SALOME platform Git repositories - modules/shaper.git/blob - src/SketchPlugin/SketchPlugin_ConstraintAngle.cpp
Salome HOME
2e76e2a4e8479b333df66224eb8b31027457636e
[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(PREV_TYPE_ID(), ModelAPI_AttributeInteger::typeId());
76   data()->attribute(PREV_TYPE_ID())->setIsArgument(false);
77   aValidators->registerNotObligatory(getKind(), PREV_TYPE_ID());
78   if (attribute(TYPE_ID())->isInitialized())
79     integer(PREV_TYPE_ID())->setValue(integer(TYPE_ID())->value());
80
81   data()->addAttribute(SELECTED_FIRST_POINT_ID(), GeomDataAPI_Point2D::typeId());
82   data()->attribute(SELECTED_FIRST_POINT_ID())->setIsArgument(false);
83   aValidators->registerNotObligatory(getKind(), SELECTED_FIRST_POINT_ID());
84
85   data()->addAttribute(SELECTED_SECOND_POINT_ID(), GeomDataAPI_Point2D::typeId());
86   data()->attribute(SELECTED_SECOND_POINT_ID())->setIsArgument(false);
87   aValidators->registerNotObligatory(getKind(), SELECTED_SECOND_POINT_ID());
88 }
89
90 void SketchPlugin_ConstraintAngle::colorConfigInfo(std::string& theSection, std::string& theName,
91                                                    std::string& theDefault)
92 {
93   theSection = "Visualization";
94   theName = "sketch_dimension_color";
95   theDefault = SKETCH_DIMENSION_COLOR;
96 }
97
98 void SketchPlugin_ConstraintAngle::execute()
99 {
100   std::shared_ptr<ModelAPI_Data> aData = data();
101
102   AttributeRefAttrPtr anAttrA = aData->refattr(SketchPlugin_Constraint::ENTITY_A());
103   AttributeRefAttrPtr anAttrB = aData->refattr(SketchPlugin_Constraint::ENTITY_B());
104   if (!anAttrA->isInitialized() || !anAttrB->isInitialized())
105     return;
106
107   AttributeDoublePtr anAttrValue = real(ANGLE_VALUE_ID());
108   if (!anAttrValue->isInitialized())
109     calculateAngle();
110
111   // the value should to be computed here, not in the
112   // getAISObject in order to change the model value
113   // inside the object transaction. This is important for creating a constraint by preselection.
114   // The display of the presentation in this case happens after the transaction commit
115   std::shared_ptr<GeomDataAPI_Point2D> aFlyOutAttr = std::dynamic_pointer_cast<
116       GeomDataAPI_Point2D>(aData->attribute(SketchPlugin_Constraint::FLYOUT_VALUE_PNT()));
117   if(!aFlyOutAttr->isInitialized())
118     compute(SketchPlugin_Constraint::FLYOUT_VALUE_PNT());
119 }
120
121 AISObjectPtr SketchPlugin_ConstraintAngle::getAISObject(AISObjectPtr thePrevious)
122 {
123   if (!sketch())
124     return thePrevious;
125
126   AISObjectPtr anAIS = SketcherPrs_Factory::angleConstraint(this, sketch(),
127                                                             thePrevious);
128   if (anAIS.get() && !thePrevious.get())
129     SketchPlugin_Tools::setDimensionColor(anAIS);
130   return anAIS;
131 }
132
133 void SketchPlugin_ConstraintAngle::attributeChanged(const std::string& theID)
134 {
135   if (myFlyoutUpdate)
136     return;
137
138   std::shared_ptr<ModelAPI_Data> aData = data();
139   if (!aData)
140     return;
141
142   if (theID == TYPE_ID())
143     updateAngleValue();
144
145   FeaturePtr aLineA = SketcherPrs_Tools::getFeatureLine(aData, ENTITY_A());
146   FeaturePtr aLineB = SketcherPrs_Tools::getFeatureLine(aData, ENTITY_B());
147   if (!aLineA || !aLineB)
148     return;
149
150   if (theID == ENTITY_A() || theID == ENTITY_B() ||
151       theID == TYPE_ID() || theID == ANGLE_VALUE_ID()) {
152     calculateAngle();
153   } else if (theID == FLYOUT_VALUE_PNT()) {
154     compute(theID);
155   }
156 }
157
158 void SketchPlugin_ConstraintAngle::calculateAngle()
159 {
160   // update *_REVERSED_* flags
161   calculateAnglePosition();
162
163   std::shared_ptr<ModelAPI_Data> aData = data();
164   std::shared_ptr<GeomAPI_Ax3> aPlane = SketchPlugin_Sketch::plane(sketch());
165   FeaturePtr aLineA = SketcherPrs_Tools::getFeatureLine(aData, ENTITY_A());
166   FeaturePtr aLineB = SketcherPrs_Tools::getFeatureLine(aData, ENTITY_B());
167
168   GeomPnt2dPtr aStartA = SketcherPrs_Tools::getPoint(aLineA.get(), SketchPlugin_Line::START_ID());
169   GeomPnt2dPtr aEndA = SketcherPrs_Tools::getPoint(aLineA.get(), SketchPlugin_Line::END_ID());
170   GeomPnt2dPtr aStartB = SketcherPrs_Tools::getPoint(aLineB.get(), SketchPlugin_Line::START_ID());
171   GeomPnt2dPtr aEndB = SketcherPrs_Tools::getPoint(aLineB.get(), SketchPlugin_Line::END_ID());
172
173   std::shared_ptr<GeomAPI_Lin2d> aLine1(new GeomAPI_Lin2d(aStartA, aEndA));
174   std::shared_ptr<GeomAPI_Lin2d> aLine2(new GeomAPI_Lin2d(aStartB, aEndB));
175
176   bool isReversed1 = boolean(ANGLE_REVERSED_FIRST_LINE_ID())->value();
177   bool isReversed2 = boolean(ANGLE_REVERSED_SECOND_LINE_ID())->value();
178
179   AttributeDoublePtr anAngleValueAttr = real(ANGLE_VALUE_ID());
180   if (!anAngleValueAttr->isInitialized()) {
181     std::shared_ptr<GeomAPI_Angle2d> anAng(
182         new GeomAPI_Angle2d(aLine1, isReversed1, aLine2, isReversed2));
183     anAngleValueAttr->setValue(getAngleForType(fabs(anAng->angleDegree())));
184   }
185
186   std::shared_ptr<GeomAPI_Angle2d> anAng(new GeomAPI_Angle2d(aLine1, false, aLine2, false));
187   double anAngle = anAng->angleDegree();
188
189   anAngle /= fabs(anAngle);
190   anAngle *= getAngleForType(anAngleValueAttr->value(), isReversed1, isReversed2);
191
192   // update value of the constraint to be passed to the solver
193   real(SketchPlugin_Constraint::VALUE())->setValue(anAngle);
194 }
195
196 void SketchPlugin_ConstraintAngle::calculateAnglePosition()
197 {
198   if (attribute(ANGLE_REVERSED_FIRST_LINE_ID())->isInitialized() &&
199       attribute(ANGLE_REVERSED_SECOND_LINE_ID())->isInitialized())
200     return; // already calculated
201
202   DataPtr aData = data();
203   FeaturePtr aLineA = SketcherPrs_Tools::getFeatureLine(aData, ENTITY_A());
204   FeaturePtr aLineB = SketcherPrs_Tools::getFeatureLine(aData, ENTITY_B());
205
206   GeomPnt2dPtr aStartA = SketcherPrs_Tools::getPoint(aLineA.get(), SketchPlugin_Line::START_ID());
207   GeomPnt2dPtr aEndA = SketcherPrs_Tools::getPoint(aLineA.get(), SketchPlugin_Line::END_ID());
208   GeomPnt2dPtr aStartB = SketcherPrs_Tools::getPoint(aLineB.get(), SketchPlugin_Line::START_ID());
209   GeomPnt2dPtr aEndB = SketcherPrs_Tools::getPoint(aLineB.get(), SketchPlugin_Line::END_ID());
210
211   bool isReversed1 = false;
212   bool isReversed2 = false;
213
214   GeomPnt2dPtr aSelected1 = SketcherPrs_Tools::getPoint(this, SELECTED_FIRST_POINT_ID());
215   GeomPnt2dPtr aSelected2 = SketcherPrs_Tools::getPoint(this, SELECTED_SECOND_POINT_ID());
216   if (aSelected1 && aSelected2) {
217     GeomPnt2dPtr anInterPnt = intersect(aLineA, aLineB);
218     if (!anInterPnt)
219       return;
220     std::shared_ptr<GeomAPI_XY> anInterXY = anInterPnt->xy();
221     isReversed1 = aSelected1->xy()->decreased(anInterXY)->dot(
222                   aEndA->xy()->decreased(aStartA->xy())) < -tolerance;
223     isReversed2 = aSelected2->xy()->decreased(anInterXY)->dot(
224                   aEndB->xy()->decreased(aStartB->xy())) < -tolerance;
225   }
226   else {
227     // no point is selected (document opened or Python script is loaded),
228     // calculate basing on the value
229     std::shared_ptr<GeomAPI_Angle2d> anAng(new GeomAPI_Angle2d(aStartA, aEndA, aStartB, aEndB));
230     isReversed1 = anAng->isReversed(0);
231     isReversed2 = anAng->isReversed(1);
232   }
233
234   // adjust reversed flags according to the angle type
235   AttributeIntegerPtr aTypeAttr = integer(TYPE_ID());
236   if (aTypeAttr && aTypeAttr->isInitialized() &&
237      (SketcherPrs_Tools::AngleType)(aTypeAttr->value()) == SketcherPrs_Tools::ANGLE_COMPLEMENTARY)
238     isReversed1 = !isReversed1;
239
240   boolean(ANGLE_REVERSED_FIRST_LINE_ID())->setValue(isReversed1);
241   boolean(ANGLE_REVERSED_SECOND_LINE_ID())->setValue(isReversed2);
242 }
243
244 // Convert angle value from the DIRECT to any given type.
245 static double angleForType(const double theAngle, const int theType)
246 {
247   double anAngle = theAngle;
248   switch ((SketcherPrs_Tools::AngleType)theType) {
249     case SketcherPrs_Tools::ANGLE_DIRECT:
250       anAngle = theAngle;
251       break;
252     case SketcherPrs_Tools::ANGLE_COMPLEMENTARY:
253       anAngle = 180.0 - theAngle;
254       break;
255     case SketcherPrs_Tools::ANGLE_BACKWARD:
256       anAngle = 360.0 - theAngle;
257       break;
258     default:
259       break;
260   }
261   return anAngle;
262 }
263
264 double SketchPlugin_ConstraintAngle::getAngleForType(double theAngle,
265                                                      bool isReversed1,
266                                                      bool isReversed2)
267 {
268   double anAngle = angleForType(theAngle, integer(TYPE_ID())->value());
269   if (isReversed1 != isReversed2)
270     anAngle = 180.0 - anAngle;
271   return anAngle;
272 }
273
274 // Convert angle value or a text expression from one angle type to another
275 static void convertAngle(AttributeDoublePtr& theAngle,
276                          const int thePrevType, const int theNewType)
277 {
278   if (theAngle->isInitialized()) {
279     if (theAngle->text().empty()) {
280       // calculate value related to the type twice:
281       // the first time - to return to direct angle,
282       // the second time - to apply new type
283       double aValue = angleForType(theAngle->value(), thePrevType);
284       aValue = angleForType(aValue, theNewType);
285       theAngle->setValue(aValue);
286     }
287     else {
288       // process the parametric value
289       std::string anAngleText = theAngle->text();
290       std::regex anAngleRegex("\\s*([-+]?[0-9]*\\.?[0-9]*)\\s*([-+])\\s*\\((.*)\\)",
291                               std::regex_constants::ECMAScript);
292
293       double anAnglePrefix = 0.0;
294       static const char aSignPrefix[2] = { '-', '+' };
295       int aSignInd = 1;
296
297       std::smatch aResult;
298       if (std::regex_search(anAngleText, aResult, anAngleRegex)) {
299         anAnglePrefix = std::atof(aResult[1].str().c_str());
300         aSignInd = aResult[2].str()[0] == aSignPrefix[0] ? 0 : 1;
301         anAngleText = aResult[3].str();
302       }
303
304       if (thePrevType != SketcherPrs_Tools::ANGLE_DIRECT)
305         aSignInd = 1 - aSignInd;
306       anAnglePrefix = angleForType(anAnglePrefix, thePrevType);
307
308       if (theNewType != SketcherPrs_Tools::ANGLE_DIRECT)
309         aSignInd = 1 - aSignInd;
310       anAnglePrefix = angleForType(anAnglePrefix, theNewType);
311
312       std::ostringstream aText;
313       bool isPrintSign = true;
314       if (fabs(anAnglePrefix) > tolerance)
315         aText << anAnglePrefix;
316       else
317         isPrintSign = aSignInd == 0;
318       if (isPrintSign)
319         aText << " " << aSignPrefix[aSignInd] << " (";
320       aText << anAngleText << (isPrintSign ? ")" : "");
321       theAngle->setText(aText.str());
322     }
323   }
324 }
325
326 void SketchPlugin_ConstraintAngle::updateAngleValue()
327 {
328   AttributeIntegerPtr anAngleType = integer(TYPE_ID());
329   AttributeIntegerPtr aPrevAngleType = integer(PREV_TYPE_ID());
330   convertAngle(real(ANGLE_VALUE_ID()), aPrevAngleType->value(), anAngleType->value());
331   aPrevAngleType->setValue(anAngleType->value());
332 }
333
334 bool SketchPlugin_ConstraintAngle::compute(const std::string& theAttributeId)
335 {
336   if (theAttributeId != SketchPlugin_Constraint::FLYOUT_VALUE_PNT())
337     return false;
338   if (!sketch())
339     return false;
340
341   std::shared_ptr<GeomDataAPI_Point2D> aFlyOutAttr = std::dynamic_pointer_cast<
342                            GeomDataAPI_Point2D>(attribute(theAttributeId));
343
344   DataPtr aData = data();
345   std::shared_ptr<GeomAPI_Ax3> aPlane = SketchPlugin_Sketch::plane(sketch());
346   FeaturePtr aLineA = SketcherPrs_Tools::getFeatureLine(aData, ENTITY_A());
347   FeaturePtr aLineB = SketcherPrs_Tools::getFeatureLine(aData, ENTITY_B());
348
349   if ((aLineA.get() == NULL) || (aLineB.get() == NULL))
350     return false;
351
352   // Intersection of lines
353   std::shared_ptr<GeomAPI_Pnt2d> anInter = intersect(aLineA, aLineB);
354   if (!anInter)
355     return false;
356
357   bool isReversed1 = boolean(ANGLE_REVERSED_FIRST_LINE_ID())->value();
358   bool isReversed2 = boolean(ANGLE_REVERSED_SECOND_LINE_ID())->value();
359
360   int anAngleType = integer(TYPE_ID())->value();
361
362   bool isSupplementary = anAngleType == (int)SketcherPrs_Tools::ANGLE_COMPLEMENTARY;
363
364   // point on lines to compose an angle
365   GeomPnt2dPtr aPointA = SketcherPrs_Tools::getPoint(aLineA.get(),
366       (isReversed1 ^ isSupplementary) ?
367       SketchPlugin_Line::START_ID() : SketchPlugin_Line::END_ID());
368   GeomPnt2dPtr aPointB = SketcherPrs_Tools::getPoint(aLineB.get(),
369       isReversed2 ? SketchPlugin_Line::START_ID() : SketchPlugin_Line::END_ID());
370
371   myFlyoutUpdate = true;
372   if (aFlyOutAttr->isInitialized()) {
373     std::shared_ptr<GeomAPI_XY> aFlyoutPoint = aFlyOutAttr->pnt()->xy();
374     std::shared_ptr<GeomAPI_XY> anInterXY = anInter->xy();
375     std::shared_ptr<GeomAPI_XY> aDirIF = aFlyoutPoint->decreased(anInterXY);
376     std::shared_ptr<GeomAPI_XY> aDirIA = aPointA->xy()->decreased(anInterXY);
377     std::shared_ptr<GeomAPI_XY> aDirIB = aPointB->xy()->decreased(anInterXY);
378     double aSign = aDirIA->cross(aDirIB);
379     aSign /= fabs(aSign);
380     if (anAngleType == (int)SketcherPrs_Tools::ANGLE_BACKWARD)
381       aSign *= -1.0;
382
383     double cross1 = aSign * aDirIA->cross(aDirIF);
384     if (cross1 < -tolerance)
385       boolean(ANGLE_REVERSED_SECOND_LINE_ID())->setValue(!isReversed2);
386     double cross2 = aSign * aDirIF->cross(aDirIB);
387     if (cross2 < -tolerance)
388       boolean(ANGLE_REVERSED_FIRST_LINE_ID())->setValue(!isReversed1);
389
390     // the direction is reversed only once
391     if ((cross1 + tolerance) * (cross2 + tolerance) < 0.0) {
392       if (anAngleType == (int)SketcherPrs_Tools::ANGLE_BACKWARD) {
393         convertAngle(real(ANGLE_VALUE_ID()), (int)SketcherPrs_Tools::ANGLE_BACKWARD,
394                      (int)SketcherPrs_Tools::ANGLE_DIRECT);
395       }
396       convertAngle(real(ANGLE_VALUE_ID()), (int)SketcherPrs_Tools::ANGLE_DIRECT,
397                    (int)SketcherPrs_Tools::ANGLE_COMPLEMENTARY);
398       if (anAngleType == (int)SketcherPrs_Tools::ANGLE_BACKWARD) {
399         convertAngle(real(ANGLE_VALUE_ID()), (int)SketcherPrs_Tools::ANGLE_DIRECT,
400                      (int)SketcherPrs_Tools::ANGLE_BACKWARD);
401       }
402     }
403
404     calculateAngle();
405   }
406   else {
407     // default position of the presentation
408     double aX = (aPointA->x() + aPointB->x() + anInter->x()) / 3.;
409     double aY = (aPointA->y() + aPointB->y() + anInter->y()) / 3.;
410     aFlyOutAttr->setValue(aX, aY);
411   }
412   myFlyoutUpdate = false;
413
414   return true;
415 }
416
417
418 // ===============   Auxiliary functions   ==================================
419 std::shared_ptr<GeomAPI_Pnt2d> intersect(FeaturePtr theLine1, FeaturePtr theLine2)
420 {
421   // Start and end points of lines
422   const std::string& aLineStartAttr = SketchPlugin_Line::START_ID();
423   const std::string& aLineEndAttr = SketchPlugin_Line::END_ID();
424   GeomPnt2dPtr aStartA = SketcherPrs_Tools::getPoint(theLine1.get(), aLineStartAttr);
425   GeomPnt2dPtr aEndA   = SketcherPrs_Tools::getPoint(theLine1.get(), aLineEndAttr);
426   GeomPnt2dPtr aStartB = SketcherPrs_Tools::getPoint(theLine2.get(), aLineStartAttr);
427   GeomPnt2dPtr aEndB   = SketcherPrs_Tools::getPoint(theLine2.get(), aLineEndAttr);
428   if (aStartA->distance(aEndA) < tolerance || aStartB->distance(aEndB) < tolerance)
429     std::shared_ptr<GeomAPI_Pnt2d>();
430
431   // Lines and their intersection point
432   std::shared_ptr<GeomAPI_Lin2d> aLA(new GeomAPI_Lin2d(aStartA, aEndA));
433   std::shared_ptr<GeomAPI_Lin2d> aLB(new GeomAPI_Lin2d(aStartB, aEndB));
434   return aLA->intersect(aLB);
435 }