From f7bdf1fba12cf5e21d7dfc185a8d2da18e6fd627 Mon Sep 17 00:00:00 2001 From: Eduard-Zippenfenig Date: Sat, 18 Jul 2026 18:08:54 +0300 Subject: [PATCH 1/4] feat(animators): implement Manim 2D backend for CBFAnimator 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- backend-string convention. Renders MP4 or GIF; module imports cleanly without manim installed (guarded like the 3D backend). Co-Authored-By: Claude Fable 5 --- mypy.ini | 3 + src/cbfkit/utils/animators/__init__.py | 8 +- src/cbfkit/utils/animators/animator.py | 42 +- src/cbfkit/utils/animators/manim_backend.py | 460 ++++++++++++++++++++ 4 files changed, 498 insertions(+), 15 deletions(-) create mode 100644 src/cbfkit/utils/animators/manim_backend.py diff --git a/mypy.ini b/mypy.ini index 5a0cf492..0d498ace 100644 --- a/mypy.ini +++ b/mypy.ini @@ -40,6 +40,9 @@ ignore_missing_imports = True [mypy-matplotlib.*] ignore_missing_imports = True +[mypy-manim.*] +ignore_missing_imports = True + [mypy-scipy.*] ignore_missing_imports = True diff --git a/src/cbfkit/utils/animators/__init__.py b/src/cbfkit/utils/animators/__init__.py index ced6aeb0..0a4f19e0 100644 --- a/src/cbfkit/utils/animators/__init__.py +++ b/src/cbfkit/utils/animators/__init__.py @@ -9,12 +9,13 @@ * ``"matplotlib"`` — generates MP4 / GIF animations via ``FuncAnimation``. * ``"plotly"`` (default) — generates interactive HTML files with play/pause controls and a timeline slider. Requires ``pip install cbfkit[plotly]``. -* ``"manim"`` — high-quality 3D animations (MP4) via Manim. Currently only - available for 3D multi-robot scenes via :func:`visualize_3d_multi_robot`. - Requires ``pip install cbfkit[manim]``. +* ``"manim"`` / ``"manim-"`` — high-quality MP4 / GIF animations via + Manim, for both :class:`CBFAnimator` 2D scenes and 3D multi-robot scenes + (:func:`visualize_3d_multi_robot`). Requires ``pip install cbfkit[manim]``. """ from .animator import CBFAnimator +from .manim_backend import CBFAnimator2DScene from .config import AnimationConfig, DEFAULT_CONFIG from .deps import ( _HAS_MANIM, @@ -29,6 +30,7 @@ __all__ = [ "AnimationConfig", "CBFAnimator", + "CBFAnimator2DScene", "DEFAULT_CONFIG", "save_animation", "_HAS_MATPLOTLIB", diff --git a/src/cbfkit/utils/animators/animator.py b/src/cbfkit/utils/animators/animator.py index 33001b91..36e44e90 100644 --- a/src/cbfkit/utils/animators/animator.py +++ b/src/cbfkit/utils/animators/animator.py @@ -6,11 +6,12 @@ from .config import AnimationConfig from .deps import _require_manim, _require_matplotlib, _require_plotly +from .manim_backend import _ManimMixin from .matplotlib_backend import _MatplotlibMixin from .plotly_backend import _PlotlyMixin -class CBFAnimator(_MatplotlibMixin, _PlotlyMixin): +class CBFAnimator(_MatplotlibMixin, _PlotlyMixin, _ManimMixin): """Declarative 2D trajectory animator for CBFKit simulations. Build an animation by chaining ``add_*`` calls, then :meth:`save` or @@ -31,7 +32,9 @@ class CBFAnimator(_MatplotlibMixin, _PlotlyMixin): aspect : str or None Axis aspect ratio (e.g. ``"equal"``). *None* keeps matplotlib default. backend : str - ``"plotly"`` (default) or ``"matplotlib"``. + ``"plotly"`` (default), ``"matplotlib"``, or ``"manim"`` / + ``"manim-"`` with quality one of ``low``, ``medium``, + ``high``, ``production`` (requires ``pip install cbfkit[manim]``). config : AnimationConfig, optional Animation parameters. Uses :data:`DEFAULT_CONFIG` when *None*. """ @@ -54,15 +57,16 @@ def __init__( ) self._backend = backend + self._manim_quality: Optional[str] = None if backend == "matplotlib": _require_matplotlib() elif backend.startswith("manim"): - # The 2D Manim backend is unimplemented whether or not Manim is - # installed, so surface that before requiring the optional dependency. - raise NotImplementedError( - "Manim 2D backend not yet implemented. " - "Use visualize_3d_multi_robot(backend='manim') for 3D scenes." - ) + # Validate the quality suffix before requiring the optional + # dependency so a typo'd backend string fails loudly either way. + from cbfkit.utils.visualization import _parse_manim_backend + + self._manim_quality = _parse_manim_backend(backend) + _require_manim() else: _require_plotly() @@ -396,23 +400,30 @@ def _compute_prediction(self, spec: dict, frame: int): def build(self): """Create the figure and all static / dynamic artists. - Returns ``(fig, ax)`` for the matplotlib backend, or the Plotly - ``Figure`` for the plotly backend. + Returns ``(fig, ax)`` for the matplotlib backend, the Plotly + ``Figure`` for the plotly backend, or the configured + :class:`~cbfkit.utils.animators.manim_backend.CBFAnimator2DScene` + class for the manim backend. """ if self._backend == "plotly": return self._build_plotly() + if self._backend.startswith("manim"): + return self._build_manim() return self._build_matplotlib() def animate(self): """Build (if needed) and create the animation object. - Returns a matplotlib ``FuncAnimation`` or a Plotly ``Figure`` - (which already contains the animation frames). + Returns a matplotlib ``FuncAnimation``, a Plotly ``Figure`` + (which already contains the animation frames), or the configured + Scene class for the manim backend (rendering happens in :meth:`save`). """ if self._backend == "plotly": if self._fig is None: self._build_plotly() return self._fig + if self._backend.startswith("manim"): + return self._build_manim() return self._animate_matplotlib() def save(self, path: str, config: Optional[AnimationConfig] = None) -> str: @@ -420,11 +431,15 @@ def save(self, path: str, config: Optional[AnimationConfig] = None) -> str: * matplotlib: saves MP4 (ffmpeg) or GIF (pillow fallback). * plotly: saves an interactive ``.html`` file. + * manim: renders MP4 (or GIF if *path* ends in ``.gif``) at the + quality encoded in the backend string (``manim-``). Returns the absolute path of the saved file. """ if self._backend == "plotly": return self._save_plotly(path) + if self._backend.startswith("manim"): + return self._save_manim(path) return self._save_matplotlib(path, config) def show(self): @@ -432,9 +447,12 @@ def show(self): * matplotlib: opens a matplotlib window. * plotly: opens the default web browser. + * manim: renders and opens the video in the default player. """ if self._backend == "plotly": return self._show_plotly() + if self._backend.startswith("manim"): + return self._show_manim() return self._show_matplotlib() # -- properties --------------------------------------------------------- diff --git a/src/cbfkit/utils/animators/manim_backend.py b/src/cbfkit/utils/animators/manim_backend.py new file mode 100644 index 00000000..fcf75eec --- /dev/null +++ b/src/cbfkit/utils/animators/manim_backend.py @@ -0,0 +1,460 @@ +"""Manim 2D backend mixin for CBFAnimator. + +Renders the declarative element API (goals, obstacles, trajectories, agents, +predictions, time overlay) as a high-quality Manim video. Mirrors the +architecture of :mod:`cbfkit.utils.visualizations.manim_3d_multi_robot`: +data is injected into a Scene subclass via class attributes, then rendered. + +Usage (standalone test with synthetic data): + manim -pql src/cbfkit/utils/animators/manim_backend.py CBFAnimator2DScene + +Usage (from library): + from cbfkit.utils.animator import CBFAnimator + CBFAnimator(states, backend="manim-medium").add_agent(0, 1).save("out.mp4") +""" + +from __future__ import annotations + +import os +import shutil + +import numpy as np + +from .deps import _require_manim + +try: + from manim import ( + UL, + UP, + Circle, + DashedLine, + DashedVMobject, + Dot, + Ellipse, + Line, + Scene, + Text, + TracedPath, # noqa: F401 (re-exported for parity with the 3D module) + ValueTracker, + VGroup, + config, + rate_functions, + ) + + _MANIM_AVAILABLE = True +except ImportError: + _MANIM_AVAILABLE = False + # Fallback base class so this module still imports when Manim is absent; + # actual rendering is gated by _require_manim() in _ManimMixin methods. + Scene = object # type: ignore[assignment,misc] + + +# --------------------------------------------------------------------------- +# Colour handling: translate matplotlib-style colour strings to hex +# --------------------------------------------------------------------------- +_TAB10_HEX = { + "tab:blue": "#1f77b4", + "tab:orange": "#ff7f0e", + "tab:green": "#2ca02c", + "tab:red": "#d62728", + "tab:purple": "#9467bd", + "tab:brown": "#8c564b", + "tab:pink": "#e377c2", + "tab:gray": "#7f7f7f", + "tab:olive": "#bcbd22", + "tab:cyan": "#17becf", +} + +_NAMED_HEX = { + "r": "#d62728", + "red": "#d62728", + "g": "#2ca02c", + "green": "#2ca02c", + "b": "#1f77b4", + "blue": "#1f77b4", + "k": "#000000", + "black": "#000000", + "w": "#ffffff", + "white": "#ffffff", + "y": "#bcbd22", + "yellow": "#bcbd22", + "c": "#17becf", + "cyan": "#17becf", + "m": "#e377c2", + "magenta": "#e377c2", + "orange": "#ff7f0e", + "purple": "#9467bd", + "brown": "#8c564b", + "pink": "#e377c2", + "gray": "#7f7f7f", + "grey": "#7f7f7f", +} + + +def _to_hex(color: str) -> str: + """Best-effort conversion of a matplotlib-style colour string to hex.""" + if not isinstance(color, str) or color.startswith("#"): + return color + if color in _TAB10_HEX: + return _TAB10_HEX[color] + return _NAMED_HEX.get(color.lower(), color) + + +# --------------------------------------------------------------------------- +# Core Manim Scene +# --------------------------------------------------------------------------- +class CBFAnimator2DScene(Scene): + """Render a 2D CBFAnimator element set as a Manim animation. + + Pass data via class attributes before calling ``scene.render()``, or let + the scene generate synthetic demo data when run standalone. + """ + + # --- class-level defaults (overridden by _ManimMixin._build_manim) ----- + states: np.ndarray | None = None + dt: float = 0.1 + x_lim: tuple[float, float] = (-4, 4) + y_lim: tuple[float, float] = (-4, 4) + title: str = "System Behavior" + aspect: str | None = None + goals: list[dict] = [] + obstacles: list[dict] = [] + trajectories: list[dict] = [] + agents: list[dict] = [] + predictions: list[dict] = [] + show_time_overlay: bool = False + # Bound CBFAnimator._compute_prediction (set by _build_manim) + compute_prediction = None + + # ----------------------------------------------------------------------- + def construct(self): + if self.states is None: + self._synthetic_demo() + + states = np.asarray(self.states) + n_frames = len(states) + + # White canvas so matplotlib-convention colours (black obstacles, + # dark trajectories) stay visible, matching the other backends. + self.camera.background_color = "#ffffff" + + axes, unit_x, unit_y = self._build_axes() + self.add(axes) + + if self.title: + self.add(Text(self.title, font_size=28, color="#000000").to_edge(UP, buff=0.2)) + + def c2p(x: float, y: float): + return axes.c2p(float(x), float(y)) + + # --- static goals --------------------------------------------------- + for g in self.goals: + pos, color = g["position"], _to_hex(g["color"]) + dot = Dot(c2p(pos[0], pos[1]), radius=0.06, color=color) + ring = Circle(radius=g["radius"] * unit_x, color=color, stroke_width=2) + ring.move_to(c2p(pos[0], pos[1])) + self.add(dot, DashedVMobject(ring, num_dashes=24)) + + # --- static obstacles ------------------------------------------------- + for obs in self.obstacles: + c, color = obs["center"], _to_hex(obs["color"]) + if obs["ellipse_radii"] is not None: + rx, ry = obs["ellipse_radii"] + mob = Ellipse(width=2 * rx * unit_x, height=2 * ry * unit_y, color=color) + elif obs["radius"] is not None: + mob = Circle(radius=obs["radius"] * unit_x, color=color) + else: + continue + mob.set_fill(color, opacity=obs["alpha"]).set_stroke(color, width=2) + mob.move_to(c2p(c[0], c[1])) + self.add(mob) + + # --- progress tracker ------------------------------------------------- + progress = ValueTracker(0) + + def current_frame() -> int: + return min(int(progress.get_value()), n_frames - 1) + + def _reveal_updater(segments_or_dots): + """Reveal pre-built per-frame mobjects up to the current frame.""" + + def updater(_mob): + frame = current_frame() + for k, seg in enumerate(segments_or_dots): + seg.set_opacity(seg.target_opacity if k < frame else 0.0) + + return updater + + def _build_reveal_group(xs, ys, color, *, style, stroke_width, opacity, dashed=False): + """Pre-build per-frame segments (line) or dots (scatter), initially hidden.""" + group = VGroup() + if style == "scatter": + for k in range(len(xs)): + d = Dot(c2p(xs[k], ys[k]), radius=0.03, color=color) + d.target_opacity = opacity + d.set_opacity(0) + group.add(d) + else: + line_cls = DashedLine if dashed else Line + for k in range(len(xs) - 1): + seg = line_cls( + c2p(xs[k], ys[k]), + c2p(xs[k + 1], ys[k + 1]), + stroke_width=stroke_width, + color=color, + ) + seg.target_opacity = opacity + seg.set_opacity(0) + group.add(seg) + group.add_updater(_reveal_updater(list(group))) + return group + + # --- animated trajectories -------------------------------------------- + for spec in self.trajectories: + data = spec["data"] if spec["data"] is not None else states + group = _build_reveal_group( + data[:, spec["x_idx"]], + data[:, spec["y_idx"]], + _to_hex(spec["color"]), + style=spec["style"], + stroke_width=2 * spec["linewidth"], + opacity=spec["alpha"], + ) + self.add(group) + + # --- agents (body + optional zone + optional trail) -------------------- + for spec in self.agents: + src = spec["data"] if spec["data"] is not None else states + x_idx, y_idx = spec["x_idx"], spec["y_idx"] + color = _to_hex(spec["body_color"]) + start = c2p(src[0, x_idx], src[0, y_idx]) + + body = Circle(radius=spec["body_radius"] * unit_x, color=color) + body.set_fill(color, opacity=spec["body_alpha"]).set_stroke(width=0) + body.move_to(start) + + def _make_follow(_src, _xi, _yi): + def updater(mob): + frame = current_frame() + mob.move_to(c2p(_src[frame, _xi], _src[frame, _yi])) + + return updater + + body.add_updater(_make_follow(src, x_idx, y_idx)) + self.add(body) + + if spec["zone_radius"] is not None: + zone_color = _to_hex(spec["zone_color"]) + zone = Circle(radius=spec["zone_radius"] * unit_x, color=zone_color) + zone.set_fill(zone_color, opacity=spec["zone_alpha"]).set_stroke(width=0) + zone.move_to(start) + zone.add_updater(_make_follow(src, x_idx, y_idx)) + self.add(zone) + + if spec["trail"]: + trail = _build_reveal_group( + src[:, x_idx], + src[:, y_idx], + _to_hex(spec["trail_color"]), + style="line", + stroke_width=3, + opacity=spec["trail_alpha"], + dashed=spec["trail_style"] == "--", + ) + self.add(trail) + + # --- per-frame predictions --------------------------------------------- + for spec in self.predictions: + self.add(self._prediction_group(spec, c2p, current_frame)) + + # --- time overlay -------------------------------------------------------- + if self.show_time_overlay: + time_text = Text("t = 0.0 s", font_size=20, color="#000000").to_corner(UL, buff=0.3) + + def _time_updater(mob): + mob.become( + Text( + f"t = {current_frame() * self.dt:.1f} s", font_size=20, color="#000000" + ).to_corner(UL, buff=0.3) + ) + + time_text.add_updater(_time_updater) + self.add(time_text) + + # --- play: real-time playback, clamped to a sane render length ----------- + playback_seconds = min(30.0, max(2.0, n_frames * self.dt)) + self.play( + progress.animate(run_time=playback_seconds, rate_func=rate_functions.linear).set_value( + n_frames - 1 + ) + ) + self.wait(0.5) + + # ----------------------------------------------------------------------- + def _build_axes(self): + """Create 2D axes fitting the frame; returns (axes, unit_x, unit_y). + + ``unit_x`` / ``unit_y`` convert data units to scene units so element + radii can be drawn to scale. With ``aspect="equal"`` both units match. + """ + from manim import Axes # local import keeps module importable sans manim + + x_span = float(self.x_lim[1] - self.x_lim[0]) + y_span = float(self.y_lim[1] - self.y_lim[0]) + max_w, max_h = 12.0, 6.0 + + if self.aspect == "equal": + unit = min(max_w / x_span, max_h / y_span) + x_len, y_len = x_span * unit, y_span * unit + else: + x_len, y_len = max_w, max_h + + axes = Axes( + x_range=[self.x_lim[0], self.x_lim[1], max(x_span / 8, 1e-6)], + y_range=[self.y_lim[0], self.y_lim[1], max(y_span / 8, 1e-6)], + x_length=x_len, + y_length=y_len, + tips=False, + axis_config={ + "include_ticks": True, + "tick_size": 0.04, + "stroke_width": 1.5, + "color": "#555555", + }, + ) + return axes, x_len / x_span, y_len / y_span + + # ----------------------------------------------------------------------- + def _prediction_group(self, spec: dict, c2p, current_frame): + """A VGroup redrawn every frame from the shared prediction computer.""" + color = _to_hex(spec["color"]) + group = VGroup() + + def updater(mob): + frame = current_frame() + px, py = self.compute_prediction(spec, frame) + segs = VGroup() + n = len(px) - 1 + for k in range(n): + seg = Line( + c2p(px[k], py[k]), + c2p(px[k + 1], py[k + 1]), + stroke_width=2 * spec["linewidth"], + color=color, + ) + alpha = spec["alpha"] * (1.0 - k / n) if spec["fade"] else spec["alpha"] + seg.set_opacity(alpha) + segs.add(seg) + mob.become(segs) if n >= 1 else mob.become(VGroup()) + + group.add_updater(updater) + return group + + # ----------------------------------------------------------------------- + def _synthetic_demo(self): + """Simple goal-reaching demo so the scene renders standalone.""" + n = 100 + t = np.linspace(0, 1, n) + states = np.stack([-3 + 6 * t, 1.5 * np.sin(2 * np.pi * t) * (1 - t)], axis=1) + type(self).states = states + type(self).goals = [{"position": (3.0, 0.0), "radius": 0.3, "color": "g", "label": "Goal"}] + type(self).obstacles = [ + {"center": (0.0, 0.5), "radius": 0.5, "ellipse_radii": None, "color": "k", "alpha": 0.3} + ] + type(self).agents = [ + { + "x_idx": 0, + "y_idx": 1, + "data": None, + "body_radius": 0.2, + "body_color": "blue", + "body_alpha": 0.8, + "zone_radius": None, + "zone_color": "blue", + "zone_alpha": 0.15, + "trail": True, + "trail_color": "blue", + "trail_alpha": 0.5, + "trail_style": "-", + "label": "Agent", + "zorder": 5, + } + ] + type(self).show_time_overlay = True + + +# --------------------------------------------------------------------------- +# Mixin for CBFAnimator +# --------------------------------------------------------------------------- +class _ManimMixin: + """Manim-specific build / save / show methods. + + Mixed into :class:`~cbfkit.utils.animators.animator.CBFAnimator`. + Expects the host class to provide the element descriptor lists, limits, + ``_dt``, ``_title``, ``_aspect``, ``_manim_quality``, and + ``_compute_prediction``. + """ + + def _build_manim(self): + """Inject animator state into :class:`CBFAnimator2DScene`; return it.""" + _require_manim() + CBFAnimator2DScene.states = np.asarray(self._states) + CBFAnimator2DScene.dt = self._dt + CBFAnimator2DScene.x_lim = self._x_lim + CBFAnimator2DScene.y_lim = self._y_lim + CBFAnimator2DScene.title = self._title + CBFAnimator2DScene.aspect = self._aspect + CBFAnimator2DScene.goals = self._goals + CBFAnimator2DScene.obstacles = self._obstacles + CBFAnimator2DScene.trajectories = self._trajectories + CBFAnimator2DScene.agents = self._agents + CBFAnimator2DScene.predictions = self._predictions + CBFAnimator2DScene.show_time_overlay = self._show_time + CBFAnimator2DScene.compute_prediction = self._compute_prediction + return CBFAnimator2DScene + + def _save_manim(self, path: str) -> str: + """Render to *path* (``.mp4``, or ``.gif`` via Manim's gif format).""" + import tempfile + + scene_cls = self._build_manim() + + config.quality = self._manim_quality + if path.lower().endswith(".gif"): + config.format = "gif" + # Render intermediates in a temp dir so the caller's cwd stays clean; + # the finished video is copied to *path* below. + with tempfile.TemporaryDirectory(prefix="cbfkit_manim_") as tmp_media: + config.media_dir = tmp_media + scene = scene_cls() + scene.render() + # For MP4 the file writer reports the exact output path; for GIF + # it still reports the .mp4 name, so fall back to globbing the + # media dir for the rendered file with the requested extension. + rendered = str(scene.renderer.file_writer.movie_file_path) + if not os.path.exists(rendered): + import glob + + ext = os.path.splitext(path)[1] or ".mp4" + candidates = glob.glob( + os.path.join(tmp_media, "videos", "**", f"*{ext}"), recursive=True + ) + if not candidates: + raise FileNotFoundError( + f"Manim did not produce a {ext} file under {tmp_media!r}." + ) + rendered = candidates[0] + + out_dir = os.path.dirname(path) + if out_dir: + os.makedirs(out_dir, exist_ok=True) + shutil.copy2(rendered, path) + return os.path.abspath(path) + + def _show_manim(self): + """Render and open the result in the default player (Manim preview).""" + scene_cls = self._build_manim() + config.quality = self._manim_quality + config.preview = True + scene = scene_cls() + scene.render() From ec471775837e092a319789860bc879259d3b9299 Mon Sep 17 00:00:00 2001 From: Eduard-Zippenfenig Date: Sat, 18 Jul 2026 18:08:54 +0300 Subject: [PATCH 2/4] test(animators): cover Manim 2D backend construction, dispatch, and render 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 --- tests/test_utils/test_animator.py | 79 +++++++++++++++++++++++++++++-- 1 file changed, 74 insertions(+), 5 deletions(-) diff --git a/tests/test_utils/test_animator.py b/tests/test_utils/test_animator.py index 994851db..736338b0 100644 --- a/tests/test_utils/test_animator.py +++ b/tests/test_utils/test_animator.py @@ -78,7 +78,9 @@ def test_build_creates_figure(self, simple_states): plt.close(fig) def test_build_sets_limits_and_labels(self, simple_states): - a = CBFAnimator(simple_states, x_lim=(-5, 5), y_lim=(-3, 3), title="Test", backend="matplotlib") + a = CBFAnimator( + simple_states, x_lim=(-5, 5), y_lim=(-3, 3), title="Test", backend="matplotlib" + ) fig, ax = a.build() assert ax.get_xlim() == (-5, 5) assert ax.get_ylim() == (-3, 3) @@ -259,7 +261,9 @@ def test_save_creates_file(self, simple_states, tmp_path): def test_save_gif_fallback(self, simple_states, tmp_path): """Force GIF fallback by using a config and verifying some file is created.""" - a = CBFAnimator(simple_states, dt=0.1, config=AnimationConfig(fps=5, dpi=50), backend="matplotlib") + a = CBFAnimator( + simple_states, dt=0.1, config=AnimationConfig(fps=5, dpi=50), backend="matplotlib" + ) a.add_trajectory(x_idx=0, y_idx=1) path = str(tmp_path / "test_anim.mp4") result = a.save(path) @@ -423,7 +427,72 @@ def test_require_manim_raises_when_missing(self, monkeypatch): class TestManimBackend: - def test_manim_backend_accepted_but_not_implemented(self, simple_states): - """backend='manim' is a valid value but raises NotImplementedError for 2D.""" - with pytest.raises(NotImplementedError, match="Manim 2D backend not yet implemented"): + """2D Manim backend: construction, dispatch, and guards. + + Dependency-free tests monkeypatch ``_HAS_MANIM`` / the render path so + they run in CI (where the ``manim`` extra is not installed); the real + render smoke test skips without manim. + """ + + def test_manim_backend_raises_import_error_when_missing(self, simple_states, monkeypatch): + from cbfkit.utils.animators import deps + + monkeypatch.setattr(deps, "_HAS_MANIM", False) + with pytest.raises(ImportError, match=r"cbfkit\[manim\]"): CBFAnimator(simple_states, backend="manim") + + def test_manim_invalid_quality_raises_value_error(self, simple_states): + # Quality is validated before the optional dependency is required, + # so this fails loudly whether or not manim is installed. + with pytest.raises(ValueError, match="Unknown Manim backend"): + CBFAnimator(simple_states, backend="manim-ultra") + + @pytest.mark.parametrize( + "backend,quality", + [ + ("manim", "low_quality"), + ("manim-low", "low_quality"), + ("manim-medium", "medium_quality"), + ("manim-high", "high_quality"), + ("manim-production", "production_quality"), + ], + ) + def test_manim_quality_parsing(self, simple_states, backend, quality, monkeypatch): + from cbfkit.utils.animators import deps + + monkeypatch.setattr(deps, "_HAS_MANIM", True) + a = CBFAnimator(simple_states, backend=backend) + assert a._manim_quality == quality + + def test_manim_save_dispatches_to_backend(self, simple_states, monkeypatch, tmp_path): + from cbfkit.utils.animators import deps + + monkeypatch.setattr(deps, "_HAS_MANIM", True) + a = CBFAnimator(simple_states, backend="manim-medium") + + recorded = {} + + def fake_save(path): + recorded["path"] = path + recorded["quality"] = a._manim_quality + return path + + monkeypatch.setattr(a, "_save_manim", fake_save) + out = a.save(str(tmp_path / "anim.mp4")) + assert recorded["path"] == out + assert recorded["quality"] == "medium_quality" + + def test_manim_real_render_smoke(self, simple_states, tmp_path): + from cbfkit.utils.animators.deps import _HAS_MANIM + + if not _HAS_MANIM: + pytest.skip("manim not installed") + + a = CBFAnimator(simple_states[:10], dt=0.1, backend="manim", title="Test", aspect="equal") + a.add_goal((1.0, 0.0), radius=0.2, color="g") + a.add_obstacle((0.0, 0.5), radius=0.3, color="k") + a.add_agent(x_idx=0, y_idx=1, body_radius=0.1, body_color="blue") + a.show_time() + out = a.save(str(tmp_path / "manim_smoke.mp4")) + assert os.path.exists(out) + assert os.path.getsize(out) > 0 From 69536704a3edf356c9f6aca7c752d590c32208d1 Mon Sep 17 00:00:00 2001 From: Eduard-Zippenfenig Date: Sat, 18 Jul 2026 18:09:21 +0300 Subject: [PATCH 3/4] docs: mention 2D CBFAnimator support in Manim showcase; add demo GIF Demo renders the README quick-start CBF example (unicycle reach-avoid) via backend='manim-medium'. Co-Authored-By: Claude Fable 5 --- README.md | 2 +- media/showcase/manim_2d_animator.gif | Bin 0 -> 153160 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 media/showcase/manim_2d_animator.gif diff --git a/README.md b/README.md index f54cc743..a630a009 100755 --- a/README.md +++ b/README.md @@ -142,7 +142,7 @@ python benchmarks/qp_solver_comparison.py #### Multi-robot 3D coordination -Cinematic 3D simulation rendering with Manim. Multi-robot reach-avoid in 3D, rendered via CBFKit's Manim backend. Shows the visualization stack scales from quick matplotlib plots to publication-quality 3D animations. +Cinematic 3D simulation rendering with Manim. Multi-robot reach-avoid in 3D, rendered via CBFKit's Manim backend. Shows the visualization stack scales from quick matplotlib plots to publication-quality 3D animations. The same backend also renders 2D `CBFAnimator` scenes: pass `backend="manim"` (or `"manim-"`) to any `CBFAnimator` and `save("out.mp4")` produces a publication-quality video (`.gif` also supported).

Manim 3D render of multi-robot reach-avoid

diff --git a/media/showcase/manim_2d_animator.gif b/media/showcase/manim_2d_animator.gif new file mode 100644 index 0000000000000000000000000000000000000000..e476bbfc84181c5c962632795261a7eb9c2e7abb GIT binary patch literal 153160 zcmb^2cQ9P<9|!ugUEkGfSRsfYM31(53DJWjSVW2FLA0=|tq4I7k_amZAw^4&2&>oV z5s_f^-h18a`@47UU-$lTXYOlu=gjQv?94gOd3HX}^Eq!_eH{fwhdyvRXrBU{1A#yg z2m}g+!eB6JYHB(vUa!^49{qXo~oqzZ<;5)u*;78bs8<%-x# zF^NY-l9H0w!ahrfmrB2tl9iQ}!<8v0C|nOAUXNE%ivFUktgL#jNL52aL-R?Ej*gDr zvwHoQ68)4${pXDa=Tch?&jlwM#h2Yotv5C{Hc6^7ebHcMW@c$=X=P<)9rM;E$>#R$ z+jk3`Z0+o9Gi~kd?Xg&_qobo!;`@8|?m3@}&vH(5c1f>tJ@@dw>)QwS)7}5`FvR`w zQ}^Ug9_La%dU|?#olE)Pm0IGJ)9Cfd*W26MH?zVw%je<4hk=2CK|w(wFF%Fk)rAzc zgr55n8h);@5tmc>!RU0q$nxzW|e#>S@qv^1@>Ha9o7G&i-JYwc|5Y;GBEYdv?+*52OHIknx@ z)z#hI-Ft4fr+2Hjudi?5+~VQT`1H`^(D3l@;nm;gMvh1Ste=ej&%c%3v9YmFLS&`}FkeLZJW@ zfS*EpQ`^E^Q{6=SnuH_-bUq*W51~*5zz2x^H#z>BCIA%$Sf}REDQ{wmjvKcK_F2me-bq){N)TFVF8t6)`Wq+fiQ7l_BZWnWbOx?&oWThfDuD zDhhjZFi|uY3@VHI^Yl_hZ96O94;Gr_8N4z0Qv9pLy25>|^GnI^&)BB;3pc)g_*3CF zSZv$%^|DIH`uLB;eF@$lp}y0L1rHvfC%%R5j*IK{dQH~R&>lA_Sv!&29??$>hJMaz z3-_6>yO3$))l`*ul_vb4$M#=s+*P(cLn%9*?$`7Ko;SYB4F;W_ERO&DUbWVk{&+uJ zH`#7&9PGrNG@mrKme?3GF-q*wPFn97quk^P@c{cMSBxf$oCn@<3^)+!d zoaFYX%;FU;T(papge#?9sC=VQ!&%YB7t!3%n&46C+o2?LZxX5a5{HkHoB6|PuYFpZ z7ta$>>(=eKNsP?rp%N0bljBDXBt2lSfpl?78k+NKOi4*268tKq=A_T)E~oh3&Mu2p zvsSGX;D2g!@2-OELi?X*r3nr2tR0xBh|yW?G*XNn?V}@7oG=LhDhC2I5FicH&Lx9J zX-NH4X@UTV5BzyNb}YoEjF?yyUbr;v8%}Y?lj7>eBXckf16aNd_kQ{wZ{@0a8n%r9 z0Oc}W9^ZTiJZK(8R99Yz24lc8VzIn#SFZ2$$mpYP=*fXwq>UJXQiq?P%MY390PV2* zj7k;fx7I6PEOHxR#g^Dc#sQFaVj@7&Zb+FAPHg|0N-6fNf#cSSo$*D;0{B>(y21@41bn z=l;JgW%#a$p>%T_M&xa5)1`zvrB5^EZoA}4J?e#m@@rpT7f2RJ%ot4HDE1nS9zZIE zq|e=S@@v@+tZrs)&blU(*On#4YDJ;?edis0V20FWSn#U{Mg6wx_gfEBqX9b|w(7BO z+9+Jcw?u&Sy4pr4(>a^nh37LtHXES*`Em~R@cdL;8;u{p9q&hexBaZv4Jc;*J1cSm zkUU9uUq9F(PCvVsI@a=PgZs&TVr}DH>rursmdJ0@z)U}nFB5M|ilcW?Uj=cP$QwLS+tp=|m>4IcJ^d{2Z`5V-Ko z0soA~S>$vKrZ}i?K0Kr?9Z1!~*J^@Sy!9|^3@fAn^6&Kmiqwy4V&!}IGcr8Bazp)e zyv8(>AG)wbvYO2sjOv?HQzo^NAR-AWG!6`sm{I^{@!*=e41e5))l0f}i@e+4Gv7zH zDSwPJG_Vp&bLer(@d!w{7<(N8+>Yf&{b=Ed#@?)&>&vp$27nt|gsaVMa`PNcvEVXe zHh=`8NC|TUhqdx;Z6K^C9q9qRMq!J7#fWyWerdTjmLyKq6M=Cl;gqlcy{V7T{#nV

