From 6a3c0555d05e676ff4981a9fc762a2731de0063d Mon Sep 17 00:00:00 2001 From: Ajinkya Dahale Date: Thu, 30 Mar 2023 20:48:36 +0530 Subject: [PATCH 01/10] [Sketcher][planegcs] Support angle via point with params These are intended to use when calculating normal simply with points could be numerically expensive or otherwise nonviable. --- src/Mod/Sketcher/App/planegcs/Constraints.cpp | 235 ++++++++++++++++++ src/Mod/Sketcher/App/planegcs/Constraints.h | 89 ++++++- src/Mod/Sketcher/App/planegcs/Geo.h | 13 +- 3 files changed, 335 insertions(+), 2 deletions(-) diff --git a/src/Mod/Sketcher/App/planegcs/Constraints.cpp b/src/Mod/Sketcher/App/planegcs/Constraints.cpp index aee8375afa..b670dc1b3f 100644 --- a/src/Mod/Sketcher/App/planegcs/Constraints.cpp +++ b/src/Mod/Sketcher/App/planegcs/Constraints.cpp @@ -2650,6 +2650,241 @@ 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)); + + +// use numeric for testing +#if 0 + double const eps = 0.00001; + double oldparam = *param; + double v0 = this->error(); + *param += eps; + double vr = this->error(); + *param = oldparam - eps; + double vl = this->error(); + *param = oldparam; + //If not nasty, real derivative should be between left one and right one + double numretl = (v0-vl)/eps; + double numretr = (vr-v0)/eps; + assert(deriv <= std::max(numretl,numretr) ); + assert(deriv >= std::min(numretl,numretr) ); +#endif + + return scale * deriv; +} + +// -------------------------------------------------------- +// 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)); + + +// use numeric for testing +#if 0 + double const eps = 0.00001; + double oldparam = *param; + double v0 = this->error(); + *param += eps; + double vr = this->error(); + *param = oldparam - eps; + double vl = this->error(); + *param = oldparam; + //If not nasty, real derivative should be between left one and right one + double numretl = (v0-vl)/eps; + double numretr = (vr-v0)/eps; + assert(deriv <= std::max(numretl,numretr) ); + assert(deriv >= std::min(numretl,numretr) ); +#endif + + return scale * deriv; +} + // -------------------------------------------------------- // ConstraintSnell diff --git a/src/Mod/Sketcher/App/planegcs/Constraints.h b/src/Mod/Sketcher/App/planegcs/Constraints.h index e4ff32edf9..d092789e96 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 @@ public: 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/Geo.h b/src/Mod/Sketcher/App/planegcs/Geo.h index 91f0eb6f6f..fa7f6c0455 100644 --- a/src/Mod/Sketcher/App/planegcs/Geo.h +++ b/src/Mod/Sketcher/App/planegcs/Geo.h @@ -161,6 +161,14 @@ public: 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) + { + 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 @@ -414,6 +422,9 @@ public: // 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; int PushOwnParams(VEC_pD& pvec) override; void ReconstructOnNewPvec(VEC_pD& pvec, int& cnt) override; @@ -433,7 +444,7 @@ public: /// 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); }; From 5ba050b4674464ff3b5df3d86f9390089da8b39e Mon Sep 17 00:00:00 2001 From: Ajinkya Dahale Date: Wed, 5 Apr 2023 17:45:25 +0530 Subject: [PATCH 02/10] [Sketcher][planegcs] Implement parametric `BSpline::CalculateNormal` As opposed to "punctual" that already exists for curves. --- src/Mod/Sketcher/App/planegcs/Geo.cpp | 153 ++++++++++++++++++++++++++ src/Mod/Sketcher/App/planegcs/Geo.h | 7 +- 2 files changed, 157 insertions(+), 3 deletions(-) diff --git a/src/Mod/Sketcher/App/planegcs/Geo.cpp b/src/Mod/Sketcher/App/planegcs/Geo.cpp index b085384fd6..46872cb2dd 100644 --- a/src/Mod/Sketcher/App/planegcs/Geo.cpp +++ b/src/Mod/Sketcher/App/planegcs/Geo.cpp @@ -773,6 +773,159 @@ DeriVector2 BSpline::CalculateNormal(const Point& p, const double* derivparam) c return ret; } +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; + + // 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(*param, startpole + degree, degree, d, flattenedknots); + for (size_t i = 0; i < numpoints; ++i) + d[i] = *poleyat(i) * *weightat(i); + double ysum = BSpline::splineValue(*param, startpole + degree, degree, d, flattenedknots); + for (size_t i = 0; i < numpoints; ++i) + d[i] = *weightat(i); + double wsum = BSpline::splineValue(*param, 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( + *param, 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( + *param, 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( + *param, startpole + degree, degree - 1, d, flattenedknots); + + DeriVector2 result(wsum*xslopesum - wslopesum*xsum, wsum*yslopesum - wslopesum*ysum); + + // get dx, dy of the normal as well + bool dpfound = false; + for (size_t i = 0; i < numpoints; ++i) { + if (derivparam == polexat(i)) { + VEC_D d(numpoints); + d[i] = 1; + double 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]); + double slopefactor = BSpline::splineValue(*param, startpole + degree, degree-1, sd, flattenedknots); + result.dx = *weightat(i) * (wsum*slopefactor - wslopesum*factor); + dpfound = true; + break; + } + if (derivparam == poleyat(i)) { + VEC_D d(numpoints); + d[i] = 1; + double 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]); + double slopefactor = BSpline::splineValue(*param, startpole + degree, degree-1, sd, flattenedknots); + result.dy = *weightat(i) * (wsum*slopefactor - wslopesum*factor); + dpfound = true; + break; + } + if (derivparam == weightat(i)) { + VEC_D d(numpoints); + d[i] = 1; + double 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]); + double slopefactor = BSpline::splineValue(*param, startpole + degree, degree-1, sd, flattenedknots); + 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)))); + dpfound = true; + 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) { + 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 { // place holder diff --git a/src/Mod/Sketcher/App/planegcs/Geo.h b/src/Mod/Sketcher/App/planegcs/Geo.h index fa7f6c0455..f65bdfdfd1 100644 --- a/src/Mod/Sketcher/App/planegcs/Geo.h +++ b/src/Mod/Sketcher/App/planegcs/Geo.h @@ -162,7 +162,8 @@ public: 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) + virtual DeriVector2 CalculateNormal(const double* param, + const double* derivparam = nullptr) const { DeriVector2 pointDV = Value(*param, 0.0); Point p(&pointDV.x, &pointDV.y); @@ -423,8 +424,8 @@ public: 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 CalculateNormal(const double* param, + const double* derivparam = nullptr) const override; DeriVector2 Value(double u, double du, const double* derivparam = nullptr) const override; int PushOwnParams(VEC_pD& pvec) override; void ReconstructOnNewPvec(VEC_pD& pvec, int& cnt) override; From 82f03593fd808f6026d314dd4f3141f8ddf56559 Mon Sep 17 00:00:00 2001 From: Ajinkya Dahale Date: Thu, 8 Feb 2024 19:47:41 +0530 Subject: [PATCH 03/10] [planegcs] Implement `calculateAngleViaParams` For use in angle-via-point with complex curves. --- src/Mod/Sketcher/App/planegcs/GCS.cpp | 10 ++++++++++ src/Mod/Sketcher/App/planegcs/GCS.h | 4 ++++ 2 files changed, 14 insertions(+) diff --git a/src/Mod/Sketcher/App/planegcs/GCS.cpp b/src/Mod/Sketcher/App/planegcs/GCS.cpp index 1b7bf211bc..b8d76d545c 100644 --- a/src/Mod/Sketcher/App/planegcs/GCS.cpp +++ b/src/Mod/Sketcher/App/planegcs/GCS.cpp @@ -1679,6 +1679,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 441a1e9af1..1438157cec 100644 --- a/src/Mod/Sketcher/App/planegcs/GCS.h +++ b/src/Mod/Sketcher/App/planegcs/GCS.h @@ -501,6 +501,10 @@ public: 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 From 65b4dd10ae53a85802283ec543e05c4e3ee0cc83 Mon Sep 17 00:00:00 2001 From: Ajinkya Dahale Date: Wed, 5 Apr 2023 23:11:28 +0530 Subject: [PATCH 04/10] [Sketcher] Expose general tangency with B-splines to Sketcher The following commits were squashed into this [Sketcher] Handle some corner cases in AngleViaPoint [Sketcher] Avoid redundant constraints with B-splines... When involving tangent, perpendicular and angle constraints. [Sketcher] Add pre-commit changes [Sketcher] Do not allow 2-selection tangent with B-spline Also... [Sketcher] Report error when using direct tangency with B-splines [Sketcher] Fix malformed constraint when B-spline is selected second To clarify, this means the second curve selected. The position of the point in selection order does not matter in angle-via-point. [Sketcher] Fix wrong number for B-Spline tangent on redundancy [Sketcher] Remove existing point-on-object in some redundant cases Particularly when point constrained on a B-spline is being used for tangent, perpendicular or angle via point with the same B-spline. [Sketcher] Fix direction issue with B-spline tangents. Without these changes the solver might try to "twist" the B-spline to make the angle between curves be 0 instead of PI (which may be closer to the initial shape). --- src/Mod/Sketcher/App/Sketch.cpp | 153 +++++- src/Mod/Sketcher/App/Sketch.h | 2 + src/Mod/Sketcher/App/SketchObject.cpp | 15 +- src/Mod/Sketcher/App/planegcs/GCS.cpp | 30 ++ src/Mod/Sketcher/App/planegcs/GCS.h | 15 + src/Mod/Sketcher/App/planegcs/Geo.cpp | 178 ++++--- src/Mod/Sketcher/Gui/CommandConstraints.cpp | 522 +++++++++++--------- 7 files changed, 611 insertions(+), 304 deletions(-) diff --git a/src/Mod/Sketcher/App/Sketch.cpp b/src/Mod/Sketcher/App/Sketch.cpp index 46d8ffd78e..df487b2b23 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 2014e81ec1..9035c7ae23 100644 --- a/src/Mod/Sketcher/App/Sketch.h +++ b/src/Mod/Sketcher/App/Sketch.h @@ -488,6 +488,8 @@ public: // 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 fe6906223e..82a9455b5c 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/GCS.cpp b/src/Mod/Sketcher/App/planegcs/GCS.cpp index b8d76d545c..c7e9a19d96 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); diff --git a/src/Mod/Sketcher/App/planegcs/GCS.h b/src/Mod/Sketcher/App/planegcs/GCS.h index 1438157cec..68175e3e18 100644 --- a/src/Mod/Sketcher/App/planegcs/GCS.h +++ b/src/Mod/Sketcher/App/planegcs/GCS.h @@ -317,6 +317,21 @@ public: 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, diff --git a/src/Mod/Sketcher/App/planegcs/Geo.cpp b/src/Mod/Sketcher/App/planegcs/Geo.cpp index 46872cb2dd..e384274470 100644 --- a/src/Mod/Sketcher/App/planegcs/Geo.cpp +++ b/src/Mod/Sketcher/App/planegcs/Geo.cpp @@ -777,31 +777,43 @@ DeriVector2 BSpline::CalculateNormal(const double* param, const double* derivpar { // 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) + for (size_t j = 1; j < mult.size() && *(knots[j]) <= *param; ++j) { startpole += mult[j]; - if (!periodic && startpole >= poles.size()) + } + 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()]; }; + 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 + // 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) + for (size_t i = 0; i < numpoints; ++i) { d[i] = *polexat(i) * *weightat(i); + } double xsum = BSpline::splineValue(*param, startpole + degree, degree, d, flattenedknots); - for (size_t i = 0; i < numpoints; ++i) + for (size_t i = 0; i < numpoints; ++i) { d[i] = *poleyat(i) * *weightat(i); + } double ysum = BSpline::splineValue(*param, startpole + degree, degree, d, flattenedknots); - for (size_t i = 0; i < numpoints; ++i) + for (size_t i = 0; i < numpoints; ++i) { d[i] = *weightat(i); + } double wsum = BSpline::splineValue(*param, startpole + degree, degree, d, flattenedknots); d.resize(numpoints - 1); @@ -809,22 +821,22 @@ DeriVector2 BSpline::CalculateNormal(const double* param, const double* derivpar 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( - *param, startpole + degree, degree - 1, d, flattenedknots); + double xslopesum = + degree * BSpline::splineValue(*param, 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( - *param, startpole + degree, degree - 1, d, flattenedknots); + double yslopesum = + degree * BSpline::splineValue(*param, 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( - *param, startpole + degree, degree - 1, d, flattenedknots); + double wslopesum = + degree * BSpline::splineValue(*param, startpole + degree, degree - 1, d, flattenedknots); - DeriVector2 result(wsum*xslopesum - wslopesum*xsum, wsum*yslopesum - wslopesum*ysum); + DeriVector2 result(wsum * xslopesum - wslopesum * xsum, wsum * yslopesum - wslopesum * ysum); // get dx, dy of the normal as well bool dpfound = false; @@ -832,95 +844,113 @@ DeriVector2 BSpline::CalculateNormal(const double* param, const double* derivpar if (derivparam == polexat(i)) { VEC_D d(numpoints); d[i] = 1; - double 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]); - double slopefactor = BSpline::splineValue(*param, startpole + degree, degree-1, sd, flattenedknots); - result.dx = *weightat(i) * (wsum*slopefactor - wslopesum*factor); + double 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]); + } + double slopefactor = + BSpline::splineValue(*param, startpole + degree, degree - 1, sd, flattenedknots); + result.dx = *weightat(i) * (wsum * slopefactor - wslopesum * factor); dpfound = true; break; } if (derivparam == poleyat(i)) { VEC_D d(numpoints); d[i] = 1; - double 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]); - double slopefactor = BSpline::splineValue(*param, startpole + degree, degree-1, sd, flattenedknots); - result.dy = *weightat(i) * (wsum*slopefactor - wslopesum*factor); + double 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]); + } + double slopefactor = + BSpline::splineValue(*param, startpole + degree, degree - 1, sd, flattenedknots); + result.dy = *weightat(i) * (wsum * slopefactor - wslopesum * factor); dpfound = true; break; } if (derivparam == weightat(i)) { VEC_D d(numpoints); d[i] = 1; - double 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]); - double slopefactor = BSpline::splineValue(*param, startpole + degree, degree-1, sd, flattenedknots); - 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)))); + double 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]); + } + double slopefactor = + BSpline::splineValue(*param, startpole + degree, degree - 1, sd, flattenedknots); + 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)))); dpfound = true; 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. + // many tangent constraints on the same curve after all). Assume that this is the param + // provided. if (derivparam == param) { - VEC_D sd(numpoints-1), ssd(numpoints-2); + 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]); + 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]); + 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); + 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]); + 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]); + 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); + 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]); + 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]); + 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); + 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; + result.dx = wsum * xslopeslopesum - wslopeslopesum * xsum; + result.dy = wsum * yslopeslopesum - wslopeslopesum * ysum; } return result.rotate90ccw(); diff --git a/src/Mod/Sketcher/Gui/CommandConstraints.cpp b/src/Mod/Sketcher/Gui/CommandConstraints.cpp index 3e52a87070..73fae676e8 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) @@ -5699,11 +5744,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 +5765,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 +5812,8 @@ void CmdSketcherConstrainPerpendicular::activated(int iMsg) GeoId2, GeoId3, static_cast(PosId3)); + + removeRedundantPointOnObject(Obj, GeoId1, GeoId2, GeoId3); }); if (!safe) { @@ -5770,7 +5827,7 @@ void CmdSketcherConstrainPerpendicular::activated(int iMsg) getSelection().clearSelection(); return; - }; + } Gui::TranslatedUserWarning( Obj, @@ -5833,15 +5890,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 +5927,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 +6128,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 +6333,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 +6376,8 @@ void CmdSketcherConstrainPerpendicular::applyConstraint(std::vector& GeoId2, GeoId3, static_cast(PosId3)); + + removeRedundantPointOnObject(Obj, GeoId1, GeoId2, GeoId3); }); if (!safe) { @@ -6335,7 +6391,7 @@ void CmdSketcherConstrainPerpendicular::applyConstraint(std::vector& getSelection().clearSelection(); return; - }; + } } // ====================================================================================== @@ -6519,11 +6575,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 +6596,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 +6639,8 @@ void CmdSketcherConstrainTangent::activated(int iMsg) GeoId2, GeoId3, static_cast(PosId3)); + + removeRedundantPointOnObject(Obj, GeoId1, GeoId2, GeoId3); }); if (!safe) { @@ -6590,7 +6654,7 @@ void CmdSketcherConstrainTangent::activated(int iMsg) getSelection().clearSelection(); return; - }; + } Gui::TranslatedUserWarning( Obj, @@ -6669,15 +6733,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 +6741,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 +6761,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 +6920,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 +6973,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 +7205,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 +7248,8 @@ void CmdSketcherConstrainTangent::applyConstraint(std::vector& selSeq GeoId2, GeoId3, static_cast(PosId3)); + + removeRedundantPointOnObject(Obj, GeoId1, GeoId2, GeoId3); }); if (!safe) { @@ -7208,7 +7263,7 @@ void CmdSketcherConstrainTangent::applyConstraint(std::vector& selSeq getSelection().clearSelection(); return; - }; + } } // ====================================================================================== @@ -8530,11 +8585,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 +8609,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 +8665,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 +8683,7 @@ void CmdSketcherConstrainAngle::activated(int iMsg) } return; - }; + } } else if (SubNames.size() < 3) { @@ -8707,7 +8770,7 @@ void CmdSketcherConstrainAngle::activated(int iMsg) return; } } - }; + } Gui::TranslatedUserWarning( Obj, @@ -8783,26 +8846,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 +8898,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 +8912,7 @@ void CmdSketcherConstrainAngle::applyConstraint(std::vector& selSeq, } return; - }; + } } void CmdSketcherConstrainAngle::updateAction(int mode) @@ -9566,18 +9640,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, From b1ef4be6fa258d7979683731059f5ba40b81ed65 Mon Sep 17 00:00:00 2001 From: Ajinkya Dahale Date: Sun, 21 Jan 2024 23:27:56 +0530 Subject: [PATCH 05/10] [planegcs] Remove some numerical testing If needed this can be moved to a gtest, --- src/Mod/Sketcher/App/planegcs/Constraints.cpp | 36 ------------------- 1 file changed, 36 deletions(-) diff --git a/src/Mod/Sketcher/App/planegcs/Constraints.cpp b/src/Mod/Sketcher/App/planegcs/Constraints.cpp index b670dc1b3f..f99bfbb69d 100644 --- a/src/Mod/Sketcher/App/planegcs/Constraints.cpp +++ b/src/Mod/Sketcher/App/planegcs/Constraints.cpp @@ -2745,24 +2745,6 @@ double ConstraintAngleViaPointAndParam::grad(double* 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)); - -// use numeric for testing -#if 0 - double const eps = 0.00001; - double oldparam = *param; - double v0 = this->error(); - *param += eps; - double vr = this->error(); - *param = oldparam - eps; - double vl = this->error(); - *param = oldparam; - //If not nasty, real derivative should be between left one and right one - double numretl = (v0-vl)/eps; - double numretr = (vr-v0)/eps; - assert(deriv <= std::max(numretl,numretr) ); - assert(deriv >= std::min(numretl,numretr) ); -#endif - return scale * deriv; } @@ -2864,24 +2846,6 @@ double ConstraintAngleViaPointAndTwoParams::grad(double* 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)); - -// use numeric for testing -#if 0 - double const eps = 0.00001; - double oldparam = *param; - double v0 = this->error(); - *param += eps; - double vr = this->error(); - *param = oldparam - eps; - double vl = this->error(); - *param = oldparam; - //If not nasty, real derivative should be between left one and right one - double numretl = (v0-vl)/eps; - double numretr = (vr-v0)/eps; - assert(deriv <= std::max(numretl,numretr) ); - assert(deriv >= std::min(numretl,numretr) ); -#endif - return scale * deriv; } From da48a722699dc5a575af76246de9faed96960a57 Mon Sep 17 00:00:00 2001 From: Ajinkya Dahale Date: Fri, 26 Jan 2024 21:03:45 +0530 Subject: [PATCH 06/10] [planegcs] Implement `BSpline::Value()` Needed for gtests currently. --- src/Mod/Sketcher/App/planegcs/Geo.cpp | 69 ++++++++++++++++++++++++++- 1 file changed, 67 insertions(+), 2 deletions(-) diff --git a/src/Mod/Sketcher/App/planegcs/Geo.cpp b/src/Mod/Sketcher/App/planegcs/Geo.cpp index e384274470..931ed28af7 100644 --- a/src/Mod/Sketcher/App/planegcs/Geo.cpp +++ b/src/Mod/Sketcher/App/planegcs/Geo.cpp @@ -956,10 +956,75 @@ DeriVector2 BSpline::CalculateNormal(const double* param, const double* derivpar return result.rotate90ccw(); } -DeriVector2 BSpline::Value(double /*u*/, double /*du*/, const double* /*derivparam*/) const +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; } From 1e19926a7707bf73e417751dbaff2adadfa13a74 Mon Sep 17 00:00:00 2001 From: Ajinkya Dahale Date: Thu, 8 Feb 2024 20:02:29 +0530 Subject: [PATCH 07/10] [planegcs] Add `SketcherExport` macro to classes in Geo.h ...for use in tests. --- src/Mod/Sketcher/App/planegcs/Geo.h | 30 +++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/Mod/Sketcher/App/planegcs/Geo.h b/src/Mod/Sketcher/App/planegcs/Geo.h index f65bdfdfd1..022fdff613 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 @@ public: // 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() @@ -189,7 +191,7 @@ public: virtual Curve* Copy() = 0; }; -class Line: public Curve +class SketcherExport Line: public Curve { public: Line() @@ -205,7 +207,7 @@ public: Line* Copy() override; }; -class Circle: public Curve +class SketcherExport Circle: public Curve { public: Circle() @@ -223,7 +225,7 @@ public: Circle* Copy() override; }; -class Arc: public Circle +class SketcherExport Arc: public Circle { public: Arc() @@ -245,7 +247,7 @@ public: Arc* Copy() override; }; -class MajorRadiusConic: public Curve +class SketcherExport MajorRadiusConic: public Curve { public: ~MajorRadiusConic() override @@ -260,7 +262,7 @@ public: // DeriVector2 CalculateNormal(Point &p, double* derivparam = 0) = 0; }; -class Ellipse: public MajorRadiusConic +class SketcherExport Ellipse: public MajorRadiusConic { public: Ellipse() @@ -286,7 +288,7 @@ public: Ellipse* Copy() override; }; -class ArcOfEllipse: public Ellipse +class SketcherExport ArcOfEllipse: public Ellipse { public: ArcOfEllipse() @@ -310,7 +312,7 @@ public: ArcOfEllipse* Copy() override; }; -class Hyperbola: public MajorRadiusConic +class SketcherExport Hyperbola: public MajorRadiusConic { public: Hyperbola() @@ -336,7 +338,7 @@ public: Hyperbola* Copy() override; }; -class ArcOfHyperbola: public Hyperbola +class SketcherExport ArcOfHyperbola: public Hyperbola { public: ArcOfHyperbola() @@ -358,7 +360,7 @@ public: ArcOfHyperbola* Copy() override; }; -class Parabola: public Curve +class SketcherExport Parabola: public Curve { public: Parabola() @@ -374,7 +376,7 @@ public: Parabola* Copy() override; }; -class ArcOfParabola: public Parabola +class SketcherExport ArcOfParabola: public Parabola { public: ArcOfParabola() @@ -395,7 +397,7 @@ public: ArcOfParabola* Copy() override; }; -class BSpline: public Curve +class SketcherExport BSpline: public Curve { public: BSpline() From 459d210f15ad6a7d139a77c39f6d7fb333a20745 Mon Sep 17 00:00:00 2001 From: Ajinkya Dahale Date: Thu, 25 Jan 2024 00:21:57 +0530 Subject: [PATCH 08/10] [planegcs][test] Add Spline and Arc tangent test --- .../Mod/Sketcher/App/planegcs/CMakeLists.txt | 6 + .../Mod/Sketcher/App/planegcs/Constraints.cpp | 181 ++++++++++++++++++ 2 files changed, 187 insertions(+) create mode 100644 tests/src/Mod/Sketcher/App/planegcs/Constraints.cpp diff --git a/tests/src/Mod/Sketcher/App/planegcs/CMakeLists.txt b/tests/src/Mod/Sketcher/App/planegcs/CMakeLists.txt index dffcf38874..40d07edb2e 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 0000000000..670553d180 --- /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); +} From 73434e008a979096170869d84fb7ed80f5bd9740 Mon Sep 17 00:00:00 2001 From: Ajinkya Dahale Date: Sun, 28 Jan 2024 23:19:38 +0530 Subject: [PATCH 09/10] [planegcs] Simplify `GCS::BSpline::CalculateNormal()` --- src/Mod/Sketcher/App/planegcs/Geo.cpp | 261 +++++++++++++------------- src/Mod/Sketcher/App/planegcs/Geo.h | 8 + 2 files changed, 140 insertions(+), 129 deletions(-) diff --git a/src/Mod/Sketcher/App/planegcs/Geo.cpp b/src/Mod/Sketcher/App/planegcs/Geo.cpp index 931ed28af7..b46ce7c0f7 100644 --- a/src/Mod/Sketcher/App/planegcs/Geo.cpp +++ b/src/Mod/Sketcher/App/planegcs/Geo.cpp @@ -784,10 +784,6 @@ DeriVector2 BSpline::CalculateNormal(const double* param, const double* derivpar 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; }; @@ -798,115 +794,60 @@ DeriVector2 BSpline::CalculateNormal(const double* param, const double* derivpar return weights[(startpole + i) % weights.size()]; }; - size_t numpoints = degree + 1; + 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 - VEC_D d(numpoints); - for (size_t i = 0; i < numpoints; ++i) { - d[i] = *polexat(i) * *weightat(i); - } - double xsum = BSpline::splineValue(*param, startpole + degree, degree, d, flattenedknots); - for (size_t i = 0; i < numpoints; ++i) { - d[i] = *poleyat(i) * *weightat(i); - } - double ysum = BSpline::splineValue(*param, startpole + degree, degree, d, flattenedknots); - for (size_t i = 0; i < numpoints; ++i) { - d[i] = *weightat(i); - } - double wsum = BSpline::splineValue(*param, 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(*param, 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(*param, 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(*param, startpole + degree, degree - 1, d, flattenedknots); - 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 - bool dpfound = false; for (size_t i = 0; i < numpoints; ++i) { if (derivparam == polexat(i)) { - VEC_D d(numpoints); - d[i] = 1; - double 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]); - } - double slopefactor = - BSpline::splineValue(*param, startpole + degree, degree - 1, sd, flattenedknots); + double factor, slopefactor; + doSomething(i, factor, slopefactor); result.dx = *weightat(i) * (wsum * slopefactor - wslopesum * factor); - dpfound = true; break; } if (derivparam == poleyat(i)) { - VEC_D d(numpoints); - d[i] = 1; - double 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]); - } - double slopefactor = - BSpline::splineValue(*param, startpole + degree, degree - 1, sd, flattenedknots); + double factor, slopefactor; + doSomething(i, factor, slopefactor); result.dy = *weightat(i) * (wsum * slopefactor - wslopesum * factor); - dpfound = true; break; } if (derivparam == weightat(i)) { - VEC_D d(numpoints); - d[i] = 1; - double 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]); - } - double slopefactor = - BSpline::splineValue(*param, startpole + degree, degree - 1, sd, flattenedknots); + 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)))); - dpfound = true; break; } } @@ -914,51 +855,53 @@ DeriVector2 BSpline::CalculateNormal(const double* param, const double* derivpar // 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) { - 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; + 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) { @@ -1029,6 +972,66 @@ DeriVector2 BSpline::Value(double u, double /*du*/, const double* /*derivparam*/ 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 022fdff613..4f78061a6e 100644 --- a/src/Mod/Sketcher/App/planegcs/Geo.h +++ b/src/Mod/Sketcher/App/planegcs/Geo.h @@ -429,6 +429,14 @@ public: 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; From 3f1f6387d18a2dd9d94f617b12b02c792bf937d2 Mon Sep 17 00:00:00 2001 From: Ajinkya Dahale Date: Sat, 3 Feb 2024 17:12:49 +0530 Subject: [PATCH 10/10] [Sketcher] Handle spline selection in Dimension tool --- src/Mod/Sketcher/Gui/CommandConstraints.cpp | 72 +++++++++++++-------- 1 file changed, 46 insertions(+), 26 deletions(-) diff --git a/src/Mod/Sketcher/Gui/CommandConstraints.cpp b/src/Mod/Sketcher/Gui/CommandConstraints.cpp index 73fae676e8..fa5e13c066 100644 --- a/src/Mod/Sketcher/Gui/CommandConstraints.cpp +++ b/src/Mod/Sketcher/Gui/CommandConstraints.cpp @@ -1300,41 +1300,43 @@ public: 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 hasSplineAndCo() const { return s_spl > 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 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; } - 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 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; } - 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 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; } - bool has2MoreEllipses() const { return s_pts == 0 && s_lns == 0 && s_cir == 0 && s_ell >= 2; } + 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; + size_t s_pts, s_lns, s_cir, s_ell, s_spl; }; class DrawSketchHandlerDimension : public DrawSketchHandler @@ -1580,6 +1582,7 @@ protected: std::vector selLine; std::vector selCircleArc; std::vector selEllipseAndCo; + std::vector selSplineAndCo; std::vector initialSelection; @@ -1623,6 +1626,7 @@ protected: selLine.clear(); selCircleArc.clear(); selEllipseAndCo.clear(); + selSplineAndCo.clear(); } } @@ -1666,7 +1670,8 @@ protected: } } - std::vector& getSelectionVector(Base::Type selGeoType) { + std::vector& getSelectionVector(Base::Type selGeoType) + { if (selGeoType == Part::GeomPoint::getClassTypeId()) { return selPoints; } @@ -1683,6 +1688,9 @@ protected: selGeoType == Part::GeomArcOfParabola::getClassTypeId()) { return selEllipseAndCo; } + else if (selGeoType == Part::GeomBSplineCurve::getClassTypeId()) { + return selSplineAndCo; + } static std::vector emptyVector; return emptyVector; @@ -1713,12 +1721,13 @@ protected: 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); } @@ -1821,6 +1830,17 @@ protected: } } + 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