diff --git a/manim/animation/transform.py b/manim/animation/transform.py index 9719ddf7c1..88e55505ba 100644 --- a/manim/animation/transform.py +++ b/manim/animation/transform.py @@ -29,12 +29,14 @@ import inspect import types from collections.abc import Callable, Iterable, Sequence -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, Self import numpy as np from manim.data_structures import MethodWithArgs from manim.mobject.opengl.opengl_mobject import OpenGLGroup, OpenGLMobject +from manim.mobject.opengl.opengl_vectorized_mobject import OpenGLVMobject +from manim.mobject.types.vectorized_mobject import VMobject from .. import config from ..animation.animation import Animation @@ -66,6 +68,8 @@ class Transform(Animation): path_func A function defining the path that the points of the ``mobject`` are being moved along until they match the points of the ``target_mobject``, see :mod:`.utils.paths`. + If path_func is None, it defaults to a straight line, + which is set up inside the path_arc setter. path_arc The arc angle (in radians) that the points of ``mobject`` will follow to reach the points of the target if using a circular path arc, see ``path_arc_centers``. @@ -142,13 +146,11 @@ def __init__( replace_mobject_with_target_in_scene: bool = False, **kwargs, ) -> None: - self.path_arc_axis: np.ndarray = path_arc_axis - self.path_arc_centers: Point3DLike | Point3DLike_Array | None = path_arc_centers - self.path_arc: float = path_arc - - # path_func is a property a few lines below so it doesn't need to be set in any case + self.path_arc_axis = path_arc_axis + self.path_arc_centers = path_arc_centers + self.path_arc = path_arc if path_func is not None: - self.path_func: Callable = path_func + self.path_func = path_func elif self.path_arc_centers is not None: self.path_func = path_along_circles( path_arc, @@ -156,12 +158,12 @@ def __init__( self.path_arc_axis, ) - self.replace_mobject_with_target_in_scene: bool = ( - replace_mobject_with_target_in_scene - ) - self.target_mobject: Mobject = ( + self.replace_mobject_with_target_in_scene = replace_mobject_with_target_in_scene + + self.target_mobject = ( target_mobject if target_mobject is not None else Mobject() ) + super().__init__(mobject, **kwargs) @property @@ -240,11 +242,11 @@ def get_all_families_zipped(self) -> Iterable[tuple]: # more precise typing? def interpolate_submobject( self, - submobject: Mobject, - starting_submobject: Mobject, - target_copy: Mobject, + submobject: VMobject | OpenGLVMobject, + starting_submobject: VMobject | OpenGLVMobject, + target_copy: VMobject | OpenGLVMobject, alpha: float, - ) -> Transform: + ) -> Self: submobject.interpolate(starting_submobject, target_copy, alpha, self.path_func) return self @@ -461,7 +463,9 @@ class ApplyMethod(Transform): Parameters ---------- method - The method that will be applied in the animation. + The bound method of a Mobject that will be applied in the animation. + Pass the method itself, not its result. + For example, pass ``sq.shift`` not ``sq.shift(UP)``. args Any positional arguments to be passed when applying the method. kwargs @@ -482,6 +486,7 @@ def check_validity_of_input(self, method: Callable) -> None: raise ValueError( "Whoops, looks like you accidentally invoked " "the method you want to animate", + "Pass the method itself, e.g. ``sq.shift`` instead of ``sq.shift(UP)``.", ) assert isinstance(method.__self__, (Mobject, OpenGLMobject)) @@ -681,7 +686,9 @@ def initialize_matrix(self, matrix: np.ndarray) -> np.ndarray: new_matrix[:2, :2] = matrix matrix = new_matrix elif matrix.shape != (3, 3): - raise ValueError("Matrix has bad dimensions") + raise ValueError( + "Only a 2D Matrix having shape either (2,2) or (3,3) is accepted." + ) return matrix @@ -689,16 +696,11 @@ class ApplyComplexFunction(ApplyMethod): def __init__(self, function: types.MethodType, mobject: Mobject, **kwargs) -> None: self.function = function method = mobject.apply_complex_function + rotational_component = function(complex(1)) - function(complex(0)) + arc = np.log(rotational_component).imag if rotational_component != 0 else 0 + kwargs["path_arc"] = arc if np.isfinite(arc) else 0 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): """An animation moving mobjects cyclically.