manim 0.18.1__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/__main__.py +45 -12
- manim/_config/__init__.py +2 -2
- manim/_config/cli_colors.py +8 -4
- manim/_config/default.cfg +0 -2
- manim/_config/logger_utils.py +5 -0
- manim/_config/utils.py +29 -38
- manim/animation/animation.py +148 -8
- manim/animation/composition.py +16 -13
- manim/animation/creation.py +184 -8
- manim/animation/fading.py +5 -8
- manim/animation/indication.py +93 -26
- manim/animation/movement.py +21 -3
- manim/animation/rotation.py +2 -1
- manim/animation/specialized.py +3 -5
- manim/animation/speedmodifier.py +3 -3
- manim/animation/transform.py +4 -5
- manim/animation/updaters/mobject_update_utils.py +17 -14
- manim/camera/camera.py +2 -2
- manim/cli/__init__.py +17 -0
- manim/cli/cfg/group.py +52 -36
- manim/cli/checkhealth/checks.py +92 -76
- manim/cli/checkhealth/commands.py +12 -5
- manim/cli/default_group.py +148 -24
- manim/cli/init/commands.py +28 -23
- manim/cli/plugins/commands.py +13 -3
- manim/cli/render/commands.py +47 -42
- manim/cli/render/global_options.py +43 -9
- manim/cli/render/render_options.py +84 -19
- manim/constants.py +11 -4
- manim/mobject/frame.py +0 -1
- manim/mobject/geometry/arc.py +109 -75
- manim/mobject/geometry/boolean_ops.py +20 -17
- manim/mobject/geometry/labeled.py +300 -77
- manim/mobject/geometry/line.py +120 -60
- manim/mobject/geometry/polygram.py +109 -25
- manim/mobject/geometry/shape_matchers.py +35 -15
- manim/mobject/geometry/tips.py +36 -27
- manim/mobject/graph.py +48 -40
- manim/mobject/graphing/coordinate_systems.py +110 -45
- manim/mobject/graphing/functions.py +16 -10
- manim/mobject/graphing/number_line.py +23 -9
- manim/mobject/graphing/probability.py +2 -10
- manim/mobject/graphing/scale.py +6 -5
- manim/mobject/matrix.py +17 -19
- manim/mobject/mobject.py +149 -103
- manim/mobject/opengl/opengl_geometry.py +4 -8
- manim/mobject/opengl/opengl_mobject.py +506 -343
- manim/mobject/opengl/opengl_point_cloud_mobject.py +3 -7
- manim/mobject/opengl/opengl_surface.py +1 -2
- manim/mobject/opengl/opengl_vectorized_mobject.py +27 -65
- manim/mobject/svg/brace.py +61 -13
- manim/mobject/svg/svg_mobject.py +2 -1
- manim/mobject/table.py +11 -12
- manim/mobject/text/code_mobject.py +186 -550
- manim/mobject/text/numbers.py +7 -7
- manim/mobject/text/tex_mobject.py +22 -13
- manim/mobject/text/text_mobject.py +29 -20
- manim/mobject/three_d/polyhedra.py +98 -1
- manim/mobject/three_d/three_dimensions.py +59 -31
- manim/mobject/types/image_mobject.py +37 -23
- manim/mobject/types/point_cloud_mobject.py +103 -67
- manim/mobject/types/vectorized_mobject.py +387 -214
- manim/mobject/value_tracker.py +2 -1
- manim/mobject/vector_field.py +2 -4
- manim/opengl/__init__.py +3 -3
- manim/plugins/__init__.py +2 -3
- manim/plugins/plugins_flags.py +3 -3
- manim/renderer/cairo_renderer.py +11 -11
- manim/renderer/opengl_renderer.py +19 -20
- manim/renderer/shader.py +2 -3
- manim/renderer/shader_wrapper.py +3 -2
- manim/scene/moving_camera_scene.py +23 -0
- manim/scene/scene.py +72 -41
- manim/scene/scene_file_writer.py +313 -164
- manim/scene/section.py +15 -15
- manim/scene/three_d_scene.py +8 -15
- manim/scene/vector_space_scene.py +3 -6
- manim/typing.py +326 -66
- manim/utils/bezier.py +1658 -381
- manim/utils/caching.py +11 -5
- manim/utils/color/AS2700.py +2 -0
- manim/utils/color/BS381.py +2 -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 +2 -0
- manim/utils/color/__init__.py +8 -5
- manim/utils/color/core.py +818 -301
- manim/utils/color/manim_colors.py +7 -9
- manim/utils/commands.py +40 -19
- manim/utils/config_ops.py +18 -13
- manim/utils/debug.py +8 -6
- manim/utils/deprecation.py +92 -43
- manim/utils/docbuild/autoaliasattr_directive.py +45 -8
- manim/utils/docbuild/autocolor_directive.py +12 -13
- manim/utils/docbuild/manim_directive.py +35 -29
- manim/utils/docbuild/module_parsing.py +74 -27
- manim/utils/family.py +3 -3
- manim/utils/family_ops.py +12 -4
- manim/utils/file_ops.py +22 -16
- manim/utils/hashing.py +7 -7
- manim/utils/images.py +10 -4
- manim/utils/ipython_magic.py +12 -8
- manim/utils/iterables.py +161 -119
- manim/utils/module_ops.py +55 -19
- manim/utils/opengl.py +68 -23
- manim/utils/parameter_parsing.py +3 -2
- manim/utils/paths.py +11 -5
- manim/utils/polylabel.py +168 -0
- manim/utils/qhull.py +218 -0
- manim/utils/rate_functions.py +69 -32
- manim/utils/simple_functions.py +24 -15
- manim/utils/sounds.py +7 -1
- manim/utils/space_ops.py +48 -37
- 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 +20 -14
- manim/utils/tex.py +4 -2
- manim/utils/tex_file_writing.py +45 -45
- manim/utils/tex_templates.py +1 -1
- manim/utils/unit.py +6 -5
- {manim-0.18.1.dist-info → manim-0.19.0.dist-info}/METADATA +16 -9
- manim-0.19.0.dist-info/RECORD +221 -0
- {manim-0.18.1.dist-info → manim-0.19.0.dist-info}/WHEEL +1 -1
- manim-0.18.1.dist-info/RECORD +0 -217
- {manim-0.18.1.dist-info → manim-0.19.0.dist-info}/LICENSE +0 -0
- {manim-0.18.1.dist-info → manim-0.19.0.dist-info}/LICENSE.community +0 -0
- {manim-0.18.1.dist-info → manim-0.19.0.dist-info}/entry_points.txt +0 -0
|
@@ -178,7 +178,7 @@ def always_rotate(mobject: Mobject, rate: float = 20 * DEGREES, **kwargs) -> Mob
|
|
|
178
178
|
|
|
179
179
|
|
|
180
180
|
def turn_animation_into_updater(
|
|
181
|
-
animation: Animation, cycle: bool = False, **kwargs
|
|
181
|
+
animation: Animation, cycle: bool = False, delay: float = 0, **kwargs
|
|
182
182
|
) -> Mobject:
|
|
183
183
|
"""
|
|
184
184
|
Add an updater to the animation's mobject which applies
|
|
@@ -187,6 +187,8 @@ def turn_animation_into_updater(
|
|
|
187
187
|
If cycle is True, this repeats over and over. Otherwise,
|
|
188
188
|
the updater will be popped upon completion
|
|
189
189
|
|
|
190
|
+
The ``delay`` parameter is the delay (in seconds) before the animation starts..
|
|
191
|
+
|
|
190
192
|
Examples
|
|
191
193
|
--------
|
|
192
194
|
|
|
@@ -206,21 +208,22 @@ def turn_animation_into_updater(
|
|
|
206
208
|
mobject = animation.mobject
|
|
207
209
|
animation.suspend_mobject_updating = False
|
|
208
210
|
animation.begin()
|
|
209
|
-
animation.total_time =
|
|
211
|
+
animation.total_time = -delay
|
|
210
212
|
|
|
211
213
|
def update(m: Mobject, dt: float):
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
214
|
+
if animation.total_time >= 0:
|
|
215
|
+
run_time = animation.get_run_time()
|
|
216
|
+
time_ratio = animation.total_time / run_time
|
|
217
|
+
if cycle:
|
|
218
|
+
alpha = time_ratio % 1
|
|
219
|
+
else:
|
|
220
|
+
alpha = np.clip(time_ratio, 0, 1)
|
|
221
|
+
if alpha >= 1:
|
|
222
|
+
animation.finish()
|
|
223
|
+
m.remove_updater(update)
|
|
224
|
+
return
|
|
225
|
+
animation.interpolate(alpha)
|
|
226
|
+
animation.update_mobjects(dt)
|
|
224
227
|
animation.total_time += dt
|
|
225
228
|
|
|
226
229
|
mobject.add_updater(update)
|
manim/camera/camera.py
CHANGED
|
@@ -8,8 +8,9 @@ import copy
|
|
|
8
8
|
import itertools as it
|
|
9
9
|
import operator as op
|
|
10
10
|
import pathlib
|
|
11
|
+
from collections.abc import Iterable
|
|
11
12
|
from functools import reduce
|
|
12
|
-
from typing import Any, Callable
|
|
13
|
+
from typing import Any, Callable
|
|
13
14
|
|
|
14
15
|
import cairo
|
|
15
16
|
import numpy as np
|
|
@@ -376,7 +377,6 @@ class Camera:
|
|
|
376
377
|
np.array
|
|
377
378
|
The pixel array which can then be passed to set_background.
|
|
378
379
|
"""
|
|
379
|
-
|
|
380
380
|
logger.info("Starting set_background")
|
|
381
381
|
coords = self.get_coords_of_all_pixels()
|
|
382
382
|
new_background = np.apply_along_axis(coords_to_colors_func, 2, coords)
|
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
|
@@ -8,24 +8,26 @@ group.
|
|
|
8
8
|
|
|
9
9
|
from __future__ import annotations
|
|
10
10
|
|
|
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
16
|
import cloup
|
|
15
17
|
from rich.errors import StyleSyntaxError
|
|
16
18
|
from rich.style import Style
|
|
17
19
|
|
|
18
|
-
from
|
|
19
|
-
from
|
|
20
|
-
from
|
|
21
|
-
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
|
|
22
24
|
|
|
23
25
|
RICH_COLOUR_INSTRUCTIONS: str = """
|
|
24
26
|
[red]The default colour is used by the input statement.
|
|
25
27
|
If left empty, the default colour will be used.[/red]
|
|
26
28
|
[magenta] For a full list of styles, visit[/magenta] [green]https://rich.readthedocs.io/en/latest/style.html[/green]
|
|
27
29
|
"""
|
|
28
|
-
RICH_NON_STYLE_ENTRIES: str = ["log.width", "log.height", "log.timestamps"]
|
|
30
|
+
RICH_NON_STYLE_ENTRIES: list[str] = ["log.width", "log.height", "log.timestamps"]
|
|
29
31
|
|
|
30
32
|
__all__ = [
|
|
31
33
|
"value_from_string",
|
|
@@ -40,7 +42,8 @@ __all__ = [
|
|
|
40
42
|
|
|
41
43
|
|
|
42
44
|
def value_from_string(value: str) -> str | int | bool:
|
|
43
|
-
"""
|
|
45
|
+
"""Extract the literal of proper datatype from a ``value`` string.
|
|
46
|
+
|
|
44
47
|
Parameters
|
|
45
48
|
----------
|
|
46
49
|
value
|
|
@@ -48,51 +51,60 @@ def value_from_string(value: str) -> str | int | bool:
|
|
|
48
51
|
|
|
49
52
|
Returns
|
|
50
53
|
-------
|
|
51
|
-
|
|
52
|
-
|
|
54
|
+
:class:`str` | :class:`int` | :class:`bool`
|
|
55
|
+
The literal of appropriate datatype.
|
|
53
56
|
"""
|
|
54
|
-
|
|
57
|
+
with contextlib.suppress(SyntaxError, ValueError):
|
|
55
58
|
value = literal_eval(value)
|
|
56
|
-
except (SyntaxError, ValueError):
|
|
57
|
-
pass
|
|
58
59
|
return value
|
|
59
60
|
|
|
60
61
|
|
|
61
|
-
def _is_expected_datatype(
|
|
62
|
-
|
|
63
|
-
|
|
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``.
|
|
64
68
|
|
|
65
69
|
Parameters
|
|
66
70
|
----------
|
|
67
71
|
value
|
|
68
|
-
The string of the value to check
|
|
72
|
+
The string of the value to check, obtained from reading the user input.
|
|
69
73
|
expected
|
|
70
|
-
The string of the literal datatype must be matched by
|
|
71
|
-
reading the cfg file.
|
|
72
|
-
|
|
73
|
-
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``.
|
|
74
79
|
|
|
75
80
|
Returns
|
|
76
81
|
-------
|
|
77
82
|
:class:`bool`
|
|
78
|
-
Whether or not
|
|
83
|
+
Whether or not the literal from ``value`` matches the datatype of the
|
|
84
|
+
literal from ``expected``.
|
|
79
85
|
"""
|
|
80
|
-
|
|
81
|
-
|
|
86
|
+
value_literal = value_from_string(value)
|
|
87
|
+
ExpectedLiteralType = type(value_from_string(expected))
|
|
82
88
|
|
|
83
|
-
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
|
+
)
|
|
84
94
|
|
|
85
95
|
|
|
86
96
|
def is_valid_style(style: str) -> bool:
|
|
87
|
-
"""Checks whether the entered color is
|
|
97
|
+
"""Checks whether the entered color style is valid, according to ``rich``.
|
|
98
|
+
|
|
88
99
|
Parameters
|
|
89
100
|
----------
|
|
90
101
|
style
|
|
91
102
|
The style to check whether it is valid.
|
|
103
|
+
|
|
92
104
|
Returns
|
|
93
105
|
-------
|
|
94
|
-
|
|
95
|
-
|
|
106
|
+
:class:`bool`
|
|
107
|
+
Whether the color style is valid or not, according to ``rich``.
|
|
96
108
|
"""
|
|
97
109
|
try:
|
|
98
110
|
Style.parse(style)
|
|
@@ -101,16 +113,20 @@ def is_valid_style(style: str) -> bool:
|
|
|
101
113
|
return False
|
|
102
114
|
|
|
103
115
|
|
|
104
|
-
def replace_keys(default: dict) -> dict:
|
|
105
|
-
"""
|
|
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
|
+
|
|
106
120
|
Parameters
|
|
107
121
|
----------
|
|
108
122
|
default
|
|
109
|
-
The dictionary
|
|
123
|
+
The dictionary whose keys will be checked and replaced.
|
|
124
|
+
|
|
110
125
|
Returns
|
|
111
126
|
-------
|
|
112
127
|
:class:`dict`
|
|
113
|
-
The dictionary
|
|
128
|
+
The dictionary whose keys are modified by replacing ``_`` with ``.``
|
|
129
|
+
and vice versa.
|
|
114
130
|
"""
|
|
115
131
|
for key in default:
|
|
116
132
|
if "_" in key:
|
|
@@ -134,7 +150,7 @@ def replace_keys(default: dict) -> dict:
|
|
|
134
150
|
help="Manages Manim configuration files.",
|
|
135
151
|
)
|
|
136
152
|
@cloup.pass_context
|
|
137
|
-
def cfg(ctx):
|
|
153
|
+
def cfg(ctx: cloup.Context) -> None:
|
|
138
154
|
"""Responsible for the cfg subcommand."""
|
|
139
155
|
pass
|
|
140
156
|
|
|
@@ -148,7 +164,7 @@ def cfg(ctx):
|
|
|
148
164
|
help="Specify if this config is for user or the working directory.",
|
|
149
165
|
)
|
|
150
166
|
@cloup.option("-o", "--open", "openfile", is_flag=True)
|
|
151
|
-
def write(level: str = None, openfile: bool = False) -> None:
|
|
167
|
+
def write(level: str | None = None, openfile: bool = False) -> None:
|
|
152
168
|
config_paths = config_file_paths()
|
|
153
169
|
console.print(
|
|
154
170
|
"[yellow bold]Manim Configuration File Writer[/yellow bold]",
|
|
@@ -167,7 +183,7 @@ To save your config please save that file and place it in your current working d
|
|
|
167
183
|
action = "save this as"
|
|
168
184
|
for category in parser:
|
|
169
185
|
console.print(f"{category}", style="bold green underline")
|
|
170
|
-
default = parser[category]
|
|
186
|
+
default = cast(dict[str, Any], parser[category])
|
|
171
187
|
if category == "logger":
|
|
172
188
|
console.print(RICH_COLOUR_INSTRUCTIONS)
|
|
173
189
|
default = replace_keys(default)
|
|
@@ -197,7 +213,7 @@ To save your config please save that file and place it in your current working d
|
|
|
197
213
|
"""Not enough values in input.
|
|
198
214
|
You may have added a new entry to default.cfg, in which case you will have to
|
|
199
215
|
modify write_cfg_subcmd_input to account for it.""",
|
|
200
|
-
)
|
|
216
|
+
) from None
|
|
201
217
|
if temp:
|
|
202
218
|
while temp and not _is_expected_datatype(
|
|
203
219
|
temp,
|
|
@@ -250,7 +266,7 @@ modify write_cfg_subcmd_input to account for it.""",
|
|
|
250
266
|
|
|
251
267
|
|
|
252
268
|
@cfg.command(context_settings=cli_ctx_settings)
|
|
253
|
-
def show():
|
|
269
|
+
def show() -> None:
|
|
254
270
|
parser = make_config_parser()
|
|
255
271
|
rich_non_style_entries = [a.replace(".", "_") for a in RICH_NON_STYLE_ENTRIES]
|
|
256
272
|
for category in parser:
|
|
@@ -270,7 +286,7 @@ def show():
|
|
|
270
286
|
@cfg.command(context_settings=cli_ctx_settings)
|
|
271
287
|
@cloup.option("-d", "--directory", default=Path.cwd())
|
|
272
288
|
@cloup.pass_context
|
|
273
|
-
def export(ctx, directory):
|
|
289
|
+
def export(ctx: cloup.Context, directory: str) -> None:
|
|
274
290
|
directory_path = Path(directory)
|
|
275
291
|
if directory_path.absolute == Path.cwd().absolute:
|
|
276
292
|
console.print(
|
manim/cli/checkhealth/checks.py
CHANGED
|
@@ -1,65 +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
|
|
10
|
-
|
|
11
|
-
from ..._config import config
|
|
9
|
+
from typing import Callable, Protocol, cast
|
|
12
10
|
|
|
13
11
|
__all__ = ["HEALTH_CHECKS"]
|
|
14
12
|
|
|
15
|
-
|
|
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] = []
|
|
16
25
|
|
|
17
26
|
|
|
18
27
|
def healthcheck(
|
|
19
28
|
description: str,
|
|
20
29
|
recommendation: str,
|
|
21
|
-
skip_on_failed: list[
|
|
22
|
-
post_fail_fix_hook: Callable | None = None,
|
|
23
|
-
):
|
|
30
|
+
skip_on_failed: list[HealthCheckFunction | str] | None = None,
|
|
31
|
+
post_fail_fix_hook: Callable[..., object] | None = None,
|
|
32
|
+
) -> Callable[[Callable[[], bool]], HealthCheckFunction]:
|
|
24
33
|
"""Decorator used for declaring health checks.
|
|
25
34
|
|
|
26
|
-
This decorator attaches some data to a function,
|
|
27
|
-
|
|
35
|
+
This decorator attaches some data to a function, which is then added to a
|
|
36
|
+
a list containing all checks.
|
|
28
37
|
|
|
29
38
|
Parameters
|
|
30
39
|
----------
|
|
31
40
|
description
|
|
32
|
-
A brief description of this check, displayed when
|
|
33
|
-
|
|
41
|
+
A brief description of this check, displayed when the ``checkhealth``
|
|
42
|
+
subcommand is run.
|
|
34
43
|
recommendation
|
|
35
44
|
Help text which is displayed in case the check fails.
|
|
36
45
|
skip_on_failed
|
|
37
|
-
A list of check functions which, if they fail, cause
|
|
38
|
-
|
|
46
|
+
A list of check functions which, if they fail, cause the current check
|
|
47
|
+
to be skipped.
|
|
39
48
|
post_fail_fix_hook
|
|
40
|
-
A function that is
|
|
41
|
-
|
|
42
|
-
|
|
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.
|
|
43
52
|
|
|
44
53
|
Returns
|
|
45
54
|
-------
|
|
46
|
-
|
|
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.
|
|
47
58
|
"""
|
|
59
|
+
new_skip_on_failed: list[str]
|
|
48
60
|
if skip_on_failed is None:
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
+
]
|
|
53
66
|
|
|
54
|
-
def
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
|
61
75
|
|
|
62
|
-
return
|
|
76
|
+
return wrapper
|
|
63
77
|
|
|
64
78
|
|
|
65
79
|
@healthcheck(
|
|
@@ -77,7 +91,14 @@ def healthcheck(
|
|
|
77
91
|
"PATH variable."
|
|
78
92
|
),
|
|
79
93
|
)
|
|
80
|
-
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
|
+
"""
|
|
81
102
|
path_to_manim = shutil.which("manim")
|
|
82
103
|
return path_to_manim is not None
|
|
83
104
|
|
|
@@ -93,10 +114,30 @@ def is_manim_on_path():
|
|
|
93
114
|
),
|
|
94
115
|
skip_on_failed=[is_manim_on_path],
|
|
95
116
|
)
|
|
96
|
-
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
|
+
"""
|
|
97
137
|
path_to_manim = shutil.which("manim")
|
|
98
|
-
|
|
99
|
-
|
|
138
|
+
assert path_to_manim is not None
|
|
139
|
+
with open(path_to_manim, "rb") as manim_binary:
|
|
140
|
+
manim_exec = manim_binary.read()
|
|
100
141
|
|
|
101
142
|
# first condition below corresponds to the executable being
|
|
102
143
|
# some sort of python script. second condition happens when
|
|
@@ -104,45 +145,6 @@ def is_manim_executable_associated_to_this_library():
|
|
|
104
145
|
return b"manim.__main__" in manim_exec or b'"%~dp0\\manim"' in manim_exec
|
|
105
146
|
|
|
106
147
|
|
|
107
|
-
@healthcheck(
|
|
108
|
-
description="Checking whether ffmpeg is available",
|
|
109
|
-
recommendation=(
|
|
110
|
-
"Manim does not work without ffmpeg. Please follow our "
|
|
111
|
-
"installation instructions "
|
|
112
|
-
"at https://docs.manim.community/en/stable/installation.html "
|
|
113
|
-
"to download ffmpeg. Then, either ...\n\n"
|
|
114
|
-
"(a) ... make the ffmpeg executable available to your system's PATH,\n"
|
|
115
|
-
"(b) or, alternatively, use <manim cfg write --open> to create a "
|
|
116
|
-
"custom configuration and set the ffmpeg_executable variable to the "
|
|
117
|
-
"full absolute path to the ffmpeg executable."
|
|
118
|
-
),
|
|
119
|
-
)
|
|
120
|
-
def is_ffmpeg_available():
|
|
121
|
-
path_to_ffmpeg = shutil.which(config.ffmpeg_executable)
|
|
122
|
-
return path_to_ffmpeg is not None and os.access(path_to_ffmpeg, os.X_OK)
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
@healthcheck(
|
|
126
|
-
description="Checking whether ffmpeg is working",
|
|
127
|
-
recommendation=(
|
|
128
|
-
"Your installed version of ffmpeg does not support x264 encoding, "
|
|
129
|
-
"which manim requires. Please follow our installation instructions "
|
|
130
|
-
"at https://docs.manim.community/en/stable/installation.html "
|
|
131
|
-
"to download and install a newer version of ffmpeg."
|
|
132
|
-
),
|
|
133
|
-
skip_on_failed=[is_ffmpeg_available],
|
|
134
|
-
)
|
|
135
|
-
def is_ffmpeg_working():
|
|
136
|
-
ffmpeg_version = subprocess.run(
|
|
137
|
-
[config.ffmpeg_executable, "-version"],
|
|
138
|
-
stdout=subprocess.PIPE,
|
|
139
|
-
).stdout.decode()
|
|
140
|
-
return (
|
|
141
|
-
ffmpeg_version.startswith("ffmpeg version")
|
|
142
|
-
and "--enable-libx264" in ffmpeg_version
|
|
143
|
-
)
|
|
144
|
-
|
|
145
|
-
|
|
146
148
|
@healthcheck(
|
|
147
149
|
description="Checking whether latex is available",
|
|
148
150
|
recommendation=(
|
|
@@ -155,7 +157,14 @@ def is_ffmpeg_working():
|
|
|
155
157
|
"LaTeX distribution on your operating system."
|
|
156
158
|
),
|
|
157
159
|
)
|
|
158
|
-
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
|
+
"""
|
|
159
168
|
path_to_latex = shutil.which("latex")
|
|
160
169
|
return path_to_latex is not None and os.access(path_to_latex, os.X_OK)
|
|
161
170
|
|
|
@@ -170,6 +179,13 @@ def is_latex_available():
|
|
|
170
179
|
),
|
|
171
180
|
skip_on_failed=[is_latex_available],
|
|
172
181
|
)
|
|
173
|
-
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
|
+
"""
|
|
174
190
|
path_to_dvisvgm = shutil.which("dvisvgm")
|
|
175
191
|
return path_to_dvisvgm is not None and os.access(path_to_dvisvgm, os.X_OK)
|
|
@@ -6,11 +6,12 @@ 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
|
|
14
15
|
|
|
15
16
|
__all__ = ["checkhealth"]
|
|
16
17
|
|
|
@@ -18,13 +19,13 @@ __all__ = ["checkhealth"]
|
|
|
18
19
|
@cloup.command(
|
|
19
20
|
context_settings=None,
|
|
20
21
|
)
|
|
21
|
-
def checkhealth():
|
|
22
|
+
def checkhealth() -> None:
|
|
22
23
|
"""This subcommand checks whether Manim is installed correctly
|
|
23
24
|
and has access to its required (and optional) system dependencies.
|
|
24
25
|
"""
|
|
25
26
|
click.echo(f"Python executable: {sys.executable}\n")
|
|
26
27
|
click.echo("Checking whether your installation of Manim Community is healthy...")
|
|
27
|
-
failed_checks = []
|
|
28
|
+
failed_checks: list[HealthCheckFunction] = []
|
|
28
29
|
|
|
29
30
|
for check in HEALTH_CHECKS:
|
|
30
31
|
click.echo(f"- {check.description} ... ", nl=False)
|
|
@@ -62,7 +63,7 @@ def checkhealth():
|
|
|
62
63
|
import manim as mn
|
|
63
64
|
|
|
64
65
|
class CheckHealthDemo(mn.Scene):
|
|
65
|
-
def
|
|
66
|
+
def _inner_construct(self) -> None:
|
|
66
67
|
banner = mn.ManimBanner().shift(mn.UP * 0.5)
|
|
67
68
|
self.play(banner.create())
|
|
68
69
|
self.wait(0.5)
|
|
@@ -79,5 +80,11 @@ def checkhealth():
|
|
|
79
80
|
mn.FadeOut(text_tex_group, shift=mn.DOWN),
|
|
80
81
|
)
|
|
81
82
|
|
|
83
|
+
def construct(self) -> None:
|
|
84
|
+
self.execution_time = timeit.timeit(self._inner_construct, number=1)
|
|
85
|
+
|
|
82
86
|
with mn.tempconfig({"preview": True, "disable_caching": True}):
|
|
83
|
-
CheckHealthDemo()
|
|
87
|
+
scene = CheckHealthDemo()
|
|
88
|
+
scene.render()
|
|
89
|
+
|
|
90
|
+
click.echo(f"Scene rendered in {scene.execution_time:.2f} seconds.")
|