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.

Files changed (146) hide show
  1. manim/__init__.py +3 -6
  2. manim/__main__.py +61 -20
  3. manim/_config/__init__.py +6 -3
  4. manim/_config/cli_colors.py +16 -8
  5. manim/_config/default.cfg +1 -3
  6. manim/_config/logger_utils.py +14 -8
  7. manim/_config/utils.py +651 -472
  8. manim/animation/animation.py +152 -5
  9. manim/animation/composition.py +80 -39
  10. manim/animation/creation.py +196 -14
  11. manim/animation/fading.py +5 -9
  12. manim/animation/indication.py +103 -47
  13. manim/animation/movement.py +22 -5
  14. manim/animation/rotation.py +3 -2
  15. manim/animation/specialized.py +4 -6
  16. manim/animation/speedmodifier.py +10 -5
  17. manim/animation/transform.py +4 -5
  18. manim/animation/transform_matching_parts.py +1 -1
  19. manim/animation/updaters/mobject_update_utils.py +17 -14
  20. manim/camera/camera.py +15 -6
  21. manim/cli/__init__.py +17 -0
  22. manim/cli/cfg/group.py +70 -44
  23. manim/cli/checkhealth/checks.py +93 -75
  24. manim/cli/checkhealth/commands.py +14 -5
  25. manim/cli/default_group.py +157 -25
  26. manim/cli/init/commands.py +32 -24
  27. manim/cli/plugins/commands.py +16 -3
  28. manim/cli/render/commands.py +72 -60
  29. manim/cli/render/ease_of_access_options.py +4 -3
  30. manim/cli/render/global_options.py +51 -15
  31. manim/cli/render/output_options.py +6 -5
  32. manim/cli/render/render_options.py +97 -32
  33. manim/constants.py +65 -19
  34. manim/gui/gui.py +2 -0
  35. manim/mobject/frame.py +0 -1
  36. manim/mobject/geometry/arc.py +112 -78
  37. manim/mobject/geometry/boolean_ops.py +32 -25
  38. manim/mobject/geometry/labeled.py +300 -77
  39. manim/mobject/geometry/line.py +132 -64
  40. manim/mobject/geometry/polygram.py +126 -30
  41. manim/mobject/geometry/shape_matchers.py +35 -15
  42. manim/mobject/geometry/tips.py +38 -29
  43. manim/mobject/graph.py +414 -133
  44. manim/mobject/graphing/coordinate_systems.py +126 -64
  45. manim/mobject/graphing/functions.py +25 -15
  46. manim/mobject/graphing/number_line.py +24 -10
  47. manim/mobject/graphing/probability.py +2 -10
  48. manim/mobject/graphing/scale.py +6 -5
  49. manim/mobject/matrix.py +17 -19
  50. manim/mobject/mobject.py +314 -165
  51. manim/mobject/opengl/opengl_compatibility.py +2 -0
  52. manim/mobject/opengl/opengl_geometry.py +30 -9
  53. manim/mobject/opengl/opengl_image_mobject.py +2 -0
  54. manim/mobject/opengl/opengl_mobject.py +509 -343
  55. manim/mobject/opengl/opengl_point_cloud_mobject.py +5 -7
  56. manim/mobject/opengl/opengl_surface.py +3 -2
  57. manim/mobject/opengl/opengl_three_dimensions.py +2 -0
  58. manim/mobject/opengl/opengl_vectorized_mobject.py +46 -79
  59. manim/mobject/svg/brace.py +63 -13
  60. manim/mobject/svg/svg_mobject.py +4 -3
  61. manim/mobject/table.py +11 -13
  62. manim/mobject/text/code_mobject.py +186 -548
  63. manim/mobject/text/numbers.py +9 -7
  64. manim/mobject/text/tex_mobject.py +23 -14
  65. manim/mobject/text/text_mobject.py +70 -24
  66. manim/mobject/three_d/polyhedra.py +98 -1
  67. manim/mobject/three_d/three_d_utils.py +4 -4
  68. manim/mobject/three_d/three_dimensions.py +62 -34
  69. manim/mobject/types/image_mobject.py +42 -24
  70. manim/mobject/types/point_cloud_mobject.py +105 -67
  71. manim/mobject/types/vectorized_mobject.py +496 -228
  72. manim/mobject/value_tracker.py +5 -4
  73. manim/mobject/vector_field.py +5 -5
  74. manim/opengl/__init__.py +3 -3
  75. manim/plugins/__init__.py +14 -1
  76. manim/plugins/plugins_flags.py +14 -8
  77. manim/renderer/cairo_renderer.py +20 -10
  78. manim/renderer/opengl_renderer.py +21 -23
  79. manim/renderer/opengl_renderer_window.py +2 -0
  80. manim/renderer/shader.py +2 -3
  81. manim/renderer/shader_wrapper.py +5 -2
  82. manim/renderer/vectorized_mobject_rendering.py +5 -0
  83. manim/scene/moving_camera_scene.py +23 -0
  84. manim/scene/scene.py +90 -43
  85. manim/scene/scene_file_writer.py +316 -165
  86. manim/scene/section.py +17 -15
  87. manim/scene/three_d_scene.py +13 -21
  88. manim/scene/vector_space_scene.py +22 -9
  89. manim/typing.py +830 -70
  90. manim/utils/bezier.py +1667 -399
  91. manim/utils/caching.py +13 -5
  92. manim/utils/color/AS2700.py +2 -0
  93. manim/utils/color/BS381.py +3 -0
  94. manim/utils/color/DVIPSNAMES.py +96 -0
  95. manim/utils/color/SVGNAMES.py +179 -0
  96. manim/utils/color/X11.py +3 -0
  97. manim/utils/color/XKCD.py +3 -0
  98. manim/utils/color/__init__.py +8 -5
  99. manim/utils/color/core.py +844 -309
  100. manim/utils/color/manim_colors.py +7 -9
  101. manim/utils/commands.py +48 -20
  102. manim/utils/config_ops.py +18 -13
  103. manim/utils/debug.py +8 -7
  104. manim/utils/deprecation.py +90 -40
  105. manim/utils/docbuild/__init__.py +17 -0
  106. manim/utils/docbuild/autoaliasattr_directive.py +234 -0
  107. manim/utils/docbuild/autocolor_directive.py +21 -17
  108. manim/utils/docbuild/manim_directive.py +50 -35
  109. manim/utils/docbuild/module_parsing.py +245 -0
  110. manim/utils/exceptions.py +6 -0
  111. manim/utils/family.py +5 -3
  112. manim/utils/family_ops.py +17 -4
  113. manim/utils/file_ops.py +26 -16
  114. manim/utils/hashing.py +9 -7
  115. manim/utils/images.py +10 -4
  116. manim/utils/ipython_magic.py +14 -8
  117. manim/utils/iterables.py +161 -119
  118. manim/utils/module_ops.py +57 -19
  119. manim/utils/opengl.py +83 -24
  120. manim/utils/parameter_parsing.py +32 -0
  121. manim/utils/paths.py +21 -23
  122. manim/utils/polylabel.py +168 -0
  123. manim/utils/qhull.py +218 -0
  124. manim/utils/rate_functions.py +74 -39
  125. manim/utils/simple_functions.py +24 -15
  126. manim/utils/sounds.py +7 -1
  127. manim/utils/space_ops.py +125 -69
  128. manim/utils/testing/__init__.py +17 -0
  129. manim/utils/testing/_frames_testers.py +13 -8
  130. manim/utils/testing/_show_diff.py +5 -3
  131. manim/utils/testing/_test_class_makers.py +33 -18
  132. manim/utils/testing/frames_comparison.py +27 -19
  133. manim/utils/tex.py +127 -197
  134. manim/utils/tex_file_writing.py +47 -45
  135. manim/utils/tex_templates.py +2 -1
  136. manim/utils/unit.py +6 -5
  137. {manim-0.18.0.post0.dist-info → manim-0.19.0.dist-info}/LICENSE.community +1 -1
  138. {manim-0.18.0.post0.dist-info → manim-0.19.0.dist-info}/METADATA +40 -39
  139. manim-0.19.0.dist-info/RECORD +221 -0
  140. {manim-0.18.0.post0.dist-info → manim-0.19.0.dist-info}/WHEEL +1 -1
  141. manim/cli/new/__init__.py +0 -0
  142. manim/cli/new/group.py +0 -189
  143. manim/plugins/import_plugins.py +0 -43
  144. manim-0.18.0.post0.dist-info/RECORD +0 -217
  145. {manim-0.18.0.post0.dist-info → manim-0.19.0.dist-info}/LICENSE +0 -0
  146. {manim-0.18.0.post0.dist-info → manim-0.19.0.dist-info}/entry_points.txt +0 -0
