15
15
import random
16
16
import threading
17
17
import time
18
+ from dataclasses import dataclass
18
19
from pathlib import Path
19
20
from queue import Queue
20
21
38
39
from watchdog .observers import Observer
39
40
40
41
from manim import __version__
42
+ from manim .data_structures import MethodWithArgs
41
43
from manim .mobject .mobject import Mobject
42
44
from manim .mobject .opengl .opengl_mobject import OpenGLPoint
43
45
59
61
if TYPE_CHECKING :
60
62
from collections .abc import Iterable , Sequence
61
63
from types import FrameType
62
- from typing import Any , Callable , TypeAlias
64
+ from typing import Any , Callable , TypeAlias , Union
63
65
64
66
from typing_extensions import Self
65
67
66
68
from manim .typing import Point3D
67
69
68
- SceneInteractAction : TypeAlias = tuple [str , Iterable [Any ], dict [str , Any ]]
70
+ SceneInteractAction : TypeAlias = Union [
71
+ MethodWithArgs , "SceneInteractContinue" , "SceneInteractRerun"
72
+ ]
73
+ """The SceneInteractAction type alias is used for elements in the queue
74
+ used by :meth:`.Scene.interact()`.
75
+
76
+ The elements can be one of the following three:
77
+
78
+ - a :class:`~.MethodWithArgs` object, which represents a :class:`Scene`
79
+ method to be called along with its args and kwargs,
80
+ - a :class:`~.SceneInteractContinue` object, indicating that the scene
81
+ interaction is over and the scene will continue rendering after that, or
82
+ - a :class:`~.SceneInteractRerun` object, indicating that the scene should
83
+ render again.
69
84
"""
70
- The SceneInteractAction type alias is used for elements in the queue
71
- used by Scene.interact().
72
- The elements consist consist of:
73
-
74
- - a string, which is either the name of a Scene method or some special keyword
75
- starting with "rerun" or "exit",
76
- - a list of args for the Scene method (only used if the first string actually
77
- corresponds to a method) and
78
- - a dict of kwargs for the Scene method (if the first string corresponds to one.
79
- Otherwise, currently Scene.interact() extracts a possible "from_animation_number" from it if the first string starts with "rerun"),
80
- as seen around the source code where it's common to use self.queue.put((method_name, [], {})) and similar items.
81
85
86
+
87
+ @dataclass
88
+ class SceneInteractContinue :
89
+ """Object which, when encountered in :meth:`.Scene.interact`, triggers
90
+ the end of the scene interaction, continuing with the rest of the
91
+ animations, if any. This object can be queued in :attr:`.Scene.queue`
92
+ for later use in :meth:`.Scene.interact`.
93
+
94
+ Attributes
95
+ ----------
96
+ sender : str
97
+ The name of the entity which issued the end of the scene interaction,
98
+ such as ``"gui"`` or ``"keyboard"``.
82
99
"""
83
100
101
+ __slots__ = ["sender" ]
102
+
103
+ sender : str
104
+
105
+
106
+ class SceneInteractRerun :
107
+ """Object which, when encountered in :meth:`.Scene.interact`, triggers
108
+ the rerun of the scene. This object can be queued in :attr:`.Scene.queue`
109
+ for later use in :meth:`.Scene.interact`.
110
+
111
+ Attributes
112
+ ----------
113
+ sender : str
114
+ The name of the entity which issued the rerun of the scene, such as
115
+ ``"gui"``, ``"keyboard"``, ``"play"`` or ``"file"``.
116
+ kwargs : dict[str, Any]
117
+ Additional keyword arguments when rerunning the scene. Currently,
118
+ only ``"from_animation_number"`` is being used, which determines the
119
+ animation from which to start rerunning the scene.
120
+ """
121
+
122
+ __slots__ = ["sender" , "kwargs" ]
123
+
124
+ def __init__ (self , sender : str , ** kwargs : Any ) -> None :
125
+ self .sender = sender
126
+ self .kwargs = kwargs
127
+
84
128
85
129
class RerunSceneHandler (FileSystemEventHandler ):
86
130
"""A class to handle rerunning a Scene after the input file is modified."""
@@ -90,7 +134,7 @@ def __init__(self, queue: Queue[SceneInteractAction]) -> None:
90
134
self .queue = queue
91
135
92
136
def on_modified (self , event : DirModifiedEvent | FileModifiedEvent ) -> None :
93
- self .queue .put (( "rerun_file" , [], {} ))
137
+ self .queue .put (SceneInteractRerun ( "file" ))
94
138
95
139
96
140
class Scene :
@@ -1106,20 +1150,15 @@ def play(
1106
1150
and config .renderer == RendererType .OPENGL
1107
1151
and threading .current_thread ().name != "MainThread"
1108
1152
):
1153
+ # TODO: are these actually being used?
1109
1154
kwargs .update (
1110
1155
{
1111
1156
"subcaption" : subcaption ,
1112
1157
"subcaption_duration" : subcaption_duration ,
1113
1158
"subcaption_offset" : subcaption_offset ,
1114
1159
}
1115
1160
)
1116
- self .queue .put (
1117
- (
1118
- "play" ,
1119
- args ,
1120
- kwargs ,
1121
- )
1122
- )
1161
+ self .queue .put (SceneInteractRerun ("play" , ** kwargs ))
1123
1162
return
1124
1163
1125
1164
start_time = self .time
@@ -1363,17 +1402,19 @@ def load_module_into_namespace(
1363
1402
load_module_into_namespace (manim .opengl , namespace )
1364
1403
1365
1404
def embedded_rerun (* args : Any , ** kwargs : Any ) -> None :
1366
- self .queue .put (( "rerun_keyboard" , args , kwargs ))
1405
+ self .queue .put (SceneInteractRerun ( "keyboard" ))
1367
1406
shell .exiter ()
1368
1407
1369
1408
namespace ["rerun" ] = embedded_rerun
1370
1409
1371
1410
shell (local_ns = namespace )
1372
- self .queue .put (( "exit_keyboard" , [], {} ))
1411
+ self .queue .put (SceneInteractContinue ( "keyboard" ))
1373
1412
1374
1413
def get_embedded_method (method_name : str ) -> Callable [..., None ]:
1414
+ method = getattr (self , method_name )
1415
+
1375
1416
def embedded_method (* args : Any , ** kwargs : Any ) -> None :
1376
- self .queue .put (( method_name , args , kwargs ))
1417
+ self .queue .put (MethodWithArgs ( method , args , kwargs ))
1377
1418
1378
1419
return embedded_method
1379
1420
@@ -1437,34 +1478,33 @@ def interact(self, shell: Any, keyboard_thread: threading.Thread) -> None:
1437
1478
last_time = time .time ()
1438
1479
while not (self .renderer .window .is_closing or self .quit_interaction ):
1439
1480
if not self .queue .empty ():
1440
- tup = self .queue .get_nowait ()
1441
- if tup [ 0 ]. startswith ( "rerun" ):
1481
+ action = self .queue .get_nowait ()
1482
+ if isinstance ( action , SceneInteractRerun ):
1442
1483
# Intentionally skip calling join() on the file thread to save time.
1443
- if not tup [ 0 ]. endswith ( "keyboard" ) :
1484
+ if action . sender != "keyboard" :
1444
1485
if shell .pt_app :
1445
1486
shell .pt_app .app .exit (exception = EOFError )
1446
1487
file_observer .unschedule_all ()
1447
1488
raise RerunSceneException
1448
1489
keyboard_thread .join ()
1449
1490
1450
- kwargs = tup [2 ]
1451
- if "from_animation_number" in kwargs :
1452
- config ["from_animation_number" ] = kwargs [
1491
+ if "from_animation_number" in action .kwargs :
1492
+ config ["from_animation_number" ] = action .kwargs [
1453
1493
"from_animation_number"
1454
1494
]
1455
1495
# # TODO: This option only makes sense if interactive_embed() is run at the
1456
1496
# # end of a scene by default.
1457
- # if "upto_animation_number" in kwargs:
1458
- # config["upto_animation_number"] = kwargs[
1497
+ # if "upto_animation_number" in action. kwargs:
1498
+ # config["upto_animation_number"] = action. kwargs[
1459
1499
# "upto_animation_number"
1460
1500
# ]
1461
1501
1462
1502
keyboard_thread .join ()
1463
1503
file_observer .unschedule_all ()
1464
1504
raise RerunSceneException
1465
- elif tup [ 0 ]. startswith ( "exit" ):
1505
+ elif isinstance ( action , SceneInteractContinue ):
1466
1506
# Intentionally skip calling join() on the file thread to save time.
1467
- if not tup [ 0 ]. endswith ( "keyboard" ) and shell .pt_app :
1507
+ if action . sender != "keyboard" and shell .pt_app :
1468
1508
shell .pt_app .app .exit (exception = EOFError )
1469
1509
keyboard_thread .join ()
1470
1510
# Remove exit_keyboard from the queue if necessary.
@@ -1473,8 +1513,7 @@ def interact(self, shell: Any, keyboard_thread: threading.Thread) -> None:
1473
1513
keyboard_thread_needs_join = False
1474
1514
break
1475
1515
else :
1476
- method , args , kwargs = tup
1477
- getattr (self , method )(* args , ** kwargs )
1516
+ action .method (* action .args , ** action .kwargs )
1478
1517
else :
1479
1518
self .renderer .animation_start_time = 0
1480
1519
dt = time .time () - last_time
@@ -1561,14 +1600,14 @@ def _configure_pygui(self, update: bool = True) -> None:
1561
1600
dpg .set_viewport_height (540 )
1562
1601
1563
1602
def rerun_callback (sender : Any , data : Any ) -> None :
1564
- self .queue .put (( "rerun_gui" , [], {} ))
1603
+ self .queue .put (SceneInteractRerun ( "gui" ))
1565
1604
1566
1605
def continue_callback (sender : Any , data : Any ) -> None :
1567
- self .queue .put (( "exit_gui" , [], {} ))
1606
+ self .queue .put (SceneInteractContinue ( "gui" ))
1568
1607
1569
1608
def scene_selection_callback (sender : Any , data : Any ) -> None :
1570
1609
config ["scene_names" ] = (dpg .get_value (sender ),)
1571
- self .queue .put (( "rerun_gui" , [], {} ))
1610
+ self .queue .put (SceneInteractRerun ( "gui" ))
1572
1611
1573
1612
scene_classes = scene_classes_from_file (
1574
1613
Path (config ["input_file" ]), full_list = True
0 commit comments