manim 0.17.3__py3-none-any.whl → 0.18.0.post0__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 (84) hide show
  1. manim/__init__.py +1 -0
  2. manim/__main__.py +2 -0
  3. manim/_config/__init__.py +0 -1
  4. manim/_config/logger_utils.py +1 -0
  5. manim/_config/utils.py +14 -5
  6. manim/animation/changing.py +9 -5
  7. manim/animation/creation.py +8 -3
  8. manim/animation/indication.py +4 -4
  9. manim/animation/speedmodifier.py +2 -4
  10. manim/animation/updaters/mobject_update_utils.py +134 -16
  11. manim/camera/camera.py +31 -17
  12. manim/cli/checkhealth/__init__.py +0 -0
  13. manim/cli/checkhealth/checks.py +173 -0
  14. manim/cli/checkhealth/commands.py +81 -0
  15. manim/cli/render/global_options.py +6 -0
  16. manim/constants.py +58 -54
  17. manim/mobject/geometry/__init__.py +1 -0
  18. manim/mobject/geometry/arc.py +126 -91
  19. manim/mobject/geometry/boolean_ops.py +6 -10
  20. manim/mobject/geometry/labeled.py +155 -0
  21. manim/mobject/geometry/line.py +66 -50
  22. manim/mobject/geometry/polygram.py +23 -15
  23. manim/mobject/geometry/shape_matchers.py +24 -15
  24. manim/mobject/geometry/tips.py +62 -40
  25. manim/mobject/graph.py +3 -4
  26. manim/mobject/graphing/coordinate_systems.py +190 -139
  27. manim/mobject/graphing/number_line.py +5 -2
  28. manim/mobject/graphing/probability.py +4 -3
  29. manim/mobject/graphing/scale.py +7 -7
  30. manim/mobject/logo.py +108 -22
  31. manim/mobject/matrix.py +33 -37
  32. manim/mobject/mobject.py +327 -260
  33. manim/mobject/opengl/opengl_image_mobject.py +1 -1
  34. manim/mobject/opengl/opengl_mobject.py +18 -12
  35. manim/mobject/opengl/opengl_point_cloud_mobject.py +1 -1
  36. manim/mobject/opengl/opengl_surface.py +1 -1
  37. manim/mobject/opengl/opengl_vectorized_mobject.py +21 -17
  38. manim/mobject/svg/brace.py +3 -1
  39. manim/mobject/svg/svg_mobject.py +9 -11
  40. manim/mobject/table.py +50 -54
  41. manim/mobject/text/numbers.py +48 -6
  42. manim/mobject/text/tex_mobject.py +8 -12
  43. manim/mobject/text/text_mobject.py +32 -24
  44. manim/mobject/three_d/three_d_utils.py +13 -8
  45. manim/mobject/three_d/three_dimensions.py +61 -43
  46. manim/mobject/types/image_mobject.py +5 -4
  47. manim/mobject/types/point_cloud_mobject.py +8 -6
  48. manim/mobject/types/vectorized_mobject.py +385 -258
  49. manim/mobject/vector_field.py +19 -11
  50. manim/plugins/import_plugins.py +1 -1
  51. manim/plugins/plugins_flags.py +1 -6
  52. manim/renderer/shader.py +2 -2
  53. manim/scene/scene.py +15 -7
  54. manim/scene/scene_file_writer.py +1 -2
  55. manim/scene/three_d_scene.py +1 -1
  56. manim/scene/vector_space_scene.py +17 -7
  57. manim/typing.py +133 -0
  58. manim/utils/bezier.py +267 -83
  59. manim/utils/color/AS2700.py +234 -0
  60. manim/utils/color/BS381.py +315 -0
  61. manim/utils/color/X11.py +530 -0
  62. manim/utils/color/XKCD.py +949 -0
  63. manim/utils/color/__init__.py +58 -0
  64. manim/utils/color/core.py +1036 -0
  65. manim/utils/color/manim_colors.py +220 -0
  66. manim/utils/docbuild/autocolor_directive.py +92 -0
  67. manim/utils/docbuild/manim_directive.py +40 -6
  68. manim/utils/file_ops.py +1 -1
  69. manim/utils/hashing.py +1 -1
  70. manim/utils/iterables.py +1 -1
  71. manim/utils/rate_functions.py +33 -0
  72. manim/utils/simple_functions.py +0 -18
  73. manim/utils/space_ops.py +55 -42
  74. manim/utils/testing/frames_comparison.py +9 -0
  75. manim/utils/tex.py +2 -0
  76. manim/utils/tex_file_writing.py +29 -2
  77. {manim-0.17.3.dist-info → manim-0.18.0.post0.dist-info}/METADATA +14 -14
  78. {manim-0.17.3.dist-info → manim-0.18.0.post0.dist-info}/RECORD +82 -71
  79. {manim-0.17.3.dist-info → manim-0.18.0.post0.dist-info}/WHEEL +1 -1
  80. manim/communitycolors.py +0 -9
  81. manim/utils/color.py +0 -552
  82. {manim-0.17.3.dist-info → manim-0.18.0.post0.dist-info}/LICENSE +0 -0
  83. {manim-0.17.3.dist-info → manim-0.18.0.post0.dist-info}/LICENSE.community +0 -0
  84. {manim-0.17.3.dist-info → manim-0.18.0.post0.dist-info}/entry_points.txt +0 -0