@@ -1,65 +1,197 @@
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 typing import TYPE_CHECKING, Any, Callable
17
+
5
18
  import cloup
6
19
 
7
- from .. import logger
20
+ from manim.utils.deprecation import deprecated
8
21
 
9
22
  __all__ = ["DefaultGroup"]
10
23
 
24
+ if TYPE_CHECKING:
25
+ from click import Command, Context
26
+
11
27
 
12
28
  class DefaultGroup(cloup.Group):
13
- """Invokes a subcommand marked with ``default=True`` if any subcommand not
29
+ """Invokes a subcommand marked with ``default=True`` if any subcommand is not
14
30
  chosen.
31
+
32
+ Parameters
33
+ ----------
34
+ *args
35
+ Positional arguments to forward to :class:`cloup.Group`.
36
+ **kwargs
37
+ Keyword arguments to forward to :class:`cloup.Group`. The keyword
38
+ ``ignore_unknown_options`` must be set to ``False``.
39
+
40
+ Attributes
41
+ ----------
42
+ default_cmd_name : str | None
43
+ The name of the default command, if specified through the ``default``
44
+ keyword argument. Otherwise, this is set to ``None``.
45
+ default_if_no_args : bool
46
+ Whether to include or not the default command, if no command arguments
47
+ are supplied. This can be specified through the ``default_if_no_args``
48
+ keyword argument. Default is ``False``.
15
49
  """
16
50
 
17
- def __init__(self, *args, **kwargs):
51
+ def __init__(self, *args: Any, **kwargs: Any):
18
52
  # To resolve as the default command.
19
53
  if not kwargs.get("ignore_unknown_options", True):
20
54
  raise ValueError("Default group accepts unknown options")
21
55
  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)
56
+ self.default_cmd_name: str | None = kwargs.pop("default", None)
57
+ self.default_if_no_args: bool = kwargs.pop("default_if_no_args", False)
24
58
  super().__init__(*args, **kwargs)
25
59
 
26
- def set_default_command(self, command):
27
- """Sets a command function as the default command."""
60
+ def set_default_command(self, command: Command) -> None:
61
+ """Sets a command function as the default command.
62
+
63
+ Parameters
64
+ ----------
65
+ command
66
+ The command to set as default.
67
+ """
28
68
  cmd_name = command.name
29
69
  self.add_command(command)
30
70
  self.default_cmd_name = cmd_name
31
71
 
32
- def parse_args(self, ctx, args):
33
- if not args and self.default_if_no_args:
72
+ def parse_args(self, ctx: Context, args: list[str]) -> list[str]:
73
+ """Parses the list of ``args`` by forwarding it to
74
+ :meth:`cloup.Group.parse_args`. Before doing so, if
75
+ :attr:`default_if_no_args` is set to ``True`` and ``args`` is empty,
76
+ this function appends to it the name of the default command specified
77
+ by :attr:`default_cmd_name`.
78
+
79
+ Parameters
80
+ ----------
81
+ ctx
82
+ The Click context.
83
+ args
84
+ A list of arguments. If it's empty and :attr:`default_if_no_args`
85
+ is ``True``, append the name of the default command to it.
86
+
87
+ Returns
88
+ -------
89
+ list[str]
90
+ The parsed arguments.
91
+ """
92
+ if not args and self.default_if_no_args and self.default_cmd_name:
34
93
  args.insert(0, self.default_cmd_name)
35
- return super().parse_args(ctx, args)
94
+ parsed_args: list[str] = super().parse_args(ctx, args)
95
+ return parsed_args
96
+
97
+ def get_command(self, ctx: Context, cmd_name: str) -> Command | None:
98
+ """Get a command function by its name, by forwarding the arguments to
99
+ :meth:`cloup.Group.get_command`. If ``cmd_name`` does not match any of
100
+ the command names in :attr:`commands`, attempt to get the default command
101
+ instead.
102
+
103
+ Parameters
104
+ ----------
105
+ ctx
106
+ The Click context.
107
+ cmd_name
108
+ The name of the command to get.
36
109
 
