3b1b-manim/manimlib/utils/tex_file_writing.py

152 lines
4.3 KiB
Python
Raw Normal View History

from __future__ import annotations
2022-04-12 19:19:59 +08:00
import os
import re
2022-05-22 23:47:45 +08:00
import yaml
2024-12-04 20:30:53 -06:00
import subprocess
from functools import lru_cache
from pathlib import Path
import tempfile
from manimlib.utils.cache import cache_on_disk
from manimlib.config import manim_config
2022-05-22 23:47:45 +08:00
from manimlib.config import get_manim_dir
2021-10-07 17:37:10 +08:00
from manimlib.logger import log
from manimlib.utils.simple_functions import hash_string
2018-05-09 14:10:04 -07:00
2022-08-22 21:52:48 +08:00
def get_tex_template_config(template_name: str) -> dict[str, str]:
2022-08-20 13:01:59 +08:00
name = template_name.replace(" ", "_").lower()
2024-12-04 20:30:53 -06:00
template_path = os.path.join(get_manim_dir(), "manimlib", "tex_templates.yml")
with open(template_path, encoding="utf-8") as tex_templates_file:
2022-05-22 23:47:45 +08:00
templates_dict = yaml.safe_load(tex_templates_file)
if name not in templates_dict:
Video work (#2284) * Comment tweak * Directly print traceback Since the shell.showtraceback is giving some issues * Make InteracrtiveSceneEmbed into a class This way it can keep track of it's internal shell; use of get_ipython has a finicky relationship with reloading. * Move remaining checkpoint_paste logic into scene_embed.py This involved making a few context managers for Scene: temp_record, temp_skip, temp_progress_bar, which seem useful in and of themselves. * Change null key to be the empty string * Ensure temporary svg paths for Text are deleted * Remove unused dict_ops.py functions * Remove break_into_partial_movies from file_writer configuration * Rewrite guarantee_existence using Path * Clean up SceneFileWriter It had a number of vestigial functions no longer used, and some setup that could be made more organized. * Remove --save_pngs CLI arg (which did nothing) * Add --subdivide CLI arg * Remove add_extension_if_not_present * Remove get_sorted_integer_files * Have find_file return Path * Minor clean up * Clean up num_tex_symbols * Fix find_file * Minor cleanup for extract_scene.py * Add preview_frame_while_skipping option to scene config * Use shell.showtraceback function * Move keybindings to config, instead of in-place constants * Replace DEGREES -> DEG * Add arg to clear the cache * Separate out full_tex_to_svg from tex_to_svg And only cache to disk the results of full_tex_to_svg. Otherwise, making edits to the tex_templates would not show up without clearing the cache. * Bug fix in handling BlankScene * Make checkpoint_states an instance variable of CheckpointManager As per https://github.com/3b1b/manim/issues/2272 * Move resizing out of Window.focus, and into Window.init_for_scene * Make default output directory "." instead of "" To address https://github.com/3b1b/manim/issues/2261 * Remove input_file_path arg from SceneFileWriter * Use Dict syntax in place of dict for config more consistently across config.py * Simplify get_output_directory * Swap order of preamble and additional preamble * Minor stylistic tweak * Have UnitInterval pass on kwargs to NumberLine * Add simple get_dist function * Have TracedPath always update to the stroke configuration passed in * Have Mobject.match_points apply to all parts of data in pointlike_data_key * Always call Mobject.update upon adding an updater * Add Surface.uv_to_point * Make sure Surface.set_opacity takes in a recurse option * Update num_tex_symbols to account for \{ and \}
2024-12-26 11:35:34 -06:00
log.warning(f"Cannot recognize template {name}, falling back to 'default'.")
2022-05-22 23:47:45 +08:00
name = "default"
2022-08-22 21:52:48 +08:00
return templates_dict[name]
2022-05-22 23:47:45 +08:00
@lru_cache
A few bug fixes (#2277) * Comment tweak * Directly print traceback Since the shell.showtraceback is giving some issues * Make InteracrtiveSceneEmbed into a class This way it can keep track of it's internal shell; use of get_ipython has a finicky relationship with reloading. * Move remaining checkpoint_paste logic into scene_embed.py This involved making a few context managers for Scene: temp_record, temp_skip, temp_progress_bar, which seem useful in and of themselves. * Change null key to be the empty string * Ensure temporary svg paths for Text are deleted * Remove unused dict_ops.py functions * Remove break_into_partial_movies from file_writer configuration * Rewrite guarantee_existence using Path * Clean up SceneFileWriter It had a number of vestigial functions no longer used, and some setup that could be made more organized. * Remove --save_pngs CLI arg (which did nothing) * Add --subdivide CLI arg * Remove add_extension_if_not_present * Remove get_sorted_integer_files * Have find_file return Path * Minor clean up * Clean up num_tex_symbols * Fix find_file * Minor cleanup for extract_scene.py * Add preview_frame_while_skipping option to scene config * Use shell.showtraceback function * Move keybindings to config, instead of in-place constants * Replace DEGREES -> DEG * Add arg to clear the cache * Separate out full_tex_to_svg from tex_to_svg And only cache to disk the results of full_tex_to_svg. Otherwise, making edits to the tex_templates would not show up without clearing the cache. * Bug fix in handling BlankScene * Make checkpoint_states an instance variable of CheckpointManager As per https://github.com/3b1b/manim/issues/2272 * Move resizing out of Window.focus, and into Window.init_for_scene * Make default output directory "." instead of "" To address https://github.com/3b1b/manim/issues/2261 * Remove input_file_path arg from SceneFileWriter * Use Dict syntax in place of dict for config more consistently across config.py * Simplify get_output_directory * Swap order of preamble and additional preamble
2024-12-12 20:45:34 -06:00
def get_tex_config(template: str = "") -> tuple[str, str]:
"""
A few bug fixes (#2277) * Comment tweak * Directly print traceback Since the shell.showtraceback is giving some issues * Make InteracrtiveSceneEmbed into a class This way it can keep track of it's internal shell; use of get_ipython has a finicky relationship with reloading. * Move remaining checkpoint_paste logic into scene_embed.py This involved making a few context managers for Scene: temp_record, temp_skip, temp_progress_bar, which seem useful in and of themselves. * Change null key to be the empty string * Ensure temporary svg paths for Text are deleted * Remove unused dict_ops.py functions * Remove break_into_partial_movies from file_writer configuration * Rewrite guarantee_existence using Path * Clean up SceneFileWriter It had a number of vestigial functions no longer used, and some setup that could be made more organized. * Remove --save_pngs CLI arg (which did nothing) * Add --subdivide CLI arg * Remove add_extension_if_not_present * Remove get_sorted_integer_files * Have find_file return Path * Minor clean up * Clean up num_tex_symbols * Fix find_file * Minor cleanup for extract_scene.py * Add preview_frame_while_skipping option to scene config * Use shell.showtraceback function * Move keybindings to config, instead of in-place constants * Replace DEGREES -> DEG * Add arg to clear the cache * Separate out full_tex_to_svg from tex_to_svg And only cache to disk the results of full_tex_to_svg. Otherwise, making edits to the tex_templates would not show up without clearing the cache. * Bug fix in handling BlankScene * Make checkpoint_states an instance variable of CheckpointManager As per https://github.com/3b1b/manim/issues/2272 * Move resizing out of Window.focus, and into Window.init_for_scene * Make default output directory "." instead of "" To address https://github.com/3b1b/manim/issues/2261 * Remove input_file_path arg from SceneFileWriter * Use Dict syntax in place of dict for config more consistently across config.py * Simplify get_output_directory * Swap order of preamble and additional preamble
2024-12-12 20:45:34 -06:00
Returns a compiler and preamble to use for rendering LaTeX
"""
template = template or manim_config.tex.template
A few bug fixes (#2277) * Comment tweak * Directly print traceback Since the shell.showtraceback is giving some issues * Make InteracrtiveSceneEmbed into a class This way it can keep track of it's internal shell; use of get_ipython has a finicky relationship with reloading. * Move remaining checkpoint_paste logic into scene_embed.py This involved making a few context managers for Scene: temp_record, temp_skip, temp_progress_bar, which seem useful in and of themselves. * Change null key to be the empty string * Ensure temporary svg paths for Text are deleted * Remove unused dict_ops.py functions * Remove break_into_partial_movies from file_writer configuration * Rewrite guarantee_existence using Path * Clean up SceneFileWriter It had a number of vestigial functions no longer used, and some setup that could be made more organized. * Remove --save_pngs CLI arg (which did nothing) * Add --subdivide CLI arg * Remove add_extension_if_not_present * Remove get_sorted_integer_files * Have find_file return Path * Minor clean up * Clean up num_tex_symbols * Fix find_file * Minor cleanup for extract_scene.py * Add preview_frame_while_skipping option to scene config * Use shell.showtraceback function * Move keybindings to config, instead of in-place constants * Replace DEGREES -> DEG * Add arg to clear the cache * Separate out full_tex_to_svg from tex_to_svg And only cache to disk the results of full_tex_to_svg. Otherwise, making edits to the tex_templates would not show up without clearing the cache. * Bug fix in handling BlankScene * Make checkpoint_states an instance variable of CheckpointManager As per https://github.com/3b1b/manim/issues/2272 * Move resizing out of Window.focus, and into Window.init_for_scene * Make default output directory "." instead of "" To address https://github.com/3b1b/manim/issues/2261 * Remove input_file_path arg from SceneFileWriter * Use Dict syntax in place of dict for config more consistently across config.py * Simplify get_output_directory * Swap order of preamble and additional preamble
2024-12-12 20:45:34 -06:00
config = get_tex_template_config(template)
return config["compiler"], config["preamble"]
2022-05-22 23:47:45 +08:00
2024-12-04 20:30:53 -06:00
def get_full_tex(content: str, preamble: str = ""):
return "\n\n".join((
2022-05-22 23:47:45 +08:00
"\\documentclass[preview]{standalone}",
preamble,
"\\begin{document}",
content,
"\\end{document}"
)) + "\n"
@lru_cache(maxsize=128)
2024-12-04 20:30:53 -06:00
def latex_to_svg(
latex: str,
template: str = "",
additional_preamble: str = "",
short_tex: str = "",
show_message_during_execution: bool = True,
2024-12-04 20:30:53 -06:00
) -> str:
"""Convert LaTeX string to SVG string.
Args:
latex: LaTeX source code
template: Path to a template LaTeX file
additional_preamble: String including any added "\\usepackage{...}" style imports
Returns:
str: SVG source code
Raises:
LatexError: If LaTeX compilation fails
NotImplementedError: If compiler is not supported
"""
if show_message_during_execution:
A few bug fixes (#2277) * Comment tweak * Directly print traceback Since the shell.showtraceback is giving some issues * Make InteracrtiveSceneEmbed into a class This way it can keep track of it's internal shell; use of get_ipython has a finicky relationship with reloading. * Move remaining checkpoint_paste logic into scene_embed.py This involved making a few context managers for Scene: temp_record, temp_skip, temp_progress_bar, which seem useful in and of themselves. * Change null key to be the empty string * Ensure temporary svg paths for Text are deleted * Remove unused dict_ops.py functions * Remove break_into_partial_movies from file_writer configuration * Rewrite guarantee_existence using Path * Clean up SceneFileWriter It had a number of vestigial functions no longer used, and some setup that could be made more organized. * Remove --save_pngs CLI arg (which did nothing) * Add --subdivide CLI arg * Remove add_extension_if_not_present * Remove get_sorted_integer_files * Have find_file return Path * Minor clean up * Clean up num_tex_symbols * Fix find_file * Minor cleanup for extract_scene.py * Add preview_frame_while_skipping option to scene config * Use shell.showtraceback function * Move keybindings to config, instead of in-place constants * Replace DEGREES -> DEG * Add arg to clear the cache * Separate out full_tex_to_svg from tex_to_svg And only cache to disk the results of full_tex_to_svg. Otherwise, making edits to the tex_templates would not show up without clearing the cache. * Bug fix in handling BlankScene * Make checkpoint_states an instance variable of CheckpointManager As per https://github.com/3b1b/manim/issues/2272 * Move resizing out of Window.focus, and into Window.init_for_scene * Make default output directory "." instead of "" To address https://github.com/3b1b/manim/issues/2261 * Remove input_file_path arg from SceneFileWriter * Use Dict syntax in place of dict for config more consistently across config.py * Simplify get_output_directory * Swap order of preamble and additional preamble
2024-12-12 20:45:34 -06:00
message = f"Writing {(short_tex or latex)[:70]}..."
else:
message = ""
compiler, preamble = get_tex_config(template)
preamble = "\n".join([preamble, additional_preamble])
full_tex = get_full_tex(latex, preamble)
return full_tex_to_svg(full_tex, compiler, message)
A few bug fixes (#2277) * Comment tweak * Directly print traceback Since the shell.showtraceback is giving some issues * Make InteracrtiveSceneEmbed into a class This way it can keep track of it's internal shell; use of get_ipython has a finicky relationship with reloading. * Move remaining checkpoint_paste logic into scene_embed.py This involved making a few context managers for Scene: temp_record, temp_skip, temp_progress_bar, which seem useful in and of themselves. * Change null key to be the empty string * Ensure temporary svg paths for Text are deleted * Remove unused dict_ops.py functions * Remove break_into_partial_movies from file_writer configuration * Rewrite guarantee_existence using Path * Clean up SceneFileWriter It had a number of vestigial functions no longer used, and some setup that could be made more organized. * Remove --save_pngs CLI arg (which did nothing) * Add --subdivide CLI arg * Remove add_extension_if_not_present * Remove get_sorted_integer_files * Have find_file return Path * Minor clean up * Clean up num_tex_symbols * Fix find_file * Minor cleanup for extract_scene.py * Add preview_frame_while_skipping option to scene config * Use shell.showtraceback function * Move keybindings to config, instead of in-place constants * Replace DEGREES -> DEG * Add arg to clear the cache * Separate out full_tex_to_svg from tex_to_svg And only cache to disk the results of full_tex_to_svg. Otherwise, making edits to the tex_templates would not show up without clearing the cache. * Bug fix in handling BlankScene * Make checkpoint_states an instance variable of CheckpointManager As per https://github.com/3b1b/manim/issues/2272 * Move resizing out of Window.focus, and into Window.init_for_scene * Make default output directory "." instead of "" To address https://github.com/3b1b/manim/issues/2261 * Remove input_file_path arg from SceneFileWriter * Use Dict syntax in place of dict for config more consistently across config.py * Simplify get_output_directory * Swap order of preamble and additional preamble
2024-12-12 20:45:34 -06:00
@cache_on_disk
def full_tex_to_svg(full_tex: str, compiler: str = "latex", message: str = ""):
if message:
print(message, end="\r")
2022-05-22 23:47:45 +08:00
if compiler == "latex":
dvi_ext = ".dvi"
elif compiler == "xelatex":
dvi_ext = ".xdv"
else:
2024-12-04 20:30:53 -06:00
raise NotImplementedError(f"Compiler '{compiler}' is not implemented")
# Write intermediate files to a temporary directory
with tempfile.TemporaryDirectory() as temp_dir:
A few bug fixes (#2277) * Comment tweak * Directly print traceback Since the shell.showtraceback is giving some issues * Make InteracrtiveSceneEmbed into a class This way it can keep track of it's internal shell; use of get_ipython has a finicky relationship with reloading. * Move remaining checkpoint_paste logic into scene_embed.py This involved making a few context managers for Scene: temp_record, temp_skip, temp_progress_bar, which seem useful in and of themselves. * Change null key to be the empty string * Ensure temporary svg paths for Text are deleted * Remove unused dict_ops.py functions * Remove break_into_partial_movies from file_writer configuration * Rewrite guarantee_existence using Path * Clean up SceneFileWriter It had a number of vestigial functions no longer used, and some setup that could be made more organized. * Remove --save_pngs CLI arg (which did nothing) * Add --subdivide CLI arg * Remove add_extension_if_not_present * Remove get_sorted_integer_files * Have find_file return Path * Minor clean up * Clean up num_tex_symbols * Fix find_file * Minor cleanup for extract_scene.py * Add preview_frame_while_skipping option to scene config * Use shell.showtraceback function * Move keybindings to config, instead of in-place constants * Replace DEGREES -> DEG * Add arg to clear the cache * Separate out full_tex_to_svg from tex_to_svg And only cache to disk the results of full_tex_to_svg. Otherwise, making edits to the tex_templates would not show up without clearing the cache. * Bug fix in handling BlankScene * Make checkpoint_states an instance variable of CheckpointManager As per https://github.com/3b1b/manim/issues/2272 * Move resizing out of Window.focus, and into Window.init_for_scene * Make default output directory "." instead of "" To address https://github.com/3b1b/manim/issues/2261 * Remove input_file_path arg from SceneFileWriter * Use Dict syntax in place of dict for config more consistently across config.py * Simplify get_output_directory * Swap order of preamble and additional preamble
2024-12-12 20:45:34 -06:00
tex_path = Path(temp_dir, "working").with_suffix(".tex")
dvi_path = tex_path.with_suffix(dvi_ext)
2024-12-04 20:30:53 -06:00
# Write tex file
A few bug fixes (#2277) * Comment tweak * Directly print traceback Since the shell.showtraceback is giving some issues * Make InteracrtiveSceneEmbed into a class This way it can keep track of it's internal shell; use of get_ipython has a finicky relationship with reloading. * Move remaining checkpoint_paste logic into scene_embed.py This involved making a few context managers for Scene: temp_record, temp_skip, temp_progress_bar, which seem useful in and of themselves. * Change null key to be the empty string * Ensure temporary svg paths for Text are deleted * Remove unused dict_ops.py functions * Remove break_into_partial_movies from file_writer configuration * Rewrite guarantee_existence using Path * Clean up SceneFileWriter It had a number of vestigial functions no longer used, and some setup that could be made more organized. * Remove --save_pngs CLI arg (which did nothing) * Add --subdivide CLI arg * Remove add_extension_if_not_present * Remove get_sorted_integer_files * Have find_file return Path * Minor clean up * Clean up num_tex_symbols * Fix find_file * Minor cleanup for extract_scene.py * Add preview_frame_while_skipping option to scene config * Use shell.showtraceback function * Move keybindings to config, instead of in-place constants * Replace DEGREES -> DEG * Add arg to clear the cache * Separate out full_tex_to_svg from tex_to_svg And only cache to disk the results of full_tex_to_svg. Otherwise, making edits to the tex_templates would not show up without clearing the cache. * Bug fix in handling BlankScene * Make checkpoint_states an instance variable of CheckpointManager As per https://github.com/3b1b/manim/issues/2272 * Move resizing out of Window.focus, and into Window.init_for_scene * Make default output directory "." instead of "" To address https://github.com/3b1b/manim/issues/2261 * Remove input_file_path arg from SceneFileWriter * Use Dict syntax in place of dict for config more consistently across config.py * Simplify get_output_directory * Swap order of preamble and additional preamble
2024-12-12 20:45:34 -06:00
tex_path.write_text(full_tex)
2024-12-04 20:30:53 -06:00
# Run latex compiler
process = subprocess.run(
[
2024-12-11 08:23:17 -06:00
compiler,
*(['-no-pdf'] if compiler == "xelatex" else []),
2024-12-04 20:30:53 -06:00
"-interaction=batchmode",
"-halt-on-error",
2024-12-11 08:23:17 -06:00
f"-output-directory={temp_dir}",
2024-12-04 20:30:53 -06:00
tex_path
2024-12-11 08:23:17 -06:00
],
2024-12-04 20:30:53 -06:00
capture_output=True,
text=True
2022-05-22 23:47:45 +08:00
)
2024-12-04 20:30:53 -06:00
if process.returncode != 0:
# Handle error
error_str = ""
A few bug fixes (#2277) * Comment tweak * Directly print traceback Since the shell.showtraceback is giving some issues * Make InteracrtiveSceneEmbed into a class This way it can keep track of it's internal shell; use of get_ipython has a finicky relationship with reloading. * Move remaining checkpoint_paste logic into scene_embed.py This involved making a few context managers for Scene: temp_record, temp_skip, temp_progress_bar, which seem useful in and of themselves. * Change null key to be the empty string * Ensure temporary svg paths for Text are deleted * Remove unused dict_ops.py functions * Remove break_into_partial_movies from file_writer configuration * Rewrite guarantee_existence using Path * Clean up SceneFileWriter It had a number of vestigial functions no longer used, and some setup that could be made more organized. * Remove --save_pngs CLI arg (which did nothing) * Add --subdivide CLI arg * Remove add_extension_if_not_present * Remove get_sorted_integer_files * Have find_file return Path * Minor clean up * Clean up num_tex_symbols * Fix find_file * Minor cleanup for extract_scene.py * Add preview_frame_while_skipping option to scene config * Use shell.showtraceback function * Move keybindings to config, instead of in-place constants * Replace DEGREES -> DEG * Add arg to clear the cache * Separate out full_tex_to_svg from tex_to_svg And only cache to disk the results of full_tex_to_svg. Otherwise, making edits to the tex_templates would not show up without clearing the cache. * Bug fix in handling BlankScene * Make checkpoint_states an instance variable of CheckpointManager As per https://github.com/3b1b/manim/issues/2272 * Move resizing out of Window.focus, and into Window.init_for_scene * Make default output directory "." instead of "" To address https://github.com/3b1b/manim/issues/2261 * Remove input_file_path arg from SceneFileWriter * Use Dict syntax in place of dict for config more consistently across config.py * Simplify get_output_directory * Swap order of preamble and additional preamble
2024-12-12 20:45:34 -06:00
log_path = tex_path.with_suffix(".log")
if log_path.exists():
content = log_path.read_text()
error_match = re.search(r"(?<=\n! ).*\n.*\n", content)
if error_match:
error_str = error_match.group()
2024-12-04 20:30:53 -06:00
raise LatexError(error_str or "LaTeX compilation failed")
# Run dvisvgm and capture output directly
process = subprocess.run(
[
"dvisvgm",
dvi_path,
"-n", # no fonts
"-v", "0", # quiet
"--stdout", # output to stdout instead of file
],
capture_output=True
2022-05-22 23:47:45 +08:00
)
2024-12-04 20:30:53 -06:00
# Return SVG string
result = process.stdout.decode('utf-8')
A few bug fixes (#2277) * Comment tweak * Directly print traceback Since the shell.showtraceback is giving some issues * Make InteracrtiveSceneEmbed into a class This way it can keep track of it's internal shell; use of get_ipython has a finicky relationship with reloading. * Move remaining checkpoint_paste logic into scene_embed.py This involved making a few context managers for Scene: temp_record, temp_skip, temp_progress_bar, which seem useful in and of themselves. * Change null key to be the empty string * Ensure temporary svg paths for Text are deleted * Remove unused dict_ops.py functions * Remove break_into_partial_movies from file_writer configuration * Rewrite guarantee_existence using Path * Clean up SceneFileWriter It had a number of vestigial functions no longer used, and some setup that could be made more organized. * Remove --save_pngs CLI arg (which did nothing) * Add --subdivide CLI arg * Remove add_extension_if_not_present * Remove get_sorted_integer_files * Have find_file return Path * Minor clean up * Clean up num_tex_symbols * Fix find_file * Minor cleanup for extract_scene.py * Add preview_frame_while_skipping option to scene config * Use shell.showtraceback function * Move keybindings to config, instead of in-place constants * Replace DEGREES -> DEG * Add arg to clear the cache * Separate out full_tex_to_svg from tex_to_svg And only cache to disk the results of full_tex_to_svg. Otherwise, making edits to the tex_templates would not show up without clearing the cache. * Bug fix in handling BlankScene * Make checkpoint_states an instance variable of CheckpointManager As per https://github.com/3b1b/manim/issues/2272 * Move resizing out of Window.focus, and into Window.init_for_scene * Make default output directory "." instead of "" To address https://github.com/3b1b/manim/issues/2261 * Remove input_file_path arg from SceneFileWriter * Use Dict syntax in place of dict for config more consistently across config.py * Simplify get_output_directory * Swap order of preamble and additional preamble
2024-12-12 20:45:34 -06:00
if message:
print(" " * len(message), end="\r")
return result
2022-05-22 23:47:45 +08:00
class LatexError(Exception):
pass