diff options
| author | Kaalleen <36401965+kaalleen@users.noreply.github.com> | 2025-04-15 18:27:06 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-04-15 18:27:06 +0200 |
| commit | bc0943c6c86374ca80f3f10ffa44773ef87c5f24 (patch) | |
| tree | a51a23bf03e87090b0fd52fdd145b1bff6f117e0 /lib | |
| parent | 0f1c83f8ef9f156a7297708f087fc11d1d4d48ef (diff) | |
Add transform extension which also transforms the fill angles (#3657)
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/elements/fill_stitch.py | 2 | ||||
| -rw-r--r-- | lib/extensions/__init__.py | 2 | ||||
| -rw-r--r-- | lib/extensions/jump_to_stroke.py | 2 | ||||
| -rw-r--r-- | lib/extensions/transform_elements.py | 112 |
4 files changed, 116 insertions, 2 deletions
diff --git a/lib/elements/fill_stitch.py b/lib/elements/fill_stitch.py index 5f33eb65..fa4ce845 100644 --- a/lib/elements/fill_stitch.py +++ b/lib/elements/fill_stitch.py @@ -311,7 +311,7 @@ class FillStitch(EmbroideryElement): type='float', sort_index=21, select_items=[('fill_method', 'tartan_fill')], - default=45) + default=-45) @cache def tartan_angle(self): return self.get_float_param('tartan_angle', -45) diff --git a/lib/extensions/__init__.py b/lib/extensions/__init__.py index 60d0e10d..22da70a7 100644 --- a/lib/extensions/__init__.py +++ b/lib/extensions/__init__.py @@ -72,6 +72,7 @@ from .stroke_to_satin import StrokeToSatin from .tartan import Tartan from .test_swatches import TestSwatches from .thread_list import ThreadList +from .transform_elements import TransformElements from .troubleshoot import Troubleshoot from .unlink_clone import UnlinkClone from .update_svg import UpdateSvg @@ -148,6 +149,7 @@ extensions = [ Tartan, TestSwatches, ThreadList, + TransformElements, Troubleshoot, UnlinkClone, UpdateSvg, diff --git a/lib/extensions/jump_to_stroke.py b/lib/extensions/jump_to_stroke.py index 867b859f..7470faeb 100644 --- a/lib/extensions/jump_to_stroke.py +++ b/lib/extensions/jump_to_stroke.py @@ -49,7 +49,7 @@ class JumpToStroke(InkstitchExtension): next_elements = self.elements[1:] + next_elements for element, next_element in zip(self.elements, next_elements): layer, group = self._get_element_layer_and_group(element) - stitch_groups = element.to_stitch_groups(last_stitch_group, next_element) + stitch_groups = element.embroider(last_stitch_group, next_element) multiple = not self.options.merge_subpaths and stitch_groups if multiple: 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)) |
