1111from ..mobject .mobject import Mobject
1212from ..mobject .opengl import opengl_mobject
1313from ..utils .rate_functions import linear , smooth
14+ from .protocol import AnimationProtocol
15+ from .scene_buffer import SceneBuffer
1416
1517__all__ = ["Animation" , "Wait" , "override_animation" ]
1618
17-
1819from copy import deepcopy
19- from typing import TYPE_CHECKING , Callable , Iterable , Sequence
20+ from typing import TYPE_CHECKING , Callable , Iterable , Sequence , TypeVar
2021
2122if TYPE_CHECKING :
22- from manim . scene . scene import Scene
23+ from typing_extensions import Self
2324
2425
2526DEFAULT_ANIMATION_RUN_TIME : float = 1.0
2627DEFAULT_ANIMATION_LAG_RATIO : float = 0.0
2728
2829
29- class Animation :
30+ class Animation ( AnimationProtocol ) :
3031 """An animation.
3132
3233 Animations have a fixed time span.
@@ -127,17 +128,17 @@ def __new__(
127128
128129 def __init__ (
129130 self ,
130- mobject : Mobject | None ,
131+ mobject : OpenGLMobject | None ,
131132 lag_ratio : float = DEFAULT_ANIMATION_LAG_RATIO ,
132133 run_time : float = DEFAULT_ANIMATION_RUN_TIME ,
133134 rate_func : Callable [[float ], float ] = smooth ,
134135 reverse_rate_function : bool = False ,
135- name : str = None ,
136- remover : bool = False , # remove a mobject from the screen?
136+ name : str | None = None ,
137+ remover : bool = False , # remove a mobject from the screen at end of animation
137138 suspend_mobject_updating : bool = True ,
138139 introducer : bool = False ,
139140 * ,
140- _on_finish : Callable [[], None ] = lambda _ : None ,
141+ _on_finish : Callable [[SceneBuffer ], object ] = lambda _ : None ,
141142 ** kwargs ,
142143 ) -> None :
143144 self ._typecheck_input (mobject )
@@ -149,15 +150,15 @@ def __init__(
149150 self .introducer : bool = introducer
150151 self .suspend_mobject_updating : bool = suspend_mobject_updating
151152 self .lag_ratio : float = lag_ratio
152- self ._on_finish : Callable [[ Scene ], None ] = _on_finish
153- if config [ "renderer" ] == RendererType . OPENGL :
154- self .starting_mobject : OpenGLMobject = OpenGLMobject ()
155- self . mobject : OpenGLMobject = (
156- mobject if mobject is not None else OpenGLMobject ()
157- )
158- else :
159- self . starting_mobject : Mobject = Mobject ()
160- self . mobject : Mobject = mobject if mobject is not None else Mobject ( )
153+ self ._on_finish = _on_finish
154+
155+ self .buffer = SceneBuffer ()
156+ self . apply_buffer = False # ask scene to apply buffer
157+
158+ self . starting_mobject : OpenGLMobject = OpenGLMobject ( )
159+ self . mobject : OpenGLMobject = (
160+ mobject if mobject is not None else OpenGLMobject ()
161+ )
161162 if kwargs :
162163 logger .debug ("Animation received extra kwargs: %s" , kwargs )
163164
@@ -169,7 +170,7 @@ def __init__(
169170 ),
170171 )
171172
172- def _typecheck_input (self , mobject : Mobject | None ) -> None :
173+ def _typecheck_input (self , mobject : Mobject | OpenGLMobject | None ) -> None :
173174 if mobject is None :
174175 logger .debug ("Animation with empty mobject" )
175176 elif not isinstance (mobject , (Mobject , OpenGLMobject )):
@@ -213,10 +214,12 @@ def begin(self) -> None:
213214 self .mobject .suspend_updating ()
214215 self .interpolate (0 )
215216
217+ # TODO: Figure out a way to check
218+ # if self.mobject in scene.get_mobject_family
219+ if self .is_introducer ():
220+ self .buffer .add (self .mobject )
221+
216222 def finish (self ) -> None :
217- # TODO: begin and finish should require a scene as parameter.
218- # That way Animation.clean_up_from_screen and Scene.add_mobjects_from_animations
219- # could be removed as they fulfill basically the same purpose.
220223 """Finish the animation.
221224
222225 This method gets called when the animation is over.
@@ -226,39 +229,9 @@ def finish(self) -> None:
226229 if self .suspend_mobject_updating and self .mobject is not None :
227230 self .mobject .resume_updating ()
228231
229- def clean_up_from_scene (self , scene : Scene ) -> None :
230- """Clean up the :class:`~.Scene` after finishing the animation.
231-
232- This includes to :meth:`~.Scene.remove` the Animation's
233- :class:`~.Mobject` if the animation is a remover.
234-
235- Parameters
236- ----------
237- scene
238- The scene the animation should be cleaned up from.
239- """
240- self ._on_finish (scene )
232+ self ._on_finish (self .buffer )
241233 if self .is_remover ():
242- scene .remove (self .mobject )
243-
244- def _setup_scene (self , scene : Scene ) -> None :
245- """Setup up the :class:`~.Scene` before starting the animation.
246-
247- This includes to :meth:`~.Scene.add` the Animation's
248- :class:`~.Mobject` if the animation is an introducer.
249-
250- Parameters
251- ----------
252- scene
253- The scene the animation should be cleaned up from.
254- """
255- if scene is None :
256- return
257- if (
258- self .is_introducer ()
259- and self .mobject not in scene .get_mobject_family_members ()
260- ):
261- scene .add (self .mobject )
234+ self .buffer .remove (self .mobject )
262235
263236 def create_starting_mobject (self ) -> Mobject :
264237 # Keep track of where the mobject starts
@@ -294,6 +267,17 @@ def update_mobjects(self, dt: float) -> None:
294267 for mob in self .get_all_mobjects_to_update ():
295268 mob .update (dt )
296269
270+ def process_subanimation_buffer (self , buffer : SceneBuffer ):
271+ """
272+ This is used in animations that are proxies around
273+ other animations, like :class:`.AnimationGroup`
274+ """
275+ self .buffer .remove (* buffer .to_remove )
276+ for to_replace_pairs in buffer .to_replace :
277+ self .buffer .replace (* to_replace_pairs )
278+ self .buffer .add (* buffer .to_add )
279+ buffer .clear ()
280+
297281 def get_all_mobjects_to_update (self ) -> list [Mobject ]:
298282 """Get all mobjects to be updated during the animation.
299283
@@ -305,9 +289,9 @@ def get_all_mobjects_to_update(self) -> list[Mobject]:
305289 # The surrounding scene typically handles
306290 # updating of self.mobject. Besides, in
307291 # most cases its updating is suspended anyway
308- return list ( filter ( lambda m : m is not self .mobject , self . get_all_mobjects ()))
292+ return [ m for m in self . get_all_mobjects () if m is not self .mobject ]
309293
310- def copy (self ) -> Animation :
294+ def copy (self ) -> Self :
311295 """Create a copy of the animation.
312296
313297 Returns
@@ -343,7 +327,7 @@ def interpolate_mobject(self, alpha: float) -> None:
343327 is completed. For example, alpha-values of 0, 0.5, and 1 correspond
344328 to the animation being completed 0%, 50%, and 100%, respectively.
345329 """
346- families = list (self .get_all_families_zipped ())
330+ families = tuple (self .get_all_families_zipped ())
347331 for i , mobs in enumerate (families ):
348332 sub_alpha = self .get_sub_alpha (alpha , i , len (families ))
349333 self .interpolate_submobject (* mobs , sub_alpha )
@@ -356,7 +340,7 @@ def interpolate_submobject(
356340 alpha : float ,
357341 ) -> Animation :
358342 # Typically implemented by subclass
359- pass
343+ raise NotImplementedError ()
360344
361345 def get_sub_alpha (self , alpha : float , index : int , num_submobjects : int ) -> float :
362346 """Get the animation progress of any submobjects subanimation.
@@ -422,7 +406,7 @@ def get_run_time(self) -> float:
422406 def set_rate_func (
423407 self ,
424408 rate_func : Callable [[float ], float ],
425- ) -> Animation :
409+ ) -> Self :
426410 """Set the rate function of the animation.
427411
428412 Parameters
@@ -451,7 +435,7 @@ def get_rate_func(
451435 """
452436 return self .rate_func
453437
454- def set_name (self , name : str ) -> Animation :
438+ def set_name (self , name : str ) -> Self :
455439 """Set the name of the animation.
456440
457441 Parameters
@@ -489,7 +473,9 @@ def is_introducer(self) -> bool:
489473
490474
491475def prepare_animation (
492- anim : Animation | mobject ._AnimationBuilder ,
476+ anim : AnimationProtocol
477+ | mobject ._AnimationBuilder
478+ | opengl_mobject ._AnimationBuilder ,
493479) -> Animation :
494480 r"""Returns either an unchanged animation, or the animation built
495481 from a passed animation factory.
@@ -517,10 +503,7 @@ def prepare_animation(
517503 TypeError: Object 42 cannot be converted to an animation
518504
519505 """
520- if isinstance (anim , mobject ._AnimationBuilder ):
521- return anim .build ()
522-
523- if isinstance (anim , opengl_mobject ._AnimationBuilder ):
506+ if isinstance (anim , (mobject ._AnimationBuilder , opengl_mobject ._AnimationBuilder )):
524507 return anim .build ()
525508
526509 if isinstance (anim , Animation ):
@@ -576,9 +559,6 @@ def begin(self) -> None:
576559 def finish (self ) -> None :
577560 pass
578561
579- def clean_up_from_scene (self , scene : Scene ) -> None :
580- pass
581-
582562 def update_mobjects (self , dt : float ) -> None :
583563 pass
584564
@@ -625,7 +605,9 @@ def construct(self):
625605
626606 """
627607
628- def decorator (func ):
608+ _F = TypeVar ("_F" , bound = Callable )
609+
610+ def decorator (func : _F ) -> _F :
629611 func ._override_animation = animation_class
630612 return func
631613
0 commit comments