2018-12-24 12:37:51 -08:00
|
|
|
from time import sleep
|
|
|
|
import _thread as thread
|
|
|
|
import datetime
|
2018-03-31 15:11:35 -07:00
|
|
|
import inspect
|
2015-03-26 22:49:22 -06:00
|
|
|
import itertools as it
|
2015-03-22 16:15:29 -06:00
|
|
|
import os
|
2018-03-31 15:11:35 -07:00
|
|
|
import random
|
2017-09-27 17:29:13 +08:00
|
|
|
import shutil
|
2016-02-23 22:29:32 -08:00
|
|
|
import subprocess as sp
|
2018-03-31 15:11:35 -07:00
|
|
|
import warnings
|
|
|
|
|
|
|
|
from tqdm import tqdm as ProgressDisplay
|
2018-12-24 12:37:51 -08:00
|
|
|
import numpy as np
|
2015-03-22 16:15:29 -06:00
|
|
|
|
2018-12-24 12:37:51 -08:00
|
|
|
from manimlib.animation.animation import Animation
|
|
|
|
from manimlib.animation.creation import Write
|
|
|
|
from manimlib.animation.transform import MoveToTarget, ApplyMethod
|
|
|
|
from manimlib.camera.camera import Camera
|
|
|
|
from manimlib.constants import *
|
|
|
|
from manimlib.container.container import Container
|
|
|
|
from manimlib.continual_animation.continual_animation import ContinualAnimation
|
|
|
|
from manimlib.mobject.mobject import Mobject
|
|
|
|
from manimlib.mobject.svg.tex_mobject import TextMobject
|
|
|
|
from manimlib.utils.iterables import list_update
|
|
|
|
from manimlib.utils.output_directory_getters import add_extension_if_not_present
|
|
|
|
from manimlib.utils.output_directory_getters import get_image_output_directory
|
|
|
|
from manimlib.utils.output_directory_getters import get_movie_output_directory
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2018-01-29 13:34:06 -08:00
|
|
|
class Scene(Container):
|
2016-02-27 16:32:53 -08:00
|
|
|
CONFIG = {
|
2018-04-06 13:58:59 -07:00
|
|
|
"camera_class": Camera,
|
|
|
|
"camera_config": {},
|
|
|
|
"frame_duration": LOW_QUALITY_FRAME_DURATION,
|
|
|
|
"construct_args": [],
|
|
|
|
"skip_animations": False,
|
|
|
|
"ignore_waits": False,
|
|
|
|
"write_to_movie": False,
|
|
|
|
"save_frames": False,
|
|
|
|
"save_pngs": False,
|
|
|
|
"pngs_mode": "RGBA",
|
|
|
|
"movie_file_extension": ".mp4",
|
|
|
|
"name": None,
|
|
|
|
"always_continually_update": False,
|
|
|
|
"random_seed": 0,
|
|
|
|
"start_at_animation_number": None,
|
|
|
|
"end_at_animation_number": None,
|
2018-12-22 14:27:22 -08:00
|
|
|
"livestreaming": False,
|
|
|
|
"to_twitch": False,
|
2018-12-22 18:25:35 -08:00
|
|
|
"twitch_key": None,
|
2015-09-28 16:25:18 -07:00
|
|
|
}
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2015-09-28 16:25:18 -07:00
|
|
|
def __init__(self, **kwargs):
|
2018-04-06 13:58:59 -07:00
|
|
|
# Perhaps allow passing in a non-empty *mobjects parameter?
|
|
|
|
Container.__init__(self, **kwargs)
|
2017-02-02 15:36:24 -08:00
|
|
|
self.camera = self.camera_class(**self.camera_config)
|
2015-10-29 13:45:28 -07:00
|
|
|
self.mobjects = []
|
2017-08-24 11:43:38 -07:00
|
|
|
self.continual_animations = []
|
2017-05-09 12:25:28 -07:00
|
|
|
self.foreground_mobjects = []
|
2016-07-28 11:16:07 -07:00
|
|
|
self.num_plays = 0
|
2017-05-09 13:14:08 -07:00
|
|
|
self.saved_frames = []
|
2017-05-10 17:22:26 -07:00
|
|
|
self.shared_locals = {}
|
2017-12-22 13:00:13 +01:00
|
|
|
self.frame_num = 0
|
2018-01-28 20:49:02 +01:00
|
|
|
self.current_scene_time = 0
|
2018-02-20 22:59:38 -08:00
|
|
|
self.original_skipping_status = self.skip_animations
|
2018-11-01 11:23:34 +03:00
|
|
|
self.stream_lock = False
|
2017-05-09 14:54:50 -07:00
|
|
|
if self.name is None:
|
|
|
|
self.name = self.__class__.__name__
|
2017-12-06 15:17:59 -08:00
|
|
|
if self.random_seed is not None:
|
|
|
|
random.seed(self.random_seed)
|
|
|
|
np.random.seed(self.random_seed)
|
2015-10-29 13:45:28 -07:00
|
|
|
|
2016-08-10 10:26:07 -07:00
|
|
|
self.setup()
|
2017-05-09 13:14:08 -07:00
|
|
|
if self.write_to_movie:
|
|
|
|
self.open_movie_pipe()
|
2018-12-22 14:27:22 -08:00
|
|
|
if self.livestreaming:
|
2018-10-30 09:00:51 +03:00
|
|
|
return None
|
2018-02-19 17:24:17 -08:00
|
|
|
try:
|
|
|
|
self.construct(*self.construct_args)
|
|
|
|
except EndSceneEarlyException:
|
|
|
|
pass
|
2018-02-20 17:50:39 -08:00
|
|
|
|
|
|
|
# Always tack on one last frame, so that scenes
|
|
|
|
# with no play calls still display something
|
|
|
|
self.skip_animations = False
|
|
|
|
self.wait(self.frame_duration)
|
|
|
|
|
2017-05-09 13:14:08 -07:00
|
|
|
if self.write_to_movie:
|
|
|
|
self.close_movie_pipe()
|
2018-04-06 13:58:59 -07:00
|
|
|
print("Played a total of %d animations" % self.num_plays)
|
2015-06-10 22:00:35 -07:00
|
|
|
|
2016-08-10 10:26:07 -07:00
|
|
|
def setup(self):
|
2017-04-21 17:40:49 -07:00
|
|
|
"""
|
|
|
|
This is meant to be implement by any scenes which
|
2018-02-26 19:07:57 -08:00
|
|
|
are comonly subclassed, and have some common setup
|
2017-04-21 17:40:49 -07:00
|
|
|
involved before the construct method is called.
|
|
|
|
"""
|
|
|
|
pass
|
2016-08-10 10:26:07 -07:00
|
|
|
|
2017-10-16 19:21:42 -07:00
|
|
|
def setup_bases(self):
|
|
|
|
for base in self.__class__.__bases__:
|
|
|
|
base.setup(self)
|
|
|
|
|
2015-06-10 22:00:35 -07:00
|
|
|
def construct(self):
|
2018-04-06 13:58:59 -07:00
|
|
|
pass # To be implemented in subclasses
|
2015-03-22 16:15:29 -06:00
|
|
|
|
|
|
|
def __str__(self):
|
2017-05-09 14:54:50 -07:00
|
|
|
return self.name
|
2015-10-12 19:39:46 -07:00
|
|
|
|
2015-04-03 16:41:25 -07:00
|
|
|
def set_name(self, name):
|
|
|
|
self.name = name
|
2015-06-09 11:26:12 -07:00
|
|
|
return self
|
2015-04-03 16:41:25 -07:00
|
|
|
|
2017-10-20 16:29:30 -07:00
|
|
|
def set_variables_as_attrs(self, *objects, **newly_named_objects):
|
2017-05-10 17:22:26 -07:00
|
|
|
"""
|
2017-10-20 16:29:30 -07:00
|
|
|
This method is slightly hacky, making it a little easier
|
|
|
|
for certain methods (typically subroutines of construct)
|
|
|
|
to share local variables.
|
2017-05-10 17:22:26 -07:00
|
|
|
"""
|
|
|
|
caller_locals = inspect.currentframe().f_back.f_locals
|
2018-08-09 17:56:05 -07:00
|
|
|
for key, value in list(caller_locals.items()):
|
2017-10-24 13:41:28 -07:00
|
|
|
for o in objects:
|
|
|
|
if value is o:
|
|
|
|
setattr(self, key, value)
|
2018-08-09 17:56:05 -07:00
|
|
|
for key, value in list(newly_named_objects.items()):
|
2017-10-20 16:29:30 -07:00
|
|
|
setattr(self, key, value)
|
2017-05-10 17:22:26 -07:00
|
|
|
return self
|
|
|
|
|
2017-10-20 16:29:30 -07:00
|
|
|
def get_attrs(self, *keys):
|
|
|
|
return [getattr(self, key) for key in keys]
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
# Only these methods should touch the camera
|
2016-03-07 19:07:00 -08:00
|
|
|
|
2016-02-27 16:29:11 -08:00
|
|
|
def set_camera(self, camera):
|
|
|
|
self.camera = camera
|
|
|
|
|
2015-10-29 13:45:28 -07:00
|
|
|
def get_frame(self):
|
2017-09-26 17:41:45 -07:00
|
|
|
return np.array(self.camera.get_pixel_array())
|
|
|
|
|
|
|
|
def get_image(self):
|
2016-02-23 22:29:32 -08:00
|
|
|
return self.camera.get_image()
|
2015-10-29 13:45:28 -07:00
|
|
|
|
2017-09-26 17:41:45 -07:00
|
|
|
def set_camera_pixel_array(self, pixel_array):
|
|
|
|
self.camera.set_pixel_array(pixel_array)
|
2016-11-24 10:38:52 -08:00
|
|
|
|
2016-11-11 11:18:41 -08:00
|
|
|
def set_camera_background(self, background):
|
2016-11-23 17:50:25 -08:00
|
|
|
self.camera.set_background(background)
|
2016-11-11 11:18:41 -08:00
|
|
|
|
|
|
|
def reset_camera(self):
|
|
|
|
self.camera.reset()
|
|
|
|
|
|
|
|
def capture_mobjects_in_camera(self, mobjects, **kwargs):
|
|
|
|
self.camera.capture_mobjects(mobjects, **kwargs)
|
|
|
|
|
2018-01-17 21:32:50 -08:00
|
|
|
def update_frame(
|
2018-04-06 13:58:59 -07:00
|
|
|
self,
|
|
|
|
mobjects=None,
|
|
|
|
background=None,
|
|
|
|
include_submobjects=True,
|
|
|
|
dont_update_when_skipping=True,
|
|
|
|
**kwargs):
|
2018-02-19 17:24:17 -08:00
|
|
|
if self.skip_animations and dont_update_when_skipping:
|
|
|
|
return
|
2016-03-08 23:13:41 -08:00
|
|
|
if mobjects is None:
|
2017-09-11 06:52:15 -07:00
|
|
|
mobjects = list_update(
|
|
|
|
self.mobjects,
|
2018-01-17 21:32:50 -08:00
|
|
|
self.foreground_mobjects,
|
2017-09-11 06:52:15 -07:00
|
|
|
)
|
2016-03-07 19:07:00 -08:00
|
|
|
if background is not None:
|
2017-09-26 17:41:45 -07:00
|
|
|
self.set_camera_pixel_array(background)
|
2016-03-07 19:07:00 -08:00
|
|
|
else:
|
2016-11-11 11:18:41 -08:00
|
|
|
self.reset_camera()
|
2017-08-24 11:43:38 -07:00
|
|
|
|
2018-01-17 21:32:50 -08:00
|
|
|
kwargs["include_submobjects"] = include_submobjects
|
2016-11-11 11:18:41 -08:00
|
|
|
self.capture_mobjects_in_camera(mobjects, **kwargs)
|
2016-03-17 23:53:59 -07:00
|
|
|
|
|
|
|
def freeze_background(self):
|
|
|
|
self.update_frame()
|
|
|
|
self.set_camera(Camera(self.get_frame()))
|
|
|
|
self.clear()
|
2016-03-07 19:07:00 -08:00
|
|
|
###
|
|
|
|
|
2018-08-12 12:17:32 -07:00
|
|
|
def continual_update(self, dt):
|
2018-11-04 16:18:14 -08:00
|
|
|
for mobject in self.get_mobject_family_members():
|
2018-08-12 12:17:32 -07:00
|
|
|
mobject.update(dt)
|
2017-08-24 11:43:38 -07:00
|
|
|
for continual_animation in self.continual_animations:
|
2017-08-31 23:20:16 -07:00
|
|
|
continual_animation.update(dt)
|
2017-08-24 11:43:38 -07:00
|
|
|
|
|
|
|
def wind_down(self, *continual_animations, **kwargs):
|
2017-08-24 12:38:37 -07:00
|
|
|
wind_down_time = kwargs.get("wind_down_time", 1)
|
2017-08-24 11:43:38 -07:00
|
|
|
for continual_animation in continual_animations:
|
2017-08-24 12:38:37 -07:00
|
|
|
continual_animation.begin_wind_down(wind_down_time)
|
2018-01-15 19:15:05 -08:00
|
|
|
self.wait(wind_down_time)
|
2018-04-06 13:58:59 -07:00
|
|
|
# TODO, this is not done with the remove method so as to
|
|
|
|
# keep the relevant mobjects. Better way?
|
2018-08-09 17:56:05 -07:00
|
|
|
self.continual_animations = [ca for ca in self.continual_animations if ca in continual_animations]
|
2017-08-24 19:05:04 -07:00
|
|
|
|
|
|
|
def should_continually_update(self):
|
2018-08-12 12:17:32 -07:00
|
|
|
if self.always_continually_update:
|
|
|
|
return True
|
|
|
|
if len(self.continual_animations) > 0:
|
|
|
|
return True
|
|
|
|
any_time_based_update = any([
|
|
|
|
len(m.get_time_based_updaters()) > 0
|
2018-11-04 16:18:14 -08:00
|
|
|
for m in self.get_mobject_family_members()
|
2018-08-12 12:17:32 -07:00
|
|
|
])
|
|
|
|
if any_time_based_update:
|
|
|
|
return True
|
|
|
|
return False
|
2017-08-24 19:05:04 -07:00
|
|
|
|
|
|
|
###
|
2017-08-24 11:43:38 -07:00
|
|
|
|
2017-02-09 21:09:51 -08:00
|
|
|
def get_top_level_mobjects(self):
|
|
|
|
# Return only those which are not in the family
|
|
|
|
# of another mobject from the scene
|
|
|
|
mobjects = self.get_mobjects()
|
2018-08-21 19:15:16 -07:00
|
|
|
families = [m.get_family() for m in mobjects]
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2017-02-09 21:09:51 -08:00
|
|
|
def is_top_level(mobject):
|
|
|
|
num_families = sum([
|
2017-10-05 21:03:30 -05:00
|
|
|
(mobject in family)
|
2017-02-09 21:09:51 -08:00
|
|
|
for family in families
|
|
|
|
])
|
|
|
|
return num_families == 1
|
2018-08-09 17:56:05 -07:00
|
|
|
return list(filter(is_top_level, mobjects))
|
2017-10-05 21:03:30 -05:00
|
|
|
|
2018-08-13 14:13:30 -07:00
|
|
|
def get_mobject_family_members(self):
|
|
|
|
return self.camera.extract_mobject_family_members(self.mobjects)
|
|
|
|
|
2017-08-24 11:43:38 -07:00
|
|
|
def separate_mobjects_and_continual_animations(self, mobjects_or_continual_animations):
|
|
|
|
mobjects = []
|
|
|
|
continual_animations = []
|
|
|
|
for item in mobjects_or_continual_animations:
|
|
|
|
if isinstance(item, Mobject):
|
|
|
|
mobjects.append(item)
|
|
|
|
elif isinstance(item, ContinualAnimation):
|
|
|
|
mobjects.append(item.mobject)
|
|
|
|
continual_animations.append(item)
|
|
|
|
else:
|
|
|
|
raise Exception("""
|
2017-10-05 21:03:30 -05:00
|
|
|
Adding/Removing something which is
|
2017-08-24 11:43:38 -07:00
|
|
|
not a Mobject or a ContinualAnimation
|
|
|
|
""")
|
|
|
|
return mobjects, continual_animations
|
|
|
|
|
|
|
|
def add(self, *mobjects_or_continual_animations):
|
2015-03-26 22:49:22 -06:00
|
|
|
"""
|
|
|
|
Mobjects will be displayed, from background to foreground,
|
|
|
|
in the order with which they are entered.
|
|
|
|
"""
|
2017-08-24 11:43:38 -07:00
|
|
|
mobjects, continual_animations = self.separate_mobjects_and_continual_animations(
|
|
|
|
mobjects_or_continual_animations
|
|
|
|
)
|
2018-08-12 12:17:32 -07:00
|
|
|
mobjects += self.foreground_mobjects
|
2018-04-06 13:58:59 -07:00
|
|
|
self.restructure_mobjects(to_remove=mobjects)
|
2018-01-18 10:21:28 -08:00
|
|
|
self.mobjects += mobjects
|
|
|
|
self.continual_animations += continual_animations
|
2015-06-09 11:26:12 -07:00
|
|
|
return self
|
2015-03-22 16:15:29 -06:00
|
|
|
|
2015-11-02 19:09:55 -08:00
|
|
|
def add_mobjects_among(self, values):
|
2015-10-09 19:53:38 -07:00
|
|
|
"""
|
|
|
|
So a scene can just add all mobjects it's defined up to that point
|
2015-11-02 19:09:55 -08:00
|
|
|
by calling add_mobjects_among(locals().values())
|
2015-10-09 19:53:38 -07:00
|
|
|
"""
|
2018-08-09 17:56:05 -07:00
|
|
|
mobjects = [x for x in values if isinstance(x, Mobject)]
|
2015-11-02 19:09:55 -08:00
|
|
|
self.add(*mobjects)
|
2015-11-02 13:03:01 -08:00
|
|
|
return self
|
2015-10-09 19:53:38 -07:00
|
|
|
|
2017-08-24 11:43:38 -07:00
|
|
|
def remove(self, *mobjects_or_continual_animations):
|
|
|
|
mobjects, continual_animations = self.separate_mobjects_and_continual_animations(
|
|
|
|
mobjects_or_continual_animations
|
|
|
|
)
|
2018-01-18 16:23:31 -08:00
|
|
|
|
2018-01-17 21:32:50 -08:00
|
|
|
to_remove = self.camera.extract_mobject_family_members(mobjects)
|
2018-01-18 16:23:31 -08:00
|
|
|
for list_name in "mobjects", "foreground_mobjects":
|
|
|
|
self.restructure_mobjects(mobjects, list_name, False)
|
2018-01-17 21:32:50 -08:00
|
|
|
|
2018-08-09 17:56:05 -07:00
|
|
|
self.continual_animations = [ca for ca in self.continual_animations if ca not in continual_animations and
|
|
|
|
ca.mobject not in to_remove]
|
2017-03-25 12:17:56 -07:00
|
|
|
return self
|
|
|
|
|
2018-01-18 16:23:31 -08:00
|
|
|
def restructure_mobjects(
|
2018-04-06 13:58:59 -07:00
|
|
|
self, to_remove,
|
|
|
|
mobject_list_name="mobjects",
|
|
|
|
extract_families=True
|
|
|
|
):
|
2018-01-17 21:32:50 -08:00
|
|
|
"""
|
|
|
|
In cases where the scene contains a group, e.g. Group(m1, m2, m3), but one
|
|
|
|
of its submobjects is removed, e.g. scene.remove(m1), the list of mobjects
|
2018-01-17 22:01:20 -08:00
|
|
|
will be editing to contain other submobjects, but not m1, e.g. it will now
|
2018-01-17 21:32:50 -08:00
|
|
|
insert m2 and m3 to where the group once was.
|
|
|
|
"""
|
2018-01-18 16:23:31 -08:00
|
|
|
if extract_families:
|
|
|
|
to_remove = self.camera.extract_mobject_family_members(to_remove)
|
|
|
|
_list = getattr(self, mobject_list_name)
|
|
|
|
new_list = self.get_restructured_mobject_list(_list, to_remove)
|
|
|
|
setattr(self, mobject_list_name, new_list)
|
|
|
|
return self
|
|
|
|
|
|
|
|
def get_restructured_mobject_list(self, mobjects, to_remove):
|
2018-01-17 22:01:20 -08:00
|
|
|
new_mobjects = []
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2018-01-17 22:01:20 -08:00
|
|
|
def add_safe_mobjects_from_list(list_to_examine, set_to_remove):
|
|
|
|
for mob in list_to_examine:
|
|
|
|
if mob in set_to_remove:
|
|
|
|
continue
|
2018-08-21 19:15:16 -07:00
|
|
|
intersect = set_to_remove.intersection(mob.get_family())
|
2018-01-17 22:01:20 -08:00
|
|
|
if intersect:
|
|
|
|
add_safe_mobjects_from_list(mob.submobjects, intersect)
|
|
|
|
else:
|
|
|
|
new_mobjects.append(mob)
|
|
|
|
add_safe_mobjects_from_list(mobjects, set(to_remove))
|
|
|
|
return new_mobjects
|
2015-06-09 11:26:12 -07:00
|
|
|
|
2017-05-09 12:25:28 -07:00
|
|
|
def add_foreground_mobjects(self, *mobjects):
|
|
|
|
self.foreground_mobjects = list_update(
|
2017-10-05 21:03:30 -05:00
|
|
|
self.foreground_mobjects,
|
2017-05-09 12:25:28 -07:00
|
|
|
mobjects
|
|
|
|
)
|
|
|
|
self.add(*mobjects)
|
|
|
|
return self
|
|
|
|
|
|
|
|
def add_foreground_mobject(self, mobject):
|
|
|
|
return self.add_foreground_mobjects(mobject)
|
|
|
|
|
2018-01-18 16:23:31 -08:00
|
|
|
def remove_foreground_mobjects(self, *to_remove):
|
|
|
|
self.restructure_mobjects(to_remove, "foreground_mobjects")
|
2017-05-09 12:25:28 -07:00
|
|
|
return self
|
|
|
|
|
|
|
|
def remove_foreground_mobject(self, mobject):
|
|
|
|
return self.remove_foreground_mobjects(mobject)
|
|
|
|
|
2016-08-02 12:26:15 -07:00
|
|
|
def bring_to_front(self, *mobjects):
|
|
|
|
self.add(*mobjects)
|
2015-10-12 19:39:46 -07:00
|
|
|
return self
|
|
|
|
|
2016-08-02 12:26:15 -07:00
|
|
|
def bring_to_back(self, *mobjects):
|
|
|
|
self.remove(*mobjects)
|
2018-01-17 21:32:50 -08:00
|
|
|
self.mobjects = list(mobjects) + self.mobjects
|
2015-10-12 19:39:46 -07:00
|
|
|
return self
|
|
|
|
|
2015-06-10 22:00:35 -07:00
|
|
|
def clear(self):
|
2015-10-29 13:45:28 -07:00
|
|
|
self.mobjects = []
|
2017-08-24 11:43:38 -07:00
|
|
|
self.foreground_mobjects = []
|
|
|
|
self.continual_animation = []
|
2015-06-10 22:00:35 -07:00
|
|
|
return self
|
2015-03-30 17:51:26 -07:00
|
|
|
|
2016-07-18 11:50:26 -07:00
|
|
|
def get_mobjects(self):
|
|
|
|
return list(self.mobjects)
|
|
|
|
|
|
|
|
def get_mobject_copies(self):
|
|
|
|
return [m.copy() for m in self.mobjects]
|
|
|
|
|
2018-01-17 21:32:50 -08:00
|
|
|
def get_moving_mobjects(self, *animations):
|
2018-08-12 12:17:32 -07:00
|
|
|
# Go through mobjects from start to end, and
|
|
|
|
# as soon as there's one that needs updating of
|
|
|
|
# some kind per frame, return the list from that
|
|
|
|
# point forward.
|
|
|
|
animation_mobjects = [anim.mobject for anim in animations]
|
|
|
|
ca_mobjects = [ca.mobject for ca in self.continual_animations]
|
2018-08-23 14:46:22 -07:00
|
|
|
mobjects = self.get_mobject_family_members()
|
2018-08-12 12:17:32 -07:00
|
|
|
for i, mob in enumerate(mobjects):
|
|
|
|
update_possibilities = [
|
|
|
|
mob in animation_mobjects,
|
|
|
|
mob in ca_mobjects,
|
|
|
|
len(mob.get_updaters()) > 0,
|
|
|
|
mob in self.foreground_mobjects
|
|
|
|
]
|
|
|
|
for possibility in update_possibilities:
|
|
|
|
if possibility:
|
|
|
|
return mobjects[i:]
|
|
|
|
return []
|
2015-03-22 16:15:29 -06:00
|
|
|
|
2017-08-24 11:43:38 -07:00
|
|
|
def get_time_progression(self, run_time):
|
2018-02-19 17:24:17 -08:00
|
|
|
if self.skip_animations:
|
|
|
|
times = [run_time]
|
|
|
|
else:
|
|
|
|
step = self.frame_duration
|
2018-02-20 17:50:39 -08:00
|
|
|
times = np.arange(0, run_time, step)
|
2015-12-31 09:25:36 -08:00
|
|
|
time_progression = ProgressDisplay(times)
|
2017-08-24 11:43:38 -07:00
|
|
|
return time_progression
|
|
|
|
|
|
|
|
def get_animation_time_progression(self, animations):
|
2018-02-19 16:50:58 -08:00
|
|
|
run_time = np.max([animation.run_time for animation in animations])
|
2017-08-24 11:43:38 -07:00
|
|
|
time_progression = self.get_time_progression(run_time)
|
2016-02-27 18:50:33 -08:00
|
|
|
time_progression.set_description("".join([
|
2018-04-06 13:58:59 -07:00
|
|
|
"Animation %d: " % self.num_plays,
|
2016-02-27 18:50:33 -08:00
|
|
|
str(animations[0]),
|
|
|
|
(", etc." if len(animations) > 1 else ""),
|
|
|
|
]))
|
2016-02-27 12:44:52 -08:00
|
|
|
return time_progression
|
|
|
|
|
2016-07-28 11:16:07 -07:00
|
|
|
def compile_play_args_to_animation_list(self, *args):
|
|
|
|
"""
|
2018-01-20 11:19:12 -08:00
|
|
|
Each arg can either be an animation, or a mobject method
|
|
|
|
followed by that methods arguments (and potentially follow
|
|
|
|
by a dict of kwargs for that method).
|
2017-10-05 21:03:30 -05:00
|
|
|
This animation list is built by going through the args list,
|
|
|
|
and each animation is simply added, but when a mobject method
|
|
|
|
s hit, a MoveToTarget animation is built using the args that
|
|
|
|
follow up until either another animation is hit, another method
|
2016-07-28 11:16:07 -07:00
|
|
|
is hit, or the args list runs out.
|
|
|
|
"""
|
|
|
|
animations = []
|
2016-09-06 14:17:39 -07:00
|
|
|
state = {
|
2018-04-06 13:58:59 -07:00
|
|
|
"curr_method": None,
|
|
|
|
"last_method": None,
|
|
|
|
"method_args": [],
|
2016-07-28 11:16:07 -07:00
|
|
|
}
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2016-09-06 14:17:39 -07:00
|
|
|
def compile_method(state):
|
|
|
|
if state["curr_method"] is None:
|
2016-07-28 11:16:07 -07:00
|
|
|
return
|
2018-06-02 08:59:26 -04:00
|
|
|
mobject = state["curr_method"].__self__
|
|
|
|
if state["last_method"] and state["last_method"].__self__ is mobject:
|
2016-09-06 14:17:39 -07:00
|
|
|
animations.pop()
|
2018-04-06 13:58:59 -07:00
|
|
|
# method should already have target then.
|
2016-09-06 14:17:39 -07:00
|
|
|
else:
|
2018-02-26 19:07:57 -08:00
|
|
|
mobject.generate_target()
|
2018-01-20 11:19:12 -08:00
|
|
|
#
|
2018-01-20 11:45:47 -08:00
|
|
|
if len(state["method_args"]) > 0 and isinstance(state["method_args"][-1], dict):
|
2018-01-20 11:19:12 -08:00
|
|
|
method_kwargs = state["method_args"].pop()
|
|
|
|
else:
|
|
|
|
method_kwargs = {}
|
2018-06-02 08:59:26 -04:00
|
|
|
state["curr_method"].__func__(
|
2018-04-06 13:58:59 -07:00
|
|
|
mobject.target,
|
2018-01-20 11:19:12 -08:00
|
|
|
*state["method_args"],
|
|
|
|
**method_kwargs
|
2016-09-06 14:17:39 -07:00
|
|
|
)
|
|
|
|
animations.append(MoveToTarget(mobject))
|
|
|
|
state["last_method"] = state["curr_method"]
|
|
|
|
state["curr_method"] = None
|
|
|
|
state["method_args"] = []
|
2016-09-06 16:48:04 -07:00
|
|
|
|
2016-07-28 11:16:07 -07:00
|
|
|
for arg in args:
|
|
|
|
if isinstance(arg, Animation):
|
2016-09-06 14:17:39 -07:00
|
|
|
compile_method(state)
|
2016-07-28 11:16:07 -07:00
|
|
|
animations.append(arg)
|
|
|
|
elif inspect.ismethod(arg):
|
2016-09-06 14:17:39 -07:00
|
|
|
compile_method(state)
|
|
|
|
state["curr_method"] = arg
|
|
|
|
elif state["curr_method"] is not None:
|
|
|
|
state["method_args"].append(arg)
|
2016-09-07 22:04:24 -07:00
|
|
|
elif isinstance(arg, Mobject):
|
|
|
|
raise Exception("""
|
2017-10-05 21:03:30 -05:00
|
|
|
I think you may have invoked a method
|
2016-09-07 22:04:24 -07:00
|
|
|
you meant to pass in as a Scene.play argument
|
|
|
|
""")
|
2016-07-28 11:16:07 -07:00
|
|
|
else:
|
|
|
|
raise Exception("Invalid play arguments")
|
2016-09-06 14:17:39 -07:00
|
|
|
compile_method(state)
|
2016-07-28 11:16:07 -07:00
|
|
|
return animations
|
2016-02-27 18:50:33 -08:00
|
|
|
|
2018-02-19 17:24:17 -08:00
|
|
|
def handle_animation_skipping(self):
|
2018-02-10 22:45:46 -08:00
|
|
|
if self.start_at_animation_number:
|
|
|
|
if self.num_plays == self.start_at_animation_number:
|
2018-02-20 22:59:38 -08:00
|
|
|
self.skip_animations = False
|
2018-02-10 22:45:46 -08:00
|
|
|
if self.end_at_animation_number:
|
|
|
|
if self.num_plays >= self.end_at_animation_number:
|
2018-02-10 22:34:39 -08:00
|
|
|
self.skip_animations = True
|
2018-02-19 17:24:17 -08:00
|
|
|
raise EndSceneEarlyException()
|
2016-08-25 17:15:48 -07:00
|
|
|
|
2018-02-19 17:24:17 -08:00
|
|
|
def play(self, *args, **kwargs):
|
2018-12-22 14:27:22 -08:00
|
|
|
if self.livestreaming:
|
2018-11-01 11:23:34 +03:00
|
|
|
self.stream_lock = False
|
2018-02-19 17:24:17 -08:00
|
|
|
if len(args) == 0:
|
|
|
|
warnings.warn("Called Scene.play with no animations")
|
|
|
|
return
|
|
|
|
self.handle_animation_skipping()
|
2016-07-28 11:16:07 -07:00
|
|
|
animations = self.compile_play_args_to_animation_list(*args)
|
2018-02-19 16:50:58 -08:00
|
|
|
for animation in animations:
|
|
|
|
# This is where kwargs to play like run_time and rate_func
|
|
|
|
# get applied to all animations
|
|
|
|
animation.update_config(**kwargs)
|
2018-08-12 12:17:32 -07:00
|
|
|
# Anything animated that's not already in the
|
|
|
|
# scene gets added to the scene
|
2018-08-13 14:13:30 -07:00
|
|
|
if animation.mobject not in self.get_mobject_family_members():
|
2018-08-12 12:17:32 -07:00
|
|
|
self.add(animation.mobject)
|
2018-01-17 21:32:50 -08:00
|
|
|
moving_mobjects = self.get_moving_mobjects(*animations)
|
2018-02-19 17:24:17 -08:00
|
|
|
|
|
|
|
# Paint all non-moving objects onto the screen, so they don't
|
|
|
|
# have to be rendered every frame
|
2018-04-06 13:58:59 -07:00
|
|
|
self.update_frame(excluded_mobjects=moving_mobjects)
|
2016-03-07 19:07:00 -08:00
|
|
|
static_image = self.get_frame()
|
2018-08-12 12:17:32 -07:00
|
|
|
total_run_time = 0
|
2017-08-24 11:43:38 -07:00
|
|
|
for t in self.get_animation_time_progression(animations):
|
2015-04-03 16:41:25 -07:00
|
|
|
for animation in animations:
|
|
|
|
animation.update(t / animation.run_time)
|
2018-08-12 19:06:08 -07:00
|
|
|
self.continual_update(dt=t - total_run_time)
|
2016-02-27 18:50:33 -08:00
|
|
|
self.update_frame(moving_mobjects, static_image)
|
2016-09-06 16:48:04 -07:00
|
|
|
self.add_frames(self.get_frame())
|
2018-08-12 12:17:32 -07:00
|
|
|
total_run_time = t
|
|
|
|
self.mobjects_from_last_animation = [
|
|
|
|
anim.mobject for anim in animations
|
|
|
|
]
|
2016-07-19 11:07:26 -07:00
|
|
|
self.clean_up_animations(*animations)
|
2018-02-19 17:24:17 -08:00
|
|
|
if self.skip_animations:
|
2018-08-12 12:17:32 -07:00
|
|
|
self.continual_update(total_run_time)
|
2018-02-19 17:24:17 -08:00
|
|
|
else:
|
|
|
|
self.continual_update(0)
|
2018-02-10 22:45:46 -08:00
|
|
|
self.num_plays += 1
|
2018-11-01 11:23:34 +03:00
|
|
|
|
2018-12-22 14:27:22 -08:00
|
|
|
if self.livestreaming:
|
2018-11-01 11:23:34 +03:00
|
|
|
self.stream_lock = True
|
|
|
|
thread.start_new_thread(self.idle_stream, ())
|
|
|
|
|
2016-07-19 11:07:26 -07:00
|
|
|
return self
|
|
|
|
|
2018-11-01 11:23:34 +03:00
|
|
|
def idle_stream(self):
|
|
|
|
while(self.stream_lock):
|
2018-11-03 21:05:20 +03:00
|
|
|
a = datetime.datetime.now()
|
2018-11-01 11:23:34 +03:00
|
|
|
self.update_frame()
|
|
|
|
n_frames = 1
|
|
|
|
frame = self.get_frame()
|
|
|
|
self.add_frames(*[frame] * n_frames)
|
2018-11-03 21:05:20 +03:00
|
|
|
b = datetime.datetime.now()
|
|
|
|
time_diff = (b - a).total_seconds()
|
|
|
|
if time_diff < self.frame_duration:
|
|
|
|
sleep(self.frame_duration - time_diff)
|
2018-11-01 11:23:34 +03:00
|
|
|
|
2016-07-19 11:07:26 -07:00
|
|
|
def clean_up_animations(self, *animations):
|
2015-04-03 16:41:25 -07:00
|
|
|
for animation in animations:
|
2017-03-25 12:17:56 -07:00
|
|
|
animation.clean_up(self)
|
2015-06-09 11:26:12 -07:00
|
|
|
return self
|
2015-03-22 16:15:29 -06:00
|
|
|
|
2016-07-19 11:07:26 -07:00
|
|
|
def get_mobjects_from_last_animation(self):
|
|
|
|
if hasattr(self, "mobjects_from_last_animation"):
|
|
|
|
return self.mobjects_from_last_animation
|
|
|
|
return []
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
def wait(self, duration=DEFAULT_WAIT_TIME):
|
2017-08-24 19:05:04 -07:00
|
|
|
if self.should_continually_update():
|
2018-08-12 12:17:32 -07:00
|
|
|
total_time = 0
|
2017-08-24 19:05:04 -07:00
|
|
|
for t in self.get_time_progression(duration):
|
2018-08-12 12:17:32 -07:00
|
|
|
self.continual_update(dt=t - total_time)
|
2017-08-24 19:05:04 -07:00
|
|
|
self.update_frame()
|
|
|
|
self.add_frames(self.get_frame())
|
2018-08-12 12:17:32 -07:00
|
|
|
total_time = t
|
2018-02-20 17:50:39 -08:00
|
|
|
elif self.skip_animations:
|
2018-04-06 13:58:59 -07:00
|
|
|
# Do nothing
|
2018-02-20 17:50:39 -08:00
|
|
|
return self
|
|
|
|
else:
|
2017-08-24 11:43:38 -07:00
|
|
|
self.update_frame()
|
2018-08-12 12:17:32 -07:00
|
|
|
n_frames = int(duration / self.frame_duration)
|
|
|
|
frame = self.get_frame()
|
|
|
|
self.add_frames(*[frame] * n_frames)
|
2015-06-09 11:26:12 -07:00
|
|
|
return self
|
2015-03-22 16:15:29 -06:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
def wait_to(self, time, assert_positive=True):
|
|
|
|
if self.ignore_waits:
|
2018-01-29 21:29:36 -08:00
|
|
|
return
|
|
|
|
time -= self.current_scene_time
|
2018-04-06 13:58:59 -07:00
|
|
|
if assert_positive:
|
2018-01-29 21:29:36 -08:00
|
|
|
assert(time >= 0)
|
2018-04-06 13:58:59 -07:00
|
|
|
elif time < 0:
|
2018-01-29 21:29:36 -08:00
|
|
|
return
|
|
|
|
|
|
|
|
self.wait(time)
|
|
|
|
|
2017-03-20 14:37:51 -07:00
|
|
|
def force_skipping(self):
|
|
|
|
self.original_skipping_status = self.skip_animations
|
|
|
|
self.skip_animations = True
|
|
|
|
return self
|
|
|
|
|
|
|
|
def revert_to_original_skipping_status(self):
|
|
|
|
if hasattr(self, "original_skipping_status"):
|
|
|
|
self.skip_animations = self.original_skipping_status
|
|
|
|
return self
|
|
|
|
|
2016-09-06 16:48:04 -07:00
|
|
|
def add_frames(self, *frames):
|
2018-02-20 22:59:38 -08:00
|
|
|
if self.skip_animations:
|
|
|
|
return
|
2018-04-06 13:58:59 -07:00
|
|
|
self.current_scene_time += len(frames) * self.frame_duration
|
2017-05-09 13:14:08 -07:00
|
|
|
if self.write_to_movie:
|
|
|
|
for frame in frames:
|
2017-12-22 13:00:13 +01:00
|
|
|
if self.save_pngs:
|
2018-04-06 13:58:59 -07:00
|
|
|
self.save_image(
|
|
|
|
"frame" + str(self.frame_num), self.pngs_mode, True)
|
2017-12-22 13:00:13 +01:00
|
|
|
self.frame_num = self.frame_num + 1
|
2017-05-09 13:14:08 -07:00
|
|
|
self.writing_process.stdin.write(frame.tostring())
|
|
|
|
if self.save_frames:
|
|
|
|
self.saved_frames += list(frames)
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
# Display methods
|
2016-01-30 14:44:45 -08:00
|
|
|
|
2015-06-09 11:26:12 -07:00
|
|
|
def show_frame(self):
|
2018-04-06 13:58:59 -07:00
|
|
|
self.update_frame(dont_update_when_skipping=False)
|
2017-09-26 17:41:45 -07:00
|
|
|
self.get_image().show()
|
2015-04-03 16:41:25 -07:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
def get_image_file_path(self, name=None, dont_update=False):
|
2018-04-29 10:47:53 -07:00
|
|
|
sub_dir = "images"
|
2017-12-22 13:00:13 +01:00
|
|
|
if dont_update:
|
2018-04-29 10:47:53 -07:00
|
|
|
sub_dir = str(self)
|
|
|
|
path = get_image_output_directory(self.__class__, sub_dir)
|
2018-02-19 14:32:35 -08:00
|
|
|
file_name = add_extension_if_not_present(name or str(self), ".png")
|
2018-01-19 17:31:31 -08:00
|
|
|
return os.path.join(path, file_name)
|
2018-01-20 11:19:12 -08:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
def save_image(self, name=None, mode="RGB", dont_update=False):
|
2018-01-19 17:31:31 -08:00
|
|
|
path = self.get_image_file_path(name, dont_update)
|
2017-12-22 13:00:13 +01:00
|
|
|
if not dont_update:
|
2018-04-06 13:58:59 -07:00
|
|
|
self.update_frame(dont_update_when_skipping=False)
|
2017-09-26 17:41:45 -07:00
|
|
|
image = self.get_image()
|
|
|
|
image = image.convert(mode)
|
2018-01-19 17:31:31 -08:00
|
|
|
image.save(path)
|
2015-05-17 15:08:51 -07:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
def get_movie_file_path(self, name=None, extension=None):
|
2018-04-29 10:47:53 -07:00
|
|
|
directory = get_movie_output_directory(
|
|
|
|
self.__class__, self.camera_config, self.frame_duration
|
|
|
|
)
|
2018-01-19 17:31:31 -08:00
|
|
|
if extension is None:
|
|
|
|
extension = self.movie_file_extension
|
|
|
|
if name is None:
|
|
|
|
name = self.name
|
2018-03-06 13:39:59 -08:00
|
|
|
file_path = os.path.join(directory, name)
|
2016-02-23 22:29:32 -08:00
|
|
|
if not file_path.endswith(extension):
|
|
|
|
file_path += extension
|
|
|
|
return file_path
|
|
|
|
|
2017-05-09 13:14:08 -07:00
|
|
|
def open_movie_pipe(self):
|
|
|
|
name = str(self)
|
2018-01-19 17:31:31 -08:00
|
|
|
file_path = self.get_movie_file_path(name)
|
|
|
|
temp_file_path = file_path.replace(name, name + "Temp")
|
2018-04-06 13:58:59 -07:00
|
|
|
print("Writing to %s" % temp_file_path)
|
2017-06-06 13:38:21 -07:00
|
|
|
self.args_to_rename_file = (temp_file_path, file_path)
|
2016-02-23 22:29:32 -08:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
fps = int(1 / self.frame_duration)
|
2018-05-21 12:11:46 -07:00
|
|
|
height = self.camera.get_pixel_height()
|
|
|
|
width = self.camera.get_pixel_width()
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2016-02-23 22:29:32 -08:00
|
|
|
command = [
|
|
|
|
FFMPEG_BIN,
|
2018-04-06 13:58:59 -07:00
|
|
|
'-y', # overwrite output file if it exists
|
2016-02-23 22:29:32 -08:00
|
|
|
'-f', 'rawvideo',
|
2018-04-06 13:58:59 -07:00
|
|
|
'-s', '%dx%d' % (width, height), # size of one frame
|
2017-09-19 13:12:45 -07:00
|
|
|
'-pix_fmt', 'rgba',
|
2018-04-06 13:58:59 -07:00
|
|
|
'-r', str(fps), # frames per second
|
|
|
|
'-i', '-', # The imput comes from a pipe
|
2018-11-02 20:48:38 +03:00
|
|
|
'-c:v', 'h264_nvenc',
|
2018-04-06 13:58:59 -07:00
|
|
|
'-an', # Tells FFMPEG not to expect any audio
|
2016-02-23 22:29:32 -08:00
|
|
|
'-loglevel', 'error',
|
|
|
|
]
|
2018-02-20 14:03:59 -08:00
|
|
|
if self.movie_file_extension == ".mov":
|
|
|
|
# This is if the background of the exported video
|
|
|
|
# should be transparent.
|
2018-02-20 16:45:19 -08:00
|
|
|
command += [
|
2018-08-08 11:50:34 -07:00
|
|
|
'-vcodec', 'qtrle',
|
2018-04-06 13:58:59 -07:00
|
|
|
]
|
2018-02-20 14:03:59 -08:00
|
|
|
else:
|
|
|
|
command += [
|
2018-02-20 16:45:19 -08:00
|
|
|
'-vcodec', 'libx264',
|
2018-02-20 14:03:59 -08:00
|
|
|
'-pix_fmt', 'yuv420p',
|
|
|
|
]
|
2018-12-22 14:27:22 -08:00
|
|
|
if self.livestreaming:
|
|
|
|
if self.to_twitch:
|
2018-10-31 21:29:01 +03:00
|
|
|
command += ['-f', 'flv']
|
2018-12-22 18:25:35 -08:00
|
|
|
command += ['rtmp://live.twitch.tv/app/' + self.twitch_key]
|
2018-10-31 21:29:01 +03:00
|
|
|
else:
|
|
|
|
command += ['-f', 'mpegts']
|
|
|
|
command += [STREAMING_PROTOCOL + '://' + STREAMING_IP + ':' + STREAMING_PORT]
|
2018-10-31 11:16:18 +03:00
|
|
|
else:
|
|
|
|
command += [temp_file_path]
|
2018-01-08 12:40:36 -08:00
|
|
|
# self.writing_process = sp.Popen(command, stdin=sp.PIPE, shell=True)
|
|
|
|
self.writing_process = sp.Popen(command, stdin=sp.PIPE)
|
2017-05-09 13:14:08 -07:00
|
|
|
|
|
|
|
def close_movie_pipe(self):
|
|
|
|
self.writing_process.stdin.close()
|
|
|
|
self.writing_process.wait()
|
2018-12-22 14:27:22 -08:00
|
|
|
if self.livestreaming:
|
2018-10-31 11:16:18 +03:00
|
|
|
return True
|
2017-09-27 17:29:13 +08:00
|
|
|
if os.name == 'nt':
|
|
|
|
shutil.move(*self.args_to_rename_file)
|
|
|
|
else:
|
|
|
|
os.rename(*self.args_to_rename_file)
|
2018-02-19 17:24:17 -08:00
|
|
|
|
2018-11-02 20:48:38 +03:00
|
|
|
def tex(self, latex):
|
|
|
|
eq = TextMobject(latex)
|
2018-11-03 21:05:20 +03:00
|
|
|
anims = []
|
|
|
|
anims.append(Write(eq))
|
|
|
|
for mobject in self.mobjects:
|
|
|
|
anims.append(ApplyMethod(mobject.shift,2*UP))
|
|
|
|
self.play(*anims)
|
2018-11-02 20:48:38 +03:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2018-02-19 17:24:17 -08:00
|
|
|
class EndSceneEarlyException(Exception):
|
|
|
|
pass
|