You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
masque/masque/traits/positionable.py

143 lines
3.3 KiB
Python

# TODO top-level comment about how traits should set __slots__ = (), and how to use AutoSlots
from typing import TypeVar, Any, Optional
from abc import ABCMeta, abstractmethod
import numpy
from numpy.typing import NDArray, ArrayLike
from ..error import MasqueError
T = TypeVar('T', bound='Positionable')
I = TypeVar('I', bound='PositionableImpl')
class Positionable(metaclass=ABCMeta):
"""
Abstract class for all positionable entities
"""
__slots__ = ()
'''
---- Abstract properties
'''
@property
@abstractmethod
def offset(self) -> NDArray[numpy.float64]:
"""
[x, y] offset
"""
pass
# @offset.setter
# @abstractmethod
# def offset(self, val: ArrayLike):
# pass
@abstractmethod
def set_offset(self: T, offset: ArrayLike) -> T:
"""
Set the offset
Args:
offset: [x_offset, y,offset]
Returns:
self
"""
pass
@abstractmethod
def translate(self: T, offset: ArrayLike) -> T:
"""
Translate the entity by the given offset
Args:
offset: [x_offset, y,offset]
Returns:
self
"""
pass
@abstractmethod
def get_bounds(self) -> Optional[NDArray[numpy.float64]]:
"""
Returns `[[x_min, y_min], [x_max, y_max]]` which specify a minimal bounding box for the entity.
Returns `None` for an empty entity.
"""
pass
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()
assert(bounds is not None)
return bounds
class PositionableImpl(Positionable, metaclass=ABCMeta):
"""
Simple implementation of Positionable
"""
__slots__ = ()
_offset: NDArray[numpy.float64]
""" `[x_offset, y_offset]` """
'''
---- Properties
'''
# offset property
@property
def offset(self) -> Any: #TODO mypy#3003 NDArray[numpy.float64]:
"""
[x, y] offset
"""
return self._offset
@offset.setter
def offset(self, val: ArrayLike) -> None:
if not isinstance(val, numpy.ndarray) or val.dtype != numpy.float64:
val = numpy.array(val, dtype=float)
if val.size != 2:
raise MasqueError('Offset must be convertible to size-2 ndarray')
self._offset = val.flatten()
'''
---- Methods
'''
def set_offset(self: I, offset: ArrayLike) -> I:
self.offset = offset
return self
def translate(self: I, offset: ArrayLike) -> I:
self._offset += offset # type: ignore # NDArray += ArrayLike should be fine??
return self
def _lock(self: I) -> I:
"""
Lock the entity, disallowing further changes
Returns:
self
"""
self._offset.flags.writeable = False
return self
def _unlock(self: I) -> I:
"""
Unlock the entity
Returns:
self
"""
self._offset.flags.writeable = True
return self