diff --git a/src/Mod/Sketcher/App/Sketch.cpp b/src/Mod/Sketcher/App/Sketch.cpp index 46d8ffd78ed0..df487b2b2310 100644 --- a/src/Mod/Sketcher/App/Sketch.cpp +++ b/src/Mod/Sketcher/App/Sketch.cpp @@ -2868,6 +2868,11 @@ int Sketch::addTangentConstraint(int geoId1, int geoId2) GCSsys.addConstraintTangent(l, a, tag); return ConstraintsCounter; } + else if (Geoms[geoId2].type == BSpline) { + Base::Console().Error("Direct tangency constraint between line and B-spline is not " + "supported. Use tangent-via-point instead."); + return -1; + } } else if (Geoms[geoId1].type == Circle) { GCS::Circle& c = Circles[Geoms[geoId1].index]; @@ -2888,6 +2893,11 @@ int Sketch::addTangentConstraint(int geoId1, int geoId2) GCSsys.addConstraintTangent(c, a, tag); return ConstraintsCounter; } + else if (Geoms[geoId2].type == BSpline) { + Base::Console().Error("Direct tangency constraint between circle and B-spline is not " + "supported. Use tangent-via-point instead."); + return -1; + } } else if (Geoms[geoId1].type == Ellipse) { if (Geoms[geoId2].type == Circle) { @@ -2900,6 +2910,11 @@ int Sketch::addTangentConstraint(int geoId1, int geoId2) "supported. Use tangent-via-point instead."); return -1; } + else if (Geoms[geoId2].type == BSpline) { + Base::Console().Error("Direct tangency constraint between ellipse and B-spline is not " + "supported. Use tangent-via-point instead."); + return -1; + } } else if (Geoms[geoId1].type == Arc) { GCS::Arc& a = Arcs[Geoms[geoId1].index]; @@ -2920,6 +2935,16 @@ int Sketch::addTangentConstraint(int geoId1, int geoId2) GCSsys.addConstraintTangent(a, a2, tag); return ConstraintsCounter; } + else if (Geoms[geoId2].type == BSpline) { + Base::Console().Error("Direct tangency constraint between arc and B-spline is not " + "supported. Use tangent-via-point instead."); + return -1; + } + } + else if (Geoms[geoId1].type == BSpline) { + Base::Console().Error("Direct tangency constraint including B-splines is not " + "supported. Use tangent-via-point instead."); + return -1; } return -1; @@ -3041,7 +3066,6 @@ int Sketch::addAngleAtPointConstraint(int geoId1, ConstraintType cTyp, bool driving) { - if (!(cTyp == Angle || cTyp == Tangent || cTyp == Perpendicular)) { // assert(0);//none of the three types. Why are we here?? return -1; @@ -3146,19 +3170,116 @@ int Sketch::addAngleAtPointConstraint(int geoId1, } int tag = -1; + // FIXME: Perform construction of any parameters where this method is called instead of here if (e2c) { - // increases ConstraintsCounter - tag = Sketch::addPointOnObjectConstraint(geoId1, pos1, geoId2, driving); + if (Geoms[geoId2].type == BSpline) { + GCS::Point& p1 = Points[getPointId(geoId1, pos1)]; + auto* partBsp = static_cast(Geoms[geoId2].geo); + double uNear; + partBsp->closestParameter(Base::Vector3d(*p1.x, *p1.y, 0.0), uNear); + double* pointparam = new double(uNear); + Parameters.push_back(pointparam); + --ConstraintsCounter; // Do this just before point-on-object because ConstraintsCounter + // is increased again before being used + tag = addPointOnObjectConstraint(geoId1, + pos1, + geoId2, + pointparam, + driving); // increases ConstraintsCounter + GCSsys.addConstraintAngleViaPointAndParam(*crv2, + *crv1, + p, + pointparam, + angle, + tag, + driving); + } + else { + // increases ConstraintsCounter + tag = Sketch::addPointOnObjectConstraint(geoId1, + pos1, + geoId2, + driving); // increases ConstraintsCounter + GCSsys.addConstraintAngleViaPoint(*crv1, *crv2, p, angle, tag, driving); + } } if (e2e) { tag = ++ConstraintsCounter; GCSsys.addConstraintP2PCoincident(p, *p2, tag, driving); + GCSsys.addConstraintAngleViaPoint(*crv1, *crv2, p, angle, tag, driving); } if (avp) { tag = ++ConstraintsCounter; + if (Geoms[geoId1].type == BSpline || Geoms[geoId2].type == BSpline) { + if (Geoms[geoId1].type == BSpline && Geoms[geoId2].type == BSpline) { + GCS::Point& p3 = Points[getPointId(geoId3, pos3)]; + auto* partBsp = static_cast(Geoms[geoId1].geo); + double uNear; + partBsp->closestParameter(Base::Vector3d(*p3.x, *p3.y, 0.0), uNear); + double* pointparam1 = new double(uNear); + Parameters.push_back(pointparam1); + --ConstraintsCounter; // Do this just before point-on-object because + // ConstraintsCounter is increased again before being used + addPointOnObjectConstraint(geoId3, + pos3, + geoId1, + pointparam1, + driving); // increases ConstraintsCounter + partBsp = static_cast(Geoms[geoId2].geo); + partBsp->closestParameter(Base::Vector3d(*p3.x, *p3.y, 0.0), uNear); + double* pointparam2 = new double(uNear); + --ConstraintsCounter; // Do this just before point-on-object because + // ConstraintsCounter is increased again before being used + addPointOnObjectConstraint(geoId3, + pos3, + geoId2, + pointparam2, + driving); // increases ConstraintsCounter + Parameters.push_back(pointparam2); + GCSsys.addConstraintAngleViaPointAndTwoParams(*crv1, + *crv2, + p, + pointparam1, + pointparam2, + angle, + tag, + driving); + } + else { + if (Geoms[geoId1].type != BSpline) { + std::swap(geoId1, geoId2); + std::swap(crv1, crv2); + std::swap(pos1, pos2); + // FIXME: Confirm whether or not this is needed + // *angle = -*angle; + } + GCS::Point& p3 = Points[getPointId(geoId3, pos3)]; + auto* partBsp = static_cast(Geoms[geoId1].geo); + double uNear; + partBsp->closestParameter(Base::Vector3d(*p3.x, *p3.y, 0.0), uNear); + double* pointparam = new double(uNear); + Parameters.push_back(pointparam); + --ConstraintsCounter; // Do this just before point-on-object because + // ConstraintsCounter is increased again before being used + addPointOnObjectConstraint(geoId3, + pos3, + geoId1, + pointparam, + driving); // increases ConstraintsCounter + GCSsys.addConstraintAngleViaPointAndParam(*crv1, + *crv2, + p, + pointparam, + angle, + tag, + driving); + } + } + else { + GCSsys.addConstraintAngleViaPoint(*crv1, *crv2, p, angle, tag, driving); + } } - GCSsys.addConstraintAngleViaPoint(*crv1, *crv2, p, angle, tag, driving); return ConstraintsCounter; } @@ -4139,6 +4260,30 @@ double Sketch::calculateAngleViaPoint(int geoId1, int geoId2, double px, double return GCSsys.calculateAngleViaPoint(*crv1, *crv2, p); } +double Sketch::calculateAngleViaParams(int geoId1, int geoId2, double param1, double param2) +{ + geoId1 = checkGeoId(geoId1); + geoId2 = checkGeoId(geoId2); + + // check pointers + GCS::Curve* crv1 = getGCSCurveByGeoId(geoId1); + GCS::Curve* crv2 = getGCSCurveByGeoId(geoId2); + if (!crv1 || !crv2) { + throw Base::ValueError("calculateAngleViaPoint: getGCSCurveByGeoId returned NULL!"); + } + // FIXME: This should probably not be needed + auto* crv1AsBSpline = dynamic_cast(crv1); + if (crv1AsBSpline && crv1AsBSpline->flattenedknots.empty()) { + crv1AsBSpline->setupFlattenedKnots(); + } + auto* crv2AsBSpline = dynamic_cast(crv2); + if (crv2AsBSpline && crv2AsBSpline->flattenedknots.empty()) { + crv2AsBSpline->setupFlattenedKnots(); + } + + return GCSsys.calculateAngleViaParams(*crv1, *crv2, ¶m1, ¶m2); +} + Base::Vector3d Sketch::calculateNormalAtPoint(int geoIdCurve, double px, double py) const { geoIdCurve = checkGeoId(geoIdCurve); diff --git a/src/Mod/Sketcher/App/Sketch.h b/src/Mod/Sketcher/App/Sketch.h index 2014e81ec11a..9035c7ae23be 100644 --- a/src/Mod/Sketcher/App/Sketch.h +++ b/src/Mod/Sketcher/App/Sketch.h @@ -488,6 +488,8 @@ class SketcherExport Sketch: public Base::Persistence // value as the point approaches intersection of curves). double calculateAngleViaPoint(int geoId1, int geoId2, double px, double py); + double calculateAngleViaParams(int geoId1, int geoId2, double param1, double param2); + // This is to be used for rendering of angle-via-point constraint. Base::Vector3d calculateNormalAtPoint(int geoIdCurve, double px, double py) const; diff --git a/src/Mod/Sketcher/App/SketchObject.cpp b/src/Mod/Sketcher/App/SketchObject.cpp index fe6906223e27..82a9455b5ca9 100644 --- a/src/Mod/Sketcher/App/SketchObject.cpp +++ b/src/Mod/Sketcher/App/SketchObject.cpp @@ -8578,13 +8578,24 @@ double SketchObject::calculateAngleViaPoint(int GeoId1, int GeoId2, double px, d // Temporary sketch based calculation. Slow, but guaranteed consistency with constraints. Sketcher::Sketch sk; - const Part::Geometry* p1 = this->getGeometry(GeoId1); - const Part::Geometry* p2 = this->getGeometry(GeoId2); + const Part::GeomCurve* p1 = dynamic_cast(this->getGeometry(GeoId1)); + const Part::GeomCurve* p2 = dynamic_cast(this->getGeometry(GeoId2)); if (p1 && p2) { + // TODO: Check if any of these are B-splines int i1 = sk.addGeometry(this->getGeometry(GeoId1)); int i2 = sk.addGeometry(this->getGeometry(GeoId2)); + if (p1->is() || + p2->is()) { + double p1ClosestParam, p2ClosestParam; + Base::Vector3d pt(px, py, 0); + p1->closestParameter(pt, p1ClosestParam); + p2->closestParameter(pt, p2ClosestParam); + + return sk.calculateAngleViaParams(i1, i2, p1ClosestParam, p2ClosestParam); + } + return sk.calculateAngleViaPoint(i1, i2, px, py); } else diff --git a/src/Mod/Sketcher/App/planegcs/Constraints.cpp b/src/Mod/Sketcher/App/planegcs/Constraints.cpp index aee8375afab3..f99bfbb69dbc 100644 --- a/src/Mod/Sketcher/App/planegcs/Constraints.cpp +++ b/src/Mod/Sketcher/App/planegcs/Constraints.cpp @@ -2650,6 +2650,205 @@ double ConstraintAngleViaPoint::grad(double* param) return scale * deriv; } +// -------------------------------------------------------- +// ConstraintAngleViaPointAndParam +ConstraintAngleViaPointAndParam::ConstraintAngleViaPointAndParam(Curve& acrv1, + Curve& acrv2, + Point p, + double* cparam, + double* angle) +{ + pvec.push_back(angle); + pvec.push_back(p.x); + pvec.push_back(p.y); + pvec.push_back(cparam); + acrv1.PushOwnParams(pvec); + acrv2.PushOwnParams(pvec); + crv1 = acrv1.Copy(); + crv2 = acrv2.Copy(); + origpvec = pvec; + pvecChangedFlag = true; + rescale(); +} + +ConstraintAngleViaPointAndParam::~ConstraintAngleViaPointAndParam() +{ + delete crv1; + crv1 = nullptr; + delete crv2; + crv2 = nullptr; +} + +void ConstraintAngleViaPointAndParam::ReconstructGeomPointers() +{ + int cnt = 0; + cnt++; // skip angle - we have an inline function for that + poa.x = pvec[cnt]; + cnt++; + poa.y = pvec[cnt]; + cnt++; + cnt++; // skip cparam + crv1->ReconstructOnNewPvec(pvec, cnt); + crv2->ReconstructOnNewPvec(pvec, cnt); + pvecChangedFlag = false; +} + +ConstraintType ConstraintAngleViaPointAndParam::getTypeId() +{ + return AngleViaPointAndParam; +} + +void ConstraintAngleViaPointAndParam::rescale(double coef) +{ + scale = coef * 1.; +} + +double ConstraintAngleViaPointAndParam::error() +{ + if (pvecChangedFlag) { + ReconstructGeomPointers(); + } + double ang = *angle(); + DeriVector2 n1 = crv1->CalculateNormal(cparam()); + DeriVector2 n2 = crv2->CalculateNormal(poa); + + // rotate n1 by angle + DeriVector2 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 ConstraintAngleViaPointAndParam::grad(double* param) +{ + // first of all, check that we need to compute anything. + if (findParamInPvec(param) == -1) { + return 0.0; + } + + double deriv = 0.; + + if (pvecChangedFlag) { + ReconstructGeomPointers(); + } + + if (param == angle()) { + deriv += -1.0; + } + DeriVector2 n1 = crv1->CalculateNormal(cparam(), param); + DeriVector2 n2 = crv2->CalculateNormal(poa, param); + deriv -= ((-n1.dx) * n1.y / pow(n1.length(), 2) + n1.dy * n1.x / pow(n1.length(), 2)); + deriv += ((-n2.dx) * n2.y / pow(n2.length(), 2) + n2.dy * n2.x / pow(n2.length(), 2)); + + return scale * deriv; +} + +// -------------------------------------------------------- +// ConstraintAngleViaPointAndTwoParams +ConstraintAngleViaPointAndTwoParams::ConstraintAngleViaPointAndTwoParams(Curve& acrv1, + Curve& acrv2, + Point p, + double* cparam1, + double* cparam2, + double* angle) +{ + pvec.push_back(angle); + pvec.push_back(p.x); + pvec.push_back(p.y); + pvec.push_back(cparam1); + pvec.push_back(cparam2); + acrv1.PushOwnParams(pvec); + acrv2.PushOwnParams(pvec); + crv1 = acrv1.Copy(); + crv2 = acrv2.Copy(); + origpvec = pvec; + pvecChangedFlag = true; + rescale(); +} + +ConstraintAngleViaPointAndTwoParams::~ConstraintAngleViaPointAndTwoParams() +{ + delete crv1; + crv1 = nullptr; + delete crv2; + crv2 = nullptr; +} + +void ConstraintAngleViaPointAndTwoParams::ReconstructGeomPointers() +{ + int cnt = 0; + cnt++; // skip angle - we have an inline function for that + poa.x = pvec[cnt]; + cnt++; + poa.y = pvec[cnt]; + cnt++; + cnt++; // skip cparam1 - we have an inline function for that + cnt++; // skip cparam2 - we have an inline function for that + crv1->ReconstructOnNewPvec(pvec, cnt); + crv2->ReconstructOnNewPvec(pvec, cnt); + pvecChangedFlag = false; +} + +ConstraintType ConstraintAngleViaPointAndTwoParams::getTypeId() +{ + return AngleViaPointAndTwoParams; +} + +void ConstraintAngleViaPointAndTwoParams::rescale(double coef) +{ + scale = coef * 1.; +} + +double ConstraintAngleViaPointAndTwoParams::error() +{ + if (pvecChangedFlag) { + ReconstructGeomPointers(); + } + double ang = *angle(); + DeriVector2 n1 = crv1->CalculateNormal(cparam1()); + DeriVector2 n2 = crv2->CalculateNormal(cparam2()); + + // rotate n1 by angle + DeriVector2 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 ConstraintAngleViaPointAndTwoParams::grad(double* param) +{ + // first of all, check that we need to compute anything. + if (findParamInPvec(param) == -1) { + return 0.0; + } + + double deriv = 0.; + + if (pvecChangedFlag) { + ReconstructGeomPointers(); + } + + if (param == angle()) { + deriv += -1.0; + } + DeriVector2 n1 = crv1->CalculateNormal(cparam1(), param); + DeriVector2 n2 = crv2->CalculateNormal(cparam2(), param); + deriv -= ((-n1.dx) * n1.y / pow(n1.length(), 2) + n1.dy * n1.x / pow(n1.length(), 2)); + deriv += ((-n2.dx) * n2.y / pow(n2.length(), 2) + n2.dy * n2.x / pow(n2.length(), 2)); + + return scale * deriv; +} + // -------------------------------------------------------- // ConstraintSnell diff --git a/src/Mod/Sketcher/App/planegcs/Constraints.h b/src/Mod/Sketcher/App/planegcs/Constraints.h index e4ff32edf9c9..d092789e9628 100644 --- a/src/Mod/Sketcher/App/planegcs/Constraints.h +++ b/src/Mod/Sketcher/App/planegcs/Constraints.h @@ -78,7 +78,9 @@ enum ConstraintType PointOnBSpline = 29, C2CDistance = 30, C2LDistance = 31, - P2CDistance = 32 + P2CDistance = 32, + AngleViaPointAndParam = 33, + AngleViaPointAndTwoParams = 34 }; enum InternalAlignmentType @@ -1173,6 +1175,91 @@ class ConstraintSnell: public Constraint double grad(double*) override; }; +class ConstraintAngleViaPointAndParam: public Constraint +{ +private: + inline double* angle() + { + return pvec[0]; + }; + inline double* cparam() + { + return pvec[3]; + }; + Curve* crv1; + 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: + // We assume first curve needs param1 + ConstraintAngleViaPointAndParam(Curve& acrv1, + Curve& acrv2, + Point p, + double* param1, + double* angle); + ~ConstraintAngleViaPointAndParam() override; + ConstraintType getTypeId() override; + void rescale(double coef = 1.) override; + double error() override; + double grad(double*) override; +}; + +// TODO: Do we need point here at all? +class ConstraintAngleViaPointAndTwoParams: public Constraint +{ +private: + inline double* angle() + { + return pvec[0]; + }; + inline double* cparam1() + { + return pvec[3]; + }; + inline double* cparam2() + { + return pvec[4]; + }; + Curve* crv1; + 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: + ConstraintAngleViaPointAndTwoParams(Curve& acrv1, + Curve& acrv2, + Point p, + double* param1, + double* param2, + double* angle); + ~ConstraintAngleViaPointAndTwoParams() override; + ConstraintType getTypeId() override; + void rescale(double coef = 1.) override; + double error() override; + double grad(double*) override; +}; + class ConstraintEqualLineLength: public Constraint { private: diff --git a/src/Mod/Sketcher/App/planegcs/GCS.cpp b/src/Mod/Sketcher/App/planegcs/GCS.cpp index 1b7bf211bca0..c7e9a19d964e 100644 --- a/src/Mod/Sketcher/App/planegcs/GCS.cpp +++ b/src/Mod/Sketcher/App/planegcs/GCS.cpp @@ -774,6 +774,36 @@ int System::addConstraintAngleViaPoint(Curve& crv1, return addConstraint(constr); } +int System::addConstraintAngleViaPointAndParam(Curve& crv1, + Curve& crv2, + Point& p, + double* cparam, + double* angle, + int tagId, + bool driving) +{ + Constraint* constr = new ConstraintAngleViaPointAndParam(crv1, crv2, p, cparam, angle); + constr->setTag(tagId); + constr->setDriving(driving); + return addConstraint(constr); +} + +int System::addConstraintAngleViaPointAndTwoParams(Curve& crv1, + Curve& crv2, + Point& p, + double* cparam1, + double* cparam2, + double* angle, + int tagId, + bool driving) +{ + Constraint* constr = + new ConstraintAngleViaPointAndTwoParams(crv1, crv2, p, cparam1, cparam2, angle); + constr->setTag(tagId); + constr->setDriving(driving); + return addConstraint(constr); +} + int System::addConstraintMidpointOnLine(Line& l1, Line& l2, int tagId, bool driving) { Constraint* constr = new ConstraintMidpointOnLine(l1, l2); @@ -1679,6 +1709,16 @@ System::calculateAngleViaPoint(const Curve& crv1, const Curve& crv2, Point& p1, return atan2(-n2.x * n1.y + n2.y * n1.x, n2.x * n1.x + n2.y * n1.y); } +double System::calculateAngleViaParams(const Curve& crv1, + const Curve& crv2, + double* param1, + double* param2) const +{ + GCS::DeriVector2 n1 = crv1.CalculateNormal(param1); + GCS::DeriVector2 n2 = crv2.CalculateNormal(param2); + return atan2(-n2.x * n1.y + n2.y * n1.x, n2.x * n1.x + n2.y * n1.y); +} + void System::calculateNormalAtPoint(const Curve& crv, const Point& p, double& rtnX, diff --git a/src/Mod/Sketcher/App/planegcs/GCS.h b/src/Mod/Sketcher/App/planegcs/GCS.h index 441a1e9af125..68175e3e181a 100644 --- a/src/Mod/Sketcher/App/planegcs/GCS.h +++ b/src/Mod/Sketcher/App/planegcs/GCS.h @@ -317,6 +317,21 @@ class SketcherExport System double* angle, int tagId = 0, bool driving = true); + int addConstraintAngleViaPointAndParam(Curve& crv1, + Curve& crv2, + Point& p, + double* cparam, + double* angle, + int tagId = 0, + bool driving = true); + int addConstraintAngleViaPointAndTwoParams(Curve& crv1, + Curve& crv2, + Point& p, + double* cparam1, + double* cparam2, + double* angle, + int tagId = 0, + bool driving = true); int addConstraintMidpointOnLine(Line& l1, Line& l2, int tagId = 0, bool driving = true); int addConstraintMidpointOnLine(Point& l1p1, Point& l1p2, @@ -501,6 +516,10 @@ class SketcherExport System double calculateAngleViaPoint(const Curve& crv1, const Curve& crv2, Point& p) const; double calculateAngleViaPoint(const Curve& crv1, const Curve& crv2, Point& p1, Point& p2) const; + double calculateAngleViaParams(const Curve& crv1, + const Curve& crv2, + double* param1, + double* param2) const; void calculateNormalAtPoint(const Curve& crv, const Point& p, double& rtnX, double& rtnY) const; // Calculates errors of all constraints which have a tag equal to diff --git a/src/Mod/Sketcher/App/planegcs/Geo.cpp b/src/Mod/Sketcher/App/planegcs/Geo.cpp index b085384fd6c7..b46ce7c0f71b 100644 --- a/src/Mod/Sketcher/App/planegcs/Geo.cpp +++ b/src/Mod/Sketcher/App/planegcs/Geo.cpp @@ -773,14 +773,265 @@ DeriVector2 BSpline::CalculateNormal(const Point& p, const double* derivparam) c return ret; } -DeriVector2 BSpline::Value(double /*u*/, double /*du*/, const double* /*derivparam*/) const +DeriVector2 BSpline::CalculateNormal(const double* param, const double* derivparam) const { + // TODO: is there any advantage in making this a `static`? + size_t startpole = 0; + for (size_t j = 1; j < mult.size() && *(knots[j]) <= *param; ++j) { + startpole += mult[j]; + } + if (!periodic && startpole >= poles.size()) { + startpole = poles.size() - degree - 1; + } + + auto polexat = [&](size_t i) { + return poles[(startpole + i) % poles.size()].x; + }; + auto poleyat = [&](size_t i) { + return poles[(startpole + i) % poles.size()].y; + }; + auto weightat = [&](size_t i) { + return weights[(startpole + i) % weights.size()]; + }; + + double xsum, xslopesum; + double ysum, yslopesum; + double wsum, wslopesum; + + valueHomogenous(*param, &xsum, &ysum, &wsum, &xslopesum, &yslopesum, &wslopesum); + + // Tangent vector + // This should in principle be identical to error gradient wrt curve parameter in + // point-on-object + DeriVector2 result(wsum * xslopesum - wslopesum * xsum, wsum * yslopesum - wslopesum * ysum); + + size_t numpoints = degree + 1; + + // FIXME: Find an appropriate name for this method + auto doSomething = [&](size_t i, double& factor, double& slopefactor) { + VEC_D d(numpoints); + d[i] = 1; + factor = BSpline::splineValue(*param, startpole + degree, degree, d, flattenedknots); + VEC_D sd(numpoints - 1); + if (i > 0) { + sd[i - 1] = + 1.0 / (flattenedknots[startpole + i + degree] - flattenedknots[startpole + i]); + } + if (i < numpoints - 1) { + sd[i] = -1.0 + / (flattenedknots[startpole + i + 1 + degree] - flattenedknots[startpole + i + 1]); + } + slopefactor = + BSpline::splineValue(*param, startpole + degree, degree - 1, sd, flattenedknots); + }; + + // get dx, dy of the normal as well + for (size_t i = 0; i < numpoints; ++i) { + if (derivparam == polexat(i)) { + double factor, slopefactor; + doSomething(i, factor, slopefactor); + result.dx = *weightat(i) * (wsum * slopefactor - wslopesum * factor); + break; + } + if (derivparam == poleyat(i)) { + double factor, slopefactor; + doSomething(i, factor, slopefactor); + result.dy = *weightat(i) * (wsum * slopefactor - wslopesum * factor); + break; + } + if (derivparam == weightat(i)) { + double factor, slopefactor; + doSomething(i, factor, slopefactor); + result.dx = degree + * (factor * (xslopesum - wslopesum * (*polexat(i))) + - slopefactor * (xsum - wsum * (*polexat(i)))); + result.dy = degree + * (factor * (yslopesum - wslopesum * (*poleyat(i))) + - slopefactor * (ysum - wsum * (*poleyat(i)))); + break; + } + } + + // the curve parameter being used by the constraint is not known to the geometry (there can be + // many tangent constraints on the same curve after all). Assume that this is the param + // provided. + if (derivparam != param) { + return result.rotate90ccw(); + } + + // derivparam == param now. Done this way just to reduce "cognitive complexity". + VEC_D sd(numpoints - 1), ssd(numpoints - 2); + for (size_t i = 1; i < numpoints; ++i) { + sd[i - 1] = (*weightat(i) - *weightat(i - 1)) + / (flattenedknots[startpole + i + degree] - flattenedknots[startpole + i]); + } + for (size_t i = 1; i < numpoints - 1; ++i) { + ssd[i - 1] = (sd[i] - sd[i - 1]) + / (flattenedknots[startpole + i + degree] - flattenedknots[startpole + i]); + } + double wslopeslopesum = degree * (degree - 1) + * BSpline::splineValue(*param, startpole + degree, degree - 2, ssd, flattenedknots); + + for (size_t i = 1; i < numpoints; ++i) { + sd[i - 1] = (*polexat(i) * *weightat(i) - *polexat(i - 1) * *weightat(i - 1)) + / (flattenedknots[startpole + i + degree] - flattenedknots[startpole + i]); + } + for (size_t i = 1; i < numpoints - 1; ++i) { + ssd[i - 1] = (sd[i] - sd[i - 1]) + / (flattenedknots[startpole + i + degree] - flattenedknots[startpole + i]); + } + double xslopeslopesum = degree * (degree - 1) + * BSpline::splineValue(*param, startpole + degree, degree - 2, ssd, flattenedknots); + + for (size_t i = 1; i < numpoints; ++i) { + sd[i - 1] = (*poleyat(i) * *weightat(i) - *poleyat(i - 1) * *weightat(i - 1)) + / (flattenedknots[startpole + i + degree] - flattenedknots[startpole + i]); + } + for (size_t i = 1; i < numpoints - 1; ++i) { + ssd[i - 1] = (sd[i] - sd[i - 1]) + / (flattenedknots[startpole + i + degree] - flattenedknots[startpole + i]); + } + double yslopeslopesum = degree * (degree - 1) + * BSpline::splineValue(*param, startpole + degree, degree - 2, ssd, flattenedknots); + + result.dx = wsum * xslopeslopesum - wslopeslopesum * xsum; + result.dy = wsum * yslopeslopesum - wslopeslopesum * ysum; + + return result.rotate90ccw(); +} + +DeriVector2 BSpline::Value(double u, double /*du*/, const double* /*derivparam*/) const +{ + // TODO: is there any advantage in making this a `static`? + size_t startpole = 0; + for (size_t j = 1; j < mult.size() && *(knots[j]) <= u; ++j) { + startpole += mult[j]; + } + if (!periodic && startpole >= poles.size()) { + startpole = poles.size() - degree - 1; + } + + // double xsum = 0., xslopesum = 0.; + // double ysum = 0., yslopesum = 0.; + // double wsum = 0., wslopesum = 0.; + + auto polexat = [&](size_t i) { + return poles[(startpole + i) % poles.size()].x; + }; + auto poleyat = [&](size_t i) { + return poles[(startpole + i) % poles.size()].y; + }; + auto weightat = [&](size_t i) { + return weights[(startpole + i) % weights.size()]; + }; + + size_t numpoints = degree + 1; + // Tangent vector + // This should in principle be identical to error gradient wrt curve parameter in + // point-on-object + VEC_D d(numpoints); + for (size_t i = 0; i < numpoints; ++i) { + d[i] = *polexat(i) * *weightat(i); + } + double xsum = BSpline::splineValue(u, startpole + degree, degree, d, flattenedknots); + for (size_t i = 0; i < numpoints; ++i) { + d[i] = *poleyat(i) * *weightat(i); + } + double ysum = BSpline::splineValue(u, startpole + degree, degree, d, flattenedknots); + for (size_t i = 0; i < numpoints; ++i) { + d[i] = *weightat(i); + } + double wsum = BSpline::splineValue(u, startpole + degree, degree, d, flattenedknots); + + d.resize(numpoints - 1); + for (size_t i = 1; i < numpoints; ++i) { + d[i - 1] = (*polexat(i) * *weightat(i) - *polexat(i - 1) * *weightat(i - 1)) + / (flattenedknots[startpole + i + degree] - flattenedknots[startpole + i]); + } + double xslopesum = + degree * BSpline::splineValue(u, startpole + degree, degree - 1, d, flattenedknots); + for (size_t i = 1; i < numpoints; ++i) { + d[i - 1] = (*poleyat(i) * *weightat(i) - *poleyat(i - 1) * *weightat(i - 1)) + / (flattenedknots[startpole + i + degree] - flattenedknots[startpole + i]); + } + double yslopesum = + degree * BSpline::splineValue(u, startpole + degree, degree - 1, d, flattenedknots); + for (size_t i = 1; i < numpoints; ++i) { + d[i - 1] = (*weightat(i) - *weightat(i - 1)) + / (flattenedknots[startpole + i + degree] - flattenedknots[startpole + i]); + } + double wslopesum = + degree * BSpline::splineValue(u, startpole + degree, degree - 1, d, flattenedknots); + // place holder - DeriVector2 ret = DeriVector2(); + DeriVector2 ret = DeriVector2(xsum / wsum, + ysum / wsum, + (wsum * xslopesum - wslopesum * xsum) / wsum / wsum, + (wsum * yslopesum - wslopesum * ysum) / wsum / wsum); return ret; } +void BSpline::valueHomogenous(const double u, + double* xw, + double* yw, + double* w, + double* dxwdu, + double* dywdu, + double* dwdu) const +{ + // TODO: is there any advantage in making this a `static`? + size_t startpole = 0; + for (size_t j = 1; j < mult.size() && *(knots[j]) <= u; ++j) { + startpole += mult[j]; + } + if (!periodic && startpole >= poles.size()) { + startpole = poles.size() - degree - 1; + } + + auto polexat = [&](size_t i) { + return poles[(startpole + i) % poles.size()].x; + }; + auto poleyat = [&](size_t i) { + return poles[(startpole + i) % poles.size()].y; + }; + auto weightat = [&](size_t i) { + return weights[(startpole + i) % weights.size()]; + }; + + size_t numpoints = degree + 1; + VEC_D d(numpoints); + for (size_t i = 0; i < numpoints; ++i) { + d[i] = *polexat(i) * *weightat(i); + } + *xw = BSpline::splineValue(u, startpole + degree, degree, d, flattenedknots); + for (size_t i = 0; i < numpoints; ++i) { + d[i] = *poleyat(i) * *weightat(i); + } + *yw = BSpline::splineValue(u, startpole + degree, degree, d, flattenedknots); + for (size_t i = 0; i < numpoints; ++i) { + d[i] = *weightat(i); + } + *w = BSpline::splineValue(u, startpole + degree, degree, d, flattenedknots); + + d.resize(numpoints - 1); + for (size_t i = 1; i < numpoints; ++i) { + d[i - 1] = (*polexat(i) * *weightat(i) - *polexat(i - 1) * *weightat(i - 1)) + / (flattenedknots[startpole + i + degree] - flattenedknots[startpole + i]); + } + *dxwdu = degree * BSpline::splineValue(u, startpole + degree, degree - 1, d, flattenedknots); + for (size_t i = 1; i < numpoints; ++i) { + d[i - 1] = (*poleyat(i) * *weightat(i) - *poleyat(i - 1) * *weightat(i - 1)) + / (flattenedknots[startpole + i + degree] - flattenedknots[startpole + i]); + } + *dywdu = degree * BSpline::splineValue(u, startpole + degree, degree - 1, d, flattenedknots); + for (size_t i = 1; i < numpoints; ++i) { + d[i - 1] = (*weightat(i) - *weightat(i - 1)) + / (flattenedknots[startpole + i + degree] - flattenedknots[startpole + i]); + } + *dwdu = degree * BSpline::splineValue(u, startpole + degree, degree - 1, d, flattenedknots); +} + int BSpline::PushOwnParams(VEC_pD& pvec) { std::size_t cnt = 0; diff --git a/src/Mod/Sketcher/App/planegcs/Geo.h b/src/Mod/Sketcher/App/planegcs/Geo.h index 91f0eb6f6f3a..4f78061a6e56 100644 --- a/src/Mod/Sketcher/App/planegcs/Geo.h +++ b/src/Mod/Sketcher/App/planegcs/Geo.h @@ -25,10 +25,11 @@ #include "Util.h" #include +#include "../../SketcherGlobal.h" namespace GCS { -class Point +class SketcherExport Point { public: Point() @@ -62,7 +63,7 @@ static constexpr double pi_18 = pi / 18.0; /// manually as well. The class also provides a bunch of methods to do math /// on it (and derivatives are calculated implicitly). /// -class DeriVector2 +class SketcherExport DeriVector2 { public: DeriVector2() @@ -146,7 +147,8 @@ class DeriVector2 // Geometries /////////////////////////////////////// -class Curve // a base class for all curve-based objects (line, circle/arc, ellipse/arc) +/// A base class for all curve-based objects (line, circle/arc, ellipse/arc). +class SketcherExport Curve { public: virtual ~Curve() @@ -161,6 +163,15 @@ class Curve // a base class for all curve-based objects (line, circle/arc, elli virtual DeriVector2 CalculateNormal(const Point& p, const double* derivparam = nullptr) const = 0; + // returns normal vector at parameter instead of at the given point. + virtual DeriVector2 CalculateNormal(const double* param, + const double* derivparam = nullptr) const + { + DeriVector2 pointDV = Value(*param, 0.0); + Point p(&pointDV.x, &pointDV.y); + return CalculateNormal(p, derivparam); + } + /** * @brief Value: returns point (vector) given the value of parameter * @param u: value of parameter @@ -180,7 +191,7 @@ class Curve // a base class for all curve-based objects (line, circle/arc, elli virtual Curve* Copy() = 0; }; -class Line: public Curve +class SketcherExport Line: public Curve { public: Line() @@ -196,7 +207,7 @@ class Line: public Curve Line* Copy() override; }; -class Circle: public Curve +class SketcherExport Circle: public Curve { public: Circle() @@ -214,7 +225,7 @@ class Circle: public Curve Circle* Copy() override; }; -class Arc: public Circle +class SketcherExport Arc: public Circle { public: Arc() @@ -236,7 +247,7 @@ class Arc: public Circle Arc* Copy() override; }; -class MajorRadiusConic: public Curve +class SketcherExport MajorRadiusConic: public Curve { public: ~MajorRadiusConic() override @@ -251,7 +262,7 @@ class MajorRadiusConic: public Curve // DeriVector2 CalculateNormal(Point &p, double* derivparam = 0) = 0; }; -class Ellipse: public MajorRadiusConic +class SketcherExport Ellipse: public MajorRadiusConic { public: Ellipse() @@ -277,7 +288,7 @@ class Ellipse: public MajorRadiusConic Ellipse* Copy() override; }; -class ArcOfEllipse: public Ellipse +class SketcherExport ArcOfEllipse: public Ellipse { public: ArcOfEllipse() @@ -301,7 +312,7 @@ class ArcOfEllipse: public Ellipse ArcOfEllipse* Copy() override; }; -class Hyperbola: public MajorRadiusConic +class SketcherExport Hyperbola: public MajorRadiusConic { public: Hyperbola() @@ -327,7 +338,7 @@ class Hyperbola: public MajorRadiusConic Hyperbola* Copy() override; }; -class ArcOfHyperbola: public Hyperbola +class SketcherExport ArcOfHyperbola: public Hyperbola { public: ArcOfHyperbola() @@ -349,7 +360,7 @@ class ArcOfHyperbola: public Hyperbola ArcOfHyperbola* Copy() override; }; -class Parabola: public Curve +class SketcherExport Parabola: public Curve { public: Parabola() @@ -365,7 +376,7 @@ class Parabola: public Curve Parabola* Copy() override; }; -class ArcOfParabola: public Parabola +class SketcherExport ArcOfParabola: public Parabola { public: ArcOfParabola() @@ -386,7 +397,7 @@ class ArcOfParabola: public Parabola ArcOfParabola* Copy() override; }; -class BSpline: public Curve +class SketcherExport BSpline: public Curve { public: BSpline() @@ -414,7 +425,18 @@ class BSpline: public Curve // interface helpers VEC_D flattenedknots; DeriVector2 CalculateNormal(const Point& p, const double* derivparam = nullptr) const override; + // TODO: override parametric version + DeriVector2 CalculateNormal(const double* param, + const double* derivparam = nullptr) const override; DeriVector2 Value(double u, double du, const double* derivparam = nullptr) const override; + // Returns value in homogenous coordinates (x*w, y*w, w) at given parameter u + void valueHomogenous(const double u, + double* xw, + double* yw, + double* w, + double* dxwdu, + double* dywdu, + double* dwdu) const; int PushOwnParams(VEC_pD& pvec) override; void ReconstructOnNewPvec(VEC_pD& pvec, int& cnt) override; BSpline* Copy() override; @@ -433,7 +455,7 @@ class BSpline: public Curve /// x is the point at which combination is needed /// k is the range in `flattenedknots` that contains x /// p is the degree - /// d is the vector of (relevant) poles (this will be changed) + /// d is the vector of (relevant) poles (note that this is not const and will be changed) /// flatknots is the vector of knots static double splineValue(double x, size_t k, unsigned int p, VEC_D& d, const VEC_D& flatknots); }; diff --git a/src/Mod/Sketcher/Gui/CommandConstraints.cpp b/src/Mod/Sketcher/Gui/CommandConstraints.cpp index 3e52a8707070..fa5e13c066cc 100644 --- a/src/Mod/Sketcher/Gui/CommandConstraints.cpp +++ b/src/Mod/Sketcher/Gui/CommandConstraints.cpp @@ -187,6 +187,54 @@ bool isGeoConcentricCompatible(const Part::Geometry* geo) return (isEllipse(*geo) || isArcOfEllipse(*geo) || isCircle(*geo) || isArcOfCircle(*geo)); } +// Removes point-on-object constraints made redundant with certain constraints +// under certain conditions. Currently, that happens only when the constraint is on +// a B-spline, for 3-selection tangent, perpendicular, and angle constraints. +// Returns true if constraints were removed. +// GeoId3 HAS to be the point, and the other two are the curves. +bool removeRedundantPointOnObject(SketchObject* Obj, int GeoId1, int GeoId2, int GeoId3) +{ + const std::vector& cvals = Obj->Constraints.getValues(); + + std::vector cidsToBeRemoved; + + int cid = 0; + for (auto it = cvals.begin(); it != cvals.end(); ++it, ++cid) { + if ((*it)->Type == Sketcher::PointOnObject && + (((*it)->First == GeoId3 && (*it)->Second == GeoId1) || + ((*it)->First == GeoId3 && (*it)->Second == GeoId2))) { + + // ONLY do this if it is a B-spline (or any other where point + // on object is implied). + const Part::Geometry* geom = Obj->getGeometry((*it)->Second); + if (isBSplineCurve(*geom)) + cidsToBeRemoved.push_back(cid); + } + } + + if (!cidsToBeRemoved.empty()) { + for (auto it = cidsToBeRemoved.rbegin(); it != cidsToBeRemoved.rend(); ++it) { + Gui::cmdAppObjectArgs(Obj, + "delConstraint(%d)", + *it);// remove the preexisting point on object constraint. + } + + // A substitution requires a solve() so that the autoremove redundants works when + // Autorecompute not active. However, delConstraint includes such solve() internally. So + // at this point it is already solved. + tryAutoRecomputeIfNotSolve(Obj); + + notifyConstraintSubstitutions(QObject::tr("One or two point on object constraint(s) was/were deleted, " + "since the latest constraint being applied internally applies point-on-object as well.")); + + // TODO: find way to get selection here, or clear elsewhere + // getSelection().clearSelection(); + return true; + } + + return false; +} + /// Makes an angle constraint between 2 lines void SketcherGui::makeAngleBetweenTwoLines(Sketcher::SketchObject* Obj, Gui::Command* cmd, @@ -232,8 +280,6 @@ void SketcherGui::makeAngleBetweenTwoLines(Sketcher::SketchObject* Obj, } } - - bool SketcherGui::calculateAngle(Sketcher::SketchObject* Obj, int& GeoId1, int& GeoId2, Sketcher::PointPos& PosId1, Sketcher::PointPos& PosId2, double& ActAngle) { const Part::Geometry* geom1 = Obj->getGeometry(GeoId1); @@ -312,7 +358,6 @@ bool SketcherGui::calculateAngle(Sketcher::SketchObject* Obj, int& GeoId1, int& return true; } - /// Makes a simple tangency constraint using extra point + tangent via point /// ellipse => an ellipse /// geom2 => any of an ellipse, an arc of ellipse, a circle, or an arc (of circle) @@ -1255,41 +1300,43 @@ class CmdSketcherCompConstrainTools : public Gui::GroupCommand class GeomSelectionSizes { public: - GeomSelectionSizes(size_t s_pts, size_t s_lns, size_t s_cir, size_t s_ell) : - s_pts(s_pts), s_lns(s_lns), s_cir(s_cir), s_ell(s_ell) {} + GeomSelectionSizes(size_t s_pts, size_t s_lns, size_t s_cir, size_t s_ell, size_t s_spl) : + s_pts(s_pts), s_lns(s_lns), s_cir(s_cir), s_ell(s_ell), s_spl(s_spl) {} ~GeomSelectionSizes() {} bool hasPoints() const { return s_pts > 0; } bool hasLines() const { return s_lns > 0; } bool hasCirclesOrArcs() const { return s_cir > 0; } bool hasEllipseAndCo() const { return s_ell > 0; } - - bool has1Point() const { return s_pts == 1 && s_lns == 0 && s_cir == 0 && s_ell == 0; } - bool has2Points() const { return s_pts == 2 && s_lns == 0 && s_cir == 0 && s_ell == 0; } - bool has1Point1Line() const { return s_pts == 1 && s_lns == 1 && s_cir == 0 && s_ell == 0; } - bool has3Points() const { return s_pts == 3 && s_lns == 0 && s_cir == 0 && s_ell == 0; } - bool has4MorePoints() const { return s_pts >= 4 && s_lns == 0 && s_cir == 0 && s_ell == 0; } - bool has2Points1Line() const { return s_pts == 2 && s_lns == 1 && s_cir == 0 && s_ell == 0; } - bool has3MorePoints1Line() const { return s_pts >= 3 && s_lns == 1 && s_cir == 0 && s_ell == 0; } - bool has1Point1Circle() const { return s_pts == 1 && s_lns == 0 && s_cir == 1 && s_ell == 0; } - bool has1MorePoint1Ellipse() const { return s_pts >= 1 && s_lns == 0 && s_cir == 0 && s_ell == 1; } - - bool has1Line() const { return s_pts == 0 && s_lns == 1 && s_cir == 0 && s_ell == 0; } - bool has2Lines() const { return s_pts == 0 && s_lns == 2 && s_cir == 0 && s_ell == 0; } - bool has3MoreLines() const { return s_pts == 0 && s_lns >= 3 && s_cir == 0 && s_ell == 0; } - bool has1Line1Circle() const { return s_pts == 0 && s_lns == 1 && s_cir == 1 && s_ell == 0; } - bool has1Line2Circles() const { return s_pts == 0 && s_lns == 1 && s_cir == 2 && s_ell == 0; } - bool has1Line1Ellipse() const { return s_pts == 0 && s_lns == 1 && s_cir == 0 && s_ell == 1; } - - bool has1Circle() const { return s_pts == 0 && s_lns == 0 && s_cir == 1 && s_ell == 0; } - bool has2Circles() const { return s_pts == 0 && s_lns == 0 && s_cir == 2 && s_ell == 0; } - bool has3MoreCircles() const { return s_pts == 0 && s_lns == 0 && s_cir >= 3 && s_ell == 0; } - bool has1Circle1Ellipse() const { return s_pts == 0 && s_lns == 0 && s_cir == 1 && s_ell == 1; } - - bool has1Ellipse() const { return s_pts == 0 && s_lns == 0 && s_cir == 0 && s_ell == 1; } - bool has2MoreEllipses() const { return s_pts == 0 && s_lns == 0 && s_cir == 0 && s_ell >= 2; } - - size_t s_pts, s_lns, s_cir, s_ell; + bool hasSplineAndCo() const { return s_spl > 0; } + + bool has1Point() const { return s_pts == 1 && s_lns == 0 && s_cir == 0 && s_ell == 0 && s_spl == 0; } + bool has2Points() const { return s_pts == 2 && s_lns == 0 && s_cir == 0 && s_ell == 0 && s_spl == 0; } + bool has1Point1Line() const { return s_pts == 1 && s_lns == 1 && s_cir == 0 && s_ell == 0 && s_spl == 0; } + bool has3Points() const { return s_pts == 3 && s_lns == 0 && s_cir == 0 && s_ell == 0 && s_spl == 0; } + bool has4MorePoints() const { return s_pts >= 4 && s_lns == 0 && s_cir == 0 && s_ell == 0 && s_spl == 0; } + bool has2Points1Line() const { return s_pts == 2 && s_lns == 1 && s_cir == 0 && s_ell == 0 && s_spl == 0; } + bool has3MorePoints1Line() const { return s_pts >= 3 && s_lns == 1 && s_cir == 0 && s_ell == 0 && s_spl == 0; } + bool has1Point1Circle() const { return s_pts == 1 && s_lns == 0 && s_cir == 1 && s_ell == 0 && s_spl == 0; } + bool has1MorePoint1Ellipse() const { return s_pts >= 1 && s_lns == 0 && s_cir == 0 && s_ell == 1 && s_spl == 0; } + + bool has1Line() const { return s_pts == 0 && s_lns == 1 && s_cir == 0 && s_ell == 0 && s_spl == 0; } + bool has2Lines() const { return s_pts == 0 && s_lns == 2 && s_cir == 0 && s_ell == 0 && s_spl == 0; } + bool has3MoreLines() const { return s_pts == 0 && s_lns >= 3 && s_cir == 0 && s_ell == 0 && s_spl == 0; } + bool has1Line1Circle() const { return s_pts == 0 && s_lns == 1 && s_cir == 1 && s_ell == 0 && s_spl == 0; } + bool has1Line2Circles() const { return s_pts == 0 && s_lns == 1 && s_cir == 2 && s_ell == 0 && s_spl == 0; } + bool has1Line1Ellipse() const { return s_pts == 0 && s_lns == 1 && s_cir == 0 && s_ell == 1 && s_spl == 0; } + + bool has1Circle() const { return s_pts == 0 && s_lns == 0 && s_cir == 1 && s_ell == 0 && s_spl == 0; } + bool has2Circles() const { return s_pts == 0 && s_lns == 0 && s_cir == 2 && s_ell == 0 && s_spl == 0; } + bool has3MoreCircles() const { return s_pts == 0 && s_lns == 0 && s_cir >= 3 && s_ell == 0 && s_spl == 0; } + bool has1Circle1Ellipse() const { return s_pts == 0 && s_lns == 0 && s_cir == 1 && s_ell == 1 && s_spl == 0; } + + bool has1Ellipse() const { return s_pts == 0 && s_lns == 0 && s_cir == 0 && s_ell == 1 && s_spl == 0; } + bool has2MoreEllipses() const { return s_pts == 0 && s_lns == 0 && s_cir == 0 && s_ell >= 2 && s_spl == 0; } + bool has1Point1Spline1MoreEdge() const { return s_pts == 1 && s_spl >= 1 && (s_lns + s_cir + s_ell + s_spl) == 2; } + + size_t s_pts, s_lns, s_cir, s_ell, s_spl; }; class DrawSketchHandlerDimension : public DrawSketchHandler @@ -1535,6 +1582,7 @@ class DrawSketchHandlerDimension : public DrawSketchHandler std::vector selLine; std::vector selCircleArc; std::vector selEllipseAndCo; + std::vector selSplineAndCo; std::vector initialSelection; @@ -1578,6 +1626,7 @@ class DrawSketchHandlerDimension : public DrawSketchHandler selLine.clear(); selCircleArc.clear(); selEllipseAndCo.clear(); + selSplineAndCo.clear(); } } @@ -1621,7 +1670,8 @@ class DrawSketchHandlerDimension : public DrawSketchHandler } } - std::vector& getSelectionVector(Base::Type selGeoType) { + std::vector& getSelectionVector(Base::Type selGeoType) + { if (selGeoType == Part::GeomPoint::getClassTypeId()) { return selPoints; } @@ -1638,6 +1688,9 @@ class DrawSketchHandlerDimension : public DrawSketchHandler selGeoType == Part::GeomArcOfParabola::getClassTypeId()) { return selEllipseAndCo; } + else if (selGeoType == Part::GeomBSplineCurve::getClassTypeId()) { + return selSplineAndCo; + } static std::vector emptyVector; return emptyVector; @@ -1668,12 +1721,13 @@ class DrawSketchHandlerDimension : public DrawSketchHandler bool makeAppropriateConstraint(Base::Vector2d onSketchPos) { bool selAllowed = false; - GeomSelectionSizes selection(selPoints.size(), selLine.size(), selCircleArc.size(), selEllipseAndCo.size()); + GeomSelectionSizes selection(selPoints.size(), selLine.size(), selCircleArc.size(), selEllipseAndCo.size(), selSplineAndCo.size()); if (selection.hasPoints()) { if (selection.has1Point()) { makeCts_1Point(selAllowed, onSketchPos); } else if (selection.has2Points()) { makeCts_2Point(selAllowed, onSketchPos); } else if (selection.has1Point1Line()) { makeCts_1Point1Line(selAllowed, onSketchPos); } + else if (selection.has1Point1Spline1MoreEdge()) { makeCts_1Point1Spline1MoreEdge(selAllowed);} else if (selection.has3Points()) { makeCts_3Point(selAllowed, selection.s_pts); } else if (selection.has4MorePoints()) { makeCts_4MorePoint(selAllowed, selection.s_pts); } else if (selection.has2Points1Line()) { makeCts_2Point1Line(selAllowed, onSketchPos, selection.s_pts); } @@ -1776,6 +1830,17 @@ class DrawSketchHandlerDimension : public DrawSketchHandler } } + void makeCts_1Point1Spline1MoreEdge(bool& /*selAllowed*/) + { + //angle + if (availableConstraint == AvailableConstraint::FIRST) { + // FIXME: Once everything is implemented uncomment restartCommand and setAllowed + // restartCommand(QT_TRANSLATE_NOOP("Command", "Add 'Angle' constraint")); + // TODO: Find the appropriate geoids and call createAngleConstrain + // selAllowed = true; + } + } + void makeCts_4MorePoint(bool& selAllowed, size_t s_pts) { //Horizontal, vertical @@ -5699,11 +5764,11 @@ void CmdSketcherConstrainPerpendicular::activated(int iMsg) 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 (isEdge(GeoId1, PosId1) && isEdge(GeoId2, PosId2) && isVertex(GeoId3, PosId3)) { @@ -5720,35 +5785,45 @@ void CmdSketcherConstrainPerpendicular::activated(int iMsg) bool safe = addConstraintSafely(Obj, [&]() { // add missing point-on-object constraints if (!IsPointAlreadyOnCurve(GeoId1, GeoId3, PosId3, Obj)) { - Gui::cmdAppObjectArgs( - selection[0].getObject(), - "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))", - GeoId3, - static_cast(PosId3), - GeoId1); - }; + const Part::Geometry *geom1 = Obj->getGeometry(GeoId1); + if (!(geom1 && isBSplineCurve(*geom1))) { + Gui::cmdAppObjectArgs( + selection[0].getObject(), + "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))", + GeoId3, + static_cast(PosId3), + GeoId1); + } + } if (!IsPointAlreadyOnCurve(GeoId2, GeoId3, PosId3, Obj)) { - Gui::cmdAppObjectArgs( - selection[0].getObject(), - "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))", - GeoId3, - static_cast(PosId3), - GeoId2); - }; + const Part::Geometry *geom2 = Obj->getGeometry(GeoId2); + if (!(geom2 && isBSplineCurve(*geom2))) { + Gui::cmdAppObjectArgs( + selection[0].getObject(), + "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))", + GeoId3, + static_cast(PosId3), + GeoId2); + } + } if (!IsPointAlreadyOnCurve( GeoId1, GeoId3, PosId3, - Obj)) {// FIXME: it's a good idea to add a check if the sketch is solved - Gui::cmdAppObjectArgs( - selection[0].getObject(), - "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))", - GeoId3, - static_cast(PosId3), - GeoId1); - }; + Obj)) { + // FIXME: it's a good idea to add a check if the sketch is solved + const Part::Geometry *geom1 = Obj->getGeometry(GeoId1); + if (!(geom1 && isBSplineCurve(*geom1))) { + Gui::cmdAppObjectArgs( + selection[0].getObject(), + "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))", + GeoId3, + static_cast(PosId3), + GeoId1); + } + } Gui::cmdAppObjectArgs( selection[0].getObject(), @@ -5757,6 +5832,8 @@ void CmdSketcherConstrainPerpendicular::activated(int iMsg) GeoId2, GeoId3, static_cast(PosId3)); + + removeRedundantPointOnObject(Obj, GeoId1, GeoId2, GeoId3); }); if (!safe) { @@ -5770,7 +5847,7 @@ void CmdSketcherConstrainPerpendicular::activated(int iMsg) getSelection().clearSelection(); return; - }; + } Gui::TranslatedUserWarning( Obj, @@ -5833,15 +5910,6 @@ void CmdSketcherConstrainPerpendicular::activated(int iMsg) const Part::Geometry* geom2 = Obj->getGeometry(GeoId2); - if (geom2 && isBSplineCurve(*geom2)) { - // unsupported until normal to B-spline at any point implemented. - Gui::TranslatedUserWarning( - Obj, - QObject::tr("Wrong selection"), - QObject::tr("Perpendicular to B-spline edge currently unsupported.")); - return; - } - if (isBsplinePole(geom2)) { Gui::TranslatedUserWarning( Obj, @@ -5879,14 +5947,14 @@ void CmdSketcherConstrainPerpendicular::activated(int iMsg) return; } - if (isBSplineCurve(*geo1) || isBSplineCurve(*geo2)) { - // unsupported until tangent to B-spline at any point implemented. - Gui::TranslatedUserWarning( - Obj, - QObject::tr("Wrong selection"), - QObject::tr("Perpendicular to B-spline edge currently unsupported.")); - return; - } + // if (isBSplineCurve(*geo1) || isBSplineCurve(*geo2)) { + // // unsupported until tangent to B-spline at any point implemented. + // Gui::TranslatedUserWarning( + // Obj, + // QObject::tr("Wrong selection"), + // QObject::tr("Perpendicular to B-spline edge currently unsupported.")); + // return; + // } if (isLineSegment(*geo1)) { std::swap(GeoId1, GeoId2); @@ -6080,14 +6148,14 @@ void CmdSketcherConstrainPerpendicular::applyConstraint(std::vector& return; } - if (isBSplineCurve(*geo1) || isBSplineCurve(*geo2)) { - // unsupported until tangent to B-spline at any point implemented. - Gui::TranslatedUserWarning( - Obj, - QObject::tr("Wrong selection"), - QObject::tr("Perpendicular to B-spline edge currently unsupported.")); - return; - } + // if (isBSplineCurve(*geo1) || isBSplineCurve(*geo2)) { + // // unsupported until tangent to B-spline at any point implemented. + // Gui::TranslatedUserWarning( + // Obj, + // QObject::tr("Wrong selection"), + // QObject::tr("Perpendicular to B-spline edge currently unsupported.")); + // return; + // } if (isLineSegment(*geo1)) { std::swap(GeoId1, GeoId2); @@ -6285,35 +6353,41 @@ void CmdSketcherConstrainPerpendicular::applyConstraint(std::vector& bool safe = addConstraintSafely(Obj, [&]() { // add missing point-on-object constraints if (!IsPointAlreadyOnCurve(GeoId1, GeoId3, PosId3, Obj)) { - Gui::cmdAppObjectArgs( - Obj, - "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))", - GeoId3, - static_cast(PosId3), - GeoId1); - }; + const Part::Geometry *geom1 = Obj->getGeometry(GeoId1); + if (!(geom1 && isBSplineCurve(*geom1))) { + Gui::cmdAppObjectArgs( + Obj, + "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))", + GeoId3, + static_cast(PosId3), + GeoId1); + } + } if (!IsPointAlreadyOnCurve(GeoId2, GeoId3, PosId3, Obj)) { - Gui::cmdAppObjectArgs( - Obj, - "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))", - GeoId3, - static_cast(PosId3), - GeoId2); - }; + const Part::Geometry *geom2 = Obj->getGeometry(GeoId2); + if (!(geom2 && isBSplineCurve(*geom2))) { + Gui::cmdAppObjectArgs( + Obj, + "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))", + GeoId3, + static_cast(PosId3), + GeoId2); + } + } - if (!IsPointAlreadyOnCurve( - GeoId1, - GeoId3, - PosId3, - Obj)) {// FIXME: it's a good idea to add a check if the sketch is solved - Gui::cmdAppObjectArgs( - Obj, - "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))", - GeoId3, - static_cast(PosId3), - GeoId1); - }; + if (!IsPointAlreadyOnCurve(GeoId1, GeoId3, PosId3, Obj)) { + // FIXME: it's a good idea to add a check if the sketch is solved + const Part::Geometry *geom1 = Obj->getGeometry(GeoId1); + if (!(geom1 && isBSplineCurve(*geom1))) { + Gui::cmdAppObjectArgs( + Obj, + "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))", + GeoId3, + static_cast(PosId3), + GeoId1); + } + } Gui::cmdAppObjectArgs( Obj, @@ -6322,6 +6396,8 @@ void CmdSketcherConstrainPerpendicular::applyConstraint(std::vector& GeoId2, GeoId3, static_cast(PosId3)); + + removeRedundantPointOnObject(Obj, GeoId1, GeoId2, GeoId3); }); if (!safe) { @@ -6335,7 +6411,7 @@ void CmdSketcherConstrainPerpendicular::applyConstraint(std::vector& getSelection().clearSelection(); return; - }; + } } // ====================================================================================== @@ -6519,11 +6595,11 @@ void CmdSketcherConstrainTangent::activated(int iMsg) 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 (isEdge(GeoId1, PosId1) && isEdge(GeoId2, PosId2) && isVertex(GeoId3, PosId3)) { @@ -6540,35 +6616,41 @@ void CmdSketcherConstrainTangent::activated(int iMsg) bool safe = addConstraintSafely(Obj, [&]() { // add missing point-on-object constraints if (!IsPointAlreadyOnCurve(GeoId1, GeoId3, PosId3, Obj)) { - Gui::cmdAppObjectArgs( - selection[0].getObject(), - "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))", - GeoId3, - static_cast(PosId3), - GeoId1); - }; + const Part::Geometry *geom1 = Obj->getGeometry(GeoId1); + if (!(geom1 && isBSplineCurve(*geom1))) { + Gui::cmdAppObjectArgs( + selection[0].getObject(), + "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))", + GeoId3, + static_cast(PosId3), + GeoId1); + } + } if (!IsPointAlreadyOnCurve(GeoId2, GeoId3, PosId3, Obj)) { - Gui::cmdAppObjectArgs( - selection[0].getObject(), - "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))", - GeoId3, - static_cast(PosId3), - GeoId2); - }; + const Part::Geometry *geom2 = Obj->getGeometry(GeoId2); + if (!(geom2 && isBSplineCurve(*geom2))) { + Gui::cmdAppObjectArgs( + selection[0].getObject(), + "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))", + GeoId3, + static_cast(PosId3), + GeoId2); + } + } - if (!IsPointAlreadyOnCurve( - GeoId1, - GeoId3, - PosId3, - Obj)) {// FIXME: it's a good idea to add a check if the sketch is solved - Gui::cmdAppObjectArgs( - selection[0].getObject(), - "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))", - GeoId3, - static_cast(PosId3), - GeoId1); - }; + if (!IsPointAlreadyOnCurve(GeoId1, GeoId3, PosId3, Obj)) { + // FIXME: it's a good idea to add a check if the sketch is solved + const Part::Geometry *geom1 = Obj->getGeometry(GeoId1); + if (!(geom1 && isBSplineCurve(*geom1))) { + Gui::cmdAppObjectArgs( + selection[0].getObject(), + "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))", + GeoId3, + static_cast(PosId3), + GeoId1); + } + } Gui::cmdAppObjectArgs( selection[0].getObject(), @@ -6577,6 +6659,8 @@ void CmdSketcherConstrainTangent::activated(int iMsg) GeoId2, GeoId3, static_cast(PosId3)); + + removeRedundantPointOnObject(Obj, GeoId1, GeoId2, GeoId3); }); if (!safe) { @@ -6590,7 +6674,7 @@ void CmdSketcherConstrainTangent::activated(int iMsg) getSelection().clearSelection(); return; - }; + } Gui::TranslatedUserWarning( Obj, @@ -6669,15 +6753,6 @@ void CmdSketcherConstrainTangent::activated(int iMsg) const Part::Geometry* geom2 = Obj->getGeometry(GeoId2); - if (geom2 && isBSplineCurve(*geom2)) { - // unsupported until tangent to B-spline at any point implemented. - Gui::TranslatedUserWarning( - Obj, - QObject::tr("Wrong selection"), - QObject::tr("Tangency to B-spline edge currently unsupported.")); - return; - } - if (isBsplinePole(geom2)) { Gui::TranslatedUserWarning( Obj, @@ -6686,16 +6761,18 @@ void CmdSketcherConstrainTangent::activated(int iMsg) return; } - openCommand(QT_TRANSLATE_NOOP("Command", "Add tangent constraint")); - Gui::cmdAppObjectArgs(selection[0].getObject(), - "addConstraint(Sketcher.Constraint('Tangent',%d,%d,%d))", - GeoId1, - static_cast(PosId1), - GeoId2); - commitCommand(); - tryAutoRecompute(Obj); + if (!substituteConstraintCombinations(Obj, GeoId1, GeoId2)) { + openCommand(QT_TRANSLATE_NOOP("Command", "Add tangent constraint")); + Gui::cmdAppObjectArgs(selection[0].getObject(), + "addConstraint(Sketcher.Constraint('Tangent',%d,%d,%d))", + GeoId1, + static_cast(PosId1), + GeoId2); + commitCommand(); + tryAutoRecompute(Obj); - getSelection().clearSelection(); + getSelection().clearSelection(); + } return; } else if (isEdge(GeoId1, PosId1) @@ -6704,15 +6781,6 @@ void CmdSketcherConstrainTangent::activated(int iMsg) const Part::Geometry* geom1 = Obj->getGeometry(GeoId1); const Part::Geometry* geom2 = Obj->getGeometry(GeoId2); - if (geom1 && geom2 && (isBSplineCurve(*geom1) || isBSplineCurve(*geom2))) { - // unsupported until tangent to B-spline at any point implemented. - Gui::TranslatedUserWarning( - Obj, - QObject::tr("Wrong selection"), - QObject::tr("Tangency to B-spline edge currently unsupported.")); - return; - } - if (isBsplinePole(geom1) || isBsplinePole(geom2)) { Gui::TranslatedUserWarning( Obj, @@ -6872,6 +6940,14 @@ void CmdSketcherConstrainTangent::activated(int iMsg) return; } } + else if (geom1 && geom2 && (isBSplineCurve(*geom1) || isBSplineCurve(*geom2))) { + Gui::TranslatedUserWarning( + Obj, + QObject::tr("Wrong selection"), + QObject::tr("Only tangent-via-point is supported with a B-spline.")); + getSelection().clearSelection(); + return; + } openCommand(QT_TRANSLATE_NOOP("Command", "Add tangent constraint")); Gui::cmdAppObjectArgs(selection[0].getObject(), @@ -6917,15 +6993,6 @@ void CmdSketcherConstrainTangent::applyConstraint(std::vector& selSeq const Part::Geometry* geom1 = Obj->getGeometry(GeoId1); const Part::Geometry* geom2 = Obj->getGeometry(GeoId2); - if (geom1 && geom2 && (isBSplineCurve(*geom1) || isBSplineCurve(*geom2))) { - // unsupported until tangent to B-spline at any point implemented. - Gui::TranslatedUserWarning( - Obj, - QObject::tr("Wrong selection"), - QObject::tr("Tangency to B-spline edge currently unsupported.")); - return; - } - if (isBsplinePole(geom1) || isBsplinePole(geom2)) { Gui::TranslatedUserWarning( Obj, @@ -7158,35 +7225,41 @@ void CmdSketcherConstrainTangent::applyConstraint(std::vector& selSeq bool safe = addConstraintSafely(Obj, [&]() { // add missing point-on-object constraints if (!IsPointAlreadyOnCurve(GeoId1, GeoId3, PosId3, Obj)) { - Gui::cmdAppObjectArgs( - Obj, - "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))", - GeoId3, - static_cast(PosId3), - GeoId1); - }; + const Part::Geometry *geom1 = Obj->getGeometry(GeoId1); + if (!(geom1 && isBSplineCurve(*geom1))) { + Gui::cmdAppObjectArgs( + Obj, + "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))", + GeoId3, + static_cast(PosId3), + GeoId1); + } + } if (!IsPointAlreadyOnCurve(GeoId2, GeoId3, PosId3, Obj)) { - Gui::cmdAppObjectArgs( - Obj, - "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))", - GeoId3, - static_cast(PosId3), - GeoId2); - }; + const Part::Geometry *geom2 = Obj->getGeometry(GeoId2); + if (!(geom2 && isBSplineCurve(*geom2))) { + Gui::cmdAppObjectArgs( + Obj, + "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))", + GeoId3, + static_cast(PosId3), + GeoId2); + } + } - if (!IsPointAlreadyOnCurve( - GeoId1, - GeoId3, - PosId3, - Obj)) {// FIXME: it's a good idea to add a check if the sketch is solved - Gui::cmdAppObjectArgs( - Obj, - "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))", - GeoId3, - static_cast(PosId3), - GeoId1); - }; + if (!IsPointAlreadyOnCurve(GeoId1, GeoId3, PosId3, Obj)) { + // FIXME: it's a good idea to add a check if the sketch is solved + const Part::Geometry *geom1 = Obj->getGeometry(GeoId1); + if (!(geom1 && isBSplineCurve(*geom1))) { + Gui::cmdAppObjectArgs( + Obj, + "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))", + GeoId3, + static_cast(PosId3), + GeoId1); + } + } Gui::cmdAppObjectArgs( Obj, @@ -7195,6 +7268,8 @@ void CmdSketcherConstrainTangent::applyConstraint(std::vector& selSeq GeoId2, GeoId3, static_cast(PosId3)); + + removeRedundantPointOnObject(Obj, GeoId1, GeoId2, GeoId3); }); if (!safe) { @@ -7208,7 +7283,7 @@ void CmdSketcherConstrainTangent::applyConstraint(std::vector& selSeq getSelection().clearSelection(); return; - }; + } } // ====================================================================================== @@ -8530,11 +8605,11 @@ void CmdSketcherConstrainAngle::activated(int iMsg) if (isVertex(GeoId1, PosId1)) { std::swap(GeoId1, GeoId2); std::swap(PosId1, PosId2); - }; + } if (isVertex(GeoId2, PosId2)) { std::swap(GeoId2, GeoId3); std::swap(PosId2, PosId3); - }; + } bool bothexternal = areBothPointsOrSegmentsFixed(Obj, GeoId1, GeoId2); @@ -8554,32 +8629,38 @@ void CmdSketcherConstrainAngle::activated(int iMsg) // add missing point-on-object constraints if (!IsPointAlreadyOnCurve(GeoId1, GeoId3, PosId3, Obj)) { - Gui::cmdAppObjectArgs( - selection[0].getObject(), - "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))", - GeoId3, - static_cast(PosId3), - GeoId1); + const Part::Geometry *geom1 = Obj->getGeometry(GeoId1); + if (!(geom1 && isBSplineCurve(*geom1))) { + Gui::cmdAppObjectArgs( + selection[0].getObject(), + "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))", + GeoId3, + static_cast(PosId3), + GeoId1); + } } if (!IsPointAlreadyOnCurve(GeoId2, GeoId3, PosId3, Obj)) { - Gui::cmdAppObjectArgs( - selection[0].getObject(), - "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))", - GeoId3, - static_cast(PosId3), - GeoId2); + const Part::Geometry *geom2 = Obj->getGeometry(GeoId2); + if (!(geom2 && isBSplineCurve(*geom2))) { + Gui::cmdAppObjectArgs( + selection[0].getObject(), + "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))", + GeoId3, + static_cast(PosId3), + GeoId2); + } } - if (!IsPointAlreadyOnCurve( - GeoId1, - GeoId3, - PosId3, - Obj)) {// FIXME: it's a good idea to add a check if the sketch is solved - Gui::cmdAppObjectArgs( - selection[0].getObject(), - "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))", - GeoId3, - static_cast(PosId3), - GeoId1); + if (!IsPointAlreadyOnCurve(GeoId1, GeoId3, PosId3, Obj)) { + // FIXME: it's a good idea to add a check if the sketch is solved + const Part::Geometry *geom1 = Obj->getGeometry(GeoId1); + if (!(geom1 && isBSplineCurve(*geom1))) { + Gui::cmdAppObjectArgs( + selection[0].getObject(), + "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))", + GeoId3, + static_cast(PosId3), + GeoId1); + } } // assuming point-on-curves have been solved, calculate the angle. @@ -8604,6 +8685,8 @@ void CmdSketcherConstrainAngle::activated(int iMsg) static_cast(PosId3), ActAngle); + removeRedundantPointOnObject(Obj, GeoId1, GeoId2, GeoId3); + if (bothexternal || constraintCreationMode == Reference) {// it is a constraint on a external line, make it non-driving @@ -8620,7 +8703,7 @@ void CmdSketcherConstrainAngle::activated(int iMsg) } return; - }; + } } else if (SubNames.size() < 3) { @@ -8707,7 +8790,7 @@ void CmdSketcherConstrainAngle::activated(int iMsg) return; } } - }; + } Gui::TranslatedUserWarning( Obj, @@ -8783,26 +8866,35 @@ void CmdSketcherConstrainAngle::applyConstraint(std::vector& selSeq, // add missing point-on-object constraints if (!IsPointAlreadyOnCurve(GeoId1, GeoId3, PosId3, Obj)) { - Gui::cmdAppObjectArgs(Obj, - "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))", - GeoId3, - static_cast(PosId3), - GeoId1); + const Part::Geometry *geom1 = Obj->getGeometry(GeoId1); + if (!(geom1 && isBSplineCurve(*geom1))) { + Gui::cmdAppObjectArgs(Obj, + "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))", + GeoId3, + static_cast(PosId3), + GeoId1); + } } if (!IsPointAlreadyOnCurve(GeoId2, GeoId3, PosId3, Obj)) { - Gui::cmdAppObjectArgs(Obj, - "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))", - GeoId3, - static_cast(PosId3), - GeoId2); + const Part::Geometry *geom2 = Obj->getGeometry(GeoId2); + if (!(geom2 && isBSplineCurve(*geom2))) { + Gui::cmdAppObjectArgs(Obj, + "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))", + GeoId3, + static_cast(PosId3), + GeoId2); + } } if (!IsPointAlreadyOnCurve(GeoId1, GeoId3, PosId3, Obj)) { // FIXME: it's a good idea to add a check if the sketch is solved - Gui::cmdAppObjectArgs(Obj, - "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))", - GeoId3, - static_cast(PosId3), - GeoId1); + const Part::Geometry *geom1 = Obj->getGeometry(GeoId1); + if (!(geom1 && isBSplineCurve(*geom1))) { + Gui::cmdAppObjectArgs(Obj, + "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))", + GeoId3, + static_cast(PosId3), + GeoId1); + } } // assuming point-on-curves have been solved, calculate the angle. @@ -8826,6 +8918,8 @@ void CmdSketcherConstrainAngle::applyConstraint(std::vector& selSeq, static_cast(PosId3), ActAngle); + removeRedundantPointOnObject(Obj, GeoId1, GeoId2, GeoId3); + if (bothexternal || constraintCreationMode == Reference) { // it is a constraint on a external line, make it non-driving const std::vector& ConStr = Obj->Constraints.getValues(); @@ -8838,7 +8932,7 @@ void CmdSketcherConstrainAngle::applyConstraint(std::vector& selSeq, } return; - }; + } } void CmdSketcherConstrainAngle::updateAction(int mode) @@ -9566,18 +9660,18 @@ void CmdSketcherConstrainSnellsLaw::activated(int iMsg) QObject::tr("Wrong selection"), QObject::tr("Incompatible geometry is selected.")); return; - }; + } const Part::Geometry* geo = Obj->getGeometry(GeoId3); - if (geo && isBSplineCurve(*geo)) { - // unsupported until normal to B-spline at any point implemented. - Gui::TranslatedUserWarning( - Obj, - QObject::tr("Wrong selection"), - QObject::tr("SnellsLaw on B-spline edge is currently unsupported.")); - return; - } + // if (geo && isBSplineCurve(*geo)) { + // // unsupported until normal to B-spline at any point implemented. + // Gui::TranslatedUserWarning( + // Obj, + // QObject::tr("Wrong selection"), + // QObject::tr("SnellsLaw on B-spline edge is currently unsupported.")); + // return; + // } if (isBsplinePole(geo)) { Gui::TranslatedUserWarning(Obj, diff --git a/tests/src/Mod/Sketcher/App/planegcs/CMakeLists.txt b/tests/src/Mod/Sketcher/App/planegcs/CMakeLists.txt index dffcf388746d..40d07edb2e48 100644 --- a/tests/src/Mod/Sketcher/App/planegcs/CMakeLists.txt +++ b/tests/src/Mod/Sketcher/App/planegcs/CMakeLists.txt @@ -3,3 +3,9 @@ target_sources( PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/GCS.cpp ) + +target_sources( + Sketcher_tests_run + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/Constraints.cpp +) diff --git a/tests/src/Mod/Sketcher/App/planegcs/Constraints.cpp b/tests/src/Mod/Sketcher/App/planegcs/Constraints.cpp new file mode 100644 index 000000000000..670553d180cc --- /dev/null +++ b/tests/src/Mod/Sketcher/App/planegcs/Constraints.cpp @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later + +#ifdef WIN32 +#define _USE_MATH_DEFINES +#endif +#include + +#include "gtest/gtest.h" + +#include "Mod/Sketcher/App/planegcs/GCS.h" +#include "Mod/Sketcher/App/planegcs/Geo.h" +#include "Mod/Sketcher/App/planegcs/Constraints.h" + +class SystemTest: public GCS::System +{ +public: + size_t getNumberOfConstraints(int tagID = -1) + { + return _getNumberOfConstraints(tagID); + } +}; + +class ConstraintsTest: public ::testing::Test +{ +protected: + void SetUp() override + { + _system = std::make_unique(); + } + + void TearDown() override + { + _system.reset(); + } + + SystemTest* System() + { + return _system.get(); + } + +private: + std::unique_ptr _system; +}; + +TEST_F(ConstraintsTest, tangentBSplineAndArc) // NOLINT +{ + // Arrange + // TODO: Add arc, B-spline, and point + double pointX = 3.5, arcStartX = 5.0, arcEndX = 0.0, arcCenterX = 0.0; + double pointY = 3.5, arcStartY = 0.0, arcEndY = 5.0, arcCenterY = 0.0; + GCS::Point point, arcStart, arcEnd, arcCenter; + point.x = &pointX; + point.y = &pointY; + arcStart.x = &arcStartX; + arcStart.y = &arcStartY; + arcEnd.x = &arcEndX; + arcEnd.y = &arcEndY; + arcCenter.x = &arcCenterX; + arcCenter.y = &arcCenterY; + double arcRadius = 5.0, arcStartAngle = 0.0, arcEndAngle = M_PI / 2; + double desiredAngle = M_PI; + double bSplineStartX = 0.0, bSplineEndX = 16.0; + double bSplineStartY = 10.0, bSplineEndY = -10.0; + GCS::Point bSplineStart, bSplineEnd; + bSplineStart.x = &bSplineStartX; + bSplineStart.y = &bSplineStartY; + bSplineEnd.x = &bSplineEndX; + bSplineEnd.y = &bSplineEndY; + std::vector bSplineControlPointsX(5); + std::vector bSplineControlPointsY(5); + bSplineControlPointsX[0] = 0.0; + bSplineControlPointsY[0] = 10.0; + bSplineControlPointsX[1] = 0.0; + bSplineControlPointsY[1] = 6.0; + bSplineControlPointsX[2] = 6.0; + bSplineControlPointsY[2] = 0.5; + bSplineControlPointsX[3] = 16.0; + bSplineControlPointsY[3] = 0.5; + bSplineControlPointsX[4] = 16.0; + bSplineControlPointsY[4] = -10.0; + std::vector bSplineControlPoints(5); + for (size_t i = 0; i < bSplineControlPoints.size(); ++i) { + bSplineControlPoints[i].x = &bSplineControlPointsX[i]; + bSplineControlPoints[i].y = &bSplineControlPointsY[i]; + } + std::vector weights(bSplineControlPoints.size(), 1.0); + std::vector weightsAsPtr; + std::vector knots(bSplineControlPoints.size()); + std::vector knotsAsPtr; + std::vector mult(bSplineControlPoints.size(), 1); + mult.front() = 4; // Hardcoded for cubic + mult.back() = 4; // Hardcoded for cubic + for (size_t i = 0; i < bSplineControlPoints.size(); ++i) { + weightsAsPtr.push_back(&weights[i]); + knots[i] = i; + knotsAsPtr.push_back(&knots[i]); + } + GCS::Arc arc; + arc.start = arcStart; + arc.end = arcEnd; + arc.center = arcCenter; + arc.rad = &arcRadius; + arc.startAngle = &arcStartAngle; + arc.endAngle = &arcEndAngle; + GCS::BSpline bspline; + bspline.start = bSplineStart; + bspline.end = bSplineEnd; + bspline.poles = bSplineControlPoints; + bspline.weights = weightsAsPtr; + bspline.knots = knotsAsPtr; + bspline.mult = mult; + bspline.degree = 3; + bspline.periodic = false; + double bsplineParam = 0.35; + + std::vector params = {point.x, + point.y, + arcStart.x, + arcStart.y, + arcEnd.x, + arcEnd.y, + arcCenter.x, + arcCenter.y, + &arcRadius, + bSplineStart.x, + bSplineStart.y, + bSplineEnd.x, + bSplineEnd.y, + &bSplineControlPointsX[0], + &bSplineControlPointsY[0], + &bSplineControlPointsX[1], + &bSplineControlPointsY[1], + &bSplineControlPointsX[2], + &bSplineControlPointsY[2], + &bSplineControlPointsX[3], + &bSplineControlPointsY[3], + &bSplineControlPointsX[4], + &bSplineControlPointsY[4], + &desiredAngle, + &bsplineParam}; + params.insert(params.end(), weightsAsPtr.begin(), weightsAsPtr.end()); + params.insert(params.end(), knotsAsPtr.begin(), knotsAsPtr.end()); + + // Act + // TODO: Apply constraint and solve + System()->addConstraintArcRules(arc); + System()->addConstraintPointOnArc(point, arc, 0, true); + System()->addConstraintPointOnBSpline(point, bspline, &bsplineParam, 0, true); + System()->addConstraintAngleViaPointAndParam(bspline, + arc, + point, + &bsplineParam, + &desiredAngle, + 0, + true); + int solveResult = System()->solve(params); + if (solveResult == GCS::Success) { + System()->applySolution(); + } + + // Assert + EXPECT_EQ(solveResult, GCS::Success); + // is point on arc? + EXPECT_DOUBLE_EQ((arcRadius) * (arcRadius), + (pointX - arcCenterX) * (pointX - arcCenterX) + + (pointY - arcCenterY) * (pointY - arcCenterY)); + // is point on B-spline? + GCS::DeriVector2 pointAtBSplineParam = bspline.Value(bsplineParam, 1.0); + EXPECT_DOUBLE_EQ(pointAtBSplineParam.x, pointX); + EXPECT_DOUBLE_EQ(pointAtBSplineParam.y, pointY); + // TODO: are tangents at relevant parameter equal? + GCS::DeriVector2 centerToPoint((pointX - arcCenterX), (pointY - arcCenterY)); + GCS::DeriVector2 tangentBSplineAtPoint(pointAtBSplineParam.dx, pointAtBSplineParam.dy); + double dprd; + // FIXME: This error is probably too high. Fixing this may require improving the solver, + // however. + EXPECT_NEAR(std::fabs(centerToPoint.crossProdNorm(tangentBSplineAtPoint, dprd)) + / (centerToPoint.length() * tangentBSplineAtPoint.length()), + 1.0, + 0.005); +}