Kill CONFIG in geometry.py

This commit is contained in:
Grant Sanderson
2022-12-15 18:19:09 -08:00
parent c244f8738f
commit 2a645b27f8

View File

@ -31,7 +31,7 @@ from typing import TYPE_CHECKING
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Iterable from typing import Iterable
from manimlib.constants import ManimColor from manimlib.constants import ManimColor, np_vector
DEFAULT_DOT_RADIUS = 0.08 DEFAULT_DOT_RADIUS = 0.08
@ -60,14 +60,11 @@ class TipableVMobject(VMobject):
- Straightforward accessors, returning information pertaining - Straightforward accessors, returning information pertaining
to the TipableVMobject instance's tip(s), its length etc to the TipableVMobject instance's tip(s), its length etc
""" """
CONFIG = { tip_config: dict = dict(
"tip_config": { fill_opacity=1.0,
"fill_opacity": 1, stroke_width=0.0,
"stroke_width": 0, tip_style=0.0, # triangle=0, inner_smooth=1, dot=2
"tip_style": 0, # triangle=0, inner_smooth=1, dot=2 )
},
"normal_vector": OUT,
}
# Adding, Creating, Modifying tips # Adding, Creating, Modifying tips
def add_tip(self, at_start: bool = False, **kwargs): def add_tip(self, at_start: bool = False, **kwargs):
@ -181,19 +178,19 @@ class TipableVMobject(VMobject):
def get_default_tip_length(self) -> float: def get_default_tip_length(self) -> float:
return self.tip_length return self.tip_length
def get_first_handle(self) -> np.ndarray: def get_first_handle(self) -> np_vector:
return self.get_points()[1] return self.get_points()[1]
def get_last_handle(self) -> np.ndarray: def get_last_handle(self) -> np_vector:
return self.get_points()[-2] return self.get_points()[-2]
def get_end(self) -> np.ndarray: def get_end(self) -> np_vector:
if self.has_tip(): if self.has_tip():
return self.tip.get_start() return self.tip.get_start()
else: else:
return VMobject.get_end(self) return VMobject.get_end(self)
def get_start(self) -> np.ndarray: def get_start(self) -> np_vector:
if self.has_start_tip(): if self.has_start_tip():
return self.start_tip.get_start() return self.start_tip.get_start()
else: else:
@ -205,38 +202,31 @@ class TipableVMobject(VMobject):
class Arc(TipableVMobject): class Arc(TipableVMobject):
CONFIG = {
"radius": 1.0,
"n_components": 8,
"anchors_span_full_range": True,
"arc_center": ORIGIN,
}
def __init__( def __init__(
self, self,
start_angle: float = 0, start_angle: float = 0,
angle: float = TAU / 4, angle: float = TAU / 4,
radius: float = 1.0,
n_components: int = 8,
arc_center: np_vector = ORIGIN,
**kwargs **kwargs
): ):
self.start_angle = start_angle super().__init__(**kwargs)
self.angle = angle
VMobject.__init__(self, **kwargs)
def init_points(self) -> None:
self.set_points(Arc.create_quadratic_bezier_points( self.set_points(Arc.create_quadratic_bezier_points(
angle=self.angle, angle=angle,
start_angle=self.start_angle, start_angle=start_angle,
n_components=self.n_components n_components=n_components
)) ))
self.scale(self.radius, about_point=ORIGIN) self.scale(radius, about_point=ORIGIN)
self.shift(self.arc_center) self.shift(arc_center)
@staticmethod @staticmethod
def create_quadratic_bezier_points( def create_quadratic_bezier_points(
angle: float, angle: float,
start_angle: float = 0, start_angle: float = 0,
n_components: int = 8 n_components: int = 8
) -> np.ndarray: ) -> np_vector:
samples = np.array([ samples = np.array([
[np.cos(a), np.sin(a), 0] [np.cos(a), np.sin(a), 0]
for a in np.linspace( for a in np.linspace(
@ -254,7 +244,7 @@ class Arc(TipableVMobject):
points[2::3] = samples[2::2] points[2::3] = samples[2::2]
return points return points
def get_arc_center(self) -> np.ndarray: def get_arc_center(self) -> np_vector:
""" """
Looks at the normals to the first two Looks at the normals to the first two
anchors, and finds their intersection points anchors, and finds their intersection points
@ -277,7 +267,7 @@ class Arc(TipableVMobject):
angle = angle_of_vector(self.get_end() - self.get_arc_center()) angle = angle_of_vector(self.get_end() - self.get_arc_center())
return angle % TAU return angle % TAU
def move_arc_center_to(self, point: np.ndarray): def move_arc_center_to(self, point: np_vector):
self.shift(point - self.get_arc_center()) self.shift(point - self.get_arc_center())
return self return self
@ -285,8 +275,8 @@ class Arc(TipableVMobject):
class ArcBetweenPoints(Arc): class ArcBetweenPoints(Arc):
def __init__( def __init__(
self, self,
start: np.ndarray, start: np_vector,
end: np.ndarray, end: np_vector,
angle: float = TAU / 4, angle: float = TAU / 4,
**kwargs **kwargs
): ):
@ -299,34 +289,39 @@ class ArcBetweenPoints(Arc):
class CurvedArrow(ArcBetweenPoints): class CurvedArrow(ArcBetweenPoints):
def __init__( def __init__(
self, self,
start_point: np.ndarray, start_point: np_vector,
end_point: np.ndarray, end_point: np_vector,
**kwargs **kwargs
): ):
ArcBetweenPoints.__init__(self, start_point, end_point, **kwargs) super().__init__(start_point, end_point, **kwargs)
self.add_tip() self.add_tip()
class CurvedDoubleArrow(CurvedArrow): class CurvedDoubleArrow(CurvedArrow):
def __init__( def __init__(
self, self,
start_point: np.ndarray, start_point: np_vector,
end_point: np.ndarray, end_point: np_vector,
**kwargs **kwargs
): ):
CurvedArrow.__init__(self, start_point, end_point, **kwargs) super().__init__(start_point, end_point, **kwargs)
self.add_tip(at_start=True) self.add_tip(at_start=True)
class Circle(Arc): class Circle(Arc):
CONFIG = { def __init__(
"color": RED, self,
"close_new_points": True, start_angle: float = 0,
"anchors_span_full_range": False stroke_color: ManimColor = RED,
} close_new_points: bool = True,
**kwargs
def __init__(self, start_angle: float = 0, **kwargs): ):
Arc.__init__(self, start_angle, TAU, **kwargs) super().__init__(
start_angle, TAU,
stroke_color=stroke_color,
close_new_points=close_new_points,
**kwargs
)
def surround( def surround(
self, self,
@ -335,15 +330,12 @@ class Circle(Arc):
stretch: bool = False, stretch: bool = False,
buff: float = MED_SMALL_BUFF buff: float = MED_SMALL_BUFF
): ):
# Ignores dim_to_match and stretch; result will always be a circle
# TODO: Perhaps create an ellipse class to handle singele-dimension stretching
self.replace(mobject, dim_to_match, stretch) self.replace(mobject, dim_to_match, stretch)
self.stretch((self.get_width() + 2 * buff) / self.get_width(), 0) self.stretch((self.get_width() + 2 * buff) / self.get_width(), 0)
self.stretch((self.get_height() + 2 * buff) / self.get_height(), 1) self.stretch((self.get_height() + 2 * buff) / self.get_height(), 1)
return self return self
def point_at_angle(self, angle: float) -> np.ndarray: def point_at_angle(self, angle: float) -> np_vector:
start_angle = self.get_start_angle() start_angle = self.get_start_angle()
return self.point_from_proportion( return self.point_from_proportion(
((angle - start_angle) % TAU) / TAU ((angle - start_angle) % TAU) / TAU
@ -354,55 +346,75 @@ class Circle(Arc):
class Dot(Circle): class Dot(Circle):
CONFIG = { def __init__(
"radius": DEFAULT_DOT_RADIUS, self,
"stroke_width": 0, point: np_vector = ORIGIN,
"fill_opacity": 1.0, radius: float = DEFAULT_DOT_RADIUS,
"color": WHITE stroke_width: float = 0.0,
} fill_opacity: float = 1.0,
color: ManimColor = WHITE,
def __init__(self, point: np.ndarray = ORIGIN, **kwargs): **kwargs
super().__init__(arc_center=point, **kwargs) ):
super().__init__(
arc_center=point,
radius=radius,
stroke_width=stroke_width,
fill_opacity=fill_opacity,
color=color,
**kwargs
)
class SmallDot(Dot): class SmallDot(Dot):
CONFIG = { def __init__(
"radius": DEFAULT_SMALL_DOT_RADIUS, self,
} point: np_vector = ORIGIN,
radius: float = DEFAULT_SMALL_DOT_RADIUS,
**kwargs
):
super().__init__(point, radius=radius, **kwargs)
class Ellipse(Circle): class Ellipse(Circle):
CONFIG = { def __init__(
"width": 2, self,
"height": 1 width: float = 2.0,
} height: float = 1.0,
**kwargs
def __init__(self, **kwargs): ):
super().__init__(**kwargs) super().__init__(**kwargs)
self.set_width(self.width, stretch=True) self.set_width(width, stretch=True)
self.set_height(self.height, stretch=True) self.set_height(height, stretch=True)
class AnnularSector(Arc): class AnnularSector(VMobject):
CONFIG = { def __init__(
"inner_radius": 1, self,
"outer_radius": 2, angle: float = TAU / 4,
"angle": TAU / 4, start_angle: float = 0.0,
"start_angle": 0, inner_radius: float = 1.0,
"fill_opacity": 1, outer_radius: float = 2.0,
"stroke_width": 0, arc_center: np_vector = ORIGIN,
"color": WHITE, color: ManimColor = GREY_A,
} fill_opacity: float = 1.0,
stroke_width: float = 0.0,
**kwargs,
):
super().__init__(
color=color,
fill_opacity=fill_opacity,
stroke_width=stroke_width,
)
def init_points(self): # Initialize points
inner_arc, outer_arc = [ inner_arc, outer_arc = [
Arc( Arc(
start_angle=self.start_angle, start_angle=start_angle,
angle=self.angle, angle=angle,
radius=radius, radius=radius,
arc_center=self.arc_center, arc_center=arc_center,
) )
for radius in (self.inner_radius, self.outer_radius) for radius in (inner_radius, outer_radius)
] ]
outer_arc.reverse_points() outer_arc.reverse_points()
self.append_points(inner_arc.get_points()) self.append_points(inner_arc.get_points())
@ -412,56 +424,64 @@ class AnnularSector(Arc):
class Sector(AnnularSector): class Sector(AnnularSector):
CONFIG = { def __init__(
"outer_radius": 1, self,
"inner_radius": 0 angle: float = TAU / 4,
} radius: float = 1.0,
**kwargs
):
super().__init__(
angle,
inner_radius=0,
outer_radius=radius,
**kwargs
)
class Annulus(Circle): class Annulus(VMobject):
CONFIG = { def __init__(
"inner_radius": 1, self,
"outer_radius": 2, inner_radius: float = 1.0,
"fill_opacity": 1, outer_radius: float = 2.0,
"stroke_width": 0, fill_opacity: float = 1.0,
"color": WHITE, stroke_width: float = 0.0,
"mark_paths_closed": False, color: ManimColor = GREY_A,
} center: np_vector = ORIGIN,
**kwargs,
):
super().__init__(
color=color,
fill_opacity=fill_opacity,
stroke_width=stroke_width,
)
def init_points(self): self.radius = outer_radius
self.radius = self.outer_radius outer_circle = Circle(radius=outer_radius)
outer_circle = Circle(radius=self.outer_radius) inner_circle = Circle(radius=inner_radius)
inner_circle = Circle(radius=self.inner_radius)
inner_circle.reverse_points() inner_circle.reverse_points()
self.append_points(outer_circle.get_points()) self.append_points(outer_circle.get_points())
self.append_points(inner_circle.get_points()) self.append_points(inner_circle.get_points())
self.shift(self.arc_center) self.shift(center)
class Line(TipableVMobject): class Line(TipableVMobject):
CONFIG = {
"buff": 0,
# Angle of arc specified here
"path_arc": 0,
}
def __init__( def __init__(
self, self,
start: np.ndarray = LEFT, start: np_vector | Mobject = LEFT,
end: np.ndarray = RIGHT, end: np_vector | Mobject = RIGHT,
buff: float = 0.0,
path_arc: float = 0.0,
**kwargs **kwargs
): ):
digest_config(self, kwargs)
self.set_start_and_end_attrs(start, end)
super().__init__(**kwargs) super().__init__(**kwargs)
self.path_arc = path_arc
def init_points(self) -> None: self.set_start_and_end_attrs(start, end)
self.set_points_by_ends(self.start, self.end, self.buff, self.path_arc) self.set_points_by_ends(self.start, self.end, buff, path_arc)
def set_points_by_ends( def set_points_by_ends(
self, self,
start: np.ndarray, start: np_vector,
end: np.ndarray, end: np_vector,
buff: float = 0, buff: float = 0,
path_arc: float = 0 path_arc: float = 0
): ):
@ -497,7 +517,7 @@ class Line(TipableVMobject):
self.path_arc = new_value self.path_arc = new_value
self.init_points() self.init_points()
def set_start_and_end_attrs(self, start: np.ndarray, end: np.ndarray): def set_start_and_end_attrs(self, start: np_vector | Mobject, end: np_vector | Mobject):
# If either start or end are Mobjects, this # If either start or end are Mobjects, this
# gives their centers # gives their centers
rough_start = self.pointify(start) rough_start = self.pointify(start)
@ -511,9 +531,9 @@ class Line(TipableVMobject):
def pointify( def pointify(
self, self,
mob_or_point: Mobject | np.ndarray, mob_or_point: Mobject | np_vector,
direction: np.ndarray | None = None direction: np_vector | None = None
) -> np.ndarray: ) -> np_vector:
""" """
Take an argument passed into Line (or subclass) and turn Take an argument passed into Line (or subclass) and turn
it into a 3d point. it into a 3d point.
@ -530,7 +550,7 @@ class Line(TipableVMobject):
result[:len(point)] = point result[:len(point)] = point
return result return result
def put_start_and_end_on(self, start: np.ndarray, end: np.ndarray): def put_start_and_end_on(self, start: np_vector, end: np_vector):
curr_start, curr_end = self.get_start_and_end() curr_start, curr_end = self.get_start_and_end()
if np.isclose(curr_start, curr_end).all(): if np.isclose(curr_start, curr_end).all():
# Handle null lines more gracefully # Handle null lines more gracefully
@ -538,16 +558,16 @@ class Line(TipableVMobject):
return self return self
return super().put_start_and_end_on(start, end) return super().put_start_and_end_on(start, end)
def get_vector(self) -> np.ndarray: def get_vector(self) -> np_vector:
return self.get_end() - self.get_start() return self.get_end() - self.get_start()
def get_unit_vector(self) -> np.ndarray: def get_unit_vector(self) -> np_vector:
return normalize(self.get_vector()) return normalize(self.get_vector())
def get_angle(self) -> float: def get_angle(self) -> float:
return angle_of_vector(self.get_vector()) return angle_of_vector(self.get_vector())
def get_projection(self, point: np.ndarray) -> np.ndarray: def get_projection(self, point: np_vector) -> np_vector:
""" """
Return projection of a point onto the line Return projection of a point onto the line
""" """
@ -558,7 +578,7 @@ class Line(TipableVMobject):
def get_slope(self) -> float: def get_slope(self) -> float:
return np.tan(self.get_angle()) return np.tan(self.get_angle())
def set_angle(self, angle: float, about_point: np.ndarray | None = None): def set_angle(self, angle: float, about_point: np_vector | None = None):
if about_point is None: if about_point is None:
about_point = self.get_start() about_point = self.get_start()
self.rotate( self.rotate(
@ -571,118 +591,124 @@ class Line(TipableVMobject):
self.scale(length / self.get_length(), **kwargs) self.scale(length / self.get_length(), **kwargs)
return self return self
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
class DashedLine(Line): class DashedLine(Line):
CONFIG = { def __init__(
"dash_length": DEFAULT_DASH_LENGTH, self,
"dash_spacing": None, start: np_vector = LEFT,
"positive_space_ratio": 0.5, end: np_vector = RIGHT,
} dash_length: float = DEFAULT_DASH_LENGTH,
positive_space_ratio: float = 0.5,
**kwargs
):
super().__init__(start, end, **kwargs)
def __init__(self, *args, **kwargs): num_dashes = self.calculate_num_dashes(dash_length, positive_space_ratio)
super().__init__(*args, **kwargs)
ps_ratio = self.positive_space_ratio
num_dashes = self.calculate_num_dashes(ps_ratio)
dashes = DashedVMobject( dashes = DashedVMobject(
self, self,
num_dashes=num_dashes, num_dashes=num_dashes,
positive_space_ratio=ps_ratio positive_space_ratio=positive_space_ratio
) )
self.clear_points() self.clear_points()
self.add(*dashes) self.add(*dashes)
def calculate_num_dashes(self, positive_space_ratio: float) -> int: def calculate_num_dashes(self, dash_length: float, positive_space_ratio: float) -> int:
try: try:
full_length = self.dash_length / positive_space_ratio full_length = dash_length / positive_space_ratio
return int(np.ceil(self.get_length() / full_length)) return int(np.ceil(self.get_length() / full_length))
except ZeroDivisionError: except ZeroDivisionError:
return 1 return 1
def calculate_positive_space_ratio(self) -> float: def get_start(self) -> np_vector:
return fdiv(
self.dash_length,
self.dash_length + self.dash_spacing,
)
def get_start(self) -> np.ndarray:
if len(self.submobjects) > 0: if len(self.submobjects) > 0:
return self.submobjects[0].get_start() return self.submobjects[0].get_start()
else: else:
return Line.get_start(self) return Line.get_start(self)
def get_end(self) -> np.ndarray: def get_end(self) -> np_vector:
if len(self.submobjects) > 0: if len(self.submobjects) > 0:
return self.submobjects[-1].get_end() return self.submobjects[-1].get_end()
else: else:
return Line.get_end(self) return Line.get_end(self)
def get_first_handle(self) -> np.ndarray: def get_first_handle(self) -> np_vector:
return self.submobjects[0].get_points()[1] return self.submobjects[0].get_points()[1]
def get_last_handle(self) -> np.ndarray: def get_last_handle(self) -> np_vector:
return self.submobjects[-1].get_points()[-2] return self.submobjects[-1].get_points()[-2]
class TangentLine(Line): class TangentLine(Line):
CONFIG = { def __init__(
"length": 1, self,
"d_alpha": 1e-6 vmob: VMobject,
} alpha: float,
length: float = 2,
def __init__(self, vmob: VMobject, alpha: float, **kwargs): d_alpha: float = 1e-6,
digest_config(self, kwargs) **kwargs
da = self.d_alpha ):
a1 = clip(alpha - da, 0, 1) a1 = clip(alpha - d_alpha, 0, 1)
a2 = clip(alpha + da, 0, 1) a2 = clip(alpha + d_alpha, 0, 1)
super().__init__(vmob.pfp(a1), vmob.pfp(a2), **kwargs) super().__init__(vmob.pfp(a1), vmob.pfp(a2), **kwargs)
self.scale(self.length / self.get_length()) self.scale(length / self.get_length())
class Elbow(VMobject): class Elbow(VMobject):
CONFIG = { def __init__(
"width": 0.2, self,
"angle": 0, width: float = 0.2,
} angle: float = 0,
**kwargs
def __init__(self, **kwargs): ):
super().__init__(**kwargs) super().__init__(**kwargs)
self.set_points_as_corners([UP, UP + RIGHT, RIGHT]) self.set_points_as_corners([UP, UR, RIGHT])
self.set_width(self.width, about_point=ORIGIN) self.set_width(width, about_point=ORIGIN)
self.rotate(self.angle, about_point=ORIGIN) self.rotate(angle, about_point=ORIGIN)
class Arrow(Line): class Arrow(Line):
CONFIG = { def __init__(
"color": GREY_A, self,
"stroke_width": 5, start: np_vector | Mobject,
"tip_width_ratio": 4, end: np_vector | Mobject,
"width_to_tip_len": 0.0075, color: ManimColor = GREY_A,
"max_tip_length_to_length_ratio": 0.3, stroke_width: float = 5,
"max_width_to_length_ratio": 10, buff: float = 0.25,
"buff": 0.25, tip_width_ratio: float = 5,
} width_to_tip_len: float = 0.0075,
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
self.width_to_tip_len = width_to_tip_len
self.max_tip_length_to_length_ratio = max_tip_length_to_length_ratio
self.max_width_to_length_ratio = max_width_to_length_ratio
self.max_stroke_width = stroke_width
super().__init__(
start, end,
color=color,
stroke_width=stroke_width,
buff=buff,
**kwargs
)
def set_points_by_ends( def set_points_by_ends(
self, self,
start: np.ndarray, start: np_vector,
end: np.ndarray, end: np_vector,
buff: float = 0, buff: float = 0,
path_arc: float = 0 path_arc: float = 0
): ):
super().set_points_by_ends(start, end, buff, path_arc) super().set_points_by_ends(start, end, buff, path_arc)
self.insert_tip_anchor() self.insert_tip_anchor()
return self
def init_colors(self) -> None:
super().init_colors()
self.create_tip_with_stroke_width() self.create_tip_with_stroke_width()
return self
def get_arc_length(self) -> float:
# Push up into Line?
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
def insert_tip_anchor(self): def insert_tip_anchor(self):
prev_end = self.get_end() prev_end = self.get_end()
@ -697,6 +723,8 @@ class Arrow(Line):
return self return self
def create_tip_with_stroke_width(self): def create_tip_with_stroke_width(self):
if not self.has_points():
return self
width = min( width = min(
self.max_stroke_width, self.max_stroke_width,
self.max_width_to_length_ratio * self.get_length(), self.max_width_to_length_ratio * self.get_length(),
@ -713,11 +741,9 @@ class Arrow(Line):
def reset_tip(self): def reset_tip(self):
self.set_points_by_ends( self.set_points_by_ends(
self.get_start(), self.get_start(), self.get_end(),
self.get_end(), path_arc=self.path_arc
path_arc=self.path_arc,
) )
self.create_tip_with_stroke_width()
return self return self
def set_stroke( def set_stroke(
@ -729,7 +755,7 @@ class Arrow(Line):
super().set_stroke(color=color, width=width, *args, **kwargs) super().set_stroke(color=color, width=width, *args, **kwargs)
if isinstance(width, numbers.Number): if isinstance(width, numbers.Number):
self.max_stroke_width = width self.max_stroke_width = width
self.reset_tip() self.create_tip_with_stroke_width()
return self return self
def _handle_scale_side_effects(self, scale_factor: float): def _handle_scale_side_effects(self, scale_factor: float):
@ -737,22 +763,39 @@ class Arrow(Line):
class FillArrow(Line): class FillArrow(Line):
CONFIG = { def __init__(
"fill_color": GREY_A, self,
"fill_opacity": 1, start: np_vector | Mobject = LEFT,
"stroke_width": 0, end: np_vector | Mobject = LEFT,
"buff": MED_SMALL_BUFF, fill_color: ManimColor = GREY_A,
"thickness": 0.05, fill_opacity: float = 1.0,
"tip_width_ratio": 5, stroke_width: float = 0.0,
"tip_angle": PI / 3, buff: float = MED_SMALL_BUFF,
"max_tip_length_to_length_ratio": 0.5, thickness: float = 0.05,
"max_width_to_length_ratio": 0.1, 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,
**kwargs
)
def set_points_by_ends( def set_points_by_ends(
self, self,
start: np.ndarray, start: np_vector,
end: np.ndarray, end: np_vector,
buff: float = 0, buff: float = 0,
path_arc: float = 0 path_arc: float = 0
) -> None: ) -> None:
@ -806,7 +849,7 @@ class FillArrow(Line):
self.append_points(points2) self.append_points(points2)
self.add_line_to(points1[0]) self.add_line_to(points1[0])
if length > 0: if length > 0 and self.get_length() > 0:
# Final correction # Final correction
super().scale(length / self.get_length()) super().scale(length / self.get_length())
@ -824,15 +867,15 @@ class FillArrow(Line):
) )
return self return self
def get_start(self) -> np.ndarray: def get_start(self) -> np_vector:
nppc = self.n_points_per_curve nppc = self.n_points_per_curve
points = self.get_points() points = self.get_points()
return (points[0] + points[-nppc]) / 2 return (points[0] + points[-nppc]) / 2
def get_end(self) -> np.ndarray: def get_end(self) -> np_vector:
return self.get_points()[self.tip_index] return self.get_points()[self.tip_index]
def put_start_and_end_on(self, start: np.ndarray, end: np.ndarray): def put_start_and_end_on(self, start: np_vector, end: np_vector):
self.set_points_by_ends(start, end, buff=0, path_arc=self.path_arc) self.set_points_by_ends(start, end, buff=0, path_arc=self.path_arc)
return self return self
@ -853,45 +896,36 @@ class FillArrow(Line):
class Vector(Arrow): class Vector(Arrow):
CONFIG = { def __init__(
"buff": 0, self,
} direction: np_vector = RIGHT,
buff: float = 0.0,
def __init__(self, direction: np.ndarray = RIGHT, **kwargs): **kwargs
):
if len(direction) == 2: if len(direction) == 2:
direction = np.hstack([direction, 0]) direction = np.hstack([direction, 0])
super().__init__(ORIGIN, direction, **kwargs) super().__init__(ORIGIN, direction, buff=buff, **kwargs)
class DoubleArrow(Arrow):
def __init__(self, *args, **kwargs):
Arrow.__init__(self, *args, **kwargs)
self.add_tip(at_start=True)
class CubicBezier(VMobject): class CubicBezier(VMobject):
def __init__( def __init__(
self, self,
a0: np.ndarray, a0: np_vector,
h0: np.ndarray, h0: np_vector,
h1: np.ndarray, h1: np_vector,
a1: np.ndarray, a1: np_vector,
**kwargs **kwargs
): ):
VMobject.__init__(self, **kwargs) super().__init__(**kwargs)
self.add_cubic_bezier_curve(a0, h0, h1, a1) self.add_cubic_bezier_curve(a0, h0, h1, a1)
class Polygon(VMobject): class Polygon(VMobject):
def __init__(self, *vertices: np.ndarray, **kwargs): def __init__(self, *vertices: np_vector, **kwargs):
self.vertices = vertices
super().__init__(**kwargs) super().__init__(**kwargs)
self.set_points_as_corners([*vertices, vertices[0]])
def init_points(self) -> None: def get_vertices(self) -> list[np_vector]:
verts = self.vertices
self.set_points_as_corners([*verts, verts[0]])
def get_vertices(self) -> list[np.ndarray]:
return self.get_start_anchors() return self.get_start_anchors()
def round_corners(self, radius: float | None = None): def round_corners(self, radius: float | None = None):
@ -940,22 +974,23 @@ class Polygon(VMobject):
return self return self
class Polyline(Polygon): class Polyline(VMobject):
def init_points(self) -> None: def __init__(self, *vertices: np_vector, **kwargs):
self.set_points_as_corners(self.vertices) super().__init__(**kwargs)
self.set_points_as_corners(vertices)
class RegularPolygon(Polygon): class RegularPolygon(Polygon):
CONFIG = { def __init__(
"start_angle": None, self,
} n: int = 6,
radius: float = 1.0,
def __init__(self, n: int = 6, **kwargs): start_angle: float | None = None,
digest_config(self, kwargs, locals()) **kwargs
if self.start_angle is None: ):
# 0 for odd, 90 for even # Defaults to 0 for odd, 90 for even
self.start_angle = (n % 2) * 90 * DEGREES start_angle = start_angle or (n % 2) * 90 * DEGREES
start_vect = rotate_vector(RIGHT, self.start_angle) start_vect = rotate_vector(radius * RIGHT, start_angle)
vertices = compass_directions(n, start_vect) vertices = compass_directions(n, start_vect)
super().__init__(*vertices, **kwargs) super().__init__(*vertices, **kwargs)
@ -966,36 +1001,42 @@ class Triangle(RegularPolygon):
class ArrowTip(Triangle): class ArrowTip(Triangle):
CONFIG = { def __init__(
"fill_opacity": 1, self,
"fill_color": WHITE, angle: float = 0,
"stroke_width": 0, width: float = DEFAULT_ARROW_TIP_WIDTH,
"width": DEFAULT_ARROW_TIP_WIDTH, length: float = DEFAULT_ARROW_TIP_LENGTH,
"length": DEFAULT_ARROW_TIP_LENGTH, fill_opacity: float = 1.0,
"angle": 0, fill_color: ManimColor = WHITE,
"tip_style": 0, # triangle=0, inner_smooth=1, dot=2 stroke_width: float = 0.0,
} tip_style: int = 0, # triangle=0, inner_smooth=1, dot=2
**kwargs
def __init__(self, **kwargs): ):
Triangle.__init__(self, start_angle=0, **kwargs) super().__init__(
self.set_height(self.width) start_angle=0,
self.set_width(self.length, stretch=True) fill_opacity=fill_opacity,
if self.tip_style == 1: fill_color=fill_color,
self.set_height(self.length * 0.9, stretch=True) stroke_width=stroke_width,
self.data["points"][4] += np.array([0.6 * self.length, 0, 0]) **kwargs
elif self.tip_style == 2: )
h = self.length / 2 self.set_height(width)
self.set_width(length, stretch=True)
if tip_style == 1:
self.set_height(length * 0.9, stretch=True)
self.data["points"][4] += np.array([0.6 * length, 0, 0])
elif tip_style == 2:
h = length / 2
self.clear_points() self.clear_points()
self.data["points"] = Dot().set_width(h).get_points() self.data["points"] = Dot().set_width(h).get_points()
self.rotate(self.angle) self.rotate(angle)
def get_base(self) -> np.ndarray: def get_base(self) -> np_vector:
return self.point_from_proportion(0.5) return self.point_from_proportion(0.5)
def get_tip_point(self) -> np.ndarray: def get_tip_point(self) -> np_vector:
return self.get_points()[0] return self.get_points()[0]
def get_vector(self) -> np.ndarray: def get_vector(self) -> np_vector:
return self.get_tip_point() - self.get_base() return self.get_tip_point() - self.get_base()
def get_angle(self) -> float: def get_angle(self) -> float:
@ -1006,42 +1047,30 @@ class ArrowTip(Triangle):
class Rectangle(Polygon): class Rectangle(Polygon):
CONFIG = {
"color": WHITE,
"width": 4.0,
"height": 2.0,
"mark_paths_closed": True,
"close_new_points": True,
}
def __init__( def __init__(
self, self,
width: float | None = None, width: float = 4.0,
height: float | None = None, height: float = 2.0,
color: ManimColor = WHITE,
**kwargs **kwargs
): ):
Polygon.__init__(self, UR, UL, DL, DR, **kwargs) super().__init__(UR, UL, DL, DR, color=color, **kwargs)
if width is None:
width = self.width
if height is None:
height = self.height
self.set_width(width, stretch=True) self.set_width(width, stretch=True)
self.set_height(height, stretch=True) self.set_height(height, stretch=True)
class Square(Rectangle): class Square(Rectangle):
def __init__(self, side_length: float = 2.0, **kwargs): def __init__(self, side_length: float = 2.0, **kwargs):
self.side_length = side_length
super().__init__(side_length, side_length, **kwargs) super().__init__(side_length, side_length, **kwargs)
class RoundedRectangle(Rectangle): class RoundedRectangle(Rectangle):
CONFIG = { def __init__(
"corner_radius": 0.5, self,
} width: float = 4.0,
height: float = 2.0,
def __init__(self, **kwargs): corner_radius: float = 0.5,
Rectangle.__init__(self, **kwargs) **kwargs
self.round_corners(self.corner_radius) ):
super().__init__(width, height, **kwargs)
self.round_corners(corner_radius)