ManimGL is the OpenGL-based mathematical animation engine written and
maintained by Grant Sanderson (3Blue1Brown). It is the same library used to
render the videos on the 3Blue1Brown YouTube channel — a Python framework for
programming precise, expressive animations of mathematical objects, equations,
graphs, and 3D surfaces, then writing them out as mp4/gif/png files or
previewing them live in an interactive OpenGL window.
Important
This is the original
3b1b/manim(also called ManimGL). It is not the same project asmanim-community/manim(ManimCE). The two libraries share a common ancestor and a similar high-level API, but they have diverged significantly. Code, default behavior, available classes, CLI flags, configuration files, and renderer implementations are different. If you arrived here looking for the community-maintained edition, installmanim(the package on PyPI) instead ofmanimgl.
1. Quick Start
ManimGL turns Python Scene classes into rendered animations. You write a
class that inherits from Scene, implement a construct method, populate it
with mobjects (mathematical objects) and play calls, then run the
manimgl command on your file.
Key features at a glance:
- OpenGL renderer with shader-based vector graphics for sharp output at any resolution.
- Live interactive window — preview animations as you write them, drop into an IPython shell mid-scene.
- LaTeX integration via the
TexandTexTextmobjects with 100+ built-in TeX templates. - Rich mobject library — geometry (Circle, Polygon, Arrow, ...), text (
Text,Tex), 3D surfaces (Sphere, Torus, Cube, ...), coordinate systems (Axes, NumberPlane, ComplexPlane), value trackers, vector fields, stream lines, and matrices. - Animation library — fades, transforms, creation effects, indications, motion along paths, decimal counters, rotations, lagged-start groups, and string-matching transforms (
TransformMatchingStrings,TransformMatchingTex). - Updaters and the
.animatesyntax — every Mobject method can be animated by chaining.animate.<method>(...)or by attaching frame-by-frame updater functions. - CLI quality presets —
-l(480p),-m(720p),--hd(1080p),--uhd(4K) — plus arbitrary-r WxHand--fps. - Hot-reload during development via the embedded IPython shell (
reload_scene()), and resume-mid-scene with-e <line>.
Prerequisites: Python 3.8+, FFmpeg on PATH, and a working LaTeX
distribution (TeX Live, MacTeX, or MiKTeX) if you intend to render Tex /
TexText. Most modern systems already meet the OpenGL/GLSL 3.30 requirement.
Install and run a one-liner:
pip install manimgl
manimgl example_scenes.py OpeningManimExample
The first command pulls ManimGL plus its Python dependencies. The second
opens an OpenGL window that plays the OpeningManimExample scene from the
example_scenes.py file shipped with this repo.
Tip
Skip the file argument entirely (
manimgl) and ManimGL drops you straight into a blank interactive scene withfrom manimlib import *already executed in the IPython namespace — perfect for live experimentation.
Minimal scene file (hello.py):
from manimlib import *
class HelloManim(Scene):
def construct(self):
circle = Circle()
circle.set_fill(BLUE, opacity=0.5)
square = Square()
self.play(ShowCreation(square))
self.play(ReplacementTransform(square, circle))
self.wait()
Run it:
manimgl hello.py HelloManim
Expected: an OpenGL window opens, draws a square, morphs it into a blue filled circle, and pauses for one second.
What to try next:
- Add
-wto write the result to an.mp4instead of previewing. - Replace
Square()withTex(R"e^{\pi i} = -1")for a LaTeX equation. - Browse the bundled
example_scenes.py— it ships every major capability as a runnable demo.
2. Installation
2.1 System Requirements
| Requirement | Detail |
|---|---|
| Python | 3.8 or newer (depends on ipython>=8.18.0 and modern type-hint syntax). |
| OpenGL | A driver and GPU supporting GLSL 3.30 core profile (covers virtually every desktop machine made since 2010). |
| FFmpeg | Must be on PATH. Used to encode .mp4/.gif/.mov files and to mux audio. |
| LaTeX | A latex (or xelatex / lualatex) binary on PATH plus dvisvgm. Required only if you use Tex or TexText. |
| Operating system | Linux, macOS, and Windows are all supported. |
Important
If
Tex(...)raisesLatexError: LaTeX compilation failed, the issue is almost always your TeX distribution rather than ManimGL. Install TeX Live (Linux), MacTeX (macOS), or MiKTeX (Windows) and re-run. This is the single most-reported issue on the project tracker (issues #2446, #2440, #2314, #2390, etc.).
2.2 Install Methods
pip
pip install manimgl
Source (development)
git clone https://github.com/3b1b/manim.git
cd manim
pip install -e .
pipx
pipx install manimgl
After install, the manimgl console script is on your PATH, and the
importable Python package is manimlib (from manimlib import *).
Warning
pip install maniminstalls the wrong project.manimon PyPI is ManimCE (manim-community/manim).manimglon PyPI is this project.
2.3 Runtime Dependencies
ManimGL pulls in the following Python packages (from requirements.txt):
| Group | Packages |
|---|---|
| Rendering | moderngl, moderngl_window, PyOpenGL, pyglet (transitively via moderngl_window) |
| Math / shapes | numpy, scipy, sympy, mapbox-earcut, skia-pathops, isosurfaces, trimesh, pywavefront |
| Text & SVG | manimpango>=0.6.0, svgelements>=1.8.1, fontTools, Pillow |
| Color | colour, matplotlib (colormaps only) |
| Audio | pydub |
| Interactive | ipython>=8.18.0, pyperclip, screeninfo |
| Caching & config | addict, appdirs, diskcache, pyyaml, validators, tqdm, rich |
| Misc | pygments, setuptools, audioop-lts (Python 3.13+ only) |
pip install manimgl installs all of these automatically.
2.4 Verifying the Install
manimgl --version
ManimGL v1.7.x
If the binary prints a green ManimGL vX.Y.Z, the install succeeded. If you
also see a Python traceback after the version line, you have a missing
runtime dependency (most often FFmpeg or LaTeX) — see Troubleshooting.
A fast smoke test that exercises the renderer end-to-end:
manimgl
This launches a BlankScene with an OpenGL window and an embedded IPython
shell. From the shell:
play(ShowCreation(Circle()))
A blue ring should be drawn into the window over one second.
3. Configuration
ManimGL reads configuration from layered sources, merged in this order (later sources override earlier ones):
manimlib/default_config.ymlshipped inside the package.custom_config.ymlin the directory you runmanimglfrom.- The file passed via
--config_file /path/to/file.yml. - CLI flags such as
-l,--fps,--vcodec.
Note
Most users only need a
custom_config.ymlnext to their scene files. The Admin Guide's Configuration Reference documents every key in the schema.
3.1 Minimal custom_config.yml
directories:
base: "./manim_output"
subdirs:
output: "videos"
raster_images: "images"
camera:
background_color: "#0e0e1a"
fps: 60
tex:
template: "default"
text:
font: "Consolas"
alignment: "LEFT"
What each block does:
directories: where rendered videos, images, sounds, and downloaded assets go.camera: default frame size, frame rate, and background color for every scene unless overridden.tex: which TeX template (a preamble bundle) to use forTex/TexText.text: default font/alignment forText(...)mobjects.
3.2 Common Settings
| Goal | Setting | Example |
|---|---|---|
| Default to 1080p | camera.resolution |
resolution: (1920, 1080) |
| Slow-mo 60fps | camera.fps |
fps: 60 |
| Different background | camera.background_color |
background_color: "#101010" |
| Open window in upper-right of screen | window.position_string |
position_string: UR |
| Write videos under a project root | directories.base |
base: "/Users/me/manim_files" |
| Choose multi-monitor screen | window.monitor_index |
monitor_index: 1 |
| Enable verbose logs | log_level |
log_level: "DEBUG" |
| Use Chinese-capable LaTeX | tex.template |
template: "ctex" |
3.3 CLI Flags Used at Render Time
| Flag | Purpose |
|---|---|
-w / --write_file |
Render to .mp4 instead of previewing. |
-s / --skip_animations |
Skip animations and write only the last frame as a .png. |
-l / --low_quality |
480p (854x480). |
-m / --medium_quality |
720p (1280x720). |
--hd |
1080p (1920x1080). |
--uhd |
4K (3840x2160). |
-r WxH / --resolution WxH |
Arbitrary resolution, e.g. -r 1440x900. |
--fps INT |
Override frame rate. |
-c COLOR / --color COLOR |
Override background color (any name, hex, or rgb() string). |
-t / --transparent |
Render to .mov with alpha (uses prores_ks codec). |
-i / --gif |
Save output as .gif instead of .mp4. |
-o / --open |
Open the rendered file when done. Implies -w. |
--finder |
Reveal the rendered file in Finder/Explorer when done. Implies -w. |
-f / --full_screen |
Show preview window in full screen. |
-p / --presenter_mode |
Pause on every wait() until you press space or right-arrow. |
-a / --write_all |
Render every Scene subclass in the file. |
-n N[,M] / --start_at_animation_number |
Skip to animation N (and optionally stop at M). |
-e LINE / --embed |
Insert self.embed() at the given line and drop into IPython there. |
--vcodec STR |
Override FFmpeg video codec (default libx264). |
--pix_fmt STR |
Override FFmpeg pixel format (default yuv420p). |
--file_name NAME |
Override output file name. |
--video_dir PATH |
Override output directory. |
--config_file PATH |
Use a non-default custom config file. |
--log-level LEVEL |
DEBUG / INFO / WARNING / ERROR / CRITICAL. |
--leave_progress_bars |
Keep tqdm bars visible after the scene finishes. |
--show_animation_progress |
Print a progress bar for each individual animation. |
--prerun |
Run the scene once with skipping enabled to compute the total frame count first (drives the global progress bar). |
--subdivide |
Write each animation to its own movie file under a folder. |
--clear-cache |
Erase the LaTeX/Text mobject cache and exit. |
--autoreload |
Re-import the scene module on every IPython cell so edits in your editor take effect without leaving the shell. |
-q / --quiet |
Suppress most stdout. |
-v / --version |
Print version and exit. |
Tip
If you don't pass any of
-w,-o,--finder, the renderer assumes "preview only" and opens an interactive window without writing files.
4. Core Usage
A ManimGL session is built from three pieces — mobjects (the things on screen), animations (changes over time), and a scene (the runtime that plays them). This section walks through each layer.
4.1 Writing a Scene
Subclass Scene (or one of its variants) and implement construct:
from manimlib import *
class FirstScene(Scene):
def construct(self):
text = Text("Hello, ManimGL", font_size=72)
self.play(Write(text))
self.wait()
self.play(text.animate.shift(2 * UP).set_color(YELLOW))
self.wait()
Run it: manimgl first.py FirstScene.
The Scene subclasses you can pick from:
| Class | When to use |
|---|---|
Scene |
The default. Camera frame faces the XY plane. |
ThreeDScene |
Subclass of Scene that adds a 3D-aware add(). Use when working with Sphere, Torus, Cube, etc. |
InteractiveScene |
Adds drag-to-select, color palette, crosshair overlay, undo/redo, and a Cmd+C / Cmd+V workflow for live scene authoring. |
Note
Scene.__init__reads camera, file-writer, and runtime config from the globalmanim_configdict (built from your YAML files plus CLI overrides), then layers in anydefault_camera_config/default_file_writer_configyou set on your subclass. Per-instance overrides win.
4.2 The .animate Syntax
Every Mobject method can be turned into an animation by inserting .animate
between the mobject and the method call. ManimGL captures the call, runs the
real method on a copy, and interpolates from the original to the result.
self.play(circle.animate.shift(2 * RIGHT))
self.play(grid.animate.set_color(YELLOW))
self.play(grid.animate.apply_complex_function(lambda z: z**2), run_time=6)
self.play(grid.animate.set_height(TAU - MED_SMALL_BUFF))
Chaining works:
self.play(circle.animate.shift(2 * RIGHT).scale(0.25))
4.3 Common Mobjects
These are imported by from manimlib import *. The full list lives in the
Admin Guide; the table below covers the ones you reach for most often.
| Category | Mobject |
|---|---|
| Geometry | Circle, Dot, SmallDot, Ellipse, Square, Rectangle, RoundedRectangle, Polygon, RegularPolygon, Triangle, Line, DashedLine, Arrow, Vector, Arc, ArcBetweenPoints, CurvedArrow, Annulus, AnnularSector, Sector, Elbow, CubicBezier |
| Text & math | Text, Tex, TexText, MarkupText, Code, BulletedList |
| Numbers | DecimalNumber, Integer, NumberLine, UnitInterval, Slider |
| Coordinate systems | Axes, ThreeDAxes, NumberPlane, ComplexPlane |
| Functions | ParametricCurve, FunctionGraph, ImplicitFunction |
| 3D | Sphere, Torus, Cylinder, Cone, Cube, Prism, Disk3D, Square3D, Line3D, Dodecahedron, SurfaceMesh, TexturedSurface |
| Containers | Group, VGroup, VGroup3D, SGroup |
| Probability | SampleSpace, BarChart |
| Decoration | SurroundingRectangle, BackgroundRectangle, Cross, Underline, Brace |
| Tracking | ValueTracker, ExponentialValueTracker, ComplexValueTracker, TracedPath, TracingTail, AnimatedBoundary |
| Vector fields | VectorField, TimeVaryingVectorField, StreamLines, AnimatedStreamLines |
| Matrices | Matrix, IntegerMatrix, DecimalMatrix, TexMatrix, MobjectMatrix |
| Boolean ops | Union, Difference, Intersection, Exclusion |
| Interactive widgets | Button, Checkbox, LinearNumberSlider, ColorSliders, Textbox, ControlPanel, MotionMobject |
Building shapes
circle = Circle(radius=1.5, color=BLUE).set_fill(BLUE_E, opacity=0.5)
square = Square(side_length=2, fill_color=RED, fill_opacity=0.3)
arrow = Arrow(LEFT, 2 * RIGHT, buff=0)
poly = RegularPolygon(n=6).scale(1.2)
Text and equations
title = Text("Pythagorean Theorem", font="Arial", font_size=72, t2c={"theorem": YELLOW})
eq = Tex(r"a^2 + b^2 = c^2", font_size=96)
prose = TexText(r"Therefore $\triangle ABC$ is right-angled.")
Text is layered on Pango/HarfBuzz via manimpango and supports per-token
style maps:
Text(
"And the same as slant and weight",
font="Consolas",
t2s={"slant": ITALIC},
t2w={"weight": BOLD},
t2c={"slant": ORANGE, "weight": RED},
)
The t2c / t2s / t2w / t2f dicts (text-to-color, text-to-slant,
text-to-weight, text-to-font) take literal substrings and apply the value
to every match.
Indexing into Tex
Tex mobjects are indexable by substring:
equation = Tex(R"e^{\pi i} = -1", font_size=144)
self.play(FlashAround(equation["e"]))
self.play(Indicate(equation[R"\pi"]))
self.play(equation[re.compile(r"\w\^2")][0].animate.set_color(RED))
If a substring's symbols aren't drawn in source order (common with \sum,
\frac, \infty), pre-declare it with isolate=[...]:
Tex(R"\sum_{n=1}^\infty {1 \over n^2}", isolate=[R"\infty"])
Coordinate systems
Axes, NumberPlane, ComplexPlane, and ThreeDAxes all inherit from
CoordinateSystem, which exposes:
| Method | Shorthand | What it does |
|---|---|---|
coords_to_point(x, y[, z]) |
c2p |
Convert a point in axis units to scene coordinates. |
point_to_coords(point) |
p2c |
Inverse of c2p. |
get_graph(func, **kw) |
— | Render a ParametricCurve of func. |
get_graph_label(graph, label) |
— | Place a label next to a graph. |
input_to_graph_point(x, graph) |
i2gp |
The point on a graph at a given x. |
add_coordinate_labels(...) |
— | Draw DecimalNumbers along the axes. |
axes = Axes(
x_range=(-1, 10),
y_range=(-2, 2, 0.5),
height=6,
width=10,
axis_config=dict(stroke_color=GREY_A, numbers_to_exclude=[0]),
)
axes.add_coordinate_labels(font_size=20)
graph = axes.get_graph(lambda x: 2 * np.sin(x), color=BLUE)
label = axes.get_graph_label(graph, R"\sin(x)")
self.play(ShowCreation(graph), FadeIn(label, RIGHT))
4.4 Animations
Every animation is a class that takes one or more mobjects plus optional
run_time, rate_func, lag_ratio, and time_span. Pass instances to
self.play(...).
Creation / destruction:
| Animation | Effect |
|---|---|
ShowCreation(m) |
Draws m along its path. |
Uncreate(m) |
Reverses ShowCreation. |
Write(m) |
Like ShowCreation but tuned for text/equations. |
DrawBorderThenFill(m) |
Outline first, then fill. |
ShowIncreasingSubsets(m) |
Reveal submobjects one by one. |
ShowSubmobjectsOneByOne(m) |
Show then hide each submobject in turn. |
AddTextWordByWord(m) |
Append words sequentially. |
Fades:
| Animation | Effect |
|---|---|
FadeIn(m) |
Opacity 0 → 1. |
FadeOut(m) |
Opacity 1 → 0 and remove from scene. |
FadeInFromPoint(m, point) / FadeOutToPoint(m, point) |
Fade with translation from/to a point. |
FadeTransform(a, b) |
Cross-fade a into b. |
FadeTransformPieces(a, b) |
Per-submobject cross-fade. |
VFadeIn(m) / VFadeOut(m) / VFadeInThenOut(m) |
Stroke/fill-only fade for VMobjects (avoids point-count changes). |
Growing:
| Animation | Effect |
|---|---|
GrowFromPoint(m, point) / GrowFromCenter(m) / GrowFromEdge(m, edge) |
Scale up from origin. |
GrowArrow(arrow) |
Draw an arrow growing along its length. |
Indication:
| Animation | Effect |
|---|---|
Indicate(m) |
Briefly scales and color-pulses. |
FocusOn(point) |
Highlight a region with a fade-in dot. |
Flash(point) |
Radiating lines from a point. |
CircleIndicate(m) |
Surround m with an animated circle. |
ShowPassingFlash(m) / VShowPassingFlash(m) |
A bright stroke moves along m. |
FlashAround(m) / FlashUnder(m) |
Trace a flash around or below m. |
ShowCreationThenDestruction(m) / ShowCreationThenFadeOut(m) |
Compose creation with destruction/fade. |
ApplyWave(m) |
Sinusoidal wobble. |
WiggleOutThenIn(m) |
Quick wiggle. |
TurnInsideOut(m) |
Reverse normal vector — useful for surfaces. |
FlashyFadeIn(m) |
Big-bang reveal. |
Movement and transforms:
| Animation | Effect |
|---|---|
Transform(a, b) |
Morph a into b, leaving a mutated. |
ReplacementTransform(a, b) |
Like Transform but replaces a with b in scene. |
TransformFromCopy(a, b) |
Copy of a becomes b; a stays put. |
MoveToTarget(m) |
Animate m to its m.target (set via m.generate_target()). |
ApplyMethod(m.method, *args) |
Animate the result of calling method on m. (Often replaced by m.animate.method(...).) |
ApplyFunction(func, m) / ApplyPointwiseFunction(func, m) / ApplyComplexFunction(func, m) |
Apply a function to every point of m. |
ApplyMatrix(matrix, m) |
Apply a 2x2 or 3x3 matrix. |
Restore(m) |
Animate back to the most recent m.save_state(). |
FadeToColor(m, color), ScaleInPlace(m, factor), ShrinkToCenter(m) |
Convenience subclasses. |
CyclicReplace(*ms) / Swap(a, b) |
Permute mobjects in place. |
MoveAlongPath(m, path) |
Slide m along a VMobject path. |
Rotating(m) / Rotate(m, angle) |
Continuous rotation / fixed-angle rotation. |
Homotopy(func, m), SmoothedVectorizedHomotopy(...), ComplexHomotopy(...), PhaseFlow(...) |
Animate by a continuous function of position and time. |
Numbers:
| Animation | Effect |
|---|---|
ChangingDecimal(decimal, func_of_time) |
Set a DecimalNumber's value each frame from a callback. |
ChangeDecimalToValue(decimal, target) |
Counter sweeps to target. |
CountInFrom(decimal, start) |
Counter sweeps from start to current. |
String matching:
| Animation | Effect |
|---|---|
TransformMatchingStrings(a, b, matched_keys=..., key_map=...) |
Match common substrings in two StringMobjects and morph piece by piece. |
TransformMatchingTex(a, b) / TransformMatchingShapes(a, b) |
Variants for Tex and shape-based matching. |
Updaters:
| Animation | Effect |
|---|---|
UpdateFromFunc(m, func) |
Run func(m) every frame for the animation's duration. |
UpdateFromAlphaFunc(m, func) |
Run func(m, alpha) where alpha ramps 0->1. |
MaintainPositionRelativeTo(m, ref) |
Pin m to ref while another animation runs. |
Composition:
| Animation | Effect |
|---|---|
AnimationGroup(*anims) |
Play multiple animations in parallel. |
Succession(*anims) |
Play sequentially; total time = sum of run times. |
LaggedStart(*anims, lag_ratio=...) |
Stagger overlapping starts. |
LaggedStartMap(Anim, group, **kw) |
Apply Anim to each item in a group with stagger. |
Broadcast(m) |
Concentric expanding copies. |
Tip
Pass
time_span=(start, end)to any animation to delay its start or stretch it across part of the play call. Useful for choreographing multiple animations inside a singleplay.
4.5 Updaters and Always-Redraw
ManimGL's updater system is a per-frame callback attached to a mobject:
square = Square()
brace = always_redraw(Brace, square, UP) # Re-runs Brace(square, UP) every frame
label = TexText("Width = 0.00")
number = label.make_number_changeable("0.00")
label.always.next_to(brace, UP) # Pin label above the brace
number.f_always.set_value(square.get_width) # Read width each frame
self.add(square, brace, label)
self.play(square.animate.set_width(5, stretch=True))
Four flavors:
mobject.add_updater(lambda m: ...)— explicit, always available. Optional second argument isdt.mobject.always.<method>(*args)— callsmethod(*args)every frame.mobject.f_always.<method>(*funcs)— likealwaysbut each arg is a no-arg callable evaluated every frame.always_redraw(constructor, *args, **kwargs)— callconstructor(...)every frame and replace the mobject's data with the result.
Note
Updaters fire on every frame the mobject is in the scene, including during animations. They run after the animation's
interpolate(alpha)method, so an updater that sets a property will override the animation's effect on that property. Usemobject.suspend_updating()/resume_updating()if you need to pause updaters temporarily.
4.6 Rate Functions
Rate functions reshape time within an animation. ManimGL ships these (and
you can pass any Callable[[float], float] taking values in [0, 1]):
| Function | Curve |
|---|---|
linear |
Constant velocity |
smooth |
Default — ease in then out |
rush_into / rush_from |
Start fast / end fast |
slow_into |
Decelerate to a stop |
double_smooth |
Double-S curve |
there_and_back |
Out and back |
there_and_back_with_pause |
Out, pause at peak, back |
running_start |
Pulls back, then forward |
overshoot |
Past target then settles |
not_quite_there |
Stops short of target |
wiggle |
N wiggles |
lingering |
Most of run_time at the end |
exponential_decay |
Approaches 1 asymptotically |
squish_rate_func(func, a, b) |
Compress an existing curve into [a, b] |
Pass them via rate_func=:
self.play(square.animate.scale(2), rate_func=there_and_back, run_time=2)
4.7 Camera Frame and Three Dimensions
The active camera is self.camera, and its frame mobject is self.frame
(also reachable as self.camera.frame). The frame is itself a Mobject —
animate it the same way as anything else:
self.play(self.frame.animate.scale(0.75).to_corner(UL))
self.frame.add_updater(lambda m, dt: m.increment_theta(-0.1 * dt))
For 3D scenes, subclass ThreeDScene and use Sphere, Torus, Cube,
Cylinder, TexturedSurface, etc. Reorient the camera with
self.frame.reorient(theta_deg, phi_deg, gamma_deg) or
self.frame.set_phi(...) / set_theta(...).
Mobject.fix_in_frame() glues a mobject to the camera frame so it stays in
place even as the camera moves — perfect for HUD overlays like titles.
4.8 Live Interactive Development
Calling self.embed() inside construct drops you into an IPython shell
with the scene paused and the local namespace (self, mobjects, play,
add, remove, clear, wait, save_state, restore) ready:
class InteractiveDevelopment(Scene):
def construct(self):
circle = Circle()
square = Square()
self.play(ShowCreation(square))
self.embed()
# The shell now has `self`, `square`, `circle`, `play`, `add`...
Tips while embedded:
touch()— give the OpenGL window keyboard/mouse focus.- Hold
dwhile moving the mouse to rotate the camera. - Hold
fto pan,z+ scroll to zoom. - Press
rto reset the camera. - Press
qto release window focus and return to the shell. - Type
reload_scene()to pick up source edits without restarting. - Type
exit()or hit Ctrl+D to end the scene.
Tip
Run
manimgl your_file.py YourScene -e 42to injectself.embed()at line 42 automatically. No need to edit the source.
5. Common Tasks
5.1 Render a Scene to an MP4
Goal: turn MyScene in myfile.py into MyScene.mp4.
manimgl myfile.py MyScene -w
Verify: look in videos/ (or wherever directories.output points) for
MyScene.mp4.
If it fails: ensure FFmpeg is on PATH (ffmpeg -version), and check
the log for codec errors. Pass --vcodec libx264 to force the default.
5.2 Render Every Scene in a File
manimgl myfile.py -a
ManimGL discovers every Scene subclass and renders them in order. If the
file defines SCENES_IN_ORDER = [...], that list takes precedence.
5.3 Save the Final Frame as a Still Image
manimgl myfile.py MyScene -s -w
Without -s, the renderer writes a video. With -s, it skips animations
entirely and writes only the last frame as a .png. Adding -w forces
file output instead of preview.
5.4 Quick Iteration at Low Quality
manimgl myfile.py MyScene -l
-l switches to 480p (854x480) and renders far faster. Combine with -w
to write or omit it to preview.
5.5 Render a 4K Final Master
manimgl myfile.py MyScene --uhd -w --fps 60
5.6 Render with a Transparent Background
manimgl myfile.py MyScene -t -w
Output is a .mov using the prores_ks codec with an alpha channel — drop
it directly into a video editor.
Warning
Transparency on Windows has known issues at certain resolutions (issue #2110). If the output looks corrupt, try the same scene with a different
--vcodecsuch asqtrle, or render on Linux/macOS.
5.7 Save a Scene as a GIF
manimgl myfile.py MyScene -i -w
The -i flag changes the file extension to .gif and clears the vcodec
so FFmpeg picks an appropriate one.
5.8 Skip to the N-th Animation
manimgl myfile.py MyScene -n 5
Skips animations 0–4 and starts rendering from animation 5. Use -n 5,12
to render animations 5 through 12 inclusive.
5.9 Drop into IPython at a Specific Line
manimgl myfile.py -e 42
Inserts self.embed() after line 42 of the file and starts the scene whose
nearest preceding class line is the one to use. No source edits required.
5.10 Convert Live-Authored Selection Back to Code
While running an InteractiveScene with a window, hold Ctrl and drag to
highlight a region (or just tap Ctrl to select the mobject under the
cursor). Then:
| Keystroke | Effect |
|---|---|
| Cmd/Ctrl + C | Copy IDs of selection to clipboard. |
| Cmd/Ctrl + V | Paste — interpreted as a copied mobject, copied LaTeX, or copied text. |
| Cmd/Ctrl + Z | Undo the last operation. |
| Cmd/Ctrl + Shift + Z | Redo. |
| Cmd/Ctrl + S | Save selection to file. |
| g (hold) | Grab and drag selection. |
| h / v / z (hold) | Constrain drag to X / Y / Z axis. |
| t (hold) | Resize selection. |
| Cmd + t | Toggle between selecting top-level mobjects and inner submobjects. |
5.11 Use presenter_mode for Talks
manimgl talk.py SlideOne -p
Every self.wait() becomes a hard pause that resumes only when you press
Space or ->. Combine with -w only if you want to render the
talk-paced version to file.
5.12 Add Sound to a Scene
class WithSound(Scene):
def construct(self):
self.add_sound("intro.wav", time_offset=0)
self.play(Write(Text("Hello"), run_time=2))
self.wait()
Sounds are pulled from directories.sounds (default sounds/ next to your
scene file). Supported formats are anything pydub can decode (WAV, MP3,
M4A, etc.).
5.13 Make Numbers in Tex Animate
tex = Tex("x^2 + y^2 = 4.00")
value = tex.make_number_changeable("4.00")
value.add_updater(lambda v: v.set_value(circle.get_radius() ** 2))
self.add(tex)
Or animate it explicitly:
self.play(ChangeDecimalToValue(value, 9.0))
5.14 Build an Interactive Control Panel
class ControlsExample(Scene):
drag_to_pan = False
def setup(self):
self.textbox = Textbox()
self.checkbox = Checkbox()
self.color_picker = ColorSliders()
self.panel = ControlPanel(
Text("Text", font_size=24), self.textbox, Line(),
Text("Show/Hide", font_size=24), self.checkbox, Line(),
Text("Color", font_size=24), self.color_picker,
)
self.add(self.panel)
def construct(self):
text = Text("text", font_size=96)
# ...attach an updater that reads from self.textbox.get_value()...
5.15 Clear the LaTeX/Text Cache
ManimGL caches compiled SVGs for Tex and Text mobjects in
appdirs.user_cache_dir("manim") (or wherever directories.cache points).
To wipe it:
manimgl --clear-cache
This is what to try first if a Tex mobject keeps rendering as it did
before you fixed the source string.
6. Troubleshooting
Diagnostic Decision Tree
Did `manimgl --version` print without errors?
+- No -> Your install is broken. Reinstall: `pip install --force-reinstall manimgl`.
+- Yes ->
Did the scene fail before any rendering?
+- Yes -> Python import error -> check Tex/Text imports & dependencies.
+- No ->
Did Tex/TexText fail?
+- Yes -> LaTeX issue -> see below.
+- No ->
Did FFmpeg fail?
+- Yes -> FFmpeg not on PATH or unsupported codec -> see below.
+- No -> Rendering visual issue -> file a GitHub issue.
Error: LatexError: LaTeX compilation failed
Cause: A Tex(...) or TexText(...) call failed because either (a)
your LaTeX distribution is missing, (b) it can't find a required package,
or (c) the source string has a syntax error LaTeX rejects.
Solution:
- Verify a LaTeX binary is on
PATH:latex --version. - If installed but missing packages, install one of: TeX Live
(
sudo apt install texlive-fullor download from TUG), MacTeX, MiKTeX. - The default template loads
amsmath,amssymb,physics,mathrsfs,dsfont,xcolor, etc. If a customtex.templateyou reference is missing packages, switch totemplate: "default"ortemplate: "basic"incustom_config.yml. - Read the LaTeX log printed below the error — it points to the exact line.
Prevention: test your Tex strings in a standalone .tex file first.
Issues #2446, #2440, #2314, and #2390 all stem from missing LaTeX installs.
Error: Couldn't find ffmpeg or avconv - defaulting to ffmpeg
Cause: FFmpeg is not on PATH (issue #2417, frequent on Chromebook
Crostini and minimal Docker images).
Solution:
# macOS
brew install ffmpeg
# Debian/Ubuntu
sudo apt install ffmpeg
# Windows (with Chocolatey)
choco install ffmpeg
Then reopen your shell and re-run manimgl.
Prevention: set file_writer.ffmpeg_bin: "/full/path/to/ffmpeg" in
custom_config.yml if you can't put FFmpeg on the system PATH.
Error: IndexError: list index out of range on UpdatersExample
Cause: Updater pipeline regression visible on Windows + Python 3.10 + manimgl 1.7.2 (issues #2456, #2457).
Solution:
- Update to the latest
manimgl:pip install --upgrade manimgl. - If the error persists, replace
mobject.always.<method>(...)with the long formmobject.add_updater(lambda m: m.<method>(...)).
Prevention: monitor the releases page on the GitHub repo.
Error: TypeError: 'Animation only works for Mobjects.'
Cause: You passed a non-Mobject to an animation (e.g.
FadeIn(text_string) instead of FadeIn(Text(text_string))).
Solution: wrap the value in the appropriate Mobject constructor first.
Error: Exception: 'Whoops, looks like you accidentally invoked the method you want to animate'
Cause: You called m.method() directly inside self.play(...) instead
of using m.animate.method() or ApplyMethod(m.method, ...).
Solution: rewrite
self.play(circle.shift(2 * RIGHT)) # WRONG
self.play(circle.animate.shift(2 * RIGHT)) # RIGHT
Error: Exception: 'Trying to restore without having saved'
Cause: Restore(m) or m.restore() called before m.save_state().
Solution: call m.save_state() once before the first Restore in the
animation.
Error: Exception: 'MoveToTarget called on mobject without attribute target'
Cause: MoveToTarget(m) requires m.target to be set first via
m.generate_target() then mutating m.target.
Solution:
m.generate_target()
m.target.shift(2 * UP).set_color(RED)
self.play(MoveToTarget(m))
Error: ValueError: At least 2 mobjects needed for Union/Intersection/Exclusion.
Cause: Boolean operations require two or more VMobjects.
Solution: pass at least two: Union(circle, square).
Error: Exception: 'Functions passed to ApplyFunction must return object of type Mobject'
Cause: The function passed to ApplyFunction returned something other
than a Mobject — possibly None because it mutated in place.
Solution: make the function return its argument:
self.play(ApplyFunction(lambda m: m.shift(UP) and m, target))
Or just use ApplyMethod / .animate.shift(UP) instead.
Error: IOError: <file_name> not Found
Cause: ManimGL is looking in directories.raster_images,
directories.vector_images, directories.three_d_models, or
directories.sounds for the file, and not finding it.
Solution: drop the asset into the corresponding subdirectory of
directories.base, or pass an absolute path.
Error: Exception: 'Only xz and xy are valid floor planes'
Cause: Passed an unsupported argument to Scene.set_floor_plane. Only
"xy" (default) and "xz" are accepted.
Solution: call self.set_floor_plane("xz") for camera setups where
the ground is the X-Z plane, or omit the call to keep the X-Y default.
Visual issue: black spots on surface singularities
Cause: Numerical instabilities at the singular points of parametric
surfaces (issue #2452 — e.g. the poles of a Sphere).
Solution: subtly perturb the singular points or increase
resolution=(theta_steps, phi_steps) on the surface mobject.
Visual issue: axis numbers disappear when indicating multiple ones at once
Cause: Issue #2352 — playing several Indicate animations on axis
labels can cause earlier ones to vanish.
Solution: wrap them in a LaggedStartMap(Indicate, axes_numbers)
instead of multiple parallel Indicate calls.
Visual issue: Tex substring access fails to color the right symbol
Cause: LaTeX draws the symbol in a different order than it appears in
the source string (common with \sum, \frac, \infty).
Solution: declare the substring up front:
Tex(R"\sum_{n=1}^\infty {1 \over n^2}", isolate=[R"\infty"])
Visual issue: Ellipse draws a wrong curve
Cause: Known issue #2436 — when the ellipse is highly eccentric, its quadratic-bezier approximation has visible cusp artifacts.
Solution: use ParametricCurve with a parametric ellipse function for
high-precision rendering, or stretch a Circle non-uniformly.
Visual issue: TexText.make_number_changeable doesn't work
Cause: Issue #2206 — under some templates the spacing math around the number's bounding box drifts after replacement.
Solution: use Tex (math mode) instead of TexText (text mode) when
the number is the only thing that changes.
Visual issue: TexText does not display
Cause: Issue #2358 — TexText with empty content or with characters outside the loaded font.
Solution: ensure the source string is non-empty and that your TeX
template loads a font that covers the characters you use (try
tex.template: "default").
Performance: scene runs slowly even with -l
Causes: too many submobjects, expensive updaters, or repeatedly
creating fresh mobjects per frame in always_redraw.
Solutions:
- Group static mobjects in a
VGroupso they're rendered as one batch. - Replace
always_redraw(Constructor, *args)with a manual updater that reuses an existing mobject in place. - Use
-sto render a single frame and confirm the scene's static cost.
Misuse: manim (lowercase) installs the wrong project
Cause: Pip-installing manim gets the community edition (ManimCE), not
this project (issue #2441).
Solution: pip uninstall manim; pip install manimgl.
Caution
Mixing
manimglandmanim(ManimCE) in the same Python environment is not supported. Their import paths conflict (manimlibvsmanim) and some shared dependency versions disagree. Use one or the other per virtual environment.
7. FAQ
Q: How is this different from manim-community/manim (ManimCE)?
They share a common ancestor (Grant Sanderson's original manim) but have
diverged in 2020+. ManimGL is what 3Blue1Brown uses to make videos and
emphasizes a fast OpenGL preview, live IPython workflow, and Grant's
preferred mobject API. ManimCE prioritizes broad documentation, stability,
and pluggable renderers (Cairo + OpenGL). The CLI binaries are different
(manimgl vs manim), the import path is different (manimlib vs
manim), and the class APIs have small but real disagreements. Pick one
and stick with it for a project.
Q: Can I render with the GPU instead of OpenGL on the CPU?
OpenGL is the GPU pipeline — every Mobject is drawn through moderngl
and shaders. There is no separate "CPU renderer". Rendering speed is
dominated by how many submobjects you have and how often you regenerate
them, not by GPU saturation. (See discussion in issue #775.)
Q: Where does the rendered video go?
Into directories.output (default videos/) under directories.base from
your custom_config.yml. If directories.mirror_module_path: True, the
output path mirrors the source file's path so multi-scene projects stay
organized.
Q: How do I change the background color?
In custom_config.yml:
camera:
background_color: "#101010"
Or per-run with -c:
manimgl myfile.py MyScene -c "#101010"
Or per-scene by setting default_camera_config = dict(background_color="#101010")
on the Scene subclass.
Q: How do I use a different default font for Text?
text:
font: "Inter"
alignment: "LEFT"
Or per-mobject: Text("Hello", font="Inter"). The font must be installed
on the system; manimpango looks it up via Pango/HarfBuzz.
Q: How do I use Chinese (or any CJK) text in Tex?
Set tex.template: "ctex" in custom_config.yml. The bundled ctex
template includes \usepackage[UTF8]{ctex} and uses xelatex so it can
typeset Chinese characters and mixes of Chinese plus Latin scripts.
Q: How do I extend manim_config in code?
Import from manimlib.config:
from manimlib.config import manim_config
manim_config.camera.fps = 60
Modifications you make before scene construction take effect for that run.
Q: Does ManimGL support hot reload while editing scenes?
Yes. From inside an embedded shell, type reload_scene() and the module
will be re-imported and the scene restarted from the same embed_line. To
get cell-level hot reload (every line you type re-imports the file first),
add --autoreload to the manimgl command.
Q: How do I ensure my scenes are deterministic?
Scene.random_seed = <int> (default 0) on your subclass. ManimGL seeds
Python's random and NumPy's RNG with this value at scene start. Set it
to None to disable seeding.
Q: How do I run multiple scenes from the same file?
manimgl myfile.py SceneA SceneB SceneC
Or render every scene: manimgl myfile.py -a. If you need an explicit
order, define SCENES_IN_ORDER = [SceneA, SceneB, SceneC] at module level.
Q: How do I make the camera follow a moving object?
Attach an updater to self.frame:
self.frame.add_updater(lambda m: m.move_to(target_mobject.get_center()))
Q: How do I report a bug?
Open an issue at https://github.com/3b1b/manim/issues. Include the exact
manimgl --version output, your Python version, OS, the pip freeze of
your environment, the minimal scene that reproduces, and the full Python
traceback.
Q: Is the project actively maintained?
ManimGL is maintained by Grant Sanderson at the pace required to ship 3Blue1Brown videos. Pull requests are reviewed but throughput is limited. For a more "community-driven" cadence, look at ManimCE.
Q: Where can I see real-world ManimGL code?
Read the example_scenes.py file shipped with this repo for a tour. For
full-length productions, https://github.com/3b1b/videos is the public
mirror of every animation 3Blue1Brown has shipped — thousands of scenes.
Q: How do I save and restore scene state?
self.save_state()
# ...play animations...
self.restore_state(self.undo_stack[-1]) # or self.undo()
Scene.max_num_saved_states = 50 controls how deep the undo stack goes.
InteractiveScene exposes Cmd/Ctrl+Z and Cmd/Ctrl+Shift+Z for the same
operations interactively.
Q: Why does --version exit without rendering anything?
-v short-circuits the run loop after printing if no file argument was
supplied. Drop the flag (or pass a file) to render.
Q: Can I write my own animation classes?
Yes — subclass Animation and implement interpolate_submobject(self, sub, sub_starting, alpha) (per-submobject interpolation) or
interpolate_mobject(self, alpha) (whole-mobject interpolation). See
the Admin Guide's developer section for the exact contract.
Q: How do I disable the click-and-drag-to-pan behavior?
Set drag_to_pan = False on your Scene subclass (it is True by
default).
Q: Why is the window opening on the wrong monitor?
Set window.monitor_index: <n> in custom_config.yml to pick a specific
display, or window.position_string: UR (upper-right) / UL / DL /
DR / UO (upper middle) / etc. for placement on the chosen monitor.