2022-04-12 19:19:59 +08:00
|
|
|
import copy
|
2018-03-31 15:11:35 -07:00
|
|
|
import inspect
|
2018-12-22 14:27:22 -08:00
|
|
|
import sys
|
2015-05-06 17:58:34 -07:00
|
|
|
|
2021-02-07 21:38:19 +08:00
|
|
|
from manimlib.config import get_custom_config
|
2021-10-07 17:37:10 +08:00
|
|
|
from manimlib.logger import log
|
2022-04-23 09:03:53 -07:00
|
|
|
from manimlib.scene.interactive_scene import InteractiveScene
|
2022-04-12 19:19:59 +08:00
|
|
|
from manimlib.scene.scene import Scene
|
2015-05-06 17:58:34 -07:00
|
|
|
|
2015-10-10 18:48:54 -07:00
|
|
|
|
2022-04-22 08:33:18 -07:00
|
|
|
class BlankScene(InteractiveScene):
|
2021-01-02 22:20:13 -08:00
|
|
|
def construct(self):
|
2021-02-07 21:38:19 +08:00
|
|
|
exec(get_custom_config()["universal_import_line"])
|
2021-01-02 22:20:13 -08:00
|
|
|
self.embed()
|
|
|
|
|
|
|
|
|
2018-12-25 19:51:03 -08:00
|
|
|
def is_child_scene(obj, module):
|
2018-04-06 13:58:59 -07:00
|
|
|
if not inspect.isclass(obj):
|
|
|
|
return False
|
|
|
|
if not issubclass(obj, Scene):
|
|
|
|
return False
|
|
|
|
if obj == Scene:
|
|
|
|
return False
|
2019-07-24 18:01:12 -05:00
|
|
|
if not obj.__module__.startswith(module.__name__):
|
|
|
|
return False
|
2018-04-06 13:58:59 -07:00
|
|
|
return True
|
|
|
|
|
2015-05-07 21:28:02 -07:00
|
|
|
|
2019-01-25 11:03:14 -08:00
|
|
|
def prompt_user_for_choice(scene_classes):
|
2021-01-02 22:20:13 -08:00
|
|
|
name_to_class = {}
|
2021-02-05 13:09:03 +05:30
|
|
|
max_digits = len(str(len(scene_classes)))
|
|
|
|
for idx, scene_class in enumerate(scene_classes, start=1):
|
2019-01-25 11:03:14 -08:00
|
|
|
name = scene_class.__name__
|
2021-02-05 13:09:03 +05:30
|
|
|
print(f"{str(idx).zfill(max_digits)}: {name}")
|
2021-01-02 22:20:13 -08:00
|
|
|
name_to_class[name] = scene_class
|
2018-04-06 13:58:59 -07:00
|
|
|
try:
|
2021-01-02 22:20:13 -08:00
|
|
|
user_input = input(
|
2022-12-18 10:50:34 -08:00
|
|
|
"\nThat module has multiple scenes, " + \
|
|
|
|
"which ones would you like to render?" + \
|
2021-02-05 13:09:03 +05:30
|
|
|
"\nScene Name or Number: "
|
2021-01-02 22:20:13 -08:00
|
|
|
)
|
2018-04-06 13:58:59 -07:00
|
|
|
return [
|
2021-11-30 11:41:33 -08:00
|
|
|
name_to_class[split_str] if not split_str.isnumeric() else scene_classes[int(split_str) - 1]
|
2021-02-05 13:09:03 +05:30
|
|
|
for split_str in user_input.replace(" ", "").split(",")
|
2018-04-06 13:58:59 -07:00
|
|
|
]
|
2021-10-07 17:37:10 +08:00
|
|
|
except IndexError:
|
|
|
|
log.error("Invalid scene number")
|
|
|
|
sys.exit(2)
|
2018-12-22 14:27:22 -08:00
|
|
|
except KeyError:
|
2021-10-07 17:37:10 +08:00
|
|
|
log.error("Invalid scene name")
|
2018-12-25 19:51:03 -08:00
|
|
|
sys.exit(2)
|
|
|
|
except EOFError:
|
|
|
|
sys.exit(1)
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2015-10-20 21:55:46 -07:00
|
|
|
|
2021-01-02 22:20:13 -08:00
|
|
|
def get_scene_config(config):
|
2022-12-29 14:36:50 -08:00
|
|
|
scene_parameters = inspect.signature(Scene).parameters.keys()
|
|
|
|
return {
|
|
|
|
key: config[key]
|
|
|
|
for key in set(scene_parameters).intersection(config.keys())
|
|
|
|
}
|
2021-01-02 22:03:00 -08:00
|
|
|
|
2023-01-08 10:15:22 -05:00
|
|
|
|
2021-11-30 11:41:33 -08:00
|
|
|
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
|
2023-01-04 16:36:25 -08:00
|
|
|
errors to be exposed preemptively for long running scenes.
|
2021-11-30 11:41:33 -08:00
|
|
|
"""
|
|
|
|
pre_config = copy.deepcopy(scene_config)
|
|
|
|
pre_config["file_writer_config"]["write_to_movie"] = False
|
2023-01-04 16:36:25 -08:00
|
|
|
pre_config["file_writer_config"]["save_last_frame"] = False
|
2021-12-07 10:07:15 -08:00
|
|
|
pre_config["file_writer_config"]["quiet"] = True
|
2021-11-30 11:41:33 -08:00
|
|
|
pre_config["skip_animations"] = True
|
|
|
|
pre_scene = scene_class(**pre_config)
|
|
|
|
pre_scene.run()
|
2021-12-07 10:07:15 -08:00
|
|
|
total_time = pre_scene.time - pre_scene.skip_time
|
2022-05-14 17:47:31 -07:00
|
|
|
return int(total_time * scene_config["camera_config"]["fps"])
|
2021-11-30 11:41:33 -08:00
|
|
|
|
|
|
|
|
2021-01-02 22:20:13 -08:00
|
|
|
def get_scenes_to_render(scene_classes, scene_config, config):
|
2018-04-06 13:58:59 -07:00
|
|
|
if config["write_all"]:
|
2021-01-02 22:20:13 -08:00
|
|
|
return [sc(**scene_config) for sc in scene_classes]
|
2020-02-11 19:52:14 -08:00
|
|
|
|
2019-01-25 11:03:14 -08:00
|
|
|
result = []
|
2019-01-12 12:31:29 -08:00
|
|
|
for scene_name in config["scene_names"]:
|
2019-01-25 11:03:14 -08:00
|
|
|
found = False
|
|
|
|
for scene_class in scene_classes:
|
|
|
|
if scene_class.__name__ == scene_name:
|
2021-11-30 11:41:33 -08:00
|
|
|
fw_config = scene_config["file_writer_config"]
|
|
|
|
if fw_config["write_to_movie"]:
|
|
|
|
fw_config["total_frames"] = compute_total_frames(scene_class, scene_config)
|
2021-01-02 22:20:13 -08:00
|
|
|
scene = scene_class(**scene_config)
|
2020-02-11 19:52:14 -08:00
|
|
|
result.append(scene)
|
2019-01-25 11:03:14 -08:00
|
|
|
found = True
|
|
|
|
break
|
|
|
|
if not found and (scene_name != ""):
|
2021-10-07 17:37:10 +08:00
|
|
|
log.error(f"No scene named {scene_name} found")
|
2019-01-25 11:03:14 -08:00
|
|
|
if result:
|
|
|
|
return result
|
2022-08-27 11:25:36 +08:00
|
|
|
|
|
|
|
# another case
|
|
|
|
result=[]
|
2021-01-02 22:20:13 -08:00
|
|
|
if len(scene_classes) == 1:
|
2022-08-27 11:25:36 +08:00
|
|
|
scene_classes = [scene_classes[0]]
|
2021-01-02 22:20:13 -08:00
|
|
|
else:
|
2022-08-27 11:25:36 +08:00
|
|
|
scene_classes = prompt_user_for_choice(scene_classes)
|
|
|
|
for scene_class in scene_classes:
|
|
|
|
fw_config = scene_config["file_writer_config"]
|
|
|
|
if fw_config["write_to_movie"]:
|
|
|
|
fw_config["total_frames"] = compute_total_frames(scene_class, scene_config)
|
2022-08-27 12:11:55 +08:00
|
|
|
scene = scene_class(**scene_config)
|
|
|
|
result.append(scene)
|
2022-08-27 11:25:36 +08:00
|
|
|
return result
|
2019-01-25 11:03:14 -08:00
|
|
|
|
|
|
|
|
|
|
|
def get_scene_classes_from_module(module):
|
2019-07-24 18:01:12 -05:00
|
|
|
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)
|
|
|
|
)
|
|
|
|
]
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2015-10-28 16:03:33 -07:00
|
|
|
|
2018-12-22 14:27:22 -08:00
|
|
|
def main(config):
|
2018-12-24 12:49:10 -08:00
|
|
|
module = config["module"]
|
2021-01-02 22:20:13 -08:00
|
|
|
scene_config = get_scene_config(config)
|
|
|
|
if module is None:
|
|
|
|
# If no module was passed in, just play the blank scene
|
|
|
|
return [BlankScene(**scene_config)]
|
|
|
|
|
2019-01-25 11:03:14 -08:00
|
|
|
all_scene_classes = get_scene_classes_from_module(module)
|
2021-01-02 22:20:13 -08:00
|
|
|
scenes = get_scenes_to_render(all_scene_classes, scene_config, config)
|
2020-02-11 19:52:14 -08:00
|
|
|
return scenes
|