456 lines · 19.2 KB · Text
RawDownload
#######################Manim's building blocks####################### This document explains the building blocks of manim and will give you all thenecessary tools to start producing your own videos. Essentially, manim puts at your disposal three different concepts that you canorchestrate together to produce mathematical animations: the**mathematical object** (or **mobject** for short), the **animation**, and the**scene**.  As we will see in the following sections, each of these threeconcepts is implemented in manim as a separate class: the :class:`.Mobject`,:class:`.Animation`, and :class:`.Scene` classes. .. note:: It is recommended that you read the tutorials :doc:`quickstart` and          :doc:`output_and_config` before reading this page.  ********Mobjects******** Mobjects are the basic building blocks for all manim animations.  Each classthat derives from :class:`.Mobject` represents an object that can be displayedon the screen.  For example, simple shapes such as :class:`.Circle`,:class:`.Arrow`, and :class:`.Rectangle` are all mobjects.  More complicatedconstructs such as :class:`.Axes`, :class:`.FunctionGraph`, or:class:`.BarChart` are mobjects as well. If you try to display an instance of :class:`.Mobject` on the screen, you will onlysee an empty frame.  The reason is that the :class:`.Mobject` class is anabstract base class of all other mobjects, i.e. it does not have anypre-determined visual shape that can be displayed on the screen.  It is only theskeleton of a thing that *could* be displayed.  Therefore, you will rarely needto use plain instances of :class:`.Mobject`; instead, you will most likelycreate instances of its derived classes.  One of these derived classes is:class:`.VMobject`.  The ``V`` stands for Vectorized Mobject.  In essence, avmobject is a mobject that uses `vector graphics<https://en.wikipedia.org/wiki/Vector_graphics>`_ to be displayed.  Most ofthe time, you will be dealing with vmobjects, though we will continue to usethe term "mobject" to refer to the class of shapes that can be displayed on thescreen, as it is more general. .. note:: Any object that can be displayed on the screen is a ``mobject``, even if          it is not necessarily *mathematical* in nature. .. tip:: To see examples of classes derived from :class:`.Mobject`, see the         :mod:`.geometry` module.  Most of these are in fact derived from         :class:`.VMobject` as well.  Creating and displaying mobjects================================ As explained in :doc:`quickstart`, usually all of the code in a manimscript is put inside the :meth:`.Scene.construct` method of a :class:`.Scene` class.To display a mobject on the screen, call the :meth:`~.Scene.add` method of thecontaining :class:`.Scene`.  This is the principal way of displaying a mobjecton the screen when it is not being animated.  To remove a mobject from thescreen, simply call the :meth:`~.Scene.remove` method from the containing:class:`.Scene`. .. manim:: CreatingMobjects     class CreatingMobjects(Scene):        def construct(self):            circle = Circle()            self.add(circle)            self.wait(1)            self.remove(circle)            self.wait(1)  Placing mobjects================ Let's define a new :class:`.Scene` called ``Shapes`` and :meth:`~.Scene.add`some mobjects to it.  This script generates a static picture that displays acircle, a square, and a triangle: .. manim:: Shapes     class Shapes(Scene):        def construct(self):            circle = Circle()            square = Square()            triangle = Triangle()             circle.shift(LEFT)            square.shift(UP)            triangle.shift(RIGHT)             self.add(circle, square, triangle)            self.wait(1) By default, mobjects are placed at the center of coordinates, or *origin*, whenthey are first created.  They are also given some default colors.  Further, the``Shapes`` scene places the mobjects by using the :meth:`.shift` method.  Thesquare is shifted one unit in the ``UP`` direction from the origin, while thecircle and triangle are shifted one unit ``LEFT`` and ``RIGHT``, respectively. .. attention:: Unlike other graphics software, manim places the center of               coordinates at the center of the screen.  The positive vertical               direction is up, and the positive horizontal direction is right.               See also the constants ``ORIGIN``, ``UP``, ``DOWN``, ``LEFT``,               ``RIGHT``, and others, defined in the :mod:`.constants` module. There are many other possible ways to place mobjects on the screen, for example:meth:`.move_to`, :meth:`.next_to`, and :meth:`.align_to`.  The next scene``MobjectPlacement`` uses all three. .. manim:: MobjectPlacement     class MobjectPlacement(Scene):        def construct(self):            circle = Circle()            square = Square()            triangle = Triangle()             # place the circle two units left from the origin            circle.move_to(LEFT * 2)            # place the square to the left of the circle            square.next_to(circle, LEFT)            # align the left border of the triangle to the left border of the circle            triangle.align_to(circle, LEFT)             self.add(circle, square, triangle)            self.wait(1) The :meth:`.move_to` method uses absolute units (measured relative to the``ORIGIN``), while :meth:`.next_to` uses relative units (measured from themobject passed as the first argument).  :meth:`align_to` uses ``LEFT`` not asmeasuring units but as a way to determine the border to use for alignment.  Thecoordinates of the borders of a mobject are determined using an imaginarybounding box around it. .. tip:: Many methods in manim can be chained together.  For example the two         lines          .. code-block:: python              square = Square()             square.shift(LEFT)          can be replaced by          .. code-block:: python              square = Square().shift(LEFT)          Technically, this is possible because most methods calls return the modified mobject.  Styling mobjects================ The following scene changes the default aesthetics of the mobjects. .. manim:: MobjectStyling     class MobjectStyling(Scene):        def construct(self):            circle = Circle().shift(LEFT)            square = Square().shift(UP)            triangle = Triangle().shift(RIGHT)             circle.set_stroke(color=GREEN, width=20)            square.set_fill(YELLOW, opacity=1.0)            triangle.set_fill(PINK, opacity=0.5)             self.add(circle, square, triangle)            self.wait(1) This scene uses two of the main functions that change the visual style of amobject: :meth:`.set_stroke` and :meth:`.set_fill`.  The former changes thevisual style of the mobject's border while the latter changes the style of theinterior.  By default, most mobjects have a fully transparent interior so youmust specify the ``opacity`` parameter to display the color.  Anopacity of ``1.0`` means fully opaque, while ``0.0`` means fully transparent. Only instances of :class:`.VMobject` implement :meth:`.set_stroke` and:meth:`.set_fill`.  Instances of :class:`.Mobject` implement:meth:`.~Mobject.set_color` instead.  The vast majority of pre-defined classesare derived from :class:`.VMobject` so it is usually safe to assume that youhave access to :meth:`.set_stroke` and :meth:`.set_fill`.  Mobject on-screen order======================= The next scene is exactly the same as the ``MobjectStyling`` scene from theprevious section, except for exactly one line. .. manim:: MobjectZOrder     class MobjectZOrder(Scene):        def construct(self):            circle = Circle().shift(LEFT)            square = Square().shift(UP)            triangle = Triangle().shift(RIGHT)             circle.set_stroke(color=GREEN, width=20)            square.set_fill(YELLOW, opacity=1.0)            triangle.set_fill(PINK, opacity=0.5)             self.add(triangle, square, circle)            self.wait(1) The only difference here (besides the scene name) is the order in which themobjects are added to the scene.  In ``MobjectStyling``, we added them as``add(circle, square, triangle)``, whereas in ``MobjectZOrder`` we add them as``add(triangle, square, circle)``. As you can see, the order of the arguments of :meth:`~.Scene.add` determinesthe order that the mobjects are displayed on the screen, with the left-mostarguments being put in the back.  **********Animations********** At the heart of manim is animation.  Generally, you can add an animation toyour scene by calling the :meth:`~.Scene.play` method. .. manim:: SomeAnimations     class SomeAnimations(Scene):        def construct(self):            square = Square()             # some animations display mobjects, ...            self.play(FadeIn(square))             # ... some move or rotate mobjects around...            self.play(Rotate(square, PI/4))             # some animations remove mobjects from the screen            self.play(FadeOut(square))             self.wait(1) Put simply, animations are procedures that interpolate between two mobjects.For example, :code:`FadeIn(square)` starts with a fully transparent version of:code:`square` and ends with a fully opaque version, interpolating between themby gradually increasing the opacity.  :class:`.FadeOut` works in the oppositeway: it interpolates from fully opaque to fully transparent.  As anotherexample, :class:`.Rotate` starts with the mobject passed to it as argument, andends with the same object but rotated by a certain amount, this timeinterpolating the mobject's angle instead of its opacity.  Animating methods================= Any property of a mobject that can be changed can be animated.  In fact, anymethod that changes a mobject's property can be used as an animation, throughthe use of :meth:`.animate`. .. manim:: AnimateExample    :ref_classes: Animation     class AnimateExample(Scene):        def construct(self):            square = Square().set_fill(RED, opacity=1.0)            self.add(square)             # animate the change of color            self.play(square.animate.set_fill(WHITE))            self.wait(1)             # animate the change of position and the rotation at the same time            self.play(square.animate.shift(UP).rotate(PI / 3))            self.wait(1) :meth:`.animate` is a property of all mobjects that animates the methods that comeafterward. For example, :code:`square.set_fill(WHITE)` sets the fill color ofthe square, while :code:`square.animate.set_fill(WHITE)` animates this action. Animation run time================== By default, any animation passed to :meth:`play` lasts for exactly one second.Use the :code:`run_time` argument to control the duration. .. manim:: RunTime     class RunTime(Scene):        def construct(self):            square = Square()            self.add(square)            self.play(square.animate.shift(UP), run_time=3)            self.wait(1) Creating a custom animation=========================== Even though Manim has many built-in animations, you will find times when you need to smoothly animate from one state of a :class:`~.Mobject` to another.If you find yourself in that situation, then you can define your own custom animation.You start by extending the :class:`~.Animation` class and overriding its :meth:`~.Animation.interpolate_mobject`.The :meth:`~.Animation.interpolate_mobject` method receives alpha as a parameter that starts at 0 and changes throughout the animation.So, you just have to manipulate self.mobject inside Animation according to the alpha value in its interpolate_mobject method.Then you get all the benefits of :class:`~.Animation` such as playing it for different run times or using different rate functions. Let's say you start with a number and want to create a :class:`~.Transform` animation that transforms it to a target number.You can do it using :class:`~.FadeTransform`, which will fade out the starting number and fade in the target number.But when we think about transforming a number from one to another, an intuitive way of doing it is by incrementing or decrementing it smoothly.Manim has a feature that allows you to customize this behavior by defining your own custom animation. You can start by creating your own ``Count`` class that extends :class:`~.Animation`.The class can have a constructor with three arguments, a :class:`~.DecimalNumber` Mobject, start, and end.The constructor will pass the :class:`~.DecimalNumber` Mobject to the super constructor (in this case, the :class:`~.Animation` constructor) and will set start and end. The only thing that you need to do is to define how you want it to look at every step of the animation.Manim provides you with the alpha value in the :meth:`~.Animation.interpolate_mobject` method based on frame rate of video, rate function, and run time of animation played.The alpha parameter holds a value between 0 and 1 representing the step of the currently playing animation.For example, 0 means the beginning of the animation, 0.5 means halfway through the animation, and 1 means the end of the animation. In the case of the ``Count`` animation, you just have to figure out a way to determine the number to display at the given alpha value and then set that value in the :meth:`~.Animation.interpolate_mobject` method of the ``Count`` animation.Suppose you are starting at 50 and incrementing until the :class:`~.DecimalNumber` reaches 100 at the end of the animation. * If alpha is 0, you want the value to be 50.* If alpha is 0.5, you want the value to be 75.* If alpha is 1, you want the value to be 100. Generally, you start with the starting number and add only some part of the value to be increment according to the alpha value.So, the logic of calculating the number to display at each step will be ``50 + alpha * (100 - 50)``.Once you set the calculated value for the :class:`~.DecimalNumber`, you are done. .. note::     If you're creating a custom animation and want to use a ``rate_func``, you must explicitly apply    ``self.rate_func(alpha)`` to the parameter you're animating. For example, try switching the rate    function to ``rate_functions.there_and_back`` to observe how it affects the counting behavior.  Once you have defined your ``Count`` animation, you can play it in your :class:`~.Scene` for any duration you want for any :class:`~.DecimalNumber` with any rate function. .. manim:: CountingScene    :ref_classes: Animation DecimalNumber    :ref_methods: Animation.interpolate_mobject Scene.play     class Count(Animation):        def __init__(self, number: DecimalNumber, start: float, end: float, **kwargs) -> None:            # Pass number as the mobject of the animation            super().__init__(number,  **kwargs)            # Set start and end            self.start = start            self.end = end         def interpolate_mobject(self, alpha: float) -> None:            # Set value of DecimalNumber according to alpha            value = self.start + (self.rate_func(alpha) * (self.end - self.start))            self.mobject.set_value(value)      class CountingScene(Scene):        def construct(self):            # Create Decimal Number and add it to scene            number = DecimalNumber().set_color(WHITE).scale(5)            # Add an updater to keep the DecimalNumber centered as its value changes            number.add_updater(lambda number: number.move_to(ORIGIN))             self.add(number)             self.wait()             # Play the Count Animation to count from 0 to 100 in 4 seconds            self.play(Count(number, 0, 100), run_time=4, rate_func=linear)             self.wait() Using coordinates of a mobject============================== Mobjects contain points that define their boundaries.These points can be used to add other mobjects respectively to each other,e.g. by methods like :meth:`~.Mobject.get_center` , :meth:`~.Mobject.get_top`and :meth:`~.Mobject.get_start`. Here is an example of some important coordinates: .. manim:: MobjectExample    :save_last_frame:     class MobjectExample(Scene):        def construct(self):            p1 = [-1,-1, 0]            p2 = [ 1,-1, 0]            p3 = [ 1, 1, 0]            p4 = [-1, 1, 0]            a  = Line(p1,p2).append_points(Line(p2,p3).points).append_points(Line(p3,p4).points)            point_start  = a.get_start()            point_end    = a.get_end()            point_center = a.get_center()            self.add(Text(f"a.get_start() = {np.round(point_start,2).tolist()}", font_size=24).to_edge(UR).set_color(YELLOW))            self.add(Text(f"a.get_end() = {np.round(point_end,2).tolist()}", font_size=24).next_to(self.mobjects[-1],DOWN).set_color(RED))            self.add(Text(f"a.get_center() = {np.round(point_center,2).tolist()}", font_size=24).next_to(self.mobjects[-1],DOWN).set_color(BLUE))             self.add(Dot(a.get_start()).set_color(YELLOW).scale(2))            self.add(Dot(a.get_end()).set_color(RED).scale(2))            self.add(Dot(a.get_top()).set_color(GREEN_A).scale(2))            self.add(Dot(a.get_bottom()).set_color(GREEN_D).scale(2))            self.add(Dot(a.get_center()).set_color(BLUE).scale(2))            self.add(Dot(a.point_from_proportion(0.5)).set_color(ORANGE).scale(2))            self.add(*[Dot(x) for x in a.points])            self.add(a) Transforming mobjects into other mobjects=========================================It is also possible to transform a mobject into another mobject like this: .. manim:: ExampleTransform     class ExampleTransform(Scene):        def construct(self):            self.camera.background_color = WHITE            m1 = Square().set_color(RED)            m2 = Rectangle().set_color(RED).rotate(0.2)            self.play(Transform(m1,m2)) The Transform function maps points of the previous mobject to the points of thenext mobject.This might result in strange behaviour, e.g. when the dots of one mobject arearranged clockwise and the other points are arranged counterclockwise.Here it might help to use the `flip` function and reposition the points via the`roll <https://numpy.org/doc/stable/reference/generated/numpy.roll.html>`_function of numpy: .. manim:: ExampleRotation     class ExampleRotation(Scene):        def construct(self):            self.camera.background_color = WHITE            m1a = Square().set_color(RED).shift(LEFT)            m1b = Circle().set_color(RED).shift(LEFT)            m2a = Square().set_color(BLUE).shift(RIGHT)            m2b = Circle().set_color(BLUE).shift(RIGHT)             points = m2a.points            points = np.roll(points, int(len(points)/4), axis=0)            m2a.points = points             self.play(Transform(m1a,m1b),Transform(m2a,m2b), run_time=1) ******Scenes****** The :class:`.Scene` class is the connective tissue of manim.  Every mobject hasto be :meth:`added <.Scene.add>` to a scene to be displayed, or :meth:`removed<.Scene.remove>` from it to cease being displayed.  Every animation has to be:meth:`played <.Scene.play>` by a scene, and every time interval where noanimation occurs is determined by a call to :meth:`~.Scene.wait`.  All of thecode of your video must be contained in the :meth:`~.Scene.construct` method ofa class that derives from :class:`.Scene`.  Finally, a single file may containmultiple :class:`.Scene` subclasses if multiple scenes are to berendered at the same time.