"""Provides the :class:`Rect` class."""
from __future__ import annotations
from typing import Optional, List
import numpy as np
from fibomat.shapes import Shape
from fibomat.shapes.arc_spline import ArcSplineCompatible, ArcSpline
from fibomat.linalg import VectorLike, Vector, BoundingBox, signed_angle_between
from fibomat.shapes.line import Line
[docs]class Rect(Shape, ArcSplineCompatible):
"""2-dim rect."""
[docs] def __init__( # pylint: disable=too-many-arguments
self,
width: float, height: float, theta: float = 0,
center: Optional[VectorLike] = None,
description: Optional[str] = None
):
"""
Args:
width (float): width of rect
height (float): height of rect
theta (float): rotation angle of rect, default to 0
center (VectorLike, optional): center of rect, default to (0, 0)
description (str, optional): description
"""
super().__init__(description)
width_half = float(width) / 2
height_half = float(height) / 2
center = Vector(center)
self._rect = ArcSpline([
(*(center + (width_half, height_half)), 0.),
(*(center + (-width_half, height_half)), 0.),
(*(center + (-width_half, -height_half)), 0.),
(*(center + (width_half, -height_half)), 0.)
], True).rotated(theta, 'center')
self._rot_angle = float(theta)
# self._axes = VectorArray([float(width) / 2, 0.], [0., float(height) / 2])
# self._axes = self._axes.rotated(theta)
#
# self._center = Vector(center) if center is not None else Vector()
[docs] @classmethod
def from_bounding_box(cls, bbox: BoundingBox) -> Rect:
"""Create a rect from a BoundingBox.
Args:
bbox: bounding box from which a rect is constructed
Returns:
Rect
"""
return cls(bbox.width, bbox.height, 0., bbox.center)
[docs] @classmethod
def from_line(cls, line: Line, height: float):
"""Create a rect defined by a line a certain height
Args:
line (Line):
height (float):
Returns:
Rect
"""
direction = (line.end - line.start)
width = direction.mag
# is theta correct?
return cls(width=width, height=height, center=line.center, theta=direction.angle_about_x_axis)
[docs] def to_arc_spline(self) -> ArcSpline:
vertices = np.append(self.corners, np.zeros(4)[:, np.newaxis], axis=1)
return ArcSpline(vertices, True, self.description)
@property
def width(self) -> float:
"""Width of rect.
Access:
get
Returns:
float
"""
start, end = self._rect.vertices[(0, 1), :2]
return np.linalg.norm(start - end)
@property
def height(self) -> float:
"""Height of rect.
Access:
get
Returns:
float
"""
start, end = self._rect.vertices[(1, 2), :2]
return np.linalg.norm(start - end)
@property
def theta(self) -> float:
""" Rotation angle of rect.
Access:
get
Returns:
float
"""
return self._rot_angle
@property
def corners(self) -> List[Vector]:
"""Corner points of rect
Access:
get
Returns:
List[Vector]
"""
return [Vector(vec) for vec in self._rect.vertices[:, :2]]
def __repr__(self) -> str:
return '{}(width={!r}, height={!r}, theta={!r}, center={!r})'.format(
self.__class__.__name__, self.width, self.height, self.theta, self.center)
@property
def center(self) -> Vector:
return self._rect.center
@property
def bounding_box(self) -> BoundingBox:
return BoundingBox.from_points(self.corners)
@property
def is_closed(self) -> bool:
return True
def _impl_translate(self, trans_vec: VectorLike) -> None:
self._rect._impl_translate(trans_vec)
def _impl_rotate(self, theta: float) -> None:
self._rect._impl_rotate(theta)
self._rot_angle += float(theta)
def _impl_scale(self, fac: float) -> None:
self._rect._impl_scale(fac)
def _impl_mirror(self, mirror_axis: VectorLike) -> None:
# how das mirroring affect rot angle?
# TODO: test this carefully
ref_vec = self._rect.vertices[0, :2]
self._rect._impl_mirror(mirror_axis)
rot_angle = signed_angle_between(ref_vec, self._rect.vertices[0, :2])
self._rot_angle += rot_angle