Refactored scene

This commit is contained in:
Grant Sanderson 2015-10-29 13:45:28 -07:00
parent 75954ff01c
commit e4b61dc5df
12 changed files with 239 additions and 220 deletions

View file

@ -1,10 +1,15 @@
import os
import numpy as np
DEFAULT_HEIGHT = 1440
DEFAULT_WIDTH = 2560
DEFAULT_FRAME_DURATION = 0.04
PRODUCTION_QUALITY_DISPLAY_CONFIG = {
"height" : 1440,
"width" : 2560,
"frame_duration" : 0.04,
"height" : DEFAULT_HEIGHT,
"width" : DEFAULT_WIDTH ,
"frame_duration" : DEFAULT_FRAME_DURATION,
}
MEDIUM_QUALITY_DISPLAY_CONFIG = {
@ -26,8 +31,6 @@ DEFAULT_POINT_DENSITY_1D = 200
DEFAULT_POINT_THICKNESS = 3
#TODO, Make sure these are not needd
DEFAULT_HEIGHT = PRODUCTION_QUALITY_DISPLAY_CONFIG["height"]
DEFAULT_WIDTH = PRODUCTION_QUALITY_DISPLAY_CONFIG["width"]
SPACE_HEIGHT = 4.0
SPACE_WIDTH = SPACE_HEIGHT * DEFAULT_WIDTH / DEFAULT_HEIGHT
@ -37,7 +40,6 @@ DEFAULT_MOBJECT_TO_MOBJECT_BUFFER = 0.2
#All in seconds
DEFAULT_FRAME_DURATION = 0.04
DEFAULT_ANIMATION_RUN_TIME = 1.0
DEFAULT_POINTWISE_FUNCTION_RUN_TIME = 3.0
DEFAULT_DITHER_TIME = 1.0
@ -74,19 +76,13 @@ for folder in [IMAGE_DIR, GIF_DIR, MOVIE_DIR, TEX_DIR,
PDF_DENSITY = 800
SIZE_TO_REPLACE = "SizeHere"
TEX_TEXT_TO_REPLACE = "YourTextHere"
TEMPLATE_TEX_FILE = os.path.join(TEX_DIR, "template.tex")
TEMPLATE_TEXT_FILE = os.path.join(TEX_DIR, "text_template.tex")
TEMPLATE_TEX_FILE = os.path.join(THIS_DIR, "template.tex")
TEMPLATE_TEXT_FILE = os.path.join(THIS_DIR, "text_template.tex")
MAX_LEN_FOR_HUGE_TEX_FONT = 25
LOGO_PATH = os.path.join(IMAGE_DIR, "logo.png")
PI_CREATURE_DIR = os.path.join(IMAGE_DIR, "PiCreature")
PI_CREATURE_PART_NAME_TO_DIR = lambda name : os.path.join(PI_CREATURE_DIR, "pi_creature_"+name) + ".png"
PI_CREATURE_SCALE_VAL = 0.5
PI_CREATURE_MOUTH_TO_EYES_DISTANCE = 0.25
### Colors ###
@ -138,12 +134,9 @@ COLOR_MAP = {
"BLACK" : "#000000",
}
PALETTE = COLOR_MAP.values()
global_dict = globals()
global_dict.update(COLOR_MAP)
for name in ["BLUE", "TEAL", "GREEN",
"YELLOW", "GOLD", "RED",
"MAROON", "PURPLE"]:
global_dict[name] = global_dict[name + "_C"]
globals().update(COLOR_MAP)
for name in filter(lambda s : s.endswith("_C"), COLOR_MAP.keys()):
globals()[name.replace("_C", "")] = globals()[name]

View file

@ -139,8 +139,8 @@ def write_to_movie(scene, name):
file_path = get_file_path(name, ".mp4")
print "Writing to %s"%file_path
fps = int(1/scene.display_config["frame_duration"])
dim = (scene.display_config["width"], scene.display_config["height"])
fps = int(1/scene.frame_duration)
dim = (scene.width, scene.height)
command = [
FFMPEG_BIN,

View file

@ -187,10 +187,8 @@ def main():
inspect.getmembers(module, is_scene)
)
config["movie_prefix"] = config["module"]
scene_kwargs = {
"display_config" : config["display_config"],
"announce_construction" : True
}
scene_kwargs = config["display_config"]
scene_kwargs["announce_construction"] = True
for SceneClass in get_scene_classes(scene_names_to_classes, config):
for args in get_scene_args(SceneClass, config):
scene_kwargs["construct_args"] = args

71
generate_logo.py Normal file
View file

@ -0,0 +1,71 @@
from animation.transform import Transform
from mobject import Mobject
from mobject.tex_mobject import TextMobject
from topics.geometry import Circle
from topics.three_dimensions import Sphere
from scene import Scene
from helpers import *
class LogoGeneration(Scene):
DEFAULT_CONFIG = {
"radius" : 1.5,
"inner_radius_ratio" : 0.55,
"circle_density" : 100,
"circle_blue" : "skyblue",
"circle_brown" : DARK_BROWN,
"circle_repeats" : 5,
"sphere_density" : 50,
"sphere_blue" : DARK_BLUE,
"sphere_brown" : LIGHT_BROWN,
"interpolation_factor" : 0.3,
"frame_duration" : 0.01,
}
def construct(self):
digest_config(self, {})
circle = Circle(
density = self.circle_density,
color = self.circle_blue
)
circle.repeat(self.circle_repeats)
circle.scale(self.radius)
sphere = Sphere(
density = self.sphere_density,
color = self.sphere_blue
)
sphere.scale(self.radius)
sphere.rotate(-np.pi / 7, [1, 0, 0])
sphere.rotate(-np.pi / 7)
iris = Mobject()
Mobject.interpolate(
circle, sphere, iris,
self.interpolation_factor
)
for mob, color in [(iris, self.sphere_brown), (circle, self.circle_brown)]:
mob.highlight(color, lambda (x, y, z) : x < 0 and y > 0)
mob.highlight(
"black",
lambda point: np.linalg.norm(point) < \
self.inner_radius_ratio*self.radius
)
name = TextMobject("3Blue1Brown").center()
name.highlight("grey")
name.shift(2*DOWN)
self.play(Transform(
circle, iris,
run_time = DEFAULT_ANIMATION_RUN_TIME
))
self.frames = drag_pixels(self.frames)
self.set_frame_as_background()
self.save_image()
self.add(name)
self.dither()
print "Dragging pixels..."

View file

@ -11,6 +11,12 @@ import re
from constants import *
def list_update(l1, l2):
return filter(lambda e : e not in l2, l1) + l2
def all_elements_are_instances(iterable, Class):
return all(map(lambda e : isinstance(e, Class), iterable))
def adjascent_pairs(objects):
return zip(objects, list(objects[1:])+[objects[0]])

View file

@ -87,7 +87,7 @@ def generate_tex_file(expression, size, template_tex_file):
def tex_to_dvi(tex_file):
result = tex_file.replace(".tex", ".dvi")
if not os.path.exists(filestem + ".dvi"):
if not os.path.exists(result):
commands = [
"latex",
"-interaction=batchmode",
@ -122,7 +122,7 @@ def dvi_to_png(dvi_file, regen_if_exists = False):
"convert",
"-density",
str(PDF_DENSITY),
path,
dvi_file,
"-size",
str(DEFAULT_WIDTH) + "x" + str(DEFAULT_HEIGHT),
os.path.join(images_dir, name + ".png")

View file

@ -1,62 +0,0 @@
#!/usr/bin/env python
import numpy as np
import itertools as it
from copy import deepcopy
import sys
from animation import *
from mobject import *
from constants import *
from region import *
from scene import Scene
class LogoGeneration(Scene):
LOGO_RADIUS = 1.5
INNER_RADIUS_RATIO = 0.55
CIRCLE_DENSITY = 100
CIRCLE_BLUE = "skyblue"
SPHERE_DENSITY = 50
SPHERE_BLUE = DARK_BLUE
CIRCLE_SPHERE_INTERPOLATION = 0.3
FRAME_DURATION = 0.01
def construct(self):
self.frame_duration = FRAME_DURATION
circle = Circle(
density = self.CIRCLE_DENSITY,
color = self.CIRCLE_BLUE
).repeat(5).scale(self.LOGO_RADIUS)
sphere = Sphere(
density = self.SPHERE_DENSITY,
color = self.SPHERE_BLUE
).scale(self.LOGO_RADIUS)
sphere.rotate(-np.pi / 7, [1, 0, 0])
sphere.rotate(-np.pi / 7)
alpha = 0.3
iris = Mobject()
Mobject.interpolate(
circle, sphere, iris,
self.CIRCLE_SPHERE_INTERPOLATION
)
for mob, color in [(iris, LIGHT_BROWN), (circle, DARK_BROWN)]:
mob.highlight(color, lambda (x, y, z) : x < 0 and y > 0)
mob.highlight(
"black",
lambda point: np.linalg.norm(point) < \
self.INNER_RADIUS_RATIO*self.LOGO_RADIUS
)
name = TextMobject("3Blue1Brown").center()
name.highlight("grey")
name.shift(2*DOWN)
self.play(Transform(
circle, iris,
run_time = DEFAULT_ANIMATION_RUN_TIME
))
self.add(name)
self.dither()
print "Dragging pixels..."
self.frames = drag_pixels(self.frames)

View file

@ -14,10 +14,13 @@ from helpers import *
import displayer as disp
from tk_scene import TkSceneRoot
from mobject import Mobject
from animation.transform import ApplyMethod
class Scene(object):
DEFAULT_CONFIG = {
"display_config" : PRODUCTION_QUALITY_DISPLAY_CONFIG,
"height" : DEFAULT_HEIGHT,
"width" : DEFAULT_WIDTH ,
"frame_duration" : DEFAULT_FRAME_DURATION,
"construct_args" : [],
"background" : None,
"start_dither_time" : DEFAULT_DITHER_TIME,
@ -27,20 +30,18 @@ class Scene(object):
digest_config(self, kwargs)
if self.announce_construction:
print "Constructing %s..."%str(self)
self.frame_duration = self.display_config["frame_duration"]
self.frames = []
self.mobjects = []
if self.background:
self.original_background = np.array(background)
#TODO, Error checking?
else:
self.original_background = np.zeros(
(self.display_config["height"], self.display_config["width"], 3),
(self.height, self.width, 3),
dtype = 'uint8'
)
self.background = self.original_background
self.shape = self.background.shape[:2]
#TODO, space shape
self.frames = [self.background]
self.mobjects = []
self.construct(*self.construct_args)
def construct(self):
@ -56,18 +57,23 @@ class Scene(object):
self.name = name
return self
def get_frame(self):
return self.frames[-1]
def set_frame(self, frame):
self.frames[-1] = frame
return self
def add(self, *mobjects):
"""
Mobjects will be displayed, from background to foreground,
in the order with which they are entered.
"""
for mobject in mobjects:
if not isinstance(mobject, Mobject):
if not all_elements_are_instances(mobjects, Mobject):
raise Exception("Adding something which is not a mobject")
#In case it's already in there, it should
#now be closer to the foreground.
self.remove(mobject)
self.mobjects.append(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)
return self
def add_local_mobjects(self):
@ -81,11 +87,13 @@ class Scene(object):
))
def remove(self, *mobjects):
for mobject in mobjects:
if not isinstance(mobject, Mobject):
if not all_elements_are_instances(mobjects, Mobject):
raise Exception("Removing something which is not a mobject")
while mobject in self.mobjects:
self.mobjects.remove(mobject)
mobjects = filter(lambda m : m in self.mobjects, mobjects)
if len(mobjects):
return
self.mobjects = filter(lambda m : m not in mobjects, self.mobjects)
self.repaint_mojects()
return self
def bring_to_front(self, mobject):
@ -95,11 +103,13 @@ class Scene(object):
def bring_to_back(self, mobject):
self.remove(mobject)
self.mobjects = [mobject] + self.mobjects
self.repaint_mojects()
return self
def clear(self):
self.reset_background()
self.remove(*self.mobjects)
self.mobjects = []
self.set_frame(self.background)
return self
def highlight_region(self, region, color = None):
@ -108,6 +118,7 @@ class Scene(object):
image_array = self.background,
color = color,
)
self.repaint_mojects()
return self
def highlight_region_over_time_range(self, region, time_range = None, color = "black"):
@ -128,6 +139,10 @@ class Scene(object):
self.background = self.original_background
return self
def repaint_mojects(self):
self.set_frame(disp.paint_mobjects(self.mobjects, self.background))
return self
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
@ -135,6 +150,9 @@ class Scene(object):
self.background = disp.paint_mobjects(mobjects, self.background)
return self
def set_frame_as_background(self):
self.background = self.get_frame()
def play(self, *animations, **kwargs):
if "run_time" in kwargs:
run_time = kwargs["run_time"]
@ -188,9 +206,6 @@ class Scene(object):
def apply(self, mobject_method, *args, **kwargs):
self.play(ApplyMethod(mobject_method, *args, **kwargs))
def get_frame(self):
return disp.paint_mobjects(self.mobjects, self.background)
def dither(self, duration = DEFAULT_DITHER_TIME):
self.frames += [self.get_frame()]*int(duration / self.frame_duration)
return self

View file

@ -3,6 +3,11 @@ from helpers import *
from mobject import Mobject, CompoundMobject
from image_mobject import ImageMobject
PI_CREATURE_DIR = os.path.join(IMAGE_DIR, "PiCreature")
PI_CREATURE_PART_NAME_TO_DIR = lambda name : os.path.join(PI_CREATURE_DIR, "pi_creature_"+name) + ".png"
PI_CREATURE_SCALE_VAL = 0.5
PI_CREATURE_MOUTH_TO_EYES_DISTANCE = 0.25
class PiCreature(CompoundMobject):
DEFAULT_COLOR = BLUE
PART_NAMES = [

View file

@ -26,7 +26,8 @@ class ComplexPlane(NumberPlane):
"x_faded_line_frequency" : self.faded_line_frequency,
"y_line_frequency" : self.line_frequency,
"y_faded_line_frequency" : self.faded_line_frequency,
"num_pair_at_center" : (self.number_at_center.real, self.number_at_center.imag),
"num_pair_at_center" : (self.number_at_center.real,
self.number_at_center.imag),
})
NumberPlane.__init__(self, **kwargs)
@ -88,23 +89,15 @@ class ComplexFunction(ApplyPointwiseFunction):
)
class ComplexHomotopy(Homotopy):
def __init__(self, complex_homotopy, **kwargs):
def __init__(self, complex_homotopy, mobject = ComplexPlane, **kwargs):
"""
Complex Hootopy a function (z, t) to z'
Complex Hootopy a function Cx[0, 1] to C
"""
def homotopy((x, y, z, t)):
c = complex_homotopy((complex(x, y), t))
return (c.real, c.imag, z)
if len(args) > 0:
args = list(args)
mobject = args.pop(0)
elif "mobject" in kwargs:
mobject = kwargs["mobject"]
else:
mobject = Grid()
Homotopy.__init__(self, homotopy, mobject, *args, **kwargs)
self.name = "ComplexHomotopy" + \
to_cammel_case(complex_homotopy.__name__)
class ComplexMultiplication(Scene):
@staticmethod

View file

@ -133,7 +133,7 @@ class CurvedLine(Line):
class PartialCircle(Mobject1D):
DEFAULT_CONFIG = {
"radius" : 1.0,
"start_angle" : 0
"start_angle" : 0,
}
def __init__(self, angle, **kwargs):
digest_locals(self)