Manim Lab

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.

✍️ Write Render ▶️ Preview ⬇️ Download
Start in 3 steps
1. Choose an example Load starter code or paste your own.
2. Run render Use low quality first for fast testing.
3. Preview and refine Edit, rerender, and download when ready.
Learning tracks
Keep the lessons nearby, but let the workspace stay in focus.

🛠️ Setup & first run

Install Manim, test your environment, and render your first scene.

✏️ Text, maths & shapes

Learn text, LaTeX, layout, simple animations, and clean scene structure.

📈 Graphs & project clips

Move from small demos to graphs, parameter shifts, and event-ready clips.

Featured demo • Logo + audio render Demo

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.

invmath.svg intro.mp3
What this demo shows
  • • Loading an SVG with SVGMobject("invmath.svg")
  • • Adding sound with self.add_sound("intro.mp3")
  • • Rendering a short branded scene with both media types together

This is intentionally separate from the main modules so it feels like a ready-made showcase, not just another lesson.

Assets (optional)
Upload images, SVGs, audio, or data files for your scene.
Quick guide for files
  • • You can upload one file or many files together.
  • • In code, use the exact uploaded filename only.
  • • For images use ImageMobject("photo.png") or SVGMobject("logo.svg").
  • • For audio use self.add_sound("intro.mp3").
  • • Start with Low quality when testing scenes with files.
AI prompt to Manim
Describe the scene you want and generate starter code.
Try:

Module 1 • Your first scene Beginner

A simple scene needs a Scene class, a construct method, and one animation.

1.1 Basic scene pattern
from manim import *

class HelloWorld(Scene):
    def construct(self):
        text = Tex("Hello, InvariantMath!")
        self.play(Write(text))
        self.wait(1)
Slow explanation
1.2 The Manim “dictionary”
  • Scene — the container.
  • Mobject — the thing you show.
  • Animation — the action you play.

Module 2 • Text & LaTeX Beginner

Use text for titles and MathTex for mathematics.

2.1 Text vs. maths
title = Text("ODE–Integration Bee 2.0")
equation = MathTex(r"\int_0^1 x^n\,dx = \frac{1}{n+1}")
More patterns
2.2 Positioning on screen
title.to_edge(UP)
equation.next_to(title, DOWN, buff=0.5)

Module 3 • Heat equation diffusion Intermediate

This is a gentle first taste of mathematical motion: a curve starts concentrated, then spreads and softens as time moves forward.

3.1 Watch the heat spread
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()
3.2 Why this feels so visual
  • • The curve changes shape smoothly, so students can literally watch diffusion happen.
  • • The glowing filled area makes the scene feel less like a static plot and more like a living process.

Module 4 • Saddle point with stable/unstable manifolds Intermediate

Some directions fall into the centre, others move away from it. This scene makes that contrast visible at a glance.

4.1 Stable versus unstable directions
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()
4.2 What students notice immediately
  • • The green line marks directions that move inward, while the red line marks directions that move outward.
  • • The stream lines turn an abstract phase portrait into something that feels physical and intuitive.

Module 5 • Fourier epicycles drawing a curve Advanced

This is one of the most magical Manim ideas: circles spin on circles, and together they draw a curve in real time.

5.1 A compact epicycle sketch
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()
5.2 A richer version that draws an infinity curve
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)
More context

Module 6 • Polygon symmetry / dihedral action Intermediate

Rotations and reflections become much easier to remember when the polygon itself performs the symmetry moves on screen.

6.1 Rotate, reflect, repeat
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()
6.2 What this teaches visually
  • • A single rotation already hints at cyclic symmetry, while the reflection introduces the full dihedral story.
  • • Students get to see group actions as motion, not just as notation on a page.

Manim workspace

Paste code, render fast, preview immediately, then refine.

Render workspace
Render output
Your latest render appears here.

Click Run render to generate the current scene and preview it here.

Actions
Saved locally
Start from an example
Choose a starter scene and edit it.
Quick inserts
Add common Manim patterns without typing them from scratch.
Insert:
Editor
Write your scene here. Your draft is saved automatically in this browser.
Choose files
Add any image, SVG, audio, or data files you want this scene to use.
No files selected yet. You can upload multiple assets for one scene.
Python spacing matters Tab = 4 spaces Enter keeps indentation Paste, edit, rerender
You can also copy this into a local .py file and run it outside the browser.
Line 1, Column 1 Indentation helper active
Syntax preview
# Your Manim code will appear here with syntax colours.