Description
First of all: This might just as well be a bug report - I am unsure...
Motivation
Look at the following scene:
WantedScene.mp4
Swiping a line across the Scene and playing a Flash as soon as it hits a dot... Or more generally: Animate one thing and play additional animations based on the state of the animated thing. Sounds like a job for an update function. Consider the following code:
class UsingUpdater(Scene):
def construct(self):
#Setup
dots = VGroup(*[Dot(RIGHT*random.randrange(-6, 6)+UP*random.randrange(-2, 2)) for i in range(10)])
line = Line(UP*3, DOWN*3).align_on_border(LEFT)
self.add(dots)
self.play(Create(line))
#Updater
def flash_hit_dots(mob):
for dot in dots:
if dot.get_center()[0] < mob.get_center()[0] and not hasattr(dot, "marked"):
self.play(Flash(dot, run_time=0.5))
dot.marked = True
line.add_updater(flash_hit_dots)
#Animate Line
self.play(line.animate.align_on_border(RIGHT), run_time=5)
self.wait()
However, this results in ValueError: write to closed file
.
Description of proposed feature
The feature request is quite simple, but I think it is hard to implement: Allow using Scene.play()
while an Animation is running.
Additional comments
I have seen quite a few different workarounds for this problem, even in 3b1b's code. Some are based on splitting the animation at each new event, others are based on precalculating all timings in advance - but all of them have one thing in common: They are tedious and error-prone. In case you are wondering, how I created the example Scene: Have a look at this mess...
class Workaround(Scene):
def construct(self):
#Setup
dots = VGroup(*[Dot(RIGHT*random.randrange(-6, 6)+UP*random.randrange(-2, 2)) for i in range(10)])
line = Line(UP*3, DOWN*3).align_on_border(LEFT)
self.add(dots)
self.play(Create(line))
run_time = 5
rate_func = smooth
fps = config["frame_rate"]
#First pass to determine frames to start the flash:
animations = []
frames = run_time*fps
x_0 = line.get_center()[0]
x_1 = line.copy().align_on_border(RIGHT).get_center()[0]
for t in range(frames):
alpha = rate_func(t/frames)
x = x_0 + alpha*(x_1-x_0)
for dot in dots:
if dot.get_center()[0] < x and not hasattr(dot, "marked"):
#create dummy wait animation:
wait = FadeOut(Mobject(), run_time=t/fps)
animations.append(AnimationGroup(wait, Flash(dot, run_time=0.5), lag_ratio=1))
dot.marked = True
#Run the animations
self.play(AnimationGroup(
ApplyMethod(line.align_on_border, RIGHT, run_time=run_time, rate_func=rate_func),
*animations
))
self.wait()
Metadata
Metadata
Assignees
Type
Projects
Status