Skip to content

Commit c74256c

Browse files
committed
Move more stuff out of Scene
1 parent 7af7998 commit c74256c

File tree

4 files changed

+135
-108
lines changed

4 files changed

+135
-108
lines changed

manim/renderer/cairo_renderer.py

Lines changed: 124 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,108 @@
11
import numpy as np
2-
from .. import file_writer_config
2+
from .. import camera_config, file_writer_config, logger
33
from ..utils.iterables import list_update
44
from ..utils.exceptions import EndSceneEarlyException
5+
from ..utils.hashing import get_hash_from_play_call, get_hash_from_wait_call
6+
from ..constants import DEFAULT_WAIT_TIME
7+
8+
9+
def handle_caching_play(func):
10+
"""
11+
Decorator that returns a wrapped version of func that will compute the hash of the play invocation.
12+
13+
The returned function will act according to the computed hash: either skip the animation because it's already cached, or let the invoked function play normally.
14+
15+
Parameters
16+
----------
17+
func : Callable[[...], None]
18+
The play like function that has to be written to the video file stream. Take the same parameters as `scene.play`.
19+
"""
20+
21+
def wrapper(self, *args, **kwargs):
22+
self.revert_to_original_skipping_status()
23+
self.update_skipping_status()
24+
animations = self.scene.compile_play_args_to_animation_list(*args, **kwargs)
25+
self.scene.add_mobjects_from_animations(animations)
26+
if file_writer_config["skip_animations"]:
27+
logger.debug(f"Skipping animation {self.num_plays}")
28+
func(self, *args, **kwargs)
29+
return
30+
if not file_writer_config["disable_caching"]:
31+
mobjects_on_scene = self.scene.get_mobjects()
32+
hash_play = get_hash_from_play_call(
33+
self, self.camera, animations, mobjects_on_scene
34+
)
35+
self.play_hashes_list.append(hash_play)
36+
if self.file_writer.is_already_cached(hash_play):
37+
logger.info(
38+
f"Animation {self.num_plays} : Using cached data (hash : %(hash_play)s)",
39+
{"hash_play": hash_play},
40+
)
41+
file_writer_config["skip_animations"] = True
42+
else:
43+
hash_play = "uncached_{:05}".format(self.num_plays)
44+
self.play_hashes_list.append(hash_play)
45+
func(self, *args, **kwargs)
46+
47+
return wrapper
48+
49+
50+
def handle_play_like_call(func):
51+
"""
52+
This method is used internally to wrap the
53+
passed function, into a function that
54+
actually writes to the video stream.
55+
Simultaneously, it also adds to the number
56+
of animations played.
57+
58+
Parameters
59+
----------
60+
func : function
61+
The play() like function that has to be
62+
written to the video file stream.
63+
64+
Returns
65+
-------
66+
function
67+
The play() like function that can now write
68+
to the video file stream.
69+
"""
70+
71+
def wrapper(self, *args, **kwargs):
72+
allow_write = not file_writer_config["skip_animations"]
73+
self.file_writer.begin_animation(allow_write)
74+
func(self, *args, **kwargs)
75+
self.file_writer.end_animation(allow_write)
76+
self.num_plays += 1
77+
78+
return wrapper
579

680

781
class CairoRenderer:
82+
"""A renderer using Cairo.
83+
84+
num_plays : Number of play() functions in the scene.
85+
time: time elapsed since initialisation of scene.
86+
"""
87+
888
def __init__(self, scene, camera, file_writer):
9-
self.scene = scene
1089
self.camera = camera
1190
self.file_writer = file_writer
91+
self.scene = scene
92+
self.original_skipping_status = file_writer_config["skip_animations"]
93+
self.play_hashes_list = []
94+
self.num_plays = 0
95+
self.time = 0
96+
97+
@handle_caching_play
98+
@handle_play_like_call
99+
def play(self, *args, **kwargs):
100+
self.scene.play_internal(*args, **kwargs)
101+
102+
@handle_caching_play
103+
@handle_play_like_call
104+
def wait(self, duration=DEFAULT_WAIT_TIME, stop_condition=None):
105+
self.scene.wait_internal(duration=duration, stop_condition=stop_condition)
12106

