Skip to content

Add classes MethodWithArgs, SceneInteractContinue and SceneInteractRerun inside new module manim.data_structures #4315

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

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
7 changes: 4 additions & 3 deletions manim/animation/transform.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@

import numpy as np

from manim.data_structures import MethodWithArgs
from manim.mobject.opengl.opengl_mobject import OpenGLGroup, OpenGLMobject

from .. import config
Expand Down Expand Up @@ -438,13 +439,13 @@ def check_validity_of_input(self, mobject: Mobject) -> None:


class _MethodAnimation(MoveToTarget):
def __init__(self, mobject, methods):
def __init__(self, mobject: Mobject, methods: list[MethodWithArgs]) -> None:
self.methods = methods
super().__init__(mobject)

def finish(self) -> None:
for method, method_args, method_kwargs in self.methods:
method.__func__(self.mobject, *method_args, **method_kwargs)
for item in self.methods:
item.method.__func__(self.mobject, *item.args, **item.kwargs)
super().finish()


Expand Down
71 changes: 71 additions & 0 deletions manim/data_structures.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
from __future__ import annotations

from collections.abc import Iterable
from dataclasses import dataclass
from types import MethodType
from typing import Any


@dataclass
class MethodWithArgs:
"""Object containing a :attr:`method` which is intended to be called later
with the positional arguments :attr:`args` and the keyword arguments
:attr:`kwargs`.

Attributes
----------
method : MethodType
A callable representing a method of some class.
args : Iterable[Any]
Positional arguments for :attr:`method`.
kwargs : dict[str, Any]
Keyword arguments for :attr:`method`.
"""

__slots__ = ["method", "args", "kwargs"]

method: MethodType
args: Iterable[Any]
kwargs: dict[str, Any]


@dataclass
class SceneInteractContinue:
"""Object which, when encountered in :meth:`~.Scene.interact`, triggers
the end of the scene interaction, continuing with the rest of the
animations, if any. This object can be queued in :attr:`~.Scene.queue`
for later use in :meth:`~.Scene.interact`.

Attributes
----------
sender : str
The name of the entity which issued the end of the scene interaction,
such as "gui" or "keyboard".
"""

__slots__ = ["sender"]

sender: str


class SceneInteractRerun:
"""Object which, when encountered in :meth:`~.Scene.interact`, triggers
the rerun of the scene. This object can be queued in :attr:`~.Scene.queue`
for later use in :meth:`~.Scene.interact`.

Attributes
----------
sender : str
The name of the entity which issued the rerun of the scene, such as
"gui", "keyboard", "play" or "file".
kwargs : dict[str, Any]
Additional keyword arguments when rerunning the scene. Currently,
only `"from_animation_number"` is being used, which determines the
animation from which to start rerunning the scene.
"""

__slots__ = ["sender", "kwargs"]

def __init__(self, sender: str, **kwargs: Any) -> None:
self.sender = sender
self.kwargs = kwargs
14 changes: 8 additions & 6 deletions manim/gui/gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@
from collections.abc import Sequence
from typing import TYPE_CHECKING, Any

from .. import __version__, config
from ..utils.module_ops import scene_classes_from_file
from manim import __version__
from manim._config import config
from manim.data_structures import SceneInteractContinue, SceneInteractRerun
from manim.utils.module_ops import scene_classes_from_file

if TYPE_CHECKING:
from ..renderer.opengl_renderer import OpenGLRenderer
from manim.renderer.opengl_renderer import OpenGLRenderer


__all__ = ["configure_pygui"]
Expand All @@ -44,14 +46,14 @@ def configure_pygui(
dpg.set_viewport_height(540)

def rerun_callback(sender, data):
renderer.scene.queue.put(("rerun_gui", [], {}))
renderer.scene.queue.put(SceneInteractRerun("gui"))

def continue_callback(sender, data):
renderer.scene.queue.put(("exit_gui", [], {}))
renderer.scene.queue.put(SceneInteractContinue("gui"))

def scene_selection_callback(sender, data):
config["scene_names"] = (dpg.get_value(sender),)
renderer.scene.queue.put(("rerun_gui", [], {}))
renderer.scene.queue.put(SceneInteractRerun("gui"))

scene_classes = scene_classes_from_file(Path(config["input_file"]), full_list=True)
scene_names = [scene_class.__name__ for scene_class in scene_classes]
Expand Down
5 changes: 3 additions & 2 deletions manim/mobject/mobject.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import numpy as np

from manim.data_structures import MethodWithArgs
from manim.mobject.opengl.opengl_compatibility import ConvertToOpenGL

from .. import config, logger
Expand Down Expand Up @@ -3232,7 +3233,7 @@ def __init__(self, mobject) -> None:

self.overridden_animation = None
self.is_chaining = False
self.methods = []
self.methods: list[MethodWithArgs] = []

# Whether animation args can be passed
self.cannot_pass_args = False
Expand Down Expand Up @@ -3267,7 +3268,7 @@ def update_target(*method_args, **method_kwargs):
**method_kwargs,
)
else:
self.methods.append([method, method_args, method_kwargs])
self.methods.append(MethodWithArgs(method, method_args, method_kwargs))
method(*method_args, **method_kwargs)
return self

Expand Down
5 changes: 3 additions & 2 deletions manim/mobject/opengl/opengl_mobject.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

from manim import config, logger
from manim.constants import *
from manim.data_structures import MethodWithArgs
from manim.renderer.shader_wrapper import get_colormap_code
from manim.utils.bezier import integer_interpolate, interpolate
from manim.utils.color import (
Expand Down Expand Up @@ -2938,7 +2939,7 @@ def __init__(self, mobject: OpenGLMobject):

self.overridden_animation = None
self.is_chaining = False
self.methods = []
self.methods: list[MethodWithArgs] = []

# Whether animation args can be passed
self.cannot_pass_args = False
Expand Down Expand Up @@ -2973,7 +2974,7 @@ def update_target(*method_args, **method_kwargs):
**method_kwargs,
)
else:
self.methods.append([method, method_args, method_kwargs])
self.methods.append(MethodWithArgs(method, method_args, method_kwargs))
method(*method_args, **method_kwargs)
return self

Expand Down
70 changes: 35 additions & 35 deletions manim/scene/scene.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@
from watchdog.events import DirModifiedEvent, FileModifiedEvent, FileSystemEventHandler
from watchdog.observers import Observer

from manim.data_structures import (
MethodWithArgs,
SceneInteractContinue,
SceneInteractRerun,
)
from manim.mobject.mobject import Mobject
from manim.mobject.opengl.opengl_mobject import OpenGLPoint

Expand All @@ -55,26 +60,26 @@
if TYPE_CHECKING:
from collections.abc import Iterable, Sequence
from types import FrameType
from typing import Any, Callable, TypeAlias
from typing import Any, Callable, TypeAlias, Union

from typing_extensions import Self

from manim.typing import Point3D

SceneInteractAction: TypeAlias = tuple[str, Iterable[Any], dict[str, Any]]
"""
The SceneInteractAction type alias is used for elements in the queue
SceneInteractAction: TypeAlias = Union[
MethodWithArgs, SceneInteractContinue, SceneInteractRerun
]
"""The SceneInteractAction type alias is used for elements in the queue
used by Scene.interact().
The elements consist consist of:

- a string, which is either the name of a Scene method or some special keyword
starting with "rerun" or "exit",
- a list of args for the Scene method (only used if the first string actually
corresponds to a method) and
- a dict of kwargs for the Scene method (if the first string corresponds to one.
Otherwise, currently Scene.interact() extracts a possible "from_animation_number" from it if the first string starts with "rerun"),
as seen around the source code where it's common to use self.queue.put((method_name, [], {})) and similar items.
The elements can be one of the following three:

