[traits] Formalize Flippable and Pivotable depending on Positionable

This commit is contained in:
Jan Petykiewicz 2026-02-15 14:34:10 -08:00
commit 48f7569c1f
5 changed files with 26 additions and 34 deletions

View file

@ -11,7 +11,7 @@ import numpy
from numpy import pi from numpy import pi
from numpy.typing import ArrayLike, NDArray 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 .utils import rotate_offsets_around, rotation_matrix_2d
from .error import PortError, format_stacktrace from .error import PortError, format_stacktrace
@ -20,7 +20,7 @@ logger = logging.getLogger(__name__)
@functools.total_ordering @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`. A point at which a `Device` can be snapped to another `Device`.

View file

@ -15,8 +15,8 @@ from .utils import annotations_t, rotation_matrix_2d, annotations_eq, annotation
from .repetition import Repetition from .repetition import Repetition
from .traits import ( from .traits import (
PositionableImpl, RotatableImpl, ScalableImpl, PositionableImpl, RotatableImpl, ScalableImpl,
Mirrorable, PivotableImpl, Copyable, RepeatableImpl, AnnotatableImpl, PivotableImpl, Copyable, RepeatableImpl, AnnotatableImpl,
Flippable, FlippableImpl, FlippableImpl,
) )
@ -26,9 +26,9 @@ if TYPE_CHECKING:
@functools.total_ordering @functools.total_ordering
class Ref( class Ref(
PositionableImpl, RotatableImpl, ScalableImpl, Mirrorable, FlippableImpl, PivotableImpl, RepeatableImpl, AnnotatableImpl,
PivotableImpl, Copyable, RepeatableImpl, AnnotatableImpl, PositionableImpl, RotatableImpl, ScalableImpl,
FlippableImpl, Flippable, Copyable,
): ):
""" """
`Ref` provides basic support for nesting Pattern objects within each other. `Ref` provides basic support for nesting Pattern objects within each other.

View file

@ -6,8 +6,8 @@ import numpy
from numpy.typing import NDArray, ArrayLike from numpy.typing import NDArray, ArrayLike
from ..traits import ( from ..traits import (
Rotatable, Copyable, Scalable, FlippableImpl, Copyable, Scalable, FlippableImpl,
Positionable, Pivotable, PivotableImpl, RepeatableImpl, AnnotatableImpl, PivotableImpl, RepeatableImpl, AnnotatableImpl,
) )
if TYPE_CHECKING: if TYPE_CHECKING:
@ -26,8 +26,8 @@ normalized_shape_tuple = tuple[
DEFAULT_POLY_NUM_VERTICES = 24 DEFAULT_POLY_NUM_VERTICES = 24
class Shape(Positionable, Rotatable, FlippableImpl, Copyable, Scalable, class Shape(FlippableImpl, PivotableImpl, RepeatableImpl, AnnotatableImpl,
AnnotatableImpl, RepeatableImpl, PivotableImpl, Pivotable, Copyable, Scalable,
metaclass=ABCMeta): metaclass=ABCMeta):
""" """
Class specifying functions common to all shapes. Class specifying functions common to all shapes.

View file

@ -1,14 +1,12 @@
from typing import Self, cast, TYPE_CHECKING from typing import Self
from abc import ABCMeta, abstractmethod from abc import ABCMeta, abstractmethod
import numpy import numpy
from numpy.typing import NDArray from numpy.typing import NDArray
from ..error import MasqueError from ..error import MasqueError
from .repeatable import Repeatable
if TYPE_CHECKING:
from .positionable import Positionable from .positionable import Positionable
from .repeatable import Repeatable
class Mirrorable(metaclass=ABCMeta): class Mirrorable(metaclass=ABCMeta):
@ -48,7 +46,7 @@ class Mirrorable(metaclass=ABCMeta):
return self return self
class Flippable(metaclass=ABCMeta): class Flippable(Positionable, metaclass=ABCMeta):
""" """
Trait class for entities which can be mirrored relative to an external line. Trait class for entities which can be mirrored relative to an external line.
""" """
@ -85,22 +83,19 @@ class Flippable(metaclass=ABCMeta):
pass pass
class FlippableImpl(Flippable, Repeatable, metaclass=ABCMeta): class FlippableImpl(Flippable, Mirrorable, Repeatable, metaclass=ABCMeta):
""" """
Implementation of `Flippable` for objects which are `Mirrorable`, `Positionable`, Implementation of `Flippable` for objects which are `Mirrorable`, `Positionable`,
and `Repeatable`. and `Repeatable`.
""" """
__slots__ = () __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: 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) axis, pivot = self._check_flip_args(axis=axis, x=x, y=y)
cast('Positionable', self).translate(-pivot) self.translate(-pivot)
cast('Mirrorable', self).mirror(axis) self.mirror(axis)
if self.repetition is not None: if self.repetition is not None:
self.repetition.mirror(axis) self.repetition.mirror(axis)
self.offset[1 - axis] *= -1 self.offset[1 - axis] *= -1
cast('Positionable', self).translate(+pivot) self.translate(+pivot)
return self return self

View file

@ -1,4 +1,4 @@
from typing import Self, cast, Any, TYPE_CHECKING from typing import Self
from abc import ABCMeta, abstractmethod from abc import ABCMeta, abstractmethod
import numpy import numpy
@ -8,7 +8,6 @@ from numpy.typing import ArrayLike
from ..error import MasqueError from ..error import MasqueError
from ..utils import rotation_matrix_2d 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 _empty_slots = () # Workaround to get mypy to ignore intentionally empty slots for superclass
@ -81,7 +80,7 @@ class RotatableImpl(Rotatable, metaclass=ABCMeta):
return self return self
class Pivotable(metaclass=ABCMeta): class Pivotable(Positionable, metaclass=ABCMeta):
""" """
Trait class for entites which can be rotated around a point. Trait class for entites which can be rotated around a point.
This requires that they are `Positionable` but not necessarily `Rotatable` themselves. This requires that they are `Positionable` but not necessarily `Rotatable` themselves.
@ -103,20 +102,18 @@ class Pivotable(metaclass=ABCMeta):
pass pass
class PivotableImpl(Pivotable, metaclass=ABCMeta): class PivotableImpl(Pivotable, Rotatable, metaclass=ABCMeta):
""" """
Implementation of `Pivotable` for objects which are `Rotatable` Implementation of `Pivotable` for objects which are `Rotatable`
and `Positionable`.
""" """
__slots__ = () __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: def rotate_around(self, pivot: ArrayLike, rotation: float) -> Self:
pivot = numpy.asarray(pivot, dtype=float) pivot = numpy.asarray(pivot, dtype=float)
cast('Positionable', self).translate(-pivot) self.translate(-pivot)
cast('Rotatable', self).rotate(rotation) self.rotate(rotation)
self.offset = numpy.dot(rotation_matrix_2d(rotation), self.offset) self.offset = numpy.dot(rotation_matrix_2d(rotation), self.offset)
cast('Positionable', self).translate(+pivot) self.translate(+pivot)
return self return self