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
@@ -26,7 +26,7 @@ render scenes that are defined within doctests, for example::
26
26
  >>> from manim import Create, Dot, RED, Scene
27
27
  >>> dot = Dot(color=RED)
28
28
  >>> dot.color
29
- <Color #fc6255>
29
+ ManimColor('#FC6255')
30
30
  >>> class DirectiveDoctestExample(Scene):
31
31
  ... def construct(self):
32
32
  ... self.play(Create(dot))
@@ -77,25 +77,40 @@ directive:
77
77
  that is rendered in a reference block after the source code.
78
78
 
79
79
  """
80
+
80
81
  from __future__ import annotations
81
82
 
82
83
  import csv
83
84
  import itertools as it
84
- import os
85
85
  import re
86
86
  import shutil
87
87
  import sys
88
+ import textwrap
88
89
  from pathlib import Path
89
90
  from timeit import timeit
91
+ from typing import TYPE_CHECKING, Any, TypedDict
90
92
 
91
93
  import jinja2
92
94
  from docutils import nodes
93
- from docutils.parsers.rst import Directive, directives # type: ignore
95
+ from docutils.parsers.rst import Directive, directives
94
96
  from docutils.statemachine import StringList
95
97
 
96
98
  from manim import QUALITIES
99
+ from manim import __version__ as manim_version
100
+
101
+ if TYPE_CHECKING:
102
+ from sphinx.application import Sphinx
103
+
104
+
105
+ __all__ = ["ManimDirective"]
106
+
97
107
 
98
- classnamedict = {}
108
+ classnamedict: dict[str, int] = {}
109
+
110
+
111
+ class SetupMetadata(TypedDict):
112
+ parallel_read_safe: bool
113
+ parallel_write_safe: bool
99
114
 
100
115
 
101
116
  class SkipManimNode(nodes.Admonition, nodes.Element):
@@ -108,14 +123,16 @@ class SkipManimNode(nodes.Admonition, nodes.Element):
108
123
  pass
109
124
 
110
125
 
111
- def visit(self, node, name=""):
112
- self.visit_admonition(node, name)
126
+ def visit(self: SkipManimNode, node: nodes.Element, name: str = "") -> None:
127
+ # TODO: Parent classes don't have a visit_admonition() method.
128
+ self.visit_admonition(node, name) # type: ignore[attr-defined]
113
129
  if not isinstance(node[0], nodes.title):
114
130
  node.insert(0, nodes.title("skip-manim", "Example Placeholder"))
115
131
 
116
132
 
117
- def depart(self, node):
118
- self.depart_admonition(node)
133
+ def depart(self: SkipManimNode, node: nodes.Element) -> None:
134
+ # TODO: Parent classes don't have a depart_admonition() method.
135
+ self.depart_admonition(node) # type: ignore[attr-defined]
119
136
 
120
137
 
121
138
  def process_name_list(option_input: str, reference_type: str) -> list[str]:
@@ -141,6 +158,7 @@ class ManimDirective(Directive):
141
158
 
142
159
  See the module docstring for documentation.
143
160
  """
161
+
144
162
  has_content = True
145
163
  required_arguments = 1
146
164
  optional_arguments = 0
@@ -160,24 +178,33 @@ class ManimDirective(Directive):
160
178
  }
161
179
  final_argument_whitespace = True
162
180
 
163
- def run(self):
181
+ def run(self) -> list[nodes.Element]:
164
182
  # Rendering is skipped if the tag skip-manim is present,
165
183
  # or if we are making the pot-files
166
184
  should_skip = (
167
- "skip-manim" in self.state.document.settings.env.app.builder.tags.tags
185
+ "skip-manim" in self.state.document.settings.env.app.builder.tags
168
186
  or self.state.document.settings.env.app.builder.name == "gettext"
169
187
  )
170
188
  if should_skip:
189
+ clsname = self.arguments[0]
171
190
  node = SkipManimNode()
172
191
  self.state.nested_parse(
173
192
  StringList(
174
193
  [
175
- f"Placeholder block for ``{self.arguments[0]}``.",
194
+ f"Placeholder block for ``{clsname}``.",
176
195
  "",
177
196
  ".. code-block:: python",
178
197
  "",
179
198
  ]
180
199
  + [" " + line for line in self.content]
200
+ + [
201
+ "",
202
+ ".. raw:: html",
203
+ "",
204
+ f' <pre data-manim-binder data-manim-classname="{clsname}">',
205
+ ]
206
+ + [" " + line for line in self.content]
207
+ + [" </pre>"],
181
208
  ),
182
209
  self.content_offset,
183
210
  node,
@@ -206,14 +233,10 @@ class ManimDirective(Directive):
206
233
  + self.options.get("ref_functions", [])
207
234
  + self.options.get("ref_methods", [])
208
235
  )
