diff --git a/src/Mod/Sketcher/App/ConstraintPyImp.cpp b/src/Mod/Sketcher/App/ConstraintPyImp.cpp index fc29c2785a..6e0d80c1cd 100644 --- a/src/Mod/Sketcher/App/ConstraintPyImp.cpp +++ b/src/Mod/Sketcher/App/ConstraintPyImp.cpp @@ -276,6 +276,17 @@ int ConstraintPy::PyInit(PyObject* args, PyObject* /*kwd*/) this->getConstraintPtr()->ThirdPos = (Sketcher::PointPos) intArg4; return 0; } + else if (strcmp("PerpendicularViaPoint", ConstraintType) == 0) { + this->getConstraintPtr()->Type = Perpendicular; + //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 = intArg1; this->getConstraintPtr()->FirstPos = (Sketcher::PointPos) intArg2; @@ -412,6 +423,12 @@ std::string ConstraintPy::representation(void) const else result << "'TangentViaPoint'>"; break; + case Perpendicular : + if (this->getConstraintPtr()->Third == Constraint::GeoUndef) + result << "'Perpendicular'>"; + else + result << "'PerpendicularViaPoint'>"; + break; case Distance : result << "'Distance'>";break; case Angle : if (this->getConstraintPtr()->Third == Constraint::GeoUndef) diff --git a/src/Mod/Sketcher/App/Sketch.cpp b/src/Mod/Sketcher/App/Sketch.cpp index 489e5d1f8a..e0b49a43ea 100644 --- a/src/Mod/Sketcher/App/Sketch.cpp +++ b/src/Mod/Sketcher/App/Sketch.cpp @@ -674,31 +674,35 @@ int Sketch::addConstraint(const Constraint *constraint) rtn = addParallelConstraint(constraint->First,constraint->Second); break; case Perpendicular: - if (constraint->SecondPos != none) // perpendicularity at common point - rtn = addPerpendicularConstraint(constraint->First,constraint->FirstPos, - constraint->Second,constraint->SecondPos); - else if (constraint->Second != Constraint::GeoUndef) { - if (constraint->FirstPos != none) // "First" is a connecting point - rtn = addPerpendicularConstraint(constraint->First,constraint->FirstPos, - constraint->Second); - else // simple perpendicularity - rtn = addPerpendicularConstraint(constraint->First,constraint->Second); + if (constraint->FirstPos == none && + constraint->SecondPos == none && + constraint->Third == Constraint::GeoUndef){ + //simple perpendicularity + rtn = addPerpendicularConstraint(constraint->First,constraint->Second); + } else { + //any other point-wise perpendicularity + rtn = addAngleAtPointConstraint( + constraint->First, constraint->FirstPos, + constraint->Second, constraint->SecondPos, + constraint->Third, constraint->ThirdPos, + constraint->Value, constraint->Type); } + + break; case Tangent: - 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) { - if (constraint->FirstPos != none) // "First" is a tangency point - rtn = addTangentConstraint(constraint->First,constraint->FirstPos, - constraint->Second); - else // simple tangency - rtn = addTangentConstraint(constraint->First,constraint->Second); + if (constraint->FirstPos == none && + constraint->SecondPos == none && + constraint->Third == Constraint::GeoUndef){ + //simple tangency + rtn = addTangentConstraint(constraint->First,constraint->Second); + } else { + //any other point-wise tangency (endpoint-to-curve, endpoint-to-endpoint, tangent-via-point) + rtn = addAngleAtPointConstraint( + constraint->First, constraint->FirstPos, + constraint->Second, constraint->SecondPos, + constraint->Third, constraint->ThirdPos, + constraint->Value, constraint->Type); } break; case Distance: @@ -718,11 +722,11 @@ int Sketch::addConstraint(const Constraint *constraint) break; case Angle: if (constraint->Third != Constraint::GeoUndef){ - rtn = addAngleViaPointConstraint ( - constraint->First, - constraint->Second, + rtn = addAngleAtPointConstraint ( + constraint->First, constraint->FirstPos, + constraint->Second, constraint->SecondPos, constraint->Third, constraint->ThirdPos, - constraint->Value); + constraint->Value, constraint->Type); } 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); @@ -1034,187 +1038,6 @@ int Sketch::addPerpendicularConstraint(int geoId1, int geoId2) return -1; } -// perpendicularity at specific point constraint -int Sketch::addPerpendicularConstraint(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 addPerpendicularConstraint(geoId1, geoId2); - - 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.addConstraintPerpendicular(l1, l2, tag); - return ConstraintsCounter; - } - else if (Geoms[geoId2].type == Arc) { - GCS::Arc &a2 = Arcs[Geoms[geoId2].index]; - GCS::Point &p2 = Points[Geoms[geoId2].midPointId]; - int tag = ++ConstraintsCounter; - GCSsys.addConstraintPointOnArc(p1, a2, tag); - GCSsys.addConstraintPointOnLine(p2, l1, tag); - return ConstraintsCounter; - } - else if (Geoms[geoId2].type == Circle) { - GCS::Circle &c2 = Circles[Geoms[geoId2].index]; - GCS::Point &p2 = Points[Geoms[geoId2].midPointId]; - int tag = ++ConstraintsCounter; - GCSsys.addConstraintPointOnCircle(p1, c2, tag); - GCSsys.addConstraintPointOnLine(p2, l1, tag); - return ConstraintsCounter; - } - else if (Geoms[geoId2].type == Ellipse) { - - GCS::Ellipse &c2 = Ellipses[Geoms[geoId2].index]; - GCS::Point &p2 = Points[Geoms[geoId2].midPointId]; - int tag = ++ConstraintsCounter; - GCSsys.addConstraintPointOnEllipse(p1, c2, tag); - GCSsys.addConstraintPointOnLine(p2, l1, tag); - return ConstraintsCounter; - } - } - 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.addConstraintPointOnLine(a1.center, l2, tag); - return ConstraintsCounter; - } - else if (Geoms[geoId2].type == Arc || Geoms[geoId2].type == Circle || Geoms[geoId2].type == Ellipse) { - int tag = ++ConstraintsCounter; - GCS::Point ¢er = Points[Geoms[geoId2].midPointId]; - double *radius; - if (Geoms[geoId2].type == Arc) { - GCS::Arc &a2 = Arcs[Geoms[geoId2].index]; - radius = a2.rad; - } - else if (Geoms[geoId2].type == Circle) { - GCS::Circle &c2 = Circles[Geoms[geoId2].index]; - radius = c2.rad; - } - else { - - GCS::Ellipse &c2 = Ellipses[Geoms[geoId2].index]; - radius = c2.radmin; - } - if (pos1 == start) - GCSsys.addConstraintPerpendicularCircle2Arc(center, radius, a1, tag); - else if (pos1 == end) - GCSsys.addConstraintPerpendicularArc2Circle(a1, center, radius, tag); - return ConstraintsCounter; - } - } - return -1; -} - -// perpendicularity at common point constraint -int Sketch::addPerpendicularConstraint(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); - - int pointId1 = getPointId(geoId1, pos1); - int pointId2 = getPointId(geoId2, pos2); - - if (pointId1 < 0 || pointId1 >= int(Points.size()) || - pointId2 < 0 || pointId2 >= int(Points.size())) - 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.addConstraintPerpendicular(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 == 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.addConstraintPerpendicularLine2Arc(l1.p2, l1.p1, a2, tag); - return ConstraintsCounter; - } - else if (pos1 == end) { - int tag = ++ConstraintsCounter; - GCSsys.addConstraintPerpendicularLine2Arc(l1.p1, l1.p2, a2, tag); - return ConstraintsCounter; - } - } - else if (pos2 == end) { - if (pos1 == start) { - int tag = ++ConstraintsCounter; - GCSsys.addConstraintPerpendicularArc2Line(a2, l1.p1, l1.p2, tag); - return ConstraintsCounter; - } - else if (pos1 == end) { - int tag = ++ConstraintsCounter; - GCSsys.addConstraintPerpendicularArc2Line(a2, l1.p2, l1.p1, tag); - return ConstraintsCounter; - } - } - else - 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.addConstraintPerpendicularArc2Arc(a1, true, a2, false, tag); - else // if (pos2 == end) - GCSsys.addConstraintPerpendicularArc2Arc(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.addConstraintPerpendicularArc2Arc(a1, false, a2, false, tag); - else // if (pos2 == end) - GCSsys.addConstraintPerpendicularArc2Arc(a1, false, a2, true, tag); - return ConstraintsCounter; - } - } - } - return -1; -} - // simple tangency constraint int Sketch::addTangentConstraint(int geoId1, int geoId2) { @@ -1324,132 +1147,122 @@ int Sketch::addTangentConstraint(int geoId1, int geoId2) return -1; } -// endpoint-to-curve tangency -int Sketch::addTangentConstraint(int geoId1, PointPos pos1, int geoId2) +//This function handles any type of tangent, perpendicular and angle +// constraint that involves a point. +// i.e. endpoint-to-curve, endpoint-to-endpoint and tangent-via-point +//geoid1, geoid2 and geoid3 as in in the constraint object. +//For perp-ty and tangency, angle is used to lock the direction. +//angle==0 - autodetect direction. +pi/2, -pi/2 - specific direction. +int Sketch::addAngleAtPointConstraint( + int geoId1, PointPos pos1, + int geoId2, PointPos pos2, + int geoId3, PointPos pos3, + double value, + ConstraintType cTyp) { + + if(!(cTyp == Angle || cTyp == Tangent || cTyp == Perpendicular)) { + assert(0);//none of the three types. Why are we here?? + return -1; + } + + bool avp = geoId3!=Constraint::GeoUndef; //is angle-via-point? + bool e2c = pos2 == none && pos1 != none;//is endpoint-to-curve? + bool e2e = pos2 != none && pos1 != none;//is endpoint-to-endpoint? + + if (!( avp || e2c || e2e )) { + assert(0);//none of the three types. Why are we here?? + return -1; + } + geoId1 = checkGeoId(geoId1); geoId2 = checkGeoId(geoId2); - - int pointId1 = getPointId(geoId1, pos1); - - 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 == 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; - } - - // 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; -} - -// endpoint-to-endpoint tangency -int Sketch::addTangentConstraint(int geoId1, PointPos pos1, int geoId2, PointPos pos2) -{ - geoId1 = checkGeoId(geoId1); - geoId2 = checkGeoId(geoId2); - - int pointId1 = getPointId(geoId1, pos1); - int pointId2 = getPointId(geoId2, pos2); - - if (pointId1 < 0 || pointId1 >= 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[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; - } - - // 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(avp) + geoId3 = checkGeoId(geoId3); if (Geoms[geoId1].type == Point || - Geoms[geoId2].type == Point) - return -1;//first two objects must be curves! + Geoms[geoId2].type == Point){ + assert(0);//point is not a curve. No tangency/whatever! + return -1; + } GCS::Curve* crv1 =getGCSCurveByGeoId(geoId1); GCS::Curve* crv2 =getGCSCurveByGeoId(geoId2); if (!crv1 || !crv2) { - Base::Console().Error("addTangentViaPointConstraint: getGCSCurveByGeoId returned NULL!\n"); + assert(0); + Base::Console().Error("addAngleAtPointConstraint: getGCSCurveByGeoId returned NULL!\n"); return -1; } - int pointId = getPointId(geoId3, pos3);; + int pointId = -1; + if(avp) + pointId = getPointId(geoId3, pos3); + else if (e2e || e2c) + pointId = getPointId(geoId1, pos1); + if (pointId < 0 || pointId >= int(Points.size())){ - Base::Console().Error("addTangentViaPointConstraint: point index out of range.\n"); + assert(0); + Base::Console().Error("addAngleAtPointConstraint: point index out of range.\n"); return -1; } GCS::Point &p = Points[pointId]; + GCS::Point* p2 = 0; + if(e2e){//we need second point + int pointId = getPointId(geoId2, pos2); + if (pointId < 0 || pointId >= int(Points.size())){ + assert(0); + Base::Console().Error("addAngleAtPointConstraint: point index out of range.\n"); + return -1; + } + p2 = &(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; + //For tangency/perpendicularity, we don't just copy the angle. + //The angle stored for tangency/perpendicularity is offset, so that the options + // are -Pi/2 and Pi/2. If value is 0 - this is an indicator of an old sketch. + // Use autodetect then. + //The same functionality is implemented in SketchObject.cpp, where + // it is used to permanently lock down the autodecision. + if (cTyp == Angle) + *angle = value; + else { + //The same functionality is implemented in SketchObject.cpp, where + // it is used to permanently lock down the autodecision. + double angleOffset = 0.0;//the difference between the datum value and the actual angle to apply. (datum=angle+offset) + double angleDesire = 0.0;//the desired angle value (and we are to decide if 180* should be added to it) + if (cTyp == Tangent) {angleOffset = -M_PI/2; angleDesire = 0.0;} + if (cTyp == Perpendicular) {angleOffset = 0; angleDesire = M_PI/2;} + + if (value==0.0) {//autodetect tangency internal/external (and same for perpendicularity) + double angleErr = GCSsys.calculateAngleViaPoint(*crv1, *crv2, p) - angleDesire; + //bring angleErr to -pi..pi + if (angleErr > M_PI) angleErr -= M_PI*2; + if (angleErr < -M_PI) angleErr += M_PI*2; + + //the autodetector + if(abs(angleErr) > M_PI/2 ) + angleDesire += M_PI; + + *angle = angleDesire; + } else + *angle = value-angleOffset; + } + + + int tag = -1; + if(e2c) + tag = Sketch::addPointOnObjectConstraint(geoId1, pos1, geoId2);//increases ConstraintsCounter + if (e2e){ + tag = ++ConstraintsCounter; + GCSsys.addConstraintP2PCoincident(p, *p2, tag); + } + if(avp) + tag = ++ConstraintsCounter; - int tag = ++ConstraintsCounter; GCSsys.addConstraintAngleViaPoint(*crv1, *crv2, p, angle, tag); return ConstraintsCounter; } @@ -1644,30 +1457,6 @@ 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) { diff --git a/src/Mod/Sketcher/App/Sketch.h b/src/Mod/Sketcher/App/Sketch.h index da6becdad4..8ee9d6934d 100644 --- a/src/Mod/Sketcher/App/Sketch.h +++ b/src/Mod/Sketcher/App/Sketch.h @@ -160,13 +160,14 @@ public: int addParallelConstraint(int geoId1, int geoId2); /// add a perpendicular constraint between two lines int addPerpendicularConstraint(int geoId1, int geoId2); - int addPerpendicularConstraint(int geoId1, PointPos pos1, int geoId2); - int addPerpendicularConstraint(int geoId1, PointPos pos1, int geoId2, PointPos pos2); /// add a tangency constraint between two geometries 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); + int addAngleAtPointConstraint( + int geoId1, PointPos pos1, + int geoId2, PointPos pos2, + int geoId3, PointPos pos3, + double value, + ConstraintType cTyp); /// 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 diff --git a/src/Mod/Sketcher/App/SketchObject.cpp b/src/Mod/Sketcher/App/SketchObject.cpp index 1c217a8479..8800fae1b1 100644 --- a/src/Mod/Sketcher/App/SketchObject.cpp +++ b/src/Mod/Sketcher/App/SketchObject.cpp @@ -194,7 +194,9 @@ int SketchObject::setDatum(int ConstrId, double Datum) type != DistanceX && type != DistanceY && type != Radius && - type != Angle) + type != Angle && + type != Tangent && //for tangent, value==0 is autodecide, value==Pi/2 is external and value==-Pi/2 is internal + type != Perpendicular) return -1; if ((type == Distance || type == Radius) && Datum <= 0) @@ -435,13 +437,32 @@ int SketchObject::setConstruction(int GeoId, bool on) return 0; } +//ConstraintList is used only to make copies. int SketchObject::addConstraints(const std::vector &ConstraintList) { const std::vector< Constraint * > &vals = this->Constraints.getValues(); std::vector< Constraint * > newVals(vals); newVals.insert(newVals.end(), ConstraintList.begin(), ConstraintList.end()); + + //test if tangent constraints have been added; AutoLockTangency. + std::vector< Constraint * > tbd;//list of temporary copies that need to be deleted + for(int i = newVals.size()-ConstraintList.size(); iType == Tangent || newVals[i]->Type == Perpendicular ){ + Constraint *constNew = newVals[i]->clone(); + AutoLockTangencyAndPerpty(constNew); + tbd.push_back(constNew); + newVals[i] = constNew; + } + } + this->Constraints.setValues(newVals); + + //clean up - delete temporary copies of constraints that were made to affect the constraints + for(int i=0; iConstraints.getSize()-1; } @@ -451,6 +472,10 @@ int SketchObject::addConstraint(const Constraint *constraint) std::vector< Constraint * > newVals(vals); Constraint *constNew = constraint->clone(); + + if (constNew->Type == Tangent || constNew->Type == Perpendicular) + AutoLockTangencyAndPerpty(constNew); + newVals.push_back(constNew); this->Constraints.setValues(newVals); delete constNew; @@ -2264,16 +2289,35 @@ void SketchObject::validateConstraints() } } +//This function is necessary for precalculation of an angle when adding +// an angle constraint. It is also used here, in SketchObject, to +// lock down the type of tangency/perpendicularity. 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)); + const Part::GeomCurve &g1 = *(dynamic_cast(this->getGeometry(GeoId1))); + const Part::GeomCurve &g2 = *(dynamic_cast(this->getGeometry(GeoId2))); + Base::Vector3d p(px, py, 0.0); - return sk.calculateAngleViaPoint(i1,i2,px,py); + double u1 = 0.0; + double u2 = 0.0; + if (! g1.closestParameterToBasicCurve(p, u1) ) throw Base::Exception("SketchObject::calculateAngleViaPoint: closestParameter(curve1) failed!"); + if (! g2.closestParameterToBasicCurve(p, u2) ) throw Base::Exception("SketchObject::calculateAngleViaPoint: closestParameter(curve2) failed!"); + + gp_Dir tan1, tan2; + if (! g1.tangent(u1,tan1) ) throw Base::Exception("SketchObject::calculateAngleViaPoint: tangent1 failed!"); + if (! g2.tangent(u2,tan2) ) throw Base::Exception("SketchObject::calculateAngleViaPoint: tangent2 failed!"); + + assert(abs(tan1.Z())<0.0001); + assert(abs(tan2.Z())<0.0001); + + double ang = atan2(-tan2.X()*tan1.Y()+tan2.Y()*tan1.X(), tan2.X()*tan1.X() + tan2.Y()*tan1.Y()); + return ang; } +//Tests if the provided point lies exactly in a curve (satisfies +// point-on-object constraint). It is used to decide whether it is nesessary to +// constrain a point onto curves when 3-element selection tangent-via-point-like +// constraints are applied. bool SketchObject::isPointOnCurve(int geoIdCurve, double px, double py) { //DeepSOIC: this may be slow, but I wanted to reuse the existing code @@ -2288,6 +2332,7 @@ bool SketchObject::isPointOnCurve(int geoIdCurve, double px, double py) return err*err < 10.0*sk.getSolverPrecision(); } +//This one was done just for fun to see to what precision the constraints are solved. double SketchObject::calculateConstraintError(int ConstrId) { Sketcher::Sketch sk; @@ -2409,6 +2454,124 @@ int SketchObject::getVertexIndexGeoPos(int GeoId, PointPos PosId) const return -1; } +///changeConstraintsLocking locks or unlocks all tangent and perpendicular +/// constraints. (Constraint locking prevents it from flipping to another valid +/// configuration, when e.g. external geometry is updated from outside.) The +/// sketch solve is not triggered by the function, but the SketchObject is +/// touched (a recompute will be necessary). The geometry should not be affected +/// by the function. +///The bLock argument specifies, what to do. If true, all constraints are +/// unlocked and locked again. If false, all tangent and perp. constraints are +/// unlocked. +int SketchObject::changeConstraintsLocking(bool bLock) +{ + int cntSuccess = 0; + int cntToBeAffected = 0;//==cntSuccess+cntFail + const std::vector< Constraint * > &vals = this->Constraints.getValues(); + + std::vector< Constraint * > newVals(vals);//modifiable copy of pointers array + + std::vector< Constraint * > tbd;//list of temporary Constraint copies that need to be deleted later + + for(int i = 0; iType == Tangent || newVals[i]->Type == Perpendicular ){ + //create a constraint copy, affect it, replace the pointer + cntToBeAffected++; + Constraint *constNew = newVals[i]->clone(); + bool ret = AutoLockTangencyAndPerpty(constNew, /*bForce=*/true, bLock); + if (ret) cntSuccess++; + tbd.push_back(constNew); + newVals[i] = constNew; + } + } + + this->Constraints.setValues(newVals); + + //clean up - delete temporary copies of constraints that were made to affect the constraints + for(int i=0; iType == Tangent || cstr->Type == Perpendicular); + if(cstr->Value != 0.0 && ! bForce) /*tangency type already set. If not bForce - don't touch.*/ + return true; + if(!bLock){ + cstr->Value=0.0;//reset + } else { + //decide on tangency type. Write the angle value into the datum field of the constraint. + int geoId1, geoId2, geoIdPt; + PointPos posPt; + geoId1 = cstr->First; + geoId2 = cstr->Second; + geoIdPt = cstr->Third; + posPt = cstr->ThirdPos; + if (geoIdPt == Constraint::GeoUndef){//not tangent-via-point, try endpoint-to-endpoint... + geoIdPt = cstr->First; + posPt = cstr->FirstPos; + } + if (posPt == none){//not endpoint-to-curve and not endpoint-to-endpoint tangent (is simple tangency) + //no tangency lockdown is implemented for simple tangency. Do nothing. + return false; + } else { + Base::Vector3d p = getPoint(geoIdPt, posPt); + + //this piece of code is also present in Sketch.cpp, correct for offset + //and to do the autodecision for old sketches. + double angleOffset = 0.0;//the difference between the datum value and the actual angle to apply. (datum=angle+offset) + double angleDesire = 0.0;//the desired angle value (and we are to decide if 180* should be added to it) + if (cstr->Type == Tangent) {angleOffset = -M_PI/2; angleDesire = 0.0;} + if (cstr->Type == Perpendicular) {angleOffset = 0; angleDesire = M_PI/2;} + + double angleErr = calculateAngleViaPoint(geoId1, geoId2, p.x, p.y) - angleDesire; + + //bring angleErr to -pi..pi + if (angleErr > M_PI) angleErr -= M_PI*2; + if (angleErr < -M_PI) angleErr += M_PI*2; + + //the autodetector + if(abs(angleErr) > M_PI/2 ) + angleDesire += M_PI; + + cstr->Value = angleDesire + angleOffset; //external tangency. The angle stored is offset by Pi/2 so that a value of 0.0 is invalid and threated as "undecided". + } + } + } catch (Base::Exception& e){ + //failure to determine tangency type is not a big deal, so a warning. + assert(0);//but it shouldn't happen (failure to determine tangency type)! + Base::Console().Warning("Error in AutoLockTangency. %s \n", e.what()); + return false; + } + return true; +} + // Python Sketcher feature --------------------------------------------------------- namespace App { diff --git a/src/Mod/Sketcher/App/SketchObject.h b/src/Mod/Sketcher/App/SketchObject.h index 1974577128..38900bec0d 100644 --- a/src/Mod/Sketcher/App/SketchObject.h +++ b/src/Mod/Sketcher/App/SketchObject.h @@ -155,6 +155,7 @@ public: double calculateAngleViaPoint(int geoId1, int geoId2, double px, double py); bool isPointOnCurve(int geoIdCurve, double px, double py); double calculateConstraintError(int ConstrId); + int changeConstraintsLocking(bool bLock); // from base class virtual PyObject *getPyObject(void); @@ -185,6 +186,8 @@ private: std::vector VertexId2GeoId; std::vector VertexId2PosId; + + bool AutoLockTangencyAndPerpty(Constraint* cstr, bool bForce = false, bool bLock = true); }; typedef App::FeaturePythonT SketchObjectPython; diff --git a/src/Mod/Sketcher/App/SketchObjectPy.xml b/src/Mod/Sketcher/App/SketchObjectPy.xml index 7cdda5de96..933ec00463 100644 --- a/src/Mod/Sketcher/App/SketchObjectPy.xml +++ b/src/Mod/Sketcher/App/SketchObjectPy.xml @@ -157,6 +157,23 @@ + + + + changeConstraintsLocking(bLock) - locks or unlocks all tangent and + perpendicular constraints. (Constraint locking prevents it from + flipping to another valid configuration, when e.g. external geometry + is updated from outside.) The sketch solve is not triggered by the + function, but the SketchObject is touched (a recompute will be + necessary). The geometry should not be affected by the function. + + The bLock argument specifies, what to do. If true, all constraints + are unlocked and locked again. If false, all tangent and perp. + constraints are unlocked. + + + + Number of Constraints in this sketch diff --git a/src/Mod/Sketcher/App/SketchObjectPyImp.cpp b/src/Mod/Sketcher/App/SketchObjectPyImp.cpp index a8ee28c930..943380d997 100644 --- a/src/Mod/Sketcher/App/SketchObjectPyImp.cpp +++ b/src/Mod/Sketcher/App/SketchObjectPyImp.cpp @@ -729,6 +729,19 @@ PyObject* SketchObjectPy::calculateConstraintError(PyObject *args) return Py::new_reference_to(Py::Float(err)); } +PyObject* SketchObjectPy::changeConstraintsLocking(PyObject *args) +{ + int bLock=0; + if (!PyArg_ParseTuple(args, "i", &bLock)) + return 0; + + SketchObject* obj = this->getSketchObjectPtr(); + + int naff = obj->changeConstraintsLocking((bool)bLock); + + return Py::new_reference_to(Py::Int(naff)); +} + PyObject* SketchObjectPy::ExposeInternalGeometry(PyObject *args) { int GeoId; diff --git a/src/Mod/Sketcher/App/freegcs/Constraints.cpp b/src/Mod/Sketcher/App/freegcs/Constraints.cpp index e720e74c57..fc5d08c58d 100644 --- a/src/Mod/Sketcher/App/freegcs/Constraints.cpp +++ b/src/Mod/Sketcher/App/freegcs/Constraints.cpp @@ -37,7 +37,7 @@ namespace GCS /////////////////////////////////////// Constraint::Constraint() -: origpvec(0), pvec(0), scale(1.), tag(0), remapped(true) +: origpvec(0), pvec(0), scale(1.), tag(0), pvecChangedFlag(true) { } @@ -50,13 +50,13 @@ void Constraint::redirectParams(MAP_pD_pD redirectionmap) if (it != redirectionmap.end()) pvec[i] = it->second; } - remapped=true; + pvecChangedFlag=true; } void Constraint::revertParams() { pvec = origpvec; - remapped=true; + pvecChangedFlag=true; } ConstraintType Constraint::getTypeId() @@ -2195,7 +2195,7 @@ ConstraintAngleViaPoint::ConstraintAngleViaPoint(Curve &acrv1, Curve &acrv2, Poi crv1 = acrv1.Copy(); crv2 = acrv2.Copy(); origpvec = pvec; - remapped=true; + pvecChangedFlag=true; rescale(); } ConstraintAngleViaPoint::~ConstraintAngleViaPoint() @@ -2204,7 +2204,7 @@ ConstraintAngleViaPoint::~ConstraintAngleViaPoint() delete crv2; crv2 = 0; } -void ConstraintAngleViaPoint::ReconstructEverything() +void ConstraintAngleViaPoint::ReconstructGeomPointers() { int cnt=0; cnt++;//skip angle - we have an inline function for that @@ -2212,7 +2212,7 @@ void ConstraintAngleViaPoint::ReconstructEverything() poa.y = pvec[cnt]; cnt++; crv1->ReconstructOnNewPvec(pvec,cnt); crv2->ReconstructOnNewPvec(pvec,cnt); - remapped=false; + pvecChangedFlag=false; } ConstraintType ConstraintAngleViaPoint::getTypeId() @@ -2227,7 +2227,7 @@ void ConstraintAngleViaPoint::rescale(double coef) double ConstraintAngleViaPoint::error() { - if (remapped) ReconstructEverything(); + if (pvecChangedFlag) ReconstructGeomPointers(); double ang=*angle(); Vector2D n1 = crv1->CalculateNormal(poa); Vector2D n2 = crv2->CalculateNormal(poa); @@ -2254,7 +2254,7 @@ double ConstraintAngleViaPoint::grad(double *param) double deriv=0.; - if (remapped) ReconstructEverything(); + if (pvecChangedFlag) ReconstructGeomPointers(); if (param == angle()) deriv += -1.0; Vector2D n1 = crv1->CalculateNormal(poa); diff --git a/src/Mod/Sketcher/App/freegcs/Constraints.h b/src/Mod/Sketcher/App/freegcs/Constraints.h index f1ad2aed9d..d82aa11b5c 100644 --- a/src/Mod/Sketcher/App/freegcs/Constraints.h +++ b/src/Mod/Sketcher/App/freegcs/Constraints.h @@ -52,7 +52,7 @@ namespace GCS InternalAlignmentPoint2Ellipse = 15, EqualMajorAxesEllipse = 16, EllipticalArcRangeToEndPoints = 17, - AngleViaPoint = 19 //DeepSOIC: skipped 18 for the very unlikely case that tangency via point will be included + AngleViaPoint = 18 }; enum InternalAlignmentType { @@ -75,7 +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) + bool pvecChangedFlag; //indicates that pvec has changed and saved pointers must be reconstructed (currently used only in AngleViaPoint) public: Constraint(); virtual ~Constraint(){} @@ -436,9 +436,17 @@ namespace GCS 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(); + Curve* crv2; + //These two pointers hold copies of the curves that were passed on + // constraint creation. The curves must be deleted upon destruction of + // the constraint. It is necessary to have copies, since messing with + // original objects that were passed is a very bad idea (but messing is + // necessary, because we need to support redirectParams()/revertParams + // functions. + //The pointers in the curves need to be reconstructed if pvec was redirected + // (test pvecChangedFlag variable before use!) + Point poa;//poa=point of angle //needs to be reconstructed if pvec was redirected/reverted. The point is easily shallow-copied by C++, so no pointer type here and no delete is necessary. + void ReconstructGeomPointers(); //writes pointers in pvec to the parameters of crv1, crv2 and poa public: ConstraintAngleViaPoint(Curve &acrv1, Curve &acrv2, Point p, double* angle); ~ConstraintAngleViaPoint(); diff --git a/src/Mod/Sketcher/App/freegcs/Geo.cpp b/src/Mod/Sketcher/App/freegcs/Geo.cpp index 4a3e19b816..f1ce63e38c 100644 --- a/src/Mod/Sketcher/App/freegcs/Geo.cpp +++ b/src/Mod/Sketcher/App/freegcs/Geo.cpp @@ -1,3 +1,25 @@ +/*************************************************************************** + * Copyright (c) Victor Titov (DeepSOIC) * + * (vv.titov@gmail.com) 2014 * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ #define DEBUG_DERIVS 0 #if DEBUG_DERIVS @@ -72,24 +94,24 @@ Vector2D Circle::CalculateNormal(Point &p, double* derivparam) Vector2D ret(0.0, 0.0); if(derivparam){ if (derivparam == center.x) { - ret.x += -1; + ret.x += 1; ret.y += 0; }; if (derivparam == center.y) { ret.x += 0; - ret.y += -1; + ret.y += 1; }; if (derivparam == p.x) { - ret.x += +1; + ret.x += -1; ret.y += 0; }; if (derivparam == p.y) { ret.x += 0; - ret.y += +1; + ret.y += -1; }; } else { - ret.x = pv.x - cv.x; - ret.y = pv.y - cv.y; + ret.x = cv.x - pv.x; + ret.y = cv.y - pv.y; }; return ret; @@ -186,8 +208,8 @@ Vector2D Ellipse::CalculateNormal(Point &p, double* derivparam) 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. + 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 @@ -213,8 +235,8 @@ Vector2D Ellipse::CalculateNormal(Point &p, double* derivparam) 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; + ret.x = -(pf1e.x + pf2e.x); + ret.y = -(pf1e.y + pf2e.y); }; return ret; diff --git a/src/Mod/Sketcher/App/freegcs/Geo.h b/src/Mod/Sketcher/App/freegcs/Geo.h index fa45de3512..ec8c8a241f 100644 --- a/src/Mod/Sketcher/App/freegcs/Geo.h +++ b/src/Mod/Sketcher/App/freegcs/Geo.h @@ -62,14 +62,13 @@ namespace GCS 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 + // 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 (or point coordinate) 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) diff --git a/src/Mod/Sketcher/Gui/CommandConstraints.cpp b/src/Mod/Sketcher/Gui/CommandConstraints.cpp index 40200c2d2c..f5fdc16511 100644 --- a/src/Mod/Sketcher/Gui/CommandConstraints.cpp +++ b/src/Mod/Sketcher/Gui/CommandConstraints.cpp @@ -148,21 +148,34 @@ bool isSimpleVertex(const Sketcher::SketchObject* Obj, int GeoId, PointPos PosId const Part::Geometry *geo = Obj->getGeometry(GeoId); if (geo->getTypeId() == Part::GeomPoint::getClassTypeId()) return true; - else if (PosId == Sketcher::mid && - (geo->getTypeId() == Part::GeomCircle::getClassTypeId() || - geo->getTypeId() == Part::GeomArcOfCircle::getClassTypeId())) + else if (PosId == Sketcher::mid) return true; else return false; } -/// Makes a tangency constraint using external construction line between +bool IsPointAlreadyOnCurve(int GeoIdCurve, int GeoIdPoint, Sketcher::PointPos PosIdPoint, Sketcher::SketchObject* Obj) +{ + //This func is a "smartness" behind three-element tangent-, perp.- and angle-via-point. + //We want to find out, if the point supplied by user is already on + // both of the curves. If not, necessary point-on-object constraints + // are to be added automatically. + //Simple geometric test seems to be the best, because a point can be + // constrained to a curve in a number of ways (e.g. it is an endpoint of an + // arc, or is coincident to endpoint of an arc, or it is an endpoint of an + // ellipse's majopr diameter line). Testing all those possibilities is way + // too much trouble, IMO(DeepSOIC). + Base::Vector3d p = Obj->getPoint(GeoIdPoint, PosIdPoint); + return Obj->isPointOnCurve(GeoIdCurve, p.x, p.y); +} + +/// Makes a simple tangency constraint using extra point + tangent via point /// geom1 => an ellipse /// geom2 => any of an ellipse, an arc of ellipse, a circle, or an arc (of circle) /// NOTE: A command must be opened before calling this function, which this function /// commits or aborts as appropriate. The reason is for compatibility reasons with /// other code e.g. "Autoconstraints" in DrawSketchHandler.cpp -void SketcherGui::makeTangentToEllipseviaConstructionLine(const Sketcher::SketchObject* Obj, +void SketcherGui::makeTangentToEllipseviaNewPoint(const Sketcher::SketchObject* Obj, const Part::Geometry *geom1, const Part::Geometry *geom2, int geoId1, @@ -193,35 +206,22 @@ void SketcherGui::makeTangentToEllipseviaConstructionLine(const Sketcher::Sketch 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(); - try { - // 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)))", - Obj->getNameInDocument(), - PoE.x,PoE.y,endpoint.x,endpoint.y); // create line for major axis - - Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.toggleConstruction(%d) ",Obj->getNameInDocument(),currentgeoid+1); - + // Add a point + Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.addGeometry(Part.Point(App.Vector(%f,%f,0)))", + Obj->getNameInDocument(), PoE.x,PoE.y); + int GeoIdPoint = Obj->getHighestCurveIndex(); + // Point on first object Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d)) ", - Obj->getNameInDocument(),currentgeoid+1,Sketcher::start,geoId1); // constrain major axis + Obj->getNameInDocument(),GeoIdPoint,Sketcher::start,geoId1); // constrain major axis // Point on second object Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d)) ", - Obj->getNameInDocument(),currentgeoid+1,Sketcher::start,geoId2); // constrain major axis - // tangent to first object + Obj->getNameInDocument(),GeoIdPoint,Sketcher::start,geoId2); // constrain major axis + // tangent via point Gui::Command::doCommand( - Gui::Command::Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Tangent',%d,%d)) ", - Obj->getNameInDocument(),currentgeoid+1,geoId1); - // tangent to second object - Gui::Command::doCommand( - Gui::Command::Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Tangent',%d,%d)) ", - Obj->getNameInDocument(),currentgeoid+1,geoId2); - + Gui::Command::Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('TangentViaPoint',%d,%d,%d,%d))", + Obj->getNameInDocument(), geoId1, geoId2 ,GeoIdPoint, Sketcher::start); } catch (const Base::Exception& e) { Base::Console().Error("%s\n", e.what()); @@ -233,13 +233,13 @@ void SketcherGui::makeTangentToEllipseviaConstructionLine(const Sketcher::Sketch Gui::Command::commitCommand(); Gui::Command::updateActive(); } -/// Makes a tangency constraint using external construction line between +/// Makes a simple tangency constraint using extra point + tangent via point /// geom1 => an arc of ellipse /// geom2 => any of an arc of ellipse, a circle, or an arc (of circle) /// NOTE: A command must be opened before calling this function, which this function /// commits or aborts as appropriate. The reason is for compatibility reasons with /// other code e.g. "Autoconstraints" in DrawSketchHandler.cpp -void SketcherGui::makeTangentToArcOfEllipseviaConstructionLine(const Sketcher::SketchObject* Obj, +void SketcherGui::makeTangentToArcOfEllipseviaNewPoint(const Sketcher::SketchObject* Obj, const Part::Geometry *geom1, const Part::Geometry *geom2, int geoId1, @@ -268,38 +268,22 @@ void SketcherGui::makeTangentToArcOfEllipseviaConstructionLine(const Sketcher::S 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(); - - Gui::Command::openCommand("add tangent constraint"); - try { - - // 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)))", - Obj->getNameInDocument(), - PoE.x,PoE.y,endpoint.x,endpoint.y); // create line for major axis - - Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.toggleConstruction(%d) ",Obj->getNameInDocument(),currentgeoid+1); + // Add a point + Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.addGeometry(Part.Point(App.Vector(%f,%f,0)))", + Obj->getNameInDocument(), PoE.x,PoE.y); + int GeoIdPoint = Obj->getHighestCurveIndex(); // Point on first object Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d)) ", - Obj->getNameInDocument(),currentgeoid+1,Sketcher::start,geoId1); // constrain major axis + Obj->getNameInDocument(),GeoIdPoint,Sketcher::start,geoId1); // constrain major axis // Point on second object Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d)) ", - Obj->getNameInDocument(),currentgeoid+1,Sketcher::start,geoId2); // constrain major axis - // tangent to first object + Obj->getNameInDocument(),GeoIdPoint,Sketcher::start,geoId2); // constrain major axis + // tangent via point Gui::Command::doCommand( - Gui::Command::Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Tangent',%d,%d)) ", - Obj->getNameInDocument(),currentgeoid+1,geoId1); - // tangent to second object - Gui::Command::doCommand( - Gui::Command::Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Tangent',%d,%d)) ", - Obj->getNameInDocument(),currentgeoid+1,geoId2); - + Gui::Command::Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('TangentViaPoint',%d,%d,%d,%d))", + Obj->getNameInDocument(), geoId1, geoId2 ,GeoIdPoint, Sketcher::start); } catch (const Base::Exception& e) { Base::Console().Error("%s\n", e.what()); @@ -1363,13 +1347,22 @@ CmdSketcherConstrainPerpendicular::CmdSketcherConstrainPerpendicular() void CmdSketcherConstrainPerpendicular::activated(int iMsg) { + QString strBasicHelp = + QObject::tr( + "There is a number of ways this constraint can be applied.\n\n" + "Accepted combinations: two curves; an endpoint and a curve; two endpoints; two curves and a point.", + /*disambig.:*/ "perpendicular constraint"); + QString strError; + // get the selection std::vector selection = getSelection().getSelectionEx(); // only one sketch with its subelements are allowed to be selected if (selection.size() != 1) { + strError = QObject::tr("Select some geometry from the sketch.", "perpendicular constraint"); + if (!strError.isEmpty()) strError.append(QString::fromLatin1("\n\n")); QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), - QObject::tr("Select two entities from the sketch.")); + strError+strBasicHelp); return; } @@ -1377,184 +1370,66 @@ void CmdSketcherConstrainPerpendicular::activated(int iMsg) const std::vector &SubNames = selection[0].getSubNames(); Sketcher::SketchObject* Obj = dynamic_cast(selection[0].getObject()); - if (SubNames.size() != 2) { + if (SubNames.size() != 2 && SubNames.size() != 3) { + strError = QObject::tr("Wrong number of selected objects!","perpendicular constraint"); + if (!strError.isEmpty()) strError.append(QString::fromLatin1("\n\n")); QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), - QObject::tr("Select exactly two entities from the sketch.")); + strError+strBasicHelp); return; } - 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)) + if (checkBothExternal(GeoId1, GeoId2)) //checkBothExternal displays error message return; - if (isVertex(GeoId1,PosId1) && isVertex(GeoId2,PosId2)) { // perpendicularity 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 perpendicularity constraint at an unconnected point!")); - return; - } - - const Part::Geometry *geo1 = Obj->getGeometry(GeoId1); - const Part::Geometry *geo2 = Obj->getGeometry(GeoId2); - if ((PosId1 != Sketcher::start && PosId1 != Sketcher::end) || - (PosId2 != Sketcher::start && PosId2 != Sketcher::end) || - (geo1->getTypeId() != Part::GeomLineSegment::getClassTypeId() && - geo1->getTypeId() != Part::GeomArcOfCircle::getClassTypeId()) || - (geo2->getTypeId() != Part::GeomLineSegment::getClassTypeId() && - geo2->getTypeId() != Part::GeomArcOfCircle::getClassTypeId())) { - - QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), - QObject::tr("The selected points should be end points of arcs and lines.")); - return; - } - - openCommand("add perpendicular constraint"); - Gui::Command::doCommand( - Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Perpendicular',%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))) { // connecting point - if (isVertex(GeoId2,PosId2)) { + if (SubNames.size() == 3) { //perpendicular via point + 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 (isSimpleVertex(Obj, GeoId1, PosId1)) { - QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), - QObject::tr("Cannot add a perpendicularity constraint at an unconnected point!")); - return; - } + if (isEdge(GeoId1, PosId1) && isEdge(GeoId2, PosId2) && isVertex(GeoId3, PosId3)) { - const Part::Geometry *geo1 = Obj->getGeometry(GeoId1); - const Part::Geometry *geo2 = Obj->getGeometry(GeoId2); - if ((PosId1 != Sketcher::start && PosId1 != Sketcher::end) || - (geo1->getTypeId() != Part::GeomLineSegment::getClassTypeId() && - geo1->getTypeId() != Part::GeomArcOfCircle::getClassTypeId())) { - QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), - QObject::tr("The selected point should be an end point of an arc or line.")); - return; - } - else if (geo2->getTypeId() != Part::GeomLineSegment::getClassTypeId() && - geo2->getTypeId() != Part::GeomArcOfCircle::getClassTypeId() && - geo2->getTypeId() != Part::GeomCircle::getClassTypeId()) { - QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), - QObject::tr("The selected edge should be an arc, line or circle.")); - return; - } - - openCommand("add perpendicularity constraint"); - Gui::Command::doCommand( - Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Perpendicular',%d,%d,%d)) ", - selection[0].getFeatName(),GeoId1,PosId1,GeoId2); - commitCommand(); - updateActive(); - getSelection().clearSelection(); - return; - } - else if (isEdge(GeoId1,PosId1) && isEdge(GeoId2,PosId2)) { // simple perpendicularity between GeoId1 and GeoId2 - - const Part::Geometry *geo1 = Obj->getGeometry(GeoId1); - const Part::Geometry *geo2 = Obj->getGeometry(GeoId2); - if (geo1->getTypeId() != Part::GeomLineSegment::getClassTypeId() && - geo2->getTypeId() != Part::GeomLineSegment::getClassTypeId()) { - QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), - QObject::tr("One of the selected edges should be a line.")); - return; - } - - if (geo1->getTypeId() == Part::GeomLineSegment::getClassTypeId()) - std::swap(GeoId1,GeoId2); - - // GeoId2 is the line - geo1 = Obj->getGeometry(GeoId1); - geo2 = Obj->getGeometry(GeoId2); - - if( geo1->getTypeId() == Part::GeomEllipse::getClassTypeId() || - geo1->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId() ) { - - Base::Vector3d center; - double majord; - double minord; - double phi; - - if( geo1->getTypeId() == Part::GeomEllipse::getClassTypeId() ){ - const Part::GeomEllipse *ellipse = static_cast(geo1); - - center=ellipse->getCenter(); - majord=ellipse->getMajorRadius(); - minord=ellipse->getMinorRadius(); - phi=ellipse->getAngleXU(); - } else - if( geo1->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId() ){ - const Part::GeomArcOfEllipse *aoe = static_cast(geo1); - - center=aoe->getCenter(); - majord=aoe->getMajorRadius(); - minord=aoe->getMinorRadius(); - phi=aoe->getAngleXU(); - } - - const Part::GeomLineSegment *line = static_cast(geo2); - - Base::Vector3d point1=line->getStartPoint(); - Base::Vector3d point2=line->getEndPoint(); - - Base::Vector3d direction=point1-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 perpendicular constraint"); - - try { - - // 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 (ellipse, arc of ellipse) - Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d)) ", - selection[0].getFeatName(),currentgeoid+1,Sketcher::start,GeoId1); - // construction line tangent to ellipse/arcofellipse + + try{ + //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('Tangent',%d,%d)) ", - selection[0].getFeatName(),currentgeoid+1,GeoId1); - // 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); - - // line perpendicular to construction line - Gui::Command::doCommand( - Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Perpendicular',%d,%d)) ", - selection[0].getFeatName(),currentgeoid+1,GeoId2); - - } - catch (const Base::Exception& e) { + Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('PerpendicularViaPoint',%d,%d,%d,%d)) ", + selection[0].getFeatName(),GeoId1,GeoId2,GeoId3,PosId3); + } catch (const Base::Exception& e) { Base::Console().Error("%s\n", e.what()); + QMessageBox::warning(Gui::getMainWindow(), + QObject::tr("Error"), + QString::fromLatin1(e.what())); Gui::Command::abortCommand(); Gui::Command::updateActive(); return; @@ -1563,21 +1438,157 @@ void CmdSketcherConstrainPerpendicular::activated(int iMsg) commitCommand(); updateActive(); getSelection().clearSelection(); - return; - - } - openCommand("add perpendicular constraint"); - Gui::Command::doCommand( - Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Perpendicular',%d,%d)) ", - selection[0].getFeatName(),GeoId1,GeoId2); - commitCommand(); - updateActive(); - getSelection().clearSelection(); - return; + return; + + }; + strError = QObject::tr("With 3 objects, there must be 2 curves and 1 point.", "tangent constraint"); + + } else if (SubNames.size() == 2) { + + if (isVertex(GeoId1,PosId1) && isVertex(GeoId2,PosId2)) { //endpoint-to-endpoint perpendicularity + + if (isSimpleVertex(Obj, GeoId1, PosId1) || + isSimpleVertex(Obj, GeoId2, PosId2)) { + QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), + QObject::tr("Cannot add a perpendicularity constraint at an unconnected point!")); + return; + } + + openCommand("add perpendicular constraint"); + Gui::Command::doCommand( + Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Perpendicular',%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))) { // endpoint-to-curve + 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 perpendicularity constraint at an unconnected point!")); + return; + } + + openCommand("add perpendicularity constraint"); + Gui::Command::doCommand( + Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Perpendicular',%d,%d,%d)) ", + selection[0].getFeatName(),GeoId1,PosId1,GeoId2); + commitCommand(); + updateActive(); + getSelection().clearSelection(); + return; + } + else if (isEdge(GeoId1,PosId1) && isEdge(GeoId2,PosId2)) { // simple perpendicularity between GeoId1 and GeoId2 + + const Part::Geometry *geo1 = Obj->getGeometry(GeoId1); + const Part::Geometry *geo2 = Obj->getGeometry(GeoId2); + if (geo1->getTypeId() != Part::GeomLineSegment::getClassTypeId() && + geo2->getTypeId() != Part::GeomLineSegment::getClassTypeId()) { + QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), + QObject::tr("One of the selected edges should be a line.")); + return; + } + + if (geo1->getTypeId() == Part::GeomLineSegment::getClassTypeId()) + std::swap(GeoId1,GeoId2); + + // GeoId2 is the line + geo1 = Obj->getGeometry(GeoId1); + geo2 = Obj->getGeometry(GeoId2); + + if( geo1->getTypeId() == Part::GeomEllipse::getClassTypeId() || + geo1->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId() ) { + + Base::Vector3d center; + double majord; + double minord; + double phi; + + if( geo1->getTypeId() == Part::GeomEllipse::getClassTypeId() ){ + const Part::GeomEllipse *ellipse = static_cast(geo1); + + center=ellipse->getCenter(); + majord=ellipse->getMajorRadius(); + minord=ellipse->getMinorRadius(); + phi=ellipse->getAngleXU(); + } else + if( geo1->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId() ){ + const Part::GeomArcOfEllipse *aoe = static_cast(geo1); + + center=aoe->getCenter(); + majord=aoe->getMajorRadius(); + minord=aoe->getMinorRadius(); + phi=aoe->getAngleXU(); + } + + const Part::GeomLineSegment *line = static_cast(geo2); + + Base::Vector3d point1=line->getStartPoint(); + Base::Vector3d point2=line->getEndPoint(); + + Base::Vector3d direction=point1-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); + + openCommand("add perpendicular constraint"); + + try { + // Add a point + Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.addGeometry(Part.Point(App.Vector(%f,%f,0)))", + Obj->getNameInDocument(), PoE.x,PoE.y); + int GeoIdPoint = Obj->getHighestCurveIndex(); + + // Point on first object (ellipse, arc of ellipse) + Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d)) ", + selection[0].getFeatName(),GeoIdPoint,Sketcher::start,GeoId1); + // Point on second object + Gui::Command::doCommand(Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d)) ", + selection[0].getFeatName(),GeoIdPoint,Sketcher::start,GeoId2); + + // add constraint: Perpendicular-via-point + Gui::Command::doCommand( + Gui::Command::Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('PerpendicularViaPoint',%d,%d,%d,%d))", + Obj->getNameInDocument(), GeoId1, GeoId2 ,GeoIdPoint, Sketcher::start); + + } + catch (const Base::Exception& e) { + Base::Console().Error("%s\n", e.what()); + Gui::Command::abortCommand(); + Gui::Command::updateActive(); + return; + } + + commitCommand(); + updateActive(); + getSelection().clearSelection(); + return; + + } + + openCommand("add perpendicular constraint"); + Gui::Command::doCommand( + Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Perpendicular',%d,%d)) ", + selection[0].getFeatName(),GeoId1,GeoId2); + commitCommand(); + updateActive(); + getSelection().clearSelection(); + return; + } } + + if (!strError.isEmpty()) strError.append(QString::fromLatin1("\n\n")); QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), - QObject::tr("Select exactly two entities from the sketch.")); + strError+strBasicHelp); return; } @@ -1603,42 +1614,49 @@ 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) { + QString strBasicHelp = + QObject::tr( + "There is a number of ways this constraint can be applied.\n\n" + "Accepted combinations: two curves; an endpoint and a curve; two endpoints; two curves and a point.", + /*disambig.:*/ "tangent constraint"); + + QString strError; // get the selection std::vector selection = getSelection().getSelectionEx(); // only one sketch with its subelements are allowed to be selected if (selection.size() != 1) { + strError = QObject::tr("Select some geometry from the sketch.", "tangent constraint"); + if (!strError.isEmpty()) strError.append(QString::fromLatin1("\n\n")); QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), - QObject::tr("Select two entities from the sketch.")); + strError+strBasicHelp); return; + } // get the needed lists and objects const std::vector &SubNames = selection[0].getSubNames(); Sketcher::SketchObject* Obj = dynamic_cast(selection[0].getObject()); - if (SubNames.size() != 2 && SubNames.size() != 3) - goto ExitWithMessage; + if (SubNames.size() != 2 && SubNames.size() != 3){ + strError = QObject::tr("Wrong number of selected objects!","tangent constraint"); + if (!strError.isEmpty()) strError.append(QString::fromLatin1("\n\n")); + QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), + strError+strBasicHelp); + return; + + } 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)) + if (checkBothExternal(GeoId1, GeoId2)) //checkBothExternal displays error message return; - if (SubNames.size() == 3) { + if (SubNames.size() == 3) { //tangent via point 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) ){ @@ -1651,40 +1669,54 @@ void CmdSketcherConstrainTangent::activated(int iMsg) }; if (isEdge(GeoId1, PosId1) && isEdge(GeoId2, PosId2) && isVertex(GeoId3, PosId3)) { - double ActAngle = 0.0; - openCommand("add angle constraint"); + openCommand("add tangent constraint"); + + try{ + //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); + }; - //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); - }; + Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('TangentViaPoint',%d,%d,%d,%d)) ", + selection[0].getFeatName(),GeoId1,GeoId2,GeoId3,PosId3); + } catch (const Base::Exception& e) { + Base::Console().Error("%s\n", e.what()); + QMessageBox::warning(Gui::getMainWindow(), + QObject::tr("Error"), + QString::fromLatin1(e.what())); + Gui::Command::abortCommand(); + Gui::Command::updateActive(); + return; + } - 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(); + updateActive(); + getSelection().clearSelection(); return; }; + strError = QObject::tr("With 3 objects, there must be 2 curves and 1 point.", "tangent constraint"); } else if (SubNames.size() == 2) { - if (isVertex(GeoId1,PosId1) && isVertex(GeoId2,PosId2)) { // tangency at common point + + if (isVertex(GeoId1,PosId1) && isVertex(GeoId2,PosId2)) { // endpoint-to-endpoint tangency if (isSimpleVertex(Obj, GeoId1, PosId1) || isSimpleVertex(Obj, GeoId2, PosId2)) { @@ -1693,97 +1725,6 @@ void CmdSketcherConstrainTangent::activated(int iMsg) 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; - } - - } - openCommand("add tangent constraint"); Gui::Command::doCommand( Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Tangent',%d,%d,%d,%d)) ", @@ -1794,7 +1735,7 @@ void CmdSketcherConstrainTangent::activated(int iMsg) return; } else if ((isVertex(GeoId1,PosId1) && isEdge(GeoId2,PosId2)) || - (isEdge(GeoId1,PosId1) && isVertex(GeoId2,PosId2))) { // tangency point + (isEdge(GeoId1,PosId1) && isVertex(GeoId2,PosId2))) { // endpoint-to-curve tangency if (isVertex(GeoId2,PosId2)) { std::swap(GeoId1,GeoId2); std::swap(PosId1,PosId2); @@ -1836,8 +1777,8 @@ void CmdSketcherConstrainTangent::activated(int iMsg) 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); + Gui::Command::openCommand("add tangent constraint point"); + makeTangentToEllipseviaNewPoint(Obj,geom1,geom2,GeoId1,GeoId2); getSelection().clearSelection(); return; } @@ -1858,8 +1799,8 @@ void CmdSketcherConstrainTangent::activated(int iMsg) 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); + Gui::Command::openCommand("add tangent constraint point"); + makeTangentToArcOfEllipseviaNewPoint(Obj,geom1,geom2,GeoId1,GeoId2); getSelection().clearSelection(); return; } @@ -1877,9 +1818,10 @@ void CmdSketcherConstrainTangent::activated(int iMsg) } } -ExitWithMessage: + + if (!strError.isEmpty()) strError.append(QString::fromLatin1("\n\n")); QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), - QObject::tr("Select exactly two entities from the sketch.")); + strError+strBasicHelp); return; } @@ -2082,13 +2024,14 @@ CmdSketcherConstrainAngle::CmdSketcherConstrainAngle() void CmdSketcherConstrainAngle::activated(int iMsg) { + //TODO: comprehensive messages, like in CmdSketcherConstrainTangent // get the selection std::vector selection = getSelection().getSelectionEx(); // only one sketch with its subelements are allowed to be selected if (selection.size() != 1) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), - QObject::tr("Select vertexes from the sketch.")); + QObject::tr("Select only entities from the sketch.")); return; } @@ -2097,7 +2040,10 @@ void CmdSketcherConstrainAngle::activated(int iMsg) Sketcher::SketchObject* Obj = dynamic_cast(selection[0].getObject()); if (SubNames.size() < 1 || SubNames.size() > 3) { - goto MessageAndExit; + //goto ExitWithMessage; + QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), + QObject::tr("Select one or two lines from the sketch. Or select two edges and a point.")); + } @@ -2253,7 +2199,7 @@ void CmdSketcherConstrainAngle::activated(int iMsg) } }; -MessageAndExit: +//ExitWithMessage: //gotos cause compilation fails on linux due to jumping over initializations. QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), 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/CommandConstraints.h b/src/Mod/Sketcher/Gui/CommandConstraints.h index 8e8d3f635c..92871cdece 100644 --- a/src/Mod/Sketcher/Gui/CommandConstraints.h +++ b/src/Mod/Sketcher/Gui/CommandConstraints.h @@ -34,7 +34,7 @@ namespace SketcherGui { /// NOTE: A command must be opened before calling this function, which this function /// commits or aborts as appropriate. The reason is for compatibility reasons with /// other code e.g. "Autoconstraints" in DrawSketchHandler.cpp -void makeTangentToEllipseviaConstructionLine(const Sketcher::SketchObject* Obj, +void makeTangentToEllipseviaNewPoint(const Sketcher::SketchObject* Obj, const Part::Geometry *geom1, const Part::Geometry *geom2, int geoId1, @@ -46,7 +46,7 @@ void makeTangentToEllipseviaConstructionLine(const Sketcher::SketchObject* Obj, /// NOTE: A command must be opened before calling this function, which this function /// commits or aborts as appropriate. The reason is for compatibility reasons with /// other code e.g. "Autoconstraints" in DrawSketchHandler.cpp -void makeTangentToArcOfEllipseviaConstructionLine(const Sketcher::SketchObject* Obj, +void makeTangentToArcOfEllipseviaNewPoint(const Sketcher::SketchObject* Obj, const Part::Geometry *geom1, const Part::Geometry *geom2, int geoId1, diff --git a/src/Mod/Sketcher/Gui/DrawSketchHandler.cpp b/src/Mod/Sketcher/Gui/DrawSketchHandler.cpp index 5485ec1315..f6e11a0ab0 100644 --- a/src/Mod/Sketcher/Gui/DrawSketchHandler.cpp +++ b/src/Mod/Sketcher/Gui/DrawSketchHandler.cpp @@ -463,7 +463,7 @@ void DrawSketchHandler::createAutoConstraints(const std::vector geom2->getTypeId() == Part::GeomCircle::getClassTypeId() || geom2->getTypeId() == Part::GeomArcOfCircle::getClassTypeId() ) { // in all these cases an intermediate element is needed - makeTangentToEllipseviaConstructionLine(Obj,geom1,geom2,geoId1,geoId2); + makeTangentToEllipseviaNewPoint(Obj,geom1,geom2,geoId1,geoId2); return; } } @@ -486,7 +486,7 @@ void DrawSketchHandler::createAutoConstraints(const std::vector // in all these cases an intermediate element is needed // TODO: INSERT COMMON CODE HERE // in all these cases an intermediate element is needed - makeTangentToArcOfEllipseviaConstructionLine(Obj,geom1,geom2,geoId1,geoId2); + makeTangentToArcOfEllipseviaNewPoint(Obj,geom1,geom2,geoId1,geoId2); return; } } diff --git a/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp b/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp index 9b5252c673..82fc5d846a 100644 --- a/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp +++ b/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp @@ -2415,7 +2415,7 @@ void ViewProviderSketch::drawConstraintIcons() break; case Perpendicular: // second icon is available only when there is no common point - if ((*it)->FirstPos == Sketcher::none) + if ((*it)->FirstPos == Sketcher::none && (*it)->Third == Constraint::GeoUndef) multipleIcons = true; break; case Equal: @@ -3132,8 +3132,37 @@ Restart: Base::Vector3d midpos1, dir1, norm1; Base::Vector3d midpos2, dir2, norm2; + bool twoIcons = false;//a very local flag. It's set to true to indicate that the second dir+norm are valid and should be used + + + if (Constr->Third != Constraint::GeoUndef || //perpty via point + Constr->FirstPos != Sketcher::none) { //endpoint-to-curve or endpoint-to-endpoint perpty + + 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); + if (temp) + midpos1 = edit->ActSketch.getPoint(ptGeoId, ptPosId); + else + midpos1 = getSketchObject()->getPoint(ptGeoId, ptPosId); + + norm1 = edit->ActSketch.calculateNormalAtPoint(Constr->Second, midpos1.x, midpos1.y); + norm1.Normalize(); + dir1 = norm1; dir1.RotateZ(-M_PI/2.0); + + } else if (Constr->FirstPos == Sketcher::none) { - if (Constr->FirstPos == Sketcher::none) { if (geo1->getTypeId() == Part::GeomLineSegment::getClassTypeId()) { const Part::GeomLineSegment *lineSeg1 = dynamic_cast(geo1); midpos1 = ((lineSeg1->getEndPoint()+lineSeg1->getStartPoint())/2); @@ -3147,7 +3176,7 @@ Restart: norm1 = Base::Vector3d(cos(midangle),sin(midangle),0); dir1 = Base::Vector3d(-norm1.y,norm1.x,0); midpos1 = arc->getCenter() + arc->getRadius() * norm1; - } else if (geo1->getTypeId() == Part::GeomCircle::getClassTypeId()) { + } else if (geo1->getTypeId() == Part::GeomCircle::getClassTypeId()) { const Part::GeomCircle *circle = dynamic_cast(geo1); norm1 = Base::Vector3d(cos(M_PI/4),sin(M_PI/4),0); dir1 = Base::Vector3d(-norm1.y,norm1.x,0); @@ -3168,28 +3197,22 @@ Restart: norm2 = Base::Vector3d(cos(midangle),sin(midangle),0); dir2 = Base::Vector3d(-norm2.y,norm2.x,0); midpos2 = arc->getCenter() + arc->getRadius() * norm2; - } else if (geo2->getTypeId() == Part::GeomCircle::getClassTypeId()) { + } else if (geo2->getTypeId() == Part::GeomCircle::getClassTypeId()) { const Part::GeomCircle *circle = dynamic_cast(geo2); norm2 = Base::Vector3d(cos(M_PI/4),sin(M_PI/4),0); dir2 = Base::Vector3d(-norm2.y,norm2.x,0); midpos2 = circle->getCenter() + circle->getRadius() * norm2; } else break; + twoIcons = true; - } else { - if (temp) - midpos1 = edit->ActSketch.getPoint(Constr->First, Constr->FirstPos); - else - midpos1 = getSketchObject()->getPoint(Constr->First, Constr->FirstPos); - norm1 = Base::Vector3d(0,1,0); - dir1 = Base::Vector3d(1,0,0); } Base::Vector3d relpos1 = seekConstraintPosition(midpos1, norm1, dir1, 2.5, edit->constrGroup->getChild(i)); dynamic_cast(sep->getChild(CONSTRAINT_SEPARATOR_INDEX_FIRST_TRANSLATION))->abPos = SbVec3f(midpos1.x, midpos1.y, zConstr); dynamic_cast(sep->getChild(CONSTRAINT_SEPARATOR_INDEX_FIRST_TRANSLATION))->translation = SbVec3f(relpos1.x, relpos1.y, 0); - if (Constr->FirstPos == Sketcher::none) { + if (twoIcons) { Base::Vector3d relpos2 = seekConstraintPosition(midpos2, norm2, dir2, 2.5, edit->constrGroup->getChild(i)); Base::Vector3d secondPos = midpos2 - midpos1;