@@ -14,12 +14,21 @@ __all__ = [
14
14
 
15
15
  import itertools as it
16
16
  import sys
17
- import typing
18
- from typing import Callable, Optional, Sequence, Union
17
+ from typing import (
18
+ TYPE_CHECKING,
19
+ Callable,
20
+ Generator,
21
+ Hashable,
22
+ Iterable,
23
+ Literal,
24
+ Mapping,
25
+ Sequence,
26
+ )
19
27
 
20
- import colour
21
28
  import numpy as np
29
+ import numpy.typing as npt
22
30
  from PIL.Image import Image
31
+ from typing_extensions import Self
23
32
 
24
33
  from manim.mobject.opengl.opengl_compatibility import ConvertToOpenGL
25
34
  from manim.mobject.opengl.opengl_vectorized_mobject import OpenGLVMobject
@@ -38,11 +47,25 @@ from ...utils.bezier import (
38
47
  partial_bezier_points,
39
48
  proportions_along_bezier_curve_for_point,
40
49
  )
41
- from ...utils.color import BLACK, WHITE, color_to_rgba
42
- from ...utils.deprecation import deprecated
50
+ from ...utils.color import BLACK, WHITE, ManimColor, ParsableManimColor
43
51
  from ...utils.iterables import make_even, resize_array, stretch_array_to_length, tuplify
44
52
  from ...utils.space_ops import rotate_vector, shoelace_direction
45
53
 
54
+ if TYPE_CHECKING:
55
+ from manim.typing import (
56
+ BezierPoints,
57
+ CubicBezierPoints,
58
+ ManimFloat,
59
+ MappingFunction,
60
+ Point2D,
61
+ Point3D,
62
+ Point3D_Array,
63
+ QuadraticBezierPoints,
64
+ RGBA_Array_Float,
65
+ Vector3,
66
+ Zeros,
67
+ )
68
+
46
69
  # TODO
47
70
  # - Change cubic curve groups to have 4 points instead of 3
48
71
  # - Change sub_path idea accordingly
@@ -81,68 +104,78 @@ class VMobject(Mobject):
81
104
 
82
105
  def __init__(
83
106
  self,
84
- fill_color=None,
85
- fill_opacity=0.0,
86
- stroke_color=None,
87
- stroke_opacity=1.0,
88
- stroke_width=DEFAULT_STROKE_WIDTH,
89
- background_stroke_color=BLACK,
90
- background_stroke_opacity=1.0,
91
- background_stroke_width=0,
92
- sheen_factor=0.0,
107
+ fill_color: ParsableManimColor | None = None,
108
+ fill_opacity: float = 0.0,
109
+ stroke_color: ParsableManimColor | None = None,
110
+ stroke_opacity: float = 1.0,
111
+ stroke_width: float = DEFAULT_STROKE_WIDTH,
112
+ background_stroke_color: ParsableManimColor | None = BLACK,
113
+ background_stroke_opacity: float = 1.0,
114
+ background_stroke_width: float = 0,
115
+ sheen_factor: float = 0.0,
93
116
  joint_type: LineJointType | None = None,
94
- sheen_direction=UL,
95
- close_new_points=False,
96
- pre_function_handle_to_anchor_scale_factor=0.01,
97
- make_smooth_after_applying_functions=False,
98
- background_image=None,
99
- shade_in_3d=False,
117
+ sheen_direction: Vector3 = UL,
118
+ close_new_points: bool = False,
119
+ pre_function_handle_to_anchor_scale_factor: float = 0.01,
120
+ make_smooth_after_applying_functions: bool = False,
121
+ background_image: Image | str | None = None,
122
+ shade_in_3d: bool = False,
100
123
  # TODO, do we care about accounting for varying zoom levels?
101
- tolerance_for_point_equality=1e-6,
102
- n_points_per_cubic_curve=4,
124
+ tolerance_for_point_equality: float = 1e-6,
125
+ n_points_per_cubic_curve: int = 4,
103
126
  **kwargs,
104
127
  ):
105
128
  self.fill_opacity = fill_opacity
106
129
  self.stroke_opacity = stroke_opacity
107
130
  self.stroke_width = stroke_width
108
- self.background_stroke_color = background_stroke_color
109
- self.background_stroke_opacity = background_stroke_opacity
110
- self.background_stroke_width = background_stroke_width
111
- self.sheen_factor = sheen_factor
112
- if joint_type is None:
113
- joint_type = LineJointType.AUTO
114
- self.joint_type = joint_type
115
- self.sheen_direction = sheen_direction
116
- self.close_new_points = close_new_points
117
- self.pre_function_handle_to_anchor_scale_factor = (
131
+ if background_stroke_color is not None:
132
+ self.background_stroke_color: ManimColor = ManimColor(
133
+ background_stroke_color
134
+ )
135
+ self.background_stroke_opacity: float = background_stroke_opacity
136
+ self.background_stroke_width: float = background_stroke_width
137
+ self.sheen_factor: float = sheen_factor
138
+ self.joint_type: LineJointType = (
139
+ LineJointType.AUTO if joint_type is None else joint_type
140
+ )
141
+ self.sheen_direction: Vector3 = sheen_direction
142
+ self.close_new_points: bool = close_new_points
143
+ self.pre_function_handle_to_anchor_scale_factor: float = (
118
144
  pre_function_handle_to_anchor_scale_factor
119
145
  )
120
- self.make_smooth_after_applying_functions = make_smooth_after_applying_functions
121
- self.background_image = background_image
122
- self.shade_in_3d = shade_in_3d
123
- self.tolerance_for_point_equality = tolerance_for_point_equality
124
- self.n_points_per_cubic_curve = n_points_per_cubic_curve
146
+ self.make_smooth_after_applying_functions: bool = (
147
+ make_smooth_after_applying_functions
148
+ )
149
+ self.background_image: Image | str | None = background_image
150
+ self.shade_in_3d: bool = shade_in_3d
151
+ self.tolerance_for_point_equality: float = tolerance_for_point_equality
152
+ self.n_points_per_cubic_curve: int = n_points_per_cubic_curve
125
153
  super().__init__(**kwargs)
154
+ self.submobjects: list[VMobject]
126
155
 
127
- if fill_color:
128
- self.fill_color = fill_color
129
- if stroke_color:
130
- self.stroke_color = stroke_color
156
+ # TODO: Find where color overwrites are happening and remove the color doubling
157
+ # if "color" in kwargs:
158
+ # fill_color = kwargs["color"]
159
+ # stroke_color = kwargs["color"]
160
+ if fill_color is not None:
161
+ self.fill_color = ManimColor.parse(fill_color)
162
+ if stroke_color is not None:
163
+ self.stroke_color = ManimColor.parse(stroke_color)
131
164
 
132
165
  # OpenGL compatibility
133
166
  @property
134
- def n_points_per_curve(self):
167
+ def n_points_per_curve(self) -> int:
135
168
  return self.n_points_per_cubic_curve
136
169
 
137
- def get_group_class(self):
170
+ def get_group_class(self) -> type[VGroup]:
138
171
  return VGroup
139
172
 
140
173
  @staticmethod
141
- def get_mobject_type_class():
174
+ def get_mobject_type_class() -> type[VMobject]:
142
175
  return VMobject
143
176
 
144
177
  # Colors
145
- def init_colors(self, propagate_colors=True):
178
+ def init_colors(self, propagate_colors: bool = True) -> Self:
146
179
  self.set_fill(
147
180
  color=self.fill_color,
148
181
  opacity=self.fill_opacity,
@@ -172,7 +205,9 @@ class VMobject(Mobject):
172
205
 
173
206
  return self
174
207
 
175
- def generate_rgbas_array(self, color, opacity):
208
+ def generate_rgbas_array(
209
+ self, color: ManimColor | list[ManimColor], opacity: float | Iterable[float]
210
+ ) -> RGBA_Array_Float:
176
211
  """
177
212
  First arg can be either a color, or a tuple/list of colors.
178
213
  Likewise, opacity can either be a float, or a tuple of floats.
@@ -180,10 +215,14 @@ class VMobject(Mobject):
180
215
  one color was passed in, a second slightly light color
181
216
  will automatically be added for the gradient
182
217
  """
183
- colors = [c if (c is not None) else BLACK for c in tuplify(color)]
184
- opacities = [o if (o is not None) else 0 for o in tuplify(opacity)]
185
- rgbas = np.array(
186
- [color_to_rgba(c, o) for c, o in zip(*make_even(colors, opacities))],
218
+ colors: list[ManimColor] = [
219
+ ManimColor(c) if (c is not None) else BLACK for c in tuplify(color)
220
+ ]
221
+ opacities: list[float] = [
222
+ o if (o is not None) else 0.0 for o in tuplify(opacity)
223
+ ]
224
+ rgbas: npt.NDArray[RGBA_Array_Float] = np.array(
225
+ [c.to_rgba_with_alpha(o) for c, o in zip(*make_even(colors, opacities))],
187
226
  )
188
227
 
189
228
  sheen_factor = self.get_sheen_factor()
@@ -194,7 +233,12 @@ class VMobject(Mobject):
194
233
  rgbas = np.append(rgbas, light_rgbas, axis=0)
195
234
  return rgbas
196
235
 
197
- def update_rgbas_array(self, array_name, color=None, opacity=None):
236
+ def update_rgbas_array(
237
+ self,
238
+ array_name: str,
239
+ color: ManimColor | None = None,
240
+ opacity: float | None = None,
241
+ ) -> Self:
198
242
  rgbas = self.generate_rgbas_array(color, opacity)
199
243
  if not hasattr(self, array_name):
200
244
  setattr(self, array_name, rgbas)
@@ -217,10 +261,10 @@ class VMobject(Mobject):
217
261
 
218
262
  def set_fill(
219
263
  self,
220
- color: str | None = None,
264
+ color: ParsableManimColor | None = None,
221
265
  opacity: float | None = None,
222
266
  family: bool = True,
223
- ):
267
+ ) -> Self:
224
268
  """Set the fill color and fill opacity of a :class:`VMobject`.
225
269
 
226
270
  Parameters
@@ -260,18 +304,19 @@ class VMobject(Mobject):
260
304
  for submobject in self.submobjects:
261
305
  submobject.set_fill(color, opacity, family)
262
306
  self.update_rgbas_array("fill_rgbas", color, opacity)
307
+ self.fill_rgbas: RGBA_Array_Float
263
308
  if opacity is not None:
264
309
  self.fill_opacity = opacity
265
310
  return self
266
311
 
267
312
  def set_stroke(
268
313
  self,
269
- color=None,
270
- width=None,
271
- opacity=None,
314
+ color: ParsableManimColor = None,
315
+ width: float | None = None,
316
+ opacity: float | None = None,
272
317
  background=False,
273
- family=True,
274
- ):
318
+ family: bool = True,
319
+ ) -> Self:
275
320
  if family:
276
321
  for submobject in self.submobjects:
277
322
  submobject.set_stroke(color, width, opacity, background, family)
@@ -289,29 +334,32 @@ class VMobject(Mobject):
289
334
  if opacity is not None:
290
335
  setattr(self, opacity_name, opacity)
291
336
  if color is not None and background:
292
- self.background_stroke_color = color
337
+ if isinstance(color, (list, tuple)):
338
+ self.background_stroke_color = color
339
+ else:
340
+ self.background_stroke_color = ManimColor(color)
293
341
  return self
294
342
 
295
- def set_background_stroke(self, **kwargs):
343
+ def set_background_stroke(self, **kwargs) -> Self:
296
344
  kwargs["background"] = True
297
345
  self.set_stroke(**kwargs)
298
346
  return self
299
347
 
300
348
  def set_style(
301
349
  self,
302
- fill_color=None,
303
- fill_opacity=None,
304
- stroke_color=None,
305
- stroke_width=None,
306
- stroke_opacity=None,
307
- background_stroke_color=None,
308
- background_stroke_width=None,
309
- background_stroke_opacity=None,
310
- sheen_factor=None,
311
- sheen_direction=None,
312
- background_image=None,
313
- family=True,
314
- ):
350
+ fill_color: ParsableManimColor | None = None,
351
+ fill_opacity: float | None = None,
352
+ stroke_color: ParsableManimColor | None = None,
353
+ stroke_width: float | None = None,
354
+ stroke_opacity: float | None = None,
355
+ background_stroke_color: ParsableManimColor | None = None,
356
+ background_stroke_width: float | None = None,
357
+ background_stroke_opacity: float | None = None,
358
+ sheen_factor: float | None = None,
359
+ sheen_direction: Vector3 | None = None,
360
+ background_image: Image | str | None = None,
361
+ family: bool = True,
362
+ ) -> Self:
315
363
  self.set_fill(color=fill_color, opacity=fill_opacity, family=family)
316
364
  self.set_stroke(
317
365
  color=stroke_color,
@@ -335,16 +383,17 @@ class VMobject(Mobject):
335
383
  self.color_using_background_image(background_image)
336
384
  return self
337
385
 
338
- def get_style(self, simple=False):
386
+ def get_style(self, simple: bool = False) -> dict:
339
387
  ret = {
340
388
  "stroke_opacity": self.get_stroke_opacity(),
341
389
  "stroke_width": self.get_stroke_width(),
342
390
  }
343
391
 
392
+ # TODO: FIX COLORS HERE
344
393
  if simple:
345
- ret["fill_color"] = colour.rgb2hex(self.get_fill_color().get_rgb())
394
+ ret["fill_color"] = self.get_fill_color()
346
395
  ret["fill_opacity"] = self.get_fill_opacity()
347
- ret["stroke_color"] = colour.rgb2hex(self.get_stroke_color().get_rgb())
396
+ ret["stroke_color"] = self.get_stroke_color()
348
397
  else:
349
398
  ret["fill_color"] = self.get_fill_colors()
350
399
  ret["fill_opacity"] = self.get_fill_opacities()
@@ -358,7 +407,7 @@ class VMobject(Mobject):
358
407
 
359
408
  return ret
360
409
 
361
- def match_style(self, vmobject, family=True):
410
+ def match_style(self, vmobject: VMobject, family: bool = True) -> Self:
362
411
  self.set_style(**vmobject.get_style(), family=False)
363
412
 
364
413
  if family:
@@ -373,18 +422,18 @@ class VMobject(Mobject):
373
422
  sm1.match_style(sm2)
374
423
  return self
375
424
 
376
- def set_color(self, color, family=True):
425
+ def set_color(self, color: ParsableManimColor, family: bool = True) -> Self:
377
426
  self.set_fill(color, family=family)
378
427
  self.set_stroke(color, family=family)
379
428
  return self
380
429
 
381
- def set_opacity(self, opacity, family=True):
430
+ def set_opacity(self, opacity: float, family: bool = True) -> Self:
382
431
  self.set_fill(opacity=opacity, family=family)
383
432
  self.set_stroke(opacity=opacity, family=family)
384
433
  self.set_stroke(opacity=opacity, family=family, background=True)
385
434
  return self
386
435
 
387
- def fade(self, darkness=0.5, family=True):
436
+ def fade(self, darkness: float = 0.5, family: bool = True) -> Self:
388
437
  factor = 1.0 - darkness
389
438
  self.set_fill(opacity=factor * self.get_fill_opacity(), family=False)
390
439
  self.set_stroke(opacity=factor * self.get_stroke_opacity(), family=False)
@@ -395,13 +444,13 @@ class VMobject(Mobject):
395
444
  super().fade(darkness, family)
396
445
  return self
397
446
 
398
- def get_fill_rgbas(self):
447
+ def get_fill_rgbas(self) -> RGBA_Array_Float | Zeros:
399
448
  try:
400
449
  return self.fill_rgbas
401
450
  except AttributeError:
402
451
  return np.zeros((1, 4))
403
452
 
404
- def get_fill_color(self):
453
+ def get_fill_color(self) -> ManimColor:
405
454
  """
406
455
  If there are multiple colors (for gradient)
407
456
  this returns the first one
@@ -410,66 +459,71 @@ class VMobject(Mobject):
410
459
 
411
460
  fill_color = property(get_fill_color, set_fill)
412
461
 
413
- def get_fill_opacity(self):
462
+ def get_fill_opacity(self) -> ManimFloat:
414
463
  """
415
464
  If there are multiple opacities, this returns the
416
465
  first
417
466
  """
418
467
  return self.get_fill_opacities()[0]
419
468
 
420
- def get_fill_colors(self):
469
+ # TODO: Does this just do a copy?
470
+ # TODO: I have the feeling that this function should not return None, does that have any usage ?
471
+ def get_fill_colors(self) -> list[ManimColor | None]:
421
472
  return [
422
- colour.Color(rgb=rgba[:3]) if rgba.any() else None
473
+ ManimColor(rgba[:3]) if rgba.any() else None
423
474
  for rgba in self.get_fill_rgbas()
424
475
  ]
425
476
 
426
- def get_fill_opacities(self):
477
+ def get_fill_opacities(self) -> npt.NDArray[ManimFloat]:
427
478
  return self.get_fill_rgbas()[:, 3]
428
479
 
429
- def get_stroke_rgbas(self, background=False):
480
+ def get_stroke_rgbas(self, background: bool = False) -> RGBA_Array_float | Zeros:
430
481
  try:
431
482
  if background:
483
+ self.background_stroke_rgbas: RGBA_Array_Float
432
484
  rgbas = self.background_stroke_rgbas
433
485
  else:
486
+ self.stroke_rgbas: RGBA_Array_Float
434
487
  rgbas = self.stroke_rgbas
435
488
  return rgbas
436
489
  except AttributeError:
437
490
  return np.zeros((1, 4))
438
491
 
439
- def get_stroke_color(self, background=False):
492
+ def get_stroke_color(self, background: bool = False) -> ManimColor | None:
440
493
  return self.get_stroke_colors(background)[0]
441
494
 
442
495
  stroke_color = property(get_stroke_color, set_stroke)
443
496
 
444
- def get_stroke_width(self, background=False):
497
+ def get_stroke_width(self, background: bool = False) -> float:
445
498
  if background:
499
+ self.background_stroke_width: float
446
500
  width = self.background_stroke_width
447
501
  else:
448
502
  width = self.stroke_width
449
503
  if isinstance(width, str):
450
504
  width = int(width)
451
- return max(0, width)
505
+ return max(0.0, width)
452
506
 
453
- def get_stroke_opacity(self, background=False):
507
+ def get_stroke_opacity(self, background: bool = False) -> ManimFloat:
454
508
  return self.get_stroke_opacities(background)[0]
455
509
 
456
- def get_stroke_colors(self, background=False):
510
+ def get_stroke_colors(self, background: bool = False) -> list[ManimColor | None]:
457
511
  return [
458
- colour.Color(rgb=rgba[:3]) if rgba.any() else None
512
+ ManimColor(rgba[:3]) if rgba.any() else None
459
513
  for rgba in self.get_stroke_rgbas(background)
460
514
  ]
461
515
 
462
- def get_stroke_opacities(self, background=False):
516
+ def get_stroke_opacities(self, background: bool = False) -> npt.NDArray[ManimFloat]:
463
517
  return self.get_stroke_rgbas(background)[:, 3]
464
518
 
465
- def get_color(self):
519
+ def get_color(self) -> ManimColor:
466
520
  if np.all(self.get_fill_opacities() == 0):
467
521
  return self.get_stroke_color()
468
522
  return self.get_fill_color()
469
523
 
470
524
  color = property(get_color, set_color)
471
525
 
472
- def set_sheen_direction(self, direction: np.ndarray, family=True):
526
+ def set_sheen_direction(self, direction: Vector3, family: bool = True) -> Self:
473
527
  """Sets the direction of the applied sheen.
474
528
 
475
529
  Parameters
@@ -494,10 +548,12 @@ class VMobject(Mobject):
494
548
  for submob in self.get_family():
495
549
  submob.sheen_direction = direction
496
550
  else:
497
- self.sheen_direction = direction
551
+ self.sheen_direction: Vector3 = direction
498
552
  return self
499
553
 
500
- def rotate_sheen_direction(self, angle: float, axis: np.ndarray = OUT, family=True):
554
+ def rotate_sheen_direction(
555
+ self, angle: float, axis: Vector3 = OUT, family: bool = True
556
+ ) -> Self:
501
557
  """Rotates the direction of the applied sheen.
502
558
 
503
559
  Parameters
@@ -528,7 +584,9 @@ class VMobject(Mobject):
528
584
  self.sheen_direction = rotate_vector(self.sheen_direction, angle, axis)
529
585
  return self
530
586
 
531
- def set_sheen(self, factor: float, direction: np.ndarray = None, family=True):
587
+ def set_sheen(
588
+ self, factor: float, direction: Vector3 | None = None, family: bool = True
589
+ ) -> Self:
532
590
  """Applies a color gradient from a direction.
533
591
 
534
592
  Parameters
@@ -554,7 +612,7 @@ class VMobject(Mobject):
554
612
  if family:
555
613
  for submob in self.submobjects:
556
614
  submob.set_sheen(factor, direction, family)
557
- self.sheen_factor = factor
615
+ self.sheen_factor: float = factor
558
616
  if direction is not None:
559
617
  # family set to false because recursion will
560
618
  # already be handled above
@@ -565,13 +623,13 @@ class VMobject(Mobject):
565
623
  self.set_fill(self.get_fill_color(), family=family)
566
624
  return self
567
625
 
568
- def get_sheen_direction(self):
626
+ def get_sheen_direction(self) -> Vector3:
569
627
  return np.array(self.sheen_direction)
570
628
 
571
- def get_sheen_factor(self):
629
+ def get_sheen_factor(self) -> float:
572
630
  return self.sheen_factor
573
631
 
574
- def get_gradient_start_and_end_points(self):
632
+ def get_gradient_start_and_end_points(self) -> tuple[Point3D, Point3D]:
575
633
  if self.shade_in_3d:
576
634
  return get_3d_vmob_gradient_start_and_end_points(self)
577
635
  else:
@@ -583,8 +641,8 @@ class VMobject(Mobject):
583
641
  offset = np.dot(bases, direction)
584
642
  return (c - offset, c + offset)
585
643
 
586
- def color_using_background_image(self, background_image: Image | str):
587
- self.background_image = background_image
644
+ def color_using_background_image(self, background_image: Image | str) -> Self:
645
+ self.background_image: Image | str = background_image
588
646
  self.set_color(WHITE)
589
647
  for submob in self.submobjects:
590
648
  submob.color_using_background_image(background_image)
@@ -593,26 +651,28 @@ class VMobject(Mobject):
593
651
  def get_background_image(self) -> Image | str:
594
652
  return self.background_image
595
653
 
596
- def match_background_image(self, vmobject):
654
+ def match_background_image(self, vmobject: VMobject) -> Self:
597
655
  self.color_using_background_image(vmobject.get_background_image())
598
656
  return self
599
657
 
600
- def set_shade_in_3d(self, value=True, z_index_as_group=False):
658
+ def set_shade_in_3d(
659
+ self, value: bool = True, z_index_as_group: bool = False
660
+ ) -> Self:
601
661
  for submob in self.get_family():
602
662
  submob.shade_in_3d = value
603
663
  if z_index_as_group:
604
664
  submob.z_index_group = self
605
665
  return self
606
666
 
607
- def set_points(self, points):
608
- self.points = np.array(points)
667
+ def set_points(self, points: Point3D_Array) -> Self:
668
+ self.points: Point3D_Array = np.array(points)
609
669
  return self
610
670
 
611
671
  def resize_points(
612
672
  self,
613
673
  new_length: int,
614
- resize_func: Callable[[np.ndarray, int], np.ndarray] = resize_array,
615
- ):
674
+ resize_func: Callable[[Point3D, int], Point3D] = resize_array,
675
+ ) -> Self:
616
676
  """Resize the array of anchor points and handles to have
617
677
  the specified size.
618
678
 
@@ -631,11 +691,11 @@ class VMobject(Mobject):
631
691
 
632
692
  def set_anchors_and_handles(
633
693
  self,
634
- anchors1: Sequence[float],
635
- handles1: Sequence[float],
636
- handles2: Sequence[float],
637
- anchors2: Sequence[float],
638
- ):
694
+ anchors1: CubicBezierPoints,
695
+ handles1: CubicBezierPoints,
696
+ handles2: CubicBezierPoints,
697
+ anchors2: CubicBezierPoints,
698
+ ) -> Self:
639
699
  """Given two sets of anchors and handles, process them to set them as anchors
640
700
  and handles of the VMobject.
641
701
 
@@ -663,17 +723,17 @@ class VMobject(Mobject):
663
723
  self.points[index::nppcc] = array
664
724
  return self
665
725
 
666
- def clear_points(self):
726
+ def clear_points(self) -> None:
667
727
  self.points = np.zeros((0, self.dim))
668
728
 
669
- def append_points(self, new_points):
729
+ def append_points(self, new_points: Point3D_Array) -> Self:
670
730
  # TODO, check that number new points is a multiple of 4?
671
731
  # or else that if len(self.points) % 4 == 1, then
672
732
  # len(new_points) % 4 == 3?
673
733
  self.points = np.append(self.points, new_points, axis=0)
674
734
  return self
675
735
 
676
- def start_new_path(self, point):
736
+ def start_new_path(self, point: Point3D) -> Self:
677
737
  if len(self.points) % 4 != 0:
678
738
  # close the open path by appending the last
679
739
  # start anchor sufficiently often
@@ -685,23 +745,24 @@ class VMobject(Mobject):
685
745
 
686
746
  def add_cubic_bezier_curve(
687
747
  self,
688
- anchor1: np.ndarray,
689
- handle1: np.ndarray,
690
- handle2: np.ndarray,
691
- anchor2,
748
+ anchor1: CubicBezierPoints,
749
+ handle1: CubicBezierPoints,
750
+ handle2: CubicBezierPoints,
751
+ anchor2: CubicBezierPoints,
692
752
  ) -> None:
693
753
  # TODO, check the len(self.points) % 4 == 0?
694
754
  self.append_points([anchor1, handle1, handle2, anchor2])
695
755
 
696
- def add_cubic_bezier_curves(self, curves):
756
+ # what type is curves?
757
+ def add_cubic_bezier_curves(self, curves) -> None:
697
758
  self.append_points(curves.flatten())
698
759
 
699
760
  def add_cubic_bezier_curve_to(
700
761
  self,
701
- handle1: np.ndarray,
702
- handle2: np.ndarray,
703
- anchor: np.ndarray,
704
- ):
762
+ handle1: CubicBezierPoints,
763
+ handle2: CubicBezierPoints,
764
+ anchor: CubicBezierPoints,
765
+ ) -> Self:
705
766
  """Add cubic bezier curve to the path.
706
767
 
707
768
  NOTE : the first anchor is not a parameter as by default the end of the last sub-path!
@@ -730,9 +791,9 @@ class VMobject(Mobject):
730
791
 
731
792
  def add_quadratic_bezier_curve_to(
732
793
  self,
733
- handle: np.ndarray,
734
- anchor: np.ndarray,
735
- ):
794
+ handle: QuadraticBezierPoints,
795
+ anchor: QuadraticBezierPoints,
796
+ ) -> Self:
736
797
  """Add Quadratic bezier curve to the path.
737
798
 
738
799
  Returns
@@ -754,7 +815,7 @@ class VMobject(Mobject):
754
815
  )
755
816
  return self
756
817
 
757
- def add_line_to(self, point: np.ndarray):
818
+ def add_line_to(self, point: Point3D) -> Self:
758
819
  """Add a straight line from the last point of VMobject to the given point.
759
820
 
760
821
  Parameters
@@ -777,7 +838,7 @@ class VMobject(Mobject):
777
838
  )
778
839
  return self
779
840
 
780
- def add_smooth_curve_to(self, *points: np.array):
841
+ def add_smooth_curve_to(self, *points: Point3D) -> Self:
781
842
  """Creates a smooth curve from given points and add it to the VMobject. If two points are passed in, the first is interpreted
782
843
  as a handle, the second as an anchor.
783
844
 
@@ -820,28 +881,28 @@ class VMobject(Mobject):
820
881
  self.append_points([last_a2, handle1, handle2, new_anchor])
821
882
  return self
822
883
 
823
- def has_new_path_started(self):
884
+ def has_new_path_started(self) -> bool:
824
885
  nppcc = self.n_points_per_cubic_curve # 4
825
886
  # A new path starting is defined by a control point which is not part of a bezier subcurve.
826
887
  return len(self.points) % nppcc == 1
827
888
 
828
- def get_last_point(self):
889
+ def get_last_point(self) -> Point3D:
829
890
  return self.points[-1]
830
891
 
831
- def is_closed(self):
892
+ def is_closed(self) -> bool:
832
893
  # TODO use consider_points_equals_2d ?
833
894
  return self.consider_points_equals(self.points[0], self.points[-1])
834
895
 
835
- def close_path(self):
896
+ def close_path(self) -> None:
836
897
  if not self.is_closed():
837
898
  self.add_line_to(self.get_subpaths()[-1][0])
838
899
 
839
- def add_points_as_corners(self, points: np.ndarray) -> VMobject:
900
+ def add_points_as_corners(self, points: Iterable[Point3D]) -> Iterable[Point3D]:
840
901
  for point in points:
841
902
  self.add_line_to(point)
842
903
  return points
843
904
 
844
- def set_points_as_corners(self, points: Sequence[float]):
905
+ def set_points_as_corners(self, points: Point3D_Array) -> Self:
845
906
  """Given an array of points, set them as corner of the vmobject.
846
907
 
847
908
  To achieve that, this algorithm sets handles aligned with the anchors such that the resultant bezier curve will be the segment
@@ -866,12 +927,12 @@ class VMobject(Mobject):
866
927
  )
867
928
  return self
868
929
 
869
- def set_points_smoothly(self, points):
930
+ def set_points_smoothly(self, points: Point3D_Array) -> Self:
870
931
  self.set_points_as_corners(points)
871
932
  self.make_smooth()
872
933
  return self
873
934
 
874
- def change_anchor_mode(self, mode: str):
935
+ def change_anchor_mode(self, mode: Literal["jagged", "smooth"]) -> Self:
875
936
  """Changes the anchor mode of the bezier curves. This will modify the handles.
876
937
 
877
938
  There can be only two modes, "jagged", and "smooth".
@@ -881,7 +942,7 @@ class VMobject(Mobject):
881
942
  :class:`VMobject`
882
943
  ``self``
883
944
  """
884
- assert mode in ["jagged", "smooth"]
945
+ assert mode in ["jagged", "smooth"], 'mode must be either "jagged" or "smooth"'
885
946
  nppcc = self.n_points_per_cubic_curve
886
947
  for submob in self.family_members_with_points():
887
948
  subpaths = submob.get_subpaths()
@@ -893,7 +954,7 @@ class VMobject(Mobject):
893
954
  anchors = np.append(subpath[::nppcc], subpath[-1:], 0)
