Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
lpe-embrodery-stitch.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Embroidery stitch live path effect (Implementation)
4 *
5 * Copyright (C) 2016 Michael Soegtrop
6 *
7 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
8 */
9
10#include "ui/widget/scalar.h"
11#include <glibmm/i18n.h>
12
15
16#include <2geom/path.h>
17#include <2geom/piecewise.h>
18#include <2geom/sbasis.h>
22
23namespace Inkscape {
24namespace LivePathEffect {
25
26using namespace Geom;
27using namespace LPEEmbroderyStitchOrdering;
28
30 // clang-format off
31 { LPEEmbroderyStitch::order_method_no_reorder, N_("no reordering"), "no-reorder" },
32 { LPEEmbroderyStitch::order_method_zigzag, N_("zig-zag"), "zig-zag" },
33 { LPEEmbroderyStitch::order_method_zigzag_rev_first, N_("zig-zag, reverse first"), "zig-zag-rev-first" },
34 { LPEEmbroderyStitch::order_method_closest, N_("closest"), "closest" },
35 { LPEEmbroderyStitch::order_method_closest_rev_first, N_("closest, reverse first"), "closest-rev-first" },
36 { LPEEmbroderyStitch::order_method_tsp_kopt_2, N_("traveling salesman 2-opt (fast, bad)"), "tsp-2opt" },
37 { LPEEmbroderyStitch::order_method_tsp_kopt_3, N_("traveling salesman 3-opt (fast, ok)"), "tsp-3opt" },
38 { LPEEmbroderyStitch::order_method_tsp_kopt_4, N_("traveling salesman 4-opt (seconds)"), "tsp-4opt" },
39 { LPEEmbroderyStitch::order_method_tsp_kopt_5, N_("traveling salesman 5-opt (minutes)"), "tsp-5opt" }
40 // clang-format on
41};
42
45
52
55
57 Effect(lpeobject),
58 ordering(_("Ordering method"), _("Method used to order sub paths"), "ordering", OrderMethodConverter, &wr, this, order_method_no_reorder),
59 connection(_("Connection method"), _("Method to connect end points of sub paths"), "connection", ConnectMethodConverter, &wr, this, connect_method_line),
60 stitch_length(_("Stitch length"), _("Divide path into straight segments of given length (in user units)"), "stitch-length", &wr, this, 10.0),
61 stitch_min_length(_("Minimum stitch length [%]"), _("Merge stitches that are shorter than this percentage of the stitch length"), "stitch-min-length", &wr, this, 25.0),
62 stitch_pattern(_("Stitch pattern"), _("Select between different stitch patterns"), "stitch_pattern", &wr, this, 0),
63 show_stitches(_("Show stitches"), _("Creates gaps between stitches (use only for preview, deactivate for use with embroidery machines)"), "show-stitches", &wr, this, false),
64 show_stitch_gap(_("Show stitch gap"), _("Length of the gap between stitches when showing stitches"), "show-stitch-gap", &wr, this, 0.5),
65 jump_if_longer(_("Jump if longer"), _("Jump connection if longer than"), "jump-if-longer", &wr, this, 100)
66{
67 registerParameter(dynamic_cast<Parameter *>(&ordering));
68 registerParameter(dynamic_cast<Parameter *>(&connection));
69 registerParameter(dynamic_cast<Parameter *>(&stitch_length));
72 registerParameter(dynamic_cast<Parameter *>(&show_stitches));
75
83 jump_if_longer.param_set_range(0.0, 1000000);
84}
85
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()) {
142 return start_this;
144 return end_prev;
146 return 0.5 * start_this + 0.5 * end_prev;
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()) {
169 return end_this;
171 return end_this;
173 return 0.5 * start_next + 0.5 * end_this;
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
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->initialPoint();
216 info.endOrig = it->back().finalPoint();
217 }
218
219 // Compute sub-path ordering
220 switch (ordering.get_value()) {
222 OrderingOriginal(orderinginfos);
223 break;
224
226 OrderingZigZag(orderinginfos, false);
227 break;
228
230 OrderingZigZag(orderinginfos, true);
231 break;
232
234 OrderingClosest(orderinginfos, false);
235 break;
236
238 OrderingClosest(orderinginfos, true);
239 break;
240
242 OrderingAdvanced(orderinginfos, 2);
243 break;
244
246 OrderingAdvanced(orderinginfos, 3);
247 break;
248
250 OrderingAdvanced(orderinginfos, 4);
251 break;
252
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
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 (auto & seg : pwStitch.segs) {
338 // Create anew piecewise with just one segment
339 Piecewise<D2<SBasis> > pwOne;
340 pwOne.push_cut(0);
341 pwOne.push_seg(seg);
342 pwOne.push_cut(1);
343
344 // make piecewise equidistant in time
346 Interval pwdomain = pwOneEqdist.domain();
347
348 // Compute the points of the 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 (const auto & ipv : pathv) {
365 if (connect_with_previous) {
366 path_out.back().append(ipv);
367 } else {
368 path_out.push_back(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
382} //namespace LivePathEffect
383} /* namespace Inkscape */
Path - a sequence of contiguous curves.
Conversion between Bezier control points and SBasis curves.
Adaptor that creates 2D functions from 1D ones.
Definition d2.h:55
constexpr C min() const
constexpr C max() const
Range of real numbers that is never empty.
Definition interval.h:59
Function that interpolates linearly between two values.
Definition linear.h:55
Sequence of subpaths.
Definition pathvector.h:122
size_type size() const
Get the number of paths in the vector.
Definition pathvector.h:147
void push_back(Path const &path)
Append a path at the end.
Definition pathvector.h:172
Sequence::const_iterator const_iterator
Definition pathvector.h:127
iterator begin()
Definition pathvector.h:151
iterator end()
Definition pathvector.h:152
Sequence of contiguous curves, aka spline.
Definition path.h:353
void append(Curve *curve)
Add a new curve to the end of the path.
Definition path.h:750
Function defined as discrete pieces.
Definition piecewise.h:71
Interval domain() const
Definition piecewise.h:215
output_type valueAt(double t) const
Definition piecewise.h:102
void push_seg(const T &s)
Definition piecewise.h:157
void push_cut(double c)
Definition piecewise.h:152
output_type lastValue() const
Definition piecewise.h:109
std::vector< T > segs
Definition piecewise.h:76
Two-dimensional point that doubles as a vector.
Definition point.h:66
Polynomial in symmetric power basis.
Definition sbasis.h:70
void registerParameter(Parameter *param)
Definition effect.cpp:1704
Geom::Point GetStartPointInterpolAfterRev(std::vector< OrderingInfo > const &info, unsigned i)
Geom::Point GetEndPointInterpolAfterRev(std::vector< OrderingInfo > const &info, unsigned i)
LPEEmbroderyStitch(LivePathEffectObject *lpeobject)
Geom::Point GetStartPointInterpolBeforeRev(std::vector< OrderingInfo > const &info, unsigned i)
Geom::PathVector doEffect_path(Geom::PathVector const &path_in) override
Geom::Point GetEndPointInterpolBeforeRev(std::vector< OrderingInfo > const &info, unsigned i)
void param_set_digits(unsigned digits)
void param_set_range(double min, double max)
Simplified management of enumerations of svg items with UI labels.
Definition enums.h:42
double Coord
Floating point type used to store coordinates.
Definition coord.h:76
@ Y
Definition coord.h:48
@ X
Definition coord.h:48
Geom::Point end
Various utility functions.
Definition affine.h:22
Path path_from_sbasis(D2< SBasis > const &B, double tol, bool only_cubicbeziers=false)
Make a path from a d2 sbasis.
Bezier reverse(const Bezier &a)
Definition bezier.h:342
Angle distance(Angle const &a, Angle const &b)
Definition angle.h:163
PathVector path_from_piecewise(Piecewise< D2< SBasis > > const &B, double tol, bool only_cubicbeziers=false)
Make a path from a d2 sbasis.
Piecewise< D2< SBasis > > arc_length_parametrization(D2< SBasis > const &M, unsigned order=3, double tol=.01)
void OrderingClosest(std::vector< OrderingInfo > &infos, bool revfirst)
void OrderingZigZag(std::vector< OrderingInfo > &infos, bool revfirst)
void OrderingAdvanced(std::vector< OrderingInfo > &infos, int nDims)
static const Util::EnumData< LPEEmbroderyStitch::order_method > OrderMethodData[LPEEmbroderyStitch::order_method_count]
static const Util::EnumData< LPEEmbroderyStitch::connect_method > ConnectMethodData[LPEEmbroderyStitch::connect_method_count]
static const Util::EnumDataConverter< LPEEmbroderyStitch::connect_method > ConnectMethodConverter(ConnectMethodData, sizeof(ConnectMethodData)/sizeof(*ConnectMethodData))
static const Util::EnumDataConverter< LPEEmbroderyStitch::order_method > OrderMethodConverter(OrderMethodData, sizeof(OrderMethodData)/sizeof(*OrderMethodData))
Helper class to stream background task notifications as a series of messages.
Piecewise function class.
auto len
Definition safe-printf.h:21
two-dimensional geometric operators.
Conversion between SBasis and Bezier basis polynomials.
Polynomial in symmetric power basis (S-basis)
Simplified management of enumerations of svg items with UI labels.
Definition enums.h:27