37
- def get_command(self, ctx, cmd_name):
38
- if cmd_name not in self.commands:
110
+ Returns
111
+ -------
112
+ :class:`click.Command` | None
113
+ The command, if found. Otherwise, ``None``.
114
+ """
115
+ if cmd_name not in self.commands and self.default_cmd_name:
39
116
  # No command name matched.
40
- ctx.arg0 = cmd_name
117
+ ctx.meta["arg0"] = cmd_name
41
118
  cmd_name = self.default_cmd_name
42
119
  return super().get_command(ctx, cmd_name)
43
120
 
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
121
+ def resolve_command(
122
+ self, ctx: Context, args: list[str]
123
+ ) -> tuple[str | None, Command | None, list[str]]:
124
+ """Given a list of ``args`` given by a CLI, find a command which
125
+ matches the first element, and return its name (``cmd_name``), the
126
+ command function itself (``cmd``) and the rest of the arguments which
127
+ shall be passed to the function (``cmd_args``). If not found, return
128
+ ``None``, ``None`` and the rest of the arguments.
129
+
130
+ After resolving the command, if the Click context given by ``ctx``
131
+ contains an ``arg0`` attribute in its :attr:`click.Context.meta`
132
+ dictionary, insert it as the first element of the returned
133
+ ``cmd_args``.
134
+
135
+ Parameters
136
+ ----------
137
+ ctx
138
+ The Click context.
139
+ cmd_name
140
+ The name of the command to get.
141
+
142
+ Returns
143
+ -------
144
+ cmd_name : str | None
145
+ The command name, if found. Otherwise, ``None``.
146
+ cmd : :class:`click.Command` | None
147
+ The command, if found. Otherwise, ``None``.
148
+ cmd_args : list[str]
149
+ The rest of the arguments to be passed to ``cmd``.
150
+ """
151
+ cmd_name, cmd, args = super().resolve_command(ctx, args)
152
+ if "arg0" in ctx.meta:
153
+ args.insert(0, ctx.meta["arg0"])
154
+ if cmd is not None:
155
+ cmd_name = cmd.name
50
156
  return cmd_name, cmd, args
51
157
 
52
- def command(self, *args, **kwargs):
158
+ @deprecated
159
+ def command(
160
+ self, *args: Any, **kwargs: Any
161
+ ) -> Callable[[Callable[..., object]], Command]:
162
+ """Return a decorator which converts any function into the default
163
+ subcommand for this :class:`DefaultGroup`.
164
+
165
+ .. warning::
166
+ This method is deprecated. Use the ``default`` parameter of
167
+ :class:`DefaultGroup` or :meth:`set_default_command` instead.
168
+
169
+ Parameters
170
+ ----------
171
+ *args
172
+ Positional arguments to pass to :meth:`cloup.Group.command`.
173
+ **kwargs
174
+ Keyword arguments to pass to :meth:`cloup.Group.command`.
175
+
176
+ Returns
177
+ -------
178
+ Callable[[Callable[..., object]], click.Command]
179
+ A decorator which transforms its input into this
180
+ :class:`DefaultGroup`'s default subcommand.
181
+ """
53
182
  default = kwargs.pop("default", False)
54
- decorator = super().command(*args, **kwargs)
183
+ decorator: Callable[[Callable[..., object]], Command] = super().command(
184
+ *args, **kwargs
185
+ )
55
186
  if not default:
56
187
  return decorator
57
- logger.log(
58
- "Use default param of DefaultGroup or " "set_default_command() instead",
188
+ warnings.warn(
189
+ "Use default param of DefaultGroup or set_default_command() instead",
59
190
  DeprecationWarning,
191
+ stacklevel=1,
60
192
  )
61
193
 
62
- def _decorator(f):
194
+ def _decorator(f: Callable) -> Command:
63
195
  cmd = decorator(f)
64
196
  self.set_default_command(cmd)
65
197
  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,
@@ -30,16 +32,18 @@ CFG_DEFAULTS = {
30
32
  "resolution": (854, 480),
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
 
@@ -5,12 +5,15 @@ plugin``. Here you can specify options, subcommands, and subgroups for the plugi
5
5
  group.
6
6
 
7
7
  """