894
955
  if mode == "smooth":
895
956
  h1, h2 = get_smooth_handle_points(anchors)
896
- elif mode == "jagged":
957
+ else: # mode == "jagged"
897
958
  # The following will make the handles aligned with the anchors, thus making the bezier curve a segment
898
959
  a1 = anchors[:-1]
899
960
  a2 = anchors[1:]
@@ -905,18 +966,18 @@ class VMobject(Mobject):
905
966
  submob.append_points(new_subpath)
906
967
  return self
907
968
 
908
- def make_smooth(self):
969
+ def make_smooth(self) -> Self:
909
970
  return self.change_anchor_mode("smooth")
910
971
 
911
- def make_jagged(self):
972
+ def make_jagged(self) -> Self:
912
973
  return self.change_anchor_mode("jagged")
913
974
 
914
- def add_subpath(self, points: np.ndarray):
975
+ def add_subpath(self, points: Point3D_Array) -> Self:
915
976
  assert len(points) % 4 == 0
916
- self.points = np.append(self.points, points, axis=0)
977
+ self.points: Point3D_Array = np.append(self.points, points, axis=0)
917
978
  return self
918
979
 
919
- def append_vectorized_mobject(self, vectorized_mobject):
980
+ def append_vectorized_mobject(self, vectorized_mobject: VMobject) -> None:
920
981
  new_points = list(vectorized_mobject.points)
921
982
 
922
983
  if self.has_new_path_started():
@@ -925,7 +986,7 @@ class VMobject(Mobject):
925
986
  self.points = self.points[:-1]
926
987
  self.append_points(new_points)
927
988
 
928
- def apply_function(self, function):
989
+ def apply_function(self, function: MappingFunction) -> Self:
929
990
  factor = self.pre_function_handle_to_anchor_scale_factor
930
991
  self.scale_handle_to_anchor_distances(factor)
931
992
  super().apply_function(function)
@@ -937,15 +998,15 @@ class VMobject(Mobject):
937
998
  def rotate(
938
999
  self,
939
1000
  angle: float,
940
- axis: np.ndarray = OUT,
941
- about_point: Sequence[float] | None = None,
1001
+ axis: Vector3 = OUT,
1002
+ about_point: Point3D | None = None,
942
1003
  **kwargs,
943
- ):
1004
+ ) -> Self:
944
1005
  self.rotate_sheen_direction(angle, axis)
945
1006
  super().rotate(angle, axis, about_point, **kwargs)
946
1007
  return self
947
1008
 
948
- def scale_handle_to_anchor_distances(self, factor: float):
1009
+ def scale_handle_to_anchor_distances(self, factor: float) -> Self:
949
1010
  """If the distance between a given handle point H and its associated
950
1011
  anchor point A is d, then it changes H to be a distances factor*d
951
1012
  away from A, but so that the line from A to H doesn't change.
@@ -977,10 +1038,10 @@ class VMobject(Mobject):
977
1038
  return self
978
1039
 
979
1040
  #
980
- def consider_points_equals(self, p0, p1):
1041
+ def consider_points_equals(self, p0: Point3D, p1: Point3D) -> bool:
981
1042
  return np.allclose(p0, p1, atol=self.tolerance_for_point_equality)
982
1043
 
983
- def consider_points_equals_2d(self, p0: np.ndarray, p1: np.ndarray) -> bool:
1044
+ def consider_points_equals_2d(self, p0: Point2D, p1: Point2D) -> bool:
984
1045
  """Determine if two points are close enough to be considered equal.
985
1046
 
986
1047
  This uses the algorithm from np.isclose(), but expanded here for the
@@ -1006,10 +1067,14 @@ class VMobject(Mobject):
1006
1067
  return True
1007
1068
 
1008
1069
  # Information about line
1009
- def get_cubic_bezier_tuples_from_points(self, points):
1010
- return np.array(list(self.gen_cubic_bezier_tuples_from_points(points)))
1011
-
1012
- def gen_cubic_bezier_tuples_from_points(self, points: np.ndarray) -> tuple:
1070
+ def get_cubic_bezier_tuples_from_points(
1071
+ self, points: Point3D_Array
1072
+ ) -> npt.NDArray[Point3D_Array]:
1073
+ return np.array(self.gen_cubic_bezier_tuples_from_points(points))
1074
+
1075
+ def gen_cubic_bezier_tuples_from_points(
1076
+ self, points: Point3D_Array
1077
+ ) -> tuple[Point3D_Array]:
1013
1078
  """Returns the bezier tuples from an array of points.
1014
1079
 
1015
1080
  self.points is a list of the anchors and handles of the bezier curves of the mobject (ie [anchor1, handle1, handle2, anchor2, anchor3 ..])
@@ -1024,23 +1089,23 @@ class VMobject(Mobject):
1024
1089
 
1025
1090
  Returns
1026
1091
  -------
1027
- typing.Tuple
1092
+ tuple
1028
1093
  Bezier control points.
1029
1094
  """
1030
1095
  nppcc = self.n_points_per_cubic_curve
1031
1096
  remainder = len(points) % nppcc
1032
1097
  points = points[: len(points) - remainder]
1033
1098
  # Basically take every nppcc element.
1034
- return (points[i : i + nppcc] for i in range(0, len(points), nppcc))
1099
+ return tuple(points[i : i + nppcc] for i in range(0, len(points), nppcc))
1035
1100
 
1036
- def get_cubic_bezier_tuples(self):
1101
+ def get_cubic_bezier_tuples(self) -> npt.NDArray[Point3D_Array]:
1037
1102
  return self.get_cubic_bezier_tuples_from_points(self.points)
1038
1103
 
1039
1104
  def _gen_subpaths_from_points(
1040
1105
  self,
1041
- points: np.ndarray,
1042
- filter_func: typing.Callable[[int], bool],
1043
- ) -> tuple:
1106
+ points: Point3D_Array,
1107
+ filter_func: Callable[[int], bool],
1108
+ ) -> Generator[Point3D_Array]:
1044
1109
  """Given an array of points defining the bezier curves of the vmobject, return subpaths formed by these points.
1045
1110
  Here, Two bezier curves form a path if at least two of their anchors are evaluated True by the relation defined by filter_func.
1046
1111
 
@@ -1058,7 +1123,7 @@ class VMobject(Mobject):
1058
1123
 
1059
1124
  Returns
1060
1125
  -------
1061
- typing.Tuple
1126
+ Generator[Point3D_Array]
1062
1127
  subpaths formed by the points.
1063
1128
  """
1064
1129
  nppcc = self.n_points_per_cubic_curve
@@ -1070,7 +1135,7 @@ class VMobject(Mobject):
1070
1135
  if (i2 - i1) >= nppcc
1071
1136
  )
1072
1137
 
1073
- def get_subpaths_from_points(self, points):
1138
+ def get_subpaths_from_points(self, points: Point3D_Array) -> list[Point3D_Array]:
1074
1139
  return list(
1075
1140
  self._gen_subpaths_from_points(
1076
1141
  points,
@@ -1078,25 +1143,27 @@ class VMobject(Mobject):
1078
1143
  ),
1079
1144
  )
1080
1145
 
1081
- def gen_subpaths_from_points_2d(self, points):
1146
+ def gen_subpaths_from_points_2d(
1147
+ self, points: Point3D_Array
1148
+ ) -> Generator[Point3D_Array]:
1082
1149
  return self._gen_subpaths_from_points(
1083
1150
  points,
1084
1151
  lambda n: not self.consider_points_equals_2d(points[n - 1], points[n]),
1085
1152
  )
1086
1153
 
1087
- def get_subpaths(self) -> tuple:
1154
+ def get_subpaths(self) -> list[Point3D_Array]:
1088
1155
  """Returns subpaths formed by the curves of the VMobject.
1089
1156
 
1090
1157
  Subpaths are ranges of curves with each pair of consecutive curves having their end/start points coincident.
1091
1158
 
1092
1159
  Returns
1093
1160
  -------
1094
- typing.Tuple
1161
+ list[Point3D_Array]
1095
1162
  subpaths.
1096
1163
  """
1097
1164
  return self.get_subpaths_from_points(self.points)
1098
1165
 
1099
- def get_nth_curve_points(self, n: int) -> np.ndarray:
1166
+ def get_nth_curve_points(self, n: int) -> Point3D_Array:
1100
1167
  """Returns the points defining the nth curve of the vmobject.
1101
1168
 
1102
1169
  Parameters
@@ -1106,14 +1173,14 @@ class VMobject(Mobject):
1106
1173
 
1107
1174
  Returns
1108
1175
  -------
1109
- np.ndarray
1110
- points defininf the nth bezier curve (anchors, handles)
1176
+ Point3D_Array
1177
+ points defining the nth bezier curve (anchors, handles)
1111
1178
  """
1112
1179
  assert n < self.get_num_curves()
1113
1180
  nppcc = self.n_points_per_cubic_curve
1114
1181
  return self.points[nppcc * n : nppcc * (n + 1)]
1115
1182
 
