diff options
| author | Kaalleen <36401965+kaalleen@users.noreply.github.com> | 2024-11-12 19:07:24 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-11-12 19:07:24 +0100 |
| commit | 45dda2616d4d636759136e2c65998670ea06855c (patch) | |
| tree | 0dd33c7ab51cb69b0b6128df0095c5f84e876f2b | |
| parent | 7c99a138d12bcd5f9f4cdbfd3ff7c2b15ec890e4 (diff) | |
Clipped groups (#3261)
| -rw-r--r-- | lib/elements/fill_stitch.py | 5 | ||||
| -rw-r--r-- | lib/elements/stroke.py | 18 | ||||
| -rw-r--r-- | lib/lettering/font.py | 24 | ||||
| -rw-r--r-- | lib/lettering/glyph.py | 18 | ||||
| -rw-r--r-- | lib/marker.py | 2 | ||||
| -rw-r--r-- | lib/svg/clip.py | 26 |
6 files changed, 74 insertions, 19 deletions
diff --git a/lib/elements/fill_stitch.py b/lib/elements/fill_stitch.py index c539cf7e..4e5503ca 100644 --- a/lib/elements/fill_stitch.py +++ b/lib/elements/fill_stitch.py @@ -782,10 +782,9 @@ class FillStitch(EmbroideryElement): return ensure_multi_polygon(set_precision(shape, 0.00000000001), 3) def _get_clipped_path(self): - if self.node.clip is None: - return self.original_shape - clip_path = get_clip_path(self.node) + if clip_path is None: + return self.original_shape # make sure clip path and shape are valid clip_path = make_valid(clip_path) diff --git a/lib/elements/stroke.py b/lib/elements/stroke.py index 925a7dcd..d41d74db 100644 --- a/lib/elements/stroke.py +++ b/lib/elements/stroke.py @@ -423,9 +423,19 @@ class Stroke(EmbroideryElement): @property def paths(self): + return self._get_paths() + + @property + def unclipped_paths(self): + return self._get_paths(False) + + def _get_paths(self, clipped=True): path = self.parse_path() flattened = self.flatten(path) - flattened = self._get_clipped_path(flattened) + if clipped: + flattened = self._get_clipped_path(flattened) + if flattened is None: + return [] # manipulate invalid path if len(flattened[0]) == 1: @@ -447,10 +457,10 @@ class Stroke(EmbroideryElement): return shgeo.MultiLineString(line_strings) def _get_clipped_path(self, paths): - if self.node.clip is None: + clip_path = get_clip_path(self.node) + if clip_path is None: return paths - clip_path = get_clip_path(self.node) # path to linestrings line_strings = [shgeo.LineString(path) for path in paths] try: @@ -460,7 +470,7 @@ class Stroke(EmbroideryElement): coords = [] if intersection.is_empty: - return paths + return None elif isinstance(intersection, shgeo.MultiLineString): for c in [intersection for intersection in intersection.geoms if isinstance(intersection, shgeo.LineString)]: coords.append(c.coords) diff --git a/lib/lettering/font.py b/lib/lettering/font.py index e1f0962b..750ef73f 100644 --- a/lib/lettering/font.py +++ b/lib/lettering/font.py @@ -222,7 +222,7 @@ class Font(object): glyph_set = glyph_sets[i % 2] line = line.strip() - letter_group = self._render_line(line, position, glyph_set) + letter_group = self._render_line(destination_group, line, position, glyph_set) if (back_and_forth and self.reversible and i % 2 == 1) or variant == '←': letter_group[:] = reversed(letter_group) destination_group.append(letter_group) @@ -262,7 +262,7 @@ class Font(object): def get_variant(self, variant): return self.variants.get(variant, self.variants[self.default_variant]) - def _render_line(self, line, position, glyph_set): + def _render_line(self, destination_group, line, position, glyph_set): """Render a line of text. An SVG XML node tree will be returned, with an svg:g at its root. If @@ -299,14 +299,14 @@ class Font(object): glyph = glyph_set[self.default_glyph] if glyph is not None: - node = self._render_glyph(glyph, position, character, last_character) + node = self._render_glyph(destination_group, glyph, position, character, last_character) group.append(node) last_character = character return group - def _render_glyph(self, glyph, position, character, last_character): + def _render_glyph(self, destination_group, glyph, position, character, last_character): """Render a single glyph. An SVG XML node tree will be returned, with an svg:g at its root. @@ -341,6 +341,7 @@ class Font(object): position.x += self.horiz_adv_x.get(character, horiz_adv_x_default) - glyph.min_x self._update_commands(node, glyph) + self._update_clips(destination_group, node, glyph) # this is used to recognize a glyph layer later in the process # because this is not unique it will be overwritten by inkscape when inserted into the document @@ -366,6 +367,14 @@ class Font(object): c.set(CONNECTION_END, "#%s" % new_element_id) c.set(CONNECTION_START, "#%s" % new_symbol_id) + def _update_clips(self, destination_group, node, glyph): + svg = destination_group.getroottree().getroot() + for node_id, clip in glyph.clips.items(): + if clip not in svg.defs: + svg.defs.append(clip) + el = node.find(f".//*[@id='{node_id}']") + el.clip = clip + def _add_trims(self, destination_group, text, trim_option, use_trim_symbols, back_and_forth): """ trim_option == 0 --> no trims @@ -500,16 +509,19 @@ class Font(object): glyph_group = ancestor break element.transform = element.composed_transform(glyph_group.getparent()) - element.apply_transform() + if sort_index is not None and int(sort_index) in self.combine_at_sort_indices: + element.apply_transform() if not sort_index: elements_by_color[404].append([element]) continue parent = element.getparent() + if element.clip is None and parent.clip is not None: + element.clip = parent.clip if last_parent != parent or int(sort_index) not in elements_by_color or not is_grouped_with_marker(element): elements_by_color[int(sort_index)].append([element]) else: elements_by_color[int(sort_index)][-1].append(element) - last_parent = element.getparent() + last_parent = parent return elements_by_color diff --git a/lib/lettering/glyph.py b/lib/lettering/glyph.py index 0b916fc0..a3a9df71 100644 --- a/lib/lettering/glyph.py +++ b/lib/lettering/glyph.py @@ -3,6 +3,7 @@ # Copyright (c) 2010 Authors # Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details. +from collections import defaultdict from copy import copy from inkex import paths, transforms, units @@ -36,11 +37,20 @@ class Glyph(object): """ self._process_baseline(group.getroottree().getroot()) + self.clips = self._process_clips(group) self.node = self._process_group(group) self._process_bbox() self._move_to_origin() self._process_commands() + def _process_clips(self, group): + clips = defaultdict(list) + for node in group.iterdescendants(): + if node.clip: + node_id = node.get_id() + clips[node_id] = node.clip + return clips + def _process_group(self, group): new_group = copy(group) # new_group.attrib.pop('transform', None) @@ -124,3 +134,11 @@ class Glyph(object): x, y = transform.apply_to_point((oldx, oldy)) node.set('x', x) node.set('y', y) + + # transform clips + for node in self.node.iterdescendants(): + node_id = node.get_id() + if node_id in self.clips: + clip = self.clips[node_id] + for childnode in clip.iterchildren(): + childnode.transform @= transform diff --git a/lib/marker.py b/lib/marker.py index 6a5f71e6..dd0a27bf 100644 --- a/lib/marker.py +++ b/lib/marker.py @@ -63,7 +63,7 @@ def get_marker_elements(node, marker, get_fills=True, get_strokes=True, get_sati fills.append(fill) if get_strokes and stroke is not None: - stroke = Stroke(marker).paths + stroke = Stroke(marker).unclipped_paths line_strings = [shgeo.LineString(path) for path in stroke] strokes.append(shgeo.MultiLineString(line_strings)) diff --git a/lib/svg/clip.py b/lib/svg/clip.py index f2c77222..3fcffa4e 100644 --- a/lib/svg/clip.py +++ b/lib/svg/clip.py @@ -4,18 +4,34 @@ # Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details. from shapely.geometry import MultiPolygon, Polygon +from shapely.validation import make_valid from ..elements import EmbroideryElement +from ..utils import ensure_multi_polygon +from .tags import SVG_GROUP_TAG def get_clip_path(node): # get clip and apply node transform - clip = node.clip - transform = node.composed_transform() + clip = _clip_paths(node) + for group in node.iterancestors(SVG_GROUP_TAG): + group_clip = _clip_paths(group) + if clip and group_clip: + clip = clip.intersection(group_clip) + elif group_clip: + clip = group_clip + if clip: + return ensure_multi_polygon(clip) + + +def _clip_paths(node_or_group): + clip = node_or_group.clip + if clip is None: + return + transform = node_or_group.composed_transform() clip.transform = transform clip_element = EmbroideryElement(clip) clip_paths = [path for path in clip_element.paths if len(path) > 3] - clip_paths.sort(key=lambda point_list: Polygon(point_list).area, reverse=True) if clip_paths: - return MultiPolygon([(clip_paths[0], clip_paths[1:])]) - return MultiPolygon() + clip_paths.sort(key=lambda point_list: Polygon(point_list).area, reverse=True) + return make_valid(MultiPolygon([(clip_paths[0], clip_paths[1:])])) |
