2020-07-22 02:45:16 -07:00
|
|
|
# TODO top-level comment about how traits should set __slots__ = (), and how to use AutoSlots
|
|
|
|
|
2023-02-23 13:37:34 -08:00
|
|
|
from typing import Self, Any
|
2020-07-22 02:45:16 -07:00
|
|
|
from abc import ABCMeta, abstractmethod
|
2022-02-23 15:47:38 -08:00
|
|
|
|
|
|
|
import numpy
|
|
|
|
from numpy.typing import NDArray, ArrayLike
|
2020-07-22 02:45:16 -07:00
|
|
|
|
2020-11-09 21:59:28 -08:00
|
|
|
from ..error import MasqueError
|
2020-07-22 02:45:16 -07:00
|
|
|
|
|
|
|
|
2023-01-22 22:16:09 -08:00
|
|
|
_empty_slots = () # Workaround to get mypy to ignore intentionally empty slots for superclass
|
|
|
|
|
|
|
|
|
2020-07-22 02:45:16 -07:00
|
|
|
class Positionable(metaclass=ABCMeta):
|
|
|
|
"""
|
|
|
|
Abstract class for all positionable entities
|
|
|
|
"""
|
|
|
|
__slots__ = ()
|
|
|
|
|
|
|
|
'''
|
|
|
|
---- Abstract properties
|
|
|
|
'''
|
|
|
|
@property
|
|
|
|
@abstractmethod
|
2022-02-23 15:47:38 -08:00
|
|
|
def offset(self) -> NDArray[numpy.float64]:
|
2020-07-22 02:45:16 -07:00
|
|
|
"""
|
|
|
|
[x, y] offset
|
|
|
|
"""
|
|
|
|
pass
|
|
|
|
|
2023-01-19 22:20:16 -08:00
|
|
|
@offset.setter
|
|
|
|
@abstractmethod
|
2023-01-25 23:57:02 -08:00
|
|
|
def offset(self, val: ArrayLike) -> None:
|
2023-01-19 22:20:16 -08:00
|
|
|
pass
|
2020-07-22 02:45:16 -07:00
|
|
|
|
|
|
|
@abstractmethod
|
2023-02-23 13:37:34 -08:00
|
|
|
def set_offset(self, offset: ArrayLike) -> Self:
|
2020-07-22 02:45:16 -07:00
|
|
|
"""
|
|
|
|
Set the offset
|
|
|
|
|
|
|
|
Args:
|
|
|
|
offset: [x_offset, y,offset]
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
self
|
|
|
|
"""
|
|
|
|
pass
|
|
|
|
|
|
|
|
@abstractmethod
|
2023-02-23 13:37:34 -08:00
|
|
|
def translate(self, offset: ArrayLike) -> Self:
|
2020-07-22 02:45:16 -07:00
|
|
|
"""
|
|
|
|
Translate the entity by the given offset
|
|
|
|
|
|
|
|
Args:
|
|
|
|
offset: [x_offset, y,offset]
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
self
|
|
|
|
"""
|
|
|
|
pass
|
|
|
|
|
2022-02-23 15:47:38 -08:00
|
|
|
@abstractmethod
|
2023-02-23 13:15:32 -08:00
|
|
|
def get_bounds(self) -> NDArray[numpy.float64] | None:
|
2022-02-23 15:47:38 -08:00
|
|
|
"""
|
|
|
|
Returns `[[x_min, y_min], [x_max, y_max]]` which specify a minimal bounding box for the entity.
|
2022-02-27 21:21:34 -08:00
|
|
|
Returns `None` for an empty entity.
|
2022-02-23 15:47:38 -08:00
|
|
|
"""
|
|
|
|
pass
|
|
|
|
|
2022-02-27 21:21:34 -08:00
|
|
|
def get_bounds_nonempty(self) -> NDArray[numpy.float64]:
|
|
|
|
"""
|
|
|
|
Returns `[[x_min, y_min], [x_max, y_max]]` which specify a minimal bounding box for the entity.
|
|
|
|
Asserts that the entity is non-empty (i.e., `get_bounds()` does not return None).
|
|
|
|
|
|
|
|
This is handy for destructuring like `xy_min, xy_max = entity.get_bounds_nonempty()`
|
|
|
|
"""
|
|
|
|
bounds = self.get_bounds()
|
2023-01-23 22:27:26 -08:00
|
|
|
assert bounds is not None
|
2022-02-27 21:21:34 -08:00
|
|
|
return bounds
|
|
|
|
|
2020-07-22 02:45:16 -07:00
|
|
|
|
|
|
|
class PositionableImpl(Positionable, metaclass=ABCMeta):
|
|
|
|
"""
|
|
|
|
Simple implementation of Positionable
|
|
|
|
"""
|
2023-01-22 22:16:09 -08:00
|
|
|
__slots__ = _empty_slots
|
2020-07-22 02:45:16 -07:00
|
|
|
|
2022-02-23 15:47:38 -08:00
|
|
|
_offset: NDArray[numpy.float64]
|
2020-07-22 02:45:16 -07:00
|
|
|
""" `[x_offset, y_offset]` """
|
|
|
|
|
|
|
|
'''
|
|
|
|
---- Properties
|
|
|
|
'''
|
|
|
|
# offset property
|
|
|
|
@property
|
2023-01-23 22:27:26 -08:00
|
|
|
def offset(self) -> Any: # TODO mypy#3003 NDArray[numpy.float64]:
|
2020-07-22 02:45:16 -07:00
|
|
|
"""
|
|
|
|
[x, y] offset
|
|
|
|
"""
|
|
|
|
return self._offset
|
|
|
|
|
|
|
|
@offset.setter
|
2022-02-23 15:47:38 -08:00
|
|
|
def offset(self, val: ArrayLike) -> None:
|
2020-08-12 21:43:46 -07:00
|
|
|
if not isinstance(val, numpy.ndarray) or val.dtype != numpy.float64:
|
2020-07-22 02:45:16 -07:00
|
|
|
val = numpy.array(val, dtype=float)
|
|
|
|
|
|
|
|
if val.size != 2:
|
2020-11-09 21:59:28 -08:00
|
|
|
raise MasqueError('Offset must be convertible to size-2 ndarray')
|
2023-01-22 22:16:09 -08:00
|
|
|
self._offset = val.flatten() # type: ignore
|
2020-07-22 02:45:16 -07:00
|
|
|
|
|
|
|
'''
|
|
|
|
---- Methods
|
|
|
|
'''
|
2023-02-23 13:37:34 -08:00
|
|
|
def set_offset(self, offset: ArrayLike) -> Self:
|
2020-07-22 02:45:16 -07:00
|
|
|
self.offset = offset
|
|
|
|
return self
|
|
|
|
|
2023-02-23 13:37:34 -08:00
|
|
|
def translate(self, offset: ArrayLike) -> Self:
|
2022-02-23 15:47:38 -08:00
|
|
|
self._offset += offset # type: ignore # NDArray += ArrayLike should be fine??
|
2020-07-22 02:45:16 -07:00
|
|
|
return self
|