1116
- def get_nth_curve_function(self, n: int) -> typing.Callable[[float], np.ndarray]:
1183
+ def get_nth_curve_function(self, n: int) -> Callable[[float], Point3D]:
1117
1184
  """Returns the expression of the nth curve.
1118
1185
 
1119
1186
  Parameters
@@ -1123,7 +1190,7 @@ class VMobject(Mobject):
1123
1190
 
1124
1191
  Returns
1125
1192
  -------
1126
- typing.Callable[float]
1193
+ Callable[float, Point3D]
1127
1194
  expression of the nth bezier curve.
1128
1195
  """
1129
1196
  return bezier(self.get_nth_curve_points(n))
@@ -1132,7 +1199,7 @@ class VMobject(Mobject):
1132
1199
  self,
1133
1200
  n: int,
1134
1201
  sample_points: int | None = None,
1135
- ) -> np.ndarray:
1202
+ ) -> npt.NDArray[ManimFloat]:
1136
1203
  """Returns the array of short line lengths used for length approximation.
1137
1204
 
1138
1205
  Parameters
@@ -1144,7 +1211,6 @@ class VMobject(Mobject):
1144
1211
 
1145
1212
  Returns
1146
1213
  -------
1147
- np.ndarray
1148
1214
  The short length-pieces of the nth curve.
1149
1215
  """
1150
1216
  if sample_points is None:
@@ -1185,7 +1251,7 @@ class VMobject(Mobject):
1185
1251
  self,
1186
1252
  n: int,
1187
1253
  sample_points: int | None = None,
1188
- ) -> tuple[typing.Callable[[float], np.ndarray], float]:
1254
+ ) -> tuple[Callable[[float], Point3D], float]:
1189
1255
  """Returns the expression of the nth curve along with its (approximate) length.
1190
1256
 
1191
1257
  Parameters
@@ -1197,7 +1263,7 @@ class VMobject(Mobject):
1197
1263
 
1198
1264
  Returns
1199
1265
  -------
1200
- curve : typing.Callable[[float], np.ndarray]
1266
+ curve : Callable[[float], Point3D]
1201
1267
  The function for the nth curve.
1202
1268
  length : :class:`float`
1203
1269
  The length of the nth curve.
@@ -1215,19 +1281,19 @@ class VMobject(Mobject):
1215
1281
  Returns
1216
1282
  -------
1217
1283
  int
1218
- number of curves. of the vmobject.
1284
+ number of curves of the vmobject.
1219
1285
  """
1220
1286
  nppcc = self.n_points_per_cubic_curve
1221
1287
  return len(self.points) // nppcc
1222
1288
 
1223
1289
  def get_curve_functions(
1224
1290
  self,
1225
- ) -> typing.Iterable[typing.Callable[[float], np.ndarray]]:
1291
+ ) -> Generator[Callable[[float], Point3D]]:
1226
1292
  """Gets the functions for the curves of the mobject.
1227
1293
 
1228
1294
  Returns
1229
1295
  -------
1230
- typing.Iterable[typing.Callable[[float], np.ndarray]]
1296
+ Generator[Callable[[float], Point3D]]
1231
1297
  The functions for the curves.
1232
1298
  """
1233
1299
 
@@ -1238,7 +1304,7 @@ class VMobject(Mobject):
1238
1304
 
1239
1305
  def get_curve_functions_with_lengths(
1240
1306
  self, **kwargs
1241
- ) -> typing.Iterable[tuple[typing.Callable[[float], np.ndarray], float]]:
1307
+ ) -> Generator[tuple[Callable[[float], Point3D], float]]:
1242
1308
  """Gets the functions and lengths of the curves for the mobject.
1243
1309
 
1244
1310
  Parameters
@@ -1248,7 +1314,7 @@ class VMobject(Mobject):
1248
1314
 
1249
1315
  Returns
1250
1316
  -------
1251
- typing.Iterable[typing.Tuple[typing.Callable[[float], np.ndarray], float]]
1317
+ Generator[tuple[Callable[[float], Point3D], float]]
1252
1318
  The functions and lengths of the curves.
1253
1319
  """
1254
1320
 
@@ -1257,7 +1323,7 @@ class VMobject(Mobject):
1257
1323
  for n in range(num_curves):
1258
1324
  yield self.get_nth_curve_function_with_length(n, **kwargs)
1259
1325
 
1260
- def point_from_proportion(self, alpha: float) -> np.ndarray:
1326
+ def point_from_proportion(self, alpha: float) -> Point3D:
1261
1327
  """Gets the point at a proportion along the path of the :class:`VMobject`.
1262
1328
 
1263
1329
  Parameters
@@ -1303,7 +1369,7 @@ class VMobject(Mobject):
1303
1369
 
1304
1370
  def proportion_from_point(
1305
1371
  self,
1306
- point: typing.Iterable[float | int],
1372
+ point: Iterable[float | int],
1307
1373
  ) -> float:
1308
1374
  """Returns the proportion along the path of the :class:`VMobject`
1309
1375
  a particular given point is at.
@@ -1357,7 +1423,7 @@ class VMobject(Mobject):
1357
1423
 
1358
1424
  return alpha
1359
1425
 
1360
- def get_anchors_and_handles(self) -> typing.Iterable[np.ndarray]:
1426
+ def get_anchors_and_handles(self) -> list[Point3D_Array]:
1361
1427
  """Returns anchors1, handles1, handles2, anchors2,
1362
1428
  where (anchors1[i], handles1[i], handles2[i], anchors2[i])
1363
1429
  will be four points defining a cubic bezier curve
@@ -1365,50 +1431,52 @@ class VMobject(Mobject):
1365
1431
 
1366
1432
  Returns
1367
1433
  -------
1368
- typing.Iterable[np.ndarray]
1434
+ `list[Point3D_Array]`
1369
1435
  Iterable of the anchors and handles.
1370
1436
  """
1371
1437
  nppcc = self.n_points_per_cubic_curve
1372
1438
  return [self.points[i::nppcc] for i in range(nppcc)]
1373
1439
 
1374
- def get_start_anchors(self) -> np.ndarray:
1440
+ def get_start_anchors(self) -> Point3D_Array:
1375
1441
  """Returns the start anchors of the bezier curves.
1376
1442
 
1377
1443
  Returns
1378
1444
  -------
1379
- np.ndarray
1445
+ Point3D_Array
1380
1446
  Starting anchors
1381
1447
  """
1382
- return self.points[0 :: self.n_points_per_cubic_curve]
1448
+ return self.points[:: self.n_points_per_cubic_curve]
1383
1449
 
1384
- def get_end_anchors(self) -> np.ndarray:
1450
+ def get_end_anchors(self) -> Point3D_Array:
1385
1451
  """Return the end anchors of the bezier curves.
1386
1452
 
1387
1453
  Returns
1388
1454
  -------
1389
- np.ndarray
1455
+ Point3D_Array
1390
1456
  Starting anchors
1391
1457
  """
1392
1458
  nppcc = self.n_points_per_cubic_curve
1393
1459
  return self.points[nppcc - 1 :: nppcc]
1394
1460
 
1395
- def get_anchors(self) -> np.ndarray:
1461
+ def get_anchors(self) -> Point3D_Array:
1396
1462
  """Returns the anchors of the curves forming the VMobject.
1397
1463
 
1398
1464
  Returns
1399
1465
  -------
1400
- np.ndarray
1466
+ Point3D_Array
1401
1467
  The anchors.
1402
1468
  """
1403
1469
  if self.points.shape[0] == 1:
1404
1470
  return self.points
1405
1471
  return np.array(
1406
- list(it.chain(*zip(self.get_start_anchors(), self.get_end_anchors()))),
1472
+ tuple(it.chain(*zip(self.get_start_anchors(), self.get_end_anchors()))),
1407
1473
  )
1408
1474
 
1409
- def get_points_defining_boundary(self):
1475
+ def get_points_defining_boundary(self) -> Point3D_Array:
1410
1476
  # Probably returns all anchors, but this is weird regarding the name of the method.
1411
- return np.array(list(it.chain(*(sm.get_anchors() for sm in self.get_family()))))
1477
+ return np.array(
1478
+ tuple(it.chain(*(sm.get_anchors() for sm in self.get_family())))
1479
+ )
1412
1480
 
1413
1481
  def get_arc_length(self, sample_points_per_curve: int | None = None) -> float:
1414
1482
  """Return the approximated length of the whole curve.
@@ -1432,7 +1500,7 @@ class VMobject(Mobject):
1432
1500
  )
1433
1501
 
1434
1502
  # Alignment
1435
- def align_points(self, vmobject: VMobject):
1503
+ def align_points(self, vmobject: VMobject) -> Self:
1436
1504
  """Adds points to self and vmobject so that they both have the same number of subpaths, with
1437
1505
  corresponding subpaths each containing the same number of points.
1438
1506
 
@@ -1503,7 +1571,7 @@ class VMobject(Mobject):
1503
1571
  vmobject.set_points(new_path2)
1504
1572
  return self
1505
1573
 
