3b1b-manim/scene/scene.py

256 lines
7.6 KiB
Python
Raw Normal View History

from PIL import Image
from colour import Color
import numpy as np
2015-03-26 22:49:22 -06:00
import itertools as it
import warnings
import time
import os
import copy
import progressbar
import inspect
from helpers import *
from mobject import *
2015-04-03 16:41:25 -07:00
from animation import *
import displayer as disp
2015-06-09 11:26:12 -07:00
from tk_scene import TkSceneRoot
class Scene(object):
DEFAULT_CONFIG = {
"display_config" : PRODUCTION_QUALITY_DISPLAY_CONFIG,
"construct_args" : [],
"background" : None,
2015-10-10 18:48:54 -07:00
"start_dither_time" : DEFAULT_DITHER_TIME,
"announce_construction" : False,
}
def __init__(self, **kwargs):
digest_config(self, Scene, kwargs)
2015-10-10 18:48:54 -07:00
if self.announce_construction:
print "Constructing %s..."%str(self)
self.frame_duration = self.display_config["frame_duration"]
self.frames = []
2015-03-26 22:49:22 -06:00
self.mobjects = []
if self.background:
self.original_background = np.array(background)
2015-03-26 22:49:22 -06:00
#TODO, Error checking?
else:
2015-04-03 16:41:25 -07:00
self.original_background = np.zeros(
2015-08-17 13:19:34 -07:00
(self.display_config["height"], self.display_config["width"], 3),
2015-04-03 16:41:25 -07:00
dtype = 'uint8'
)
self.background = self.original_background
self.shape = self.background.shape[:2]
2015-04-14 17:55:25 -07:00
#TODO, space shape
self.construct(*self.construct_args)
2015-06-10 22:00:35 -07:00
def construct(self):
pass #To be implemented in subclasses
def __str__(self):
if hasattr(self, "name"):
return self.name
2015-10-10 18:48:54 -07:00
return self.__class__.__name__ + \
self.args_to_string(*self.construct_args)
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
def add(self, *mobjects):
2015-03-26 22:49:22 -06:00
"""
Mobjects will be displayed, from background to foreground,
in the order with which they are entered.
"""
for mobject in mobjects:
#In case it's already in there, it should
#now be closer to the foreground.
self.remove(mobject)
self.mobjects.append(mobject)
2015-06-09 11:26:12 -07:00
return self
def add_local_mobjects(self):
"""
So a scene can just add all mobjects it's defined up to that point
"""
caller_locals = inspect.currentframe().f_back.f_locals
self.add(*filter(
lambda m : isinstance(m, Mobject),
caller_locals.values()
))
def remove(self, *mobjects):
2015-03-26 22:49:22 -06:00
for mobject in mobjects:
if not isinstance(mobject, Mobject):
raise Exception("Removing something which is not a mobject")
2015-03-26 22:49:22 -06:00
while mobject in self.mobjects:
self.mobjects.remove(mobject)
2015-06-09 11:26:12 -07:00
return self
2015-06-10 22:00:35 -07:00
def clear(self):
self.reset_background()
self.remove(*self.mobjects)
return self
def highlight_region(self, region, color = None):
self.background = disp.paint_region(
region,
image_array = self.background,
color = color,
)
2015-06-09 11:26:12 -07:00
return self
def highlight_region_over_time_range(self, region, time_range = None, color = "black"):
if time_range:
frame_range = map(lambda t : t / self.frame_duration, time_range)
frame_range[0] = max(frame_range[0], 0)
frame_range[1] = min(frame_range[1], len(self.frames))
else:
frame_range = (0, len(self.frames))
for index in range(frame_range[0], frame_range[1]):
self.frames[index] = disp.paint_region(
region,
image_array = self.frames[index],
color = color
)
def reset_background(self):
self.background = self.original_background
2015-06-10 22:00:35 -07:00
return self
2015-10-06 15:28:21 -07:00
def paint_into_background(self, *mobjects):
#This way current mobjects don't have to be redrawn with
#every change, and one can later call "apply" without worrying
#about it applying to these mobjects
self.background = disp.paint_mobjects(mobjects, self.background)
2015-10-06 15:28:21 -07:00
return self
def play(self, *animations, **kwargs):
2015-04-03 16:41:25 -07:00
if "run_time" in kwargs:
run_time = kwargs["run_time"]
else:
run_time = animations[0].run_time
for animation in animations:
animation.set_run_time(run_time)
moving_mobjects = [anim.mobject for anim in animations]
2015-03-26 22:49:22 -06:00
self.remove(*moving_mobjects)
background = self.get_frame()
2015-04-03 16:41:25 -07:00
print "Generating " + ", ".join(map(str, animations))
progress_bar = progressbar.ProgressBar(maxval=run_time)
progress_bar.start()
for t in np.arange(0, run_time, self.frame_duration):
progress_bar.update(t)
2015-04-03 16:41:25 -07:00
for animation in animations:
animation.update(t / animation.run_time)
new_frame = disp.paint_mobjects(moving_mobjects, background)
2015-03-26 22:49:22 -06:00
self.frames.append(new_frame)
2015-04-03 16:41:25 -07:00
for animation in animations:
animation.clean_up()
2015-03-26 22:49:22 -06:00
self.add(*moving_mobjects)
progress_bar.finish()
2015-06-09 11:26:12 -07:00
return self
def play_over_time_range(self, t0, t1, *animations):
2015-06-27 04:49:10 -07:00
needed_scene_time = max(abs(t0), abs(t1))
existing_scene_time = len(self.frames)*self.frame_duration
if existing_scene_time < needed_scene_time:
self.dither(needed_scene_time - existing_scene_time)
existing_scene_time = needed_scene_time
#So negative values may be used
if t0 < 0:
t0 = float(t0)%existing_scene_time
if t1 < 0:
t1 = float(t1)%existing_scene_time
t0, t1 = min(t0, t1), max(t0, t1)
2015-08-17 13:19:34 -07:00
moving_mobjects = [anim.mobject for anim in animations]
2015-06-27 04:49:10 -07:00
for t in np.arange(t0, t1, self.frame_duration):
2015-08-17 13:19:34 -07:00
for animation in animations:
animation.update((t-t0)/(t1 - t0))
2015-06-27 04:49:10 -07:00
index = int(t/self.frame_duration)
self.frames[index] = disp.paint_mobjects(moving_mobjects, self.frames[index])
2015-08-17 13:19:34 -07:00
for animation in animations:
animation.clean_up()
2015-06-09 11:26:12 -07:00
return self
def apply(self, mob_to_anim_func, **kwargs):
self.play(*[
mob_to_anim_func(mobject)
for mobject in self.mobjects
])
2015-03-26 22:49:22 -06:00
def get_frame(self):
return disp.paint_mobjects(self.mobjects, self.background)
2015-03-26 22:49:22 -06:00
def dither(self, duration = DEFAULT_DITHER_TIME):
self.frames += [self.get_frame()]*int(duration / self.frame_duration)
2015-06-09 11:26:12 -07:00
return self
def repeat(self, num):
self.frames = self.frames*num
return self
2015-05-07 21:28:02 -07:00
def write_to_gif(self, name = None,
end_dither_time = DEFAULT_DITHER_TIME):
2015-03-26 22:49:22 -06:00
self.dither(end_dither_time)
disp.write_to_gif(self, name or str(self))
2015-06-27 04:49:10 -07:00
def write_to_movie(self, name = None):
2015-10-10 18:48:54 -07:00
if len(self.frames) == 0:
print "No frames, I'll just save an image instead"
self.show_frame()
self.save_image(name = name)
return
disp.write_to_movie(self, name or str(self))
2015-06-09 11:26:12 -07:00
def show_frame(self):
2015-04-03 16:41:25 -07:00
Image.fromarray(self.get_frame()).show()
2015-06-09 11:26:12 -07:00
def preview(self):
TkSceneRoot(self)
2015-06-19 08:31:02 -07:00
def save_image(self, directory = MOVIE_DIR, name = None):
path = os.path.join(directory, name or str(self)) + ".png"
2015-05-17 15:08:51 -07:00
Image.fromarray(self.get_frame()).save(path)
2015-05-07 21:28:02 -07:00
# To list possible args that subclasses have
# Elements should always be a tuple
args_list = []
# For subclasses to turn args in the above
# list into stings which can be appended to the name
@staticmethod
def args_to_string(*args):
return ""
2015-10-10 18:48:54 -07:00
@staticmethod
def string_to_args(string):
raise Exception("string_to_args Not Implemented!")
2015-08-17 13:19:34 -07:00