feat(lib): use and add more config option (#452)

* feat(lib): use and add more config option

Closes #441

* feat(lib): add support for more options and test them

* chore(docs): add attributes to documentation

* chore(docs): sort members by member type

* chore(docs): small improvements

* chore(docs): cleanup
This commit is contained in:
Jérome Eertmans
2024-09-05 14:08:10 +02:00
committed by GitHub
parent 87867762ea
commit a39a9c2bb1
9 changed files with 302 additions and 25 deletions

View File

@ -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

View File

@ -283,8 +283,7 @@ class ManimSlidesDirective(Directive):
# Rendering is skipped if the tag skip-manim is present,
# or if we are making the pot-files
should_skip = (
"skip-manim-slides"
in self.state.document.settings.env.app.builder.tags.tags
self.state.document.settings.env.app.builder.tags.has("skip-manim-slides")
or self.state.document.settings.env.app.builder.name == "gettext"
or "SKIP_MANIM_SLIDES" in os.environ
)

View File

@ -3,6 +3,7 @@ from __future__ import annotations
__all__ = ["BaseSlide"]
import platform
import shutil
from abc import abstractmethod
from collections.abc import MutableMapping, Sequence, ValuesView
from pathlib import Path
@ -32,6 +33,10 @@ LEFT: np.ndarray = np.array([-1.0, 0.0, 0.0])
class BaseSlide:
disable_caching: bool = False
flush_cache: bool = False
skip_reversing: bool = False
def __init__(
self, *args: Any, output_folder: Path = FOLDER_PATH, **kwargs: Any
) -> None:
@ -170,11 +175,23 @@ class BaseSlide:
animations. You must still call :code:`self.add` or
play some animation that introduces each Mobject for
it to appear. The same applies when removing objects.
.. seealso::
:attr:`canvas` for usage examples.
"""
self._canvas.update(objects)
def remove_from_canvas(self, *names: str) -> None:
"""Remove objects from the canvas."""
"""
Remove objects from the canvas.
:param names: The names of objects to remove.
.. seealso::
:attr:`canvas` for usage examples.
"""
for name in names:
self._canvas.pop(name)
@ -186,8 +203,12 @@ class BaseSlide:
@property
def mobjects_without_canvas(self) -> Sequence[Mobject]:
"""
Return the list of objects contained in the scene, minus those present in
Return the list of Mobjects contained in the scene, minus those present in
the canvas.
.. seealso::
:attr:`canvas` for usage examples.
"""
return [
mobject
@ -275,7 +296,7 @@ class BaseSlide:
next slide is played. By default, this is the right arrow key.
:param args:
Positional arguments to be passed to
Positional arguments passed to
:meth:`Scene.next_section<manim.scene.scene.Scene.next_section>`,
or ignored if `manimlib` API is used.
:param loop:
@ -284,27 +305,38 @@ class BaseSlide:
If set, next slide will play immediately play the next slide
upon terminating.
Note that this is only supported by ``manim-slides present``
.. warning::
Only supported by ``manim-slides present``
and ``manim-slides convert --to=html``.
:param playback_rate:
Playback rate at which the video is played.
Note that this is only supported by ``manim-slides present``.
.. warning::
Only supported by ``manim-slides present``.
:param reversed_playback_rate:
Playback rate at which the reversed video is played.
Note that this is only supported by ``manim-slides present``.
.. warning::
Only supported by ``manim-slides present``.
:param notes:
Presenter notes, in Markdown format.
Note that PowerPoint does not support Markdown.
.. note::
PowerPoint does not support Markdown formatting,
so the text will be displayed as is.
Note that this is only supported by ``manim-slides present``
and ``manim-slides convert --to=html/pptx``.
.. warning::
Only supported by ``manim-slides present``,
``manim-slides convert --to=html`` and
``manim-slides convert --to=pptx``.
:param dedent_notes:
If set, apply :func:`textwrap.dedent` to notes.
:param kwargs:
Keyword arguments to be passed to
Keyword arguments passed to
:meth:`Scene.next_section<manim.scene.scene.Scene.next_section>`,
or ignored if `manimlib` API is used.
@ -445,11 +477,17 @@ class BaseSlide:
)
)
def _save_slides(self, use_cache: bool = True) -> None:
def _save_slides(
self,
use_cache: bool = True,
flush_cache: bool = False,
skip_reversing: bool = False,
) -> None:
"""
Save slides, optionally using cached files.
Note that cached files only work with Manim.
.. warning:
Caching files only work with Manim.
"""
self._add_last_slide()
@ -458,6 +496,9 @@ class BaseSlide:
scene_name = str(self)
scene_files_folder = files_folder / scene_name
if flush_cache and scene_files_folder.exists():
shutil.rmtree(scene_files_folder)
scene_files_folder.mkdir(parents=True, exist_ok=True)
files: list[Path] = self._partial_movie_files
@ -492,6 +533,9 @@ class BaseSlide:
# We only reverse video if it was not present
if not use_cache or not rev_file.exists():
if skip_reversing:
rev_file = dst_file
else:
reverse_video_file(dst_file, rev_file)
slides.append(

View File

@ -13,6 +13,22 @@ class Slide(BaseSlide, Scene): # type: ignore[misc]
"""
Inherits from :class:`Scene<manim.scene.scene.Scene>` and provide necessary tools
for slides rendering.
:param args: Positional arguments passed to scene object.
:param output_folder: Where the slide animation files should be written.
:param kwargs: Keyword arguments passed to scene object.
:cvar bool disable_caching: :data:`False`: Whether to disable the use of
cached animation files.
:cvar bool flush_cache: :data:`False`: Whether to flush the cache.
Unlike with Manim, flushing is performed before rendering.
:cvar bool skip_reversing: :data:`False`: Whether to generate reversed animations.
If set to :data:`False`, and no cached reversed animation
exists (or caching is disabled) for a given slide,
then the reversed animation will be simply the same
as the original one, i.e., ``rev_file = file``,
for the current slide config.
"""
@property
@ -102,16 +118,29 @@ class Slide(BaseSlide, Scene): # type: ignore[misc]
)
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()
self._save_slides(
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]

View File

@ -62,7 +62,11 @@ class Slide(BaseSlide, Scene): # type: ignore[misc]
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):

View File

@ -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

View File

@ -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

View File

@ -38,3 +38,7 @@ class BasicSlide(Slide):
self.next_slide()
self.zoom(other_text, [])
class BasicSlideSkipReversing(BasicSlide):
skip_reversing = True

View File

@ -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()