diff --git a/manim/_config/utils.py b/manim/_config/utils.py index e7b1d0cdc8..f7778ae169 100644 --- a/manim/_config/utils.py +++ b/manim/_config/utils.py @@ -309,6 +309,7 @@ class MyScene(Scene): "write_to_movie", "zero_pad", "force_window", + "no_latex_cleanup", } def __init__(self) -> None: @@ -580,6 +581,7 @@ def digest_parser(self, parser: configparser.ConfigParser) -> ManimConfig: "use_projection_stroke_shaders", "enable_wireframe", "force_window", + "no_latex_cleanup", ]: setattr(self, key, parser["CLI"].getboolean(key, fallback=False)) @@ -756,6 +758,7 @@ def digest_args(self, args: argparse.Namespace) -> ManimConfig: "enable_wireframe", "force_window", "dry_run", + "no_latex_cleanup", ]: if hasattr(args, key): attr = getattr(args, key) @@ -960,6 +963,12 @@ def digest_file(self, filename: str | os.PathLike) -> ManimConfig: doc="Set to force window when using the opengl renderer", ) + no_latex_cleanup = property( + lambda self: self._d["no_latex_cleanup"], + lambda self, val: self._set_boolean("no_latex_cleanup", val), + doc="Prevents deletion of .aux, .dvi, and .log files produced by Tex and MathTex.", + ) + @property def verbosity(self): """Logger verbosity; "DEBUG", "INFO", "WARNING", "ERROR", or "CRITICAL" (-v).""" diff --git a/manim/cli/render/global_options.py b/manim/cli/render/global_options.py index b17e4671c2..17c754e623 100644 --- a/manim/cli/render/global_options.py +++ b/manim/cli/render/global_options.py @@ -102,4 +102,10 @@ def validate_gui_location(ctx, param, value): help="Renders animations without outputting image or video files and disables the window", default=False, ), + option( + "--no_latex_cleanup", + is_flag=True, + help="Prevents deletion of .aux, .dvi, and .log files produced by Tex and MathTex.", + default=False, + ), ) diff --git a/manim/utils/tex_file_writing.py b/manim/utils/tex_file_writing.py index 0f419bb9e9..c7758f899e 100644 --- a/manim/utils/tex_file_writing.py +++ b/manim/utils/tex_file_writing.py @@ -13,6 +13,7 @@ import re import unicodedata from pathlib import Path +from typing import Iterable from manim.utils.tex import TexTemplate @@ -51,19 +52,28 @@ def tex_to_svg_file( if tex_template is None: tex_template = config["tex_template"] tex_file = generate_tex_file(expression, environment, tex_template) + + # check if svg already exists + svg_file = tex_file.with_suffix(".svg") + if svg_file.exists(): + return svg_file + dvi_file = compile_tex( tex_file, tex_template.tex_compiler, tex_template.output_format, ) - return convert_to_svg(dvi_file, tex_template.output_format) + svg_file = convert_to_svg(dvi_file, tex_template.output_format) + if not config["no_latex_cleanup"]: + delete_nonsvg_files() + return svg_file def generate_tex_file( expression: str, environment: str | None = None, tex_template: TexTemplate | None = None, -): +) -> Path: """Takes a tex expression (and an optional tex environment), and returns a fully formed tex file ready for compilation. @@ -251,6 +261,23 @@ def convert_to_svg(dvi_file: Path, extension: str, page: int = 1): return result +def delete_nonsvg_files(additional_endings: Iterable[str] = ()) -> None: + """Deletes every file that does not have a suffix in ``(".svg", ".tex", *additional_endings)`` + + Parameters: + ----------- + additional_endings + Additional endings to whitelist + """ + + tex_dir = config.get_dir("tex_dir") + file_suffix_whitelist = {".svg", ".tex", *additional_endings} + + for f in tex_dir.iterdir(): + if f.suffix not in file_suffix_whitelist: + f.unlink() + + def print_all_tex_errors(log_file: Path, tex_compiler: str, tex_file: Path) -> None: if not log_file.exists(): raise RuntimeError( diff --git a/tests/module/mobject/text/test_texmobject.py b/tests/module/mobject/text/test_texmobject.py index 9103345d18..a3cf26c355 100644 --- a/tests/module/mobject/text/test_texmobject.py +++ b/tests/module/mobject/text/test_texmobject.py @@ -212,3 +212,17 @@ def test_tempconfig_resetting_tex_template(): assert config.tex_template.preamble == "Custom preamble!" assert config.tex_template.preamble != "Custom preamble!" + + +def test_tex_garbage_collection(tmpdir, monkeypatch): + monkeypatch.chdir(tmpdir) + Path(tmpdir, "media").mkdir() + + with tempconfig({"media_dir": "media"}): + tex_without_log = Tex("Hello World!") # f7bc61042256dea9.tex + assert Path("media", "Tex", "f7bc61042256dea9.tex").exists() + assert not Path("media", "Tex", "f7bc61042256dea9.log").exists() + + with tempconfig({"media_dir": "media", "no_latex_cleanup": True}): + tex_with_log = Tex("Hello World, again!") # 3ef79eaaa2d0b15b.tex + assert Path("media", "Tex", "3ef79eaaa2d0b15b.log").exists()