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