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/iterables.py CHANGED
@@ -19,12 +19,30 @@ __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
+ Callable,
24
+ Collection,
25
+ Generator,
26
+ Hashable,
27
+ Iterable,
28
+ Reversible,
29
+ Sequence,
30
+ )
31
+ from typing import TYPE_CHECKING, TypeVar, overload
23
32
 
24
33
  import numpy as np
25
34
 
35
+ T = TypeVar("T")
36
+ U = TypeVar("U")
37
+ F = TypeVar("F", np.float64, np.int_)
38
+ H = TypeVar("H", bound=Hashable)
26
39
 
27
- def adjacent_n_tuples(objects: Sequence, n: int) -> zip:
40
+
41
+ if TYPE_CHECKING:
42
+ import numpy.typing as npt
43
+
44
+
45
+ def adjacent_n_tuples(objects: Sequence[T], n: int) -> zip[tuple[T, ...]]:
28
46
  """Returns the Sequence objects cyclically split into n length tuples.
29
47
 
30
48
  See Also
@@ -33,18 +51,17 @@ def adjacent_n_tuples(objects: Sequence, n: int) -> zip:
33
51
 
34
52
  Examples
35
53
  --------
36
- Normal usage::
54
+ .. code-block:: pycon
37
55
 
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)]
56
+ >>> list(adjacent_n_tuples([1, 2, 3, 4], 2))
57
+ [(1, 2), (2, 3), (3, 4), (4, 1)]
58
+ >>> list(adjacent_n_tuples([1, 2, 3, 4], 3))
59
+ [(1, 2, 3), (2, 3, 4), (3, 4, 1), (4, 1, 2)]
43
60
  """
44
61
  return zip(*([*objects[k:], *objects[:k]] for k in range(n)))
45
62
 
46
63
 
47
- def adjacent_pairs(objects: Sequence) -> zip:
64
+ def adjacent_pairs(objects: Sequence[T]) -> zip[tuple[T, ...]]:
48
65
  """Alias for ``adjacent_n_tuples(objects, 2)``.
49
66
 
50
67
  See Also
@@ -53,24 +70,24 @@ def adjacent_pairs(objects: Sequence) -> zip:
53
70
 
54
71
  Examples
55
72
  --------
56
- Normal usage::
73
+ .. code-block:: pycon
57
74
 
58
- list(adjacent_pairs([1, 2, 3, 4]))
59
- # returns [(1, 2), (2, 3), (3, 4), (4, 1)]
75
+ >>> list(adjacent_pairs([1, 2, 3, 4]))
76
+ [(1, 2), (2, 3), (3, 4), (4, 1)]
60
77
  """
61
78
  return adjacent_n_tuples(objects, 2)
62
79
 
63
80
 
64
- def all_elements_are_instances(iterable: Iterable, Class) -> bool:
81
+ def all_elements_are_instances(iterable: Iterable[object], Class: type[object]) -> bool:
65
82
  """Returns ``True`` if all elements of iterable are instances of Class.
66
83
  False otherwise.
67
84
  """
68
- return all([isinstance(e, Class) for e in iterable])
85
+ return all(isinstance(e, Class) for e in iterable)
69
86
 
70
87
 
71
88
  def batch_by_property(
72
- items: Sequence, property_func: Callable
73
- ) -> list[tuple[list, Any]]:
89
+ items: Iterable[T], property_func: Callable[[T], U]
90
+ ) -> list[tuple[list[T], U | None]]:
74
91
  """Takes in a Sequence, and returns a list of tuples, (batch, prop)
75
92
  such that all items in a batch have the same output when
76
93
  put into the Callable property_func, and such that chaining all these
@@ -79,13 +96,13 @@ def batch_by_property(
79
96
 
80
97
  Examples
81
98
  --------
82
- Normal usage::
99
+ .. code-block:: pycon
83
100
 
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)]
101
+ >>> batch_by_property([(1, 2), (3, 4), (5, 6, 7), (8, 9)], len)
102
+ [([(1, 2), (3, 4)], 2), ([(5, 6, 7)], 3), ([(8, 9)], 2)]
86
103
  """
87
- batch_prop_pairs = []
88
- curr_batch = []
104
+ batch_prop_pairs: list[tuple[list[T], U | None]] = []
105
+ curr_batch: list[T] = []
89
106
  curr_prop = None
90
107
  for item in items:
91
108
  prop = property_func(item)
@@ -103,67 +120,84 @@ def batch_by_property(
103
120
  return batch_prop_pairs
104
121
 
105
122
 
106
- def concatenate_lists(*list_of_lists: Iterable) -> list:
123
+ def concatenate_lists(*list_of_lists: Iterable[T]) -> list[T]:
107
124
  """Combines the Iterables provided as arguments into one list.
108
125
 
109
126
  Examples
110
127
  --------
111
- Normal usage::
128
+ .. code-block:: pycon
112
129
 
113
- concatenate_lists([1, 2], [3, 4], [5])
114
- # returns [1, 2, 3, 4, 5]
130
+ >>> concatenate_lists([1, 2], [3, 4], [5])
131
+ [1, 2, 3, 4, 5]
115
132
  """
116
133
  return [item for lst in list_of_lists for item in lst]
117
134
 
118
135
 
119
- def list_difference_update(l1: Iterable, l2: Iterable) -> list:
136
+ def list_difference_update(l1: Iterable[T], l2: Iterable[T]) -> list[T]:
120
137
  """Returns a list containing all the elements of l1 not in l2.
121
138
 
122
139
  Examples
123
140
  --------
124
- Normal usage::
141
+ .. code-block:: pycon
125
142
 
126
- list_difference_update([1, 2, 3, 4], [2, 4])
127
- # returns [1, 3]
143
+ >>> list_difference_update([1, 2, 3, 4], [2, 4])
144
+ [1, 3]
128
145
  """
129
146
  return [e for e in l1 if e not in l2]
130
147
 
131
148
 
132
- def list_update(l1: Iterable, l2: Iterable) -> list:
149
+ def list_update(l1: Iterable[T], l2: Iterable[T]) -> list[T]:
133
150
  """Used instead of ``set.update()`` to maintain order,
134
151
  making sure duplicates are removed from l1, not l2.
135
152
  Removes overlap of l1 and l2 and then concatenates l2 unchanged.
136
153
 
137
154
  Examples
138
155
  --------
139
- Normal usage::
156
+ .. code-block:: pycon
140
157
 
141
- list_update([1, 2, 3], [2, 4, 4])
142
- # returns [1, 3, 2, 4, 4]
158
+ >>> list_update([1, 2, 3], [2, 4, 4])
159
+ [1, 3, 2, 4, 4]
143
160
  """
144
161
  return [e for e in l1 if e not in l2] + list(l2)
145
162
 
146
163
 
147
- def listify(obj) -> list:
164
+ @overload
165
+ def listify(obj: str) -> list[str]: ...
166
+
167
+
168
+ @overload
169
+ def listify(obj: Iterable[T]) -> list[T]: ...
170
+
171
+
172
+ @overload
173
+ def listify(obj: T) -> list[T]: ...
174
+
175
+
176
+ def listify(obj: str | Iterable[T] | T) -> list[str] | list[T]:
148
177
  """Converts obj to a list intelligently.
149
178
 
150
179
  Examples
151
180
  --------
152
- Normal usage::
153
-
154
- listify('str') # ['str']
155
- listify((1, 2)) # [1, 2]
156
- listify(len) # [<built-in function len>]
181
+ .. code-block:: pycon
182
+
183
+ >>> listify("str")
184
+ ['str']
185
+ >>> listify((1, 2))
186
+ [1, 2]
187
+ >>> listify(len)
188
+ [<built-in function len>]
157
189
  """
158
190
  if isinstance(obj, str):
159
191
  return [obj]
160
- try:
192
+ if isinstance(obj, Iterable):
161
193
  return list(obj)
162
- except TypeError:
194
+ else:
163
195
  return [obj]
164
196
 
165
197
 
166
- def make_even(iterable_1: Iterable, iterable_2: Iterable) -> tuple[list, list]:
198
+ def make_even(
199
+ iterable_1: Iterable[T], iterable_2: Iterable[U]
200
+ ) -> tuple[list[T], list[U]]:
167
201
  """Extends the shorter of the two iterables with duplicate values until its
168
202
  length is equal to the longer iterable (favours earlier elements).
169
203
 
@@ -173,13 +207,13 @@ def make_even(iterable_1: Iterable, iterable_2: Iterable) -> tuple[list, list]:
173
207
 
174
208
  Examples
175
209
  --------
176
- Normal usage::
210
+ .. code-block:: pycon
177
211
 
178
- make_even([1, 2], [3, 4, 5, 6])
212
+ >>> make_even([1, 2], [3, 4, 5, 6])
179
213
  ([1, 1, 2, 2], [3, 4, 5, 6])
180
214
 
181
- make_even([1, 2], [3, 4, 5, 6, 7])
182
- # ([1, 1, 1, 2, 2], [3, 4, 5, 6, 7])
215
+ >>> make_even([1, 2], [3, 4, 5, 6, 7])
216
+ ([1, 1, 1, 2, 2], [3, 4, 5, 6, 7])
183
217
  """
184
218
  list_1, list_2 = list(iterable_1), list(iterable_2)
185
219
  len_list_1 = len(list_1)
@@ -192,8 +226,8 @@ def make_even(iterable_1: Iterable, iterable_2: Iterable) -> tuple[list, list]:
192
226
 
193
227
 
194
228
  def make_even_by_cycling(
195
- iterable_1: Collection, iterable_2: Collection
196
- ) -> tuple[list, list]:
229
+ iterable_1: Collection[T], iterable_2: Collection[U]
230
+ ) -> tuple[list[T], list[U]]:
197
231
  """Extends the shorter of the two iterables with duplicate values until its
198
232
  length is equal to the longer iterable (cycles over shorter iterable).
199
233
 
@@ -203,13 +237,13 @@ def make_even_by_cycling(
203
237
 
204
238
  Examples
205
239
  --------
206
- Normal usage::
240
+ .. code-block:: pycon
207
241
 
208
- make_even_by_cycling([1, 2], [3, 4, 5, 6])
242
+ >>> make_even_by_cycling([1, 2], [3, 4, 5, 6])
209
243
  ([1, 2, 1, 2], [3, 4, 5, 6])
210
244
 
211
- make_even_by_cycling([1, 2], [3, 4, 5, 6, 7])
212
- # ([1, 2, 1, 2, 1], [3, 4, 5, 6, 7])
245
+ >>> make_even_by_cycling([1, 2], [3, 4, 5, 6, 7])
246
+ ([1, 2, 1, 2, 1], [3, 4, 5, 6, 7])
213
247
  """
214
248
  length = max(len(iterable_1), len(iterable_2))
215
249
  cycle1 = it.cycle(iterable_1)
@@ -220,7 +254,7 @@ def make_even_by_cycling(
220
254
  )
221
255
 
222
256
 
223
- def remove_list_redundancies(lst: Reversible) -> list:
257
+ def remove_list_redundancies(lst: Reversible[H]) -> list[H]:
224
258
  """Used instead of ``list(set(l))`` to maintain order.
225
259
  Keeps the last occurrence of each element.
226
260
  """
@@ -234,21 +268,21 @@ def remove_list_redundancies(lst: Reversible) -> list:
234
268
  return reversed_result
235
269
 
236
270
 
237
- def remove_nones(sequence: Iterable) -> list:
271
+ def remove_nones(sequence: Iterable[T | None]) -> list[T]:
238
272
  """Removes elements where bool(x) evaluates to False.
239
273
 
240
274
  Examples
241
275
  --------
242
- Normal usage::
276
+ .. code-block:: pycon
243
277
 
244
- remove_nones(['m', '', 'l', 0, 42, False, True])
245
- # ['m', 'l', 42, True]
278
+ >>> remove_nones(["m", "", "l", 0, 42, False, True])
279
+ ['m', 'l', 42, True]
246
280
  """
247
281
  # Note this is redundant with it.chain
248
282
  return [x for x in sequence if x]
249
283
 
250
284
 
251
- def resize_array(nparray: np.ndarray, length: int) -> np.ndarray:
285
+ def resize_array(nparray: npt.NDArray[F], length: int) -> npt.NDArray[F]:
252
286
  """Extends/truncates nparray so that ``len(result) == length``.
253
287
  The elements of nparray are cycled to achieve the desired length.
254
288
 
@@ -259,7 +293,7 @@ def resize_array(nparray: np.ndarray, length: int) -> np.ndarray:
259
293
 
260
294
  Examples
261
295
  --------
262
- Normal usage::
296
+ .. code-block:: pycon
263
297
 
264
298
  >>> points = np.array([[1, 2], [3, 4]])
265
299
  >>> resize_array(points, 1)
@@ -277,7 +311,9 @@ def resize_array(nparray: np.ndarray, length: int) -> np.ndarray:
277
311
  return np.resize(nparray, (length, *nparray.shape[1:]))
278
312
 
279
313
 
280
- def resize_preserving_order(nparray: np.ndarray, length: int) -> np.ndarray:
314
+ def resize_preserving_order(
315
+ nparray: npt.NDArray[np.float64], length: int
316
+ ) -> npt.NDArray[np.float64]:
281
317
  """Extends/truncates nparray so that ``len(result) == length``.
282
318
  The elements of nparray are duplicated to achieve the desired length
283
319
  (favours earlier elements).
@@ -291,21 +327,19 @@ def resize_preserving_order(nparray: np.ndarray, length: int) -> np.ndarray:
291
327
 
292
328
  Examples
293
329
  --------
294
- Normal usage::
295
-
296
- resize_preserving_order(np.array([]), 5)
297
- # np.array([0., 0., 0., 0., 0.])
330
+ .. code-block:: pycon
298
331
 
299
- nparray = np.array([[1, 2],
300
- [3, 4]])
332
+ >>> resize_preserving_order(np.array([]), 5)
333
+ array([0., 0., 0., 0., 0.])
301
334
 
302
- resize_preserving_order(nparray, 1)
303
- # np.array([[1, 2]])
335
+ >>> nparray = np.array([[1, 2], [3, 4]])
336
+ >>> resize_preserving_order(nparray, 1)
337
+ array([[1, 2]])
304
338
 
305
- resize_preserving_order(nparray, 3)
306
- # np.array([[1, 2],
307
- # [1, 2],
308
- # [3, 4]])
339
+ >>> resize_preserving_order(nparray, 3)
340
+ array([[1, 2],
341
+ [1, 2],
342
+ [3, 4]])
309
343
  """
310
344
  if len(nparray) == 0:
311
345
  return np.zeros((length, *nparray.shape[1:]))
@@ -315,7 +349,7 @@ def resize_preserving_order(nparray: np.ndarray, length: int) -> np.ndarray:
315
349
  return nparray[indices]
316
350
 
317
351
 
318
- def resize_with_interpolation(nparray: np.ndarray, length: int) -> np.ndarray:
352
+ def resize_with_interpolation(nparray: npt.NDArray[F], length: int) -> npt.NDArray[F]:
319
353
  """Extends/truncates nparray so that ``len(result) == length``.
320
354
  New elements are interpolated to achieve the desired length.
321
355
 
@@ -329,39 +363,29 @@ def resize_with_interpolation(nparray: np.ndarray, length: int) -> np.ndarray:
329
363
 
330
364
  Examples
331
365
  --------
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. ]])
366
+ .. code-block:: pycon
367
+
368
+ >>> nparray = np.array([[1, 2], [3, 4]])
369
+ >>> resize_with_interpolation(nparray, 1)
370
+ array([[1., 2.]])
371
+ >>> resize_with_interpolation(nparray, 4)
372
+ array([[1. , 2. ],
373
+ [1.66666667, 2.66666667],
374
+ [2.33333333, 3.33333333],
375
+ [3. , 4. ]])
376
+ >>> nparray = np.array([[[1, 2], [3, 4]]])
377
+ >>> nparray = np.array([[1, 2], [3, 4], [5, 6]])
378
+ >>> resize_with_interpolation(nparray, 4)
379
+ array([[1. , 2. ],
380
+ [2.33333333, 3.33333333],
381
+ [3.66666667, 4.66666667],
382
+ [5. , 6. ]])
383
+ >>> nparray = np.array([[1, 2], [3, 4], [1, 2]])
384
+ >>> resize_with_interpolation(nparray, 4)
385
+ array([[1. , 2. ],
386
+ [2.33333333, 3.33333333],
387
+ [2.33333333, 3.33333333],
388
+ [1. , 2. ]])
365
389
  """
366
390
  if len(nparray) == length:
367
391
  return nparray
@@ -375,7 +399,7 @@ def resize_with_interpolation(nparray: np.ndarray, length: int) -> np.ndarray:
375
399
  )
376
400
 
377
401
 
378
- def stretch_array_to_length(nparray: np.ndarray, length: int) -> np.ndarray:
402
+ def stretch_array_to_length(nparray: npt.NDArray[F], length: int) -> npt.NDArray[F]:
379
403
  # todo: is this the same as resize_preserving_order()?
380
404
  curr_len = len(nparray)
381
405
  if curr_len > length:
@@ -385,35 +409,54 @@ def stretch_array_to_length(nparray: np.ndarray, length: int) -> np.ndarray:
385
409
  return nparray[indices.astype(int)]
386
410
 
387
411
 
388
- def tuplify(obj) -> tuple:
412
+ @overload
413
+ def tuplify(obj: str) -> tuple[str]: ...
414
+
415
+
416
+ @overload
417
+ def tuplify(obj: Iterable[T]) -> tuple[T]: ...
418
+
419
+
420
+ @overload
421
+ def tuplify(obj: T) -> tuple[T]: ...
422
+
423
+
424
+ def tuplify(obj: str | Iterable[T] | T) -> tuple[str] | tuple[T]:
389
425
  """Converts obj to a tuple intelligently.
390
426
 
391
427
  Examples
392
428
  --------
393
- Normal usage::
394
-
395
- tuplify('str') # ('str',)
396
- tuplify([1, 2]) # (1, 2)
397
- tuplify(len) # (<built-in function len>,)
429
+ .. code-block:: pycon
430
+
431
+ >>> tuplify("str")
432
+ ('str',)
433
+ >>> tuplify([1, 2])
434
+ (1, 2)
435
+ >>> tuplify(len)
436
+ (<built-in function len>,)
398
437
  """
399
438
  if isinstance(obj, str):
400
439
  return (obj,)
401
- try:
440
+ if isinstance(obj, Iterable):
402
441
  return tuple(obj)
403
- except TypeError:
442
+ else:
404
443
  return (obj,)
405
444
 
406
445
 
407
- def uniq_chain(*args: Iterable) -> Generator:
446
+ def uniq_chain(*args: Iterable[T]) -> Generator[T, None, None]:
408
447
  """Returns a generator that yields all unique elements of the Iterables
409
448
  provided via args in the order provided.
410
449
 
411
450
  Examples
412
451
  --------
413
- Normal usage::
414
-
415
- uniq_chain([1, 2], [2, 3], [1, 4, 4])
416
- # yields 1, 2, 3, 4
452
+ .. code-block:: pycon
453
+
454
+ >>> gen = uniq_chain([1, 2], [2, 3], [1, 4, 4])
455
+ >>> from collections.abc import Generator
456
+ >>> isinstance(gen, Generator)
457
+ True
458
+ >>> tuple(gen)
459
+ (1, 2, 3, 4)
417
460
  """
418
461
  unique_items = set()
419
462
  for x in it.chain(*args):
manim/utils/module_ops.py CHANGED
@@ -2,18 +2,29 @@ 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, Any, 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 manim.scene.scene import Scene
15
23
 
16
- def get_module(file_name: Path):
24
+ __all__ = ["scene_classes_from_file"]
25
+
26
+
27
+ def get_module(file_name: Path) -> types.ModuleType:
17
28
  if str(file_name) == "-":
18
29
  module = types.ModuleType("input_scenes")
19
30
  logger.info(
@@ -46,19 +57,22 @@ def get_module(file_name: Path):
46
57
  )
47
58
 
48
59
  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
60
+ if isinstance(spec, importlib.machinery.ModuleSpec):
61
+ module = importlib.util.module_from_spec(spec)
62
+ sys.modules[module_name] = module
63
+ sys.path.insert(0, str(file_name.parent.absolute()))
64
+ assert spec.loader
65
+ spec.loader.exec_module(module)
66
+ return module
67
+ raise FileNotFoundError(f"{file_name} not found")
54
68
  else:
55
69
  raise FileNotFoundError(f"{file_name} not found")
56
70
 
57
71
 
58
- def get_scene_classes_from_module(module):
72
+ def get_scene_classes_from_module(module: types.ModuleType) -> list[type[Scene]]:
59
73
  from ..scene.scene import Scene
60
74
 
61
- def is_child_scene(obj, module):
75
+ def is_child_scene(obj: Any, module: types.ModuleType) -> bool:
62
76
  return (
63
77
  inspect.isclass(obj)
64
78
  and issubclass(obj, Scene)
@@ -72,9 +86,9 @@ def get_scene_classes_from_module(module):
72
86
  ]
73
87
 
74
88
 
75
- def get_scenes_to_render(scene_classes):
89
+ def get_scenes_to_render(scene_classes: list[type[Scene]]) -> list[type[Scene]]:
76
90
  if not scene_classes:
77
- logger.error(constants.NO_SCENE_MESSAGE)
91
+ logger.error(NO_SCENE_MESSAGE)
78
92
  return []
79
93
  if config["write_all"]:
80
94
  return scene_classes
@@ -87,7 +101,7 @@ def get_scenes_to_render(scene_classes):
87
101
  found = True
88
102
  break
89
103
  if not found and (scene_name != ""):
90
- logger.error(constants.SCENE_NOT_FOUND_MESSAGE.format(scene_name))
104
+ logger.error(SCENE_NOT_FOUND_MESSAGE.format(scene_name))
91
105
  if result:
92
106
  return result
93
107
  if len(scene_classes) == 1:
@@ -96,7 +110,7 @@ def get_scenes_to_render(scene_classes):
96
110
  return prompt_user_for_choice(scene_classes)
97
111
 
98
112
 
99
- def prompt_user_for_choice(scene_classes):
113
+ def prompt_user_for_choice(scene_classes: list[type[Scene]]) -> list[type[Scene]]:
100
114
  num_to_class = {}
101
115
  SceneFileWriter.force_output_as_scene_name = True
102
116
  for count, scene_class in enumerate(scene_classes, 1):
@@ -105,16 +119,22 @@ def prompt_user_for_choice(scene_classes):
105
119
  num_to_class[count] = scene_class
106
120
  try:
107
121
  user_input = console.input(
108
- f"[log.message] {constants.CHOOSE_NUMBER_MESSAGE} [/log.message]",
122
+ f"[log.message] {CHOOSE_NUMBER_MESSAGE} [/log.message]",
109
123
  )
110
- scene_classes = [
111
- num_to_class[int(num_str)]
112
- for num_str in re.split(r"\s*,\s*", user_input.strip())
124
+
125
+ if user_input == "*":
126
+ selected_scenes_classes = scene_classes
127
+ else:
128
+ selected_scenes_classes = [
129
+ num_to_class[int(num_str)]
130
+ for num_str in re.split(r"\s*,\s*", user_input.strip())
131
+ ]
132
+ config["scene_names"] = [
133
+ scene_class.__name__ for scene_class in selected_scenes_classes
113
134
  ]
114
- config["scene_names"] = [scene_class.__name__ for scene_class in scene_classes]
115
- return scene_classes
135
+ return selected_scenes_classes
116
136
  except KeyError:
117
- logger.error(constants.INVALID_NUMBER_MESSAGE)
137
+ logger.error(INVALID_NUMBER_MESSAGE)
118
138
  sys.exit(2)
119
139
  except EOFError:
120
140
  sys.exit(1)
@@ -123,9 +143,31 @@ def prompt_user_for_choice(scene_classes):
123
143
  sys.exit(1)
124
144
 
125
145
 
146
+ @overload
147
+ def scene_classes_from_file(
148
+ file_path: Path, require_single_scene: bool, full_list: Literal[True]
149
+ ) -> list[type[Scene]]: ...
150
+
151
+
152
+ @overload
153
+ def scene_classes_from_file(
154
+ file_path: Path,
155
+ require_single_scene: Literal[True],
156
+ full_list: Literal[False] = False,
157
+ ) -> type[Scene]: ...
158
+
159
+
160
+ @overload
161
+ def scene_classes_from_file(
162
+ file_path: Path,
163
+ require_single_scene: Literal[False] = False,
164
+ full_list: Literal[False] = False,
165
+ ) -> list[type[Scene]]: ...
166
+
167
+
126
168
  def scene_classes_from_file(
127
- file_path: Path, require_single_scene=False, full_list=False
128
- ):
169
+ file_path: Path, require_single_scene: bool = False, full_list: bool = False
170
+ ) -> type[Scene] | list[type[Scene]]:
129
171
  module = get_module(file_path)
130
172
  all_scene_classes = get_scene_classes_from_module(module)
131
173
  if full_list: