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
|
|
|
|
import inspect
|
|
|
|
|
|
|
|
from helpers import *
|
|
|
|
from mobject import *
|
2015-04-03 16:41:25 -07:00
|
|
|
from animation import *
|
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-03-22 16:15:29 -06:00
|
|
|
|
2015-04-26 14:25:43 -07:00
|
|
|
DEFAULT_COUNT_NUM_OFFSET = (SPACE_WIDTH - 1, SPACE_HEIGHT - 1, 0)
|
|
|
|
DEFAULT_COUNT_RUN_TIME = 5.0
|
|
|
|
|
2015-03-22 16:15:29 -06:00
|
|
|
class Scene(object):
|
2015-05-07 21:28:02 -07:00
|
|
|
def __init__(self,
|
|
|
|
display_config = PRODUCTION_QUALITY_DISPLAY_CONFIG,
|
2015-03-26 22:49:22 -06:00
|
|
|
background = None,
|
2015-04-03 16:41:25 -07:00
|
|
|
start_dither_time = DEFAULT_DITHER_TIME):
|
2015-05-07 21:28:02 -07:00
|
|
|
self.display_config = display_config
|
|
|
|
self.frame_duration = display_config["frame_duration"]
|
2015-03-22 16:15:29 -06:00
|
|
|
self.frames = []
|
2015-03-26 22:49:22 -06:00
|
|
|
self.mobjects = []
|
|
|
|
if 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-05-07 21:28:02 -07:00
|
|
|
(display_config["height"], display_config["width"], 3),
|
2015-04-03 16:41:25 -07:00
|
|
|
dtype = 'uint8'
|
|
|
|
)
|
2015-03-30 17:51:26 -07:00
|
|
|
self.background = self.original_background
|
|
|
|
self.shape = self.background.shape[:2]
|
2015-04-14 17:55:25 -07:00
|
|
|
#TODO, space shape
|
2015-06-10 22:00:35 -07:00
|
|
|
self.construct()
|
|
|
|
|
|
|
|
def construct(self):
|
|
|
|
pass #To be implemented in subclasses
|
2015-03-22 16:15:29 -06:00
|
|
|
|
|
|
|
def __str__(self):
|
2015-05-07 21:28:02 -07:00
|
|
|
return self.__class__.__name__
|
2015-03-22 16:15:29 -06: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-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.
|
|
|
|
"""
|
|
|
|
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
|
2015-03-22 16:15:29 -06:00
|
|
|
|
|
|
|
def remove(self, *mobjects):
|
2015-03-26 22:49:22 -06:00
|
|
|
for mobject in mobjects:
|
2015-05-06 17:58:34 -07:00
|
|
|
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
|
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-06-09 11:26:12 -07:00
|
|
|
return self
|
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-04-03 16:41:25 -07:00
|
|
|
def animate(self, *animations, **kwargs):
|
|
|
|
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-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-03-26 22:49:22 -06:00
|
|
|
new_frame = background
|
2015-04-03 16:41:25 -07:00
|
|
|
for animation in animations:
|
|
|
|
animation.update(t / animation.run_time)
|
|
|
|
new_frame = disp.paint_mobject(animation.mobject, new_frame)
|
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)
|
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-06-27 04:49:10 -07:00
|
|
|
def animate_over_time_range(self, t0, t1, animation):
|
|
|
|
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)
|
|
|
|
|
|
|
|
for t in np.arange(t0, t1, self.frame_duration):
|
|
|
|
animation.update((t-t0)/(t1 - t0))
|
|
|
|
index = int(t/self.frame_duration)
|
|
|
|
self.frames[index] = disp.paint_mobject(
|
|
|
|
animation.mobject, self.frames[index]
|
|
|
|
)
|
|
|
|
animation.clean_up()
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
2015-04-26 14:25:43 -07:00
|
|
|
def count(self, items, item_type = "mobject", *args, **kwargs):
|
|
|
|
if item_type == "mobject":
|
|
|
|
self.count_mobjects(items, *args, **kwargs)
|
|
|
|
elif item_type == "region":
|
|
|
|
self.count_regions(items, *args, **kwargs)
|
2015-05-17 15:08:51 -07:00
|
|
|
else:
|
|
|
|
raise Exception("Unknown item_type, should be mobject or region")
|
2015-06-09 11:26:12 -07:00
|
|
|
return self
|
2015-04-26 14:25:43 -07:00
|
|
|
|
|
|
|
def count_mobjects(
|
|
|
|
self, mobjects, mode = "highlight",
|
|
|
|
color = "red",
|
2015-06-19 08:31:02 -07:00
|
|
|
display_numbers = True,
|
2015-04-26 14:25:43 -07:00
|
|
|
num_offset = DEFAULT_COUNT_NUM_OFFSET,
|
|
|
|
run_time = DEFAULT_COUNT_RUN_TIME):
|
2015-04-16 21:09:12 -07:00
|
|
|
"""
|
2015-05-17 15:08:51 -07:00
|
|
|
Note, leaves final number mobject as "number" attribute
|
2015-04-26 14:25:43 -07:00
|
|
|
|
|
|
|
mode can be "highlight", "show_creation" or "show", otherwise
|
|
|
|
a warning is given and nothing is animating during the count
|
2015-04-16 21:09:12 -07:00
|
|
|
"""
|
|
|
|
if len(mobjects) > 50: #TODO
|
|
|
|
raise Exception("I don't know if you should be counting \
|
|
|
|
too many mobjects...")
|
2015-05-17 15:08:51 -07:00
|
|
|
if len(mobjects) == 0:
|
|
|
|
raise Exception("Counting mobject list of length 0")
|
2015-04-26 14:25:43 -07:00
|
|
|
if mode not in ["highlight", "show_creation", "show"]:
|
|
|
|
raise Warning("Unknown mode")
|
2015-04-16 21:09:12 -07:00
|
|
|
frame_time = run_time / len(mobjects)
|
|
|
|
if mode == "highlight":
|
|
|
|
self.add(*mobjects)
|
|
|
|
for mob, num in zip(mobjects, it.count(1)):
|
2015-06-19 08:31:02 -07:00
|
|
|
if display_numbers:
|
|
|
|
num_mob = tex_mobject(str(num))
|
|
|
|
num_mob.center().shift(num_offset)
|
|
|
|
self.add(num_mob)
|
2015-04-16 21:09:12 -07:00
|
|
|
if mode == "highlight":
|
|
|
|
original_color = mob.color
|
|
|
|
mob.highlight(color)
|
|
|
|
self.dither(frame_time)
|
|
|
|
mob.highlight(original_color)
|
|
|
|
if mode == "show_creation":
|
|
|
|
self.animate(ShowCreation(mob, run_time = frame_time))
|
2015-04-26 14:25:43 -07:00
|
|
|
if mode == "show":
|
|
|
|
self.add(mob)
|
|
|
|
self.dither(frame_time)
|
2015-06-19 08:31:02 -07:00
|
|
|
if display_numbers:
|
|
|
|
self.remove(num_mob)
|
|
|
|
if display_numbers:
|
|
|
|
self.add(num_mob)
|
|
|
|
self.number = num_mob
|
2015-06-09 11:26:12 -07:00
|
|
|
return self
|
2015-04-26 14:25:43 -07:00
|
|
|
|
|
|
|
def count_regions(self, regions,
|
|
|
|
mode = "one_at_a_time",
|
|
|
|
num_offset = DEFAULT_COUNT_NUM_OFFSET,
|
|
|
|
run_time = DEFAULT_COUNT_RUN_TIME,
|
|
|
|
**unused_kwargsn):
|
|
|
|
if mode not in ["one_at_a_time", "show_all"]:
|
|
|
|
raise Warning("Unknown mode")
|
|
|
|
frame_time = run_time / (len(regions))
|
|
|
|
for region, count in zip(regions, it.count(1)):
|
|
|
|
num_mob = tex_mobject(str(count))
|
|
|
|
num_mob.center().shift(num_offset)
|
|
|
|
self.add(num_mob)
|
|
|
|
self.highlight_region(region)
|
|
|
|
self.dither(frame_time)
|
|
|
|
if mode == "one_at_a_time":
|
|
|
|
self.reset_background()
|
2015-04-16 21:09:12 -07:00
|
|
|
self.remove(num_mob)
|
|
|
|
self.add(num_mob)
|
|
|
|
self.number = num_mob
|
2015-06-09 11:26:12 -07:00
|
|
|
return self
|
2015-04-16 21:09:12 -07:00
|
|
|
|
2015-04-26 14:25:43 -07:00
|
|
|
|
2015-03-26 22:49:22 -06:00
|
|
|
def get_frame(self):
|
|
|
|
frame = self.background
|
|
|
|
for mob in self.mobjects:
|
|
|
|
frame = disp.paint_mobject(mob, frame)
|
|
|
|
return frame
|
2015-03-22 16:15:29 -06: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-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)
|
2015-03-22 16:15:29 -06:00
|
|
|
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-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-03-22 16:15:29 -06:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|