Implement Manim 2D backend for CBFAnimator - #367
Conversation
Fills the declared gap where backend='manim' raised NotImplementedError. Adds _ManimMixin + CBFAnimator2DScene rendering the full declarative element API (goals, obstacles, trajectories, agents, predictions, time overlay) on a white canvas, with quality selected via the existing manim-<quality> backend-string convention. Renders MP4 or GIF; module imports cleanly without manim installed (guarded like the 3D backend). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ender Replaces the NotImplementedError pin with quality-parsing, import-guard, and save-dispatch tests (dependency-free) plus a real-render smoke test that skips when manim is not installed (as in CI). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Demo renders the README quick-start CBF example (unicycle reach-avoid) via backend='manim-medium'. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…mple Repeated renders in one process crashed because Manim's global config and animation cache outlived each render's temp media dir. Scope overrides with tempconfig and disable caching; add a regression test. examples/unicycle/reach_goal/manim_2d_animation.py reproduces the showcase GIF from the README quick-start simulation (CBFKIT_TEST_MODE-gated). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
bardhh
left a comment
There was a problem hiding this comment.
Hi @Eduard-Zippenfenig .
One small change first:
- Please add a prediction case to the smoke test. Right now test_manim_real_render_smoke covers goals/obstacles/agents/trail/time, but the per-frame prediction path (_prediction_group + compute_prediction) isn't tested. Something like this would test that as well (placed before a.save():
a.add_prediction(
source="linear",
agent_x_idx=0, agent_y_idx=1,
agent_vx_idx=2, agent_vy_idx=3,
horizon=5,
)
Thanks for your contributions!
Add the maintainer-requested add_prediction() call to test_manim_real_render_smoke so the per-frame prediction path (_prediction_group + compute_prediction) is exercised during a real render. Also add TestPredictionComputation, a dependency-free unit test of the backend-shared CBFAnimator._compute_prediction, so the prediction logic is verified in CI (the smoke test skips when the manim extra is absent). Covers linear constant-velocity math, the degenerate zero-velocity case, "data" source row selection, and the out-of-range fallback. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Thanks so much for taking a look, and for the quick turnaround! Made the change in The smoke test. Dropped your One small thing I noticed while wiring it up. The One extra I hope is useful. I noticed the smoke test skips whenever the Quick check: the four new tests pass, the smoke test renders (rather than skips) with the prediction in place, and the full Thanks again for the review — really appreciate you making time for it. |
Renders one CBF scene (unicycle reach-avoid) through plotly, matplotlib, and the manim-low/medium/high tiers, reporting wall-clock render time and output file size. Reproducible source for the PR benchmark table. Skips rows whose optional deps are absent; honors CBFKIT_TEST_MODE. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
bardhh
left a comment
There was a problem hiding this comment.
Thanks @Eduard-Zippenfenig — this addresses it exactly, and TestPredictionComputation was a good call given the smoke test skips on CI. On your question: let's keep the degenerate frame-0 prediction as-is — the smoke test's job is exercising the render path, and your unit test already pins the real math where CI can see it. Merging once the checks finish. Appreciate the careful work on this one, including the repeated-render fix and the benchmark script.
Summary
Implements the 2D Manim backend for
CBFAnimator, filling the gap wherebackend="manim"was accepted but raisedNotImplementedError("Manim 2D backend not yet implemented").CBFAnimator(states, backend="manim")(or"manim-<low|medium|high|production>") now renders the full declarative element API to MP4 (or GIF) via Manim — reaching parity with the existing matplotlib and plotly backends while adding render-quality tiers.Demo
Rendered from the README quick-start CBF example (unicycle reach-avoid) with
backend="manim-medium":The existing high-level path works as a drop-in:
visualize_crowd(..., backend="manim")renders pedestrians with safety zones and fading predictions with no other changes.Backend parity
The new backend renders the same declarative element API as the existing ones — nothing new to learn, just a new
backend=value:Benchmark
Same 500-frame CBF scene rendered through each backend with identical content (Python 3.11, manim 0.20.1, single machine — indicative, your hardware will differ). Reproduce with
python benchmarks/animator_backend_comparison.py:Every Manim tier produces a dramatically smaller file than matplotlib (37–156 KiB vs 223 KiB); manim-low and manim-medium also render faster, while manim-high trades render time for full 1080p60 output. plotly stays fastest for interactive HTML, at a much larger payload.
Design
Mirrors the existing 3D module (
manim_3d_multi_robot.py) throughout:try: import manimwithScene = objectfallback — the module imports cleanly when themanimextra is absent (as in CI); the constructor raises the standardcbfkit[manim]ImportErrorvia_require_manim().CBFAnimator2DScenevia class attributes beforescene.render(); animation driven by aValueTracker+ updaters.manim-<quality>backend-string convention and_parse_manim_backend— no new config fields. Invalid quality strings raiseValueErrorbefore the dependency check.tempconfig+disable_caching, so repeated renders in one process stay isolated); the finished video is copied to the requested path._ManimMixinadded toCBFAnimatoralongside the matplotlib/plotly mixins; dispatch added tobuild/animate/save/show. Also adds a[mypy-manim.*]stanza tomypy.ini.Tests
ImportError, invalid-qualityValueError,save()dispatch with a mocked render, andTestPredictionComputation— the shared_compute_predictioncore (linear constant-velocity math, degenerate zero-velocity,datarow selection, out-of-range fallback).ruff check srcclean; changed files formatted with black 25.11.0 (pre-commit pin).Reproduce
The demo GIF regenerates from a committed example:
pip install cbfkit[manim] # + ffmpeg; on macOS also: brew install cairo pango python examples/unicycle/reach_goal/manim_2d_animation.pyRuns the README quick-start CBF simulation (unicycle reaches a goal while a CBF filter keeps it clear of an obstacle) and renders it with
backend="manim-medium"— the exact script behindmedia/showcase/manim_2d_animator.gif.