Merge lp:~msoegtrop/inkscape/embroidery into lp:~inkscape.dev/inkscape/trunk

Proposed by Michael Soegtrop
Status: Merged
Merged at revision: 15738
Proposed branch: lp:~msoegtrop/inkscape/embroidery
Merge into: lp:~inkscape.dev/inkscape/trunk
Diff against target: 2544 lines (+2417/-3)
11 files modified
astylerc (+4/-3)
po/POTFILES.in (+2/-0)
src/live_effects/CMakeLists.txt (+6/-0)
src/live_effects/effect-enum.h (+2/-0)
src/live_effects/effect.cpp (+10/-0)
src/live_effects/lpe-bool.cpp (+400/-0)
src/live_effects/lpe-bool.h (+63/-0)
src/live_effects/lpe-embrodery-stitch-ordering.cpp (+1139/-0)
src/live_effects/lpe-embrodery-stitch-ordering.h (+313/-0)
src/live_effects/lpe-embrodery-stitch.cpp (+400/-0)
src/live_effects/lpe-embrodery-stitch.h (+78/-0)
To merge this branch: bzr merge lp:~msoegtrop/inkscape/embroidery
Reviewer Review Type Date Requested Status
Jabiertxof code Approve
Jabiertxo Pending
Review via email: mp+325084@code.launchpad.net

Description of the change

Same as lpe-bool + an additional embroidery stitching LPE (the original reason why I wrote the bool LPE). The embroidery stuff doesn't change any existing file (except the few lines to link in the LPE) so it should be safe.

To post a comment you must log in.
Revision history for this message
Jabiertxof (jabiertxof) wrote :

Approve with some fixes in inline comment diff.
Very good work. Maybe too much complex to my maind :) Is great have you here!

review: Approve (code)
lp:~msoegtrop/inkscape/embroidery updated
14866. By Michael Soegtrop

fixed enum order + added cpp files to POFILES.in

Revision history for this message
Michael Soegtrop (msoegtrop) wrote :
Download full text (49.3 KiB)

Dear Jabier,

thanks :-) It is indeed quite complicated. The embroidery LPE typically
takes complicated structures of adjacent paths, typically created with
path interpolation and bool LPEs, and then cuts the path into stitches.
The complicated part is to order the resulting groups such that the
number / length of jump stitches is optimized. Since even simple designs
have 1000s of stitches, a straight forward TSP takes ages (TSP is
NP-complete). So I do complicated stuff like first grouping nearby
stitches together and then only connecting these groups with TSP style
algorithms. But this of cause makes the algorithm complicated. As is it
can handle many 1000 stitches in < 1 second. But it can still be improved.

I think the combination of path interpolation, bool LPE and stitching is
very powerful. It allows much more control than expensive commercial
embroidery SW (I have the one from Bernina, a few 1000 EUR and can't do
what I want).

I can send you some examples next weekend. I guess the embroidery LPE is
no so obvious.

Regarding your comment: I put in in the namespace for safety, because I
have a lot of global helper / utility function and wanted to avoid
collisions. But it is not required, it is just a habit in such
circumstances.

Thanks for your help! Please let me know if there is anything to be done
from my side. I will check my mail again in 15 minutes.

Best regards,

Michael

On 05.06.2017 20:26, Jabiertxof wrote:
> Review: Approve code
>
> Approve with some fixes in inline comment diff.
> Very good work. Maybe too much complex to my maind :) Is great have you here!
>
> Diff comments:
>
>> === modified file 'src/live_effects/effect-enum.h'
>> --- src/live_effects/effect-enum.h 2017-05-07 00:23:42 +0000
>> +++ src/live_effects/effect-enum.h 2017-06-05 14:13:17 +0000
>> @@ -42,6 +42,8 @@
>> TAPER_STROKE,
>> MIRROR_SYMMETRY,
>> COPY_ROTATE,
>> + EMBRODERY_STITCH,
> Set at same order than in effect.cpp, for example after the FILLET_CHAMFER, we use this enum values to access toExtended data defined in Effect.cpp and we need to be the same order
>
>> + BOOL_OP,
>> ATTACH_PATH,
>> FILL_BETWEEN_STROKES,
>> FILL_BETWEEN_MANY,
>>
>> === modified file 'src/live_effects/effect.cpp'
>> --- src/live_effects/effect.cpp 2017-05-29 00:09:29 +0000
>> +++ src/live_effects/effect.cpp 2017-06-05 14:13:17 +0000
>> @@ -116,6 +118,8 @@
>> {MIRROR_SYMMETRY, N_("Mirror symmetry"), "mirror_symmetry"},
>> {COPY_ROTATE, N_("Rotate copies"), "copy_rotate"},
>> /* Ponyscape -> Inkscape 0.92*/
>> + {EMBRODERY_STITCH, N_("Embrodery stitch"), "embrodery_stitch"},
> Put in 0.93 section
>
>> + {BOOL_OP, N_("Boolean operation"), "bool_op"},
>> {ATTACH_PATH, N_("Attach path"), "attach_path"},
>> {FILL_BETWEEN_STROKES, N_("Fill between strokes"), "fill_between_strokes"},
>> {FILL_BETWEEN_MANY, N_("Fill between many"), "fill_between_many"},
>>
>> === added file 'src/live_effects/lpe-embrodery-stitch-ordering.cpp'
>> --- src/live_effects/lpe-embrod...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'astylerc'
--- astylerc 2013-09-01 23:39:00 +0000
+++ astylerc 2017-06-05 19:01:49 +0000
@@ -1,8 +1,9 @@
1# Inkscape coding style options for the Artistic Style formatter1# Inkscape coding style options for the Artistic Style formatter
2# See http://astyle.sourceforge.net/2# See http://astyle.sourceforge.net/
3# See https://inkscape.org/de/develop/coding-style/
34
4# Opening braces broken from functions only5# Opening braces broken from functions only
5--brackets=stroustrup6--style=stroustrup
67
7# Four-space indent; convert stray tabs8# Four-space indent; convert stray tabs
8--indent=spaces=49--indent=spaces=4
@@ -15,7 +16,7 @@
1516
16# Attach pointers and references to variable names17# Attach pointers and references to variable names
17--align-pointer=name18--align-pointer=name
18# --align-reference=name19--align-reference=name
1920
20# Add brackets to single-statement blocks21# Add brackets to single-statement blocks
21--add-brackets22--add-brackets
@@ -23,4 +24,4 @@
23# Misc. options24# Misc. options
24--indent-preprocessor25--indent-preprocessor
25--indent-col1-comments26--indent-col1-comments
26--suffix=none27
2728
=== modified file 'po/POTFILES.in'
--- po/POTFILES.in 2017-05-08 01:43:41 +0000
+++ po/POTFILES.in 2017-06-05 19:01:49 +0000
@@ -119,6 +119,7 @@
119src/live_effects/effect.cpp119src/live_effects/effect.cpp
120src/live_effects/lpe-attach-path.cpp120src/live_effects/lpe-attach-path.cpp
121src/live_effects/lpe-bendpath.cpp121src/live_effects/lpe-bendpath.cpp
122src/live_effects/lpe-bool.cpp
122src/live_effects/lpe-bounding-box.cpp123src/live_effects/lpe-bounding-box.cpp
123src/live_effects/lpe-bspline.cpp124src/live_effects/lpe-bspline.cpp
124src/live_effects/lpe-clone-original.cpp125src/live_effects/lpe-clone-original.cpp
@@ -126,6 +127,7 @@
126src/live_effects/lpe-copy_rotate.cpp127src/live_effects/lpe-copy_rotate.cpp
127src/live_effects/lpe-curvestitch.cpp128src/live_effects/lpe-curvestitch.cpp
128src/live_effects/lpe-ellipse_5pts.cpp129src/live_effects/lpe-ellipse_5pts.cpp
130src/live_effects/lpe-embrodery-stitch.cpp
129src/live_effects/lpe-envelope.cpp131src/live_effects/lpe-envelope.cpp
130src/live_effects/lpe-extrude.cpp132src/live_effects/lpe-extrude.cpp
131src/live_effects/lpe-fill-between-many.cpp133src/live_effects/lpe-fill-between-many.cpp
132134
=== modified file 'src/live_effects/CMakeLists.txt'
--- src/live_effects/CMakeLists.txt 2017-05-06 21:00:45 +0000
+++ src/live_effects/CMakeLists.txt 2017-06-05 19:01:49 +0000
@@ -53,6 +53,9 @@
53 lpegroupbbox.cpp53 lpegroupbbox.cpp
54 lpeobject-reference.cpp54 lpeobject-reference.cpp
55 lpe-vonkoch.cpp55 lpe-vonkoch.cpp
56 lpe-embrodery-stitch.cpp
57 lpe-embrodery-stitch-ordering.cpp
58 lpe-bool.cpp
56 lpeobject.cpp59 lpeobject.cpp
57 spiro-converters.cpp60 spiro-converters.cpp
58 spiro.cpp61 spiro.cpp
@@ -136,6 +139,9 @@
136 lpe-test-doEffect-stack.h139 lpe-test-doEffect-stack.h
137 lpe-text_label.h140 lpe-text_label.h
138 lpe-vonkoch.h141 lpe-vonkoch.h
142 lpe-embrodery-stitch.h
143 lpe-embrodery-stitch-ordering.h
144 lpe-bool.h
139 lpegroupbbox.h145 lpegroupbbox.h
140 lpeobject-reference.h146 lpeobject-reference.h
141 lpeobject.h147 lpeobject.h
142148
=== modified file 'src/live_effects/effect-enum.h'
--- src/live_effects/effect-enum.h 2017-05-07 00:23:42 +0000
+++ src/live_effects/effect-enum.h 2017-06-05 19:01:49 +0000
@@ -49,6 +49,8 @@
49 BOUNDING_BOX,49 BOUNDING_BOX,
50 MEASURE_LINE,50 MEASURE_LINE,
51 FILLET_CHAMFER,51 FILLET_CHAMFER,
52 BOOL_OP,
53 EMBRODERY_STITCH,
52 DOEFFECTSTACK_TEST,54 DOEFFECTSTACK_TEST,
53 ANGLE_BISECTOR,55 ANGLE_BISECTOR,
54 CIRCLE_WITH_RADIUS,56 CIRCLE_WITH_RADIUS,
5557
=== modified file 'src/live_effects/effect.cpp'
--- src/live_effects/effect.cpp 2017-05-29 00:09:29 +0000
+++ src/live_effects/effect.cpp 2017-06-05 19:01:49 +0000
@@ -62,6 +62,8 @@
62#include "live_effects/lpe-test-doEffect-stack.h"62#include "live_effects/lpe-test-doEffect-stack.h"
63#include "live_effects/lpe-text_label.h"63#include "live_effects/lpe-text_label.h"
64#include "live_effects/lpe-vonkoch.h"64#include "live_effects/lpe-vonkoch.h"
65#include "live_effects/lpe-embrodery-stitch.h"
66#include "live_effects/lpe-bool.h"
6567
66#include "xml/node-event-vector.h"68#include "xml/node-event-vector.h"
67#include "message-stack.h"69#include "message-stack.h"
@@ -124,6 +126,8 @@
124/* 9.93 */126/* 9.93 */
125 {MEASURE_LINE, N_("Measure Line"), "measure_line"},127 {MEASURE_LINE, N_("Measure Line"), "measure_line"},
126 {FILLET_CHAMFER, N_("Fillet/Chamfer"), "fillet_chamfer"},128 {FILLET_CHAMFER, N_("Fillet/Chamfer"), "fillet_chamfer"},
129 {BOOL_OP, N_("Boolean operation"), "bool_op"},
130 {EMBRODERY_STITCH, N_("Embrodery stitch"), "embrodery_stitch"},
127#ifdef LPE_ENABLE_TEST_EFFECTS131#ifdef LPE_ENABLE_TEST_EFFECTS
128 {DOEFFECTSTACK_TEST, N_("doEffect stack test"), "doeffectstacktest"},132 {DOEFFECTSTACK_TEST, N_("doEffect stack test"), "doeffectstacktest"},
129 {ANGLE_BISECTOR, N_("Angle bisector"), "angle_bisector"},133 {ANGLE_BISECTOR, N_("Angle bisector"), "angle_bisector"},
@@ -164,6 +168,12 @@
164{168{
165 Effect* neweffect = NULL;169 Effect* neweffect = NULL;
166 switch (lpenr) {170 switch (lpenr) {
171 case EMBRODERY_STITCH:
172 neweffect = static_cast<Effect*> ( new LPEEmbroderyStitch(lpeobj) );
173 break;
174 case BOOL_OP:
175 neweffect = static_cast<Effect*> ( new LPEBool(lpeobj) );
176 break;
167 case PATTERN_ALONG_PATH:177 case PATTERN_ALONG_PATH:
168 neweffect = static_cast<Effect*> ( new LPEPatternAlongPath(lpeobj) );178 neweffect = static_cast<Effect*> ( new LPEPatternAlongPath(lpeobj) );
169 break;179 break;
170180
=== added file 'src/live_effects/lpe-bool.cpp'
--- src/live_effects/lpe-bool.cpp 1970-01-01 00:00:00 +0000
+++ src/live_effects/lpe-bool.cpp 2017-06-05 19:01:49 +0000
@@ -0,0 +1,400 @@
1/*
2 * Boolean operation live path effect
3 *
4 * Copyright (C) 2016-2017 Michael Soegtrop
5 *
6 * Released under GNU GPL, read the file 'COPYING' for more information
7 */
8
9#include <glibmm/i18n.h>
10#include <math.h>
11#include <string.h>
12#include <algorithm>
13
14#include "live_effects/lpe-bool.h"
15
16#include "display/curve.h"
17#include "sp-item.h"
18#include "2geom/path.h"
19#include "sp-shape.h"
20#include "sp-text.h"
21#include "2geom/bezier-curve.h"
22#include "2geom/path-sink.h"
23#include "2geom/affine.h"
24#include "splivarot.h"
25#include "helper/geom.h"
26#include "livarot/Path.h"
27#include "livarot/Shape.h"
28#include "livarot/path-description.h"
29#include "2geom/svg-path-parser.h"
30
31namespace Inkscape {
32namespace LivePathEffect {
33
34// Define an extended boolean operation type
35
36static const Util::EnumData<LPEBool::bool_op_ex> BoolOpData[LPEBool::bool_op_ex_count] = {
37 { LPEBool::bool_op_ex_union, N_("union"), "union" },
38 { LPEBool::bool_op_ex_inters, N_("intersection"), "inters" },
39 { LPEBool::bool_op_ex_diff, N_("difference"), "diff" },
40 { LPEBool::bool_op_ex_symdiff, N_("symmetric difference"), "symdiff" },
41 { LPEBool::bool_op_ex_cut, N_("division"), "cut" },
42 // Note on naming of operations:
43 // bool_op_cut is called "Division" in the manu, see sp_selected_path_cut
44 // bool_op_slice is called "Cut path" in the menu, see sp_selected_path_slice
45 { LPEBool::bool_op_ex_slice, N_("cut"), "slice" },
46 { LPEBool::bool_op_ex_slice_inside, N_("cut inside"), "slice-inside" },
47 { LPEBool::bool_op_ex_slice_outside, N_("cut outside"), "slice-outside" },
48};
49
50static const Util::EnumDataConverter<LPEBool::bool_op_ex> BoolOpConverter(BoolOpData, sizeof(BoolOpData) / sizeof(*BoolOpData));
51
52static const Util::EnumData<fill_typ> FillTypeData[] = {
53 { fill_oddEven, N_("odd-even"), "oddeven" },
54 { fill_nonZero, N_("non-zero"), "nonzero" },
55 { fill_positive, N_("positive"), "positive" },
56 { fill_justDont, N_("from curve"), "from-curve" }
57};
58
59static const Util::EnumDataConverter<fill_typ> FillTypeConverter(FillTypeData, sizeof(FillTypeData) / sizeof(*FillTypeData));
60
61static const Util::EnumData<fill_typ> FillTypeDataThis[] = {
62 { fill_oddEven, N_("odd-even"), "oddeven" },
63 { fill_nonZero, N_("non-zero"), "nonzero" },
64 { fill_positive, N_("positive"), "positive" }
65};
66
67static const Util::EnumDataConverter<fill_typ> FillTypeConverterThis(FillTypeDataThis, sizeof(FillTypeDataThis) / sizeof(*FillTypeDataThis));
68
69LPEBool::LPEBool(LivePathEffectObject *lpeobject) :
70 Effect(lpeobject),
71 operand_path(_("Operand path:"), _("Operand for the boolean operation"), "operand-path", &wr, this),
72 bool_operation(_("Operation:"), _("Boolean Operation"), "operation", BoolOpConverter, &wr, this, bool_op_ex_union),
73 swap_operands(_("Swap operands:"), _("Swap operands (useful e.g. for difference)"), "swap-operands", &wr, this),
74 rmv_inner(_("Remove inner:"), _("For cut operations: remove inner (non-contour) lines of cutting path to avoid invisible extra points"), "rmv-inner", &wr, this),
75 fill_type_this(_("Fill type this:"), _("Fill type (winding mode) for this path"), "filltype-this", FillTypeConverterThis, &wr, this, fill_oddEven),
76 fill_type_operand(_("Fill type operand:"), _("Fill type (winding mode) for operand path"), "filltype-operand", FillTypeConverter, &wr, this, fill_justDont)
77{
78 registerParameter(&operand_path);
79 registerParameter(&bool_operation);
80 registerParameter(&swap_operands);
81 registerParameter(&rmv_inner);
82 registerParameter(&fill_type_this);
83 registerParameter(&fill_type_operand);
84
85 show_orig_path = true;
86}
87
88LPEBool::~LPEBool()
89{
90
91}
92
93void LPEBool::resetDefaults(SPItem const * /*item*/)
94{
95}
96
97bool cmp_cut_position(const Path::cut_position &a, const Path::cut_position &b)
98{
99 return a.piece == b.piece ? a.t < b.t : a.piece < b.piece;
100}
101
102Geom::PathVector
103sp_pathvector_boolop_slice_intersect(Geom::PathVector const &pathva, Geom::PathVector const &pathvb, bool inside, fill_typ fra, fill_typ frb)
104{
105 // This is similar to sp_pathvector_boolop/bool_op_slice, but keeps only edges inside the cutter area.
106 // The code is also based on sp_pathvector_boolop_slice.
107 //
108 // We have two paths on input
109 // - a closed area which is used to cut out pieces from a contour (called area below)
110 // - a contour which is cut into pieces by the border of thr area (called contour below)
111 //
112 // The code below works in the following steps
113 // (a) Convert the area to a shape, so that we can ask the winding number for any point
114 // (b) Add both, the contour and the area to a single shape and intersect them
115 // (c) Find the intersection points between area border and contour (vector toCut)
116 // (d) Split the original contour at the intersection points
117 // (e) check for each contour edge in combined shape if its center is inside the area - if not discard it
118 // (f) create a vector of all inside edges
119 // (g) convert the piece numbers to the piece numbers after applying the cuts
120 // (h) fill a bool vector with information which pieces are in
121 // (i) filter the descr_cmd of the result path with this bool vector
122 //
123 // The main inefficieny here is step (e) because I use a winding function of the area-shape which goes
124 // through teh complete edge list for each point I ask for, so effort is n-edges-contour * n-edges-area.
125 // It is tricky to improve this without building into the livarot code.
126 // One way might be to decide at the intersection points which edges touching the intersection points are
127 // in by making a loop through all edges on the intersection vertex. Since this is a directed non intersecting
128 // graph, this should provide sufficient information.
129 // But since I anyway will change this to the new mechanism some time speed is fairly ok, I didn't look into this.
130
131
132 // extract the livarot Paths from the source objects
133 // also get the winding rule specified in the style
134 // Livarot's outline of arcs is broken. So convert the path to linear and cubics only, for which the outline is created correctly.
135 Path *contour_path = Path_for_pathvector(pathv_to_linear_and_cubic_beziers(pathva));
136 Path *area_path = Path_for_pathvector(pathv_to_linear_and_cubic_beziers(pathvb));
137
138 // Shapes from above paths
139 Shape *area_shape = new Shape;
140 Shape *combined_shape = new Shape;
141 Shape *combined_inters = new Shape;
142
143 // Add the area (process to intersection free shape)
144 area_path->ConvertWithBackData(1.0);
145 area_path->Fill(combined_shape, 1);
146
147 // Convert this to a shape with full winding information
148 area_shape->ConvertToShape(combined_shape, frb);
149
150 // Add the contour to the combined path (just add, no winding processing)
151 contour_path->ConvertWithBackData(1.0);
152 contour_path->Fill(combined_shape, 0, true, false, false);
153
154 // Intersect the area and the contour - no fill processing
155 combined_inters->ConvertToShape(combined_shape, fill_justDont);
156
157 // Result path
158 Path *result_path = new Path;
159 result_path->SetBackData(false);
160
161 // Cutting positions for contour
162 std::vector<Path::cut_position> toCut;
163
164 if (combined_inters->hasBackData()) {
165 // should always be the case, but ya never know
166 {
167 for (int i = 0; i < combined_inters->numberOfPoints(); i++) {
168 if (combined_inters->getPoint(i).totalDegree() > 2) {
169 // possibly an intersection
170 // we need to check that at least one edge from the source path is incident to it
171 // before we declare it's an intersection
172 int cb = combined_inters->getPoint(i).incidentEdge[FIRST];
173 int nbOrig = 0;
174 int nbOther = 0;
175 int piece = -1;
176 float t = 0.0;
177 while (cb >= 0 && cb < combined_inters->numberOfEdges()) {
178 if (combined_inters->ebData[cb].pathID == 0) {
179 // the source has an edge incident to the point, get its position on the path
180 piece = combined_inters->ebData[cb].pieceID;
181 if (combined_inters->getEdge(cb).st == i) {
182 t = combined_inters->ebData[cb].tSt;
183 } else {
184 t = combined_inters->ebData[cb].tEn;
185 }
186 nbOrig++;
187 }
188 if (combined_inters->ebData[cb].pathID == 1) {
189 nbOther++; // the cut is incident to this point
190 }
191 cb = combined_inters->NextAt(i, cb);
192 }
193 if (nbOrig > 0 && nbOther > 0) {
194 // point incident to both path and cut: an intersection
195 // note that you only keep one position on the source; you could have degenerate
196 // cases where the source crosses itself at this point, and you wouyld miss an intersection
197 Path::cut_position cutpos;
198 cutpos.piece = piece;
199 cutpos.t = t;
200 toCut.push_back(cutpos);
201 }
202 }
203 }
204 }
205 {
206 // remove the edges from the intersection polygon
207 int i = combined_inters->numberOfEdges() - 1;
208 for (; i >= 0; i--) {
209 if (combined_inters->ebData[i].pathID == 1) {
210 combined_inters->SubEdge(i);
211 } else {
212 const Shape::dg_arete &edge = combined_inters->getEdge(i);
213 const Shape::dg_point &start = combined_inters->getPoint(edge.st);
214 const Shape::dg_point &end = combined_inters->getPoint(edge.en);
215 Geom::Point mid = 0.5 * (start.x + end.x);
216 int wind = area_shape->PtWinding(mid);
217 if (wind == 0) {
218 combined_inters->SubEdge(i);
219 }
220 }
221 }
222 }
223 }
224
225 // create a vector of pieces, which are in the intersection
226 std::vector<Path::cut_position> inside_pieces(combined_inters->numberOfEdges());
227 for (int i = 0; i < combined_inters->numberOfEdges(); i++) {
228 inside_pieces[i].piece = combined_inters->ebData[i].pieceID;
229 // Use the t middle point, this is safe to compare with values from toCut in the presence of roundoff errors
230 inside_pieces[i].t = 0.5 * (combined_inters->ebData[i].tSt + combined_inters->ebData[i].tEn);
231 }
232 std::sort(inside_pieces.begin(), inside_pieces.end(), cmp_cut_position);
233
234 // sort cut positions
235 std::sort(toCut.begin(), toCut.end(), cmp_cut_position);
236
237 // Compute piece ids after ConvertPositionsToMoveTo
238 {
239 int idIncr = 0;
240 std::vector<Path::cut_position>::iterator itPiece = inside_pieces.begin();
241 std::vector<Path::cut_position>::iterator itCut = toCut.begin();
242 while (itPiece != inside_pieces.end()) {
243 while (itCut != toCut.end() && cmp_cut_position(*itCut, *itPiece)) {
244 ++itCut;
245 idIncr += 2;
246 }
247 itPiece->piece += idIncr;
248 ++itPiece;
249 }
250 }
251
252 // Copy the original path to result and cut at the intersection points
253 result_path->Copy(contour_path);
254 result_path->ConvertPositionsToMoveTo(toCut.size(), toCut.data()); // cut where you found intersections
255
256 // Create an array of bools which states which pieces are in
257 std::vector<bool> inside_flags(result_path->descr_cmd.size(), false);
258 for (std::vector<Path::cut_position>::iterator itPiece = inside_pieces.begin(); itPiece != inside_pieces.end(); ++itPiece) {
259 inside_flags[ itPiece->piece ] = true;
260 // also enable the element -1 to get the MoveTo
261 if (itPiece->piece >= 1) {
262 inside_flags[ itPiece->piece - 1 ] = true;
263 }
264 }
265
266#if 0 // CONCEPT TESTING
267 //Check if the inside/outside verdict is consistent - just for testing the concept
268 // Retrieve the pieces
269 int nParts = 0;
270 Path **parts = result_path->SubPaths(nParts, false);
271
272 // Each piece should be either fully in or fully out
273 int iPiece = 0;
274 for (int iPart = 0; iPart < nParts; iPart++) {
275 bool andsum = true;
276 bool orsum = false;
277 for (int iCmd = 0; iCmd < parts[iPart]->descr_cmd.size(); iCmd++, iPiece++) {
278 andsum = andsum && inside_flags[ iPiece ];
279 orsum = andsum || inside_flags[ iPiece ];
280 }
281
282 if (andsum != orsum) {
283 g_warning("Inconsistent inside/outside verdict for part=%d", iPart);
284 }
285 }
286 g_free(parts);
287#endif
288
289 // iterate over the commands of a path and keep those which are inside
290 int iDest = 0;
291 for (int iSrc = 0; iSrc < result_path->descr_cmd.size(); iSrc++) {
292 if (inside_flags[iSrc] == inside) {
293 result_path->descr_cmd[iDest++] = result_path->descr_cmd[iSrc];
294 } else {
295 delete result_path->descr_cmd[iSrc];
296 }
297 }
298 result_path->descr_cmd.resize(iDest);
299
300 delete combined_inters;
301 delete combined_shape;
302 delete area_shape;
303 delete contour_path;
304 delete area_path;
305
306 gchar *result_str = result_path->svg_dump_path();
307 Geom::PathVector outres = Geom::parse_svg_path(result_str);
308 // CONCEPT TESTING g_warning( "%s", result_str );
309 g_free(result_str);
310 delete result_path;
311
312 return outres;
313}
314
315// remove inner contours
316Geom::PathVector
317sp_pathvector_boolop_remove_inner(Geom::PathVector const &pathva, fill_typ fra)
318{
319 Geom::PathVector patht;
320 Path *patha = Path_for_pathvector(pathv_to_linear_and_cubic_beziers(pathva));
321
322 Shape *shape = new Shape;
323 Shape *shapeshape = new Shape;
324 Path *resultp = new Path;
325 resultp->SetBackData(false);
326
327 patha->ConvertWithBackData(0.1);
328 patha->Fill(shape, 0);
329 shapeshape->ConvertToShape(shape, fra);
330 shapeshape->ConvertToForme(resultp, 1, &patha);
331
332 delete shape;
333 delete shapeshape;
334 delete patha;
335
336 gchar *result_str = resultp->svg_dump_path();
337 Geom::PathVector resultpv = Geom::parse_svg_path(result_str);
338 g_free(result_str);
339
340 delete resultp;
341 return resultpv;
342}
343
344static fill_typ GetFillTyp(SPItem *item)
345{
346 SPCSSAttr *css = sp_repr_css_attr(item->getRepr(), "style");
347 gchar const *val = sp_repr_css_property(css, "fill-rule", NULL);
348 if (val && strcmp(val, "nonzero") == 0) {
349 return fill_nonZero;
350 } else if (val && strcmp(val, "evenodd") == 0) {
351 return fill_oddEven;
352 } else {
353 return fill_nonZero;
354 }
355}
356
357void LPEBool::doEffect(SPCurve *curve)
358{
359 Geom::PathVector path_in = curve->get_pathvector();
360
361 if (operand_path.linksToPath() && operand_path.getObject()) {
362 bool_op_ex op = bool_operation.get_value();
363 bool swap = swap_operands.get_value();
364
365 Geom::PathVector path_a = swap ? operand_path.get_pathvector() : path_in;
366 Geom::PathVector path_b = swap ? path_in : operand_path.get_pathvector();
367
368 // TODO: I would like to use the original objects fill rule if the UI selected rule is fill_justDont.
369 // But it doesn't seem possible to access them from here, because SPCurve is not derived from SPItem.
370 // The nearest function in the call stack, where this is available is SPLPEItem::performPathEffect (this is then an SPItem)
371 // For the parameter curve, this is possible.
372 // fill_typ fill_this = fill_type_this. get_value()!=fill_justDont ? fill_type_this.get_value() : GetFillTyp( curve ) ;
373 fill_typ fill_this = fill_type_this.get_value();
374 fill_typ fill_operand = fill_type_operand.get_value() != fill_justDont ? fill_type_operand.get_value() : GetFillTyp(operand_path.getObject());
375
376 fill_typ fill_a = swap ? fill_operand : fill_this;
377 fill_typ fill_b = swap ? fill_this : fill_operand;
378
379 if (rmv_inner.get_value()) {
380 path_b = sp_pathvector_boolop_remove_inner(path_b, fill_b);
381 }
382
383 Geom::PathVector path_out;
384
385 if (op == bool_op_ex_slice) {
386 // For slicing, the bool op is added to the line group which is sliced, not the cut path. This swapped order is correct.
387 path_out = sp_pathvector_boolop(path_b, path_a, to_bool_op(op), fill_b, fill_a);
388 } else if (op == bool_op_ex_slice_inside) {
389 path_out = sp_pathvector_boolop_slice_intersect(path_a, path_b, true, fill_a, fill_b);
390 } else if (op == bool_op_ex_slice_outside) {
391 path_out = sp_pathvector_boolop_slice_intersect(path_a, path_b, false, fill_a, fill_b);
392 } else {
393 path_out = sp_pathvector_boolop(path_a, path_b, to_bool_op(op), fill_a, fill_b);
394 }
395 curve->set_pathvector(path_out);
396 }
397}
398
399} // namespace LivePathEffect
400} /* namespace Inkscape */
0401
=== added file 'src/live_effects/lpe-bool.h'
--- src/live_effects/lpe-bool.h 1970-01-01 00:00:00 +0000
+++ src/live_effects/lpe-bool.h 2017-06-05 19:01:49 +0000
@@ -0,0 +1,63 @@
1/*
2 * Boolean operation live path effect
3 *
4 * Copyright (C) 2016 Michael Soegtrop
5 *
6 * Released under GNU GPL, read the file 'COPYING' for more information
7 */
8
9#ifndef INKSCAPE_LPE_BOOL_H
10#define INKSCAPE_LPE_BOOL_H
11
12#include "live_effects/effect.h"
13#include "live_effects/parameter/parameter.h"
14#include "live_effects/parameter/originalpath.h"
15#include "live_effects/parameter/bool.h"
16#include "live_effects/parameter/enum.h"
17#include "livarot/LivarotDefs.h"
18
19namespace Inkscape {
20namespace LivePathEffect {
21
22class LPEBool : public Effect {
23public:
24 LPEBool(LivePathEffectObject *lpeobject);
25 virtual ~LPEBool();
26
27 void doEffect(SPCurve *curve);
28 virtual void resetDefaults(SPItem const *item);
29
30 enum bool_op_ex {
31 bool_op_ex_union = bool_op_union,
32 bool_op_ex_inters = bool_op_inters,
33 bool_op_ex_diff = bool_op_diff,
34 bool_op_ex_symdiff = bool_op_symdiff,
35 bool_op_ex_cut = bool_op_cut,
36 bool_op_ex_slice = bool_op_slice,
37 bool_op_ex_slice_inside, // like bool_op_slice, but leaves only the contour pieces inside of the cut path
38 bool_op_ex_slice_outside, // like bool_op_slice, but leaves only the contour pieces outside of the cut path
39 bool_op_ex_count
40 };
41
42 inline friend bool_op to_bool_op(bool_op_ex val)
43 {
44 assert(val <= bool_op_ex_slice);
45 (bool_op) val;
46 }
47
48private:
49 LPEBool(const LPEBool &);
50 LPEBool &operator=(const LPEBool &);
51
52 OriginalPathParam operand_path;
53 EnumParam<bool_op_ex> bool_operation;
54 EnumParam<fill_typ> fill_type_this;
55 EnumParam<fill_typ> fill_type_operand;
56 BoolParam swap_operands;
57 BoolParam rmv_inner;
58};
59
60}; //namespace LivePathEffect
61}; //namespace Inkscape
62
63#endif
064
=== added file 'src/live_effects/lpe-embrodery-stitch-ordering.cpp'
--- src/live_effects/lpe-embrodery-stitch-ordering.cpp 1970-01-01 00:00:00 +0000
+++ src/live_effects/lpe-embrodery-stitch-ordering.cpp 2017-06-05 19:01:49 +0000
@@ -0,0 +1,1139 @@
1/*
2 * Sub-path Ordering functions for embroidery stitch LPE (Implementation)
3 *
4 * Copyright (C) 2016 Michael Soegtrop
5 *
6 * Released under GNU GPL, read the file 'COPYING' for more information
7 */
8
9#include "live_effects/lpe-embrodery-stitch-ordering.h"
10
11#include <numeric>
12
13namespace Inkscape {
14namespace LivePathEffect {
15namespace LPEEmbroderyStitchOrdering {
16
17using namespace Geom;
18
19// ==================== Debug Trace Macros ====================
20
21// ATTENTION: both level and area macros must be enabled for tracing
22
23// These macros are for enabling certain levels of tracing
24#define DebugTrace1(list) // g_warning list
25#define DebugTrace2(list) // g_warning list
26
27// These macros are for enabling certain areas of tracing
28#define DebugTraceGrouping(list) // list
29#define DebugTraceTSP(list) // list
30
31// Combinations of above
32#define DebugTrace1TSP(list) DebugTraceTSP( DebugTrace1(list) )
33#define DebugTrace2TSP(list) DebugTraceTSP( DebugTrace2(list) )
34
35// ==================== Template Utilities ====================
36
37// Delete all objects pointed to by a vector and clear the vector
38
39template< typename T > void delete_and_clear(std::vector<T> &vector)
40{
41 for (typename std::vector<T>::iterator it = vector.begin(); it != vector.end(); ++it) {
42 delete *it;
43 }
44 vector.clear();
45}
46
47// Assert that there are no duplicates in a vector
48
49template< typename T > void assert_unique(std::vector<T> &vector)
50{
51 typename std::vector<T> copy = vector;
52 std::sort(copy.begin(), copy.end());
53 assert(std::unique(copy.begin(), copy.end()) == copy.end());
54}
55
56// remove element(s) by value
57
58template< typename T > void remove_by_value(std::vector<T> *vector, const T &value)
59{
60 vector->erase(std::remove(vector->begin(), vector->end(), value), vector->end());
61}
62
63// fill a vector with increasing elements (similar to C++11 iota)
64// iota is included in some C++ libraries, not in other (it is always included for C++11)
65// To avoid issues, use our own name (not iota)
66
67template<class OutputIterator, class Counter>
68void fill_increasing(OutputIterator begin, OutputIterator end, Counter counter)
69{
70 while (begin != end) {
71 *begin++ = counter++;
72 }
73}
74
75// check if an iteratable sequence contains an element
76
77template<class InputIterator, class Element>
78bool contains(InputIterator begin, InputIterator end, const Element &elem)
79{
80 while (begin != end) {
81 if (*begin == elem) {
82 return true;
83 }
84 ++begin;
85 }
86 return false;
87}
88
89// Check if a vector contains an element
90
91template<class Element>
92bool contains(std::vector<Element> const &vector, const Element &elem)
93{
94 return contains(vector.begin(), vector.end(), elem);
95}
96
97// ==================== Multi-dimensional iterator functions ====================
98
99// Below are 3 simple template functions to do triangle/pyramid iteration (without diagonal).
100// Here is a sample of iterating over 5 elements in 3 dimensions:
101//
102// 0 1 2
103// 0 1 3
104// 0 1 4
105// 0 2 3
106// 0 2 4
107// 1 2 4
108// 1 3 4
109// 2 3 4
110// end end end
111//
112// If the number of elements is less then the number of dimensions, the number of dimensions is reduced automatically.
113//
114// I thought about creating an iterator class for this, but it doesn't match that well, so I used functions on iterator vectors.
115
116// Initialize a vector of iterators
117
118template<class Iterator>
119void triangleit_begin(std::vector<Iterator> &iterators, Iterator const &begin, Iterator const &end, size_t n)
120{
121 iterators.clear();
122 // limit number of dimensions to number of elements
123 size_t n1 = end - begin;
124 n = std::min(n, n1);
125 if (n) {
126 iterators.push_back(begin);
127 for (int i = 1; i < n; i++) {
128 iterators.push_back(iterators.back() + 1);
129 }
130 }
131}
132
133// Increment a vector of iterators
134
135template<class Iterator>
136void triangleit_incr(std::vector<Iterator> &iterators, Iterator const &end)
137{
138 size_t n = iterators.size();
139
140 for (int i = 0; i < n; i++) {
141 iterators[n - 1 - i]++;
142 // Each dimension ends at end-i, so that there are elements left for the i higher dimensions
143 if (iterators[n - 1 - i] != end - i) {
144 // Assign increasing numbers to the higher dimension
145 for (int j = n - i; j < n; j++) {
146 iterators[j] = iterators[j - 1] + 1;
147 }
148 return;
149 }
150 }
151}
152
153// Check if a vector of iterators is at the end
154
155template<class Iterator>
156bool triangleit_test(std::vector<Iterator> &iterators, Iterator const &end)
157{
158 if (iterators.empty()) {
159 return false;
160 } else {
161 return iterators.back() != end;
162 }
163}
164
165// ==================== Trivial Ordering Functions ====================
166
167// Sub-path reordering: do nothing - keep original order
168
169void OrderingOriginal(std::vector<OrderingInfo> &infos)
170{
171}
172
173// Sub-path reordering: reverse every other sub path
174
175void OrderingZigZag(std::vector<OrderingInfo> &infos, bool revfirst)
176{
177 for (std::vector<OrderingInfo>::iterator it = infos.begin(); it != infos.end(); ++it) {
178 it->reverse = (it->index & 1) == (revfirst ? 0 : 1);
179 }
180}
181
182// Sub-path reordering: continue with the neartest start or end point of yet unused sub paths
183
184void OrderingClosest(std::vector<OrderingInfo> &infos, bool revfirst)
185{
186 std::vector<OrderingInfo> result;
187 result.reserve(infos.size());
188
189 result.push_back(infos[0]);
190 result.back().reverse = revfirst;
191 Point p = result.back().GetEndRev();
192
193 infos[0].used = true;
194
195
196 for (unsigned int iRnd = 1; iRnd < infos.size(); iRnd++) {
197 // find closest point to p
198 unsigned iBest = 0;
199 bool revBest = false;
200 Coord distBest = infinity();
201
202 for (std::vector<OrderingInfo>::iterator it = infos.begin(); it != infos.end(); ++it) {
203 it->index = it - infos.begin();
204 it->reverse = (it->index & 1) != 0;
205
206 if (!it->used) {
207 Coord dist = distance(p, it->GetBegOrig());
208 if (dist < distBest) {
209 distBest = dist;
210 iBest = it - infos.begin();
211 revBest = false;
212 }
213
214 dist = distance(p, it->GetEndOrig());
215 if (dist < distBest) {
216 distBest = dist;
217 iBest = it - infos.begin();
218 revBest = true;
219 }
220 }
221 }
222
223 result.push_back(infos[iBest]);
224 result.back().reverse = revBest;
225 p = result.back().GetEndRev();
226 infos[iBest].used = true;
227 }
228
229 infos = result;
230}
231
232// ==================== Traveling Salesman k-opt Ordering Function and Utilities ====================
233
234// A Few notes on this:
235// - This is a relatively simple Lin-type k-opt algorithm, but the grouping optimizations done make it already quite complex.
236// - The main Ordering Function is OrderingAdvanced
237// - Lines which start at the end of another line are connected and treated as one (struct OrderingInfoEx)
238// - Groups of zig-zag OrderingInfoEx are grouped (struct OrderingGroup) if both ends of the segment mutually agree with a next neighbor.
239// These groups are treated as a unit in the TSP algorithm.
240// The only option is to reverse the first segment, so that a group has 4 end points, 2 of which are used externally.
241// - Run a k-opt (k=2..5) Lin like Traveling Salesman Problem algorithm on the groups as a unit and the remaining edges.
242// See https://en.wikipedia.org/wiki/Travelling_salesman_problem#Iterative_improvement
243// The algorithm uses a greedy nearest neighbor as start configuration and does not use repeated random starts.
244// - The algorithm searches an open tour (rather than a closed one), so the longest segment in the closed path is ignored.
245// - TODO: it might be faster to use k=3 with a few random starting patterns instead of k=5
246// - TODO: it is surely wiser to implement e.g. Lin-Kenrighan TSP, but the simple k-opt works ok.
247// - TODO(EASY): add a jump distance, above which threads are removed and make the length of this jump distance constant and large,
248// so that mostly the number of jumps is optimized
249
250// Find 2 nearest points to given point
251
252void OrderingPoint::FindNearest2(const std::vector<OrderingInfoEx *> &infos)
253{
254 // This implementation is not terribly elegant (unSTLish).
255 // But for the first 2 elements using e.g. partial_sort is not simpler.
256
257 Coord dist0 = infinity();
258 Coord dist1 = infinity();
259 nearest[0] = 0;
260 nearest[1] = 0;
261
262 for (std::vector<OrderingInfoEx *>::const_iterator it = infos.begin(); it != infos.end(); ++it) {
263 Coord dist = distance(point, (*it)->beg.point);
264 if (dist < dist1) {
265 if (&(*it)->beg != this && &(*it)->end != this) {
266 if (dist < dist0) {
267 nearest[1] = nearest[0];
268 nearest[0] = &(*it)->beg;
269 dist1 = dist0;
270 dist0 = dist;
271 } else {
272 nearest[1] = &(*it)->beg;
273 dist1 = dist;
274 }
275 }
276 }
277
278 dist = distance(point, (*it)->end.point);
279 if (dist < dist1) {
280 if (&(*it)->beg != this && &(*it)->end != this) {
281 if (dist < dist0) {
282 nearest[1] = nearest[0];
283 nearest[0] = &(*it)->end;
284 dist1 = dist0;
285 dist0 = dist;
286 } else {
287 nearest[1] = &(*it)->end;
288 dist1 = dist;
289 }
290 }
291 }
292 }
293}
294
295// Check if "this" is among the nearest of its nearest
296
297void OrderingPoint::EnforceMutual(void)
298{
299 if (nearest[0] && !(this == nearest[0]->nearest[0] || this == nearest[0]->nearest[1])) {
300 nearest[0] = 0;
301 }
302
303 if (nearest[1] && !(this == nearest[1]->nearest[0] || this == nearest[1]->nearest[1])) {
304 nearest[1] = 0;
305 }
306
307 if (nearest[1] && !nearest[0]) {
308 nearest[0] = nearest[1];
309 nearest[1] = 0;
310 }
311}
312
313// Check if the subpath indices of this and other are the same, otherwise zero both nearest
314
315void OrderingPoint::EnforceSymmetric(const OrderingPoint &other)
316{
317 if (nearest[0] && !(
318 (other.nearest[0] && nearest[0]->infoex == other.nearest[0]->infoex) ||
319 (other.nearest[1] && nearest[0]->infoex == other.nearest[1]->infoex)
320 )) {
321 nearest[0] = 0;
322 }
323
324 if (nearest[1] && !(
325 (other.nearest[0] && nearest[1]->infoex == other.nearest[0]->infoex) ||
326 (other.nearest[1] && nearest[1]->infoex == other.nearest[1]->infoex)
327 )) {
328 nearest[1] = 0;
329 }
330
331 if (nearest[1] && !nearest[0]) {
332 nearest[0] = nearest[1];
333 nearest[1] = 0;
334 }
335}
336
337void OrderingPoint::Dump(void)
338{
339 Coord dist0 = nearest[0] ? distance(point, nearest[0]->point) : -1.0;
340 Coord dist1 = nearest[1] ? distance(point, nearest[1]->point) : -1.0;
341 int idx0 = nearest[0] ? nearest[0]->infoex->idx : -1;
342 int idx1 = nearest[1] ? nearest[1]->infoex->idx : -1;
343 DebugTrace2(("I=%d X=%.5lf Y=%.5lf d1=%.3lf d2=%.3lf i1=%d i2=%d", infoex->idx, point.x(), 297.0 - point.y(), dist0, dist1, idx0, idx1));
344}
345
346
347// If this element can be grouped (has neighbours) but is not yet grouped, create a new group
348
349void OrderingInfoEx::MakeGroup(std::vector<OrderingInfoEx *> &infos, std::vector<OrderingGroup *> *groups)
350{
351 if (grouped || !beg.HasNearest() || !end.HasNearest()) {
352 return;
353 }
354
355 groups->push_back(new OrderingGroup(groups->size()));
356
357 // Add neighbors recursively
358 AddToGroup(infos, groups->back());
359}
360
361// Add this and all connected elements to the group
362
363void OrderingInfoEx::AddToGroup(std::vector<OrderingInfoEx *> &infos, OrderingGroup *group)
364{
365 if (grouped || !beg.HasNearest() || !end.HasNearest()) {
366 return;
367 }
368
369 group->items.push_back(this);
370 grouped = true;
371 // Note: beg and end neighbors have been checked to be symmetric
372 if (beg.nearest[0]) {
373 beg.nearest[0]->infoex->AddToGroup(infos, group);
374 }
375 if (beg.nearest[1]) {
376 beg.nearest[1]->infoex->AddToGroup(infos, group);
377 }
378 if (end.nearest[0]) {
379 end.nearest[0]->infoex->AddToGroup(infos, group);
380 }
381 if (end.nearest[1]) {
382 end.nearest[1]->infoex->AddToGroup(infos, group);
383 }
384}
385
386// Constructor
387
388OrderingGroupNeighbor::OrderingGroupNeighbor(OrderingGroupPoint *me, OrderingGroupPoint *other) :
389 point(other),
390 distance(Geom::distance(me->point, other->point))
391{
392}
393
394// Comparison function for sorting by distance
395
396bool OrderingGroupNeighbor::Compare(const OrderingGroupNeighbor &a, const OrderingGroupNeighbor &b)
397{
398 return a.distance < b.distance;
399}
400
401// Find the nearest unused neighbor point
402
403OrderingGroupNeighbor *OrderingGroupPoint::FindNearestUnused(void)
404{
405 for (std::vector<OrderingGroupNeighbor>::iterator it = nearest.begin(); it != nearest.end(); ++it) {
406 if (!it->point->used) {
407 DebugTrace1TSP(("Nearest: group %d, size %d, point %d, nghb %d, xFrom %.4lf, yFrom %.4lf, xTo %.4lf, yTo %.4lf, dist %.4lf",
408 it->point->group->index, it->point->group->items.size(), it->point->indexInGroup, it - nearest.begin(),
409 point.x(), 297 - point.y(),
410 it->point->point.x(), 297 - it->point->point.y(),
411 it->distance));
412 return &*it;
413 }
414 }
415
416 // it shouldn't happen that we can't find any point at all
417 assert(0);
418 return 0;
419}
420
421// Return the other end in the group of the point
422
423OrderingGroupPoint *OrderingGroupPoint::GetOtherEndGroup(void)
424{
425 return group->endpoints[ indexInGroup ^ 1 ];
426}
427
428// Return the alternate point (if one exists), 0 otherwise
429
430OrderingGroupPoint *OrderingGroupPoint::GetAltPointGroup(void)
431{
432 if (group->nEndPoints < 4) {
433 return 0;
434 }
435
436 OrderingGroupPoint *alt = group->endpoints[ indexInGroup ^ 2 ];
437 return alt->used ? 0 : alt;
438}
439
440
441// Sets the rev flags in the group assuming that the group starts with this point
442
443void OrderingGroupPoint::SetRevInGroup(void)
444{
445 // If this is not a front point, the item list needs to be reversed
446 group->revItemList = !front;
447
448 // If this is not a begin point, the items need to be reversed
449 group->revItems = !begin;
450}
451
452// Mark an end point as used and also mark the two other alternating points as used
453// Returns the used point
454
455void OrderingGroupPoint::UsePoint(void)
456{
457 group->UsePoint(indexInGroup);
458}
459
460// Mark an end point as unused and possibly also mark the two other alternating points as unused
461// Returns the used point
462
463void OrderingGroupPoint::UnusePoint(void)
464{
465 group->UnusePoint(indexInGroup);
466}
467
468// Return the other end in the connection
469OrderingGroupPoint *OrderingGroupPoint::GetOtherEndConnection(void)
470{
471 assert(connection);
472 assert(connection->points[ indexInConnection ] == this);
473 assert(connection->points[ indexInConnection ^ 1 ]);
474
475 return connection->points[ indexInConnection ^ 1 ];
476}
477
478
479// Set the end points of a group from the items
480
481void OrderingGroup::SetEndpoints(void)
482{
483 assert(items.size() >= 1);
484
485 if (items.size() == 1) {
486 // A simple line:
487 //
488 // b0-front--e1
489
490 nEndPoints = 2;
491 endpoints[0] = new OrderingGroupPoint(items.front()->beg.point, this, 0, true, true);
492 endpoints[1] = new OrderingGroupPoint(items.front()->end.point, this, 1, false, true);
493 } else {
494 // If the number of elements is even, the group is
495 // either from items.front().beg to items.back().beg
496 // or from items.front().end to items.back().end:
497 // Below: b=beg, e=end, numbers are end point indices
498 //
499 // b0-front--e b0-front--e2
500 // | |
501 // b---------e b---------e
502 // | |
503 // b---------e b---------e
504 // | |
505 // b1-back---e b1-back---e3
506 //
507 //
508 // if the number of elements is odd, it is crossed:
509 //
510 // b0-front--e b--front--e2
511 // | |
512 // b---------e b---------e
513 // | |
514 // b--back---e1 b3-back---e
515 //
516 // TODO: this is not true with the following kind of pattern
517 //
518 // b--front--e
519 // b---------e
520 // b--------e
521 // b--back--e
522 //
523 // Here only one connection is possible, from front.end to back.beg
524 //
525 // TODO: also this is not true if segment direction is alternating
526 //
527 // TOTO: => Just see where you end up from front().begin and front().end
528 //
529 // the numbering is such that either end points 0 and 1 are used or 2 and 3.
530 int cross = items.size() & 1 ? 2 : 0;
531 nEndPoints = 4;
532
533 endpoints[0 ] = new OrderingGroupPoint(items.front()->beg.point, this, 0, true, true);
534 endpoints[1 ^ cross] = new OrderingGroupPoint(items.back() ->beg.point, this, 1 ^ cross, true, false);
535 endpoints[2 ] = new OrderingGroupPoint(items.front()->end.point, this, 2, false, true);
536 endpoints[3 ^ cross] = new OrderingGroupPoint(items.back() ->end.point, this, 3 ^ cross, false, false);
537 }
538}
539
540// Add all points from another group as neighbors
541
542void OrderingGroup::AddNeighbors(OrderingGroup *nghb)
543{
544 for (int iThis = 0; iThis < nEndPoints; iThis++) {
545 for (int iNghb = 0; iNghb < nghb->nEndPoints; iNghb++) {
546 endpoints[iThis]->nearest.push_back(OrderingGroupNeighbor(endpoints[iThis], nghb->endpoints[iNghb]));
547 }
548 }
549}
550
551// Mark an end point as used and also mark the two other alternating points as used
552// Returns the used point
553
554OrderingGroupPoint *OrderingGroup::UsePoint(int index)
555{
556 assert(index < nEndPoints);
557 assert(!endpoints[index]->used);
558 endpoints[index]->used = true;
559 if (nEndPoints == 4) {
560 int offs = index < 2 ? 2 : 0;
561 endpoints[0 + offs]->used = true;
562 endpoints[1 + offs]->used = true;
563 }
564
565 return endpoints[index];
566}
567
568// Mark an end point as unused and possibly also mark the two other alternating points as unused
569// Returns the used point
570
571void OrderingGroup::UnusePoint(int index)
572{
573 assert(index < nEndPoints);
574 assert(endpoints[index]->used);
575 endpoints[index]->used = false;
576
577 if (nEndPoints == 4 && !endpoints[index ^ 1]->used) {
578 int offs = index < 2 ? 2 : 0;
579 endpoints[0 + offs]->used = false;
580 endpoints[1 + offs]->used = false;
581 }
582}
583
584// Add an end point
585void OrderingSegment::AddPoint(OrderingGroupPoint *point)
586{
587 assert(point);
588 assert(nEndPoints < 4);
589 endpoints[ nEndPoints++ ] = point;
590
591 // If both ends of a group are added and the group has 4 points, add the other two as well
592 if (nEndPoints == 2 && endpoints[0]->group == endpoints[1]->group) {
593 OrderingGroup *group = endpoints[0]->group;
594 if (group->nEndPoints == 4) {
595 for (int i = 0; i < 4; i++) {
596 endpoints[i] = group->endpoints[i];
597 }
598 nEndPoints = 4;
599 }
600 }
601}
602
603// Get begin point (taking swap and end bit into account
604OrderingGroupPoint *OrderingSegment::GetBeginPoint(unsigned int iSwap, unsigned int iEnd)
605{
606 int iPoint = ((iEnd >> endbit) & 1) + (((iSwap >> swapbit) & 1) << 1);
607 assert(iPoint < nEndPoints);
608 return endpoints[iPoint];
609}
610
611// Get end point (taking swap and end bit into account
612OrderingGroupPoint *OrderingSegment::GetEndPoint(unsigned int iSwap, unsigned int iEnd)
613{
614 int iPoint = (((iEnd >> endbit) & 1) ^ 1) + (((iSwap >> swapbit) & 1) << 1);
615 assert(iPoint < nEndPoints);
616 return endpoints[iPoint];
617}
618
619
620// Find the next unused point in list
621std::vector<OrderingGroupPoint *>::iterator FindUnusedAndUse(std::vector<OrderingGroupPoint *> *unusedPoints, std::vector<OrderingGroupPoint *>::iterator const from)
622{
623 for (std::vector<OrderingGroupPoint *>::iterator it = from; it != unusedPoints->end(); ++it) {
624 if (!(*it)->used) {
625 (*it)->UsePoint();
626 return it;
627 }
628 }
629 return unusedPoints->end();
630}
631
632// Find the shortest reconnect between the given points
633
634bool FindShortestReconnect(std::vector<OrderingSegment> &segments, std::vector<OrderingGroupConnection *> &connections, std::vector<OrderingGroupConnection *> &allconnections, OrderingGroupConnection **longestConnect, Coord *total, Coord olddist)
635{
636 // Find the longest connection outside of the active set
637 // The longest segment is then the longest of this longest outside segment and all inside segments
638 OrderingGroupConnection *longestOutside = 0;
639
640 if (contains(connections, *longestConnect)) {
641 // The longest connection is inside the active set, so we need to search for the longest outside
642 Coord length = 0.0;
643 for (std::vector<OrderingGroupConnection *>::iterator it = allconnections.begin(); it != allconnections.end(); it++) {
644 if ((*it)->Distance() > length) {
645 if (!contains(connections, *it)) {
646 longestOutside = *it;
647 length = (*it)->Distance();
648 }
649 }
650 }
651 } else {
652 longestOutside = *longestConnect;
653 }
654
655 // length of longestConnect outside
656 Coord longestOutsideLength = longestOutside ? longestOutside->Distance() : 0.0;
657
658 // We measure length without the longest, so subtract the longest length from the old distance
659 olddist -= (*longestConnect)->Distance();
660
661 // Assign a swap bit and end bit to each active connection
662 int nEndBits = 0;
663 int nSwapBits = 0;
664 for (std::vector<OrderingSegment>::iterator it = segments.begin(); it != segments.end(); it++) {
665 it->endbit = nEndBits++;
666 if (it->nEndPoints == 4) {
667 it->swapbit = nSwapBits++;
668 } else {
669 // bit 32 should always be 0
670 it->swapbit = 31;
671 }
672 }
673
674 unsigned int swapMask = (1U << nSwapBits) - 1;
675 unsigned int endMask = (1U << nEndBits) - 1;
676
677 // Create a permutation vector
678 std::vector<int> permutation(segments.size());
679 fill_increasing(permutation.begin(), permutation.end(), 0);
680
681 // best improvement
682 bool improved = false;
683 Coord distBest = olddist;
684 std::vector<int> permutationBest;
685 unsigned int iSwapBest;
686 unsigned int iEndBest;
687 int nTrials = 0;
688
689 // Loop over the permutations
690 do {
691 // Loop over the swap bits
692 unsigned int iSwap = 0;
693 do {
694 // Loop over the end bits
695 unsigned int iEnd = 0;
696 do {
697 // Length of all active connections
698 Coord lengthTotal = 0;
699 // Length of longest connection (active or inactive)
700 Coord lengthLongest = longestOutsideLength;
701
702 // Close the loop with the end point of the last segment
703 OrderingGroupPoint *prevend = segments[permutation.back()].GetEndPoint(iSwap, iEnd);
704 for (std::vector<int>::iterator it = permutation.begin(); it != permutation.end(); it++) {
705 OrderingGroupPoint *thisbeg = segments[*it].GetBeginPoint(iSwap, iEnd);
706 Coord length = Geom::distance(thisbeg->point, prevend->point);
707 lengthTotal += length;
708 if (length > lengthLongest) {
709 lengthLongest = length;
710 }
711 prevend = segments[*it].GetEndPoint(iSwap, iEnd);
712 }
713 lengthTotal -= lengthLongest;
714
715 // If there is an improvement, remember the best selection
716 if (lengthTotal + 1e-6 < distBest) {
717 improved = true;
718 distBest = lengthTotal;
719 permutationBest = permutation;
720 iSwapBest = iSwap;
721 iEndBest = iEnd;
722
723 // Just debug printing
724 OrderingGroupPoint *prevend = segments[permutation.back()].GetEndPoint(iSwap, iEnd);
725 for (std::vector<int>::iterator it = permutation.begin(); it != permutation.end(); it++) {
726 OrderingGroupPoint *thisbeg = segments[*it].GetBeginPoint(iSwap, iEnd);
727 DebugTrace2TSP(("IMP 0F=%d %d %.6lf", thisbeg->group->index, thisbeg->indexInGroup, Geom::distance(thisbeg->point, prevend->point)));
728 DebugTrace2TSP(("IMP 0T=%d %d %.6lf", prevend->group->index, prevend->indexInGroup, Geom::distance(thisbeg->point, prevend->point)));
729 prevend = segments[*it].GetEndPoint(iSwap, iEnd);
730 }
731 }
732
733 nTrials++;
734
735 // bit 0 is always 0, because the first segment is kept fixed
736 iEnd += 2;
737 } while (iEnd & endMask);
738 iSwap++;
739 } while (iSwap & swapMask);
740 // first segment is kept fixed
741 } while (std::next_permutation(permutation.begin() + 1, permutation.end()));
742
743 if (improved) {
744 DebugTrace2TSP(("Improvement %lf->%lf in %d", olddist, distBest, nTrials));
745 // change the connections
746
747 for (std::vector<OrderingGroupConnection *>::iterator it = connections.begin(); it != connections.end(); ++it) {
748 DebugTrace2TSP(("WAS 0F=%d %d %.6lf", (*it)->points[0]->group->index, (*it)->points[0]->indexInGroup, (*it)->Distance()));
749 DebugTrace2TSP(("WAS 0T=%d %d %.6lf", (*it)->points[1]->group->index, (*it)->points[1]->indexInGroup, (*it)->Distance()));
750 }
751 DebugTrace2TSP(("OLDDIST %.6lf delta %.6lf", olddist, olddist - (*longestConnect)->Distance()));
752 DebugTrace2TSP(("LONG =%d %d %.6lf", (*longestConnect)->points[0]->group->index, (*longestConnect)->points[0]->indexInGroup, (*longestConnect)->Distance()));
753 DebugTrace2TSP(("LONG =%d %d %.6lf", (*longestConnect)->points[1]->group->index, (*longestConnect)->points[1]->indexInGroup, (*longestConnect)->Distance()));
754
755 int perm = permutationBest.back();
756
757 for (std::vector<OrderingGroupConnection *>::iterator it = connections.begin(); it != connections.end(); ++it) {
758 (*it)->Connect(1, segments[ perm ].GetEndPoint(iSwapBest, iEndBest));
759 perm = permutationBest[ it - connections.begin() ];
760 (*it)->Connect(0, segments[ perm ].GetBeginPoint(iSwapBest, iEndBest));
761
762 }
763
764 for (std::vector<OrderingGroupConnection *>::iterator it = connections.begin(); it != connections.end(); ++it) {
765 DebugTrace2TSP(("IS 0F=%d %d %.6lf", (*it)->points[0]->group->index, (*it)->points[0]->indexInGroup, (*it)->Distance()));
766 DebugTrace2TSP(("IS 0T=%d %d %.6lf", (*it)->points[1]->group->index, (*it)->points[1]->indexInGroup, (*it)->Distance()));
767 }
768
769 (*longestConnect) = longestOutside;
770 for (std::vector<OrderingGroupConnection *>::iterator it = connections.begin(); it != connections.end(); ++it) {
771 if ((*it)->Distance() > (*longestConnect)->Distance()) {
772 *longestConnect = *it;
773 }
774 }
775 DebugTrace2TSP(("LONG =%d %d %.6lf", (*longestConnect)->points[0]->group->index, (*longestConnect)->points[0]->indexInGroup, (*longestConnect)->Distance()));
776 DebugTrace2TSP(("LONG =%d %d %.6lf", (*longestConnect)->points[1]->group->index, (*longestConnect)->points[1]->indexInGroup, (*longestConnect)->Distance()));
777 }
778
779 return improved;
780}
781
782// Check if connections form a tour
783void AssertIsTour(std::vector<OrderingGroup *> &groups, std::vector<OrderingGroupConnection *> &connections, OrderingGroupConnection *longestConnection)
784{
785 for (std::vector<OrderingGroupConnection *>::iterator it = connections.begin(); it != connections.end(); it++) {
786 for (int i = 0; i < 2; i++) {
787 OrderingGroupPoint *pnt = (*it)->points[i];
788 assert(pnt->connection == *it);
789 assert(pnt->connection->points[pnt->indexInConnection] == pnt);
790 assert(pnt->group->endpoints[pnt->indexInGroup] == pnt);
791 }
792 }
793
794 Coord length1 = 0;
795 Coord longest1 = 0;
796 OrderingGroupPoint *current = connections.front()->points[0];
797
798 for (unsigned int n = 0; n < connections.size(); n++) {
799 DebugTrace2TSP(("Tour test 1 %p g=%d/%d c=%d/%d %p %p %.6lf %.3lf %.3lf %d %d %d", current, current->group->index, current->indexInGroup, current->connection->index, current->indexInConnection, current->connection->points[0], current->connection->points[1], current->connection->Distance(), current->point.x(), 297 - current->point.y(), current->begin, current->front, current->group->items.size()));
800 Coord length = current->connection->Distance();
801 length1 += length;
802 longest1 = std::max(length, longest1);
803 current = current->GetOtherEndConnection();
804
805 DebugTrace2TSP(("Tour test 2 %p g=%d/%d c=%d/%d %p %p %.6lf %.3lf %.3lf %d %d %d", current, current->group->index, current->indexInGroup, current->connection->index, current->indexInConnection, current->connection->points[0], current->connection->points[1], current->connection->Distance(), current->point.x(), 297 - current->point.y(), current->begin, current->front, current->group->items.size()));
806 current = current->GetOtherEndGroup();
807 }
808 DebugTrace2TSP(("Tour test 3 %p g=%d/%d c=%d/%d %p %p", current, current->group->index, current->indexInGroup, current->connection->index, current->indexInConnection, current->connection->points[0], current->connection->points[1]));
809 assert(current == connections.front()->points[0]);
810
811 // The other direction
812 Coord length2 = 0;
813 Coord longest2 = 0;
814 current = connections.front()->points[0];
815 for (unsigned int n = 0; n < connections.size(); n++) {
816 current = current->GetOtherEndGroup();
817 Coord length = current->connection->Distance();
818 length2 += length;
819 longest2 = std::max(length, longest2);
820 current = current->GetOtherEndConnection();
821 }
822 assert(current == connections.front()->points[0]);
823
824 DebugTrace1TSP(("Tour length %.6lf(%.6lf) longest %.6lf(%.6lf) remaining %.6lf(%.6lf)", length1, length2, longest1, longest2, length1 - longest1, length2 - longest2));
825}
826
827// Bring a tour into linear order after a modification
828
829/* I would like to avoid this.
830 * It is no problem to travel a tour with changing directions using the GetOtherEnd functions,
831 * but it is difficult to know the segments, that is which endpoint of a connection is connected to which by the unmodified pieces of the tour.
832 * In the end it is probably better to implement the Lin-Kernighan algorithm which avoids this problem by creating connected changes. */
833
834void LinearizeTour(std::vector<OrderingGroupConnection *> &connections)
835{
836 OrderingGroupPoint *current = connections.front()->points[0];
837
838 for (unsigned int iNew = 0; iNew < connections.size(); iNew++) {
839 // swap the connection at location n with the current connection
840 OrderingGroupConnection *connection = current->connection;
841 unsigned int iOld = connection->index;
842 assert(connections[iOld] == connection);
843
844 connections[iOld] = connections[iNew];
845 connections[iNew] = connection;
846 connections[iOld]->index = iOld;
847 connections[iNew]->index = iNew;
848
849 // swap the points of a connection
850 assert(current == connection->points[0] || current == connection->points[1]);
851 if (current != connection->points[0]) {
852 connection->points[1] = connection->points[0];
853 connection->points[0] = current;
854 connection->points[1]->indexInConnection = 1;
855 connection->points[0]->indexInConnection = 0;
856 }
857
858 current = current->GetOtherEndConnection();
859 current = current->GetOtherEndGroup();
860 }
861}
862
863// Use some Traveling Salesman Problem (TSP) like heuristics to bring several groups into a
864// order with as short as possible interconnection paths
865
866void OrderGroups(std::vector<OrderingGroup *> *groups, const int nDims)
867{
868 // There is no point in ordering just one group
869 if (groups->size() <= 1) {
870 return;
871 }
872
873 // Initialize the endpoints for all groups
874 for (std::vector<OrderingGroup *>::iterator it = groups->begin(); it != groups->end(); ++it) {
875 (*it)->SetEndpoints();
876 }
877
878 // Find the neighboring points for all end points of all groups and sort by distance
879 for (std::vector<OrderingGroup *>::iterator itThis = groups->begin(); itThis != groups->end(); ++itThis) {
880 for (int i = 0; i < (*itThis)->nEndPoints; i++) {
881 // This can be up to 2x too large, but still better than incrementing the size
882 (*itThis)->endpoints[i]->nearest.reserve(4 * groups->size());
883 }
884
885 for (std::vector<OrderingGroup *>::iterator itNghb = groups->begin(); itNghb != groups->end(); ++itNghb) {
886 if (itThis != itNghb) {
887 (*itThis)->AddNeighbors(*itNghb);
888 }
889 }
890
891 for (int i = 0; i < (*itThis)->nEndPoints; i++) {
892 std::sort((*itThis)->endpoints[i]->nearest.begin(), (*itThis)->endpoints[i]->nearest.end(), OrderingGroupNeighbor::Compare);
893 }
894 }
895
896 // =========== Step 1: Create a simple nearest neighbor chain ===========
897
898 // Vector of connection points
899 std::vector<OrderingGroupConnection *> connections;
900 connections.reserve(groups->size());
901 // Total Jump Distance
902 Coord total = 0.0;
903
904 // Start with the first group and connect always with nearest unused point
905 OrderingGroupPoint *crnt = groups->front()->endpoints[0];
906
907 // The longest connection is ignored (we don't want cycles)
908 OrderingGroupConnection *longestConnect = 0;
909
910 for (unsigned int nConnected = 0; nConnected < groups->size(); nConnected++) {
911 // Mark both end points of the current segment as used
912 crnt->UsePoint();
913 crnt = crnt->GetOtherEndGroup();
914 crnt->UsePoint();
915
916 // if this is the last segment, Mark start point of first segment as unused,
917 // so that the end can connect to it
918 if (nConnected == groups->size() - 1) {
919 groups->front()->endpoints[0]->UnusePoint();
920 }
921
922 // connect to next segment
923 OrderingGroupNeighbor *nghb = crnt->FindNearestUnused();
924 connections.push_back(new OrderingGroupConnection(crnt, nghb->point, connections.size()));
925 total += nghb->distance;
926 crnt = nghb->point;
927
928 if (!longestConnect || nghb->distance > longestConnect->Distance()) {
929 longestConnect = connections.back();
930 }
931 }
932
933 DebugTrace1TSP(("Total jump distance %.3lf (closed)", total));
934 DebugTrace1TSP(("Total jump distance %.3lf (open)", total - longestConnect->Distance()));
935
936 AssertIsTour(*groups, connections, longestConnect);
937
938 // =========== Step 2: Choose nDims segments to clear and reconnect ===========
939
940 bool improvement;
941 int nRuns = 0;
942 int nTrials = 0;
943 int nImprovements = 0;
944
945 do {
946 improvement = false;
947 nRuns ++;
948 std::vector< std::vector<OrderingGroupConnection *>::iterator > iterators;
949
950 for (
951 triangleit_begin(iterators, connections.begin(), connections.end(), nDims);
952 triangleit_test(iterators, connections.end());
953 triangleit_incr(iterators, connections.end())
954 ) {
955 nTrials ++;
956
957 Coord dist = 0;
958
959 std::vector<OrderingSegment> segments(iterators.size());
960 std::vector<OrderingGroupConnection *> changedconnections;
961 changedconnections.reserve(3);
962 OrderingGroupConnection *prev = *iterators.back();
963
964
965 for (size_t i = 0; i < iterators.size(); i++) {
966 dist += (*iterators[i])->Distance();
967 segments[i].AddPoint(prev->points[1]);
968 segments[i].AddPoint((*iterators[i])->points[0]);
969 prev = *iterators[i];
970 changedconnections.push_back(*iterators[i]);
971 }
972
973 if (FindShortestReconnect(segments, changedconnections, connections, &longestConnect, &total, dist)) {
974 nImprovements ++;
975
976 AssertIsTour(*groups, connections, longestConnect);
977 LinearizeTour(connections);
978 AssertIsTour(*groups, connections, longestConnect);
979 improvement = true;
980 }
981 }
982 } while (improvement && nRuns < 10);
983
984 DebugTrace1TSP(("Finished after %d rounds, %d trials, %d improvements", nRuns, nTrials, nImprovements));
985
986 // =========== Step N: Create vector of groups from vector of connection points ===========
987
988 std::vector<OrderingGroup *> result;
989 result.reserve(groups->size());
990
991 // Go through the groups starting with the longest connection (which is this way left out)
992 {
993 OrderingGroupPoint *current = longestConnect->points[1];
994
995 for (unsigned int n = 0; n < connections.size(); n++) {
996 result.push_back(current->group);
997 current->SetRevInGroup();
998 current = current->GetOtherEndGroup();
999 current = current->GetOtherEndConnection();
1000 }
1001 }
1002
1003 assert(result.size() == groups->size());
1004 assert_unique(result);
1005
1006 delete_and_clear(connections);
1007
1008 *groups = result;
1009}
1010
1011// Global optimization of path length
1012
1013void OrderingAdvanced(std::vector<OrderingInfo> &infos, int nDims)
1014{
1015 if (infos.size() < 3) {
1016 return;
1017 }
1018
1019 // Create extended ordering info vector and copy data from normal ordering info
1020 std::vector<OrderingInfoEx *> infoex;
1021 infoex.reserve(infos.size());
1022
1023 for (std::vector<OrderingInfo>::const_iterator it = infos.begin(); it != infos.end();) {
1024 // Note: This assumes that the index in the OrderingInfo matches the vector index!
1025 infoex.push_back(new OrderingInfoEx(*it, infoex.size()));
1026 ++it;
1027 while (it != infos.end() && it->begOrig == infoex.back()->end.point) {
1028 infoex.back()->end.point = it->endOrig;
1029 infoex.back()->origIndices.push_back(it->index);
1030 ++it;
1031 }
1032 }
1033
1034 // Find closest 2 points for each point and enforce that 2nd nearest is not further away than 1.8xthe nearest
1035 // If this is not the case, clear nearest and 2nd nearest point
1036 for (std::vector<OrderingInfoEx *>::iterator it = infoex.begin(); it != infoex.end(); ++it) {
1037 (*it)->beg.FindNearest2(infoex);
1038 (*it)->end.FindNearest2(infoex);
1039 }
1040
1041 DebugTraceGrouping(
1042 DebugTrace2(("STEP1"));
1043 for (std::vector<OrderingInfoEx *>::iterator it = infoex.begin(); it != infoex.end(); ++it) {
1044 (*it)->beg.Dump();
1045 (*it)->end.Dump();
1046 }
1047 )
1048
1049 // Make sure the nearest points are mutual
1050 for (std::vector<OrderingInfoEx *>::iterator it = infoex.begin(); it != infoex.end(); ++it) {
1051 (*it)->beg.EnforceMutual();
1052 (*it)->end.EnforceMutual();
1053 }
1054
1055 DebugTraceGrouping(
1056 DebugTrace2(("STEP2"));
1057 for (std::vector<OrderingInfoEx *>::iterator it = infoex.begin(); it != infoex.end(); ++it) {
1058 (*it)->beg.Dump();
1059 (*it)->end.Dump();
1060 }
1061 )
1062
1063 // Make sure the nearest points for begin and end lead to the same sub-path (same index)
1064 for (std::vector<OrderingInfoEx *>::iterator it = infoex.begin(); it != infoex.end(); ++it) {
1065 (*it)->beg.EnforceSymmetric((*it)->end);
1066 (*it)->end.EnforceSymmetric((*it)->beg);
1067 }
1068
1069 DebugTraceGrouping(
1070 DebugTrace2(("STEP3"));
1071 for (std::vector<OrderingInfoEx *>::iterator it = infoex.begin(); it != infoex.end(); ++it) {
1072 (*it)->beg.Dump();
1073 (*it)->end.Dump();
1074 }
1075 )
1076
1077 // The remaining nearest neighbors should be 100% non ambiguous, so group them
1078 std::vector<OrderingGroup *> groups;
1079 for (std::vector<OrderingInfoEx *>::iterator it = infoex.begin(); it != infoex.end(); ++it) {
1080 (*it)->MakeGroup(infoex, &groups);
1081 }
1082
1083 // Create single groups for ungrouped lines
1084 std::vector<OrderingInfo> result;
1085 result.reserve(infos.size());
1086 int nUngrouped = 0;
1087 for (std::vector<OrderingInfoEx *>::iterator it = infoex.begin(); it != infoex.end(); ++it) {
1088 if (!(*it)->grouped) {
1089 groups.push_back(new OrderingGroup(groups.size()));
1090 groups.back()->items.push_back(*it);
1091 nUngrouped++;
1092 }
1093 }
1094
1095 DebugTraceGrouping(
1096 DebugTrace2(("Ungrouped lines = %d", nUngrouped));
1097 DebugTrace2(("%d Groups found", groups.size()));
1098 for (std::vector<OrderingGroup *>::iterator it = groups.begin(); it != groups.end(); ++it) {
1099 DebugTrace2(("Group size %d", (*it)->items.size()));
1100 }
1101 )
1102
1103 // Order groups, so that the connection path gets shortest
1104 OrderGroups(&groups, nDims);
1105
1106 // Copy grouped lines to output
1107 for (std::vector<OrderingGroup *>::iterator itGroup = groups.begin(); itGroup != groups.end(); ++itGroup) {
1108 for (unsigned int iItem = 0; iItem < (*itGroup)->items.size(); iItem++) {
1109 unsigned int iItemRev = (*itGroup)->revItemList ? (*itGroup)->items.size() - 1 - iItem : iItem;
1110 OrderingInfoEx *item = (*itGroup)->items[iItemRev];
1111
1112 // If revItems is false, even items shall have reverse=false
1113 // In this case ( ( iItem & 1 ) == 0 )== true, revItems=false, (true==false) == false
1114 bool reverse = ((iItem & 1) == 0) == (*itGroup)->revItems;
1115 if (!reverse) {
1116 for (std::vector<int>::iterator itOrig = item->origIndices.begin(); itOrig != item->origIndices.end(); ++itOrig) {
1117 result.push_back(infos[*itOrig]);
1118 result.back().reverse = false;
1119 }
1120 } else {
1121 for (std::vector<int>::reverse_iterator itOrig = item->origIndices.rbegin(); itOrig != item->origIndices.rend(); ++itOrig) {
1122 result.push_back(infos[*itOrig]);
1123 result.back().reverse = true;
1124 }
1125 }
1126 }
1127 result.back().connect = true;
1128 }
1129
1130
1131 delete_and_clear(groups);
1132 delete_and_clear(infoex);
1133
1134 infos = result;
1135}
1136
1137} // namespace LPEEmbroderyStitchOrdering
1138} // namespace LivePathEffect
1139} // namespace Inkscape
01140
=== added file 'src/live_effects/lpe-embrodery-stitch-ordering.h'
--- src/live_effects/lpe-embrodery-stitch-ordering.h 1970-01-01 00:00:00 +0000
+++ src/live_effects/lpe-embrodery-stitch-ordering.h 2017-06-05 19:01:49 +0000
@@ -0,0 +1,313 @@
1/*
2 * Sub-path Ordering functions for embroidery stitch LPE
3 *
4 * Copyright (C) 2016 Michael Soegtrop
5 *
6 * Released under GNU GPL, read the file 'COPYING' for more information
7 */
8
9#ifndef INKSCAPE_LPE_EMBRODERY_STITCH_ORDERING_H
10#define INKSCAPE_LPE_EMBRODERY_STITCH_ORDERING_H
11
12#include "live_effects/effect.h"
13
14namespace Inkscape {
15namespace LivePathEffect {
16namespace LPEEmbroderyStitchOrdering {
17
18// Structure keeping information on the ordering and reversing of sub paths
19// Used for simple ordering functions like zig-zag
20
21struct OrderingInfo {
22 int index;
23 bool reverse;
24 bool used;
25 bool connect;
26 Geom::Point begOrig; // begin point in original orientation
27 Geom::Point endOrig; // end point in original orientation
28
29 Geom::Point GetBegOrig() const
30 {
31 return begOrig;
32 }
33 Geom::Point GetEndOrig() const
34 {
35 return endOrig;
36 }
37 Geom::Point GetBegRev() const
38 {
39 return reverse ? endOrig : begOrig;
40 }
41 Geom::Point GetEndRev() const
42 {
43 return reverse ? begOrig : endOrig;
44 }
45};
46
47// Structure for a path end-point in OrderingInfoEx.
48// This keeps information about the two nearest neighbor points.
49
50struct OrderingInfoEx;
51
52struct OrderingPoint {
53 OrderingPoint(const Geom::Point &pointIn, OrderingInfoEx *infoexIn, bool beginIn) :
54 point(pointIn),
55 infoex(infoexIn),
56 begin(beginIn)
57 {
58 nearest[0] = nearest[1] = 0;
59 }
60
61 // Check if both nearest values are valid
62 bool IsNearestValid() const
63 {
64 return nearest[0] && nearest[1];
65 }
66 // Check if at least one nearest values are valid
67 bool HasNearest() const
68 {
69 return nearest[0] || nearest[1];
70 }
71 // Find 2 nearest points to given point
72 void FindNearest2(const std::vector<OrderingInfoEx *> &infos);
73 // Check if "this" is among the nearest of its nearest
74 void EnforceMutual(void);
75 // Check if the subpath indices of this and other are the same, otherwise zero both nearest
76 void EnforceSymmetric(const OrderingPoint &other);
77 // Dump point information
78 void Dump(void);
79
80 Geom::Point point;
81 OrderingInfoEx *infoex;
82 bool begin;
83 const OrderingPoint *nearest[2];
84};
85
86// Structure keeping information on the ordering and reversing of sub paths
87// Used for advanced ordering functions with block creation and Traveling Salesman Problem Optimization
88// A OrderingInfoEx may contain several original sub-paths in case sub-paths are directly connected.
89// Directly connected sub-paths happen e.g. after a slice boolean operation.
90
91struct OrderingGroup;
92
93struct OrderingInfoEx {
94 OrderingInfoEx(const OrderingInfo &infoIn, int idxIn) :
95 beg(infoIn.begOrig, this, true),
96 end(infoIn.endOrig, this, false),
97 idx(idxIn),
98 grouped(false)
99 {
100 origIndices.push_back(infoIn.index);
101 }
102
103 // If this element can be grouped (has neighbours) but is not yet grouped, create a new group
104 void MakeGroup(std::vector<OrderingInfoEx *> &infos, std::vector<OrderingGroup *> *groups);
105 // Add this and all connected elements to the group
106 void AddToGroup(std::vector<OrderingInfoEx *> &infos, OrderingGroup *group);
107
108 int idx;
109 bool grouped; // true if this element has been grouped
110 OrderingPoint beg; // begin point in original orientation
111 OrderingPoint end; // end point in original orientation
112 std::vector<int> origIndices; // Indices of the original OrderInfos (more than 1 if directly connected
113};
114
115// Neighbor information for OrderingGroupPoint - a distance and a OrderingGroupPoint
116
117struct OrderingGroupPoint;
118
119struct OrderingGroupNeighbor {
120 OrderingGroupNeighbor(OrderingGroupPoint *me, OrderingGroupPoint *other);
121
122 // Distance from owner of this neighbor info
123 Geom::Coord distance;
124 // Neighbor point (which in turn contains a pointer to the neighbor group
125 OrderingGroupPoint *point;
126
127 // Comparison function for sorting by distance
128 static bool Compare(const OrderingGroupNeighbor &a, const OrderingGroupNeighbor &b);
129};
130
131// An end point in an OrderingGroup, with nearest neighbor information
132
133struct OrderingGroupConnection;
134
135struct OrderingGroupPoint {
136 OrderingGroupPoint(const Geom::Point &pointIn, OrderingGroup *groupIn, int indexIn, bool beginIn, bool frontIn) :
137 point(pointIn),
138 group(groupIn),
139 indexInGroup(indexIn),
140 connection(0),
141 indexInConnection(0),
142 begin(beginIn),
143 front(frontIn),
144 used(false)
145 {
146 }
147
148 // Find the nearest unused neighbor point
149 OrderingGroupNeighbor *FindNearestUnused(void);
150 // Return the other end in the group of the point
151 OrderingGroupPoint *GetOtherEndGroup(void);
152 // Return the alternate point (if one exists), 0 otherwise
153 OrderingGroupPoint *GetAltPointGroup(void);
154 // Sets the rev flags in the group assuming that the group starts with this point
155 void SetRevInGroup(void);
156 // Mark an end point as used and also mark the two other alternating points as used
157 // Returns the used point
158 void UsePoint(void);
159 // Mark an end point as unused and possibly also mark the two other alternating points as unused
160 // Returns the used point
161 void UnusePoint(void);
162 // Return the other end in the connection
163 OrderingGroupPoint *GetOtherEndConnection(void);
164
165 // The coordinates of the point
166 Geom::Point point;
167 // The group to which the point belongs
168 OrderingGroup *group;
169 // The end-point index within the group
170 int indexInGroup;
171 // The connection, which connects this point
172 OrderingGroupConnection *connection;
173 // The end point index in the connection
174 int indexInConnection;
175 // True if this is a begin point (rather than an end point)
176 bool begin;
177 // True if this is a front point (rather than a back point)
178 bool front;
179 // True if the point is used/connected to another point
180 bool used;
181 // The nearest neighbors, to which this group end point may connect
182 std::vector<OrderingGroupNeighbor> nearest;
183};
184
185// A connection between two points/groups
186struct OrderingGroupConnection {
187 OrderingGroupConnection(OrderingGroupPoint *fromIn, OrderingGroupPoint *toIn, int indexIn) :
188 index(indexIn)
189 {
190 assert(fromIn->connection == 0);
191 assert(toIn->connection == 0);
192 points[0] = 0;
193 points[1] = 0;
194 Connect(0, fromIn);
195 Connect(1, toIn);
196 }
197
198 // Connect one of the conection endpoints to the given point
199 void Connect(int index, OrderingGroupPoint *point)
200 {
201 assert(point);
202 points[index] = point;
203 point->connection = this;
204 point->indexInConnection = index;
205 }
206
207 // Get length of connection
208 Geom::Coord Distance()
209 {
210 return Geom::distance(points[0]->point, points[1]->point);
211 }
212
213 OrderingGroupPoint *points[2];
214 // index of connection in the connections vector (just for debugging)
215 int index;
216};
217
218// A group of OrderingInfoEx, which build a block in path interconnect length optimization.
219// A block can have two sets of endpoints.
220// If a block has 2 sets of endpoints, one can swap between the two sets.
221
222struct OrderingGroup {
223 OrderingGroup(int indexIn) :
224 nEndPoints(0),
225 revItemList(false),
226 revItems(false),
227 index(indexIn)
228 {
229 for (int i = 0; i < sizeof(endpoints) / sizeof(*endpoints); i++) {
230 endpoints[i] = 0;
231 }
232 }
233
234 ~OrderingGroup()
235 {
236 for (int i = 0; i < nEndPoints; i++) {
237 delete endpoints[i];
238 }
239 }
240
241 // Set the endpoints of a group from the items
242 void SetEndpoints(void);
243 // Add all points from another group as neighbors
244 void AddNeighbors(OrderingGroup *nghb);
245 // Mark an end point as used and also mark the two other alternating points as used
246 // Returns the used point
247 OrderingGroupPoint *UsePoint(int index);
248 // Mark an end point as unused and possibly also mark the two other alternating points as unused
249 // Returns the used point
250 void UnusePoint(int index);
251
252 // Items on the group
253 std::vector<OrderingInfoEx *> items;
254 // End points of the group
255 OrderingGroupPoint *endpoints[4];
256 // Number of endpoints used (either 2 or 4)
257 int nEndPoints;
258 // Index of the group (just for debugging purposes)
259 int index;
260 // If true, the items in the group shall be output from back to front.
261 bool revItemList;
262 // If false, the individual items are output alternatingly normal-reversed
263 // If true, the individual items are output alternatingly reversed-normal
264 bool revItems;
265};
266
267// A segment is either a OrderingGroup or a series of groups and connections.
268// Usually a segment has just 2 end points.
269// If a segment is just one ordering group, it has the same number of end points as the ordering group
270// A main difference between a segment and a group is that the segment does not own the end points.
271
272struct OrderingSegment {
273 OrderingSegment() :
274 nEndPoints(0),
275 endbit(0),
276 swapbit(0)
277 {}
278
279 // Add an end point
280 void AddPoint(OrderingGroupPoint *point);
281 // Get begin point (taking swap and end bit into account
282 OrderingGroupPoint *GetBeginPoint(unsigned int iSwap, unsigned int iEnd);
283 // Get end point (taking swap and end bit into account
284 OrderingGroupPoint *GetEndPoint(unsigned int iSwap, unsigned int iEnd);
285
286 // End points of the group
287 OrderingGroupPoint *endpoints[4];
288 // Number of endpoints used (either 2 or 4)
289 int nEndPoints;
290 // bit index in the end counter
291 int endbit;
292 // bit index in the swap counter
293 int swapbit;
294};
295
296
297// Sub-path reordering: do nothing - keep original order
298void OrderingOriginal(std::vector<OrderingInfo> &infos);
299
300// Sub-path reordering: reverse every other sub path
301void OrderingZigZag(std::vector<OrderingInfo> &infos, bool revfirst);
302
303// Sub-path reordering: continue with the neartest start or end point of yet unused sub paths
304void OrderingClosest(std::vector<OrderingInfo> &infos, bool revfirst);
305
306// Global optimization of path length
307void OrderingAdvanced(std::vector<OrderingInfo> &infos, int nDims);
308
309} //LPEEmbroderyStitchOrdering
310} //namespace LivePathEffect
311} //namespace Inkscape
312
313#endif
0314
=== added file 'src/live_effects/lpe-embrodery-stitch.cpp'
--- src/live_effects/lpe-embrodery-stitch.cpp 1970-01-01 00:00:00 +0000
+++ src/live_effects/lpe-embrodery-stitch.cpp 2017-06-05 19:01:49 +0000
@@ -0,0 +1,400 @@
1/*
2 * Embroidery stitch live path effect (Implementation)
3 *
4 * Copyright (C) 2016 Michael Soegtrop
5 *
6 * Released under GNU GPL, read the file 'COPYING' for more information
7 */
8
9#include "ui/widget/scalar.h"
10#include <glibmm/i18n.h>
11
12#include "live_effects/lpe-embrodery-stitch.h"
13#include "live_effects/lpe-embrodery-stitch-ordering.h"
14
15#include <2geom/path.h>
16#include <2geom/piecewise.h>
17#include <2geom/sbasis.h>
18#include <2geom/sbasis-geometric.h>
19#include <2geom/bezier-to-sbasis.h>
20#include <2geom/sbasis-to-bezier.h>
21
22namespace Inkscape {
23namespace LivePathEffect {
24
25using namespace Geom;
26using namespace LPEEmbroderyStitchOrdering;
27
28static const Util::EnumData<LPEEmbroderyStitch::order_method> OrderMethodData[LPEEmbroderyStitch::order_method_count] = {
29 { LPEEmbroderyStitch::order_method_no_reorder, N_("no reordering"), "no-reorder" },
30 { LPEEmbroderyStitch::order_method_zigzag, N_("zig-zag"), "zig-zag" },
31 { LPEEmbroderyStitch::order_method_zigzag_rev_first, N_("zig-zag, reverse first"), "zig-zag-rev-first" },
32 { LPEEmbroderyStitch::order_method_closest, N_("closest"), "closest" },
33 { LPEEmbroderyStitch::order_method_closest_rev_first, N_("closest, reverse first"), "closest-rev-first" },
34 { LPEEmbroderyStitch::order_method_tsp_kopt_2, N_("traveling salesman 2-opt (fast, bad)"), "tsp-2opt" },
35 { LPEEmbroderyStitch::order_method_tsp_kopt_3, N_("traveling salesman 3-opt (fast, ok)"), "tsp-3opt" },
36 { LPEEmbroderyStitch::order_method_tsp_kopt_4, N_("traveling salesman 4-opt (seconds)"), "tsp-4opt" },
37 { LPEEmbroderyStitch::order_method_tsp_kopt_5, N_("traveling salesman 5-opt (miutes)"), "tsp-5opt" }
38};
39
40static const Util::EnumDataConverter<LPEEmbroderyStitch::order_method>
41OrderMethodConverter(OrderMethodData, sizeof(OrderMethodData) / sizeof(*OrderMethodData));
42
43static const Util::EnumData<LPEEmbroderyStitch::connect_method> ConnectMethodData[LPEEmbroderyStitch::connect_method_count] = {
44 { LPEEmbroderyStitch::connect_method_line, N_("straight line"), "line" },
45 { LPEEmbroderyStitch::connect_method_move_point_from, N_("move to begin"), "move-begin" },
46 { LPEEmbroderyStitch::connect_method_move_point_mid, N_("move to middle"), "move-middle" },
47 { LPEEmbroderyStitch::connect_method_move_point_to, N_("move to end"), "move-end" }
48};
49
50static const Util::EnumDataConverter<LPEEmbroderyStitch::connect_method>
51ConnectMethodConverter(ConnectMethodData, sizeof(ConnectMethodData) / sizeof(*ConnectMethodData));
52
53LPEEmbroderyStitch::LPEEmbroderyStitch(LivePathEffectObject *lpeobject) :
54 Effect(lpeobject),
55 ordering(_("Ordering method"), _("Method used to order sub paths"), "ordering", OrderMethodConverter, &wr, this, order_method_no_reorder),
56 connection(_("Connection method"), _("Method to connect end points of sub paths"), "connection", ConnectMethodConverter, &wr, this, connect_method_line),
57 stitch_length(_("Stitch length"), _("If not 0, linearize path with given step length"), "stitch-length", &wr, this, 10.0),
58 stitch_min_length(_("Minimum stitch length [%]"), _("Combine steps shorter than this [%]"), "stitch-min-length", &wr, this, 25.0),
59 stitch_pattern(_("Stitch pattern"), _("Select between different stitch patterns"), "stitch_pattern", &wr, this, 0),
60 show_stitches(_("Show stitches"), _("Show stitches as small gaps (just for inspection - don't use for output)"), "show-stitches", &wr, this, false),
61 show_stitch_gap(_("Show stitch gap"), _("Gap between stitches when showing stitches"), "show-stitch-gap", &wr, this, 0.5),
62 jump_if_longer(_("Jump if longer"), _("Jump connection if longer than"), "jump-if-longer", &wr, this, 100)
63{
64 registerParameter(dynamic_cast<Parameter *>(&ordering));
65 registerParameter(dynamic_cast<Parameter *>(&connection));
66 registerParameter(dynamic_cast<Parameter *>(&stitch_length));
67 registerParameter(dynamic_cast<Parameter *>(&stitch_min_length));
68 registerParameter(dynamic_cast<Parameter *>(&stitch_pattern));
69 registerParameter(dynamic_cast<Parameter *>(&show_stitches));
70 registerParameter(dynamic_cast<Parameter *>(&show_stitch_gap));
71 registerParameter(dynamic_cast<Parameter *>(&jump_if_longer));
72
73 stitch_length.param_set_digits(1);
74 stitch_length.param_set_range(1, 10000);
75 stitch_min_length.param_set_digits(1);
76 stitch_min_length.param_set_range(0, 100);
77 stitch_pattern.param_make_integer();
78 stitch_pattern.param_set_range(0, 2);
79 show_stitch_gap.param_set_range(0.001, 10);
80 jump_if_longer.param_set_range(0.0, 1000000);
81}
82
83LPEEmbroderyStitch::~LPEEmbroderyStitch()
84{
85
86}
87
88double LPEEmbroderyStitch::GetPatternInitialStep(int pattern, int line)
89{
90 switch (pattern) {
91 case 0:
92 return 0.0;
93
94 case 1:
95 switch (line % 4) {
96 case 0:
97 return 0.0;
98 case 1:
99 return 0.25;
100 case 2:
101 return 0.50;
102 case 3:
103 return 0.75;
104 }
105 return 0.0;
106
107 case 2:
108 switch (line % 4) {
109 case 0:
110 return 0.0;
111 case 1:
112 return 0.5;
113 case 2:
114 return 0.75;
115 case 3:
116 return 0.25;
117 }
118 return 0.0;
119
120 default:
121 return 0.0;
122 }
123
124}
125
126Point LPEEmbroderyStitch::GetStartPointInterpolAfterRev(std::vector<OrderingInfo> const &info, unsigned i)
127{
128 Point start_this = info[i].GetBegRev();
129
130 if (i == 0) {
131 return start_this;
132 }
133
134 if (!info[i - 1].connect) {
135 return start_this;
136 }
137
138 Point end_prev = info[i - 1].GetEndRev();
139
140 switch (connection.get_value()) {
141 case connect_method_line:
142 return start_this;
143 case connect_method_move_point_from:
144 return end_prev;
145 case connect_method_move_point_mid:
146 return 0.5 * start_this + 0.5 * end_prev;
147 case connect_method_move_point_to:
148 return start_this;
149 default:
150 return start_this;
151 }
152}
153Point LPEEmbroderyStitch::GetEndPointInterpolAfterRev(std::vector<OrderingInfo> const &info, unsigned i)
154{
155 Point end_this = info[i].GetEndRev();
156
157 if (i + 1 == info.size()) {
158 return end_this;
159 }
160
161 if (!info[i].connect) {
162 return end_this;
163 }
164
165 Point start_next = info[i + 1].GetBegRev();
166
167 switch (connection.get_value()) {
168 case connect_method_line:
169 return end_this;
170 case connect_method_move_point_from:
171 return end_this;
172 case connect_method_move_point_mid:
173 return 0.5 * start_next + 0.5 * end_this;
174 case connect_method_move_point_to:
175 return start_next;
176 default:
177 return end_this;
178 }
179}
180
181Point LPEEmbroderyStitch::GetStartPointInterpolBeforeRev(std::vector<OrderingInfo> const &info, unsigned i)
182{
183 if (info[i].reverse) {
184 return GetEndPointInterpolAfterRev(info, i);
185 } else {
186 return GetStartPointInterpolAfterRev(info, i);
187 }
188}
189
190Point LPEEmbroderyStitch::GetEndPointInterpolBeforeRev(std::vector<OrderingInfo> const &info, unsigned i)
191{
192 if (info[i].reverse) {
193 return GetStartPointInterpolAfterRev(info, i);
194 } else {
195 return GetEndPointInterpolAfterRev(info, i);
196 }
197}
198
199PathVector LPEEmbroderyStitch::doEffect_path(PathVector const &path_in)
200{
201 if (path_in.size() >= 2) {
202 PathVector path_out;
203
204 // Create vectors with start and end points
205 std::vector<OrderingInfo> orderinginfos(path_in.size());
206 // connect next path to this one
207 bool connect_with_previous = false;
208
209 for (PathVector::const_iterator it = path_in.begin(); it != path_in.end(); ++it) {
210 OrderingInfo &info = orderinginfos[ it - path_in.begin() ];
211 info.index = it - path_in.begin();
212 info.reverse = false;
213 info.used = false;
214 info.connect = true;
215 info.begOrig = it->front().initialPoint();
216 info.endOrig = it->back().finalPoint();
217 }
218
219 // Compute sub-path ordering
220 switch (ordering.get_value()) {
221 case order_method_no_reorder:
222 OrderingOriginal(orderinginfos);
223 break;
224
225 case order_method_zigzag:
226 OrderingZigZag(orderinginfos, false);
227 break;
228
229 case order_method_zigzag_rev_first:
230 OrderingZigZag(orderinginfos, true);
231 break;
232
233 case order_method_closest:
234 OrderingClosest(orderinginfos, false);
235 break;
236
237 case order_method_closest_rev_first:
238 OrderingClosest(orderinginfos, true);
239 break;
240
241 case order_method_tsp_kopt_2:
242 OrderingAdvanced(orderinginfos, 2);
243 break;
244
245 case order_method_tsp_kopt_3:
246 OrderingAdvanced(orderinginfos, 3);
247 break;
248
249 case order_method_tsp_kopt_4:
250 OrderingAdvanced(orderinginfos, 4);
251 break;
252
253 case order_method_tsp_kopt_5:
254 OrderingAdvanced(orderinginfos, 5);
255 break;
256
257 }
258
259 // Iterate over sub-paths in order found above
260 // Divide paths into stitches (currently always equidistant)
261 // Interpolate between neighboring paths, so that their ends coincide
262 for (std::vector<OrderingInfo>::const_iterator it = orderinginfos.begin(); it != orderinginfos.end(); ++it) {
263 // info index
264 unsigned iInfo = it - orderinginfos.begin();
265 // subpath index
266 unsigned iPath = it->index;
267 // decide of path shall be reversed
268 bool reverse = it->reverse;
269 // minimum stitch length in absolute measure
270 double stitch_min_length_abs = stitch_min_length * 0.01 * stitch_length;
271
272 // convert path to piecewise
273 Piecewise<D2<SBasis> > pwOrig = path_in[iPath].toPwSb();
274 // make piecewise equidistant in time
275 Piecewise<D2<SBasis> > pwEqdist = arc_length_parametrization(pwOrig);
276 Piecewise<D2<SBasis> > pwStitch;
277
278 // cut into stitches
279 double cutpos = 0.0;
280 Interval pwdomain = pwEqdist.domain();
281
282 // step length of first stitch
283 double step = GetPatternInitialStep(stitch_pattern, iInfo) * stitch_length;
284 if (step < stitch_min_length_abs) {
285 step += stitch_length;
286 }
287
288 bool last = false;
289 bool first = true;
290 double posnext;
291 for (double pos = pwdomain.min(); !last; pos = posnext, cutpos += 1.0) {
292 // start point
293 Point p1;
294 if (first) {
295 p1 = GetStartPointInterpolBeforeRev(orderinginfos, iInfo);
296 first = false;
297 } else {
298 p1 = pwEqdist.valueAt(pos);
299 }
300
301 // end point of this stitch
302 Point p2;
303 posnext = pos + step;
304 // last stitch is to end
305 if (posnext >= pwdomain.max() - stitch_min_length_abs) {
306 p2 = GetEndPointInterpolBeforeRev(orderinginfos, iInfo);
307 last = true;
308 } else {
309 p2 = pwEqdist.valueAt(posnext);
310 }
311
312 pwStitch.push_cut(cutpos);
313 pwStitch.push_seg(D2<SBasis>(SBasis(Linear(p1[X], p2[X])), SBasis(Linear(p1[Y], p2[Y]))));
314
315 // stitch length for all except first step
316 step = stitch_length;
317 }
318 pwStitch.push_cut(cutpos);
319
320 if (reverse) {
321 pwStitch = Geom::reverse(pwStitch);
322 }
323
324 if (it->connect && iInfo != orderinginfos.size() - 1) {
325 // Connect this segment with the previous segment by a straight line
326 Point end = pwStitch.lastValue();
327 Point start_next = GetStartPointInterpolAfterRev(orderinginfos, iInfo + 1);
328 // connect end and start point
329 if (end != start_next && distance(end, start_next) <= jump_if_longer) {
330 cutpos += 1.0;
331 pwStitch.push_seg(D2<SBasis>(SBasis(Linear(end[X], start_next[X])), SBasis(Linear(end[Y], start_next[Y]))));
332 pwStitch.push_cut(cutpos);
333 }
334 }
335
336 if (show_stitches) {
337 for (std::vector< D2<SBasis> >::iterator it = pwStitch.segs.begin(); it != pwStitch.segs.end(); ++it) {
338 // Create anew piecewise with just one segment
339 Piecewise<D2<SBasis> > pwOne;
340 pwOne.push_cut(0);
341 pwOne.push_seg(*it);
342 pwOne.push_cut(1);
343
344 // make piecewise equidistant in time
345 Piecewise<D2<SBasis> > pwOneEqdist = arc_length_parametrization(pwOne);
346 Interval pwdomain = pwOneEqdist.domain();
347
348 // Compute the points of teh shortened piece
349 Coord len = pwdomain.max() - pwdomain.min();
350 Coord offs = 0.5 * (show_stitch_gap < 0.5 * len ? show_stitch_gap : 0.5 * len);
351 Point p1 = pwOneEqdist.valueAt(pwdomain.min() + offs);
352 Point p2 = pwOneEqdist.valueAt(pwdomain.max() - offs);
353 Piecewise<D2<SBasis> > pwOneGap;
354
355 // Create Linear SBasis
356 D2<SBasis> sbasis = D2<SBasis>(SBasis(Linear(p1[X], p2[X])), SBasis(Linear(p1[Y], p2[Y])));
357
358 // Convert to path and add to path list
359 Geom::Path path = path_from_sbasis(sbasis , LPE_CONVERSION_TOLERANCE, false);
360 path_out.push_back(path);
361 }
362 } else {
363 PathVector pathv = path_from_piecewise(pwStitch, LPE_CONVERSION_TOLERANCE);
364 for (size_t ipv = 0; ipv < pathv.size(); ipv++) {
365 if (connect_with_previous) {
366 path_out.back().append(pathv[ipv]);
367 } else {
368 path_out.push_back(pathv[ipv]);
369 }
370 }
371 }
372
373 connect_with_previous = it->connect;
374 }
375
376 return path_out;
377 } else {
378 return path_in;
379 }
380}
381
382void
383LPEEmbroderyStitch::resetDefaults(SPItem const *item)
384{
385 Effect::resetDefaults(item);
386}
387
388
389/** /todo check whether this special case is necessary. It seems to "bug" editing behavior:
390 * scaling an object with transforms preserved behaves differently from scaling with
391 * transforms optimized (difference caused by this special method).
392 * special casing is probably needed, because rotation should not be propagated to the strokepath.
393 */
394void
395LPEEmbroderyStitch::transform_multiply(Affine const &postmul, bool set)
396{
397}
398
399} //namespace LivePathEffect
400} /* namespace Inkscape */
0401
=== added file 'src/live_effects/lpe-embrodery-stitch.h'
--- src/live_effects/lpe-embrodery-stitch.h 1970-01-01 00:00:00 +0000
+++ src/live_effects/lpe-embrodery-stitch.h 2017-06-05 19:01:49 +0000
@@ -0,0 +1,78 @@
1/*
2 * Embroidery stitch live path effect
3 *
4 * Copyright (C) 2016 Michael Soegtrop
5 *
6 * Released under GNU GPL, read the file 'COPYING' for more information
7 */
8
9#ifndef INKSCAPE_LPE_EMBRODERY_STITCH_H
10#define INKSCAPE_LPE_EMBRODERY_STITCH_H
11
12#include "live_effects/effect.h"
13#include "live_effects/parameter/parameter.h"
14#include "live_effects/parameter/bool.h"
15#include "live_effects/parameter/enum.h"
16#include "live_effects/lpe-embrodery-stitch-ordering.h"
17
18namespace Inkscape {
19namespace LivePathEffect {
20
21using namespace LPEEmbroderyStitchOrdering;
22
23class LPEEmbroderyStitch : public Effect {
24public:
25
26 LPEEmbroderyStitch(LivePathEffectObject *lpeobject);
27 virtual ~LPEEmbroderyStitch();
28
29 virtual Geom::PathVector doEffect_path(Geom::PathVector const &path_in);
30
31 virtual void resetDefaults(SPItem const *item);
32
33 virtual void transform_multiply(Geom::Affine const &postmul, bool set);
34
35 enum order_method {
36 order_method_no_reorder,
37 order_method_zigzag,
38 order_method_zigzag_rev_first,
39 order_method_closest,
40 order_method_closest_rev_first,
41 order_method_tsp_kopt_2,
42 order_method_tsp_kopt_3,
43 order_method_tsp_kopt_4,
44 order_method_tsp_kopt_5,
45 order_method_count
46 };
47 enum connect_method {
48 connect_method_line,
49 connect_method_move_point_from,
50 connect_method_move_point_mid,
51 connect_method_move_point_to,
52 connect_method_count
53 };
54
55private:
56 EnumParam<order_method> ordering;
57 EnumParam<connect_method> connection;
58 ScalarParam stitch_length;
59 ScalarParam stitch_min_length;
60 ScalarParam stitch_pattern;
61 BoolParam show_stitches;
62 ScalarParam show_stitch_gap;
63 ScalarParam jump_if_longer;
64
65 LPEEmbroderyStitch(const LPEEmbroderyStitch &);
66 LPEEmbroderyStitch &operator=(const LPEEmbroderyStitch &);
67
68 double GetPatternInitialStep(int pattern, int line);
69 Geom::Point GetStartPointInterpolAfterRev(std::vector<OrderingInfo> const &info, unsigned i);
70 Geom::Point GetEndPointInterpolAfterRev(std::vector<OrderingInfo> const &info, unsigned i);
71 Geom::Point GetStartPointInterpolBeforeRev(std::vector<OrderingInfo> const &info, unsigned i);
72 Geom::Point GetEndPointInterpolBeforeRev(std::vector<OrderingInfo> const &info, unsigned i);
73};
74
75} //namespace LivePathEffect
76} //namespace Inkscape
77
78#endif