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/mobject/mobject.py CHANGED
@@ -1,36 +1,27 @@
1
1
  """Base classes for objects that can be displayed."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  __all__ = ["Mobject", "Group", "override_animate"]
5
6
 
6
7
 
7
8
  import copy
9
+ import inspect
8
10
  import itertools as it
11
+ import math
9
12
  import operator as op
10
13
  import random
11
14
  import sys
12
15
  import types
13
16
  import warnings
17
+ from collections.abc import Callable, Iterable
14
18
  from functools import partialmethod, reduce
15
- from math import ceil
16
19
  from pathlib import Path
17
- from typing import (
18
- TYPE_CHECKING,
19
- Callable,
20
- Dict,
21
- Iterable,
22
- List,
23
- Optional,
24
- Sequence,
25
- Tuple,
26
- Type,
27
- TypeVar,
28
- Union,
29
- )
20
+ from typing import TYPE_CHECKING, Any, Literal
30
21
 
31
22
  import numpy as np
32
- from colour import Color
33
23
 
24
+ from manim.data_structures import MethodWithArgs
34
25
  from manim.mobject.opengl.opengl_compatibility import ConvertToOpenGL
35
26
 
36
27
  from .. import config, logger
@@ -39,24 +30,38 @@ from ..utils.color import (
39
30
  BLACK,
40
31
  WHITE,
41
32
  YELLOW_C,
42
- Colors,
33
+ ManimColor,
34
+ ParsableManimColor,
43
35
  color_gradient,
44
36
  interpolate_color,
45
37
  )
46
38
  from ..utils.exceptions import MultiAnimationOverrideException
47
39
  from ..utils.iterables import list_update, remove_list_redundancies
48
40
  from ..utils.paths import straight_path
49
- from ..utils.simple_functions import get_parameters
50
41
  from ..utils.space_ops import angle_between_vectors, normalize, rotation_matrix
51
42
 
52
- # TODO: Explain array_attrs
53
-
54
- Updater = Union[Callable[["Mobject"], None], Callable[["Mobject", float], None]]
55
- T = TypeVar("T", bound="Mobject")
56
-
57
43
  if TYPE_CHECKING:
44
+ from typing_extensions import Self, TypeAlias
45
+
46
+ from manim.typing import (
47
+ FunctionOverride,
48
+ MappingFunction,
49
+ MatrixMN,
50
+ MultiMappingFunction,
51
+ PathFuncType,
52
+ PixelArray,
53
+ Point3D,
54
+ Point3DLike,
55
+ Point3DLike_Array,
56
+ Vector3DLike,
57
+ )
58
+
58
59
  from ..animation.animation import Animation
59
60
 
61
+ TimeBasedUpdater: TypeAlias = Callable[["Mobject", float], object]
62
+ NonTimeBasedUpdater: TypeAlias = Callable[["Mobject"], object]
63
+ Updater: TypeAlias = NonTimeBasedUpdater | TimeBasedUpdater
64
+
60
65
 
61
66
  class Mobject:
62
67
  """Mathematical Object: base class for objects that can be displayed on screen.
@@ -81,36 +86,100 @@ class Mobject:
81
86
  animation_overrides = {}
82
87
 
83
88
  @classmethod
84
- def __init_subclass__(cls, **kwargs):
89
+ def __init_subclass__(cls, **kwargs) -> None:
85
90
  super().__init_subclass__(**kwargs)
86
91
 
87
92
  cls.animation_overrides: dict[
88
93
  type[Animation],
89
- Callable[[Mobject], Animation],
94
+ FunctionOverride,
90
95
  ] = {}
91
96
  cls._add_intrinsic_animation_overrides()
92
97
  cls._original__init__ = cls.__init__
93
98
 
94
- def __init__(self, color=WHITE, name=None, dim=3, target=None, z_index=0):
99
+ def __init__(
100
+ self,
101
+ color: ParsableManimColor | list[ParsableManimColor] = WHITE,
102
+ name: str | None = None,
103
+ dim: int = 3,
104
+ target=None,
105
+ z_index: float = 0,
106
+ ) -> None:
95
107
  self.name = self.__class__.__name__ if name is None else name
96
108
  self.dim = dim
97
109
  self.target = target
98
110
  self.z_index = z_index
99
111
  self.point_hash = None
100
112
  self.submobjects = []
101
- self.updaters = []
113
+ self.updaters: list[Updater] = []
102
114
  self.updating_suspended = False
103
- self.color = Color(color) if color else None
115
+ self.color = ManimColor.parse(color)
104
116
 
105
117
  self.reset_points()
106
118
  self.generate_points()
107
119
  self.init_colors()
108
120
 
121
+ def _assert_valid_submobjects(self, submobjects: Iterable[Mobject]) -> Self:
122
+ """Check that all submobjects are actually instances of
123
+ :class:`Mobject`, and that none of them is ``self`` (a
124
+ :class:`Mobject` cannot contain itself).
125
+
126
+ This is an auxiliary function called when adding Mobjects to the
127
+ :attr:`submobjects` list.
128
+
129
+ This function is intended to be overridden by subclasses such as
130
+ :class:`VMobject`, which should assert that only other VMobjects
131
+ may be added into it.
132
+
133
+ Parameters
134
+ ----------
135
+ submobjects
136
+ The list containing values to validate.
137
+
138
+ Returns
139
+ -------
140
+ :class:`Mobject`
141
+ The Mobject itself.
142
+
143
+ Raises
144
+ ------
145
+ TypeError
146
+ If any of the values in `submobjects` is not a :class:`Mobject`.
147
+ ValueError
148
+ If there was an attempt to add a :class:`Mobject` as its own
149
+ submobject.
150
+ """
151
+ return self._assert_valid_submobjects_internal(submobjects, Mobject)
152
+
153
+ def _assert_valid_submobjects_internal(
154
+ self, submobjects: list[Mobject], mob_class: type[Mobject]
155
+ ) -> Self:
156
+ for i, submob in enumerate(submobjects):
157
+ if not isinstance(submob, mob_class):
158
+ error_message = (
159
+ f"Only values of type {mob_class.__name__} can be added "
160
+ f"as submobjects of {type(self).__name__}, but the value "
161
+ f"{submob} (at index {i}) is of type "
162
+ f"{type(submob).__name__}."
163
+ )
164
+ # Intended for subclasses such as VMobject, which
165
+ # cannot have regular Mobjects as submobjects
166
+ if isinstance(submob, Mobject):
167
+ error_message += (
168
+ " You can try adding this value into a Group instead."
169
+ )
170
+ raise TypeError(error_message)
171
+ if submob is self:
172
+ raise ValueError(
173
+ f"Cannot add {type(self).__name__} as a submobject of "
174
+ f"itself (at index {i})."
175
+ )
176
+ return self
177
+
109
178
  @classmethod
110
179
  def animation_override_for(
111
180
  cls,
112
181
  animation_class: type[Animation],
113
- ) -> Callable[[Mobject, ...], Animation] | None:
182
+ ) -> FunctionOverride | None:
114
183
  """Returns the function defining a specific animation override for this class.
115
184
 
116
185
  Parameters
@@ -130,7 +199,7 @@ class Mobject:
130
199
  return None
131
200
 
132
201
  @classmethod
133
- def _add_intrinsic_animation_overrides(cls):
202
+ def _add_intrinsic_animation_overrides(cls) -> None:
134
203
  """Initializes animation overrides marked with the :func:`~.override_animation`
135
204
  decorator.
136
205
  """
@@ -148,8 +217,8 @@ class Mobject:
148
217
  def add_animation_override(
149
218
  cls,
150
219
  animation_class: type[Animation],
151
- override_func: Callable[[Mobject, ...], Animation],
152
- ):
220
+ override_func: FunctionOverride,
221
+ ) -> None:
153
222
  """Add an animation override.
154
223
 
155
224
  This does not apply to subclasses.
@@ -160,7 +229,7 @@ class Mobject:
160
229
  The animation type to be overridden
161
230
  override_func
162
231
  The function returning an animation replacing the default animation. It gets
163
- passed the parameters given to the animnation constructor.
232
+ passed the parameters given to the animation constructor.
164
233
 
165
234
  Raises
166
235
  ------
@@ -178,7 +247,7 @@ class Mobject:
178
247
  )
179
248
 
180
249
  @classmethod
181
- def set_default(cls, **kwargs):
250
+ def set_default(cls, **kwargs) -> None:
182
251
  """Sets the default values of keyword arguments.
183
252
 
184
253
  If this method is called without any additional keyword
@@ -201,10 +270,10 @@ class Mobject:
201
270
  >>> from manim import Square, GREEN
202
271
  >>> Square.set_default(color=GREEN, fill_opacity=0.25)
203
272
  >>> s = Square(); s.color, s.fill_opacity
204
- (<Color #83c167>, 0.25)
273
+ (ManimColor('#83C167'), 0.25)
205
274
  >>> Square.set_default()
206
275
  >>> s = Square(); s.color, s.fill_opacity
207
- (<Color white>, 0.0)
276
+ (ManimColor('#FFFFFF'), 0.0)
208
277
 
209
278
  .. manim:: ChangedDefaultTextcolor
210
279
  :save_last_frame:
@@ -226,7 +295,7 @@ class Mobject:
226
295
  cls.__init__ = cls._original__init__
227
296
 
228
297
  @property
229
- def animate(self) -> _AnimationBuilder | T:
298
+ def animate(self) -> _AnimationBuilder | Self:
230
299
  """Used to animate the application of any method of :code:`self`.
231
300
 
232
301
  Any method called on :code:`animate` is converted to an animation of applying
@@ -249,7 +318,9 @@ class Mobject:
249
318
 
250
319
  ::
251
320
 
252
- self.play(my_mobject.animate.shift(RIGHT), my_mobject.animate.rotate(PI))
321
+ self.play(
322
+ my_mobject.animate.shift(RIGHT), my_mobject.animate.rotate(PI)
323
+ )
253
324
 
254
325
  make use of method chaining.
255
326
 
@@ -316,14 +387,14 @@ class Mobject:
316
387
  will interpolate the :class:`~.Mobject` between its points prior to
317
388
  ``.animate`` and its points after applying ``.animate`` to it. This may
318
389
  result in unexpected behavior when attempting to interpolate along paths,
319
- or rotations.
390
+ or rotations (see :meth:`.rotate`).
320
391
  If you want animations to consider the points between, consider using
321
- :class:`~.ValueTracker` with updaters instead.
392
+ :class:`~.ValueTracker` with updaters instead (see :meth:`.add_updater`).
322
393
 
323
394
  """
324
395
  return _AnimationBuilder(self)
325
396
 
326
- def __deepcopy__(self, clone_from_id):
397
+ def __deepcopy__(self, clone_from_id) -> Self:
327
398
  cls = self.__class__
328
399
  result = cls.__new__(cls)
329
400
  clone_from_id[id(self)] = result
@@ -332,30 +403,28 @@ class Mobject:
332
403
  result.original_id = str(id(self))
333
404
  return result
334
405
 
335
- def __repr__(self):
406
+ def __repr__(self) -> str:
336
407
  return str(self.name)
337
408
 
338
- def reset_points(self):
409
+ def reset_points(self) -> None:
339
410
  """Sets :attr:`points` to be an empty array."""
340
411
  self.points = np.zeros((0, self.dim))
341
412
 
342
- def init_colors(self):
413
+ def init_colors(self) -> object:
343
414
  """Initializes the colors.
344
415
 
345
416
  Gets called upon creation. This is an empty method that can be implemented by
346
417
  subclasses.
347
418
  """
348
- pass
349
419
 
350
- def generate_points(self):
420
+ def generate_points(self) -> object:
351
421
  """Initializes :attr:`points` and therefore the shape.
352
422
 
353
423
  Gets called upon creation. This is an empty method that can be implemented by
354
424
  subclasses.
355
425
  """
356
- pass
357
426
 
358
- def add(self, *mobjects: Mobject):
427
+ def add(self, *mobjects: Mobject) -> Self:
359
428
  """Add mobjects as submobjects.
360
429
 
361
430
  The mobjects are added to :attr:`submobjects`.
@@ -406,12 +475,19 @@ class Mobject:
406
475
  >>> len(outer.submobjects)
407
476
  1
408
477
 
478
+ Only Mobjects can be added::
479
+
480
+ >>> outer.add(3)
481
+ Traceback (most recent call last):
482
+ ...
483
+ TypeError: Only values of type Mobject can be added as submobjects of Mobject, but the value 3 (at index 0) is of type int.
484
+
409
485
  Adding an object to itself raises an error::
410
486
 
411
487
  >>> outer.add(outer)
412
488
  Traceback (most recent call last):
413
489
  ...
414
- ValueError: Mobject cannot contain self
490
+ ValueError: Cannot add Mobject as a submobject of itself (at index 0).
415
491
 
416
492
  A given mobject cannot be added as a submobject
417
493
  twice to some parent::
@@ -425,21 +501,18 @@ class Mobject:
425
501
  [child]
426
502
 
427
503
  """
428
- for m in mobjects:
429
- if not isinstance(m, Mobject):
430
- raise TypeError("All submobjects must be of type Mobject")
431
- if m is self:
432
- raise ValueError("Mobject cannot contain self")
433
- if any(mobjects.count(elem) > 1 for elem in mobjects):
434
- logger.warning(
435
- "Attempted adding some Mobject as a child more than once, "
436
- "this is not possible. Repetitions are ignored.",
437
- )
438
- mobjects = remove_list_redundancies(mobjects)
439
- self.submobjects = list_update(self.submobjects, mobjects)
504
+ self._assert_valid_submobjects(mobjects)
505
+ unique_mobjects = remove_list_redundancies(mobjects)
506
+ if len(mobjects) != len(unique_mobjects):
507
+ logger.warning(
508
+ "Attempted adding some Mobject as a child more than once, "
509
+ "this is not possible. Repetitions are ignored.",
510
+ )
511
+
512
+ self.submobjects = list_update(self.submobjects, unique_mobjects)
440
513
  return self
441
514
 
442
- def insert(self, index: int, mobject: Mobject):
515
+ def insert(self, index: int, mobject: Mobject) -> None:
443
516
  """Inserts a mobject at a specific position into self.submobjects
444
517
 
445
518
  Effectively just calls ``self.submobjects.insert(index, mobject)``,
@@ -454,19 +527,16 @@ class Mobject:
454
527
  mobject
455
528
  The mobject to be inserted.
