From 48f7569c1fb0817ce6a13c16e307a15fd2ec4acd Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Sun, 15 Feb 2026 14:34:10 -0800 Subject: [PATCH] [traits] Formalize Flippable and Pivotable depending on Positionable --- masque/ports.py | 4 ++-- masque/ref.py | 10 +++++----- masque/shapes/shape.py | 8 ++++---- masque/traits/mirrorable.py | 19 +++++++------------ masque/traits/rotatable.py | 19 ++++++++----------- 5 files changed, 26 insertions(+), 34 deletions(-) diff --git a/masque/ports.py b/masque/ports.py index f2651de..c40cf55 100644 --- a/masque/ports.py +++ b/masque/ports.py @@ -11,7 +11,7 @@ import numpy from numpy import pi from numpy.typing import ArrayLike, NDArray -from .traits import PositionableImpl, Rotatable, PivotableImpl, Copyable, Flippable +from .traits import PositionableImpl, PivotableImpl, Copyable, Mirrorable, Flippable from .utils import rotate_offsets_around, rotation_matrix_2d from .error import PortError, format_stacktrace @@ -20,7 +20,7 @@ logger = logging.getLogger(__name__) @functools.total_ordering -class Port(PositionableImpl, Rotatable, PivotableImpl, Copyable, Flippable): +class Port(PivotableImpl, PositionableImpl, Mirrorable, Flippable, Copyable): """ A point at which a `Device` can be snapped to another `Device`. diff --git a/masque/ref.py b/masque/ref.py index 70e52db..3a64dce 100644 --- a/masque/ref.py +++ b/masque/ref.py @@ -15,8 +15,8 @@ from .utils import annotations_t, rotation_matrix_2d, annotations_eq, annotation from .repetition import Repetition from .traits import ( PositionableImpl, RotatableImpl, ScalableImpl, - Mirrorable, PivotableImpl, Copyable, RepeatableImpl, AnnotatableImpl, - Flippable, FlippableImpl, + PivotableImpl, Copyable, RepeatableImpl, AnnotatableImpl, + FlippableImpl, ) @@ -26,9 +26,9 @@ if TYPE_CHECKING: @functools.total_ordering class Ref( - PositionableImpl, RotatableImpl, ScalableImpl, Mirrorable, - PivotableImpl, Copyable, RepeatableImpl, AnnotatableImpl, - FlippableImpl, Flippable, + FlippableImpl, PivotableImpl, RepeatableImpl, AnnotatableImpl, + PositionableImpl, RotatableImpl, ScalableImpl, + Copyable, ): """ `Ref` provides basic support for nesting Pattern objects within each other. diff --git a/masque/shapes/shape.py b/masque/shapes/shape.py index 269c460..13d2e1e 100644 --- a/masque/shapes/shape.py +++ b/masque/shapes/shape.py @@ -6,8 +6,8 @@ import numpy from numpy.typing import NDArray, ArrayLike from ..traits import ( - Rotatable, Copyable, Scalable, FlippableImpl, - Positionable, Pivotable, PivotableImpl, RepeatableImpl, AnnotatableImpl, + Copyable, Scalable, FlippableImpl, + PivotableImpl, RepeatableImpl, AnnotatableImpl, ) if TYPE_CHECKING: @@ -26,8 +26,8 @@ normalized_shape_tuple = tuple[ DEFAULT_POLY_NUM_VERTICES = 24 -class Shape(Positionable, Rotatable, FlippableImpl, Copyable, Scalable, - AnnotatableImpl, RepeatableImpl, PivotableImpl, Pivotable, +class Shape(FlippableImpl, PivotableImpl, RepeatableImpl, AnnotatableImpl, + Copyable, Scalable, metaclass=ABCMeta): """ Class specifying functions common to all shapes. diff --git a/masque/traits/mirrorable.py b/masque/traits/mirrorable.py index 9b4072b..644db61 100644 --- a/masque/traits/mirrorable.py +++ b/masque/traits/mirrorable.py @@ -1,15 +1,13 @@ -from typing import Self, cast, TYPE_CHECKING +from typing import Self from abc import ABCMeta, abstractmethod import numpy from numpy.typing import NDArray from ..error import MasqueError +from .positionable import Positionable from .repeatable import Repeatable -if TYPE_CHECKING: - from .positionable import Positionable - class Mirrorable(metaclass=ABCMeta): """ @@ -48,7 +46,7 @@ class Mirrorable(metaclass=ABCMeta): return self -class Flippable(metaclass=ABCMeta): +class Flippable(Positionable, metaclass=ABCMeta): """ Trait class for entities which can be mirrored relative to an external line. """ @@ -85,22 +83,19 @@ class Flippable(metaclass=ABCMeta): pass -class FlippableImpl(Flippable, Repeatable, metaclass=ABCMeta): +class FlippableImpl(Flippable, Mirrorable, Repeatable, metaclass=ABCMeta): """ Implementation of `Flippable` for objects which are `Mirrorable`, `Positionable`, and `Repeatable`. """ __slots__ = () - offset: NDArray[numpy.float64] - """ `[x_offset, y_offset]` """ - def flip_across(self, axis: int | None = None, *, x: float | None = None, y: float | None = None) -> Self: axis, pivot = self._check_flip_args(axis=axis, x=x, y=y) - cast('Positionable', self).translate(-pivot) - cast('Mirrorable', self).mirror(axis) + self.translate(-pivot) + self.mirror(axis) if self.repetition is not None: self.repetition.mirror(axis) self.offset[1 - axis] *= -1 - cast('Positionable', self).translate(+pivot) + self.translate(+pivot) return self diff --git a/masque/traits/rotatable.py b/masque/traits/rotatable.py index 2fa86c1..2517e2e 100644 --- a/masque/traits/rotatable.py +++ b/masque/traits/rotatable.py @@ -1,4 +1,4 @@ -from typing import Self, cast, Any, TYPE_CHECKING +from typing import Self from abc import ABCMeta, abstractmethod import numpy @@ -8,8 +8,7 @@ from numpy.typing import ArrayLike from ..error import MasqueError from ..utils import rotation_matrix_2d -if TYPE_CHECKING: - from .positionable import Positionable +from .positionable import Positionable _empty_slots = () # Workaround to get mypy to ignore intentionally empty slots for superclass @@ -81,7 +80,7 @@ class RotatableImpl(Rotatable, metaclass=ABCMeta): return self -class Pivotable(metaclass=ABCMeta): +class Pivotable(Positionable, metaclass=ABCMeta): """ Trait class for entites which can be rotated around a point. This requires that they are `Positionable` but not necessarily `Rotatable` themselves. @@ -103,20 +102,18 @@ class Pivotable(metaclass=ABCMeta): pass -class PivotableImpl(Pivotable, metaclass=ABCMeta): +class PivotableImpl(Pivotable, Rotatable, metaclass=ABCMeta): """ Implementation of `Pivotable` for objects which are `Rotatable` + and `Positionable`. """ __slots__ = () - offset: Any # TODO see if we can get around defining `offset` in PivotableImpl - """ `[x_offset, y_offset]` """ - def rotate_around(self, pivot: ArrayLike, rotation: float) -> Self: pivot = numpy.asarray(pivot, dtype=float) - cast('Positionable', self).translate(-pivot) - cast('Rotatable', self).rotate(rotation) + self.translate(-pivot) + self.rotate(rotation) self.offset = numpy.dot(rotation_matrix_2d(rotation), self.offset) - cast('Positionable', self).translate(+pivot) + self.translate(+pivot) return self