mirror of
https://github.com/ManimCommunity/manim.git
synced 2026-06-22 10:01:47 +00:00
Add .animate attribute to mobjects (#881)
Adds a .animate attribute to mobjects to be used to animate methods.
This commit is contained in:
parent
33f20f4e27
commit
ffd8b42d26
16 changed files with 120 additions and 143 deletions
|
|
@ -204,10 +204,10 @@ Animations
|
|||
def construct(self):
|
||||
square = Square(color=BLUE, fill_opacity=1)
|
||||
|
||||
self.play(square.shift, LEFT)
|
||||
self.play(square.set_fill, ORANGE)
|
||||
self.play(square.scale, 0.3)
|
||||
self.play(square.rotate, 0.4)
|
||||
self.play(square.animate.shift(LEFT))
|
||||
self.play(square.animate.set_fill(ORANGE))
|
||||
self.play(square.animate.scale(0.3))
|
||||
self.play(square.animate.rotate(0.4))
|
||||
|
||||
.. manim:: MovingFrameBox
|
||||
:ref_modules: manim.mobject.svg.tex_mobject
|
||||
|
|
@ -268,8 +268,8 @@ Animations
|
|||
self.add(path, dot)
|
||||
self.play(Rotating(dot, radians=PI, about_point=RIGHT, run_time=2))
|
||||
self.wait()
|
||||
self.play(dot.shift, UP)
|
||||
self.play(dot.shift, LEFT)
|
||||
self.play(dot.animate.shift(UP))
|
||||
self.play(dot.animate.shift(LEFT))
|
||||
self.wait()
|
||||
|
||||
|
||||
|
|
@ -394,9 +394,9 @@ Special Camera Settings
|
|||
moving_dot = Dot().move_to(graph.points[0]).set_color(ORANGE)
|
||||
|
||||
dot_at_start_graph = Dot().move_to(graph.points[0])
|
||||
dot_at_end_grap = Dot().move_to(graph.points[-1])
|
||||
self.add(graph, dot_at_end_grap, dot_at_start_graph, moving_dot)
|
||||
self.play( self.camera_frame.scale,0.5,self.camera_frame.move_to,moving_dot)
|
||||
dot_at_end_graph = Dot().move_to(graph.points[-1])
|
||||
self.add(graph, dot_at_end_graph, dot_at_start_graph, moving_dot)
|
||||
self.play(self.camera_frame.animate.scale(0.5),self.camera_frame.animate.move_to(moving_dot))
|
||||
|
||||
def update_curve(mob):
|
||||
mob.move_to(moving_dot.get_center())
|
||||
|
|
@ -462,15 +462,15 @@ Special Camera Settings
|
|||
# Scale in x y z
|
||||
scale_factor = [0.5, 1.5, 0]
|
||||
self.play(
|
||||
frame.scale, scale_factor,
|
||||
zoomed_display.scale, scale_factor,
|
||||
frame.animate.scale(scale_factor),
|
||||
zoomed_display.animate.scale(scale_factor),
|
||||
FadeOut(zoomed_camera_text),
|
||||
FadeOut(frame_text)
|
||||
)
|
||||
self.wait()
|
||||
self.play(ScaleInPlace(zoomed_display, 2))
|
||||
self.wait()
|
||||
self.play(frame.shift, 2.5 * DOWN)
|
||||
self.play(frame.animate.shift(2.5 * DOWN))
|
||||
self.wait()
|
||||
self.play(self.get_zoomed_display_pop_out_animation(), unfold_camera, rate_func=lambda t: smooth(1 - t))
|
||||
self.play(Uncreate(zoomed_display_frame), FadeOut(frame))
|
||||
|
|
@ -640,14 +640,15 @@ Advanced Projects
|
|||
grid_transform_title.move_to(grid_title, UL)
|
||||
grid.prepare_for_nonlinear_transform()
|
||||
self.play(
|
||||
grid.apply_function,
|
||||
lambda p: p
|
||||
+ np.array(
|
||||
[
|
||||
np.sin(p[1]),
|
||||
np.sin(p[0]),
|
||||
0,
|
||||
]
|
||||
grid.animate.apply_function(
|
||||
lambda p: p
|
||||
+ np.array(
|
||||
[
|
||||
np.sin(p[1]),
|
||||
np.sin(p[0]),
|
||||
0,
|
||||
]
|
||||
)
|
||||
),
|
||||
run_time=3,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -52,14 +52,15 @@ class OpeningManimExample(Scene):
|
|||
grid_transform_title.move_to(grid_title, UL)
|
||||
grid.prepare_for_nonlinear_transform()
|
||||
self.play(
|
||||
grid.apply_function,
|
||||
lambda p: p
|
||||
+ np.array(
|
||||
[
|
||||
np.sin(p[1]),
|
||||
np.sin(p[0]),
|
||||
0,
|
||||
]
|
||||
grid.animate.apply_function(
|
||||
lambda p: p
|
||||
+ np.array(
|
||||
[
|
||||
np.sin(p[1]),
|
||||
np.sin(p[0]),
|
||||
0,
|
||||
]
|
||||
)
|
||||
),
|
||||
run_time=3,
|
||||
)
|
||||
|
|
@ -121,8 +122,7 @@ class UpdatersExample(Scene):
|
|||
decimal.add_updater(lambda d: d.set_value(square.get_center()[1]))
|
||||
self.add(square, decimal)
|
||||
self.play(
|
||||
square.to_edge,
|
||||
DOWN,
|
||||
square.animate.to_edge(DOWN),
|
||||
rate_func=there_and_back,
|
||||
run_time=5,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -128,10 +128,6 @@ class Animation:
|
|||
def copy(self) -> "Animation":
|
||||
return deepcopy(self)
|
||||
|
||||
def update_config(self, **kwargs: typing.Dict[str, typing.Any]) -> "Animation":
|
||||
self.__dict__.update(kwargs)
|
||||
return self
|
||||
|
||||
# Methods for interpolation, the mean of an Animation
|
||||
def interpolate(self, alpha: float) -> None:
|
||||
alpha = np.clip(alpha, 0, 1)
|
||||
|
|
|
|||
|
|
@ -188,6 +188,11 @@ class MoveToTarget(Transform):
|
|||
)
|
||||
|
||||
|
||||
class _MethodAnimation(MoveToTarget):
|
||||
def __init__(self, mobject):
|
||||
super().__init__(mobject)
|
||||
|
||||
|
||||
class ApplyMethod(Transform):
|
||||
def __init__(
|
||||
self, method: types.MethodType, *args, **kwargs
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ class TracedPath(VMobject):
|
|||
trace = TracedPath(circ.get_start)
|
||||
rolling_circle.add_updater(lambda m: m.rotate(-0.3))
|
||||
self.add(trace, rolling_circle)
|
||||
self.play(rolling_circle.shift, 8*RIGHT, run_time=4, rate_func=linear)
|
||||
self.play(rolling_circle.animate.shift(8*RIGHT), run_time=4, rate_func=linear)
|
||||
|
||||
"""
|
||||
|
||||
|
|
|
|||
|
|
@ -57,6 +57,17 @@ class Mobject(Container):
|
|||
self.init_colors()
|
||||
Container.__init__(self, **kwargs)
|
||||
|
||||
@property
|
||||
def animate(self):
|
||||
"""
|
||||
Used to animate the application of a method.
|
||||
|
||||
Examples
|
||||
--------
|
||||
self.play(mobject.animate.shift(RIGHT))
|
||||
"""
|
||||
return _AnimationBuilder(self)
|
||||
|
||||
def __repr__(self):
|
||||
return str(self.name)
|
||||
|
||||
|
|
@ -1295,3 +1306,19 @@ class Group(Mobject):
|
|||
def __init__(self, *mobjects, **kwargs):
|
||||
Mobject.__init__(self, **kwargs)
|
||||
self.add(*mobjects)
|
||||
|
||||
|
||||
class _AnimationBuilder:
|
||||
def __init__(self, mobject):
|
||||
self.mobject = mobject
|
||||
|
||||
def __getattr__(self, method_name):
|
||||
from ..animation.transform import _MethodAnimation
|
||||
|
||||
self.method = getattr(self.mobject.generate_target(), method_name)
|
||||
|
||||
def build(*method_args, **method_kwargs):
|
||||
self.method(*method_args, **method_kwargs)
|
||||
return _MethodAnimation(self.mobject)
|
||||
|
||||
return build
|
||||
|
|
|
|||
|
|
@ -31,8 +31,7 @@ class DecimalNumber(VMobject):
|
|||
decimal.add_updater(lambda d: d.set_value(square.get_center()[1]))
|
||||
self.add(square, decimal)
|
||||
self.play(
|
||||
square.to_edge,
|
||||
DOWN,
|
||||
square.animate.to_edge(DOWN),
|
||||
rate_func=there_and_back,
|
||||
run_time=5,
|
||||
)
|
||||
|
|
@ -261,7 +260,7 @@ class Variable(VMobject):
|
|||
self.wait()
|
||||
var_tracker = on_screen_var.tracker
|
||||
var = 10.5
|
||||
self.play(var_tracker.set_value, var)
|
||||
self.play(var_tracker.animate.set_value(var))
|
||||
self.wait()
|
||||
|
||||
int_var = 0
|
||||
|
|
@ -275,7 +274,7 @@ class Variable(VMobject):
|
|||
self.wait()
|
||||
var_tracker = on_screen_int_var.tracker
|
||||
var = 10.5
|
||||
self.play(var_tracker.set_value, var)
|
||||
self.play(var_tracker.animate.set_value(var))
|
||||
self.wait()
|
||||
|
||||
# If you wish to have a somewhat more complicated label for your
|
||||
|
|
|
|||
|
|
@ -1063,18 +1063,18 @@ class VGroup(VMobject):
|
|||
self.wait()
|
||||
gr += gr2 # Add group to another
|
||||
self.play(
|
||||
gr.shift, DOWN,
|
||||
gr.animate.shift(DOWN),
|
||||
)
|
||||
gr -= gr2 # Remove group
|
||||
self.play( # Animate groups separately
|
||||
gr.shift, LEFT,
|
||||
gr2.shift, UP,
|
||||
gr.animate.shift(LEFT),
|
||||
gr2.animate.shift(UP),
|
||||
)
|
||||
self.play( #Animate groups without modification
|
||||
(gr+gr2).shift, RIGHT
|
||||
(gr+gr2).animate.shift(RIGHT)
|
||||
)
|
||||
self.play( # Animate group without component
|
||||
(gr-circle_red).shift, RIGHT
|
||||
(gr-circle_red).animate.shift(RIGHT)
|
||||
)
|
||||
"""
|
||||
if not all(isinstance(m, VMobject) for m in vmobjects):
|
||||
|
|
@ -1156,7 +1156,7 @@ class VDict(VMobject):
|
|||
|
||||
# access submobjects like a python dict
|
||||
my_dict["t"].set_color(PURPLE)
|
||||
self.play(my_dict["t"].scale, 3)
|
||||
self.play(my_dict["t"].animate.scale(3))
|
||||
self.wait()
|
||||
|
||||
# also supports python dict styled reassignment
|
||||
|
|
|
|||
|
|
@ -36,9 +36,9 @@ class ValueTracker(Mobject):
|
|||
)
|
||||
)
|
||||
self.add(number_line, pointer,label)
|
||||
self.play(pointer_value.set_value, 5)
|
||||
self.play(pointer_value.animate.set_value(5)),
|
||||
self.wait()
|
||||
self.play(pointer_value.set_value, 3)
|
||||
self.play(pointer_value.animate.set_value(3))
|
||||
|
||||
"""
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ Examples
|
|||
text = Text("Hello World").set_color(BLUE)
|
||||
self.add(text)
|
||||
self.camera_frame.save_state()
|
||||
self.play(self.camera_frame.set_width, text.get_width() * 1.2)
|
||||
self.play(self.camera_frame.animate.set_width(text.get_width() * 1.2))
|
||||
self.wait(0.3)
|
||||
self.play(Restore(self.camera_frame))
|
||||
|
||||
|
|
@ -28,9 +28,9 @@ Examples
|
|||
t = Triangle(color=GREEN, fill_opacity=0.5).move_to(2 * RIGHT)
|
||||
self.wait(0.3)
|
||||
self.add(s, t)
|
||||
self.play(self.camera_frame.move_to, s)
|
||||
self.play(self.camera_frame.animate.move_to(s))
|
||||
self.wait(0.3)
|
||||
self.play(self.camera_frame.move_to, t)
|
||||
self.play(self.camera_frame.animate.move_to(t))
|
||||
|
||||
|
||||
.. manim:: MovingAndZoomingCamera
|
||||
|
|
@ -40,14 +40,14 @@ Examples
|
|||
s = Square(color=BLUE, fill_opacity=0.5).move_to(2 * LEFT)
|
||||
t = Triangle(color=YELLOW, fill_opacity=0.5).move_to(2 * RIGHT)
|
||||
self.add(s, t)
|
||||
self.play(self.camera_frame.move_to, s,
|
||||
self.camera_frame.set_width,s.get_width()*2)
|
||||
self.play(self.camera_frame.animate.move_to(s),
|
||||
self.camera_frame.animate.set_width(s.get_width()*2))
|
||||
self.wait(0.3)
|
||||
self.play(self.camera_frame.move_to, t,
|
||||
self.camera_frame.set_width,t.get_width()*2)
|
||||
self.play(self.camera_frame.animate.move_to(t),
|
||||
self.camera_frame.animate.set_width(t.get_width()*2))
|
||||
|
||||
self.play(self.camera_frame.move_to, ORIGIN,
|
||||
self.camera_frame.set_width,14)
|
||||
self.play(self.camera_frame.animate.move_to(ORIGIN),
|
||||
self.camera_frame.animate.set_width(14))
|
||||
|
||||
.. manim:: MovingCameraOnGraph
|
||||
|
||||
|
|
@ -64,10 +64,10 @@ Examples
|
|||
x_max=3 * PI
|
||||
)
|
||||
dot_at_start_graph = Dot().move_to(graph.points[0])
|
||||
dot_at_end_grap = Dot().move_to(graph.points[-1])
|
||||
self.add(graph, dot_at_end_grap, dot_at_start_graph)
|
||||
self.play(self.camera_frame.scale, 0.5, self.camera_frame.move_to, dot_at_start_graph)
|
||||
self.play(self.camera_frame.move_to, dot_at_end_grap)
|
||||
dot_at_end_graph = Dot().move_to(graph.points[-1])
|
||||
self.add(graph, dot_at_end_graph, dot_at_start_graph)
|
||||
self.play(self.camera_frame.animate.scale(0.5), self.camera_frame.animate.move_to(dot_at_start_graph))
|
||||
self.play(self.camera_frame.animate.move_to(dot_at_end_graph))
|
||||
self.play(Restore(self.camera_frame))
|
||||
self.wait()
|
||||
|
||||
|
|
|
|||
|
|
@ -17,11 +17,11 @@ import numpy as np
|
|||
|
||||
from .. import config, logger
|
||||
from ..animation.animation import Animation, Wait
|
||||
from ..animation.transform import MoveToTarget
|
||||
from ..animation.transform import MoveToTarget, _MethodAnimation
|
||||
from ..camera.camera import Camera
|
||||
from ..constants import *
|
||||
from ..container import Container
|
||||
from ..mobject.mobject import Mobject
|
||||
from ..mobject.mobject import Mobject, _AnimationBuilder
|
||||
from ..utils.iterables import list_update, list_difference_update
|
||||
from ..utils.family import extract_mobject_family_members
|
||||
from ..renderer.cairo_renderer import CairoRenderer
|
||||
|
|
@ -596,83 +596,32 @@ class Scene(Container):
|
|||
)
|
||||
return all_moving_mobject_families, static_mobjects
|
||||
|
||||
def compile_play_args_to_animation_list(self, *args, **kwargs):
|
||||
def compile_animations(self, *animations, **play_kwargs):
|
||||
"""
|
||||
Each arg can either be an animation, or a mobject method
|
||||
followed by that methods arguments (and potentially follow
|
||||
by a dict of kwargs for that method).
|
||||
This animation list is built by going through the args list,
|
||||
and each animation is simply added, but when a mobject method
|
||||
is hit, a MoveToTarget animation is built using the args that
|
||||
follow up until either another animation is hit, another method
|
||||
is hit, or the args list runs out.
|
||||
Creates _MethodAnimations from any _AnimationBuilders and updates animation
|
||||
kwargs with kwargs passed to play().
|
||||
|
||||
Parameters
|
||||
----------
|
||||
*args : Animation or method of a mobject, which is followed by that method's arguments
|
||||
*animations : Tuple[:class:`Animation`]
|
||||
Animations to be played.
|
||||
|
||||
**kwargs : any named arguments like run_time or lag_ratio.
|
||||
**play_kwargs
|
||||
Configuration for the call to play().
|
||||
|
||||
Returns
|
||||
-------
|
||||
list : list of animations with the parameters applied to them.
|
||||
Tuple[:class:`Animation`]
|
||||
Animations to be played.
|
||||
"""
|
||||
animations = []
|
||||
state = {
|
||||
"curr_method": None,
|
||||
"last_method": None,
|
||||
"method_args": [],
|
||||
}
|
||||
|
||||
def compile_method(state):
|
||||
if state["curr_method"] is None:
|
||||
return
|
||||
mobject = state["curr_method"].__self__
|
||||
if state["last_method"] and state["last_method"].__self__ is mobject:
|
||||
animations.pop()
|
||||
# method should already have target then.
|
||||
else:
|
||||
mobject.generate_target()
|
||||
#
|
||||
if len(state["method_args"]) > 0 and isinstance(
|
||||
state["method_args"][-1], dict
|
||||
):
|
||||
method_kwargs = state["method_args"].pop()
|
||||
else:
|
||||
method_kwargs = {}
|
||||
state["curr_method"].__func__(
|
||||
mobject.target, *state["method_args"], **method_kwargs
|
||||
)
|
||||
animations.append(MoveToTarget(mobject))
|
||||
state["last_method"] = state["curr_method"]
|
||||
state["curr_method"] = None
|
||||
state["method_args"] = []
|
||||
|
||||
for arg in args:
|
||||
if isinstance(arg, Animation):
|
||||
compile_method(state)
|
||||
animations.append(arg)
|
||||
elif inspect.ismethod(arg):
|
||||
compile_method(state)
|
||||
state["curr_method"] = arg
|
||||
elif state["curr_method"] is not None:
|
||||
state["method_args"].append(arg)
|
||||
elif isinstance(arg, Mobject):
|
||||
raise ValueError(
|
||||
"""
|
||||
I think you may have invoked a method
|
||||
you meant to pass in as a Scene.play argument
|
||||
"""
|
||||
)
|
||||
else:
|
||||
raise ValueError("Invalid play arguments")
|
||||
compile_method(state)
|
||||
|
||||
for animation in animations:
|
||||
# This is where kwargs to play like run_time and rate_func
|
||||
# get applied to all animations
|
||||
animation.update_config(**kwargs)
|
||||
|
||||
if inspect.ismethod(animation):
|
||||
raise TypeError(
|
||||
"Passing mobject methods to Scene.play is no longer supported. Use "
|
||||
"Mobject.animate instead."
|
||||
)
|
||||
for k, v in play_kwargs.items():
|
||||
setattr(animation, k, v)
|
||||
return animations
|
||||
|
||||
def _get_animation_time_progression(self, animations, duration):
|
||||
|
|
@ -814,15 +763,15 @@ class Scene(Container):
|
|||
"""
|
||||
self.wait(max_time, stop_condition=stop_condition)
|
||||
|
||||
def compile_animation_data(self, *args, skip_rendering=False, **kwargs):
|
||||
if len(args) == 0:
|
||||
def compile_animation_data(self, *animations, skip_rendering=False, **play_kwargs):
|
||||
if len(animations) == 0:
|
||||
warnings.warn("Called Scene.play with no animations")
|
||||
return None
|
||||
|
||||
self.last_t = 0
|
||||
self.animations = self.compile_play_args_to_animation_list(*args, **kwargs)
|
||||
self.animations = self.compile_animations(*animations, **play_kwargs)
|
||||
self.add_mobjects_from_animations(self.animations)
|
||||
|
||||
self.last_t = 0
|
||||
self.stop_condition = None
|
||||
self.moving_mobjects = None
|
||||
self.static_mobjects = None
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ Examples
|
|||
self.wait(1)
|
||||
self.activate_zooming(animate=False)
|
||||
self.wait(1)
|
||||
self.play(dot.shift, LEFT)
|
||||
self.play(dot.animate.shift(LEFT))
|
||||
|
||||
.. manim:: ChangingZoomScale
|
||||
|
||||
|
|
@ -38,10 +38,10 @@ Examples
|
|||
self.wait(1)
|
||||
self.activate_zooming(animate=False)
|
||||
self.wait(1)
|
||||
self.play(dot.shift, LEFT * 0.3)
|
||||
self.play(dot.animate.shift(LEFT * 0.3))
|
||||
|
||||
self.play(self.zoomed_camera.frame.scale, 4)
|
||||
self.play(self.zoomed_camera.frame.shift, 0.5 * DOWN)
|
||||
self.play(self.zoomed_camera.frame.animate.scale(4))
|
||||
self.play(self.zoomed_camera.frame.animate.shift(0.5 * DOWN))
|
||||
|
||||
"""
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ def handle_caching_play(func):
|
|||
def wrapper(self, scene, *args, **kwargs):
|
||||
self.skip_animations = self.original_skipping_status
|
||||
self.update_skipping_status()
|
||||
animations = scene.compile_play_args_to_animation_list(*args, **kwargs)
|
||||
animations = scene.compile_animations(*args, **kwargs)
|
||||
scene.add_mobjects_from_animations(animations)
|
||||
if self.skip_animations:
|
||||
logger.debug(f"Skipping animation {self.num_plays}")
|
||||
|
|
|
|||
|
|
@ -43,13 +43,13 @@ class RotateTest(Scene):
|
|||
class MoveToTest(Scene):
|
||||
def construct(self):
|
||||
square = Square()
|
||||
self.play(square.move_to, np.array([1.0, 1.0, 0.0]))
|
||||
self.play(square.animate.move_to(np.array([1.0, 1.0, 0.0])))
|
||||
|
||||
|
||||
class ShiftTest(Scene):
|
||||
def construct(self):
|
||||
square = Square()
|
||||
self.play(square.shift, UP)
|
||||
self.play(square.animate.shift(UP))
|
||||
|
||||
|
||||
MODULE_NAME = "movements"
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ class RestoreTest(Scene):
|
|||
circle = Circle()
|
||||
self.play(Transform(square, circle))
|
||||
square.save_state()
|
||||
self.play(square.shift, UP)
|
||||
self.play(square.animate.shift(UP))
|
||||
self.play(Restore(square))
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ class UpdaterTest(Scene):
|
|||
self.add(dot, square)
|
||||
square.add_updater(lambda m: m.next_to(dot, RIGHT, buff=SMALL_BUFF))
|
||||
self.add(square)
|
||||
self.play(dot.shift, UP * 2)
|
||||
self.play(dot.animate.shift(UP * 2))
|
||||
square.clear_updaters()
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue