Implement Mobject.always (#4594)

This commit is contained in:
Aarush Deshpande 2026-02-17 19:47:58 -05:00 committed by GitHub
commit 601a007192
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 66 additions and 1 deletions

View file

@ -394,6 +394,42 @@ class Mobject:
"""
return _AnimationBuilder(self)
@property
def always(self) -> Self:
"""Call a method on a mobject every frame.
This is syntactic sugar for ``mob.add_updater(lambda m: m.method(*args, **kwargs), call_updater=True)``.
Note that this will call the method immediately. If this behavior is not
desired, you should use :meth:`add_updater` directly.
.. warning::
Chaining of methods is allowed, but each method will be added
as its own updater. If you are chaining methods, make sure they
do not interfere with each other or you may get unexpected results.
.. warning::
:attr:`always` is not compatible with :meth:`.ValueTracker.get_value`, because
the value will be computed once and then never updated again. Use :meth:`add_updater`
if you would like to use a :class:`~.ValueTracker` to update the value.
Example
-------
.. manim:: AlwaysExample
class AlwaysExample(Scene):
def construct(self):
sq = Square().to_edge(LEFT)
t = Text("Hello World!")
t.always.next_to(sq, UP)
self.add(sq, t)
self.play(sq.animate.to_edge(RIGHT))
"""
# can't use typing.cast because Self is under TYPE_CHECKING
return _UpdaterBuilder(self) # type: ignore[return-value]
def __deepcopy__(self, clone_from_id) -> Self:
cls = self.__class__
result = cls.__new__(cls)
@ -3339,6 +3375,24 @@ class _AnimationBuilder:
return anim
class _UpdaterBuilder:
"""Syntactic sugar for adding updaters to mobjects."""
def __init__(self, mobject: Mobject):
self._mobject = mobject
def __getattr__(self, name: str, /) -> Callable[..., Self]:
# just return a function that will add the updater
def add_updater(*method_args, **method_kwargs) -> Self:
self._mobject.add_updater(
lambda m: getattr(m, name)(*method_args, **method_kwargs),
call_updater=True,
)
return self
return add_updater
def override_animate(method) -> types.FunctionType:
r"""Decorator for overriding method animations.

View file

@ -1,6 +1,6 @@
from __future__ import annotations
from manim import Circle, FadeIn
from manim import UP, Circle, Dot, FadeIn
from manim.animation.updaters.mobject_update_utils import turn_animation_into_updater
@ -54,3 +54,14 @@ def test_turn_animation_into_updater_positive_run_time_persists():
# The updater should still be present (not finished)
assert len(mobject.updaters) == len(original_updaters) + 1
assert updater in mobject.updaters
def test_always():
d = Dot()
circ = Circle()
d.always.next_to(circ, UP)
assert len(d.updaters) == 1
# we should be able to chain updaters
d2 = Dot()
d.always.next_to(d2, UP).next_to(circ, UP)
assert len(d.updaters) == 3