From b8031a1f7562740c91a103db9974806f8eb24455 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20Manr=C3=ADquez?= Date: Fri, 27 Jun 2025 01:12:44 -0400 Subject: [PATCH 1/7] Add classes MethodWithArgs, SceneInteractRerun and SceneInteractExit --- manim/animation/transform.py | 7 +-- manim/gui/gui.py | 14 +++--- manim/mobject/mobject.py | 5 +- manim/mobject/opengl/opengl_mobject.py | 5 +- manim/scene/scene.py | 66 ++++++++++++-------------- 5 files changed, 49 insertions(+), 48 deletions(-) diff --git a/manim/animation/transform.py b/manim/animation/transform.py index 667208d88a..cbf7d39640 100644 --- a/manim/animation/transform.py +++ b/manim/animation/transform.py @@ -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 @@ -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() diff --git a/manim/gui/gui.py b/manim/gui/gui.py index f173c1bbc1..7b7d309eb6 100644 --- a/manim/gui/gui.py +++ b/manim/gui/gui.py @@ -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 SceneInteractExit, 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"] @@ -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(SceneInteractExit("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] diff --git a/manim/mobject/mobject.py b/manim/mobject/mobject.py index 15df06dee2..027ea215ba 100644 --- a/manim/mobject/mobject.py +++ b/manim/mobject/mobject.py @@ -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 @@ -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 @@ -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 diff --git a/manim/mobject/opengl/opengl_mobject.py b/manim/mobject/opengl/opengl_mobject.py index 6428995cd5..55995a84b8 100644 --- a/manim/mobject/opengl/opengl_mobject.py +++ b/manim/mobject/opengl/opengl_mobject.py @@ -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 ( @@ -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 @@ -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 diff --git a/manim/scene/scene.py b/manim/scene/scene.py index 2c6ef3cdc9..8b6c37bc5a 100644 --- a/manim/scene/scene.py +++ b/manim/scene/scene.py @@ -34,6 +34,7 @@ from watchdog.events import DirModifiedEvent, FileModifiedEvent, FileSystemEventHandler from watchdog.observers import Observer +from manim.data_structures import MethodWithArgs, SceneInteractExit, SceneInteractRerun from manim.mobject.mobject import Mobject from manim.mobject.opengl.opengl_mobject import OpenGLPoint @@ -55,26 +56,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, SceneInteractExit, 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:`~.SceneInteractExit` object, indicating that the scene + interaction is over, or + - a :class:`~.SceneInteractRerun` object, indicating that the scene should + render again. """ @@ -86,7 +87,7 @@ def __init__(self, queue: Queue[SceneInteractAction]) -> None: self.queue = queue def on_modified(self, event: DirModifiedEvent | FileModifiedEvent) -> None: - self.queue.put(("rerun_file", [], {})) + self.queue.put(SceneInteractRerun("file")) class Scene: @@ -1102,6 +1103,7 @@ def play( and config.renderer == RendererType.OPENGL and threading.current_thread().name != "MainThread" ): + # TODO: are these actually being used? kwargs.update( { "subcaption": subcaption, @@ -1109,13 +1111,7 @@ def play( "subcaption_offset": subcaption_offset, } ) - self.queue.put( - ( - "play", - args, - kwargs, - ) - ) + self.queue.put(SceneInteractRerun("play", **kwargs)) return start_time = self.time @@ -1359,17 +1355,19 @@ def load_module_into_namespace( 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(SceneInteractExit("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 @@ -1434,34 +1432,33 @@ def interact(self, shell: Any, keyboard_thread: threading.Thread) -> None: 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 # # 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, SceneInteractExit): # 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. @@ -1470,8 +1467,7 @@ def interact(self, shell: Any, keyboard_thread: threading.Thread) -> None: 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 From de8c5dd2a20b4cd50332928c692dd73e63be7a25 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 27 Jun 2025 05:35:26 +0000 Subject: [PATCH 2/7] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- manim/data_structures.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manim/data_structures.py b/manim/data_structures.py index 18f2a196d4..932a588ec9 100644 --- a/manim/data_structures.py +++ b/manim/data_structures.py @@ -26,4 +26,4 @@ class SceneInteractExit: __slots__ = ["sender"] def __init__(self, sender: str) -> None: - self.sender = sender \ No newline at end of file + self.sender = sender From a7c3a2eecb8dd01c4e53e6f456a173cb33108986 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20Manr=C3=ADquez?= Date: Fri, 27 Jun 2025 01:35:39 -0400 Subject: [PATCH 3/7] Add missing manim.data_structures file --- manim/data_structures.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 manim/data_structures.py diff --git a/manim/data_structures.py b/manim/data_structures.py new file mode 100644 index 0000000000..18f2a196d4 --- /dev/null +++ b/manim/data_structures.py @@ -0,0 +1,29 @@ +from __future__ import annotations + +from collections.abc import Iterable +from dataclasses import dataclass +from types import MethodType +from typing import Any + + +@dataclass +class MethodWithArgs: + __slots__ = ["method", "args", "kwargs"] + method: MethodType + args: Iterable[Any] + kwargs: dict[str, Any] + + +class SceneInteractRerun: + __slots__ = ["sender", "kwargs"] + + def __init__(self, sender: str, **kwargs: Any) -> None: + self.sender = sender + self.kwargs = kwargs + + +class SceneInteractExit: + __slots__ = ["sender"] + + def __init__(self, sender: str) -> None: + self.sender = sender \ No newline at end of file From 6291c4178d6c13def39e4a190e9597cd89099ffa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20Manr=C3=ADquez?= Date: Sat, 28 Jun 2025 00:31:16 -0400 Subject: [PATCH 4/7] Rename SceneInteractExit to SceneInteractContinue and use dataclasses --- manim/data_structures.py | 48 +++++++++++++++++++++++++++++++++++----- manim/gui/gui.py | 4 ++-- manim/scene/scene.py | 16 +++++++++----- 3 files changed, 54 insertions(+), 14 deletions(-) diff --git a/manim/data_structures.py b/manim/data_structures.py index 18f2a196d4..a590865403 100644 --- a/manim/data_structures.py +++ b/manim/data_structures.py @@ -6,15 +6,42 @@ from typing import Any -@dataclass +@dataclass(slots=True) class MethodWithArgs: - __slots__ = ["method", "args", "kwargs"] + """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`. + """ + method: MethodType args: Iterable[Any] kwargs: dict[str, Any] class SceneInteractRerun: + """Object which, when encountered in :meth:`~.Scene.interact`, triggers + the rerun of the scene. + + 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: @@ -22,8 +49,17 @@ def __init__(self, sender: str, **kwargs: Any) -> None: self.kwargs = kwargs -class SceneInteractExit: - __slots__ = ["sender"] +@dataclass(slots=True) +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. + + Attributes + ---------- + sender : str + The name of the entity which issued the end of the scene interaction, + such as "gui" or "keyboard". + """ - def __init__(self, sender: str) -> None: - self.sender = sender \ No newline at end of file + sender: str diff --git a/manim/gui/gui.py b/manim/gui/gui.py index 7b7d309eb6..35fed9cf4f 100644 --- a/manim/gui/gui.py +++ b/manim/gui/gui.py @@ -15,7 +15,7 @@ from manim import __version__ from manim._config import config -from manim.data_structures import SceneInteractExit, SceneInteractRerun +from manim.data_structures import SceneInteractContinue, SceneInteractRerun from manim.utils.module_ops import scene_classes_from_file if TYPE_CHECKING: @@ -49,7 +49,7 @@ def rerun_callback(sender, data): renderer.scene.queue.put(SceneInteractRerun("gui")) def continue_callback(sender, data): - renderer.scene.queue.put(SceneInteractExit("gui")) + renderer.scene.queue.put(SceneInteractContinue("gui")) def scene_selection_callback(sender, data): config["scene_names"] = (dpg.get_value(sender),) diff --git a/manim/scene/scene.py b/manim/scene/scene.py index 8b6c37bc5a..47d2e73c7b 100644 --- a/manim/scene/scene.py +++ b/manim/scene/scene.py @@ -34,7 +34,11 @@ from watchdog.events import DirModifiedEvent, FileModifiedEvent, FileSystemEventHandler from watchdog.observers import Observer -from manim.data_structures import MethodWithArgs, SceneInteractExit, SceneInteractRerun +from manim.data_structures import ( + MethodWithArgs, + SceneInteractContinue, + SceneInteractRerun, +) from manim.mobject.mobject import Mobject from manim.mobject.opengl.opengl_mobject import OpenGLPoint @@ -63,7 +67,7 @@ from manim.typing import Point3D SceneInteractAction: TypeAlias = Union[ - MethodWithArgs, SceneInteractExit, SceneInteractRerun + MethodWithArgs, SceneInteractContinue, SceneInteractRerun ] """The SceneInteractAction type alias is used for elements in the queue used by Scene.interact(). @@ -72,8 +76,8 @@ - a :class:`~.MethodWithArgs` object, which represents a :class:`Scene` method to be called along with its args and kwargs, - - a :class:`~.SceneInteractExit` object, indicating that the scene - interaction is over, or + - 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. """ @@ -1361,7 +1365,7 @@ def embedded_rerun(*args: Any, **kwargs: Any) -> None: namespace["rerun"] = embedded_rerun shell(local_ns=namespace) - self.queue.put(SceneInteractExit("keyboard")) + self.queue.put(SceneInteractContinue("keyboard")) def get_embedded_method(method_name: str) -> Callable[..., None]: method = getattr(self, method_name) @@ -1456,7 +1460,7 @@ def interact(self, shell: Any, keyboard_thread: threading.Thread) -> None: keyboard_thread.join() file_observer.unschedule_all() raise RerunSceneException - elif isinstance(action, SceneInteractExit): + elif isinstance(action, SceneInteractContinue): # Intentionally skip calling join() on the file thread to save time. if action.sender != "keyboard" and shell.pt_app: shell.pt_app.app.exit(exception=EOFError) From 1bdb9d1aafb2abcde1304ed28adf2aaf754919e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20Manr=C3=ADquez?= Date: Sat, 28 Jun 2025 00:37:35 -0400 Subject: [PATCH 5/7] Revert using @dataclass(slots=True), because Python 3.9 does not support it --- manim/data_structures.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/manim/data_structures.py b/manim/data_structures.py index a590865403..2cc08336fe 100644 --- a/manim/data_structures.py +++ b/manim/data_structures.py @@ -6,7 +6,7 @@ from typing import Any -@dataclass(slots=True) +@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 @@ -22,6 +22,8 @@ class MethodWithArgs: Keyword arguments for :attr:`method`. """ + __slots__ = ["method", "args", "kwargs"] + method: MethodType args: Iterable[Any] kwargs: dict[str, Any] @@ -49,7 +51,7 @@ def __init__(self, sender: str, **kwargs: Any) -> None: self.kwargs = kwargs -@dataclass(slots=True) +@dataclass class SceneInteractContinue: """Object which, when encountered in :meth:`~.Scene.interact`, triggers the end of the scene interaction, continuing with the rest of the @@ -62,4 +64,6 @@ class SceneInteractContinue: such as "gui" or "keyboard". """ + __slots__ = ["sender"] + sender: str From faa8eaa2a2e503fefd639c04a6c5664ea8046180 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20Manr=C3=ADquez?= Date: Sat, 28 Jun 2025 00:41:11 -0400 Subject: [PATCH 6/7] Change order of dataclasses --- manim/data_structures.py | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/manim/data_structures.py b/manim/data_structures.py index 2cc08336fe..d7818abcac 100644 --- a/manim/data_structures.py +++ b/manim/data_structures.py @@ -29,6 +29,24 @@ class MethodWithArgs: 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. + + 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. @@ -49,21 +67,3 @@ class SceneInteractRerun: def __init__(self, sender: str, **kwargs: Any) -> None: self.sender = sender self.kwargs = kwargs - - -@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. - - 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 From c800f79ac6a329a6163c8eff12432bf24fb5f9c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20Manr=C3=ADquez?= Date: Sat, 28 Jun 2025 00:43:49 -0400 Subject: [PATCH 7/7] Add references to Scene.queue in docstrings --- manim/data_structures.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/manim/data_structures.py b/manim/data_structures.py index d7818abcac..f8ec7ffce4 100644 --- a/manim/data_structures.py +++ b/manim/data_structures.py @@ -33,7 +33,8 @@ class MethodWithArgs: 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. + animations, if any. This object can be queued in :attr:`~.Scene.queue` + for later use in :meth:`~.Scene.interact`. Attributes ---------- @@ -49,7 +50,8 @@ class SceneInteractContinue: class SceneInteractRerun: """Object which, when encountered in :meth:`~.Scene.interact`, triggers - the rerun of the scene. + the rerun of the scene. This object can be queued in :attr:`~.Scene.queue` + for later use in :meth:`~.Scene.interact`. Attributes ----------