Merge pull request #1379 from friedkeenan/3b1b-animate

Add .animate syntax
This commit is contained in:
Grant Sanderson 2021-02-10 13:43:08 -08:00 committed by GitHub
commit 4ff876b536
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 223 additions and 133 deletions

View file

@ -12,11 +12,11 @@ class SquareToCircle(Scene):
self.play(ReplacementTransform(square, circle)) self.play(ReplacementTransform(square, circle))
self.wait() self.wait()
# Try typing the following lines # Try typing the following lines
# self.play(circle.stretch, 4, {"dim": 0}) # self.play(circle.animate.stretch(4, dim=0))
# self.play(Rotate(circle, TAU / 4)) # self.play(Rotate(circle, TAU / 4))
# self.play(circle.shift, 2 * RIGHT, circle.scale, 0.25) # self.play(circle.animate.shift(2 * RIGHT), circle.animate.scale(0.25))
# circle.insert_n_curves(10) # circle.insert_n_curves(10)
# self.play(circle.apply_complex_function, lambda z: z**2) # self.play(circle.animate.apply_complex_function(lambda z: z**2))
class SquareToCircleEmbed(Scene): class SquareToCircleEmbed(Scene):
def construct(self): def construct(self):
@ -26,12 +26,12 @@ class SquareToCircleEmbed(Scene):
self.add(circle) self.add(circle)
self.wait() self.wait()
self.play(circle.stretch, 4, {"dim": 0}) self.play(circle.animate.stretch(4, dim=0))
self.wait(1.5) self.wait(1.5)
self.play(Rotate(circle, TAU / 4)) self.play(Rotate(circle, TAU / 4))
self.wait(1.5) self.wait(1.5)
self.play(circle.shift, 2 * RIGHT, circle.scale, 0.25) self.play(circle.animate.shift(2 * RIGHT), circle.animate.scale(0.25))
self.wait(1.5) self.wait(1.5)
circle.insert_n_curves(10) circle.insert_n_curves(10)
self.play(circle.apply_complex_function, lambda z: z**2) self.play(circle.animate.apply_complex_function(lambda z: z**2))
self.wait(2) self.wait(2)

View file

