manim-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +104 -0
- package/dist/demo.mp4 +0 -0
- package/dist/index.js +65 -0
- package/dist/mcp-app.html +142 -0
- package/dist/server.js +1492 -0
- package/package.json +67 -0
- package/references/composer/SKILL.md +154 -0
- package/references/composer/references/3b1b-series-patterns.md +217 -0
- package/references/composer/references/domain-planning-guides/calculus-planning.md +188 -0
- package/references/composer/references/domain-planning-guides/linear-algebra-planning.md +169 -0
- package/references/composer/references/domain-planning-guides/ml-planning.md +286 -0
- package/references/composer/references/domain-planning-guides/number-theory-planning.md +187 -0
- package/references/composer/references/domain-planning-guides/physics-planning.md +249 -0
- package/references/composer/references/domain-planning-guides/probability-planning.md +200 -0
- package/references/composer/references/mathematical-storytelling.md +359 -0
- package/references/composer/references/narrative-patterns.md +221 -0
- package/references/composer/references/opening-patterns.md +284 -0
- package/references/composer/references/pacing-guide.md +289 -0
- package/references/composer/references/scene-archetypes.md +534 -0
- package/references/composer/references/scene-examples.md +379 -0
- package/references/composer/references/visual-techniques.md +480 -0
- package/references/composer/templates/scenes-template.md +147 -0
- package/references/manimce/SKILL.md +166 -0
- package/references/manimce/examples/3d_visualization.py +373 -0
- package/references/manimce/examples/basic_animations.py +212 -0
- package/references/manimce/examples/graph_plotting.py +401 -0
- package/references/manimce/examples/lorenz_attractor.py +172 -0
- package/references/manimce/examples/math_visualization.py +315 -0
- package/references/manimce/examples/updater_patterns.py +369 -0
- package/references/manimce/rules/3b1b-translation.md +594 -0
- package/references/manimce/rules/3d.md +254 -0
- package/references/manimce/rules/advanced-animations.md +594 -0
- package/references/manimce/rules/animation-groups.md +212 -0
- package/references/manimce/rules/animations.md +128 -0
- package/references/manimce/rules/api-pitfalls.md +89 -0
- package/references/manimce/rules/axes.md +214 -0
- package/references/manimce/rules/camera.md +208 -0
- package/references/manimce/rules/cli.md +232 -0
- package/references/manimce/rules/color-conventions.md +444 -0
- package/references/manimce/rules/colors.md +199 -0
- package/references/manimce/rules/config.md +264 -0
- package/references/manimce/rules/creation-animations.md +158 -0
- package/references/manimce/rules/graphing.md +233 -0
- package/references/manimce/rules/grouping.md +220 -0
- package/references/manimce/rules/latex.md +202 -0
- package/references/manimce/rules/lines.md +241 -0
- package/references/manimce/rules/long-form-video.md +552 -0
- package/references/manimce/rules/mathematical-domains.md +689 -0
- package/references/manimce/rules/mobjects.md +116 -0
- package/references/manimce/rules/multi-scene-composition.md +112 -0
- package/references/manimce/rules/pedagogy.md +532 -0
- package/references/manimce/rules/physics-simulations.md +610 -0
- package/references/manimce/rules/positioning.md +211 -0
- package/references/manimce/rules/scenes.md +121 -0
- package/references/manimce/rules/shapes.md +300 -0
- package/references/manimce/rules/styling.md +177 -0
- package/references/manimce/rules/text-animations.md +222 -0
- package/references/manimce/rules/text.md +189 -0
- package/references/manimce/rules/timing.md +227 -0
- package/references/manimce/rules/transform-animations.md +157 -0
- package/references/manimce/rules/updaters.md +226 -0
- package/references/manimce/templates/basic_scene.py +64 -0
- package/references/manimce/templates/camera_scene.py +100 -0
- package/references/manimce/templates/threed_scene.py +138 -0
|
@@ -0,0 +1,552 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: long-form-video
|
|
3
|
+
description: Comprehensive guide to structuring long-form math videos (15-30+ min) in the 3Blue1Brown style using ManimCE
|
|
4
|
+
metadata:
|
|
5
|
+
tags: video-structure, narrative, pacing, transitions, scenes, long-form, 3b1b
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Long-Form Video Structure in ManimCE
|
|
9
|
+
|
|
10
|
+
This guide captures the architectural patterns from 407 3Blue1Brown video files (2015-2026) for building coherent, long-form mathematical explainer videos.
|
|
11
|
+
|
|
12
|
+
## Scene-Per-Section Architecture
|
|
13
|
+
|
|
14
|
+
3b1b videos decompose into 10-40 individual Scene classes per video file. Each Scene class corresponds to one conceptual section of the video. Within each Scene, the `construct()` method is organized into commented sections that follow a clear narrative arc.
|
|
15
|
+
|
|
16
|
+
### Method-Per-Section Pattern
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
from manim import *
|
|
20
|
+
|
|
21
|
+
class ExplainEigenvalues(MovingCameraScene):
|
|
22
|
+
"""One section of a larger linear algebra video."""
|
|
23
|
+
|
|
24
|
+
def construct(self):
|
|
25
|
+
# Setup: establish the visual environment
|
|
26
|
+
self.setup_coordinate_plane()
|
|
27
|
+
|
|
28
|
+
# Introduce: present the concept with simple example
|
|
29
|
+
self.introduce_matrix_action()
|
|
30
|
+
|
|
31
|
+
# Demonstrate: show the key behavior
|
|
32
|
+
self.show_eigenvector_behavior()
|
|
33
|
+
|
|
34
|
+
# Deepen: build complexity
|
|
35
|
+
self.show_eigenvalue_equation()
|
|
36
|
+
|
|
37
|
+
# Conclude: transition to next topic
|
|
38
|
+
self.conclude_with_question()
|
|
39
|
+
|
|
40
|
+
def setup_coordinate_plane(self):
|
|
41
|
+
plane = NumberPlane()
|
|
42
|
+
plane.set_stroke(BLUE, 1, opacity=0.4)
|
|
43
|
+
basis_i = Vector([1, 0], color=GREEN)
|
|
44
|
+
basis_j = Vector([0, 1], color=RED)
|
|
45
|
+
i_label = MathTex(r"\hat{\imath}").next_to(basis_i, DOWN)
|
|
46
|
+
j_label = MathTex(r"\hat{\jmath}").next_to(basis_j, LEFT)
|
|
47
|
+
i_label.set_color(GREEN)
|
|
48
|
+
j_label.set_color(RED)
|
|
49
|
+
|
|
50
|
+
self.play(Create(plane), run_time=2)
|
|
51
|
+
self.play(
|
|
52
|
+
GrowArrow(basis_i), GrowArrow(basis_j),
|
|
53
|
+
Write(i_label), Write(j_label),
|
|
54
|
+
)
|
|
55
|
+
self.wait()
|
|
56
|
+
|
|
57
|
+
self.plane = plane
|
|
58
|
+
self.basis_vectors = VGroup(basis_i, basis_j)
|
|
59
|
+
|
|
60
|
+
def introduce_matrix_action(self):
|
|
61
|
+
matrix = Matrix([[3, 1], [0, 2]])
|
|
62
|
+
matrix.to_corner(UL)
|
|
63
|
+
self.play(Write(matrix))
|
|
64
|
+
self.wait()
|
|
65
|
+
self.matrix_mob = matrix
|
|
66
|
+
|
|
67
|
+
def show_eigenvector_behavior(self):
|
|
68
|
+
# Eigenvector stays on its span
|
|
69
|
+
eigen_vec = Vector([1, 0], color=YELLOW)
|
|
70
|
+
label = MathTex(r"\vec{v}", color=YELLOW)
|
|
71
|
+
label.next_to(eigen_vec, UP)
|
|
72
|
+
self.play(GrowArrow(eigen_vec), Write(label))
|
|
73
|
+
self.wait()
|
|
74
|
+
|
|
75
|
+
# After transformation, still on span but scaled
|
|
76
|
+
scaled_vec = Vector([3, 0], color=YELLOW)
|
|
77
|
+
self.play(
|
|
78
|
+
Transform(eigen_vec, scaled_vec),
|
|
79
|
+
run_time=2,
|
|
80
|
+
)
|
|
81
|
+
self.wait()
|
|
82
|
+
|
|
83
|
+
def show_eigenvalue_equation(self):
|
|
84
|
+
equation = MathTex(
|
|
85
|
+
r"A", r"\vec{v}", r"=", r"\lambda", r"\vec{v}",
|
|
86
|
+
tex_to_color_map={
|
|
87
|
+
r"\vec{v}": YELLOW,
|
|
88
|
+
r"\lambda": TEAL,
|
|
89
|
+
r"A": WHITE,
|
|
90
|
+
}
|
|
91
|
+
)
|
|
92
|
+
equation.to_edge(UP)
|
|
93
|
+
self.play(Write(equation))
|
|
94
|
+
self.wait(2)
|
|
95
|
+
|
|
96
|
+
def conclude_with_question(self):
|
|
97
|
+
question = Text("But how do we find these special vectors?")
|
|
98
|
+
question.scale(0.8)
|
|
99
|
+
question.to_edge(DOWN)
|
|
100
|
+
self.play(Write(question))
|
|
101
|
+
self.wait(2)
|
|
102
|
+
self.play(FadeOut(question))
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Composing 20+ Scenes Into a Video
|
|
106
|
+
|
|
107
|
+
In 3b1b's workflow, a single Python file contains many Scene classes that together form one video. Scenes are rendered individually and concatenated. Here is the typical organization pattern:
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
from manim import *
|
|
111
|
+
|
|
112
|
+
# --- Constants used across all scenes ---
|
|
113
|
+
FORMULA_COLOR = YELLOW
|
|
114
|
+
GRAPH_COLOR = BLUE
|
|
115
|
+
HIGHLIGHT_COLOR = GREEN
|
|
116
|
+
|
|
117
|
+
# --- Section 1: Opening ---
|
|
118
|
+
class OpeningQuote(Scene):
|
|
119
|
+
"""Every EoLA/EoC chapter opens with a quote."""
|
|
120
|
+
def construct(self):
|
|
121
|
+
quote = Tex(
|
|
122
|
+
r"``There is hardly any theory which is more elementary \\"
|
|
123
|
+
r"than linear algebra, in spite of the fact that generations \\"
|
|
124
|
+
r"of professors and textbook writers have obscured its \\"
|
|
125
|
+
r"simplicity by preposterous calculations with matrices.''",
|
|
126
|
+
font_size=36,
|
|
127
|
+
)
|
|
128
|
+
quote.to_edge(UP, buff=1.0)
|
|
129
|
+
author = Text("-- Jean Dieudonne", color=YELLOW)
|
|
130
|
+
author.next_to(quote, DOWN, buff=1.0)
|
|
131
|
+
|
|
132
|
+
self.play(FadeIn(quote, lag_ratio=0.5, rate_func=linear, run_time=5))
|
|
133
|
+
self.wait(2)
|
|
134
|
+
self.play(Write(author, run_time=3))
|
|
135
|
+
self.wait()
|
|
136
|
+
|
|
137
|
+
# --- Section 2: Introduce the Problem ---
|
|
138
|
+
class IntroduceProblem(Scene):
|
|
139
|
+
def construct(self):
|
|
140
|
+
# Present the mystery first (see "Mystery First" pattern below)
|
|
141
|
+
...
|
|
142
|
+
|
|
143
|
+
# --- Section 3: Build Intuition ---
|
|
144
|
+
class BuildIntuition(Scene):
|
|
145
|
+
def construct(self):
|
|
146
|
+
...
|
|
147
|
+
|
|
148
|
+
# --- Section 4: Formal Proof ---
|
|
149
|
+
class FormalProof(Scene):
|
|
150
|
+
def construct(self):
|
|
151
|
+
...
|
|
152
|
+
|
|
153
|
+
# --- Section 5: End Screen ---
|
|
154
|
+
class EndScreen(Scene):
|
|
155
|
+
def construct(self):
|
|
156
|
+
# Standard 3b1b end screen with two video recommendations
|
|
157
|
+
...
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Scene Naming Conventions (from the codebase)
|
|
161
|
+
|
|
162
|
+
- **Descriptive names**: `AttentionPatterns`, `IntroduceEulersFormula`, `ShowPattern`
|
|
163
|
+
- **Teacher-student scenes**: `AskAboutPosition`, `ProblemSolvingRule1` (using `TeacherStudentsScene`)
|
|
164
|
+
- **Simple utility scenes**: `SimpleRect`, `SimpleFEq` (small annotation clips)
|
|
165
|
+
- **Supplement scenes**: placed in `supplements.py` for tangential content
|
|
166
|
+
|
|
167
|
+
## Progressive Complexity Pattern
|
|
168
|
+
|
|
169
|
+
Every 3b1b video follows: simple cases first, complex cases later. Concrete examples before abstract formulas.
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
class ProgressiveComplexity(Scene):
|
|
173
|
+
def construct(self):
|
|
174
|
+
# Phase 1: Simplest possible case
|
|
175
|
+
simple_eq = MathTex(r"2 \times 3 = 6")
|
|
176
|
+
self.play(Write(simple_eq))
|
|
177
|
+
self.wait()
|
|
178
|
+
|
|
179
|
+
# Phase 2: Slightly more complex
|
|
180
|
+
medium_eq = MathTex(r"x^2 + y^2 = r^2")
|
|
181
|
+
self.play(
|
|
182
|
+
simple_eq.animate.shift(UP * 1.5).scale(0.7).set_opacity(0.5),
|
|
183
|
+
Write(medium_eq),
|
|
184
|
+
)
|
|
185
|
+
self.wait()
|
|
186
|
+
|
|
187
|
+
# Phase 3: The general case
|
|
188
|
+
general_eq = MathTex(
|
|
189
|
+
r"\sum_{n=0}^{\infty} \frac{f^{(n)}(a)}{n!}(x-a)^n"
|
|
190
|
+
)
|
|
191
|
+
self.play(
|
|
192
|
+
medium_eq.animate.shift(UP * 1.5).scale(0.7).set_opacity(0.5),
|
|
193
|
+
Write(general_eq),
|
|
194
|
+
)
|
|
195
|
+
self.wait(2)
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Pacing: wait(), run_time, and lag_ratio
|
|
199
|
+
|
|
200
|
+
### self.wait() usage
|
|
201
|
+
|
|
202
|
+
3b1b uses `self.wait()` generously, typically 1-2 seconds between concepts and up to `self.wait(2)` or `self.wait(3)` for important pauses where voiceover explains.
|
|
203
|
+
|
|
204
|
+
```python
|
|
205
|
+
# After revealing a key result, pause for the viewer
|
|
206
|
+
self.play(Write(result))
|
|
207
|
+
self.wait(2) # Let it sink in
|
|
208
|
+
|
|
209
|
+
# Quick pause between steps
|
|
210
|
+
self.play(Create(arrow))
|
|
211
|
+
self.wait() # Default 1 second
|
|
212
|
+
|
|
213
|
+
# Long pause for complex diagrams
|
|
214
|
+
self.play(FadeIn(full_diagram))
|
|
215
|
+
self.wait(3) # Give viewer time to read everything
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### run_time Control
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
# Fast animations for simple transitions (0.5s)
|
|
222
|
+
self.play(FadeOut(old_stuff), run_time=0.5)
|
|
223
|
+
|
|
224
|
+
# Standard animations (1s default)
|
|
225
|
+
self.play(Create(circle))
|
|
226
|
+
|
|
227
|
+
# Slow, dramatic reveals (2-3s)
|
|
228
|
+
self.play(Write(important_equation), run_time=2)
|
|
229
|
+
|
|
230
|
+
# Very slow for complex transformations (3-5s)
|
|
231
|
+
self.play(
|
|
232
|
+
Transform(plane, transformed_plane),
|
|
233
|
+
run_time=3,
|
|
234
|
+
rate_func=smooth,
|
|
235
|
+
)
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### lag_ratio for Staggered Effects
|
|
239
|
+
|
|
240
|
+
The `lag_ratio` parameter is used extensively throughout the codebase for wave-like, cascading animations:
|
|
241
|
+
|
|
242
|
+
```python
|
|
243
|
+
# Staggered fade-in of word-by-word text
|
|
244
|
+
words = VGroup(*[Text(w) for w in ["Hello", "beautiful", "world"]])
|
|
245
|
+
words.arrange(RIGHT, buff=0.3)
|
|
246
|
+
self.play(
|
|
247
|
+
LaggedStartMap(FadeIn, words, shift=0.5 * UP, lag_ratio=0.25)
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
# Cascading creation of bars in a chart
|
|
251
|
+
bars = VGroup(*[Rectangle(height=h, width=0.3) for h in [1, 2, 3, 2, 1]])
|
|
252
|
+
bars.arrange(RIGHT, buff=0.1, aligned_edge=DOWN)
|
|
253
|
+
self.play(
|
|
254
|
+
LaggedStartMap(GrowFromEdge, bars, edge=DOWN, lag_ratio=0.15),
|
|
255
|
+
run_time=2,
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
# Lagged arrow growth
|
|
259
|
+
arrows = VGroup(*[Arrow(ORIGIN, RIGHT) for _ in range(5)])
|
|
260
|
+
self.play(
|
|
261
|
+
LaggedStartMap(GrowArrow, arrows, lag_ratio=0.2, run_time=1.5)
|
|
262
|
+
)
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
## Transition Patterns Between Topics
|
|
266
|
+
|
|
267
|
+
### FadeOut/FadeIn Group Transitions
|
|
268
|
+
|
|
269
|
+
The most common pattern: fade out everything from the previous section, fade in the new section.
|
|
270
|
+
|
|
271
|
+
```python
|
|
272
|
+
class TransitionPatterns(Scene):
|
|
273
|
+
def construct(self):
|
|
274
|
+
# --- Section A content ---
|
|
275
|
+
title_a = Text("Linear Transformations")
|
|
276
|
+
diagram_a = NumberPlane()
|
|
277
|
+
section_a = VGroup(title_a, diagram_a)
|
|
278
|
+
|
|
279
|
+
self.play(Write(title_a), Create(diagram_a))
|
|
280
|
+
self.wait(2)
|
|
281
|
+
|
|
282
|
+
# --- Transition: clear and introduce next section ---
|
|
283
|
+
title_b = Text("Matrix Multiplication")
|
|
284
|
+
self.play(
|
|
285
|
+
FadeOut(section_a, shift=LEFT),
|
|
286
|
+
FadeIn(title_b, shift=RIGHT),
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
# --- Section B content ---
|
|
290
|
+
...
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### Zooming Transition (MovingCameraScene)
|
|
294
|
+
|
|
295
|
+
```python
|
|
296
|
+
class ZoomTransition(MovingCameraScene):
|
|
297
|
+
def construct(self):
|
|
298
|
+
overview = VGroup(
|
|
299
|
+
*[Circle(radius=0.5).shift(2 * d) for d in [LEFT, RIGHT, UP, DOWN]]
|
|
300
|
+
)
|
|
301
|
+
self.add(overview)
|
|
302
|
+
|
|
303
|
+
# Zoom into one element
|
|
304
|
+
target = overview[0]
|
|
305
|
+
self.play(
|
|
306
|
+
self.camera.frame.animate.set_width(3).move_to(target),
|
|
307
|
+
run_time=2,
|
|
308
|
+
)
|
|
309
|
+
self.wait()
|
|
310
|
+
|
|
311
|
+
# Zoom back out
|
|
312
|
+
self.play(
|
|
313
|
+
self.camera.frame.animate.set_width(14).move_to(ORIGIN),
|
|
314
|
+
run_time=2,
|
|
315
|
+
)
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### Rearrangement Transition
|
|
319
|
+
|
|
320
|
+
A 3b1b hallmark: instead of cutting, rearrange existing elements into a new configuration.
|
|
321
|
+
|
|
322
|
+
```python
|
|
323
|
+
# Move equation to corner, make room for diagram
|
|
324
|
+
equation.generate_target()
|
|
325
|
+
equation.target.scale(0.6).to_corner(UL)
|
|
326
|
+
self.play(MoveToTarget(equation))
|
|
327
|
+
|
|
328
|
+
# Or with .animate
|
|
329
|
+
self.play(
|
|
330
|
+
equation.animate.scale(0.6).to_corner(UL),
|
|
331
|
+
FadeIn(new_diagram),
|
|
332
|
+
)
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
## The "Mystery First" Pattern
|
|
336
|
+
|
|
337
|
+
3b1b videos almost always open with a surprising or mysterious result, then spend the video explaining it. This is the signature pedagogical hook.
|
|
338
|
+
|
|
339
|
+
```python
|
|
340
|
+
class MysteryFirst(Scene):
|
|
341
|
+
"""Pattern: Show the surprising result, THEN explain."""
|
|
342
|
+
|
|
343
|
+
def construct(self):
|
|
344
|
+
# Step 1: Show the surprising claim
|
|
345
|
+
claim = MathTex(r"1 + 2 + 4 + 8 + \cdots = -1")
|
|
346
|
+
claim.scale(1.5)
|
|
347
|
+
question_mark = Text("???", color=RED, font_size=72)
|
|
348
|
+
question_mark.next_to(claim, RIGHT)
|
|
349
|
+
|
|
350
|
+
self.play(Write(claim))
|
|
351
|
+
self.play(FadeIn(question_mark, scale=1.5))
|
|
352
|
+
self.wait(2)
|
|
353
|
+
|
|
354
|
+
# Step 2: "Let me show you what I mean..."
|
|
355
|
+
self.play(FadeOut(question_mark))
|
|
356
|
+
explanation_title = Text("Here's what's going on...")
|
|
357
|
+
explanation_title.to_edge(UP)
|
|
358
|
+
self.play(
|
|
359
|
+
claim.animate.scale(0.6).to_corner(UL),
|
|
360
|
+
Write(explanation_title),
|
|
361
|
+
)
|
|
362
|
+
self.wait()
|
|
363
|
+
|
|
364
|
+
# Step 3: Build up the explanation from scratch
|
|
365
|
+
# ... rest of the scene
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
### Moser's Circle Problem Pattern (Build false confidence, then reveal truth)
|
|
369
|
+
|
|
370
|
+
From `_2023/moser_reboot/main.py` and `_2015/moser_main.py`:
|
|
371
|
+
|
|
372
|
+
```python
|
|
373
|
+
class BuildFalseConfidence(Scene):
|
|
374
|
+
"""Show a pattern that LOOKS like powers of 2, then reveal it breaks."""
|
|
375
|
+
|
|
376
|
+
def construct(self):
|
|
377
|
+
# Reveal values one by one
|
|
378
|
+
values = [1, 2, 4, 8, 16, 31] # NOT 32!
|
|
379
|
+
labels = VGroup()
|
|
380
|
+
for i, v in enumerate(values):
|
|
381
|
+
label = MathTex(str(v))
|
|
382
|
+
labels.add(label)
|
|
383
|
+
self.play(FadeIn(label, shift=0.25 * UP), run_time=0.5)
|
|
384
|
+
self.wait(0.5)
|
|
385
|
+
|
|
386
|
+
# Highlight the "pattern"
|
|
387
|
+
brace = Brace(labels[:5], DOWN)
|
|
388
|
+
pattern_text = brace.get_text("Powers of 2?")
|
|
389
|
+
self.play(GrowFromCenter(brace), Write(pattern_text))
|
|
390
|
+
self.wait(2)
|
|
391
|
+
|
|
392
|
+
# The reveal: it BREAKS
|
|
393
|
+
highlight = SurroundingRectangle(labels[-1], color=RED)
|
|
394
|
+
not_32 = MathTex(r"\neq 32", color=RED)
|
|
395
|
+
not_32.next_to(labels[-1], RIGHT)
|
|
396
|
+
self.play(Create(highlight), Write(not_32))
|
|
397
|
+
self.wait(2)
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
## Opening Quote Pattern
|
|
401
|
+
|
|
402
|
+
Every Essence of Linear Algebra and Essence of Calculus chapter opens with a literary or mathematical quote. The ManimCE equivalent:
|
|
403
|
+
|
|
404
|
+
```python
|
|
405
|
+
class OpeningQuoteScene(Scene):
|
|
406
|
+
"""Standard 3b1b opening quote."""
|
|
407
|
+
|
|
408
|
+
def construct(self):
|
|
409
|
+
quote_text = (
|
|
410
|
+
r"``The introduction of numbers as coordinates is an act of "
|
|
411
|
+
r"violence.'' "
|
|
412
|
+
)
|
|
413
|
+
quote = Tex(quote_text, font_size=40)
|
|
414
|
+
quote.set_width(FRAME_WIDTH - 2)
|
|
415
|
+
quote.to_edge(UP, buff=1.0)
|
|
416
|
+
|
|
417
|
+
author = Text("-- Hermann Weyl", color=YELLOW)
|
|
418
|
+
author.next_to(quote, DOWN, buff=1.0)
|
|
419
|
+
|
|
420
|
+
self.play(
|
|
421
|
+
FadeIn(quote, lag_ratio=0.5, rate_func=linear, run_time=5)
|
|
422
|
+
)
|
|
423
|
+
self.wait(2)
|
|
424
|
+
self.play(Write(author, run_time=3))
|
|
425
|
+
self.wait()
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
## End Screen Pattern
|
|
429
|
+
|
|
430
|
+
3b1b videos end with a standard screen showing clickable video thumbnails and patron credits.
|
|
431
|
+
|
|
432
|
+
```python
|
|
433
|
+
class SimpleEndScreen(Scene):
|
|
434
|
+
"""Simplified ManimCE end screen pattern."""
|
|
435
|
+
|
|
436
|
+
def construct(self):
|
|
437
|
+
# Black background with divider
|
|
438
|
+
divider = DashedLine(
|
|
439
|
+
FRAME_WIDTH / 2 * LEFT, FRAME_WIDTH / 2 * RIGHT
|
|
440
|
+
)
|
|
441
|
+
self.add(divider)
|
|
442
|
+
|
|
443
|
+
# Title
|
|
444
|
+
title = Text("Clicky Stuffs", font_size=54)
|
|
445
|
+
title.to_edge(UP, buff=0.3)
|
|
446
|
+
self.add(title)
|
|
447
|
+
|
|
448
|
+
# Video recommendation placeholders
|
|
449
|
+
screen1 = ScreenRectangle(height=2.5)
|
|
450
|
+
screen2 = ScreenRectangle(height=2.5)
|
|
451
|
+
screens = VGroup(screen1, screen2).arrange(RIGHT, buff=0.5)
|
|
452
|
+
screens.next_to(divider, UP, buff=0.5)
|
|
453
|
+
|
|
454
|
+
self.play(
|
|
455
|
+
FadeIn(screens, lag_ratio=0.3),
|
|
456
|
+
Write(title),
|
|
457
|
+
)
|
|
458
|
+
|
|
459
|
+
# Patron credits scroll
|
|
460
|
+
thanks = Text(
|
|
461
|
+
"These videos are funded by viewers.\n"
|
|
462
|
+
"Special thanks to channel supporters.",
|
|
463
|
+
font_size=24,
|
|
464
|
+
color=GREY_A,
|
|
465
|
+
)
|
|
466
|
+
thanks.next_to(divider, DOWN, buff=0.3)
|
|
467
|
+
self.play(Write(thanks))
|
|
468
|
+
self.wait(5)
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
## Comment-Driven Section Structure
|
|
472
|
+
|
|
473
|
+
Inside every 3b1b `construct()` method, sections are separated by descriptive comments. This is the universal organizational pattern across all 407 files:
|
|
474
|
+
|
|
475
|
+
```python
|
|
476
|
+
def construct(self):
|
|
477
|
+
# Setup
|
|
478
|
+
axes = Axes(...)
|
|
479
|
+
self.add(axes)
|
|
480
|
+
|
|
481
|
+
# Show the function
|
|
482
|
+
graph = axes.plot(lambda x: x**2)
|
|
483
|
+
self.play(Create(graph))
|
|
484
|
+
self.wait()
|
|
485
|
+
|
|
486
|
+
# Highlight the derivative
|
|
487
|
+
tangent = ...
|
|
488
|
+
self.play(Create(tangent))
|
|
489
|
+
self.wait()
|
|
490
|
+
|
|
491
|
+
# Show the formula
|
|
492
|
+
formula = MathTex(...)
|
|
493
|
+
self.play(Write(formula))
|
|
494
|
+
self.wait(2)
|
|
495
|
+
|
|
496
|
+
# Transition to next concept
|
|
497
|
+
self.play(FadeOut(VGroup(graph, tangent, formula)))
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
## Scene Class Attributes for Configuration
|
|
501
|
+
|
|
502
|
+
Instead of ManimGL's `CONFIG` dict pattern, ManimCE uses class attributes or `__init__` parameters:
|
|
503
|
+
|
|
504
|
+
```python
|
|
505
|
+
class ConfigurableScene(Scene):
|
|
506
|
+
"""ManimCE pattern: use class attributes for configuration."""
|
|
507
|
+
wave_color = BLUE
|
|
508
|
+
wave_amplitude = 1.0
|
|
509
|
+
n_waves = 5
|
|
510
|
+
run_time = 30
|
|
511
|
+
|
|
512
|
+
def construct(self):
|
|
513
|
+
axes = Axes((-12, 12), (-4, 4))
|
|
514
|
+
for i in range(self.n_waves):
|
|
515
|
+
wave = axes.plot(
|
|
516
|
+
lambda x: self.wave_amplitude * np.sin(x + i),
|
|
517
|
+
color=self.wave_color,
|
|
518
|
+
)
|
|
519
|
+
self.play(Create(wave), run_time=0.5)
|
|
520
|
+
self.wait()
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
## Reusable Helper Functions
|
|
524
|
+
|
|
525
|
+
Complex scenes extract helper functions (the `get_*` naming pattern):
|
|
526
|
+
|
|
527
|
+
```python
|
|
528
|
+
def get_labeled_vector(direction, label_text, color):
|
|
529
|
+
"""Helper to create a consistently styled labeled vector."""
|
|
530
|
+
vec = Vector(direction, color=color)
|
|
531
|
+
label = MathTex(label_text, color=color, font_size=30)
|
|
532
|
+
label.next_to(vec.get_end(), normalize(direction), buff=0.1)
|
|
533
|
+
return VGroup(vec, label)
|
|
534
|
+
|
|
535
|
+
def get_matrix_mob(entries, bracket_color=WHITE):
|
|
536
|
+
"""Create a styled matrix mobject."""
|
|
537
|
+
m = Matrix(entries)
|
|
538
|
+
m.get_brackets().set_color(bracket_color)
|
|
539
|
+
return m
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
## Best Practices Summary
|
|
543
|
+
|
|
544
|
+
1. **One Scene class per conceptual section** -- 10-40 scenes per video
|
|
545
|
+
2. **Comment every section** inside `construct()` with `# Description`
|
|
546
|
+
3. **Mystery first** -- open with the surprising result
|
|
547
|
+
4. **Progressive complexity** -- simple to complex, concrete to abstract
|
|
548
|
+
5. **Generous pauses** -- `self.wait(2)` after key reveals
|
|
549
|
+
6. **Rearrange, don't cut** -- move existing elements rather than fade-out/fade-in when possible
|
|
550
|
+
7. **Extract helpers** -- `get_*` functions for reusable mobject creation
|
|
551
|
+
8. **Class attributes** for configuration, not `CONFIG` dicts
|
|
552
|
+
9. **End with a question** -- each section should leave the viewer wanting the next one
|