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
@@ -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
  ),
@@ -1,28 +1,59 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import logging
3
4
  import re
5
+ import sys
4
6
  from typing import TYPE_CHECKING
5
7
 
6
- import click
7
- from cloup import option, option_group
8
-
9
- from ... import logger
8
+ from cloup import Choice, option, option_group
10
9
 
11
10
  if TYPE_CHECKING:
12
- from cloup._option_groups import OptionGroupDecorator
11
+ from click import Context, Option
12
+
13
+ __all__ = ["global_options"]
14
+
15
+ logger = logging.getLogger("manim")
16
+
17
+
18
+ def validate_gui_location(
19
+ ctx: Context, param: Option, value: str | None
20
+ ) -> tuple[int, int] | None:
21
+ """If the ``value`` string is given, extract from it the GUI location,
22
+ which should be in any of these formats: 'x;y', 'x,y' or 'x-y'.
13
23
 
24
+ Parameters
25
+ ----------
26
+ ctx
27
+ The Click context.
28
+ param
29
+ A Click option.
30
+ value
31
+ The optional string which will be parsed.
14
32
 
15
- def validate_gui_location(ctx, param, value):
16
- if value:
17
- try:
18
- x_offset, y_offset = map(int, re.split(r"[;,\-]", value))
19
- return (x_offset, y_offset)
20
- except Exception:
21
- logger.error("GUI location option is invalid.")
22
- exit()
33
+ Returns
34
+ -------
35
+ tuple[int, int] | None
36
+ If ``value`` is ``None``, the return value is ``None``. Otherwise, it's
37
+ the ``(x, y)`` location for the GUI.
23
38
 
39
+ Raises
40
+ ------
41
+ ValueError
42
+ If ``value`` has an invalid format.
43
+ """
44
+ if value is None:
45
+ return None
24
46
 
25
- global_options: OptionGroupDecorator = option_group(
47
+ try:
48
+ x_offset, y_offset = map(int, re.split(r"[;,\-]", value))
49
+ except Exception:
50
+ logger.error("GUI location option is invalid.")
51
+ sys.exit()
52
+
53
+ return (x_offset, y_offset)
54
+
55
+
56
+ global_options = option_group(
26
57
  "Global options",
27
58
  option(
28
59
  "-c",
@@ -53,7 +84,7 @@ global_options: OptionGroupDecorator = option_group(
53
84
  option(
54
85
  "-v",
55
86
  "--verbosity",
56
- type=click.Choice(
87
+ type=Choice(
57
88
  ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
58
89
  case_sensitive=False,
59
90
  ),
@@ -94,12 +125,23 @@ global_options: OptionGroupDecorator = option_group(
94
125
  "--force_window",
95
126
  is_flag=True,
96
127
  help="Force window to open when using the opengl renderer, intended for debugging as it may impact performance",
97
- default=False,
128
+ default=None,
98
129
  ),
99
130
  option(
100
131
  "--dry_run",
101
132
  is_flag=True,
102
133
  help="Renders animations without outputting image or video files and disables the window",
103
- default=False,
134
+ default=None,
135
+ ),
136
+ option(
137
+ "--no_latex_cleanup",
138
+ is_flag=True,
139
+ help="Prevents deletion of .aux, .dvi, and .log files produced by Tex and MathTex.",
140
+ default=None,
141
+ ),
142
+ option(
143
+ "--preview_command",
144
+ help="The command used to preview the output file (for example vlc for video files)",
145
+ default=None,
104
146
  ),
105
147
  )
@@ -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 IntRange, Path, option, option_group
4
+
5
+ __all__ = ["output_options"]
5
6
 
6
7
  output_options = option_group(
7
8
  "Output options",
@@ -15,7 +16,7 @@ output_options = option_group(
15
16
  option(
16
17
  "-0",
17
18
  "--zero_pad",
18
- type=click.IntRange(0, 9),
19
+ type=IntRange(0, 9),
19
20
  default=None,
20
21
  help="Zero padding for PNG file names.",
21
22
  ),
@@ -27,13 +28,13 @@ output_options = option_group(
27
28
  ),
28
29
  option(
29
30
  "--media_dir",
30
- type=click.Path(),
31
+ type=Path(),
31
32
  default=None,
32
33
  help="Path to store rendered videos and latex.",
33
34
  ),
34
35
  option(
35
36
  "--log_dir",
36
- type=click.Path(),
37
+ type=Path(),
37
38
  help="Path to store render logs.",
38
39
  default=None,
39
40
  ),
@@ -1,39 +1,105 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import logging
3
4
  import re
5
+ import sys
6
+ from typing import TYPE_CHECKING
4
7
 
5
- import click
6
- from cloup import option, option_group
8
+ from cloup import Choice, option, option_group
7
9
 
8
10
  from manim.constants import QUALITIES, RendererType
9
11
 
10
- from ... import logger
12
+ if TYPE_CHECKING:
13
+ from click import Context, Option
11
14
 
15
+ __all__ = ["render_options"]
16
+
17
+ logger = logging.getLogger("manim")
18
+
19
+
20
+ def validate_scene_range(
21
+ ctx: Context, param: Option, value: str | None
22
+ ) -> tuple[int] | tuple[int, int] | None:
23
+ """If the ``value`` string is given, extract from it the scene range, which
24
+ should be in any of these formats: 'start', 'start;end', 'start,end' or
25
+ 'start-end'. Otherwise, return ``None``.
26
+
27
+ Parameters
28
+ ----------
29
+ ctx
30
+ The Click context.
31
+ param
32
+ A Click option.
33
+ value
34
+ The optional string which will be parsed.
35
+
36
+ Returns
37
+ -------
38
+ tuple[int] | tuple[int, int] | None
39
+ If ``value`` is ``None``, the return value is ``None``. Otherwise, it's
40
+ the scene range, given by a tuple which may contain a single value
41
+ ``start`` or two values ``start`` and ``end``.
42
+
43
+ Raises
44
+ ------
45
+ ValueError
46
+ If ``value`` has an invalid format.
47
+ """
48
+ if value is None:
49
+ return None
12
50
 
13
- def validate_scene_range(ctx, param, value):
14
51
  try:
15
52
  start = int(value)
16
53
  return (start,)
17
54
  except Exception:
18
55
  pass
19
56
 
20
- if value:
21
- try:
22
- start, end = map(int, re.split(r"[;,\-]", value))
23
- return start, end
24
- except Exception:
25
- logger.error("Couldn't determine a range for -n option.")
26
- exit()
57
+ try:
58
+ start, end = map(int, re.split(r"[;,\-]", value))
59
+ except Exception:
60
+ logger.error("Couldn't determine a range for -n option.")
61
+ sys.exit()
62
+
63
+ return start, end
64
+
65
+
66
+ def validate_resolution(
67
+ ctx: Context, param: Option, value: str | None
68
+ ) -> tuple[int, int] | None:
69
+ """If the ``value`` string is given, extract from it the resolution, which
70
+ should be in any of these formats: 'W;H', 'W,H' or 'W-H'. Otherwise, return
71
+ ``None``.
72
+
73
+ Parameters
74
+ ----------
75
+ ctx
76
+ The Click context.
77
+ param
78
+ A Click option.
79
+ value
80
+ The optional string which will be parsed.
81
+
82
+ Returns
83
+ -------
84
+ tuple[int, int] | None
85
+ If ``value`` is ``None``, the return value is ``None``. Otherwise, it's
86
+ the resolution as a ``(W, H)`` tuple.
87
+
88
+ Raises
89
+ ------
90
+ ValueError
91
+ If ``value`` has an invalid format.
92
+ """
93
+ if value is None:
94
+ return None
27
95
 
96
+ try:
97
+ width, height = map(int, re.split(r"[;,\-]", value))
98
+ except Exception:
99
+ logger.error("Resolution option is invalid.")
100
+ sys.exit()
28
101
 
29
- def validate_resolution(ctx, param, value):
30
- if value:
31
- try:
32
- start, end = map(int, re.split(r"[;,\-]", value))
33
- return (start, end)
34
- except Exception:
35
- logger.error("Resolution option is invalid.")
36
- exit()
102
+ return width, height
37
103
 
38
104
 
39
105
  render_options = option_group(
@@ -55,23 +121,29 @@ render_options = option_group(
55
121
  ),
56
122
  option(
57
123
  "--format",
58
- type=click.Choice(["png", "gif", "mp4", "webm", "mov"], case_sensitive=False),
124
+ type=Choice(["png", "gif", "mp4", "webm", "mov"], case_sensitive=False),
125
+ default=None,
126
+ ),
127
+ option(
128
+ "-s",
129
+ "--save_last_frame",
59
130
  default=None,
131
+ is_flag=True,
132
+ help="Render and save only the last frame of a scene as a PNG image.",
60
133
  ),
61
- option("-s", "--save_last_frame", is_flag=True, default=None),
62
134
  option(
63
135
  "-q",
64
136
  "--quality",
65
137
  default=None,
66
- type=click.Choice(
67
- list(reversed([q["flag"] for q in QUALITIES.values() if q["flag"]])), # type: ignore
138
+ type=Choice(
139
+ list(reversed([q["flag"] for q in QUALITIES.values() if q["flag"]])),
68
140
  case_sensitive=False,
69
141
  ),
70
142
  help="Render quality at the follow resolution framerates, respectively: "
71
143
  + ", ".join(
72
144
  reversed(
73
145
  [
74
- f'{q["pixel_width"]}x{q["pixel_height"]} {q["frame_rate"]}FPS'
146
+ f"{q['pixel_width']}x{q['pixel_height']} {q['frame_rate']}FPS"
75
147
  for q in QUALITIES.values()
76
148
  if q["flag"]
77
149
  ]
@@ -83,7 +155,7 @@ render_options = option_group(
83
155
  "--resolution",
84
156
  callback=validate_resolution,
85
157
  default=None,
86
- help="Resolution in (W,H) for when 16:9 aspect ratio isn't possible.",
158
+ help='Resolution in "W,H" for when 16:9 aspect ratio isn\'t possible.',
87
159
  ),
88
160
  option(
89
161
  "--fps",
@@ -95,7 +167,7 @@ render_options = option_group(
95
167
  ),
96
168
  option(
97
169
  "--renderer",
98
- type=click.Choice(
170
+ type=Choice(
99
171
  [renderer_type.value for renderer_type in RendererType],
100
172
  case_sensitive=False,
101
173
  ),
@@ -122,13 +194,6 @@ render_options = option_group(
122
194
  is_flag=True,
123
195
  help="Save section videos in addition to movie file.",
124
196
  ),
125
- option(
126
- "-s",
127
- "--save_last_frame",
128
- default=None,
129
- is_flag=True,
130
- help="Save last frame as png (Deprecated).",
131
- ),
132
197
  option(
133
198
  "-t",
134
199
  "--transparent",