@ -1,8 +1,8 @@
Example Scenes Example Scenes
============== ==============
After understanding the previous knowledge, we can understand more scenes. After understanding the previous knowledge, we can understand more scenes.
Many example scenes are given in ``example_scenes.py``, let's start with Many example scenes are given in ``example_scenes.py``, let's start with
the simplest and one by one. the simplest and one by one.
InteractiveDevlopment InteractiveDevlopment
@ -33,9 +33,9 @@ InteractiveDevlopment
# the interactive shell # the interactive shell
self.play(ReplacementTransform(square, circle)) self.play(ReplacementTransform(square, circle))
self.wait() self.wait()
self.play(circle.stretch, 4, 0) self.play(circle.animate.stretch(4, 0))
self.play(Rotate(circle, 90 * DEGREES)) self.play(Rotate(circle, 90 * DEGREES))
self.play(circle.shift, 2 * RIGHT, circle.scale, 0.25) self.play(circle.animate.shift(2 * RIGHT), circle.animate.scale(0.25))
text = Text(""" text = Text("""
In general, using the interactive shell In general, using the interactive shell
@ -73,43 +73,52 @@ AnimatingMethods
grid = Tex(r"\pi").get_grid(10, 10, height=4) grid = Tex(r"\pi").get_grid(10, 10, height=4)
self.add(grid) self.add(grid)
# If you pass in a mobject method to the scene's "play" function, # You can animate the application of mobject methods with the
# it will apply an animation interpolating between the mobject's # ".animate" syntax:
# initial state and whatever happens when you apply that method. self.play(grid.animate.shift(LEFT))
# For example, calling grid.shift(2 * LEFT) would shift it two units
# to the left, but the following line animates that motion. # Alternatively, you can use the older syntax by passing the
self.play(grid.shift, 2 * LEFT) # method and then the arguments to the scene's "play" function:
self.play(grid.shift, LEFT)
# Both of those will interpolate between the mobject's initial
# state and whatever happens when you apply that method.
# For this example, calling grid.shift(LEFT) would shift the
# grid one unit to the left, but both of the previous calls to
# "self.play" animate that motion.
# The same applies for any method, including those setting colors. # The same applies for any method, including those setting colors.
self.play(grid.set_color, YELLOW) self.play(grid.animate.set_color(YELLOW))
self.wait() self.wait()
self.play(grid.set_submobject_colors_by_gradient, BLUE, GREEN) self.play(grid.animate.set_submobject_colors_by_gradient(BLUE, GREEN))
self.wait() self.wait()
self.play(grid.set_height, TAU - MED_SMALL_BUFF) self.play(grid.animate.set_height(TAU - MED_SMALL_BUFF))
self.wait() self.wait()
# The method Mobject.apply_complex_function lets you apply arbitrary # The method Mobject.apply_complex_function lets you apply arbitrary
# complex functions, treating the points defining the mobject as # complex functions, treating the points defining the mobject as
# complex numbers. # complex numbers.
self.play(grid.apply_complex_function, np.exp, run_time=5) self.play(grid.animate.apply_complex_function(np.exp), run_time=5)
self.wait() self.wait()
# Even more generally, you could apply Mobject.apply_function, # Even more generally, you could apply Mobject.apply_function,
# which takes in functions form R^3 to R^3 # which takes in functions form R^3 to R^3
self.play( self.play(
grid.apply_function, grid.animate.apply_function(
lambda p: [ lambda p: [
p[0] + 0.5 * math.sin(p[1]), p[0] + 0.5 * math.sin(p[1]),
p[1] + 0.5 * math.sin(p[0]), p[1] + 0.5 * math.sin(p[0]),
p[2] p[2]
], ]
),
run_time=5, run_time=5,
) )
self.wait() self.wait()
The new usage in this scene is ``.get_grid()`` and ``self.play(mob.method, args)``. The new usage in this scene is ``.get_grid()`` and ``self.play(mob.animate.method(args))``.
- ``.get_grid()`` method will return a new mobject containing multiple copies of this one arranged in a grid. - ``.get_grid()`` method will return a new mobject containing multiple copies of this one arranged in a grid.
- ``self.play(mob.method, args)`` animate the method, and the details are in the comments above. - ``self.play(mob.animate.method(args))`` animates the method, and the details are in the comments above.
TextExample TextExample
----------- -----------
@ -328,18 +337,18 @@ UpdatersExample
# Notice that the brace and label track with the square # Notice that the brace and label track with the square
self.play( self.play(
square.scale, 2, square.animate.scale(2),
rate_func=there_and_back, rate_func=there_and_back,
run_time=2, run_time=2,
) )
self.wait() self.wait()
self.play( self.play(
square.set_width, 5, {"stretch": True}, square.set_width(5, stretch=True),
run_time=3, run_time=3,
) )
self.wait() self.wait()
self.play( self.play(
square.set_width, 2, square.animate.set_width(2),
run_time=3 run_time=3
) )
self.wait() self.wait()
@ -355,7 +364,7 @@ UpdatersExample
) )
self.wait(4 * PI) self.wait(4 * PI)
The new classes and usage in this scene are ``always_redraw()``, ``DecimalNumber``, ``.to_edge()``, The new classes and usage in this scene are ``always_redraw()``, ``DecimalNumber``, ``.to_edge()``,
``.center()``, ``always()``, ``f_always()``, ``.set_y()`` and ``.add_updater()``. ``.center()``, ``always()``, ``f_always()``, ``.set_y()`` and ``.add_updater()``.
- ``always_redraw()`` function create a new mobject every frame. - ``always_redraw()`` function create a new mobject every frame.
@ -412,9 +421,9 @@ CoordinateSystemExample
dot = Dot(color=RED) dot = Dot(color=RED)
dot.move_to(axes.c2p(0, 0)) dot.move_to(axes.c2p(0, 0))
self.play(FadeIn(dot, scale=0.5)) self.play(FadeIn(dot, scale=0.5))
self.play(dot.move_to, axes.c2p(3, 2)) self.play(dot.animate.move_to(axes.c2p(3, 2)))
self.wait() self.wait()
self.play(dot.move_to, axes.c2p(5, 0.5)) self.play(dot.animate.move_to(axes.c2p(5, 0.5)))
self.wait() self.wait()
# Similarly, you can call axes.point_to_coords, or axes.p2c # Similarly, you can call axes.point_to_coords, or axes.p2c
@ -431,9 +440,9 @@ CoordinateSystemExample
ShowCreation(h_line), ShowCreation(h_line),
ShowCreation(v_line), ShowCreation(v_line),
) )
self.play(dot.move_to, axes.c2p(3, -2)) self.play(dot.animate.move_to(axes.c2p(3, -2)))
self.wait() self.wait()
self.play(dot.move_to, axes.c2p(1, 1)) self.play(dot.animate.move_to(axes.c2p(1, 1)))
self.wait() self.wait()
# If we tie the dot to a particular set of coordinates, notice # If we tie the dot to a particular set of coordinates, notice
@ -441,8 +450,8 @@ CoordinateSystemExample
# system defined by them. # system defined by them.
f_always(dot.move_to, lambda: axes.c2p(1, 1)) f_always(dot.move_to, lambda: axes.c2p(1, 1))
self.play( self.play(
axes.scale, 0.75, axes.animate.scale(0.75),
axes.to_corner, UL, axes.animate.to_corner(UL),
run_time=2, run_time=2,
) )
self.wait() self.wait()
@ -534,8 +543,8 @@ GraphExample
lambda: axes.i2gp(x_tracker.get_value(), parabola) lambda: axes.i2gp(x_tracker.get_value(), parabola)
) )
self.play(x_tracker.set_value, 4, run_time=3) self.play(x_tracker.animate.set_value(4), run_time=3)
self.play(x_tracker.set_value, -2, run_time=3) self.play(x_tracker.animate.set_value(-2), run_time=3)
self.wait() self.wait()
SurfaceExample SurfaceExample
@ -608,8 +617,8 @@ SurfaceExample
self.play( self.play(
Transform(surface, surfaces[2]), Transform(surface, surfaces[2]),
# Move camera frame during the transition # Move camera frame during the transition
frame.increment_phi, -10 * DEGREES, frame.animate.increment_phi(-10 * DEGREES),
frame.increment_theta, -20 * DEGREES, frame.animate.increment_theta(-20 * DEGREES),
run_time=3 run_time=3
) )
# Add ambient rotation # Add ambient rotation
@ -624,8 +633,8 @@ SurfaceExample
light = self.camera.light_source light = self.camera.light_source
self.add(light) self.add(light)
light.save_state() light.save_state()
self.play(light.move_to, 3 * IN, run_time=5) self.play(light.animate.move_to(3 * IN), run_time=5)
self.play(light.shift, 10 * OUT, run_time=5) self.play(light.animate.shift(10 * OUT), run_time=5)
drag_text = Text("Try moving the mouse while pressing d or s") drag_text = Text("Try moving the mouse while pressing d or s")
drag_text.move_to(light_text) drag_text.move_to(light_text)
@ -634,7 +643,7 @@ SurfaceExample
self.play(FadeTransform(light_text, drag_text)) self.play(FadeTransform(light_text, drag_text))
self.wait() self.wait()
This scene shows an example of using a three-dimensional surface, and This scene shows an example of using a three-dimensional surface, and
the related usage has been briefly described in the notes. the related usage has been briefly described in the notes.
- ``.fix_in_frame()`` makes the object not change with the view angle of the screen, and is always displayed at a fixed position on the screen. - ``.fix_in_frame()`` makes the object not change with the view angle of the screen, and is always displayed at a fixed position on the screen.
@ -675,7 +684,7 @@ OpeningManimExample
FadeTransform(intro_words, linear_transform_words) FadeTransform(intro_words, linear_transform_words)
) )
self.wait() self.wait()
self.play(grid.apply_matrix, matrix, run_time=3) self.play(grid.animate.apply_matrix(matrix), run_time=3)
self.wait() self.wait()
# Complex map # Complex map
@ -699,12 +708,12 @@ OpeningManimExample
) )
self.wait() self.wait()
self.play( self.play(
moving_c_grid.apply_complex_function, lambda z: z**2, moving_c_grid.animate.apply_complex_function(lambda z: z**2),
run_time=6, run_time=6,
) )
self.wait(2) self.wait(2)
This scene is a comprehensive application of a two-dimensional scene. This scene is a comprehensive application of a two-dimensional scene.
After seeing these scenes, you have already understood part of the After seeing these scenes, you have already understood part of the
usage of manim. For more examples, see `the video code of 3b1b <https://github.com/3b1b/videos>`_. usage of manim. For more examples, see `the video code of 3b1b <https://github.com/3b1b/videos>`_.