- a :class:`~.MethodWithArgs` object, which represents a :class:`Scene`
method to be called along with its args and kwargs,
- a :class:`~.SceneInteractContinue` object, indicating that the scene
interaction is over and the scene will continue rendering after that, or
- a :class:`~.SceneInteractRerun` object, indicating that the scene should
render again.
"""


Expand All @@ -86,7 +91,7 @@
self.queue = queue

def on_modified(self, event: DirModifiedEvent | FileModifiedEvent) -> None:
self.queue.put(("rerun_file", [], {}))
self.queue.put(SceneInteractRerun("file"))


class Scene:
Expand Down Expand Up @@ -1102,20 +1107,15 @@
and config.renderer == RendererType.OPENGL
and threading.current_thread().name != "MainThread"
):
# TODO: are these actually being used?
kwargs.update(
{
"subcaption": subcaption,
"subcaption_duration": subcaption_duration,
"subcaption_offset": subcaption_offset,
}
)
self.queue.put(
(
"play",
args,
kwargs,
)
)
self.queue.put(SceneInteractRerun("play", **kwargs))
return

start_time = self.time
Expand Down Expand Up @@ -1359,17 +1359,19 @@
load_module_into_namespace(manim.opengl, namespace)

def embedded_rerun(*args: Any, **kwargs: Any) -> None:
self.queue.put(("rerun_keyboard", args, kwargs))
self.queue.put(SceneInteractRerun("keyboard"))
shell.exiter()

namespace["rerun"] = embedded_rerun

shell(local_ns=namespace)
self.queue.put(("exit_keyboard", [], {}))
self.queue.put(SceneInteractContinue("keyboard"))

def get_embedded_method(method_name: str) -> Callable[..., None]:
method = getattr(self, method_name)

def embedded_method(*args: Any, **kwargs: Any) -> None:
self.queue.put((method_name, args, kwargs))
self.queue.put(MethodWithArgs(method, args, kwargs))

return embedded_method

Expand Down Expand Up @@ -1434,34 +1436,33 @@
last_time = time.time()
while not (self.renderer.window.is_closing or self.quit_interaction):
if not self.queue.empty():
tup = self.queue.get_nowait()
if tup[0].startswith("rerun"):
action = self.queue.get_nowait()
if isinstance(action, SceneInteractRerun):
# Intentionally skip calling join() on the file thread to save time.
if not tup[0].endswith("keyboard"):
if action.sender != "keyboard":
if shell.pt_app:
shell.pt_app.app.exit(exception=EOFError)
file_observer.unschedule_all()
raise RerunSceneException
keyboard_thread.join()

kwargs = tup[2]
if "from_animation_number" in kwargs:
config["from_animation_number"] = kwargs[
if "from_animation_number" in action.kwargs:
config["from_animation_number"] = action.kwargs[
"from_animation_number"
]
# # TODO: This option only makes sense if interactive_embed() is run at the

Check notice

Code scanning / CodeQL

Commented-out code Note

This comment appears to contain commented-out code.
# # end of a scene by default.
# if "upto_animation_number" in kwargs:
# config["upto_animation_number"] = kwargs[
# if "upto_animation_number" in action.kwargs:
# config["upto_animation_number"] = action.kwargs[
# "upto_animation_number"
# ]

keyboard_thread.join()
file_observer.unschedule_all()
raise RerunSceneException
elif tup[0].startswith("exit"):
elif isinstance(action, SceneInteractContinue):
# Intentionally skip calling join() on the file thread to save time.
if not tup[0].endswith("keyboard") and shell.pt_app:
if action.sender != "keyboard" and shell.pt_app:
shell.pt_app.app.exit(exception=EOFError)
keyboard_thread.join()
# Remove exit_keyboard from the queue if necessary.
Expand All @@ -1470,8 +1471,7 @@
keyboard_thread_needs_join = False
break
else:
method, args, kwargs = tup
getattr(self, method)(*args, **kwargs)
action.method(*action.args, **action.kwargs)
else:
self.renderer.animation_start_time = 0
dt = time.time() - last_time
Expand Down