diff options
| author | capellancitizen <thecapellancitizen@gmail.com> | 2024-03-28 17:21:42 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-03-28 22:21:42 +0100 |
| commit | 2bbebe56fdcf3fb351df1c636d349946e548e505 (patch) | |
| tree | f2eddba82b6f5a1490d1d2ca43e6c8ee91bfd3a4 /tests/test_clone.py | |
| parent | 51f2746b90ecfc56dad50d620a86ab2aa00b68b0 (diff) | |
Fixed clones of group elements not appearing. (#2766)
Diffstat (limited to 'tests/test_clone.py')
| -rw-r--r-- | tests/test_clone.py | 337 |
1 files changed, 337 insertions, 0 deletions
diff --git a/tests/test_clone.py b/tests/test_clone.py new file mode 100644 index 00000000..4a920e9e --- /dev/null +++ b/tests/test_clone.py @@ -0,0 +1,337 @@ +from lib.elements import Clone, EmbroideryElement +from lib.svg.tags import INKSTITCH_ATTRIBS, SVG_RECT_TAG +from inkex import SvgDocumentElement, Rectangle, Circle, Group, Use, Transform, TextElement +from inkex.tester import TestCase +from inkex.tester.svg import svg + +from typing import Optional + +from math import sqrt + + +def element_fill_angle(element: EmbroideryElement) -> Optional[float]: + angle = element.node.get(INKSTITCH_ATTRIBS['angle']) + if angle is not None: + angle = float(angle) + return angle + + +class CloneElementTest(TestCase): + def assertAngleAlmostEqual(self, a, b): + # Take the mod 180 of the returned angles, because e.g. -130deg and 50deg produce fills along the same angle. + # We have to use a precision of 4 decimal digits because of the precision of the matrices as they are stored in the svg trees + # generated by these tests. + self.assertAlmostEqual(a % 180, b % 180, 4) + + def test_not_embroiderable(self): + root: SvgDocumentElement = svg() + text = root.add(TextElement()) + text.text = "Can't embroider this!" + use = root.add(Use()) + use.href = text + + clone = Clone(use) + stitch_groups = clone.to_stitch_groups(None) + self.assertEqual(len(stitch_groups), 0) + + # These tests make sure the element cloning works as expected, using the `clone_elements` method. + + def test_basic(self): + root: SvgDocumentElement = svg() + rect = root.add(Rectangle(attrib={ + "width": "10", + "height": "10", + INKSTITCH_ATTRIBS["angle"]: "30" + })) + use = root.add(Use()) + use.href = rect + + clone = Clone(use) + with clone.clone_elements() as elements: + self.assertEqual(len(elements), 1) + self.assertAlmostEqual(element_fill_angle(elements[0]), 30) + + def test_angle_rotated(self): + root: SvgDocumentElement = svg() + rect = root.add(Rectangle(attrib={ + "width": "10", + "height": "10", + INKSTITCH_ATTRIBS["angle"]: "30" + })) + use = root.add(Use()) + use.href = rect + use.set('transform', Transform().add_rotate(20)) + + clone = Clone(use) + with clone.clone_elements() as elements: + self.assertEqual(len(elements), 1) + self.assertAngleAlmostEqual(element_fill_angle(elements[0]), 10) + + def test_angle_flipped(self): + root: SvgDocumentElement = svg() + rect = root.add(Rectangle(attrib={ + "width": "10", + "height": "10", + INKSTITCH_ATTRIBS["angle"]: "30" + })) + use = root.add(Use()) + use.href = rect + use.set('transform', Transform().add_scale(-1, 1)) + + clone = Clone(use) + with clone.clone_elements() as elements: + self.assertEqual(len(elements), 1) + self.assertAngleAlmostEqual(element_fill_angle(elements[0]), -30) + + def test_angle_flipped_rotated(self): + root: SvgDocumentElement = svg() + rect = root.add(Rectangle(attrib={ + "width": "10", + "height": "10", + INKSTITCH_ATTRIBS["angle"]: "30" + })) + use = root.add(Use()) + use.href = rect + use.set('transform', Transform().add_rotate(20).add_scale(-1, 1)) + + clone = Clone(use) + with clone.clone_elements() as elements: + self.assertEqual(len(elements), 1) + # Fill angle goes from 30 -> -30 after flip -> -50 after rotate + self.assertAngleAlmostEqual(element_fill_angle(elements[0]), -50) + + def test_angle_non_uniform_scale(self): + """ + The angle isn't *as* well-defined for non-rotational scales, but we try to follow how the slope will be altered. + """ + root: SvgDocumentElement = svg() + rect = root.add(Rectangle(attrib={ + "width": "10", + "height": "10", + INKSTITCH_ATTRIBS["angle"]: "30" + })) + use = root.add(Use()) + use.href = rect + use.set('transform', Transform().add_rotate(10).add_scale(1, -sqrt(3))) + + clone = Clone(use) + with clone.clone_elements() as elements: + self.assertEqual(len(elements), 1) + # Slope of the stitching goes from tan(30deg) = 1/sqrt(3) to -sqrt(3)/sqrt(3) = tan(-45deg), + # then rotated another -10 degrees to -55 + self.assertAngleAlmostEqual(element_fill_angle(elements[0]), -55) + + def test_angle_inherits_down_tree(self): + """ + The stitching angle of a clone is based in part on the relative transforms of the source and clone. + """ + root: SvgDocumentElement = svg() + g1 = root.add(Group()) + g1.set('transform', Transform().add_rotate(3)) + rect = g1.add(Rectangle(attrib={ + "width": "10", + "height": "10", + INKSTITCH_ATTRIBS["angle"]: "30" + })) + g2 = root.add(Group()) + g2.set('transform', Transform().add_translate((20, 0)).add_rotate(-7)) + use = g2.add(Use()) + use.href = rect + use.set('transform', Transform().add_rotate(11)) + + clone = Clone(use) + with clone.clone_elements() as elements: + self.assertEqual(len(elements), 1) + # Angle goes from 30 -> 40 (g1 -> g2) -> 29 (use) + self.assertAngleAlmostEqual(element_fill_angle(elements[0]), 29) + + def test_transform_inherits_from_cloned_element(self): + """ + Elements cloned by cloned_elements need to inherit their transform from their href'd element and their use to match what's shown. + """ + root: SvgDocumentElement = svg() + rect = root.add(Rectangle(attrib={ + "width": "10", + "height": "10", + INKSTITCH_ATTRIBS["angle"]: "30", + })) + rect.set('transform', Transform().add_scale(2, 2)) + use = root.add(Use()) + use.href = rect + use.set('transform', Transform().add_translate((5, 10))) + + clone = Clone(use) + with clone.clone_elements() as elements: + self.assertEqual(len(elements), 1) + self.assertTransformEqual( + elements[0].node.composed_transform(), + Transform().add_translate((5, 10)).add_scale(2, 2)) + + def test_transform_inherits_from_tree(self): + root: SvgDocumentElement = svg() + g1 = root.add(Group()) + g1.set('transform', Transform().add_translate((0, 5)).add_rotate(5)) + rect = g1.add(Rectangle(attrib={ + "width": "10", + "height": "10", + INKSTITCH_ATTRIBS["angle"]: "30", + })) + rect.set('transform', Transform().add_scale(2, 2)) + use = root.add(Use()) + use.href = g1 + use.set('transform', Transform().add_translate((5, 10))) + + clone = Clone(use) + with clone.clone_elements() as elements: + self.assertEqual(len(elements), 1) + self.assertTransformEqual( + elements[0].node.composed_transform(), + Transform().add_translate((5, 10)) # use + .add_translate((0, 5)).add_rotate(5) # g1 + .add_scale(2, 2), # rect + 5) + + def test_transform_inherits_from_tree_up_tree(self): + root: SvgDocumentElement = svg() + g1 = root.add(Group()) + g1.set('transform', Transform().add_translate((0, 5)).add_rotate(5)) + rect = g1.add(Rectangle(attrib={ + "width": "10", + "height": "10", + INKSTITCH_ATTRIBS["angle"]: "30", + })) + rect.set('transform', Transform().add_scale(2, 2)) + circ = g1.add(Circle()) + circ.radius = 5 + g2 = root.add(Group()) + g2.set('transform', Transform().add_translate((1, 2)).add_scale(0.5, 1)) + use = g2.add(Use()) + use.href = g1 + use.set('transform', Transform().add_translate((5, 10))) + + clone = Clone(use) + with clone.clone_elements() as elements: + self.assertEqual(len(elements), 2) + self.assertTransformEqual( + elements[0].node.composed_transform(), + Transform().add_translate((1, 2)).add_scale(0.5, 1) # g2 + .add_translate((5, 10)) # use + .add_translate((0, 5)).add_rotate(5) # g1 + .add_scale(2, 2), # rect + 5) + self.assertTransformEqual( + elements[1].node.composed_transform(), + Transform().add_translate((1, 2)).add_scale(0.5, 1) # g2 + .add_translate((5, 10)) # use + .add_translate((0, 5)).add_rotate(5), # g1 + 5) + + def test_clone_fill_angle_not_specified(self): + root: SvgDocumentElement = svg() + rect = root.add(Rectangle(attrib={ + "width": "10", + "height": "10", + INKSTITCH_ATTRIBS["angle"]: "30" + })) + use = root.add(Use()) + use.href = rect + use.set('transform', Transform().add_rotate(20)) + + clone = Clone(use) + self.assertEqual(clone.clone_fill_angle, None) + + def test_clone_fill_angle(self): + root: SvgDocumentElement = svg() + rect = root.add(Rectangle(attrib={ + "width": "10", + "height": "10", + INKSTITCH_ATTRIBS["angle"]: "30" + })) + use = root.add(Use()) + use.href = rect + use.set(INKSTITCH_ATTRIBS["angle"], 42) + use.set('transform', Transform().add_rotate(20)) + + clone = Clone(use) + self.assertEqual(clone.clone_fill_angle, 42) + + with clone.clone_elements() as elements: + self.assertEqual(len(elements), 1) + self.assertAngleAlmostEqual(element_fill_angle(elements[0]), 42) + + def test_angle_manually_flipped(self): + root: SvgDocumentElement = svg() + rect = root.add(Rectangle(attrib={ + "width": "10", + "height": "10", + INKSTITCH_ATTRIBS["angle"]: "30" + })) + use = root.add(Use()) + use.href = rect + use.set('transform', Transform().add_rotate(20)) + use.set(INKSTITCH_ATTRIBS["flip_angle"], True) + + clone = Clone(use) + self.assertTrue(clone.flip_angle) + with clone.clone_elements() as elements: + self.assertEqual(len(elements), 1) + self.assertAngleAlmostEqual(element_fill_angle(elements[0]), -10) + + def test_recursive_uses(self): + root: SvgDocumentElement = svg() + g1 = root.add(Group()) + rect = g1.add(Rectangle(attrib={ + "width": "10", + "height": "10", + })) + u1 = g1.add(Use()) + u1.set('transform', Transform().add_translate((20, 0))) + u1.href = rect + u2 = root.add(Use()) + u2.set('transform', Transform().add_translate((0, 20)).add_scale(0.5, 0.5)) + u2.href = g1 + u3 = root.add(Use()) + u3.set('transform', Transform().add_translate((0, 30))) + u3.href = u2 + + clone = Clone(u3) + with clone.clone_elements() as elements: + # There should be two elements cloned from u3, two rects, one corresponding to rect and one corresponding to u1. + # Their transforms should derive from the elements they href. + self.assertEqual(len(elements), 2) + self.assertEqual(elements[0].node.tag, SVG_RECT_TAG) + self.assertTransformEqual(elements[0].node.composed_transform(), + Transform().add_translate((0, 30)) # u3 + .add_translate(0, 20).add_scale(0.5, 0.5) # u2 + ) + + self.assertEqual(elements[1].node.tag, SVG_RECT_TAG) + self.assertTransformEqual(elements[1].node.composed_transform(), + Transform().add_translate((0, 30)) # u3 + .add_translate((0, 20)).add_scale(0.5, 0.5) # u2 + .add_translate((20, 0)) # u1 + ) + + def test_recursive_uses_angle(self): + root: SvgDocumentElement = svg() + rect = root.add(Rectangle(attrib={ + "width": "10", + "height": "10", + INKSTITCH_ATTRIBS["angle"]: "30" + })) + u1 = root.add(Use()) + u1.set('transform', Transform().add_rotate(60)) + u1.href = rect + g = root.add(Group()) + g.set('transform', Transform().add_rotate(-10)) + u2 = g.add(Use()) + u2.href = u1 + u3 = root.add(Use()) + u3.set('transform', Transform().add_rotate(7)) + u3.href = g + + clone = Clone(u3) + with clone.clone_elements() as elements: + self.assertEqual(len(elements), 1) + # Angle goes from 30 -> -30 (u1) -> -20 (g -> u2) -> -27 (u3) + self.assertAngleAlmostEqual(element_fill_angle(elements[0]), -27) |
