diff --git a/manimlib/__init__.py b/manimlib/__init__.py index 8467f980..c3696701 100644 --- a/manimlib/__init__.py +++ b/manimlib/__init__.py @@ -61,6 +61,7 @@ from manimlib.scene.interactive_scene import * from manimlib.scene.scene import * from manimlib.utils.bezier import * +from manimlib.utils.cache import * from manimlib.utils.color import * from manimlib.utils.dict_ops import * from manimlib.utils.customization import * diff --git a/manimlib/mobject/svg/old_tex_mobject.py b/manimlib/mobject/svg/old_tex_mobject.py index 61d4dd62..343b43df 100644 --- a/manimlib/mobject/svg/old_tex_mobject.py +++ b/manimlib/mobject/svg/old_tex_mobject.py @@ -7,7 +7,7 @@ import re from manimlib.constants import BLACK, WHITE from manimlib.mobject.svg.svg_mobject import SVGMobject from manimlib.mobject.types.vectorized_mobject import VGroup -from manimlib.utils.tex_file_writing import tex_content_to_svg_file +from manimlib.utils.tex_file_writing import tex_to_svg from typing import TYPE_CHECKING @@ -76,12 +76,12 @@ class SingleStringTex(SVGMobject): self.additional_preamble ) - def get_file_path(self) -> str: - content = self.get_tex_file_body(self.tex_string) - file_path = tex_content_to_svg_file( - content, self.template, self.additional_preamble, self.tex_string + def get_svg_string_by_content(self, content: str) -> str: + return get_cached_value( + key=hash_string(str((content, self.template, self.additional_preamble))), + value_func=lambda: tex_to_svg(content, self.template, self.additional_preamble), + message=f"Writing {self.tex_string}..." ) - return file_path def get_tex_file_body(self, tex_string: str) -> str: new_tex = self.get_modified_expression(tex_string) diff --git a/manimlib/mobject/svg/svg_mobject.py b/manimlib/mobject/svg/svg_mobject.py index 688fa4c5..a2514316 100644 --- a/manimlib/mobject/svg/svg_mobject.py +++ b/manimlib/mobject/svg/svg_mobject.py @@ -20,7 +20,6 @@ from manimlib.mobject.types.vectorized_mobject import VMobject from manimlib.utils.directories import get_mobject_data_dir from manimlib.utils.images import get_full_vector_image_path from manimlib.utils.iterables import hash_obj -from manimlib.utils.simple_functions import hash_string from typing import TYPE_CHECKING if TYPE_CHECKING: diff --git a/manimlib/mobject/svg/tex_mobject.py b/manimlib/mobject/svg/tex_mobject.py index 9412830f..f8078bcd 100644 --- a/manimlib/mobject/svg/tex_mobject.py +++ b/manimlib/mobject/svg/tex_mobject.py @@ -6,10 +6,12 @@ from pathlib import Path from manimlib.mobject.svg.string_mobject import StringMobject from manimlib.mobject.types.vectorized_mobject import VGroup from manimlib.mobject.types.vectorized_mobject import VMobject +from manimlib.utils.cache import get_cached_value from manimlib.utils.color import color_to_hex from manimlib.utils.color import hex_to_int -from manimlib.utils.tex_file_writing import tex_content_to_svg_file +from manimlib.utils.tex_file_writing import tex_to_svg from manimlib.utils.tex import num_tex_symbols +from manimlib.utils.simple_functions import hash_string from manimlib.logger import log from typing import TYPE_CHECKING @@ -84,11 +86,11 @@ class Tex(StringMobject): ) def get_svg_string_by_content(self, content: str) -> str: - # TODO, implement this without writing to a file - file_path = tex_content_to_svg_file( - content, self.template, self.additional_preamble, self.tex_string + return get_cached_value( + key=hash_string(str((content, self.template, self.additional_preamble))), + value_func=lambda: tex_to_svg(content, self.template, self.additional_preamble), + message=f"Writing {self.tex_string}..." ) - return Path(file_path).read_text() def _handle_scale_side_effects(self, scale_factor: float) -> Self: self.font_size *= scale_factor diff --git a/manimlib/mobject/svg/text_mobject.py b/manimlib/mobject/svg/text_mobject.py index 6989515e..5568295a 100644 --- a/manimlib/mobject/svg/text_mobject.py +++ b/manimlib/mobject/svg/text_mobject.py @@ -16,9 +16,10 @@ from manimlib.constants import DEFAULT_PIXEL_WIDTH, FRAME_WIDTH from manimlib.constants import NORMAL from manimlib.logger import log from manimlib.mobject.svg.string_mobject import StringMobject -from manimlib.utils.customization import get_customization +from manimlib.utils.cache import get_cached_value from manimlib.utils.color import color_to_hex from manimlib.utils.color import int_to_hex +from manimlib.utils.customization import get_customization from manimlib.utils.directories import get_downloads_dir from manimlib.utils.directories import get_text_dir from manimlib.utils.simple_functions import hash_string @@ -172,17 +173,14 @@ class MarkupText(StringMobject): ) def get_svg_string_by_content(self, content: str) -> str: - # TODO, check the cache - hash_content = str(( + key = hash_string(str(( content, self.justify, self.indent, self.alignment, self.line_width - )) - # hash_string(hash_content) - key = hashlib.sha256(hash_content.encode()).hexdigest() - return self.markup_to_svg_string(content) + ))) + return get_cached_value(key, lambda: self.markup_to_svg_string(content)) def markup_to_svg_string(self, markup_str: str) -> str: self.validate_markup_string(markup_str) diff --git a/manimlib/utils/cache.py b/manimlib/utils/cache.py new file mode 100644 index 00000000..0b3f3d2e --- /dev/null +++ b/manimlib/utils/cache.py @@ -0,0 +1,33 @@ +import appdirs +import os +from diskcache import Cache +from contextlib import contextmanager + + +CACHE_SIZE = 1e9 # 1 Gig + + +def get_cached_value(key, value_func, message=""): + cache_dir = appdirs.user_cache_dir("manim") + cache = Cache(cache_dir, size_limit=CACHE_SIZE) + + value = cache.get(key) + if value is None: + with display_during_execution(message): + value = value_func() + cache.set(key, value) + return value + + +@contextmanager +def display_during_execution(message: str): + # Merge into a single line + to_print = message.replace("\n", " ") + max_characters = os.get_terminal_size().columns - 1 + if len(to_print) > max_characters: + to_print = to_print[:max_characters - 3] + "..." + try: + print(to_print, end="\r") + yield + finally: + print(" " * len(to_print), end="\r") diff --git a/manimlib/utils/tex_file_writing.py b/manimlib/utils/tex_file_writing.py index 6538adc9..94ca2a95 100644 --- a/manimlib/utils/tex_file_writing.py +++ b/manimlib/utils/tex_file_writing.py @@ -1,10 +1,12 @@ from __future__ import annotations -from contextlib import contextmanager import os import re import yaml +from pathlib import Path +import tempfile + from manimlib.config import get_custom_config from manimlib.config import get_manim_dir from manimlib.logger import log @@ -51,9 +53,10 @@ def get_tex_config() -> dict[str, str]: return SAVED_TEX_CONFIG -def tex_content_to_svg_file( - content: str, template: str, additional_preamble: str, - short_tex: str +def tex_to_svg( + content: str, + template: str, + additional_preamble: str, ) -> str: tex_config = get_tex_config() if not template or template == tex_config["template"]: @@ -74,14 +77,11 @@ def tex_content_to_svg_file( "\\end{document}" )) + "\n" - svg_file = os.path.join( - get_tex_dir(), hash_string(full_tex) + ".svg" - ) - if not os.path.exists(svg_file): - # If svg doesn't exist, create it - with display_during_execution("Writing " + short_tex): - create_tex_svg(full_tex, svg_file, compiler) - return svg_file + with tempfile.NamedTemporaryFile(suffix='.svg', mode='r+') as tmp: + create_tex_svg(full_tex, tmp.name, compiler) + # Read the contents + tmp.seek(0) + return tmp.read() def create_tex_svg(full_tex: str, svg_file: str, compiler: str) -> None: @@ -145,20 +145,5 @@ def create_tex_svg(full_tex: str, svg_file: str, compiler: str) -> None: pass -# TODO, perhaps this should live elsewhere -@contextmanager -def display_during_execution(message: str): - # Merge into a single line - to_print = message.replace("\n", " ") - max_characters = os.get_terminal_size().columns - 1 - if len(to_print) > max_characters: - to_print = to_print[:max_characters - 3] + "..." - try: - print(to_print, end="\r") - yield - finally: - print(" " * len(to_print), end="\r") - - class LatexError(Exception): pass