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,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 # 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".
|
|
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
|
manim/utils/file_ops.py
CHANGED
|
@@ -28,6 +28,8 @@ from shutil import copyfile
|
|
|
28
28
|
from typing import TYPE_CHECKING
|
|
29
29
|
|
|
30
30
|
if TYPE_CHECKING:
|
|
31
|
+
from manim.typing import StrPath
|
|
32
|
+
|
|
31
33
|
from ..scene.scene_file_writer import SceneFileWriter
|
|
32
34
|
|
|
33
35
|
from manim import __version__, config, logger
|
|
@@ -45,7 +47,8 @@ def is_mp4_format() -> bool:
|
|
|
45
47
|
``True`` if format is set as mp4
|
|
46
48
|
|
|
47
49
|
"""
|
|
48
|
-
|
|
50
|
+
val: bool = config["format"] == "mp4"
|
|
51
|
+
return val
|
|
49
52
|
|
|
50
53
|
|
|
51
54
|
def is_gif_format() -> bool:
|
|
@@ -58,7 +61,8 @@ def is_gif_format() -> bool:
|
|
|
58
61
|
``True`` if format is set as gif
|
|
59
62
|
|
|
60
63
|
"""
|
|
61
|
-
|
|
64
|
+
val: bool = config["format"] == "gif"
|
|
65
|
+
return val
|
|
62
66
|
|
|
63
67
|
|
|
64
68
|
def is_webm_format() -> bool:
|
|
@@ -71,7 +75,8 @@ def is_webm_format() -> bool:
|
|
|
71
75
|
``True`` if format is set as webm
|
|
72
76
|
|
|
73
77
|
"""
|
|
74
|
-
|
|
78
|
+
val: bool = config["format"] == "webm"
|
|
79
|
+
return val
|
|
75
80
|
|
|
76
81
|
|
|
77
82
|
def is_mov_format() -> bool:
|
|
@@ -84,7 +89,8 @@ def is_mov_format() -> bool:
|
|
|
84
89
|
``True`` if format is set as mov
|
|
85
90
|
|
|
86
91
|
"""
|
|
87
|
-
|
|
92
|
+
val: bool = config["format"] == "mov"
|
|
93
|
+
return val
|
|
88
94
|
|
|
89
95
|
|
|
90
96
|
def is_png_format() -> bool:
|
|
@@ -97,7 +103,8 @@ def is_png_format() -> bool:
|
|
|
97
103
|
``True`` if format is set as png
|
|
98
104
|
|
|
99
105
|
"""
|
|
100
|
-
|
|
106
|
+
val: bool = config["format"] == "png"
|
|
107
|
+
return val
|
|
101
108
|
|
|
102
109
|
|
|
103
110
|
def write_to_movie() -> bool:
|
|
@@ -124,7 +131,7 @@ def write_to_movie() -> bool:
|
|
|
124
131
|
|
|
125
132
|
def ensure_executable(path_to_exe: Path) -> bool:
|
|
126
133
|
if path_to_exe.parent == Path("."):
|
|
127
|
-
executable = shutil.which(path_to_exe.stem)
|
|
134
|
+
executable: StrPath | None = shutil.which(path_to_exe.stem)
|
|
128
135
|
if executable is None:
|
|
129
136
|
return False
|
|
130
137
|
else:
|
|
@@ -159,7 +166,7 @@ def guarantee_empty_existence(path: Path) -> Path:
|
|
|
159
166
|
|
|
160
167
|
|
|
161
168
|
def seek_full_path_from_defaults(
|
|
162
|
-
file_name:
|
|
169
|
+
file_name: StrPath, default_dir: Path, extensions: list[str]
|
|
163
170
|
) -> Path:
|
|
164
171
|
possible_paths = [Path(file_name).expanduser()]
|
|
165
172
|
possible_paths += [
|
|
@@ -186,10 +193,12 @@ def modify_atime(file_path: str) -> None:
|
|
|
186
193
|
os.utime(file_path, times=(time.time(), Path(file_path).stat().st_mtime))
|
|
187
194
|
|
|
188
195
|
|
|
189
|
-
def open_file(file_path, in_browser=False):
|
|
196
|
+
def open_file(file_path: Path, in_browser: bool = False) -> None:
|
|
190
197
|
current_os = platform.system()
|
|
191
198
|
if current_os == "Windows":
|
|
192
|
-
os.startfile
|
|
199
|
+
# The method os.startfile is only available in Windows,
|
|
200
|
+
# ignoring type error caused by this.
|
|
201
|
+
os.startfile(file_path if not in_browser else file_path.parent) # type: ignore[attr-defined]
|
|
193
202
|
else:
|
|
194
203
|
if current_os == "Linux":
|
|
195
204
|
commands = ["xdg-open"]
|
|
@@ -198,14 +207,15 @@ def open_file(file_path, in_browser=False):
|
|
|
198
207
|
commands = ["cygstart"]
|
|
199
208
|
file_path = file_path if not in_browser else file_path.parent
|
|
200
209
|
elif current_os == "Darwin":
|
|
201
|
-
if
|
|
202
|
-
commands = ["ffplay", "-loglevel", config["ffmpeg_loglevel"].lower()]
|
|
203
|
-
else:
|
|
204
|
-
commands = ["open"] if not in_browser else ["open", "-R"]
|
|
210
|
+
commands = ["open"] if not in_browser else ["open", "-R"]
|
|
205
211
|
else:
|
|
206
212
|
raise OSError("Unable to identify your operating system...")
|
|
207
|
-
|
|
208
|
-
|
|
213
|
+
|
|
214
|
+
# check after so that file path is set correctly
|
|
215
|
+
if config.preview_command:
|
|
216
|
+
commands = [config.preview_command]
|
|
217
|
+
commands.append(str(file_path))
|
|
218
|
+
sp.run(commands)
|
|
209
219
|
|
|
210
220
|
|
|
211
221
|
def open_media_file(file_writer: SceneFileWriter) -> None:
|
|
@@ -248,7 +258,7 @@ def get_template_path() -> Path:
|
|
|
248
258
|
return Path.resolve(Path(__file__).parent.parent / "templates")
|
|
249
259
|
|
|
250
260
|
|
|
251
|
-
def add_import_statement(file: Path):
|
|
261
|
+
def add_import_statement(file: Path) -> None:
|
|
252
262
|
"""Prepends an import statement in a file
|
|
253
263
|
|
|
254
264
|
Parameters
|
manim/utils/hashing.py
CHANGED
|
@@ -14,15 +14,17 @@ from typing import Any
|
|
|
14
14
|
|
|
15
15
|
import numpy as np
|
|
16
16
|
|
|
17
|
-
from manim.
|
|
18
|
-
from manim.camera.camera import Camera
|
|
19
|
-
from manim.mobject.mobject import Mobject
|
|
20
|
-
|
|
21
|
-
from .. import config, logger
|
|
17
|
+
from manim._config import config, logger
|
|
22
18
|
|
|
23
19
|
if typing.TYPE_CHECKING:
|
|
20
|
+
from manim.animation.animation import Animation
|
|
21
|
+
from manim.camera.camera import Camera
|
|
22
|
+
from manim.mobject.mobject import Mobject
|
|
23
|
+
from manim.opengl.opengl_renderer import OpenGLCamera
|
|
24
24
|
from manim.scene.scene import Scene
|
|
25
25
|
|
|
26
|
+
__all__ = ["KEYS_TO_FILTER_OUT", "get_hash_from_play_call", "get_json"]
|
|
27
|
+
|
|
26
28
|
# Sometimes there are elements that are not suitable for hashing (too long or
|
|
27
29
|
# run-dependent). This is used to filter them out.
|
|
28
30
|
KEYS_TO_FILTER_OUT = {
|
|
@@ -221,7 +223,7 @@ class _CustomEncoder(json.JSONEncoder):
|
|
|
221
223
|
# We return the repr and not a list to avoid the JsonEncoder to iterate over it.
|
|
222
224
|
return repr(obj)
|
|
223
225
|
elif hasattr(obj, "__dict__"):
|
|
224
|
-
temp =
|
|
226
|
+
temp = obj.__dict__
|
|
225
227
|
# MappingProxy is scene-caching nightmare. It contains all of the object methods and attributes. We skip it as the mechanism will at some point process the object, but instantiated.
|
|
226
228
|
# Indeed, there is certainly no case where scene-caching will receive only a non instancied object, as this is never used in the library or encouraged to be used user-side.
|
|
227
229
|
if isinstance(temp, MappingProxyType):
|
|
@@ -322,7 +324,7 @@ def get_json(obj: dict):
|
|
|
322
324
|
|
|
323
325
|
def get_hash_from_play_call(
|
|
324
326
|
scene_object: Scene,
|
|
325
|
-
camera_object: Camera,
|
|
327
|
+
camera_object: Camera | OpenGLCamera,
|
|
326
328
|
animations_list: typing.Iterable[Animation],
|
|
327
329
|
current_mobjects_list: typing.Iterable[Mobject],
|
|
328
330
|
) -> str:
|
manim/utils/images.py
CHANGED
|
@@ -9,16 +9,22 @@ __all__ = [
|
|
|
9
9
|
"change_to_rgba_array",
|
|
10
10
|
]
|
|
11
11
|
|
|
12
|
-
from pathlib import Path
|
|
12
|
+
from pathlib import Path, PurePath
|
|
13
|
+
from typing import TYPE_CHECKING
|
|
13
14
|
|
|
14
15
|
import numpy as np
|
|
15
16
|
from PIL import Image
|
|
16
17
|
|
|
18
|
+
from manim.typing import RGBPixelArray
|
|
19
|
+
|
|
17
20
|
from .. import config
|
|
18
21
|
from ..utils.file_ops import seek_full_path_from_defaults
|
|
19
22
|
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
pass
|
|
25
|
+
|
|
20
26
|
|
|
21
|
-
def get_full_raster_image_path(image_file_name: str) -> Path:
|
|
27
|
+
def get_full_raster_image_path(image_file_name: str | PurePath) -> Path:
|
|
22
28
|
return seek_full_path_from_defaults(
|
|
23
29
|
image_file_name,
|
|
24
30
|
default_dir=config.get_dir("assets_dir"),
|
|
@@ -26,7 +32,7 @@ def get_full_raster_image_path(image_file_name: str) -> Path:
|
|
|
26
32
|
)
|
|
27
33
|
|
|
28
34
|
|
|
29
|
-
def get_full_vector_image_path(image_file_name: str) -> Path:
|
|
35
|
+
def get_full_vector_image_path(image_file_name: str | PurePath) -> Path:
|
|
30
36
|
return seek_full_path_from_defaults(
|
|
31
37
|
image_file_name,
|
|
32
38
|
default_dir=config.get_dir("assets_dir"),
|
|
@@ -49,7 +55,7 @@ def invert_image(image: np.array) -> Image:
|
|
|
49
55
|
return Image.fromarray(arr)
|
|
50
56
|
|
|
51
57
|
|
|
52
|
-
def change_to_rgba_array(image, dtype="uint8"):
|
|
58
|
+
def change_to_rgba_array(image: RGBPixelArray, dtype: str = "uint8") -> RGBPixelArray:
|
|
53
59
|
"""Converts an RGB array into RGBA with the alpha value opacity maxed."""
|
|
54
60
|
pa = image
|
|
55
61
|
if len(pa.shape) == 2:
|
manim/utils/ipython_magic.py
CHANGED
|
@@ -3,18 +3,19 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import mimetypes
|
|
6
|
-
import os
|
|
7
6
|
import shutil
|
|
8
7
|
from datetime import datetime
|
|
9
8
|
from pathlib import Path
|
|
10
9
|
from typing import Any
|
|
11
10
|
|
|
12
|
-
from manim import
|
|
11
|
+
from manim import config, logger, tempconfig
|
|
13
12
|
from manim.__main__ import main
|
|
14
13
|
from manim.renderer.shader import shader_program_cache
|
|
15
14
|
|
|
16
15
|
from ..constants import RendererType
|
|
17
16
|
|
|
17
|
+
__all__ = ["ManimMagic"]
|
|
18
|
+
|
|
18
19
|
try:
|
|
19
20
|
from IPython import get_ipython
|
|
20
21
|
from IPython.core.interactiveshell import InteractiveShell
|
|
@@ -33,15 +34,15 @@ else:
|
|
|
33
34
|
class ManimMagic(Magics):
|
|
34
35
|
def __init__(self, shell: InteractiveShell) -> None:
|
|
35
36
|
super().__init__(shell)
|
|
36
|
-
self.rendered_files = {}
|
|
37
|
+
self.rendered_files: dict[Path, Path] = {}
|
|
37
38
|
|
|
38
39
|
@needs_local_scope
|
|
39
40
|
@line_cell_magic
|
|
40
41
|
def manim(
|
|
41
42
|
self,
|
|
42
43
|
line: str,
|
|
43
|
-
cell: str = None,
|
|
44
|
-
local_ns: dict[str, Any] = None,
|
|
44
|
+
cell: str | None = None,
|
|
45
|
+
local_ns: dict[str, Any] | None = None,
|
|
45
46
|
) -> None:
|
|
46
47
|
r"""Render Manim scenes contained in IPython cells.
|
|
47
48
|
Works as a line or cell magic.
|
|
@@ -126,8 +127,9 @@ else:
|
|
|
126
127
|
|
|
127
128
|
modified_args = self.add_additional_args(args)
|
|
128
129
|
args = main(modified_args, standalone_mode=False, prog_name="manim")
|
|
130
|
+
assert isinstance(local_ns, dict)
|
|
129
131
|
with tempconfig(local_ns.get("config", {})):
|
|
130
|
-
config.digest_args(args)
|
|
132
|
+
config.digest_args(args) # type: ignore[arg-type]
|
|
131
133
|
|
|
132
134
|
renderer = None
|
|
133
135
|
if config.renderer == RendererType.OPENGL:
|
|
@@ -166,8 +168,9 @@ else:
|
|
|
166
168
|
shutil.copy(local_path, tmpfile)
|
|
167
169
|
|
|
168
170
|
file_type = mimetypes.guess_type(config["output_file"])[0]
|
|
171
|
+
assert isinstance(file_type, str)
|
|
169
172
|
embed = config["media_embed"]
|
|
170
|
-
if embed
|
|
173
|
+
if not embed:
|
|
171
174
|
# videos need to be embedded when running in google colab.
|
|
172
175
|
# do this automatically in case config.media_embed has not been
|
|
173
176
|
# set explicitly.
|
|
@@ -193,4 +196,7 @@ else:
|
|
|
193
196
|
|
|
194
197
|
|
|
195
198
|
def _generate_file_name() -> str:
|
|
196
|
-
|
|
199
|
+
val: str = (
|
|
200
|
+
config["scene_names"][0] + "@" + datetime.now().strftime("%Y-%m-%d@%H-%M-%S")
|
|
201
|
+
)
|
|
202
|
+
return val
|