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,594 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: advanced-animations
|
|
3
|
+
description: Advanced animation patterns from 3b1b including TransformMatchingTex, VShowPassingFlash, UpdateFromAlphaFunc, generate_target, always_redraw, and more
|
|
4
|
+
metadata:
|
|
5
|
+
tags: advanced, transform, matching-tex, alpha-func, updater, always-redraw, generate-target, move-to-target, homotopy, number-changeable
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Advanced Animation Patterns
|
|
9
|
+
|
|
10
|
+
These are the advanced animation techniques used throughout 3b1b's videos, translated to ManimCE equivalents.
|
|
11
|
+
|
|
12
|
+
## TransformMatchingTex for Equation Manipulation
|
|
13
|
+
|
|
14
|
+
Transform equations term-by-term using matching LaTeX substrings. This is the backbone of algebraic derivation scenes.
|
|
15
|
+
|
|
16
|
+
```python
|
|
17
|
+
from manim import *
|
|
18
|
+
|
|
19
|
+
class TransformMatchingTexDemo(Scene):
|
|
20
|
+
"""Smoothly morph one equation into another, matching terms by LaTeX."""
|
|
21
|
+
|
|
22
|
+
def construct(self):
|
|
23
|
+
# Step 1: Start equation
|
|
24
|
+
eq1 = MathTex(
|
|
25
|
+
r"x^2", r"+", r"2x", r"+", r"1", r"=", r"0"
|
|
26
|
+
)
|
|
27
|
+
self.play(Write(eq1))
|
|
28
|
+
self.wait()
|
|
29
|
+
|
|
30
|
+
# Step 2: Factor
|
|
31
|
+
eq2 = MathTex(
|
|
32
|
+
r"(", r"x", r"+", r"1", r")^2", r"=", r"0"
|
|
33
|
+
)
|
|
34
|
+
self.play(TransformMatchingTex(eq1, eq2))
|
|
35
|
+
self.wait()
|
|
36
|
+
|
|
37
|
+
# Step 3: Take square root
|
|
38
|
+
eq3 = MathTex(r"x", r"+", r"1", r"=", r"0")
|
|
39
|
+
self.play(TransformMatchingTex(eq2, eq3))
|
|
40
|
+
self.wait()
|
|
41
|
+
|
|
42
|
+
# Step 4: Solve
|
|
43
|
+
eq4 = MathTex(r"x", r"=", r"-1")
|
|
44
|
+
self.play(TransformMatchingTex(eq3, eq4))
|
|
45
|
+
self.wait(2)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### TransformMatchingShapes for Non-TeX Morphing
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
class TransformMatchingShapesDemo(Scene):
|
|
52
|
+
"""Morph shapes that share visual similarity."""
|
|
53
|
+
|
|
54
|
+
def construct(self):
|
|
55
|
+
text1 = Text("Hello World", font_size=48)
|
|
56
|
+
text2 = Text("World Hello", font_size=48, color=YELLOW)
|
|
57
|
+
|
|
58
|
+
self.play(Write(text1))
|
|
59
|
+
self.wait()
|
|
60
|
+
self.play(TransformMatchingShapes(text1, text2))
|
|
61
|
+
self.wait()
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## VShowPassingFlash for Flowing Effects
|
|
65
|
+
|
|
66
|
+
A flash of light that travels along a path. Used in `custom/end_screen.py` and throughout for highlighting flow/movement.
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
class FlashingEffect(Scene):
|
|
70
|
+
"""VShowPassingFlash: a pulse traveling along a path."""
|
|
71
|
+
|
|
72
|
+
def construct(self):
|
|
73
|
+
# Create a path
|
|
74
|
+
circle = Circle(radius=2, color=BLUE)
|
|
75
|
+
self.add(circle)
|
|
76
|
+
|
|
77
|
+
# Flash traveling around the circle
|
|
78
|
+
flash_circle = circle.copy().set_stroke(YELLOW, width=8)
|
|
79
|
+
self.play(
|
|
80
|
+
ShowPassingFlash(flash_circle, time_width=0.3),
|
|
81
|
+
run_time=2,
|
|
82
|
+
)
|
|
83
|
+
self.wait()
|
|
84
|
+
|
|
85
|
+
# Multiple flashes on different paths
|
|
86
|
+
paths = VGroup(*[
|
|
87
|
+
Line(3 * LEFT + i * 0.5 * UP, 3 * RIGHT + i * 0.5 * UP, color=BLUE)
|
|
88
|
+
for i in range(-3, 4)
|
|
89
|
+
])
|
|
90
|
+
self.add(paths)
|
|
91
|
+
|
|
92
|
+
flash_paths = paths.copy().set_stroke(WHITE, 5)
|
|
93
|
+
self.play(
|
|
94
|
+
LaggedStartMap(
|
|
95
|
+
ShowPassingFlash, flash_paths,
|
|
96
|
+
time_width=0.5, lag_ratio=0.15,
|
|
97
|
+
),
|
|
98
|
+
run_time=3,
|
|
99
|
+
)
|
|
100
|
+
self.wait()
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## UpdateFromAlphaFunc for Custom Interpolation
|
|
104
|
+
|
|
105
|
+
Directly from `_2020/sir.py` and `_2017/mug.py` -- gives you full control over how a mobject changes as alpha goes from 0 to 1.
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
class AlphaFuncDemo(Scene):
|
|
109
|
+
"""UpdateFromAlphaFunc: custom animation via alpha parameter."""
|
|
110
|
+
|
|
111
|
+
def construct(self):
|
|
112
|
+
circle = Circle(radius=1, color=WHITE)
|
|
113
|
+
self.add(circle)
|
|
114
|
+
|
|
115
|
+
# Custom color transition using alpha
|
|
116
|
+
start_color = BLUE
|
|
117
|
+
end_color = RED
|
|
118
|
+
|
|
119
|
+
self.play(
|
|
120
|
+
UpdateFromAlphaFunc(
|
|
121
|
+
circle,
|
|
122
|
+
lambda mob, alpha: mob.set_color(
|
|
123
|
+
interpolate_color(start_color, end_color, alpha)
|
|
124
|
+
),
|
|
125
|
+
run_time=2,
|
|
126
|
+
)
|
|
127
|
+
)
|
|
128
|
+
self.wait()
|
|
129
|
+
|
|
130
|
+
# Complex multi-property animation
|
|
131
|
+
square = Square(side_length=1, color=GREEN, fill_opacity=0.5)
|
|
132
|
+
self.add(square)
|
|
133
|
+
|
|
134
|
+
def complex_update(mob, alpha):
|
|
135
|
+
mob.set_width(1 + 2 * alpha)
|
|
136
|
+
mob.set_fill(opacity=1 - 0.5 * alpha)
|
|
137
|
+
mob.rotate(alpha * PI / 4)
|
|
138
|
+
mob.set_color(interpolate_color(GREEN, YELLOW, alpha))
|
|
139
|
+
|
|
140
|
+
self.play(
|
|
141
|
+
UpdateFromAlphaFunc(square, complex_update, run_time=3)
|
|
142
|
+
)
|
|
143
|
+
self.wait()
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## always_redraw vs add_updater
|
|
147
|
+
|
|
148
|
+
### always_redraw: Rebuild Every Frame
|
|
149
|
+
|
|
150
|
+
Use when the mobject needs to be completely reconstructed each frame (e.g., geometry that depends on a moving point).
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
class AlwaysRedrawDemo(Scene):
|
|
154
|
+
"""always_redraw: complete reconstruction every frame."""
|
|
155
|
+
|
|
156
|
+
def construct(self):
|
|
157
|
+
axes = Axes(x_range=[-3, 3], y_range=[-2, 4], x_length=8, y_length=5)
|
|
158
|
+
graph = axes.plot(lambda x: x**2, color=YELLOW)
|
|
159
|
+
self.add(axes, graph)
|
|
160
|
+
|
|
161
|
+
# Tracker controls the x-position
|
|
162
|
+
x_tracker = ValueTracker(-2)
|
|
163
|
+
|
|
164
|
+
# Dot on the graph (always_redraw reconstructs it each frame)
|
|
165
|
+
dot = always_redraw(
|
|
166
|
+
lambda: Dot(
|
|
167
|
+
axes.i2gp(x_tracker.get_value(), graph),
|
|
168
|
+
color=RED,
|
|
169
|
+
)
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
# Tangent line that follows the dot
|
|
173
|
+
tangent = always_redraw(
|
|
174
|
+
lambda: axes.get_secant_slope_group(
|
|
175
|
+
x_tracker.get_value(), graph, dx=0.01,
|
|
176
|
+
secant_line_color=BLUE,
|
|
177
|
+
secant_line_length=4,
|
|
178
|
+
)
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
# Horizontal and vertical lines to axes
|
|
182
|
+
h_line = always_redraw(
|
|
183
|
+
lambda: axes.get_horizontal_line(
|
|
184
|
+
axes.i2gp(x_tracker.get_value(), graph),
|
|
185
|
+
line_config={"dashed_ratio": 0.5},
|
|
186
|
+
)
|
|
187
|
+
)
|
|
188
|
+
v_line = always_redraw(
|
|
189
|
+
lambda: axes.get_vertical_line(
|
|
190
|
+
axes.i2gp(x_tracker.get_value(), graph),
|
|
191
|
+
line_config={"dashed_ratio": 0.5},
|
|
192
|
+
)
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
self.add(dot, tangent, h_line, v_line)
|
|
196
|
+
|
|
197
|
+
# Animate the tracker
|
|
198
|
+
self.play(x_tracker.animate.set_value(2), run_time=5)
|
|
199
|
+
self.wait()
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### add_updater: Modify In-Place
|
|
203
|
+
|
|
204
|
+
Use when you need to adjust properties of an existing mobject without rebuilding it.
|
|
205
|
+
|
|
206
|
+
```python
|
|
207
|
+
class AddUpdaterDemo(Scene):
|
|
208
|
+
"""add_updater: modify an existing mobject each frame."""
|
|
209
|
+
|
|
210
|
+
def construct(self):
|
|
211
|
+
# A label that always follows another object
|
|
212
|
+
dot = Dot(color=YELLOW)
|
|
213
|
+
label = Text("Point", font_size=24)
|
|
214
|
+
label.add_updater(lambda m: m.next_to(dot, UR, buff=0.1))
|
|
215
|
+
|
|
216
|
+
self.add(dot, label)
|
|
217
|
+
|
|
218
|
+
# Move the dot around
|
|
219
|
+
self.play(dot.animate.shift(3 * RIGHT + UP), run_time=2)
|
|
220
|
+
self.play(dot.animate.shift(2 * DOWN + LEFT), run_time=2)
|
|
221
|
+
self.wait()
|
|
222
|
+
|
|
223
|
+
# Counter that tracks a value
|
|
224
|
+
counter = Integer(0, font_size=48)
|
|
225
|
+
counter.to_corner(UR)
|
|
226
|
+
value_tracker = ValueTracker(0)
|
|
227
|
+
counter.add_updater(
|
|
228
|
+
lambda m: m.set_value(int(value_tracker.get_value()))
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
self.add(counter)
|
|
232
|
+
self.play(value_tracker.animate.set_value(100), run_time=3)
|
|
233
|
+
self.wait()
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### ManimGL f_always to ManimCE
|
|
237
|
+
|
|
238
|
+
In ManimGL, `f_always` provides a shorthand for method-calling updaters:
|
|
239
|
+
```python
|
|
240
|
+
# ManimGL:
|
|
241
|
+
dot.f_always.move_to(lambda: axes.i2gp(tracker.get_value(), graph))
|
|
242
|
+
|
|
243
|
+
# ManimCE equivalent:
|
|
244
|
+
dot.add_updater(lambda m: m.move_to(axes.i2gp(tracker.get_value(), graph)))
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## generate_target() / MoveToTarget Pattern
|
|
248
|
+
|
|
249
|
+
The classic 3b1b pattern for complex repositioning: create a target copy, modify it, then animate to it.
|
|
250
|
+
|
|
251
|
+
```python
|
|
252
|
+
class GenerateTargetDemo(Scene):
|
|
253
|
+
"""generate_target: set up a target state, then animate to it."""
|
|
254
|
+
|
|
255
|
+
def construct(self):
|
|
256
|
+
# Create initial mobject
|
|
257
|
+
equation = MathTex(r"E = mc^2", font_size=72)
|
|
258
|
+
self.play(Write(equation))
|
|
259
|
+
self.wait()
|
|
260
|
+
|
|
261
|
+
# Generate a target and modify it
|
|
262
|
+
equation.generate_target()
|
|
263
|
+
equation.target.scale(0.5)
|
|
264
|
+
equation.target.to_corner(UL)
|
|
265
|
+
equation.target.set_opacity(0.6)
|
|
266
|
+
|
|
267
|
+
# Animate to target
|
|
268
|
+
self.play(MoveToTarget(equation), run_time=1.5)
|
|
269
|
+
self.wait()
|
|
270
|
+
|
|
271
|
+
# This is equivalent to (but more flexible than):
|
|
272
|
+
# self.play(equation.animate.scale(0.5).to_corner(UL).set_opacity(0.6))
|
|
273
|
+
# generate_target is preferred when you need to inspect/adjust the target
|
|
274
|
+
|
|
275
|
+
# Complex rearrangement example
|
|
276
|
+
items = VGroup(*[
|
|
277
|
+
Circle(radius=0.3, color=random_bright_color(), fill_opacity=0.7)
|
|
278
|
+
for _ in range(6)
|
|
279
|
+
])
|
|
280
|
+
items.arrange(RIGHT, buff=0.3)
|
|
281
|
+
self.play(LaggedStartMap(FadeIn, items, lag_ratio=0.1))
|
|
282
|
+
self.wait()
|
|
283
|
+
|
|
284
|
+
# Rearrange into 2x3 grid
|
|
285
|
+
for i, item in enumerate(items):
|
|
286
|
+
item.generate_target()
|
|
287
|
+
row, col = divmod(i, 3)
|
|
288
|
+
item.target.move_to(
|
|
289
|
+
2 * DOWN + (col - 1) * 1.2 * RIGHT + row * 1.2 * DOWN
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
self.play(
|
|
293
|
+
LaggedStartMap(MoveToTarget, items, lag_ratio=0.1),
|
|
294
|
+
run_time=1.5,
|
|
295
|
+
)
|
|
296
|
+
self.wait()
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
## make_number_changeable() Equivalent
|
|
300
|
+
|
|
301
|
+
In ManimGL, `label.make_number_changeable("0.00")` creates a dynamic decimal that can be updated. ManimCE uses `DecimalNumber` with updaters.
|
|
302
|
+
|
|
303
|
+
```python
|
|
304
|
+
class DynamicNumbers(Scene):
|
|
305
|
+
"""ManimCE equivalent of make_number_changeable."""
|
|
306
|
+
|
|
307
|
+
def construct(self):
|
|
308
|
+
# Static template with a placeholder for the number
|
|
309
|
+
tracker = ValueTracker(0)
|
|
310
|
+
|
|
311
|
+
# Create the label with a DecimalNumber
|
|
312
|
+
prefix = MathTex(r"c = ", font_size=36)
|
|
313
|
+
number = DecimalNumber(
|
|
314
|
+
0, num_decimal_places=4, font_size=36, color=RED,
|
|
315
|
+
include_sign=True,
|
|
316
|
+
)
|
|
317
|
+
number.add_updater(lambda m: m.set_value(tracker.get_value()))
|
|
318
|
+
number.add_updater(lambda m: m.next_to(prefix, RIGHT, buff=0.1))
|
|
319
|
+
|
|
320
|
+
label = VGroup(prefix, number)
|
|
321
|
+
label.to_corner(UR)
|
|
322
|
+
self.add(label)
|
|
323
|
+
|
|
324
|
+
# Animate the value changing
|
|
325
|
+
self.play(tracker.animate.set_value(0.01), run_time=3)
|
|
326
|
+
self.wait()
|
|
327
|
+
self.play(tracker.animate.set_value(1.5), run_time=2)
|
|
328
|
+
self.wait()
|
|
329
|
+
|
|
330
|
+
# Alternative: use a variable
|
|
331
|
+
var = Variable(0, MathTex(r"\theta"), num_decimal_places=2)
|
|
332
|
+
var.to_edge(DOWN)
|
|
333
|
+
self.add(var)
|
|
334
|
+
self.play(var.tracker.animate.set_value(PI), run_time=2)
|
|
335
|
+
self.wait()
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
## Homotopy for Continuous Deformation
|
|
339
|
+
|
|
340
|
+
From `_2016/eola/chapter1.py` -- continuously deform a mobject via a homotopy function.
|
|
341
|
+
|
|
342
|
+
```python
|
|
343
|
+
class HomotopyDemo(Scene):
|
|
344
|
+
"""Homotopy: continuously deform space."""
|
|
345
|
+
|
|
346
|
+
def construct(self):
|
|
347
|
+
plane = NumberPlane()
|
|
348
|
+
self.add(plane)
|
|
349
|
+
|
|
350
|
+
# Plane wave homotopy (from EoLA)
|
|
351
|
+
def wave_homotopy(x, y, z, t):
|
|
352
|
+
"""Deform the plane with a wave effect."""
|
|
353
|
+
offset = np.sin(x - t * TAU) * 0.5 * t
|
|
354
|
+
return [x, y + offset, z]
|
|
355
|
+
|
|
356
|
+
self.play(
|
|
357
|
+
Homotopy(wave_homotopy, plane),
|
|
358
|
+
run_time=3,
|
|
359
|
+
)
|
|
360
|
+
self.wait()
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
## ChangeSpeed / Rate Function Composition
|
|
364
|
+
|
|
365
|
+
Control animation speed dynamically. In ManimCE, use custom rate functions.
|
|
366
|
+
|
|
367
|
+
```python
|
|
368
|
+
class SpeedControl(Scene):
|
|
369
|
+
"""Custom rate functions for speed control."""
|
|
370
|
+
|
|
371
|
+
def construct(self):
|
|
372
|
+
circle = Circle(radius=2, color=BLUE)
|
|
373
|
+
|
|
374
|
+
# Custom rate function: slow start, fast middle, slow end
|
|
375
|
+
def custom_rate(t):
|
|
376
|
+
return smooth(t) # Built-in smooth (ease in/out)
|
|
377
|
+
|
|
378
|
+
# Rush into (accelerating)
|
|
379
|
+
dot1 = Dot(color=RED)
|
|
380
|
+
dot1.move_to(3 * LEFT)
|
|
381
|
+
self.play(
|
|
382
|
+
dot1.animate.move_to(3 * RIGHT),
|
|
383
|
+
rate_func=rate_functions.rush_into,
|
|
384
|
+
run_time=2,
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
# Rush from (decelerating)
|
|
388
|
+
dot2 = Dot(color=GREEN)
|
|
389
|
+
dot2.move_to(3 * LEFT + DOWN)
|
|
390
|
+
self.play(
|
|
391
|
+
dot2.animate.move_to(3 * RIGHT + DOWN),
|
|
392
|
+
rate_func=rate_functions.rush_from,
|
|
393
|
+
run_time=2,
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
# There and back
|
|
397
|
+
dot3 = Dot(color=YELLOW)
|
|
398
|
+
self.play(
|
|
399
|
+
dot3.animate.shift(3 * RIGHT),
|
|
400
|
+
rate_func=there_and_back,
|
|
401
|
+
run_time=2,
|
|
402
|
+
)
|
|
403
|
+
self.wait()
|
|
404
|
+
|
|
405
|
+
# Custom double-speed function
|
|
406
|
+
def double_speed(t):
|
|
407
|
+
"""Play at 2x speed, then hold."""
|
|
408
|
+
return min(2 * t, 1)
|
|
409
|
+
|
|
410
|
+
square = Square(color=PURPLE)
|
|
411
|
+
self.play(
|
|
412
|
+
Create(square),
|
|
413
|
+
rate_func=double_speed,
|
|
414
|
+
run_time=2, # Actually creates in 1s, holds for 1s
|
|
415
|
+
)
|
|
416
|
+
self.wait()
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
## Cycle Animation (Looping)
|
|
420
|
+
|
|
421
|
+
Create looping animations using updaters or repeated play calls.
|
|
422
|
+
|
|
423
|
+
```python
|
|
424
|
+
class CycleAnimationDemo(Scene):
|
|
425
|
+
"""Looping animations using updaters."""
|
|
426
|
+
|
|
427
|
+
def construct(self):
|
|
428
|
+
# Method 1: Continuous rotation via updater
|
|
429
|
+
square = Square(color=BLUE, fill_opacity=0.5)
|
|
430
|
+
square.add_updater(lambda m, dt: m.rotate(dt * PI / 2))
|
|
431
|
+
self.add(square)
|
|
432
|
+
self.wait(4) # Rotates continuously
|
|
433
|
+
|
|
434
|
+
square.clear_updaters()
|
|
435
|
+
|
|
436
|
+
# Method 2: Pulsing effect via updater
|
|
437
|
+
circle = Circle(radius=1, color=RED, fill_opacity=0.3)
|
|
438
|
+
time_tracker = ValueTracker(0)
|
|
439
|
+
time_tracker.add_updater(lambda m, dt: m.increment_value(dt))
|
|
440
|
+
|
|
441
|
+
circle.add_updater(
|
|
442
|
+
lambda m: m.set_width(
|
|
443
|
+
2 + 0.3 * np.sin(time_tracker.get_value() * 2 * PI)
|
|
444
|
+
)
|
|
445
|
+
)
|
|
446
|
+
|
|
447
|
+
self.add(circle, time_tracker)
|
|
448
|
+
self.wait(5)
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
## LaggedStart / LaggedStartMap Patterns
|
|
452
|
+
|
|
453
|
+
The most-used animation combinator in 3b1b's code.
|
|
454
|
+
|
|
455
|
+
```python
|
|
456
|
+
class LaggedPatterns(Scene):
|
|
457
|
+
"""The many uses of LaggedStart and LaggedStartMap."""
|
|
458
|
+
|
|
459
|
+
def construct(self):
|
|
460
|
+
# LaggedStartMap: apply the same animation to each submobject
|
|
461
|
+
dots = VGroup(*[Dot(color=YELLOW) for _ in range(10)])
|
|
462
|
+
dots.arrange(RIGHT, buff=0.3)
|
|
463
|
+
|
|
464
|
+
self.play(
|
|
465
|
+
LaggedStartMap(FadeIn, dots, shift=0.5 * UP, lag_ratio=0.15),
|
|
466
|
+
run_time=2,
|
|
467
|
+
)
|
|
468
|
+
self.wait()
|
|
469
|
+
|
|
470
|
+
# LaggedStart: different animations, staggered
|
|
471
|
+
anims = [
|
|
472
|
+
dot.animate.shift(UP * np.random.uniform(0.5, 1.5))
|
|
473
|
+
for dot in dots
|
|
474
|
+
]
|
|
475
|
+
self.play(LaggedStart(*anims, lag_ratio=0.1, run_time=2))
|
|
476
|
+
self.wait()
|
|
477
|
+
|
|
478
|
+
# LaggedStartMap with GrowArrow
|
|
479
|
+
arrows = VGroup(*[
|
|
480
|
+
Arrow(ORIGIN, RIGHT, color=BLUE) for _ in range(5)
|
|
481
|
+
])
|
|
482
|
+
arrows.arrange(DOWN, buff=0.3).shift(3 * LEFT)
|
|
483
|
+
|
|
484
|
+
self.play(
|
|
485
|
+
LaggedStartMap(GrowArrow, arrows, lag_ratio=0.2, run_time=1.5)
|
|
486
|
+
)
|
|
487
|
+
self.wait()
|
|
488
|
+
|
|
489
|
+
# LaggedStartMap for removal
|
|
490
|
+
self.play(
|
|
491
|
+
LaggedStartMap(FadeOut, dots, shift=DOWN, lag_ratio=0.05),
|
|
492
|
+
run_time=1,
|
|
493
|
+
)
|
|
494
|
+
self.wait()
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
## Succession for Sequential Animations
|
|
498
|
+
|
|
499
|
+
```python
|
|
500
|
+
class SuccessionDemo(Scene):
|
|
501
|
+
"""Succession: chain animations into one play() call."""
|
|
502
|
+
|
|
503
|
+
def construct(self):
|
|
504
|
+
circle = Circle(color=BLUE)
|
|
505
|
+
square = Square(color=RED)
|
|
506
|
+
triangle = Triangle(color=GREEN)
|
|
507
|
+
|
|
508
|
+
self.play(
|
|
509
|
+
Succession(
|
|
510
|
+
Create(circle),
|
|
511
|
+
circle.animate.shift(LEFT * 2),
|
|
512
|
+
FadeIn(square),
|
|
513
|
+
square.animate.shift(RIGHT * 2),
|
|
514
|
+
FadeIn(triangle),
|
|
515
|
+
run_time=6,
|
|
516
|
+
)
|
|
517
|
+
)
|
|
518
|
+
self.wait()
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
## AnimationGroup with time_span (ManimCE Specific)
|
|
522
|
+
|
|
523
|
+
```python
|
|
524
|
+
class TimeSpanDemo(Scene):
|
|
525
|
+
"""Control when animations start and stop within a play() call."""
|
|
526
|
+
|
|
527
|
+
def construct(self):
|
|
528
|
+
c1 = Circle(color=BLUE).shift(2 * LEFT)
|
|
529
|
+
c2 = Circle(color=RED)
|
|
530
|
+
c3 = Circle(color=GREEN).shift(2 * RIGHT)
|
|
531
|
+
|
|
532
|
+
# c1 fades in from 0-1s, c2 from 0.5-1.5s, c3 from 1-2s
|
|
533
|
+
self.play(
|
|
534
|
+
FadeIn(c1),
|
|
535
|
+
FadeIn(c2),
|
|
536
|
+
FadeIn(c3),
|
|
537
|
+
run_time=2,
|
|
538
|
+
)
|
|
539
|
+
self.wait()
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
## Indicate / Circumscribe for Emphasis
|
|
543
|
+
|
|
544
|
+
```python
|
|
545
|
+
class EmphasisAnimations(Scene):
|
|
546
|
+
"""Drawing attention to specific elements."""
|
|
547
|
+
|
|
548
|
+
def construct(self):
|
|
549
|
+
equation = MathTex(r"a^2 + b^2 = c^2")
|
|
550
|
+
equation.scale(1.5)
|
|
551
|
+
self.add(equation)
|
|
552
|
+
|
|
553
|
+
# Circumscribe: draw a shape around
|
|
554
|
+
self.play(Circumscribe(equation[0][:2], color=YELLOW)) # a^2
|
|
555
|
+
self.wait()
|
|
556
|
+
|
|
557
|
+
# Indicate: briefly highlight
|
|
558
|
+
self.play(Indicate(equation[0][4:6], color=RED)) # b^2
|
|
559
|
+
self.wait()
|
|
560
|
+
|
|
561
|
+
# Flash: point flash effect
|
|
562
|
+
self.play(Flash(equation[0][-2:], color=GREEN)) # c^2
|
|
563
|
+
self.wait()
|
|
564
|
+
|
|
565
|
+
# Wiggle: shake for attention
|
|
566
|
+
self.play(Wiggle(equation))
|
|
567
|
+
self.wait()
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
## CountInFrom for Animated Number Reveal
|
|
571
|
+
|
|
572
|
+
```python
|
|
573
|
+
class CountInDemo(Scene):
|
|
574
|
+
"""Animate a number counting up from zero."""
|
|
575
|
+
|
|
576
|
+
def construct(self):
|
|
577
|
+
# Large integer counting up
|
|
578
|
+
number = Integer(12288, font_size=72, color=YELLOW)
|
|
579
|
+
self.play(CountInFrom(number, 0), run_time=2)
|
|
580
|
+
self.wait()
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
## Best Practices
|
|
584
|
+
|
|
585
|
+
1. **TransformMatchingTex** for equation derivations -- split LaTeX into meaningful substrings
|
|
586
|
+
2. **generate_target / MoveToTarget** for complex repositioning of existing mobjects
|
|
587
|
+
3. **always_redraw** when geometry must be rebuilt from scratch each frame (e.g., tangent lines)
|
|
588
|
+
4. **add_updater** when modifying properties of an existing mobject (position, color, value)
|
|
589
|
+
5. **LaggedStartMap** for any operation applied to a group -- controls the cascade timing
|
|
590
|
+
6. **UpdateFromAlphaFunc** for fully custom animation logic
|
|
591
|
+
7. **DecimalNumber with updater** instead of ManimGL's `make_number_changeable`
|
|
592
|
+
8. **rate_func** for speed control: `smooth`, `rush_into`, `rush_from`, `there_and_back`, `linear`
|
|
593
|
+
9. **ShowPassingFlash** for flowing highlight effects along paths
|
|
594
|
+
10. **Circumscribe / Indicate / Flash** for drawing attention to specific parts
|