summaryrefslogtreecommitdiff
path: root/lib
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
parent0f1c83f8ef9f156a7297708f087fc11d1d4d48ef (diff)
Add transform extension which also transforms the fill angles (#3657)
Diffstat (limited to 'lib')
-rw-r--r--lib/elements/fill_stitch.py2
-rw-r--r--lib/extensions/__init__.py2
-rw-r--r--lib/extensions/jump_to_stroke.py2
-rw-r--r--lib/extensions/transform_elements.py112
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))