Initial release — OpenMontage: the first open-source agentic video production system
11 production pipelines, 47 tools, 124 agent skills. Supports cloud APIs (fal.ai, OpenAI, ElevenLabs, Suno, HeyGen, Runway) and free local providers (diffusers, Piper TTS, WAN 2.1, Hunyuan, CogVideo). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 Adithya S Kolavi (adithyaskolavi@gmail.com)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,151 @@
|
||||
---
|
||||
name: manimce-best-practices
|
||||
description: |
|
||||
Trigger when: (1) User mentions "manim" or "Manim Community" or "ManimCE", (2) Code contains `from manim import *`, (3) User runs `manim` CLI commands, (4) Working with Scene, MathTex, Create(), or ManimCE-specific classes.
|
||||
|
||||
Best practices for Manim Community Edition - the community-maintained Python animation engine. Covers Scene structure, animations, LaTeX/MathTex, 3D with ThreeDScene, camera control, styling, and CLI usage.
|
||||
|
||||
NOT for ManimGL/3b1b version (which uses `manimlib` imports and `manimgl` CLI).
|
||||
---
|
||||
|
||||
## How to use
|
||||
|
||||
Read individual rule files for detailed explanations and code examples:
|
||||
|
||||
### Core Concepts
|
||||
- [rules/scenes.md](rules/scenes.md) - Scene structure, construct method, and scene types
|
||||
- [rules/mobjects.md](rules/mobjects.md) - Mobject types, VMobject, Groups, and positioning
|
||||
- [rules/animations.md](rules/animations.md) - Animation classes, playing animations, and timing
|
||||
|
||||
### Creation & Transformation
|
||||
- [rules/creation-animations.md](rules/creation-animations.md) - Create, Write, FadeIn, DrawBorderThenFill
|
||||
- [rules/transform-animations.md](rules/transform-animations.md) - Transform, ReplacementTransform, morphing
|
||||
- [rules/animation-groups.md](rules/animation-groups.md) - AnimationGroup, LaggedStart, Succession
|
||||
|
||||
### Text & Math
|
||||
- [rules/text.md](rules/text.md) - Text mobjects, fonts, and styling
|
||||
- [rules/latex.md](rules/latex.md) - MathTex, Tex, LaTeX rendering, and coloring formulas
|
||||
- [rules/text-animations.md](rules/text-animations.md) - Write, AddTextLetterByLetter, TypeWithCursor
|
||||
|
||||
### Styling & Appearance
|
||||
- [rules/colors.md](rules/colors.md) - Color constants, gradients, and color manipulation
|
||||
- [rules/styling.md](rules/styling.md) - Fill, stroke, opacity, and visual properties
|
||||
|
||||
### Positioning & Layout
|
||||
- [rules/positioning.md](rules/positioning.md) - move_to, next_to, align_to, shift methods
|
||||
- [rules/grouping.md](rules/grouping.md) - VGroup, Group, arrange, and layout patterns
|
||||
|
||||
### Coordinate Systems & Graphing
|
||||
- [rules/axes.md](rules/axes.md) - Axes, NumberPlane, coordinate systems
|
||||
- [rules/graphing.md](rules/graphing.md) - Plotting functions, parametric curves
|
||||
- [rules/3d.md](rules/3d.md) - ThreeDScene, 3D axes, surfaces, camera orientation
|
||||
|
||||
### Animation Control
|
||||
- [rules/timing.md](rules/timing.md) - Rate functions, easing, run_time, lag_ratio
|
||||
- [rules/updaters.md](rules/updaters.md) - Updaters, ValueTracker, dynamic animations
|
||||
- [rules/camera.md](rules/camera.md) - MovingCameraScene, zoom, pan, frame manipulation
|
||||
|
||||
### Configuration & CLI
|
||||
- [rules/cli.md](rules/cli.md) - Command-line interface, rendering options, quality flags
|
||||
- [rules/config.md](rules/config.md) - Configuration system, manim.cfg, settings
|
||||
|
||||
### Shapes & Geometry
|
||||
- [rules/shapes.md](rules/shapes.md) - Circle, Square, Rectangle, Polygon, and geometric primitives
|
||||
- [rules/lines.md](rules/lines.md) - Line, Arrow, Vector, DashedLine, and connectors
|
||||
|
||||
## Working Examples
|
||||
|
||||
Complete, tested example files demonstrating common patterns:
|
||||
|
||||
- [examples/basic_animations.py](examples/basic_animations.py) - Shape creation, text, lagged animations, path movement
|
||||
- [examples/math_visualization.py](examples/math_visualization.py) - LaTeX equations, color-coded math, derivations
|
||||
- [examples/updater_patterns.py](examples/updater_patterns.py) - ValueTracker, dynamic animations, physics simulations
|
||||
- [examples/graph_plotting.py](examples/graph_plotting.py) - Axes, functions, areas, Riemann sums, polar plots
|
||||
- [examples/3d_visualization.py](examples/3d_visualization.py) - ThreeDScene, surfaces, 3D camera, parametric curves
|
||||
|
||||
## Scene Templates
|
||||
|
||||
Copy and modify these templates to start new projects:
|
||||
|
||||
- [templates/basic_scene.py](templates/basic_scene.py) - Standard 2D scene template
|
||||
- [templates/camera_scene.py](templates/camera_scene.py) - MovingCameraScene with zoom/pan
|
||||
- [templates/threed_scene.py](templates/threed_scene.py) - 3D scene with surfaces and camera rotation
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### Basic Scene Structure
|
||||
```python
|
||||
from manim import *
|
||||
|
||||
class MyScene(Scene):
|
||||
def construct(self):
|
||||
# Create mobjects
|
||||
circle = Circle()
|
||||
|
||||
# Add to scene (static)
|
||||
self.add(circle)
|
||||
|
||||
# Or animate
|
||||
self.play(Create(circle))
|
||||
|
||||
# Wait
|
||||
self.wait(1)
|
||||
```
|
||||
|
||||
### Render Command
|
||||
```bash
|
||||
# Basic render with preview
|
||||
manim -pql scene.py MyScene
|
||||
|
||||
# Quality flags: -ql (low), -qm (medium), -qh (high), -qk (4k)
|
||||
manim -pqh scene.py MyScene
|
||||
```
|
||||
|
||||
### Key Differences from 3b1b/ManimGL
|
||||
|
||||
| Feature | Manim Community | 3b1b/ManimGL |
|
||||
|---------|-----------------|--------------|
|
||||
| Import | `from manim import *` | `from manimlib import *` |
|
||||
| CLI | `manim` | `manimgl` |
|
||||
| Math text | `MathTex(r"\pi")` | `Tex(R"\pi")` |
|
||||
| Scene | `Scene` | `InteractiveScene` |
|
||||
| Package | `manim` (PyPI) | `manimgl` (PyPI) |
|
||||
|
||||
### Jupyter Notebook Support
|
||||
|
||||
Use the `%%manim` cell magic:
|
||||
|
||||
```python
|
||||
%%manim -qm MyScene
|
||||
class MyScene(Scene):
|
||||
def construct(self):
|
||||
self.play(Create(Circle()))
|
||||
```
|
||||
|
||||
### Common Pitfalls to Avoid
|
||||
|
||||
1. **Version confusion** - Ensure you're using `manim` (Community), not `manimgl` (3b1b version)
|
||||
2. **Check imports** - `from manim import *` is ManimCE; `from manimlib import *` is ManimGL
|
||||
3. **Outdated tutorials** - Video tutorials may be outdated; prefer official documentation
|
||||
4. **manimpango issues** - If text rendering fails, check manimpango installation requirements
|
||||
5. **PATH issues (Windows)** - If `manim` command not found, use `python -m manim` or check PATH
|
||||
|
||||
### Installation
|
||||
|
||||
```bash
|
||||
# Install Manim Community
|
||||
pip install manim
|
||||
|
||||
# Check installation
|
||||
manim checkhealth
|
||||
```
|
||||
|
||||
### Useful Commands
|
||||
|
||||
```bash
|
||||
manim -pql scene.py Scene # Preview low quality (development)
|
||||
manim -pqh scene.py Scene # Preview high quality
|
||||
manim --format gif scene.py # Output as GIF
|
||||
manim checkhealth # Verify installation
|
||||
manim plugins -l # List plugins
|
||||
```
|
||||
@@ -0,0 +1,373 @@
|
||||
"""
|
||||
3D Visualization Patterns for Manim Community
|
||||
|
||||
Demonstrates ThreeDScene, 3D axes, surfaces, and camera control.
|
||||
Adapted from 3b1b patterns for ManimCE.
|
||||
|
||||
Run with: manim -pql 3d_visualization.py SceneName
|
||||
"""
|
||||
|
||||
from manim import *
|
||||
import numpy as np
|
||||
|
||||
|
||||
class Basic3DScene(ThreeDScene):
|
||||
"""Basic 3D scene with shapes."""
|
||||
|
||||
def construct(self):
|
||||
# Set camera orientation
|
||||
self.set_camera_orientation(phi=60 * DEGREES, theta=-45 * DEGREES)
|
||||
|
||||
# 3D shapes
|
||||
sphere = Sphere(radius=1, color=BLUE)
|
||||
cube = Cube(side_length=1.5, color=RED, fill_opacity=0.7)
|
||||
cone = Cone(base_radius=0.8, height=1.5, color=GREEN)
|
||||
|
||||
# Position shapes
|
||||
sphere.shift(LEFT * 3)
|
||||
cone.shift(RIGHT * 3)
|
||||
|
||||
self.play(Create(sphere), Create(cube), Create(cone))
|
||||
self.wait()
|
||||
|
||||
# Rotate camera
|
||||
self.begin_ambient_camera_rotation(rate=0.3)
|
||||
self.wait(4)
|
||||
self.stop_ambient_camera_rotation()
|
||||
|
||||
|
||||
class ThreeDAxesExample(ThreeDScene):
|
||||
"""3D coordinate axes and plotting."""
|
||||
|
||||
def construct(self):
|
||||
self.set_camera_orientation(phi=70 * DEGREES, theta=-45 * DEGREES)
|
||||
|
||||
# Create 3D axes
|
||||
axes = ThreeDAxes(
|
||||
x_range=[-3, 3, 1],
|
||||
y_range=[-3, 3, 1],
|
||||
z_range=[-2, 2, 1],
|
||||
x_length=6,
|
||||
y_length=6,
|
||||
z_length=4,
|
||||
)
|
||||
|
||||
# Axis labels
|
||||
x_label = axes.get_x_axis_label(r"x")
|
||||
y_label = axes.get_y_axis_label(r"y")
|
||||
z_label = axes.get_z_axis_label(r"z")
|
||||
|
||||
self.play(Create(axes))
|
||||
self.add_fixed_orientation_mobjects(x_label, y_label, z_label)
|
||||
self.wait()
|
||||
|
||||
# Add a point
|
||||
point = Dot3D(axes.c2p(2, 1, 1.5), color=RED, radius=0.1)
|
||||
self.play(Create(point))
|
||||
|
||||
# Camera rotation
|
||||
self.begin_ambient_camera_rotation(rate=0.2)
|
||||
self.wait(5)
|
||||
|
||||
|
||||
class ParametricSurfaceExample(ThreeDScene):
|
||||
"""3D parametric surface visualization."""
|
||||
|
||||
def construct(self):
|
||||
self.set_camera_orientation(phi=60 * DEGREES, theta=-60 * DEGREES)
|
||||
|
||||
axes = ThreeDAxes(
|
||||
x_range=[-3, 3],
|
||||
y_range=[-3, 3],
|
||||
z_range=[-2, 2],
|
||||
)
|
||||
|
||||
# Saddle surface: z = x^2 - y^2
|
||||
surface = Surface(
|
||||
lambda u, v: axes.c2p(u, v, u ** 2 - v ** 2),
|
||||
u_range=[-2, 2],
|
||||
v_range=[-2, 2],
|
||||
resolution=(20, 20),
|
||||
fill_opacity=0.7,
|
||||
)
|
||||
surface.set_color_by_gradient(BLUE, GREEN, YELLOW)
|
||||
|
||||
self.play(Create(axes))
|
||||
self.play(Create(surface), run_time=2)
|
||||
|
||||
self.begin_ambient_camera_rotation(rate=0.15)
|
||||
self.wait(5)
|
||||
|
||||
|
||||
class SphereVisualization(ThreeDScene):
|
||||
"""Sphere with parametric representation."""
|
||||
|
||||
def construct(self):
|
||||
self.set_camera_orientation(phi=70 * DEGREES, theta=30 * DEGREES)
|
||||
|
||||
# Parametric sphere
|
||||
sphere = Surface(
|
||||
lambda u, v: np.array([
|
||||
np.cos(v) * np.sin(u),
|
||||
np.sin(v) * np.sin(u),
|
||||
np.cos(u)
|
||||
]),
|
||||
u_range=[0, PI],
|
||||
v_range=[0, 2 * PI],
|
||||
resolution=(20, 40),
|
||||
)
|
||||
sphere.set_color_by_gradient(BLUE_E, BLUE, TEAL)
|
||||
|
||||
self.play(Create(sphere), run_time=2)
|
||||
|
||||
# Animate camera
|
||||
self.begin_ambient_camera_rotation(rate=0.2)
|
||||
self.wait(5)
|
||||
|
||||
|
||||
class Function3DPlot(ThreeDScene):
|
||||
"""Plotting z = f(x, y) surfaces."""
|
||||
|
||||
def construct(self):
|
||||
self.set_camera_orientation(phi=65 * DEGREES, theta=-45 * DEGREES)
|
||||
|
||||
axes = ThreeDAxes(
|
||||
x_range=[-3, 3],
|
||||
y_range=[-3, 3],
|
||||
z_range=[-1, 1],
|
||||
)
|
||||
|
||||
# Sine wave surface
|
||||
surface = Surface(
|
||||
lambda u, v: axes.c2p(
|
||||
u, v,
|
||||
np.sin(np.sqrt(u ** 2 + v ** 2))
|
||||
),
|
||||
u_range=[-3, 3],
|
||||
v_range=[-3, 3],
|
||||
resolution=(30, 30),
|
||||
)
|
||||
surface.set_color_by_gradient(PURPLE, RED, ORANGE)
|
||||
|
||||
self.play(Create(axes))
|
||||
self.play(Create(surface), run_time=2)
|
||||
|
||||
self.begin_ambient_camera_rotation(rate=0.1)
|
||||
self.wait(6)
|
||||
|
||||
|
||||
class VectorField3D(ThreeDScene):
|
||||
"""3D vector field visualization."""
|
||||
|
||||
def construct(self):
|
||||
self.set_camera_orientation(phi=60 * DEGREES, theta=-45 * DEGREES)
|
||||
|
||||
axes = ThreeDAxes(
|
||||
x_range=[-3, 3],
|
||||
y_range=[-3, 3],
|
||||
z_range=[-3, 3],
|
||||
)
|
||||
|
||||
# Create arrows representing a vector field
|
||||
arrows = VGroup()
|
||||
for x in np.arange(-2, 3, 1):
|
||||
for y in np.arange(-2, 3, 1):
|
||||
for z in np.arange(-2, 3, 1):
|
||||
# Vector field: F = (-y, x, z)
|
||||
start = axes.c2p(x, y, z)
|
||||
direction = np.array([-y, x, z]) * 0.3
|
||||
end = start + direction
|
||||
|
||||
arrow = Arrow3D(
|
||||
start=start,
|
||||
end=end,
|
||||
color=interpolate_color(
|
||||
BLUE, RED,
|
||||
(z + 2) / 4
|
||||
),
|
||||
)
|
||||
arrows.add(arrow)
|
||||
|
||||
self.play(Create(axes))
|
||||
self.play(LaggedStart(*[Create(a) for a in arrows], lag_ratio=0.02))
|
||||
|
||||
self.begin_ambient_camera_rotation(rate=0.1)
|
||||
self.wait(5)
|
||||
|
||||
|
||||
class CameraMovement3D(ThreeDScene):
|
||||
"""Demonstrating 3D camera controls."""
|
||||
|
||||
def construct(self):
|
||||
# Start with a default view
|
||||
self.set_camera_orientation(phi=75 * DEGREES, theta=-45 * DEGREES)
|
||||
|
||||
# Create a 3D object
|
||||
torus = Torus(
|
||||
major_radius=2,
|
||||
minor_radius=0.5,
|
||||
color=BLUE,
|
||||
fill_opacity=0.8
|
||||
)
|
||||
|
||||
self.play(Create(torus))
|
||||
self.wait()
|
||||
|
||||
# Move camera to different angles
|
||||
self.move_camera(phi=30 * DEGREES, theta=0, run_time=2)
|
||||
self.wait()
|
||||
|
||||
self.move_camera(phi=90 * DEGREES, theta=90 * DEGREES, run_time=2)
|
||||
self.wait()
|
||||
|
||||
# Zoom by adjusting frame
|
||||
self.move_camera(zoom=1.5, run_time=1)
|
||||
self.wait()
|
||||
|
||||
self.move_camera(zoom=0.7, run_time=1)
|
||||
self.wait()
|
||||
|
||||
|
||||
class Line3DExample(ThreeDScene):
|
||||
"""3D lines and curves."""
|
||||
|
||||
def construct(self):
|
||||
self.set_camera_orientation(phi=70 * DEGREES, theta=-45 * DEGREES)
|
||||
|
||||
axes = ThreeDAxes()
|
||||
|
||||
# 3D helix
|
||||
helix = ParametricFunction(
|
||||
lambda t: np.array([
|
||||
np.cos(t),
|
||||
np.sin(t),
|
||||
t / 4
|
||||
]),
|
||||
t_range=[0, 4 * PI],
|
||||
color=YELLOW,
|
||||
)
|
||||
|
||||
# Line in 3D
|
||||
line = Line3D(
|
||||
start=axes.c2p(-2, -2, -1),
|
||||
end=axes.c2p(2, 2, 1),
|
||||
color=RED,
|
||||
)
|
||||
|
||||
self.play(Create(axes))
|
||||
self.play(Create(helix), run_time=2)
|
||||
self.play(Create(line))
|
||||
|
||||
self.begin_ambient_camera_rotation(rate=0.15)
|
||||
self.wait(5)
|
||||
|
||||
|
||||
class TextIn3D(ThreeDScene):
|
||||
"""Text and math in 3D scenes."""
|
||||
|
||||
def construct(self):
|
||||
self.set_camera_orientation(phi=60 * DEGREES, theta=-45 * DEGREES)
|
||||
|
||||
axes = ThreeDAxes()
|
||||
|
||||
# 3D text (stays fixed to camera)
|
||||
title = Text("3D Visualization", font_size=48)
|
||||
title.to_corner(UL)
|
||||
self.add_fixed_in_frame_mobjects(title)
|
||||
|
||||
# Math label fixed to camera
|
||||
equation = MathTex(r"z = x^2 + y^2")
|
||||
equation.to_corner(UR)
|
||||
self.add_fixed_in_frame_mobjects(equation)
|
||||
|
||||
# Surface
|
||||
paraboloid = Surface(
|
||||
lambda u, v: axes.c2p(u, v, u ** 2 + v ** 2),
|
||||
u_range=[-1.5, 1.5],
|
||||
v_range=[-1.5, 1.5],
|
||||
resolution=(15, 15),
|
||||
)
|
||||
paraboloid.set_color_by_gradient(BLUE, GREEN)
|
||||
|
||||
self.play(Write(title), Write(equation))
|
||||
self.play(Create(axes))
|
||||
self.play(Create(paraboloid))
|
||||
|
||||
self.begin_ambient_camera_rotation(rate=0.1)
|
||||
self.wait(5)
|
||||
|
||||
|
||||
class AnimatedSurface(ThreeDScene):
|
||||
"""Surface that changes over time."""
|
||||
|
||||
def construct(self):
|
||||
self.set_camera_orientation(phi=65 * DEGREES, theta=-45 * DEGREES)
|
||||
|
||||
axes = ThreeDAxes(
|
||||
x_range=[-3, 3],
|
||||
y_range=[-3, 3],
|
||||
z_range=[-2, 2],
|
||||
)
|
||||
|
||||
# Time parameter
|
||||
time = ValueTracker(0)
|
||||
|
||||
# Animated wave surface
|
||||
surface = always_redraw(
|
||||
lambda: Surface(
|
||||
lambda u, v: axes.c2p(
|
||||
u, v,
|
||||
np.sin(np.sqrt(u ** 2 + v ** 2) - time.get_value())
|
||||
),
|
||||
u_range=[-3, 3],
|
||||
v_range=[-3, 3],
|
||||
resolution=(25, 25),
|
||||
).set_color_by_gradient(BLUE, TEAL)
|
||||
)
|
||||
|
||||
self.add(axes, surface)
|
||||
|
||||
# Animate
|
||||
self.play(
|
||||
time.animate.set_value(4 * PI),
|
||||
run_time=8,
|
||||
rate_func=linear
|
||||
)
|
||||
|
||||
|
||||
class MultipleObjects3D(ThreeDScene):
|
||||
"""Combining multiple 3D objects."""
|
||||
|
||||
def construct(self):
|
||||
self.set_camera_orientation(phi=60 * DEGREES, theta=-30 * DEGREES)
|
||||
|
||||
# Create various 3D shapes
|
||||
sphere = Sphere(radius=0.5, color=RED).shift(LEFT * 2 + UP)
|
||||
cube = Cube(side_length=0.8, color=BLUE).shift(RIGHT * 2)
|
||||
cylinder = Cylinder(
|
||||
radius=0.4,
|
||||
height=1.2,
|
||||
color=GREEN
|
||||
).shift(DOWN + LEFT)
|
||||
|
||||
# Arrows connecting them
|
||||
arrow1 = Arrow3D(
|
||||
start=sphere.get_center(),
|
||||
end=cube.get_center(),
|
||||
color=YELLOW
|
||||
)
|
||||
arrow2 = Arrow3D(
|
||||
start=cube.get_center(),
|
||||
end=cylinder.get_center(),
|
||||
color=YELLOW
|
||||
)
|
||||
|
||||
self.play(
|
||||
Create(sphere),
|
||||
Create(cube),
|
||||
Create(cylinder),
|
||||
)
|
||||
self.play(Create(arrow1), Create(arrow2))
|
||||
|
||||
self.begin_ambient_camera_rotation(rate=0.2)
|
||||
self.wait(5)
|
||||
@@ -0,0 +1,2 @@
|
||||
# Attention Visualization Package
|
||||
# Converted from 3b1b ManimGL to ManimCE
|
||||
@@ -0,0 +1,453 @@
|
||||
"""
|
||||
Attention Visualization Helpers - Converted from 3b1b ManimGL to ManimCE
|
||||
|
||||
Original: videos/_2024/transformers/helpers.py
|
||||
Contains utility functions and classes for attention visualization.
|
||||
"""
|
||||
|
||||
from manim import *
|
||||
import numpy as np
|
||||
import warnings
|
||||
import random
|
||||
import itertools as it
|
||||
from typing import Optional, Tuple
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# UTILITY FUNCTIONS
|
||||
# =============================================================================
|
||||
|
||||
def softmax(logits, temperature=1.0):
|
||||
"""Numerically stable softmax function."""
|
||||
logits = np.array(logits)
|
||||
with warnings.catch_warnings():
|
||||
warnings.filterwarnings('ignore')
|
||||
logits = logits - np.max(logits)
|
||||
exps = np.exp(np.divide(logits, temperature, where=temperature != 0))
|
||||
|
||||
if np.isinf(exps).any() or np.isnan(exps).any() or temperature == 0:
|
||||
result = np.zeros_like(logits)
|
||||
result[np.argmax(logits)] = 1
|
||||
return result
|
||||
return exps / np.sum(exps)
|
||||
|
||||
|
||||
def value_to_color(
|
||||
value,
|
||||
low_positive_color=BLUE_E,
|
||||
high_positive_color=BLUE_B,
|
||||
low_negative_color=RED_E,
|
||||
high_negative_color=RED_B,
|
||||
min_value=0.0,
|
||||
max_value=10.0
|
||||
):
|
||||
"""Map a numeric value to a color based on sign and magnitude."""
|
||||
# Clamp alpha between 0 and 1
|
||||
alpha = max(0, min(1, abs(value - min_value) / (max_value - min_value))) if max_value != min_value else 0.5
|
||||
|
||||
if value >= 0:
|
||||
return interpolate_color(low_positive_color, high_positive_color, alpha)
|
||||
else:
|
||||
return interpolate_color(low_negative_color, high_negative_color, alpha)
|
||||
|
||||
|
||||
def get_paragraph(words, line_len=40, font_size=48):
|
||||
"""Handle word wrapping for text display."""
|
||||
words = list(map(str.strip, words))
|
||||
word_lens = list(map(len, words))
|
||||
lines = []
|
||||
lh, rh = 0, 0
|
||||
while rh < len(words):
|
||||
rh += 1
|
||||
if sum(word_lens[lh:rh]) > line_len:
|
||||
rh -= 1
|
||||
lines.append(words[lh:rh])
|
||||
lh = rh
|
||||
lines.append(words[lh:])
|
||||
text = "\n".join([" ".join(line).strip() for line in lines])
|
||||
return Text(text, font_size=font_size)
|
||||
|
||||
|
||||
def random_bright_color(hue_range=(0.0, 1.0)):
|
||||
"""Generate a random bright color within a hue range."""
|
||||
import colorsys
|
||||
hue = random.uniform(*hue_range)
|
||||
rgb = colorsys.hsv_to_rgb(hue, 0.7, 0.9)
|
||||
return rgb_to_color(rgb)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# CUSTOM MOBJECT CLASSES
|
||||
# =============================================================================
|
||||
|
||||
class NumericEmbedding(VGroup):
|
||||
"""
|
||||
A vertical vector of decimal numbers representing an embedding.
|
||||
Displays values with color coding based on magnitude.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
values: Optional[np.ndarray] = None,
|
||||
length: int = 7,
|
||||
num_decimal_places: int = 1,
|
||||
value_range: Tuple[float, float] = (-9.9, 9.9),
|
||||
show_ellipsis: bool = True,
|
||||
ellipsis_row: int = -2,
|
||||
dark_color=GREY_C,
|
||||
light_color=WHITE,
|
||||
bracket_color=GREY_B,
|
||||
**kwargs
|
||||
):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.value_range = value_range
|
||||
self.dark_color = dark_color
|
||||
self.light_color = light_color
|
||||
|
||||
if values is None:
|
||||
values = np.random.uniform(*value_range, size=length)
|
||||
|
||||
self.values = values
|
||||
self.length = len(values)
|
||||
|
||||
# Create decimal number entries
|
||||
self.elements = VGroup()
|
||||
for i, val in enumerate(values):
|
||||
if show_ellipsis and i == (ellipsis_row % len(values)):
|
||||
entry = MathTex(r"\vdots")
|
||||
else:
|
||||
entry = DecimalNumber(
|
||||
val,
|
||||
num_decimal_places=num_decimal_places,
|
||||
include_sign=True,
|
||||
font_size=36
|
||||
)
|
||||
# Color based on value
|
||||
alpha = abs(val) / max(abs(value_range[0]), abs(value_range[1]))
|
||||
entry.set_color(interpolate_color(dark_color, light_color, alpha))
|
||||
self.elements.add(entry)
|
||||
|
||||
self.elements.arrange(DOWN, buff=0.15)
|
||||
|
||||
# Add brackets
|
||||
self.left_bracket = MathTex(r"\left[")
|
||||
self.right_bracket = MathTex(r"\right]")
|
||||
|
||||
self.left_bracket.stretch_to_fit_height(self.elements.get_height() * 1.1)
|
||||
self.right_bracket.stretch_to_fit_height(self.elements.get_height() * 1.1)
|
||||
|
||||
self.left_bracket.next_to(self.elements, LEFT, buff=0.1)
|
||||
self.right_bracket.next_to(self.elements, RIGHT, buff=0.1)
|
||||
|
||||
self.left_bracket.set_color(bracket_color)
|
||||
self.right_bracket.set_color(bracket_color)
|
||||
|
||||
self.add(self.left_bracket, self.elements, self.right_bracket)
|
||||
|
||||
def get_brackets(self):
|
||||
return VGroup(self.left_bracket, self.right_bracket)
|
||||
|
||||
def get_entries(self):
|
||||
return self.elements
|
||||
|
||||
|
||||
class WeightMatrix(VGroup):
|
||||
"""
|
||||
A matrix of decimal numbers with color-coded entries.
|
||||
Used to represent weight matrices in neural networks.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
values: Optional[np.ndarray] = None,
|
||||
shape: Tuple[int, int] = (6, 8),
|
||||
value_range: Tuple[float, float] = (-9.9, 9.9),
|
||||
num_decimal_places: int = 1,
|
||||
show_ellipsis: bool = True,
|
||||
ellipsis_row: int = -2,
|
||||
ellipsis_col: int = -2,
|
||||
low_positive_color=BLUE_E,
|
||||
high_positive_color=BLUE_B,
|
||||
low_negative_color=RED_E,
|
||||
high_negative_color=RED_B,
|
||||
**kwargs
|
||||
):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.shape = shape
|
||||
self.value_range = value_range
|
||||
self.low_positive_color = low_positive_color
|
||||
self.high_positive_color = high_positive_color
|
||||
self.low_negative_color = low_negative_color
|
||||
self.high_negative_color = high_negative_color
|
||||
|
||||
if values is None:
|
||||
values = np.random.uniform(*value_range, size=shape)
|
||||
|
||||
self.values = values
|
||||
|
||||
# Create matrix entries
|
||||
self.rows = VGroup()
|
||||
n_rows, n_cols = shape
|
||||
|
||||
for i in range(n_rows):
|
||||
row = VGroup()
|
||||
for j in range(n_cols):
|
||||
if show_ellipsis and i == (ellipsis_row % n_rows):
|
||||
entry = MathTex(r"\vdots")
|
||||
elif show_ellipsis and j == (ellipsis_col % n_cols):
|
||||
entry = MathTex(r"\cdots")
|
||||
else:
|
||||
val = values[i, j]
|
||||
entry = DecimalNumber(
|
||||
val,
|
||||
num_decimal_places=num_decimal_places,
|
||||
include_sign=True,
|
||||
font_size=24
|
||||
)
|
||||
entry.set_color(value_to_color(
|
||||
val,
|
||||
low_positive_color,
|
||||
high_positive_color,
|
||||
low_negative_color,
|
||||
high_negative_color,
|
||||
0, max(abs(value_range[0]), abs(value_range[1]))
|
||||
))
|
||||
row.add(entry)
|
||||
row.arrange(RIGHT, buff=0.2)
|
||||
self.rows.add(row)
|
||||
|
||||
self.rows.arrange(DOWN, buff=0.15)
|
||||
|
||||
# Add brackets
|
||||
self.left_bracket = MathTex(r"\left[")
|
||||
self.right_bracket = MathTex(r"\right]")
|
||||
|
||||
self.left_bracket.stretch_to_fit_height(self.rows.get_height() * 1.1)
|
||||
self.right_bracket.stretch_to_fit_height(self.rows.get_height() * 1.1)
|
||||
|
||||
self.left_bracket.next_to(self.rows, LEFT, buff=0.1)
|
||||
self.right_bracket.next_to(self.rows, RIGHT, buff=0.1)
|
||||
|
||||
self.add(self.left_bracket, self.rows, self.right_bracket)
|
||||
|
||||
def get_entries(self):
|
||||
entries = VGroup()
|
||||
for row in self.rows:
|
||||
for entry in row:
|
||||
if isinstance(entry, DecimalNumber):
|
||||
entries.add(entry)
|
||||
return entries
|
||||
|
||||
def get_rows(self):
|
||||
return self.rows
|
||||
|
||||
|
||||
class ContextAnimation(LaggedStart):
|
||||
"""
|
||||
Animation showing context flow from source words to target word.
|
||||
Creates arcing lines that flash from sources to target.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
target,
|
||||
sources,
|
||||
direction=UP,
|
||||
time_width=2,
|
||||
min_stroke_width=1,
|
||||
max_stroke_width=5,
|
||||
strengths=None,
|
||||
run_time=3,
|
||||
path_arc=PI / 2,
|
||||
**kwargs,
|
||||
):
|
||||
arcs = VGroup()
|
||||
if strengths is None:
|
||||
strengths = np.random.random(len(sources)) ** 2
|
||||
|
||||
for source, strength in zip(sources, strengths):
|
||||
sign = direction[1] * (-1) ** int(source.get_x() < target.get_x())
|
||||
arc = Line(
|
||||
source.get_edge_center(direction),
|
||||
target.get_edge_center(direction),
|
||||
path_arc=sign * path_arc,
|
||||
)
|
||||
arc.set_stroke(
|
||||
color=random_bright_color(hue_range=(0.1, 0.3)),
|
||||
width=interpolate(min_stroke_width, max_stroke_width, strength)
|
||||
)
|
||||
arcs.add(arc)
|
||||
|
||||
arcs.shuffle()
|
||||
lag_ratio = 0.5 / max(len(arcs), 1)
|
||||
|
||||
super().__init__(
|
||||
*[
|
||||
ShowPassingFlash(arc, time_width=time_width)
|
||||
for arc in arcs
|
||||
],
|
||||
lag_ratio=lag_ratio,
|
||||
run_time=run_time,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
|
||||
class NeuralNetwork(VGroup):
|
||||
"""
|
||||
Visual representation of a neural network with layers and connections.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
layer_sizes=[6, 12, 6],
|
||||
neuron_radius=0.1,
|
||||
v_buff=0.3,
|
||||
h_buff=1.5,
|
||||
max_stroke_width=2.0,
|
||||
**kwargs
|
||||
):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.max_stroke_width = max_stroke_width
|
||||
|
||||
# Create layers
|
||||
self.layers = VGroup()
|
||||
for n in layer_sizes:
|
||||
layer = VGroup(*[
|
||||
Circle(radius=neuron_radius, color=WHITE, fill_opacity=random.random())
|
||||
for _ in range(n)
|
||||
])
|
||||
layer.arrange(DOWN, buff=v_buff)
|
||||
self.layers.add(layer)
|
||||
|
||||
self.layers.arrange(RIGHT, buff=h_buff)
|
||||
|
||||
# Create connections
|
||||
self.lines = VGroup()
|
||||
for l1, l2 in zip(self.layers, self.layers[1:]):
|
||||
layer_lines = VGroup()
|
||||
for n1 in l1:
|
||||
for n2 in l2:
|
||||
line = Line(
|
||||
n1.get_center(),
|
||||
n2.get_center(),
|
||||
buff=neuron_radius
|
||||
)
|
||||
line.set_stroke(
|
||||
color=value_to_color(random.uniform(-10, 10)),
|
||||
width=max_stroke_width * random.random(),
|
||||
opacity=random.random() ** 2
|
||||
)
|
||||
layer_lines.add(line)
|
||||
self.lines.add(layer_lines)
|
||||
|
||||
self.add(self.lines, self.layers)
|
||||
|
||||
|
||||
class AttentionPattern(VGroup):
|
||||
"""
|
||||
Visual representation of attention weights between tokens.
|
||||
Shows which tokens attend to which with varying line widths.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
n_tokens=8,
|
||||
token_labels=None,
|
||||
attention_weights=None,
|
||||
**kwargs
|
||||
):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
if token_labels is None:
|
||||
token_labels = [f"T{i}" for i in range(n_tokens)]
|
||||
|
||||
if attention_weights is None:
|
||||
# Random attention pattern
|
||||
attention_weights = softmax(np.random.randn(n_tokens, n_tokens), temperature=0.5)
|
||||
|
||||
# Create token representations
|
||||
self.tokens = VGroup()
|
||||
for label in token_labels:
|
||||
token = VGroup(
|
||||
Square(side_length=0.8, color=BLUE, fill_opacity=0.3),
|
||||
Text(label, font_size=24)
|
||||
)
|
||||
token[1].move_to(token[0])
|
||||
self.tokens.add(token)
|
||||
|
||||
self.tokens.arrange(RIGHT, buff=0.5)
|
||||
|
||||
# Create attention lines (simplified - just showing strongest connections)
|
||||
self.attention_lines = VGroup()
|
||||
for i in range(n_tokens):
|
||||
for j in range(n_tokens):
|
||||
if attention_weights[i, j] > 0.1: # Threshold
|
||||
line = Line(
|
||||
self.tokens[i].get_bottom(),
|
||||
self.tokens[j].get_bottom(),
|
||||
path_arc=-0.5,
|
||||
)
|
||||
line.set_stroke(
|
||||
color=YELLOW,
|
||||
width=attention_weights[i, j] * 5,
|
||||
opacity=attention_weights[i, j]
|
||||
)
|
||||
self.attention_lines.add(line)
|
||||
|
||||
self.add(self.tokens, self.attention_lines)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# ANIMATION HELPERS
|
||||
# =============================================================================
|
||||
|
||||
class RandomizeMatrixEntries(Animation):
|
||||
"""Animation that smoothly randomizes matrix entries."""
|
||||
|
||||
def __init__(self, matrix, **kwargs):
|
||||
self.matrix = matrix
|
||||
self.entries = matrix.get_entries()
|
||||
self.start_values = [
|
||||
entry.get_value() if hasattr(entry, 'get_value') else 0
|
||||
for entry in self.entries
|
||||
]
|
||||
self.target_values = np.random.uniform(
|
||||
matrix.value_range[0],
|
||||
matrix.value_range[1],
|
||||
len(self.entries)
|
||||
)
|
||||
super().__init__(matrix, **kwargs)
|
||||
|
||||
def interpolate_mobject(self, alpha: float) -> None:
|
||||
for index, entry in enumerate(self.entries):
|
||||
if hasattr(entry, 'set_value'):
|
||||
start = self.start_values[index]
|
||||
target = self.target_values[index]
|
||||
entry.set_value(interpolate(start, target, alpha))
|
||||
|
||||
|
||||
def show_attention_flow(scene, source_mobs, target_mob, weights=None, run_time=2):
|
||||
"""Helper to animate attention flow from multiple sources to a target."""
|
||||
if weights is None:
|
||||
weights = np.random.random(len(source_mobs))
|
||||
weights = weights / weights.sum()
|
||||
|
||||
arrows = VGroup()
|
||||
for source, weight in zip(source_mobs, weights):
|
||||
arrow = CurvedArrow(
|
||||
source.get_top(),
|
||||
target_mob.get_top(),
|
||||
angle=-TAU/4
|
||||
)
|
||||
arrow.set_stroke(width=weight * 5, color=YELLOW)
|
||||
arrow.set_opacity(weight)
|
||||
arrows.add(arrow)
|
||||
|
||||
scene.play(
|
||||
LaggedStart(*[Create(a) for a in arrows], lag_ratio=0.2),
|
||||
run_time=run_time
|
||||
)
|
||||
return arrows
|
||||
@@ -0,0 +1,834 @@
|
||||
"""
|
||||
Attention Mechanism Visualization - Converted from 3b1b ManimGL to ManimCE
|
||||
|
||||
Original: videos/_2024/transformers/attention.py
|
||||
Demonstrates the attention mechanism used in transformers.
|
||||
|
||||
Run with: manim -pql scenes.py SceneName
|
||||
"""
|
||||
|
||||
from manim import *
|
||||
import numpy as np
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Add parent directory to path for helpers import
|
||||
sys.path.insert(0, str(Path(__file__).parent))
|
||||
from helpers import (
|
||||
NumericEmbedding, WeightMatrix, ContextAnimation,
|
||||
NeuralNetwork, AttentionPattern, softmax, value_to_color,
|
||||
random_bright_color, show_attention_flow
|
||||
)
|
||||
|
||||
|
||||
class AttentionPatterns(Scene):
|
||||
"""
|
||||
Demonstrates how attention allows words to influence each other.
|
||||
Shows adjectives modifying nouns through attention connections.
|
||||
"""
|
||||
|
||||
def construct(self):
|
||||
# Add sentence
|
||||
phrase = "a fluffy blue creature roamed the verdant forest"
|
||||
phrase_mob = Text(phrase, font_size=36)
|
||||
phrase_mob.move_to(2 * UP)
|
||||
|
||||
words = phrase.split()
|
||||
word_mobs = VGroup()
|
||||
|
||||
# Create individual word mobjects
|
||||
current_x = phrase_mob.get_left()[0]
|
||||
for word in words:
|
||||
# Find the word in the phrase
|
||||
word_mob = Text(word, font_size=36)
|
||||
word_mobs.add(word_mob)
|
||||
|
||||
word_mobs.arrange(RIGHT, buff=0.3)
|
||||
word_mobs.move_to(2 * UP)
|
||||
|
||||
self.play(
|
||||
LaggedStart(*[FadeIn(w, shift=0.5 * UP) for w in word_mobs], lag_ratio=0.15)
|
||||
)
|
||||
self.wait()
|
||||
|
||||
# Create word rectangles
|
||||
word_rects = VGroup()
|
||||
for word_mob in word_mobs:
|
||||
rect = SurroundingRectangle(word_mob, buff=0.1)
|
||||
rect.set_stroke(GREY, 2)
|
||||
rect.set_fill(GREY, 0.2)
|
||||
word_rects.add(rect)
|
||||
|
||||
# Identify adjectives and nouns
|
||||
adj_indices = [1, 2, 6] # fluffy, blue, verdant
|
||||
noun_indices = [3, 7] # creature, forest
|
||||
|
||||
adj_rects = VGroup(*[word_rects[i] for i in adj_indices])
|
||||
noun_rects = VGroup(*[word_rects[i] for i in noun_indices])
|
||||
|
||||
adj_mobs = VGroup(*[word_mobs[i] for i in adj_indices])
|
||||
noun_mobs = VGroup(*[word_mobs[i] for i in noun_indices])
|
||||
|
||||
# Color the rectangles
|
||||
adj_rects[0].set_fill(BLUE_C, 0.3)
|
||||
adj_rects[1].set_fill(BLUE_D, 0.3)
|
||||
adj_rects[2].set_fill(GREEN, 0.3)
|
||||
noun_rects.set_fill(GREY_BROWN, 0.3)
|
||||
|
||||
self.play(
|
||||
LaggedStart(*[DrawBorderThenFill(r) for r in adj_rects], lag_ratio=0.2),
|
||||
)
|
||||
self.wait()
|
||||
|
||||
# Show arrows from adjectives to nouns
|
||||
adj_arrows = VGroup(
|
||||
CurvedArrow(adj_mobs[0].get_top(), noun_mobs[0].get_top(), angle=-0.5),
|
||||
CurvedArrow(adj_mobs[1].get_top(), noun_mobs[0].get_top(), angle=-0.5),
|
||||
CurvedArrow(adj_mobs[2].get_top(), noun_mobs[1].get_top(), angle=-0.5),
|
||||
)
|
||||
adj_arrows.set_color(GREY_B)
|
||||
|
||||
self.play(
|
||||
LaggedStart(*[DrawBorderThenFill(r) for r in noun_rects], lag_ratio=0.2),
|
||||
LaggedStart(*[Create(a) for a in adj_arrows], lag_ratio=0.2),
|
||||
)
|
||||
self.wait()
|
||||
|
||||
# Animate context flow
|
||||
self.play(
|
||||
ContextAnimation(noun_mobs[0], adj_mobs[:2], strengths=[1, 1]),
|
||||
ContextAnimation(noun_mobs[1], adj_mobs[2:], strengths=[1]),
|
||||
)
|
||||
self.wait()
|
||||
|
||||
# Show embeddings
|
||||
all_rects = word_rects.copy()
|
||||
embeddings = VGroup()
|
||||
for rect in all_rects:
|
||||
emb = NumericEmbedding(length=8)
|
||||
emb.set_width(0.4)
|
||||
emb.next_to(rect, DOWN, buff=1.2)
|
||||
embeddings.add(emb)
|
||||
|
||||
emb_arrows = VGroup()
|
||||
for rect, emb in zip(all_rects, embeddings):
|
||||
arrow = Arrow(rect.get_bottom(), emb.get_top(), buff=0.1)
|
||||
emb_arrows.add(arrow)
|
||||
|
||||
self.play(
|
||||
FadeIn(word_rects),
|
||||
LaggedStart(*[GrowArrow(a) for a in emb_arrows], lag_ratio=0.1),
|
||||
LaggedStart(*[FadeIn(e, shift=0.5 * DOWN) for e in embeddings], lag_ratio=0.1),
|
||||
FadeOut(adj_arrows)
|
||||
)
|
||||
self.wait()
|
||||
|
||||
# Show embedding dimension
|
||||
brace = Brace(embeddings[0], LEFT, buff=SMALL_BUFF)
|
||||
dim_label = Text("12,288", font_size=24, color=YELLOW)
|
||||
dim_label.next_to(brace, LEFT)
|
||||
|
||||
self.play(
|
||||
GrowFromCenter(brace),
|
||||
FadeIn(dim_label)
|
||||
)
|
||||
self.wait(2)
|
||||
|
||||
|
||||
class QueryKeyValueExplanation(Scene):
|
||||
"""
|
||||
Explains the Query, Key, Value mechanism in attention.
|
||||
"""
|
||||
|
||||
def construct(self):
|
||||
# Title
|
||||
title = Text("Query, Key, Value", font_size=48)
|
||||
title.to_edge(UP)
|
||||
self.play(Write(title))
|
||||
self.wait()
|
||||
|
||||
# Create three matrices
|
||||
q_matrix = WeightMatrix(shape=(4, 4), value_range=(-5, 5))
|
||||
k_matrix = WeightMatrix(shape=(4, 4), value_range=(-5, 5))
|
||||
v_matrix = WeightMatrix(shape=(4, 4), value_range=(-5, 5))
|
||||
|
||||
matrices = VGroup(q_matrix, k_matrix, v_matrix)
|
||||
matrices.arrange(RIGHT, buff=1)
|
||||
matrices.set_height(2)
|
||||
matrices.next_to(title, DOWN, buff=0.8)
|
||||
|
||||
# Labels
|
||||
q_label = Text("Query (Q)", font_size=30, color=BLUE)
|
||||
k_label = Text("Key (K)", font_size=30, color=GREEN)
|
||||
v_label = Text("Value (V)", font_size=30, color=RED)
|
||||
|
||||
q_label.next_to(q_matrix, UP)
|
||||
k_label.next_to(k_matrix, UP)
|
||||
v_label.next_to(v_matrix, UP)
|
||||
|
||||
self.play(
|
||||
FadeIn(q_matrix),
|
||||
Write(q_label),
|
||||
)
|
||||
self.wait()
|
||||
|
||||
self.play(
|
||||
FadeIn(k_matrix),
|
||||
Write(k_label),
|
||||
)
|
||||
self.wait()
|
||||
|
||||
self.play(
|
||||
FadeIn(v_matrix),
|
||||
Write(v_label),
|
||||
)
|
||||
self.wait()
|
||||
|
||||
# Explain the formula
|
||||
formula = MathTex(
|
||||
r"\text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V",
|
||||
font_size=36
|
||||
)
|
||||
formula.next_to(matrices, DOWN, buff=1)
|
||||
|
||||
self.play(Write(formula))
|
||||
self.wait(2)
|
||||
|
||||
# Highlight different parts
|
||||
# Q*K^T computes similarity
|
||||
explanation1 = Text("Q·K^T → measures similarity between queries and keys", font_size=24)
|
||||
explanation1.next_to(formula, DOWN, buff=0.5)
|
||||
|
||||
self.play(Write(explanation1))
|
||||
self.wait()
|
||||
|
||||
# Softmax normalizes
|
||||
explanation2 = Text("Softmax → converts to attention weights (probabilities)", font_size=24)
|
||||
explanation2.next_to(explanation1, DOWN, buff=0.3)
|
||||
|
||||
self.play(Write(explanation2))
|
||||
self.wait()
|
||||
|
||||
# Multiply by V
|
||||
explanation3 = Text("× V → weighted sum of values", font_size=24)
|
||||
explanation3.next_to(explanation2, DOWN, buff=0.3)
|
||||
|
||||
self.play(Write(explanation3))
|
||||
self.wait(2)
|
||||
|
||||
|
||||
class AttentionMatrixVisualization(Scene):
|
||||
"""
|
||||
Shows how attention scores form a matrix pattern.
|
||||
"""
|
||||
|
||||
def construct(self):
|
||||
# Create tokens
|
||||
tokens = ["The", "cat", "sat", "on", "the", "mat"]
|
||||
n = len(tokens)
|
||||
|
||||
# Token labels on left (queries)
|
||||
query_labels = VGroup(*[Text(t, font_size=24) for t in tokens])
|
||||
query_labels.arrange(DOWN, buff=0.4)
|
||||
query_labels.shift(LEFT * 4)
|
||||
|
||||
# Token labels on top (keys)
|
||||
key_labels = VGroup(*[Text(t, font_size=24) for t in tokens])
|
||||
key_labels.arrange(RIGHT, buff=0.4)
|
||||
key_labels.next_to(query_labels, RIGHT, buff=1)
|
||||
key_labels.shift(UP * 2)
|
||||
|
||||
# Create attention grid
|
||||
attention_scores = softmax(np.random.randn(n, n) * 2, temperature=0.3)
|
||||
|
||||
grid = VGroup()
|
||||
for i in range(n):
|
||||
row = VGroup()
|
||||
for j in range(n):
|
||||
score = attention_scores[i, j]
|
||||
cell = Square(side_length=0.5)
|
||||
cell.set_fill(
|
||||
color=interpolate_color(BLACK, YELLOW, score),
|
||||
opacity=0.8
|
||||
)
|
||||
cell.set_stroke(WHITE, 0.5)
|
||||
row.add(cell)
|
||||
row.arrange(RIGHT, buff=0)
|
||||
grid.add(row)
|
||||
|
||||
grid.arrange(DOWN, buff=0)
|
||||
grid.next_to(query_labels, RIGHT, buff=0.5)
|
||||
grid.align_to(query_labels, UP)
|
||||
|
||||
# Adjust key labels position
|
||||
key_labels.move_to(grid.get_top() + UP * 0.5)
|
||||
key_labels.align_to(grid, LEFT)
|
||||
|
||||
# Title
|
||||
title = Text("Attention Matrix", font_size=36)
|
||||
title.to_edge(UP)
|
||||
|
||||
self.play(Write(title))
|
||||
self.play(
|
||||
LaggedStart(*[FadeIn(l) for l in query_labels], lag_ratio=0.1),
|
||||
LaggedStart(*[FadeIn(l) for l in key_labels], lag_ratio=0.1),
|
||||
)
|
||||
self.wait()
|
||||
|
||||
# Animate grid appearing
|
||||
all_cells = VGroup(*[cell for row in grid for cell in row])
|
||||
self.play(
|
||||
LaggedStart(*[FadeIn(c, scale=0.5) for c in all_cells], lag_ratio=0.02)
|
||||
)
|
||||
self.wait()
|
||||
|
||||
# Highlight a row (how "cat" attends to all words)
|
||||
highlight_row = 1 # "cat"
|
||||
row_highlight = SurroundingRectangle(grid[highlight_row], color=BLUE, buff=0.05)
|
||||
|
||||
explanation = Text(
|
||||
'"cat" attends mostly to itself and "sat"',
|
||||
font_size=24
|
||||
)
|
||||
explanation.next_to(grid, DOWN, buff=0.5)
|
||||
|
||||
self.play(Create(row_highlight), Write(explanation))
|
||||
self.wait(2)
|
||||
|
||||
|
||||
class MultiHeadedAttention(ThreeDScene):
|
||||
"""
|
||||
Explains multi-head attention mechanism with 3D visualization.
|
||||
Shows multiple attention heads arranged in depth with camera rotation.
|
||||
Inspired by 3b1b's transformer visualization.
|
||||
"""
|
||||
|
||||
def construct(self):
|
||||
# Title animation: Single head -> Multi-head
|
||||
single_title = Text("Single head of attention", font_size=42)
|
||||
multiple_title = Text("Multi-headed attention", font_size=42)
|
||||
|
||||
for title in [single_title, multiple_title]:
|
||||
title.to_edge(UP)
|
||||
|
||||
self.play(Write(single_title))
|
||||
self.wait(0.5)
|
||||
|
||||
# Flash around "head"
|
||||
head_text = single_title[7:11] # "head"
|
||||
self.play(
|
||||
Indicate(head_text, color=YELLOW, scale_factor=1.2),
|
||||
head_text.animate.set_color(YELLOW),
|
||||
)
|
||||
self.wait(0.5)
|
||||
|
||||
# Transform title
|
||||
self.play(
|
||||
TransformMatchingShapes(single_title, multiple_title),
|
||||
run_time=1.5
|
||||
)
|
||||
self.wait()
|
||||
|
||||
# Create attention pattern visualization (grid with dots)
|
||||
def create_attention_pattern(n_rows=8, seed=None):
|
||||
"""Create a grid visualization of attention weights."""
|
||||
if seed is not None:
|
||||
np.random.seed(seed)
|
||||
|
||||
# Create the base grid
|
||||
grid = VGroup()
|
||||
cell_size = 0.4
|
||||
for i in range(n_rows):
|
||||
for j in range(n_rows):
|
||||
cell = Square(side_length=cell_size)
|
||||
cell.set_stroke(WHITE, 0.5, opacity=0.3)
|
||||
cell.move_to(np.array([j * cell_size, -i * cell_size, 0]))
|
||||
grid.add(cell)
|
||||
|
||||
grid.center()
|
||||
|
||||
# Generate causal attention pattern (lower triangular)
|
||||
pattern = np.random.normal(0, 1, (n_rows, n_rows))
|
||||
for n in range(n_rows):
|
||||
pattern[:, n][n + 1:] = -np.inf # Mask future tokens
|
||||
exp_vals = np.exp(pattern[:, n] - np.max(pattern[:, n][pattern[:, n] > -np.inf]))
|
||||
pattern[:, n] = exp_vals / np.sum(exp_vals[exp_vals < np.inf])
|
||||
pattern = np.nan_to_num(pattern, nan=0.0, posinf=0.0, neginf=0.0)
|
||||
|
||||
# Add dots based on attention weights
|
||||
dots = VGroup()
|
||||
for i in range(n_rows):
|
||||
for j in range(n_rows):
|
||||
value = pattern[i, j]
|
||||
if value > 0.05: # Threshold for visibility
|
||||
dot = Dot(
|
||||
radius=cell_size * 0.4 * value,
|
||||
color=GREY_B,
|
||||
fill_opacity=0.8
|
||||
)
|
||||
dot.move_to(grid[i * n_rows + j].get_center())
|
||||
dots.add(dot)
|
||||
|
||||
# Create border rectangle
|
||||
border = SurroundingRectangle(grid, buff=0.05)
|
||||
border.set_stroke(WHITE, 2)
|
||||
border.set_fill(BLACK, 0.9)
|
||||
|
||||
pattern_mob = VGroup(border, grid, dots)
|
||||
return pattern_mob
|
||||
|
||||
# Create multiple attention heads
|
||||
n_heads = 12
|
||||
heads = VGroup()
|
||||
for i in range(n_heads):
|
||||
head = create_attention_pattern(n_rows=6, seed=i * 42)
|
||||
head.set_height(2.5)
|
||||
heads.add(head)
|
||||
|
||||
# Arrange in 3D depth (along z-axis)
|
||||
for i, head in enumerate(heads):
|
||||
head.shift(OUT * i * 0.5) # Stack in z direction
|
||||
|
||||
heads.center()
|
||||
heads.shift(DOWN * 0.5)
|
||||
|
||||
# Show first head (screen rectangle style)
|
||||
first_head = heads[-1].copy()
|
||||
first_head.move_to(ORIGIN + DOWN * 0.5)
|
||||
first_head.shift(IN * (n_heads - 1) * 0.25) # Reset z position
|
||||
|
||||
self.play(FadeIn(first_head))
|
||||
self.wait()
|
||||
|
||||
# Add fixed-in-frame elements
|
||||
self.add_fixed_in_frame_mobjects(multiple_title)
|
||||
|
||||
# Rotate camera to reveal depth
|
||||
self.move_camera(
|
||||
phi=70 * DEGREES,
|
||||
theta=-60 * DEGREES,
|
||||
run_time=2
|
||||
)
|
||||
|
||||
# Fan out the heads from the first one
|
||||
self.play(
|
||||
LaggedStart(
|
||||
*[FadeIn(head, shift=OUT * 0.3) for head in heads[:-1]],
|
||||
lag_ratio=0.15
|
||||
),
|
||||
FadeOut(first_head),
|
||||
run_time=3
|
||||
)
|
||||
self.add(heads)
|
||||
self.wait()
|
||||
|
||||
# Add matrix labels for each head (W_Q, W_K)
|
||||
wq_labels = VGroup()
|
||||
wk_labels = VGroup()
|
||||
|
||||
colors = [YELLOW, TEAL]
|
||||
n_shown = min(5, n_heads)
|
||||
|
||||
for i, head in enumerate(list(heads)[-n_shown:]):
|
||||
head_num = n_heads - n_shown + i + 1
|
||||
wq = MathTex(f"W_Q^{{({head_num})}}", font_size=28, color=YELLOW)
|
||||
wk = MathTex(f"W_K^{{({head_num})}}", font_size=28, color=TEAL)
|
||||
|
||||
# Position above each head
|
||||
wq.next_to(head, UP, buff=0.2)
|
||||
wq.shift(LEFT * 0.3)
|
||||
wk.next_to(head, UP, buff=0.2)
|
||||
wk.shift(RIGHT * 0.3)
|
||||
|
||||
# Rotate to face camera
|
||||
for label in [wq, wk]:
|
||||
label.rotate(70 * DEGREES, axis=RIGHT)
|
||||
label.rotate(-60 * DEGREES, axis=OUT)
|
||||
|
||||
wq_labels.add(wq)
|
||||
wk_labels.add(wk)
|
||||
|
||||
# Add dots to indicate more heads
|
||||
dots_label = MathTex(r"\cdots", font_size=48, color=WHITE)
|
||||
dots_label.next_to(heads[0], OUT, buff=0.5)
|
||||
dots_label.rotate(70 * DEGREES, axis=RIGHT)
|
||||
dots_label.rotate(-60 * DEGREES, axis=OUT)
|
||||
|
||||
self.play(
|
||||
LaggedStart(*[FadeIn(wq, shift=UP * 0.2) for wq in wq_labels], lag_ratio=0.2),
|
||||
run_time=1.5
|
||||
)
|
||||
self.play(
|
||||
LaggedStart(*[FadeIn(wk, shift=UP * 0.2) for wk in wk_labels], lag_ratio=0.2),
|
||||
FadeIn(dots_label),
|
||||
run_time=1.5
|
||||
)
|
||||
self.wait()
|
||||
|
||||
# Add brace showing "96 heads" (scaled down for demonstration)
|
||||
brace_text = Text("96 heads", font_size=36, color=WHITE)
|
||||
brace_text.rotate(70 * DEGREES, axis=RIGHT)
|
||||
brace_text.rotate(-60 * DEGREES, axis=OUT)
|
||||
brace_text.next_to(heads, UP, buff=0.8)
|
||||
brace_text.shift(LEFT * 2)
|
||||
|
||||
self.play(FadeIn(brace_text, shift=UP * 0.3))
|
||||
self.wait()
|
||||
|
||||
# Rotate camera to show different angle
|
||||
self.move_camera(
|
||||
phi=60 * DEGREES,
|
||||
theta=-80 * DEGREES,
|
||||
run_time=2
|
||||
)
|
||||
self.wait()
|
||||
|
||||
# Explanation text (fixed in frame)
|
||||
explanation = VGroup(
|
||||
Text("Each head learns different patterns:", font_size=24),
|
||||
Text("• Syntactic relationships", font_size=20, color=BLUE),
|
||||
Text("• Semantic connections", font_size=20, color=GREEN),
|
||||
Text("• Positional patterns", font_size=20, color=YELLOW),
|
||||
)
|
||||
explanation.arrange(DOWN, aligned_edge=LEFT, buff=0.15)
|
||||
explanation.to_corner(DL, buff=0.5)
|
||||
|
||||
self.add_fixed_in_frame_mobjects(explanation)
|
||||
self.play(
|
||||
LaggedStart(*[Write(e) for e in explanation], lag_ratio=0.3)
|
||||
)
|
||||
self.wait()
|
||||
|
||||
# Return to front view
|
||||
self.move_camera(
|
||||
phi=0,
|
||||
theta=-90 * DEGREES,
|
||||
run_time=2
|
||||
)
|
||||
self.wait()
|
||||
|
||||
# Show concatenation concept
|
||||
concat_text = Text("Concatenate outputs from all heads", font_size=28)
|
||||
concat_text.to_edge(DOWN, buff=0.5)
|
||||
self.add_fixed_in_frame_mobjects(concat_text)
|
||||
self.play(Write(concat_text))
|
||||
self.wait()
|
||||
|
||||
# Final hold
|
||||
self.wait()
|
||||
self.wait(2)
|
||||
|
||||
|
||||
class SelfAttentionDemo(Scene):
|
||||
"""
|
||||
Interactive demonstration of self-attention on a simple sentence.
|
||||
"""
|
||||
|
||||
def construct(self):
|
||||
# Title
|
||||
title = Text("Self-Attention in Action", font_size=42)
|
||||
title.to_edge(UP)
|
||||
self.play(Write(title))
|
||||
|
||||
# Create sentence
|
||||
sentence = "The quick brown fox"
|
||||
words = sentence.split()
|
||||
|
||||
word_boxes = VGroup()
|
||||
for word in words:
|
||||
box = VGroup(
|
||||
RoundedRectangle(
|
||||
width=1.5, height=0.8,
|
||||
corner_radius=0.1,
|
||||
fill_opacity=0.3,
|
||||
fill_color=BLUE,
|
||||
stroke_color=WHITE
|
||||
),
|
||||
Text(word, font_size=28)
|
||||
)
|
||||
box[1].move_to(box[0])
|
||||
word_boxes.add(box)
|
||||
|
||||
word_boxes.arrange(RIGHT, buff=0.5)
|
||||
word_boxes.next_to(title, DOWN, buff=1)
|
||||
|
||||
self.play(
|
||||
LaggedStart(*[FadeIn(b, scale=0.8) for b in word_boxes], lag_ratio=0.2)
|
||||
)
|
||||
self.wait()
|
||||
|
||||
# Show attention from "fox" to other words
|
||||
target_idx = 3 # "fox"
|
||||
attention_weights = [0.1, 0.3, 0.4, 0.2] # Attention weights
|
||||
|
||||
# Highlight target
|
||||
target_box = word_boxes[target_idx]
|
||||
target_highlight = SurroundingRectangle(target_box, color=YELLOW, buff=0.1)
|
||||
|
||||
self.play(Create(target_highlight))
|
||||
|
||||
# Create attention arrows
|
||||
attention_arrows = VGroup()
|
||||
weight_labels = VGroup()
|
||||
|
||||
for i, (box, weight) in enumerate(zip(word_boxes, attention_weights)):
|
||||
if i != target_idx:
|
||||
arrow = CurvedArrow(
|
||||
box.get_bottom() + DOWN * 0.1,
|
||||
target_box.get_bottom() + DOWN * 0.1,
|
||||
angle=0.5 if i < target_idx else -0.5
|
||||
)
|
||||
arrow.set_stroke(
|
||||
color=interpolate_color(GREY, YELLOW, weight),
|
||||
width=weight * 8
|
||||
)
|
||||
attention_arrows.add(arrow)
|
||||
|
||||
label = DecimalNumber(weight, num_decimal_places=1, font_size=20)
|
||||
label.next_to(arrow.point_from_proportion(0.5), DOWN, buff=0.1)
|
||||
weight_labels.add(label)
|
||||
|
||||
self.play(
|
||||
LaggedStart(*[Create(a) for a in attention_arrows], lag_ratio=0.2),
|
||||
LaggedStart(*[FadeIn(l) for l in weight_labels], lag_ratio=0.2),
|
||||
)
|
||||
self.wait()
|
||||
|
||||
# Show weighted combination
|
||||
result_text = Text(
|
||||
'"fox" = 0.1×"The" + 0.3×"quick" + 0.4×"brown" + 0.2×"fox"',
|
||||
font_size=24
|
||||
)
|
||||
result_text.next_to(word_boxes, DOWN, buff=1.5)
|
||||
|
||||
self.play(Write(result_text))
|
||||
self.wait(2)
|
||||
|
||||
|
||||
class ScaledDotProductAttention(Scene):
|
||||
"""
|
||||
Step-by-step visualization of scaled dot-product attention.
|
||||
"""
|
||||
|
||||
def construct(self):
|
||||
# Title
|
||||
title = Text("Scaled Dot-Product Attention", font_size=40)
|
||||
title.to_edge(UP)
|
||||
self.play(Write(title))
|
||||
|
||||
# Step 1: Show Q, K, V
|
||||
step1 = Text("Step 1: Compute Q, K, V from input", font_size=28)
|
||||
step1.next_to(title, DOWN, buff=0.5)
|
||||
|
||||
q_vec = NumericEmbedding(length=4).set_height(1.5)
|
||||
k_vec = NumericEmbedding(length=4).set_height(1.5)
|
||||
v_vec = NumericEmbedding(length=4).set_height(1.5)
|
||||
|
||||
vectors = VGroup(q_vec, k_vec, v_vec)
|
||||
vectors.arrange(RIGHT, buff=1)
|
||||
vectors.next_to(step1, DOWN, buff=0.5)
|
||||
|
||||
q_label = Text("Q", color=BLUE, font_size=24).next_to(q_vec, UP)
|
||||
k_label = Text("K", color=GREEN, font_size=24).next_to(k_vec, UP)
|
||||
v_label = Text("V", color=RED, font_size=24).next_to(v_vec, UP)
|
||||
|
||||
self.play(Write(step1))
|
||||
self.play(
|
||||
FadeIn(q_vec), FadeIn(k_vec), FadeIn(v_vec),
|
||||
Write(q_label), Write(k_label), Write(v_label)
|
||||
)
|
||||
self.wait()
|
||||
|
||||
# Step 2: Compute Q·K^T
|
||||
self.play(
|
||||
FadeOut(step1),
|
||||
VGroup(vectors, q_label, k_label, v_label).animate.shift(UP)
|
||||
)
|
||||
|
||||
step2 = Text("Step 2: Q · K^T (dot product)", font_size=28)
|
||||
step2.next_to(title, DOWN, buff=0.5)
|
||||
|
||||
dot_product = MathTex(r"Q \cdot K^T = ", font_size=36)
|
||||
score = DecimalNumber(2.5, font_size=36, color=YELLOW)
|
||||
dot_result = VGroup(dot_product, score).arrange(RIGHT)
|
||||
dot_result.next_to(vectors, DOWN, buff=0.5)
|
||||
|
||||
self.play(Write(step2))
|
||||
self.play(Write(dot_product), FadeIn(score))
|
||||
self.wait()
|
||||
|
||||
# Step 3: Scale
|
||||
self.play(FadeOut(step2))
|
||||
step3 = Text("Step 3: Scale by √d_k", font_size=28)
|
||||
step3.next_to(title, DOWN, buff=0.5)
|
||||
|
||||
scale_formula = MathTex(r"\frac{Q \cdot K^T}{\sqrt{d_k}} = \frac{2.5}{\sqrt{4}} = 1.25", font_size=32)
|
||||
scale_formula.next_to(dot_result, DOWN, buff=0.3)
|
||||
|
||||
self.play(Write(step3))
|
||||
self.play(Write(scale_formula))
|
||||
self.wait()
|
||||
|
||||
# Step 4: Softmax
|
||||
self.play(FadeOut(step3))
|
||||
step4 = Text("Step 4: Softmax → attention weights", font_size=28)
|
||||
step4.next_to(title, DOWN, buff=0.5)
|
||||
|
||||
softmax_text = MathTex(r"\text{softmax}(1.25) \rightarrow \text{weights}", font_size=32)
|
||||
softmax_text.next_to(scale_formula, DOWN, buff=0.3)
|
||||
|
||||
self.play(Write(step4))
|
||||
self.play(Write(softmax_text))
|
||||
self.wait()
|
||||
|
||||
# Step 5: Multiply by V
|
||||
self.play(FadeOut(step4))
|
||||
step5 = Text("Step 5: Weighted sum of V", font_size=28)
|
||||
step5.next_to(title, DOWN, buff=0.5)
|
||||
|
||||
final = MathTex(r"\text{Output} = \text{weights} \times V", font_size=32)
|
||||
final.next_to(softmax_text, DOWN, buff=0.3)
|
||||
|
||||
self.play(Write(step5))
|
||||
self.play(Write(final))
|
||||
self.wait(2)
|
||||
|
||||
|
||||
class PositionalEncoding(Scene):
|
||||
"""
|
||||
Explains positional encoding in transformers.
|
||||
"""
|
||||
|
||||
def construct(self):
|
||||
title = Text("Positional Encoding", font_size=42)
|
||||
title.to_edge(UP)
|
||||
self.play(Write(title))
|
||||
|
||||
# Problem statement
|
||||
problem = Text(
|
||||
"Problem: Attention has no sense of word order!",
|
||||
font_size=28, color=RED
|
||||
)
|
||||
problem.next_to(title, DOWN, buff=0.5)
|
||||
self.play(Write(problem))
|
||||
self.wait()
|
||||
|
||||
# Show two sentences
|
||||
sent1 = Text('"The cat ate the fish"', font_size=24)
|
||||
sent2 = Text('"The fish ate the cat"', font_size=24)
|
||||
sents = VGroup(sent1, sent2).arrange(DOWN, buff=0.3)
|
||||
sents.next_to(problem, DOWN, buff=0.5)
|
||||
|
||||
self.play(Write(sent1), Write(sent2))
|
||||
self.wait()
|
||||
|
||||
# Show they have same words
|
||||
same = Text("Same words, different meanings!", font_size=24, color=YELLOW)
|
||||
same.next_to(sents, DOWN, buff=0.3)
|
||||
self.play(Write(same))
|
||||
self.wait()
|
||||
|
||||
# Solution
|
||||
self.play(FadeOut(problem), FadeOut(sents), FadeOut(same))
|
||||
|
||||
solution = Text(
|
||||
"Solution: Add position information to embeddings",
|
||||
font_size=28, color=GREEN
|
||||
)
|
||||
solution.next_to(title, DOWN, buff=0.5)
|
||||
self.play(Write(solution))
|
||||
|
||||
# Show formula
|
||||
formula = MathTex(
|
||||
r"PE_{(pos, 2i)} &= \sin\left(\frac{pos}{10000^{2i/d}}\right) \\",
|
||||
r"PE_{(pos, 2i+1)} &= \cos\left(\frac{pos}{10000^{2i/d}}\right)",
|
||||
font_size=32
|
||||
)
|
||||
formula.next_to(solution, DOWN, buff=0.5)
|
||||
self.play(Write(formula))
|
||||
self.wait()
|
||||
|
||||
# Visual representation
|
||||
positions = VGroup()
|
||||
for i in range(5):
|
||||
pos_vec = VGroup()
|
||||
for j in range(8):
|
||||
val = np.sin(i / (10000 ** (j / 8))) if j % 2 == 0 else np.cos(i / (10000 ** (j / 8)))
|
||||
cell = Square(side_length=0.3)
|
||||
cell.set_fill(interpolate_color(BLUE, RED, (val + 1) / 2), opacity=0.8)
|
||||
cell.set_stroke(WHITE, 0.5)
|
||||
pos_vec.add(cell)
|
||||
pos_vec.arrange(DOWN, buff=0)
|
||||
positions.add(pos_vec)
|
||||
|
||||
positions.arrange(RIGHT, buff=0.2)
|
||||
positions.set_height(2)
|
||||
positions.next_to(formula, DOWN, buff=0.5)
|
||||
|
||||
pos_labels = VGroup(*[
|
||||
Text(f"pos={i}", font_size=16).next_to(p, DOWN, buff=0.1)
|
||||
for i, p in enumerate(positions)
|
||||
])
|
||||
|
||||
self.play(
|
||||
LaggedStart(*[FadeIn(p) for p in positions], lag_ratio=0.1),
|
||||
LaggedStart(*[FadeIn(l) for l in pos_labels], lag_ratio=0.1),
|
||||
)
|
||||
self.wait(2)
|
||||
|
||||
|
||||
# Additional simplified scenes for the key concepts
|
||||
|
||||
class WhatIsAttention(Scene):
|
||||
"""Simple introduction to attention."""
|
||||
|
||||
def construct(self):
|
||||
title = Text("What is Attention?", font_size=48)
|
||||
title.to_edge(UP)
|
||||
self.play(Write(title))
|
||||
|
||||
# Key idea
|
||||
idea = Text(
|
||||
"Attention lets each word look at other words\nto understand context",
|
||||
font_size=32, line_spacing=1.5
|
||||
)
|
||||
idea.next_to(title, DOWN, buff=1)
|
||||
self.play(Write(idea))
|
||||
self.wait()
|
||||
|
||||
# Example
|
||||
example_sentence = Text("The bank was steep", font_size=36)
|
||||
example_sentence.next_to(idea, DOWN, buff=1)
|
||||
|
||||
self.play(Write(example_sentence))
|
||||
self.wait()
|
||||
|
||||
# Highlight "bank" and "steep"
|
||||
bank_box = SurroundingRectangle(
|
||||
example_sentence[4:8], # "bank"
|
||||
color=YELLOW, buff=0.05
|
||||
)
|
||||
steep_box = SurroundingRectangle(
|
||||
example_sentence[13:18], # "steep"
|
||||
color=GREEN, buff=0.05
|
||||
)
|
||||
|
||||
self.play(Create(bank_box))
|
||||
self.wait()
|
||||
|
||||
arrow = CurvedArrow(
|
||||
steep_box.get_top(),
|
||||
bank_box.get_top(),
|
||||
angle=-0.5,
|
||||
color=YELLOW
|
||||
)
|
||||
|
||||
self.play(Create(steep_box), Create(arrow))
|
||||
|
||||
meaning = Text(
|
||||
'"steep" helps us know "bank" means riverbank, not financial bank',
|
||||
font_size=24, color=GREY_B
|
||||
)
|
||||
meaning.next_to(example_sentence, DOWN, buff=0.8)
|
||||
self.play(Write(meaning))
|
||||
self.wait(2)
|
||||
@@ -0,0 +1,212 @@
|
||||
"""
|
||||
Basic Animation Patterns for Manim Community
|
||||
|
||||
This file demonstrates fundamental animation techniques adapted from 3b1b patterns.
|
||||
Run with: manim -pql basic_animations.py SceneName
|
||||
"""
|
||||
|
||||
from manim import *
|
||||
|
||||
|
||||
class ShapeCreation(Scene):
|
||||
"""Demonstrates various ways to create and animate shapes."""
|
||||
|
||||
def construct(self):
|
||||
# Create shapes
|
||||
circle = Circle(radius=1, color=BLUE, fill_opacity=0.5)
|
||||
square = Square(side_length=2, color=RED)
|
||||
triangle = Triangle(color=GREEN, fill_opacity=0.8)
|
||||
|
||||
# Arrange shapes
|
||||
shapes = VGroup(circle, square, triangle).arrange(RIGHT, buff=1)
|
||||
|
||||
# Different creation animations
|
||||
self.play(Create(circle)) # Draw outline progressively
|
||||
self.play(DrawBorderThenFill(square)) # Border first, then fill
|
||||
self.play(GrowFromCenter(triangle)) # Grow from center point
|
||||
|
||||
self.wait()
|
||||
|
||||
# Transform between shapes
|
||||
self.play(Transform(circle, square.copy().shift(UP * 2)))
|
||||
|
||||
self.wait()
|
||||
|
||||
|
||||
class TextAnimations(Scene):
|
||||
"""Demonstrates text and LaTeX animations."""
|
||||
|
||||
def construct(self):
|
||||
# Plain text
|
||||
title = Text("Manim Community", font_size=72, color=BLUE)
|
||||
self.play(Write(title))
|
||||
self.wait()
|
||||
|
||||
# Move title up
|
||||
self.play(title.animate.to_edge(UP))
|
||||
|
||||
# LaTeX math
|
||||
equation = MathTex(r"e^{i\pi} + 1 = 0", font_size=64)
|
||||
self.play(Write(equation))
|
||||
self.wait()
|
||||
|
||||
# Transform equation
|
||||
expanded = MathTex(r"e^{i\pi} = -1", font_size=64)
|
||||
self.play(TransformMatchingTex(equation, expanded))
|
||||
|
||||
self.wait()
|
||||
|
||||
|
||||
class LaggedAnimations(Scene):
|
||||
"""Demonstrates staggered animations using LaggedStart patterns."""
|
||||
|
||||
def construct(self):
|
||||
# Create a grid of dots
|
||||
dots = VGroup(*[
|
||||
Dot(radius=0.15, color=interpolate_color(BLUE, RED, i / 24))
|
||||
for i in range(25)
|
||||
]).arrange_in_grid(rows=5, cols=5, buff=0.5)
|
||||
|
||||
# Staggered fade in
|
||||
self.play(
|
||||
LaggedStart(*[FadeIn(dot, scale=0.5) for dot in dots], lag_ratio=0.1)
|
||||
)
|
||||
self.wait()
|
||||
|
||||
# Staggered transformation using LaggedStart with animate
|
||||
self.play(
|
||||
LaggedStart(
|
||||
*[dot.animate.scale(1.5).set_color(YELLOW) for dot in dots],
|
||||
lag_ratio=0.05
|
||||
)
|
||||
)
|
||||
self.wait()
|
||||
|
||||
# Wave effect using AnimationGroup with rate_func
|
||||
self.play(
|
||||
LaggedStart(
|
||||
*[dot.animate(rate_func=there_and_back).shift(UP * 0.5) for dot in dots],
|
||||
lag_ratio=0.02,
|
||||
run_time=2
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class AnimationComposition(Scene):
|
||||
"""Demonstrates combining multiple animations."""
|
||||
|
||||
def construct(self):
|
||||
# Create objects
|
||||
circle = Circle(color=BLUE, fill_opacity=0.5)
|
||||
label = Text("Circle", font_size=36).next_to(circle, DOWN)
|
||||
|
||||
# Group them
|
||||
group = VGroup(circle, label)
|
||||
|
||||
# Animate together
|
||||
self.play(
|
||||
Create(circle),
|
||||
Write(label),
|
||||
run_time=2
|
||||
)
|
||||
self.wait()
|
||||
|
||||
# Sequential animations with Succession
|
||||
square = Square(color=RED, fill_opacity=0.5).shift(RIGHT * 3)
|
||||
square_label = Text("Square", font_size=36).next_to(square, DOWN)
|
||||
|
||||
self.play(
|
||||
Succession(
|
||||
group.animate.shift(LEFT * 2),
|
||||
Create(square),
|
||||
Write(square_label),
|
||||
lag_ratio=0.5
|
||||
)
|
||||
)
|
||||
self.wait()
|
||||
|
||||
|
||||
class PathAnimations(Scene):
|
||||
"""Demonstrates movement along paths."""
|
||||
|
||||
def construct(self):
|
||||
# Create a path
|
||||
path = VMobject()
|
||||
path.set_points_smoothly([
|
||||
LEFT * 3,
|
||||
LEFT * 2 + UP * 2,
|
||||
ORIGIN + UP,
|
||||
RIGHT * 2 + UP * 2,
|
||||
RIGHT * 3,
|
||||
])
|
||||
path.set_color(GREY)
|
||||
|
||||
# Create moving object
|
||||
dot = Dot(color=RED, radius=0.2)
|
||||
dot.move_to(path.get_start())
|
||||
|
||||
self.add(path)
|
||||
self.play(Create(path))
|
||||
|
||||
# Move along path
|
||||
self.play(MoveAlongPath(dot, path), run_time=3, rate_func=smooth)
|
||||
|
||||
self.wait()
|
||||
|
||||
|
||||
class ColorTransitions(Scene):
|
||||
"""Demonstrates color manipulation and gradients."""
|
||||
|
||||
def construct(self):
|
||||
# Color gradient on shapes
|
||||
squares = VGroup(*[
|
||||
Square(side_length=0.8, fill_opacity=0.8)
|
||||
for _ in range(7)
|
||||
]).arrange(RIGHT, buff=0.2)
|
||||
|
||||
# Apply gradient colors
|
||||
colors = [RED, ORANGE, YELLOW, GREEN, BLUE, PURPLE, PINK]
|
||||
for square, color in zip(squares, colors):
|
||||
square.set_fill(color)
|
||||
square.set_stroke(WHITE, width=2)
|
||||
|
||||
self.play(LaggedStartMap(GrowFromCenter, squares, lag_ratio=0.1))
|
||||
self.wait()
|
||||
|
||||
# Animate color change
|
||||
self.play(
|
||||
*[square.animate.set_fill(interpolate_color(BLUE, RED, i / 6))
|
||||
for i, square in enumerate(squares)],
|
||||
run_time=2
|
||||
)
|
||||
self.wait()
|
||||
|
||||
|
||||
class GroupOperations(Scene):
|
||||
"""Demonstrates VGroup operations and arrangements."""
|
||||
|
||||
def construct(self):
|
||||
# Create VGroup
|
||||
shapes = VGroup(
|
||||
Circle(color=RED),
|
||||
Square(color=GREEN),
|
||||
Triangle(color=BLUE),
|
||||
)
|
||||
|
||||
# Arrange horizontally
|
||||
shapes.arrange(RIGHT, buff=1)
|
||||
self.play(Create(shapes))
|
||||
self.wait()
|
||||
|
||||
# Scale entire group
|
||||
self.play(shapes.animate.scale(0.5))
|
||||
self.wait()
|
||||
|
||||
# Arrange vertically
|
||||
self.play(shapes.animate.arrange(DOWN, buff=0.5))
|
||||
self.wait()
|
||||
|
||||
# Apply operation to all
|
||||
self.play(shapes.animate.set_fill(YELLOW, opacity=0.5))
|
||||
|
||||
self.wait()
|
||||
@@ -0,0 +1,401 @@
|
||||
"""
|
||||
Graph and Function Plotting Patterns for Manim Community
|
||||
|
||||
Demonstrates Axes, NumberPlane, function plotting, and coordinate systems.
|
||||
Adapted from 3b1b patterns for ManimCE.
|
||||
|
||||
Run with: manim -pql graph_plotting.py SceneName
|
||||
"""
|
||||
|
||||
from manim import *
|
||||
import numpy as np
|
||||
|
||||
|
||||
class BasicAxes(Scene):
|
||||
"""Basic axes setup and labeling."""
|
||||
|
||||
def construct(self):
|
||||
# Create axes
|
||||
axes = Axes(
|
||||
x_range=[-3, 3, 1],
|
||||
y_range=[-2, 2, 1],
|
||||
x_length=8,
|
||||
y_length=5,
|
||||
axis_config={
|
||||
"include_tip": True,
|
||||
"include_numbers": True,
|
||||
},
|
||||
)
|
||||
|
||||
# Labels
|
||||
x_label = axes.get_x_axis_label("x")
|
||||
y_label = axes.get_y_axis_label("y")
|
||||
|
||||
self.play(Create(axes), Write(x_label), Write(y_label))
|
||||
self.wait()
|
||||
|
||||
|
||||
class FunctionPlotting(Scene):
|
||||
"""Plotting functions on axes."""
|
||||
|
||||
def construct(self):
|
||||
axes = Axes(
|
||||
x_range=[-3, 3, 1],
|
||||
y_range=[-1, 9, 2],
|
||||
x_length=8,
|
||||
y_length=5,
|
||||
axis_config={"include_numbers": True},
|
||||
)
|
||||
|
||||
# Plot y = x^2
|
||||
parabola = axes.plot(
|
||||
lambda x: x ** 2,
|
||||
color=BLUE,
|
||||
x_range=[-3, 3]
|
||||
)
|
||||
|
||||
# Label
|
||||
label = MathTex(r"y = x^2", color=BLUE)
|
||||
label.next_to(parabola, UR)
|
||||
|
||||
self.play(Create(axes))
|
||||
self.play(Create(parabola), Write(label))
|
||||
self.wait()
|
||||
|
||||
|
||||
class MultipleFunctions(Scene):
|
||||
"""Multiple functions on same axes."""
|
||||
|
||||
def construct(self):
|
||||
axes = Axes(
|
||||
x_range=[-2 * PI, 2 * PI, PI / 2],
|
||||
y_range=[-1.5, 1.5, 0.5],
|
||||
x_length=10,
|
||||
y_length=4,
|
||||
)
|
||||
|
||||
# Plot sine and cosine
|
||||
sine = axes.plot(np.sin, color=BLUE, x_range=[-2 * PI, 2 * PI])
|
||||
cosine = axes.plot(np.cos, color=RED, x_range=[-2 * PI, 2 * PI])
|
||||
|
||||
# Labels
|
||||
sin_label = MathTex(r"\sin(x)", color=BLUE).to_corner(UR)
|
||||
cos_label = MathTex(r"\cos(x)", color=RED).next_to(sin_label, DOWN)
|
||||
|
||||
self.play(Create(axes))
|
||||
self.play(Create(sine), Write(sin_label))
|
||||
self.play(Create(cosine), Write(cos_label))
|
||||
self.wait()
|
||||
|
||||
|
||||
class AreaUnderCurve(Scene):
|
||||
"""Visualizing area under a curve (integration)."""
|
||||
|
||||
def construct(self):
|
||||
axes = Axes(
|
||||
x_range=[0, 5, 1],
|
||||
y_range=[0, 10, 2],
|
||||
x_length=8,
|
||||
y_length=5,
|
||||
)
|
||||
|
||||
# Function
|
||||
func = axes.plot(lambda x: 0.5 * x ** 2, color=BLUE, x_range=[0, 4])
|
||||
|
||||
# Area under curve from x=1 to x=3
|
||||
area = axes.get_area(
|
||||
func,
|
||||
x_range=[1, 3],
|
||||
color=BLUE,
|
||||
opacity=0.3
|
||||
)
|
||||
|
||||
# Integral notation
|
||||
integral = MathTex(
|
||||
r"\int_1^3 \frac{x^2}{2} \, dx",
|
||||
font_size=48
|
||||
).to_corner(UR)
|
||||
|
||||
self.play(Create(axes))
|
||||
self.play(Create(func))
|
||||
self.play(FadeIn(area))
|
||||
self.play(Write(integral))
|
||||
self.wait()
|
||||
|
||||
|
||||
class NumberPlaneExample(Scene):
|
||||
"""Using NumberPlane for coordinate grid."""
|
||||
|
||||
def construct(self):
|
||||
# Create number plane
|
||||
plane = NumberPlane(
|
||||
x_range=[-7, 7, 1],
|
||||
y_range=[-4, 4, 1],
|
||||
background_line_style={
|
||||
"stroke_color": BLUE_D,
|
||||
"stroke_width": 1,
|
||||
"stroke_opacity": 0.5,
|
||||
}
|
||||
)
|
||||
|
||||
# Plot a point
|
||||
point = Dot(plane.c2p(2, 3), color=RED, radius=0.15)
|
||||
point_label = MathTex("(2, 3)", color=RED).next_to(point, UR, buff=0.1)
|
||||
|
||||
# Vector from origin to point
|
||||
vector = Arrow(
|
||||
plane.c2p(0, 0),
|
||||
plane.c2p(2, 3),
|
||||
buff=0,
|
||||
color=YELLOW
|
||||
)
|
||||
|
||||
self.play(Create(plane))
|
||||
self.play(GrowArrow(vector))
|
||||
self.play(FadeIn(point), Write(point_label))
|
||||
self.wait()
|
||||
|
||||
|
||||
class ParametricCurve(Scene):
|
||||
"""Plotting parametric curves."""
|
||||
|
||||
def construct(self):
|
||||
axes = Axes(
|
||||
x_range=[-4, 4, 1],
|
||||
y_range=[-4, 4, 1],
|
||||
x_length=7,
|
||||
y_length=7,
|
||||
)
|
||||
|
||||
# Parametric curve (circle)
|
||||
circle = axes.plot_parametric_curve(
|
||||
lambda t: np.array([2 * np.cos(t), 2 * np.sin(t), 0]),
|
||||
t_range=[0, 2 * PI],
|
||||
color=BLUE
|
||||
)
|
||||
|
||||
# Lissajous curve
|
||||
lissajous = axes.plot_parametric_curve(
|
||||
lambda t: np.array([2 * np.sin(3 * t), 2 * np.sin(2 * t), 0]),
|
||||
t_range=[0, 2 * PI],
|
||||
color=RED
|
||||
)
|
||||
|
||||
self.play(Create(axes))
|
||||
self.play(Create(circle))
|
||||
self.wait()
|
||||
self.play(Transform(circle, lissajous))
|
||||
self.wait()
|
||||
|
||||
|
||||
class TangentLine(Scene):
|
||||
"""Showing tangent line to a curve."""
|
||||
|
||||
def construct(self):
|
||||
axes = Axes(
|
||||
x_range=[-1, 4, 1],
|
||||
y_range=[-1, 10, 2],
|
||||
x_length=8,
|
||||
y_length=5,
|
||||
)
|
||||
|
||||
# Function y = x^2
|
||||
func = axes.plot(lambda x: x ** 2, color=BLUE, x_range=[0, 3])
|
||||
|
||||
# Point of tangency at x = 2
|
||||
x_val = 2
|
||||
point = Dot(axes.c2p(x_val, x_val ** 2), color=RED)
|
||||
|
||||
# Tangent line: derivative of x^2 is 2x, at x=2 slope is 4
|
||||
tangent = axes.plot(
|
||||
lambda x: 4 * (x - 2) + 4, # Point-slope form
|
||||
color=YELLOW,
|
||||
x_range=[0.5, 3.5]
|
||||
)
|
||||
|
||||
# Label
|
||||
slope_label = MathTex(r"m = 2x = 4", color=YELLOW).to_corner(UR)
|
||||
|
||||
self.play(Create(axes))
|
||||
self.play(Create(func))
|
||||
self.play(FadeIn(point))
|
||||
self.play(Create(tangent), Write(slope_label))
|
||||
self.wait()
|
||||
|
||||
|
||||
class AnimatedGraph(Scene):
|
||||
"""Animating a function parameter change."""
|
||||
|
||||
def construct(self):
|
||||
axes = Axes(
|
||||
x_range=[-3, 3, 1],
|
||||
y_range=[-2, 2, 1],
|
||||
x_length=8,
|
||||
y_length=5,
|
||||
)
|
||||
|
||||
# Amplitude tracker
|
||||
amplitude = ValueTracker(1)
|
||||
|
||||
# Graph that updates with amplitude
|
||||
graph = always_redraw(
|
||||
lambda: axes.plot(
|
||||
lambda x: amplitude.get_value() * np.sin(x),
|
||||
color=BLUE,
|
||||
x_range=[-3, 3]
|
||||
)
|
||||
)
|
||||
|
||||
# Amplitude display
|
||||
amp_text = always_redraw(
|
||||
lambda: MathTex(
|
||||
f"A = {amplitude.get_value():.1f}"
|
||||
).to_corner(UR)
|
||||
)
|
||||
|
||||
self.add(axes, graph, amp_text)
|
||||
|
||||
# Animate amplitude change
|
||||
self.play(amplitude.animate.set_value(2), run_time=2)
|
||||
self.play(amplitude.animate.set_value(0.5), run_time=2)
|
||||
self.play(amplitude.animate.set_value(1.5), run_time=1)
|
||||
self.wait()
|
||||
|
||||
|
||||
class RiemannSum(Scene):
|
||||
"""Visualizing Riemann sums for integration."""
|
||||
|
||||
def construct(self):
|
||||
axes = Axes(
|
||||
x_range=[0, 5, 1],
|
||||
y_range=[0, 5, 1],
|
||||
x_length=8,
|
||||
y_length=5,
|
||||
)
|
||||
|
||||
# Function
|
||||
func = axes.plot(lambda x: 0.2 * x ** 2, color=BLUE, x_range=[0, 4])
|
||||
|
||||
self.play(Create(axes), Create(func))
|
||||
self.wait()
|
||||
|
||||
# Riemann rectangles
|
||||
dx_values = [1, 0.5, 0.25]
|
||||
|
||||
for dx in dx_values:
|
||||
rects = axes.get_riemann_rectangles(
|
||||
func,
|
||||
x_range=[1, 3],
|
||||
dx=dx,
|
||||
color=BLUE,
|
||||
fill_opacity=0.5,
|
||||
stroke_width=1,
|
||||
)
|
||||
|
||||
if dx == 1:
|
||||
self.play(Create(rects))
|
||||
else:
|
||||
self.play(Transform(rects, rects))
|
||||
|
||||
self.wait()
|
||||
|
||||
|
||||
class ImplicitFunction(Scene):
|
||||
"""Plotting implicit functions (level curves)."""
|
||||
|
||||
def construct(self):
|
||||
axes = Axes(
|
||||
x_range=[-4, 4, 1],
|
||||
y_range=[-4, 4, 1],
|
||||
x_length=7,
|
||||
y_length=7,
|
||||
)
|
||||
|
||||
# Circle x^2 + y^2 = 4 as parametric
|
||||
circle = axes.plot_parametric_curve(
|
||||
lambda t: np.array([2 * np.cos(t), 2 * np.sin(t), 0]),
|
||||
t_range=[0, 2 * PI],
|
||||
color=BLUE
|
||||
)
|
||||
|
||||
# Equation label
|
||||
equation = MathTex(r"x^2 + y^2 = 4", color=BLUE).to_corner(UR)
|
||||
|
||||
self.play(Create(axes))
|
||||
self.play(Create(circle), Write(equation))
|
||||
self.wait()
|
||||
|
||||
|
||||
class CoordinateLabeling(Scene):
|
||||
"""Advanced coordinate labeling techniques."""
|
||||
|
||||
def construct(self):
|
||||
axes = Axes(
|
||||
x_range=[-1, 5, 1],
|
||||
y_range=[-1, 5, 1],
|
||||
x_length=7,
|
||||
y_length=7,
|
||||
axis_config={"include_numbers": True},
|
||||
)
|
||||
|
||||
# Function
|
||||
func = axes.plot(lambda x: np.sqrt(x), color=BLUE, x_range=[0, 4])
|
||||
|
||||
# Highlight a specific point
|
||||
x_val = 2
|
||||
y_val = np.sqrt(2)
|
||||
|
||||
point = Dot(axes.c2p(x_val, y_val), color=RED)
|
||||
|
||||
# Dashed lines to axes
|
||||
h_line = DashedLine(
|
||||
axes.c2p(0, y_val),
|
||||
axes.c2p(x_val, y_val),
|
||||
color=GREY
|
||||
)
|
||||
v_line = DashedLine(
|
||||
axes.c2p(x_val, 0),
|
||||
axes.c2p(x_val, y_val),
|
||||
color=GREY
|
||||
)
|
||||
|
||||
# Labels
|
||||
x_label = MathTex("2").next_to(axes.c2p(x_val, 0), DOWN)
|
||||
y_label = MathTex(r"\sqrt{2}").next_to(axes.c2p(0, y_val), LEFT)
|
||||
|
||||
self.play(Create(axes))
|
||||
self.play(Create(func))
|
||||
self.play(Create(v_line), Create(h_line))
|
||||
self.play(FadeIn(point), Write(x_label), Write(y_label))
|
||||
self.wait()
|
||||
|
||||
|
||||
class PolarPlot(Scene):
|
||||
"""Plotting in polar coordinates."""
|
||||
|
||||
def construct(self):
|
||||
# Polar axes
|
||||
polar_plane = PolarPlane(
|
||||
radius_max=3,
|
||||
size=6,
|
||||
)
|
||||
|
||||
# Polar curve: r = 1 + sin(theta) (cardioid)
|
||||
cardioid = polar_plane.plot_polar_graph(
|
||||
lambda theta: 1 + np.sin(theta),
|
||||
theta_range=[0, 2 * PI],
|
||||
color=BLUE
|
||||
)
|
||||
|
||||
# Rose curve: r = 2*cos(3*theta)
|
||||
rose = polar_plane.plot_polar_graph(
|
||||
lambda theta: 2 * np.cos(3 * theta),
|
||||
theta_range=[0, PI],
|
||||
color=RED
|
||||
)
|
||||
|
||||
self.play(Create(polar_plane))
|
||||
self.play(Create(cardioid))
|
||||
self.wait()
|
||||
self.play(Transform(cardioid, rose))
|
||||
self.wait()
|
||||
@@ -0,0 +1,172 @@
|
||||
"""
|
||||
Lorenz Attractor - Converted from 3b1b ManimGL to ManimCE
|
||||
|
||||
Original: videos/_2024/manim_demo/lorenz.py
|
||||
This demonstrates a chaotic system visualization with 3D curves and tracing dots.
|
||||
|
||||
Run with: manim -pql lorenz_attractor.py LorenzAttractor
|
||||
"""
|
||||
|
||||
from manim import *
|
||||
from scipy.integrate import solve_ivp
|
||||
import numpy as np
|
||||
|
||||
|
||||
def lorenz_system(t, state, sigma=10, rho=28, beta=8 / 3):
|
||||
"""The Lorenz system of differential equations."""
|
||||
x, y, z = state
|
||||
dxdt = sigma * (y - x)
|
||||
dydt = x * (rho - z) - y
|
||||
dzdt = x * y - beta * z
|
||||
return [dxdt, dydt, dzdt]
|
||||
|
||||
|
||||
def ode_solution_points(function, state0, time, dt=0.01):
|
||||
"""Solve ODE and return solution points."""
|
||||
solution = solve_ivp(
|
||||
function,
|
||||
t_span=(0, time),
|
||||
y0=state0,
|
||||
t_eval=np.arange(0, time, dt)
|
||||
)
|
||||
return solution.y.T
|
||||
|
||||
|
||||
class LorenzAttractor(ThreeDScene):
|
||||
"""
|
||||
Visualization of the Lorenz attractor - a classic chaotic system.
|
||||
|
||||
Shows multiple trajectories starting from nearly identical initial conditions
|
||||
that diverge chaotically over time.
|
||||
"""
|
||||
|
||||
def construct(self):
|
||||
# Set up 3D axes
|
||||
axes = ThreeDAxes(
|
||||
x_range=(-50, 50, 10),
|
||||
y_range=(-50, 50, 10),
|
||||
z_range=(0, 50, 10),
|
||||
x_length=12,
|
||||
y_length=12,
|
||||
z_length=6,
|
||||
)
|
||||
axes.center()
|
||||
|
||||
# Set camera orientation
|
||||
self.set_camera_orientation(phi=76 * DEGREES, theta=43 * DEGREES)
|
||||
|
||||
self.add(axes)
|
||||
|
||||
# Add the equations (fixed to screen)
|
||||
equations = MathTex(
|
||||
r"\frac{dx}{dt} &= \sigma(y-x) \\",
|
||||
r"\frac{dy}{dt} &= x(\rho-z)-y \\",
|
||||
r"\frac{dz}{dt} &= xy-\beta z",
|
||||
font_size=30
|
||||
)
|
||||
equations.to_corner(UL)
|
||||
self.add_fixed_in_frame_mobjects(equations)
|
||||
self.play(Write(equations))
|
||||
|
||||
# Compute a set of solutions with slightly different initial conditions
|
||||
epsilon = 1e-5
|
||||
evolution_time = 20 # Reduced for faster rendering
|
||||
n_points = 5 # Reduced for performance
|
||||
|
||||
states = [
|
||||
[10, 10, 10 + n * epsilon]
|
||||
for n in range(n_points)
|
||||
]
|
||||
colors = color_gradient([BLUE_E, BLUE_A], len(states))
|
||||
|
||||
# Create curves from ODE solutions
|
||||
curves = VGroup()
|
||||
for state, color in zip(states, colors):
|
||||
points = ode_solution_points(lorenz_system, state, evolution_time)
|
||||
# Scale points to fit axes
|
||||
scaled_points = [axes.c2p(p[0], p[1], p[2]) for p in points]
|
||||
curve = VMobject()
|
||||
curve.set_points_smoothly(scaled_points)
|
||||
curve.set_stroke(color, width=2, opacity=0.8)
|
||||
curves.add(curve)
|
||||
|
||||
# Create dots that will trace the curves
|
||||
dots = VGroup(*[
|
||||
Dot3D(color=color, radius=0.15)
|
||||
for color in colors
|
||||
])
|
||||
|
||||
# Position dots at start of curves
|
||||
for dot, curve in zip(dots, curves):
|
||||
dot.move_to(curve.get_start())
|
||||
|
||||
self.add(dots)
|
||||
|
||||
# Start ambient camera rotation
|
||||
self.begin_ambient_camera_rotation(rate=0.1)
|
||||
|
||||
# Animate curves being drawn with dots following
|
||||
self.play(
|
||||
*[Create(curve, rate_func=linear) for curve in curves],
|
||||
*[MoveAlongPath(dot, curve, rate_func=linear) for dot, curve in zip(dots, curves)],
|
||||
run_time=evolution_time,
|
||||
)
|
||||
|
||||
self.wait(2)
|
||||
|
||||
|
||||
class LorenzAttractorSimple(ThreeDScene):
|
||||
"""
|
||||
Simplified version with just one trajectory and traced path.
|
||||
Better for understanding the basic pattern.
|
||||
"""
|
||||
|
||||
def construct(self):
|
||||
# Set up axes
|
||||
axes = ThreeDAxes(
|
||||
x_range=(-50, 50, 10),
|
||||
y_range=(-50, 50, 10),
|
||||
z_range=(0, 50, 10),
|
||||
x_length=10,
|
||||
y_length=10,
|
||||
z_length=5,
|
||||
)
|
||||
|
||||
self.set_camera_orientation(phi=70 * DEGREES, theta=45 * DEGREES)
|
||||
self.add(axes)
|
||||
|
||||
# Compute single trajectory
|
||||
evolution_time = 15
|
||||
points = ode_solution_points(lorenz_system, [10, 10, 10], evolution_time)
|
||||
scaled_points = [axes.c2p(p[0], p[1], p[2]) for p in points]
|
||||
|
||||
# Create curve
|
||||
curve = VMobject()
|
||||
curve.set_points_smoothly(scaled_points)
|
||||
curve.set_stroke(BLUE, width=2)
|
||||
|
||||
# Create moving dot with traced path
|
||||
dot = Dot3D(color=RED, radius=0.2)
|
||||
dot.move_to(curve.get_start())
|
||||
|
||||
# Traced path follows the dot
|
||||
traced_path = TracedPath(
|
||||
dot.get_center,
|
||||
stroke_color=YELLOW,
|
||||
stroke_width=3,
|
||||
)
|
||||
|
||||
self.add(traced_path, dot)
|
||||
|
||||
# Title
|
||||
title = Text("Lorenz Attractor", font_size=36)
|
||||
title.to_corner(UL)
|
||||
self.add_fixed_in_frame_mobjects(title)
|
||||
|
||||
# Animate
|
||||
self.begin_ambient_camera_rotation(rate=0.15)
|
||||
self.play(
|
||||
MoveAlongPath(dot, curve, rate_func=linear),
|
||||
run_time=evolution_time,
|
||||
)
|
||||
self.wait(2)
|
||||
@@ -0,0 +1,315 @@
|
||||
"""
|
||||
Mathematical Visualization Patterns for Manim Community
|
||||
|
||||
Demonstrates LaTeX rendering, equation animations, and color-coded math.
|
||||
Adapted from 3b1b patterns for ManimCE compatibility.
|
||||
|
||||
Run with: manim -pql math_visualization.py SceneName
|
||||
"""
|
||||
|
||||
from manim import *
|
||||
|
||||
|
||||
class ColorCodedEquation(Scene):
|
||||
"""Demonstrates color-coding for syntax highlighting in equations."""
|
||||
|
||||
def construct(self):
|
||||
# Method 1: Use set_color_by_tex after creation (safer approach)
|
||||
equation = MathTex(
|
||||
r"\vec{v}_1", r"=", r"\begin{bmatrix} 1 \\ \lambda_1 \end{bmatrix}"
|
||||
)
|
||||
equation.scale(1.5)
|
||||
|
||||
# Color specific parts
|
||||
equation[0].set_color(TEAL) # \vec{v}_1
|
||||
|
||||
self.play(Write(equation))
|
||||
self.wait()
|
||||
|
||||
# Second equation with multiple colored parts
|
||||
equation2 = MathTex(r"A", r"\vec{v}_1", r"=", r"\lambda_1", r"\vec{v}_1")
|
||||
equation2.scale(1.5)
|
||||
equation2[0].set_color(RED) # A
|
||||
equation2[1].set_color(TEAL) # first \vec{v}_1
|
||||
equation2[3].set_color(YELLOW) # \lambda_1
|
||||
equation2[4].set_color(TEAL) # second \vec{v}_1
|
||||
|
||||
self.play(TransformMatchingTex(equation, equation2))
|
||||
self.wait()
|
||||
|
||||
|
||||
class EquationDerivation(Scene):
|
||||
"""Shows step-by-step equation derivation with highlighting."""
|
||||
|
||||
def construct(self):
|
||||
# Starting equation
|
||||
eq1 = MathTex(r"x^2 + 5x + 6 = 0")
|
||||
eq1.to_edge(UP)
|
||||
|
||||
self.play(Write(eq1))
|
||||
self.wait()
|
||||
|
||||
# Factor step
|
||||
eq2 = MathTex(r"(x + 2)(x + 3) = 0")
|
||||
eq2.next_to(eq1, DOWN, buff=0.8)
|
||||
|
||||
self.play(
|
||||
TransformFromCopy(eq1, eq2),
|
||||
run_time=1.5
|
||||
)
|
||||
self.wait()
|
||||
|
||||
# Solutions
|
||||
eq3 = MathTex(r"x = -2", color=BLUE)
|
||||
eq4 = MathTex(r"x = -3", color=GREEN)
|
||||
solutions = VGroup(eq3, eq4).arrange(RIGHT, buff=1)
|
||||
solutions.next_to(eq2, DOWN, buff=0.8)
|
||||
|
||||
self.play(
|
||||
LaggedStart(
|
||||
Write(eq3),
|
||||
Write(eq4),
|
||||
lag_ratio=0.3
|
||||
)
|
||||
)
|
||||
|
||||
# Highlight solutions
|
||||
boxes = VGroup(
|
||||
SurroundingRectangle(eq3, color=BLUE),
|
||||
SurroundingRectangle(eq4, color=GREEN),
|
||||
)
|
||||
self.play(Create(boxes))
|
||||
self.wait()
|
||||
|
||||
|
||||
class MatrixTransformation(Scene):
|
||||
"""Demonstrates matrix notation and transformations."""
|
||||
|
||||
def construct(self):
|
||||
# Matrix definition
|
||||
matrix = MathTex(
|
||||
r"A = \begin{bmatrix} 2 & 1 \\ 1 & 3 \end{bmatrix}"
|
||||
).scale(1.2)
|
||||
|
||||
self.play(Write(matrix))
|
||||
self.wait()
|
||||
|
||||
# Move to side
|
||||
self.play(matrix.animate.to_edge(LEFT))
|
||||
|
||||
# Show transformation
|
||||
vector = MathTex(
|
||||
r"\vec{x} = \begin{bmatrix} 1 \\ 1 \end{bmatrix}",
|
||||
color=YELLOW
|
||||
)
|
||||
vector.next_to(matrix, RIGHT, buff=1)
|
||||
|
||||
self.play(Write(vector))
|
||||
self.wait()
|
||||
|
||||
# Result
|
||||
result = MathTex(
|
||||
r"A\vec{x} = \begin{bmatrix} 3 \\ 4 \end{bmatrix}",
|
||||
tex_to_color_map={r"\vec{x}": YELLOW}
|
||||
)
|
||||
result.next_to(vector, RIGHT, buff=1)
|
||||
|
||||
arrow = Arrow(vector.get_right(), result.get_left(), buff=0.2)
|
||||
|
||||
self.play(GrowArrow(arrow), Write(result))
|
||||
self.wait()
|
||||
|
||||
|
||||
class IntegralVisualization(Scene):
|
||||
"""Shows integral notation with visual meaning."""
|
||||
|
||||
def construct(self):
|
||||
# Integral expression
|
||||
integral = MathTex(
|
||||
r"\int_0^1 x^2 \, dx = \frac{1}{3}",
|
||||
font_size=64
|
||||
)
|
||||
integral.to_edge(UP)
|
||||
|
||||
self.play(Write(integral))
|
||||
self.wait()
|
||||
|
||||
# Create axes
|
||||
axes = Axes(
|
||||
x_range=[0, 1.2, 0.5],
|
||||
y_range=[0, 1.2, 0.5],
|
||||
x_length=5,
|
||||
y_length=3,
|
||||
axis_config={"include_tip": True},
|
||||
)
|
||||
axes.shift(DOWN)
|
||||
|
||||
# Create graph
|
||||
graph = axes.plot(lambda x: x**2, x_range=[0, 1], color=BLUE)
|
||||
|
||||
# Create area under curve
|
||||
area = axes.get_area(graph, x_range=[0, 1], color=BLUE, opacity=0.3)
|
||||
|
||||
self.play(Create(axes))
|
||||
self.play(Create(graph))
|
||||
self.play(FadeIn(area))
|
||||
self.wait()
|
||||
|
||||
|
||||
class SummationNotation(Scene):
|
||||
"""Demonstrates summation and series notation."""
|
||||
|
||||
def construct(self):
|
||||
# Summation formula
|
||||
formula = MathTex(
|
||||
r"\sum_{n=1}^{\infty} \frac{1}{n^2} = \frac{\pi^2}{6}",
|
||||
font_size=64
|
||||
)
|
||||
|
||||
self.play(Write(formula))
|
||||
self.wait()
|
||||
|
||||
# Show first few terms
|
||||
terms = MathTex(
|
||||
r"= 1 + \frac{1}{4} + \frac{1}{9} + \frac{1}{16} + \cdots",
|
||||
font_size=48
|
||||
)
|
||||
terms.next_to(formula, DOWN, buff=0.8)
|
||||
|
||||
self.play(Write(terms))
|
||||
self.wait()
|
||||
|
||||
# Create surrounding box around result
|
||||
box = SurroundingRectangle(formula, color=YELLOW, buff=0.2)
|
||||
self.play(Create(box))
|
||||
self.wait()
|
||||
|
||||
|
||||
class FunctionNotation(Scene):
|
||||
"""Shows function definition and evaluation."""
|
||||
|
||||
def construct(self):
|
||||
# Function definition
|
||||
f_def = MathTex(r"f(x) = x^2 + 2x + 1", font_size=56)
|
||||
f_def.to_edge(UP)
|
||||
|
||||
self.play(Write(f_def))
|
||||
self.wait()
|
||||
|
||||
# Evaluation at x=3
|
||||
eval_step1 = MathTex(r"f(3) = 3^2 + 2(3) + 1", font_size=48)
|
||||
eval_step2 = MathTex(r"f(3) = 9 + 6 + 1", font_size=48)
|
||||
eval_step3 = MathTex(r"f(3) = 16", font_size=48, color=GREEN)
|
||||
|
||||
steps = VGroup(eval_step1, eval_step2, eval_step3)
|
||||
steps.arrange(DOWN, buff=0.5)
|
||||
steps.next_to(f_def, DOWN, buff=1)
|
||||
|
||||
for step in steps:
|
||||
self.play(Write(step))
|
||||
self.wait(0.5)
|
||||
|
||||
# Box the answer
|
||||
box = SurroundingRectangle(eval_step3, color=GREEN)
|
||||
self.play(Create(box))
|
||||
self.wait()
|
||||
|
||||
|
||||
class LimitNotation(Scene):
|
||||
"""Demonstrates limit notation and evaluation."""
|
||||
|
||||
def construct(self):
|
||||
# Limit expression
|
||||
limit = MathTex(
|
||||
r"\lim_{x \to 0} \frac{\sin x}{x} = 1",
|
||||
font_size=64
|
||||
)
|
||||
|
||||
self.play(Write(limit))
|
||||
self.wait()
|
||||
|
||||
# Show approaching behavior
|
||||
approaching = MathTex(
|
||||
r"x \to 0: \quad",
|
||||
r"\frac{\sin(0.1)}{0.1} \approx 0.998",
|
||||
font_size=40
|
||||
)
|
||||
approaching.next_to(limit, DOWN, buff=1)
|
||||
|
||||
self.play(Write(approaching))
|
||||
self.wait()
|
||||
|
||||
|
||||
class DerivativeChainRule(Scene):
|
||||
"""Shows the chain rule for derivatives."""
|
||||
|
||||
def construct(self):
|
||||
title = Text("Chain Rule", font_size=48, color=BLUE)
|
||||
title.to_edge(UP)
|
||||
|
||||
# Chain rule formula
|
||||
rule = MathTex(
|
||||
r"\frac{d}{dx}[f(g(x))] = f'(g(x)) \cdot g'(x)",
|
||||
font_size=48
|
||||
)
|
||||
|
||||
# Example
|
||||
example_title = Text("Example:", font_size=36)
|
||||
example = MathTex(
|
||||
r"\frac{d}{dx}[\sin(x^2)] = \cos(x^2) \cdot 2x",
|
||||
tex_to_color_map={
|
||||
r"\sin": BLUE,
|
||||
r"\cos": BLUE,
|
||||
r"x^2": YELLOW,
|
||||
r"2x": YELLOW,
|
||||
},
|
||||
font_size=44
|
||||
)
|
||||
|
||||
content = VGroup(rule, example_title, example)
|
||||
content.arrange(DOWN, buff=0.8)
|
||||
|
||||
self.play(Write(title))
|
||||
self.play(Write(rule))
|
||||
self.wait()
|
||||
self.play(Write(example_title))
|
||||
self.play(Write(example))
|
||||
self.wait()
|
||||
|
||||
|
||||
class TexHighlighting(Scene):
|
||||
"""Advanced tex highlighting techniques."""
|
||||
|
||||
def construct(self):
|
||||
# Create equation with substrings to highlight
|
||||
equation = MathTex(
|
||||
r"E", r"=", r"m", r"c^2",
|
||||
font_size=96
|
||||
)
|
||||
|
||||
self.play(Write(equation))
|
||||
self.wait()
|
||||
|
||||
# Highlight individual parts
|
||||
self.play(equation[0].animate.set_color(YELLOW)) # E
|
||||
self.wait(0.3)
|
||||
self.play(equation[2].animate.set_color(BLUE)) # m
|
||||
self.wait(0.3)
|
||||
self.play(equation[3].animate.set_color(RED)) # c^2
|
||||
self.wait()
|
||||
|
||||
# Add labels
|
||||
e_label = Text("Energy", font_size=24, color=YELLOW)
|
||||
m_label = Text("Mass", font_size=24, color=BLUE)
|
||||
c_label = Text("Speed of Light", font_size=24, color=RED)
|
||||
|
||||
e_label.next_to(equation[0], UP)
|
||||
m_label.next_to(equation[2], DOWN)
|
||||
c_label.next_to(equation[3], UP)
|
||||
|
||||
self.play(
|
||||
FadeIn(e_label, shift=DOWN * 0.3),
|
||||
FadeIn(m_label, shift=UP * 0.3),
|
||||
FadeIn(c_label, shift=DOWN * 0.3),
|
||||
)
|
||||
self.wait()
|
||||
@@ -0,0 +1,369 @@
|
||||
"""
|
||||
Updater and ValueTracker Patterns for Manim Community
|
||||
|
||||
Demonstrates dynamic animations using updaters and ValueTracker.
|
||||
Adapted from 3b1b's animation patterns for ManimCE.
|
||||
|
||||
Run with: manim -pql updater_patterns.py SceneName
|
||||
"""
|
||||
|
||||
from manim import *
|
||||
import numpy as np
|
||||
|
||||
|
||||
class BasicUpdater(Scene):
|
||||
"""Simple updater that makes an object follow another."""
|
||||
|
||||
def construct(self):
|
||||
# Leader dot
|
||||
leader = Dot(color=RED, radius=0.2)
|
||||
leader_label = Text("Leader", font_size=24).next_to(leader, UP)
|
||||
|
||||
# Follower that always stays next to leader
|
||||
follower = Dot(color=BLUE, radius=0.15)
|
||||
follower.add_updater(lambda m: m.next_to(leader, RIGHT, buff=0.5))
|
||||
|
||||
follower_label = Text("Follower", font_size=24, color=BLUE)
|
||||
follower_label.add_updater(lambda m: m.next_to(follower, DOWN))
|
||||
|
||||
self.add(leader, leader_label, follower, follower_label)
|
||||
|
||||
# Move the leader - follower automatically follows
|
||||
self.play(leader.animate.shift(RIGHT * 3), run_time=2)
|
||||
self.play(leader.animate.shift(UP * 2), run_time=2)
|
||||
self.play(leader.animate.shift(LEFT * 4 + DOWN), run_time=2)
|
||||
self.wait()
|
||||
|
||||
|
||||
class ValueTrackerBasics(Scene):
|
||||
"""Demonstrates ValueTracker for animating numeric values."""
|
||||
|
||||
def construct(self):
|
||||
# Create a ValueTracker
|
||||
tracker = ValueTracker(0)
|
||||
|
||||
# DecimalNumber that displays the tracker value
|
||||
number = DecimalNumber(
|
||||
0,
|
||||
num_decimal_places=2,
|
||||
font_size=72,
|
||||
include_sign=True
|
||||
)
|
||||
number.add_updater(lambda m: m.set_value(tracker.get_value()))
|
||||
|
||||
# Label
|
||||
label = Text("Value: ", font_size=48)
|
||||
label.next_to(number, LEFT)
|
||||
|
||||
self.add(label, number)
|
||||
|
||||
# Animate the tracker
|
||||
self.play(tracker.animate.set_value(10), run_time=2)
|
||||
self.wait(0.5)
|
||||
self.play(tracker.animate.set_value(-5), run_time=2)
|
||||
self.wait(0.5)
|
||||
self.play(tracker.animate.set_value(0), run_time=1)
|
||||
self.wait()
|
||||
|
||||
|
||||
class CircleRadiusTracker(Scene):
|
||||
"""Circle that grows/shrinks with a ValueTracker."""
|
||||
|
||||
def construct(self):
|
||||
tracker = ValueTracker(1)
|
||||
|
||||
# Circle with radius controlled by tracker
|
||||
circle = always_redraw(
|
||||
lambda: Circle(
|
||||
radius=tracker.get_value(),
|
||||
color=BLUE,
|
||||
fill_opacity=0.3
|
||||
)
|
||||
)
|
||||
|
||||
# Radius label
|
||||
radius_text = always_redraw(
|
||||
lambda: MathTex(
|
||||
f"r = {tracker.get_value():.2f}"
|
||||
).to_edge(UP)
|
||||
)
|
||||
|
||||
self.add(circle, radius_text)
|
||||
|
||||
# Animate radius changes
|
||||
self.play(tracker.animate.set_value(2.5), run_time=2)
|
||||
self.play(tracker.animate.set_value(0.5), run_time=2)
|
||||
self.play(tracker.animate.set_value(1.5), run_time=1)
|
||||
self.wait()
|
||||
|
||||
|
||||
class RotatingUpdater(Scene):
|
||||
"""Object that rotates continuously using dt (delta time)."""
|
||||
|
||||
def construct(self):
|
||||
# Create rotating group
|
||||
square = Square(side_length=2, color=BLUE, fill_opacity=0.5)
|
||||
dot = Dot(color=RED).move_to(square.get_corner(UR))
|
||||
|
||||
group = VGroup(square, dot)
|
||||
|
||||
# Add rotation updater with dt for smooth rotation
|
||||
group.add_updater(lambda m, dt: m.rotate(dt * PI / 2))
|
||||
|
||||
self.add(group)
|
||||
self.wait(4) # Watch it rotate
|
||||
|
||||
# Remove updater
|
||||
group.clear_updaters()
|
||||
self.wait()
|
||||
|
||||
|
||||
class TracedPathExample(Scene):
|
||||
"""Demonstrates TracedPath for drawing motion trails."""
|
||||
|
||||
def construct(self):
|
||||
# Moving dot
|
||||
dot = Dot(color=RED, radius=0.15)
|
||||
dot.move_to(LEFT * 3)
|
||||
|
||||
# Traced path follows the dot
|
||||
traced_path = TracedPath(
|
||||
dot.get_center,
|
||||
stroke_color=YELLOW,
|
||||
stroke_width=3
|
||||
)
|
||||
|
||||
self.add(traced_path, dot)
|
||||
|
||||
# Move dot in a pattern
|
||||
self.play(
|
||||
dot.animate.shift(RIGHT * 3 + UP * 2),
|
||||
run_time=1.5
|
||||
)
|
||||
self.play(
|
||||
dot.animate.shift(RIGHT * 2 + DOWN * 3),
|
||||
run_time=1.5
|
||||
)
|
||||
self.play(
|
||||
dot.animate.shift(LEFT * 2 + UP * 1),
|
||||
run_time=1.5
|
||||
)
|
||||
self.wait()
|
||||
|
||||
|
||||
class SineWaveTracker(Scene):
|
||||
"""Animated sine wave using ValueTracker."""
|
||||
|
||||
def construct(self):
|
||||
# Phase tracker
|
||||
phase = ValueTracker(0)
|
||||
|
||||
# Axes
|
||||
axes = Axes(
|
||||
x_range=[0, 2 * PI, PI / 2],
|
||||
y_range=[-1.5, 1.5, 0.5],
|
||||
x_length=10,
|
||||
y_length=4,
|
||||
)
|
||||
|
||||
# Sine wave that updates with phase
|
||||
sine_wave = always_redraw(
|
||||
lambda: axes.plot(
|
||||
lambda x: np.sin(x + phase.get_value()),
|
||||
color=BLUE,
|
||||
x_range=[0, 2 * PI]
|
||||
)
|
||||
)
|
||||
|
||||
# Dot that follows the wave
|
||||
dot = always_redraw(
|
||||
lambda: Dot(color=RED).move_to(
|
||||
axes.c2p(PI, np.sin(PI + phase.get_value()))
|
||||
)
|
||||
)
|
||||
|
||||
self.add(axes, sine_wave, dot)
|
||||
|
||||
# Animate phase change (wave shifts)
|
||||
self.play(
|
||||
phase.animate.set_value(2 * PI),
|
||||
run_time=4,
|
||||
rate_func=linear
|
||||
)
|
||||
|
||||
|
||||
class ArrowUpdater(Scene):
|
||||
"""Arrow that always points from one object to another."""
|
||||
|
||||
def construct(self):
|
||||
# Two dots
|
||||
dot1 = Dot(color=BLUE, radius=0.2).shift(LEFT * 2)
|
||||
dot2 = Dot(color=RED, radius=0.2).shift(RIGHT * 2)
|
||||
|
||||
# Arrow that always connects them
|
||||
arrow = always_redraw(
|
||||
lambda: Arrow(
|
||||
dot1.get_center(),
|
||||
dot2.get_center(),
|
||||
buff=0.3,
|
||||
color=YELLOW
|
||||
)
|
||||
)
|
||||
|
||||
# Distance label
|
||||
distance = always_redraw(
|
||||
lambda: DecimalNumber(
|
||||
np.linalg.norm(dot2.get_center() - dot1.get_center()),
|
||||
num_decimal_places=2,
|
||||
font_size=36
|
||||
).next_to(arrow, UP)
|
||||
)
|
||||
|
||||
self.add(dot1, dot2, arrow, distance)
|
||||
|
||||
# Move dots around
|
||||
self.play(dot1.animate.shift(UP * 2), run_time=1.5)
|
||||
self.play(dot2.animate.shift(DOWN + LEFT * 2), run_time=1.5)
|
||||
self.play(
|
||||
dot1.animate.shift(RIGHT * 3),
|
||||
dot2.animate.shift(UP * 2),
|
||||
run_time=2
|
||||
)
|
||||
self.wait()
|
||||
|
||||
|
||||
class ParametricCurveTracer(Scene):
|
||||
"""Traces a parametric curve using ValueTracker."""
|
||||
|
||||
def construct(self):
|
||||
# Parameter t
|
||||
t_tracker = ValueTracker(0)
|
||||
|
||||
# Parametric curve (Lissajous)
|
||||
def parametric_func(t):
|
||||
return np.array([
|
||||
2 * np.sin(2 * t),
|
||||
2 * np.sin(3 * t),
|
||||
0
|
||||
])
|
||||
|
||||
# Dot at current position
|
||||
dot = always_redraw(
|
||||
lambda: Dot(color=RED, radius=0.15).move_to(
|
||||
parametric_func(t_tracker.get_value())
|
||||
)
|
||||
)
|
||||
|
||||
# Traced path
|
||||
path = TracedPath(
|
||||
dot.get_center,
|
||||
stroke_color=BLUE,
|
||||
stroke_width=2
|
||||
)
|
||||
|
||||
self.add(path, dot)
|
||||
|
||||
# Trace the curve
|
||||
self.play(
|
||||
t_tracker.animate.set_value(2 * PI),
|
||||
run_time=6,
|
||||
rate_func=linear
|
||||
)
|
||||
self.wait()
|
||||
|
||||
|
||||
class MultipleTrackers(Scene):
|
||||
"""Using multiple ValueTrackers together."""
|
||||
|
||||
def construct(self):
|
||||
# Separate trackers for x and y
|
||||
x_tracker = ValueTracker(0)
|
||||
y_tracker = ValueTracker(0)
|
||||
|
||||
# Dot controlled by both trackers
|
||||
dot = always_redraw(
|
||||
lambda: Dot(color=RED, radius=0.2).move_to(
|
||||
RIGHT * x_tracker.get_value() + UP * y_tracker.get_value()
|
||||
)
|
||||
)
|
||||
|
||||
# Coordinate display
|
||||
coords = always_redraw(
|
||||
lambda: MathTex(
|
||||
f"({x_tracker.get_value():.1f}, {y_tracker.get_value():.1f})"
|
||||
).to_corner(UL)
|
||||
)
|
||||
|
||||
self.add(dot, coords)
|
||||
|
||||
# Animate both trackers
|
||||
self.play(x_tracker.animate.set_value(3), run_time=1.5)
|
||||
self.play(y_tracker.animate.set_value(2), run_time=1.5)
|
||||
self.play(
|
||||
x_tracker.animate.set_value(-2),
|
||||
y_tracker.animate.set_value(-1),
|
||||
run_time=2
|
||||
)
|
||||
self.wait()
|
||||
|
||||
|
||||
class SpringMassSimulation(Scene):
|
||||
"""Simple physics simulation with updaters."""
|
||||
|
||||
def construct(self):
|
||||
# Physics parameters
|
||||
k = 10 # Spring constant
|
||||
mass = 1
|
||||
damping = 0.5
|
||||
|
||||
# State trackers
|
||||
position = ValueTracker(2) # Initial displacement
|
||||
velocity = ValueTracker(0)
|
||||
|
||||
# Ground line
|
||||
ground = Line(LEFT * 4, RIGHT * 4, color=WHITE).shift(DOWN * 2)
|
||||
|
||||
# Mass (square)
|
||||
mass_obj = always_redraw(
|
||||
lambda: Square(
|
||||
side_length=0.8,
|
||||
color=BLUE,
|
||||
fill_opacity=0.8
|
||||
).move_to(UP * position.get_value())
|
||||
)
|
||||
|
||||
# Spring (simplified as line)
|
||||
spring = always_redraw(
|
||||
lambda: Line(
|
||||
ground.get_center() + UP * 0.1,
|
||||
mass_obj.get_bottom(),
|
||||
color=GREY
|
||||
)
|
||||
)
|
||||
|
||||
self.add(ground, spring, mass_obj)
|
||||
|
||||
# Physics update function
|
||||
def physics_update(mob, dt):
|
||||
x = position.get_value()
|
||||
v = velocity.get_value()
|
||||
|
||||
# F = -kx - damping*v
|
||||
acceleration = (-k * x - damping * v) / mass
|
||||
new_v = v + acceleration * dt
|
||||
new_x = x + new_v * dt
|
||||
|
||||
velocity.set_value(new_v)
|
||||
position.set_value(new_x)
|
||||
|
||||
# Add physics updater to a dummy mobject
|
||||
physics_driver = Mobject()
|
||||
physics_driver.add_updater(physics_update)
|
||||
self.add(physics_driver)
|
||||
|
||||
# Let it run
|
||||
self.wait(5)
|
||||
|
||||
# Clean up
|
||||
physics_driver.clear_updaters()
|
||||
self.wait()
|
||||
@@ -0,0 +1,254 @@
|
||||
---
|
||||
name: 3d
|
||||
description: ThreeDScene, 3D objects, surfaces, and 3D camera
|
||||
metadata:
|
||||
tags: 3d, threedscene, surface, sphere, cube, camera
|
||||
---
|
||||
|
||||
# 3D Graphics in Manim
|
||||
|
||||
Create 3D visualizations with ThreeDScene.
|
||||
|
||||
## ThreeDScene Basics
|
||||
|
||||
```python
|
||||
from manim import *
|
||||
|
||||
class Basic3D(ThreeDScene):
|
||||
def construct(self):
|
||||
# Set camera angle
|
||||
self.set_camera_orientation(phi=75 * DEGREES, theta=-45 * DEGREES)
|
||||
|
||||
# Add 3D axes
|
||||
axes = ThreeDAxes()
|
||||
self.add(axes)
|
||||
```
|
||||
|
||||
## Camera Orientation
|
||||
|
||||
```python
|
||||
class CameraOrientation(ThreeDScene):
|
||||
def construct(self):
|
||||
axes = ThreeDAxes()
|
||||
|
||||
# phi: angle from z-axis (0 = top view, 90 = side view)
|
||||
# theta: rotation around z-axis
|
||||
# gamma: roll angle
|
||||
|
||||
self.set_camera_orientation(
|
||||
phi=75 * DEGREES,
|
||||
theta=-45 * DEGREES,
|
||||
gamma=0
|
||||
)
|
||||
|
||||
self.add(axes)
|
||||
```
|
||||
|
||||
### Animated Camera Movement
|
||||
|
||||
```python
|
||||
class AnimatedCamera(ThreeDScene):
|
||||
def construct(self):
|
||||
axes = ThreeDAxes()
|
||||
self.add(axes)
|
||||
|
||||
self.set_camera_orientation(phi=75*DEGREES, theta=0)
|
||||
|
||||
# Animate camera movement
|
||||
self.move_camera(phi=45*DEGREES, theta=90*DEGREES, run_time=3)
|
||||
```
|
||||
|
||||
### Continuous Camera Rotation
|
||||
|
||||
```python
|
||||
class RotatingCamera(ThreeDScene):
|
||||
def construct(self):
|
||||
axes = ThreeDAxes()
|
||||
self.add(axes)
|
||||
|
||||
self.set_camera_orientation(phi=75*DEGREES, theta=0)
|
||||
|
||||
# Start ambient rotation
|
||||
self.begin_ambient_camera_rotation(rate=0.2)
|
||||
self.wait(5)
|
||||
self.stop_ambient_camera_rotation()
|
||||
```
|
||||
|
||||
## 3D Primitives
|
||||
|
||||
### Sphere
|
||||
|
||||
```python
|
||||
class SphereExample(ThreeDScene):
|
||||
def construct(self):
|
||||
sphere = Sphere(radius=1, resolution=(20, 20))
|
||||
sphere.set_color(BLUE)
|
||||
|
||||
self.set_camera_orientation(phi=75*DEGREES, theta=-45*DEGREES)
|
||||
self.add(sphere)
|
||||
```
|
||||
|
||||
### Cube / Prism
|
||||
|
||||
```python
|
||||
class CubeExample(ThreeDScene):
|
||||
def construct(self):
|
||||
cube = Cube(side_length=2, fill_opacity=0.8)
|
||||
cube.set_color(RED)
|
||||
|
||||
# Rectangular prism
|
||||
prism = Prism(dimensions=[3, 1, 2])
|
||||
|
||||
self.set_camera_orientation(phi=75*DEGREES, theta=-45*DEGREES)
|
||||
self.add(cube)
|
||||
```
|
||||
|
||||
### Cylinder / Cone
|
||||
|
||||
```python
|
||||
class CylinderCone(ThreeDScene):
|
||||
def construct(self):
|
||||
cylinder = Cylinder(radius=1, height=2, fill_opacity=0.8)
|
||||
cone = Cone(base_radius=1, height=2, fill_opacity=0.8)
|
||||
|
||||
cylinder.shift(LEFT * 2)
|
||||
cone.shift(RIGHT * 2)
|
||||
|
||||
self.set_camera_orientation(phi=75*DEGREES, theta=-45*DEGREES)
|
||||
self.add(cylinder, cone)
|
||||
```
|
||||
|
||||
### Torus
|
||||
|
||||
```python
|
||||
class TorusExample(ThreeDScene):
|
||||
def construct(self):
|
||||
torus = Torus(major_radius=2, minor_radius=0.5)
|
||||
|
||||
self.set_camera_orientation(phi=75*DEGREES, theta=-45*DEGREES)
|
||||
self.add(torus)
|
||||
```
|
||||
|
||||
## 3D Axes
|
||||
|
||||
```python
|
||||
class ThreeDAxesExample(ThreeDScene):
|
||||
def construct(self):
|
||||
axes = ThreeDAxes(
|
||||
x_range=[-4, 4, 1],
|
||||
y_range=[-4, 4, 1],
|
||||
z_range=[-4, 4, 1],
|
||||
x_length=8,
|
||||
y_length=8,
|
||||
z_length=6,
|
||||
)
|
||||
|
||||
# Add axis labels
|
||||
x_label = axes.get_x_axis_label("x")
|
||||
y_label = axes.get_y_axis_label("y")
|
||||
z_label = axes.get_z_axis_label("z")
|
||||
|
||||
self.set_camera_orientation(phi=75*DEGREES, theta=-45*DEGREES)
|
||||
self.add(axes, x_label, y_label, z_label)
|
||||
```
|
||||
|
||||
## Surface Plots
|
||||
|
||||
```python
|
||||
class SurfacePlot(ThreeDScene):
|
||||
def construct(self):
|
||||
axes = ThreeDAxes(x_range=[-3, 3], y_range=[-3, 3], z_range=[-2, 2])
|
||||
|
||||
# Function z = f(x, y)
|
||||
surface = axes.plot_surface(
|
||||
lambda u, v: np.sin(u) * np.cos(v),
|
||||
u_range=[-3, 3],
|
||||
v_range=[-3, 3],
|
||||
resolution=(30, 30),
|
||||
colorscale=[BLUE, GREEN, YELLOW, RED],
|
||||
)
|
||||
|
||||
self.set_camera_orientation(phi=75*DEGREES, theta=-45*DEGREES)
|
||||
self.add(axes, surface)
|
||||
```
|
||||
|
||||
### Surface Class (standalone)
|
||||
|
||||
```python
|
||||
class SurfaceExample(ThreeDScene):
|
||||
def construct(self):
|
||||
def param_func(u, v):
|
||||
x = u
|
||||
y = v
|
||||
z = np.sin(np.sqrt(u**2 + v**2))
|
||||
return np.array([x, y, z])
|
||||
|
||||
surface = Surface(
|
||||
param_func,
|
||||
u_range=[-3, 3],
|
||||
v_range=[-3, 3],
|
||||
resolution=(30, 30),
|
||||
fill_opacity=0.8,
|
||||
)
|
||||
surface.set_color_by_gradient(BLUE, GREEN)
|
||||
|
||||
self.set_camera_orientation(phi=75*DEGREES, theta=-45*DEGREES)
|
||||
self.add(surface)
|
||||
```
|
||||
|
||||
## 3D Parametric Curves
|
||||
|
||||
```python
|
||||
class ParametricCurve3D(ThreeDScene):
|
||||
def construct(self):
|
||||
# Helix
|
||||
curve = ParametricFunction(
|
||||
lambda t: np.array([
|
||||
np.cos(t),
|
||||
np.sin(t),
|
||||
t * 0.2
|
||||
]),
|
||||
t_range=[-4*PI, 4*PI],
|
||||
color=YELLOW
|
||||
)
|
||||
curve.set_shade_in_3d(True)
|
||||
|
||||
self.set_camera_orientation(phi=75*DEGREES, theta=-45*DEGREES)
|
||||
self.add(ThreeDAxes(), curve)
|
||||
```
|
||||
|
||||
## Shading in 3D
|
||||
|
||||
```python
|
||||
class Shading3D(ThreeDScene):
|
||||
def construct(self):
|
||||
sphere = Sphere()
|
||||
|
||||
# Enable shading for realistic lighting
|
||||
sphere.set_shade_in_3d(True)
|
||||
|
||||
self.set_camera_orientation(phi=75*DEGREES, theta=-45*DEGREES)
|
||||
self.add(sphere)
|
||||
```
|
||||
|
||||
## Arrow3D and Line3D
|
||||
|
||||
```python
|
||||
class Vectors3D(ThreeDScene):
|
||||
def construct(self):
|
||||
axes = ThreeDAxes()
|
||||
|
||||
arrow = Arrow3D(ORIGIN, [2, 1, 2], color=RED)
|
||||
line = Line3D(ORIGIN, [-2, 1, 1], color=BLUE)
|
||||
|
||||
self.set_camera_orientation(phi=75*DEGREES, theta=-45*DEGREES)
|
||||
self.add(axes, arrow, line)
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Always set camera orientation** - Default view may not show 3D well
|
||||
2. **Use set_shade_in_3d for realism** - Adds depth perception
|
||||
3. **Use ambient camera rotation sparingly** - Can be disorienting
|
||||
4. **Match resolution to detail needed** - Higher res = slower render
|
||||
5. **Use colorscale for surfaces** - Shows elevation/value changes
|
||||
@@ -0,0 +1,212 @@
|
||||
---
|
||||
name: animation-groups
|
||||
description: AnimationGroup, LaggedStart, Succession for complex animation sequences
|
||||
metadata:
|
||||
tags: animationgroup, laggedstart, succession, lag_ratio, sequence
|
||||
---
|
||||
|
||||
# Animation Groups
|
||||
|
||||
Control how multiple animations play together.
|
||||
|
||||
## AnimationGroup
|
||||
|
||||
Play multiple animations with controlled timing.
|
||||
|
||||
```python
|
||||
from manim import *
|
||||
|
||||
class AnimationGroupExample(Scene):
|
||||
def construct(self):
|
||||
circles = VGroup(*[Circle() for _ in range(5)]).arrange(RIGHT)
|
||||
|
||||
# All animations play simultaneously (lag_ratio=0)
|
||||
self.play(AnimationGroup(
|
||||
*[Create(c) for c in circles],
|
||||
lag_ratio=0
|
||||
))
|
||||
```
|
||||
|
||||
### lag_ratio Parameter
|
||||
|
||||
Controls the delay between animation starts:
|
||||
- `lag_ratio=0`: All start simultaneously
|
||||
- `lag_ratio=0.5`: Each starts when previous is 50% complete
|
||||
- `lag_ratio=1`: Each starts when previous finishes (sequential)
|
||||
|
||||
```python
|
||||
class LagRatioDemo(Scene):
|
||||
def construct(self):
|
||||
squares = VGroup(*[Square() for _ in range(4)]).arrange(RIGHT)
|
||||
|
||||
# Staggered start - each begins when previous is 25% done
|
||||
self.play(AnimationGroup(
|
||||
*[FadeIn(s) for s in squares],
|
||||
lag_ratio=0.25,
|
||||
run_time=2
|
||||
))
|
||||
```
|
||||
|
||||
## LaggedStart
|
||||
|
||||
Convenience class with default `lag_ratio=0.05` (5% overlap).
|
||||
|
||||
```python
|
||||
class LaggedStartExample(Scene):
|
||||
def construct(self):
|
||||
dots = VGroup(*[Dot() for _ in range(10)]).arrange(RIGHT)
|
||||
|
||||
# Rapid staggered animation
|
||||
self.play(LaggedStart(
|
||||
*[GrowFromCenter(d) for d in dots],
|
||||
lag_ratio=0.1
|
||||
))
|
||||
```
|
||||
|
||||
### Common LaggedStart Patterns
|
||||
|
||||
```python
|
||||
# Staggered fade in
|
||||
self.play(LaggedStart(*[FadeIn(m) for m in mobjects], lag_ratio=0.2))
|
||||
|
||||
# Wave effect
|
||||
self.play(LaggedStart(
|
||||
*[m.animate.shift(UP * 0.5) for m in mobjects],
|
||||
lag_ratio=0.1
|
||||
))
|
||||
|
||||
# Staggered color change
|
||||
self.play(LaggedStart(
|
||||
*[m.animate.set_color(RED) for m in mobjects],
|
||||
lag_ratio=0.15
|
||||
))
|
||||
```
|
||||
|
||||
## Succession
|
||||
|
||||
Play animations one after another (equivalent to `lag_ratio=1`).
|
||||
|
||||
```python
|
||||
class SuccessionExample(Scene):
|
||||
def construct(self):
|
||||
circle = Circle().shift(LEFT * 2)
|
||||
square = Square()
|
||||
triangle = Triangle().shift(RIGHT * 2)
|
||||
|
||||
# Animations play in sequence
|
||||
self.play(Succession(
|
||||
Create(circle),
|
||||
Create(square),
|
||||
Create(triangle)
|
||||
))
|
||||
```
|
||||
|
||||
### Succession vs Multiple play() Calls
|
||||
|
||||
```python
|
||||
# These are equivalent:
|
||||
|
||||
# Using Succession
|
||||
self.play(Succession(
|
||||
Create(circle),
|
||||
Create(square)
|
||||
))
|
||||
|
||||
# Using separate play calls
|
||||
self.play(Create(circle))
|
||||
self.play(Create(square))
|
||||
```
|
||||
|
||||
Succession is useful when you want to treat sequential animations as a single unit.
|
||||
|
||||
## Combining Group Types
|
||||
|
||||
```python
|
||||
class CombinedExample(Scene):
|
||||
def construct(self):
|
||||
group1 = VGroup(*[Circle() for _ in range(3)]).arrange(RIGHT).shift(UP)
|
||||
group2 = VGroup(*[Square() for _ in range(3)]).arrange(RIGHT).shift(DOWN)
|
||||
|
||||
# First group appears with stagger, then second group
|
||||
self.play(Succession(
|
||||
LaggedStart(*[Create(c) for c in group1], lag_ratio=0.2),
|
||||
LaggedStart(*[Create(s) for s in group2], lag_ratio=0.2)
|
||||
))
|
||||
```
|
||||
|
||||
## LaggedStartMap
|
||||
|
||||
Apply an animation to all submobjects of a mobject with staggered timing.
|
||||
|
||||
```python
|
||||
class LaggedStartMapExample(Scene):
|
||||
def construct(self):
|
||||
dots = VGroup(*[Dot(radius=0.16) for _ in range(35)]).arrange_in_grid(rows=5, cols=7)
|
||||
|
||||
# Apply FadeIn to all dots with stagger
|
||||
self.play(LaggedStartMap(FadeIn, dots, lag_ratio=0.1))
|
||||
self.wait(0.5)
|
||||
|
||||
# Change color with stagger using LaggedStart
|
||||
self.play(LaggedStart(
|
||||
*[dot.animate.set_color(YELLOW) for dot in dots],
|
||||
lag_ratio=0.05
|
||||
))
|
||||
```
|
||||
|
||||
LaggedStartMap is cleaner for applying the same animation to each submobject. For property changes, use LaggedStart with `.animate`.
|
||||
|
||||
## AnimationGroup with run_time
|
||||
|
||||
The total `run_time` is distributed among animations based on `lag_ratio`.
|
||||
|
||||
```python
|
||||
self.play(AnimationGroup(
|
||||
*[Create(c) for c in circles],
|
||||
lag_ratio=0.5,
|
||||
run_time=4 # Total duration is 4 seconds
|
||||
))
|
||||
```
|
||||
|
||||
## Practical Examples
|
||||
|
||||
### Text Appearing Word by Word
|
||||
|
||||
```python
|
||||
class WordByWord(Scene):
|
||||
def construct(self):
|
||||
words = VGroup(
|
||||
Text("Hello"),
|
||||
Text("World"),
|
||||
Text("!")
|
||||
).arrange(RIGHT)
|
||||
|
||||
self.play(LaggedStart(
|
||||
*[Write(w) for w in words],
|
||||
lag_ratio=0.5
|
||||
))
|
||||
```
|
||||
|
||||
### Grid Animation
|
||||
|
||||
```python
|
||||
class GridAnimation(Scene):
|
||||
def construct(self):
|
||||
grid = VGroup(*[
|
||||
Square().scale(0.3)
|
||||
for _ in range(25)
|
||||
]).arrange_in_grid(5, 5)
|
||||
|
||||
# Diagonal wave effect
|
||||
self.play(LaggedStart(
|
||||
*[GrowFromCenter(s) for s in grid],
|
||||
lag_ratio=0.05
|
||||
))
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use LaggedStart for visual polish** - Staggered animations look more dynamic
|
||||
2. **Keep lag_ratio small (0.05-0.2)** - Too high feels slow
|
||||
3. **Use Succession for distinct steps** - When animations are conceptually separate
|
||||
4. **Adjust run_time with lag_ratio** - More items may need longer total time
|
||||
@@ -0,0 +1,128 @@
|
||||
---
|
||||
name: animations
|
||||
description: Animation classes, playing animations, and animation timing in Manim
|
||||
metadata:
|
||||
tags: animation, play, run_time, rate_func, animate
|
||||
---
|
||||
|
||||
# Animations in Manim
|
||||
|
||||
Animations interpolate mobjects between states over time. They are played using `self.play()`.
|
||||
|
||||
## The .animate Syntax
|
||||
|
||||
The most common way to animate is using the `.animate` property:
|
||||
|
||||
```python
|
||||
# Move a square to the right
|
||||
self.play(square.animate.shift(RIGHT))
|
||||
|
||||
# Scale up
|
||||
self.play(circle.animate.scale(2))
|
||||
|
||||
# Change color
|
||||
self.play(text.animate.set_color(RED))
|
||||
|
||||
# Chain multiple changes
|
||||
self.play(square.animate.shift(RIGHT).rotate(PI/4).set_color(BLUE))
|
||||
```
|
||||
|
||||
## Animation Parameters
|
||||
|
||||
### run_time
|
||||
Controls animation duration in seconds (default: 1).
|
||||
|
||||
```python
|
||||
self.play(Create(circle), run_time=2) # 2 second animation
|
||||
self.play(Create(circle), run_time=0.5) # Half second
|
||||
```
|
||||
|
||||
### rate_func
|
||||
Controls the animation's timing curve (easing).
|
||||
|
||||
```python
|
||||
from manim import smooth, linear, there_and_back
|
||||
|
||||
self.play(square.animate.shift(RIGHT), rate_func=smooth)
|
||||
self.play(square.animate.shift(RIGHT), rate_func=linear)
|
||||
self.play(square.animate.shift(RIGHT), rate_func=there_and_back)
|
||||
```
|
||||
|
||||
## Playing Multiple Animations
|
||||
|
||||
### Simultaneously
|
||||
|
||||
```python
|
||||
# All play at the same time
|
||||
self.play(
|
||||
Create(circle),
|
||||
FadeIn(square),
|
||||
Write(text)
|
||||
)
|
||||
```
|
||||
|
||||
### Sequentially
|
||||
|
||||
```python
|
||||
# One after another
|
||||
self.play(Create(circle))
|
||||
self.play(FadeIn(square))
|
||||
self.play(Write(text))
|
||||
|
||||
# Or use Succession
|
||||
self.play(Succession(
|
||||
Create(circle),
|
||||
FadeIn(square),
|
||||
Write(text)
|
||||
))
|
||||
```
|
||||
|
||||
## Common Animation Classes
|
||||
|
||||
### Creation Animations
|
||||
```python
|
||||
Create(mobject) # Draw the mobject progressively
|
||||
Write(text) # Write text/equations
|
||||
FadeIn(mobject) # Fade in from transparent
|
||||
DrawBorderThenFill(mob) # Draw outline, then fill
|
||||
GrowFromCenter(mobject) # Grow from center point
|
||||
```
|
||||
|
||||
### Removal Animations
|
||||
```python
|
||||
FadeOut(mobject) # Fade to transparent
|
||||
Uncreate(mobject) # Reverse of Create
|
||||
ShrinkToCenter(mobject) # Shrink to center and disappear
|
||||
```
|
||||
|
||||
### Transform Animations
|
||||
```python
|
||||
Transform(mob1, mob2) # Morph mob1 into mob2
|
||||
ReplacementTransform(mob1, mob2) # Replace mob1 with mob2
|
||||
TransformFromCopy(mob1, mob2) # Keep mob1, create mob2
|
||||
```
|
||||
|
||||
### Movement Animations
|
||||
```python
|
||||
MoveToTarget(mobject) # Move to preset target
|
||||
Rotate(mobject, angle) # Rotate by angle
|
||||
Circumscribe(mobject) # Draw attention with circle
|
||||
```
|
||||
|
||||
## Animation vs Instant Changes
|
||||
|
||||
```python
|
||||
# Animated change (visible transition)
|
||||
self.play(circle.animate.set_color(RED))
|
||||
|
||||
# Instant change (no animation)
|
||||
circle.set_color(RED)
|
||||
self.add(circle)
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use .animate for simple transformations** - Cleaner than explicit Animation classes
|
||||
2. **Keep run_time reasonable** - 0.5-2 seconds for most animations
|
||||
3. **Use rate_func for polish** - `smooth` is usually better than `linear`
|
||||
4. **Group related animations** - Play simultaneously when conceptually related
|
||||
@@ -0,0 +1,214 @@
|
||||
---
|
||||
name: axes
|
||||
description: Axes, NumberPlane, and coordinate systems in Manim
|
||||
metadata:
|
||||
tags: axes, numberplane, coordinate, grid, numberline
|
||||
---
|
||||
|
||||
# Coordinate Systems
|
||||
|
||||
Create axes, grids, and number lines for mathematical visualizations.
|
||||
|
||||
## Axes
|
||||
|
||||
Basic 2D coordinate axes.
|
||||
|
||||
```python
|
||||
from manim import *
|
||||
|
||||
class AxesExample(Scene):
|
||||
def construct(self):
|
||||
# Default axes
|
||||
axes = Axes()
|
||||
self.add(axes)
|
||||
```
|
||||
|
||||
### Customizing Axes
|
||||
|
||||
```python
|
||||
class CustomAxes(Scene):
|
||||
def construct(self):
|
||||
axes = Axes(
|
||||
x_range=[-5, 5, 1], # [min, max, step]
|
||||
y_range=[-3, 3, 1],
|
||||
x_length=10, # Physical length on screen
|
||||
y_length=6,
|
||||
axis_config={
|
||||
"color": BLUE,
|
||||
"include_tip": True,
|
||||
"include_numbers": True,
|
||||
},
|
||||
x_axis_config={
|
||||
"numbers_to_include": [-4, -2, 0, 2, 4],
|
||||
},
|
||||
y_axis_config={
|
||||
"numbers_to_include": [-2, 0, 2],
|
||||
},
|
||||
)
|
||||
self.add(axes)
|
||||
```
|
||||
|
||||
### Adding Labels
|
||||
|
||||
```python
|
||||
class AxesLabels(Scene):
|
||||
def construct(self):
|
||||
axes = Axes(x_range=[-5, 5], y_range=[-3, 3])
|
||||
|
||||
# Add axis labels
|
||||
x_label = axes.get_x_axis_label("x")
|
||||
y_label = axes.get_y_axis_label("y")
|
||||
|
||||
# Custom labels
|
||||
x_label = axes.get_x_axis_label(MathTex(r"\theta"))
|
||||
y_label = axes.get_y_axis_label(MathTex(r"f(\theta)"))
|
||||
|
||||
self.add(axes, x_label, y_label)
|
||||
```
|
||||
|
||||
## NumberPlane
|
||||
|
||||
Grid with axes - shows coordinate lines.
|
||||
|
||||
```python
|
||||
class NumberPlaneExample(Scene):
|
||||
def construct(self):
|
||||
# Default plane
|
||||
plane = NumberPlane()
|
||||
self.add(plane)
|
||||
```
|
||||
|
||||
### Customizing NumberPlane
|
||||
|
||||
```python
|
||||
class CustomPlane(Scene):
|
||||
def construct(self):
|
||||
plane = NumberPlane(
|
||||
x_range=[-4, 4, 1],
|
||||
y_range=[-3, 3, 1],
|
||||
x_length=8,
|
||||
y_length=6,
|
||||
background_line_style={
|
||||
"stroke_color": BLUE_D,
|
||||
"stroke_width": 1,
|
||||
"stroke_opacity": 0.5,
|
||||
},
|
||||
axis_config={
|
||||
"color": WHITE,
|
||||
},
|
||||
)
|
||||
self.add(plane)
|
||||
```
|
||||
|
||||
## ComplexPlane
|
||||
|
||||
For visualizing complex numbers.
|
||||
|
||||
```python
|
||||
class ComplexPlaneExample(Scene):
|
||||
def construct(self):
|
||||
plane = ComplexPlane()
|
||||
|
||||
# Plot complex number
|
||||
z = complex(2, 1) # 2 + i
|
||||
dot = Dot(plane.n2p(z), color=YELLOW)
|
||||
label = MathTex("2+i").next_to(dot, UR)
|
||||
|
||||
self.add(plane, dot, label)
|
||||
```
|
||||
|
||||
## NumberLine
|
||||
|
||||
Single axis line.
|
||||
|
||||
```python
|
||||
class NumberLineExample(Scene):
|
||||
def construct(self):
|
||||
line = NumberLine(
|
||||
x_range=[-5, 5, 1],
|
||||
length=10,
|
||||
include_numbers=True,
|
||||
include_tip=True,
|
||||
)
|
||||
self.add(line)
|
||||
```
|
||||
|
||||
## Coordinate Conversions
|
||||
|
||||
```python
|
||||
class CoordinateConversion(Scene):
|
||||
def construct(self):
|
||||
axes = Axes(x_range=[-5, 5], y_range=[-3, 3])
|
||||
|
||||
# Convert coordinates to screen position
|
||||
point = axes.c2p(2, 1) # coords_to_point: (2, 1) -> screen position
|
||||
|
||||
# Convert screen position to coordinates
|
||||
coords = axes.p2c(point) # point_to_coords: screen -> (x, y)
|
||||
|
||||
dot = Dot(point, color=RED)
|
||||
self.add(axes, dot)
|
||||
```
|
||||
|
||||
### Shorthand Methods
|
||||
|
||||
```python
|
||||
axes = Axes()
|
||||
|
||||
# c2p = coords_to_point
|
||||
axes.c2p(x, y)
|
||||
|
||||
# p2c = point_to_coords
|
||||
axes.p2c(point)
|
||||
|
||||
# i2gp = input_to_graph_point (for graphs)
|
||||
axes.i2gp(x, graph)
|
||||
|
||||
# For NumberPlane/ComplexPlane
|
||||
plane.n2p(complex_number) # number_to_point
|
||||
plane.p2n(point) # point_to_number
|
||||
```
|
||||
|
||||
## ThreeDAxes
|
||||
|
||||
For 3D visualizations.
|
||||
|
||||
```python
|
||||
class ThreeDAxesExample(ThreeDScene):
|
||||
def construct(self):
|
||||
axes = ThreeDAxes(
|
||||
x_range=[-4, 4, 1],
|
||||
y_range=[-4, 4, 1],
|
||||
z_range=[-4, 4, 1],
|
||||
x_length=8,
|
||||
y_length=8,
|
||||
z_length=6,
|
||||
)
|
||||
|
||||
self.set_camera_orientation(phi=75 * DEGREES, theta=-45 * DEGREES)
|
||||
self.add(axes)
|
||||
```
|
||||
|
||||
## Plotting Points
|
||||
|
||||
```python
|
||||
class PlotPoints(Scene):
|
||||
def construct(self):
|
||||
axes = Axes(x_range=[-5, 5], y_range=[-3, 3])
|
||||
|
||||
points = [(1, 2), (-2, 1), (3, -1), (0, 2)]
|
||||
dots = VGroup(*[
|
||||
Dot(axes.c2p(x, y), color=YELLOW)
|
||||
for x, y in points
|
||||
])
|
||||
|
||||
self.add(axes, dots)
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Set appropriate ranges** - Don't include unnecessary empty space
|
||||
2. **Match x_length/y_length to range ratio** - Prevents distortion
|
||||
3. **Use NumberPlane for transformations** - Grid shows distortion clearly
|
||||
4. **Use c2p for all coordinate work** - Don't manually convert
|
||||
5. **Include numbers sparingly** - Too many numbers clutter the display
|
||||
@@ -0,0 +1,208 @@
|
||||
---
|
||||
name: camera
|
||||
description: MovingCameraScene, zoom, pan, and camera manipulation
|
||||
metadata:
|
||||
tags: camera, zoom, pan, frame, movingcamerascene, 3d
|
||||
---
|
||||
|
||||
# Camera Control
|
||||
|
||||
Control what the viewer sees with camera manipulation.
|
||||
|
||||
## MovingCameraScene
|
||||
|
||||
For 2D scenes with camera movement (zoom, pan).
|
||||
|
||||
```python
|
||||
from manim import *
|
||||
|
||||
class CameraExample(MovingCameraScene):
|
||||
def construct(self):
|
||||
circle = Circle()
|
||||
square = Square().shift(RIGHT * 3)
|
||||
self.add(circle, square)
|
||||
|
||||
# Access camera frame
|
||||
# self.camera.frame is the viewable area
|
||||
```
|
||||
|
||||
## Zooming
|
||||
|
||||
### Zoom In/Out by Scaling Frame
|
||||
|
||||
```python
|
||||
class ZoomExample(MovingCameraScene):
|
||||
def construct(self):
|
||||
dots = VGroup(*[Dot() for _ in range(100)])
|
||||
dots.arrange_in_grid(10, 10, buff=0.3)
|
||||
self.add(dots)
|
||||
|
||||
# Zoom in (make frame smaller)
|
||||
self.play(self.camera.frame.animate.scale(0.5))
|
||||
self.wait()
|
||||
|
||||
# Zoom out (make frame larger)
|
||||
self.play(self.camera.frame.animate.scale(4))
|
||||
```
|
||||
|
||||
### Zoom to Specific Width
|
||||
|
||||
```python
|
||||
class ZoomToWidth(MovingCameraScene):
|
||||
def construct(self):
|
||||
text = Text("Focus on me!")
|
||||
self.add(text)
|
||||
|
||||
# Zoom to fit text with padding
|
||||
self.play(
|
||||
self.camera.frame.animate.set(width=text.width * 1.5)
|
||||
)
|
||||
```
|
||||
|
||||
## Panning
|
||||
|
||||
### Move Camera to Location
|
||||
|
||||
```python
|
||||
class PanExample(MovingCameraScene):
|
||||
def construct(self):
|
||||
c1 = Circle().shift(LEFT * 3)
|
||||
c2 = Circle().shift(RIGHT * 3)
|
||||
self.add(c1, c2)
|
||||
|
||||
# Pan to first circle
|
||||
self.play(self.camera.frame.animate.move_to(c1))
|
||||
self.wait()
|
||||
|
||||
# Pan to second circle
|
||||
self.play(self.camera.frame.animate.move_to(c2))
|
||||
```
|
||||
|
||||
### Combined Zoom and Pan
|
||||
|
||||
```python
|
||||
class ZoomAndPan(MovingCameraScene):
|
||||
def construct(self):
|
||||
square = Square().shift(LEFT * 2)
|
||||
triangle = Triangle().shift(RIGHT * 2)
|
||||
self.add(square, triangle)
|
||||
|
||||
# Zoom in and pan simultaneously
|
||||
self.play(
|
||||
self.camera.frame.animate.scale(0.5).move_to(square)
|
||||
)
|
||||
self.wait()
|
||||
|
||||
# Move to triangle (still zoomed)
|
||||
self.play(self.camera.frame.animate.move_to(triangle))
|
||||
```
|
||||
|
||||
## Save and Restore Camera State
|
||||
|
||||
```python
|
||||
class SaveRestoreCamera(MovingCameraScene):
|
||||
def construct(self):
|
||||
circle = Circle()
|
||||
self.add(circle)
|
||||
|
||||
# Save current state
|
||||
self.camera.frame.save_state()
|
||||
|
||||
# Make changes
|
||||
self.play(self.camera.frame.animate.scale(0.3).move_to(circle))
|
||||
self.wait()
|
||||
|
||||
# Restore to saved state
|
||||
self.play(Restore(self.camera.frame))
|
||||
```
|
||||
|
||||
## auto_zoom
|
||||
|
||||
Automatically zoom to fit mobjects.
|
||||
|
||||
```python
|
||||
class AutoZoomExample(MovingCameraScene):
|
||||
def construct(self):
|
||||
squares = VGroup(*[
|
||||
Square().shift(RIGHT * i + UP * j)
|
||||
for i in range(-2, 3) for j in range(-2, 3)
|
||||
])
|
||||
self.add(squares)
|
||||
|
||||
# Zoom to fit specific mobject
|
||||
self.play(self.camera.auto_zoom(squares[0]))
|
||||
self.wait()
|
||||
|
||||
# Zoom to fit all with margin
|
||||
self.play(self.camera.auto_zoom(squares, margin=1))
|
||||
```
|
||||
|
||||
## 3D Camera (ThreeDScene)
|
||||
|
||||
```python
|
||||
class ThreeDCameraExample(ThreeDScene):
|
||||
def construct(self):
|
||||
axes = ThreeDAxes()
|
||||
sphere = Sphere()
|
||||
self.add(axes, sphere)
|
||||
|
||||
# Set initial camera orientation
|
||||
self.set_camera_orientation(
|
||||
phi=75 * DEGREES, # Angle from z-axis
|
||||
theta=-45 * DEGREES # Angle around z-axis
|
||||
)
|
||||
```
|
||||
|
||||
### Animated Camera Rotation
|
||||
|
||||
```python
|
||||
class RotatingCamera(ThreeDScene):
|
||||
def construct(self):
|
||||
axes = ThreeDAxes()
|
||||
self.add(axes)
|
||||
|
||||
self.set_camera_orientation(phi=75 * DEGREES, theta=0)
|
||||
|
||||
# Continuous rotation
|
||||
self.begin_ambient_camera_rotation(rate=0.2)
|
||||
self.wait(5)
|
||||
self.stop_ambient_camera_rotation()
|
||||
```
|
||||
|
||||
### Move 3D Camera
|
||||
|
||||
```python
|
||||
class Move3DCamera(ThreeDScene):
|
||||
def construct(self):
|
||||
axes = ThreeDAxes()
|
||||
self.add(axes)
|
||||
|
||||
self.set_camera_orientation(phi=75 * DEGREES, theta=-45 * DEGREES)
|
||||
|
||||
# Animate camera movement
|
||||
self.move_camera(
|
||||
phi=45 * DEGREES,
|
||||
theta=45 * DEGREES,
|
||||
run_time=3
|
||||
)
|
||||
```
|
||||
|
||||
## Camera Background
|
||||
|
||||
```python
|
||||
class CameraBackground(Scene):
|
||||
def construct(self):
|
||||
# Set background color
|
||||
self.camera.background_color = BLUE_E
|
||||
|
||||
circle = Circle()
|
||||
self.add(circle)
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use MovingCameraScene for zoom/pan** - Regular Scene camera is static
|
||||
2. **Save state before complex movements** - Easy to restore
|
||||
3. **Use auto_zoom for dynamic content** - Automatically fits content
|
||||
4. **Keep camera movements smooth** - Don't make viewers dizzy
|
||||
5. **Use 3D camera rotation sparingly** - Can be disorienting
|
||||
@@ -0,0 +1,232 @@
|
||||
---
|
||||
name: cli
|
||||
description: Command-line interface, rendering options, and quality flags
|
||||
metadata:
|
||||
tags: cli, render, quality, preview, command, terminal
|
||||
---
|
||||
|
||||
# Manim CLI
|
||||
|
||||
The `manim` command-line interface for rendering scenes.
|
||||
|
||||
## Basic Usage
|
||||
|
||||
```bash
|
||||
# Render a scene
|
||||
manim file.py SceneName
|
||||
|
||||
# With preview (opens video after rendering)
|
||||
manim -p file.py SceneName
|
||||
|
||||
# Preview with low quality (fast)
|
||||
manim -pql file.py SceneName
|
||||
```
|
||||
|
||||
## Quality Flags
|
||||
|
||||
Quality presets for different use cases:
|
||||
|
||||
```bash
|
||||
# Low Quality: 854x480, 15fps (fast for testing)
|
||||
manim -ql file.py SceneName
|
||||
|
||||
# Medium Quality: 1280x720, 30fps
|
||||
manim -qm file.py SceneName
|
||||
|
||||
# High Quality: 1920x1080, 60fps
|
||||
manim -qh file.py SceneName
|
||||
|
||||
# 2K Quality: 2560x1440, 60fps
|
||||
manim -qp file.py SceneName
|
||||
|
||||
# 4K Quality: 3840x2160, 60fps
|
||||
manim -qk file.py SceneName
|
||||
```
|
||||
|
||||
### Common Combinations
|
||||
|
||||
```bash
|
||||
# Preview + Low Quality (development workflow)
|
||||
manim -pql file.py SceneName
|
||||
|
||||
# Preview + High Quality (final check)
|
||||
manim -pqh file.py SceneName
|
||||
```
|
||||
|
||||
## Preview Flag
|
||||
|
||||
```bash
|
||||
# -p: Open video after rendering
|
||||
manim -p file.py SceneName
|
||||
|
||||
# Without -p: Render only (no auto-open)
|
||||
manim file.py SceneName
|
||||
```
|
||||
|
||||
## Rendering Multiple Scenes
|
||||
|
||||
```bash
|
||||
# Render all scenes in file
|
||||
manim -a file.py
|
||||
|
||||
# Render specific scenes
|
||||
manim file.py Scene1 Scene2 Scene3
|
||||
```
|
||||
|
||||
## Output Options
|
||||
|
||||
### Save Last Frame Only
|
||||
|
||||
```bash
|
||||
# -s: Save only the last frame as PNG
|
||||
manim -s file.py SceneName
|
||||
|
||||
# With quality
|
||||
manim -sql file.py SceneName
|
||||
```
|
||||
|
||||
### Output Format
|
||||
|
||||
```bash
|
||||
# GIF output
|
||||
manim --format gif file.py SceneName
|
||||
|
||||
# PNG sequence
|
||||
manim --format png file.py SceneName
|
||||
|
||||
# WebM (default is MP4)
|
||||
manim --format webm file.py SceneName
|
||||
```
|
||||
|
||||
### Custom Output Directory
|
||||
|
||||
```bash
|
||||
manim -o custom_name file.py SceneName
|
||||
manim --media_dir /path/to/output file.py SceneName
|
||||
```
|
||||
|
||||
## Frame Control
|
||||
|
||||
```bash
|
||||
# Start from specific animation number
|
||||
manim -n 5 file.py SceneName
|
||||
|
||||
# Render frames from animation 3 to 7
|
||||
manim -n 3,7 file.py SceneName
|
||||
```
|
||||
|
||||
## Resolution and FPS
|
||||
|
||||
```bash
|
||||
# Custom resolution
|
||||
manim -r 1920,1080 file.py SceneName
|
||||
|
||||
# Custom frame rate
|
||||
manim --fps 24 file.py SceneName
|
||||
|
||||
# Both
|
||||
manim -r 1280,720 --fps 30 file.py SceneName
|
||||
```
|
||||
|
||||
## Transparency
|
||||
|
||||
```bash
|
||||
# Render with transparent background
|
||||
manim -t file.py SceneName
|
||||
```
|
||||
|
||||
## Renderer Selection
|
||||
|
||||
```bash
|
||||
# Cairo renderer (default, 2D)
|
||||
manim --renderer cairo file.py SceneName
|
||||
|
||||
# OpenGL renderer (3D, faster preview)
|
||||
manim --renderer opengl file.py SceneName
|
||||
```
|
||||
|
||||
## Other Useful Flags
|
||||
|
||||
```bash
|
||||
# Verbose output
|
||||
manim -v DEBUG file.py SceneName
|
||||
|
||||
# Quiet mode
|
||||
manim -v WARNING file.py SceneName
|
||||
|
||||
# Show progress bar
|
||||
manim --progress_bar display file.py SceneName
|
||||
|
||||
# Disable caching
|
||||
manim --disable_caching file.py SceneName
|
||||
|
||||
# Write to movie even if no animations
|
||||
manim --write_to_movie file.py SceneName
|
||||
```
|
||||
|
||||
## Help
|
||||
|
||||
```bash
|
||||
# Show all options
|
||||
manim --help
|
||||
|
||||
# Show render command options
|
||||
manim render --help
|
||||
```
|
||||
|
||||
## Other Commands
|
||||
|
||||
```bash
|
||||
# Check installation and dependencies
|
||||
manim checkhealth
|
||||
|
||||
# Initialize new project
|
||||
manim init
|
||||
|
||||
# Show config values
|
||||
manim cfg show
|
||||
|
||||
# Write current config to file
|
||||
manim cfg write
|
||||
|
||||
# List installed plugins
|
||||
manim plugins -l
|
||||
```
|
||||
|
||||
## Jupyter Notebook Support
|
||||
|
||||
Use the `%%manim` cell magic in Jupyter notebooks:
|
||||
|
||||
```python
|
||||
%%manim -qm -v WARNING MyScene
|
||||
class MyScene(Scene):
|
||||
def construct(self):
|
||||
circle = Circle()
|
||||
self.play(Create(circle))
|
||||
```
|
||||
|
||||
Flags work the same as CLI (`-qm`, `-ql`, etc.).
|
||||
|
||||
## Typical Development Workflow
|
||||
|
||||
```bash
|
||||
# 1. Develop with fast preview
|
||||
manim -pql scene.py MyScene
|
||||
|
||||
# 2. Check at medium quality
|
||||
manim -pqm scene.py MyScene
|
||||
|
||||
# 3. Final render at high quality
|
||||
manim -qh scene.py MyScene
|
||||
|
||||
# 4. Create GIF for sharing
|
||||
manim --format gif -qm scene.py MyScene
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use -pql for development** - Fast iteration cycle
|
||||
2. **Use -qh for final output** - Good quality, reasonable render time
|
||||
3. **Use -s for thumbnails** - Quick last-frame capture
|
||||
4. **Use -a sparingly** - Renders everything, can be slow
|
||||
5. **Use --format gif for demos** - Easy to share and embed
|
||||
@@ -0,0 +1,201 @@
|
||||
---
|
||||
name: colors
|
||||
description: Color constants, gradients, and color manipulation in Manim
|
||||
metadata:
|
||||
tags: color, colors, gradient, rgb, hex, palette
|
||||
---
|
||||
|
||||
# Colors in Manim
|
||||
|
||||
Manim provides predefined color constants and supports custom colors.
|
||||
|
||||
## Color Constants
|
||||
|
||||
### Primary Colors
|
||||
```python
|
||||
RED, GREEN, BLUE
|
||||
YELLOW, ORANGE, PINK, PURPLE
|
||||
WHITE, BLACK, GREY (or GRAY)
|
||||
```
|
||||
|
||||
### Color Variants (Shades)
|
||||
Most colors have variants from `_A` (lightest) to `_E` (darkest):
|
||||
```python
|
||||
BLUE_A, BLUE_B, BLUE_C, BLUE_D, BLUE_E
|
||||
RED_A, RED_B, RED_C, RED_D, RED_E
|
||||
GREEN_A, GREEN_B, GREEN_C, GREEN_D, GREEN_E
|
||||
GREY_A, GREY_B, GREY_C, GREY_D, GREY_E
|
||||
```
|
||||
|
||||
### Common Named Colors
|
||||
```python
|
||||
TEAL, TEAL_A, TEAL_B, TEAL_C, TEAL_D, TEAL_E
|
||||
GOLD, GOLD_A, GOLD_B, GOLD_C, GOLD_D, GOLD_E
|
||||
MAROON, MAROON_A, MAROON_B, MAROON_C, MAROON_D, MAROON_E
|
||||
PURPLE, PURPLE_A, PURPLE_B, PURPLE_C, PURPLE_D, PURPLE_E
|
||||
```
|
||||
|
||||
### Special Colors
|
||||
```python
|
||||
PURE_RED, PURE_GREEN, PURE_BLUE # RGB primaries
|
||||
LIGHT_GREY, DARK_GREY
|
||||
LIGHTER_GREY, DARKER_GREY
|
||||
LIGHT_BROWN, DARK_BROWN
|
||||
```
|
||||
|
||||
## Using Colors
|
||||
|
||||
### Setting Color on Creation
|
||||
```python
|
||||
circle = Circle(color=RED)
|
||||
square = Square(color=BLUE, fill_color=GREEN, fill_opacity=0.5)
|
||||
text = Text("Hello", color=YELLOW)
|
||||
```
|
||||
|
||||
### Setting Color After Creation
|
||||
```python
|
||||
circle = Circle()
|
||||
circle.set_color(RED)
|
||||
```
|
||||
|
||||
## Hex Colors
|
||||
|
||||
```python
|
||||
# Use hex strings
|
||||
circle = Circle(color="#FF5733")
|
||||
square = Square(color="#2ECC71")
|
||||
|
||||
# RGB values (0-1 range)
|
||||
from manim import rgb_to_color
|
||||
custom = rgb_to_color([0.5, 0.2, 0.8])
|
||||
```
|
||||
|
||||
## Fill vs Stroke Color
|
||||
|
||||
```python
|
||||
square = Square()
|
||||
square.set_fill(RED, opacity=0.8) # Interior color
|
||||
square.set_stroke(BLUE, width=4) # Border color
|
||||
```
|
||||
|
||||
### Combined Styling
|
||||
```python
|
||||
square = Square(
|
||||
color=BLUE, # Sets both fill and stroke
|
||||
fill_opacity=0.5, # Fill transparency
|
||||
stroke_width=4 # Border thickness
|
||||
)
|
||||
```
|
||||
|
||||
## Gradients
|
||||
|
||||
### Color Gradient on Mobject
|
||||
```python
|
||||
text = Text("GRADIENT")
|
||||
text.set_color_by_gradient(RED, YELLOW, GREEN)
|
||||
```
|
||||
|
||||
### Gradient Along Path
|
||||
```python
|
||||
line = Line(LEFT * 3, RIGHT * 3)
|
||||
line.set_color_by_gradient(BLUE, GREEN, YELLOW)
|
||||
```
|
||||
|
||||
## Color Interpolation
|
||||
|
||||
Create colors between two colors:
|
||||
|
||||
```python
|
||||
from manim import interpolate_color
|
||||
|
||||
# Get color halfway between RED and BLUE
|
||||
mid_color = interpolate_color(RED, BLUE, 0.5)
|
||||
|
||||
# Create a range of colors
|
||||
colors = [interpolate_color(RED, BLUE, alpha) for alpha in np.linspace(0, 1, 10)]
|
||||
```
|
||||
|
||||
## ManimColor Class
|
||||
|
||||
For advanced color manipulation, use ManimColor directly:
|
||||
|
||||
```python
|
||||
from manim import ManimColor
|
||||
|
||||
# Create from various formats
|
||||
color1 = ManimColor("#FF0000") # From hex
|
||||
color2 = ManimColor((0.0, 1.0, 0.5)) # From RGB floats (0-1)
|
||||
color3 = ManimColor([255, 165, 0]) # From RGB ints (0-255)
|
||||
|
||||
# Color manipulation methods
|
||||
lighter = color1.lighter() # Lighter version
|
||||
darker = color1.darker() # Darker version
|
||||
inverted = color1.invert() # Inverted color
|
||||
with_alpha = color1.opacity(0.5) # With 50% opacity
|
||||
|
||||
# Convert formats
|
||||
hex_str = color1.to_hex() # To hex string
|
||||
rgb = color1.to_rgb() # To RGB float array
|
||||
hsv = color1.to_hsv() # To HSV array
|
||||
|
||||
# Interpolation
|
||||
mixed = color1.interpolate(color2, 0.5) # Blend two colors
|
||||
```
|
||||
|
||||
## Opacity
|
||||
|
||||
```python
|
||||
# Set opacity (0 = transparent, 1 = opaque)
|
||||
circle = Circle(fill_opacity=0.5, stroke_opacity=0.8)
|
||||
|
||||
# Modify opacity
|
||||
circle.set_opacity(0.5) # Both fill and stroke
|
||||
circle.set_fill_opacity(0.7) # Fill only
|
||||
circle.set_stroke_opacity(0.3) # Stroke only
|
||||
```
|
||||
|
||||
## Color by Value
|
||||
|
||||
Color mobjects based on a value (useful for data visualization):
|
||||
|
||||
```python
|
||||
class ColorByValue(Scene):
|
||||
def construct(self):
|
||||
dots = VGroup(*[Dot() for _ in range(10)]).arrange(RIGHT)
|
||||
|
||||
for i, dot in enumerate(dots):
|
||||
# Color from blue (cold) to red (hot)
|
||||
dot.set_color(interpolate_color(BLUE, RED, i / 9))
|
||||
|
||||
self.add(dots)
|
||||
```
|
||||
|
||||
## Random Colors
|
||||
|
||||
```python
|
||||
from manim import random_color, random_bright_color
|
||||
|
||||
circle = Circle(color=random_color())
|
||||
square = Square(color=random_bright_color())
|
||||
```
|
||||
|
||||
## Color Lists for Animations
|
||||
|
||||
```python
|
||||
class ColorCycle(Scene):
|
||||
def construct(self):
|
||||
circle = Circle()
|
||||
self.add(circle)
|
||||
|
||||
colors = [RED, ORANGE, YELLOW, GREEN, BLUE, PURPLE]
|
||||
for color in colors:
|
||||
self.play(circle.animate.set_color(color), run_time=0.5)
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use color variants for depth** - `BLUE_E` for shadows, `BLUE_A` for highlights
|
||||
2. **Maintain color consistency** - Use the same colors for related concepts
|
||||
3. **Use opacity for layering** - Semi-transparent fills show overlapping
|
||||
4. **Consider colorblind accessibility** - Avoid red-green only distinctions
|
||||
5. **Use gradients sparingly** - They can be distracting
|
||||
@@ -0,0 +1,264 @@
|
||||
---
|
||||
name: config
|
||||
description: Configuration system, manim.cfg, and settings
|
||||
metadata:
|
||||
tags: config, configuration, settings, manim.cfg, options
|
||||
---
|
||||
|
||||
# Configuration
|
||||
|
||||
Configure Manim's behavior through files and code.
|
||||
|
||||
## Configuration Hierarchy
|
||||
|
||||
Manim reads configuration from (in order of precedence):
|
||||
1. Command-line arguments (highest priority)
|
||||
2. User's `manim.cfg` in current directory
|
||||
3. User's global config
|
||||
4. Default values (lowest priority)
|
||||
|
||||
## manim.cfg File
|
||||
|
||||
Create a `manim.cfg` file in your project directory:
|
||||
|
||||
```ini
|
||||
[CLI]
|
||||
# Preview after rendering
|
||||
preview = True
|
||||
|
||||
# Default quality
|
||||
quality = medium_quality
|
||||
|
||||
# Output format
|
||||
format = mp4
|
||||
|
||||
# Frame rate
|
||||
frame_rate = 30
|
||||
|
||||
[output]
|
||||
# Custom output directory
|
||||
media_dir = ./media
|
||||
|
||||
# Save last frame as PNG
|
||||
save_last_frame = False
|
||||
|
||||
[renderer]
|
||||
# Background color
|
||||
background_color = BLACK
|
||||
|
||||
[style]
|
||||
# Default font
|
||||
font = Arial
|
||||
```
|
||||
|
||||
## Common Configuration Options
|
||||
|
||||
### CLI Section
|
||||
|
||||
```ini
|
||||
[CLI]
|
||||
# Quality presets: low_quality, medium_quality, high_quality, production_quality, fourk_quality
|
||||
quality = medium_quality
|
||||
|
||||
# Preview video after rendering
|
||||
preview = True
|
||||
|
||||
# Frame rate
|
||||
frame_rate = 30
|
||||
|
||||
# Output format: mp4, gif, mov, webm, png
|
||||
format = mp4
|
||||
|
||||
# Transparent background
|
||||
transparent = False
|
||||
|
||||
# Progress bar: display, leave, none
|
||||
progress_bar = display
|
||||
```
|
||||
|
||||
### Rendering Section
|
||||
|
||||
```ini
|
||||
[renderer]
|
||||
# Background color (hex or color name)
|
||||
background_color = #1e1e1e
|
||||
|
||||
# Renderer type: cairo, opengl
|
||||
renderer = cairo
|
||||
```
|
||||
|
||||
### Resolution
|
||||
|
||||
```ini
|
||||
[CLI]
|
||||
# Frame dimensions
|
||||
pixel_width = 1920
|
||||
pixel_height = 1080
|
||||
```
|
||||
|
||||
## Programmatic Configuration
|
||||
|
||||
Access and modify config in your Python code:
|
||||
|
||||
```python
|
||||
# Access config values
|
||||
config.pixel_width # e.g., 1920
|
||||
config.frame_rate # e.g., 30
|
||||
config.background_color # e.g., BLACK
|
||||
|
||||
# Modify config (before creating scenes)
|
||||
config.pixel_width = 1920
|
||||
config.pixel_height = 1080
|
||||
config.frame_rate = 60
|
||||
config.background_color = BLUE_E
|
||||
```
|
||||
|
||||
### In Scene
|
||||
|
||||
```python
|
||||
class MyScene(Scene):
|
||||
def construct(self):
|
||||
# Access frame dimensions
|
||||
width = config.frame_width
|
||||
height = config.frame_height
|
||||
|
||||
# Create rectangle matching frame size
|
||||
frame_rect = Rectangle(
|
||||
width=width,
|
||||
height=height,
|
||||
stroke_color=WHITE
|
||||
)
|
||||
self.add(frame_rect)
|
||||
```
|
||||
|
||||
## Background Color
|
||||
|
||||
### In Config File
|
||||
|
||||
```ini
|
||||
[renderer]
|
||||
background_color = BLACK
|
||||
# Or hex color
|
||||
background_color = #1a1a2e
|
||||
```
|
||||
|
||||
### In Code
|
||||
|
||||
```python
|
||||
class DarkBackground(Scene):
|
||||
def construct(self):
|
||||
self.camera.background_color = "#1a1a2e"
|
||||
# ... rest of scene
|
||||
```
|
||||
|
||||
## Output Directory Structure
|
||||
|
||||
Default media directory structure:
|
||||
```
|
||||
media/
|
||||
├── videos/
|
||||
│ └── scene_file/
|
||||
│ ├── 480p15/ # Low quality
|
||||
│ ├── 720p30/ # Medium quality
|
||||
│ ├── 1080p60/ # High quality
|
||||
│ └── 2160p60/ # 4K quality
|
||||
├── images/
|
||||
│ └── scene_file/
|
||||
│ └── SceneName.png
|
||||
└── Tex/ # LaTeX cache
|
||||
```
|
||||
|
||||
### Custom Output Directory
|
||||
|
||||
```ini
|
||||
[output]
|
||||
media_dir = ./output
|
||||
```
|
||||
|
||||
Or via CLI:
|
||||
```bash
|
||||
manim --media_dir ./output file.py Scene
|
||||
```
|
||||
|
||||
## Tex Configuration
|
||||
|
||||
For LaTeX rendering:
|
||||
|
||||
```ini
|
||||
[tex]
|
||||
# Custom preamble
|
||||
preamble = \usepackage{amsmath}\usepackage{amssymb}
|
||||
|
||||
# Tex compiler
|
||||
tex_compiler = latex
|
||||
```
|
||||
|
||||
## Caching
|
||||
|
||||
```ini
|
||||
[CLI]
|
||||
# Disable caching (useful for debugging)
|
||||
disable_caching = True
|
||||
|
||||
# Max cached files
|
||||
max_files_cached = 100
|
||||
```
|
||||
|
||||
## Viewing Current Config
|
||||
|
||||
```bash
|
||||
# Show all config values
|
||||
manim cfg show
|
||||
|
||||
# Show specific section
|
||||
manim cfg show CLI
|
||||
|
||||
# Write current config to file
|
||||
manim cfg write
|
||||
```
|
||||
|
||||
## Project-Specific Config
|
||||
|
||||
Create `manim.cfg` in your project root:
|
||||
|
||||
```ini
|
||||
[CLI]
|
||||
quality = high_quality
|
||||
preview = True
|
||||
frame_rate = 60
|
||||
|
||||
[renderer]
|
||||
background_color = #0d1117
|
||||
|
||||
[output]
|
||||
media_dir = ./renders
|
||||
```
|
||||
|
||||
## Plugins
|
||||
|
||||
Manim has an extensible plugin system:
|
||||
|
||||
```bash
|
||||
# List installed plugins
|
||||
manim plugins -l
|
||||
|
||||
# Install a plugin
|
||||
pip install manim-pluginname
|
||||
```
|
||||
|
||||
Enable plugins in `manim.cfg`:
|
||||
|
||||
```ini
|
||||
[CLI]
|
||||
plugins = manim-pluginname
|
||||
# For multiple plugins:
|
||||
plugins = plugin1,plugin2
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use manim.cfg for project defaults** - Consistent settings across team
|
||||
2. **Keep quality low during development** - Faster iteration
|
||||
3. **Set background_color in config** - Not in every scene
|
||||
4. **Use custom media_dir** - Keep renders organized
|
||||
5. **Commit manim.cfg to version control** - Share settings with collaborators
|
||||
@@ -0,0 +1,158 @@
|
||||
---
|
||||
name: creation-animations
|
||||
description: Create, Write, FadeIn, DrawBorderThenFill and other creation animations
|
||||
metadata:
|
||||
tags: create, write, fadein, fadeout, grow, shrink, uncreate
|
||||
---
|
||||
|
||||
# Creation Animations
|
||||
|
||||
Animations that introduce mobjects to the scene.
|
||||
|
||||
## Create
|
||||
|
||||
Draws a VMobject progressively along its path.
|
||||
|
||||
```python
|
||||
from manim import *
|
||||
|
||||
class CreateExample(Scene):
|
||||
def construct(self):
|
||||
circle = Circle()
|
||||
self.play(Create(circle))
|
||||
```
|
||||
|
||||
Best for: Geometric shapes, lines, arrows.
|
||||
|
||||
## Write
|
||||
|
||||
Simulates handwriting. Best for text and equations.
|
||||
|
||||
```python
|
||||
class WriteExample(Scene):
|
||||
def construct(self):
|
||||
text = Text("Hello World")
|
||||
equation = MathTex(r"E = mc^2")
|
||||
|
||||
self.play(Write(text))
|
||||
self.wait()
|
||||
self.play(Write(equation))
|
||||
```
|
||||
|
||||
Write automatically sets appropriate timing based on text length.
|
||||
|
||||
## DrawBorderThenFill
|
||||
|
||||
Draws the outline first, then fills in the shape.
|
||||
|
||||
```python
|
||||
class DrawBorderExample(Scene):
|
||||
def construct(self):
|
||||
square = Square(fill_opacity=0.8, color=BLUE)
|
||||
self.play(DrawBorderThenFill(square))
|
||||
```
|
||||
|
||||
Best for: Shapes with fills where you want to emphasize the outline first.
|
||||
|
||||
## FadeIn / FadeOut
|
||||
|
||||
Simple opacity transitions.
|
||||
|
||||
```python
|
||||
class FadeExample(Scene):
|
||||
def construct(self):
|
||||
circle = Circle()
|
||||
|
||||
# Fade in
|
||||
self.play(FadeIn(circle))
|
||||
self.wait()
|
||||
|
||||
# Fade out
|
||||
self.play(FadeOut(circle))
|
||||
```
|
||||
|
||||
### Directional Fades
|
||||
|
||||
```python
|
||||
# Fade in from a direction
|
||||
self.play(FadeIn(square, shift=UP)) # Fade in while moving up
|
||||
self.play(FadeIn(square, shift=LEFT)) # Fade in from right
|
||||
|
||||
# Fade out to a direction
|
||||
self.play(FadeOut(square, shift=DOWN)) # Fade out while moving down
|
||||
```
|
||||
|
||||
### Scale Fades
|
||||
|
||||
```python
|
||||
self.play(FadeIn(circle, scale=0.5)) # Fade in while growing
|
||||
self.play(FadeOut(circle, scale=2)) # Fade out while shrinking
|
||||
```
|
||||
|
||||
## GrowFromCenter / ShrinkToCenter
|
||||
|
||||
```python
|
||||
class GrowExample(Scene):
|
||||
def construct(self):
|
||||
circle = Circle()
|
||||
|
||||
self.play(GrowFromCenter(circle))
|
||||
self.wait()
|
||||
self.play(ShrinkToCenter(circle))
|
||||
```
|
||||
|
||||
## GrowFromPoint
|
||||
|
||||
Grow from a specific point.
|
||||
|
||||
```python
|
||||
self.play(GrowFromPoint(circle, ORIGIN))
|
||||
self.play(GrowFromPoint(circle, LEFT * 3))
|
||||
```
|
||||
|
||||
## GrowFromEdge
|
||||
|
||||
Grow from a specific edge.
|
||||
|
||||
```python
|
||||
self.play(GrowFromEdge(square, LEFT)) # Grow from left edge
|
||||
self.play(GrowFromEdge(square, DOWN)) # Grow from bottom edge
|
||||
```
|
||||
|
||||
## SpinInFromNothing
|
||||
|
||||
Object spins in while growing.
|
||||
|
||||
```python
|
||||
self.play(SpinInFromNothing(circle))
|
||||
```
|
||||
|
||||
## Uncreate
|
||||
|
||||
Reverse of Create - erases the mobject.
|
||||
|
||||
```python
|
||||
self.play(Create(circle))
|
||||
self.wait()
|
||||
self.play(Uncreate(circle)) # Erases in reverse
|
||||
```
|
||||
|
||||
## AddTextLetterByLetter
|
||||
|
||||
Types text one character at a time.
|
||||
|
||||
```python
|
||||
class TypingExample(Scene):
|
||||
def construct(self):
|
||||
text = Text("Hello World")
|
||||
self.play(AddTextLetterByLetter(text, time_per_char=0.1))
|
||||
```
|
||||
|
||||
Note: Only works with `Text`, not `MathTex`.
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use Write for text** - Looks more natural than Create
|
||||
2. **Use Create for shapes** - Clean progressive drawing
|
||||
3. **Use FadeIn for quick introductions** - When drawing isn't important
|
||||
4. **Match removal to creation** - If you Create, use Uncreate; if FadeIn, use FadeOut
|
||||
@@ -0,0 +1,233 @@
|
||||
---
|
||||
name: graphing
|
||||
description: Plotting functions, parametric curves, and data visualization
|
||||
metadata:
|
||||
tags: plot, graph, function, parametric, curve, data
|
||||
---
|
||||
|
||||
# Graphing Functions
|
||||
|
||||
Plot mathematical functions and curves.
|
||||
|
||||
## Plotting Functions on Axes
|
||||
|
||||
```python
|
||||
from manim import *
|
||||
|
||||
class BasicPlot(Scene):
|
||||
def construct(self):
|
||||
axes = Axes(x_range=[-3, 3], y_range=[-2, 8])
|
||||
|
||||
# Plot a function
|
||||
graph = axes.plot(lambda x: x**2, color=BLUE)
|
||||
|
||||
self.add(axes, graph)
|
||||
```
|
||||
|
||||
## plot() Parameters
|
||||
|
||||
```python
|
||||
class PlotParameters(Scene):
|
||||
def construct(self):
|
||||
axes = Axes(x_range=[-5, 5], y_range=[-2, 2])
|
||||
|
||||
graph = axes.plot(
|
||||
lambda x: np.sin(x),
|
||||
x_range=[-PI, PI], # Limit domain
|
||||
color=YELLOW,
|
||||
stroke_width=4,
|
||||
)
|
||||
|
||||
self.add(axes, graph)
|
||||
```
|
||||
|
||||
## Multiple Functions
|
||||
|
||||
```python
|
||||
class MultiplePlots(Scene):
|
||||
def construct(self):
|
||||
axes = Axes(x_range=[-3, 3], y_range=[-2, 10])
|
||||
|
||||
sin_graph = axes.plot(lambda x: np.sin(x), color=BLUE)
|
||||
cos_graph = axes.plot(lambda x: np.cos(x), color=RED)
|
||||
quad_graph = axes.plot(lambda x: x**2, color=GREEN)
|
||||
|
||||
self.add(axes, sin_graph, cos_graph, quad_graph)
|
||||
```
|
||||
|
||||
## Adding Labels to Graphs
|
||||
|
||||
```python
|
||||
class GraphLabels(Scene):
|
||||
def construct(self):
|
||||
axes = Axes(x_range=[-3, 3], y_range=[-2, 10])
|
||||
graph = axes.plot(lambda x: x**2, color=BLUE)
|
||||
|
||||
# Add label to graph
|
||||
label = axes.get_graph_label(
|
||||
graph,
|
||||
label=MathTex("y = x^2"),
|
||||
x_val=2,
|
||||
direction=UR
|
||||
)
|
||||
|
||||
self.add(axes, graph, label)
|
||||
```
|
||||
|
||||
## Parametric Curves
|
||||
|
||||
Plot curves defined by parametric equations.
|
||||
|
||||
```python
|
||||
class ParametricExample(Scene):
|
||||
def construct(self):
|
||||
axes = Axes(x_range=[-3, 3], y_range=[-3, 3])
|
||||
|
||||
# Circle: x = cos(t), y = sin(t)
|
||||
curve = axes.plot_parametric_curve(
|
||||
lambda t: np.array([np.cos(t), np.sin(t), 0]),
|
||||
t_range=[0, 2 * PI],
|
||||
color=YELLOW
|
||||
)
|
||||
|
||||
self.add(axes, curve)
|
||||
```
|
||||
|
||||
### Parametric Curve Examples
|
||||
|
||||
```python
|
||||
# Lissajous curve
|
||||
curve = axes.plot_parametric_curve(
|
||||
lambda t: np.array([np.sin(3*t), np.sin(2*t), 0]),
|
||||
t_range=[0, 2*PI],
|
||||
)
|
||||
|
||||
# Spiral
|
||||
curve = axes.plot_parametric_curve(
|
||||
lambda t: np.array([t*np.cos(t), t*np.sin(t), 0]),
|
||||
t_range=[0, 4*PI],
|
||||
)
|
||||
|
||||
# Heart curve
|
||||
curve = axes.plot_parametric_curve(
|
||||
lambda t: np.array([
|
||||
16 * np.sin(t)**3,
|
||||
13*np.cos(t) - 5*np.cos(2*t) - 2*np.cos(3*t) - np.cos(4*t),
|
||||
0
|
||||
]) / 10,
|
||||
t_range=[0, 2*PI],
|
||||
)
|
||||
```
|
||||
|
||||
## ParametricFunction (standalone)
|
||||
|
||||
Create parametric curves without axes:
|
||||
|
||||
```python
|
||||
class StandaloneParametric(Scene):
|
||||
def construct(self):
|
||||
curve = ParametricFunction(
|
||||
lambda t: np.array([np.cos(t), np.sin(t), 0]),
|
||||
t_range=[0, 2*PI],
|
||||
color=BLUE
|
||||
)
|
||||
self.add(curve)
|
||||
```
|
||||
|
||||
## Area Under Curve
|
||||
|
||||
```python
|
||||
class AreaUnderCurve(Scene):
|
||||
def construct(self):
|
||||
axes = Axes(x_range=[-1, 5], y_range=[-1, 10])
|
||||
graph = axes.plot(lambda x: x**2, x_range=[0, 3], color=BLUE)
|
||||
|
||||
# Shade area under curve
|
||||
area = axes.get_area(
|
||||
graph,
|
||||
x_range=[0, 2],
|
||||
color=BLUE,
|
||||
opacity=0.5
|
||||
)
|
||||
|
||||
self.add(axes, graph, area)
|
||||
```
|
||||
|
||||
## Riemann Rectangles
|
||||
|
||||
```python
|
||||
class RiemannRectangles(Scene):
|
||||
def construct(self):
|
||||
axes = Axes(x_range=[-1, 5], y_range=[-1, 10])
|
||||
graph = axes.plot(lambda x: x**2, color=BLUE)
|
||||
|
||||
rects = axes.get_riemann_rectangles(
|
||||
graph,
|
||||
x_range=[0, 3],
|
||||
dx=0.5,
|
||||
color=YELLOW,
|
||||
stroke_width=1
|
||||
)
|
||||
|
||||
self.add(axes, graph, rects)
|
||||
```
|
||||
|
||||
## Animated Graphing
|
||||
|
||||
```python
|
||||
class AnimatedGraph(Scene):
|
||||
def construct(self):
|
||||
axes = Axes(x_range=[-3, 3], y_range=[-2, 2])
|
||||
self.add(axes)
|
||||
|
||||
graph = axes.plot(lambda x: np.sin(x), color=BLUE)
|
||||
|
||||
# Animate the graph being drawn
|
||||
self.play(Create(graph), run_time=3)
|
||||
```
|
||||
|
||||
## Moving Point on Graph
|
||||
|
||||
```python
|
||||
class MovingPointOnGraph(Scene):
|
||||
def construct(self):
|
||||
axes = Axes(x_range=[-3, 3], y_range=[-2, 2])
|
||||
graph = axes.plot(lambda x: np.sin(x), color=BLUE)
|
||||
|
||||
# Point that follows graph
|
||||
x_tracker = ValueTracker(-3)
|
||||
|
||||
dot = always_redraw(lambda: Dot(
|
||||
axes.i2gp(x_tracker.get_value(), graph),
|
||||
color=YELLOW
|
||||
))
|
||||
|
||||
self.add(axes, graph, dot)
|
||||
self.play(x_tracker.animate.set_value(3), run_time=4)
|
||||
```
|
||||
|
||||
## 3D Surface Plots
|
||||
|
||||
```python
|
||||
class SurfacePlot(ThreeDScene):
|
||||
def construct(self):
|
||||
axes = ThreeDAxes()
|
||||
|
||||
surface = axes.plot_surface(
|
||||
lambda u, v: np.sin(u) * np.cos(v),
|
||||
u_range=[-PI, PI],
|
||||
v_range=[-PI, PI],
|
||||
colorscale=[BLUE, GREEN, YELLOW],
|
||||
)
|
||||
|
||||
self.set_camera_orientation(phi=75*DEGREES, theta=-45*DEGREES)
|
||||
self.add(axes, surface)
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Set x_range on plot for discontinuities** - Avoid graphing undefined regions
|
||||
2. **Use get_graph_label for clarity** - Label functions on the graph
|
||||
3. **Match graph color to concept** - Consistent color coding
|
||||
4. **Use i2gp for points on graphs** - Automatically handles conversion
|
||||
5. **Animate graph creation** - More engaging than static display
|
||||
@@ -0,0 +1,220 @@
|
||||
---
|
||||
name: grouping
|
||||
description: VGroup, Group, arrange, and layout patterns
|
||||
metadata:
|
||||
tags: vgroup, group, arrange, layout, grid, submobjects
|
||||
---
|
||||
|
||||
# Grouping Mobjects
|
||||
|
||||
Organize multiple mobjects into groups for collective manipulation.
|
||||
|
||||
## VGroup
|
||||
|
||||
VGroup (Vectorized Group) is for grouping VMobjects. Most commonly used.
|
||||
|
||||
```python
|
||||
from manim import *
|
||||
|
||||
class VGroupExample(Scene):
|
||||
def construct(self):
|
||||
# Create a group
|
||||
group = VGroup(
|
||||
Circle(),
|
||||
Square(),
|
||||
Triangle()
|
||||
)
|
||||
|
||||
# Operations apply to all members
|
||||
group.set_color(RED)
|
||||
group.shift(UP)
|
||||
|
||||
self.add(group)
|
||||
```
|
||||
|
||||
## Group
|
||||
|
||||
Group is for mixing different mobject types (VMobjects, ImageMobjects, etc.).
|
||||
|
||||
```python
|
||||
class GroupExample(Scene):
|
||||
def construct(self):
|
||||
# Mix different types
|
||||
text = Text("Hello")
|
||||
group = Group(
|
||||
Circle(),
|
||||
text
|
||||
)
|
||||
self.add(group)
|
||||
```
|
||||
|
||||
## Creating Groups
|
||||
|
||||
```python
|
||||
# From individual mobjects
|
||||
group = VGroup(circle, square, triangle)
|
||||
|
||||
# From a list
|
||||
shapes = [Circle(), Square(), Triangle()]
|
||||
group = VGroup(*shapes)
|
||||
|
||||
# Using list comprehension
|
||||
group = VGroup(*[Circle() for _ in range(5)])
|
||||
|
||||
# Empty group, add later
|
||||
group = VGroup()
|
||||
group.add(Circle())
|
||||
group.add(Square())
|
||||
```
|
||||
|
||||
## arrange
|
||||
|
||||
Arrange mobjects in a line.
|
||||
|
||||
```python
|
||||
class ArrangeExample(Scene):
|
||||
def construct(self):
|
||||
# Horizontal arrangement (default)
|
||||
row = VGroup(*[Circle().scale(0.3) for _ in range(5)])
|
||||
row.arrange(RIGHT, buff=0.5).shift(UP * 2)
|
||||
|
||||
# Vertical arrangement
|
||||
column = VGroup(*[Square().scale(0.3) for _ in range(4)])
|
||||
column.arrange(DOWN, buff=0.5).shift(LEFT * 2)
|
||||
|
||||
# With custom buffer
|
||||
spaced = VGroup(*[Triangle().scale(0.3) for _ in range(3)])
|
||||
spaced.arrange(RIGHT, buff=1).shift(DOWN * 2)
|
||||
|
||||
self.add(row, column, spaced)
|
||||
```
|
||||
|
||||
### Direction Options
|
||||
```python
|
||||
group.arrange(RIGHT) # Left to right
|
||||
group.arrange(LEFT) # Right to left
|
||||
group.arrange(UP) # Bottom to top
|
||||
group.arrange(DOWN) # Top to bottom
|
||||
```
|
||||
|
||||
## arrange_in_grid
|
||||
|
||||
Arrange in a grid pattern.
|
||||
|
||||
```python
|
||||
class GridExample(Scene):
|
||||
def construct(self):
|
||||
# Auto grid
|
||||
grid = VGroup(*[Square().scale(0.3) for _ in range(20)])
|
||||
grid.arrange_in_grid()
|
||||
|
||||
# Specify rows and columns
|
||||
grid = VGroup(*[Circle().scale(0.2) for _ in range(12)])
|
||||
grid.arrange_in_grid(rows=3, cols=4)
|
||||
|
||||
# With spacing
|
||||
grid.arrange_in_grid(rows=3, cols=4, buff=0.5)
|
||||
|
||||
self.add(grid)
|
||||
```
|
||||
|
||||
## Accessing Group Members
|
||||
|
||||
```python
|
||||
group = VGroup(Circle(), Square(), Triangle())
|
||||
|
||||
# By index
|
||||
first = group[0] # Circle
|
||||
second = group[1] # Square
|
||||
last = group[-1] # Triangle
|
||||
|
||||
# Slicing
|
||||
first_two = group[0:2] # VGroup with Circle and Square
|
||||
|
||||
# Iteration
|
||||
for mob in group:
|
||||
mob.set_color(random_color())
|
||||
|
||||
# Length
|
||||
num_items = len(group)
|
||||
```
|
||||
|
||||
## Modifying Groups
|
||||
|
||||
```python
|
||||
group = VGroup(Circle(), Square())
|
||||
|
||||
# Add mobjects
|
||||
group.add(Triangle())
|
||||
group.add(Star(), Pentagon())
|
||||
|
||||
# Remove mobjects
|
||||
group.remove(circle)
|
||||
|
||||
# Insert at position
|
||||
group.insert(0, new_mobject)
|
||||
|
||||
# Submobjects list
|
||||
group.submobjects # List of all children
|
||||
```
|
||||
|
||||
## Group Transformations
|
||||
|
||||
```python
|
||||
group = VGroup(Circle(), Square(), Triangle()).arrange(RIGHT)
|
||||
|
||||
# All transformations apply to entire group
|
||||
group.shift(UP * 2)
|
||||
group.scale(0.5)
|
||||
group.rotate(PI / 4)
|
||||
group.set_color(BLUE)
|
||||
|
||||
# But can target individuals
|
||||
group[0].set_color(RED) # Just the circle
|
||||
```
|
||||
|
||||
## Nested Groups
|
||||
|
||||
```python
|
||||
class NestedGroups(Scene):
|
||||
def construct(self):
|
||||
# Create sub-groups
|
||||
row1 = VGroup(*[Circle() for _ in range(3)]).arrange(RIGHT)
|
||||
row2 = VGroup(*[Square() for _ in range(3)]).arrange(RIGHT)
|
||||
row3 = VGroup(*[Triangle() for _ in range(3)]).arrange(RIGHT)
|
||||
|
||||
# Group of groups
|
||||
all_rows = VGroup(row1, row2, row3).arrange(DOWN)
|
||||
|
||||
self.add(all_rows)
|
||||
```
|
||||
|
||||
## Useful Group Methods
|
||||
|
||||
```python
|
||||
group = VGroup(Circle(), Square(), Triangle())
|
||||
|
||||
# Get bounding box info
|
||||
group.get_center()
|
||||
group.get_width()
|
||||
group.get_height()
|
||||
|
||||
# Set position for whole group
|
||||
group.move_to(ORIGIN)
|
||||
group.to_edge(LEFT)
|
||||
|
||||
# Copy entire group
|
||||
group_copy = group.copy()
|
||||
|
||||
# Match layout of another group
|
||||
group1.match_height(group2)
|
||||
group1.match_width(group2)
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use VGroup for VMobjects** - Better performance and compatibility
|
||||
2. **Use arrange after creating** - Don't position individually then group
|
||||
3. **Name your groups semantically** - `equation_parts` not `group1`
|
||||
4. **Use nested groups for structure** - Rows within columns, etc.
|
||||
5. **Copy groups when needed** - Avoid unintended modifications
|
||||
@@ -0,0 +1,202 @@
|
||||
---
|
||||
name: latex
|
||||
description: MathTex, Tex, LaTeX rendering and equation styling in Manim
|
||||
metadata:
|
||||
tags: latex, mathtex, tex, equation, formula, math
|
||||
---
|
||||
|
||||
# LaTeX in Manim
|
||||
|
||||
Manim uses LaTeX to render mathematical expressions and formatted text.
|
||||
|
||||
## MathTex vs Tex
|
||||
|
||||
- **MathTex**: Automatically wraps content in math mode (`align*` environment)
|
||||
- **Tex**: Raw LaTeX - you control the mode
|
||||
|
||||
```python
|
||||
from manim import *
|
||||
|
||||
class LaTeXComparison(Scene):
|
||||
def construct(self):
|
||||
# MathTex - auto math mode
|
||||
math = MathTex(r"E = mc^2")
|
||||
|
||||
# Tex - need explicit math delimiters
|
||||
tex = Tex(r"$E = mc^2$")
|
||||
|
||||
# Both render the same
|
||||
VGroup(math, tex).arrange(DOWN)
|
||||
self.add(math, tex)
|
||||
```
|
||||
|
||||
## Basic MathTex
|
||||
|
||||
```python
|
||||
class MathTexExample(Scene):
|
||||
def construct(self):
|
||||
# Simple equation
|
||||
eq1 = MathTex(r"x^2 + y^2 = z^2")
|
||||
|
||||
# Fractions
|
||||
eq2 = MathTex(r"\frac{a}{b}")
|
||||
|
||||
# Square roots
|
||||
eq3 = MathTex(r"\sqrt{2}")
|
||||
|
||||
# Greek letters
|
||||
eq4 = MathTex(r"\alpha + \beta = \gamma")
|
||||
|
||||
# Integrals
|
||||
eq5 = MathTex(r"\int_0^\infty e^{-x} dx")
|
||||
|
||||
# Summations
|
||||
eq6 = MathTex(r"\sum_{n=1}^{\infty} \frac{1}{n^2}")
|
||||
|
||||
equations = VGroup(eq1, eq2, eq3, eq4, eq5, eq6).arrange_in_grid(2, 3)
|
||||
self.add(equations)
|
||||
```
|
||||
|
||||
## Coloring Parts of Equations
|
||||
|
||||
### Using set_color_by_tex
|
||||
|
||||
```python
|
||||
class ColoredEquation(Scene):
|
||||
def construct(self):
|
||||
eq = MathTex(r"e^{i\pi} + 1 = 0")
|
||||
eq.set_color_by_tex("e", RED)
|
||||
eq.set_color_by_tex(r"\pi", BLUE)
|
||||
eq.set_color_by_tex("i", GREEN)
|
||||
self.add(eq)
|
||||
```
|
||||
|
||||
### Using substrings_to_isolate
|
||||
|
||||
For precise coloring, isolate substrings first:
|
||||
|
||||
```python
|
||||
class IsolatedColoring(Scene):
|
||||
def construct(self):
|
||||
eq = MathTex(
|
||||
r"e^x = x^0 + x^1 + \frac{1}{2}x^2 + \cdots",
|
||||
substrings_to_isolate=["x"]
|
||||
)
|
||||
eq.set_color_by_tex("x", YELLOW)
|
||||
self.add(eq)
|
||||
```
|
||||
|
||||
### Using index_labels for debugging
|
||||
|
||||
```python
|
||||
class DebugLabels(Scene):
|
||||
def construct(self):
|
||||
eq = MathTex(r"\frac{a}{b}")
|
||||
# Add index labels to see which index is which part
|
||||
self.add(index_labels(eq[0]))
|
||||
self.add(eq)
|
||||
```
|
||||
|
||||
### Direct indexing
|
||||
|
||||
```python
|
||||
eq = MathTex(r"a + b = c")
|
||||
eq[0][0].set_color(RED) # 'a'
|
||||
eq[0][2].set_color(BLUE) # 'b'
|
||||
eq[0][4].set_color(GREEN) # 'c'
|
||||
```
|
||||
|
||||
## Multi-part Equations
|
||||
|
||||
Split equations into parts for individual control:
|
||||
|
||||
```python
|
||||
class MultiPartEquation(Scene):
|
||||
def construct(self):
|
||||
eq = MathTex("a", "^2", "+", "b", "^2", "=", "c", "^2")
|
||||
|
||||
eq[0].set_color(RED) # a
|
||||
eq[3].set_color(BLUE) # b
|
||||
eq[6].set_color(GREEN) # c
|
||||
|
||||
self.play(Write(eq))
|
||||
```
|
||||
|
||||
## Text with Math (Tex)
|
||||
|
||||
```python
|
||||
class MixedContent(Scene):
|
||||
def construct(self):
|
||||
# Mix text and math
|
||||
tex = Tex(r"The area is $A = \pi r^2$")
|
||||
self.play(Write(tex))
|
||||
```
|
||||
|
||||
## Custom LaTeX Packages
|
||||
|
||||
```python
|
||||
class CustomPackage(Scene):
|
||||
def construct(self):
|
||||
template = TexTemplate()
|
||||
template.add_to_preamble(r"\usepackage{mathrsfs}")
|
||||
|
||||
eq = Tex(
|
||||
r"$\mathscr{L}$",
|
||||
tex_template=template
|
||||
)
|
||||
self.add(eq)
|
||||
```
|
||||
|
||||
## Equation Alignment
|
||||
|
||||
```python
|
||||
class AlignedEquations(Scene):
|
||||
def construct(self):
|
||||
eqs = MathTex(
|
||||
r"a &= b + c \\",
|
||||
r"d &= e + f + g \\",
|
||||
r"h &= i"
|
||||
)
|
||||
self.add(eqs)
|
||||
```
|
||||
|
||||
## Common LaTeX Symbols
|
||||
|
||||
```python
|
||||
# Greek letters
|
||||
MathTex(r"\alpha \beta \gamma \delta \epsilon")
|
||||
MathTex(r"\Gamma \Delta \Theta \Lambda \Pi")
|
||||
|
||||
# Operators
|
||||
MathTex(r"\times \div \pm \mp \cdot")
|
||||
|
||||
# Relations
|
||||
MathTex(r"\leq \geq \neq \approx \equiv")
|
||||
|
||||
# Arrows
|
||||
MathTex(r"\rightarrow \leftarrow \Rightarrow \Leftrightarrow")
|
||||
|
||||
# Sets
|
||||
MathTex(r"\in \notin \subset \supset \cup \cap")
|
||||
|
||||
# Calculus
|
||||
MathTex(r"\int \iint \oint \partial \nabla")
|
||||
```
|
||||
|
||||
## Font Size
|
||||
|
||||
```python
|
||||
# Using font_size parameter
|
||||
eq = MathTex(r"E = mc^2", font_size=72)
|
||||
|
||||
# Using scale
|
||||
eq = MathTex(r"E = mc^2").scale(2)
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use raw strings** - Always use `r"..."` for LaTeX
|
||||
2. **Use MathTex for pure math** - Simpler than adding `$...$`
|
||||
3. **Use Tex for mixed content** - When combining text and math
|
||||
4. **Split for animation control** - Separate parts you'll animate differently
|
||||
5. **Use substrings_to_isolate** - For reliable coloring of repeated elements
|
||||
@@ -0,0 +1,241 @@
|
||||
---
|
||||
name: lines
|
||||
description: Line, Arrow, Vector, DashedLine and connectors
|
||||
metadata:
|
||||
tags: line, arrow, vector, dashedline, brace, connector
|
||||
---
|
||||
|
||||
# Lines and Arrows
|
||||
|
||||
Connect points and show relationships with lines and arrows.
|
||||
|
||||
## Line
|
||||
|
||||
Basic line between two points.
|
||||
|
||||
```python
|
||||
from manim import *
|
||||
|
||||
class LineExample(Scene):
|
||||
def construct(self):
|
||||
# Line from two points
|
||||
line = Line(LEFT * 2, RIGHT * 2)
|
||||
|
||||
# With styling
|
||||
styled_line = Line(
|
||||
UP * 2, DOWN * 2,
|
||||
color=BLUE,
|
||||
stroke_width=4
|
||||
)
|
||||
|
||||
self.add(line, styled_line)
|
||||
```
|
||||
|
||||
### Line Properties
|
||||
|
||||
```python
|
||||
line = Line(LEFT, RIGHT)
|
||||
|
||||
# Get points
|
||||
line.get_start()
|
||||
line.get_end()
|
||||
line.get_center()
|
||||
line.get_length()
|
||||
line.get_angle()
|
||||
|
||||
# Modify
|
||||
line.put_start_and_end_on(new_start, new_end)
|
||||
line.set_length(3) # Keep direction, change length
|
||||
```
|
||||
|
||||
## Arrow
|
||||
|
||||
Line with an arrowhead.
|
||||
|
||||
```python
|
||||
class ArrowExample(Scene):
|
||||
def construct(self):
|
||||
# Basic arrow
|
||||
arrow = Arrow(LEFT * 2, RIGHT * 2)
|
||||
|
||||
# Styled arrow
|
||||
styled = Arrow(
|
||||
start=UP,
|
||||
end=DOWN,
|
||||
color=RED,
|
||||
stroke_width=6,
|
||||
tip_length=0.4,
|
||||
max_tip_length_to_length_ratio=0.5
|
||||
)
|
||||
|
||||
self.add(arrow, styled)
|
||||
```
|
||||
|
||||
### Arrow Variations
|
||||
|
||||
```python
|
||||
# Double-headed arrow
|
||||
double = DoubleArrow(LEFT * 2, RIGHT * 2)
|
||||
|
||||
# Arrow with custom tip
|
||||
arrow = Arrow(LEFT, RIGHT)
|
||||
arrow.tip # Access the tip mobject
|
||||
```
|
||||
|
||||
## Vector
|
||||
|
||||
Arrow starting from origin (useful for physics/math).
|
||||
|
||||
```python
|
||||
class VectorExample(Scene):
|
||||
def construct(self):
|
||||
# Vector from origin
|
||||
v1 = Vector([2, 1, 0], color=YELLOW)
|
||||
v2 = Vector([-1, 2, 0], color=GREEN)
|
||||
|
||||
self.add(v1, v2)
|
||||
```
|
||||
|
||||
## DashedLine
|
||||
|
||||
```python
|
||||
class DashedLineExample(Scene):
|
||||
def construct(self):
|
||||
dashed = DashedLine(
|
||||
LEFT * 2, RIGHT * 2,
|
||||
dash_length=0.2,
|
||||
dashed_ratio=0.5, # Ratio of dash to gap
|
||||
color=WHITE
|
||||
)
|
||||
self.add(dashed)
|
||||
```
|
||||
|
||||
## TangentLine
|
||||
|
||||
Line tangent to a curve at a point.
|
||||
|
||||
```python
|
||||
class TangentLineExample(Scene):
|
||||
def construct(self):
|
||||
circle = Circle(radius=2)
|
||||
|
||||
# Tangent at specific point (t parameter 0-1 along curve)
|
||||
tangent = TangentLine(circle, alpha=0.25, length=3, color=YELLOW)
|
||||
|
||||
self.add(circle, tangent)
|
||||
```
|
||||
|
||||
## Brace
|
||||
|
||||
Curly brace for highlighting.
|
||||
|
||||
```python
|
||||
class BraceExample(Scene):
|
||||
def construct(self):
|
||||
rect = Rectangle(width=4, height=1)
|
||||
|
||||
# Brace under the rectangle
|
||||
brace = Brace(rect, DOWN)
|
||||
|
||||
# With label
|
||||
brace_text = brace.get_text("Width")
|
||||
|
||||
# Alternative: BraceLabel
|
||||
brace_label = BraceLabel(rect, "Width", DOWN)
|
||||
|
||||
self.add(rect, brace, brace_text)
|
||||
```
|
||||
|
||||
### Brace Directions
|
||||
|
||||
```python
|
||||
brace_down = Brace(mobject, DOWN)
|
||||
brace_up = Brace(mobject, UP)
|
||||
brace_left = Brace(mobject, LEFT)
|
||||
brace_right = Brace(mobject, RIGHT)
|
||||
```
|
||||
|
||||
## CurvedArrow
|
||||
|
||||
Curved arrow between points.
|
||||
|
||||
```python
|
||||
class CurvedArrowExample(Scene):
|
||||
def construct(self):
|
||||
curved = CurvedArrow(
|
||||
start_point=LEFT * 2,
|
||||
end_point=RIGHT * 2,
|
||||
angle=PI/2 # Curvature
|
||||
)
|
||||
self.add(curved)
|
||||
```
|
||||
|
||||
## Elbow
|
||||
|
||||
Right-angle connector.
|
||||
|
||||
```python
|
||||
class ElbowExample(Scene):
|
||||
def construct(self):
|
||||
elbow = Elbow(width=2, angle=PI/2)
|
||||
self.add(elbow)
|
||||
```
|
||||
|
||||
## NumberLine Ticks
|
||||
|
||||
```python
|
||||
class TicksExample(Scene):
|
||||
def construct(self):
|
||||
line = NumberLine(x_range=[-3, 3, 1])
|
||||
self.add(line)
|
||||
```
|
||||
|
||||
## Connecting Mobjects
|
||||
|
||||
### Line Between Mobjects
|
||||
|
||||
```python
|
||||
class ConnectMobjects(Scene):
|
||||
def construct(self):
|
||||
c1 = Circle().shift(LEFT * 2)
|
||||
c2 = Circle().shift(RIGHT * 2)
|
||||
|
||||
# Line connecting centers
|
||||
line = Line(c1.get_center(), c2.get_center())
|
||||
|
||||
# Arrow between edges
|
||||
arrow = Arrow(
|
||||
c1.get_right(), # Right edge of c1
|
||||
c2.get_left(), # Left edge of c2
|
||||
buff=0.1 # Small gap from edges
|
||||
)
|
||||
|
||||
self.add(c1, c2, line, arrow)
|
||||
```
|
||||
|
||||
### Dynamic Connections with Updaters
|
||||
|
||||
```python
|
||||
class DynamicLine(Scene):
|
||||
def construct(self):
|
||||
dot1 = Dot(LEFT * 2)
|
||||
dot2 = Dot(RIGHT * 2)
|
||||
|
||||
# Line that follows dots
|
||||
line = always_redraw(lambda: Line(
|
||||
dot1.get_center(),
|
||||
dot2.get_center(),
|
||||
color=YELLOW
|
||||
))
|
||||
|
||||
self.add(dot1, dot2, line)
|
||||
self.play(dot1.animate.shift(UP * 2), run_time=2)
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use Arrow for direction** - Clearer than plain lines
|
||||
2. **Use Vector for physics/math** - Semantically meaningful
|
||||
3. **Use Brace for labeling dimensions** - Professional look
|
||||
4. **Use DashedLine for auxiliary lines** - Distinguishes from main content
|
||||
5. **Use always_redraw for dynamic lines** - Updates with moving endpoints
|
||||
@@ -0,0 +1,116 @@
|
||||
---
|
||||
name: mobjects
|
||||
description: Mobject types, VMobject, and the mobject hierarchy in Manim
|
||||
metadata:
|
||||
tags: mobject, vmobject, group, submobjects, hierarchy
|
||||
---
|
||||
|
||||
# Mobjects in Manim
|
||||
|
||||
Mobject (Mathematical Object) is the base class for all displayable objects in Manim.
|
||||
|
||||
## Mobject Hierarchy
|
||||
|
||||
```
|
||||
Mobject (base class)
|
||||
├── VMobject (Vectorized Mobject - most common)
|
||||
│ ├── Circle, Square, Rectangle, Polygon
|
||||
│ ├── Line, Arrow, Vector
|
||||
│ ├── Text, MathTex, Tex
|
||||
│ ├── Axes, NumberPlane
|
||||
│ └── VGroup
|
||||
├── ImageMobject (for images)
|
||||
├── PMobject (point clouds)
|
||||
└── Group (for non-VMobject collections)
|
||||
```
|
||||
|
||||
## VMobject (Vectorized Mobject)
|
||||
|
||||
Most shapes you'll use are VMobjects - they're defined by Bézier curves.
|
||||
|
||||
```python
|
||||
# Common VMobjects
|
||||
circle = Circle()
|
||||
square = Square()
|
||||
rect = Rectangle(width=4, height=2)
|
||||
triangle = Triangle()
|
||||
polygon = Polygon(ORIGIN, RIGHT, UP)
|
||||
line = Line(LEFT, RIGHT)
|
||||
arrow = Arrow(LEFT, RIGHT)
|
||||
```
|
||||
|
||||
## Creating Custom VMobjects
|
||||
|
||||
```python
|
||||
class CustomShape(VMobject):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
# Define points using set_points_as_corners or set_points_smoothly
|
||||
self.set_points_as_corners([
|
||||
LEFT, UP, RIGHT, DOWN, LEFT
|
||||
])
|
||||
```
|
||||
|
||||
## Mobject Properties
|
||||
|
||||
### Position and Size
|
||||
|
||||
```python
|
||||
mobject.get_center() # Returns center point
|
||||
mobject.get_width() # Returns width
|
||||
mobject.get_height() # Returns height
|
||||
mobject.get_top() # Top edge center point
|
||||
mobject.get_bottom() # Bottom edge center point
|
||||
mobject.get_left() # Left edge center point
|
||||
mobject.get_right() # Right edge center point
|
||||
```
|
||||
|
||||
### Bounding Box Corners
|
||||
|
||||
```python
|
||||
mobject.get_corner(UL) # Upper left corner
|
||||
mobject.get_corner(UR) # Upper right corner
|
||||
mobject.get_corner(DL) # Lower left corner
|
||||
mobject.get_corner(DR) # Lower right corner
|
||||
```
|
||||
|
||||
## Submobjects
|
||||
|
||||
Mobjects can contain other mobjects as submobjects.
|
||||
|
||||
```python
|
||||
# Access submobjects
|
||||
group = VGroup(Circle(), Square())
|
||||
group.submobjects # List of child mobjects
|
||||
group[0] # First submobject (Circle)
|
||||
group[1] # Second submobject (Square)
|
||||
|
||||
# Iterate over submobjects
|
||||
for mob in group:
|
||||
mob.set_color(RED)
|
||||
```
|
||||
|
||||
## Copying Mobjects
|
||||
|
||||
```python
|
||||
# Create a copy
|
||||
circle_copy = circle.copy()
|
||||
|
||||
# Copy and position
|
||||
circle_copy = circle.copy().shift(RIGHT * 2)
|
||||
```
|
||||
|
||||
## Method Chaining
|
||||
|
||||
Most mobject methods return `self`, allowing method chaining:
|
||||
|
||||
```python
|
||||
circle = Circle().set_color(RED).shift(LEFT).scale(2)
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use VMobject for custom shapes** - Better rendering and animation support
|
||||
2. **Prefer VGroup over Group** - VGroup works better with most animations
|
||||
3. **Use copy() when reusing** - Avoid unintended modifications to original
|
||||
4. **Chain methods for readability** - But break into lines if too long
|
||||
@@ -0,0 +1,211 @@
|
||||
---
|
||||
name: positioning
|
||||
description: move_to, next_to, align_to, shift and positioning methods
|
||||
metadata:
|
||||
tags: position, move_to, next_to, shift, align, layout
|
||||
---
|
||||
|
||||
# Positioning in Manim
|
||||
|
||||
Methods for placing and moving mobjects in the scene.
|
||||
|
||||
## Coordinate System
|
||||
|
||||
Manim uses a coordinate system where:
|
||||
- Origin (0, 0, 0) is at the center of the screen
|
||||
- X-axis: LEFT (-) to RIGHT (+)
|
||||
- Y-axis: DOWN (-) to UP (+)
|
||||
- Z-axis: IN (-) to OUT (+) (for 3D)
|
||||
|
||||
### Direction Constants
|
||||
```python
|
||||
UP = np.array([0, 1, 0])
|
||||
DOWN = np.array([0, -1, 0])
|
||||
LEFT = np.array([-1, 0, 0])
|
||||
RIGHT = np.array([1, 0, 0])
|
||||
ORIGIN = np.array([0, 0, 0])
|
||||
|
||||
# Diagonals
|
||||
UL = UP + LEFT # Upper left
|
||||
UR = UP + RIGHT # Upper right
|
||||
DL = DOWN + LEFT # Lower left
|
||||
DR = DOWN + RIGHT # Lower right
|
||||
```
|
||||
|
||||
## move_to
|
||||
|
||||
Move to an absolute position.
|
||||
|
||||
```python
|
||||
from manim import *
|
||||
|
||||
class MoveToExample(Scene):
|
||||
def construct(self):
|
||||
circle = Circle()
|
||||
|
||||
# Move to origin
|
||||
circle.move_to(ORIGIN)
|
||||
|
||||
# Move to specific coordinates
|
||||
circle.move_to(RIGHT * 2 + UP * 1)
|
||||
|
||||
# Move to another mobject's position
|
||||
square = Square().shift(LEFT * 2)
|
||||
circle.move_to(square)
|
||||
|
||||
# Move to a specific point of another mobject
|
||||
circle.move_to(square.get_top())
|
||||
```
|
||||
|
||||
## shift
|
||||
|
||||
Move relative to current position.
|
||||
|
||||
```python
|
||||
class ShiftExample(Scene):
|
||||
def construct(self):
|
||||
circle = Circle()
|
||||
|
||||
# Shift in one direction
|
||||
circle.shift(RIGHT)
|
||||
circle.shift(UP * 2)
|
||||
|
||||
# Shift in multiple directions
|
||||
circle.shift(RIGHT * 2 + UP * 1)
|
||||
|
||||
# Chain shifts
|
||||
circle.shift(LEFT).shift(DOWN)
|
||||
```
|
||||
|
||||
## next_to
|
||||
|
||||
Position relative to another mobject.
|
||||
|
||||
```python
|
||||
class NextToExample(Scene):
|
||||
def construct(self):
|
||||
square = Square()
|
||||
circle = Circle()
|
||||
triangle = Triangle()
|
||||
|
||||
# Place circle to the right of square
|
||||
circle.next_to(square, RIGHT)
|
||||
|
||||
# With buffer (spacing)
|
||||
triangle.next_to(square, DOWN, buff=0.5)
|
||||
|
||||
# Aligned to specific edge
|
||||
circle.next_to(square, RIGHT, aligned_edge=UP)
|
||||
```
|
||||
|
||||
### buff Parameter
|
||||
```python
|
||||
# Default buffer
|
||||
circle.next_to(square, RIGHT) # Uses DEFAULT_MOBJECT_TO_MOBJECT_BUFFER
|
||||
|
||||
# Custom buffer
|
||||
circle.next_to(square, RIGHT, buff=0) # No gap
|
||||
circle.next_to(square, RIGHT, buff=1) # 1 unit gap
|
||||
circle.next_to(square, RIGHT, buff=0.5) # Half unit gap
|
||||
```
|
||||
|
||||
## align_to
|
||||
|
||||
Align edges with another mobject.
|
||||
|
||||
```python
|
||||
class AlignToExample(Scene):
|
||||
def construct(self):
|
||||
square = Square().shift(LEFT)
|
||||
circle = Circle().shift(RIGHT)
|
||||
|
||||
# Align circle's left edge with square's left edge
|
||||
circle.align_to(square, LEFT)
|
||||
|
||||
# Align tops
|
||||
circle.align_to(square, UP)
|
||||
|
||||
# Align to a point
|
||||
circle.align_to(ORIGIN, DOWN)
|
||||
```
|
||||
|
||||
## Edge Methods
|
||||
|
||||
Position at screen edges.
|
||||
|
||||
```python
|
||||
class EdgeExample(Scene):
|
||||
def construct(self):
|
||||
# To screen edges
|
||||
text1 = Text("Top").to_edge(UP)
|
||||
text2 = Text("Bottom").to_edge(DOWN)
|
||||
text3 = Text("Left").to_edge(LEFT)
|
||||
text4 = Text("Right").to_edge(RIGHT)
|
||||
|
||||
# With buffer
|
||||
text5 = Text("Buffered").to_edge(UP, buff=1)
|
||||
```
|
||||
|
||||
## Corner Methods
|
||||
|
||||
Position at screen corners.
|
||||
|
||||
```python
|
||||
class CornerExample(Scene):
|
||||
def construct(self):
|
||||
t1 = Text("UL").to_corner(UL)
|
||||
t2 = Text("UR").to_corner(UR)
|
||||
t3 = Text("DL").to_corner(DL)
|
||||
t4 = Text("DR").to_corner(DR)
|
||||
|
||||
# With buffer
|
||||
t5 = Text("Buffered").to_corner(UL, buff=0.5)
|
||||
```
|
||||
|
||||
## center
|
||||
|
||||
Center on screen or another mobject.
|
||||
|
||||
```python
|
||||
mobject.center() # Center on screen
|
||||
mobject.center_on(other) # Center on another mobject (custom helper)
|
||||
```
|
||||
|
||||
## Getting Positions
|
||||
|
||||
```python
|
||||
circle = Circle()
|
||||
|
||||
# Get various points
|
||||
circle.get_center() # Center point
|
||||
circle.get_top() # Top edge center
|
||||
circle.get_bottom() # Bottom edge center
|
||||
circle.get_left() # Left edge center
|
||||
circle.get_right() # Right edge center
|
||||
circle.get_corner(UL) # Upper left corner
|
||||
circle.get_corner(DR) # Lower right corner
|
||||
circle.get_start() # Start of path
|
||||
circle.get_end() # End of path
|
||||
```
|
||||
|
||||
## Animated Positioning
|
||||
|
||||
```python
|
||||
class AnimatedPosition(Scene):
|
||||
def construct(self):
|
||||
square = Square()
|
||||
self.add(square)
|
||||
|
||||
# Animate movement
|
||||
self.play(square.animate.shift(RIGHT * 2))
|
||||
self.play(square.animate.move_to(UP * 2))
|
||||
self.play(square.animate.to_edge(LEFT))
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use next_to for relative positioning** - Maintains relationships
|
||||
2. **Use move_to for absolute positioning** - Precise coordinates
|
||||
3. **Use shift for relative adjustments** - Quick tweaks
|
||||
4. **Use to_edge/to_corner for screen positioning** - Responsive layouts
|
||||
5. **Adjust buff for visual spacing** - Don't let elements crowd
|
||||
@@ -0,0 +1,121 @@
|
||||
---
|
||||
name: scenes
|
||||
description: Scene structure, construct method, and scene types in Manim
|
||||
metadata:
|
||||
tags: scene, construct, setup, render, ThreeDScene, MovingCameraScene
|
||||
---
|
||||
|
||||
# Scenes in Manim
|
||||
|
||||
A Scene is the canvas where all animations take place. Every Manim animation is defined within a Scene class.
|
||||
|
||||
## Basic Scene Structure
|
||||
|
||||
All animation code resides within the `construct()` method of a Scene subclass.
|
||||
|
||||
```python
|
||||
from manim import *
|
||||
|
||||
class MyScene(Scene):
|
||||
def construct(self):
|
||||
circle = Circle()
|
||||
self.play(Create(circle))
|
||||
self.wait(1)
|
||||
```
|
||||
|
||||
## Scene Lifecycle Methods
|
||||
|
||||
### construct()
|
||||
The main method where you define your animation. Called automatically when rendering.
|
||||
|
||||
### setup()
|
||||
Called before `construct()`. Use for initialization that should happen before animation logic.
|
||||
|
||||
```python
|
||||
class MyScene(Scene):
|
||||
def setup(self):
|
||||
self.camera.background_color = BLUE_E
|
||||
|
||||
def construct(self):
|
||||
circle = Circle()
|
||||
self.play(Create(circle))
|
||||
```
|
||||
|
||||
## Scene Methods
|
||||
|
||||
### Adding and Removing Objects
|
||||
|
||||
```python
|
||||
# Add without animation (instant)
|
||||
self.add(mobject)
|
||||
self.add(mobject1, mobject2, mobject3)
|
||||
|
||||
# Remove without animation
|
||||
self.remove(mobject)
|
||||
|
||||
# Clear all mobjects
|
||||
self.clear()
|
||||
```
|
||||
|
||||
### Playing Animations
|
||||
|
||||
```python
|
||||
# Play a single animation
|
||||
self.play(Create(circle))
|
||||
|
||||
# Play multiple animations simultaneously
|
||||
self.play(Create(circle), FadeIn(square))
|
||||
|
||||
# With run_time
|
||||
self.play(Create(circle), run_time=2)
|
||||
```
|
||||
|
||||
### Waiting
|
||||
|
||||
```python
|
||||
# Wait for 1 second (default)
|
||||
self.wait()
|
||||
|
||||
# Wait for specific duration
|
||||
self.wait(2)
|
||||
```
|
||||
|
||||
## Scene Types
|
||||
|
||||
### Scene (Default)
|
||||
Standard 2D scene for most animations.
|
||||
|
||||
### ThreeDScene
|
||||
For 3D animations with camera orientation control.
|
||||
|
||||
```python
|
||||
class My3DScene(ThreeDScene):
|
||||
def construct(self):
|
||||
self.set_camera_orientation(phi=75 * DEGREES, theta=-45 * DEGREES)
|
||||
axes = ThreeDAxes()
|
||||
sphere = Sphere()
|
||||
self.add(axes, sphere)
|
||||
```
|
||||
|
||||
### MovingCameraScene
|
||||
For animations that require camera movement (zoom, pan).
|
||||
|
||||
```python
|
||||
class ZoomScene(MovingCameraScene):
|
||||
def construct(self):
|
||||
circle = Circle()
|
||||
self.add(circle)
|
||||
self.play(self.camera.frame.animate.scale(0.5).move_to(circle))
|
||||
```
|
||||
|
||||
## Multiple Scenes in One File
|
||||
|
||||
Render specific scene:
|
||||
```bash
|
||||
manim -pql file.py Scene1
|
||||
```
|
||||
|
||||
Render all scenes:
|
||||
```bash
|
||||
manim -pql -a file.py
|
||||
```
|
||||
@@ -0,0 +1,300 @@
|
||||
---
|
||||
name: shapes
|
||||
description: Circle, Square, Rectangle, Polygon and geometric primitives
|
||||
metadata:
|
||||
tags: shapes, circle, square, rectangle, polygon, geometry
|
||||
---
|
||||
|
||||
# Geometric Shapes
|
||||
|
||||
Basic geometric primitives in Manim.
|
||||
|
||||
## Circle
|
||||
|
||||
```python
|
||||
from manim import *
|
||||
|
||||
class CircleExample(Scene):
|
||||
def construct(self):
|
||||
# Default circle
|
||||
c1 = Circle()
|
||||
|
||||
# With parameters
|
||||
c2 = Circle(
|
||||
radius=2,
|
||||
color=BLUE,
|
||||
fill_opacity=0.5,
|
||||
stroke_width=4
|
||||
)
|
||||
|
||||
self.add(c1, c2)
|
||||
```
|
||||
|
||||
### Circle Methods
|
||||
|
||||
```python
|
||||
circle = Circle()
|
||||
|
||||
# Get properties
|
||||
circle.get_radius()
|
||||
circle.get_center()
|
||||
|
||||
# Create from points
|
||||
Circle.from_three_points(p1, p2, p3)
|
||||
|
||||
# Surround another mobject
|
||||
triangle = Triangle()
|
||||
circle = Circle().surround(triangle) # Circle wraps around triangle
|
||||
circle = Circle().surround(triangle, buffer_factor=1.5) # With padding
|
||||
circle = Circle().surround(triangle, stretch=True) # Stretch to fit
|
||||
```
|
||||
|
||||
## Ellipse
|
||||
|
||||
```python
|
||||
class EllipseExample(Scene):
|
||||
def construct(self):
|
||||
ellipse = Ellipse(
|
||||
width=4,
|
||||
height=2,
|
||||
color=GREEN
|
||||
)
|
||||
self.add(ellipse)
|
||||
```
|
||||
|
||||
## Square
|
||||
|
||||
```python
|
||||
class SquareExample(Scene):
|
||||
def construct(self):
|
||||
# Default square
|
||||
s1 = Square()
|
||||
|
||||
# With parameters
|
||||
s2 = Square(
|
||||
side_length=2,
|
||||
color=RED,
|
||||
fill_opacity=0.8
|
||||
)
|
||||
|
||||
self.add(s1, s2)
|
||||
```
|
||||
|
||||
## Rectangle
|
||||
|
||||
```python
|
||||
class RectangleExample(Scene):
|
||||
def construct(self):
|
||||
rect = Rectangle(
|
||||
width=4,
|
||||
height=2,
|
||||
color=YELLOW,
|
||||
fill_opacity=0.5
|
||||
)
|
||||
self.add(rect)
|
||||
```
|
||||
|
||||
### RoundedRectangle
|
||||
|
||||
```python
|
||||
class RoundedRectExample(Scene):
|
||||
def construct(self):
|
||||
rounded = RoundedRectangle(
|
||||
width=4,
|
||||
height=2,
|
||||
corner_radius=0.5,
|
||||
color=BLUE,
|
||||
fill_opacity=0.8
|
||||
)
|
||||
self.add(rounded)
|
||||
```
|
||||
|
||||
## Triangle
|
||||
|
||||
```python
|
||||
class TriangleExample(Scene):
|
||||
def construct(self):
|
||||
# Equilateral triangle
|
||||
tri = Triangle(color=PURPLE)
|
||||
|
||||
# Custom triangle (using Polygon)
|
||||
custom_tri = Polygon(
|
||||
ORIGIN, RIGHT * 2, UP * 3,
|
||||
color=GREEN
|
||||
)
|
||||
|
||||
self.add(tri, custom_tri.shift(RIGHT * 3))
|
||||
```
|
||||
|
||||
## Polygon
|
||||
|
||||
Create any polygon from vertices.
|
||||
|
||||
```python
|
||||
class PolygonExample(Scene):
|
||||
def construct(self):
|
||||
# Pentagon
|
||||
pentagon = RegularPolygon(n=5, color=ORANGE)
|
||||
|
||||
# Hexagon
|
||||
hexagon = RegularPolygon(n=6, color=TEAL)
|
||||
|
||||
# Custom polygon
|
||||
custom = Polygon(
|
||||
[-2, -1, 0],
|
||||
[2, -1, 0],
|
||||
[2, 1, 0],
|
||||
[0, 2, 0],
|
||||
[-2, 1, 0],
|
||||
color=PINK
|
||||
)
|
||||
|
||||
VGroup(pentagon, hexagon, custom).arrange(RIGHT, buff=1)
|
||||
self.add(pentagon, hexagon, custom)
|
||||
```
|
||||
|
||||
## RegularPolygon
|
||||
|
||||
```python
|
||||
class RegularPolygonExamples(Scene):
|
||||
def construct(self):
|
||||
shapes = VGroup(
|
||||
RegularPolygon(n=3), # Triangle
|
||||
RegularPolygon(n=4), # Square
|
||||
RegularPolygon(n=5), # Pentagon
|
||||
RegularPolygon(n=6), # Hexagon
|
||||
RegularPolygon(n=8), # Octagon
|
||||
).arrange(RIGHT)
|
||||
self.add(shapes)
|
||||
```
|
||||
|
||||
## Star
|
||||
|
||||
```python
|
||||
class StarExample(Scene):
|
||||
def construct(self):
|
||||
star = Star(
|
||||
n=5, # Number of points
|
||||
outer_radius=2,
|
||||
inner_radius=1, # Optional: auto-calculated if not specified
|
||||
density=2, # How vertices connect (affects shape)
|
||||
color=YELLOW,
|
||||
fill_opacity=1
|
||||
)
|
||||
self.add(star)
|
||||
|
||||
# Different densities create different star patterns
|
||||
star_d2 = Star(7, outer_radius=2, density=2, color=RED)
|
||||
star_d3 = Star(7, outer_radius=2, density=3, color=PURPLE)
|
||||
```
|
||||
|
||||
## RegularPolygram
|
||||
|
||||
Star-like shapes with vertices connected by density.
|
||||
|
||||
```python
|
||||
class PolygramExample(Scene):
|
||||
def construct(self):
|
||||
# Pentagram (5-pointed star pattern)
|
||||
pentagram = RegularPolygram(5, radius=2)
|
||||
self.add(pentagram)
|
||||
```
|
||||
|
||||
## Annulus (Ring)
|
||||
|
||||
```python
|
||||
class AnnulusExample(Scene):
|
||||
def construct(self):
|
||||
ring = Annulus(
|
||||
inner_radius=1,
|
||||
outer_radius=2,
|
||||
color=BLUE,
|
||||
fill_opacity=0.5
|
||||
)
|
||||
self.add(ring)
|
||||
```
|
||||
|
||||
## Sector and Arc
|
||||
|
||||
```python
|
||||
class SectorArcExample(Scene):
|
||||
def construct(self):
|
||||
# Sector (pie slice)
|
||||
sector = Sector(
|
||||
radius=2,
|
||||
angle=PI/2,
|
||||
start_angle=0,
|
||||
color=RED,
|
||||
fill_opacity=0.8
|
||||
).shift(LEFT * 2)
|
||||
|
||||
# Arc (just the curve)
|
||||
arc = Arc(
|
||||
radius=2,
|
||||
angle=PI/2,
|
||||
start_angle=PI,
|
||||
color=BLUE
|
||||
).shift(RIGHT * 2)
|
||||
|
||||
self.add(sector, arc)
|
||||
```
|
||||
|
||||
## ArcBetweenPoints
|
||||
|
||||
```python
|
||||
class ArcBetweenPointsExample(Scene):
|
||||
def construct(self):
|
||||
arc = ArcBetweenPoints(
|
||||
start=LEFT * 2,
|
||||
end=RIGHT * 2,
|
||||
angle=PI/2, # Curvature
|
||||
color=GREEN
|
||||
)
|
||||
self.add(arc)
|
||||
```
|
||||
|
||||
## Dot
|
||||
|
||||
```python
|
||||
class DotExample(Scene):
|
||||
def construct(self):
|
||||
# Default dot
|
||||
d1 = Dot()
|
||||
|
||||
# Customized
|
||||
d2 = Dot(
|
||||
point=RIGHT * 2,
|
||||
radius=0.2,
|
||||
color=YELLOW
|
||||
)
|
||||
|
||||
self.add(d1, d2)
|
||||
```
|
||||
|
||||
## Common Shape Operations
|
||||
|
||||
```python
|
||||
shape = Square()
|
||||
|
||||
# Transform
|
||||
shape.scale(2)
|
||||
shape.rotate(PI/4)
|
||||
shape.stretch(2, dim=0) # Stretch horizontally
|
||||
|
||||
# Style
|
||||
shape.set_fill(RED, opacity=0.5)
|
||||
shape.set_stroke(WHITE, width=4)
|
||||
|
||||
# Position
|
||||
shape.move_to(ORIGIN)
|
||||
shape.shift(UP * 2)
|
||||
shape.next_to(other, RIGHT)
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use RegularPolygon for regular shapes** - More precise than manual Polygon
|
||||
2. **Set fill_opacity for visibility** - Default is often 0 (transparent)
|
||||
3. **Use Dot for points** - Better than Circle with small radius
|
||||
4. **Use RoundedRectangle for UI elements** - More polished look
|
||||
5. **Combine shapes with VGroup** - For complex figures
|
||||
@@ -0,0 +1,177 @@
|
||||
---
|
||||
name: styling
|
||||
description: Fill, stroke, opacity and visual styling of mobjects
|
||||
metadata:
|
||||
tags: fill, stroke, opacity, style, width, appearance
|
||||
---
|
||||
|
||||
# Styling Mobjects
|
||||
|
||||
Control the visual appearance of mobjects with fill, stroke, and opacity settings.
|
||||
|
||||
## Fill Properties
|
||||
|
||||
Fill controls the interior of shapes.
|
||||
|
||||
```python
|
||||
from manim import *
|
||||
|
||||
class FillExample(Scene):
|
||||
def construct(self):
|
||||
# Set fill on creation
|
||||
circle = Circle(fill_color=BLUE, fill_opacity=0.8)
|
||||
|
||||
# Set fill after creation
|
||||
square = Square()
|
||||
square.set_fill(RED, opacity=0.5)
|
||||
|
||||
self.add(circle, square)
|
||||
```
|
||||
|
||||
### Fill Methods
|
||||
```python
|
||||
mobject.set_fill(color=RED) # Color only
|
||||
mobject.set_fill(RED, opacity=0.5) # Color and opacity
|
||||
mobject.set_fill(opacity=0.5) # Opacity only
|
||||
mobject.set_fill_color(RED) # Color only (alternative)
|
||||
mobject.set_fill_opacity(0.5) # Opacity only (alternative)
|
||||
```
|
||||
|
||||
## Stroke Properties
|
||||
|
||||
Stroke controls the outline/border of shapes.
|
||||
|
||||
```python
|
||||
class StrokeExample(Scene):
|
||||
def construct(self):
|
||||
# Set stroke on creation
|
||||
circle = Circle(stroke_color=BLUE, stroke_width=4)
|
||||
|
||||
# Set stroke after creation
|
||||
square = Square()
|
||||
square.set_stroke(RED, width=8)
|
||||
|
||||
self.add(circle, square)
|
||||
```
|
||||
|
||||
### Stroke Methods
|
||||
```python
|
||||
mobject.set_stroke(color=RED) # Color only
|
||||
mobject.set_stroke(RED, width=4) # Color and width
|
||||
mobject.set_stroke(width=4) # Width only
|
||||
mobject.set_stroke(opacity=0.5) # Opacity only
|
||||
mobject.set_stroke_color(RED) # Color only (alternative)
|
||||
mobject.set_stroke_width(4) # Width only (alternative)
|
||||
mobject.set_stroke_opacity(0.5) # Opacity only (alternative)
|
||||
```
|
||||
|
||||
### Stroke Width Reference
|
||||
```python
|
||||
# Common stroke widths
|
||||
DEFAULT_STROKE_WIDTH = 4
|
||||
thin = 1
|
||||
normal = 4
|
||||
thick = 8
|
||||
very_thick = 12
|
||||
```
|
||||
|
||||
## Combined Styling
|
||||
|
||||
```python
|
||||
class CombinedStyling(Scene):
|
||||
def construct(self):
|
||||
square = Square()
|
||||
square.set_fill(BLUE, opacity=0.5)
|
||||
square.set_stroke(YELLOW, width=6)
|
||||
self.add(square)
|
||||
```
|
||||
|
||||
### Method Chaining
|
||||
```python
|
||||
square = Square().set_fill(RED, 0.5).set_stroke(WHITE, 4)
|
||||
```
|
||||
|
||||
## The set_style Method
|
||||
|
||||
Set multiple style properties at once:
|
||||
|
||||
```python
|
||||
square = Square()
|
||||
square.set_style(
|
||||
fill_color=BLUE,
|
||||
fill_opacity=0.5,
|
||||
stroke_color=WHITE,
|
||||
stroke_width=4,
|
||||
stroke_opacity=1
|
||||
)
|
||||
```
|
||||
|
||||
## Opacity
|
||||
|
||||
Control transparency of mobjects:
|
||||
|
||||
```python
|
||||
# Overall opacity
|
||||
mobject.set_opacity(0.5) # Affects both fill and stroke
|
||||
|
||||
# Separate opacities
|
||||
mobject.set_fill_opacity(0.8)
|
||||
mobject.set_stroke_opacity(0.3)
|
||||
|
||||
# Fade effect
|
||||
mobject.fade(0.5) # 0.5 = 50% faded (opposite of opacity)
|
||||
```
|
||||
|
||||
## Background Rectangle
|
||||
|
||||
Add a background behind text or other mobjects:
|
||||
|
||||
```python
|
||||
class BackgroundExample(Scene):
|
||||
def construct(self):
|
||||
text = Text("Important!")
|
||||
bg = BackgroundRectangle(text, fill_opacity=0.8, buff=0.1)
|
||||
group = VGroup(bg, text)
|
||||
self.add(group)
|
||||
```
|
||||
|
||||
## Applying Style to Submobjects
|
||||
|
||||
```python
|
||||
# Apply to all submobjects (family=True, default)
|
||||
group.set_fill(RED, opacity=0.5, family=True)
|
||||
|
||||
# Apply only to parent, not submobjects
|
||||
group.set_fill(RED, opacity=0.5, family=False)
|
||||
```
|
||||
|
||||
## Style Based on Position
|
||||
|
||||
```python
|
||||
class GradientFill(Scene):
|
||||
def construct(self):
|
||||
squares = VGroup(*[Square() for _ in range(5)]).arrange(RIGHT)
|
||||
|
||||
for i, sq in enumerate(squares):
|
||||
opacity = (i + 1) / 5
|
||||
sq.set_fill(BLUE, opacity=opacity)
|
||||
|
||||
self.add(squares)
|
||||
```
|
||||
|
||||
## Copying Style
|
||||
|
||||
```python
|
||||
# Copy style from another mobject
|
||||
source = Circle().set_fill(RED, 0.5).set_stroke(WHITE, 4)
|
||||
target = Square()
|
||||
target.match_style(source) # Now has same fill and stroke
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use fill_opacity for shapes** - Fully opaque fills can hide other elements
|
||||
2. **Consistent stroke width** - Pick a width and stick with it
|
||||
3. **Contrast fill and stroke** - Different colors help definition
|
||||
4. **Use BackgroundRectangle for readability** - Behind text on busy backgrounds
|
||||
5. **Chain methods for concise code** - But break lines if too long
|
||||
@@ -0,0 +1,222 @@
|
||||
---
|
||||
name: text-animations
|
||||
description: Write, AddTextLetterByLetter, TypeWithCursor text animations
|
||||
metadata:
|
||||
tags: text, write, typing, letter, cursor, animation
|
||||
---
|
||||
|
||||
# Text Animations
|
||||
|
||||
Animations specifically designed for text and equations.
|
||||
|
||||
## Write
|
||||
|
||||
The most common text animation. Simulates handwriting.
|
||||
|
||||
```python
|
||||
from manim import *
|
||||
|
||||
class WriteExample(Scene):
|
||||
def construct(self):
|
||||
text = Text("Hello World")
|
||||
equation = MathTex(r"E = mc^2")
|
||||
|
||||
self.play(Write(text))
|
||||
self.wait()
|
||||
self.play(Write(equation))
|
||||
```
|
||||
|
||||
### Write Parameters
|
||||
|
||||
```python
|
||||
self.play(Write(
|
||||
text,
|
||||
run_time=2, # Override auto-calculated time
|
||||
rate_func=linear, # Timing curve
|
||||
reverse=False, # Write backwards if True
|
||||
))
|
||||
```
|
||||
|
||||
Write automatically adjusts `run_time` based on text length.
|
||||
|
||||
## AddTextLetterByLetter
|
||||
|
||||
Types text one character at a time.
|
||||
|
||||
```python
|
||||
class LetterByLetterExample(Scene):
|
||||
def construct(self):
|
||||
text = Text("Typing effect")
|
||||
|
||||
self.play(AddTextLetterByLetter(
|
||||
text,
|
||||
time_per_char=0.1 # Speed of typing
|
||||
))
|
||||
```
|
||||
|
||||
**Note:** Only works with `Text`, not `MathTex`.
|
||||
|
||||
## RemoveTextLetterByLetter
|
||||
|
||||
Reverse of AddTextLetterByLetter - removes character by character.
|
||||
|
||||
```python
|
||||
class RemoveLetterByLetter(Scene):
|
||||
def construct(self):
|
||||
text = Text("Disappearing text")
|
||||
self.add(text)
|
||||
|
||||
self.play(RemoveTextLetterByLetter(
|
||||
text,
|
||||
time_per_char=0.05
|
||||
))
|
||||
```
|
||||
|
||||
## TypeWithCursor
|
||||
|
||||
Types text with a visible cursor.
|
||||
|
||||
```python
|
||||
class TypeWithCursorExample(Scene):
|
||||
def construct(self):
|
||||
text = Text("Typing with cursor")
|
||||
|
||||
# Create cursor
|
||||
cursor = Rectangle(
|
||||
color=GREY_A,
|
||||
fill_color=GREY_A,
|
||||
fill_opacity=1.0,
|
||||
height=1.1,
|
||||
width=0.1,
|
||||
)
|
||||
|
||||
self.play(TypeWithCursor(text, cursor))
|
||||
|
||||
# Optional: blink cursor after typing
|
||||
self.play(Blink(cursor, blinks=3))
|
||||
```
|
||||
|
||||
### Cursor Customization
|
||||
|
||||
```python
|
||||
# Line cursor
|
||||
cursor = Line(UP * 0.5, DOWN * 0.5, color=WHITE, stroke_width=2)
|
||||
|
||||
# Block cursor
|
||||
cursor = Rectangle(width=0.5, height=1, fill_opacity=0.8, color=WHITE)
|
||||
|
||||
# Custom cursor position
|
||||
self.play(TypeWithCursor(
|
||||
text,
|
||||
cursor,
|
||||
buff=0.05, # Space between text and cursor
|
||||
keep_cursor_y=True, # Keep cursor at consistent height
|
||||
leave_cursor_on=True # Show cursor after animation
|
||||
))
|
||||
```
|
||||
|
||||
## Blink (for cursors)
|
||||
|
||||
```python
|
||||
class BlinkExample(Scene):
|
||||
def construct(self):
|
||||
cursor = Rectangle(height=1, width=0.1, fill_opacity=1)
|
||||
self.add(cursor)
|
||||
|
||||
self.play(Blink(cursor, blinks=5, time_on=0.3, time_off=0.3))
|
||||
```
|
||||
|
||||
## Word by Word Animation
|
||||
|
||||
Using LaggedStart for word-by-word appearance:
|
||||
|
||||
```python
|
||||
class WordByWord(Scene):
|
||||
def construct(self):
|
||||
# Split into individual Text objects
|
||||
words = VGroup(
|
||||
Text("Hello"),
|
||||
Text("World"),
|
||||
Text("!")
|
||||
).arrange(RIGHT, buff=0.3)
|
||||
|
||||
self.play(LaggedStart(
|
||||
*[Write(word) for word in words],
|
||||
lag_ratio=0.5
|
||||
))
|
||||
```
|
||||
|
||||
## Equation Transformations
|
||||
|
||||
Animate between equations:
|
||||
|
||||
```python
|
||||
class EquationTransform(Scene):
|
||||
def construct(self):
|
||||
eq1 = MathTex(r"a^2 + b^2 = c^2")
|
||||
eq2 = MathTex(r"c = \sqrt{a^2 + b^2}")
|
||||
|
||||
self.play(Write(eq1))
|
||||
self.wait()
|
||||
self.play(TransformMatchingTex(eq1, eq2))
|
||||
```
|
||||
|
||||
## Highlighting Text
|
||||
|
||||
```python
|
||||
class HighlightText(Scene):
|
||||
def construct(self):
|
||||
text = Text("Important message")
|
||||
self.add(text)
|
||||
|
||||
# Circumscribe (draw around)
|
||||
self.play(Circumscribe(text, color=YELLOW))
|
||||
|
||||
# Indicate (pulse)
|
||||
self.play(Indicate(text, color=RED))
|
||||
|
||||
# Flash
|
||||
self.play(Flash(text.get_center(), color=WHITE))
|
||||
```
|
||||
|
||||
## Replacing Text
|
||||
|
||||
```python
|
||||
class ReplaceText(Scene):
|
||||
def construct(self):
|
||||
text1 = Text("Before")
|
||||
text2 = Text("After")
|
||||
|
||||
self.play(Write(text1))
|
||||
self.wait()
|
||||
|
||||
# Transform text
|
||||
self.play(Transform(text1, text2))
|
||||
|
||||
# Or replacement transform
|
||||
self.play(ReplacementTransform(text1, text2))
|
||||
```
|
||||
|
||||
## Colored Text Animation
|
||||
|
||||
```python
|
||||
class ColoredTextAnimation(Scene):
|
||||
def construct(self):
|
||||
text = Text("Colorful")
|
||||
self.play(Write(text))
|
||||
|
||||
# Animate color change per letter
|
||||
self.play(LaggedStart(
|
||||
*[char.animate.set_color(random_bright_color()) for char in text],
|
||||
lag_ratio=0.1
|
||||
))
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use Write for most text** - Natural and smooth
|
||||
2. **Use AddTextLetterByLetter for "typing" effect** - Terminal/code aesthetics
|
||||
3. **Use TypeWithCursor for interactive feel** - Good for tutorials
|
||||
4. **Use TransformMatchingTex for equations** - Smooth mathematical transitions
|
||||
5. **Adjust time_per_char for pacing** - 0.05-0.1 is usually good
|
||||
6. **Only use Text (not MathTex) for letter-by-letter** - API limitation
|
||||
@@ -0,0 +1,189 @@
|
||||
---
|
||||
name: text
|
||||
description: Text mobjects, fonts, and text styling in Manim
|
||||
metadata:
|
||||
tags: text, font, typography, markup, paragraph
|
||||
---
|
||||
|
||||
# Text in Manim
|
||||
|
||||
The `Text` class renders text using Pango/Cairo, supporting various fonts and styles.
|
||||
|
||||
## Basic Text
|
||||
|
||||
```python
|
||||
from manim import *
|
||||
|
||||
class TextExample(Scene):
|
||||
def construct(self):
|
||||
text = Text("Hello World")
|
||||
self.play(Write(text))
|
||||
```
|
||||
|
||||
## Text Parameters
|
||||
|
||||
```python
|
||||
text = Text(
|
||||
"Hello World",
|
||||
font_size=48, # Size (default: 48)
|
||||
color=BLUE, # Text color
|
||||
font="Arial", # Font family
|
||||
weight=BOLD, # NORMAL, BOLD, etc.
|
||||
slant=ITALIC, # NORMAL, ITALIC, OBLIQUE
|
||||
line_spacing=1.5, # Space between lines
|
||||
)
|
||||
```
|
||||
|
||||
## Font Size
|
||||
|
||||
```python
|
||||
# Using font_size parameter
|
||||
small = Text("Small", font_size=24)
|
||||
medium = Text("Medium", font_size=48)
|
||||
large = Text("Large", font_size=72)
|
||||
|
||||
# Using scale after creation
|
||||
text = Text("Hello").scale(2)
|
||||
```
|
||||
|
||||
## Custom Fonts
|
||||
|
||||
```python
|
||||
# Use any installed system font
|
||||
text = Text("Custom Font", font="Comic Sans MS")
|
||||
text = Text("Monospace", font="Courier New")
|
||||
text = Text("Serif", font="Times New Roman")
|
||||
```
|
||||
|
||||
## Text Styling with MarkupText
|
||||
|
||||
Use Pango markup for mixed styling within one Text object:
|
||||
|
||||
```python
|
||||
class MarkupExample(Scene):
|
||||
def construct(self):
|
||||
text = MarkupText(
|
||||
f'all in red <span fgcolor="{YELLOW}">except this</span>',
|
||||
color=RED
|
||||
)
|
||||
self.play(Write(text))
|
||||
```
|
||||
|
||||
### Available Markup Tags
|
||||
|
||||
```python
|
||||
# Bold and italic
|
||||
text = MarkupText('<b>Bold</b> and <i>Italic</i>')
|
||||
|
||||
# Colors using fgcolor
|
||||
text = MarkupText('<span fgcolor="yellow">Yellow</span>')
|
||||
|
||||
# Subscripts and superscripts
|
||||
text = MarkupText('H<sub>2</sub>O and x<sup>2</sup>')
|
||||
|
||||
# Font size
|
||||
text = MarkupText('<big>Big</big> and <small>small</small>')
|
||||
|
||||
# Underline and strikethrough
|
||||
text = MarkupText('<u>Underline</u> and <s>Strike</s>')
|
||||
|
||||
# Double underline with color
|
||||
text = MarkupText('<span underline="double" underline_color="green">text</span>')
|
||||
|
||||
# Monospace
|
||||
text = MarkupText('type <tt>help</tt> for help')
|
||||
```
|
||||
|
||||
### Gradients in MarkupText
|
||||
|
||||
```python
|
||||
# Global gradient
|
||||
text = MarkupText("nice gradient", gradient=(BLUE, GREEN))
|
||||
|
||||
# Inline gradient
|
||||
text = MarkupText(
|
||||
'nice <gradient from="RED" to="YELLOW">colored</gradient> text'
|
||||
)
|
||||
```
|
||||
|
||||
### Escaping Special Characters
|
||||
|
||||
```python
|
||||
# Must escape these characters:
|
||||
# > as >
|
||||
# < as <
|
||||
# & as &
|
||||
text = MarkupText("5 > 3 and 2 < 4")
|
||||
```
|
||||
|
||||
## Multi-line Text
|
||||
|
||||
```python
|
||||
# Using \n for line breaks
|
||||
text = Text("Line 1\nLine 2\nLine 3")
|
||||
|
||||
# Using Paragraph for better control
|
||||
from manim import Paragraph
|
||||
|
||||
para = Paragraph(
|
||||
"This is a longer text",
|
||||
"that spans multiple lines",
|
||||
"with automatic alignment",
|
||||
line_spacing=0.5
|
||||
)
|
||||
```
|
||||
|
||||
## Coloring Parts of Text
|
||||
|
||||
```python
|
||||
class ColoredText(Scene):
|
||||
def construct(self):
|
||||
text = Text("Hello World")
|
||||
text[0:5].set_color(RED) # "Hello" in red
|
||||
text[6:11].set_color(BLUE) # "World" in blue
|
||||
self.play(Write(text))
|
||||
```
|
||||
|
||||
## Text with Gradients
|
||||
|
||||
```python
|
||||
text = Text("Gradient Text")
|
||||
text.set_color_by_gradient(RED, YELLOW, GREEN)
|
||||
```
|
||||
|
||||
## Accessing Characters
|
||||
|
||||
```python
|
||||
text = Text("ABCDE")
|
||||
|
||||
# Individual characters
|
||||
text[0] # 'A'
|
||||
text[1] # 'B'
|
||||
|
||||
# Slices
|
||||
text[0:3] # 'ABC'
|
||||
text[-1] # 'E'
|
||||
|
||||
# Iterate
|
||||
for char in text:
|
||||
char.set_color(random_color())
|
||||
```
|
||||
|
||||
## Text Positioning
|
||||
|
||||
```python
|
||||
# Standard positioning methods work
|
||||
text = Text("Hello")
|
||||
text.to_edge(UP)
|
||||
text.to_corner(UL)
|
||||
text.move_to(ORIGIN)
|
||||
text.next_to(other_mobject, DOWN)
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use Text for regular text** - Simple and fast
|
||||
2. **Use MarkupText for mixed styles** - When you need multiple colors/weights
|
||||
3. **Use MathTex for math** - Text doesn't render LaTeX
|
||||
4. **Install fonts system-wide** - Manim uses system fonts
|
||||
5. **Keep font_size consistent** - Use the same size for related text
|
||||
@@ -0,0 +1,201 @@
|
||||
---
|
||||
name: timing
|
||||
description: Rate functions, easing, run_time, and animation timing control
|
||||
metadata:
|
||||
tags: timing, rate_func, easing, smooth, linear, run_time
|
||||
---
|
||||
|
||||
# Animation Timing
|
||||
|
||||
Control the speed and feel of animations with timing parameters.
|
||||
|
||||
## run_time
|
||||
|
||||
Controls how long an animation takes in seconds.
|
||||
|
||||
```python
|
||||
from manim import *
|
||||
|
||||
class RunTimeExample(Scene):
|
||||
def construct(self):
|
||||
circle = Circle()
|
||||
|
||||
# Default (1 second)
|
||||
self.play(Create(circle))
|
||||
|
||||
# Longer animation
|
||||
self.play(circle.animate.shift(RIGHT), run_time=3)
|
||||
|
||||
# Quick animation
|
||||
self.play(circle.animate.set_color(RED), run_time=0.5)
|
||||
```
|
||||
|
||||
## Rate Functions
|
||||
|
||||
Rate functions control how the animation progresses over time (easing).
|
||||
|
||||
### Using Rate Functions
|
||||
|
||||
```python
|
||||
self.play(
|
||||
circle.animate.shift(RIGHT),
|
||||
rate_func=smooth
|
||||
)
|
||||
```
|
||||
|
||||
### Common Rate Functions
|
||||
|
||||
```python
|
||||
# Smooth start and end (default for most animations)
|
||||
smooth
|
||||
|
||||
# Constant speed
|
||||
linear
|
||||
|
||||
# Start slow, end fast
|
||||
rush_into
|
||||
|
||||
# Start fast, end slow
|
||||
rush_from
|
||||
|
||||
# Go there and back
|
||||
there_and_back
|
||||
|
||||
# Go there and back with pause
|
||||
there_and_back_with_pause
|
||||
|
||||
# Double smooth (extra smooth)
|
||||
double_smooth
|
||||
|
||||
# Stay put (useful for delays in AnimationGroup)
|
||||
lingering
|
||||
```
|
||||
|
||||
### Ease Functions (CSS-like)
|
||||
|
||||
```python
|
||||
# Ease in (start slow)
|
||||
ease_in_sine
|
||||
ease_in_quad
|
||||
ease_in_cubic
|
||||
ease_in_expo
|
||||
ease_in_circ
|
||||
ease_in_back # Slight overshoot at start
|
||||
|
||||
# Ease out (end slow)
|
||||
ease_out_sine
|
||||
ease_out_quad
|
||||
ease_out_cubic
|
||||
ease_out_expo
|
||||
ease_out_circ
|
||||
ease_out_back # Slight overshoot at end
|
||||
ease_out_bounce # Bouncy ending
|
||||
|
||||
# Ease in-out (slow at both ends)
|
||||
ease_in_out_sine
|
||||
ease_in_out_quad
|
||||
ease_in_out_cubic
|
||||
ease_in_out_expo
|
||||
ease_in_out_circ
|
||||
ease_in_out_back
|
||||
```
|
||||
|
||||
## Visual Comparison
|
||||
|
||||
```python
|
||||
class RateFuncComparison(Scene):
|
||||
def construct(self):
|
||||
funcs = [linear, smooth, rush_into, rush_from, there_and_back]
|
||||
names = ["linear", "smooth", "rush_into", "rush_from", "there_and_back"]
|
||||
|
||||
dots = VGroup()
|
||||
labels = VGroup()
|
||||
|
||||
for i, (func, name) in enumerate(zip(funcs, names)):
|
||||
dot = Dot().shift(LEFT * 4 + DOWN * i)
|
||||
label = Text(name, font_size=24).next_to(dot, LEFT)
|
||||
dots.add(dot)
|
||||
labels.add(label)
|
||||
|
||||
self.add(dots, labels)
|
||||
|
||||
self.play(*[
|
||||
dot.animate(rate_func=func).shift(RIGHT * 8)
|
||||
for dot, func in zip(dots, funcs)
|
||||
], run_time=3)
|
||||
```
|
||||
|
||||
## Combining run_time and rate_func
|
||||
|
||||
```python
|
||||
self.play(
|
||||
square.animate.shift(RIGHT * 3),
|
||||
run_time=2,
|
||||
rate_func=ease_out_bounce
|
||||
)
|
||||
```
|
||||
|
||||
## there_and_back
|
||||
|
||||
Animation goes forward then reverses.
|
||||
|
||||
```python
|
||||
class ThereAndBackExample(Scene):
|
||||
def construct(self):
|
||||
square = Square()
|
||||
self.add(square)
|
||||
|
||||
# Moves right then back to start
|
||||
self.play(
|
||||
square.animate.shift(RIGHT * 2),
|
||||
rate_func=there_and_back,
|
||||
run_time=2
|
||||
)
|
||||
```
|
||||
|
||||
## Custom Rate Functions
|
||||
|
||||
Create your own rate function (takes t from 0 to 1, returns progress 0 to 1):
|
||||
|
||||
```python
|
||||
def my_rate_func(t):
|
||||
# Quadratic ease
|
||||
return t ** 2
|
||||
|
||||
self.play(
|
||||
circle.animate.shift(RIGHT),
|
||||
rate_func=my_rate_func
|
||||
)
|
||||
```
|
||||
|
||||
## wait() Timing
|
||||
|
||||
```python
|
||||
# Wait for default time (1 second)
|
||||
self.wait()
|
||||
|
||||
# Wait for specific duration
|
||||
self.wait(2) # 2 seconds
|
||||
self.wait(0.5) # Half second
|
||||
```
|
||||
|
||||
## Animation Speed Multiplier
|
||||
|
||||
Using `run_time` on AnimationGroup affects all children:
|
||||
|
||||
```python
|
||||
self.play(AnimationGroup(
|
||||
Create(circle),
|
||||
Create(square),
|
||||
lag_ratio=0.5
|
||||
), run_time=3) # Total duration is 3 seconds
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use smooth for most animations** - Looks natural
|
||||
2. **Use linear for constant motion** - Mechanical/precise movement
|
||||
3. **Use ease_out_bounce for playful effects** - Attention-grabbing
|
||||
4. **Keep run_time between 0.5-3 seconds** - Maintain viewer attention
|
||||
5. **Use there_and_back for emphasis** - Show something temporarily
|
||||
6. **Match rate_func to content** - Smooth for elegant, bouncy for fun
|
||||
@@ -0,0 +1,157 @@
|
||||
---
|
||||
name: transform-animations
|
||||
description: Transform, ReplacementTransform, and morphing animations
|
||||
metadata:
|
||||
tags: transform, replacementtransform, morph, transformfromcopy
|
||||
---
|
||||
|
||||
# Transform Animations
|
||||
|
||||
Animations that morph one mobject into another.
|
||||
|
||||
## Transform
|
||||
|
||||
Morphs the source mobject into the shape of the target. The source mobject is modified.
|
||||
|
||||
```python
|
||||
class TransformExample(Scene):
|
||||
def construct(self):
|
||||
square = Square()
|
||||
circle = Circle()
|
||||
|
||||
self.play(Create(square))
|
||||
self.play(Transform(square, circle))
|
||||
# Note: 'square' now looks like 'circle' but is still 'square'
|
||||
```
|
||||
|
||||
**Important:** After Transform, the original variable still references the mobject, even though it looks like the target.
|
||||
|
||||
## ReplacementTransform
|
||||
|
||||
Morphs source into target and replaces the reference. More intuitive for most uses.
|
||||
|
||||
```python
|
||||
class ReplacementTransformExample(Scene):
|
||||
def construct(self):
|
||||
square = Square()
|
||||
circle = Circle()
|
||||
triangle = Triangle()
|
||||
|
||||
self.play(Create(square))
|
||||
self.play(ReplacementTransform(square, circle))
|
||||
# 'square' is removed, 'circle' is now in the scene
|
||||
self.play(ReplacementTransform(circle, triangle))
|
||||
# 'circle' is removed, 'triangle' is now in the scene
|
||||
```
|
||||
|
||||
## Transform vs ReplacementTransform
|
||||
|
||||
```python
|
||||
# Transform - source variable changes appearance
|
||||
self.play(Transform(A, B))
|
||||
# A is still in scene (but looks like B)
|
||||
# B is NOT in scene
|
||||
|
||||
# ReplacementTransform - source is replaced by target
|
||||
self.play(ReplacementTransform(A, B))
|
||||
# A is removed from scene
|
||||
# B is now in scene
|
||||
```
|
||||
|
||||
## TransformFromCopy
|
||||
|
||||
Creates a copy of source and morphs it to target. Original remains unchanged.
|
||||
|
||||
```python
|
||||
class TransformFromCopyExample(Scene):
|
||||
def construct(self):
|
||||
square = Square().shift(LEFT * 2)
|
||||
circle = Circle().shift(RIGHT * 2)
|
||||
|
||||
self.add(square)
|
||||
self.play(TransformFromCopy(square, circle))
|
||||
# Both square and circle are now visible
|
||||
```
|
||||
|
||||
## TransformMatchingShapes
|
||||
|
||||
Intelligently matches and transforms corresponding parts.
|
||||
|
||||
```python
|
||||
class MatchingShapesExample(Scene):
|
||||
def construct(self):
|
||||
source = Text("ABC")
|
||||
target = Text("ABCD")
|
||||
|
||||
self.play(Write(source))
|
||||
self.play(TransformMatchingShapes(source, target))
|
||||
```
|
||||
|
||||
## TransformMatchingTex
|
||||
|
||||
Matches LaTeX parts by their TeX strings.
|
||||
|
||||
```python
|
||||
class MatchingTexExample(Scene):
|
||||
def construct(self):
|
||||
eq1 = MathTex("a", "^2", "+", "b", "^2")
|
||||
eq2 = MathTex("a", "^2", "+", "2ab", "+", "b", "^2")
|
||||
|
||||
self.play(Write(eq1))
|
||||
self.play(TransformMatchingTex(eq1, eq2))
|
||||
```
|
||||
|
||||
## MoveToTarget
|
||||
|
||||
Pre-set a target state and animate to it.
|
||||
|
||||
```python
|
||||
class MoveToTargetExample(Scene):
|
||||
def construct(self):
|
||||
square = Square()
|
||||
self.add(square)
|
||||
|
||||
# Generate and modify target
|
||||
square.generate_target()
|
||||
square.target.shift(RIGHT * 2)
|
||||
square.target.set_color(RED)
|
||||
square.target.scale(2)
|
||||
|
||||
self.play(MoveToTarget(square))
|
||||
```
|
||||
|
||||
## Path Arc Transforms
|
||||
|
||||
Control the path of transformation with `path_arc`.
|
||||
|
||||
```python
|
||||
class PathArcExample(Scene):
|
||||
def construct(self):
|
||||
dot1 = Dot(LEFT * 2)
|
||||
dot2 = Dot(RIGHT * 2)
|
||||
|
||||
self.add(dot1)
|
||||
# Transform along an arc
|
||||
self.play(Transform(dot1, dot2, path_arc=PI/2))
|
||||
```
|
||||
|
||||
## Chained Transformations
|
||||
|
||||
```python
|
||||
class ChainedExample(Scene):
|
||||
def construct(self):
|
||||
shape = Square()
|
||||
self.play(Create(shape))
|
||||
|
||||
# Chain of transformations
|
||||
for target in [Circle(), Triangle(), Star()]:
|
||||
self.play(Transform(shape, target))
|
||||
self.wait(0.5)
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use ReplacementTransform for clarity** - More intuitive variable behavior
|
||||
2. **Use TransformFromCopy to preserve original** - When you need both visible
|
||||
3. **Use TransformMatchingTex for equations** - Better alignment of matching parts
|
||||
4. **Set path_arc for visual interest** - Curved paths look more dynamic
|
||||
@@ -0,0 +1,226 @@
|
||||
---
|
||||
name: updaters
|
||||
description: Updaters, ValueTracker, and dynamic animations
|
||||
metadata:
|
||||
tags: updater, valuetracker, dynamic, always, add_updater
|
||||
---
|
||||
|
||||
# Updaters and Dynamic Animations
|
||||
|
||||
Updaters allow mobjects to automatically update based on other values or mobjects.
|
||||
|
||||
## Basic Updaters
|
||||
|
||||
Add a function that runs every frame.
|
||||
|
||||
```python
|
||||
from manim import *
|
||||
|
||||
class UpdaterExample(Scene):
|
||||
def construct(self):
|
||||
dot = Dot()
|
||||
label = Text("Follow me").next_to(dot, UP)
|
||||
|
||||
# Label always follows the dot
|
||||
label.add_updater(lambda m: m.next_to(dot, UP))
|
||||
|
||||
self.add(dot, label)
|
||||
self.play(dot.animate.shift(RIGHT * 3), run_time=2)
|
||||
self.play(dot.animate.shift(DOWN * 2), run_time=2)
|
||||
```
|
||||
|
||||
## Updater Syntax
|
||||
|
||||
```python
|
||||
# Lambda function
|
||||
mobject.add_updater(lambda m: m.move_to(target.get_center()))
|
||||
|
||||
# Named function
|
||||
def follow_target(mob):
|
||||
mob.next_to(target, RIGHT)
|
||||
|
||||
mobject.add_updater(follow_target)
|
||||
|
||||
# With dt (delta time) parameter
|
||||
def time_based_update(mob, dt):
|
||||
mob.rotate(dt * PI) # Rotate based on time elapsed
|
||||
|
||||
mobject.add_updater(time_based_update)
|
||||
```
|
||||
|
||||
## ValueTracker
|
||||
|
||||
A mobject that holds a numeric value. Perfect for animating parameters.
|
||||
|
||||
```python
|
||||
class ValueTrackerExample(Scene):
|
||||
def construct(self):
|
||||
# Create tracker
|
||||
tracker = ValueTracker(0)
|
||||
|
||||
# Create number display
|
||||
number = DecimalNumber(0, num_decimal_places=2)
|
||||
number.add_updater(lambda m: m.set_value(tracker.get_value()))
|
||||
|
||||
# Create circle that grows with tracker
|
||||
circle = Circle()
|
||||
circle.add_updater(lambda m: m.set_width(tracker.get_value()))
|
||||
|
||||
self.add(number, circle)
|
||||
|
||||
# Animate the tracker
|
||||
self.play(tracker.animate.set_value(4), run_time=3)
|
||||
self.play(tracker.animate.set_value(1), run_time=2)
|
||||
```
|
||||
|
||||
### ValueTracker Operations
|
||||
|
||||
```python
|
||||
tracker = ValueTracker(5)
|
||||
|
||||
# Get and set value
|
||||
current = tracker.get_value()
|
||||
tracker.set_value(10)
|
||||
|
||||
# Increment
|
||||
tracker.increment_value(2.5)
|
||||
|
||||
# Arithmetic operators (direct manipulation, no animation)
|
||||
tracker += 1
|
||||
tracker -= 2
|
||||
tracker *= 3
|
||||
tracker /= 2
|
||||
|
||||
# Animate changes
|
||||
self.play(tracker.animate.set_value(100))
|
||||
self.play(tracker.animate.increment_value(-50))
|
||||
```
|
||||
|
||||
## DecimalNumber with ValueTracker
|
||||
|
||||
Display a changing number:
|
||||
|
||||
```python
|
||||
class NumberDisplay(Scene):
|
||||
def construct(self):
|
||||
tracker = ValueTracker(0)
|
||||
|
||||
number = DecimalNumber(
|
||||
0,
|
||||
num_decimal_places=2,
|
||||
include_sign=True,
|
||||
font_size=72
|
||||
)
|
||||
number.add_updater(lambda m: m.set_value(tracker.get_value()))
|
||||
number.add_updater(lambda m: m.move_to(ORIGIN))
|
||||
|
||||
self.add(number)
|
||||
self.play(tracker.animate.set_value(100), run_time=3)
|
||||
```
|
||||
|
||||
## always_redraw
|
||||
|
||||
Recreate a mobject every frame based on current values.
|
||||
|
||||
```python
|
||||
class AlwaysRedrawExample(Scene):
|
||||
def construct(self):
|
||||
tracker = ValueTracker(1)
|
||||
|
||||
# Line that always connects two points based on tracker
|
||||
line = always_redraw(
|
||||
lambda: Line(
|
||||
LEFT * 2,
|
||||
RIGHT * 2 * tracker.get_value()
|
||||
)
|
||||
)
|
||||
|
||||
self.add(line)
|
||||
self.play(tracker.animate.set_value(2), run_time=2)
|
||||
self.play(tracker.animate.set_value(0.5), run_time=2)
|
||||
```
|
||||
|
||||
## Common Updater Patterns
|
||||
|
||||
### Following Another Mobject
|
||||
```python
|
||||
follower.add_updater(lambda m: m.move_to(leader.get_center()))
|
||||
follower.add_updater(lambda m: m.next_to(leader, RIGHT))
|
||||
```
|
||||
|
||||
### Pointing at Another Mobject
|
||||
```python
|
||||
arrow = Arrow(ORIGIN, RIGHT)
|
||||
arrow.add_updater(lambda m: m.put_start_and_end_on(
|
||||
start.get_center(),
|
||||
end.get_center()
|
||||
))
|
||||
```
|
||||
|
||||
### Rotating Continuously
|
||||
```python
|
||||
mobject.add_updater(lambda m, dt: m.rotate(dt * PI))
|
||||
```
|
||||
|
||||
### Matching Properties
|
||||
```python
|
||||
# Match color
|
||||
follower.add_updater(lambda m: m.set_color(leader.get_color()))
|
||||
|
||||
# Match position with offset
|
||||
follower.add_updater(lambda m: m.move_to(leader.get_center() + UP))
|
||||
```
|
||||
|
||||
## Removing Updaters
|
||||
|
||||
```python
|
||||
# Remove specific updater
|
||||
mobject.remove_updater(updater_function)
|
||||
|
||||
# Remove all updaters
|
||||
mobject.clear_updaters()
|
||||
|
||||
# Suspend temporarily
|
||||
mobject.suspend_updating()
|
||||
mobject.resume_updating()
|
||||
```
|
||||
|
||||
## Updaters with Animations
|
||||
|
||||
Updaters continue running during animations:
|
||||
|
||||
```python
|
||||
class UpdaterDuringAnimation(Scene):
|
||||
def construct(self):
|
||||
dot = Dot()
|
||||
trail = TracedPath(dot.get_center, stroke_color=YELLOW)
|
||||
|
||||
self.add(dot, trail)
|
||||
self.play(dot.animate.shift(RIGHT * 3 + UP * 2), run_time=3)
|
||||
```
|
||||
|
||||
## TracedPath
|
||||
|
||||
Built-in updater for drawing paths:
|
||||
|
||||
```python
|
||||
class TracedPathExample(Scene):
|
||||
def construct(self):
|
||||
dot = Dot()
|
||||
path = TracedPath(dot.get_center, stroke_width=2, stroke_color=BLUE)
|
||||
|
||||
self.add(dot, path)
|
||||
self.play(
|
||||
dot.animate.shift(RIGHT * 2),
|
||||
dot.animate.shift(UP * 2),
|
||||
run_time=3
|
||||
)
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use ValueTracker for animated parameters** - Clean and controllable
|
||||
2. **Use always_redraw for complex shapes** - When updaters get complicated
|
||||
3. **Clear updaters when done** - Prevent performance issues
|
||||
4. **Keep updater functions simple** - Complex logic can slow rendering
|
||||
5. **Use dt for time-based animations** - Frame-rate independent
|
||||
@@ -0,0 +1,64 @@
|
||||
"""
|
||||
Basic Scene Template for Manim Community
|
||||
|
||||
Copy this file and modify to create your own scene.
|
||||
|
||||
Render: manim -pql your_file.py YourScene
|
||||
"""
|
||||
|
||||
from manim import *
|
||||
|
||||
|
||||
class YourScene(Scene):
|
||||
"""
|
||||
Basic scene template.
|
||||
|
||||
Attributes to configure:
|
||||
- background_color: Scene background (default: BLACK)
|
||||
"""
|
||||
|
||||
def construct(self):
|
||||
# ============================================================
|
||||
# SETUP: Configure scene, create initial objects
|
||||
# ============================================================
|
||||
|
||||
# Optional: Set background color
|
||||
# self.camera.background_color = "#1a1a2e"
|
||||
|
||||
# Create your mobjects
|
||||
title = Text("Your Animation Title", font_size=48)
|
||||
shape = Circle(color=BLUE, fill_opacity=0.5)
|
||||
|
||||
# Position objects
|
||||
title.to_edge(UP)
|
||||
shape.move_to(ORIGIN)
|
||||
|
||||
# ============================================================
|
||||
# ANIMATION: Animate your objects
|
||||
# ============================================================
|
||||
|
||||
# Write title
|
||||
self.play(Write(title))
|
||||
self.wait(0.5)
|
||||
|
||||
# Create shape
|
||||
self.play(Create(shape))
|
||||
self.wait(0.5)
|
||||
|
||||
# Transform or animate
|
||||
self.play(shape.animate.scale(1.5).set_color(RED))
|
||||
self.wait()
|
||||
|
||||
# ============================================================
|
||||
# CLEANUP: Final animations, fade out
|
||||
# ============================================================
|
||||
|
||||
self.play(
|
||||
FadeOut(title),
|
||||
FadeOut(shape),
|
||||
)
|
||||
self.wait()
|
||||
|
||||
|
||||
# Run this specific scene:
|
||||
# manim -pql basic_scene.py YourScene
|
||||
@@ -0,0 +1,100 @@
|
||||
"""
|
||||
Moving Camera Scene Template for Manim Community
|
||||
|
||||
Use this for scenes that require zooming, panning, or following objects.
|
||||
|
||||
Render: manim -pql your_file.py YourCameraScene
|
||||
"""
|
||||
|
||||
from manim import *
|
||||
|
||||
|
||||
class YourCameraScene(MovingCameraScene):
|
||||
"""
|
||||
Template for scenes with camera movement.
|
||||
|
||||
Inherits from MovingCameraScene which provides:
|
||||
- self.camera.frame: The camera frame mobject
|
||||
- Ability to zoom, pan, and follow objects
|
||||
"""
|
||||
|
||||
def construct(self):
|
||||
# ============================================================
|
||||
# SETUP: Create objects to showcase camera movement
|
||||
# ============================================================
|
||||
|
||||
# Create a grid of shapes to demonstrate camera movement
|
||||
shapes = VGroup(*[
|
||||
Circle(radius=0.3, color=color, fill_opacity=0.5)
|
||||
for color in [RED, BLUE, GREEN, YELLOW, PURPLE]
|
||||
]).arrange(RIGHT, buff=1)
|
||||
|
||||
# Add labels
|
||||
labels = VGroup(*[
|
||||
Text(str(i + 1), font_size=24).move_to(shape)
|
||||
for i, shape in enumerate(shapes)
|
||||
])
|
||||
|
||||
# Title
|
||||
title = Text("Camera Movement Demo", font_size=36).to_edge(UP)
|
||||
|
||||
self.add(shapes, labels)
|
||||
self.play(Write(title))
|
||||
self.wait()
|
||||
|
||||
# ============================================================
|
||||
# CAMERA OPERATIONS: Zoom, pan, follow
|
||||
# ============================================================
|
||||
|
||||
# --- ZOOM IN ---
|
||||
# Save original camera state
|
||||
self.camera.frame.save_state()
|
||||
|
||||
# Zoom into first shape
|
||||
self.play(
|
||||
self.camera.frame.animate.set(width=4).move_to(shapes[0])
|
||||
)
|
||||
self.wait()
|
||||
|
||||
# --- PAN ---
|
||||
# Move camera to another shape
|
||||
self.play(
|
||||
self.camera.frame.animate.move_to(shapes[2])
|
||||
)
|
||||
self.wait()
|
||||
|
||||
# --- ZOOM OUT ---
|
||||
# Restore original camera
|
||||
self.play(Restore(self.camera.frame))
|
||||
self.wait()
|
||||
|
||||
# --- FOLLOW OBJECT ---
|
||||
# Create moving dot
|
||||
dot = Dot(color=RED, radius=0.15).move_to(LEFT * 5)
|
||||
self.add(dot)
|
||||
|
||||
# Set camera to follow the dot
|
||||
self.camera.frame.add_updater(
|
||||
lambda m: m.move_to(dot.get_center())
|
||||
)
|
||||
|
||||
# Move the dot (camera follows automatically)
|
||||
self.play(dot.animate.move_to(RIGHT * 5), run_time=3)
|
||||
self.wait()
|
||||
|
||||
# Stop following
|
||||
self.camera.frame.clear_updaters()
|
||||
|
||||
# ============================================================
|
||||
# CLEANUP: Reset and fade out
|
||||
# ============================================================
|
||||
|
||||
self.play(
|
||||
self.camera.frame.animate.move_to(ORIGIN).set(width=14)
|
||||
)
|
||||
self.play(FadeOut(shapes, labels, title, dot))
|
||||
self.wait()
|
||||
|
||||
|
||||
# Run this specific scene:
|
||||
# manim -pql camera_scene.py YourCameraScene
|
||||
@@ -0,0 +1,138 @@
|
||||
"""
|
||||
3D Scene Template for Manim Community
|
||||
|
||||
Use this for 3D visualizations with camera rotation and surfaces.
|
||||
|
||||
Render: manim -pql your_file.py Your3DScene
|
||||
"""
|
||||
|
||||
from manim import *
|
||||
import numpy as np
|
||||
|
||||
|
||||
class Your3DScene(ThreeDScene):
|
||||
"""
|
||||
Template for 3D scenes.
|
||||
|
||||
Inherits from ThreeDScene which provides:
|
||||
- set_camera_orientation(phi, theta, gamma)
|
||||
- move_camera()
|
||||
- begin_ambient_camera_rotation() / stop_ambient_camera_rotation()
|
||||
- add_fixed_in_frame_mobjects() for 2D overlays
|
||||
"""
|
||||
|
||||
def construct(self):
|
||||
# ============================================================
|
||||
# CAMERA SETUP
|
||||
# ============================================================
|
||||
|
||||
# Set initial camera orientation
|
||||
# phi: angle from z-axis (0 = top-down, 90 = side view)
|
||||
# theta: rotation around z-axis
|
||||
self.set_camera_orientation(
|
||||
phi=70 * DEGREES,
|
||||
theta=-45 * DEGREES
|
||||
)
|
||||
|
||||
# ============================================================
|
||||
# 3D AXES
|
||||
# ============================================================
|
||||
|
||||
axes = ThreeDAxes(
|
||||
x_range=[-3, 3, 1],
|
||||
y_range=[-3, 3, 1],
|
||||
z_range=[-2, 2, 1],
|
||||
x_length=6,
|
||||
y_length=6,
|
||||
z_length=4,
|
||||
)
|
||||
|
||||
# Axis labels (stay fixed to camera orientation)
|
||||
axis_labels = axes.get_axis_labels(
|
||||
x_label="x",
|
||||
y_label="y",
|
||||
z_label="z"
|
||||
)
|
||||
|
||||
self.play(Create(axes))
|
||||
self.add(axis_labels)
|
||||
self.wait()
|
||||
|
||||
# ============================================================
|
||||
# 3D OBJECTS
|
||||
# ============================================================
|
||||
|
||||
# --- Basic 3D shapes ---
|
||||
sphere = Sphere(radius=0.5, color=BLUE).shift(LEFT * 2)
|
||||
cube = Cube(side_length=0.8, color=RED, fill_opacity=0.8)
|
||||
|
||||
self.play(Create(sphere), Create(cube))
|
||||
self.wait()
|
||||
|
||||
# --- 3D Surface ---
|
||||
# z = sin(sqrt(x^2 + y^2))
|
||||
surface = Surface(
|
||||
lambda u, v: axes.c2p(
|
||||
u, v,
|
||||
np.sin(np.sqrt(u ** 2 + v ** 2))
|
||||
),
|
||||
u_range=[-2.5, 2.5],
|
||||
v_range=[-2.5, 2.5],
|
||||
resolution=(20, 20),
|
||||
fill_opacity=0.6,
|
||||
)
|
||||
surface.set_color_by_gradient(BLUE, TEAL, GREEN)
|
||||
|
||||
self.play(
|
||||
FadeOut(sphere),
|
||||
FadeOut(cube),
|
||||
Create(surface),
|
||||
run_time=2
|
||||
)
|
||||
self.wait()
|
||||
|
||||
# ============================================================
|
||||
# 2D OVERLAY (Fixed to screen)
|
||||
# ============================================================
|
||||
|
||||
# Title that stays fixed to screen (doesn't rotate with 3D scene)
|
||||
title = Text("3D Surface Visualization", font_size=36)
|
||||
title.to_corner(UL)
|
||||
self.add_fixed_in_frame_mobjects(title)
|
||||
self.play(Write(title))
|
||||
|
||||
# Math equation overlay
|
||||
equation = MathTex(r"z = \sin\sqrt{x^2 + y^2}")
|
||||
equation.to_corner(UR)
|
||||
self.add_fixed_in_frame_mobjects(equation)
|
||||
self.play(Write(equation))
|
||||
|
||||
# ============================================================
|
||||
# CAMERA MOVEMENT
|
||||
# ============================================================
|
||||
|
||||
# --- Manual camera movement ---
|
||||
self.move_camera(phi=45 * DEGREES, theta=30 * DEGREES, run_time=2)
|
||||
self.wait()
|
||||
|
||||
# --- Continuous rotation ---
|
||||
self.begin_ambient_camera_rotation(rate=0.2) # radians per second
|
||||
self.wait(5)
|
||||
self.stop_ambient_camera_rotation()
|
||||
|
||||
# ============================================================
|
||||
# CLEANUP
|
||||
# ============================================================
|
||||
|
||||
self.play(
|
||||
FadeOut(surface),
|
||||
FadeOut(axes),
|
||||
FadeOut(axis_labels),
|
||||
FadeOut(title),
|
||||
FadeOut(equation),
|
||||
)
|
||||
self.wait()
|
||||
|
||||
|
||||
# Run this specific scene:
|
||||
# manim -pql threed_scene.py Your3DScene
|
||||
Reference in New Issue
Block a user