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.
- manim/__init__.py +3 -6
- manim/__main__.py +61 -20
- manim/_config/__init__.py +6 -3
- manim/_config/cli_colors.py +16 -8
- manim/_config/default.cfg +1 -3
- manim/_config/logger_utils.py +14 -8
- manim/_config/utils.py +651 -472
- manim/animation/animation.py +152 -5
- manim/animation/composition.py +80 -39
- manim/animation/creation.py +196 -14
- manim/animation/fading.py +5 -9
- manim/animation/indication.py +103 -47
- manim/animation/movement.py +22 -5
- manim/animation/rotation.py +3 -2
- manim/animation/specialized.py +4 -6
- manim/animation/speedmodifier.py +10 -5
- manim/animation/transform.py +4 -5
- manim/animation/transform_matching_parts.py +1 -1
- manim/animation/updaters/mobject_update_utils.py +17 -14
- manim/camera/camera.py +15 -6
- manim/cli/__init__.py +17 -0
- manim/cli/cfg/group.py +70 -44
- manim/cli/checkhealth/checks.py +93 -75
- manim/cli/checkhealth/commands.py +14 -5
- manim/cli/default_group.py +157 -25
- manim/cli/init/commands.py +32 -24
- manim/cli/plugins/commands.py +16 -3
- manim/cli/render/commands.py +72 -60
- manim/cli/render/ease_of_access_options.py +4 -3
- manim/cli/render/global_options.py +51 -15
- manim/cli/render/output_options.py +6 -5
- manim/cli/render/render_options.py +97 -32
- manim/constants.py +65 -19
- manim/gui/gui.py +2 -0
- manim/mobject/frame.py +0 -1
- manim/mobject/geometry/arc.py +112 -78
- manim/mobject/geometry/boolean_ops.py +32 -25
- manim/mobject/geometry/labeled.py +300 -77
- manim/mobject/geometry/line.py +132 -64
- manim/mobject/geometry/polygram.py +126 -30
- manim/mobject/geometry/shape_matchers.py +35 -15
- manim/mobject/geometry/tips.py +38 -29
- manim/mobject/graph.py +414 -133
- manim/mobject/graphing/coordinate_systems.py +126 -64
- manim/mobject/graphing/functions.py +25 -15
- manim/mobject/graphing/number_line.py +24 -10
- manim/mobject/graphing/probability.py +2 -10
- manim/mobject/graphing/scale.py +6 -5
- manim/mobject/matrix.py +17 -19
- manim/mobject/mobject.py +314 -165
- manim/mobject/opengl/opengl_compatibility.py +2 -0
- manim/mobject/opengl/opengl_geometry.py +30 -9
- manim/mobject/opengl/opengl_image_mobject.py +2 -0
- manim/mobject/opengl/opengl_mobject.py +509 -343
- manim/mobject/opengl/opengl_point_cloud_mobject.py +5 -7
- manim/mobject/opengl/opengl_surface.py +3 -2
- manim/mobject/opengl/opengl_three_dimensions.py +2 -0
- manim/mobject/opengl/opengl_vectorized_mobject.py +46 -79
- manim/mobject/svg/brace.py +63 -13
- manim/mobject/svg/svg_mobject.py +4 -3
- manim/mobject/table.py +11 -13
- manim/mobject/text/code_mobject.py +186 -548
- manim/mobject/text/numbers.py +9 -7
- manim/mobject/text/tex_mobject.py +23 -14
- manim/mobject/text/text_mobject.py +70 -24
- manim/mobject/three_d/polyhedra.py +98 -1
- manim/mobject/three_d/three_d_utils.py +4 -4
- manim/mobject/three_d/three_dimensions.py +62 -34
- manim/mobject/types/image_mobject.py +42 -24
- manim/mobject/types/point_cloud_mobject.py +105 -67
- manim/mobject/types/vectorized_mobject.py +496 -228
- manim/mobject/value_tracker.py +5 -4
- manim/mobject/vector_field.py +5 -5
- manim/opengl/__init__.py +3 -3
- manim/plugins/__init__.py +14 -1
- manim/plugins/plugins_flags.py +14 -8
- manim/renderer/cairo_renderer.py +20 -10
- manim/renderer/opengl_renderer.py +21 -23
- manim/renderer/opengl_renderer_window.py +2 -0
- manim/renderer/shader.py +2 -3
- manim/renderer/shader_wrapper.py +5 -2
- manim/renderer/vectorized_mobject_rendering.py +5 -0
- manim/scene/moving_camera_scene.py +23 -0
- manim/scene/scene.py +90 -43
- manim/scene/scene_file_writer.py +316 -165
- manim/scene/section.py +17 -15
- manim/scene/three_d_scene.py +13 -21
- manim/scene/vector_space_scene.py +22 -9
- manim/typing.py +830 -70
- manim/utils/bezier.py +1667 -399
- manim/utils/caching.py +13 -5
- manim/utils/color/AS2700.py +2 -0
- manim/utils/color/BS381.py +3 -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 +3 -0
- manim/utils/color/__init__.py +8 -5
- manim/utils/color/core.py +844 -309
- manim/utils/color/manim_colors.py +7 -9
- manim/utils/commands.py +48 -20
- manim/utils/config_ops.py +18 -13
- manim/utils/debug.py +8 -7
- manim/utils/deprecation.py +90 -40
- manim/utils/docbuild/__init__.py +17 -0
- manim/utils/docbuild/autoaliasattr_directive.py +234 -0
- manim/utils/docbuild/autocolor_directive.py +21 -17
- manim/utils/docbuild/manim_directive.py +50 -35
- manim/utils/docbuild/module_parsing.py +245 -0
- manim/utils/exceptions.py +6 -0
- manim/utils/family.py +5 -3
- manim/utils/family_ops.py +17 -4
- manim/utils/file_ops.py +26 -16
- manim/utils/hashing.py +9 -7
- manim/utils/images.py +10 -4
- manim/utils/ipython_magic.py +14 -8
- manim/utils/iterables.py +161 -119
- manim/utils/module_ops.py +57 -19
- manim/utils/opengl.py +83 -24
- manim/utils/parameter_parsing.py +32 -0
- manim/utils/paths.py +21 -23
- manim/utils/polylabel.py +168 -0
- manim/utils/qhull.py +218 -0
- manim/utils/rate_functions.py +74 -39
- manim/utils/simple_functions.py +24 -15
- manim/utils/sounds.py +7 -1
- manim/utils/space_ops.py +125 -69
- manim/utils/testing/__init__.py +17 -0
- 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 +27 -19
- manim/utils/tex.py +127 -197
- manim/utils/tex_file_writing.py +47 -45
- manim/utils/tex_templates.py +2 -1
- manim/utils/unit.py +6 -5
- {manim-0.18.0.post0.dist-info → manim-0.19.0.dist-info}/LICENSE.community +1 -1
- {manim-0.18.0.post0.dist-info → manim-0.19.0.dist-info}/METADATA +40 -39
- manim-0.19.0.dist-info/RECORD +221 -0
- {manim-0.18.0.post0.dist-info → manim-0.19.0.dist-info}/WHEEL +1 -1
- manim/cli/new/__init__.py +0 -0
- manim/cli/new/group.py +0 -189
- manim/plugins/import_plugins.py +0 -43
- manim-0.18.0.post0.dist-info/RECORD +0 -217
- {manim-0.18.0.post0.dist-info → manim-0.19.0.dist-info}/LICENSE +0 -0
- {manim-0.18.0.post0.dist-info → manim-0.19.0.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"""A directive for documenting type aliases and other module-level attributes."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from docutils import nodes
|
|
8
|
+
from docutils.parsers.rst import Directive
|
|
9
|
+
from docutils.statemachine import ViewList
|
|
10
|
+
|
|
11
|
+
from manim.utils.docbuild.module_parsing import parse_module_attributes
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from sphinx.application import Sphinx
|
|
15
|
+
|
|
16
|
+
__all__ = ["AliasAttrDocumenter"]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
ALIAS_DOCS_DICT, DATA_DICT, TYPEVAR_DICT = parse_module_attributes()
|
|
20
|
+
ALIAS_LIST = [
|
|
21
|
+
alias_name
|
|
22
|
+
for module_dict in ALIAS_DOCS_DICT.values()
|
|
23
|
+
for category_dict in module_dict.values()
|
|
24
|
+
for alias_name in category_dict
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def smart_replace(base: str, alias: str, substitution: str) -> str:
|
|
29
|
+
"""Auxiliary function for substituting type aliases into a base
|
|
30
|
+
string, when there are overlaps between the aliases themselves.
|
|
31
|
+
|
|
32
|
+
Parameters
|
|
33
|
+
----------
|
|
34
|
+
base
|
|
35
|
+
The string in which the type aliases will be located and
|
|
36
|
+
replaced.
|
|
37
|
+
alias
|
|
38
|
+
The substring to be substituted.
|
|
39
|
+
substitution
|
|
40
|
+
The string which will replace every occurrence of ``alias``.
|
|
41
|
+
|
|
42
|
+
Returns
|
|
43
|
+
-------
|
|
44
|
+
str
|
|
45
|
+
The new string after the alias substitution.
|
|
46
|
+
"""
|
|
47
|
+
occurrences = []
|
|
48
|
+
len_alias = len(alias)
|
|
49
|
+
len_base = len(base)
|
|
50
|
+
|
|
51
|
+
def condition(char: str) -> bool:
|
|
52
|
+
return not char.isalnum() and char != "_"
|
|
53
|
+
|
|
54
|
+
start = 0
|
|
55
|
+
i = 0
|
|
56
|
+
while True:
|
|
57
|
+
i = base.find(alias, start)
|
|
58
|
+
if i == -1:
|
|
59
|
+
break
|
|
60
|
+
if (i == 0 or condition(base[i - 1])) and (
|
|
61
|
+
i + len_alias == len_base or condition(base[i + len_alias])
|
|
62
|
+
):
|
|
63
|
+
occurrences.append(i)
|
|
64
|
+
start = i + len_alias
|
|
65
|
+
|
|
66
|
+
for o in occurrences[::-1]:
|
|
67
|
+
base = base[:o] + substitution + base[o + len_alias :]
|
|
68
|
+
|
|
69
|
+
return base
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def setup(app: Sphinx) -> None:
|
|
73
|
+
app.add_directive("autoaliasattr", AliasAttrDocumenter)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class AliasAttrDocumenter(Directive):
|
|
77
|
+
"""Directive which replaces Sphinx's Autosummary for module-level
|
|
78
|
+
attributes: instead, it manually crafts a new "Type Aliases"
|
|
79
|
+
section, where all the module-level attributes which are explicitly
|
|
80
|
+
annotated as :class:`TypeAlias` are considered as such, for their
|
|
81
|
+
use all around the Manim docs.
|
|
82
|
+
|
|
83
|
+
These type aliases are separated from the "regular" module-level
|
|
84
|
+
attributes, which get their traditional "Module Attributes"
|
|
85
|
+
section autogenerated with Sphinx's Autosummary under "Type
|
|
86
|
+
Aliases".
|
|
87
|
+
|
|
88
|
+
See ``docs/source/_templates/autosummary/module.rst`` to watch
|
|
89
|
+
this directive in action.
|
|
90
|
+
|
|
91
|
+
See :func:`~.parse_module_attributes` for more information on how
|
|
92
|
+
the modules are parsed to obtain the :class:`TypeAlias` information
|
|
93
|
+
and separate it from the other attributes.
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
objtype = "autoaliasattr"
|
|
97
|
+
required_arguments = 1
|
|
98
|
+
has_content = True
|
|
99
|
+
|
|
100
|
+
def run(self) -> list[nodes.Element]:
|
|
101
|
+
module_name = self.arguments[0]
|
|
102
|
+
# not present in the keys of the DICTs
|
|
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)
|
|
107
|
+
|
|
108
|
+
content = nodes.container()
|
|
109
|
+
|
|
110
|
+
# Add "Type Aliases" section
|
|
111
|
+
if module_alias_dict is not None:
|
|
112
|
+
module_alias_section = nodes.section(ids=[f"{module_name}.alias"])
|
|
113
|
+
content += module_alias_section
|
|
114
|
+
|
|
115
|
+
# Use a rubric (title-like), just like in `module.rst`
|
|
116
|
+
module_alias_section += nodes.rubric(text="Type Aliases")
|
|
117
|
+
|
|
118
|
+
# category_name: str
|
|
119
|
+
# category_dict: AliasCategoryDict = dict[str, AliasInfo]
|
|
120
|
+
for category_name, category_dict in module_alias_dict.items():
|
|
121
|
+
category_section = nodes.section(
|
|
122
|
+
ids=[category_name.lower().replace(" ", "_")]
|
|
123
|
+
)
|
|
124
|
+
module_alias_section += category_section
|
|
125
|
+
# category_name can be possibly "" for uncategorized aliases
|
|
126
|
+
if category_name:
|
|
127
|
+
category_section += nodes.title(text=category_name)
|
|
128
|
+
|
|
129
|
+
category_alias_container = nodes.container()
|
|
130
|
+
category_section += category_alias_container
|
|
131
|
+
|
|
132
|
+
# alias_name: str
|
|
133
|
+
# alias_info: AliasInfo = dict[str, str]
|
|
134
|
+
# Contains "definition": str
|
|
135
|
+
# Can possibly contain "doc": str
|
|
136
|
+
for alias_name, alias_info in category_dict.items():
|
|
137
|
+
# Replace all occurrences of type aliases in the
|
|
138
|
+
# definition for automatic cross-referencing!
|
|
139
|
+
alias_def = alias_info["definition"]
|
|
140
|
+
for A in ALIAS_LIST:
|
|
141
|
+
alias_def = smart_replace(alias_def, A, f":class:`~.{A}`")
|
|
142
|
+
|
|
143
|
+
# Using the `.. class::` directive is CRUCIAL, since
|
|
144
|
+
# function/method parameters are always annotated via
|
|
145
|
+
# classes - therefore Sphinx expects a class
|
|
146
|
+
unparsed = ViewList(
|
|
147
|
+
[
|
|
148
|
+
f".. class:: {alias_name}",
|
|
149
|
+
"",
|
|
150
|
+
" .. parsed-literal::",
|
|
151
|
+
"",
|
|
152
|
+
f" {alias_def}",
|
|
153
|
+
"",
|
|
154
|
+
]
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
if "doc" in alias_info:
|
|
158
|
+
# Replace all occurrences of type aliases in
|
|
159
|
+
# the docs for automatic cross-referencing!
|
|
160
|
+
alias_doc = alias_info["doc"]
|
|
161
|
+
for A in ALIAS_LIST:
|
|
162
|
+
alias_doc = alias_doc.replace(f"`{A}`", f":class:`~.{A}`")
|
|
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
|
+
|
|
169
|
+
# Add all the lines with 4 spaces behind, to consider all the
|
|
170
|
+
# documentation as a paragraph INSIDE the `.. class::` block
|
|
171
|
+
doc_lines = alias_doc.split("\n")
|
|
172
|
+
unparsed.extend(ViewList([f" {line}" for line in doc_lines]))
|
|
173
|
+
|
|
174
|
+
# Parse the reST text into a fresh container
|
|
175
|
+
# https://www.sphinx-doc.org/en/master/extdev/markupapi.html#parsing-directive-content-as-rest
|
|
176
|
+
alias_container = nodes.container()
|
|
177
|
+
self.state.nested_parse(unparsed, 0, alias_container)
|
|
178
|
+
category_alias_container += alias_container
|
|
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
|
+
|
|
211
|
+
# Then, add the traditional "Module Attributes" section
|
|
212
|
+
if module_attrs_list is not None:
|
|
213
|
+
module_attrs_section = nodes.section(ids=[f"{module_name}.data"])
|
|
214
|
+
content += module_attrs_section
|
|
215
|
+
|
|
216
|
+
# Use the same rubric (title-like) as in `module.rst`
|
|
217
|
+
module_attrs_section += nodes.rubric(text="Module Attributes")
|
|
218
|
+
# Let Sphinx Autosummary do its thing as always
|
|
219
|
+
# Add all the attribute names with 4 spaces behind, so that
|
|
220
|
+
# they're considered as INSIDE the `.. autosummary::` block
|
|
221
|
+
unparsed = ViewList(
|
|
222
|
+
[
|
|
223
|
+
".. autosummary::",
|
|
224
|
+
*(f" {attr}" for attr in module_attrs_list),
|
|
225
|
+
]
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
# Parse the reST text into a fresh container
|
|
229
|
+
# https://www.sphinx-doc.org/en/master/extdev/markupapi.html#parsing-directive-content-as-rest
|
|
230
|
+
data_container = nodes.container()
|
|
231
|
+
self.state.nested_parse(unparsed, 0, data_container)
|
|
232
|
+
module_attrs_section += data_container
|
|
233
|
+
|
|
234
|
+
return [content]
|
|
@@ -1,13 +1,20 @@
|
|
|
1
|
+
"""A directive for documenting colors in Manim."""
|
|
2
|
+
|
|
1
3
|
from __future__ import annotations
|
|
2
4
|
|
|
3
5
|
import inspect
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
4
7
|
|
|
5
8
|
from docutils import nodes
|
|
6
9
|
from docutils.parsers.rst import Directive
|
|
7
|
-
from sphinx.application import Sphinx
|
|
8
10
|
|
|
9
11
|
from manim import ManimColor
|
|
10
12
|
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from sphinx.application import Sphinx
|
|
15
|
+
|
|
16
|
+
__all__ = ["ManimColorModuleDocumenter"]
|
|
17
|
+
|
|
11
18
|
|
|
12
19
|
def setup(app: Sphinx) -> None:
|
|
13
20
|
app.add_directive("automanimcolormodule", ManimColorModuleDocumenter)
|
|
@@ -19,11 +26,11 @@ class ManimColorModuleDocumenter(Directive):
|
|
|
19
26
|
has_content = True
|
|
20
27
|
|
|
21
28
|
def add_directive_header(self, sig: str) -> None:
|
|
22
|
-
|
|
29
|
+
# TODO: The Directive class has no method named
|
|
30
|
+
# add_directive_header.
|
|
31
|
+
super().add_directive_header(sig) # type: ignore[misc]
|
|
23
32
|
|
|
24
|
-
def run(
|
|
25
|
-
self,
|
|
26
|
-
) -> None:
|
|
33
|
+
def run(self) -> list[nodes.Element]:
|
|
27
34
|
module_name = self.arguments[0]
|
|
28
35
|
try:
|
|
29
36
|
import importlib
|
|
@@ -32,8 +39,8 @@ class ManimColorModuleDocumenter(Directive):
|
|
|
32
39
|
except ImportError:
|
|
33
40
|
return [
|
|
34
41
|
nodes.error(
|
|
35
|
-
None,
|
|
36
|
-
nodes.paragraph(text="Failed to import module '
|
|
42
|
+
None, # type: ignore[arg-type]
|
|
43
|
+
nodes.paragraph(text=f"Failed to import module '{module_name}'"),
|
|
37
44
|
)
|
|
38
45
|
]
|
|
39
46
|
|
|
@@ -48,13 +55,13 @@ class ManimColorModuleDocumenter(Directive):
|
|
|
48
55
|
|
|
49
56
|
# Create header rows for the table
|
|
50
57
|
thead = nodes.thead()
|
|
51
|
-
|
|
58
|
+
header_row = nodes.row()
|
|
52
59
|
for _ in range(num_color_cols):
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
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
|
|
58
65
|
tgroup += thead
|
|
59
66
|
|
|
60
67
|
color_elements = []
|
|
@@ -64,10 +71,7 @@ class ManimColorModuleDocumenter(Directive):
|
|
|
64
71
|
luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b
|
|
65
72
|
|
|
66
73
|
# Choose the font color based on the background luminance
|
|
67
|
-
if luminance > 0.5
|
|
68
|
-
font_color = "black"
|
|
69
|
-
else:
|
|
70
|
-
font_color = "white"
|
|
74
|
+
font_color = "black" if luminance > 0.5 else "white"
|
|
71
75
|
|
|
72
76
|
color_elements.append((member_name, member_obj.to_hex(), font_color))
|
|
73
77
|
|
|
@@ -77,27 +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
88
|
import textwrap
|
|
89
89
|
from pathlib import Path
|
|
90
90
|
from timeit import timeit
|
|
91
|
+
from typing import TYPE_CHECKING, Any, TypedDict
|
|
91
92
|
|
|
92
93
|
import jinja2
|
|
93
94
|
from docutils import nodes
|
|
94
|
-
from docutils.parsers.rst import Directive, directives
|
|
95
|
+
from docutils.parsers.rst import Directive, directives
|
|
95
96
|
from docutils.statemachine import StringList
|
|
96
97
|
|
|
97
98
|
from manim import QUALITIES
|
|
98
99
|
from manim import __version__ as manim_version
|
|
99
100
|
|
|
100
|
-
|
|
101
|
+
if TYPE_CHECKING:
|
|
102
|
+
from sphinx.application import Sphinx
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
__all__ = ["ManimDirective"]
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
classnamedict: dict[str, int] = {}
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class SetupMetadata(TypedDict):
|
|
112
|
+
parallel_read_safe: bool
|
|
113
|
+
parallel_write_safe: bool
|
|
101
114
|
|
|
102
115
|
|
|
103
116
|
class SkipManimNode(nodes.Admonition, nodes.Element):
|
|
@@ -110,14 +123,16 @@ class SkipManimNode(nodes.Admonition, nodes.Element):
|
|
|
110
123
|
pass
|
|
111
124
|
|
|
112
125
|
|
|
113
|
-
def visit(self, node, name=""):
|
|
114
|
-
|
|
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]
|
|
115
129
|
if not isinstance(node[0], nodes.title):
|
|
116
130
|
node.insert(0, nodes.title("skip-manim", "Example Placeholder"))
|
|
117
131
|
|
|
118
132
|
|
|
119
|
-
def depart(self, node):
|
|
120
|
-
|
|
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]
|
|
121
136
|
|
|
122
137
|
|
|
123
138
|
def process_name_list(option_input: str, reference_type: str) -> list[str]:
|
|
@@ -143,6 +158,7 @@ class ManimDirective(Directive):
|
|
|
143
158
|
|
|
144
159
|
See the module docstring for documentation.
|
|
145
160
|
"""
|
|
161
|
+
|
|
146
162
|
has_content = True
|
|
147
163
|
required_arguments = 1
|
|
148
164
|
optional_arguments = 0
|
|
@@ -162,11 +178,11 @@ class ManimDirective(Directive):
|
|
|
162
178
|
}
|
|
163
179
|
final_argument_whitespace = True
|
|
164
180
|
|
|
165
|
-
def run(self):
|
|
181
|
+
def run(self) -> list[nodes.Element]:
|
|
166
182
|
# Rendering is skipped if the tag skip-manim is present,
|
|
167
183
|
# or if we are making the pot-files
|
|
168
184
|
should_skip = (
|
|
169
|
-
"skip-manim" in self.state.document.settings.env.app.builder.tags
|
|
185
|
+
"skip-manim" in self.state.document.settings.env.app.builder.tags
|
|
170
186
|
or self.state.document.settings.env.app.builder.name == "gettext"
|
|
171
187
|
)
|
|
172
188
|
if should_skip:
|
|
@@ -217,14 +233,10 @@ class ManimDirective(Directive):
|
|
|
217
233
|
+ self.options.get("ref_functions", [])
|
|
218
234
|
+ self.options.get("ref_methods", [])
|
|
219
235
|
)
|
|
220
|
-
if ref_content
|
|
221
|
-
ref_block = "References: " + " ".join(ref_content)
|
|
222
|
-
|
|
223
|
-
else:
|
|
224
|
-
ref_block = ""
|
|
236
|
+
ref_block = "References: " + " ".join(ref_content) if ref_content else ""
|
|
225
237
|
|
|
226
238
|
if "quality" in self.options:
|
|
227
|
-
quality = f
|
|
239
|
+
quality = f"{self.options['quality']}_quality"
|
|
228
240
|
else:
|
|
229
241
|
quality = "example_quality"
|
|
230
242
|
frame_rate = QUALITIES[quality]["frame_rate"]
|
|
@@ -235,13 +247,13 @@ class ManimDirective(Directive):
|
|
|
235
247
|
document = state_machine.document
|
|
236
248
|
|
|
237
249
|
source_file_name = Path(document.attributes["source"])
|
|
238
|
-
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]
|
|
239
251
|
source_rel_dir = source_rel_name.parents[0]
|
|
240
|
-
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]
|
|
241
253
|
if not dest_dir.exists():
|
|
242
254
|
dest_dir.mkdir(parents=True, exist_ok=True)
|
|
243
255
|
|
|
244
|
-
|
|
256
|
+
source_block_in = [
|
|
245
257
|
".. code-block:: python",
|
|
246
258
|
"",
|
|
247
259
|
" from manim import *\n",
|
|
@@ -254,13 +266,13 @@ class ManimDirective(Directive):
|
|
|
254
266
|
"",
|
|
255
267
|
" </pre>",
|
|
256
268
|
]
|
|
257
|
-
source_block = "\n".join(
|
|
269
|
+
source_block = "\n".join(source_block_in)
|
|
258
270
|
|
|
259
|
-
config.media_dir = (Path(setup.confdir) / "media").absolute()
|
|
271
|
+
config.media_dir = (Path(setup.confdir) / "media").absolute() # type: ignore[attr-defined,assignment]
|
|
260
272
|
config.images_dir = "{media_dir}/images"
|
|
261
273
|
config.video_dir = "{media_dir}/videos/{quality}"
|
|
262
274
|
output_file = f"{clsname}-{classnamedict[clsname]}"
|
|
263
|
-
config.assets_dir = Path("_static")
|
|
275
|
+
config.assets_dir = Path("_static") # type: ignore[assignment]
|
|
264
276
|
config.progress_bar = "none"
|
|
265
277
|
config.verbosity = "WARNING"
|
|
266
278
|
|
|
@@ -278,7 +290,7 @@ class ManimDirective(Directive):
|
|
|
278
290
|
if save_as_gif:
|
|
279
291
|
example_config["format"] = "gif"
|
|
280
292
|
|
|
281
|
-
user_code = self.content
|
|
293
|
+
user_code = list(self.content)
|
|
282
294
|
if user_code[0].startswith(">>> "): # check whether block comes from doctest
|
|
283
295
|
user_code = [
|
|
284
296
|
line[4:] for line in user_code if line.startswith((">>> ", "... "))
|
|
@@ -322,7 +334,7 @@ class ManimDirective(Directive):
|
|
|
322
334
|
clsname=clsname,
|
|
323
335
|
clsname_lowercase=clsname.lower(),
|
|
324
336
|
hide_source=hide_source,
|
|
325
|
-
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]
|
|
326
338
|
no_autoplay=no_autoplay,
|
|
327
339
|
output_file=output_file,
|
|
328
340
|
save_last_frame=save_last_frame,
|
|
@@ -341,18 +353,18 @@ class ManimDirective(Directive):
|
|
|
341
353
|
rendering_times_file_path = Path("../rendering_times.csv")
|
|
342
354
|
|
|
343
355
|
|
|
344
|
-
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:
|
|
345
357
|
with rendering_times_file_path.open("a") as file:
|
|
346
358
|
csv.writer(file).writerow(
|
|
347
359
|
[
|
|
348
360
|
re.sub(r"^(reference\/)|(manim\.)", "", file_name),
|
|
349
361
|
scene_name,
|
|
350
|
-
"
|
|
362
|
+
f"{run_time:.3f}",
|
|
351
363
|
],
|
|
352
364
|
)
|
|
353
365
|
|
|
354
366
|
|
|
355
|
-
def _log_rendering_times(*args):
|
|
367
|
+
def _log_rendering_times(*args: tuple[Any]) -> None:
|
|
356
368
|
if rendering_times_file_path.exists():
|
|
357
369
|
with rendering_times_file_path.open() as file:
|
|
358
370
|
data = list(csv.reader(file))
|
|
@@ -365,9 +377,9 @@ def _log_rendering_times(*args):
|
|
|
365
377
|
data = [row for row in data if row]
|
|
366
378
|
|
|
367
379
|
max_file_length = max(len(row[0]) for row in data)
|
|
368
|
-
for key,
|
|
380
|
+
for key, group_iter in it.groupby(data, key=lambda row: row[0]):
|
|
369
381
|
key = key.ljust(max_file_length + 1, ".")
|
|
370
|
-
group = list(
|
|
382
|
+
group = list(group_iter)
|
|
371
383
|
if len(group) == 1:
|
|
372
384
|
row = group[0]
|
|
373
385
|
print(f"{key}{row[2].rjust(7, '.')}s {row[1]}")
|
|
@@ -377,21 +389,21 @@ def _log_rendering_times(*args):
|
|
|
377
389
|
f"{key}{f'{time_sum:.3f}'.rjust(7, '.')}s => {len(group)} EXAMPLES",
|
|
378
390
|
)
|
|
379
391
|
for row in group:
|
|
380
|
-
print(f"{' '*
|
|
392
|
+
print(f"{' ' * max_file_length} {row[2].rjust(7)}s {row[1]}")
|
|
381
393
|
print("")
|
|
382
394
|
|
|
383
395
|
|
|
384
|
-
def _delete_rendering_times(*args):
|
|
396
|
+
def _delete_rendering_times(*args: tuple[Any]) -> None:
|
|
385
397
|
if rendering_times_file_path.exists():
|
|
386
398
|
rendering_times_file_path.unlink()
|
|
387
399
|
|
|
388
400
|
|
|
389
|
-
def setup(app):
|
|
401
|
+
def setup(app: Sphinx) -> SetupMetadata:
|
|
390
402
|
app.add_node(SkipManimNode, html=(visit, depart))
|
|
391
403
|
|
|
392
|
-
setup.app = app
|
|
393
|
-
setup.config = app.config
|
|
394
|
-
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]
|
|
395
407
|
|
|
396
408
|
app.add_directive("manim", ManimDirective)
|
|
397
409
|
|
|
@@ -408,7 +420,10 @@ def setup(app):
|
|
|
408
420
|
).strip(),
|
|
409
421
|
)
|
|
410
422
|
|
|
411
|
-
metadata = {
|
|
423
|
+
metadata: SetupMetadata = {
|
|
424
|
+
"parallel_read_safe": False,
|
|
425
|
+
"parallel_write_safe": True,
|
|
426
|
+
}
|
|
412
427
|
return metadata
|
|
413
428
|
|
|
414
429
|
|