mirror of
https://github.com/3b1b/manim.git
synced 2025-04-13 09:47:07 +00:00
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
This commit is contained in:
parent
3d9a0cd25e
commit
f427fc67df
5 changed files with 80 additions and 85 deletions
|
@ -5,6 +5,7 @@ from manimlib import __version__
|
|||
from manimlib.config import manim_config
|
||||
from manimlib.config import parse_cli
|
||||
import manimlib.extract_scene
|
||||
from manimlib.utils.cache import clear_cache
|
||||
from manimlib.window import Window
|
||||
|
||||
|
||||
|
@ -54,6 +55,8 @@ def main():
|
|||
args = parse_cli()
|
||||
if args.version and args.file is None:
|
||||
return
|
||||
if args.clear_cache:
|
||||
clear_cache()
|
||||
|
||||
run_scenes()
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ import inspect
|
|||
import os
|
||||
import sys
|
||||
import yaml
|
||||
from pathlib import Path
|
||||
from ast import literal_eval
|
||||
from addict import Dict
|
||||
|
||||
|
@ -31,11 +32,11 @@ def initialize_manim_config() -> Dict:
|
|||
"""
|
||||
args = parse_cli()
|
||||
global_defaults_file = os.path.join(get_manim_dir(), "manimlib", "default_config.yml")
|
||||
config = merge_dicts_recursively(
|
||||
config = Dict(merge_dicts_recursively(
|
||||
load_yaml(global_defaults_file),
|
||||
load_yaml("custom_config.yml"), # From current working directory
|
||||
load_yaml(args.config_file) if args.config_file else dict(),
|
||||
)
|
||||
))
|
||||
|
||||
log.setLevel(args.log_level or config["log_level"])
|
||||
|
||||
|
@ -47,7 +48,7 @@ def initialize_manim_config() -> Dict:
|
|||
update_run_config(config, args)
|
||||
update_embed_config(config, args)
|
||||
|
||||
return Dict(config)
|
||||
return config
|
||||
|
||||
|
||||
def parse_cli():
|
||||
|
@ -211,6 +212,11 @@ def parse_cli():
|
|||
"--log-level",
|
||||
help="Level of messages to Display, can be DEBUG / INFO / WARNING / ERROR / CRITICAL"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--clear-cache",
|
||||
action="store_true",
|
||||
help="Erase the cache used for Tex and Text Mobjects"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--autoreload",
|
||||
action="store_true",
|
||||
|
@ -225,41 +231,41 @@ def parse_cli():
|
|||
sys.exit(2)
|
||||
|
||||
|
||||
def update_directory_config(config: dict):
|
||||
dir_config = config["directories"]
|
||||
base = dir_config['base']
|
||||
for key, subdir in dir_config['subdirs'].items():
|
||||
def update_directory_config(config: Dict):
|
||||
dir_config = config.directories
|
||||
base = dir_config.base
|
||||
for key, subdir in dir_config.subdirs.items():
|
||||
dir_config[key] = os.path.join(base, subdir)
|
||||
|
||||
|
||||
def update_window_config(config: dict, args: Namespace):
|
||||
window_config = config["window"]
|
||||
def update_window_config(config: Dict, args: Namespace):
|
||||
window_config = config.window
|
||||
for key in "position", "size":
|
||||
if window_config.get(key):
|
||||
window_config[key] = literal_eval(window_config[key])
|
||||
if args.full_screen:
|
||||
window_config["full_screen"] = True
|
||||
window_config.full_screen = True
|
||||
|
||||
|
||||
def update_camera_config(config: dict, args: Namespace):
|
||||
camera_config = config["camera"]
|
||||
arg_resolution = get_resolution_from_args(args, config["resolution_options"])
|
||||
camera_config["resolution"] = arg_resolution or literal_eval(camera_config["resolution"])
|
||||
def update_camera_config(config: Dict, args: Namespace):
|
||||
camera_config = config.camera
|
||||
arg_resolution = get_resolution_from_args(args, config.resolution_options)
|
||||
camera_config.resolution = arg_resolution or literal_eval(camera_config.resolution)
|
||||
if args.fps:
|
||||
camera_config["fps"] = args.fps
|
||||
camera_config.fps = args.fps
|
||||
if args.color:
|
||||
try:
|
||||
camera_config["background_color"] = colour.Color(args.color)
|
||||
camera_config.background_color = colour.Color(args.color)
|
||||
except Exception:
|
||||
log.error("Please use a valid color")
|
||||
log.error(err)
|
||||
sys.exit(2)
|
||||
if args.transparent:
|
||||
camera_config["background_opacity"] = 0.0
|
||||
camera_config.background_opacity = 0.0
|
||||
|
||||
|
||||
def update_file_writer_config(config: dict, args: Namespace):
|
||||
file_writer_config = config["file_writer"]
|
||||
def update_file_writer_config(config: Dict, args: Namespace):
|
||||
file_writer_config = config.file_writer
|
||||
file_writer_config.update(
|
||||
write_to_movie=(not args.skip_animations and args.write_file),
|
||||
subdivide_output=args.subdivide,
|
||||
|
@ -268,26 +274,25 @@ def update_file_writer_config(config: dict, args: Namespace):
|
|||
movie_file_extension=(get_file_ext(args)),
|
||||
output_directory=get_output_directory(args, config),
|
||||
file_name=args.file_name,
|
||||
input_file_path=args.file or "",
|
||||
open_file_upon_completion=args.open,
|
||||
show_file_location_upon_completion=args.finder,
|
||||
quiet=args.quiet,
|
||||
)
|
||||
|
||||
if args.vcodec:
|
||||
file_writer_config["video_codec"] = args.vcodec
|
||||
file_writer_config.video_codec = args.vcodec
|
||||
elif args.transparent:
|
||||
file_writer_config["video_codec"] = 'prores_ks'
|
||||
file_writer_config["pixel_format"] = ''
|
||||
file_writer_config.video_codec = 'prores_ks'
|
||||
file_writer_config.pixel_format = ''
|
||||
elif args.gif:
|
||||
file_writer_config["video_codec"] = ''
|
||||
file_writer_config.video_codec = ''
|
||||
|
||||
if args.pix_fmt:
|
||||
file_writer_config["pixel_format"] = args.pix_fmt
|
||||
file_writer_config.pixel_format = args.pix_fmt
|
||||
|
||||
|
||||
def update_scene_config(config: dict, args: Namespace):
|
||||
scene_config = config["scene"]
|
||||
def update_scene_config(config: Dict, args: Namespace):
|
||||
scene_config = config.scene
|
||||
start, end = get_animations_numbers(args)
|
||||
scene_config.update(
|
||||
# Note, Scene.__init__ makes use of both manimlib.camera and
|
||||
|
@ -301,13 +306,13 @@ def update_scene_config(config: dict, args: Namespace):
|
|||
presenter_mode=args.presenter_mode,
|
||||
)
|
||||
if args.leave_progress_bars:
|
||||
scene_config["leave_progress_bars"] = True
|
||||
scene_config.leave_progress_bars = True
|
||||
if args.show_animation_progress:
|
||||
scene_config["show_animation_progress"] = True
|
||||
scene_config.show_animation_progress = True
|
||||
|
||||
|
||||
def update_run_config(config: dict, args: Namespace):
|
||||
config["run"] = dict(
|
||||
def update_run_config(config: Dict, args: Namespace):
|
||||
config.run = Dict(
|
||||
file_name=args.file,
|
||||
embed_line=(int(args.embed) if args.embed is not None else None),
|
||||
is_reload=False,
|
||||
|
@ -319,9 +324,9 @@ def update_run_config(config: dict, args: Namespace):
|
|||
)
|
||||
|
||||
|
||||
def update_embed_config(config: dict, args: Namespace):
|
||||
def update_embed_config(config: Dict, args: Namespace):
|
||||
if args.autoreload:
|
||||
config["embed"]["autoreload"] = True
|
||||
config.embed.autoreload = True
|
||||
|
||||
|
||||
# Helpers for the functions above
|
||||
|
@ -375,17 +380,15 @@ def get_animations_numbers(args: Namespace) -> tuple[int | None, int | None]:
|
|||
return int(stan), None
|
||||
|
||||
|
||||
def get_output_directory(args: Namespace, config: dict) -> str:
|
||||
dir_config = config["directories"]
|
||||
output_directory = args.video_dir or dir_config["output"]
|
||||
if dir_config["mirror_module_path"] and args.file:
|
||||
to_cut = dir_config["removed_mirror_prefix"]
|
||||
ext = os.path.abspath(args.file)
|
||||
ext = ext.replace(to_cut, "").replace(".py", "")
|
||||
if ext.startswith("_"):
|
||||
ext = ext[1:]
|
||||
output_directory = os.path.join(output_directory, ext)
|
||||
return output_directory
|
||||
def get_output_directory(args: Namespace, config: Dict) -> str:
|
||||
dir_config = config.directories
|
||||
out_dir = args.video_dir or dir_config.output
|
||||
if dir_config.mirror_module_path and args.file:
|
||||
file_path = Path(args.file).absolute()
|
||||
rel_path = file_path.relative_to(dir_config.removed_mirror_prefix)
|
||||
rel_path = Path(str(rel_path).lstrip("_"))
|
||||
out_dir = Path(out_dir, rel_path).with_suffix("")
|
||||
return out_dir
|
||||
|
||||
|
||||
# Create global configuration
|
||||
|
|
|
@ -111,7 +111,7 @@ def get_scenes_to_render(all_scene_classes: list, scene_config: Dict, run_config
|
|||
def get_scene_classes(module: Optional[Module]):
|
||||
if module is None:
|
||||
# If no module was passed in, just play the blank scene
|
||||
return [BlankScene(**scene_config)]
|
||||
return [BlankScene]
|
||||
if hasattr(module, "SCENES_IN_ORDER"):
|
||||
return module.SCENES_IN_ORDER
|
||||
else:
|
||||
|
|
|
@ -34,10 +34,8 @@ class SceneFileWriter(object):
|
|||
png_mode: str = "RGBA",
|
||||
save_last_frame: bool = False,
|
||||
movie_file_extension: str = ".mp4",
|
||||
# What python file is generating this scene
|
||||
input_file_path: str = "",
|
||||
# Where should this be written
|
||||
output_directory: str = "",
|
||||
output_directory: str = ".",
|
||||
file_name: str | None = None,
|
||||
open_file_upon_completion: bool = False,
|
||||
show_file_location_upon_completion: bool = False,
|
||||
|
@ -57,7 +55,6 @@ class SceneFileWriter(object):
|
|||
self.png_mode = png_mode
|
||||
self.save_last_frame = save_last_frame
|
||||
self.movie_file_extension = movie_file_extension
|
||||
self.input_file_path = input_file_path
|
||||
self.output_directory = output_directory
|
||||
self.file_name = file_name
|
||||
self.open_file_upon_completion = open_file_upon_completion
|
||||
|
|
|
@ -31,22 +31,13 @@ def get_tex_template_config(template_name: str) -> dict[str, str]:
|
|||
|
||||
|
||||
@lru_cache
|
||||
def get_tex_config(template: str = "") -> dict[str, str]:
|
||||
def get_tex_config(template: str = "") -> tuple[str, str]:
|
||||
"""
|
||||
Returns a dict which should look something like this:
|
||||
{
|
||||
"template": "default",
|
||||
"compiler": "latex",
|
||||
"preamble": "..."
|
||||
}
|
||||
Returns a compiler and preamble to use for rendering LaTeX
|
||||
"""
|
||||
template = template or manim_config.tex.template
|
||||
template_config = get_tex_template_config(template)
|
||||
return {
|
||||
"template": template,
|
||||
"compiler": template_config["compiler"],
|
||||
"preamble": template_config["preamble"]
|
||||
}
|
||||
config = get_tex_template_config(template)
|
||||
return config["compiler"], config["preamble"]
|
||||
|
||||
|
||||
def get_full_tex(content: str, preamble: str = ""):
|
||||
|
@ -60,7 +51,6 @@ def get_full_tex(content: str, preamble: str = ""):
|
|||
|
||||
|
||||
@lru_cache(maxsize=128)
|
||||
@cache_on_disk
|
||||
def latex_to_svg(
|
||||
latex: str,
|
||||
template: str = "",
|
||||
|
@ -83,14 +73,21 @@ def latex_to_svg(
|
|||
NotImplementedError: If compiler is not supported
|
||||
"""
|
||||
if show_message_during_execution:
|
||||
max_message_len = 80
|
||||
message = f"Writing {short_tex or latex}"
|
||||
if len(message) > max_message_len:
|
||||
message = message[:max_message_len - 3] + "..."
|
||||
print(message, end="\r")
|
||||
message = f"Writing {(short_tex or latex)[:70]}..."
|
||||
else:
|
||||
message = ""
|
||||
|
||||
tex_config = get_tex_config(template)
|
||||
compiler = tex_config["compiler"]
|
||||
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)
|
||||
|
||||
|
||||
@cache_on_disk
|
||||
def full_tex_to_svg(full_tex: str, compiler: str = "latex", message: str = ""):
|
||||
if message:
|
||||
print(message, end="\r")
|
||||
|
||||
if compiler == "latex":
|
||||
dvi_ext = ".dvi"
|
||||
|
@ -99,17 +96,13 @@ def latex_to_svg(
|
|||
else:
|
||||
raise NotImplementedError(f"Compiler '{compiler}' is not implemented")
|
||||
|
||||
preamble = tex_config["preamble"] + "\n" + additional_preamble
|
||||
full_tex = get_full_tex(latex, preamble)
|
||||
|
||||
# Write intermediate files to a temporary directory
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
base_path = os.path.join(temp_dir, "working")
|
||||
tex_path = base_path + ".tex"
|
||||
dvi_path = base_path + dvi_ext
|
||||
tex_path = Path(temp_dir, "working").with_suffix(".tex")
|
||||
dvi_path = tex_path.with_suffix(dvi_ext)
|
||||
|
||||
# Write tex file
|
||||
Path(tex_path).write_text(full_tex)
|
||||
tex_path.write_text(full_tex)
|
||||
|
||||
# Run latex compiler
|
||||
process = subprocess.run(
|
||||
|
@ -128,13 +121,12 @@ def latex_to_svg(
|
|||
if process.returncode != 0:
|
||||
# Handle error
|
||||
error_str = ""
|
||||
log_path = base_path + ".log"
|
||||
if os.path.exists(log_path):
|
||||
with open(log_path, "r", encoding="utf-8") as log_file:
|
||||
content = log_file.read()
|
||||
error_match = re.search(r"(?<=\n! ).*\n.*\n", content)
|
||||
if error_match:
|
||||
error_str = error_match.group()
|
||||
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()
|
||||
raise LatexError(error_str or "LaTeX compilation failed")
|
||||
|
||||
# Run dvisvgm and capture output directly
|
||||
|
@ -152,7 +144,7 @@ def latex_to_svg(
|
|||
# Return SVG string
|
||||
result = process.stdout.decode('utf-8')
|
||||
|
||||
if show_message_during_execution:
|
||||
if message:
|
||||
print(" " * len(message), end="\r")
|
||||
|
||||
return result
|
||||
|
|
Loading…
Add table
Reference in a new issue