mirror of
https://github.com/ManimCommunity/manim.git
synced 2026-06-22 10:01:47 +00:00
Implement Mobject.always (#4594)
This commit is contained in:
parent
ab17eb58a3
commit
601a007192
2 changed files with 66 additions and 1 deletions
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue