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
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 += [
|
|
@@ -183,13 +190,15 @@ def modify_atime(file_path: str) -> None:
|
|
|
183
190
|
file_path
|
|
184
191
|
The path of the file.
|
|
185
192
|
"""
|
|
186
|
-
os.utime(file_path, times=(time.time(),
|
|
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
|
@@ -2,27 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
import collections
|
|
6
5
|
import copy
|
|
7
6
|
import inspect
|
|
8
7
|
import json
|
|
9
|
-
import typing
|
|
10
8
|
import zlib
|
|
9
|
+
from collections.abc import Callable, Hashable, Iterable, Sequence
|
|
11
10
|
from time import perf_counter
|
|
12
11
|
from types import FunctionType, MappingProxyType, MethodType, ModuleType
|
|
13
|
-
from typing import Any
|
|
12
|
+
from typing import TYPE_CHECKING, Any, overload
|
|
14
13
|
|
|
15
14
|
import numpy as np
|
|
16
15
|
|
|
17
|
-
from manim.
|
|
18
|
-
from manim.camera.camera import Camera
|
|
19
|
-
from manim.mobject.mobject import Mobject
|
|
16
|
+
from manim._config import config, logger
|
|
20
17
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from manim.animation.animation import Animation
|
|
20
|
+
from manim.camera.camera import Camera
|
|
21
|
+
from manim.mobject.mobject import Mobject
|
|
22
|
+
from manim.renderer.opengl_renderer import OpenGLCamera
|
|
24
23
|
from manim.scene.scene import Scene
|
|
25
24
|
|
|
25
|
+
__all__ = ["KEYS_TO_FILTER_OUT", "get_hash_from_play_call", "get_json"]
|
|
26
|
+
|
|
26
27
|
# Sometimes there are elements that are not suitable for hashing (too long or
|
|
27
28
|
# run-dependent). This is used to filter them out.
|
|
28
29
|
KEYS_TO_FILTER_OUT = {
|
|
@@ -53,14 +54,16 @@ class _Memoizer:
|
|
|
53
54
|
THRESHOLD_WARNING = 170_000
|
|
54
55
|
|
|
55
56
|
@classmethod
|
|
56
|
-
def reset_already_processed(cls):
|
|
57
|
+
def reset_already_processed(cls: type[_Memoizer]) -> None:
|
|
57
58
|
cls._already_processed.clear()
|
|
58
59
|
|
|
59
60
|
@classmethod
|
|
60
|
-
def check_already_processed_decorator(
|
|
61
|
+
def check_already_processed_decorator(
|
|
62
|
+
cls: type[_Memoizer], is_method: bool = False
|
|
63
|
+
) -> Callable:
|
|
61
64
|
"""Decorator to handle the arguments that goes through the decorated function.
|
|
62
|
-
Returns
|
|
63
|
-
the decorated function call go ahead.
|
|
65
|
+
Returns the value of ALREADY_PROCESSED_PLACEHOLDER if the obj has been processed,
|
|
66
|
+
or lets the decorated function call go ahead.
|
|
64
67
|
|
|
65
68
|
Parameters
|
|
66
69
|
----------
|
|
@@ -68,7 +71,7 @@ class _Memoizer:
|
|
|
68
71
|
Whether the function passed is a method, by default False.
|
|
69
72
|
"""
|
|
70
73
|
|
|
71
|
-
def layer(func):
|
|
74
|
+
def layer(func: Callable[[Any], Any]) -> Callable:
|
|
72
75
|
# NOTE : There is probably a better way to separate both case when func is
|
|
73
76
|
# a method or a function.
|
|
74
77
|
if is_method:
|
|
@@ -81,9 +84,9 @@ class _Memoizer:
|
|
|
81
84
|
return layer
|
|
82
85
|
|
|
83
86
|
@classmethod
|
|
84
|
-
def check_already_processed(cls, obj: Any) -> Any:
|
|
87
|
+
def check_already_processed(cls: type[_Memoizer], obj: Any) -> Any:
|
|
85
88
|
"""Checks if obj has been already processed. Returns itself if it has not been,
|
|
86
|
-
or the value of
|
|
89
|
+
or the value of ALREADY_PROCESSED_PLACEHOLDER if it has.
|
|
87
90
|
Marks the object as processed in the second case.
|
|
88
91
|
|
|
89
92
|
Parameters
|
|
@@ -100,7 +103,7 @@ class _Memoizer:
|
|
|
100
103
|
return cls._handle_already_processed(obj, lambda x: x)
|
|
101
104
|
|
|
102
105
|
@classmethod
|
|
103
|
-
def mark_as_processed(cls, obj: Any) -> None:
|
|
106
|
+
def mark_as_processed(cls: type[_Memoizer], obj: Any) -> None:
|
|
104
107
|
"""Marks an object as processed.
|
|
105
108
|
|
|
106
109
|
Parameters
|
|
@@ -113,10 +116,10 @@ class _Memoizer:
|
|
|
113
116
|
|
|
114
117
|
@classmethod
|
|
115
118
|
def _handle_already_processed(
|
|
116
|
-
cls,
|
|
117
|
-
obj,
|
|
118
|
-
default_function:
|
|
119
|
-
):
|
|
119
|
+
cls: type[_Memoizer],
|
|
120
|
+
obj: Any,
|
|
121
|
+
default_function: Callable[[Any], Any],
|
|
122
|
+
) -> str | Any:
|
|
120
123
|
if isinstance(
|
|
121
124
|
obj,
|
|
122
125
|
(
|
|
@@ -129,7 +132,7 @@ class _Memoizer:
|
|
|
129
132
|
# It makes no sense (and it'd slower) to memoize objects of these primitive
|
|
130
133
|
# types. Hence, we simply return the object.
|
|
131
134
|
return obj
|
|
132
|
-
if isinstance(obj,
|
|
135
|
+
if isinstance(obj, Hashable):
|
|
133
136
|
try:
|
|
134
137
|
return cls._return(obj, hash, default_function)
|
|
135
138
|
except TypeError:
|
|
@@ -141,11 +144,11 @@ class _Memoizer:
|
|
|
141
144
|
|
|
142
145
|
@classmethod
|
|
143
146
|
def _return(
|
|
144
|
-
cls,
|
|
145
|
-
obj:
|
|
146
|
-
obj_to_membership_sign:
|
|
147
|
-
default_func,
|
|
148
|
-
memoizing=True,
|
|
147
|
+
cls: type[_Memoizer],
|
|
148
|
+
obj: Any,
|
|
149
|
+
obj_to_membership_sign: Callable[[Any], int],
|
|
150
|
+
default_func: Callable[[Any], Any],
|
|
151
|
+
memoizing: bool = True,
|
|
149
152
|
) -> str | Any:
|
|
150
153
|
obj_membership_sign = obj_to_membership_sign(obj)
|
|
151
154
|
if obj_membership_sign in cls._already_processed:
|
|
@@ -171,9 +174,8 @@ class _Memoizer:
|
|
|
171
174
|
|
|
172
175
|
|
|
173
176
|
class _CustomEncoder(json.JSONEncoder):
|
|
174
|
-
def default(self, obj: Any):
|
|
175
|
-
"""
|
|
176
|
-
This method is used to serialize objects to JSON format.
|
|
177
|
+
def default(self, obj: Any) -> Any:
|
|
178
|
+
"""This method is used to serialize objects to JSON format.
|
|
177
179
|
|
|
178
180
|
If obj is a function, then it will return a dict with two keys : 'code', for
|
|
179
181
|
the code source, and 'nonlocals' for all nonlocalsvalues. (including nonlocals
|
|
@@ -207,7 +209,7 @@ class _CustomEncoder(json.JSONEncoder):
|
|
|
207
209
|
del cvardict[i]
|
|
208
210
|
try:
|
|
209
211
|
code = inspect.getsource(obj)
|
|
210
|
-
except OSError:
|
|
212
|
+
except (OSError, TypeError):
|
|
211
213
|
# This happens when rendering videos included in the documentation
|
|
212
214
|
# within doctests and should be replaced by a solution avoiding
|
|
213
215
|
# hash collision (due to the same, empty, code strings) at some point.
|
|
@@ -218,10 +220,10 @@ class _CustomEncoder(json.JSONEncoder):
|
|
|
218
220
|
if obj.size > 1000:
|
|
219
221
|
obj = np.resize(obj, (100, 100))
|
|
220
222
|
return f"TRUNCATED ARRAY: {repr(obj)}"
|
|
221
|
-
# We return the repr and not a list to avoid the
|
|
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):
|
|
@@ -232,11 +234,17 @@ class _CustomEncoder(json.JSONEncoder):
|
|
|
232
234
|
# Serialize it with only the type of the object. You can change this to whatever string when debugging the serialization process.
|
|
233
235
|
return str(type(obj))
|
|
234
236
|
|
|
235
|
-
|
|
237
|
+
@overload
|
|
238
|
+
def _cleaned_iterable(self, iterable: Sequence[Any]) -> list[Any]: ...
|
|
239
|
+
|
|
240
|
+
@overload
|
|
241
|
+
def _cleaned_iterable(self, iterable: dict[Any, Any]) -> dict[Any, Any]: ...
|
|
242
|
+
|
|
243
|
+
def _cleaned_iterable(self, iterable):
|
|
236
244
|
"""Check for circular reference at each iterable that will go through the JSONEncoder, as well as key of the wrong format.
|
|
237
245
|
|
|
238
|
-
If a key with a bad format is found (i.e not a int, string, or float), it gets replaced
|
|
239
|
-
If a circular reference is found within the iterable, it will be replaced by the
|
|
246
|
+
If a key with a bad format is found (i.e not a int, string, or float), it gets replaced by its hash using the same process implemented here.
|
|
247
|
+
If a circular reference is found within the iterable, it will be replaced by the value of ALREADY_PROCESSED_PLACEHOLDER.
|
|
240
248
|
|
|
241
249
|
Parameters
|
|
242
250
|
----------
|
|
@@ -244,10 +252,10 @@ class _CustomEncoder(json.JSONEncoder):
|
|
|
244
252
|
The iterable to check.
|
|
245
253
|
"""
|
|
246
254
|
|
|
247
|
-
def _key_to_hash(key):
|
|
255
|
+
def _key_to_hash(key: Any) -> int:
|
|
248
256
|
return zlib.crc32(json.dumps(key, cls=_CustomEncoder).encode())
|
|
249
257
|
|
|
250
|
-
def _iter_check_list(lst):
|
|
258
|
+
def _iter_check_list(lst: Sequence[Any]) -> list[Any]:
|
|
251
259
|
processed_list = [None] * len(lst)
|
|
252
260
|
for i, el in enumerate(lst):
|
|
253
261
|
el = _Memoizer.check_already_processed(el)
|
|
@@ -260,13 +268,13 @@ class _CustomEncoder(json.JSONEncoder):
|
|
|
260
268
|
processed_list[i] = new_value
|
|
261
269
|
return processed_list
|
|
262
270
|
|
|
263
|
-
def _iter_check_dict(dct):
|
|
271
|
+
def _iter_check_dict(dct: dict[Any, Any]) -> dict[Any, Any]:
|
|
264
272
|
processed_dict = {}
|
|
265
273
|
for k, v in dct.items():
|
|
266
274
|
v = _Memoizer.check_already_processed(v)
|
|
267
275
|
if k in KEYS_TO_FILTER_OUT:
|
|
268
276
|
continue
|
|
269
|
-
# We check if the k is of the right format (
|
|
277
|
+
# We check if the k is of the right format (supported by JSON)
|
|
270
278
|
if not isinstance(k, (str, int, float, bool)) and k is not None:
|
|
271
279
|
k_new = _key_to_hash(k)
|
|
272
280
|
else:
|
|
@@ -284,8 +292,10 @@ class _CustomEncoder(json.JSONEncoder):
|
|
|
284
292
|
return _iter_check_list(iterable)
|
|
285
293
|
elif isinstance(iterable, dict):
|
|
286
294
|
return _iter_check_dict(iterable)
|
|
295
|
+
else:
|
|
296
|
+
raise TypeError("'iterable' is neither an iterable nor a dictionary.")
|
|
287
297
|
|
|
288
|
-
def encode(self, obj: Any):
|
|
298
|
+
def encode(self, obj: Any) -> str:
|
|
289
299
|
"""Overriding of :meth:`JSONEncoder.encode`, to make our own process.
|
|
290
300
|
|
|
291
301
|
Parameters
|
|
@@ -304,7 +314,7 @@ class _CustomEncoder(json.JSONEncoder):
|
|
|
304
314
|
return super().encode(obj)
|
|
305
315
|
|
|
306
316
|
|
|
307
|
-
def get_json(obj:
|
|
317
|
+
def get_json(obj: Any) -> str:
|
|
308
318
|
"""Recursively serialize `object` to JSON using the :class:`CustomEncoder` class.
|
|
309
319
|
|
|
310
320
|
Parameters
|
|
@@ -322,9 +332,9 @@ def get_json(obj: dict):
|
|
|
322
332
|
|
|
323
333
|
def get_hash_from_play_call(
|
|
324
334
|
scene_object: Scene,
|
|
325
|
-
camera_object: Camera,
|
|
326
|
-
animations_list:
|
|
327
|
-
current_mobjects_list:
|
|
335
|
+
camera_object: Camera | OpenGLCamera,
|
|
336
|
+
animations_list: Iterable[Animation],
|
|
337
|
+
current_mobjects_list: Iterable[Mobject],
|
|
328
338
|
) -> str:
|
|
329
339
|
"""Take the list of animations and a list of mobjects and output their hashes. This is meant to be used for `scene.play` function.
|
|
330
340
|
|
manim/utils/images.py
CHANGED
|
@@ -9,7 +9,8 @@ __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
|
|
@@ -17,8 +18,13 @@ from PIL import Image
|
|
|
17
18
|
from .. import config
|
|
18
19
|
from ..utils.file_ops import seek_full_path_from_defaults
|
|
19
20
|
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
from collections.abc import Sequence
|
|
20
23
|
|
|
21
|
-
|
|
24
|
+
from manim.typing import PixelArray, RGBAPixelArray, RGBPixelArray
|
|
25
|
+
|
|
26
|
+
|
|
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"),
|
|
@@ -34,22 +40,22 @@ def get_full_vector_image_path(image_file_name: str) -> Path:
|
|
|
34
40
|
)
|
|
35
41
|
|
|
36
42
|
|
|
37
|
-
def drag_pixels(frames:
|
|
43
|
+
def drag_pixels(frames: Sequence[PixelArray]) -> list[np.ndarray]:
|
|
38
44
|
curr = frames[0]
|
|
39
|
-
new_frames = []
|
|
45
|
+
new_frames: list[np.ndarray] = []
|
|
40
46
|
for frame in frames:
|
|
41
47
|
curr += (curr == 0) * np.array(frame)
|
|
42
48
|
new_frames.append(np.array(curr))
|
|
43
49
|
return new_frames
|
|
44
50
|
|
|
45
51
|
|
|
46
|
-
def invert_image(image:
|
|
52
|
+
def invert_image(image: PixelArray) -> Image:
|
|
47
53
|
arr = np.array(image)
|
|
48
54
|
arr = (255 * np.ones(arr.shape)).astype(arr.dtype) - arr
|
|
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") -> RGBAPixelArray:
|
|
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,6 +127,7 @@ 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
132
|
config.digest_args(args)
|
|
131
133
|
|
|
@@ -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
|