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.

Files changed (146) hide show
  1. manim/__init__.py +3 -6
  2. manim/__main__.py +61 -20
  3. manim/_config/__init__.py +6 -3
  4. manim/_config/cli_colors.py +16 -8
  5. manim/_config/default.cfg +1 -3
  6. manim/_config/logger_utils.py +14 -8
  7. manim/_config/utils.py +651 -472
  8. manim/animation/animation.py +152 -5
  9. manim/animation/composition.py +80 -39
  10. manim/animation/creation.py +196 -14
  11. manim/animation/fading.py +5 -9
  12. manim/animation/indication.py +103 -47
  13. manim/animation/movement.py +22 -5
  14. manim/animation/rotation.py +3 -2
  15. manim/animation/specialized.py +4 -6
  16. manim/animation/speedmodifier.py +10 -5
  17. manim/animation/transform.py +4 -5
  18. manim/animation/transform_matching_parts.py +1 -1
  19. manim/animation/updaters/mobject_update_utils.py +17 -14
  20. manim/camera/camera.py +15 -6
  21. manim/cli/__init__.py +17 -0
  22. manim/cli/cfg/group.py +70 -44
  23. manim/cli/checkhealth/checks.py +93 -75
  24. manim/cli/checkhealth/commands.py +14 -5
  25. manim/cli/default_group.py +157 -25
  26. manim/cli/init/commands.py +32 -24
  27. manim/cli/plugins/commands.py +16 -3
  28. manim/cli/render/commands.py +72 -60
  29. manim/cli/render/ease_of_access_options.py +4 -3
  30. manim/cli/render/global_options.py +51 -15
  31. manim/cli/render/output_options.py +6 -5
  32. manim/cli/render/render_options.py +97 -32
  33. manim/constants.py +65 -19
  34. manim/gui/gui.py +2 -0
  35. manim/mobject/frame.py +0 -1
  36. manim/mobject/geometry/arc.py +112 -78
  37. manim/mobject/geometry/boolean_ops.py +32 -25
  38. manim/mobject/geometry/labeled.py +300 -77
  39. manim/mobject/geometry/line.py +132 -64
  40. manim/mobject/geometry/polygram.py +126 -30
  41. manim/mobject/geometry/shape_matchers.py +35 -15
  42. manim/mobject/geometry/tips.py +38 -29
  43. manim/mobject/graph.py +414 -133
  44. manim/mobject/graphing/coordinate_systems.py +126 -64
  45. manim/mobject/graphing/functions.py +25 -15
  46. manim/mobject/graphing/number_line.py +24 -10
  47. manim/mobject/graphing/probability.py +2 -10
  48. manim/mobject/graphing/scale.py +6 -5
  49. manim/mobject/matrix.py +17 -19
  50. manim/mobject/mobject.py +314 -165
  51. manim/mobject/opengl/opengl_compatibility.py +2 -0
  52. manim/mobject/opengl/opengl_geometry.py +30 -9
  53. manim/mobject/opengl/opengl_image_mobject.py +2 -0
  54. manim/mobject/opengl/opengl_mobject.py +509 -343
  55. manim/mobject/opengl/opengl_point_cloud_mobject.py +5 -7
  56. manim/mobject/opengl/opengl_surface.py +3 -2
  57. manim/mobject/opengl/opengl_three_dimensions.py +2 -0
  58. manim/mobject/opengl/opengl_vectorized_mobject.py +46 -79
  59. manim/mobject/svg/brace.py +63 -13
  60. manim/mobject/svg/svg_mobject.py +4 -3
  61. manim/mobject/table.py +11 -13
  62. manim/mobject/text/code_mobject.py +186 -548
  63. manim/mobject/text/numbers.py +9 -7
  64. manim/mobject/text/tex_mobject.py +23 -14
  65. manim/mobject/text/text_mobject.py +70 -24
  66. manim/mobject/three_d/polyhedra.py +98 -1
  67. manim/mobject/three_d/three_d_utils.py +4 -4
  68. manim/mobject/three_d/three_dimensions.py +62 -34
  69. manim/mobject/types/image_mobject.py +42 -24
  70. manim/mobject/types/point_cloud_mobject.py +105 -67
  71. manim/mobject/types/vectorized_mobject.py +496 -228
  72. manim/mobject/value_tracker.py +5 -4
  73. manim/mobject/vector_field.py +5 -5
  74. manim/opengl/__init__.py +3 -3
  75. manim/plugins/__init__.py +14 -1
  76. manim/plugins/plugins_flags.py +14 -8
  77. manim/renderer/cairo_renderer.py +20 -10
  78. manim/renderer/opengl_renderer.py +21 -23
  79. manim/renderer/opengl_renderer_window.py +2 -0
  80. manim/renderer/shader.py +2 -3
  81. manim/renderer/shader_wrapper.py +5 -2
  82. manim/renderer/vectorized_mobject_rendering.py +5 -0
  83. manim/scene/moving_camera_scene.py +23 -0
  84. manim/scene/scene.py +90 -43
  85. manim/scene/scene_file_writer.py +316 -165
  86. manim/scene/section.py +17 -15
  87. manim/scene/three_d_scene.py +13 -21
  88. manim/scene/vector_space_scene.py +22 -9
  89. manim/typing.py +830 -70
  90. manim/utils/bezier.py +1667 -399
  91. manim/utils/caching.py +13 -5
  92. manim/utils/color/AS2700.py +2 -0
  93. manim/utils/color/BS381.py +3 -0
  94. manim/utils/color/DVIPSNAMES.py +96 -0
  95. manim/utils/color/SVGNAMES.py +179 -0
  96. manim/utils/color/X11.py +3 -0
  97. manim/utils/color/XKCD.py +3 -0
  98. manim/utils/color/__init__.py +8 -5
  99. manim/utils/color/core.py +844 -309
  100. manim/utils/color/manim_colors.py +7 -9
  101. manim/utils/commands.py +48 -20
  102. manim/utils/config_ops.py +18 -13
  103. manim/utils/debug.py +8 -7
  104. manim/utils/deprecation.py +90 -40
  105. manim/utils/docbuild/__init__.py +17 -0
  106. manim/utils/docbuild/autoaliasattr_directive.py +234 -0
  107. manim/utils/docbuild/autocolor_directive.py +21 -17
  108. manim/utils/docbuild/manim_directive.py +50 -35
  109. manim/utils/docbuild/module_parsing.py +245 -0
  110. manim/utils/exceptions.py +6 -0
  111. manim/utils/family.py +5 -3
  112. manim/utils/family_ops.py +17 -4
  113. manim/utils/file_ops.py +26 -16
  114. manim/utils/hashing.py +9 -7
  115. manim/utils/images.py +10 -4
  116. manim/utils/ipython_magic.py +14 -8
  117. manim/utils/iterables.py +161 -119
  118. manim/utils/module_ops.py +57 -19
  119. manim/utils/opengl.py +83 -24
  120. manim/utils/parameter_parsing.py +32 -0
  121. manim/utils/paths.py +21 -23
  122. manim/utils/polylabel.py +168 -0
  123. manim/utils/qhull.py +218 -0
  124. manim/utils/rate_functions.py +74 -39
  125. manim/utils/simple_functions.py +24 -15
  126. manim/utils/sounds.py +7 -1
  127. manim/utils/space_ops.py +125 -69
  128. manim/utils/testing/__init__.py +17 -0
  129. manim/utils/testing/_frames_testers.py +13 -8
  130. manim/utils/testing/_show_diff.py +5 -3
  131. manim/utils/testing/_test_class_makers.py +33 -18
  132. manim/utils/testing/frames_comparison.py +27 -19
  133. manim/utils/tex.py +127 -197
  134. manim/utils/tex_file_writing.py +47 -45
  135. manim/utils/tex_templates.py +2 -1
  136. manim/utils/unit.py +6 -5
  137. {manim-0.18.0.post0.dist-info → manim-0.19.0.dist-info}/LICENSE.community +1 -1
  138. {manim-0.18.0.post0.dist-info → manim-0.19.0.dist-info}/METADATA +40 -39
  139. manim-0.19.0.dist-info/RECORD +221 -0
  140. {manim-0.18.0.post0.dist-info → manim-0.19.0.dist-info}/WHEEL +1 -1
  141. manim/cli/new/__init__.py +0 -0
  142. manim/cli/new/group.py +0 -189
  143. manim/plugins/import_plugins.py +0 -43
  144. manim-0.18.0.post0.dist-info/RECORD +0 -217
  145. {manim-0.18.0.post0.dist-info → manim-0.19.0.dist-info}/LICENSE +0 -0
  146. {manim-0.18.0.post0.dist-info → manim-0.19.0.dist-info}/entry_points.txt +0 -0
