Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix code transform animation #4111

Closed
1 change: 1 addition & 0 deletions manim/mobject/text/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
:toctree: ../reference

~code_mobject
~code_transform
~numbers
~tex_mobject
~text_mobject
Expand Down
97 changes: 97 additions & 0 deletions manim/mobject/text/code_transform.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
from __future__ import annotations

from manim import *
github-advanced-security[bot] marked this conversation as resolved.
Fixed
Show resolved Hide resolved


def find_line_matches(before: Code, after: Code):
before_lines = [
line.lstrip() if line.strip() != "" else None
for line in before.code_string.splitlines()
]
after_lines = [
line.lstrip() if line.strip() != "" else None
for line in after.code_string.splitlines()
]

matches = []

for i, b_line in enumerate(before_lines):
if b_line is None:
continue
for j, a_line in enumerate(after_lines):
if a_line is not None and b_line == a_line:
matches.append((i, j))
before_lines[i] = None
after_lines[j] = None
break

deletions = []
for i, line in enumerate(before_lines):
if before_lines[i] is not None:
deletions.append((i, len(line)))

additions = []
for j, line in enumerate(after_lines):
if after_lines[j] is not None:
additions.append((j, len(line)))

return matches, deletions, additions


class CodeTransform(AnimationGroup):
"""
An animation that smoothly transitions between two Code objects.

PARAMETERS
----------
before : Code
The initial Code object.
after : Code
The target Code object after the transition.
"""

def __init__(self, before: Code, after: Code, **kwargs):
matches, deletions, additions = find_line_matches(before, after)

transform_pairs = [(before.code[i], after.code[j]) for i, j in matches]

delete_lines = [before.code[i] for i, _ in deletions]

add_lines = [after.code[j] for j, _ in additions]

animations = []

if hasattr(before, "background_mobject") and hasattr(
after, "background_mobject"
):
animations.append(
Transform(before.background_mobject, after.background_mobject)
)

if hasattr(before, "line_numbers") and hasattr(after, "line_numbers"):
animations.append(Transform(before.line_numbers, after.line_numbers))

if delete_lines:
animations.append(FadeOut(*delete_lines))

if transform_pairs:
animations.append(
LaggedStart(
*[
Transform(before_line, after_line)
for before_line, after_line in transform_pairs
]
)
)

if add_lines:
animations.append(FadeIn(*add_lines))

super().__init__(
*animations,
group=None,
run_time=None,
rate_func=linear,
lag_ratio=0.0,
**kwargs,
)
2 changes: 1 addition & 1 deletion manim/utils/file_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ def open_file(file_path: Path, in_browser: bool = False) -> None:
if current_os == "Windows":
# The method os.startfile is only available in Windows,
# ignoring type error caused by this.
os.startfile(file_path if not in_browser else file_path.parent) # type: ignore[attr-defined]
os.startfile(file_path if not in_browser else file_path.parent)
else:
if current_os == "Linux":
commands = ["xdg-open"]
Expand Down
Binary file not shown.
58 changes: 58 additions & 0 deletions tests/test_graphical_units/test_code_transform.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
from manim import *
from manim.mobject.text.code_transform import CodeTransform
from manim.utils.testing.frames_comparison import frames_comparison

__module_test__ = "code_transform"


@frames_comparison
def test_code_transform(scene):
before_code = """from manim import *

class Animation(Scene):
def construct(self):

square = Square(side_length=2.0, color=RED)
square.shift(LEFT * 2)

self.play(Create(square))
self.wait()
"""

after_code = """from manim import *

class Animation(Scene):
def construct(self):

circle = Circle(radius=1.0, color=BLUE)
circle.shift(RIGHT * 2)

square = Square(side_length=2.0, color=RED)
square.shift(LEFT * 2)

self.play(Create(circle))
self.wait()
"""

before = Code(
code=before_code,
tab_width=4,
background="window",
language="Python",
font="Monospace",
style="one-dark",
line_spacing=1,
).scale(0.8)

after = Code(
code=after_code,
tab_width=4,
background="window",
language="Python",
font="Monospace",
style="one-dark",
line_spacing=1,
).scale(0.8)

scene.add(before)
scene.play(CodeTransform(before, after))
Loading