summaryrefslogtreecommitdiff
path: root/lib/extensions/transform_elements.py
diff options
context:
space:
mode:
authorKaalleen <36401965+kaalleen@users.noreply.github.com>2025-04-15 18:27:06 +0200
committerGitHub <noreply@github.com>2025-04-15 18:27:06 +0200
commitbc0943c6c86374ca80f3f10ffa44773ef87c5f24 (patch)
treea51a23bf03e87090b0fd52fdd145b1bff6f117e0 /lib/extensions/transform_elements.py
parent0f1c83f8ef9f156a7297708f087fc11d1d4d48ef (diff)
Add transform extension which also transforms the fill angles (#3657)
Diffstat (limited to 'lib/extensions/transform_elements.py')
-rw-r--r--lib/extensions/transform_elements.py112
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))