Merge lp:~inkscape.dev/inkscape/copy-rotate-lpe-improvements into lp:~inkscape.dev/inkscape/trunk

Proposed by Jabiertxof
Status: Merged
Approved by: Jabiertxof
Approved revision: 13750
Merged at revision: 14720
Proposed branch: lp:~inkscape.dev/inkscape/copy-rotate-lpe-improvements
Merge into: lp:~inkscape.dev/inkscape/trunk
Diff against target: 500 lines (+313/-43)
3 files modified
src/live_effects/effect.cpp (+1/-1)
src/live_effects/lpe-copy_rotate.cpp (+304/-30)
src/live_effects/lpe-copy_rotate.h (+8/-12)
To merge this branch: bzr merge lp:~inkscape.dev/inkscape/copy-rotate-lpe-improvements
Reviewer Review Type Date Requested Status
Krzysztof Kosinski Approve
Martin Owens Approve
Jabiertxof Pending
Review via email: mp+247530@code.launchpad.net

Description of the change

Add Kaleidoscope Feature to Copy-Rotate LPE.
Here are videos working:
https://www.youtube.com/watch?v=LfMixSKy3Eo
https://www.youtube.com/watch?v=fBQpvfgT4mE

To post a comment you must log in.
13726. By Jabiertxof <email address hidden>

update to trunk

13727. By Jabiertxof <email address hidden>

update to trunk

13728. By Jabiertxof <email address hidden>

adding rotation transform

13729. By Jabiertxof <email address hidden>

update to trunk

13730. By Jabiertxof <email address hidden>

Allow rotate copies whithout distorsion in kaleidoscope shapes

13731. By Jabiertxof <email address hidden>

update to trunk

13732. By Jabiertxof <email address hidden>

update to trunk

13733. By Jabiertxof <email address hidden>

astyle copy-rotate LPE

13734. By Jabiertxof <email address hidden>

Rename some variables to fit coding style

13735. By Jabiertxof <email address hidden>

update to trunk

13736. By Jabiertxof <email address hidden>

opening kaleidscope

13737. By Jabiertxof <email address hidden>

update to trunk

13738. By Jabiertxof <email address hidden>

opening kaleidscope

13739. By Jabiertxof <email address hidden>

update to trunk

13740. By Jabiertxof <email address hidden>

opening kaleidscope

13741. By Jabiertxof <email address hidden>

opening kaleidscope

13742. By Jabiertxof <email address hidden>

Change from kaleidoscope to multiangle fusion

13743. By Jabiertxof <email address hidden>

update to trunk

13744. By Jabiertxof <email address hidden>

fix minor bug

13745. By Jabiertxof <email address hidden>

update to trunk

13746. By Jabiertxof <email address hidden>

fixes for update to trunk

13747. By Jabiertxof <email address hidden>

update to trunk

13748. By Jabiertxof <email address hidden>

update to trunk

13749. By Jabiertxof <email address hidden>

Fix order LPE

13750. By Jabiertxof <email address hidden>

Fix compiling bugs

Revision history for this message
Martin Owens (doctormo) wrote :

Everything looks good to me.

review: Approve
Revision history for this message
Krzysztof Kosinski (tweenk) wrote :

Approve after these fixes.

review: Approve
Revision history for this message
Jabiertxof (jabiertxof) wrote :

Thanks very much @Krzysztof for the review. I hope I can merge tonight.

Revision history for this message
Jabiertxof (jabiertxof) wrote :

Thanks Martin!

13751. By Jabiertxof <email address hidden>

Fix Krzysztof comments on merge proposal

13752. By Jabiertxof <email address hidden>

Fix more Krzysztof comments on merge proposal, temporary disable LPE on clip and paths

13753. By Jabiertxof <email address hidden>

update to trunk

13754. By Jabiertxof <email address hidden>

update to trunk

Revision history for this message
Marcel Partap (empee584) wrote :