1506
- def insert_n_curves(self, n: int):
1574
+ def insert_n_curves(self, n: int) -> Self:
1507
1575
  """Inserts n curves to the bezier curves of the vmobject.
1508
1576
 
1509
1577
  Parameters
@@ -1527,7 +1595,9 @@ class VMobject(Mobject):
1527
1595
  self.append_points([new_path_point])
1528
1596
  return self
1529
1597
 
1530
- def insert_n_curves_to_point_list(self, n: int, points: np.ndarray) -> np.ndarray:
1598
+ def insert_n_curves_to_point_list(
1599
+ self, n: int, points: Point3D_Array
1600
+ ) -> npt.NDArray[BezierPoints]:
1531
1601
  """Given an array of k points defining a bezier curves (anchors and handles), returns points defining exactly k + n bezier curves.
1532
1602
 
1533
1603
  Parameters
@@ -1539,7 +1609,6 @@ class VMobject(Mobject):
1539
1609
 
1540
1610
  Returns
1541
1611
  -------
1542
- np.ndarray
1543
1612
  Points generated.
1544
1613
  """
1545
1614
 
@@ -1582,7 +1651,7 @@ class VMobject(Mobject):
1582
1651
  )
1583
1652
  return new_points
1584
1653
 
1585
- def align_rgbas(self, vmobject):
1654
+ def align_rgbas(self, vmobject: VMobject) -> Self:
1586
1655
  attrs = ["fill_rgbas", "stroke_rgbas", "background_stroke_rgbas"]
1587
1656
  for attr in attrs:
1588
1657
  a1 = getattr(self, attr)
@@ -1595,14 +1664,16 @@ class VMobject(Mobject):
1595
1664
  setattr(self, attr, new_a1)
1596
1665
  return self
1597
1666
 
1598
- def get_point_mobject(self, center=None):
1667
+ def get_point_mobject(self, center: Point3D | None = None) -> VectorizedPoint:
1599
1668
  if center is None:
1600
1669
  center = self.get_center()
1601
1670
  point = VectorizedPoint(center)
1602
1671
  point.match_style(self)
1603
1672
  return point
1604
1673
 
1605
- def interpolate_color(self, mobject1, mobject2, alpha):
1674
+ def interpolate_color(
1675
+ self, mobject1: VMobject, mobject2: VMobject, alpha: float
1676
+ ) -> None:
1606
1677
  attrs = [
1607
1678
  "fill_rgbas",
1608
1679
  "stroke_rgbas",
@@ -1626,7 +1697,7 @@ class VMobject(Mobject):
1626
1697
  vmobject: VMobject,
1627
1698
  a: float,
1628
1699
  b: float,
1629
- ):
1700
+ ) -> Self:
1630
1701
  """Given two bounds a and b, transforms the points of the self vmobject into the points of the vmobject
1631
1702
  passed as parameter with respect to the bounds. Points here stand for control points of the bezier curves (anchors and handles)
1632
1703
 
@@ -1683,7 +1754,7 @@ class VMobject(Mobject):
1683
1754
  )
1684
1755
  return self
1685
1756
 
1686
- def get_subcurve(self, a: float, b: float) -> VMobject:
1757
+ def get_subcurve(self, a: float, b: float) -> Self:
1687
1758
  """Returns the subcurve of the VMobject between the interval [a, b].
1688
1759
  The curve is a VMobject itself.
1689
1760
 
@@ -1711,7 +1782,7 @@ class VMobject(Mobject):
1711
1782
  vmob.pointwise_become_partial(self, a, b)
1712
1783
  return vmob
1713
1784
 
1714
- def get_direction(self):
1785
+ def get_direction(self) -> Literal["CW", "CCW"]:
1715
1786
  """Uses :func:`~.space_ops.shoelace_direction` to calculate the direction.
1716
1787
  The direction of points determines in which direction the
1717
1788
  object is drawn, clockwise or counterclockwise.
@@ -1731,7 +1802,7 @@ class VMobject(Mobject):
1731
1802
  """
1732
1803
  return shoelace_direction(self.get_start_anchors())
1733
1804
 
1734
- def reverse_direction(self):
1805
+ def reverse_direction(self) -> Self:
1735
1806
  """Reverts the point direction by inverting the point order.
1736
1807
 
1737
1808
  Returns
@@ -1756,7 +1827,7 @@ class VMobject(Mobject):
1756
1827
  self.points = self.points[::-1]
1757
1828
  return self
1758
1829
 
1759
- def force_direction(self, target_direction: str):
1830
+ def force_direction(self, target_direction: Literal["CW", "CCW"]) -> Self:
1760
1831
  """Makes sure that points are either directed clockwise or
1761
1832
  counterclockwise.
1762
1833
 
@@ -1835,21 +1906,16 @@ class VGroup(VMobject, metaclass=ConvertToOpenGL):
1835
1906
  super().__init__(**kwargs)
1836
1907
  self.add(*vmobjects)
1837
1908
 
1838
- def __repr__(self):
1839
- return (
1840
- self.__class__.__name__
1841
- + "("
1842
- + ", ".join(str(mob) for mob in self.submobjects)
1843
- + ")"
1844
- )
1909
+ def __repr__(self) -> str:
1910
+ return f'{self.__class__.__name__}({", ".join(str(mob) for mob in self.submobjects)})'
1845
1911
 
1846
- def __str__(self):
1912
+ def __str__(self) -> str:
1847
1913
  return (
1848
1914
  f"{self.__class__.__name__} of {len(self.submobjects)} "
1849
1915
  f"submobject{'s' if len(self.submobjects) > 0 else ''}"
1850
1916
  )
1851
1917
 
1852
- def add(self, *vmobjects: VMobject):
1918
+ def add(self, *vmobjects: VMobject) -> Self:
1853
1919
  """Checks if all passed elements are an instance of VMobject and then add them to submobjects
1854
1920
 
1855
1921
  Parameters
@@ -1901,21 +1967,21 @@ class VGroup(VMobject, metaclass=ConvertToOpenGL):
1901
1967
  raise TypeError("All submobjects must be of type VMobject")
1902
1968
  return super().add(*vmobjects)
1903
1969
 
1904
- def __add__(self, vmobject):
1970
+ def __add__(self, vmobject: VMobject) -> Self:
1905
1971
  return VGroup(*self.submobjects, vmobject)
1906
1972
 
1907
- def __iadd__(self, vmobject):
1973
+ def __iadd__(self, vmobject: VMobject) -> Self:
1908
1974
  return self.add(vmobject)
1909
1975
 
1910
- def __sub__(self, vmobject):
1976
+ def __sub__(self, vmobject: VMobject) -> Self:
1911
1977
  copy = VGroup(*self.submobjects)
1912
1978
  copy.remove(vmobject)
1913
1979
  return copy
1914
1980
 
1915
- def __isub__(self, vmobject):
1981
+ def __isub__(self, vmobject: VMobject) -> Self:
1916
1982
  return self.remove(vmobject)
1917
1983
 
1918
- def __setitem__(self, key: int, value: VMobject | typing.Sequence[VMobject]):
1984
+ def __setitem__(self, key: int, value: VMobject | Sequence[VMobject]) -> None:
1919
1985
  """Override the [] operator for item assignment.
1920
1986
 
1921
1987
  Parameters
@@ -2042,27 +2108,25 @@ class VDict(VMobject, metaclass=ConvertToOpenGL):
2042
2108
  def __init__(
2043
2109
  self,
2044
2110
  mapping_or_iterable: (
2045
- typing.Mapping[typing.Hashable, VMobject]
2046
- | typing.Iterable[tuple[typing.Hashable, VMobject]]
2111
+ Mapping[Hashable, VMobject] | Iterable[tuple[Hashable, VMobject]]
2047
2112
  ) = {},
2048
2113
  show_keys: bool = False,
2049
2114
  **kwargs,
2050
- ):
2115
+ ) -> None:
2051
2116
  super().__init__(**kwargs)
2052
2117
  self.show_keys = show_keys
2053
2118
  self.submob_dict = {}
2054
2119
  self.add(mapping_or_iterable)
2055
2120
 
2056
- def __repr__(self):
2057
- return __class__.__name__ + "(" + repr(self.submob_dict) + ")"
2121
+ def __repr__(self) -> str:
2122
+ return f"{self.__class__.__name__}({repr(self.submob_dict)})"
2058
2123
 
2059
2124
  def add(
2060
2125
  self,
2061
2126
  mapping_or_iterable: (
2062
- typing.Mapping[typing.Hashable, VMobject]
2063
- | typing.Iterable[tuple[typing.Hashable, VMobject]]
2127
+ Mapping[Hashable, VMobject] | Iterable[tuple[Hashable, VMobject]]
2064
2128
  ),
2065
- ):
2129
+ ) -> Self:
2066
2130
  """Adds the key-value pairs to the :class:`VDict` object.
2067
2131
 
2068
2132
  Also, it internally adds the value to the `submobjects` :class:`list`
@@ -2090,7 +2154,7 @@ class VDict(VMobject, metaclass=ConvertToOpenGL):
2090
2154
 
2091
2155
  return self
2092
2156
 
