Merge pull request #180 from 3b1b/simple-improvements

Factored helper functions into better organized utils folder
This commit is contained in:
Grant Sanderson 2018-03-30 18:49:49 -07:00 committed by GitHub
commit 2945296083
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
77 changed files with 1044 additions and 959 deletions

3
.gitignore vendored
View file

@ -12,6 +12,7 @@ ben_cairo_test.py
.flooignore
*.xml
*.iml
manim.sublime-project
manim.sublime-workspace
primes.py

View file

@ -1,7 +1,7 @@
from helpers import *
from constants import *
from mobject.tex_mobject import TexMobject
from mobject import Mobject
from mobject.mobject import Mobject
from mobject.image_mobject import ImageMobject
from mobject.vectorized_mobject import *
@ -22,10 +22,10 @@ from topics.objects import *
from topics.complex_numbers import *
from topics.common_scenes import *
from topics.probability import *
from scene import Scene
from scene.scene import Scene
from scene.reconfigurable_scene import ReconfigurableScene
from scene.zoomed_scene import *
from camera import Camera
from camera.camera import Camera
from mobject.svg_mobject import *
from mobject.tex_mobject import *

View file

@ -1,7 +1,7 @@
from helpers import *
from constants import *
from mobject.tex_mobject import TexMobject
from mobject import Mobject
from mobject.mobject import Mobject
from mobject.image_mobject import ImageMobject
from mobject.vectorized_mobject import *
@ -22,10 +22,10 @@ from topics.objects import *
from topics.complex_numbers import *
from topics.common_scenes import *
from topics.probability import *
from scene import Scene
from scene.scene import Scene
from scene.reconfigurable_scene import ReconfigurableScene
from scene.zoomed_scene import *
from camera import Camera
from camera.camera import Camera
from mobject.svg_mobject import *
from mobject.tex_mobject import *

View file

@ -1,7 +1,7 @@
from helpers import *
from constants import *
from mobject.tex_mobject import TexMobject
from mobject import Mobject
from mobject.mobject import Mobject
from mobject.image_mobject import ImageMobject
from mobject.vectorized_mobject import *
@ -22,10 +22,10 @@ from topics.three_dimensions import *
from topics.objects import *
from topics.probability import *
from topics.complex_numbers import *
from scene import Scene
from scene.scene import Scene
from scene.reconfigurable_scene import ReconfigurableScene
from scene.zoomed_scene import *
from camera import Camera
from camera.camera import Camera
from mobject.svg_mobject import *
from mobject.tex_mobject import *
from topics.graph_scene import *

View file

@ -1,7 +1,7 @@
from helpers import *
from constants import *
from mobject.tex_mobject import TexMobject
from mobject import Mobject
from mobject.mobject import Mobject
from mobject.image_mobject import ImageMobject
from mobject.vectorized_mobject import *
@ -22,10 +22,10 @@ from topics.objects import *
from topics.complex_numbers import *
from topics.common_scenes import *
from topics.probability import *
from scene import Scene
from scene.scene import Scene
from scene.reconfigurable_scene import ReconfigurableScene
from scene.zoomed_scene import *
from camera import Camera
from camera.camera import Camera
from mobject.svg_mobject import *
from mobject.tex_mobject import *

View file

@ -2,6 +2,4 @@ __all__ = [
"animation",
"simple_animations",
"transform"
]
from animation import Animation
]

View file

