feat(skills): add manim-video skill for mathematical and technical animations
Production pipeline for creating 3Blue1Brown-style animated videos using Manim Community Edition. The agent handles the full workflow: creative planning, Python code generation, rendering, scene stitching, audio muxing, and iterative refinement. Modes: concept explainers, equation derivations, algorithm visualizations, data stories, architecture diagrams, paper explainers, 3D visualizations. 9 reference files, setup verification script, README. All API references verified against ManimCommunity/manim source.
This commit is contained in:
23
skills/creative/manim-video/README.md
Normal file
23
skills/creative/manim-video/README.md
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# Manim Video Skill
|
||||||
|
|
||||||
|
Production pipeline for mathematical and technical animations using [Manim Community Edition](https://www.manim.community/).
|
||||||
|
|
||||||
|
## What it does
|
||||||
|
|
||||||
|
Creates 3Blue1Brown-style animated videos from text prompts. The agent handles the full pipeline: creative planning, Python code generation, rendering, scene stitching, and iterative refinement.
|
||||||
|
|
||||||
|
## Use cases
|
||||||
|
|
||||||
|
- **Concept explainers** — "Explain how neural networks learn"
|
||||||
|
- **Equation derivations** — "Animate the proof of the Pythagorean theorem"
|
||||||
|
- **Algorithm visualizations** — "Show how quicksort works step by step"
|
||||||
|
- **Data stories** — "Animate our before/after performance metrics"
|
||||||
|
- **Architecture diagrams** — "Show our microservice architecture building up"
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
Python 3.10+, Manim CE (`pip install manim`), LaTeX, ffmpeg.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash skills/creative/manim-video/scripts/setup.sh
|
||||||
|
```
|
||||||
231
skills/creative/manim-video/SKILL.md
Normal file
231
skills/creative/manim-video/SKILL.md
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
---
|
||||||
|
name: manim-video
|
||||||
|
description: "Production pipeline for mathematical and technical animations using Manim Community Edition. Creates 3Blue1Brown-style explainer videos, algorithm visualizations, equation derivations, architecture diagrams, and data stories. Use when users request: animated explanations, math animations, concept visualizations, algorithm walkthroughs, technical explainers, 3Blue1Brown style videos, or any programmatic animation with geometric/mathematical content."
|
||||||
|
version: 1.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# Manim Video Production Pipeline
|
||||||
|
|
||||||
|
## Creative Standard
|
||||||
|
|
||||||
|
This is educational cinema. Every frame teaches. Every animation reveals structure.
|
||||||
|
|
||||||
|
**Before writing a single line of code**, articulate the narrative arc. What misconception does this correct? What is the "aha moment"? What visual story takes the viewer from confusion to understanding? The user's prompt is a starting point — interpret it with pedagogical ambition.
|
||||||
|
|
||||||
|
**Geometry before algebra.** Show the shape first, the equation second. Visual memory encodes faster than symbolic memory. When the viewer sees the geometric pattern before the formula, the equation feels earned.
|
||||||
|
|
||||||
|
**First-render excellence is non-negotiable.** The output must be visually clear and aesthetically cohesive without revision rounds. If something looks cluttered, poorly timed, or like "AI-generated slides," it is wrong.
|
||||||
|
|
||||||
|
**Opacity layering directs attention.** Never show everything at full brightness. Primary elements at 1.0, contextual elements at 0.4, structural elements (axes, grids) at 0.15. The brain processes visual salience in layers.
|
||||||
|
|
||||||
|
**Breathing room.** Every animation needs `self.wait()` after it. The viewer needs time to absorb what just appeared. Never rush from one animation to the next. A 2-second pause after a key reveal is never wasted.
|
||||||
|
|
||||||
|
**Cohesive visual language.** All scenes share a color palette, consistent typography sizing, matching animation speeds. A technically correct video where every scene uses random different colors is an aesthetic failure.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
Run `scripts/setup.sh` to verify all dependencies. Requires: Python 3.10+, Manim Community Edition (`pip install manim`), LaTeX (`texlive-full` on Linux, `mactex` on macOS), and ffmpeg.
|
||||||
|
|
||||||
|
## Modes
|
||||||
|
|
||||||
|
| Mode | Input | Output | Reference |
|
||||||
|
|------|-------|--------|-----------|
|
||||||
|
| **Concept explainer** | Topic/concept | Animated explanation with geometric intuition | `references/scene-planning.md` |
|
||||||
|
| **Equation derivation** | Math expressions | Step-by-step animated proof | `references/equations.md` |
|
||||||
|
| **Algorithm visualization** | Algorithm description | Step-by-step execution with data structures | `references/graphs-and-data.md` |
|
||||||
|
| **Data story** | Data/metrics | Animated charts, comparisons, counters | `references/graphs-and-data.md` |
|
||||||
|
| **Architecture diagram** | System description | Components building up with connections | `references/mobjects.md` |
|
||||||
|
| **Paper explainer** | Research paper | Key findings and methods animated | `references/scene-planning.md` |
|
||||||
|
| **3D visualization** | 3D concept | Rotating surfaces, parametric curves, spatial geometry | `references/camera-and-3d.md` |
|
||||||
|
|
||||||
|
## Stack
|
||||||
|
|
||||||
|
Single Python script per project. No browser, no Node.js, no GPU required.
|
||||||
|
|
||||||
|
| Layer | Tool | Purpose |
|
||||||
|
|-------|------|---------|
|
||||||
|
| Core | Manim Community Edition | Scene rendering, animation engine |
|
||||||
|
| Math | LaTeX (texlive/MiKTeX) | Equation rendering via `MathTex` |
|
||||||
|
| Video I/O | ffmpeg | Scene stitching, format conversion, audio muxing |
|
||||||
|
| TTS | ElevenLabs / Qwen3-TTS (optional) | Narration voiceover |
|
||||||
|
|
||||||
|
## Pipeline
|
||||||
|
|
||||||
|
```
|
||||||
|
PLAN --> CODE --> RENDER --> STITCH --> AUDIO (optional) --> REVIEW
|
||||||
|
```
|
||||||
|
|
||||||
|
1. **PLAN** — Write `plan.md` with narrative arc, scene list, visual elements, color palette, voiceover script
|
||||||
|
2. **CODE** — Write `script.py` with one class per scene, each independently renderable
|
||||||
|
3. **RENDER** — `manim -ql script.py Scene1 Scene2 ...` for draft, `-qh` for production
|
||||||
|
4. **STITCH** — ffmpeg concat of scene clips into `final.mp4`
|
||||||
|
5. **AUDIO** (optional) — Add voiceover and/or background music via ffmpeg. See `references/rendering.md`
|
||||||
|
6. **REVIEW** — Render preview stills, verify against plan, adjust
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
project-name/
|
||||||
|
plan.md # Narrative arc, scene breakdown
|
||||||
|
script.py # All scenes in one file
|
||||||
|
concat.txt # ffmpeg scene list
|
||||||
|
final.mp4 # Stitched output
|
||||||
|
media/ # Auto-generated by Manim
|
||||||
|
videos/script/480p15/
|
||||||
|
```
|
||||||
|
|
||||||
|
## Creative Direction
|
||||||
|
|
||||||
|
### Color Palettes
|
||||||
|
|
||||||
|
| Palette | Background | Primary | Secondary | Accent | Use case |
|
||||||
|
|---------|-----------|---------|-----------|--------|----------|
|
||||||
|
| **Classic 3B1B** | `#1C1C1C` | `#58C4DD` (BLUE) | `#83C167` (GREEN) | `#FFFF00` (YELLOW) | General math/CS |
|
||||||
|
| **Warm academic** | `#2D2B55` | `#FF6B6B` | `#FFD93D` | `#6BCB77` | Approachable |
|
||||||
|
| **Neon tech** | `#0A0A0A` | `#00F5FF` | `#FF00FF` | `#39FF14` | Systems, architecture |
|
||||||
|
| **Monochrome** | `#1A1A2E` | `#EAEAEA` | `#888888` | `#FFFFFF` | Minimalist |
|
||||||
|
|
||||||
|
### Animation Speed
|
||||||
|
|
||||||
|
| Context | run_time | self.wait() after |
|
||||||
|
|---------|----------|-------------------|
|
||||||
|
| Title/intro appear | 1.5s | 1.0s |
|
||||||
|
| Key equation reveal | 2.0s | 2.0s |
|
||||||
|
| Transform/morph | 1.5s | 1.5s |
|
||||||
|
| Supporting label | 0.8s | 0.5s |
|
||||||
|
| FadeOut cleanup | 0.5s | 0.3s |
|
||||||
|
| "Aha moment" reveal | 2.5s | 3.0s |
|
||||||
|
|
||||||
|
### Typography Scale
|
||||||
|
|
||||||
|
| Role | Font size | Usage |
|
||||||
|
|------|-----------|-------|
|
||||||
|
| Title | 48 | Scene titles, opening text |
|
||||||
|
| Heading | 36 | Section headers within a scene |
|
||||||
|
| Body | 30 | Explanatory text |
|
||||||
|
| Label | 24 | Annotations, axis labels |
|
||||||
|
| Caption | 20 | Subtitles, fine print |
|
||||||
|
|
||||||
|
### Fonts
|
||||||
|
|
||||||
|
Always specify fonts explicitly — the default renders poorly. See `references/visual-design.md` for full recommendations.
|
||||||
|
|
||||||
|
```python
|
||||||
|
Text("Title", font_size=48, font="Inter", weight=BOLD) # body text
|
||||||
|
Text("code()", font_size=24, font="JetBrains Mono") # monospaced
|
||||||
|
MathTex(r"\nabla L") # math (uses LaTeX)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Per-Scene Variation
|
||||||
|
|
||||||
|
Never use identical config for all scenes. For each scene:
|
||||||
|
- **Different dominant color** from the palette
|
||||||
|
- **Different layout** — don't always center everything
|
||||||
|
- **Different animation entry** — vary between Write, FadeIn, GrowFromCenter, Create
|
||||||
|
- **Different visual weight** — some scenes dense, others sparse
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
### Step 1: Plan (plan.md)
|
||||||
|
|
||||||
|
Before any code, write `plan.md`. See `references/scene-planning.md` for the comprehensive template.
|
||||||
|
|
||||||
|
### Step 2: Code (script.py)
|
||||||
|
|
||||||
|
One class per scene. Every scene is independently renderable.
|
||||||
|
|
||||||
|
```python
|
||||||
|
from manim import *
|
||||||
|
|
||||||
|
BG = "#1C1C1C"
|
||||||
|
PRIMARY = "#58C4DD"
|
||||||
|
SECONDARY = "#83C167"
|
||||||
|
ACCENT = "#FFFF00"
|
||||||
|
|
||||||
|
class Scene1_Introduction(Scene):
|
||||||
|
def construct(self):
|
||||||
|
self.camera.background_color = BG
|
||||||
|
title = Text("Why Does This Work?", font_size=48, color=PRIMARY)
|
||||||
|
self.add_subcaption("Why does this work?", duration=2)
|
||||||
|
self.play(Write(title), run_time=1.5)
|
||||||
|
self.wait(1.0)
|
||||||
|
self.play(FadeOut(title), run_time=0.5)
|
||||||
|
```
|
||||||
|
|
||||||
|
Key patterns:
|
||||||
|
- **Subtitles** on every animation: `self.add_subcaption("text", duration=N)` or `subcaption="text"` on `self.play()`
|
||||||
|
- **Shared color constants** at file top for cross-scene consistency
|
||||||
|
- **`self.camera.background_color`** set in every scene
|
||||||
|
- **Clean exits** — FadeOut all mobjects at scene end: `self.play(FadeOut(Group(*self.mobjects)))`
|
||||||
|
|
||||||
|
### Step 3: Render
|
||||||
|
|
||||||
|
```bash
|
||||||
|
manim -ql script.py Scene1_Introduction Scene2_CoreConcept # draft
|
||||||
|
manim -qh script.py Scene1_Introduction Scene2_CoreConcept # production
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 4: Stitch
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cat > concat.txt << 'EOF'
|
||||||
|
file 'media/videos/script/480p15/Scene1_Introduction.mp4'
|
||||||
|
file 'media/videos/script/480p15/Scene2_CoreConcept.mp4'
|
||||||
|
EOF
|
||||||
|
ffmpeg -y -f concat -safe 0 -i concat.txt -c copy final.mp4
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 5: Review
|
||||||
|
|
||||||
|
```bash
|
||||||
|
manim -ql --format=png -s script.py Scene2_CoreConcept # preview still
|
||||||
|
```
|
||||||
|
|
||||||
|
## Critical Implementation Notes
|
||||||
|
|
||||||
|
### Raw Strings for LaTeX
|
||||||
|
```python
|
||||||
|
# WRONG: MathTex("\frac{1}{2}")
|
||||||
|
# RIGHT:
|
||||||
|
MathTex(r"\frac{1}{2}")
|
||||||
|
```
|
||||||
|
|
||||||
|
### buff >= 0.5 for Edge Text
|
||||||
|
```python
|
||||||
|
label.to_edge(DOWN, buff=0.5) # never < 0.5
|
||||||
|
```
|
||||||
|
|
||||||
|
### FadeOut Before Replacing Text
|
||||||
|
```python
|
||||||
|
self.play(ReplacementTransform(note1, note2)) # not Write(note2) on top
|
||||||
|
```
|
||||||
|
|
||||||
|
### Never Animate Non-Added Mobjects
|
||||||
|
```python
|
||||||
|
self.play(Create(circle)) # must add first
|
||||||
|
self.play(circle.animate.set_color(RED)) # then animate
|
||||||
|
```
|
||||||
|
|
||||||
|
## Performance Targets
|
||||||
|
|
||||||
|
| Quality | Resolution | FPS | Speed |
|
||||||
|
|---------|-----------|-----|-------|
|
||||||
|
| `-ql` (draft) | 854x480 | 15 | 5-15s/scene |
|
||||||
|
| `-qm` (medium) | 1280x720 | 30 | 15-60s/scene |
|
||||||
|
| `-qh` (production) | 1920x1080 | 60 | 30-120s/scene |
|
||||||
|
|
||||||
|
Always iterate at `-ql`. Only render `-qh` for final output.
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
| File | Contents |
|
||||||
|
|------|----------|
|
||||||
|
| `references/animations.md` | Core animations, rate functions, composition, `.animate` syntax, timing patterns |
|
||||||
|
| `references/mobjects.md` | Text, shapes, VGroup/Group, positioning, styling, custom mobjects |
|
||||||
|
| `references/visual-design.md` | 12 design principles, opacity layering, layout templates, color palettes |
|
||||||
|
| `references/equations.md` | LaTeX in Manim, TransformMatchingTex, derivation patterns |
|
||||||
|
| `references/graphs-and-data.md` | Axes, plotting, BarChart, animated data, algorithm visualization |
|
||||||
|
| `references/camera-and-3d.md` | MovingCameraScene, ThreeDScene, 3D surfaces, camera control |
|
||||||
|
| `references/scene-planning.md` | Narrative arcs, layout templates, scene transitions, planning template |
|
||||||
|
| `references/rendering.md` | CLI reference, quality presets, ffmpeg, voiceover workflow, GIF export |
|
||||||
|
| `references/troubleshooting.md` | LaTeX errors, animation errors, common mistakes, debugging |
|
||||||
122
skills/creative/manim-video/references/animations.md
Normal file
122
skills/creative/manim-video/references/animations.md
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
# Animations Reference
|
||||||
|
|
||||||
|
## Core Concept
|
||||||
|
|
||||||
|
An animation is a Python object that computes intermediate visual states of a mobject over time. Animations are objects passed to `self.play()`, not functions.
|
||||||
|
|
||||||
|
`run_time` controls seconds (default: 1). Always specify it explicitly for important animations.
|
||||||
|
|
||||||
|
## Creation Animations
|
||||||
|
|
||||||
|
```python
|
||||||
|
self.play(Create(circle)) # traces outline
|
||||||
|
self.play(Write(equation)) # simulates handwriting (for Text/MathTex)
|
||||||
|
self.play(FadeIn(group)) # opacity 0 -> 1
|
||||||
|
self.play(GrowFromCenter(dot)) # scale 0 -> 1 from center
|
||||||
|
self.play(DrawBorderThenFill(sq)) # outline first, then fill
|
||||||
|
```
|
||||||
|
|
||||||
|
## Removal Animations
|
||||||
|
|
||||||
|
```python
|
||||||
|
self.play(FadeOut(mobject)) # opacity 1 -> 0
|
||||||
|
self.play(Uncreate(circle)) # reverse of Create
|
||||||
|
self.play(ShrinkToCenter(group)) # scale 1 -> 0
|
||||||
|
```
|
||||||
|
|
||||||
|
## Transform Animations
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Transform -- modifies the original in place
|
||||||
|
self.play(Transform(circle, square))
|
||||||
|
# After: circle IS the square (same object, new appearance)
|
||||||
|
|
||||||
|
# ReplacementTransform -- replaces old with new
|
||||||
|
self.play(ReplacementTransform(circle, square))
|
||||||
|
# After: circle removed, square on screen
|
||||||
|
|
||||||
|
# TransformMatchingTex -- smart equation morphing
|
||||||
|
eq1 = MathTex(r"a^2 + b^2")
|
||||||
|
eq2 = MathTex(r"a^2 + b^2 = c^2")
|
||||||
|
self.play(TransformMatchingTex(eq1, eq2))
|
||||||
|
```
|
||||||
|
|
||||||
|
**Critical**: After `Transform(A, B)`, variable `A` references the on-screen mobject. Variable `B` is NOT on screen. Use `ReplacementTransform` when you want to work with `B` afterwards.
|
||||||
|
|
||||||
|
## The .animate Syntax
|
||||||
|
|
||||||
|
```python
|
||||||
|
self.play(circle.animate.set_color(RED))
|
||||||
|
self.play(circle.animate.shift(RIGHT * 2).scale(0.5)) # chain multiple
|
||||||
|
```
|
||||||
|
|
||||||
|
## Emphasis Animations
|
||||||
|
|
||||||
|
```python
|
||||||
|
self.play(Indicate(mobject)) # brief yellow flash + scale
|
||||||
|
self.play(Circumscribe(mobject)) # draw rectangle around it
|
||||||
|
self.play(Flash(point)) # radial flash
|
||||||
|
self.play(Wiggle(mobject)) # shake side to side
|
||||||
|
```
|
||||||
|
|
||||||
|
## Rate Functions
|
||||||
|
|
||||||
|
```python
|
||||||
|
self.play(FadeIn(mob), rate_func=smooth) # default: ease in/out
|
||||||
|
self.play(FadeIn(mob), rate_func=linear) # constant speed
|
||||||
|
self.play(FadeIn(mob), rate_func=rush_into) # start slow, end fast
|
||||||
|
self.play(FadeIn(mob), rate_func=rush_from) # start fast, end slow
|
||||||
|
self.play(FadeIn(mob), rate_func=there_and_back) # animate then reverse
|
||||||
|
```
|
||||||
|
|
||||||
|
## Composition
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Simultaneous
|
||||||
|
self.play(FadeIn(title), Create(circle), run_time=2)
|
||||||
|
|
||||||
|
# AnimationGroup with lag
|
||||||
|
self.play(AnimationGroup(*[FadeIn(i) for i in items], lag_ratio=0.2))
|
||||||
|
|
||||||
|
# LaggedStart
|
||||||
|
self.play(LaggedStart(*[Write(l) for l in lines], lag_ratio=0.3, run_time=3))
|
||||||
|
|
||||||
|
# Succession (sequential in one play call)
|
||||||
|
self.play(Succession(FadeIn(title), Wait(0.5), Write(subtitle)))
|
||||||
|
```
|
||||||
|
|
||||||
|
## Updaters
|
||||||
|
|
||||||
|
```python
|
||||||
|
tracker = ValueTracker(0)
|
||||||
|
dot = Dot().add_updater(lambda m: m.move_to(axes.c2p(tracker.get_value(), 0)))
|
||||||
|
self.play(tracker.animate.set_value(5), run_time=3)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Subtitles
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Method 1: standalone
|
||||||
|
self.add_subcaption("Key insight", duration=2)
|
||||||
|
self.play(Write(equation), run_time=2.0)
|
||||||
|
|
||||||
|
# Method 2: inline
|
||||||
|
self.play(Write(equation), subcaption="Key insight", subcaption_duration=2)
|
||||||
|
```
|
||||||
|
|
||||||
|
Manim auto-generates `.srt` subtitle files. Always add subcaptions for accessibility.
|
||||||
|
|
||||||
|
## Timing Patterns
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Pause-after-reveal
|
||||||
|
self.play(Write(key_equation), run_time=2.0)
|
||||||
|
self.wait(2.0)
|
||||||
|
|
||||||
|
# Dim-and-focus
|
||||||
|
self.play(old_content.animate.set_opacity(0.3), FadeIn(new_content))
|
||||||
|
|
||||||
|
# Clean exit
|
||||||
|
self.play(FadeOut(Group(*self.mobjects)), run_time=0.5)
|
||||||
|
self.wait(0.3)
|
||||||
|
```
|
||||||
76
skills/creative/manim-video/references/camera-and-3d.md
Normal file
76
skills/creative/manim-video/references/camera-and-3d.md
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
# Camera and 3D Reference
|
||||||
|
|
||||||
|
## MovingCameraScene (2D Camera Control)
|
||||||
|
|
||||||
|
```python
|
||||||
|
class ZoomExample(MovingCameraScene):
|
||||||
|
def construct(self):
|
||||||
|
circle = Circle(radius=2, color=BLUE)
|
||||||
|
self.play(Create(circle))
|
||||||
|
# Zoom in
|
||||||
|
self.play(self.camera.frame.animate.set(width=4).move_to(circle.get_top()), run_time=2)
|
||||||
|
self.wait(2)
|
||||||
|
# Zoom back out
|
||||||
|
self.play(self.camera.frame.animate.set(width=14.222).move_to(ORIGIN), run_time=2)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Camera Operations
|
||||||
|
|
||||||
|
```python
|
||||||
|
self.camera.frame.animate.set(width=6) # zoom in
|
||||||
|
self.camera.frame.animate.set(width=20) # zoom out
|
||||||
|
self.camera.frame.animate.move_to(target) # pan
|
||||||
|
self.camera.frame.save_state() # save
|
||||||
|
self.play(Restore(self.camera.frame)) # restore
|
||||||
|
```
|
||||||
|
|
||||||
|
## ThreeDScene
|
||||||
|
|
||||||
|
```python
|
||||||
|
class ThreeDExample(ThreeDScene):
|
||||||
|
def construct(self):
|
||||||
|
self.set_camera_orientation(phi=60*DEGREES, theta=-45*DEGREES)
|
||||||
|
axes = ThreeDAxes()
|
||||||
|
surface = Surface(
|
||||||
|
lambda u, v: axes.c2p(u, v, np.sin(u) * np.cos(v)),
|
||||||
|
u_range=[-PI, PI], v_range=[-PI, PI], resolution=(30, 30)
|
||||||
|
)
|
||||||
|
surface.set_color_by_gradient(BLUE, GREEN, YELLOW)
|
||||||
|
self.play(Create(axes), Create(surface))
|
||||||
|
self.begin_ambient_camera_rotation(rate=0.2)
|
||||||
|
self.wait(5)
|
||||||
|
self.stop_ambient_camera_rotation()
|
||||||
|
```
|
||||||
|
|
||||||
|
### Camera Control in 3D
|
||||||
|
|
||||||
|
```python
|
||||||
|
self.set_camera_orientation(phi=70*DEGREES, theta=-45*DEGREES)
|
||||||
|
self.move_camera(phi=45*DEGREES, theta=30*DEGREES, run_time=2)
|
||||||
|
self.begin_ambient_camera_rotation(rate=0.2)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3D Mobjects
|
||||||
|
|
||||||
|
```python
|
||||||
|
sphere = Sphere(radius=1).set_color(BLUE).set_opacity(0.7)
|
||||||
|
cube = Cube(side_length=2, fill_color=GREEN, fill_opacity=0.5)
|
||||||
|
arrow = Arrow3D(start=ORIGIN, end=[2, 1, 1], color=RED)
|
||||||
|
# 2D text facing camera:
|
||||||
|
label = Text("Label", font_size=30)
|
||||||
|
self.add_fixed_in_frame_mobjects(label)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Parametric Curves
|
||||||
|
|
||||||
|
```python
|
||||||
|
helix = ParametricFunction(
|
||||||
|
lambda t: [np.cos(t), np.sin(t), t / (2*PI)],
|
||||||
|
t_range=[0, 4*PI], color=YELLOW
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## When to Use 3D
|
||||||
|
- Surfaces, vector fields, spatial geometry, 3D transforms
|
||||||
|
## When NOT to Use 3D
|
||||||
|
- 2D concepts, text-heavy scenes, flat data (bar charts, time series)
|
||||||
80
skills/creative/manim-video/references/equations.md
Normal file
80
skills/creative/manim-video/references/equations.md
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
# Equations and LaTeX Reference
|
||||||
|
|
||||||
|
## Basic LaTeX
|
||||||
|
|
||||||
|
```python
|
||||||
|
eq = MathTex(r"E = mc^2")
|
||||||
|
eq = MathTex(r"f(x) &= x^2 + 2x + 1 \\ &= (x + 1)^2") # multi-line aligned
|
||||||
|
```
|
||||||
|
|
||||||
|
**Always use raw strings (`r""`).**
|
||||||
|
|
||||||
|
## Step-by-Step Derivations
|
||||||
|
|
||||||
|
```python
|
||||||
|
step1 = MathTex(r"a^2 + b^2 = c^2")
|
||||||
|
step2 = MathTex(r"a^2 = c^2 - b^2")
|
||||||
|
self.play(Write(step1), run_time=1.5)
|
||||||
|
self.wait(1.5)
|
||||||
|
self.play(TransformMatchingTex(step1, step2), run_time=1.5)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Selective Color
|
||||||
|
|
||||||
|
```python
|
||||||
|
eq = MathTex(r"a^2", r"+", r"b^2", r"=", r"c^2")
|
||||||
|
eq[0].set_color(RED)
|
||||||
|
eq[4].set_color(GREEN)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Building Incrementally
|
||||||
|
|
||||||
|
```python
|
||||||
|
parts = MathTex(r"f(x)", r"=", r"\sum_{n=0}^{\infty}", r"\frac{f^{(n)}(a)}{n!}", r"(x-a)^n")
|
||||||
|
self.play(Write(parts[0:2]))
|
||||||
|
self.wait(0.5)
|
||||||
|
self.play(Write(parts[2]))
|
||||||
|
self.wait(0.5)
|
||||||
|
self.play(Write(parts[3:]))
|
||||||
|
```
|
||||||
|
|
||||||
|
## Highlighting
|
||||||
|
|
||||||
|
```python
|
||||||
|
highlight = SurroundingRectangle(eq[2], color=YELLOW, buff=0.1)
|
||||||
|
self.play(Create(highlight))
|
||||||
|
self.play(Indicate(eq[4], color=YELLOW))
|
||||||
|
```
|
||||||
|
|
||||||
|
## Annotation
|
||||||
|
|
||||||
|
```python
|
||||||
|
brace = Brace(eq, DOWN, color=YELLOW)
|
||||||
|
label = brace.get_text("Fundamental Theorem", font_size=24)
|
||||||
|
self.play(GrowFromCenter(brace), Write(label))
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common LaTeX
|
||||||
|
|
||||||
|
```python
|
||||||
|
MathTex(r"\frac{a}{b}") # fraction
|
||||||
|
MathTex(r"\alpha, \beta, \gamma") # Greek
|
||||||
|
MathTex(r"\sum_{i=1}^{n} x_i") # summation
|
||||||
|
MathTex(r"\int_{0}^{\infty} e^{-x} dx") # integral
|
||||||
|
MathTex(r"\vec{v}") # vector
|
||||||
|
MathTex(r"\lim_{x \to \infty} f(x)") # limit
|
||||||
|
```
|
||||||
|
|
||||||
|
## Derivation Pattern
|
||||||
|
|
||||||
|
```python
|
||||||
|
class DerivationScene(Scene):
|
||||||
|
def construct(self):
|
||||||
|
self.camera.background_color = BG
|
||||||
|
s1 = MathTex(r"ax^2 + bx + c = 0")
|
||||||
|
self.play(Write(s1))
|
||||||
|
self.wait(1.5)
|
||||||
|
s2 = MathTex(r"x^2 + \frac{b}{a}x + \frac{c}{a} = 0")
|
||||||
|
s2.next_to(s1, DOWN, buff=0.8)
|
||||||
|
self.play(s1.animate.set_opacity(0.4), TransformMatchingTex(s1.copy(), s2))
|
||||||
|
```
|
||||||
91
skills/creative/manim-video/references/graphs-and-data.md
Normal file
91
skills/creative/manim-video/references/graphs-and-data.md
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
# Graphs, Plots, and Data Visualization
|
||||||
|
|
||||||
|
## Axes
|
||||||
|
|
||||||
|
```python
|
||||||
|
axes = Axes(
|
||||||
|
x_range=[-3, 3, 1], y_range=[-2, 2, 1],
|
||||||
|
x_length=8, y_length=5,
|
||||||
|
axis_config={"include_numbers": True, "font_size": 24}
|
||||||
|
)
|
||||||
|
axes.set_opacity(0.15) # structural element
|
||||||
|
x_label = axes.get_x_axis_label(r"x")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Plotting
|
||||||
|
|
||||||
|
```python
|
||||||
|
graph = axes.plot(lambda x: x**2, color=BLUE)
|
||||||
|
graph_label = axes.get_graph_label(graph, label=r"x^2", x_val=2)
|
||||||
|
area = axes.get_area(graph, x_range=[0, 2], color=BLUE, opacity=0.3)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Animated Plotting
|
||||||
|
|
||||||
|
```python
|
||||||
|
self.play(Create(graph), run_time=3) # trace the graph
|
||||||
|
|
||||||
|
# Moving dot along curve
|
||||||
|
dot = Dot(color=YELLOW).move_to(axes.c2p(0, 0))
|
||||||
|
self.play(MoveAlongPath(dot, graph), run_time=3)
|
||||||
|
|
||||||
|
# Dynamic parameter
|
||||||
|
tracker = ValueTracker(1)
|
||||||
|
dynamic = always_redraw(lambda: axes.plot(lambda x: tracker.get_value() * x**2, color=BLUE))
|
||||||
|
self.add(dynamic)
|
||||||
|
self.play(tracker.animate.set_value(3), run_time=2)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Bar Charts
|
||||||
|
|
||||||
|
```python
|
||||||
|
chart = BarChart(
|
||||||
|
values=[4, 6, 2, 8, 5], bar_names=["A", "B", "C", "D", "E"],
|
||||||
|
y_range=[0, 10, 2], bar_colors=[RED, GREEN, BLUE, YELLOW, PURPLE]
|
||||||
|
)
|
||||||
|
self.play(Create(chart), run_time=2)
|
||||||
|
self.play(chart.animate.change_bar_values([6, 3, 7, 4, 9]))
|
||||||
|
```
|
||||||
|
|
||||||
|
## Number Lines
|
||||||
|
|
||||||
|
```python
|
||||||
|
nl = NumberLine(x_range=[0, 10, 1], length=10, include_numbers=True)
|
||||||
|
pointer = Arrow(nl.n2p(3) + UP * 0.5, nl.n2p(3), color=RED, buff=0)
|
||||||
|
tracker = ValueTracker(3)
|
||||||
|
pointer.add_updater(lambda m: m.put_start_and_end_on(
|
||||||
|
nl.n2p(tracker.get_value()) + UP * 0.5, nl.n2p(tracker.get_value())))
|
||||||
|
self.play(tracker.animate.set_value(8), run_time=2)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Animated Counters
|
||||||
|
|
||||||
|
```python
|
||||||
|
counter = DecimalNumber(0, font_size=72, num_decimal_places=0)
|
||||||
|
self.play(counter.animate.set_value(1000), run_time=3, rate_func=rush_from)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Algorithm Visualization Pattern
|
||||||
|
|
||||||
|
```python
|
||||||
|
values = [5, 2, 8, 1, 9, 3]
|
||||||
|
bars = VGroup(*[
|
||||||
|
Rectangle(width=0.6, height=v * 0.4, color=BLUE, fill_opacity=0.7)
|
||||||
|
for v in values
|
||||||
|
]).arrange(RIGHT, buff=0.2, aligned_edge=DOWN).move_to(ORIGIN)
|
||||||
|
self.play(LaggedStart(*[GrowFromEdge(b, DOWN) for b in bars], lag_ratio=0.1))
|
||||||
|
# Highlight, swap, etc.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Data Story Pattern
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Before/After comparison
|
||||||
|
before = BarChart(values=[3, 5, 2], bar_colors=[RED]*3).shift(LEFT * 3)
|
||||||
|
after = BarChart(values=[8, 9, 7], bar_colors=[GREEN]*3).shift(RIGHT * 3)
|
||||||
|
self.play(Create(before)); self.wait(1)
|
||||||
|
self.play(Create(after)); self.wait(1)
|
||||||
|
arrow = Arrow(before.get_right(), after.get_left(), color=YELLOW)
|
||||||
|
label = Text("+167%", font_size=36, color=YELLOW).next_to(arrow, UP)
|
||||||
|
self.play(GrowArrow(arrow), Write(label))
|
||||||
|
```
|
||||||
106
skills/creative/manim-video/references/mobjects.md
Normal file
106
skills/creative/manim-video/references/mobjects.md
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
# Mobjects Reference
|
||||||
|
|
||||||
|
Everything visible on screen is a Mobject. They have position, color, opacity, and can be animated.
|
||||||
|
|
||||||
|
## Text
|
||||||
|
|
||||||
|
```python
|
||||||
|
title = Text("Hello World", font_size=48, color=BLUE)
|
||||||
|
eq = MathTex(r"E = mc^2", font_size=40)
|
||||||
|
|
||||||
|
# Multi-part (for selective coloring)
|
||||||
|
eq = MathTex(r"a^2", r"+", r"b^2", r"=", r"c^2")
|
||||||
|
eq[0].set_color(RED)
|
||||||
|
eq[4].set_color(BLUE)
|
||||||
|
|
||||||
|
# Mixed text and math
|
||||||
|
t = Tex(r"The area is $\pi r^2$", font_size=36)
|
||||||
|
|
||||||
|
# Styled markup
|
||||||
|
t = MarkupText('<span foreground="#58C4DD">Blue</span> text', font_size=30)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Always use raw strings (`r""`) for any string with backslashes.**
|
||||||
|
|
||||||
|
## Shapes
|
||||||
|
|
||||||
|
```python
|
||||||
|
circle = Circle(radius=1, color=BLUE, fill_opacity=0.5)
|
||||||
|
square = Square(side_length=2, color=RED)
|
||||||
|
rect = Rectangle(width=4, height=2, color=GREEN)
|
||||||
|
dot = Dot(point=ORIGIN, radius=0.08, color=YELLOW)
|
||||||
|
line = Line(LEFT * 2, RIGHT * 2, color=WHITE)
|
||||||
|
arrow = Arrow(LEFT, RIGHT, color=ORANGE)
|
||||||
|
rrect = RoundedRectangle(corner_radius=0.3, width=4, height=2)
|
||||||
|
brace = Brace(rect, DOWN, color=YELLOW)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Positioning
|
||||||
|
|
||||||
|
```python
|
||||||
|
mob.move_to(ORIGIN) # center
|
||||||
|
mob.move_to(UP * 2 + RIGHT) # relative
|
||||||
|
label.next_to(circle, DOWN, buff=0.3) # next to another
|
||||||
|
title.to_edge(UP, buff=0.5) # screen edge (buff >= 0.5!)
|
||||||
|
mob.to_corner(UL, buff=0.5) # corner
|
||||||
|
```
|
||||||
|
|
||||||
|
## VGroup vs Group
|
||||||
|
|
||||||
|
**VGroup** is for collections of shapes (VMobjects only — Circle, Square, Arrow, Line, MathTex):
|
||||||
|
```python
|
||||||
|
shapes = VGroup(circle, square, arrow)
|
||||||
|
shapes.arrange(DOWN, buff=0.5)
|
||||||
|
shapes.set_color(BLUE)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Group** is for mixed collections (Text + shapes, or any Mobject types):
|
||||||
|
```python
|
||||||
|
# Text objects are Mobjects, not VMobjects — use Group when mixing
|
||||||
|
labeled_shape = Group(circle, Text("Label").next_to(circle, DOWN))
|
||||||
|
labeled_shape.move_to(ORIGIN)
|
||||||
|
|
||||||
|
# FadeOut everything on screen (may contain mixed types)
|
||||||
|
self.play(FadeOut(Group(*self.mobjects)))
|
||||||
|
```
|
||||||
|
|
||||||
|
**Rule: if your group contains any `Text()` objects, use `Group`, not `VGroup`.** VGroup will raise a TypeError on Manim CE v0.20+. MathTex and Tex are VMobjects and work with VGroup.
|
||||||
|
|
||||||
|
Both support `arrange()`, `arrange_in_grid()`, `set_opacity()`, `shift()`, `scale()`, `move_to()`.
|
||||||
|
|
||||||
|
## Styling
|
||||||
|
|
||||||
|
```python
|
||||||
|
mob.set_color(BLUE)
|
||||||
|
mob.set_fill(RED, opacity=0.5)
|
||||||
|
mob.set_stroke(WHITE, width=2)
|
||||||
|
mob.set_opacity(0.4)
|
||||||
|
mob.set_z_index(1) # layering
|
||||||
|
```
|
||||||
|
|
||||||
|
## Specialized Mobjects
|
||||||
|
|
||||||
|
```python
|
||||||
|
nl = NumberLine(x_range=[-3, 3, 1], length=8, include_numbers=True)
|
||||||
|
table = Table([["A", "B"], ["C", "D"]], row_labels=[Text("R1"), Text("R2")])
|
||||||
|
code = Code("example.py", tab_width=4, font_size=20, language="python")
|
||||||
|
highlight = SurroundingRectangle(target, color=YELLOW, buff=0.2)
|
||||||
|
bg = BackgroundRectangle(equation, fill_opacity=0.7, buff=0.2)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Custom Mobjects
|
||||||
|
|
||||||
|
```python
|
||||||
|
class NetworkNode(Group):
|
||||||
|
def __init__(self, label_text, color=BLUE, **kwargs):
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self.circle = Circle(radius=0.4, color=color, fill_opacity=0.3)
|
||||||
|
self.label = Text(label_text, font_size=20).move_to(self.circle)
|
||||||
|
self.add(self.circle, self.label)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Constants
|
||||||
|
|
||||||
|
Directions: `UP, DOWN, LEFT, RIGHT, ORIGIN, UL, UR, DL, DR`
|
||||||
|
Colors: `RED, BLUE, GREEN, YELLOW, WHITE, GRAY, ORANGE, PINK, PURPLE, TEAL, GOLD`
|
||||||
|
Frame: `config.frame_width = 14.222, config.frame_height = 8.0`
|
||||||
93
skills/creative/manim-video/references/rendering.md
Normal file
93
skills/creative/manim-video/references/rendering.md
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
# Rendering Reference
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
```bash
|
||||||
|
manim --version # Manim CE
|
||||||
|
pdflatex --version # LaTeX
|
||||||
|
ffmpeg -version # ffmpeg
|
||||||
|
```
|
||||||
|
|
||||||
|
## CLI Reference
|
||||||
|
|
||||||
|
```bash
|
||||||
|
manim -ql script.py Scene1 Scene2 # draft (480p 15fps)
|
||||||
|
manim -qm script.py Scene1 # medium (720p 30fps)
|
||||||
|
manim -qh script.py Scene1 # production (1080p 60fps)
|
||||||
|
manim -ql --format=png -s script.py Scene1 # preview still (last frame)
|
||||||
|
manim -ql --format=gif script.py Scene1 # GIF output
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quality Presets
|
||||||
|
|
||||||
|
| Flag | Resolution | FPS | Use case |
|
||||||
|
|------|-----------|-----|----------|
|
||||||
|
| `-ql` | 854x480 | 15 | Draft iteration (layout, timing) |
|
||||||
|
| `-qm` | 1280x720 | 30 | Preview (use for text-heavy scenes) |
|
||||||
|
| `-qh` | 1920x1080 | 60 | Production |
|
||||||
|
|
||||||
|
**Text rendering quality:** `-ql` (480p15) produces noticeably poor text kerning and readability. For scenes with significant text, preview stills at `-qm` to catch issues invisible at 480p. Use `-ql` only for testing layout and animation timing.
|
||||||
|
|
||||||
|
## Output Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
media/videos/script/480p15/Scene1_Intro.mp4
|
||||||
|
media/images/script/Scene1_Intro.png (from -s flag)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Stitching with ffmpeg
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cat > concat.txt << 'EOF'
|
||||||
|
file 'media/videos/script/480p15/Scene1_Intro.mp4'
|
||||||
|
file 'media/videos/script/480p15/Scene2_Core.mp4'
|
||||||
|
EOF
|
||||||
|
ffmpeg -y -f concat -safe 0 -i concat.txt -c copy final.mp4
|
||||||
|
```
|
||||||
|
|
||||||
|
## Add Voiceover
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Mux narration
|
||||||
|
ffmpeg -y -i final.mp4 -i narration.mp3 -c:v copy -c:a aac -b:a 192k -shortest final_narrated.mp4
|
||||||
|
|
||||||
|
# Concat per-scene audio first
|
||||||
|
cat > audio_concat.txt << 'EOF'
|
||||||
|
file 'audio/scene1.mp3'
|
||||||
|
file 'audio/scene2.mp3'
|
||||||
|
EOF
|
||||||
|
ffmpeg -y -f concat -safe 0 -i audio_concat.txt -c copy full_narration.mp3
|
||||||
|
```
|
||||||
|
|
||||||
|
## Add Background Music
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ffmpeg -y -i final.mp4 -i music.mp3 \
|
||||||
|
-filter_complex "[1:a]volume=0.15[bg];[0:a][bg]amix=inputs=2:duration=shortest" \
|
||||||
|
-c:v copy final_with_music.mp4
|
||||||
|
```
|
||||||
|
|
||||||
|
## GIF Export
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ffmpeg -y -i scene.mp4 \
|
||||||
|
-vf "fps=15,scale=640:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" \
|
||||||
|
output.gif
|
||||||
|
```
|
||||||
|
|
||||||
|
## Aspect Ratios
|
||||||
|
|
||||||
|
```bash
|
||||||
|
manim -ql --resolution 1080,1920 script.py Scene # 9:16 vertical
|
||||||
|
manim -ql --resolution 1080,1080 script.py Scene # 1:1 square
|
||||||
|
```
|
||||||
|
|
||||||
|
## Render Workflow
|
||||||
|
|
||||||
|
1. Draft render all scenes at `-ql`
|
||||||
|
2. Preview stills at key moments (`-s`)
|
||||||
|
3. Fix and re-render only broken scenes
|
||||||
|
4. Stitch with ffmpeg
|
||||||
|
5. Review stitched output
|
||||||
|
6. Production render at `-qh`
|
||||||
|
7. Re-stitch + add audio
|
||||||
118
skills/creative/manim-video/references/scene-planning.md
Normal file
118
skills/creative/manim-video/references/scene-planning.md
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
# Scene Planning Reference
|
||||||
|
|
||||||
|
## Narrative Arc Structures
|
||||||
|
|
||||||
|
### Discovery Arc (most common)
|
||||||
|
1. Hook -- pose a question or surprising result
|
||||||
|
2. Intuition -- build visual understanding
|
||||||
|
3. Formalize -- introduce the equation/algorithm
|
||||||
|
4. Reveal -- the "aha moment"
|
||||||
|
5. Extend -- implications or generalizations
|
||||||
|
|
||||||
|
### Problem-Solution Arc
|
||||||
|
1. Problem -- what's broken
|
||||||
|
2. Failed attempt -- obvious approach fails
|
||||||
|
3. Key insight -- the idea that works
|
||||||
|
4. Solution -- implement it
|
||||||
|
5. Result -- show improvement
|
||||||
|
|
||||||
|
### Comparison Arc
|
||||||
|
1. Setup -- introduce two approaches
|
||||||
|
2. Approach A -- how it works
|
||||||
|
3. Approach B -- how it works
|
||||||
|
4. Contrast -- differences
|
||||||
|
5. Verdict -- which is better
|
||||||
|
|
||||||
|
### Build-Up Arc (architecture/systems)
|
||||||
|
1. Component A -- first piece
|
||||||
|
2. Component B -- second piece
|
||||||
|
3. Connection -- how they interact
|
||||||
|
4. Scale -- add more pieces
|
||||||
|
5. Full picture -- zoom out
|
||||||
|
|
||||||
|
## Scene Transitions
|
||||||
|
|
||||||
|
### Clean Break (default)
|
||||||
|
```python
|
||||||
|
self.play(FadeOut(Group(*self.mobjects)), run_time=0.5)
|
||||||
|
self.wait(0.3)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Carry-Forward
|
||||||
|
Keep one element, fade the rest. Next scene starts with it still on screen.
|
||||||
|
|
||||||
|
### Transform Bridge
|
||||||
|
End scene with a shape, start next scene by transforming it.
|
||||||
|
|
||||||
|
## Cross-Scene Consistency
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Shared constants at file top
|
||||||
|
BG = "#1C1C1C"
|
||||||
|
PRIMARY = "#58C4DD"
|
||||||
|
SECONDARY = "#83C167"
|
||||||
|
ACCENT = "#FFFF00"
|
||||||
|
TITLE_SIZE = 48
|
||||||
|
BODY_SIZE = 30
|
||||||
|
LABEL_SIZE = 24
|
||||||
|
FAST = 0.8; NORMAL = 1.5; SLOW = 2.5
|
||||||
|
```
|
||||||
|
|
||||||
|
## Scene Checklist
|
||||||
|
|
||||||
|
- [ ] Background color set
|
||||||
|
- [ ] Subcaptions on every animation
|
||||||
|
- [ ] `self.wait()` after every reveal
|
||||||
|
- [ ] Text buff >= 0.5 for edge positioning
|
||||||
|
- [ ] No text overlap
|
||||||
|
- [ ] Color constants used (not hardcoded)
|
||||||
|
- [ ] Opacity layering applied
|
||||||
|
- [ ] Clean exit at scene end
|
||||||
|
- [ ] No more than 5-6 elements visible at once
|
||||||
|
|
||||||
|
## Duration Estimation
|
||||||
|
|
||||||
|
| Content | Duration |
|
||||||
|
|---------|----------|
|
||||||
|
| Title card | 3-5s |
|
||||||
|
| Concept introduction | 10-20s |
|
||||||
|
| Equation reveal | 15-25s |
|
||||||
|
| Algorithm step | 5-10s |
|
||||||
|
| Data comparison | 10-15s |
|
||||||
|
| "Aha moment" | 15-30s |
|
||||||
|
| Conclusion | 5-10s |
|
||||||
|
|
||||||
|
## Planning Template
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# [Video Title]
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
- **Topic**: [Core concept]
|
||||||
|
- **Hook**: [Opening question]
|
||||||
|
- **Aha moment**: [Key insight]
|
||||||
|
- **Target audience**: [Prerequisites]
|
||||||
|
- **Length**: [seconds/minutes]
|
||||||
|
- **Resolution**: 480p (draft) / 1080p (final)
|
||||||
|
|
||||||
|
## Color Palette
|
||||||
|
- Background: #1C1C1C
|
||||||
|
- Primary: #58C4DD -- [purpose]
|
||||||
|
- Secondary: #83C167 -- [purpose]
|
||||||
|
- Accent: #FFFF00 -- [purpose]
|
||||||
|
|
||||||
|
## Arc: [Discovery / Problem-Solution / Comparison / Build-Up]
|
||||||
|
|
||||||
|
## Scene 1: [Name] (~Ns)
|
||||||
|
**Purpose**: [one sentence]
|
||||||
|
**Layout**: [FULL_CENTER / LEFT_RIGHT / GRID / PROGRESSIVE]
|
||||||
|
|
||||||
|
### Visual elements
|
||||||
|
- [Mobject: type, position, color]
|
||||||
|
|
||||||
|
### Animation sequence
|
||||||
|
1. [Animation] -- [what it reveals] (~Ns)
|
||||||
|
|
||||||
|
### Subtitle
|
||||||
|
"[text]"
|
||||||
|
```
|
||||||
135
skills/creative/manim-video/references/troubleshooting.md
Normal file
135
skills/creative/manim-video/references/troubleshooting.md
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
# Troubleshooting
|
||||||
|
|
||||||
|
## LaTeX Errors
|
||||||
|
|
||||||
|
**Missing raw string** (the #1 error):
|
||||||
|
```python
|
||||||
|
# WRONG: MathTex("\\frac{1}{2}") -- \\f is form-feed
|
||||||
|
# RIGHT: MathTex(r"\frac{1}{2}")
|
||||||
|
```
|
||||||
|
|
||||||
|
**Unbalanced braces**: `MathTex(r"\frac{1}{2")` -- missing closing brace.
|
||||||
|
|
||||||
|
**LaTeX not installed**: `which pdflatex` -- install texlive-full or mactex.
|
||||||
|
|
||||||
|
**Missing package**: Add to preamble:
|
||||||
|
```python
|
||||||
|
tex_template = TexTemplate()
|
||||||
|
tex_template.add_to_preamble(r"\usepackage{mathrsfs}")
|
||||||
|
MathTex(r"\mathscr{L}", tex_template=tex_template)
|
||||||
|
```
|
||||||
|
|
||||||
|
## VGroup TypeError
|
||||||
|
|
||||||
|
**Error:** `TypeError: Only values of type VMobject can be added as submobjects of VGroup`
|
||||||
|
|
||||||
|
**Cause:** `Text()` objects are `Mobject`, not `VMobject`. Mixing `Text` with shapes in a `VGroup` fails on Manim CE v0.20+.
|
||||||
|
|
||||||
|
```python
|
||||||
|
# WRONG: Text is not a VMobject
|
||||||
|
group = VGroup(circle, Text("Label"))
|
||||||
|
|
||||||
|
# RIGHT: use Group for mixed types
|
||||||
|
group = Group(circle, Text("Label"))
|
||||||
|
|
||||||
|
# RIGHT: VGroup is fine for shapes-only
|
||||||
|
shapes = VGroup(circle, square, arrow)
|
||||||
|
|
||||||
|
# RIGHT: MathTex IS a VMobject — VGroup works
|
||||||
|
equations = VGroup(MathTex(r"a"), MathTex(r"b"))
|
||||||
|
```
|
||||||
|
|
||||||
|
**Rule:** If the group contains any `Text()`, use `Group`. If it's all shapes or all `MathTex`, `VGroup` is fine.
|
||||||
|
|
||||||
|
**FadeOut everything:** Always use `Group(*self.mobjects)`, not `VGroup(*self.mobjects)`:
|
||||||
|
```python
|
||||||
|
self.play(FadeOut(Group(*self.mobjects))) # safe for mixed types
|
||||||
|
```
|
||||||
|
|
||||||
|
## Group save_state() / restore() Not Supported
|
||||||
|
|
||||||
|
**Error:** `NotImplementedError: Please override in a child class.`
|
||||||
|
|
||||||
|
**Cause:** `Group.save_state()` and `Group.restore()` are not implemented in Manim CE v0.20+. Only `VGroup` and individual `Mobject` subclasses support save/restore.
|
||||||
|
|
||||||
|
```python
|
||||||
|
# WRONG: Group doesn't support save_state
|
||||||
|
group = Group(circle, Text("label"))
|
||||||
|
group.save_state() # NotImplementedError!
|
||||||
|
|
||||||
|
# RIGHT: use FadeIn with shift/scale instead of save_state/restore
|
||||||
|
self.play(FadeIn(group, shift=UP * 0.3, scale=0.8))
|
||||||
|
|
||||||
|
# RIGHT: or save/restore on individual VMobjects
|
||||||
|
circle.save_state()
|
||||||
|
self.play(circle.animate.shift(RIGHT))
|
||||||
|
self.play(Restore(circle))
|
||||||
|
```
|
||||||
|
|
||||||
|
## letter_spacing Is Not a Valid Parameter
|
||||||
|
|
||||||
|
**Error:** `TypeError: Mobject.__init__() got an unexpected keyword argument 'letter_spacing'`
|
||||||
|
|
||||||
|
**Cause:** `Text()` does not accept `letter_spacing`. Manim uses Pango for text rendering and does not expose kerning controls on `Text()`.
|
||||||
|
|
||||||
|
```python
|
||||||
|
# WRONG
|
||||||
|
Text("HERMES", letter_spacing=6)
|
||||||
|
|
||||||
|
# RIGHT: use MarkupText with Pango attributes for spacing control
|
||||||
|
MarkupText('<span letter_spacing="6000">HERMES</span>', font_size=18)
|
||||||
|
# Note: Pango letter_spacing is in 1/1024 of a point
|
||||||
|
```
|
||||||
|
|
||||||
|
## Animation Errors
|
||||||
|
|
||||||
|
**Invisible animation** -- mobject never added:
|
||||||
|
```python
|
||||||
|
# WRONG: circle = Circle(); self.play(circle.animate.set_color(RED))
|
||||||
|
# RIGHT: self.play(Create(circle)); self.play(circle.animate.set_color(RED))
|
||||||
|
```
|
||||||
|
|
||||||
|
**Transform confusion** -- after Transform(A, B), A is on screen, B is not. Use ReplacementTransform if you want B.
|
||||||
|
|
||||||
|
**Duplicate animation** -- same mobject twice in one play():
|
||||||
|
```python
|
||||||
|
# WRONG: self.play(c.animate.shift(RIGHT), c.animate.set_color(RED))
|
||||||
|
# RIGHT: self.play(c.animate.shift(RIGHT).set_color(RED))
|
||||||
|
```
|
||||||
|
|
||||||
|
**Updater fights animation**:
|
||||||
|
```python
|
||||||
|
mob.suspend_updating()
|
||||||
|
self.play(mob.animate.shift(RIGHT))
|
||||||
|
mob.resume_updating()
|
||||||
|
```
|
||||||
|
|
||||||
|
## Rendering Issues
|
||||||
|
|
||||||
|
**Blurry output**: Using -ql (480p). Switch to -qm/-qh for final.
|
||||||
|
|
||||||
|
**Slow render**: Use -ql during development. Reduce Surface resolution. Shorter self.wait().
|
||||||
|
|
||||||
|
**Stale output**: `manim -ql --disable_caching script.py Scene`
|
||||||
|
|
||||||
|
**ffmpeg concat fails**: All clips must match resolution/FPS/codec.
|
||||||
|
|
||||||
|
## Common Mistakes
|
||||||
|
|
||||||
|
**Text clips at edge**: `buff >= 0.5` for `.to_edge()`
|
||||||
|
|
||||||
|
**Overlapping text**: Use `ReplacementTransform(old, new)`, not `Write(new)` on top.
|
||||||
|
|
||||||
|
**Too crowded**: Max 5-6 elements visible. Split into scenes or use opacity layering.
|
||||||
|
|
||||||
|
**No breathing room**: `self.wait(1.5)` minimum after reveals, `self.wait(2.0)` for key moments.
|
||||||
|
|
||||||
|
**Missing background color**: Set `self.camera.background_color = BG` in every scene.
|
||||||
|
|
||||||
|
## Debugging Strategy
|
||||||
|
|
||||||
|
1. Render a still: `manim -ql -s script.py Scene` -- instant layout check
|
||||||
|
2. Isolate the broken scene -- render only that one
|
||||||
|
3. Replace `self.play()` with `self.add()` to see final state instantly
|
||||||
|
4. Print positions: `print(mob.get_center())`
|
||||||
|
5. Clear cache: delete `media/` directory
|
||||||
119
skills/creative/manim-video/references/visual-design.md
Normal file
119
skills/creative/manim-video/references/visual-design.md
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
# Visual Design Principles
|
||||||
|
|
||||||
|
## 12 Core Principles
|
||||||
|
|
||||||
|
1. **Geometry Before Algebra** — Show the shape first, the equation second.
|
||||||
|
2. **Opacity Layering** — PRIMARY=1.0, CONTEXT=0.4, GRID=0.15. Direct attention through brightness.
|
||||||
|
3. **One New Idea Per Scene** — Each scene introduces exactly one concept.
|
||||||
|
4. **Spatial Consistency** — Same concept occupies the same screen region throughout.
|
||||||
|
5. **Color = Meaning** — Assign colors to concepts, not mobjects. If velocity is blue, it stays blue.
|
||||||
|
6. **Progressive Disclosure** — Show simplest version first, add complexity incrementally.
|
||||||
|
7. **Transform, Don't Replace** — Use Transform/ReplacementTransform to show connections.
|
||||||
|
8. **Breathing Room** — `self.wait(1.5)` minimum after showing something new.
|
||||||
|
9. **Visual Weight Balance** — Don't cluster everything on one side.
|
||||||
|
10. **Consistent Motion Vocabulary** — Pick a small set of animation types and reuse them.
|
||||||
|
11. **Dark Background, Light Content** — #1C1C1C to #2D2B55 backgrounds maximize contrast.
|
||||||
|
12. **Intentional Empty Space** — Leave at least 15% of the frame empty.
|
||||||
|
|
||||||
|
## Layout Templates
|
||||||
|
|
||||||
|
### FULL_CENTER
|
||||||
|
One main element centered, title above, note below.
|
||||||
|
Best for: single equations, single diagrams, title cards.
|
||||||
|
|
||||||
|
### LEFT_RIGHT
|
||||||
|
Two elements side by side at x=-3.5 and x=3.5.
|
||||||
|
Best for: equation + visual, before/after, comparison.
|
||||||
|
|
||||||
|
### TOP_BOTTOM
|
||||||
|
Main element at y=1.5, supporting content at y=-1.5.
|
||||||
|
Best for: concept + examples, theorem + cases.
|
||||||
|
|
||||||
|
### GRID
|
||||||
|
Multiple elements via `arrange_in_grid()`.
|
||||||
|
Best for: comparison matrices, multi-step processes.
|
||||||
|
|
||||||
|
### PROGRESSIVE
|
||||||
|
Elements appear one at a time, arranged DOWN with aligned_edge=LEFT.
|
||||||
|
Best for: algorithms, proofs, step-by-step processes.
|
||||||
|
|
||||||
|
### ANNOTATED_DIAGRAM
|
||||||
|
Central diagram with floating labels connected by arrows.
|
||||||
|
Best for: architecture diagrams, annotated figures.
|
||||||
|
|
||||||
|
## Color Palettes
|
||||||
|
|
||||||
|
### Classic 3B1B
|
||||||
|
```python
|
||||||
|
BG="#1C1C1C"; PRIMARY=BLUE; SECONDARY=GREEN; ACCENT=YELLOW; HIGHLIGHT=RED
|
||||||
|
```
|
||||||
|
|
||||||
|
### Warm Academic
|
||||||
|
```python
|
||||||
|
BG="#2D2B55"; PRIMARY="#FF6B6B"; SECONDARY="#FFD93D"; ACCENT="#6BCB77"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Neon Tech
|
||||||
|
```python
|
||||||
|
BG="#0A0A0A"; PRIMARY="#00F5FF"; SECONDARY="#FF00FF"; ACCENT="#39FF14"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Font Selection
|
||||||
|
|
||||||
|
Manim's default `Text()` uses the system's default sans-serif font, which often renders with poor kerning. Always specify a font explicitly.
|
||||||
|
|
||||||
|
### Recommended Fonts
|
||||||
|
|
||||||
|
| Use case | Font | Fallback |
|
||||||
|
|----------|------|----------|
|
||||||
|
| Body text, titles | `"Inter"`, `"SF Pro Display"` | `"Helvetica Neue"`, `"Arial"` |
|
||||||
|
| Code, terminal | `"JetBrains Mono"`, `"SF Mono"` | `"Menlo"`, `"Courier New"` |
|
||||||
|
| Math labels | Use `MathTex` (renders via LaTeX, not system fonts) | — |
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Clean body text
|
||||||
|
title = Text("Gradient Descent", font_size=48, font="Inter", weight=BOLD)
|
||||||
|
|
||||||
|
# Monospaced code
|
||||||
|
code_label = Text("loss.backward()", font_size=24, font="JetBrains Mono")
|
||||||
|
|
||||||
|
# Math — always use MathTex, not Text
|
||||||
|
equation = MathTex(r"\nabla L = \frac{\partial L}{\partial w}")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Font Availability
|
||||||
|
|
||||||
|
Not all fonts are installed on all systems. Manim falls back silently to a default if the font is missing. Use widely available fonts:
|
||||||
|
- **macOS**: SF Pro Display, SF Mono, Menlo, Helvetica Neue
|
||||||
|
- **Linux**: DejaVu Sans, Liberation Sans, Ubuntu, Noto Sans
|
||||||
|
- **Cross-platform**: Inter (install via Google Fonts), JetBrains Mono (install from jetbrains.com)
|
||||||
|
|
||||||
|
For maximum portability, use `"Helvetica Neue"` (body) and `"Menlo"` (code) — both available on macOS and have Linux equivalents.
|
||||||
|
|
||||||
|
### Fine-Grained Text Control
|
||||||
|
|
||||||
|
`Text()` does not support `letter_spacing` or kerning parameters. For fine control, use `MarkupText` with Pango attributes:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Letter spacing (Pango units: 1/1024 of a point)
|
||||||
|
MarkupText('<span letter_spacing="6000">HERMES</span>', font_size=18, font="Menlo")
|
||||||
|
|
||||||
|
# Bold specific words
|
||||||
|
MarkupText('This is <b>important</b>', font_size=24)
|
||||||
|
|
||||||
|
# Color specific words
|
||||||
|
MarkupText('Red <span foreground="#FF6B6B">warning</span>', font_size=24)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Text Rendering Quality
|
||||||
|
|
||||||
|
Manim's text rendering quality depends heavily on output resolution. At `-ql` (480p), text kerning looks noticeably poor. Always preview text-heavy scenes at `-qm` (720p) or higher. See `references/rendering.md` for quality preset guidance.
|
||||||
|
|
||||||
|
## Visual Hierarchy Checklist
|
||||||
|
|
||||||
|
For every frame:
|
||||||
|
1. What is the ONE thing to look at? (brightest/largest)
|
||||||
|
2. What is context? (dimmed to 0.3-0.4)
|
||||||
|
3. What is structural? (dimmed to 0.15)
|
||||||
|
4. Enough empty space? (>15%)
|
||||||
|
5. All text readable at phone size?
|
||||||
14
skills/creative/manim-video/scripts/setup.sh
Executable file
14
skills/creative/manim-video/scripts/setup.sh
Executable file
@@ -0,0 +1,14 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
G="\033[0;32m"; R="\033[0;31m"; N="\033[0m"
|
||||||
|
ok() { echo -e " ${G}+${N} $1"; }
|
||||||
|
fail() { echo -e " ${R}x${N} $1"; }
|
||||||
|
echo ""; echo "Manim Video Skill — Setup Check"; echo ""
|
||||||
|
errors=0
|
||||||
|
command -v python3 &>/dev/null && ok "Python $(python3 --version 2>&1 | awk '{print $2}')" || { fail "Python 3 not found"; errors=$((errors+1)); }
|
||||||
|
python3 -c "import manim" 2>/dev/null && ok "Manim $(manim --version 2>&1 | head -1)" || { fail "Manim not installed: pip install manim"; errors=$((errors+1)); }
|
||||||
|
command -v pdflatex &>/dev/null && ok "LaTeX (pdflatex)" || { fail "LaTeX not found (macOS: brew install --cask mactex-no-gui)"; errors=$((errors+1)); }
|
||||||
|
command -v ffmpeg &>/dev/null && ok "ffmpeg" || { fail "ffmpeg not found"; errors=$((errors+1)); }
|
||||||
|
echo ""
|
||||||
|
[ $errors -eq 0 ] && echo -e "${G}All prerequisites satisfied.${N}" || echo -e "${R}$errors prerequisite(s) missing.${N}"
|
||||||
|
echo ""
|
||||||
Reference in New Issue
Block a user