Skip to content

Commit 5e60b55

Browse files
committed
Completely rewritten TeX template management
1 parent a29eabd commit 5e60b55

File tree

8 files changed

+450
-43
lines changed

8 files changed

+450
-43
lines changed

example_scenes/customtex.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from manim import *
2+
3+
class ExampleFileScene(Scene):
4+
def construct(self):
5+
text=TexMobject(r"\vv{vb}")
6+
#text=TextMobject(r"$\vv{vb}$")
7+
self.play(Write(text))
8+
9+
class ExampleScene(Scene):
10+
def construct(self):
11+
tpl=TexTemplate()
12+
tpl.append_package(["esvect",["f"]])
13+
config.register_tex_template(tpl)
14+
15+
#text=TextMobject(r"$\vv{vb}$")
16+
text=TexMobject(r"\vv{vb}")
17+
self.play(Write(text))

manim/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,4 @@
6868
from .utils.sounds import *
6969
from .utils.space_ops import *
7070
from .utils.strings import *
71+
from .utils.tex import *

manim/__main__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ def main():
77
args = config.parse_cli()
88
cfg = config.get_configuration(args)
99
config.initialize_directories(cfg)
10+
config.initialize_tex(cfg)
1011
extract_scene.main(cfg)
1112

1213

manim/config.py

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@
33
import os
44
import sys
55
import types
6+
import manim.constants as consts
67

8+
from .utils.tex import *
79
from . import constants
810
from . import dirs
911
from .logger import logger
1012

11-
__all__ = ["parse_cli", "get_configuration", "initialize_directories"]
13+
__all__ = ["parse_cli", "get_configuration", "initialize_directories","register_tex_template","initialize_tex"]
1214

1315

1416
def parse_cli():
@@ -140,7 +142,12 @@ def parse_cli():
140142
"--text_dir",
141143
help="Directory to write text",
142144
)
145+
parser.add_argument(
146+
"--tex_template",
147+
help="Specify a custom TeX template file",
148+
)
143149
return parser.parse_args()
150+
144151
except argparse.ArgumentError as err:
145152
logger.error(str(err))
146153
sys.exit(2)
@@ -176,6 +183,7 @@ def get_configuration(args):
176183
"video_dir": args.video_dir,
177184
"tex_dir": args.tex_dir,
178185
"text_dir": args.text_dir,
186+
"tex_template": args.tex_template,
179187
}
180188

181189
# Camera configuration
@@ -281,3 +289,39 @@ def initialize_directories(config):
281289
dirs.VIDEO_DIR = dir_config["video_dir"]
282290
dirs.TEX_DIR = dir_config["tex_dir"]
283291
dirs.TEXT_DIR = dir_config["text_dir"]
292+
293+
def register_tex_template(tpl):
294+
"""Register the given LaTeX template for later use.
295+
296+
Parameters
297+
----------
298+
tpl : :class:`~.TexTemplate`
299+
The LaTeX template to register.
300+
"""
301+
consts.TEX_TEMPLATE = tpl
302+
303+
def initialize_tex(config):
304+
"""Safely create a LaTeX template object from a file.
305+
If file is not readable, the default template file is used.
306+
307+
Parameters
308+
----------
309+
filename : :class:`str`
310+
The name of the file with the LaTeX template.
311+
"""
312+
filename=""
313+
if config["tex_template"]:
314+
filename = os.path.expanduser(config["tex_template"])
315+
if filename and not os.access(filename, os.R_OK):
316+
# custom template not available, fallback to default
317+
logger.warning(
318+
f"Custom TeX template {filename} not found or not readable. "
319+
"Falling back to the default template."
320+
)
321+
filename = ""
322+
if filename:
323+
# still having a filename -> use the file
324+
consts.TEX_TEMPLATE = TexTemplateFromFile(filename=filename)
325+
else:
326+
# use the default template
327+
consts.TEX_TEMPLATE = TexTemplate()

manim/constants.py

Lines changed: 69 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,58 @@
22
import os
33
from .logger import logger
44

