manim 0.17.0__py3-none-any.whl → 0.19.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- manim/__init__.py +11 -6
- manim/__main__.py +62 -19
- manim/_config/__init__.py +10 -9
- manim/_config/cli_colors.py +26 -9
- manim/_config/default.cfg +1 -3
- manim/_config/logger_utils.py +23 -13
- manim/_config/utils.py +662 -468
- manim/animation/animation.py +164 -18
- manim/animation/changing.py +34 -23
- manim/animation/composition.py +265 -67
- manim/animation/creation.py +208 -26
- manim/animation/fading.py +16 -18
- manim/animation/growing.py +35 -15
- manim/animation/indication.py +150 -76
- manim/animation/movement.py +56 -22
- manim/animation/numbers.py +64 -6
- manim/animation/rotation.py +78 -7
- manim/animation/specialized.py +6 -7
- manim/animation/speedmodifier.py +13 -10
- manim/animation/transform.py +14 -11
- manim/animation/transform_matching_parts.py +3 -4
- manim/animation/updaters/mobject_update_utils.py +152 -30
- manim/animation/updaters/update.py +10 -7
- manim/camera/camera.py +182 -118
- manim/camera/mapping_camera.py +34 -3
- manim/camera/moving_camera.py +95 -74
- manim/camera/multi_camera.py +23 -15
- manim/camera/three_d_camera.py +70 -52
- manim/cli/__init__.py +17 -0
- manim/cli/cfg/group.py +76 -44
- manim/cli/checkhealth/checks.py +192 -0
- manim/cli/checkhealth/commands.py +90 -0
- manim/cli/default_group.py +158 -25
- manim/cli/init/commands.py +33 -25
- manim/cli/plugins/commands.py +16 -3
- manim/cli/render/commands.py +72 -60
- manim/cli/render/ease_of_access_options.py +4 -3
- manim/cli/render/global_options.py +59 -17
- manim/cli/render/output_options.py +6 -5
- manim/cli/render/render_options.py +98 -33
- manim/constants.py +109 -59
- manim/data_structures.py +31 -0
- manim/mobject/frame.py +8 -5
- manim/mobject/geometry/__init__.py +1 -0
- manim/mobject/geometry/arc.py +277 -135
- manim/mobject/geometry/boolean_ops.py +32 -31
- manim/mobject/geometry/labeled.py +376 -0
- manim/mobject/geometry/line.py +192 -87
- manim/mobject/geometry/polygram.py +224 -58
- manim/mobject/geometry/shape_matchers.py +61 -25
- manim/mobject/geometry/tips.py +122 -48
- manim/mobject/graph.py +1027 -419
- manim/mobject/graphing/coordinate_systems.py +533 -278
- manim/mobject/graphing/functions.py +53 -32
- manim/mobject/graphing/number_line.py +123 -65
- manim/mobject/graphing/probability.py +88 -62
- manim/mobject/graphing/scale.py +33 -19
- manim/mobject/logo.py +118 -28
- manim/mobject/matrix.py +87 -83
- manim/mobject/mobject.py +912 -442
- manim/mobject/opengl/dot_cloud.py +16 -5
- manim/mobject/opengl/opengl_compatibility.py +4 -2
- manim/mobject/opengl/opengl_geometry.py +254 -153
- manim/mobject/opengl/opengl_image_mobject.py +3 -1
- manim/mobject/opengl/opengl_mobject.py +779 -482
- manim/mobject/opengl/opengl_point_cloud_mobject.py +41 -14
- manim/mobject/opengl/opengl_surface.py +14 -92
- manim/mobject/opengl/opengl_three_dimensions.py +12 -8
- manim/mobject/opengl/opengl_vectorized_mobject.py +98 -100
- manim/mobject/svg/brace.py +173 -41
- manim/mobject/svg/svg_mobject.py +139 -53
- manim/mobject/table.py +61 -68
- manim/mobject/text/code_mobject.py +193 -539
- manim/mobject/text/numbers.py +81 -34
- manim/mobject/text/tex_mobject.py +130 -78
- manim/mobject/text/text_mobject.py +288 -164
- manim/mobject/three_d/polyhedra.py +111 -13
- manim/mobject/three_d/three_d_utils.py +17 -8
- manim/mobject/three_d/three_dimensions.py +239 -106
- manim/mobject/types/image_mobject.py +50 -30
- manim/mobject/types/point_cloud_mobject.py +120 -75
- manim/mobject/types/vectorized_mobject.py +841 -408
- manim/mobject/value_tracker.py +105 -38
- manim/mobject/vector_field.py +50 -31
- manim/opengl/__init__.py +3 -3
- manim/plugins/__init__.py +14 -1
- manim/plugins/plugins_flags.py +10 -14
- manim/renderer/cairo_renderer.py +65 -50
- manim/renderer/opengl_renderer.py +89 -69
- manim/renderer/opengl_renderer_window.py +39 -18
- manim/renderer/shader.py +123 -87
- manim/renderer/shader_wrapper.py +44 -28
- manim/renderer/vectorized_mobject_rendering.py +38 -10
- manim/scene/moving_camera_scene.py +32 -3
- manim/scene/scene.py +507 -242
- manim/scene/scene_file_writer.py +371 -220
- manim/scene/section.py +20 -16
- manim/scene/three_d_scene.py +14 -22
- manim/scene/vector_space_scene.py +223 -129
- manim/scene/zoomed_scene.py +46 -41
- manim/typing.py +990 -0
- manim/utils/bezier.py +1823 -371
- manim/utils/caching.py +12 -5
- manim/utils/color/AS2700.py +236 -0
- manim/utils/color/BS381.py +318 -0
- manim/utils/color/DVIPSNAMES.py +96 -0
- manim/utils/color/SVGNAMES.py +179 -0
- manim/utils/color/X11.py +533 -0
- manim/utils/color/XKCD.py +952 -0
- manim/utils/color/__init__.py +61 -0
- manim/utils/color/core.py +1667 -0
- manim/utils/color/manim_colors.py +218 -0
- manim/utils/commands.py +48 -20
- manim/utils/config_ops.py +39 -19
- manim/utils/debug.py +8 -7
- manim/utils/deprecation.py +86 -39
- manim/utils/docbuild/__init__.py +17 -0
- manim/utils/docbuild/autoaliasattr_directive.py +236 -0
- manim/utils/docbuild/autocolor_directive.py +99 -0
- manim/utils/docbuild/manim_directive.py +94 -41
- manim/utils/docbuild/module_parsing.py +245 -0
- manim/utils/exceptions.py +6 -0
- manim/utils/family.py +5 -3
- manim/utils/family_ops.py +17 -4
- manim/utils/file_ops.py +27 -17
- manim/utils/hashing.py +55 -45
- manim/utils/images.py +13 -7
- manim/utils/ipython_magic.py +13 -7
- manim/utils/iterables.py +163 -120
- manim/utils/module_ops.py +66 -24
- manim/utils/opengl.py +77 -24
- manim/utils/parameter_parsing.py +32 -0
- manim/utils/paths.py +30 -33
- manim/utils/polylabel.py +235 -0
- manim/utils/qhull.py +218 -0
- manim/utils/rate_functions.py +98 -32
- manim/utils/simple_functions.py +25 -33
- manim/utils/sounds.py +7 -1
- manim/utils/space_ops.py +188 -115
- manim/utils/testing/__init__.py +17 -0
- manim/utils/testing/_frames_testers.py +13 -8
- manim/utils/testing/_show_diff.py +5 -3
- manim/utils/testing/_test_class_makers.py +34 -18
- manim/utils/testing/frames_comparison.py +37 -19
- manim/utils/tex.py +130 -198
- manim/utils/tex_file_writing.py +77 -47
- manim/utils/tex_templates.py +2 -1
- manim/utils/unit.py +6 -5
- {manim-0.17.0.dist-info → manim-0.19.1.dist-info}/METADATA +64 -65
- manim-0.19.1.dist-info/RECORD +220 -0
- {manim-0.17.0.dist-info → manim-0.19.1.dist-info}/WHEEL +1 -1
- manim-0.19.1.dist-info/entry_points.txt +3 -0
- {manim-0.17.0.dist-info → manim-0.19.1.dist-info/licenses}/LICENSE.community +1 -1
- manim/cli/new/group.py +0 -189
- manim/communitycolors.py +0 -9
- manim/gui/__init__.py +0 -0
- manim/gui/gui.py +0 -82
- manim/plugins/import_plugins.py +0 -43
- manim/utils/color.py +0 -552
- manim-0.17.0.dist-info/RECORD +0 -206
- manim-0.17.0.dist-info/entry_points.txt +0 -4
- /manim/cli/{new → checkhealth}/__init__.py +0 -0
- {manim-0.17.0.dist-info → manim-0.19.1.dist-info/licenses}/LICENSE +0 -0
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from typing import Any
|
|
4
5
|
|
|
6
|
+
from manim.renderer.cairo_renderer import CairoRenderer
|
|
7
|
+
from manim.renderer.opengl_renderer import OpenGLRenderer
|
|
5
8
|
from manim.scene.scene import Scene
|
|
6
9
|
from manim.scene.scene_file_writer import SceneFileWriter
|
|
10
|
+
from manim.typing import PixelArray, StrPath
|
|
7
11
|
|
|
8
12
|
from ._frames_testers import _FramesTester
|
|
9
13
|
|
|
@@ -11,13 +15,14 @@ from ._frames_testers import _FramesTester
|
|
|
11
15
|
def _make_test_scene_class(
|
|
12
16
|
base_scene: type[Scene],
|
|
13
17
|
construct_test: Callable[[Scene], None],
|
|
14
|
-
test_renderer,
|
|
18
|
+
test_renderer: CairoRenderer | OpenGLRenderer | None,
|
|
15
19
|
) -> type[Scene]:
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
20
|
+
# TODO: Get the type annotation right for the base_scene argument.
|
|
21
|
+
class _TestedScene(base_scene): # type: ignore[valid-type, misc]
|
|
22
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
23
|
+
super().__init__(*args, renderer=test_renderer, **kwargs)
|
|
19
24
|
|
|
20
|
-
def construct(self):
|
|
25
|
+
def construct(self) -> None:
|
|
21
26
|
construct_test(self)
|
|
22
27
|
|
|
23
28
|
# Manim hack to render the very last frame (normally the last frame is not the very end of the animation)
|
|
@@ -28,7 +33,7 @@ def _make_test_scene_class(
|
|
|
28
33
|
return _TestedScene
|
|
29
34
|
|
|
30
35
|
|
|
31
|
-
def _make_test_renderer_class(from_renderer):
|
|
36
|
+
def _make_test_renderer_class(from_renderer: type) -> Any:
|
|
32
37
|
# Just for inheritance.
|
|
33
38
|
class _TestRenderer(from_renderer):
|
|
34
39
|
pass
|
|
@@ -39,39 +44,50 @@ def _make_test_renderer_class(from_renderer):
|
|
|
39
44
|
class DummySceneFileWriter(SceneFileWriter):
|
|
40
45
|
"""Delegate of SceneFileWriter used to test the frames."""
|
|
41
46
|
|
|
42
|
-
def __init__(
|
|
47
|
+
def __init__(
|
|
48
|
+
self,
|
|
49
|
+
renderer: CairoRenderer | OpenGLRenderer,
|
|
50
|
+
scene_name: str,
|
|
51
|
+
**kwargs: Any,
|
|
52
|
+
) -> None:
|
|
43
53
|
super().__init__(renderer, scene_name, **kwargs)
|
|
44
54
|
self.i = 0
|
|
45
55
|
|
|
46
|
-
def init_output_directories(self, scene_name):
|
|
56
|
+
def init_output_directories(self, scene_name: str) -> None:
|
|
47
57
|
pass
|
|
48
58
|
|
|
49
|
-
def add_partial_movie_file(self, hash_animation):
|
|
59
|
+
def add_partial_movie_file(self, hash_animation: str | None) -> None:
|
|
50
60
|
pass
|
|
51
61
|
|
|
52
|
-
def begin_animation(
|
|
62
|
+
def begin_animation(
|
|
63
|
+
self, allow_write: bool = True, file_path: StrPath | None = None
|
|
64
|
+
) -> Any:
|
|
53
65
|
pass
|
|
54
66
|
|
|
55
|
-
def end_animation(self, allow_write):
|
|
67
|
+
def end_animation(self, allow_write: bool = False) -> None:
|
|
56
68
|
pass
|
|
57
69
|
|
|
58
|
-
def combine_to_movie(self):
|
|
70
|
+
def combine_to_movie(self) -> None:
|
|
59
71
|
pass
|
|
60
72
|
|
|
61
|
-
def combine_to_section_videos(self):
|
|
73
|
+
def combine_to_section_videos(self) -> None:
|
|
62
74
|
pass
|
|
63
75
|
|
|
64
|
-
def clean_cache(self):
|
|
76
|
+
def clean_cache(self) -> None:
|
|
65
77
|
pass
|
|
66
78
|
|
|
67
|
-
def write_frame(
|
|
79
|
+
def write_frame(
|
|
80
|
+
self, frame_or_renderer: PixelArray | OpenGLRenderer, num_frames: int = 1
|
|
81
|
+
) -> None:
|
|
68
82
|
self.i += 1
|
|
69
83
|
|
|
70
84
|
|
|
71
85
|
def _make_scene_file_writer_class(tester: _FramesTester) -> type[SceneFileWriter]:
|
|
72
86
|
class TestSceneFileWriter(DummySceneFileWriter):
|
|
73
|
-
def write_frame(
|
|
87
|
+
def write_frame(
|
|
88
|
+
self, frame_or_renderer: PixelArray | OpenGLRenderer, num_frames: int = 1
|
|
89
|
+
) -> None:
|
|
74
90
|
tester.check_frame(self.i, frame_or_renderer)
|
|
75
|
-
super().write_frame(frame_or_renderer)
|
|
91
|
+
super().write_frame(frame_or_renderer, num_frames=num_frames)
|
|
76
92
|
|
|
77
93
|
return TestSceneFileWriter
|
|
@@ -2,9 +2,12 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import functools
|
|
4
4
|
import inspect
|
|
5
|
+
from collections.abc import Callable
|
|
5
6
|
from pathlib import Path
|
|
6
|
-
from typing import
|
|
7
|
+
from typing import Any
|
|
7
8
|
|
|
9
|
+
import cairo
|
|
10
|
+
import pytest
|
|
8
11
|
from _pytest.fixtures import FixtureRequest
|
|
9
12
|
|
|
10
13
|
from manim import Scene
|
|
@@ -12,7 +15,9 @@ from manim._config import tempconfig
|
|
|
12
15
|
from manim._config.utils import ManimConfig
|
|
13
16
|
from manim.camera.three_d_camera import ThreeDCamera
|
|
14
17
|
from manim.renderer.cairo_renderer import CairoRenderer
|
|
18
|
+
from manim.renderer.opengl_renderer import OpenGLRenderer
|
|
15
19
|
from manim.scene.three_d_scene import ThreeDScene
|
|
20
|
+
from manim.typing import StrPath
|
|
16
21
|
|
|
17
22
|
from ._frames_testers import _ControlDataWriter, _FramesTester
|
|
18
23
|
from ._test_class_makers import (
|
|
@@ -25,16 +30,17 @@ from ._test_class_makers import (
|
|
|
25
30
|
SCENE_PARAMETER_NAME = "scene"
|
|
26
31
|
_tests_root_dir_path = Path(__file__).absolute().parents[2]
|
|
27
32
|
PATH_CONTROL_DATA = _tests_root_dir_path / Path("control_data", "graphical_units_data")
|
|
33
|
+
MIN_CAIRO_VERSION = 11800
|
|
28
34
|
|
|
29
35
|
|
|
30
36
|
def frames_comparison(
|
|
31
|
-
func=None,
|
|
37
|
+
func: Callable | None = None,
|
|
32
38
|
*,
|
|
33
39
|
last_frame: bool = True,
|
|
34
|
-
renderer_class=CairoRenderer,
|
|
35
|
-
base_scene=Scene,
|
|
36
|
-
**custom_config,
|
|
37
|
-
):
|
|
40
|
+
renderer_class: type[CairoRenderer | OpenGLRenderer] = CairoRenderer,
|
|
41
|
+
base_scene: type[Scene] = Scene,
|
|
42
|
+
**custom_config: Any,
|
|
43
|
+
) -> Callable:
|
|
38
44
|
"""Compares the frames generated by the test with control frames previously registered.
|
|
39
45
|
|
|
40
46
|
If there is no control frames for this test, the test will fail. To generate
|
|
@@ -57,7 +63,7 @@ def frames_comparison(
|
|
|
57
63
|
If the scene has a moving animation, then the test must set last_frame to False.
|
|
58
64
|
"""
|
|
59
65
|
|
|
60
|
-
def decorator_maker(tested_scene_construct):
|
|
66
|
+
def decorator_maker(tested_scene_construct: Callable) -> Callable:
|
|
61
67
|
if (
|
|
62
68
|
SCENE_PARAMETER_NAME
|
|
63
69
|
not in inspect.getfullargspec(tested_scene_construct).args
|
|
@@ -76,11 +82,20 @@ def frames_comparison(
|
|
|
76
82
|
"There is no module test name indicated for the graphical unit test. You have to declare __module_test__ in the test file.",
|
|
77
83
|
)
|
|
78
84
|
module_name = tested_scene_construct.__globals__.get("__module_test__")
|
|
85
|
+
assert isinstance(module_name, str)
|
|
79
86
|
test_name = tested_scene_construct.__name__[len("test_") :]
|
|
80
87
|
|
|
81
88
|
@functools.wraps(tested_scene_construct)
|
|
82
89
|
# The "request" parameter is meant to be used as a fixture by pytest. See below.
|
|
83
|
-
def wrapper(
|
|
90
|
+
def wrapper(
|
|
91
|
+
*args: Any, request: FixtureRequest, tmp_path: StrPath, **kwargs: Any
|
|
92
|
+
) -> None:
|
|
93
|
+
# check for cairo version
|
|
94
|
+
if (
|
|
95
|
+
renderer_class is CairoRenderer
|
|
96
|
+
and cairo.cairo_version() < MIN_CAIRO_VERSION
|
|
97
|
+
):
|
|
98
|
+
pytest.skip("Cairo version is too old. Skipping cairo graphical tests.")
|
|
84
99
|
# Wraps the test_function to a construct method, to "freeze" the eventual additional arguments (parametrizations fixtures).
|
|
85
100
|
construct = functools.partial(tested_scene_construct, *args, **kwargs)
|
|
86
101
|
|
|
@@ -137,13 +152,13 @@ def frames_comparison(
|
|
|
137
152
|
inspect.Parameter("tmp_path", inspect.Parameter.KEYWORD_ONLY),
|
|
138
153
|
]
|
|
139
154
|
new_sig = old_sig.replace(parameters=parameters)
|
|
140
|
-
wrapper.__signature__ = new_sig
|
|
155
|
+
wrapper.__signature__ = new_sig # type: ignore[attr-defined]
|
|
141
156
|
|
|
142
157
|
# Reach a bit into pytest internals to hoist the marks from our wrapped
|
|
143
158
|
# function.
|
|
144
|
-
|
|
159
|
+
wrapper.pytestmark = [] # type: ignore[attr-defined]
|
|
145
160
|
new_marks = getattr(tested_scene_construct, "pytestmark", [])
|
|
146
|
-
wrapper.pytestmark = new_marks
|
|
161
|
+
wrapper.pytestmark = new_marks # type: ignore[attr-defined]
|
|
147
162
|
return wrapper
|
|
148
163
|
|
|
149
164
|
# Case where the decorator is called with and without parentheses.
|
|
@@ -183,9 +198,10 @@ def _make_test_comparing_frames(
|
|
|
183
198
|
Callable[[], None]
|
|
184
199
|
The pytest test.
|
|
185
200
|
"""
|
|
186
|
-
|
|
187
201
|
if is_set_test_data_test:
|
|
188
|
-
frames_tester = _ControlDataWriter(
|
|
202
|
+
frames_tester: _FramesTester = _ControlDataWriter(
|
|
203
|
+
file_path, size_frame=size_frame
|
|
204
|
+
)
|
|
189
205
|
else:
|
|
190
206
|
frames_tester = _FramesTester(file_path, show_diff=show_diff)
|
|
191
207
|
|
|
@@ -196,7 +212,7 @@ def _make_test_comparing_frames(
|
|
|
196
212
|
)
|
|
197
213
|
testRenderer = _make_test_renderer_class(renderer_class)
|
|
198
214
|
|
|
199
|
-
def real_test():
|
|
215
|
+
def real_test() -> None:
|
|
200
216
|
with frames_tester.testing():
|
|
201
217
|
sceneTested = _make_test_scene_class(
|
|
202
218
|
base_scene=base_scene,
|
|
@@ -205,11 +221,13 @@ def _make_test_comparing_frames(
|
|
|
205
221
|
# If you pass a custom renderer to the Scene, the Camera class given as an argument in the Scene
|
|
206
222
|
# is not passed to the renderer. See __init__ of Scene.
|
|
207
223
|
# This potentially prevents OpenGL testing.
|
|
208
|
-
test_renderer=
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
224
|
+
test_renderer=(
|
|
225
|
+
testRenderer(file_writer_class=file_writer_class)
|
|
226
|
+
if base_scene is not ThreeDScene
|
|
227
|
+
else testRenderer(
|
|
228
|
+
file_writer_class=file_writer_class,
|
|
229
|
+
camera_class=ThreeDCamera,
|
|
230
|
+
)
|
|
213
231
|
), # testRenderer(file_writer_class=file_writer_class),
|
|
214
232
|
)
|
|
215
233
|
scene_tested = sceneTested(skip_animations=True)
|
manim/utils/tex.py
CHANGED
|
@@ -4,160 +4,135 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
__all__ = [
|
|
6
6
|
"TexTemplate",
|
|
7
|
-
"TexTemplateFromFile",
|
|
8
7
|
]
|
|
9
8
|
|
|
10
9
|
import copy
|
|
11
|
-
import os
|
|
12
10
|
import re
|
|
11
|
+
import warnings
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
13
|
from pathlib import Path
|
|
14
|
+
from typing import TYPE_CHECKING, Any
|
|
14
15
|
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from typing_extensions import Self
|
|
15
18
|
|
|
16
|
-
|
|
17
|
-
"""TeX templates are used for creating Tex() and MathTex() objects.
|
|
18
|
-
|
|
19
|
-
Parameters
|
|
20
|
-
----------
|
|
21
|
-
tex_compiler
|
|
22
|
-
The TeX compiler to be used, e.g. ``latex``, ``pdflatex`` or ``lualatex``
|
|
23
|
-
output_format
|
|
24
|
-
The output format resulting from compilation, e.g. ``.dvi`` or ``.pdf``
|
|
25
|
-
documentclass
|
|
26
|
-
The command defining the documentclass, e.g. ``\\documentclass[preview]{standalone}``
|
|
27
|
-
preamble
|
|
28
|
-
The document's preamble, i.e. the part between ``\\documentclass`` and ``\\begin{document}``
|
|
29
|
-
placeholder_text
|
|
30
|
-
Text in the document that will be replaced by the expression to be rendered
|
|
31
|
-
post_doc_commands
|
|
32
|
-
Text (definitions, commands) to be inserted at right after ``\\begin{document}``, e.g. ``\\boldmath``
|
|
33
|
-
|
|
34
|
-
Attributes
|
|
35
|
-
----------
|
|
36
|
-
tex_compiler : :class:`str`
|
|
37
|
-
The TeX compiler to be used, e.g. ``latex``, ``pdflatex`` or ``lualatex``
|
|
38
|
-
output_format : :class:`str`
|
|
39
|
-
The output format resulting from compilation, e.g. ``.dvi`` or ``.pdf``
|
|
40
|
-
documentclass : :class:`str`
|
|
41
|
-
The command defining the documentclass, e.g. ``\\documentclass[preview]{standalone}``
|
|
42
|
-
preamble : :class:`str`
|
|
43
|
-
The document's preamble, i.e. the part between ``\\documentclass`` and ``\\begin{document}``
|
|
44
|
-
placeholder_text : :class:`str`
|
|
45
|
-
Text in the document that will be replaced by the expression to be rendered
|
|
46
|
-
post_doc_commands : :class:`str`
|
|
47
|
-
Text (definitions, commands) to be inserted at right after ``\\begin{document}``, e.g. ``\\boldmath``
|
|
48
|
-
"""
|
|
19
|
+
from manim.typing import StrPath
|
|
49
20
|
|
|
50
|
-
|
|
51
|
-
default_preamble = r"""
|
|
52
|
-
\usepackage[english]{babel}
|
|
21
|
+
_DEFAULT_PREAMBLE = r"""\usepackage[english]{babel}
|
|
53
22
|
\usepackage{amsmath}
|
|
54
|
-
\usepackage{amssymb}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
default_post_doc_commands = ""
|
|
60
|
-
|
|
61
|
-
def __init__(
|
|
62
|
-
self,
|
|
63
|
-
tex_compiler: str | None = None,
|
|
64
|
-
output_format: str | None = None,
|
|
65
|
-
documentclass: str | None = None,
|
|
66
|
-
preamble: str | None = None,
|
|
67
|
-
placeholder_text: str | None = None,
|
|
68
|
-
post_doc_commands: str | None = None,
|
|
69
|
-
**kwargs,
|
|
70
|
-
):
|
|
71
|
-
self.tex_compiler = (
|
|
72
|
-
tex_compiler
|
|
73
|
-
if tex_compiler is not None
|
|
74
|
-
else TexTemplate.default_tex_compiler
|
|
75
|
-
)
|
|
76
|
-
self.output_format = (
|
|
77
|
-
output_format
|
|
78
|
-
if output_format is not None
|
|
79
|
-
else TexTemplate.default_output_format
|
|
80
|
-
)
|
|
81
|
-
self.documentclass = (
|
|
82
|
-
documentclass
|
|
83
|
-
if documentclass is not None
|
|
84
|
-
else TexTemplate.default_documentclass
|
|
85
|
-
)
|
|
86
|
-
self.preamble = (
|
|
87
|
-
preamble if preamble is not None else TexTemplate.default_preamble
|
|
88
|
-
)
|
|
89
|
-
self.placeholder_text = (
|
|
90
|
-
placeholder_text
|
|
91
|
-
if placeholder_text is not None
|
|
92
|
-
else TexTemplate.default_placeholder_text
|
|
93
|
-
)
|
|
94
|
-
self.post_doc_commands = (
|
|
95
|
-
post_doc_commands
|
|
96
|
-
if post_doc_commands is not None
|
|
97
|
-
else TexTemplate.default_post_doc_commands
|
|
98
|
-
)
|
|
99
|
-
self._rebuild()
|
|
100
|
-
|
|
101
|
-
def __eq__(self, other: TexTemplate) -> bool:
|
|
102
|
-
return (
|
|
103
|
-
self.body == other.body
|
|
104
|
-
and self.tex_compiler == other.tex_compiler
|
|
105
|
-
and self.output_format == other.output_format
|
|
106
|
-
and self.post_doc_commands == other.post_doc_commands
|
|
107
|
-
)
|
|
23
|
+
\usepackage{amssymb}"""
|
|
24
|
+
|
|
25
|
+
_BEGIN_DOCUMENT = r"\begin{document}"
|
|
26
|
+
_END_DOCUMENT = r"\end{document}"
|
|
27
|
+
|
|
108
28
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
29
|
+
@dataclass(eq=True)
|
|
30
|
+
class TexTemplate:
|
|
31
|
+
"""TeX templates are used to create ``Tex`` and ``MathTex`` objects."""
|
|
32
|
+
|
|
33
|
+
_body: str = field(default="", init=False)
|
|
34
|
+
"""A custom body, can be set from a file."""
|
|
35
|
+
|
|
36
|
+
tex_compiler: str = "latex"
|
|
37
|
+
"""The TeX compiler to be used, e.g. ``latex``, ``pdflatex`` or ``lualatex``."""
|
|
38
|
+
|
|
39
|
+
description: str = ""
|
|
40
|
+
"""A description of the template"""
|
|
41
|
+
|
|
42
|
+
output_format: str = ".dvi"
|
|
43
|
+
"""The output format resulting from compilation, e.g. ``.dvi`` or ``.pdf``."""
|
|
44
|
+
|
|
45
|
+
documentclass: str = r"\documentclass[preview]{standalone}"
|
|
46
|
+
r"""The command defining the documentclass, e.g. ``\documentclass[preview]{standalone}``."""
|
|
47
|
+
|
|
48
|
+
preamble: str = _DEFAULT_PREAMBLE
|
|
49
|
+
r"""The document's preamble, i.e. the part between ``\documentclass`` and ``\begin{document}``."""
|
|
50
|
+
|
|
51
|
+
placeholder_text: str = "YourTextHere"
|
|
52
|
+
"""Text in the document that will be replaced by the expression to be rendered."""
|
|
53
|
+
|
|
54
|
+
post_doc_commands: str = ""
|
|
55
|
+
r"""Text (definitions, commands) to be inserted at right after ``\begin{document}``, e.g. ``\boldmath``."""
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def body(self) -> str:
|
|
59
|
+
"""The entire TeX template."""
|
|
60
|
+
return self._body or "\n".join(
|
|
61
|
+
filter(
|
|
62
|
+
None,
|
|
63
|
+
[
|
|
64
|
+
self.documentclass,
|
|
65
|
+
self.preamble,
|
|
66
|
+
_BEGIN_DOCUMENT,
|
|
67
|
+
self.post_doc_commands,
|
|
68
|
+
self.placeholder_text,
|
|
69
|
+
_END_DOCUMENT,
|
|
70
|
+
],
|
|
71
|
+
)
|
|
125
72
|
)
|
|
126
73
|
|
|
127
|
-
|
|
128
|
-
|
|
74
|
+
@body.setter
|
|
75
|
+
def body(self, value: str) -> None:
|
|
76
|
+
self._body = value
|
|
77
|
+
|
|
78
|
+
@classmethod
|
|
79
|
+
def from_file(cls, file: StrPath = "tex_template.tex", **kwargs: Any) -> Self:
|
|
80
|
+
"""Create an instance by reading the content of a file.
|
|
81
|
+
|
|
82
|
+
Using the ``add_to_preamble`` and ``add_to_document`` methods on this instance
|
|
83
|
+
will have no effect, as the body is read from the file.
|
|
84
|
+
"""
|
|
85
|
+
instance = cls(**kwargs)
|
|
86
|
+
instance.body = Path(file).read_text(encoding="utf-8")
|
|
87
|
+
return instance
|
|
88
|
+
|
|
89
|
+
def add_to_preamble(self, txt: str, prepend: bool = False) -> Self:
|
|
90
|
+
r"""Adds text to the TeX template's preamble (e.g. definitions, packages). Text can be inserted at the beginning or at the end of the preamble.
|
|
129
91
|
|
|
130
92
|
Parameters
|
|
131
93
|
----------
|
|
132
94
|
txt
|
|
133
|
-
String containing the text to be added, e.g.
|
|
95
|
+
String containing the text to be added, e.g. ``\usepackage{hyperref}``.
|
|
134
96
|
prepend
|
|
135
|
-
Whether the text should be added at the beginning of the preamble, i.e. right after
|
|
97
|
+
Whether the text should be added at the beginning of the preamble, i.e. right after ``\documentclass``.
|
|
98
|
+
Default is to add it at the end of the preamble, i.e. right before ``\begin{document}``.
|
|
136
99
|
"""
|
|
100
|
+
if self._body:
|
|
101
|
+
warnings.warn(
|
|
102
|
+
"This TeX template was created with a fixed body, trying to add text the preamble will have no effect.",
|
|
103
|
+
UserWarning,
|
|
104
|
+
stacklevel=2,
|
|
105
|
+
)
|
|
137
106
|
if prepend:
|
|
138
107
|
self.preamble = txt + "\n" + self.preamble
|
|
139
108
|
else:
|
|
140
109
|
self.preamble += "\n" + txt
|
|
141
|
-
self
|
|
110
|
+
return self
|
|
142
111
|
|
|
143
|
-
def add_to_document(self, txt: str):
|
|
144
|
-
"""Adds
|
|
112
|
+
def add_to_document(self, txt: str) -> Self:
|
|
113
|
+
r"""Adds text to the TeX template just after \begin{document}, e.g. ``\boldmath``.
|
|
145
114
|
|
|
146
115
|
Parameters
|
|
147
116
|
----------
|
|
148
117
|
txt
|
|
149
118
|
String containing the text to be added.
|
|
150
119
|
"""
|
|
151
|
-
self.
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
120
|
+
if self._body:
|
|
121
|
+
warnings.warn(
|
|
122
|
+
"This TeX template was created with a fixed body, trying to add text the document will have no effect.",
|
|
123
|
+
UserWarning,
|
|
124
|
+
stacklevel=2,
|
|
125
|
+
)
|
|
126
|
+
self.post_doc_commands += txt
|
|
127
|
+
return self
|
|
128
|
+
|
|
129
|
+
def get_texcode_for_expression(self, expression: str) -> str:
|
|
130
|
+
r"""Inserts expression verbatim into TeX template.
|
|
156
131
|
|
|
157
132
|
Parameters
|
|
158
133
|
----------
|
|
159
134
|
expression
|
|
160
|
-
The string containing the expression to be typeset, e.g.
|
|
135
|
+
The string containing the expression to be typeset, e.g. ``$\sqrt{2}$``
|
|
161
136
|
|
|
162
137
|
Returns
|
|
163
138
|
-------
|
|
@@ -166,102 +141,59 @@ class TexTemplate:
|
|
|
166
141
|
"""
|
|
167
142
|
return self.body.replace(self.placeholder_text, expression)
|
|
168
143
|
|
|
169
|
-
def
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
Parameters
|
|
174
|
-
----------
|
|
175
|
-
environment
|
|
176
|
-
The tex_environment as a string. Acceptable formats include:
|
|
177
|
-
``{align*}``, ``align*``, ``{tabular}[t]{cccl}``, ``tabular}{cccl``, ``\\begin{tabular}[t]{cccl}``.
|
|
178
|
-
|
|
179
|
-
Returns
|
|
180
|
-
-------
|
|
181
|
-
Tuple[:class:`str`, :class:`str`]
|
|
182
|
-
A pair of strings representing the opening and closing of the tex environment, e.g.
|
|
183
|
-
``\\begin{tabular}{cccl}`` and ``\\end{tabular}``
|
|
184
|
-
"""
|
|
185
|
-
|
|
186
|
-
# If the environment starts with \begin, remove it
|
|
187
|
-
if environment[0:6] == r"\begin":
|
|
188
|
-
environment = environment[6:]
|
|
189
|
-
|
|
190
|
-
# If environment begins with { strip it
|
|
191
|
-
if environment[0] == r"{":
|
|
192
|
-
environment = environment[1:]
|
|
193
|
-
|
|
194
|
-
# The \begin command takes everything and closes with a brace
|
|
195
|
-
begin = r"\begin{" + environment
|
|
196
|
-
if (
|
|
197
|
-
begin[-1] != r"}" and begin[-1] != r"]"
|
|
198
|
-
): # If it doesn't end on } or ], assume missing }
|
|
199
|
-
begin += r"}"
|
|
200
|
-
|
|
201
|
-
# While the \end command terminates at the first closing brace
|
|
202
|
-
split_at_brace = re.split(r"}", environment, 1)
|
|
203
|
-
end = r"\end{" + split_at_brace[0] + r"}"
|
|
204
|
-
|
|
205
|
-
return begin, end
|
|
206
|
-
|
|
207
|
-
def get_texcode_for_expression_in_env(self, expression: str, environment: str):
|
|
208
|
-
r"""Inserts expression into TeX template wrapped in \begin{environment} and \end{environment}
|
|
144
|
+
def get_texcode_for_expression_in_env(
|
|
145
|
+
self, expression: str, environment: str
|
|
146
|
+
) -> str:
|
|
147
|
+
r"""Inserts expression into TeX template wrapped in ``\begin{environment}`` and ``\end{environment}``.
|
|
209
148
|
|
|
210
149
|
Parameters
|
|
211
150
|
----------
|
|
212
151
|
expression
|
|
213
|
-
The string containing the expression to be typeset, e.g.
|
|
152
|
+
The string containing the expression to be typeset, e.g. ``$\sqrt{2}$``.
|
|
214
153
|
environment
|
|
215
|
-
The string containing the environment in which the expression should be typeset, e.g. ``align
|
|
154
|
+
The string containing the environment in which the expression should be typeset, e.g. ``align*``.
|
|
216
155
|
|
|
217
156
|
Returns
|
|
218
157
|
-------
|
|
219
158
|
:class:`str`
|
|
220
159
|
LaTeX code based on template, containing the given expression inside its environment, ready for typesetting
|
|
221
160
|
"""
|
|
222
|
-
begin, end =
|
|
223
|
-
return self.body.replace(
|
|
161
|
+
begin, end = _texcode_for_environment(environment)
|
|
162
|
+
return self.body.replace(
|
|
163
|
+
self.placeholder_text, "\n".join([begin, expression, end])
|
|
164
|
+
)
|
|
224
165
|
|
|
225
|
-
def copy(self) ->
|
|
166
|
+
def copy(self) -> Self:
|
|
167
|
+
"""Create a deep copy of the TeX template instance."""
|
|
226
168
|
return copy.deepcopy(self)
|
|
227
169
|
|
|
228
170
|
|
|
229
|
-
|
|
230
|
-
"""
|
|
171
|
+
def _texcode_for_environment(environment: str) -> tuple[str, str]:
|
|
172
|
+
r"""Processes the tex_environment string to return the correct ``\begin{environment}[extra]{extra}`` and
|
|
173
|
+
``\end{environment}`` strings.
|
|
231
174
|
|
|
232
175
|
Parameters
|
|
233
176
|
----------
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
body : :class:`str`
|
|
244
|
-
Content of the TeX template file
|
|
245
|
-
tex_compiler : :class:`str`
|
|
246
|
-
The TeX compiler to be used, e.g. ``latex``, ``pdflatex`` or ``lualatex``
|
|
247
|
-
output_format : :class:`str`
|
|
248
|
-
The output format resulting from compilation, e.g. ``.dvi`` or ``.pdf``
|
|
177
|
+
environment
|
|
178
|
+
The tex_environment as a string. Acceptable formats include:
|
|
179
|
+
``{align*}``, ``align*``, ``{tabular}[t]{cccl}``, ``tabular}{cccl``, ``\begin{tabular}[t]{cccl}``.
|
|
180
|
+
|
|
181
|
+
Returns
|
|
182
|
+
-------
|
|
183
|
+
Tuple[:class:`str`, :class:`str`]
|
|
184
|
+
A pair of strings representing the opening and closing of the tex environment, e.g.
|
|
185
|
+
``\begin{tabular}{cccl}`` and ``\end{tabular}``
|
|
249
186
|
"""
|
|
187
|
+
environment = environment.removeprefix(r"\begin").removeprefix("{")
|
|
250
188
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
def _rebuild(self):
|
|
258
|
-
self.body = self.template_file.read_text()
|
|
259
|
-
|
|
260
|
-
def file_not_mutable(self):
|
|
261
|
-
raise Exception("Cannot modify TexTemplate when using a template file.")
|
|
189
|
+
# The \begin command takes everything and closes with a brace
|
|
190
|
+
begin = r"\begin{" + environment
|
|
191
|
+
# If it doesn't end on } or ], assume missing }
|
|
192
|
+
if not begin.endswith(("}", "]")):
|
|
193
|
+
begin += "}"
|
|
262
194
|
|
|
263
|
-
|
|
264
|
-
|
|
195
|
+
# While the \end command terminates at the first closing brace
|
|
196
|
+
split_at_brace = re.split("}", environment, maxsplit=1)
|
|
197
|
+
end = r"\end{" + split_at_brace[0] + "}"
|
|
265
198
|
|
|
266
|
-
|
|
267
|
-
self.file_not_mutable()
|
|
199
|
+
return begin, end
|