Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
lpe-bool.cpp
Go to the documentation of this file.
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
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>
22
23#include "inkscape.h"
24#include "selection.h"
25#include "selection-chemistry.h"
26#include "snap.h"
27#include "style.h"
28
29#include "display/curve.h"
30#include "helper/geom.h"
31#include "livarot/Path.h"
32#include "livarot/Shape.h"
35#include "object/sp-clippath.h"
36#include "object/sp-defs.h"
37#include "object/sp-root.h"
38#include "object/sp-shape.h"
39#include "object/sp-text.h"
40#include "path/path-boolop.h"
41#include "path/path-util.h"
42#include "svg/svg.h"
43#include "ui/tools/tool-base.h"
44
45namespace Inkscape {
46namespace LivePathEffect {
47
48// Define an extended boolean operation type
49
51 {LPEBool::bool_op_ex_union, N_("union"), "union"},
52 {LPEBool::bool_op_ex_inters, N_("intersection"), "inters"},
53 {LPEBool::bool_op_ex_diff, N_("difference"), "diff"},
54 {LPEBool::bool_op_ex_symdiff, N_("symmetric difference"), "symdiff"},
55 {LPEBool::bool_op_ex_cut, N_("division"), "cut"},
56 {LPEBool::bool_op_ex_cut_both, N_("division both"), "cut-both"},
57 // Note on naming of operations:
58 // bool_op_cut is called "Division" in the manu, see sp_selected_path_cut
59 // bool_op_slice is called "Cut path" in the menu, see sp_selected_path_slice
60 // TODO: this 3 options are commented because dont work properly
61 // maybe in 1.2 can be fixed but need libarot base to do
62 // {LPEBool::bool_op_ex_slice, N_("cut"), "slice"},
63 // {LPEBool::bool_op_ex_slice_inside, N_("cut inside"), "slice-inside"},
64 // {LPEBool::bool_op_ex_slice_outside, N_("cut outside"), "slice-outside"},
65};
66
68
70 { fill_oddEven, N_("even-odd"), "oddeven" },
71 { fill_nonZero, N_("non-zero"), "nonzero" },
72 { fill_positive, N_("positive"), "positive" },
73 { fill_justDont, N_("take from object"), "from-curve" }
74};
75
77
79 : Effect(lpeobject)
80 , operand_item(_("Operand path:"), _("Operand for the boolean operation"), "operand-path", &wr, this)
81 , bool_operation(_("Operation:"), _("Boolean Operation"), "operation", BoolOpConverter, &wr, this, bool_op_ex_union)
82 , swap_operands(_("Swap operands"), _("Swap operands (useful e.g. for difference)"), "swap-operands", &wr, this)
83 , rmv_inner(
84 _("Remove inner"),
85 _("For cut operations: remove inner (non-contour) lines of cutting path to avoid invisible extra points"),
86 "rmv-inner", &wr, this)
87 , fill_type_this(_("Fill type this:"), _("Fill type (winding mode) for this path"), "filltype-this",
89 , fill_type_operand(_("Fill type operand:"), _("Fill type (winding mode) for operand path"), "filltype-operand",
91 , filter("Filter", "Previous filter", "filter", &wr, this, "", true)
92{
96 //registerParameter(&rmv_inner);
100 show_orig_path = true;
103 operand = cast<SPItem>(operand_item.getObject());
104 if (operand) {
106 }
107}
108
109LPEBool::~LPEBool() = default;
110
111bool LPEBool::doOnOpen(SPLPEItem const *lpeitem)
112{
113 if (!is_load || is_applied) {
114 return false;
115 }
116 Glib::ustring version = lpeversion.param_getSVGValue();
117 if (version < "1.2") {
118 lpeversion.param_setValue("1.2", true);
119 }
122 return false;
123}
124
126{
127 return a.piece == b.piece ? a.t < b.t : a.piece < b.piece;
128}
129
132{
133 // This is similar to sp_pathvector_boolop/bool_op_slice, but keeps only edges inside the cutter area.
134 // The code is also based on sp_pathvector_boolop_slice.
135 //
136 // We have two paths on input
137 // - a closed area which is used to cut out pieces from a contour (called area below)
138 // - a contour which is cut into pieces by the border of thr area (called contour below)
139 //
140 // The code below works in the following steps
141 // (a) Convert the area to a shape, so that we can ask the winding number for any point
142 // (b) Add both, the contour and the area to a single shape and intersect them
143 // (c) Find the intersection points between area border and contour (vector toCut)
144 // (d) Split the original contour at the intersection points
145 // (e) check for each contour edge in combined shape if its center is inside the area - if not discard it
146 // (f) create a vector of all inside edges
147 // (g) convert the piece numbers to the piece numbers after applying the cuts
148 // (h) fill a bool vector with information which pieces are in
149 // (i) filter the descr_cmd of the result path with this bool vector
150 //
151 // The main inefficiency here is step (e) because I use a winding function of the area-shape which goes
152 // through the complete edge list for each point I ask for, so effort is n-edges-contour * n-edges-area.
153 // It is tricky to improve this without building into the livarot code.
154 // One way might be to decide at the intersection points which edges touching the intersection points are
155 // in by making a loop through all edges on the intersection vertex. Since this is a directed non intersecting
156 // graph, this should provide sufficient information.
157 // But since I anyway will change this to the new mechanism some time speed is fairly ok, I didn't look into this.
158
159
160 // extract the livarot Paths from the source objects
161 // also get the winding rule specified in the style
162 // Livarot's outline of arcs is broken. So convert the path to linear and cubics only, for which the outline is created correctly.
163 auto contour_path = Path_for_pathvector(pathv_to_linear_and_cubic_beziers(pathva));
165
166 // Shapes from above paths
167 Shape *area_shape = new Shape;
168 Shape *combined_shape = new Shape;
169 Shape *combined_inters = new Shape;
170
171 // Add the area (process to intersection free shape)
172 area_path->ConvertWithBackData(1.0);
173 area_path->Fill(combined_shape, 1);
174
175 // Convert this to a shape with full winding information
176 area_shape->ConvertToShape(combined_shape, frb);
177
178 // Add the contour to the combined path (just add, no winding processing)
179 contour_path->ConvertWithBackData(1.0);
180 contour_path->Fill(combined_shape, 0, true, false, false);
181
182 // Intersect the area and the contour - no fill processing
183 combined_inters->ConvertToShape(combined_shape, fill_justDont);
184
185 // Result path
186 Path *result_path = new Path;
187 result_path->SetBackData(false);
188
189 // Cutting positions for contour
190 std::vector<Path::cut_position> toCut;
191
192 if (combined_inters->hasBackData()) {
193 // should always be the case, but ya never know
194 {
195 for (int i = 0; i < combined_inters->numberOfPoints(); i++) {
196 if (combined_inters->getPoint(i).totalDegree() > 2) {
197 // possibly an intersection
198 // we need to check that at least one edge from the source path is incident to it
199 // before we declare it's an intersection
200 int cb = combined_inters->getPoint(i).incidentEdge[FIRST];
201 int nbOrig = 0;
202 int nbOther = 0;
203 int piece = -1;
204 float t = 0.0;
205 while (cb >= 0 && cb < combined_inters->numberOfEdges()) {
206 if (combined_inters->ebData[cb].pathID == 0) {
207 // the source has an edge incident to the point, get its position on the path
208 piece = combined_inters->ebData[cb].pieceID;
209 if (combined_inters->getEdge(cb).st == i) {
210 t = combined_inters->ebData[cb].tSt;
211 } else {
212 t = combined_inters->ebData[cb].tEn;
213 }
214 nbOrig++;
215 }
216 if (combined_inters->ebData[cb].pathID == 1) {
217 nbOther++; // the cut is incident to this point
218 }
219 cb = combined_inters->NextAt(i, cb);
220 }
221 if (nbOrig > 0 && nbOther > 0) {
222 // point incident to both path and cut: an intersection
223 // note that you only keep one position on the source; you could have degenerate
224 // cases where the source crosses itself at this point, and you wouyld miss an intersection
225 Path::cut_position cutpos;
226 cutpos.piece = piece;
227 cutpos.t = t;
228 toCut.push_back(cutpos);
229 }
230 }
231 }
232 }
233 {
234 // remove the edges from the intersection polygon
235 int i = combined_inters->numberOfEdges() - 1;
236 for (; i >= 0; i--) {
237 if (combined_inters->ebData[i].pathID == 1) {
238 combined_inters->SubEdge(i);
239 } else {
240 const Shape::dg_arete &edge = combined_inters->getEdge(i);
241 const Shape::dg_point &start = combined_inters->getPoint(edge.st);
242 const Shape::dg_point &end = combined_inters->getPoint(edge.en);
243 Geom::Point mid = 0.5 * (start.x + end.x);
244 int wind = area_shape->PtWinding(mid);
245 if (wind == 0) {
246 combined_inters->SubEdge(i);
247 }
248 }
249 }
250 }
251 }
252
253 // create a vector of pieces, which are in the intersection
254 std::vector<Path::cut_position> inside_pieces(combined_inters->numberOfEdges());
255 for (int i = 0; i < combined_inters->numberOfEdges(); i++) {
256 inside_pieces[i].piece = combined_inters->ebData[i].pieceID;
257 // Use the t middle point, this is safe to compare with values from toCut in the presence of roundoff errors
258 inside_pieces[i].t = 0.5 * (combined_inters->ebData[i].tSt + combined_inters->ebData[i].tEn);
259 }
260 std::sort(inside_pieces.begin(), inside_pieces.end(), cmp_cut_position);
261
262 // sort cut positions
263 std::sort(toCut.begin(), toCut.end(), cmp_cut_position);
264
265 // Compute piece ids after ConvertPositionsToMoveTo
266 {
267 int idIncr = 0;
268 std::vector<Path::cut_position>::iterator itPiece = inside_pieces.begin();
269 std::vector<Path::cut_position>::iterator itCut = toCut.begin();
270 while (itPiece != inside_pieces.end()) {
271 while (itCut != toCut.end() && cmp_cut_position(*itCut, *itPiece)) {
272 ++itCut;
273 idIncr += 2;
274 }
275 itPiece->piece += idIncr;
276 ++itPiece;
277 }
278 }
279
280 // Copy the original path to result and cut at the intersection points
281 result_path->Copy(contour_path.get());
282 result_path->ConvertPositionsToMoveTo(toCut.size(), toCut.data()); // cut where you found intersections
283
284 // Create an array of bools which states which pieces are in
285 std::vector<bool> inside_flags(result_path->descr_cmd.size(), false);
286 for (auto & inside_piece : inside_pieces) {
287 inside_flags[ inside_piece.piece ] = true;
288 // also enable the element -1 to get the MoveTo
289 if (inside_piece.piece >= 1) {
290 inside_flags[ inside_piece.piece - 1 ] = true;
291 }
292 }
293
294#if 0 // CONCEPT TESTING
295 //Check if the inside/outside verdict is consistent - just for testing the concept
296 // Retrieve the pieces
297 int nParts = 0;
298 Path **parts = result_path->SubPaths(nParts, false);
299
300 // Each piece should be either fully in or fully out
301 int iPiece = 0;
302 for (int iPart = 0; iPart < nParts; iPart++) {
303 bool andsum = true;
304 bool orsum = false;
305 for (int iCmd = 0; iCmd < parts[iPart]->descr_cmd.size(); iCmd++, iPiece++) {
306 andsum = andsum && inside_flags[ iPiece ];
307 orsum = andsum || inside_flags[ iPiece ];
308 }
309
310 if (andsum != orsum) {
311 g_warning("Inconsistent inside/outside verdict for part=%d", iPart);
312 }
313 }
314 g_free(parts);
315#endif
316
317 // iterate over the commands of a path and keep those which are inside
318 int iDest = 0;
319 for (int iSrc = 0; iSrc < result_path->descr_cmd.size(); iSrc++) {
320 if (inside_flags[iSrc] == inside) {
321 result_path->descr_cmd[iDest++] = result_path->descr_cmd[iSrc];
322 } else {
323 delete result_path->descr_cmd[iSrc];
324 }
325 }
326 result_path->descr_cmd.resize(iDest);
327
328 delete combined_inters;
329 delete combined_shape;
330 delete area_shape;
331
332 auto outres = result_path->MakePathVector();
333 delete result_path;
334
335 return outres;
336}
337
338// remove inner contours
341{
342 Geom::PathVector patht;
344
345 Shape *shape = new Shape;
346 Shape *shapeshape = new Shape;
347 Path *resultp = new Path;
348 resultp->SetBackData(false);
349
350 patha->ConvertWithBackData(0.1);
351 patha->Fill(shape, 0);
352 shapeshape->ConvertToShape(shape, fra);
353 Path *paths[] = { patha.get() };
354 shapeshape->ConvertToForme(resultp, 1, paths);
355
356 delete shape;
357 delete shapeshape;
358
359 auto resultpv = resultp->MakePathVector();
360
361 delete resultp;
362 return resultpv;
363}
364
366{
367 SPCSSAttr *css = sp_repr_css_attr(item->getRepr(), "style");
368 gchar const *val = sp_repr_css_property(css, "fill-rule", nullptr);
369 if (val && strcmp(val, "nonzero") == 0) {
370 return fill_nonZero;
371 } else if (val && strcmp(val, "evenodd") == 0) {
372 return fill_oddEven;
373 } else {
374 return fill_nonZero;
375 }
376}
377
379{
380 auto operand = cast<SPItem>(operand_item.getObject());
381 if (operand) {
383 if (!repr) {
384 return;
385 }
386 SPFilter *filt = operand->style->getFilter();
387 if (filt && filt->getId() && strcmp(filt->getId(), "selectable_hidder_filter") != 0) {
388 filter.param_setValue(filt->getId(), true);
389 }
390 if (!filt || (filt->getId() && strcmp(filt->getId(), "selectable_hidder_filter") != 0)) {
392 sp_repr_css_set_property(css, "filter", "url(#selectable_hidder_filter)");
393 sp_repr_css_change(repr, css, "style");
395 }
396 }
397}
398
400{
401 if (operand) {
403 if (!repr) {
404 return;
405 }
406 SPFilter *filt = operand->style->getFilter();
407 if (filt && (filt->getId() && strcmp(filt->getId(), "selectable_hidder_filter") == 0)) {
409 Glib::ustring filtstr = filter.param_getSVGValue();
410 if (filtstr != "") {
411 Glib::ustring url = "url(#";
412 url += filtstr;
413 url += ")";
414 sp_repr_css_set_property(css, "filter", url.c_str());
415 // blur is removed when no item using it
416 /*SPDocument *document = getSPDoc();
417 SPObject * filterobj = nullptr;
418 if((filterobj = document->getObjectById(filtstr))) {
419 for (auto obj:filterobj->childList(false)) {
420 if (obj) {
421 obj->deleteObject(false);
422 break;
423 }
424 }
425 } */
427 } else {
429 }
430 sp_repr_css_change(repr, css, "style");
432 }
433 }
434}
435
436void
438{
439 if (onremove) {
440 onremove = false;
441 }
442}
443
445{
446 SPDocument *document = getSPDoc();
447 if (!document) {
448 return;
449 }
450
451 _hp.clear();
452 Inkscape::XML::Document *xml_doc = document->getReprDoc();
453 SPObject *elemref = nullptr;
454 Inkscape::XML::Node *boolfilter = nullptr;
455 if (!(elemref = document->getObjectById("selectable_hidder_filter"))) {
456 boolfilter = xml_doc->createElement("svg:filter");
457 boolfilter->setAttribute("id", "selectable_hidder_filter");
458 boolfilter->setAttribute("width", "1");
459 boolfilter->setAttribute("height", "1");
460 boolfilter->setAttribute("x", "0");
461 boolfilter->setAttribute("y", "0");
462 boolfilter->setAttribute("style", "color-interpolation-filters:sRGB;");
463 boolfilter->setAttribute("inkscape:label", "LPE boolean visibility");
464 /* Create <path> */
465 Inkscape::XML::Node *primitive = xml_doc->createElement("svg:feComposite");
466 primitive->setAttribute("id", "boolops_hidder_primitive");
467 primitive->setAttribute("result", "composite1");
468 primitive->setAttribute("operator", "arithmetic");
469 primitive->setAttribute("in2", "SourceGraphic");
470 primitive->setAttribute("in", "BackgroundImage");
471 Inkscape::XML::Node *defs = document->getDefs()->getRepr();
472 defs->addChild(boolfilter, nullptr);
473 Inkscape::GC::release(boolfilter);
474 boolfilter->addChild(primitive, nullptr);
475 Inkscape::GC::release(primitive);
476 } else {
477 for (auto obj : elemref->childList(false)) {
478 if (obj && strcmp(obj->getId(), "boolops_hidder_primitive") != 0) {
479 obj->deleteObject(true);
480 }
481 }
482 }
483 if (!is_load && !isOnClipboard() && operand_item.lperef &&
484 operand_item.lperef->isAttached() && operand_item.lperef.get()->getObject() == nullptr)
485 {
487 return;
488 }
489 auto current_operand = cast<SPItem>(operand_item.getObject());
490 if (onremove && current_operand) {
491 operand_id = current_operand->getId();
492 return;
493 }
494 operand = cast<SPItem>(getSPDoc()->getObjectById(operand_id));
495 if (!operand_item.linksToItem()) {
497 current_operand = cast<SPItem>(operand_item.getObject());
498 }
499 if (!current_operand && !operand) {
500 return;
501 }
502 if (!current_operand) {
504 }
505 if (current_operand && !operand ) {
506 operand_id = current_operand->getId();
507 sp_lpe_item_update_patheffect(sp_lpe_item, false, false, true);
508 return;
509 }
510 if (current_operand && !operand_item.isConnected()) {
511 operand_item.start_listening(current_operand);
512 sp_lpe_item_update_patheffect(sp_lpe_item, false, false, true);
513 return;
514 }
515
516 if (current_operand) {
517 if (!(document->getObjectById(current_operand->getId()))) {
519 operand = nullptr;
520 operand_id = "";
521 current_operand = nullptr;
522 } else {
523 operand_id = current_operand->getId();
524 }
525 }
526 auto operandlpe = cast<SPLPEItem>(operand_item.getObject());
527 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
528 if (desktop) {
530 if (selection && operand && sp_lpe_item && selection->includes(operand) && selection->includes(sp_lpe_item)) {
531 if (operandlpe && operandlpe->hasPathEffectOfType(Inkscape::LivePathEffect::EffectType::BOOL_OP)) {
532 sp_lpe_item_update_patheffect(operandlpe, false, false);
533 }
534 }
535 }
536 if (!current_operand) {
537 if (operand) {
539 }
540 operand = nullptr;
541 operand_id = "";
542 }
543
544 if (current_operand && operand != current_operand) {
545 if (operand) {
547 }
548 operand = current_operand;
550 if (is_load && sp_lpe_item) {
552 }
553 }
554 if (current_operand) {
556 if (is_visible && op != bool_op_ex_cut_both) {
557 add_filter();
558 } else {
559 remove_filter(current_operand);
560 }
561 }
562}
563
564void LPEBool::transform_multiply(Geom::Affine const &postmul, bool /*set*/)
565{
568 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
572 }
573 }
574}
575
577{
579 Geom::PathVector clippv;
580 if (auto objitem = cast<SPItem>(object)) {
581 SPObject *clip_path = objitem->getClipObject();
582 if (clip_path) {
583 std::vector<SPObject *> clip_path_list = clip_path->childList(true);
584 if (clip_path_list.size()) {
585 for (auto clip : clip_path_list) {
586 if (auto clipshape = cast<SPShape>(clip)) {
587 auto curve = _from_original_d
588 ? clipshape->curveForEdit()
589 : clipshape->curve();
590 if (curve) {
591 clippv = curve->get_pathvector();
592 }
593 }
594 }
595 }
596 }
597 }
598 if (auto group = cast<SPGroup>(object)) {
599 std::vector<SPItem *> item_list = group->item_list();
600 for (auto iter : item_list) {
601 Geom::PathVector tmp = get_union(root, iter, _from_original_d);
602 if (res.empty()) {
603 res = std::move(tmp);
604 } else {
606 }
607 }
608 }
609 if (auto shape = cast<SPShape>(object)) {
610 FillRule originfill = fill_oddEven;
611 auto curve = _from_original_d
612 ? shape->curveForEdit()
613 : shape->curve();
614 if (curve) {
615 auto tmp = curve->get_pathvector() * i2anc_affine(shape, root->parent);
616 if (res.empty()) {
617 res = std::move(tmp);
618 } else {
619 res = sp_pathvector_boolop(res, tmp, to_bool_op(bool_op_ex_union), originfill, GetFillTyp(shape));
620 }
621 }
622 originfill = GetFillTyp(shape);
623 }
624 if (auto text = cast<SPText>(object)) {
625 auto curve = text->getNormalizedBpath();
626 curve.transform(i2anc_affine(text, root->parent));
627 Geom::PathVector tmp = curve.get_pathvector();
628 if (res.empty()) {
629 res = std::move(tmp);
630 } else {
632 }
633 }
634 if (!clippv.empty()) {
636 }
637 return res;
638}
639
641{
642 Geom::PathVector path_in = curve->get_pathvector();
643 auto current_operand = cast<SPItem>(operand_item.getObject());
644 if (current_operand == current_shape) {
645 g_warning("operand and current shape are the same");
647 return;
648 }
649 if (onremove) {
650 current_operand = cast<SPItem>(getSPDoc()->getObjectById(operand_id));
651 }
652 if (current_operand) {
654 bool swap = swap_operands.get_value();
655 if (op == bool_op_ex_cut_both) {
656 swap = false;
657 }
658
659 Geom::Affine current_affine = sp_lpe_item->transform;
660 Geom::PathVector operand_pv = get_union(current_operand, current_operand);
661 if (operand_pv.empty()) {
662 return;
663 }
664 path_in *= current_affine;
665
666 Geom::PathVector path_a = swap ? path_in : operand_pv;
667 Geom::PathVector path_b = swap ? operand_pv : path_in;
668 _hp = path_a;
669 _hp.insert(_hp.end(), path_b.begin(), path_b.end());
670 _hp *= current_affine.inverse();
671 auto item = cast<SPItem>(operand_item.getObject());
673 FillRule fill_operand =
675
676 FillRule fill_a = swap ? fill_this : fill_operand;
677 FillRule fill_b = swap ? fill_operand : fill_this;
678
679 if (rmv_inner.get_value()) {
680 path_b = sp_pathvector_boolop_remove_inner(path_b, fill_b);
681 }
682 Geom::PathVector path_out;
683 helperLineSatellites = false;
684 if (op == bool_op_ex_cut) {
685 if (onremove) {
686 path_out = sp_pathvector_boolop(path_a, path_b, to_bool_op(bool_op_ex_diff), fill_a, fill_b);
687 } else {
688 path_out = sp_pathvector_boolop(path_a, path_b, to_bool_op(op), fill_a, fill_b);
689 }
690 /* } else if (op == bool_op_ex_slice) {
691 path_out = sp_pathvector_boolop_slice_intersect(path_a, path_b, true, fill_a, fill_b);
692 Geom::PathVector path_tmp = sp_pathvector_boolop_slice_intersect(path_a, path_b, false, fill_a, fill_b);
693 for (auto pathit : path_tmp) {
694 path_out.push_back(pathit);
695 }
696 } else if (op == bool_op_ex_slice_inside) {
697 path_out = sp_pathvector_boolop_slice_intersect(path_a, path_b, true, fill_a, fill_b);
698 } else if (op == bool_op_ex_slice_outside) {
699 path_out = sp_pathvector_boolop_slice_intersect(path_a, path_b, false, fill_a, fill_b);
700 */
701 } else if (op == bool_op_ex_cut_both){
702 if (onremove) {
703 path_out = sp_pathvector_boolop(path_a, path_b, to_bool_op(bool_op_ex_diff), fill_a, fill_b);
704 } else {
706 path_out = sp_pathvector_boolop(path_a, path_b, bool_op_diff, fill_a, fill_b);
707 auto tmp = sp_pathvector_boolop(path_a, path_b, bool_op_inters, fill_a, fill_b);
708 path_out.insert(path_out.end(),tmp.begin(),tmp.end());
709 /* auto tmp2 = sp_pathvector_boolop(path_a, path_b, (bool_op) bool_op_diff, fill_a, fill_b);
710 path_out.insert(path_out.end(),tmp2.begin(),tmp2.end()); */
711 }
712 } else {
713 path_out = sp_pathvector_boolop(path_a, path_b, (BooleanOp) op, fill_a, fill_b);
714 }
715 curve->set_pathvector(path_out * current_affine.inverse());
716 }
717}
718
719void LPEBool::addCanvasIndicators(SPLPEItem const * /*lpeitem*/, std::vector<Geom::PathVector> &hp_vec)
720{
721 hp_vec.push_back(_hp);
722}
723
725LPEBool::dupleNode(SPObject * origin, Glib::ustring element_type)
726{
728 Inkscape::XML::Node *dest = xml_doc->createElement(element_type.c_str());
729 dest->setAttribute("transform", origin->getAttribute("transform"));
730 dest->setAttribute("d", origin->getAttribute("d"));
731 dest->setAttribute("style", origin->getAttribute("style"));
732 dest->setAttribute("mask", origin->getAttribute("mask"));
733 dest->setAttribute("clip-path", origin->getAttribute("clip-path"));
734 dest->setAttribute("class", origin->getAttribute("class"));
735 dest->setAttribute("style", origin->getAttribute("style"));
736 for (auto iter : origin->style->properties()) {
737 if (iter->style_src != SPStyleSrc::UNSET) {
738 auto key = iter->id();
739 if (key != SPAttr::FONT && key != SPAttr::D && key != SPAttr::MARKER) {
740 const gchar *attr = origin->getAttribute(iter->name().c_str());
741 if (attr) {
742 dest->setAttribute(iter->name(), attr);
743 }
744 }
745 }
746 }
747 return dest;
748}
749
751{
752 // 1.2 feature no need to legacy bool
753 auto operandit_item = cast<SPItem>(operandit);
754 auto operandit_g = cast<SPGroup>(operandit);
755 auto operandit_shape = cast<SPShape>(operandit);
758 //unionpv *= sp_lpe_item->transform;
759 auto divisionit = cast<SPItem>(getSPDoc()->getObjectById(division_id));
760 if (operandit_g) {
761 Inkscape::XML::Node *dest = dupleNode(operandit, "svg:g");
762 dest->setAttribute("transform", nullptr);
763 if (!division_other) {
764 division_other = cast<SPGroup>(sp_lpe_item->parent->appendChildRepr(dest));
768 } else {
769 division_other = cast<SPGroup>(division_other->appendChildRepr(dest));
770 }
771 Inkscape::XML::Node *dest2 = dupleNode(operandit, "svg:g");
772 dest2->setAttribute("transform", nullptr);
773 if (!division_both) {
774 division_both = cast<SPGroup>(sp_lpe_item->parent->appendChildRepr(dest2));
777 } else {
778 division_both = cast<SPGroup>(division_both->appendChildRepr(dest2));
779 }
780
781 for (auto& child: operandit_g->children) {
782 auto item = cast<SPItem>(&child);
783 if (item) {
784 fractureit(item, unionpv);
785 }
786 }
787 }
788 if (operandit_shape) {
789 if (auto c = operandit_shape->curve()) {
790 auto curve = *c;
791 curve.transform(i2anc_affine(operandit_shape, sp_lpe_item->parent));
792 auto intesect = sp_pathvector_boolop(unionpv, curve.get_pathvector(), bool_op_inters, fill_a, fill_b);
793 Inkscape::XML::Node *dest = dupleNode(operandit_shape, "svg:path");
794 dest->setAttribute("d", sp_svg_write_path(intesect));
795 dest->setAttribute("transform", nullptr);
796 if (!division_other) {
797 division_other = cast<SPGroup>(sp_lpe_item->parent);
798 }
799 auto divisionitem = cast<SPItem>(division_other->appendChildRepr(dest));
801 if (division_other_id.empty()) {
802 division_other->reorder(divisionitem, divisionit);
803 division_other_id = Glib::ustring(dest->attribute("id"));
804 }
805 auto operandit_pathv = sp_pathvector_boolop(unionpv, curve.get_pathvector(), bool_op_diff, fill_a, fill_b);
806 Inkscape::XML::Node *dest2 = dupleNode(operandit_shape, "svg:path");
807 dest2->setAttribute("transform", nullptr);
808 dest2->setAttribute("d", sp_svg_write_path(operandit_pathv));
809 if (!division_both) {
810 division_both = cast<SPGroup>(sp_lpe_item->parent);
811 auto divisionitem2 = cast<SPItem>(division_both->appendChildRepr(dest2));
812 division_both->reorder(divisionitem2, divisionitem);
813 } else {
815 }
817 }
818 }
819}
820
821void LPEBool::divisionit(SPObject * operand_a, SPObject * operand_b, Geom::PathVector unionpv)
822{
823 auto operand_a_item = cast<SPItem>(operand_a);
824 auto operand_b_item = cast<SPItem>(operand_b);
825 auto operand_b_g = cast<SPGroup>(operand_b);
826 auto operand_b_shape = cast<SPShape>(operand_b);
829 if (operand_b_g) {
830 Inkscape::XML::Node *dest = dupleNode(operand_b, "svg:g");
831 dest->setAttribute("transform", nullptr);
832 if (!division) {
833 division = cast<SPGroup>(sp_lpe_item->parent->appendChildRepr(dest));
837 } else {
838 division = cast<SPGroup>(division->appendChildRepr(dest));
839 }
840 for (auto& child: operand_b_g->children) {
841 auto item = cast<SPItem>(&child);
842 if (item) {
843 divisionit(operand_a, item, unionpv);
844 }
845 }
846 }
847 if (operand_b_shape) {
848 if (!division) {
849 division = cast<SPGroup>(sp_lpe_item->parent);
850 }
851 if (auto c = operand_b_shape->curveForEdit()) {
852 auto curve = *c;
853 curve.transform(i2anc_affine(operand_b_shape, sp_lpe_item->parent));
854 auto intesect = sp_pathvector_boolop(unionpv, curve.get_pathvector(), bool_op_inters, fill_a, fill_b);
855 Inkscape::XML::Node *dest = dupleNode(operand_b_shape, "svg:path");
856 dest->setAttribute("d", sp_svg_write_path(intesect));
857 dest->setAttribute("transform", nullptr);
858 auto item = cast<SPItem>(division->appendChildRepr(dest));
860 if (item && division_id.empty()) {
862 }
863 }
864 }
865}
866
867void LPEBool::doOnRemove(SPLPEItem const * lpeitem)
868{
869 // set "keep paths" hook on sp-lpe-item.cpp
871 auto operand = cast<SPItem>(getSPDoc()->getObjectById(operand_id));
872 if (operand) {
873 if (keep_paths) {
875 if (op == bool_op_ex_cut || op == bool_op_ex_cut_both) {
876 reverse = lpeitem->pos_in_parent() > operand->pos_in_parent();
877 division = nullptr;
879 divisionit(operand, sp_lpe_item, unionpv);
880 onremove = true;
882 if (op == bool_op_ex_cut_both) {
883 auto * a = cast<SPItem>(getSPDoc()->getObjectById(division_id));
884 if (a) {
885 unionpv = get_union(sp_lpe_item, sp_lpe_item, true);
886 fractureit(operand, unionpv);
887 auto * b = cast<SPItem>(getSPDoc()->getObjectById(division_other_id));
888 if (a && b) {
889 if (reverse) {
890 b->lowerOne();
891 }
892 }
893 }
894 }
895 // we reset variables because LPE is not removed on undo so I need to get clean on redo
896 division = nullptr;
897 division_both = nullptr;
898 division_other = nullptr;
899 operand_id = "";
900 division_id = "";
902 onremove = false;
903 }
904 if (is_visible) {
906 }
907 }
908 }
909}
910
911// TODO: Migrate the tree next function to effect.cpp/h to avoid duplication
913{
914 auto operand = cast<SPItem>(operand_item.getObject());
915 if (operand) {
916 if (!is_visible) {
918 }
919 }
920}
921
922} // namespace LivePathEffect
923} /* namespace Inkscape */
Path - a sequence of contiguous curves.
FillRule
Definition LivarotDefs.h:67
@ fill_oddEven
Definition LivarotDefs.h:68
@ fill_nonZero
Definition LivarotDefs.h:69
@ fill_justDont
Definition LivarotDefs.h:71
@ fill_positive
Definition LivarotDefs.h:70
BooleanOp
Definition LivarotDefs.h:76
@ bool_op_diff
Definition LivarotDefs.h:79
@ bool_op_inters
Definition LivarotDefs.h:78
@ FIRST
Definition LivarotDefs.h:91
TODO: insert short description here.
TODO: insert short description here.
Point origin
Definition aa.cpp:227
3x3 affine transformation matrix.
Bezier curve.
3x3 matrix representing an affine transformation.
Definition affine.h:70
Affine inverse() const
Compute the inverse matrix.
Definition affine.cpp:388
Sequence of subpaths.
Definition pathvector.h:122
void clear()
Remove all paths from the vector.
Definition pathvector.h:195
bool empty() const
Check whether the vector contains any paths.
Definition pathvector.h:145
iterator insert(iterator pos, Path const &p)
Definition pathvector.h:179
iterator begin()
Definition pathvector.h:151
iterator end()
Definition pathvector.h:152
Sequence of contiguous curves, aka spline.
Definition path.h:353
size_type size() const
Natural size of the path.
Definition path.h:490
Two-dimensional point that doubles as a vector.
Definition point.h:66
constexpr Coord x() const noexcept
Definition point.h:104
void registerParameter(Parameter *param)
Definition effect.cpp:1710
virtual void processObjects(LPEAction lpe_action)
Definition effect.cpp:1416
bool isOnClipboard()
The lpe is on clipboard.
Definition effect.cpp:1248
void param_setValue(Glib::ustring newvalue, bool write=false)
Definition hidden.cpp:71
Glib::ustring param_getSVGValue() const override
Definition hidden.cpp:53
void doOnVisibilityToggled(SPLPEItem const *) override
Definition lpe-bool.cpp:912
EnumParam< FillRule > fill_type_this
Definition lpe-bool.h:70
EnumParam< FillRule > fill_type_operand
Definition lpe-bool.h:71
Inkscape::XML::Node * dupleNode(SPObject *origin, Glib::ustring element_type)
Definition lpe-bool.cpp:725
friend BooleanOp to_bool_op(bool_op_ex val)
Definition lpe-bool.h:57
Geom::PathVector get_union(SPObject *root, SPObject *object, bool prefear_original=false)
Definition lpe-bool.cpp:576
void divisionit(SPObject *operand_a, SPObject *operand_b, Geom::PathVector unionpv)
Definition lpe-bool.cpp:821
void fractureit(SPObject *operandit, Geom::PathVector unionpv)
Definition lpe-bool.cpp:750
void doAfterEffect(SPLPEItem const *lpeitem, SPCurve *curve) override
Is performed at the end of the LPE only one time per "lpeitem" in paths/shapes is called in middle of...
Definition lpe-bool.cpp:437
void doBeforeEffect(SPLPEItem const *lpeitem) override
Is performed each time before the effect is updated.
Definition lpe-bool.cpp:444
EnumParam< bool_op_ex > bool_operation
Definition lpe-bool.h:69
void doEffect(SPCurve *curve) override
Definition lpe-bool.cpp:640
void remove_filter(SPObject *object)
Definition lpe-bool.cpp:399
OriginalSatelliteParam operand_item
Definition lpe-bool.h:68
void addCanvasIndicators(SPLPEItem const *lpeitem, std::vector< Geom::PathVector > &hp_vec) override
Add possible canvas indicators (i.e., helperpaths other than the original path) to hp_vec This functi...
Definition lpe-bool.cpp:719
LPEBool(LivePathEffectObject *lpeobject)
Definition lpe-bool.cpp:78
void transform_multiply(Geom::Affine const &postmul, bool set) override
Overridden function to apply transforms for example to powerstroke, jointtype or tapperstroke.
Definition lpe-bool.cpp:564
void doOnRemove(SPLPEItem const *) override
Definition lpe-bool.cpp:867
bool doOnOpen(SPLPEItem const *lpeitem) override
Is performed on load document or revert If the item is fixed legacy return true.
Definition lpe-bool.cpp:111
std::shared_ptr< SatelliteReference > lperef
Definition satellite.h:58
The set of selected SPObjects for a given document and layer model.
Definition selection.h:80
bool includes(XML::Node *repr, bool anyAncestor=false)
Returns true if the given item is selected.
Definition selection.h:140
Simplified management of enumerations of svg items with UI labels.
Definition enums.h:42
Interface for refcounted XML nodes.
Definition node.h:80
virtual void addChild(Node *child, Node *after)=0
Insert another node as a child of this node.
void setAttribute(Util::const_char_ptr key, Util::const_char_ptr value)
Change an attribute of this node.
Definition node.cpp:25
virtual char const * attribute(char const *key) const =0
Get the string representation of a node's attribute.
Wrapper around a Geom::PathVector object.
Definition curve.h:26
To do: update description of desktop.
Definition desktop.h:149
Inkscape::Selection * getSelection() const
Definition desktop.h:188
Typed SVG document implementation.
Definition document.h:103
SPObject * getObjectById(std::string const &id) const
SPDefs * getDefs()
Return the main defs object for the document.
Definition document.cpp:245
Inkscape::XML::Document * getReprDoc()
Our Inkscape::XML::Document.
Definition document.h:213
Base class for visual SVG elements.
Definition sp-item.h:109
Geom::Affine transform
Definition sp-item.h:138
unsigned int pos_in_parent() const
Definition sp-item.cpp:1076
void doWriteTransform(Geom::Affine const &transform, Geom::Affine const *adv=nullptr, bool compensate=true)
Set a new transform on an object.
Definition sp-item.cpp:1658
bool pathEffectsEnabled() const
SPObject is an abstract base class of all of the document nodes at the SVG document level.
Definition sp-object.h:160
std::vector< SPObject * > childList(bool add_ref, Action action=ActionGeneral)
Retrieves the children as a std vector object, optionally ref'ing the children in the process,...
SPDocument * document
Definition sp-object.h:188
char const * getId() const
Returns the objects current ID string.
SPStyle * style
Represents the style properties, whether from presentation attributes, the style attribute,...
Definition sp-object.h:248
SPObject * parent
Definition sp-object.h:189
void reorder(SPObject *obj, SPObject *prev)
In list of object's children, move object behind prev.
Inkscape::XML::Node * getRepr()
Returns the XML representation of tree.
SPObject * appendChildRepr(Inkscape::XML::Node *repr)
Append repr as child of this object.
SPFilter * getFilter()
Definition style.h:335
A class to store/manipulate directed graphs.
Definition Shape.h:65
void ConvertToForme(Path *dest)
Extract contours from a directed graph.
Definition ShapeMisc.cpp:42
int PtWinding(const Geom::Point px) const
Definition Shape.cpp:2001
std::vector< back_data > ebData
Definition Shape.h:422
int numberOfEdges() const
Returns number of edges.
Definition Shape.h:498
int NextAt(int p, int b) const
Definition Shape.h:190
int numberOfPoints() const
Returns number of points.
Definition Shape.h:484
dg_arete const & getEdge(int n) const
Get an edge.
Definition Shape.h:548
int ConvertToShape(Shape *a, FillRule directed=fill_nonZero, bool invert=false)
Using a given fill rule, find all intersections in the shape given, create a new intersection free sh...
dg_point const & getPoint(int n) const
Get a point.
Definition Shape.h:537
bool hasBackData() const
Do we have back data?
Definition Shape.h:526
void SubEdge(int e)
Definition Shape.cpp:1177
RootCluster root
std::shared_ptr< Css const > css
double c[8][4]
Geom::PathVector pathv_to_linear_and_cubic_beziers(Geom::PathVector const &pathv)
Definition geom.cpp:588
Specific geometry functions for Inkscape, not provided my lib2geom.
SPItem * item
Geom::Point start
Geom::Point end
vector< vector< Point > > paths
Definition metro.cpp:36
Affine identity()
Create an identity matrix.
Definition affine.h:210
bool clip(std::vector< RatQuad > &rq, const xAx &cs, const Rect &R)
static R & release(R &r)
Decrements the reference count of a anchored object.
static const Util::EnumDataConverter< LPEBool::bool_op_ex > BoolOpConverter(BoolOpData, sizeof(BoolOpData)/sizeof(*BoolOpData))
Geom::PathVector sp_pathvector_boolop_slice_intersect(Geom::PathVector const &pathva, Geom::PathVector const &pathvb, bool inside, FillRule fra, FillRule frb)
Definition lpe-bool.cpp:131
static const Util::EnumData< LPEBool::bool_op_ex > BoolOpData[LPEBool::bool_op_ex_count]
Definition lpe-bool.cpp:50
bool cmp_cut_position(const Path::cut_position &a, const Path::cut_position &b)
Definition lpe-bool.cpp:125
static const Util::EnumDataConverter< FillRule > FillTypeConverter(FillTypeData, sizeof(FillTypeData)/sizeof(*FillTypeData))
Geom::PathVector sp_pathvector_boolop_remove_inner(Geom::PathVector const &pathva, FillRule fra)
Definition lpe-bool.cpp:340
static FillRule GetFillTyp(SPItem *item)
Definition lpe-bool.cpp:365
static const Util::EnumData< FillRule > FillTypeData[]
Definition lpe-bool.cpp:69
Helper class to stream background task notifications as a series of messages.
static cairo_user_data_key_t key
Geom::PathVector sp_pathvector_boolop(Geom::PathVector const &pathva, Geom::PathVector const &pathvb, BooleanOp bop, FillRule fra, FillRule frb)
Perform a boolean operation on two pathvectors.
Boolean operations.
TODO: insert short description here.
callback interface for SVG path data
std::unique_ptr< Path > Path_for_pathvector(Geom::PathVector const &pathv)
Creates a Livarot Path object from the Geom::PathVector.
Definition path-util.cpp:25
Path utilities.
Ocnode * child[8]
Definition quantize.cpp:33
SPCSSAttr * sp_repr_css_attr_new()
Creates an empty SPCSSAttr (a class for manipulating CSS style properties).
Definition repr-css.cpp:67
void sp_repr_css_change(Node *repr, SPCSSAttr *css, gchar const *attr)
Creates a new SPCSAttr with the values filled from a repr, merges in properties from the given SPCSAt...
Definition repr-css.cpp:358
void sp_repr_css_attr_unref(SPCSSAttr *css)
Unreferences an SPCSSAttr (will be garbage collected if no references remain).
Definition repr-css.cpp:76
SPCSSAttr * sp_repr_css_attr(Node const *repr, gchar const *attr)
Creates a new SPCSSAttr with one attribute (i.e.
Definition repr-css.cpp:88
char const * sp_repr_css_property(SPCSSAttr *css, gchar const *name, gchar const *defval)
Returns a character string of the value of a given style property or a default value if the attribute...
Definition repr-css.cpp:147
void sp_repr_css_unset_property(SPCSSAttr *css, gchar const *name)
Set a style property to "inkscape:unset".
Definition repr-css.cpp:202
void sp_repr_css_set_property(SPCSSAttr *css, gchar const *name, gchar const *value)
Set a style property to a new value (e.g.
Definition repr-css.cpp:191
Geom::Affine sp_item_transform_repr(SPItem *item)
Find out the inverse of previous transform of an item (from its repr)
Definition sp-item.cpp:1500
Geom::Affine i2anc_affine(SPObject const *object, SPObject const *ancestor)
Definition sp-item.cpp:1787
void sp_lpe_item_update_patheffect(SPLPEItem *lpeitem, bool wholetree, bool write, bool with_satellites)
Calls any registered handlers for the update_patheffect action.
SPRoot: SVG <svg> implementation.
Simplified management of enumerations of svg items with UI labels.
Definition enums.h:27
Interface for XML documents.
Definition document.h:43
virtual Node * createElement(char const *name)=0
An edge in the directed graph.
Definition Shape.h:462
A point or vertex in the directed graph.
Definition Shape.h:448
int incidentEdge[2]
Definition Shape.h:452
int totalDegree() const
Definition Shape.h:455
Definition curve.h:24
SPStyle - a style object for SPItem objects.
parse SVG path specifications
static void sp_svg_write_path(Inkscape::SVG::PathString &str, Geom::Path const &p, bool normalize=false)
Definition svg-path.cpp:109
SPDesktop * desktop