Source code for fibomat.linalg.transformables.transformable_base
"""
Provides the :class:`TransformableBase` class.
"""
from __future__ import annotations
from typing import Optional, Union, TypeVar, Literal, Type, Generic, Callable
import abc
import numpy as np
from fibomat.linalg.vectors import Vector, VectorLike, DimVector
from fibomat.describable import Describable
from fibomat.linalg.transformables.transformation_builder import (
_TransformationBuilder, _TranslationBuilder, _RotationBuilder, _ScaleBuilder, _MirrorBuilder
)
from fibomat.linalg.boundingboxes import BoundingBox, DimBoundingBox
from fibomat.units import LengthQuantity
VectorT = TypeVar('VectorT', Vector, DimVector)
ScalarT = TypeVar('ScalarT', int, float, np.integer, np.float, LengthQuantity)
BBoxT = TypeVar('BBoxT', BoundingBox, DimBoundingBox)
SelfT = TypeVar('SelfT', bound='TransformableBase')
[docs]class TransformableBase(Describable, Generic[VectorT, ScalarT, BBoxT], abc.ABC):
"""
:class:`Transformable` is a base class providing the translate, rotate and uniform scale
transformations.
In order to use this mixin in a child class, the following methods and properties must be implemented:
* :attr:`Transformable.center`
* :meth:`Transformable.translate`
* :meth:`Transformable.simple_rotate`
* :meth:`Transformable.simple_scale`
"""
_VectorClass: Type[VectorT]
[docs] def __init__(self: SelfT, description: Optional[str] = None):
"""
Args:
pivot (VectorLike, optional): if set, the :attr:`Transformable.pivot` is set to `pivot`. If not set,
:attr:`Transformable.center` is used as default.
description (str, optional): optional description
"""
super().__init__(description)
self._pivot: Optional[Callable[[SelfT], VectorT]] = None
@property
@abc.abstractmethod
def center(self) -> VectorT:
"""center of the (geometric) object
Access:
get
Returns:
Any
"""
raise NotImplementedError
@property
def pivot(self) -> VectorT:
"""Origin of the (geometric) object. If origin is set to `None`, :attr:`Transformable.center` will be
returned.
Pivot must be set to a callable function without parameters. ::
transformable_obj = ...
transformable_obj.pivot = lambda: return Vector(1, 2)
print(transformable_obj.pivot) # will print Vector(1, 2)
Access:
get/set
Returns:
Vector
"""
# if self._pivot is not None:
# return Vector(self._pivot(self))
# return self.center
if self._pivot is not None:
return self._VectorClass(self._pivot(self))
return self.center
@pivot.setter
def pivot(self: SelfT, value: Callable[[SelfT], VectorLike]):
self._pivot = value
@property
@abc.abstractmethod
def bounding_box(self) -> BBoxT:
"""
:class:`~fibomat.boundingbox.BoundingBox`: bounding box of transformable
Access:
get
"""
raise NotImplementedError
@abc.abstractmethod
def _impl_translate(self, trans_vec: VectorT) -> None:
"""Translate the object by `trans_vec`.
Args:
trans_vec (VectorLike): translation linalg
Returns:
None
"""
raise NotImplementedError
@abc.abstractmethod
def _impl_rotate(self, theta: float) -> None:
"""Rotate the object by theta.
Args:
theta: rotation angle
Returns:
None
"""
raise NotImplementedError
@abc.abstractmethod
def _impl_scale(self, fac: float) -> None:
"""Scale the object by fac.
Args:
fac: scale factor
Returns:
None
"""
raise NotImplementedError
@abc.abstractmethod
def _impl_mirror(self, mirror_axis: VectorT) -> None:
"""Mirror the object at mirror_axis.
Args:
mirror_axis (VectorLike): mirror_plane
Returns:
None
"""
raise NotImplementedError
def _apply_shifted_trafo(
self: SelfT,
trafo: Callable[[float], None],
arg: float,
origin: Optional[Union[VectorT, str]] = None
) -> SelfT:
if origin:
if isinstance(origin, str):
if origin == 'center':
origin = self.center
elif origin == 'pivot':
origin = self.pivot
else:
raise ValueError(f'Unknown origin `{origin}`')
else:
origin = self._VectorClass(origin)
self._impl_translate(-origin) # pylint: disable=invalid-unary-operand-type
trafo(float(arg))
self._impl_translate(origin)
else:
trafo(float(arg))
return self
[docs] def translated_to(self: SelfT, pos: VectorT) -> SelfT:
"""Return a translated copy of the object so that self.pivot == pos
Args:
pos: new position of object
Returns:
TransformableBase
"""
return self.translated(self._VectorClass(pos) - self.pivot)
[docs] def translated(self: SelfT, trans_vec: VectorT) -> SelfT:
"""Return a translated copy of the object by `trans_vec`.
Args:
trans_vec (VectorLike): translation vector
Returns:
TransformableBase
"""
# pylint: disable=protected-access
# if not np.isclose(float(self._VectorClass(trans_vec).mag), 0.):
clone: SelfT = self.clone()
clone._impl_translate(trans_vec)
return clone
# return self
[docs] def rotated(self: SelfT, theta: float, origin: Optional[Union[VectorT, str]] = None) -> SelfT:
"""Return a rotated copy around `origin` with angle `theta` in math. positive direction (counterclockwise).
Args:
theta (float): rotation angle in rad
origin (Optional[Union[linalg.VectorLike, str]], optional):
origin of rotation. If not set, (0,0) is used as
origin. If origin == 'center', the
:attr:`Transformable.center` of the object will
be used. The same applies for the case that
origin == 'origin' with the
:attr:`Transformable.origin` property. Default
to None.
Returns:
TransformableBase
"""
# pylint: disable=protected-access
if not np.isclose(float(theta), 0.):
clone: SelfT = self.clone()
clone._apply_shifted_trafo(clone._impl_rotate, theta, origin)
return clone
return self
[docs] def scaled(self: SelfT, fac: float, origin: Optional[Union[VectorT, str]] = None) -> SelfT:
"""Return a scale object homogeneously about `origin` with factor `s`.
Args:
fac (float): rotation angle in rad
origin (Optional[Union[linalg.VectorLike, str]], optional):
origin of rotation. If not set, (0,0) is used as
origin. If origin == 'center', the
:attr:`Transformable.center` of the object will
be used. The same applies for the case that
origin == 'origin' with the
:attr:`Transformable.origin` property. Default
to None.
Returns:
TransformableBase
"""
if not np.isclose(float(fac), 1.):
clone: SelfT = self.clone()
clone._apply_shifted_trafo(clone._impl_scale, fac, origin) # pylint: disable=protected-access
return clone
return self
[docs] def mirrored(self: SelfT, mirror_plane: VectorT) -> SelfT:
"""Return a mirrored object mirrored about `mirror_plane`.
Args:
mirror_plane (VectorLike): mirror plane to be used.
Returns:
TransformableBase
"""
clone: T = self.clone()
clone._impl_mirror(mirror_plane) # pylint: disable=protected-access
return clone
[docs] def transformed(self: SelfT, transformations: _TransformationBuilder[VectorT]) -> SelfT:
"""Return a transformed object. the transformation can be build by the following functions:
- :func:`~fibomat.linalg.transformation_builder.translate`
- :func:`~fibomat.linalg.transformation_builder.rotate`
- :func:`~fibomat.linalg.transformation_builder.scale`
- :func:`~fibomat.linalg.transformation_builder.mirror`
E.g. ::
transformable_obj.transform(translate([1, 2]) | rotate(np.pi/3) | mirror([3,4])
Args:
transformations (_TransformationBuilder): transformation
Returns:
TransformableBase
"""
# pylint: disable=protected-access
clone: SelfT = self.clone()
for trafo in transformations.transformations:
if isinstance(trafo, _TranslationBuilder):
clone._impl_translate(trafo.trans_vec)
elif isinstance(trafo, _RotationBuilder):
clone._apply_shifted_trafo(clone._impl_rotate, trafo.theta, trafo.origin)
elif isinstance(trafo, _ScaleBuilder):
clone._apply_shifted_trafo(clone._impl_scale, trafo.fac, trafo.origin)
elif isinstance(trafo, _MirrorBuilder):
clone._impl_mirror(trafo.mirror_plane)
else:
raise TypeError(f'{trafo.__class__} is an unknown transforamtion.')
return clone