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.
- manim/__init__.py +11 -6
- manim/__main__.py +62 -19
- manim/_config/__init__.py +10 -9
- manim/_config/cli_colors.py +26 -9
- manim/_config/default.cfg +1 -3
- manim/_config/logger_utils.py +23 -13
- manim/_config/utils.py +662 -468
- manim/animation/animation.py +164 -18
- manim/animation/changing.py +34 -23
- manim/animation/composition.py +265 -67
- manim/animation/creation.py +208 -26
- manim/animation/fading.py +16 -18
- manim/animation/growing.py +35 -15
- manim/animation/indication.py +150 -76
- manim/animation/movement.py +56 -22
- manim/animation/numbers.py +64 -6
- manim/animation/rotation.py +78 -7
- manim/animation/specialized.py +6 -7
- manim/animation/speedmodifier.py +13 -10
- manim/animation/transform.py +14 -11
- manim/animation/transform_matching_parts.py +3 -4
- manim/animation/updaters/mobject_update_utils.py +152 -30
- manim/animation/updaters/update.py +10 -7
- manim/camera/camera.py +182 -118
- manim/camera/mapping_camera.py +34 -3
- manim/camera/moving_camera.py +95 -74
- manim/camera/multi_camera.py +23 -15
- manim/camera/three_d_camera.py +70 -52
- manim/cli/__init__.py +17 -0
- manim/cli/cfg/group.py +76 -44
- manim/cli/checkhealth/checks.py +192 -0
- manim/cli/checkhealth/commands.py +90 -0
- manim/cli/default_group.py +158 -25
- manim/cli/init/commands.py +33 -25
- 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 +59 -17
- manim/cli/render/output_options.py +6 -5
- manim/cli/render/render_options.py +98 -33
- manim/constants.py +109 -59
- manim/data_structures.py +31 -0
- manim/mobject/frame.py +8 -5
- manim/mobject/geometry/__init__.py +1 -0
- manim/mobject/geometry/arc.py +277 -135
- manim/mobject/geometry/boolean_ops.py +32 -31
- manim/mobject/geometry/labeled.py +376 -0
- manim/mobject/geometry/line.py +192 -87
- manim/mobject/geometry/polygram.py +224 -58
- manim/mobject/geometry/shape_matchers.py +61 -25
- manim/mobject/geometry/tips.py +122 -48
- manim/mobject/graph.py +1027 -419
- manim/mobject/graphing/coordinate_systems.py +533 -278
- manim/mobject/graphing/functions.py +53 -32
- manim/mobject/graphing/number_line.py +123 -65
- manim/mobject/graphing/probability.py +88 -62
- manim/mobject/graphing/scale.py +33 -19
- manim/mobject/logo.py +118 -28
- manim/mobject/matrix.py +87 -83
- manim/mobject/mobject.py +912 -442
- manim/mobject/opengl/dot_cloud.py +16 -5
- manim/mobject/opengl/opengl_compatibility.py +4 -2
- manim/mobject/opengl/opengl_geometry.py +254 -153
- manim/mobject/opengl/opengl_image_mobject.py +3 -1
- manim/mobject/opengl/opengl_mobject.py +779 -482
- manim/mobject/opengl/opengl_point_cloud_mobject.py +41 -14
- manim/mobject/opengl/opengl_surface.py +14 -92
- manim/mobject/opengl/opengl_three_dimensions.py +12 -8
- manim/mobject/opengl/opengl_vectorized_mobject.py +98 -100
- manim/mobject/svg/brace.py +173 -41
- manim/mobject/svg/svg_mobject.py +139 -53
- manim/mobject/table.py +61 -68
- manim/mobject/text/code_mobject.py +193 -539
- manim/mobject/text/numbers.py +81 -34
- manim/mobject/text/tex_mobject.py +130 -78
- manim/mobject/text/text_mobject.py +288 -164
- manim/mobject/three_d/polyhedra.py +111 -13
- manim/mobject/three_d/three_d_utils.py +17 -8
- manim/mobject/three_d/three_dimensions.py +239 -106
- manim/mobject/types/image_mobject.py +50 -30
- manim/mobject/types/point_cloud_mobject.py +120 -75
- manim/mobject/types/vectorized_mobject.py +841 -408
- manim/mobject/value_tracker.py +105 -38
- manim/mobject/vector_field.py +50 -31
- manim/opengl/__init__.py +3 -3
- manim/plugins/__init__.py +14 -1
- manim/plugins/plugins_flags.py +10 -14
- manim/renderer/cairo_renderer.py +65 -50
- manim/renderer/opengl_renderer.py +89 -69
- manim/renderer/opengl_renderer_window.py +39 -18
- manim/renderer/shader.py +123 -87
- manim/renderer/shader_wrapper.py +44 -28
- manim/renderer/vectorized_mobject_rendering.py +38 -10
- manim/scene/moving_camera_scene.py +32 -3
- manim/scene/scene.py +507 -242
- manim/scene/scene_file_writer.py +371 -220
- manim/scene/section.py +20 -16
- manim/scene/three_d_scene.py +14 -22
- manim/scene/vector_space_scene.py +223 -129
- manim/scene/zoomed_scene.py +46 -41
- manim/typing.py +990 -0
- manim/utils/bezier.py +1823 -371
- manim/utils/caching.py +12 -5
- manim/utils/color/AS2700.py +236 -0
- manim/utils/color/BS381.py +318 -0
- manim/utils/color/DVIPSNAMES.py +96 -0
- manim/utils/color/SVGNAMES.py +179 -0
- manim/utils/color/X11.py +533 -0
- manim/utils/color/XKCD.py +952 -0
- manim/utils/color/__init__.py +61 -0
- manim/utils/color/core.py +1667 -0
- manim/utils/color/manim_colors.py +218 -0
- manim/utils/commands.py +48 -20
- manim/utils/config_ops.py +39 -19
- manim/utils/debug.py +8 -7
- manim/utils/deprecation.py +86 -39
- manim/utils/docbuild/__init__.py +17 -0
- manim/utils/docbuild/autoaliasattr_directive.py +236 -0
- manim/utils/docbuild/autocolor_directive.py +99 -0
- manim/utils/docbuild/manim_directive.py +94 -41
- 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 +27 -17
- manim/utils/hashing.py +55 -45
- manim/utils/images.py +13 -7
- manim/utils/ipython_magic.py +13 -7
- manim/utils/iterables.py +163 -120
- manim/utils/module_ops.py +66 -24
- manim/utils/opengl.py +77 -24
- manim/utils/parameter_parsing.py +32 -0
- manim/utils/paths.py +30 -33
- manim/utils/polylabel.py +235 -0
- manim/utils/qhull.py +218 -0
- manim/utils/rate_functions.py +98 -32
- manim/utils/simple_functions.py +25 -33
- manim/utils/sounds.py +7 -1
- manim/utils/space_ops.py +188 -115
- 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 +34 -18
- manim/utils/testing/frames_comparison.py +37 -19
- manim/utils/tex.py +130 -198
- manim/utils/tex_file_writing.py +77 -47
- manim/utils/tex_templates.py +2 -1
- manim/utils/unit.py +6 -5
- {manim-0.17.0.dist-info → manim-0.19.1.dist-info}/METADATA +64 -65
- manim-0.19.1.dist-info/RECORD +220 -0
- {manim-0.17.0.dist-info → manim-0.19.1.dist-info}/WHEEL +1 -1
- manim-0.19.1.dist-info/entry_points.txt +3 -0
- {manim-0.17.0.dist-info → manim-0.19.1.dist-info/licenses}/LICENSE.community +1 -1
- manim/cli/new/group.py +0 -189
- manim/communitycolors.py +0 -9
- manim/gui/__init__.py +0 -0
- manim/gui/gui.py +0 -82
- manim/plugins/import_plugins.py +0 -43
- manim/utils/color.py +0 -552
- manim-0.17.0.dist-info/RECORD +0 -206
- manim-0.17.0.dist-info/entry_points.txt +0 -4
- /manim/cli/{new → checkhealth}/__init__.py +0 -0
- {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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 ``{
|
|
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
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
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
|
-
"
|
|
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,
|
|
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(
|
|
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"{' '*
|
|
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(
|
|
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
|
-
|
|
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
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
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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
|