Merge lp:~msoegtrop/inkscape/lpe-bool into lp:~inkscape.dev/inkscape/trunk
- lpe-bool
- Merge into trunk
Status: | Merged |
---|---|
Merge reported by: | Jabiertxof |
Merged at revision: | not available |
Proposed branch: | lp:~msoegtrop/inkscape/lpe-bool |
Merge into: | lp:~inkscape.dev/inkscape/trunk |
Diff against target: |
576 lines (+476/-3) 7 files modified
astylerc (+4/-3) po/POTFILES.in (+1/-0) src/live_effects/CMakeLists.txt (+2/-0) src/live_effects/effect-enum.h (+1/-0) src/live_effects/effect.cpp (+5/-0) src/live_effects/lpe-bool.cpp (+400/-0) src/live_effects/lpe-bool.h (+63/-0) |
To merge this branch: | bzr merge lp:~msoegtrop/inkscape/lpe-bool |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Jabiertxof | code + ux | Approve | |
Review via email: mp+294072@code.launchpad.net |
Commit message
Description of the change
Live Path Effect to perform boolean operations between two paths
- union
- intersection
- difference
- symmetric difference (xor)
- cut one path into pieces at the borders of the other path
- various slicing options intended for cutting lines and line groups (not closed paths) at a closed path.
Jabiertxof (jabiertxof) wrote : | # |
Michael Soegtrop (msoegtrop) wrote : | # |
Dear Jabier,
On 15.05.2016 11:44, Jabiertxof wrote:
>> + enum bool_op_ex
>> > + {
>> > + bool_op_ex_union = bool_op_union,
>> > + bool_op_ex_inters = bool_op_inters,
>> > + bool_op_ex_diff = bool_op_diff,
>> > + bool_op_ex_symdiff = bool_op_symdiff,
>> > + bool_op_ex_cut = bool_op_cut,
>> > + bool_op_ex_slice = bool_op_slice,
>> > + bool_op_
>> > + bool_op_
>> > + bool_op_
>> > + bool_op_
>> > + bool_op_
>> > + bool_op_ex_count
>> > + };
>> > +
> Dont undertand well. Why some bools are asigned and others no?
>
This is something like a derived enum. The values which are the same as
in the base enum have to be the same. The new values are arbitrary.
Another, usually more elegant, way of doing this is to create a
conversion operator from the base enum to the derived enum, but in this
case local arrays (for the UI selector) are indexed with the enum, so I
prefered to have the full enum here.
Best regards,
Michael
Jabiertxof (jabiertxof) wrote : | # |
Hi Michael, I aprove your branch.
Only some questions more. UX thing.
In the dropdown list of modes there is a list order alphabeticaly, in the middle there is your new boolops options about slicing, this make the lastest boolean opetation less visible, maybe is better get grouped all "usual" boolops operations and add to the button the slice ones, usualy less used I think.
Also maybe is a good thing the boolops operation have the same name than Boolops in menu and also beter display it in the same order than in the path menu.
Finaly the combo box become too big, I understand you want to explain it but if you reduce a bit the strings the full widget dont be resized from normal state. Maibe you can get some space, removing the label of the combo and puting it over.
Cheers, Jabier.
Michael Soegtrop (msoegtrop) wrote : | # |
Dear Jabier,
thanks for your comments! I see how I can improve this. The sorting in
the drop down list is automatic - I have to see how I can switch this off.
I think I could separate options of the slicer into separate check
boxes, which are only enabled if a slicer is selected.
Do you think it makes sense to add the slice inside/outside bool ops as
manual operations to the menu? I could do this easily, but then I would
like to move the bool op code code somewhere else.
Best regards,
Michael
On 18.05.2016 00:47, Jabiertxof wrote:
> Review: Approve code + ux
>
> Hi Michael, I aprove your branch.
> Only some questions more. UX thing.
>
> In the dropdown list of modes there is a list order alphabeticaly, in the middle there is your new boolops options about slicing, this make the lastest boolean opetation less visible, maybe is better get grouped all "usual" boolops operations and add to the button the slice ones, usualy less used I think.
>
> Also maybe is a good thing the boolops operation have the same name than Boolops in menu and also beter display it in the same order than in the path menu.
>
> Finaly the combo box become too big, I understand you want to explain it but if you reduce a bit the strings the full widget dont be resized from normal state. Maibe you can get some space, removing the label of the combo and puting it over.
>
> Cheers, Jabier.
--
=======
= Dipl. Phys. Michael Sögtrop
= Datenerfassungs- und Informationssysteme
= Steuerungs- und Automatisierung
=
= Anzinger Str. 10c
= 85586 Poing
=
= Tel.: (08121) 972433
= Fax.: (08121) 972434
=======
- 14878. By Michael Soegtrop
-
Fixed Bool LPE review issues
Michael Soegtrop (msoegtrop) wrote : | # |
Dear Jabier,
I made some changes and pushed it. What I did is:
- Fix the spacing (by running astyle over my new the files)
- On the way fixed the astylerc file (the file in bzr is for an old
version of astyle and doesn't match the info on the inkscape code style
web site)
- Factored out the "remover inner lines" option into an extra bool
option. This makes the drop down box as well as the code simpler.
- Adjusted naming to the menu (cut instead of slice and divide instead
of cut). I kept the internal names as they are, because this matches the
internal names of the bool ops.
Please note, that the saving format is slightly different, in case an
remover inner lines op was used. If you have saved files, you might need
to reselect the bool op after loading.
I didn't manage to change the sort order of the drop down. The
alphabetic sorting is automatic and I couldn't find a way to switch it off.
I am not sure what the next steps are. Should I resubmit it for merge?
Cheers,
Michael
On 18.05.2016 00:47, Jabiertxof wrote:
> Review: Approve code + ux
>
> Hi Michael, I aprove your branch.
> Only some questions more. UX thing.
>
> In the dropdown list of modes there is a list order alphabeticaly, in the middle there is your new boolops options about slicing, this make the lastest boolean opetation less visible, maybe is better get grouped all "usual" boolops operations and add to the button the slice ones, usualy less used I think.
>
> Also maybe is a good thing the boolops operation have the same name than Boolops in menu and also beter display it in the same order than in the path menu.
>
> Finaly the combo box become too big, I understand you want to explain it but if you reduce a bit the strings the full widget dont be resized from normal state. Maibe you can get some space, removing the label of the combo and puting it over.
>
> Cheers, Jabier.
- 14879. By Michael Soegtrop
-
updated to latest trunk
Jabiertxof (jabiertxof) wrote : | # |
Hi Michael, how are you, can I help you with the last feats i request you? forget them if is a problem, it can be added later once is merged.
Cheers, Jabier.
Michael Soegtrop (msoegtrop) wrote : | # |
Dear Jabier,
I was just busy with other things. I did a small experiment with
mbed.org and the BBC microbit and somehow couldn't stop messing around
with it. But this project is finished now - Inkscape is next. There is
no real problem with the task as such.
Best regards,
Michael
Jabiertxof (jabiertxof) wrote : | # |
Great, Best luky!
- 14880. By Michael Soegtrop
-
updated to latest trunk
- 14881. By Michael Soegtrop
-
fixed enum order + added cpp files to POFILES.in
Preview Diff
1 | === modified file 'astylerc' |
2 | --- astylerc 2013-09-01 23:39:00 +0000 |
3 | +++ astylerc 2017-06-05 19:02:59 +0000 |
4 | @@ -1,8 +1,9 @@ |
5 | # Inkscape coding style options for the Artistic Style formatter |
6 | # See http://astyle.sourceforge.net/ |
7 | +# See https://inkscape.org/de/develop/coding-style/ |
8 | |
9 | # Opening braces broken from functions only |
10 | ---brackets=stroustrup |
11 | +--style=stroustrup |
12 | |
13 | # Four-space indent; convert stray tabs |
14 | --indent=spaces=4 |
15 | @@ -15,7 +16,7 @@ |
16 | |
17 | # Attach pointers and references to variable names |
18 | --align-pointer=name |
19 | -# --align-reference=name |
20 | +--align-reference=name |
21 | |
22 | # Add brackets to single-statement blocks |
23 | --add-brackets |
24 | @@ -23,4 +24,4 @@ |
25 | # Misc. options |
26 | --indent-preprocessor |
27 | --indent-col1-comments |
28 | ---suffix=none |
29 | + |
30 | |
31 | === modified file 'po/POTFILES.in' |
32 | --- po/POTFILES.in 2017-05-08 01:43:41 +0000 |
33 | +++ po/POTFILES.in 2017-06-05 19:02:59 +0000 |
34 | @@ -119,6 +119,7 @@ |
35 | src/live_effects/effect.cpp |
36 | src/live_effects/lpe-attach-path.cpp |
37 | src/live_effects/lpe-bendpath.cpp |
38 | +src/live_effects/lpe-bool.cpp |
39 | src/live_effects/lpe-bounding-box.cpp |
40 | src/live_effects/lpe-bspline.cpp |
41 | src/live_effects/lpe-clone-original.cpp |
42 | |
43 | === modified file 'src/live_effects/CMakeLists.txt' |
44 | --- src/live_effects/CMakeLists.txt 2017-05-06 21:00:45 +0000 |
45 | +++ src/live_effects/CMakeLists.txt 2017-06-05 19:02:59 +0000 |
46 | @@ -53,6 +53,7 @@ |
47 | lpegroupbbox.cpp |
48 | lpeobject-reference.cpp |
49 | lpe-vonkoch.cpp |
50 | + lpe-bool.cpp |
51 | lpeobject.cpp |
52 | spiro-converters.cpp |
53 | spiro.cpp |
54 | @@ -136,6 +137,7 @@ |
55 | lpe-test-doEffect-stack.h |
56 | lpe-text_label.h |
57 | lpe-vonkoch.h |
58 | + lpe-bool.h |
59 | lpegroupbbox.h |
60 | lpeobject-reference.h |
61 | lpeobject.h |
62 | |
63 | === modified file 'src/live_effects/effect-enum.h' |
64 | --- src/live_effects/effect-enum.h 2017-05-07 00:23:42 +0000 |
65 | +++ src/live_effects/effect-enum.h 2017-06-05 19:02:59 +0000 |
66 | @@ -49,6 +49,7 @@ |
67 | BOUNDING_BOX, |
68 | MEASURE_LINE, |
69 | FILLET_CHAMFER, |
70 | + BOOL_OP, |
71 | DOEFFECTSTACK_TEST, |
72 | ANGLE_BISECTOR, |
73 | CIRCLE_WITH_RADIUS, |
74 | |
75 | === modified file 'src/live_effects/effect.cpp' |
76 | --- src/live_effects/effect.cpp 2017-05-29 00:09:29 +0000 |
77 | +++ src/live_effects/effect.cpp 2017-06-05 19:02:59 +0000 |
78 | @@ -62,6 +62,7 @@ |
79 | #include "live_effects/lpe-test-doEffect-stack.h" |
80 | #include "live_effects/lpe-text_label.h" |
81 | #include "live_effects/lpe-vonkoch.h" |
82 | +#include "live_effects/lpe-bool.h" |
83 | |
84 | #include "xml/node-event-vector.h" |
85 | #include "message-stack.h" |
86 | @@ -124,6 +125,7 @@ |
87 | /* 9.93 */ |
88 | {MEASURE_LINE, N_("Measure Line"), "measure_line"}, |
89 | {FILLET_CHAMFER, N_("Fillet/Chamfer"), "fillet_chamfer"}, |
90 | + {BOOL_OP, N_("Boolean operation"), "bool_op"}, |
91 | #ifdef LPE_ENABLE_TEST_EFFECTS |
92 | {DOEFFECTSTACK_TEST, N_("doEffect stack test"), "doeffectstacktest"}, |
93 | {ANGLE_BISECTOR, N_("Angle bisector"), "angle_bisector"}, |
94 | @@ -164,6 +166,9 @@ |
95 | { |
96 | Effect* neweffect = NULL; |
97 | switch (lpenr) { |
98 | + case BOOL_OP: |
99 | + neweffect = static_cast<Effect*> ( new LPEBool(lpeobj) ); |
100 | + break; |
101 | case PATTERN_ALONG_PATH: |
102 | neweffect = static_cast<Effect*> ( new LPEPatternAlongPath(lpeobj) ); |
103 | break; |
104 | |
105 | === added file 'src/live_effects/lpe-bool.cpp' |
106 | --- src/live_effects/lpe-bool.cpp 1970-01-01 00:00:00 +0000 |
107 | +++ src/live_effects/lpe-bool.cpp 2017-06-05 19:02:59 +0000 |
108 | @@ -0,0 +1,400 @@ |
109 | +/* |
110 | + * Boolean operation live path effect |
111 | + * |
112 | + * Copyright (C) 2016-2017 Michael Soegtrop |
113 | + * |
114 | + * Released under GNU GPL, read the file 'COPYING' for more information |
115 | + */ |
116 | + |
117 | +#include <glibmm/i18n.h> |
118 | +#include <math.h> |
119 | +#include <string.h> |
120 | +#include <algorithm> |
121 | + |
122 | +#include "live_effects/lpe-bool.h" |
123 | + |
124 | +#include "display/curve.h" |
125 | +#include "sp-item.h" |
126 | +#include "2geom/path.h" |
127 | +#include "sp-shape.h" |
128 | +#include "sp-text.h" |
129 | +#include "2geom/bezier-curve.h" |
130 | +#include "2geom/path-sink.h" |
131 | +#include "2geom/affine.h" |
132 | +#include "splivarot.h" |
133 | +#include "helper/geom.h" |
134 | +#include "livarot/Path.h" |
135 | +#include "livarot/Shape.h" |
136 | +#include "livarot/path-description.h" |
137 | +#include "2geom/svg-path-parser.h" |
138 | + |
139 | +namespace Inkscape { |
140 | +namespace LivePathEffect { |
141 | + |
142 | +// Define an extended boolean operation type |
143 | + |
144 | +static const Util::EnumData<LPEBool::bool_op_ex> BoolOpData[LPEBool::bool_op_ex_count] = { |
145 | + { LPEBool::bool_op_ex_union, N_("union"), "union" }, |
146 | + { LPEBool::bool_op_ex_inters, N_("intersection"), "inters" }, |
147 | + { LPEBool::bool_op_ex_diff, N_("difference"), "diff" }, |
148 | + { LPEBool::bool_op_ex_symdiff, N_("symmetric difference"), "symdiff" }, |
149 | + { LPEBool::bool_op_ex_cut, N_("division"), "cut" }, |
150 | + // Note on naming of operations: |
151 | + // bool_op_cut is called "Division" in the manu, see sp_selected_path_cut |
152 | + // bool_op_slice is called "Cut path" in the menu, see sp_selected_path_slice |
153 | + { LPEBool::bool_op_ex_slice, N_("cut"), "slice" }, |
154 | + { LPEBool::bool_op_ex_slice_inside, N_("cut inside"), "slice-inside" }, |
155 | + { LPEBool::bool_op_ex_slice_outside, N_("cut outside"), "slice-outside" }, |
156 | +}; |
157 | + |
158 | +static const Util::EnumDataConverter<LPEBool::bool_op_ex> BoolOpConverter(BoolOpData, sizeof(BoolOpData) / sizeof(*BoolOpData)); |
159 | + |
160 | +static const Util::EnumData<fill_typ> FillTypeData[] = { |
161 | + { fill_oddEven, N_("odd-even"), "oddeven" }, |
162 | + { fill_nonZero, N_("non-zero"), "nonzero" }, |
163 | + { fill_positive, N_("positive"), "positive" }, |
164 | + { fill_justDont, N_("from curve"), "from-curve" } |
165 | +}; |
166 | + |
167 | +static const Util::EnumDataConverter<fill_typ> FillTypeConverter(FillTypeData, sizeof(FillTypeData) / sizeof(*FillTypeData)); |
168 | + |
169 | +static const Util::EnumData<fill_typ> FillTypeDataThis[] = { |
170 | + { fill_oddEven, N_("odd-even"), "oddeven" }, |
171 | + { fill_nonZero, N_("non-zero"), "nonzero" }, |
172 | + { fill_positive, N_("positive"), "positive" } |
173 | +}; |
174 | + |
175 | +static const Util::EnumDataConverter<fill_typ> FillTypeConverterThis(FillTypeDataThis, sizeof(FillTypeDataThis) / sizeof(*FillTypeDataThis)); |
176 | + |
177 | +LPEBool::LPEBool(LivePathEffectObject *lpeobject) : |
178 | + Effect(lpeobject), |
179 | + operand_path(_("Operand path:"), _("Operand for the boolean operation"), "operand-path", &wr, this), |
180 | + bool_operation(_("Operation:"), _("Boolean Operation"), "operation", BoolOpConverter, &wr, this, bool_op_ex_union), |
181 | + swap_operands(_("Swap operands:"), _("Swap operands (useful e.g. for difference)"), "swap-operands", &wr, this), |
182 | + rmv_inner(_("Remove inner:"), _("For cut operations: remove inner (non-contour) lines of cutting path to avoid invisible extra points"), "rmv-inner", &wr, this), |
183 | + fill_type_this(_("Fill type this:"), _("Fill type (winding mode) for this path"), "filltype-this", FillTypeConverterThis, &wr, this, fill_oddEven), |
184 | + fill_type_operand(_("Fill type operand:"), _("Fill type (winding mode) for operand path"), "filltype-operand", FillTypeConverter, &wr, this, fill_justDont) |
185 | +{ |
186 | + registerParameter(&operand_path); |
187 | + registerParameter(&bool_operation); |
188 | + registerParameter(&swap_operands); |
189 | + registerParameter(&rmv_inner); |
190 | + registerParameter(&fill_type_this); |
191 | + registerParameter(&fill_type_operand); |
192 | + |
193 | + show_orig_path = true; |
194 | +} |
195 | + |
196 | +LPEBool::~LPEBool() |
197 | +{ |
198 | + |
199 | +} |
200 | + |
201 | +void LPEBool::resetDefaults(SPItem const * /*item*/) |
202 | +{ |
203 | +} |
204 | + |
205 | +bool cmp_cut_position(const Path::cut_position &a, const Path::cut_position &b) |
206 | +{ |
207 | + return a.piece == b.piece ? a.t < b.t : a.piece < b.piece; |
208 | +} |
209 | + |
210 | +Geom::PathVector |
211 | +sp_pathvector_boolop_slice_intersect(Geom::PathVector const &pathva, Geom::PathVector const &pathvb, bool inside, fill_typ fra, fill_typ frb) |
212 | +{ |
213 | + // This is similar to sp_pathvector_boolop/bool_op_slice, but keeps only edges inside the cutter area. |
214 | + // The code is also based on sp_pathvector_boolop_slice. |
215 | + // |
216 | + // We have two paths on input |
217 | + // - a closed area which is used to cut out pieces from a contour (called area below) |
218 | + // - a contour which is cut into pieces by the border of thr area (called contour below) |
219 | + // |
220 | + // The code below works in the following steps |
221 | + // (a) Convert the area to a shape, so that we can ask the winding number for any point |
222 | + // (b) Add both, the contour and the area to a single shape and intersect them |
223 | + // (c) Find the intersection points between area border and contour (vector toCut) |
224 | + // (d) Split the original contour at the intersection points |
225 | + // (e) check for each contour edge in combined shape if its center is inside the area - if not discard it |
226 | + // (f) create a vector of all inside edges |
227 | + // (g) convert the piece numbers to the piece numbers after applying the cuts |
228 | + // (h) fill a bool vector with information which pieces are in |
229 | + // (i) filter the descr_cmd of the result path with this bool vector |
230 | + // |
231 | + // The main inefficieny here is step (e) because I use a winding function of the area-shape which goes |
232 | + // through teh complete edge list for each point I ask for, so effort is n-edges-contour * n-edges-area. |
233 | + // It is tricky to improve this without building into the livarot code. |
234 | + // One way might be to decide at the intersection points which edges touching the intersection points are |
235 | + // in by making a loop through all edges on the intersection vertex. Since this is a directed non intersecting |
236 | + // graph, this should provide sufficient information. |
237 | + // But since I anyway will change this to the new mechanism some time speed is fairly ok, I didn't look into this. |
238 | + |
239 | + |
240 | + // extract the livarot Paths from the source objects |
241 | + // also get the winding rule specified in the style |
242 | + // Livarot's outline of arcs is broken. So convert the path to linear and cubics only, for which the outline is created correctly. |
243 | + Path *contour_path = Path_for_pathvector(pathv_to_linear_and_cubic_beziers(pathva)); |
244 | + Path *area_path = Path_for_pathvector(pathv_to_linear_and_cubic_beziers(pathvb)); |
245 | + |
246 | + // Shapes from above paths |
247 | + Shape *area_shape = new Shape; |
248 | + Shape *combined_shape = new Shape; |
249 | + Shape *combined_inters = new Shape; |
250 | + |
251 | + // Add the area (process to intersection free shape) |
252 | + area_path->ConvertWithBackData(1.0); |
253 | + area_path->Fill(combined_shape, 1); |
254 | + |
255 | + // Convert this to a shape with full winding information |
256 | + area_shape->ConvertToShape(combined_shape, frb); |
257 | + |
258 | + // Add the contour to the combined path (just add, no winding processing) |
259 | + contour_path->ConvertWithBackData(1.0); |
260 | + contour_path->Fill(combined_shape, 0, true, false, false); |
261 | + |
262 | + // Intersect the area and the contour - no fill processing |
263 | + combined_inters->ConvertToShape(combined_shape, fill_justDont); |
264 | + |
265 | + // Result path |
266 | + Path *result_path = new Path; |
267 | + result_path->SetBackData(false); |
268 | + |
269 | + // Cutting positions for contour |
270 | + std::vector<Path::cut_position> toCut; |
271 | + |
272 | + if (combined_inters->hasBackData()) { |
273 | + // should always be the case, but ya never know |
274 | + { |
275 | + for (int i = 0; i < combined_inters->numberOfPoints(); i++) { |
276 | + if (combined_inters->getPoint(i).totalDegree() > 2) { |
277 | + // possibly an intersection |
278 | + // we need to check that at least one edge from the source path is incident to it |
279 | + // before we declare it's an intersection |
280 | + int cb = combined_inters->getPoint(i).incidentEdge[FIRST]; |
281 | + int nbOrig = 0; |
282 | + int nbOther = 0; |
283 | + int piece = -1; |
284 | + float t = 0.0; |
285 | + while (cb >= 0 && cb < combined_inters->numberOfEdges()) { |
286 | + if (combined_inters->ebData[cb].pathID == 0) { |
287 | + // the source has an edge incident to the point, get its position on the path |
288 | + piece = combined_inters->ebData[cb].pieceID; |
289 | + if (combined_inters->getEdge(cb).st == i) { |
290 | + t = combined_inters->ebData[cb].tSt; |
291 | + } else { |
292 | + t = combined_inters->ebData[cb].tEn; |
293 | + } |
294 | + nbOrig++; |
295 | + } |
296 | + if (combined_inters->ebData[cb].pathID == 1) { |
297 | + nbOther++; // the cut is incident to this point |
298 | + } |
299 | + cb = combined_inters->NextAt(i, cb); |
300 | + } |
301 | + if (nbOrig > 0 && nbOther > 0) { |
302 | + // point incident to both path and cut: an intersection |
303 | + // note that you only keep one position on the source; you could have degenerate |
304 | + // cases where the source crosses itself at this point, and you wouyld miss an intersection |
305 | + Path::cut_position cutpos; |
306 | + cutpos.piece = piece; |
307 | + cutpos.t = t; |
308 | + toCut.push_back(cutpos); |
309 | + } |
310 | + } |
311 | + } |
312 | + } |
313 | + { |
314 | + // remove the edges from the intersection polygon |
315 | + int i = combined_inters->numberOfEdges() - 1; |
316 | + for (; i >= 0; i--) { |
317 | + if (combined_inters->ebData[i].pathID == 1) { |
318 | + combined_inters->SubEdge(i); |
319 | + } else { |
320 | + const Shape::dg_arete &edge = combined_inters->getEdge(i); |
321 | + const Shape::dg_point &start = combined_inters->getPoint(edge.st); |
322 | + const Shape::dg_point &end = combined_inters->getPoint(edge.en); |
323 | + Geom::Point mid = 0.5 * (start.x + end.x); |
324 | + int wind = area_shape->PtWinding(mid); |
325 | + if (wind == 0) { |
326 | + combined_inters->SubEdge(i); |
327 | + } |
328 | + } |
329 | + } |
330 | + } |
331 | + } |
332 | + |
333 | + // create a vector of pieces, which are in the intersection |
334 | + std::vector<Path::cut_position> inside_pieces(combined_inters->numberOfEdges()); |
335 | + for (int i = 0; i < combined_inters->numberOfEdges(); i++) { |
336 | + inside_pieces[i].piece = combined_inters->ebData[i].pieceID; |
337 | + // Use the t middle point, this is safe to compare with values from toCut in the presence of roundoff errors |
338 | + inside_pieces[i].t = 0.5 * (combined_inters->ebData[i].tSt + combined_inters->ebData[i].tEn); |
339 | + } |
340 | + std::sort(inside_pieces.begin(), inside_pieces.end(), cmp_cut_position); |
341 | + |
342 | + // sort cut positions |
343 | + std::sort(toCut.begin(), toCut.end(), cmp_cut_position); |
344 | + |
345 | + // Compute piece ids after ConvertPositionsToMoveTo |
346 | + { |
347 | + int idIncr = 0; |
348 | + std::vector<Path::cut_position>::iterator itPiece = inside_pieces.begin(); |
349 | + std::vector<Path::cut_position>::iterator itCut = toCut.begin(); |
350 | + while (itPiece != inside_pieces.end()) { |
351 | + while (itCut != toCut.end() && cmp_cut_position(*itCut, *itPiece)) { |
352 | + ++itCut; |
353 | + idIncr += 2; |
354 | + } |
355 | + itPiece->piece += idIncr; |
356 | + ++itPiece; |
357 | + } |
358 | + } |
359 | + |
360 | + // Copy the original path to result and cut at the intersection points |
361 | + result_path->Copy(contour_path); |
362 | + result_path->ConvertPositionsToMoveTo(toCut.size(), toCut.data()); // cut where you found intersections |
363 | + |
364 | + // Create an array of bools which states which pieces are in |
365 | + std::vector<bool> inside_flags(result_path->descr_cmd.size(), false); |
366 | + for (std::vector<Path::cut_position>::iterator itPiece = inside_pieces.begin(); itPiece != inside_pieces.end(); ++itPiece) { |
367 | + inside_flags[ itPiece->piece ] = true; |
368 | + // also enable the element -1 to get the MoveTo |
369 | + if (itPiece->piece >= 1) { |
370 | + inside_flags[ itPiece->piece - 1 ] = true; |
371 | + } |
372 | + } |
373 | + |
374 | +#if 0 // CONCEPT TESTING |
375 | + //Check if the inside/outside verdict is consistent - just for testing the concept |
376 | + // Retrieve the pieces |
377 | + int nParts = 0; |
378 | + Path **parts = result_path->SubPaths(nParts, false); |
379 | + |
380 | + // Each piece should be either fully in or fully out |
381 | + int iPiece = 0; |
382 | + for (int iPart = 0; iPart < nParts; iPart++) { |
383 | + bool andsum = true; |
384 | + bool orsum = false; |
385 | + for (int iCmd = 0; iCmd < parts[iPart]->descr_cmd.size(); iCmd++, iPiece++) { |
386 | + andsum = andsum && inside_flags[ iPiece ]; |
387 | + orsum = andsum || inside_flags[ iPiece ]; |
388 | + } |
389 | + |
390 | + if (andsum != orsum) { |
391 | + g_warning("Inconsistent inside/outside verdict for part=%d", iPart); |
392 | + } |
393 | + } |
394 | + g_free(parts); |
395 | +#endif |
396 | + |
397 | + // iterate over the commands of a path and keep those which are inside |
398 | + int iDest = 0; |
399 | + for (int iSrc = 0; iSrc < result_path->descr_cmd.size(); iSrc++) { |
400 | + if (inside_flags[iSrc] == inside) { |
401 | + result_path->descr_cmd[iDest++] = result_path->descr_cmd[iSrc]; |
402 | + } else { |
403 | + delete result_path->descr_cmd[iSrc]; |
404 | + } |
405 | + } |
406 | + result_path->descr_cmd.resize(iDest); |
407 | + |
408 | + delete combined_inters; |
409 | + delete combined_shape; |
410 | + delete area_shape; |
411 | + delete contour_path; |
412 | + delete area_path; |
413 | + |
414 | + gchar *result_str = result_path->svg_dump_path(); |
415 | + Geom::PathVector outres = Geom::parse_svg_path(result_str); |
416 | + // CONCEPT TESTING g_warning( "%s", result_str ); |
417 | + g_free(result_str); |
418 | + delete result_path; |
419 | + |
420 | + return outres; |
421 | +} |
422 | + |
423 | +// remove inner contours |
424 | +Geom::PathVector |
425 | +sp_pathvector_boolop_remove_inner(Geom::PathVector const &pathva, fill_typ fra) |
426 | +{ |
427 | + Geom::PathVector patht; |
428 | + Path *patha = Path_for_pathvector(pathv_to_linear_and_cubic_beziers(pathva)); |
429 | + |
430 | + Shape *shape = new Shape; |
431 | + Shape *shapeshape = new Shape; |
432 | + Path *resultp = new Path; |
433 | + resultp->SetBackData(false); |
434 | + |
435 | + patha->ConvertWithBackData(0.1); |
436 | + patha->Fill(shape, 0); |
437 | + shapeshape->ConvertToShape(shape, fra); |
438 | + shapeshape->ConvertToForme(resultp, 1, &patha); |
439 | + |
440 | + delete shape; |
441 | + delete shapeshape; |
442 | + delete patha; |
443 | + |
444 | + gchar *result_str = resultp->svg_dump_path(); |
445 | + Geom::PathVector resultpv = Geom::parse_svg_path(result_str); |
446 | + g_free(result_str); |
447 | + |
448 | + delete resultp; |
449 | + return resultpv; |
450 | +} |
451 | + |
452 | +static fill_typ GetFillTyp(SPItem *item) |
453 | +{ |
454 | + SPCSSAttr *css = sp_repr_css_attr(item->getRepr(), "style"); |
455 | + gchar const *val = sp_repr_css_property(css, "fill-rule", NULL); |
456 | + if (val && strcmp(val, "nonzero") == 0) { |
457 | + return fill_nonZero; |
458 | + } else if (val && strcmp(val, "evenodd") == 0) { |
459 | + return fill_oddEven; |
460 | + } else { |
461 | + return fill_nonZero; |
462 | + } |
463 | +} |
464 | + |
465 | +void LPEBool::doEffect(SPCurve *curve) |
466 | +{ |
467 | + Geom::PathVector path_in = curve->get_pathvector(); |
468 | + |
469 | + if (operand_path.linksToPath() && operand_path.getObject()) { |
470 | + bool_op_ex op = bool_operation.get_value(); |
471 | + bool swap = swap_operands.get_value(); |
472 | + |
473 | + Geom::PathVector path_a = swap ? operand_path.get_pathvector() : path_in; |
474 | + Geom::PathVector path_b = swap ? path_in : operand_path.get_pathvector(); |
475 | + |
476 | + // TODO: I would like to use the original objects fill rule if the UI selected rule is fill_justDont. |
477 | + // But it doesn't seem possible to access them from here, because SPCurve is not derived from SPItem. |
478 | + // The nearest function in the call stack, where this is available is SPLPEItem::performPathEffect (this is then an SPItem) |
479 | + // For the parameter curve, this is possible. |
480 | + // fill_typ fill_this = fill_type_this. get_value()!=fill_justDont ? fill_type_this.get_value() : GetFillTyp( curve ) ; |
481 | + fill_typ fill_this = fill_type_this.get_value(); |
482 | + fill_typ fill_operand = fill_type_operand.get_value() != fill_justDont ? fill_type_operand.get_value() : GetFillTyp(operand_path.getObject()); |
483 | + |
484 | + fill_typ fill_a = swap ? fill_operand : fill_this; |
485 | + fill_typ fill_b = swap ? fill_this : fill_operand; |
486 | + |
487 | + if (rmv_inner.get_value()) { |
488 | + path_b = sp_pathvector_boolop_remove_inner(path_b, fill_b); |
489 | + } |
490 | + |
491 | + Geom::PathVector path_out; |
492 | + |
493 | + if (op == bool_op_ex_slice) { |
494 | + // For slicing, the bool op is added to the line group which is sliced, not the cut path. This swapped order is correct. |
495 | + path_out = sp_pathvector_boolop(path_b, path_a, to_bool_op(op), fill_b, fill_a); |
496 | + } else if (op == bool_op_ex_slice_inside) { |
497 | + path_out = sp_pathvector_boolop_slice_intersect(path_a, path_b, true, fill_a, fill_b); |
498 | + } else if (op == bool_op_ex_slice_outside) { |
499 | + path_out = sp_pathvector_boolop_slice_intersect(path_a, path_b, false, fill_a, fill_b); |
500 | + } else { |
501 | + path_out = sp_pathvector_boolop(path_a, path_b, to_bool_op(op), fill_a, fill_b); |
502 | + } |
503 | + curve->set_pathvector(path_out); |
504 | + } |
505 | +} |
506 | + |
507 | +} // namespace LivePathEffect |
508 | +} /* namespace Inkscape */ |
509 | |
510 | === added file 'src/live_effects/lpe-bool.h' |
511 | --- src/live_effects/lpe-bool.h 1970-01-01 00:00:00 +0000 |
512 | +++ src/live_effects/lpe-bool.h 2017-06-05 19:02:59 +0000 |
513 | @@ -0,0 +1,63 @@ |
514 | +/* |
515 | + * Boolean operation live path effect |
516 | + * |
517 | + * Copyright (C) 2016 Michael Soegtrop |
518 | + * |
519 | + * Released under GNU GPL, read the file 'COPYING' for more information |
520 | + */ |
521 | + |
522 | +#ifndef INKSCAPE_LPE_BOOL_H |
523 | +#define INKSCAPE_LPE_BOOL_H |
524 | + |
525 | +#include "live_effects/effect.h" |
526 | +#include "live_effects/parameter/parameter.h" |
527 | +#include "live_effects/parameter/originalpath.h" |
528 | +#include "live_effects/parameter/bool.h" |
529 | +#include "live_effects/parameter/enum.h" |
530 | +#include "livarot/LivarotDefs.h" |
531 | + |
532 | +namespace Inkscape { |
533 | +namespace LivePathEffect { |
534 | + |
535 | +class LPEBool : public Effect { |
536 | +public: |
537 | + LPEBool(LivePathEffectObject *lpeobject); |
538 | + virtual ~LPEBool(); |
539 | + |
540 | + void doEffect(SPCurve *curve); |
541 | + virtual void resetDefaults(SPItem const *item); |
542 | + |
543 | + enum bool_op_ex { |
544 | + bool_op_ex_union = bool_op_union, |
545 | + bool_op_ex_inters = bool_op_inters, |
546 | + bool_op_ex_diff = bool_op_diff, |
547 | + bool_op_ex_symdiff = bool_op_symdiff, |
548 | + bool_op_ex_cut = bool_op_cut, |
549 | + bool_op_ex_slice = bool_op_slice, |
550 | + bool_op_ex_slice_inside, // like bool_op_slice, but leaves only the contour pieces inside of the cut path |
551 | + bool_op_ex_slice_outside, // like bool_op_slice, but leaves only the contour pieces outside of the cut path |
552 | + bool_op_ex_count |
553 | + }; |
554 | + |
555 | + inline friend bool_op to_bool_op(bool_op_ex val) |
556 | + { |
557 | + assert(val <= bool_op_ex_slice); |
558 | + (bool_op) val; |
559 | + } |
560 | + |
561 | +private: |
562 | + LPEBool(const LPEBool &); |
563 | + LPEBool &operator=(const LPEBool &); |
564 | + |
565 | + OriginalPathParam operand_path; |
566 | + EnumParam<bool_op_ex> bool_operation; |
567 | + EnumParam<fill_typ> fill_type_this; |
568 | + EnumParam<fill_typ> fill_type_operand; |
569 | + BoolParam swap_operands; |
570 | + BoolParam rmv_inner; |
571 | +}; |
572 | + |
573 | +}; //namespace LivePathEffect |
574 | +}; //namespace Inkscape |
575 | + |
576 | +#endif |
Hi Michael, the code seems good to me, I put some inline questions. Any way still going problems to make boolops in my branch. Please contact with me.