456
529
  """
457
- if not isinstance(mobject, Mobject):
458
- raise TypeError("All submobjects must be of type Mobject")
459
- if mobject is self:
460
- raise ValueError("Mobject cannot contain self")
530
+ self._assert_valid_submobjects([mobject])
461
531
  self.submobjects.insert(index, mobject)
462
532
 
463
- def __add__(self, mobject):
533
+ def __add__(self, mobject: Mobject):
464
534
  raise NotImplementedError
465
535
 
466
- def __iadd__(self, mobject):
536
+ def __iadd__(self, mobject: Mobject):
467
537
  raise NotImplementedError
468
538
 
469
- def add_to_back(self, *mobjects: Mobject):
539
+ def add_to_back(self, *mobjects: Mobject) -> Self:
470
540
  """Add all passed mobjects to the back of the submobjects.
471
541
 
472
542
  If :attr:`submobjects` already contains the given mobjects, they just get moved
@@ -510,19 +580,13 @@ class Mobject:
510
580
  :meth:`add`
511
581
 
512
582
  """
513
- if self in mobjects:
514
- raise ValueError("A mobject shouldn't contain itself")
515
-
516
- for mobject in mobjects:
517
- if not isinstance(mobject, Mobject):
518
- raise TypeError("All submobjects must be of type Mobject")
519
-
583
+ self._assert_valid_submobjects(mobjects)
520
584
  self.remove(*mobjects)
521
585
  # dict.fromkeys() removes duplicates while maintaining order
522
586
  self.submobjects = list(dict.fromkeys(mobjects)) + self.submobjects
523
587
  return self
524
588
 
525
- def remove(self, *mobjects: Mobject):
589
+ def remove(self, *mobjects: Mobject) -> Self:
526
590
  """Remove :attr:`submobjects`.
527
591
 
528
592
  The mobjects are removed from :attr:`submobjects`, if they exist.
@@ -555,7 +619,7 @@ class Mobject:
555
619
  def __isub__(self, other):
556
620
  raise NotImplementedError
557
621
 
558
- def set(self, **kwargs):
622
+ def set(self, **kwargs) -> Self:
559
623
  """Sets attributes.
560
624
 
561
625
  I.e. ``my_mobject.set(foo=1)`` applies ``my_mobject.foo = 1``.
@@ -606,13 +670,12 @@ class Mobject:
606
670
  >>> mob.foo
607
671
  0
608
672
  """
609
-
610
673
  for attr, value in kwargs.items():
611
674
  setattr(self, attr, value)
612
675
 
613
676
  return self
614
677
 
615
- def __getattr__(self, attr):
678
+ def __getattr__(self, attr: str) -> types.MethodType:
616
679
  # Add automatic compatibility layer
617
680
  # between properties and get_* and set_*
618
681
  # methods.
@@ -660,7 +723,7 @@ class Mobject:
660
723
  raise AttributeError(f"{type(self).__name__} object has no attribute '{attr}'")
661
724
 
662
725
  @property
663
- def width(self):
726
+ def width(self) -> float:
664
727
  """The width of the mobject.
665
728
 
666
729
  Returns
@@ -688,16 +751,15 @@ class Mobject:
688
751
  :meth:`length_over_dim`
689
752
 
690
753
  """
691
-
692
754
  # Get the length across the X dimension
693
755
  return self.length_over_dim(0)
694
756
 
695
757
  @width.setter
696
- def width(self, value):
758
+ def width(self, value: float):
697
759
  self.scale_to_fit_width(value)
698
760
 
699
761
  @property
700
- def height(self):
762
+ def height(self) -> float:
701
763
  """The height of the mobject.
702
764
 
703
765
  Returns
@@ -725,16 +787,15 @@ class Mobject:
725
787
  :meth:`length_over_dim`
726
788
 
727
789
  """
728
-
729
790
  # Get the length across the Y dimension
730
791
  return self.length_over_dim(1)
731
792
 
732
793
  @height.setter
733
- def height(self, value):
794
+ def height(self, value: float):
734
795
  self.scale_to_fit_height(value)
735
796
 
736
797
  @property
737
- def depth(self):
798
+ def depth(self) -> float:
738
799
  """The depth of the mobject.
739
800
 
740
801
  Returns
@@ -746,25 +807,25 @@ class Mobject:
746
807
  :meth:`length_over_dim`
747
808
 
748
809
  """
749
-
750
810
  # Get the length across the Z dimension
751
811
  return self.length_over_dim(2)
752
812
 
753
813
  @depth.setter
754
- def depth(self, value):
814
+ def depth(self, value: float):
755
815
  self.scale_to_fit_depth(value)
756
816
 
757
- def get_array_attrs(self):
817
+ # Can't be staticmethod because of point_cloud_mobject.py
818
+ def get_array_attrs(self) -> list[Literal["points"]]:
758
819
  return ["points"]
759
820
 
760
- def apply_over_attr_arrays(self, func):
821
+ def apply_over_attr_arrays(self, func: MultiMappingFunction) -> Self:
761
822
  for attr in self.get_array_attrs():
762
823
  setattr(self, attr, func(getattr(self, attr)))
763
824
  return self
764
825
 
765
826
  # Displaying
766
827
 
767
- def get_image(self, camera=None):
828
+ def get_image(self, camera=None) -> PixelArray:
768
829
  if camera is None:
769
830
  from ..camera.camera import Camera
770
831
 
@@ -772,17 +833,18 @@ class Mobject:
772
833
  camera.capture_mobject(self)
773
834
  return camera.get_image()
774
835
 
775
- def show(self, camera=None):
836
+ def show(self, camera=None) -> None:
776
837
  self.get_image(camera=camera).show()
777
838
 
778
- def save_image(self, name=None):
839
+ def save_image(self, name: str | None = None) -> None:
779
840
  """Saves an image of only this :class:`Mobject` at its position to a png
780
- file."""
841
+ file.
842
+ """
781
843
  self.get_image().save(
782
844
  Path(config.get_dir("video_dir")).joinpath((name or str(self)) + ".png"),
783
845
  )
784
846
 
785
- def copy(self: T) -> T:
847
+ def copy(self) -> Self:
786
848
  """Create and return an identical copy of the :class:`Mobject` including all
787
849
  :attr:`submobjects`.
788
850
 
@@ -797,7 +859,7 @@ class Mobject:
797
859
  """
798
860
  return copy.deepcopy(self)
799
861
 
800
- def generate_target(self, use_deepcopy=False):
862
+ def generate_target(self, use_deepcopy: bool = False) -> Self:
801
863
  self.target = None # Prevent unbounded linear recursion
802
864
  if use_deepcopy:
803
865
  self.target = copy.deepcopy(self)
@@ -807,7 +869,7 @@ class Mobject:
807
869
 
808
870
  # Updating
809
871
 
810
- def update(self, dt: float = 0, recursive: bool = True):
872
+ def update(self, dt: float = 0, recursive: bool = True) -> Self:
811
873
  """Apply all updaters.
812
874
 
813
875
  Does nothing if updating is suspended.
@@ -831,20 +893,18 @@ class Mobject:
831
893
  :meth:`get_updaters`
832
894
 
833
895
  """
834
- if self.updating_suspended:
835
- return self
836
- for updater in self.updaters:
837
- parameters = get_parameters(updater)
838
- if "dt" in parameters:
839
- updater(self, dt)
840
- else:
841
- updater(self)
896
+ if not self.updating_suspended:
897
+ for updater in self.updaters:
898
+ if "dt" in inspect.signature(updater).parameters:
899
+ updater(self, dt)
900
+ else:
901
+ updater(self)
842
902
  if recursive:
843
903
  for submob in self.submobjects:
844
- submob.update(dt, recursive)
904
+ submob.update(dt, recursive=recursive)
845
905
  return self
846
906
 
847
- def get_time_based_updaters(self) -> list[Updater]:
907
+ def get_time_based_updaters(self) -> list[TimeBasedUpdater]:
848
908
  """Return all updaters using the ``dt`` parameter.
849
909
 
850
910
  The updaters use this parameter as the input for difference in time.
@@ -860,14 +920,18 @@ class Mobject:
860
920
  :meth:`has_time_based_updater`
861
921
 
862
922
  """
863
- return [updater for updater in self.updaters if "dt" in get_parameters(updater)]
923
+ return [
924
+ updater
925
+ for updater in self.updaters
926
+ if "dt" in inspect.signature(updater).parameters
927
+ ]
864
928
 
865
929
  def has_time_based_updater(self) -> bool:
866
930
  """Test if ``self`` has a time based updater.
867
931
 
868
932
  Returns
869
933
  -------
870
- class:`bool`
934
+ :class:`bool`
871
935
  ``True`` if at least one updater uses the ``dt`` parameter, ``False``
872
936
  otherwise.
873
937
 
@@ -876,7 +940,9 @@ class Mobject:
876
940
  :meth:`get_time_based_updaters`
877
941
 
878
942
  """
879
- return any("dt" in get_parameters(updater) for updater in self.updaters)
943
+ return any(
944
+ "dt" in inspect.signature(updater).parameters for updater in self.updaters
945
+ )
880
946
 
881
947
  def get_updaters(self) -> list[Updater]:
882
948
  """Return all updaters.
@@ -894,7 +960,7 @@ class Mobject:
894
960
  """
895
961
  return self.updaters
896
962
 
897
- def get_family_updaters(self):
963
+ def get_family_updaters(self) -> list[Updater]:
898
964
  return list(it.chain(*(sm.get_updaters() for sm in self.get_family())))
899
965
 
900
966
  def add_updater(
@@ -902,7 +968,7 @@ class Mobject:
902
968
  update_function: Updater,
903
969
  index: int | None = None,
904
970
  call_updater: bool = False,
905
- ):
971
+ ) -> Self:
906
972
  """Add an update function to this mobject.
907
973
 
908
974
  Update functions, or updaters in short, are functions that are applied to the
@@ -935,26 +1001,26 @@ class Mobject:
935
1001
 
936
1002
  class NextToUpdater(Scene):
937
1003
  def construct(self):
938
- def dot_position(mobject):
1004
+ def update_label(mobject):
939
1005
  mobject.set_value(dot.get_center()[0])
940
1006
  mobject.next_to(dot)
941
1007
 
942
1008
  dot = Dot(RIGHT*3)
943
1009
  label = DecimalNumber()
944
- label.add_updater(dot_position)
1010
+ label.add_updater(update_label)
945
1011
  self.add(dot, label)
946
1012
 
947
- self.play(Rotating(dot, about_point=ORIGIN, angle=TAU, run_time=TAU, rate_func=linear))
1013
+ self.play(Rotating(dot, angle=TAU, about_point=ORIGIN, run_time=TAU, rate_func=linear))
948
1014
 
949
1015
  .. manim:: DtUpdater
950
1016
 
951
1017
  class DtUpdater(Scene):
952
1018
  def construct(self):
953
- line = Square()
1019
+ square = Square()
954
1020
 
955
- #Let the line rotate 90° per second
956
- line.add_updater(lambda mobject, dt: mobject.rotate(dt*90*DEGREES))
957
- self.add(line)
1021
+ #Let the square rotate 90° per second
1022
+ square.add_updater(lambda mobject, dt: mobject.rotate(dt*90*DEGREES))
1023
+ self.add(square)
958
1024
  self.wait(2)
959
1025
 
960
1026
  See also
@@ -962,17 +1028,23 @@ class Mobject:
962
1028
  :meth:`get_updaters`
963
1029
  :meth:`remove_updater`
964
1030
  :class:`~.UpdateFromFunc`
1031
+ :class:`~.Rotating`
1032
+ :meth:`rotate`
1033
+ :attr:`~.Mobject.animate`
965
1034
  """
966
-
967
1035
  if index is None:
968
1036
  self.updaters.append(update_function)
969
1037
  else:
970
1038
  self.updaters.insert(index, update_function)
971
1039
  if call_updater:
972
- update_function(self, 0)
1040
+ parameters = inspect.signature(update_function).parameters
1041
+ if "dt" in parameters:
1042
+ update_function(self, 0)
1043
+ else:
1044
+ update_function(self)
973
1045
  return self
974
1046
 
975
- def remove_updater(self, update_function: Updater):
1047
+ def remove_updater(self, update_function: Updater) -> Self:
976
1048
  """Remove an updater.
977
1049
 
978
1050
  If the same updater is applied multiple times, every instance gets removed.
@@ -999,7 +1071,7 @@ class Mobject:
999
1071
  self.updaters.remove(update_function)
1000
1072
  return self
1001
1073
 
1002
- def clear_updaters(self, recursive: bool = True):
1074
+ def clear_updaters(self, recursive: bool = True) -> Self:
1003
1075
  """Remove every updater.
1004
1076
 
1005
1077
  Parameters
@@ -1025,7 +1097,7 @@ class Mobject:
1025
1097
  submob.clear_updaters()
1026
1098
  return self
1027
1099
 
1028
- def match_updaters(self, mobject: Mobject):
1100
+ def match_updaters(self, mobject: Mobject) -> Self:
1029
1101
  """Match the updaters of the given mobject.
1030
1102
 
1031
1103
  Parameters
@@ -1049,13 +1121,12 @@ class Mobject:
1049
1121
  :meth:`clear_updaters`
1050
1122
 
1051
1123
  """
1052
-
1053
1124
  self.clear_updaters()
1054
1125
  for updater in mobject.get_updaters():
1055
1126
  self.add_updater(updater)
1056
1127
  return self
1057
1128
 
1058
- def suspend_updating(self, recursive: bool = True):
1129
+ def suspend_updating(self, recursive: bool = True) -> Self:
1059
1130
  """Disable updating from updaters and animations.
1060
1131
 
1061
1132
 
@@ -1075,14 +1146,13 @@ class Mobject:
1075
1146
  :meth:`add_updater`
1076
1147
 
1077
1148
  """
1078
-
1079
1149
  self.updating_suspended = True
1080
1150
  if recursive:
1081
1151
  for submob in self.submobjects:
1082
1152
  submob.suspend_updating(recursive)
1083
1153
  return self
1084
1154
 
1085
- def resume_updating(self, recursive: bool = True):
1155
+ def resume_updating(self, recursive: bool = True) -> Self:
1086
1156
  """Enable updating from updaters and animations.
1087
1157
 
1088
1158
  Parameters
@@ -1110,7 +1180,7 @@ class Mobject:
1110
1180
 
1111
1181
  # Transforming operations
1112
1182
 
