diff --git a/skills/creative/manim-video/references/animations.md b/skills/creative/manim-video/references/animations.md index b0ca0ab7..84b2cb01 100644 --- a/skills/creative/manim-video/references/animations.md +++ b/skills/creative/manim-video/references/animations.md @@ -120,3 +120,138 @@ self.play(old_content.animate.set_opacity(0.3), FadeIn(new_content)) self.play(FadeOut(Group(*self.mobjects)), run_time=0.5) self.wait(0.3) ``` + +## Reactive Mobjects: always_redraw() + +Rebuild a mobject from scratch every frame — essential when its geometry depends on other animated objects: + +```python +# Brace that follows a resizing square +brace = always_redraw(Brace, square, UP) +self.add(brace) +self.play(square.animate.scale(2)) # brace auto-adjusts + +# Horizontal line that tracks a moving dot +h_line = always_redraw(lambda: axes.get_h_line(dot.get_left())) + +# Label that always stays next to another mobject +label = always_redraw(lambda: Text("here", font_size=20).next_to(dot, UP, buff=0.2)) +``` + +Note: `always_redraw` recreates the mobject every frame. For simple property tracking, use `add_updater` instead (cheaper): +```python +label.add_updater(lambda m: m.next_to(dot, UP)) +``` + +## TracedPath — Trajectory Tracing + +Draw the path a point has traveled: + +```python +dot = Dot(color=YELLOW) +path = TracedPath(dot.get_center, stroke_color=YELLOW, stroke_width=2) +self.add(dot, path) +self.play(dot.animate.shift(RIGHT * 3 + UP * 2), run_time=2) +# path shows the trail the dot left behind + +# Fading trail (dissipates over time): +path = TracedPath(dot.get_center, dissipating_time=0.5, stroke_opacity=[0, 1]) +``` + +Use cases: gradient descent paths, planetary orbits, function tracing, particle trajectories. + +## FadeTransform — Smoother Cross-Fades + +`Transform` morphs shapes through ugly intermediate warping. `FadeTransform` cross-fades with position matching — use it when source and target look different: + +```python +# UGLY: Transform warps circle into square through a blob +self.play(Transform(circle, square)) + +# SMOOTH: FadeTransform cross-fades cleanly +self.play(FadeTransform(circle, square)) + +# FadeTransformPieces: per-submobject FadeTransform +self.play(FadeTransformPieces(group1, group2)) + +# TransformFromCopy: animate a COPY while keeping the original visible +self.play(TransformFromCopy(source, target)) +# source stays on screen, a copy morphs into target +``` + +**Recommendation:** Use `FadeTransform` as default for dissimilar shapes. Use `Transform`/`ReplacementTransform` only for similar shapes (circle→ellipse, equation→equation). + +## ApplyMatrix — Linear Transformation Visualization + +Animate a matrix transformation on mobjects: + +```python +# Apply a 2x2 matrix to a grid +matrix = [[2, 1], [1, 1]] +self.play(ApplyMatrix(matrix, number_plane), run_time=2) + +# Also works on individual mobjects +self.play(ApplyMatrix([[0, -1], [1, 0]], square)) # 90-degree rotation +``` + +Pairs with `LinearTransformationScene` — see `camera-and-3d.md`. + +## squish_rate_func — Time-Window Staggering + +Compress any rate function into a time window within an animation. Enables overlapping stagger without `LaggedStart`: + +```python +self.play( + FadeIn(a, rate_func=squish_rate_func(smooth, 0, 0.5)), # 0% to 50% + FadeIn(b, rate_func=squish_rate_func(smooth, 0.25, 0.75)), # 25% to 75% + FadeIn(c, rate_func=squish_rate_func(smooth, 0.5, 1.0)), # 50% to 100% + run_time=2 +) +``` + +More precise than `LaggedStart` when you need exact overlap control. + +## Additional Rate Functions + +```python +from manim import ( + smooth, linear, rush_into, rush_from, + there_and_back, there_and_back_with_pause, + running_start, double_smooth, wiggle, + lingering, exponential_decay, not_quite_there, + squish_rate_func +) + +# running_start: pulls back before going forward (anticipation) +self.play(FadeIn(mob, rate_func=running_start)) + +# there_and_back_with_pause: goes there, holds, comes back +self.play(mob.animate.shift(UP), rate_func=there_and_back_with_pause) + +# not_quite_there: stops at a fraction of the full animation +self.play(FadeIn(mob, rate_func=not_quite_there(0.7))) +``` + +## ShowIncreasingSubsets / ShowSubmobjectsOneByOne + +Reveal group members progressively — ideal for algorithm visualization: + +```python +# Reveal array elements one at a time +array = Group(*[Square() for _ in range(8)]).arrange(RIGHT) +self.play(ShowIncreasingSubsets(array), run_time=3) + +# Show submobjects with staggered appearance +self.play(ShowSubmobjectsOneByOne(code_lines), run_time=4) +``` + +## ShowPassingFlash + +A flash of light travels along a path: + +```python +# Flash traveling along a curve +self.play(ShowPassingFlash(curve.copy().set_color(YELLOW), time_width=0.3)) + +# Great for: data flow, electrical signals, network traffic +``` diff --git a/skills/creative/manim-video/references/camera-and-3d.md b/skills/creative/manim-video/references/camera-and-3d.md index 71448ad6..3ac8fc11 100644 --- a/skills/creative/manim-video/references/camera-and-3d.md +++ b/skills/creative/manim-video/references/camera-and-3d.md @@ -74,3 +74,62 @@ helix = ParametricFunction( - Surfaces, vector fields, spatial geometry, 3D transforms ## When NOT to Use 3D - 2D concepts, text-heavy scenes, flat data (bar charts, time series) + +## ZoomedScene — Inset Zoom + +Show a magnified inset of a detail while keeping the full view visible: + +```python +class ZoomExample(ZoomedScene): + def __init__(self, **kwargs): + super().__init__( + zoom_factor=0.3, # how much of the scene the zoom box covers + zoomed_display_height=3, # size of the inset + zoomed_display_width=3, + zoomed_camera_frame_starting_position=ORIGIN, + **kwargs + ) + + def construct(self): + self.camera.background_color = BG + # ... create your scene content ... + + # Activate the zoom + self.activate_zooming() + + # Move the zoom frame to a point of interest + self.play(self.zoomed_camera.frame.animate.move_to(detail_point)) + self.wait(2) + + # Deactivate + self.play(self.get_zoomed_display_pop_out_animation(), rate_func=lambda t: smooth(1-t)) +``` + +Use cases: zooming into a specific term in an equation, showing fine detail in a diagram, magnifying a region of a plot. + +## LinearTransformationScene — Linear Algebra + +Pre-built scene with basis vectors and grid for visualizing matrix transformations: + +```python +class LinearTransformExample(LinearTransformationScene): + def __init__(self, **kwargs): + super().__init__( + show_coordinates=True, + show_basis_vectors=True, + **kwargs + ) + + def construct(self): + matrix = [[2, 1], [1, 1]] + + # Add a vector before applying the transform + vector = self.get_vector([1, 2], color=YELLOW) + self.add_vector(vector) + + # Apply the transformation — grid, basis vectors, and your vector all transform + self.apply_matrix(matrix) + self.wait(2) +``` + +This produces the signature 3Blue1Brown "Essence of Linear Algebra" look — grid lines deforming, basis vectors stretching, determinant visualized through area change. diff --git a/skills/creative/manim-video/references/equations.md b/skills/creative/manim-video/references/equations.md index 183691fb..78d63f2b 100644 --- a/skills/creative/manim-video/references/equations.md +++ b/skills/creative/manim-video/references/equations.md @@ -78,3 +78,88 @@ class DerivationScene(Scene): s2.next_to(s1, DOWN, buff=0.8) self.play(s1.animate.set_opacity(0.4), TransformMatchingTex(s1.copy(), s2)) ``` + +## substrings_to_isolate for Complex Equations + +For dense equations where manually splitting into parts is impractical, use `substrings_to_isolate` to tell Manim which substrings to track as individual elements: + +```python +# Without isolation — the whole expression is one blob +lagrangian = MathTex( + r"\mathcal{L} = \bar{\psi}(i \gamma^\mu D_\mu - m)\psi - \tfrac{1}{4}F_{\mu\nu}F^{\mu\nu}" +) + +# With isolation — each named substring is a separate submobject +lagrangian = MathTex( + r"\mathcal{L} = \bar{\psi}(i \gamma^\mu D_\mu - m)\psi - \tfrac{1}{4}F_{\mu\nu}F^{\mu\nu}", + substrings_to_isolate=[r"\psi", r"D_\mu", r"\gamma^\mu", r"F_{\mu\nu}"] +) +# Now you can color individual terms +lagrangian.set_color_by_tex(r"\psi", BLUE) +lagrangian.set_color_by_tex(r"F_{\mu\nu}", YELLOW) +``` + +Essential for `TransformMatchingTex` on complex equations — without isolation, matching fails on dense expressions. + +## Multi-Line Complex Equations + +For equations with multiple related lines, pass each line as a separate argument: + +```python +maxwell = MathTex( + r"\nabla \cdot \mathbf{E} = \frac{\rho}{\epsilon_0}", + r"\nabla \times \mathbf{B} = \mu_0\mathbf{J} + \mu_0\epsilon_0\frac{\partial \mathbf{E}}{\partial t}" +).arrange(DOWN) + +# Each line is a separate submobject — animate independently +self.play(Write(maxwell[0])) +self.wait(1) +self.play(Write(maxwell[1])) +``` + +## TransformMatchingTex with key_map + +Map specific substrings between source and target equations during transformation: + +```python +eq1 = MathTex(r"A^2 + B^2 = C^2") +eq2 = MathTex(r"A^2 = C^2 - B^2") + +self.play(TransformMatchingTex( + eq1, eq2, + key_map={"+": "-"}, # map "+" in source to "-" in target + path_arc=PI / 2, # arc the pieces into position +)) +``` + +## set_color_by_tex — Color by Substring + +```python +eq = MathTex(r"E = mc^2") +eq.set_color_by_tex("E", BLUE) +eq.set_color_by_tex("m", RED) +eq.set_color_by_tex("c", GREEN) +``` + +## TransformMatchingTex with matched_keys + +When matching substrings are ambiguous, specify which to align explicitly: + +```python +kw = dict(font_size=72, t2c={"A": BLUE, "B": TEAL, "C": GREEN}) +lines = [ + MathTex(r"A^2 + B^2 = C^2", **kw), + MathTex(r"A^2 = C^2 - B^2", **kw), + MathTex(r"A^2 = (C + B)(C - B)", **kw), + MathTex(r"A = \sqrt{(C + B)(C - B)}", **kw), +] + +self.play(TransformMatchingTex( + lines[0].copy(), lines[1], + matched_keys=["A^2", "B^2", "C^2"], # explicitly match these + key_map={"+": "-"}, # map + to - + path_arc=PI / 2, # arc pieces into position +)) +``` + +Without `matched_keys`, the animation matches the longest common substrings, which can produce unexpected results on complex equations (e.g., "^2 = C^2" matching across terms). diff --git a/skills/creative/manim-video/references/graphs-and-data.md b/skills/creative/manim-video/references/graphs-and-data.md index c97396c4..e5c36ada 100644 --- a/skills/creative/manim-video/references/graphs-and-data.md +++ b/skills/creative/manim-video/references/graphs-and-data.md @@ -89,3 +89,75 @@ 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)) ``` + +## Graph / DiGraph — Graph Theory Visualization + +Built-in graph mobjects with automatic layout: + +```python +# Undirected graph +g = Graph( + vertices=[1, 2, 3, 4, 5], + edges=[(1, 2), (2, 3), (3, 4), (4, 5), (5, 1), (1, 3)], + layout="spring", # or "circular", "kamada_kawai", "planar", "tree" + labels=True, + vertex_config={"fill_color": PRIMARY}, + edge_config={"stroke_color": SUBTLE}, +) +self.play(Create(g)) + +# Directed graph +dg = DiGraph( + vertices=["A", "B", "C"], + edges=[("A", "B"), ("B", "C"), ("C", "A")], + layout="circular", + labels=True, + edge_config={("A", "B"): {"stroke_color": RED}}, +) + +# Add/remove vertices and edges dynamically +self.play(g.animate.add_vertices(6, positions={6: RIGHT * 2})) +self.play(g.animate.add_edges((1, 6))) +self.play(g.animate.remove_vertices(3)) +``` + +Layout algorithms: `"spring"`, `"circular"`, `"kamada_kawai"`, `"planar"`, `"spectral"`, `"tree"` (for rooted trees, specify `root=`). + +## ArrowVectorField / StreamLines — Vector Fields + +```python +# Arrow field: arrows showing direction at each point +field = ArrowVectorField( + lambda pos: np.array([-pos[1], pos[0], 0]), # rotation field + x_range=[-3, 3], y_range=[-3, 3], + colors=[BLUE, GREEN, YELLOW, RED] +) +self.play(Create(field)) + +# StreamLines: flowing particle traces through the field +stream = StreamLines( + lambda pos: np.array([-pos[1], pos[0], 0]), + stroke_width=2, max_anchors_per_line=30 +) +self.add(stream) +stream.start_animation(warm_up=True, flow_speed=1.5) +self.wait(3) +stream.end_animation() +``` + +Use cases: electromagnetic fields, fluid flow, gradient fields, ODE phase portraits. + +## ComplexPlane / PolarPlane + +```python +# Complex plane with Re/Im labels +cplane = ComplexPlane().add_coordinates() +dot = Dot(cplane.n2p(2 + 1j), color=YELLOW) +label = Text("2+i", font_size=20).next_to(dot, UR, buff=0.1) + +# Apply complex function to the plane +self.play(cplane.animate.apply_complex_function(lambda z: z**2), run_time=3) + +# Polar plane +polar = PolarPlane(radius_max=3).add_coordinates() +``` diff --git a/skills/creative/manim-video/references/mobjects.md b/skills/creative/manim-video/references/mobjects.md index 069eee8f..d9c7b50b 100644 --- a/skills/creative/manim-video/references/mobjects.md +++ b/skills/creative/manim-video/references/mobjects.md @@ -104,3 +104,161 @@ class NetworkNode(Group): 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` + +## SVGMobject — Import SVG Files + +```python +logo = SVGMobject("path/to/logo.svg") +logo.set_color(WHITE).scale(0.5).to_corner(UR) +self.play(FadeIn(logo)) + +# SVG submobjects are individually animatable +for part in logo.submobjects: + self.play(part.animate.set_color(random_color())) +``` + +## ImageMobject — Display Images + +```python +img = ImageMobject("screenshot.png") +img.set_height(3).to_edge(RIGHT) +self.play(FadeIn(img)) +``` + +Note: images cannot be animated with `.animate` (they're raster, not vector). Use `FadeIn`/`FadeOut` and `shift`/`scale` only. + +## Variable — Auto-Updating Display + +```python +var = Variable(0, Text("x"), num_decimal_places=2) +var.move_to(ORIGIN) +self.add(var) + +# Animate the value +self.play(var.tracker.animate.set_value(5), run_time=2) +# Display auto-updates: "x = 5.00" +``` + +Cleaner than manual `DecimalNumber` + `add_updater` for simple labeled-value displays. + +## BulletedList + +```python +bullets = BulletedList( + "First key point", + "Second important fact", + "Third conclusion", + font_size=28 +) +bullets.to_edge(LEFT, buff=1.0) +self.play(Write(bullets)) + +# Highlight individual items +self.play(bullets[1].animate.set_color(YELLOW)) +``` + +## DashedLine and Angle Markers + +```python +# Dashed line (asymptotes, construction lines) +dashed = DashedLine(LEFT * 3, RIGHT * 3, color=SUBTLE, dash_length=0.15) + +# Angle marker between two lines +line1 = Line(ORIGIN, RIGHT * 2) +line2 = Line(ORIGIN, UP * 2 + RIGHT) +angle = Angle(line1, line2, radius=0.5, color=YELLOW) +angle_label = angle.get_value() # returns the angle in radians + +# Right angle marker +right_angle = RightAngle(line1, Line(ORIGIN, UP * 2), length=0.3, color=WHITE) +``` + +## Boolean Operations (CSG) + +Combine, subtract, or intersect 2D shapes: + +```python +circle = Circle(radius=1.5, color=BLUE, fill_opacity=0.5).shift(LEFT * 0.5) +square = Square(side_length=2, color=RED, fill_opacity=0.5).shift(RIGHT * 0.5) + +# Union, Intersection, Difference, Exclusion +union = Union(circle, square, color=GREEN, fill_opacity=0.5) +intersect = Intersection(circle, square, color=YELLOW, fill_opacity=0.5) +diff = Difference(circle, square, color=PURPLE, fill_opacity=0.5) +exclude = Exclusion(circle, square, color=ORANGE, fill_opacity=0.5) +``` + +Use cases: Venn diagrams, set theory, geometric proofs, area calculations. + +## LabeledArrow / LabeledLine + +```python +# Arrow with built-in label (auto-positioned) +arr = LabeledArrow(Text("force", font_size=18), start=LEFT, end=RIGHT, color=RED) + +# Line with label +line = LabeledLine(Text("d = 5m", font_size=18), start=LEFT * 2, end=RIGHT * 2) +``` + +Auto-handles label positioning — cleaner than manual `Arrow` + `Text().next_to()`. + +## Text Color/Font/Style Per-Substring (t2c, t2f, t2s, t2w) + +```python +# Color specific words (t2c = text-to-color) +text = Text( + "Gradient descent minimizes the loss function", + t2c={"Gradient descent": BLUE, "loss function": RED} +) + +# Different fonts per word (t2f = text-to-font) +text = Text( + "Use Menlo for code and Inter for prose", + t2f={"Menlo": "Menlo", "Inter": "Inter"} +) + +# Italic/slant per word (t2s = text-to-slant) +text = Text("Normal and italic text", t2s={"italic": ITALIC}) + +# Bold per word (t2w = text-to-weight) +text = Text("Normal and bold text", t2w={"bold": BOLD}) +``` + +These are much cleaner than creating separate Text objects and grouping them. + +## Backstroke for Readability Over Backgrounds + +When text overlaps other content (graphs, diagrams, images), add a dark stroke behind it: + +```python +# CE syntax: +label.set_stroke(BLACK, width=5, background=True) + +# Apply to a group +for mob in labels: + mob.set_stroke(BLACK, width=4, background=True) +``` + +This is how 3Blue1Brown keeps text readable over complex backgrounds without using BackgroundRectangle. + +## Complex Function Transforms + +Apply complex functions to entire mobjects — transforms the plane: + +```python +c_grid = ComplexPlane() +moving_grid = c_grid.copy() +moving_grid.prepare_for_nonlinear_transform() # adds more sample points for smooth deformation + +self.play( + moving_grid.animate.apply_complex_function(lambda z: z**2), + run_time=5, +) + +# Also works with R3->R3 functions: +self.play(grid.animate.apply_function( + lambda p: [p[0] + 0.5 * math.sin(p[1]), p[1] + 0.5 * math.sin(p[0]), p[2]] +), run_time=5) +``` + +**Critical:** Call `prepare_for_nonlinear_transform()` before applying nonlinear functions — without it, the grid has too few sample points and the deformation looks jagged. diff --git a/skills/creative/manim-video/references/rendering.md b/skills/creative/manim-video/references/rendering.md index f4c86339..882eb19d 100644 --- a/skills/creative/manim-video/references/rendering.md +++ b/skills/creative/manim-video/references/rendering.md @@ -91,3 +91,95 @@ manim -ql --resolution 1080,1080 script.py Scene # 1:1 square 5. Review stitched output 6. Production render at `-qh` 7. Re-stitch + add audio + +## manim.cfg — Project Configuration + +Create `manim.cfg` in the project directory for per-project defaults: + +```ini +[CLI] +quality = low_quality +preview = True +media_dir = ./media + +[renderer] +background_color = #0D1117 + +[tex] +tex_template_file = custom_template.tex +``` + +This eliminates repetitive CLI flags and `self.camera.background_color` in every scene. + +## Sections — Chapter Markers + +Mark sections within a scene for organized output: + +```python +class LongVideo(Scene): + def construct(self): + self.next_section("Introduction") + # ... intro content ... + + self.next_section("Main Concept") + # ... main content ... + + self.next_section("Conclusion") + # ... closing ... +``` + +Render individual sections: `manim --save_sections script.py LongVideo` +This outputs separate video files per section — useful for long videos where you want to re-render only one part. + +## manim-voiceover Plugin (Recommended for Narrated Videos) + +The official `manim-voiceover` plugin integrates TTS directly into scene code, auto-syncing animation duration to voiceover length. This is significantly cleaner than the manual ffmpeg muxing approach above. + +### Installation + +```bash +pip install "manim-voiceover[elevenlabs]" +# Or for free/local TTS: +pip install "manim-voiceover[gtts]" # Google TTS (free, lower quality) +pip install "manim-voiceover[azure]" # Azure Cognitive Services +``` + +### Usage + +```python +from manim import * +from manim_voiceover import VoiceoverScene +from manim_voiceover.services.elevenlabs import ElevenLabsService + +class NarratedScene(VoiceoverScene): + def construct(self): + self.set_speech_service(ElevenLabsService( + voice_name="Alice", + model_id="eleven_multilingual_v2" + )) + + # Voiceover auto-controls scene duration + with self.voiceover(text="Here is a circle being drawn.") as tracker: + self.play(Create(Circle()), run_time=tracker.duration) + + with self.voiceover(text="Now let's transform it into a square.") as tracker: + self.play(Transform(circle, Square()), run_time=tracker.duration) +``` + +### Key Features + +- `tracker.duration` — total voiceover duration in seconds +- `tracker.time_until_bookmark("mark1")` — sync specific animations to specific words +- Auto-generates subtitle `.srt` files +- Caches audio locally — re-renders don't re-generate TTS +- Works with: ElevenLabs, Azure, Google TTS, pyttsx3 (offline), and custom services + +### Bookmarks for Precise Sync + +```python +with self.voiceover(text='This is a circle.') as tracker: + self.wait_until_bookmark("circle") + self.play(Create(Circle()), run_time=tracker.time_until_bookmark("circle", limit=1)) +``` + +This is the recommended approach for any video with narration. The manual ffmpeg muxing workflow above is still useful for adding background music or post-production audio mixing.