mirror of
https://github.com/3b1b/manim.git
synced 2025-08-05 16:49:03 +00:00

* Remove print("Reloading...") * Change where exception mode is set, to be quieter * Add default fallback monitor for when no monitors are detected * Have StringMobject work with svg strings rather than necessarily writing to file Change SVGMobject to allow taking in a string of svg code as an input * Add caching functionality, and have Tex and Text both use it for saved svg strings * Clean up tex_file_writing * Get rid of get_tex_dir and get_text_dir * Allow for a configurable cache location * Make caching on disk a decorator, and update implementations for Tex and Text mobjects * Remove stray prints * Clean up how configuration is handled In principle, all we need here is that manim looks to the default_config.yaml file, and updates it based on any local configuration files, whether in the current working directory or as specified by a CLI argument. * Make the default size for hash_string an option * Remove utils/customization.py * Remove stray prints * Consolidate camera configuration This is still not optimal, but at least makes clearer the way that importing from constants.py kicks off some of the configuration code. * Factor out configuration to be passed into a scene vs. that used to run a scene * Use newer extract_scene.main interface * Add clarifying message to note what exactly is being reloaded * Minor clean up * Minor clean up * If it's worth caching to disk, then might as well do so in memory too during development * No longer any need for custom hash_seeds in Tex and Text * Remove display_during_execution * Get rid of (no longer used) mobject_data directory reference * Remove get_downloads_dir reference from register_font * Update where downloads go * Easier use of subdirectories in configuration * Add new pip requirements
124 lines
4 KiB
Python
124 lines
4 KiB
Python
import copy
|
|
import inspect
|
|
import sys
|
|
|
|
from manimlib.config import get_global_config
|
|
from manimlib.logger import log
|
|
from manimlib.scene.interactive_scene import InteractiveScene
|
|
from manimlib.scene.scene import Scene
|
|
|
|
|
|
class BlankScene(InteractiveScene):
|
|
def construct(self):
|
|
exec(get_global_config()["universal_import_line"])
|
|
self.embed()
|
|
|
|
|
|
def is_child_scene(obj, module):
|
|
if not inspect.isclass(obj):
|
|
return False
|
|
if not issubclass(obj, Scene):
|
|
return False
|
|
if obj == Scene:
|
|
return False
|
|
if not obj.__module__.startswith(module.__name__):
|
|
return False
|
|
return True
|
|
|
|
|
|
def prompt_user_for_choice(scene_classes):
|
|
name_to_class = {}
|
|
max_digits = len(str(len(scene_classes)))
|
|
for idx, scene_class in enumerate(scene_classes, start=1):
|
|
name = scene_class.__name__
|
|
print(f"{str(idx).zfill(max_digits)}: {name}")
|
|
name_to_class[name] = scene_class
|
|
try:
|
|
user_input = input(
|
|
"\nThat module has multiple scenes, " + \
|
|
"which ones would you like to render?" + \
|
|
"\nScene Name or Number: "
|
|
)
|
|
return [
|
|
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:
|
|
log.error("Invalid scene number")
|
|
sys.exit(2)
|
|
except KeyError:
|
|
log.error("Invalid scene name")
|
|
sys.exit(2)
|
|
except EOFError:
|
|
sys.exit(1)
|
|
|
|
|
|
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.
|
|
"""
|
|
pre_config = copy.deepcopy(scene_config)
|
|
pre_config["file_writer_config"]["write_to_movie"] = False
|
|
pre_config["file_writer_config"]["save_last_frame"] = False
|
|
pre_config["file_writer_config"]["quiet"] = True
|
|
pre_config["skip_animations"] = True
|
|
pre_scene = scene_class(**pre_config)
|
|
pre_scene.run()
|
|
total_time = pre_scene.time - pre_scene.skip_time
|
|
return int(total_time * scene_config["camera_config"]["fps"])
|
|
|
|
|
|
def scene_from_class(scene_class, scene_config, run_config):
|
|
fw_config = scene_config["file_writer_config"]
|
|
if fw_config["write_to_movie"] and run_config["prerun"]:
|
|
fw_config["total_frames"] = compute_total_frames(scene_class, scene_config)
|
|
return scene_class(**scene_config)
|
|
|
|
|
|
def get_scenes_to_render(all_scene_classes, scene_config, run_config):
|
|
if run_config["write_all"]:
|
|
return [sc(**scene_config) for sc in all_scene_classes]
|
|
|
|
names_to_classes = {sc.__name__: sc for sc in all_scene_classes}
|
|
scene_names = run_config["scene_names"]
|
|
|
|
for name in set.difference(set(scene_names), names_to_classes):
|
|
log.error(f"No scene named {name} found")
|
|
scene_names.remove(name)
|
|
|
|
if scene_names:
|
|
classes_to_run = [names_to_classes[name] for name in scene_names]
|
|
elif len(all_scene_classes) == 1:
|
|
classes_to_run = [all_scene_classes[0]]
|
|
else:
|
|
classes_to_run = prompt_user_for_choice(all_scene_classes)
|
|
|
|
return [
|
|
scene_from_class(scene_class, scene_config, run_config)
|
|
for scene_class in classes_to_run
|
|
]
|
|
|
|
|
|
def get_scene_classes_from_module(module):
|
|
if hasattr(module, "SCENES_IN_ORDER"):
|
|
return module.SCENES_IN_ORDER
|
|
else:
|
|
return [
|
|
member[1]
|
|
for member in inspect.getmembers(
|
|
module,
|
|
lambda x: is_child_scene(x, module)
|
|
)
|
|
]
|
|
|
|
|
|
def main(scene_config, run_config):
|
|
if run_config["module"] is None:
|
|
# If no module was passed in, just play the blank scene
|
|
return [BlankScene(**scene_config)]
|
|
|
|
all_scene_classes = get_scene_classes_from_module(run_config["module"])
|
|
return get_scenes_to_render(all_scene_classes, scene_config, run_config)
|