mirror of
https://github.com/ManimCommunity/manim.git
synced 2026-06-22 10:01:47 +00:00
Add classes MethodWithArgs, SceneInteractContinue and SceneInteractRerun inside new module manim.data_structures (#4315)
* Add classes MethodWithArgs, SceneInteractRerun and SceneInteractExit * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Add missing manim.data_structures file * Rename SceneInteractExit to SceneInteractContinue and use dataclasses * Revert using @dataclass(slots=True), because Python 3.9 does not support it * Change order of dataclasses * Add references to Scene.queue in docstrings * Include data_structures in utilities_misc.rst * Use qualified names and mark strings as code in data_structures.py * Add module docstring to data_structures.py * Move Scene.interact() objects from data_structures.py into scene.py * Use qualified name in SceneInteractAction docstring --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
fd8ab62613
commit
e73b6590dd
6 changed files with 120 additions and 46 deletions
|
|
@ -15,6 +15,7 @@ Module Index
|
|||
~utils.commands
|
||||
~utils.config_ops
|
||||
constants
|
||||
data_structures
|
||||
~utils.debug
|
||||
~utils.deprecation
|
||||
~utils.docbuild
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ from typing import TYPE_CHECKING, Any, Callable
|
|||
|
||||
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 @@ class MoveToTarget(Transform):
|
|||
|
||||
|
||||
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()
|
||||
|
||||
|
||||
|
|
|
|||
31
manim/data_structures.py
Normal file
31
manim/data_structures.py
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
"""Data classes and other necessary data structures for use in Manim."""
|
||||
|
||||
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]
|
||||
|
|
@ -21,6 +21,7 @@ from typing import TYPE_CHECKING
|
|||
|
||||
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 @@ class _AnimationBuilder:
|
|||
|
||||
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 @@ class _AnimationBuilder:
|
|||
**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
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import numpy as np
|
|||
|
||||
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 @@ class _AnimationBuilder:
|
|||
|
||||
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 @@ class _AnimationBuilder:
|
|||
**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
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import platform
|
|||
import random
|
||||
import threading
|
||||
import time
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from queue import Queue
|
||||
|
||||
|
|
@ -38,6 +39,7 @@ from watchdog.events import DirModifiedEvent, FileModifiedEvent, FileSystemEvent
|
|||
from watchdog.observers import Observer
|
||||
|
||||
from manim import __version__
|
||||
from manim.data_structures import MethodWithArgs
|
||||
from manim.mobject.mobject import Mobject
|
||||
from manim.mobject.opengl.opengl_mobject import OpenGLPoint
|
||||
|
||||
|
|
@ -59,29 +61,71 @@ from ..utils.module_ops import scene_classes_from_file
|
|||
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
|
||||
used by Scene.interact().
|
||||
The elements consist consist of:
|
||||
SceneInteractAction: TypeAlias = Union[
|
||||
MethodWithArgs, "SceneInteractContinue", "SceneInteractRerun"
|
||||
]
|
||||
"""The SceneInteractAction type alias is used for elements in the queue
|
||||
used by :meth:`.Scene.interact()`.
|
||||
|
||||
- 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.
|
||||
"""
|
||||
|
||||
|
||||
@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
|
||||
|
||||
|
||||
class RerunSceneHandler(FileSystemEventHandler):
|
||||
"""A class to handle rerunning a Scene after the input file is modified."""
|
||||
|
||||
|
|
@ -90,7 +134,7 @@ class RerunSceneHandler(FileSystemEventHandler):
|
|||
self.queue = queue
|
||||
|
||||
def on_modified(self, event: DirModifiedEvent | FileModifiedEvent) -> None:
|
||||
self.queue.put(("rerun_file", [], {}))
|
||||
self.queue.put(SceneInteractRerun("file"))
|
||||
|
||||
|
||||
class Scene:
|
||||
|
|
@ -1106,6 +1150,7 @@ class Scene:
|
|||
and config.renderer == RendererType.OPENGL
|
||||
and threading.current_thread().name != "MainThread"
|
||||
):
|
||||
# TODO: are these actually being used?
|
||||
kwargs.update(
|
||||
{
|
||||
"subcaption": subcaption,
|
||||
|
|
@ -1113,13 +1158,7 @@ class Scene:
|
|||
"subcaption_offset": subcaption_offset,
|
||||
}
|
||||
)
|
||||
self.queue.put(
|
||||
(
|
||||
"play",
|
||||
args,
|
||||
kwargs,
|
||||
)
|
||||
)
|
||||
self.queue.put(SceneInteractRerun("play", **kwargs))
|
||||
return
|
||||
|
||||
start_time = self.time
|
||||
|
|
@ -1363,17 +1402,19 @@ class Scene:
|
|||
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
|
||||
|
||||
|
|
@ -1437,34 +1478,33 @@ class Scene:
|
|||
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, 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.
|
||||
|
|
@ -1473,8 +1513,7 @@ class Scene:
|
|||
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
|
||||
|
|
@ -1561,14 +1600,14 @@ class Scene:
|
|||
dpg.set_viewport_height(540)
|
||||
|
||||
def rerun_callback(sender: Any, data: Any) -> None:
|
||||
self.queue.put(("rerun_gui", [], {}))
|
||||
self.queue.put(SceneInteractRerun("gui"))
|
||||
|
||||
def continue_callback(sender: Any, data: Any) -> None:
|
||||
self.queue.put(("exit_gui", [], {}))
|
||||
self.queue.put(SceneInteractContinue("gui"))
|
||||
|
||||
def scene_selection_callback(sender: Any, data: Any) -> None:
|
||||
config["scene_names"] = (dpg.get_value(sender),)
|
||||
self.queue.put(("rerun_gui", [], {}))
|
||||
self.queue.put(SceneInteractRerun("gui"))
|
||||
|
||||
scene_classes = scene_classes_from_file(
|
||||
Path(config["input_file"]), full_list=True
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue