3b1b-manim/manimlib/animation/transform.py

328 lines
9.6 KiB
Python
Raw Normal View History

from __future__ import annotations
2015-06-10 22:00:35 -07:00
import inspect
import numpy as np
2015-06-10 22:00:35 -07:00
from manimlib.animation.animation import Animation
2019-04-21 08:12:05 -07:00
from manimlib.constants import DEGREES
2022-04-12 19:19:59 +08:00
from manimlib.constants import OUT
from manimlib.mobject.mobject import Group
from manimlib.mobject.mobject import Mobject
from manimlib.utils.paths import path_along_arc
from manimlib.utils.paths import straight_path
2015-10-12 19:39:46 -07:00
from typing import TYPE_CHECKING
if TYPE_CHECKING:
2022-12-14 10:55:32 -08:00
from typing import Callable
2022-04-12 19:19:59 +08:00
import numpy.typing as npt
from manimlib.scene.scene import Scene
2022-12-14 10:55:32 -08:00
from manimlib.constants import ManimColor
2015-06-10 22:00:35 -07:00
class Transform(Animation):
2022-12-14 12:05:33 -08:00
replace_mobject_with_target_in_scene: bool = False
def __init__(
self,
mobject: Mobject,
target_mobject: Mobject | None = None,
2022-12-14 12:05:33 -08:00
path_arc: float = 0.0,
path_arc_axis: np.ndarray = OUT,
path_func: Callable | None = None,
**kwargs
):
2018-02-07 16:20:16 -08:00
self.target_mobject = target_mobject
2022-12-14 12:05:33 -08:00
self.path_arc = path_arc
self.path_arc_axis = path_arc_axis
self.path_func = path_func
super().__init__(mobject, **kwargs)
2016-04-17 19:29:27 -07:00
self.init_path_func()
def init_path_func(self) -> None:
if self.path_func is not None:
return
elif self.path_arc == 0:
self.path_func = straight_path
else:
self.path_func = path_along_arc(
self.path_arc,
self.path_arc_axis,
)
def begin(self) -> None:
self.target_mobject = self.create_target()
self.check_target_mobject_validity()
# Use a copy of target_mobject for the align_data_and_family
2021-01-03 16:53:29 -08:00
# call so that the actual target_mobject stays
2021-08-07 22:25:26 +07:00
# preserved, since calling allign_data will potentially
2021-01-03 16:53:29 -08:00
# change the structure of both arguments
self.target_copy = self.target_mobject.copy()
self.mobject.align_data_and_family(self.target_copy)
super().begin()
if not self.mobject.has_updaters:
self.mobject.lock_matching_data(
self.starting_mobject,
self.target_copy,
)
def finish(self) -> None:
2021-01-13 00:35:57 -10:00
super().finish()
self.mobject.unlock_data()
def create_target(self) -> Mobject:
# Has no meaningful effect here, but may be useful
# in subclasses
return self.target_mobject
def check_target_mobject_validity(self) -> None:
if self.target_mobject is None:
raise Exception(
2021-01-03 16:53:29 -08:00
f"{self.__class__.__name__}.create_target not properly implemented"
)
def clean_up_from_scene(self, scene: Scene) -> None:
super().clean_up_from_scene(scene)
if self.replace_mobject_with_target_in_scene:
scene.remove(self.mobject)
scene.add(self.target_mobject)
def update_config(self, **kwargs) -> None:
2016-07-22 11:22:31 -07:00
Animation.update_config(self, **kwargs)
if "path_arc" in kwargs:
2017-02-13 18:43:55 -08:00
self.path_func = path_along_arc(
kwargs["path_arc"],
2017-03-03 17:33:06 -08:00
kwargs.get("path_arc_axis", OUT)
2017-02-13 18:43:55 -08:00
)
2016-07-22 11:22:31 -07:00
def get_all_mobjects(self) -> list[Mobject]:
return [
self.mobject,
self.starting_mobject,
self.target_mobject,
self.target_copy,
]
def get_all_families_zipped(self) -> zip[tuple[Mobject]]:
return zip(*[
mob.get_family()
for mob in [
self.mobject,
self.starting_mobject,
self.target_copy,
]
])
def interpolate_submobject(
self,
submob: Mobject,
start: Mobject,
target_copy: Mobject,
alpha: float
):
2021-01-03 17:16:19 -08:00
submob.interpolate(start, target_copy, alpha, self.path_func)
return self
2017-01-28 15:49:22 -08:00
class ReplacementTransform(Transform):
2022-12-14 12:05:33 -08:00
replace_mobject_with_target_in_scene: bool = True
2017-01-28 15:49:22 -08:00
class TransformFromCopy(Transform):
2022-12-14 12:05:33 -08:00
replace_mobject_with_target_in_scene: bool = True
def __init__(self, mobject: Mobject, target_mobject: Mobject, **kwargs):
2022-12-14 12:05:33 -08:00
super().__init__(mobject.copy(), target_mobject, **kwargs)
2015-06-10 22:00:35 -07:00
2016-08-30 22:17:07 -07:00
class MoveToTarget(Transform):
def __init__(self, mobject: Mobject, **kwargs):
2019-02-08 18:42:40 -08:00
self.check_validity_of_input(mobject)
2019-02-09 10:56:51 -08:00
super().__init__(mobject, mobject.target, **kwargs)
2019-02-08 18:42:40 -08:00
def check_validity_of_input(self, mobject: Mobject) -> None:
2016-08-30 22:17:07 -07:00
if not hasattr(mobject, "target"):
raise Exception(
2022-12-14 12:05:33 -08:00
"MoveToTarget called on mobject without attribute 'target'"
2019-02-08 18:42:40 -08:00
)
2016-08-30 22:17:07 -07:00
2021-02-10 07:43:46 -06:00
class _MethodAnimation(MoveToTarget):
def __init__(self, mobject: Mobject, methods: list[Callable], **kwargs):
2021-02-10 07:43:46 -06:00
self.methods = methods
super().__init__(mobject, **kwargs)
2021-02-10 07:43:46 -06:00
2015-06-10 22:00:35 -07:00
class ApplyMethod(Transform):
def __init__(self, method: Callable, *args, **kwargs):
2015-06-10 22:00:35 -07:00
"""
method is a method of Mobject, *args are arguments for
that method. Key word arguments should be passed in
as the last arg, as a dict, since **kwargs is for
2021-08-07 22:25:26 +07:00
configuration of the transform itself
2015-06-10 22:00:35 -07:00
Relies on the fact that mobject methods return the mobject
"""
self.check_validity_of_input(method)
self.method = method
self.method_args = args
2019-02-09 10:56:51 -08:00
super().__init__(method.__self__, **kwargs)
def check_validity_of_input(self, method: Callable) -> None:
if not inspect.ismethod(method):
raise Exception(
"Whoops, looks like you accidentally invoked "
"the method you want to animate"
)
assert(isinstance(method.__self__, Mobject))
def create_target(self) -> Mobject:
method = self.method
# Make sure it's a list so that args.pop() works
args = list(self.method_args)
if len(args) > 0 and isinstance(args[-1], dict):
method_kwargs = args.pop()
else:
method_kwargs = {}
target = method.__self__.copy()
method.__func__(target, *args, **method_kwargs)
return target
2015-06-10 22:00:35 -07:00
2015-10-20 21:55:46 -07:00
class ApplyPointwiseFunction(ApplyMethod):
def __init__(
self,
function: Callable[[np.ndarray], np.ndarray],
mobject: Mobject,
2022-12-14 12:05:33 -08:00
run_time: float = 3.0,
**kwargs
):
2022-12-14 12:05:33 -08:00
super().__init__(mobject.apply_function, function, run_time=run_time, **kwargs)
2015-10-20 21:55:46 -07:00
2022-12-14 12:05:33 -08:00
class ApplyPointwiseFunctionToCenter(Transform):
def __init__(
self,
function: Callable[[np.ndarray], np.ndarray],
mobject: Mobject,
**kwargs
):
2019-02-08 18:42:40 -08:00
self.function = function
2022-12-14 12:05:33 -08:00
super().__init__(mobject, **kwargs)
2022-12-14 12:05:33 -08:00
def create_target(self) -> Mobject:
return self.mobject.copy().move_to(self.function(self.mobject.get_center()))
2019-02-08 18:42:40 -08:00
class FadeToColor(ApplyMethod):
def __init__(
self,
mobject: Mobject,
color: ManimColor,
**kwargs
):
2019-02-09 10:56:51 -08:00
super().__init__(mobject.set_color, color, **kwargs)
class ScaleInPlace(ApplyMethod):
def __init__(
self,
mobject: Mobject,
scale_factor: npt.ArrayLike,
**kwargs
):
2019-02-09 10:56:51 -08:00
super().__init__(mobject.scale, scale_factor, **kwargs)
2018-07-14 10:31:29 -07:00
class ShrinkToCenter(ScaleInPlace):
def __init__(self, mobject: Mobject, **kwargs):
super().__init__(mobject, 0, **kwargs)
2022-11-04 12:27:50 -07:00
class Restore(Transform):
def __init__(self, mobject: Mobject, **kwargs):
2022-11-04 12:27:50 -07:00
if not hasattr(mobject, "saved_state") or mobject.saved_state is None:
raise Exception("Trying to restore without having saved")
super().__init__(mobject, mobject.saved_state, **kwargs)
2015-06-10 22:00:35 -07:00
class ApplyFunction(Transform):
def __init__(
self,
function: Callable[[Mobject], Mobject],
mobject: Mobject,
**kwargs
):
2019-02-08 18:42:40 -08:00
self.function = function
2019-02-09 10:56:51 -08:00
super().__init__(mobject, **kwargs)
2019-02-08 18:42:40 -08:00
def create_target(self) -> Mobject:
2019-12-15 08:44:57 -08:00
target = self.function(self.mobject.copy())
if not isinstance(target, Mobject):
raise Exception("Functions passed to ApplyFunction must return object of type Mobject")
return target
2016-07-12 10:34:35 -07:00
class ApplyMatrix(ApplyPointwiseFunction):
def __init__(
self,
matrix: npt.ArrayLike,
mobject: Mobject,
**kwargs
):
matrix = self.initialize_matrix(matrix)
def func(p):
return np.dot(p, matrix.T)
2019-02-09 10:56:51 -08:00
super().__init__(func, mobject, **kwargs)
def initialize_matrix(self, matrix: npt.ArrayLike) -> np.ndarray:
matrix = np.array(matrix)
if matrix.shape == (2, 2):
2016-07-12 10:34:35 -07:00
new_matrix = np.identity(3)
new_matrix[:2, :2] = matrix
matrix = new_matrix
elif matrix.shape != (3, 3):
raise Exception("Matrix has bad dimensions")
return matrix
2015-06-10 22:00:35 -07:00
class ApplyComplexFunction(ApplyMethod):
def __init__(
self,
function: Callable[[complex], complex],
mobject: Mobject,
**kwargs
):
self.function = function
2019-02-09 10:56:51 -08:00
method = mobject.apply_complex_function
super().__init__(method, function, **kwargs)
def init_path_func(self) -> None:
func1 = self.function(complex(1))
self.path_arc = np.log(func1).imag
super().init_path_func()
###
class CyclicReplace(Transform):
2022-12-14 12:05:33 -08:00
def __init__(self, *mobjects: Mobject, path_arc=90 * DEGREES, **kwargs):
super().__init__(Group(*mobjects), path_arc=path_arc, **kwargs)
def create_target(self) -> Mobject:
2022-12-14 12:05:33 -08:00
group = self.mobject
target = group.copy()
cycled_targets = [target[-1], *target[:-1]]
2022-12-14 12:05:33 -08:00
for m1, m2 in zip(cycled_targets, group):
m1.move_to(m2)
return target
2015-06-10 22:00:35 -07:00
class Swap(CyclicReplace):
2022-12-14 12:05:33 -08:00
"""Alternate name for CyclicReplace"""
pass