[BREAKING][Ref / Label / Pattern] Make rotate/mirror consistent intrinsic transfomations

offset and repetition are extrinsic; use rotate_around() and flip() to
alter both
mirror() and rotate() only affect the object's intrinsic properties
This commit is contained in:
jan 2026-03-09 23:34:25 -07:00
commit 5f91bd9c6c
6 changed files with 204 additions and 25 deletions

View file

@ -25,7 +25,8 @@ class Rotatable(metaclass=ABCMeta):
@abstractmethod
def rotate(self, val: float) -> Self:
"""
Rotate the shape around its origin (0, 0), ignoring its offset.
Intrinsic transformation: Rotate the shape around its origin (0, 0), ignoring its offset.
This does NOT affect the object's repetition grid.
Args:
val: Angle to rotate by (counterclockwise, radians)
@ -63,6 +64,10 @@ class RotatableImpl(Rotatable, metaclass=ABCMeta):
# Methods
#
def rotate(self, rotation: float) -> Self:
"""
Intrinsic transformation: Rotate the shape around its origin (0, 0), ignoring its offset.
This does NOT affect the object's repetition grid.
"""
self.rotation += rotation
return self
@ -82,7 +87,7 @@ class RotatableImpl(Rotatable, metaclass=ABCMeta):
class Pivotable(Positionable, metaclass=ABCMeta):
"""
Trait class for entites which can be rotated around a point.
Trait class for entities which can be rotated around a point.
This requires that they are `Positionable` but not necessarily `Rotatable` themselves.
"""
__slots__ = ()
@ -90,7 +95,11 @@ class Pivotable(Positionable, metaclass=ABCMeta):
@abstractmethod
def rotate_around(self, pivot: ArrayLike, rotation: float) -> Self:
"""
Rotate the object around a point.
Extrinsic transformation: Rotate the object around a point in the container's
coordinate system. This affects both the object's offset and its repetition grid.
For objects that are also `Rotatable`, this also performs an intrinsic
rotation of the object.
Args:
pivot: Point (x, y) to rotate around
@ -110,9 +119,12 @@ class PivotableImpl(Pivotable, Rotatable, metaclass=ABCMeta):
__slots__ = ()
def rotate_around(self, pivot: ArrayLike, rotation: float) -> Self:
from .repeatable import Repeatable #noqa: PLC0415
pivot = numpy.asarray(pivot, dtype=float)
self.translate(-pivot)
self.rotate(rotation)
if isinstance(self, Repeatable) and self.repetition is not None:
self.repetition.rotate(rotation)
self.offset = numpy.dot(rotation_matrix_2d(rotation), self.offset)
self.translate(+pivot)
return self