manim 0.18.0.post0__py3-none-any.whl → 0.19.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of manim might be problematic. Click here for more details.

Files changed (146) hide show
  1. manim/__init__.py +3 -6
  2. manim/__main__.py +61 -20
  3. manim/_config/__init__.py +6 -3
  4. manim/_config/cli_colors.py +16 -8
  5. manim/_config/default.cfg +1 -3
  6. manim/_config/logger_utils.py +14 -8
  7. manim/_config/utils.py +651 -472
  8. manim/animation/animation.py +152 -5
  9. manim/animation/composition.py +80 -39
  10. manim/animation/creation.py +196 -14
  11. manim/animation/fading.py +5 -9
  12. manim/animation/indication.py +103 -47
  13. manim/animation/movement.py +22 -5
  14. manim/animation/rotation.py +3 -2
  15. manim/animation/specialized.py +4 -6
  16. manim/animation/speedmodifier.py +10 -5
  17. manim/animation/transform.py +4 -5
  18. manim/animation/transform_matching_parts.py +1 -1
  19. manim/animation/updaters/mobject_update_utils.py +17 -14
  20. manim/camera/camera.py +15 -6
  21. manim/cli/__init__.py +17 -0
  22. manim/cli/cfg/group.py +70 -44
  23. manim/cli/checkhealth/checks.py +93 -75
  24. manim/cli/checkhealth/commands.py +14 -5
  25. manim/cli/default_group.py +157 -25
  26. manim/cli/init/commands.py +32 -24
  27. manim/cli/plugins/commands.py +16 -3
  28. manim/cli/render/commands.py +72 -60
  29. manim/cli/render/ease_of_access_options.py +4 -3
  30. manim/cli/render/global_options.py +51 -15
  31. manim/cli/render/output_options.py +6 -5
  32. manim/cli/render/render_options.py +97 -32
  33. manim/constants.py +65 -19
  34. manim/gui/gui.py +2 -0
  35. manim/mobject/frame.py +0 -1
  36. manim/mobject/geometry/arc.py +112 -78
  37. manim/mobject/geometry/boolean_ops.py +32 -25
  38. manim/mobject/geometry/labeled.py +300 -77
  39. manim/mobject/geometry/line.py +132 -64
  40. manim/mobject/geometry/polygram.py +126 -30
  41. manim/mobject/geometry/shape_matchers.py +35 -15
  42. manim/mobject/geometry/tips.py +38 -29
  43. manim/mobject/graph.py +414 -133
  44. manim/mobject/graphing/coordinate_systems.py +126 -64
  45. manim/mobject/graphing/functions.py +25 -15
  46. manim/mobject/graphing/number_line.py +24 -10
  47. manim/mobject/graphing/probability.py +2 -10
  48. manim/mobject/graphing/scale.py +6 -5
  49. manim/mobject/matrix.py +17 -19
  50. manim/mobject/mobject.py +314 -165
  51. manim/mobject/opengl/opengl_compatibility.py +2 -0
  52. manim/mobject/opengl/opengl_geometry.py +30 -9
  53. manim/mobject/opengl/opengl_image_mobject.py +2 -0
  54. manim/mobject/opengl/opengl_mobject.py +509 -343
  55. manim/mobject/opengl/opengl_point_cloud_mobject.py +5 -7
  56. manim/mobject/opengl/opengl_surface.py +3 -2
  57. manim/mobject/opengl/opengl_three_dimensions.py +2 -0
  58. manim/mobject/opengl/opengl_vectorized_mobject.py +46 -79
  59. manim/mobject/svg/brace.py +63 -13
  60. manim/mobject/svg/svg_mobject.py +4 -3
  61. manim/mobject/table.py +11 -13
  62. manim/mobject/text/code_mobject.py +186 -548
  63. manim/mobject/text/numbers.py +9 -7
  64. manim/mobject/text/tex_mobject.py +23 -14
  65. manim/mobject/text/text_mobject.py +70 -24
  66. manim/mobject/three_d/polyhedra.py +98 -1
  67. manim/mobject/three_d/three_d_utils.py +4 -4
  68. manim/mobject/three_d/three_dimensions.py +62 -34
  69. manim/mobject/types/image_mobject.py +42 -24
  70. manim/mobject/types/point_cloud_mobject.py +105 -67
  71. manim/mobject/types/vectorized_mobject.py +496 -228
  72. manim/mobject/value_tracker.py +5 -4
  73. manim/mobject/vector_field.py +5 -5
  74. manim/opengl/__init__.py +3 -3
  75. manim/plugins/__init__.py +14 -1
  76. manim/plugins/plugins_flags.py +14 -8
  77. manim/renderer/cairo_renderer.py +20 -10
  78. manim/renderer/opengl_renderer.py +21 -23
  79. manim/renderer/opengl_renderer_window.py +2 -0
  80. manim/renderer/shader.py +2 -3
  81. manim/renderer/shader_wrapper.py +5 -2
  82. manim/renderer/vectorized_mobject_rendering.py +5 -0
  83. manim/scene/moving_camera_scene.py +23 -0
  84. manim/scene/scene.py +90 -43
  85. manim/scene/scene_file_writer.py +316 -165
  86. manim/scene/section.py +17 -15
  87. manim/scene/three_d_scene.py +13 -21
  88. manim/scene/vector_space_scene.py +22 -9
  89. manim/typing.py +830 -70
  90. manim/utils/bezier.py +1667 -399
  91. manim/utils/caching.py +13 -5
  92. manim/utils/color/AS2700.py +2 -0
  93. manim/utils/color/BS381.py +3 -0
  94. manim/utils/color/DVIPSNAMES.py +96 -0
  95. manim/utils/color/SVGNAMES.py +179 -0
  96. manim/utils/color/X11.py +3 -0
  97. manim/utils/color/XKCD.py +3 -0
  98. manim/utils/color/__init__.py +8 -5
  99. manim/utils/color/core.py +844 -309
  100. manim/utils/color/manim_colors.py +7 -9
  101. manim/utils/commands.py +48 -20
  102. manim/utils/config_ops.py +18 -13
  103. manim/utils/debug.py +8 -7
  104. manim/utils/deprecation.py +90 -40
  105. manim/utils/docbuild/__init__.py +17 -0
  106. manim/utils/docbuild/autoaliasattr_directive.py +234 -0
  107. manim/utils/docbuild/autocolor_directive.py +21 -17
  108. manim/utils/docbuild/manim_directive.py +50 -35
  109. manim/utils/docbuild/module_parsing.py +245 -0
  110. manim/utils/exceptions.py +6 -0
  111. manim/utils/family.py +5 -3
  112. manim/utils/family_ops.py +17 -4
  113. manim/utils/file_ops.py +26 -16
  114. manim/utils/hashing.py +9 -7
  115. manim/utils/images.py +10 -4
  116. manim/utils/ipython_magic.py +14 -8
  117. manim/utils/iterables.py +161 -119
  118. manim/utils/module_ops.py +57 -19
  119. manim/utils/opengl.py +83 -24
  120. manim/utils/parameter_parsing.py +32 -0
  121. manim/utils/paths.py +21 -23
  122. manim/utils/polylabel.py +168 -0
  123. manim/utils/qhull.py +218 -0
  124. manim/utils/rate_functions.py +74 -39
  125. manim/utils/simple_functions.py +24 -15
  126. manim/utils/sounds.py +7 -1
  127. manim/utils/space_ops.py +125 -69
  128. manim/utils/testing/__init__.py +17 -0
  129. manim/utils/testing/_frames_testers.py +13 -8
  130. manim/utils/testing/_show_diff.py +5 -3
  131. manim/utils/testing/_test_class_makers.py +33 -18
  132. manim/utils/testing/frames_comparison.py +27 -19
  133. manim/utils/tex.py +127 -197
  134. manim/utils/tex_file_writing.py +47 -45
  135. manim/utils/tex_templates.py +2 -1
  136. manim/utils/unit.py +6 -5
  137. {manim-0.18.0.post0.dist-info → manim-0.19.0.dist-info}/LICENSE.community +1 -1
  138. {manim-0.18.0.post0.dist-info → manim-0.19.0.dist-info}/METADATA +40 -39
  139. manim-0.19.0.dist-info/RECORD +221 -0
  140. {manim-0.18.0.post0.dist-info → manim-0.19.0.dist-info}/WHEEL +1 -1
  141. manim/cli/new/__init__.py +0 -0
  142. manim/cli/new/group.py +0 -189
  143. manim/plugins/import_plugins.py +0 -43
  144. manim-0.18.0.post0.dist-info/RECORD +0 -217
  145. {manim-0.18.0.post0.dist-info → manim-0.19.0.dist-info}/LICENSE +0 -0
  146. {manim-0.18.0.post0.dist-info → manim-0.19.0.dist-info}/entry_points.txt +0 -0
manim/mobject/mobject.py CHANGED
@@ -14,12 +14,12 @@ import random
14
14
  import sys
15
15
  import types
16
16
  import warnings
17
+ from collections.abc import Iterable
17
18
  from functools import partialmethod, reduce
18
19
  from pathlib import Path
19
- from typing import TYPE_CHECKING, Callable, Iterable, Literal, TypeVar, Union
20
+ from typing import TYPE_CHECKING
20
21
 
21
22
  import numpy as np
22
- from typing_extensions import Self, TypeAlias
23
23
 
24
24
  from manim.mobject.opengl.opengl_compatibility import ConvertToOpenGL
25
25
 
@@ -39,29 +39,29 @@ from ..utils.iterables import list_update, remove_list_redundancies
39
39
  from ..utils.paths import straight_path
40
40
  from ..utils.space_ops import angle_between_vectors, normalize, rotation_matrix
41
41
 
42
- # TODO: Explain array_attrs
42
+ if TYPE_CHECKING:
43
+ from typing import Any, Callable, Literal
43
44
 
44
- TimeBasedUpdater: TypeAlias = Callable[["Mobject", float], None]
45
- NonTimeBasedUpdater: TypeAlias = Callable[["Mobject"], None]
46
- Updater: TypeAlias = Union[NonTimeBasedUpdater, TimeBasedUpdater]
47
- T = TypeVar("T", bound="Mobject")
45
+ from typing_extensions import Self, TypeAlias
48
46
 
49
- if TYPE_CHECKING:
50
47
  from manim.typing import (
51
48
  FunctionOverride,
52
- Image,
53
- ManimFloat,
54
- ManimInt,
55
49
  MappingFunction,
50
+ MultiMappingFunction,
56
51
  PathFuncType,
52
+ PixelArray,
57
53
  Point3D,
58
- Point3D_Array,
59
- Vector,
60
- Vector3,
54
+ Point3DLike,
55
+ Point3DLike_Array,
56
+ Vector3D,
61
57
  )
62
58
 
63
59
  from ..animation.animation import Animation
64
60
 
61
+ TimeBasedUpdater: TypeAlias = Callable[["Mobject", float], object]
62
+ NonTimeBasedUpdater: TypeAlias = Callable[["Mobject"], object]
63
+ Updater: TypeAlias = NonTimeBasedUpdater | TimeBasedUpdater
64
+
65
65
 
66
66
  class Mobject:
67
67
  """Mathematical Object: base class for objects that can be displayed on screen.
@@ -118,6 +118,63 @@ class Mobject:
118
118
  self.generate_points()
119
119
  self.init_colors()
120
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
+
121
178
  @classmethod
122
179
  def animation_override_for(
123
180
  cls,
@@ -238,7 +295,7 @@ class Mobject:
238
295
  cls.__init__ = cls._original__init__
239
296
 
240
297
  @property
241
- def animate(self) -> _AnimationBuilder | T:
298
+ def animate(self) -> _AnimationBuilder | Self:
242
299
  """Used to animate the application of any method of :code:`self`.
243
300
 
244
301
  Any method called on :code:`animate` is converted to an animation of applying
@@ -261,7 +318,9 @@ class Mobject:
261
318
 
262
319
  ::
263
320
 
264
- 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
+ )
265
324
 
266
325
  make use of method chaining.
267
326
 
@@ -351,14 +410,14 @@ class Mobject:
351
410
  """Sets :attr:`points` to be an empty array."""
352
411
  self.points = np.zeros((0, self.dim))
353
412
 
354
- def init_colors(self) -> None:
413
+ def init_colors(self) -> object:
355
414
  """Initializes the colors.
356
415
 
357
416
  Gets called upon creation. This is an empty method that can be implemented by
358
417
  subclasses.
359
418
  """
360
419
 
361
- def generate_points(self) -> None:
420
+ def generate_points(self) -> object:
362
421
  """Initializes :attr:`points` and therefore the shape.
363
422
 
364
423
  Gets called upon creation. This is an empty method that can be implemented by
@@ -416,12 +475,19 @@ class Mobject:
416
475
  >>> len(outer.submobjects)
417
476
  1
418
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
+
419
485
  Adding an object to itself raises an error::
420
486
 
421
487
  >>> outer.add(outer)
422
488
  Traceback (most recent call last):
423
489
  ...
424
- ValueError: Mobject cannot contain self
490
+ ValueError: Cannot add Mobject as a submobject of itself (at index 0).
425
491
 
426
492
  A given mobject cannot be added as a submobject
427
493
  twice to some parent::
@@ -435,12 +501,7 @@ class Mobject:
435
501
  [child]
436
502
 
437
503
  """
438
- for m in mobjects:
439
- if not isinstance(m, Mobject):
440
- raise TypeError("All submobjects must be of type Mobject")
441
- if m is self:
442
- raise ValueError("Mobject cannot contain self")
443
-
504
+ self._assert_valid_submobjects(mobjects)
444
505
  unique_mobjects = remove_list_redundancies(mobjects)
445
506
  if len(mobjects) != len(unique_mobjects):
446
507
  logger.warning(
@@ -466,10 +527,7 @@ class Mobject:
466
527
  mobject
467
528
  The mobject to be inserted.
468
529
  """
469
- if not isinstance(mobject, Mobject):
470
- raise TypeError("All submobjects must be of type Mobject")
471
- if mobject is self:
472
- raise ValueError("Mobject cannot contain self")
530
+ self._assert_valid_submobjects([mobject])
473
531
  self.submobjects.insert(index, mobject)
474
532
 
475
533
  def __add__(self, mobject: Mobject):
@@ -522,13 +580,7 @@ class Mobject:
522
580
  :meth:`add`
523
581
 
524
582
  """
525
- if self in mobjects:
526
- raise ValueError("A mobject shouldn't contain itself")
527
-
528
- for mobject in mobjects:
529
- if not isinstance(mobject, Mobject):
530
- raise TypeError("All submobjects must be of type Mobject")
531
-
583
+ self._assert_valid_submobjects(mobjects)
532
584
  self.remove(*mobjects)
533
585
  # dict.fromkeys() removes duplicates while maintaining order
534
586
  self.submobjects = list(dict.fromkeys(mobjects)) + self.submobjects
@@ -618,7 +670,6 @@ class Mobject:
618
670
  >>> mob.foo
619
671
  0
620
672
  """
621
-
622
673
  for attr, value in kwargs.items():
623
674
  setattr(self, attr, value)
624
675
 
@@ -700,7 +751,6 @@ class Mobject:
700
751
  :meth:`length_over_dim`
701
752
 
702
753
  """
703
-
704
754
  # Get the length across the X dimension
705
755
  return self.length_over_dim(0)
706
756
 
@@ -737,7 +787,6 @@ class Mobject:
737
787
  :meth:`length_over_dim`
738
788
 
739
789
  """
740
-
741
790
  # Get the length across the Y dimension
742
791
  return self.length_over_dim(1)
743
792
 
@@ -758,7 +807,6 @@ class Mobject:
758
807
  :meth:`length_over_dim`
759
808
 
760
809
  """
761
-
762
810
  # Get the length across the Z dimension
763
811
  return self.length_over_dim(2)
764
812
 
@@ -770,14 +818,14 @@ class Mobject:
770
818
  def get_array_attrs(self) -> list[Literal["points"]]:
771
819
  return ["points"]
772
820
 
773
- def apply_over_attr_arrays(self, func: MappingFunction) -> Self:
821
+ def apply_over_attr_arrays(self, func: MultiMappingFunction) -> Self:
774
822
  for attr in self.get_array_attrs():
775
823
  setattr(self, attr, func(getattr(self, attr)))
776
824
  return self
777
825
 
778
826
  # Displaying
779
827
 
780
- def get_image(self, camera=None) -> Image:
828
+ def get_image(self, camera=None) -> PixelArray:
781
829
  if camera is None:
782
830
  from ..camera.camera import Camera
783
831
 
@@ -790,7 +838,8 @@ class Mobject:
790
838
 
791
839
  def save_image(self, name: str | None = None) -> None:
792
840
  """Saves an image of only this :class:`Mobject` at its position to a png
793
- file."""
841
+ file.
842
+ """
794
843
  self.get_image().save(
795
844
  Path(config.get_dir("video_dir")).joinpath((name or str(self)) + ".png"),
796
845
  )
@@ -883,7 +932,7 @@ class Mobject:
883
932
 
884
933
  Returns
885
934
  -------
886
- class:`bool`
935
+ :class:`bool`
887
936
  ``True`` if at least one updater uses the ``dt`` parameter, ``False``
888
937
  otherwise.
889
938
 
@@ -968,11 +1017,11 @@ class Mobject:
968
1017
 
969
1018
  class DtUpdater(Scene):
970
1019
  def construct(self):
971
- line = Square()
1020
+ square = Square()
972
1021
 
973
- #Let the line rotate 90° per second
974
- line.add_updater(lambda mobject, dt: mobject.rotate(dt*90*DEGREES))
975
- self.add(line)
1022
+ #Let the square rotate 90° per second
1023
+ square.add_updater(lambda mobject, dt: mobject.rotate(dt*90*DEGREES))
1024
+ self.add(square)
976
1025
  self.wait(2)
977
1026
 
978
1027
  See also
@@ -981,7 +1030,6 @@ class Mobject:
981
1030
  :meth:`remove_updater`
982
1031
  :class:`~.UpdateFromFunc`
983
1032
  """
984
-
985
1033
  if index is None:
986
1034
  self.updaters.append(update_function)
987
1035
  else:
@@ -1071,7 +1119,6 @@ class Mobject:
1071
1119
  :meth:`clear_updaters`
1072
1120
 
1073
1121
  """
1074
-
1075
1122
  self.clear_updaters()
1076
1123
  for updater in mobject.get_updaters():
1077
1124
  self.add_updater(updater)
@@ -1097,7 +1144,6 @@ class Mobject:
1097
1144
  :meth:`add_updater`
1098
1145
 
1099
1146
  """
1100
-
1101
1147
  self.updating_suspended = True
1102
1148
  if recursive:
1103
1149
  for submob in self.submobjects:
@@ -1154,7 +1200,7 @@ class Mobject:
1154
1200
  for mob in self.family_members_with_points():
1155
1201
  func(mob)
1156
1202
 
1157
- def shift(self, *vectors: Vector3) -> Self:
1203
+ def shift(self, *vectors: Vector3D) -> Self:
1158
1204
  """Shift by the given vectors.
1159
1205
 
1160
1206
  Parameters
@@ -1172,7 +1218,6 @@ class Mobject:
1172
1218
  --------
1173
1219
  :meth:`move_to`
1174
1220
  """
1175
-
1176
1221
  total_vector = reduce(op.add, vectors)
1177
1222
  for mob in self.family_members_with_points():
1178
1223
  mob.points = mob.points.astype("float")
@@ -1226,15 +1271,15 @@ class Mobject:
1226
1271
  )
1227
1272
  return self
1228
1273
 
1229
- def rotate_about_origin(self, angle: float, axis: Vector3 = OUT, axes=[]) -> Self:
1274
+ def rotate_about_origin(self, angle: float, axis: Vector3D = OUT, axes=[]) -> Self:
1230
1275
  """Rotates the :class:`~.Mobject` about the ORIGIN, which is at [0,0,0]."""
1231
1276
  return self.rotate(angle, axis, about_point=ORIGIN)
1232
1277
 
1233
1278
  def rotate(
1234
1279
  self,
1235
1280
  angle: float,
1236
- axis: Vector3 = OUT,
1237
- about_point: Point3D | None = None,
1281
+ axis: Vector3D = OUT,
1282
+ about_point: Point3DLike | None = None,
1238
1283
  **kwargs,
1239
1284
  ) -> Self:
1240
1285
  """Rotates the :class:`~.Mobject` about a certain point."""
@@ -1244,7 +1289,7 @@ class Mobject:
1244
1289
  )
1245
1290
  return self
1246
1291
 
1247
- def flip(self, axis: Vector3 = UP, **kwargs) -> Self:
1292
+ def flip(self, axis: Vector3D = UP, **kwargs) -> Self:
1248
1293
  """Flips/Mirrors an mobject about its center.
1249
1294
 
1250
1295
  Examples
@@ -1264,7 +1309,7 @@ class Mobject:
1264
1309
  return self.rotate(TAU / 2, axis, **kwargs)
1265
1310
 
1266
1311
  def stretch(self, factor: float, dim: int, **kwargs) -> Self:
1267
- def func(points):
1312
+ def func(points: Point3D_Array) -> Point3D_Array:
1268
1313
  points[:, dim] *= factor
1269
1314
  return points
1270
1315
 
@@ -1275,9 +1320,12 @@ class Mobject:
1275
1320
  # Default to applying matrix about the origin, not mobjects center
1276
1321
  if len(kwargs) == 0:
1277
1322
  kwargs["about_point"] = ORIGIN
1278
- self.apply_points_function_about_point(
1279
- lambda points: np.apply_along_axis(function, 1, points), **kwargs
1280
- )
1323
+
1324
+ def multi_mapping_function(points: Point3D_Array) -> Point3D_Array:
1325
+ result: Point3D_Array = np.apply_along_axis(function, 1, points)
1326
+ return result
1327
+
1328
+ self.apply_points_function_about_point(multi_mapping_function, **kwargs)
1281
1329
  return self
1282
1330
 
1283
1331
  def apply_function_to_position(self, function: MappingFunction) -> Self:
@@ -1337,20 +1385,6 @@ class Mobject:
1337
1385
 
1338
1386
  return self.apply_function(R3_func)
1339
1387
 
1340
- def wag(
1341
- self, direction: Vector3 = RIGHT, axis: Vector3 = DOWN, wag_factor: float = 1.0
1342
- ) -> Self:
1343
- for mob in self.family_members_with_points():
1344
- alphas = np.dot(mob.points, np.transpose(axis))
1345
- alphas -= min(alphas)
1346
- alphas /= max(alphas)
1347
- alphas = alphas**wag_factor
1348
- mob.points += np.dot(
1349
- alphas.reshape((len(alphas), 1)),
1350
- np.array(direction).reshape((1, mob.dim)),
1351
- )
1352
- return self
1353
-
1354
1388
  def reverse_points(self) -> Self:
1355
1389
  for mob in self.family_members_with_points():
1356
1390
  mob.apply_over_attr_arrays(lambda arr: np.array(list(reversed(arr))))
@@ -1370,11 +1404,12 @@ class Mobject:
1370
1404
  # Note, much of these are now redundant with default behavior of
1371
1405
  # above methods
1372
1406
 
1407
+ # TODO: name is inconsistent with OpenGLMobject.apply_points_function()
1373
1408
  def apply_points_function_about_point(
1374
1409
  self,
1375
- func: MappingFunction,
1376
- about_point: Point3D = None,
1377
- about_edge=None,
1410
+ func: MultiMappingFunction,
1411
+ about_point: Point3DLike | None = None,
1412
+ about_edge: Vector3D | None = None,
1378
1413
  ) -> Self:
1379
1414
  if about_point is None:
1380
1415
  if about_edge is None:
@@ -1404,7 +1439,7 @@ class Mobject:
1404
1439
  return self
1405
1440
 
1406
1441
  def align_on_border(
1407
- self, direction: Vector3, buff: float = DEFAULT_MOBJECT_TO_EDGE_BUFFER
1442
+ self, direction: Vector3D, buff: float = DEFAULT_MOBJECT_TO_EDGE_BUFFER
1408
1443
  ) -> Self:
1409
1444
  """Direction just needs to be a vector pointing towards side or
1410
1445
  corner in the 2d plane.
@@ -1421,24 +1456,72 @@ class Mobject:
1421
1456
  return self
1422
1457
 
1423
1458
  def to_corner(
1424
- self, corner: Vector3 = DL, buff: float = DEFAULT_MOBJECT_TO_EDGE_BUFFER
1459
+ self, corner: Vector3D = DL, buff: float = DEFAULT_MOBJECT_TO_EDGE_BUFFER
1425
1460
  ) -> Self:
1461
+ """Moves this :class:`~.Mobject` to the given corner of the screen.
1462
+
1463
+ Returns
1464
+ -------
1465
+ :class:`.Mobject`
1466
+ The newly positioned mobject.
1467
+
1468
+ Examples
1469
+ --------
1470
+
1471
+ .. manim:: ToCornerExample
1472
+ :save_last_frame:
1473
+
1474
+ class ToCornerExample(Scene):
1475
+ def construct(self):
1476
+ c = Circle()
1477
+ c.to_corner(UR)
1478
+ t = Tex("To the corner!")
1479
+ t2 = MathTex("x^3").shift(DOWN)
1480
+ self.add(c,t,t2)
1481
+ t.to_corner(DL, buff=0)
1482
+ t2.to_corner(UL, buff=1.5)
1483
+ """
1426
1484
  return self.align_on_border(corner, buff)
1427
1485
 
1428
1486
  def to_edge(
1429
- self, edge: Vector3 = LEFT, buff: float = DEFAULT_MOBJECT_TO_EDGE_BUFFER
1487
+ self, edge: Vector3D = LEFT, buff: float = DEFAULT_MOBJECT_TO_EDGE_BUFFER
1430
1488
  ) -> Self:
1489
+ """Moves this :class:`~.Mobject` to the given edge of the screen,
1490
+ without affecting its position in the other dimension.
1491
+
1492
+ Returns
1493
+ -------
1494
+ :class:`.Mobject`
1495
+ The newly positioned mobject.
1496
+
1497
+ Examples
1498
+ --------
1499
+
1500
+ .. manim:: ToEdgeExample
1501
+ :save_last_frame:
1502
+
1503
+ class ToEdgeExample(Scene):
1504
+ def construct(self):
1505
+ tex_top = Tex("I am at the top!")
1506
+ tex_top.to_edge(UP)
1507
+ tex_side = Tex("I am moving to the side!")
1508
+ c = Circle().shift(2*DOWN)
1509
+ self.add(tex_top, tex_side, c)
1510
+ tex_side.to_edge(LEFT)
1511
+ c.to_edge(RIGHT, buff=0)
1512
+
1513
+ """
1431
1514
  return self.align_on_border(edge, buff)
1432
1515
 
1433
1516
  def next_to(
1434
1517
  self,
1435
- mobject_or_point: Mobject | Point3D,
1436
- direction: Vector3 = RIGHT,
1518
+ mobject_or_point: Mobject | Point3DLike,
1519
+ direction: Vector3D = RIGHT,
1437
1520
  buff: float = DEFAULT_MOBJECT_TO_MOBJECT_BUFFER,
1438
- aligned_edge: Vector3 = ORIGIN,
1521
+ aligned_edge: Vector3D = ORIGIN,
1439
1522
  submobject_to_align: Mobject | None = None,
1440
1523
  index_of_submobject_to_align: int | None = None,
1441
- coor_mask: Vector3 = np.array([1, 1, 1]),
1524
+ coor_mask: Vector3D = np.array([1, 1, 1]),
1442
1525
  ) -> Self:
1443
1526
  """Move this :class:`~.Mobject` next to another's :class:`~.Mobject` or Point3D.
1444
1527
 
@@ -1497,11 +1580,9 @@ class Mobject:
1497
1580
  return True
1498
1581
  if self.get_bottom()[1] > config["frame_y_radius"]:
1499
1582
  return True
1500
- if self.get_top()[1] < -config["frame_y_radius"]:
1501
- return True
1502
- return False
1583
+ return self.get_top()[1] < -config["frame_y_radius"]
1503
1584
 
1504
- def stretch_about_point(self, factor: float, dim: int, point: Point3D) -> Self:
1585
+ def stretch_about_point(self, factor: float, dim: int, point: Point3DLike) -> Self:
1505
1586
  return self.stretch(factor, dim, about_point=point)
1506
1587
 
1507
1588
  def rescale_to_fit(
@@ -1531,15 +1612,14 @@ class Mobject:
1531
1612
  >>> from manim import *
1532
1613
  >>> sq = Square()
1533
1614
  >>> sq.height
1534
- 2.0
1615
+ np.float64(2.0)
1535
1616
  >>> sq.scale_to_fit_width(5)
1536
1617
  Square
1537
1618
  >>> sq.width
1538
- 5.0
1619
+ np.float64(5.0)
1539
1620
  >>> sq.height
1540
- 5.0
1621
+ np.float64(5.0)
1541
1622
  """
1542
-
1543
1623
  return self.rescale_to_fit(width, 0, stretch=False, **kwargs)
1544
1624
 
1545
1625
  def stretch_to_fit_width(self, width: float, **kwargs) -> Self:
@@ -1557,15 +1637,14 @@ class Mobject:
1557
1637
  >>> from manim import *
1558
1638
  >>> sq = Square()
1559
1639
  >>> sq.height
1560
- 2.0
1640
+ np.float64(2.0)
1561
1641
  >>> sq.stretch_to_fit_width(5)
1562
1642
  Square
1563
1643
  >>> sq.width
1564
- 5.0
1644
+ np.float64(5.0)
1565
1645
  >>> sq.height
1566
- 2.0
1646
+ np.float64(2.0)
1567
1647
  """
1568
-
1569
1648
  return self.rescale_to_fit(width, 0, stretch=True, **kwargs)
1570
1649
 
1571
1650
  def scale_to_fit_height(self, height: float, **kwargs) -> Self:
@@ -1583,15 +1662,14 @@ class Mobject:
1583
1662
  >>> from manim import *
1584
1663
  >>> sq = Square()
1585
1664
  >>> sq.width
1586
- 2.0
1665
+ np.float64(2.0)
1587
1666
  >>> sq.scale_to_fit_height(5)
1588
1667
  Square
1589
1668
  >>> sq.height
1590
- 5.0
1669
+ np.float64(5.0)
1591
1670
  >>> sq.width
1592
- 5.0
1671
+ np.float64(5.0)
1593
1672
  """
1594
-
1595
1673
  return self.rescale_to_fit(height, 1, stretch=False, **kwargs)
1596
1674
 
1597
1675
  def stretch_to_fit_height(self, height: float, **kwargs) -> Self:
@@ -1609,43 +1687,40 @@ class Mobject:
1609
1687
  >>> from manim import *
1610
1688
  >>> sq = Square()
1611
1689
  >>> sq.width
1612
- 2.0
1690
+ np.float64(2.0)
1613
1691
  >>> sq.stretch_to_fit_height(5)
1614
1692
  Square
1615
1693
  >>> sq.height
1616
- 5.0
1694
+ np.float64(5.0)
1617
1695
  >>> sq.width
1618
- 2.0
1696
+ np.float64(2.0)
1619
1697
  """
1620
-
1621
1698
  return self.rescale_to_fit(height, 1, stretch=True, **kwargs)
1622
1699
 
1623
1700
  def scale_to_fit_depth(self, depth: float, **kwargs) -> Self:
1624
1701
  """Scales the :class:`~.Mobject` to fit a depth while keeping width/height proportional."""
1625
-
1626
1702
  return self.rescale_to_fit(depth, 2, stretch=False, **kwargs)
1627
1703
 
1628
1704
  def stretch_to_fit_depth(self, depth: float, **kwargs) -> Self:
1629
1705
  """Stretches the :class:`~.Mobject` to fit a depth, not keeping width/height proportional."""
1630
-
1631
1706
  return self.rescale_to_fit(depth, 2, stretch=True, **kwargs)
1632
1707
 
1633
- def set_coord(self, value, dim: int, direction: Vector3 = ORIGIN) -> Self:
1708
+ def set_coord(self, value, dim: int, direction: Vector3D = ORIGIN) -> Self:
1634
1709
  curr = self.get_coord(dim, direction)
1635
1710
  shift_vect = np.zeros(self.dim)
1636
1711
  shift_vect[dim] = value - curr
1637
1712
  self.shift(shift_vect)
1638
1713
  return self
1639
1714
 
1640
- def set_x(self, x: float, direction: Vector3 = ORIGIN) -> Self:
1715
+ def set_x(self, x: float, direction: Vector3D = ORIGIN) -> Self:
1641
1716
  """Set x value of the center of the :class:`~.Mobject` (``int`` or ``float``)"""
1642
1717
  return self.set_coord(x, 0, direction)
1643
1718
 
1644
- def set_y(self, y: float, direction: Vector3 = ORIGIN) -> Self:
1719
+ def set_y(self, y: float, direction: Vector3D = ORIGIN) -> Self:
1645
1720
  """Set y value of the center of the :class:`~.Mobject` (``int`` or ``float``)"""
1646
1721
  return self.set_coord(y, 1, direction)
1647
1722
 
1648
- def set_z(self, z: float, direction: Vector3 = ORIGIN) -> Self:
1723
+ def set_z(self, z: float, direction: Vector3D = ORIGIN) -> Self:
1649
1724
  """Set z value of the center of the :class:`~.Mobject` (``int`` or ``float``)"""
1650
1725
  return self.set_coord(z, 2, direction)
1651
1726
 
@@ -1657,9 +1732,9 @@ class Mobject:
1657
1732
 
1658
1733
  def move_to(
1659
1734
  self,
1660
- point_or_mobject: Point3D | Mobject,
1661
- aligned_edge: Vector3 = ORIGIN,
1662
- coor_mask: Vector3 = np.array([1, 1, 1]),
1735
+ point_or_mobject: Point3DLike | Mobject,
1736
+ aligned_edge: Vector3D = ORIGIN,
1737
+ coor_mask: Vector3D = np.array([1, 1, 1]),
1663
1738
  ) -> Self:
1664
1739
  """Move center of the :class:`~.Mobject` to certain Point3D."""
1665
1740
  if isinstance(point_or_mobject, Mobject):
@@ -1699,12 +1774,16 @@ class Mobject:
1699
1774
  self.scale((length + buff) / length)
1700
1775
  return self
1701
1776
 
1702
- def put_start_and_end_on(self, start: Point3D, end: Point3D) -> Self:
1777
+ def put_start_and_end_on(self, start: Point3DLike, end: Point3DLike) -> Self:
1703
1778
  curr_start, curr_end = self.get_start_and_end()
1704
1779
  curr_vect = curr_end - curr_start
1705
1780
  if np.all(curr_vect == 0):
1706
- raise Exception("Cannot position endpoints of closed loop")
1707
- target_vect = np.array(end) - np.array(start)
1781
+ # TODO: this looks broken. It makes self.points a Point3D instead
1782
+ # of a Point3D_Array. However, modifying this breaks some tests
1783
+ # where this is currently expected.
1784
+ self.points = np.array(start)
1785
+ return self
1786
+ target_vect = np.asarray(end) - np.asarray(start)
1708
1787
  axis = (
1709
1788
  normalize(np.cross(curr_vect, target_vect))
1710
1789
  if np.linalg.norm(np.cross(curr_vect, target_vect)) != 0
@@ -1753,7 +1832,6 @@ class Mobject:
1753
1832
  :class:`~.BackgroundRectangle`
1754
1833
 
1755
1834
  """
1756
-
1757
1835
  # TODO, this does not behave well when the mobject has points,
1758
1836
  # since it gets displayed on top
1759
1837
  from manim.mobject.geometry.shape_matchers import BackgroundRectangle
@@ -1806,7 +1884,7 @@ class Mobject:
1806
1884
 
1807
1885
  def set_colors_by_radial_gradient(
1808
1886
  self,
1809
- center: Point3D | None = None,
1887
+ center: Point3DLike | None = None,
1810
1888
  radius: float = 1,
1811
1889
  inner_color: ParsableManimColor = WHITE,
1812
1890
  outer_color: ParsableManimColor = BLACK,
@@ -1834,7 +1912,7 @@ class Mobject:
1834
1912
 
1835
1913
  def set_submobject_colors_by_radial_gradient(
1836
1914
  self,
1837
- center: Point3D | None = None,
1915
+ center: Point3DLike | None = None,
1838
1916
  radius: float = 1,
1839
1917
  inner_color: ParsableManimColor = WHITE,
1840
1918
  outer_color: ParsableManimColor = BLACK,
@@ -1872,7 +1950,17 @@ class Mobject:
1872
1950
  return self
1873
1951
 
1874
1952
  def get_color(self) -> ManimColor:
1875
- """Returns the color of the :class:`~.Mobject`"""
1953
+ """Returns the color of the :class:`~.Mobject`
1954
+
1955
+ Examples
1956
+ --------
1957
+ ::
1958
+
1959
+ >>> from manim import Square, RED
1960
+ >>> Square(color=RED).get_color() == RED
1961
+ True
1962
+
1963
+ """
1876
1964
  return self.color
1877
1965
 
1878
1966
  ##
@@ -1895,14 +1983,15 @@ class Mobject:
1895
1983
 
1896
1984
  def reduce_across_dimension(self, reduce_func: Callable, dim: int):
1897
1985
  """Find the min or max value from a dimension across all points in this and submobjects."""
1898
- assert dim >= 0 and dim <= 2
1986
+ assert dim >= 0
1987
+ assert dim <= 2
1899
1988
  if len(self.submobjects) == 0 and len(self.points) == 0:
1900
1989
  # If we have no points and no submobjects, return 0 (e.g. center)
1901
1990
  return 0
1902
1991
 
1903
1992
  # If we do not have points (but do have submobjects)
1904
1993
  # use only the points from those.
1905
- if len(self.points) == 0:
1994
+ if len(self.points) == 0: # noqa: SIM108
1906
1995
  rv = None
1907
1996
  else:
1908
1997
  # Otherwise, be sure to include our own points
@@ -1911,10 +2000,7 @@ class Mobject:
1911
2000
  # smallest dimension they have and compare it to the return value.
1912
2001
  for mobj in self.submobjects:
1913
2002
  value = mobj.reduce_across_dimension(reduce_func, dim)
1914
- if rv is None:
1915
- rv = value
1916
- else:
1917
- rv = reduce_func([value, rv])
2003
+ rv = value if rv is None else reduce_func([value, rv])
1918
2004
  return rv
1919
2005
 
1920
2006
  def nonempty_submobjects(self) -> list[Self]:
@@ -1952,11 +2038,14 @@ class Mobject:
1952
2038
  return len(self.points)
1953
2039
 
1954
2040
  def get_extremum_along_dim(
1955
- self, points: Point3D_Array | None = None, dim: int = 0, key: int = 0
1956
- ) -> np.ndarray | float:
1957
- if points is None:
1958
- points = self.get_points_defining_boundary()
1959
- values = points[:, dim]
2041
+ self, points: Point3DLike_Array | None = None, dim: int = 0, key: int = 0
2042
+ ) -> float:
2043
+ np_points: Point3D_Array = (
2044
+ self.get_points_defining_boundary()
2045
+ if points is None
2046
+ else np.asarray(points)
2047
+ )
2048
+ values = np_points[:, dim]
1960
2049
  if key < 0:
1961
2050
  return np.min(values)
1962
2051
  elif key == 0:
@@ -1964,14 +2053,14 @@ class Mobject:
1964
2053
  else:
1965
2054
  return np.max(values)
1966
2055
 
1967
- def get_critical_point(self, direction: Vector3) -> Point3D:
2056
+ def get_critical_point(self, direction: Vector3D) -> Point3D:
1968
2057
  """Picture a box bounding the :class:`~.Mobject`. Such a box has
1969
2058
  9 'critical points': 4 corners, 4 edge center, the
1970
2059
  center. This returns one of them, along the given direction.
1971
2060
 
1972
2061
  ::
1973
2062
 
1974
- sample = Arc(start_angle=PI/7, angle = PI/5)
2063
+ sample = Arc(start_angle=PI / 7, angle=PI / 5)
1975
2064
 
1976
2065
  # These are all equivalent
1977
2066
  max_y_1 = sample.get_top()[1]
@@ -1993,11 +2082,11 @@ class Mobject:
1993
2082
 
1994
2083
  # Pseudonyms for more general get_critical_point method
1995
2084
 
1996
- def get_edge_center(self, direction: Vector3) -> Point3D:
2085
+ def get_edge_center(self, direction: Vector3D) -> Point3D:
1997
2086
  """Get edge Point3Ds for certain direction."""
1998
2087
  return self.get_critical_point(direction)
1999
2088
 
2000
- def get_corner(self, direction: Vector3) -> Point3D:
2089
+ def get_corner(self, direction: Vector3D) -> Point3D:
2001
2090
  """Get corner Point3Ds for certain direction."""
2002
2091
  return self.get_critical_point(direction)
2003
2092
 
@@ -2008,7 +2097,7 @@ class Mobject:
2008
2097
  def get_center_of_mass(self) -> Point3D:
2009
2098
  return np.apply_along_axis(np.mean, 0, self.get_all_points())
2010
2099
 
2011
- def get_boundary_point(self, direction: Vector3) -> Point3D:
2100
+ def get_boundary_point(self, direction: Vector3D) -> Point3D:
2012
2101
  all_points = self.get_points_defining_boundary()
2013
2102
  index = np.argmax(np.dot(all_points, np.array(direction).T))
2014
2103
  return all_points[index]
@@ -2067,19 +2156,19 @@ class Mobject:
2067
2156
  dim,
2068
2157
  ) - self.reduce_across_dimension(min, dim)
2069
2158
 
2070
- def get_coord(self, dim: int, direction: Vector3 = ORIGIN):
2159
+ def get_coord(self, dim: int, direction: Vector3D = ORIGIN):
2071
2160
  """Meant to generalize ``get_x``, ``get_y`` and ``get_z``"""
2072
2161
  return self.get_extremum_along_dim(dim=dim, key=direction[dim])
2073
2162
 
2074
- def get_x(self, direction: Vector3 = ORIGIN) -> ManimFloat:
2163
+ def get_x(self, direction: Vector3D = ORIGIN) -> float:
2075
2164
  """Returns x Point3D of the center of the :class:`~.Mobject` as ``float``"""
2076
2165
  return self.get_coord(0, direction)
2077
2166
 
2078
- def get_y(self, direction: Vector3 = ORIGIN) -> ManimFloat:
2167
+ def get_y(self, direction: Vector3D = ORIGIN) -> float:
2079
2168
  """Returns y Point3D of the center of the :class:`~.Mobject` as ``float``"""
2080
2169
  return self.get_coord(1, direction)
2081
2170
 
2082
- def get_z(self, direction: Vector3 = ORIGIN) -> ManimFloat:
2171
+ def get_z(self, direction: Vector3D = ORIGIN) -> float:
2083
2172
  """Returns z Point3D of the center of the :class:`~.Mobject` as ``float``"""
2084
2173
  return self.get_coord(2, direction)
2085
2174
 
@@ -2100,7 +2189,7 @@ class Mobject:
2100
2189
  def point_from_proportion(self, alpha: float) -> Point3D:
2101
2190
  raise NotImplementedError("Please override in a child class.")
2102
2191
 
2103
- def proportion_from_point(self, point: Point3D) -> float:
2192
+ def proportion_from_point(self, point: Point3DLike) -> float:
2104
2193
  raise NotImplementedError("Please override in a child class.")
2105
2194
 
2106
2195
  def get_pieces(self, n_pieces: float) -> Group:
@@ -2150,7 +2239,7 @@ class Mobject:
2150
2239
  return self.match_dim_size(mobject, 2, **kwargs)
2151
2240
 
2152
2241
  def match_coord(
2153
- self, mobject: Mobject, dim: int, direction: Vector3 = ORIGIN
2242
+ self, mobject: Mobject, dim: int, direction: Vector3D = ORIGIN
2154
2243
  ) -> Self:
2155
2244
  """Match the Point3Ds with the Point3Ds of another :class:`~.Mobject`."""
2156
2245
  return self.set_coord(
@@ -2173,8 +2262,8 @@ class Mobject:
2173
2262
 
2174
2263
  def align_to(
2175
2264
  self,
2176
- mobject_or_point: Mobject | Point3D,
2177
- direction: Vector3 = ORIGIN,
2265
+ mobject_or_point: Mobject | Point3DLike,
2266
+ direction: Vector3D = ORIGIN,
2178
2267
  ) -> Self:
2179
2268
  """Aligns mobject to another :class:`~.Mobject` in a certain direction.
2180
2269
 
@@ -2229,7 +2318,7 @@ class Mobject:
2229
2318
 
2230
2319
  def arrange(
2231
2320
  self,
2232
- direction: Vector3 = RIGHT,
2321
+ direction: Vector3D = RIGHT,
2233
2322
  buff: float = DEFAULT_MOBJECT_TO_MOBJECT_BUFFER,
2234
2323
  center: bool = True,
2235
2324
  **kwargs,
@@ -2262,7 +2351,7 @@ class Mobject:
2262
2351
  rows: int | None = None,
2263
2352
  cols: int | None = None,
2264
2353
  buff: float | tuple[float, float] = MED_SMALL_BUFF,
2265
- cell_alignment: Vector3 = ORIGIN,
2354
+ cell_alignment: Vector3D = ORIGIN,
2266
2355
  row_alignments: str | None = None, # "ucd"
2267
2356
  col_alignments: str | None = None, # "lcr"
2268
2357
  row_heights: Iterable[float | None] | None = None,
@@ -2395,10 +2484,10 @@ class Mobject:
2395
2484
  buff_x = buff_y = buff
2396
2485
 
2397
2486
  # Initialize alignments correctly
2398
- def init_alignments(alignments, num, mapping, name, dir):
2487
+ def init_alignments(alignments, num, mapping, name, dir_):
2399
2488
  if alignments is None:
2400
2489
  # Use cell_alignment as fallback
2401
- return [cell_alignment * dir] * num
2490
+ return [cell_alignment * dir_] * num
2402
2491
  if len(alignments) != num:
2403
2492
  raise ValueError(f"{name}_alignments has a mismatching size.")
2404
2493
  alignments = list(alignments)
@@ -2500,13 +2589,13 @@ class Mobject:
2500
2589
 
2501
2590
  def sort(
2502
2591
  self,
2503
- point_to_num_func: Callable[[Point3D], ManimInt] = lambda p: p[0],
2504
- submob_func: Callable[[Mobject], ManimInt] | None = None,
2592
+ point_to_num_func: Callable[[Point3DLike], float] = lambda p: p[0],
2593
+ submob_func: Callable[[Mobject], Any] | None = None,
2505
2594
  ) -> Self:
2506
2595
  """Sorts the list of :attr:`submobjects` by a function defined by ``submob_func``."""
2507
2596
  if submob_func is None:
2508
2597
 
2509
- def submob_func(m: Mobject):
2598
+ def submob_func(m: Mobject) -> float:
2510
2599
  return point_to_num_func(m.get_center())
2511
2600
 
2512
2601
  self.submobjects.sort(key=submob_func)
@@ -2667,13 +2756,13 @@ class Mobject:
2667
2756
 
2668
2757
  def add_n_more_submobjects(self, n: int) -> Self | None:
2669
2758
  if n == 0:
2670
- return
2759
+ return None
2671
2760
 
2672
2761
  curr = len(self.submobjects)
2673
2762
  if curr == 0:
2674
2763
  # If empty, simply add n point mobjects
2675
2764
  self.submobjects = [self.get_point_mobject() for k in range(n)]
2676
- return
2765
+ return None
2677
2766
 
2678
2767
  target = curr + n
2679
2768
  # TODO, factor this out to utils so as to reuse
@@ -2728,7 +2817,6 @@ class Mobject:
2728
2817
  def become(
2729
2818
  self,
2730
2819
  mobject: Mobject,
2731
- copy_submobjects: bool = True,
2732
2820
  match_height: bool = False,
2733
2821
  match_width: bool = False,
2734
2822
  match_depth: bool = False,
@@ -2741,20 +2829,25 @@ class Mobject:
2741
2829
  .. note::
2742
2830
 
2743
2831
  If both match_height and match_width are ``True`` then the transformed :class:`~.Mobject`
2744
- will match the height first and then the width
2832
+ will match the height first and then the width.
2745
2833
 
2746
2834
  Parameters
2747
2835
  ----------
2748
2836
  match_height
2749
- If ``True``, then the transformed :class:`~.Mobject` will match the height of the original
2837
+ Whether or not to preserve the height of the original
2838
+ :class:`~.Mobject`.
2750
2839
  match_width
2751
- If ``True``, then the transformed :class:`~.Mobject` will match the width of the original
2840
+ Whether or not to preserve the width of the original
2841
+ :class:`~.Mobject`.
2752
2842
  match_depth
2753
- If ``True``, then the transformed :class:`~.Mobject` will match the depth of the original
2843
+ Whether or not to preserve the depth of the original
2844
+ :class:`~.Mobject`.
2754
2845
  match_center
2755
- If ``True``, then the transformed :class:`~.Mobject` will match the center of the original
2846
+ Whether or not to preserve the center of the original
2847
+ :class:`~.Mobject`.
2756
2848
  stretch
2757
- If ``True``, then the transformed :class:`~.Mobject` will stretch to fit the proportions of the original
2849
+ Whether or not to stretch the target mobject to match the
2850
+ the proportions of the original :class:`~.Mobject`.
2758
2851
 
2759
2852
  Examples
2760
2853
  --------
@@ -2768,8 +2861,65 @@ class Mobject:
2768
2861
  self.wait(0.5)
2769
2862
  circ.become(square)
2770
2863
  self.wait(0.5)
2771
- """
2772
2864
 
2865
+
2866
+ The following examples illustrate how mobject measurements
2867
+ change when using the ``match_...`` and ``stretch`` arguments.
2868
+ We start with a rectangle that is 2 units high and 4 units wide,
2869
+ which we want to turn into a circle of radius 3::
2870
+
2871
+ >>> from manim import Rectangle, Circle
2872
+ >>> import numpy as np
2873
+ >>> rect = Rectangle(height=2, width=4)
2874
+ >>> circ = Circle(radius=3)
2875
+
2876
+ With ``stretch=True``, the target circle is deformed to match
2877
+ the proportions of the rectangle, which results in the target
2878
+ mobject being an ellipse with height 2 and width 4. We can
2879
+ check that the resulting points satisfy the ellipse equation
2880
+ :math:`x^2/a^2 + y^2/b^2 = 1` with :math:`a = 4/2` and :math:`b = 2/2`
2881
+ being the semi-axes::
2882
+
2883
+ >>> result = rect.copy().become(circ, stretch=True)
2884
+ >>> result.height, result.width
2885
+ (np.float64(2.0), np.float64(4.0))
2886
+ >>> ellipse_points = np.array(result.get_anchors())
2887
+ >>> ellipse_eq = np.sum(ellipse_points**2 * [1/4, 1, 0], axis=1)
2888
+ >>> np.allclose(ellipse_eq, 1)
2889
+ True
2890
+
2891
+ With ``match_height=True`` and ``match_width=True`` the circle is
2892
+ scaled such that the height or the width of the rectangle will
2893
+ be preserved, respectively.
2894
+ The points of the resulting mobject satisfy the circle equation
2895
+ :math:`x^2 + y^2 = r^2` for the corresponding radius :math:`r`::
2896
+
2897
+ >>> result = rect.copy().become(circ, match_height=True)
2898
+ >>> result.height, result.width
2899
+ (np.float64(2.0), np.float64(2.0))
2900
+ >>> circle_points = np.array(result.get_anchors())
2901
+ >>> circle_eq = np.sum(circle_points**2, axis=1)
2902
+ >>> np.allclose(circle_eq, 1)
2903
+ True
2904
+ >>> result = rect.copy().become(circ, match_width=True)
2905
+ >>> result.height, result.width
2906
+ (np.float64(4.0), np.float64(4.0))
2907
+ >>> circle_points = np.array(result.get_anchors())
2908
+ >>> circle_eq = np.sum(circle_points**2, axis=1)
2909
+ >>> np.allclose(circle_eq, 2**2)
2910
+ True
2911
+
2912
+ With ``match_center=True``, the resulting mobject is moved such that
2913
+ its center is the same as the center of the original mobject::
2914
+
2915
+ >>> rect = rect.shift(np.array([0, 1, 0]))
2916
+ >>> np.allclose(rect.get_center(), circ.get_center())
2917
+ False
2918
+ >>> result = rect.copy().become(circ, match_center=True)
2919
+ >>> np.allclose(rect.get_center(), result.get_center())
2920
+ True
2921
+ """
2922
+ mobject = mobject.copy()
2773
2923
  if stretch:
2774
2924
  mobject.stretch_to_fit_height(self.height)
2775
2925
  mobject.stretch_to_fit_width(self.width)
@@ -2825,7 +2975,7 @@ class Mobject:
2825
2975
  self,
2826
2976
  z_index_value: float,
2827
2977
  family: bool = True,
2828
- ) -> T:
2978
+ ) -> Self:
2829
2979
  """Sets the :class:`~.Mobject`'s :attr:`z_index` to the value specified in `z_index_value`.
2830
2980
 
2831
2981
  Parameters
@@ -2922,8 +3072,7 @@ class _AnimationBuilder:
2922
3072
 
2923
3073
  if (self.is_chaining and has_overridden_animation) or self.overridden_animation:
2924
3074
  raise NotImplementedError(
2925
- "Method chaining is currently not supported for "
2926
- "overridden animations",
3075
+ "Method chaining is currently not supported for overridden animations",
2927
3076
  )
2928
3077
 
2929
3078
  def update_target(*method_args, **method_kwargs):