Merge lp:~msoegtrop/inkscape/lpe-bool into lp:~inkscape.dev/inkscape/trunk

Proposed by Michael Soegtrop
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
Reviewer Review Type Date Requested Status
Jabiertxof code + ux Approve
Review via email: mp+294072@code.launchpad.net

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.

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

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.

Revision history for this message
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_ex_slice_inside, // like bool_op_slice, but leaves only the contour pieces inside of the cut path
>> > + bool_op_ex_slice_outside, // like bool_op_slice, but leaves only the contour pieces outside of the cut path
>> > + bool_op_ex_slice_rmv_inner, // like bool_op_ex_slice, but remove inner contours
>> > + bool_op_ex_slice_inside_rmv_inner, // like bool_op_ex_slice_inside, but remove inner contours
>> > + bool_op_ex_slice_outside_rmv_inner, // like bool_op_ex_slice_outside, but remove inner contours
>> > + 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

Revision history for this message
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.

review: Approve (code + ux)
Revision history for this message
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 Automatisierungstechnik
=
= Anzinger Str. 10c
= 85586 Poing
=
= Tel.: (08121) 972433
= Fax.: (08121) 972434
===========================================

lp:~msoegtrop/inkscape/lpe-bool updated
14878. By Michael Soegtrop

Fixed Bool LPE review issues

Revision history for this message
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.

lp:~msoegtrop/inkscape/lpe-bool updated
14879. By Michael Soegtrop

updated to latest trunk

Revision history for this message
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.

Revision history for this message
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

Revision history for this message
Jabiertxof (jabiertxof) wrote :

Great, Best luky!

lp:~msoegtrop/inkscape/lpe-bool updated
14880. By Michael Soegtrop

updated to latest trunk

14881. By Michael Soegtrop

fixed enum order + added cpp files to POFILES.in

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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