@ -1,15 +1,11 @@
from PIL import Image
from colour import Color
import numpy as np
import warnings
import time
import os
import progressbar
import inspect
from copy import deepcopy
from helpers import *
from mobject import Mobject
from constants import *
from mobject.mobject import Mobject
from utils.rate_functions import smooth
from utils.config_ops import instantiate
from utils.config_ops import digest_config
class Animation(object):
CONFIG = {

View file

@ -1,14 +1,17 @@
import numpy as np
import itertools as it
from helpers import *
from constants import *
import warnings
from mobject import Mobject, Group
from mobject.mobject import Mobject, Group
from mobject.vectorized_mobject import VMobject
from mobject.tex_mobject import TextMobject
from animation import Animation
from .animation import Animation
from transform import Transform
from utils.bezier import inverse_interpolate
from utils.config_ops import digest_config
from utils.rate_functions import squish_rate_func
class LaggedStart(Animation):
CONFIG = {

View file

@ -1,7 +1,9 @@
from helpers import *
from mobject import Mobject, Group
from constants import *
from mobject.mobject import Mobject, Group
from simple_animations import MaintainPositionRelativeTo
import copy
from utils.config_ops import instantiate
from utils.config_ops import digest_config
class ContinualAnimation(object):
CONFIG = {

View file

@ -1,12 +1,14 @@
import numpy as np
import operator as op
from animation import Animation
from .animation import Animation
from transform import Transform
from mobject import Mobject1D, Mobject
from mobject.mobject import Mobject
from mobject.point_cloud_mobject import Mobject1D
from topics.geometry import Line
from utils.paths import path_along_arc
from helpers import *
from constants import *
class Vibrate(Animation):
CONFIG = {

View file

@ -1,14 +1,17 @@
import numpy as np
import itertools as it
from helpers import *
from constants import *
import warnings
from mobject import Mobject, Group
from mobject.mobject import Mobject, Group
from mobject.vectorized_mobject import VMobject
from mobject.tex_mobject import TextMobject
from animation import Animation
from .animation import Animation
from transform import Transform
from utils.bezier import interpolate
from utils.config_ops import digest_config
from utils.rate_functions import smooth, double_smooth, there_and_back, wiggle
class Rotating(Animation):
CONFIG = {

View file

@ -4,11 +4,17 @@ import inspect
import copy
import warnings
from helpers import *
from constants import *
from animation import Animation
from mobject import Mobject, Point, VMobject, Group
from .animation import Animation
from mobject.mobject import Mobject, Group
from mobject.vectorized_mobject import VMobject, VectorizedPoint
from topics.geometry import Dot, Circle
from utils.config_ops import digest_config
from utils.iterables import adjacent_pairs
from utils.paths import straight_path, path_along_arc, counterclockwise_path
from utils.rate_functions import smooth, there_and_back
from utils.rate_functions import squish_rate_func
class Transform(Animation):
CONFIG = {
@ -107,7 +113,7 @@ class GrowFromPoint(Transform):
def __init__(self, mobject, point, **kwargs):
digest_config(self, kwargs)
target = mobject.copy()
point_mob = Point(point)
point_mob = VectorizedPoint(point)
if self.point_color:
point_mob.set_color(self.point_color)
mobject.replace(point_mob)

View file

@ -14,7 +14,6 @@ as a convenience for scripts createing scenes for videos
"""
from constants import *
from helpers import *
from animation.animation import *
from animation.compositions import *
@ -59,6 +58,18 @@ from topics.probability import *
from topics.three_dimensions import *
from topics.vector_space_scene import *
from utils.bezier import *
from utils.color import *
from utils.config_ops import *
from utils.images import *
from utils.iterables import *
from utils.paths import *
from utils.rate_functions import *
from utils.simple_functions import *
from utils.sounds import *
from utils.space_ops import *
from utils.strings import *
from special_animations import *
# Non manim libraries that are also nice to have without thinking

View file

@ -1 +0,0 @@
from camera import *

View file

@ -6,13 +6,21 @@ from PIL import Image
from colour import Color
import aggdraw
import copy
from helpers import *
from mobject import Mobject, PMobject, VMobject, \
ImageMobject, Group
import time
from constants import *
from mobject.mobject import Mobject, Group
from mobject.point_cloud_mobject import PMobject
from mobject.vectorized_mobject import VMobject
from mobject.image_mobject import ImageMobject
from utils.color import rgb_to_hex, color_to_int_rgba
from utils.config_ops import digest_config, digest_locals, DictAsObject
from utils.images import get_full_raster_image_path
from utils.iterables import remove_list_redundancies, list_difference_update
from utils.iterables import batch_by_property
from utils.simple_functions import fdiv
class Camera(object):
CONFIG = {
"background_image" : None,

View file

@ -1,6 +1,12 @@
import os
import numpy as np
# Things anyone wishing to use this repository for their
# own use will want to change this
MEDIA_DIR = os.path.join(os.path.expanduser('~'), "Dropbox (3Blue1Brown)/3Blue1Brown Team Folder")
#
DEFAULT_PIXEL_HEIGHT = 1080
DEFAULT_PIXEL_WIDTH = 1920
@ -66,10 +72,6 @@ RIGHT_SIDE = FRAME_X_RADIUS*RIGHT
TAU = 2*np.pi
DEGREES = TAU/360
# Change this to point to where you want
# animation files to output
MEDIA_DIR = os.path.join(os.path.expanduser('~'), "Dropbox (3Blue1Brown)/3Blue1Brown Team Folder")
ANIMATIONS_DIR = os.path.join(MEDIA_DIR, "animations")
RASTER_IMAGE_DIR = os.path.join(MEDIA_DIR, "designs", "raster_images")
SVG_IMAGE_DIR = os.path.join(MEDIA_DIR, "designs", "svg_images")
@ -100,13 +102,11 @@ TEX_TEXT_TO_REPLACE = "YourTextHere"
TEMPLATE_TEX_FILE = os.path.join(THIS_DIR, "template.tex")
TEMPLATE_TEXT_FILE = os.path.join(THIS_DIR, "text_template.tex")
FFMPEG_BIN = "ffmpeg"
### Colors ###
COLOR_MAP = {
"DARK_BLUE" : "#236B8E",
"DARK_BROWN" : "#8B4513",

View file

@ -1 +1 @@
from container import *

View file

@ -1,4 +1,5 @@
from helpers import *
from constants import *
from utils.config_ops import digest_config
# Currently, this is only used by both Scene and MOBject.
# Still, we abstract its functionality here, albeit purely nominally.

View file

@ -1,31 +1,6 @@
#!/usr/bin/env python
from helpers import *
from mobject.tex_mobject import TexMobject
from mobject import Mobject
from mobject.image_mobject import ImageMobject
from mobject.vectorized_mobject import *
from mobject.svg_mobject import *
from mobject.tex_mobject import *
from scene import Scene
from camera import Camera
from animation.animation import Animation
from animation.transform import *
from animation.simple_animations import *
from animation.compositions import *
from animation.playground import *
from topics.geometry import *
from topics.characters import *
from topics.functions import *
from topics.number_line import *
from topics.combinatorics import *
from topics.three_dimensions import *
from topics.three_dimensions import *
from big_ol_pile_of_manim_imports import *
# To watch one of these scenes, run the following:
# python extract_scene.py file_name <SceneName> -p

View file

@ -11,9 +11,10 @@ import imp
import os
import subprocess as sp
from helpers import *
from scene import Scene
from camera import Camera
from constants import *
from scene.scene import Scene
from camera.camera import Camera
from utils.sounds import play_error_sound, play_finish_sound
HELP_MESSAGE = """
Usage:

View file

@ -1,728 +0,0 @@
import numpy as np
import itertools as it
import operator as op
from PIL import Image
from colour import Color
import random
import inspect
import string
import re
import os
from scipy import linalg
from constants import *
CLOSED_THRESHOLD = 0.01
STRAIGHT_PATH_THRESHOLD = 0.01
def play_chord(*nums):
commands = [
"play",
"-n",
"-c1",
"--no-show-progress",
"synth",
] + [
"sin %-"+str(num)
for num in nums
] + [
"fade h 0.5 1 0.5",
"> /dev/null"
]
try:
os.system(" ".join(commands))
except:
pass
def play_error_sound():
play_chord(11, 8, 6, 1)
def play_finish_sound():
play_chord(12, 9, 5, 2)
def get_smooth_handle_points(points):
points = np.array(points)
num_handles = len(points) - 1
dim = points.shape[1]
if num_handles < 1:
return np.zeros((0, dim)), np.zeros((0, dim))
#Must solve 2*num_handles equations to get the handles.
#l and u are the number of lower an upper diagonal rows
#in the matrix to solve.
l, u = 2, 1
#diag is a representation of the matrix in diagonal form
#See https://www.particleincell.com/2012/bezier-splines/
#for how to arive at these equations
diag = np.zeros((l+u+1, 2*num_handles))
diag[0,1::2] = -1
diag[0,2::2] = 1
diag[1,0::2] = 2
diag[1,1::2] = 1
diag[2,1:-2:2] = -2
diag[3,0:-3:2] = 1
#last
diag[2,-2] = -1
diag[1,-1] = 2
#This is the b as in Ax = b, where we are solving for x,
#and A is represented using diag. However, think of entries
#to x and b as being points in space, not numbers
b = np.zeros((2*num_handles, dim))
b[1::2] = 2*points[1:]
b[0] = points[0]
b[-1] = points[-1]
solve_func = lambda b : linalg.solve_banded(
(l, u), diag, b
)
if is_closed(points):
#Get equations to relate first and last points
matrix = diag_to_matrix((l, u), diag)
#last row handles second derivative
matrix[-1, [0, 1, -2, -1]] = [2, -1, 1, -2]
#first row handles first derivative
matrix[0,:] = np.zeros(matrix.shape[1])
matrix[0,[0, -1]] = [1, 1]
b[0] = 2*points[0]
b[-1] = np.zeros(dim)
solve_func = lambda b : linalg.solve(matrix, b)
handle_pairs = np.zeros((2*num_handles, dim))
for i in range(dim):
handle_pairs[:,i] = solve_func(b[:,i])
return handle_pairs[0::2], handle_pairs[1::2]
def diag_to_matrix(l_and_u, diag):
"""
Converts array whose rows represent diagonal
entries of a matrix into the matrix itself.
See scipy.linalg.solve_banded
"""
l, u = l_and_u
dim = diag.shape[1]
matrix = np.zeros((dim, dim))
for i in range(l+u+1):
np.fill_diagonal(
matrix[max(0,i-u):,max(0,u-i):],
diag[i,max(0,u-i):]
)
return matrix
def is_closed(points):
return np.linalg.norm(points[0] - points[-1]) < CLOSED_THRESHOLD
## Color
def color_to_rgb(color):
return np.array(Color(color).get_rgb())
def color_to_rgba(color, alpha = 1):
return np.append(color_to_rgb(color), [alpha])
def rgb_to_color(rgb):
try:
return Color(rgb = rgb)
except:
return Color(WHITE)
def rgba_to_color(rgba):
return rgb_to_color(rgba[:3])
def rgb_to_hex(rgb):
return "#" + "".join('%02x'%int(255*x) for x in rgb)
def invert_color(color):
return rgb_to_color(1.0 - color_to_rgb(color))
def color_to_int_rgb(color):
return (255*color_to_rgb(color)).astype('uint8')
def color_to_int_rgba(color, alpha = 255):
return np.append(color_to_int_rgb(color), alpha)
def color_gradient(reference_colors, length_of_output):
if length_of_output == 0:
return reference_colors[0]
rgbs = map(color_to_rgb, reference_colors)
alphas = np.linspace(0, (len(rgbs) - 1), length_of_output)
floors = alphas.astype('int')
alphas_mod1 = alphas % 1
#End edge case
alphas_mod1[-1] = 1
floors[-1] = len(rgbs) - 2
return [
rgb_to_color(interpolate(rgbs[i], rgbs[i+1], alpha))
for i, alpha in zip(floors, alphas_mod1)
]
def interpolate_color(color1, color2, alpha):
rgb = interpolate(color_to_rgb(color1), color_to_rgb(color2), alpha)
return rgb_to_color(rgb)
def average_color(*colors):
rgbs = np.array(map(color_to_rgb, colors))
mean_rgb = np.apply_along_axis(np.mean, 0, rgbs)
return rgb_to_color(mean_rgb)
###
def compass_directions(n = 4, start_vect = RIGHT):
angle = 2*np.pi/n
return np.array([
rotate_vector(start_vect, k*angle)
for k in range(n)
])
def partial_bezier_points(points, a, b):
"""
Given an array of points which define
a bezier curve, and two numbers 0<=a<b<=1,
return an array of the same size, which
describes the portion of the original bezier
curve on the interval [a, b].
This algorithm is pretty nifty, and pretty dense.
"""
a_to_1 = np.array([
bezier(points[i:])(a)
for i in range(len(points))
])
return np.array([
bezier(a_to_1[:i+1])((b-a)/(1.-a))
for i in range(len(points))
])
def bezier(points):
n = len(points) - 1
return lambda t : sum([
((1-t)**(n-k))*(t**k)*choose(n, k)*point
for k, point in enumerate(points)
])
def remove_list_redundancies(l):
"""
Used instead of list(set(l)) to maintain order
Keeps the last occurance of each element
"""
reversed_result = []
used = set()
for x in reversed(l):
if not x in used:
reversed_result.append(x)
used.add(x)
reversed_result.reverse()
return reversed_result
def list_update(l1, l2):
"""
Used instead of list(set(l1).update(l2)) to maintain order,
making sure duplicates are removed from l1, not l2.
"""
return filter(lambda e : e not in l2, l1) + list(l2)
def list_difference_update(l1, l2):
return filter(lambda e : e not in l2, l1)
def all_elements_are_instances(iterable, Class):
return all(map(lambda e : isinstance(e, Class), iterable))
def adjacent_pairs(objects):
return zip(objects, list(objects[1:])+[objects[0]])
def batch_by_property(items, property_func):
"""
Takes in a list, and returns a list of tuples, (batch, prop)
such that all items in a batch have the same output when
put into property_func, and such that chaining all these
batches together would give the original list.
"""
batch_prop_pairs = []
def add_batch_prop_pair(batch):
if len(batch) > 0:
batch_prop_pairs.append(
(batch, property_func(batch[0]))
)
curr_batch = []
curr_prop = None
for item in items:
prop = property_func(item)
if prop != curr_prop:
add_batch_prop_pair(curr_batch)
curr_prop = prop
curr_batch = [item]
else:
curr_batch.append(item)
add_batch_prop_pair(curr_batch)
return batch_prop_pairs
def complex_to_R3(complex_num):
return np.array((complex_num.real, complex_num.imag, 0))
def R3_to_complex(point):
return complex(*point[:2])
def tuplify(obj):
if isinstance(obj, str):
return (obj,)
try:
return tuple(obj)
except:
return (obj,)
def instantiate(obj):
"""
Useful so that classes or instance of those classes can be
included in configuration, which can prevent defaults from
getting created during compilation/importing
"""
return obj() if isinstance(obj, type) else obj
def get_all_descendent_classes(Class):
awaiting_review = [Class]
result = []
while awaiting_review:
Child = awaiting_review.pop()
awaiting_review += Child.__subclasses__()
result.append(Child)
return result
def filtered_locals(caller_locals):
result = caller_locals.copy()
ignored_local_args = ["self", "kwargs"]
for arg in ignored_local_args:
result.pop(arg, caller_locals)
return result
def digest_config(obj, kwargs, caller_locals = {}):
"""
Sets init args and CONFIG values as local variables
The purpose of this function is to ensure that all
configuration of any object is inheritable, able to
be easily passed into instantiation, and is attached
as an attribute of the object.
"""
# Assemble list of CONFIGs from all super classes
classes_in_hierarchy = [obj.__class__]
static_configs = []
while len(classes_in_hierarchy) > 0:
Class = classes_in_hierarchy.pop()
classes_in_hierarchy += Class.__bases__
if hasattr(Class, "CONFIG"):
static_configs.append(Class.CONFIG)
#Order matters a lot here, first dicts have higher priority
caller_locals = filtered_locals(caller_locals)
all_dicts = [kwargs, caller_locals, obj.__dict__]
all_dicts += static_configs
all_new_dicts = [kwargs, caller_locals] + static_configs
obj.__dict__ = merge_config(all_dicts)
#Keep track of the configuration of objects upon
#instantiation
obj.initial_config = merge_config(all_new_dicts)
def merge_config(all_dicts):
all_config = reduce(op.add, [d.items() for d in all_dicts])
config = dict()
for c in all_config:
key, value = c
if not key in config:
config[key] = value
else:
#When two dictionaries have the same key, they are merged.
if isinstance(value, dict) and isinstance(config[key], dict):
config[key] = merge_config([config[key], value])
return config
def soft_dict_update(d1, d2):
"""
Adds key values pairs of d2 to d1 only when d1 doesn't
already have that key
"""
for key, value in d2.items():
if key not in d1:
d1[key] = value
def digest_locals(obj, keys = None):
caller_locals = filtered_locals(
inspect.currentframe().f_back.f_locals
)
if keys is None:
keys = caller_locals.keys()
for key in keys:
setattr(obj, key, caller_locals[key])
def interpolate(start, end, alpha):
return (1-alpha)*start + alpha*end
def mid(start, end):
return (start + end)/2.0
def inverse_interpolate(start, end, value):
return np.true_divide(value - start, end - start)
def match_interpolate(new_start, new_end, old_start, old_end, old_value):
return interpolate(
new_start, new_end,
inverse_interpolate(old_start, old_end, old_value))
def clamp(lower, upper, val):
if val < lower:
return lower
elif val > upper:
return upper
return val
def center_of_mass(points):
points = [np.array(point).astype("float") for point in points]
return sum(points) / len(points)
def choose(n, r):
if n < r: return 0
if r == 0: return 1
denom = reduce(op.mul, xrange(1, r+1), 1)
numer = reduce(op.mul, xrange(n, n-r, -1), 1)
return numer//denom
def is_on_line(p0, p1, p2, threshold = 0.01):
"""
Returns true of p0 is on the line between p1 and p2
"""
p0, p1, p2 = map(lambda tup : np.array(tup[:2]), [p0, p1, p2])
p1 -= p0
p2 -= p0
return abs((p1[0] / p1[1]) - (p2[0] / p2[1])) < threshold
def intersection(line1, line2):
"""
A "line" should come in the form [(x0, y0), (x1, y1)] for two
points it runs through
"""
p0, p1, p2, p3 = map(
lambda tup : np.array(tup[:2]),
[line1[0], line1[1], line2[0], line2[1]]
)
p1, p2, p3 = map(lambda x : x - p0, [p1, p2, p3])
transform = np.zeros((2, 2))
transform[:,0], transform[:,1] = p1, p2
if np.linalg.det(transform) == 0: return
inv = np.linalg.inv(transform)
new_p3 = np.dot(inv, p3.reshape((2, 1)))
#Where does line connecting (0, 1) to new_p3 hit x axis
x_intercept = new_p3[0] / (1 - new_p3[1])
result = np.dot(transform, [[x_intercept], [0]])
result = result.reshape((2,)) + p0
return result
def random_bright_color():
color = random_color()
curr_rgb = color_to_rgb(color)
new_rgb = interpolate(
curr_rgb, np.ones(len(curr_rgb)), 0.5
)
return Color(rgb = new_rgb)
def random_color():
return random.choice(PALETTE)
################################################
def straight_path(start_points, end_points, alpha):
return interpolate(start_points, end_points, alpha)
def path_along_arc(arc_angle, axis = OUT):
"""
If vect is vector from start to end, [vect[:,1], -vect[:,0]] is
perpendicular to vect in the left direction.
"""
if abs(arc_angle) < STRAIGHT_PATH_THRESHOLD:
return straight_path
if np.linalg.norm(axis) == 0:
axis = OUT
unit_axis = axis/np.linalg.norm(axis)
def path(start_points, end_points, alpha):
vects = end_points - start_points
centers = start_points + 0.5*vects
if arc_angle != np.pi:
centers += np.cross(unit_axis, vects/2.0)/np.tan(arc_angle/2)
rot_matrix = rotation_matrix(alpha*arc_angle, unit_axis)
return centers + np.dot(start_points-centers, rot_matrix.T)
return path
def clockwise_path():
return path_along_arc(-np.pi)
def counterclockwise_path():
return path_along_arc(np.pi)
################################################
def to_camel_case(name):
return "".join([
filter(
lambda c : c not in string.punctuation + string.whitespace, part
).capitalize()
for part in name.split("_")
])
def initials(name, sep_values = [" ", "_"]):
return "".join([
(s[0] if s else "")
for s in re.split("|".join(sep_values), name)
])
def camel_case_initials(name):
return filter(lambda c : c.isupper(), name)
################################################
def get_full_raster_image_path(image_file_name):
possible_paths = [
image_file_name,
os.path.join(RASTER_IMAGE_DIR, image_file_name),
os.path.join(RASTER_IMAGE_DIR, image_file_name + ".jpg"),
os.path.join(RASTER_IMAGE_DIR, image_file_name + ".png"),
os.path.join(RASTER_IMAGE_DIR, image_file_name + ".gif"),
]
for path in possible_paths:
if os.path.exists(path):
return path
raise IOError("File %s not Found"%image_file_name)
def drag_pixels(frames):
curr = frames[0]
new_frames = []
for frame in frames:
curr += (curr == 0) * np.array(frame)
new_frames.append(np.array(curr))
return new_frames
def invert_image(image):
arr = np.array(image)
arr = (255 * np.ones(arr.shape)).astype(arr.dtype) - arr
return Image.fromarray(arr)
def stretch_array_to_length(nparray, length):
curr_len = len(nparray)
if curr_len > length:
raise Warning("Trying to stretch array to a length shorter than its own")
indices = np.arange(length)/ float(length)
indices *= curr_len
return nparray[indices.astype('int')]
def make_even(iterable_1, iterable_2):
list_1, list_2 = list(iterable_1), list(iterable_2)
length = max(len(list_1), len(list_2))
return (
[list_1[(n * len(list_1)) / length] for n in xrange(length)],
[list_2[(n * len(list_2)) / length] for n in xrange(length)]
)
def make_even_by_cycling(iterable_1, iterable_2):
length = max(len(iterable_1), len(iterable_2))
cycle1 = it.cycle(iterable_1)
cycle2 = it.cycle(iterable_2)
return (
[cycle1.next() for x in range(length)],
[cycle2.next() for x in range(length)]
)
### Rate Functions ###
def sigmoid(x):
return 1.0/(1 + np.exp(-x))
def smooth(t, inflection = 10.0):
error = sigmoid(-inflection / 2)
return (sigmoid(inflection*(t - 0.5)) - error) / (1 - 2*error)
def rush_into(t):
return 2*smooth(t/2.0)
def rush_from(t):
return 2*smooth(t/2.0+0.5) - 1
def slow_into(t):
return np.sqrt(1-(1-t)*(1-t))
def double_smooth(t):
if t < 0.5:
return 0.5*smooth(2*t)
else:
return 0.5*(1 + smooth(2*t - 1))
def there_and_back(t, inflection = 10.0):
new_t = 2*t if t < 0.5 else 2*(1 - t)
return smooth(new_t, inflection)
def there_and_back_with_pause(t):
if t < 1./3:
return smooth(3*t)
elif t < 2./3:
return 1
else:
return smooth(3 - 3*t)
def running_start(t, pull_factor = -0.5):
return bezier([0, 0, pull_factor, pull_factor, 1, 1, 1])(t)
def not_quite_there(func = smooth, proportion = 0.7):
def result(t):
return proportion*func(t)
return result
def wiggle(t, wiggles = 2):
return there_and_back(t) * np.sin(wiggles*np.pi*t)
def squish_rate_func(func, a = 0.4, b = 0.6):
def result(t):
if a == b:
return a
if t < a:
return func(0)
elif t > b:
return func(1)
else:
return func((t-a)/(b-a))
return result
# Stylistically, should this take parameters (with default values)?
# Ultimately, the functionality is entirely subsumed by squish_rate_func,
# but it may be useful to have a nice name for with nice default params for
# "lingering", different from squish_rate_func's default params
def lingering(t):
return squish_rate_func(lambda t: t, 0, 0.8)(t)
### Functional Functions ###
def composition(func_list):
"""
func_list should contain elements of the form (f, args)
"""
return reduce(
lambda (f1, args1), (f2, args2) : (lambda x : f1(f2(x, *args2), *args1)),
func_list,
lambda x : x
)
def remove_nones(sequence):
return filter(lambda x : x, sequence)
#Matrix operations
def thick_diagonal(dim, thickness = 2):
row_indices = np.arange(dim).repeat(dim).reshape((dim, dim))
col_indices = np.transpose(row_indices)
return (np.abs(row_indices - col_indices)<thickness).astype('uint8')
def rotation_matrix(angle, axis):
"""
Rotation in R^3 about a specified axis of rotation.
"""
about_z = rotation_about_z(angle)
z_to_axis = z_to_vector(axis)
axis_to_z = np.linalg.inv(z_to_axis)
return reduce(np.dot, [z_to_axis, about_z, axis_to_z])
def rotation_about_z(angle):
return [
[np.cos(angle), -np.sin(angle), 0],
[np.sin(angle), np.cos(angle), 0],
[0, 0, 1]
]
def z_to_vector(vector):
"""
Returns some matrix in SO(3) which takes the z-axis to the
(normalized) vector provided as an argument
"""
norm = np.linalg.norm(vector)
if norm == 0:
return np.identity(3)
v = np.array(vector) / norm
phi = np.arccos(v[2])
if any(v[:2]):
#projection of vector to unit circle
axis_proj = v[:2] / np.linalg.norm(v[:2])
theta = np.arccos(axis_proj[0])
if axis_proj[1] < 0:
theta = -theta
else:
theta = 0
phi_down = np.array([
[np.cos(phi), 0, np.sin(phi)],
[0, 1, 0],
[-np.sin(phi), 0, np.cos(phi)]
])
return np.dot(rotation_about_z(theta), phi_down)
def rotate_vector(vector, angle, axis = OUT):
return np.dot(rotation_matrix(angle, axis), vector)
def angle_between(v1, v2):
return np.arccos(np.dot(
v1 / np.linalg.norm(v1),
v2 / np.linalg.norm(v2)
))
def angle_of_vector(vector):
"""
Returns polar coordinate theta when vector is project on xy plane
"""
z = complex(*vector[:2])
if z == 0:
return 0
return np.angle(complex(*vector[:2]))
def angle_between_vectors(v1, v2):
"""
Returns the angle between two 3D vectors.
This angle will always be btw 0 and TAU/2.
"""
l1 = np.linalg.norm(v1)
l2 = np.linalg.norm(v2)
return np.arccos(np.dot(v1,v2)/(l1*l2))
def project_along_vector(point, vector):
matrix = np.identity(3) - np.outer(vector, vector)
return np.dot(point, matrix.T)
def concatenate_lists(*list_of_lists):
return [item for l in list_of_lists for item in l]
# Occasionally convenient in order to write dict.x instead of more laborious
# (and less in keeping with all other attr accesses) dict["x"]
class DictAsObject(object):
def __init__(self, dict):
self.__dict__ = dict
# Just to have a less heavyweight name for this extremely common operation
#
# We may wish to have more fine-grained control over division by zero behavior
# in the future (separate specifiable values for 0/0 and x/0 with x != 0),
# but for now, we just allow the option to handle indeterminate 0/0.
def fdiv(a, b, zero_over_zero_value = None):
if zero_over_zero_value != None:
out = np.full_like(a, zero_over_zero_value)
where = np.logical_or (a != 0, b != 0)
else:
out = None
where = True
return np.true_divide(a, b, out = out, where = where)
def add_extension_if_not_present(file_name, extension):
# This could conceivably be smarter about handling existing differing extensions
if(file_name[-len(extension):] != extension):
return file_name + extension
else:
return file_name
# For debugging purposes
def print_mobject_family(mob, n_tabs = 0):
print "\t"*n_tabs, mob, id(mob)
for submob in mob.submobjects:
print_mobject_family(submob, n_tabs + 1)

View file

@ -1,10 +0,0 @@
__all__ = [
"mobject",
"image_mobject",
"tex_mobject",
]
from mobject import Mobject, Group
from point_cloud_mobject import Point, Mobject1D, Mobject2D, PMobject
from vectorized_mobject import VMobject, VGroup
from image_mobject import ImageMobject

View file

@ -4,9 +4,14 @@ import os
from PIL import Image
from random import random
from helpers import *
from mobject import Mobject
from constants import *
from .mobject import Mobject
from point_cloud_mobject import PMobject
from utils.bezier import interpolate
from utils.color import color_to_int_rgb
from utils.color import interpolate_color
from utils.config_ops import digest_config
from utils.images import get_full_raster_image_path
class ImageMobject(Mobject):
"""

View file

@ -1,13 +1,21 @@
import numpy as np
import operator as op
import itertools as it
import os
import copy
from PIL import Image
from colour import Color
from helpers import *
from constants import *
from container.container import Container
from utils.bezier import interpolate
from utils.color import color_to_rgb, color_gradient
from utils.color import interpolate_color
from utils.iterables import remove_list_redundancies, list_update
from utils.paths import straight_path
from utils.space_ops import rotation_matrix, angle_of_vector
from utils.space_ops import complex_to_R3, R3_to_complex
from container import *
#TODO: Explain array_attrs
@ -87,7 +95,7 @@ class Mobject(Container):
def get_image(self, camera = None):
if camera is None:
from camera import Camera
from camera.camera import Camera
camera = Camera()
camera.capture_mobject(self)
return camera.get_image()
@ -750,6 +758,12 @@ class Mobject(Container):
)
return self
def print_submobject_family(self, n_tabs = 0):
"""For debugging purposes"""
print "\t"*n_tabs, self, id(self)
for submob in self.submobjects:
submob.print_mobject_family(n_tabs + 1)
## Alignment
def align_data(self, mobject):
self.align_submobjects(mobject)

View file

@ -1,5 +1,11 @@
from constants import *
from .mobject import Mobject
from helpers import *
from utils.bezier import interpolate
from utils.color import color_to_rgb, color_to_rgba, rgba_to_color
from utils.color import color_gradient
from utils.color import interpolate_color
from utils.config_ops import digest_config
from utils.iterables import stretch_array_to_length
class PMobject(Mobject):
def init_points(self):

View file

@ -3,9 +3,10 @@ import itertools as it
from PIL import Image
from copy import deepcopy
from mobject import Mobject
from mobject.mobject import Mobject
from utils.iterables import adjacent_pairs
from helpers import *
from constants import *
#TODO, this whole class should be something vectorized.
class Region(Mobject):

View file

@ -1,9 +1,14 @@
from xml.dom import minidom
import itertools as it
import re
import warnings
import string
from constants import *
from vectorized_mobject import VMobject, VGroup
from topics.geometry import Rectangle, Circle
from helpers import *
from utils.bezier import is_closed
from utils.config_ops import digest_config, digest_locals
def string_to_numbers(num_string):
num_string = num_string.replace("-",",-")

View file

@ -1,11 +1,13 @@
from helpers import *
from constants import *
from vectorized_mobject import VMobject, VGroup, VectorizedPoint
from svg_mobject import SVGMobject, VMobjectFromSVGPathstring
from topics.geometry import BackgroundRectangle
from utils.config_ops import digest_config
import collections
import sys
import operator as op
TEX_MOB_SCALE_FACTOR = 0.05

View file

@ -1,8 +1,15 @@
import re
from colour import Color
from constants import *
from .mobject import Mobject
from utils.bezier import bezier, partial_bezier_points
from utils.bezier import interpolate, get_smooth_handle_points, is_closed
from utils.color import color_to_rgb
from utils.color import interpolate_color
from utils.iterables import make_even
from mobject import Mobject
from helpers import *
class VMobject(Mobject):
CONFIG = {

View file

View file

@ -1,9 +1,9 @@
#!/usr/bin/env python
from helpers import *
from constants import *
from mobject.tex_mobject import TexMobject
from mobject import Mobject
from mobject.mobject import Mobject
from mobject.image_mobject import ImageMobject
from mobject.vectorized_mobject import *
@ -19,8 +19,8 @@ from topics.characters import *
from topics.functions import *
from topics.number_line import *
from topics.numerals import *
from scene import Scene
from camera import Camera
from scene.scene import Scene
from camera.camera import Camera
from mobject.svg_mobject import *
from mobject.tex_mobject import *
from topics.three_dimensions import *

View file

@ -1,35 +1,7 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from helpers import *
from mobject.tex_mobject import TexMobject
from mobject import Mobject
from mobject.image_mobject import ImageMobject
from mobject.vectorized_mobject import *
from animation.animation import Animation
from animation.transform import *
from animation.simple_animations import *
from animation.compositions import *
from animation.continual_animation import *
from animation.playground import *
from topics.geometry import *
from topics.characters import *
from topics.functions import *
from topics.number_line import *
from topics.numerals import *
#from topics.combinatorics import *
from scene import Scene
from scene.zoomed_scene import *
from camera import Camera
from mobject.svg_mobject import *
from mobject.tex_mobject import *
from topics.three_dimensions import *
from topics.light import *
from topics.objects import *
from topics.common_scenes import *
from big_ol_pile_of_manim_imports import *
import types
import functools
@ -260,7 +232,7 @@ class ThreeDSpotlight(VGroup):
fill_opacity = self.ambient_light.opacity_function(a1*distance),
stroke_width = 0
))
class ContinualThreeDLightConeUpdate(ContinualAnimation):
def update(self, dt):
self.mobject.update()

View file

@ -11,7 +11,7 @@ from mobject import *
from constants import *
from mobject.region import *
import displayer as disp
from scene import Scene, GraphScene
from scene.scene import Scene, GraphScene
from scene.graphs import *
from moser_main import EulersFormula
from script_wrapper import command_line_create_scene

View file

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
from helpers import *
from constants import *
import scipy
from big_ol_pile_of_manim_imports import *

View file

@ -7,7 +7,7 @@ from hilbert.curves import \
SnakeCurve
from helpers import *
from constants import *

View file

@ -5,10 +5,10 @@ import sys
import os.path
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
from helpers import *
from constants import *
from mobject.tex_mobject import TexMobject
from mobject import Mobject, Group
from mobject.mobject import Mobject, Group
from mobject.image_mobject import ImageMobject
from mobject.vectorized_mobject import *
@ -29,10 +29,10 @@ from topics.three_dimensions import *
from topics.objects import *
from topics.probability import *
from topics.complex_numbers import *
from scene import Scene
from scene.scene import Scene
from scene.reconfigurable_scene import ReconfigurableScene
from scene.zoomed_scene import *
from camera import Camera
from camera.camera import Camera
from mobject.svg_mobject import *
from mobject.tex_mobject import *

View file

@ -10,7 +10,7 @@ from animation import *
from mobject import *
from constants import *
from mobject.region import *
from scene import Scene, SceneFromVideo
from scene.scene import Scene, SceneFromVideo
from script_wrapper import command_line_create_scene
MOVIE_PREFIX = "counting_in_binary/"

View file

@ -3,9 +3,9 @@ import itertools as it
from copy import deepcopy
import sys
from helpers import *
from constants import *
from scene import Scene
from scene.scene import Scene
from geometry import Polygon
from mobject.region import region_from_polygon_vertices, region_from_line_boundary

View file

@ -1,9 +1,9 @@
import numpy as np
import itertools as it
from mobject import Mobject, Mobject1D, Mobject2D, Mobject
from mobject.mobject import Mobject, Mobject1D, Mobject2D, Mobject
from geometry import Line
from helpers import *
from constants import *
class Stars(Mobject1D):
CONFIG = {

View file

@ -2,7 +2,7 @@
import scipy
from big_ol_pile_of_manim_imports import *
from old_projects.fourier import *
FREQUENCY_COLOR = RED
USE_ALMOST_FOURIER_BY_DEFAULT = False

View file

@ -1,5 +1,3 @@
__all__ = [
"scene"
]
from scene import Scene

View file

@ -1,7 +1,7 @@
from helpers import *
from constants import *
from camera import MovingCamera
from scene import Scene
from camera.camera import MovingCamera
from .scene import Scene
from topics.geometry import ScreenRectangle
class MovingCameraScene(Scene):

View file

@ -1,10 +1,10 @@
import numpy as np
from scene import Scene
from .scene import Scene
from animation.transform import Transform
from mobject import Mobject
from mobject.mobject import Mobject
from helpers import *
from constants import *
class ReconfigurableScene(Scene):
CONFIG = {

View file

@ -10,16 +10,27 @@ import copy
from tqdm import tqdm as ProgressDisplay
import inspect
import subprocess as sp
import random
from helpers import *
from constants import *
from camera import Camera
from camera.camera import Camera
from tk_scene import TkSceneRoot
from mobject import Mobject, VMobject
from animation import Animation
from mobject.mobject import Mobject
from mobject.vectorized_mobject import VMobject
from animation.animation import Animation
from animation.transform import MoveToTarget
from animation.continual_animation import ContinualAnimation
from container import *
from utils.iterables import list_update
from container.container import Container
def add_extension_if_not_present(file_name, extension):
# This could conceivably be smarter about handling existing differing extensions
if(file_name[-len(extension):] != extension):
return file_name + extension
else:
return file_name
class Scene(Container):
CONFIG = {

View file

@ -3,7 +3,7 @@ import cv2
import itertools as it
from tqdm import tqdm as show_progress
from scene import Scene
from .scene import Scene
class SceneFromVideo(Scene):

View file

@ -1,12 +1,13 @@
import numpy as np
from scene import Scene
from .scene import Scene
from animation.transform import FadeIn
from mobject import Mobject
from mobject.mobject import Mobject
from topics.geometry import Rectangle
from camera import MovingCamera, Camera
from camera.camera import Camera
from camera.camera import MovingCamera
from helpers import *
from constants import *
class ZoomedScene(Scene):
"""

View file

@ -1,9 +1,9 @@
import numpy as np
import itertools as it
from helpers import *
from scene import Scene
from animation import Animation
from constants import *
from scene.scene import Scene
from animation.animation import Animation
from mobject.tex_mobject import TexMobject
class RearrangeEquation(Scene):

View file

@ -1,6 +1,10 @@
from helpers import *
import random
import numpy as np
import itertools as it
from mobject import Mobject
from constants import *
from mobject.mobject import Mobject, Group
from mobject.svg_mobject import SVGMobject
from mobject.vectorized_mobject import VMobject, VGroup
from mobject.tex_mobject import TextMobject, TexMobject
@ -8,11 +12,14 @@ from mobject.tex_mobject import TextMobject, TexMobject
from topics.objects import Bubble, ThoughtBubble, SpeechBubble
from topics.geometry import ScreenRectangle
from animation import Animation
from animation.transform import *
from animation.animation import Animation
from animation.transform import Transform, ApplyMethod, MoveToTarget
from animation.transform import ReplacementTransform, FadeOut, FadeIn
from animation.simple_animations import Write, ShowCreation
from animation.compositions import AnimationGroup
from scene import Scene
from scene.scene import Scene
from utils.config_ops import digest_config
from utils.rate_functions import there_and_back, squish_rate_func
PI_CREATURE_DIR = os.path.join(MEDIA_DIR, "designs", "PiCreature")

View file

@ -1,9 +1,10 @@
from helpers import *
from constants import *
from mobject.vectorized_mobject import VMobject
from mobject.tex_mobject import TexMobject
from scene import Scene
from scene.scene import Scene
from utils.simple_functions import choose
DEFAULT_COUNT_NUM_OFFSET = (FRAME_X_RADIUS - 1, FRAME_Y_RADIUS - 1, 0)

View file

@ -1,8 +1,8 @@
from helpers import *
from constants import *
from scene.scene import Scene
from animation import Animation
from animation.animation import Animation
from animation.simple_animations import Write, DrawBorderThenFill
from animation.compositions import LaggedStart
from animation.transform import FadeIn, FadeOut, ApplyMethod

View file

@ -1,14 +1,18 @@
from helpers import *
from constants import *
from mobject import VGroup
from mobject.vectorized_mobject import VGroup
from mobject.tex_mobject import TexMobject, TextMobject
from number_line import NumberPlane
from animation import Animation
from animation.animation import Animation
from animation.transform import ApplyPointwiseFunction, MoveToTarget
from animation.simple_animations import Homotopy, ShowCreation, \
SmoothedVectorizedHomotopy
from scene import Scene
from scene.scene import Scene
from utils.config_ops import instantiate
from utils.config_ops import digest_config
from utils.paths import path_along_arc
from utils.space_ops import complex_to_R3, R3_to_complex
class ComplexTransformationScene(Scene):

View file

@ -1,14 +1,14 @@
from helpers import *
from constants import *
from mobject.tex_mobject import TexMobject, TextMobject
from mobject import Mobject
from mobject.mobject import Mobject
from mobject.vectorized_mobject import VMobject, VGroup
from animation.transform import Transform, FadeIn, MoveToTarget
from animation.simple_animations import ShowCreation
from topics.geometry import Arrow, Circle, Dot
# from topics.fractals import *
from scene import Scene
from scene.scene import Scene
class CountingScene(Scene):

View file

@ -1,12 +1,17 @@
# from mobject import Mobject, Point, Mobject1D
# from mobject.mobject import Mobject, Point, Mobject1D
from mobject.vectorized_mobject import VMobject, VGroup, VectorizedPoint
from scene import Scene
from scene.scene import Scene
from animation.transform import Transform
from animation.simple_animations import ShowCreation
from topics.geometry import Line, Polygon, RegularPolygon, Square, Circle
from characters import PiCreature, Randolph, get_all_pi_creature_modes
from utils.bezier import interpolate
from utils.color import color_gradient
from utils.config_ops import digest_config
from utils.space_ops import rotation_matrix, rotate_vector, compass_directions, center_of_mass
from constants import *
from helpers import *
def rotate(points, angle = np.pi, axis = OUT):
if axis is None:

View file

@ -1,8 +1,9 @@
from scipy import integrate
from mobject.vectorized_mobject import VMobject
from utils.config_ops import digest_config
from helpers import *
from constants import *
class ParametricFunction(VMobject):
CONFIG = {

View file

@ -1,7 +1,14 @@
from helpers import *
from constants import *
from mobject import Mobject
import itertools as it
import numpy as np
from mobject.mobject import Mobject
from mobject.vectorized_mobject import VMobject, VGroup
from utils.bezier import interpolate
from utils.config_ops import digest_config, digest_locals
from utils.paths import path_along_arc
from utils.space_ops import rotate_vector, angle_of_vector, compass_directions, center_of_mass
class Arc(VMobject):
CONFIG = {

View file

@ -1,6 +1,6 @@
from helpers import *
from constants import *
from scene import Scene
from scene.scene import Scene
# from topics.geometry import
from mobject.tex_mobject import TexMobject, TextMobject
from mobject.vectorized_mobject import VGroup, VectorizedPoint
@ -9,6 +9,10 @@ from animation.transform import Transform
from topics.number_line import NumberLine
from topics.functions import ParametricFunction
from topics.geometry import Rectangle, DashedLine, Line
from utils.bezier import interpolate
from utils.color import invert_color
from utils.color import color_gradient
from utils.space_ops import angle_of_vector
class GraphScene(Scene):
CONFIG = {

View file

@ -3,9 +3,11 @@ import numpy as np
import operator as op
from random import random
from helpers import *
from constants import *
from scene import Scene
from scene.scene import Scene
from utils.rate_functions import there_and_back
from utils.space_ops import center_of_mass
class Graph():

View file

@ -1,7 +1,7 @@
from helpers import *
from constants import *
from mobject.tex_mobject import TexMobject
from mobject import Mobject
from mobject.mobject import Mobject
from mobject.vectorized_mobject import *
from animation.animation import Animation
@ -13,14 +13,18 @@ from animation.continual_animation import *
from animation.playground import *
from topics.geometry import *
from topics.functions import *
from scene import Scene
from camera import Camera
from scene.scene import Scene
from camera.camera import Camera
from mobject.svg_mobject import *
from topics.three_dimensions import *
from scipy.spatial import ConvexHull
from traceback import *
from utils.space_ops import rotation_matrix, z_to_vector
from utils.space_ops import rotate_vector, angle_between, angle_between_vectors
from utils.space_ops import project_along_vector
LIGHT_COLOR = YELLOW
SHADOW_COLOR = BLACK

View file

@ -1,7 +1,7 @@
import numpy as np
from scene import Scene
from mobject import Mobject
from scene.scene import Scene
from mobject.mobject import Mobject
from mobject.vectorized_mobject import VMobject, VGroup
from mobject.tex_mobject import TexMobject, TextMobject
from animation.transform import ApplyPointwiseFunction, Transform, \
@ -10,7 +10,7 @@ from animation.simple_animations import ShowCreation, Write
from topics.number_line import NumberPlane, Axes
from topics.geometry import Vector, Line, Circle, Arrow, Dot, BackgroundRectangle
from helpers import *
from constants import *
VECTOR_LABEL_SCALE_FACTOR = 0.8

View file

@ -1,11 +1,13 @@
from helpers import *
from constants import *
from mobject import Mobject1D
from mobject.vectorized_mobject import VMobject, VGroup
from mobject.tex_mobject import TexMobject
from topics.geometry import Line, Arrow
from topics.functions import ParametricFunction
from scene import Scene
from scene.scene import Scene
from utils.bezier import interpolate
from utils.config_ops import digest_config
from utils.space_ops import angle_of_vector
class NumberLine(VMobject):
CONFIG = {

View file

@ -1,11 +1,13 @@
from mobject.vectorized_mobject import VMobject, VGroup, VectorizedPoint
from mobject.tex_mobject import TexMobject
from animation import Animation
from animation.animation import Animation
from animation.continual_animation import ContinualAnimation
from topics.geometry import BackgroundRectangle
from scene import Scene
from helpers import *
from scene.scene import Scene
from constants import *
from utils.bezier import interpolate
from utils.config_ops import digest_config
class DecimalNumber(VMobject):
CONFIG = {

View file

@ -1,11 +1,11 @@
from helpers import *
from constants import *
from mobject import Mobject
from mobject.mobject import Mobject
from mobject.vectorized_mobject import VGroup, VMobject, VectorizedPoint
from mobject.svg_mobject import SVGMobject
from mobject.tex_mobject import TextMobject, TexMobject, Brace
from animation import Animation
from animation.animation import Animation
from animation.simple_animations import Rotating
from animation.compositions import LaggedStart, AnimationGroup
from animation.transform import ApplyMethod, FadeIn, GrowFromCenter
@ -13,6 +13,9 @@ from animation.transform import ApplyMethod, FadeIn, GrowFromCenter
from topics.geometry import Circle, Line, Rectangle, Square, \
Arc, Polygon, SurroundingRectangle
from topics.three_dimensions import Cube
from utils.config_ops import digest_config, digest_locals
from utils.space_ops import rotate_vector, angle_of_vector
from utils.space_ops import complex_to_R3, R3_to_complex
class Lightbulb(SVGMobject):
CONFIG = {

View file

@ -1,18 +1,23 @@
from helpers import *
from constants import *
from scene import Scene
from scene.scene import Scene
from animation.animation import Animation
from animation.transform import Transform, MoveToTarget
from animation.simple_animations import UpdateFromFunc
from mobject import Mobject
from mobject.mobject import Mobject
from mobject.vectorized_mobject import VGroup, VMobject, VectorizedPoint
from mobject.svg_mobject import SVGMobject
from mobject.tex_mobject import TextMobject, TexMobject, Brace
from topics.geometry import Circle, Line, Rectangle, Square, Arc, Polygon
from utils.bezier import interpolate
from utils.color import color_gradient, average_color
from utils.config_ops import digest_config
from utils.iterables import tuplify
from utils.space_ops import center_of_mass
EPSILON = 0.0001
class SampleSpaceScene(Scene):

View file

@ -1,13 +1,18 @@
from helpers import *
from constants import *
from mobject.vectorized_mobject import VGroup, VMobject, VectorizedPoint
from topics.geometry import Square, Line
from scene import Scene
from camera import Camera
from scene.scene import Scene
from camera.camera import Camera
from animation.continual_animation import AmbientMovement
from animation.transform import ApplyMethod
from utils.bezier import interpolate
from utils.iterables import list_update
from utils.space_ops import rotation_matrix, rotation_about_z, z_to_vector
class CameraWithPerspective(Camera):
CONFIG = {
"camera_distance" : 20,

View file

@ -1,10 +1,10 @@
import numpy as np
from scene import Scene
from mobject import Mobject
from scene.scene import Scene
from mobject.mobject import Mobject
from mobject.vectorized_mobject import VMobject, VGroup
from mobject.tex_mobject import TexMobject, TextMobject
from animation import Animation
from animation.animation import Animation
from animation.transform import ApplyPointwiseFunction, Transform, \
ApplyMethod, FadeOut, ApplyFunction
from animation.simple_animations import ShowCreation, Write
@ -12,8 +12,10 @@ from topics.number_line import NumberPlane, Axes
from topics.geometry import Vector, Line, Circle, Arrow, Dot, \
BackgroundRectangle, Square
from helpers import *
from constants import *
from topics.matrix import Matrix, VECTOR_LABEL_SCALE_FACTOR, vector_coordinate_label
from utils.rate_functions import rush_into, rush_from
from utils.space_ops import angle_of_vector
X_COLOR = GREEN_C

0
utils/__init__.py Normal file
View file

126
utils/bezier.py Normal file
View file

@ -0,0 +1,126 @@
import numpy as np
from scipy import linalg
from utils.simple_functions import choose
CLOSED_THRESHOLD = 0.0
def bezier(points):
n = len(points) - 1
return lambda t : sum([
((1-t)**(n-k))*(t**k)*choose(n, k)*point
for k, point in enumerate(points)
])
def partial_bezier_points(points, a, b):
"""
Given an array of points which define
a bezier curve, and two numbers 0<=a<b<=1,
return an array of the same size, which
describes the portion of the original bezier
curve on the interval [a, b].
This algorithm is pretty nifty, and pretty dense.
"""
a_to_1 = np.array([
bezier(points[i:])(a)
for i in range(len(points))
])
return np.array([
bezier(a_to_1[:i+1])((b-a)/(1.-a))
for i in range(len(points))
])
# Linear interpolation variants
def interpolate(start, end, alpha):
return (1-alpha)*start + alpha*end
def mid(start, end):
return (start + end)/2.0
def inverse_interpolate(start, end, value):
return np.true_divide(value - start, end - start)
def match_interpolate(new_start, new_end, old_start, old_end, old_value):
return interpolate(
new_start, new_end,
inverse_interpolate(old_start, old_end, old_value)
)
def clamp(lower, upper, val):
if val < lower:
return lower
elif val > upper:
return upper
return val
# Figuring out which bezier curves most smoothly connect a sequence of points
def get_smooth_handle_points(points):
points = np.array(points)
num_handles = len(points) - 1
dim = points.shape[1]
if num_handles < 1:
return np.zeros((0, dim)), np.zeros((0, dim))
#Must solve 2*num_handles equations to get the handles.
#l and u are the number of lower an upper diagonal rows
#in the matrix to solve.
l, u = 2, 1
#diag is a representation of the matrix in diagonal form
#See https://www.particleincell.com/2012/bezier-splines/
#for how to arive at these equations
diag = np.zeros((l+u+1, 2*num_handles))
diag[0,1::2] = -1
diag[0,2::2] = 1
diag[1,0::2] = 2
diag[1,1::2] = 1
diag[2,1:-2:2] = -2
diag[3,0:-3:2] = 1
#last
diag[2,-2] = -1
diag[1,-1] = 2
#This is the b as in Ax = b, where we are solving for x,
#and A is represented using diag. However, think of entries
#to x and b as being points in space, not numbers
b = np.zeros((2*num_handles, dim))
b[1::2] = 2*points[1:]
b[0] = points[0]
b[-1] = points[-1]
solve_func = lambda b : linalg.solve_banded(
(l, u), diag, b
)
if is_closed(points):
#Get equations to relate first and last points
matrix = diag_to_matrix((l, u), diag)
#last row handles second derivative
matrix[-1, [0, 1, -2, -1]] = [2, -1, 1, -2]
#first row handles first derivative
matrix[0,:] = np.zeros(matrix.shape[1])
matrix[0,[0, -1]] = [1, 1]
b[0] = 2*points[0]
b[-1] = np.zeros(dim)
solve_func = lambda b : linalg.solve(matrix, b)
handle_pairs = np.zeros((2*num_handles, dim))
for i in range(dim):
handle_pairs[:,i] = solve_func(b[:,i])
return handle_pairs[0::2], handle_pairs[1::2]
def diag_to_matrix(l_and_u, diag):
"""
Converts array whose rows represent diagonal
entries of a matrix into the matrix itself.
See scipy.linalg.solve_banded
"""
l, u = l_and_u
dim = diag.shape[1]
matrix = np.zeros((dim, dim))
for i in range(l+u+1):
np.fill_diagonal(
matrix[max(0,i-u):,max(0,u-i):],
diag[i,max(0,u-i):]
)
return matrix
def is_closed(points):
return np.linalg.norm(points[0] - points[-1]) < CLOSED_THRESHOLD

67
utils/color.py Normal file
View file

@ -0,0 +1,67 @@
from colour import Color
import numpy as np
import random
from utils.bezier import interpolate
def color_to_rgb(color):
return np.array(Color(color).get_rgb())
def color_to_rgba(color, alpha = 1):
return np.append(color_to_rgb(color), [alpha])
def rgb_to_color(rgb):
try:
return Color(rgb = rgb)
except:
return Color(WHITE)
def rgba_to_color(rgba):
return rgb_to_color(rgba[:3])
def rgb_to_hex(rgb):
return "#" + "".join('%02x'%int(255*x) for x in rgb)
def invert_color(color):
return rgb_to_color(1.0 - color_to_rgb(color))
def color_to_int_rgb(color):
return (255*color_to_rgb(color)).astype('uint8')
def color_to_int_rgba(color, alpha = 255):
return np.append(color_to_int_rgb(color), alpha)
def color_gradient(reference_colors, length_of_output):
if length_of_output == 0:
return reference_colors[0]
rgbs = map(color_to_rgb, reference_colors)
alphas = np.linspace(0, (len(rgbs) - 1), length_of_output)
floors = alphas.astype('int')
alphas_mod1 = alphas % 1
#End edge case
alphas_mod1[-1] = 1
floors[-1] = len(rgbs) - 2
return [
rgb_to_color(interpolate(rgbs[i], rgbs[i+1], alpha))
for i, alpha in zip(floors, alphas_mod1)
]
def interpolate_color(color1, color2, alpha):
rgb = interpolate(color_to_rgb(color1), color_to_rgb(color2), alpha)
return rgb_to_color(rgb)
def average_color(*colors):
rgbs = np.array(map(color_to_rgb, colors))
mean_rgb = np.apply_along_axis(np.mean, 0, rgbs)
return rgb_to_color(mean_rgb)
def random_bright_color():
color = random_color()
curr_rgb = color_to_rgb(color)
new_rgb = interpolate(
curr_rgb, np.ones(len(curr_rgb)), 0.5
)
return Color(rgb = new_rgb)
def random_color():
return random.choice(PALETTE)

115
utils/config_ops.py Normal file
View file

@ -0,0 +1,115 @@
import inspect
import operator as op
def instantiate(obj):
"""
Useful so that classes or instance of those classes can be
included in configuration, which can prevent defaults from
getting created during compilation/importing
"""
return obj() if isinstance(obj, type) else obj
def get_all_descendent_classes(Class):
awaiting_review = [Class]
result = []
while awaiting_review:
Child = awaiting_review.pop()
awaiting_review += Child.__subclasses__()
result.append(Child)
return result
def filtered_locals(caller_locals):
result = caller_locals.copy()
ignored_local_args = ["self", "kwargs"]
for arg in ignored_local_args:
result.pop(arg, caller_locals)
return result
def digest_config(obj, kwargs, caller_locals = {}):
"""
Sets init args and CONFIG values as local variables
The purpose of this function is to ensure that all
configuration of any object is inheritable, able to
be easily passed into instantiation, and is attached
as an attribute of the object.
"""
# Assemble list of CONFIGs from all super classes
classes_in_hierarchy = [obj.__class__]
static_configs = []
while len(classes_in_hierarchy) > 0:
Class = classes_in_hierarchy.pop()
classes_in_hierarchy += Class.__bases__
if hasattr(Class, "CONFIG"):
static_configs.append(Class.CONFIG)
#Order matters a lot here, first dicts have higher priority
caller_locals = filtered_locals(caller_locals)
all_dicts = [kwargs, caller_locals, obj.__dict__]
all_dicts += static_configs
all_new_dicts = [kwargs, caller_locals] + static_configs
obj.__dict__ = merge_config(all_dicts)
#Keep track of the configuration of objects upon
#instantiation
obj.initial_config = merge_config(all_new_dicts)
def merge_config(all_dicts):
all_config = reduce(op.add, [d.items() for d in all_dicts])
config = dict()
for c in all_config:
key, value = c
if not key in config:
config[key] = value
else:
#When two dictionaries have the same key, they are merged.
if isinstance(value, dict) and isinstance(config[key], dict):
config[key] = merge_config([config[key], value])
return config
def soft_dict_update(d1, d2):
"""
Adds key values pairs of d2 to d1 only when d1 doesn't
already have that key
"""
for key, value in d2.items():
if key not in d1:
d1[key] = value
def digest_locals(obj, keys = None):
caller_locals = filtered_locals(
inspect.currentframe().f_back.f_locals
)
if keys is None:
keys = caller_locals.keys()
for key in keys:
setattr(obj, key, caller_locals[key])
# Occasionally convenient in order to write dict.x instead of more laborious
# (and less in keeping with all other attr accesses) dict["x"]
class DictAsObject(object):
def __init__(self, dict):
self.__dict__ = dict

30
utils/images.py Normal file
View file

@ -0,0 +1,30 @@
import numpy as np
from PIL import Image
from constants import RASTER_IMAGE_DIR
import os
def get_full_raster_image_path(image_file_name):
possible_paths = [
image_file_name,
os.path.join(RASTER_IMAGE_DIR, image_file_name),
os.path.join(RASTER_IMAGE_DIR, image_file_name + ".jpg"),
os.path.join(RASTER_IMAGE_DIR, image_file_name + ".png"),
os.path.join(RASTER_IMAGE_DIR, image_file_name + ".gif"),
]
for path in possible_paths:
if os.path.exists(path):
return path
raise IOError("File %s not Found"%image_file_name)
def drag_pixels(frames):
curr = frames[0]
new_frames = []
for frame in frames:
curr += (curr == 0) * np.array(frame)
new_frames.append(np.array(curr))
return new_frames
def invert_image(image):
arr = np.array(image)
arr = (255 * np.ones(arr.shape)).astype(arr.dtype) - arr
return Image.fromarray(arr)

113
utils/iterables.py Normal file
View file

@ -0,0 +1,113 @@
import numpy as np
import itertools as it
def remove_list_redundancies(l):
"""
Used instead of list(set(l)) to maintain order
Keeps the last occurance of each element
"""
reversed_result = []
used = set()
for x in reversed(l):
if not x in used:
reversed_result.append(x)
used.add(x)
reversed_result.reverse()
return reversed_result
def list_update(l1, l2):
"""
Used instead of list(set(l1).update(l2)) to maintain order,
making sure duplicates are removed from l1, not l2.
"""
return filter(lambda e : e not in l2, l1) + list(l2)
def list_difference_update(l1, l2):
return filter(lambda e : e not in l2, l1)
def all_elements_are_instances(iterable, Class):
return all(map(lambda e : isinstance(e, Class), iterable))
def adjacent_pairs(objects):
return zip(objects, list(objects[1:])+[objects[0]])
def batch_by_property(items, property_func):
"""
Takes in a list, and returns a list of tuples, (batch, prop)
such that all items in a batch have the same output when
put into property_func, and such that chaining all these
batches together would give the original list.
"""
batch_prop_pairs = []
def add_batch_prop_pair(batch):
if len(batch) > 0:
batch_prop_pairs.append(
(batch, property_func(batch[0]))
)
curr_batch = []
curr_prop = None
for item in items:
prop = property_func(item)
if prop != curr_prop:
add_batch_prop_pair(curr_batch)
curr_prop = prop
curr_batch = [item]
else:
curr_batch.append(item)
add_batch_prop_pair(curr_batch)
return batch_prop_pairs
def tuplify(obj):
if isinstance(obj, str):
return (obj,)
try:
return tuple(obj)
except:
return (obj,)
def stretch_array_to_length(nparray, length):
curr_len = len(nparray)
if curr_len > length:
raise Warning("Trying to stretch array to a length shorter than its own")
indices = np.arange(length)/ float(length)
indices *= curr_len
return nparray[indices.astype('int')]
def make_even(iterable_1, iterable_2):
list_1, list_2 = list(iterable_1), list(iterable_2)
length = max(len(list_1), len(list_2))
return (
[list_1[(n * len(list_1)) / length] for n in xrange(length)],
[list_2[(n * len(list_2)) / length] for n in xrange(length)]
)
def make_even_by_cycling(iterable_1, iterable_2):
length = max(len(iterable_1), len(iterable_2))
cycle1 = it.cycle(iterable_1)
cycle2 = it.cycle(iterable_2)
return (
[cycle1.next() for x in range(length)],
[cycle2.next() for x in range(length)]
)
def composition(func_list):
"""
func_list should contain elements of the form (f, args)
"""
return reduce(
lambda (f1, args1), (f2, args2) : (lambda x : f1(f2(x, *args2), *args1)),
func_list,
lambda x : x
)
def remove_nones(sequence):
return filter(lambda x : x, sequence)
## Note this is redundant with it.chain
def concatenate_lists(*list_of_lists):
return [item for l in list_of_lists for item in l]

40
utils/paths.py Normal file
View file

@ -0,0 +1,40 @@
import numpy as np
from utils.bezier import interpolate
from utils.space_ops import rotation_matrix
from constants import OUT
STRAIGHT_PATH_THRESHOLD = 0.01
def straight_path(start_points, end_points, alpha):
"""
Same function as interpolate, but renamed to reflect
intent of being used to determine how a set of points move
to another set. For instance, it should be a specific case
of path_along_arc
"""
return interpolate(start_points, end_points, alpha)
def path_along_arc(arc_angle, axis = OUT):
"""
If vect is vector from start to end, [vect[:,1], -vect[:,0]] is
perpendicular to vect in the left direction.
"""
if abs(arc_angle) < STRAIGHT_PATH_THRESHOLD:
return straight_path
if np.linalg.norm(axis) == 0:
axis = OUT
unit_axis = axis/np.linalg.norm(axis)
def path(start_points, end_points, alpha):
vects = end_points - start_points
centers = start_points + 0.5*vects
if arc_angle != np.pi:
centers += np.cross(unit_axis, vects/2.0)/np.tan(arc_angle/2)
rot_matrix = rotation_matrix(alpha*arc_angle, unit_axis)
return centers + np.dot(start_points-centers, rot_matrix.T)
return path
def clockwise_path():
return path_along_arc(-np.pi)
def counterclockwise_path():
return path_along_arc(np.pi)

68
utils/rate_functions.py Normal file
View file

@ -0,0 +1,68 @@
import numpy as np
from utils.simple_functions import sigmoid
from utils.bezier import bezier
def smooth(t, inflection = 10.0):
error = sigmoid(-inflection / 2)
return (sigmoid(inflection*(t - 0.5)) - error) / (1 - 2*error)
def rush_into(t):
return 2*smooth(t/2.0)
def rush_from(t):
return 2*smooth(t/2.0+0.5) - 1
def slow_into(t):
return np.sqrt(1-(1-t)*(1-t))
def double_smooth(t):
if t < 0.5:
return 0.5*smooth(2*t)
else:
return 0.5*(1 + smooth(2*t - 1))
def there_and_back(t, inflection = 10.0):
new_t = 2*t if t < 0.5 else 2*(1 - t)
return smooth(new_t, inflection)
def there_and_back_with_pause(t):
if t < 1./3:
return smooth(3*t)
elif t < 2./3:
return 1
else:
return smooth(3 - 3*t)
def running_start(t, pull_factor = -0.5):
return bezier([0, 0, pull_factor, pull_factor, 1, 1, 1])(t)
def not_quite_there(func = smooth, proportion = 0.7):
def result(t):
return proportion*func(t)
return result
def wiggle(t, wiggles = 2):
return there_and_back(t) * np.sin(wiggles*np.pi*t)
def squish_rate_func(func, a = 0.4, b = 0.6):
def result(t):
if a == b:
return a
if t < a:
return func(0)
elif t > b:
return func(1)
else:
return func((t-a)/(b-a))
return result
# Stylistically, should this take parameters (with default values)?
# Ultimately, the functionality is entirely subsumed by squish_rate_func,
# but it may be useful to have a nice name for with nice default params for
# "lingering", different from squish_rate_func's default params
def lingering(t):
return squish_rate_func(lambda t: t, 0, 0.8)(t)

27
utils/simple_functions.py Normal file
View file

@ -0,0 +1,27 @@
import operator as op
import numpy as np
def sigmoid(x):
return 1.0/(1 + np.exp(-x))
def choose(n, r):
if n < r: return 0
if r == 0: return 1
denom = reduce(op.mul, xrange(1, r+1), 1)
numer = reduce(op.mul, xrange(n, n-r, -1), 1)
return numer//denom
# Just to have a less heavyweight name for this extremely common operation
#
# We may wish to have more fine-grained control over division by zero behavior
# in the future (separate specifiable values for 0/0 and x/0 with x != 0),
# but for now, we just allow the option to handle indeterminate 0/0.
def fdiv(a, b, zero_over_zero_value = None):
if zero_over_zero_value != None:
out = np.full_like(a, zero_over_zero_value)
where = np.logical_or (a != 0, b != 0)
else:
out = None
where = True
return np.true_divide(a, b, out = out, where = where)

26
utils/sounds.py Normal file
View file

@ -0,0 +1,26 @@
import os
def play_chord(*nums):
commands = [
"play",
"-n",
"-c1",
"--no-show-progress",
"synth",
] + [
"sin %-"+str(num)
for num in nums
] + [
"fade h 0.5 1 0.5",
"> /dev/null"
]
try:
os.system(" ".join(commands))
except:
pass
def play_error_sound():
play_chord(11, 8, 6, 1)
def play_finish_sound():
play_chord(12, 9, 5, 2)

110
utils/space_ops.py Normal file
View file

@ -0,0 +1,110 @@
import numpy as np
from constants import RIGHT, OUT
#Matrix operations
def thick_diagonal(dim, thickness = 2):
row_indices = np.arange(dim).repeat(dim).reshape((dim, dim))
col_indices = np.transpose(row_indices)
return (np.abs(row_indices - col_indices)<thickness).astype('uint8')
def rotation_matrix(angle, axis):
"""
Rotation in R^3 about a specified axis of rotation.
"""
about_z = rotation_about_z(angle)
z_to_axis = z_to_vector(axis)
axis_to_z = np.linalg.inv(z_to_axis)
return reduce(np.dot, [z_to_axis, about_z, axis_to_z])
def rotation_about_z(angle):
return [
[np.cos(angle), -np.sin(angle), 0],
[np.sin(angle), np.cos(angle), 0],
[0, 0, 1]
]
def z_to_vector(vector):
"""
Returns some matrix in SO(3) which takes the z-axis to the
(normalized) vector provided as an argument
"""
norm = np.linalg.norm(vector)
if norm == 0:
return np.identity(3)
v = np.array(vector) / norm
phi = np.arccos(v[2])
if any(v[:2]):
#projection of vector to unit circle
axis_proj = v[:2] / np.linalg.norm(v[:2])
theta = np.arccos(axis_proj[0])
if axis_proj[1] < 0:
theta = -theta
else:
theta = 0
phi_down = np.array([
[np.cos(phi), 0, np.sin(phi)],
[0, 1, 0],
[-np.sin(phi), 0, np.cos(phi)]
])
return np.dot(rotation_about_z(theta), phi_down)
def rotate_vector(vector, angle, axis = OUT):
return np.dot(rotation_matrix(angle, axis), vector)
def angle_between(v1, v2):
return np.arccos(np.dot(
v1 / np.linalg.norm(v1),
v2 / np.linalg.norm(v2)
))
def angle_of_vector(vector):
"""
Returns polar coordinate theta when vector is project on xy plane
"""
z = complex(*vector[:2])
if z == 0:
return 0
return np.angle(complex(*vector[:2]))
def angle_between_vectors(v1, v2):
"""
Returns the angle between two 3D vectors.
This angle will always be btw 0 and TAU/2.
"""
l1 = np.linalg.norm(v1)
l2 = np.linalg.norm(v2)
return np.arccos(np.dot(v1,v2)/(l1*l2))
def project_along_vector(point, vector):
matrix = np.identity(3) - np.outer(vector, vector)
return np.dot(point, matrix.T)
###
def compass_directions(n = 4, start_vect = RIGHT):
angle = 2*np.pi/n
return np.array([
rotate_vector(start_vect, k*angle)
for k in range(n)
])
def complex_to_R3(complex_num):
return np.array((complex_num.real, complex_num.imag, 0))
def R3_to_complex(point):
return complex(*point[:2])
def center_of_mass(points):
points = [np.array(point).astype("float") for point in points]
return sum(points) / len(points)
# TODO: It feels like this should live elsewhere

19
utils/strings.py Normal file
View file

@ -0,0 +1,19 @@
import re
import string
def to_camel_case(name):
return "".join([
filter(
lambda c : c not in string.punctuation + string.whitespace, part
).capitalize()
for part in name.split("_")
])
def initials(name, sep_values = [" ", "_"]):
return "".join([
(s[0] if s else "")
for s in re.split("|".join(sep_values), name)
])
def camel_case_initials(name):
return filter(lambda c : c.isupper(), name)