View file

@ -1,10 +1,10 @@
Quick Start Quick Start
=========== ===========
After installing the manim environment according to the instructions on the After installing the manim environment according to the instructions on the
:doc:`installation` page, you can try to make a scene yourself from scratch. :doc:`installation` page, you can try to make a scene yourself from scratch.
First, create a new ``.py`` file (such as ``start.py``) according to the following First, create a new ``.py`` file (such as ``start.py``) according to the following
directory structure: directory structure:
.. code-block:: text .. code-block:: text
@ -45,7 +45,7 @@ A window will pop up on the screen. And then you can :
- scroll the middle mouse button to move the screen up and down - scroll the middle mouse button to move the screen up and down
- hold down the :kbd:`z` on the keyboard while scrolling the middle mouse button to zoom the screen - hold down the :kbd:`z` on the keyboard while scrolling the middle mouse button to zoom the screen
- hold down the :kbd:`s` key on the keyboard and move the mouse to pan the screen - hold down the :kbd:`s` key on the keyboard and move the mouse to pan the screen
- hold down the :kbd:`d` key on the keyboard and move the mouse to change the three-dimensional perspective. - hold down the :kbd:`d` key on the keyboard and move the mouse to change the three-dimensional perspective.
Finally, you can close the window and exit the program by pressing :kbd:`q`. Finally, you can close the window and exit the program by pressing :kbd:`q`.
@ -55,8 +55,8 @@ Run this command again:
python manim.py start.py SquareToCircle -os python manim.py start.py SquareToCircle -os
At this time, no window will pop up. When the program is finished, this rendered At this time, no window will pop up. When the program is finished, this rendered
image will be automatically opened (saved in the subdirectory ``images/`` of the same image will be automatically opened (saved in the subdirectory ``images/`` of the same
level directory of ``start.py`` by default): level directory of ``start.py`` by default):
.. image:: ../_static/quickstart/SquareToCircle.png .. image:: ../_static/quickstart/SquareToCircle.png
@ -67,36 +67,36 @@ Make an image
Next, let's take a detailed look at what each row does. Next, let's take a detailed look at what each row does.
**Line 1**: **Line 1**:
.. code-block:: python .. code-block:: python
from manimlib.imports import * from manimlib.imports import *
This will import all the classes that may be used when using manim. This will import all the classes that may be used when using manim.
**Line 3**: **Line 3**:
.. code-block:: python .. code-block:: python
class SquareToCircle(Scene): class SquareToCircle(Scene):
Create a :class:`Scene` subclass ``SquareToCircle``, which will be Create a :class:`Scene` subclass ``SquareToCircle``, which will be
the scene you write and render. the scene you write and render.
**Line 4**: **Line 4**:
.. code-block:: python .. code-block:: python
def construct(self): def construct(self):
Write the ``construct()`` method, the content of which will determine Write the ``construct()`` method, the content of which will determine
how to create the mobjects in the screen and what operations need to be performed. how to create the mobjects in the screen and what operations need to be performed.
**Line 5**: **Line 5**:
.. code-block:: python .. code-block:: python
circle = Circle() circle = Circle()
Create a circle (an instance of the :class:`Circle` class), called ``circle`` Create a circle (an instance of the :class:`Circle` class), called ``circle``
@ -104,7 +104,7 @@ Create a circle (an instance of the :class:`Circle` class), called ``circle``
**Line 6~7**: **Line 6~7**:
.. code-block:: python .. code-block:: python
circle.set_fill(BLUE, opacity=0.5) circle.set_fill(BLUE, opacity=0.5)
circle.set_stroke(BLUE_E, width=4) circle.set_stroke(BLUE_E, width=4)
@ -116,7 +116,7 @@ Set the circle style by calling the circle's method.
**Line 9**: **Line 9**:
.. code-block:: python .. code-block:: python
self.add(circle) self.add(circle)
Add this circle to the screen through the ``.add()`` method of :class:`Scene`. Add this circle to the screen through the ``.add()`` method of :class:`Scene`.
@ -137,7 +137,7 @@ Let's change some codes and add some animations to make videos instead of just p
circle.set_fill(BLUE, opacity=0.5) circle.set_fill(BLUE, opacity=0.5)
circle.set_stroke(BLUE_E, width=4) circle.set_stroke(BLUE_E, width=4)
square = Square() square = Square()
self.play(ShowCreation(square)) self.play(ShowCreation(square))
self.wait() self.wait()
self.play(ReplacementTransform(square, circle)) self.play(ReplacementTransform(square, circle))
@ -149,51 +149,51 @@ Run this command this time:
python manim.py start.py SquareToCircle python manim.py start.py SquareToCircle
The pop-up window will play animations of drawing a square and transforming The pop-up window will play animations of drawing a square and transforming
it into a circle. If you want to save this video, run: it into a circle. If you want to save this video, run:
.. code-block:: sh .. code-block:: sh
python manim.py start.py SquareToCircle -ow python manim.py start.py SquareToCircle -ow
This time there will be no pop-up window, but the video file (saved in the subdirectory This time there will be no pop-up window, but the video file (saved in the subdirectory
``videos/`` of the same level directory of ``start.py`` by default) will be automatically ``videos/`` of the same level directory of ``start.py`` by default) will be automatically
opened after the operation is over: opened after the operation is over:
.. raw:: html .. raw:: html
<video class="manim-video" controls loop autoplay src="../_static/quickstart/SquareToCircle.mp4"></video> <video class="manim-video" controls loop autoplay src="../_static/quickstart/SquareToCircle.mp4"></video>
Let's take a look at the code this time. The first 7 lines are the same as the previous Let's take a look at the code this time. The first 7 lines are the same as the previous
ones, and the 8th line is similar to the 5th line, which creates an instance of the ones, and the 8th line is similar to the 5th line, which creates an instance of the
:class:`Square` class and named it ``square``. :class:`Square` class and named it ``square``.
**Line 10**: **Line 10**:
.. code-block:: python .. code-block:: python
self.play(ShowCreation(square)) self.play(ShowCreation(square))
An animation is played through :class:`Scene`'s ``.play()`` method. :class:`ShowCreation` An animation is played through :class:`Scene`'s ``.play()`` method. :class:`ShowCreation`
is an animation that shows the process of creating a given mobject. is an animation that shows the process of creating a given mobject.
``self.play(ShowCreation(square))`` is to play the animation of creating ``square``. ``self.play(ShowCreation(square))`` is to play the animation of creating ``square``.
**Line 11**: **Line 11**:
.. code-block:: python .. code-block:: python
self.wait() self.wait()
Use :class:`Scene`'s ``.wait()`` method to pause (default 1s), you can pass in Use :class:`Scene`'s ``.wait()`` method to pause (default 1s), you can pass in
parameters to indicate the pause time (for example, ``self.wait(3)`` means pause for 3s). parameters to indicate the pause time (for example, ``self.wait(3)`` means pause for 3s).
**Line 12**: **Line 12**:
.. code-block:: python .. code-block:: python
self.play(ReplacementTransform(square, circle)) self.play(ReplacementTransform(square, circle))
Play the animation that transforms ``square`` into ``circle``. Play the animation that transforms ``square`` into ``circle``.
``ReplacementTransform(A, B)`` means to transform A into B's pattern and replace A with B. ``ReplacementTransform(A, B)`` means to transform A into B's pattern and replace A with B.
**Line 13**: Same as line 11, pause for 1s. **Line 13**: Same as line 11, pause for 1s.
@ -202,34 +202,34 @@ Play the animation that transforms ``square`` into ``circle``.
Enable interaction Enable interaction
------------------ ------------------
Interaction is a new feature of the new version. You can add the following line Interaction is a new feature of the new version. You can add the following line
at the end of the code to enable interaction: at the end of the code to enable interaction:
.. code-block:: python .. code-block:: python
self.embed() self.embed()
Then run ``python manim.py start.py SquareToCircle``. Then run ``python manim.py start.py SquareToCircle``.
After the previous animation is executed, the ipython terminal will be opened on After the previous animation is executed, the ipython terminal will be opened on
the command line. After that, you can continue to write code in it, and the statement the command line. After that, you can continue to write code in it, and the statement
you entered will be executed immediately after pressing :kbd:`Enter`. you entered will be executed immediately after pressing :kbd:`Enter`.
For example: input the following lines (without comment lines) into it respectively For example: input the following lines (without comment lines) into it respectively
(``self.play`` can be abbreviated as ``play`` in this mode): (``self.play`` can be abbreviated as ``play`` in this mode):
.. code-block:: python .. code-block:: python
# Stretched 4 times in the vertical direction # Stretched 4 times in the vertical direction
play(circle.stretch, 4, {"dim": 0}) play(circle.animate.stretch(4, dim=0}))
# Rotate the ellipse 90° # Rotate the ellipse 90°
play(Rotate(circle, TAU / 4)) play(Rotate(circle, TAU / 4))
# Move 2 units to the right and shrink to 1/4 of the original # Move 2 units to the right and shrink to 1/4 of the original
play(circle.shift, 2 * RIGHT, circle.scale, 0.25) play(circle.animate.shift(2 * RIGHT), circle.animate.scale(0.25))
# Insert 10 curves into circle for non-linear transformation (no animation will play) # Insert 10 curves into circle for non-linear transformation (no animation will play)
circle.insert_n_curves(10) circle.insert_n_curves(10)
# Apply a complex transformation of f(z)=z^2 to all points on the circle # Apply a complex transformation of f(z)=z^2 to all points on the circle
play(circle.apply_complex_function, lambda z: z**2) play(circle.animate.apply_complex_function(lambda z: z**2))
# Close the window and exit the program # Close the window and exit the program
exit() exit()
@ -239,8 +239,8 @@ You will get an animation similar to the following:
<video class="manim-video" controls loop autoplay src="../_static/quickstart/SquareToCircleEmbed.mp4"></video> <video class="manim-video" controls loop autoplay src="../_static/quickstart/SquareToCircleEmbed.mp4"></video>
If you want to enter the interactive mode directly, you don't have to write an If you want to enter the interactive mode directly, you don't have to write an
empty scene containing only ``self.embed()``, you can directly run the following command empty scene containing only ``self.embed()``, you can directly run the following command
(this will enter the ipython terminal while the window pops up): (this will enter the ipython terminal while the window pops up):
.. code-block:: sh .. code-block:: sh
@ -250,7 +250,7 @@ empty scene containing only ``self.embed()``, you can directly run the following
You succeeded! You succeeded!
-------------- --------------
After reading the above content, you already know how to use manim. After reading the above content, you already know how to use manim.
Below you can see some examples, in the :doc:`example_scenes` page. Below you can see some examples, in the :doc:`example_scenes` page.
But before that, you'd better have a look at the :doc:`configuration` of manim. But before that, you'd better have a look at the :doc:`configuration` of manim.

