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 "SketchPlugin_ConstraintAngle.h"
21 #include <SketchPlugin_Line.h>
22 #include <SketchPlugin_Tools.h>
23 #include <SketcherPrs_Tools.h>
25 #include <ModelAPI_AttributeDouble.h>
26 #include <ModelAPI_AttributeInteger.h>
27 #include <ModelAPI_Session.h>
28 #include <ModelAPI_Validator.h>
30 #include <GeomDataAPI_Point2D.h>
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>
38 #include <SketcherPrs_Factory.h>
39 #include <SketcherPrs_Tools.h>
45 const double tolerance = 1.e-7;
46 #define PI 3.1415926535897932
48 /// \brief Calculate intersection point of two lines
49 static std::shared_ptr<GeomAPI_Pnt2d> intersect(FeaturePtr theLine1, FeaturePtr theLine2);
52 SketchPlugin_ConstraintAngle::SketchPlugin_ConstraintAngle()
54 myFlyoutUpdate = false;
57 void SketchPlugin_ConstraintAngle::initAttributes()
59 ModelAPI_ValidatorsFactory* aValidators = ModelAPI_Session::get()->validators();
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());
66 data()->addAttribute(ANGLE_VALUE_ID(), ModelAPI_AttributeDouble::typeId());
67 data()->addAttribute(TYPE_ID(), ModelAPI_AttributeInteger::typeId());
69 data()->addAttribute(ANGLE_REVERSED_FIRST_LINE_ID(), ModelAPI_AttributeBoolean::typeId());
70 data()->addAttribute(ANGLE_REVERSED_SECOND_LINE_ID(), ModelAPI_AttributeBoolean::typeId());
72 data()->addAttribute(LOCATION_TYPE_ID(), ModelAPI_AttributeInteger::typeId());
73 aValidators->registerNotObligatory(getKind(), LOCATION_TYPE_ID());
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());
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());
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());
90 void SketchPlugin_ConstraintAngle::colorConfigInfo(std::string& theSection, std::string& theName,
91 std::string& theDefault)
93 theSection = "Visualization";
94 theName = "sketch_dimension_color";
95 theDefault = SKETCH_DIMENSION_COLOR;
98 void SketchPlugin_ConstraintAngle::execute()
100 std::shared_ptr<ModelAPI_Data> aData = data();
102 AttributeRefAttrPtr anAttrA = aData->refattr(SketchPlugin_Constraint::ENTITY_A());
103 AttributeRefAttrPtr anAttrB = aData->refattr(SketchPlugin_Constraint::ENTITY_B());
104 if (!anAttrA->isInitialized() || !anAttrB->isInitialized())
107 AttributeDoublePtr anAttrValue = real(ANGLE_VALUE_ID());
108 if (!anAttrValue->isInitialized())
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());
121 AISObjectPtr SketchPlugin_ConstraintAngle::getAISObject(AISObjectPtr thePrevious)
126 AISObjectPtr anAIS = SketcherPrs_Factory::angleConstraint(this, sketch(),
128 if (anAIS.get() && !thePrevious.get())
129 SketchPlugin_Tools::setDimensionColor(anAIS);
133 void SketchPlugin_ConstraintAngle::attributeChanged(const std::string& theID)
138 std::shared_ptr<ModelAPI_Data> aData = data();
142 if (theID == TYPE_ID())
145 FeaturePtr aLineA = SketcherPrs_Tools::getFeatureLine(aData, ENTITY_A());
146 FeaturePtr aLineB = SketcherPrs_Tools::getFeatureLine(aData, ENTITY_B());
147 if (!aLineA || !aLineB)
150 if (theID == ENTITY_A() || theID == ENTITY_B() ||
151 theID == TYPE_ID() || theID == ANGLE_VALUE_ID()) {
153 } else if (theID == FLYOUT_VALUE_PNT()) {
158 void SketchPlugin_ConstraintAngle::calculateAngle()
160 // update *_REVERSED_* flags
161 calculateAnglePosition();
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());
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());
173 std::shared_ptr<GeomAPI_Lin2d> aLine1(new GeomAPI_Lin2d(aStartA, aEndA));
174 std::shared_ptr<GeomAPI_Lin2d> aLine2(new GeomAPI_Lin2d(aStartB, aEndB));
176 bool isReversed1 = boolean(ANGLE_REVERSED_FIRST_LINE_ID())->value();
177 bool isReversed2 = boolean(ANGLE_REVERSED_SECOND_LINE_ID())->value();
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())));
186 std::shared_ptr<GeomAPI_Angle2d> anAng(new GeomAPI_Angle2d(aLine1, false, aLine2, false));
187 double anAngle = anAng->angleDegree();
189 anAngle /= fabs(anAngle);
190 anAngle *= getAngleForType(anAngleValueAttr->value(), isReversed1, isReversed2);
192 // update value of the constraint to be passed to the solver
193 real(SketchPlugin_Constraint::VALUE())->setValue(anAngle);
196 void SketchPlugin_ConstraintAngle::calculateAnglePosition()
198 if (attribute(ANGLE_REVERSED_FIRST_LINE_ID())->isInitialized() &&
199 attribute(ANGLE_REVERSED_SECOND_LINE_ID())->isInitialized())
200 return; // already calculated
202 DataPtr aData = data();
203 FeaturePtr aLineA = SketcherPrs_Tools::getFeatureLine(aData, ENTITY_A());
204 FeaturePtr aLineB = SketcherPrs_Tools::getFeatureLine(aData, ENTITY_B());
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());
211 bool isReversed1 = false;
212 bool isReversed2 = false;
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);
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;
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);
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;
240 boolean(ANGLE_REVERSED_FIRST_LINE_ID())->setValue(isReversed1);
241 boolean(ANGLE_REVERSED_SECOND_LINE_ID())->setValue(isReversed2);
244 // Convert angle value from the DIRECT to any given type.
245 static double angleForType(const double theAngle, const int theType)
247 double anAngle = theAngle;
248 switch ((SketcherPrs_Tools::AngleType)theType) {
249 case SketcherPrs_Tools::ANGLE_DIRECT:
252 case SketcherPrs_Tools::ANGLE_COMPLEMENTARY:
253 anAngle = 180.0 - theAngle;
255 case SketcherPrs_Tools::ANGLE_BACKWARD:
256 anAngle = 360.0 - theAngle;
264 double SketchPlugin_ConstraintAngle::getAngleForType(double theAngle,
268 double anAngle = angleForType(theAngle, integer(TYPE_ID())->value());
269 if (isReversed1 != isReversed2)
270 anAngle = 180.0 - anAngle;
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)
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);
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);
293 double anAnglePrefix = 0.0;
294 static const char aSignPrefix[2] = { '-', '+' };
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();
304 if (thePrevType != SketcherPrs_Tools::ANGLE_DIRECT)
305 aSignInd = 1 - aSignInd;
306 anAnglePrefix = angleForType(anAnglePrefix, thePrevType);
308 if (theNewType != SketcherPrs_Tools::ANGLE_DIRECT)
309 aSignInd = 1 - aSignInd;
310 anAnglePrefix = angleForType(anAnglePrefix, theNewType);
312 std::ostringstream aText;
313 bool isPrintSign = true;
314 if (fabs(anAnglePrefix) > tolerance)
315 aText << anAnglePrefix;
317 isPrintSign = aSignInd == 0;
319 aText << " " << aSignPrefix[aSignInd] << " (";
320 aText << anAngleText << (isPrintSign ? ")" : "");
321 theAngle->setText(aText.str());
326 void SketchPlugin_ConstraintAngle::updateAngleValue()
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());
334 bool SketchPlugin_ConstraintAngle::compute(const std::string& theAttributeId)
336 if (theAttributeId != SketchPlugin_Constraint::FLYOUT_VALUE_PNT())
341 std::shared_ptr<GeomDataAPI_Point2D> aFlyOutAttr = std::dynamic_pointer_cast<
342 GeomDataAPI_Point2D>(attribute(theAttributeId));
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());
349 if ((aLineA.get() == NULL) || (aLineB.get() == NULL))
352 // Intersection of lines
353 std::shared_ptr<GeomAPI_Pnt2d> anInter = intersect(aLineA, aLineB);
357 bool isReversed1 = boolean(ANGLE_REVERSED_FIRST_LINE_ID())->value();
358 bool isReversed2 = boolean(ANGLE_REVERSED_SECOND_LINE_ID())->value();
360 int anAngleType = integer(TYPE_ID())->value();
362 bool isSupplementary = anAngleType == (int)SketcherPrs_Tools::ANGLE_COMPLEMENTARY;
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());
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)
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);
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);
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);
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);
412 myFlyoutUpdate = false;
418 // =============== Auxiliary functions ==================================
419 std::shared_ptr<GeomAPI_Pnt2d> intersect(FeaturePtr theLine1, FeaturePtr theLine2)
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>();
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);