2022-02-15 18:39:45 +08:00
|
|
|
from __future__ import annotations
|
|
|
|
|
2022-02-08 00:21:53 +08:00
|
|
|
import itertools as it
|
2021-01-30 17:52:02 -08:00
|
|
|
|
2022-02-15 18:39:45 +08:00
|
|
|
import numpy as np
|
|
|
|
|
2021-01-04 23:09:29 -08:00
|
|
|
from manimlib.animation.composition import AnimationGroup
|
2021-01-05 18:03:06 -08:00
|
|
|
from manimlib.animation.fading import FadeInFromPoint
|
|
|
|
from manimlib.animation.fading import FadeOutToPoint
|
2022-04-23 17:17:43 +08:00
|
|
|
from manimlib.animation.fading import FadeTransformPieces
|
2022-02-08 00:21:53 +08:00
|
|
|
from manimlib.animation.transform import ReplacementTransform
|
2021-01-04 23:09:29 -08:00
|
|
|
from manimlib.animation.transform import Transform
|
2021-01-05 18:03:06 -08:00
|
|
|
from manimlib.mobject.mobject import Mobject
|
|
|
|
from manimlib.mobject.mobject import Group
|
2022-03-30 21:57:27 +08:00
|
|
|
from manimlib.mobject.svg.labelled_string import LabelledString
|
2021-01-04 23:09:29 -08:00
|
|
|
from manimlib.mobject.types.vectorized_mobject import VGroup
|
|
|
|
from manimlib.mobject.types.vectorized_mobject import VMobject
|
2021-01-05 18:03:06 -08:00
|
|
|
from manimlib.utils.config_ops import digest_config
|
2021-01-04 23:09:29 -08:00
|
|
|
|
2022-02-15 18:39:45 +08:00
|
|
|
from typing import TYPE_CHECKING
|
2022-02-16 21:08:25 +08:00
|
|
|
|
2022-02-15 18:39:45 +08:00
|
|
|
if TYPE_CHECKING:
|
2022-04-23 17:17:43 +08:00
|
|
|
from manimlib.mobject.svg.tex_mobject import SingleStringTex
|
|
|
|
from manimlib.mobject.svg.tex_mobject import Tex
|
2022-02-15 18:39:45 +08:00
|
|
|
from manimlib.scene.scene import Scene
|
|
|
|
|
2021-01-04 23:09:29 -08:00
|
|
|
|
|
|
|
class TransformMatchingParts(AnimationGroup):
|
2021-01-05 18:03:06 -08:00
|
|
|
CONFIG = {
|
|
|
|
"mobject_type": Mobject,
|
|
|
|
"group_type": Group,
|
2021-01-06 16:14:36 -08:00
|
|
|
"transform_mismatches": False,
|
2021-01-05 18:03:06 -08:00
|
|
|
"fade_transform_mismatches": False,
|
2021-01-07 11:48:16 -08:00
|
|
|
"key_map": dict(),
|
2021-01-05 18:03:06 -08:00
|
|
|
}
|
|
|
|
|
2022-02-15 18:39:45 +08:00
|
|
|
def __init__(self, mobject: Mobject, target_mobject: Mobject, **kwargs):
|
2021-01-05 18:03:06 -08:00
|
|
|
digest_config(self, kwargs)
|
2021-01-07 11:48:16 -08:00
|
|
|
assert(isinstance(mobject, self.mobject_type))
|
|
|
|
assert(isinstance(target_mobject, self.mobject_type))
|
2021-01-04 23:09:29 -08:00
|
|
|
source_map = self.get_shape_map(mobject)
|
|
|
|
target_map = self.get_shape_map(target_mobject)
|
|
|
|
|
2021-01-07 11:48:16 -08:00
|
|
|
# Create two mobjects whose submobjects all match each other
|
|
|
|
# according to whatever keys are used for source_map and
|
|
|
|
# target_map
|
|
|
|
transform_source = self.group_type()
|
|
|
|
transform_target = self.group_type()
|
2021-01-05 18:03:06 -08:00
|
|
|
kwargs["final_alpha_value"] = 0
|
2021-01-04 23:09:29 -08:00
|
|
|
for key in set(source_map).intersection(target_map):
|
|
|
|
transform_source.add(source_map[key])
|
|
|
|
transform_target.add(target_map[key])
|
2021-01-05 18:03:06 -08:00
|
|
|
anims = [Transform(transform_source, transform_target, **kwargs)]
|
2021-01-07 11:48:16 -08:00
|
|
|
# User can manually specify when one part should transform
|
|
|
|
# into another despite not matching by using key_map
|
|
|
|
key_mapped_source = self.group_type()
|
|
|
|
key_mapped_target = self.group_type()
|
|
|
|
for key1, key2 in self.key_map.items():
|
|
|
|
if key1 in source_map and key2 in target_map:
|
|
|
|
key_mapped_source.add(source_map[key1])
|
|
|
|
key_mapped_target.add(target_map[key2])
|
|
|
|
source_map.pop(key1, None)
|
|
|
|
target_map.pop(key2, None)
|
|
|
|
if len(key_mapped_source) > 0:
|
|
|
|
anims.append(FadeTransformPieces(
|
|
|
|
key_mapped_source,
|
|
|
|
key_mapped_target,
|
|
|
|
))
|
2021-01-05 18:03:06 -08:00
|
|
|
|
2021-01-07 11:48:16 -08:00
|
|
|
fade_source = self.group_type()
|
|
|
|
fade_target = self.group_type()
|
2021-01-04 23:09:29 -08:00
|
|
|
for key in set(source_map).difference(target_map):
|
|
|
|
fade_source.add(source_map[key])
|
|
|
|
for key in set(target_map).difference(source_map):
|
|
|
|
fade_target.add(target_map[key])
|
|
|
|
|
2021-01-06 16:14:36 -08:00
|
|
|
if self.transform_mismatches:
|
2021-01-30 17:52:02 -08:00
|
|
|
anims.append(Transform(fade_source.copy(), fade_target, **kwargs))
|
2021-01-05 18:03:06 -08:00
|
|
|
if self.fade_transform_mismatches:
|
|
|
|
anims.append(FadeTransformPieces(fade_source, fade_target, **kwargs))
|
|
|
|
else:
|
|
|
|
anims.append(FadeOutToPoint(
|
2021-12-13 16:03:12 -08:00
|
|
|
fade_source, target_mobject.get_center(), **kwargs
|
2021-01-05 18:03:06 -08:00
|
|
|
))
|
|
|
|
anims.append(FadeInFromPoint(
|
2021-12-13 16:03:12 -08:00
|
|
|
fade_target.copy(), mobject.get_center(), **kwargs
|
2021-01-05 18:03:06 -08:00
|
|
|
))
|
|
|
|
|
|
|
|
super().__init__(*anims)
|
2021-01-04 23:09:29 -08:00
|
|
|
|
|
|
|
self.to_remove = mobject
|
|
|
|
self.to_add = target_mobject
|
|
|
|
|
2022-02-15 18:39:45 +08:00
|
|
|
def get_shape_map(self, mobject: Mobject) -> dict[int, VGroup]:
|
|
|
|
shape_map: dict[int, VGroup] = {}
|
2021-01-05 18:03:06 -08:00
|
|
|
for sm in self.get_mobject_parts(mobject):
|
|
|
|
key = self.get_mobject_key(sm)
|
2021-01-04 23:09:29 -08:00
|
|
|
if key not in shape_map:
|
|
|
|
shape_map[key] = VGroup()
|
|
|
|
shape_map[key].add(sm)
|
|
|
|
return shape_map
|
|
|
|
|
2022-02-15 18:39:45 +08:00
|
|
|
def clean_up_from_scene(self, scene: Scene) -> None:
|
2021-01-04 23:09:29 -08:00
|
|
|
for anim in self.animations:
|
|
|
|
anim.update(0)
|
|
|
|
scene.remove(self.mobject)
|
|
|
|
scene.remove(self.to_remove)
|
|
|
|
scene.add(self.to_add)
|
2021-01-05 18:03:06 -08:00
|
|
|
|
2021-01-07 11:48:16 -08:00
|
|
|
@staticmethod
|
2022-02-15 18:39:45 +08:00
|
|
|
def get_mobject_parts(mobject: Mobject) -> Mobject:
|
2021-01-05 18:03:06 -08:00
|
|
|
# To be implemented in subclass
|
|
|
|
return mobject
|
|
|
|
|
2021-01-07 11:48:16 -08:00
|
|
|
@staticmethod
|
2022-02-15 18:39:45 +08:00
|
|
|
def get_mobject_key(mobject: Mobject) -> int:
|
2021-01-05 18:03:06 -08:00
|
|
|
# To be implemented in subclass
|
|
|
|
return hash(mobject)
|
|
|
|
|
|
|
|
|
|
|
|
class TransformMatchingShapes(TransformMatchingParts):
|
|
|
|
CONFIG = {
|
|
|
|
"mobject_type": VMobject,
|
|
|
|
"group_type": VGroup,
|
|
|
|
}
|
|
|
|
|
2021-01-07 11:48:16 -08:00
|
|
|
@staticmethod
|
2022-02-15 18:39:45 +08:00
|
|
|
def get_mobject_parts(mobject: VMobject) -> list[VMobject]:
|
2021-01-05 18:03:06 -08:00
|
|
|
return mobject.family_members_with_points()
|
|
|
|
|
2021-01-07 11:48:16 -08:00
|
|
|
@staticmethod
|
2022-02-15 18:39:45 +08:00
|
|
|
def get_mobject_key(mobject: VMobject) -> int:
|
2021-01-30 17:52:02 -08:00
|
|
|
mobject.save_state()
|
|
|
|
mobject.center()
|
|
|
|
mobject.set_height(1)
|
|
|
|
result = hash(np.round(mobject.get_points(), 3).tobytes())
|
|
|
|
mobject.restore()
|
|
|
|
return result
|
2021-01-05 18:03:06 -08:00
|
|
|
|
|
|
|
|
|
|
|
class TransformMatchingTex(TransformMatchingParts):
|
|
|
|
CONFIG = {
|
2021-03-31 23:28:49 -07:00
|
|
|
"mobject_type": VMobject,
|
2021-01-05 18:03:06 -08:00
|
|
|
"group_type": VGroup,
|
|
|
|
}
|
|
|
|
|
2021-01-07 11:48:16 -08:00
|
|
|
@staticmethod
|
2022-02-15 18:39:45 +08:00
|
|
|
def get_mobject_parts(mobject: Tex) -> list[SingleStringTex]:
|
2021-01-05 18:03:06 -08:00
|
|
|
return mobject.submobjects
|
|
|
|
|
2021-01-07 11:48:16 -08:00
|
|
|
@staticmethod
|
2022-02-15 18:39:45 +08:00
|
|
|
def get_mobject_key(mobject: Tex) -> str:
|
2021-01-07 12:14:51 -08:00
|
|
|
return mobject.get_tex()
|
2022-02-08 00:21:53 +08:00
|
|
|
|
|
|
|
|
2022-03-29 23:38:06 +08:00
|
|
|
class TransformMatchingStrings(AnimationGroup):
|
2022-02-08 00:21:53 +08:00
|
|
|
CONFIG = {
|
2022-05-04 21:56:13 +08:00
|
|
|
"key_map": {},
|
2022-03-30 21:53:00 +08:00
|
|
|
"transform_mismatches": False,
|
2022-02-08 00:21:53 +08:00
|
|
|
}
|
|
|
|
|
2022-03-28 18:54:43 +08:00
|
|
|
def __init__(self,
|
2022-04-10 08:36:13 +08:00
|
|
|
source: LabelledString,
|
|
|
|
target: LabelledString,
|
2022-03-28 18:54:43 +08:00
|
|
|
**kwargs
|
|
|
|
):
|
2022-02-08 00:21:53 +08:00
|
|
|
digest_config(self, kwargs)
|
2022-04-10 08:36:13 +08:00
|
|
|
assert isinstance(source, LabelledString)
|
|
|
|
assert isinstance(target, LabelledString)
|
2022-02-08 00:21:53 +08:00
|
|
|
anims = []
|
2022-05-03 23:39:37 +08:00
|
|
|
source_indices = list(range(len(source.labels)))
|
|
|
|
target_indices = list(range(len(target.labels)))
|
2022-04-10 08:36:13 +08:00
|
|
|
|
2022-05-03 23:39:37 +08:00
|
|
|
def get_filtered_indices_lists(indices_lists, rest_indices):
|
2022-05-04 21:56:13 +08:00
|
|
|
result = []
|
|
|
|
for indices_list in indices_lists:
|
|
|
|
if not indices_list:
|
|
|
|
continue
|
|
|
|
if not all(index in rest_indices for index in indices_list):
|
|
|
|
continue
|
|
|
|
result.append(indices_list)
|
|
|
|
for index in indices_list:
|
|
|
|
rest_indices.remove(index)
|
|
|
|
return result
|
2022-03-30 21:53:00 +08:00
|
|
|
|
2022-05-03 23:39:37 +08:00
|
|
|
def add_anims(anim_class, indices_lists_pairs):
|
|
|
|
for source_indices_lists, target_indices_lists in indices_lists_pairs:
|
2022-04-23 17:17:43 +08:00
|
|
|
source_indices_lists = get_filtered_indices_lists(
|
2022-05-03 23:39:37 +08:00
|
|
|
source_indices_lists, source_indices
|
2022-04-23 17:17:43 +08:00
|
|
|
)
|
|
|
|
target_indices_lists = get_filtered_indices_lists(
|
2022-05-03 23:39:37 +08:00
|
|
|
target_indices_lists, target_indices
|
2022-04-23 17:17:43 +08:00
|
|
|
)
|
2022-04-10 08:36:13 +08:00
|
|
|
if not source_indices_lists or not target_indices_lists:
|
2022-03-31 16:15:58 +08:00
|
|
|
continue
|
2022-05-03 23:39:37 +08:00
|
|
|
anims.append(anim_class(
|
|
|
|
source.build_parts_from_indices_lists(source_indices_lists),
|
|
|
|
target.build_parts_from_indices_lists(target_indices_lists),
|
|
|
|
**kwargs
|
|
|
|
))
|
2022-03-30 21:53:00 +08:00
|
|
|
|
2022-05-03 23:39:37 +08:00
|
|
|
def get_substr_to_indices_lists_map(part_items):
|
2022-04-23 17:17:43 +08:00
|
|
|
result = {}
|
2022-05-03 23:39:37 +08:00
|
|
|
for substr, indices_list in part_items:
|
2022-04-23 17:17:43 +08:00
|
|
|
if substr not in result:
|
|
|
|
result[substr] = []
|
2022-05-03 23:39:37 +08:00
|
|
|
result[substr].append(indices_list)
|
2022-04-10 08:36:13 +08:00
|
|
|
return result
|
2022-02-08 00:21:53 +08:00
|
|
|
|
2022-04-23 17:17:43 +08:00
|
|
|
def add_anims_from(anim_class, func):
|
2022-05-03 23:39:37 +08:00
|
|
|
source_substr_map = get_substr_to_indices_lists_map(func(source))
|
|
|
|
target_substr_map = get_substr_to_indices_lists_map(func(target))
|
|
|
|
common_substrings = sorted([
|
|
|
|
s for s in source_substr_map if s and s in target_substr_map
|
|
|
|
], key=len, reverse=True)
|
2022-04-23 17:17:43 +08:00
|
|
|
add_anims(
|
|
|
|
anim_class,
|
|
|
|
[
|
2022-05-03 23:39:37 +08:00
|
|
|
(source_substr_map[substr], target_substr_map[substr])
|
|
|
|
for substr in common_substrings
|
2022-04-23 17:17:43 +08:00
|
|
|
]
|
|
|
|
)
|
|
|
|
|
|
|
|
add_anims(
|
|
|
|
ReplacementTransform,
|
|
|
|
[
|
2022-05-03 23:39:37 +08:00
|
|
|
(
|
|
|
|
source.get_submob_indices_lists_by_selector(k),
|
|
|
|
target.get_submob_indices_lists_by_selector(v)
|
|
|
|
)
|
2022-04-23 17:17:43 +08:00
|
|
|
for k, v in self.key_map.items()
|
|
|
|
]
|
2022-03-28 18:54:43 +08:00
|
|
|
)
|
|
|
|
add_anims_from(
|
|
|
|
FadeTransformPieces,
|
2022-04-23 17:17:43 +08:00
|
|
|
LabelledString.get_specified_part_items
|
2022-03-28 18:54:43 +08:00
|
|
|
)
|
|
|
|
add_anims_from(
|
|
|
|
FadeTransformPieces,
|
2022-04-23 17:17:43 +08:00
|
|
|
LabelledString.get_group_part_items
|
2022-03-28 18:54:43 +08:00
|
|
|
)
|
2022-02-08 00:21:53 +08:00
|
|
|
|
2022-04-10 08:36:13 +08:00
|
|
|
rest_source = VGroup(*[source[index] for index in source_indices])
|
|
|
|
rest_target = VGroup(*[target[index] for index in target_indices])
|
2022-03-30 21:53:00 +08:00
|
|
|
if self.transform_mismatches:
|
2022-04-10 08:36:13 +08:00
|
|
|
anims.append(
|
|
|
|
ReplacementTransform(rest_source, rest_target, **kwargs)
|
|
|
|
)
|
2022-03-28 17:55:50 +08:00
|
|
|
else:
|
2022-04-10 08:36:13 +08:00
|
|
|
anims.append(
|
|
|
|
FadeOutToPoint(rest_source, target.get_center(), **kwargs)
|
|
|
|
)
|
|
|
|
anims.append(
|
|
|
|
FadeInFromPoint(rest_target, source.get_center(), **kwargs)
|
|
|
|
)
|
2022-02-08 00:21:53 +08:00
|
|
|
|
|
|
|
super().__init__(*anims)
|