Skip to content

Commit 32ab690

Browse files
Rapsssitojeertmanspre-commit-ci[bot]
authored
feat(lib): add skip_animations compatibility (#516)
* feat: Add skip_animations compatibility * Add tests, config and changelog * chore(fmt): auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update manim_slides/slide/base.py Co-authored-by: Jérome Eertmans <[email protected]> * chore(fmt): auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * chore(tests): implement tests --------- Co-authored-by: Jérome Eertmans <[email protected]> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent df31345 commit 32ab690

File tree

6 files changed

+107
-5
lines changed

6 files changed

+107
-5
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
(unreleased)=
1111
## [Unreleased](https://github.com/jeertmans/manim-slides/compare/v5.3.1...HEAD)
1212

13+
(unreleased-added)=
14+
### Added
15+
16+
- Added `skip_animations` compatibility with ManimCE.
17+
[@Rapsssito](https://github.com/Rapsssito) [#516](https://github.com/jeertmans/manim-slides/pull/516)
18+
1319
(v5.3.1)=
1420
## [v5.3.1](https://github.com/jeertmans/manim-slides/compare/v5.3.0...v5.3.1)
1521

manim_slides/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ class BaseSlideConfig(BaseModel): # type: ignore
160160
reversed_playback_rate: float = 1.0
161161
notes: str = ""
162162
dedent_notes: bool = True
163+
skip_animations: bool = False
163164

164165
@classmethod
165166
def wrapper(cls, arg_name: str) -> Callable[..., Any]:

manim_slides/slide/base.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ def wait_time_between_slides(self, wait_time: float) -> None:
277277
self._wait_time_between_slides = max(wait_time, 0.0)
278278

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

@@ -299,6 +299,11 @@ def next_slide(
299299
Positional arguments passed to
300300
:meth:`Scene.next_section<manim.scene.scene.Scene.next_section>`,
301301
or ignored if `manimlib` API is used.
302+
:param skip_animations:
303+
Exclude the next slide from the output.
304+
305+
If `manim` is used, this is also passed to `:meth:`Scene.next_section<manim.scene.scene.Scene.next_section>`,
306+
which will avoid rendering the corresponding animations.
302307
:param loop:
303308
If set, next slide will be looping.
304309
:param auto_next:
@@ -521,9 +526,16 @@ def _save_slides(
521526
ascii=True if platform.system() == "Windows" else None,
522527
disable=not self._show_progress_bar,
523528
):
529+
if pre_slide_config.skip_animations:
530+
continue
524531
slide_files = files[pre_slide_config.slides_slice]
525532

526-
file = merge_basenames(slide_files)
533+
try:
534+
file = merge_basenames(slide_files)
535+
except ValueError as e:
536+
raise ValueError(
537+
f"Failed to merge basenames of files for slide: {pre_slide_config!r}"
538+
) from e
527539
dst_file = scene_files_folder / file.name
528540
rev_file = scene_files_folder / f"{file.stem}_reversed{file.suffix}"
529541

manim_slides/slide/manim.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,15 @@ def _leave_progress_bar(self) -> bool:
8989
def _start_at_animation_number(self) -> Optional[int]:
9090
return config["from_animation_number"] # type: ignore
9191

92+
def play(self, *args: Any, **kwargs: Any) -> None:
93+
"""Overload 'self.play' and increment animation count."""
94+
super().play(*args, **kwargs)
95+
96+
if self._base_slide_config.skip_animations:
97+
# Manim will not render the animations, so we reset the animation
98+
# counter to the previous value
99+
self._current_animation -= 1
100+
92101
def next_section(self, *args: Any, **kwargs: Any) -> None:
93102
"""
94103
Alias to :meth:`next_slide`.
@@ -111,7 +120,9 @@ def next_slide(
111120
base_slide_config: BaseSlideConfig,
112121
**kwargs: Any,
113122
) -> None:
114-
Scene.next_section(self, *args, **kwargs)
123+
Scene.next_section(
124+
self, *args, skip_animations=base_slide_config.skip_animations, **kwargs
125+
)
115126
BaseSlide.next_slide.__wrapped__(
116127
self,
117128
base_slide_config=base_slide_config,

tests/test_slide.py

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1+
import contextlib
2+
import os
13
import random
24
import shutil
35
import sys
6+
import tempfile
7+
import threading
8+
from collections.abc import Iterator
49
from pathlib import Path
510
from typing import Any, Union
611

@@ -17,6 +22,7 @@
1722
Dot,
1823
FadeIn,
1924
GrowFromCenter,
25+
Square,
2026
Text,
2127
)
2228
from manim.renderer.opengl_renderer import OpenGLRenderer
@@ -229,8 +235,26 @@ def assert_constructs(cls: SlideType) -> None:
229235
init_slide(cls).construct()
230236

231237

238+
_L = (
239+
threading.Lock()
240+
) # We cannot change directory multiple times at once (in the same thread)
241+
242+
243+
@contextlib.contextmanager
244+
def tmp_cwd() -> Iterator[str]:
245+
old_cwd = os.getcwd()
246+
247+
with tempfile.TemporaryDirectory() as tmp_dir, _L:
248+
try:
249+
os.chdir(tmp_dir)
250+
yield tmp_dir
251+
finally:
252+
os.chdir(old_cwd)
253+
254+
232255
def assert_renders(cls: SlideType) -> None:
233-
init_slide(cls).render()
256+
with tmp_cwd():
257+
init_slide(cls).render()
234258

235259

236260
class TestSlide:
@@ -479,6 +503,53 @@ def construct(self) -> None:
479503
self.next_slide()
480504
assert self._current_slide == 2
481505

506+
def test_next_slide_skip_animations(self) -> None:
507+
class Foo(CESlide):
508+
def construct(self) -> None:
509+
circle = Circle(color=BLUE)
510+
self.play(GrowFromCenter(circle))
511+
assert not self._base_slide_config.skip_animations
512+
self.next_slide(skip_animations=True)
513+
square = Square(color=BLUE)
514+
self.play(GrowFromCenter(square))
515+
assert self._base_slide_config.skip_animations
516+
self.next_slide()
517+
assert not self._base_slide_config.skip_animations
518+
self.play(GrowFromCenter(square))
519+
520+
class Bar(CESlide):
521+
def construct(self) -> None:
522+
circle = Circle(color=BLUE)
523+
self.play(GrowFromCenter(circle))
524+
assert not self._base_slide_config.skip_animations
525+
self.next_slide(skip_animations=False)
526+
square = Square(color=BLUE)
527+
self.play(GrowFromCenter(square))
528+
assert not self._base_slide_config.skip_animations
529+
self.next_slide()
530+
assert not self._base_slide_config.skip_animations
531+
self.play(GrowFromCenter(square))
532+
533+
with tmp_cwd() as tmp_dir:
534+
init_slide(Foo).render()
535+
init_slide(Bar).render()
536+
537+
slides_folder = Path(tmp_dir) / "slides"
538+
539+
assert slides_folder.exists()
540+
541+
slide_file = slides_folder / "Foo.json"
542+
543+
config = PresentationConfig.from_file(slide_file)
544+
545+
assert len(config.slides) == 2
546+
547+
slide_file = slides_folder / "Bar.json"
548+
549+
config = PresentationConfig.from_file(slide_file)
550+
551+
assert len(config.slides) == 3
552+
482553
def test_canvas(self) -> None:
483554
@assert_constructs
484555
class _(CESlide):

uv.lock

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)