inkstitch/lib/svg/clip.py

82 wiersze
2.9 KiB
Python

# 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 shapely.validation import make_valid
from ..elements import EmbroideryElement
from ..utils import ensure_multi_polygon
from .tags import SVG_GROUP_TAG, SVG_PATH_TAG
def get_clips(node):
clips = []
for element in node.iterancestors(SVG_GROUP_TAG):
if element.clip is not None:
clips.append(element.clip)
return clips
def get_clip_path(node):
# get clip and apply node 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)
path_effect = _get_path_effects(node_or_group)
clip_paths = None
if path_effect == 'ignore':
return
elif path_effect == 'inverse':
for path in clip.iterdescendants(SVG_PATH_TAG):
if path.get('class', None) == 'powerclip':
original_transform = path.transform
path.transform @= transform
clip_element = EmbroideryElement(path)
clip_paths = [path for path in clip_element.paths if len(path) > 3]
path.transform = original_transform
break
else:
clip_paths = [path for path in clip_element.paths if len(path) > 3]
if clip_paths:
clip_paths.sort(key=lambda point_list: Polygon(point_list).area, reverse=True)
return make_valid(MultiPolygon([(clip_paths[0], clip_paths[1:])]))
def _get_path_effects(node):
path_effects = node.get('inkscape:path-effect', None)
if path_effects is not None:
path_effects = path_effects.split(';')
for path_effect in path_effects:
effect = node.getroottree().getroot().getElementById(path_effect[1:])
if effect.get('effect', None) == 'powerclip':
if effect.get('hide_clip', 'false') in ['1', 'true', 'True']:
# The clip is inactive
return 'ignore'
elif effect.get('flatten', 'false') in ['1', 'true', 'True']:
# Clipping is already calculated into the path.
# This means we can ignore the clip.
return 'ignore'
elif effect.get('inverse', 'false') in ['1', 'true', 'True']:
return 'inverse'
else:
return 'effect'
return 'standard'