5+
MEDIA_DIR = ""
6+
VIDEO_DIR = ""
7+
VIDEO_OUTPUT_DIR = ""
8+
TEX_DIR = ""
9+
TEXT_DIR = ""
10+
TEX_TEMPLATE = None
11+
12+
def initialize_directories(config):
13+
global MEDIA_DIR
14+
global VIDEO_DIR
15+
global VIDEO_OUTPUT_DIR
16+
global TEX_DIR
17+
global TEXT_DIR
18+
19+
video_path_specified = config["video_dir"] or config["video_output_dir"]
20+
21+
if not (video_path_specified and config["tex_dir"]):
22+
if config["media_dir"]:
23+
MEDIA_DIR = config["media_dir"]
24+
else:
25+
MEDIA_DIR = os.path.join(
26+
os.path.expanduser('~'),
27+
"Dropbox (3Blue1Brown)/3Blue1Brown Team Folder"
28+
)
29+
if not os.path.isdir(MEDIA_DIR):
30+
MEDIA_DIR = "./media"
31+
print(
32+
f"Media will be written to {MEDIA_DIR + os.sep}. You can change "
33+
"this behavior with the --media_dir flag."
34+
)
35+
else:
36+
if config["media_dir"]:
37+
print(
38+
"Ignoring --media_dir, since both --tex_dir and a video "
39+
"directory were both passed"
40+
)
41+
42+
TEX_DIR = config["tex_dir"] or os.path.join(MEDIA_DIR, "Tex")
43+
TEXT_DIR = os.path.join(MEDIA_DIR, "texts")
44+
if not video_path_specified:
45+
VIDEO_DIR = os.path.join(MEDIA_DIR, "videos")
46+
VIDEO_OUTPUT_DIR = os.path.join(MEDIA_DIR, "videos")
47+
elif config["video_output_dir"]:
48+
VIDEO_OUTPUT_DIR = config["video_output_dir"]
49+
else:
50+
VIDEO_DIR = config["video_dir"]
51+
52+
for folder in [VIDEO_DIR, VIDEO_OUTPUT_DIR, TEX_DIR, TEXT_DIR]:
53+
if folder != "" and not os.path.exists(folder):
54+
os.makedirs(folder)
55+
56+
557
NOT_SETTING_FONT_MSG='''
658
You haven't set font.
759
If you are not using English, this may cause text rendering problem.
@@ -20,19 +72,23 @@ class MyText(Text):
2072
OBLIQUE = 'OBLIQUE'
2173
BOLD = 'BOLD'
2274

23-
TEX_USE_CTEX = False
24-
TEX_TEXT_TO_REPLACE = "YourTextHere"
25-
TEMPLATE_TEX_FILE = os.path.join(
26-
os.path.dirname(os.path.realpath(__file__)),
27-
"tex_template.tex" if not TEX_USE_CTEX else "ctex_template.tex"
28-
)
29-
with open(TEMPLATE_TEX_FILE, "r") as infile:
30-
TEMPLATE_TEXT_FILE_BODY = infile.read()
31-
TEMPLATE_TEX_FILE_BODY = TEMPLATE_TEXT_FILE_BODY.replace(
32-
TEX_TEXT_TO_REPLACE,
33-
"\\begin{align*}\n" + TEX_TEXT_TO_REPLACE + "\n\\end{align*}",
34-
)
35-
75+
HELP_MESSAGE = """
76+
Usage:
77+
python extract_scene.py <module> [<scene name>]
78+
-p preview in low quality
79+
-s show and save picture of last frame
80+
-w write result to file [this is default if nothing else is stated]
81+
-o <file_name> write to a different file_name
82+
-l use low quality
83+
-m use medium quality
84+
-a run and save every scene in the script, or all args for the given scene
85+
-q don't print progress
86+
-f when writing to a movie file, export the frames in png sequence
87+
-t use transperency when exporting images
88+
-n specify the number of the animation to start from
89+
-r specify a resolution
90+
-c specify a background color
91+
"""
3692
SCENE_NOT_FOUND_MESSAGE = """
3793
{} is not in the script
3894
"""

manim/mobject/svg/tex_mobject.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
from ...utils.strings import split_string_list_to_isolate_substrings
1212
from ...utils.tex_file_writing import tex_to_svg_file
1313

14-
1514
TEX_MOB_SCALE_FACTOR = 0.05
1615

1716

@@ -21,10 +20,8 @@ class TexSymbol(VMobjectFromSVGPathstring):
2120
"""
2221
pass
2322

24-
2523
class SingleStringTexMobject(SVGMobject):
2624
CONFIG = {
27-
"template_tex_file_body": TEMPLATE_TEX_FILE_BODY,
2825
"stroke_width": 0,
2926
"fill_opacity": 1.0,
3027
"background_stroke_width": 1,
@@ -33,6 +30,7 @@ class SingleStringTexMobject(SVGMobject):
3330
"height": None,
3431
"organize_left_to_right": False,
3532
"alignment": "",
33+
"type": "tex",
3634
}
3735

3836
def __init__(self, tex_string, **kwargs):
@@ -41,7 +39,7 @@ def __init__(self, tex_string, **kwargs):
4139
self.tex_string = tex_string
4240
file_name = tex_to_svg_file(
4341
self.get_modified_expression(tex_string),
44-
self.template_tex_file_body
42+
self.type
4543
)
4644
SVGMobject.__init__(self, file_name=file_name, **kwargs)
4745
if self.height is None:
@@ -247,9 +245,9 @@ def sort_alphabetically(self):
247245

248246
class TextMobject(TexMobject):
249247
CONFIG = {
250-
"template_tex_file_body": TEMPLATE_TEXT_FILE_BODY,
251248
"alignment": "\\centering",
252249
"arg_separator": "",
250+
"type": "text",
253251
}
254252

255253

@@ -258,7 +256,6 @@ class BulletedList(TextMobject):
258256
"buff": MED_LARGE_BUFF,
259257
"dot_scale_factor": 2,
260258
# Have to include because of handle_multiple_args implementation
261-
"template_tex_file_body": TEMPLATE_TEXT_FILE_BODY,
262259
"alignment": "",
263260
}
264261

@@ -325,4 +322,4 @@ def __init__(self, *text_parts, **kwargs):
325322
else:
326323
underline.set_width(self.underline_width)
327324
self.add(underline)
328-
self.underline = underline
325+
self.underline = underline

0 commit comments

Comments
 (0)