From 0d60cf620766c86431f1573b5feb5661d9857c76 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 28 Sep 2015 16:25:18 -0700 Subject: [PATCH] Applied digest_config to almost all __init__ functions --- animation/animation.py | 30 ++--- animation/simple_animations.py | 97 +++++++------- animation/transform.py | 145 ++++++++++----------- constants.py | 8 +- displayer.py | 2 +- helpers.py | 19 +++ mobject/function_graphs.py | 116 ++++++++--------- mobject/image_mobject.py | 73 ++++++----- mobject/mobject.py | 63 +++++---- mobject/simple_mobjects.py | 176 +++++++++++++++----------- mobject/three_dimensional_mobjects.py | 29 +++-- sample_script.py | 5 +- scene/scene.py | 20 +-- scripts/music_and_measure.py | 50 ++++---- 14 files changed, 436 insertions(+), 397 deletions(-) diff --git a/animation/animation.py b/animation/animation.py index 8ca76f6c..913aacff 100644 --- a/animation/animation.py +++ b/animation/animation.py @@ -14,27 +14,23 @@ from constants import * from mobject import Mobject, Point class Animation(object): - def __init__(self, - mobject, - run_time = DEFAULT_ANIMATION_RUN_TIME, - alpha_func = smooth, - name = None): + DEFAULT_CONFIG = { + "run_time" : DEFAULT_ANIMATION_RUN_TIME, + "alpha_func" : smooth, + "name" : None, + } + def __init__(self, mobject, **kwargs): if isinstance(mobject, type) and issubclass(mobject, Mobject): - self.mobject = mobject() - elif isinstance(mobject, Mobject): - self.mobject = mobject - else: + mobject = mobject() + elif not isinstance(mobject, Mobject): raise Exception("Invalid mobject parameter, must be \ subclass or instance of Mobject") + digest_config(self, Animation, kwargs, locals()) self.starting_mobject = copy.deepcopy(self.mobject) - self.alpha_func = alpha_func or (lambda x : x) - self.run_time = run_time - #TODO, Adress the idea of filtering the animation - self.filter_functions = [] - self.restricted_height = SPACE_HEIGHT - self.restricted_width = SPACE_WIDTH - self.spacial_center = np.zeros(3) - self.name = name or self.__class__.__name__ + str(self.mobject) + if self.alpha_func is None: + self.alpha_func = (lambda x : x) + if self.name is None: + self.name = self.__class__.__name__ + str(self.mobject) self.update(0) def __str__(self): diff --git a/animation/simple_animations.py b/animation/simple_animations.py index c70498ab..54e1dae3 100644 --- a/animation/simple_animations.py +++ b/animation/simple_animations.py @@ -16,13 +16,15 @@ class DelayByOrder(Animation): Warning: This will not work on all animation types, but when it does, it will be pretty cool """ - def __init__(self, animation, max_power = 5, **kwargs): - self.animation = animation - self.max_power = max_power - kwargs = dict([ + DEFAULT_CONFIG = { + "max_power" : 5 + } + def __init__(self, animation, **kwargs): + digest_config(self, DelayByOrder, kwargs, locals()) + kwargs.update(dict([ (attr, getattr(animation, attr)) - for attr in "run_time", "alpha_func" - ]) + for attr in Animation.DEFAULT_CONFIG + ])) self.num_mobject_points = animation.mobject.get_num_points() Animation.__init__(self, animation.mobject, **kwargs) self.name = self.__class__.__name__ + str(self.animation) @@ -38,45 +40,31 @@ class DelayByOrder(Animation): self.animation.update_mobject(alpha_array) class Rotating(Animation): - def __init__(self, - mobject, - axis = None, - axes = [RIGHT, UP], - radians = 2 * np.pi, - run_time = 20.0, - alpha_func = None, - *args, **kwargs): - Animation.__init__( - self, mobject, - run_time = run_time, - alpha_func = alpha_func, - *args, **kwargs - ) - self.axes = [axis] if axis is not None else axes - self.radians = radians + DEFAULT_CONFIG = { + "axes" : [RIGHT, UP], + "radians" : 2*np.pi, + "run_time" : 20.0, + "alpha_func" : None, + } + def __init__(self, mobject, **kwargs): + digest_config(self, Rotating, kwargs, locals()) + Animation.__init__(self, mobject, **kwargs) def update_mobject(self, alpha): self.mobject.points = self.starting_mobject.points for axis in self.axes: - self.mobject.rotate( - self.radians * alpha, - axis - ) + self.mobject.rotate(self.radians * alpha, axis) class RotationAsTransform(Rotating): - def __init__(self, mobject, radians, axis = IN, axes = None, - run_time = DEFAULT_ANIMATION_RUN_TIME, - alpha_func = smooth, - *args, **kwargs): - Rotating.__init__( - self, - mobject, - axis = axis, - axes = axes, - run_time = run_time, - radians = radians, - alpha_func = alpha_func, - ) + DEFAULT_CONFIG = { + "axes" : [IN], + "radians" : np.pi / 2, + "run_time" : DEFAULT_ANIMATION_RUN_TIME, + "alpha_func" : smooth, + } + def __init__(self, mobject, **kwargs): + digest_config(self, RotationAsTransform, kwargs, locals()) + Rotating.__init__(self, mobject, **kwargs) class FadeOut(Animation): def update_mobject(self, alpha): @@ -90,9 +78,9 @@ class FadeIn(Animation): #TODO, Why do you need to do this? Shouldn't points always align? class ShimmerIn(DelayByOrder): - def __init__(self, mobject, *args, **kwargs): + def __init__(self, mobject, **kwargs): mobject.sort_points(lambda p : np.dot(p, DOWN+RIGHT)) - DelayByOrder.__init__(self, FadeIn(mobject, *args, **kwargs)) + DelayByOrder.__init__(self, FadeIn(mobject, **kwargs)) class ShowCreation(Animation): @@ -107,21 +95,22 @@ class ShowCreation(Animation): ) class Flash(Animation): - def __init__(self, mobject, color = "white", slow_factor = 0.01, - run_time = 0.1, alpha_func = None, - *args, **kwargs): - Animation.__init__(self, mobject, run_time = run_time, - alpha_func = alpha_func, - *args, **kwargs) - self.intermediate = Mobject(color = color) + DEFAULT_CONFIG = { + "color" : "white", + "slow_factor" : 0.01, + "run_time" : 0.1, + "alpha_func" : None, + } + def __init__(self, mobject, **kwargs): + digest_config(self, Flash, kwargs, locals()) + self.intermediate = Mobject(color = self.color) self.intermediate.add_points([ point + (x, y, 0) for point in self.mobject.points for x in [-1, 1] for y in [-1, 1] ]) - self.reference_mobjects.append(self.intermediate) - self.slow_factor = slow_factor + Animation.__init__(self, mobject, **kwargs) def update_mobject(self, alpha): #Makes alpha go from 0 to slow_factor to 0 instead of 0 to 1 @@ -134,12 +123,12 @@ class Flash(Animation): ) class Homotopy(Animation): - def __init__(self, homotopy, *args, **kwargs): + def __init__(self, homotopy, **kwargs): """ Homotopy a function from (x, y, z, t) to (x', y', z') """ - self.homotopy = homotopy - Animation.__init__(self, *args, **kwargs) + digest_config(self, Homotopy, kwargs, locals()) + Animation.__init__(self, **kwargs) def update_mobject(self, alpha): self.mobject.points = np.array([ @@ -148,7 +137,7 @@ class Homotopy(Animation): ]) class ComplexHomotopy(Homotopy): - def __init__(self, complex_homotopy, *args, **kwargs): + def __init__(self, complex_homotopy, **kwargs): """ Complex Hootopy a function (z, t) to z' """ diff --git a/animation/transform.py b/animation/transform.py index dcedc6b6..8c73bf59 100644 --- a/animation/transform.py +++ b/animation/transform.py @@ -5,7 +5,7 @@ import copy import warnings from animation import Animation -from mobject import Mobject, Point +from mobject import Mobject, Point, Grid from constants import * from helpers import * @@ -31,25 +31,25 @@ def counterclockwise_path(start_points, end_points, alpha): return semi_circular_path(start_points, end_points, alpha, OUT) class Transform(Animation): - def __init__(self, mobject1, mobject2, - run_time = DEFAULT_TRANSFORM_RUN_TIME, - interpolation_function = straight_path, - black_out_extra_points = False, - *args, **kwargs): - self.interpolation_function = interpolation_function - count1, count2 = mobject1.get_num_points(), mobject2.get_num_points() + DEFAULT_CONFIG = { + "run_time" : DEFAULT_TRANSFORM_RUN_TIME, + "interpolation_function" : straight_path, + "should_black_out_extra_points" : False + } + def __init__(self, mobject, ending_mobject, **kwargs): + digest_config(self, Transform, kwargs, locals()) + count1, count2 = mobject.get_num_points(), ending_mobject.get_num_points() if count2 == 0: - mobject2.add_points([SPACE_WIDTH*RIGHT+SPACE_HEIGHT*UP]) - count2 = mobject2.get_num_points() - Mobject.align_data(mobject1, mobject2) - self.ending_mobject = mobject2 - if black_out_extra_points and count2 < count1: + ending_mobject.add_points([SPACE_WIDTH*RIGHT+SPACE_HEIGHT*UP]) + count2 = ending_mobject.get_num_points() + Mobject.align_data(mobject, ending_mobject) + if self.should_black_out_extra_points and count2 < count1: self.black_out_extra_points(count1, count2) - Animation.__init__(self, mobject1, run_time = run_time, *args, **kwargs) - self.name += "To" + str(mobject2) - self.mobject.SHOULD_BUFF_POINTS = \ - mobject1.SHOULD_BUFF_POINTS and mobject2.SHOULD_BUFF_POINTS + Animation.__init__(self, mobject, **kwargs) + self.name += "To" + str(ending_mobject) + self.mobject.should_buffer_points = \ + mobject.should_buffer_points and ending_mobject.should_buffer_points def black_out_extra_points(self, count1, count2): #Ensure redundant pixels fade to black @@ -89,53 +89,28 @@ class Transform(Animation): ) class ClockwiseTransform(Transform): - def __init__(self, mobject1, mobject2, **kwargs): - Transform.__init__( - self, mobject1, mobject2, - interpolation_function = clockwise_path, **kwargs - ) + DEFAULT_CONFIG = { + "interpolation_function" : clockwise_path + } + def __init__(self, *args, **kwargs): + digest_config(self, ClockwiseTransform, kwargs) + Transform.__init__(self, *args, **kwargs) class CounterclockwiseTransform(Transform): - def __init__(self, mobject1, mobject2, **kwargs): - Transform.__init__( - self, mobject1, mobject2, - interpolation_function = counterclockwise_path, **kwargs - ) + DEFAULT_CONFIG = { + "interpolation_function" : counterclockwise_path + } + def __init__(self, *args, **kwargs): + digest_config(self, ClockwiseTransform, kwargs) + Transform.__init__(self, *args, **kwargs) class SpinInFromNothing(Transform): + DEFAULT_CONFIG = { + "interpolation_function" : counterclockwise_path + } def __init__(self, mob, **kwargs): - name = "interpolation_function" - interp_func = kwargs[name] if name in kwargs else counterclockwise_path - dot = Point(mob.get_center(), color = "black") - Transform.__init__( - self, dot, mob, - interpolation_function = interp_func, - **kwargs - ) - - -class FadeToColor(Transform): - def __init__(self, mobject, color, *args, **kwargs): - target = copy.deepcopy(mobject).highlight(color) - Transform.__init__(self, mobject, target, *args, **kwargs) - -class Highlight(FadeToColor): - def __init__(self, mobject, color = "red", - run_time = DEFAULT_ANIMATION_RUN_TIME, - alpha_func = there_and_back, *args, **kwargs): - FadeToColor.__init__( - self, mobject, color, - run_time = run_time, - alpha_func = alpha_func, - *args, **kwargs - ) - -class ScaleInPlace(Transform): - def __init__(self, mobject, scale_factor, *args, **kwargs): - target = copy.deepcopy(mobject) - center = mobject.get_center() - target.shift(-center).scale(scale_factor).shift(center) - Transform.__init__(self, mobject, target, *args, **kwargs) + digest_config(self, SpinInFromNothing, kwargs) + Transform.__init__(self, Point(mob.get_center()), mob, **kwargs) class ApplyMethod(Transform): def __init__(self, method, *args, **kwargs): @@ -155,6 +130,14 @@ class ApplyMethod(Transform): **kwargs ) +class FadeToColor(ApplyMethod): + def __init__(self, mobject, color, **kwargs): + ApplyMethod.__init__(self, mobject.highlight, color, **kwargs) + +class ScaleInPlace(ApplyMethod): + def __init__(self, mobject, scale_factor, **kwargs): + ApplyMethod.__init__(self, mobject.scale_in_place, scale_factor, **kwargs) + class ApplyFunction(Transform): def __init__(self, function, mobject, **kwargs): Transform.__init__( @@ -165,16 +148,15 @@ class ApplyFunction(Transform): ) self.name = "ApplyFunctionTo"+str(mobject) - class ApplyPointwiseFunction(Transform): - def __init__(self, function, mobject, - run_time = DEFAULT_ANIMATION_RUN_TIME, **kwargs): + DEFAULT_CONFIG = { + "run_time" : DEFAULT_ANIMATION_RUN_TIME + } + def __init__(self, function, mobject, **kwargs): + digest_config(self, ApplyPointwiseFunction, kwargs) map_image = copy.deepcopy(mobject) map_image.points = np.array(map(function, map_image.points)) - Transform.__init__( - self, mobject, map_image, - run_time = run_time, **kwargs - ) + Transform.__init__(self, mobject, map_image, **kwargs) self.name = "".join([ "Apply", "".join([s.capitalize() for s in function.__name__.split("_")]), @@ -201,22 +183,25 @@ class ComplexFunction(ApplyPointwiseFunction): class TransformAnimations(Transform): - def __init__(self, start_anim, end_anim, - alpha_func = squish_alpha_func(smooth), - **kwargs): + DEFAULT_CONFIG = { + "alpha_func" : squish_alpha_func(smooth) + } + def __init__(self, start_anim, end_anim, **kwargs): + digest_config(self, TransformAnimations, kwargs, locals()) if "run_time" in kwargs: - run_time = kwargs.pop("run_time") + self.run_time = kwargs.pop("run_time") + else: + self.run_time = max(start_anim.run_time, end_anim.run_time) + for anim in start_anim, end_anim: + anim.set_run_time(self.run_time) + + if start_anim.starting_mobject.get_num_points() != end_anim.starting_mobject.get_num_points(): + Mobject.align_data(start_anim.starting_mobject, end_anim.starting_mobject) for anim in start_anim, end_anim: - anim.set_run_time(run_time) - self.start_anim, self.end_anim = start_anim, end_anim - Transform.__init__( - self, - start_anim.mobject, - end_anim.mobject, - run_time = max(start_anim.run_time, end_anim.run_time), - alpha_func = alpha_func, - **kwargs - ) + if hasattr(anim, "ending_mobject"): + Mobject.align_data(anim.starting_mobject, anim.ending_mobject) + + Transform.__init__(self, start_anim.mobject, end_anim.mobject, **kwargs) #Rewire starting and ending mobjects start_anim.mobject = self.starting_mobject end_anim.mobject = self.ending_mobject diff --git a/constants.py b/constants.py index 86bf211a..16a8ce32 100644 --- a/constants.py +++ b/constants.py @@ -2,7 +2,7 @@ import os import numpy as np -GENERALLY_BUFF_POINTS = True +GENERALLY_BUFFER_POINTS = True PRODUCTION_QUALITY_DISPLAY_CONFIG = { "height" : 1440, @@ -27,14 +27,16 @@ SPACE_HEIGHT = 4.0 SPACE_WIDTH = SPACE_HEIGHT * DEFAULT_WIDTH / DEFAULT_HEIGHT +DEFAULT_MOBJECT_TO_EDGE_BUFFER = 0.5 +DEFAULT_MOBJECT_TO_MOBJECT_BUFFER = 0.2 + + #All in seconds DEFAULT_FRAME_DURATION = 0.04 DEFAULT_ANIMATION_RUN_TIME = 1.0 DEFAULT_TRANSFORM_RUN_TIME = 1.0 DEFAULT_DITHER_TIME = 1.0 -DEFAULT_NUM_STARS = 1000 - ORIGIN = np.array(( 0, 0, 0)) UP = np.array(( 0, 1, 0)) diff --git a/displayer.py b/displayer.py index b8cc78b5..e8122cbe 100644 --- a/displayer.py +++ b/displayer.py @@ -69,7 +69,7 @@ def paint_mobject(mobject, image_array = None): flattener = np.array([1, width], dtype = 'int').reshape((2, 1)) indices = np.dot(points, flattener) indices = indices.reshape(indices.size) - if mobject.should_buffer_points():#Is this alright? + if mobject.should_buffer_points:#Is this alright? for tweak in [ indices + 1, indices + width, diff --git a/helpers.py b/helpers.py index 4f7766fc..939ea7e3 100644 --- a/helpers.py +++ b/helpers.py @@ -9,6 +9,25 @@ import operator as op from constants import * +def digest_config(obj, Class, kwargs, local_args = {}): + """ + To be used in initializing most-to-all objects. + Sets key word args as local variables + """ + if hasattr(Class, "DEFAULT_CONFIG"): + config = Class.DEFAULT_CONFIG.copy() + else: + config = {} + for key in config.keys(): + if hasattr(obj, key): + config.pop(key) + if key in kwargs: + config[key] = kwargs.pop(key) + for key in local_args: + if key not in ["self", "kwargs"]: + config[key] = local_args[key] + obj.__dict__.update(config) + def interpolate(start, end, alpha): return (1-alpha)*start + alpha*end diff --git a/mobject/function_graphs.py b/mobject/function_graphs.py index c12ebe8f..202bdc99 100644 --- a/mobject/function_graphs.py +++ b/mobject/function_graphs.py @@ -7,41 +7,42 @@ from constants import * from helpers import * class FunctionGraph(Mobject1D): - DEFAULT_COLOR = "lightblue" - def __init__(self, function, x_range = [-10, 10], *args, **kwargs): - self.function = function - self.x_min = x_range[0] / SPACE_WIDTH - self.x_max = x_range[1] / SPACE_WIDTH - Mobject1D.__init__(self, *args, **kwargs) + DEFAULT_CONFIG = { + "color" : "lightblue", + "x_min" : -10, + "x_max" : 10, + "spacial_radius" : SPACE_WIDTH, + } + def __init__(self, function, **kwargs): + digest_config(self, FunctionGraph, kwargs, locals()) + Mobject1D.__init__(self, **kwargs) def generate_points(self): - scale_factor = 2.0 * SPACE_WIDTH / (self.x_max - self.x_min) - self.epsilon /= scale_factor + numerical_radius = (self.x_max - self.x_min)/2 + numerical_center = (self.x_max + self.x_min)/2 + ratio = numerical_radius / self.spacial_radius + epsilon = self.epsilon * ratio self.add_points([ - np.array([x, self.function(x), 0]) + np.array([(x-numerical_center)/ratio, self.function(x), 0]) for x in np.arange(self.x_min, self.x_max, self.epsilon) ]) - self.scale(scale_factor) class ParametricFunction(Mobject): - DEFAULT_COLOR = "white" - def __init__(self, - function, - dim = 1, - expected_measure = 10.0, - density = None, - *args, - **kwargs): - self.function = function - self.dim = dim - self.expected_measure = expected_measure - if density: - self.epsilon = 1.0 / density + DEFAULT_CONFIG = { + "color" : "white", + "dim" : 1, + "expected_measure" : 10.0, + "density" : None + } + def __init__(self, function, **kwargs): + digest_config(self, ParametricFunction, kwargs, locals()) + if self.density: + self.epsilon = 1.0 / self.density elif self.dim == 1: - self.epsilon = 1.0 / expected_measure / DEFAULT_POINT_DENSITY_1D + self.epsilon = 1.0 / self.expected_measure / DEFAULT_POINT_DENSITY_1D else: - self.epsilon = 1.0 / np.sqrt(expected_measure) / DEFAULT_POINT_DENSITY_2D + self.epsilon = 1.0 / np.sqrt(self.expected_measure) / DEFAULT_POINT_DENSITY_2D Mobject.__init__(self, *args, **kwargs) def generate_points(self): @@ -58,16 +59,15 @@ class ParametricFunction(Mobject): ]) class Grid(Mobject1D): - DEFAULT_COLOR = "green" - def __init__(self, - radius = max(SPACE_HEIGHT, SPACE_WIDTH), - interval_size = 1.0, - subinterval_size = 0.5, - *args, **kwargs): - self.radius = radius - self.interval_size = interval_size - self.subinterval_size = subinterval_size - Mobject1D.__init__(self, *args, **kwargs) + DEFAULT_CONFIG = { + "color" : "green", + "radius" : max(SPACE_HEIGHT, SPACE_WIDTH), + "interval_size" : 1.0, + "subinterval_size" : 0.5, + } + def __init__(self, **kwargs): + digest_config(self, Grid, kwargs) + Mobject1D.__init__(self, **kwargs) def generate_points(self): self.add_points([ @@ -91,27 +91,20 @@ class Grid(Mobject1D): ], color = color) class NumberLine(Mobject1D): - def __init__(self, - radius = SPACE_WIDTH, - unit_length_to_spacial_width = 1, - tick_size = 0.1, - tick_frequency = 0.5, - number_at_center = 0, - numbers_with_elongated_ticks = [0], - longer_tick_multiple = 2, - **kwargs): - #TODO, There must be better (but still safe) way to add all - #these config arguments as attributes. - self.radius = radius - self.unit_length_to_spacial_width = unit_length_to_spacial_width - self.tick_size = tick_size - self.tick_frequency = tick_frequency - self.numbers_with_elongated_ticks = numbers_with_elongated_ticks - self.number_at_center = number_at_center - self.longer_tick_multiple = longer_tick_multiple - numerical_radius = float(radius) / unit_length_to_spacial_width - self.left_num = number_at_center - numerical_radius - self.right_num = number_at_center + numerical_radius + DEFAULT_CONFIG = { + "radius" : SPACE_WIDTH, + "unit_length_to_spacial_width" : 1, + "tick_size" : 0.1, + "tick_frequency" : 0.5, + "number_at_center" : 0, + "numbers_with_elongated_ticks" : [0], + "longer_tick_multiple" : 2, + } + def __init__(self, **kwargs): + digest_config(self, NumberLine, kwargs) + numerical_radius = float(self.radius) / self.unit_length_to_spacial_width + self.left_num = self.number_at_center - numerical_radius + self.right_num = self.number_at_center + numerical_radius Mobject1D.__init__(self, **kwargs) def generate_points(self): @@ -170,15 +163,14 @@ class UnitInterval(NumberLine): "numbers_with_elongated_ticks" : [0, 1], } def __init__(self, **kwargs): - config = self.DEFAULT_CONFIG - config.update(kwargs) - NumberLine.__init__(self, **config) + digest_config(self, UnitInterval, kwargs) + NumberLine.__init__(self, **kwargs) class Axes(CompoundMobject): - def __init__(self, *args, **kwargs): - x_axis = NumberLine(*args, **kwargs) - y_axis = NumberLine(*args, **kwargs).rotate(np.pi/2, OUT) + def __init__(self, **kwargs): + x_axis = NumberLine(**kwargs) + y_axis = NumberLine(**kwargs).rotate(np.pi/2, OUT) CompoundMobject.__init__(self, x_axis, y_axis) diff --git a/mobject/image_mobject.py b/mobject/image_mobject.py index e03f1db2..051c38c4 100644 --- a/mobject/image_mobject.py +++ b/mobject/image_mobject.py @@ -7,23 +7,24 @@ from random import random from tex_utils import * from mobject import * -class ImageMobject(Mobject2D): +class ImageMobject(Mobject): """ Automatically filters out black pixels """ -# SHOULD_BUFF_POINTS = False - def __init__(self, - image_file, - filter_color = "black", - invert = True, - use_cache = True, - *args, **kwargs): - Mobject2D.__init__(self, *args, **kwargs) - self.filter_rgb = 255 * np.array(Color(filter_color).get_rgb()).astype('uint8') + DEFAULT_CONFIG = { + "filter_color" : "black", + "invert" : True, + "use_cache" : True, + "should_buffer_points" : False, + "scale_value" : 1.0 + } + def __init__(self, image_file, **kwargs): + digest_config(self, ImageMobject, kwargs, locals()) + Mobject.__init__(self, **kwargs) + self.filter_rgb = 255 * np.array(Color(self.filter_color).get_rgb()).astype('uint8') self.name = to_cammel_case( os.path.split(image_file)[-1].split(".")[0] ) - self.use_cache = use_cache possible_paths = [ image_file, os.path.join(IMAGE_DIR, image_file), @@ -32,31 +33,33 @@ class ImageMobject(Mobject2D): ] for path in possible_paths: if os.path.exists(path): - self.generate_points_from_file(path, invert) + self.generate_points_from_file(path) + self.scale(self.scale_value) + self.center() return raise IOError("File not Found") - def generate_points_from_file(self, path, invert): - if self.use_cache and self.read_in_cached_attrs(path, invert): + def generate_points_from_file(self, path): + if self.use_cache and self.read_in_cached_attrs(path): return image = Image.open(path).convert('RGB') - if invert: + if self.invert: image = invert_image(image) self.generate_points_from_image_array(np.array(image)) - self.cache_attrs(path, invert) + self.cache_attrs(path) - def get_cached_attr_files(self, path, invert, attrs): + def get_cached_attr_files(self, path, attrs): #Hash should be unique to (path, invert) pair - unique_hash = str(hash(path+str(invert))) + unique_hash = str(hash(path+str(self.invert))) return [ os.path.join(IMAGE_MOBJECT_DIR, unique_hash)+"."+attr for attr in attrs ] - def read_in_cached_attrs(self, path, invert, + def read_in_cached_attrs(self, path, attrs = ("points", "rgbs"), dtype = "float64"): - cached_attr_files = self.get_cached_attr_files(path, invert, attrs) + cached_attr_files = self.get_cached_attr_files(path, attrs) if all(map(os.path.exists, cached_attr_files)): for attr, cache_file in zip(attrs, cached_attr_files): arr = np.fromfile(cache_file, dtype = dtype) @@ -65,10 +68,10 @@ class ImageMobject(Mobject2D): return True return False - def cache_attrs(self, path, invert, + def cache_attrs(self, path, attrs = ("points", "rgbs"), dtype = "float64"): - cached_attr_files = self.get_cached_attr_files(path, invert, attrs) + cached_attr_files = self.get_cached_attr_files(path, attrs) for attr, cache_file in zip(attrs, cached_attr_files): getattr(self, attr).astype(dtype).tofile(cache_file) @@ -98,20 +101,26 @@ class ImageMobject(Mobject2D): return False class Face(ImageMobject): - def __init__(self, mode = "simple", *args, **kwargs): + DEFAULT_CONFIG = { + "mode" : "simple", + "scale_value" : 0.5 + } + def __init__(self, **kwargs): """ Mode can be "simple", "talking", "straight" """ - ImageMobject.__init__(self, mode + "_face", *args, **kwargs) - self.scale(0.5) - self.center() + digest_config(self, Face, kwargs) + ImageMobject.__init__(self, self.mode + "_face", **kwargs) class VideoIcon(ImageMobject): - def __init__(self, *args, **kwargs): - ImageMobject.__init__(self, "video_icon", *args, **kwargs) - self.scale(0.3) - self.center() + DEFAULT_CONFIG = { + "scale_value" : 0.3 + } + def __init__(self, **kwargs): + digest_config(self, VideoIcon, kwargs) + ImageMobject.__init__(self, "video_icon", **kwargs) +#TODO, Make both of these proper mobject classes def text_mobject(text, size = None): size = size or "\\Large" #TODO, auto-adjust? return tex_mobject(text, size, TEMPLATE_TEXT_FILE) @@ -130,8 +139,8 @@ def tex_mobject(expression, #TODO, is checking listiness really the best here? result = CompoundMobject(*map(ImageMobject, image_files)) else: - result = ImageMobject(image_files) - return result.highlight("white").center() + result = ImageMobject(image_files, should_buffer_points = True) + return result.highlight("white") diff --git a/mobject/mobject.py b/mobject/mobject.py index 79f3b80f..88612547 100644 --- a/mobject/mobject.py +++ b/mobject/mobject.py @@ -17,26 +17,20 @@ class Mobject(object): Mathematical Object """ #Number of numbers used to describe a point (3 for pos, 3 for normal vector) + DEFAULT_CONFIG = { + "color" : "white", + "should_buffer_points" : GENERALLY_BUFFER_POINTS, + "name" : None, + } DIM = 3 - DEFAULT_COLOR = Color("skyblue") - SHOULD_BUFF_POINTS = GENERALLY_BUFF_POINTS - EDGE_BUFFER = 0.5 - NEXT_TO_BUFFER = 0.2 - - def __init__(self, - color = None, - name = None, - center = None, - **kwargs - ): - self.color = Color(color) if color else Color(self.DEFAULT_COLOR) - if not hasattr(self, "name"): - self.name = name or self.__class__.__name__ + def __init__(self, **kwargs): + digest_config(self, Mobject, kwargs) + self.color = Color(self.color) + if self.name is None: + self.name = self.__class__.__name__ self.has_normals = hasattr(self, 'unit_normal') self.init_points() self.generate_points() - if center: - self.center().shift(center) def init_points(self): self.points = np.zeros((0, 3)) @@ -130,13 +124,13 @@ class Mobject(object): return self #Wrapper functions for better naming - def to_corner(self, corner = LEFT+DOWN, buff = EDGE_BUFFER): + def to_corner(self, corner = LEFT+DOWN, buff = DEFAULT_MOBJECT_TO_EDGE_BUFFER): return self.align_on_border(corner, buff) - def to_edge(self, edge = LEFT, buff = EDGE_BUFFER): + def to_edge(self, edge = LEFT, buff = DEFAULT_MOBJECT_TO_EDGE_BUFFER): return self.align_on_border(edge, buff) - def align_on_border(self, direction, buff = EDGE_BUFFER): + def align_on_border(self, direction, buff = DEFAULT_MOBJECT_TO_EDGE_BUFFER): """ Direction just needs to be a vector pointing towards side or corner in the 2d plane. @@ -155,7 +149,7 @@ class Mobject(object): def next_to(self, mobject, direction = RIGHT, - buff = NEXT_TO_BUFFER, + buff = DEFAULT_MOBJECT_TO_MOBJECT_BUFFER, aligned_edge = None): if aligned_edge is not None: anchor_point = self.get_corner(aligned_edge-direction) @@ -231,6 +225,11 @@ class Mobject(object): self.rgbs[:,:] = rgb return self + def set_color(self, color): + self.highlight(color) + self.color = Color(color) + return self + def to_original_color(self): self.highlight(self.color) return self @@ -315,7 +314,7 @@ class Mobject(object): ### Stuff subclasses should deal with ### def should_buffer_points(self): # potentially changed in subclasses - return GENERALLY_BUFF_POINTS + return GENERALLY_BUFFER_POINTS def generate_points(self): #Typically implemented in subclass, unless purposefully left blank @@ -346,16 +345,24 @@ class Mobject(object): alpha * getattr(mobject2, attr) setattr(target_mobject, attr, new_array) +#TODO, Make the two implementations bellow not redundant class Mobject1D(Mobject): - def __init__(self, density = DEFAULT_POINT_DENSITY_1D, *args, **kwargs): - self.epsilon = 1.0 / density - - Mobject.__init__(self, *args, **kwargs) + DEFAULT_CONFIG = { + "density" : DEFAULT_POINT_DENSITY_1D, + } + def __init__(self, **kwargs): + digest_config(self, Mobject1D, kwargs) + self.epsilon = 1.0 / self.density + Mobject.__init__(self, **kwargs) class Mobject2D(Mobject): - def __init__(self, density = DEFAULT_POINT_DENSITY_2D, *args, **kwargs): - self.epsilon = 1.0 / density - Mobject.__init__(self, *args, **kwargs) + DEFAULT_CONFIG = { + "density" : DEFAULT_POINT_DENSITY_2D, + } + def __init__(self, **kwargs): + digest_config(self, Mobject1D, kwargs) + self.epsilon = 1.0 / self.density + Mobject.__init__(self, **kwargs) class CompoundMobject(Mobject): def __init__(self, *mobjects): diff --git a/mobject/simple_mobjects.py b/mobject/simple_mobjects.py index b3baf05a..8fc64f66 100644 --- a/mobject/simple_mobjects.py +++ b/mobject/simple_mobjects.py @@ -8,28 +8,24 @@ from helpers import * class Point(Mobject): - DEFAULT_COLOR = "black" - def __init__(self, location = ORIGIN, *args, **kwargs): - self.location = np.array(location) - Mobject.__init__(self, *args, **kwargs) + DEFAULT_CONFIG = { + "color" : "black", + } + def __init__(self, location = ORIGIN, **kwargs): + digest_config(self, Point, kwargs, locals()) + Mobject.__init__(self, **kwargs) def generate_points(self): - self.add_points(self.location.reshape((1, 3))) + self.add_points(self.location) class Dot(Mobject1D): #Use 1D density, even though 2D - DEFAULT_COLOR = "white" - DEFAULT_RADIUS = 0.05 - def __init__(self, center = ORIGIN, radius = DEFAULT_RADIUS, - *args, **kwargs): - center = np.array(center) - if center.size == 1: - raise Exception("Center must have 2 or 3 coordinates!") - elif center.size == 2: - center = np.append(center, [0]) - self.center_point = center - self.radius = radius - Mobject1D.__init__(self, *args, **kwargs) + DEFAULT_CONFIG = { + "radius" : 0.05 + } + def __init__(self, center_point = ORIGIN, **kwargs): + digest_config(self, Dot, kwargs, locals()) + Mobject1D.__init__(self, **kwargs) def generate_points(self): self.add_points([ @@ -40,22 +36,30 @@ class Dot(Mobject1D): #Use 1D density, even though 2D ]) class Cross(Mobject1D): - RADIUS = 0.3 - DEFAULT_COLOR = "white" + DEFAULT_CONFIG = { + "color" : "yellow", + "radius" : 0.3 + } + def __init__(self, center_point = ORIGIN, **kwargs): + digest_config(self, Cross, kwargs, locals()) + Mobject1D.__init__(self, **kwargs) + def generate_points(self): self.add_points([ (sgn * x, x, 0) - for x in np.arange(-self.RADIUS / 2, self.RADIUS/2, self.epsilon) + for x in np.arange(-self.radius / 2, self.radius/2, self.epsilon) for sgn in [-1, 1] ]) + self.shift(self.center_point) class Line(Mobject1D): - MIN_DENSITY = 0.1 - def __init__(self, start, end, density = DEFAULT_POINT_DENSITY_1D, - *args, **kwargs): + DEFAULT_CONFIG = { + "min_density" : 0.1 + } + def __init__(self, start, end, **kwargs): + digest_config(self, Line, kwargs) self.set_start_and_end(start, end) - density *= max(self.get_length(), self.MIN_DENSITY) - Mobject1D.__init__(self, density = density, *args, **kwargs) + Mobject1D.__init__(self, **kwargs) def set_start_and_end(self, start, end): preliminary_start, preliminary_end = [ @@ -74,9 +78,11 @@ class Line(Mobject1D): ] def generate_points(self): + length = np.linalg.norm(self.end - self.start) + epsilon = self.epsilon / max(length, self.min_density) self.add_points([ interpolate(self.start, self.end, t) - for t in np.arange(0, 1, self.epsilon) + for t in np.arange(0, 1, epsilon) ]) def get_length(self): @@ -90,22 +96,21 @@ class Line(Mobject1D): return rise/run class Arrow(Line): - DEFAULT_COLOR = "white" - DEFAULT_TIP_LENGTH = 0.25 + DEFAULT_CONFIG = { + "color" : "white", + "tip_length" : 0.25 + } def __init__(self, *args, **kwargs): - if "tip_length" in kwargs: - tip_length = kwargs.pop("tip_length") - else: - tip_length = self.DEFAULT_TIP_LENGTH + digest_config(self, Arrow, kwargs) Line.__init__(self, *args, **kwargs) - self.add_tip(tip_length) + self.add_tip() - def add_tip(self, tip_length): + def add_tip(self): vect = self.start-self.end - vect = vect*tip_length/np.linalg.norm(vect) + vect = vect*self.tip_length/np.linalg.norm(vect) self.add_points([ interpolate(self.end, self.end+v, t) - for t in np.arange(0, 1, tip_length*self.epsilon) + for t in np.arange(0, 1, self.tip_length*self.epsilon) for v in [ rotate_vector(vect, np.pi/4, axis) for axis in IN, OUT @@ -113,7 +118,7 @@ class Arrow(Line): ]) class CurvedLine(Line): - def __init__(self, start, end, via = None, *args, **kwargs): + def __init__(self, start, end, via = None, **kwargs): self.set_start_and_end(start, end) if via == None: self.via = rotate_vector( @@ -124,7 +129,7 @@ class CurvedLine(Line): self.via = via.get_center() else: self.via = via - Line.__init__(self, start, end, *args, **kwargs) + Line.__init__(self, start, end, **kwargs) def generate_points(self): self.add_points([ @@ -137,9 +142,12 @@ class CurvedLine(Line): ]) class Circle(Mobject1D): - DEFAULT_COLOR = "red" - def __init__(self, radius = 1.0, **kwargs): - self.radius = radius + DEFAULT_CONFIG = { + "color" : "red", + "radius" : 1.0 + } + def __init__(self, **kwargs): + digest_config(self, Circle, kwargs) Mobject1D.__init__(self, **kwargs) def generate_points(self): @@ -149,9 +157,13 @@ class Circle(Mobject1D): ]) class Rectangle(Mobject1D): - DEFAULT_COLOR = "yellow" - def __init__(self, height = 2.0, width = 2.0, **kwargs): - self.height, self.width = height, width + DEFAULT_CONFIG = { + "color" : "yellow", + "height" : 2.0, + "width" : 4.0 + } + def __init__(self, **kwargs): + digest_config(self, Rectangle, kwargs) Mobject1D.__init__(self, **kwargs) def generate_points(self): @@ -164,17 +176,27 @@ class Rectangle(Mobject1D): ]) class Square(Rectangle): - def __init__(self, side_length = 2.0, **kwargs): - Rectangle.__init__(self, side_length, side_length, **kwargs) + DEFAULT_CONFIG = { + "height" : 2.0, + "width" : 2.0, + } + def __init__(self, **kwargs): + digest_config(self, Square, kwargs) + Rectangle.__init__(self, **kwargs) class Bubble(Mobject): - def __init__(self, direction = LEFT, index_of_tip = -1, center = ORIGIN): - self.direction = direction - self.content = Mobject() - self.index_of_tip = index_of_tip - self.center_offset = center - Mobject.get_center(self) - if direction[0] > 0: + DEFAULT_CONFIG = { + "direction" : LEFT, + "index_of_tip" : -1, + "center_point" : ORIGIN, + } + def __init__(self, **kwargs): + digest_config(self, Bubble, kwargs) + Mobject.__init__(self, **kwargs) + self.center_offset = self.center_point - Mobject.get_center(self) + if self.direction[0] > 0: self.rotate(np.pi, UP) + self.content = Mobject() def get_tip(self): return self.points[self.index_of_tip] @@ -217,13 +239,19 @@ class Bubble(Mobject): return self class SpeechBubble(Bubble): - INITIAL_WIDTH = 4 - INITIAL_HEIGHT = 2 - def __init__(self, *args, **kwargs): - Mobject.__init__(self, *args, **kwargs) + DEFAULT_CONFIG = { + "initial_width" : 4, + "initial_height" : 2, + } + def __init__(self, **kwargs): + digest_config(self, SpeechBubble, kwargs) + Bubble.__init__(self, **kwargs) + + def generate_points(self): complex_power = 0.9 - radius = self.INITIAL_WIDTH/2 - circle = Circle(density = radius*DEFAULT_POINT_DENSITY_1D) + radius = self.initial_width/2 + circle = Circle(radius = radius) + circle.scale(1.0/radius) circle.apply_complex_function(lambda z : z**complex_power) circle.scale(radius) boundary_point_as_complex = radius*complex(-1)**complex_power @@ -243,28 +271,28 @@ class SpeechBubble(Bubble): ) self.highlight("white") self.rotate(np.pi/2) - self.points[:,1] *= float(self.INITIAL_HEIGHT)/self.INITIAL_WIDTH - Bubble.__init__(self, direction = LEFT) + self.points[:,1] *= float(self.initial_height)/self.initial_width class ThoughtBubble(Bubble): - NUM_BULGES = 7 - INITIAL_INNER_RADIUS = 1.8 - INITIAL_WIDTH = 6 - def __init__(self, *args, **kwargs): - Mobject.__init__(self, *args, **kwargs) + DEFAULT_CONFIG = { + "num_bulges" : 7, + "initial_inner_radius" : 1.8, + "initial_width" : 6 + } + def __init__(self, **kwargs): + digest_config(self, ThoughtBubble, kwargs) + Bubble.__init__(self, **kwargs) + self.index_of_tip = np.argmin(self.points[:,1]) + + def generate_points(self): self.add(Circle().scale(0.15).shift(2.5*DOWN+2*LEFT)) self.add(Circle().scale(0.3).shift(2*DOWN+1.5*LEFT)) - for n in range(self.NUM_BULGES): - theta = 2*np.pi*n/self.NUM_BULGES + for n in range(self.num_bulges): + theta = 2*np.pi*n/self.num_bulges self.add(Circle().shift((np.cos(theta), np.sin(theta), 0))) - self.filter_out(lambda p : np.linalg.norm(p) < self.INITIAL_INNER_RADIUS) - self.stretch_to_fit_width(self.INITIAL_WIDTH) + self.filter_out(lambda p : np.linalg.norm(p) < self.initial_inner_radius) + self.stretch_to_fit_width(self.initial_width) self.highlight("white") - Bubble.__init__( - self, - index_of_tip = np.argmin(self.points[:,1]), - **kwargs - ) diff --git a/mobject/three_dimensional_mobjects.py b/mobject/three_dimensional_mobjects.py index 66d43e53..605e69c7 100644 --- a/mobject/three_dimensional_mobjects.py +++ b/mobject/three_dimensional_mobjects.py @@ -7,15 +7,14 @@ from constants import * from helpers import * class Stars(Mobject): - DEFAULT_COLOR = "white" - SHOULD_BUFF_POINTS = False - def __init__(self, - radius = SPACE_WIDTH, - num_points = DEFAULT_NUM_STARS, - *args, **kwargs): - self.num_points = num_points - self.radius = radius - Mobject.__init__(self, *args, **kwargs) + DEFAULT_CONFIG = { + "should_buffer_points" : False, + "radius" : SPACE_WIDTH, + "num_points" : 1000, + } + def __init__(self, **kwargs): + digest_config(self, Stars, kwargs) + Mobject.__init__(self, **kwargs) def generate_points(self): self.add_points([ @@ -42,12 +41,12 @@ class CubeWithFaces(Mobject2D): for sgn in [-1, 1] ]) self.pose_at_angle() + self.set_color("skyblue") def unit_normal(self, coords): return np.array(map(lambda x : 1 if abs(x) == 1 else 0, coords)) class Cube(Mobject1D): - DEFAULT_COLOR = "yellow" def generate_points(self): self.add_points([ ([a, b, c][p[0]], [a, b, c][p[1]], [a, b, c][p[2]]) @@ -55,9 +54,9 @@ class Cube(Mobject1D): for a, b, c in it.product([-1, 1], [-1, 1], np.arange(-1, 1, self.epsilon)) ]) self.pose_at_angle() + self.set_color("yellow") class Octohedron(Mobject1D): - DEFAULT_COLOR = "pink" def generate_points(self): x = np.array([1, 0, 0]) y = np.array([0, 1, 0]) @@ -72,9 +71,9 @@ class Octohedron(Mobject1D): Line(pair[0], pair[1], density = 1/self.epsilon).points ) self.pose_at_angle() + self.set_color("pink") class Dodecahedron(Mobject1D): - DEFAULT_COLOR = "limegreen" def generate_points(self): phi = (1 + np.sqrt(5)) / 2 x = np.array([1, 0, 0]) @@ -99,6 +98,7 @@ class Dodecahedron(Mobject1D): matrix = b*np.array([x[perm], y[perm], z[perm]]) self.add_points(np.dot(five_lines_points, matrix)) self.pose_at_angle() + self.set_color("limegreen") class Sphere(Mobject2D): def generate_points(self): @@ -111,6 +111,9 @@ class Sphere(Mobject2D): for phi in np.arange(self.epsilon, np.pi, self.epsilon) for theta in np.arange(0, 2 * np.pi, 2 * self.epsilon / np.sin(phi)) ]) + self.set_color("blue") def unit_normal(self, coords): - return np.array(coords) / np.linalg.norm(coords) \ No newline at end of file + return np.array(coords) / np.linalg.norm(coords) + + \ No newline at end of file diff --git a/sample_script.py b/sample_script.py index aff80a97..106bc3b0 100644 --- a/sample_script.py +++ b/sample_script.py @@ -16,7 +16,10 @@ from script_wrapper import command_line_create_scene class SampleScene(Scene): def construct(self): - pass + square = Square() + self.add(square) + self.dither() + self.play(Flash(square)) if __name__ == "__main__": command_line_create_scene() \ No newline at end of file diff --git a/scene/scene.py b/scene/scene.py index a4fe48fb..20db87f6 100644 --- a/scene/scene.py +++ b/scene/scene.py @@ -15,16 +15,18 @@ import displayer as disp from tk_scene import TkSceneRoot class Scene(object): - def __init__(self, - display_config = PRODUCTION_QUALITY_DISPLAY_CONFIG, - construct_args = [], - background = None, - start_dither_time = DEFAULT_DITHER_TIME): - self.display_config = display_config - self.frame_duration = display_config["frame_duration"] + DEFAULT_CONFIG = { + "display_config" : PRODUCTION_QUALITY_DISPLAY_CONFIG, + "construct_args" : [], + "background" : None, + "start_dither_time" : DEFAULT_DITHER_TIME + } + def __init__(self, **kwargs): + digest_config(self, Scene, kwargs) + self.frame_duration = self.display_config["frame_duration"] self.frames = [] self.mobjects = [] - if background: + if self.background: self.original_background = np.array(background) #TODO, Error checking? else: @@ -35,7 +37,7 @@ class Scene(object): self.background = self.original_background self.shape = self.background.shape[:2] #TODO, space shape - self.construct(*construct_args) + self.construct(*self.construct_args) def construct(self): pass #To be implemented in subclasses diff --git a/scripts/music_and_measure.py b/scripts/music_and_measure.py index fbd63a39..76bc92da 100644 --- a/scripts/music_and_measure.py +++ b/scripts/music_and_measure.py @@ -65,21 +65,27 @@ def zero_to_one_interval(): return interval class OpenInterval(Mobject): - def __init__(self, center = ORIGIN, width = 2, **kwargs): + def __init__(self, center_point = ORIGIN, width = 2, **kwargs): + digest_config(self, OpenInterval, kwargs, locals()) Mobject.__init__(self, **kwargs) self.add(tex_mobject("(").shift(LEFT)) self.add(tex_mobject(")").shift(RIGHT)) scale_factor = width / 2.0 self.stretch(scale_factor, 0) self.stretch(0.5+0.5*scale_factor, 1) - self.shift(center) + self.shift(center_point) class Piano(ImageMobject): - SHOULD_BUFF_POINTS = False + DEFAULT_CONFIG = { + "should_buffer_points" : False, + "invert" : False, + "scale_value" : 0.5 + } def __init__(self, **kwargs): - ImageMobject.__init__(self, "piano_keyboard", invert = False) + digest_config(self, Piano, kwargs) + ImageMobject.__init__(self, "piano_keyboard") jump = self.get_width()/24 - self.scale(0.5).center() + self.center() self.half_note_jump = self.get_width()/24 self.ivory_jump = self.get_width()/14 @@ -100,28 +106,26 @@ class Piano(ImageMobject): class VibratingString(Animation): - def __init__(self, - num_periods = 1, - overtones = 4, - amplitude = 0.5, - radius = INTERVAL_RADIUS, - center = ORIGIN, - color = "white", - run_time = 3.0, - alpha_func = None, - **kwargs): - self.radius = radius - self.center = center + DEFAULT_CONFIG = { + "num_periods" : 1, + "overtones" : 4, + "amplitude" : 0.5, + "radius" : INTERVAL_RADIUS, + "center" : ORIGIN, + "color" : "white", + "run_time" : 3.0, + "alpha_func" : None + } + def __init__(self, **kwargs): + digest_config(self, VibratingString, kwargs) def func(x, t): return sum([ - (amplitude/((k+1)**2.5))*np.sin(2*mult*t)*np.sin(k*mult*x) - for k in range(overtones) - for mult in [(num_periods+k)*np.pi] + (self.amplitude/((k+1)**2.5))*np.sin(2*mult*t)*np.sin(k*mult*x) + for k in range(self.overtones) + for mult in [(self.num_periods+k)*np.pi] ]) self.func = func - kwargs["run_time"] = run_time - kwargs["alpha_func"] = alpha_func - Animation.__init__(self, Mobject1D(color = color), **kwargs) + Animation.__init__(self, Mobject1D(color = self.color), **kwargs) def update_mobject(self, alpha): self.mobject.init_points()