manim 0.18.1__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.
- manim/__main__.py +45 -12
- manim/_config/__init__.py +2 -2
- manim/_config/cli_colors.py +8 -4
- manim/_config/default.cfg +0 -2
- manim/_config/logger_utils.py +5 -0
- manim/_config/utils.py +29 -38
- manim/animation/animation.py +148 -8
- manim/animation/composition.py +16 -13
- manim/animation/creation.py +184 -8
- manim/animation/fading.py +5 -8
- manim/animation/indication.py +93 -26
- manim/animation/movement.py +21 -3
- manim/animation/rotation.py +2 -1
- manim/animation/specialized.py +3 -5
- manim/animation/speedmodifier.py +3 -3
- manim/animation/transform.py +4 -5
- manim/animation/updaters/mobject_update_utils.py +17 -14
- manim/camera/camera.py +2 -2
- manim/cli/__init__.py +17 -0
- manim/cli/cfg/group.py +52 -36
- manim/cli/checkhealth/checks.py +92 -76
- manim/cli/checkhealth/commands.py +12 -5
- manim/cli/default_group.py +148 -24
- manim/cli/init/commands.py +28 -23
- manim/cli/plugins/commands.py +13 -3
- manim/cli/render/commands.py +47 -42
- manim/cli/render/global_options.py +43 -9
- manim/cli/render/render_options.py +84 -19
- manim/constants.py +11 -4
- manim/mobject/frame.py +0 -1
- manim/mobject/geometry/arc.py +109 -75
- manim/mobject/geometry/boolean_ops.py +20 -17
- manim/mobject/geometry/labeled.py +300 -77
- manim/mobject/geometry/line.py +120 -60
- manim/mobject/geometry/polygram.py +109 -25
- manim/mobject/geometry/shape_matchers.py +35 -15
- manim/mobject/geometry/tips.py +36 -27
- manim/mobject/graph.py +48 -40
- manim/mobject/graphing/coordinate_systems.py +110 -45
- manim/mobject/graphing/functions.py +16 -10
- manim/mobject/graphing/number_line.py +23 -9
- manim/mobject/graphing/probability.py +2 -10
- manim/mobject/graphing/scale.py +6 -5
- manim/mobject/matrix.py +17 -19
- manim/mobject/mobject.py +149 -103
- manim/mobject/opengl/opengl_geometry.py +4 -8
- manim/mobject/opengl/opengl_mobject.py +506 -343
- manim/mobject/opengl/opengl_point_cloud_mobject.py +3 -7
- manim/mobject/opengl/opengl_surface.py +1 -2
- manim/mobject/opengl/opengl_vectorized_mobject.py +27 -65
- manim/mobject/svg/brace.py +61 -13
- manim/mobject/svg/svg_mobject.py +2 -1
- manim/mobject/table.py +11 -12
- manim/mobject/text/code_mobject.py +186 -550
- manim/mobject/text/numbers.py +7 -7
- manim/mobject/text/tex_mobject.py +22 -13
- manim/mobject/text/text_mobject.py +29 -20
- manim/mobject/three_d/polyhedra.py +98 -1
- manim/mobject/three_d/three_dimensions.py +59 -31
- manim/mobject/types/image_mobject.py +37 -23
- manim/mobject/types/point_cloud_mobject.py +103 -67
- manim/mobject/types/vectorized_mobject.py +387 -214
- manim/mobject/value_tracker.py +2 -1
- manim/mobject/vector_field.py +2 -4
- manim/opengl/__init__.py +3 -3
- manim/plugins/__init__.py +2 -3
- manim/plugins/plugins_flags.py +3 -3
- manim/renderer/cairo_renderer.py +11 -11
- manim/renderer/opengl_renderer.py +19 -20
- manim/renderer/shader.py +2 -3
- manim/renderer/shader_wrapper.py +3 -2
- manim/scene/moving_camera_scene.py +23 -0
- manim/scene/scene.py +72 -41
- manim/scene/scene_file_writer.py +313 -164
- manim/scene/section.py +15 -15
- manim/scene/three_d_scene.py +8 -15
- manim/scene/vector_space_scene.py +3 -6
- manim/typing.py +326 -66
- manim/utils/bezier.py +1658 -381
- manim/utils/caching.py +11 -5
- manim/utils/color/AS2700.py +2 -0
- manim/utils/color/BS381.py +2 -0
- manim/utils/color/DVIPSNAMES.py +96 -0
- manim/utils/color/SVGNAMES.py +179 -0
- manim/utils/color/X11.py +3 -0
- manim/utils/color/XKCD.py +2 -0
- manim/utils/color/__init__.py +8 -5
- manim/utils/color/core.py +818 -301
- manim/utils/color/manim_colors.py +7 -9
- manim/utils/commands.py +40 -19
- manim/utils/config_ops.py +18 -13
- manim/utils/debug.py +8 -6
- manim/utils/deprecation.py +92 -43
- manim/utils/docbuild/autoaliasattr_directive.py +45 -8
- manim/utils/docbuild/autocolor_directive.py +12 -13
- manim/utils/docbuild/manim_directive.py +35 -29
- manim/utils/docbuild/module_parsing.py +74 -27
- manim/utils/family.py +3 -3
- manim/utils/family_ops.py +12 -4
- manim/utils/file_ops.py +22 -16
- manim/utils/hashing.py +7 -7
- manim/utils/images.py +10 -4
- manim/utils/ipython_magic.py +12 -8
- manim/utils/iterables.py +161 -119
- manim/utils/module_ops.py +55 -19
- manim/utils/opengl.py +68 -23
- manim/utils/parameter_parsing.py +3 -2
- manim/utils/paths.py +11 -5
- manim/utils/polylabel.py +168 -0
- manim/utils/qhull.py +218 -0
- manim/utils/rate_functions.py +69 -32
- manim/utils/simple_functions.py +24 -15
- manim/utils/sounds.py +7 -1
- manim/utils/space_ops.py +48 -37
- manim/utils/testing/_frames_testers.py +13 -8
- manim/utils/testing/_show_diff.py +5 -3
- manim/utils/testing/_test_class_makers.py +33 -18
- manim/utils/testing/frames_comparison.py +20 -14
- manim/utils/tex.py +4 -2
- manim/utils/tex_file_writing.py +45 -45
- manim/utils/tex_templates.py +1 -1
- manim/utils/unit.py +6 -5
- {manim-0.18.1.dist-info → manim-0.19.0.dist-info}/METADATA +16 -9
- manim-0.19.0.dist-info/RECORD +221 -0
- {manim-0.18.1.dist-info → manim-0.19.0.dist-info}/WHEEL +1 -1
- manim-0.18.1.dist-info/RECORD +0 -217
- {manim-0.18.1.dist-info → manim-0.19.0.dist-info}/LICENSE +0 -0
- {manim-0.18.1.dist-info → manim-0.19.0.dist-info}/LICENSE.community +0 -0
- {manim-0.18.1.dist-info → manim-0.19.0.dist-info}/entry_points.txt +0 -0
|
@@ -12,17 +12,16 @@ from manim.utils.docbuild.module_parsing import parse_module_attributes
|
|
|
12
12
|
|
|
13
13
|
if TYPE_CHECKING:
|
|
14
14
|
from sphinx.application import Sphinx
|
|
15
|
-
from typing_extensions import TypeAlias
|
|
16
15
|
|
|
17
16
|
__all__ = ["AliasAttrDocumenter"]
|
|
18
17
|
|
|
19
18
|
|
|
20
|
-
ALIAS_DOCS_DICT, DATA_DICT = parse_module_attributes()
|
|
19
|
+
ALIAS_DOCS_DICT, DATA_DICT, TYPEVAR_DICT = parse_module_attributes()
|
|
21
20
|
ALIAS_LIST = [
|
|
22
21
|
alias_name
|
|
23
22
|
for module_dict in ALIAS_DOCS_DICT.values()
|
|
24
23
|
for category_dict in module_dict.values()
|
|
25
|
-
for alias_name in category_dict
|
|
24
|
+
for alias_name in category_dict
|
|
26
25
|
]
|
|
27
26
|
|
|
28
27
|
|
|
@@ -45,11 +44,12 @@ def smart_replace(base: str, alias: str, substitution: str) -> str:
|
|
|
45
44
|
str
|
|
46
45
|
The new string after the alias substitution.
|
|
47
46
|
"""
|
|
48
|
-
|
|
49
47
|
occurrences = []
|
|
50
48
|
len_alias = len(alias)
|
|
51
49
|
len_base = len(base)
|
|
52
|
-
|
|
50
|
+
|
|
51
|
+
def condition(char: str) -> bool:
|
|
52
|
+
return not char.isalnum() and char != "_"
|
|
53
53
|
|
|
54
54
|
start = 0
|
|
55
55
|
i = 0
|
|
@@ -99,10 +99,11 @@ class AliasAttrDocumenter(Directive):
|
|
|
99
99
|
|
|
100
100
|
def run(self) -> list[nodes.Element]:
|
|
101
101
|
module_name = self.arguments[0]
|
|
102
|
-
# Slice module_name[6:] to remove the "manim." prefix which is
|
|
103
102
|
# not present in the keys of the DICTs
|
|
104
|
-
|
|
105
|
-
|
|
103
|
+
module_name = module_name.removeprefix("manim.")
|
|
104
|
+
module_alias_dict = ALIAS_DOCS_DICT.get(module_name, None)
|
|
105
|
+
module_attrs_list = DATA_DICT.get(module_name, None)
|
|
106
|
+
module_typevars = TYPEVAR_DICT.get(module_name, None)
|
|
106
107
|
|
|
107
108
|
content = nodes.container()
|
|
108
109
|
|
|
@@ -160,6 +161,11 @@ class AliasAttrDocumenter(Directive):
|
|
|
160
161
|
for A in ALIAS_LIST:
|
|
161
162
|
alias_doc = alias_doc.replace(f"`{A}`", f":class:`~.{A}`")
|
|
162
163
|
|
|
164
|
+
# also hyperlink the TypeVars from that module
|
|
165
|
+
if module_typevars is not None:
|
|
166
|
+
for T in module_typevars:
|
|
167
|
+
alias_doc = alias_doc.replace(f"`{T}`", f":class:`{T}`")
|
|
168
|
+
|
|
163
169
|
# Add all the lines with 4 spaces behind, to consider all the
|
|
164
170
|
# documentation as a paragraph INSIDE the `.. class::` block
|
|
165
171
|
doc_lines = alias_doc.split("\n")
|
|
@@ -171,6 +177,37 @@ class AliasAttrDocumenter(Directive):
|
|
|
171
177
|
self.state.nested_parse(unparsed, 0, alias_container)
|
|
172
178
|
category_alias_container += alias_container
|
|
173
179
|
|
|
180
|
+
# then add the module TypeVars section
|
|
181
|
+
if module_typevars is not None:
|
|
182
|
+
module_typevars_section = nodes.section(ids=[f"{module_name}.typevars"])
|
|
183
|
+
content += module_typevars_section
|
|
184
|
+
|
|
185
|
+
# Use a rubric (title-like), just like in `module.rst`
|
|
186
|
+
module_typevars_section += nodes.rubric(text="TypeVar's")
|
|
187
|
+
|
|
188
|
+
# name: str
|
|
189
|
+
# definition: TypeVarDict = dict[str, str]
|
|
190
|
+
for name, definition in module_typevars.items():
|
|
191
|
+
# Using the `.. class::` directive is CRUCIAL, since
|
|
192
|
+
# function/method parameters are always annotated via
|
|
193
|
+
# classes - therefore Sphinx expects a class
|
|
194
|
+
unparsed = ViewList(
|
|
195
|
+
[
|
|
196
|
+
f".. class:: {name}",
|
|
197
|
+
"",
|
|
198
|
+
" .. parsed-literal::",
|
|
199
|
+
"",
|
|
200
|
+
f" {definition}",
|
|
201
|
+
"",
|
|
202
|
+
]
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
# Parse the reST text into a fresh container
|
|
206
|
+
# https://www.sphinx-doc.org/en/master/extdev/markupapi.html#parsing-directive-content-as-rest
|
|
207
|
+
typevar_container = nodes.container()
|
|
208
|
+
self.state.nested_parse(unparsed, 0, typevar_container)
|
|
209
|
+
module_typevars_section += typevar_container
|
|
210
|
+
|
|
174
211
|
# Then, add the traditional "Module Attributes" section
|
|
175
212
|
if module_attrs_list is not None:
|
|
176
213
|
module_attrs_section = nodes.section(ids=[f"{module_name}.data"])
|
|
@@ -26,7 +26,9 @@ class ManimColorModuleDocumenter(Directive):
|
|
|
26
26
|
has_content = True
|
|
27
27
|
|
|
28
28
|
def add_directive_header(self, sig: str) -> None:
|
|
29
|
-
|
|
29
|
+
# TODO: The Directive class has no method named
|
|
30
|
+
# add_directive_header.
|
|
31
|
+
super().add_directive_header(sig) # type: ignore[misc]
|
|
30
32
|
|
|
31
33
|
def run(self) -> list[nodes.Element]:
|
|
32
34
|
module_name = self.arguments[0]
|
|
@@ -37,8 +39,8 @@ class ManimColorModuleDocumenter(Directive):
|
|
|
37
39
|
except ImportError:
|
|
38
40
|
return [
|
|
39
41
|
nodes.error(
|
|
40
|
-
None,
|
|
41
|
-
nodes.paragraph(text="Failed to import module '
|
|
42
|
+
None, # type: ignore[arg-type]
|
|
43
|
+
nodes.paragraph(text=f"Failed to import module '{module_name}'"),
|
|
42
44
|
)
|
|
43
45
|
]
|
|
44
46
|
|
|
@@ -53,13 +55,13 @@ class ManimColorModuleDocumenter(Directive):
|
|
|
53
55
|
|
|
54
56
|
# Create header rows for the table
|
|
55
57
|
thead = nodes.thead()
|
|
56
|
-
|
|
58
|
+
header_row = nodes.row()
|
|
57
59
|
for _ in range(num_color_cols):
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
thead +=
|
|
60
|
+
header_col1 = nodes.paragraph(text="Color Name")
|
|
61
|
+
header_col2 = nodes.paragraph(text="RGB Hex Code")
|
|
62
|
+
header_row += nodes.entry("", header_col1)
|
|
63
|
+
header_row += nodes.entry("", header_col2)
|
|
64
|
+
thead += header_row
|
|
63
65
|
tgroup += thead
|
|
64
66
|
|
|
65
67
|
color_elements = []
|
|
@@ -69,10 +71,7 @@ class ManimColorModuleDocumenter(Directive):
|
|
|
69
71
|
luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b
|
|
70
72
|
|
|
71
73
|
# Choose the font color based on the background luminance
|
|
72
|
-
if luminance > 0.5
|
|
73
|
-
font_color = "black"
|
|
74
|
-
else:
|
|
75
|
-
font_color = "white"
|
|
74
|
+
font_color = "black" if luminance > 0.5 else "white"
|
|
76
75
|
|
|
77
76
|
color_elements.append((member_name, member_obj.to_hex(), font_color))
|
|
78
77
|
|
|
@@ -82,18 +82,17 @@ from __future__ import annotations
|
|
|
82
82
|
|
|
83
83
|
import csv
|
|
84
84
|
import itertools as it
|
|
85
|
-
import os
|
|
86
85
|
import re
|
|
87
86
|
import shutil
|
|
88
87
|
import sys
|
|
89
88
|
import textwrap
|
|
90
89
|
from pathlib import Path
|
|
91
90
|
from timeit import timeit
|
|
92
|
-
from typing import TYPE_CHECKING, Any
|
|
91
|
+
from typing import TYPE_CHECKING, Any, TypedDict
|
|
93
92
|
|
|
94
93
|
import jinja2
|
|
95
94
|
from docutils import nodes
|
|
96
|
-
from docutils.parsers.rst import Directive, directives
|
|
95
|
+
from docutils.parsers.rst import Directive, directives
|
|
97
96
|
from docutils.statemachine import StringList
|
|
98
97
|
|
|
99
98
|
from manim import QUALITIES
|
|
@@ -102,12 +101,18 @@ from manim import __version__ as manim_version
|
|
|
102
101
|
if TYPE_CHECKING:
|
|
103
102
|
from sphinx.application import Sphinx
|
|
104
103
|
|
|
104
|
+
|
|
105
105
|
__all__ = ["ManimDirective"]
|
|
106
106
|
|
|
107
107
|
|
|
108
108
|
classnamedict: dict[str, int] = {}
|
|
109
109
|
|
|
110
110
|
|
|
111
|
+
class SetupMetadata(TypedDict):
|
|
112
|
+
parallel_read_safe: bool
|
|
113
|
+
parallel_write_safe: bool
|
|
114
|
+
|
|
115
|
+
|
|
111
116
|
class SkipManimNode(nodes.Admonition, nodes.Element):
|
|
112
117
|
"""Auxiliary node class that is used when the ``skip-manim`` tag is present
|
|
113
118
|
or ``.pot`` files are being built.
|
|
@@ -119,13 +124,15 @@ class SkipManimNode(nodes.Admonition, nodes.Element):
|
|
|
119
124
|
|
|
120
125
|
|
|
121
126
|
def visit(self: SkipManimNode, node: nodes.Element, name: str = "") -> None:
|
|
122
|
-
|
|
127
|
+
# TODO: Parent classes don't have a visit_admonition() method.
|
|
128
|
+
self.visit_admonition(node, name) # type: ignore[attr-defined]
|
|
123
129
|
if not isinstance(node[0], nodes.title):
|
|
124
130
|
node.insert(0, nodes.title("skip-manim", "Example Placeholder"))
|
|
125
131
|
|
|
126
132
|
|
|
127
133
|
def depart(self: SkipManimNode, node: nodes.Element) -> None:
|
|
128
|
-
|
|
134
|
+
# TODO: Parent classes don't have a depart_admonition() method.
|
|
135
|
+
self.depart_admonition(node) # type: ignore[attr-defined]
|
|
129
136
|
|
|
130
137
|
|
|
131
138
|
def process_name_list(option_input: str, reference_type: str) -> list[str]:
|
|
@@ -175,7 +182,7 @@ class ManimDirective(Directive):
|
|
|
175
182
|
# Rendering is skipped if the tag skip-manim is present,
|
|
176
183
|
# or if we are making the pot-files
|
|
177
184
|
should_skip = (
|
|
178
|
-
"skip-manim" in self.state.document.settings.env.app.builder.tags
|
|
185
|
+
"skip-manim" in self.state.document.settings.env.app.builder.tags
|
|
179
186
|
or self.state.document.settings.env.app.builder.name == "gettext"
|
|
180
187
|
)
|
|
181
188
|
if should_skip:
|
|
@@ -226,14 +233,10 @@ class ManimDirective(Directive):
|
|
|
226
233
|
+ self.options.get("ref_functions", [])
|
|
227
234
|
+ self.options.get("ref_methods", [])
|
|
228
235
|
)
|
|
229
|
-
if ref_content
|
|
230
|
-
ref_block = "References: " + " ".join(ref_content)
|
|
231
|
-
|
|
232
|
-
else:
|
|
233
|
-
ref_block = ""
|
|
236
|
+
ref_block = "References: " + " ".join(ref_content) if ref_content else ""
|
|
234
237
|
|
|
235
238
|
if "quality" in self.options:
|
|
236
|
-
quality = f
|
|
239
|
+
quality = f"{self.options['quality']}_quality"
|
|
237
240
|
else:
|
|
238
241
|
quality = "example_quality"
|
|
239
242
|
frame_rate = QUALITIES[quality]["frame_rate"]
|
|
@@ -244,13 +247,13 @@ class ManimDirective(Directive):
|
|
|
244
247
|
document = state_machine.document
|
|
245
248
|
|
|
246
249
|
source_file_name = Path(document.attributes["source"])
|
|
247
|
-
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]
|
|
248
251
|
source_rel_dir = source_rel_name.parents[0]
|
|
249
|
-
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]
|
|
250
253
|
if not dest_dir.exists():
|
|
251
254
|
dest_dir.mkdir(parents=True, exist_ok=True)
|
|
252
255
|
|
|
253
|
-
|
|
256
|
+
source_block_in = [
|
|
254
257
|
".. code-block:: python",
|
|
255
258
|
"",
|
|
256
259
|
" from manim import *\n",
|
|
@@ -263,13 +266,13 @@ class ManimDirective(Directive):
|
|
|
263
266
|
"",
|
|
264
267
|
" </pre>",
|
|
265
268
|
]
|
|
266
|
-
source_block = "\n".join(
|
|
269
|
+
source_block = "\n".join(source_block_in)
|
|
267
270
|
|
|
268
|
-
config.media_dir = (Path(setup.confdir) / "media").absolute()
|
|
271
|
+
config.media_dir = (Path(setup.confdir) / "media").absolute() # type: ignore[attr-defined,assignment]
|
|
269
272
|
config.images_dir = "{media_dir}/images"
|
|
270
273
|
config.video_dir = "{media_dir}/videos/{quality}"
|
|
271
274
|
output_file = f"{clsname}-{classnamedict[clsname]}"
|
|
272
|
-
config.assets_dir = Path("_static")
|
|
275
|
+
config.assets_dir = Path("_static") # type: ignore[assignment]
|
|
273
276
|
config.progress_bar = "none"
|
|
274
277
|
config.verbosity = "WARNING"
|
|
275
278
|
|
|
@@ -287,7 +290,7 @@ class ManimDirective(Directive):
|
|
|
287
290
|
if save_as_gif:
|
|
288
291
|
example_config["format"] = "gif"
|
|
289
292
|
|
|
290
|
-
user_code = self.content
|
|
293
|
+
user_code = list(self.content)
|
|
291
294
|
if user_code[0].startswith(">>> "): # check whether block comes from doctest
|
|
292
295
|
user_code = [
|
|
293
296
|
line[4:] for line in user_code if line.startswith((">>> ", "... "))
|
|
@@ -331,7 +334,7 @@ class ManimDirective(Directive):
|
|
|
331
334
|
clsname=clsname,
|
|
332
335
|
clsname_lowercase=clsname.lower(),
|
|
333
336
|
hide_source=hide_source,
|
|
334
|
-
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]
|
|
335
338
|
no_autoplay=no_autoplay,
|
|
336
339
|
output_file=output_file,
|
|
337
340
|
save_last_frame=save_last_frame,
|
|
@@ -350,13 +353,13 @@ class ManimDirective(Directive):
|
|
|
350
353
|
rendering_times_file_path = Path("../rendering_times.csv")
|
|
351
354
|
|
|
352
355
|
|
|
353
|
-
def _write_rendering_stats(scene_name: str, run_time:
|
|
356
|
+
def _write_rendering_stats(scene_name: str, run_time: float, file_name: str) -> None:
|
|
354
357
|
with rendering_times_file_path.open("a") as file:
|
|
355
358
|
csv.writer(file).writerow(
|
|
356
359
|
[
|
|
357
360
|
re.sub(r"^(reference\/)|(manim\.)", "", file_name),
|
|
358
361
|
scene_name,
|
|
359
|
-
"
|
|
362
|
+
f"{run_time:.3f}",
|
|
360
363
|
],
|
|
361
364
|
)
|
|
362
365
|
|
|
@@ -374,9 +377,9 @@ def _log_rendering_times(*args: tuple[Any]) -> None:
|
|
|
374
377
|
data = [row for row in data if row]
|
|
375
378
|
|
|
376
379
|
max_file_length = max(len(row[0]) for row in data)
|
|
377
|
-
for key,
|
|
380
|
+
for key, group_iter in it.groupby(data, key=lambda row: row[0]):
|
|
378
381
|
key = key.ljust(max_file_length + 1, ".")
|
|
379
|
-
group = list(
|
|
382
|
+
group = list(group_iter)
|
|
380
383
|
if len(group) == 1:
|
|
381
384
|
row = group[0]
|
|
382
385
|
print(f"{key}{row[2].rjust(7, '.')}s {row[1]}")
|
|
@@ -395,12 +398,12 @@ def _delete_rendering_times(*args: tuple[Any]) -> None:
|
|
|
395
398
|
rendering_times_file_path.unlink()
|
|
396
399
|
|
|
397
400
|
|
|
398
|
-
def setup(app: Sphinx) ->
|
|
401
|
+
def setup(app: Sphinx) -> SetupMetadata:
|
|
399
402
|
app.add_node(SkipManimNode, html=(visit, depart))
|
|
400
403
|
|
|
401
|
-
setup.app = app
|
|
402
|
-
setup.config = app.config
|
|
403
|
-
setup.confdir = app.confdir
|
|
404
|
+
setup.app = app # type: ignore[attr-defined]
|
|
405
|
+
setup.config = app.config # type: ignore[attr-defined]
|
|
406
|
+
setup.confdir = app.confdir # type: ignore[attr-defined]
|
|
404
407
|
|
|
405
408
|
app.add_directive("manim", ManimDirective)
|
|
406
409
|
|
|
@@ -417,7 +420,10 @@ def setup(app: Sphinx) -> dict[str, Any]:
|
|
|
417
420
|
).strip(),
|
|
418
421
|
)
|
|
419
422
|
|
|
420
|
-
metadata = {
|
|
423
|
+
metadata: SetupMetadata = {
|
|
424
|
+
"parallel_read_safe": False,
|
|
425
|
+
"parallel_write_safe": True,
|
|
426
|
+
}
|
|
421
427
|
return metadata
|
|
422
428
|
|
|
423
429
|
|
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import ast
|
|
6
|
+
import sys
|
|
7
|
+
from ast import Attribute, Name, Subscript
|
|
6
8
|
from pathlib import Path
|
|
9
|
+
from typing import Any
|
|
7
10
|
|
|
8
11
|
from typing_extensions import TypeAlias
|
|
9
12
|
|
|
@@ -26,6 +29,10 @@ ModuleLevelAliasDict: TypeAlias = dict[str, AliasCategoryDict]
|
|
|
26
29
|
classified by category in different `AliasCategoryDict` objects.
|
|
27
30
|
"""
|
|
28
31
|
|
|
32
|
+
ModuleTypeVarDict: TypeAlias = dict[str, str]
|
|
33
|
+
"""Dictionary containing every :class:`TypeVar` defined in a module."""
|
|
34
|
+
|
|
35
|
+
|
|
29
36
|
AliasDocsDict: TypeAlias = dict[str, ModuleLevelAliasDict]
|
|
30
37
|
"""Dictionary which, for every module in Manim, contains documentation
|
|
31
38
|
about their module-level attributes which are explicitly defined as
|
|
@@ -39,17 +46,22 @@ by Sphinx via the ``data`` role, hence the name) which are NOT
|
|
|
39
46
|
explicitly defined as :class:`TypeAlias`.
|
|
40
47
|
"""
|
|
41
48
|
|
|
49
|
+
TypeVarDict: TypeAlias = dict[str, ModuleTypeVarDict]
|
|
50
|
+
"""A dictionary mapping module names to dictionaries of :class:`TypeVar` objects."""
|
|
51
|
+
|
|
42
52
|
ALIAS_DOCS_DICT: AliasDocsDict = {}
|
|
43
53
|
DATA_DICT: DataDict = {}
|
|
54
|
+
TYPEVAR_DICT: TypeVarDict = {}
|
|
44
55
|
|
|
45
56
|
MANIM_ROOT = Path(__file__).resolve().parent.parent.parent
|
|
46
57
|
|
|
47
58
|
# In the following, we will use ``type(xyz) is xyz_type`` instead of
|
|
48
59
|
# isinstance checks to make sure no subclasses of the type pass the
|
|
49
60
|
# check
|
|
61
|
+
# ruff: noqa: E721
|
|
50
62
|
|
|
51
63
|
|
|
52
|
-
def parse_module_attributes() -> tuple[AliasDocsDict, DataDict]:
|
|
64
|
+
def parse_module_attributes() -> tuple[AliasDocsDict, DataDict, TypeVarDict]:
|
|
53
65
|
"""Read all files, generate Abstract Syntax Trees from them, and
|
|
54
66
|
extract useful information about the type aliases defined in the
|
|
55
67
|
files: the category they belong to, their definition and their
|
|
@@ -57,25 +69,30 @@ def parse_module_attributes() -> tuple[AliasDocsDict, DataDict]:
|
|
|
57
69
|
|
|
58
70
|
Returns
|
|
59
71
|
-------
|
|
60
|
-
ALIAS_DOCS_DICT :
|
|
72
|
+
ALIAS_DOCS_DICT : :class:`AliasDocsDict`
|
|
61
73
|
A dictionary containing the information from all the type
|
|
62
|
-
aliases in Manim. See
|
|
74
|
+
aliases in Manim. See :class:`AliasDocsDict` for more information.
|
|
63
75
|
|
|
64
|
-
DATA_DICT :
|
|
76
|
+
DATA_DICT : :class:`DataDict`
|
|
65
77
|
A dictionary containing the names of all DOCUMENTED
|
|
66
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.
|
|
67
83
|
"""
|
|
68
84
|
global ALIAS_DOCS_DICT
|
|
69
85
|
global DATA_DICT
|
|
86
|
+
global TYPEVAR_DICT
|
|
70
87
|
|
|
71
|
-
if ALIAS_DOCS_DICT or DATA_DICT:
|
|
72
|
-
return ALIAS_DOCS_DICT, DATA_DICT
|
|
88
|
+
if ALIAS_DOCS_DICT or DATA_DICT or TYPEVAR_DICT:
|
|
89
|
+
return ALIAS_DOCS_DICT, DATA_DICT, TYPEVAR_DICT
|
|
73
90
|
|
|
74
91
|
for module_path in MANIM_ROOT.rglob("*.py"):
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
module_name = ".".join(
|
|
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)
|
|
79
96
|
|
|
80
97
|
module_content = module_path.read_text(encoding="utf-8")
|
|
81
98
|
|
|
@@ -84,6 +101,9 @@ def parse_module_attributes() -> tuple[AliasDocsDict, DataDict]:
|
|
|
84
101
|
category_dict: AliasCategoryDict | None = None
|
|
85
102
|
alias_info: AliasInfo | None = None
|
|
86
103
|
|
|
104
|
+
# For storing TypeVars
|
|
105
|
+
module_typevars: ModuleTypeVarDict = {}
|
|
106
|
+
|
|
87
107
|
# For storing regular module attributes
|
|
88
108
|
data_list: list[str] = []
|
|
89
109
|
data_name: str | None = None
|
|
@@ -125,8 +145,7 @@ def parse_module_attributes() -> tuple[AliasDocsDict, DataDict]:
|
|
|
125
145
|
and (
|
|
126
146
|
(
|
|
127
147
|
# if TYPE_CHECKING
|
|
128
|
-
type(node.test) is ast.Name
|
|
129
|
-
and node.test.id == "TYPE_CHECKING"
|
|
148
|
+
type(node.test) is ast.Name and node.test.id == "TYPE_CHECKING"
|
|
130
149
|
)
|
|
131
150
|
or (
|
|
132
151
|
# if typing.TYPE_CHECKING
|
|
@@ -137,32 +156,43 @@ def parse_module_attributes() -> tuple[AliasDocsDict, DataDict]:
|
|
|
137
156
|
)
|
|
138
157
|
)
|
|
139
158
|
):
|
|
140
|
-
inner_nodes = node.body
|
|
159
|
+
inner_nodes: list[Any] = node.body
|
|
141
160
|
else:
|
|
142
161
|
inner_nodes = [node]
|
|
143
162
|
|
|
144
163
|
for node in inner_nodes:
|
|
145
|
-
#
|
|
146
|
-
|
|
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 = (
|
|
147
170
|
type(node) is ast.AnnAssign
|
|
148
171
|
and type(node.annotation) is ast.Name
|
|
149
172
|
and node.annotation.id == "TypeAlias"
|
|
150
173
|
and type(node.target) is ast.Name
|
|
151
174
|
and node.value is not None
|
|
152
|
-
)
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
#
|
|
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 # type: ignore[attr-defined]
|
|
181
|
+
definition_node = node.value # type: ignore[attr-defined]
|
|
182
|
+
|
|
183
|
+
# If the definition is a Union, replace with vertical bar notation.
|
|
184
|
+
# Instead of "Union[Type1, Type2]", we'll have "Type1 | Type2".
|
|
156
185
|
if (
|
|
157
|
-
type(
|
|
158
|
-
and type(
|
|
159
|
-
and
|
|
186
|
+
type(definition_node) is ast.Subscript
|
|
187
|
+
and type(definition_node.value) is ast.Name
|
|
188
|
+
and definition_node.value.id == "Union"
|
|
160
189
|
):
|
|
190
|
+
union_elements = definition_node.slice.elts # type: ignore[attr-defined]
|
|
161
191
|
definition = " | ".join(
|
|
162
|
-
ast.unparse(elem) for elem in
|
|
192
|
+
ast.unparse(elem) for elem in union_elements
|
|
163
193
|
)
|
|
164
194
|
else:
|
|
165
|
-
definition = ast.unparse(
|
|
195
|
+
definition = ast.unparse(definition_node)
|
|
166
196
|
|
|
167
197
|
definition = definition.replace("npt.", "")
|
|
168
198
|
if category_dict is None:
|
|
@@ -172,6 +202,19 @@ def parse_module_attributes() -> tuple[AliasDocsDict, DataDict]:
|
|
|
172
202
|
alias_info = category_dict[alias_name]
|
|
173
203
|
continue
|
|
174
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
|
+
|
|
175
218
|
# If here, the node is not a TypeAlias definition
|
|
176
219
|
alias_info = None
|
|
177
220
|
|
|
@@ -179,13 +222,15 @@ def parse_module_attributes() -> tuple[AliasDocsDict, DataDict]:
|
|
|
179
222
|
# Does the assignment have a target of type Name? Then
|
|
180
223
|
# it could be considered a definition of a module attribute.
|
|
181
224
|
if type(node) is ast.AnnAssign:
|
|
182
|
-
target = node.target
|
|
225
|
+
target: Name | Attribute | Subscript | ast.expr | None = node.target
|
|
183
226
|
elif type(node) is ast.Assign and len(node.targets) == 1:
|
|
184
227
|
target = node.targets[0]
|
|
185
228
|
else:
|
|
186
229
|
target = None
|
|
187
230
|
|
|
188
|
-
if type(target) is ast.Name
|
|
231
|
+
if type(target) is ast.Name and not (
|
|
232
|
+
type(node) is ast.Assign and target.id not in module_typevars
|
|
233
|
+
):
|
|
189
234
|
data_name = target.id
|
|
190
235
|
else:
|
|
191
236
|
data_name = None
|
|
@@ -194,5 +239,7 @@ def parse_module_attributes() -> tuple[AliasDocsDict, DataDict]:
|
|
|
194
239
|
ALIAS_DOCS_DICT[module_name] = module_dict
|
|
195
240
|
if len(data_list) > 0:
|
|
196
241
|
DATA_DICT[module_name] = data_list
|
|
242
|
+
if module_typevars:
|
|
243
|
+
TYPEVAR_DICT[module_name] = module_typevars
|
|
197
244
|
|
|
198
|
-
return ALIAS_DOCS_DICT, DATA_DICT
|
|
245
|
+
return ALIAS_DOCS_DICT, DATA_DICT, TYPEVAR_DICT
|
manim/utils/family.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import itertools as it
|
|
4
|
-
from
|
|
4
|
+
from collections.abc import Iterable
|
|
5
5
|
|
|
6
6
|
from ..mobject.mobject import Mobject
|
|
7
7
|
from ..utils.iterables import remove_list_redundancies
|
|
@@ -11,9 +11,9 @@ __all__ = ["extract_mobject_family_members"]
|
|
|
11
11
|
|
|
12
12
|
def extract_mobject_family_members(
|
|
13
13
|
mobjects: Iterable[Mobject],
|
|
14
|
-
use_z_index=False,
|
|
14
|
+
use_z_index: bool = False,
|
|
15
15
|
only_those_with_points: bool = False,
|
|
16
|
-
):
|
|
16
|
+
) -> list[Mobject]:
|
|
17
17
|
"""Returns a list of the types of mobjects and their family members present.
|
|
18
18
|
A "family" in this context refers to a mobject, its submobjects, and their
|
|
19
19
|
submobjects, recursively.
|
manim/utils/family_ops.py
CHANGED
|
@@ -2,20 +2,26 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import itertools as it
|
|
4
4
|
|
|
5
|
+
from manim.mobject.mobject import Mobject
|
|
6
|
+
|
|
5
7
|
__all__ = [
|
|
6
8
|
"extract_mobject_family_members",
|
|
7
9
|
"restructure_list_to_exclude_certain_family_members",
|
|
8
10
|
]
|
|
9
11
|
|
|
10
12
|
|
|
11
|
-
def extract_mobject_family_members(
|
|
13
|
+
def extract_mobject_family_members(
|
|
14
|
+
mobject_list: list[Mobject], only_those_with_points: bool = False
|
|
15
|
+
) -> list[Mobject]:
|
|
12
16
|
result = list(it.chain(*(mob.get_family() for mob in mobject_list)))
|
|
13
17
|
if only_those_with_points:
|
|
14
18
|
result = [mob for mob in result if mob.has_points()]
|
|
15
19
|
return result
|
|
16
20
|
|
|
17
21
|
|
|
18
|
-
def restructure_list_to_exclude_certain_family_members(
|
|
22
|
+
def restructure_list_to_exclude_certain_family_members(
|
|
23
|
+
mobject_list: list[Mobject], to_remove: list[Mobject]
|
|
24
|
+
) -> list[Mobject]:
|
|
19
25
|
"""
|
|
20
26
|
Removes anything in to_remove from mobject_list, but in the event that one of
|
|
21
27
|
the items to be removed is a member of the family of an item in mobject_list,
|
|
@@ -25,10 +31,12 @@ def restructure_list_to_exclude_certain_family_members(mobject_list, to_remove):
|
|
|
25
31
|
but one of its submobjects is removed, e.g. scene.remove(m1), it's useful
|
|
26
32
|
for the list of mobject_list to be edited to contain other submobjects, but not m1.
|
|
27
33
|
"""
|
|
28
|
-
new_list = []
|
|
34
|
+
new_list: list[Mobject] = []
|
|
29
35
|
to_remove = extract_mobject_family_members(to_remove)
|
|
30
36
|
|
|
31
|
-
def add_safe_mobjects_from_list(
|
|
37
|
+
def add_safe_mobjects_from_list(
|
|
38
|
+
list_to_examine: list[Mobject], set_to_remove: set[Mobject]
|
|
39
|
+
) -> None:
|
|
32
40
|
for mob in list_to_examine:
|
|
33
41
|
if mob in set_to_remove:
|
|
34
42
|
continue
|