[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.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`.

View file

@ -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.

View file

@ -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.

View file

@ -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

View file

@ -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