2093
- def remove(self, key: typing.Hashable):
2157
+ def remove(self, key: Hashable) -> Self:
2094
2158
  """Removes the mobject from the :class:`VDict` object having the key `key`
2095
2159
 
2096
2160
  Also, it internally removes the mobject from the `submobjects` :class:`list`
@@ -2118,7 +2182,7 @@ class VDict(VMobject, metaclass=ConvertToOpenGL):
2118
2182
  del self.submob_dict[key]
2119
2183
  return self
2120
2184
 
2121
- def __getitem__(self, key: typing.Hashable):
2185
+ def __getitem__(self, key: Hashable):
2122
2186
  """Override the [] operator for item retrieval.
2123
2187
 
2124
2188
  Parameters
@@ -2140,7 +2204,7 @@ class VDict(VMobject, metaclass=ConvertToOpenGL):
2140
2204
  submob = self.submob_dict[key]
2141
2205
  return submob
2142
2206
 
2143
- def __setitem__(self, key: typing.Hashable, value: VMobject):
2207
+ def __setitem__(self, key: Hashable, value: VMobject) -> None:
2144
2208
  """Override the [] operator for item assignment.
2145
2209
 
2146
2210
  Parameters
@@ -2165,7 +2229,7 @@ class VDict(VMobject, metaclass=ConvertToOpenGL):
2165
2229
  self.remove(key)
2166
2230
  self.add([(key, value)])
2167
2231
 
2168
- def __delitem__(self, key: typing.Hashable):
2232
+ def __delitem__(self, key: Hashable):
2169
2233
  """Override the del operator for deleting an item.
2170
2234
 
2171
2235
  Parameters
@@ -2197,7 +2261,7 @@ class VDict(VMobject, metaclass=ConvertToOpenGL):
2197
2261
  """
2198
2262
  del self.submob_dict[key]
2199
2263
 
2200
- def __contains__(self, key: typing.Hashable):
2264
+ def __contains__(self, key: Hashable):
2201
2265
  """Override the in operator.
2202
2266
 
2203
2267
  Parameters
@@ -2221,7 +2285,7 @@ class VDict(VMobject, metaclass=ConvertToOpenGL):
2221
2285
  """
2222
2286
  return key in self.submob_dict
2223
2287
 
2224
- def get_all_submobjects(self):
2288
+ def get_all_submobjects(self) -> list[list]:
2225
2289
  """To get all the submobjects associated with a particular :class:`VDict` object
2226
2290
 
2227
2291
  Returns
@@ -2239,7 +2303,7 @@ class VDict(VMobject, metaclass=ConvertToOpenGL):
2239
2303
  submobjects = self.submob_dict.values()
2240
2304
  return submobjects
2241
2305
 
2242
- def add_key_value_pair(self, key: typing.Hashable, value: VMobject):
2306
+ def add_key_value_pair(self, key: Hashable, value: VMobject) -> None:
2243
2307
  """A utility function used by :meth:`add` to add the key-value pair
2244
2308
  to :attr:`submob_dict`. Not really meant to be used externally.
2245
2309
 
@@ -2284,14 +2348,14 @@ class VDict(VMobject, metaclass=ConvertToOpenGL):
2284
2348
  class VectorizedPoint(VMobject, metaclass=ConvertToOpenGL):
2285
2349
  def __init__(
2286
2350
  self,
2287
- location=ORIGIN,
2288
- color=BLACK,
2289
- fill_opacity=0,
2290
- stroke_width=0,
2291
- artificial_width=0.01,
2292
- artificial_height=0.01,
2351
+ location: Point3D = ORIGIN,
2352
+ color: ManimColor = BLACK,
2353
+ fill_opacity: float = 0,
2354
+ stroke_width: float = 0,
2355
+ artificial_width: float = 0.01,
2356
+ artificial_height: float = 0.01,
2293
2357
  **kwargs,
2294
- ):
2358
+ ) -> None:
2295
2359
  self.artificial_width = artificial_width
2296
2360
  self.artificial_height = artificial_height
2297
2361
  super().__init__(
@@ -2305,17 +2369,17 @@ class VectorizedPoint(VMobject, metaclass=ConvertToOpenGL):
2305
2369
  basecls = OpenGLVMobject if config.renderer == RendererType.OPENGL else VMobject
2306
2370
 
2307
2371
  @basecls.width.getter
2308
- def width(self):
2372
+ def width(self) -> float:
2309
2373
  return self.artificial_width
2310
2374
 
2311
2375
  @basecls.height.getter
2312
- def height(self):
2376
+ def height(self) -> float:
2313
2377
  return self.artificial_height
2314
2378
 
2315
- def get_location(self):
2379
+ def get_location(self) -> Point3D:
2316
2380
  return np.array(self.points[0])
2317
2381
 
2318
- def set_location(self, new_loc):
2382
+ def set_location(self, new_loc: Point3D):
2319
2383
  self.set_points(np.array([new_loc]))
2320
2384
 
2321
2385
 
@@ -2336,7 +2400,7 @@ class CurvesAsSubmobjects(VGroup):
2336
2400
 
2337
2401
  """
2338
2402
 
2339
- def __init__(self, vmobject, **kwargs):
2403
+ def __init__(self, vmobject: VMobject, **kwargs) -> None:
2340
2404
  super().__init__(**kwargs)
2341
2405
  tuples = vmobject.get_cubic_bezier_tuples()
2342
2406
  for tup in tuples:
@@ -2345,6 +2409,69 @@ class CurvesAsSubmobjects(VGroup):
2345
2409
  part.match_style(vmobject)
2346
2410
  self.add(part)
2347
2411
 
2412
+ def point_from_proportion(self, alpha: float) -> Point3D:
2413
+ """Gets the point at a proportion along the path of the :class:`CurvesAsSubmobjects`.
2414
+
2415
+ Parameters
2416
+ ----------
2417
+ alpha
2418
+ The proportion along the the path of the :class:`CurvesAsSubmobjects`.
2419
+
2420
+ Returns
2421
+ -------
2422
+ :class:`numpy.ndarray`
2423
+ The point on the :class:`CurvesAsSubmobjects`.
2424
+
2425
+ Raises
2426
+ ------
2427
+ :exc:`ValueError`
2428
+ If ``alpha`` is not between 0 and 1.
2429
+ :exc:`Exception`
2430
+ If the :class:`CurvesAsSubmobjects` has no submobjects, or no submobject has points.
2431
+ """
2432
+ if alpha < 0 or alpha > 1:
2433
+ raise ValueError(f"Alpha {alpha} not between 0 and 1.")
2434
+
2435
+ self._throw_error_if_no_submobjects()
2436
+ submobjs_with_pts = self._get_submobjects_with_points()
2437
+
2438
+ if alpha == 1:
2439
+ return submobjs_with_pts[-1].points[-1]
2440
+
2441
+ submobjs_arc_lengths = tuple(
2442
+ part.get_arc_length() for part in submobjs_with_pts
2443
+ )
2444
+
2445
+ total_length = sum(submobjs_arc_lengths)
2446
+ target_length = alpha * total_length
2447
+ current_length = 0
2448
+
2449
+ for i, part in enumerate(submobjs_with_pts):
2450
+ part_length = submobjs_arc_lengths[i]
2451
+ if current_length + part_length >= target_length:
2452
+ residue = (target_length - current_length) / part_length
2453
+ return part.point_from_proportion(residue)
2454
+
2455
+ current_length += part_length
2456
+
2457
+ def _throw_error_if_no_submobjects(self):
2458
+ if len(self.submobjects) == 0:
2459
+ caller_name = sys._getframe(1).f_code.co_name
2460
+ raise Exception(
2461
+ f"Cannot call CurvesAsSubmobjects.{caller_name} for a CurvesAsSubmobject with no submobjects"
2462
+ )
2463
+
2464
+ def _get_submobjects_with_points(self):
2465
+ submobjs_with_pts = tuple(
2466
+ part for part in self.submobjects if len(part.points) > 0
2467
+ )
2468
+ if len(submobjs_with_pts) == 0:
2469
+ caller_name = sys._getframe(1).f_code.co_name
2470
+ raise Exception(
2471
+ f"Cannot call CurvesAsSubmobjects.{caller_name} for a CurvesAsSubmobject whose submobjects have no points"
2472
+ )
2473
+ return submobjs_with_pts
2474
+
2348
2475
 
2349
2476
  class DashedVMobject(VMobject, metaclass=ConvertToOpenGL):
2350
2477
  """A :class:`VMobject` composed of dashes instead of lines.
@@ -2402,14 +2529,14 @@ class DashedVMobject(VMobject, metaclass=ConvertToOpenGL):
2402
2529
 
2403
2530
  def __init__(
2404
2531
  self,
2405
- vmobject,
2406
- num_dashes=15,
2407
- dashed_ratio=0.5,
2408
- dash_offset=0,
2409
- color=WHITE,
2410
- equal_lengths=True,
2532
+ vmobject: VMobject,
2533
+ num_dashes: int = 15,
2534
+ dashed_ratio: float = 0.5,
2535
+ dash_offset: float = 0,
2536
+ color: ManimColor = WHITE,
2537
+ equal_lengths: bool = True,
2411
2538
  **kwargs,
2412
- ):
2539
+ ) -> None:
2413
2540
  self.dashed_ratio = dashed_ratio
2414
2541
  self.num_dashes = num_dashes
2415
2542
  super().__init__(color=color, **kwargs)