1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Boolean operation live path effect
4 *
5 * Copyright (C) 2016-2017 Michael Soegtrop
6 *
7 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
8 */
9
10 #include "live_effects/lpe-bool.h"
11
12 #include <algorithm>
13 #include <cmath>
14 #include <cstring>
15 #include <glibmm/i18n.h>
16
17 #include "2geom/affine.h"
18 #include "2geom/bezier-curve.h"
19 #include "2geom/path-sink.h"
20 #include "2geom/path.h"
21 #include "2geom/svg-path-parser.h"
22 #include "display/curve.h"
23 #include "helper/geom.h"
24 #include "inkscape.h"
25 #include "selection-chemistry.h"
26 #include "livarot/Path.h"
27 #include "livarot/Shape.h"
28 #include "livarot/path-description.h"
29 #include "live_effects/lpeobject.h"
30 #include "object/sp-clippath.h"
31 #include "object/sp-defs.h"
32 #include "object/sp-shape.h"
33 #include "object/sp-text.h"
34 #include "object/sp-root.h"
35 #include "path/path-boolop.h"
36 #include "path/path-util.h"
37 #include "snap.h"
38 #include "style.h"
39 #include "svg/svg.h"
40 #include "ui/tools/tool-base.h"
41
42 namespace Inkscape {
43 namespace LivePathEffect {
44
45 // Define an extended boolean operation type
46
47 static const Util::EnumData<LPEBool::bool_op_ex> BoolOpData[LPEBool::bool_op_ex_count] = {
48 {LPEBool::bool_op_ex_union, N_("union"), "union"},
49 {LPEBool::bool_op_ex_inters, N_("intersection"), "inters"},
50 {LPEBool::bool_op_ex_diff, N_("difference"), "diff"},
51 {LPEBool::bool_op_ex_symdiff, N_("symmetric difference"), "symdiff"},
52 {LPEBool::bool_op_ex_cut, N_("division"), "cut"},
53 // Note on naming of operations:
54 // bool_op_cut is called "Division" in the manu, see sp_selected_path_cut
55 // bool_op_slice is called "Cut path" in the menu, see sp_selected_path_slice
56 // TODO: this 3 options are commented because dont work properly
57 // maybe in 1.2 can be fixed but need libarot base to do
58 // {LPEBool::bool_op_ex_slice, N_("cut"), "slice"},
59 // {LPEBool::bool_op_ex_slice_inside, N_("cut inside"), "slice-inside"},
60 // {LPEBool::bool_op_ex_slice_outside, N_("cut outside"), "slice-outside"},
61 };
62
63 static const Util::EnumDataConverter<LPEBool::bool_op_ex> BoolOpConverter(BoolOpData, sizeof(BoolOpData) / sizeof(*BoolOpData));
64
65 static const Util::EnumData<fill_typ> FillTypeData[] = {
66 { fill_oddEven, N_("even-odd"), "oddeven" },
67 { fill_nonZero, N_("non-zero"), "nonzero" },
68 { fill_positive, N_("positive"), "positive" },
69 { fill_justDont, N_("take from object"), "from-curve" }
70 };
71
72 static const Util::EnumDataConverter<fill_typ> FillTypeConverter(FillTypeData, sizeof(FillTypeData) / sizeof(*FillTypeData));
73
LPEBool(LivePathEffectObject * lpeobject)74 LPEBool::LPEBool(LivePathEffectObject *lpeobject)
75 : Effect(lpeobject)
76 , operand_path(_("Operand path:"), _("Operand for the boolean operation"), "operand-path", &wr, this)
77 , bool_operation(_("Operation:"), _("Boolean Operation"), "operation", BoolOpConverter, &wr, this, bool_op_ex_union)
78 , swap_operands(_("Swap operands"), _("Swap operands (useful e.g. for difference)"), "swap-operands", &wr, this)
79 , rmv_inner(
80 _("Remove inner"),
81 _("For cut operations: remove inner (non-contour) lines of cutting path to avoid invisible extra points"),
82 "rmv-inner", &wr, this)
83 , fill_type_this(_("Fill type this:"), _("Fill type (winding mode) for this path"), "filltype-this",
84 FillTypeConverter, &wr, this, fill_justDont)
85 , fill_type_operand(_("Fill type operand:"), _("Fill type (winding mode) for operand path"), "filltype-operand",
86 FillTypeConverter, &wr, this, fill_justDont)
87 , filter("Filter", "Previous filter", "filter", &wr, this, "", true)
88 {
89 registerParameter(&operand_path);
90 registerParameter(&bool_operation);
91 registerParameter(&swap_operands);
92 registerParameter(&rmv_inner);
93 registerParameter(&fill_type_this);
94 registerParameter(&filter);
95 registerParameter(&fill_type_operand);
96 show_orig_path = true;
97 is_load = true;
98 prev_affine = Geom::identity();
99 operand = dynamic_cast<SPItem *>(operand_path.getObject());
100 if (operand) {
101 operand_id = operand->getId();
102 }
103 }
104
~LPEBool()105 LPEBool::~LPEBool() {
106 doOnRemove(nullptr);
107 }
cmp_cut_position(const Path::cut_position & a,const Path::cut_position & b)108 bool cmp_cut_position(const Path::cut_position &a, const Path::cut_position &b)
109 {
110 return a.piece == b.piece ? a.t < b.t : a.piece < b.piece;
111 }
112
113 Geom::PathVector
sp_pathvector_boolop_slice_intersect(Geom::PathVector const & pathva,Geom::PathVector const & pathvb,bool inside,fill_typ fra,fill_typ frb)114 sp_pathvector_boolop_slice_intersect(Geom::PathVector const &pathva, Geom::PathVector const &pathvb, bool inside, fill_typ fra, fill_typ frb)
115 {
116 // This is similar to sp_pathvector_boolop/bool_op_slice, but keeps only edges inside the cutter area.
117 // The code is also based on sp_pathvector_boolop_slice.
118 //
119 // We have two paths on input
120 // - a closed area which is used to cut out pieces from a contour (called area below)
121 // - a contour which is cut into pieces by the border of thr area (called contour below)
122 //
123 // The code below works in the following steps
124 // (a) Convert the area to a shape, so that we can ask the winding number for any point
125 // (b) Add both, the contour and the area to a single shape and intersect them
126 // (c) Find the intersection points between area border and contour (vector toCut)
127 // (d) Split the original contour at the intersection points
128 // (e) check for each contour edge in combined shape if its center is inside the area - if not discard it
129 // (f) create a vector of all inside edges
130 // (g) convert the piece numbers to the piece numbers after applying the cuts
131 // (h) fill a bool vector with information which pieces are in
132 // (i) filter the descr_cmd of the result path with this bool vector
133 //
134 // The main inefficiency here is step (e) because I use a winding function of the area-shape which goes
135 // through the complete edge list for each point I ask for, so effort is n-edges-contour * n-edges-area.
136 // It is tricky to improve this without building into the livarot code.
137 // One way might be to decide at the intersection points which edges touching the intersection points are
138 // in by making a loop through all edges on the intersection vertex. Since this is a directed non intersecting
139 // graph, this should provide sufficient information.
140 // But since I anyway will change this to the new mechanism some time speed is fairly ok, I didn't look into this.
141
142
143 // extract the livarot Paths from the source objects
144 // also get the winding rule specified in the style
145 // Livarot's outline of arcs is broken. So convert the path to linear and cubics only, for which the outline is created correctly.
146 Path *contour_path = Path_for_pathvector(pathv_to_linear_and_cubic_beziers(pathva));
147 Path *area_path = Path_for_pathvector(pathv_to_linear_and_cubic_beziers(pathvb));
148
149 // Shapes from above paths
150 Shape *area_shape = new Shape;
151 Shape *combined_shape = new Shape;
152 Shape *combined_inters = new Shape;
153
154 // Add the area (process to intersection free shape)
155 area_path->ConvertWithBackData(1.0);
156 area_path->Fill(combined_shape, 1);
157
158 // Convert this to a shape with full winding information
159 area_shape->ConvertToShape(combined_shape, frb);
160
161 // Add the contour to the combined path (just add, no winding processing)
162 contour_path->ConvertWithBackData(1.0);
163 contour_path->Fill(combined_shape, 0, true, false, false);
164
165 // Intersect the area and the contour - no fill processing
166 combined_inters->ConvertToShape(combined_shape, fill_justDont);
167
168 // Result path
169 Path *result_path = new Path;
170 result_path->SetBackData(false);
171
172 // Cutting positions for contour
173 std::vector<Path::cut_position> toCut;
174
175 if (combined_inters->hasBackData()) {
176 // should always be the case, but ya never know
177 {
178 for (int i = 0; i < combined_inters->numberOfPoints(); i++) {
179 if (combined_inters->getPoint(i).totalDegree() > 2) {
180 // possibly an intersection
181 // we need to check that at least one edge from the source path is incident to it
182 // before we declare it's an intersection
183 int cb = combined_inters->getPoint(i).incidentEdge[FIRST];
184 int nbOrig = 0;
185 int nbOther = 0;
186 int piece = -1;
187 float t = 0.0;
188 while (cb >= 0 && cb < combined_inters->numberOfEdges()) {
189 if (combined_inters->ebData[cb].pathID == 0) {
190 // the source has an edge incident to the point, get its position on the path
191 piece = combined_inters->ebData[cb].pieceID;
192 if (combined_inters->getEdge(cb).st == i) {
193 t = combined_inters->ebData[cb].tSt;
194 } else {
195 t = combined_inters->ebData[cb].tEn;
196 }
197 nbOrig++;
198 }
199 if (combined_inters->ebData[cb].pathID == 1) {
200 nbOther++; // the cut is incident to this point
201 }
202 cb = combined_inters->NextAt(i, cb);
203 }
204 if (nbOrig > 0 && nbOther > 0) {
205 // point incident to both path and cut: an intersection
206 // note that you only keep one position on the source; you could have degenerate
207 // cases where the source crosses itself at this point, and you wouyld miss an intersection
208 Path::cut_position cutpos;
209 cutpos.piece = piece;
210 cutpos.t = t;
211 toCut.push_back(cutpos);
212 }
213 }
214 }
215 }
216 {
217 // remove the edges from the intersection polygon
218 int i = combined_inters->numberOfEdges() - 1;
219 for (; i >= 0; i--) {
220 if (combined_inters->ebData[i].pathID == 1) {
221 combined_inters->SubEdge(i);
222 } else {
223 const Shape::dg_arete &edge = combined_inters->getEdge(i);
224 const Shape::dg_point &start = combined_inters->getPoint(edge.st);
225 const Shape::dg_point &end = combined_inters->getPoint(edge.en);
226 Geom::Point mid = 0.5 * (start.x + end.x);
227 int wind = area_shape->PtWinding(mid);
228 if (wind == 0) {
229 combined_inters->SubEdge(i);
230 }
231 }
232 }
233 }
234 }
235
236 // create a vector of pieces, which are in the intersection
237 std::vector<Path::cut_position> inside_pieces(combined_inters->numberOfEdges());
238 for (int i = 0; i < combined_inters->numberOfEdges(); i++) {
239 inside_pieces[i].piece = combined_inters->ebData[i].pieceID;
240 // Use the t middle point, this is safe to compare with values from toCut in the presence of roundoff errors
241 inside_pieces[i].t = 0.5 * (combined_inters->ebData[i].tSt + combined_inters->ebData[i].tEn);
242 }
243 std::sort(inside_pieces.begin(), inside_pieces.end(), cmp_cut_position);
244
245 // sort cut positions
246 std::sort(toCut.begin(), toCut.end(), cmp_cut_position);
247
248 // Compute piece ids after ConvertPositionsToMoveTo
249 {
250 int idIncr = 0;
251 std::vector<Path::cut_position>::iterator itPiece = inside_pieces.begin();
252 std::vector<Path::cut_position>::iterator itCut = toCut.begin();
253 while (itPiece != inside_pieces.end()) {
254 while (itCut != toCut.end() && cmp_cut_position(*itCut, *itPiece)) {
255 ++itCut;
256 idIncr += 2;
257 }
258 itPiece->piece += idIncr;
259 ++itPiece;
260 }
261 }
262
263 // Copy the original path to result and cut at the intersection points
264 result_path->Copy(contour_path);
265 result_path->ConvertPositionsToMoveTo(toCut.size(), toCut.data()); // cut where you found intersections
266
267 // Create an array of bools which states which pieces are in
268 std::vector<bool> inside_flags(result_path->descr_cmd.size(), false);
269 for (auto & inside_piece : inside_pieces) {
270 inside_flags[ inside_piece.piece ] = true;
271 // also enable the element -1 to get the MoveTo
272 if (inside_piece.piece >= 1) {
273 inside_flags[ inside_piece.piece - 1 ] = true;
274 }
275 }
276
277 #if 0 // CONCEPT TESTING
278 //Check if the inside/outside verdict is consistent - just for testing the concept
279 // Retrieve the pieces
280 int nParts = 0;
281 Path **parts = result_path->SubPaths(nParts, false);
282
283 // Each piece should be either fully in or fully out
284 int iPiece = 0;
285 for (int iPart = 0; iPart < nParts; iPart++) {
286 bool andsum = true;
287 bool orsum = false;
288 for (int iCmd = 0; iCmd < parts[iPart]->descr_cmd.size(); iCmd++, iPiece++) {
289 andsum = andsum && inside_flags[ iPiece ];
290 orsum = andsum || inside_flags[ iPiece ];
291 }
292
293 if (andsum != orsum) {
294 g_warning("Inconsistent inside/outside verdict for part=%d", iPart);
295 }
296 }
297 g_free(parts);
298 #endif
299
300 // iterate over the commands of a path and keep those which are inside
301 int iDest = 0;
302 for (int iSrc = 0; iSrc < result_path->descr_cmd.size(); iSrc++) {
303 if (inside_flags[iSrc] == inside) {
304 result_path->descr_cmd[iDest++] = result_path->descr_cmd[iSrc];
305 } else {
306 delete result_path->descr_cmd[iSrc];
307 }
308 }
309 result_path->descr_cmd.resize(iDest);
310
311 delete combined_inters;
312 delete combined_shape;
313 delete area_shape;
314 delete contour_path;
315 delete area_path;
316
317 gchar *result_str = result_path->svg_dump_path();
318 Geom::PathVector outres = Geom::parse_svg_path(result_str);
319 // CONCEPT TESTING g_warning( "%s", result_str );
320 g_free(result_str);
321 delete result_path;
322
323 return outres;
324 }
325
326 // remove inner contours
327 Geom::PathVector
sp_pathvector_boolop_remove_inner(Geom::PathVector const & pathva,fill_typ fra)328 sp_pathvector_boolop_remove_inner(Geom::PathVector const &pathva, fill_typ fra)
329 {
330 Geom::PathVector patht;
331 Path *patha = Path_for_pathvector(pathv_to_linear_and_cubic_beziers(pathva));
332
333 Shape *shape = new Shape;
334 Shape *shapeshape = new Shape;
335 Path *resultp = new Path;
336 resultp->SetBackData(false);
337
338 patha->ConvertWithBackData(0.1);
339 patha->Fill(shape, 0);
340 shapeshape->ConvertToShape(shape, fra);
341 shapeshape->ConvertToForme(resultp, 1, &patha);
342
343 delete shape;
344 delete shapeshape;
345 delete patha;
346
347 gchar *result_str = resultp->svg_dump_path();
348 Geom::PathVector resultpv = Geom::parse_svg_path(result_str);
349 g_free(result_str);
350
351 delete resultp;
352 return resultpv;
353 }
354
GetFillTyp(SPItem * item)355 static fill_typ GetFillTyp(SPItem *item)
356 {
357 SPCSSAttr *css = sp_repr_css_attr(item->getRepr(), "style");
358 gchar const *val = sp_repr_css_property(css, "fill-rule", nullptr);
359 if (val && strcmp(val, "nonzero") == 0) {
360 return fill_nonZero;
361 } else if (val && strcmp(val, "evenodd") == 0) {
362 return fill_oddEven;
363 } else {
364 return fill_nonZero;
365 }
366 }
367
add_filter()368 void LPEBool::add_filter()
369 {
370 if (operand) {
371 Inkscape::XML::Node *repr = operand->getRepr();
372 if (!repr) {
373 return;
374 }
375 SPFilter *filt = operand->style->getFilter();
376 if (filt && filt->getId() && strcmp(filt->getId(), "selectable_hidder_filter") != 0) {
377 filter.param_setValue(filt->getId(), true);
378 }
379 if (!filt || (filt->getId() && strcmp(filt->getId(), "selectable_hidder_filter") != 0)) {
380 SPCSSAttr *css = sp_repr_css_attr_new();
381 sp_repr_css_set_property(css, "filter", "url(#selectable_hidder_filter)");
382 sp_repr_css_change(repr, css, "style");
383 sp_repr_css_attr_unref(css);
384 }
385 }
386 }
387
remove_filter()388 void LPEBool::remove_filter()
389 {
390 if (operand) {
391 Inkscape::XML::Node *repr = operand->getRepr();
392 if (!repr) {
393 return;
394 }
395 SPFilter *filt = operand->style->getFilter();
396 if (filt && (filt->getId() && strcmp(filt->getId(), "selectable_hidder_filter") == 0)) {
397 SPCSSAttr *css = sp_repr_css_attr_new();
398 Glib::ustring filtstr = filter.param_getSVGValue();
399 if (filtstr != "") {
400 Glib::ustring url = "url(#";
401 url += filtstr;
402 url += ")";
403 sp_repr_css_set_property(css, "filter", url.c_str());
404 // blur is removed when no item using it
405 /*SPDocument *document = getSPDoc();
406 SPObject * filterobj = nullptr;
407 if((filterobj = document->getObjectById(filtstr))) {
408 for (auto obj:filterobj->childList(false)) {
409 if (obj) {
410 obj->deleteObject(false);
411 break;
412 }
413 }
414 } */
415 filter.param_setValue("");
416 } else {
417 sp_repr_css_unset_property(css, "filter");
418 }
419 sp_repr_css_change(repr, css, "style");
420 sp_repr_css_attr_unref(css);
421 }
422 }
423 }
424
doBeforeEffect(SPLPEItem const * lpeitem)425 void LPEBool::doBeforeEffect(SPLPEItem const *lpeitem)
426 {
427 SPDocument *document = getSPDoc();
428 if (!document) {
429 return;
430 }
431 _hp.clear();
432 Inkscape::XML::Document *xml_doc = document->getReprDoc();
433 SPObject *elemref = nullptr;
434 Inkscape::XML::Node *boolfilter = nullptr;
435 if (!(elemref = document->getObjectById("selectable_hidder_filter"))) {
436 boolfilter = xml_doc->createElement("svg:filter");
437 boolfilter->setAttribute("id", "selectable_hidder_filter");
438 boolfilter->setAttribute("width", "1");
439 boolfilter->setAttribute("height", "1");
440 boolfilter->setAttribute("x", "0");
441 boolfilter->setAttribute("y", "0");
442 boolfilter->setAttribute("style", "color-interpolation-filters:sRGB;");
443 boolfilter->setAttribute("inkscape:label", "LPE boolean visibility");
444 /* Create <path> */
445 Inkscape::XML::Node *primitive = xml_doc->createElement("svg:feComposite");
446 primitive->setAttribute("id", "boolops_hidder_primitive");
447 primitive->setAttribute("result", "composite1");
448 primitive->setAttribute("operator", "arithmetic");
449 primitive->setAttribute("in2", "SourceGraphic");
450 primitive->setAttribute("in", "BackgroundImage");
451 Inkscape::XML::Node *defs = document->getDefs()->getRepr();
452 defs->addChild(boolfilter, nullptr);
453 Inkscape::GC::release(boolfilter);
454 boolfilter->addChild(primitive, nullptr);
455 Inkscape::GC::release(primitive);
456 } else {
457 for (auto obj : elemref->childList(false)) {
458 if (obj && strcmp(obj->getId(), "boolops_hidder_primitive") != 0) {
459 obj->deleteObject(true);
460 }
461 }
462 }
463 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
464 SPItem *current_operand = dynamic_cast<SPItem *>(operand_path.getObject());
465 operand = dynamic_cast<SPItem *>(lpeitem->document->getObjectById(operand_id));
466
467 if (!current_operand && !operand) {
468 return;
469 }
470 if (!current_operand) {
471 operand_path.remove_link();
472 operand = nullptr;
473 }
474 if (current_operand && current_operand->getId()) {
475 if (!(document->getObjectById(current_operand->getId()))) {
476 operand_path.remove_link();
477 operand = nullptr;
478 operand_id = "";
479 current_operand = nullptr;
480 } else {
481 operand_id = current_operand->getId();
482 }
483 }
484 SPLPEItem *operandlpe = dynamic_cast<SPLPEItem *>(operand_path.getObject());
485 if (desktop &&
486 operand &&
487 sp_lpe_item &&
488 desktop->getSelection()->includes(operand) &&
489 desktop->getSelection()->includes(sp_lpe_item))
490 {
491 if (operandlpe && operandlpe->hasPathEffectOfType(Inkscape::LivePathEffect::EffectType::BOOL_OP)) {
492 sp_lpe_item_update_patheffect(operandlpe, false, false);
493 }
494 desktop->getSelection()->remove(operand);
495 }
496 if (!current_operand) {
497 if (operand) {
498 remove_filter();
499 }
500 operand = nullptr;
501 operand_id = "";
502 }
503
504 if (current_operand && operand != current_operand) {
505 if (operand) {
506 remove_filter();
507 }
508 operand = current_operand;
509 remove_filter();
510 if (is_load && sp_lpe_item) {
511 sp_lpe_item_update_patheffect(sp_lpe_item, true, true);
512 }
513 }
514 if (operand) {
515 if (is_visible) {
516 add_filter();
517 if (operand->getPosition() - 1 != sp_lpe_item->getPosition()) {
518 sp_lpe_item->parent->reorder(operand,sp_lpe_item);
519 }
520 } else {
521 remove_filter();
522 }
523 }
524 }
525
transform_multiply(Geom::Affine const & postmul,bool)526 void LPEBool::transform_multiply(Geom::Affine const &postmul, bool /*set*/)
527 {
528 operand = dynamic_cast<SPItem *>(sp_lpe_item->document->getObjectById(operand_id));
529 if (operand && !isOnClipboard()) {
530 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
531 if (desktop && !desktop->getSelection()->includes(operand)) {
532 prev_affine = operand->transform * sp_item_transform_repr(sp_lpe_item).inverse() * postmul;
533 operand->doWriteTransform(prev_affine);
534 }
535 }
536 }
537
get_union(SPObject * object)538 Geom::PathVector LPEBool::get_union(SPObject *object)
539 {
540 Geom::PathVector res;
541 Geom::PathVector clippv;
542 SPItem *objitem = dynamic_cast<SPItem *>(object);
543 if (objitem) {
544 SPObject *clip_path = objitem->getClipObject();
545 if (clip_path) {
546 std::vector<SPObject *> clip_path_list = clip_path->childList(true);
547 if (clip_path_list.size()) {
548 for (auto clip : clip_path_list) {
549 SPShape *clipshape = dynamic_cast<SPShape *>(clip);
550 if (clipshape) {
551 clippv = clipshape->curve()->get_pathvector();
552 }
553 }
554 }
555 }
556 }
557 SPGroup *group = dynamic_cast<SPGroup *>(object);
558 if (group) {
559 std::vector<SPItem *> item_list = sp_item_group_item_list(group);
560 for (auto iter : item_list) {
561 if (res.empty()) {
562 res = get_union(SP_OBJECT(iter));
563 } else {
564 res = sp_pathvector_boolop(res, get_union(SP_OBJECT(iter)), to_bool_op(bool_op_ex_union), fill_oddEven,
565 fill_oddEven);
566 }
567 }
568 }
569 SPShape *shape = dynamic_cast<SPShape *>(object);
570 if (shape) {
571 fill_typ originfill = fill_oddEven;
572 SPCurve *curve = shape->curve();
573 if (curve) {
574 if (res.empty()) {
575 res = curve->get_pathvector();
576 } else {
577 res = sp_pathvector_boolop(res, curve->get_pathvector(), to_bool_op(bool_op_ex_union), originfill,
578 GetFillTyp(shape));
579 }
580 }
581 originfill = GetFillTyp(shape);
582 }
583 SPText *text = dynamic_cast<SPText *>(object);
584 if (text) {
585 std::unique_ptr<SPCurve> curve = text->getNormalizedBpath();
586 if (curve) {
587 if (res.empty()) {
588 res = curve->get_pathvector();
589 } else {
590 res = sp_pathvector_boolop(res, curve->get_pathvector(), to_bool_op(bool_op_ex_union), fill_oddEven,
591 fill_oddEven);
592 }
593 }
594 }
595 if (!clippv.empty()) {
596 res = sp_pathvector_boolop(res, clippv, to_bool_op(bool_op_ex_inters), fill_oddEven, fill_oddEven);
597 }
598 return res;
599 }
600
doEffect(SPCurve * curve)601 void LPEBool::doEffect(SPCurve *curve)
602 {
603 Geom::PathVector path_in = curve->get_pathvector();
604 if (operand == SP_ITEM(current_shape)) {
605 g_warning("operand and current shape are the same");
606 operand_path.param_set_default();
607 return;
608 }
609 if (operand_path.getObject() && operand) {
610 bool_op_ex op = bool_operation.get_value();
611 bool swap = swap_operands.get_value();
612
613 Geom::Affine current_affine = sp_lpe_item->transform;
614 Geom::Affine operand_affine = operand->transform;
615 Geom::PathVector operand_pv = get_union(operand);
616 if (operand_pv.empty()) {
617 return;
618 }
619 path_in *= current_affine;
620 operand_pv *= operand_affine;
621
622 Geom::PathVector path_a = swap ? path_in : operand_pv;
623 Geom::PathVector path_b = swap ? operand_pv : path_in;
624 _hp = path_a;
625 _hp.insert(_hp.end(), path_b.begin(), path_b.end());
626 _hp *= current_affine.inverse();
627 fill_typ fill_this = fill_type_this.get_value() != fill_justDont ? fill_type_this.get_value() : GetFillTyp(current_shape);
628 fill_typ fill_operand = fill_type_operand.get_value() != fill_justDont ? fill_type_operand.get_value() : GetFillTyp(operand_path.getObject());
629
630 fill_typ fill_a = swap ? fill_this : fill_operand;
631 fill_typ fill_b = swap ? fill_operand : fill_this;
632
633 if (rmv_inner.get_value()) {
634 path_b = sp_pathvector_boolop_remove_inner(path_b, fill_b);
635 }
636
637 Geom::PathVector path_out;
638 if (op == bool_op_ex_cut) {
639 Geom::PathVector path_tmp = sp_pathvector_boolop(path_a, path_b, to_bool_op(op), fill_a, fill_b);
640 for (auto pathit : path_tmp) {
641 if (pathit.size() != 2) {
642 path_out.push_back(pathit);
643 }
644 }
645 /* } else if (op == bool_op_ex_slice) {
646 path_out = sp_pathvector_boolop_slice_intersect(path_a, path_b, true, fill_a, fill_b);
647 Geom::PathVector path_tmp = sp_pathvector_boolop_slice_intersect(path_a, path_b, false, fill_a, fill_b);
648 for (auto pathit : path_tmp) {
649 path_out.push_back(pathit);
650 }
651 } else if (op == bool_op_ex_slice_inside) {
652 path_out = sp_pathvector_boolop_slice_intersect(path_a, path_b, true, fill_a, fill_b);
653 } else if (op == bool_op_ex_slice_outside) {
654 path_out = sp_pathvector_boolop_slice_intersect(path_a, path_b, false, fill_a, fill_b);
655 */
656 } else {
657 path_out = sp_pathvector_boolop(path_a, path_b, to_bool_op(op), fill_a, fill_b);
658 }
659 /*
660 Maybe add a bit simplify?
661 Geom::PathVector pathv = path_out * current_affine.inverse();
662 gdouble size = Geom::L2(pathv.boundsFast()->dimensions());
663 Path* pathliv = Path_for_pathvector(pathv);
664 pathliv->ConvertEvenLines(0.00002 * size);
665 pathliv->Simplify(0.00002 * size);
666 pathv = Geom::parse_svg_path(pathliv->svg_dump_path()); */
667 curve->set_pathvector(path_out * current_affine.inverse());
668 }
669 }
670
addCanvasIndicators(SPLPEItem const *,std::vector<Geom::PathVector> & hp_vec)671 void LPEBool::addCanvasIndicators(SPLPEItem const * /*lpeitem*/, std::vector<Geom::PathVector> &hp_vec)
672 {
673 hp_vec.push_back(_hp);
674 }
675
doOnRemove(SPLPEItem const *)676 void LPEBool::doOnRemove(SPLPEItem const * /*lpeitem*/)
677 {
678 // set "keep paths" hook on sp-lpe-item.cpp
679 std::vector<SPLPEItem *> lpeitems = getCurrrentLPEItems();
680 if (lpeitems.size() == 1) {
681 sp_lpe_item = lpeitems[0];
682 if (!sp_lpe_item->path_effects_enabled) {
683 return;
684 }
685 SPItem *operand = dynamic_cast<SPItem *>(operand_path.getObject());
686 if (operand) {
687 if (keep_paths) {
688 if (is_visible) {
689 operand->deleteObject(true);
690 }
691 } else {
692 if (is_visible) {
693 remove_filter();
694 }
695 }
696 }
697 }
698 }
699
700 // TODO: Migrate the tree next function to effect.cpp/h to avoid duplication
doOnVisibilityToggled(SPLPEItem const *)701 void LPEBool::doOnVisibilityToggled(SPLPEItem const * /*lpeitem*/)
702 {
703 SPItem *operand = dynamic_cast<SPItem *>(operand_path.getObject());
704 if (operand) {
705 if (!is_visible) {
706 remove_filter();
707 }
708 }
709 }
710
711 } // namespace LivePathEffect
712 } /* namespace Inkscape */
713