2
2
3
3
__all__ = ["ManimBanner" ]
4
4
5
- import numpy as np
6
-
7
5
from ..animation .composition import AnimationGroup , Succession
8
6
from ..animation .fading import FadeIn
9
- from ..animation .transform import ApplyMethod
7
+ from ..animation .update import UpdateFromAlphaFunc
10
8
from ..constants import DOWN , LEFT , ORIGIN , RIGHT , TAU , UP
11
9
from ..mobject .geometry import Circle , Square , Triangle
12
10
from ..mobject .svg .tex_mobject import MathTex , Tex
13
11
from ..mobject .types .vectorized_mobject import VGroup
14
- from ..mobject .value_tracker import ValueTracker
15
- from ..utils .space_ops import normalize
12
+ from ..utils .rate_functions import ease_in_out_cubic , ease_out_sine , smooth
16
13
from ..utils .tex_templates import TexFontTemplates
17
14
18
15
@@ -30,27 +27,26 @@ class ManimBanner(VGroup):
30
27
31
28
Examples
32
29
--------
30
+ .. manim:: DarkThemeBanner
33
31
34
- .. manim:: BannerDarkBackground
35
-
36
- class BannerDarkBackground(Scene):
32
+ class DarkThemeBanner(Scene):
37
33
def construct(self):
38
- banner = ManimBanner().scale(0.5).to_corner(DR)
39
- self.play(FadeIn( banner))
34
+ banner = ManimBanner()
35
+ self.play(banner.create( ))
40
36
self.play(banner.expand())
41
- self.play(FadeOut(banner))
37
+ self.wait()
38
+ self.play(Unwrite(banner))
42
39
43
- .. manim:: BannerLightBackground
40
+ .. manim:: LightThemeBanner
44
41
45
- class BannerLightBackground (Scene):
42
+ class LightThemeBanner (Scene):
46
43
def construct(self):
47
44
self.camera.background_color = "#ece6e2"
48
- banner_large = ManimBanner(dark_theme=False).scale(0.7)
49
- banner_small = ManimBanner(dark_theme=False).scale(0.35)
50
- banner_small.next_to(banner_large, DOWN)
51
- self.play(banner_large.create(), banner_small.create())
52
- self.play(banner_large.expand(), banner_small.expand())
53
- self.play(FadeOut(banner_large), FadeOut(banner_small))
45
+ banner = ManimBanner(dark_theme=False)
46
+ self.play(banner.create())
47
+ self.play(banner.expand())
48
+ self.wait()
49
+ self.play(Unwrite(banner))
54
50
55
51
"""
56
52
@@ -71,7 +67,8 @@ def __init__(self, dark_theme: bool = True):
71
67
self .circle = Circle (color = logo_green , fill_opacity = 1 ).shift (LEFT )
72
68
self .square = Square (color = logo_blue , fill_opacity = 1 ).shift (UP )
73
69
self .triangle = Triangle (color = logo_red , fill_opacity = 1 ).shift (RIGHT )
74
- self .add (self .triangle , self .square , self .circle , self .M )
70
+ self .shapes = VGroup (self .triangle , self .square , self .circle )
71
+ self .add (self .shapes , self .M )
75
72
self .move_to (ORIGIN )
76
73
77
74
anim = VGroup ()
@@ -90,7 +87,6 @@ def __init__(self, dark_theme: bool = True):
90
87
# Note: "anim" is only shown in the expanded state
91
88
# and thus not yet added to the submobjects of self.
92
89
self .anim = anim
93
- self .anim .set_opacity (0 )
94
90
95
91
def scale (self , scale_factor : float , ** kwargs ) -> "ManimBanner" :
96
92
"""Scale the banner by the specified scale factor.
@@ -111,54 +107,48 @@ def scale(self, scale_factor: float, **kwargs) -> "ManimBanner":
111
107
self .anim .scale (scale_factor , ** kwargs )
112
108
return super ().scale (scale_factor , ** kwargs )
113
109
114
- def create (self ) :
110
+ def create (self , run_time : float = 2 ) -> AnimationGroup :
115
111
"""The creation animation for Manim's logo.
116
112
113
+ Parameters
114
+ ----------
115
+ run_time
116
+ The run time of the animation.
117
+
117
118
Returns
118
119
-------
119
120
:class:`~.AnimationGroup`
120
121
An animation to be used in a :meth:`.Scene.play` call.
121
122
"""
122
- shape_center = VGroup (self .circle , self .square , self .triangle ).get_center ()
123
-
124
- spiral_run_time = 2.1
123
+ shape_center = self .shapes .get_center ()
125
124
expansion_factor = 8 * self .scale_factor
126
125
127
- tracker = ValueTracker (0 )
128
-
129
- for mob in [self .circle , self .square , self .triangle ]:
130
- mob .final_position = mob .get_center ()
131
- mob .initial_position = (
132
- mob .final_position
133
- + (mob .final_position - shape_center ) * expansion_factor
126
+ for shape in self .shapes :
127
+ shape .final_position = shape .get_center ()
128
+ shape .initial_position = (
129
+ shape .final_position
130
+ + (shape .final_position - shape_center ) * expansion_factor
134
131
)
135
- mob .initial_to_final_distance = np .linalg .norm (
136
- mob .final_position - mob .initial_position
137
- )
138
- mob .move_to (mob .initial_position )
139
- mob .current_time = 0
140
- mob .starting_mobject = mob .copy ()
141
-
142
- def updater (mob , dt ):
143
- mob .become (mob .starting_mobject )
144
- direction = shape_center - mob .get_center ()
145
- mob .shift (
146
- normalize (direction , fall_back = direction )
147
- * mob .initial_to_final_distance
148
- * tracker .get_value ()
149
- )
150
- mob .rotate (TAU * tracker .get_value (), about_point = shape_center )
151
- mob .rotate (- TAU * tracker .get_value ())
132
+ shape .move_to (shape .initial_position )
133
+ shape .save_state ()
152
134
153
- mob .add_updater (updater )
154
-
155
- spin_animation = ApplyMethod (tracker .set_value , 1 , run_time = spiral_run_time )
135
+ def spiral_updater (shapes , alpha ):
136
+ for shape in shapes :
137
+ shape .restore ()
138
+ shape .shift ((shape .final_position - shape .initial_position ) * alpha )
139
+ shape .rotate (TAU * alpha , about_point = shape_center )
140
+ shape .rotate (- TAU * alpha , about_point = shape .get_center_of_mass ())
141
+ shape .set_opacity (min (1 , alpha * 3 ))
156
142
157
143
return AnimationGroup (
158
- FadeIn (self , run_time = spiral_run_time / 2 ), spin_animation
144
+ UpdateFromAlphaFunc (
145
+ self .shapes , spiral_updater , run_time = run_time , rate_func = ease_out_sine
146
+ ),
147
+ FadeIn (self .M , run_time = run_time / 2 ),
148
+ lag_ratio = 0.1 ,
159
149
)
160
150
161
- def expand (self ) -> Succession :
151
+ def expand (self , run_time : float = 1.5 , direction = "center" ) -> Succession :
162
152
"""An animation that expands Manim's logo into its banner.
163
153
164
154
The returned animation transforms the banner from its initial
@@ -174,34 +164,96 @@ def expand(self) -> Succession:
174
164
it is added as a submobject so subsequent animations
175
165
to the banner object apply to the text "anim" as well.
176
166
167
+ Parameters
168
+ ----------
169
+ run_time
170
+ The run time of the animation.
171
+ direction
172
+ The direction in which the logo is expanded.
173
+
177
174
Returns
178
175
-------
179
176
:class:`~.Succession`
180
177
An animation to be used in a :meth:`.Scene.play` call.
181
178
179
+ Examples
180
+ --------
181
+ .. manim:: ExpandDirections
182
+
183
+ class ExpandDirections(Scene):
184
+ def construct(self):
185
+ banners = [ManimBanner().scale(0.5).shift(UP*x) for x in [-2, 0, 2]]
186
+ self.play(
187
+ banners[0].expand(direction="right"),
188
+ banners[1].expand(direction="center"),
189
+ banners[2].expand(direction="left"),
190
+ )
191
+
182
192
"""
193
+ if direction not in ["left" , "right" , "center" ]:
194
+ raise ValueError ("direction must be 'left', 'right' or 'center'." )
195
+
183
196
m_shape_offset = 6.25 * self .scale_factor
197
+ shape_sliding_overshoot = self .scale_factor * 0.8
184
198
m_anim_buff = 0.06
185
- self .add (self .anim )
186
- self .anim .next_to (self .M , buff = m_anim_buff ).shift (
187
- m_shape_offset * LEFT
188
- ).align_to (self .M , DOWN )
189
- move_left = AnimationGroup (
190
- ApplyMethod (self .triangle .shift , m_shape_offset * LEFT ),
191
- ApplyMethod (self .square .shift , m_shape_offset * LEFT ),
192
- ApplyMethod (self .circle .shift , m_shape_offset * LEFT ),
193
- ApplyMethod (self .M .shift , m_shape_offset * LEFT ),
194
- )
195
- move_right = AnimationGroup (
196
- ApplyMethod (self .triangle .shift , m_shape_offset * RIGHT ),
197
- ApplyMethod (self .square .shift , m_shape_offset * RIGHT ),
198
- ApplyMethod (self .circle .shift , m_shape_offset * RIGHT ),
199
- ApplyMethod (self .M .shift , 0 * LEFT ),
200
- AnimationGroup (
201
- * [ApplyMethod (obj .set_opacity , 1 ) for obj in self .anim ], lag_ratio = 0.15
199
+ self .anim .next_to (self .M , buff = m_anim_buff ).align_to (self .M , DOWN )
200
+ self .anim .set_opacity (0 )
201
+ self .shapes .save_state ()
202
+ m_clone = self .anim [- 1 ].copy ()
203
+ self .add (m_clone )
204
+ m_clone .move_to (self .shapes )
205
+
206
+ self .M .save_state ()
207
+ left_group = VGroup (self .M , self .anim , m_clone )
208
+
209
+ def shift (vector ):
210
+ self .shapes .restore ()
211
+ left_group .align_to (self .M .saved_state , LEFT )
212
+ if direction == "right" :
213
+ self .shapes .shift (vector )
214
+ elif direction == "center" :
215
+ self .shapes .shift (vector / 2 )
216
+ left_group .shift (- vector / 2 )
217
+ elif direction == "left" :
218
+ left_group .shift (- vector )
219
+
220
+ def slide_and_uncover (mob , alpha ):
221
+ shift (alpha * (m_shape_offset + shape_sliding_overshoot ) * RIGHT )
222
+
223
+ # Add letters when they are covered
224
+ for letter in mob .anim :
225
+ if mob .square .get_center ()[0 ] > letter .get_center ()[0 ]:
226
+ letter .set_opacity (1 )
227
+ self .add (letter )
228
+
229
+ # Finish animation
230
+ if alpha == 1 :
231
+ self .remove (* [self .anim ])
232
+ self .add_to_back (self .anim )
233
+ mob .shapes .set_z_index (0 )
234
+ mob .shapes .save_state ()
235
+ mob .M .save_state ()
236
+
237
+ def slide_back (mob , alpha ):
238
+ if alpha == 0 :
239
+ m_clone .set_opacity (1 )
240
+ m_clone .move_to (mob .anim [- 1 ])
241
+ mob .anim .set_opacity (1 )
242
+
243
+ shift (alpha * shape_sliding_overshoot * LEFT )
244
+
245
+ if alpha == 1 :
246
+ mob .remove (m_clone )
247
+ mob .add_to_back (mob .shapes )
248
+
249
+ return Succession (
250
+ UpdateFromAlphaFunc (
251
+ self ,
252
+ slide_and_uncover ,
253
+ run_time = run_time * 2 / 3 ,
254
+ rate_func = ease_in_out_cubic ,
255
+ ),
256
+ UpdateFromAlphaFunc (
257
+ self , slide_back , run_time = run_time * 1 / 3 , rate_func = smooth
202
258
),
203
- # It would be nice to have the last AnimationGroup replaced by
204
- # FadeIn(self.anim, lag_ratio=1)
205
- # Currently not working though.
206
259
)
207
- return Succession (move_left , move_right )
0 commit comments