mirror of
https://github.com/3b1b/manim.git
synced 2025-04-13 09:47:07 +00:00
Create single progress display for full scene render
When a scene is written to file, it will now do a preliminary run of a copy of the scene with skip_animations turned on to count the total frames, which has the added benefit of catching runtime errors early, and allowing an quicker preview of the last frame to be sure everything will render as expected. The Progress display bars for individual animations are replaced with a more global progress display bar showing the full render time for the scene. This has the downside that all the non-rendering computations in a scene are run twice, so any scene with slow computations unrelated to rendering will take longer. But those are rarer, so the benefits seem worth it.
This commit is contained in:
parent
49743daf32
commit
9dd1f47dab
3 changed files with 69 additions and 31 deletions
|
@ -1,5 +1,6 @@
|
|||
import inspect
|
||||
import sys
|
||||
import copy
|
||||
|
||||
from manimlib.scene.scene import Scene
|
||||
from manimlib.config import get_custom_config
|
||||
|
@ -38,7 +39,7 @@ def prompt_user_for_choice(scene_classes):
|
|||
"\nScene Name or Number: "
|
||||
)
|
||||
return [
|
||||
name_to_class[split_str] if not split_str.isnumeric() else scene_classes[int(split_str)-1]
|
||||
name_to_class[split_str] if not split_str.isnumeric() else scene_classes[int(split_str) - 1]
|
||||
for split_str in user_input.replace(" ", "").split(",")
|
||||
]
|
||||
except IndexError:
|
||||
|
@ -67,6 +68,24 @@ def get_scene_config(config):
|
|||
])
|
||||
|
||||
|
||||
def compute_total_frames(scene_class, scene_config):
|
||||
"""
|
||||
When a scene is being written to file, a copy of the scene is run with
|
||||
skip_animations set to true so as to count how many frames it will require.
|
||||
This allows for a total progress bar on rendering, and also allows runtime
|
||||
errors to be exposed preemptively for long running scenes. The final frame
|
||||
is saved by default, so that one can more quickly check that the last frame
|
||||
looks as expected.
|
||||
"""
|
||||
pre_config = copy.deepcopy(scene_config)
|
||||
pre_config["file_writer_config"]["write_to_movie"] = False
|
||||
pre_config["file_writer_config"]["save_last_frame"] = True
|
||||
pre_config["skip_animations"] = True
|
||||
pre_scene = scene_class(**pre_config)
|
||||
pre_scene.run()
|
||||
return int(pre_scene.time * scene_config["camera_config"]["frame_rate"])
|
||||
|
||||
|
||||
def get_scenes_to_render(scene_classes, scene_config, config):
|
||||
if config["write_all"]:
|
||||
return [sc(**scene_config) for sc in scene_classes]
|
||||
|
@ -76,6 +95,9 @@ def get_scenes_to_render(scene_classes, scene_config, config):
|
|||
found = False
|
||||
for scene_class in scene_classes:
|
||||
if scene_class.__name__ == scene_name:
|
||||
fw_config = scene_config["file_writer_config"]
|
||||
if fw_config["write_to_movie"]:
|
||||
fw_config["total_frames"] = compute_total_frames(scene_class, scene_config)
|
||||
scene = scene_class(**scene_config)
|
||||
result.append(scene)
|
||||
found = True
|
||||
|
|
|
@ -282,48 +282,42 @@ class Scene(object):
|
|||
self.skip_time += self.time
|
||||
|
||||
# Methods associated with running animations
|
||||
def get_time_progression(self, run_time, n_iterations=None, override_skip_animations=False):
|
||||
def get_time_progression(self, run_time, n_iterations=None, desc="", override_skip_animations=False):
|
||||
if self.skip_animations and not override_skip_animations:
|
||||
times = [run_time]
|
||||
return [run_time]
|
||||
else:
|
||||
step = 1 / self.camera.frame_rate
|
||||
times = np.arange(0, run_time, step)
|
||||
time_progression = ProgressDisplay(
|
||||
|
||||
if self.file_writer.has_progress_display:
|
||||
self.file_writer.set_progress_display_subdescription(desc)
|
||||
return times
|
||||
|
||||
return ProgressDisplay(
|
||||
times,
|
||||
total=n_iterations,
|
||||
leave=self.leave_progress_bars,
|
||||
ascii=True if platform.system() == 'Windows' else None
|
||||
ascii=True if platform.system() == 'Windows' else None,
|
||||
desc=desc,
|
||||
)
|
||||
return time_progression
|
||||
|
||||
def get_run_time(self, animations):
|
||||
return np.max([animation.run_time for animation in animations])
|
||||
|
||||
def get_animation_time_progression(self, animations):
|
||||
run_time = self.get_run_time(animations)
|
||||
time_progression = self.get_time_progression(run_time)
|
||||
time_progression.set_description("".join([
|
||||
f"Animation {self.num_plays}: {animations[0]}",
|
||||
", etc." if len(animations) > 1 else "",
|
||||
]))
|
||||
description = f"{self.num_plays} {animations[0]}"
|
||||
if len(animations) > 1:
|
||||
description += ", etc."
|
||||
time_progression = self.get_time_progression(run_time, desc=description)
|
||||
return time_progression
|
||||
|
||||
def get_wait_time_progression(self, duration, stop_condition):
|
||||
def get_wait_time_progression(self, duration, stop_condition=None):
|
||||
kw = {"desc": f"{self.num_plays} Waiting"}
|
||||
if stop_condition is not None:
|
||||
time_progression = self.get_time_progression(
|
||||
duration,
|
||||
n_iterations=-1, # So it doesn't show % progress
|
||||
override_skip_animations=True
|
||||
)
|
||||
time_progression.set_description(
|
||||
"Waiting for {}".format(stop_condition.__name__)
|
||||
)
|
||||
else:
|
||||
time_progression = self.get_time_progression(duration)
|
||||
time_progression.set_description(
|
||||
"Waiting {}".format(self.num_plays)
|
||||
)
|
||||
return time_progression
|
||||
kw["n_iterations"] = -1 # So it doesn't show % progress
|
||||
kw["override_skip_animations"] = True
|
||||
return self.get_time_progression(duration, **kw)
|
||||
|
||||
def anims_from_play_args(self, *args, **kwargs):
|
||||
"""
|
||||
|
@ -488,13 +482,9 @@ class Scene(object):
|
|||
time_progression.close()
|
||||
break
|
||||
self.unlock_mobject_data()
|
||||
elif self.skip_animations:
|
||||
# Do nothing
|
||||
return self
|
||||
else:
|
||||
self.update_frame(duration)
|
||||
n_frames = int(duration * self.camera.frame_rate)
|
||||
for n in range(n_frames):
|
||||
for n in self.get_wait_time_progression(duration):
|
||||
self.emit_frame()
|
||||
return self
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import subprocess as sp
|
|||
import os
|
||||
import sys
|
||||
import platform
|
||||
from tqdm import tqdm as ProgressDisplay
|
||||
|
||||
from manimlib.constants import FFMPEG_BIN
|
||||
from manimlib.utils.config_ops import digest_config
|
||||
|
@ -35,12 +36,15 @@ class SceneFileWriter(object):
|
|||
"open_file_upon_completion": False,
|
||||
"show_file_location_upon_completion": False,
|
||||
"quiet": False,
|
||||
"total_frames": 0,
|
||||
"progress_description_len": 35,
|
||||
}
|
||||
|
||||
def __init__(self, scene, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
self.scene = scene
|
||||
self.writing_process = None
|
||||
self.has_progress_display = False
|
||||
self.init_output_directories()
|
||||
self.init_audio()
|
||||
|
||||
|
@ -205,15 +209,37 @@ class SceneFileWriter(object):
|
|||
command += [self.temp_file_path]
|
||||
self.writing_process = sp.Popen(command, stdin=sp.PIPE)
|
||||
|
||||
if self.total_frames > 0:
|
||||
self.progress_display = ProgressDisplay(
|
||||
range(self.total_frames),
|
||||
leave=False,
|
||||
ascii=True if platform.system() == 'Windows' else None,
|
||||
desc="Full render: "
|
||||
)
|
||||
self.has_progress_display = True
|
||||
|
||||
def set_progress_display_subdescription(self, desc):
|
||||
desc_len = self.progress_description_len
|
||||
full_desc = f"Full render ({desc})"
|
||||
if len(full_desc) > desc_len:
|
||||
full_desc = full_desc[:desc_len - 4] + "...)"
|
||||
else:
|
||||
full_desc += " " * (desc_len - len(full_desc))
|
||||
self.progress_display.set_description(full_desc)
|
||||
|
||||
def write_frame(self, camera):
|
||||
if self.write_to_movie:
|
||||
raw_bytes = camera.get_raw_fbo_data()
|
||||
self.writing_process.stdin.write(raw_bytes)
|
||||
if self.has_progress_display:
|
||||
self.progress_display.update()
|
||||
|
||||
def close_movie_pipe(self):
|
||||
self.writing_process.stdin.close()
|
||||
self.writing_process.wait()
|
||||
self.writing_process.terminate()
|
||||
if self.has_progress_display:
|
||||
self.progress_display.close()
|
||||
shutil.move(self.temp_file_path, self.final_file_path)
|
||||
|
||||
def combine_movie_files(self):
|
||||
|
|
Loading…
Add table
Reference in a new issue