3b1b-manim/scene/scene.py

236 lines
7.5 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
DEFAULT_COUNT_NUM_OFFSET = (SPACE_WIDTH - 1, SPACE_HEIGHT - 1, 0)
DEFAULT_COUNT_RUN_TIME = 5.0
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"]
self.frames = []
2015-03-26 22:49:22 -06:00
self.mobjects = []
if 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-05-07 21:28:02 -07:00
(display_config["height"], 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
2015-06-10 22:00:35 -07:00
self.construct()
def construct(self):
pass #To be implemented in subclasses
def __str__(self):
2015-05-07 21:28:02 -07:00
return self.__class__.__name__
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 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 reset_background(self):
self.background = self.original_background
2015-06-10 22:00:35 -07:00
return self
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-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-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)
progress_bar.finish()
2015-06-09 11:26:12 -07:00
return self
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
def count_mobjects(
self, mobjects, mode = "highlight",
color = "red",
num_offset = DEFAULT_COUNT_NUM_OFFSET,
run_time = DEFAULT_COUNT_RUN_TIME):
"""
2015-05-17 15:08:51 -07:00
Note, leaves final number mobject as "number" attribute
mode can be "highlight", "show_creation" or "show", otherwise
a warning is given and nothing is animating during the count
"""
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")
if mode not in ["highlight", "show_creation", "show"]:
raise Warning("Unknown mode")
frame_time = run_time / len(mobjects)
if mode == "highlight":
self.add(*mobjects)
for mob, num in zip(mobjects, it.count(1)):
num_mob = tex_mobject(str(num))
num_mob.center().shift(num_offset)
self.add(num_mob)
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))
if mode == "show":
self.add(mob)
self.dither(frame_time)
self.remove(num_mob)
self.add(num_mob)
self.number = num_mob
2015-06-09 11:26:12 -07:00
return self
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()
self.remove(num_mob)
self.add(num_mob)
self.number = num_mob
2015-06-09 11:26:12 -07:00
return self
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-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-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-05-07 21:28:02 -07:00
def write_to_movie(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_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-05-17 15:08:51 -07:00
def save_image(self, path):
path = os.path.join(MOVIE_DIR, path) + ".png"
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 ""