3b1b-manim/manimlib/animation/creation.py

257 lines
7.9 KiB
Python
Raw Normal View History

from __future__ import annotations
from abc import ABC, abstractmethod
import numpy as np
from manimlib.animation.animation import Animation
2022-12-14 10:58:35 -08:00
from manimlib.constants import WHITE
2022-05-06 22:09:58 +08:00
from manimlib.mobject.svg.string_mobject import StringMobject
from manimlib.mobject.types.vectorized_mobject import VMobject
2019-02-09 08:59:02 -08:00
from manimlib.utils.bezier import integer_interpolate
from manimlib.utils.rate_functions import linear
2019-02-09 08:59:02 -08:00
from manimlib.utils.rate_functions import double_smooth
from manimlib.utils.rate_functions import smooth
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from manimlib.mobject.mobject import Mobject
2022-12-14 10:58:35 -08:00
from manimlib.scene.scene import Scene
from manimlib.constants import ManimColor
class ShowPartial(Animation, ABC):
2019-02-08 15:44:58 -08:00
"""
Abstract class for ShowCreation and ShowPassingFlash
"""
2022-12-14 10:58:35 -08:00
def __init__(self, mobject: Mobject, should_match_start: bool = False, **kwargs):
self.should_match_start = should_match_start
super().__init__(mobject, **kwargs)
2021-01-13 11:55:45 -10:00
def begin(self) -> None:
2021-01-13 11:55:45 -10:00
super().begin()
if not self.should_match_start:
self.mobject.lock_matching_data(self.mobject, self.starting_mobject)
2019-02-08 15:44:58 -08:00
def finish(self) -> None:
super().finish()
self.mobject.unlock_data()
def interpolate_submobject(
self,
submob: VMobject,
start_submob: VMobject,
alpha: float
) -> None:
2019-02-08 15:44:58 -08:00
submob.pointwise_become_partial(
start_submob, *self.get_bounds(alpha)
)
@abstractmethod
def get_bounds(self, alpha: float) -> tuple[float, float]:
raise Exception("Not Implemented")
class ShowCreation(ShowPartial):
2022-12-14 10:58:35 -08:00
def __init__(self, mobject: Mobject, lag_ratio: float = 1.0, **kwargs):
super().__init__(mobject, lag_ratio=lag_ratio, **kwargs)
def get_bounds(self, alpha: float) -> tuple[float, float]:
return (0, alpha)
class Uncreate(ShowCreation):
2022-12-14 10:58:35 -08:00
def __init__(
self,
mobject: Mobject,
rate_func: Callable[[float], float] = lambda t: smooth(1 - t),
remover: bool = True,
should_match_start: bool = True,
**kwargs,
):
super().__init__(
mobject,
rate_func=rate_func,
remover=remover,
should_match_start=should_match_start,
**kwargs,
)
2019-02-09 08:59:02 -08:00
class DrawBorderThenFill(Animation):
2022-12-14 10:58:35 -08:00
def __init__(
self,
vmobject: VMobject,
run_time: float = 2.0,
rate_func: Callable[[float], float] = double_smooth,
stroke_width: float = 2.0,
stroke_color: ManimColor = None,
draw_border_animation_config: dict = {},
fill_animation_config: dict = {},
**kwargs
):
2021-01-13 11:55:45 -10:00
assert(isinstance(vmobject, VMobject))
2022-12-14 10:58:35 -08:00
self.sm_to_index = {hash(sm): 0 for sm in vmobject.get_family()}
self.stroke_width = stroke_width
self.stroke_color = stroke_color
self.draw_border_animation_config = draw_border_animation_config
self.fill_animation_config = fill_animation_config
super().__init__(
vmobject,
run_time=run_time,
rate_func=rate_func,
**kwargs
)
self.mobject = vmobject
def begin(self) -> None:
# Trigger triangulation calculation
for submob in self.mobject.get_family():
submob.get_triangulation()
2019-02-09 08:59:02 -08:00
self.outline = self.get_outline()
super().begin()
2021-01-13 11:55:45 -10:00
self.mobject.match_style(self.outline)
self.mobject.lock_matching_data(self.mobject, self.outline)
def finish(self) -> None:
2021-02-03 14:17:55 -08:00
super().finish()
self.mobject.unlock_data()
def get_outline(self) -> VMobject:
2019-02-09 08:59:02 -08:00
outline = self.mobject.copy()
outline.set_fill(opacity=0)
2022-12-14 10:58:35 -08:00
for sm in outline.family_members_with_points():
2019-03-24 11:32:12 -07:00
sm.set_stroke(
2022-12-14 10:58:35 -08:00
color=self.stroke_color or sm.get_stroke_color(),
width=self.stroke_width,
2019-03-24 11:32:12 -07:00
)
2019-02-09 08:59:02 -08:00
return outline
2022-12-14 10:58:35 -08:00
def get_all_mobjects(self) -> list[Mobject]:
2019-02-09 08:59:02 -08:00
return [*super().get_all_mobjects(), self.outline]
def interpolate_submobject(
self,
submob: VMobject,
start: VMobject,
outline: VMobject,
alpha: float
) -> None:
2019-02-09 08:59:02 -08:00
index, subalpha = integer_interpolate(0, 2, alpha)
2021-01-13 11:55:45 -10:00
if index == 1 and self.sm_to_index[hash(submob)] == 0:
# First time crossing over
submob.set_data(outline.data)
submob.unlock_data()
2021-02-10 16:45:23 -08:00
if not self.mobject.has_updaters:
submob.lock_matching_data(submob, start)
submob.needs_new_triangulation = False
2021-01-13 11:55:45 -10:00
self.sm_to_index[hash(submob)] = 1
2019-02-09 08:59:02 -08:00
if index == 0:
2020-02-18 22:40:12 -08:00
submob.pointwise_become_partial(outline, 0, subalpha)
2019-02-09 08:59:02 -08:00
else:
submob.interpolate(outline, start, subalpha)
2019-02-09 08:59:02 -08:00
class Write(DrawBorderThenFill):
2022-12-14 10:58:35 -08:00
def __init__(
self,
vmobject: VMobject,
run_time: float = -1, # If negative, this will be reassigned
lag_ratio: float = -1, # If negative, this will be reassigned
rate_func: Callable[[float], float] = linear,
stroke_color: ManimColor = WHITE,
**kwargs
):
family_size = len(vmobject.family_members_with_points())
super().__init__(
vmobject,
run_time=self.compute_run_time(family_size, run_time),
lag_ratio=self.compute_lag_ratio(family_size, lag_ratio),
rate_func=rate_func,
stroke_color=stroke_color,
**kwargs
)
2022-12-14 10:58:35 -08:00
def compute_run_time(self, family_size: int, run_time: float):
if run_time < 0:
return 1 if family_size < 15 else 2
return run_time
2019-01-17 14:09:15 -08:00
2022-12-14 10:58:35 -08:00
def compute_lag_ratio(self, family_size: int, lag_ratio: float):
if lag_ratio < 0:
return min(4.0 / (family_size + 1.0), 0.2)
return lag_ratio
2019-02-09 10:56:51 -08:00
2022-12-14 10:58:35 -08:00
class ShowIncreasingSubsets(Animation):
def __init__(
self,
group: Mobject,
int_func: Callable[[float], float] = np.round,
suspend_mobject_updating: bool = False,
**kwargs
):
self.all_submobs = list(group.submobjects)
2022-12-14 10:58:35 -08:00
self.int_func = int_func
super().__init__(
group,
suspend_mobject_updating=suspend_mobject_updating,
**kwargs
)
2019-01-17 14:09:15 -08:00
def interpolate_mobject(self, alpha: float) -> None:
2019-01-17 14:09:15 -08:00
n_submobs = len(self.all_submobs)
index = int(self.int_func(alpha * n_submobs))
2019-09-19 13:38:03 -07:00
self.update_submobject_list(index)
def update_submobject_list(self, index: int) -> None:
self.mobject.set_submobjects(self.all_submobs[:index])
2019-09-19 13:38:03 -07:00
class ShowSubmobjectsOneByOne(ShowIncreasingSubsets):
2022-12-14 10:58:35 -08:00
def __init__(
self,
group: Mobject,
int_func: Callable[[float], float] = np.ceil,
**kwargs
):
super().__init__(group, int_func=int_func, **kwargs)
def update_submobject_list(self, index: int) -> None:
2019-09-19 13:38:03 -07:00
# N = len(self.all_submobs)
if index == 0:
self.mobject.set_submobjects([])
2019-09-19 13:38:03 -07:00
else:
2021-11-22 08:05:59 -08:00
self.mobject.set_submobjects([self.all_submobs[index - 1]])
2022-03-31 10:36:14 +08:00
class AddTextWordByWord(ShowIncreasingSubsets):
2022-12-14 10:58:35 -08:00
def __init__(
self,
string_mobject: StringMobject,
time_per_word: float = 0.2,
run_time: float = -1.0, # If negative, it will be recomputed with time_per_word
rate_func: Callable[[float], float] = linear,
**kwargs
):
2022-05-06 22:09:58 +08:00
assert isinstance(string_mobject, StringMobject)
2022-05-04 22:18:19 +08:00
grouped_mobject = string_mobject.build_groups()
2022-12-14 10:58:35 -08:00
if run_time < 0:
run_time = time_per_word * len(grouped_mobject)
super().__init__(
grouped_mobject,
run_time=run_time,
rate_func=rate_func,
**kwargs
)
self.string_mobject = string_mobject
def clean_up_from_scene(self, scene: Scene) -> None:
scene.remove(self.mobject)
if not self.is_remover():
scene.add(self.string_mobject)