Very nice stuff ๐Ÿ‘
Now by the way, let's get inspired by https://www.youtube.com/watch?v=rYk9YfJuQUs (Kudos to Greece!) for further workflow improvements ๐Ÿ™ƒ!

Revision history for this message
Jabiertxof (jabiertxof) wrote :

Thanks Marcel!

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/live_effects/effect.cpp'
2--- src/live_effects/effect.cpp 2016-03-19 11:17:59 +0000
3+++ src/live_effects/effect.cpp 2016-03-19 11:19:31 +0000
4@@ -110,7 +110,6 @@
5 {PATH_LENGTH, N_("Path length"), "path_length"},
6 {PERP_BISECTOR, N_("Perpendicular bisector"), "perp_bisector"},
7 {PERSPECTIVE_PATH, N_("Perspective path"), "perspective_path"},
8- {COPY_ROTATE, N_("Rotate copies"), "copy_rotate"},
9 {RECURSIVE_SKELETON, N_("Recursive skeleton"), "recursive_skeleton"},
10 {TANGENT_TO_CURVE, N_("Tangent to curve"), "tangent_to_curve"},
11 {TEXT_LABEL, N_("Text label"), "text_label"},
12@@ -145,6 +144,7 @@
13 {BSPLINE, N_("BSpline"), "bspline"},
14 {JOIN_TYPE, N_("Join type"), "join_type"},
15 {TAPER_STROKE, N_("Taper stroke"), "taper_stroke"},
16+ {COPY_ROTATE, N_("Rotate copies"), "copy_rotate"},
17 /* Ponyscape -> Inkscape 0.92*/
18 {ATTACH_PATH, N_("Attach path"), "attach_path"},
19 {FILL_BETWEEN_STROKES, N_("Fill between strokes"), "fill_between_strokes"},
20
21=== modified file 'src/live_effects/lpe-copy_rotate.cpp'
22--- src/live_effects/lpe-copy_rotate.cpp 2016-03-02 19:34:31 +0000
23+++ src/live_effects/lpe-copy_rotate.cpp 2016-03-19 11:19:31 +0000
24@@ -5,7 +5,7 @@
25 * Authors:
26 * Maximilian Albert <maximilian.albert@gmail.com>
27 * Johan Engelen <j.b.c.engelen@alumnus.utwente.nl>
28- *
29+ * Jabiertxo Arraiza Cenoz <jabier.arraiza@marker.es>
30 * Copyright (C) Authors 2007-2012
31 *
32 * Released under GNU GPL, read the file 'COPYING' for more information
33@@ -13,7 +13,8 @@
34
35 #include <glibmm/i18n.h>
36 #include <gdk/gdk.h>
37-
38+#include <2geom/path-intersection.h>
39+#include <2geom/sbasis-to-bezier.h>
40 #include "live_effects/lpe-copy_rotate.h"
41 #include <2geom/path.h>
42 #include <2geom/transforms.h>
43@@ -41,22 +42,39 @@
44 virtual Geom::Point knot_get() const;
45 };
46
47-class KnotHolderEntityOrigin : public LPEKnotHolderEntity {
48-public:
49- KnotHolderEntityOrigin(LPECopyRotate *effect) : LPEKnotHolderEntity(effect) {};
50- virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
51- virtual Geom::Point knot_get() const;
52-};
53-
54 } // namespace CR
55
56+int
57+pointSideOfLine(Geom::Point const &A, Geom::Point const &B, Geom::Point const &X)
58+{
59+ //http://stackoverflow.com/questions/1560492/how-to-tell-whether-a-point-is-to-the-right-or-left-side-of-a-line
60+ double pos = (B[Geom::X]-A[Geom::X])*(X[Geom::Y]-A[Geom::Y]) - (B[Geom::Y]-A[Geom::Y])*(X[Geom::X]-A[Geom::X]);
61+ return (pos < 0) ? -1 : (pos > 0);
62+}
63+
64+bool
65+pointInTriangle(Geom::Point const &p, Geom::Point const &p1, Geom::Point const &p2, Geom::Point const &p3)
66+{
67+ //http://totologic.blogspot.com.es/2014/01/accurate-point-in-triangle-test.html
68+ using Geom::X;
69+ using Geom::Y;
70+ double denominator = (p1[X]*(p2[Y] - p3[Y]) + p1[Y]*(p3[X] - p2[X]) + p2[X]*p3[Y] - p2[Y]*p3[X]);
71+ double t1 = (p[X]*(p3[Y] - p1[Y]) + p[Y]*(p1[X] - p3[X]) - p1[X]*p3[Y] + p1[Y]*p3[X]) / denominator;
72+ double t2 = (p[X]*(p2[Y] - p1[Y]) + p[Y]*(p1[X] - p2[X]) - p1[X]*p2[Y] + p1[Y]*p2[X]) / -denominator;
73+ double s = t1 + t2;
74+
75+ return 0 <= t1 && t1 <= 1 && 0 <= t2 && t2 <= 1 && s <= 1;
76+}
77+
78+
79 LPECopyRotate::LPECopyRotate(LivePathEffectObject *lpeobject) :
80 Effect(lpeobject),
81 origin(_("Origin"), _("Origin of the rotation"), "origin", &wr, this, "Adjust the origin of the rotation"),
82 starting_angle(_("Starting:"), _("Angle of the first copy"), "starting_angle", &wr, this, 0.0),
83 rotation_angle(_("Rotation angle:"), _("Angle between two successive copies"), "rotation_angle", &wr, this, 30.0),
84 num_copies(_("Number of copies:"), _("Number of copies of the original path"), "num_copies", &wr, this, 5),
85- copiesTo360(_("360ยบ Copies"), _("No rotation angle, fixed to 360ยบ"), "copiesTo360", &wr, this, true),
86+ copies_to_360(_("360ยบ Copies"), _("No rotation angle, fixed to 360ยบ"), "copies_to_360", &wr, this, true),
87+ fuse_paths(_("Fuse paths"), _("Fuse paths by helper line"), "fuse_paths", &wr, this, false),
88 dist_angle_handle(100.0)
89 {
90 show_orig_path = true;
91@@ -64,12 +82,13 @@
92 apply_to_clippath_and_mask = true;
93
94 // register all your parameters here, so Inkscape knows which parameters this effect has:
95- registerParameter(&copiesTo360);
96+ registerParameter(&copies_to_360);
97+ registerParameter(&fuse_paths);
98 registerParameter(&starting_angle);
99 registerParameter(&rotation_angle);
100 registerParameter(&num_copies);
101 registerParameter(&origin);
102-
103+
104 num_copies.param_make_integer(true);
105 num_copies.param_set_range(0, 1000);
106 }
107@@ -93,15 +112,45 @@
108 dir = unit_vector(B - A);
109 }
110
111+void
112+LPECopyRotate::transform_multiply(Geom::Affine const& postmul, bool set)
113+{
114+ if(fuse_paths) {
115+ Geom::Coord angle = Geom::deg_from_rad(atan(-postmul[1]/postmul[0]));
116+ angle += starting_angle;
117+ starting_angle.param_set_value(angle);
118+ }
119+ // cycle through all parameters. Most parameters will not need transformation, but path and point params do.
120+
121+ for (std::vector<Parameter *>::iterator it = param_vector.begin(); it != param_vector.end(); ++it) {
122+ Parameter * param = *it;
123+ param->param_transform_multiply(postmul, set);
124+ }
125+}
126
127 void
128 LPECopyRotate::doBeforeEffect (SPLPEItem const* lpeitem)
129 {
130 using namespace Geom;
131 original_bbox(lpeitem);
132- if(copiesTo360 ){
133+ if (copies_to_360) {
134 rotation_angle.param_set_value(360.0/(double)num_copies);
135- }
136+ }
137+ if (fuse_paths && rotation_angle * num_copies > 360 && rotation_angle > 0) {
138+ num_copies.param_set_value(floor(360/rotation_angle));
139+ }
140+ if (fuse_paths && copies_to_360) {
141+ num_copies.param_set_increments(2,2);
142+ if ((int)num_copies%2 !=0) {
143+ num_copies.param_set_value(num_copies+1);
144+ }
145+ } else {
146+ num_copies.param_set_increments(1,1);
147+ }
148+
149+ if (dist_angle_handle < 1.0) {
150+ dist_angle_handle = 1.0;
151+ }
152 A = Point(boundingbox_X.min(), boundingbox_Y.middle());
153 B = Point(boundingbox_X.middle(), boundingbox_Y.middle());
154 dir = unit_vector(B - A);
155@@ -109,27 +158,251 @@
156 // likely due to SVG's choice of coordinate system orientation (max)
157 start_pos = origin + dir * Rotate(-rad_from_deg(starting_angle)) * dist_angle_handle;
158 rot_pos = origin + dir * Rotate(-rad_from_deg(rotation_angle+starting_angle)) * dist_angle_handle;
159- if(copiesTo360 ){
160+ if ( fuse_paths || copies_to_360 ) {
161 rot_pos = origin;
162 }
163-}
164-
165+ SPLPEItem * item = const_cast<SPLPEItem*>(lpeitem);
166+ item->apply_to_clippath(item);
167+ item->apply_to_mask(item);
168+}
169+
170+void
171+LPECopyRotate::split(Geom::PathVector &path_on, Geom::Path const &divider)
172+{
173+ Geom::PathVector tmp_path;
174+ double time_start = 0.0;
175+ Geom::Path original = path_on[0];
176+ int position = 0;
177+ Geom::Crossings cs = crossings(original,divider);
178+ std::vector<double> crossed;
179+ for(unsigned int i = 0; i < cs.size(); i++) {
180+ crossed.push_back(cs[i].ta);
181+ }
182+ std::sort(crossed.begin(), crossed.end());
183+ for (unsigned int i = 0; i < crossed.size(); i++) {
184+ double time_end = crossed[i];
185+ Geom::Path portion_original = original.portion(time_start,time_end);
186+ if (!portion_original.empty()) {
187+ Geom::Point side_checker = portion_original.pointAt(0.001);
188+ position = pointSideOfLine(divider[0].finalPoint(), divider[1].finalPoint(), side_checker);
189+ if (rotation_angle != 180) {
190+ position = pointInTriangle(side_checker, divider.initialPoint(), divider[0].finalPoint(), divider[1].finalPoint());
191+ }
192+ if (position == 1) {
193+ tmp_path.push_back(portion_original);
194+ }
195+ portion_original.clear();
196+ time_start = time_end;
197+ }
198+ }
199+ position = pointSideOfLine(divider[0].finalPoint(), divider[1].finalPoint(), original.finalPoint());
200+ if (rotation_angle != 180) {
201+ position = pointInTriangle(original.finalPoint(), divider.initialPoint(), divider[0].finalPoint(), divider[1].finalPoint());
202+ }
203+ if (cs.size() > 0 && position == 1) {
204+ Geom::Path portion_original = original.portion(time_start, original.size());
205+ if(!portion_original.empty()){
206+ if (!original.closed()) {
207+ tmp_path.push_back(portion_original);
208+ } else {
209+ if (tmp_path.size() > 0 && tmp_path[0].size() > 0 ) {
210+ portion_original.setFinal(tmp_path[0].initialPoint());
211+ portion_original.append(tmp_path[0]);
212+ tmp_path[0] = portion_original;
213+ } else {
214+ tmp_path.push_back(portion_original);
215+ }
216+ }
217+ portion_original.clear();
218+ }
219+ }
220+ if (cs.size()==0 && position == 1) {
221+ tmp_path.push_back(original);
222+ }
223+ path_on = tmp_path;
224+}
225+
226+void
227+LPECopyRotate::setFusion(Geom::PathVector &path_on, Geom::Path divider, double size_divider)
228+{
229+ split(path_on,divider);
230+ Geom::PathVector tmp_path;
231+ Geom::Affine pre = Geom::Translate(-origin);
232+ for (Geom::PathVector::const_iterator path_it = path_on.begin(); path_it != path_on.end(); ++path_it) {
233+ Geom::Path original = *path_it;
234+ if (path_it->empty()) {
235+ continue;
236+ }
237+ Geom::PathVector tmp_path_helper;
238+ Geom::Path append_path = original;
239+
240+ for (int i = 0; i < num_copies; ++i) {
241+ Geom::Rotate rot(-Geom::rad_from_deg(rotation_angle * (i)));
242+ Geom::Affine m = pre * rot * Geom::Translate(origin);
243+ if (i%2 != 0) {
244+ Geom::Point A = (Geom::Point)origin;
245+ Geom::Point B = origin + dir * Geom::Rotate(-Geom::rad_from_deg((rotation_angle*i)+starting_angle)) * size_divider;
246+ Geom::Affine m1(1.0, 0.0, 0.0, 1.0, A[0], A[1]);
247+ double hyp = Geom::distance(A, B);
248+ double c = (B[0] - A[0]) / hyp; // cos(alpha)
249+ double s = (B[1] - A[1]) / hyp; // sin(alpha)
250+
251+ Geom::Affine m2(c, -s, s, c, 0.0, 0.0);
252+ Geom::Affine sca(1.0, 0.0, 0.0, -1.0, 0.0, 0.0);
253+
254+ Geom::Affine tmp_m = m1.inverse() * m2;
255+ m = tmp_m;
256+ m = m * sca;
257+ m = m * m2.inverse();
258+ m = m * m1;
259+ } else {
260+ append_path = original;
261+ }
262+ append_path *= m;
263+ if (tmp_path_helper.size() > 0) {
264+ if (Geom::are_near(tmp_path_helper[tmp_path_helper.size()-1].finalPoint(), append_path.finalPoint())) {
265+ Geom::Path tmp_append = append_path.reversed();
266+ tmp_append.setInitial(tmp_path_helper[tmp_path_helper.size()-1].finalPoint());
267+ tmp_path_helper[tmp_path_helper.size()-1].append(tmp_append);
268+ } else if (Geom::are_near(tmp_path_helper[tmp_path_helper.size()-1].initialPoint(), append_path.initialPoint())) {
269+ Geom::Path tmp_append = append_path;
270+ tmp_path_helper[tmp_path_helper.size()-1] = tmp_path_helper[tmp_path_helper.size()-1].reversed();
271+ tmp_append.setInitial(tmp_path_helper[tmp_path_helper.size()-1].finalPoint());
272+ tmp_path_helper[tmp_path_helper.size()-1].append(tmp_append);
273+ tmp_path_helper[tmp_path_helper.size()-1] = tmp_path_helper[tmp_path_helper.size()-1].reversed();
274+ } else if (Geom::are_near(tmp_path_helper[tmp_path_helper.size()-1].finalPoint(), append_path.initialPoint())) {
275+ Geom::Path tmp_append = append_path;
276+ tmp_append.setInitial(tmp_path_helper[tmp_path_helper.size()-1].finalPoint());
277+ tmp_path_helper[tmp_path_helper.size()-1].append(tmp_append);
278+ } else if (Geom::are_near(tmp_path_helper[tmp_path_helper.size()-1].initialPoint(), append_path.finalPoint())) {
279+ Geom::Path tmp_append = append_path.reversed();
280+ tmp_path_helper[tmp_path_helper.size()-1] = tmp_path_helper[tmp_path_helper.size()-1].reversed();
281+ tmp_append.setInitial(tmp_path_helper[tmp_path_helper.size()-1].finalPoint());
282+ tmp_path_helper[tmp_path_helper.size()-1].append(tmp_append);
283+ tmp_path_helper[tmp_path_helper.size()-1] = tmp_path_helper[tmp_path_helper.size()-1].reversed();
284+ } else if (Geom::are_near(tmp_path_helper[0].finalPoint(), append_path.finalPoint())) {
285+ Geom::Path tmp_append = append_path.reversed();
286+ tmp_append.setInitial(tmp_path_helper[0].finalPoint());
287+ tmp_path_helper[0].append(tmp_append);
288+ } else if (Geom::are_near(tmp_path_helper[0].initialPoint(), append_path.initialPoint())) {
289+ Geom::Path tmp_append = append_path;
290+ tmp_path_helper[0] = tmp_path_helper[0].reversed();
291+ tmp_append.setInitial(tmp_path_helper[0].finalPoint());
292+ tmp_path_helper[0].append(tmp_append);
293+ tmp_path_helper[0] = tmp_path_helper[0].reversed();
294+ } else {
295+ tmp_path_helper.push_back(append_path);
296+ }
297+ if ( Geom::are_near(tmp_path_helper[tmp_path_helper.size()-1].finalPoint(),tmp_path_helper[tmp_path_helper.size()-1].initialPoint())) {
298+ tmp_path_helper[tmp_path_helper.size()-1].close();
299+ }
300+ } else {
301+ tmp_path_helper.push_back(append_path);
302+ }
303+ }
304+ if (tmp_path_helper.size() > 0) {
305+ tmp_path_helper[tmp_path_helper.size()-1] = tmp_path_helper[tmp_path_helper.size()-1];
306+ tmp_path_helper[0] = tmp_path_helper[0];
307+ if (rotation_angle * num_copies != 360) {
308+ Geom::Ray base_a(divider.pointAt(1),divider.pointAt(0));
309+ double diagonal = Geom::distance(Geom::Point(boundingbox_X.min(),boundingbox_Y.min()),Geom::Point(boundingbox_X.max(),boundingbox_Y.max()));
310+ Geom::Rect bbox(Geom::Point(boundingbox_X.min(),boundingbox_Y.min()),Geom::Point(boundingbox_X.max(),boundingbox_Y.max()));
311+ double size_divider = Geom::distance(origin,bbox) + (diagonal * 2);
312+ Geom::Point base_point = origin + dir * Geom::Rotate(-Geom::rad_from_deg((rotation_angle * num_copies) + starting_angle)) * size_divider;
313+ Geom::Ray base_b(divider.pointAt(1), base_point);
314+ if (Geom::are_near(tmp_path_helper[0].initialPoint(),base_a) &&
315+ Geom::are_near(tmp_path_helper[0].finalPoint(),base_a))
316+ {
317+ tmp_path_helper[0].close();
318+ if (tmp_path_helper.size() > 1) {
319+ tmp_path_helper[tmp_path_helper.size()-1].close();
320+ }
321+ } else if (Geom::are_near(tmp_path_helper[tmp_path_helper.size()-1].initialPoint(),base_b) &&
322+ Geom::are_near(tmp_path_helper[tmp_path_helper.size()-1].finalPoint(),base_b))
323+ {
324+ tmp_path_helper[0].close();
325+ if (tmp_path_helper.size() > 1) {
326+ tmp_path_helper[tmp_path_helper.size()-1].close();
327+ }
328+ } else if ((Geom::are_near(tmp_path_helper[0].initialPoint(),base_a) &&
329+ Geom::are_near(tmp_path_helper[tmp_path_helper.size()-1].finalPoint(),base_b)) ||
330+ (Geom::are_near(tmp_path_helper[0].initialPoint(),base_b) &&
331+ Geom::are_near(tmp_path_helper[tmp_path_helper.size()-1].finalPoint(),base_a)))
332+ {
333+ Geom::Path close_path = Geom::Path(tmp_path_helper[tmp_path_helper.size()-1].finalPoint());
334+ close_path.appendNew<Geom::LineSegment>((Geom::Point)origin);
335+ close_path.appendNew<Geom::LineSegment>(tmp_path_helper[0].initialPoint());
336+ tmp_path_helper[0].append(close_path);
337+ }
338+ }
339+
340+ if (Geom::are_near(tmp_path_helper[0].finalPoint(),tmp_path_helper[0].initialPoint())) {
341+ tmp_path_helper[0].close();
342+ }
343+ }
344+ tmp_path.insert(tmp_path.end(), tmp_path_helper.begin(), tmp_path_helper.end());
345+ tmp_path_helper.clear();
346+ }
347+ path_on = tmp_path;
348+ tmp_path.clear();
349+}
350
351 Geom::Piecewise<Geom::D2<Geom::SBasis> >
352 LPECopyRotate::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in)
353 {
354 using namespace Geom;
355
356- if(num_copies == 1){
357+ if (num_copies == 1 && !fuse_paths) {
358 return pwd2_in;
359 }
360
361+ double diagonal = Geom::distance(Geom::Point(boundingbox_X.min(),boundingbox_Y.min()),Geom::Point(boundingbox_X.max(),boundingbox_Y.max()));
362+ Geom::Rect bbox(Geom::Point(boundingbox_X.min(),boundingbox_Y.min()),Geom::Point(boundingbox_X.max(),boundingbox_Y.max()));
363+ double size_divider = Geom::distance(origin,bbox) + (diagonal * 2);
364+ Geom::Point line_start = origin + dir * Rotate(-rad_from_deg(starting_angle)) * size_divider;
365+ Geom::Point line_end = origin + dir * Rotate(-rad_from_deg(rotation_angle + starting_angle)) * size_divider;
366+ //Note:: beter way to do this
367+ //Whith AppendNew have problems whith the crossing order
368+ Geom::Path divider = Geom::Path(line_start);
369+ divider.appendNew<Geom::LineSegment>((Geom::Point)origin);
370+ divider.appendNew<Geom::LineSegment>(line_end);
371 Piecewise<D2<SBasis> > output;
372 Affine pre = Translate(-origin) * Rotate(-rad_from_deg(starting_angle));
373- for (int i = 0; i < num_copies; ++i) {
374- Rotate rot(-rad_from_deg(rotation_angle * i));
375- Affine t = pre * rot * Translate(origin);
376- output.concat(pwd2_in * t);
377+ if (fuse_paths) {
378+ Geom::PathVector path_out;
379+ Geom::PathVector tmp_path;
380+ PathVector const original_pathv = path_from_piecewise(remove_short_cuts(pwd2_in, 0.1), 0.001);
381+ for (Geom::PathVector::const_iterator path_it = original_pathv.begin(); path_it != original_pathv.end(); ++path_it) {
382+ if (path_it->empty()) {
383+ continue;
384+ }
385+ bool end_open = false;
386+ if (path_it->closed()) {
387+ const Geom::Curve &closingline = path_it->back_closed();
388+ if (!are_near(closingline.initialPoint(), closingline.finalPoint())) {
389+ end_open = true;
390+ }
391+ }
392+ Geom::Path original = (Geom::Path)(*path_it);
393+ if (end_open && path_it->closed()) {
394+ original.close(false);
395+ original.appendNew<Geom::LineSegment>( original.initialPoint() );
396+ original.close(true);
397+ }
398+ tmp_path.push_back(original);
399+ setFusion(tmp_path, divider, size_divider);
400+ path_out.insert(path_out.end(), tmp_path.begin(), tmp_path.end());
401+ tmp_path.clear();
402+ }
403+ if (path_out.size()>0) {
404+ output = paths_to_pw(path_out);
405+ }
406+ } else {
407+ for (int i = 0; i < num_copies; ++i) {
408+ Rotate rot(-rad_from_deg(rotation_angle * i));
409+ Affine t = pre * rot * Translate(origin);
410+ output.concat(pwd2_in * t);
411+ }
412 }
413 return output;
414 }
415@@ -148,8 +421,16 @@
416 hp_vec.push_back(pathv);
417 }
418
419+void
420+LPECopyRotate::resetDefaults(SPItem const* item)
421+{
422+ Effect::resetDefaults(item);
423+ original_bbox(SP_LPE_ITEM(item));
424+}
425
426-void LPECopyRotate::addKnotHolderEntities(KnotHolder *knotholder, SPDesktop *desktop, SPItem *item) {
427+void
428+LPECopyRotate::addKnotHolderEntities(KnotHolder *knotholder, SPDesktop *desktop, SPItem *item)
429+{
430 {
431 KnotHolderEntity *e = new CR::KnotHolderEntityStartingAngle(this);
432 e->create( desktop, item, knotholder, Inkscape::CTRL_TYPE_UNKNOWN,
433@@ -162,13 +443,6 @@
434 _("Adjust the rotation angle"));
435 knotholder->add(e);
436 }
437-}
438-
439-void
440-LPECopyRotate::resetDefaults(SPItem const* item)
441-{
442- Effect::resetDefaults(item);
443- original_bbox(SP_LPE_ITEM(item));
444 };
445
446 namespace CR {
447
448=== modified file 'src/live_effects/lpe-copy_rotate.h'
449--- src/live_effects/lpe-copy_rotate.h 2015-01-20 23:05:32 +0000
450+++ src/live_effects/lpe-copy_rotate.h 2016-03-19 11:19:31 +0000
451@@ -22,24 +22,22 @@
452 namespace LivePathEffect {
453
454 namespace CR {
455- // we need a separate namespace to avoid clashes with LPEPerpBisector
456- class KnotHolderEntityStartingAngle;
457- class KnotHolderEntityRotationAngle;
458+// we need a separate namespace to avoid clashes with LPEPerpBisector
459+class KnotHolderEntityStartingAngle;
460+class KnotHolderEntityRotationAngle;
461 }
462
463 class LPECopyRotate : public Effect, GroupBBoxEffect {
464 public:
465 LPECopyRotate(LivePathEffectObject *lpeobject);
466 virtual ~LPECopyRotate();
467-
468 virtual void doOnApply (SPLPEItem const* lpeitem);
469-
470 virtual Geom::Piecewise<Geom::D2<Geom::SBasis> > doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in);
471-
472 virtual void doBeforeEffect (SPLPEItem const* lpeitem);
473-
474+ virtual void setFusion(Geom::PathVector &path_in, Geom::Path divider, double sizeDivider);
475+ virtual void split(Geom::PathVector &path_in, Geom::Path const &divider);
476 virtual void resetDefaults(SPItem const* item);
477-
478+ virtual void transform_multiply(Geom::Affine const& postmul, bool set);
479 /* the knotholder entity classes must be declared friends */
480 friend class CR::KnotHolderEntityStartingAngle;
481 friend class CR::KnotHolderEntityRotationAngle;
482@@ -53,16 +51,14 @@
483 ScalarParam starting_angle;
484 ScalarParam rotation_angle;
485 ScalarParam num_copies;
486- BoolParam copiesTo360;
487-
488+ BoolParam copies_to_360;
489+ BoolParam fuse_paths;
490 Geom::Point A;
491 Geom::Point B;
492 Geom::Point dir;
493-
494 Geom::Point start_pos;
495 Geom::Point rot_pos;
496 double dist_angle_handle;
497-
498 LPECopyRotate(const LPECopyRotate&);
499 LPECopyRotate& operator=(const LPECopyRotate&);
500 };