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
@@ -1,22 +1,46 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import copy
4
+ import inspect
4
5
  import itertools as it
5
6
  import random
6
7
  import sys
8
+ import types
9
+ from collections.abc import Callable, Iterable, Iterator, Sequence
7
10
  from functools import partialmethod, wraps
8
11
  from math import ceil
9
- from typing import Iterable, Sequence
12
+ from typing import TYPE_CHECKING, Any, ClassVar, Protocol, TypeVar, cast
10
13
 
11
14
  import moderngl
12
15
  import numpy as np
13
- from colour import Color
16
+ import numpy.typing as npt
17
+ from typing_extensions import (
18
+ Never,
19
+ Self,
20
+ TypeAlias,
21
+ overload,
22
+ override,
23
+ )
14
24
 
15
25
  from manim import config, logger
16
26
  from manim.constants import *
27
+ from manim.data_structures import MethodWithArgs
28
+ from manim.renderer.shader_wrapper import get_colormap_code
29
+ from manim.typing import (
30
+ Point3D,
31
+ Point3D_Array,
32
+ Point3DLike,
33
+ Point3DLike_Array,
34
+ )
17
35
  from manim.utils.bezier import integer_interpolate, interpolate
18
- from manim.utils.color import *
19
- from manim.utils.color import Colors
36
+ from manim.utils.color import (
37
+ WHITE,
38
+ ManimColor,
39
+ ParsableManimColor,
40
+ color_gradient,
41
+ color_to_rgb,
42
+ rgb_to_hex,
43
+ )
20
44
  from manim.utils.config_ops import _Data, _Uniforms
21
45
 
22
46
  # from ..utils.iterables import batch_by_property
@@ -31,17 +55,40 @@ from manim.utils.iterables import (
31
55
  uniq_chain,
32
56
  )
33
57
  from manim.utils.paths import straight_path
34
- from manim.utils.simple_functions import get_parameters
35
58
  from manim.utils.space_ops import (
36
59
  angle_between_vectors,
37
60
  normalize,
38
61
  rotation_matrix_transpose,
39
62
  )
40
63
 
41
-
42
- def affects_shader_info_id(func):
64
+ if TYPE_CHECKING:
65
+ from manim.animation.animation import Animation
66
+ from manim.renderer.shader_wrapper import ShaderWrapper
67
+ from manim.typing import (
68
+ FloatRGB_Array,
69
+ FloatRGBA_Array,
70
+ ManimFloat,
71
+ MappingFunction,
72
+ MatrixMN,
73
+ MultiMappingFunction,
74
+ PathFuncType,
75
+ Vector3D,
76
+ Vector3DLike,
77
+ )
78
+
79
+ _TimeBasedUpdater: TypeAlias = Callable[["OpenGLMobject", float], object]
80
+ _NonTimeBasedUpdater: TypeAlias = Callable[["OpenGLMobject"], object]
81
+ _Updater: TypeAlias = _NonTimeBasedUpdater | _TimeBasedUpdater
82
+
83
+ _T = TypeVar("_T")
84
+ _T_np = TypeVar("_T_np", bound=np.generic)
85
+
86
+
87
+ def affects_shader_info_id(
88
+ func: Callable[[OpenGLMobject], OpenGLMobject],
89
+ ) -> Callable[[OpenGLMobject], OpenGLMobject]:
43
90
  @wraps(func)
44
- def wrapper(self):
91
+ def wrapper(self: OpenGLMobject) -> OpenGLMobject:
45
92
  for mob in self.get_family():
46
93
  func(mob)
47
94
  mob.refresh_shader_wrapper_id()
@@ -50,6 +97,14 @@ def affects_shader_info_id(func):
50
97
  return wrapper
51
98
 
52
99
 
100
+ __all__ = ["OpenGLMobject", "OpenGLGroup", "OpenGLPoint", "_AnimationBuilder"]
101
+
102
+
103
+ _ShaderDType: TypeAlias = np.void
104
+ """The dtype for NumPy arrays representing shader data. It's a structured dtype with signature `(point, np.float32, (3,))`."""
105
+ _ShaderData: TypeAlias = npt.NDArray[_ShaderDType]
106
+
107
+
53
108
  class OpenGLMobject:
54
109
  """Mathematical Object: base class for objects that can be displayed on screen.
55
110
 
@@ -66,79 +121,85 @@ class OpenGLMobject:
66
121
 
67
122
  """
68
123
 
69
- shader_dtype = [
124
+ _original__init__: ClassVar[Callable[..., None]]
125
+
126
+ shader_dtype: ClassVar[Sequence[tuple[str, type[np.generic], tuple[int, ...]]]] = [
70
127
  ("point", np.float32, (3,)),
71
128
  ]
72
- shader_folder = ""
129
+ shader_folder: ClassVar[str] = ""
73
130
 
74
131
  # _Data and _Uniforms are set as class variables to tell manim how to handle setting/getting these attributes later.
75
- points = _Data()
76
- bounding_box = _Data()
77
- rgbas = _Data()
78
-
79
- is_fixed_in_frame = _Uniforms()
80
- is_fixed_orientation = _Uniforms()
81
- fixed_orientation_center = _Uniforms() # for fixed orientation reference
82
- gloss = _Uniforms()
83
- shadow = _Uniforms()
132
+ points: _Data[Point3D_Array] = _Data()
133
+ bounding_box: _Data[Point3D_Array] = _Data()
134
+ rgbas: _Data[FloatRGBA_Array] = _Data()
135
+
136
+ is_fixed_in_frame: _Uniforms = _Uniforms()
137
+ is_fixed_orientation: _Uniforms = _Uniforms()
138
+ fixed_orientation_center: _Uniforms[tuple[float, float, float]] = (
139
+ _Uniforms()
140
+ ) # for fixed orientation reference
141
+ gloss: _Uniforms = _Uniforms()
142
+ shadow: _Uniforms = _Uniforms()
84
143
 
85
144
  def __init__(
86
145
  self,
87
- color=WHITE,
88
- opacity=1,
89
- dim=3, # TODO, get rid of this
146
+ color: ParsableManimColor | Sequence[ParsableManimColor] = WHITE,
147
+ opacity: float = 1,
148
+ dim: int = 3, # TODO, get rid of this
90
149
  # Lighting parameters
91
150
  # Positive gloss up to 1 makes it reflect the light.
92
- gloss=0.0,
151
+ gloss: float = 0.0,
93
152
  # Positive shadow up to 1 makes a side opposite the light darker
94
- shadow=0.0,
153
+ shadow: float = 0.0,
95
154
  # For shaders
96
- render_primitive=moderngl.TRIANGLES,
97
- texture_paths=None,
98
- depth_test=False,
155
+ render_primitive: int = moderngl.TRIANGLES,
156
+ texture_paths: dict[str, str] | None = None,
157
+ depth_test: bool = False,
99
158
  # If true, the mobject will not get rotated according to camera position
100
- is_fixed_in_frame=False,
101
- is_fixed_orientation=False,
159
+ is_fixed_in_frame: bool = False,
160
+ is_fixed_orientation: bool = False,
102
161
  # Must match in attributes of vert shader
103
162
  # Event listener
104
- listen_to_events=False,
105
- model_matrix=None,
106
- should_render=True,
163
+ listen_to_events: bool = False,
164
+ model_matrix: MatrixMN | None = None,
165
+ should_render: bool = True,
107
166
  name: str | None = None,
108
- **kwargs,
167
+ **kwargs: Any,
109
168
  ):
110
- self.name = self.__class__.__name__ if name is None else name
169
+ self.name: str = self.__class__.__name__ if name is None else name
111
170
  # getattr in case data/uniforms are already defined in parent classes.
112
- self.data = getattr(self, "data", {})
113
- self.uniforms = getattr(self, "uniforms", {})
171
+ self.data: dict[str, npt.NDArray[Any]] = getattr(self, "data", {})
172
+ self.uniforms: dict[str, float | tuple[float, ...]] = getattr(
173
+ self, "uniforms", {}
174
+ )
114
175
 
115
- self.opacity = opacity
116
- self.dim = dim # TODO, get rid of this
176
+ self.opacity: float | Iterable[float] = opacity
177
+ self.dim: int = dim # TODO, get rid of this
117
178
  # Lighting parameters
118
179
  # Positive gloss up to 1 makes it reflect the light.
119
180
  self.gloss = gloss
120
181
  # Positive shadow up to 1 makes a side opposite the light darker
121
182
  self.shadow = shadow
122
183
  # For shaders
123
- self.render_primitive = render_primitive
124
- self.texture_paths = texture_paths
125
- self.depth_test = depth_test
184
+ self.render_primitive: int = render_primitive
185
+ self.texture_paths: dict[str, str] | None = texture_paths
186
+ self.depth_test: bool = depth_test
126
187
  # If true, the mobject will not get rotated according to camera position
127
188
  self.is_fixed_in_frame = float(is_fixed_in_frame)
128
189
  self.is_fixed_orientation = float(is_fixed_orientation)
129
190
  self.fixed_orientation_center = (0, 0, 0)
130
191
  # Must match in attributes of vert shader
131
192
  # Event listener
132
- self.listen_to_events = listen_to_events
133
-
134
- self._submobjects = []
135
- self.parents = []
136
- self.parent = None
137
- self.family = [self]
138
- self.locked_data_keys = set()
139
- self.needs_new_bounding_box = True
193
+ self.listen_to_events: bool = listen_to_events
194
+
195
+ self._submobjects: list[OpenGLMobject] = []
196
+ self.parents: list[OpenGLMobject] = []
197
+ self.parent: OpenGLMobject | None = None
198
+ self.family: list[OpenGLMobject] = [self]
199
+ self.locked_data_keys: set[str] = set()
200
+ self.needs_new_bounding_box: bool = True
140
201
  if model_matrix is None:
141
- self.model_matrix = np.eye(4)
202
+ self.model_matrix: MatrixMN = np.eye(4)
142
203
  else:
143
204
  self.model_matrix = model_matrix
144
205
 
@@ -146,41 +207,101 @@ class OpenGLMobject:
146
207
  self.init_updaters()
147
208
  # self.init_event_listners()
148
209
  self.init_points()
149
- self.color = Color(color) if color else None
210
+ self.color: ManimColor | list[ManimColor] = ManimColor.parse(color)
150
211
  self.init_colors()
151
212
 
152
- self.shader_indices = None
213
+ self.shader_indices: Sequence[int] | None = None
153
214
 
154
215
  if self.depth_test:
155
216
  self.apply_depth_test()
156
217
 
157
- self.should_render = should_render
218
+ self.should_render: bool = should_render
219
+
220
+ def _assert_valid_submobjects(self, submobjects: Iterable[OpenGLMobject]) -> Self:
221
+ """Check that all submobjects are actually instances of
222
+ :class:`OpenGLMobject`, and that none of them is
223
+ ``self`` (an :class:`OpenGLMobject` cannot contain itself).
224
+
225
+ This is an auxiliary function called when adding OpenGLMobjects to the
226
+ :attr:`submobjects` list.
227
+
228
+ This function is intended to be overridden by subclasses such as
229
+ :class:`OpenGLVMobject`, which should assert that only other
230
+ OpenGLVMobjects may be added into it.
231
+
232
+ Parameters
233
+ ----------
234
+ submobjects
235
+ The list containing values to validate.
236
+
237
+ Returns
238
+ -------
239
+ :class:`OpenGLMobject`
240
+ The OpenGLMobject itself.
241
+
242
+ Raises
243
+ ------
244
+ TypeError
245
+ If any of the values in `submobjects` is not an
246
+ :class:`OpenGLMobject`.
247
+ ValueError
248
+ If there was an attempt to add an :class:`OpenGLMobject` as its own
249
+ submobject.
250
+ """
251
+ return self._assert_valid_submobjects_internal(submobjects, OpenGLMobject)
252
+
253
+ def _assert_valid_submobjects_internal(
254
+ self, submobjects: Iterable[OpenGLMobject], mob_class: type[OpenGLMobject]
255
+ ) -> Self:
256
+ for i, submob in enumerate(submobjects):
257
+ if not isinstance(submob, mob_class):
258
+ error_message = (
259
+ f"Only values of type {mob_class.__name__} can be added "
260
+ f"as submobjects of {type(self).__name__}, but the value "
261
+ f"{submob} (at index {i}) is of type "
262
+ f"{type(submob).__name__}."
263
+ )
264
+ # Intended for subclasses such as OpenGLVMobject, which
265
+ # cannot have regular OpenGLMobjects as submobjects
266
+ if isinstance(submob, OpenGLMobject):
267
+ error_message += (
268
+ " You can try adding this value into a Group instead."
269
+ )
270
+ raise TypeError(error_message)
271
+ if submob is self:
272
+ raise ValueError(
273
+ f"Cannot add {type(self).__name__} as a submobject of "
274
+ f"itself (at index {i})."
275
+ )
276
+ return self
158
277
 
159
278
  @classmethod
160
- def __init_subclass__(cls, **kwargs):
279
+ def __init_subclass__(cls, **kwargs: Any) -> None:
161
280
  super().__init_subclass__(**kwargs)
162
281
  cls._original__init__ = cls.__init__
163
282
 
164
- def __str__(self):
283
+ @override
284
+ def __str__(self) -> str:
165
285
  return self.__class__.__name__
166
286
 
167
- def __repr__(self):
287
+ @override
288
+ def __repr__(self) -> str:
168
289
  return str(self.name)
169
290
 
170
- def __sub__(self, other):
291
+ def __sub__(self, other: Never) -> object:
171
292
  return NotImplemented
172
293
 
173
- def __isub__(self, other):
294
+ def __isub__(self, other: Never) -> object:
174
295
  return NotImplemented
175
296
 
176
- def __add__(self, mobject):
297
+ def __add__(self, mobject: Never) -> object:
177
298
  return NotImplemented
178
299
 
179
- def __iadd__(self, mobject):
300
+ def __iadd__(self, mobject: Never) -> object:
180
301
  return NotImplemented
181
302
 
182
303
  @classmethod
183
- def set_default(cls, **kwargs):
304
+ def set_default(cls, **kwargs: Any) -> None:
184
305
  """Sets the default values of keyword arguments.
185
306
 
186
307
  If this method is called without any additional keyword
@@ -203,10 +324,10 @@ class OpenGLMobject:
203
324
  >>> from manim import Square, GREEN
204
325
  >>> Square.set_default(color=GREEN, fill_opacity=0.25)
205
326
  >>> s = Square(); s.color, s.fill_opacity
206
- (<Color #83c167>, 0.25)
327
+ (ManimColor('#83C167'), 0.25)
207
328
  >>> Square.set_default()
208
329
  >>> s = Square(); s.color, s.fill_opacity
209
- (<Color white>, 0.0)
330
+ (ManimColor('#FFFFFF'), 0.0)
210
331
 
211
332
  .. manim:: ChangedDefaultTextcolor
212
333
  :save_last_frame:
@@ -223,32 +344,37 @@ class OpenGLMobject:
223
344
 
224
345
  """
225
346
  if kwargs:
226
- cls.__init__ = partialmethod(cls.__init__, **kwargs)
347
+ # Apparently mypy does not correctly understand `partialmethod`:
348
+ # see https://github.com/python/mypy/issues/8619
349
+ cls.__init__ = partialmethod(cls.__init__, **kwargs) # type: ignore[assignment]
227
350
  else:
228
351
  cls.__init__ = cls._original__init__
229
352
 
230
- def init_data(self):
353
+ def init_data(self) -> None:
231
354
  """Initializes the ``points``, ``bounding_box`` and ``rgbas`` attributes and groups them into self.data.
232
- Subclasses can inherit and overwrite this method to extend `self.data`."""
355
+ Subclasses can inherit and overwrite this method to extend `self.data`.
356
+ """
233
357
  self.points = np.zeros((0, 3))
234
358
  self.bounding_box = np.zeros((3, 3))
235
359
  self.rgbas = np.zeros((1, 4))
236
360
 
237
- def init_colors(self):
361
+ def init_colors(self) -> None:
238
362
  """Initializes the colors.
239
363
 
240
- Gets called upon creation"""
364
+ Gets called upon creation
365
+ """
241
366
  self.set_color(self.color, self.opacity)
242
367
 
243
- def init_points(self):
368
+ def init_points(self) -> None:
244
369
  """Initializes :attr:`points` and therefore the shape.
245
370
 
246
371
  Gets called upon creation. This is an empty method that can be implemented by
247
- subclasses."""
372
+ subclasses.
373
+ """
248
374
  # Typically implemented in subclass, unless purposefully left blank
249
375
  pass
250
376
 
251
- def set(self, **kwargs) -> OpenGLMobject:
377
+ def set(self, **kwargs: object) -> Self:
252
378
  """Sets attributes.
253
379
 
254
380
  Mainly to be used along with :attr:`animate` to
@@ -276,24 +402,23 @@ class OpenGLMobject:
276
402
 
277
403
 
278
404
  """
279
-
280
405
  for attr, value in kwargs.items():
281
406
  setattr(self, attr, value)
282
407
 
283
408
  return self
284
409
 
285
- def set_data(self, data):
410
+ def set_data(self, data: dict[str, Any]) -> Self:
286
411
  for key in data:
287
412
  self.data[key] = data[key].copy()
288
413
  return self
289
414
 
290
- def set_uniforms(self, uniforms):
415
+ def set_uniforms(self, uniforms: dict[str, Any]) -> Self:
291
416
  for key in uniforms:
292
417
  self.uniforms[key] = uniforms[key] # Copy?
293
418
  return self
294
419
 
295
420
  @property
296
- def animate(self):
421
+ def animate(self) -> _AnimationBuilder | Self:
297
422
  """Used to animate the application of a method.
298
423
 
299
424
  .. warning::
@@ -304,7 +429,9 @@ class OpenGLMobject:
304
429
 
305
430
  ::
306
431
 
307
- self.play(my_mobject.animate.shift(RIGHT), my_mobject.animate.rotate(PI))
432
+ self.play(
433
+ my_mobject.animate.shift(RIGHT), my_mobject.animate.rotate(PI)
434
+ )
308
435
 
309
436
  make use of method chaining for ``animate``, meaning::
310
437
 
@@ -381,7 +508,7 @@ class OpenGLMobject:
381
508
  return _AnimationBuilder(self)
382
509
 
383
510
  @property
384
- def width(self):
511
+ def width(self) -> float:
385
512
  """The width of the mobject.
386
513
 
387
514
  Returns
@@ -409,17 +536,16 @@ class OpenGLMobject:
409
536
  :meth:`length_over_dim`
410
537
 
411
538
  """
412
-
413
539
  # Get the length across the X dimension
414
540
  return self.length_over_dim(0)
415
541
 
416
542
  # Only these methods should directly affect points
417
543
  @width.setter
418
- def width(self, value):
544
+ def width(self, value: float) -> None:
419
545
  self.rescale_to_fit(value, 0, stretch=False)
420
546
 
421
547
  @property
422
- def height(self):
548
+ def height(self) -> float:
423
549
  """The height of the mobject.
424
550
 
425
551
  Returns
@@ -447,16 +573,15 @@ class OpenGLMobject:
447
573
  :meth:`length_over_dim`
448
574
 
449
575
  """
450
-
451
576
  # Get the length across the Y dimension
452
577
  return self.length_over_dim(1)
453
578
 
454
579
  @height.setter
455
- def height(self, value):
580
+ def height(self, value: float) -> None:
456
581
  self.rescale_to_fit(value, 1, stretch=False)
457
582
 
458
583
  @property
459
- def depth(self):
584
+ def depth(self) -> float:
460
585
  """The depth of the mobject.
461
586
 
462
587
  Returns
@@ -468,21 +593,24 @@ class OpenGLMobject:
468
593
  :meth:`length_over_dim`
469
594
 
470
595
  """
471
-
472
596
  # Get the length across the Z dimension
473
597
  return self.length_over_dim(2)
474
598
 
475
599
  @depth.setter
476
- def depth(self, value):
600
+ def depth(self, value: float) -> None:
477
601
  self.rescale_to_fit(value, 2, stretch=False)
478
602
 
479
- def resize_points(self, new_length, resize_func=resize_array):
603
+ def resize_points(
604
+ self,
605
+ new_length: int,
606
+ resize_func: Callable[[Point3D_Array, int], Point3D_Array] = resize_array,
607
+ ) -> Self:
480
608
  if new_length != len(self.points):
481
609
  self.points = resize_func(self.points, new_length)
482
610
  self.refresh_bounding_box()
483
611
  return self
484
612
 
485
- def set_points(self, points):
613
+ def set_points(self, points: Point3DLike_Array) -> Self:
486
614
  if len(points) == len(self.points):
487
615
  self.points[:] = points
488
616
  elif isinstance(points, np.ndarray):
@@ -492,23 +620,28 @@ class OpenGLMobject:
492
620
  self.refresh_bounding_box()
493
621
  return self
494
622
 
495
- def apply_over_attr_arrays(self, func):
623
+ def apply_over_attr_arrays(
624
+ self, func: Callable[[npt.NDArray[_T_np]], npt.NDArray[_T_np]]
625
+ ) -> Self:
496
626
  for attr in self.get_array_attrs():
497
627
  setattr(self, attr, func(getattr(self, attr)))
498
628
  return self
499
629
 
500
- def append_points(self, new_points):
630
+ def get_array_attrs(self) -> Iterable[str]:
631
+ return ["points"]
632
+
633
+ def append_points(self, new_points: Point3DLike_Array) -> Self:
501
634
  self.points = np.vstack([self.points, new_points])
502
635
  self.refresh_bounding_box()
503
636
  return self
504
637
 
505
- def reverse_points(self):
638
+ def reverse_points(self) -> Self:
506
639
  for mob in self.get_family():
507
640
  for key in mob.data:
508
641
  mob.data[key] = mob.data[key][::-1]
509
642
  return self
510
643
 
511
- def get_midpoint(self) -> np.ndarray:
644
+ def get_midpoint(self) -> Point3D:
512
645
  """Get coordinates of the middle of the path that forms the :class:`~.OpenGLMobject`.
513
646
 
514
647
  Examples
@@ -531,18 +664,19 @@ class OpenGLMobject:
531
664
  """
532
665
  return self.point_from_proportion(0.5)
533
666
 
667
+ # TODO: name is inconsistent with Mobject.apply_points_function_about_point()
534
668
  def apply_points_function(
535
669
  self,
536
- func,
537
- about_point=None,
538
- about_edge=ORIGIN,
539
- works_on_bounding_box=False,
540
- ):
670
+ func: MultiMappingFunction,
671
+ about_point: Point3DLike | None = None,
672
+ about_edge: Vector3DLike | None = ORIGIN,
673
+ works_on_bounding_box: bool = False,
674
+ ) -> Self:
541
675
  if about_point is None and about_edge is not None:
542
676
  about_point = self.get_bounding_box_point(about_edge)
543
677
 
544
678
  for mob in self.get_family():
545
- arrs = []
679
+ arrs: list[Point3D_Array] = []
546
680
  if mob.has_points():
547
681
  arrs.append(mob.points)
548
682
  if works_on_bounding_box:
@@ -563,7 +697,7 @@ class OpenGLMobject:
563
697
 
564
698
  # Others related to points
565
699
 
566
- def match_points(self, mobject):
700
+ def match_points(self, mobject: OpenGLMobject) -> Self:
567
701
  """Edit points, positions, and submobjects to be identical
568
702
  to another :class:`~.OpenGLMobject`, while keeping the style unchanged.
569
703
 
@@ -581,29 +715,31 @@ class OpenGLMobject:
581
715
  self.wait(0.5)
582
716
  """
583
717
  self.set_points(mobject.points)
718
+ return self
584
719
 
585
- def clear_points(self):
586
- self.resize_points(0)
720
+ def clear_points(self) -> Self:
721
+ self.points = np.empty((0, 3))
722
+ return self
587
723
 
588
- def get_num_points(self):
724
+ def get_num_points(self) -> int:
589
725
  return len(self.points)
590
726
 
591
- def get_all_points(self):
727
+ def get_all_points(self) -> Point3D_Array:
592
728
  if self.submobjects:
593
729
  return np.vstack([sm.points for sm in self.get_family()])
594
730
  else:
595
731
  return self.points
596
732
 
597
- def has_points(self):
733
+ def has_points(self) -> bool:
598
734
  return self.get_num_points() > 0
599
735
 
600
- def get_bounding_box(self):
736
+ def get_bounding_box(self) -> Point3D_Array:
601
737
  if self.needs_new_bounding_box:
602
738
  self.bounding_box = self.compute_bounding_box()
603
739
  self.needs_new_bounding_box = False
604
740
  return self.bounding_box
605
741
 
606
- def compute_bounding_box(self):
742
+ def compute_bounding_box(self) -> Point3D_Array:
607
743
  all_points = np.vstack(
608
744
  [
609
745
  self.points,
@@ -623,7 +759,9 @@ class OpenGLMobject:
623
759
  mids = (mins + maxs) / 2
624
760
  return np.array([mins, mids, maxs])
625
761
 
626
- def refresh_bounding_box(self, recurse_down=False, recurse_up=True):
762
+ def refresh_bounding_box(
763
+ self, recurse_down: bool = False, recurse_up: bool = True
764
+ ) -> Self:
627
765
  for mob in self.get_family(recurse_down):
628
766
  mob.needs_new_bounding_box = True
629
767
  if recurse_up:
@@ -631,30 +769,33 @@ class OpenGLMobject:
631
769
  parent.refresh_bounding_box()
632
770
  return self
633
771
 
634
- def is_point_touching(self, point, buff=MED_SMALL_BUFF):
772
+ def is_point_touching(
773
+ self, point: Point3DLike, buff: float = MED_SMALL_BUFF
774
+ ) -> bool:
635
775
  bb = self.get_bounding_box()
636
776
  mins = bb[0] - buff
637
777
  maxs = bb[2] + buff
638
- return (point >= mins).all() and (point <= maxs).all()
778
+ rv: bool = (point >= mins).all() and (point <= maxs).all()
779
+ return rv
639
780
 
640
781
  # Family matters
641
782
 
642
- def __getitem__(self, value):
783
+ def __getitem__(self, value: int | slice) -> OpenGLMobject:
643
784
  if isinstance(value, slice):
644
785
  GroupClass = self.get_group_class()
645
786
  return GroupClass(*self.split().__getitem__(value))
646
787
  return self.split().__getitem__(value)
647
788
 
648
- def __iter__(self):
789
+ def __iter__(self) -> Iterator[OpenGLMobject]:
649
790
  return iter(self.split())
650
791
 
651
- def __len__(self):
792
+ def __len__(self) -> int:
652
793
  return len(self.split())
653
794
 
654
- def split(self):
795
+ def split(self) -> Sequence[OpenGLMobject]:
655
796
  return self.submobjects
656
797
 
657
- def assemble_family(self):
798
+ def assemble_family(self) -> Self:
658
799
  sub_families = (sm.get_family() for sm in self.submobjects)
659
800
  self.family = [self, *uniq_chain(*sub_families)]
660
801
  self.refresh_has_updater_status()
@@ -663,18 +804,16 @@ class OpenGLMobject:
663
804
  parent.assemble_family()
664
805
  return self
665
806
 
666
- def get_family(self, recurse=True):
807
+ def get_family(self, recurse: bool = True) -> Sequence[OpenGLMobject]:
667
808
  if recurse and hasattr(self, "family"):
668
809
  return self.family
669
810
  else:
670
811
  return [self]
671
812
 
672
- def family_members_with_points(self):
813
+ def family_members_with_points(self) -> Sequence[OpenGLMobject]:
673
814
  return [m for m in self.get_family() if m.has_points()]
674
815
 
675
- def add(
676
- self, *mobjects: OpenGLMobject, update_parent: bool = False
677
- ) -> OpenGLMobject:
816
+ def add(self, *mobjects: OpenGLMobject, update_parent: bool = False) -> Self:
678
817
  """Add mobjects as submobjects.
679
818
 
680
819
  The mobjects are added to :attr:`submobjects`.
@@ -725,28 +864,33 @@ class OpenGLMobject:
725
864
  >>> len(outer.submobjects)
726
865
  1
727
866
 
867
+ Only OpenGLMobjects can be added::
868
+
869
+ >>> outer.add(3)
870
+ Traceback (most recent call last):
871
+ ...
872
+ TypeError: Only values of type OpenGLMobject can be added as submobjects of OpenGLMobject, but the value 3 (at index 0) is of type int.
873
+
728
874
  Adding an object to itself raises an error::
729
875
 
730
876
  >>> outer.add(outer)
731
877
  Traceback (most recent call last):
732
878
  ...
733
- ValueError: OpenGLMobject cannot contain self
879
+ ValueError: Cannot add OpenGLMobject as a submobject of itself (at index 0).
734
880
 
735
881
  """
736
882
  if update_parent:
737
883
  assert len(mobjects) == 1, "Can't set multiple parents."
738
884
  mobjects[0].parent = self
739
885
 
740
- if self in mobjects:
741
- raise ValueError("OpenGLMobject cannot contain self")
886
+ self._assert_valid_submobjects(mobjects)
887
+
742
888
  if any(mobjects.count(elem) > 1 for elem in mobjects):
743
889
  logger.warning(
744
890
  "Attempted adding some Mobject as a child more than once, "
745
891
  "this is not possible. Repetitions are ignored.",
746
892
  )
747
893
  for mobject in mobjects:
748
- if not isinstance(mobject, OpenGLMobject):
749
- raise TypeError("All submobjects must be of type OpenGLMobject")
750
894
  if mobject not in self.submobjects:
751
895
  self.submobjects.append(mobject)
752
896
  if self not in mobject.parents:
@@ -754,7 +898,9 @@ class OpenGLMobject:
754
898
  self.assemble_family()
755
899
  return self
756
900
 
757
- def insert(self, index: int, mobject: OpenGLMobject, update_parent: bool = False):
901
+ def insert(
902
+ self, index: int, mobject: OpenGLMobject, update_parent: bool = False
903
+ ) -> Self:
758
904
  """Inserts a mobject at a specific position into self.submobjects
759
905
 
760
906
  Effectively just calls ``self.submobjects.insert(index, mobject)``,
@@ -771,15 +917,10 @@ class OpenGLMobject:
771
917
  update_parent
772
918
  Whether or not to set ``mobject.parent`` to ``self``.
773
919
  """
774
-
775
920
  if update_parent:
776
921
  mobject.parent = self
777
922
 
778
- if mobject is self:
779
- raise ValueError("OpenGLMobject cannot contain self")
780
-
781
- if not isinstance(mobject, OpenGLMobject):
782
- raise TypeError("All submobjects must be of type OpenGLMobject")
923
+ self._assert_valid_submobjects([mobject])
783
924
 
784
925
  if mobject not in self.submobjects:
785
926
  self.submobjects.insert(index, mobject)
@@ -790,9 +931,7 @@ class OpenGLMobject:
790
931
  self.assemble_family()
791
932
  return self
792
933
 
793
- def remove(
794
- self, *mobjects: OpenGLMobject, update_parent: bool = False
795
- ) -> OpenGLMobject:
934
+ def remove(self, *mobjects: OpenGLMobject, update_parent: bool = False) -> Self:
796
935
  """Remove :attr:`submobjects`.
797
936
 
798
937
  The mobjects are removed from :attr:`submobjects`, if they exist.
@@ -826,7 +965,7 @@ class OpenGLMobject:
826
965
  self.assemble_family()
827
966
  return self
828
967
 
829
- def add_to_back(self, *mobjects: OpenGLMobject) -> OpenGLMobject:
968
+ def add_to_back(self, *mobjects: OpenGLMobject) -> Self:
830
969
  # NOTE: is the note true OpenGLMobjects?
831
970
  """Add all passed mobjects to the back of the submobjects.
832
971
 
@@ -871,10 +1010,12 @@ class OpenGLMobject:
871
1010
  :meth:`add`
872
1011
 
873
1012
  """
1013
+ self._assert_valid_submobjects(mobjects)
874
1014
  self.submobjects = list_update(mobjects, self.submobjects)
875
1015
  return self
876
1016
 
877
- def replace_submobject(self, index, new_submob):
1017
+ def replace_submobject(self, index: int, new_submob: OpenGLMobject) -> Self:
1018
+ self._assert_valid_submobjects([new_submob])
878
1019
  old_submob = self.submobjects[index]
879
1020
  if self in old_submob.parents:
880
1021
  old_submob.parents.remove(self)
@@ -882,36 +1023,14 @@ class OpenGLMobject:
882
1023
  self.assemble_family()
883
1024
  return self
884
1025
 
885
- def invert(self, recursive=False):
886
- """Inverts the list of :attr:`submobjects`.
887
-
888
- Parameters
889
- ----------
890
- recursive
891
- If ``True``, all submobject lists of this mobject's family are inverted.
892
-
893
- Examples
894
- --------
895
-
896
- .. manim:: InvertSumobjectsExample
897
-
898
- class InvertSumobjectsExample(Scene):
899
- def construct(self):
900
- s = VGroup(*[Dot().shift(i*0.1*RIGHT) for i in range(-20,20)])
901
- s2 = s.copy()
902
- s2.invert()
903
- s2.shift(DOWN)
904
- self.play(Write(s), Write(s2))
905
- """
906
- if recursive:
907
- for submob in self.submobjects:
908
- submob.invert(recursive=True)
909
- list.reverse(self.submobjects)
910
- self.assemble_family()
911
-
912
1026
  # Submobject organization
913
1027
 
914
- def arrange(self, direction=RIGHT, center=True, **kwargs):
1028
+ def arrange(
1029
+ self,
1030
+ direction: Vector3DLike = RIGHT,
1031
+ center: bool = True,
1032
+ **kwargs: Any,
1033
+ ) -> Self:
915
1034
  """Sorts :class:`~.OpenGLMobject` next to each other on screen.
916
1035
 
917
1036
  Examples
@@ -940,14 +1059,14 @@ class OpenGLMobject:
940
1059
  rows: int | None = None,
941
1060
  cols: int | None = None,
942
1061
  buff: float | tuple[float, float] = MED_SMALL_BUFF,
943
- cell_alignment: np.ndarray = ORIGIN,
1062
+ cell_alignment: Vector3DLike = ORIGIN,
944
1063
  row_alignments: str | None = None, # "ucd"
945
1064
  col_alignments: str | None = None, # "lcr"
946
- row_heights: Iterable[float | None] | None = None,
947
- col_widths: Iterable[float | None] | None = None,
1065
+ row_heights: Sequence[float | None] | None = None,
1066
+ col_widths: Sequence[float | None] | None = None,
948
1067
  flow_order: str = "rd",
949
- **kwargs,
950
- ) -> OpenGLMobject:
1068
+ **kwargs: Any,
1069
+ ) -> Self:
951
1070
  """Arrange submobjects in a grid.
952
1071
 
953
1072
  Parameters
@@ -1043,16 +1162,27 @@ class OpenGLMobject:
1043
1162
  start_pos = self.get_center()
1044
1163
 
1045
1164
  # get cols / rows values if given (implicitly)
1046
- def init_size(num, alignments, sizes):
1165
+ def init_size(
1166
+ num: int | None,
1167
+ alignments: str | None,
1168
+ sizes: Sequence[float | None] | None,
1169
+ name: str,
1170
+ ) -> int:
1047
1171
  if num is not None:
1048
1172
  return num
1049
1173
  if alignments is not None:
1050
1174
  return len(alignments)
1051
1175
  if sizes is not None:
1052
1176
  return len(sizes)
1177
+ raise ValueError(
1178
+ f"At least one of the following parameters: '{name}s', "
1179
+ f"'{name}_alignments' or "
1180
+ f"'{name}_{'widths' if name == 'col' else 'heights'}', "
1181
+ "must not be None"
1182
+ )
1053
1183
 
1054
- cols = init_size(cols, col_alignments, col_widths)
1055
- rows = init_size(rows, row_alignments, row_heights)
1184
+ cols = init_size(cols, col_alignments, col_widths, "col")
1185
+ rows = init_size(rows, row_alignments, row_heights, "row")
1056
1186
 
1057
1187
  # calculate rows cols
1058
1188
  if rows is None and cols is None:
@@ -1076,34 +1206,37 @@ class OpenGLMobject:
1076
1206
  buff_x = buff_y = buff
1077
1207
 
1078
1208
  # Initialize alignments correctly
1079
- def init_alignments(alignments, num, mapping, name, dir):
1080
- if alignments is None:
1209
+ def init_alignments(
1210
+ str_alignments: str | None,
1211
+ num: int,
1212
+ mapping: dict[str, Vector3D],
1213
+ name: str,
1214
+ direction: Vector3D,
1215
+ ) -> Sequence[Vector3D]:
1216
+ if str_alignments is None:
1081
1217
  # Use cell_alignment as fallback
1082
- return [cell_alignment * dir] * num
1083
- if len(alignments) != num:
1218
+ return [cast(Vector3D, cell_alignment * direction)] * num
1219
+ if len(str_alignments) != num:
1084
1220
  raise ValueError(f"{name}_alignments has a mismatching size.")
1085
- alignments = list(alignments)
1086
- for i in range(num):
1087
- alignments[i] = mapping[alignments[i]]
1088
- return alignments
1221
+ return [mapping[letter] for letter in str_alignments]
1089
1222
 
1090
- row_alignments = init_alignments(
1223
+ row_alignments_seq: Sequence[Vector3D] = init_alignments(
1091
1224
  row_alignments,
1092
1225
  rows,
1093
1226
  {"u": UP, "c": ORIGIN, "d": DOWN},
1094
1227
  "row",
1095
1228
  RIGHT,
1096
1229
  )
1097
- col_alignments = init_alignments(
1230
+ col_alignments_seq: Sequence[Vector3D] = init_alignments(
1098
1231
  col_alignments,
1099
1232
  cols,
1100
1233
  {"l": LEFT, "c": ORIGIN, "r": RIGHT},
1101
1234
  "col",
1102
1235
  UP,
1103
1236
  )
1104
- # Now row_alignment[r] + col_alignment[c] is the alignment in cell [r][c]
1237
+ # Now row_alignments_seq[r] + col_alignment_seq[c] is the alignment in cell [r][c]
1105
1238
 
1106
- mapper = {
1239
+ mapper: dict[str, Callable[[int, int], int]] = {
1107
1240
  "dr": lambda r, c: (rows - r - 1) + c * rows,
1108
1241
  "dl": lambda r, c: (rows - r - 1) + (cols - c - 1) * rows,
1109
1242
  "ur": lambda r, c: r + c * rows,
@@ -1114,20 +1247,31 @@ class OpenGLMobject:
1114
1247
  "lu": lambda r, c: r * cols + (cols - c - 1),
1115
1248
  }
1116
1249
  if flow_order not in mapper:
1250
+ valid_flow_orders = ",".join([f'"{key}"' for key in mapper])
1117
1251
  raise ValueError(
1118
- 'flow_order must be one of the following values: "dr", "rd", "ld" "dl", "ru", "ur", "lu", "ul".',
1252
+ f"flow_order must be one of the following values: {valid_flow_orders}.",
1119
1253
  )
1120
- flow_order = mapper[flow_order]
1254
+ flow_order_func = mapper[flow_order]
1121
1255
 
1122
1256
  # Reverse row_alignments and row_heights. Necessary since the
1123
1257
  # grid filling is handled bottom up for simplicity reasons.
1124
- def reverse(maybe_list):
1258
+ if TYPE_CHECKING:
1259
+
1260
+ @overload
1261
+ def reverse(maybe_list: None) -> None: ...
1262
+ @overload
1263
+ def reverse(maybe_list: Sequence[_T]) -> list[_T]: ...
1264
+ @overload
1265
+ def reverse(maybe_list: Sequence[_T] | None) -> list[_T] | None: ...
1266
+
1267
+ def reverse(maybe_list: Sequence[_T] | None) -> list[_T] | None:
1125
1268
  if maybe_list is not None:
1126
1269
  maybe_list = list(maybe_list)
1127
1270
  maybe_list.reverse()
1128
1271
  return maybe_list
1272
+ return None
1129
1273
 
1130
- row_alignments = reverse(row_alignments)
1274
+ row_alignments_seq = reverse(row_alignments_seq)
1131
1275
  row_heights = reverse(row_heights)
1132
1276
 
1133
1277
  placeholder = OpenGLMobject()
@@ -1136,7 +1280,7 @@ class OpenGLMobject:
1136
1280
  # properties of 0.
1137
1281
 
1138
1282
  mobs.extend([placeholder] * (rows * cols - len(mobs)))
1139
- grid = [[mobs[flow_order(r, c)] for c in range(cols)] for r in range(rows)]
1283
+ grid = [[mobs[flow_order_func(r, c)] for c in range(cols)] for r in range(rows)]
1140
1284
 
1141
1285
  measured_heigths = [
1142
1286
  max(grid[r][c].height for c in range(cols)) for r in range(rows)
@@ -1146,24 +1290,30 @@ class OpenGLMobject:
1146
1290
  ]
1147
1291
 
1148
1292
  # Initialize row_heights / col_widths correctly using measurements as fallback
1149
- def init_sizes(sizes, num, measures, name):
1293
+ def init_sizes(
1294
+ sizes: Sequence[float | None] | None,
1295
+ num: int,
1296
+ measures: Sequence[float],
1297
+ name: str,
1298
+ ) -> Sequence[float]:
1150
1299
  if sizes is None:
1151
1300
  sizes = [None] * num
1152
1301
  if len(sizes) != num:
1153
1302
  raise ValueError(f"{name} has a mismatching size.")
1154
1303
  return [
1155
- sizes[i] if sizes[i] is not None else measures[i] for i in range(num)
1304
+ size if (size := sizes[i]) is not None else measures[i]
1305
+ for i in range(num)
1156
1306
  ]
1157
1307
 
1158
1308
  heights = init_sizes(row_heights, rows, measured_heigths, "row_heights")
1159
1309
  widths = init_sizes(col_widths, cols, measured_widths, "col_widths")
1160
1310
 
1161
- x, y = 0, 0
1311
+ x, y = 0.0, 0.0
1162
1312
  for r in range(rows):
1163
- x = 0
1313
+ x = 0.0
1164
1314
  for c in range(cols):
1165
1315
  if grid[r][c] is not placeholder:
1166
- alignment = row_alignments[r] + col_alignments[c]
1316
+ alignment = row_alignments_seq[r] + col_alignments_seq[c]
1167
1317
  line = Line(
1168
1318
  x * RIGHT + y * UP,
1169
1319
  (x + widths[c]) * RIGHT + (y + heights[r]) * UP,
@@ -1179,7 +1329,13 @@ class OpenGLMobject:
1179
1329
  self.move_to(start_pos)
1180
1330
  return self
1181
1331
 
1182
- def get_grid(self, n_rows, n_cols, height=None, **kwargs):
1332
+ def get_grid(
1333
+ self,
1334
+ n_rows: int,
1335
+ n_cols: int,
1336
+ height: float | None = None,
1337
+ **kwargs: Any,
1338
+ ) -> OpenGLGroup:
1183
1339
  """
1184
1340
  Returns a new mobject containing multiple copies of this one
1185
1341
  arranged in a grid
@@ -1190,11 +1346,15 @@ class OpenGLMobject:
1190
1346
  grid.set_height(height)
1191
1347
  return grid
1192
1348
 
1193
- def duplicate(self, n: int):
1194
- """Returns an :class:`~.OpenGLVGroup` containing ``n`` copies of the mobject."""
1349
+ def duplicate(self, n: int) -> OpenGLGroup:
1350
+ """Returns an :class:`~.OpenGLGroup` containing ``n`` copies of the mobject."""
1195
1351
  return self.get_group_class()(*[self.copy() for _ in range(n)])
1196
1352
 
1197
- def sort(self, point_to_num_func=lambda p: p[0], submob_func=None):
1353
+ def sort(
1354
+ self,
1355
+ point_to_num_func: Callable[[Point3DLike], float] = lambda p: p[0],
1356
+ submob_func: Callable[[OpenGLMobject], Any] | None = None,
1357
+ ) -> Self:
1198
1358
  """Sorts the list of :attr:`submobjects` by a function defined by ``submob_func``."""
1199
1359
  if submob_func is not None:
1200
1360
  self.submobjects.sort(key=submob_func)
@@ -1202,7 +1362,7 @@ class OpenGLMobject:
1202
1362
  self.submobjects.sort(key=lambda m: point_to_num_func(m.get_center()))
1203
1363
  return self
1204
1364
 
1205
- def shuffle(self, recurse=False):
1365
+ def shuffle(self, recurse: bool = False) -> Self:
1206
1366
  """Shuffles the order of :attr:`submobjects`
1207
1367
 
1208
1368
  Examples
@@ -1225,7 +1385,7 @@ class OpenGLMobject:
1225
1385
  self.assemble_family()
1226
1386
  return self
1227
1387
 
1228
- def invert(self, recursive=False):
1388
+ def invert(self, recursive: bool = False) -> Self:
1229
1389
  """Inverts the list of :attr:`submobjects`.
1230
1390
 
1231
1391
  Parameters
@@ -1249,11 +1409,13 @@ class OpenGLMobject:
1249
1409
  if recursive:
1250
1410
  for submob in self.submobjects:
1251
1411
  submob.invert(recursive=True)
1252
- list.reverse(self.submobjects)
1412
+ self.submobjects.reverse()
1413
+ self.assemble_family()
1414
+ return self
1253
1415
 
1254
1416
  # Copying
1255
1417
 
1256
- def copy(self, shallow: bool = False):
1418
+ def copy(self, shallow: bool = False) -> OpenGLMobject:
1257
1419
  """Create and return an identical copy of the :class:`OpenGLMobject` including all
1258
1420
  :attr:`submobjects`.
1259
1421
 
@@ -1311,95 +1473,103 @@ class OpenGLMobject:
1311
1473
  # setattr(copy_mobject, attr, value.copy())
1312
1474
  return copy_mobject
1313
1475
 
1314
- def deepcopy(self):
1476
+ def deepcopy(self) -> OpenGLMobject:
1315
1477
  parents = self.parents
1316
1478
  self.parents = []
1317
1479
  result = copy.deepcopy(self)
1318
1480
  self.parents = parents
1319
1481
  return result
1320
1482
 
1321
- def generate_target(self, use_deepcopy: bool = False):
1322
- self.target = None # Prevent exponential explosion
1483
+ def generate_target(self, use_deepcopy: bool = False) -> OpenGLMobject:
1484
+ self.target: OpenGLMobject | None = None # Prevent exponential explosion
1323
1485
  if use_deepcopy:
1324
1486
  self.target = self.deepcopy()
1325
1487
  else:
1326
1488
  self.target = self.copy()
1327
1489
  return self.target
1328
1490
 
1329
- def save_state(self, use_deepcopy: bool = False):
1491
+ def save_state(self, use_deepcopy: bool = False) -> Self:
1330
1492
  """Save the current state (position, color & size). Can be restored with :meth:`~.OpenGLMobject.restore`."""
1331
1493
  if hasattr(self, "saved_state"):
1332
1494
  # Prevent exponential growth of data
1333
- self.saved_state = None
1495
+ self.saved_state: OpenGLMobject | None = None
1334
1496
  if use_deepcopy:
1335
1497
  self.saved_state = self.deepcopy()
1336
1498
  else:
1337
1499
  self.saved_state = self.copy()
1338
1500
  return self
1339
1501
 
1340
- def restore(self):
1502
+ def restore(self) -> Self:
1341
1503
  """Restores the state that was previously saved with :meth:`~.OpenGLMobject.save_state`."""
1342
- if not hasattr(self, "saved_state") or self.save_state is None:
1504
+ if not hasattr(self, "saved_state") or self.saved_state is None:
1343
1505
  raise Exception("Trying to restore without having saved")
1344
1506
  self.become(self.saved_state)
1345
1507
  return self
1346
1508
 
1347
1509
  # Updating
1348
1510
 
1349
- def init_updaters(self):
1350
- self.time_based_updaters = []
1351
- self.non_time_updaters = []
1352
- self.has_updaters = False
1353
- self.updating_suspended = False
1354
-
1355
- def update(self, dt=0, recurse=True):
1356
- if not self.has_updaters or self.updating_suspended:
1357
- return self
1358
- for updater in self.time_based_updaters:
1359
- updater(self, dt)
1360
- for updater in self.non_time_updaters:
1361
- updater(self)
1511
+ def init_updaters(self) -> None:
1512
+ self.time_based_updaters: list["_TimeBasedUpdater"] = [] # noqa: UP037
1513
+ self.non_time_updaters: list["_NonTimeBasedUpdater"] = [] # noqa: UP037
1514
+ self.has_updaters: bool = False
1515
+ self.updating_suspended: bool = False
1516
+
1517
+ def update(self, dt: float = 0, recurse: bool = True) -> Self:
1518
+ if self.has_updaters and not self.updating_suspended:
1519
+ for time_based_updater in self.time_based_updaters:
1520
+ time_based_updater(self, dt)
1521
+ for non_time_updater in self.non_time_updaters:
1522
+ non_time_updater(self)
1362
1523
  if recurse:
1363
1524
  for submob in self.submobjects:
1364
1525
  submob.update(dt, recurse)
1365
1526
  return self
1366
1527
 
1367
- def get_time_based_updaters(self):
1528
+ def get_time_based_updaters(self) -> Sequence[_TimeBasedUpdater]:
1368
1529
  return self.time_based_updaters
1369
1530
 
1370
- def has_time_based_updater(self):
1531
+ def has_time_based_updater(self) -> bool:
1371
1532
  return len(self.time_based_updaters) > 0
1372
1533
 
1373
- def get_updaters(self):
1374
- return self.time_based_updaters + self.non_time_updaters
1534
+ def get_updaters(self) -> Sequence[_Updater]:
1535
+ return cast("list[_Updater]", self.time_based_updaters) + cast(
1536
+ "list[_Updater]", self.non_time_updaters
1537
+ )
1375
1538
 
1376
- def get_family_updaters(self):
1539
+ def get_family_updaters(self) -> Sequence[_Updater]:
1377
1540
  return list(it.chain(*(sm.get_updaters() for sm in self.get_family())))
1378
1541
 
1379
- def add_updater(self, update_function, index=None, call_updater=False):
1380
- if "dt" in get_parameters(update_function):
1542
+ def add_updater(
1543
+ self,
1544
+ update_function: _Updater,
1545
+ index: int | None = None,
1546
+ call_updater: bool = False,
1547
+ ) -> Self:
1548
+ updater_list: list[_TimeBasedUpdater] | list[_NonTimeBasedUpdater]
1549
+ if "dt" in inspect.signature(update_function).parameters:
1381
1550
  updater_list = self.time_based_updaters
1382
1551
  else:
1383
1552
  updater_list = self.non_time_updaters
1384
1553
 
1385
1554
  if index is None:
1386
- updater_list.append(update_function)
1555
+ cast("list[_Updater]", updater_list).append(update_function)
1387
1556
  else:
1388
- updater_list.insert(index, update_function)
1557
+ cast("list[_Updater]", updater_list).insert(index, update_function)
1389
1558
 
1390
1559
  self.refresh_has_updater_status()
1391
1560
  if call_updater:
1392
1561
  self.update()
1393
1562
  return self
1394
1563
 
1395
- def remove_updater(self, update_function):
1564
+ def remove_updater(self, update_function: _Updater) -> Self:
1396
1565
  for updater_list in [self.time_based_updaters, self.non_time_updaters]:
1566
+ updater_list = cast("list[_Updater]", updater_list)
1397
1567
  while update_function in updater_list:
1398
1568
  updater_list.remove(update_function)
1399
1569
  self.refresh_has_updater_status()
1400
1570
  return self
1401
1571
 
1402
- def clear_updaters(self, recurse=True):
1572
+ def clear_updaters(self, recurse: bool = True) -> Self:
1403
1573
  self.time_based_updaters = []
1404
1574
  self.non_time_updaters = []
1405
1575
  self.refresh_has_updater_status()
@@ -1408,20 +1578,20 @@ class OpenGLMobject:
1408
1578
  submob.clear_updaters()
1409
1579
  return self
1410
1580
 
1411
- def match_updaters(self, mobject):
1581
+ def match_updaters(self, mobject: OpenGLMobject) -> Self:
1412
1582
  self.clear_updaters()
1413
1583
  for updater in mobject.get_updaters():
1414
1584
  self.add_updater(updater)
1415
1585
  return self
1416
1586
 
1417
- def suspend_updating(self, recurse=True):
1587
+ def suspend_updating(self, recurse: bool = True) -> Self:
1418
1588
  self.updating_suspended = True
1419
1589
  if recurse:
1420
1590
  for submob in self.submobjects:
1421
1591
  submob.suspend_updating(recurse)
1422
1592
  return self
1423
1593
 
1424
- def resume_updating(self, recurse=True, call_updater=True):
1594
+ def resume_updating(self, recurse: bool = True, call_updater: bool = True) -> Self:
1425
1595
  self.updating_suspended = False
1426
1596
  if recurse:
1427
1597
  for submob in self.submobjects:
@@ -1432,13 +1602,13 @@ class OpenGLMobject:
1432
1602
  self.update(dt=0, recurse=recurse)
1433
1603
  return self
1434
1604
 
1435
- def refresh_has_updater_status(self):
1605
+ def refresh_has_updater_status(self) -> Self:
1436
1606
  self.has_updaters = any(mob.get_updaters() for mob in self.get_family())
1437
1607
  return self
1438
1608
 
1439
1609
  # Transforming operations
1440
1610
 
1441
- def shift(self, vector):
1611
+ def shift(self, vector: Vector3DLike) -> Self:
1442
1612
  self.apply_points_function(
1443
1613
  lambda points: points + vector,
1444
1614
  about_edge=None,
@@ -1449,10 +1619,10 @@ class OpenGLMobject:
1449
1619
  def scale(
1450
1620
  self,
1451
1621
  scale_factor: float,
1452
- about_point: Sequence[float] | None = None,
1453
- about_edge: Sequence[float] = ORIGIN,
1454
- **kwargs,
1455
- ) -> OpenGLMobject:
1622
+ about_point: Point3DLike | None = None,
1623
+ about_edge: Point3DLike | None = ORIGIN,
1624
+ **_kwargs: object,
1625
+ ) -> Self:
1456
1626
  r"""Scale the size by a factor.
1457
1627
 
1458
1628
  Default behavior is to scale about the center of the mobject.
@@ -1471,7 +1641,7 @@ class OpenGLMobject:
1471
1641
  if :math:`\alpha < 0`, the mobject is also flipped.
1472
1642
  kwargs
1473
1643
  Additional keyword arguments passed to
1474
- :meth:`apply_points_function_about_point`.
1644
+ :meth:`apply_points_function`.
1475
1645
 
1476
1646
  Returns
1477
1647
  -------
@@ -1504,28 +1674,27 @@ class OpenGLMobject:
1504
1674
  about_point=about_point,
1505
1675
  about_edge=about_edge,
1506
1676
  works_on_bounding_box=True,
1507
- **kwargs,
1508
1677
  )
1509
1678
  return self
1510
1679
 
1511
- def stretch(self, factor, dim, **kwargs):
1512
- def func(points):
1680
+ def stretch(self, factor: float, dim: int, **kwargs: Any) -> Self:
1681
+ def func(points: Point3D_Array) -> Point3D_Array:
1513
1682
  points[:, dim] *= factor
1514
1683
  return points
1515
1684
 
1516
1685
  self.apply_points_function(func, works_on_bounding_box=True, **kwargs)
1517
1686
  return self
1518
1687
 
1519
- def rotate_about_origin(self, angle, axis=OUT):
1688
+ def rotate_about_origin(self, angle: float, axis: Vector3DLike = OUT) -> Self:
1520
1689
  return self.rotate(angle, axis, about_point=ORIGIN)
1521
1690
 
1522
1691
  def rotate(
1523
1692
  self,
1524
- angle,
1525
- axis=OUT,
1526
- about_point: Sequence[float] | None = None,
1527
- **kwargs,
1528
- ):
1693
+ angle: float,
1694
+ axis: Vector3DLike = OUT,
1695
+ about_point: Point3DLike | None = None,
1696
+ **kwargs: Any,
1697
+ ) -> Self:
1529
1698
  """Rotates the :class:`~.OpenGLMobject` about a certain point."""
1530
1699
  rot_matrix_T = rotation_matrix_transpose(angle, axis)
1531
1700
  self.apply_points_function(
@@ -1535,7 +1704,7 @@ class OpenGLMobject:
1535
1704
  )
1536
1705
  return self
1537
1706
 
1538
- def flip(self, axis=UP, **kwargs):
1707
+ def flip(self, axis: Vector3DLike = UP, **kwargs: Any) -> Self:
1539
1708
  """Flips/Mirrors an mobject about its center.
1540
1709
 
1541
1710
  Examples
@@ -1554,25 +1723,28 @@ class OpenGLMobject:
1554
1723
  """
1555
1724
  return self.rotate(TAU / 2, axis, **kwargs)
1556
1725
 
1557
- def apply_function(self, function, **kwargs):
1726
+ def apply_function(self, function: MappingFunction, **kwargs: Any) -> Self:
1558
1727
  # Default to applying matrix about the origin, not mobjects center
1559
1728
  if len(kwargs) == 0:
1560
1729
  kwargs["about_point"] = ORIGIN
1561
- self.apply_points_function(
1562
- lambda points: np.array([function(p) for p in points]), **kwargs
1563
- )
1730
+
1731
+ def multi_mapping_function(points: Point3D_Array) -> Point3D_Array:
1732
+ result: Point3D_Array = np.apply_along_axis(function, 1, points)
1733
+ return result
1734
+
1735
+ self.apply_points_function(multi_mapping_function, **kwargs)
1564
1736
  return self
1565
1737
 
1566
- def apply_function_to_position(self, function):
1738
+ def apply_function_to_position(self, function: MappingFunction) -> Self:
1567
1739
  self.move_to(function(self.get_center()))
1568
1740
  return self
1569
1741
 
1570
- def apply_function_to_submobject_positions(self, function):
1742
+ def apply_function_to_submobject_positions(self, function: MappingFunction) -> Self:
1571
1743
  for submob in self.submobjects:
1572
1744
  submob.apply_function_to_position(function)
1573
1745
  return self
1574
1746
 
1575
- def apply_matrix(self, matrix, **kwargs):
1747
+ def apply_matrix(self, matrix: MatrixMN, **kwargs: Any) -> Self:
1576
1748
  # Default to applying matrix about the origin, not mobjects center
1577
1749
  if ("about_point" not in kwargs) and ("about_edge" not in kwargs):
1578
1750
  kwargs["about_point"] = ORIGIN
@@ -1584,7 +1756,9 @@ class OpenGLMobject:
1584
1756
  )
1585
1757
  return self
1586
1758
 
1587
- def apply_complex_function(self, function, **kwargs):
1759
+ def apply_complex_function(
1760
+ self, function: Callable[[complex], complex], **kwargs: Any
1761
+ ) -> Self:
1588
1762
  """Applies a complex function to a :class:`OpenGLMobject`.
1589
1763
  The x and y coordinates correspond to the real and imaginary parts respectively.
1590
1764
 
@@ -1611,14 +1785,14 @@ class OpenGLMobject:
1611
1785
  self.play(t.animate.set_value(TAU), run_time=3)
1612
1786
  """
1613
1787
 
1614
- def R3_func(point):
1788
+ def R3_func(point: Point3D) -> Point3D:
1615
1789
  x, y, z = point
1616
1790
  xy_complex = function(complex(x, y))
1617
- return [xy_complex.real, xy_complex.imag, z]
1791
+ return np.array([xy_complex.real, xy_complex.imag, z])
1618
1792
 
1619
- return self.apply_function(R3_func)
1793
+ return self.apply_function(R3_func, **kwargs)
1620
1794
 
1621
- def hierarchical_model_matrix(self):
1795
+ def hierarchical_model_matrix(self) -> MatrixMN:
1622
1796
  if self.parent is None:
1623
1797
  return self.model_matrix
1624
1798
 
@@ -1629,7 +1803,12 @@ class OpenGLMobject:
1629
1803
  current_object = current_object.parent
1630
1804
  return np.linalg.multi_dot(list(reversed(model_matrices)))
1631
1805
 
1632
- def wag(self, direction=RIGHT, axis=DOWN, wag_factor=1.0):
1806
+ def wag(
1807
+ self,
1808
+ direction: Vector3DLike = RIGHT,
1809
+ axis: Vector3DLike = DOWN,
1810
+ wag_factor: float = 1.0,
1811
+ ) -> Self:
1633
1812
  for mob in self.family_members_with_points():
1634
1813
  alphas = np.dot(mob.points, np.transpose(axis))
1635
1814
  alphas -= min(alphas)
@@ -1646,12 +1825,16 @@ class OpenGLMobject:
1646
1825
 
1647
1826
  # Positioning methods
1648
1827
 
1649
- def center(self):
1828
+ def center(self) -> Self:
1650
1829
  """Moves the mobject to the center of the Scene."""
1651
1830
  self.shift(-self.get_center())
1652
1831
  return self
1653
1832
 
1654
- def align_on_border(self, direction, buff=DEFAULT_MOBJECT_TO_EDGE_BUFFER):
1833
+ def align_on_border(
1834
+ self,
1835
+ direction: Vector3DLike,
1836
+ buff: float = DEFAULT_MOBJECT_TO_EDGE_BUFFER,
1837
+ ) -> Self:
1655
1838
  """
1656
1839
  Direction just needs to be a vector pointing towards side or
1657
1840
  corner in the 2d plane.
@@ -1662,27 +1845,35 @@ class OpenGLMobject:
1662
1845
  0,
1663
1846
  )
1664
1847
  point_to_align = self.get_bounding_box_point(direction)
1665
- shift_val = target_point - point_to_align - buff * np.array(direction)
1848
+ shift_val = target_point - point_to_align - buff * np.asarray(direction)
1666
1849
  shift_val = shift_val * abs(np.sign(direction))
1667
1850
  self.shift(shift_val)
1668
1851
  return self
1669
1852
 
1670
- def to_corner(self, corner=LEFT + DOWN, buff=DEFAULT_MOBJECT_TO_EDGE_BUFFER):
1853
+ def to_corner(
1854
+ self,
1855
+ corner: Vector3DLike = LEFT + DOWN,
1856
+ buff: float = DEFAULT_MOBJECT_TO_EDGE_BUFFER,
1857
+ ) -> Self:
1671
1858
  return self.align_on_border(corner, buff)
1672
1859
 
1673
- def to_edge(self, edge=LEFT, buff=DEFAULT_MOBJECT_TO_EDGE_BUFFER):
1860
+ def to_edge(
1861
+ self,
1862
+ edge: Vector3DLike = LEFT,
1863
+ buff: float = DEFAULT_MOBJECT_TO_EDGE_BUFFER,
1864
+ ) -> Self:
1674
1865
  return self.align_on_border(edge, buff)
1675
1866
 
1676
1867
  def next_to(
1677
1868
  self,
1678
- mobject_or_point,
1679
- direction=RIGHT,
1680
- buff=DEFAULT_MOBJECT_TO_MOBJECT_BUFFER,
1681
- aligned_edge=ORIGIN,
1682
- submobject_to_align=None,
1683
- index_of_submobject_to_align=None,
1684
- coor_mask=np.array([1, 1, 1]),
1685
- ):
1869
+ mobject_or_point: OpenGLMobject | Point3DLike,
1870
+ direction: Vector3DLike = RIGHT,
1871
+ buff: float = DEFAULT_MOBJECT_TO_MOBJECT_BUFFER,
1872
+ aligned_edge: Vector3DLike = ORIGIN,
1873
+ submobject_to_align: OpenGLMobject | None = None,
1874
+ index_of_submobject_to_align: int | None = None,
1875
+ coor_mask: Vector3DLike = np.array([1, 1, 1]),
1876
+ ) -> Self:
1686
1877
  """Move this :class:`~.OpenGLMobject` next to another's :class:`~.OpenGLMobject` or coordinate.
1687
1878
 
1688
1879
  Examples
@@ -1703,6 +1894,10 @@ class OpenGLMobject:
1703
1894
  self.add(d, c, s, t)
1704
1895
 
1705
1896
  """
1897
+ np_direction = np.asarray(direction)
1898
+ np_aligned_edge = np.asarray(aligned_edge)
1899
+
1900
+ target_point: Point3DLike
1706
1901
  if isinstance(mobject_or_point, OpenGLMobject):
1707
1902
  mob = mobject_or_point
1708
1903
  if index_of_submobject_to_align is not None:
@@ -1710,7 +1905,7 @@ class OpenGLMobject:
1710
1905
  else:
1711
1906
  target_aligner = mob
1712
1907
  target_point = target_aligner.get_bounding_box_point(
1713
- aligned_edge + direction,
1908
+ np_aligned_edge + np_direction,
1714
1909
  )
1715
1910
  else:
1716
1911
  target_point = mobject_or_point
@@ -1720,36 +1915,43 @@ class OpenGLMobject:
1720
1915
  aligner = self[index_of_submobject_to_align]
1721
1916
  else:
1722
1917
  aligner = self
1723
- point_to_align = aligner.get_bounding_box_point(aligned_edge - direction)
1724
- self.shift((target_point - point_to_align + buff * direction) * coor_mask)
1918
+ point_to_align = aligner.get_bounding_box_point(np_aligned_edge - np_direction)
1919
+ self.shift((target_point - point_to_align + buff * np_direction) * coor_mask)
1725
1920
  return self
1726
1921
 
1727
- def shift_onto_screen(self, **kwargs):
1728
- space_lengths = [config["frame_x_radius"], config["frame_y_radius"]]
1922
+ def shift_onto_screen(self, **kwargs: Any) -> Self:
1923
+ space_lengths: list[float] = [
1924
+ config["frame_x_radius"],
1925
+ config["frame_y_radius"],
1926
+ ]
1729
1927
  for vect in UP, DOWN, LEFT, RIGHT:
1730
1928
  dim = np.argmax(np.abs(vect))
1731
- buff = kwargs.get("buff", DEFAULT_MOBJECT_TO_EDGE_BUFFER)
1929
+ buff: float = kwargs.get("buff", DEFAULT_MOBJECT_TO_EDGE_BUFFER)
1732
1930
  max_val = space_lengths[dim] - buff
1733
1931
  edge_center = self.get_edge_center(vect)
1734
1932
  if np.dot(edge_center, vect) > max_val:
1735
- self.to_edge(vect, **kwargs)
1933
+ self.to_edge(vect, buff=buff)
1736
1934
  return self
1737
1935
 
1738
- def is_off_screen(self):
1936
+ def is_off_screen(self) -> bool:
1739
1937
  if self.get_left()[0] > config.frame_x_radius:
1740
1938
  return True
1741
1939
  if self.get_right()[0] < config.frame_x_radius:
1742
1940
  return True
1743
1941
  if self.get_bottom()[1] > config.frame_y_radius:
1744
1942
  return True
1745
- if self.get_top()[1] < -config.frame_y_radius:
1746
- return True
1747
- return False
1943
+ return cast(float, self.get_top()[1]) < -config.frame_y_radius
1748
1944
 
1749
- def stretch_about_point(self, factor, dim, point):
1945
+ def stretch_about_point(self, factor: float, dim: int, point: Point3DLike) -> Self:
1750
1946
  return self.stretch(factor, dim, about_point=point)
1751
1947
 
1752
- def rescale_to_fit(self, length, dim, stretch=False, **kwargs):
1948
+ def rescale_to_fit(
1949
+ self,
1950
+ length: float,
1951
+ dim: int,
1952
+ stretch: bool = False,
1953
+ **kwargs: Any,
1954
+ ) -> Self:
1753
1955
  old_length = self.length_over_dim(dim)
1754
1956
  if old_length == 0:
1755
1957
  return self
@@ -1759,7 +1961,7 @@ class OpenGLMobject:
1759
1961
  self.scale(length / old_length, **kwargs)
1760
1962
  return self
1761
1963
 
1762
- def stretch_to_fit_width(self, width, **kwargs):
1964
+ def stretch_to_fit_width(self, width: float, **kwargs: Any) -> Self:
1763
1965
  """Stretches the :class:`~.OpenGLMobject` to fit a width, not keeping height/depth proportional.
1764
1966
 
1765
1967
  Returns
@@ -1772,27 +1974,33 @@ class OpenGLMobject:
1772
1974
  ::
1773
1975
 
1774
1976
  >>> from manim import *
1977
+ >>> import numpy as np
1775
1978
  >>> sq = Square()
1776
1979
  >>> sq.height
1777
- 2.0
1980
+ np.float64(2.0)
1778
1981
  >>> sq.stretch_to_fit_width(5)
1779
1982
  Square
1780
1983
  >>> sq.width
1781
- 5.0
1984
+ np.float64(5.0)
1782
1985
  >>> sq.height
1783
- 2.0
1986
+ np.float64(2.0)
1784
1987
  """
1785
1988
  return self.rescale_to_fit(width, 0, stretch=True, **kwargs)
1786
1989
 
1787
- def stretch_to_fit_height(self, height, **kwargs):
1990
+ def stretch_to_fit_height(self, height: float, **kwargs: Any) -> Self:
1788
1991
  """Stretches the :class:`~.OpenGLMobject` to fit a height, not keeping width/height proportional."""
1789
1992
  return self.rescale_to_fit(height, 1, stretch=True, **kwargs)
1790
1993
 
1791
- def stretch_to_fit_depth(self, depth, **kwargs):
1994
+ def stretch_to_fit_depth(self, depth: float, **kwargs: Any) -> Self:
1792
1995
  """Stretches the :class:`~.OpenGLMobject` to fit a depth, not keeping width/height proportional."""
1793
1996
  return self.rescale_to_fit(depth, 1, stretch=True, **kwargs)
1794
1997
 
1795
- def set_width(self, width, stretch=False, **kwargs):
1998
+ def set_width(
1999
+ self,
2000
+ width: float,
2001
+ stretch: bool = False,
2002
+ **kwargs: Any,
2003
+ ) -> Self:
1796
2004
  """Scales the :class:`~.OpenGLMobject` to fit a width while keeping height/depth proportional.
1797
2005
 
1798
2006
  Returns
@@ -1805,52 +2013,65 @@ class OpenGLMobject:
1805
2013
  ::
1806
2014
 
1807
2015
  >>> from manim import *
2016
+ >>> import numpy as np
1808
2017
  >>> sq = Square()
1809
2018
  >>> sq.height
1810
- 2.0
2019
+ np.float64(2.0)
1811
2020
  >>> sq.scale_to_fit_width(5)
1812
2021
  Square
1813
2022
  >>> sq.width
1814
- 5.0
2023
+ np.float64(5.0)
1815
2024
  >>> sq.height
1816
- 5.0
2025
+ np.float64(5.0)
1817
2026
  """
1818
2027
  return self.rescale_to_fit(width, 0, stretch=stretch, **kwargs)
1819
2028
 
1820
2029
  scale_to_fit_width = set_width
1821
2030
 
1822
- def set_height(self, height, stretch=False, **kwargs):
2031
+ def set_height(
2032
+ self,
2033
+ height: float,
2034
+ stretch: bool = False,
2035
+ **kwargs: Any,
2036
+ ) -> Self:
1823
2037
  """Scales the :class:`~.OpenGLMobject` to fit a height while keeping width/depth proportional."""
1824
2038
  return self.rescale_to_fit(height, 1, stretch=stretch, **kwargs)
1825
2039
 
1826
2040
  scale_to_fit_height = set_height
1827
2041
 
1828
- def set_depth(self, depth, stretch=False, **kwargs):
2042
+ def set_depth(
2043
+ self,
2044
+ depth: float,
2045
+ stretch: bool = False,
2046
+ **kwargs: Any,
2047
+ ) -> Self:
1829
2048
  """Scales the :class:`~.OpenGLMobject` to fit a depth while keeping width/height proportional."""
1830
2049
  return self.rescale_to_fit(depth, 2, stretch=stretch, **kwargs)
1831
2050
 
1832
2051
  scale_to_fit_depth = set_depth
1833
2052
 
1834
- def set_coord(self, value, dim, direction=ORIGIN):
2053
+ def set_coord(
2054
+ self, value: float, dim: int, direction: Vector3DLike = ORIGIN
2055
+ ) -> Self:
1835
2056
  curr = self.get_coord(dim, direction)
1836
2057
  shift_vect = np.zeros(self.dim)
1837
2058
  shift_vect[dim] = value - curr
1838
2059
  self.shift(shift_vect)
1839
2060
  return self
1840
2061
 
1841
- def set_x(self, x, direction=ORIGIN):
2062
+ def set_x(self, x: float, direction: Vector3DLike = ORIGIN) -> Self:
1842
2063
  """Set x value of the center of the :class:`~.OpenGLMobject` (``int`` or ``float``)"""
1843
2064
  return self.set_coord(x, 0, direction)
1844
2065
 
1845
- def set_y(self, y, direction=ORIGIN):
2066
+ def set_y(self, y: float, direction: Vector3DLike = ORIGIN) -> Self:
1846
2067
  """Set y value of the center of the :class:`~.OpenGLMobject` (``int`` or ``float``)"""
1847
2068
  return self.set_coord(y, 1, direction)
1848
2069
 
1849
- def set_z(self, z, direction=ORIGIN):
2070
+ def set_z(self, z: float, direction: Vector3DLike = ORIGIN) -> Self:
1850
2071
  """Set z value of the center of the :class:`~.OpenGLMobject` (``int`` or ``float``)"""
1851
2072
  return self.set_coord(z, 2, direction)
1852
2073
 
1853
- def space_out_submobjects(self, factor=1.5, **kwargs):
2074
+ def space_out_submobjects(self, factor: float = 1.5, **kwargs: Any) -> Self:
1854
2075
  self.scale(factor, **kwargs)
1855
2076
  for submob in self.submobjects:
1856
2077
  submob.scale(1.0 / factor)
@@ -1858,11 +2079,12 @@ class OpenGLMobject:
1858
2079
 
1859
2080
  def move_to(
1860
2081
  self,
1861
- point_or_mobject,
1862
- aligned_edge=ORIGIN,
1863
- coor_mask=np.array([1, 1, 1]),
1864
- ):
2082
+ point_or_mobject: Point3DLike | OpenGLMobject,
2083
+ aligned_edge: Vector3DLike = ORIGIN,
2084
+ coor_mask: Vector3DLike = np.array([1, 1, 1]),
2085
+ ) -> Self:
1865
2086
  """Move center of the :class:`~.OpenGLMobject` to certain coordinate."""
2087
+ target: Point3DLike
1866
2088
  if isinstance(point_or_mobject, OpenGLMobject):
1867
2089
  target = point_or_mobject.get_bounding_box_point(aligned_edge)
1868
2090
  else:
@@ -1871,7 +2093,12 @@ class OpenGLMobject:
1871
2093
  self.shift((target - point_to_align) * coor_mask)
1872
2094
  return self
1873
2095
 
1874
- def replace(self, mobject, dim_to_match=0, stretch=False):
2096
+ def replace(
2097
+ self,
2098
+ mobject: OpenGLMobject,
2099
+ dim_to_match: int = 0,
2100
+ stretch: bool = False,
2101
+ ) -> Self:
1875
2102
  if not mobject.get_num_points() and not mobject.submobjects:
1876
2103
  self.scale(0)
1877
2104
  return self
@@ -1893,13 +2120,13 @@ class OpenGLMobject:
1893
2120
  dim_to_match: int = 0,
1894
2121
  stretch: bool = False,
1895
2122
  buff: float = MED_SMALL_BUFF,
1896
- ):
2123
+ ) -> Self:
1897
2124
  self.replace(mobject, dim_to_match, stretch)
1898
2125
  length = mobject.length_over_dim(dim_to_match)
1899
2126
  self.scale((length + buff) / length)
1900
2127
  return self
1901
2128
 
1902
- def put_start_and_end_on(self, start, end):
2129
+ def put_start_and_end_on(self, start: Point3DLike, end: Point3DLike) -> Self:
1903
2130
  curr_start, curr_end = self.get_start_and_end()
1904
2131
  curr_vect = curr_end - curr_start
1905
2132
  if np.all(curr_vect == 0):
@@ -1911,7 +2138,7 @@ class OpenGLMobject:
1911
2138
  else OUT
1912
2139
  )
1913
2140
  self.scale(
1914
- np.linalg.norm(target_vect) / np.linalg.norm(curr_vect),
2141
+ float(np.linalg.norm(target_vect) / np.linalg.norm(curr_vect)),
1915
2142
  about_point=curr_start,
1916
2143
  )
1917
2144
  self.rotate(
@@ -1924,9 +2151,15 @@ class OpenGLMobject:
1924
2151
 
1925
2152
  # Color functions
1926
2153
 
1927
- def set_rgba_array(self, color=None, opacity=None, name="rgbas", recurse=True):
2154
+ def set_rgba_array(
2155
+ self,
2156
+ color: ParsableManimColor | Iterable[ParsableManimColor] | None = None,
2157
+ opacity: float | Iterable[float] | None = None,
2158
+ name: str = "rgbas",
2159
+ recurse: bool = True,
2160
+ ) -> Self:
1928
2161
  if color is not None:
1929
- rgbs = np.array([color_to_rgb(c) for c in listify(color)])
2162
+ rgbs: FloatRGB_Array = np.array([color_to_rgb(c) for c in listify(color)])
1930
2163
  if opacity is not None:
1931
2164
  opacities = listify(opacity)
1932
2165
 
@@ -1949,12 +2182,19 @@ class OpenGLMobject:
1949
2182
 
1950
2183
  # Color and opacity
1951
2184
  if color is not None and opacity is not None:
1952
- rgbas = np.array([[*rgb, o] for rgb, o in zip(*make_even(rgbs, opacities))])
2185
+ rgbas: FloatRGBA_Array = np.array(
2186
+ [[*rgb, o] for rgb, o in zip(*make_even(rgbs, opacities))]
2187
+ )
1953
2188
  for mob in self.get_family(recurse):
1954
2189
  mob.data[name] = rgbas.copy()
1955
2190
  return self
1956
2191
 
1957
- def set_rgba_array_direct(self, rgbas: np.ndarray, name="rgbas", recurse=True):
2192
+ def set_rgba_array_direct(
2193
+ self,
2194
+ rgbas: FloatRGBA_Array,
2195
+ name: str = "rgbas",
2196
+ recurse: bool = True,
2197
+ ) -> Self:
1958
2198
  """Directly set rgba data from `rgbas` and optionally do the same recursively
