summaryrefslogtreecommitdiff
path: root/lib/extensions/jump_to_stroke.py
blob: 1487de4d7174cc166f9db52b4b3953963af65dba (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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# 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 inkex import (Boolean, DirectedLineSegment, Path, PathElement, Transform,
                   errormsg)

from ..elements import Stroke
from ..i18n import _
from ..svg import PIXELS_PER_MM, get_correction_transform
from ..svg.tags import INKSTITCH_ATTRIBS, SVG_GROUP_TAG
from .base import InkstitchExtension


class JumpToStroke(InkstitchExtension):
    """Adds a running stitch as a connection between two (or more) selected elements.
       The elements must have the same color and a minimum distance (collapse_len)."""

    def __init__(self, *args, **kwargs):
        InkstitchExtension.__init__(self, *args, **kwargs)
        self.arg_parser.add_argument("--tab")

        self.arg_parser.add_argument("-i", "--minimum-jump-length", type=float, default=3.0, dest="min_jump")
        self.arg_parser.add_argument("-a", "--maximum-jump-length", type=float, default=0, dest="max_jump")
        self.arg_parser.add_argument("--connect", type=str, default="all", dest="connect")
        self.arg_parser.add_argument("--exclude-trim", type=Boolean, default=True, dest="exclude_trim")
        self.arg_parser.add_argument("--exclude-stop", type=Boolean, default=True, dest="exclude_stop")
        self.arg_parser.add_argument("--exclude-force-lock-stitch", type=Boolean, default=True, dest="exclude_forced_lock")

        self.arg_parser.add_argument("-m", "--merge", type=Boolean, default=False, dest="merge")
        self.arg_parser.add_argument("-l", "--stitch-length", type=float, default=2.5, dest="running_stitch_length_mm")
        self.arg_parser.add_argument("-t", "--tolerance", type=float, default=2.0, dest="running_stitch_tolerance_mm")

    def effect(self):
        if not self.svg.selection or not self.get_elements() or len(self.elements) < 2:
            errormsg(_("Please select at least two elements to convert the jump stitch to a running stitch."))
            return

        last_group = None
        last_layer = None
        last_element = None
        last_stitch_group = None
        for element in self.elements:
            group = None
            layer = None
            for ancestor in element.node.iterancestors(SVG_GROUP_TAG):
                if group is None:
                    group = ancestor
                if ancestor.groupmode == "layer":
                    layer = ancestor
                    break

            stitch_group = element.to_stitch_groups(last_stitch_group)

            if (last_stitch_group is None or
                    element.color != last_element.color or
                    (self.options.connect == "layer" and last_layer != layer) or
                    (self.options.connect == "group" and last_group != group) or
                    (self.options.exclude_trim and (last_element.has_command("trim") or last_element.trim_after)) or
                    (self.options.exclude_stop and (last_element.has_command("stop") or last_element.stop_after)) or
                    (self.options.exclude_forced_lock and last_element.force_lock_stitches)):
                last_layer = layer
                last_group = group
                last_stitch_group = stitch_group[-1]
                last_element = element
                continue

            start = last_stitch_group.stitches[-1]
            end = stitch_group[-1].stitches[0]
            self.generate_stroke(last_element, element, start, end)

            last_group = group
            last_layer = layer
            last_stitch_group = stitch_group[-1]
            last_element = element

    def generate_stroke(self, last_element, element, start, end):
        node = element.node
        parent = node.getparent()
        index = parent.index(node)

        # do not add a running stitch if the distance is smaller than min_jump setting
        line = DirectedLineSegment((start.x, start.y), (end.x, end.y))
        if line.length < self.options.min_jump * PIXELS_PER_MM:
            return
        # do not add a running stitch if the distance is longer than max_jump setting
        if self.options.max_jump > 0 and line.length > self.options.max_jump * PIXELS_PER_MM:
            return

        path = Path([(start.x, start.y), (end.x, end.y)])
        # option: merge line with paths
        merged = False
        if self.options.merge and isinstance(last_element, Stroke) and last_element.node.TAG == "path":
            path.transform(Transform(get_correction_transform(last_element.node)), True)
            path = last_element.node.get_path() + path[1:]
            last_element.node.set('d', str(path))
            path.transform(-Transform(get_correction_transform(last_element.node)), True)
            merged = True
        if self.options.merge and isinstance(element, Stroke) and node.TAG == "path":
            path.transform(Transform(get_correction_transform(node)), True)
            path = path + node.get_path()[1:]
            node.set('d', str(path))
            if merged:
                # remove last element (since it is merged)
                last_parent = last_element.node.getparent()
                last_parent.remove(last_element.node)
                # remove parent group if empty
                if len(last_parent) == 0:
                    last_parent.getparent().remove(last_parent)
            return

        if merged:
            return

        # add simple stroke to connect elements
        path.transform(Transform(get_correction_transform(node)), True)
        color = element.color
        style = f'stroke:{color};stroke-width:1px;stroke-dasharray:3, 1;fill:none;'

        line = PathElement(d=str(path), style=style)
        line.set(INKSTITCH_ATTRIBS['running_stitch_length_mm'], self.options.running_stitch_length_mm)
        line.set(INKSTITCH_ATTRIBS['running_stitch_tolerance_mm'], self.options.running_stitch_tolerance_mm)
        parent.insert(index, line)


if __name__ == '__main__':
    JumpToStroke().run()