2022-02-15 18:39:45 +08:00
|
|
|
from __future__ import annotations
|
|
|
|
|
2015-12-16 19:49:21 -08:00
|
|
|
from copy import deepcopy
|
2015-06-10 22:00:35 -07:00
|
|
|
|
2021-02-11 12:21:06 -08:00
|
|
|
from manimlib.mobject.mobject import _AnimationBuilder
|
|
|
|
from manimlib.mobject.mobject import Mobject
|
2024-09-28 09:48:20 -05:00
|
|
|
from manimlib.utils.iterables import remove_list_redundancies
|
2018-12-24 12:37:51 -08:00
|
|
|
from manimlib.utils.rate_functions import smooth
|
2022-05-02 11:40:42 -07:00
|
|
|
from manimlib.utils.rate_functions import squish_rate_func
|
2020-02-18 22:43:06 -08:00
|
|
|
from manimlib.utils.simple_functions import clip
|
2015-06-10 22:00:35 -07:00
|
|
|
|
2022-02-15 18:39:45 +08:00
|
|
|
from typing import TYPE_CHECKING
|
2022-02-16 21:08:25 +08:00
|
|
|
|
2022-02-15 18:39:45 +08:00
|
|
|
if TYPE_CHECKING:
|
2022-04-12 19:19:59 +08:00
|
|
|
from typing import Callable
|
|
|
|
|
2022-02-15 18:39:45 +08:00
|
|
|
from manimlib.scene.scene import Scene
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2019-02-11 22:25:22 -08:00
|
|
|
DEFAULT_ANIMATION_RUN_TIME = 1.0
|
2019-03-16 22:13:09 -07:00
|
|
|
DEFAULT_ANIMATION_LAG_RATIO = 0
|
2019-02-11 22:25:22 -08:00
|
|
|
|
|
|
|
|
2015-06-10 22:00:35 -07:00
|
|
|
class Animation(object):
|
2022-12-13 15:45:57 -08:00
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
mobject: Mobject,
|
|
|
|
run_time: float = DEFAULT_ANIMATION_RUN_TIME,
|
|
|
|
# Tuple of times, between which the animation will run
|
|
|
|
time_span: tuple[float, float] | None = None,
|
|
|
|
# If 0, the animation is applied to all submobjects at the same time
|
|
|
|
# If 1, it is applied to each successively.
|
|
|
|
# If 0 < lag_ratio < 1, its applied to each with lagged start times
|
|
|
|
lag_ratio: float = DEFAULT_ANIMATION_LAG_RATIO,
|
|
|
|
rate_func: Callable[[float], float] = smooth,
|
|
|
|
name: str = "",
|
2018-04-06 13:58:59 -07:00
|
|
|
# Does this animation add or remove a mobject form the screen
|
2022-12-13 15:45:57 -08:00
|
|
|
remover: bool = False,
|
2020-08-30 16:00:42 -07:00
|
|
|
# What to enter into the update function upon completion
|
2022-12-13 15:45:57 -08:00
|
|
|
final_alpha_value: float = 1.0,
|
2024-09-28 09:48:20 -05:00
|
|
|
# If set to True, the mobject itself will have its internal updaters called,
|
|
|
|
# but the start or target mobjects would not be suspended. To completely suspend
|
|
|
|
# updating, call mobject.suspend_updating() before the animation
|
|
|
|
suspend_mobject_updating: bool = False,
|
2022-12-13 15:45:57 -08:00
|
|
|
):
|
|
|
|
self.mobject = mobject
|
|
|
|
self.run_time = run_time
|
|
|
|
self.time_span = time_span
|
|
|
|
self.rate_func = rate_func
|
|
|
|
self.name = name or self.__class__.__name__ + str(self.mobject)
|
|
|
|
self.remover = remover
|
|
|
|
self.final_alpha_value = final_alpha_value
|
|
|
|
self.lag_ratio = lag_ratio
|
|
|
|
self.suspend_mobject_updating = suspend_mobject_updating
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2024-08-16 12:15:55 -05:00
|
|
|
assert isinstance(mobject, Mobject)
|
2022-12-13 15:45:57 -08:00
|
|
|
|
2022-02-15 18:39:45 +08:00
|
|
|
def __str__(self) -> str:
|
2022-12-13 15:45:57 -08:00
|
|
|
return self.name
|
2019-02-09 08:59:13 -08:00
|
|
|
|
2022-02-15 18:39:45 +08:00
|
|
|
def begin(self) -> None:
|
2019-02-09 09:36:37 -08:00
|
|
|
# This is called right as an animation is being
|
|
|
|
# played. As much initialization as possible,
|
|
|
|
# especially any mobject copying, should live in
|
|
|
|
# this method
|
2022-05-02 11:40:42 -07:00
|
|
|
if self.time_span is not None:
|
|
|
|
start, end = self.time_span
|
|
|
|
self.run_time = max(end, self.run_time)
|
2022-04-14 16:27:58 -07:00
|
|
|
self.mobject.set_animating_status(True)
|
2019-02-09 10:09:19 -08:00
|
|
|
self.starting_mobject = self.create_starting_mobject()
|
|
|
|
if self.suspend_mobject_updating:
|
2023-11-06 12:31:16 -05:00
|
|
|
self.mobject_was_updating = not self.mobject.updating_suspended
|
2019-02-09 10:09:19 -08:00
|
|
|
self.mobject.suspend_updating()
|
2020-02-18 22:43:06 -08:00
|
|
|
self.families = list(self.get_all_families_zipped())
|
2019-02-08 12:32:24 -08:00
|
|
|
self.interpolate(0)
|
2015-06-10 22:00:35 -07:00
|
|
|
|
2022-02-15 18:39:45 +08:00
|
|
|
def finish(self) -> None:
|
2020-08-30 16:00:42 -07:00
|
|
|
self.interpolate(self.final_alpha_value)
|
2022-04-14 16:27:58 -07:00
|
|
|
self.mobject.set_animating_status(False)
|
2023-11-06 12:31:16 -05:00
|
|
|
if self.suspend_mobject_updating and self.mobject_was_updating:
|
2019-02-09 10:09:19 -08:00
|
|
|
self.mobject.resume_updating()
|
2019-02-08 10:33:08 -08:00
|
|
|
|
2022-02-15 18:39:45 +08:00
|
|
|
def clean_up_from_scene(self, scene: Scene) -> None:
|
2019-02-08 11:00:04 -08:00
|
|
|
if self.is_remover():
|
|
|
|
scene.remove(self.mobject)
|
2019-02-08 10:33:08 -08:00
|
|
|
|
2022-02-15 18:39:45 +08:00
|
|
|
def create_starting_mobject(self) -> Mobject:
|
2019-02-09 10:09:19 -08:00
|
|
|
# Keep track of where the mobject starts
|
|
|
|
return self.mobject.copy()
|
|
|
|
|
2022-02-15 18:39:45 +08:00
|
|
|
def get_all_mobjects(self) -> tuple[Mobject, Mobject]:
|
2019-02-09 08:59:13 -08:00
|
|
|
"""
|
|
|
|
Ordering must match the ording of arguments to interpolate_submobject
|
|
|
|
"""
|
|
|
|
return self.mobject, self.starting_mobject
|
2015-12-16 19:49:21 -08:00
|
|
|
|
2022-02-15 18:39:45 +08:00
|
|
|
def get_all_families_zipped(self) -> zip[tuple[Mobject]]:
|
2019-02-11 20:50:07 -08:00
|
|
|
return zip(*[
|
2021-01-12 13:08:01 -10:00
|
|
|
mob.get_family()
|
2019-02-11 20:50:07 -08:00
|
|
|
for mob in self.get_all_mobjects()
|
|
|
|
])
|
2019-02-05 15:39:58 -08:00
|
|
|
|
2022-02-15 18:39:45 +08:00
|
|
|
def update_mobjects(self, dt: float) -> None:
|
2019-02-08 12:32:24 -08:00
|
|
|
"""
|
|
|
|
Updates things like starting_mobject, and (for
|
2024-09-28 09:48:20 -05:00
|
|
|
Transforms) target_mobject.
|
2019-02-08 12:32:24 -08:00
|
|
|
"""
|
2019-02-09 10:55:23 -08:00
|
|
|
for mob in self.get_all_mobjects_to_update():
|
2019-02-08 11:57:27 -08:00
|
|
|
mob.update(dt)
|
|
|
|
|
2022-02-15 18:39:45 +08:00
|
|
|
def get_all_mobjects_to_update(self) -> list[Mobject]:
|
2019-02-09 10:55:23 -08:00
|
|
|
# The surrounding scene typically handles
|
2024-09-28 09:48:20 -05:00
|
|
|
# updating of self.mobject.
|
2023-06-29 10:18:44 +08:00
|
|
|
items = list(filter(
|
2019-02-09 10:55:23 -08:00
|
|
|
lambda m: m is not self.mobject,
|
|
|
|
self.get_all_mobjects()
|
|
|
|
))
|
2024-09-28 09:48:20 -05:00
|
|
|
items = remove_list_redundancies(items)
|
2023-06-29 10:18:44 +08:00
|
|
|
return items
|
2019-02-09 10:55:23 -08:00
|
|
|
|
2019-02-09 08:59:13 -08:00
|
|
|
def copy(self):
|
|
|
|
return deepcopy(self)
|
|
|
|
|
2022-12-16 15:21:31 -08:00
|
|
|
def update_rate_info(
|
|
|
|
self,
|
|
|
|
run_time: float | None = None,
|
|
|
|
rate_func: Callable[[float], float] | None = None,
|
|
|
|
lag_ratio: float | None = None,
|
|
|
|
):
|
|
|
|
self.run_time = run_time or self.run_time
|
|
|
|
self.rate_func = rate_func or self.rate_func
|
|
|
|
self.lag_ratio = lag_ratio or self.lag_ratio
|
2019-02-09 08:59:13 -08:00
|
|
|
return self
|
|
|
|
|
|
|
|
# Methods for interpolation, the mean of an Animation
|
2022-02-15 18:39:45 +08:00
|
|
|
def interpolate(self, alpha: float) -> None:
|
2022-11-18 09:07:18 -08:00
|
|
|
self.interpolate_mobject(alpha)
|
2015-06-10 22:00:35 -07:00
|
|
|
|
2022-02-15 18:39:45 +08:00
|
|
|
def update(self, alpha: float) -> None:
|
2019-02-08 12:32:24 -08:00
|
|
|
"""
|
|
|
|
This method shouldn't exist, but it's here to
|
|
|
|
keep many old scenes from breaking
|
|
|
|
"""
|
|
|
|
self.interpolate(alpha)
|
|
|
|
|
2024-07-23 15:13:30 +08:00
|
|
|
def time_spanned_alpha(self, alpha: float) -> float:
|
|
|
|
if self.time_span is not None:
|
|
|
|
start, end = self.time_span
|
|
|
|
return clip(alpha * self.run_time - start, 0, end - start) / (end - start)
|
|
|
|
return alpha
|
|
|
|
|
2022-02-15 18:39:45 +08:00
|
|
|
def interpolate_mobject(self, alpha: float) -> None:
|
2020-02-18 22:43:06 -08:00
|
|
|
for i, mobs in enumerate(self.families):
|
2024-07-23 15:13:30 +08:00
|
|
|
sub_alpha = self.get_sub_alpha(self.time_spanned_alpha(alpha), i, len(self.families))
|
2019-02-08 12:00:51 -08:00
|
|
|
self.interpolate_submobject(*mobs, sub_alpha)
|
|
|
|
|
2022-02-15 18:39:45 +08:00
|
|
|
def interpolate_submobject(
|
|
|
|
self,
|
|
|
|
submobject: Mobject,
|
|
|
|
starting_submobject: Mobject,
|
|
|
|
alpha: float
|
|
|
|
):
|
2019-02-08 12:00:51 -08:00
|
|
|
# Typically ipmlemented by subclass
|
|
|
|
pass
|
2016-07-22 15:50:52 -07:00
|
|
|
|
2022-02-15 18:39:45 +08:00
|
|
|
def get_sub_alpha(
|
|
|
|
self,
|
|
|
|
alpha: float,
|
|
|
|
index: int,
|
|
|
|
num_submobjects: int
|
|
|
|
) -> float:
|
2019-02-08 15:38:02 -08:00
|
|
|
# TODO, make this more understanable, and/or combine
|
|
|
|
# its functionality with AnimationGroup's method
|
|
|
|
# build_animations_with_timings
|
|
|
|
lag_ratio = self.lag_ratio
|
|
|
|
full_length = (num_submobjects - 1) * lag_ratio + 1
|
|
|
|
value = alpha * full_length
|
|
|
|
lower = index * lag_ratio
|
2022-11-18 09:07:18 -08:00
|
|
|
raw_sub_alpha = clip((value - lower), 0, 1)
|
|
|
|
return self.rate_func(raw_sub_alpha)
|
2016-07-22 15:50:52 -07:00
|
|
|
|
2019-02-09 08:59:13 -08:00
|
|
|
# Getters and setters
|
2022-02-15 18:39:45 +08:00
|
|
|
def set_run_time(self, run_time: float):
|
2019-02-08 12:32:24 -08:00
|
|
|
self.run_time = run_time
|
2015-06-10 22:00:35 -07:00
|
|
|
return self
|
|
|
|
|
2022-02-15 18:39:45 +08:00
|
|
|
def get_run_time(self) -> float:
|
2022-05-02 11:40:42 -07:00
|
|
|
if self.time_span:
|
|
|
|
return max(self.run_time, self.time_span[1])
|
2016-07-21 15:16:32 -07:00
|
|
|
return self.run_time
|
|
|
|
|
2022-02-15 18:39:45 +08:00
|
|
|
def set_rate_func(self, rate_func: Callable[[float], float]):
|
2016-01-01 14:51:16 -08:00
|
|
|
self.rate_func = rate_func
|
2015-06-10 22:00:35 -07:00
|
|
|
return self
|
|
|
|
|
2022-02-15 18:39:45 +08:00
|
|
|
def get_rate_func(self) -> Callable[[float], float]:
|
2016-07-21 15:16:32 -07:00
|
|
|
return self.rate_func
|
|
|
|
|
2022-02-15 18:39:45 +08:00
|
|
|
def set_name(self, name: str):
|
2015-06-10 22:00:35 -07:00
|
|
|
self.name = name
|
2016-07-22 15:50:52 -07:00
|
|
|
return self
|
2015-06-10 22:00:35 -07:00
|
|
|
|
2022-02-15 18:39:45 +08:00
|
|
|
def is_remover(self) -> bool:
|
2016-07-19 11:07:26 -07:00
|
|
|
return self.remover
|
2021-02-10 07:43:46 -06:00
|
|
|
|
|
|
|
|
2022-02-15 18:39:45 +08:00
|
|
|
def prepare_animation(anim: Animation | _AnimationBuilder):
|
2021-02-10 07:43:46 -06:00
|
|
|
if isinstance(anim, _AnimationBuilder):
|
|
|
|
return anim.build()
|
|
|
|
|
|
|
|
if isinstance(anim, Animation):
|
|
|
|
return anim
|
|
|
|
|
|
|
|
raise TypeError(f"Object {anim} cannot be converted to an animation")
|