1959
2199
  with submobjects. This can be used if the `rgbas` have already been generated
1960
2200
  with the correct shape and simply need to be set.
@@ -1970,13 +2210,19 @@ class OpenGLMobject:
1970
2210
  """
1971
2211
  for mob in self.get_family(recurse):
1972
2212
  mob.data[name] = rgbas.copy()
2213
+ return self
1973
2214
 
1974
- def set_color(self, color, opacity=None, recurse=True):
2215
+ def set_color(
2216
+ self,
2217
+ color: ParsableManimColor | Sequence[ParsableManimColor] | None,
2218
+ opacity: float | Iterable[float] | None = None,
2219
+ recurse: bool = True,
2220
+ ) -> Self:
1975
2221
  self.set_rgba_array(color, opacity, recurse=False)
1976
2222
  # Recurse to submobjects differently from how set_rgba_array
1977
2223
  # in case they implement set_color differently
1978
2224
  if color is not None:
1979
- self.color = Color(color)
2225
+ self.color = ManimColor.parse(color)
1980
2226
  if opacity is not None:
1981
2227
  self.opacity = opacity
1982
2228
  if recurse:
@@ -1984,24 +2230,26 @@ class OpenGLMobject:
1984
2230
  submob.set_color(color, recurse=True)
1985
2231
  return self
1986
2232
 
1987
- def set_opacity(self, opacity, recurse=True):
2233
+ def set_opacity(
2234
+ self, opacity: float | Iterable[float] | None, recurse: bool = True
2235
+ ) -> Self:
1988
2236
  self.set_rgba_array(color=None, opacity=opacity, recurse=False)
1989
2237
  if recurse:
1990
2238
  for submob in self.submobjects:
1991
2239
  submob.set_opacity(opacity, recurse=True)
1992
2240
  return self
1993
2241
 
1994
- def get_color(self):
2242
+ def get_color(self) -> str:
1995
2243
  return rgb_to_hex(self.rgbas[0, :3])
1996
2244
 
1997
- def get_opacity(self):
1998
- return self.rgbas[0, 3]
2245
+ def get_opacity(self) -> float:
2246
+ rv: float = self.rgbas[0, 3]
2247
+ return rv
1999
2248
 
2000
- def set_color_by_gradient(self, *colors):
2001
- self.set_submobject_colors_by_gradient(*colors)
2002
- return self
2249
+ def set_color_by_gradient(self, *colors: ParsableManimColor) -> Self:
2250
+ return self.set_submobject_colors_by_gradient(*colors)
2003
2251
 
2004
- def set_submobject_colors_by_gradient(self, *colors):
2252
+ def set_submobject_colors_by_gradient(self, *colors: ParsableManimColor) -> Self:
2005
2253
  if len(colors) == 0:
2006
2254
  raise Exception("Need at least one color")
2007
2255
  elif len(colors) == 1:
@@ -2015,21 +2263,21 @@ class OpenGLMobject:
2015
2263
  mob.set_color(color)
2016
2264
  return self
2017
2265
 
2018
- def fade(self, darkness=0.5, recurse=True):
2019
- self.set_opacity(1.0 - darkness, recurse=recurse)
2266
+ def fade(self, darkness: float = 0.5, recurse: bool = True) -> Self:
2267
+ return self.set_opacity(1.0 - darkness, recurse=recurse)
2020
2268
 
2021
- def get_gloss(self):
2269
+ def get_gloss(self) -> float:
2022
2270
  return self.gloss
2023
2271
 
2024
- def set_gloss(self, gloss, recurse=True):
2272
+ def set_gloss(self, gloss: float, recurse: bool = True) -> Self:
2025
2273
  for mob in self.get_family(recurse):
2026
2274
  mob.gloss = gloss
2027
2275
  return self
2028
2276
 
2029
- def get_shadow(self):
2277
+ def get_shadow(self) -> float:
2030
2278
  return self.shadow
2031
2279
 
2032
- def set_shadow(self, shadow, recurse=True):
2280
+ def set_shadow(self, shadow: float, recurse: bool = True) -> Self:
2033
2281
  for mob in self.get_family(recurse):
2034
2282
  mob.shadow = shadow
2035
2283
  return self
@@ -2037,8 +2285,11 @@ class OpenGLMobject:
2037
2285
  # Background rectangle
2038
2286
 
2039
2287
  def add_background_rectangle(
2040
- self, color: Colors | None = None, opacity: float = 0.75, **kwargs
2041
- ):
2288
+ self,
2289
+ color: ParsableManimColor | None = None,
2290
+ opacity: float = 0.75,
2291
+ **kwargs: Any,
2292
+ ) -> Self:
2042
2293
  # TODO, this does not behave well when the mobject has points,
2043
2294
  # since it gets displayed on top
2044
2295
  """Add a BackgroundRectangle as submobject.
@@ -2070,146 +2321,153 @@ class OpenGLMobject:
2070
2321
  """
2071
2322
  from manim.mobject.geometry.shape_matchers import BackgroundRectangle
2072
2323
 
2073
- self.background_rectangle = BackgroundRectangle(
2074
- self, color=color, fill_opacity=opacity, **kwargs
2324
+ self.background_rectangle: BackgroundRectangle = BackgroundRectangle(
2325
+ self, # type: ignore[arg-type]
2326
+ color=color,
2327
+ fill_opacity=opacity,
2328
+ **kwargs,
2075
2329
  )
2076
- self.add_to_back(self.background_rectangle)
2330
+ self.add_to_back(self.background_rectangle) # type: ignore[arg-type]
2077
2331
  return self
2078
2332
 
2079
- def add_background_rectangle_to_submobjects(self, **kwargs):
2333
+ def add_background_rectangle_to_submobjects(self, **kwargs: Any) -> Self:
2080
2334
  for submobject in self.submobjects:
2081
2335
  submobject.add_background_rectangle(**kwargs)
2082
2336
  return self
2083
2337
 
2084
- def add_background_rectangle_to_family_members_with_points(self, **kwargs):
2338
+ def add_background_rectangle_to_family_members_with_points(
2339
+ self, **kwargs: Any
2340
+ ) -> Self:
2085
2341
  for mob in self.family_members_with_points():
2086
2342
  mob.add_background_rectangle(**kwargs)
2087
2343
  return self
2088
2344
 
2089
2345
  # Getters
2090
2346
 
2091
- def get_bounding_box_point(self, direction):
2347
+ def get_bounding_box_point(self, direction: Vector3DLike) -> Point3D:
2092
2348
  bb = self.get_bounding_box()
2093
2349
  indices = (np.sign(direction) + 1).astype(int)
2094
2350
  return np.array([bb[indices[i]][i] for i in range(3)])
2095
2351
 
2096
- def get_edge_center(self, direction) -> np.ndarray:
2352
+ def get_edge_center(self, direction: Vector3DLike) -> Point3D:
2097
2353
  """Get edge coordinates for certain direction."""
2098
2354
  return self.get_bounding_box_point(direction)
2099
2355
 
2100
- def get_corner(self, direction) -> np.ndarray:
2356
+ def get_corner(self, direction: Vector3DLike) -> Point3D:
2101
2357
  """Get corner coordinates for certain direction."""
2102
2358
  return self.get_bounding_box_point(direction)
2103
2359
 
2104
- def get_center(self) -> np.ndarray:
2360
+ def get_center(self) -> Point3D:
2105
2361
  """Get center coordinates."""
2106
2362
  return self.get_bounding_box()[1]
2107
2363
 
2108
- def get_center_of_mass(self):
2364
+ def get_center_of_mass(self) -> Point3D:
2109
2365
  return self.get_all_points().mean(0)
2110
2366
 
2111
- def get_boundary_point(self, direction):
2367
+ def get_boundary_point(self, direction: Vector3DLike) -> Point3D:
2112
2368
  all_points = self.get_all_points()
2113
2369
  boundary_directions = all_points - self.get_center()
2114
2370
  norms = np.linalg.norm(boundary_directions, axis=1)
2115
2371
  boundary_directions /= np.repeat(norms, 3).reshape((len(norms), 3))
2116
- index = np.argmax(np.dot(boundary_directions, np.array(direction).T))
2372
+ index = np.argmax(np.dot(boundary_directions, direction))
2117
2373
  return all_points[index]
2118
2374
 
2119
- def get_continuous_bounding_box_point(self, direction):
2120
- dl, center, ur = self.get_bounding_box()
2375
+ def get_continuous_bounding_box_point(self, direction: Vector3DLike) -> Point3D:
2376
+ _dl, center, ur = self.get_bounding_box()
2121
2377
  corner_vect = ur - center
2122
- return center + direction / np.max(
2378
+ np_direction = np.asarray(direction)
2379
+ return center + np_direction / np.max(
2123
2380
  np.abs(
2124
2381
  np.true_divide(
2125
- direction,
2382
+ np_direction,
2126
2383
  corner_vect,
2127
- out=np.zeros(len(direction)),
2384
+ out=np.zeros(len(np_direction)),
2128
2385
  where=((corner_vect) != 0),
2129
2386
  ),
2130
2387
  ),
2131
2388
  )
2132
2389
 
2133
- def get_top(self) -> np.ndarray:
2390
+ def get_top(self) -> Point3D:
2134
2391
  """Get top coordinates of a box bounding the :class:`~.OpenGLMobject`"""
2135
2392
  return self.get_edge_center(UP)
2136
2393
 
2137
- def get_bottom(self) -> np.ndarray:
2394
+ def get_bottom(self) -> Point3D:
2138
2395
  """Get bottom coordinates of a box bounding the :class:`~.OpenGLMobject`"""
2139
2396
  return self.get_edge_center(DOWN)
2140
2397
 
2141
- def get_right(self) -> np.ndarray:
2398
+ def get_right(self) -> Point3D:
2142
2399
  """Get right coordinates of a box bounding the :class:`~.OpenGLMobject`"""
2143
2400
  return self.get_edge_center(RIGHT)
2144
2401
 
2145
- def get_left(self) -> np.ndarray:
2402
+ def get_left(self) -> Point3D:
2146
2403
  """Get left coordinates of a box bounding the :class:`~.OpenGLMobject`"""
2147
2404
  return self.get_edge_center(LEFT)
2148
2405
 
2149
- def get_zenith(self) -> np.ndarray:
2406
+ def get_zenith(self) -> Point3D:
2150
2407
  """Get zenith coordinates of a box bounding a 3D :class:`~.OpenGLMobject`."""
2151
2408
  return self.get_edge_center(OUT)
2152
2409
 
2153
- def get_nadir(self) -> np.ndarray:
2410
+ def get_nadir(self) -> Point3D:
2154
2411
  """Get nadir (opposite the zenith) coordinates of a box bounding a 3D :class:`~.OpenGLMobject`."""
2155
2412
  return self.get_edge_center(IN)
2156
2413
 
2157
- def length_over_dim(self, dim):
2414
+ def length_over_dim(self, dim: int) -> float:
2158
2415
  bb = self.get_bounding_box()
2159
- return abs((bb[2] - bb[0])[dim])
2416
+ rv: float = abs((bb[2] - bb[0])[dim])
2417
+ return rv
2160
2418
 
2161
- def get_width(self):
2419
+ def get_width(self) -> float:
2162
2420
  """Returns the width of the mobject."""
2163
2421
  return self.length_over_dim(0)
2164
2422
 
2165
- def get_height(self):
2423
+ def get_height(self) -> float:
2166
2424
  """Returns the height of the mobject."""
2167
2425
  return self.length_over_dim(1)
2168
2426
 
2169
- def get_depth(self):
2427
+ def get_depth(self) -> float:
2170
2428
  """Returns the depth of the mobject."""
2171
2429
  return self.length_over_dim(2)
2172
2430
 
2173
- def get_coord(self, dim: int, direction=ORIGIN):
2431
+ def get_coord(self, dim: int, direction: Vector3DLike = ORIGIN) -> ManimFloat:
2174
2432
  """Meant to generalize ``get_x``, ``get_y`` and ``get_z``"""
2175
2433
  return self.get_bounding_box_point(direction)[dim]
2176
2434
 
2177
- def get_x(self, direction=ORIGIN) -> np.float64:
2435
+ def get_x(self, direction: Vector3DLike = ORIGIN) -> ManimFloat:
2178
2436
  """Returns x coordinate of the center of the :class:`~.OpenGLMobject` as ``float``"""
2179
2437
  return self.get_coord(0, direction)
2180
2438
 
2181
- def get_y(self, direction=ORIGIN) -> np.float64:
2439
+ def get_y(self, direction: Vector3DLike = ORIGIN) -> ManimFloat:
2182
2440
  """Returns y coordinate of the center of the :class:`~.OpenGLMobject` as ``float``"""
2183
2441
  return self.get_coord(1, direction)
2184
2442
 
2185
- def get_z(self, direction=ORIGIN) -> np.float64:
2443
+ def get_z(self, direction: Vector3DLike = ORIGIN) -> ManimFloat:
2186
2444
  """Returns z coordinate of the center of the :class:`~.OpenGLMobject` as ``float``"""
2187
2445
  return self.get_coord(2, direction)
2188
2446
 
2189
- def get_start(self):
2447
+ def get_start(self) -> Point3D:
2190
2448
  """Returns the point, where the stroke that surrounds the :class:`~.OpenGLMobject` starts."""
2191
2449
  self.throw_error_if_no_points()
2192
2450
  return np.array(self.points[0])
2193
2451
 
2194
- def get_end(self):
2452
+ def get_end(self) -> Point3D:
2195
2453
  """Returns the point, where the stroke that surrounds the :class:`~.OpenGLMobject` ends."""
2196
2454
  self.throw_error_if_no_points()
2197
2455
  return np.array(self.points[-1])
2198
2456
 
2199
- def get_start_and_end(self):
2457
+ def get_start_and_end(self) -> tuple[Point3D, Point3D]:
2200
2458
  """Returns starting and ending point of a stroke as a ``tuple``."""
2201
2459
  return self.get_start(), self.get_end()
2202
2460
 
2203
- def point_from_proportion(self, alpha):
2461
+ def point_from_proportion(self, alpha: float) -> Point3D:
2204
2462
  points = self.points
2205
2463
  i, subalpha = integer_interpolate(0, len(points) - 1, alpha)
2206
2464
  return interpolate(points[i], points[i + 1], subalpha)
2207
2465
 
2208
- def pfp(self, alpha):
2466
+ def pfp(self, alpha: float) -> Point3D:
2209
2467
  """Abbreviation for point_from_proportion"""
2210
2468
  return self.point_from_proportion(alpha)
2211
2469
 
2212
- def get_pieces(self, n_pieces):
2470
+ def get_pieces(self, n_pieces: int) -> OpenGLMobject:
2213
2471
  template = self.copy()
2214
2472
  template.submobjects = []
2215
2473
  alphas = np.linspace(0, 1, n_pieces + 1)
@@ -2220,34 +2478,41 @@ class OpenGLMobject:
2220
2478
  )
2221
2479
  )
2222
2480
 
2223
- def get_z_index_reference_point(self):
2481
+ def get_z_index_reference_point(self) -> Point3D:
2224
2482
  # TODO, better place to define default z_index_group?
2225
2483
  z_index_group = getattr(self, "z_index_group", self)
2226
2484
  return z_index_group.get_center()
2227
2485
 
2228
2486
  # Match other mobject properties
2229
2487
 
2230
- def match_color(self, mobject: OpenGLMobject):
2488
+ def match_color(self, mobject: OpenGLMobject) -> Self:
2231
2489
  """Match the color with the color of another :class:`~.OpenGLMobject`."""
2232
2490
  return self.set_color(mobject.get_color())
2233
2491
 
2234
- def match_dim_size(self, mobject: OpenGLMobject, dim, **kwargs):
2492
+ def match_dim_size(
2493
+ self,
2494
+ mobject: OpenGLMobject,
2495
+ dim: int,
2496
+ **kwargs: Any,
2497
+ ) -> Self:
2235
2498
  """Match the specified dimension with the dimension of another :class:`~.OpenGLMobject`."""
2236
2499
  return self.rescale_to_fit(mobject.length_over_dim(dim), dim, **kwargs)
2237
2500
 
2238
- def match_width(self, mobject: OpenGLMobject, **kwargs):
2501
+ def match_width(self, mobject: OpenGLMobject, **kwargs: Any) -> Self:
2239
2502
  """Match the width with the width of another :class:`~.OpenGLMobject`."""
2240
2503
  return self.match_dim_size(mobject, 0, **kwargs)
2241
2504
 
2242
- def match_height(self, mobject: OpenGLMobject, **kwargs):
2505
+ def match_height(self, mobject: OpenGLMobject, **kwargs: Any) -> Self:
2243
2506
  """Match the height with the height of another :class:`~.OpenGLMobject`."""
2244
2507
  return self.match_dim_size(mobject, 1, **kwargs)
2245
2508
 
2246
- def match_depth(self, mobject: OpenGLMobject, **kwargs):
2509
+ def match_depth(self, mobject: OpenGLMobject, **kwargs: Any) -> Self:
2247
2510
  """Match the depth with the depth of another :class:`~.OpenGLMobject`."""
2248
2511
  return self.match_dim_size(mobject, 2, **kwargs)
2249
2512
 
2250
- def match_coord(self, mobject: OpenGLMobject, dim, direction=ORIGIN):
2513
+ def match_coord(
2514
+ self, mobject: OpenGLMobject, dim: int, direction: Vector3DLike = ORIGIN
2515
+ ) -> Self:
2251
2516
  """Match the coordinates with the coordinates of another :class:`~.OpenGLMobject`."""
2252
2517
  return self.set_coord(
2253
2518
  mobject.get_coord(dim, direction),
@@ -2255,23 +2520,23 @@ class OpenGLMobject:
2255
2520
  direction=direction,
2256
2521
  )
2257
2522
 
2258
- def match_x(self, mobject, direction=ORIGIN):
2523
+ def match_x(self, mobject: OpenGLMobject, direction: Vector3DLike = ORIGIN) -> Self:
2259
2524
  """Match x coord. to the x coord. of another :class:`~.OpenGLMobject`."""
2260
2525
  return self.match_coord(mobject, 0, direction)
2261
2526
 
2262
- def match_y(self, mobject, direction=ORIGIN):
2527
+ def match_y(self, mobject: OpenGLMobject, direction: Vector3DLike = ORIGIN) -> Self:
2263
2528
  """Match y coord. to the x coord. of another :class:`~.OpenGLMobject`."""
2264
2529
  return self.match_coord(mobject, 1, direction)
2265
2530
 
2266
- def match_z(self, mobject, direction=ORIGIN):
2531
+ def match_z(self, mobject: OpenGLMobject, direction: Vector3DLike = ORIGIN) -> Self:
2267
2532
  """Match z coord. to the x coord. of another :class:`~.OpenGLMobject`."""
2268
2533
  return self.match_coord(mobject, 2, direction)
2269
2534
 
2270
2535
  def align_to(
2271
2536
  self,
2272
- mobject_or_point: OpenGLMobject | Sequence[float],
2273
- direction=ORIGIN,
2274
- ):
2537
+ mobject_or_point: OpenGLMobject | Point3DLike,
2538
+ direction: Vector3DLike = ORIGIN,
2539
+ ) -> Self:
2275
2540
  """
