Source code for fibomat.shapes.ellipse

"""Provides the :class:`Ellipse` class."""
# pylint: disable=too-many-arguments,invalid-name

from __future__ import annotations
from typing import Optional

import numpy as np

from fibomat.shapes.shape import Shape
from fibomat.linalg import VectorLike, Vector, BoundingBox, rotate, translate
from fibomat.shapes.arc_spline import ArcSpline, ArcSplineCompatible
from fibomat.shapes.circle import Circle


[docs]class Ellipse(Shape, ArcSplineCompatible): """2-dim ellipse."""
[docs] def __init__( self, a: float, b: float, theta: float = 0, center: Optional[VectorLike] = None, description: Optional[str] = None ): """ Args: a (float): length of half axis in pos. x-direction (unrotated) b (float): length of half axis in pos. y-direction (unrotated) theta (float): rotation angle, default to 0 center (VectorLike, optional): center of circle, default to (0, 0) description (str, optional): description """ super().__init__(description) # raise NotImplementedError assert a > 0. assert b > 0. # center, axis in pos. x direction, axis in pos. y direction (all unrotated) # self._axes = VectorArray( # (float(a), 0.), # (0., float(b)) # ).rotated(float(theta)) self._norm_a_axes = Vector(1, 0.).rotated(float(theta)) self._a = float(a) self._b = float(b) self._center = Vector(center) if center is not None else Vector()
def __repr__(self) -> str: return '{}(a={!r}, b={!r}, theta={!r}, center={!r})'.format( self.__class__.__name__, self.a, self.b, self.theta, self.center)
[docs] def to_arc_spline(self) -> ArcSpline: if np.isclose(self._a, self._b): return Circle(r=self._a, center=self._center).to_arc_spline() from fibomat.shapes.parametric_curve import ParametricCurve from sympy import Curve from sympy.abc import t from sympy import sin, cos ellipse = ParametricCurve.from_sympy_curve( Curve([self.a * cos(t), self.b * sin(t)], (t, 0, 2*np.pi)), try_length_integration=False ) ellipse_arc_spline = ellipse.to_arc_spline() return ellipse_arc_spline.transformed(rotate(self.theta) | translate(self.center))
@property def a(self) -> float: """Length of half axis in pos. x-direction (unrotated) Access: get Returns: float """ return self._a @property def b(self) -> float: """Length of half axis in pos. y-direction (unrotated) Access: get Returns: float """ return self._b @property def theta(self) -> float: """rotation angle of ellipse. Access: get Returns: float """ return self._norm_a_axes.angle_about_x_axis @property def center(self) -> Vector: return self._center @property def bounding_box(self) -> BoundingBox: # TODO: test this carefully! # https://stackoverflow.com/questions/87734/how-do-you-calculate-the-axis-aligned-bounding-box-of-an-ellipse # https://www.iquilezles.org/www/articles/ellipses/ellipses.htm u = np.array(self._a * self._norm_a_axes) # b axis os orthogoan to a axis v = np.array((self._b * self._norm_a_axes).rotated(self.theta + np.pi/4)) w = np.sqrt(u*u + v*v) return BoundingBox(self._center-w, self._center+w) @property def is_closed(self) -> bool: return True def _impl_translate(self, trans_vec: VectorLike) -> None: self._center += Vector(trans_vec) def _impl_rotate(self, theta: float) -> None: self._axes = self._norm_a_axes.rotated(float(theta)) def _impl_scale(self, fac: float) -> None: self._center *= float(fac) self._a *= float(fac) self._b *= float(fac) def _impl_mirror(self, mirror_axis: VectorLike) -> None: self._norm_a_axes = self._norm_a_axes.mirrored(mirror_axis) self._center = self._center.mirrored(mirror_axis)
# TODO: change a and b??