View file

@ -37,7 +37,7 @@ class OpeningManimExample(Scene):
FadeTransform(intro_words, linear_transform_words) FadeTransform(intro_words, linear_transform_words)
) )
self.wait() self.wait()
self.play(grid.apply_matrix, matrix, run_time=3) self.play(grid.animate.apply_matrix(matrix), run_time=3)
self.wait() self.wait()
# Complex map # Complex map
@ -61,7 +61,7 @@ class OpeningManimExample(Scene):
) )
self.wait() self.wait()
self.play( self.play(
moving_c_grid.apply_complex_function, lambda z: z**2, moving_c_grid.animate.apply_complex_function(lambda z: z**2),
run_time=6, run_time=6,
) )
self.wait(2) self.wait(2)
@ -72,35 +72,44 @@ class AnimatingMethods(Scene):
grid = Tex(r"\pi").get_grid(10, 10, height=4) grid = Tex(r"\pi").get_grid(10, 10, height=4)
self.add(grid) self.add(grid)
# If you pass in a mobject method to the scene's "play" function, # You can animate the application of mobject methods with the
# it will apply an animation interpolating between the mobject's # ".animate" syntax:
# initial state and whatever happens when you apply that method. self.play(grid.animate.shift(LEFT))
# For example, calling grid.shift(2 * LEFT) would shift it two units
# to the left, but the following line animates that motion. # Alternatively, you can use the older syntax by passing the
self.play(grid.shift, 2 * LEFT) # method and then the arguments to the scene's "play" function:
self.play(grid.shift, LEFT)
# Both of those will interpolate between the mobject's initial
# state and whatever happens when you apply that method.
# For this example, calling grid.shift(LEFT) would shift the
# grid one unit to the left, but both of the previous calls to
# "self.play" animate that motion.
# The same applies for any method, including those setting colors. # The same applies for any method, including those setting colors.
self.play(grid.set_color, YELLOW) self.play(grid.animate.set_color(YELLOW))
self.wait() self.wait()
self.play(grid.set_submobject_colors_by_gradient, BLUE, GREEN) self.play(grid.animate.set_submobject_colors_by_gradient(BLUE, GREEN))
self.wait() self.wait()
self.play(grid.set_height, TAU - MED_SMALL_BUFF) self.play(grid.animate.set_height(TAU - MED_SMALL_BUFF))
self.wait() self.wait()
# The method Mobject.apply_complex_function lets you apply arbitrary # The method Mobject.apply_complex_function lets you apply arbitrary
# complex functions, treating the points defining the mobject as # complex functions, treating the points defining the mobject as
# complex numbers. # complex numbers.
self.play(grid.apply_complex_function, np.exp, run_time=5) self.play(grid.animate.apply_complex_function(np.exp), run_time=5)
self.wait() self.wait()
# Even more generally, you could apply Mobject.apply_function, # Even more generally, you could apply Mobject.apply_function,
# which takes in functions form R^3 to R^3 # which takes in functions form R^3 to R^3
self.play( self.play(
grid.apply_function, grid.animate.apply_function(
lambda p: [ lambda p: [
p[0] + 0.5 * math.sin(p[1]), p[0] + 0.5 * math.sin(p[1]),
p[1] + 0.5 * math.sin(p[0]), p[1] + 0.5 * math.sin(p[0]),
p[2] p[2]
], ]
),
run_time=5, run_time=5,
) )
self.wait() self.wait()
@ -294,18 +303,18 @@ class UpdatersExample(Scene):
# Notice that the brace and label track with the square # Notice that the brace and label track with the square
self.play( self.play(
square.scale, 2, square.animate.scale(2),
rate_func=there_and_back, rate_func=there_and_back,
run_time=2, run_time=2,
) )
self.wait() self.wait()
self.play( self.play(
square.set_width, 5, {"stretch": True}, square.set_width(5, stretch=True),
run_time=3, run_time=3,
) )
self.wait() self.wait()
self.play( self.play(
square.set_width, 2, square.animate.set_width(2),
run_time=3 run_time=3
) )
self.wait() self.wait()
@ -361,9 +370,9 @@ class CoordinateSystemExample(Scene):
dot = Dot(color=RED) dot = Dot(color=RED)
dot.move_to(axes.c2p(0, 0)) dot.move_to(axes.c2p(0, 0))
self.play(FadeIn(dot, scale=0.5)) self.play(FadeIn(dot, scale=0.5))
self.play(dot.move_to, axes.c2p(3, 2)) self.play(dot.animate.move_to(axes.c2p(3, 2)))
self.wait() self.wait()
self.play(dot.move_to, axes.c2p(5, 0.5)) self.play(dot.animate.move_to(axes.c2p(5, 0.5)))
self.wait() self.wait()
# Similarly, you can call axes.point_to_coords, or axes.p2c # Similarly, you can call axes.point_to_coords, or axes.p2c
@ -380,9 +389,9 @@ class CoordinateSystemExample(Scene):
ShowCreation(h_line), ShowCreation(h_line),
ShowCreation(v_line), ShowCreation(v_line),
) )
self.play(dot.move_to, axes.c2p(3, -2)) self.play(dot.animate.move_to(axes.c2p(3, -2)))
self.wait() self.wait()
self.play(dot.move_to, axes.c2p(1, 1)) self.play(dot.animate.move_to(axes.c2p(1, 1)))
self.wait() self.wait()
# If we tie the dot to a particular set of coordinates, notice # If we tie the dot to a particular set of coordinates, notice
@ -390,8 +399,8 @@ class CoordinateSystemExample(Scene):
# system defined by them. # system defined by them.
f_always(dot.move_to, lambda: axes.c2p(1, 1)) f_always(dot.move_to, lambda: axes.c2p(1, 1))
self.play( self.play(
axes.scale, 0.75, axes.animate.scale(0.75),
axes.to_corner, UL, axes.animate.to_corner(UL),
run_time=2, run_time=2,
) )
self.wait() self.wait()
@ -477,8 +486,8 @@ class GraphExample(Scene):
lambda: axes.i2gp(x_tracker.get_value(), parabola) lambda: axes.i2gp(x_tracker.get_value(), parabola)
) )
self.play(x_tracker.set_value, 4, run_time=3) self.play(x_tracker.animate.set_value(4), run_time=3)
self.play(x_tracker.set_value, -2, run_time=3) self.play(x_tracker.animate.set_value(-2), run_time=3)
self.wait() self.wait()
@ -546,8 +555,8 @@ class SurfaceExample(Scene):
self.play( self.play(
Transform(surface, surfaces[2]), Transform(surface, surfaces[2]),
# Move camera frame during the transition # Move camera frame during the transition
frame.increment_phi, -10 * DEGREES, frame.animate.increment_phi(-10 * DEGREES),
frame.increment_theta, -20 * DEGREES, frame.animate.increment_theta(-20 * DEGREES),
run_time=3 run_time=3
) )
# Add ambient rotation # Add ambient rotation
@ -562,8 +571,8 @@ class SurfaceExample(Scene):
light = self.camera.light_source light = self.camera.light_source
self.add(light) self.add(light)
light.save_state() light.save_state()
self.play(light.move_to, 3 * IN, run_time=5) self.play(light.animate.move_to(3 * IN), run_time=5)
self.play(light.shift, 10 * OUT, run_time=5) self.play(light.animate.shift(10 * OUT), run_time=5)
drag_text = Text("Try moving the mouse while pressing d or s") drag_text = Text("Try moving the mouse while pressing d or s")
drag_text.move_to(light_text) drag_text.move_to(light_text)
@ -593,9 +602,9 @@ class InteractiveDevlopment(Scene):
# the interactive shell # the interactive shell
self.play(ReplacementTransform(square, circle)) self.play(ReplacementTransform(square, circle))
self.wait() self.wait()
self.play(circle.stretch, 4, 0) self.play(circle.animate.stretch(4, 0))
self.play(Rotate(circle, 90 * DEGREES)) self.play(Rotate(circle, 90 * DEGREES))
self.play(circle.shift, 2 * RIGHT, circle.scale, 0.25) self.play(circle.animate.shift(2 * RIGHT), circle.animate.scale(0.25))
text = Text(""" text = Text("""
In general, using the interactive shell In general, using the interactive shell

View file

@ -1,6 +1,6 @@
from copy import deepcopy from copy import deepcopy
from manimlib.mobject.mobject import Mobject from manimlib.mobject.mobject import Mobject, _AnimationBuilder
from manimlib.utils.config_ops import digest_config from manimlib.utils.config_ops import digest_config
from manimlib.utils.rate_functions import smooth from manimlib.utils.rate_functions import smooth
from manimlib.utils.simple_functions import clip from manimlib.utils.simple_functions import clip
@ -159,3 +159,13 @@ class Animation(object):
def is_remover(self): def is_remover(self):
return self.remover return self.remover
def prepare_animation(anim):
if isinstance(anim, _AnimationBuilder):
return anim.build()
if isinstance(anim, Animation):
return anim
raise TypeError(f"Object {anim} cannot be converted to an animation")

View file

@ -1,6 +1,6 @@
import numpy as np import numpy as np
from manimlib.animation.animation import Animation from manimlib.animation.animation import Animation, prepare_animation
from manimlib.mobject.mobject import Group from manimlib.mobject.mobject import Group
from manimlib.utils.bezier import integer_interpolate from manimlib.utils.bezier import integer_interpolate
from manimlib.utils.bezier import interpolate from manimlib.utils.bezier import interpolate
@ -29,7 +29,7 @@ class AnimationGroup(Animation):
def __init__(self, *animations, **kwargs): def __init__(self, *animations, **kwargs):
digest_config(self, kwargs) digest_config(self, kwargs)
self.animations = animations self.animations = [prepare_animation(anim) for anim in animations]
if self.group is None: if self.group is None:
self.group = Group(*remove_list_redundancies( self.group = Group(*remove_list_redundancies(
[anim.mobject for anim in animations] [anim.mobject for anim in animations]

View file

@ -149,6 +149,12 @@ class MoveToTarget(Transform):
) )
class _MethodAnimation(MoveToTarget):
def __init__(self, mobject, methods):
self.methods = methods
super().__init__(mobject)
class ApplyMethod(Transform): class ApplyMethod(Transform):
def __init__(self, method, *args, **kwargs): def __init__(self, method, *args, **kwargs):
""" """

View file

@ -80,6 +80,11 @@ class Mobject(object):
if self.depth_test: if self.depth_test:
self.apply_depth_test() self.apply_depth_test()
@property
def animate(self):
# Borrowed from https://github.com/ManimCommunity/manim/
return _AnimationBuilder(self)
def __str__(self): def __str__(self):
return self.__class__.__name__ return self.__class__.__name__
@ -1573,3 +1578,51 @@ class Point(Mobject):
def set_location(self, new_loc): def set_location(self, new_loc):
self.set_points(np.array(new_loc, ndmin=2, dtype=float)) self.set_points(np.array(new_loc, ndmin=2, dtype=float))
class _AnimationBuilder:
def __init__(self, mobject):
self.mobject = mobject
self.overridden_animation = None
self.mobject.generate_target()
self.is_chaining = False
self.methods = []
def __getattr__(self, method_name):
method = getattr(self.mobject.target, method_name)
self.methods.append(method)
has_overridden_animation = hasattr(method, "_override_animate")
if (self.is_chaining and has_overridden_animation) or self.overridden_animation:
raise NotImplementedError(
"Method chaining is currently not supported for "
"overridden animations"
)
def update_target(*method_args, **method_kwargs):
if has_overridden_animation:
self.overridden_animation = method._override_animate(
self.mobject, *method_args, **method_kwargs
)
else:
method(*method_args, **method_kwargs)
return self
self.is_chaining = True
return update_target
def build(self):
from manimlib.animation.transform import _MethodAnimation
if self.overridden_animation:
return self.overridden_animation
return _MethodAnimation(self.mobject, self.methods)
def override_animate(method):
def decorator(animation_method):
method._override_animate = animation_method
return animation_method
return decorator

View file

@ -10,7 +10,7 @@ import numpy as np
import time import time
from IPython.terminal.embed import InteractiveShellEmbed from IPython.terminal.embed import InteractiveShellEmbed
from manimlib.animation.animation import Animation from manimlib.animation.animation import prepare_animation
from manimlib.animation.transform import MoveToTarget from manimlib.animation.transform import MoveToTarget
from manimlib.mobject.mobject import Point from manimlib.mobject.mobject import Point
from manimlib.camera.camera import Camera from manimlib.camera.camera import Camera
@ -348,10 +348,7 @@ class Scene(object):
state["method_args"] = [] state["method_args"] = []
for arg in args: for arg in args:
if isinstance(arg, Animation): if inspect.ismethod(arg):
compile_method(state)
animations.append(arg)
elif inspect.ismethod(arg):
compile_method(state) compile_method(state)
state["curr_method"] = arg state["curr_method"] = arg
elif state["curr_method"] is not None: elif state["curr_method"] is not None:
@ -362,7 +359,13 @@ class Scene(object):
you meant to pass in as a Scene.play argument you meant to pass in as a Scene.play argument
""") """)
else: else:
raise Exception("Invalid play arguments") try:
anim = prepare_animation(arg)
except TypeError:
raise TypeError(f"Unexpected argument {arg} passed to Scene.play()")
compile_method(state)
animations.append(anim)
compile_method(state) compile_method(state)
for animation in animations: for animation in animations: