diff --git a/src/Gui/Quarter/QuarterWidget.cpp b/src/Gui/Quarter/QuarterWidget.cpp index 024330bb6b..9e618027fa 100644 --- a/src/Gui/Quarter/QuarterWidget.cpp +++ b/src/Gui/Quarter/QuarterWidget.cpp @@ -784,7 +784,7 @@ QuarterWidget::redraw(void) // we're triggering the next paintGL(). Set a flag to remember this // to avoid that we process the delay queue in paintGL() PRIVATE(this)->processdelayqueue = false; - this->viewport()->update(); + this->viewport()->repaint(); } /*! diff --git a/src/Mod/Sketcher/App/CMakeLists.txt b/src/Mod/Sketcher/App/CMakeLists.txt index ca7884a22f..0652b2f259 100644 --- a/src/Mod/Sketcher/App/CMakeLists.txt +++ b/src/Mod/Sketcher/App/CMakeLists.txt @@ -73,6 +73,7 @@ SET(FreeGCS_SRCS freegcs/GCS.cpp freegcs/GCS.h freegcs/Util.h + freegcs/Geo.cpp freegcs/Geo.h freegcs/Constraints.cpp freegcs/Constraints.h diff --git a/src/Mod/Sketcher/App/ConstraintPyImp.cpp b/src/Mod/Sketcher/App/ConstraintPyImp.cpp index bef00372f6..fc29c2785a 100644 --- a/src/Mod/Sketcher/App/ConstraintPyImp.cpp +++ b/src/Mod/Sketcher/App/ConstraintPyImp.cpp @@ -52,9 +52,12 @@ int ConstraintPy::PyInit(PyObject* args, PyObject* /*kwd*/) int ThirdIndex = Constraint::GeoUndef; int ThirdPos = none; double Value = 0; + int intArg1, intArg2, intArg3, intArg4, intArg5; // Note: In Python 2.x PyArg_ParseTuple prints a warning if a float is given but an integer is expected. // This means we must use a PyObject and check afterwards if it's a float or integer. PyObject* index_or_value; + PyObject* oNumArg4; + PyObject* oNumArg5; int any_index; // ConstraintType, GeoIndex @@ -237,10 +240,10 @@ int ConstraintPy::PyInit(PyObject* args, PyObject* /*kwd*/) } PyErr_Clear(); - if (PyArg_ParseTuple(args, "siiiO", &ConstraintType, &FirstIndex, &FirstPos, &SecondIndex, &index_or_value)) { + if (PyArg_ParseTuple(args, "siiiO", &ConstraintType, &intArg1, &intArg2, &intArg3, &oNumArg4)) { // Value, ConstraintType, GeoIndex1, PosIndex1, GeoIndex2, PosIndex2 - if (PyInt_Check(index_or_value)) { - SecondPos = PyInt_AsLong(index_or_value); + if (PyInt_Check(oNumArg4)) { + intArg4 = PyInt_AsLong(oNumArg4); bool valid = false; if (strcmp("Coincident", ConstraintType) == 0) { this->getConstraintPtr()->Type = Coincident; @@ -262,22 +265,33 @@ int ConstraintPy::PyInit(PyObject* args, PyObject* /*kwd*/) this->getConstraintPtr()->Type = Tangent; valid = true; } + else if (strcmp("TangentViaPoint", ConstraintType) == 0) { + this->getConstraintPtr()->Type = Tangent; + //valid = true;//non-standard assignment + this->getConstraintPtr()->First = intArg1; + this->getConstraintPtr()->FirstPos = Sketcher::none; + this->getConstraintPtr()->Second = intArg2; + this->getConstraintPtr()->SecondPos = Sketcher::none; + this->getConstraintPtr()->Third = intArg3; + this->getConstraintPtr()->ThirdPos = (Sketcher::PointPos) intArg4; + return 0; + } if (valid) { - this->getConstraintPtr()->First = FirstIndex; - this->getConstraintPtr()->FirstPos = (Sketcher::PointPos) FirstPos; - this->getConstraintPtr()->Second = SecondIndex; - this->getConstraintPtr()->SecondPos = (Sketcher::PointPos) SecondPos; + this->getConstraintPtr()->First = intArg1; + this->getConstraintPtr()->FirstPos = (Sketcher::PointPos) intArg2; + this->getConstraintPtr()->Second = intArg3; + this->getConstraintPtr()->SecondPos = (Sketcher::PointPos) intArg4; return 0; } } // ConstraintType, GeoIndex1, PosIndex1, GeoIndex2, Value - if (PyNumber_Check(index_or_value)) { // can be float or int - Value = PyFloat_AsDouble(index_or_value); + if (PyNumber_Check(oNumArg4)) { // can be float or int + Value = PyFloat_AsDouble(oNumArg4); if (strcmp("Distance",ConstraintType) == 0 ) { this->getConstraintPtr()->Type = Distance; - this->getConstraintPtr()->First = FirstIndex; - this->getConstraintPtr()->FirstPos = (Sketcher::PointPos) FirstPos; - this->getConstraintPtr()->Second = SecondIndex; + this->getConstraintPtr()->First = intArg1; + this->getConstraintPtr()->FirstPos = (Sketcher::PointPos) intArg2; + this->getConstraintPtr()->Second = intArg3; this->getConstraintPtr()->Value = Value; return 0; } @@ -285,23 +299,23 @@ int ConstraintPy::PyInit(PyObject* args, PyObject* /*kwd*/) } PyErr_Clear(); - if (PyArg_ParseTuple(args, "siiiiO", &ConstraintType, &FirstIndex, &FirstPos, &SecondIndex, &SecondPos, &index_or_value)) { + if (PyArg_ParseTuple(args, "siiiiO", &ConstraintType, &intArg1, &intArg2, &intArg3, &intArg4, &oNumArg5)) { // ConstraintType, GeoIndex1, PosIndex1, GeoIndex2, PosIndex2, GeoIndex3 - if (PyInt_Check(index_or_value)) { - ThirdIndex = PyInt_AsLong(index_or_value); + if (PyInt_Check(oNumArg5)) { + intArg5 = PyInt_AsLong(oNumArg5); if (strcmp("Symmetric",ConstraintType) == 0 ) { this->getConstraintPtr()->Type = Symmetric; - this->getConstraintPtr()->First = FirstIndex; - this->getConstraintPtr()->FirstPos = (Sketcher::PointPos) FirstPos; - this->getConstraintPtr()->Second = SecondIndex; - this->getConstraintPtr()->SecondPos = (Sketcher::PointPos) SecondPos; - this->getConstraintPtr()->Third = ThirdIndex; + this->getConstraintPtr()->First = intArg1; + this->getConstraintPtr()->FirstPos = (Sketcher::PointPos) intArg2; + this->getConstraintPtr()->Second = intArg3; + this->getConstraintPtr()->SecondPos = (Sketcher::PointPos) intArg4; + this->getConstraintPtr()->Third = intArg5; return 0; } } // ConstraintType, GeoIndex1, PosIndex1, GeoIndex2, PosIndex2, Value - if (PyNumber_Check(index_or_value)) { // can be float or int - Value = PyFloat_AsDouble(index_or_value); + if (PyNumber_Check(oNumArg5)) { // can be float or int + Value = PyFloat_AsDouble(oNumArg5); bool valid=false; if (strcmp("Distance",ConstraintType) == 0 ) { this->getConstraintPtr()->Type = Distance; @@ -316,19 +330,36 @@ int ConstraintPy::PyInit(PyObject* args, PyObject* /*kwd*/) valid = true; } else if (strcmp("Angle",ConstraintType) == 0 ) { - if (PyObject_TypeCheck(index_or_value, &(Base::QuantityPy::Type))) { - Base::Quantity q = *(static_cast(index_or_value)->getQuantityPtr()); + if (PyObject_TypeCheck(oNumArg5, &(Base::QuantityPy::Type))) { + Base::Quantity q = *(static_cast(oNumArg5)->getQuantityPtr()); if (q.getUnit() == Base::Unit::Angle) Value = q.getValueAs(Base::Quantity::Radian); } this->getConstraintPtr()->Type = Angle; valid = true; } + else if (strcmp("AngleViaPoint",ConstraintType) == 0 ) { + if (PyObject_TypeCheck(oNumArg5, &(Base::QuantityPy::Type))) { + Base::Quantity q = *(static_cast(oNumArg5)->getQuantityPtr()); + if (q.getUnit() == Base::Unit::Angle) + Value = q.getValueAs(Base::Quantity::Radian); + } + this->getConstraintPtr()->Type = Angle; + //valid = true;//non-standard assignment + this->getConstraintPtr()->First = intArg1; + this->getConstraintPtr()->FirstPos = Sketcher::none; + this->getConstraintPtr()->Second = intArg2; //let's goof up all the terminology =) + this->getConstraintPtr()->SecondPos = Sketcher::none; + this->getConstraintPtr()->Third = intArg3; + this->getConstraintPtr()->ThirdPos = (Sketcher::PointPos) intArg4; + this->getConstraintPtr()->Value = Value; + return 0; + } if (valid) { - this->getConstraintPtr()->First = FirstIndex; - this->getConstraintPtr()->FirstPos = (Sketcher::PointPos) FirstPos; - this->getConstraintPtr()->Second = SecondIndex; - this->getConstraintPtr()->SecondPos = (Sketcher::PointPos) SecondPos; + this->getConstraintPtr()->First = intArg1; + this->getConstraintPtr()->FirstPos = (Sketcher::PointPos) intArg2; + this->getConstraintPtr()->Second = intArg3; + this->getConstraintPtr()->SecondPos = (Sketcher::PointPos) intArg4; this->getConstraintPtr()->Value = Value; return 0; } @@ -375,9 +406,19 @@ std::string ConstraintPy::representation(void) const case Horizontal : result << "'Horizontal' (" << getConstraintPtr()->First << ")>";break; case Vertical : result << "'Vertical' (" << getConstraintPtr()->First << ")>";break; case Parallel : result << "'Parallel'>";break; - case Tangent : result << "'Tangent'>";break; + case Tangent : + if (this->getConstraintPtr()->Third == Constraint::GeoUndef) + result << "'Tangent'>"; + else + result << "'TangentViaPoint'>"; + break; case Distance : result << "'Distance'>";break; - case Angle : result << "'Angle'>";break; + case Angle : + if (this->getConstraintPtr()->Third == Constraint::GeoUndef) + result << "'Angle'>"; + else + result << "'AngleViaPoint'>"; + break; case InternalAlignment : switch(this->getConstraintPtr()->AlignmentType) { case Undef : result << "'InternalAlignment:Undef'>";break; diff --git a/src/Mod/Sketcher/App/Sketch.cpp b/src/Mod/Sketcher/App/Sketch.cpp index ee3306a217..489e5d1f8a 100644 --- a/src/Mod/Sketcher/App/Sketch.cpp +++ b/src/Mod/Sketcher/App/Sketch.cpp @@ -599,6 +599,31 @@ int Sketch::checkGeoId(int geoId) return geoId; } +GCS::Curve* Sketch::getGCSCurveByGeoId(int geoId) +{ + geoId = checkGeoId(geoId); + switch (Geoms[geoId].type) { + case Line: + return &Lines[Geoms[geoId].index]; + break; + case Circle: + return &Circles[Geoms[geoId].index]; + break; + case Arc: + return &Arcs[Geoms[geoId].index]; + break; + case Ellipse: + return &Ellipses[Geoms[geoId].index]; + break; + case ArcOfEllipse: + return &ArcsOfEllipse[Geoms[geoId].index]; + break; + default: + assert(0); + return 0; + }; +} + // constraint adding ========================================================== int Sketch::addConstraint(const Constraint *constraint) @@ -661,7 +686,11 @@ int Sketch::addConstraint(const Constraint *constraint) } break; case Tangent: - if (constraint->SecondPos != none) // tangency at common point + if (constraint->Third != Constraint::GeoUndef){ + rtn = addTangentViaPointConstraint(constraint->First, + constraint->Second, + constraint->Third, constraint->ThirdPos); + } else if (constraint->SecondPos != none) // tangency at common point rtn = addTangentConstraint(constraint->First,constraint->FirstPos, constraint->Second,constraint->SecondPos); else if (constraint->Second != Constraint::GeoUndef) { @@ -688,7 +717,13 @@ int Sketch::addConstraint(const Constraint *constraint) rtn = addDistanceConstraint(constraint->First,constraint->Value); break; case Angle: - if (constraint->SecondPos != none) // angle between two lines (with explicit start points) + if (constraint->Third != Constraint::GeoUndef){ + rtn = addAngleViaPointConstraint ( + constraint->First, + constraint->Second, + constraint->Third, constraint->ThirdPos, + constraint->Value); + } else if (constraint->SecondPos != none) // angle between two lines (with explicit start points) rtn = addAngleConstraint(constraint->First,constraint->FirstPos, constraint->Second,constraint->SecondPos,constraint->Value); else if (constraint->Second != Constraint::GeoUndef) // angle between two lines @@ -1289,106 +1324,55 @@ int Sketch::addTangentConstraint(int geoId1, int geoId2) return -1; } -// tangency at specific point constraint +// endpoint-to-curve tangency int Sketch::addTangentConstraint(int geoId1, PointPos pos1, int geoId2) { - // accepts the following combinations: - // 1) Line1, start/end, Line2/Circle2/Arc2 - // 2) Arc1, start/end, Line2/Circle2/Arc2 geoId1 = checkGeoId(geoId1); geoId2 = checkGeoId(geoId2); int pointId1 = getPointId(geoId1, pos1); - if (pointId1 < 0 || pointId1 >= int(Points.size())) - return addTangentConstraint(geoId1, geoId2); + if (pointId1 < 0 || pointId1 >= int(Points.size())){ + Base::Console().Error("addTangentConstraint (endpoint to curve): point index out of range.\n"); + return -1; + } GCS::Point &p1 = Points[pointId1]; - if (Geoms[geoId1].type == Line) { - GCS::Line &l1 = Lines[Geoms[geoId1].index]; - if (Geoms[geoId2].type == Line) { - GCS::Line &l2 = Lines[Geoms[geoId2].index]; - int tag = ++ConstraintsCounter; - GCSsys.addConstraintPointOnLine(p1, l2, tag); - GCSsys.addConstraintParallel(l1, l2, tag); - return ConstraintsCounter; - } - else if (Geoms[geoId2].type == Arc) { - GCS::Arc &a2 = Arcs[Geoms[geoId2].index]; - int tag = ++ConstraintsCounter; - GCSsys.addConstraintPointOnArc(p1, a2, tag); - GCSsys.addConstraintTangent(l1, a2, tag); - return ConstraintsCounter; - } - else if (Geoms[geoId2].type == Circle) { - GCS::Circle &c2 = Circles[Geoms[geoId2].index]; - int tag = ++ConstraintsCounter; - GCSsys.addConstraintPointOnCircle(p1, c2, tag); - GCSsys.addConstraintTangent(l1, c2, tag); - return ConstraintsCounter; - } - else if (Geoms[geoId2].type == Ellipse) { - - GCS::Ellipse &e = Ellipses[Geoms[geoId2].index]; - int tag = ++ConstraintsCounter; - GCSsys.addConstraintPointOnEllipse(p1, e, tag); - GCSsys.addConstraintTangent(l1, e, tag); - return ConstraintsCounter; - } + + if(Geoms[geoId1].type == Point || Geoms[geoId2].type == Point) + return -1;//supplied points are not endpoints of curves. + GCS::Curve* crv1 = getGCSCurveByGeoId(geoId1); + GCS::Curve* crv2 = getGCSCurveByGeoId(geoId2); + if (!crv1 || !crv2) { + Base::Console().Error("addTangentConstraint (endpoint to curve): getGCSCurveByGeoId returned NULL!\n"); + return -1; } - else if (Geoms[geoId1].type == Arc) { - GCS::Arc &a1 = Arcs[Geoms[geoId1].index]; - if (Geoms[geoId2].type == Line) { - GCS::Line &l2 = Lines[Geoms[geoId2].index]; - int tag = ++ConstraintsCounter; - GCSsys.addConstraintPointOnLine(p1, l2, tag); - GCSsys.addConstraintTangent(l2, a1, tag); - return ConstraintsCounter; - } - else if (Geoms[geoId2].type == Arc) { - GCS::Arc &a2 = Arcs[Geoms[geoId2].index]; - int tag = ++ConstraintsCounter; - GCSsys.addConstraintPointOnArc(p1, a2, tag); - GCSsys.addConstraintTangent(a1, a2, tag); - return ConstraintsCounter; - } - else if (Geoms[geoId2].type == Circle) { - GCS::Circle &c2 = Circles[Geoms[geoId2].index]; - if (pos1 == start) { - int tag = ++ConstraintsCounter; - GCSsys.addConstraintTangentCircle2Arc(c2, a1, tag); - return ConstraintsCounter; - } - else if (pos1 == end) { - int tag = ++ConstraintsCounter; - GCSsys.addConstraintTangentArc2Circle(a1, c2, tag); - return ConstraintsCounter; - } - } else if (Geoms[geoId2].type == Ellipse) { - - GCS::Ellipse &e = Ellipses[Geoms[geoId2].index]; - if (pos1 == start) { - int tag = ++ConstraintsCounter; - GCSsys.addConstraintTangentEllipse2Arc(e, a1, tag); - return ConstraintsCounter; - } - else if (pos1 == end) { - int tag = ++ConstraintsCounter; - GCSsys.addConstraintTangentArc2Ellipse(a1, e, tag); - return ConstraintsCounter; - } - } - } - return -1; + + // add the parameter for the angle + FixParameters.push_back(new double(0.0)); + double *angle = FixParameters[FixParameters.size()-1]; + + //decide if the tangency is internal (angle=0) or external (angle=pi) + //FIXME: The point-on-object constraint should be solved before doing this + //to be strictly correct. But if the point is not way off, the result will be + //close. + *angle = GCSsys.calculateAngleViaPoint(*crv1, *crv2, p1); + if(abs(*angle) > M_PI/2 ) + *angle = M_PI; + else + *angle = 0.0; + + + //ConstraintsCounter will be incremented in the following call. + int tag = + Sketch::addPointOnObjectConstraint(geoId1, pos1, geoId2);//increases ConstraintsCounter + GCSsys.addConstraintAngleViaPoint(*crv1, *crv2, p1, angle, tag); + return ConstraintsCounter; } -// tangency at common point constraint +// endpoint-to-endpoint tangency int Sketch::addTangentConstraint(int geoId1, PointPos pos1, int geoId2, PointPos pos2) { - // accepts the following combinations: - // 1) Line1, start/end, Line2/Arc2, start/end - // 2) Arc1, start/end, Line2, start/end (converted to case #1) - // 3) Arc1, start/end, Arc2, start/end geoId1 = checkGeoId(geoId1); geoId2 = checkGeoId(geoId2); @@ -1396,114 +1380,78 @@ int Sketch::addTangentConstraint(int geoId1, PointPos pos1, int geoId2, PointPos int pointId2 = getPointId(geoId2, pos2); if (pointId1 < 0 || pointId1 >= int(Points.size()) || - pointId2 < 0 || pointId2 >= int(Points.size())) + pointId2 < 0 || pointId2 >= int(Points.size())) { + Base::Console().Error("addTangentConstraint (endpoint to endpoint): point index out of range.\n"); return -1; + } GCS::Point &p1 = Points[pointId1]; GCS::Point &p2 = Points[pointId2]; - if (Geoms[geoId2].type == Line) { - if (Geoms[geoId1].type == Line) { - GCS::Line &l1 = Lines[Geoms[geoId1].index]; - GCS::Line &l2 = Lines[Geoms[geoId2].index]; - int tag = ++ConstraintsCounter; - GCSsys.addConstraintP2PCoincident(p1, p2, tag); - GCSsys.addConstraintParallel(l1, l2, tag); - return ConstraintsCounter; - } - else { - std::swap(geoId1, geoId2); - std::swap(pos1, pos2); - std::swap(pointId1, pointId2); - p1 = Points[pointId1]; - p2 = Points[pointId2]; - } + + if(Geoms[geoId1].type == Point || Geoms[geoId2].type == Point) + return -1;//supplied points are not endpoints of curves. + GCS::Curve* crv1 = getGCSCurveByGeoId(geoId1); + GCS::Curve* crv2 = getGCSCurveByGeoId(geoId2); + if (!crv1 || !crv2) { + Base::Console().Error("addTangentConstraint (endpoint to endpoint): getGCSCurveByGeoId returned NULL!\n"); + return -1; } - if (Geoms[geoId1].type == Line) { - GCS::Line &l1 = Lines[Geoms[geoId1].index]; - if (Geoms[geoId2].type == Arc) { - GCS::Arc &a2 = Arcs[Geoms[geoId2].index]; - if (pos2 == start) { - if (pos1 == start) { - int tag = ++ConstraintsCounter; - GCSsys.addConstraintTangentLine2Arc(l1.p2, l1.p1, a2, tag); - return ConstraintsCounter; - } - else if (pos1 == end) { - int tag = ++ConstraintsCounter; - GCSsys.addConstraintTangentLine2Arc(l1.p1, l1.p2, a2, tag); - return ConstraintsCounter; - } - } - else if (pos2 == end) { - if (pos1 == start) { - int tag = ++ConstraintsCounter; - GCSsys.addConstraintTangentArc2Line(a2, l1.p1, l1.p2, tag); - return ConstraintsCounter; - } - else if (pos1 == end) { - int tag = ++ConstraintsCounter; - GCSsys.addConstraintTangentArc2Line(a2, l1.p2, l1.p1, tag); - return ConstraintsCounter; - } - } - else - return -1; - } - if (Geoms[geoId2].type == ArcOfEllipse) { - GCS::ArcOfEllipse &a2 = ArcsOfEllipse[Geoms[geoId2].index]; - if (pos2 == start) { - if (pos1 == start) { - int tag = ++ConstraintsCounter; - GCSsys.addConstraintTangentLine2ArcOfEllipse(l1.p2, l1.p1, l1, a2, tag); - return ConstraintsCounter; - } - else if (pos1 == end) { - int tag = ++ConstraintsCounter; - GCSsys.addConstraintTangentLine2ArcOfEllipse(l1.p1, l1.p2, l1, a2, tag); - return ConstraintsCounter; - } - } - else if (pos2 == end) { - if (pos1 == start) { - int tag = ++ConstraintsCounter; - GCSsys.addConstraintTangentArcOfEllipse2Line(a2, l1, l1.p1, l1.p2, tag); - return ConstraintsCounter; - } - else if (pos1 == end) { - int tag = ++ConstraintsCounter; - GCSsys.addConstraintTangentArcOfEllipse2Line(a2, l1, l1.p2, l1.p1, tag); - return ConstraintsCounter; - } - } - else - return -1; - } + // add the parameter for the angle + FixParameters.push_back(new double(0.0)); + double *angle = FixParameters[FixParameters.size()-1]; + + //decide if the tangency is internal (angle=0) or external (angle=pi) + *angle = GCSsys.calculateAngleViaPoint(*crv1, *crv2, p1, p2); + if(abs(*angle) > M_PI/2 ) + *angle = M_PI; + else + *angle = 0.0; + + int tag = ++ConstraintsCounter; + GCSsys.addConstraintP2PCoincident(p1, p2, tag); + GCSsys.addConstraintAngleViaPoint(*crv1, *crv2, p1, angle, tag); + return ConstraintsCounter; +} + +int Sketch::addTangentViaPointConstraint(int geoId1, int geoId2, int geoId3, PointPos pos3) +{ + geoId1 = checkGeoId(geoId1); + geoId2 = checkGeoId(geoId2); + geoId3 = checkGeoId(geoId3); + + if (Geoms[geoId1].type == Point || + Geoms[geoId2].type == Point) + return -1;//first two objects must be curves! + + GCS::Curve* crv1 =getGCSCurveByGeoId(geoId1); + GCS::Curve* crv2 =getGCSCurveByGeoId(geoId2); + if (!crv1 || !crv2) { + Base::Console().Error("addTangentViaPointConstraint: getGCSCurveByGeoId returned NULL!\n"); + return -1; } - else if (Geoms[geoId1].type == Arc) { - GCS::Arc &a1 = Arcs[Geoms[geoId1].index]; - if (Geoms[geoId2].type == Arc) { - GCS::Arc &a2 = Arcs[Geoms[geoId2].index]; - if (pos1 == start && (pos2 == start || pos2 == end)) { - int tag = ++ConstraintsCounter; - if (pos2 == start) - GCSsys.addConstraintTangentArc2Arc(a1, true, a2, false, tag); - else // if (pos2 == end) - GCSsys.addConstraintTangentArc2Arc(a1, true, a2, true, tag); - // GCSsys.addConstraintTangentArc2Arc(a2, false, a1, false, tag); - return ConstraintsCounter; - } - else if (pos1 == end && (pos2 == start || pos2 == end)) { - int tag = ++ConstraintsCounter; - if (pos2 == start) - GCSsys.addConstraintTangentArc2Arc(a1, false, a2, false, tag); - else // if (pos2 == end) - GCSsys.addConstraintTangentArc2Arc(a1, false, a2, true, tag); - return ConstraintsCounter; - } - } + + int pointId = getPointId(geoId3, pos3);; + if (pointId < 0 || pointId >= int(Points.size())){ + Base::Console().Error("addTangentViaPointConstraint: point index out of range.\n"); + return -1; } - return -1; + GCS::Point &p = Points[pointId]; + + // add the parameter for the angle + FixParameters.push_back(new double(0.0)); + double *angle = FixParameters[FixParameters.size()-1]; + + //decide if the tangency is internal (angle=0) or external (angle=pi) + *angle = GCSsys.calculateAngleViaPoint(*crv1, *crv2, p); + if(abs(*angle) > M_PI/2 ) + *angle = M_PI; + else + *angle = 0.0; + + int tag = ++ConstraintsCounter; + GCSsys.addConstraintAngleViaPoint(*crv1, *crv2, p, angle, tag); + return ConstraintsCounter; } // line length constraint @@ -1696,6 +1644,31 @@ int Sketch::addAngleConstraint(int geoId1, PointPos pos1, int geoId2, PointPos p return ConstraintsCounter; } +int Sketch::addAngleViaPointConstraint(int geoId1, int geoId2, int geoId3, PointPos pos3, double value) +{ + geoId1 = checkGeoId(geoId1); + geoId2 = checkGeoId(geoId2); + geoId3 = checkGeoId(geoId3); + + if (Geoms[geoId1].type == Point || + Geoms[geoId2].type == Point) + return -1;//first two objects must be curves! + + GCS::Curve* crv1 =getGCSCurveByGeoId(geoId1); + GCS::Curve* crv2 =getGCSCurveByGeoId(geoId2); + int pointId = getPointId(geoId3, pos3);; + GCS::Point &p = Points[pointId]; + + // add the parameter for the angle + FixParameters.push_back(new double(value)); + double *angle = FixParameters[FixParameters.size()-1]; + + int tag = ++ConstraintsCounter; + GCSsys.addConstraintAngleViaPoint(*crv1, *crv2, p, angle, tag); + return ConstraintsCounter; + +} + int Sketch::addEqualConstraint(int geoId1, int geoId2) { geoId1 = checkGeoId(geoId1); @@ -2040,6 +2013,30 @@ int Sketch::addInternalAlignmentEllipseFocus2(int geoId1, int geoId2) return -1; } +double Sketch::calculateAngleViaPoint(int geoId1, int geoId2, double px, double py) +{ + geoId1 = checkGeoId(geoId1); + geoId2 = checkGeoId(geoId2); + + GCS::Point p; + p.x = &px; + p.y = &py; + + return GCSsys.calculateAngleViaPoint(*getGCSCurveByGeoId(geoId1), *getGCSCurveByGeoId(geoId2), p); +} + +Base::Vector3d Sketch::calculateNormalAtPoint(int geoIdCurve, double px, double py) +{ + geoIdCurve = checkGeoId(geoIdCurve); + + GCS::Point p; + p.x = &px; + p.y = &py; + + double tx = 0.0, ty = 0.0; + GCSsys.calculateNormalAtPoint(*getGCSCurveByGeoId(geoIdCurve), p, tx, ty); + return Base::Vector3d(tx,ty,0.0); +} bool Sketch::updateGeometry() { diff --git a/src/Mod/Sketcher/App/Sketch.h b/src/Mod/Sketcher/App/Sketch.h index de54b8cfa0..da6becdad4 100644 --- a/src/Mod/Sketcher/App/Sketch.h +++ b/src/Mod/Sketcher/App/Sketch.h @@ -51,6 +51,8 @@ public: /// solve the actual set up sketch int solve(void); + /// get standard (aka fine) solver precision + double getSolverPrecision(){ return GCSsys.getFinePrecision(); } /// delete all geometry and constraints, leave an empty sketch void clear(void); /** set the sketch up with geoms and constraints @@ -164,12 +166,15 @@ public: int addTangentConstraint(int geoId1, int geoId2); int addTangentConstraint(int geoId1, PointPos pos1, int geoId2); int addTangentConstraint(int geoId1, PointPos pos1, int geoId2, PointPos pos2); + int addTangentViaPointConstraint(int geoId1, int geoId2, int geoId3, PointPos pos3); /// add a radius constraint on a circle or an arc int addRadiusConstraint(int geoId, double value); /// add an angle constraint on a line or between two lines int addAngleConstraint(int geoId, double value); int addAngleConstraint(int geoId1, int geoId2, double value); int addAngleConstraint(int geoId1, PointPos pos1, int geoId2, PointPos pos2, double value); + /// add angle-via-point constraint between any two curves + int addAngleViaPointConstraint(int geoId1, int geoId2, int geoId3, PointPos pos3, double value); /// add ellipse XDir axis angle constraint with respect to XAxis or a lines int addEllipseAngleXUConstraint(int geoId, double value); int addEllipseAngleXUConstraint(int geoId1, int geoId2, double value); @@ -191,6 +196,19 @@ public: int addInternalAlignmentEllipseFocus1(int geoId1, int geoId2); int addInternalAlignmentEllipseFocus2(int geoId1, int geoId2); //@} + + //This func is to be used during angle-via-point constraint creation. It calculates + //the angle between geoId1,geoId2 at point px,py. The point should be on both curves, + //otherwise the result will be systematically off (but smoothly approach the correct + //value as the point approaches intersection of curves). + double calculateAngleViaPoint(int geoId1, int geoId2, double px, double py ); + + //This is to be used for rendering of angle-via-point constraint. + Base::Vector3d calculateNormalAtPoint(int geoIdCurve, double px, double py); + + //icstr should be the value returned by addXXXXConstraint + //see more info in respective function in GCS. + double calculateConstraintError(int icstr) { return GCSsys.calculateConstraintErrorByTag(icstr);} enum GeoType { None = 0, @@ -238,12 +256,15 @@ protected: bool isInitMove; bool isFine; + + private: bool updateGeometry(void); /// checks if the index bounds and converts negative indices to positive int checkGeoId(int geoId); + GCS::Curve* getGCSCurveByGeoId(int geoId); }; } //namespace Part diff --git a/src/Mod/Sketcher/App/SketchObject.cpp b/src/Mod/Sketcher/App/SketchObject.cpp index 4632f97bfa..1c217a8479 100644 --- a/src/Mod/Sketcher/App/SketchObject.cpp +++ b/src/Mod/Sketcher/App/SketchObject.cpp @@ -2213,6 +2213,7 @@ bool SketchObject::evaluateConstraint(const Constraint *constraint) const return false; } break; + //TODO: angle-via-point, tangent-via-point, perp-via-point, snell's law default: break; } @@ -2263,6 +2264,67 @@ void SketchObject::validateConstraints() } } +double SketchObject::calculateAngleViaPoint(int GeoId1, int GeoId2, double px, double py) +{ + //DeepSOIC: this may be slow, but I wanted to reuse the conversion from Geometry to GCS shapes that is done in Sketch + Sketcher::Sketch sk; + int i1 = sk.addGeometry(this->getGeometry(GeoId1)); + int i2 = sk.addGeometry(this->getGeometry(GeoId2)); + + return sk.calculateAngleViaPoint(i1,i2,px,py); +} + +bool SketchObject::isPointOnCurve(int geoIdCurve, double px, double py) +{ + //DeepSOIC: this may be slow, but I wanted to reuse the existing code + Sketcher::Sketch sk; + int icrv = sk.addGeometry(this->getGeometry(geoIdCurve)); + Base::Vector3d pp; + pp.x = px; pp.y = py; + Part::GeomPoint p(pp); + int ipnt = sk.addPoint(p); + int icstr = sk.addPointOnObjectConstraint(ipnt, Sketcher::start, icrv); + double err = sk.calculateConstraintError(icstr); + return err*err < 10.0*sk.getSolverPrecision(); +} + +double SketchObject::calculateConstraintError(int ConstrId) +{ + Sketcher::Sketch sk; + const std::vector &clist = this->Constraints.getValues(); + if (ConstrId < 0 || ConstrId >= int(clist.size())) + return std::numeric_limits::quiet_NaN(); + + Constraint* cstr = clist[ConstrId]->clone(); + double result=0.0; + try{ + std::vector GeoIdList; + int g; + GeoIdList.push_back(cstr->First); + GeoIdList.push_back(cstr->Second); + GeoIdList.push_back(cstr->Third); + + //add only necessary geometry to the sketch + for(int i=0; igetGeometry(g)); + } + } + + cstr->First = GeoIdList[0]; + cstr->Second = GeoIdList[1]; + cstr->Third = GeoIdList[2]; + int icstr = sk.addConstraint(cstr); + result = sk.calculateConstraintError(icstr); + } catch(...) {//cleanup + delete cstr; + throw; + } + delete cstr; + return result; +} + PyObject *SketchObject::getPyObject(void) { if (PythonObject.is(Py::_None())) { diff --git a/src/Mod/Sketcher/App/SketchObject.h b/src/Mod/Sketcher/App/SketchObject.h index 38fac9ce5f..1974577128 100644 --- a/src/Mod/Sketcher/App/SketchObject.h +++ b/src/Mod/Sketcher/App/SketchObject.h @@ -152,6 +152,10 @@ public: /// generates a warning message about redundant constraints and appends it to the given message static void appendRedundantMsg(const std::vector &redundant, std::string &msg); + double calculateAngleViaPoint(int geoId1, int geoId2, double px, double py); + bool isPointOnCurve(int geoIdCurve, double px, double py); + double calculateConstraintError(int ConstrId); + // from base class virtual PyObject *getPyObject(void); virtual unsigned int getMemSize(void) const; diff --git a/src/Mod/Sketcher/App/SketchObjectPy.xml b/src/Mod/Sketcher/App/SketchObjectPy.xml index 87359522fd..7cdda5de96 100644 --- a/src/Mod/Sketcher/App/SketchObjectPy.xml +++ b/src/Mod/Sketcher/App/SketchObjectPy.xml @@ -127,6 +127,36 @@ Deletes all unused (not further constrained) internal geometry + + + + calculateAngleViaPoint(GeoId1, GeoId2, px, py) - calculates angle between + curves identified by GeoId1 and GeoId2 at point (x,y). The point must be + on intersection of the curves, otherwise the result may be useless (except + line-to-line, where (0,0) is OK). Returned value is in radians. + + + + + + + isPointOnObject(GeoIdCurve, float x, float y) - tests if the point (x,y) + geometrically lies on a curve (e.g. ellipse). It treats lines as infinte, + arcs as full circles/ellipses/etc. Returns boolean value. + + + + + + + calculateConstraintError(index) - calculates the error function of the + constraint identified by its index and returns the signed error value. + The error value roughly corresponds to by how much the constraint is + violated. If the constraint internally has more than one error function, + the returned value is RMS of all errors (sign is lost in this case). + + + Number of Constraints in this sketch diff --git a/src/Mod/Sketcher/App/SketchObjectPyImp.cpp b/src/Mod/Sketcher/App/SketchObjectPyImp.cpp index e8da794cee..a8ee28c930 100644 --- a/src/Mod/Sketcher/App/SketchObjectPyImp.cpp +++ b/src/Mod/Sketcher/App/SketchObjectPyImp.cpp @@ -677,7 +677,56 @@ PyObject* SketchObjectPy::trim(PyObject *args) } Py_Return; +} +PyObject* SketchObjectPy::calculateAngleViaPoint(PyObject *args) +{ + int GeoId1=0, GeoId2=0; + double px=0, py=0; + if (!PyArg_ParseTuple(args, "iidd", &GeoId1, &GeoId2, &px, &py)) + return 0; + + SketchObject* obj = this->getSketchObjectPtr(); + if (GeoId1 > obj->getHighestCurveIndex() || -GeoId1 > obj->getExternalGeometryCount() || + GeoId2 > obj->getHighestCurveIndex() || -GeoId2 > obj->getExternalGeometryCount() ) { + PyErr_SetString(PyExc_ValueError, "Invalid geometry Id"); + return 0; + } + double ang = obj->calculateAngleViaPoint(GeoId1, GeoId2, px, py); + + return Py::new_reference_to(Py::Float(ang)); +} + +PyObject* SketchObjectPy::isPointOnCurve(PyObject *args) +{ + int GeoId=Constraint::GeoUndef; + double px=0, py=0; + if (!PyArg_ParseTuple(args, "idd", &GeoId, &px, &py)) + return 0; + + SketchObject* obj = this->getSketchObjectPtr(); + if (GeoId > obj->getHighestCurveIndex() || -GeoId > obj->getExternalGeometryCount()) { + PyErr_SetString(PyExc_ValueError, "Invalid geometry Id"); + return 0; + } + + return Py::new_reference_to(Py::Boolean(obj->isPointOnCurve(GeoId, px, py))); +} + +PyObject* SketchObjectPy::calculateConstraintError(PyObject *args) +{ + int ic=-1; + if (!PyArg_ParseTuple(args, "i", &ic)) + return 0; + + SketchObject* obj = this->getSketchObjectPtr(); + if (ic >= obj->Constraints.getSize() || ic < 0) { + PyErr_SetString(PyExc_ValueError, "Invalid constraint Id"); + return 0; + } + double err = obj->calculateConstraintError(ic); + + return Py::new_reference_to(Py::Float(err)); } PyObject* SketchObjectPy::ExposeInternalGeometry(PyObject *args) diff --git a/src/Mod/Sketcher/App/freegcs/Constraints.cpp b/src/Mod/Sketcher/App/freegcs/Constraints.cpp index 14f5fcd076..e720e74c57 100644 --- a/src/Mod/Sketcher/App/freegcs/Constraints.cpp +++ b/src/Mod/Sketcher/App/freegcs/Constraints.cpp @@ -24,6 +24,11 @@ #include "Constraints.h" #include +#define DEBUG_DERIVS 0 +#if DEBUG_DERIVS +#include +#endif + namespace GCS { @@ -32,7 +37,7 @@ namespace GCS /////////////////////////////////////// Constraint::Constraint() -: origpvec(0), pvec(0), scale(1.), tag(0) +: origpvec(0), pvec(0), scale(1.), tag(0), remapped(true) { } @@ -45,11 +50,13 @@ void Constraint::redirectParams(MAP_pD_pD redirectionmap) if (it != redirectionmap.end()) pvec[i] = it->second; } + remapped=true; } void Constraint::revertParams() { pvec = origpvec; + remapped=true; } ConstraintType Constraint::getTypeId() @@ -2177,4 +2184,107 @@ double ConstraintEllipticalArcRangeToEndPoints::maxStep(MAP_pD_D &dir, double li return lim; } +// L2LAngle +ConstraintAngleViaPoint::ConstraintAngleViaPoint(Curve &acrv1, Curve &acrv2, Point p, double* angle) +{ + pvec.push_back(angle); + pvec.push_back(p.x); + pvec.push_back(p.y); + acrv1.PushOwnParams(pvec); + acrv2.PushOwnParams(pvec); + crv1 = acrv1.Copy(); + crv2 = acrv2.Copy(); + origpvec = pvec; + remapped=true; + rescale(); +} +ConstraintAngleViaPoint::~ConstraintAngleViaPoint() +{ + delete crv1; crv1 = 0; + delete crv2; crv2 = 0; +} + +void ConstraintAngleViaPoint::ReconstructEverything() +{ + int cnt=0; + cnt++;//skip angle - we have an inline function for that + poa.x = pvec[cnt]; cnt++; + poa.y = pvec[cnt]; cnt++; + crv1->ReconstructOnNewPvec(pvec,cnt); + crv2->ReconstructOnNewPvec(pvec,cnt); + remapped=false; +} + +ConstraintType ConstraintAngleViaPoint::getTypeId() +{ + return AngleViaPoint; +} + +void ConstraintAngleViaPoint::rescale(double coef) +{ + scale = coef * 1.; +} + +double ConstraintAngleViaPoint::error() +{ + if (remapped) ReconstructEverything(); + double ang=*angle(); + Vector2D n1 = crv1->CalculateNormal(poa); + Vector2D n2 = crv2->CalculateNormal(poa); + + //rotate n1 by angle + Vector2D n1r (n1.x*cos(ang) - n1.y*sin(ang), n1.x*sin(ang) + n1.y*cos(ang) ); + + //calculate angle between n1r and n2. Since we have rotated the n1, the angle is the error function. + //for our atan2, y is a dot product (n2) * (n1r rotated ccw by 90 degrees). + // x is a dot product (n2) * (n1r) + double err = atan2(-n2.x*n1r.y+n2.y*n1r.x, n2.x*n1r.x + n2.y*n1r.y); + //essentially, the function is equivalent to atan2(n2)-(atan2(n1)+angle). The only difference is behavior when normals are zero (the intended result is also zero in this case). + return scale * err; +} + +double ConstraintAngleViaPoint::grad(double *param) +{ + //first of all, check that we need to compute anything. + int i; + for( i=0 ; iCalculateNormal(poa); + Vector2D n2 = crv2->CalculateNormal(poa); + + Vector2D dn1 = crv1->CalculateNormal(poa, param); + Vector2D dn2 = crv2->CalculateNormal(poa, param); + deriv -= ( (-dn1.x)*n1.y / pow(n1.length(),2) + dn1.y*n1.x / pow(n1.length(),2) ); + deriv += ( (-dn2.x)*n2.y / pow(n2.length(),2) + dn2.y*n2.x / pow(n2.length(),2) ); + + +//use numeric for testing +#if 0 + double const eps = 0.00001; + double oldparam = *param; + double v0 = this->error(); + *param += eps; + double vr = this->error(); + *param = oldparam - eps; + double vl = this->error(); + *param = oldparam; + //If not nasty, real derivative should be between left one and right one + double numretl = (v0-vl)/eps; + double numretr = (vr-v0)/eps; + assert(deriv <= std::max(numretl,numretr) ); + assert(deriv >= std::min(numretl,numretr) ); +#endif + + return scale * deriv; +} + + } //namespace GCS diff --git a/src/Mod/Sketcher/App/freegcs/Constraints.h b/src/Mod/Sketcher/App/freegcs/Constraints.h index 6cfe395c27..f1ad2aed9d 100644 --- a/src/Mod/Sketcher/App/freegcs/Constraints.h +++ b/src/Mod/Sketcher/App/freegcs/Constraints.h @@ -51,7 +51,8 @@ namespace GCS TangentEllipseLine = 14, InternalAlignmentPoint2Ellipse = 15, EqualMajorAxesEllipse = 16, - EllipticalArcRangeToEndPoints = 17 + EllipticalArcRangeToEndPoints = 17, + AngleViaPoint = 19 //DeepSOIC: skipped 18 for the very unlikely case that tangency via point will be included }; enum InternalAlignmentType { @@ -74,6 +75,7 @@ namespace GCS VEC_pD pvec; double scale; int tag; + bool remapped; //indicates that pvec has changed and saved pointers must be reconstructed (currently used only in AngleViaPoint) public: Constraint(); virtual ~Constraint(){} @@ -429,6 +431,23 @@ namespace GCS virtual double maxStep(MAP_pD_D &dir, double lim=1.); }; + class ConstraintAngleViaPoint : public Constraint + { + private: + inline double* angle() { return pvec[0]; }; + Curve* crv1; + Curve* crv2;//warning: need to be reconstructed if pvec was redirected (test remapped variable before use!) + Point poa;//pot=point of angle //warning: needs to be reconstructed if pvec was redirected (test remapped variable before use!) + void ReconstructEverything(); + public: + ConstraintAngleViaPoint(Curve &acrv1, Curve &acrv2, Point p, double* angle); + ~ConstraintAngleViaPoint(); + virtual ConstraintType getTypeId(); + virtual void rescale(double coef=1.); + virtual double error(); + virtual double grad(double *); + }; + } //namespace GCS diff --git a/src/Mod/Sketcher/App/freegcs/GCS.cpp b/src/Mod/Sketcher/App/freegcs/GCS.cpp index 64a2f12433..3383dac751 100644 --- a/src/Mod/Sketcher/App/freegcs/GCS.cpp +++ b/src/Mod/Sketcher/App/freegcs/GCS.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include "GCS.h" #include "qp_eq.h" @@ -415,6 +416,13 @@ int System::addConstraintL2LAngle(Point &l1p1, Point &l1p2, return addConstraint(constr); } +int System::addConstraintAngleViaPoint(Curve &crv1, Curve &crv2, Point &p, double *angle, int tagId) +{ + Constraint *constr = new ConstraintAngleViaPoint(crv1, crv2, p, angle); + constr->setTag(tagId); + return addConstraint(constr); +} + int System::addConstraintMidpointOnLine(Line &l1, Line &l2, int tagId) { Constraint *constr = new ConstraintMidpointOnLine(l1, l2); @@ -674,95 +682,6 @@ int System::addConstraintTangent(Ellipse &e, Arc &a, int tagId) return 0; } -int System::addConstraintTangentLine2Arc(Point &p1, Point &p2, Arc &a, int tagId) -{ - addConstraintP2PCoincident(p2, a.start, tagId); - double incrAngle = *(a.startAngle) < *(a.endAngle) ? M_PI/2 : -M_PI/2; - return addConstraintP2PAngle(p1, p2, a.startAngle, incrAngle, tagId); -} - -int System::addConstraintTangentArc2Line(Arc &a, Point &p1, Point &p2, int tagId) -{ - addConstraintP2PCoincident(p1, a.end, tagId); - double incrAngle = *(a.startAngle) < *(a.endAngle) ? M_PI/2 : -M_PI/2; - return addConstraintP2PAngle(p1, p2, a.endAngle, incrAngle, tagId); -} - -int System::addConstraintTangentLine2ArcOfEllipse(Point &p1, Point &p2, Line &l, ArcOfEllipse &a, int tagId) -{ - addConstraintP2PCoincident(p2, a.start, tagId); - return addConstraintTangent(l, a, tagId); -} - -int System::addConstraintTangentArcOfEllipse2Line(ArcOfEllipse &a, Line &l, Point &p1, Point &p2, int tagId) -{ - addConstraintP2PCoincident(p1, a.end, tagId); - return addConstraintTangent(l, a, tagId); -} - -int System::addConstraintTangentCircle2Arc(Circle &c, Arc &a, int tagId) -{ - addConstraintPointOnCircle(a.start, c, tagId); - double dx = *(a.start.x) - *(c.center.x); - double dy = *(a.start.y) - *(c.center.y); - if (dx * cos(*(a.startAngle)) + dy * sin(*(a.startAngle)) > 0) - return addConstraintP2PAngle(c.center, a.start, a.startAngle, 0, tagId); - else - return addConstraintP2PAngle(c.center, a.start, a.startAngle, M_PI, tagId); -} - -int System::addConstraintTangentEllipse2Arc(Ellipse &e, Arc &a, int tagId) -{ - - /*addConstraintPointOnEllipse(a.start, e, tagId); - double dx = *(a.start.x) - *(e.center.x); - double dy = *(a.start.y) - *(e.center.y); - if (dx * cos(*(a.startAngle)) + dy * sin(*(a.startAngle)) > 0) - return addConstraintP2PAngle(e.center, a.start, a.startAngle, 0, tagId); - else - return addConstraintP2PAngle(e.center, a.start, a.startAngle, M_PI, tagId);*/ - return 0; -} - -int System::addConstraintTangentArc2Circle(Arc &a, Circle &c, int tagId) -{ - addConstraintPointOnCircle(a.end, c, tagId); - double dx = *(a.end.x) - *(c.center.x); - double dy = *(a.end.y) - *(c.center.y); - if (dx * cos(*(a.endAngle)) + dy * sin(*(a.endAngle)) > 0) - return addConstraintP2PAngle(c.center, a.end, a.endAngle, 0, tagId); - else - return addConstraintP2PAngle(c.center, a.end, a.endAngle, M_PI, tagId); -} - -int System::addConstraintTangentArc2Ellipse(Arc &a, Ellipse &e, int tagId) -{ - - /*addConstraintPointOnEllipse(a.end, e, tagId); - double dx = *(a.end.x) - *(e.center.x); - double dy = *(a.end.y) - *(e.center.y); - if (dx * cos(*(a.endAngle)) + dy * sin(*(a.endAngle)) > 0) - return addConstraintP2PAngle(e.center, a.end, a.endAngle, 0, tagId); - else - return addConstraintP2PAngle(e.center, a.end, a.endAngle, M_PI, tagId);*/ - return 0; -} - -int System::addConstraintTangentArc2Arc(Arc &a1, bool reverse1, Arc &a2, bool reverse2, - int tagId) -{ - Point &p1 = reverse1 ? a1.start : a1.end; - Point &p2 = reverse2 ? a2.end : a2.start; - addConstraintP2PCoincident(p1, p2, tagId); - - double *angle1 = reverse1 ? a1.startAngle : a1.endAngle; - double *angle2 = reverse2 ? a2.endAngle : a2.startAngle; - if (cos(*angle1) * cos(*angle2) + sin(*angle1) * sin(*angle2) > 0) - return addConstraintEqual(angle1, angle2, tagId); - else - return addConstraintP2PAngle(p2, a2.center, angle1, 0, tagId); -} - int System::addConstraintCircleRadius(Circle &c, double *radius, int tagId) { return addConstraintEqual(c.rad, radius, tagId); @@ -1029,6 +948,53 @@ int System::addConstraintInternalAlignmentEllipseFocus2(ArcOfEllipse &a, Point & return addConstraintInternalAlignmentPoint2Ellipse(a,p1,EllipseFocus2Y,tagId); } +//calculates angle between two curves at point of their intersection p. If two +//points are supplied, p is used for first curve and p2 for second, yielding a +//remote angle computation (this is useful when the endpoints haven't) been +//made coincident yet +double System::calculateAngleViaPoint(Curve &crv1, Curve &crv2, Point &p) + {return calculateAngleViaPoint(crv1, crv2, p, p);} +double System::calculateAngleViaPoint(Curve &crv1, Curve &crv2, Point &p1, Point &p2) +{ + GCS::Vector2D n1 = crv1.CalculateNormal(p1); + GCS::Vector2D n2 = crv2.CalculateNormal(p2); + return atan2(-n2.x*n1.y+n2.y*n1.x, n2.x*n1.x + n2.y*n1.y); +} + +void System::calculateNormalAtPoint(Curve &crv, Point &p, double &rtnX, double &rtnY) +{ + GCS::Vector2D n1 = crv.CalculateNormal(p); + rtnX = n1.x; + rtnY = n1.y; +} + +double System::calculateConstraintErrorByTag(int tagId) +{ + int cnt = 0; //how many constraints have been accumulated + double sqErr = 0.0; //accumulator of squared errors + double err = 0.0;//last computed signed error value + + for (std::vector::const_iterator + constr=clist.begin(); constr != clist.end(); ++constr) { + if ((*constr)->getTag() == tagId){ + err = (*constr)->error(); + sqErr += err*err; + cnt++; + }; + } + switch (cnt) { + case 0: //constraint not found! + return std::numeric_limits::quiet_NaN(); + break; + case 1: + return err; + break; + default: + return sqrt(sqErr/(double)cnt); + } + +} + void System::rescaleConstraint(int id, double coeff) { if (id >= clist.size() || id < 0) @@ -1223,11 +1189,16 @@ int System::solve(bool isFine, Algorithm alg) } if (res == Success) { for (std::set::const_iterator constr=redundant.begin(); - constr != redundant.end(); constr++) - if ((*constr)->error() > XconvergenceFine) { - res = Converged; - return res; - } + constr != redundant.end(); constr++){ + //DeepSOIC: there used to be a comparison of signed error value to + //convergence, which makes no sense. Potentially I fixed bug, and + //chances are low I've broken anything. + double err = (*constr)->error(); + if (err*err > XconvergenceFine) { + res = Converged; + return res; + } + } } return res; } @@ -1652,7 +1623,7 @@ int System::solve(SubSystem *subsysA, SubSystem *subsysB, bool isFine) subsysA->calcResidual(resA); double convergence = isFine ? XconvergenceFine : XconvergenceRough; - int maxIterNumber = MaxIterations * xsize; + int maxIterNumber = MaxIterations; double divergingLim = 1e6*subsysA->error() + 1e12; double mu = 0; diff --git a/src/Mod/Sketcher/App/freegcs/GCS.h b/src/Mod/Sketcher/App/freegcs/GCS.h index f624551253..4c1de001e0 100644 --- a/src/Mod/Sketcher/App/freegcs/GCS.h +++ b/src/Mod/Sketcher/App/freegcs/GCS.h @@ -27,6 +27,14 @@ namespace GCS { + /////////////////////////////////////// + // BFGS Solver parameters + /////////////////////////////////////// + #define XconvergenceRough 1e-8 + #define XconvergenceFine 1e-10 + #define smallF 1e-20 + #define MaxIterations 100 //Note that the total number of iterations allowed is MaxIterations *xLength + /////////////////////////////////////// // Solver @@ -109,6 +117,8 @@ namespace GCS int addConstraintL2LAngle(Line &l1, Line &l2, double *angle, int tagId=0); int addConstraintL2LAngle(Point &l1p1, Point &l1p2, Point &l2p1, Point &l2p2, double *angle, int tagId=0); + int addConstraintAngleViaPoint(Curve &crv1, Curve &crv2, Point &p, + double *angle, int tagId=0); int addConstraintMidpointOnLine(Line &l1, Line &l2, int tagId=0); int addConstraintMidpointOnLine(Point &l1p1, Point &l1p2, Point &l2p1, Point &l2p2, int tagId=0); @@ -149,16 +159,7 @@ namespace GCS int addConstraintTangent(Arc &a1, Arc &a2, int tagId=0); int addConstraintTangent(Circle &c, Arc &a, int tagId=0); int addConstraintTangent(Ellipse &e, Arc &a, int tagId=0); - int addConstraintTangentLine2ArcOfEllipse(Point &p1, Point &p2, Line &l, ArcOfEllipse &a, int tagId=0); - int addConstraintTangentArcOfEllipse2Line(ArcOfEllipse &a, Line &l, Point &p1, Point &p2, int tagId=0); - int addConstraintTangentLine2Arc(Point &p1, Point &p2, Arc &a, int tagId=0); - int addConstraintTangentArc2Line(Arc &a, Point &p1, Point &p2, int tagId=0); - int addConstraintTangentCircle2Arc(Circle &c, Arc &a, int tagId=0); - int addConstraintTangentEllipse2Arc(Ellipse &e, Arc &a, int tagId=0); - int addConstraintTangentArc2Circle(Arc &a, Circle &c, int tagId=0); - int addConstraintTangentArc2Ellipse(Arc &a, Ellipse &e, int tagId=0); - int addConstraintTangentArc2Arc(Arc &a1, bool reverse1, Arc &a2, bool reverse2, - int tagId=0); + int addConstraintCircleRadius(Circle &c, double *radius, int tagId=0); int addConstraintEllipseAngleXU(Ellipse &e, double *angle, int tagId=0); int addConstraintArcRadius(Arc &a, double *radius, int tagId=0); @@ -183,6 +184,17 @@ namespace GCS int addConstraintInternalAlignmentEllipseMinorDiameter(ArcOfEllipse &a, Point &p1, Point &p2, int tagId=0); int addConstraintInternalAlignmentEllipseFocus1(ArcOfEllipse &a, Point &p1, int tagId=0); int addConstraintInternalAlignmentEllipseFocus2(ArcOfEllipse &a, Point &p1, int tagId=0); + + double calculateAngleViaPoint(Curve &crv1, Curve &crv2, Point &p); + double calculateAngleViaPoint(Curve &crv1, Curve &crv2, Point &p1, Point &p2); + void calculateNormalAtPoint(Curve &crv, Point &p, double &rtnX, double &rtnY); + + // Calculates errors of all constraints which have a tag equal to + // the one supplied. Individual errors are summed up using RMS. + // If none are found, NAN is returned + // If there's only one, a signed value is returned. + // Effectively, it calculates the error of a UI constraint + double calculateConstraintErrorByTag(int tagId); void rescaleConstraint(int id, double coeff); @@ -197,6 +209,8 @@ namespace GCS void applySolution(); void undoSolution(); + double getFinePrecision(){ return XconvergenceFine;}//FIXME: looks like XconvergenceFine is not the solver precision, at least in DogLeg slover. + int diagnose(); int dofsNumber() { return hasDiagnosis ? dofs : -1; } void getConflicting(VEC_I &conflictingOut) const @@ -205,13 +219,6 @@ namespace GCS { redundantOut = hasDiagnosis ? redundantTags : VEC_I(0); } }; - /////////////////////////////////////// - // BFGS Solver parameters - /////////////////////////////////////// - #define XconvergenceRough 1e-8 - #define XconvergenceFine 1e-10 - #define smallF 1e-20 - #define MaxIterations 100 //Note that the total number of iterations allowed is MaxIterations *xLength /////////////////////////////////////// // Helper elements diff --git a/src/Mod/Sketcher/App/freegcs/Geo.cpp b/src/Mod/Sketcher/App/freegcs/Geo.cpp new file mode 100644 index 0000000000..4a3e19b816 --- /dev/null +++ b/src/Mod/Sketcher/App/freegcs/Geo.cpp @@ -0,0 +1,279 @@ + +#define DEBUG_DERIVS 0 +#if DEBUG_DERIVS +#include +#endif + +#include "Geo.h" +namespace GCS{ + + +Vector2D Line::CalculateNormal(Point &p, double* derivparam) +{ + Vector2D p1v(*p1.x, *p1.y); + Vector2D p2v(*p2.x, *p2.y); + + Vector2D ret(0.0, 0.0); + if(derivparam){ + if(derivparam==this->p1.x){ + ret.y += -1.0; + //ret.x += 0; + }; + if(derivparam==this->p1.y){ + //ret.y += 0; + ret.x += 1.0; + }; + if(derivparam==this->p2.x){ + ret.y += 1.0; + //ret.x += 0; + }; + if(derivparam==this->p2.y){ + //ret.y += 0; + ret.x += -1.0; + }; + } else { + ret.x = -(p2v.y - p1v.y); + ret.y = (p2v.x - p1v.x); + }; + + return ret; +} + +int Line::PushOwnParams(VEC_pD &pvec) +{ + int cnt=0; + pvec.push_back(p1.x); cnt++; + pvec.push_back(p1.y); cnt++; + pvec.push_back(p2.x); cnt++; + pvec.push_back(p2.y); cnt++; + return cnt; +} +void Line::ReconstructOnNewPvec(VEC_pD &pvec, int &cnt) +{ + p1.x=pvec[cnt]; cnt++; + p1.y=pvec[cnt]; cnt++; + p2.x=pvec[cnt]; cnt++; + p2.y=pvec[cnt]; cnt++; +} +Line* Line::Copy() +{ + Line* crv = new Line(*this); + return crv; +} + + +//---------------circle + +Vector2D Circle::CalculateNormal(Point &p, double* derivparam) +{ + Vector2D cv (*center.x, *center.y); + Vector2D pv (*p.x, *p.y); + + Vector2D ret(0.0, 0.0); + if(derivparam){ + if (derivparam == center.x) { + ret.x += -1; + ret.y += 0; + }; + if (derivparam == center.y) { + ret.x += 0; + ret.y += -1; + }; + if (derivparam == p.x) { + ret.x += +1; + ret.y += 0; + }; + if (derivparam == p.y) { + ret.x += 0; + ret.y += +1; + }; + } else { + ret.x = pv.x - cv.x; + ret.y = pv.y - cv.y; + }; + + return ret; +} + +int Circle::PushOwnParams(VEC_pD &pvec) +{ + int cnt=0; + pvec.push_back(center.x); cnt++; + pvec.push_back(center.y); cnt++; + pvec.push_back(rad); cnt++; + return cnt; +} +void Circle::ReconstructOnNewPvec(VEC_pD &pvec, int &cnt) +{ + center.x=pvec[cnt]; cnt++; + center.y=pvec[cnt]; cnt++; + rad=pvec[cnt]; cnt++; +} +Circle* Circle::Copy() +{ + Circle* crv = new Circle(*this); + return crv; +} + +//------------arc +int Arc::PushOwnParams(VEC_pD &pvec) +{ + int cnt=0; + cnt += Circle::PushOwnParams(pvec); + pvec.push_back(start.x); cnt++; + pvec.push_back(start.y); cnt++; + pvec.push_back(end.x); cnt++; + pvec.push_back(end.y); cnt++; + pvec.push_back(startAngle); cnt++; + pvec.push_back(endAngle); cnt++; + return cnt; +} +void Arc::ReconstructOnNewPvec(VEC_pD &pvec, int &cnt) +{ + Circle::ReconstructOnNewPvec(pvec,cnt); + start.x=pvec[cnt]; cnt++; + start.y=pvec[cnt]; cnt++; + end.x=pvec[cnt]; cnt++; + end.y=pvec[cnt]; cnt++; + startAngle=pvec[cnt]; cnt++; + endAngle=pvec[cnt]; cnt++; +} +Arc* Arc::Copy() +{ + Arc* crv = new Arc(*this); + return crv; +} + + +//--------------ellipse +Vector2D Ellipse::CalculateNormal(Point &p, double* derivparam) +{ + Vector2D cv (*center.x, *center.y); + Vector2D f1v (*focus1X, *focus1Y); + Vector2D pv (*p.x, *p.y); + + Vector2D ret(0.0, 0.0); + + Vector2D f2v ( 2*cv.x - f1v.x, 2*cv.y - f1v.y ); //position of focus2 + if(derivparam){ + + Vector2D dc; + Vector2D df1; + Vector2D dp; + + if (derivparam == center.x) dc.x = 1.0; + if (derivparam == center.y) dc.y = 1.0; + if (derivparam == focus1X) df1.x = 1.0; + if (derivparam == focus1Y) df1.y = 1.0; + if (derivparam == p.x) dp.x = 1.0; + if (derivparam == p.y) dp.y = 1.0; + //todo: exit if all are zero + + Vector2D pf1 = Vector2D(pv.x - f1v.x, pv.y - f1v.y);//same as during error function calculation. I reuse the values during derivative calculation + Vector2D pf2 = Vector2D(pv.x - f2v.x, pv.y - f2v.y); + Vector2D pf1e = pf1.getNormalized(); + Vector2D pf2e = pf2.getNormalized(); + + Vector2D df2 (2*dc.x - df1.x, 2*dc.y - df1.y ); + + Vector2D dpf1 (dp.x - df1.x, dp.y - df1.y);//derivative before normalization + Vector2D dpf1e (dpf1.x/pf1.length(), dpf1.y/pf1.length());//first portion of normalization derivative (normalized' = unnormalized'/len + unnormalized*(1/len)') + dpf1e.x += -pf1.x/pow(pf1.length(),2)*(dpf1.x*pf1e.x + dpf1.y*pf1e.y);//second part of normalization dreivative + dpf1e.y += -pf1.y/pow(pf1.length(),2)*(dpf1.x*pf1e.x + dpf1.y*pf1e.y); + + Vector2D dpf2 (dp.x - df2.x, dp.y - df2.y);//same stuff for pf2 + Vector2D dpf2e (dpf2.x/pf2.length(), dpf2.y/pf2.length());//first portion of normalization derivative (normalized' = unnormalized'/len + unnormalized*(1/len)') + dpf2e.x += -pf2.x/pow(pf2.length(),2)*(dpf2.x*pf2e.x + dpf2.y*pf2e.y);//second part of normalization dreivative + dpf2e.y += -pf2.y/pow(pf2.length(),2)*(dpf2.x*pf2e.x + dpf2.y*pf2e.y); + + ret.x = dpf1e.x + dpf2e.x; + ret.y = dpf1e.y + dpf2e.y;//DeepSOIC: derivative calculated manually... error-prone =) Tested, fixed, looks good. + +//numeric derivatives for testing +#if 0 //make sure to enable DEBUG_DERIVS when enabling + double const eps = 0.00001; + double oldparam = *derivparam; + Vector2D v0 = this->CalculateNormal(p); + *derivparam += eps; + Vector2D vr = this->CalculateNormal(p); + *derivparam = oldparam - eps; + Vector2D vl = this->CalculateNormal(p); + *derivparam = oldparam; + //If not nasty, real derivative should be between left one and right one + Vector2D numretl ((v0.x-vl.x)/eps, (v0.y-vl.y)/eps); + Vector2D numretr ((vr.x-v0.x)/eps, (vr.y-v0.y)/eps); + assert(ret.x <= std::max(numretl.x,numretr.x) ); + assert(ret.x >= std::min(numretl.x,numretr.x) ); + assert(ret.y <= std::max(numretl.y,numretr.y) ); + assert(ret.y >= std::min(numretl.y,numretr.y) ); +#endif + + } else { + Vector2D pf1 = Vector2D(pv.x - f1v.x, pv.y - f1v.y); + Vector2D pf2 = Vector2D(pv.x - f2v.x, pv.y - f2v.y); + Vector2D pf1e = pf1.getNormalized(); + Vector2D pf2e = pf2.getNormalized(); + ret.x = pf1e.x + pf2e.x; + ret.y = pf1e.y + pf2e.y; + }; + + return ret; +} + +int Ellipse::PushOwnParams(VEC_pD &pvec) +{ + int cnt=0; + pvec.push_back(center.x); cnt++; + pvec.push_back(center.y); cnt++; + pvec.push_back(focus1X); cnt++; + pvec.push_back(focus1Y); cnt++; + pvec.push_back(radmin); cnt++; + return cnt; +} +void Ellipse::ReconstructOnNewPvec(VEC_pD &pvec, int &cnt) +{ + center.x=pvec[cnt]; cnt++; + center.y=pvec[cnt]; cnt++; + focus1X=pvec[cnt]; cnt++; + focus1Y=pvec[cnt]; cnt++; + radmin=pvec[cnt]; cnt++; +} +Ellipse* Ellipse::Copy() +{ + Ellipse* crv = new Ellipse(*this); + return crv; +} + + +//---------------arc of ellipse +int ArcOfEllipse::PushOwnParams(VEC_pD &pvec) +{ + int cnt=0; + cnt += Ellipse::PushOwnParams(pvec); + pvec.push_back(start.x); cnt++; + pvec.push_back(start.y); cnt++; + pvec.push_back(end.x); cnt++; + pvec.push_back(end.y); cnt++; + pvec.push_back(startAngle); cnt++; + pvec.push_back(endAngle); cnt++; + return cnt; + +} +void ArcOfEllipse::ReconstructOnNewPvec(VEC_pD &pvec, int &cnt) +{ + Ellipse::ReconstructOnNewPvec(pvec,cnt); + start.x=pvec[cnt]; cnt++; + start.y=pvec[cnt]; cnt++; + end.x=pvec[cnt]; cnt++; + end.y=pvec[cnt]; cnt++; + startAngle=pvec[cnt]; cnt++; + endAngle=pvec[cnt]; cnt++; +} +ArcOfEllipse* ArcOfEllipse::Copy() +{ + ArcOfEllipse* crv = new ArcOfEllipse(*this); + return crv; +} + + +}//namespace GCS diff --git a/src/Mod/Sketcher/App/freegcs/Geo.h b/src/Mod/Sketcher/App/freegcs/Geo.h index a17f4e73ae..fa45de3512 100644 --- a/src/Mod/Sketcher/App/freegcs/Geo.h +++ b/src/Mod/Sketcher/App/freegcs/Geo.h @@ -23,13 +23,32 @@ #ifndef FREEGCS_GEO_H #define FREEGCS_GEO_H +#include +#include "Util.h" + namespace GCS { + class Vector2D /* DeepSOIC: I tried to reuse Base::Vector2D by #include , + * but I failed to resolve bullshit compilation errors that arose in the process, so... + * Anyway, the benefit is that solver has less dependencies on FreeCAD and can be + * stripped off easier. + * I could have used Eigen's Vector2f, but I found it overblown and too complex to use. + */ + { + public: + Vector2D(){x=0; y=0;} + Vector2D(double x, double y) {this->x = x; this->y = y;} + double x; + double y; + double length() {return sqrt(x*x + y*y);} + + //unlike other vectors in FreeCAD, this normalization creates a new vector instead of modifying existing one. + Vector2D getNormalized(){double l=length(); if(l==0.0) l=1.0; return Vector2D(x/l,y/l);} //returns zero vector if the original is zero. + }; /////////////////////////////////////// // Geometries /////////////////////////////////////// - class Point { public: @@ -38,56 +57,97 @@ namespace GCS double *y; }; - class Line + class Curve //a base class for all curve-based objects (line, circle/arc, ellipse/arc) + { + public: + + //returns normal vector. The vector should point to the left when one + //walks along the curve from start to end. Ellipses and circles are + //assumed to be walked counterclockwise, so the vector should point + //into the shape. + + //derivparam is a pointer to a curve parameter to compute the + //derivative for. if derivparam is nullptr, the actual normal vector is + //returned, otherwise a derivative of normal vector by *derivparam is + //returned + virtual Vector2D CalculateNormal(Point &p, double* derivparam = 0) = 0; + + //adds curve's parameters to pvec (used by constraints) + virtual int PushOwnParams(VEC_pD &pvec) = 0; + //recunstruct curve's parameters reading them from pvec starting from index cnt. + //cnt will be incremented by the same value as returned by PushOwnParams() + virtual void ReconstructOnNewPvec (VEC_pD &pvec, int &cnt) = 0; + virtual Curve* Copy() = 0; //DeepSOIC: I haven't found a way to simply copy a curve object provided pointer to a curve object. + }; + + class Line: public Curve { public: Line(){} Point p1; Point p2; + Vector2D CalculateNormal(Point &p, double* derivparam = 0); + virtual int PushOwnParams(VEC_pD &pvec); + virtual void ReconstructOnNewPvec (VEC_pD &pvec, int &cnt); + virtual Line* Copy(); }; - class Arc - { - public: - Arc(){startAngle=0;endAngle=0;rad=0;} - double *startAngle; - double *endAngle; - double *rad; - Point start; - Point end; - Point center; - }; - - class Circle + class Circle: public Curve { public: Circle(){rad = 0;} Point center; double *rad; + Vector2D CalculateNormal(Point &p, double* derivparam = 0); + virtual int PushOwnParams(VEC_pD &pvec); + virtual void ReconstructOnNewPvec (VEC_pD &pvec, int &cnt); + virtual Circle* Copy(); + }; + + class Arc: public Circle + { + public: + Arc(){startAngle=0;endAngle=0;rad=0;} + double *startAngle; + double *endAngle; + //double *rad; //inherited + Point start; + Point end; + //Point center; //inherited + virtual int PushOwnParams(VEC_pD &pvec); + virtual void ReconstructOnNewPvec (VEC_pD &pvec, int &cnt); + virtual Arc* Copy(); }; - class Ellipse + class Ellipse: public Curve { public: Ellipse(){ radmin = 0;} Point center; - double *focus1X; //+u + double *focus1X; double *focus1Y; double *radmin; + Vector2D CalculateNormal(Point &p, double* derivparam = 0); + virtual int PushOwnParams(VEC_pD &pvec); + virtual void ReconstructOnNewPvec (VEC_pD &pvec, int &cnt); + virtual Ellipse* Copy(); }; - class ArcOfEllipse + class ArcOfEllipse: public Ellipse { public: ArcOfEllipse(){startAngle=0;endAngle=0;radmin = 0;} double *startAngle; double *endAngle; - double *radmin; + //double *radmin; //inherited Point start; Point end; - Point center; - double *focus1X; //+u - double *focus1Y; + //Point center; //inherited + //double *focus1X; //inherited + //double *focus1Y; //inherited + virtual int PushOwnParams(VEC_pD &pvec); + virtual void ReconstructOnNewPvec (VEC_pD &pvec, int &cnt); + virtual ArcOfEllipse* Copy(); }; } //namespace GCS diff --git a/src/Mod/Sketcher/Gui/CommandConstraints.cpp b/src/Mod/Sketcher/Gui/CommandConstraints.cpp index 9732335191..40200c2d2c 100644 --- a/src/Mod/Sketcher/Gui/CommandConstraints.cpp +++ b/src/Mod/Sketcher/Gui/CommandConstraints.cpp @@ -38,6 +38,7 @@ #include #include +#include #include "ViewProviderSketch.h" #include "ui_InsertDatum.h" @@ -1602,6 +1603,15 @@ CmdSketcherConstrainTangent::CmdSketcherConstrainTangent() eType = ForEdit; } +//helper function +bool IsPointAlreadyOnCurve(int GeoIdCurve, int GeoIdPoint, Sketcher::PointPos PosIdPoint, Sketcher::SketchObject* Obj) +{ + //TODO: make smarter + Base::Vector3d p = Obj->getPoint(GeoIdPoint, PosIdPoint); + return Obj->isPointOnCurve(GeoIdCurve, p.x, p.y); + +} + void CmdSketcherConstrainTangent::activated(int iMsg) { // get the selection @@ -1618,211 +1628,256 @@ void CmdSketcherConstrainTangent::activated(int iMsg) const std::vector &SubNames = selection[0].getSubNames(); Sketcher::SketchObject* Obj = dynamic_cast(selection[0].getObject()); - if (SubNames.size() != 2) { - QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), - QObject::tr("Select exactly two entities from the sketch.")); - return; - } + if (SubNames.size() != 2 && SubNames.size() != 3) + goto ExitWithMessage; - int GeoId1, GeoId2; - Sketcher::PointPos PosId1, PosId2; + int GeoId1, GeoId2, GeoId3; + Sketcher::PointPos PosId1, PosId2, PosId3; getIdsFromName(SubNames[0], Obj, GeoId1, PosId1); getIdsFromName(SubNames[1], Obj, GeoId2, PosId2); if (checkBothExternal(GeoId1, GeoId2)) return; + if (SubNames.size() == 3) { + getIdsFromName(SubNames[2], Obj, GeoId3, PosId3); + //let's sink the point to be GeoId3. We want to keep the order the two curves have been selected in. + if ( isVertex(GeoId1, PosId1) ){ + std::swap(GeoId1,GeoId2); + std::swap(PosId1,PosId2); + }; + if ( isVertex(GeoId2, PosId2) ){ + std::swap(GeoId2,GeoId3); + std::swap(PosId2,PosId3); + }; - if (isVertex(GeoId1,PosId1) && isVertex(GeoId2,PosId2)) { // tangency at common point + if (isEdge(GeoId1, PosId1) && isEdge(GeoId2, PosId2) && isVertex(GeoId3, PosId3)) { + double ActAngle = 0.0; + + openCommand("add angle constraint"); + + //add missing point-on-object constraints + if(! IsPointAlreadyOnCurve(GeoId1, GeoId3, PosId3, Obj)){ + Gui::Command::doCommand( + Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d)) ", + selection[0].getFeatName(),GeoId3,PosId3,GeoId1); + }; + if(! IsPointAlreadyOnCurve(GeoId2, GeoId3, PosId3, Obj)){ + Gui::Command::doCommand( + Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d)) ", + selection[0].getFeatName(),GeoId3,PosId3,GeoId2); + }; + + if(! IsPointAlreadyOnCurve(GeoId1, GeoId3, PosId3, Obj)){//FIXME: it's a good idea to add a check if the sketch is solved + Gui::Command::doCommand( + Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d)) ", + selection[0].getFeatName(),GeoId3,PosId3,GeoId1); + }; + + + Gui::Command::doCommand( + Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('TangentViaPoint',%d,%d,%d,%d)) ", + selection[0].getFeatName(),GeoId1,GeoId2,GeoId3,PosId3); + commitCommand(); - if (isSimpleVertex(Obj, GeoId1, PosId1) || - isSimpleVertex(Obj, GeoId2, PosId2)) { - QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), - QObject::tr("Cannot add a tangency constraint at an unconnected point!")); return; - } - - const Part::Geometry *geom1 = Obj->getGeometry(GeoId1); - const Part::Geometry *geom2 = Obj->getGeometry(GeoId2); - - if( geom1 && geom2 && - ( geom1->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId() || - geom2->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId() )){ - - if(geom1->getTypeId() != Part::GeomArcOfEllipse::getClassTypeId()) - std::swap(GeoId1,GeoId2); - - // GeoId1 is the arc of ellipse - geom1 = Obj->getGeometry(GeoId1); - geom2 = Obj->getGeometry(GeoId2); - - if( geom2->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId() || - geom2->getTypeId() == Part::GeomArcOfCircle::getClassTypeId() ) { - - const Part::GeomArcOfEllipse *aoe = static_cast(geom1); - - Base::Vector3d center=aoe->getCenter(); - double majord=aoe->getMajorRadius(); - double minord=aoe->getMinorRadius(); - double phi=aoe->getAngleXU(); - - Base::Vector3d center2; - - if( geom2->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId()) - center2= (static_cast(geom2))->getCenter(); - else if( geom2->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) - center2= (static_cast(geom2))->getCenter(); - - Base::Vector3d direction=center2-center; - double tapprox=atan2(direction.y,direction.x)-phi; // we approximate the eccentric anomally by the polar - - Base::Vector3d PoE = Base::Vector3d(center.x+majord*cos(tapprox)*cos(phi)-minord*sin(tapprox)*sin(phi), - center.y+majord*cos(tapprox)*sin(phi)+minord*sin(tapprox)*cos(phi), 0); - - Base::Vector3d perp = Base::Vector3d(direction.y,-direction.x); - - Base::Vector3d endpoint = PoE+perp; - - int currentgeoid= Obj->getHighestCurveIndex(); - - openCommand("add tangent constraint"); - - try { - // do points coincident - Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Coincident',%d,%d,%d,%d)) ", - selection[0].getFeatName(),GeoId1,PosId1,GeoId2,PosId2); - - // Add a construction line - Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.addGeometry(Part.Line(App.Vector(%f,%f,0),App.Vector(%f,%f,0)))", - selection[0].getFeatName(), - PoE.x,PoE.y,endpoint.x,endpoint.y); - - Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.toggleConstruction(%d) ",Obj->getNameInDocument(),currentgeoid+1); - - Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Distance',%d,%f)) ", - selection[0].getFeatName(),currentgeoid+1,direction.Length()); - - // Point on first object - Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d)) ", - selection[0].getFeatName(),currentgeoid+1,Sketcher::start,GeoId1); // constrain major axis - // Point on second object - Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d)) ", - selection[0].getFeatName(),currentgeoid+1,Sketcher::start,GeoId2); // constrain major axis - // tangent to first object - Gui::Command::doCommand( - Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Tangent',%d,%d)) ", - selection[0].getFeatName(),currentgeoid+1,GeoId1); - // tangent to second object - Gui::Command::doCommand( - Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Tangent',%d,%d)) ", - selection[0].getFeatName(),currentgeoid+1,GeoId2); - - } - catch (const Base::Exception& e) { - Base::Console().Error("%s\n", e.what()); - Gui::Command::abortCommand(); - Gui::Command::updateActive(); + + }; + + } else if (SubNames.size() == 2) { + if (isVertex(GeoId1,PosId1) && isVertex(GeoId2,PosId2)) { // tangency at common point + + if (isSimpleVertex(Obj, GeoId1, PosId1) || + isSimpleVertex(Obj, GeoId2, PosId2)) { + QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), + QObject::tr("Cannot add a tangency constraint at an unconnected point!")); + return; + } + + const Part::Geometry *geom1 = Obj->getGeometry(GeoId1); + const Part::Geometry *geom2 = Obj->getGeometry(GeoId2); + + if( geom1 && geom2 && + ( geom1->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId() || + geom2->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId() )){ + + if(geom1->getTypeId() != Part::GeomArcOfEllipse::getClassTypeId()) + std::swap(GeoId1,GeoId2); + + // GeoId1 is the arc of ellipse + geom1 = Obj->getGeometry(GeoId1); + geom2 = Obj->getGeometry(GeoId2); + + if( geom2->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId() || + geom2->getTypeId() == Part::GeomArcOfCircle::getClassTypeId() ) { + + const Part::GeomArcOfEllipse *aoe = static_cast(geom1); + + Base::Vector3d center=aoe->getCenter(); + double majord=aoe->getMajorRadius(); + double minord=aoe->getMinorRadius(); + double phi=aoe->getAngleXU(); + + Base::Vector3d center2; + + if( geom2->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId()) + center2= (static_cast(geom2))->getCenter(); + else if( geom2->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) + center2= (static_cast(geom2))->getCenter(); + + Base::Vector3d direction=center2-center; + double tapprox=atan2(direction.y,direction.x)-phi; // we approximate the eccentric anomally by the polar + + Base::Vector3d PoE = Base::Vector3d(center.x+majord*cos(tapprox)*cos(phi)-minord*sin(tapprox)*sin(phi), + center.y+majord*cos(tapprox)*sin(phi)+minord*sin(tapprox)*cos(phi), 0); + + Base::Vector3d perp = Base::Vector3d(direction.y,-direction.x); + + Base::Vector3d endpoint = PoE+perp; + + int currentgeoid= Obj->getHighestCurveIndex(); + + openCommand("add tangent constraint"); + + try { + // do points coincident + Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Coincident',%d,%d,%d,%d)) ", + selection[0].getFeatName(),GeoId1,PosId1,GeoId2,PosId2); + + // Add a construction line + Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.addGeometry(Part.Line(App.Vector(%f,%f,0),App.Vector(%f,%f,0)))", + selection[0].getFeatName(), + PoE.x,PoE.y,endpoint.x,endpoint.y); + + Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.toggleConstruction(%d) ",Obj->getNameInDocument(),currentgeoid+1); + + Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Distance',%d,%f)) ", + selection[0].getFeatName(),currentgeoid+1,direction.Length()); + + // Point on first object + Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d)) ", + selection[0].getFeatName(),currentgeoid+1,Sketcher::start,GeoId1); // constrain major axis + // Point on second object + Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d)) ", + selection[0].getFeatName(),currentgeoid+1,Sketcher::start,GeoId2); // constrain major axis + // tangent to first object + Gui::Command::doCommand( + Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Tangent',%d,%d)) ", + selection[0].getFeatName(),currentgeoid+1,GeoId1); + // tangent to second object + Gui::Command::doCommand( + Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Tangent',%d,%d)) ", + selection[0].getFeatName(),currentgeoid+1,GeoId2); + + } + catch (const Base::Exception& e) { + Base::Console().Error("%s\n", e.what()); + Gui::Command::abortCommand(); + Gui::Command::updateActive(); + return; + } + + commitCommand(); + updateActive(); + getSelection().clearSelection(); return; } - commitCommand(); - updateActive(); - getSelection().clearSelection(); + } + + openCommand("add tangent constraint"); + Gui::Command::doCommand( + Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Tangent',%d,%d,%d,%d)) ", + selection[0].getFeatName(),GeoId1,PosId1,GeoId2,PosId2); + commitCommand(); + updateActive(); + getSelection().clearSelection(); + return; + } + else if ((isVertex(GeoId1,PosId1) && isEdge(GeoId2,PosId2)) || + (isEdge(GeoId1,PosId1) && isVertex(GeoId2,PosId2))) { // tangency point + if (isVertex(GeoId2,PosId2)) { + std::swap(GeoId1,GeoId2); + std::swap(PosId1,PosId2); + } + + if (isSimpleVertex(Obj, GeoId1, PosId1)) { + QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), + QObject::tr("Cannot add a tangency constraint at an unconnected point!")); return; } - - } - openCommand("add tangent constraint"); - Gui::Command::doCommand( - Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Tangent',%d,%d,%d,%d)) ", - selection[0].getFeatName(),GeoId1,PosId1,GeoId2,PosId2); - commitCommand(); - updateActive(); - getSelection().clearSelection(); - return; - } - else if ((isVertex(GeoId1,PosId1) && isEdge(GeoId2,PosId2)) || - (isEdge(GeoId1,PosId1) && isVertex(GeoId2,PosId2))) { // tangency point - if (isVertex(GeoId2,PosId2)) { - std::swap(GeoId1,GeoId2); - std::swap(PosId1,PosId2); + openCommand("add tangent constraint"); + Gui::Command::doCommand( + Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Tangent',%d,%d,%d)) ", + selection[0].getFeatName(),GeoId1,PosId1,GeoId2); + commitCommand(); + updateActive(); + getSelection().clearSelection(); + return; } + else if (isEdge(GeoId1,PosId1) && isEdge(GeoId2,PosId2)) { // simple tangency between GeoId1 and GeoId2 - if (isSimpleVertex(Obj, GeoId1, PosId1)) { - QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), - QObject::tr("Cannot add a tangency constraint at an unconnected point!")); + const Part::Geometry *geom1 = Obj->getGeometry(GeoId1); + const Part::Geometry *geom2 = Obj->getGeometry(GeoId2); + + if( geom1 && geom2 && + ( geom1->getTypeId() == Part::GeomEllipse::getClassTypeId() || + geom2->getTypeId() == Part::GeomEllipse::getClassTypeId() )){ + + if(geom1->getTypeId() != Part::GeomEllipse::getClassTypeId()) + std::swap(GeoId1,GeoId2); + + // GeoId1 is the ellipse + geom1 = Obj->getGeometry(GeoId1); + geom2 = Obj->getGeometry(GeoId2); + + if( geom2->getTypeId() == Part::GeomEllipse::getClassTypeId() || + geom2->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId() || + geom2->getTypeId() == Part::GeomCircle::getClassTypeId() || + geom2->getTypeId() == Part::GeomArcOfCircle::getClassTypeId() ) { + + Gui::Command::openCommand("add tangent constraint via construction element"); + makeTangentToEllipseviaConstructionLine(Obj,geom1,geom2,GeoId1,GeoId2); + getSelection().clearSelection(); + return; + } + } + + if( geom1 && geom2 && + ( geom1->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId() || + geom2->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId() )){ + + if(geom1->getTypeId() != Part::GeomArcOfEllipse::getClassTypeId()) + std::swap(GeoId1,GeoId2); + + // GeoId1 is the arc of ellipse + geom1 = Obj->getGeometry(GeoId1); + geom2 = Obj->getGeometry(GeoId2); + + if( geom2->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId() || + geom2->getTypeId() == Part::GeomCircle::getClassTypeId() || + geom2->getTypeId() == Part::GeomArcOfCircle::getClassTypeId() ) { + + Gui::Command::openCommand("add tangent constraint via construction element"); + makeTangentToArcOfEllipseviaConstructionLine(Obj,geom1,geom2,GeoId1,GeoId2); + getSelection().clearSelection(); + return; + } + + } + + openCommand("add tangent constraint"); + Gui::Command::doCommand( + Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Tangent',%d,%d)) ", + selection[0].getFeatName(),GeoId1,GeoId2); + commitCommand(); + updateActive(); + getSelection().clearSelection(); return; } - openCommand("add tangent constraint"); - Gui::Command::doCommand( - Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Tangent',%d,%d,%d)) ", - selection[0].getFeatName(),GeoId1,PosId1,GeoId2); - commitCommand(); - updateActive(); - getSelection().clearSelection(); - return; - } - else if (isEdge(GeoId1,PosId1) && isEdge(GeoId2,PosId2)) { // simple tangency between GeoId1 and GeoId2 - - const Part::Geometry *geom1 = Obj->getGeometry(GeoId1); - const Part::Geometry *geom2 = Obj->getGeometry(GeoId2); - - if( geom1 && geom2 && - ( geom1->getTypeId() == Part::GeomEllipse::getClassTypeId() || - geom2->getTypeId() == Part::GeomEllipse::getClassTypeId() )){ - - if(geom1->getTypeId() != Part::GeomEllipse::getClassTypeId()) - std::swap(GeoId1,GeoId2); - - // GeoId1 is the ellipse - geom1 = Obj->getGeometry(GeoId1); - geom2 = Obj->getGeometry(GeoId2); - - if( geom2->getTypeId() == Part::GeomEllipse::getClassTypeId() || - geom2->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId() || - geom2->getTypeId() == Part::GeomCircle::getClassTypeId() || - geom2->getTypeId() == Part::GeomArcOfCircle::getClassTypeId() ) { - - Gui::Command::openCommand("add tangent constraint via construction element"); - makeTangentToEllipseviaConstructionLine(Obj,geom1,geom2,GeoId1,GeoId2); - getSelection().clearSelection(); - return; - } - } - - if( geom1 && geom2 && - ( geom1->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId() || - geom2->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId() )){ - - if(geom1->getTypeId() != Part::GeomArcOfEllipse::getClassTypeId()) - std::swap(GeoId1,GeoId2); - - // GeoId1 is the arc of ellipse - geom1 = Obj->getGeometry(GeoId1); - geom2 = Obj->getGeometry(GeoId2); - - if( geom2->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId() || - geom2->getTypeId() == Part::GeomCircle::getClassTypeId() || - geom2->getTypeId() == Part::GeomArcOfCircle::getClassTypeId() ) { - - Gui::Command::openCommand("add tangent constraint via construction element"); - makeTangentToArcOfEllipseviaConstructionLine(Obj,geom1,geom2,GeoId1,GeoId2); - getSelection().clearSelection(); - return; - } - - } - - openCommand("add tangent constraint"); - Gui::Command::doCommand( - Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Tangent',%d,%d)) ", - selection[0].getFeatName(),GeoId1,GeoId2); - commitCommand(); - updateActive(); - getSelection().clearSelection(); - return; } +ExitWithMessage: QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), QObject::tr("Select exactly two entities from the sketch.")); return; @@ -2024,6 +2079,7 @@ CmdSketcherConstrainAngle::CmdSketcherConstrainAngle() eType = ForEdit; } + void CmdSketcherConstrainAngle::activated(int iMsg) { // get the selection @@ -2040,114 +2096,166 @@ void CmdSketcherConstrainAngle::activated(int iMsg) const std::vector &SubNames = selection[0].getSubNames(); Sketcher::SketchObject* Obj = dynamic_cast(selection[0].getObject()); - if (SubNames.size() < 1 || SubNames.size() > 2) { - QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), - QObject::tr("Select one or two lines from the sketch.")); - return; + if (SubNames.size() < 1 || SubNames.size() > 3) { + goto MessageAndExit; } - int GeoId1, GeoId2=Constraint::GeoUndef; - Sketcher::PointPos PosId1, PosId2=Sketcher::none; + int GeoId1, GeoId2=Constraint::GeoUndef, GeoId3 = Constraint::GeoUndef; + Sketcher::PointPos PosId1, PosId2=Sketcher::none, PosId3 = Sketcher::none; getIdsFromName(SubNames[0], Obj, GeoId1, PosId1); - if (SubNames.size() == 2) + if (SubNames.size() > 1) getIdsFromName(SubNames[1], Obj, GeoId2, PosId2); + if (SubNames.size() > 2) + getIdsFromName(SubNames[2], Obj, GeoId3, PosId3); - if (checkBothExternal(GeoId1, GeoId2)) - return; - else if (isVertex(GeoId1,PosId1) && isEdge(GeoId2,PosId2)) { - std::swap(GeoId1,GeoId2); - std::swap(PosId1,PosId2); - } + if (SubNames.size() == 3){//standalone implementation of angle-via-point - if (isEdge(GeoId2,PosId2)) { // line to line angle + //let's sink the point to be GeoId3. We want to keep the order the two curves have been selected in. + if ( isVertex(GeoId1, PosId1) ){ + std::swap(GeoId1,GeoId2); + std::swap(PosId1,PosId2); + }; + if ( isVertex(GeoId2, PosId2) ){ + std::swap(GeoId2,GeoId3); + std::swap(PosId2,PosId3); + }; - const Part::Geometry *geom1 = Obj->getGeometry(GeoId1); - const Part::Geometry *geom2 = Obj->getGeometry(GeoId2); - if (geom1->getTypeId() == Part::GeomLineSegment::getClassTypeId() && - geom2->getTypeId() == Part::GeomLineSegment::getClassTypeId()) { - const Part::GeomLineSegment *lineSeg1 = dynamic_cast(geom1); - const Part::GeomLineSegment *lineSeg2 = dynamic_cast(geom2); + if (isEdge(GeoId1, PosId1) && isEdge(GeoId2, PosId2) && isVertex(GeoId3, PosId3)) { + double ActAngle = 0.0; - // find the two closest line ends - Sketcher::PointPos PosId1,PosId2; - Base::Vector3d p1a = lineSeg1->getStartPoint(); - Base::Vector3d p1b = lineSeg1->getEndPoint(); - Base::Vector3d p2a = lineSeg2->getStartPoint(); - Base::Vector3d p2b = lineSeg2->getEndPoint(); - double length = DBL_MAX; - for (int i=0; i <= 1; i++) { - for (int j=0; j <= 1; j++) { - double tmp = ((j?p2a:p2b)-(i?p1a:p1b)).Length(); - if (tmp < length) { - length = tmp; - PosId1 = i ? Sketcher::start : Sketcher::end; - PosId2 = j ? Sketcher::start : Sketcher::end; + openCommand("add angle constraint"); + + //add missing point-on-object constraints + if(! IsPointAlreadyOnCurve(GeoId1, GeoId3, PosId3, Obj)){ + Gui::Command::doCommand( + Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d)) ", + selection[0].getFeatName(),GeoId3,PosId3,GeoId1); + }; + if(! IsPointAlreadyOnCurve(GeoId2, GeoId3, PosId3, Obj)){ + Gui::Command::doCommand( + Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d)) ", + selection[0].getFeatName(),GeoId3,PosId3,GeoId2); + }; + if(! IsPointAlreadyOnCurve(GeoId1, GeoId3, PosId3, Obj)){//FIXME: it's a good idea to add a check if the sketch is solved + Gui::Command::doCommand( + Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d)) ", + selection[0].getFeatName(),GeoId3,PosId3,GeoId1); + }; + + //assuming point-on-curves have been solved, calculate the angle. + //DeepSOIC: this may be slow, but I wanted to reuse the conversion from Geometry to GCS shapes that is done in Sketch + Base::Vector3d p = Obj->getPoint(GeoId3, PosId3 ); + ActAngle = Obj->calculateAngleViaPoint(GeoId1,GeoId2,p.x,p.y); + + Gui::Command::doCommand( + Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('AngleViaPoint',%d,%d,%d,%d,%f)) ", + selection[0].getFeatName(),GeoId1,GeoId2,GeoId3,PosId3,ActAngle); + commitCommand(); + + finishDistanceConstraint(this, Obj); + return; + }; + + } else if (SubNames.size() < 3) { + + if (checkBothExternal(GeoId1, GeoId2)) + return; + if (isVertex(GeoId1,PosId1) && isEdge(GeoId2,PosId2)) { + std::swap(GeoId1,GeoId2); + std::swap(PosId1,PosId2); + } + + if (isEdge(GeoId2,PosId2)) { // line to line angle + + const Part::Geometry *geom1 = Obj->getGeometry(GeoId1); + const Part::Geometry *geom2 = Obj->getGeometry(GeoId2); + if (geom1->getTypeId() == Part::GeomLineSegment::getClassTypeId() && + geom2->getTypeId() == Part::GeomLineSegment::getClassTypeId()) { + const Part::GeomLineSegment *lineSeg1 = dynamic_cast(geom1); + const Part::GeomLineSegment *lineSeg2 = dynamic_cast(geom2); + + // find the two closest line ends + Sketcher::PointPos PosId1,PosId2; + Base::Vector3d p1a = lineSeg1->getStartPoint(); + Base::Vector3d p1b = lineSeg1->getEndPoint(); + Base::Vector3d p2a = lineSeg2->getStartPoint(); + Base::Vector3d p2b = lineSeg2->getEndPoint(); + double length = DBL_MAX; + for (int i=0; i <= 1; i++) { + for (int j=0; j <= 1; j++) { + double tmp = ((j?p2a:p2b)-(i?p1a:p1b)).Length(); + if (tmp < length) { + length = tmp; + PosId1 = i ? Sketcher::start : Sketcher::end; + PosId2 = j ? Sketcher::start : Sketcher::end; + } } } - } - Base::Vector3d dir1 = ((PosId1 == Sketcher::start) ? 1. : -1.) * - (lineSeg1->getEndPoint()-lineSeg1->getStartPoint()); - Base::Vector3d dir2 = ((PosId2 == Sketcher::start) ? 1. : -1.) * - (lineSeg2->getEndPoint()-lineSeg2->getStartPoint()); + Base::Vector3d dir1 = ((PosId1 == Sketcher::start) ? 1. : -1.) * + (lineSeg1->getEndPoint()-lineSeg1->getStartPoint()); + Base::Vector3d dir2 = ((PosId2 == Sketcher::start) ? 1. : -1.) * + (lineSeg2->getEndPoint()-lineSeg2->getStartPoint()); - // check if the two lines are parallel, in this case an angle is not possible - Base::Vector3d dir3 = dir1 % dir2; - if (dir3.Length() < Precision::Intersection()) { - Base::Vector3d dist = (p1a - p2a) % dir1; - if (dist.Sqr() > Precision::Intersection()) { - QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Parallel lines"), - QObject::tr("An angle constraint cannot be set for two parallel lines.")); - return; + // check if the two lines are parallel, in this case an angle is not possible + Base::Vector3d dir3 = dir1 % dir2; + if (dir3.Length() < Precision::Intersection()) { + Base::Vector3d dist = (p1a - p2a) % dir1; + if (dist.Sqr() > Precision::Intersection()) { + QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Parallel lines"), + QObject::tr("An angle constraint cannot be set for two parallel lines.")); + return; + } } + + double ActAngle = atan2(-dir1.y*dir2.x+dir1.x*dir2.y, + dir1.x*dir2.x+dir1.y*dir2.y); + if (ActAngle < 0) { + ActAngle *= -1; + std::swap(GeoId1,GeoId2); + std::swap(PosId1,PosId2); + } + + openCommand("add angle constraint"); + Gui::Command::doCommand( + Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Angle',%d,%d,%d,%d,%f)) ", + selection[0].getFeatName(),GeoId1,PosId1,GeoId2,PosId2,ActAngle); + commitCommand(); + + finishDistanceConstraint(this, Obj); + return; + } + } else if (isEdge(GeoId1,PosId1)) { // line angle + if (GeoId1 < 0) { + QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), + GeoId1 < -2 ? QObject::tr("Cannot add an angle constraint on an external geometry!") + : QObject::tr("Cannot add an angle constraint on an axis!")); + return; } - double ActAngle = atan2(-dir1.y*dir2.x+dir1.x*dir2.y, - dir1.x*dir2.x+dir1.y*dir2.y); - if (ActAngle < 0) { - ActAngle *= -1; - std::swap(GeoId1,GeoId2); - std::swap(PosId1,PosId2); + const Part::Geometry *geom = Obj->getGeometry(GeoId1); + if (geom->getTypeId() == Part::GeomLineSegment::getClassTypeId()) { + const Part::GeomLineSegment *lineSeg; + lineSeg = dynamic_cast(geom); + Base::Vector3d dir = lineSeg->getEndPoint()-lineSeg->getStartPoint(); + double ActAngle = atan2(dir.y,dir.x); + + openCommand("add angle constraint"); + Gui::Command::doCommand( + Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Angle',%d,%f)) ", + selection[0].getFeatName(),GeoId1,ActAngle); + commitCommand(); + + finishDistanceConstraint(this, Obj); + return; } - - openCommand("add angle constraint"); - Gui::Command::doCommand( - Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Angle',%d,%d,%d,%d,%f)) ", - selection[0].getFeatName(),GeoId1,PosId1,GeoId2,PosId2,ActAngle); - commitCommand(); - - finishDistanceConstraint(this, Obj); - return; - } - } else if (isEdge(GeoId1,PosId1)) { // line angle - if (GeoId1 < 0) { - QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), - GeoId1 < -2 ? QObject::tr("Cannot add an angle constraint on an external geometry!") - : QObject::tr("Cannot add an angle constraint on an axis!")); - return; } + }; - const Part::Geometry *geom = Obj->getGeometry(GeoId1); - if (geom->getTypeId() == Part::GeomLineSegment::getClassTypeId()) { - const Part::GeomLineSegment *lineSeg; - lineSeg = dynamic_cast(geom); - Base::Vector3d dir = lineSeg->getEndPoint()-lineSeg->getStartPoint(); - double ActAngle = atan2(dir.y,dir.x); - - openCommand("add angle constraint"); - Gui::Command::doCommand( - Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Angle',%d,%f)) ", - selection[0].getFeatName(),GeoId1,ActAngle); - commitCommand(); - - finishDistanceConstraint(this, Obj); - return; - } - } - +MessageAndExit: QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), - QObject::tr("Select exactly one or two lines from the sketch.")); + QObject::tr("Select one or two lines from the sketch. Or select two edges and a point.")); return; } diff --git a/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp b/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp index 5c3a853c32..9b5252c673 100644 --- a/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp +++ b/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp @@ -1208,32 +1208,43 @@ void ViewProviderSketch::moveConstraint(int constNum, const Base::Vector2D &toPo Base::Vector3d p0(0.,0.,0.); if (Constr->Second != Constraint::GeoUndef) { // line to line angle - const Part::Geometry *geo1 = GeoById(geomlist, Constr->First); - const Part::Geometry *geo2 = GeoById(geomlist, Constr->Second); - if (geo1->getTypeId() != Part::GeomLineSegment::getClassTypeId() || - geo2->getTypeId() != Part::GeomLineSegment::getClassTypeId()) - return; - const Part::GeomLineSegment *lineSeg1 = dynamic_cast(geo1); - const Part::GeomLineSegment *lineSeg2 = dynamic_cast(geo2); - - bool flip1 = (Constr->FirstPos == end); - bool flip2 = (Constr->SecondPos == end); - Base::Vector3d dir1 = (flip1 ? -1. : 1.) * (lineSeg1->getEndPoint()-lineSeg1->getStartPoint()); - Base::Vector3d dir2 = (flip2 ? -1. : 1.) * (lineSeg2->getEndPoint()-lineSeg2->getStartPoint()); - Base::Vector3d pnt1 = flip1 ? lineSeg1->getEndPoint() : lineSeg1->getStartPoint(); - Base::Vector3d pnt2 = flip2 ? lineSeg2->getEndPoint() : lineSeg2->getStartPoint(); - - // line-line intersection - { - double det = dir1.x*dir2.y - dir1.y*dir2.x; - if ((det > 0 ? det : -det) < 1e-10) + Base::Vector3d dir1, dir2; + if(Constr->Third == Constraint::GeoUndef) { //angle between two lines + const Part::Geometry *geo1 = GeoById(geomlist, Constr->First); + const Part::Geometry *geo2 = GeoById(geomlist, Constr->Second); + if (geo1->getTypeId() != Part::GeomLineSegment::getClassTypeId() || + geo2->getTypeId() != Part::GeomLineSegment::getClassTypeId()) return; - double c1 = dir1.y*pnt1.x - dir1.x*pnt1.y; - double c2 = dir2.y*pnt2.x - dir2.x*pnt2.y; - double x = (dir1.x*c2 - dir2.x*c1)/det; - double y = (dir1.y*c2 - dir2.y*c1)/det; - p0 = Base::Vector3d(x,y,0); + const Part::GeomLineSegment *lineSeg1 = dynamic_cast(geo1); + const Part::GeomLineSegment *lineSeg2 = dynamic_cast(geo2); + + bool flip1 = (Constr->FirstPos == end); + bool flip2 = (Constr->SecondPos == end); + dir1 = (flip1 ? -1. : 1.) * (lineSeg1->getEndPoint()-lineSeg1->getStartPoint()); + dir2 = (flip2 ? -1. : 1.) * (lineSeg2->getEndPoint()-lineSeg2->getStartPoint()); + Base::Vector3d pnt1 = flip1 ? lineSeg1->getEndPoint() : lineSeg1->getStartPoint(); + Base::Vector3d pnt2 = flip2 ? lineSeg2->getEndPoint() : lineSeg2->getStartPoint(); + + // line-line intersection + { + double det = dir1.x*dir2.y - dir1.y*dir2.x; + if ((det > 0 ? det : -det) < 1e-10) + return;// lines are parallel - constraint unmoveable (DeepSOIC: why?..) + double c1 = dir1.y*pnt1.x - dir1.x*pnt1.y; + double c2 = dir2.y*pnt2.x - dir2.x*pnt2.y; + double x = (dir1.x*c2 - dir2.x*c1)/det; + double y = (dir1.y*c2 - dir2.y*c1)/det; + p0 = Base::Vector3d(x,y,0); + } + } else {//angle-via-point + Base::Vector3d p = edit->ActSketch.getPoint(Constr->Third, Constr->ThirdPos); + p0 = Base::Vector3d(p.x, p.y, 0); + dir1 = edit->ActSketch.calculateNormalAtPoint(Constr->First, p.x, p.y); + dir1.RotateZ(-M_PI/2);//convert to vector of tangency by rotating + dir2 = edit->ActSketch.calculateNormalAtPoint(Constr->Second, p.x, p.y); + dir2.RotateZ(-M_PI/2); } + } else if (Constr->First != Constraint::GeoUndef) { // line angle const Part::Geometry *geo = GeoById(geomlist, Constr->First); if (geo->getTypeId() != Part::GeomLineSegment::getClassTypeId()) @@ -3419,9 +3430,34 @@ Restart: assert(Constr->Second >= -extGeoCount && Constr->Second < intGeoCount); Base::Vector3d pos, relPos; - if (Constr->Type == PointOnObject) { - pos = edit->ActSketch.getPoint(Constr->First, Constr->FirstPos); - relPos = Base::Vector3d(0.f, 1.f, 0.f); + if ( Constr->Type == PointOnObject || + (Constr->Type == Tangent && Constr->Third != Constraint::GeoUndef) || //Tangency via point + (Constr->Type == Tangent && Constr->FirstPos != Sketcher::none) //endpoint-to-curve or endpoint-to-endpoint tangency + ) { + + //find the point of tangency/point that is on object + //just any point among first/second/third should be OK + int ptGeoId; + Sketcher::PointPos ptPosId; + do {//dummy loop to use break =) Maybe goto? + ptGeoId = Constr->First; + ptPosId = Constr->FirstPos; + if (ptPosId != Sketcher::none) break; + ptGeoId = Constr->Second; + ptPosId = Constr->SecondPos; + if (ptPosId != Sketcher::none) break; + ptGeoId = Constr->Third; + ptPosId = Constr->ThirdPos; + if (ptPosId != Sketcher::none) break; + assert(0);//no point found! + } while (false); + pos = edit->ActSketch.getPoint(ptGeoId, ptPosId); + + Base::Vector3d norm = edit->ActSketch.calculateNormalAtPoint(Constr->Second, pos.x, pos.y); + norm.Normalize(); + Base::Vector3d dir = norm; dir.RotateZ(-M_PI/2.0); + + relPos = seekConstraintPosition(pos, norm, dir, 2.5, edit->constrGroup->getChild(i)); dynamic_cast(sep->getChild(CONSTRAINT_SEPARATOR_INDEX_FIRST_TRANSLATION))->abPos = SbVec3f(pos.x, pos.y, zConstr); //Absolute Reference dynamic_cast(sep->getChild(CONSTRAINT_SEPARATOR_INDEX_FIRST_TRANSLATION))->translation = SbVec3f(relPos.x, relPos.y, 0); } @@ -3575,50 +3611,60 @@ Restart: SbVec3f p0; double startangle,range,endangle; if (Constr->Second != Constraint::GeoUndef) { - const Part::Geometry *geo1 = GeoById(*geomlist, Constr->First); - const Part::Geometry *geo2 = GeoById(*geomlist, Constr->Second); - if (geo1->getTypeId() != Part::GeomLineSegment::getClassTypeId() || - geo2->getTypeId() != Part::GeomLineSegment::getClassTypeId()) - break; - const Part::GeomLineSegment *lineSeg1 = dynamic_cast(geo1); - const Part::GeomLineSegment *lineSeg2 = dynamic_cast(geo2); + Base::Vector3d dir1, dir2; + if(Constr->Third == Constraint::GeoUndef) { //angle between two lines + const Part::Geometry *geo1 = GeoById(*geomlist, Constr->First); + const Part::Geometry *geo2 = GeoById(*geomlist, Constr->Second); + if (geo1->getTypeId() != Part::GeomLineSegment::getClassTypeId() || + geo2->getTypeId() != Part::GeomLineSegment::getClassTypeId()) + break; + const Part::GeomLineSegment *lineSeg1 = dynamic_cast(geo1); + const Part::GeomLineSegment *lineSeg2 = dynamic_cast(geo2); - bool flip1 = (Constr->FirstPos == end); - bool flip2 = (Constr->SecondPos == end); - Base::Vector3d dir1 = (flip1 ? -1. : 1.) * (lineSeg1->getEndPoint()-lineSeg1->getStartPoint()); - Base::Vector3d dir2 = (flip2 ? -1. : 1.) * (lineSeg2->getEndPoint()-lineSeg2->getStartPoint()); - Base::Vector3d pnt1 = flip1 ? lineSeg1->getEndPoint() : lineSeg1->getStartPoint(); - Base::Vector3d pnt2 = flip2 ? lineSeg2->getEndPoint() : lineSeg2->getStartPoint(); + bool flip1 = (Constr->FirstPos == end); + bool flip2 = (Constr->SecondPos == end); + dir1 = (flip1 ? -1. : 1.) * (lineSeg1->getEndPoint()-lineSeg1->getStartPoint()); + dir2 = (flip2 ? -1. : 1.) * (lineSeg2->getEndPoint()-lineSeg2->getStartPoint()); + Base::Vector3d pnt1 = flip1 ? lineSeg1->getEndPoint() : lineSeg1->getStartPoint(); + Base::Vector3d pnt2 = flip2 ? lineSeg2->getEndPoint() : lineSeg2->getStartPoint(); - // line-line intersection - { - double det = dir1.x*dir2.y - dir1.y*dir2.x; - if ((det > 0 ? det : -det) < 1e-10) { - // lines are coincident (or parallel) and in this case the center - // of the point pairs with the shortest distance is used - Base::Vector3d p1[2], p2[2]; - p1[0] = lineSeg1->getStartPoint(); - p1[1] = lineSeg1->getEndPoint(); - p2[0] = lineSeg2->getStartPoint(); - p2[1] = lineSeg2->getEndPoint(); - double length = DBL_MAX; - for (int i=0; i <= 1; i++) { - for (int j=0; j <= 1; j++) { - double tmp = (p2[j]-p1[i]).Length(); - if (tmp < length) { - length = tmp; - p0.setValue((p2[j].x+p1[i].x)/2,(p2[j].y+p1[i].y)/2,0); + // line-line intersection + { + double det = dir1.x*dir2.y - dir1.y*dir2.x; + if ((det > 0 ? det : -det) < 1e-10) { + // lines are coincident (or parallel) and in this case the center + // of the point pairs with the shortest distance is used + Base::Vector3d p1[2], p2[2]; + p1[0] = lineSeg1->getStartPoint(); + p1[1] = lineSeg1->getEndPoint(); + p2[0] = lineSeg2->getStartPoint(); + p2[1] = lineSeg2->getEndPoint(); + double length = DBL_MAX; + for (int i=0; i <= 1; i++) { + for (int j=0; j <= 1; j++) { + double tmp = (p2[j]-p1[i]).Length(); + if (tmp < length) { + length = tmp; + p0.setValue((p2[j].x+p1[i].x)/2,(p2[j].y+p1[i].y)/2,0); + } } } } + else { + double c1 = dir1.y*pnt1.x - dir1.x*pnt1.y; + double c2 = dir2.y*pnt2.x - dir2.x*pnt2.y; + double x = (dir1.x*c2 - dir2.x*c1)/det; + double y = (dir1.y*c2 - dir2.y*c1)/det; + p0 = SbVec3f(x,y,0); + } } - else { - double c1 = dir1.y*pnt1.x - dir1.x*pnt1.y; - double c2 = dir2.y*pnt2.x - dir2.x*pnt2.y; - double x = (dir1.x*c2 - dir2.x*c1)/det; - double y = (dir1.y*c2 - dir2.y*c1)/det; - p0 = SbVec3f(x,y,0); - } + } else {//angle-via-point + Base::Vector3d p = edit->ActSketch.getPoint(Constr->Third, Constr->ThirdPos); + p0 = SbVec3f(p.x, p.y, 0); + dir1 = edit->ActSketch.calculateNormalAtPoint(Constr->First, p.x, p.y); + dir1.RotateZ(-M_PI/2);//convert to vector of tangency by rotating + dir2 = edit->ActSketch.calculateNormalAtPoint(Constr->Second, p.x, p.y); + dir2.RotateZ(-M_PI/2); } startangle = atan2(dir1.y,dir1.x);