Manim#

Manim is a Python library for creating mathematical animations in the style of those used by the 3Blue1Brown YouYube channel.

Examples#

Here are some examples from the First Steps with Manim notebook.

from manim import *

config.media_width = "60%"
Manim Community v0.18.0

%%manim -v WARNING -qm CircleToSquare

class CircleToSquare(Scene):
    def construct(self):
        blue_circle = Circle(color=BLUE, fill_opacity=0.5)
        green_square = Square(color=GREEN, fill_opacity=0.8)
        self.play(Create(blue_circle))
        self.wait()
        
        self.play(Transform(blue_circle, green_square))
        self.wait()
Animation 0: Create(Circle):   0%|          | 0/30 [00:00<?, ?it/s]
Animation 0: Create(Circle):  17%|█▋        | 5/30 [00:00<00:00, 47.77it/s]
Animation 0: Create(Circle):  47%|████▋     | 14/30 [00:00<00:00, 67.99it/s]
Animation 0: Create(Circle):  77%|███████▋  | 23/30 [00:00<00:00, 73.87it/s]
                                                                            

Animation 2: Transform(Circle):   0%|          | 0/30 [00:00<?, ?it/s]
Animation 2: Transform(Circle):  20%|██        | 6/30 [00:00<00:00, 56.94it/s]
Animation 2: Transform(Circle):  53%|█████▎    | 16/30 [00:00<00:00, 79.84it/s]
Animation 2: Transform(Circle):  87%|████████▋ | 26/30 [00:00<00:00, 88.17it/s]
                                                                               

Manim and JupyterBook#

Unfortunately, the videos generated above will not work with JupyterBook output.

What Works#

Static Images

This works out of the box, just use --format=gif or --format=png. Don’t try to do animations though (animated gifs).

Embeded Movies

If you can find where the rendered movie is stored, then you can import it with IPython.display.Video class with the embed=True option.

Movies in _static

If you are careful to put your movies in the _static/ directory that Sphinx copies (see html_static_path), then you can embed them with the <video> tag:

<video autoplay loop width='420' 
       src='../_static/CircleToSquare.mp4' type='video/mp4'/>

gives

Caveats:

  • You need to know where you are. The relative path ../_static above is used because this document is nested one level down. This makes your document somewhat brittle and you cannot move it to a different level.

    I played with using an absolute path /_static, but this breaks [RTD] for example which translates to

    • https://...readthedocs.io/_static/CircleToSquare.mp4

    but should be

    • https://...readthedocs.io/en/latest/_static/CircleToSquare.mp4

  • This may work if you run the notebook with Jupyter, but can fail. For example, if you run jupyter in the same directory as this notebook, then ../ will be above the tree and it will fail.

The manim Sphinx directive

You can include the manim.utils.docbuild.manim_directive directive in your conf.py file, then use Sphinx. Unfortunately, you must fallback to the eval-rst directive which is a bit of a pain (code must be indented)

Static Images#

Static images work, so we can use Manim as a drawing tool, but we cannot “play” animations out of the box.

%%manim -v WARNING --progress_bar None -r 400,200 --format=gif -qm CircleAndSquare

class CircleAndSquare(Scene):
    def construct(self):
        blue_circle = Circle(color=BLUE, fill_opacity=0.5)
        green_square = Square(color=GREEN, fill_opacity=0.8)
        green_square.shift(2*RIGHT)
        self.add(blue_circle)
        self.add(green_square)
../_images/207845337c866bf126dc72b83055a966978f36fe487ab21722c1eb708d3eb783.png

Embedded Videos#

Embedding videos works, but you need to know where the files are:

from IPython.display import Video
Video('./media/videos/OtherNotes/720p30/CircleToSquare.mp4', 
      width=500, 
      embed=True,
      html_attributes="controls muted loop autoplay")

The following will work with Jupyter notebooks since the path is relative to the notebook, but will not work with Sphinx because these movies are not copied to the output directory:

<video autoplay loop width='420' 
       src='./media/videos/OtherNotes/720p30/CircleToSquare.mp4' type='video/mp4'>
</video>

Movies in _static#

If you know where you are, you can put the movie in the appropriate _static folder that is copied over by Sphinx. Then you can embed them:

<video autoplay loop width='420' 
       src='../_static/CircleToSquare.mp4' type='video/mp4'>
</video>
%%manim -v WARNING --disable_caching --progress_bar None -qm -o ../../../../_static/CircleToSquare.mp4 CircleToSquare

class CircleToSquare(Scene):
    def construct(self):
        blue_circle = Circle(color=BLUE, fill_opacity=0.5)
        green_square = Square(color=GREEN, fill_opacity=0.8)
        self.play(Create(blue_circle))
        self.wait()
        
        self.play(Transform(blue_circle, green_square))
        self.wait()

This can be displayed in python:

from IPython.display import Video
Video('../_static/CircleToSquare.mp4', 
      width=500, 
      embed=False,
      mimetype="video/mp4",
      html_attributes="controls muted loop autoplay")

Looking under the hood, what happens here is that the following files are generated relative to this notebook. This is why we need such and ugly output path for %%manim.

`-- media
    |-- images
    |   `-- OtherNotes
    |       `-- CircleAndSquare_ManimCE_v0.13.1.png
    |-- jupyter
    |   |-- CircleAndSquare@2022-01-08@00-13-03.png
    |   |-- CircleToSquare@2022-01-08@00-13-03.mp4
    |   `-- CircleToSquare@2022-01-08@00-13-04.gif
    `-- videos
        `-- OtherNotes
            |-- 200p30
            |   |-- CircleToSquare_ManimCE_v0.13.1.gif
            |   `-- partial_movie_files
            |       |-- CircleAndSquare
            |       `-- CircleToSquare
            |           |-- partial_movie_file_list.txt
            |           |-- uncached_00000.mp4
            |           |-- uncached_00001.mp4
            |           |-- uncached_00002.mp4
            |           `-- uncached_00003.mp4
            `-- 720p30
                |-- CircleToSquare.mp4
                `-- partial_movie_files
                    `-- CircleToSquare
                        |-- 1342159004_1441543657_2959157631.mp4
                        |-- 1353618911_2262467609_2655279732.mp4
                        |-- 1353618911_398514950_1253908064.mp4
                        |-- 1353618911_398514950_694114964.mp4
                        `-- partial_movie_file_list.txt

%%manim -v WARNING --progress_bar None --disable_caching -qm BannerExample

config.media_width = "75%"

class BannerExample(Scene):
    def construct(self):
        self.camera.background_color = "#ece6e2"
        banner_large = ManimBanner(dark_theme=False).scale(0.7)
        self.play(banner_large.create())
        self.play(banner_large.expand())

Manim Sphinx Directive#

The manim.utils.docbuild.manim_directive directive works:

# conf.py
...
extensions = [
    "manim.utils.docbuild.manim_directive",
    ...
]
...

It needs to be escaped using the eval-rst directive

```{eval-rst}
.. manim:: MyScene

   class MyScene(Scene):
       def construct(self):
           self.camera.background_color = "#ece6e2"
           banner_large = ManimBanner(dark_theme=False).scale(0.7)
           self.play(banner_large.create())
           self.play(banner_large.expand())
```

gives

Note

The following gives a WARNING: Directive 'manim' cannot be mocked: MockingError:  MockStateMachine has not yet implemented attribute 'insert_input' message and fails to render. This is probably also why the previous directive does not give a proper width.

```{manim} MyScene

class MyScene(Scene):
    def construct(self):
        self.camera.background_color = "#ece6e2"
        banner_large = ManimBanner(dark_theme=False).scale(0.7)
        self.play(banner_large.create())
        self.play(banner_large.expand())
```

References#