Skip to content

Commit f427fc6

Browse files
authored
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 #2272 * Move resizing out of Window.focus, and into Window.init_for_scene * Make default output directory "." instead of "" To address #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
1 parent 3d9a0cd commit f427fc6

File tree

5 files changed

+80
-85
lines changed

5 files changed

+80
-85
lines changed

manimlib/__main__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from manimlib.config import manim_config
66
from manimlib.config import parse_cli
77
import manimlib.extract_scene
8+
from manimlib.utils.cache import clear_cache
89
from manimlib.window import Window
910

1011

@@ -54,6 +55,8 @@ def main():
5455
args = parse_cli()
5556
if args.version and args.file is None:
5657
return
58+
if args.clear_cache:
59+
clear_cache()
5760

5861
run_scenes()
5962

manimlib/config.py

Lines changed: 47 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import os
88
import sys
99
import yaml
10+
from pathlib import Path
1011
from ast import literal_eval
1112
from addict import Dict
1213

@@ -31,11 +32,11 @@ def initialize_manim_config() -> Dict:
3132
"""
3233
args = parse_cli()
3334
global_defaults_file = os.path.join(get_manim_dir(), "manimlib", "default_config.yml")
34-
config = merge_dicts_recursively(
35+
config = Dict(merge_dicts_recursively(
3536
load_yaml(global_defaults_file),
3637
load_yaml("custom_config.yml"), # From current working directory
3738
load_yaml(args.config_file) if args.config_file else dict(),
38-
)
39+
))
3940

4041
log.setLevel(args.log_level or config["log_level"])
4142

@@ -47,7 +48,7 @@ def initialize_manim_config() -> Dict:
4748
update_run_config(config, args)
4849
update_embed_config(config, args)
4950

50-
return Dict(config)
51+
return config
5152

5253

5354
def parse_cli():
@@ -211,6 +212,11 @@ def parse_cli():
211212
"--log-level",
212213
help="Level of messages to Display, can be DEBUG / INFO / WARNING / ERROR / CRITICAL"
213214
)
215+
parser.add_argument(
216+
"--clear-cache",
217+
action="store_true",
218+
help="Erase the cache used for Tex and Text Mobjects"
219+
)
214220
parser.add_argument(
215221
"--autoreload",
216222
action="store_true",
@@ -225,41 +231,41 @@ def parse_cli():
225231
sys.exit(2)
226232

227233

228-
def update_directory_config(config: dict):
229-
dir_config = config["directories"]
230-
base = dir_config['base']
231-
for key, subdir in dir_config['subdirs'].items():
234+
def update_directory_config(config: Dict):
235+
dir_config = config.directories
236+
base = dir_config.base
237+
for key, subdir in dir_config.subdirs.items():
232238
dir_config[key] = os.path.join(base, subdir)
233239

234240

235-
def update_window_config(config: dict, args: Namespace):
236-
window_config = config["window"]
241+
def update_window_config(config: Dict, args: Namespace):
242+
window_config = config.window
237243
for key in "position", "size":
238244
if window_config.get(key):
239245
window_config[key] = literal_eval(window_config[key])
240246
if args.full_screen:
241-
window_config["full_screen"] = True
247+
window_config.full_screen = True
242248

243249

244-
def update_camera_config(config: dict, args: Namespace):
245-
camera_config = config["camera"]
246-
arg_resolution = get_resolution_from_args(args, config["resolution_options"])
247-
camera_config["resolution"] = arg_resolution or literal_eval(camera_config["resolution"])
250+
def update_camera_config(config: Dict, args: Namespace):
251+
camera_config = config.camera
252+
arg_resolution = get_resolution_from_args(args, config.resolution_options)
253+
camera_config.resolution = arg_resolution or literal_eval(camera_config.resolution)
248254
if args.fps:
249-
camera_config["fps"] = args.fps
255+
camera_config.fps = args.fps
250256
if args.color:
251257
try:
252-
camera_config["background_color"] = colour.Color(args.color)
258+
camera_config.background_color = colour.Color(args.color)
253259
except Exception:
254260
log.error("Please use a valid color")
255261
log.error(err)
256262
sys.exit(2)
257263
if args.transparent:
258-
camera_config["background_opacity"] = 0.0
264+
camera_config.background_opacity = 0.0
259265

260266

261-
def update_file_writer_config(config: dict, args: Namespace):
262-
file_writer_config = config["file_writer"]
267+
def update_file_writer_config(config: Dict, args: Namespace):
268+
file_writer_config = config.file_writer
263269
file_writer_config.update(
264270
write_to_movie=(not args.skip_animations and args.write_file),
265271
subdivide_output=args.subdivide,
@@ -268,26 +274,25 @@ def update_file_writer_config(config: dict, args: Namespace):
268274
movie_file_extension=(get_file_ext(args)),
269275
output_directory=get_output_directory(args, config),
270276
file_name=args.file_name,
271-
input_file_path=args.file or "",
272277
open_file_upon_completion=args.open,
273278
show_file_location_upon_completion=args.finder,
274279
quiet=args.quiet,
275280
)
276281

277282
if args.vcodec:
278-
file_writer_config["video_codec"] = args.vcodec
283+
file_writer_config.video_codec = args.vcodec
279284
elif args.transparent:
280-
file_writer_config["video_codec"] = 'prores_ks'
281-
file_writer_config["pixel_format"] = ''
285+
file_writer_config.video_codec = 'prores_ks'
286+
file_writer_config.pixel_format = ''
282287
elif args.gif:
283-
file_writer_config["video_codec"] = ''
288+
file_writer_config.video_codec = ''
284289

285290
if args.pix_fmt:
286-
file_writer_config["pixel_format"] = args.pix_fmt
291+
file_writer_config.pixel_format = args.pix_fmt
287292

288293

289-
def update_scene_config(config: dict, args: Namespace):
290-
scene_config = config["scene"]
294+
def update_scene_config(config: Dict, args: Namespace):
295+
scene_config = config.scene
291296
start, end = get_animations_numbers(args)
292297
scene_config.update(
293298
# Note, Scene.__init__ makes use of both manimlib.camera and
@@ -301,13 +306,13 @@ def update_scene_config(config: dict, args: Namespace):
301306
presenter_mode=args.presenter_mode,
302307
)
303308
if args.leave_progress_bars:
304-
scene_config["leave_progress_bars"] = True
309+
scene_config.leave_progress_bars = True
305310
if args.show_animation_progress:
306-
scene_config["show_animation_progress"] = True
311+
scene_config.show_animation_progress = True
307312

308313

309-
def update_run_config(config: dict, args: Namespace):
310-
config["run"] = dict(
314+
def update_run_config(config: Dict, args: Namespace):
315+
config.run = Dict(
311316
file_name=args.file,
312317
embed_line=(int(args.embed) if args.embed is not None else None),
313318
is_reload=False,
@@ -319,9 +324,9 @@ def update_run_config(config: dict, args: Namespace):
319324
)
320325

321326

322-
def update_embed_config(config: dict, args: Namespace):
327+
def update_embed_config(config: Dict, args: Namespace):
323328
if args.autoreload:
324-
config["embed"]["autoreload"] = True
329+
config.embed.autoreload = True
325330

326331

327332
# Helpers for the functions above
@@ -375,17 +380,15 @@ def get_animations_numbers(args: Namespace) -> tuple[int | None, int | None]:
375380
return int(stan), None
376381

377382

378-
def get_output_directory(args: Namespace, config: dict) -> str:
379-
dir_config = config["directories"]
380-
output_directory = args.video_dir or dir_config["output"]
381-
if dir_config["mirror_module_path"] and args.file:
382-
to_cut = dir_config["removed_mirror_prefix"]
383-
ext = os.path.abspath(args.file)
384-
ext = ext.replace(to_cut, "").replace(".py", "")
385-
if ext.startswith("_"):
386-
ext = ext[1:]
387-
output_directory = os.path.join(output_directory, ext)
388-
return output_directory
383+
def get_output_directory(args: Namespace, config: Dict) -> str:
384+
dir_config = config.directories
385+
out_dir = args.video_dir or dir_config.output
386+
if dir_config.mirror_module_path and args.file:
387+
file_path = Path(args.file).absolute()
388+
rel_path = file_path.relative_to(dir_config.removed_mirror_prefix)
389+
rel_path = Path(str(rel_path).lstrip("_"))
390+
out_dir = Path(out_dir, rel_path).with_suffix("")
391+
return out_dir
389392

390393

391394
# Create global configuration

manimlib/extract_scene.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ def get_scenes_to_render(all_scene_classes: list, scene_config: Dict, run_config
111111
def get_scene_classes(module: Optional[Module]):
112112
if module is None:
113113
# If no module was passed in, just play the blank scene
114-
return [BlankScene(**scene_config)]
114+
return [BlankScene]
115115
if hasattr(module, "SCENES_IN_ORDER"):
116116
return module.SCENES_IN_ORDER
117117
else:

manimlib/scene/scene_file_writer.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,8 @@ def __init__(
3434
png_mode: str = "RGBA",
3535
save_last_frame: bool = False,
3636
movie_file_extension: str = ".mp4",
37-
# What python file is generating this scene
38-
input_file_path: str = "",
3937
# Where should this be written
40-
output_directory: str = "",
38+
output_directory: str = ".",
4139
file_name: str | None = None,
4240
open_file_upon_completion: bool = False,
4341
show_file_location_upon_completion: bool = False,
@@ -57,7 +55,6 @@ def __init__(
5755
self.png_mode = png_mode
5856
self.save_last_frame = save_last_frame
5957
self.movie_file_extension = movie_file_extension
60-
self.input_file_path = input_file_path
6158
self.output_directory = output_directory
6259
self.file_name = file_name
6360
self.open_file_upon_completion = open_file_upon_completion

manimlib/utils/tex_file_writing.py

Lines changed: 28 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -31,22 +31,13 @@ def get_tex_template_config(template_name: str) -> dict[str, str]:
3131

3232

3333
@lru_cache
34-
def get_tex_config(template: str = "") -> dict[str, str]:
34+
def get_tex_config(template: str = "") -> tuple[str, str]:
3535
"""
36-
Returns a dict which should look something like this:
37-
{
38-
"template": "default",
39-
"compiler": "latex",
40-
"preamble": "..."
41-
}
36+
Returns a compiler and preamble to use for rendering LaTeX
4237
"""
4338
template = template or manim_config.tex.template
44-
template_config = get_tex_template_config(template)
45-
return {
46-
"template": template,
47-
"compiler": template_config["compiler"],
48-
"preamble": template_config["preamble"]
49-
}
39+
config = get_tex_template_config(template)
40+
return config["compiler"], config["preamble"]
5041

5142

5243
def get_full_tex(content: str, preamble: str = ""):
@@ -60,7 +51,6 @@ def get_full_tex(content: str, preamble: str = ""):
6051

6152

6253
@lru_cache(maxsize=128)
63-
@cache_on_disk
6454
def latex_to_svg(
6555
latex: str,
6656
template: str = "",
@@ -83,14 +73,21 @@ def latex_to_svg(
8373
NotImplementedError: If compiler is not supported
8474
"""
8575
if show_message_during_execution:
86-
max_message_len = 80
87-
message = f"Writing {short_tex or latex}"
88-
if len(message) > max_message_len:
89-
message = message[:max_message_len - 3] + "..."
90-
print(message, end="\r")
76+
message = f"Writing {(short_tex or latex)[:70]}..."
77+
else:
78+
message = ""
79+
80+
compiler, preamble = get_tex_config(template)
81+
82+
preamble = "\n".join([preamble, additional_preamble])
83+
full_tex = get_full_tex(latex, preamble)
84+
return full_tex_to_svg(full_tex, compiler, message)
9185

92-
tex_config = get_tex_config(template)
93-
compiler = tex_config["compiler"]
86+
87+
@cache_on_disk
88+
def full_tex_to_svg(full_tex: str, compiler: str = "latex", message: str = ""):
89+
if message:
90+
print(message, end="\r")
9491

9592
if compiler == "latex":
9693
dvi_ext = ".dvi"
@@ -99,17 +96,13 @@ def latex_to_svg(
9996
else:
10097
raise NotImplementedError(f"Compiler '{compiler}' is not implemented")
10198

102-
preamble = tex_config["preamble"] + "\n" + additional_preamble
103-
full_tex = get_full_tex(latex, preamble)
104-
10599
# Write intermediate files to a temporary directory
106100
with tempfile.TemporaryDirectory() as temp_dir:
107-
base_path = os.path.join(temp_dir, "working")
108-
tex_path = base_path + ".tex"
109-
dvi_path = base_path + dvi_ext
101+
tex_path = Path(temp_dir, "working").with_suffix(".tex")
102+
dvi_path = tex_path.with_suffix(dvi_ext)
110103

111104
# Write tex file
112-
Path(tex_path).write_text(full_tex)
105+
tex_path.write_text(full_tex)
113106

114107
# Run latex compiler
115108
process = subprocess.run(
@@ -128,13 +121,12 @@ def latex_to_svg(
128121
if process.returncode != 0:
129122
# Handle error
130123
error_str = ""
131-
log_path = base_path + ".log"
132-
if os.path.exists(log_path):
133-
with open(log_path, "r", encoding="utf-8") as log_file:
134-
content = log_file.read()
135-
error_match = re.search(r"(?<=\n! ).*\n.*\n", content)
136-
if error_match:
137-
error_str = error_match.group()
124+
log_path = tex_path.with_suffix(".log")
125+
if log_path.exists():
126+
content = log_path.read_text()
127+
error_match = re.search(r"(?<=\n! ).*\n.*\n", content)
128+
if error_match:
129+
error_str = error_match.group()
138130
raise LatexError(error_str or "LaTeX compilation failed")
139131

140132
# Run dvisvgm and capture output directly
@@ -152,7 +144,7 @@ def latex_to_svg(
152144
# Return SVG string
153145
result = process.stdout.decode('utf-8')
154146

155-
if show_message_during_execution:
147+
if message:
156148
print(" " * len(message), end="\r")
157149

158150
return result

0 commit comments

Comments
 (0)