13107
def update_frame( # TODO Description in Docstring
14108
self,
@@ -74,12 +168,20 @@ def add_frame(self, frame, num_frames=1):
74168
The number of times to add frame.
75169
"""
76170
dt = 1 / self.camera.frame_rate
77-
self.scene.increment_time(num_frames * dt)
171+
self.time += num_frames * dt
78172
if file_writer_config["skip_animations"]:
79173
return
80174
for _ in range(num_frames):
81175
self.file_writer.write_frame(frame)
82176

177+
def show_frame(self):
178+
"""
179+
Opens the current frame in the Default Image Viewer
180+
of your system.
181+
"""
182+
self.update_frame(ignore_skipping=True)
183+
self.camera.get_image().show()
184+
83185
def update_skipping_status(self):
84186
"""
85187
This method is used internally to check if the current
@@ -89,17 +191,29 @@ def update_skipping_status(self):
89191
raises an EndSceneEarlyException if they don't correspond.
90192
"""
91193
if file_writer_config["from_animation_number"]:
92-
if self.scene.num_plays < file_writer_config["from_animation_number"]:
194+
if self.num_plays < file_writer_config["from_animation_number"]:
93195
file_writer_config["skip_animations"] = True
94196
if file_writer_config["upto_animation_number"]:
95-
if self.scene.num_plays > file_writer_config["upto_animation_number"]:
197+
if self.num_plays > file_writer_config["upto_animation_number"]:
96198
file_writer_config["skip_animations"] = True
97199
raise EndSceneEarlyException()
98200

99-
def show_frame(self):
201+
def revert_to_original_skipping_status(self):
100202
"""
101-
Opens the current frame in the Default Image Viewer
102-
of your system.
203+
Forces the scene to go back to its original skipping status,
204+
by setting skip_animations to whatever it reads
205+
from original_skipping_status.
206+
207+
Returns
208+
-------
209+
Scene
210+
The Scene, with the original skipping status.
103211
"""
104-
self.update_frame(ignore_skipping=True)
105-
self.camera.get_image().show()
212+
if hasattr(self, "original_skipping_status"):
213+
file_writer_config["skip_animations"] = self.original_skipping_status
214+
return self
215+
216+
def finish(self):
217+
file_writer_config["skip_animations"] = False
218+
self.file_writer.finish()
219+
logger.info(f"Rendered {str(self.scene)}\nPlayed {self.num_plays} animations")

manim/scene/scene.py

Lines changed: 7 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,6 @@ def construct(self):
5252
file_writer : The object that writes the animations in the scene to a video file.
5353
mobjects : The list of mobjects present in the scene.
5454
foreground_mobjects : List of mobjects explicitly in the foreground.
55-
num_plays : Number of play() functions in the scene.
56-
time: time elapsed since initialisation of scene.
5755
random_seed: The seed with which all random operations are done.
5856
5957
"""
@@ -73,15 +71,10 @@ def __init__(self, **kwargs):
7371
**file_writer_config,
7472
)
7573
self.renderer = CairoRenderer(self, self.camera, self.file_writer)
76-
self.play_hashes_list = []
7774

7875
self.mobjects = []
79-
self.original_skipping_status = file_writer_config["skip_animations"]
8076
# TODO, remove need for foreground mobjects
8177
self.foreground_mobjects = []
82-
self.num_plays = 0
83-
self.time = 0
84-
self.original_skipping_status = file_writer_config["skip_animations"]
8578
if self.random_seed is not None:
8679
random.seed(self.random_seed)
8780
np.random.seed(self.random_seed)
@@ -96,13 +89,8 @@ def render(self):
9689
self.construct()
9790
except EndSceneEarlyException:
9891
pass
99-
10092
self.tear_down()
101-
# We have to reset these settings in case of multiple renders.
102-
file_writer_config["skip_animations"] = False
103-
104-
self.file_writer.finish()
105-
self.print_end_message()
93+
self.renderer.finish()
10694

10795
def setup(self):
10896
"""
@@ -130,15 +118,6 @@ def construct(self):
130118
def __str__(self):
131119
return self.__class__.__name__
132120

133-
def print_end_message(self):
134-
"""
135-
Used internally to print the number of
136-
animations played after the scene ends,
137-
as well as the name of the scene rendered
138-
(useful when using the `-a` option).
139-
"""
140-
logger.info(f"Rendered {str(self)}\nPlayed {self.num_plays} animations")
141-
142121
def set_variables_as_attrs(self, *objects, **newly_named_objects):
143122
"""
144123
This method is slightly hacky, making it a little easier
@@ -199,18 +178,6 @@ def should_update_mobjects(self):
199178

200179
###
201180

202-
def increment_time(self, d_time):
203-
"""
204-
Increments the time elapsed after intialisation of scene by
205-
passed "d_time".
206-
207-
Parameters
208-
----------
209-
d_time : int or float
210-
Time in seconds to increment by.
211-
"""
212-
self.time += d_time
213-
214181
###
215182

