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
|
# Authors: see git history
#
# Copyright (c) 2025 Authors
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
import json
from typing import Optional
from inkex import (Boolean, PathElement, ShapeElement, Transform, Vector2d,
errormsg)
from ..i18n import _
from ..tartan.utils import get_tartan_settings
from .base import InkstitchExtension
class TransformElements(InkstitchExtension):
'''
This will apply transformations while also transforming fill angles.
'''
def __init__(self, *args, **kwargs):
InkstitchExtension.__init__(self, *args, **kwargs)
self.arg_parser.add_argument("--notebook")
self.arg_parser.add_argument("-r", "--rotate", type=float, default=0, dest="rotate")
self.arg_parser.add_argument("-f", "--flip-horizontally", type=Boolean, default=False, dest="horizontal_flip")
self.arg_parser.add_argument("-v", "--flip-vertically", type=Boolean, default=False, dest="vertical_flip")
def effect(self) -> None:
if not self.svg.selection:
errormsg(_("Please select one or more elements."))
return
selection_center = self.svg.selection.bounding_box().center
nodes = self.get_nodes()
for node in nodes:
parent_transform = node.composed_transform() @ -node.transform
if self.options.rotate != 0:
self.rotate_node(node, parent_transform, selection_center)
if self.options.horizontal_flip:
self.flip_node_horizontally(node, parent_transform, selection_center)
if self.options.vertical_flip:
self.flip_node_vertically(node, parent_transform, selection_center)
# Apply transform to path elements, simply because it's possible and nicer
if isinstance(node, PathElement):
node.apply_transform()
def flip_node_vertically(self, node: ShapeElement, parent_transform: Transform, center: Vector2d) -> None:
node.transform = (
-parent_transform @
Transform(f'translate({center[0], center[1]}) scale(1, -1) translate({-center[0], -center[1]})') @
node.composed_transform()
)
self.adapt_fill_angle(node, -1)
def flip_node_horizontally(self, node: ShapeElement, parent_transform: Transform, center: Vector2d) -> None:
node.transform = (
-parent_transform @
Transform(f'translate({center[0], center[1]}) scale(-1, 1) translate({-center[0], -center[1]})') @
node.composed_transform()
)
self.adapt_fill_angle(node, -1)
def rotate_node(self, node: ShapeElement, parent_transform: Transform, center: Vector2d) -> None:
node.transform = (
-parent_transform @
Transform(f'rotate({self.options.rotate}, {center[0]}, {center[1]})') @
node.composed_transform()
)
self.adapt_fill_angle(node, None, self.options.rotate)
def adapt_fill_angle(self, node: ShapeElement, multiplier: Optional[int] = None, rotation: Optional[float] = None) -> None:
if not node.style("fill", "black"):
return
self._apply_angle(node, "inkstitch:fill_underlay_angle", None, multiplier, rotation)
if node.get('inkstitch:fill_method', None) == "tartan_fill":
if rotation is None:
self._apply_angle(node, "inkstitch:tartan_angle", "-45", multiplier, rotation)
# Also rotate tartan pattern rotation setting
self._rotate_tartan_pattern(node, multiplier, rotation)
elif node.get('inkstitch:fill_method', None) == "meander_fill":
self._apply_angle(node, "inkstitch:meander_angle", "0", multiplier, rotation)
else:
self._apply_angle(node, "inkstitch:angle", "0", multiplier, rotation)
def _apply_angle(self, node: ShapeElement, attrib: str, default: Optional[str], multiplier: Optional[int], rotation: Optional[float]) -> None:
angle_string = node.get(attrib, default)
if angle_string is None:
return
try:
angle = float(angle_string)
except ValueError:
return
if multiplier is not None:
angle *= multiplier
elif rotation is not None:
angle -= rotation
node.set(attrib, str(angle))
def _rotate_tartan_pattern(self, node: ShapeElement, multiplier: Optional[int], rotation: Optional[float]) -> None:
settings = get_tartan_settings(node)
tartan_rotation = settings['rotate']
if multiplier is not None:
tartan_rotation *= multiplier
elif rotation is not None:
tartan_rotation += rotation
settings['rotate'] = tartan_rotation
node.set("inkstitch:tartan", json.dumps(settings))
|