mirror of
https://github.com/jeertmans/manim-slides.git
synced 2025-05-23 05:25:54 +08:00
feat(render): support skipping animations (#155)
* feat(render): support skipping animations Manim Slides now supports when Manim or ManimGL skip rendering some animations. All non-public attributes or methods are now named with leading __ * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
@ -47,15 +47,15 @@ class Slide(Scene): # type:ignore
|
|||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
self.output_folder = output_folder
|
self.__output_folder = output_folder
|
||||||
self.slides: List[SlideConfig] = []
|
self.__slides: List[SlideConfig] = []
|
||||||
self.current_slide = 1
|
self.__current_slide = 1
|
||||||
self.current_animation = 0
|
self.__current_animation = 0
|
||||||
self.loop_start_animation: Optional[int] = None
|
self.__loop_start_animation: Optional[int] = None
|
||||||
self.pause_start_animation = 0
|
self.__pause_start_animation = 0
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def partial_movie_files(self) -> List[str]:
|
def __partial_movie_files(self) -> List[str]:
|
||||||
"""Returns a list of partial movie files, a.k.a animations."""
|
"""Returns a list of partial movie files, a.k.a animations."""
|
||||||
if MANIMGL:
|
if MANIMGL:
|
||||||
from manimlib.utils.file_ops import get_sorted_integer_files
|
from manimlib.utils.file_ops import get_sorted_integer_files
|
||||||
@ -71,25 +71,32 @@ class Slide(Scene): # type:ignore
|
|||||||
return self.renderer.file_writer.partial_movie_files
|
return self.renderer.file_writer.partial_movie_files
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def show_progress_bar(self) -> bool:
|
def __show_progress_bar(self) -> bool:
|
||||||
"""Returns True if progress bar should be displayed."""
|
"""Returns True if progress bar should be displayed."""
|
||||||
if MANIMGL:
|
if MANIMGL:
|
||||||
return getattr(super(Scene, self), "show_progress_bar", True)
|
return getattr(self, "show_progress_bar", True)
|
||||||
else:
|
else:
|
||||||
return config["progress_bar"] != "none"
|
return config["progress_bar"] != "none"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def leave_progress_bar(self) -> bool:
|
def __leave_progress_bar(self) -> bool:
|
||||||
"""Returns True if progress bar should be left after completed."""
|
"""Returns True if progress bar should be left after completed."""
|
||||||
if MANIMGL:
|
if MANIMGL:
|
||||||
return getattr(super(Scene, self), "leave_progress_bars", False)
|
return getattr(self, "leave_progress_bars", False)
|
||||||
else:
|
else:
|
||||||
return config["progress_bar"] == "leave"
|
return config["progress_bar"] == "leave"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def __start_at_animation_number(self) -> Optional[int]:
|
||||||
|
if MANIMGL:
|
||||||
|
return getattr(self, "start_at_animation_number", None)
|
||||||
|
else:
|
||||||
|
return config["from_animation_number"]
|
||||||
|
|
||||||
def play(self, *args: Any, **kwargs: Any) -> None:
|
def play(self, *args: Any, **kwargs: Any) -> None:
|
||||||
"""Overloads `self.play` and increment animation count."""
|
"""Overloads `self.play` and increment animation count."""
|
||||||
super().play(*args, **kwargs)
|
super().play(*args, **kwargs)
|
||||||
self.current_animation += 1
|
self.__current_animation += 1
|
||||||
|
|
||||||
def next_slide(self):
|
def next_slide(self):
|
||||||
"""
|
"""
|
||||||
@ -133,19 +140,19 @@ class Slide(Scene): # type:ignore
|
|||||||
self.play(FadeOut(text))
|
self.play(FadeOut(text))
|
||||||
"""
|
"""
|
||||||
assert (
|
assert (
|
||||||
self.loop_start_animation is None
|
self.__loop_start_animation is None
|
||||||
), "You cannot call `self.next_slide()` inside a loop"
|
), "You cannot call `self.next_slide()` inside a loop"
|
||||||
|
|
||||||
self.slides.append(
|
self.__slides.append(
|
||||||
SlideConfig(
|
SlideConfig(
|
||||||
type=SlideType.slide,
|
type=SlideType.slide,
|
||||||
start_animation=self.pause_start_animation,
|
start_animation=self.__pause_start_animation,
|
||||||
end_animation=self.current_animation,
|
end_animation=self.__current_animation,
|
||||||
number=self.current_slide,
|
number=self.__current_slide,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.current_slide += 1
|
self.__current_slide += 1
|
||||||
self.pause_start_animation = self.current_animation
|
self.__pause_start_animation = self.__current_animation
|
||||||
|
|
||||||
def pause(self) -> None:
|
def pause(self) -> None:
|
||||||
"""
|
"""
|
||||||
@ -161,22 +168,22 @@ class Slide(Scene): # type:ignore
|
|||||||
)
|
)
|
||||||
self.next_slide()
|
self.next_slide()
|
||||||
|
|
||||||
def add_last_slide(self) -> None:
|
def __add_last_slide(self) -> None:
|
||||||
"""Adds a 'last' slide to the end of slides."""
|
"""Adds a 'last' slide to the end of slides."""
|
||||||
|
|
||||||
if (
|
if (
|
||||||
len(self.slides) > 0
|
len(self.__slides) > 0
|
||||||
and self.current_animation == self.slides[-1].end_animation
|
and self.__current_animation == self.__slides[-1].end_animation
|
||||||
):
|
):
|
||||||
self.slides[-1].type = SlideType.last
|
self.__slides[-1].type = SlideType.last
|
||||||
return
|
return
|
||||||
|
|
||||||
self.slides.append(
|
self.__slides.append(
|
||||||
SlideConfig(
|
SlideConfig(
|
||||||
type=SlideType.last,
|
type=SlideType.last,
|
||||||
start_animation=self.pause_start_animation,
|
start_animation=self.__pause_start_animation,
|
||||||
end_animation=self.current_animation,
|
end_animation=self.__current_animation,
|
||||||
number=self.current_slide,
|
number=self.__current_slide,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -207,42 +214,42 @@ class Slide(Scene): # type:ignore
|
|||||||
|
|
||||||
self.end_loop()
|
self.end_loop()
|
||||||
"""
|
"""
|
||||||
assert self.loop_start_animation is None, "You cannot nest loops"
|
assert self.__loop_start_animation is None, "You cannot nest loops"
|
||||||
self.loop_start_animation = self.current_animation
|
self.__loop_start_animation = self.__current_animation
|
||||||
|
|
||||||
def end_loop(self) -> None:
|
def end_loop(self) -> None:
|
||||||
"""Ends an existing loop. See :func:`start_loop` for more details."""
|
"""Ends an existing loop. See :func:`start_loop` for more details."""
|
||||||
assert (
|
assert (
|
||||||
self.loop_start_animation is not None
|
self.__loop_start_animation is not None
|
||||||
), "You have to start a loop before ending it"
|
), "You have to start a loop before ending it"
|
||||||
self.slides.append(
|
self.__slides.append(
|
||||||
SlideConfig(
|
SlideConfig(
|
||||||
type=SlideType.loop,
|
type=SlideType.loop,
|
||||||
start_animation=self.loop_start_animation,
|
start_animation=self.__loop_start_animation,
|
||||||
end_animation=self.current_animation,
|
end_animation=self.__current_animation,
|
||||||
number=self.current_slide,
|
number=self.__current_slide,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.current_slide += 1
|
self.__current_slide += 1
|
||||||
self.loop_start_animation = None
|
self.__loop_start_animation = None
|
||||||
self.pause_start_animation = self.current_animation
|
self.__pause_start_animation = self.__current_animation
|
||||||
|
|
||||||
def save_slides(self, use_cache: bool = True) -> None:
|
def __save_slides(self, use_cache: bool = True) -> None:
|
||||||
"""
|
"""
|
||||||
Saves slides, optionally using cached files.
|
Saves slides, optionally using cached files.
|
||||||
|
|
||||||
Note that cached files only work with Manim.
|
Note that cached files only work with Manim.
|
||||||
"""
|
"""
|
||||||
self.add_last_slide()
|
self.__add_last_slide()
|
||||||
|
|
||||||
if not os.path.exists(self.output_folder):
|
if not os.path.exists(self.__output_folder):
|
||||||
os.mkdir(self.output_folder)
|
os.mkdir(self.__output_folder)
|
||||||
|
|
||||||
files_folder = os.path.join(self.output_folder, "files")
|
files_folder = os.path.join(self.__output_folder, "files")
|
||||||
if not os.path.exists(files_folder):
|
if not os.path.exists(files_folder):
|
||||||
os.mkdir(files_folder)
|
os.mkdir(files_folder)
|
||||||
|
|
||||||
scene_name = type(self).__name__
|
scene_name = str(self)
|
||||||
scene_files_folder = os.path.join(files_folder, scene_name)
|
scene_files_folder = os.path.join(files_folder, scene_name)
|
||||||
|
|
||||||
old_animation_files = set()
|
old_animation_files = set()
|
||||||
@ -257,17 +264,17 @@ class Slide(Scene): # type:ignore
|
|||||||
|
|
||||||
files = []
|
files = []
|
||||||
for src_file in tqdm(
|
for src_file in tqdm(
|
||||||
self.partial_movie_files,
|
self.__partial_movie_files,
|
||||||
desc=f"Copying animation files to '{scene_files_folder}' and generating reversed animations",
|
desc=f"Copying animation files to '{scene_files_folder}' and generating reversed animations",
|
||||||
leave=self.leave_progress_bar,
|
leave=self.__leave_progress_bar,
|
||||||
ascii=True if platform.system() == "Windows" else None,
|
ascii=True if platform.system() == "Windows" else None,
|
||||||
disable=not self.show_progress_bar,
|
disable=not self.__show_progress_bar,
|
||||||
):
|
):
|
||||||
if src_file is None:
|
if src_file is None and not MANIMGL:
|
||||||
# This happens if rendering with -na,b
|
# This happens if rendering with -na,b (manim only)
|
||||||
# where animations not in [a,b] will be skipped
|
# where animations not in [a,b] will be skipped
|
||||||
# but animations before a will have a None src_file
|
# but animations before a will have a None src_file
|
||||||
raise ValueError("Skipping animation is currently not supported for Manim Slides")
|
continue
|
||||||
|
|
||||||
filename = os.path.basename(src_file)
|
filename = os.path.basename(src_file)
|
||||||
rev_filename = "{}_reversed{}".format(*os.path.splitext(filename))
|
rev_filename = "{}_reversed{}".format(*os.path.splitext(filename))
|
||||||
@ -288,14 +295,25 @@ class Slide(Scene): # type:ignore
|
|||||||
|
|
||||||
files.append(dst_file)
|
files.append(dst_file)
|
||||||
|
|
||||||
|
if offset := self.__start_at_animation_number:
|
||||||
|
self.__slides = [
|
||||||
|
slide for slide in self.__slides if slide.end_animation > offset
|
||||||
|
]
|
||||||
|
|
||||||
|
for slide in self.__slides:
|
||||||
|
slide.start_animation -= offset
|
||||||
|
slide.end_animation -= offset
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Copied {len(files)} animations to '{os.path.abspath(scene_files_folder)}' and generated reversed animations"
|
f"Copied {len(files)} animations to '{os.path.abspath(scene_files_folder)}' and generated reversed animations"
|
||||||
)
|
)
|
||||||
|
|
||||||
slide_path = os.path.join(self.output_folder, "%s.json" % (scene_name,))
|
slide_path = os.path.join(self.__output_folder, "%s.json" % (scene_name,))
|
||||||
|
|
||||||
with open(slide_path, "w") as f:
|
with open(slide_path, "w") as f:
|
||||||
f.write(PresentationConfig(slides=self.slides, files=files).json(indent=2))
|
f.write(
|
||||||
|
PresentationConfig(slides=self.__slides, files=files).json(indent=2)
|
||||||
|
)
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Slide '{scene_name}' configuration written in '{os.path.abspath(slide_path)}'"
|
f"Slide '{scene_name}' configuration written in '{os.path.abspath(slide_path)}'"
|
||||||
@ -304,11 +322,11 @@ class Slide(Scene): # type:ignore
|
|||||||
def run(self, *args: Any, **kwargs: Any) -> None:
|
def run(self, *args: Any, **kwargs: Any) -> None:
|
||||||
"""MANIMGL renderer"""
|
"""MANIMGL renderer"""
|
||||||
super().run(*args, **kwargs)
|
super().run(*args, **kwargs)
|
||||||
self.save_slides(use_cache=False)
|
self.__save_slides(use_cache=False)
|
||||||
|
|
||||||
def render(self, *args: Any, **kwargs: Any) -> None:
|
def render(self, *args: Any, **kwargs: Any) -> None:
|
||||||
"""MANIM render"""
|
"""MANIM render"""
|
||||||
# We need to disable the caching limit since we rely on intermidiate files
|
# We need to disable the caching limit since we rely on intermediate files
|
||||||
max_files_cached = config["max_files_cached"]
|
max_files_cached = config["max_files_cached"]
|
||||||
config["max_files_cached"] = float("inf")
|
config["max_files_cached"] = float("inf")
|
||||||
|
|
||||||
@ -316,7 +334,7 @@ class Slide(Scene): # type:ignore
|
|||||||
|
|
||||||
config["max_files_cached"] = max_files_cached
|
config["max_files_cached"] = max_files_cached
|
||||||
|
|
||||||
self.save_slides()
|
self.__save_slides()
|
||||||
|
|
||||||
|
|
||||||
class ThreeDSlide(Slide, ThreeDScene): # type: ignore
|
class ThreeDSlide(Slide, ThreeDScene): # type: ignore
|
||||||
|
Reference in New Issue
Block a user