[Mirrorable / Flippable] Bifurcate mirror into flip (relative to line) vs mirror (relative to own offset/origin)
This commit is contained in:
parent
accad3db9f
commit
44986bac67
11 changed files with 115 additions and 111 deletions
|
|
@ -8,16 +8,13 @@ from numpy.typing import ArrayLike
|
||||||
from .ref import Ref
|
from .ref import Ref
|
||||||
from .ports import PortList, Port
|
from .ports import PortList, Port
|
||||||
from .utils import rotation_matrix_2d
|
from .utils import rotation_matrix_2d
|
||||||
|
from .traits import Flippable
|
||||||
#if TYPE_CHECKING:
|
|
||||||
# from .builder import Builder, Tool
|
|
||||||
# from .library import ILibrary
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Abstract(PortList):
|
class Abstract(PortList, Flippable):
|
||||||
"""
|
"""
|
||||||
An `Abstract` is a container for a name and associated ports.
|
An `Abstract` is a container for a name and associated ports.
|
||||||
|
|
||||||
|
|
@ -131,50 +128,18 @@ class Abstract(PortList):
|
||||||
port.rotate(rotation)
|
port.rotate(rotation)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def mirror_port_offsets(self, across_axis: int = 0) -> Self:
|
def mirror(self, axis: int = 0) -> Self:
|
||||||
"""
|
"""
|
||||||
Mirror the offsets of all shapes, labels, and refs across an axis
|
Mirror the Abstract across an axis through its origin.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
across_axis: Axis to mirror across
|
axis: Axis to mirror across (0: mirror across x axis, 1: mirror across y axis)
|
||||||
(0: mirror across x axis, 1: mirror across y axis)
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
self
|
self
|
||||||
"""
|
"""
|
||||||
for port in self.ports.values():
|
for port in self.ports.values():
|
||||||
port.offset[across_axis - 1] *= -1
|
port.flip_across(axis=axis)
|
||||||
return self
|
|
||||||
|
|
||||||
def mirror_ports(self, across_axis: int = 0) -> Self:
|
|
||||||
"""
|
|
||||||
Mirror each port's rotation across an axis, relative to its
|
|
||||||
offset
|
|
||||||
|
|
||||||
Args:
|
|
||||||
across_axis: Axis to mirror across
|
|
||||||
(0: mirror across x axis, 1: mirror across y axis)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
self
|
|
||||||
"""
|
|
||||||
for port in self.ports.values():
|
|
||||||
port.mirror(across_axis)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def mirror(self, across_axis: int = 0) -> Self:
|
|
||||||
"""
|
|
||||||
Mirror the Pattern across an axis
|
|
||||||
|
|
||||||
Args:
|
|
||||||
axis: Axis to mirror across
|
|
||||||
(0: mirror across x axis, 1: mirror across y axis)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
self
|
|
||||||
"""
|
|
||||||
self.mirror_ports(across_axis)
|
|
||||||
self.mirror_port_offsets(across_axis)
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def apply_ref_transform(self, ref: Ref) -> Self:
|
def apply_ref_transform(self, ref: Ref) -> Self:
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,12 @@ from numpy.typing import ArrayLike, NDArray
|
||||||
|
|
||||||
from .repetition import Repetition
|
from .repetition import Repetition
|
||||||
from .utils import rotation_matrix_2d, annotations_t, annotations_eq, annotations_lt, rep2key
|
from .utils import rotation_matrix_2d, annotations_t, annotations_eq, annotations_lt, rep2key
|
||||||
from .traits import PositionableImpl, Copyable, Pivotable, RepeatableImpl, Bounded
|
from .traits import PositionableImpl, Copyable, Pivotable, RepeatableImpl, Bounded, Flippable
|
||||||
from .traits import AnnotatableImpl
|
from .traits import AnnotatableImpl
|
||||||
|
|
||||||
|
|
||||||
@functools.total_ordering
|
@functools.total_ordering
|
||||||
class Label(PositionableImpl, RepeatableImpl, AnnotatableImpl, Bounded, Pivotable, Copyable):
|
class Label(PositionableImpl, RepeatableImpl, AnnotatableImpl, Bounded, Pivotable, Copyable, Flippable):
|
||||||
"""
|
"""
|
||||||
A text annotation with a position (but no size; it is not drawn)
|
A text annotation with a position (but no size; it is not drawn)
|
||||||
"""
|
"""
|
||||||
|
|
@ -102,6 +102,22 @@ class Label(PositionableImpl, RepeatableImpl, AnnotatableImpl, Bounded, Pivotabl
|
||||||
self.translate(+pivot)
|
self.translate(+pivot)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
def flip_across(self, axis: int | None = None, *, x: float | None = None, y: float | None = None) -> Self:
|
||||||
|
"""
|
||||||
|
Mirror the object across a line.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
axis: Axis to mirror across. 0 mirrors across x=0. 1 mirrors across y=0.
|
||||||
|
x: Vertical line x=val to mirror across.
|
||||||
|
y: Horizontal line y=val to mirror across.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
self
|
||||||
|
"""
|
||||||
|
if self.repetition is not None:
|
||||||
|
self.repetition.flip_across(axis=axis, x=x, y=y)
|
||||||
|
return self
|
||||||
|
|
||||||
def get_bounds_single(self) -> NDArray[numpy.float64]:
|
def get_bounds_single(self) -> NDArray[numpy.float64]:
|
||||||
"""
|
"""
|
||||||
Return the bounds of the label.
|
Return the bounds of the label.
|
||||||
|
|
|
||||||
|
|
@ -733,50 +733,31 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable):
|
||||||
cast('Rotatable', entry).rotate(rotation)
|
cast('Rotatable', entry).rotate(rotation)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def mirror_element_centers(self, across_axis: int = 0) -> Self:
|
def mirror_elements(self, across_axis: int = 0) -> Self:
|
||||||
"""
|
"""
|
||||||
Mirror the offsets of all shapes, labels, and refs across an axis
|
Mirror each shape, ref, and port relative to (0,0).
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
across_axis: Axis to mirror across
|
across_axis: Axis to mirror across
|
||||||
(0: mirror across x axis, 1: mirror across y axis)
|
(0: mirror across x axis, 1: mirror across y axis)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
self
|
||||||
|
"""
|
||||||
|
return self.flip_across(axis=across_axis)
|
||||||
|
|
||||||
|
def mirror(self, axis: int = 0) -> Self:
|
||||||
|
"""
|
||||||
|
Mirror the Pattern across an axis through its origin.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
axis: Axis to mirror across (0: x-axis, 1: y-axis).
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
self
|
self
|
||||||
"""
|
"""
|
||||||
for entry in chain(chain_elements(self.shapes, self.refs, self.labels), self.ports.values()):
|
for entry in chain(chain_elements(self.shapes, self.refs, self.labels), self.ports.values()):
|
||||||
cast('Positionable', entry).offset[1 - across_axis] *= -1
|
cast('Flippable', entry).flip_across(axis=axis)
|
||||||
return self
|
|
||||||
|
|
||||||
def mirror_elements(self, across_axis: int = 0) -> Self:
|
|
||||||
"""
|
|
||||||
Mirror each shape, ref, and pattern across an axis, relative
|
|
||||||
to its offset
|
|
||||||
|
|
||||||
Args:
|
|
||||||
across_axis: Axis to mirror across
|
|
||||||
(0: mirror across x axis, 1: mirror across y axis)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
self
|
|
||||||
"""
|
|
||||||
for entry in chain(chain_elements(self.shapes, self.refs), self.ports.values()):
|
|
||||||
cast('Mirrorable', entry).mirror(across_axis)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def mirror(self, across_axis: int = 0) -> Self:
|
|
||||||
"""
|
|
||||||
Mirror the Pattern across an axis
|
|
||||||
|
|
||||||
Args:
|
|
||||||
across_axis: Axis to mirror across
|
|
||||||
(0: mirror across x axis, 1: mirror across y axis)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
self
|
|
||||||
"""
|
|
||||||
self.mirror_elements(across_axis)
|
|
||||||
self.mirror_element_centers(across_axis)
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def copy(self) -> Self:
|
def copy(self) -> Self:
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,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, Mirrorable
|
from .traits import PositionableImpl, Rotatable, PivotableImpl, Copyable, Mirrorable, Flippable, FlippableImpl
|
||||||
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
|
||||||
|
|
||||||
|
|
@ -19,7 +19,7 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@functools.total_ordering
|
@functools.total_ordering
|
||||||
class Port(PositionableImpl, Rotatable, PivotableImpl, Copyable, Mirrorable):
|
class Port(PositionableImpl, Rotatable, PivotableImpl, Copyable, FlippableImpl):
|
||||||
"""
|
"""
|
||||||
A point at which a `Device` can be snapped to another `Device`.
|
A point at which a `Device` can be snapped to another `Device`.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ from .repetition import Repetition
|
||||||
from .traits import (
|
from .traits import (
|
||||||
PositionableImpl, RotatableImpl, ScalableImpl,
|
PositionableImpl, RotatableImpl, ScalableImpl,
|
||||||
Mirrorable, PivotableImpl, Copyable, RepeatableImpl, AnnotatableImpl,
|
Mirrorable, PivotableImpl, Copyable, RepeatableImpl, AnnotatableImpl,
|
||||||
|
Flippable, FlippableImpl,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -25,7 +26,7 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
@functools.total_ordering
|
@functools.total_ordering
|
||||||
class Ref(
|
class Ref(
|
||||||
PositionableImpl, RotatableImpl, ScalableImpl, Mirrorable,
|
FlippableImpl, PositionableImpl, RotatableImpl, ScalableImpl,
|
||||||
PivotableImpl, Copyable, RepeatableImpl, AnnotatableImpl,
|
PivotableImpl, Copyable, RepeatableImpl, AnnotatableImpl,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -384,7 +384,6 @@ class Arc(PositionableImpl, Shape):
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def mirror(self, axis: int = 0) -> 'Arc':
|
def mirror(self, axis: int = 0) -> 'Arc':
|
||||||
self.offset[1 - axis] *= -1
|
|
||||||
self.rotation *= -1
|
self.rotation *= -1
|
||||||
self.rotation += axis * pi
|
self.rotation += axis * pi
|
||||||
self.angles *= -1
|
self.angles *= -1
|
||||||
|
|
|
||||||
|
|
@ -189,7 +189,6 @@ class Ellipse(PositionableImpl, Shape):
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def mirror(self, axis: int = 0) -> Self:
|
def mirror(self, axis: int = 0) -> Self:
|
||||||
self.offset[1 - axis] *= -1
|
|
||||||
self.rotation *= -1
|
self.rotation *= -1
|
||||||
self.rotation += axis * pi
|
self.rotation += axis * pi
|
||||||
return self
|
return self
|
||||||
|
|
|
||||||
|
|
@ -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, Mirrorable, Copyable, Scalable,
|
Rotatable, Mirrorable, Copyable, Scalable, FlippableImpl,
|
||||||
Positionable, PivotableImpl, RepeatableImpl, AnnotatableImpl,
|
Positionable, Pivotable, PivotableImpl, RepeatableImpl, AnnotatableImpl,
|
||||||
)
|
)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
|
@ -26,8 +26,9 @@ normalized_shape_tuple = tuple[
|
||||||
DEFAULT_POLY_NUM_VERTICES = 24
|
DEFAULT_POLY_NUM_VERTICES = 24
|
||||||
|
|
||||||
|
|
||||||
class Shape(Positionable, Rotatable, Mirrorable, Copyable, Scalable,
|
class Shape(Positionable, Rotatable, FlippableImpl, Copyable, Scalable,
|
||||||
PivotableImpl, RepeatableImpl, AnnotatableImpl, metaclass=ABCMeta):
|
AnnotatableImpl, RepeatableImpl, PivotableImpl, Pivotable,
|
||||||
|
metaclass=ABCMeta):
|
||||||
"""
|
"""
|
||||||
Class specifying functions common to all shapes.
|
Class specifying functions common to all shapes.
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,7 @@ class Text(PositionableImpl, RotatableImpl, Shape):
|
||||||
*,
|
*,
|
||||||
offset: ArrayLike = (0.0, 0.0),
|
offset: ArrayLike = (0.0, 0.0),
|
||||||
rotation: float = 0.0,
|
rotation: float = 0.0,
|
||||||
|
mirrored: bool = False,
|
||||||
repetition: Repetition | None = None,
|
repetition: Repetition | None = None,
|
||||||
annotations: annotations_t = None,
|
annotations: annotations_t = None,
|
||||||
raw: bool = False,
|
raw: bool = False,
|
||||||
|
|
@ -80,6 +81,7 @@ class Text(PositionableImpl, RotatableImpl, Shape):
|
||||||
self._string = string
|
self._string = string
|
||||||
self._height = height
|
self._height = height
|
||||||
self._rotation = rotation
|
self._rotation = rotation
|
||||||
|
self._mirrored = mirrored
|
||||||
self._repetition = repetition
|
self._repetition = repetition
|
||||||
self._annotations = annotations
|
self._annotations = annotations
|
||||||
else:
|
else:
|
||||||
|
|
@ -87,6 +89,7 @@ class Text(PositionableImpl, RotatableImpl, Shape):
|
||||||
self.string = string
|
self.string = string
|
||||||
self.height = height
|
self.height = height
|
||||||
self.rotation = rotation
|
self.rotation = rotation
|
||||||
|
self.mirrored = mirrored
|
||||||
self.repetition = repetition
|
self.repetition = repetition
|
||||||
self.annotations = annotations
|
self.annotations = annotations
|
||||||
self.font_path = font_path
|
self.font_path = font_path
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,11 @@ from .scalable import (
|
||||||
Scalable as Scalable,
|
Scalable as Scalable,
|
||||||
ScalableImpl as ScalableImpl,
|
ScalableImpl as ScalableImpl,
|
||||||
)
|
)
|
||||||
from .mirrorable import Mirrorable as Mirrorable
|
from .mirrorable import (
|
||||||
|
Mirrorable as Mirrorable,
|
||||||
|
Flippable as Flippable,
|
||||||
|
FlippableImpl as FlippableImpl,
|
||||||
|
)
|
||||||
from .copyable import Copyable as Copyable
|
from .copyable import Copyable as Copyable
|
||||||
from .annotatable import (
|
from .annotatable import (
|
||||||
Annotatable as Annotatable,
|
Annotatable as Annotatable,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,14 @@
|
||||||
from typing import Self
|
from typing import Self, Any, TYPE_CHECKING, cast
|
||||||
from abc import ABCMeta, abstractmethod
|
from abc import ABCMeta, abstractmethod
|
||||||
|
|
||||||
|
import numpy
|
||||||
|
from numpy.typing import ArrayLike, NDArray
|
||||||
|
|
||||||
|
from ..error import MasqueError
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from .positionable import Positionable
|
||||||
|
|
||||||
|
|
||||||
class Mirrorable(metaclass=ABCMeta):
|
class Mirrorable(metaclass=ABCMeta):
|
||||||
"""
|
"""
|
||||||
|
|
@ -11,10 +19,10 @@ class Mirrorable(metaclass=ABCMeta):
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def mirror(self, axis: int = 0) -> Self:
|
def mirror(self, axis: int = 0) -> Self:
|
||||||
"""
|
"""
|
||||||
Mirror the entity across an axis.
|
Mirror the entity across an axis through its origin, ignoring its offset.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
axis: Axis to mirror across.
|
axis: Axis to mirror across (0: x-axis, 1: y-axis).
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
self
|
self
|
||||||
|
|
@ -23,10 +31,11 @@ class Mirrorable(metaclass=ABCMeta):
|
||||||
|
|
||||||
def mirror2d(self, across_x: bool = False, across_y: bool = False) -> Self:
|
def mirror2d(self, across_x: bool = False, across_y: bool = False) -> Self:
|
||||||
"""
|
"""
|
||||||
Optionally mirror the entity across both axes
|
Optionally mirror the entity across both axes through its origin.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
axes: (mirror_across_x, mirror_across_y)
|
across_x: Mirror across x axis (flip y)
|
||||||
|
across_y: Mirror across y axis (flip x)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
self
|
self
|
||||||
|
|
@ -38,30 +47,56 @@ class Mirrorable(metaclass=ABCMeta):
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
|
||||||
#class MirrorableImpl(Mirrorable, metaclass=ABCMeta):
|
class Flippable(metaclass=ABCMeta):
|
||||||
# """
|
"""
|
||||||
# Simple implementation of `Mirrorable`
|
Trait class for entities which can be mirrored relative to an external line.
|
||||||
# """
|
"""
|
||||||
# __slots__ = ()
|
__slots__ = ()
|
||||||
#
|
|
||||||
# _mirrored: NDArray[numpy.bool]
|
@staticmethod
|
||||||
# """ Whether to mirror the instance across the x and/or y axes. """
|
def _check_flip_args(axis: int | None = None, *, x: float | None = None, y: float | None = None) -> tuple[int, float]:
|
||||||
#
|
pivot = numpy.zeros(2)
|
||||||
# #
|
if axis is not None:
|
||||||
# # Properties
|
if x is not None or y is not None:
|
||||||
# #
|
raise MasqueError('Cannot specify both axis and x or y')
|
||||||
# # Mirrored property
|
return axis, pivot
|
||||||
# @property
|
if x is not None:
|
||||||
# def mirrored(self) -> NDArray[numpy.bool]:
|
if y is not None:
|
||||||
# """ Whether to mirror across the [x, y] axes, respectively """
|
raise MasqueError('Cannot specify both x and y')
|
||||||
# return self._mirrored
|
return 0, pivot + (x, 0)
|
||||||
#
|
if y is not None:
|
||||||
# @mirrored.setter
|
return 1, pivot + (0, y)
|
||||||
# def mirrored(self, val: Sequence[bool]) -> None:
|
raise MasqueError('Must specify one of axis, x, or y')
|
||||||
# if is_scalar(val):
|
|
||||||
# raise MasqueError('Mirrored must be a 2-element list of booleans')
|
@abstractmethod
|
||||||
# self._mirrored = numpy.array(val, dtype=bool)
|
def flip_across(self, axis: int | None = None, *, x: float | None = None, y: float | None = None) -> Self:
|
||||||
#
|
"""
|
||||||
# #
|
Mirror the object across a line.
|
||||||
# # Methods
|
|
||||||
# #
|
Args:
|
||||||
|
axis: Axis to mirror across. 0 mirrors across x=0. 1 mirrors across y=0.
|
||||||
|
x: Vertical line x=val to mirror across.
|
||||||
|
y: Horizontal line y=val to mirror across.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
self
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class FlippableImpl(Flippable, metaclass=ABCMeta):
|
||||||
|
"""
|
||||||
|
Implementation of `Flippable` for objects which are `Mirrorable` and `Positionable`.
|
||||||
|
"""
|
||||||
|
__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.offset[1 - axis] *= -1
|
||||||
|
cast('Positionable', self).translate(+pivot)
|
||||||
|
return self
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue