Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(lib): add skip_animations compatibility #516

Merged
merged 8 commits into from
Jan 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
(unreleased)=
## [Unreleased](https://github.com/jeertmans/manim-slides/compare/v5.3.1...HEAD)

(unreleased-added)=
### Added

- Added `skip_animations` compatibility with ManimCE.
[@Rapsssito](https://github.com/Rapsssito) [#516](https://github.com/jeertmans/manim-slides/pull/516)

(v5.3.1)=
## [v5.3.1](https://github.com/jeertmans/manim-slides/compare/v5.3.0...v5.3.1)

Expand Down
1 change: 1 addition & 0 deletions manim_slides/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ class BaseSlideConfig(BaseModel): # type: ignore
reversed_playback_rate: float = 1.0
notes: str = ""
dedent_notes: bool = True
skip_animations: bool = False

@classmethod
def wrapper(cls, arg_name: str) -> Callable[..., Any]:
Expand Down
16 changes: 14 additions & 2 deletions manim_slides/slide/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ def wait_time_between_slides(self, wait_time: float) -> None:
self._wait_time_between_slides = max(wait_time, 0.0)

def play(self, *args: Any, **kwargs: Any) -> None:
"""Overload `self.play` and increment animation count."""
"""Overload 'self.play' and increment animation count."""
super().play(*args, **kwargs) # type: ignore[misc]
self._current_animation += 1

Expand All @@ -299,6 +299,11 @@ def next_slide(
Positional arguments passed to
:meth:`Scene.next_section<manim.scene.scene.Scene.next_section>`,
or ignored if `manimlib` API is used.
:param skip_animations:
Exclude the next slide from the output.

If `manim` is used, this is also passed to `:meth:`Scene.next_section<manim.scene.scene.Scene.next_section>`,
which will avoid rendering the corresponding animations.
:param loop:
If set, next slide will be looping.
:param auto_next:
Expand Down Expand Up @@ -521,9 +526,16 @@ def _save_slides(
ascii=True if platform.system() == "Windows" else None,
disable=not self._show_progress_bar,
):
if pre_slide_config.skip_animations:
continue
slide_files = files[pre_slide_config.slides_slice]

file = merge_basenames(slide_files)
try:
file = merge_basenames(slide_files)
except ValueError as e:
raise ValueError(
f"Failed to merge basenames of files for slide: {pre_slide_config!r}"
) from e
dst_file = scene_files_folder / file.name
rev_file = scene_files_folder / f"{file.stem}_reversed{file.suffix}"

Expand Down
13 changes: 12 additions & 1 deletion manim_slides/slide/manim.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,15 @@ def _leave_progress_bar(self) -> bool:
def _start_at_animation_number(self) -> Optional[int]:
return config["from_animation_number"] # type: ignore

def play(self, *args: Any, **kwargs: Any) -> None:
"""Overload 'self.play' and increment animation count."""
super().play(*args, **kwargs)

if self._base_slide_config.skip_animations:
# Manim will not render the animations, so we reset the animation
# counter to the previous value
self._current_animation -= 1

def next_section(self, *args: Any, **kwargs: Any) -> None:
"""
Alias to :meth:`next_slide`.
Expand All @@ -111,7 +120,9 @@ def next_slide(
base_slide_config: BaseSlideConfig,
**kwargs: Any,
) -> None:
Scene.next_section(self, *args, **kwargs)
Scene.next_section(
self, *args, skip_animations=base_slide_config.skip_animations, **kwargs
)
BaseSlide.next_slide.__wrapped__(
self,
base_slide_config=base_slide_config,
Expand Down
73 changes: 72 additions & 1 deletion tests/test_slide.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import contextlib
import os
import random
import shutil
import sys
import tempfile
import threading
from collections.abc import Iterator
from pathlib import Path
from typing import Any, Union

Expand All @@ -17,6 +22,7 @@
Dot,
FadeIn,
GrowFromCenter,
Square,
Text,
)
from manim.renderer.opengl_renderer import OpenGLRenderer
Expand Down Expand Up @@ -229,8 +235,26 @@ def assert_constructs(cls: SlideType) -> None:
init_slide(cls).construct()


_L = (
threading.Lock()
) # We cannot change directory multiple times at once (in the same thread)


@contextlib.contextmanager
def tmp_cwd() -> Iterator[str]:
old_cwd = os.getcwd()

with tempfile.TemporaryDirectory() as tmp_dir, _L:
try:
os.chdir(tmp_dir)
yield tmp_dir
finally:
os.chdir(old_cwd)


def assert_renders(cls: SlideType) -> None:
init_slide(cls).render()
with tmp_cwd():
init_slide(cls).render()


class TestSlide:
Expand Down Expand Up @@ -479,6 +503,53 @@ def construct(self) -> None:
self.next_slide()
assert self._current_slide == 2

def test_next_slide_skip_animations(self) -> None:
class Foo(CESlide):
def construct(self) -> None:
circle = Circle(color=BLUE)
self.play(GrowFromCenter(circle))
assert not self._base_slide_config.skip_animations
self.next_slide(skip_animations=True)
square = Square(color=BLUE)
self.play(GrowFromCenter(square))
assert self._base_slide_config.skip_animations
self.next_slide()
assert not self._base_slide_config.skip_animations
self.play(GrowFromCenter(square))

class Bar(CESlide):
def construct(self) -> None:
circle = Circle(color=BLUE)
self.play(GrowFromCenter(circle))
assert not self._base_slide_config.skip_animations
self.next_slide(skip_animations=False)
square = Square(color=BLUE)
self.play(GrowFromCenter(square))
assert not self._base_slide_config.skip_animations
self.next_slide()
assert not self._base_slide_config.skip_animations
self.play(GrowFromCenter(square))

with tmp_cwd() as tmp_dir:
init_slide(Foo).render()
init_slide(Bar).render()

slides_folder = Path(tmp_dir) / "slides"

assert slides_folder.exists()

slide_file = slides_folder / "Foo.json"

config = PresentationConfig.from_file(slide_file)

assert len(config.slides) == 2

slide_file = slides_folder / "Bar.json"

config = PresentationConfig.from_file(slide_file)

assert len(config.slides) == 3

def test_canvas(self) -> None:
@assert_constructs
class _(CESlide):
Expand Down
3 changes: 2 additions & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading