animation.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. from __future__ import annotations
  2. from copy import deepcopy
  3. from manimlib.mobject.mobject import _AnimationBuilder
  4. from manimlib.mobject.mobject import Mobject
  5. from manimlib.utils.iterables import remove_list_redundancies
  6. from manimlib.utils.rate_functions import smooth
  7. from manimlib.utils.rate_functions import squish_rate_func
  8. from manimlib.utils.simple_functions import clip
  9. from typing import TYPE_CHECKING
  10. if TYPE_CHECKING:
  11. from typing import Callable
  12. from manimlib.scene.scene import Scene
  13. DEFAULT_ANIMATION_RUN_TIME = 1.0
  14. DEFAULT_ANIMATION_LAG_RATIO = 0
  15. class Animation(object):
  16. def __init__(
  17. self,
  18. mobject: Mobject,
  19. run_time: float = DEFAULT_ANIMATION_RUN_TIME,
  20. # Tuple of times, between which the animation will run
  21. time_span: tuple[float, float] | None = None,
  22. # If 0, the animation is applied to all submobjects at the same time
  23. # If 1, it is applied to each successively.
  24. # If 0 < lag_ratio < 1, its applied to each with lagged start times
  25. lag_ratio: float = DEFAULT_ANIMATION_LAG_RATIO,
  26. rate_func: Callable[[float], float] = smooth,
  27. name: str = "",
  28. # Does this animation add or remove a mobject form the screen
  29. remover: bool = False,
  30. # What to enter into the update function upon completion
  31. final_alpha_value: float = 1.0,
  32. # If set to True, the mobject itself will have its internal updaters called,
  33. # but the start or target mobjects would not be suspended. To completely suspend
  34. # updating, call mobject.suspend_updating() before the animation
  35. suspend_mobject_updating: bool = False,
  36. ):
  37. self.mobject = mobject
  38. self.run_time = run_time
  39. self.time_span = time_span
  40. self.rate_func = rate_func
  41. self.name = name or self.__class__.__name__ + str(self.mobject)
  42. self.remover = remover
  43. self.final_alpha_value = final_alpha_value
  44. self.lag_ratio = lag_ratio
  45. self.suspend_mobject_updating = suspend_mobject_updating
  46. assert isinstance(mobject, Mobject)
  47. def __str__(self) -> str:
  48. return self.name
  49. def begin(self) -> None:
  50. # This is called right as an animation is being
  51. # played. As much initialization as possible,
  52. # especially any mobject copying, should live in
  53. # this method
  54. if self.time_span is not None:
  55. start, end = self.time_span
  56. self.run_time = max(end, self.run_time)
  57. self.mobject.set_animating_status(True)
  58. self.starting_mobject = self.create_starting_mobject()
  59. if self.suspend_mobject_updating:
  60. self.mobject_was_updating = not self.mobject.updating_suspended
  61. self.mobject.suspend_updating()
  62. self.families = list(self.get_all_families_zipped())
  63. self.interpolate(0)
  64. def finish(self) -> None:
  65. self.interpolate(self.final_alpha_value)
  66. self.mobject.set_animating_status(False)
  67. if self.suspend_mobject_updating and self.mobject_was_updating:
  68. self.mobject.resume_updating()
  69. def clean_up_from_scene(self, scene: Scene) -> None:
  70. if self.is_remover():
  71. scene.remove(self.mobject)
  72. def create_starting_mobject(self) -> Mobject:
  73. # Keep track of where the mobject starts
  74. return self.mobject.copy()
  75. def get_all_mobjects(self) -> tuple[Mobject, Mobject]:
  76. """
  77. Ordering must match the ording of arguments to interpolate_submobject
  78. """
  79. return self.mobject, self.starting_mobject
  80. def get_all_families_zipped(self) -> zip[tuple[Mobject]]:
  81. return zip(*[
  82. mob.get_family()
  83. for mob in self.get_all_mobjects()
  84. ])
  85. def update_mobjects(self, dt: float) -> None:
  86. """
  87. Updates things like starting_mobject, and (for
  88. Transforms) target_mobject.
  89. """
  90. for mob in self.get_all_mobjects_to_update():
  91. mob.update(dt)
  92. def get_all_mobjects_to_update(self) -> list[Mobject]:
  93. # The surrounding scene typically handles
  94. # updating of self.mobject.
  95. items = list(filter(
  96. lambda m: m is not self.mobject,
  97. self.get_all_mobjects()
  98. ))
  99. items = remove_list_redundancies(items)
  100. return items
  101. def copy(self):
  102. return deepcopy(self)
  103. def update_rate_info(
  104. self,
  105. run_time: float | None = None,
  106. rate_func: Callable[[float], float] | None = None,
  107. lag_ratio: float | None = None,
  108. ):
  109. self.run_time = run_time or self.run_time
  110. self.rate_func = rate_func or self.rate_func
  111. self.lag_ratio = lag_ratio or self.lag_ratio
  112. return self
  113. # Methods for interpolation, the mean of an Animation
  114. def interpolate(self, alpha: float) -> None:
  115. self.interpolate_mobject(alpha)
  116. def update(self, alpha: float) -> None:
  117. """
  118. This method shouldn't exist, but it's here to
  119. keep many old scenes from breaking
  120. """
  121. self.interpolate(alpha)
  122. def time_spanned_alpha(self, alpha: float) -> float:
  123. if self.time_span is not None:
  124. start, end = self.time_span
  125. return clip(alpha * self.run_time - start, 0, end - start) / (end - start)
  126. return alpha
  127. def interpolate_mobject(self, alpha: float) -> None:
  128. for i, mobs in enumerate(self.families):
  129. sub_alpha = self.get_sub_alpha(self.time_spanned_alpha(alpha), i, len(self.families))
  130. self.interpolate_submobject(*mobs, sub_alpha)
  131. def interpolate_submobject(
  132. self,
  133. submobject: Mobject,
  134. starting_submobject: Mobject,
  135. alpha: float
  136. ):
  137. # Typically ipmlemented by subclass
  138. pass
  139. def get_sub_alpha(
  140. self,
  141. alpha: float,
  142. index: int,
  143. num_submobjects: int
  144. ) -> float:
  145. # TODO, make this more understanable, and/or combine
  146. # its functionality with AnimationGroup's method
  147. # build_animations_with_timings
  148. lag_ratio = self.lag_ratio
  149. full_length = (num_submobjects - 1) * lag_ratio + 1
  150. value = alpha * full_length
  151. lower = index * lag_ratio
  152. raw_sub_alpha = clip((value - lower), 0, 1)
  153. return self.rate_func(raw_sub_alpha)
  154. # Getters and setters
  155. def set_run_time(self, run_time: float):
  156. self.run_time = run_time
  157. return self
  158. def get_run_time(self) -> float:
  159. if self.time_span:
  160. return max(self.run_time, self.time_span[1])
  161. return self.run_time
  162. def set_rate_func(self, rate_func: Callable[[float], float]):
  163. self.rate_func = rate_func
  164. return self
  165. def get_rate_func(self) -> Callable[[float], float]:
  166. return self.rate_func
  167. def set_name(self, name: str):
  168. self.name = name
  169. return self
  170. def is_remover(self) -> bool:
  171. return self.remover
  172. def prepare_animation(anim: Animation | _AnimationBuilder):
  173. if isinstance(anim, _AnimationBuilder):
  174. return anim.build()
  175. if isinstance(anim, Animation):
  176. return anim
  177. raise TypeError(f"Object {anim} cannot be converted to an animation")