2015-03-22 16:15:29 -06:00
|
|
|
from PIL import Image
|
|
|
|
from colour import Color
|
|
|
|
import numpy as np
|
2015-03-26 22:49:22 -06:00
|
|
|
import itertools as it
|
2015-03-22 16:15:29 -06:00
|
|
|
import warnings
|
|
|
|
import time
|
|
|
|
import os
|
|
|
|
import copy
|
|
|
|
import progressbar
|
2015-10-09 19:53:38 -07:00
|
|
|
import inspect
|
2015-03-22 16:15:29 -06:00
|
|
|
|
|
|
|
from helpers import *
|
2015-10-27 21:00:50 -07:00
|
|
|
|
2015-03-22 16:15:29 -06:00
|
|
|
import displayer as disp
|
2015-06-09 11:26:12 -07:00
|
|
|
from tk_scene import TkSceneRoot
|
2015-10-28 16:03:33 -07:00
|
|
|
from mobject import Mobject
|
2015-10-29 13:45:28 -07:00
|
|
|
from animation.transform import ApplyMethod
|
2015-03-22 16:15:29 -06:00
|
|
|
|
|
|
|
class Scene(object):
|
2015-09-28 16:25:18 -07:00
|
|
|
DEFAULT_CONFIG = {
|
2015-10-29 13:45:28 -07:00
|
|
|
"height" : DEFAULT_HEIGHT,
|
|
|
|
"width" : DEFAULT_WIDTH ,
|
|
|
|
"frame_duration" : DEFAULT_FRAME_DURATION,
|
|
|
|
"construct_args" : [],
|
|
|
|
"background" : None,
|
|
|
|
"start_dither_time" : DEFAULT_DITHER_TIME,
|
2015-10-10 18:48:54 -07:00
|
|
|
"announce_construction" : False,
|
2015-09-28 16:25:18 -07:00
|
|
|
}
|
|
|
|
def __init__(self, **kwargs):
|
2015-10-28 16:03:33 -07:00
|
|
|
digest_config(self, kwargs)
|
2015-10-10 18:48:54 -07:00
|
|
|
if self.announce_construction:
|
|
|
|
print "Constructing %s..."%str(self)
|
2015-09-28 16:25:18 -07:00
|
|
|
if self.background:
|
2015-03-30 17:51:26 -07:00
|
|
|
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-10-29 13:45:28 -07:00
|
|
|
(self.height, self.width, 3),
|
2015-04-03 16:41:25 -07:00
|
|
|
dtype = 'uint8'
|
|
|
|
)
|
2015-10-29 13:45:28 -07:00
|
|
|
self.background = self.original_background
|
|
|
|
self.frames = [self.background]
|
|
|
|
self.mobjects = []
|
|
|
|
|
2015-09-28 16:25:18 -07:00
|
|
|
self.construct(*self.construct_args)
|
2015-06-10 22:00:35 -07:00
|
|
|
|
|
|
|
def construct(self):
|
|
|
|
pass #To be implemented in subclasses
|
2015-03-22 16:15:29 -06:00
|
|
|
|
|
|
|
def __str__(self):
|
2015-10-08 11:14:55 -07:00
|
|
|
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-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
|
|
|
|
2015-10-29 13:45:28 -07:00
|
|
|
def get_frame(self):
|
|
|
|
return self.frames[-1]
|
|
|
|
|
|
|
|
def set_frame(self, frame):
|
|
|
|
self.frames[-1] = frame
|
|
|
|
return self
|
|
|
|
|
2015-03-22 16:15:29 -06: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.
|
|
|
|
"""
|
2015-10-29 13:45:28 -07:00
|
|
|
if not all_elements_are_instances(mobjects, Mobject):
|
|
|
|
raise Exception("Adding something which is not a mobject")
|
|
|
|
self.set_frame(disp.paint_mobjects(mobjects, self.get_frame()))
|
|
|
|
old_mobjects = filter(lambda m : m not in mobjects, self.mobjects)
|
|
|
|
self.mobjects = old_mobjects + list(mobjects)
|
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
|
|
|
"""
|
2015-11-02 19:09:55 -08:00
|
|
|
mobjects = filter(lambda x : isinstance(x, Mobject), values)
|
|
|
|
self.add(*mobjects)
|
2015-11-02 13:03:01 -08:00
|
|
|
return self
|
2015-10-09 19:53:38 -07:00
|
|
|
|
2015-03-22 16:15:29 -06:00
|
|
|
def remove(self, *mobjects):
|
2015-10-29 13:45:28 -07:00
|
|
|
if not all_elements_are_instances(mobjects, Mobject):
|
|
|
|
raise Exception("Removing something which is not a mobject")
|
|
|
|
mobjects = filter(lambda m : m in self.mobjects, mobjects)
|
2015-11-02 19:09:55 -08:00
|
|
|
if len(mobjects) == 0:
|
2015-10-29 13:45:28 -07:00
|
|
|
return
|
|
|
|
self.mobjects = filter(lambda m : m not in mobjects, self.mobjects)
|
|
|
|
self.repaint_mojects()
|
2015-06-09 11:26:12 -07:00
|
|
|
return self
|
|
|
|
|
2015-10-12 19:39:46 -07:00
|
|
|
def bring_to_front(self, mobject):
|
|
|
|
self.add(mobject)
|
|
|
|
return self
|
|
|
|
|
|
|
|
def bring_to_back(self, mobject):
|
|
|
|
self.remove(mobject)
|
|
|
|
self.mobjects = [mobject] + self.mobjects
|
2015-10-29 13:45:28 -07:00
|
|
|
self.repaint_mojects()
|
2015-10-12 19:39:46 -07:00
|
|
|
return self
|
|
|
|
|
2015-06-10 22:00:35 -07:00
|
|
|
def clear(self):
|
|
|
|
self.reset_background()
|
2015-10-29 13:45:28 -07:00
|
|
|
self.mobjects = []
|
|
|
|
self.set_frame(self.background)
|
2015-06-10 22:00:35 -07:00
|
|
|
return self
|
2015-03-22 16:15:29 -06:00
|
|
|
|
2015-03-30 17:51:26 -07:00
|
|
|
def highlight_region(self, region, color = None):
|
|
|
|
self.background = disp.paint_region(
|
|
|
|
region,
|
|
|
|
image_array = self.background,
|
|
|
|
color = color,
|
|
|
|
)
|
2015-10-29 13:45:28 -07:00
|
|
|
self.repaint_mojects()
|
2015-06-09 11:26:12 -07:00
|
|
|
return self
|
2015-03-30 17:51:26 -07:00
|
|
|
|
2015-08-21 19:58:36 -07:00
|
|
|
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
|
|
|
|
)
|
|
|
|
|
2015-03-30 17:51:26 -07:00
|
|
|
def reset_background(self):
|
|
|
|
self.background = self.original_background
|
2015-06-10 22:00:35 -07:00
|
|
|
return self
|
2015-03-30 17:51:26 -07:00
|
|
|
|
2015-10-29 13:45:28 -07:00
|
|
|
def repaint_mojects(self):
|
|
|
|
self.set_frame(disp.paint_mobjects(self.mobjects, self.background))
|
|
|
|
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
|
2015-10-09 19:53:38 -07:00
|
|
|
self.background = disp.paint_mobjects(mobjects, self.background)
|
2015-10-06 15:28:21 -07:00
|
|
|
return self
|
|
|
|
|
2015-10-29 13:45:28 -07:00
|
|
|
def set_frame_as_background(self):
|
|
|
|
self.background = self.get_frame()
|
|
|
|
|
2015-09-25 19:43:53 -07:00
|
|
|
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)
|
2015-11-02 19:09:55 -08:00
|
|
|
moving_mobjects = [
|
|
|
|
mobject
|
|
|
|
for anim in animations
|
|
|
|
for mobject in anim.mobject.get_full_submobject_family()
|
|
|
|
]
|
2015-11-02 13:03:01 -08:00
|
|
|
|
|
|
|
bundle = Mobject(*self.mobjects)
|
|
|
|
static_mobjects = filter(
|
|
|
|
lambda m : m not in moving_mobjects,
|
|
|
|
bundle.get_full_submobject_family()
|
|
|
|
)
|
|
|
|
background = disp.paint_mobjects(
|
|
|
|
static_mobjects,
|
|
|
|
self.background,
|
|
|
|
include_sub_mobjects = False
|
|
|
|
)
|
2015-03-22 16:15:29 -06:00
|
|
|
|
2015-04-03 16:41:25 -07:00
|
|
|
print "Generating " + ", ".join(map(str, animations))
|
2015-03-22 16:15:29 -06:00
|
|
|
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)
|
2015-10-09 19:53:38 -07:00
|
|
|
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-11-02 14:09:49 -08:00
|
|
|
self.add(*moving_mobjects)
|
2015-11-02 13:03:01 -08:00
|
|
|
self.repaint_mojects()
|
2015-03-22 16:15:29 -06:00
|
|
|
progress_bar.finish()
|
2015-06-09 11:26:12 -07:00
|
|
|
return self
|
2015-03-22 16:15:29 -06:00
|
|
|
|
2015-09-25 19:43:53 -07:00
|
|
|
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)
|
2015-10-10 10:34:21 -07:00
|
|
|
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
|
2015-04-16 21:09:12 -07:00
|
|
|
|
2015-10-12 19:39:46 -07:00
|
|
|
def apply(self, mobject_method, *args, **kwargs):
|
|
|
|
self.play(ApplyMethod(mobject_method, *args, **kwargs))
|
2015-10-05 17:45:13 -07:00
|
|
|
|
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
|
2015-03-22 16:15:29 -06:00
|
|
|
|
2015-08-07 18:10:00 -07:00
|
|
|
def repeat(self, num):
|
|
|
|
self.frames = self.frames*num
|
|
|
|
return 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
|
2015-03-22 16:15:29 -06:00
|
|
|
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-29 17:00:46 -07:00
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2015-03-22 16:15:29 -06:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|