跳转至

更新器

影片代码
import manimgx as m


class UpdatersHero(m.Scene):
    def construct(self) -> None:
        dot = m.Dot(color=m.YELLOW).shift(3 * m.LEFT)
        label = m.Text("dot", font_size=36)
        label.add_updater(lambda mob: mob.next_to(dot, m.UP))
        trail = m.TracedPath(dot.get_center, stroke_color=m.YELLOW, stroke_width=4)
        self.add(trail, dot, label)
        self.play(dot.animate.shift(6 * m.RIGHT), run_time=2)
        self.play(m.Rotate(dot, m.PI, about_point=m.ORIGIN), run_time=2)
        self.wait()

动画在其运行时间内改变一个 mobject。更新器在每一帧改变它,只要它被附加就持续:在其他东西移动时它保持一条规则。标签停留在一个点上方,数字显示跟踪器在哪里,形状随时间流逝而转动。

更新器是一个函数:ManimGX 在每一帧用 mobject 调用它,在 play 和 wait 中同样如此。一个还接受 dt 的函数得到自它上次运行以来的时间,以秒计,用于持续的运动:lambda mob, dt: mob.rotate(dt) 每秒将 mobject 转动一弧度。

添加更新器

add_updater

代码
import manimgx as m


class MobjectAddUpdaterExample(m.Scene):
    def construct(self) -> None:
        hand = m.Line(m.ORIGIN, 3 * m.RIGHT, color=m.BLUE)
        # time-based: a quarter turn about the origin every second
        hand.add_updater(
            lambda mob, dt: mob.rotate(dt * m.PI / 2, about_point=m.ORIGIN)
        )
        dot = m.Dot(radius=0.2, color=m.YELLOW)
        # per frame: at the hand's end, wherever it is
        dot.add_updater(lambda mob: mob.move_to(hand.get_end()))
        self.add(hand, dot)
        self.wait(3)

添加更新器:一个随时间推移保持 mobject 更新的函数。

更新器是关于 mobject 的函数,每帧运行一次——以保持某种关系, 如标签在点旁边——或者,若其有一个名为 dt 的参数,则是关于 mobject 和 dt 的函数:dt 为自上次运行以来、自添加以来、mobject 加入场景以来 或其更新恢复以来的场景时间秒数。基于时间的更新器按经过的时间移动 mobject: lambda mob, dt: mob.rotate(dt * PI) 以每秒半圈的速度旋转它,与帧率无关。 更新器的返回值被忽略。

场景在每一帧运行其中各 mobject 的更新器,在正在播放的动画将它们移动到该帧时刻之后, 以及在每次 play 和 wait 的结束时运行:按 mobject 加入场景的顺序逐个处理, mobject 自身的更新器在其子 mobject 的之前运行,而 recorder 在所有其余的之后运行。因此,要让一个 mobject 跟随另一个,应在它之后添加,否则它会看到另一个的更新器在前一时刻将其留在何处。 更新器在动画播放 mobject 时继续运行,在动画之下(参见 animation options 中的 suspend_mobject_updating)。基于时间的更新器在场景的模拟时钟上步进, config.simulation_rate 每秒滴答一次,因此它在任何帧率下都采取相同的步长; 将一个无论时间如何划分都做相同之事的更新器声明为 flow, 它便会在每一帧精确运行。

mobject.add_updater(update_function, index=None, call_updater=False)
update_function

更新器:关于 mobject 的函数,或关于 mobject 和 dt 的函数。

index

它在 mobject 的更新器中的位置,更新器按顺序运行;None 表示最后。

call_updater

是否立即运行一次;基于时间的更新器收到的 dt 为 0。

源码

manimgx\mobject.py

def add_updater(
    self,
    update_function: Updater[Self],
    index: int | None = None,
    call_updater: bool = False,
) -> Self:
    """添加更新器:一个随时间推移保持 mobject 更新的函数。

    更新器是关于 mobject 的函数,每帧运行一次——以保持某种关系,
    如标签在点旁边——或者,若其有一个名为 `dt` 的参数,则是关于
    mobject 和 `dt` 的函数:`dt` 为自上次运行以来、自添加以来、mobject 加入场景以来
    或其更新恢复以来的场景时间秒数。基于时间的更新器按经过的时间移动 mobject:
    `lambda mob, dt: mob.rotate(dt * PI)` 以每秒半圈的速度旋转它,与帧率无关。
    更新器的返回值被忽略。

    场景在每一帧运行其中各 mobject 的更新器,在正在播放的动画将它们移动到该帧时刻之后,
    以及在每次 play 和 wait 的结束时运行:按 mobject 加入场景的顺序逐个处理,
    mobject 自身的更新器在其子 mobject 的之前运行,而
    [recorder][manimgx.mobject.record] 在所有其余的之后运行。因此,要让一个 mobject
    跟随另一个,应在它之后添加,否则它会看到另一个的更新器在前一时刻将其留在何处。
    更新器在动画播放 mobject 时继续运行,在动画之下(参见
    [animation options][manimgx.animation.timeline.AnimationOptions] 中的
    `suspend_mobject_updating`)。基于时间的更新器在场景的模拟时钟上步进,
    `config.simulation_rate` 每秒滴答一次,因此它在任何帧率下都采取相同的步长;
    将一个无论时间如何划分都做相同之事的更新器声明为 [flow][manimgx.mobject.flow],
    它便会在每一帧精确运行。

    Args:
        update_function: 更新器:关于 mobject 的函数,或关于 mobject
            和 `dt` 的函数。
        index: 它在 mobject 的更新器中的位置,更新器按顺序运行;None
            表示最后。
        call_updater: 是否立即运行一次;基于时间的更新器收到的
            `dt` 为 0。

    Examples:
        ```python
        import manimgx as m


        class MobjectAddUpdaterExample(m.Scene):
            def construct(self) -> None:
                hand = m.Line(m.ORIGIN, 3 * m.RIGHT, color=m.BLUE)
                # time-based: a quarter turn about the origin every second
                hand.add_updater(
                    lambda mob, dt: mob.rotate(dt * m.PI / 2, about_point=m.ORIGIN)
                )
                dot = m.Dot(radius=0.2, color=m.YELLOW)
                # per frame: at the hand's end, wherever it is
                dot.add_updater(lambda mob: mob.move_to(hand.get_end()))
                self.add(hand, dot)
                self.wait(3)
        ```
    """
    if index is None:
        self.updaters.append(update_function)
    else:
        self.updaters.insert(index, update_function)
    if not _per_frame(update_function):
        self._since[update_function] = clock.now  # it integrates from now
    if call_updater:
        if _per_frame(update_function):
            update_function(self)
        else:
            cast("Callable[[Self, float], object]", update_function)(self, 0)
    return self

remove_updater

从 mobject 中移除一个更新器,每次添加的都被移除。

这还会取消当前更新中尚未运行的调用, 包括等待其他更新器完成的记录器。

mobject.remove_updater(update_function)
update_function

要移除的更新器。

源码

manimgx\mobject.py

def remove_updater(self, update_function: Updater[Never]) -> Self:
    """从 mobject 中移除一个更新器,每次添加的都被移除。

    这还会取消当前更新中尚未运行的调用,
    包括等待其他更新器完成的记录器。

    Args:
        update_function: 要移除的更新器。
    """
    self.updaters = [u for u in self.updaters if u is not update_function]
    since = self.__dict__.get("_since_")
    if since is not None and update_function not in self.updaters:
        since.pop(update_function, None)
    return self

clear_updaters

移除 mobject 的所有更新器,包括仍在当前更新中等待运行的调用。

mobject.clear_updaters(recursive=True)
recursive

其整个家族的更新器是否也被移除。

源码

manimgx\mobject.py

def clear_updaters(self, recursive: bool = True) -> Self:
    """移除 mobject 的所有更新器,包括仍在当前更新中等待运行的调用。

    Args:
        recursive: 其整个家族的更新器是否也被移除。
    """
    self.updaters = []
    self.__dict__.pop("_since_", None)
    if recursive:
        for sub in self.submobjects:
            sub.clear_updaters()
    return self

get_updaters

mobject 自身的更新器,按运行顺序排列。

通过 add_updater、 remove_updater 和 clear_updaters 来更改它们,这些方法会保持 其时钟。

mobject.get_updaters()

返回 它们的一个快照。

源码

manimgx\mobject.py

def get_updaters(self) -> tuple[Updater[Self], ...]:
    """mobject 自身的更新器,按运行顺序排列。

    通过 [add_updater][manimgx.Mobject.add_updater]、
    [remove_updater][manimgx.Mobject.remove_updater] 和
    [clear_updaters][manimgx.Mobject.clear_updaters] 来更改它们,这些方法会保持
    其时钟。

    Returns:
        它们的一个快照。
    """
    return tuple(self.updaters)

suspend_updating

停止 mobject 的更新器运行,以及其下方的家族的更新器,直到 resume_updating:通过被暂停的 mobject 更新的任何东西都不运行。

动画在播放时暂停其所播放 mobject 的更新,并 改为运行其下方的更新器(参见 add_updater)。

mobject.suspend_updating(recursive=True)
recursive

其家族的每个成员是否也被暂停,以使其在 mobject 之外独立更新的地方保持停止(场景也单独持有的成员), 以及当 mobject 单独恢复时。

源码

manimgx\mobject.py

def suspend_updating(self, recursive: bool = True) -> Self:
    """停止 mobject 的更新器运行,以及其下方的家族的更新器,直到
    [resume_updating][manimgx.Mobject.resume_updating]:通过被暂停的 mobject
    更新的任何东西都不运行。

    动画在播放时暂停其所播放 mobject 的更新,并
    改为运行其下方的更新器(参见
    [add_updater][manimgx.Mobject.add_updater])。

    Args:
        recursive: 其家族的每个成员是否也被暂停,以使其在
            mobject 之外独立更新的地方保持停止(场景也单独持有的成员),
            以及当 mobject 单独恢复时。
    """
    self.updating_suspended = True
    if recursive:
        for sub in self.submobjects:
            sub.suspend_updating(recursive)
    return self

resume_updating

让 mobject 的更新器再次运行,从现在开始:被暂停的 时间不予补偿。

它们不在此处运行:场景在它下次将世界推进到的地方运行它们——在 它正在计算的时刻,当动画在结束时恢复它们时, 否则在下一个时刻,如下一帧。

mobject.resume_updating(recursive=True)
recursive

其整个家族的更新器是否也恢复:单独恢复时, mobject 再次运行,以及其下方未被自身暂停的成员。

源码

manimgx\mobject.py

def resume_updating(self, recursive: bool = True) -> Self:
    """让 mobject 的更新器再次运行,从现在开始:被暂停的
    时间不予补偿。

    它们不在此处运行:场景在它下次将世界推进到的地方运行它们——在
    它正在计算的时刻,当动画在结束时恢复它们时,
    否则在下一个时刻,如下一帧。

    Args:
        recursive: 其整个家族的更新器是否也恢复:单独恢复时,
            mobject 再次运行,以及其下方未被自身暂停的成员。
    """
    # not run here as well: the instant's own pass runs them, and a per-frame
    # updater would run twice at one instant
    self.updating_suspended = False
    if recursive:
        for sub in self.submobjects:
            sub.resume_updating(recursive)
    self._stamp(clock.now, recursive)
    return self

update

立即运行该 mobject 的每个更新器一次;当其更新被挂起时,不运行任何内容。

场景本身会在每一帧运行更新器:此方法则是在当下手动运行它们。

mobject.update(dt=0, recursive=True)
dt

传递给其基于时间的更新器的秒数。

recursive

其子 mobject 的更新器是否也随之一并运行。

源码

manimgx\mobject.py

def update(self, dt: float = 0, recursive: bool = True) -> Self:
    """立即运行该 mobject 的每个更新器一次;当其更新被挂起时,不运行任何内容。

    场景本身会在每一帧运行更新器:此方法则是在当下手动运行它们。

    Args:
        dt: 传递给其基于时间的更新器的秒数。
        recursive: 其子 mobject 的更新器是否也随之一并运行。
    """
    # CE's; the scene uses `advance`
    if self.updating_suspended:
        return self
    updaters = self.updaters
    for updater in updaters:
        if updaters is not self.updaters and not _has_updater(
            self.updaters, updater
        ):
            continue
        if _per_frame(updater):
            updater(self)
        else:
            cast("Callable[[Self, float], object]", updater)(self, dt)
    if recursive:
        for sub in self.submobjects:
            sub.update(dt, recursive)
    return self

always

代码
import manimgx as m


class MobjectAlwaysExample(m.Scene):
    def construct(self) -> None:
        square = m.Square(color=m.BLUE, fill_opacity=0.5).shift(4 * m.LEFT)
        label = m.Text("always above")
        label.always.next_to(square, m.UP)
        self.add(square, label)
        self.play(square.animate.shift(8 * m.RIGHT), run_time=2)

一个代理,将对其进行的每个方法调用转化为更新器:该调用 每帧再次执行。

label.always.next_to(dot, UP) 使标签保持在点上方,无论点移到哪里。 每个调用也会立即执行,并添加一个每帧更新器(参见 add_updater);代理返回自身,因此调用可以链式书写。 参数按所写形式只取一次:传入要跟随的 mobject(dot), 而非其位置(dot.get_center()),后者会停留在原处。

mobject.always
源码

manimgx\mobject.py

def always(self) -> "Always[Self]":
    """一个代理,将对其进行的每个方法调用转化为更新器:该调用
    每帧再次执行。

    `label.always.next_to(dot, UP)` 使标签保持在点上方,无论点移到哪里。
    每个调用也会立即执行,并添加一个每帧更新器(参见
    [add_updater][manimgx.Mobject.add_updater]);代理返回自身,因此调用可以链式书写。
    参数按所写形式只取一次:传入要跟随的 mobject(`dot`),
    而非其位置(`dot.get_center()`),后者会停留在原处。

    Examples:
        ```python
        import manimgx as m


        class MobjectAlwaysExample(m.Scene):
            def construct(self) -> None:
                square = m.Square(color=m.BLUE, fill_opacity=0.5).shift(4 * m.LEFT)
                label = m.Text("always above")
                label.always.next_to(square, m.UP)
                self.add(square, label)
                self.play(square.animate.shift(8 * m.RIGHT), run_time=2)
        ```
    """
    # CE's
    from manimgx.animation.transform import Always

    return Always(self)

基于时间的更新器如何运行

ManimGX 在仿真时钟的每个滴答运行基于时间的更新器,无论帧率如何每秒 60 次,以使仿真在每个帧率下给出相同结果。流,无论其时间如何分割都做相同的事,改为在每一帧运行;记录器,读取世界,在所有其他东西之后运行。

flow

代码
import manimgx as m
from manimgx.mobject import flow


@flow
def orbit(dot: m.Mobject, dt: float) -> None:
    dot.rotate(dt * m.PI / 2, about_point=m.ORIGIN)  # a quarter turn a second


class FlowExample(m.Scene):
    def construct(self) -> None:
        dot = m.Dot(3 * m.RIGHT, radius=0.25, color=m.YELLOW)
        dot.add_updater(orbit)
        self.add(m.Circle(radius=3, color=m.BLUE), dot)
        self.wait(3)

将一个基于时间的更新器声明为流:即在一段时间内无论该时间如何划分为步长, 其所做之事都相同的更新器。

基于时间的更新器会收到 dt,即自上次运行以来经过的秒数。大多数更新器是可组合的: 以某个速率移动 mobject 的更新器(mob.shift(dt * velocity)),或按其已运行时间的 函数来设定它的更新器,在一步 dt 中所做之事与在两步 dt / 2 中所做之事相同。流在 场景计算的每个时刻运行——每一帧,以及每次 play 和 wait 的结束——并收到确切的经过 时间,因此每一帧都显示其在该帧时刻的状态。

任何其他基于时间的更新器都是被模拟的,因为其所做之事可能依赖于其步长(积分器、 弹跳的球):它在自己的时钟上步进,config.simulation_rate 每秒滴答一次(60), 以及每次 play 和 wait 的结束,因此它在任何帧率下都采取相同的步长,而一帧显示的是 其最后一次滴答留下的状态。当一个模拟更新器在场景中时,场景会计算每一个滴答, 包括帧之间的滴答。将一个可组合的更新器声明为流,可使它在每一帧都精确,并省去 那些滴答;always_shift 和 always_rotate 添加的是流。一个只读取世界状态的更新器,如追踪路径的更新器,是一个 recorder。

manimgx.mobject.flow(updater)
updater

一个基于时间的更新器:关于 mobject 和 dt 的函数。

返回 同一个更新器,被声明为流,可用 add_updater 添加。

源码

manimgx\mobject.py

def flow[U: Callable[..., object]](updater: U) -> U:
    """将一个基于时间的更新器声明为流:即在一段时间内无论该时间如何划分为步长,
    其所做之事都相同的更新器。

    基于时间的更新器会收到 `dt`,即自上次运行以来经过的秒数。大多数更新器是可组合的:
    以某个速率移动 mobject 的更新器(`mob.shift(dt * velocity)`),或按其已运行时间的
    函数来设定它的更新器,在一步 `dt` 中所做之事与在两步 `dt / 2` 中所做之事相同。流在
    场景计算的每个时刻运行——每一帧,以及每次 play 和 wait 的结束——并收到确切的经过
    时间,因此每一帧都显示其在该帧时刻的状态。

    任何其他基于时间的更新器都是被模拟的,因为其所做之事可能依赖于其步长(积分器、
    弹跳的球):它在自己的时钟上步进,`config.simulation_rate` 每秒滴答一次(60),
    以及每次 play 和 wait 的结束,因此它在任何帧率下都采取相同的步长,而一帧显示的是
    其最后一次滴答留下的状态。当一个模拟更新器在场景中时,场景会计算每一个滴答,
    包括帧之间的滴答。将一个可组合的更新器声明为流,可使它在每一帧都精确,并省去
    那些滴答;[always_shift][manimgx.always_shift] 和 [always_rotate][manimgx.always_rotate]
    添加的是流。一个只读取世界状态的更新器,如追踪路径的更新器,是一个
    [recorder][manimgx.mobject.record]。

    Args:
        updater: 一个基于时间的更新器:关于 mobject 和 `dt` 的函数。

    Returns:
        同一个更新器,被声明为流,可用
        [add_updater][manimgx.Mobject.add_updater] 添加。

    Examples:
        ```python
        import manimgx as m
        from manimgx.mobject import flow


        @flow
        def orbit(dot: m.Mobject, dt: float) -> None:
            dot.rotate(dt * m.PI / 2, about_point=m.ORIGIN)  # a quarter turn a second


        class FlowExample(m.Scene):
            def construct(self) -> None:
                dot = m.Dot(3 * m.RIGHT, radius=0.25, color=m.YELLOW)
                dot.add_updater(orbit)
                self.add(m.Circle(radius=3, color=m.BLUE), dot)
                self.wait(3)
        ```
    """
    _FLOWS.add(_function(updater))
    return updater

record

将一个基于时间的更新器声明为记录器:即读取世界状态且仅改变自身 mobject 的更新器, 如 TracedPath 的更新器。

场景在它计算的每个时刻运行记录器——模拟时钟的每个滴答、每一帧,以及每次 play 和 wait 的结束——在所有其他东西就位之后:正在播放的动画、mobject 的更新器和场景的更新器。 在一帧或一次 play 或 wait 的结束时,它运行两次:首先在仅由时间带到那里的世界上运行, 如一个滴答所看到的那样,然后在每帧更新器运行之后再运行一次,以便它能区分它们的 变化(追踪路径将其分散到结束于该处的帧上)。记录器是被模拟的(参见 flow):当记录器在场景中时,场景会计算模拟时钟的滴答。

manimgx.mobject.record(updater)
updater

一个基于时间的更新器:关于 mobject 和 dt 的函数,或此类方法, 在其类定义处装饰。

返回 同一个更新器,被声明为记录器,可用 add_updater 添加。

源码

manimgx\mobject.py

def record[U: Callable[..., object]](updater: U) -> U:
    """将一个基于时间的更新器声明为记录器:即读取世界状态且仅改变自身 mobject 的更新器,
    如 [TracedPath][manimgx.TracedPath] 的更新器。

    场景在它计算的每个时刻运行记录器——模拟时钟的每个滴答、每一帧,以及每次 play 和
    wait 的结束——在所有其他东西就位之后:正在播放的动画、mobject 的更新器和场景的更新器。
    在一帧或一次 play 或 wait 的结束时,它运行两次:首先在仅由时间带到那里的世界上运行,
    如一个滴答所看到的那样,然后在每帧更新器运行之后再运行一次,以便它能区分它们的
    变化(追踪路径将其分散到结束于该处的帧上)。记录器是被模拟的(参见
    [flow][manimgx.mobject.flow]):当记录器在场景中时,场景会计算模拟时钟的滴答。

    Args:
        updater: 一个基于时间的更新器:关于 mobject 和 `dt` 的函数,或此类方法,
            在其类定义处装饰。

    Returns:
        同一个更新器,被声明为记录器,可用
        [add_updater][manimgx.Mobject.add_updater] 添加。
    """
    # the scene computes a frame or an event in two passes (`Scene._instant`) only while
    # a recorder is in it; the second pass is `clock.framing`
    _RECORDERS.add(_function(updater))
    return updater

现成更新器

always_redraw

代码
import manimgx as m


class AlwaysRedrawExample(m.Scene):
    def construct(self) -> None:
        width = m.ValueTracker(2)
        box = m.always_redraw(
            lambda: m.Rectangle(width=width.get_value(), height=3, color=m.BLUE)
        )
        brace = m.always_redraw(lambda: m.Brace(box, m.DOWN, color=m.YELLOW))
        self.add(box, brace)
        self.play(width.animate.set_value(11), run_time=2)
        self.play(width.animate.set_value(5))

创建一个每帧由函数重新构建的 mobject。

func 现在构建该 mobject,并且每一帧该 mobject 都变为 func 当时所构建的对象(参见 become):它在场景中始终是同一个对象,并跟随 func 所读取的 任何内容——值追踪器、其他 mobject。适用于形状以无运动跟随的方式依赖于其他对象的情况:贴合 不断增长的形状的花括号、两个移动点之间的连线、曲线下方的面积。

m.always_redraw(func)
func

一个无参函数,用于构建该 mobject。

返回 该 mobject,附带其更新器:将其添加到场景中。

源码

manimgx\animation\updaters.py

def always_redraw[M: Mobject](func: Callable[[], M]) -> M:
    """创建一个每帧由函数重新构建的 mobject。

    `func` 现在构建该 mobject,并且每一帧该 mobject 都变为 `func` 当时所构建的对象(参见
    [`become`][manimgx.Mobject.become]):它在场景中始终是同一个对象,并跟随 `func` 所读取的
    任何内容——值追踪器、其他 mobject。适用于形状以无运动跟随的方式依赖于其他对象的情况:贴合
    不断增长的形状的花括号、两个移动点之间的连线、曲线下方的面积。

    Args:
        func: 一个无参函数,用于构建该 mobject。

    Returns:
        该 mobject,附带其更新器:将其添加到场景中。

    Examples:
        ```python
        import manimgx as m


        class AlwaysRedrawExample(m.Scene):
            def construct(self) -> None:
                width = m.ValueTracker(2)
                box = m.always_redraw(
                    lambda: m.Rectangle(width=width.get_value(), height=3, color=m.BLUE)
                )
                brace = m.always_redraw(lambda: m.Brace(box, m.DOWN, color=m.YELLOW))
                self.add(box, brace)
                self.play(width.animate.set_value(11), run_time=2)
                self.play(width.animate.set_value(5))
        ```
    """
    mob = func()
    mob.add_updater(lambda m: m.become(func()))
    return mob

always_rotate

代码
import manimgx as m


class AlwaysRotateExample(m.Scene):
    def construct(self) -> None:
        square = m.Square(side_length=3, color=m.BLUE, fill_opacity=0.5)
        square.shift(3.5 * m.LEFT)
        m.always_rotate(square, rate=m.PI / 2)  # a quarter turn a second
        center = m.Dot(3.5 * m.RIGHT, color=m.YELLOW)
        moon = m.Dot(3.5 * m.RIGHT + 2 * m.UP, radius=0.25, color=m.TEAL)
        m.always_rotate(moon, rate=-m.PI, about_point=center.get_center())
        self.add(square, center, moon)
        self.wait(4)

使 mobject 以恒定速度持续旋转。

它添加一个基于时间的更新器,即 flow,因此在每一帧该 mobject 都恰好 处于其速度所带到的角度,无论在播放还是等待期间皆然。

m.always_rotate(mobject, rate=20 * DEGREES, axis=OUT, **kwargs)
mobject

要旋转的 mobject。

rate

速度,单位为弧度每秒;正值表示逆时针旋转。

axis

旋转所绕的轴。

about_point

保持不动的点,以场景坐标表示;它优先于 about_edge。

about_edge

mobject 边界框上保持不动的点,以方向命名: UP 表示其顶边的中点,UR 表示其右上角,ORIGIN 表示其中心。

返回 该 mobject,附带其更新器。

源码

manimgx\animation\updaters.py

def always_rotate[M: Mobject](
    mobject: M,
    rate: float = 20 * DEGREES,
    axis: Vector3DLike = OUT,
    **kwargs: Unpack[Pivot],
) -> M:
    """使 mobject 以恒定速度持续旋转。

    它添加一个基于时间的更新器,即 [flow][manimgx.mobject.flow],因此在每一帧该 mobject 都恰好
    处于其速度所带到的角度,无论在播放还是等待期间皆然。

    Args:
        mobject: 要旋转的 mobject。
        rate: 速度,单位为弧度每秒;正值表示逆时针旋转。
        axis: 旋转所绕的轴。
        **kwargs: [Pivot keywords][manimgx.mobject.Pivot]:旋转所绕的点,
            未指定时为其中心。

    Returns:
        该 mobject,附带其更新器。

    Examples:
        ```python
        import manimgx as m


        class AlwaysRotateExample(m.Scene):
            def construct(self) -> None:
                square = m.Square(side_length=3, color=m.BLUE, fill_opacity=0.5)
                square.shift(3.5 * m.LEFT)
                m.always_rotate(square, rate=m.PI / 2)  # a quarter turn a second
                center = m.Dot(3.5 * m.RIGHT, color=m.YELLOW)
                moon = m.Dot(3.5 * m.RIGHT + 2 * m.UP, radius=0.25, color=m.TEAL)
                m.always_rotate(moon, rate=-m.PI, about_point=center.get_center())
                self.add(square, center, moon)
                self.wait(4)
        ```
    """
    mobject.add_updater(flow(lambda m, dt: m.rotate(dt * rate, axis, **kwargs)))
    return mobject

always_shift

代码
import manimgx as m


class AlwaysShiftExample(m.Scene):
    def construct(self) -> None:
        square = m.Square(side_length=2, color=m.BLUE, fill_opacity=0.8)
        square.shift(5 * m.LEFT)
        m.always_shift(square, m.RIGHT, rate=2.5)
        self.add(square)
        self.play(square.animate.set_color(m.YELLOW), run_time=2)
        self.wait(2)

使 mobject 沿某方向以恒定速度持续移动。

它添加一个基于时间的更新器,即 flow,因此在每一帧该 mobject 都恰好 位于其速度所带到的位置,无论在播放还是等待期间皆然。

m.always_shift(mobject, direction=RIGHT, rate=0.1)
mobject

要移动的 mobject。

direction

移动的方向;仅方向起作用,长度不起作用。

rate

速度,单位为场景单位每秒。

返回 该 mobject,附带其更新器。

源码

manimgx\animation\updaters.py

def always_shift[M: Mobject](
    mobject: M, direction: Vector3DLike = RIGHT, rate: float = 0.1
) -> M:
    """使 mobject 沿某方向以恒定速度持续移动。

    它添加一个基于时间的更新器,即 [flow][manimgx.mobject.flow],因此在每一帧该 mobject 都恰好
    位于其速度所带到的位置,无论在播放还是等待期间皆然。

    Args:
        mobject: 要移动的 mobject。
        direction: 移动的方向;仅方向起作用,长度不起作用。
        rate: 速度,单位为场景单位每秒。

    Returns:
        该 mobject,附带其更新器。

    Examples:
        ```python
        import manimgx as m


        class AlwaysShiftExample(m.Scene):
            def construct(self) -> None:
                square = m.Square(side_length=2, color=m.BLUE, fill_opacity=0.8)
                square.shift(5 * m.LEFT)
                m.always_shift(square, m.RIGHT, rate=2.5)
                self.add(square)
                self.play(square.animate.set_color(m.YELLOW), run_time=2)
                self.wait(2)
        ```
    """
    unit = normalize(direction)
    mobject.add_updater(flow(lambda m, dt: m.shift(dt * rate * unit)))
    return mobject

turn_animation_into_updater

代码
import manimgx as m


class TurnAnimationIntoUpdaterExample(m.Scene):
    def construct(self) -> None:
        shape = m.Square(side_length=2, color=m.BLUE, fill_opacity=0.5)
        squares = m.VGroup(*(shape.copy() for _ in range(3))).arrange(buff=1.5)
        self.add(squares)
        for i, square in enumerate(squares):  # half a second apart
            m.turn_animation_into_updater(
                m.Rotate(square, m.PI / 2, run_time=1), delay=0.5 * i
            )
        self.wait(2.5)

从 mobject 的更新器播放动画,按 mobject 自身的时间运行,而非在 play 中播放。

动画立即开始:它取该 mobject 当前的样子,并显示其起始状态。随后其时间随场景时间一同推进, 从现在起或从该 mobject 加入场景时起;经过 delay 秒后开始播放,按其运行时间和速率函数运行。 当动画结束时,它完成并移除其更新器;若使用 cycle,则改为从头开始,周而复始。不会向场景添加 或从中移除任何内容:请自行添加该 mobject。动画及其时间属于该 mobject:其副本播放它们的副本, 作用于自身。

m.turn_animation_into_updater(animation, cycle=False, delay=0)
cycle

是否永远重复。

delay

开始前等待的时间,单位为秒。

返回 动画的 mobject,附带其更新器。

源码

manimgx\animation\updaters.py

def turn_animation_into_updater(
    animation: Animation, cycle: bool = False, delay: float = 0
) -> Mobject:
    """从 mobject 的更新器播放动画,按 mobject 自身的时间运行,而非在 play 中播放。

    动画立即开始:它取该 mobject 当前的样子,并显示其起始状态。随后其时间随场景时间一同推进,
    从现在起或从该 mobject 加入场景时起;经过 `delay` 秒后开始播放,按其运行时间和速率函数运行。
    当动画结束时,它完成并移除其更新器;若使用 `cycle`,则改为从头开始,周而复始。不会向场景添加
    或从中移除任何内容:请自行添加该 mobject。动画及其时间属于该 mobject:其副本播放它们的副本,
    作用于自身。

    Args:
        cycle: 是否永远重复。
        delay: 开始前等待的时间,单位为秒。

    Returns:
        动画的 mobject,附带其更新器。

    Examples:
        ```python
        import manimgx as m


        class TurnAnimationIntoUpdaterExample(m.Scene):
            def construct(self) -> None:
                shape = m.Square(side_length=2, color=m.BLUE, fill_opacity=0.5)
                squares = m.VGroup(*(shape.copy() for _ in range(3))).arrange(buff=1.5)
                self.add(squares)
                for i, square in enumerate(squares):  # half a second apart
                    m.turn_animation_into_updater(
                        m.Rotate(square, m.PI / 2, run_time=1), delay=0.5 * i
                    )
                self.wait(2.5)
        ```
    """
    mobject = animation.mobject
    animation.suspend_mobject_updating = False
    animation.begin()
    animation.interpolate(
        0
    )  # the object is the animation's from now: at its start, until then
    # the animation and its time are kept on the mobject, so that a copy of it plays a copy
    # of them: the updater holds only their key, and plays those of the mobject it runs on
    key = next(_PLAYED_KEYS)
    played: dict[int, _Played] = mobject.__dict__.setdefault("_played", {})
    played[key] = _Played(animation, -delay)

    def update(m: Mobject, dt: float) -> None:
        state = m.__dict__.get("_played", {}).get(key)
        if state is None:  # nothing of its own to play
            return
        state.elapsed += dt  # its time now: what this frame shows
        if state.elapsed < 0:
            return
        animation, elapsed = state.animation, state.elapsed
        run_time = animation.get_run_time()
        if run_time > 0 and (cycle or elapsed < run_time):
            animation.interpolate(
                (elapsed / run_time) % 1 if cycle else elapsed / run_time
            )
            animation.advance(clock.now)
        else:
            animation.finish()
            m.remove_updater(update)
            del m.__dict__["_played"][key]

    mobject.add_updater(flow(update))  # the animation at the time it has run
    return mobject

cycle_animation

代码
import manimgx as m


class CycleAnimationExample(m.Scene):
    def construct(self) -> None:
        orbit = m.Circle(radius=3, color=m.BLUE)
        planet = m.Dot(orbit.get_start(), radius=0.25, color=m.YELLOW)
        m.cycle_animation(
            m.MoveAlongPath(planet, orbit, rate_func=m.linear, run_time=2)
        )
        self.add(orbit, planet)
        self.wait(4)

从 mobject 的更新器一遍又一遍地播放动画:即带 cycle 的 turn_animation_into_updater。

每个周期都从头开始播放动画:若动画的终点与起点不同,则在每个周期处跳回起点。

m.cycle_animation(animation, delay=0)
delay

第一个周期开始前等待的时间,单位为秒。

返回 动画的 mobject,附带其更新器。

源码

manimgx\animation\updaters.py

def cycle_animation(animation: Animation, delay: float = 0) -> Mobject:
    """从 mobject 的更新器一遍又一遍地播放动画:即带 `cycle` 的
    [`turn_animation_into_updater`][manimgx.turn_animation_into_updater]。

    每个周期都从头开始播放动画:若动画的终点与起点不同,则在每个周期处跳回起点。

    Args:
        delay: 第一个周期开始前等待的时间,单位为秒。

    Returns:
        动画的 mobject,附带其更新器。

    Examples:
        ```python
        import manimgx as m


        class CycleAnimationExample(m.Scene):
            def construct(self) -> None:
                orbit = m.Circle(radius=3, color=m.BLUE)
                planet = m.Dot(orbit.get_start(), radius=0.25, color=m.YELLOW)
                m.cycle_animation(
                    m.MoveAlongPath(planet, orbit, rate_func=m.linear, run_time=2)
                )
                self.add(orbit, planet)
                self.wait(4)
        ```
    """
    return turn_animation_into_updater(animation, cycle=True, delay=delay)

作为动画的更新器

UpdateFromFunc

代码
import manimgx as m


class UpdateFromFuncExample(m.Scene):
    def construct(self) -> None:
        dot = m.Dot(4 * m.LEFT, radius=0.2, color=m.YELLOW)
        label = m.Text("dot", font_size=60)
        self.add(dot, label)
        self.play(
            dot.animate.shift(8 * m.RIGHT),
            m.UpdateFromFunc(label, lambda mob: mob.next_to(dot, m.UP)),
            run_time=2,
        )

在动画运行期间的每一帧对一个 mobject 调用一个函数。

该函数接收该 mobject,而非进度:用于在其他动画播放时保持一个 mobject 为最新状态。该 mobject 自身的更新器直接作用于它 (suspend_mobject_updating 为 False)。

m.UpdateFromFunc(mobject, update_function, **kwargs)
mobject

要更新的 mobject。

update_function

该 mobject 的函数,在每一帧调用。

run_time

动画播放时长,以秒为单位(默认 1)。

lag_ratio

mobject 各部分如何交错:每个部分在其前一个部分开始后,经过其运行时长的该分数比例后开始 (默认 0:全部同时;1:一个接一个)。

rate_func

动画进度如何随时间运行:一个从 [0, 1] 到 [0, 1] 的函数 (默认为 smooth;参见 rate functions)。

reverse_rate_function

是否反向运行动画(默认 False)。

name

动画的名称。

remover

动画结束时 mobject 是否离开场景 (默认 False)。

suspend_mobject_updating

动画期间 mobject 的更新器是否在其下运行(默认 True):它们 持续作用于 mobject,每一帧显示动画应用于其结果。 若为 False,则它们作用于被动画的 mobject 本身。

introducer

动画开始时 mobject 是否加入场景(默认 False); 否则若场景缺少它,play 在其开始时将其带入。

use_override

若某 mobject 的类用另一个动画代替此动画播放,是否如此执行 (默认 True)。

还接受 动画关键字。

源码

manimgx\animation\motion.py

def __init__(
    self,
    mobject: M,
    update_function: Callable[[M], object],
    **kwargs: Unpack[AnimationOptions],
) -> None:
    self.update_function = update_function
    super().__init__(mobject, **kwargs)

UpdateFromAlphaFunc

代码
import manimgx as m


class UpdateFromAlphaFuncExample(m.Scene):
    def construct(self) -> None:
        square = m.Square(side_length=2, color=m.BLUE, fill_opacity=0.5)

        def slide(mob: m.Square, alpha: float) -> None:
            mob.move_to((8 * alpha - 4) * m.RIGHT)
            mob.set_color(m.interpolate_color(m.BLUE, m.YELLOW, alpha))

        self.play(m.UpdateFromAlphaFunc(square, slide, run_time=2))

在每一帧对一个 mobject 调用一个函数,并传入动画的进度。

该函数接收该 mobject 与经速率函数缓动后的进度,并将该 mobject 设置为 该进度处的状态。该 mobject 自身的更新器直接作用于它 (suspend_mobject_updating 为 False)。

m.UpdateFromAlphaFunc(mobject, update_function, **kwargs)
mobject

要更新的 mobject。

update_function

该 mobject 与缓动后进度的函数,进度从 0 到 1。

run_time

动画播放时长,以秒为单位(默认 1)。

lag_ratio

mobject 各部分如何交错:每个部分在其前一个部分开始后,经过其运行时长的该分数比例后开始 (默认 0:全部同时;1:一个接一个)。

rate_func

动画进度如何随时间运行:一个从 [0, 1] 到 [0, 1] 的函数 (默认为 smooth;参见 rate functions)。

reverse_rate_function

是否反向运行动画(默认 False)。

name

动画的名称。

remover

动画结束时 mobject 是否离开场景 (默认 False)。

suspend_mobject_updating

动画期间 mobject 的更新器是否在其下运行(默认 True):它们 持续作用于 mobject,每一帧显示动画应用于其结果。 若为 False,则它们作用于被动画的 mobject 本身。

introducer

动画开始时 mobject 是否加入场景(默认 False); 否则若场景缺少它,play 在其开始时将其带入。

use_override

若某 mobject 的类用另一个动画代替此动画播放,是否如此执行 (默认 True)。

还接受 动画关键字。

源码

manimgx\animation\motion.py

def __init__(
    self,
    mobject: M,
    update_function: Callable[[M, float], object],
    **kwargs: Unpack[AnimationOptions],
) -> None:
    self.update_function = update_function
    super().__init__(mobject, **kwargs)

MaintainPositionRelativeTo

代码
import manimgx as m


class MaintainPositionRelativeToExample(m.Scene):
    def construct(self) -> None:
        leader = m.Square(side_length=2, color=m.BLUE, fill_opacity=0.5)
        follower = m.Circle(color=m.YELLOW).next_to(leader, m.RIGHT, buff=0.5)
        m.VGroup(leader, follower).shift(4 * m.LEFT + 1.5 * m.DOWN)
        self.add(leader, follower)
        self.play(
            leader.animate.shift(5 * m.RIGHT + 3 * m.UP),
            m.MaintainPositionRelativeTo(follower, leader),
            run_time=2,
        )

在每一帧使一个 mobject 与另一个 mobject 的中心保持相同的偏移。

该偏移动画创建时为两者中心之间的偏移;将其与移动 tracked_mobject 的 动画一同播放。

m.MaintainPositionRelativeTo(mobject, tracked_mobject, **kwargs)
mobject

要保持位置的 mobject。

tracked_mobject

它所跟随的 mobject。

run_time

动画播放时长,以秒为单位(默认 1)。

lag_ratio

mobject 各部分如何交错:每个部分在其前一个部分开始后,经过其运行时长的该分数比例后开始 (默认 0:全部同时;1:一个接一个)。

rate_func

动画进度如何随时间运行:一个从 [0, 1] 到 [0, 1] 的函数 (默认为 smooth;参见 rate functions)。

reverse_rate_function

是否反向运行动画(默认 False)。

name

动画的名称。

remover

动画结束时 mobject 是否离开场景 (默认 False)。

suspend_mobject_updating

动画期间 mobject 的更新器是否在其下运行(默认 True):它们 持续作用于 mobject,每一帧显示动画应用于其结果。 若为 False,则它们作用于被动画的 mobject 本身。

introducer

动画开始时 mobject 是否加入场景(默认 False); 否则若场景缺少它,play 在其开始时将其带入。

use_override

若某 mobject 的类用另一个动画代替此动画播放,是否如此执行 (默认 True)。

还接受 动画关键字。

源码

manimgx\animation\motion.py

def __init__(
    self,
    mobject: Mobject,
    tracked_mobject: Mobject,
    **kwargs: Unpack[AnimationOptions],
) -> None:
    self.tracked_mobject = tracked_mobject
    self.diff = mobject.get_center() - tracked_mobject.get_center()
    super().__init__(mobject, **kwargs)

轨迹与轮廓

TracedPath

代码
import manimgx as m


class TracedPathExample(m.Scene):
    def construct(self) -> None:
        wheel = m.Circle(radius=1, color=m.BLUE).shift(5 * m.LEFT)
        dot = m.Dot(wheel.get_bottom(), radius=0.12, color=m.YELLOW)
        rolling = m.VGroup(wheel, dot)
        path = m.TracedPath(
            dot.get_center, stroke_color=m.YELLOW, stroke_width=4
        )

        def roll(mob: m.Mobject, dt: float) -> None:  # 2.5 radians a second
            mob.rotate(-2.5 * dt, about_point=mob[0].get_center())

        rolling.add_updater(roll)
        self.add(path, rolling)
        # 2.5 units a second: the wheel rolls without slipping
        self.play(
            rolling.animate.shift(10 * m.RIGHT), run_time=4, rate_func=m.linear
        )

移动点所描绘的路径:一条经过其所有曾到之处的线。

traced_point_func 给出该点。路径在仿真时钟的每个滴答 (每秒 config.simulation_rate 次) 以及每次 play 和 wait 结束时记录其位置,因此在任何帧率下路径都相同;在两个滴答之间的帧上, 路径延伸到该点当时的位置。它在场景中时进行描绘,从加入场景时起,且在每个瞬间于其他所有内容 移动之后进行:其更新器是一个 recorder。

逐帧更新器仅在帧上移动该点,因此它在某帧移动该点的量就是该点在以该帧结束的那一帧时间内的 变化量:路径将该移动按时间比例分散到该帧上。一个被 play 带动同时被逐帧更新器转动的点描绘出 一条平滑曲线,而非阶梯。使用 dissipating_time 时,仅保留最近这么多秒内所描绘的那一段。

m.TracedPath(traced_point_func, dissipating_time=None, **kwargs)
traced_point_func

一个无参函数,给出要描绘的点(dot.get_center)。

dissipating_time

路径每一段保留的时长,单位为秒;None:全部保留。

color

填充与描边共用的颜色(默认白色);None 表示该类的默认值。

fill_color

填充的颜色;未给出时为 color。多个颜色沿 sheen_direction 构成渐变。

fill_opacity

填充的不透明度,取值从 0 到 1(默认 0:无填充)。

stroke_color

描边的颜色;未给出时为 color。多个颜色构成渐变。

stroke_opacity

描边的不透明度,取值从 0 到 1(默认 1)。

stroke_width

描边的宽度,以场景单位的百分之一计(默认 4;0:无描边)。

background_stroke_color

绘制于填充之后的轮廓颜色(默认黑色)。

background_stroke_opacity

轮廓的不透明度,取值从 0 到 1(默认 1)。

background_stroke_width

轮廓的宽度,以场景单位的百分之一计(默认 0:无)。

sheen_factor

颜色朝 sheen_direction 变亮的程度,取值从 -1 到 1(默认 0); 负值变暗。

sheen_direction

颜色变亮的方向(默认 UL)。

joint_type

描边在路径转折处的连接方式:圆角、斜切或斜接,按二维场景的绘制方式 (参见 LineJointType;默认 AUTO:斜接)。

cap_style

描边在其显示的每一端的端点样式(开放路径的端点、虚线的端点):圆头、平头 或方头,按二维场景的绘制方式(参见 CapStyleType;默认 AUTO:平头)。

shade_in_3d

三维场景的光是否对该 mobject 着色。

material

