diff options
Diffstat (limited to 'lib/extensions/transform_elements.py')
| -rw-r--r-- | lib/extensions/transform_elements.py | 112 |
1 files changed, 112 insertions, 0 deletions
diff --git a/lib/extensions/transform_elements.py b/lib/extensions/transform_elements.py new file mode 100644 index 00000000..66d0ad82 --- /dev/null +++ b/lib/extensions/transform_elements.py @@ -0,0 +1,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)) |
