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 theembed=True
option.- Movies in
_static
If you are careful to put your movies in the
_static/
directory that Sphinx copies (seehtml_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 tohttps://...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 thetree
and it will fail.
- The
manim
Sphinx directive You can include the
manim.utils.docbuild.manim_directive
directive in yourconf.py
file, then use Sphinx. Unfortunately, you must fallback to theeval-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)
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())
```