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)=
|
||||||
## [Unreleased](https://github.com/jeertmans/manim-slides/compare/v5.3.1...HEAD)
|
## [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)=
|
||||||
## [v5.3.1](https://github.com/jeertmans/manim-slides/compare/v5.3.0...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
|
reversed_playback_rate: float = 1.0
|
||||||
notes: str = ""
|
notes: str = ""
|
||||||
dedent_notes: bool = True
|
dedent_notes: bool = True
|
||||||
|
skip_animations: bool = False
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def wrapper(cls, arg_name: str) -> Callable[..., Any]:
|
def wrapper(cls, arg_name: str) -> Callable[..., Any]:
|
||||||
|
@ -277,7 +277,7 @@ class BaseSlide:
|
|||||||
self._wait_time_between_slides = max(wait_time, 0.0)
|
self._wait_time_between_slides = max(wait_time, 0.0)
|
||||||
|
|
||||||
def play(self, *args: Any, **kwargs: Any) -> None:
|
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]
|
super().play(*args, **kwargs) # type: ignore[misc]
|
||||||
self._current_animation += 1
|
self._current_animation += 1
|
||||||
|
|
||||||
@ -299,6 +299,11 @@ class BaseSlide:
|
|||||||
Positional arguments passed to
|
Positional arguments passed to
|
||||||
:meth:`Scene.next_section<manim.scene.scene.Scene.next_section>`,
|
:meth:`Scene.next_section<manim.scene.scene.Scene.next_section>`,
|
||||||
or ignored if `manimlib` API is used.
|
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:
|
:param loop:
|
||||||
If set, next slide will be looping.
|
If set, next slide will be looping.
|
||||||
:param auto_next:
|
:param auto_next:
|
||||||
@ -521,9 +526,16 @@ class BaseSlide:
|
|||||||
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 pre_slide_config.skip_animations:
|
||||||
|
continue
|
||||||
slide_files = files[pre_slide_config.slides_slice]
|
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
|
dst_file = scene_files_folder / file.name
|
||||||
rev_file = scene_files_folder / f"{file.stem}_reversed{file.suffix}"
|
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]:
|
def _start_at_animation_number(self) -> Optional[int]:
|
||||||
return config["from_animation_number"] # type: ignore
|
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:
|
def next_section(self, *args: Any, **kwargs: Any) -> None:
|
||||||
"""
|
"""
|
||||||
Alias to :meth:`next_slide`.
|
Alias to :meth:`next_slide`.
|
||||||
@ -111,7 +120,9 @@ class Slide(BaseSlide, Scene): # type: ignore[misc]
|
|||||||
base_slide_config: BaseSlideConfig,
|
base_slide_config: BaseSlideConfig,
|
||||||
**kwargs: Any,
|
**kwargs: Any,
|
||||||
) -> None:
|
) -> None:
|
||||||
Scene.next_section(self, *args, **kwargs)
|
Scene.next_section(
|
||||||
|
self, *args, skip_animations=base_slide_config.skip_animations, **kwargs
|
||||||
|
)
|
||||||
BaseSlide.next_slide.__wrapped__(
|
BaseSlide.next_slide.__wrapped__(
|
||||||
self,
|
self,
|
||||||
base_slide_config=base_slide_config,
|
base_slide_config=base_slide_config,
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
|
import contextlib
|
||||||
|
import os
|
||||||
import random
|
import random
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import threading
|
||||||
|
from collections.abc import Iterator
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Union
|
from typing import Any, Union
|
||||||
|
|
||||||
@ -17,6 +22,7 @@ from manim import (
|
|||||||
Dot,
|
Dot,
|
||||||
FadeIn,
|
FadeIn,
|
||||||
GrowFromCenter,
|
GrowFromCenter,
|
||||||
|
Square,
|
||||||
Text,
|
Text,
|
||||||
)
|
)
|
||||||
from manim.renderer.opengl_renderer import OpenGLRenderer
|
from manim.renderer.opengl_renderer import OpenGLRenderer
|
||||||
@ -229,8 +235,26 @@ def assert_constructs(cls: SlideType) -> None:
|
|||||||
init_slide(cls).construct()
|
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:
|
def assert_renders(cls: SlideType) -> None:
|
||||||
init_slide(cls).render()
|
with tmp_cwd():
|
||||||
|
init_slide(cls).render()
|
||||||
|
|
||||||
|
|
||||||
class TestSlide:
|
class TestSlide:
|
||||||
@ -479,6 +503,53 @@ class TestSlide:
|
|||||||
self.next_slide()
|
self.next_slide()
|
||||||
assert self._current_slide == 2
|
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:
|
def test_canvas(self) -> None:
|
||||||
@assert_constructs
|
@assert_constructs
|
||||||
class _(CESlide):
|
class _(CESlide):
|
||||||
|
3
uv.lock
generated
3
uv.lock
generated
@ -1482,7 +1482,6 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "manim-slides"
|
name = "manim-slides"
|
||||||
version = "5.3.1"
|
|
||||||
source = { editable = "." }
|
source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "av" },
|
{ name = "av" },
|
||||||
@ -2762,6 +2761,8 @@ name = "pyqt6-qt6"
|
|||||||
version = "6.8.1"
|
version = "6.8.1"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
wheels = [
|
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/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/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 },
|
{ 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