From 704e344d1a61a937346d8310cb76832ff14e2dee Mon Sep 17 00:00:00 2001 From: Taksh Date: Sun, 5 Jul 2026 21:14:38 +0530 Subject: [PATCH 1/2] fix(animation): pass mobjects as single args in LaggedStartMap Default arg_creator returned bare mobjects that were unpacked via Mobject.__iter__, so Restore(*mob) could build zero subanimations. Wrap non-tuple values and error clearly on empty targets. Fixes #4861 Co-authored-by: Cursor --- manim/animation/composition.py | 20 +++++++++++++++++++- tests/module/animation/test_composition.py | 17 +++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/manim/animation/composition.py b/manim/animation/composition.py index 82488425f8..a21f3d3bd7 100644 --- a/manim/animation/composition.py +++ b/manim/animation/composition.py @@ -413,9 +413,27 @@ def identity(mob: Mobject) -> Mobject: arg_creator = identity + def animation_args(value: Any) -> tuple[Any, ...]: + # ``arg_creator`` may return either a tuple of constructor args (e.g. + # ``(m.set_color, YELLOW)``) or a single mobject (the default identity). + # Only tuples are unpacked; mobjects must be passed as a single argument + # so animations like ``Restore(mob)`` are not called as ``Restore(*mob)``. + if isinstance(value, tuple): + return value + return (value,) + args_list = [arg_creator(submob) for submob in mobject] + if not args_list: + raise ValueError( + f"LaggedStartMap cannot build animations from {mobject!r}: " + "it has no submobjects to map over. Pass a VGroup of the " + "targets to animate, or provide a non-empty mobject." + ) anim_kwargs = dict(kwargs) if "lag_ratio" in anim_kwargs: anim_kwargs.pop("lag_ratio") - animations = [animation_class(*args, **anim_kwargs) for args in args_list] + animations = [ + animation_class(*animation_args(args), **anim_kwargs) + for args in args_list + ] super().__init__(*animations, run_time=run_time, lag_ratio=lag_ratio) diff --git a/tests/module/animation/test_composition.py b/tests/module/animation/test_composition.py index 3c7a3dcc3e..0f0f180b24 100644 --- a/tests/module/animation/test_composition.py +++ b/tests/module/animation/test_composition.py @@ -6,6 +6,7 @@ from manim.animation.animation import Animation, Wait from manim.animation.composition import AnimationGroup, LaggedStartMap, Succession +from manim.animation.transform import Restore from manim.animation.creation import Create, Write from manim.animation.fading import FadeIn, FadeOut from manim.constants import DOWN, UP @@ -191,6 +192,22 @@ def finish(self): assert circ_animation.finished +def test_laggedstartmap_restore_builds_subanimations(): + mobject = VGroup(Square(), Square().shift(UP)) + for submob in mobject: + submob.save_state() + submob.scale(0) + animation = LaggedStartMap(Restore, mobject, lag_ratio=0.1, run_time=1) + assert len(animation.animations) == len(mobject) + assert all(isinstance(subanimation, Restore) for subanimation in animation.animations) + + +def test_laggedstartmap_empty_mobject_raises(): + mobject = VGroup() + with pytest.raises(ValueError, match="no submobjects"): + LaggedStartMap(Restore, mobject) + + def test_laggedstartmap_only_passes_kwargs_to_subanimations(): mobject = VGroup(Square(), Circle()) animation = LaggedStartMap( From 8f6b53f57dea8a6e3ee55d163bfa8b08caeea56d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 5 Jul 2026 15:51:21 +0000 Subject: [PATCH 2/2] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- manim/animation/composition.py | 3 +-- tests/module/animation/test_composition.py | 6 ++++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/manim/animation/composition.py b/manim/animation/composition.py index a21f3d3bd7..3895af5723 100644 --- a/manim/animation/composition.py +++ b/manim/animation/composition.py @@ -433,7 +433,6 @@ def animation_args(value: Any) -> tuple[Any, ...]: if "lag_ratio" in anim_kwargs: anim_kwargs.pop("lag_ratio") animations = [ - animation_class(*animation_args(args), **anim_kwargs) - for args in args_list + animation_class(*animation_args(args), **anim_kwargs) for args in args_list ] super().__init__(*animations, run_time=run_time, lag_ratio=lag_ratio) diff --git a/tests/module/animation/test_composition.py b/tests/module/animation/test_composition.py index 0f0f180b24..b14726a9b9 100644 --- a/tests/module/animation/test_composition.py +++ b/tests/module/animation/test_composition.py @@ -6,9 +6,9 @@ from manim.animation.animation import Animation, Wait from manim.animation.composition import AnimationGroup, LaggedStartMap, Succession -from manim.animation.transform import Restore from manim.animation.creation import Create, Write from manim.animation.fading import FadeIn, FadeOut +from manim.animation.transform import Restore from manim.constants import DOWN, UP from manim.mobject.geometry.arc import Circle from manim.mobject.geometry.line import Line @@ -199,7 +199,9 @@ def test_laggedstartmap_restore_builds_subanimations(): submob.scale(0) animation = LaggedStartMap(Restore, mobject, lag_ratio=0.1, run_time=1) assert len(animation.animations) == len(mobject) - assert all(isinstance(subanimation, Restore) for subanimation in animation.animations) + assert all( + isinstance(subanimation, Restore) for subanimation in animation.animations + ) def test_laggedstartmap_empty_mobject_raises():