manim/utils/iterables.py CHANGED
@@ -19,12 +19,29 @@ __all__ = [
19
19
  ]
20
20
 
21
21
  import itertools as it
22
- from typing import Any, Callable, Collection, Generator, Iterable, Reversible, Sequence
22
+ from collections.abc import (
23
+ Collection,
24
+ Generator,
25
+ Hashable,
26
+ Iterable,
27
+ Reversible,
28
+ Sequence,
29
+ )
30
+ from typing import TYPE_CHECKING, Callable, TypeVar, overload
23
31
 
24
32
  import numpy as np
25
33
 
34
+ T = TypeVar("T")
35
+ U = TypeVar("U")
36
+ F = TypeVar("F", np.float64, np.int_)
37
+ H = TypeVar("H", bound=Hashable)
26
38
 
27
- def adjacent_n_tuples(objects: Sequence, n: int) -> zip:
39
+
40
+ if TYPE_CHECKING:
41
+ import numpy.typing as npt
42
+
43
+
44
+ def adjacent_n_tuples(objects: Sequence[T], n: int) -> zip[tuple[T, ...]]:
28
45
  """Returns the Sequence objects cyclically split into n length tuples.
29
46
 
30
47
  See Also
@@ -33,18 +50,17 @@ def adjacent_n_tuples(objects: Sequence, n: int) -> zip:
33
50
 
34
51
  Examples
35
52
  --------
36
- Normal usage::
53
+ .. code-block:: pycon
37
54
 
38
- list(adjacent_n_tuples([1, 2, 3, 4], 2))
39
- # returns [(1, 2), (2, 3), (3, 4), (4, 1)]
40
-
41
- list(adjacent_n_tuples([1, 2, 3, 4], 3))
42
- # returns [(1, 2, 3), (2, 3, 4), (3, 4, 1), (4, 1, 2)]
55
+ >>> list(adjacent_n_tuples([1, 2, 3, 4], 2))
56
+ [(1, 2), (2, 3), (3, 4), (4, 1)]
57
+ >>> list(adjacent_n_tuples([1, 2, 3, 4], 3))
58
+ [(1, 2, 3), (2, 3, 4), (3, 4, 1), (4, 1, 2)]
43
59
  """
