Search code examples
pythonanimationgraphicsmanim

Problem in manim animation only last 2 seconds


class PendBall(Scene):
    def construct(self):
        square = Square(side_length=1.5, fill_opacity=1, fill_color=BLUE_E)
        line = Line(ORIGIN, [0, 4, 0], buff=0)

        bullet = Circle(radius=0.2, fill_color=RED, fill_opacity=1)
        bullet.shift(6*LEFT)
        self.flag = False

        def updatebullet(bullet: Mobject, dt):
            if bullet.get_x() >= square.get_left()[0] or self.flag:
                bullet_vel = -1
                self.flag = True
                self.play(Rotate(line, angle=PI/4,
                                 about_point=[0, 4, 0], run_time=10),
                          Rotate(square, angle=PI/4, about_point=[0, 4, 0], run_time=10))


            else:
                bullet_vel = 1
            bullet.shift(np.array([bullet_vel, 0, 0])*dt)

        bullet.add_updater(updatebullet)

        self.play(FadeIn(square, line))
        self.play(FadeIn(bullet))
        self.wait(10)

the animation only lasts for two seconds and also seems like is getting stuck on an infinite loop

Error

when I add the runtimes in the rotation class interpreter get stuck into an infinite loop when I ELIMINATE the runtimes the animation only lasts 2 sec

when trying to add run time to the fadein bullet I get the error ValueError: write to closed file


Solution

  • The actual problem is that you cannot call self.play() inside an updater. But you have a little nice function that can be useful here. It is turn_animation_into_updater(). This one can be called from an updater, and creates another updater which plays that animation (in parallel with others), until the animation reach its end, at which moment the auxiliar updater is removed.

    Other observations about your code:

    • You dont want to set the velocity back to 1, once it was changed to -1. So the else part can be removed
    • You don't need a flag. You can test if the velocity is positive
    • You can store the velocity as part of the bullet object, so that it is preserverd between updater calls.
    • To animate the block, you may want to use an appropiate rate_func which decreases its velocity when the animation is reaching its end.

    With all these ideas:

    class Test(Scene):
        def construct(self):
            square = Square(side_length=1.5, fill_opacity=1, fill_color=BLUE_E)
            line = Line(ORIGIN, [0, 4, 0], buff=0)
    
            bullet = Circle(radius=0.2, fill_color=RED, fill_opacity=1)
            bullet.shift(6*LEFT)
            bullet.velocity = 2
            ease_out_sine = rate_functions.ease_out_sine
    
            def updatebullet(bullet: Mobject, dt):
                if bullet.get_x() >= square.get_left()[0] and bullet.velocity > 0:
                    bullet.velocity *= -1
                    turn_animation_into_updater(
                        Rotate(line, angle=PI/4, about_point=[0, 4, 0], 
                               run_time=5, rate_func=ease_out_sine))
                    turn_animation_into_updater(
                        Rotate(square, angle=PI/4, about_point=[0, 4, 0], 
                               run_time=5, rate_func=ease_out_sine))
                bullet.shift(np.array([bullet.velocity, 0, 0])*dt)
    
            bullet.add_updater(updatebullet)
            square.add_updater(lambda m,dt: None)
            line.add_updater(lambda m,dt: None)
            self.play(FadeIn(square, line))
            self.play(FadeIn(bullet))
            self.wait(10)
    

    Which produces:

    Result