diff --git a/lib/elements/fill_stitch.py b/lib/elements/fill_stitch.py index a2d4855f1..912f3ba62 100644 --- a/lib/elements/fill_stitch.py +++ b/lib/elements/fill_stitch.py @@ -23,6 +23,7 @@ from ..stitches import (auto_fill, circular_fill, contour_fill, guided_fill, legacy_fill) from ..stitches.meander_fill import meander_fill from ..svg import PIXELS_PER_MM, get_node_transform +from ..svg.clip import get_clip_path from ..svg.tags import INKSCAPE_LABEL from ..utils import cache, version from ..utils.param import ParamOption @@ -571,12 +572,9 @@ class FillStitch(EmbroideryElement): if self.node.clip is None: return self.original_shape - from .element import EmbroideryElement - clip_element = EmbroideryElement(self.node.clip) - clip_element.paths.sort(key=lambda point_list: shgeo.Polygon(point_list).area, reverse=True) - polygon = shgeo.MultiPolygon([(clip_element.paths[0], clip_element.paths[1:])]) + clip_path = get_clip_path(self.node) try: - intersection = polygon.intersection(self.original_shape) + intersection = clip_path.intersection(self.original_shape) except TopologicalError: return self.original_shape diff --git a/lib/elements/stroke.py b/lib/elements/stroke.py index 7ec6b76cf..16689901d 100644 --- a/lib/elements/stroke.py +++ b/lib/elements/stroke.py @@ -5,7 +5,7 @@ from math import ceil -import shapely.geometry +import shapely.geometry as shgeo from inkex import Transform from ..i18n import _ @@ -18,7 +18,9 @@ from ..threads import ThreadColor from ..utils import Point, cache from ..utils.param import ParamOption from .element import EmbroideryElement, param +from ..svg.clip import get_clip_path from .validation import ValidationWarning +from shapely.errors import TopologicalError class MultipleGuideLineWarning(ValidationWarning): @@ -349,6 +351,7 @@ class Stroke(EmbroideryElement): def paths(self): path = self.parse_path() flattened = self.flatten(path) + flattened = self._get_clipped_path(flattened) # manipulate invalid path if len(flattened[0]) == 1: @@ -366,8 +369,32 @@ class Stroke(EmbroideryElement): @cache def as_multi_line_string(self): - line_strings = [shapely.geometry.LineString(path) for path in self.paths] - return shapely.geometry.MultiLineString(line_strings) + line_strings = [shgeo.LineString(path) for path in self.paths] + return shgeo.MultiLineString(line_strings) + + def _get_clipped_path(self, paths): + if self.node.clip is None: + return paths + + clip_path = get_clip_path(self.node) + # path to linestrings + line_strings = [shgeo.LineString(path) for path in paths] + try: + intersection = clip_path.intersection(shgeo.MultiLineString(line_strings)) + except TopologicalError: + return paths + + coords = [] + if intersection.is_empty: + return paths + elif isinstance(intersection, shgeo.MultiLineString): + for c in [intersection for intersection in intersection.geoms if isinstance(intersection, shgeo.LineString)]: + coords.append(c.coords) + elif isinstance(intersection, shgeo.LineString): + coords.append(intersection.coords) + else: + return paths + return coords def get_ripple_target(self): command = self.get_command('ripple_target') @@ -431,7 +458,7 @@ class Stroke(EmbroideryElement): # apply max distances max_len_path = [path[0]] for points in zip(path[:-1], path[1:]): - line = shapely.geometry.LineString(points) + line = shgeo.LineString(points) dist = line.length if dist > self.max_stitch_length: num_subsections = ceil(dist / self.max_stitch_length) diff --git a/lib/svg/clip.py b/lib/svg/clip.py new file mode 100644 index 000000000..36c739476 --- /dev/null +++ b/lib/svg/clip.py @@ -0,0 +1,18 @@ +# Authors: see git history +# +# Copyright (c) 2023 Authors +# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details. + +from shapely.geometry import MultiPolygon, Polygon + +from ..elements import EmbroideryElement + + +def get_clip_path(node): + # get clip and apply node transform + clip = node.clip + transform = node.composed_transform() + clip.transform = transform + clip_element = EmbroideryElement(clip) + clip_element.paths.sort(key=lambda point_list: Polygon(point_list).area, reverse=True) + return MultiPolygon([(clip_element.paths[0], clip_element.paths[1:])])