44
60
  return zip(*([*objects[k:], *objects[:k]] for k in range(n)))
45
61
 
46
62
 
47
- def adjacent_pairs(objects: Sequence) -> zip:
63
+ def adjacent_pairs(objects: Sequence[T]) -> zip[tuple[T, ...]]:
48
64
  """Alias for ``adjacent_n_tuples(objects, 2)``.
49
65
 
50
66
  See Also
@@ -53,15 +69,15 @@ def adjacent_pairs(objects: Sequence) -> zip:
53
69
 
54
70
  Examples
55
71
  --------
56
- Normal usage::
72
+ .. code-block:: pycon
57
73
 
58
- list(adjacent_pairs([1, 2, 3, 4]))
59
- # returns [(1, 2), (2, 3), (3, 4), (4, 1)]
74
+ >>> list(adjacent_pairs([1, 2, 3, 4]))
75
+ [(1, 2), (2, 3), (3, 4), (4, 1)]
60
76
  """
61
77
  return adjacent_n_tuples(objects, 2)
62
78
 
63
79
 
64
- def all_elements_are_instances(iterable: Iterable, Class) -> bool:
80
+ def all_elements_are_instances(iterable: Iterable[object], Class: type[object]) -> bool:
65
81
  """Returns ``True`` if all elements of iterable are instances of Class.
66
82
  False otherwise.
67
83
  """
@@ -69,8 +85,8 @@ def all_elements_are_instances(iterable: Iterable, Class) -> bool:
69
85
 
70
86
 
71
87
  def batch_by_property(
72
- items: Sequence, property_func: Callable
73
- ) -> list[tuple[list, Any]]:
88
+ items: Iterable[T], property_func: Callable[[T], U]
89
+ ) -> list[tuple[list[T], U | None]]:
74
90
  """Takes in a Sequence, and returns a list of tuples, (batch, prop)
75
91
  such that all items in a batch have the same output when
76
92
  put into the Callable property_func, and such that chaining all these
@@ -79,13 +95,13 @@ def batch_by_property(
79
95
 
80
96
  Examples
81
97
  --------
82
- Normal usage::
98
+ .. code-block:: pycon
83
99
 
84
- batch_by_property([(1, 2), (3, 4), (5, 6, 7), (8, 9)], len)
85
- # returns [([(1, 2), (3, 4)], 2), ([(5, 6, 7)], 3), ([(8, 9)], 2)]
100
+ >>> batch_by_property([(1, 2), (3, 4), (5, 6, 7), (8, 9)], len)
101
+ [([(1, 2), (3, 4)], 2), ([(5, 6, 7)], 3), ([(8, 9)], 2)]
86
102
  """
87
- batch_prop_pairs = []
88
- curr_batch = []
103
+ batch_prop_pairs: list[tuple[list[T], U | None]] = []
104
+ curr_batch: list[T] = []
89
105
  curr_prop = None
90
106
  for item in items:
91
107
  prop = property_func(item)
@@ -103,67 +119,84 @@ def batch_by_property(
103
119
  return batch_prop_pairs
104
120
 
105
121
 
106
- def concatenate_lists(*list_of_lists: Iterable) -> list:
122
+ def concatenate_lists(*list_of_lists: Iterable[T]) -> list[T]:
107
123
  """Combines the Iterables provided as arguments into one list.
108
124
 
109
125
  Examples
110
126
  --------
111
- Normal usage::
127
+ .. code-block:: pycon
112
128
 
113
- concatenate_lists([1, 2], [3, 4], [5])
114
- # returns [1, 2, 3, 4, 5]
129
+ >>> concatenate_lists([1, 2], [3, 4], [5])
130
+ [1, 2, 3, 4, 5]
115
131
  """
116
132
  return [item for lst in list_of_lists for item in lst]
117
133
 
118
134
 
119
- def list_difference_update(l1: Iterable, l2: Iterable) -> list:
135
+ def list_difference_update(l1: Iterable[T], l2: Iterable[T]) -> list[T]:
120
136
  """Returns a list containing all the elements of l1 not in l2.
121
137
 
122
138
  Examples
123
139
  --------
124
- Normal usage::
140
+ .. code-block:: pycon
125
141
 
126
- list_difference_update([1, 2, 3, 4], [2, 4])
127
- # returns [1, 3]
142
+ >>> list_difference_update([1, 2, 3, 4], [2, 4])
143
+ [1, 3]
128
144
  """
129
145
  return [e for e in l1 if e not in l2]
130
146
 
131
147
 
132
- def list_update(l1: Iterable, l2: Iterable) -> list:
148
+ def list_update(l1: Iterable[T], l2: Iterable[T]) -> list[T]:
133
149
  """Used instead of ``set.update()`` to maintain order,
134
150
  making sure duplicates are removed from l1, not l2.
135
151
  Removes overlap of l1 and l2 and then concatenates l2 unchanged.
136
152
 
137
153
  Examples
138
154
  --------
139
- Normal usage::
155
+ .. code-block:: pycon
140
156
 
141
- list_update([1, 2, 3], [2, 4, 4])
142
- # returns [1, 3, 2, 4, 4]
157
+ >>> list_update([1, 2, 3], [2, 4, 4])
158
+ [1, 3, 2, 4, 4]
143
159
  """
144
160
  return [e for e in l1 if e not in l2] + list(l2)
145
161
 
146
162
 
147
- def listify(obj) -> list:
163
+ @overload
164
+ def listify(obj: str) -> list[str]: ...
165
+
166
+
167
+ @overload
168
+ def listify(obj: Iterable[T]) -> list[T]: ...
169
+
170
+
171
+ @overload
172
+ def listify(obj: T) -> list[T]: ...
173
+
174
+
175
+ def listify(obj: str | Iterable[T] | T) -> list[str] | list[T]:
148
176
  """Converts obj to a list intelligently.
149
177
 
150
178
  Examples
151
179
  --------
152
- Normal usage::
153
-
154
- listify('str') # ['str']
155
- listify((1, 2)) # [1, 2]
156
- listify(len) # [<built-in function len>]
180
+ .. code-block:: pycon
181
+
182
+ >>> listify("str")
183
+ ['str']
184
+ >>> listify((1, 2))
185
+ [1, 2]
186
+ >>> listify(len)
187
+ [<built-in function len>]
157
188
  """
158
189
  if isinstance(obj, str):
159
190
  return [obj]
160
- try:
191
+ if isinstance(obj, Iterable):
161
192
  return list(obj)
162
- except TypeError:
193
+ else:
163
194
  return [obj]
164
195
 
165
196
 
166
- def make_even(iterable_1: Iterable, iterable_2: Iterable) -> tuple[list, list]:
197
+ def make_even(
198
+ iterable_1: Iterable[T], iterable_2: Iterable[U]
199
+ ) -> tuple[list[T], list[U]]:
167
200
  """Extends the shorter of the two iterables with duplicate values until its
168
201
  length is equal to the longer iterable (favours earlier elements).
169
202
 
@@ -173,13 +206,13 @@ def make_even(iterable_1: Iterable, iterable_2: Iterable) -> tuple[list, list]:
173
206
 
174
207
  Examples
175
208
  --------
176
- Normal usage::
209
+ .. code-block:: pycon
177
210
 
178
- make_even([1, 2], [3, 4, 5, 6])
211
+ >>> make_even([1, 2], [3, 4, 5, 6])
179
212
  ([1, 1, 2, 2], [3, 4, 5, 6])
180
213
 
181
- make_even([1, 2], [3, 4, 5, 6, 7])
182
- # ([1, 1, 1, 2, 2], [3, 4, 5, 6, 7])
214
+ >>> make_even([1, 2], [3, 4, 5, 6, 7])
215
+ ([1, 1, 1, 2, 2], [3, 4, 5, 6, 7])
183
216
  """
184
217
  list_1, list_2 = list(iterable_1), list(iterable_2)
185
218
  len_list_1 = len(list_1)
@@ -192,8 +225,8 @@ def make_even(iterable_1: Iterable, iterable_2: Iterable) -> tuple[list, list]:
192
225
 
193
226
 
194
227
  def make_even_by_cycling(
195
- iterable_1: Collection, iterable_2: Collection
196
- ) -> tuple[list, list]:
228
+ iterable_1: Collection[T], iterable_2: Collection[U]
229
+ ) -> tuple[list[T], list[U]]:
197
230
  """Extends the shorter of the two iterables with duplicate values until its
198
231
  length is equal to the longer iterable (cycles over shorter iterable).
199
232
 
@@ -203,13 +236,13 @@ def make_even_by_cycling(
203
236
 
204
237
  Examples
205
238
  --------
206
- Normal usage::
239
+ .. code-block:: pycon
207
240
 
208
- make_even_by_cycling([1, 2], [3, 4, 5, 6])
241
+ >>> make_even_by_cycling([1, 2], [3, 4, 5, 6])
209
242
  ([1, 2, 1, 2], [3, 4, 5, 6])
210
243
 
211
- make_even_by_cycling([1, 2], [3, 4, 5, 6, 7])
212
- # ([1, 2, 1, 2, 1], [3, 4, 5, 6, 7])
244
+ >>> make_even_by_cycling([1, 2], [3, 4, 5, 6, 7])
245
+ ([1, 2, 1, 2, 1], [3, 4, 5, 6, 7])
213
246
  """
214
247
  length = max(len(iterable_1), len(iterable_2))
215
248
  cycle1 = it.cycle(iterable_1)
@@ -220,7 +253,7 @@ def make_even_by_cycling(
220
253
  )
221
254
 
222
255
 
223
- def remove_list_redundancies(lst: Reversible) -> list:
256
+ def remove_list_redundancies(lst: Reversible[H]) -> list[H]:
224
257
  """Used instead of ``list(set(l))`` to maintain order.
225
258
  Keeps the last occurrence of each element.
226
259
  """
@@ -234,21 +267,21 @@ def remove_list_redundancies(lst: Reversible) -> list:
234
267
  return reversed_result
235
268
 
236
269
 
237
- def remove_nones(sequence: Iterable) -> list:
270
+ def remove_nones(sequence: Iterable[T | None]) -> list[T]:
238
271
  """Removes elements where bool(x) evaluates to False.
239
272
 
240
273
  Examples
241
274
  --------
242
- Normal usage::
275
+ .. code-block:: pycon
243
276
 
244
- remove_nones(['m', '', 'l', 0, 42, False, True])
245
- # ['m', 'l', 42, True]
277
+ >>> remove_nones(["m", "", "l", 0, 42, False, True])
278
+ ['m', 'l', 42, True]
246
279
  """
247
280
  # Note this is redundant with it.chain
248
281
  return [x for x in sequence if x]
249
282
 
250
283
 
251
- def resize_array(nparray: np.ndarray, length: int) -> np.ndarray:
284
+ def resize_array(nparray: npt.NDArray[F], length: int) -> npt.NDArray[F]:
252
285
  """Extends/truncates nparray so that ``len(result) == length``.
253
286
  The elements of nparray are cycled to achieve the desired length.
254
287
 
@@ -259,7 +292,7 @@ def resize_array(nparray: np.ndarray, length: int) -> np.ndarray:
259
292
 
260
293
  Examples
261
294
  --------
262
- Normal usage::
295
+ .. code-block:: pycon
263
296
 
264
297
  >>> points = np.array([[1, 2], [3, 4]])
265
298
  >>> resize_array(points, 1)
@@ -277,7 +310,9 @@ def resize_array(nparray: np.ndarray, length: int) -> np.ndarray:
277
310
  return np.resize(nparray, (length, *nparray.shape[1:]))
278
311
 
279
312
 
280
- def resize_preserving_order(nparray: np.ndarray, length: int) -> np.ndarray:
313
+ def resize_preserving_order(
314
+ nparray: npt.NDArray[np.float64], length: int
315
+ ) -> npt.NDArray[np.float64]:
281
316
  """Extends/truncates nparray so that ``len(result) == length``.
282
317
  The elements of nparray are duplicated to achieve the desired length
283
318
  (favours earlier elements).
@@ -291,21 +326,19 @@ def resize_preserving_order(nparray: np.ndarray, length: int) -> np.ndarray:
291
326
 
292
327
  Examples
293
328
  --------
294
- Normal usage::
295
-
296
- resize_preserving_order(np.array([]), 5)
297
- # np.array([0., 0., 0., 0., 0.])
329
+ .. code-block:: pycon
298
330
 
299
- nparray = np.array([[1, 2],
300
- [3, 4]])
331
+ >>> resize_preserving_order(np.array([]), 5)
332
+ array([0., 0., 0., 0., 0.])
301
333
 
302
- resize_preserving_order(nparray, 1)
303
- # np.array([[1, 2]])
334
+ >>> nparray = np.array([[1, 2], [3, 4]])
335
+ >>> resize_preserving_order(nparray, 1)
336
+ array([[1, 2]])
304
337
 
305
- resize_preserving_order(nparray, 3)
306
- # np.array([[1, 2],
307
- # [1, 2],
308
- # [3, 4]])
338
+ >>> resize_preserving_order(nparray, 3)
339
+ array([[1, 2],
340
+ [1, 2],
341
+ [3, 4]])
309
342
  """
310
343
  if len(nparray) == 0:
311
344
  return np.zeros((length, *nparray.shape[1:]))
@@ -315,7 +348,7 @@ def resize_preserving_order(nparray: np.ndarray, length: int) -> np.ndarray:
315
348
  return nparray[indices]
316
349
 
317
350
 
318
- def resize_with_interpolation(nparray: np.ndarray, length: int) -> np.ndarray:
351
+ def resize_with_interpolation(nparray: npt.NDArray[F], length: int) -> npt.NDArray[F]:
319
352
  """Extends/truncates nparray so that ``len(result) == length``.
320
353
  New elements are interpolated to achieve the desired length.
321
354
 
@@ -329,39 +362,29 @@ def resize_with_interpolation(nparray: np.ndarray, length: int) -> np.ndarray:
329
362
 
330
363
  Examples
331
364
  --------
332
- Normal usage::
333
-
334
- nparray = np.array([[1, 2],
335
- [3, 4]])
336
-
337
- resize_with_interpolation(nparray, 1)
338
- # np.array([[1., 2.]])
339
-
340
- resize_with_interpolation(nparray, 4)
341
- # np.array([[1. , 2. ],
342
- # [1.66666667, 2.66666667],
343
- # [2.33333333, 3.33333333],
344
- # [3. , 4. ]])
345
-
346
- nparray = np.array([[[1, 2],[3, 4]]])
347
- resize_with_interpolation(nparray, 3)
348
- # np.array([[[1., 2.], [3., 4.]],
349
- # [[1., 2.], [3., 4.]],
350
- # [[1., 2.], [3., 4.]]])
351
-
352
- nparray = np.array([[1, 2], [3, 4], [5, 6]])
353
- resize_with_interpolation(nparray, 4)
354
- # np.array([[1. , 2. ],
355
- # [2.33333333, 3.33333333],
356
- # [3.66666667, 4.66666667],
357
- # [5. , 6. ]])
358
-
359
- nparray = np.array([[1, 2], [3, 4], [1, 2]])
360
- resize_with_interpolation(nparray, 4)
361
- # np.array([[1. , 2. ],
362
- # [2.33333333, 3.33333333],
363
- # [2.33333333, 3.33333333],
364
- # [1. , 2. ]])
365
+ .. code-block:: pycon
366
+
367
+ >>> nparray = np.array([[1, 2], [3, 4]])
368
+ >>> resize_with_interpolation(nparray, 1)
369
+ array([[1., 2.]])
370
+ >>> resize_with_interpolation(nparray, 4)
371
+ array([[1. , 2. ],
372
+ [1.66666667, 2.66666667],
373
+ [2.33333333, 3.33333333],
374
+ [3. , 4. ]])
375
+ >>> nparray = np.array([[[1, 2], [3, 4]]])
376
+ >>> nparray = np.array([[1, 2], [3, 4], [5, 6]])
377
+ >>> resize_with_interpolation(nparray, 4)
378
+ array([[1. , 2. ],
379
+ [2.33333333, 3.33333333],
380
+ [3.66666667, 4.66666667],
381
+ [5. , 6. ]])
382
+ >>> nparray = np.array([[1, 2], [3, 4], [1, 2]])
383
+ >>> resize_with_interpolation(nparray, 4)
384
+ array([[1. , 2. ],
385
+ [2.33333333, 3.33333333],
386
+ [2.33333333, 3.33333333],
387
+ [1. , 2. ]])
365
388
  """
366
389
  if len(nparray) == length:
367
390
  return nparray
@@ -375,7 +398,7 @@ def resize_with_interpolation(nparray: np.ndarray, length: int) -> np.ndarray:
375
398
  )
376
399
 
377
400
 
378
- def stretch_array_to_length(nparray: np.ndarray, length: int) -> np.ndarray:
401
+ def stretch_array_to_length(nparray: npt.NDArray[F], length: int) -> npt.NDArray[F]:
379
402
  # todo: is this the same as resize_preserving_order()?
380
403
  curr_len = len(nparray)
381
404
  if curr_len > length:
@@ -385,35 +408,54 @@ def stretch_array_to_length(nparray: np.ndarray, length: int) -> np.ndarray:
385
408
  return nparray[indices.astype(int)]
386
409
 
387
410
 
388
- def tuplify(obj) -> tuple:
411
+ @overload
412
+ def tuplify(obj: str) -> tuple[str]: ...
413
+
414
+
415
+ @overload
416
+ def tuplify(obj: Iterable[T]) -> tuple[T]: ...
417
+
418
+
419
+ @overload
420
+ def tuplify(obj: T) -> tuple[T]: ...
421
+
422
+
423
+ def tuplify(obj: str | Iterable[T] | T) -> tuple[str] | tuple[T]:
389
424
  """Converts obj to a tuple intelligently.
390
425
 
391
426
  Examples
392
427
  --------
393
- Normal usage::
394
-
395
- tuplify('str') # ('str',)
396
- tuplify([1, 2]) # (1, 2)
397
- tuplify(len) # (<built-in function len>,)
428
+ .. code-block:: pycon
429
+
430
+ >>> tuplify("str")
431
+ ('str',)
432
+ >>> tuplify([1, 2])
433
+ (1, 2)
434
+ >>> tuplify(len)
435
+ (<built-in function len>,)
398
436
  """
399
437
  if isinstance(obj, str):
400
438
  return (obj,)
401
- try:
439
+ if isinstance(obj, Iterable):
402
440
  return tuple(obj)
403
- except TypeError:
441
+ else:
404
442
  return (obj,)
405
443
 
406
444
 
407
- def uniq_chain(*args: Iterable) -> Generator:
445
+ def uniq_chain(*args: Iterable[T]) -> Generator[T, None, None]:
408
446
  """Returns a generator that yields all unique elements of the Iterables
409
447
  provided via args in the order provided.
410
448
 
411
449
  Examples
412
450
  --------
413
- Normal usage::
414
-
415
- uniq_chain([1, 2], [2, 3], [1, 4, 4])
416
- # yields 1, 2, 3, 4
451
+ .. code-block:: pycon
452
+
453
+ >>> gen = uniq_chain([1, 2], [2, 3], [1, 4, 4])
454
+ >>> from collections.abc import Generator
455
+ >>> isinstance(gen, Generator)
456
+ True
457
+ >>> tuple(gen)
458
+ (1, 2, 3, 4)
417
459
  """
418
460
  unique_items = set()
419
461
  for x in it.chain(*args):
manim/utils/module_ops.py CHANGED
@@ -2,18 +2,31 @@ from __future__ import annotations
2
2
 
3
3
  import importlib.util
4
4
  import inspect
5
- import os
6
5
  import re
7
6
  import sys
8
7
  import types
9
8
  import warnings
10
9
  from pathlib import Path
10
+ from typing import TYPE_CHECKING, Literal, overload
11
11
 
12
- from .. import config, console, constants, logger
13
- from ..scene.scene_file_writer import SceneFileWriter
12
+ from manim._config import config, console, logger
13
+ from manim.constants import (
14
+ CHOOSE_NUMBER_MESSAGE,
15
+ INVALID_NUMBER_MESSAGE,
16
+ NO_SCENE_MESSAGE,
17
+ SCENE_NOT_FOUND_MESSAGE,
18
+ )
19
+ from manim.scene.scene_file_writer import SceneFileWriter
14
20
 
21
+ if TYPE_CHECKING:
22
+ from typing import Any
15
23
 
16
- def get_module(file_name: Path):
24
+ from manim.scene.scene import Scene
25
+
26
+ __all__ = ["scene_classes_from_file"]
27
+
28
+
29
+ def get_module(file_name: Path) -> types.ModuleType:
17
30
  if str(file_name) == "-":
18
31
  module = types.ModuleType("input_scenes")
19
32
  logger.info(
@@ -46,19 +59,22 @@ def get_module(file_name: Path):
46
59
  )
47
60
 
48
61
  spec = importlib.util.spec_from_file_location(module_name, file_name)
49
- module = importlib.util.module_from_spec(spec)
50
- sys.modules[module_name] = module
51
- sys.path.insert(0, str(file_name.parent.absolute()))
52
- spec.loader.exec_module(module)
53
- return module
62
+ if isinstance(spec, importlib.machinery.ModuleSpec):
63
+ module = importlib.util.module_from_spec(spec)
64
+ sys.modules[module_name] = module
65
+ sys.path.insert(0, str(file_name.parent.absolute()))
66
+ assert spec.loader
67
+ spec.loader.exec_module(module)
68
+ return module
69
+ raise FileNotFoundError(f"{file_name} not found")
54
70
  else:
55
71
  raise FileNotFoundError(f"{file_name} not found")
56
72
 
57
73
 
58
- def get_scene_classes_from_module(module):
74
+ def get_scene_classes_from_module(module: types.ModuleType) -> list[type[Scene]]:
59
75
  from ..scene.scene import Scene
60
76
 
61
- def is_child_scene(obj, module):
77
+ def is_child_scene(obj: Any, module: types.ModuleType) -> bool:
62
78
  return (
63
79
  inspect.isclass(obj)
64
80
  and issubclass(obj, Scene)
@@ -72,9 +88,9 @@ def get_scene_classes_from_module(module):
72
88
  ]
73
89
 
74
90
 
75
- def get_scenes_to_render(scene_classes):
91
+ def get_scenes_to_render(scene_classes: list[type[Scene]]) -> list[type[Scene]]:
76
92
  if not scene_classes:
77
- logger.error(constants.NO_SCENE_MESSAGE)
93
+ logger.error(NO_SCENE_MESSAGE)
78
94
  return []
79
95
  if config["write_all"]:
80
96
  return scene_classes
@@ -87,7 +103,7 @@ def get_scenes_to_render(scene_classes):
87
103
  found = True
88
104
  break
89
105
  if not found and (scene_name != ""):
90
- logger.error(constants.SCENE_NOT_FOUND_MESSAGE.format(scene_name))
106
+ logger.error(SCENE_NOT_FOUND_MESSAGE.format(scene_name))
91
107
  if result:
92
108
  return result
93
109
  if len(scene_classes) == 1:
@@ -96,7 +112,7 @@ def get_scenes_to_render(scene_classes):
96
112
  return prompt_user_for_choice(scene_classes)
97
113
 
98
114
 
99
- def prompt_user_for_choice(scene_classes):
115
+ def prompt_user_for_choice(scene_classes: list[type[Scene]]) -> list[type[Scene]]:
100
116
  num_to_class = {}
101
117
  SceneFileWriter.force_output_as_scene_name = True
102
118
  for count, scene_class in enumerate(scene_classes, 1):
@@ -105,7 +121,7 @@ def prompt_user_for_choice(scene_classes):
105
121
  num_to_class[count] = scene_class
106
122
  try:
107
123
  user_input = console.input(
108
- f"[log.message] {constants.CHOOSE_NUMBER_MESSAGE} [/log.message]",
124
+ f"[log.message] {CHOOSE_NUMBER_MESSAGE} [/log.message]",
109
125
  )
110
126
  scene_classes = [
111
127
  num_to_class[int(num_str)]
@@ -114,7 +130,7 @@ def prompt_user_for_choice(scene_classes):
114
130
  config["scene_names"] = [scene_class.__name__ for scene_class in scene_classes]
115
131
  return scene_classes
116
132
  except KeyError:
117
- logger.error(constants.INVALID_NUMBER_MESSAGE)
133
+ logger.error(INVALID_NUMBER_MESSAGE)
118
134
  sys.exit(2)
119
135
  except EOFError:
120
136
  sys.exit(1)
@@ -123,9 +139,31 @@ def prompt_user_for_choice(scene_classes):
123
139
  sys.exit(1)
124
140
 
125
141
 
142
+ @overload
143
+ def scene_classes_from_file(
144
+ file_path: Path, require_single_scene: bool, full_list: Literal[True]
145
+ ) -> list[type[Scene]]: ...
146
+
147
+
148
+ @overload
149
+ def scene_classes_from_file(
150
+ file_path: Path,
151
+ require_single_scene: Literal[True],
152
+ full_list: Literal[False] = False,
153
+ ) -> type[Scene]: ...
154
+
155
+
156
+ @overload
157
+ def scene_classes_from_file(
158
+ file_path: Path,
159
+ require_single_scene: Literal[False] = False,
160
+ full_list: Literal[False] = False,
161
+ ) -> list[type[Scene]]: ...
162
+
163
+
126
164
  def scene_classes_from_file(
127
- file_path: Path, require_single_scene=False, full_list=False
128
- ):
165
+ file_path: Path, require_single_scene: bool = False, full_list: bool = False
166
+ ) -> type[Scene] | list[type[Scene]]:
129
167
  module = get_module(file_path)
130
168
  all_scene_classes = get_scene_classes_from_module(module)
131
169
  if full_list: