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.
Files changed (163) hide show
  1. manim/__init__.py +11 -6
  2. manim/__main__.py +62 -19
  3. manim/_config/__init__.py +10 -9
  4. manim/_config/cli_colors.py +26 -9
  5. manim/_config/default.cfg +1 -3
  6. manim/_config/logger_utils.py +23 -13
  7. manim/_config/utils.py +662 -468
  8. manim/animation/animation.py +164 -18
  9. manim/animation/changing.py +34 -23
  10. manim/animation/composition.py +265 -67
  11. manim/animation/creation.py +208 -26
  12. manim/animation/fading.py +16 -18
  13. manim/animation/growing.py +35 -15
  14. manim/animation/indication.py +150 -76
  15. manim/animation/movement.py +56 -22
  16. manim/animation/numbers.py +64 -6
  17. manim/animation/rotation.py +78 -7
  18. manim/animation/specialized.py +6 -7
  19. manim/animation/speedmodifier.py +13 -10
  20. manim/animation/transform.py +14 -11
  21. manim/animation/transform_matching_parts.py +3 -4
  22. manim/animation/updaters/mobject_update_utils.py +152 -30
  23. manim/animation/updaters/update.py +10 -7
  24. manim/camera/camera.py +182 -118
  25. manim/camera/mapping_camera.py +34 -3
  26. manim/camera/moving_camera.py +95 -74
  27. manim/camera/multi_camera.py +23 -15
  28. manim/camera/three_d_camera.py +70 -52
  29. manim/cli/__init__.py +17 -0
  30. manim/cli/cfg/group.py +76 -44
  31. manim/cli/checkhealth/checks.py +192 -0
  32. manim/cli/checkhealth/commands.py +90 -0
  33. manim/cli/default_group.py +158 -25
  34. manim/cli/init/commands.py +33 -25
  35. manim/cli/plugins/commands.py +16 -3
  36. manim/cli/render/commands.py +72 -60
  37. manim/cli/render/ease_of_access_options.py +4 -3
  38. manim/cli/render/global_options.py +59 -17
  39. manim/cli/render/output_options.py +6 -5
  40. manim/cli/render/render_options.py +98 -33
  41. manim/constants.py +109 -59
  42. manim/data_structures.py +31 -0
  43. manim/mobject/frame.py +8 -5
  44. manim/mobject/geometry/__init__.py +1 -0
  45. manim/mobject/geometry/arc.py +277 -135
  46. manim/mobject/geometry/boolean_ops.py +32 -31
  47. manim/mobject/geometry/labeled.py +376 -0
  48. manim/mobject/geometry/line.py +192 -87
  49. manim/mobject/geometry/polygram.py +224 -58
  50. manim/mobject/geometry/shape_matchers.py +61 -25
  51. manim/mobject/geometry/tips.py +122 -48
  52. manim/mobject/graph.py +1027 -419
  53. manim/mobject/graphing/coordinate_systems.py +533 -278
  54. manim/mobject/graphing/functions.py +53 -32
  55. manim/mobject/graphing/number_line.py +123 -65
  56. manim/mobject/graphing/probability.py +88 -62
  57. manim/mobject/graphing/scale.py +33 -19
  58. manim/mobject/logo.py +118 -28
  59. manim/mobject/matrix.py +87 -83
  60. manim/mobject/mobject.py +912 -442
  61. manim/mobject/opengl/dot_cloud.py +16 -5
  62. manim/mobject/opengl/opengl_compatibility.py +4 -2
  63. manim/mobject/opengl/opengl_geometry.py +254 -153
  64. manim/mobject/opengl/opengl_image_mobject.py +3 -1
  65. manim/mobject/opengl/opengl_mobject.py +779 -482
  66. manim/mobject/opengl/opengl_point_cloud_mobject.py +41 -14
  67. manim/mobject/opengl/opengl_surface.py +14 -92
  68. manim/mobject/opengl/opengl_three_dimensions.py +12 -8
  69. manim/mobject/opengl/opengl_vectorized_mobject.py +98 -100
  70. manim/mobject/svg/brace.py +173 -41
  71. manim/mobject/svg/svg_mobject.py +139 -53
  72. manim/mobject/table.py +61 -68
  73. manim/mobject/text/code_mobject.py +193 -539
  74. manim/mobject/text/numbers.py +81 -34
  75. manim/mobject/text/tex_mobject.py +130 -78
  76. manim/mobject/text/text_mobject.py +288 -164
  77. manim/mobject/three_d/polyhedra.py +111 -13
  78. manim/mobject/three_d/three_d_utils.py +17 -8
  79. manim/mobject/three_d/three_dimensions.py +239 -106
  80. manim/mobject/types/image_mobject.py +50 -30
  81. manim/mobject/types/point_cloud_mobject.py +120 -75
  82. manim/mobject/types/vectorized_mobject.py +841 -408
  83. manim/mobject/value_tracker.py +105 -38
  84. manim/mobject/vector_field.py +50 -31
  85. manim/opengl/__init__.py +3 -3
  86. manim/plugins/__init__.py +14 -1
  87. manim/plugins/plugins_flags.py +10 -14
  88. manim/renderer/cairo_renderer.py +65 -50
  89. manim/renderer/opengl_renderer.py +89 -69
  90. manim/renderer/opengl_renderer_window.py +39 -18
  91. manim/renderer/shader.py +123 -87
  92. manim/renderer/shader_wrapper.py +44 -28
  93. manim/renderer/vectorized_mobject_rendering.py +38 -10
  94. manim/scene/moving_camera_scene.py +32 -3
  95. manim/scene/scene.py +507 -242
  96. manim/scene/scene_file_writer.py +371 -220
  97. manim/scene/section.py +20 -16
  98. manim/scene/three_d_scene.py +14 -22
  99. manim/scene/vector_space_scene.py +223 -129
  100. manim/scene/zoomed_scene.py +46 -41
  101. manim/typing.py +990 -0
  102. manim/utils/bezier.py +1823 -371
  103. manim/utils/caching.py +12 -5
  104. manim/utils/color/AS2700.py +236 -0
  105. manim/utils/color/BS381.py +318 -0
  106. manim/utils/color/DVIPSNAMES.py +96 -0
  107. manim/utils/color/SVGNAMES.py +179 -0
  108. manim/utils/color/X11.py +533 -0
  109. manim/utils/color/XKCD.py +952 -0
  110. manim/utils/color/__init__.py +61 -0
  111. manim/utils/color/core.py +1667 -0
  112. manim/utils/color/manim_colors.py +218 -0
  113. manim/utils/commands.py +48 -20
  114. manim/utils/config_ops.py +39 -19
  115. manim/utils/debug.py +8 -7
  116. manim/utils/deprecation.py +86 -39
  117. manim/utils/docbuild/__init__.py +17 -0
  118. manim/utils/docbuild/autoaliasattr_directive.py +236 -0
  119. manim/utils/docbuild/autocolor_directive.py +99 -0
  120. manim/utils/docbuild/manim_directive.py +94 -41
  121. manim/utils/docbuild/module_parsing.py +245 -0
  122. manim/utils/exceptions.py +6 -0
  123. manim/utils/family.py +5 -3
  124. manim/utils/family_ops.py +17 -4
  125. manim/utils/file_ops.py +27 -17
  126. manim/utils/hashing.py +55 -45
  127. manim/utils/images.py +13 -7
  128. manim/utils/ipython_magic.py +13 -7
  129. manim/utils/iterables.py +163 -120
  130. manim/utils/module_ops.py +66 -24
  131. manim/utils/opengl.py +77 -24
  132. manim/utils/parameter_parsing.py +32 -0
  133. manim/utils/paths.py +30 -33
  134. manim/utils/polylabel.py +235 -0
  135. manim/utils/qhull.py +218 -0
  136. manim/utils/rate_functions.py +98 -32
  137. manim/utils/simple_functions.py +25 -33
  138. manim/utils/sounds.py +7 -1
  139. manim/utils/space_ops.py +188 -115
  140. manim/utils/testing/__init__.py +17 -0
  141. manim/utils/testing/_frames_testers.py +13 -8
  142. manim/utils/testing/_show_diff.py +5 -3
  143. manim/utils/testing/_test_class_makers.py +34 -18
  144. manim/utils/testing/frames_comparison.py +37 -19
  145. manim/utils/tex.py +130 -198
  146. manim/utils/tex_file_writing.py +77 -47
  147. manim/utils/tex_templates.py +2 -1
  148. manim/utils/unit.py +6 -5
  149. {manim-0.17.0.dist-info → manim-0.19.1.dist-info}/METADATA +64 -65
  150. manim-0.19.1.dist-info/RECORD +220 -0
  151. {manim-0.17.0.dist-info → manim-0.19.1.dist-info}/WHEEL +1 -1
  152. manim-0.19.1.dist-info/entry_points.txt +3 -0
  153. {manim-0.17.0.dist-info → manim-0.19.1.dist-info/licenses}/LICENSE.community +1 -1
  154. manim/cli/new/group.py +0 -189
  155. manim/communitycolors.py +0 -9
  156. manim/gui/__init__.py +0 -0
  157. manim/gui/gui.py +0 -82
  158. manim/plugins/import_plugins.py +0 -43
  159. manim/utils/color.py +0 -552
  160. manim-0.17.0.dist-info/RECORD +0 -206
  161. manim-0.17.0.dist-info/entry_points.txt +0 -4
  162. /manim/cli/{new → checkhealth}/__init__.py +0 -0
  163. {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
- return config["format"] == "mp4"
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
- return config["format"] == "gif"
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
- return config["format"] == "webm"
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
- return config["format"] == "mov"
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
- return config["format"] == "png"
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: str, default_dir: Path, extensions: list[str]
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(), os.path.getmtime(file_path)))
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(file_path if not in_browser else file_path.parent)
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 is_gif_format():
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
- commands.append(file_path)
208
- sp.Popen(commands)
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.animation.animation import Animation
18
- from manim.camera.camera import Camera
19
- from manim.mobject.mobject import Mobject
16
+ from manim._config import config, logger
20
17
 
21
- from .. import config, logger
22
-
23
- if typing.TYPE_CHECKING:
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(cls: _Memoizer, is_method: bool = False):
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 _ALREADY_PROCESSED_PLACEHOLDER if the obj has been processed, or lets
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 _ALREADY_PROCESSED_PLACEHOLDER if it has.
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: typing.Callable[[Any], Any],
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, collections.abc.Hashable):
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: typing.Any,
146
- obj_to_membership_sign: typing.Callable[[Any], int],
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 JsonEncoder to iterate over it.
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 = getattr(obj, "__dict__")
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
- def _cleaned_iterable(self, iterable: typing.Iterable[Any]):
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 byt its hash using the same process implemented here.
239
- If a circular reference is found within the iterable, it will be replaced by the string "already processed".
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 (supporter by Json)
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: dict):
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: typing.Iterable[Animation],
327
- current_mobjects_list: typing.Iterable[Mobject],
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
- def get_full_raster_image_path(image_file_name: str) -> Path:
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: list[np.array]) -> list[np.array]:
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: np.array) -> 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:
@@ -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 Group, config, logger, tempconfig
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 is None:
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
- return config["scene_names"][0] + "@" + datetime.now().strftime("%Y-%m-%d@%H-%M-%S")
199
+ val: str = (
200
+ config["scene_names"][0] + "@" + datetime.now().strftime("%Y-%m-%d@%H-%M-%S")
201
+ )
202
+ return val