8
+
8
9
  from __future__ import annotations
9
10
 
10
11
  import cloup
11
12
 
12
- from ...constants import CONTEXT_SETTINGS, EPILOG
13
- from ...plugins.plugins_flags import list_plugins
13
+ from manim.constants import CONTEXT_SETTINGS, EPILOG
14
+ from manim.plugins.plugins_flags import list_plugins
15
+
16
+ __all__ = ["plugins"]
14
17
 
15
18
 
16
19
  @cloup.command(
@@ -26,6 +29,16 @@ from ...plugins.plugins_flags import list_plugins
26
29
  is_flag=True,
27
30
  help="List available plugins.",
28
31
  )
29
- def plugins(list_available):
32
+ def plugins(list_available: bool) -> None:
33
+ """Print a list of all available plugins when calling ``manim plugins -l``
34
+ or ``manim plugins --list``.
35
+
36
+ Parameters
37
+ ----------
38
+ list_available
39
+ If the ``-l`` or ``-list`` option is passed to ``manim plugins``, this
40
+ parameter will be set to ``True``, which will print a list of all
41
+ available plugins.
42
+ """
30
43
  if list_available:
31
44
  list_plugins()
@@ -5,24 +5,56 @@ Manim's render subcommand is accessed in the command-line interface via
5
5
  can specify options, and arguments for the render command.
6
6
 
7
7
  """
8
+
8
9
  from __future__ import annotations
9
10
 
11
+ import http.client
10
12
  import json
11
13
  import sys
14
+ import urllib.error
15
+ import urllib.request
16
+ from argparse import Namespace
12
17
  from pathlib import Path
18
+ from typing import Any, cast
13
19
 
14
- import click
15
20
  import cloup
16
- import requests
17
21
 
18
- from ... import __version__, config, console, error_console, logger
19
- from ..._config import tempconfig
20
- from ...constants import EPILOG, RendererType
21
- from ...utils.module_ops import scene_classes_from_file
22
- from .ease_of_access_options import ease_of_access_options
23
- from .global_options import global_options
24
- from .output_options import output_options
25
- from .render_options import render_options
22
+ from manim import __version__
23
+ from manim._config import (
24
+ config,
25
+ console,
26
+ error_console,
27
+ logger,
28
+ tempconfig,
29
+ )
30
+ from manim.cli.render.ease_of_access_options import ease_of_access_options
31
+ from manim.cli.render.global_options import global_options
32
+ from manim.cli.render.output_options import output_options
33
+ from manim.cli.render.render_options import render_options
34
+ from manim.constants import EPILOG, RendererType
35
+ from manim.utils.module_ops import scene_classes_from_file
36
+
37
+ __all__ = ["render"]
38
+
39
+
40
+ class ClickArgs(Namespace):
41
+ def __init__(self, args: dict[str, Any]) -> None:
42
+ for name in args:
43
+ setattr(self, name, args[name])
44
+
45
+ def _get_kwargs(self) -> list[tuple[str, Any]]:
46
+ return list(self.__dict__.items())
47
+
48
+ def __eq__(self, other: object) -> bool:
49
+ if not isinstance(other, ClickArgs):
50
+ return NotImplemented
51
+ return vars(self) == vars(other)
52
+
53
+ def __contains__(self, key: str) -> bool:
54
+ return key in self.__dict__
55
+
56
+ def __repr__(self) -> str:
57
+ return str(self.__dict__)
26
58
 
27
59
 
28
60
  @cloup.command(
@@ -30,56 +62,34 @@ from .render_options import render_options
30
62
  no_args_is_help=True,
31
63
  epilog=EPILOG,
32
64
  )
33
- @click.argument("file", type=Path, required=True)
34
- @click.argument("scene_names", required=False, nargs=-1)
65
+ @cloup.argument("file", type=cloup.Path(path_type=Path), required=True)
66
+ @cloup.argument("scene_names", required=False, nargs=-1)
35
67
  @global_options
36
68
  @output_options
37
- @render_options # type: ignore
69
+ @render_options
38
70
  @ease_of_access_options
39
- def render(
40
- **args,
41
- ):
71
+ def render(**kwargs: Any) -> ClickArgs | dict[str, Any]:
42
72
  """Render SCENE(S) from the input FILE.
43
73
 
44
74
  FILE is the file path of the script or a config file.
45
75
 
46
76
  SCENES is an optional list of scenes in the file.
47
77
  """
48
-
49
- if args["save_as_gif"]:
78
+ if kwargs["save_as_gif"]:
50
79
  logger.warning("--save_as_gif is deprecated, please use --format=gif instead!")
51
- args["format"] = "gif"
80
+ kwargs["format"] = "gif"
52
81
 
53
- if args["save_pngs"]:
82
+ if kwargs["save_pngs"]:
54
83
  logger.warning("--save_pngs is deprecated, please use --format=png instead!")
55
- args["format"] = "png"
84
+ kwargs["format"] = "png"
56
85
 
57
- if args["show_in_file_browser"]:
86
+ if kwargs["show_in_file_browser"]:
58
87
  logger.warning(
59
88
  "The short form of show_in_file_browser is deprecated and will be moved to support --format.",
60
89
  )
61
90
 
62
- class ClickArgs:
63
- def __init__(self, args):
64
- for name in args:
65
- setattr(self, name, args[name])
66
-
67
- def _get_kwargs(self):
68
- return list(self.__dict__.items())
69
-
70
- def __eq__(self, other):
71
- if not isinstance(other, ClickArgs):
72
- return NotImplemented
73
- return vars(self) == vars(other)
74
-
75
- def __contains__(self, key):
76
- return key in self.__dict__
77
-
78
- def __repr__(self):
79
- return str(self.__dict__)
80
-
81
- click_args = ClickArgs(args)
82
- if args["jupyter"]:
91
+ click_args = ClickArgs(kwargs)
92
+ if kwargs["jupyter"]:
83
93
  return click_args
84
94
 
85
95
  config.digest_args(click_args)
@@ -120,13 +130,26 @@ def render(
120
130
  if config.notify_outdated_version:
121
131
  manim_info_url = "https://pypi.org/pypi/manim/json"
122
132
  warn_prompt = "Cannot check if latest release of manim is installed"
123
- req_info = {}
124
133
 
125
134
  try:
126
- req_info = requests.get(manim_info_url, timeout=10)
127
- req_info.raise_for_status()
128
-
129
- stable = req_info.json()["info"]["version"]
135
+ with urllib.request.urlopen(
136
+ urllib.request.Request(manim_info_url),
137
+ timeout=10,
138
+ ) as response:
139
+ response = cast(http.client.HTTPResponse, response)
140
+ json_data = json.loads(response.read())
141
+ except urllib.error.HTTPError:
142
+ logger.debug("HTTP Error: %s", warn_prompt)
143
+ except urllib.error.URLError:
144
+ logger.debug("URL Error: %s", warn_prompt)
145
+ except json.JSONDecodeError:
146
+ logger.debug(
147
+ "Error while decoding JSON from %r: %s", manim_info_url, warn_prompt
148
+ )
149
+ except Exception:
150
+ logger.debug("Something went wrong: %s", warn_prompt)
151
+ else:
152
+ stable = json_data["info"]["version"]
130
153
  if stable != __version__:
131
154
  console.print(
132
155
  f"You are using manim version [red]v{__version__}[/red], but version [green]v{stable}[/green] is available.",
@@ -134,16 +157,5 @@ def render(
134
157
  console.print(
135
158
  "You should consider upgrading via [yellow]pip install -U manim[/yellow]",
136
159
  )
137
- except requests.exceptions.HTTPError:
138
- logger.debug(f"HTTP Error: {warn_prompt}")
139
- except requests.exceptions.ConnectionError:
140
- logger.debug(f"Connection Error: {warn_prompt}")
141
- except requests.exceptions.Timeout:
142
- logger.debug(f"Timed Out: {warn_prompt}")
143
- except json.JSONDecodeError:
144
- logger.debug(warn_prompt)
145
- logger.debug(f"Error decoding JSON from {manim_info_url}")
146
- except Exception:
147
- logger.debug(f"Something went wrong: {warn_prompt}")
148
160
 
149
- return args
161
+ return kwargs
@@ -1,7 +1,8 @@
1
1
  from __future__ import annotations
2
2
 
3
- import click
4
- from cloup import option, option_group
3
+ from cloup import Choice, option, option_group
4
+
5
+ __all__ = ["ease_of_access_options"]
5
6
 
6
7
  ease_of_access_options = option_group(
7
8
  "Ease of access options",
@@ -9,7 +10,7 @@ ease_of_access_options = option_group(
9
10
  "--progress_bar",
10
11
  default=None,
11
12
  show_default=False,
12
- type=click.Choice(
13
+ type=Choice(
13
14
  ["display", "leave", "none"],
14
15
  case_sensitive=False,
15
16
  ),