Skip to content

Commit

Permalink
A few bug fixes (#2277)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
3b1b authored Dec 13, 2024
1 parent 3d9a0cd commit f427fc6
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 85 deletions.
3 changes: 3 additions & 0 deletions manimlib/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
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


Expand Down Expand Up @@ -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()

Expand Down
91 changes: 47 additions & 44 deletions manimlib/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import os
import sys
import yaml
from pathlib import Path
from ast import literal_eval
from addict import Dict

Expand All @@ -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"])

Expand All @@ -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():
Expand Down Expand Up @@ -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",
Expand All @@ -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,
Expand All @@ -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
Expand All @@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion manimlib/extract_scene.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
5 changes: 1 addition & 4 deletions manimlib/scene/scene_file_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,8 @@ def __init__(
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,
Expand All @@ -57,7 +55,6 @@ def __init__(
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
Expand Down
64 changes: 28 additions & 36 deletions manimlib/utils/tex_file_writing.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = ""):
Expand All @@ -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 = "",
Expand All @@ -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 = ""

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)

tex_config = get_tex_config(template)
compiler = tex_config["compiler"]

@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"
Expand All @@ -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(
Expand All @@ -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
Expand All @@ -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
Expand Down

0 comments on commit f427fc6

Please sign in to comment.