manim 0.18.0.post0__py3-none-any.whl → 0.19.0__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.
Potentially problematic release.
This version of manim might be problematic. Click here for more details.
- manim/__init__.py +3 -6
- manim/__main__.py +61 -20
- manim/_config/__init__.py +6 -3
- manim/_config/cli_colors.py +16 -8
- manim/_config/default.cfg +1 -3
- manim/_config/logger_utils.py +14 -8
- manim/_config/utils.py +651 -472
- manim/animation/animation.py +152 -5
- manim/animation/composition.py +80 -39
- manim/animation/creation.py +196 -14
- manim/animation/fading.py +5 -9
- manim/animation/indication.py +103 -47
- manim/animation/movement.py +22 -5
- manim/animation/rotation.py +3 -2
- manim/animation/specialized.py +4 -6
- manim/animation/speedmodifier.py +10 -5
- manim/animation/transform.py +4 -5
- manim/animation/transform_matching_parts.py +1 -1
- manim/animation/updaters/mobject_update_utils.py +17 -14
- manim/camera/camera.py +15 -6
- manim/cli/__init__.py +17 -0
- manim/cli/cfg/group.py +70 -44
- manim/cli/checkhealth/checks.py +93 -75
- manim/cli/checkhealth/commands.py +14 -5
- manim/cli/default_group.py +157 -25
- manim/cli/init/commands.py +32 -24
- 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 +51 -15
- manim/cli/render/output_options.py +6 -5
- manim/cli/render/render_options.py +97 -32
- manim/constants.py +65 -19
- manim/gui/gui.py +2 -0
- manim/mobject/frame.py +0 -1
- manim/mobject/geometry/arc.py +112 -78
- manim/mobject/geometry/boolean_ops.py +32 -25
- manim/mobject/geometry/labeled.py +300 -77
- manim/mobject/geometry/line.py +132 -64
- manim/mobject/geometry/polygram.py +126 -30
- manim/mobject/geometry/shape_matchers.py +35 -15
- manim/mobject/geometry/tips.py +38 -29
- manim/mobject/graph.py +414 -133
- manim/mobject/graphing/coordinate_systems.py +126 -64
- manim/mobject/graphing/functions.py +25 -15
- manim/mobject/graphing/number_line.py +24 -10
- manim/mobject/graphing/probability.py +2 -10
- manim/mobject/graphing/scale.py +6 -5
- manim/mobject/matrix.py +17 -19
- manim/mobject/mobject.py +314 -165
- manim/mobject/opengl/opengl_compatibility.py +2 -0
- manim/mobject/opengl/opengl_geometry.py +30 -9
- manim/mobject/opengl/opengl_image_mobject.py +2 -0
- manim/mobject/opengl/opengl_mobject.py +509 -343
- manim/mobject/opengl/opengl_point_cloud_mobject.py +5 -7
- manim/mobject/opengl/opengl_surface.py +3 -2
- manim/mobject/opengl/opengl_three_dimensions.py +2 -0
- manim/mobject/opengl/opengl_vectorized_mobject.py +46 -79
- manim/mobject/svg/brace.py +63 -13
- manim/mobject/svg/svg_mobject.py +4 -3
- manim/mobject/table.py +11 -13
- manim/mobject/text/code_mobject.py +186 -548
- manim/mobject/text/numbers.py +9 -7
- manim/mobject/text/tex_mobject.py +23 -14
- manim/mobject/text/text_mobject.py +70 -24
- manim/mobject/three_d/polyhedra.py +98 -1
- manim/mobject/three_d/three_d_utils.py +4 -4
- manim/mobject/three_d/three_dimensions.py +62 -34
- manim/mobject/types/image_mobject.py +42 -24
- manim/mobject/types/point_cloud_mobject.py +105 -67
- manim/mobject/types/vectorized_mobject.py +496 -228
- manim/mobject/value_tracker.py +5 -4
- manim/mobject/vector_field.py +5 -5
- manim/opengl/__init__.py +3 -3
- manim/plugins/__init__.py +14 -1
- manim/plugins/plugins_flags.py +14 -8
- manim/renderer/cairo_renderer.py +20 -10
- manim/renderer/opengl_renderer.py +21 -23
- manim/renderer/opengl_renderer_window.py +2 -0
- manim/renderer/shader.py +2 -3
- manim/renderer/shader_wrapper.py +5 -2
- manim/renderer/vectorized_mobject_rendering.py +5 -0
- manim/scene/moving_camera_scene.py +23 -0
- manim/scene/scene.py +90 -43
- manim/scene/scene_file_writer.py +316 -165
- manim/scene/section.py +17 -15
- manim/scene/three_d_scene.py +13 -21
- manim/scene/vector_space_scene.py +22 -9
- manim/typing.py +830 -70
- manim/utils/bezier.py +1667 -399
- manim/utils/caching.py +13 -5
- manim/utils/color/AS2700.py +2 -0
- manim/utils/color/BS381.py +3 -0
- manim/utils/color/DVIPSNAMES.py +96 -0
- manim/utils/color/SVGNAMES.py +179 -0
- manim/utils/color/X11.py +3 -0
- manim/utils/color/XKCD.py +3 -0
- manim/utils/color/__init__.py +8 -5
- manim/utils/color/core.py +844 -309
- manim/utils/color/manim_colors.py +7 -9
- manim/utils/commands.py +48 -20
- manim/utils/config_ops.py +18 -13
- manim/utils/debug.py +8 -7
- manim/utils/deprecation.py +90 -40
- manim/utils/docbuild/__init__.py +17 -0
- manim/utils/docbuild/autoaliasattr_directive.py +234 -0
- manim/utils/docbuild/autocolor_directive.py +21 -17
- manim/utils/docbuild/manim_directive.py +50 -35
- 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 +26 -16
- manim/utils/hashing.py +9 -7
- manim/utils/images.py +10 -4
- manim/utils/ipython_magic.py +14 -8
- manim/utils/iterables.py +161 -119
- manim/utils/module_ops.py +57 -19
- manim/utils/opengl.py +83 -24
- manim/utils/parameter_parsing.py +32 -0
- manim/utils/paths.py +21 -23
- manim/utils/polylabel.py +168 -0
- manim/utils/qhull.py +218 -0
- manim/utils/rate_functions.py +74 -39
- manim/utils/simple_functions.py +24 -15
- manim/utils/sounds.py +7 -1
- manim/utils/space_ops.py +125 -69
- 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 +33 -18
- manim/utils/testing/frames_comparison.py +27 -19
- manim/utils/tex.py +127 -197
- manim/utils/tex_file_writing.py +47 -45
- manim/utils/tex_templates.py +2 -1
- manim/utils/unit.py +6 -5
- {manim-0.18.0.post0.dist-info → manim-0.19.0.dist-info}/LICENSE.community +1 -1
- {manim-0.18.0.post0.dist-info → manim-0.19.0.dist-info}/METADATA +40 -39
- manim-0.19.0.dist-info/RECORD +221 -0
- {manim-0.18.0.post0.dist-info → manim-0.19.0.dist-info}/WHEEL +1 -1
- manim/cli/new/__init__.py +0 -0
- manim/cli/new/group.py +0 -189
- manim/plugins/import_plugins.py +0 -43
- manim-0.18.0.post0.dist-info/RECORD +0 -217
- {manim-0.18.0.post0.dist-info → manim-0.19.0.dist-info}/LICENSE +0 -0
- {manim-0.18.0.post0.dist-info → manim-0.19.0.dist-info}/entry_points.txt +0 -0
manim/camera/camera.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"""A camera converts the mobjects contained in a Scene into an array of pixels."""
|
|
2
2
|
|
|
3
|
-
|
|
4
3
|
from __future__ import annotations
|
|
5
4
|
|
|
6
5
|
__all__ = ["Camera", "BackgroundColoredVMobjectDisplayer"]
|
|
@@ -9,8 +8,9 @@ import copy
|
|
|
9
8
|
import itertools as it
|
|
10
9
|
import operator as op
|
|
11
10
|
import pathlib
|
|
11
|
+
from collections.abc import Iterable
|
|
12
12
|
from functools import reduce
|
|
13
|
-
from typing import Any, Callable
|
|
13
|
+
from typing import Any, Callable
|
|
14
14
|
|
|
15
15
|
import cairo
|
|
16
16
|
import numpy as np
|
|
@@ -37,6 +37,14 @@ LINE_JOIN_MAP = {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
|
|
40
|
+
CAP_STYLE_MAP = {
|
|
41
|
+
CapStyleType.AUTO: None, # TODO: this could be improved
|
|
42
|
+
CapStyleType.ROUND: cairo.LineCap.ROUND,
|
|
43
|
+
CapStyleType.BUTT: cairo.LineCap.BUTT,
|
|
44
|
+
CapStyleType.SQUARE: cairo.LineCap.SQUARE,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
40
48
|
class Camera:
|
|
41
49
|
"""Base camera class.
|
|
42
50
|
|
|
@@ -369,7 +377,6 @@ class Camera:
|
|
|
369
377
|
np.array
|
|
370
378
|
The pixel array which can then be passed to set_background.
|
|
371
379
|
"""
|
|
372
|
-
|
|
373
380
|
logger.info("Starting set_background")
|
|
374
381
|
coords = self.get_coords_of_all_pixels()
|
|
375
382
|
new_background = np.apply_along_axis(coords_to_colors_func, 2, coords)
|
|
@@ -778,11 +785,13 @@ class Camera:
|
|
|
778
785
|
ctx.set_line_width(
|
|
779
786
|
width
|
|
780
787
|
* self.cairo_line_width_multiple
|
|
781
|
-
# This ensures lines have constant width as you zoom in on them.
|
|
782
788
|
* (self.frame_width / self.frame_width),
|
|
789
|
+
# This ensures lines have constant width as you zoom in on them.
|
|
783
790
|
)
|
|
784
791
|
if vmobject.joint_type != LineJointType.AUTO:
|
|
785
792
|
ctx.set_line_join(LINE_JOIN_MAP[vmobject.joint_type])
|
|
793
|
+
if vmobject.cap_style != CapStyleType.AUTO:
|
|
794
|
+
ctx.set_line_cap(CAP_STYLE_MAP[vmobject.cap_style])
|
|
786
795
|
ctx.stroke_preserve()
|
|
787
796
|
return self
|
|
788
797
|
|
|
@@ -973,8 +982,8 @@ class Camera:
|
|
|
973
982
|
sub_image = Image.fromarray(image_mobject.get_pixel_array(), mode="RGBA")
|
|
974
983
|
|
|
975
984
|
# Reshape
|
|
976
|
-
pixel_width = max(int(pdist([ul_coords, ur_coords])), 1)
|
|
977
|
-
pixel_height = max(int(pdist([ul_coords, dl_coords])), 1)
|
|
985
|
+
pixel_width = max(int(pdist([ul_coords, ur_coords]).item()), 1)
|
|
986
|
+
pixel_height = max(int(pdist([ul_coords, dl_coords]).item()), 1)
|
|
978
987
|
sub_image = sub_image.resize(
|
|
979
988
|
(pixel_width, pixel_height),
|
|
980
989
|
resample=image_mobject.resampling_algorithm,
|
manim/cli/__init__.py
CHANGED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""The Manim CLI, and the available commands for ``manim``.
|
|
2
|
+
|
|
3
|
+
This page is a work in progress. Please run ``manim`` or ``manim --help`` in
|
|
4
|
+
your terminal to find more information on the following commands.
|
|
5
|
+
|
|
6
|
+
Available commands
|
|
7
|
+
------------------
|
|
8
|
+
|
|
9
|
+
.. autosummary::
|
|
10
|
+
:toctree: ../reference
|
|
11
|
+
|
|
12
|
+
cfg
|
|
13
|
+
checkhealth
|
|
14
|
+
init
|
|
15
|
+
plugins
|
|
16
|
+
render
|
|
17
|
+
"""
|
manim/cli/cfg/group.py
CHANGED
|
@@ -5,32 +5,45 @@ cfg``. Here you can specify options, subcommands, and subgroups for the cfg
|
|
|
5
5
|
group.
|
|
6
6
|
|
|
7
7
|
"""
|
|
8
|
+
|
|
8
9
|
from __future__ import annotations
|
|
9
10
|
|
|
10
|
-
import
|
|
11
|
+
import contextlib
|
|
11
12
|
from ast import literal_eval
|
|
12
13
|
from pathlib import Path
|
|
14
|
+
from typing import Any, cast
|
|
13
15
|
|
|
14
|
-
import click
|
|
15
16
|
import cloup
|
|
16
17
|
from rich.errors import StyleSyntaxError
|
|
17
18
|
from rich.style import Style
|
|
18
19
|
|
|
19
|
-
from
|
|
20
|
-
from
|
|
21
|
-
from
|
|
22
|
-
from
|
|
20
|
+
from manim._config import cli_ctx_settings, console
|
|
21
|
+
from manim._config.utils import config_file_paths, make_config_parser
|
|
22
|
+
from manim.constants import EPILOG
|
|
23
|
+
from manim.utils.file_ops import guarantee_existence, open_file
|
|
23
24
|
|
|
24
25
|
RICH_COLOUR_INSTRUCTIONS: str = """
|
|
25
26
|
[red]The default colour is used by the input statement.
|
|
26
27
|
If left empty, the default colour will be used.[/red]
|
|
27
28
|
[magenta] For a full list of styles, visit[/magenta] [green]https://rich.readthedocs.io/en/latest/style.html[/green]
|
|
28
29
|
"""
|
|
29
|
-
RICH_NON_STYLE_ENTRIES: str = ["log.width", "log.height", "log.timestamps"]
|
|
30
|
+
RICH_NON_STYLE_ENTRIES: list[str] = ["log.width", "log.height", "log.timestamps"]
|
|
31
|
+
|
|
32
|
+
__all__ = [
|
|
33
|
+
"value_from_string",
|
|
34
|
+
"value_from_string",
|
|
35
|
+
"is_valid_style",
|
|
36
|
+
"replace_keys",
|
|
37
|
+
"cfg",
|
|
38
|
+
"write",
|
|
39
|
+
"show",
|
|
40
|
+
"export",
|
|
41
|
+
]
|
|
30
42
|
|
|
31
43
|
|
|
32
44
|
def value_from_string(value: str) -> str | int | bool:
|
|
33
|
-
"""
|
|
45
|
+
"""Extract the literal of proper datatype from a ``value`` string.
|
|
46
|
+
|
|
34
47
|
Parameters
|
|
35
48
|
----------
|
|
36
49
|
value
|
|
@@ -38,51 +51,60 @@ def value_from_string(value: str) -> str | int | bool:
|
|
|
38
51
|
|
|
39
52
|
Returns
|
|
40
53
|
-------
|
|
41
|
-
|
|
42
|
-
|
|
54
|
+
:class:`str` | :class:`int` | :class:`bool`
|
|
55
|
+
The literal of appropriate datatype.
|
|
43
56
|
"""
|
|
44
|
-
|
|
57
|
+
with contextlib.suppress(SyntaxError, ValueError):
|
|
45
58
|
value = literal_eval(value)
|
|
46
|
-
except (SyntaxError, ValueError):
|
|
47
|
-
pass
|
|
48
59
|
return value
|
|
49
60
|
|
|
50
61
|
|
|
51
|
-
def _is_expected_datatype(
|
|
52
|
-
|
|
53
|
-
|
|
62
|
+
def _is_expected_datatype(
|
|
63
|
+
value: str, expected: str, validate_style: bool = False
|
|
64
|
+
) -> bool:
|
|
65
|
+
"""Check whether the literal from ``value`` is the same datatype as the
|
|
66
|
+
literal from ``expected``. If ``validate_style`` is ``True``, also check if
|
|
67
|
+
the style given by ``value`` is valid, according to ``rich``.
|
|
54
68
|
|
|
55
69
|
Parameters
|
|
56
70
|
----------
|
|
57
71
|
value
|
|
58
|
-
The string of the value to check
|
|
72
|
+
The string of the value to check, obtained from reading the user input.
|
|
59
73
|
expected
|
|
60
|
-
The string of the literal datatype must be matched by
|
|
61
|
-
reading the cfg file.
|
|
62
|
-
|
|
63
|
-
Whether or not to confirm if
|
|
74
|
+
The string of the literal datatype which must be matched by ``value``.
|
|
75
|
+
This is obtained from reading the ``cfg`` file.
|
|
76
|
+
validate_style
|
|
77
|
+
Whether or not to confirm if ``value`` is a valid style, according to
|
|
78
|
+
``rich``. Default is ``False``.
|
|
64
79
|
|
|
65
80
|
Returns
|
|
66
81
|
-------
|
|
67
82
|
:class:`bool`
|
|
68
|
-
Whether or not
|
|
83
|
+
Whether or not the literal from ``value`` matches the datatype of the
|
|
84
|
+
literal from ``expected``.
|
|
69
85
|
"""
|
|
70
|
-
|
|
71
|
-
|
|
86
|
+
value_literal = value_from_string(value)
|
|
87
|
+
ExpectedLiteralType = type(value_from_string(expected))
|
|
72
88
|
|
|
73
|
-
return isinstance(
|
|
89
|
+
return isinstance(value_literal, ExpectedLiteralType) and (
|
|
90
|
+
(isinstance(value_literal, str) and is_valid_style(value_literal))
|
|
91
|
+
if validate_style
|
|
92
|
+
else True
|
|
93
|
+
)
|
|
74
94
|
|
|
75
95
|
|
|
76
96
|
def is_valid_style(style: str) -> bool:
|
|
77
|
-
"""Checks whether the entered color is
|
|
97
|
+
"""Checks whether the entered color style is valid, according to ``rich``.
|
|
98
|
+
|
|
78
99
|
Parameters
|
|
79
100
|
----------
|
|
80
101
|
style
|
|
81
102
|
The style to check whether it is valid.
|
|
103
|
+
|
|
82
104
|
Returns
|
|
83
105
|
-------
|
|
84
|
-
|
|
85
|
-
|
|
106
|
+
:class:`bool`
|
|
107
|
+
Whether the color style is valid or not, according to ``rich``.
|
|
86
108
|
"""
|
|
87
109
|
try:
|
|
88
110
|
Style.parse(style)
|
|
@@ -91,16 +113,20 @@ def is_valid_style(style: str) -> bool:
|
|
|
91
113
|
return False
|
|
92
114
|
|
|
93
115
|
|
|
94
|
-
def replace_keys(default: dict) -> dict:
|
|
95
|
-
"""
|
|
116
|
+
def replace_keys(default: dict[str, Any]) -> dict[str, Any]:
|
|
117
|
+
"""Replace ``_`` with ``.`` and vice versa in a dictionary's keys for
|
|
118
|
+
``rich``.
|
|
119
|
+
|
|
96
120
|
Parameters
|
|
97
121
|
----------
|
|
98
122
|
default
|
|
99
|
-
The dictionary
|
|
123
|
+
The dictionary whose keys will be checked and replaced.
|
|
124
|
+
|
|
100
125
|
Returns
|
|
101
126
|
-------
|
|
102
127
|
:class:`dict`
|
|
103
|
-
The dictionary
|
|
128
|
+
The dictionary whose keys are modified by replacing ``_`` with ``.``
|
|
129
|
+
and vice versa.
|
|
104
130
|
"""
|
|
105
131
|
for key in default:
|
|
106
132
|
if "_" in key:
|
|
@@ -123,22 +149,22 @@ def replace_keys(default: dict) -> dict:
|
|
|
123
149
|
epilog=EPILOG,
|
|
124
150
|
help="Manages Manim configuration files.",
|
|
125
151
|
)
|
|
126
|
-
@
|
|
127
|
-
def cfg(ctx):
|
|
152
|
+
@cloup.pass_context
|
|
153
|
+
def cfg(ctx: cloup.Context) -> None:
|
|
128
154
|
"""Responsible for the cfg subcommand."""
|
|
129
155
|
pass
|
|
130
156
|
|
|
131
157
|
|
|
132
158
|
@cfg.command(context_settings=cli_ctx_settings, no_args_is_help=True)
|
|
133
|
-
@
|
|
159
|
+
@cloup.option(
|
|
134
160
|
"-l",
|
|
135
161
|
"--level",
|
|
136
|
-
type=
|
|
162
|
+
type=cloup.Choice(["user", "cwd"], case_sensitive=False),
|
|
137
163
|
default="cwd",
|
|
138
164
|
help="Specify if this config is for user or the working directory.",
|
|
139
165
|
)
|
|
140
|
-
@
|
|
141
|
-
def write(level: str = None, openfile: bool = False) -> None:
|
|
166
|
+
@cloup.option("-o", "--open", "openfile", is_flag=True)
|
|
167
|
+
def write(level: str | None = None, openfile: bool = False) -> None:
|
|
142
168
|
config_paths = config_file_paths()
|
|
143
169
|
console.print(
|
|
144
170
|
"[yellow bold]Manim Configuration File Writer[/yellow bold]",
|
|
@@ -157,7 +183,7 @@ To save your config please save that file and place it in your current working d
|
|
|
157
183
|
action = "save this as"
|
|
158
184
|
for category in parser:
|
|
159
185
|
console.print(f"{category}", style="bold green underline")
|
|
160
|
-
default = parser[category]
|
|
186
|
+
default = cast(dict[str, Any], parser[category])
|
|
161
187
|
if category == "logger":
|
|
162
188
|
console.print(RICH_COLOUR_INSTRUCTIONS)
|
|
163
189
|
default = replace_keys(default)
|
|
@@ -187,7 +213,7 @@ To save your config please save that file and place it in your current working d
|
|
|
187
213
|
"""Not enough values in input.
|
|
188
214
|
You may have added a new entry to default.cfg, in which case you will have to
|
|
189
215
|
modify write_cfg_subcmd_input to account for it.""",
|
|
190
|
-
)
|
|
216
|
+
) from None
|
|
191
217
|
if temp:
|
|
192
218
|
while temp and not _is_expected_datatype(
|
|
193
219
|
temp,
|
|
@@ -240,7 +266,7 @@ modify write_cfg_subcmd_input to account for it.""",
|
|
|
240
266
|
|
|
241
267
|
|
|
242
268
|
@cfg.command(context_settings=cli_ctx_settings)
|
|
243
|
-
def show():
|
|
269
|
+
def show() -> None:
|
|
244
270
|
parser = make_config_parser()
|
|
245
271
|
rich_non_style_entries = [a.replace(".", "_") for a in RICH_NON_STYLE_ENTRIES]
|
|
246
272
|
for category in parser:
|
|
@@ -258,9 +284,9 @@ def show():
|
|
|
258
284
|
|
|
259
285
|
|
|
260
286
|
@cfg.command(context_settings=cli_ctx_settings)
|
|
261
|
-
@
|
|
262
|
-
@
|
|
263
|
-
def export(ctx, directory):
|
|
287
|
+
@cloup.option("-d", "--directory", default=Path.cwd())
|
|
288
|
+
@cloup.pass_context
|
|
289
|
+
def export(ctx: cloup.Context, directory: str) -> None:
|
|
264
290
|
directory_path = Path(directory)
|
|
265
291
|
if directory_path.absolute == Path.cwd().absolute:
|
|
266
292
|
console.print(
|
manim/cli/checkhealth/checks.py
CHANGED
|
@@ -1,63 +1,79 @@
|
|
|
1
1
|
"""Auxiliary module for the checkhealth subcommand, contains
|
|
2
|
-
the actual check implementations.
|
|
2
|
+
the actual check implementations.
|
|
3
|
+
"""
|
|
3
4
|
|
|
4
5
|
from __future__ import annotations
|
|
5
6
|
|
|
6
7
|
import os
|
|
7
8
|
import shutil
|
|
8
|
-
import
|
|
9
|
-
from typing import Callable
|
|
9
|
+
from typing import Callable, Protocol, cast
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
__all__ = ["HEALTH_CHECKS"]
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
|
|
14
|
+
class HealthCheckFunction(Protocol):
|
|
15
|
+
description: str
|
|
16
|
+
recommendation: str
|
|
17
|
+
skip_on_failed: list[str]
|
|
18
|
+
post_fail_fix_hook: Callable[..., object] | None
|
|
19
|
+
__name__: str
|
|
20
|
+
|
|
21
|
+
def __call__(self) -> bool: ...
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
HEALTH_CHECKS: list[HealthCheckFunction] = []
|
|
14
25
|
|
|
15
26
|
|
|
16
27
|
def healthcheck(
|
|
17
28
|
description: str,
|
|
18
29
|
recommendation: str,
|
|
19
|
-
skip_on_failed: list[
|
|
20
|
-
post_fail_fix_hook: Callable | None = None,
|
|
21
|
-
):
|
|
30
|
+
skip_on_failed: list[HealthCheckFunction | str] | None = None,
|
|
31
|
+
post_fail_fix_hook: Callable[..., object] | None = None,
|
|
32
|
+
) -> Callable[[Callable[[], bool]], HealthCheckFunction]:
|
|
22
33
|
"""Decorator used for declaring health checks.
|
|
23
34
|
|
|
24
|
-
This decorator attaches some data to a function,
|
|
25
|
-
|
|
35
|
+
This decorator attaches some data to a function, which is then added to a
|
|
36
|
+
a list containing all checks.
|
|
26
37
|
|
|
27
38
|
Parameters
|
|
28
39
|
----------
|
|
29
40
|
description
|
|
30
|
-
A brief description of this check, displayed when
|
|
31
|
-
|
|
41
|
+
A brief description of this check, displayed when the ``checkhealth``
|
|
42
|
+
subcommand is run.
|
|
32
43
|
recommendation
|
|
33
44
|
Help text which is displayed in case the check fails.
|
|
34
45
|
skip_on_failed
|
|
35
|
-
A list of check functions which, if they fail, cause
|
|
36
|
-
|
|
46
|
+
A list of check functions which, if they fail, cause the current check
|
|
47
|
+
to be skipped.
|
|
37
48
|
post_fail_fix_hook
|
|
38
|
-
A function that is
|
|
39
|
-
|
|
40
|
-
|
|
49
|
+
A function that is meant to (interactively) help to fix the detected
|
|
50
|
+
problem, if possible. This is only called upon explicit confirmation of
|
|
51
|
+
the user.
|
|
41
52
|
|
|
42
53
|
Returns
|
|
43
54
|
-------
|
|
44
|
-
|
|
55
|
+
Callable[Callable[[], bool], :class:`HealthCheckFunction`]
|
|
56
|
+
A decorator which converts a function into a health check function, as
|
|
57
|
+
required by the ``checkhealth`` subcommand.
|
|
45
58
|
"""
|
|
59
|
+
new_skip_on_failed: list[str]
|
|
46
60
|
if skip_on_failed is None:
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
61
|
+
new_skip_on_failed = []
|
|
62
|
+
else:
|
|
63
|
+
new_skip_on_failed = [
|
|
64
|
+
skip.__name__ if callable(skip) else skip for skip in skip_on_failed
|
|
65
|
+
]
|
|
51
66
|
|
|
52
|
-
def
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
67
|
+
def wrapper(func: Callable[[], bool]) -> HealthCheckFunction:
|
|
68
|
+
health_func = cast(HealthCheckFunction, func)
|
|
69
|
+
health_func.description = description
|
|
70
|
+
health_func.recommendation = recommendation
|
|
71
|
+
health_func.skip_on_failed = new_skip_on_failed
|
|
72
|
+
health_func.post_fail_fix_hook = post_fail_fix_hook
|
|
73
|
+
HEALTH_CHECKS.append(health_func)
|
|
74
|
+
return health_func
|
|
59
75
|
|
|
60
|
-
return
|
|
76
|
+
return wrapper
|
|
61
77
|
|
|
62
78
|
|
|
63
79
|
@healthcheck(
|
|
@@ -75,7 +91,14 @@ def healthcheck(
|
|
|
75
91
|
"PATH variable."
|
|
76
92
|
),
|
|
77
93
|
)
|
|
78
|
-
def is_manim_on_path():
|
|
94
|
+
def is_manim_on_path() -> bool:
|
|
95
|
+
"""Check whether ``manim`` is in ``PATH``.
|
|
96
|
+
|
|
97
|
+
Returns
|
|
98
|
+
-------
|
|
99
|
+
:class:`bool`
|
|
100
|
+
Whether ``manim`` is in ``PATH`` or not.
|
|
101
|
+
"""
|
|
79
102
|
path_to_manim = shutil.which("manim")
|
|
80
103
|
return path_to_manim is not None
|
|
81
104
|
|
|
@@ -91,10 +114,30 @@ def is_manim_on_path():
|
|
|
91
114
|
),
|
|
92
115
|
skip_on_failed=[is_manim_on_path],
|
|
93
116
|
)
|
|
94
|
-
def is_manim_executable_associated_to_this_library():
|
|
117
|
+
def is_manim_executable_associated_to_this_library() -> bool:
|
|
118
|
+
"""Check whether the ``manim`` executable in ``PATH`` is associated to this
|
|
119
|
+
library. To verify this, the executable should look like this:
|
|
120
|
+
|
|
121
|
+
.. code-block:: python
|
|
122
|
+
|
|
123
|
+
#!<MANIM_PATH>/.../python
|
|
124
|
+
import sys
|
|
125
|
+
from manim.__main__ import main
|
|
126
|
+
|
|
127
|
+
if __name__ == "__main__":
|
|
128
|
+
sys.exit(main())
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
Returns
|
|
132
|
+
-------
|
|
133
|
+
:class:`bool`
|
|
134
|
+
Whether the ``manim`` executable in ``PATH`` is associated to this
|
|
135
|
+
library or not.
|
|
136
|
+
"""
|
|
95
137
|
path_to_manim = shutil.which("manim")
|
|
96
|
-
|
|
97
|
-
|
|
138
|
+
assert path_to_manim is not None
|
|
139
|
+
with open(path_to_manim, "rb") as manim_binary:
|
|
140
|
+
manim_exec = manim_binary.read()
|
|
98
141
|
|
|
99
142
|
# first condition below corresponds to the executable being
|
|
100
143
|
# some sort of python script. second condition happens when
|
|
@@ -102,45 +145,6 @@ def is_manim_executable_associated_to_this_library():
|
|
|
102
145
|
return b"manim.__main__" in manim_exec or b'"%~dp0\\manim"' in manim_exec
|
|
103
146
|
|
|
104
147
|
|
|
105
|
-
@healthcheck(
|
|
106
|
-
description="Checking whether ffmpeg is available",
|
|
107
|
-
recommendation=(
|
|
108
|
-
"Manim does not work without ffmpeg. Please follow our "
|
|
109
|
-
"installation instructions "
|
|
110
|
-
"at https://docs.manim.community/en/stable/installation.html "
|
|
111
|
-
"to download ffmpeg. Then, either ...\n\n"
|
|
112
|
-
"(a) ... make the ffmpeg executable available to your system's PATH,\n"
|
|
113
|
-
"(b) or, alternatively, use <manim cfg write --open> to create a "
|
|
114
|
-
"custom configuration and set the ffmpeg_executable variable to the "
|
|
115
|
-
"full absolute path to the ffmpeg executable."
|
|
116
|
-
),
|
|
117
|
-
)
|
|
118
|
-
def is_ffmpeg_available():
|
|
119
|
-
path_to_ffmpeg = shutil.which(config.ffmpeg_executable)
|
|
120
|
-
return path_to_ffmpeg is not None and os.access(path_to_ffmpeg, os.X_OK)
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
@healthcheck(
|
|
124
|
-
description="Checking whether ffmpeg is working",
|
|
125
|
-
recommendation=(
|
|
126
|
-
"Your installed version of ffmpeg does not support x264 encoding, "
|
|
127
|
-
"which manim requires. Please follow our installation instructions "
|
|
128
|
-
"at https://docs.manim.community/en/stable/installation.html "
|
|
129
|
-
"to download and install a newer version of ffmpeg."
|
|
130
|
-
),
|
|
131
|
-
skip_on_failed=[is_ffmpeg_available],
|
|
132
|
-
)
|
|
133
|
-
def is_ffmpeg_working():
|
|
134
|
-
ffmpeg_version = subprocess.run(
|
|
135
|
-
[config.ffmpeg_executable, "-version"],
|
|
136
|
-
stdout=subprocess.PIPE,
|
|
137
|
-
).stdout.decode()
|
|
138
|
-
return (
|
|
139
|
-
ffmpeg_version.startswith("ffmpeg version")
|
|
140
|
-
and "--enable-libx264" in ffmpeg_version
|
|
141
|
-
)
|
|
142
|
-
|
|
143
|
-
|
|
144
148
|
@healthcheck(
|
|
145
149
|
description="Checking whether latex is available",
|
|
146
150
|
recommendation=(
|
|
@@ -153,7 +157,14 @@ def is_ffmpeg_working():
|
|
|
153
157
|
"LaTeX distribution on your operating system."
|
|
154
158
|
),
|
|
155
159
|
)
|
|
156
|
-
def is_latex_available():
|
|
160
|
+
def is_latex_available() -> bool:
|
|
161
|
+
"""Check whether ``latex`` is in ``PATH`` and can be executed.
|
|
162
|
+
|
|
163
|
+
Returns
|
|
164
|
+
-------
|
|
165
|
+
:class:`bool`
|
|
166
|
+
Whether ``latex`` is in ``PATH`` and can be executed or not.
|
|
167
|
+
"""
|
|
157
168
|
path_to_latex = shutil.which("latex")
|
|
158
169
|
return path_to_latex is not None and os.access(path_to_latex, os.X_OK)
|
|
159
170
|
|
|
@@ -168,6 +179,13 @@ def is_latex_available():
|
|
|
168
179
|
),
|
|
169
180
|
skip_on_failed=[is_latex_available],
|
|
170
181
|
)
|
|
171
|
-
def is_dvisvgm_available():
|
|
182
|
+
def is_dvisvgm_available() -> bool:
|
|
183
|
+
"""Check whether ``dvisvgm`` is in ``PATH`` and can be executed.
|
|
184
|
+
|
|
185
|
+
Returns
|
|
186
|
+
-------
|
|
187
|
+
:class:`bool`
|
|
188
|
+
Whether ``dvisvgm`` is in ``PATH`` and can be executed or not.
|
|
189
|
+
"""
|
|
172
190
|
path_to_dvisvgm = shutil.which("dvisvgm")
|
|
173
191
|
return path_to_dvisvgm is not None and os.access(path_to_dvisvgm, os.X_OK)
|
|
@@ -6,23 +6,26 @@ your Manim installation.
|
|
|
6
6
|
from __future__ import annotations
|
|
7
7
|
|
|
8
8
|
import sys
|
|
9
|
+
import timeit
|
|
9
10
|
|
|
10
11
|
import click
|
|
11
12
|
import cloup
|
|
12
13
|
|
|
13
|
-
from .checks import HEALTH_CHECKS
|
|
14
|
+
from manim.cli.checkhealth.checks import HEALTH_CHECKS, HealthCheckFunction
|
|
15
|
+
|
|
16
|
+
__all__ = ["checkhealth"]
|
|
14
17
|
|
|
15
18
|
|
|
16
19
|
@cloup.command(
|
|
17
20
|
context_settings=None,
|
|
18
21
|
)
|
|
19
|
-
def checkhealth():
|
|
22
|
+
def checkhealth() -> None:
|
|
20
23
|
"""This subcommand checks whether Manim is installed correctly
|
|
21
24
|
and has access to its required (and optional) system dependencies.
|
|
22
25
|
"""
|
|
23
26
|
click.echo(f"Python executable: {sys.executable}\n")
|
|
24
27
|
click.echo("Checking whether your installation of Manim Community is healthy...")
|
|
25
|
-
failed_checks = []
|
|
28
|
+
failed_checks: list[HealthCheckFunction] = []
|
|
26
29
|
|
|
27
30
|
for check in HEALTH_CHECKS:
|
|
28
31
|
click.echo(f"- {check.description} ... ", nl=False)
|
|
@@ -60,7 +63,7 @@ def checkhealth():
|
|
|
60
63
|
import manim as mn
|
|
61
64
|
|
|
62
65
|
class CheckHealthDemo(mn.Scene):
|
|
63
|
-
def
|
|
66
|
+
def _inner_construct(self) -> None:
|
|
64
67
|
banner = mn.ManimBanner().shift(mn.UP * 0.5)
|
|
65
68
|
self.play(banner.create())
|
|
66
69
|
self.wait(0.5)
|
|
@@ -77,5 +80,11 @@ def checkhealth():
|
|
|
77
80
|
mn.FadeOut(text_tex_group, shift=mn.DOWN),
|
|
78
81
|
)
|
|
79
82
|
|
|
83
|
+
def construct(self) -> None:
|
|
84
|
+
self.execution_time = timeit.timeit(self._inner_construct, number=1)
|
|
85
|
+
|
|
80
86
|
with mn.tempconfig({"preview": True, "disable_caching": True}):
|
|
81
|
-
CheckHealthDemo()
|
|
87
|
+
scene = CheckHealthDemo()
|
|
88
|
+
scene.render()
|
|
89
|
+
|
|
90
|
+
click.echo(f"Scene rendered in {scene.execution_time:.2f} seconds.")
|