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
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"""Auxiliary module for the checkhealth subcommand, contains
|
|
2
|
+
the actual check implementations.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import shutil
|
|
9
|
+
from collections.abc import Callable
|
|
10
|
+
from typing import Protocol, cast
|
|
11
|
+
|
|
12
|
+
__all__ = ["HEALTH_CHECKS"]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class HealthCheckFunction(Protocol):
|
|
16
|
+
description: str
|
|
17
|
+
recommendation: str
|
|
18
|
+
skip_on_failed: list[str]
|
|
19
|
+
post_fail_fix_hook: Callable[..., object] | None
|
|
20
|
+
__name__: str
|
|
21
|
+
|
|
22
|
+
def __call__(self) -> bool: ...
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
HEALTH_CHECKS: list[HealthCheckFunction] = []
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def healthcheck(
|
|
29
|
+
description: str,
|
|
30
|
+
recommendation: str,
|
|
31
|
+
skip_on_failed: list[HealthCheckFunction | str] | None = None,
|
|
32
|
+
post_fail_fix_hook: Callable[..., object] | None = None,
|
|
33
|
+
) -> Callable[[Callable[[], bool]], HealthCheckFunction]:
|
|
34
|
+
"""Decorator used for declaring health checks.
|
|
35
|
+
|
|
36
|
+
This decorator attaches some data to a function, which is then added to a
|
|
37
|
+
a list containing all checks.
|
|
38
|
+
|
|
39
|
+
Parameters
|
|
40
|
+
----------
|
|
41
|
+
description
|
|
42
|
+
A brief description of this check, displayed when the ``checkhealth``
|
|
43
|
+
subcommand is run.
|
|
44
|
+
recommendation
|
|
45
|
+
Help text which is displayed in case the check fails.
|
|
46
|
+
skip_on_failed
|
|
47
|
+
A list of check functions which, if they fail, cause the current check
|
|
48
|
+
to be skipped.
|
|
49
|
+
post_fail_fix_hook
|
|
50
|
+
A function that is meant to (interactively) help to fix the detected
|
|
51
|
+
problem, if possible. This is only called upon explicit confirmation of
|
|
52
|
+
the user.
|
|
53
|
+
|
|
54
|
+
Returns
|
|
55
|
+
-------
|
|
56
|
+
Callable[Callable[[], bool], :class:`HealthCheckFunction`]
|
|
57
|
+
A decorator which converts a function into a health check function, as
|
|
58
|
+
required by the ``checkhealth`` subcommand.
|
|
59
|
+
"""
|
|
60
|
+
new_skip_on_failed: list[str]
|
|
61
|
+
if skip_on_failed is None:
|
|
62
|
+
new_skip_on_failed = []
|
|
63
|
+
else:
|
|
64
|
+
new_skip_on_failed = [
|
|
65
|
+
skip.__name__ if callable(skip) else skip for skip in skip_on_failed
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
def wrapper(func: Callable[[], bool]) -> HealthCheckFunction:
|
|
69
|
+
health_func = cast(HealthCheckFunction, func)
|
|
70
|
+
health_func.description = description
|
|
71
|
+
health_func.recommendation = recommendation
|
|
72
|
+
health_func.skip_on_failed = new_skip_on_failed
|
|
73
|
+
health_func.post_fail_fix_hook = post_fail_fix_hook
|
|
74
|
+
HEALTH_CHECKS.append(health_func)
|
|
75
|
+
return health_func
|
|
76
|
+
|
|
77
|
+
return wrapper
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@healthcheck(
|
|
81
|
+
description="Checking whether manim is on your PATH",
|
|
82
|
+
recommendation=(
|
|
83
|
+
"The command <manim> is currently not on your system's PATH.\n\n"
|
|
84
|
+
"You can work around this by calling the manim module directly "
|
|
85
|
+
"via <python -m manim> instead of just <manim>.\n\n"
|
|
86
|
+
"To fix the PATH issue properly: "
|
|
87
|
+
"Usually, the Python package installer pip issues a warning "
|
|
88
|
+
"during the installation which contains more information. "
|
|
89
|
+
"Consider reinstalling manim via <pip uninstall manim> "
|
|
90
|
+
"followed by <pip install manim> to see the warning again, "
|
|
91
|
+
"then consult the internet on how to modify your system's "
|
|
92
|
+
"PATH variable."
|
|
93
|
+
),
|
|
94
|
+
)
|
|
95
|
+
def is_manim_on_path() -> bool:
|
|
96
|
+
"""Check whether ``manim`` is in ``PATH``.
|
|
97
|
+
|
|
98
|
+
Returns
|
|
99
|
+
-------
|
|
100
|
+
:class:`bool`
|
|
101
|
+
Whether ``manim`` is in ``PATH`` or not.
|
|
102
|
+
"""
|
|
103
|
+
path_to_manim = shutil.which("manim")
|
|
104
|
+
return path_to_manim is not None
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@healthcheck(
|
|
108
|
+
description="Checking whether the executable belongs to manim",
|
|
109
|
+
recommendation=(
|
|
110
|
+
"The command <manim> does not belong to your installed version "
|
|
111
|
+
"of this library, it likely belongs to manimgl / manimlib.\n\n"
|
|
112
|
+
"Run manim via <python -m manim> or via <manimce>, or uninstall "
|
|
113
|
+
"and reinstall manim via <pip install --upgrade "
|
|
114
|
+
"--force-reinstall manim> to fix this."
|
|
115
|
+
),
|
|
116
|
+
skip_on_failed=[is_manim_on_path],
|
|
117
|
+
)
|
|
118
|
+
def is_manim_executable_associated_to_this_library() -> bool:
|
|
119
|
+
"""Check whether the ``manim`` executable in ``PATH`` is associated to this
|
|
120
|
+
library. To verify this, the executable should look like this:
|
|
121
|
+
|
|
122
|
+
.. code-block:: python
|
|
123
|
+
|
|
124
|
+
#!<MANIM_PATH>/.../python
|
|
125
|
+
import sys
|
|
126
|
+
from manim.__main__ import main
|
|
127
|
+
|
|
128
|
+
if __name__ == "__main__":
|
|
129
|
+
sys.exit(main())
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
Returns
|
|
133
|
+
-------
|
|
134
|
+
:class:`bool`
|
|
135
|
+
Whether the ``manim`` executable in ``PATH`` is associated to this
|
|
136
|
+
library or not.
|
|
137
|
+
"""
|
|
138
|
+
path_to_manim = shutil.which("manim")
|
|
139
|
+
assert path_to_manim is not None
|
|
140
|
+
with open(path_to_manim, "rb") as manim_binary:
|
|
141
|
+
manim_exec = manim_binary.read()
|
|
142
|
+
|
|
143
|
+
# first condition below corresponds to the executable being
|
|
144
|
+
# some sort of python script. second condition happens when
|
|
145
|
+
# the executable is actually a Windows batch file.
|
|
146
|
+
return b"manim.__main__" in manim_exec or b'"%~dp0\\manim"' in manim_exec
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
@healthcheck(
|
|
150
|
+
description="Checking whether latex is available",
|
|
151
|
+
recommendation=(
|
|
152
|
+
"Manim cannot find <latex> on your system's PATH. "
|
|
153
|
+
"You will not be able to use Tex and MathTex mobjects "
|
|
154
|
+
"in your scenes.\n\n"
|
|
155
|
+
"Consult our installation instructions "
|
|
156
|
+
"at https://docs.manim.community/en/stable/installation.html "
|
|
157
|
+
"or search the web for instructions on how to install a "
|
|
158
|
+
"LaTeX distribution on your operating system."
|
|
159
|
+
),
|
|
160
|
+
)
|
|
161
|
+
def is_latex_available() -> bool:
|
|
162
|
+
"""Check whether ``latex`` is in ``PATH`` and can be executed.
|
|
163
|
+
|
|
164
|
+
Returns
|
|
165
|
+
-------
|
|
166
|
+
:class:`bool`
|
|
167
|
+
Whether ``latex`` is in ``PATH`` and can be executed or not.
|
|
168
|
+
"""
|
|
169
|
+
path_to_latex = shutil.which("latex")
|
|
170
|
+
return path_to_latex is not None and os.access(path_to_latex, os.X_OK)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
@healthcheck(
|
|
174
|
+
description="Checking whether dvisvgm is available",
|
|
175
|
+
recommendation=(
|
|
176
|
+
"Manim could find <latex>, but not <dvisvgm> on your system's "
|
|
177
|
+
"PATH. Make sure your installed LaTeX distribution comes with "
|
|
178
|
+
"dvisvgm and consider installing a larger distribution if it "
|
|
179
|
+
"does not."
|
|
180
|
+
),
|
|
181
|
+
skip_on_failed=[is_latex_available],
|
|
182
|
+
)
|
|
183
|
+
def is_dvisvgm_available() -> bool:
|
|
184
|
+
"""Check whether ``dvisvgm`` is in ``PATH`` and can be executed.
|
|
185
|
+
|
|
186
|
+
Returns
|
|
187
|
+
-------
|
|
188
|
+
:class:`bool`
|
|
189
|
+
Whether ``dvisvgm`` is in ``PATH`` and can be executed or not.
|
|
190
|
+
"""
|
|
191
|
+
path_to_dvisvgm = shutil.which("dvisvgm")
|
|
192
|
+
return path_to_dvisvgm is not None and os.access(path_to_dvisvgm, os.X_OK)
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""A CLI utility helping to diagnose problems with
|
|
2
|
+
your Manim installation.
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import sys
|
|
9
|
+
import timeit
|
|
10
|
+
|
|
11
|
+
import click
|
|
12
|
+
import cloup
|
|
13
|
+
|
|
14
|
+
from manim.cli.checkhealth.checks import HEALTH_CHECKS, HealthCheckFunction
|
|
15
|
+
|
|
16
|
+
__all__ = ["checkhealth"]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@cloup.command(
|
|
20
|
+
context_settings=None,
|
|
21
|
+
)
|
|
22
|
+
def checkhealth() -> None:
|
|
23
|
+
"""This subcommand checks whether Manim is installed correctly
|
|
24
|
+
and has access to its required (and optional) system dependencies.
|
|
25
|
+
"""
|
|
26
|
+
click.echo(f"Python executable: {sys.executable}\n")
|
|
27
|
+
click.echo("Checking whether your installation of Manim Community is healthy...")
|
|
28
|
+
failed_checks: list[HealthCheckFunction] = []
|
|
29
|
+
|
|
30
|
+
for check in HEALTH_CHECKS:
|
|
31
|
+
click.echo(f"- {check.description} ... ", nl=False)
|
|
32
|
+
if any(
|
|
33
|
+
failed_check.__name__ in check.skip_on_failed
|
|
34
|
+
for failed_check in failed_checks
|
|
35
|
+
):
|
|
36
|
+
click.secho("SKIPPED", fg="blue")
|
|
37
|
+
continue
|
|
38
|
+
check_result = check()
|
|
39
|
+
if check_result:
|
|
40
|
+
click.secho("PASSED", fg="green")
|
|
41
|
+
else:
|
|
42
|
+
click.secho("FAILED", fg="red")
|
|
43
|
+
failed_checks.append(check)
|
|
44
|
+
|
|
45
|
+
click.echo()
|
|
46
|
+
|
|
47
|
+
if failed_checks:
|
|
48
|
+
click.echo(
|
|
49
|
+
"There are problems with your installation, "
|
|
50
|
+
"here are some recommendations to fix them:"
|
|
51
|
+
)
|
|
52
|
+
for ind, failed_check in enumerate(failed_checks):
|
|
53
|
+
click.echo(failed_check.recommendation)
|
|
54
|
+
if ind + 1 < len(failed_checks):
|
|
55
|
+
click.confirm("Continue with next recommendation?")
|
|
56
|
+
|
|
57
|
+
else: # no problems detected!
|
|
58
|
+
click.echo("No problems detected, your installation seems healthy!")
|
|
59
|
+
render_test_scene = click.confirm(
|
|
60
|
+
"Would you like to render and preview a test scene?"
|
|
61
|
+
)
|
|
62
|
+
if render_test_scene:
|
|
63
|
+
import manim as mn
|
|
64
|
+
|
|
65
|
+
class CheckHealthDemo(mn.Scene):
|
|
66
|
+
def _inner_construct(self) -> None:
|
|
67
|
+
banner = mn.ManimBanner().shift(mn.UP * 0.5)
|
|
68
|
+
self.play(banner.create())
|
|
69
|
+
self.wait(0.5)
|
|
70
|
+
self.play(banner.expand())
|
|
71
|
+
self.wait(0.5)
|
|
72
|
+
text_left = mn.Text("All systems operational!")
|
|
73
|
+
formula_right = mn.MathTex(r"\oint_{\gamma} f(z)~dz = 0")
|
|
74
|
+
text_tex_group = mn.VGroup(text_left, formula_right)
|
|
75
|
+
text_tex_group.arrange(mn.RIGHT, buff=1).next_to(banner, mn.DOWN)
|
|
76
|
+
self.play(mn.Write(text_tex_group))
|
|
77
|
+
self.wait(0.5)
|
|
78
|
+
self.play(
|
|
79
|
+
mn.FadeOut(banner, shift=mn.UP),
|
|
80
|
+
mn.FadeOut(text_tex_group, shift=mn.DOWN),
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
def construct(self) -> None:
|
|
84
|
+
self.execution_time = timeit.timeit(self._inner_construct, number=1)
|
|
85
|
+
|
|
86
|
+
with mn.tempconfig({"preview": True, "disable_caching": True}):
|
|
87
|
+
scene = CheckHealthDemo()
|
|
88
|
+
scene.render()
|
|
89
|
+
|
|
90
|
+
click.echo(f"Scene rendered in {scene.execution_time:.2f} seconds.")
|
manim/cli/default_group.py
CHANGED
|
@@ -1,65 +1,198 @@
|
|
|
1
|
-
"""DefaultGroup allows a subcommand to act as the main command
|
|
1
|
+
"""``DefaultGroup`` allows a subcommand to act as the main command.
|
|
2
2
|
|
|
3
3
|
In particular, this class is what allows ``manim`` to act as ``manim render``.
|
|
4
|
+
|
|
5
|
+
.. note::
|
|
6
|
+
This is a vendored version of https://github.com/click-contrib/click-default-group/
|
|
7
|
+
under the BSD 3-Clause "New" or "Revised" License.
|
|
8
|
+
|
|
9
|
+
This library isn't used as a dependency, as we need to inherit from
|
|
10
|
+
:class:`cloup.Group` instead of :class:`click.Group`.
|
|
4
11
|
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import warnings
|
|
16
|
+
from collections.abc import Callable
|
|
17
|
+
from typing import TYPE_CHECKING, Any
|
|
18
|
+
|
|
5
19
|
import cloup
|
|
6
20
|
|
|
7
|
-
from
|
|
21
|
+
from manim.utils.deprecation import deprecated
|
|
8
22
|
|
|
9
23
|
__all__ = ["DefaultGroup"]
|
|
10
24
|
|
|
25
|
+
if TYPE_CHECKING:
|
|
26
|
+
from click import Command, Context
|
|
27
|
+
|
|
11
28
|
|
|
12
29
|
class DefaultGroup(cloup.Group):
|
|
13
|
-
"""Invokes a subcommand marked with ``default=True`` if any subcommand not
|
|
30
|
+
"""Invokes a subcommand marked with ``default=True`` if any subcommand is not
|
|
14
31
|
chosen.
|
|
32
|
+
|
|
33
|
+
Parameters
|
|
34
|
+
----------
|
|
35
|
+
*args
|
|
36
|
+
Positional arguments to forward to :class:`cloup.Group`.
|
|
37
|
+
**kwargs
|
|
38
|
+
Keyword arguments to forward to :class:`cloup.Group`. The keyword
|
|
39
|
+
``ignore_unknown_options`` must be set to ``False``.
|
|
40
|
+
|
|
41
|
+
Attributes
|
|
42
|
+
----------
|
|
43
|
+
default_cmd_name : str | None
|
|
44
|
+
The name of the default command, if specified through the ``default``
|
|
45
|
+
keyword argument. Otherwise, this is set to ``None``.
|
|
46
|
+
default_if_no_args : bool
|
|
47
|
+
Whether to include or not the default command, if no command arguments
|
|
48
|
+
are supplied. This can be specified through the ``default_if_no_args``
|
|
49
|
+
keyword argument. Default is ``False``.
|
|
15
50
|
"""
|
|
16
51
|
|
|
17
|
-
def __init__(self, *args, **kwargs):
|
|
52
|
+
def __init__(self, *args: Any, **kwargs: Any):
|
|
18
53
|
# To resolve as the default command.
|
|
19
54
|
if not kwargs.get("ignore_unknown_options", True):
|
|
20
55
|
raise ValueError("Default group accepts unknown options")
|
|
21
56
|
self.ignore_unknown_options = True
|
|
22
|
-
self.default_cmd_name = kwargs.pop("default", None)
|
|
23
|
-
self.default_if_no_args = kwargs.pop("default_if_no_args", False)
|
|
57
|
+
self.default_cmd_name: str | None = kwargs.pop("default", None)
|
|
58
|
+
self.default_if_no_args: bool = kwargs.pop("default_if_no_args", False)
|
|
24
59
|
super().__init__(*args, **kwargs)
|
|
25
60
|
|
|
26
|
-
def set_default_command(self, command):
|
|
27
|
-
"""Sets a command function as the default command.
|
|
61
|
+
def set_default_command(self, command: Command) -> None:
|
|
62
|
+
"""Sets a command function as the default command.
|
|
63
|
+
|
|
64
|
+
Parameters
|
|
65
|
+
----------
|
|
66
|
+
command
|
|
67
|
+
The command to set as default.
|
|
68
|
+
"""
|
|
28
69
|
cmd_name = command.name
|
|
29
70
|
self.add_command(command)
|
|
30
71
|
self.default_cmd_name = cmd_name
|
|
31
72
|
|
|
32
|
-
def parse_args(self, ctx, args):
|
|
33
|
-
|
|
73
|
+
def parse_args(self, ctx: Context, args: list[str]) -> list[str]:
|
|
74
|
+
"""Parses the list of ``args`` by forwarding it to
|
|
75
|
+
:meth:`cloup.Group.parse_args`. Before doing so, if
|
|
76
|
+
:attr:`default_if_no_args` is set to ``True`` and ``args`` is empty,
|
|
77
|
+
this function appends to it the name of the default command specified
|
|
78
|
+
by :attr:`default_cmd_name`.
|
|
79
|
+
|
|
80
|
+
Parameters
|
|
81
|
+
----------
|
|
82
|
+
ctx
|
|
83
|
+
The Click context.
|
|
84
|
+
args
|
|
85
|
+
A list of arguments. If it's empty and :attr:`default_if_no_args`
|
|
86
|
+
is ``True``, append the name of the default command to it.
|
|
87
|
+
|
|
88
|
+
Returns
|
|
89
|
+
-------
|
|
90
|
+
list[str]
|
|
91
|
+
The parsed arguments.
|
|
92
|
+
"""
|
|
93
|
+
if not args and self.default_if_no_args and self.default_cmd_name:
|
|
34
94
|
args.insert(0, self.default_cmd_name)
|
|
35
|
-
|
|
95
|
+
parsed_args: list[str] = super().parse_args(ctx, args)
|
|
96
|
+
return parsed_args
|
|
97
|
+
|
|
98
|
+
def get_command(self, ctx: Context, cmd_name: str) -> Command | None:
|
|
99
|
+
"""Get a command function by its name, by forwarding the arguments to
|
|
100
|
+
:meth:`cloup.Group.get_command`. If ``cmd_name`` does not match any of
|
|
101
|
+
the command names in :attr:`commands`, attempt to get the default command
|
|
102
|
+
instead.
|
|
103
|
+
|
|
104
|
+
Parameters
|
|
105
|
+
----------
|
|
106
|
+
ctx
|
|
107
|
+
The Click context.
|
|
108
|
+
cmd_name
|
|
109
|
+
The name of the command to get.
|
|
36
110
|
|
|
37
|
-
|
|
38
|
-
|
|
111
|
+
Returns
|
|
112
|
+
-------
|
|
113
|
+
:class:`click.Command` | None
|
|
114
|
+
The command, if found. Otherwise, ``None``.
|
|
115
|
+
"""
|
|
116
|
+
if cmd_name not in self.commands and self.default_cmd_name:
|
|
39
117
|
# No command name matched.
|
|
40
|
-
ctx.arg0 = cmd_name
|
|
118
|
+
ctx.meta["arg0"] = cmd_name
|
|
41
119
|
cmd_name = self.default_cmd_name
|
|
42
120
|
return super().get_command(ctx, cmd_name)
|
|
43
121
|
|
|
44
|
-
def resolve_command(
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
122
|
+
def resolve_command(
|
|
123
|
+
self, ctx: Context, args: list[str]
|
|
124
|
+
) -> tuple[str | None, Command | None, list[str]]:
|
|
125
|
+
"""Given a list of ``args`` given by a CLI, find a command which
|
|
126
|
+
matches the first element, and return its name (``cmd_name``), the
|
|
127
|
+
command function itself (``cmd``) and the rest of the arguments which
|
|
128
|
+
shall be passed to the function (``cmd_args``). If not found, return
|
|
129
|
+
``None``, ``None`` and the rest of the arguments.
|
|
130
|
+
|
|
131
|
+
After resolving the command, if the Click context given by ``ctx``
|
|
132
|
+
contains an ``arg0`` attribute in its :attr:`click.Context.meta`
|
|
133
|
+
dictionary, insert it as the first element of the returned
|
|
134
|
+
``cmd_args``.
|
|
135
|
+
|
|
136
|
+
Parameters
|
|
137
|
+
----------
|
|
138
|
+
ctx
|
|
139
|
+
The Click context.
|
|
140
|
+
cmd_name
|
|
141
|
+
The name of the command to get.
|
|
142
|
+
|
|
143
|
+
Returns
|
|
144
|
+
-------
|
|
145
|
+
cmd_name : str | None
|
|
146
|
+
The command name, if found. Otherwise, ``None``.
|
|
147
|
+
cmd : :class:`click.Command` | None
|
|
148
|
+
The command, if found. Otherwise, ``None``.
|
|
149
|
+
cmd_args : list[str]
|
|
150
|
+
The rest of the arguments to be passed to ``cmd``.
|
|
151
|
+
"""
|
|
152
|
+
cmd_name, cmd, args = super().resolve_command(ctx, args)
|
|
153
|
+
if "arg0" in ctx.meta:
|
|
154
|
+
args.insert(0, ctx.meta["arg0"])
|
|
155
|
+
if cmd is not None:
|
|
156
|
+
cmd_name = cmd.name
|
|
50
157
|
return cmd_name, cmd, args
|
|
51
158
|
|
|
52
|
-
|
|
159
|
+
@deprecated
|
|
160
|
+
def command(
|
|
161
|
+
self, *args: Any, **kwargs: Any
|
|
162
|
+
) -> Callable[[Callable[..., object]], Command]:
|
|
163
|
+
"""Return a decorator which converts any function into the default
|
|
164
|
+
subcommand for this :class:`DefaultGroup`.
|
|
165
|
+
|
|
166
|
+
.. warning::
|
|
167
|
+
This method is deprecated. Use the ``default`` parameter of
|
|
168
|
+
:class:`DefaultGroup` or :meth:`set_default_command` instead.
|
|
169
|
+
|
|
170
|
+
Parameters
|
|
171
|
+
----------
|
|
172
|
+
*args
|
|
173
|
+
Positional arguments to pass to :meth:`cloup.Group.command`.
|
|
174
|
+
**kwargs
|
|
175
|
+
Keyword arguments to pass to :meth:`cloup.Group.command`.
|
|
176
|
+
|
|
177
|
+
Returns
|
|
178
|
+
-------
|
|
179
|
+
Callable[[Callable[..., object]], click.Command]
|
|
180
|
+
A decorator which transforms its input into this
|
|
181
|
+
:class:`DefaultGroup`'s default subcommand.
|
|
182
|
+
"""
|
|
53
183
|
default = kwargs.pop("default", False)
|
|
54
|
-
decorator = super().command(
|
|
184
|
+
decorator: Callable[[Callable[..., object]], Command] = super().command(
|
|
185
|
+
*args, **kwargs
|
|
186
|
+
)
|
|
55
187
|
if not default:
|
|
56
188
|
return decorator
|
|
57
|
-
|
|
58
|
-
"Use default param of DefaultGroup or
|
|
189
|
+
warnings.warn(
|
|
190
|
+
"Use default param of DefaultGroup or set_default_command() instead",
|
|
59
191
|
DeprecationWarning,
|
|
192
|
+
stacklevel=1,
|
|
60
193
|
)
|
|
61
194
|
|
|
62
|
-
def _decorator(f):
|
|
195
|
+
def _decorator(f: Callable) -> Command:
|
|
63
196
|
cmd = decorator(f)
|
|
64
197
|
self.set_default_command(cmd)
|
|
65
198
|
return cmd
|
manim/cli/init/commands.py
CHANGED
|
@@ -5,17 +5,19 @@ init``. Here you can specify options, subcommands, and subgroups for the init
|
|
|
5
5
|
group.
|
|
6
6
|
|
|
7
7
|
"""
|
|
8
|
+
|
|
8
9
|
from __future__ import annotations
|
|
9
10
|
|
|
10
11
|
import configparser
|
|
11
12
|
from pathlib import Path
|
|
13
|
+
from typing import Any
|
|
12
14
|
|
|
13
15
|
import click
|
|
14
16
|
import cloup
|
|
15
17
|
|
|
16
|
-
from
|
|
17
|
-
from
|
|
18
|
-
from
|
|
18
|
+
from manim._config import console
|
|
19
|
+
from manim.constants import CONTEXT_SETTINGS, EPILOG, QUALITIES
|
|
20
|
+
from manim.utils.file_ops import (
|
|
19
21
|
add_import_statement,
|
|
20
22
|
copy_template_files,
|
|
21
23
|
get_template_names,
|
|
@@ -27,19 +29,21 @@ CFG_DEFAULTS = {
|
|
|
27
29
|
"background_color": "BLACK",
|
|
28
30
|
"background_opacity": 1,
|
|
29
31
|
"scene_names": "Default",
|
|
30
|
-
"resolution": (
|
|
32
|
+
"resolution": (1920, 1080),
|
|
31
33
|
}
|
|
32
34
|
|
|
35
|
+
__all__ = ["select_resolution", "update_cfg", "project", "scene"]
|
|
36
|
+
|
|
33
37
|
|
|
34
|
-
def select_resolution():
|
|
38
|
+
def select_resolution() -> tuple[int, int]:
|
|
35
39
|
"""Prompts input of type click.Choice from user. Presents options from QUALITIES constant.
|
|
36
40
|
|
|
37
41
|
Returns
|
|
38
42
|
-------
|
|
39
|
-
|
|
40
|
-
|
|
43
|
+
tuple[int, int]
|
|
44
|
+
Tuple containing height and width.
|
|
41
45
|
"""
|
|
42
|
-
resolution_options = []
|
|
46
|
+
resolution_options: list[tuple[int, int]] = []
|
|
43
47
|
for quality in QUALITIES.items():
|
|
44
48
|
resolution_options.append(
|
|
45
49
|
(quality[1]["pixel_height"], quality[1]["pixel_width"]),
|
|
@@ -47,22 +51,25 @@ def select_resolution():
|
|
|
47
51
|
resolution_options.pop()
|
|
48
52
|
choice = click.prompt(
|
|
49
53
|
"\nSelect resolution:\n",
|
|
50
|
-
type=
|
|
54
|
+
type=cloup.Choice([f"{i[0]}p" for i in resolution_options]),
|
|
51
55
|
show_default=False,
|
|
52
56
|
default="480p",
|
|
53
57
|
)
|
|
54
|
-
|
|
58
|
+
matches = [res for res in resolution_options if f"{res[0]}p" == choice]
|
|
59
|
+
return matches[0]
|
|
55
60
|
|
|
56
61
|
|
|
57
|
-
def update_cfg(cfg_dict: dict, project_cfg_path: Path):
|
|
58
|
-
"""
|
|
62
|
+
def update_cfg(cfg_dict: dict[str, Any], project_cfg_path: Path) -> None:
|
|
63
|
+
"""Update the ``manim.cfg`` file after reading it from the specified
|
|
64
|
+
``project_cfg_path``.
|
|
59
65
|
|
|
60
66
|
Parameters
|
|
61
67
|
----------
|
|
62
68
|
cfg_dict
|
|
63
|
-
|
|
69
|
+
Values used to update ``manim.cfg`` which is found in
|
|
70
|
+
``project_cfg_path``.
|
|
64
71
|
project_cfg_path
|
|
65
|
-
Path of manim.cfg file.
|
|
72
|
+
Path of the ``manim.cfg`` file.
|
|
66
73
|
"""
|
|
67
74
|
config = configparser.ConfigParser()
|
|
68
75
|
config.read(project_cfg_path)
|
|
@@ -82,7 +89,7 @@ def update_cfg(cfg_dict: dict, project_cfg_path: Path):
|
|
|
82
89
|
context_settings=CONTEXT_SETTINGS,
|
|
83
90
|
epilog=EPILOG,
|
|
84
91
|
)
|
|
85
|
-
@cloup.argument("project_name", type=Path, required=False)
|
|
92
|
+
@cloup.argument("project_name", type=cloup.Path(path_type=Path), required=False)
|
|
86
93
|
@cloup.option(
|
|
87
94
|
"-d",
|
|
88
95
|
"--default",
|
|
@@ -91,13 +98,14 @@ def update_cfg(cfg_dict: dict, project_cfg_path: Path):
|
|
|
91
98
|
help="Default settings for project creation.",
|
|
92
99
|
nargs=1,
|
|
93
100
|
)
|
|
94
|
-
def project(default_settings, **
|
|
101
|
+
def project(default_settings: bool, **kwargs: Any) -> None:
|
|
95
102
|
"""Creates a new project.
|
|
96
103
|
|
|
97
104
|
PROJECT_NAME is the name of the folder in which the new project will be initialized.
|
|
98
105
|
"""
|
|
99
|
-
|
|
100
|
-
|
|
106
|
+
project_name: Path
|
|
107
|
+
if kwargs["project_name"]:
|
|
108
|
+
project_name = kwargs["project_name"]
|
|
101
109
|
else:
|
|
102
110
|
project_name = click.prompt("Project Name", type=Path)
|
|
103
111
|
|
|
@@ -114,7 +122,7 @@ def project(default_settings, **args):
|
|
|
114
122
|
)
|
|
115
123
|
else:
|
|
116
124
|
project_name.mkdir()
|
|
117
|
-
new_cfg = {}
|
|
125
|
+
new_cfg: dict[str, Any] = {}
|
|
118
126
|
new_cfg_path = Path.resolve(project_name / "manim.cfg")
|
|
119
127
|
|
|
120
128
|
if not default_settings:
|
|
@@ -142,23 +150,23 @@ def project(default_settings, **args):
|
|
|
142
150
|
)
|
|
143
151
|
@cloup.argument("scene_name", type=str, required=True)
|
|
144
152
|
@cloup.argument("file_name", type=str, required=False)
|
|
145
|
-
def scene(**
|
|
153
|
+
def scene(**kwargs: Any) -> None:
|
|
146
154
|
"""Inserts a SCENE to an existing FILE or creates a new FILE.
|
|
147
155
|
|
|
148
156
|
SCENE is the name of the scene that will be inserted.
|
|
149
157
|
|
|
150
158
|
FILE is the name of file in which the SCENE will be inserted.
|
|
151
159
|
"""
|
|
152
|
-
template_name = click.prompt(
|
|
160
|
+
template_name: str = click.prompt(
|
|
153
161
|
"template",
|
|
154
162
|
type=click.Choice(get_template_names(), False),
|
|
155
163
|
default="Default",
|
|
156
164
|
)
|
|
157
165
|
scene = (get_template_path() / f"{template_name}.mtp").resolve().read_text()
|
|
158
|
-
scene = scene.replace(template_name + "Template",
|
|
166
|
+
scene = scene.replace(template_name + "Template", kwargs["scene_name"], 1)
|
|
159
167
|
|
|
160
|
-
if
|
|
161
|
-
file_name = Path(
|
|
168
|
+
if kwargs["file_name"]:
|
|
169
|
+
file_name = Path(kwargs["file_name"])
|
|
162
170
|
|
|
163
171
|
if file_name.suffix != ".py":
|
|
164
172
|
file_name = file_name.with_suffix(file_name.suffix + ".py")
|
|
@@ -187,7 +195,7 @@ def scene(**args):
|
|
|
187
195
|
help="Create a new project or insert a new scene.",
|
|
188
196
|
)
|
|
189
197
|
@cloup.pass_context
|
|
190
|
-
def init(ctx):
|
|
198
|
+
def init(ctx: cloup.Context) -> None:
|
|
191
199
|
pass
|
|
192
200
|
|
|
193
201
|
|