2276
2541
  Examples:
2277
2542
  mob1.align_to(mob2, UP) moves mob1 vertically so that its
@@ -2281,6 +2546,7 @@ class OpenGLMobject:
2281
2546
  horizontally so that it's center is directly above/below
2282
2547
  the center of mob2
2283
2548
  """
2549
+ point: Point3DLike
2284
2550
  if isinstance(mobject_or_point, OpenGLMobject):
2285
2551
  point = mobject_or_point.get_bounding_box_point(direction)
2286
2552
  else:
@@ -2291,21 +2557,22 @@ class OpenGLMobject:
2291
2557
  self.set_coord(point[dim], dim, direction)
2292
2558
  return self
2293
2559
 
2294
- def get_group_class(self):
2560
+ def get_group_class(self) -> type[OpenGLGroup]:
2295
2561
  return OpenGLGroup
2296
2562
 
2297
2563
  @staticmethod
2298
- def get_mobject_type_class():
2564
+ def get_mobject_type_class() -> type[OpenGLMobject]:
2299
2565
  """Return the base class of this mobject type."""
2300
2566
  return OpenGLMobject
2301
2567
 
2302
2568
  # Alignment
2303
2569
 
2304
- def align_data_and_family(self, mobject):
2570
+ def align_data_and_family(self, mobject: OpenGLMobject) -> Self:
2305
2571
  self.align_family(mobject)
2306
2572
  self.align_data(mobject)
2573
+ return self
2307
2574
 
2308
- def align_data(self, mobject):
2575
+ def align_data(self, mobject: OpenGLMobject) -> Self:
2309
2576
  # In case any data arrays get resized when aligned to shader data
2310
2577
  # self.refresh_shader_data()
2311
2578
  for mob1, mob2 in zip(self.get_family(), mobject.get_family()):
@@ -2321,14 +2588,15 @@ class OpenGLMobject:
2321
2588
  mob1.data[key] = resize_preserving_order(arr1, len(arr2))
2322
2589
  elif len(arr1) > len(arr2):
2323
2590
  mob2.data[key] = resize_preserving_order(arr2, len(arr1))
2591
+ return self
2324
2592
 
2325
- def align_points(self, mobject):
2593
+ def align_points(self, mobject: OpenGLMobject) -> Self:
2326
2594
  max_len = max(self.get_num_points(), mobject.get_num_points())
2327
2595
  for mob in (self, mobject):
2328
2596
  mob.resize_points(max_len, resize_func=resize_preserving_order)
2329
2597
  return self
2330
2598
 
2331
- def align_family(self, mobject):
2599
+ def align_family(self, mobject: OpenGLMobject) -> Self:
2332
2600
  mob1 = self
2333
2601
  mob2 = mobject
2334
2602
  n1 = len(mob1)
@@ -2341,14 +2609,14 @@ class OpenGLMobject:
2341
2609
  sm1.align_family(sm2)
2342
2610
  return self
2343
2611
 
2344
- def push_self_into_submobjects(self):
2612
+ def push_self_into_submobjects(self) -> Self:
2345
2613
  copy = self.deepcopy()
2346
2614
  copy.submobjects = []
2347
2615
  self.resize_points(0)
2348
2616
  self.add(copy)
2349
2617
  return self
2350
2618
 
2351
- def add_n_more_submobjects(self, n):
2619
+ def add_n_more_submobjects(self, n: int) -> Self:
2352
2620
  if n == 0:
2353
2621
  return self
2354
2622
 
@@ -2377,7 +2645,13 @@ class OpenGLMobject:
2377
2645
 
2378
2646
  # Interpolate
2379
2647
 
2380
- def interpolate(self, mobject1, mobject2, alpha, path_func=straight_path()):
2648
+ def interpolate(
2649
+ self,
2650
+ mobject1: OpenGLMobject,
2651
+ mobject2: OpenGLMobject,
2652
+ alpha: float,
2653
+ path_func: PathFuncType = straight_path(),
2654
+ ) -> Self:
2381
2655
  """Turns this :class:`~.OpenGLMobject` into an interpolation between ``mobject1``
2382
2656
  and ``mobject2``.
2383
2657
 
@@ -2406,10 +2680,7 @@ class OpenGLMobject:
2406
2680
  if key not in mobject1.data or key not in mobject2.data:
2407
2681
  continue
2408
2682
 
2409
- if key in ("points", "bounding_box"):
2410
- func = path_func
2411
- else:
2412
- func = interpolate
2683
+ func = path_func if key in ("points", "bounding_box") else interpolate
2413
2684
 
2414
2685
  self.data[key][:] = func(mobject1.data[key], mobject2.data[key], alpha)
2415
2686
 
@@ -2430,14 +2701,18 @@ class OpenGLMobject:
2430
2701
  )
2431
2702
  return self
2432
2703
 
2433
- def pointwise_become_partial(self, mobject, a, b):
2704
+ def pointwise_become_partial(
2705
+ self, mobject: OpenGLMobject, a: float, b: float
2706
+ ) -> Self:
2434
2707
  """
2435
2708
  Set points in such a way as to become only
2436
2709
  part of mobject.
2437
2710
  Inputs 0 <= a < b <= 1 determine what portion
2438
2711
  of mobject to become.
2712
+
2713
+ Returns `self` to allow method chaining.
2439
2714
  """
2440
- pass # To implement in subclass
2715
+ return self # To implement in subclass
2441
2716
 
2442
2717
  def become(
2443
2718
  self,
@@ -2447,7 +2722,7 @@ class OpenGLMobject:
2447
2722
  match_depth: bool = False,
2448
2723
  match_center: bool = False,
2449
2724
  stretch: bool = False,
2450
- ):
2725
+ ) -> Self:
2451
2726
  """Edit all data and submobjects to be identical
2452
2727
  to another :class:`~.OpenGLMobject`
2453
2728
 
@@ -2482,7 +2757,6 @@ class OpenGLMobject:
2482
2757
  circ.become(square)
2483
2758
  self.wait(0.5)
2484
2759
  """
2485
-
2486
2760
  if stretch:
2487
2761
  mobject.stretch_to_fit_height(self.height)
2488
2762
  mobject.stretch_to_fit_width(self.width)
@@ -2507,7 +2781,7 @@ class OpenGLMobject:
2507
2781
 
2508
2782
  # Locking data
2509
2783
 
2510
- def lock_data(self, keys):
2784
+ def lock_data(self, keys: Iterable[str]) -> None:
2511
2785
  """
2512
2786
  To speed up some animations, particularly transformations,
2513
2787
  it can be handy to acknowledge which pieces of data
@@ -2521,7 +2795,9 @@ class OpenGLMobject:
2521
2795
  self.refresh_shader_data()
2522
2796
  self.locked_data_keys = set(keys)
2523
2797
 
2524
- def lock_matching_data(self, mobject1, mobject2):
2798
+ def lock_matching_data(
2799
+ self, mobject1: OpenGLMobject, mobject2: OpenGLMobject
2800
+ ) -> Self:
2525
2801
  for sm, sm1, sm2 in zip(
2526
2802
  self.get_family(),
2527
2803
  mobject1.get_family(),
@@ -2538,57 +2814,57 @@ class OpenGLMobject:
2538
2814
  )
2539
2815
  return self
2540
2816
 
2541
- def unlock_data(self):
2817
+ def unlock_data(self) -> None:
2542
2818
  for mob in self.get_family():
2543
2819
  mob.locked_data_keys = set()
2544
2820
 
2545
2821
  # Operations touching shader uniforms
2546
2822
 
2547
2823
  @affects_shader_info_id
2548
- def fix_in_frame(self):
2824
+ def fix_in_frame(self) -> Self:
2549
2825
  self.is_fixed_in_frame = 1.0
2550
2826
  return self
2551
2827
 
2552
2828
  @affects_shader_info_id
2553
- def fix_orientation(self):
2829
+ def fix_orientation(self) -> Self:
2554
2830
  self.is_fixed_orientation = 1.0
2555
2831
  self.fixed_orientation_center = tuple(self.get_center())
2556
2832
  self.depth_test = True
2557
2833
  return self
2558
2834
 
2559
2835
  @affects_shader_info_id
2560
- def unfix_from_frame(self):
2836
+ def unfix_from_frame(self) -> Self:
2561
2837
  self.is_fixed_in_frame = 0.0
2562
2838
  return self
2563
2839
 
2564
2840
  @affects_shader_info_id
2565
- def unfix_orientation(self):
2841
+ def unfix_orientation(self) -> Self:
2566
2842
  self.is_fixed_orientation = 0.0
2567
2843
  self.fixed_orientation_center = (0, 0, 0)
2568
2844
  self.depth_test = False
2569
2845
  return self
2570
2846
 
2571
2847
  @affects_shader_info_id
2572
- def apply_depth_test(self):
2848
+ def apply_depth_test(self) -> Self:
2573
2849
  self.depth_test = True
2574
2850
  return self
2575
2851
 
2576
2852
  @affects_shader_info_id
2577
- def deactivate_depth_test(self):
2853
+ def deactivate_depth_test(self) -> Self:
2578
2854
  self.depth_test = False
2579
2855
  return self
2580
2856
 
2581
2857
  # Shader code manipulation
2582
2858
 
2583
- def replace_shader_code(self, old, new):
2859
+ def replace_shader_code(self, old_code: str, new_code: str) -> Self:
2584
2860
  # TODO, will this work with VMobject structure, given
2585
2861
  # that it does not simpler return shader_wrappers of
2586
2862
  # family?
2587
2863
  for wrapper in self.get_shader_wrapper_list():
2588
- wrapper.replace_code(old, new)
2864
+ wrapper.replace_code(old_code, new_code)
2589
2865
  return self
2590
2866
 
2591
- def set_color_by_code(self, glsl_code):
2867
+ def set_color_by_code(self, glsl_code: str) -> Self:
2592
2868
  """
