04 · Figures & Animations

← Docs home · Task: replace a static figure with a Manim-drawn static graphic or a silent looping animation.

Source of truth: figure-animator, figure-batch-render, manim-textbook, manim-render-checkpoint. CLI: scripts/workflows/figure_animate/. Scenes: manim-videos/figure-*/.


Decide first: static or animation?

Use the figure-animator decision tree (SKILL.md):

Memory rule: exercise-prompt figures are always static; an animation in a problem reveals the answer.

Either way you get a dual-theme output (light + dark) so the figure matches the reader’s mode.


Procedure A — author one figure (static or animated)

  1. Discover candidates
    python -m figure_animate discover --project "Calculus Volume 1" --section 3.1
    
  2. Author the scene — write manim-videos/figure-<n>.<m>-<slug>/script.py.
    • Parameterize the theme (FIGURE_THEME=light|dark).
    • For an animation: drive motion with a ValueTracker + always_redraw for a smooth monotonic sweep.
    • Keep the slug ≤ 4 hyphen-tokens — longer slugs truncate the render path.
    • Shield labels from lint auto-drift by wrapping figure + labels in a Group() (see memory: figure visual-verify).
  3. Smoke-check before a full render — the manim-render-checkpoint procedure: render at low quality (-ql), extract a midframe PNG, compare to the storyboard. Only then commit to a full -qh render.
  4. Render light + dark.
    • Animation → MP4 pair, then apply an FFmpeg reverse + concat to make a ping-pong loop (~8 s, ~1 MB/theme).
    • Static → PNG pair.
  5. Visual-verifylint exit 0 ≠ correct figure. Read the rendered PNGs and confirm labels, axes, and sweep direction (AnnularSector pies sweep CCW).
  6. Register in the manifest — add an entry to projects/<Book>/figure_animations.yaml with mode: animation|static, caption:, and a cache_buster: token.
  7. Publish with the swap applied
    python scripts/workflows/publish.py --project "Calculus Volume 1" --section 3.1 --auto-render
    
    publish.py’s figure-animator survival guard rewrites the <img> to a <figure-anim-wrap> (or <figure-static-wrap>) in the published HTML. The source HTML keeps its plain <img>.

Outputs

Cache busting: when you re-render a figure, bump its cache_buster: in figure_animations.yaml and the ?v= token in the HTML together — see the bump-cache-version skill and 08.


Procedure B — batch render a whole section’s figures

Use the figure-batch-render skill when a section has many candidates:

  1. Triage the manifest — classify each entry static vs animation.
  2. Validate slugs (≤4 hyphen-tokens) before authoring — path truncation is the #1 batch failure.
  3. Bulk-author all script.py files.
  4. Batch render (expect 15–30 % first-pass failures):
    python -m figure_animate render --project "<Book>" --execute --parallel 4
    
  5. Triage failures (slug too long / scene error / timeout / missing import), then targeted re-render: figure_animate render --only <slug> --execute.
  6. Sweep orphan PNGs after renders settle, then full publish:
    python scripts/workflows/publish.py --project "<Book>" --sweep-only
    python scripts/workflows/publish.py --project "<Book>" --section X.Y --auto-render
    

Gotchas

For narrated, multi-scene videos (not silent figure loops), see 05 · Section Videos.