diff --git a/manim/animation/composition.py b/manim/animation/composition.py index 82488425f8..3895af5723 100644 --- a/manim/animation/composition.py +++ b/manim/animation/composition.py @@ -413,9 +413,26 @@ 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..b14726a9b9 100644 --- a/tests/module/animation/test_composition.py +++ b/tests/module/animation/test_composition.py @@ -8,6 +8,7 @@ from manim.animation.composition import AnimationGroup, LaggedStartMap, Succession 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 @@ -191,6 +192,24 @@ 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(