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