1113
- def apply_to_family(self, func: Callable[[Mobject], None]):
1183
+ def apply_to_family(self, func: Callable[[Mobject], None]) -> None:
1114
1184
  """Apply a function to ``self`` and every submobject with points recursively.
1115
1185
 
1116
1186
  Parameters
@@ -1132,7 +1202,7 @@ class Mobject:
1132
1202
  for mob in self.family_members_with_points():
1133
1203
  func(mob)
1134
1204
 
1135
- def shift(self, *vectors: np.ndarray):
1205
+ def shift(self, *vectors: Vector3DLike) -> Self:
1136
1206
  """Shift by the given vectors.
1137
1207
 
1138
1208
  Parameters
@@ -1150,7 +1220,6 @@ class Mobject:
1150
1220
  --------
1151
1221
  :meth:`move_to`
1152
1222
  """
1153
-
1154
1223
  total_vector = reduce(op.add, vectors)
1155
1224
  for mob in self.family_members_with_points():
1156
1225
  mob.points = mob.points.astype("float")
@@ -1158,7 +1227,13 @@ class Mobject:
1158
1227
 
1159
1228
  return self
1160
1229
 
1161
- def scale(self, scale_factor: float, **kwargs):
1230
+ def scale(
1231
+ self,
1232
+ scale_factor: float,
1233
+ *,
1234
+ about_point: Point3DLike | None = None,
1235
+ about_edge: Vector3DLike | None = None,
1236
+ ) -> Self:
1162
1237
  r"""Scale the size by a factor.
1163
1238
 
1164
1239
  Default behavior is to scale about the center of the mobject.
@@ -1169,9 +1244,10 @@ class Mobject:
1169
1244
  The scaling factor :math:`\alpha`. If :math:`0 < |\alpha| < 1`, the mobject
1170
1245
  will shrink, and for :math:`|\alpha| > 1` it will grow. Furthermore,
1171
1246
  if :math:`\alpha < 0`, the mobject is also flipped.
1172
- kwargs
1173
- Additional keyword arguments passed to
1174
- :meth:`apply_points_function_about_point`.
1247
+ about_point
1248
+ The point about which to apply the scaling.
1249
+ about_edge
1250
+ The edge about which to apply the scaling.
1175
1251
 
1176
1252
  Returns
1177
1253
  -------
@@ -1200,29 +1276,92 @@ class Mobject:
1200
1276
 
1201
1277
  """
1202
1278
  self.apply_points_function_about_point(
1203
- lambda points: scale_factor * points, **kwargs
1279
+ lambda points: scale_factor * points, about_point, about_edge
1204
1280
  )
1205
1281
  return self
1206
1282
 
1207
- def rotate_about_origin(self, angle, axis=OUT, axes=[]):
1283
+ def rotate_about_origin(self, angle: float, axis: Vector3DLike = OUT) -> Self:
1208
1284
  """Rotates the :class:`~.Mobject` about the ORIGIN, which is at [0,0,0]."""
1209
1285
  return self.rotate(angle, axis, about_point=ORIGIN)
1210
1286
 
1211
1287
  def rotate(
1212
1288
  self,
1213
- angle,
1214
- axis=OUT,
1215
- about_point: Sequence[float] | None = None,
1216
- **kwargs,
1217
- ):
1218
- """Rotates the :class:`~.Mobject` about a certain point."""
1289
+ angle: float,
1290
+ axis: Vector3DLike = OUT,
1291
+ *,
1292
+ about_point: Point3DLike | None = None,
1293
+ about_edge: Vector3DLike | None = None,
1294
+ ) -> Self:
1295
+ """Rotates the :class:`~.Mobject` around a specified axis and point.
1296
+
1297
+ Parameters
1298
+ ----------
1299
+ angle
1300
+ The angle of rotation in radians. Predefined constants such as ``DEGREES``
1301
+ can also be used to specify the angle in degrees.
1302
+ axis
1303
+ The rotation axis (see :class:`~.Rotating` for more).
1304
+ about_point
1305
+ The point about which the mobject rotates. If ``None``, rotation occurs around
1306
+ the center of the mobject.
1307
+ about_edge
1308
+ The edge about which to apply the scaling.
1309
+
1310
+ Returns
1311
+ -------
1312
+ :class:`Mobject`
1313
+ ``self`` (for method chaining)
1314
+
1315
+
1316
+ .. note::
1317
+ To animate a rotation, use :class:`~.Rotating` or :class:`~.Rotate`
1318
+ instead of ``.animate.rotate(...)``.
1319
+ The ``.animate.rotate(...)`` syntax only applies a transformation
1320
+ from the initial state to the final rotated state
1321
+ (interpolation between the two states), without showing proper rotational motion
1322
+ based on the angle (from 0 to the given angle).
1323
+
1324
+ Examples
1325
+ --------
1326
+
1327
+ .. manim:: RotateMethodExample
1328
+ :save_last_frame:
1329
+
1330
+ class RotateMethodExample(Scene):
1331
+ def construct(self):
1332
+ circle = Circle(radius=1, color=BLUE)
1333
+ line = Line(start=ORIGIN, end=RIGHT)
1334
+ arrow1 = Arrow(start=ORIGIN, end=RIGHT, buff=0, color=GOLD)
1335
+ group1 = VGroup(circle, line, arrow1)
1336
+
1337
+ group2 = group1.copy()
1338
+ arrow2 = group2[2]
1339
+ arrow2.rotate(angle=PI / 4, about_point=arrow2.get_start())
1340
+
1341
+ group3 = group1.copy()
1342
+ arrow3 = group3[2]
1343
+ arrow3.rotate(angle=120 * DEGREES, about_point=arrow3.get_start())
1344
+
1345
+ self.add(VGroup(group1, group2, group3).arrange(RIGHT, buff=1))
1346
+
1347
+ See also
1348
+ --------
1349
+ :class:`~.Rotating`, :class:`~.Rotate`, :attr:`~.Mobject.animate`, :meth:`apply_points_function_about_point`
1350
+
1351
+ """
1219
1352
  rot_matrix = rotation_matrix(angle, axis)
1220
1353
  self.apply_points_function_about_point(
1221
- lambda points: np.dot(points, rot_matrix.T), about_point, **kwargs
1354
+ lambda points: np.dot(points, rot_matrix.T), about_point, about_edge
1222
1355
  )
1223
1356
  return self
1224
1357
 
1225
- def flip(self, axis=UP, **kwargs):
1358
+ def flip(
1359
+ self,
1360
+ axis: Vector3DLike = UP,
1361
+ *,
1362
+ about_point: Point3DLike | None = None,
1363
+ about_edge: Vector3DLike | None = None,
1364
+ ) -> Self:
1226
1365
  """Flips/Mirrors an mobject about its center.
1227
1366
 
1228
1367
  Examples
@@ -1239,49 +1378,83 @@ class Mobject:
1239
1378
  self.add(s2)
1240
1379
 
1241
1380
  """
1242
- return self.rotate(TAU / 2, axis, **kwargs)
1381
+ return self.rotate(
1382
+ TAU / 2, axis, about_point=about_point, about_edge=about_edge
1383
+ )
1243
1384
 
1244
- def stretch(self, factor, dim, **kwargs):
1245
- def func(points):
1385
+ def stretch(
1386
+ self,
1387
+ factor: float,
1388
+ dim: int,
1389
+ *,
1390
+ about_point: Point3DLike | None = None,
1391
+ about_edge: Vector3DLike | None = None,
1392
+ ) -> Self:
1393
+ def func(points: Point3D_Array) -> Point3D_Array:
1246
1394
  points[:, dim] *= factor
1247
1395
  return points
1248
1396
 
1249
- self.apply_points_function_about_point(func, **kwargs)
1397
+ self.apply_points_function_about_point(func, about_point, about_edge)
1250
1398
  return self
1251
1399
 
1252
- def apply_function(self, function, **kwargs):
1400
+ def apply_function(
1401
+ self,
1402
+ function: MappingFunction,
1403
+ *,
1404
+ about_point: Point3DLike | None = None,
1405
+ about_edge: Vector3DLike | None = None,
1406
+ ) -> Self:
1253
1407
  # Default to applying matrix about the origin, not mobjects center
1254
- if len(kwargs) == 0:
1255
- kwargs["about_point"] = ORIGIN
1408
+ if about_point is None and about_edge is None:
1409
+ about_point = ORIGIN
1410
+
1411
+ def multi_mapping_function(points: Point3D_Array) -> Point3D_Array:
1412
+ result: Point3D_Array = np.apply_along_axis(function, 1, points)
1413
+ return result
1414
+
1256
1415
  self.apply_points_function_about_point(
1257
- lambda points: np.apply_along_axis(function, 1, points), **kwargs
1416
+ multi_mapping_function,
1417
+ about_point,
1418
+ about_edge,
1258
1419
  )
1259
1420
  return self
1260
1421
 
1261
- def apply_function_to_position(self, function):
1422
+ def apply_function_to_position(self, function: MappingFunction) -> Self:
1262
1423
  self.move_to(function(self.get_center()))
1263
1424
  return self
1264
1425
 
1265
- def apply_function_to_submobject_positions(self, function):
1426
+ def apply_function_to_submobject_positions(self, function: MappingFunction) -> Self:
1266
1427
  for submob in self.submobjects:
1267
1428
  submob.apply_function_to_position(function)
1268
1429
  return self
1269
1430
 
1270
- def apply_matrix(self, matrix, **kwargs):
1431
+ def apply_matrix(
1432
+ self,
1433
+ matrix: MatrixMN,
1434
+ *,
1435
+ about_point: Point3DLike | None = None,
1436
+ about_edge: Vector3DLike | None = None,
1437
+ ) -> Self:
1271
1438
  # Default to applying matrix about the origin, not mobjects center
1272
- if ("about_point" not in kwargs) and ("about_edge" not in kwargs):
1273
- kwargs["about_point"] = ORIGIN
1439
+ if about_point is None and about_edge is None:
1440
+ about_point = ORIGIN
1274
1441
  full_matrix = np.identity(self.dim)
1275
1442
  matrix = np.array(matrix)
1276
1443
  full_matrix[: matrix.shape[0], : matrix.shape[1]] = matrix
1277
1444
  self.apply_points_function_about_point(
1278
- lambda points: np.dot(points, full_matrix.T), **kwargs
1445
+ lambda points: np.dot(points, full_matrix.T), about_point, about_edge
1279
1446
  )
1280
1447
  return self
1281
1448
 
1282
- def apply_complex_function(self, function, **kwargs):
1449
+ def apply_complex_function(
1450
+ self,
1451
+ function: Callable[[complex], complex],
1452
+ *,
1453
+ about_point: Point3DLike | None = None,
1454
+ about_edge: Vector3DLike | None = None,
1455
+ ) -> Self:
1283
1456
  """Applies a complex function to a :class:`Mobject`.
1284
- The x and y coordinates correspond to the real and imaginary parts respectively.
1457
+ The x and y Point3Ds correspond to the real and imaginary parts respectively.
1285
1458
 
1286
1459
  Example
1287
1460
  -------
@@ -1311,26 +1484,16 @@ class Mobject:
1311
1484
  xy_complex = function(complex(x, y))
1312
1485
  return [xy_complex.real, xy_complex.imag, z]
1313
1486
 
1314
- return self.apply_function(R3_func)
1315
-
1316
- def wag(self, direction=RIGHT, axis=DOWN, wag_factor=1.0):
1317
- for mob in self.family_members_with_points():
1318
- alphas = np.dot(mob.points, np.transpose(axis))
1319
- alphas -= min(alphas)
1320
- alphas /= max(alphas)
1321
- alphas = alphas**wag_factor
1322
- mob.points += np.dot(
1323
- alphas.reshape((len(alphas), 1)),
1324
- np.array(direction).reshape((1, mob.dim)),
1325
- )
1326
- return self
1487
+ return self.apply_function(
1488
+ R3_func, about_point=about_point, about_edge=about_edge
1489
+ )
1327
1490
 
1328
- def reverse_points(self):
1491
+ def reverse_points(self) -> Self:
1329
1492
  for mob in self.family_members_with_points():
1330
1493
  mob.apply_over_attr_arrays(lambda arr: np.array(list(reversed(arr))))
1331
1494
  return self
1332
1495
 
1333
- def repeat(self, count: int):
1496
+ def repeat(self, count: int) -> Self:
1334
1497
  """This can make transition animations nicer"""
1335
1498
 
1336
1499
  def repeat_array(array):
@@ -1344,16 +1507,19 @@ class Mobject:
1344
1507
  # Note, much of these are now redundant with default behavior of
1345
1508
  # above methods
1346
1509
 
1510
+ # TODO: name is inconsistent with OpenGLMobject.apply_points_function()
1347
1511
  def apply_points_function_about_point(
1348
1512
  self,
1349
- func,
1350
- about_point=None,
1351
- about_edge=None,
1352
- ):
1513
+ func: MultiMappingFunction,
1514
+ about_point: Point3DLike | None = None,
1515
+ about_edge: Vector3DLike | None = None,
1516
+ ) -> Self:
1353
1517
  if about_point is None:
1354
1518
  if about_edge is None:
1355
1519
  about_edge = ORIGIN
1356
1520
  about_point = self.get_critical_point(about_edge)
1521
+ # Make a copy to prevent mutation of the original array if about_point is a view
1522
+ about_point = np.array(about_point, copy=True)
1357
1523
  for mob in self.family_members_with_points():
1358
1524
  mob.points -= about_point
1359
1525
  mob.points = func(mob.points)
@@ -1366,11 +1532,20 @@ class Mobject:
1366
1532
 
1367
1533
  # Positioning methods
1368
1534
 
1369
- def center(self):
1535
+ def center(self) -> Self:
1536
+ """Moves the center of the mobject to the center of the scene.
1537
+
1538
+ Returns
1539
+ -------
1540
+ :class:`.Mobject`
1541
+ The centered mobject.
1542
+ """
1370
1543
  self.shift(-self.get_center())
1371
1544
  return self
1372
1545
 
1373
- def align_on_border(self, direction, buff=DEFAULT_MOBJECT_TO_EDGE_BUFFER):
1546
+ def align_on_border(
1547
+ self, direction: Vector3DLike, buff: float = DEFAULT_MOBJECT_TO_EDGE_BUFFER
1548
+ ) -> Self:
1374
1549
  """Direction just needs to be a vector pointing towards side or
1375
1550
  corner in the 2d plane.
1376
1551
  """
@@ -1385,23 +1560,75 @@ class Mobject:
1385
1560
  self.shift(shift_val)
1386
1561
  return self
1387
1562
 
1388
- def to_corner(self, corner=LEFT + DOWN, buff=DEFAULT_MOBJECT_TO_EDGE_BUFFER):
1563
+ def to_corner(
1564
+ self, corner: Vector3DLike = DL, buff: float = DEFAULT_MOBJECT_TO_EDGE_BUFFER
1565
+ ) -> Self:
1566
+ """Moves this :class:`~.Mobject` to the given corner of the screen.
1567
+
1568
+ Returns
1569
+ -------
1570
+ :class:`.Mobject`
1571
+ The newly positioned mobject.
1572
+
1573
+ Examples
1574
+ --------
1575
+
1576
+ .. manim:: ToCornerExample
1577
+ :save_last_frame:
1578
+
1579
+ class ToCornerExample(Scene):
1580
+ def construct(self):
1581
+ c = Circle()
1582
+ c.to_corner(UR)
1583
+ t = Tex("To the corner!")
1584
+ t2 = MathTex("x^3").shift(DOWN)
1585
+ self.add(c,t,t2)
1586
+ t.to_corner(DL, buff=0)
1587
+ t2.to_corner(UL, buff=1.5)
1588
+ """
1389
1589
  return self.align_on_border(corner, buff)
1390
1590
 
1391
- def to_edge(self, edge=LEFT, buff=DEFAULT_MOBJECT_TO_EDGE_BUFFER):
1591
+ def to_edge(
1592
+ self, edge: Vector3DLike = LEFT, buff: float = DEFAULT_MOBJECT_TO_EDGE_BUFFER
1593
+ ) -> Self:
1594
+ """Moves this :class:`~.Mobject` to the given edge of the screen,
1595
+ without affecting its position in the other dimension.
1596
+
1597
+ Returns
1598
+ -------
1599
+ :class:`.Mobject`
1600
+ The newly positioned mobject.
1601
+
1602
+ Examples
1603
+ --------
1604
+
1605
+ .. manim:: ToEdgeExample
1606
+ :save_last_frame:
1607
+
1608
+ class ToEdgeExample(Scene):
1609
+ def construct(self):
1610
+ tex_top = Tex("I am at the top!")
1611
+ tex_top.to_edge(UP)
1612
+ tex_side = Tex("I am moving to the side!")
1613
+ c = Circle().shift(2*DOWN)
1614
+ self.add(tex_top, tex_side, c)
1615
+ tex_side.to_edge(LEFT)
1616
+ c.to_edge(RIGHT, buff=0)
1617
+
1618
+ """
1392
1619
  return self.align_on_border(edge, buff)
1393
1620
 
1394
1621
  def next_to(
1395
1622
  self,
1396
- mobject_or_point,
1397
- direction=RIGHT,
1398
- buff=DEFAULT_MOBJECT_TO_MOBJECT_BUFFER,
1399
- aligned_edge=ORIGIN,
1400
- submobject_to_align=None,
1401
- index_of_submobject_to_align=None,
1402
- coor_mask=np.array([1, 1, 1]),
1403
- ):
1404
- """Move this :class:`~.Mobject` next to another's :class:`~.Mobject` or coordinate.
1623
+ mobject_or_point: Mobject | Point3DLike,
1624
+ direction: Vector3DLike = RIGHT,
1625
+ buff: float = DEFAULT_MOBJECT_TO_MOBJECT_BUFFER,
1626
+ aligned_edge: Vector3DLike = ORIGIN,
1627
+ submobject_to_align: Mobject | None = None,
1628
+ index_of_submobject_to_align: int | None = None,
1629
+ coor_mask: Vector3DLike = np.array([1, 1, 1]),
1630
+ ) -> Self:
1631
+ """Move this :class:`~.Mobject` next to another's :class:`~.Mobject` or Point3D.
1405
1632
 
1406
1633
  Examples
1407
1634
  --------
@@ -1421,13 +1648,18 @@ class Mobject:
1421
1648
  self.add(d, c, s, t)
1422
1649
 
1423
1650
  """
1651
+ np_direction = np.asarray(direction)
1652
+ np_aligned_edge = np.asarray(aligned_edge)
1653
+
1424
1654
  if isinstance(mobject_or_point, Mobject):
1425
1655
  mob = mobject_or_point
1426
1656
  if index_of_submobject_to_align is not None:
1427
1657
  target_aligner = mob[index_of_submobject_to_align]
1428
1658
  else:
1429
1659
  target_aligner = mob
1430
- target_point = target_aligner.get_critical_point(aligned_edge + direction)
1660
+ target_point = target_aligner.get_critical_point(
1661
+ np_aligned_edge + np_direction
1662
+ )
1431
1663
  else:
1432
1664
  target_point = mobject_or_point
1433
1665
  if submobject_to_align is not None:
@@ -1436,11 +1668,11 @@ class Mobject:
1436
1668
  aligner = self[index_of_submobject_to_align]
1437
1669
  else:
1438
1670
  aligner = self
1439
- point_to_align = aligner.get_critical_point(aligned_edge - direction)
1440
- self.shift((target_point - point_to_align + buff * direction) * coor_mask)
1671
+ point_to_align = aligner.get_critical_point(np_aligned_edge - np_direction)
1672
+ self.shift((target_point - point_to_align + buff * np_direction) * coor_mask)
1441
1673
  return self
1442
1674
 
1443
- def shift_onto_screen(self, **kwargs):
1675
+ def shift_onto_screen(self, **kwargs) -> Self:
1444
1676
  space_lengths = [config["frame_x_radius"], config["frame_y_radius"]]
1445
1677
  for vect in UP, DOWN, LEFT, RIGHT:
1446
1678
  dim = np.argmax(np.abs(vect))
@@ -1458,14 +1690,14 @@ class Mobject:
1458
1690
  return True
1459
1691
  if self.get_bottom()[1] > config["frame_y_radius"]:
1460
1692
  return True
1461
- if self.get_top()[1] < -config["frame_y_radius"]:
1462
- return True
1463
- return False
1693
+ return self.get_top()[1] < -config["frame_y_radius"]
1464
1694
 
1465
- def stretch_about_point(self, factor, dim, point):
1695
+ def stretch_about_point(self, factor: float, dim: int, point: Point3DLike) -> Self:
1466
1696
  return self.stretch(factor, dim, about_point=point)
1467
1697
 
1468
- def rescale_to_fit(self, length, dim, stretch=False, **kwargs):
1698
+ def rescale_to_fit(
1699
+ self, length: float, dim: int, stretch: bool = False, **kwargs
1700
+ ) -> Self:
1469
1701
  old_length = self.length_over_dim(dim)
1470
1702
  if old_length == 0:
1471
1703
  return self
@@ -1475,7 +1707,7 @@ class Mobject:
1475
1707
  self.scale(length / old_length, **kwargs)
1476
1708
  return self
1477
1709
 
1478
- def scale_to_fit_width(self, width, **kwargs):
1710
+ def scale_to_fit_width(self, width: float, **kwargs) -> Self:
1479
1711
  """Scales the :class:`~.Mobject` to fit a width while keeping height/depth proportional.
1480
1712
 
1481
1713
  Returns
@@ -1490,18 +1722,17 @@ class Mobject:
1490
1722
  >>> from manim import *
1491
1723
  >>> sq = Square()
1492
1724
  >>> sq.height
1493
- 2.0
1725
+ np.float64(2.0)
1494
1726
  >>> sq.scale_to_fit_width(5)
1495
1727
  Square
1496
1728
  >>> sq.width
1497
- 5.0
1729
+ np.float64(5.0)
1498
1730
  >>> sq.height
1499
- 5.0
1731
+ np.float64(5.0)
1500
1732
  """
1501
-
1502
1733
  return self.rescale_to_fit(width, 0, stretch=False, **kwargs)
1503
1734
 
1504
- def stretch_to_fit_width(self, width, **kwargs):
1735
+ def stretch_to_fit_width(self, width: float, **kwargs) -> Self:
1505
1736
  """Stretches the :class:`~.Mobject` to fit a width, not keeping height/depth proportional.
1506
1737
 
1507
1738
  Returns
@@ -1516,18 +1747,17 @@ class Mobject:
1516
1747
  >>> from manim import *
1517
1748
  >>> sq = Square()
1518
1749
  >>> sq.height
1519
- 2.0
1750
+ np.float64(2.0)
1520
1751
  >>> sq.stretch_to_fit_width(5)
1521
1752
  Square
1522
1753
  >>> sq.width
1523
- 5.0
1754
+ np.float64(5.0)
1524
1755
  >>> sq.height
1525
- 2.0
1756
+ np.float64(2.0)
1526
1757
  """
1527
-
1528
1758
  return self.rescale_to_fit(width, 0, stretch=True, **kwargs)
1529
1759
 
1530
- def scale_to_fit_height(self, height, **kwargs):
1760
+ def scale_to_fit_height(self, height: float, **kwargs) -> Self:
1531
1761
  """Scales the :class:`~.Mobject` to fit a height while keeping width/depth proportional.
1532
1762
 
1533
1763
  Returns
@@ -1542,18 +1772,17 @@ class Mobject:
1542
1772
  >>> from manim import *
1543
1773
  >>> sq = Square()
1544
1774
  >>> sq.width
1545
- 2.0
1775
+ np.float64(2.0)
1546
1776
  >>> sq.scale_to_fit_height(5)
1547
1777
  Square
1548
1778
  >>> sq.height
1549
- 5.0
1779
+ np.float64(5.0)
1550
1780
  >>> sq.width
1551
- 5.0
1781
+ np.float64(5.0)
1552
1782
  """
1553
-
1554
1783
  return self.rescale_to_fit(height, 1, stretch=False, **kwargs)
1555
1784
 
1556
- def stretch_to_fit_height(self, height, **kwargs):
1785
+ def stretch_to_fit_height(self, height: float, **kwargs) -> Self:
1557
1786
  """Stretches the :class:`~.Mobject` to fit a height, not keeping width/depth proportional.
1558
1787
 
1559
1788
  Returns
@@ -1568,47 +1797,44 @@ class Mobject:
1568
1797
  >>> from manim import *
1569
1798
  >>> sq = Square()
1570
1799
  >>> sq.width
1571
- 2.0
1800
+ np.float64(2.0)
1572
1801
  >>> sq.stretch_to_fit_height(5)
1573
1802
  Square
1574
1803
  >>> sq.height
1575
- 5.0
1804
+ np.float64(5.0)
1576
1805
  >>> sq.width
1577
- 2.0
1806
+ np.float64(2.0)
1578
1807
  """
1579
-
1580
1808
  return self.rescale_to_fit(height, 1, stretch=True, **kwargs)
1581
1809
 
1582
- def scale_to_fit_depth(self, depth, **kwargs):
1810
+ def scale_to_fit_depth(self, depth: float, **kwargs) -> Self:
1583
1811
  """Scales the :class:`~.Mobject` to fit a depth while keeping width/height proportional."""
1584
-
1585
1812
  return self.rescale_to_fit(depth, 2, stretch=False, **kwargs)
1586
1813
 
1587
- def stretch_to_fit_depth(self, depth, **kwargs):
1814
+ def stretch_to_fit_depth(self, depth: float, **kwargs) -> Self:
1588
1815
  """Stretches the :class:`~.Mobject` to fit a depth, not keeping width/height proportional."""
1589
-
1590
1816
  return self.rescale_to_fit(depth, 2, stretch=True, **kwargs)
1591
1817
 
1592
- def set_coord(self, value, dim, direction=ORIGIN):
1818
+ def set_coord(self, value, dim: int, direction: Vector3DLike = ORIGIN) -> Self:
1593
1819
  curr = self.get_coord(dim, direction)
1594
1820
  shift_vect = np.zeros(self.dim)
1595
1821
  shift_vect[dim] = value - curr
1596
1822
  self.shift(shift_vect)
1597
1823
  return self
1598
1824
 
1599
- def set_x(self, x, direction=ORIGIN):
1825
+ def set_x(self, x: float, direction: Vector3DLike = ORIGIN) -> Self:
1600
1826
  """Set x value of the center of the :class:`~.Mobject` (``int`` or ``float``)"""
1601
1827
  return self.set_coord(x, 0, direction)
1602
1828
 
1603
- def set_y(self, y, direction=ORIGIN):
1829
+ def set_y(self, y: float, direction: Vector3DLike = ORIGIN) -> Self:
1604
1830
  """Set y value of the center of the :class:`~.Mobject` (``int`` or ``float``)"""
1605
1831
  return self.set_coord(y, 1, direction)
1606
1832
 
1607
- def set_z(self, z, direction=ORIGIN):
1833
+ def set_z(self, z: float, direction: Vector3DLike = ORIGIN) -> Self:
1608
1834
  """Set z value of the center of the :class:`~.Mobject` (``int`` or ``float``)"""
1609
1835
  return self.set_coord(z, 2, direction)
1610
1836
 
1611
- def space_out_submobjects(self, factor=1.5, **kwargs):
1837
+ def space_out_submobjects(self, factor: float = 1.5, **kwargs) -> Self:
1612
1838
  self.scale(factor, **kwargs)
1613
1839
  for submob in self.submobjects:
1614
1840
  submob.scale(1.0 / factor)
@@ -1616,11 +1842,11 @@ class Mobject:
1616
1842
 
1617
1843
  def move_to(
1618
1844
  self,
1619
- point_or_mobject,
1620
- aligned_edge=ORIGIN,
1621
- coor_mask=np.array([1, 1, 1]),
1622
- ):
1623
- """Move center of the :class:`~.Mobject` to certain coordinate."""
1845
+ point_or_mobject: Point3DLike | Mobject,
1846
+ aligned_edge: Vector3DLike = ORIGIN,
1847
+ coor_mask: Vector3DLike = np.array([1, 1, 1]),
1848
+ ) -> Self:
1849
+ """Move center of the :class:`~.Mobject` to certain Point3D."""
1624
1850
  if isinstance(point_or_mobject, Mobject):
1625
1851
  target = point_or_mobject.get_critical_point(aligned_edge)
1626
1852
  else:
@@ -1629,7 +1855,9 @@ class Mobject:
1629
1855
  self.shift((target - point_to_align) * coor_mask)
1630
1856
  return self
1631
1857
 
1632
- def replace(self, mobject, dim_to_match=0, stretch=False):
1858
+ def replace(
1859
+ self, mobject: Mobject, dim_to_match: int = 0, stretch: bool = False
1860
+ ) -> Self:
1633
1861
  if not mobject.get_num_points() and not mobject.submobjects:
1634
1862
  raise Warning("Attempting to replace mobject with no points")
1635
1863
  if stretch:
@@ -1647,21 +1875,25 @@ class Mobject:
1647
1875
  def surround(
1648
1876
  self,
1649
1877
  mobject: Mobject,
1650
- dim_to_match=0,
1651
- stretch=False,
1652
- buff=MED_SMALL_BUFF,
1653
- ):
1878
+ dim_to_match: int = 0,
1879
+ stretch: bool = False,
1880
+ buff: float = MED_SMALL_BUFF,
1881
+ ) -> Self:
1654
1882
  self.replace(mobject, dim_to_match, stretch)
1655
1883
  length = mobject.length_over_dim(dim_to_match)
1656
1884
  self.scale((length + buff) / length)
1657
1885
  return self
1658
1886
 
1659
- def put_start_and_end_on(self, start, end):
1887
+ def put_start_and_end_on(self, start: Point3DLike, end: Point3DLike) -> Self:
1660
1888
  curr_start, curr_end = self.get_start_and_end()
1661
1889
  curr_vect = curr_end - curr_start
1662
1890
  if np.all(curr_vect == 0):
1663
- raise Exception("Cannot position endpoints of closed loop")
1664
- target_vect = np.array(end) - np.array(start)
1891
+ # TODO: this looks broken. It makes self.points a Point3D instead
1892
+ # of a Point3D_Array. However, modifying this breaks some tests
1893
+ # where this is currently expected.
1894
+ self.points = np.array(start)
1895
+ return self
1896
+ target_vect = np.asarray(end) - np.asarray(start)
1665
1897
  axis = (
1666
1898
  normalize(np.cross(curr_vect, target_vect))
1667
1899
  if np.linalg.norm(np.cross(curr_vect, target_vect)) != 0
@@ -1681,8 +1913,8 @@ class Mobject:
1681
1913
 
1682
1914
  # Background rectangle
1683
1915
  def add_background_rectangle(
1684
- self, color: Colors | None = None, opacity: float = 0.75, **kwargs
1685
- ):
1916
+ self, color: ParsableManimColor | None = None, opacity: float = 0.75, **kwargs
1917
+ ) -> Self:
1686
1918
  """Add a BackgroundRectangle as submobject.
1687
1919
 
1688
1920
  The BackgroundRectangle is added behind other submobjects.
@@ -1710,7 +1942,6 @@ class Mobject:
1710
1942
  :class:`~.BackgroundRectangle`
1711
1943
 
1712
1944
  """
1713
-
1714
1945
  # TODO, this does not behave well when the mobject has points,
1715
1946
  # since it gets displayed on top
1716
1947
  from manim.mobject.geometry.shape_matchers import BackgroundRectangle
@@ -1721,19 +1952,21 @@ class Mobject:
1721
1952
  self.add_to_back(self.background_rectangle)
1722
1953
  return self
1723
1954
 
1724
- def add_background_rectangle_to_submobjects(self, **kwargs):
1955
+ def add_background_rectangle_to_submobjects(self, **kwargs) -> Self:
1725
1956
  for submobject in self.submobjects:
1726
1957
  submobject.add_background_rectangle(**kwargs)
1727
1958
  return self
1728
1959
 
1729
- def add_background_rectangle_to_family_members_with_points(self, **kwargs):
1960
+ def add_background_rectangle_to_family_members_with_points(self, **kwargs) -> Self:
1730
1961
  for mob in self.family_members_with_points():
1731
1962
  mob.add_background_rectangle(**kwargs)
1732
1963
  return self
1733
1964
 
1734
1965
  # Color functions
1735
1966
 
1736
- def set_color(self, color: Color = YELLOW_C, family: bool = True):
1967
+ def set_color(
1968
+ self, color: ParsableManimColor = YELLOW_C, family: bool = True
1969
+ ) -> Self:
1737
1970
  """Condition is function which takes in one arguments, (x, y, z).
1738
1971
  Here it just recurses to submobjects, but in subclasses this
1739
1972
  should be further implemented based on the the inner workings
@@ -1742,20 +1975,30 @@ class Mobject:
1742
1975
  if family:
1743
1976
  for submob in self.submobjects:
1744
1977
  submob.set_color(color, family=family)
1745
- self.color = Color(color)
1978
+
1979
+ self.color = ManimColor.parse(color)
1746
1980
  return self
1747
1981
 
1748
- def set_color_by_gradient(self, *colors):
1982
+ def set_color_by_gradient(self, *colors: ParsableManimColor) -> Self:
1983
+ """
1984
+ Parameters
1985
+ ----------
1986
+ colors
1987
+ The colors to use for the gradient. Use like `set_color_by_gradient(RED, BLUE, GREEN)`.
1988
+
1989
+ self.color = ManimColor.parse(color)
1990
+ return self
1991
+ """
1749
1992
  self.set_submobject_colors_by_gradient(*colors)
1750
1993
  return self
1751
1994
 
1752
1995
  def set_colors_by_radial_gradient(
1753
1996
  self,
1754
- center=None,
1755
- radius=1,
1756
- inner_color=WHITE,
1757
- outer_color=BLACK,
1758
- ):
1997
+ center: Point3DLike | None = None,
1998
+ radius: float = 1,
1999
+ inner_color: ParsableManimColor = WHITE,
2000
+ outer_color: ParsableManimColor = BLACK,
2001
+ ) -> Self:
1759
2002
  self.set_submobject_colors_by_radial_gradient(
1760
2003
  center,
1761
2004
  radius,
@@ -1764,7 +2007,7 @@ class Mobject:
1764
2007
  )
1765
2008
  return self
1766
2009
 
1767
- def set_submobject_colors_by_gradient(self, *colors):
2010
+ def set_submobject_colors_by_gradient(self, *colors: Iterable[ParsableManimColor]):
1768
2011
  if len(colors) == 0:
1769
2012
  raise ValueError("Need at least one color")
1770
2013
  elif len(colors) == 1:
@@ -1779,11 +2022,11 @@ class Mobject:
1779
2022
 
1780
2023
  def set_submobject_colors_by_radial_gradient(
1781
2024
  self,
1782
- center=None,
1783
- radius=1,
1784
- inner_color=WHITE,
1785
- outer_color=BLACK,
1786
- ):
2025
+ center: Point3DLike | None = None,
2026
+ radius: float = 1,
2027
+ inner_color: ParsableManimColor = WHITE,
2028
+ outer_color: ParsableManimColor = BLACK,
2029
+ ) -> Self:
1787
2030
  if center is None:
1788
2031
  center = self.get_center()
1789
2032
 
@@ -1795,11 +2038,13 @@ class Mobject:
1795
2038
 
1796
2039
  return self
1797
2040
 
1798
- def to_original_color(self):
2041
+ def to_original_color(self) -> Self:
1799
2042
  self.set_color(self.color)
1800
2043
  return self
1801
2044
 
1802
- def fade_to(self, color, alpha, family=True):
2045
+ def fade_to(
2046
+ self, color: ParsableManimColor, alpha: float, family: bool = True
2047
+ ) -> Self:
1803
2048
  if self.get_num_points() > 0:
1804
2049
  new_color = interpolate_color(self.get_color(), color, alpha)
1805
2050
  self.set_color(new_color, family=False)
@@ -1808,19 +2053,29 @@ class Mobject:
1808
2053
  submob.fade_to(color, alpha)
1809
2054
  return self
1810
2055
 
1811
- def fade(self, darkness=0.5, family=True):
2056
+ def fade(self, darkness: float = 0.5, family: bool = True) -> Self:
1812
2057
  if family:
1813
2058
  for submob in self.submobjects:
1814
2059
  submob.fade(darkness, family)
1815
2060
  return self
1816
2061
 
1817
- def get_color(self):
1818
- """Returns the color of the :class:`~.Mobject`"""
2062
+ def get_color(self) -> ManimColor:
2063
+ """Returns the color of the :class:`~.Mobject`
2064
+
2065
+ Examples
2066
+ --------
2067
+ ::
2068
+
2069
+ >>> from manim import Square, RED
2070
+ >>> Square(color=RED).get_color() == RED
2071
+ True
2072
+
2073
+ """
1819
2074
  return self.color
1820
2075
 
1821
2076
  ##
1822
2077
 
1823
- def save_state(self):
2078
+ def save_state(self) -> Self:
1824
2079
  """Save the current state (position, color & size). Can be restored with :meth:`~.Mobject.restore`."""
1825
2080
  if hasattr(self, "saved_state"):
1826
2081
  # Prevent exponential growth of data
@@ -1829,52 +2084,78 @@ class Mobject:
1829
2084
 
1830
2085
  return self
1831
2086
 
1832
- def restore(self):
2087
+ def restore(self) -> Self:
1833
2088
  """Restores the state that was previously saved with :meth:`~.Mobject.save_state`."""
1834
2089
  if not hasattr(self, "saved_state") or self.save_state is None:
1835
2090
  raise Exception("Trying to restore without having saved")
1836
2091
  self.become(self.saved_state)
1837
2092
  return self
1838
2093
 
1839
- ##
1840
-
1841
- def reduce_across_dimension(self, points_func, reduce_func, dim):
1842
- points = self.get_all_points()
1843
- if points is None or len(points) == 0:
1844
- # Note, this default means things like empty VGroups
1845
- # will appear to have a center at [0, 0, 0]
2094
+ def reduce_across_dimension(self, reduce_func: Callable, dim: int):
2095
+ """Find the min or max value from a dimension across all points in this and submobjects."""
2096
+ assert dim >= 0
2097
+ assert dim <= 2
2098
+ if len(self.submobjects) == 0 and len(self.points) == 0:
2099
+ # If we have no points and no submobjects, return 0 (e.g. center)
1846
2100
  return 0
1847
- values = points_func(points[:, dim])
1848
- return reduce_func(values)
1849
2101
 
1850
- def nonempty_submobjects(self):
2102
+ # If we do not have points (but do have submobjects)
2103
+ # use only the points from those.
2104
+ if len(self.points) == 0: # noqa: SIM108
2105
+ rv = None
2106
+ else:
2107
+ # Otherwise, be sure to include our own points
2108
+ rv = reduce_func(self.points[:, dim])
2109
+ # Recursively ask submobjects (if any) for the biggest/
2110
+ # smallest dimension they have and compare it to the return value.
2111
+ for mobj in self.submobjects:
2112
+ value = mobj.reduce_across_dimension(reduce_func, dim)
2113
+ rv = value if rv is None else reduce_func([value, rv])
2114
+ return rv
2115
+
2116
+ def nonempty_submobjects(self) -> list[Self]:
1851
2117
  return [
1852
2118
  submob
1853
2119
  for submob in self.submobjects
1854
2120
  if len(submob.submobjects) != 0 or len(submob.points) != 0
1855
2121
  ]
1856
2122
 
1857
- def get_merged_array(self, array_attr):
2123
+ def get_merged_array(self, array_attr: str) -> np.ndarray:
2124
+ """Return all of a given attribute from this mobject and all submobjects.
2125
+
2126
+ May contain duplicates; the order is in a depth-first (pre-order)
2127
+ traversal of the submobjects.
2128
+ """
1858
2129
  result = getattr(self, array_attr)
1859
2130
  for submob in self.submobjects:
1860
2131
  result = np.append(result, submob.get_merged_array(array_attr), axis=0)
1861
2132
  return result
1862
2133
 
1863
- def get_all_points(self):
2134
+ def get_all_points(self) -> Point3D_Array:
2135
+ """Return all points from this mobject and all submobjects.
2136
+
2137
+ May contain duplicates; the order is in a depth-first (pre-order)
2138
+ traversal of the submobjects.
2139
+ """
1864
2140
  return self.get_merged_array("points")
1865
2141
 
1866
2142
  # Getters
1867
2143
 
1868
- def get_points_defining_boundary(self):
2144
+ def get_points_defining_boundary(self) -> Point3D_Array:
1869
2145
  return self.get_all_points()
1870
2146
 
1871
- def get_num_points(self):
2147
+ def get_num_points(self) -> int:
1872
2148
  return len(self.points)
1873
2149
 
1874
- def get_extremum_along_dim(self, points=None, dim=0, key=0):
1875
- if points is None:
1876
- points = self.get_points_defining_boundary()
1877
- values = points[:, dim]
2150
+ def get_extremum_along_dim(
2151
+ self, points: Point3DLike_Array | None = None, dim: int = 0, key: int = 0
2152
+ ) -> float:
2153
+ np_points: Point3D_Array = (
2154
+ self.get_points_defining_boundary()
2155
+ if points is None
2156
+ else np.asarray(points)
2157
+ )
2158
+ values = np_points[:, dim]
1878
2159
  if key < 0:
1879
2160
  return np.min(values)
1880
2161
  elif key == 0:
@@ -1882,14 +2163,14 @@ class Mobject:
1882
2163
  else:
1883
2164
  return np.max(values)
1884
2165
 
1885
- def get_critical_point(self, direction):
2166
+ def get_critical_point(self, direction: Vector3DLike) -> Point3D:
1886
2167
  """Picture a box bounding the :class:`~.Mobject`. Such a box has
1887
2168
  9 'critical points': 4 corners, 4 edge center, the
1888
2169
  center. This returns one of them, along the given direction.
1889
2170
 
1890
2171
  ::
1891
2172
 
1892
- sample = Arc(start_angle=PI/7, angle = PI/5)
2173
+ sample = Arc(start_angle=PI / 7, angle=PI / 5)
1893
2174
 
1894
2175
  # These are all equivalent
1895
2176
  max_y_1 = sample.get_top()[1]
@@ -1911,28 +2192,28 @@ class Mobject:
1911
2192
 
1912
2193
  # Pseudonyms for more general get_critical_point method
1913
2194
 
1914
- def get_edge_center(self, direction) -> np.ndarray:
1915
- """Get edge coordinates for certain direction."""
2195
+ def get_edge_center(self, direction: Vector3DLike) -> Point3D:
2196
+ """Get edge Point3Ds for certain direction."""
1916
2197
  return self.get_critical_point(direction)
1917
2198
 
1918
- def get_corner(self, direction) -> np.ndarray:
1919
- """Get corner coordinates for certain direction."""
2199
+ def get_corner(self, direction: Vector3DLike) -> Point3D:
2200
+ """Get corner Point3Ds for certain direction."""
1920
2201
  return self.get_critical_point(direction)
1921
2202
 
1922
- def get_center(self) -> np.ndarray:
1923
- """Get center coordinates"""
2203
+ def get_center(self) -> Point3D:
2204
+ """Get center Point3Ds"""
1924
2205
  return self.get_critical_point(np.zeros(self.dim))
1925
2206
 
1926
- def get_center_of_mass(self):
2207
+ def get_center_of_mass(self) -> Point3D:
1927
2208
  return np.apply_along_axis(np.mean, 0, self.get_all_points())
1928
2209
 
1929
- def get_boundary_point(self, direction):
2210
+ def get_boundary_point(self, direction: Vector3DLike) -> Point3D:
1930
2211
  all_points = self.get_points_defining_boundary()
1931
- index = np.argmax(np.dot(all_points, np.array(direction).T))
2212
+ index = np.argmax(np.dot(all_points, direction))
1932
2213
  return all_points[index]
1933
2214
 
1934
- def get_midpoint(self) -> np.ndarray:
1935
- """Get coordinates of the middle of the path that forms the :class:`~.Mobject`.
2215
+ def get_midpoint(self) -> Point3D:
2216
+ """Get Point3Ds of the middle of the path that forms the :class:`~.Mobject`.
1936
2217
 
1937
2218
  Examples
1938
2219
  --------
@@ -1954,75 +2235,74 @@ class Mobject:
1954
2235
  """
1955
2236
  return self.point_from_proportion(0.5)
1956
2237
 
1957
- def get_top(self) -> np.ndarray:
1958
- """Get top coordinates of a box bounding the :class:`~.Mobject`"""
2238
+ def get_top(self) -> Point3D:
2239
+ """Get top Point3Ds of a box bounding the :class:`~.Mobject`"""
1959
2240
  return self.get_edge_center(UP)
1960
2241
 
1961
- def get_bottom(self) -> np.ndarray:
1962
- """Get bottom coordinates of a box bounding the :class:`~.Mobject`"""
2242
+ def get_bottom(self) -> Point3D:
2243
+ """Get bottom Point3Ds of a box bounding the :class:`~.Mobject`"""
1963
2244
  return self.get_edge_center(DOWN)
1964
2245
 
1965
- def get_right(self) -> np.ndarray:
1966
- """Get right coordinates of a box bounding the :class:`~.Mobject`"""
2246
+ def get_right(self) -> Point3D:
2247
+ """Get right Point3Ds of a box bounding the :class:`~.Mobject`"""
1967
2248
  return self.get_edge_center(RIGHT)
1968
2249
 
1969
- def get_left(self) -> np.ndarray:
1970
- """Get left coordinates of a box bounding the :class:`~.Mobject`"""
2250
+ def get_left(self) -> Point3D:
2251
+ """Get left Point3Ds of a box bounding the :class:`~.Mobject`"""
1971
2252
  return self.get_edge_center(LEFT)
1972
2253
 
1973
- def get_zenith(self) -> np.ndarray:
1974
- """Get zenith coordinates of a box bounding a 3D :class:`~.Mobject`."""
2254
+ def get_zenith(self) -> Point3D:
2255
+ """Get zenith Point3Ds of a box bounding a 3D :class:`~.Mobject`."""
1975
2256
  return self.get_edge_center(OUT)
1976
2257
 
1977
- def get_nadir(self) -> np.ndarray:
1978
- """Get nadir (opposite the zenith) coordinates of a box bounding a 3D :class:`~.Mobject`."""
2258
+ def get_nadir(self) -> Point3D:
2259
+ """Get nadir (opposite the zenith) Point3Ds of a box bounding a 3D :class:`~.Mobject`."""
1979
2260
  return self.get_edge_center(IN)
1980
2261
 
1981
- def length_over_dim(self, dim):
2262
+ def length_over_dim(self, dim: int) -> float:
1982
2263
  """Measure the length of an :class:`~.Mobject` in a certain direction."""
1983
2264
  return self.reduce_across_dimension(
1984
- np.max,
1985
- np.max,
2265
+ max,
1986
2266
  dim,
1987
- ) - self.reduce_across_dimension(np.min, np.min, dim)
2267
+ ) - self.reduce_across_dimension(min, dim)
1988
2268
 
1989
- def get_coord(self, dim, direction=ORIGIN):
2269
+ def get_coord(self, dim: int, direction: Vector3DLike = ORIGIN) -> float:
1990
2270
  """Meant to generalize ``get_x``, ``get_y`` and ``get_z``"""
1991
2271
  return self.get_extremum_along_dim(dim=dim, key=direction[dim])
1992
2272
 
1993
- def get_x(self, direction=ORIGIN) -> np.float64:
1994
- """Returns x coordinate of the center of the :class:`~.Mobject` as ``float``"""
2273
+ def get_x(self, direction: Vector3DLike = ORIGIN) -> float:
2274
+ """Returns x Point3D of the center of the :class:`~.Mobject` as ``float``"""
1995
2275
  return self.get_coord(0, direction)
1996
2276
 
1997
- def get_y(self, direction=ORIGIN) -> np.float64:
1998
- """Returns y coordinate of the center of the :class:`~.Mobject` as ``float``"""
2277
+ def get_y(self, direction: Vector3DLike = ORIGIN) -> float:
2278
+ """Returns y Point3D of the center of the :class:`~.Mobject` as ``float``"""
1999
2279
  return self.get_coord(1, direction)
2000
2280
 
2001
- def get_z(self, direction=ORIGIN) -> np.float64:
2002
- """Returns z coordinate of the center of the :class:`~.Mobject` as ``float``"""
2281
+ def get_z(self, direction: Vector3DLike = ORIGIN) -> float:
2282
+ """Returns z Point3D of the center of the :class:`~.Mobject` as ``float``"""
2003
2283
  return self.get_coord(2, direction)
2004
2284
 
2005
- def get_start(self):
2285
+ def get_start(self) -> Point3D:
2006
2286
  """Returns the point, where the stroke that surrounds the :class:`~.Mobject` starts."""
2007
2287
  self.throw_error_if_no_points()
2008
2288
  return np.array(self.points[0])
2009
2289
 
2010
- def get_end(self):
2290
+ def get_end(self) -> Point3D:
2011
2291
  """Returns the point, where the stroke that surrounds the :class:`~.Mobject` ends."""
2012
2292
  self.throw_error_if_no_points()
2013
2293
  return np.array(self.points[-1])
2014
2294
 
2015
- def get_start_and_end(self):
2295
+ def get_start_and_end(self) -> tuple[Point3D, Point3D]:
2016
2296
  """Returns starting and ending point of a stroke as a ``tuple``."""
2017
2297
  return self.get_start(), self.get_end()
2018
2298
 
2019
- def point_from_proportion(self, alpha):
2299
+ def point_from_proportion(self, alpha: float) -> Point3D:
2020
2300
  raise NotImplementedError("Please override in a child class.")
2021
2301
 
2022
- def proportion_from_point(self, point):
2302
+ def proportion_from_point(self, point: Point3DLike) -> float:
2023
2303
  raise NotImplementedError("Please override in a child class.")
2024
2304
 
2025
- def get_pieces(self, n_pieces):
2305
+ def get_pieces(self, n_pieces: float) -> Group:
2026
2306
  template = self.copy()
2027
2307
  template.submobjects = []
2028
2308
  alphas = np.linspace(0, 1, n_pieces + 1)
@@ -2033,7 +2313,7 @@ class Mobject:
2033
2313
  )
2034
2314
  )
2035
2315
 
2036
- def get_z_index_reference_point(self):
2316
+ def get_z_index_reference_point(self) -> Point3D:
2037
2317
  # TODO, better place to define default z_index_group?
2038
2318
  z_index_group = getattr(self, "z_index_group", self)
2039
2319
  return z_index_group.get_center()
@@ -2048,61 +2328,58 @@ class Mobject:
2048
2328
 
2049
2329
  # Match other mobject properties
2050
2330
 
2051
- def match_color(self, mobject: Mobject):
2331
+ def match_color(self, mobject: Mobject) -> Self:
2052
2332
  """Match the color with the color of another :class:`~.Mobject`."""
2053
2333
  return self.set_color(mobject.get_color())
2054
2334
 
2055
- def match_dim_size(self, mobject: Mobject, dim, **kwargs):
2335
+ def match_dim_size(self, mobject: Mobject, dim: int, **kwargs) -> Self:
2056
2336
  """Match the specified dimension with the dimension of another :class:`~.Mobject`."""
2057
2337
  return self.rescale_to_fit(mobject.length_over_dim(dim), dim, **kwargs)
2058
2338
 
2059
- def match_width(self, mobject: Mobject, **kwargs):
2339
+ def match_width(self, mobject: Mobject, **kwargs) -> Self:
2060
2340
  """Match the width with the width of another :class:`~.Mobject`."""
2061
2341
  return self.match_dim_size(mobject, 0, **kwargs)
2062
2342
 
2063
- def match_height(self, mobject: Mobject, **kwargs):
2343
+ def match_height(self, mobject: Mobject, **kwargs) -> Self:
2064
2344
  """Match the height with the height of another :class:`~.Mobject`."""
2065
2345
  return self.match_dim_size(mobject, 1, **kwargs)
2066
2346
 
2067
- def match_depth(self, mobject: Mobject, **kwargs):
2347
+ def match_depth(self, mobject: Mobject, **kwargs) -> Self:
2068
2348
  """Match the depth with the depth of another :class:`~.Mobject`."""
2069
2349
  return self.match_dim_size(mobject, 2, **kwargs)
2070
2350
 
2071
- def match_coord(self, mobject: Mobject, dim, direction=ORIGIN):
2072
- """Match the coordinates with the coordinates of another :class:`~.Mobject`."""
2351
+ def match_coord(
2352
+ self, mobject: Mobject, dim: int, direction: Vector3DLike = ORIGIN
2353
+ ) -> Self:
2354
+ """Match the Point3Ds with the Point3Ds of another :class:`~.Mobject`."""
2073
2355
  return self.set_coord(
2074
2356
  mobject.get_coord(dim, direction),
2075
2357
  dim=dim,
2076
2358
  direction=direction,
2077
2359
  )
2078
2360
 
2079
- def match_x(self, mobject: Mobject, direction=ORIGIN):
2361
+ def match_x(self, mobject: Mobject, direction=ORIGIN) -> Self:
2080
2362
  """Match x coord. to the x coord. of another :class:`~.Mobject`."""
2081
2363
  return self.match_coord(mobject, 0, direction)
2082
2364
 
2083
- def match_y(self, mobject: Mobject, direction=ORIGIN):
2365
+ def match_y(self, mobject: Mobject, direction=ORIGIN) -> Self:
2084
2366
  """Match y coord. to the x coord. of another :class:`~.Mobject`."""
2085
2367
  return self.match_coord(mobject, 1, direction)
2086
2368
 
2087
- def match_z(self, mobject: Mobject, direction=ORIGIN):
2369
+ def match_z(self, mobject: Mobject, direction=ORIGIN) -> Self:
2088
2370
  """Match z coord. to the x coord. of another :class:`~.Mobject`."""
2089
2371
  return self.match_coord(mobject, 2, direction)
2090
2372
 
2091
2373
  def align_to(
2092
2374
  self,
2093
- mobject_or_point: Mobject | np.ndarray | list,
2094
- direction=ORIGIN,
2095
- alignment_vect=UP,
2096
- ):
2375
+ mobject_or_point: Mobject | Point3DLike,
2376
+ direction: Vector3DLike = ORIGIN,
2377
+ ) -> Self:
2097
2378
  """Aligns mobject to another :class:`~.Mobject` in a certain direction.
2098
2379
 
2099
2380
  Examples:
2100
2381
  mob1.align_to(mob2, UP) moves mob1 vertically so that its
2101
2382
  top edge lines ups with mob2's top edge.
2102
-
2103
- mob1.align_to(mob2, alignment_vect = RIGHT) moves mob1
2104
- horizontally so that it's center is directly above/below
2105
- the center of mob2
2106
2383
  """
2107
2384
  if isinstance(mobject_or_point, Mobject):
2108
2385
  point = mobject_or_point.get_critical_point(direction)
@@ -2126,36 +2403,88 @@ class Mobject:
2126
2403
  def __iter__(self):
2127
2404
  return iter(self.split())
2128
2405
 
2129
- def __len__(self):
2406
+ def __len__(self) -> int:
2130
2407
  return len(self.split())
2131
2408
 
2132
- def get_group_class(self):
2409
+ def get_group_class(self) -> type[Group]:
2133
2410
  return Group
2134
2411
 
2135
2412
  @staticmethod
2136
- def get_mobject_type_class():
2413
+ def get_mobject_type_class() -> type[Mobject]:
2137
2414
  """Return the base class of this mobject type."""
2138
2415
  return Mobject
2139
2416
 
2140
- def split(self):
2417
+ def split(self) -> list[Self]:
2141
2418
  result = [self] if len(self.points) > 0 else []
2142
2419
  return result + self.submobjects
2143
2420
 
2144
- def get_family(self, recurse=True):
2145
- sub_families = list(map(Mobject.get_family, self.submobjects))
2421
+ def get_family(self, recurse: bool = True) -> list[Self]:
2422
+ """Lists all mobjects in the hierarchy (family) of the given mobject,
2423
+ including the mobject itself and all its submobjects recursively.
2424
+
2425
+ Parameters
2426
+ ----------
2427
+ recurse
2428
+ Just for consistency with get_family method in OpenGLMobject.
2429
+
2430
+ Returns
2431
+ -------
2432
+ list[Mobject]
2433
+ A list of mobjects in the family of the given mobject.
2434
+
2435
+ Examples
2436
+ --------
2437
+ ::
2438
+
2439
+ >>> from manim import Square, Rectangle, VGroup, Group, Mobject, VMobject
2440
+ >>> s, r, m, v = Square(), Rectangle(), Mobject(), VMobject()
2441
+ >>> vg = VGroup(s, r)
2442
+ >>> gr = Group(vg, m, v)
2443
+ >>> gr.get_family()
2444
+ [Group, VGroup(Square, Rectangle), Square, Rectangle, Mobject, VMobject]
2445
+
2446
+ See also
2447
+ --------
2448
+ :meth:`~.Mobject.family_members_with_points`, :meth:`~.Mobject.align_data`
2449
+
2450
+ """
2451
+ sub_families = [x.get_family() for x in self.submobjects]
2146
2452
  all_mobjects = [self] + list(it.chain(*sub_families))
2147
2453
  return remove_list_redundancies(all_mobjects)
2148
2454
 
2149
- def family_members_with_points(self):
2455
+ def family_members_with_points(self) -> list[Self]:
2456
+ """Filters the list of family members (generated by :meth:`.get_family`) to include only mobjects with points.
2457
+
2458
+ Returns
2459
+ -------
2460
+ list[Mobject]
2461
+ A list of mobjects that have points.
2462
+
2463
+ Examples
2464
+ --------
2465
+ ::
2466
+
2467
+ >>> from manim import Square, Rectangle, VGroup, Group, Mobject, VMobject
2468
+ >>> s, r, m, v = Square(), Rectangle(), Mobject(), VMobject()
2469
+ >>> vg = VGroup(s, r)
2470
+ >>> gr = Group(vg, m, v)
2471
+ >>> gr.family_members_with_points()
2472
+ [Square, Rectangle]
2473
+
2474
+ See also
2475
+ --------
2476
+ :meth:`~.Mobject.get_family`
2477
+
2478
+ """
2150
2479
  return [m for m in self.get_family() if m.get_num_points() > 0]
2151
2480
 
2152
2481
  def arrange(
2153
2482
  self,
2154
- direction: Sequence[float] = RIGHT,
2155
- buff=DEFAULT_MOBJECT_TO_MOBJECT_BUFFER,
2156
- center=True,
2483
+ direction: Vector3DLike = RIGHT,
2484
+ buff: float = DEFAULT_MOBJECT_TO_MOBJECT_BUFFER,
2485
+ center: bool = True,
2157
2486
  **kwargs,
2158
- ):
2487
+ ) -> Self:
2159
2488
  """Sorts :class:`~.Mobject` next to each other on screen.
2160
2489
 
2161
2490
  Examples
@@ -2184,14 +2513,14 @@ class Mobject:
2184
2513
  rows: int | None = None,
2185
2514
  cols: int | None = None,
2186
2515
  buff: float | tuple[float, float] = MED_SMALL_BUFF,
2187
- cell_alignment: np.ndarray = ORIGIN,
2516
+ cell_alignment: Vector3DLike = ORIGIN,
2188
2517
  row_alignments: str | None = None, # "ucd"
2189
2518
  col_alignments: str | None = None, # "lcr"
2190
2519
  row_heights: Iterable[float | None] | None = None,
2191
2520
  col_widths: Iterable[float | None] | None = None,
2192
2521
  flow_order: str = "rd",
2193
2522
  **kwargs,
2194
- ):
2523
+ ) -> Self:
2195
2524
  """Arrange submobjects in a grid.
2196
2525
 
2197
2526
  Parameters
@@ -2297,15 +2626,15 @@ class Mobject:
2297
2626
 
2298
2627
  # calculate rows cols
2299
2628
  if rows is None and cols is None:
2300
- cols = ceil(np.sqrt(len(mobs)))
2629
+ cols = math.ceil(math.sqrt(len(mobs)))
2301
2630
  # make the grid as close to quadratic as possible.
2302
2631
  # choosing cols first can results in cols>rows.
2303
2632
  # This is favored over rows>cols since in general
2304
2633
  # the sceene is wider than high.
2305
2634
  if rows is None:
2306
- rows = ceil(len(mobs) / cols)
2635
+ rows = math.ceil(len(mobs) / cols)
2307
2636
  if cols is None:
2308
- cols = ceil(len(mobs) / rows)
2637
+ cols = math.ceil(len(mobs) / rows)
2309
2638
  if rows * cols < len(mobs):
2310
2639
  raise ValueError("Too few rows and columns to fit all submobjetcs.")
2311
2640
  # rows and cols are now finally valid.
@@ -2317,10 +2646,10 @@ class Mobject:
2317
2646
  buff_x = buff_y = buff
2318
2647
 
2319
2648
  # Initialize alignments correctly
2320
- def init_alignments(alignments, num, mapping, name, dir):
2649
+ def init_alignments(alignments, num, mapping, name, dir_):
2321
2650
  if alignments is None:
2322
2651
  # Use cell_alignment as fallback
2323
- return [cell_alignment * dir] * num
2652
+ return [cell_alignment * dir_] * num
2324
2653
  if len(alignments) != num:
2325
2654
  raise ValueError(f"{name}_alignments has a mismatching size.")
2326
2655
  alignments = list(alignments)
@@ -2420,24 +2749,28 @@ class Mobject:
2420
2749
  self.move_to(start_pos)
2421
2750
  return self
2422
2751
 
2423
- def sort(self, point_to_num_func=lambda p: p[0], submob_func=None):
2752
+ def sort(
2753
+ self,
2754
+ point_to_num_func: Callable[[Point3DLike], float] = lambda p: p[0],
2755
+ submob_func: Callable[[Mobject], Any] | None = None,
2756
+ ) -> Self:
2424
2757
  """Sorts the list of :attr:`submobjects` by a function defined by ``submob_func``."""
2425
2758
  if submob_func is None:
2426
2759
 
2427
- def submob_func(m):
2760
+ def submob_func(m: Mobject) -> float:
2428
2761
  return point_to_num_func(m.get_center())
2429
2762
 
2430
2763
  self.submobjects.sort(key=submob_func)
2431
2764
  return self
2432
2765
 
2433
- def shuffle(self, recursive=False):
2766
+ def shuffle(self, recursive: bool = False) -> None:
2434
2767
  """Shuffles the list of :attr:`submobjects`."""
2435
2768
  if recursive:
2436
2769
  for submob in self.submobjects:
2437
2770
  submob.shuffle(recursive=True)
2438
2771
  random.shuffle(self.submobjects)
2439
2772
 
2440
- def invert(self, recursive=False):
2773
+ def invert(self, recursive: bool = False) -> None:
2441
2774
  """Inverts the list of :attr:`submobjects`.
2442
2775
 
2443
2776
  Parameters
@@ -2461,10 +2794,10 @@ class Mobject:
2461
2794
  if recursive:
2462
2795
  for submob in self.submobjects:
2463
2796
  submob.invert(recursive=True)
2464
- list.reverse(self.submobjects)
2797
+ self.submobjects.reverse()
2465
2798
 
2466
2799
  # Just here to keep from breaking old scenes.
2467
- def arrange_submobjects(self, *args, **kwargs):
2800
+ def arrange_submobjects(self, *args, **kwargs) -> Self:
2468
2801
  """Arrange the position of :attr:`submobjects` with a small buffer.
2469
2802
 
2470
2803
  Examples
@@ -2485,11 +2818,11 @@ class Mobject:
2485
2818
  """
2486
2819
  return self.arrange(*args, **kwargs)
2487
2820
 
2488
- def sort_submobjects(self, *args, **kwargs):
2821
+ def sort_submobjects(self, *args, **kwargs) -> Self:
2489
2822
  """Sort the :attr:`submobjects`"""
2490
2823
  return self.sort(*args, **kwargs)
2491
2824
 
2492
- def shuffle_submobjects(self, *args, **kwargs):
2825
+ def shuffle_submobjects(self, *args, **kwargs) -> None:
2493
2826
  """Shuffles the order of :attr:`submobjects`
2494
2827
 
2495
2828
  Examples
@@ -2508,12 +2841,12 @@ class Mobject:
2508
2841
  return self.shuffle(*args, **kwargs)
2509
2842
 
2510
2843
  # Alignment
2511
- def align_data(self, mobject: Mobject, skip_point_alignment: bool = False):
2512
- """Aligns the data of this mobject with another mobject.
2844
+ def align_data(self, mobject: Mobject, skip_point_alignment: bool = False) -> None:
2845
+ """Aligns the family structure and data of this mobject with another mobject.
2513
2846
 
2514
2847
  Afterwards, the two mobjects will have the same number of submobjects
2515
- (see :meth:`.align_submobjects`), the same parent structure (see
2516
- :meth:`.null_point_align`). If ``skip_point_alignment`` is false,
2848
+ (see :meth:`.align_submobjects`) and the same parent structure (see
2849
+ :meth:`.null_point_align`). If ``skip_point_alignment`` is ``False``,
2517
2850
  they will also have the same number of points (see :meth:`.align_points`).
2518
2851
 
2519
2852
  Parameters
@@ -2522,7 +2855,32 @@ class Mobject:
2522
2855
  The other mobject this mobject should be aligned to.
2523
2856
  skip_point_alignment
2524
2857
  Controls whether or not the computationally expensive
2525
- point alignment is skipped (default: False).
2858
+ point alignment is skipped (default: ``False``).
2859
+
2860
+
2861
+ .. note::
2862
+
2863
+ This method is primarily used internally by :meth:`.become` and the
2864
+ :class:`~.Transform` animation to ensure that mobjects are structurally
2865
+ compatible before transformation.
2866
+
2867
+ Examples
2868
+ --------
2869
+ ::
2870
+
2871
+ >>> from manim import Rectangle, Line, ORIGIN, RIGHT
2872
+ >>> rect = Rectangle(width=4.0, height=2.0, grid_xstep=1.0, grid_ystep=0.5)
2873
+ >>> line = Line(start=ORIGIN,end=RIGHT)
2874
+ >>> line.align_data(rect)
2875
+ >>> len(line.get_family()) == len(rect.get_family())
2876
+ True
2877
+ >>> line.get_num_points() == rect.get_num_points()
2878
+ True
2879
+
2880
+ See also
2881
+ --------
2882
+ :class:`~.Transform`, :meth:`~.Mobject.become`, :meth:`~.VMobject.align_points`, :meth:`~.Mobject.get_family`
2883
+
2526
2884
  """
2527
2885
  self.null_point_align(mobject)
2528
2886
  self.align_submobjects(mobject)
@@ -2539,7 +2897,7 @@ class Mobject:
2539
2897
  msg = f"get_point_mobject not implemented for {self.__class__.__name__}"
2540
2898
  raise NotImplementedError(msg)
2541
2899
 
2542
- def align_points(self, mobject):
2900
+ def align_points(self, mobject: Mobject) -> Self:
2543
2901
  count1 = self.get_num_points()
2544
2902
  count2 = mobject.get_num_points()
2545
2903
  if count1 < count2:
@@ -2548,10 +2906,10 @@ class Mobject:
2548
2906
  mobject.align_points_with_larger(self)
2549
2907
  return self
2550
2908
 
2551
- def align_points_with_larger(self, larger_mobject):
2909
+ def align_points_with_larger(self, larger_mobject: Mobject):
2552
2910
  raise NotImplementedError("Please override in a child class.")
2553
2911
 
2554
- def align_submobjects(self, mobject):
2912
+ def align_submobjects(self, mobject: Mobject) -> Self:
2555
2913
  mob1 = self
2556
2914
  mob2 = mobject
2557
2915
  n1 = len(mob1.submobjects)
@@ -2576,22 +2934,22 @@ class Mobject:
2576
2934
  m2.push_self_into_submobjects()
2577
2935
  return self
2578
2936
 
2579
- def push_self_into_submobjects(self):
2937
+ def push_self_into_submobjects(self) -> Self:
2580
2938
  copy = self.copy()
2581
2939
  copy.submobjects = []
2582
2940
  self.reset_points()
2583
2941
  self.add(copy)
2584
2942
  return self
2585
2943
 
2586
- def add_n_more_submobjects(self, n):
2944
+ def add_n_more_submobjects(self, n: int) -> Self | None:
2587
2945
  if n == 0:
2588
- return
2946
+ return None
2589
2947
 
2590
2948
  curr = len(self.submobjects)
2591
2949
  if curr == 0:
2592
2950
  # If empty, simply add n point mobjects
2593
2951
  self.submobjects = [self.get_point_mobject() for k in range(n)]
2594
- return
2952
+ return None
2595
2953
 
2596
2954
  target = curr + n
2597
2955
  # TODO, factor this out to utils so as to reuse
@@ -2606,67 +2964,119 @@ class Mobject:
2606
2964
  self.submobjects = new_submobs
2607
2965
  return self
2608
2966
 
2609
- def repeat_submobject(self, submob):
2967
+ def repeat_submobject(self, submob: Mobject) -> Self:
2610
2968
  return submob.copy()
2611
2969
 
2612
- def interpolate(self, mobject1, mobject2, alpha, path_func=straight_path()):
2970
+ def interpolate(
2971
+ self,
2972
+ mobject1: Mobject,
2973
+ mobject2: Mobject,
2974
+ alpha: float,
2975
+ path_func: PathFuncType = straight_path(),
2976
+ ) -> Self:
2613
2977
  """Turns this :class:`~.Mobject` into an interpolation between ``mobject1``
2614
2978
  and ``mobject2``.
2615
2979
 
2980
+ The interpolation is applied to the points and color of the mobject.
2981
+
2982
+ Parameters
2983
+ ----------
2984
+ mobject1
2985
+ The starting Mobject.
2986
+ mobject2
2987
+ The target Mobject.
2988
+ alpha
2989
+ Interpolation factor between 0 (at ``mobject1``) and 1 (at ``mobject2``).
2990
+ path_func
2991
+ The function defining the interpolation path. Defaults to a straight path.
2992
+
2993
+ Returns
2994
+ -------
2995
+ :class:`Mobject`
2996
+ ``self``
2997
+
2998
+
2999
+ .. note::
3000
+
3001
+ - Both mobjects must have the same number of points. If not, this will raise an error.
3002
+ Use :meth:`~.VMobject.align_points` to match point counts beforehand if needed.
3003
+ - This method is used internally by the :class:`~.Transform` animation
3004
+ to interpolate between two mobjects during a transformation.
3005
+
2616
3006
  Examples
2617
3007
  --------
2618
3008
 
2619
- .. manim:: DotInterpolation
3009
+ .. manim:: InterpolateExample
2620
3010
  :save_last_frame:
2621
3011
 
2622
- class DotInterpolation(Scene):
3012
+ class InterpolateExample(Scene):
2623
3013
  def construct(self):
