🛠️ Setup & first run
Install Manim, test your environment, and render your first scene.
Write Manim code, render it, preview it, and download the result — all in one place.
Public preview is open to everyone. Guests can use up to 5 AI prompts and 5 renders per day, then sign in for full access.
Install Manim, test your environment, and render your first scene.
Learn text, LaTeX, layout, simple animations, and clean scene structure.
Move from small demos to graphs, parameter shifts, and event-ready clips.
A distinct example that uses both an SVG image and an audio file, so students can immediately see that this lab supports more than plain text scenes.
SVGMobject("invmath.svg")self.add_sound("intro.mp3")This is intentionally separate from the main modules so it feels like a ready-made showcase, not just another lesson.
A new showcase scene that reads a hosted SVG outline, samples its geometry, and rebuilds the butterfly with Fourier epicycles.
svg2paths("butterfly3.svg")
Local note: if you run this outside the browser renderer, install svgpathtools in your Manim environment first.
ImageMobject("photo.png") or SVGMobject("logo.svg").self.add_sound("intro.mp3").
A simple scene needs a Scene class, a construct method, and one animation.
from manim import *
class HelloWorld(Scene):
def construct(self):
text = Tex("Hello, InvariantMath!")
self.play(Write(text))
self.wait(1)
Use text for titles and MathTex for mathematics.
title = Text("ODE–Integration Bee 2.0")
equation = MathTex(r"\int_0^1 x^n\,dx = \frac{1}{n+1}")
title.to_edge(UP)
equation.next_to(title, DOWN, buff=0.5)
This is a gentle first taste of mathematical motion: a curve starts concentrated, then spreads and softens as time moves forward.
from manim import *
import numpy as np
class HeatEquationDiffusion(Scene):
def construct(self):
self.camera.background_color = "#050816"
axes = Axes(
x_range=[-4, 4, 1],
y_range=[0, 3, 1],
x_length=10,
y_length=4,
axis_config={"include_ticks": False},
tips=False,
)
t = ValueTracker(0.05)
def heat(x, time):
sigma = 0.1 + time
return 2*np.exp(-(x**2)/(2*sigma)) / np.sqrt(1+4*time) + 0.3
graph = always_redraw(
lambda: axes.plot(
lambda x: heat(x, t.get_value()),
color=GOLD,
stroke_width=5
)
)
area = always_redraw(
lambda: axes.get_area(
graph,
x_range=[-4, 4],
color=ORANGE,
opacity=0.2
)
)
self.play(Create(axes))
self.play(FadeIn(area), Create(graph))
self.play(t.animate.set_value(2), run_time=6, rate_func=linear)
self.wait()
Some directions fall into the centre, others move away from it. This scene makes that contrast visible at a glance.
from manim import *
import numpy as np
class SaddlePoint(Scene):
def construct(self):
self.camera.background_color = "#050816"
plane = NumberPlane(
x_range=[-5, 5, 1],
y_range=[-3, 3, 1],
background_line_style={
"stroke_color": BLUE_E,
"stroke_opacity": 0.2,
}
)
def vf(p):
x, y = p[0], p[1]
return np.array([x, -y, 0]) * 0.5
stream = StreamLines(
vf,
stroke_width=1.5,
max_anchors_per_line=30
)
stable = Line(UP * 3, DOWN * 3, color=GREEN)
unstable = Line(LEFT * 4, RIGHT * 4, color=RED)
origin = Dot(ORIGIN, color=WHITE)
self.play(Create(plane))
self.play(Create(stable), Create(unstable), FadeIn(origin))
self.add(stream)
self.play(stream.create(), run_time=6)
self.wait()
This is one of the most magical Manim ideas: circles spin on circles, and together they draw a curve in real time.
from manim import *
import numpy as np
class FourierEpicycles(Scene):
def construct(self):
self.camera.background_color = "#050816"
time = ValueTracker(0)
def circle(radius, speed, phase):
return lambda t: radius*np.exp(1j*(speed*t+phase))
freqs = [
(1, 1, 0),
(0.5, -3, 0),
(0.3, 5, 0),
(0.2, -7, 0),
(0.15, 9, 0)
]
path = VMobject(color=YELLOW)
path.set_points_as_corners([ORIGIN, ORIGIN])
def update_path(m):
t = time.get_value() * TAU
z = sum(circle(r, s, p)(t) for r, s, p in freqs)
m.add_points_as_corners([np.array([z.real, z.imag, 0])])
path.add_updater(update_path)
def epicycles():
t = time.get_value() * TAU
z = 0
group = VGroup()
for r, s, p in freqs:
prev = z
z += circle(r, s, p)(t)
center = np.array([prev.real, prev.imag, 0])
tip = np.array([z.real, z.imag, 0])
group.add(Circle(radius=r).move_to(center).set_stroke(BLUE, 1))
group.add(Line(center, tip, color=WHITE))
return group
epi = always_redraw(epicycles)
self.add(epi, path)
self.play(time.animate.set_value(1), run_time=8, rate_func=linear)
self.wait()
from manim import *
import numpy as np
class FourierInfinity(Scene):
def compute_fourier_coeffs(self, points, n_terms=61):
"""
Compute discrete Fourier coefficients for a sampled closed curve.
Returns list of (frequency, coefficient), sorted by coefficient size.
"""
N = len(points)
ts = np.arange(N) / N
freqs = list(range(-n_terms // 2, n_terms // 2 + 1))
coeffs = []
for k in freqs:
c = np.sum(points * np.exp(-2j * np.pi * k * ts)) / N
coeffs.append((k, c))
coeffs.sort(key=lambda item: abs(item[1]), reverse=True)
return coeffs
def infinity_curve(self, n_samples=2000):
"""
A smooth infinity / lemniscate-like closed curve in the complex plane.
"""
t = np.linspace(0, 2 * np.pi, n_samples, endpoint=False)
# A clean, smooth figure-eight / infinity symbol
x = np.sin(t)
y = 0.55 * np.sin(2 * t)
return x + 1j * y
def construct(self):
self.camera.background_color = "#050816"
title = Text("Fourier Epicycles", font_size=34, color=GREY_A).to_edge(UP)
samples = self.infinity_curve()
coeffs = self.compute_fourier_coeffs(samples, n_terms=81)
time = ValueTracker(0)
# scale factor for scene size
SCALE = 3.2
N_USED = 35
def endpoint_and_groups(t):
z = 0j
circles = VGroup()
radii = VGroup()
tips = VGroup()
for freq, coeff in coeffs[:N_USED]:
prev = z
z += coeff * np.exp(2j * np.pi * freq * t)
center = np.array([SCALE * prev.real, SCALE * prev.imag, 0])
tip = np.array([SCALE * z.real, SCALE * z.imag, 0])
radius = SCALE * abs(coeff)
if radius > 0.015:
circles.add(
Circle(radius=radius)
.move_to(center)
.set_stroke(BLUE_B, width=1.4, opacity=0.45)
)
radii.add(
Line(center, tip, color=WHITE, stroke_width=2.0, stroke_opacity=0.9)
)
tips.add(Dot(tip, radius=0.015, color=YELLOW))
return np.array([SCALE * z.real, SCALE * z.imag, 0]), circles, radii, tips
trace = VMobject(color=YELLOW)
start_point = endpoint_and_groups(0)[0]
trace.set_points_as_corners([start_point, start_point])
def update_trace(mob):
p = endpoint_and_groups(time.get_value())[0]
old = mob.copy()
old.add_points_as_corners([p])
mob.become(old)
trace.add_updater(update_trace)
circles_group = always_redraw(lambda: endpoint_and_groups(time.get_value())[1])
radii_group = always_redraw(lambda: endpoint_and_groups(time.get_value())[2])
tracing_dot = always_redraw(
lambda: Dot(endpoint_and_groups(time.get_value())[0], radius=0.04, color=YELLOW)
)
faint_target = ParametricFunction(
lambda tt: np.array([
SCALE * np.sin(tt),
SCALE * 0.55 * np.sin(2 * tt),
0
]),
t_range=[0, TAU],
color=GREY_B,
stroke_opacity=0.15,
stroke_width=2,
)
self.play(FadeIn(title, shift=DOWN * 0.2))
self.add(faint_target)
self.add(circles_group, radii_group, trace, tracing_dot)
self.play(time.animate.set_value(1), run_time=10, rate_func=linear)
trace.remove_updater(update_trace)
self.wait(0.5)
from manim import *
import numpy as np
from svgpathtools import svg2paths
class ButterflyEpicycles(Scene):
def construct(self):
paths, _ = svg2paths("butterfly3.svg")
path = max(paths, key=lambda p: p.length())
N = 2000
pts = []
for i in range(N):
t = i / (N - 1)
z = path.point(t)
pts.append(z.real + 1j * z.imag)
pts = np.array(pts, dtype=np.complex128)
pts -= np.mean(pts)
pts /= np.max(np.abs(pts))
pts *= 3
n = len(pts)
sample_idx = np.arange(n)
freqs = np.arange(-80, 81)
coeffs = []
for k in freqs:
c = np.sum(pts * np.exp(-2j * np.pi * k * sample_idx / n)) / n
coeffs.append(c)
coeffs = np.array(coeffs)
order = np.argsort(np.abs(freqs))
freqs = freqs[order]
coeffs = coeffs[order]
t = ValueTracker(0)
def point(time_value):
total = 0j
for c, k in zip(coeffs, freqs):
total += c * np.exp(2j * np.pi * k * time_value)
return np.array([total.real, total.imag, 0.0])
def epicycles(time_value):
group = VGroup()
current = np.array([0.0, 0.0, 0.0])
for c, k in zip(coeffs, freqs):
r = abs(c)
angle = np.angle(c) + 2 * np.pi * k * time_value
nxt = current + np.array([
r * np.cos(angle),
r * np.sin(angle),
0.0
])
circle = Circle(radius=r).move_to(current)
circle.set_stroke(RED, opacity=0.3, width=1.5)
line = Line(current, nxt)
line.set_stroke(WHITE, width=2)
group.add(circle, line)
current = nxt
return group
epi = always_redraw(lambda: epicycles(t.get_value()))
trace = VMobject(color=YELLOW, stroke_width=3)
drawn = []
def update_trace(mob):
p = point(t.get_value())
drawn.append(p)
if len(drawn) > 1:
mob.set_points_as_corners(drawn)
trace.add_updater(update_trace)
dot = always_redraw(
lambda: Dot(point(t.get_value()), radius=0.04, color=WHITE)
)
self.add(epi, trace, dot)
self.play(
t.animate.set_value(1),
run_time=10,
rate_func=linear
)
trace.remove_updater(update_trace)
if len(drawn) > 1:
trace.set_points_as_corners(drawn + [drawn[0]])
self.wait(1)
The hosted butterfly3.svg asset will be attached automatically when you load this example from the page.
Rotations and reflections become much easier to remember when the polygon itself performs the symmetry moves on screen.
from manim import *
class PolygonSymmetry(Scene):
def construct(self):
self.camera.background_color = "#050816"
n = 6
polygon = RegularPolygon(n=n, radius=2, color=BLUE)
dots = VGroup(*[Dot(v) for v in polygon.get_vertices()])
self.play(Create(polygon), FadeIn(dots))
# rotation
self.play(
Rotate(
VGroup(polygon,dots),
angle=TAU/n
),
run_time=2
)
# reflection
self.play(
VGroup(polygon,dots).animate.stretch(-1,0),
run_time=2
)
# another rotation
self.play(
Rotate(
VGroup(polygon,dots),
angle=TAU/n
),
run_time=2
)
self.wait()
Paste code, render fast, preview immediately, then refine.
Click Run render to generate the current scene and preview it here.
.py file and run it outside the browser.
# Your Manim code will appear here with syntax colours.