diff --git a/po/POTFILES.in b/po/POTFILES.in index ce52299384eb39d52027ec83f00d49510de1287d..638a8e7c002457a6a8dbbbd341eddf73423a60c8 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -144,6 +144,7 @@ src/live_effects/lpe-perspective-envelope.cpp src/live_effects/lpe-powerclip.cpp src/live_effects/lpe-powermask.cpp src/live_effects/lpe-powerstroke.cpp +src/live_effects/lpe-pts2ellipse.cpp src/live_effects/lpe-rough-hatches.cpp src/live_effects/lpe-roughen.cpp src/live_effects/lpe-ruler.cpp diff --git a/src/live_effects/CMakeLists.txt b/src/live_effects/CMakeLists.txt index 9d18626bc162610f0dcfb1ea44f06607f985e477..aa482a6cf50f21ec5005c00b2de54eae7fdee6b1 100644 --- a/src/live_effects/CMakeLists.txt +++ b/src/live_effects/CMakeLists.txt @@ -61,6 +61,7 @@ set(live_effects_SRC lpeobject.cpp spiro-converters.cpp spiro.cpp + lpe-pts2ellipse.cpp parameter/array.cpp parameter/bool.cpp @@ -154,6 +155,7 @@ set(live_effects_SRC lpeobject.h spiro-converters.h spiro.h + lpe-pts2ellipse.h parameter/array.h parameter/bool.h diff --git a/src/live_effects/effect-enum.h b/src/live_effects/effect-enum.h index 20d941e693fd802fcebc950edd44f94468a053f7..dc55b52809c087f9dfad57bb23bbf74733793896 100644 --- a/src/live_effects/effect-enum.h +++ b/src/live_effects/effect-enum.h @@ -63,6 +63,7 @@ enum EffectType { LINE_SEGMENT, OFFSET, PARALLEL, + PTS2ELLIPSE, PATH_LENGTH, PERP_BISECTOR, PERSPECTIVE_PATH, diff --git a/src/live_effects/effect.cpp b/src/live_effects/effect.cpp index b827cff4bc923e175cb2ca851ad2eef098007247..1255d74218f20ad60308ccde3cd4a7a21f94a450 100644 --- a/src/live_effects/effect.cpp +++ b/src/live_effects/effect.cpp @@ -66,6 +66,7 @@ #include "live_effects/lpe-vonkoch.h" #include "live_effects/lpe-embrodery-stitch.h" #include "live_effects/lpe-bool.h" +#include "live_effects/lpe-pts2ellipse.h" #include "live_effects/lpeobject.h" @@ -136,9 +137,10 @@ const Util::EnumData LPETypeData[] = { {MEASURE_SEGMENTS, N_("Measure Segments"), "measure_segments"}, {FILLET_CHAMFER, N_("Fillet/Chamfer"), "fillet_chamfer"}, {BOOL_OP, N_("Boolean operation"), "bool_op"}, - {EMBRODERY_STITCH, N_("Embroidery stitch"), "embrodery_stitch"}, + {EMBRODERY_STITCH, N_("Embroidery stitch"), "embrodery_stitch"}, {POWERCLIP, N_("Power clip"), "powerclip"}, {POWERMASK, N_("Power mask"), "powermask"}, + {PTS2ELLIPSE, N_("Ellipse from points"), "pts2ellipse"}, #ifdef LPE_ENABLE_TEST_EFFECTS {DOEFFECTSTACK_TEST, N_("doEffect stack test"), "doeffectstacktest"}, {ANGLE_BISECTOR, N_("Angle bisector"), "angle_bisector"}, @@ -341,6 +343,9 @@ Effect::New(EffectType lpenr, LivePathEffectObject *lpeobj) case MEASURE_SEGMENTS: neweffect = static_cast ( new LPEMeasureSegments(lpeobj) ); break; + case PTS2ELLIPSE: + neweffect = static_cast ( new LPEPts2Ellipse(lpeobj) ); + break; default: g_warning("LivePathEffect::Effect::New called with invalid patheffect type (%d)", lpenr); neweffect = NULL; diff --git a/src/live_effects/lpe-pts2ellipse.cpp b/src/live_effects/lpe-pts2ellipse.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4fd1456469c22962b5ac6d64b12a8e55d0d71937 --- /dev/null +++ b/src/live_effects/lpe-pts2ellipse.cpp @@ -0,0 +1,459 @@ +/** \file + * LPE "Points to Ellipse" implementation + */ + +/* + * Authors: + * Markus Schwienbacher + * + * Copyright (C) Markus Schwienbacher 2013 + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "live_effects/lpe-pts2ellipse.h" + +#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 + +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" } +}; +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) +{ + registerParameter(&method); + registerParameter(&gen_arc); + registerParameter(&other_arc); + registerParameter(&slice_arc); + registerParameter(&gen_isometric_frame); + registerParameter(&draw_axes); + registerParameter(&rot_axes); + registerParameter(&draw_ori_path); + + rot_axes.param_set_range(-360,360); + rot_axes.param_set_increments(1,10); + + show_orig_path=true; +} + +LPEPts2Ellipse::~LPEPts2Ellipse() +{ +} + +// 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; + return a; +} + +inline double +deg2rad(double a) +{ + return a*M_PI/180.0; +} + +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) +{ + 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 e = s + da; + 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 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); + } + + if (slice && !closed) { + curve->lineto(0., 0.); + } + curve->transform(affine); + + path.append(*curve->first_path()); + if ((slice && !closed) || closed) { + path.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()); + rect.close(true); + path_out.push_back(rect); +} + +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::Path plx, ply; + plx.append(clx); + ply.append(cly); + plx*=affine; + ply*=affine; + + path_out.push_back(plx); + path_out.push_back(ply); +} + +bool +is_ccw(const std::vector & pts) +{ + // method: sum up the angles between edges + 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); + // the rest + for(size_t i=1;i pts; + for(PathVector::const_iterator pit = path_in.begin(); pit!= path_in.end(); ++pit) { + // extract first point of this path + 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()); + } + } + + // avoid identical start-point and end-point + 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; + } + return path_out; +} + +/** + * Generates an ellipse (or circle) from the vertices of a given path. Thereby, using fitting + * algorithms from 2geom. Depending on the settings made by the user regarding things like arc, + * 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) +{ + // 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) { + return -1; + } 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) + 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::Path path; + unit_arc_path(path,affine); + path_out.push_back(path); + } else if(pts.size()>=5 && EM_AUTO == method) { //!only_circle.get_value()) { + // do ellipse + try { + 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); + } + + 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; + } + } else { + // do a circle (3,4 points, or only_circle set) + try { + 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); + } + Geom::Path path; + unit_arc_path(path,affine,a0,a1,slice_arc.get_value()); + path_out.push_back(path); + } catch(...) { + return -1; + } + } + + // 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; +} + +int +LPEPts2Ellipse::genIsometricEllipse (std::vector const & pts, + Geom::PathVector & path_out) + +{ + // take the first 3 vertices for the edges + if(pts.size() < 3) return -1; + // calc edges + Point e0=pts[0]-pts[1]; + Point e1=pts[2]-pts[1]; + + Coord ce=cross(e0,e1); + // parallel or one is zero? + if(fabs(ce)<1e-9) return -1; + // unit vectors along edges + Point u0=unit_vector(e0); + Point u1=unit_vector(e1); + // calc angles + Coord a0=atan2(e0); + // Coord a1=M_PI_2-atan2(e1)-a0; + Coord a1=acos(dot(u0,u1))-M_PI_2; + // if(fabs(a1)<1e-9) return -1; + 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; + + // center of the ellipse + 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 + Affine affine; + affine*=Rotate(rot_angle); + affine*=Scale(l0,l1); + affine*=HShear(-tan(a1)); + affine*=Rotate(a0); + affine*=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; +} + +/* ######################## */ + +} //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 : diff --git a/src/live_effects/lpe-pts2ellipse.h b/src/live_effects/lpe-pts2ellipse.h new file mode 100644 index 0000000000000000000000000000000000000000..1e04159d62b73662d6054958f5cb82f9fc3f814e --- /dev/null +++ b/src/live_effects/lpe-pts2ellipse.h @@ -0,0 +1,77 @@ +#ifndef INKSCAPE_LPE_PTS_TO_ELLIPSE_H +#define INKSCAPE_LPE_PTS_TO_ELLIPSE_H + +/** \file + * LPE "Points to Ellipse" implementation + */ + +/* + * Authors: + * Markus Schwienbacher + * + * Copyright (C) Markus Schwienbacher 2013 + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#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" + +namespace Inkscape { +namespace LivePathEffect { + +enum EllipseMethod { + EM_AUTO, + EM_CIRCLE, + EM_ISONOMETRIC_CIRCLE, + EM_END +}; + +class LPEPts2Ellipse : public Effect { +public: + LPEPts2Ellipse(LivePathEffectObject *lpeobject); + virtual ~LPEPts2Ellipse(); + + virtual Geom::PathVector doEffect_path (Geom::PathVector const & path_in); + +private: + LPEPts2Ellipse(const LPEPts2Ellipse&); + LPEPts2Ellipse& operator=(const LPEPts2Ellipse&); + + + int genIsometricEllipse (std::vector const & points_in, + Geom::PathVector & path_out); + + int genFitEllipse (std::vector const & points_in, + Geom::PathVector & path_out); + + EnumParam method; + BoolParam gen_isometric_frame; + BoolParam gen_arc; + BoolParam other_arc; + BoolParam slice_arc; + BoolParam draw_axes; + ScalarParam rot_axes; + BoolParam draw_ori_path; + + std::vector points; +}; + +} //namespace LivePathEffect +} //namespace Inkscape + +#endif + +/* + 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 :