2624
- dotR = Dot(color=DARK_GREY)
2625
- dotR.shift(2 * RIGHT)
2626
- dotL = Dot(color=WHITE)
2627
- dotL.shift(2 * LEFT)
2628
-
2629
- dotMiddle = VMobject().interpolate(dotL, dotR, alpha=0.3)
3014
+ # No need for point alignment:
3015
+ dotL = Dot(color=DARK_GREY).to_edge(LEFT)
3016
+ dotR = Dot(color=YELLOW).scale(10).to_edge(RIGHT)
3017
+ dotMid1 = VMobject().interpolate(dotL, dotR, alpha=0.1)
3018
+ dotMid2 = VMobject().interpolate(dotL, dotR, alpha=0.25)
3019
+ dotMid3 = VMobject().interpolate(dotL, dotR, alpha=0.5)
3020
+ dotMid4 = VMobject().interpolate(dotL, dotR, alpha=0.75)
3021
+ dots = VGroup(dotL, dotR, dotMid1, dotMid2, dotMid3, dotMid4)
3022
+
3023
+ # Needs point alignment:
3024
+ line = Line(ORIGIN, UP).to_edge(LEFT)
3025
+ sq = Square(color=RED, fill_opacity=1, stroke_color=BLUE).to_edge(RIGHT)
3026
+ line.align_points(sq)
3027
+ mid1 = VMobject().interpolate(line, sq, alpha=0.1)
3028
+ mid2 = VMobject().interpolate(line, sq, alpha=0.25)
3029
+ mid3 = VMobject().interpolate(line, sq, alpha=0.5)
3030
+ mid4 = VMobject().interpolate(line, sq, alpha=0.75)
3031
+ linesquares = VGroup(line, sq, mid1, mid2, mid3, mid4)
3032
+
3033
+ self.add(VGroup(dots, linesquares).arrange(DOWN, buff=1))
3034
+ See also
3035
+ --------
3036
+ :class:`~.Transform`, :meth:`~.VMobject.align_points`, :meth:`~.VMobject.interpolate_color`
2630
3037
 
2631
- self.add(dotL, dotR, dotMiddle)
2632
3038
  """
2633
3039
  self.points = path_func(mobject1.points, mobject2.points, alpha)
2634
3040
  self.interpolate_color(mobject1, mobject2, alpha)
2635
3041
  return self
2636
3042
 
2637
- def interpolate_color(self, mobject1, mobject2, alpha):
3043
+ def interpolate_color(self, mobject1: Mobject, mobject2: Mobject, alpha: float):
2638
3044
  raise NotImplementedError("Please override in a child class.")
2639
3045
 
2640
3046
  def become(
2641
3047
  self,
2642
3048
  mobject: Mobject,
2643
- copy_submobjects: bool = True,
2644
3049
  match_height: bool = False,
2645
3050
  match_width: bool = False,
2646
3051
  match_depth: bool = False,
2647
3052
  match_center: bool = False,
2648
3053
  stretch: bool = False,
2649
- ):
3054
+ ) -> Self:
2650
3055
  """Edit points, colors and submobjects to be identical
2651
3056
  to another :class:`~.Mobject`
2652
3057
 
2653
3058
  .. note::
2654
3059
 
2655
3060
  If both match_height and match_width are ``True`` then the transformed :class:`~.Mobject`
2656
- will match the height first and then the width
3061
+ will match the height first and then the width.
2657
3062
 
2658
3063
  Parameters
2659
3064
  ----------
2660
3065
  match_height
2661
- If ``True``, then the transformed :class:`~.Mobject` will match the height of the original
3066
+ Whether or not to preserve the height of the original
3067
+ :class:`~.Mobject`.
2662
3068
  match_width
2663
- If ``True``, then the transformed :class:`~.Mobject` will match the width of the original
3069
+ Whether or not to preserve the width of the original
3070
+ :class:`~.Mobject`.
2664
3071
  match_depth
2665
- If ``True``, then the transformed :class:`~.Mobject` will match the depth of the original
3072
+ Whether or not to preserve the depth of the original
3073
+ :class:`~.Mobject`.
2666
3074
  match_center
2667
- If ``True``, then the transformed :class:`~.Mobject` will match the center of the original
3075
+ Whether or not to preserve the center of the original
3076
+ :class:`~.Mobject`.
2668
3077
  stretch
2669
- If ``True``, then the transformed :class:`~.Mobject` will stretch to fit the proportions of the original
3078
+ Whether or not to stretch the target mobject to match the
3079
+ the proportions of the original :class:`~.Mobject`.
2670
3080
 
2671
3081
  Examples
2672
3082
  --------
@@ -2680,22 +3090,84 @@ class Mobject:
2680
3090
  self.wait(0.5)
2681
3091
  circ.become(square)
2682
3092
  self.wait(0.5)
2683
- """
2684
3093
 
2685
- if stretch:
2686
- mobject.stretch_to_fit_height(self.height)
2687
- mobject.stretch_to_fit_width(self.width)
2688
- mobject.stretch_to_fit_depth(self.depth)
2689
- else:
2690
- if match_height:
2691
- mobject.match_height(self)
2692
- if match_width:
2693
- mobject.match_width(self)
2694
- if match_depth:
2695
- mobject.match_depth(self)
2696
3094
 
2697
- if match_center:
2698
- mobject.move_to(self.get_center())
3095
+ The following examples illustrate how mobject measurements
3096
+ change when using the ``match_...`` and ``stretch`` arguments.
3097
+ We start with a rectangle that is 2 units high and 4 units wide,
3098
+ which we want to turn into a circle of radius 3::
3099
+
3100
+ >>> from manim import Rectangle, Circle
3101
+ >>> import numpy as np
3102
+ >>> rect = Rectangle(height=2, width=4)
3103
+ >>> circ = Circle(radius=3)
3104
+
3105
+ With ``stretch=True``, the target circle is deformed to match
3106
+ the proportions of the rectangle, which results in the target
3107
+ mobject being an ellipse with height 2 and width 4. We can
3108
+ check that the resulting points satisfy the ellipse equation
3109
+ :math:`x^2/a^2 + y^2/b^2 = 1` with :math:`a = 4/2` and :math:`b = 2/2`
3110
+ being the semi-axes::
3111
+
3112
+ >>> result = rect.copy().become(circ, stretch=True)
3113
+ >>> result.height, result.width
3114
+ (np.float64(2.0), np.float64(4.0))
3115
+ >>> ellipse_points = np.array(result.get_anchors())
3116
+ >>> ellipse_eq = np.sum(ellipse_points**2 * [1/4, 1, 0], axis=1)
3117
+ >>> np.allclose(ellipse_eq, 1)
3118
+ True
3119
+
3120
+ With ``match_height=True`` and ``match_width=True`` the circle is
3121
+ scaled such that the height or the width of the rectangle will
3122
+ be preserved, respectively.
3123
+ The points of the resulting mobject satisfy the circle equation
3124
+ :math:`x^2 + y^2 = r^2` for the corresponding radius :math:`r`::
3125
+
3126
+ >>> result = rect.copy().become(circ, match_height=True)
3127
+ >>> result.height, result.width
3128
+ (np.float64(2.0), np.float64(2.0))
3129
+ >>> circle_points = np.array(result.get_anchors())
3130
+ >>> circle_eq = np.sum(circle_points**2, axis=1)
3131
+ >>> np.allclose(circle_eq, 1)
3132
+ True
3133
+ >>> result = rect.copy().become(circ, match_width=True)
3134
+ >>> result.height, result.width
3135
+ (np.float64(4.0), np.float64(4.0))
3136
+ >>> circle_points = np.array(result.get_anchors())
3137
+ >>> circle_eq = np.sum(circle_points**2, axis=1)
3138
+ >>> np.allclose(circle_eq, 2**2)
3139
+ True
3140
+
3141
+ With ``match_center=True``, the resulting mobject is moved such that
3142
+ its center is the same as the center of the original mobject::
3143
+
3144
+ >>> rect = rect.shift(np.array([0, 1, 0]))
3145
+ >>> np.allclose(rect.get_center(), circ.get_center())
3146
+ False
3147
+ >>> result = rect.copy().become(circ, match_center=True)
3148
+ >>> np.allclose(rect.get_center(), result.get_center())
3149
+ True
3150
+
3151
+ See also
3152
+ --------
3153
+ :meth:`~.Mobject.align_data`, :meth:`~.VMobject.interpolate_color`
3154
+ """
3155
+ if stretch or match_height or match_width or match_depth or match_center:
3156
+ mobject = mobject.copy()
3157
+ if stretch:
3158
+ mobject.stretch_to_fit_height(self.height)
3159
+ mobject.stretch_to_fit_width(self.width)
3160
+ mobject.stretch_to_fit_depth(self.depth)
3161
+ else:
3162
+ if match_height:
3163
+ mobject.match_height(self)
3164
+ if match_width:
3165
+ mobject.match_width(self)
3166
+ if match_depth:
3167
+ mobject.match_depth(self)
3168
+
3169
+ if match_center:
3170
+ mobject.move_to(self.get_center())
2699
3171
 
2700
3172
  self.align_data(mobject, skip_point_alignment=True)
2701
3173
  for sm1, sm2 in zip(self.get_family(), mobject.get_family()):
@@ -2703,7 +3175,7 @@ class Mobject:
2703
3175
  sm1.interpolate_color(sm1, sm2, 1)
2704
3176
  return self
2705
3177
 
2706
- def match_points(self, mobject: Mobject, copy_submobjects: bool = True):
3178
+ def match_points(self, mobject: Mobject, copy_submobjects: bool = True) -> Self:
2707
3179
  """Edit points, positions, and submobjects to be identical
2708
3180
  to another :class:`~.Mobject`, while keeping the style unchanged.
2709
3181
 
@@ -2725,7 +3197,7 @@ class Mobject:
2725
3197
  return self
2726
3198
 
2727
3199
  # Errors
2728
- def throw_error_if_no_points(self):
3200
+ def throw_error_if_no_points(self) -> None:
2729
3201
  if self.has_no_points():
2730
3202
  caller_name = sys._getframe(1).f_code.co_name
2731
3203
  raise Exception(
@@ -2737,7 +3209,7 @@ class Mobject:
2737
3209
  self,
2738
3210
  z_index_value: float,
2739
3211
  family: bool = True,
2740
- ) -> VMobject:
3212
+ ) -> Self:
2741
3213
  """Sets the :class:`~.Mobject`'s :attr:`z_index` to the value specified in `z_index_value`.
2742
3214
 
2743
3215
  Parameters
@@ -2776,8 +3248,8 @@ class Mobject:
2776
3248
  self.z_index = z_index_value
2777
3249
  return self
2778
3250
 
2779
- def set_z_index_by_z_coordinate(self):
2780
- """Sets the :class:`~.Mobject`'s z coordinate to the value of :attr:`z_index`.
3251
+ def set_z_index_by_z_Point3D(self) -> Self:
3252
+ """Sets the :class:`~.Mobject`'s z Point3D to the value of :attr:`z_index`.
2781
3253
 
2782
3254
  Returns
2783
3255
  -------
@@ -2799,25 +3271,25 @@ class Group(Mobject, metaclass=ConvertToOpenGL):
2799
3271
  be added to the group.
2800
3272
  """
2801
3273
 
2802
- def __init__(self, *mobjects, **kwargs):
3274
+ def __init__(self, *mobjects, **kwargs) -> None:
2803
3275
  super().__init__(**kwargs)
2804
3276
  self.add(*mobjects)
2805
3277
 
2806
3278
 
2807
3279
  class _AnimationBuilder:
2808
- def __init__(self, mobject):
3280
+ def __init__(self, mobject) -> None:
2809
3281
  self.mobject = mobject
2810
3282
  self.mobject.generate_target()
2811
3283
 
2812
3284
  self.overridden_animation = None
2813
3285
  self.is_chaining = False
2814
- self.methods = []
3286
+ self.methods: list[MethodWithArgs] = []
2815
3287
 
2816
3288
  # Whether animation args can be passed
2817
3289
  self.cannot_pass_args = False
2818
3290
  self.anim_args = {}
2819
3291
 
2820
- def __call__(self, **kwargs):
3292
+ def __call__(self, **kwargs) -> Self:
2821
3293
  if self.cannot_pass_args:
2822
3294
  raise ValueError(
2823
3295
  "Animation arguments must be passed before accessing methods and can only be passed once",
@@ -2828,14 +3300,13 @@ class _AnimationBuilder:
2828
3300
 
2829
3301
  return self
2830
3302
 
2831
- def __getattr__(self, method_name):
3303
+ def __getattr__(self, method_name) -> types.MethodType:
2832
3304
  method = getattr(self.mobject.target, method_name)
2833
3305
  has_overridden_animation = hasattr(method, "_override_animate")
2834
3306
 
2835
3307
  if (self.is_chaining and has_overridden_animation) or self.overridden_animation:
2836
3308
  raise NotImplementedError(
2837
- "Method chaining is currently not supported for "
2838
- "overridden animations",
3309
+ "Method chaining is currently not supported for overridden animations",
2839
3310
  )
2840
3311
 
2841
3312
  def update_target(*method_args, **method_kwargs):
@@ -2847,7 +3318,7 @@ class _AnimationBuilder:
2847
3318
  **method_kwargs,
2848
3319
  )
2849
3320
  else:
2850
- self.methods.append([method, method_args, method_kwargs])
3321
+ self.methods.append(MethodWithArgs(method, method_args, method_kwargs))
2851
3322
  method(*method_args, **method_kwargs)
2852
3323
  return self
2853
3324
 
@@ -2856,13 +3327,12 @@ class _AnimationBuilder:
2856
3327
 
2857
3328
  return update_target
2858
3329
 
2859
- def build(self):
2860
- from ..animation.transform import _MethodAnimation
3330
+ def build(self) -> Animation:
3331
+ from ..animation.transform import ( # is this to prevent circular import?
3332
+ _MethodAnimation,
3333
+ )
2861
3334
 
2862
- if self.overridden_animation:
2863
- anim = self.overridden_animation
2864
- else:
2865
- anim = _MethodAnimation(self.mobject, self.methods)
3335
+ anim = self.overridden_animation or _MethodAnimation(self.mobject, self.methods)
2866
3336
 
2867
3337
  for attr, value in self.anim_args.items():
2868
3338
  setattr(anim, attr, value)
@@ -2870,7 +3340,7 @@ class _AnimationBuilder:
2870
3340
  return anim
2871
3341
 
2872
3342
 
2873
- def override_animate(method):
3343
+ def override_animate(method) -> types.FunctionType:
2874
3344
  r"""Decorator for overriding method animations.
2875
3345
 
2876
3346
  This allows to specify a method (returning an :class:`~.Animation`)
@@ -2879,7 +3349,7 @@ def override_animate(method):
2879
3349
 
2880
3350
  .. seealso::
2881
3351
 
2882
- :attr:`Mobject.animate`
3352
+ :attr:`~.Mobject.animate`
2883
3353
 
2884
3354
  .. note::
2885
3355