2022-02-15 14:37:15 +08:00
|
|
|
from __future__ import annotations
|
|
|
|
|
2021-08-21 10:35:29 -07:00
|
|
|
import math
|
2022-02-15 14:37:15 +08:00
|
|
|
|
|
|
|
import numpy as np
|
2018-03-30 18:42:32 -07:00
|
|
|
|
2022-04-12 21:09:25 +08:00
|
|
|
from manimlib.constants import DL, DOWN, DR, LEFT, ORIGIN, OUT, RIGHT, UL, UP, UR
|
2022-12-16 09:55:50 -08:00
|
|
|
from manimlib.constants import GREY_A, RED, WHITE, BLACK
|
2023-09-04 18:46:11 -04:00
|
|
|
from manimlib.constants import MED_SMALL_BUFF, SMALL_BUFF
|
2024-12-12 10:39:54 -06:00
|
|
|
from manimlib.constants import DEG, PI, TAU
|
2018-12-24 12:37:51 -08:00
|
|
|
from manimlib.mobject.mobject import Mobject
|
2022-04-12 19:19:59 +08:00
|
|
|
from manimlib.mobject.types.vectorized_mobject import DashedVMobject
|
2018-12-24 12:37:51 -08:00
|
|
|
from manimlib.mobject.types.vectorized_mobject import VGroup
|
|
|
|
from manimlib.mobject.types.vectorized_mobject import VMobject
|
2023-09-04 19:08:13 -04:00
|
|
|
from manimlib.utils.bezier import quadratic_bezier_points_for_arc
|
2019-02-07 21:57:40 -08:00
|
|
|
from manimlib.utils.iterables import adjacent_n_tuples
|
|
|
|
from manimlib.utils.iterables import adjacent_pairs
|
2020-02-18 22:32:57 -08:00
|
|
|
from manimlib.utils.simple_functions import clip
|
2022-04-12 19:19:59 +08:00
|
|
|
from manimlib.utils.simple_functions import fdiv
|
2019-02-07 21:57:40 -08:00
|
|
|
from manimlib.utils.space_ops import angle_between_vectors
|
2022-04-12 19:19:59 +08:00
|
|
|
from manimlib.utils.space_ops import angle_of_vector
|
2023-01-11 20:47:04 -08:00
|
|
|
from manimlib.utils.space_ops import cross2d
|
2018-12-24 12:37:51 -08:00
|
|
|
from manimlib.utils.space_ops import compass_directions
|
2020-02-05 14:46:25 -08:00
|
|
|
from manimlib.utils.space_ops import find_intersection
|
2018-12-24 12:37:51 -08:00
|
|
|
from manimlib.utils.space_ops import get_norm
|
2019-02-07 10:59:04 -08:00
|
|
|
from manimlib.utils.space_ops import normalize
|
2018-12-24 12:37:51 -08:00
|
|
|
from manimlib.utils.space_ops import rotate_vector
|
2020-06-28 10:05:49 -07:00
|
|
|
from manimlib.utils.space_ops import rotation_matrix_transpose
|
2024-08-28 10:50:51 -05:00
|
|
|
from manimlib.utils.space_ops import rotation_between_vectors
|
2015-10-27 21:00:50 -07:00
|
|
|
|
2022-04-12 19:19:59 +08:00
|
|
|
from typing import TYPE_CHECKING
|
|
|
|
|
|
|
|
if TYPE_CHECKING:
|
2023-02-02 10:54:47 -08:00
|
|
|
from typing import Iterable, Optional
|
|
|
|
from manimlib.typing import ManimColor, Vect3, Vect3Array, Self
|
2022-02-15 14:37:15 +08:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2019-02-07 10:12:42 -08:00
|
|
|
DEFAULT_DOT_RADIUS = 0.08
|
2019-03-03 07:42:00 -06:00
|
|
|
DEFAULT_SMALL_DOT_RADIUS = 0.04
|
2019-02-07 15:41:54 -08:00
|
|
|
DEFAULT_DASH_LENGTH = 0.05
|
2019-03-03 07:42:00 -06:00
|
|
|
DEFAULT_ARROW_TIP_LENGTH = 0.35
|
2020-06-06 10:56:23 -07:00
|
|
|
DEFAULT_ARROW_TIP_WIDTH = 0.35
|
2019-02-07 10:12:42 -08:00
|
|
|
|
|
|
|
|
2021-08-26 11:43:19 -07:00
|
|
|
# Deprecate?
|
2019-02-07 17:53:11 -08:00
|
|
|
class TipableVMobject(VMobject):
|
2019-06-10 03:36:16 +08:00
|
|
|
"""
|
|
|
|
Meant for shared functionality between Arc and Line.
|
|
|
|
Functionality can be classified broadly into these groups:
|
|
|
|
|
|
|
|
* Adding, Creating, Modifying tips
|
|
|
|
- add_tip calls create_tip, before pushing the new tip
|
|
|
|
into the TipableVMobject's list of submobjects
|
|
|
|
- stylistic and positional configuration
|
|
|
|
|
|
|
|
* Checking for tips
|
|
|
|
- Boolean checks for whether the TipableVMobject has a tip
|
|
|
|
and a starting tip
|
|
|
|
|
|
|
|
* Getters
|
|
|
|
- Straightforward accessors, returning information pertaining
|
|
|
|
to the TipableVMobject instance's tip(s), its length etc
|
|
|
|
"""
|
2022-12-15 18:19:09 -08:00
|
|
|
tip_config: dict = dict(
|
|
|
|
fill_opacity=1.0,
|
|
|
|
stroke_width=0.0,
|
|
|
|
tip_style=0.0, # triangle=0, inner_smooth=1, dot=2
|
|
|
|
)
|
2020-02-05 14:46:25 -08:00
|
|
|
|
2019-06-10 03:36:16 +08:00
|
|
|
# Adding, Creating, Modifying tips
|
2023-01-31 14:12:41 -08:00
|
|
|
def add_tip(self, at_start: bool = False, **kwargs) -> Self:
|
2019-06-10 03:36:16 +08:00
|
|
|
"""
|
|
|
|
Adds a tip to the TipableVMobject instance, recognising
|
|
|
|
that the endpoints might need to be switched if it's
|
|
|
|
a 'starting tip' or not.
|
|
|
|
"""
|
2020-06-06 10:56:23 -07:00
|
|
|
tip = self.create_tip(at_start, **kwargs)
|
2019-03-03 07:42:00 -06:00
|
|
|
self.reset_endpoints_based_on_tip(tip, at_start)
|
|
|
|
self.asign_tip_attr(tip, at_start)
|
2021-06-13 21:07:25 +08:00
|
|
|
tip.set_color(self.get_stroke_color())
|
2019-03-03 07:42:00 -06:00
|
|
|
self.add(tip)
|
|
|
|
return self
|
|
|
|
|
2022-02-15 14:37:15 +08:00
|
|
|
def create_tip(self, at_start: bool = False, **kwargs) -> ArrowTip:
|
2019-06-10 03:36:16 +08:00
|
|
|
"""
|
|
|
|
Stylises the tip, positions it spacially, and returns
|
|
|
|
the newly instantiated tip to the caller.
|
|
|
|
"""
|
2020-06-06 10:56:23 -07:00
|
|
|
tip = self.get_unpositioned_tip(**kwargs)
|
2019-03-03 07:42:00 -06:00
|
|
|
self.position_tip(tip, at_start)
|
|
|
|
return tip
|
|
|
|
|
2022-02-15 14:37:15 +08:00
|
|
|
def get_unpositioned_tip(self, **kwargs) -> ArrowTip:
|
2019-06-10 03:36:16 +08:00
|
|
|
"""
|
|
|
|
Returns a tip that has been stylistically configured,
|
|
|
|
but has not yet been given a position in space.
|
|
|
|
"""
|
2020-06-29 22:53:44 -07:00
|
|
|
config = dict()
|
2020-06-06 10:56:23 -07:00
|
|
|
config.update(self.tip_config)
|
|
|
|
config.update(kwargs)
|
|
|
|
return ArrowTip(**config)
|
2019-03-03 07:42:00 -06:00
|
|
|
|
2022-02-15 14:37:15 +08:00
|
|
|
def position_tip(self, tip: ArrowTip, at_start: bool = False) -> ArrowTip:
|
2019-02-07 17:53:11 -08:00
|
|
|
# Last two control points, defining both
|
|
|
|
# the end, and the tangency direction
|
|
|
|
if at_start:
|
|
|
|
anchor = self.get_start()
|
|
|
|
handle = self.get_first_handle()
|
|
|
|
else:
|
|
|
|
handle = self.get_last_handle()
|
|
|
|
anchor = self.get_end()
|
2020-06-06 10:56:23 -07:00
|
|
|
tip.rotate(angle_of_vector(handle - anchor) - PI - tip.get_angle())
|
2019-03-03 07:42:00 -06:00
|
|
|
tip.shift(anchor - tip.get_tip_point())
|
2019-02-07 17:53:11 -08:00
|
|
|
return tip
|
|
|
|
|
2023-01-31 14:12:41 -08:00
|
|
|
def reset_endpoints_based_on_tip(self, tip: ArrowTip, at_start: bool) -> Self:
|
2019-03-03 07:42:00 -06:00
|
|
|
if self.get_length() == 0:
|
|
|
|
# Zero length, put_start_and_end_on wouldn't
|
|
|
|
# work
|
|
|
|
return self
|
2019-06-10 03:36:16 +08:00
|
|
|
|
2019-02-07 17:53:11 -08:00
|
|
|
if at_start:
|
2020-02-05 14:46:25 -08:00
|
|
|
start = tip.get_base()
|
|
|
|
end = self.get_end()
|
2019-02-07 17:53:11 -08:00
|
|
|
else:
|
2020-02-05 14:46:25 -08:00
|
|
|
start = self.get_start()
|
|
|
|
end = tip.get_base()
|
|
|
|
self.put_start_and_end_on(start, end)
|
2019-02-07 17:53:11 -08:00
|
|
|
return self
|
|
|
|
|
2023-01-31 14:12:41 -08:00
|
|
|
def asign_tip_attr(self, tip: ArrowTip, at_start: bool) -> Self:
|
2019-03-03 07:42:00 -06:00
|
|
|
if at_start:
|
|
|
|
self.start_tip = tip
|
|
|
|
else:
|
|
|
|
self.tip = tip
|
|
|
|
return self
|
|
|
|
|
2019-06-10 03:36:16 +08:00
|
|
|
# Checking for tips
|
2022-02-15 14:37:15 +08:00
|
|
|
def has_tip(self) -> bool:
|
2019-06-10 03:36:16 +08:00
|
|
|
return hasattr(self, "tip") and self.tip in self
|
|
|
|
|
2022-02-15 14:37:15 +08:00
|
|
|
def has_start_tip(self) -> bool:
|
2019-06-10 03:36:16 +08:00
|
|
|
return hasattr(self, "start_tip") and self.start_tip in self
|
|
|
|
|
|
|
|
# Getters
|
2022-02-15 14:37:15 +08:00
|
|
|
def pop_tips(self) -> VGroup:
|
2019-06-10 03:36:16 +08:00
|
|
|
start, end = self.get_start_and_end()
|
|
|
|
result = VGroup()
|
|
|
|
if self.has_tip():
|
|
|
|
result.add(self.tip)
|
|
|
|
self.remove(self.tip)
|
|
|
|
if self.has_start_tip():
|
|
|
|
result.add(self.start_tip)
|
|
|
|
self.remove(self.start_tip)
|
|
|
|
self.put_start_and_end_on(start, end)
|
|
|
|
return result
|
|
|
|
|
2022-02-15 14:37:15 +08:00
|
|
|
def get_tips(self) -> VGroup:
|
2019-06-10 03:36:16 +08:00
|
|
|
"""
|
|
|
|
Returns a VGroup (collection of VMobjects) containing
|
|
|
|
the TipableVMObject instance's tips.
|
|
|
|
"""
|
2019-03-03 07:42:00 -06:00
|
|
|
result = VGroup()
|
|
|
|
if hasattr(self, "tip"):
|
|
|
|
result.add(self.tip)
|
|
|
|
if hasattr(self, "start_tip"):
|
|
|
|
result.add(self.start_tip)
|
|
|
|
return result
|
|
|
|
|
2022-02-15 14:37:15 +08:00
|
|
|
def get_tip(self) -> ArrowTip:
|
2019-06-10 03:36:16 +08:00
|
|
|
"""Returns the TipableVMobject instance's (first) tip,
|
|
|
|
otherwise throws an exception."""
|
2019-03-03 07:42:00 -06:00
|
|
|
tips = self.get_tips()
|
|
|
|
if len(tips) == 0:
|
|
|
|
raise Exception("tip not found")
|
|
|
|
else:
|
|
|
|
return tips[0]
|
|
|
|
|
2022-02-15 14:37:15 +08:00
|
|
|
def get_default_tip_length(self) -> float:
|
2019-03-03 07:42:00 -06:00
|
|
|
return self.tip_length
|
|
|
|
|
2022-12-16 20:35:26 -08:00
|
|
|
def get_first_handle(self) -> Vect3:
|
2021-01-10 18:51:47 -08:00
|
|
|
return self.get_points()[1]
|
2019-02-07 17:53:11 -08:00
|
|
|
|
2022-12-16 20:35:26 -08:00
|
|
|
def get_last_handle(self) -> Vect3:
|
2021-01-10 18:51:47 -08:00
|
|
|
return self.get_points()[-2]
|
2019-02-07 17:53:11 -08:00
|
|
|
|
2022-12-16 20:35:26 -08:00
|
|
|
def get_end(self) -> Vect3:
|
2019-03-03 07:42:00 -06:00
|
|
|
if self.has_tip():
|
|
|
|
return self.tip.get_start()
|
|
|
|
else:
|
|
|
|
return VMobject.get_end(self)
|
|
|
|
|
2022-12-16 20:35:26 -08:00
|
|
|
def get_start(self) -> Vect3:
|
2019-03-03 07:42:00 -06:00
|
|
|
if self.has_start_tip():
|
|
|
|
return self.start_tip.get_start()
|
|
|
|
else:
|
|
|
|
return VMobject.get_start(self)
|
|
|
|
|
2022-02-15 14:37:15 +08:00
|
|
|
def get_length(self) -> float:
|
2019-04-04 14:29:10 -07:00
|
|
|
start, end = self.get_start_and_end()
|
|
|
|
return get_norm(start - end)
|
2019-03-03 07:53:15 -06:00
|
|
|
|
2019-03-03 07:42:00 -06:00
|
|
|
|
2019-02-07 17:53:11 -08:00
|
|
|
class Arc(TipableVMobject):
|
2022-02-15 14:37:15 +08:00
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
start_angle: float = 0,
|
|
|
|
angle: float = TAU / 4,
|
2022-12-15 18:19:09 -08:00
|
|
|
radius: float = 1.0,
|
2025-02-26 09:52:59 -06:00
|
|
|
n_components: Optional[int] = None,
|
2022-12-16 20:35:26 -08:00
|
|
|
arc_center: Vect3 = ORIGIN,
|
2022-02-15 14:37:15 +08:00
|
|
|
**kwargs
|
|
|
|
):
|
2022-12-15 18:19:09 -08:00
|
|
|
super().__init__(**kwargs)
|
2015-06-10 22:00:35 -07:00
|
|
|
|
2025-02-26 09:52:59 -06:00
|
|
|
if n_components is None:
|
|
|
|
# 16 components for a full circle
|
|
|
|
n_components = int(15 * (abs(angle) / TAU)) + 1
|
|
|
|
|
2023-09-04 19:08:13 -04:00
|
|
|
self.set_points(quadratic_bezier_points_for_arc(angle, n_components))
|
|
|
|
self.rotate(start_angle, about_point=ORIGIN)
|
2022-12-15 18:19:09 -08:00
|
|
|
self.scale(radius, about_point=ORIGIN)
|
|
|
|
self.shift(arc_center)
|
2019-02-07 09:26:18 -08:00
|
|
|
|
2022-12-16 20:35:26 -08:00
|
|
|
def get_arc_center(self) -> Vect3:
|
2019-02-07 09:26:18 -08:00
|
|
|
"""
|
|
|
|
Looks at the normals to the first two
|
|
|
|
anchors, and finds their intersection points
|
|
|
|
"""
|
|
|
|
# First two anchors and handles
|
2021-01-10 18:51:47 -08:00
|
|
|
a1, h, a2 = self.get_points()[:3]
|
2019-02-07 09:26:18 -08:00
|
|
|
# Tangent vectors
|
2020-02-05 14:46:25 -08:00
|
|
|
t1 = h - a1
|
|
|
|
t2 = h - a2
|
2019-02-07 09:26:18 -08:00
|
|
|
# Normals
|
|
|
|
n1 = rotate_vector(t1, TAU / 4)
|
|
|
|
n2 = rotate_vector(t2, TAU / 4)
|
2020-02-05 14:46:25 -08:00
|
|
|
return find_intersection(a1, n1, a2, n2)
|
|
|
|
|
2022-02-15 14:37:15 +08:00
|
|
|
def get_start_angle(self) -> float:
|
2020-02-05 14:46:25 -08:00
|
|
|
angle = angle_of_vector(self.get_start() - self.get_arc_center())
|
|
|
|
return angle % TAU
|
|
|
|
|
2022-02-15 14:37:15 +08:00
|
|
|
def get_stop_angle(self) -> float:
|
2020-02-05 14:46:25 -08:00
|
|
|
angle = angle_of_vector(self.get_end() - self.get_arc_center())
|
|
|
|
return angle % TAU
|
2018-01-22 16:01:33 -08:00
|
|
|
|
2023-01-31 14:12:41 -08:00
|
|
|
def move_arc_center_to(self, point: Vect3) -> Self:
|
2019-02-07 09:26:18 -08:00
|
|
|
self.shift(point - self.get_arc_center())
|
2018-01-22 16:01:33 -08:00
|
|
|
return self
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2018-02-23 18:36:54 +01:00
|
|
|
class ArcBetweenPoints(Arc):
|
2022-02-15 14:37:15 +08:00
|
|
|
def __init__(
|
|
|
|
self,
|
2022-12-16 20:35:26 -08:00
|
|
|
start: Vect3,
|
|
|
|
end: Vect3,
|
2022-02-15 14:37:15 +08:00
|
|
|
angle: float = TAU / 4,
|
|
|
|
**kwargs
|
|
|
|
):
|
2020-06-28 10:05:49 -07:00
|
|
|
super().__init__(angle=angle, **kwargs)
|
2019-02-07 10:59:04 -08:00
|
|
|
if angle == 0:
|
|
|
|
self.set_points_as_corners([LEFT, RIGHT])
|
2019-02-07 21:57:40 -08:00
|
|
|
self.put_start_and_end_on(start, end)
|
2018-02-23 18:36:54 +01:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2018-02-23 18:36:54 +01:00
|
|
|
class CurvedArrow(ArcBetweenPoints):
|
2022-02-15 14:37:15 +08:00
|
|
|
def __init__(
|
|
|
|
self,
|
2022-12-16 20:35:26 -08:00
|
|
|
start_point: Vect3,
|
|
|
|
end_point: Vect3,
|
2022-02-15 14:37:15 +08:00
|
|
|
**kwargs
|
|
|
|
):
|
2022-12-15 18:19:09 -08:00
|
|
|
super().__init__(start_point, end_point, **kwargs)
|
2019-02-07 09:26:18 -08:00
|
|
|
self.add_tip()
|
2018-02-23 18:36:54 +01:00
|
|
|
|
|
|
|
|
2019-02-07 09:26:18 -08:00
|
|
|
class CurvedDoubleArrow(CurvedArrow):
|
2022-02-15 14:37:15 +08:00
|
|
|
def __init__(
|
|
|
|
self,
|
2022-12-16 20:35:26 -08:00
|
|
|
start_point: Vect3,
|
|
|
|
end_point: Vect3,
|
2022-02-15 14:37:15 +08:00
|
|
|
**kwargs
|
|
|
|
):
|
2022-12-15 18:19:09 -08:00
|
|
|
super().__init__(start_point, end_point, **kwargs)
|
2019-02-07 09:26:18 -08:00
|
|
|
self.add_tip(at_start=True)
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2018-02-23 18:36:54 +01:00
|
|
|
|
2016-04-17 00:31:38 -07:00
|
|
|
class Circle(Arc):
|
2022-12-15 18:19:09 -08:00
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
start_angle: float = 0,
|
|
|
|
stroke_color: ManimColor = RED,
|
|
|
|
**kwargs
|
|
|
|
):
|
|
|
|
super().__init__(
|
|
|
|
start_angle, TAU,
|
|
|
|
stroke_color=stroke_color,
|
|
|
|
**kwargs
|
|
|
|
)
|
2015-09-28 16:25:18 -07:00
|
|
|
|
2022-02-15 14:37:15 +08:00
|
|
|
def surround(
|
|
|
|
self,
|
|
|
|
mobject: Mobject,
|
|
|
|
dim_to_match: int = 0,
|
|
|
|
stretch: bool = False,
|
|
|
|
buff: float = MED_SMALL_BUFF
|
2023-01-31 14:12:41 -08:00
|
|
|
) -> Self:
|
2018-01-10 17:57:22 -08:00
|
|
|
self.replace(mobject, dim_to_match, stretch)
|
2020-02-05 14:46:25 -08:00
|
|
|
self.stretch((self.get_width() + 2 * buff) / self.get_width(), 0)
|
|
|
|
self.stretch((self.get_height() + 2 * buff) / self.get_height(), 1)
|
2022-12-14 14:39:57 -08:00
|
|
|
return self
|
2018-01-10 17:57:22 -08:00
|
|
|
|
2022-12-16 20:35:26 -08:00
|
|
|
def point_at_angle(self, angle: float) -> Vect3:
|
2020-02-05 14:46:25 -08:00
|
|
|
start_angle = self.get_start_angle()
|
2019-01-17 14:10:52 -08:00
|
|
|
return self.point_from_proportion(
|
2022-11-03 16:48:30 -07:00
|
|
|
((angle - start_angle) % TAU) / TAU
|
2019-01-17 14:10:52 -08:00
|
|
|
)
|
|
|
|
|
2022-02-15 14:37:15 +08:00
|
|
|
def get_radius(self) -> float:
|
2021-12-07 10:07:25 -08:00
|
|
|
return get_norm(self.get_start() - self.get_center())
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2017-07-20 13:37:12 -07:00
|
|
|
class Dot(Circle):
|
2022-12-15 18:19:09 -08:00
|
|
|
def __init__(
|
|
|
|
self,
|
2022-12-16 20:35:26 -08:00
|
|
|
point: Vect3 = ORIGIN,
|
2022-12-15 18:19:09 -08:00
|
|
|
radius: float = DEFAULT_DOT_RADIUS,
|
2022-12-16 09:55:50 -08:00
|
|
|
stroke_color: ManimColor = BLACK,
|
2022-12-15 18:19:09 -08:00
|
|
|
stroke_width: float = 0.0,
|
|
|
|
fill_opacity: float = 1.0,
|
2022-12-16 09:55:50 -08:00
|
|
|
fill_color: ManimColor = WHITE,
|
2022-12-15 18:19:09 -08:00
|
|
|
**kwargs
|
|
|
|
):
|
|
|
|
super().__init__(
|
|
|
|
arc_center=point,
|
|
|
|
radius=radius,
|
2022-12-16 09:55:50 -08:00
|
|
|
stroke_color=stroke_color,
|
2022-12-15 18:19:09 -08:00
|
|
|
stroke_width=stroke_width,
|
|
|
|
fill_opacity=fill_opacity,
|
2022-12-16 09:55:50 -08:00
|
|
|
fill_color=fill_color,
|
2022-12-15 18:19:09 -08:00
|
|
|
**kwargs
|
|
|
|
)
|
2015-06-10 22:00:35 -07:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2019-03-03 07:42:00 -06:00
|
|
|
class SmallDot(Dot):
|
2022-12-15 18:19:09 -08:00
|
|
|
def __init__(
|
|
|
|
self,
|
2022-12-16 20:35:26 -08:00
|
|
|
point: Vect3 = ORIGIN,
|
2022-12-15 18:19:09 -08:00
|
|
|
radius: float = DEFAULT_SMALL_DOT_RADIUS,
|
|
|
|
**kwargs
|
|
|
|
):
|
|
|
|
super().__init__(point, radius=radius, **kwargs)
|
2019-03-03 07:42:00 -06:00
|
|
|
|
|
|
|
|
2019-02-07 10:12:42 -08:00
|
|
|
class Ellipse(Circle):
|
2022-12-15 18:19:09 -08:00
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
width: float = 2.0,
|
|
|
|
height: float = 1.0,
|
|
|
|
**kwargs
|
|
|
|
):
|
2021-01-11 10:57:23 -10:00
|
|
|
super().__init__(**kwargs)
|
2022-12-15 18:19:09 -08:00
|
|
|
self.set_width(width, stretch=True)
|
|
|
|
self.set_height(height, stretch=True)
|
|
|
|
|
|
|
|
|
|
|
|
class AnnularSector(VMobject):
|
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
angle: float = TAU / 4,
|
|
|
|
start_angle: float = 0.0,
|
|
|
|
inner_radius: float = 1.0,
|
|
|
|
outer_radius: float = 2.0,
|
2022-12-16 20:35:26 -08:00
|
|
|
arc_center: Vect3 = ORIGIN,
|
2022-12-16 09:55:50 -08:00
|
|
|
fill_color: ManimColor = GREY_A,
|
2022-12-15 18:19:09 -08:00
|
|
|
fill_opacity: float = 1.0,
|
|
|
|
stroke_width: float = 0.0,
|
|
|
|
**kwargs,
|
|
|
|
):
|
|
|
|
super().__init__(
|
2022-12-16 09:55:50 -08:00
|
|
|
fill_color=fill_color,
|
2022-12-15 18:19:09 -08:00
|
|
|
fill_opacity=fill_opacity,
|
|
|
|
stroke_width=stroke_width,
|
2022-12-19 14:43:57 -08:00
|
|
|
**kwargs,
|
2022-12-15 18:19:09 -08:00
|
|
|
)
|
|
|
|
|
|
|
|
# Initialize points
|
2019-02-07 10:12:42 -08:00
|
|
|
inner_arc, outer_arc = [
|
|
|
|
Arc(
|
2022-12-15 18:19:09 -08:00
|
|
|
start_angle=start_angle,
|
|
|
|
angle=angle,
|
2019-02-07 10:12:42 -08:00
|
|
|
radius=radius,
|
2022-12-15 18:19:09 -08:00
|
|
|
arc_center=arc_center,
|
2019-02-07 10:12:42 -08:00
|
|
|
)
|
2022-12-15 18:19:09 -08:00
|
|
|
for radius in (inner_radius, outer_radius)
|
2019-02-07 10:12:42 -08:00
|
|
|
]
|
2023-01-11 20:01:54 -08:00
|
|
|
self.set_points(inner_arc.get_points()[::-1]) # Reverse
|
2021-01-10 18:51:47 -08:00
|
|
|
self.add_line_to(outer_arc.get_points()[0])
|
2023-01-11 20:01:54 -08:00
|
|
|
self.add_subpath(outer_arc.get_points())
|
2022-12-19 14:43:57 -08:00
|
|
|
self.add_line_to(inner_arc.get_points()[-1])
|
2018-01-19 17:30:39 -08:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2018-01-18 15:06:38 -08:00
|
|
|
class Sector(AnnularSector):
|
2022-12-15 18:19:09 -08:00
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
angle: float = TAU / 4,
|
|
|
|
radius: float = 1.0,
|
|
|
|
**kwargs
|
|
|
|
):
|
|
|
|
super().__init__(
|
|
|
|
angle,
|
|
|
|
inner_radius=0,
|
|
|
|
outer_radius=radius,
|
|
|
|
**kwargs
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
class Annulus(VMobject):
|
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
inner_radius: float = 1.0,
|
|
|
|
outer_radius: float = 2.0,
|
|
|
|
fill_opacity: float = 1.0,
|
|
|
|
stroke_width: float = 0.0,
|
2022-12-16 09:55:50 -08:00
|
|
|
fill_color: ManimColor = GREY_A,
|
2022-12-16 20:35:26 -08:00
|
|
|
center: Vect3 = ORIGIN,
|
2022-12-15 18:19:09 -08:00
|
|
|
**kwargs,
|
|
|
|
):
|
|
|
|
super().__init__(
|
2022-12-16 09:55:50 -08:00
|
|
|
fill_color=fill_color,
|
2022-12-15 18:19:09 -08:00
|
|
|
fill_opacity=fill_opacity,
|
|
|
|
stroke_width=stroke_width,
|
2022-12-19 14:43:57 -08:00
|
|
|
**kwargs,
|
2022-12-15 18:19:09 -08:00
|
|
|
)
|
|
|
|
|
|
|
|
self.radius = outer_radius
|
2023-09-04 19:08:13 -04:00
|
|
|
outer_path = outer_radius * quadratic_bezier_points_for_arc(TAU)
|
|
|
|
inner_path = inner_radius * quadratic_bezier_points_for_arc(-TAU)
|
2023-01-13 13:22:26 -08:00
|
|
|
self.add_subpath(outer_path)
|
|
|
|
self.add_subpath(inner_path)
|
2022-12-15 18:19:09 -08:00
|
|
|
self.shift(center)
|
2018-01-18 15:06:38 -08:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2019-02-07 17:53:11 -08:00
|
|
|
class Line(TipableVMobject):
|
2022-02-15 14:37:15 +08:00
|
|
|
def __init__(
|
|
|
|
self,
|
2022-12-16 20:35:26 -08:00
|
|
|
start: Vect3 | Mobject = LEFT,
|
|
|
|
end: Vect3 | Mobject = RIGHT,
|
2022-12-15 18:19:09 -08:00
|
|
|
buff: float = 0.0,
|
|
|
|
path_arc: float = 0.0,
|
2022-02-15 14:37:15 +08:00
|
|
|
**kwargs
|
|
|
|
):
|
2020-06-29 11:04:23 -07:00
|
|
|
super().__init__(**kwargs)
|
2022-12-15 18:19:09 -08:00
|
|
|
self.path_arc = path_arc
|
2023-09-04 20:49:51 -04:00
|
|
|
self.buff = buff
|
2022-12-15 18:19:09 -08:00
|
|
|
self.set_start_and_end_attrs(start, end)
|
|
|
|
self.set_points_by_ends(self.start, self.end, buff, path_arc)
|
2020-06-28 10:05:49 -07:00
|
|
|
|
2022-02-15 14:37:15 +08:00
|
|
|
def set_points_by_ends(
|
|
|
|
self,
|
2022-12-16 20:35:26 -08:00
|
|
|
start: Vect3,
|
|
|
|
end: Vect3,
|
2022-02-15 14:37:15 +08:00
|
|
|
buff: float = 0,
|
|
|
|
path_arc: float = 0
|
2023-01-31 14:12:41 -08:00
|
|
|
) -> Self:
|
2023-09-04 20:49:51 -04:00
|
|
|
self.clear_points()
|
|
|
|
self.start_new_path(start)
|
|
|
|
self.add_arc_to(end, path_arc)
|
|
|
|
|
|
|
|
# Apply buffer
|
|
|
|
if buff > 0:
|
|
|
|
length = self.get_arc_length()
|
|
|
|
alpha = min(buff / length, 0.5)
|
|
|
|
self.pointwise_become_partial(self, alpha, 1 - alpha)
|
2021-08-21 10:35:29 -07:00
|
|
|
return self
|
2017-04-26 16:40:54 -07:00
|
|
|
|
2023-01-31 14:12:41 -08:00
|
|
|
def set_path_arc(self, new_value: float) -> Self:
|
2018-01-27 13:29:39 +01:00
|
|
|
self.path_arc = new_value
|
2020-02-11 19:55:00 -08:00
|
|
|
self.init_points()
|
2023-01-31 14:12:41 -08:00
|
|
|
return self
|
2018-01-27 13:29:39 +01:00
|
|
|
|
2022-12-16 20:35:26 -08:00
|
|
|
def set_start_and_end_attrs(self, start: Vect3 | Mobject, end: Vect3 | Mobject):
|
2019-02-07 10:59:04 -08:00
|
|
|
# If either start or end are Mobjects, this
|
|
|
|
# gives their centers
|
|
|
|
rough_start = self.pointify(start)
|
|
|
|
rough_end = self.pointify(end)
|
|
|
|
vect = normalize(rough_end - rough_start)
|
|
|
|
# Now that we know the direction between them,
|
2020-02-05 14:46:25 -08:00
|
|
|
# we can find the appropriate boundary point from
|
2019-02-07 10:59:04 -08:00
|
|
|
# start and end, if they're mobjects
|
2021-08-21 10:35:29 -07:00
|
|
|
self.start = self.pointify(start, vect)
|
|
|
|
self.end = self.pointify(end, -vect)
|
2019-02-07 10:59:04 -08:00
|
|
|
|
2022-02-15 14:37:15 +08:00
|
|
|
def pointify(
|
|
|
|
self,
|
2022-12-16 20:35:26 -08:00
|
|
|
mob_or_point: Mobject | Vect3,
|
|
|
|
direction: Vect3 | None = None
|
|
|
|
) -> Vect3:
|
2021-01-04 17:25:05 -08:00
|
|
|
"""
|
|
|
|
Take an argument passed into Line (or subclass) and turn
|
|
|
|
it into a 3d point.
|
|
|
|
"""
|
2016-04-17 00:31:38 -07:00
|
|
|
if isinstance(mob_or_point, Mobject):
|
2019-02-07 10:59:04 -08:00
|
|
|
mob = mob_or_point
|
|
|
|
if direction is None:
|
|
|
|
return mob.get_center()
|
|
|
|
else:
|
2021-01-14 14:15:58 -10:00
|
|
|
return mob.get_continuous_bounding_box_point(direction)
|
2020-12-08 09:27:20 -08:00
|
|
|
else:
|
|
|
|
point = mob_or_point
|
|
|
|
result = np.zeros(self.dim)
|
|
|
|
result[:len(point)] = point
|
|
|
|
return result
|
2016-04-17 00:31:38 -07:00
|
|
|
|
2023-01-31 14:12:41 -08:00
|
|
|
def put_start_and_end_on(self, start: Vect3, end: Vect3) -> Self:
|
2019-04-06 11:52:23 -07:00
|
|
|
curr_start, curr_end = self.get_start_and_end()
|
2021-08-21 10:35:29 -07:00
|
|
|
if np.isclose(curr_start, curr_end).all():
|
|
|
|
# Handle null lines more gracefully
|
|
|
|
self.set_points_by_ends(start, end, buff=0, path_arc=self.path_arc)
|
|
|
|
return self
|
2019-06-23 16:25:48 -07:00
|
|
|
return super().put_start_and_end_on(start, end)
|
2019-04-06 11:52:23 -07:00
|
|
|
|
2022-12-16 20:35:26 -08:00
|
|
|
def get_vector(self) -> Vect3:
|
2017-08-30 13:16:08 -07:00
|
|
|
return self.get_end() - self.get_start()
|
|
|
|
|
2022-12-16 20:35:26 -08:00
|
|
|
def get_unit_vector(self) -> Vect3:
|
2019-02-07 14:52:40 -08:00
|
|
|
return normalize(self.get_vector())
|
2016-04-27 17:35:04 -07:00
|
|
|
|
2022-02-15 14:37:15 +08:00
|
|
|
def get_angle(self) -> float:
|
2019-02-07 14:52:40 -08:00
|
|
|
return angle_of_vector(self.get_vector())
|
2016-03-21 19:30:09 -07:00
|
|
|
|
2022-12-16 20:35:26 -08:00
|
|
|
def get_projection(self, point: Vect3) -> Vect3:
|
2021-02-07 17:30:49 -08:00
|
|
|
"""
|
|
|
|
Return projection of a point onto the line
|
|
|
|
"""
|
|
|
|
unit_vect = self.get_unit_vector()
|
|
|
|
start = self.get_start()
|
|
|
|
return start + np.dot(point - start, unit_vect) * unit_vect
|
|
|
|
|
2022-02-15 14:37:15 +08:00
|
|
|
def get_slope(self) -> float:
|
2019-02-07 14:52:40 -08:00
|
|
|
return np.tan(self.get_angle())
|
2015-06-13 19:00:23 -07:00
|
|
|
|
2023-01-31 14:12:41 -08:00
|
|
|
def set_angle(self, angle: float, about_point: Optional[Vect3] = None) -> Self:
|
2020-08-16 09:45:34 -07:00
|
|
|
if about_point is None:
|
|
|
|
about_point = self.get_start()
|
2019-02-03 12:10:55 -08:00
|
|
|
self.rotate(
|
|
|
|
angle - self.get_angle(),
|
2020-08-16 09:45:34 -07:00
|
|
|
about_point=about_point,
|
2019-02-03 12:10:55 -08:00
|
|
|
)
|
2020-08-16 09:45:34 -07:00
|
|
|
return self
|
2017-09-01 18:57:38 -07:00
|
|
|
|
2022-02-15 14:37:15 +08:00
|
|
|
def set_length(self, length: float, **kwargs):
|
2021-08-26 11:43:19 -07:00
|
|
|
self.scale(length / self.get_length(), **kwargs)
|
2021-11-18 17:51:56 -08:00
|
|
|
return self
|
2019-06-19 16:07:22 -07:00
|
|
|
|
2022-12-15 18:19:09 -08:00
|
|
|
def get_arc_length(self) -> float:
|
|
|
|
arc_len = get_norm(self.get_vector())
|
|
|
|
if self.path_arc > 0:
|
|
|
|
arc_len *= self.path_arc / (2 * math.sin(self.path_arc / 2))
|
|
|
|
return arc_len
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2022-12-19 10:38:06 -08:00
|
|
|
|
2016-08-18 12:54:04 -07:00
|
|
|
class DashedLine(Line):
|
2022-12-15 18:19:09 -08:00
|
|
|
def __init__(
|
|
|
|
self,
|
2022-12-16 20:35:26 -08:00
|
|
|
start: Vect3 = LEFT,
|
|
|
|
end: Vect3 = RIGHT,
|
2022-12-15 18:19:09 -08:00
|
|
|
dash_length: float = DEFAULT_DASH_LENGTH,
|
|
|
|
positive_space_ratio: float = 0.5,
|
|
|
|
**kwargs
|
|
|
|
):
|
|
|
|
super().__init__(start, end, **kwargs)
|
|
|
|
|
|
|
|
num_dashes = self.calculate_num_dashes(dash_length, positive_space_ratio)
|
2019-02-07 15:41:54 -08:00
|
|
|
dashes = DashedVMobject(
|
|
|
|
self,
|
|
|
|
num_dashes=num_dashes,
|
2022-12-15 18:19:09 -08:00
|
|
|
positive_space_ratio=positive_space_ratio
|
2019-02-07 15:41:54 -08:00
|
|
|
)
|
|
|
|
self.clear_points()
|
|
|
|
self.add(*dashes)
|
2016-08-18 12:54:04 -07:00
|
|
|
|
2022-12-15 18:19:09 -08:00
|
|
|
def calculate_num_dashes(self, dash_length: float, positive_space_ratio: float) -> int:
|
2019-02-07 15:41:54 -08:00
|
|
|
try:
|
2022-12-15 18:19:09 -08:00
|
|
|
full_length = dash_length / positive_space_ratio
|
2020-02-05 14:46:25 -08:00
|
|
|
return int(np.ceil(self.get_length() / full_length))
|
2019-02-07 15:41:54 -08:00
|
|
|
except ZeroDivisionError:
|
|
|
|
return 1
|
|
|
|
|
2022-12-16 20:35:26 -08:00
|
|
|
def get_start(self) -> Vect3:
|
2019-02-07 15:41:54 -08:00
|
|
|
if len(self.submobjects) > 0:
|
|
|
|
return self.submobjects[0].get_start()
|
2017-02-27 15:56:22 -08:00
|
|
|
else:
|
2019-02-07 15:41:54 -08:00
|
|
|
return Line.get_start(self)
|
2016-08-18 12:54:04 -07:00
|
|
|
|
2022-12-16 20:35:26 -08:00
|
|
|
def get_end(self) -> Vect3:
|
2019-02-07 15:41:54 -08:00
|
|
|
if len(self.submobjects) > 0:
|
|
|
|
return self.submobjects[-1].get_end()
|
2017-02-27 15:56:22 -08:00
|
|
|
else:
|
2019-02-07 15:41:54 -08:00
|
|
|
return Line.get_end(self)
|
2016-08-18 12:54:04 -07:00
|
|
|
|
2025-02-26 09:52:59 -06:00
|
|
|
def get_start_and_end(self) -> Tuple[Vect3, Vect3]:
|
|
|
|
return self.get_start(), self.get_end()
|
|
|
|
|
2022-12-16 20:35:26 -08:00
|
|
|
def get_first_handle(self) -> Vect3:
|
2021-01-10 18:51:47 -08:00
|
|
|
return self.submobjects[0].get_points()[1]
|
2019-02-07 17:53:11 -08:00
|
|
|
|
2022-12-16 20:35:26 -08:00
|
|
|
def get_last_handle(self) -> Vect3:
|
2021-01-10 18:51:47 -08:00
|
|
|
return self.submobjects[-1].get_points()[-2]
|
2019-02-07 17:53:11 -08:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2019-06-15 12:21:29 -07:00
|
|
|
class TangentLine(Line):
|
2022-12-15 18:19:09 -08:00
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
vmob: VMobject,
|
|
|
|
alpha: float,
|
|
|
|
length: float = 2,
|
|
|
|
d_alpha: float = 1e-6,
|
|
|
|
**kwargs
|
|
|
|
):
|
|
|
|
a1 = clip(alpha - d_alpha, 0, 1)
|
|
|
|
a2 = clip(alpha + d_alpha, 0, 1)
|
2020-06-28 10:05:49 -07:00
|
|
|
super().__init__(vmob.pfp(a1), vmob.pfp(a2), **kwargs)
|
2022-12-15 18:19:09 -08:00
|
|
|
self.scale(length / self.get_length())
|
2019-06-15 12:21:29 -07:00
|
|
|
|
|
|
|
|
2018-11-29 17:29:31 -08:00
|
|
|
class Elbow(VMobject):
|
2022-12-15 18:19:09 -08:00
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
width: float = 0.2,
|
|
|
|
angle: float = 0,
|
|
|
|
**kwargs
|
|
|
|
):
|
2021-02-26 10:46:19 +01:00
|
|
|
super().__init__(**kwargs)
|
2022-12-15 18:19:09 -08:00
|
|
|
self.set_points_as_corners([UP, UR, RIGHT])
|
|
|
|
self.set_width(width, about_point=ORIGIN)
|
|
|
|
self.rotate(angle, about_point=ORIGIN)
|
2018-11-29 17:29:31 -08:00
|
|
|
|
|
|
|
|
2024-08-21 14:52:54 -05:00
|
|
|
class StrokeArrow(Line):
|
2022-12-15 18:19:09 -08:00
|
|
|
def __init__(
|
|
|
|
self,
|
2022-12-16 20:35:26 -08:00
|
|
|
start: Vect3 | Mobject,
|
|
|
|
end: Vect3 | Mobject,
|
2022-12-16 09:55:50 -08:00
|
|
|
stroke_color: ManimColor = GREY_A,
|
2022-12-15 18:19:09 -08:00
|
|
|
stroke_width: float = 5,
|
|
|
|
buff: float = 0.25,
|
|
|
|
tip_width_ratio: float = 5,
|
2023-08-15 20:37:13 -07:00
|
|
|
tip_len_to_width: float = 0.0075,
|
2022-12-15 18:19:09 -08:00
|
|
|
max_tip_length_to_length_ratio: float = 0.3,
|
|
|
|
max_width_to_length_ratio: float = 8.0,
|
|
|
|
**kwargs,
|
|
|
|
):
|
|
|
|
self.tip_width_ratio = tip_width_ratio
|
2023-08-15 20:37:13 -07:00
|
|
|
self.tip_len_to_width = tip_len_to_width
|
2022-12-15 18:19:09 -08:00
|
|
|
self.max_tip_length_to_length_ratio = max_tip_length_to_length_ratio
|
|
|
|
self.max_width_to_length_ratio = max_width_to_length_ratio
|
2023-09-04 20:49:51 -04:00
|
|
|
self.n_tip_points = 3
|
2023-11-06 12:33:26 -05:00
|
|
|
self.original_stroke_width = stroke_width
|
2022-12-15 18:19:09 -08:00
|
|
|
super().__init__(
|
|
|
|
start, end,
|
2022-12-16 09:55:50 -08:00
|
|
|
stroke_color=stroke_color,
|
2022-12-15 18:19:09 -08:00
|
|
|
stroke_width=stroke_width,
|
|
|
|
buff=buff,
|
|
|
|
**kwargs
|
|
|
|
)
|
2021-08-21 10:35:29 -07:00
|
|
|
|
2022-02-15 14:37:15 +08:00
|
|
|
def set_points_by_ends(
|
|
|
|
self,
|
2022-12-16 20:35:26 -08:00
|
|
|
start: Vect3,
|
|
|
|
end: Vect3,
|
2022-02-15 14:37:15 +08:00
|
|
|
buff: float = 0,
|
|
|
|
path_arc: float = 0
|
2023-01-31 14:12:41 -08:00
|
|
|
) -> Self:
|
2021-08-21 10:35:29 -07:00
|
|
|
super().set_points_by_ends(start, end, buff, path_arc)
|
|
|
|
self.insert_tip_anchor()
|
|
|
|
self.create_tip_with_stroke_width()
|
2022-12-15 18:19:09 -08:00
|
|
|
return self
|
2021-08-21 10:35:29 -07:00
|
|
|
|
2023-01-31 14:12:41 -08:00
|
|
|
def insert_tip_anchor(self) -> Self:
|
2021-08-21 10:35:29 -07:00
|
|
|
prev_end = self.get_end()
|
|
|
|
arc_len = self.get_arc_length()
|
2023-08-15 20:37:13 -07:00
|
|
|
tip_len = self.get_stroke_width() * self.tip_width_ratio * self.tip_len_to_width
|
2023-08-15 20:37:54 -07:00
|
|
|
if tip_len >= self.max_tip_length_to_length_ratio * arc_len or arc_len == 0:
|
2021-08-21 10:35:29 -07:00
|
|
|
alpha = self.max_tip_length_to_length_ratio
|
|
|
|
else:
|
|
|
|
alpha = tip_len / arc_len
|
2023-09-04 20:49:51 -04:00
|
|
|
|
|
|
|
if self.path_arc > 0 and self.buff > 0:
|
|
|
|
self.insert_n_curves(10) # Is this needed?
|
|
|
|
self.pointwise_become_partial(self, 0.0, 1.0 - alpha)
|
2024-08-21 14:44:33 -05:00
|
|
|
self.add_line_to(self.get_end())
|
2021-08-21 10:35:29 -07:00
|
|
|
self.add_line_to(prev_end)
|
2023-09-04 20:49:51 -04:00
|
|
|
self.n_tip_points = 3
|
2021-08-21 10:35:29 -07:00
|
|
|
return self
|
|
|
|
|
2023-01-26 23:42:03 -08:00
|
|
|
@Mobject.affects_data
|
2023-01-31 14:12:41 -08:00
|
|
|
def create_tip_with_stroke_width(self) -> Self:
|
2023-01-26 23:42:03 -08:00
|
|
|
if self.get_num_points() < 3:
|
2022-12-15 18:19:09 -08:00
|
|
|
return self
|
2023-11-06 12:33:26 -05:00
|
|
|
stroke_width = min(
|
|
|
|
self.original_stroke_width,
|
2021-08-21 10:35:29 -07:00
|
|
|
self.max_width_to_length_ratio * self.get_length(),
|
|
|
|
)
|
2023-11-06 12:33:26 -05:00
|
|
|
tip_width = self.tip_width_ratio * stroke_width
|
2023-09-04 20:49:51 -04:00
|
|
|
ntp = self.n_tip_points
|
|
|
|
self.data['stroke_width'][:-ntp] = self.data['stroke_width'][0]
|
|
|
|
self.data['stroke_width'][-ntp:, 0] = tip_width * np.linspace(1, 0, ntp)
|
2021-08-21 10:35:29 -07:00
|
|
|
return self
|
|
|
|
|
2023-01-31 14:12:41 -08:00
|
|
|
def reset_tip(self) -> Self:
|
2021-08-21 10:35:29 -07:00
|
|
|
self.set_points_by_ends(
|
2022-12-15 18:19:09 -08:00
|
|
|
self.get_start(), self.get_end(),
|
|
|
|
path_arc=self.path_arc
|
2021-08-21 10:35:29 -07:00
|
|
|
)
|
|
|
|
return self
|
|
|
|
|
2022-02-15 14:37:15 +08:00
|
|
|
def set_stroke(
|
|
|
|
self,
|
2022-04-12 19:19:59 +08:00
|
|
|
color: ManimColor | Iterable[ManimColor] | None = None,
|
|
|
|
width: float | Iterable[float] | None = None,
|
2022-02-15 14:37:15 +08:00
|
|
|
*args, **kwargs
|
2023-01-31 14:12:41 -08:00
|
|
|
) -> Self:
|
2021-08-21 10:35:29 -07:00
|
|
|
super().set_stroke(color=color, width=width, *args, **kwargs)
|
2023-11-06 12:33:26 -05:00
|
|
|
self.original_stroke_width = self.get_stroke_width()
|
2023-01-26 23:51:05 -08:00
|
|
|
if self.has_points():
|
|
|
|
self.reset_tip()
|
2021-08-21 10:35:29 -07:00
|
|
|
return self
|
|
|
|
|
2023-01-31 14:12:41 -08:00
|
|
|
def _handle_scale_side_effects(self, scale_factor: float) -> Self:
|
2023-01-26 23:51:05 -08:00
|
|
|
if scale_factor != 1.0:
|
|
|
|
self.reset_tip()
|
|
|
|
return self
|
2021-08-21 10:35:29 -07:00
|
|
|
|
|
|
|
|
2024-08-21 14:52:54 -05:00
|
|
|
class Arrow(Line):
|
|
|
|
tickness_multiplier = 0.015
|
|
|
|
|
2022-12-15 18:19:09 -08:00
|
|
|
def __init__(
|
|
|
|
self,
|
2022-12-16 20:35:26 -08:00
|
|
|
start: Vect3 | Mobject = LEFT,
|
|
|
|
end: Vect3 | Mobject = LEFT,
|
2024-08-21 15:58:57 -05:00
|
|
|
buff: float = MED_SMALL_BUFF,
|
|
|
|
path_arc: float = 0,
|
2022-12-15 18:19:09 -08:00
|
|
|
fill_color: ManimColor = GREY_A,
|
|
|
|
fill_opacity: float = 1.0,
|
|
|
|
stroke_width: float = 0.0,
|
2024-08-21 14:52:54 -05:00
|
|
|
thickness: float = 3.0,
|
2022-12-15 18:19:09 -08:00
|
|
|
tip_width_ratio: float = 5,
|
|
|
|
tip_angle: float = PI / 3,
|
|
|
|
max_tip_length_to_length_ratio: float = 0.5,
|
|
|
|
max_width_to_length_ratio: float = 0.1,
|
|
|
|
**kwargs,
|
|
|
|
):
|
|
|
|
self.thickness = thickness
|
|
|
|
self.tip_width_ratio = tip_width_ratio
|
|
|
|
self.tip_angle = tip_angle
|
|
|
|
self.max_tip_length_to_length_ratio = max_tip_length_to_length_ratio
|
|
|
|
self.max_width_to_length_ratio = max_width_to_length_ratio
|
|
|
|
super().__init__(
|
|
|
|
start, end,
|
|
|
|
fill_color=fill_color,
|
|
|
|
fill_opacity=fill_opacity,
|
|
|
|
stroke_width=stroke_width,
|
|
|
|
buff=buff,
|
2024-08-21 15:58:57 -05:00
|
|
|
path_arc=path_arc,
|
2022-12-15 18:19:09 -08:00
|
|
|
**kwargs
|
|
|
|
)
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2024-08-21 15:58:57 -05:00
|
|
|
def get_key_dimensions(self, length):
|
2024-08-21 14:52:54 -05:00
|
|
|
width = self.thickness * self.tickness_multiplier
|
|
|
|
w_ratio = fdiv(self.max_width_to_length_ratio, fdiv(width, length))
|
2020-06-28 10:05:49 -07:00
|
|
|
if w_ratio < 1:
|
2024-08-21 14:52:54 -05:00
|
|
|
width *= w_ratio
|
2020-06-28 10:05:49 -07:00
|
|
|
|
2024-08-21 14:52:54 -05:00
|
|
|
tip_width = self.tip_width_ratio * width
|
2020-06-28 10:05:49 -07:00
|
|
|
tip_length = tip_width / (2 * np.tan(self.tip_angle / 2))
|
2020-06-29 11:04:23 -07:00
|
|
|
t_ratio = fdiv(self.max_tip_length_to_length_ratio, fdiv(tip_length, length))
|
2020-06-28 10:05:49 -07:00
|
|
|
if t_ratio < 1:
|
|
|
|
tip_length *= t_ratio
|
|
|
|
tip_width *= t_ratio
|
|
|
|
|
2024-08-21 15:58:57 -05:00
|
|
|
return width, tip_width, tip_length
|
|
|
|
|
|
|
|
def set_points_by_ends(
|
|
|
|
self,
|
|
|
|
start: Vect3,
|
|
|
|
end: Vect3,
|
|
|
|
buff: float = 0,
|
|
|
|
path_arc: float = 0
|
|
|
|
) -> Self:
|
|
|
|
vect = end - start
|
|
|
|
length = max(get_norm(vect), 1e-8) # More systematic min?
|
|
|
|
unit_vect = normalize(vect)
|
|
|
|
|
|
|
|
# Find the right tip length and thickness
|
|
|
|
width, tip_width, tip_length = self.get_key_dimensions(length - buff)
|
|
|
|
|
|
|
|
# Adjust start and end based on buff
|
|
|
|
if path_arc == 0:
|
|
|
|
start = start + buff * unit_vect
|
|
|
|
end = end - buff * unit_vect
|
|
|
|
else:
|
|
|
|
R = length / 2 / math.sin(path_arc / 2)
|
|
|
|
midpoint = 0.5 * (start + end)
|
|
|
|
center = midpoint + rotate_vector(0.5 * vect, PI / 2) / math.tan(path_arc / 2)
|
|
|
|
sign = 1
|
|
|
|
start = center + rotate_vector(start - center, buff / R)
|
|
|
|
end = center + rotate_vector(end - center, -buff / R)
|
|
|
|
path_arc -= (2 * buff + tip_length) / R
|
|
|
|
vect = end - start
|
|
|
|
length = get_norm(vect)
|
|
|
|
|
|
|
|
# Find points for the stem, imagining an arrow pointed to the left
|
2020-06-28 10:05:49 -07:00
|
|
|
if path_arc == 0:
|
|
|
|
points1 = (length - tip_length) * np.array([RIGHT, 0.5 * RIGHT, ORIGIN])
|
2024-08-21 14:52:54 -05:00
|
|
|
points1 += width * UP / 2
|
|
|
|
points2 = points1[::-1] + width * DOWN
|
2020-06-28 10:05:49 -07:00
|
|
|
else:
|
|
|
|
# Find arc points
|
2023-09-04 19:08:13 -04:00
|
|
|
points1 = quadratic_bezier_points_for_arc(path_arc)
|
2020-06-28 10:05:49 -07:00
|
|
|
points2 = np.array(points1[::-1])
|
2024-08-21 14:52:54 -05:00
|
|
|
points1 *= (R + width / 2)
|
|
|
|
points2 *= (R - width / 2)
|
2020-06-28 10:05:49 -07:00
|
|
|
rot_T = rotation_matrix_transpose(PI / 2 - path_arc, OUT)
|
|
|
|
for points in points1, points2:
|
|
|
|
points[:] = np.dot(points, rot_T)
|
|
|
|
points += R * DOWN
|
|
|
|
|
|
|
|
self.set_points(points1)
|
|
|
|
# Tip
|
|
|
|
self.add_line_to(tip_width * UP / 2)
|
|
|
|
self.add_line_to(tip_length * LEFT)
|
2021-01-10 18:51:47 -08:00
|
|
|
self.tip_index = len(self.get_points()) - 1
|
2020-06-28 10:05:49 -07:00
|
|
|
self.add_line_to(tip_width * DOWN / 2)
|
|
|
|
self.add_line_to(points2[0])
|
|
|
|
# Close it out
|
2023-01-11 20:09:28 -08:00
|
|
|
self.add_subpath(points2)
|
2020-06-28 10:05:49 -07:00
|
|
|
self.add_line_to(points1[0])
|
|
|
|
|
2024-08-21 15:58:57 -05:00
|
|
|
# Reposition to match proper start and end
|
2020-06-28 10:05:49 -07:00
|
|
|
self.rotate(angle_of_vector(vect) - self.get_angle())
|
2021-02-06 21:28:53 +05:30
|
|
|
self.rotate(
|
|
|
|
PI / 2 - np.arccos(normalize(vect)[2]),
|
|
|
|
axis=rotate_vector(self.get_unit_vector(), -PI / 2),
|
|
|
|
)
|
2020-06-28 10:05:49 -07:00
|
|
|
self.shift(start - self.get_start())
|
2023-01-31 14:12:41 -08:00
|
|
|
return self
|
2020-06-28 10:05:49 -07:00
|
|
|
|
2023-01-31 14:12:41 -08:00
|
|
|
def reset_points_around_ends(self) -> Self:
|
2021-01-13 09:28:52 -10:00
|
|
|
self.set_points_by_ends(
|
2023-01-11 20:09:28 -08:00
|
|
|
self.get_start().copy(),
|
|
|
|
self.get_end().copy(),
|
|
|
|
path_arc=self.path_arc
|
2021-01-13 09:28:52 -10:00
|
|
|
)
|
2020-06-28 10:05:49 -07:00
|
|
|
return self
|
2015-08-12 14:24:36 -07:00
|
|
|
|
2022-12-16 20:35:26 -08:00
|
|
|
def get_start(self) -> Vect3:
|
2021-01-10 18:51:47 -08:00
|
|
|
points = self.get_points()
|
2023-01-11 19:52:33 -08:00
|
|
|
return 0.5 * (points[0] + points[-3])
|
2019-03-24 11:33:04 -07:00
|
|
|
|
2022-12-16 20:35:26 -08:00
|
|
|
def get_end(self) -> Vect3:
|
2021-01-10 18:51:47 -08:00
|
|
|
return self.get_points()[self.tip_index]
|
2015-08-12 14:24:36 -07:00
|
|
|
|
2024-08-21 15:58:57 -05:00
|
|
|
def get_start_and_end(self):
|
|
|
|
return (self.get_start(), self.get_end())
|
|
|
|
|
2023-01-31 14:12:41 -08:00
|
|
|
def put_start_and_end_on(self, start: Vect3, end: Vect3) -> Self:
|
2020-06-28 10:05:49 -07:00
|
|
|
self.set_points_by_ends(start, end, buff=0, path_arc=self.path_arc)
|
|
|
|
return self
|
2017-08-27 23:52:28 -07:00
|
|
|
|
2023-01-31 14:12:41 -08:00
|
|
|
def scale(self, *args, **kwargs) -> Self:
|
2020-06-28 10:05:49 -07:00
|
|
|
super().scale(*args, **kwargs)
|
|
|
|
self.reset_points_around_ends()
|
2017-08-27 23:52:28 -07:00
|
|
|
return self
|
|
|
|
|
2023-01-31 14:12:41 -08:00
|
|
|
def set_thickness(self, thickness: float) -> Self:
|
2020-12-04 08:20:59 -08:00
|
|
|
self.thickness = thickness
|
2020-06-28 10:05:49 -07:00
|
|
|
self.reset_points_around_ends()
|
|
|
|
return self
|
2016-04-27 17:35:04 -07:00
|
|
|
|
2023-01-31 14:12:41 -08:00
|
|
|
def set_path_arc(self, path_arc: float) -> Self:
|
2020-06-28 10:05:49 -07:00
|
|
|
self.path_arc = path_arc
|
|
|
|
self.reset_points_around_ends()
|
2019-03-03 07:42:00 -06:00
|
|
|
return self
|
2019-02-24 11:57:02 -06:00
|
|
|
|
2024-08-28 10:50:51 -05:00
|
|
|
def set_perpendicular_to_camera(self, camera_frame):
|
|
|
|
to_cam = camera_frame.get_implied_camera_location() - self.get_center()
|
|
|
|
normal = self.get_unit_normal()
|
|
|
|
axis = normalize(self.get_vector())
|
|
|
|
# Project to be perpendicular to axis
|
|
|
|
trg_normal = to_cam - np.dot(to_cam, axis) * axis
|
|
|
|
mat = rotation_between_vectors(normal, trg_normal)
|
2024-08-28 11:56:25 -05:00
|
|
|
self.apply_matrix(mat, about_point=self.get_start())
|
2024-08-28 10:50:51 -05:00
|
|
|
return self
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2016-03-17 23:54:28 -07:00
|
|
|
class Vector(Arrow):
|
2022-12-15 18:19:09 -08:00
|
|
|
def __init__(
|
|
|
|
self,
|
2022-12-16 20:35:26 -08:00
|
|
|
direction: Vect3 = RIGHT,
|
2022-12-15 18:19:09 -08:00
|
|
|
buff: float = 0.0,
|
|
|
|
**kwargs
|
|
|
|
):
|
2016-07-15 18:16:06 -07:00
|
|
|
if len(direction) == 2:
|
2020-02-13 12:03:54 -08:00
|
|
|
direction = np.hstack([direction, 0])
|
2022-12-15 18:19:09 -08:00
|
|
|
super().__init__(ORIGIN, direction, buff=buff, **kwargs)
|
2016-07-12 10:34:35 -07:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2016-04-17 00:31:38 -07:00
|
|
|
class CubicBezier(VMobject):
|
2022-02-15 14:37:15 +08:00
|
|
|
def __init__(
|
|
|
|
self,
|
2022-12-16 20:35:26 -08:00
|
|
|
a0: Vect3,
|
|
|
|
h0: Vect3,
|
|
|
|
h1: Vect3,
|
|
|
|
a1: Vect3,
|
2022-02-15 14:37:15 +08:00
|
|
|
**kwargs
|
|
|
|
):
|
2022-12-15 18:19:09 -08:00
|
|
|
super().__init__(**kwargs)
|
2020-02-05 14:46:25 -08:00
|
|
|
self.add_cubic_bezier_curve(a0, h0, h1, a1)
|
2015-10-20 21:55:46 -07:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2016-04-17 00:31:38 -07:00
|
|
|
class Polygon(VMobject):
|
2023-01-11 20:27:17 -08:00
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
*vertices: Vect3,
|
|
|
|
**kwargs
|
|
|
|
):
|
2023-01-17 11:37:06 -08:00
|
|
|
super().__init__(**kwargs)
|
2022-12-15 18:19:09 -08:00
|
|
|
self.set_points_as_corners([*vertices, vertices[0]])
|
2020-06-13 15:21:19 -07:00
|
|
|
|
2022-12-17 13:16:48 -08:00
|
|
|
def get_vertices(self) -> Vect3Array:
|
2019-02-05 15:24:51 -08:00
|
|
|
return self.get_start_anchors()
|
2015-12-19 13:06:09 -08:00
|
|
|
|
2023-01-31 14:12:41 -08:00
|
|
|
def round_corners(self, radius: Optional[float] = None) -> Self:
|
2022-04-25 10:26:29 -07:00
|
|
|
if radius is None:
|
|
|
|
verts = self.get_vertices()
|
|
|
|
min_edge_length = min(
|
|
|
|
get_norm(v1 - v2)
|
|
|
|
for v1, v2 in zip(verts, verts[1:])
|
|
|
|
if not np.isclose(v1, v2).all()
|
|
|
|
)
|
|
|
|
radius = 0.25 * min_edge_length
|
2019-02-07 21:57:40 -08:00
|
|
|
vertices = self.get_vertices()
|
|
|
|
arcs = []
|
|
|
|
for v1, v2, v3 in adjacent_n_tuples(vertices, 3):
|
2023-01-11 20:47:04 -08:00
|
|
|
vect1 = normalize(v2 - v1)
|
|
|
|
vect2 = normalize(v3 - v2)
|
2019-02-07 21:57:40 -08:00
|
|
|
angle = angle_between_vectors(vect1, vect2)
|
|
|
|
# Distance between vertex and start of the arc
|
|
|
|
cut_off_length = radius * np.tan(angle / 2)
|
2023-01-11 20:47:04 -08:00
|
|
|
# Negative radius gives concave curves
|
|
|
|
sign = float(np.sign(radius * cross2d(vect1, vect2)))
|
2019-02-15 15:16:02 -08:00
|
|
|
arc = ArcBetweenPoints(
|
2023-01-11 20:47:04 -08:00
|
|
|
v2 - vect1 * cut_off_length,
|
|
|
|
v2 + vect2 * cut_off_length,
|
2020-02-05 14:46:25 -08:00
|
|
|
angle=sign * angle,
|
|
|
|
n_components=2,
|
2019-02-15 15:16:02 -08:00
|
|
|
)
|
|
|
|
arcs.append(arc)
|
2019-02-07 22:25:09 -08:00
|
|
|
|
|
|
|
self.clear_points()
|
2019-02-07 21:57:40 -08:00
|
|
|
# To ensure that we loop through starting with last
|
|
|
|
arcs = [arcs[-1], *arcs[:-1]]
|
|
|
|
for arc1, arc2 in adjacent_pairs(arcs):
|
2023-01-11 20:47:04 -08:00
|
|
|
self.add_subpath(arc1.get_points())
|
|
|
|
self.add_line_to(arc2.get_start())
|
2019-02-07 21:57:40 -08:00
|
|
|
return self
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2022-12-15 18:19:09 -08:00
|
|
|
class Polyline(VMobject):
|
2023-01-11 20:31:38 -08:00
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
*vertices: Vect3,
|
|
|
|
**kwargs
|
|
|
|
):
|
2023-01-17 11:37:06 -08:00
|
|
|
super().__init__(**kwargs)
|
2022-12-15 18:19:09 -08:00
|
|
|
self.set_points_as_corners(vertices)
|
2022-02-11 23:53:21 +08:00
|
|
|
|
|
|
|
|
2017-01-16 11:43:59 -08:00
|
|
|
class RegularPolygon(Polygon):
|
2022-12-15 18:19:09 -08:00
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
n: int = 6,
|
|
|
|
radius: float = 1.0,
|
|
|
|
start_angle: float | None = None,
|
|
|
|
**kwargs
|
|
|
|
):
|
|
|
|
# Defaults to 0 for odd, 90 for even
|
2023-01-19 09:50:41 -08:00
|
|
|
if start_angle is None:
|
2024-12-12 10:39:54 -06:00
|
|
|
start_angle = (n % 2) * 90 * DEG
|
2022-12-15 18:19:09 -08:00
|
|
|
start_vect = rotate_vector(radius * RIGHT, start_angle)
|
2017-01-16 11:43:59 -08:00
|
|
|
vertices = compass_directions(n, start_vect)
|
2020-06-13 15:21:19 -07:00
|
|
|
super().__init__(*vertices, **kwargs)
|
2016-04-27 17:35:04 -07:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2019-02-07 09:26:18 -08:00
|
|
|
class Triangle(RegularPolygon):
|
|
|
|
def __init__(self, **kwargs):
|
2020-06-13 15:21:19 -07:00
|
|
|
super().__init__(n=3, **kwargs)
|
2019-02-07 09:26:18 -08:00
|
|
|
|
|
|
|
|
2019-03-03 07:42:00 -06:00
|
|
|
class ArrowTip(Triangle):
|
2022-12-15 18:19:09 -08:00
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
angle: float = 0,
|
|
|
|
width: float = DEFAULT_ARROW_TIP_WIDTH,
|
|
|
|
length: float = DEFAULT_ARROW_TIP_LENGTH,
|
|
|
|
fill_opacity: float = 1.0,
|
|
|
|
fill_color: ManimColor = WHITE,
|
|
|
|
stroke_width: float = 0.0,
|
|
|
|
tip_style: int = 0, # triangle=0, inner_smooth=1, dot=2
|
|
|
|
**kwargs
|
|
|
|
):
|
|
|
|
super().__init__(
|
|
|
|
start_angle=0,
|
|
|
|
fill_opacity=fill_opacity,
|
|
|
|
fill_color=fill_color,
|
|
|
|
stroke_width=stroke_width,
|
|
|
|
**kwargs
|
|
|
|
)
|
|
|
|
self.set_height(width)
|
|
|
|
self.set_width(length, stretch=True)
|
|
|
|
if tip_style == 1:
|
|
|
|
self.set_height(length * 0.9, stretch=True)
|
2023-01-15 18:23:41 -08:00
|
|
|
self.data["point"][4] += np.array([0.6 * length, 0, 0])
|
2022-12-15 18:19:09 -08:00
|
|
|
elif tip_style == 2:
|
|
|
|
h = length / 2
|
2022-12-17 19:48:47 -08:00
|
|
|
self.set_points(Dot().set_width(h).get_points())
|
2022-12-15 18:19:09 -08:00
|
|
|
self.rotate(angle)
|
2019-03-03 07:42:00 -06:00
|
|
|
|
2022-12-16 20:35:26 -08:00
|
|
|
def get_base(self) -> Vect3:
|
2019-03-03 07:42:00 -06:00
|
|
|
return self.point_from_proportion(0.5)
|
|
|
|
|
2022-12-16 20:35:26 -08:00
|
|
|
def get_tip_point(self) -> Vect3:
|
2021-01-10 18:51:47 -08:00
|
|
|
return self.get_points()[0]
|
2019-03-03 07:42:00 -06:00
|
|
|
|
2022-12-16 20:35:26 -08:00
|
|
|
def get_vector(self) -> Vect3:
|
2019-03-03 07:42:00 -06:00
|
|
|
return self.get_tip_point() - self.get_base()
|
|
|
|
|
2022-02-15 14:37:15 +08:00
|
|
|
def get_angle(self) -> float:
|
2019-03-03 07:42:00 -06:00
|
|
|
return angle_of_vector(self.get_vector())
|
|
|
|
|
2022-02-15 14:37:15 +08:00
|
|
|
def get_length(self) -> float:
|
2019-03-03 07:42:00 -06:00
|
|
|
return get_norm(self.get_vector())
|
|
|
|
|
|
|
|
|
2019-02-05 15:24:51 -08:00
|
|
|
class Rectangle(Polygon):
|
2022-02-15 14:37:15 +08:00
|
|
|
def __init__(
|
|
|
|
self,
|
2022-12-15 18:19:09 -08:00
|
|
|
width: float = 4.0,
|
|
|
|
height: float = 2.0,
|
2022-02-15 14:37:15 +08:00
|
|
|
**kwargs
|
|
|
|
):
|
2022-12-16 09:55:50 -08:00
|
|
|
super().__init__(UR, UL, DL, DR, **kwargs)
|
2020-06-14 17:42:25 -07:00
|
|
|
self.set_width(width, stretch=True)
|
|
|
|
self.set_height(height, stretch=True)
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2023-09-04 18:46:11 -04:00
|
|
|
def surround(self, mobject, buff=SMALL_BUFF) -> Self:
|
|
|
|
target_shape = np.array(mobject.get_shape()) + 2 * buff
|
|
|
|
self.set_shape(*target_shape)
|
|
|
|
self.move_to(mobject)
|
|
|
|
return self
|
|
|
|
|
2015-08-07 18:10:00 -07:00
|
|
|
|
|
|
|
class Square(Rectangle):
|
2022-02-15 14:37:15 +08:00
|
|
|
def __init__(self, side_length: float = 2.0, **kwargs):
|
2020-06-14 17:42:25 -07:00
|
|
|
super().__init__(side_length, side_length, **kwargs)
|
2015-10-28 16:03:33 -07:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2018-04-12 23:19:09 +02:00
|
|
|
class RoundedRectangle(Rectangle):
|
2022-12-15 18:19:09 -08:00
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
width: float = 4.0,
|
|
|
|
height: float = 2.0,
|
|
|
|
corner_radius: float = 0.5,
|
|
|
|
**kwargs
|
|
|
|
):
|
|
|
|
super().__init__(width, height, **kwargs)
|
|
|
|
self.round_corners(corner_radius)
|