2593
2869
  Takes a snippet of code and inserts it into a
2594
2870
  context which has the following variables:
@@ -2600,11 +2876,11 @@ class OpenGLMobject:
2600
2876
 
2601
2877
  def set_color_by_xyz_func(
2602
2878
  self,
2603
- glsl_snippet,
2604
- min_value=-5.0,
2605
- max_value=5.0,
2606
- colormap="viridis",
2607
- ):
2879
+ glsl_snippet: str,
2880
+ min_value: float = -5.0,
2881
+ max_value: float = 5.0,
2882
+ colormap: str = "viridis",
2883
+ ) -> Self:
2608
2884
  """
2609
2885
  Pass in a glsl expression in terms of x, y and z which returns
2610
2886
  a float.
@@ -2613,27 +2889,27 @@ class OpenGLMobject:
2613
2889
  # of the shader code
2614
2890
  for char in "xyz":
2615
2891
  glsl_snippet = glsl_snippet.replace(char, "point." + char)
2616
- rgb_list = get_colormap_list(colormap)
2892
+ # TODO: get_colormap_list does not exist
2893
+ # See https://github.com/ManimCommunity/manim/issues/4176
2894
+ rgb_list = get_colormap_list(colormap) # type: ignore[name-defined]
2617
2895
  self.set_color_by_code(
2618
- "color.rgb = float_to_color({}, {}, {}, {});".format(
2619
- glsl_snippet,
2620
- float(min_value),
2621
- float(max_value),
2622
- get_colormap_code(rgb_list),
2623
- ),
2896
+ f"color.rgb = float_to_color({glsl_snippet}, {float(min_value)}, {float(max_value)}, {get_colormap_code(rgb_list)});",
2624
2897
  )
2625
2898
  return self
2626
2899
 
2627
2900
  # For shader data
2628
2901
 
2629
- def refresh_shader_wrapper_id(self):
2630
- self.shader_wrapper.refresh_id()
2902
+ def refresh_shader_wrapper_id(self) -> Self:
2903
+ self.get_shader_wrapper().refresh_id()
2631
2904
  return self
2632
2905
 
2633
- def get_shader_wrapper(self):
2906
+ def get_shader_wrapper(self) -> "ShaderWrapper": # noqa: UP037
2634
2907
  from manim.renderer.shader_wrapper import ShaderWrapper
2635
2908
 
2636
- self.shader_wrapper = ShaderWrapper(
2909
+ # if hasattr(self, "shader_wrapper"):
2910
+ # return self.shader_wrapper
2911
+
2912
+ self.shader_wrapper: ShaderWrapper = ShaderWrapper(
2637
2913
  vert_data=self.get_shader_data(),
2638
2914
  vert_indices=self.get_shader_vert_indices(),
2639
2915
  uniforms=self.get_shader_uniforms(),
@@ -2644,14 +2920,14 @@ class OpenGLMobject:
2644
2920
  )
2645
2921
  return self.shader_wrapper
2646
2922
 
2647
- def get_shader_wrapper_list(self):
2923
+ def get_shader_wrapper_list(self) -> Sequence["ShaderWrapper"]: # noqa: UP037
2648
2924
  shader_wrappers = it.chain(
2649
2925
  [self.get_shader_wrapper()],
2650
2926
  *(sm.get_shader_wrapper_list() for sm in self.submobjects),
2651
2927
  )
2652
2928
  batches = batch_by_property(shader_wrappers, lambda sw: sw.get_id())
2653
2929
 
2654
- result = []
2930
+ result: list["ShaderWrapper"] = [] # noqa: UP037
2655
2931
  for wrapper_group, _ in batches:
2656
2932
  shader_wrapper = wrapper_group[0]
2657
2933
  if not shader_wrapper.is_valid():
@@ -2661,7 +2937,7 @@ class OpenGLMobject:
2661
2937
  result.append(shader_wrapper)
2662
2938
  return result
2663
2939
 
2664
- def check_data_alignment(self, array, data_key):
2940
+ def check_data_alignment(self, array: _ShaderData, data_key: str) -> Self:
2665
2941
  # Makes sure that self.data[key] can be broadcast into
2666
2942
  # the given array, meaning its length has to be either 1
2667
2943
  # or the length of the array
@@ -2673,45 +2949,50 @@ class OpenGLMobject:
2673
2949
  )
2674
2950
  return self
2675
2951
 
2676
- def get_resized_shader_data_array(self, length):
2952
+ def get_resized_shader_data_array(self, length: float) -> _ShaderData:
2677
2953
  # If possible, try to populate an existing array, rather
2678
2954
  # than recreating it each frame
2679
2955
  points = self.points
2680
- shader_data = np.zeros(len(points), dtype=self.shader_dtype)
2956
+ shader_data = cast(_ShaderData, np.zeros(len(points), dtype=self.shader_dtype))
2681
2957
  return shader_data
2682
2958
 
2683
- def read_data_to_shader(self, shader_data, shader_data_key, data_key):
2959
+ def read_data_to_shader(
2960
+ self,
2961
+ shader_data: _ShaderData, # has structured data type, ex. ("point", np.float32, (3,))
2962
+ shader_data_key: str,
2963
+ data_key: str,
2964
+ ) -> None:
2684
2965
  if data_key in self.locked_data_keys:
2685
2966
  return
2686
2967
  self.check_data_alignment(shader_data, data_key)
2687
2968
  shader_data[shader_data_key] = self.data[data_key]
2688
2969
 
2689
- def get_shader_data(self):
2970
+ def get_shader_data(self) -> _ShaderData:
2690
2971
  shader_data = self.get_resized_shader_data_array(self.get_num_points())
2691
2972
  self.read_data_to_shader(shader_data, "point", "points")
2692
2973
  return shader_data
2693
2974
 
2694
- def refresh_shader_data(self):
2975
+ def refresh_shader_data(self) -> None:
2695
2976
  self.get_shader_data()
2696
2977
 
2697
- def get_shader_uniforms(self):
2978
+ def get_shader_uniforms(self) -> dict[str, Any]:
2698
2979
  return self.uniforms
2699
2980
 
2700
- def get_shader_vert_indices(self):
2981
+ def get_shader_vert_indices(self) -> Sequence[int] | None:
2701
2982
  return self.shader_indices
2702
2983
 
2703
2984
  @property
2704
- def submobjects(self):
2985
+ def submobjects(self) -> list[OpenGLMobject]:
2705
2986
  return self._submobjects if hasattr(self, "_submobjects") else []
2706
2987
 
2707
2988
  @submobjects.setter
2708
- def submobjects(self, submobject_list):
2989
+ def submobjects(self, submobject_list: Iterable[OpenGLMobject]) -> None:
2709
2990
  self.remove(*self.submobjects)
2710
2991
  self.add(*submobject_list)
2711
2992
 
2712
2993
  # Errors
2713
2994
 
2714
- def throw_error_if_no_points(self):
2995
+ def throw_error_if_no_points(self) -> None:
2715
2996
  if not self.has_points():
2716
2997
  message = (
2717
2998
  "Cannot call OpenGLMobject.{} " + "for a OpenGLMobject with no points"
@@ -2721,52 +3002,57 @@ class OpenGLMobject:
2721
3002
 
2722
3003
 
2723
3004
  class OpenGLGroup(OpenGLMobject):
2724
- def __init__(self, *mobjects, **kwargs):
2725
- if not all([isinstance(m, OpenGLMobject) for m in mobjects]):
2726
- raise Exception("All submobjects must be of type OpenGLMobject")
3005
+ def __init__(self, *mobjects: OpenGLMobject, **kwargs: Any) -> None:
2727
3006
  super().__init__(**kwargs)
2728
3007
  self.add(*mobjects)
2729
3008
 
2730
3009
 
2731
3010
  class OpenGLPoint(OpenGLMobject):
2732
3011
  def __init__(
2733
- self, location=ORIGIN, artificial_width=1e-6, artificial_height=1e-6, **kwargs
2734
- ):
2735
- self.artificial_width = artificial_width
2736
- self.artificial_height = artificial_height
3012
+ self,
3013
+ location: Point3DLike = ORIGIN,
3014
+ artificial_width: float = 1e-6,
3015
+ artificial_height: float = 1e-6,
3016
+ **kwargs: Any,
3017
+ ) -> None:
3018
+ self.artificial_width: float = artificial_width
3019
+ self.artificial_height: float = artificial_height
2737
3020
  super().__init__(**kwargs)
2738
3021
  self.set_location(location)
2739
3022
 
2740
- def get_width(self):
3023
+ @override
3024
+ def get_width(self) -> float:
2741
3025
  return self.artificial_width
2742
3026
 
2743
- def get_height(self):
3027
+ @override
3028
+ def get_height(self) -> float:
2744
3029
  return self.artificial_height
2745
3030
 
2746
- def get_location(self):
2747
- return self.points[0].copy()
3031
+ def get_location(self) -> Point3D:
3032
+ return cast(Point3D, self.points[0]).copy()
2748
3033
 
2749
- def get_bounding_box_point(self, *args, **kwargs):
3034
+ @override
3035
+ def get_bounding_box_point(self, *args: object, **kwargs: Any) -> Point3D:
2750
3036
  return self.get_location()
2751
3037
 
2752
- def set_location(self, new_loc):
3038
+ def set_location(self, new_loc: Point3DLike) -> None:
2753
3039
  self.set_points(np.array(new_loc, ndmin=2, dtype=float))
2754
3040
 
2755
3041
 
2756
3042
  class _AnimationBuilder:
2757
- def __init__(self, mobject):
2758
- self.mobject = mobject
3043
+ def __init__(self, mobject: OpenGLMobject) -> None:
3044
+ self.mobject: OpenGLMobject = mobject
2759
3045
  self.mobject.generate_target()
2760
3046
 
2761
- self.overridden_animation = None
2762
- self.is_chaining = False
2763
- self.methods = []
3047
+ self.overridden_animation: Animation | None = None
3048
+ self.is_chaining: bool = False
3049
+ self.methods: list[MethodWithArgs] = []
2764
3050
 
2765
3051
  # Whether animation args can be passed
2766
- self.cannot_pass_args = False
2767
- self.anim_args = {}
3052
+ self.cannot_pass_args: bool = False
3053
+ self.anim_args: dict[str, object] = {}
2768
3054
 
2769
- def __call__(self, **kwargs):
3055
+ def __call__(self, **kwargs: Any) -> Self:
2770
3056
  if self.cannot_pass_args:
2771
3057
  raise ValueError(
2772
3058
  "Animation arguments must be passed before accessing methods and can only be passed once",
@@ -2777,26 +3063,30 @@ class _AnimationBuilder:
2777
3063
 
2778
3064
  return self
2779
3065
 
2780
- def __getattr__(self, method_name):
3066
+ def __getattr__(self, method_name: str) -> Callable[..., Self]:
2781
3067
  method = getattr(self.mobject.target, method_name)
2782
3068
  has_overridden_animation = hasattr(method, "_override_animate")
2783
3069
 
2784
3070
  if (self.is_chaining and has_overridden_animation) or self.overridden_animation:
2785
3071
  raise NotImplementedError(
2786
- "Method chaining is currently not supported for "
2787
- "overridden animations",
3072
+ "Method chaining is currently not supported for overridden animations",
2788
3073
  )
2789
3074
 
2790
- def update_target(*method_args, **method_kwargs):
3075
+ # NOTE: using `Self` here should not be a problem, because it's equivalent to a `TypeVar` introduced in `__getattr__`.
3076
+ # For this reason, here it's still in scope and can be used (that's why pyright does not flag this as an error).
3077
+ # However, mypy currently does not seem to understand this: hence the `type: ignore` comment.
3078
+ def update_target(*method_args: object, **method_kwargs: object) -> Self: # type: ignore[type-var, misc]
2791
3079
  if has_overridden_animation:
2792
- self.overridden_animation = method._override_animate(
3080
+ self.overridden_animation = cast(
3081
+ "Callable[..., Animation]", method._override_animate
3082
+ )(
2793
3083
  self.mobject,
2794
3084
  *method_args,
2795
3085
  anim_args=self.anim_args,
2796
3086
  **method_kwargs,
2797
3087
  )
2798
3088
  else:
2799
- self.methods.append([method, method_args, method_kwargs])
3089
+ self.methods.append(MethodWithArgs(method, method_args, method_kwargs))
2800
3090
  method(*method_args, **method_kwargs)
2801
3091
  return self
2802
3092
 
@@ -2805,13 +3095,12 @@ class _AnimationBuilder:
2805
3095
 
2806
3096
  return update_target
2807
3097
 
2808
- def build(self):
3098
+ def build(self) -> "Animation": # noqa: UP037
2809
3099
  from manim.animation.transform import _MethodAnimation
2810
3100
 
2811
- if self.overridden_animation:
2812
- anim = self.overridden_animation
2813
- else:
2814
- anim = _MethodAnimation(self.mobject, self.methods)
3101
+ # NOTE: To fix this mypy error, we'll need to update `_MethodAnimation` to accept `Mobject | OpenGLMobject` instead of `Mobject`.
3102
+ # Once that is done, the `type: ignore` comment below won't be necessary anymore and mypy will emit a corresponding warning.
3103
+ anim = self.overridden_animation or _MethodAnimation(self.mobject, self.methods) # type: ignore[arg-type]
2815
3104
 
2816
3105
  for attr, value in self.anim_args.items():
2817
3106
  setattr(anim, attr, value)
@@ -2819,7 +3108,15 @@ class _AnimationBuilder:
2819
3108
  return anim
2820
3109
 
2821
3110
 
2822
- def override_animate(method):
3111
+ _Decorated = TypeVar("_Decorated", bound=Callable[..., "Animation"])
3112
+
3113
+
3114
+ class _OverrideAnimateDecorator(Protocol):
3115
+ # The slash divider on the next line prevents a mypy error in line 3176.
3116
+ def __call__(self, decorated: _Decorated, /) -> _Decorated: ...
3117
+
3118
+
3119
+ def override_animate(method: types.FunctionType) -> _OverrideAnimateDecorator:
2823
3120
  r"""Decorator for overriding method animations.
2824
3121
 
2825
3122
  This allows to specify a method (returning an :class:`~.Animation`)
@@ -2871,8 +3168,8 @@ def override_animate(method):
2871
3168
 
2872
3169
  """
2873
3170
 
2874
- def decorator(animation_method):
2875
- method._override_animate = animation_method
3171
+ def decorator(animation_method: _Decorated) -> _Decorated:
3172
+ method._override_animate = animation_method # type: ignore[attr-defined]
2876
3173
  return animation_method
2877
3174
 
2878
3175
  return decorator