其表面在三维场景中如何反射场景的光(参见 Material);None:Manim 的着色(默认)。

name

mobject 的名称;未给出时为其类名。

z_index

其在绘制顺序中的位置:索引较高者绘制于较低者之上 (默认 0)。

target

MoveToTarget 将 mobject 移动至的状态。

还接受 样式关键字。

源码

manimgx\animation\updaters.py

def __init__(
    self,
    traced_point_func: Callable[[], Point3DLike],
    dissipating_time: float | None = None,
    **kwargs: Unpack[Style],
) -> None:
    if kwargs.get("stroke_color") is None:  # not given, None too
        kwargs["stroke_color"] = WHITE
    if kwargs.get("stroke_width") is None:  # not given, None too
        kwargs["stroke_width"] = 2
    super().__init__(**kwargs)
    self.traced_point_func = traced_point_func
    self.dissipating_time = dissipating_time
    self.time = 0.0  # the path's own time
    # when each curve's end was traced (its own time)
    self._traced: list[float] = []
    # the scene time of each curve's end since the last frame
    self._open: list[float] = []
    self._framed: float | None = None  # the scene time of the last frame (or event)
    self._last = np.zeros(3)  # the point as last traced
    self._loose = (
        False  # its last curve runs on to the point at a frame between two ticks
    )
    self.add_updater(
        _trace
    )  # (a function of the path it runs on: a copy traces itself)

AnimatedBoundary

代码
import manimgx as m


class AnimatedBoundaryExample(m.Scene):
    def construct(self) -> None:
        word = m.Text("So shiny!", font_size=144)
        boundary = m.AnimatedBoundary(
            word,
            colors=[m.RED, m.YELLOW, m.BLUE],
            max_stroke_width=8,
            cycle_rate=1,  # a color a second
        )
        self.add(word, boundary)
        self.wait(3)

围绕 mobject 不断自我绘制的轮廓,颜色循环切换。

每个周期以下一个颜色绘制该 mobject 的轮廓,同时上一周期所绘制的轮廓逐渐变细消失;若使用 back_and_forth,则每隔一个周期反向绘制轮廓。它跟随该 mobject:每一帧都取该 mobject 当时的 轮廓。它仅绘制轮廓:请将其与该 mobject 一同添加到场景中。

m.AnimatedBoundary(vmobject, colors=(BLUE_D, BLUE_B, BLUE_E, GREY_BROWN), max_stroke_width=3, cycle_rate=0.5, back_and_forth=True, draw_rate_func=smooth, fade_rate_func=smooth, **kwargs)
vmobject

要绘制轮廓的 mobject。

colors

颜色序列,每个周期依次使用一种。

max_stroke_width

轮廓的线宽,单位为场景单位的百分之一。

cycle_rate

每秒的周期数。

back_and_forth

是否每隔一个周期从轮廓的终点反向绘制回起点。

draw_rate_func

每次绘制的节奏:一个速率函数。

fade_rate_func

每次变细消失的节奏:一个速率函数。

color

填充与描边共用的颜色(默认白色);None 表示该类的默认值。

fill_color

填充的颜色;未给出时为 color。多个颜色沿 sheen_direction 构成渐变。

fill_opacity

填充的不透明度,取值从 0 到 1(默认 0:无填充)。

stroke_color

描边的颜色;未给出时为 color。多个颜色构成渐变。

stroke_opacity

描边的不透明度,取值从 0 到 1(默认 1)。

stroke_width

描边的宽度,以场景单位的百分之一计(默认 4;0:无描边)。

background_stroke_color

绘制于填充之后的轮廓颜色(默认黑色)。

background_stroke_opacity

轮廓的不透明度,取值从 0 到 1(默认 1)。

background_stroke_width

轮廓的宽度,以场景单位的百分之一计(默认 0:无)。

sheen_factor

颜色朝 sheen_direction 变亮的程度,取值从 -1 到 1(默认 0); 负值变暗。

sheen_direction

颜色变亮的方向(默认 UL)。

joint_type

描边在路径转折处的连接方式:圆角、斜切或斜接,按二维场景的绘制方式 (参见 LineJointType;默认 AUTO:斜接)。

cap_style

描边在其显示的每一端的端点样式(开放路径的端点、虚线的端点):圆头、平头 或方头,按二维场景的绘制方式(参见 CapStyleType;默认 AUTO:平头)。

shade_in_3d

三维场景的光是否对该 mobject 着色。

material

其表面在三维场景中如何反射场景的光(参见 Material);None:Manim 的着色(默认)。

name

mobject 的名称;未给出时为其类名。

z_index

其在绘制顺序中的位置:索引较高者绘制于较低者之上 (默认 0)。

target

MoveToTarget 将 mobject 移动至的状态。

还接受 样式关键字。

源码

manimgx\animation\updaters.py

def __init__(
    self,
    vmobject: Mobject,
    colors: Sequence[ParsableManimColor] = (BLUE_D, BLUE_B, BLUE_E, GREY_BROWN),
    max_stroke_width: float = 3,
    cycle_rate: float = 0.5,
    back_and_forth: bool = True,
    draw_rate_func: RateFunction = smooth,
    fade_rate_func: RateFunction = smooth,
    **kwargs: Unpack[Style],
):
    super().__init__(**kwargs)
    self.colors = list(colors)  # its own: the default, or what was given, as it was
    self.max_stroke_width = max_stroke_width
    self.cycle_rate = cycle_rate
    self.back_and_forth = back_and_forth
    self.draw_rate_func = draw_rate_func
    self.fade_rate_func = fade_rate_func
    self.vmobject = vmobject
    self.boundary_copies = [
        vmobject.copy().set_style(stroke_width=0, fill_opacity=0) for x in range(2)
    ]
    self.add(*self.boundary_copies)
    self.total_time = 0.0
    self.add_updater(flow(lambda m, dt: m.update_boundary_copies(dt)))

Pivot

变换所保持不动的点:给定的点,或 mobject 边界框上的一个点。

当两个关键字均未指定时,缩放、旋转或拉伸保持 mobject 的中心不动, 而函数或矩阵关于原点应用。

about_point

保持不动的点,以场景坐标表示;它优先于 about_edge。

about_edge

mobject 边界框上保持不动的点,以方向命名: UP 表示其顶边的中点,UR 表示其右上角,ORIGIN 表示其中心。

about_point

保持不动的点,以场景坐标表示;它优先于 about_edge。

about_edge

mobject 边界框上保持不动的点,以方向命名: UP 表示其顶边的中点,UR 表示其右上角,ORIGIN 表示其中心。