diff --git a/CHANGELOG.md b/CHANGELOG.md index 10d64fa9..a0b365d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added `manim-slides checkhealth` command to easily obtain important information for debug purposes. [#458](https://github.com/jeertmans/manim-slides/pull/458) +- Added support for `disable_caching` and `flush_cache` options from Manim, and + also the possibility to configure them through class options. + [#452](https://github.com/jeertmans/manim-slides/pull/452) (unreleased-chore)= ### Chore diff --git a/manim_slides/slide/base.py b/manim_slides/slide/base.py index 60ed89c5..02e72cd6 100644 --- a/manim_slides/slide/base.py +++ b/manim_slides/slide/base.py @@ -33,6 +33,15 @@ class BaseSlide: + disable_caching = False + """Whether to disable the use of cached animation files.""" + flush_cache = False + """Whether to flush the cache. + + Unlike with Manim, flushing is performed before rendering.""" + skip_reversing: bool = False + """Whether to generate reversed animations.""" + def __init__( self, *args: Any, output_folder: Path = FOLDER_PATH, **kwargs: Any ) -> None: @@ -502,7 +511,7 @@ def _save_slides( # We only reverse video if it was not present if not use_cache or not rev_file.exists(): if skip_reversing: - rev_file.symlink_to(dst_file) + rev_file = dst_file else: reverse_video_file(dst_file, rev_file) diff --git a/manim_slides/slide/manim.py b/manim_slides/slide/manim.py index cdc79dca..f419d896 100644 --- a/manim_slides/slide/manim.py +++ b/manim_slides/slide/manim.py @@ -102,19 +102,30 @@ def next_slide( ) def render(self, *args: Any, **kwargs: Any) -> None: - """MANIM render.""" + """MANIM renderer.""" # We need to disable the caching limit since we rely on intermediate files max_files_cached = config["max_files_cached"] config["max_files_cached"] = float("inf") + flush_manim_cache = config["flush_cache"] + + if flush_manim_cache: + # We need to postpone flushing *after* we saved slides + config["flush_cache"] = False + super().render(*args, **kwargs) config["max_files_cached"] = max_files_cached self._save_slides( - use_cache=not config["disable_caching"], flush_cache=config["flush_cache"] + use_cache=not (config["disable_caching"] or self.disable_caching), + flush_cache=(config["flush_cache"] or self.flush_cache), + skip_reversing=self.skip_reversing, ) + if flush_manim_cache: + self.renderer.file_writer.flush_cache_directory() + class ThreeDSlide(Slide, ThreeDScene): # type: ignore[misc] """ diff --git a/manim_slides/slide/manimlib.py b/manim_slides/slide/manimlib.py index 0aba17e3..6b13344d 100644 --- a/manim_slides/slide/manimlib.py +++ b/manim_slides/slide/manimlib.py @@ -62,7 +62,11 @@ def _start_at_animation_number(self) -> Optional[int]: def run(self, *args: Any, **kwargs: Any) -> None: """MANIMGL renderer.""" super().run(*args, **kwargs) - self._save_slides(use_cache=False) + self._save_slides( + use_cache=False, + flush_cache=self.flush_cache, + skip_reversing=self.skip_reversing, + ) class ThreeDSlide(Slide): diff --git a/requirements-dev.lock b/requirements-dev.lock index 859eba85..f7c1f2a6 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -7,6 +7,7 @@ # all-features: true # with-sources: false # generate-hashes: false +# universal: false -e file:. alabaster==1.0.0 @@ -48,10 +49,16 @@ click-default-group==1.2.4 # via manim-slides cloup==3.0.5 # via manim +colour==0.1.5 + # via manimgl comm==0.2.2 # via ipykernel +contourpy==1.3.0 + # via matplotlib coverage==7.6.1 # via pytest-cov +cycler==0.12.1 + # via matplotlib debugpy==1.8.5 # via ipykernel decorator==5.1.1 @@ -73,6 +80,8 @@ fastjsonschema==2.20.0 # via nbformat filelock==3.15.4 # via virtualenv +fonttools==4.53.1 + # via matplotlib furo==2024.8.6 # via manim-slides glcontext==3.0.0 @@ -90,8 +99,10 @@ ipykernel==6.29.5 ipython==8.26.0 # via ipykernel # via manim-slides + # via manimgl isosurfaces==0.1.2 # via manim + # via manimgl jedi==0.19.1 # via ipython jinja2==3.1.4 @@ -115,16 +126,22 @@ jupyter-core==5.7.2 # via nbformat jupyterlab-pygments==0.3.0 # via nbconvert +kiwisolver==1.4.5 + # via matplotlib lxml==5.3.0 # via manim-slides # via python-pptx manim==0.18.1 # via manim-slides +manimgl==1.6.1 + # via manim-slides manimpango==0.5.0 # via --override (workspace) # via manim + # via manimgl mapbox-earcut==1.0.2 # via manim + # via manimgl markdown-it-py==3.0.0 # via mdit-py-plugins # via myst-parser @@ -132,6 +149,8 @@ markdown-it-py==3.0.0 markupsafe==2.1.5 # via jinja2 # via nbconvert +matplotlib==3.9.2 + # via manimgl matplotlib-inline==0.1.7 # via ipykernel # via ipython @@ -143,9 +162,13 @@ mistune==3.0.2 # via nbconvert moderngl==5.11.1 # via manim + # via manimgl # via moderngl-window moderngl-window==2.4.6 # via manim + # via manimgl +mpmath==1.3.0 + # via sympy multipledispatch==1.0.0 # via pyrr myst-parser==4.0.0 @@ -166,19 +189,21 @@ networkx==3.3 # via manim nodeenv==1.9.1 # via pre-commit -numpy==2.1.0 +numpy==1.24.0 # via --override (workspace) - # via ipython + # via contourpy # via isosurfaces # via manim # via manim-slides + # via manimgl # via mapbox-earcut + # via matplotlib # via moderngl-window - # via networkx # via pyrr # via scipy packaging==24.1 # via ipykernel + # via matplotlib # via nbconvert # via pytest # via qtpy @@ -194,6 +219,8 @@ pexpect==4.9.0 pillow==10.4.0 # via manim # via manim-slides + # via manimgl + # via matplotlib # via moderngl-window # via python-pptx platformdirs==4.2.2 @@ -231,15 +258,21 @@ pydantic-settings==2.4.0 # via bump-my-version pydub==0.25.1 # via manim + # via manimgl pyglet==2.0.17 # via moderngl-window pygments==2.18.0 # via furo # via ipython # via manim + # via manimgl # via nbconvert # via rich # via sphinx +pyopengl==3.1.7 + # via manimgl +pyparsing==3.1.4 + # via matplotlib pyqt6==6.7.1 # via manim-slides pyqt6-qt6==6.7.2 @@ -271,11 +304,13 @@ pytest-qt==4.4.0 # via manim-slides python-dateutil==2.9.0.post0 # via jupyter-client + # via matplotlib python-dotenv==1.0.1 # via pydantic-settings python-pptx==1.0.2 # via manim-slides pyyaml==6.0.2 + # via manimgl # via myst-parser # via pre-commit pyzmq==26.2.0 @@ -295,6 +330,7 @@ rich==13.7.1 # via bump-my-version # via manim # via manim-slides + # via manimgl # via rich-click rich-click==1.8.3 # via bump-my-version @@ -305,8 +341,10 @@ rtoml==0.11.0 # via manim-slides scipy==1.14.1 # via manim + # via manimgl screeninfo==0.8.1 # via manim + # via manimgl setuptools==73.0.1 shiboken6==6.7.2 # via pyside6 @@ -318,6 +356,7 @@ six==1.16.0 # via python-dateutil skia-pathops==0.8.0.post1 # via manim + # via manimgl snowballstemmer==2.2.0 # via sphinx soupsieve==2.6 @@ -357,6 +396,9 @@ stack-data==0.6.3 # via ipython svgelements==1.9.6 # via manim + # via manimgl +sympy==1.13.2 + # via manimgl tinycss2==1.3.0 # via nbconvert tomlkit==0.13.2 @@ -367,6 +409,7 @@ tornado==6.4.1 tqdm==4.66.5 # via manim # via manim-slides + # via manimgl traitlets==5.14.3 # via comm # via ipykernel @@ -379,6 +422,7 @@ traitlets==5.14.3 # via nbformat # via nbsphinx typing-extensions==4.12.2 + # via ipython # via manim # via pydantic # via pydantic-core @@ -386,6 +430,8 @@ typing-extensions==4.12.2 # via rich-click urllib3==2.2.2 # via requests +validators==0.33.0 + # via manimgl virtualenv==20.26.3 # via pre-commit watchdog==4.0.2 diff --git a/requirements.lock b/requirements.lock index a6f25ba1..986ac2f7 100644 --- a/requirements.lock +++ b/requirements.lock @@ -7,6 +7,7 @@ # all-features: true # with-sources: false # generate-hashes: false +# universal: false -e file:. alabaster==1.0.0 @@ -41,10 +42,16 @@ click-default-group==1.2.4 # via manim-slides cloup==3.0.5 # via manim +colour==0.1.5 + # via manimgl comm==0.2.2 # via ipykernel +contourpy==1.3.0 + # via matplotlib coverage==7.6.1 # via pytest-cov +cycler==0.12.1 + # via matplotlib debugpy==1.8.5 # via ipykernel decorator==5.1.1 @@ -62,6 +69,8 @@ executing==2.0.1 # via stack-data fastjsonschema==2.20.0 # via nbformat +fonttools==4.53.1 + # via matplotlib furo==2024.8.6 # via manim-slides glcontext==3.0.0 @@ -77,8 +86,10 @@ ipykernel==6.29.5 ipython==8.26.0 # via ipykernel # via manim-slides + # via manimgl isosurfaces==0.1.2 # via manim + # via manimgl jedi==0.19.1 # via ipython jinja2==3.1.4 @@ -102,16 +113,22 @@ jupyter-core==5.7.2 # via nbformat jupyterlab-pygments==0.3.0 # via nbconvert +kiwisolver==1.4.5 + # via matplotlib lxml==5.3.0 # via manim-slides # via python-pptx manim==0.18.1 # via manim-slides +manimgl==1.6.1 + # via manim-slides manimpango==0.5.0 # via --override (workspace) # via manim + # via manimgl mapbox-earcut==1.0.2 # via manim + # via manimgl markdown-it-py==3.0.0 # via mdit-py-plugins # via myst-parser @@ -119,6 +136,8 @@ markdown-it-py==3.0.0 markupsafe==2.1.5 # via jinja2 # via nbconvert +matplotlib==3.9.2 + # via manimgl matplotlib-inline==0.1.7 # via ipykernel # via ipython @@ -130,9 +149,13 @@ mistune==3.0.2 # via nbconvert moderngl==5.11.1 # via manim + # via manimgl # via moderngl-window moderngl-window==2.4.6 # via manim + # via manimgl +mpmath==1.3.0 + # via sympy multipledispatch==1.0.0 # via pyrr myst-parser==4.0.0 @@ -151,19 +174,21 @@ nest-asyncio==1.6.0 # via ipykernel networkx==3.3 # via manim -numpy==2.1.0 +numpy==1.24.0 # via --override (workspace) - # via ipython + # via contourpy # via isosurfaces # via manim # via manim-slides + # via manimgl # via mapbox-earcut + # via matplotlib # via moderngl-window - # via networkx # via pyrr # via scipy packaging==24.1 # via ipykernel + # via matplotlib # via nbconvert # via pytest # via qtpy @@ -179,6 +204,8 @@ pexpect==4.9.0 pillow==10.4.0 # via manim # via manim-slides + # via manimgl + # via matplotlib # via moderngl-window # via python-pptx platformdirs==4.2.2 @@ -209,15 +236,21 @@ pydantic-extra-types==2.9.0 # via manim-slides pydub==0.25.1 # via manim + # via manimgl pyglet==2.0.17 # via moderngl-window pygments==2.18.0 # via furo # via ipython # via manim + # via manimgl # via nbconvert # via rich # via sphinx +pyopengl==3.1.7 + # via manimgl +pyparsing==3.1.4 + # via matplotlib pyqt6==6.7.1 # via manim-slides pyqt6-qt6==6.7.2 @@ -249,9 +282,11 @@ pytest-qt==4.4.0 # via manim-slides python-dateutil==2.9.0.post0 # via jupyter-client + # via matplotlib python-pptx==1.0.2 # via manim-slides pyyaml==6.0.2 + # via manimgl # via myst-parser pyzmq==26.2.0 # via ipykernel @@ -267,6 +302,7 @@ requests==2.32.3 rich==13.7.1 # via manim # via manim-slides + # via manimgl rpds-py==0.20.0 # via jsonschema # via referencing @@ -274,8 +310,10 @@ rtoml==0.11.0 # via manim-slides scipy==1.14.1 # via manim + # via manimgl screeninfo==0.8.1 # via manim + # via manimgl shiboken6==6.7.2 # via pyside6 # via pyside6-addons @@ -286,6 +324,7 @@ six==1.16.0 # via python-dateutil skia-pathops==0.8.0.post1 # via manim + # via manimgl snowballstemmer==2.2.0 # via sphinx soupsieve==2.6 @@ -325,6 +364,9 @@ stack-data==0.6.3 # via ipython svgelements==1.9.6 # via manim + # via manimgl +sympy==1.13.2 + # via manimgl tinycss2==1.3.0 # via nbconvert tornado==6.4.1 @@ -333,6 +375,7 @@ tornado==6.4.1 tqdm==4.66.5 # via manim # via manim-slides + # via manimgl traitlets==5.14.3 # via comm # via ipykernel @@ -345,12 +388,15 @@ traitlets==5.14.3 # via nbformat # via nbsphinx typing-extensions==4.12.2 + # via ipython # via manim # via pydantic # via pydantic-core # via python-pptx urllib3==2.2.2 # via requests +validators==0.33.0 + # via manimgl watchdog==4.0.2 # via manim wcwidth==0.2.13 diff --git a/tests/data/slides.py b/tests/data/slides.py index 0aeebe27..4dc84ad9 100644 --- a/tests/data/slides.py +++ b/tests/data/slides.py @@ -38,3 +38,7 @@ def construct(self): self.next_slide() self.zoom(other_text, []) + + +class BasicSlideSkipReversing(BasicSlide): + skip_reversing = True diff --git a/tests/test_slide.py b/tests/test_slide.py index 10cbdcb4..10f80534 100644 --- a/tests/test_slide.py +++ b/tests/test_slide.py @@ -97,6 +97,108 @@ def test_render_basic_slide( assert local_presentation_config.resolution == presentation_config.resolution +def test_clear_cache( + slides_file: Path, +) -> None: + runner = CliRunner() + + with runner.isolated_filesystem() as tmp_dir: + local_media_folder = ( + Path(tmp_dir) + / "media" + / "videos" + / slides_file.stem + / "480p15" + / "partial_movie_files" + / "BasicSlide" + ) + local_slides_folder = Path(tmp_dir) / "slides" + + assert not local_media_folder.exists() + assert not local_slides_folder.exists() + results = runner.invoke(render, [str(slides_file), "BasicSlide", "-ql"]) + + assert results.exit_code == 0, results + assert local_media_folder.is_dir() and list(local_media_folder.iterdir()) + assert local_slides_folder.exists() + + results = runner.invoke( + render, [str(slides_file), "BasicSlide", "-ql", "--flush_cache"] + ) + + assert results.exit_code == 0, results + assert local_media_folder.is_dir() and not list(local_media_folder.iterdir()) + assert local_slides_folder.exists() + + results = runner.invoke( + render, [str(slides_file), "BasicSlide", "-ql", "--disable_caching"] + ) + + assert results.exit_code == 0, results + assert local_media_folder.is_dir() and list(local_media_folder.iterdir()) + assert local_slides_folder.exists() + + results = runner.invoke( + render, + [ + str(slides_file), + "BasicSlide", + "-ql", + "--disable_caching", + "--flush_cache", + ], + ) + + assert results.exit_code == 0, results + assert local_media_folder.is_dir() and not list(local_media_folder.iterdir()) + assert local_slides_folder.exists() + + +@pytest.mark.parametrize( + "renderer", + [ + "--CE", + pytest.param( + "--GL", + marks=pytest.mark.skipif( + sys.version_info >= (3, 12), + reason="ManimGL requires numpy<1.25, which is outdated and Python < 3.12", + ), + ), + ], +) +@pytest.mark.parametrize( + ("klass", "skip_reversing"), + [("BasicSlide", False), ("BasicSlideSkipReversing", True)], +) +def test_skip_reversing( + renderer: str, + slides_file: Path, + manimgl_config: Path, + klass: str, + skip_reversing: bool, +) -> None: + runner = CliRunner() + + with runner.isolated_filesystem() as tmp_dir: + shutil.copy(manimgl_config, tmp_dir) + results = runner.invoke(render, [renderer, str(slides_file), klass, "-ql"]) + + assert results.exit_code == 0, results + + local_slides_folder = (Path(tmp_dir) / "slides").resolve(strict=True) + + local_config_file = (local_slides_folder / f"{klass}.json").resolve(strict=True) + + local_presentation_config = PresentationConfig.from_file(local_config_file) + + for slide in local_presentation_config.slides: + if skip_reversing: + assert slide.file == slide.rev_file + else: + assert slide.file != slide.rev_file + + def init_slide(cls: SlideType) -> Slide: if issubclass(cls, CESlide): return cls()