Skip to content

Instantly share code, notes, and snippets.

@abul4fia
Last active April 30, 2024 12:07
Show Gist options
  • Save abul4fia/e3547d788a0ad632a962c49928844edf to your computer and use it in GitHub Desktop.
Save abul4fia/e3547d788a0ad632a962c49928844edf to your computer and use it in GitHub Desktop.
Generic formula transformation under control
"""
This is an example which uses the "low-level" interface, specifying
the indexes or slices() that should be transformed one into
another.
"""
class Test(Scene):
def construct(self):
m1 = MathTex(r"\frac{x}{y} = \frac{a}{b}\times5")
self.add(m1)
m2 = MathTex(r"x\cdot b = y\cdot a\times5")
# Transform element 2 into 4, 6 into 2, 4 into 6
self.play(TransformMatchingSlices(m1, m2,
slice_map=[(2,4), (6,2), (4,6)]))
# Transform element 0:3 into 0:6, 4:7 into 7:9
m3 = MathTex(r"\text{Foobar} = \text{Mu}")
self.play(TransformMatchingSlices(m2, m3,
"""
This is an example of the higher-level interface in which you can use
the function `get_slice_map()` to get the map of slices of indexes to pass
instead of having to figure out "manually" those indexes
"""
class Test(Scene):
def construct(self):
m1 = MathTex(r"\frac{x}{y} = \frac{a}{b}\times5")
self.add(m1)
m2 = MathTex(r"x\cdot b = y\cdot a\times5")
slice_map = get_slice_map(m1, m2, [("y", "y"), ("b", "b"), ("a", "a")])
self.play(TransformMatchingSlices(m1, m2, slice_map=slice_map))
m3 = MathTex(r"\text{Foobar} = \text{Mu} \times 5")
slice_map = get_slice_map(m2, m3, [(r"x\cdot b", r"\text{Foobar}"),
(r"y\cdot a", r"\text{Mu}")])
self.play(TransformMatchingSlices(m2, m3, slice_map=slice_map))
# This code requires the functions defined in the gist
# https://gist.github.com/abul4fia/3bbe8e0c1d19a007cad035bb8be90387
from manim.animation.transform_matching_parts import TransformMatchingAbstractBase
class TransformMatchingSlices(TransformMatchingAbstractBase):
def __init__(
self,
mobject: Mobject,
target_mobject: Mobject,
transform_mismatches: bool = False,
fade_transform_mismatches: bool = False,
key_map: dict | None = None,
slice_map: list | None = None,
**kwargs,
):
if slice_map is None:
slice_map = tuple()
self.mobject = mobject
self.target_mobject = target_mobject
self.mobject_slices = []
self.target_mobject_slices = []
mobject_used = set()
target_mobject_used = set()
for i, (o, t) in enumerate(slice_map):
o_indexes = self.unroll_indexes(o)
t_indexes = self.unroll_indexes(t)
self.mobject_slices.append(self.get_submobjects_group(self.mobject, o_indexes, i))
self.target_mobject_slices.append(self.get_submobjects_group(self.target_mobject, t_indexes, i))
mobject_used.update(o_indexes)
target_mobject_used.update(t_indexes)
mobject_unused = set(range(len(self.mobject[0]))) - mobject_used
target_mobject_unused = set(range(len(self.target_mobject[0]))) - target_mobject_used
self.mobject_slices.append(self.get_submobjects_group(self.mobject, sorted(mobject_unused), len(slice_map)))
self.target_mobject_slices.append(self.get_submobjects_group(self.target_mobject, sorted(target_mobject_unused), len(slice_map)))
super().__init__(
mobject,
target_mobject,
transform_mismatches=transform_mismatches,
fade_transform_mismatches=fade_transform_mismatches,
key_map=key_map,
**kwargs,
)
def unroll_indexes(self, i):
result = []
if isinstance(i, int):
result.append(i)
elif isinstance(i, slice):
result.extend(list(range(i.start, i.stop)))
elif isinstance(i, tuple):
result.extend(list(range(*i)))
elif isinstance(i, list):
for e in i:
result.extend(self.unroll_indexes(e))
else:
raise ValueError(f"Invalid index type: {type(i)}")
return sorted(result)
def get_mobject_parts(self, mobject: Mobject) -> list[Mobject]:
if mobject == self.mobject:
return self.mobject_slices
else:
return self.target_mobject_slices
def get_submobjects_group(self, mobject:Mobject, parts:list, i:int) -> Mobject:
g = VGroup(*[mobject[0][part] for part in parts])
g.matching_key = i
return g
def get_mobject_key(self, mobject: Mobject) -> int:
return getattr(mobject, "matching_key", -1)
def get_slice_map(mobject: Mobject, target_mobject: Mobject, mapping: list[tuple], str_to_mobject=MathTex):
"""Returns the slice map to be used with TransformMatchingSlices
The inputs are:
mobject: the source object to be transformed (usually a MathTex or Tex object)
target_mobject: the target of the transformation
mapping: a list of tuples, being each tuple two strings, or two lists of strings
For each tuple, the first element is a string to be found in the source mobject
and the secon one is a string to be found in the target mobject
The search of those strings is made by shape, not by text
Returns:
a list of tuples corresponding to the slices of the matched shapes in each object
"""
slice_map = []
for _from, _to in mapping:
if isinstance(_from, str):
_from = [str_to_mobject(_from)]
else:
_from = [str_to_mobject(s) for s in _from]
if isinstance(_to, str):
_to = [str_to_mobject(_to)]
else:
_to = [str_to_mobject(s) for s in _to]
slice_map.append((search_shapes_in_text(mobject, _from),
search_shapes_in_text(target_mobject, _to))
)
return slice_map
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment