summaryrefslogtreecommitdiff
path: root/lib/svg/clip.py
blob: b8c97894bc6fc0643e8f30479483922c59aad0f3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# 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'