summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKaalleen <36401965+kaalleen@users.noreply.github.com>2024-11-12 19:07:24 +0100
committerGitHub <noreply@github.com>2024-11-12 19:07:24 +0100
commit45dda2616d4d636759136e2c65998670ea06855c (patch)
tree0dd33c7ab51cb69b0b6128df0095c5f84e876f2b
parent7c99a138d12bcd5f9f4cdbfd3ff7c2b15ec890e4 (diff)
Clipped groups (#3261)
-rw-r--r--lib/elements/fill_stitch.py5
-rw-r--r--lib/elements/stroke.py18
-rw-r--r--lib/lettering/font.py24
-rw-r--r--lib/lettering/glyph.py18
-rw-r--r--lib/marker.py2
-rw-r--r--lib/svg/clip.py26
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:])]))