I would like to create an animation with manim
, where I have a dot that turns on (instantly become visible) for t_on
seconds, and then turns off (instantly become invisible) for t_off
seconds. And this procedure repeats for a given total duration, e.g., t_total
.
I have written the following code, but it just creates a black animation with no content. Note the values in the below example are t_on=1.5
, t_off=0.5
, and t_total=6
.
from manim import *
class RecursiveDot(Scene):
def construct(self):
dot = Dot(color=BLUE, radius=0.2)
animation = self.create_animation(dot, 6, 1.5, 0.5)
self.play(animation)
def create_animation(self, dot, total_duration, visible_duration, invisible_duration):
num_cycles = int(total_duration // (visible_duration + invisible_duration))
visible_animation = Create(dot, run_time=0)
invisible_animation = Uncreate(dot, run_time=0)
animations = []
for _ in range(num_cycles):
animations.append(visible_animation)
animations.append(Wait(invisible_duration))
animations.append(invisible_animation)
animations.append(Wait(visible_duration))
return AnimationGroup(*animations, run_time=total_duration)
And I generate the vide using manim -p -ql dpath_to_where_I_saved_the_module.py RecursiveDot
I think that the use of Create()
and Uncreate()
is not a good choice, because Create
is considered by manim an animation of type "introducer", and Uncreate()
of type "remover". The first one will be run only once (even if it appears several times in the AnimationGroup) and the second removes the object from the scene once it ends (the first time).
I would use instead dot.animate.set_opacity()
to change the opacity of the dot between 1 and 0. Since these .animate
based transformations are not proper animations, it is neccessary to call .build()
after them (.play()
does that for you, but if it is part of an AnimationGroup you have to do that manually).
Also, instead of AnimationGroup()
I would use Succession()
, that waits until one animation in the list is completed before starting the following one.
Finally, I think you had reversed your Wait()
.
So my proposed code is:
class Test(Scene):
def construct(self):
dot = Dot(color=BLUE, radius=0.2)
animation = self.create_animation(dot, 6, 1.5, 0.5)
self.play(animation)
def create_animation(self, dot, total_duration, visible_duration, invisible_duration):
num_cycles = int(total_duration // (visible_duration + invisible_duration))
animations = []
for _ in range(num_cycles):
animations.append(dot.animate(run_time=0).set_opacity(1).build())
animations.append(Wait(visible_duration))
animations.append(dot.animate(run_time=0).set_opacity(0).build())
animations.append(Wait(invisible_duration))
return Succession(*animations, run_time=total_duration)
Which results in:
Update
For debugging purposes, the pass of time can be made visible with the following addition to the scene (before the main animation is called)
time = 0
t = always_redraw(lambda :
Text(f"Time: {time:.1f}", font_size=24).to_corner(UL)
)
def time_tracker(dt):
nonlocal time
time += dt
self.add_updater(time_tracker)
self.add(t)
This produces