216183
def get_top_level_mobjects(self):
@@ -646,7 +613,7 @@ def get_animation_time_progression(self, animations):
646613
time_progression.set_description(
647614
"".join(
648615
[
649-
"Animation {}: ".format(self.num_plays),
616+
"Animation {}: ".format(self.renderer.num_plays),
650617
str(animations[0]),
651618
(", etc." if len(animations) > 1 else ""),
652619
]
@@ -799,66 +766,10 @@ def finish_animations(self, animations):
799766
self.update_mobjects(0)
800767

801768
def wait(self, duration=DEFAULT_WAIT_TIME, stop_condition=None):
802-
self.play(duration=duration, stop_condition=stop_condition)
769+
self.renderer.wait(duration=duration, stop_condition=stop_condition)
803770

804771
def play(self, *args, **kwargs):
805-
self.cached_play(*args, **kwargs)
806-
self.num_plays += 1
807-
808-
def revert_to_original_skipping_status(self):
809-
"""
810-
Forces the scene to go back to its original skipping status,
811-
by setting skip_animations to whatever it reads
812-
from original_skipping_status.
813-
814-
Returns
815-
-------
816-
Scene
817-
The Scene, with the original skipping status.
818-
"""
819-
if hasattr(self, "original_skipping_status"):
820-
file_writer_config["skip_animations"] = self.original_skipping_status
821-
return self
822-
823-
def cached_play(self, *args, **kwargs):
824-
self.revert_to_original_skipping_status()
825-
self.renderer.update_skipping_status()
826-
animations = self.compile_play_args_to_animation_list(*args, **kwargs)
827-
self.add_mobjects_from_animations(animations)
828-
if file_writer_config["skip_animations"]:
829-
logger.debug(f"Skipping animation {self.num_plays}")
830-
self.file_writer_wrapped_play(*args, **kwargs)
831-
return
832-
if not file_writer_config["disable_caching"]:
833-
mobjects_on_scene = self.get_mobjects()
834-
hash_play = get_hash_from_play_call(
835-
self, self.camera, animations, mobjects_on_scene
836-
)
837-
self.play_hashes_list.append(hash_play)
838-
if self.file_writer.is_already_cached(hash_play):
839-
logger.info(
840-
f"Animation {self.num_plays} : Using cached data (hash : %(hash_play)s)",
841-
{"hash_play": hash_play},
842-
)
843-
file_writer_config["skip_animations"] = True
844-
else:
845-
hash_play = "uncached_{:05}".format(self.num_plays)
846-
self.play_hashes_list.append(hash_play)
847-
self.file_writer_wrapped_play(*args, **kwargs)
848-
849-
def file_writer_wrapped_play(self, *args, **kwargs):
850-
allow_write = not file_writer_config["skip_animations"]
851-
self.file_writer.begin_animation(allow_write)
852-
853-
self.play_or_wait(*args, **kwargs)
854-
855-
self.file_writer.end_animation(allow_write)
856-
857-
def play_or_wait(self, *args, **kwargs):
858-
if "duration" in kwargs:
859-
self.wait_internal(**kwargs)
860-
else:
861-
self.play_internal(*args, **kwargs)
772+
self.renderer.play(*args, **kwargs)
862773

863774
def play_internal(self, *args, **kwargs):
864775
"""
@@ -969,7 +880,9 @@ def get_wait_time_progression(self, duration, stop_condition):
969880
)
970881
else:
971882
time_progression = self.get_time_progression(duration)
972-
time_progression.set_description("Waiting {}".format(self.num_plays))
883+
time_progression.set_description(
884+
"Waiting {}".format(self.renderer.num_plays)
885+
)
973886
return time_progression
974887

975888
def wait_until(self, stop_condition, max_time=60):

manim/scene/scene_file_writer.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ def get_next_partial_movie_path(self):
202202
result = os.path.join(
203203
self.partial_movie_directory,
204204
"{}{}".format(
205-
self.scene.play_hashes_list[self.index_partial_movie_file],
205+
self.scene.renderer.play_hashes_list[self.index_partial_movie_file],
206206
file_writer_config["movie_file_extension"],
207207
),
208208
)
@@ -461,7 +461,7 @@ def close_movie_pipe(self):
461461
self.partial_movie_file_path,
462462
)
463463
logger.info(
464-
f"Animation {self.scene.num_plays} : Partial movie file written in %(path)s",
464+
f"Animation {self.scene.renderer.num_plays} : Partial movie file written in %(path)s",
465465
{"path": {self.partial_movie_file_path}},
466466
)
467467

@@ -502,7 +502,7 @@ def combine_movie_files(self):
502502
self.partial_movie_directory,
503503
"{}{}".format(hash_play, file_writer_config["movie_file_extension"]),
504504
)
505-
for hash_play in self.scene.play_hashes_list
505+
for hash_play in self.scene.renderer.play_hashes_list
506506
]
507507
if len(partial_movie_files) == 0:
508508
logger.error("No animations in this scene")

tests/control_data/logs_data/BasicSceneLoggingTest.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33
{"levelname": "DEBUG", "module": "hashing", "message": "Hashing done in <> s."}
44
{"levelname": "INFO", "module": "scene_file_writer", "message": "Animation 0 : Partial movie file written in <>"}
55
{"levelname": "INFO", "module": "scene_file_writer", "message": "\nFile ready at <>\n"}
6-
{"levelname": "INFO", "module": "scene", "message": "Rendered SquareToCircle\nPlayed 1 animations"}
6+
{"levelname": "INFO", "module": "cairo_renderer", "message": "Rendered SquareToCircle\nPlayed 1 animations"}

0 commit comments

Comments
 (0)