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