Source code for manim.animation.updaters.mobject_update_utils

"""Utility functions for continuous animation of mobjects."""

from __future__ import annotations

__all__ = [
    "assert_is_mobject_method",
    "always",
    "f_always",
    "always_redraw",
    "always_shift",
    "always_rotate",
    "turn_animation_into_updater",
    "cycle_animation",
]


import inspect
from typing import TYPE_CHECKING, Callable

import numpy as np

from manim.constants import DEGREES, RIGHT
from manim.mobject.mobject import Mobject
from manim.opengl import OpenGLMobject
from manim.utils.space_ops import normalize

if TYPE_CHECKING:
    from manim.animation.animation import Animation


[docs]def assert_is_mobject_method(method: Callable) -> None: assert inspect.ismethod(method) mobject = method.__self__ assert isinstance(mobject, (Mobject, OpenGLMobject))
[docs]def always(method: Callable, *args, **kwargs) -> Mobject: assert_is_mobject_method(method) mobject = method.__self__ func = method.__func__ mobject.add_updater(lambda m: func(m, *args, **kwargs)) return mobject
[docs]def f_always(method: Callable[[Mobject], None], *arg_generators, **kwargs) -> Mobject: """ More functional version of always, where instead of taking in args, it takes in functions which output the relevant arguments. """ assert_is_mobject_method(method) mobject = method.__self__ func = method.__func__ def updater(mob): args = [arg_generator() for arg_generator in arg_generators] func(mob, *args, **kwargs) mobject.add_updater(updater) return mobject
[docs]def always_redraw(func: Callable[[], Mobject]) -> Mobject: """Redraw the mobject constructed by a function every frame. This function returns a mobject with an attached updater that continuously regenerates the mobject according to the specified function. Parameters ---------- func A function without (required) input arguments that returns a mobject. Examples -------- .. manim:: TangentAnimation class TangentAnimation(Scene): def construct(self): ax = Axes() sine = ax.plot(np.sin, color=RED) alpha = ValueTracker(0) point = always_redraw( lambda: Dot( sine.point_from_proportion(alpha.get_value()), color=BLUE ) ) tangent = always_redraw( lambda: TangentLine( sine, alpha=alpha.get_value(), color=YELLOW, length=4 ) ) self.add(ax, sine, point, tangent) self.play(alpha.animate.set_value(1), rate_func=linear, run_time=2) """ mob = func() mob.add_updater(lambda _: mob.become(func())) return mob
[docs]def always_shift( mobject: Mobject, direction: np.ndarray[np.float64] = RIGHT, rate: float = 0.1 ) -> Mobject: """A mobject which is continuously shifted along some direction at a certain rate. Parameters ---------- mobject The mobject to shift. direction The direction to shift. The vector is normalized, the specified magnitude is not relevant. rate Length in Manim units which the mobject travels in one second along the specified direction. Examples -------- .. manim:: ShiftingSquare class ShiftingSquare(Scene): def construct(self): sq = Square().set_fill(opacity=1) tri = Triangle() VGroup(sq, tri).arrange(LEFT) # construct a square which is continuously # shifted to the right always_shift(sq, RIGHT, rate=5) self.add(sq) self.play(tri.animate.set_fill(opacity=1)) """ mobject.add_updater(lambda m, dt: m.shift(dt * rate * normalize(direction))) return mobject
[docs]def always_rotate(mobject: Mobject, rate: float = 20 * DEGREES, **kwargs) -> Mobject: """A mobject which is continuously rotated at a certain rate. Parameters ---------- mobject The mobject to be rotated. rate The angle which the mobject is rotated by over one second. kwags Further arguments to be passed to :meth:`.Mobject.rotate`. Examples -------- .. manim:: SpinningTriangle class SpinningTriangle(Scene): def construct(self): tri = Triangle().set_fill(opacity=1).set_z_index(2) sq = Square().to_edge(LEFT) # will keep spinning while there is an animation going on always_rotate(tri, rate=2*PI, about_point=ORIGIN) self.add(tri, sq) self.play(sq.animate.to_edge(RIGHT), rate_func=linear, run_time=1) """ mobject.add_updater(lambda m, dt: m.rotate(dt * rate, **kwargs)) return mobject
[docs]def turn_animation_into_updater( animation: Animation, cycle: bool = False, **kwargs ) -> Mobject: """ Add an updater to the animation's mobject which applies the interpolation and update functions of the animation If cycle is True, this repeats over and over. Otherwise, the updater will be popped upon completion Examples -------- .. manim:: WelcomeToManim class WelcomeToManim(Scene): def construct(self): words = Text("Welcome to") banner = ManimBanner().scale(0.5) VGroup(words, banner).arrange(DOWN) turn_animation_into_updater(Write(words, run_time=0.9)) self.add(words) self.wait(0.5) self.play(banner.expand(), run_time=0.5) """ mobject = animation.mobject animation.suspend_mobject_updating = False animation.begin() animation.total_time = 0 def update(m: Mobject, dt: float): run_time = animation.get_run_time() time_ratio = animation.total_time / run_time if cycle: alpha = time_ratio % 1 else: alpha = np.clip(time_ratio, 0, 1) if alpha >= 1: animation.finish() m.remove_updater(update) return animation.interpolate(alpha) animation.update_mobjects(dt) animation.total_time += dt mobject.add_updater(update) return mobject
[docs]def cycle_animation(animation: Animation, **kwargs) -> Mobject: return turn_animation_into_updater(animation, cycle=True, **kwargs)