manim 0.18.1__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 (129) hide show
  1. manim/__main__.py +45 -12
  2. manim/_config/__init__.py +2 -2
  3. manim/_config/cli_colors.py +8 -4
  4. manim/_config/default.cfg +0 -2
  5. manim/_config/logger_utils.py +5 -0
  6. manim/_config/utils.py +29 -38
  7. manim/animation/animation.py +148 -8
  8. manim/animation/composition.py +16 -13
  9. manim/animation/creation.py +184 -8
  10. manim/animation/fading.py +5 -8
  11. manim/animation/indication.py +93 -26
  12. manim/animation/movement.py +21 -3
  13. manim/animation/rotation.py +2 -1
  14. manim/animation/specialized.py +3 -5
  15. manim/animation/speedmodifier.py +3 -3
  16. manim/animation/transform.py +4 -5
  17. manim/animation/updaters/mobject_update_utils.py +17 -14
  18. manim/camera/camera.py +2 -2
  19. manim/cli/__init__.py +17 -0
  20. manim/cli/cfg/group.py +52 -36
  21. manim/cli/checkhealth/checks.py +92 -76
  22. manim/cli/checkhealth/commands.py +12 -5
  23. manim/cli/default_group.py +148 -24
  24. manim/cli/init/commands.py +28 -23
  25. manim/cli/plugins/commands.py +13 -3
  26. manim/cli/render/commands.py +47 -42
  27. manim/cli/render/global_options.py +43 -9
  28. manim/cli/render/render_options.py +84 -19
  29. manim/constants.py +11 -4
  30. manim/mobject/frame.py +0 -1
  31. manim/mobject/geometry/arc.py +109 -75
  32. manim/mobject/geometry/boolean_ops.py +20 -17
  33. manim/mobject/geometry/labeled.py +300 -77
  34. manim/mobject/geometry/line.py +120 -60
  35. manim/mobject/geometry/polygram.py +109 -25
  36. manim/mobject/geometry/shape_matchers.py +35 -15
  37. manim/mobject/geometry/tips.py +36 -27
  38. manim/mobject/graph.py +48 -40
  39. manim/mobject/graphing/coordinate_systems.py +110 -45
  40. manim/mobject/graphing/functions.py +16 -10
  41. manim/mobject/graphing/number_line.py +23 -9
  42. manim/mobject/graphing/probability.py +2 -10
  43. manim/mobject/graphing/scale.py +6 -5
  44. manim/mobject/matrix.py +17 -19
  45. manim/mobject/mobject.py +149 -103
  46. manim/mobject/opengl/opengl_geometry.py +4 -8
  47. manim/mobject/opengl/opengl_mobject.py +506 -343
  48. manim/mobject/opengl/opengl_point_cloud_mobject.py +3 -7
  49. manim/mobject/opengl/opengl_surface.py +1 -2
  50. manim/mobject/opengl/opengl_vectorized_mobject.py +27 -65
  51. manim/mobject/svg/brace.py +61 -13
  52. manim/mobject/svg/svg_mobject.py +2 -1
  53. manim/mobject/table.py +11 -12
  54. manim/mobject/text/code_mobject.py +186 -550
  55. manim/mobject/text/numbers.py +7 -7
  56. manim/mobject/text/tex_mobject.py +22 -13
  57. manim/mobject/text/text_mobject.py +29 -20
  58. manim/mobject/three_d/polyhedra.py +98 -1
  59. manim/mobject/three_d/three_dimensions.py +59 -31
  60. manim/mobject/types/image_mobject.py +37 -23
  61. manim/mobject/types/point_cloud_mobject.py +103 -67
  62. manim/mobject/types/vectorized_mobject.py +387 -214
  63. manim/mobject/value_tracker.py +2 -1
  64. manim/mobject/vector_field.py +2 -4
  65. manim/opengl/__init__.py +3 -3
  66. manim/plugins/__init__.py +2 -3
  67. manim/plugins/plugins_flags.py +3 -3
  68. manim/renderer/cairo_renderer.py +11 -11
  69. manim/renderer/opengl_renderer.py +19 -20
  70. manim/renderer/shader.py +2 -3
  71. manim/renderer/shader_wrapper.py +3 -2
  72. manim/scene/moving_camera_scene.py +23 -0
  73. manim/scene/scene.py +72 -41
  74. manim/scene/scene_file_writer.py +313 -164
  75. manim/scene/section.py +15 -15
  76. manim/scene/three_d_scene.py +8 -15
  77. manim/scene/vector_space_scene.py +3 -6
  78. manim/typing.py +326 -66
  79. manim/utils/bezier.py +1658 -381
  80. manim/utils/caching.py +11 -5
  81. manim/utils/color/AS2700.py +2 -0
  82. manim/utils/color/BS381.py +2 -0
  83. manim/utils/color/DVIPSNAMES.py +96 -0
  84. manim/utils/color/SVGNAMES.py +179 -0
  85. manim/utils/color/X11.py +3 -0
  86. manim/utils/color/XKCD.py +2 -0
  87. manim/utils/color/__init__.py +8 -5
  88. manim/utils/color/core.py +818 -301
  89. manim/utils/color/manim_colors.py +7 -9
  90. manim/utils/commands.py +40 -19
  91. manim/utils/config_ops.py +18 -13
  92. manim/utils/debug.py +8 -6
  93. manim/utils/deprecation.py +92 -43
  94. manim/utils/docbuild/autoaliasattr_directive.py +45 -8
  95. manim/utils/docbuild/autocolor_directive.py +12 -13
  96. manim/utils/docbuild/manim_directive.py +35 -29
  97. manim/utils/docbuild/module_parsing.py +74 -27
  98. manim/utils/family.py +3 -3
  99. manim/utils/family_ops.py +12 -4
  100. manim/utils/file_ops.py +22 -16
  101. manim/utils/hashing.py +7 -7
  102. manim/utils/images.py +10 -4
  103. manim/utils/ipython_magic.py +12 -8
  104. manim/utils/iterables.py +161 -119
  105. manim/utils/module_ops.py +55 -19
  106. manim/utils/opengl.py +68 -23
  107. manim/utils/parameter_parsing.py +3 -2
  108. manim/utils/paths.py +11 -5
  109. manim/utils/polylabel.py +168 -0
  110. manim/utils/qhull.py +218 -0
  111. manim/utils/rate_functions.py +69 -32
  112. manim/utils/simple_functions.py +24 -15
  113. manim/utils/sounds.py +7 -1
  114. manim/utils/space_ops.py +48 -37
  115. manim/utils/testing/_frames_testers.py +13 -8
  116. manim/utils/testing/_show_diff.py +5 -3
  117. manim/utils/testing/_test_class_makers.py +33 -18
  118. manim/utils/testing/frames_comparison.py +20 -14
  119. manim/utils/tex.py +4 -2
  120. manim/utils/tex_file_writing.py +45 -45
  121. manim/utils/tex_templates.py +1 -1
  122. manim/utils/unit.py +6 -5
  123. {manim-0.18.1.dist-info → manim-0.19.0.dist-info}/METADATA +16 -9
  124. manim-0.19.0.dist-info/RECORD +221 -0
  125. {manim-0.18.1.dist-info → manim-0.19.0.dist-info}/WHEEL +1 -1
  126. manim-0.18.1.dist-info/RECORD +0 -217
  127. {manim-0.18.1.dist-info → manim-0.19.0.dist-info}/LICENSE +0 -0
  128. {manim-0.18.1.dist-info → manim-0.19.0.dist-info}/LICENSE.community +0 -0
  129. {manim-0.18.1.dist-info → manim-0.19.0.dist-info}/entry_points.txt +0 -0
@@ -105,21 +105,25 @@ __all__ = [
105
105
  "exponential_decay",
106
106
  ]
107
107
 
108
- import typing
109
108
  from functools import wraps
110
109
  from math import sqrt
110
+ from typing import Any, Protocol
111
111
 
112
112
  import numpy as np
113
113
 
114
- from ..utils.bezier import bezier
115
- from ..utils.simple_functions import sigmoid
114
+ from manim.utils.simple_functions import sigmoid
115
+
116
+
117
+ # TODO: rewrite this to use ParamSpec when Python 3.9 is out of life
118
+ class RateFunction(Protocol):
119
+ def __call__(self, t: float, *args: Any, **kwargs: Any) -> float: ...
116
120
 
117
121
 
118
122
  # This is a decorator that makes sure any function it's used on will
119
123
  # return 0 if t<0 and 1 if t>1.
120
- def unit_interval(function):
124
+ def unit_interval(function: RateFunction) -> RateFunction:
121
125
  @wraps(function)
122
- def wrapper(t, *args, **kwargs):
126
+ def wrapper(t: float, *args: Any, **kwargs: Any) -> float:
123
127
  if 0 <= t <= 1:
124
128
  return function(t, *args, **kwargs)
125
129
  elif t < 0:
@@ -132,9 +136,9 @@ def unit_interval(function):
132
136
 
133
137
  # This is a decorator that makes sure any function it's used on will
134
138
  # return 0 if t<0 or t>1.
135
- def zero(function):
139
+ def zero(function: RateFunction) -> RateFunction:
136
140
  @wraps(function)
137
- def wrapper(t, *args, **kwargs):
141
+ def wrapper(t: float, *args: Any, **kwargs: Any) -> float:
138
142
  if 0 <= t <= 1:
139
143
  return function(t, *args, **kwargs)
140
144
  else:
@@ -178,7 +182,7 @@ def smoothererstep(t: float) -> float:
178
182
  The 1st, 2nd and 3rd derivatives (speed, acceleration and jerk) are zero at the endpoints.
179
183
  https://en.wikipedia.org/wiki/Smoothstep
180
184
  """
181
- alpha = 0
185
+ alpha: float = 0
182
186
  if 0 < t < 1:
183
187
  alpha = 35 * t**4 - 84 * t**5 + 70 * t**6 - 20 * t**7
184
188
  elif t >= 1:
@@ -198,7 +202,8 @@ def rush_from(t: float, inflection: float = 10.0) -> float:
198
202
 
199
203
  @unit_interval
200
204
  def slow_into(t: float) -> float:
201
- return np.sqrt(1 - (1 - t) * (1 - t))
205
+ val: float = np.sqrt(1 - (1 - t) * (1 - t))
206
+ return val
202
207
 
203
208
 
204
209
  @unit_interval
@@ -217,7 +222,7 @@ def there_and_back(t: float, inflection: float = 10.0) -> float:
217
222
 
218
223
  @zero
219
224
  def there_and_back_with_pause(t: float, pause_ratio: float = 1.0 / 3) -> float:
220
- a = 1.0 / pause_ratio
225
+ a = 2.0 / (1.0 - pause_ratio)
221
226
  if t < 0.5 - pause_ratio / 2:
222
227
  return smooth(a * t)
223
228
  elif t < 0.5 + pause_ratio / 2:
@@ -230,40 +235,60 @@ def there_and_back_with_pause(t: float, pause_ratio: float = 1.0 / 3) -> float:
230
235
  def running_start(
231
236
  t: float,
232
237
  pull_factor: float = -0.5,
233
- ) -> typing.Iterable: # what is func return type?
234
- return bezier([0, 0, pull_factor, pull_factor, 1, 1, 1])(t)
238
+ ) -> float:
239
+ t2 = t * t
240
+ t3 = t2 * t
241
+ t4 = t3 * t
242
+ t5 = t4 * t
243
+ t6 = t5 * t
244
+ mt = 1 - t
245
+ mt2 = mt * mt
246
+ mt3 = mt2 * mt
247
+ mt4 = mt3 * mt
248
+
249
+ # This is equivalent to creating a Bézier with [0, 0, pull_factor, pull_factor, 1, 1, 1]
250
+ # and evaluating it at t.
251
+ return (
252
+ 15 * t2 * mt4 * pull_factor
253
+ + 20 * t3 * mt3 * pull_factor
254
+ + 15 * t4 * mt2
255
+ + 6 * t5 * mt
256
+ + t6
257
+ )
235
258
 
236
259
 
237
260
  def not_quite_there(
238
- func: typing.Callable[[float], float] = smooth,
261
+ func: RateFunction = smooth,
239
262
  proportion: float = 0.7,
240
- ) -> typing.Callable[[float], float]:
241
- def result(t):
242
- return proportion * func(t)
263
+ ) -> RateFunction:
264
+ def result(t: float, *args: Any, **kwargs: Any) -> float:
265
+ return proportion * func(t, *args, **kwargs)
243
266
 
244
267
  return result
245
268
 
246
269
 
247
270
  @zero
248
271
  def wiggle(t: float, wiggles: float = 2) -> float:
249
- return there_and_back(t) * np.sin(wiggles * np.pi * t)
272
+ val: float = np.sin(wiggles * np.pi * t)
273
+ return there_and_back(t) * val
250
274
 
251
275
 
252
276
  def squish_rate_func(
253
- func: typing.Callable[[float], float],
277
+ func: RateFunction,
254
278
  a: float = 0.4,
255
279
  b: float = 0.6,
256
- ) -> typing.Callable[[float], float]:
257
- def result(t):
280
+ ) -> RateFunction:
281
+ def result(t: float, *args: Any, **kwargs: Any) -> float:
258
282
  if a == b:
259
283
  return a
260
284
 
261
285
  if t < a:
262
- return func(0)
286
+ new_t = 0.0
263
287
  elif t > b:
264
- return func(1)
288
+ new_t = 1.0
265
289
  else:
266
- return func((t - a) / (b - a))
290
+ new_t = (t - a) / (b - a)
291
+ return func(new_t, *args, **kwargs)
267
292
 
268
293
  return result
269
294
 
@@ -276,29 +301,37 @@ def squish_rate_func(
276
301
 
277
302
  @unit_interval
278
303
  def lingering(t: float) -> float:
279
- return squish_rate_func(lambda t: t, 0, 0.8)(t)
304
+ def identity(t: float) -> float:
305
+ return t
306
+
307
+ # TODO: Isn't this just 0.8 * t?
308
+ return squish_rate_func(identity, 0, 0.8)(t)
280
309
 
281
310
 
282
311
  @unit_interval
283
312
  def exponential_decay(t: float, half_life: float = 0.1) -> float:
284
313
  # The half-life should be rather small to minimize
285
314
  # the cut-off error at the end
286
- return 1 - np.exp(-t / half_life)
315
+ val: float = 1 - np.exp(-t / half_life)
316
+ return val
287
317
 
288
318
 
289
319
  @unit_interval
290
320
  def ease_in_sine(t: float) -> float:
291
- return 1 - np.cos((t * np.pi) / 2)
321
+ val: float = 1 - np.cos((t * np.pi) / 2)
322
+ return val
292
323
 
293
324
 
294
325
  @unit_interval
295
326
  def ease_out_sine(t: float) -> float:
296
- return np.sin((t * np.pi) / 2)
327
+ val: float = np.sin((t * np.pi) / 2)
328
+ return val
297
329
 
298
330
 
299
331
  @unit_interval
300
332
  def ease_in_out_sine(t: float) -> float:
301
- return -(np.cos(np.pi * t) - 1) / 2
333
+ val: float = -(np.cos(np.pi * t) - 1) / 2
334
+ return val
302
335
 
303
336
 
304
337
  @unit_interval
@@ -435,7 +468,8 @@ def ease_in_elastic(t: float) -> float:
435
468
  elif t == 1:
436
469
  return 1
437
470
  else:
438
- return -pow(2, 10 * t - 10) * np.sin((t * 10 - 10.75) * c4)
471
+ val: float = -pow(2, 10 * t - 10) * np.sin((t * 10 - 10.75) * c4)
472
+ return val
439
473
 
440
474
 
441
475
  @unit_interval
@@ -446,7 +480,8 @@ def ease_out_elastic(t: float) -> float:
446
480
  elif t == 1:
447
481
  return 1
448
482
  else:
449
- return pow(2, -10 * t) * np.sin((t * 10 - 0.75) * c4) + 1
483
+ val: float = pow(2, -10 * t) * np.sin((t * 10 - 0.75) * c4) + 1
484
+ return val
450
485
 
451
486
 
452
487
  @unit_interval
@@ -457,9 +492,11 @@ def ease_in_out_elastic(t: float) -> float:
457
492
  elif t == 1:
458
493
  return 1
459
494
  elif t < 0.5:
460
- return -(pow(2, 20 * t - 10) * np.sin((20 * t - 11.125) * c5)) / 2
495
+ val: float = -(pow(2, 20 * t - 10) * np.sin((20 * t - 11.125) * c5)) / 2
496
+ return val
461
497
  else:
462
- return (pow(2, -20 * t + 10) * np.sin((20 * t - 11.125) * c5)) / 2 + 1
498
+ val = (pow(2, -20 * t + 10) * np.sin((20 * t - 11.125) * c5)) / 2 + 1
499
+ return val
463
500
 
464
501
 
465
502
  @unit_interval
@@ -10,22 +10,20 @@ __all__ = [
10
10
  ]
11
11
 
12
12
 
13
- import inspect
14
13
  from functools import lru_cache
15
- from types import MappingProxyType
16
- from typing import Callable
14
+ from typing import Any, Callable, Protocol, TypeVar
17
15
 
18
16
  import numpy as np
19
17
  from scipy import special
20
18
 
21
19
 
22
20
  def binary_search(
23
- function: Callable[[int | float], int | float],
24
- target: int | float,
25
- lower_bound: int | float,
26
- upper_bound: int | float,
27
- tolerance: int | float = 1e-4,
28
- ) -> int | float | None:
21
+ function: Callable[[float], float],
22
+ target: float,
23
+ lower_bound: float,
24
+ upper_bound: float,
25
+ tolerance: float = 1e-4,
26
+ ) -> float | None:
29
27
  """Searches for a value in a range by repeatedly dividing the range in half.
30
28
 
31
29
  To be more precise, performs numerical binary search to determine the
@@ -42,10 +40,10 @@ def binary_search(
42
40
  ::
43
41
 
44
42
  >>> solution = binary_search(lambda x: x**2 + 3*x + 1, 11, 0, 5)
45
- >>> abs(solution - 2) < 1e-4
43
+ >>> bool(abs(solution - 2) < 1e-4)
46
44
  True
47
45
  >>> solution = binary_search(lambda x: x**2 + 3*x + 1, 11, 0, 5, tolerance=0.01)
48
- >>> abs(solution - 2) < 0.01
46
+ >>> bool(abs(solution - 2) < 0.01)
49
47
  True
50
48
 
51
49
  Searching in the interval :math:`[0, 5]` for a target value of :math:`71`
@@ -56,7 +54,7 @@ def binary_search(
56
54
  """
57
55
  lh = lower_bound
58
56
  rh = upper_bound
59
- mh = np.mean(np.array([lh, rh]))
57
+ mh: float = np.mean(np.array([lh, rh]))
60
58
  while abs(rh - lh) > tolerance:
61
59
  mh = np.mean(np.array([lh, rh]))
62
60
  lx, mx, rx = (function(h) for h in (lh, mh, rh))
@@ -90,10 +88,20 @@ def choose(n: int, k: int) -> int:
90
88
  - https://en.wikipedia.org/wiki/Combination
91
89
  - https://docs.scipy.org/doc/scipy/reference/generated/scipy.special.comb.html
92
90
  """
93
- return special.comb(n, k, exact=True)
91
+ value: int = special.comb(n, k, exact=True)
92
+ return value
94
93
 
95
94
 
96
- def clip(a, min_a, max_a):
95
+ class Comparable(Protocol):
96
+ def __lt__(self, other: Any) -> bool: ...
97
+
98
+ def __gt__(self, other: Any) -> bool: ...
99
+
100
+
101
+ ComparableT = TypeVar("ComparableT", bound=Comparable) # noqa: Y001
102
+
103
+
104
+ def clip(a: ComparableT, min_a: ComparableT, max_a: ComparableT) -> ComparableT:
97
105
  """Clips ``a`` to the interval [``min_a``, ``max_a``].
98
106
 
99
107
  Accepts any comparable objects (i.e. those that support <, >).
@@ -127,4 +135,5 @@ def sigmoid(x: float) -> float:
127
135
  - https://en.wikipedia.org/wiki/Sigmoid_function
128
136
  - https://en.wikipedia.org/wiki/Logistic_function
129
137
  """
130
- return 1.0 / (1 + np.exp(-x))
138
+ value: float = 1.0 / (1 + np.exp(-x))
139
+ return value
manim/utils/sounds.py CHANGED
@@ -6,13 +6,19 @@ __all__ = [
6
6
  "get_full_sound_file_path",
7
7
  ]
8
8
 
9
+ from typing import TYPE_CHECKING
9
10
 
10
11
  from .. import config
11
12
  from ..utils.file_ops import seek_full_path_from_defaults
12
13
 
14
+ if TYPE_CHECKING:
15
+ from pathlib import Path
16
+
17
+ from manim.typing import StrPath
18
+
13
19
 
14
20
  # Still in use by add_sound() function in scene_file_writer.py
15
- def get_full_sound_file_path(sound_file_name):
21
+ def get_full_sound_file_path(sound_file_name: StrPath) -> Path:
16
22
  return seek_full_path_from_defaults(
17
23
  sound_file_name,
18
24
  default_dir=config.get_dir("assets_dir"),
manim/utils/space_ops.py CHANGED
@@ -3,13 +3,14 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import itertools as it
6
- from typing import TYPE_CHECKING, Sequence
6
+ from collections.abc import Sequence
7
+ from typing import TYPE_CHECKING, Callable
7
8
 
8
9
  import numpy as np
9
10
  from mapbox_earcut import triangulate_float32 as earcut
10
11
  from scipy.spatial.transform import Rotation
11
12
 
12
- from manim.constants import DOWN, OUT, PI, RIGHT, TAU, UP, RendererType
13
+ from manim.constants import DOWN, OUT, PI, RIGHT, TAU, UP
13
14
  from manim.utils.iterables import adjacent_pairs
14
15
 
15
16
  if TYPE_CHECKING:
@@ -17,10 +18,17 @@ if TYPE_CHECKING:
17
18
 
18
19
  from manim.typing import (
19
20
  ManimFloat,
20
- Point3D_Array,
21
+ MatrixMN,
22
+ Point2D_Array,
23
+ Point3D,
24
+ Point3DLike,
25
+ Point3DLike_Array,
26
+ PointND,
27
+ PointNDLike_Array,
21
28
  Vector2D,
22
29
  Vector2D_Array,
23
30
  Vector3D,
31
+ Vector3D_Array,
24
32
  )
25
33
 
26
34
  __all__ = [
@@ -58,7 +66,8 @@ __all__ = [
58
66
 
59
67
 
60
68
  def norm_squared(v: float) -> float:
61
- return np.dot(v, v)
69
+ val: float = np.dot(v, v)
70
+ return val
62
71
 
63
72
 
64
73
  def cross(v1: Vector3D, v2: Vector3D) -> Vector3D:
@@ -192,7 +201,6 @@ def rotate_vector(
192
201
  ValueError
193
202
  If vector is not of dimension 2 or 3.
194
203
  """
195
-
196
204
  if len(vector) > 3:
197
205
  raise ValueError("Vector must have the correct dimensions.")
198
206
  if len(vector) == 2:
@@ -200,7 +208,7 @@ def rotate_vector(
200
208
  return rotation_matrix(angle, axis) @ vector
201
209
 
202
210
 
203
- def thick_diagonal(dim: int, thickness=2) -> np.ndarray:
211
+ def thick_diagonal(dim: int, thickness: int = 2) -> MatrixMN:
204
212
  row_indices = np.arange(dim).repeat(dim).reshape((dim, dim))
205
213
  col_indices = np.transpose(row_indices)
206
214
  return (np.abs(row_indices - col_indices) < thickness).astype("uint8")
@@ -248,9 +256,7 @@ def rotation_matrix(
248
256
  axis: np.ndarray,
249
257
  homogeneous: bool = False,
250
258
  ) -> np.ndarray:
251
- """
252
- Rotation in R^3 about a specified axis of rotation.
253
- """
259
+ """Rotation in R^3 about a specified axis of rotation."""
254
260
  inhomogeneous_rotation_matrix = Rotation.from_rotvec(
255
261
  angle * normalize(np.array(axis))
256
262
  ).as_matrix()
@@ -320,8 +326,10 @@ def angle_of_vector(vector: Sequence[float] | np.ndarray) -> float:
320
326
  c_vec = np.empty(vector.shape[1], dtype=np.complex128)
321
327
  c_vec.real = vector[0]
322
328
  c_vec.imag = vector[1]
323
- return np.angle(c_vec)
324
- return np.angle(complex(*vector[:2]))
329
+ val1: float = np.angle(c_vec)
330
+ return val1
331
+ val: float = np.angle(complex(*vector[:2]))
332
+ return val
325
333
 
326
334
 
327
335
  def angle_between_vectors(v1: np.ndarray, v2: np.ndarray) -> float:
@@ -340,14 +348,17 @@ def angle_between_vectors(v1: np.ndarray, v2: np.ndarray) -> float:
340
348
  float
341
349
  The angle between the vectors.
342
350
  """
343
-
344
- return 2 * np.arctan2(
351
+ val: float = 2 * np.arctan2(
345
352
  np.linalg.norm(normalize(v1) - normalize(v2)),
346
353
  np.linalg.norm(normalize(v1) + normalize(v2)),
347
354
  )
348
355
 
356
+ return val
357
+
349
358
 
350
- def normalize(vect: np.ndarray | tuple[float], fall_back=None) -> np.ndarray:
359
+ def normalize(
360
+ vect: np.ndarray | tuple[float], fall_back: np.ndarray | None = None
361
+ ) -> np.ndarray:
351
362
  norm = np.linalg.norm(vect)
352
363
  if norm > 0:
353
364
  return np.array(vect) / norm
@@ -473,12 +484,8 @@ def regular_vertices(
473
484
  start_angle : :class:`float`
474
485
  The angle the vertices start at.
475
486
  """
476
-
477
487
  if start_angle is None:
478
- if n % 2 == 0:
479
- start_angle = 0
480
- else:
481
- start_angle = TAU / 4
488
+ start_angle = 0 if n % 2 == 0 else TAU / 4
482
489
 
483
490
  start_vector = rotate_vector(RIGHT * radius, start_angle)
484
491
  vertices = compass_directions(n, start_vector)
@@ -494,11 +501,13 @@ def R3_to_complex(point: Sequence[float]) -> np.ndarray:
494
501
  return complex(*point[:2])
495
502
 
496
503
 
497
- def complex_func_to_R3_func(complex_func):
504
+ def complex_func_to_R3_func(
505
+ complex_func: Callable[[complex], complex],
506
+ ) -> Callable[[Point3DLike], Point3D]:
498
507
  return lambda p: complex_to_R3(complex_func(R3_to_complex(p)))
499
508
 
500
509
 
501
- def center_of_mass(points: Sequence[float]) -> np.ndarray:
510
+ def center_of_mass(points: PointNDLike_Array) -> PointND:
502
511
  """Gets the center of mass of the points in space.
503
512
 
504
513
  Parameters
@@ -580,12 +589,12 @@ def line_intersection(
580
589
 
581
590
 
582
591
  def find_intersection(
583
- p0s: Sequence[np.ndarray] | Point3D_Array,
584
- v0s: Sequence[np.ndarray] | Point3D_Array,
585
- p1s: Sequence[np.ndarray] | Point3D_Array,
586
- v1s: Sequence[np.ndarray] | Point3D_Array,
592
+ p0s: Point3DLike_Array,
593
+ v0s: Vector3D_Array,
594
+ p1s: Point3DLike_Array,
595
+ v1s: Vector3D_Array,
587
596
  threshold: float = 1e-5,
588
- ) -> Sequence[np.ndarray]:
597
+ ) -> list[Point3D]:
589
598
  """
590
599
  Return the intersection of a line passing through p0 in direction v0
591
600
  with one passing through p1 in direction v1 (or array of intersections
@@ -620,21 +629,22 @@ def get_winding_number(points: Sequence[np.ndarray]) -> float:
620
629
  >>> from manim import Square, get_winding_number
621
630
  >>> polygon = Square()
622
631
  >>> get_winding_number(polygon.get_vertices())
623
- 1.0
624
- >>> polygon.shift(2*UP)
632
+ np.float64(1.0)
633
+ >>> polygon.shift(2 * UP)
625
634
  Square
626
635
  >>> get_winding_number(polygon.get_vertices())
627
- 0.0
636
+ np.float64(0.0)
628
637
  """
629
- total_angle = 0
638
+ total_angle: float = 0
630
639
  for p1, p2 in adjacent_pairs(points):
631
640
  d_angle = angle_of_vector(p2) - angle_of_vector(p1)
632
641
  d_angle = ((d_angle + PI) % TAU) - PI
633
642
  total_angle += d_angle
634
- return total_angle / TAU
643
+ val: float = total_angle / TAU
644
+ return val
635
645
 
636
646
 
637
- def shoelace(x_y: np.ndarray) -> float:
647
+ def shoelace(x_y: Point2D_Array) -> float:
638
648
  """2D implementation of the shoelace formula.
639
649
 
640
650
  Returns
@@ -644,10 +654,11 @@ def shoelace(x_y: np.ndarray) -> float:
644
654
  """
645
655
  x = x_y[:, 0]
646
656
  y = x_y[:, 1]
647
- return np.trapz(y, x)
657
+ val: float = np.trapz(y, x)
658
+ return val
648
659
 
649
660
 
650
- def shoelace_direction(x_y: np.ndarray) -> str:
661
+ def shoelace_direction(x_y: Point2D_Array) -> str:
651
662
  """
652
663
  Uses the area determined by the shoelace method to determine whether
653
664
  the input set of points is directed clockwise or counterclockwise.
@@ -687,7 +698,7 @@ def cross2d(
687
698
  .. code-block:: pycon
688
699
 
689
700
  >>> cross2d(np.array([1, 2]), np.array([3, 4]))
690
- -2
701
+ np.int64(-2)
691
702
  >>> cross2d(
692
703
  ... np.array([[1, 2, 0], [1, 0, 0]]),
693
704
  ... np.array([[3, 4, 0], [0, 1, 0]]),
@@ -765,7 +776,7 @@ def earclip_triangulation(verts: np.ndarray, ring_ends: list) -> list:
765
776
  raise Exception("Could not find a ring to attach")
766
777
 
767
778
  # Setup linked list
768
- after = []
779
+ after: list[int] = []
769
780
  end0 = 0
770
781
  for end1 in ring_ends:
771
782
  after.extend(range(end0 + 1, end1))
@@ -836,7 +847,7 @@ def spherical_to_cartesian(spherical: Sequence[float]) -> np.ndarray:
836
847
 
837
848
  def perpendicular_bisector(
838
849
  line: Sequence[np.ndarray],
839
- norm_vector=OUT,
850
+ norm_vector: Vector3D = OUT,
840
851
  ) -> Sequence[np.ndarray]:
841
852
  """Returns a list of two points that correspond
842
853
  to the ends of the perpendicular bisector of the
@@ -1,21 +1,25 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import contextlib
4
+ import logging
4
5
  import warnings
6
+ from collections.abc import Generator
5
7
  from pathlib import Path
6
8
 
7
9
  import numpy as np
8
10
 
9
- from manim import logger
11
+ from manim.typing import PixelArray
10
12
 
11
13
  from ._show_diff import show_diff_helper
12
14
 
13
15
  FRAME_ABSOLUTE_TOLERANCE = 1.01
14
16
  FRAME_MISMATCH_RATIO_TOLERANCE = 1e-5
15
17
 
18
+ logger = logging.getLogger("manim")
19
+
16
20
 
17
21
  class _FramesTester:
18
- def __init__(self, file_path: Path, show_diff=False) -> None:
22
+ def __init__(self, file_path: Path, show_diff: bool = False) -> None:
19
23
  self._file_path = file_path
20
24
  self._show_diff = show_diff
21
25
  self._frames: np.ndarray
@@ -23,7 +27,7 @@ class _FramesTester:
23
27
  self._frames_compared = 0
24
28
 
25
29
  @contextlib.contextmanager
26
- def testing(self):
30
+ def testing(self) -> Generator[None, None, None]:
27
31
  with np.load(self._file_path) as data:
28
32
  self._frames = data["frame_data"]
29
33
  # For backward compatibility, when the control data contains only one frame (<= v0.8.0)
@@ -37,7 +41,7 @@ class _FramesTester:
37
41
  f"when there are {self._number_frames} control frames for this test."
38
42
  )
39
43
 
40
- def check_frame(self, frame_number: int, frame: np.ndarray):
44
+ def check_frame(self, frame_number: int, frame: PixelArray) -> None:
41
45
  assert frame_number < self._number_frames, (
42
46
  f"The tested scene is at frame number {frame_number} "
43
47
  f"when there are {self._number_frames} control frames."
@@ -63,7 +67,8 @@ class _FramesTester:
63
67
  warnings.warn(
64
68
  f"Mismatch of {number_of_mismatches} pixel values in frame {frame_number} "
65
69
  f"against control data in {self._file_path}. Below error threshold, "
66
- "continuing..."
70
+ "continuing...",
71
+ stacklevel=1,
67
72
  )
68
73
  return
69
74
 
@@ -84,17 +89,17 @@ class _ControlDataWriter(_FramesTester):
84
89
  self._number_frames_written: int = 0
85
90
 
86
91
  # Actually write a frame.
87
- def check_frame(self, index: int, frame: np.ndarray):
92
+ def check_frame(self, index: int, frame: PixelArray) -> None:
88
93
  frame = frame[np.newaxis, ...]
89
94
  self.frames = np.concatenate((self.frames, frame))
90
95
  self._number_frames_written += 1
91
96
 
92
97
  @contextlib.contextmanager
93
- def testing(self):
98
+ def testing(self) -> Generator[None, None, None]:
94
99
  yield
95
100
  self.save_contol_data()
96
101
 
97
- def save_contol_data(self):
102
+ def save_contol_data(self) -> None:
98
103
  self.frames = self.frames.astype("uint8")
99
104
  np.savez_compressed(self.file_path, frame_data=self.frames)
100
105
  logger.info(
@@ -5,13 +5,15 @@ import warnings
5
5
 
6
6
  import numpy as np
7
7
 
8
+ from manim.typing import PixelArray
9
+
8
10
 
9
11
  def show_diff_helper(
10
12
  frame_number: int,
11
- frame_data: np.ndarray,
12
- expected_frame_data: np.ndarray,
13
+ frame_data: PixelArray,
14
+ expected_frame_data: PixelArray,
13
15
  control_data_filename: str,
14
- ):
16
+ ) -> None:
15
17
  """Will visually display with matplotlib differences between frame generated and the one expected."""
16
18
  import matplotlib.gridspec as gridspec
17
19
  import matplotlib.pyplot as plt