209
- if ref_content:
210
- ref_block = "References: " + " ".join(ref_content)
211
-
212
- else:
213
- ref_block = ""
236
+ ref_block = "References: " + " ".join(ref_content) if ref_content else ""
214
237
 
215
238
  if "quality" in self.options:
216
- quality = f'{self.options["quality"]}_quality'
239
+ quality = f"{self.options['quality']}_quality"
217
240
  else:
218
241
  quality = "example_quality"
219
242
  frame_rate = QUALITIES[quality]["frame_rate"]
@@ -224,21 +247,28 @@ class ManimDirective(Directive):
224
247
  document = state_machine.document
225
248
 
226
249
  source_file_name = Path(document.attributes["source"])
227
- source_rel_name = source_file_name.relative_to(setup.confdir)
250
+ source_rel_name = source_file_name.relative_to(setup.confdir) # type: ignore[attr-defined]
228
251
  source_rel_dir = source_rel_name.parents[0]
229
- dest_dir = Path(setup.app.builder.outdir, source_rel_dir).absolute()
252
+ dest_dir = Path(setup.app.builder.outdir, source_rel_dir).absolute() # type: ignore[attr-defined]
230
253
  if not dest_dir.exists():
231
254
  dest_dir.mkdir(parents=True, exist_ok=True)
232
255
 
233
- source_block = [
256
+ source_block_in = [
234
257
  ".. code-block:: python",
235
258
  "",
236
259
  " from manim import *\n",
237
260
  *(" " + line for line in self.content),
261
+ "",
262
+ ".. raw:: html",
263
+ "",
264
+ f' <pre data-manim-binder data-manim-classname="{clsname}">',
265
+ *(" " + line for line in self.content),
266
+ "",
267
+ " </pre>",
238
268
  ]
239
- source_block = "\n".join(source_block)
269
+ source_block = "\n".join(source_block_in)
240
270
 
241
- config.media_dir = (Path(setup.confdir) / "media").absolute()
271
+ config.media_dir = (Path(setup.confdir) / "media").absolute() # type: ignore[attr-defined]
242
272
  config.images_dir = "{media_dir}/images"
243
273
  config.video_dir = "{media_dir}/videos/{quality}"
244
274
  output_file = f"{clsname}-{classnamedict[clsname]}"
@@ -260,7 +290,7 @@ class ManimDirective(Directive):
260
290
  if save_as_gif:
261
291
  example_config["format"] = "gif"
262
292
 
263
- user_code = self.content
293
+ user_code = list(self.content)
264
294
  if user_code[0].startswith(">>> "): # check whether block comes from doctest
265
295
  user_code = [
266
296
  line[4:] for line in user_code if line.startswith((">>> ", "... "))
@@ -272,10 +302,13 @@ class ManimDirective(Directive):
272
302
  f"{clsname}().render()",
273
303
  ]
274
304
 
275
- with tempconfig(example_config):
276
- run_time = timeit(lambda: exec("\n".join(code), globals()), number=1)
277
- video_dir = config.get_dir("video_dir")
278
- images_dir = config.get_dir("images_dir")
305
+ try:
306
+ with tempconfig(example_config):
307
+ run_time = timeit(lambda: exec("\n".join(code), globals()), number=1)
308
+ video_dir = config.get_dir("video_dir")
309
+ images_dir = config.get_dir("images_dir")
310
+ except Exception as e:
311
+ raise RuntimeError(f"Error while rendering example {clsname}") from e
279
312
 
280
313
  _write_rendering_stats(
281
314
  clsname,
@@ -301,7 +334,7 @@ class ManimDirective(Directive):
301
334
  clsname=clsname,
302
335
  clsname_lowercase=clsname.lower(),
303
336
  hide_source=hide_source,
304
- filesrc_rel=Path(filesrc).relative_to(setup.confdir).as_posix(),
337
+ filesrc_rel=Path(filesrc).relative_to(setup.confdir).as_posix(), # type: ignore[attr-defined]
305
338
  no_autoplay=no_autoplay,
306
339
  output_file=output_file,
307
340
  save_last_frame=save_last_frame,
@@ -320,18 +353,18 @@ class ManimDirective(Directive):
320
353
  rendering_times_file_path = Path("../rendering_times.csv")
321
354
 
322
355
 
323
- def _write_rendering_stats(scene_name, run_time, file_name):
356
+ def _write_rendering_stats(scene_name: str, run_time: float, file_name: str) -> None:
324
357
  with rendering_times_file_path.open("a") as file:
325
358
  csv.writer(file).writerow(
326
359
  [
327
360
  re.sub(r"^(reference\/)|(manim\.)", "", file_name),
328
361
  scene_name,
329
- "%.3f" % run_time,
362
+ f"{run_time:.3f}",
330
363
  ],
331
364
  )
332
365
 
333
366
 
334
- def _log_rendering_times(*args):
367
+ def _log_rendering_times(*args: tuple[Any]) -> None:
335
368
  if rendering_times_file_path.exists():
336
369
  with rendering_times_file_path.open() as file:
337
370
  data = list(csv.reader(file))
@@ -340,10 +373,13 @@ def _log_rendering_times(*args):
340
373
 
341
374
  print("\nRendering Summary\n-----------------\n")
342
375
 
376
+ # filter out empty lists caused by csv reader
377
+ data = [row for row in data if row]
378
+
343
379
  max_file_length = max(len(row[0]) for row in data)
344
- for key, group in it.groupby(data, key=lambda row: row[0]):
380
+ for key, group_iter in it.groupby(data, key=lambda row: row[0]):
345
381
  key = key.ljust(max_file_length + 1, ".")
346
- group = list(group)
382
+ group = list(group_iter)
347
383
  if len(group) == 1:
348
384
  row = group[0]
349
385
  print(f"{key}{row[2].rjust(7, '.')}s {row[1]}")
@@ -353,28 +389,45 @@ def _log_rendering_times(*args):
353
389
  f"{key}{f'{time_sum:.3f}'.rjust(7, '.')}s => {len(group)} EXAMPLES",
354
390
  )
355
391
  for row in group:
356
- print(f"{' '*(max_file_length)} {row[2].rjust(7)}s {row[1]}")
392
+ print(f"{' ' * max_file_length} {row[2].rjust(7)}s {row[1]}")
357
393
  print("")
358
394
 
359
395
 
360
- def _delete_rendering_times(*args):
396
+ def _delete_rendering_times(*args: tuple[Any]) -> None:
361
397
  if rendering_times_file_path.exists():
362
398
  rendering_times_file_path.unlink()
363
399
 
364
400
 
365
- def setup(app):
366
- app.add_node(SkipManimNode, html=(visit, depart))
401
+ def setup(app: Sphinx) -> SetupMetadata:
402
+ app.add_node(
403
+ SkipManimNode,
404
+ html=(visit, depart),
405
+ latex=(lambda a, b: None, lambda a, b: None),
406
+ )
367
407
 
368
- setup.app = app
369
- setup.config = app.config
370
- setup.confdir = app.confdir
408
+ setup.app = app # type: ignore[attr-defined]
409
+ setup.config = app.config # type: ignore[attr-defined]
410
+ setup.confdir = app.confdir # type: ignore[attr-defined]
371
411
 
372
412
  app.add_directive("manim", ManimDirective)
373
413
 
374
414
  app.connect("builder-inited", _delete_rendering_times)
375
415
  app.connect("build-finished", _log_rendering_times)
376
416
 
377
- metadata = {"parallel_read_safe": False, "parallel_write_safe": True}
417
+ app.add_js_file("manim-binder.min.js")
418
+ app.add_js_file(
419
+ None,
420
+ body=textwrap.dedent(
421
+ f"""\
422
+ window.initManimBinder({{branch: "v{manim_version}"}})
423
+ """
424
+ ).strip(),
425
+ )
426
+
427
+ metadata: SetupMetadata = {
428
+ "parallel_read_safe": False,
429
+ "parallel_write_safe": True,
430
+ }
378
431
  return metadata
379
432
 
380
433
 
@@ -0,0 +1,245 @@
1
+ """Read and parse all the Manim modules and extract documentation from them."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import ast
6
+ import sys
7
+ from ast import Attribute, Name, Subscript
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ from typing_extensions import TypeAlias
12
+
13
+ __all__ = ["parse_module_attributes"]
14
+
15
+
16
+ AliasInfo: TypeAlias = dict[str, str]
17
+ """Dictionary with a `definition` key containing the definition of
18
+ a :class:`TypeAlias` as a string, and optionally a `doc` key containing
19
+ the documentation for that alias, if it exists.
20
+ """
21
+
22
+ AliasCategoryDict: TypeAlias = dict[str, AliasInfo]
23
+ """Dictionary which holds an `AliasInfo` for every alias name in a same
24
+ category.
25
+ """
26
+
27
+ ModuleLevelAliasDict: TypeAlias = dict[str, AliasCategoryDict]
28
+ """Dictionary containing every :class:`TypeAlias` defined in a module,
29
+ classified by category in different `AliasCategoryDict` objects.
30
+ """
31
+
32
+ ModuleTypeVarDict: TypeAlias = dict[str, str]
33
+ """Dictionary containing every :class:`TypeVar` defined in a module."""
34
+
35
+
36
+ AliasDocsDict: TypeAlias = dict[str, ModuleLevelAliasDict]
37
+ """Dictionary which, for every module in Manim, contains documentation
38
+ about their module-level attributes which are explicitly defined as
39
+ :class:`TypeAlias`, separating them from the rest of attributes.
40
+ """
41
+
42
+ DataDict: TypeAlias = dict[str, list[str]]
43
+ """Type for a dictionary which, for every module, contains a list with
44
+ the names of all their DOCUMENTED module-level attributes (identified
45
+ by Sphinx via the ``data`` role, hence the name) which are NOT
46
+ explicitly defined as :class:`TypeAlias`.
47
+ """
48
+
49
+ TypeVarDict: TypeAlias = dict[str, ModuleTypeVarDict]
50
+ """A dictionary mapping module names to dictionaries of :class:`TypeVar` objects."""
51
+
52
+ ALIAS_DOCS_DICT: AliasDocsDict = {}
53
+ DATA_DICT: DataDict = {}
54
+ TYPEVAR_DICT: TypeVarDict = {}
55
+
56
+ MANIM_ROOT = Path(__file__).resolve().parent.parent.parent
57
+
58
+ # In the following, we will use ``type(xyz) is xyz_type`` instead of
59
+ # isinstance checks to make sure no subclasses of the type pass the
60
+ # check
61
+ # ruff: noqa: E721
62
+
63
+
64
+ def parse_module_attributes() -> tuple[AliasDocsDict, DataDict, TypeVarDict]:
65
+ """Read all files, generate Abstract Syntax Trees from them, and
66
+ extract useful information about the type aliases defined in the
67
+ files: the category they belong to, their definition and their
68
+ description, separating them from the "regular" module attributes.
69
+
70
+ Returns
71
+ -------
72
+ ALIAS_DOCS_DICT : :class:`AliasDocsDict`
73
+ A dictionary containing the information from all the type
74
+ aliases in Manim. See :class:`AliasDocsDict` for more information.
75
+
76
+ DATA_DICT : :class:`DataDict`
77
+ A dictionary containing the names of all DOCUMENTED
78
+ module-level attributes which are not a :class:`TypeAlias`.
79
+
80
+ TYPEVAR_DICT : :class:`TypeVarDict`
81
+ A dictionary containing the definitions of :class:`TypeVar` objects,
82
+ organized by modules.
83
+ """
84
+ global ALIAS_DOCS_DICT
85
+ global DATA_DICT
86
+ global TYPEVAR_DICT
87
+
88
+ if ALIAS_DOCS_DICT or DATA_DICT or TYPEVAR_DICT:
89
+ return ALIAS_DOCS_DICT, DATA_DICT, TYPEVAR_DICT
90
+
91
+ for module_path in MANIM_ROOT.rglob("*.py"):
92
+ module_name_t1 = module_path.resolve().relative_to(MANIM_ROOT)
93
+ module_name_t2 = list(module_name_t1.parts)
94
+ module_name_t2[-1] = module_name_t2[-1].removesuffix(".py")
95
+ module_name = ".".join(module_name_t2)
96
+
97
+ module_content = module_path.read_text(encoding="utf-8")
98
+
99
+ # For storing TypeAliases
100
+ module_dict: ModuleLevelAliasDict = {}
101
+ category_dict: AliasCategoryDict | None = None
102
+ alias_info: AliasInfo | None = None
103
+
104
+ # For storing TypeVars
105
+ module_typevars: ModuleTypeVarDict = {}
106
+
107
+ # For storing regular module attributes
108
+ data_list: list[str] = []
109
+ data_name: str | None = None
110
+
111
+ for node in ast.iter_child_nodes(ast.parse(module_content)):
112
+ # If we encounter a string:
113
+ if (
114
+ type(node) is ast.Expr
115
+ and type(node.value) is ast.Constant
116
+ and type(node.value.value) is str
117
+ ):
118
+ string = node.value.value.strip()
119
+ # It can be the start of a category
120
+ section_str = "[CATEGORY]"
121
+ if string.startswith(section_str):
122
+ category_name = string[len(section_str) :].strip()
123
+ module_dict[category_name] = {}
124
+ category_dict = module_dict[category_name]
125
+ alias_info = None
126
+ # or a docstring of the alias defined before
127
+ elif alias_info:
128
+ alias_info["doc"] = string
129
+ # or a docstring of the module attribute defined before
130
+ elif data_name:
131
+ data_list.append(data_name)
132
+ continue
133
+
134
+ # if it's defined under if TYPE_CHECKING
135
+ # go through the body of the if statement
136
+ if (
137
+ # NOTE: This logic does not (and cannot)
138
+ # check if the comparison is against a
139
+ # variable called TYPE_CHECKING
140
+ # It also says that you cannot do the following
141
+ # import typing as foo
142
+ # if foo.TYPE_CHECKING:
143
+ # BAR: TypeAlias = ...
144
+ type(node) is ast.If
145
+ and (
146
+ (
147
+ # if TYPE_CHECKING
148
+ type(node.test) is ast.Name and node.test.id == "TYPE_CHECKING"
149
+ )
150
+ or (
151
+ # if typing.TYPE_CHECKING
152
+ type(node.test) is ast.Attribute
153
+ and type(node.test.value) is ast.Name
154
+ and node.test.value.id == "typing"
155
+ and node.test.attr == "TYPE_CHECKING"
156
+ )
157
+ )
158
+ ):
159
+ inner_nodes: list[Any] = node.body
160
+ else:
161
+ inner_nodes = [node]
162
+
163
+ for node in inner_nodes:
164
+ # Check if this node is a TypeAlias (type <name> = <value>)
165
+ # or an AnnAssign annotated as TypeAlias (<target>: TypeAlias = <value>).
166
+ is_type_alias = (
167
+ sys.version_info >= (3, 12) and type(node) is ast.TypeAlias
168
+ )
169
+ is_annotated_assignment_with_value = (
170
+ type(node) is ast.AnnAssign
171
+ and type(node.annotation) is ast.Name
172
+ and node.annotation.id == "TypeAlias"
173
+ and type(node.target) is ast.Name
174
+ and node.value is not None
175
+ )
176
+ if is_type_alias or is_annotated_assignment_with_value:
177
+ # TODO: ast.TypeAlias does not exist before Python 3.12, and that
178
+ # could be the reason why MyPy does not recognize these as
179
+ # attributes of node.
180
+ alias_name = node.name.id if is_type_alias else node.target.id
181
+ definition_node = node.value
182
+
183
+ # If the definition is a Union, replace with vertical bar notation.
184
+ # Instead of "Union[Type1, Type2]", we'll have "Type1 | Type2".
185
+ if (
186
+ type(definition_node) is ast.Subscript
187
+ and type(definition_node.value) is ast.Name
188
+ and definition_node.value.id == "Union"
189
+ ):
190
+ union_elements = definition_node.slice.elts # type: ignore[attr-defined]
191
+ definition = " | ".join(
192
+ ast.unparse(elem) for elem in union_elements
193
+ )
194
+ else:
195
+ definition = ast.unparse(definition_node)
196
+
197
+ definition = definition.replace("npt.", "")
198
+ if category_dict is None:
199
+ module_dict[""] = {}
200
+ category_dict = module_dict[""]
201
+ category_dict[alias_name] = {"definition": definition}
202
+ alias_info = category_dict[alias_name]
203
+ continue
204
+
205
+ # Check if it is a typing.TypeVar (<target> = TypeVar(...)).
206
+ elif (
207
+ type(node) is ast.Assign
208
+ and type(node.targets[0]) is ast.Name
209
+ and type(node.value) is ast.Call
210
+ and type(node.value.func) is ast.Name
211
+ and node.value.func.id.endswith("TypeVar")
212
+ ):
213
+ module_typevars[node.targets[0].id] = ast.unparse(
214
+ node.value
215
+ ).replace("_", r"\_")
216
+ continue
217
+
218
+ # If here, the node is not a TypeAlias definition
219
+ alias_info = None
220
+
221
+ # It could still be a module attribute definition.
222
+ # Does the assignment have a target of type Name? Then
223
+ # it could be considered a definition of a module attribute.
224
+ if type(node) is ast.AnnAssign:
225
+ target: Name | Attribute | Subscript | ast.expr | None = node.target
226
+ elif type(node) is ast.Assign and len(node.targets) == 1:
227
+ target = node.targets[0]
228
+ else:
229
+ target = None
230
+
231
+ if type(target) is ast.Name and not (
232
+ type(node) is ast.Assign and target.id not in module_typevars
233
+ ):
234
+ data_name = target.id
235
+ else:
236
+ data_name = None
237
+
238
+ if len(module_dict) > 0:
239
+ ALIAS_DOCS_DICT[module_name] = module_dict
240
+ if len(data_list) > 0:
241
+ DATA_DICT[module_name] = data_list
242
+ if module_typevars:
243
+ TYPEVAR_DICT[module_name] = module_typevars
244
+
245
+ return ALIAS_DOCS_DICT, DATA_DICT, TYPEVAR_DICT
manim/utils/exceptions.py CHANGED
@@ -1,5 +1,11 @@
1
1
  from __future__ import annotations
2
2
 
3
+ __all__ = [
4
+ "EndSceneEarlyException",
5
+ "RerunSceneException",
6
+ "MultiAnimationOverrideException",
7
+ ]
8
+
3
9
 
4
10
  class EndSceneEarlyException(Exception):
5
11
  pass
manim/utils/family.py CHANGED
@@ -1,17 +1,19 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import itertools as it
4
- from typing import Iterable
4
+ from collections.abc import Iterable
5
5
 
6
6
  from ..mobject.mobject import Mobject
7
7
  from ..utils.iterables import remove_list_redundancies
8
8
 
9
+ __all__ = ["extract_mobject_family_members"]
10
+
9
11
 
10
12
  def extract_mobject_family_members(
11
13
  mobjects: Iterable[Mobject],
12
- use_z_index=False,
14
+ use_z_index: bool = False,
13
15
  only_those_with_points: bool = False,
14
- ):
16
+ ) -> list[Mobject]:
15
17
  """Returns a list of the types of mobjects and their family members present.
16
18
  A "family" in this context refers to a mobject, its submobjects, and their
17
19
  submobjects, recursively.
manim/utils/family_ops.py CHANGED
@@ -2,15 +2,26 @@ from __future__ import annotations
2
2
 
3
3
  import itertools as it
4
4
 
5
+ from manim.mobject.mobject import Mobject
5
6
 
6
- def extract_mobject_family_members(mobject_list, only_those_with_points=False):
7
+ __all__ = [
8
+ "extract_mobject_family_members",
9
+ "restructure_list_to_exclude_certain_family_members",
10
+ ]
11
+
12
+
13
+ def extract_mobject_family_members(
14
+ mobject_list: list[Mobject], only_those_with_points: bool = False
15
+ ) -> list[Mobject]:
7
16
  result = list(it.chain(*(mob.get_family() for mob in mobject_list)))
8
17
  if only_those_with_points:
9
18
  result = [mob for mob in result if mob.has_points()]
10
19
  return result
11
20
 
12
21
 
13
- def restructure_list_to_exclude_certain_family_members(mobject_list, to_remove):
22
+ def restructure_list_to_exclude_certain_family_members(
23
+ mobject_list: list[Mobject], to_remove: list[Mobject]
24
+ ) -> list[Mobject]:
14
25
  """
15
26
  Removes anything in to_remove from mobject_list, but in the event that one of
16
27
  the items to be removed is a member of the family of an item in mobject_list,
@@ -20,10 +31,12 @@ def restructure_list_to_exclude_certain_family_members(mobject_list, to_remove):
20
31
  but one of its submobjects is removed, e.g. scene.remove(m1), it's useful
21
32
  for the list of mobject_list to be edited to contain other submobjects, but not m1.
22
33
  """
23
- new_list = []
34
+ new_list: list[Mobject] = []
24
35
  to_remove = extract_mobject_family_members(to_remove)
25
36
 
26
- def add_safe_mobjects_from_list(list_to_examine, set_to_remove):
37
+ def add_safe_mobjects_from_list(
38
+ list_to_examine: list[Mobject], set_to_remove: set[Mobject]
39
+ ) -> None:
27
40
  for mob in list_to_examine:
28
41
  if mob in set_to_remove:
29
42
  continue