mirror of
https://github.com/jeertmans/manim-slides.git
synced 2025-05-30 05:06:39 +08:00
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 <jeertmans@icloud.com> * 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 <jeertmans@icloud.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
@ -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)
|
||||
|
||||
|
@ -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]:
|
||||
|
@ -277,7 +277,7 @@ class BaseSlide:
|
||||
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
|
||||
|
||||
@ -299,6 +299,11 @@ class BaseSlide:
|
||||
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:
|
||||
@ -521,9 +526,16 @@ class BaseSlide:
|
||||
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]
|
||||
|
||||
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}"
|
||||
|
||||
|
@ -89,6 +89,15 @@ class Slide(BaseSlide, Scene): # type: ignore[misc]
|
||||
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`.
|
||||
@ -111,7 +120,9 @@ class Slide(BaseSlide, Scene): # type: ignore[misc]
|
||||
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,
|
||||
|
@ -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
|
||||
|
||||
@ -17,6 +22,7 @@ from manim import (
|
||||
Dot,
|
||||
FadeIn,
|
||||
GrowFromCenter,
|
||||
Square,
|
||||
Text,
|
||||
)
|
||||
from manim.renderer.opengl_renderer import OpenGLRenderer
|
||||
@ -229,7 +235,25 @@ 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:
|
||||
with tmp_cwd():
|
||||
init_slide(cls).render()
|
||||
|
||||
|
||||
@ -479,6 +503,53 @@ class TestSlide:
|
||||
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):
|
||||
|
3
uv.lock
generated
3
uv.lock
generated
@ -1482,7 +1482,6 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "manim-slides"
|
||||
version = "5.3.1"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "av" },
|
||||
@ -2762,6 +2761,8 @@ name = "pyqt6-qt6"
|
||||
version = "6.8.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/19/b89eb6cecbdf1e65a44658a083693a967e9d428370026711b624e928a8ca/PyQt6_Qt6-6.8.1-1-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:2f4b8b55b1414b93f340f22e8c88d25550efcdebc4b65a3927dd947b73bd4358", size = 80877444 },
|
||||
{ url = "https://files.pythonhosted.org/packages/87/1b/94d3710ee7ef93ee99c1dac512f631de5e310f6b21e43f474ef269f840b6/PyQt6_Qt6-6.8.1-1-py3-none-manylinux_2_39_aarch64.whl", hash = "sha256:98aa99fe38ae68c5318284cd28f3479ba538c40bf6ece293980abae0925c1b24", size = 79473622 },
|
||||
{ url = "https://files.pythonhosted.org/packages/df/0a/c47a1cc272b418faff8af79b121f0cecd32b09d634253254e3a990432220/PyQt6_Qt6-6.8.1-py3-none-macosx_10_14_x86_64.whl", hash = "sha256:1eb8460a1fdb38d0b2458c2974c01d471c1e59e4eb19ea63fc447aaba3ad530e", size = 65754939 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/e6/cc4fbc97a7d0955185e33add3ce00480f0023424d17ac6f864a504f60251/PyQt6_Qt6-6.8.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:9f3790c4ce4dc576e48b8718d55fb8743057e6cbd53a6ca1dd253ffbac9b7287", size = 59956028 },
|
||||
{ url = "https://files.pythonhosted.org/packages/01/22/c2997fe76d765d9ba960e9a099238cb419a316362bdde50fedacc23e7c7d/PyQt6_Qt6-6.8.1-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:d6ca5d2b9d2ec0ee4a814b2175f641a5c4299cb80b45e0f5f8356632663f89b3", size = 72561636 },
|
||||
|
Reference in New Issue
Block a user