From ac61b8bab7a0d435b55b3a696f83274ef8c85060 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Mon, 14 Sep 2026 21:52:18 -0700 Subject: [PATCH] [Arc] fix arc mirroring --- masque/shapes/arc.py | 9 ++++----- masque/test/test_arc.py | 15 +++++++++++++++ masque/test/test_shape_transforms.py | 3 ++- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/masque/shapes/arc.py b/masque/shapes/arc.py index 53fadb8..35362a9 100644 --- a/masque/shapes/arc.py +++ b/masque/shapes/arc.py @@ -423,11 +423,10 @@ class Arc(PositionableImpl, Shape): return self def mirror(self, axis: int = 0) -> 'Arc': - if self.angle_ref != ArcAngleRef.Center: - x_major = self.radius_x > self.radius_y - y_major = self.radius_y > self.radius_x - if (axis == 0 and y_major) or (axis == 1 and x_major): - self._swap_focus_ref() + # Both external reflections use a local Y reflection; the extra pi + # rotation below accounts for the external axis. + if self.angle_ref != ArcAngleRef.Center and self.radius_y > self.radius_x: + self._swap_focus_ref() self.rotation *= -1 self.rotation += axis * pi self.angles *= -1 diff --git a/masque/test/test_arc.py b/masque/test/test_arc.py index dc23144..ae2789a 100644 --- a/masque/test/test_arc.py +++ b/masque/test/test_arc.py @@ -14,6 +14,21 @@ def test_arc_init() -> None: assert_equal(a.angles, [0, pi / 2]) assert a.width == 2 + +@pytest.mark.parametrize('axis', [0, 1]) +@pytest.mark.parametrize('radii', [(10, 6), (6, 10), (10, 10)]) +@pytest.mark.parametrize('angle_ref', list(Arc.AngleRef)) +@pytest.mark.parametrize('rotation', [0, pi / 5]) +def test_arc_reflection_preserves_caps_and_bounds(axis: int, radii: tuple, angle_ref: Arc.AngleRef, rotation: float) -> None: + arc = Arc(radii=radii, angles=(-0.3, 1.1), width=1, angle_ref=angle_ref, rotation=rotation) + reflected = arc.deepcopy().mirror(axis) + signs = numpy.ones(2) + signs[1 - axis] = -1 + assert_allclose(reflected.get_cap_edges(), arc.get_cap_edges() * signs, atol=1e-12) + expected = arc.get_bounds_single() * signs + assert_allclose(reflected.get_bounds_single(), numpy.sort(expected, axis=0), atol=1e-12) + assert_allclose(reflected.mirror(axis).get_cap_edges(), arc.get_cap_edges(), atol=1e-12) + def test_arc_to_polygons() -> None: a = Arc(radii=(10, 10), angles=(0, pi / 2), width=2) polys = a.to_polygons(num_vertices=32) diff --git a/masque/test/test_shape_transforms.py b/masque/test/test_shape_transforms.py index 2a9092a..13bb77d 100644 --- a/masque/test/test_shape_transforms.py +++ b/masque/test/test_shape_transforms.py @@ -17,7 +17,8 @@ def test_shape_mirror() -> None: a = Arc(radii=(10, 5), angles=(0, pi / 4), width=2, angle_ref=Arc.AngleRef.FocusPos) a.mirror(1) - assert a.angle_ref == Arc.AngleRef.FocusNeg + # The pi rotation already reflects the X-major focus across the Y axis. + assert a.angle_ref == Arc.AngleRef.FocusPos a = Arc(radii=(5, 10), angles=(0, pi / 4), width=2, angle_ref=Arc.AngleRef.FocusPos) a.mirror(0)