From 880cae7472e96683add8b8874398d92e9b23be1b Mon Sep 17 00:00:00 2001 From: schwieni Date: Tue, 12 Mar 2019 22:56:50 +0100 Subject: [PATCH 1/7] lpe-pts2ellipse: added Steiner ellipse and inellipse generation from three points. Did some minor cleanup too. Added me to the AUTHORS file. --- AUTHORS | 1 + src/live_effects/lpe-pts2ellipse.cpp | 481 ++++++++++++++++----------- src/live_effects/lpe-pts2ellipse.h | 27 +- 3 files changed, 309 insertions(+), 200 deletions(-) diff --git a/AUTHORS b/AUTHORS index 55f154b9ff..a0dd0b5608 100644 --- a/AUTHORS +++ b/AUTHORS @@ -143,6 +143,7 @@ Felipe Corrêa da Silva Sanches Christian Schaller Marco Scholten Tom von Schwerdtner +Markus Schwienbacher Danilo Šegan Abhishek Sharma Tim Sheridan diff --git a/src/live_effects/lpe-pts2ellipse.cpp b/src/live_effects/lpe-pts2ellipse.cpp index 48655b25ae..f9e23d68a3 100644 --- a/src/live_effects/lpe-pts2ellipse.cpp +++ b/src/live_effects/lpe-pts2ellipse.cpp @@ -1,4 +1,3 @@ -// SPDX-License-Identifier: GPL-2.0-or-later /** \file * LPE "Points to Ellipse" implementation */ @@ -9,7 +8,7 @@ * * Copyright (C) Markus Schwienbacher 2013 * - * Released under GNU GPL v2+, read the file 'COPYING' for more information. + * Released under GNU GPL, read the file 'COPYING' for more information */ #include "live_effects/lpe-pts2ellipse.h" @@ -19,7 +18,6 @@ #include #include #include -#include #include <2geom/path.h> #include <2geom/circle.h> @@ -29,15 +27,15 @@ #include -using namespace Geom; - namespace Inkscape { namespace LivePathEffect { static const Util::EnumData EllipseMethodData[] = { { EM_AUTO, N_("Auto ellipse"), "auto" }, //!< (2..4 points: circle, from 5 points: ellipse) - { EM_CIRCLE, N_("Force circle"), "circle" }, - { EM_ISONOMETRIC_CIRCLE, N_("Isometric circle"), "iso_circle" } + { EM_CIRCLE, N_("Force circle"), "circle" }, //!< always fit a circle + { EM_ISOMETRIC_CIRCLE, N_("Isometric circle"), "iso_circle" }, //!< use first two edges to generate a sheared ellipse + { EM_STEINER_ELLIPSE, N_("Steiner ellipse"), "steiner_ellipse" }, //!< generate a steiner ellipse from the first three points + { EM_STEINER_INELLIPSE, N_("Steiner inellipse"), "steiner_inellipse" } //!< generate a steiner inellipse from the first three points }; static const Util::EnumDataConverter EMConverter(EllipseMethodData, EM_END); @@ -45,11 +43,11 @@ LPEPts2Ellipse::LPEPts2Ellipse(LivePathEffectObject *lpeobject) : Effect(lpeobject), method(_("Method:"), _("Methods to generate the ellipse"), "method", EMConverter, &wr, this, EM_AUTO), - gen_isometric_frame(_("_Frame (isometric rectangle)"), _("Draw Parallelogram around the ellipse"), + gen_isometric_frame(_("_Frame (isometric rectangle)"), _("Draw parallelogram around the ellipse"), "gen_isometric_frame", &wr, this, false), gen_arc(_("_Arc"), _("Generate open arc (open ellipse)"), "gen_arc", &wr, this, false), - other_arc(_("_Other Arc side"), _("switch sides of the arc"), "arc_other", &wr, this, false), - slice_arc(_("_Slice Arc"), _("slice the arc"), "slice_arc", &wr, this, false), + other_arc(_("_Other Arc side"), _("Switch sides of the arc"), "arc_other", &wr, this, false), + slice_arc(_("_Slice Arc"), _("Slice the arc"), "slice_arc", &wr, this, false), draw_axes(_("A_xes"), _("Draw both semi-major and semi-minor axes"), "draw_axes", &wr, this, false), rot_axes(_("Axes Rotation"), _("Axes rotation angle [deg]"), "rot_axes", &wr, this, 0), draw_ori_path(_("Source _Path"), _("Show the original source path"), "draw_ori_path", &wr, this, false) @@ -63,34 +61,37 @@ LPEPts2Ellipse::LPEPts2Ellipse(LivePathEffectObject *lpeobject) : registerParameter(&rot_axes); registerParameter(&draw_ori_path); - rot_axes.param_set_range(-360,360); - rot_axes.param_set_increments(1,10); + rot_axes.param_set_range(-360, 360); + rot_axes.param_set_increments(1, 10); - show_orig_path=true; + show_orig_path = true; } LPEPts2Ellipse::~LPEPts2Ellipse() -= default; +{ +} // helper function, transforms a given value into range [0, 2pi] inline double range2pi(double a) { - a = fmod(a, 2*M_PI); - if(a<0) a+=2*M_PI; + a = fmod(a, 2 * M_PI); + if (a < 0) { + a += 2 * M_PI; + } return a; } inline double deg2rad(double a) { - return a*M_PI/180.0; + return a * M_PI / 180.0; } inline double rad2deg(double a) { - return a*180.0/M_PI; + return a * 180.0 / M_PI; } // helper function, calculates the angle between a0 and a1 in ccw sense @@ -99,88 +100,85 @@ rad2deg(double a) inline double calc_delta_angle(const double a0, const double a1) { - double da=range2pi(a1-a0); - if((fabs(da)<1e-9) && (a0moveto(cos(start), sin(start)); - double s = start; - for (int i=0; i < nda; s = (++i)*da+start) { + double x0 = cos(s); + double y0 = sin(s); + // construct the path + Geom::Path path(Geom::Point(x0, y0)); + path.setStitching(true); + for (int i = 0; i < nda;) { double e = s + da; - if (e > end) + if (e > end) { e = end; - const double len = 4*tan((e - s)/4)/3; - const double x0 = cos(s); - const double y0 = sin(s); + } + const double len = 4 * tan((e - s) / 4) / 3; const double x1 = x0 + len * cos(s + M_PI_2); const double y1 = y0 + len * sin(s + M_PI_2); const double x3 = cos(e); const double y3 = sin(e); const double x2 = x3 + len * cos(e - M_PI_2); const double y2 = y3 + len * sin(e - M_PI_2); - curve->curveto(x1,y1, x2,y2, x3,y3); + path.appendNew(Geom::Point(x1, y1), Geom::Point(x2, y2), Geom::Point(x3, y3)); + s = (++i) * da + start; + x0 = cos(s); + y0 = sin(s); } if (slice && !closed) { - curve->lineto(0., 0.); + path.appendNew(Geom::Point(0.0, 0.0)); } - curve->transform(affine); + path *= affine; - path.append(*curve->first_path()); + path_in.append(path); if ((slice && !closed) || closed) { - path.close(true); + path_in.close(true); } - // give to GC - curve->unref(); return 0; } void gen_iso_frame_paths(Geom::PathVector &path_out, const Geom::Affine &affine) { - Geom::Path rect; - SPCurve curve; - // unit rectangle - curve.moveto(-1, -1); - curve.lineto(1, -1); - curve.lineto(1, 1); - curve.lineto(-1, 1); - //curve.transform(Rotate(-rot_angle)*affine); - curve.transform(affine); - rect.append(*curve.first_path()); + Geom::Path rect(Geom::Point(-1, -1)); + rect.setStitching(true); + rect.appendNew(Geom::Point(+1, -1)); + rect.appendNew(Geom::Point(+1, +1)); + rect.appendNew(Geom::Point(-1, +1)); + rect *= affine; rect.close(true); path_out.push_back(rect); } @@ -188,57 +186,58 @@ gen_iso_frame_paths(Geom::PathVector &path_out, const Geom::Affine &affine) void gen_axes_paths(Geom::PathVector &path_out, const Geom::Affine &affine) { - LineSegment clx(Point(-1,0),Point(1,0)); - LineSegment cly(Point(0,-1),Point(0,1)); + Geom::LineSegment clx(Geom::Point(-1, 0), Geom::Point(1, 0)); + Geom::LineSegment cly(Geom::Point(0, -1), Geom::Point(0, 1)); Geom::Path plx, ply; plx.append(clx); ply.append(cly); - plx*=affine; - ply*=affine; + plx *= affine; + ply *= affine; path_out.push_back(plx); path_out.push_back(ply); } bool -is_ccw(const std::vector & pts) +is_ccw(const std::vector &pts) { // method: sum up the angles between edges - size_t n=pts.size(); + size_t n = pts.size(); // edges about vertex 0 - Point e0=pts.front()-pts.back(); - Point e1=pts[1]-pts[0]; - Coord sum=cross(e0,e1); + Geom::Point e0(pts.front() - pts.back()); + Geom::Point e1(pts[1] - pts[0]); + Geom::Coord sum = cross(e0, e1); // the rest - for(size_t i=1;i pts; - for(const auto & pit : path_in) { + std::vector pts; + for (Geom::PathVector::const_iterator pit = path_in.begin(); pit != path_in.end(); ++pit) { // extract first point of this path - pts.push_back(pit.initialPoint()); + pts.push_back(pit->initialPoint()); // iterate over all curves - for (const auto & cit : pit) { - pts.push_back(cit.finalPoint()); + for (Geom::Path::const_iterator cit = pit->begin(); cit != pit->end(); ++cit) { + pts.push_back(cit->finalPoint()); } } // avoid identical start-point and end-point - if(pts.front() == pts.back()) { + if (pts.front() == pts.back()) { pts.pop_back(); } // special mode: Use first two edges, interpret them as two sides of a parallelogram and // generate an ellipse residing inside the parallelogram. This effect is quite useful when // generating isometric views. Hence, the name. - //if(gen_isometric.get_value()) - if(method == EM_ISONOMETRIC_CIRCLE) { - if(0!=genIsometricEllipse (pts, path_out)) - return path_in; - } else { - if(0!=genFitEllipse(pts, path_out)) - return path_in; + switch(method) { + case EM_ISOMETRIC_CIRCLE: + if (0 != genIsometricEllipse(pts, path_out)) { + return path_in; + } break; + case EM_STEINER_ELLIPSE: + if (0 != genSteinerEllipse(pts, false, path_out)) { + return path_in; + } break; + case EM_STEINER_INELLIPSE: + if (0 != genSteinerEllipse(pts, true, path_out)) { + return path_in; + } break; + default: + if (0 != genFitEllipse(pts, path_out)) { + return path_in; + } } return path_out; } @@ -296,164 +305,258 @@ LPEPts2Ellipse::doEffect_path (Geom::PathVector const & path_in) * ellipse. With 5 points each point is on the ellipse. For less points we get a circle. */ int -LPEPts2Ellipse::genFitEllipse (std::vector const & pts, - Geom::PathVector & path_out) +LPEPts2Ellipse::genFitEllipse(std::vector const &pts, + Geom::PathVector &path_out) { // rotation angle based on user provided rot_axes to position the vertices const double rot_angle = -deg2rad(rot_axes); // negative for ccw rotation - Affine affine; - affine*=Rotate(rot_angle); - Coord a0=0; - Coord a1=2*M_PI; - - if(pts.size()<2) { + Geom::Affine affine; + affine *= Geom::Rotate(rot_angle); + Geom::Coord a0 = 0; + Geom::Coord a1 = 2 * M_PI; + + if (pts.size() < 2) { return -1; - } else if(pts.size()==2) { + } else if (pts.size() == 2) { // simple line: circle in the middle of the line to the vertices - Point line=pts.front()-pts.back(); - double radius=line.length()*0.5; - if(radius<1e-9) + Geom::Point line = pts.front() - pts.back(); + double radius = line.length() * 0.5; + if (radius < 1e-9) { return -1; - Point center=middle_point(pts.front(),pts.back()); - Circle circle(center[0],center[1],radius); - affine*=Scale(circle.radius()); - affine*=Translate(circle.center()); + } + Geom::Point center = middle_point(pts.front(), pts.back()); + Geom::Circle circle(center[0], center[1], radius); + affine *= Geom::Scale(circle.radius()); + affine *= Geom::Translate(circle.center()); Geom::Path path; - unit_arc_path(path,affine); + unit_arc_path(path, affine); path_out.push_back(path); - } else if(pts.size()>=5 && EM_AUTO == method) { //!only_circle.get_value()) { + } else if (pts.size() >= 5 && EM_AUTO == method) { //!only_circle.get_value()) { // do ellipse try { - Ellipse ellipse; + Geom::Ellipse ellipse; ellipse.fit(pts); - affine*=Scale(ellipse.ray(X),ellipse.ray(Y)); - affine*=Rotate(ellipse.rotationAngle()); - affine*=Translate(ellipse.center()); - if(gen_arc.get_value()) { - Affine inv_affine=affine.inverse(); - Point p0=pts.front()*inv_affine; - Point p1=pts.back()*inv_affine; - const bool ccw_wind=is_ccw(pts); - endpoints2angles(ccw_wind,other_arc.get_value(),p0,p1,a0,a1); + affine *= Geom::Scale(ellipse.ray(Geom::X), ellipse.ray(Geom::Y)); + affine *= Geom::Rotate(ellipse.rotationAngle()); + affine *= Geom::Translate(ellipse.center()); + if (gen_arc.get_value()) { + Geom::Affine inv_affine = affine.inverse(); + Geom::Point p0 = pts.front() * inv_affine; + Geom::Point p1 = pts.back() * inv_affine; + const bool ccw_wind = is_ccw(pts); + endpoints2angles(ccw_wind, other_arc.get_value(), p0, p1, a0, a1); } Geom::Path path; - unit_arc_path(path,affine,a0,a1,slice_arc.get_value()); + unit_arc_path(path, affine, a0, a1, slice_arc.get_value()); path_out.push_back(path); - if(draw_axes.get_value()) { - gen_axes_paths(path_out,affine); + if (draw_axes.get_value()) { + gen_axes_paths(path_out, affine); } - } catch(...) { + } catch (...) { return -1; } } else { // do a circle (3,4 points, or only_circle set) try { - Circle circle; + Geom::Circle circle; circle.fit(pts); - affine*=Scale(circle.radius()); - affine*=Translate(circle.center()); - - if(gen_arc.get_value()) - { - Point p0=pts.front()-circle.center(); - Point p1=pts.back()-circle.center(); - const bool ccw_wind=is_ccw(pts); - endpoints2angles(ccw_wind,other_arc.get_value(),p0,p1,a0,a1); + affine *= Geom::Scale(circle.radius()); + affine *= Geom::Translate(circle.center()); + + if (gen_arc.get_value()) { + Geom::Point p0 = pts.front() - circle.center(); + Geom::Point p1 = pts.back() - circle.center(); + const bool ccw_wind = is_ccw(pts); + endpoints2angles(ccw_wind, other_arc.get_value(), p0, p1, a0, a1); } Geom::Path path; - unit_arc_path(path,affine,a0,a1,slice_arc.get_value()); + unit_arc_path(path, affine, a0, a1, slice_arc.get_value()); path_out.push_back(path); - } catch(...) { + } catch (...) { return -1; } } // draw frame? - if(gen_isometric_frame.get_value()) { - gen_iso_frame_paths(path_out,affine); + if (gen_isometric_frame.get_value()) { + gen_iso_frame_paths(path_out, affine); } // draw axes? - if(draw_axes.get_value()) { - gen_axes_paths(path_out,affine); + if (draw_axes.get_value()) { + gen_axes_paths(path_out, affine); } return 0; } int -LPEPts2Ellipse::genIsometricEllipse (std::vector const & pts, - Geom::PathVector & path_out) +LPEPts2Ellipse::genIsometricEllipse(std::vector const &pts, + Geom::PathVector &path_out) { // take the first 3 vertices for the edges - if(pts.size() < 3) return -1; + if (pts.size() < 3) { + return -1; + } // calc edges - Point e0=pts[0]-pts[1]; - Point e1=pts[2]-pts[1]; + Geom::Point e0 = pts[0] - pts[1]; + Geom::Point e1 = pts[2] - pts[1]; - Coord ce=cross(e0,e1); + Geom::Coord ce = cross(e0, e1); // parallel or one is zero? - if(fabs(ce)<1e-9) return -1; + if (fabs(ce) < 1e-9) { + return -1; + } // unit vectors along edges - Point u0=unit_vector(e0); - Point u1=unit_vector(e1); + Geom::Point u0 = unit_vector(e0); + Geom::Point u1 = unit_vector(e1); // calc angles - Coord a0=atan2(e0); + Geom::Coord a0 = atan2(e0); // Coord a1=M_PI_2-atan2(e1)-a0; - Coord a1=acos(dot(u0,u1))-M_PI_2; + Geom::Coord a1 = acos(dot(u0, u1)) - M_PI_2; // if(fabs(a1)<1e-9) return -1; - if(ce<0) a1=-a1; + if (ce < 0) { + a1 = -a1; + } // lengths: l0= length of edge 0; l1= height of parallelogram - Coord l0=e0.length()*0.5; - Point e0n=e1-dot(u0,e1)*u0; - Coord l1=e0n.length()*0.5; + Geom::Coord l0 = e0.length() * 0.5; + Geom::Point e0n = e1 - dot(u0, e1) * u0; + Geom::Coord l1 = e0n.length() * 0.5; // center of the ellipse - Point pos=pts[1]+0.5*(e0+e1); + Geom::Point pos = pts[1] + 0.5 * (e0 + e1); + + // rotation angle based on user provided rot_axes to position the vertices + const double rot_angle = -deg2rad(rot_axes); // negative for ccw rotation + + // build up the affine transformation + Geom::Affine affine; + affine *= Geom::Rotate(rot_angle); + affine *= Geom::Scale(l0, l1); + affine *= Geom::HShear(-tan(a1)); + affine *= Geom::Rotate(a0); + affine *= Geom::Translate(pos); + + Geom::Path path; + unit_arc_path(path, affine); + path_out.push_back(path); + + // draw frame? + if (gen_isometric_frame.get_value()) { + gen_iso_frame_paths(path_out, affine); + } + + // draw axes? + if (draw_axes.get_value()) { + gen_axes_paths(path_out, affine); + } + + return 0; +} + +void +evalSteinerEllipse(Geom::Point const &pCenter, + Geom::Point const &pCenter_Pt2, + Geom::Point const &pPt0_Pt1, + const double &angle, + Geom::Point &pRes) +{ + // formula for the evaluation of points on the steiner ellipse using parameter angle + pRes = pCenter + + pCenter_Pt2*cos(angle) + + pPt0_Pt1*sin(angle)/sqrt(3); +} + +int +LPEPts2Ellipse::genSteinerEllipse(std::vector const &pts, + bool gen_inellipse, + Geom::PathVector &path_out) +{ + // take the first 3 vertices for the edges + if (pts.size() < 3) { + return -1; + } + // calc center + Geom::Point pCenter = (pts[0]+pts[1]+pts[2])/3; + // calc main directions of affine triangle + Geom::Point f1 = pts[2]-pCenter; + Geom::Point f2 = (pts[1]-pts[0])/sqrt(3); + + // calc zero angle t0 + const double denominator = dot(f1, f1) - dot(f2, f2); + double t0=0; + if(fabs(denominator) > 1e-12) { + const double cot2t0 = 2.0 * dot(f1, f2) / denominator; + t0 = atan(cot2t0)/2.0; + } + + // calc relative points of main axes (for axis directions) + Geom::Point p0(0,0), pRel0, pRel1; + evalSteinerEllipse(p0, pts[2]-pCenter, pts[1]-pts[0], t0, pRel0); + evalSteinerEllipse(p0, pts[2]-pCenter, pts[1]-pts[0], t0+M_PI_2, pRel1); + Geom::Coord l0 = pRel0.length(); + Geom::Coord l1 = pRel1.length(); + + // basic rotation + double a0 = atan2(pRel0); + + bool swapped=false; + + if (l1 > l0) { + std::swap(l0,l1); + a0 += M_PI_2; + swapped = true; + } + + // the steiner inellipse is just scaled down by 2 + if(gen_inellipse) { + l0/=2; + l1/=2; + } // rotation angle based on user provided rot_axes to position the vertices const double rot_angle = -deg2rad(rot_axes); // negative for ccw rotation // build up the affine transformation - Affine affine; - affine*=Rotate(rot_angle); - affine*=Scale(l0,l1); - affine*=HShear(-tan(a1)); - affine*=Rotate(a0); - affine*=Translate(pos); + Geom::Affine affine; + affine *= Geom::Rotate(rot_angle); + affine *= Geom::Scale(l0, l1); + affine *= Geom::Rotate(a0); + affine *= Geom::Translate(pCenter); Geom::Path path; - unit_arc_path(path,affine); + unit_arc_path(path, affine); path_out.push_back(path); // draw frame? - if(gen_isometric_frame.get_value()) { - gen_iso_frame_paths(path_out,affine); + if (gen_isometric_frame.get_value()) { + gen_iso_frame_paths(path_out, affine); } // draw axes? - if(draw_axes.get_value()) { - gen_axes_paths(path_out,affine); + if (draw_axes.get_value()) { + gen_axes_paths(path_out, affine); } return 0; } + /* ######################## */ } //namespace LivePathEffect } /* namespace Inkscape */ - /* - Local Variables: - mode:c++ - c-file-style:"stroustrup" - c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) - indent-tabs-mode:nil - fill-column:99 - End: - */ - // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/live_effects/lpe-pts2ellipse.h b/src/live_effects/lpe-pts2ellipse.h index f9fd90109c..1a48448934 100644 --- a/src/live_effects/lpe-pts2ellipse.h +++ b/src/live_effects/lpe-pts2ellipse.h @@ -1,4 +1,3 @@ -// SPDX-License-Identifier: GPL-2.0-or-later #ifndef INKSCAPE_LPE_PTS_TO_ELLIPSE_H #define INKSCAPE_LPE_PTS_TO_ELLIPSE_H @@ -12,7 +11,7 @@ * * Copyright (C) Markus Schwienbacher 2013 * - * Released under GNU GPL v2+, read the file 'COPYING' for more information. + * Released under GNU GPL, read the file 'COPYING' for more information */ #include "live_effects/effect.h" @@ -27,27 +26,33 @@ namespace LivePathEffect { enum EllipseMethod { EM_AUTO, EM_CIRCLE, - EM_ISONOMETRIC_CIRCLE, + EM_ISOMETRIC_CIRCLE, + EM_STEINER_ELLIPSE, + EM_STEINER_INELLIPSE, EM_END }; class LPEPts2Ellipse : public Effect { public: - LPEPts2Ellipse(LivePathEffectObject *lpeobject); + explicit LPEPts2Ellipse(LivePathEffectObject *lpeobject); ~LPEPts2Ellipse() override; - Geom::PathVector doEffect_path (Geom::PathVector const & path_in) override; + Geom::PathVector doEffect_path(Geom::PathVector const &path_in) override; private: - LPEPts2Ellipse(const LPEPts2Ellipse&) = delete; - LPEPts2Ellipse& operator=(const LPEPts2Ellipse&) = delete; + LPEPts2Ellipse(const LPEPts2Ellipse &); + LPEPts2Ellipse &operator=(const LPEPts2Ellipse &); - int genIsometricEllipse (std::vector const & points_in, - Geom::PathVector & path_out); + int genIsometricEllipse(std::vector const &points_in, + Geom::PathVector &path_out); - int genFitEllipse (std::vector const & points_in, - Geom::PathVector & path_out); + int genFitEllipse(std::vector const &points_in, + Geom::PathVector &path_out); + + int genSteinerEllipse(std::vector const &points_in, + bool gen_inellipse, + Geom::PathVector &path_out); EnumParam method; BoolParam gen_isometric_frame; -- GitLab From c6c398af81ea30b0ab2a58afe7d11ccd28155e7b Mon Sep 17 00:00:00 2001 From: schwieni Date: Tue, 12 Mar 2019 22:56:50 +0100 Subject: [PATCH 2/7] lpe-pts2ellipse: added Steiner ellipse and inellipse generation from three points. Did some minor cleanup too. Added me to the AUTHORS file. --- src/live_effects/lpe-pts2ellipse.cpp | 168 ++++++++++++--------------- src/live_effects/lpe-pts2ellipse.h | 36 +++--- 2 files changed, 85 insertions(+), 119 deletions(-) diff --git a/src/live_effects/lpe-pts2ellipse.cpp b/src/live_effects/lpe-pts2ellipse.cpp index f9e23d68a3..80f1b60043 100644 --- a/src/live_effects/lpe-pts2ellipse.cpp +++ b/src/live_effects/lpe-pts2ellipse.cpp @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /** \file * LPE "Points to Ellipse" implementation */ @@ -8,22 +9,22 @@ * * Copyright (C) Markus Schwienbacher 2013 * - * Released under GNU GPL, read the file 'COPYING' for more information + * Released under GNU GPL v2+, read the file 'COPYING' for more information. */ #include "live_effects/lpe-pts2ellipse.h" -#include +#include #include #include -#include +#include #include -#include <2geom/path.h> #include <2geom/circle.h> #include <2geom/ellipse.h> -#include <2geom/pathvector.h> #include <2geom/elliptical-arc.h> +#include <2geom/path.h> +#include <2geom/pathvector.h> #include @@ -31,26 +32,28 @@ namespace Inkscape { namespace LivePathEffect { static const Util::EnumData EllipseMethodData[] = { - { EM_AUTO, N_("Auto ellipse"), "auto" }, //!< (2..4 points: circle, from 5 points: ellipse) - { EM_CIRCLE, N_("Force circle"), "circle" }, //!< always fit a circle - { EM_ISOMETRIC_CIRCLE, N_("Isometric circle"), "iso_circle" }, //!< use first two edges to generate a sheared ellipse - { EM_STEINER_ELLIPSE, N_("Steiner ellipse"), "steiner_ellipse" }, //!< generate a steiner ellipse from the first three points - { EM_STEINER_INELLIPSE, N_("Steiner inellipse"), "steiner_inellipse" } //!< generate a steiner inellipse from the first three points + { EM_AUTO, N_("Auto ellipse"), "auto" }, //!< (2..4 points: circle, from 5 points: ellipse) + { EM_CIRCLE, N_("Force circle"), "circle" }, //!< always fit a circle + { EM_ISOMETRIC_CIRCLE, N_("Isometric circle"), "iso_circle" }, //!< use first two edges to generate a sheared + //!< ellipse + { EM_STEINER_ELLIPSE, N_("Steiner ellipse"), "steiner_ellipse" }, //!< generate a steiner ellipse from the first + //!< three points + { EM_STEINER_INELLIPSE, N_("Steiner inellipse"), "steiner_inellipse" } //!< generate a steiner inellipse from the + //!< first three points }; static const Util::EnumDataConverter EMConverter(EllipseMethodData, EM_END); -LPEPts2Ellipse::LPEPts2Ellipse(LivePathEffectObject *lpeobject) : - Effect(lpeobject), - method(_("Method:"), _("Methods to generate the ellipse"), - "method", EMConverter, &wr, this, EM_AUTO), - gen_isometric_frame(_("_Frame (isometric rectangle)"), _("Draw parallelogram around the ellipse"), - "gen_isometric_frame", &wr, this, false), - gen_arc(_("_Arc"), _("Generate open arc (open ellipse)"), "gen_arc", &wr, this, false), - other_arc(_("_Other Arc side"), _("Switch sides of the arc"), "arc_other", &wr, this, false), - slice_arc(_("_Slice Arc"), _("Slice the arc"), "slice_arc", &wr, this, false), - draw_axes(_("A_xes"), _("Draw both semi-major and semi-minor axes"), "draw_axes", &wr, this, false), - rot_axes(_("Axes Rotation"), _("Axes rotation angle [deg]"), "rot_axes", &wr, this, 0), - draw_ori_path(_("Source _Path"), _("Show the original source path"), "draw_ori_path", &wr, this, false) +LPEPts2Ellipse::LPEPts2Ellipse(LivePathEffectObject *lpeobject) + : Effect(lpeobject) + , method(_("Method:"), _("Methods to generate the ellipse"), "method", EMConverter, &wr, this, EM_AUTO) + , gen_isometric_frame(_("_Frame (isometric rectangle)"), _("Draw parallelogram around the ellipse"), + "gen_isometric_frame", &wr, this, false) + , gen_arc(_("_Arc"), _("Generate open arc (open ellipse)"), "gen_arc", &wr, this, false) + , other_arc(_("_Other Arc side"), _("Switch sides of the arc"), "arc_other", &wr, this, false) + , slice_arc(_("_Slice Arc"), _("Slice the arc"), "slice_arc", &wr, this, false) + , draw_axes(_("A_xes"), _("Draw both semi-major and semi-minor axes"), "draw_axes", &wr, this, false) + , rot_axes(_("Axes Rotation"), _("Axes rotation angle [deg]"), "rot_axes", &wr, this, 0) + , draw_ori_path(_("Source _Path"), _("Show the original source path"), "draw_ori_path", &wr, this, false) { registerParameter(&method); registerParameter(&gen_arc); @@ -67,13 +70,10 @@ LPEPts2Ellipse::LPEPts2Ellipse(LivePathEffectObject *lpeobject) : show_orig_path = true; } -LPEPts2Ellipse::~LPEPts2Ellipse() -{ -} +LPEPts2Ellipse::~LPEPts2Ellipse() = default; // helper function, transforms a given value into range [0, 2pi] -inline double -range2pi(double a) +inline double range2pi(double a) { a = fmod(a, 2 * M_PI); if (a < 0) { @@ -82,23 +82,14 @@ range2pi(double a) return a; } -inline double -deg2rad(double a) -{ - return a * M_PI / 180.0; -} +inline double deg2rad(double a) { return a * M_PI / 180.0; } -inline double -rad2deg(double a) -{ - return a * 180.0 / M_PI; -} +inline double rad2deg(double a) { return a * 180.0 / M_PI; } // helper function, calculates the angle between a0 and a1 in ccw sense // examples: 0..1->1, -1..1->2, pi/4..-pi/4->1.5pi // full rotations: 0..2pi->2pi, -pi..pi->2pi, pi..-pi->0, 2pi..0->0 -inline double -calc_delta_angle(const double a0, const double a1) +inline double calc_delta_angle(const double a0, const double a1) { double da = range2pi(a1 - a0); if ((fabs(da) < 1e-9) && (a0 < a1)) { @@ -107,10 +98,8 @@ calc_delta_angle(const double a0, const double a1) return da; } -int -unit_arc_path(Geom::Path &path_in, Geom::Affine &affine, - double start = 0.0, double end = 2 * M_PI, // angles - bool slice = false) +int unit_arc_path(Geom::Path &path_in, Geom::Affine &affine, double start = 0.0, double end = 2 * M_PI, // angles + bool slice = false) { double arc_angle = calc_delta_angle(start, end); if (fabs(arc_angle) < 1e-9) { @@ -170,8 +159,7 @@ unit_arc_path(Geom::Path &path_in, Geom::Affine &affine, return 0; } -void -gen_iso_frame_paths(Geom::PathVector &path_out, const Geom::Affine &affine) +void gen_iso_frame_paths(Geom::PathVector &path_out, const Geom::Affine &affine) { Geom::Path rect(Geom::Point(-1, -1)); rect.setStitching(true); @@ -183,8 +171,7 @@ gen_iso_frame_paths(Geom::PathVector &path_out, const Geom::Affine &affine) path_out.push_back(rect); } -void -gen_axes_paths(Geom::PathVector &path_out, const Geom::Affine &affine) +void gen_axes_paths(Geom::PathVector &path_out, const Geom::Affine &affine) { Geom::LineSegment clx(Geom::Point(-1, 0), Geom::Point(1, 0)); Geom::LineSegment cly(Geom::Point(0, -1), Geom::Point(0, 1)); @@ -199,8 +186,7 @@ gen_axes_paths(Geom::PathVector &path_out, const Geom::Affine &affine) path_out.push_back(ply); } -bool -is_ccw(const std::vector &pts) +bool is_ccw(const std::vector &pts) { // method: sum up the angles between edges size_t n = pts.size(); @@ -227,8 +213,8 @@ is_ccw(const std::vector &pts) } } -void -endpoints2angles(const bool ccw_wind, const bool use_other_arc, const Geom::Point &p0, const Geom::Point &p1, Geom::Coord &a0, Geom::Coord &a1) +void endpoints2angles(const bool ccw_wind, const bool use_other_arc, const Geom::Point &p0, const Geom::Point &p1, + Geom::Coord &a0, Geom::Coord &a1) { if (!p0.isZero() && !p1.isZero()) { a0 = atan2(p0); @@ -247,8 +233,7 @@ endpoints2angles(const bool ccw_wind, const bool use_other_arc, const Geom::Poin * algorithms from 2geom. Depending on the settings made by the user regarding things like arc, * slice, circle etc. the final result will be different */ -Geom::PathVector -LPEPts2Ellipse::doEffect_path(Geom::PathVector const &path_in) +Geom::PathVector LPEPts2Ellipse::doEffect_path(Geom::PathVector const &path_in) { Geom::PathVector path_out; @@ -260,12 +245,12 @@ LPEPts2Ellipse::doEffect_path(Geom::PathVector const &path_in) // from: extension/internal/odf.cpp // get all points std::vector pts; - for (Geom::PathVector::const_iterator pit = path_in.begin(); pit != path_in.end(); ++pit) { + for(const auto & pit : path_in) { // extract first point of this path - pts.push_back(pit->initialPoint()); + pts.push_back(pit.initialPoint()); // iterate over all curves - for (Geom::Path::const_iterator cit = pit->begin(); cit != pit->end(); ++cit) { - pts.push_back(cit->finalPoint()); + for (const auto &cit : pit) { + pts.push_back(cit.finalPoint()); } } @@ -277,19 +262,22 @@ LPEPts2Ellipse::doEffect_path(Geom::PathVector const &path_in) // special mode: Use first two edges, interpret them as two sides of a parallelogram and // generate an ellipse residing inside the parallelogram. This effect is quite useful when // generating isometric views. Hence, the name. - switch(method) { + switch (method) { case EM_ISOMETRIC_CIRCLE: if (0 != genIsometricEllipse(pts, path_out)) { return path_in; - } break; + } + break; case EM_STEINER_ELLIPSE: if (0 != genSteinerEllipse(pts, false, path_out)) { return path_in; - } break; + } + break; case EM_STEINER_INELLIPSE: if (0 != genSteinerEllipse(pts, true, path_out)) { return path_in; - } break; + } + break; default: if (0 != genFitEllipse(pts, path_out)) { return path_in; @@ -304,9 +292,7 @@ LPEPts2Ellipse::doEffect_path(Geom::PathVector const &path_in) * slice, circle etc. the final result will be different. We need at least 5 points to fit an * ellipse. With 5 points each point is on the ellipse. For less points we get a circle. */ -int -LPEPts2Ellipse::genFitEllipse(std::vector const &pts, - Geom::PathVector &path_out) +int LPEPts2Ellipse::genFitEllipse(std::vector const &pts, Geom::PathVector &path_out) { // rotation angle based on user provided rot_axes to position the vertices const double rot_angle = -deg2rad(rot_axes); // negative for ccw rotation @@ -331,7 +317,7 @@ LPEPts2Ellipse::genFitEllipse(std::vector const &pts, Geom::Path path; unit_arc_path(path, affine); path_out.push_back(path); - } else if (pts.size() >= 5 && EM_AUTO == method) { //!only_circle.get_value()) { + } else if (pts.size() >= 5 && EM_AUTO == method) { //! only_circle.get_value()) { // do ellipse try { Geom::Ellipse ellipse; @@ -392,9 +378,7 @@ LPEPts2Ellipse::genFitEllipse(std::vector const &pts, return 0; } -int -LPEPts2Ellipse::genIsometricEllipse(std::vector const &pts, - Geom::PathVector &path_out) +int LPEPts2Ellipse::genIsometricEllipse(std::vector const &pts, Geom::PathVector &path_out) { // take the first 3 vertices for the edges @@ -457,64 +441,56 @@ LPEPts2Ellipse::genIsometricEllipse(std::vector const &pts, return 0; } -void -evalSteinerEllipse(Geom::Point const &pCenter, - Geom::Point const &pCenter_Pt2, - Geom::Point const &pPt0_Pt1, - const double &angle, - Geom::Point &pRes) +void evalSteinerEllipse(Geom::Point const &pCenter, Geom::Point const &pCenter_Pt2, Geom::Point const &pPt0_Pt1, + const double &angle, Geom::Point &pRes) { // formula for the evaluation of points on the steiner ellipse using parameter angle - pRes = pCenter - + pCenter_Pt2*cos(angle) - + pPt0_Pt1*sin(angle)/sqrt(3); + pRes = pCenter + pCenter_Pt2 * cos(angle) + pPt0_Pt1 * sin(angle) / sqrt(3); } -int -LPEPts2Ellipse::genSteinerEllipse(std::vector const &pts, - bool gen_inellipse, - Geom::PathVector &path_out) +int LPEPts2Ellipse::genSteinerEllipse(std::vector const &pts, bool gen_inellipse, + Geom::PathVector &path_out) { // take the first 3 vertices for the edges if (pts.size() < 3) { return -1; } // calc center - Geom::Point pCenter = (pts[0]+pts[1]+pts[2])/3; + Geom::Point pCenter = (pts[0] + pts[1] + pts[2]) / 3; // calc main directions of affine triangle - Geom::Point f1 = pts[2]-pCenter; - Geom::Point f2 = (pts[1]-pts[0])/sqrt(3); + Geom::Point f1 = pts[2] - pCenter; + Geom::Point f2 = (pts[1] - pts[0]) / sqrt(3); // calc zero angle t0 const double denominator = dot(f1, f1) - dot(f2, f2); - double t0=0; - if(fabs(denominator) > 1e-12) { + double t0 = 0; + if (fabs(denominator) > 1e-12) { const double cot2t0 = 2.0 * dot(f1, f2) / denominator; - t0 = atan(cot2t0)/2.0; + t0 = atan(cot2t0) / 2.0; } // calc relative points of main axes (for axis directions) - Geom::Point p0(0,0), pRel0, pRel1; - evalSteinerEllipse(p0, pts[2]-pCenter, pts[1]-pts[0], t0, pRel0); - evalSteinerEllipse(p0, pts[2]-pCenter, pts[1]-pts[0], t0+M_PI_2, pRel1); + Geom::Point p0(0, 0), pRel0, pRel1; + evalSteinerEllipse(p0, pts[2] - pCenter, pts[1] - pts[0], t0, pRel0); + evalSteinerEllipse(p0, pts[2] - pCenter, pts[1] - pts[0], t0 + M_PI_2, pRel1); Geom::Coord l0 = pRel0.length(); Geom::Coord l1 = pRel1.length(); // basic rotation double a0 = atan2(pRel0); - bool swapped=false; + bool swapped = false; if (l1 > l0) { - std::swap(l0,l1); + std::swap(l0, l1); a0 += M_PI_2; swapped = true; } // the steiner inellipse is just scaled down by 2 - if(gen_inellipse) { - l0/=2; - l1/=2; + if (gen_inellipse) { + l0 /= 2; + l1 /= 2; } // rotation angle based on user provided rot_axes to position the vertices @@ -547,7 +523,7 @@ LPEPts2Ellipse::genSteinerEllipse(std::vector const &pts, /* ######################## */ -} //namespace LivePathEffect +} // namespace LivePathEffect } /* namespace Inkscape */ /* diff --git a/src/live_effects/lpe-pts2ellipse.h b/src/live_effects/lpe-pts2ellipse.h index 1a48448934..46722aa867 100644 --- a/src/live_effects/lpe-pts2ellipse.h +++ b/src/live_effects/lpe-pts2ellipse.h @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-or-later #ifndef INKSCAPE_LPE_PTS_TO_ELLIPSE_H #define INKSCAPE_LPE_PTS_TO_ELLIPSE_H @@ -11,7 +12,7 @@ * * Copyright (C) Markus Schwienbacher 2013 * - * Released under GNU GPL, read the file 'COPYING' for more information + * Released under GNU GPL v2+, read the file 'COPYING' for more information. */ #include "live_effects/effect.h" @@ -23,36 +24,25 @@ namespace Inkscape { namespace LivePathEffect { -enum EllipseMethod { - EM_AUTO, - EM_CIRCLE, - EM_ISOMETRIC_CIRCLE, - EM_STEINER_ELLIPSE, - EM_STEINER_INELLIPSE, - EM_END -}; +enum EllipseMethod { EM_AUTO, EM_CIRCLE, EM_ISOMETRIC_CIRCLE, EM_STEINER_ELLIPSE, EM_STEINER_INELLIPSE, EM_END }; class LPEPts2Ellipse : public Effect { -public: - explicit LPEPts2Ellipse(LivePathEffectObject *lpeobject); + public: + LPEPts2Ellipse(LivePathEffectObject *lpeobject); ~LPEPts2Ellipse() override; Geom::PathVector doEffect_path(Geom::PathVector const &path_in) override; -private: - LPEPts2Ellipse(const LPEPts2Ellipse &); - LPEPts2Ellipse &operator=(const LPEPts2Ellipse &); + private: + LPEPts2Ellipse(const LPEPts2Ellipse &) = delete; + LPEPts2Ellipse &operator=(const LPEPts2Ellipse &) = delete; - int genIsometricEllipse(std::vector const &points_in, - Geom::PathVector &path_out); + int genIsometricEllipse(std::vector const &points_in, Geom::PathVector &path_out); - int genFitEllipse(std::vector const &points_in, - Geom::PathVector &path_out); + int genFitEllipse(std::vector const &points_in, Geom::PathVector &path_out); - int genSteinerEllipse(std::vector const &points_in, - bool gen_inellipse, - Geom::PathVector &path_out); + int genSteinerEllipse(std::vector const &points_in, bool gen_inellipse, Geom::PathVector &path_out); EnumParam method; BoolParam gen_isometric_frame; @@ -66,8 +56,8 @@ private: std::vector points; }; -} //namespace LivePathEffect -} //namespace Inkscape +} // namespace LivePathEffect +} // namespace Inkscape #endif -- GitLab From 015c724062695efd9c8bbdc8e068677a51052f37 Mon Sep 17 00:00:00 2001 From: schwieni Date: Thu, 21 Mar 2019 18:18:54 +0100 Subject: [PATCH 3/7] correct clang-format lpe-pts2ellipse.cpp --- src/live_effects/lpe-pts2ellipse.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/live_effects/lpe-pts2ellipse.cpp b/src/live_effects/lpe-pts2ellipse.cpp index 80f1b60043..f831030322 100644 --- a/src/live_effects/lpe-pts2ellipse.cpp +++ b/src/live_effects/lpe-pts2ellipse.cpp @@ -245,7 +245,7 @@ Geom::PathVector LPEPts2Ellipse::doEffect_path(Geom::PathVector const &path_in) // from: extension/internal/odf.cpp // get all points std::vector pts; - for(const auto & pit : path_in) { + for (const auto &pit : path_in) { // extract first point of this path pts.push_back(pit.initialPoint()); // iterate over all curves -- GitLab From 6949c0d92402e635b23a4ed49b83e1f38b8a2cb1 Mon Sep 17 00:00:00 2001 From: schwieni Date: Fri, 22 Mar 2019 13:32:41 +0100 Subject: [PATCH 4/7] lpe-pts2ellipse: parameter enabling based on creation method --- src/live_effects/effect.cpp | 5 ++++ src/live_effects/lpe-pts2ellipse.cpp | 35 +++++++++++++++++++----- src/live_effects/parameter/parameter.cpp | 1 + src/live_effects/parameter/parameter.h | 2 ++ 4 files changed, 36 insertions(+), 7 deletions(-) diff --git a/src/live_effects/effect.cpp b/src/live_effects/effect.cpp index 63895ed619..0c027a57ff 100644 --- a/src/live_effects/effect.cpp +++ b/src/live_effects/effect.cpp @@ -1488,6 +1488,11 @@ Effect::newWidget() if ((*it)->widget_is_visible) { Parameter * param = *it; Gtk::Widget * widg = param->param_newWidget(); + if (param->widget_is_enabled) { + widg->set_sensitive(true); + } else { + widg->set_sensitive(false); + } Glib::ustring * tip = param->param_getTooltip(); if (widg) { vbox->pack_start(*widg, true, true, 2); diff --git a/src/live_effects/lpe-pts2ellipse.cpp b/src/live_effects/lpe-pts2ellipse.cpp index f831030322..8fb6c89f7c 100644 --- a/src/live_effects/lpe-pts2ellipse.cpp +++ b/src/live_effects/lpe-pts2ellipse.cpp @@ -49,11 +49,11 @@ LPEPts2Ellipse::LPEPts2Ellipse(LivePathEffectObject *lpeobject) , gen_isometric_frame(_("_Frame (isometric rectangle)"), _("Draw parallelogram around the ellipse"), "gen_isometric_frame", &wr, this, false) , gen_arc(_("_Arc"), _("Generate open arc (open ellipse)"), "gen_arc", &wr, this, false) - , other_arc(_("_Other Arc side"), _("Switch sides of the arc"), "arc_other", &wr, this, false) - , slice_arc(_("_Slice Arc"), _("Slice the arc"), "slice_arc", &wr, this, false) + , other_arc(_("_Other arc side"), _("Switch sides of the arc"), "arc_other", &wr, this, false) + , slice_arc(_("_Slice arc"), _("Slice the arc"), "slice_arc", &wr, this, false) , draw_axes(_("A_xes"), _("Draw both semi-major and semi-minor axes"), "draw_axes", &wr, this, false) - , rot_axes(_("Axes Rotation"), _("Axes rotation angle [deg]"), "rot_axes", &wr, this, 0) - , draw_ori_path(_("Source _Path"), _("Show the original source path"), "draw_ori_path", &wr, this, false) + , rot_axes(_("Axes rotation"), _("Axes rotation angle [deg]"), "rot_axes", &wr, this, 0) + , draw_ori_path(_("Source _path"), _("Show the original source path"), "draw_ori_path", &wr, this, false) { registerParameter(&method); registerParameter(&gen_arc); @@ -259,11 +259,32 @@ Geom::PathVector LPEPts2Ellipse::doEffect_path(Geom::PathVector const &path_in) pts.pop_back(); } - // special mode: Use first two edges, interpret them as two sides of a parallelogram and - // generate an ellipse residing inside the parallelogram. This effect is quite useful when - // generating isometric views. Hence, the name. + // modify GUI based on selected method switch (method) { case EM_ISOMETRIC_CIRCLE: + case EM_STEINER_ELLIPSE: + case EM_STEINER_INELLIPSE: + gen_arc.param_widget_is_enabled(false); + other_arc.param_widget_is_enabled(false); + slice_arc.param_widget_is_enabled(false); + break; + default: + gen_arc.param_widget_is_enabled(true); + if (gen_arc.get_value()) { + slice_arc.param_widget_is_enabled(true); + other_arc.param_widget_is_enabled(true); + } else { + other_arc.param_widget_is_enabled(false); + slice_arc.param_widget_is_enabled(false); + } + } + + // call method specific code + switch (method) { + case EM_ISOMETRIC_CIRCLE: + // special mode: Use first two edges, interpret them as two sides of a parallelogram and + // generate an ellipse residing inside the parallelogram. This effect is quite useful when + // generating isometric views. Hence, the name. if (0 != genIsometricEllipse(pts, path_out)) { return path_in; } diff --git a/src/live_effects/parameter/parameter.cpp b/src/live_effects/parameter/parameter.cpp index a6f8021b3c..a235e60765 100644 --- a/src/live_effects/parameter/parameter.cpp +++ b/src/live_effects/parameter/parameter.cpp @@ -34,6 +34,7 @@ Parameter::Parameter( Glib::ustring label, Glib::ustring tip, param_label(std::move(label)), oncanvas_editable(false), widget_is_visible(true), + widget_is_enabled(true), param_tooltip(std::move(tip)), param_effect(effect) { diff --git a/src/live_effects/parameter/parameter.h b/src/live_effects/parameter/parameter.h index 4ef31427ec..62684b7308 100644 --- a/src/live_effects/parameter/parameter.h +++ b/src/live_effects/parameter/parameter.h @@ -63,6 +63,7 @@ public: virtual gchar * param_getSVGValue() const = 0; virtual gchar * param_getDefaultSVGValue() const = 0; virtual void param_widget_is_visible(bool is_visible) {widget_is_visible = is_visible;} + virtual void param_widget_is_enabled(bool is_enabled) {widget_is_enabled = is_enabled;} void write_to_SVG(); virtual void param_set_default() = 0; @@ -89,6 +90,7 @@ public: bool oncanvas_editable; bool widget_is_visible; + bool widget_is_enabled; protected: -- GitLab From 633f397a4de38f4a86cbf3b2218fcb35e42a61e9 Mon Sep 17 00:00:00 2001 From: schwieni Date: Sun, 24 Mar 2019 10:14:33 +0100 Subject: [PATCH 5/7] lpe-pts2ellipse: added perspective circle from 4 points improved tool-tips for better usability --- src/live_effects/lpe-pts2ellipse.cpp | 283 +++++++++++++++++++++++---- src/live_effects/lpe-pts2ellipse.h | 41 +++- 2 files changed, 278 insertions(+), 46 deletions(-) diff --git a/src/live_effects/lpe-pts2ellipse.cpp b/src/live_effects/lpe-pts2ellipse.cpp index 8fb6c89f7c..e87b731b71 100644 --- a/src/live_effects/lpe-pts2ellipse.cpp +++ b/src/live_effects/lpe-pts2ellipse.cpp @@ -12,7 +12,8 @@ * Released under GNU GPL v2+, read the file 'COPYING' for more information. */ -#include "live_effects/lpe-pts2ellipse.h" +#include "lpe-pts2ellipse.h" + #include #include @@ -32,10 +33,13 @@ namespace Inkscape { namespace LivePathEffect { static const Util::EnumData EllipseMethodData[] = { - { EM_AUTO, N_("Auto ellipse"), "auto" }, //!< (2..4 points: circle, from 5 points: ellipse) - { EM_CIRCLE, N_("Force circle"), "circle" }, //!< always fit a circle - { EM_ISOMETRIC_CIRCLE, N_("Isometric circle"), "iso_circle" }, //!< use first two edges to generate a sheared - //!< ellipse + { EM_AUTO, N_("Auto ellipse"), "auto" }, //!< (2..4 points: circle, from 5 points: ellipse) + { EM_CIRCLE, N_("Force circle"), "circle" }, //!< always fit a circle + { EM_ISOMETRIC_CIRCLE, N_("Isometric circle"), "iso_circle" }, //!< use first two edges to generate a sheared + //!< ellipse + { EM_PERSPECTIVE_CIRCLE, N_("Perspective circle"), "perspective_circle" }, //!< use first three edges to generate an + //!< ellipse representing a distorted + //!< circle in perspective { EM_STEINER_ELLIPSE, N_("Steiner ellipse"), "steiner_ellipse" }, //!< generate a steiner ellipse from the first //!< three points { EM_STEINER_INELLIPSE, N_("Steiner inellipse"), "steiner_inellipse" } //!< generate a steiner inellipse from the @@ -45,13 +49,17 @@ static const Util::EnumDataConverter EMConverter(EllipseMethodDat LPEPts2Ellipse::LPEPts2Ellipse(LivePathEffectObject *lpeobject) : Effect(lpeobject) - , method(_("Method:"), _("Methods to generate the ellipse"), "method", EMConverter, &wr, this, EM_AUTO) + , method(_("Method:"), _("Methods to generate the ellipse\n- Auto ellipse: fits a circle (2..4 points) or an ellipse (at least 5 points)\n- Force circle: (at least 2 points) always fit to a circle\n- Isometric circle: (3 points) use first two edges\n- Perspective circle: (4 points) circle in a square in perspective view\n- Steiner ellipse: (3 points) ellipse on a triangle\n- Steiner inellipse: (3 points) ellipse inside a triangle"), "method", EMConverter, &wr, this, EM_AUTO) , gen_isometric_frame(_("_Frame (isometric rectangle)"), _("Draw parallelogram around the ellipse"), "gen_isometric_frame", &wr, this, false) - , gen_arc(_("_Arc"), _("Generate open arc (open ellipse)"), "gen_arc", &wr, this, false) + , gen_perspective_frame(_("_Perspective square"), _("Draw square surrounding the circle in perspective view\n(only in method \"Perspective circle\")"), + "gen_perspective_frame", &wr, this, false) + , gen_arc(_("_Arc"), _("Generate open arc (open ellipse) based on first and last point\n(only for methods \"Auto ellipse\" and \"Force circle\")"), "gen_arc", &wr, this, false) , other_arc(_("_Other arc side"), _("Switch sides of the arc"), "arc_other", &wr, this, false) , slice_arc(_("_Slice arc"), _("Slice the arc"), "slice_arc", &wr, this, false) , draw_axes(_("A_xes"), _("Draw both semi-major and semi-minor axes"), "draw_axes", &wr, this, false) + , draw_perspective_axes(_("Perspective axes"), _("Draw the axes in perspective view\n(only in method \"Perspective circle\")"), "draw_perspective_axes", &wr, + this, false) , rot_axes(_("Axes rotation"), _("Axes rotation angle [deg]"), "rot_axes", &wr, this, 0) , draw_ori_path(_("Source _path"), _("Show the original source path"), "draw_ori_path", &wr, this, false) { @@ -61,6 +69,8 @@ LPEPts2Ellipse::LPEPts2Ellipse(LivePathEffectObject *lpeobject) registerParameter(&slice_arc); registerParameter(&gen_isometric_frame); registerParameter(&draw_axes); + registerParameter(&gen_perspective_frame); + registerParameter(&draw_perspective_axes); registerParameter(&rot_axes); registerParameter(&draw_ori_path); @@ -68,9 +78,16 @@ LPEPts2Ellipse::LPEPts2Ellipse(LivePathEffectObject *lpeobject) rot_axes.param_set_increments(1, 10); show_orig_path = true; + + gsl_x = gsl_vector_alloc(8); + gsl_p = gsl_permutation_alloc(8); } -LPEPts2Ellipse::~LPEPts2Ellipse() = default; +LPEPts2Ellipse::~LPEPts2Ellipse() +{ + gsl_permutation_free(gsl_p); + gsl_vector_free(gsl_x); +} // helper function, transforms a given value into range [0, 2pi] inline double range2pi(double a) @@ -98,8 +115,7 @@ inline double calc_delta_angle(const double a0, const double a1) return da; } -int unit_arc_path(Geom::Path &path_in, Geom::Affine &affine, double start = 0.0, double end = 2 * M_PI, // angles - bool slice = false) +int LPEPts2Ellipse::unit_arc_path(Geom::Path &path_in, Geom::Affine &affine, double start, double end, bool slice) { double arc_angle = calc_delta_angle(start, end); if (fabs(arc_angle) < 1e-9) { @@ -159,7 +175,7 @@ int unit_arc_path(Geom::Path &path_in, Geom::Affine &affine, double start = 0.0, return 0; } -void gen_iso_frame_paths(Geom::PathVector &path_out, const Geom::Affine &affine) +void LPEPts2Ellipse::gen_iso_frame_paths(Geom::PathVector &path_out, const Geom::Affine &affine) { Geom::Path rect(Geom::Point(-1, -1)); rect.setStitching(true); @@ -171,7 +187,30 @@ void gen_iso_frame_paths(Geom::PathVector &path_out, const Geom::Affine &affine) path_out.push_back(rect); } -void gen_axes_paths(Geom::PathVector &path_out, const Geom::Affine &affine) +void LPEPts2Ellipse::gen_perspective_frame_paths(Geom::PathVector &path_out, const double rot_angle, + double projmatrix[3][3]) +{ + Geom::Point pts0[4] = { { -1.0, -1.0 }, { +1.0, -1.0 }, { +1.0, +1.0 }, { -1.0, +1.0 } }; + // five_pts.resize(4); + int h = 0; + Geom::Affine affine2; + // const double rot_angle = deg2rad(rot_axes); // negative for ccw rotation + affine2 *= Geom::Rotate(-rot_angle); + for (auto &i : pts0) { + Geom::Point point = i; + point *= affine2; + i = projectPoint(point, projmatrix); + } + + Geom::Path rect(pts0[0]); + rect.setStitching(true); + for (int i = 1; i < 4; i++) + rect.appendNew(pts0[i]); + rect.close(true); + path_out.push_back(rect); +} + +void LPEPts2Ellipse::gen_axes_paths(Geom::PathVector &path_out, const Geom::Affine &affine) { Geom::LineSegment clx(Geom::Point(-1, 0), Geom::Point(1, 0)); Geom::LineSegment cly(Geom::Point(0, -1), Geom::Point(0, 1)); @@ -186,7 +225,31 @@ void gen_axes_paths(Geom::PathVector &path_out, const Geom::Affine &affine) path_out.push_back(ply); } -bool is_ccw(const std::vector &pts) +void LPEPts2Ellipse::gen_perspective_axes_paths(Geom::PathVector &path_out, const double rot_angle, + double projmatrix[3][3]) +{ + Geom::Point pts[4]; + int h = 0; + double dA = 2.0 * M_PI / 4.0; // delta Angle + for (auto &i : pts) { + const double angle = rot_angle + dA * h++; + const Geom::Point circle_point(sin(angle), cos(angle)); + i = projectPoint(circle_point, projmatrix); + } + { + Geom::LineSegment clx(pts[0], pts[2]); + Geom::LineSegment cly(pts[1], pts[3]); + + Geom::Path plx, ply; + plx.append(clx); + ply.append(cly); + + path_out.push_back(plx); + path_out.push_back(ply); + } +} + +bool LPEPts2Ellipse::is_ccw(const std::vector &pts) { // method: sum up the angles between edges size_t n = pts.size(); @@ -237,38 +300,33 @@ Geom::PathVector LPEPts2Ellipse::doEffect_path(Geom::PathVector const &path_in) { Geom::PathVector path_out; + // 1) draw original path? if (draw_ori_path.get_value()) { path_out.insert(path_out.end(), path_in.begin(), path_in.end()); } - // from: extension/internal/odf.cpp - // get all points - std::vector pts; + // 2) get all points + // (from: extension/internal/odf.cpp) + points.resize(0); for (const auto &pit : path_in) { // extract first point of this path - pts.push_back(pit.initialPoint()); + points.push_back(pit.initialPoint()); // iterate over all curves for (const auto &cit : pit) { - pts.push_back(cit.finalPoint()); + points.push_back(cit.finalPoint()); } } - // avoid identical start-point and end-point - if (pts.front() == pts.back()) { - pts.pop_back(); + if (points.front() == points.back()) { + points.pop_back(); } - // modify GUI based on selected method + // 3) modify GUI based on selected method + // 3.1) arc options switch (method) { - case EM_ISOMETRIC_CIRCLE: - case EM_STEINER_ELLIPSE: - case EM_STEINER_INELLIPSE: - gen_arc.param_widget_is_enabled(false); - other_arc.param_widget_is_enabled(false); - slice_arc.param_widget_is_enabled(false); - break; - default: + case EM_AUTO: + case EM_CIRCLE: gen_arc.param_widget_is_enabled(true); if (gen_arc.get_value()) { slice_arc.param_widget_is_enabled(true); @@ -277,30 +335,52 @@ Geom::PathVector LPEPts2Ellipse::doEffect_path(Geom::PathVector const &path_in) other_arc.param_widget_is_enabled(false); slice_arc.param_widget_is_enabled(false); } + break; + default: + gen_arc.param_widget_is_enabled(false); + other_arc.param_widget_is_enabled(false); + slice_arc.param_widget_is_enabled(false); + } + // 3.2) perspective options + switch (method) { + case EM_PERSPECTIVE_CIRCLE: + gen_perspective_frame.param_widget_is_enabled(true); + draw_perspective_axes.param_widget_is_enabled(true); + break; + default: + gen_perspective_frame.param_widget_is_enabled(false); + draw_perspective_axes.param_widget_is_enabled(false); } - // call method specific code + // 4) call method specific code switch (method) { case EM_ISOMETRIC_CIRCLE: // special mode: Use first two edges, interpret them as two sides of a parallelogram and // generate an ellipse residing inside the parallelogram. This effect is quite useful when // generating isometric views. Hence, the name. - if (0 != genIsometricEllipse(pts, path_out)) { + if (0 != genIsometricEllipse(points, path_out)) { + return path_in; + } + break; + case EM_PERSPECTIVE_CIRCLE: + // special mode: Use first four points, interpret them as the perspective representation of a square and + // draw the ellipse as it was a circle inside that square. + if (0 != genPerspectiveEllipse(points, path_out)) { return path_in; } break; case EM_STEINER_ELLIPSE: - if (0 != genSteinerEllipse(pts, false, path_out)) { + if (0 != genSteinerEllipse(points, false, path_out)) { return path_in; } break; case EM_STEINER_INELLIPSE: - if (0 != genSteinerEllipse(pts, true, path_out)) { + if (0 != genSteinerEllipse(points, true, path_out)) { return path_in; } break; default: - if (0 != genFitEllipse(pts, path_out)) { + if (0 != genFitEllipse(points, path_out)) { return path_in; } } @@ -338,7 +418,7 @@ int LPEPts2Ellipse::genFitEllipse(std::vector const &pts, Geom::Pat Geom::Path path; unit_arc_path(path, affine); path_out.push_back(path); - } else if (pts.size() >= 5 && EM_AUTO == method) { //! only_circle.get_value()) { + } else if (pts.size() >= 5 && EM_AUTO == method) { // do ellipse try { Geom::Ellipse ellipse; @@ -353,14 +433,9 @@ int LPEPts2Ellipse::genFitEllipse(std::vector const &pts, Geom::Pat const bool ccw_wind = is_ccw(pts); endpoints2angles(ccw_wind, other_arc.get_value(), p0, p1, a0, a1); } - Geom::Path path; unit_arc_path(path, affine, a0, a1, slice_arc.get_value()); path_out.push_back(path); - - if (draw_axes.get_value()) { - gen_axes_paths(path_out, affine); - } } catch (...) { return -1; } @@ -371,7 +446,6 @@ int LPEPts2Ellipse::genFitEllipse(std::vector const &pts, Geom::Pat circle.fit(pts); affine *= Geom::Scale(circle.radius()); affine *= Geom::Translate(circle.center()); - if (gen_arc.get_value()) { Geom::Point p0 = pts.front() - circle.center(); Geom::Point p1 = pts.back() - circle.center(); @@ -508,7 +582,7 @@ int LPEPts2Ellipse::genSteinerEllipse(std::vector const &pts, bool swapped = true; } - // the steiner inellipse is just scaled down by 2 + // the Steiner inellipse is just scaled down by 2 if (gen_inellipse) { l0 /= 2; l1 /= 2; @@ -541,6 +615,129 @@ int LPEPts2Ellipse::genSteinerEllipse(std::vector const &pts, bool return 0; } +// identical to lpe-perspective-envelope.cpp +Geom::Point LPEPts2Ellipse::projectPoint(Geom::Point p, double m[][3]) +{ + Geom::Coord x = p[0]; + Geom::Coord y = p[1]; + return Geom::Point(Geom::Coord((x * m[0][0] + y * m[0][1] + m[0][2]) / (x * m[2][0] + y * m[2][1] + m[2][2])), + Geom::Coord((x * m[1][0] + y * m[1][1] + m[1][2]) / (x * m[2][0] + y * m[2][1] + m[2][2]))); +} + +int LPEPts2Ellipse::genPerspectiveEllipse(std::vector const &pts, Geom::PathVector &path_out) +{ + using Geom::X; + using Geom::Y; + // we need at least four points! + if (pts.size() < 4) + return -1; + + // 1) check if the first three edges are a valid perspective + // calc edge + Geom::Point e[] = { pts[0] - pts[1], pts[1] - pts[2], pts[2] - pts[3], pts[3] - pts[0] }; + // calc directions + Geom::Coord c[] = { cross(e[0], e[1]), cross(e[1], e[2]), cross(e[2], e[3]), cross(e[3], e[0]) }; + // is this quad not convex? + if (!((c[0] > 0 && c[1] > 0 && c[2] > 0 && c[3] > 0) || (c[0] < 0 && c[1] < 0 && c[2] < 0 && c[3] < 0))) + return -1; + + // 2) solve the direct linear transformation (see e.g. lpe-perspective-envelope.cpp or + // https://franklinta.com/2014/09/08/computing-css-matrix3d-transforms/) + + // the square points in the initial configuration (about the unit circle): + Geom::Point pts0[4] = { { -1.0, -1.0 }, { +1.0, -1.0 }, { +1.0, +1.0 }, { -1.0, +1.0 } }; + + // build equation in matrix form + double eqnVec[8] = { 0 }; + double eqnMat[64] = { 0 }; + for (unsigned int i = 0; i < 4; ++i) { + eqnMat[8 * (i + 0) + 0] = pts0[i][X]; + eqnMat[8 * (i + 0) + 1] = pts0[i][Y]; + eqnMat[8 * (i + 0) + 2] = 1; + eqnMat[8 * (i + 0) + 6] = -pts[i][X] * pts0[i][X]; + eqnMat[8 * (i + 0) + 7] = -pts[i][X] * pts0[i][Y]; + eqnMat[8 * (i + 4) + 3] = pts0[i][X]; + eqnMat[8 * (i + 4) + 4] = pts0[i][Y]; + eqnMat[8 * (i + 4) + 5] = 1; + eqnMat[8 * (i + 4) + 6] = -pts[i][Y] * pts0[i][X]; + eqnMat[8 * (i + 4) + 7] = -pts[i][Y] * pts0[i][Y]; + eqnVec[i] = pts[i][X]; + eqnVec[i + 4] = pts[i][Y]; + } + // solve using gsl library + gsl_matrix_view m = gsl_matrix_view_array(eqnMat, 8, 8); + gsl_vector_view b = gsl_vector_view_array(eqnVec, 8); + int s = 0; + gsl_linalg_LU_decomp(&m.matrix, gsl_p, &s); + gsl_linalg_LU_solve(&m.matrix, gsl_p, &b.vector, gsl_x); + // transfer the solution to the projection matrix for further use + size_t h = 0; + double projmatrix[3][3]; + for (auto &matRow : projmatrix) { + for (double &matElement : matRow) { + if (h == 8) { + projmatrix[2][2] = 1.0; + } else { + matElement = gsl_vector_get(gsl_x, h++); + } + } + } + + // 3) generate five points on a unit circle and project them + five_pts.resize(5); // reuse and avoid new/delete + h = 0; + double dA = 2.0 * M_PI / 5.0; // delta Angle + for (auto &i : five_pts) { + const double angle = dA * h++; + const Geom::Point circle_point(sin(angle), cos(angle)); + i = projectPoint(circle_point, projmatrix); + } + + // 4) fit the five points to an ellipse with the already known function inside genFitEllipse() function + // build up the affine transformation + const double rot_angle = -deg2rad(rot_axes); // negative for ccw rotation + Geom::Affine affine; + affine *= Geom::Rotate(rot_angle); + + try { + Geom::Ellipse ellipse; + ellipse.fit(five_pts); + affine *= Geom::Scale(ellipse.ray(Geom::X), ellipse.ray(Geom::Y)); + affine *= Geom::Rotate(ellipse.rotationAngle()); + affine *= Geom::Translate(ellipse.center()); + } catch (...) { + return -1; + } + + Geom::Path path; + unit_arc_path(path, affine); + path_out.push_back(path); + + // 5) frames and axes + + // draw frame? + if (gen_isometric_frame.get_value()) { + gen_iso_frame_paths(path_out, affine); + } + + // draw perspective frame? + if (gen_perspective_frame.get_value()) { + gen_perspective_frame_paths(path_out, rot_angle, projmatrix); + } + + // draw axes? + if (draw_axes.get_value()) { + gen_axes_paths(path_out, affine); + } + + // draw perspective axes? + if (draw_perspective_axes.get_value()) { + gen_perspective_axes_paths(path_out, rot_angle, projmatrix); + } + + return 0; +} + /* ######################## */ diff --git a/src/live_effects/lpe-pts2ellipse.h b/src/live_effects/lpe-pts2ellipse.h index 46722aa867..3ccf0c4bb9 100644 --- a/src/live_effects/lpe-pts2ellipse.h +++ b/src/live_effects/lpe-pts2ellipse.h @@ -18,13 +18,25 @@ #include "live_effects/effect.h" #include "live_effects/parameter/bool.h" #include "live_effects/parameter/enum.h" -// #include "live_effects/parameter/parameter.h" -// #include "live_effects/parameter/point.h" + +#include + + +// struct gsl_vector; +// struct gsl_permutation; namespace Inkscape { namespace LivePathEffect { -enum EllipseMethod { EM_AUTO, EM_CIRCLE, EM_ISOMETRIC_CIRCLE, EM_STEINER_ELLIPSE, EM_STEINER_INELLIPSE, EM_END }; +enum EllipseMethod { + EM_AUTO, + EM_CIRCLE, + EM_ISOMETRIC_CIRCLE, + EM_PERSPECTIVE_CIRCLE, + EM_STEINER_ELLIPSE, + EM_STEINER_INELLIPSE, + EM_END +}; class LPEPts2Ellipse : public Effect { public: @@ -44,16 +56,39 @@ class LPEPts2Ellipse : public Effect { int genSteinerEllipse(std::vector const &points_in, bool gen_inellipse, Geom::PathVector &path_out); + int genPerspectiveEllipse(std::vector const &points_in, Geom::PathVector &path_out); + + // utility functions + static int unit_arc_path(Geom::Path &path_in, Geom::Affine &affine, double start = 0.0, + double end = 2.0 * M_PI, // angles + bool slice = false); + static void gen_iso_frame_paths(Geom::PathVector &path_out, const Geom::Affine &affine); + static void gen_perspective_frame_paths(Geom::PathVector &path_out, const double rot_angle, + double projmatrix[3][3]); + static void gen_axes_paths(Geom::PathVector &path_out, const Geom::Affine &affine); + static void gen_perspective_axes_paths(Geom::PathVector &path_out, const double rot_angle, double projmatrix[3][3]); + static bool is_ccw(const std::vector &pts); + static Geom::Point projectPoint(Geom::Point p, double m[][3]); + + // GUI parameters EnumParam method; BoolParam gen_isometric_frame; + BoolParam gen_perspective_frame; BoolParam gen_arc; BoolParam other_arc; BoolParam slice_arc; BoolParam draw_axes; + BoolParam draw_perspective_axes; ScalarParam rot_axes; BoolParam draw_ori_path; + // collect the points from the input paths std::vector points; + + // used for solving perspective circle + gsl_vector *gsl_x; + gsl_permutation *gsl_p; + std::vector five_pts; }; } // namespace LivePathEffect -- GitLab From 2d2c778340997b1d4370bb583e40a0e8dbebfc57 Mon Sep 17 00:00:00 2001 From: schwieni Date: Sun, 24 Mar 2019 10:14:33 +0100 Subject: [PATCH 6/7] lpe-pts2ellipse: added perspective circle from 4 points improved tool-tips for better usability --- src/live_effects/lpe-pts2ellipse.cpp | 24 +++-- src/live_effects/parameter/parameter.cpp | 122 +++++++++-------------- src/live_effects/parameter/parameter.h | 102 +++++++++---------- 3 files changed, 112 insertions(+), 136 deletions(-) diff --git a/src/live_effects/lpe-pts2ellipse.cpp b/src/live_effects/lpe-pts2ellipse.cpp index e87b731b71..0e972c68ed 100644 --- a/src/live_effects/lpe-pts2ellipse.cpp +++ b/src/live_effects/lpe-pts2ellipse.cpp @@ -49,17 +49,29 @@ static const Util::EnumDataConverter EMConverter(EllipseMethodDat LPEPts2Ellipse::LPEPts2Ellipse(LivePathEffectObject *lpeobject) : Effect(lpeobject) - , method(_("Method:"), _("Methods to generate the ellipse\n- Auto ellipse: fits a circle (2..4 points) or an ellipse (at least 5 points)\n- Force circle: (at least 2 points) always fit to a circle\n- Isometric circle: (3 points) use first two edges\n- Perspective circle: (4 points) circle in a square in perspective view\n- Steiner ellipse: (3 points) ellipse on a triangle\n- Steiner inellipse: (3 points) ellipse inside a triangle"), "method", EMConverter, &wr, this, EM_AUTO) + , method( + _("Method:"), + _("Methods to generate the ellipse\n- Auto ellipse: fits a circle (2..4 points) or an ellipse (at least 5 " + "points)\n- Force circle: (at least 2 points) always fit to a circle\n- Isometric circle: (3 points) use " + "first two edges\n- Perspective circle: (4 points) circle in a square in perspective view\n- Steiner " + "ellipse: (3 points) ellipse on a triangle\n- Steiner inellipse: (3 points) ellipse inside a triangle"), + "method", EMConverter, &wr, this, EM_AUTO) , gen_isometric_frame(_("_Frame (isometric rectangle)"), _("Draw parallelogram around the ellipse"), "gen_isometric_frame", &wr, this, false) - , gen_perspective_frame(_("_Perspective square"), _("Draw square surrounding the circle in perspective view\n(only in method \"Perspective circle\")"), - "gen_perspective_frame", &wr, this, false) - , gen_arc(_("_Arc"), _("Generate open arc (open ellipse) based on first and last point\n(only for methods \"Auto ellipse\" and \"Force circle\")"), "gen_arc", &wr, this, false) + , gen_perspective_frame( + _("_Perspective square"), + _("Draw square surrounding the circle in perspective view\n(only in method \"Perspective circle\")"), + "gen_perspective_frame", &wr, this, false) + , gen_arc(_("_Arc"), + _("Generate open arc (open ellipse) based on first and last point\n(only for methods \"Auto ellipse\" " + "and \"Force circle\")"), + "gen_arc", &wr, this, false) , other_arc(_("_Other arc side"), _("Switch sides of the arc"), "arc_other", &wr, this, false) , slice_arc(_("_Slice arc"), _("Slice the arc"), "slice_arc", &wr, this, false) , draw_axes(_("A_xes"), _("Draw both semi-major and semi-minor axes"), "draw_axes", &wr, this, false) - , draw_perspective_axes(_("Perspective axes"), _("Draw the axes in perspective view\n(only in method \"Perspective circle\")"), "draw_perspective_axes", &wr, - this, false) + , draw_perspective_axes(_("Perspective axes"), + _("Draw the axes in perspective view\n(only in method \"Perspective circle\")"), + "draw_perspective_axes", &wr, this, false) , rot_axes(_("Axes rotation"), _("Axes rotation angle [deg]"), "rot_axes", &wr, this, 0) , draw_ori_path(_("Source _path"), _("Show the original source path"), "draw_ori_path", &wr, this, false) { diff --git a/src/live_effects/parameter/parameter.cpp b/src/live_effects/parameter/parameter.cpp index a235e60765..50137ec36a 100644 --- a/src/live_effects/parameter/parameter.cpp +++ b/src/live_effects/parameter/parameter.cpp @@ -6,8 +6,8 @@ */ -#include "live_effects/effect.h" #include "live_effects/parameter/parameter.h" +#include "live_effects/effect.h" #include "svg/svg.h" #include "xml/repr.h" @@ -26,29 +26,27 @@ namespace Inkscape { namespace LivePathEffect { -Parameter::Parameter( Glib::ustring label, Glib::ustring tip, - Glib::ustring key, Inkscape::UI::Widget::Registry* wr, - Effect* effect ) - : param_key(std::move(key)), - param_wr(wr), - param_label(std::move(label)), - oncanvas_editable(false), - widget_is_visible(true), - widget_is_enabled(true), - param_tooltip(std::move(tip)), - param_effect(effect) +Parameter::Parameter(Glib::ustring label, Glib::ustring tip, Glib::ustring key, Inkscape::UI::Widget::Registry *wr, + Effect *effect) + : param_key(std::move(key)) + , param_wr(wr) + , param_label(std::move(label)) + , oncanvas_editable(false) + , widget_is_visible(true) + , widget_is_enabled(true) + , param_tooltip(std::move(tip)) + , param_effect(effect) { } -void -Parameter::param_write_to_repr(const char * svgd) +void Parameter::param_write_to_repr(const char *svgd) { param_effect->getRepr()->setAttribute(param_key.c_str(), svgd); } void Parameter::write_to_SVG() { - gchar * str = param_getSVGValue(); + gchar *str = param_getSVGValue(); param_write_to_repr(str); g_free(str); } @@ -56,28 +54,25 @@ void Parameter::write_to_SVG() /*########################################### * REAL PARAM */ -ScalarParam::ScalarParam( const Glib::ustring& label, const Glib::ustring& tip, - const Glib::ustring& key, Inkscape::UI::Widget::Registry* wr, - Effect* effect, gdouble default_value) - : Parameter(label, tip, key, wr, effect), - value(default_value), - min(-SCALARPARAM_G_MAXDOUBLE), - max(SCALARPARAM_G_MAXDOUBLE), - integer(false), - defvalue(default_value), - digits(2), - inc_step(0.1), - inc_page(1), - add_slider(false), - _set_undo(true) +ScalarParam::ScalarParam(const Glib::ustring &label, const Glib::ustring &tip, const Glib::ustring &key, + Inkscape::UI::Widget::Registry *wr, Effect *effect, gdouble default_value) + : Parameter(label, tip, key, wr, effect) + , value(default_value) + , min(-SCALARPARAM_G_MAXDOUBLE) + , max(SCALARPARAM_G_MAXDOUBLE) + , integer(false) + , defvalue(default_value) + , digits(2) + , inc_step(0.1) + , inc_page(1) + , add_slider(false) + , _set_undo(true) { } -ScalarParam::~ScalarParam() -= default; +ScalarParam::~ScalarParam() = default; -bool -ScalarParam::param_readSVGValue(const gchar * strvalue) +bool ScalarParam::param_readSVGValue(const gchar *strvalue) { double newval; unsigned int success = sp_svg_number_read_d(strvalue, &newval); @@ -88,36 +83,25 @@ ScalarParam::param_readSVGValue(const gchar * strvalue) return false; } -gchar * -ScalarParam::param_getSVGValue() const +gchar *ScalarParam::param_getSVGValue() const { Inkscape::SVGOStringStream os; os << value; return g_strdup(os.str().c_str()); } -gchar * -ScalarParam::param_getDefaultSVGValue() const +gchar *ScalarParam::param_getDefaultSVGValue() const { Inkscape::SVGOStringStream os; os << defvalue; - return g_strdup(os.str().c_str()); + return g_strdup(os.str().c_str()); } -void -ScalarParam::param_set_default() -{ - param_set_value(defvalue); -} +void ScalarParam::param_set_default() { param_set_value(defvalue); } -void -ScalarParam::param_update_default(gdouble default_value) -{ - defvalue = default_value; -} +void ScalarParam::param_update_default(gdouble default_value) { defvalue = default_value; } -void -ScalarParam::param_update_default(const gchar * default_value) +void ScalarParam::param_update_default(const gchar *default_value) { double newval; unsigned int success = sp_svg_number_read_d(default_value, &newval); @@ -126,8 +110,7 @@ ScalarParam::param_update_default(const gchar * default_value) } } -void -ScalarParam::param_set_value(gdouble val) +void ScalarParam::param_set_value(gdouble val) { value = val; if (integer) @@ -138,8 +121,7 @@ ScalarParam::param_set_value(gdouble val) value = min; } -void -ScalarParam::param_set_range(gdouble min, gdouble max) +void ScalarParam::param_set_range(gdouble min, gdouble max) { // if you look at client code, you'll see that many effects // has a tendency to set an upper range of Geom::infinity(). @@ -159,8 +141,7 @@ ScalarParam::param_set_range(gdouble min, gdouble max) param_set_value(value); // reset value to see whether it is in ranges } -void -ScalarParam::param_make_integer(bool yes) +void ScalarParam::param_make_integer(bool yes) { integer = yes; digits = 0; @@ -168,19 +149,14 @@ ScalarParam::param_make_integer(bool yes) inc_page = 10; } -void -ScalarParam::param_set_undo(bool set_undo) -{ - _set_undo = set_undo; -} +void ScalarParam::param_set_undo(bool set_undo) { _set_undo = set_undo; } -Gtk::Widget * -ScalarParam::param_newWidget() +Gtk::Widget *ScalarParam::param_newWidget() { if (widget_is_visible) { - Inkscape::UI::Widget::RegisteredScalar *rsu = Gtk::manage( new Inkscape::UI::Widget::RegisteredScalar( - param_label, param_tooltip, param_key, *param_wr, param_effect->getRepr(), param_effect->getSPDoc() ) ); - + Inkscape::UI::Widget::RegisteredScalar *rsu = Gtk::manage(new Inkscape::UI::Widget::RegisteredScalar( + param_label, param_tooltip, param_key, *param_wr, param_effect->getRepr(), param_effect->getSPDoc())); + rsu->setValue(value); rsu->setDigits(digits); rsu->setIncrements(inc_step, inc_page); @@ -189,23 +165,18 @@ ScalarParam::param_newWidget() if (add_slider) { rsu->addSlider(); } - if(_set_undo){ + if (_set_undo) { rsu->set_undo_parameters(SP_VERB_DIALOG_LIVE_PATH_EFFECT, _("Change scalar parameter")); } - return dynamic_cast (rsu); + return dynamic_cast(rsu); } else { return nullptr; } } -void -ScalarParam::param_set_digits(unsigned digits) -{ - this->digits = digits; -} +void ScalarParam::param_set_digits(unsigned digits) { this->digits = digits; } -void -ScalarParam::param_set_increments(double step, double page) +void ScalarParam::param_set_increments(double step, double page) { inc_step = step; inc_page = page; @@ -213,7 +184,6 @@ ScalarParam::param_set_increments(double step, double page) - } /* namespace LivePathEffect */ } /* namespace Inkscape */ diff --git a/src/live_effects/parameter/parameter.h b/src/live_effects/parameter/parameter.h index 62684b7308..b3cbc039e0 100644 --- a/src/live_effects/parameter/parameter.h +++ b/src/live_effects/parameter/parameter.h @@ -10,17 +10,18 @@ * Released under GNU GPL v2+, read the file 'COPYING' for more information. */ -#include +#include "ui/widget/registered-widget.h" #include <2geom/forward.h> #include <2geom/pathvector.h> -#include "ui/widget/registered-widget.h" +#include // In gtk2, this wasn't an issue; we could toss around // G_MAXDOUBLE and not worry about size allocations. But // in gtk3, it is an issue: it allocates widget size for the maxmium // value you pass to it, leading to some insane lengths. // If you need this to be more, please be conservative about it. -const double SCALARPARAM_G_MAXDOUBLE = 10000000000.0; // TODO fixme: using an arbitrary large number as a magic value seems fragile. +const double SCALARPARAM_G_MAXDOUBLE = + 10000000000.0; // TODO fixme: using an arbitrary large number as a magic value seems fragile. class KnotHolder; class SPLPEItem; @@ -28,97 +29,90 @@ class SPDesktop; class SPItem; namespace Gtk { - class Widget; +class Widget; } namespace Inkscape { namespace NodePath { - class Path ; +class Path; } namespace UI { namespace Widget { - class Registry; -} +class Registry; } +} // namespace UI namespace LivePathEffect { class Effect; class Parameter { -public: - Parameter( Glib::ustring label, - Glib::ustring tip, - Glib::ustring key, - Inkscape::UI::Widget::Registry* wr, - Effect* effect); - virtual ~Parameter() = default;; - - Parameter(const Parameter&) = delete; - Parameter& operator=(const Parameter&) = delete; - - virtual bool param_readSVGValue(const gchar * strvalue) = 0; // returns true if new value is valid / accepted. - virtual gchar * param_getSVGValue() const = 0; - virtual gchar * param_getDefaultSVGValue() const = 0; - virtual void param_widget_is_visible(bool is_visible) {widget_is_visible = is_visible;} - virtual void param_widget_is_enabled(bool is_enabled) {widget_is_enabled = is_enabled;} + public: + Parameter(Glib::ustring label, Glib::ustring tip, Glib::ustring key, Inkscape::UI::Widget::Registry *wr, + Effect *effect); + virtual ~Parameter() = default; + ; + + Parameter(const Parameter &) = delete; + Parameter &operator=(const Parameter &) = delete; + + virtual bool param_readSVGValue(const gchar *strvalue) = 0; // returns true if new value is valid / accepted. + virtual gchar *param_getSVGValue() const = 0; + virtual gchar *param_getDefaultSVGValue() const = 0; + virtual void param_widget_is_visible(bool is_visible) { widget_is_visible = is_visible; } + virtual void param_widget_is_enabled(bool is_enabled) { widget_is_enabled = is_enabled; } void write_to_SVG(); - + virtual void param_set_default() = 0; - virtual void param_update_default(const gchar * default_value) = 0; + virtual void param_update_default(const gchar *default_value) = 0; // This creates a new widget (newed with Gtk::manage(new ...);) - virtual Gtk::Widget * param_newWidget() = 0; + virtual Gtk::Widget *param_newWidget() = 0; - virtual Glib::ustring * param_getTooltip() { return ¶m_tooltip; }; + virtual Glib::ustring *param_getTooltip() { return ¶m_tooltip; }; // overload these for your particular parameter to make it provide knotholder handles or canvas helperpaths virtual bool providesKnotHolderEntities() const { return false; } - virtual void addKnotHolderEntities(KnotHolder */*knotholder*/, SPItem */*item*/) {}; - virtual void addCanvasIndicators(SPLPEItem const*/*lpeitem*/, std::vector &/*hp_vec*/) {}; + virtual void addKnotHolderEntities(KnotHolder * /*knotholder*/, SPItem * /*item*/){}; + virtual void addCanvasIndicators(SPLPEItem const * /*lpeitem*/, std::vector & /*hp_vec*/){}; - virtual void param_editOncanvas(SPItem * /*item*/, SPDesktop * /*dt*/) {}; - virtual void param_setup_nodepath(Inkscape::NodePath::Path */*np*/) {}; + virtual void param_editOncanvas(SPItem * /*item*/, SPDesktop * /*dt*/){}; + virtual void param_setup_nodepath(Inkscape::NodePath::Path * /*np*/){}; - virtual void param_transform_multiply(Geom::Affine const& /*postmul*/, bool /*set*/) {}; + virtual void param_transform_multiply(Geom::Affine const & /*postmul*/, bool /*set*/){}; Glib::ustring param_key; Glib::ustring param_tooltip; - Inkscape::UI::Widget::Registry * param_wr; + Inkscape::UI::Widget::Registry *param_wr; Glib::ustring param_label; bool oncanvas_editable; bool widget_is_visible; bool widget_is_enabled; -protected: - - Effect* param_effect; + protected: + Effect *param_effect; - void param_write_to_repr(const char * svgd); + void param_write_to_repr(const char *svgd); }; class ScalarParam : public Parameter { -public: - ScalarParam( const Glib::ustring& label, - const Glib::ustring& tip, - const Glib::ustring& key, - Inkscape::UI::Widget::Registry* wr, - Effect* effect, - gdouble default_value = 1.0); + public: + ScalarParam(const Glib::ustring &label, const Glib::ustring &tip, const Glib::ustring &key, + Inkscape::UI::Widget::Registry *wr, Effect *effect, gdouble default_value = 1.0); ~ScalarParam() override; - ScalarParam(const ScalarParam&) = delete; - ScalarParam& operator=(const ScalarParam&) = delete; + ScalarParam(const ScalarParam &) = delete; + ScalarParam &operator=(const ScalarParam &) = delete; - bool param_readSVGValue(const gchar * strvalue) override; - gchar * param_getSVGValue() const override; - gchar * param_getDefaultSVGValue() const override; + bool param_readSVGValue(const gchar *strvalue) override; + gchar *param_getSVGValue() const override; + gchar *param_getDefaultSVGValue() const override; void param_set_default() override; void param_update_default(gdouble default_value); - void param_update_default(const gchar * default_value) override; + void param_update_default(const gchar *default_value) override; void param_set_value(gdouble val); void param_make_integer(bool yes = true); void param_set_range(gdouble min, gdouble max); @@ -128,11 +122,11 @@ public: double param_get_max() { return max; }; double param_get_min() { return min; }; void param_set_undo(bool set_undo); - Gtk::Widget * param_newWidget() override; + Gtk::Widget *param_newWidget() override; inline operator gdouble() const { return value; }; -protected: + protected: gdouble value; gdouble min; gdouble max; @@ -145,9 +139,9 @@ protected: bool _set_undo; }; -} //namespace LivePathEffect +} // namespace LivePathEffect -} //namespace Inkscape +} // namespace Inkscape #endif -- GitLab From 3a1866ab31229a9adb417ff230add5ab6b9b9ddf Mon Sep 17 00:00:00 2001 From: schwieni Date: Sun, 24 Mar 2019 10:14:33 +0100 Subject: [PATCH 7/7] lpe-pts2ellipse: added perspective circle from 4 points improved tool-tips for better usability --- src/live_effects/lpe-pts2ellipse.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/live_effects/lpe-pts2ellipse.cpp b/src/live_effects/lpe-pts2ellipse.cpp index 0e972c68ed..c657c18aa3 100644 --- a/src/live_effects/lpe-pts2ellipse.cpp +++ b/src/live_effects/lpe-pts2ellipse.cpp @@ -726,6 +726,10 @@ int LPEPts2Ellipse::genPerspectiveEllipse(std::vector const &pts, G path_out.push_back(path); // 5) frames and axes + bool ccw_wind = false; + if (gen_perspective_frame.get_value() || draw_perspective_axes.get_value()) + ccw_wind = is_ccw(pts); + const double ra = ccw_wind ? rot_angle : -rot_angle; // draw frame? if (gen_isometric_frame.get_value()) { @@ -734,7 +738,7 @@ int LPEPts2Ellipse::genPerspectiveEllipse(std::vector const &pts, G // draw perspective frame? if (gen_perspective_frame.get_value()) { - gen_perspective_frame_paths(path_out, rot_angle, projmatrix); + gen_perspective_frame_paths(path_out, ra, projmatrix); } // draw axes? @@ -744,7 +748,7 @@ int LPEPts2Ellipse::genPerspectiveEllipse(std::vector const &pts, G // draw perspective axes? if (draw_perspective_axes.get_value()) { - gen_perspective_axes_paths(path_out, rot_angle, projmatrix); + gen_perspective_axes_paths(path_out, ra, projmatrix); } return 0; -- GitLab