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/animation/animation.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"""Animate mobjects."""
|
|
2
2
|
|
|
3
|
-
|
|
4
3
|
from __future__ import annotations
|
|
5
4
|
|
|
6
5
|
from manim.mobject.opengl.opengl_mobject import OpenGLMobject
|
|
@@ -8,15 +7,19 @@ from manim.mobject.opengl.opengl_mobject import OpenGLMobject
|
|
|
8
7
|
from .. import config, logger
|
|
9
8
|
from ..constants import RendererType
|
|
10
9
|
from ..mobject import mobject
|
|
11
|
-
from ..mobject.mobject import Mobject
|
|
10
|
+
from ..mobject.mobject import Group, Mobject
|
|
12
11
|
from ..mobject.opengl import opengl_mobject
|
|
13
12
|
from ..utils.rate_functions import linear, smooth
|
|
14
13
|
|
|
15
|
-
__all__ = ["Animation", "Wait", "override_animation"]
|
|
14
|
+
__all__ = ["Animation", "Wait", "Add", "override_animation"]
|
|
16
15
|
|
|
17
16
|
|
|
17
|
+
from collections.abc import Iterable, Sequence
|
|
18
18
|
from copy import deepcopy
|
|
19
|
-
from
|
|
19
|
+
from functools import partialmethod
|
|
20
|
+
from typing import TYPE_CHECKING, Any, Callable
|
|
21
|
+
|
|
22
|
+
from typing_extensions import Self
|
|
20
23
|
|
|
21
24
|
if TYPE_CHECKING:
|
|
22
25
|
from manim.scene.scene import Scene
|
|
@@ -112,7 +115,7 @@ class Animation:
|
|
|
112
115
|
*args,
|
|
113
116
|
use_override=True,
|
|
114
117
|
**kwargs,
|
|
115
|
-
):
|
|
118
|
+
) -> Self:
|
|
116
119
|
if isinstance(mobject, Mobject) and use_override:
|
|
117
120
|
func = mobject.animation_override_for(cls)
|
|
118
121
|
if func is not None:
|
|
@@ -169,6 +172,19 @@ class Animation:
|
|
|
169
172
|
),
|
|
170
173
|
)
|
|
171
174
|
|
|
175
|
+
@property
|
|
176
|
+
def run_time(self) -> float:
|
|
177
|
+
return self._run_time
|
|
178
|
+
|
|
179
|
+
@run_time.setter
|
|
180
|
+
def run_time(self, value: float) -> None:
|
|
181
|
+
if value < 0:
|
|
182
|
+
raise ValueError(
|
|
183
|
+
f"The run_time of {self.__class__.__name__} cannot be "
|
|
184
|
+
f"negative. The given value was {value}."
|
|
185
|
+
)
|
|
186
|
+
self._run_time = value
|
|
187
|
+
|
|
172
188
|
def _typecheck_input(self, mobject: Mobject | None) -> None:
|
|
173
189
|
if mobject is None:
|
|
174
190
|
logger.debug("Animation with empty mobject")
|
|
@@ -398,6 +414,7 @@ class Animation:
|
|
|
398
414
|
self.run_time = run_time
|
|
399
415
|
return self
|
|
400
416
|
|
|
417
|
+
# TODO: is this getter even necessary?
|
|
401
418
|
def get_run_time(self) -> float:
|
|
402
419
|
"""Get the run time of the animation.
|
|
403
420
|
|
|
@@ -476,6 +493,52 @@ class Animation:
|
|
|
476
493
|
"""
|
|
477
494
|
return self.introducer
|
|
478
495
|
|
|
496
|
+
@classmethod
|
|
497
|
+
def __init_subclass__(cls, **kwargs) -> None:
|
|
498
|
+
super().__init_subclass__(**kwargs)
|
|
499
|
+
|
|
500
|
+
cls._original__init__ = cls.__init__
|
|
501
|
+
|
|
502
|
+
@classmethod
|
|
503
|
+
def set_default(cls, **kwargs) -> None:
|
|
504
|
+
"""Sets the default values of keyword arguments.
|
|
505
|
+
|
|
506
|
+
If this method is called without any additional keyword
|
|
507
|
+
arguments, the original default values of the initialization
|
|
508
|
+
method of this class are restored.
|
|
509
|
+
|
|
510
|
+
Parameters
|
|
511
|
+
----------
|
|
512
|
+
|
|
513
|
+
kwargs
|
|
514
|
+
Passing any keyword argument will update the default
|
|
515
|
+
values of the keyword arguments of the initialization
|
|
516
|
+
function of this class.
|
|
517
|
+
|
|
518
|
+
Examples
|
|
519
|
+
--------
|
|
520
|
+
|
|
521
|
+
.. manim:: ChangeDefaultAnimation
|
|
522
|
+
|
|
523
|
+
class ChangeDefaultAnimation(Scene):
|
|
524
|
+
def construct(self):
|
|
525
|
+
Rotate.set_default(run_time=2, rate_func=rate_functions.linear)
|
|
526
|
+
Indicate.set_default(color=None)
|
|
527
|
+
|
|
528
|
+
S = Square(color=BLUE, fill_color=BLUE, fill_opacity=0.25)
|
|
529
|
+
self.add(S)
|
|
530
|
+
self.play(Rotate(S, PI))
|
|
531
|
+
self.play(Indicate(S))
|
|
532
|
+
|
|
533
|
+
Rotate.set_default()
|
|
534
|
+
Indicate.set_default()
|
|
535
|
+
|
|
536
|
+
"""
|
|
537
|
+
if kwargs:
|
|
538
|
+
cls.__init__ = partialmethod(cls.__init__, **kwargs)
|
|
539
|
+
else:
|
|
540
|
+
cls.__init__ = cls._original__init__
|
|
541
|
+
|
|
479
542
|
|
|
480
543
|
def prepare_animation(
|
|
481
544
|
anim: Animation | mobject._AnimationBuilder,
|
|
@@ -575,6 +638,90 @@ class Wait(Animation):
|
|
|
575
638
|
pass
|
|
576
639
|
|
|
577
640
|
|
|
641
|
+
class Add(Animation):
|
|
642
|
+
"""Add Mobjects to a scene, without animating them in any other way. This
|
|
643
|
+
is similar to the :meth:`.Scene.add` method, but :class:`Add` is an
|
|
644
|
+
animation which can be grouped into other animations.
|
|
645
|
+
|
|
646
|
+
Parameters
|
|
647
|
+
----------
|
|
648
|
+
mobjects
|
|
649
|
+
One :class:`~.Mobject` or more to add to a scene.
|
|
650
|
+
run_time
|
|
651
|
+
The duration of the animation after adding the ``mobjects``. Defaults
|
|
652
|
+
to 0, which means this is an instant animation without extra wait time
|
|
653
|
+
after adding them.
|
|
654
|
+
**kwargs
|
|
655
|
+
Additional arguments to pass to the parent :class:`Animation` class.
|
|
656
|
+
|
|
657
|
+
Examples
|
|
658
|
+
--------
|
|
659
|
+
|
|
660
|
+
.. manim:: DefaultAddScene
|
|
661
|
+
|
|
662
|
+
class DefaultAddScene(Scene):
|
|
663
|
+
def construct(self):
|
|
664
|
+
text_1 = Text("I was added with Add!")
|
|
665
|
+
text_2 = Text("Me too!")
|
|
666
|
+
text_3 = Text("And me!")
|
|
667
|
+
texts = VGroup(text_1, text_2, text_3).arrange(DOWN)
|
|
668
|
+
rect = SurroundingRectangle(texts, buff=0.5)
|
|
669
|
+
|
|
670
|
+
self.play(
|
|
671
|
+
Create(rect, run_time=3.0),
|
|
672
|
+
Succession(
|
|
673
|
+
Wait(1.0),
|
|
674
|
+
# You can Add a Mobject in the middle of an animation...
|
|
675
|
+
Add(text_1),
|
|
676
|
+
Wait(1.0),
|
|
677
|
+
# ...or multiple Mobjects at once!
|
|
678
|
+
Add(text_2, text_3),
|
|
679
|
+
),
|
|
680
|
+
)
|
|
681
|
+
self.wait()
|
|
682
|
+
|
|
683
|
+
.. manim:: AddWithRunTimeScene
|
|
684
|
+
|
|
685
|
+
class AddWithRunTimeScene(Scene):
|
|
686
|
+
def construct(self):
|
|
687
|
+
# A 5x5 grid of circles
|
|
688
|
+
circles = VGroup(
|
|
689
|
+
*[Circle(radius=0.5) for _ in range(25)]
|
|
690
|
+
).arrange_in_grid(5, 5)
|
|
691
|
+
|
|
692
|
+
self.play(
|
|
693
|
+
Succession(
|
|
694
|
+
# Add a run_time of 0.2 to wait for 0.2 seconds after
|
|
695
|
+
# adding the circle, instead of using Wait(0.2) after Add!
|
|
696
|
+
*[Add(circle, run_time=0.2) for circle in circles],
|
|
697
|
+
rate_func=smooth,
|
|
698
|
+
)
|
|
699
|
+
)
|
|
700
|
+
self.wait()
|
|
701
|
+
"""
|
|
702
|
+
|
|
703
|
+
def __init__(
|
|
704
|
+
self, *mobjects: Mobject, run_time: float = 0.0, **kwargs: Any
|
|
705
|
+
) -> None:
|
|
706
|
+
mobject = mobjects[0] if len(mobjects) == 1 else Group(*mobjects)
|
|
707
|
+
super().__init__(mobject, run_time=run_time, introducer=True, **kwargs)
|
|
708
|
+
|
|
709
|
+
def begin(self) -> None:
|
|
710
|
+
pass
|
|
711
|
+
|
|
712
|
+
def finish(self) -> None:
|
|
713
|
+
pass
|
|
714
|
+
|
|
715
|
+
def clean_up_from_scene(self, scene: Scene) -> None:
|
|
716
|
+
pass
|
|
717
|
+
|
|
718
|
+
def update_mobjects(self, dt: float) -> None:
|
|
719
|
+
pass
|
|
720
|
+
|
|
721
|
+
def interpolate(self, alpha: float) -> None:
|
|
722
|
+
pass
|
|
723
|
+
|
|
724
|
+
|
|
578
725
|
def override_animation(
|
|
579
726
|
animation_class: type[Animation],
|
|
580
727
|
) -> Callable[[Callable], Callable]:
|
manim/animation/composition.py
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
"""Tools for displaying multiple animations at once."""
|
|
2
2
|
|
|
3
|
-
|
|
4
3
|
from __future__ import annotations
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
import types
|
|
6
|
+
from collections.abc import Iterable, Sequence
|
|
7
|
+
from typing import TYPE_CHECKING, Callable
|
|
7
8
|
|
|
8
9
|
import numpy as np
|
|
9
10
|
|
|
11
|
+
from manim._config import config
|
|
12
|
+
from manim.animation.animation import Animation, prepare_animation
|
|
13
|
+
from manim.constants import RendererType
|
|
14
|
+
from manim.mobject.mobject import Group, Mobject
|
|
10
15
|
from manim.mobject.opengl.opengl_mobject import OpenGLGroup
|
|
11
|
-
|
|
12
|
-
from
|
|
13
|
-
from
|
|
14
|
-
from
|
|
15
|
-
from ..mobject.mobject import Group, Mobject
|
|
16
|
-
from ..scene.scene import Scene
|
|
17
|
-
from ..utils.iterables import remove_list_redundancies
|
|
18
|
-
from ..utils.rate_functions import linear
|
|
16
|
+
from manim.scene.scene import Scene
|
|
17
|
+
from manim.utils.iterables import remove_list_redundancies
|
|
18
|
+
from manim.utils.parameter_parsing import flatten_iterable_parameters
|
|
19
|
+
from manim.utils.rate_functions import linear
|
|
19
20
|
|
|
20
21
|
if TYPE_CHECKING:
|
|
21
22
|
from manim.mobject.opengl.opengl_vectorized_mobject import OpenGLVGroup
|
|
22
|
-
|
|
23
|
-
from ..mobject.types.vectorized_mobject import VGroup
|
|
23
|
+
from manim.mobject.types.vectorized_mobject import VGroup
|
|
24
24
|
|
|
25
25
|
__all__ = ["AnimationGroup", "Succession", "LaggedStart", "LaggedStartMap"]
|
|
26
26
|
|
|
@@ -54,14 +54,15 @@ class AnimationGroup(Animation):
|
|
|
54
54
|
|
|
55
55
|
def __init__(
|
|
56
56
|
self,
|
|
57
|
-
*animations: Animation,
|
|
57
|
+
*animations: Animation | Iterable[Animation] | types.GeneratorType[Animation],
|
|
58
58
|
group: Group | VGroup | OpenGLGroup | OpenGLVGroup = None,
|
|
59
59
|
run_time: float | None = None,
|
|
60
60
|
rate_func: Callable[[float], float] = linear,
|
|
61
61
|
lag_ratio: float = 0,
|
|
62
62
|
**kwargs,
|
|
63
63
|
) -> None:
|
|
64
|
-
|
|
64
|
+
arg_anim = flatten_iterable_parameters(animations)
|
|
65
|
+
self.animations = [prepare_animation(anim) for anim in arg_anim]
|
|
65
66
|
self.rate_func = rate_func
|
|
66
67
|
self.group = group
|
|
67
68
|
if self.group is None:
|
|
@@ -81,6 +82,12 @@ class AnimationGroup(Animation):
|
|
|
81
82
|
return list(self.group)
|
|
82
83
|
|
|
83
84
|
def begin(self) -> None:
|
|
85
|
+
if not self.animations:
|
|
86
|
+
raise ValueError(
|
|
87
|
+
f"Trying to play {self} without animations, this is not supported. "
|
|
88
|
+
"Please add at least one subanimation."
|
|
89
|
+
)
|
|
90
|
+
self.anim_group_time = 0.0
|
|
84
91
|
if self.suspend_mobject_updating:
|
|
85
92
|
self.group.suspend_updating()
|
|
86
93
|
for anim in self.animations:
|
|
@@ -93,6 +100,8 @@ class AnimationGroup(Animation):
|
|
|
93
100
|
def finish(self) -> None:
|
|
94
101
|
for anim in self.animations:
|
|
95
102
|
anim.finish()
|
|
103
|
+
self.anims_begun[:] = True
|
|
104
|
+
self.anims_finished[:] = True
|
|
96
105
|
if self.suspend_mobject_updating:
|
|
97
106
|
self.group.resume_updating()
|
|
98
107
|
|
|
@@ -104,7 +113,9 @@ class AnimationGroup(Animation):
|
|
|
104
113
|
anim.clean_up_from_scene(scene)
|
|
105
114
|
|
|
106
115
|
def update_mobjects(self, dt: float) -> None:
|
|
107
|
-
for anim in self.
|
|
116
|
+
for anim in self.anims_with_timings["anim"][
|
|
117
|
+
self.anims_begun & ~self.anims_finished
|
|
118
|
+
]:
|
|
108
119
|
anim.update_mobjects(dt)
|
|
109
120
|
|
|
110
121
|
def init_run_time(self, run_time) -> float:
|
|
@@ -121,22 +132,30 @@ class AnimationGroup(Animation):
|
|
|
121
132
|
The duration of the animation in seconds.
|
|
122
133
|
"""
|
|
123
134
|
self.build_animations_with_timings()
|
|
124
|
-
if
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
135
|
+
# Note: if lag_ratio < 1, then not necessarily the final animation's
|
|
136
|
+
# end time will be the max end time! Therefore we must calculate the
|
|
137
|
+
# maximum over all the end times, and not just take the last one.
|
|
138
|
+
# Example: if you want to play 2 animations of 10s and 1s with a
|
|
139
|
+
# lag_ratio of 0.1, the 1st one will end at t=10 and the 2nd one will
|
|
140
|
+
# end at t=2, so the AnimationGroup will end at t=10.
|
|
141
|
+
self.max_end_time = max(self.anims_with_timings["end"], default=0)
|
|
128
142
|
return self.max_end_time if run_time is None else run_time
|
|
129
143
|
|
|
130
144
|
def build_animations_with_timings(self) -> None:
|
|
131
145
|
"""Creates a list of triplets of the form (anim, start_time, end_time)."""
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
146
|
+
run_times = np.array([anim.run_time for anim in self.animations])
|
|
147
|
+
num_animations = run_times.shape[0]
|
|
148
|
+
dtype = [("anim", "O"), ("start", "f8"), ("end", "f8")]
|
|
149
|
+
self.anims_with_timings = np.zeros(num_animations, dtype=dtype)
|
|
150
|
+
self.anims_begun = np.zeros(num_animations, dtype=bool)
|
|
151
|
+
self.anims_finished = np.zeros(num_animations, dtype=bool)
|
|
152
|
+
if num_animations == 0:
|
|
153
|
+
return
|
|
154
|
+
|
|
155
|
+
lags = run_times[:-1] * self.lag_ratio
|
|
156
|
+
self.anims_with_timings["anim"] = self.animations
|
|
157
|
+
self.anims_with_timings["start"][1:] = np.add.accumulate(lags)
|
|
158
|
+
self.anims_with_timings["end"] = self.anims_with_timings["start"] + run_times
|
|
140
159
|
|
|
141
160
|
def interpolate(self, alpha: float) -> None:
|
|
142
161
|
# Note, if the run_time of AnimationGroup has been
|
|
@@ -144,14 +163,32 @@ class AnimationGroup(Animation):
|
|
|
144
163
|
# times might not correspond to actual times,
|
|
145
164
|
# e.g. of the surrounding scene. Instead they'd
|
|
146
165
|
# be a rescaled version. But that's okay!
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
166
|
+
anim_group_time = self.rate_func(alpha) * self.max_end_time
|
|
167
|
+
time_goes_back = anim_group_time < self.anim_group_time
|
|
168
|
+
|
|
169
|
+
# Only update ongoing animations
|
|
170
|
+
awt = self.anims_with_timings
|
|
171
|
+
new_begun = anim_group_time >= awt["start"]
|
|
172
|
+
new_finished = anim_group_time > awt["end"]
|
|
173
|
+
to_update = awt[
|
|
174
|
+
(self.anims_begun | new_begun) & (~self.anims_finished | ~new_finished)
|
|
175
|
+
]
|
|
176
|
+
|
|
177
|
+
run_times = to_update["end"] - to_update["start"]
|
|
178
|
+
with_zero_run_time = run_times == 0
|
|
179
|
+
run_times[with_zero_run_time] = 1
|
|
180
|
+
sub_alphas = (anim_group_time - to_update["start"]) / run_times
|
|
181
|
+
if time_goes_back:
|
|
182
|
+
sub_alphas[(sub_alphas < 0) | with_zero_run_time] = 0
|
|
183
|
+
else:
|
|
184
|
+
sub_alphas[(sub_alphas > 1) | with_zero_run_time] = 1
|
|
185
|
+
|
|
186
|
+
for anim_to_update, sub_alpha in zip(to_update["anim"], sub_alphas):
|
|
187
|
+
anim_to_update.interpolate(sub_alpha)
|
|
188
|
+
|
|
189
|
+
self.anim_group_time = anim_group_time
|
|
190
|
+
self.anims_begun = new_begun
|
|
191
|
+
self.anims_finished = new_finished
|
|
155
192
|
|
|
156
193
|
|
|
157
194
|
class Succession(AnimationGroup):
|
|
@@ -195,7 +232,11 @@ class Succession(AnimationGroup):
|
|
|
195
232
|
super().__init__(*animations, lag_ratio=lag_ratio, **kwargs)
|
|
196
233
|
|
|
197
234
|
def begin(self) -> None:
|
|
198
|
-
|
|
235
|
+
if not self.animations:
|
|
236
|
+
raise ValueError(
|
|
237
|
+
f"Trying to play {self} without animations, this is not supported. "
|
|
238
|
+
"Please add at least one subanimation."
|
|
239
|
+
)
|
|
199
240
|
self.update_active_animation(0)
|
|
200
241
|
|
|
201
242
|
def finish(self) -> None:
|
|
@@ -226,8 +267,8 @@ class Succession(AnimationGroup):
|
|
|
226
267
|
self.active_animation = self.animations[index]
|
|
227
268
|
self.active_animation._setup_scene(self.scene)
|
|
228
269
|
self.active_animation.begin()
|
|
229
|
-
self.active_start_time = self.anims_with_timings[index][
|
|
230
|
-
self.active_end_time = self.anims_with_timings[index][
|
|
270
|
+
self.active_start_time = self.anims_with_timings[index]["start"]
|
|
271
|
+
self.active_end_time = self.anims_with_timings[index]["end"]
|
|
231
272
|
|
|
232
273
|
def next_animation(self) -> None:
|
|
233
274
|
"""Proceeds to the next animation.
|
|
@@ -244,7 +285,7 @@ class Succession(AnimationGroup):
|
|
|
244
285
|
self.next_animation()
|
|
245
286
|
if self.active_animation is not None and self.active_start_time is not None:
|
|
246
287
|
elapsed = current_time - self.active_start_time
|
|
247
|
-
active_run_time = self.active_animation.
|
|
288
|
+
active_run_time = self.active_animation.run_time
|
|
248
289
|
subalpha = elapsed / active_run_time if active_run_time != 0.0 else 1.0
|
|
249
290
|
self.active_animation.interpolate(subalpha)
|
|
250
291
|
|
manim/animation/creation.py
CHANGED
|
@@ -70,17 +70,22 @@ __all__ = [
|
|
|
70
70
|
"RemoveTextLetterByLetter",
|
|
71
71
|
"ShowSubmobjectsOneByOne",
|
|
72
72
|
"AddTextWordByWord",
|
|
73
|
+
"TypeWithCursor",
|
|
74
|
+
"UntypeWithCursor",
|
|
73
75
|
]
|
|
74
76
|
|
|
75
77
|
|
|
76
78
|
import itertools as it
|
|
77
|
-
from
|
|
79
|
+
from collections.abc import Iterable, Sequence
|
|
80
|
+
from typing import TYPE_CHECKING, Callable
|
|
78
81
|
|
|
79
82
|
import numpy as np
|
|
80
83
|
|
|
81
84
|
if TYPE_CHECKING:
|
|
82
85
|
from manim.mobject.text.text_mobject import Text
|
|
86
|
+
from manim.scene.scene import Scene
|
|
83
87
|
|
|
88
|
+
from manim.constants import RIGHT, TAU
|
|
84
89
|
from manim.mobject.opengl.opengl_surface import OpenGLSurface
|
|
85
90
|
from manim.mobject.opengl.opengl_vectorized_mobject import OpenGLVMobject
|
|
86
91
|
from manim.utils.color import ManimColor
|
|
@@ -88,7 +93,6 @@ from manim.utils.color import ManimColor
|
|
|
88
93
|
from .. import config
|
|
89
94
|
from ..animation.animation import Animation
|
|
90
95
|
from ..animation.composition import Succession
|
|
91
|
-
from ..constants import TAU
|
|
92
96
|
from ..mobject.mobject import Group, Mobject
|
|
93
97
|
from ..mobject.types.vectorized_mobject import VMobject
|
|
94
98
|
from ..utils.bezier import integer_interpolate
|
|
@@ -246,7 +250,9 @@ class DrawBorderThenFill(Animation):
|
|
|
246
250
|
|
|
247
251
|
def _typecheck_input(self, vmobject: VMobject | OpenGLVMobject) -> None:
|
|
248
252
|
if not isinstance(vmobject, (VMobject, OpenGLVMobject)):
|
|
249
|
-
raise TypeError(
|
|
253
|
+
raise TypeError(
|
|
254
|
+
f"{self.__class__.__name__} only works for vectorized Mobjects"
|
|
255
|
+
)
|
|
250
256
|
|
|
251
257
|
def begin(self) -> None:
|
|
252
258
|
self.outline = self.get_outline()
|
|
@@ -277,7 +283,7 @@ class DrawBorderThenFill(Animation):
|
|
|
277
283
|
alpha: float,
|
|
278
284
|
) -> None: # Fixme: not matching the parent class? What is outline doing here?
|
|
279
285
|
index: int
|
|
280
|
-
subalpha:
|
|
286
|
+
subalpha: float
|
|
281
287
|
index, subalpha = integer_interpolate(0, 2, alpha)
|
|
282
288
|
if index == 0:
|
|
283
289
|
submobject.pointwise_become_partial(outline, 0, subalpha)
|
|
@@ -347,10 +353,7 @@ class Write(DrawBorderThenFill):
|
|
|
347
353
|
) -> tuple[float, float]:
|
|
348
354
|
length = len(vmobject.family_members_with_points())
|
|
349
355
|
if run_time is None:
|
|
350
|
-
if length < 15
|
|
351
|
-
run_time = 1
|
|
352
|
-
else:
|
|
353
|
-
run_time = 2
|
|
356
|
+
run_time = 1 if length < 15 else 2
|
|
354
357
|
if lag_ratio is None:
|
|
355
358
|
lag_ratio = min(4.0 / max(1.0, length), 0.2)
|
|
356
359
|
return run_time, lag_ratio
|
|
@@ -456,7 +459,7 @@ class SpiralIn(Animation):
|
|
|
456
459
|
fade_in_fraction=0.3,
|
|
457
460
|
**kwargs,
|
|
458
461
|
) -> None:
|
|
459
|
-
self.shapes = shapes
|
|
462
|
+
self.shapes = shapes.copy()
|
|
460
463
|
self.scale_factor = scale_factor
|
|
461
464
|
self.shape_center = shapes.get_center()
|
|
462
465
|
self.fade_in_fraction = fade_in_fraction
|
|
@@ -473,15 +476,21 @@ class SpiralIn(Animation):
|
|
|
473
476
|
|
|
474
477
|
def interpolate_mobject(self, alpha: float) -> None:
|
|
475
478
|
alpha = self.rate_func(alpha)
|
|
476
|
-
for shape in self.shapes:
|
|
479
|
+
for original_shape, shape in zip(self.shapes, self.mobject):
|
|
477
480
|
shape.restore()
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
+
fill_opacity = original_shape.get_fill_opacity()
|
|
482
|
+
stroke_opacity = original_shape.get_stroke_opacity()
|
|
483
|
+
new_fill_opacity = min(
|
|
484
|
+
fill_opacity, alpha * fill_opacity / self.fade_in_fraction
|
|
485
|
+
)
|
|
486
|
+
new_stroke_opacity = min(
|
|
487
|
+
stroke_opacity, alpha * stroke_opacity / self.fade_in_fraction
|
|
488
|
+
)
|
|
481
489
|
shape.shift((shape.final_position - shape.initial_position) * alpha)
|
|
482
490
|
shape.rotate(TAU * alpha, about_point=self.shape_center)
|
|
483
491
|
shape.rotate(-TAU * alpha, about_point=shape.get_center_of_mass())
|
|
484
|
-
shape.
|
|
492
|
+
shape.set_fill(opacity=new_fill_opacity)
|
|
493
|
+
shape.set_stroke(opacity=new_stroke_opacity)
|
|
485
494
|
|
|
486
495
|
|
|
487
496
|
class ShowIncreasingSubsets(Animation):
|
|
@@ -668,3 +677,176 @@ class AddTextWordByWord(Succession):
|
|
|
668
677
|
)
|
|
669
678
|
)
|
|
670
679
|
super().__init__(*anims, **kwargs)
|
|
680
|
+
|
|
681
|
+
|
|
682
|
+
class TypeWithCursor(AddTextLetterByLetter):
|
|
683
|
+
"""Similar to :class:`~.AddTextLetterByLetter` , but with an additional cursor mobject at the end.
|
|
684
|
+
|
|
685
|
+
Parameters
|
|
686
|
+
----------
|
|
687
|
+
time_per_char
|
|
688
|
+
Frequency of appearance of the letters.
|
|
689
|
+
cursor
|
|
690
|
+
:class:`~.Mobject` shown after the last added letter.
|
|
691
|
+
buff
|
|
692
|
+
Controls how far away the cursor is to the right of the last added letter.
|
|
693
|
+
keep_cursor_y
|
|
694
|
+
If ``True``, the cursor's y-coordinate is set to the center of the ``Text`` and remains the same throughout the animation. Otherwise, it is set to the center of the last added letter.
|
|
695
|
+
leave_cursor_on
|
|
696
|
+
Whether to show the cursor after the animation.
|
|
697
|
+
|
|
698
|
+
.. tip::
|
|
699
|
+
This is currently only possible for class:`~.Text` and not for class:`~.MathTex`.
|
|
700
|
+
|
|
701
|
+
|
|
702
|
+
Examples
|
|
703
|
+
--------
|
|
704
|
+
|
|
705
|
+
.. manim:: InsertingTextExample
|
|
706
|
+
:ref_classes: Blink
|
|
707
|
+
|
|
708
|
+
class InsertingTextExample(Scene):
|
|
709
|
+
def construct(self):
|
|
710
|
+
text = Text("Inserting", color=PURPLE).scale(1.5).to_edge(LEFT)
|
|
711
|
+
cursor = Rectangle(
|
|
712
|
+
color = GREY_A,
|
|
713
|
+
fill_color = GREY_A,
|
|
714
|
+
fill_opacity = 1.0,
|
|
715
|
+
height = 1.1,
|
|
716
|
+
width = 0.5,
|
|
717
|
+
).move_to(text[0]) # Position the cursor
|
|
718
|
+
|
|
719
|
+
self.play(TypeWithCursor(text, cursor))
|
|
720
|
+
self.play(Blink(cursor, blinks=2))
|
|
721
|
+
|
|
722
|
+
"""
|
|
723
|
+
|
|
724
|
+
def __init__(
|
|
725
|
+
self,
|
|
726
|
+
text: Text,
|
|
727
|
+
cursor: Mobject,
|
|
728
|
+
buff: float = 0.1,
|
|
729
|
+
keep_cursor_y: bool = True,
|
|
730
|
+
leave_cursor_on: bool = True,
|
|
731
|
+
time_per_char: float = 0.1,
|
|
732
|
+
reverse_rate_function=False,
|
|
733
|
+
introducer=True,
|
|
734
|
+
**kwargs,
|
|
735
|
+
) -> None:
|
|
736
|
+
self.cursor = cursor
|
|
737
|
+
self.buff = buff
|
|
738
|
+
self.keep_cursor_y = keep_cursor_y
|
|
739
|
+
self.leave_cursor_on = leave_cursor_on
|
|
740
|
+
super().__init__(
|
|
741
|
+
text,
|
|
742
|
+
time_per_char=time_per_char,
|
|
743
|
+
reverse_rate_function=reverse_rate_function,
|
|
744
|
+
introducer=introducer,
|
|
745
|
+
**kwargs,
|
|
746
|
+
)
|
|
747
|
+
|
|
748
|
+
def begin(self) -> None:
|
|
749
|
+
self.y_cursor = self.cursor.get_y()
|
|
750
|
+
self.cursor.initial_position = self.mobject.get_center()
|
|
751
|
+
if self.keep_cursor_y:
|
|
752
|
+
self.cursor.set_y(self.y_cursor)
|
|
753
|
+
|
|
754
|
+
self.cursor.set_opacity(0)
|
|
755
|
+
self.mobject.add(self.cursor)
|
|
756
|
+
super().begin()
|
|
757
|
+
|
|
758
|
+
def finish(self) -> None:
|
|
759
|
+
if self.leave_cursor_on:
|
|
760
|
+
self.cursor.set_opacity(1)
|
|
761
|
+
else:
|
|
762
|
+
self.cursor.set_opacity(0)
|
|
763
|
+
self.mobject.remove(self.cursor)
|
|
764
|
+
super().finish()
|
|
765
|
+
|
|
766
|
+
def clean_up_from_scene(self, scene: Scene) -> None:
|
|
767
|
+
if not self.leave_cursor_on:
|
|
768
|
+
scene.remove(self.cursor)
|
|
769
|
+
super().clean_up_from_scene(scene)
|
|
770
|
+
|
|
771
|
+
def update_submobject_list(self, index: int) -> None:
|
|
772
|
+
for mobj in self.all_submobs[:index]:
|
|
773
|
+
mobj.set_opacity(1)
|
|
774
|
+
|
|
775
|
+
for mobj in self.all_submobs[index:]:
|
|
776
|
+
mobj.set_opacity(0)
|
|
777
|
+
|
|
778
|
+
if index != 0:
|
|
779
|
+
self.cursor.next_to(
|
|
780
|
+
self.all_submobs[index - 1], RIGHT, buff=self.buff
|
|
781
|
+
).set_y(self.cursor.initial_position[1])
|
|
782
|
+
else:
|
|
783
|
+
self.cursor.move_to(self.all_submobs[0]).set_y(
|
|
784
|
+
self.cursor.initial_position[1]
|
|
785
|
+
)
|
|
786
|
+
|
|
787
|
+
if self.keep_cursor_y:
|
|
788
|
+
self.cursor.set_y(self.y_cursor)
|
|
789
|
+
self.cursor.set_opacity(1)
|
|
790
|
+
|
|
791
|
+
|
|
792
|
+
class UntypeWithCursor(TypeWithCursor):
|
|
793
|
+
"""Similar to :class:`~.RemoveTextLetterByLetter` , but with an additional cursor mobject at the end.
|
|
794
|
+
|
|
795
|
+
Parameters
|
|
796
|
+
----------
|
|
797
|
+
time_per_char
|
|
798
|
+
Frequency of appearance of the letters.
|
|
799
|
+
cursor
|
|
800
|
+
:class:`~.Mobject` shown after the last added letter.
|
|
801
|
+
buff
|
|
802
|
+
Controls how far away the cursor is to the right of the last added letter.
|
|
803
|
+
keep_cursor_y
|
|
804
|
+
If ``True``, the cursor's y-coordinate is set to the center of the ``Text`` and remains the same throughout the animation. Otherwise, it is set to the center of the last added letter.
|
|
805
|
+
leave_cursor_on
|
|
806
|
+
Whether to show the cursor after the animation.
|
|
807
|
+
|
|
808
|
+
.. tip::
|
|
809
|
+
This is currently only possible for class:`~.Text` and not for class:`~.MathTex`.
|
|
810
|
+
|
|
811
|
+
|
|
812
|
+
Examples
|
|
813
|
+
--------
|
|
814
|
+
|
|
815
|
+
.. manim:: DeletingTextExample
|
|
816
|
+
:ref_classes: Blink
|
|
817
|
+
|
|
818
|
+
class DeletingTextExample(Scene):
|
|
819
|
+
def construct(self):
|
|
820
|
+
text = Text("Deleting", color=PURPLE).scale(1.5).to_edge(LEFT)
|
|
821
|
+
cursor = Rectangle(
|
|
822
|
+
color = GREY_A,
|
|
823
|
+
fill_color = GREY_A,
|
|
824
|
+
fill_opacity = 1.0,
|
|
825
|
+
height = 1.1,
|
|
826
|
+
width = 0.5,
|
|
827
|
+
).move_to(text[0]) # Position the cursor
|
|
828
|
+
|
|
829
|
+
self.play(UntypeWithCursor(text, cursor))
|
|
830
|
+
self.play(Blink(cursor, blinks=2))
|
|
831
|
+
|
|
832
|
+
"""
|
|
833
|
+
|
|
834
|
+
def __init__(
|
|
835
|
+
self,
|
|
836
|
+
text: Text,
|
|
837
|
+
cursor: VMobject | None = None,
|
|
838
|
+
time_per_char: float = 0.1,
|
|
839
|
+
reverse_rate_function=True,
|
|
840
|
+
introducer=False,
|
|
841
|
+
remover=True,
|
|
842
|
+
**kwargs,
|
|
843
|
+
) -> None:
|
|
844
|
+
super().__init__(
|
|
845
|
+
text,
|
|
846
|
+
cursor=cursor,
|
|
847
|
+
time_per_char=time_per_char,
|
|
848
|
+
reverse_rate_function=reverse_rate_function,
|
|
849
|
+
introducer=introducer,
|
|
850
|
+
remover=remover,
|
|
851
|
+
**kwargs,
|
|
852
|
+
)
|