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.
Files changed (163) hide show
  1. manim/__init__.py +11 -6
  2. manim/__main__.py +62 -19
  3. manim/_config/__init__.py +10 -9
  4. manim/_config/cli_colors.py +26 -9
  5. manim/_config/default.cfg +1 -3
  6. manim/_config/logger_utils.py +23 -13
  7. manim/_config/utils.py +662 -468
  8. manim/animation/animation.py +164 -18
  9. manim/animation/changing.py +34 -23
  10. manim/animation/composition.py +265 -67
  11. manim/animation/creation.py +208 -26
  12. manim/animation/fading.py +16 -18
  13. manim/animation/growing.py +35 -15
  14. manim/animation/indication.py +150 -76
  15. manim/animation/movement.py +56 -22
  16. manim/animation/numbers.py +64 -6
  17. manim/animation/rotation.py +78 -7
  18. manim/animation/specialized.py +6 -7
  19. manim/animation/speedmodifier.py +13 -10
  20. manim/animation/transform.py +14 -11
  21. manim/animation/transform_matching_parts.py +3 -4
  22. manim/animation/updaters/mobject_update_utils.py +152 -30
  23. manim/animation/updaters/update.py +10 -7
  24. manim/camera/camera.py +182 -118
  25. manim/camera/mapping_camera.py +34 -3
  26. manim/camera/moving_camera.py +95 -74
  27. manim/camera/multi_camera.py +23 -15
  28. manim/camera/three_d_camera.py +70 -52
  29. manim/cli/__init__.py +17 -0
  30. manim/cli/cfg/group.py +76 -44
  31. manim/cli/checkhealth/checks.py +192 -0
  32. manim/cli/checkhealth/commands.py +90 -0
  33. manim/cli/default_group.py +158 -25
  34. manim/cli/init/commands.py +33 -25
  35. manim/cli/plugins/commands.py +16 -3
  36. manim/cli/render/commands.py +72 -60
  37. manim/cli/render/ease_of_access_options.py +4 -3
  38. manim/cli/render/global_options.py +59 -17
  39. manim/cli/render/output_options.py +6 -5
  40. manim/cli/render/render_options.py +98 -33
  41. manim/constants.py +109 -59
  42. manim/data_structures.py +31 -0
  43. manim/mobject/frame.py +8 -5
  44. manim/mobject/geometry/__init__.py +1 -0
  45. manim/mobject/geometry/arc.py +277 -135
  46. manim/mobject/geometry/boolean_ops.py +32 -31
  47. manim/mobject/geometry/labeled.py +376 -0
  48. manim/mobject/geometry/line.py +192 -87
  49. manim/mobject/geometry/polygram.py +224 -58
  50. manim/mobject/geometry/shape_matchers.py +61 -25
  51. manim/mobject/geometry/tips.py +122 -48
  52. manim/mobject/graph.py +1027 -419
  53. manim/mobject/graphing/coordinate_systems.py +533 -278
  54. manim/mobject/graphing/functions.py +53 -32
  55. manim/mobject/graphing/number_line.py +123 -65
  56. manim/mobject/graphing/probability.py +88 -62
  57. manim/mobject/graphing/scale.py +33 -19
  58. manim/mobject/logo.py +118 -28
  59. manim/mobject/matrix.py +87 -83
  60. manim/mobject/mobject.py +912 -442
  61. manim/mobject/opengl/dot_cloud.py +16 -5
  62. manim/mobject/opengl/opengl_compatibility.py +4 -2
  63. manim/mobject/opengl/opengl_geometry.py +254 -153
  64. manim/mobject/opengl/opengl_image_mobject.py +3 -1
  65. manim/mobject/opengl/opengl_mobject.py +779 -482
  66. manim/mobject/opengl/opengl_point_cloud_mobject.py +41 -14
  67. manim/mobject/opengl/opengl_surface.py +14 -92
  68. manim/mobject/opengl/opengl_three_dimensions.py +12 -8
  69. manim/mobject/opengl/opengl_vectorized_mobject.py +98 -100
  70. manim/mobject/svg/brace.py +173 -41
  71. manim/mobject/svg/svg_mobject.py +139 -53
  72. manim/mobject/table.py +61 -68
  73. manim/mobject/text/code_mobject.py +193 -539
  74. manim/mobject/text/numbers.py +81 -34
  75. manim/mobject/text/tex_mobject.py +130 -78
  76. manim/mobject/text/text_mobject.py +288 -164
  77. manim/mobject/three_d/polyhedra.py +111 -13
  78. manim/mobject/three_d/three_d_utils.py +17 -8
  79. manim/mobject/three_d/three_dimensions.py +239 -106
  80. manim/mobject/types/image_mobject.py +50 -30
  81. manim/mobject/types/point_cloud_mobject.py +120 -75
  82. manim/mobject/types/vectorized_mobject.py +841 -408
  83. manim/mobject/value_tracker.py +105 -38
  84. manim/mobject/vector_field.py +50 -31
  85. manim/opengl/__init__.py +3 -3
  86. manim/plugins/__init__.py +14 -1
  87. manim/plugins/plugins_flags.py +10 -14
  88. manim/renderer/cairo_renderer.py +65 -50
  89. manim/renderer/opengl_renderer.py +89 -69
  90. manim/renderer/opengl_renderer_window.py +39 -18
  91. manim/renderer/shader.py +123 -87
  92. manim/renderer/shader_wrapper.py +44 -28
  93. manim/renderer/vectorized_mobject_rendering.py +38 -10
  94. manim/scene/moving_camera_scene.py +32 -3
  95. manim/scene/scene.py +507 -242
  96. manim/scene/scene_file_writer.py +371 -220
  97. manim/scene/section.py +20 -16
  98. manim/scene/three_d_scene.py +14 -22
  99. manim/scene/vector_space_scene.py +223 -129
  100. manim/scene/zoomed_scene.py +46 -41
  101. manim/typing.py +990 -0
  102. manim/utils/bezier.py +1823 -371
  103. manim/utils/caching.py +12 -5
  104. manim/utils/color/AS2700.py +236 -0
  105. manim/utils/color/BS381.py +318 -0
  106. manim/utils/color/DVIPSNAMES.py +96 -0
  107. manim/utils/color/SVGNAMES.py +179 -0
  108. manim/utils/color/X11.py +533 -0
  109. manim/utils/color/XKCD.py +952 -0
  110. manim/utils/color/__init__.py +61 -0
  111. manim/utils/color/core.py +1667 -0
  112. manim/utils/color/manim_colors.py +218 -0
  113. manim/utils/commands.py +48 -20
  114. manim/utils/config_ops.py +39 -19
  115. manim/utils/debug.py +8 -7
  116. manim/utils/deprecation.py +86 -39
  117. manim/utils/docbuild/__init__.py +17 -0
  118. manim/utils/docbuild/autoaliasattr_directive.py +236 -0
  119. manim/utils/docbuild/autocolor_directive.py +99 -0
  120. manim/utils/docbuild/manim_directive.py +94 -41
  121. manim/utils/docbuild/module_parsing.py +245 -0
  122. manim/utils/exceptions.py +6 -0
  123. manim/utils/family.py +5 -3
  124. manim/utils/family_ops.py +17 -4
  125. manim/utils/file_ops.py +27 -17
  126. manim/utils/hashing.py +55 -45
  127. manim/utils/images.py +13 -7
  128. manim/utils/ipython_magic.py +13 -7
  129. manim/utils/iterables.py +163 -120
  130. manim/utils/module_ops.py +66 -24
  131. manim/utils/opengl.py +77 -24
  132. manim/utils/parameter_parsing.py +32 -0
  133. manim/utils/paths.py +30 -33
  134. manim/utils/polylabel.py +235 -0
  135. manim/utils/qhull.py +218 -0
  136. manim/utils/rate_functions.py +98 -32
  137. manim/utils/simple_functions.py +25 -33
  138. manim/utils/sounds.py +7 -1
  139. manim/utils/space_ops.py +188 -115
  140. manim/utils/testing/__init__.py +17 -0
  141. manim/utils/testing/_frames_testers.py +13 -8
  142. manim/utils/testing/_show_diff.py +5 -3
  143. manim/utils/testing/_test_class_makers.py +34 -18
  144. manim/utils/testing/frames_comparison.py +37 -19
  145. manim/utils/tex.py +130 -198
  146. manim/utils/tex_file_writing.py +77 -47
  147. manim/utils/tex_templates.py +2 -1
  148. manim/utils/unit.py +6 -5
  149. {manim-0.17.0.dist-info → manim-0.19.1.dist-info}/METADATA +64 -65
  150. manim-0.19.1.dist-info/RECORD +220 -0
  151. {manim-0.17.0.dist-info → manim-0.19.1.dist-info}/WHEEL +1 -1
  152. manim-0.19.1.dist-info/entry_points.txt +3 -0
  153. {manim-0.17.0.dist-info → manim-0.19.1.dist-info/licenses}/LICENSE.community +1 -1
  154. manim/cli/new/group.py +0 -189
  155. manim/communitycolors.py +0 -9
  156. manim/gui/__init__.py +0 -0
  157. manim/gui/gui.py +0 -82
  158. manim/plugins/import_plugins.py +0 -43
  159. manim/utils/color.py +0 -552
  160. manim-0.17.0.dist-info/RECORD +0 -206
  161. manim-0.17.0.dist-info/entry_points.txt +0 -4
  162. /manim/cli/{new → checkhealth}/__init__.py +0 -0
  163. {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.")
@@ -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 .. import logger
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
- if not args and self.default_if_no_args:
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
- return super().parse_args(ctx, args)
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
- def get_command(self, ctx, cmd_name):
38
- if cmd_name not in self.commands:
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(self, ctx, args):
45
- base = super()
46
- cmd_name, cmd, args = base.resolve_command(ctx, args)
47
- if hasattr(ctx, "arg0"):
48
- args.insert(0, ctx.arg0)
49
- cmd_name = cmd.name
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
- def command(self, *args, **kwargs):
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(*args, **kwargs)
184
+ decorator: Callable[[Callable[..., object]], Command] = super().command(
185
+ *args, **kwargs
186
+ )
55
187
  if not default:
56
188
  return decorator
57
- logger.log(
58
- "Use default param of DefaultGroup or " "set_default_command() instead",
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
@@ -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 ... import console
17
- from ...constants import CONTEXT_SETTINGS, EPILOG, QUALITIES
18
- from ...utils.file_ops import (
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": (854, 480),
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
- :class:`tuple`
40
- Tuple containing height and width.
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=click.Choice([f"{i[0]}p" for i in resolution_options]),
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
- return [res for res in resolution_options if f"{res[0]}p" == choice][0]
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
- """Updates the manim.cfg file after reading it from the project_cfg_path.
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
- values used to update manim.cfg found project_cfg_path.
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, **args):
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
- if args["project_name"]:
100
- project_name = args["project_name"]
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(**args):
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", args["scene_name"], 1)
166
+ scene = scene.replace(template_name + "Template", kwargs["scene_name"], 1)
159
167
 
160
- if args["file_name"]:
161
- file_name = Path(args["file_name"])
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