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

View file

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

View file

@ -187,10 +187,8 @@ def main():
inspect.getmembers(module, is_scene) inspect.getmembers(module, is_scene)
) )
config["movie_prefix"] = config["module"] config["movie_prefix"] = config["module"]
scene_kwargs = { scene_kwargs = config["display_config"]
"display_config" : config["display_config"], scene_kwargs["announce_construction"] = True
"announce_construction" : True
}
for SceneClass in get_scene_classes(scene_names_to_classes, config): for SceneClass in get_scene_classes(scene_names_to_classes, config):
for args in get_scene_args(SceneClass, config): for args in get_scene_args(SceneClass, config):
scene_kwargs["construct_args"] = args 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 * 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): def adjascent_pairs(objects):
return zip(objects, list(objects[1:])+[objects[0]]) 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): def tex_to_dvi(tex_file):
result = tex_file.replace(".tex", ".dvi") result = tex_file.replace(".tex", ".dvi")
if not os.path.exists(filestem + ".dvi"): if not os.path.exists(result):
commands = [ commands = [
"latex", "latex",
"-interaction=batchmode", "-interaction=batchmode",
@ -122,7 +122,7 @@ def dvi_to_png(dvi_file, regen_if_exists = False):
"convert", "convert",
"-density", "-density",
str(PDF_DENSITY), str(PDF_DENSITY),
path, dvi_file,
"-size", "-size",
str(DEFAULT_WIDTH) + "x" + str(DEFAULT_HEIGHT), str(DEFAULT_WIDTH) + "x" + str(DEFAULT_HEIGHT),
os.path.join(images_dir, name + ".png") 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 import displayer as disp
from tk_scene import TkSceneRoot from tk_scene import TkSceneRoot
from mobject import Mobject from mobject import Mobject
from animation.transform import ApplyMethod
class Scene(object): class Scene(object):
DEFAULT_CONFIG = { DEFAULT_CONFIG = {
"display_config" : PRODUCTION_QUALITY_DISPLAY_CONFIG, "height" : DEFAULT_HEIGHT,
"width" : DEFAULT_WIDTH ,
"frame_duration" : DEFAULT_FRAME_DURATION,
"construct_args" : [], "construct_args" : [],
"background" : None, "background" : None,
"start_dither_time" : DEFAULT_DITHER_TIME, "start_dither_time" : DEFAULT_DITHER_TIME,
@ -27,20 +30,18 @@ class Scene(object):
digest_config(self, kwargs) digest_config(self, kwargs)
if self.announce_construction: if self.announce_construction:
print "Constructing %s..."%str(self) print "Constructing %s..."%str(self)
self.frame_duration = self.display_config["frame_duration"]
self.frames = []
self.mobjects = []
if self.background: if self.background:
self.original_background = np.array(background) self.original_background = np.array(background)
#TODO, Error checking? #TODO, Error checking?
else: else:
self.original_background = np.zeros( self.original_background = np.zeros(
(self.display_config["height"], self.display_config["width"], 3), (self.height, self.width, 3),
dtype = 'uint8' dtype = 'uint8'
) )
self.background = self.original_background self.background = self.original_background
self.shape = self.background.shape[:2] self.frames = [self.background]
#TODO, space shape self.mobjects = []
self.construct(*self.construct_args) self.construct(*self.construct_args)
def construct(self): def construct(self):
@ -56,18 +57,23 @@ class Scene(object):
self.name = name self.name = name
return self 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): def add(self, *mobjects):
""" """
Mobjects will be displayed, from background to foreground, Mobjects will be displayed, from background to foreground,
in the order with which they are entered. in the order with which they are entered.
""" """
for mobject in mobjects: if not all_elements_are_instances(mobjects, Mobject):
if not isinstance(mobject, Mobject):
raise Exception("Adding something which is not a mobject") raise Exception("Adding something which is not a mobject")
#In case it's already in there, it should self.set_frame(disp.paint_mobjects(mobjects, self.get_frame()))
#now be closer to the foreground. old_mobjects = filter(lambda m : m not in mobjects, self.mobjects)
self.remove(mobject) self.mobjects = old_mobjects + list(mobjects)
self.mobjects.append(mobject)
return self return self
def add_local_mobjects(self): def add_local_mobjects(self):
@ -81,11 +87,13 @@ class Scene(object):
)) ))
def remove(self, *mobjects): def remove(self, *mobjects):
for mobject in mobjects: if not all_elements_are_instances(mobjects, Mobject):
if not isinstance(mobject, Mobject):
raise Exception("Removing something which is not a mobject") raise Exception("Removing something which is not a mobject")
while mobject in self.mobjects: mobjects = filter(lambda m : m in self.mobjects, mobjects)
self.mobjects.remove(mobject) if len(mobjects):
return
self.mobjects = filter(lambda m : m not in mobjects, self.mobjects)
self.repaint_mojects()
return self return self
def bring_to_front(self, mobject): def bring_to_front(self, mobject):
@ -95,11 +103,13 @@ class Scene(object):
def bring_to_back(self, mobject): def bring_to_back(self, mobject):
self.remove(mobject) self.remove(mobject)
self.mobjects = [mobject] + self.mobjects self.mobjects = [mobject] + self.mobjects
self.repaint_mojects()
return self return self
def clear(self): def clear(self):
self.reset_background() self.reset_background()
self.remove(*self.mobjects) self.mobjects = []
self.set_frame(self.background)
return self return self
def highlight_region(self, region, color = None): def highlight_region(self, region, color = None):
@ -108,6 +118,7 @@ class Scene(object):
image_array = self.background, image_array = self.background,
color = color, color = color,
) )
self.repaint_mojects()
return self return self
def highlight_region_over_time_range(self, region, time_range = None, color = "black"): 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 self.background = self.original_background
return self return self
def repaint_mojects(self):
self.set_frame(disp.paint_mobjects(self.mobjects, self.background))
return self
def paint_into_background(self, *mobjects): def paint_into_background(self, *mobjects):
#This way current mobjects don't have to be redrawn with #This way current mobjects don't have to be redrawn with
#every change, and one can later call "apply" without worrying #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) self.background = disp.paint_mobjects(mobjects, self.background)
return self return self
def set_frame_as_background(self):
self.background = self.get_frame()
def play(self, *animations, **kwargs): def play(self, *animations, **kwargs):
if "run_time" in kwargs: if "run_time" in kwargs:
run_time = kwargs["run_time"] run_time = kwargs["run_time"]
@ -188,9 +206,6 @@ class Scene(object):
def apply(self, mobject_method, *args, **kwargs): def apply(self, mobject_method, *args, **kwargs):
self.play(ApplyMethod(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): def dither(self, duration = DEFAULT_DITHER_TIME):
self.frames += [self.get_frame()]*int(duration / self.frame_duration) self.frames += [self.get_frame()]*int(duration / self.frame_duration)
return self return self

View file

@ -3,6 +3,11 @@ from helpers import *
from mobject import Mobject, CompoundMobject from mobject import Mobject, CompoundMobject
from image_mobject import ImageMobject 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): class PiCreature(CompoundMobject):
DEFAULT_COLOR = BLUE DEFAULT_COLOR = BLUE
PART_NAMES = [ PART_NAMES = [

View file

@ -26,7 +26,8 @@ class ComplexPlane(NumberPlane):
"x_faded_line_frequency" : self.faded_line_frequency, "x_faded_line_frequency" : self.faded_line_frequency,
"y_line_frequency" : self.line_frequency, "y_line_frequency" : self.line_frequency,
"y_faded_line_frequency" : self.faded_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) NumberPlane.__init__(self, **kwargs)
@ -88,23 +89,15 @@ class ComplexFunction(ApplyPointwiseFunction):
) )
class ComplexHomotopy(Homotopy): 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)): def homotopy((x, y, z, t)):
c = complex_homotopy((complex(x, y), t)) c = complex_homotopy((complex(x, y), t))
return (c.real, c.imag, z) 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) Homotopy.__init__(self, homotopy, mobject, *args, **kwargs)
self.name = "ComplexHomotopy" + \
to_cammel_case(complex_homotopy.__name__)
class ComplexMultiplication(Scene): class ComplexMultiplication(Scene):
@staticmethod @staticmethod

View file

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