2015-06-10 22:00:35 -07:00
|
|
|
import inspect
|
|
|
|
|
2018-12-24 12:37:51 -08:00
|
|
|
import numpy as np
|
2015-06-10 22:00:35 -07:00
|
|
|
|
2018-12-24 12:37:51 -08:00
|
|
|
from manimlib.animation.animation import Animation
|
2019-02-08 19:08:09 -08:00
|
|
|
from manimlib.constants import DEFAULT_POINTWISE_FUNCTION_RUN_TIME
|
|
|
|
from manimlib.constants import OUT
|
2019-04-21 08:12:05 -07:00
|
|
|
from manimlib.constants import DEGREES
|
2018-12-24 12:37:51 -08:00
|
|
|
from manimlib.mobject.mobject import Group
|
|
|
|
from manimlib.mobject.mobject import Mobject
|
|
|
|
from manimlib.utils.config_ops import digest_config
|
|
|
|
from manimlib.utils.paths import path_along_arc
|
|
|
|
from manimlib.utils.paths import straight_path
|
|
|
|
from manimlib.utils.rate_functions import smooth
|
|
|
|
from manimlib.utils.rate_functions import squish_rate_func
|
2015-10-12 19:39:46 -07:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2015-06-10 22:00:35 -07:00
|
|
|
class Transform(Animation):
|
2016-02-27 16:32:53 -08:00
|
|
|
CONFIG = {
|
2018-04-06 13:58:59 -07:00
|
|
|
"path_arc": 0,
|
|
|
|
"path_arc_axis": OUT,
|
|
|
|
"path_func": None,
|
|
|
|
"replace_mobject_with_target_in_scene": False,
|
2015-09-28 16:25:18 -07:00
|
|
|
}
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2019-02-09 09:25:13 -08:00
|
|
|
def __init__(self, mobject, target_mobject=None, **kwargs):
|
2019-02-09 10:56:51 -08:00
|
|
|
super().__init__(mobject, **kwargs)
|
2018-02-07 16:20:16 -08:00
|
|
|
self.target_mobject = target_mobject
|
2016-04-17 19:29:27 -07:00
|
|
|
self.init_path_func()
|
2015-09-25 19:43:53 -07:00
|
|
|
|
2019-02-08 12:32:24 -08:00
|
|
|
def init_path_func(self):
|
|
|
|
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,
|
|
|
|
)
|
|
|
|
|
2019-02-08 11:57:27 -08:00
|
|
|
def begin(self):
|
2019-02-09 09:25:13 -08:00
|
|
|
self.target_mobject = self.create_target()
|
|
|
|
self.check_target_mobject_validity()
|
2021-01-03 16:53:29 -08:00
|
|
|
# Use a copy of target_mobject for the align_data
|
|
|
|
# call so that the actual target_mobject stays
|
|
|
|
# preserved, since calling allign_data will potentailly
|
|
|
|
# change the structure of both arguments
|
2019-02-08 12:32:24 -08:00
|
|
|
self.target_copy = self.target_mobject.copy()
|
|
|
|
self.mobject.align_data(self.target_copy)
|
2019-02-08 11:57:27 -08:00
|
|
|
super().begin()
|
|
|
|
|
2019-02-09 09:25:13 -08:00
|
|
|
def create_target(self):
|
|
|
|
# Has no meaningful effect here, but may be useful
|
|
|
|
# in subclasses
|
|
|
|
return self.target_mobject
|
|
|
|
|
|
|
|
def check_target_mobject_validity(self):
|
|
|
|
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"
|
2019-02-09 09:25:13 -08:00
|
|
|
)
|
|
|
|
|
2019-02-08 11:57:27 -08:00
|
|
|
def clean_up_from_scene(self, scene):
|
|
|
|
super().clean_up_from_scene(scene)
|
|
|
|
if self.replace_mobject_with_target_in_scene:
|
|
|
|
scene.remove(self.mobject)
|
|
|
|
scene.add(self.target_mobject)
|
|
|
|
|
2016-07-22 11:22:31 -07:00
|
|
|
def update_config(self, **kwargs):
|
|
|
|
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
|
|
|
|
2017-02-13 18:43:55 -08:00
|
|
|
def get_all_mobjects(self):
|
2019-02-08 12:32:24 -08:00
|
|
|
return [
|
|
|
|
self.mobject,
|
|
|
|
self.starting_mobject,
|
|
|
|
self.target_mobject,
|
|
|
|
self.target_copy,
|
|
|
|
]
|
|
|
|
|
2019-02-11 20:50:54 -08:00
|
|
|
def get_all_families_zipped(self):
|
|
|
|
return zip(*[
|
|
|
|
mob.family_members_with_points()
|
|
|
|
for mob in [
|
|
|
|
self.mobject,
|
|
|
|
self.starting_mobject,
|
|
|
|
self.target_copy,
|
|
|
|
]
|
|
|
|
])
|
|
|
|
|
|
|
|
def interpolate_submobject(self, submob, start, target_copy, alpha):
|
2019-02-08 12:32:24 -08:00
|
|
|
submob.interpolate(
|
|
|
|
start, target_copy,
|
|
|
|
alpha, self.path_func
|
|
|
|
)
|
2016-07-22 15:50:52 -07:00
|
|
|
return self
|
2016-04-10 12:34:28 -07:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2017-01-28 15:49:22 -08:00
|
|
|
class ReplacementTransform(Transform):
|
|
|
|
CONFIG = {
|
2018-04-06 13:58:59 -07:00
|
|
|
"replace_mobject_with_target_in_scene": True,
|
2017-01-28 15:49:22 -08:00
|
|
|
}
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2019-02-08 18:10:55 -08:00
|
|
|
class TransformFromCopy(Transform):
|
|
|
|
"""
|
|
|
|
Performs a reversed Transform
|
|
|
|
"""
|
|
|
|
|
2018-08-15 21:54:43 -07:00
|
|
|
def __init__(self, mobject, target_mobject, **kwargs):
|
2019-02-09 10:56:51 -08:00
|
|
|
super().__init__(target_mobject, mobject, **kwargs)
|
2018-08-15 21:54:43 -07:00
|
|
|
|
2019-02-08 18:10:55 -08:00
|
|
|
def interpolate(self, alpha):
|
|
|
|
super().interpolate(1 - alpha)
|
|
|
|
|
2018-08-15 21:54:43 -07:00
|
|
|
|
2015-08-07 18:10:00 -07:00
|
|
|
class ClockwiseTransform(Transform):
|
2016-02-27 16:32:53 -08:00
|
|
|
CONFIG = {
|
2018-04-06 13:58:59 -07:00
|
|
|
"path_arc": -np.pi
|
2015-09-28 16:25:18 -07:00
|
|
|
}
|
2015-06-13 19:00:23 -07:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2015-08-07 18:10:00 -07:00
|
|
|
class CounterclockwiseTransform(Transform):
|
2016-02-27 16:32:53 -08:00
|
|
|
CONFIG = {
|
2018-04-06 13:58:59 -07:00
|
|
|
"path_arc": np.pi
|
2015-09-28 16:25:18 -07:00
|
|
|
}
|
2015-06-10 22:00:35 -07:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2016-08-30 22:17:07 -07:00
|
|
|
class MoveToTarget(Transform):
|
|
|
|
def __init__(self, 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):
|
2016-08-30 22:17:07 -07:00
|
|
|
if not hasattr(mobject, "target"):
|
2018-04-06 13:58:59 -07:00
|
|
|
raise Exception(
|
2019-02-08 18:42:40 -08:00
|
|
|
"MoveToTarget called on mobject"
|
|
|
|
"without attribute 'target'"
|
|
|
|
)
|
2016-08-30 22:17:07 -07:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2015-06-10 22:00:35 -07:00
|
|
|
class ApplyMethod(Transform):
|
|
|
|
def __init__(self, method, *args, **kwargs):
|
|
|
|
"""
|
2019-02-08 16:51:26 -08: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
|
|
|
|
configuration of the transform itslef
|
2015-06-10 22:00:35 -07:00
|
|
|
|
|
|
|
Relies on the fact that mobject methods return the mobject
|
|
|
|
"""
|
2019-02-08 16:51:26 -08:00
|
|
|
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)
|
2019-02-08 16:51:26 -08:00
|
|
|
|
|
|
|
def check_validity_of_input(self, method):
|
2016-07-22 15:50:52 -07:00
|
|
|
if not inspect.ismethod(method):
|
|
|
|
raise Exception(
|
2018-12-27 09:41:41 -08:00
|
|
|
"Whoops, looks like you accidentally invoked "
|
2018-04-06 13:58:59 -07:00
|
|
|
"the method you want to animate"
|
|
|
|
)
|
2018-06-02 08:59:26 -04:00
|
|
|
assert(isinstance(method.__self__, Mobject))
|
2019-02-08 16:51:26 -08:00
|
|
|
|
|
|
|
def create_target(self):
|
|
|
|
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):
|
2018-01-20 11:00:23 -08:00
|
|
|
method_kwargs = args.pop()
|
|
|
|
else:
|
|
|
|
method_kwargs = {}
|
2018-06-02 08:59:26 -04:00
|
|
|
target = method.__self__.copy()
|
|
|
|
method.__func__(target, *args, **method_kwargs)
|
2019-02-08 16:51:26 -08:00
|
|
|
return target
|
2015-06-10 22:00:35 -07:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2015-10-20 21:55:46 -07:00
|
|
|
class ApplyPointwiseFunction(ApplyMethod):
|
2016-02-27 16:32:53 -08:00
|
|
|
CONFIG = {
|
2018-04-06 13:58:59 -07:00
|
|
|
"run_time": DEFAULT_POINTWISE_FUNCTION_RUN_TIME
|
2015-10-20 21:55:46 -07:00
|
|
|
}
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2015-10-20 21:55:46 -07:00
|
|
|
def __init__(self, function, mobject, **kwargs):
|
2019-02-09 10:56:51 -08:00
|
|
|
super().__init__(mobject.apply_function, function, **kwargs)
|
2015-10-20 21:55:46 -07:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2018-05-21 12:11:46 -07:00
|
|
|
class ApplyPointwiseFunctionToCenter(ApplyPointwiseFunction):
|
|
|
|
def __init__(self, function, mobject, **kwargs):
|
2019-02-08 18:42:40 -08:00
|
|
|
self.function = function
|
2019-02-09 10:56:51 -08:00
|
|
|
super().__init__(mobject.move_to, **kwargs)
|
2018-05-21 12:11:46 -07:00
|
|
|
|
2019-02-08 18:42:40 -08:00
|
|
|
def begin(self):
|
|
|
|
self.method_args = [
|
|
|
|
self.function(self.mobject.get_center())
|
|
|
|
]
|
|
|
|
super().begin()
|
|
|
|
|
2018-05-21 12:11:46 -07:00
|
|
|
|
2015-09-28 16:25:18 -07:00
|
|
|
class FadeToColor(ApplyMethod):
|
|
|
|
def __init__(self, mobject, color, **kwargs):
|
2019-02-09 10:56:51 -08:00
|
|
|
super().__init__(mobject.set_color, color, **kwargs)
|
2015-09-28 16:25:18 -07:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2015-09-28 16:25:18 -07:00
|
|
|
class ScaleInPlace(ApplyMethod):
|
|
|
|
def __init__(self, mobject, scale_factor, **kwargs):
|
2019-02-09 10:56:51 -08:00
|
|
|
super().__init__(mobject.scale, scale_factor, **kwargs)
|
2018-07-14 10:31:29 -07:00
|
|
|
|
|
|
|
|
2019-02-09 10:40:00 -08:00
|
|
|
class ShrinkToCenter(ScaleInPlace):
|
|
|
|
def __init__(self, mobject, **kwargs):
|
|
|
|
super().__init__(mobject, 0, **kwargs)
|
|
|
|
|
|
|
|
|
2018-07-14 10:31:29 -07:00
|
|
|
class Restore(ApplyMethod):
|
|
|
|
def __init__(self, mobject, **kwargs):
|
2019-02-09 10:56:51 -08:00
|
|
|
super().__init__(mobject.restore, **kwargs)
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2015-09-28 16:25:18 -07:00
|
|
|
|
2015-06-10 22:00:35 -07:00
|
|
|
class ApplyFunction(Transform):
|
2015-10-06 15:35:34 -07:00
|
|
|
def __init__(self, function, 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
|
|
|
|
2019-02-09 09:25:13 -08:00
|
|
|
def create_target(self):
|
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
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2015-08-07 18:10:00 -07:00
|
|
|
|
2016-07-12 10:34:35 -07:00
|
|
|
class ApplyMatrix(ApplyPointwiseFunction):
|
2015-10-06 18:40:18 -07:00
|
|
|
def __init__(self, matrix, mobject, **kwargs):
|
2019-02-08 19:08:09 -08:00
|
|
|
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)
|
2019-02-08 19:08:09 -08:00
|
|
|
|
|
|
|
def initialize_matrix(self, matrix):
|
2015-10-06 18:40:18 -07:00
|
|
|
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):
|
2018-06-02 08:59:26 -04:00
|
|
|
raise Exception("Matrix has bad dimensions")
|
2019-02-08 19:08:09 -08:00
|
|
|
return matrix
|
2015-06-10 22:00:35 -07:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2019-02-08 19:08:09 -08:00
|
|
|
class ApplyComplexFunction(ApplyMethod):
|
2018-03-31 18:57:21 -07:00
|
|
|
def __init__(self, function, mobject, **kwargs):
|
2019-02-08 19:08:09 -08:00
|
|
|
self.function = function
|
2019-02-09 10:56:51 -08:00
|
|
|
method = mobject.apply_complex_function
|
|
|
|
super().__init__(method, function, **kwargs)
|
2018-03-31 18:57:21 -07:00
|
|
|
|
2019-02-08 19:08:09 -08:00
|
|
|
def init_path_func(self):
|
|
|
|
func1 = self.function(complex(1))
|
|
|
|
self.path_arc = np.log(func1).imag
|
|
|
|
super().init_path_func()
|
|
|
|
|
2018-03-31 18:57:21 -07:00
|
|
|
###
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2018-03-31 15:11:35 -07:00
|
|
|
class CyclicReplace(Transform):
|
|
|
|
CONFIG = {
|
2019-04-21 08:12:05 -07:00
|
|
|
"path_arc": 90 * DEGREES,
|
2018-03-31 15:11:35 -07:00
|
|
|
}
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2018-03-31 15:11:35 -07:00
|
|
|
def __init__(self, *mobjects, **kwargs):
|
2019-02-09 09:25:13 -08:00
|
|
|
self.group = Group(*mobjects)
|
2019-02-09 10:56:51 -08:00
|
|
|
super().__init__(self.group, **kwargs)
|
2019-02-08 19:08:09 -08:00
|
|
|
|
2019-02-09 09:25:13 -08:00
|
|
|
def create_target(self):
|
|
|
|
target = self.group.copy()
|
|
|
|
cycled_targets = [target[-1], *target[:-1]]
|
|
|
|
for m1, m2 in zip(cycled_targets, self.group):
|
2019-02-08 19:08:09 -08:00
|
|
|
m1.move_to(m2)
|
2019-02-09 09:25:13 -08:00
|
|
|
return target
|
2015-06-10 22:00:35 -07:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2018-03-31 15:11:35 -07:00
|
|
|
class Swap(CyclicReplace):
|
2018-04-06 13:58:59 -07:00
|
|
|
pass # Renaming, more understandable for two entries
|
|
|
|
|
2018-03-31 15:11:35 -07:00
|
|
|
|
2019-02-08 19:08:09 -08:00
|
|
|
# TODO, this may be depricated...worth reimplementing?
|
2016-01-07 16:24:33 -08:00
|
|
|
class TransformAnimations(Transform):
|
2016-02-27 16:32:53 -08:00
|
|
|
CONFIG = {
|
2018-04-06 13:58:59 -07:00
|
|
|
"rate_func": squish_rate_func(smooth)
|
2016-01-07 16:24:33 -08:00
|
|
|
}
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2016-01-07 16:24:33 -08:00
|
|
|
def __init__(self, start_anim, end_anim, **kwargs):
|
|
|
|
digest_config(self, kwargs, locals())
|
|
|
|
if "run_time" in kwargs:
|
|
|
|
self.run_time = kwargs.pop("run_time")
|
|
|
|
else:
|
|
|
|
self.run_time = max(start_anim.run_time, end_anim.run_time)
|
|
|
|
for anim in start_anim, end_anim:
|
|
|
|
anim.set_run_time(self.run_time)
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2016-01-07 16:24:33 -08:00
|
|
|
if start_anim.starting_mobject.get_num_points() != end_anim.starting_mobject.get_num_points():
|
2016-04-10 12:34:28 -07:00
|
|
|
start_anim.starting_mobject.align_data(end_anim.starting_mobject)
|
2016-01-07 16:24:33 -08:00
|
|
|
for anim in start_anim, end_anim:
|
2017-01-27 13:23:17 -08:00
|
|
|
if hasattr(anim, "target_mobject"):
|
|
|
|
anim.starting_mobject.align_data(anim.target_mobject)
|
2016-01-07 16:24:33 -08:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
Transform.__init__(self, start_anim.mobject,
|
|
|
|
end_anim.mobject, **kwargs)
|
|
|
|
# Rewire starting and ending mobjects
|
2016-01-07 16:24:33 -08:00
|
|
|
start_anim.mobject = self.starting_mobject
|
2017-01-27 13:23:17 -08:00
|
|
|
end_anim.mobject = self.target_mobject
|
2016-01-07 16:24:33 -08:00
|
|
|
|
2019-02-08 12:32:24 -08:00
|
|
|
def interpolate(self, alpha):
|
|
|
|
self.start_anim.interpolate(alpha)
|
|
|
|
self.end_anim.interpolate(alpha)
|
|
|
|
Transform.interpolate(self, alpha)
|