~(KA5Zx##w!qw!0MJA{ZAcm;#p*(+FXQ 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

    ^3GuTpSN*`@O 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 Date: Sat, 18 Jul 2026 18:47:36 +0300 Subject: [PATCH 4/4] fix(animators): isolate Manim config per render; add reproducible example 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 --- .../unicycle/reach_goal/manim_2d_animation.py | 116 ++++++++++++++++++ src/cbfkit/utils/animators/manim_backend.py | 29 +++-- tests/test_utils/test_animator.py | 14 +++ 3 files changed, 148 insertions(+), 11 deletions(-) create mode 100644 examples/unicycle/reach_goal/manim_2d_animation.py diff --git a/examples/unicycle/reach_goal/manim_2d_animation.py b/examples/unicycle/reach_goal/manim_2d_animation.py new file mode 100644 index 00000000..3fb11092 --- /dev/null +++ b/examples/unicycle/reach_goal/manim_2d_animation.py @@ -0,0 +1,116 @@ +"""Tutorial: render a CBF-filtered unicycle reach-avoid run with the Manim 2D backend. + +Runs the README quick-start simulation (unicycle drives to a goal while a CBF +safety filter keeps it clear of an obstacle), then renders the trajectory with +``CBFAnimator(backend="manim-medium")``. This is the script that produced +``media/showcase/manim_2d_animator.gif``. + +Requires the ``manim`` extra (``pip install cbfkit[manim]``) plus ffmpeg, and +on macOS the cairo/pango libraries (``brew install ffmpeg cairo pango``). +""" + +import os + +import jax.numpy as jnp +from jax import jit + +from cbfkit.certificates import concatenate_certificates, rectify_relative_degree +from cbfkit.certificates.barrier_functions import ellipsoidal_barrier_factory +from cbfkit.certificates.conditions.barrier_conditions.zeroing_barriers import linear_class_k +from cbfkit.controllers.cbf_clf import vanilla_cbf_clf_qp_controller +from cbfkit.estimators import naive +from cbfkit.integration import runge_kutta_4 +from cbfkit.sensors import perfect +from cbfkit.simulation import simulator +from cbfkit.systems.unicycle.models.olfatisaber2002approximate.dynamics import ( + approx_unicycle_dynamics, +) +from cbfkit.utils.animators.deps import _HAS_MANIM + +# Simulation Parameters +initial_state = jnp.array([0.0, 0.0, 0.0]) +actuation_limits = jnp.array([5.0, jnp.pi]) +goal = jnp.array([4.0, 0.0]) +obstacle_center = jnp.array([2.0, 0.5, 0.0]) +obstacle_radii = jnp.array([0.5, 0.5]) +dt = 1e-2 +num_steps = 500 if not os.getenv("CBFKIT_TEST_MODE") else 50 + +# Dynamics +dynamics = approx_unicycle_dynamics(lam=1.0) # state: [x, y, theta] + + +# Nominal controller - drives toward the goal, ignorant of the obstacle +@jit +def nominal_controller(t, state, key, data): + x, y, th = state + heading = jnp.arctan2(goal[1] - y, goal[0] - x) + return ( + jnp.array( + [ + jnp.linalg.norm(jnp.array([x - goal[0], y - goal[1]])), # speed + jnp.arctan2(jnp.sin(heading - th), jnp.cos(heading - th)), # steering + ] + ), + {}, + ) + + +# CBF barrier around the obstacle +cbf_factory, _, _ = ellipsoidal_barrier_factory( + system_position_indices=(0, 1), + obstacle_position_indices=(0, 1), + ellipsoid_axis_indices=(0, 1), +) +barrier = rectify_relative_degree( + function=cbf_factory(obstacle_center, obstacle_radii), + system_dynamics=dynamics, + state_dim=3, + form="exponential", +)(certificate_conditions=linear_class_k(10.0)) + +# Safety-filtered controller +controller = vanilla_cbf_clf_qp_controller( + control_limits=actuation_limits, + dynamics_func=dynamics, + barriers=concatenate_certificates(barrier), +) + +results = simulator.execute( + x0=initial_state, + dt=dt, + num_steps=num_steps, + dynamics=dynamics, + integrator=runge_kutta_4, + nominal_controller=nominal_controller, + controller=controller, + sensor=perfect, + estimator=naive, +) +print(f"Final position: ({results.states[-1, 0]:.2f}, {results.states[-1, 1]:.2f})") + +# Render with the Manim 2D backend (skipped in test mode / without manim) +if os.getenv("CBFKIT_TEST_MODE"): + print("CBFKIT_TEST_MODE set: skipping Manim render.") +elif not _HAS_MANIM: + print("Manim not found. Rendering disabled. Install 'cbfkit[manim]' to enable it.") +else: + import numpy as np + + from cbfkit.utils.animator import CBFAnimator + + anim = CBFAnimator( + np.asarray(results.states), + dt=dt, + backend="manim-medium", # or "manim" / "manim-low" for quicker renders + title="CBF Safety Filter: Unicycle Reach-Avoid", + aspect="equal", + x_lim=(-0.7, 4.7), + y_lim=(-1.2, 1.8), + ) + anim.add_goal(np.asarray(goal), radius=0.25, color="g", label="Goal") + anim.add_obstacle(np.asarray(obstacle_center[:2]), radius=float(obstacle_radii[0]), alpha=0.3) + anim.add_agent(x_idx=0, y_idx=1, body_radius=0.12, body_color="tab:blue", trail=True) + anim.show_time() + out = anim.save("manim_2d_unicycle_reach_avoid.mp4") + print(f"Animation saved to {out}") diff --git a/src/cbfkit/utils/animators/manim_backend.py b/src/cbfkit/utils/animators/manim_backend.py index fcf75eec..c3e57727 100644 --- a/src/cbfkit/utils/animators/manim_backend.py +++ b/src/cbfkit/utils/animators/manim_backend.py @@ -37,8 +37,8 @@ TracedPath, # noqa: F401 (re-exported for parity with the 3D module) ValueTracker, VGroup, - config, rate_functions, + tempconfig, ) _MANIM_AVAILABLE = True @@ -419,15 +419,23 @@ def _save_manim(self, path: str) -> str: scene_cls = self._build_manim() - config.quality = self._manim_quality + overrides = { + "quality": self._manim_quality, + # Caching keys partial movie files by animation hash; with a fresh + # media dir per render, stale in-process cache entries would point + # at deleted files and crash a second render, so disable it. + "disable_caching": True, + } if path.lower().endswith(".gif"): - config.format = "gif" + overrides["format"] = "gif" # Render intermediates in a temp dir so the caller's cwd stays clean; - # the finished video is copied to *path* below. + # the finished video is copied to *path* below. tempconfig scopes the + # global Manim config so repeated renders in one process stay isolated. with tempfile.TemporaryDirectory(prefix="cbfkit_manim_") as tmp_media: - config.media_dir = tmp_media - scene = scene_cls() - scene.render() + overrides["media_dir"] = tmp_media + with tempconfig(overrides): + scene = scene_cls() + scene.render() # For MP4 the file writer reports the exact output path; for GIF # it still reports the .mp4 name, so fall back to globbing the # media dir for the rendered file with the requested extension. @@ -454,7 +462,6 @@ def _save_manim(self, path: str) -> str: def _show_manim(self): """Render and open the result in the default player (Manim preview).""" scene_cls = self._build_manim() - config.quality = self._manim_quality - config.preview = True - scene = scene_cls() - scene.render() + with tempconfig({"quality": self._manim_quality, "preview": True, "disable_caching": True}): + scene = scene_cls() + scene.render() diff --git a/tests/test_utils/test_animator.py b/tests/test_utils/test_animator.py index 736338b0..031abe83 100644 --- a/tests/test_utils/test_animator.py +++ b/tests/test_utils/test_animator.py @@ -496,3 +496,17 @@ def test_manim_real_render_smoke(self, simple_states, tmp_path): out = a.save(str(tmp_path / "manim_smoke.mp4")) assert os.path.exists(out) assert os.path.getsize(out) > 0 + + def test_manim_repeated_renders_same_process(self, simple_states, tmp_path): + """Regression: a second render in the same process must not reuse + cached partial-movie files from the first render's deleted temp dir.""" + from cbfkit.utils.animators.deps import _HAS_MANIM + + if not _HAS_MANIM: + pytest.skip("manim not installed") + + for i in range(2): + a = CBFAnimator(simple_states[:8], dt=0.1, backend="manim") + a.add_agent(x_idx=0, y_idx=1, body_radius=0.1) + out = a.save(str(tmp_path / f"repeat_{i}.mp4")) + assert os.path.getsize(out) > 0