2015-06-10 22:00:35 -07:00
|
|
|
import numpy as np
|
2015-10-01 17:20:17 -07:00
|
|
|
import operator as op
|
2015-06-10 22:00:35 -07:00
|
|
|
import os
|
|
|
|
from PIL import Image
|
|
|
|
from copy import deepcopy
|
|
|
|
from colour import Color
|
|
|
|
|
2015-10-27 21:00:50 -07:00
|
|
|
from helpers import *
|
2015-06-10 22:00:35 -07:00
|
|
|
|
|
|
|
|
2016-04-09 20:03:57 -07:00
|
|
|
#TODO: Explain array_attrs
|
|
|
|
|
2015-06-10 22:00:35 -07:00
|
|
|
class Mobject(object):
|
|
|
|
"""
|
|
|
|
Mathematical Object
|
|
|
|
"""
|
2016-02-27 16:32:53 -08:00
|
|
|
CONFIG = {
|
2016-04-14 19:30:47 -07:00
|
|
|
"color" : WHITE,
|
2016-04-10 12:34:28 -07:00
|
|
|
"stroke_width" : DEFAULT_POINT_THICKNESS,
|
2016-04-14 19:30:47 -07:00
|
|
|
"name" : None,
|
|
|
|
"dim" : 3,
|
2016-07-19 11:08:31 -07:00
|
|
|
"target" : None,
|
2015-09-28 16:25:18 -07:00
|
|
|
}
|
2016-04-17 00:31:38 -07:00
|
|
|
def __init__(self, *submobjects, **kwargs):
|
2015-10-28 16:03:33 -07:00
|
|
|
digest_config(self, kwargs)
|
2016-09-07 13:38:05 -07:00
|
|
|
if not all(map(lambda m : isinstance(m, Mobject), submobjects)):
|
|
|
|
raise Exception("All submobjects must be of type Mobject")
|
2016-04-17 00:31:38 -07:00
|
|
|
self.submobjects = list(submobjects)
|
2015-09-28 16:25:18 -07:00
|
|
|
self.color = Color(self.color)
|
|
|
|
if self.name is None:
|
|
|
|
self.name = self.__class__.__name__
|
2015-09-24 10:54:59 -07:00
|
|
|
self.init_points()
|
|
|
|
self.generate_points()
|
2016-04-17 19:29:27 -07:00
|
|
|
self.init_colors()
|
2015-09-24 10:54:59 -07:00
|
|
|
|
2016-02-12 14:50:35 -08:00
|
|
|
def __str__(self):
|
2016-09-16 14:06:44 -07:00
|
|
|
return str(self.name)
|
2016-02-12 14:50:35 -08:00
|
|
|
|
2015-09-24 10:54:59 -07:00
|
|
|
def init_points(self):
|
2016-04-09 20:03:57 -07:00
|
|
|
self.points = np.zeros((0, self.dim))
|
|
|
|
|
|
|
|
def init_colors(self):
|
|
|
|
#For subclasses
|
|
|
|
pass
|
2015-06-10 22:00:35 -07:00
|
|
|
|
2016-02-12 14:50:35 -08:00
|
|
|
def generate_points(self):
|
|
|
|
#Typically implemented in subclass, unless purposefully left blank
|
|
|
|
pass
|
2015-06-10 22:00:35 -07:00
|
|
|
|
2015-08-17 11:12:56 -07:00
|
|
|
def add(self, *mobjects):
|
2016-04-17 00:31:38 -07:00
|
|
|
self.submobjects = list_update(self.submobjects, mobjects)
|
2015-11-02 13:03:01 -08:00
|
|
|
return self
|
|
|
|
|
2016-08-09 14:07:23 -07:00
|
|
|
def add_to_back(self, *mobjects):
|
|
|
|
self.remove(*mobjects)
|
|
|
|
self.submobjects = list(mobjects) + self.submobjects
|
|
|
|
return self
|
|
|
|
|
2016-04-27 17:35:04 -07:00
|
|
|
def remove(self, *mobjects):
|
|
|
|
for mobject in mobjects:
|
|
|
|
if mobject in self.submobjects:
|
|
|
|
self.submobjects.remove(mobject)
|
2016-09-01 11:11:57 -07:00
|
|
|
return self
|
2016-04-27 17:35:04 -07:00
|
|
|
|
2015-11-02 13:03:01 -08:00
|
|
|
def get_array_attrs(self):
|
2016-04-09 20:03:57 -07:00
|
|
|
return ["points"]
|
2015-11-02 13:03:01 -08:00
|
|
|
|
|
|
|
def digest_mobject_attrs(self):
|
|
|
|
"""
|
|
|
|
Ensures all attributes which are mobjects are included
|
2016-04-17 00:31:38 -07:00
|
|
|
in the submobjects list.
|
2015-11-02 13:03:01 -08:00
|
|
|
"""
|
|
|
|
mobject_attrs = filter(
|
|
|
|
lambda x : isinstance(x, Mobject),
|
|
|
|
self.__dict__.values()
|
|
|
|
)
|
2016-04-17 00:31:38 -07:00
|
|
|
self.submobjects = list_update(self.submobjects, mobject_attrs)
|
2015-11-02 13:03:01 -08:00
|
|
|
return self
|
|
|
|
|
|
|
|
def apply_over_attr_arrays(self, func):
|
2015-11-02 14:09:49 -08:00
|
|
|
for attr in self.get_array_attrs():
|
2015-11-02 13:03:01 -08:00
|
|
|
setattr(self, attr, func(getattr(self, attr)))
|
|
|
|
return self
|
|
|
|
|
2016-04-09 20:03:57 -07:00
|
|
|
def get_image(self):
|
2016-02-27 12:44:52 -08:00
|
|
|
from camera import Camera
|
|
|
|
camera = Camera()
|
|
|
|
camera.capture_mobject(self)
|
2016-04-09 20:03:57 -07:00
|
|
|
return Image.fromarray(camera.get_image())
|
|
|
|
|
|
|
|
def show(self):
|
|
|
|
self.get_image().show()
|
2015-11-02 13:03:01 -08:00
|
|
|
|
|
|
|
def save_image(self, name = None):
|
2016-04-09 20:03:57 -07:00
|
|
|
self.get_image().save(
|
2015-11-02 13:03:01 -08:00
|
|
|
os.path.join(MOVIE_DIR, (name or str(self)) + ".png")
|
|
|
|
)
|
|
|
|
|
2015-11-02 19:09:55 -08:00
|
|
|
def copy(self):
|
|
|
|
return deepcopy(self)
|
2015-11-02 13:03:01 -08:00
|
|
|
|
2016-09-16 14:06:44 -07:00
|
|
|
def generate_target(self):
|
2016-11-22 15:16:40 -08:00
|
|
|
self.target = None #Prevent exponential explosion
|
2016-09-16 14:06:44 -07:00
|
|
|
self.target = self.copy()
|
|
|
|
return self.target
|
|
|
|
|
2016-04-09 20:03:57 -07:00
|
|
|
#### Transforming operations ######
|
|
|
|
|
|
|
|
def apply_to_family(self, func):
|
2016-07-18 14:03:25 -07:00
|
|
|
for mob in self.family_members_with_points():
|
2016-04-09 20:03:57 -07:00
|
|
|
func(mob)
|
2015-11-02 13:03:01 -08:00
|
|
|
|
|
|
|
def shift(self, *vectors):
|
|
|
|
total_vector = reduce(op.add, vectors)
|
2016-07-18 14:03:25 -07:00
|
|
|
for mob in self.family_members_with_points():
|
2016-04-09 20:03:57 -07:00
|
|
|
mob.points += total_vector
|
|
|
|
return self
|
|
|
|
|
2015-11-02 13:03:01 -08:00
|
|
|
|
2016-12-06 13:29:21 -08:00
|
|
|
def scale(self, scale_factor, about_point = None):
|
|
|
|
if about_point is not None:
|
|
|
|
self.shift(-about_point)
|
2016-07-18 14:03:25 -07:00
|
|
|
for mob in self.family_members_with_points():
|
2015-11-02 13:03:01 -08:00
|
|
|
mob.points *= scale_factor
|
2016-12-06 13:29:21 -08:00
|
|
|
if about_point is not None:
|
|
|
|
self.shift(about_point)
|
2015-11-02 13:03:01 -08:00
|
|
|
return self
|
|
|
|
|
2016-12-06 13:29:21 -08:00
|
|
|
def rotate_about_origin(self, angle, axis = OUT, axes = []):
|
2015-11-09 10:34:00 -08:00
|
|
|
if len(axes) == 0:
|
|
|
|
axes = [axis]
|
2016-04-10 12:34:28 -07:00
|
|
|
rot_matrix = np.identity(self.dim)
|
2015-11-09 10:34:00 -08:00
|
|
|
for axis in axes:
|
|
|
|
rot_matrix = np.dot(rot_matrix, rotation_matrix(angle, axis))
|
|
|
|
t_rot_matrix = np.transpose(rot_matrix)
|
2016-07-18 14:03:25 -07:00
|
|
|
for mob in self.family_members_with_points():
|
2015-11-09 10:34:00 -08:00
|
|
|
mob.points = np.dot(mob.points, t_rot_matrix)
|
2016-12-06 13:29:21 -08:00
|
|
|
return self
|
|
|
|
|
|
|
|
def rotate(self, angle, axis = OUT, axes = [], about_point = None):
|
|
|
|
if about_point is None:
|
|
|
|
self.rotate_about_origin(angle, axis, axes)
|
|
|
|
else:
|
|
|
|
self.do_about_point(about_point, self.rotate, angle, axis, axes)
|
2015-11-02 13:03:01 -08:00
|
|
|
return self
|
|
|
|
|
|
|
|
def stretch(self, factor, dim):
|
2016-07-18 14:03:25 -07:00
|
|
|
for mob in self.family_members_with_points():
|
2015-11-02 13:03:01 -08:00
|
|
|
mob.points[:,dim] *= factor
|
|
|
|
return self
|
|
|
|
|
|
|
|
def apply_function(self, function):
|
2016-07-18 14:03:25 -07:00
|
|
|
for mob in self.family_members_with_points():
|
2015-11-02 13:03:01 -08:00
|
|
|
mob.points = np.apply_along_axis(function, 1, mob.points)
|
|
|
|
return self
|
|
|
|
|
|
|
|
def wag(self, direction = RIGHT, axis = DOWN, wag_factor = 1.0):
|
2016-07-18 14:03:25 -07:00
|
|
|
for mob in self.family_members_with_points():
|
2015-11-02 13:03:01 -08:00
|
|
|
alphas = np.dot(mob.points, np.transpose(axis))
|
|
|
|
alphas -= min(alphas)
|
|
|
|
alphas /= max(alphas)
|
|
|
|
alphas = alphas**wag_factor
|
|
|
|
mob.points += np.dot(
|
|
|
|
alphas.reshape((len(alphas), 1)),
|
2016-04-10 12:34:28 -07:00
|
|
|
np.array(direction).reshape((1, mob.dim))
|
2015-11-02 13:03:01 -08:00
|
|
|
)
|
|
|
|
return self
|
|
|
|
|
2016-01-04 10:15:57 -08:00
|
|
|
def reverse_points(self):
|
2016-07-18 14:03:25 -07:00
|
|
|
for mob in self.family_members_with_points():
|
2016-01-04 10:15:57 -08:00
|
|
|
mob.apply_over_attr_arrays(
|
|
|
|
lambda arr : np.array(list(reversed(arr)))
|
|
|
|
)
|
|
|
|
return self
|
|
|
|
|
2015-08-17 11:12:56 -07:00
|
|
|
def repeat(self, count):
|
2015-11-02 13:03:01 -08:00
|
|
|
"""
|
|
|
|
This can make transition animations nicer
|
|
|
|
"""
|
|
|
|
def repeat_array(array):
|
|
|
|
return reduce(
|
|
|
|
lambda a1, a2 : np.append(a1, a2, axis = 0),
|
|
|
|
[array]*count
|
|
|
|
)
|
2016-07-18 14:03:25 -07:00
|
|
|
for mob in self.family_members_with_points():
|
2015-11-02 13:03:01 -08:00
|
|
|
mob.apply_over_attr_arrays(repeat_array)
|
2015-08-17 11:12:56 -07:00
|
|
|
return self
|
|
|
|
|
2015-11-02 13:03:01 -08:00
|
|
|
#### In place operations ######
|
|
|
|
|
2016-12-06 13:29:21 -08:00
|
|
|
def do_about_point(self, point, method, *args, **kwargs):
|
|
|
|
self.shift(-point)
|
2015-09-24 10:54:59 -07:00
|
|
|
method(*args, **kwargs)
|
2016-12-06 13:29:21 -08:00
|
|
|
self.shift(point)
|
|
|
|
return self
|
|
|
|
|
|
|
|
def do_in_place(self, method, *args, **kwargs):
|
|
|
|
self.do_about_point(self.get_center(), method, *args, **kwargs)
|
2015-09-24 10:54:59 -07:00
|
|
|
return self
|
|
|
|
|
2015-11-09 10:34:00 -08:00
|
|
|
def rotate_in_place(self, angle, axis = OUT, axes = []):
|
|
|
|
self.do_in_place(self.rotate, angle, axis, axes)
|
2015-06-10 22:00:35 -07:00
|
|
|
return self
|
|
|
|
|
2016-04-17 19:29:27 -07:00
|
|
|
def flip(self, axis = UP):
|
|
|
|
self.rotate_in_place(np.pi, axis)
|
2016-07-28 11:16:28 -07:00
|
|
|
return self
|
2016-04-17 19:29:27 -07:00
|
|
|
|
2015-11-02 13:03:01 -08:00
|
|
|
def scale_in_place(self, scale_factor):
|
|
|
|
self.do_in_place(self.scale, scale_factor)
|
2015-06-10 22:00:35 -07:00
|
|
|
return self
|
|
|
|
|
2017-02-27 22:03:33 -08:00
|
|
|
def scale_about_point(self, scale_factor, point):
|
|
|
|
self.do_about_point(point, self.scale, scale_factor)
|
|
|
|
return self
|
|
|
|
|
2015-11-02 13:03:01 -08:00
|
|
|
def pose_at_angle(self):
|
|
|
|
self.rotate_in_place(np.pi / 7, RIGHT+UP)
|
2015-06-13 19:00:23 -07:00
|
|
|
return self
|
|
|
|
|
2015-06-10 22:00:35 -07:00
|
|
|
def center(self):
|
|
|
|
self.shift(-self.get_center())
|
|
|
|
return self
|
|
|
|
|
2015-09-28 16:25:18 -07:00
|
|
|
def align_on_border(self, direction, buff = DEFAULT_MOBJECT_TO_EDGE_BUFFER):
|
2015-06-19 08:31:02 -07:00
|
|
|
"""
|
|
|
|
Direction just needs to be a vector pointing towards side or
|
|
|
|
corner in the 2d plane.
|
|
|
|
"""
|
2015-11-02 13:03:01 -08:00
|
|
|
target_point = np.sign(direction) * (SPACE_WIDTH, SPACE_HEIGHT, 0)
|
2016-07-25 16:04:54 -07:00
|
|
|
point_to_align = self.get_critical_point(direction)
|
|
|
|
shift_val = target_point - point_to_align - buff * np.array(direction)
|
2016-01-04 10:15:57 -08:00
|
|
|
shift_val = shift_val * abs(np.sign(direction))
|
|
|
|
self.shift(shift_val)
|
2015-06-19 08:31:02 -07:00
|
|
|
return self
|
|
|
|
|
2015-11-02 13:03:01 -08:00
|
|
|
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 = DEFAULT_MOBJECT_TO_EDGE_BUFFER):
|
|
|
|
return self.align_on_border(edge, buff)
|
|
|
|
|
2016-08-20 20:23:01 -07:00
|
|
|
def next_to(self, mobject_or_point,
|
2015-09-25 19:43:53 -07:00
|
|
|
direction = RIGHT,
|
2015-09-28 16:25:18 -07:00
|
|
|
buff = DEFAULT_MOBJECT_TO_MOBJECT_BUFFER,
|
2016-08-20 20:23:01 -07:00
|
|
|
aligned_edge = ORIGIN,
|
|
|
|
align_using_submobjects = False,
|
|
|
|
):
|
2016-07-25 16:04:54 -07:00
|
|
|
if isinstance(mobject_or_point, Mobject):
|
|
|
|
mob = mobject_or_point
|
2016-08-20 20:23:01 -07:00
|
|
|
target_point = mob.get_critical_point(
|
|
|
|
aligned_edge+direction,
|
|
|
|
use_submobject = align_using_submobjects
|
|
|
|
)
|
2016-07-25 16:04:54 -07:00
|
|
|
else:
|
|
|
|
target_point = mobject_or_point
|
2016-08-20 20:23:01 -07:00
|
|
|
point_to_align = self.get_critical_point(
|
|
|
|
aligned_edge-direction,
|
|
|
|
use_submobject = align_using_submobjects
|
|
|
|
)
|
2016-07-25 16:04:54 -07:00
|
|
|
self.shift(target_point - point_to_align + buff*direction)
|
2015-09-24 10:54:59 -07:00
|
|
|
return self
|
|
|
|
|
2016-08-20 20:23:01 -07:00
|
|
|
|
2016-08-02 12:26:15 -07:00
|
|
|
def shift_onto_screen(self, **kwargs):
|
2016-07-19 11:08:31 -07:00
|
|
|
space_lengths = [SPACE_WIDTH, SPACE_HEIGHT]
|
|
|
|
for vect in UP, DOWN, LEFT, RIGHT:
|
|
|
|
dim = np.argmax(np.abs(vect))
|
2017-01-25 16:40:59 -08:00
|
|
|
buff = kwargs.get("buff", DEFAULT_MOBJECT_TO_EDGE_BUFFER)
|
|
|
|
max_val = space_lengths[dim] - buff
|
2017-01-27 19:31:20 -08:00
|
|
|
edge_center = self.get_edge_center(vect)
|
|
|
|
if np.dot(edge_center, vect) > max_val:
|
2016-08-02 12:26:15 -07:00
|
|
|
self.to_edge(vect, **kwargs)
|
2016-09-16 14:06:44 -07:00
|
|
|
return self
|
2016-07-19 11:08:31 -07:00
|
|
|
|
2017-01-16 13:26:46 -08:00
|
|
|
def is_off_screen(self):
|
|
|
|
if self.get_left()[0] > SPACE_WIDTH:
|
|
|
|
return True
|
|
|
|
if self.get_right()[0] < -SPACE_WIDTH:
|
|
|
|
return True
|
|
|
|
if self.get_bottom()[1] > SPACE_HEIGHT:
|
|
|
|
return True
|
|
|
|
if self.get_top()[1] < -SPACE_HEIGHT:
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
2017-02-27 22:03:33 -08:00
|
|
|
def stretch_about_point(self, factor, dim, point):
|
|
|
|
self.do_about_point(point, self.stretch, factor, dim)
|
|
|
|
return self
|
|
|
|
|
2016-09-24 22:46:17 -07:00
|
|
|
def stretch_in_place(self, factor, dim):
|
|
|
|
self.do_in_place(self.stretch, factor, dim)
|
|
|
|
return self
|
|
|
|
|
2017-03-09 15:50:40 -08:00
|
|
|
def rescale_to_fit(self, length, dim, stretch = False):
|
2015-11-02 13:03:01 -08:00
|
|
|
old_length = self.length_over_dim(dim)
|
2016-07-15 18:16:06 -07:00
|
|
|
if old_length == 0:
|
|
|
|
return self
|
|
|
|
if stretch:
|
2016-09-24 22:46:17 -07:00
|
|
|
self.stretch_in_place(length/old_length, dim)
|
2016-07-15 18:16:06 -07:00
|
|
|
else:
|
2016-09-24 22:46:17 -07:00
|
|
|
self.scale_in_place(length/old_length)
|
2015-08-01 11:34:33 -07:00
|
|
|
return self
|
|
|
|
|
|
|
|
def stretch_to_fit_width(self, width):
|
2017-03-09 15:50:40 -08:00
|
|
|
return self.rescale_to_fit(width, 0, stretch = True)
|
2015-08-01 11:34:33 -07:00
|
|
|
|
|
|
|
def stretch_to_fit_height(self, height):
|
2017-03-09 15:50:40 -08:00
|
|
|
return self.rescale_to_fit(height, 1, stretch = True)
|
2015-08-01 11:34:33 -07:00
|
|
|
|
2015-10-20 21:55:46 -07:00
|
|
|
def scale_to_fit_width(self, width):
|
2017-03-09 15:50:40 -08:00
|
|
|
return self.rescale_to_fit(width, 0, stretch = False)
|
2015-10-20 21:55:46 -07:00
|
|
|
|
|
|
|
def scale_to_fit_height(self, height):
|
2017-03-09 15:50:40 -08:00
|
|
|
return self.rescale_to_fit(height, 1, stretch = False)
|
2016-07-15 18:16:06 -07:00
|
|
|
|
2017-02-16 13:03:26 -08:00
|
|
|
def space_out_submobjects(self, factor = 1.5, **kwargs):
|
2017-01-26 19:59:55 -08:00
|
|
|
self.scale_in_place(factor)
|
|
|
|
for submob in self.submobjects:
|
|
|
|
submob.scale_in_place(1./factor)
|
|
|
|
return self
|
|
|
|
|
2016-08-20 20:23:01 -07:00
|
|
|
def move_to(self, point_or_mobject, aligned_edge = ORIGIN):
|
2016-07-15 18:16:06 -07:00
|
|
|
if isinstance(point_or_mobject, Mobject):
|
2016-08-20 20:23:01 -07:00
|
|
|
target = point_or_mobject.get_critical_point(aligned_edge)
|
2016-07-15 18:16:06 -07:00
|
|
|
else:
|
|
|
|
target = point_or_mobject
|
2016-08-20 20:23:01 -07:00
|
|
|
point_to_align = self.get_critical_point(aligned_edge)
|
2016-07-25 16:04:54 -07:00
|
|
|
self.shift(target - point_to_align)
|
2016-07-15 18:16:06 -07:00
|
|
|
return self
|
2015-10-20 21:55:46 -07:00
|
|
|
|
2016-08-13 18:27:02 -07:00
|
|
|
def replace(self, mobject, dim_to_match = 0, stretch = False):
|
2016-04-17 00:31:38 -07:00
|
|
|
if not mobject.get_num_points() and not mobject.submobjects:
|
2015-08-07 18:10:00 -07:00
|
|
|
raise Warning("Attempting to replace mobject with no points")
|
|
|
|
return self
|
2015-08-03 22:23:00 -07:00
|
|
|
if stretch:
|
|
|
|
self.stretch_to_fit_width(mobject.get_width())
|
|
|
|
self.stretch_to_fit_height(mobject.get_height())
|
|
|
|
else:
|
2017-03-09 15:50:40 -08:00
|
|
|
self.rescale_to_fit(
|
2016-08-13 18:27:02 -07:00
|
|
|
mobject.length_over_dim(dim_to_match),
|
|
|
|
dim_to_match,
|
|
|
|
stretch = False
|
|
|
|
)
|
2016-07-15 18:16:06 -07:00
|
|
|
self.shift(mobject.get_center() - self.get_center())
|
2015-08-01 11:34:33 -07:00
|
|
|
return self
|
|
|
|
|
2016-02-21 15:44:54 -08:00
|
|
|
def position_endpoints_on(self, start, end):
|
|
|
|
curr_vect = self.points[-1] - self.points[0]
|
|
|
|
if np.all(curr_vect == 0):
|
|
|
|
raise Exception("Cannot position endpoints of closed loop")
|
|
|
|
target_vect = end - start
|
|
|
|
self.scale(np.linalg.norm(target_vect)/np.linalg.norm(curr_vect))
|
|
|
|
self.rotate(
|
|
|
|
angle_of_vector(target_vect) - \
|
|
|
|
angle_of_vector(curr_vect)
|
|
|
|
)
|
|
|
|
self.shift(start-self.points[0])
|
|
|
|
return self
|
|
|
|
|
2016-04-09 20:03:57 -07:00
|
|
|
## Color functions
|
2016-02-21 15:44:54 -08:00
|
|
|
|
2016-07-22 11:22:31 -07:00
|
|
|
def highlight(self, color = YELLOW_C, family = True, condition = None):
|
2016-04-09 20:03:57 -07:00
|
|
|
"""
|
|
|
|
Condition is function which takes in one arguments, (x, y, z).
|
|
|
|
"""
|
|
|
|
raise Exception("Not implemented")
|
|
|
|
|
2016-08-19 15:54:16 -07:00
|
|
|
def gradient_highlight(self, *colors):
|
|
|
|
self.submobject_gradient_highlight(*colors)
|
|
|
|
return self
|
2016-07-22 11:22:31 -07:00
|
|
|
|
2016-08-09 14:07:23 -07:00
|
|
|
def submobject_gradient_highlight(self, *colors):
|
|
|
|
if len(colors) == 0:
|
|
|
|
raise Exception("Need at least one color")
|
|
|
|
elif len(colors) == 1:
|
|
|
|
return self.highlight(*colors)
|
|
|
|
|
2016-07-22 11:22:31 -07:00
|
|
|
mobs = self.family_members_with_points()
|
2016-10-25 17:35:16 -07:00
|
|
|
new_colors = color_gradient(colors, len(mobs))
|
2016-08-09 14:07:23 -07:00
|
|
|
for mob, color in zip(mobs, new_colors):
|
2016-07-22 11:22:31 -07:00
|
|
|
mob.highlight(color, family = False)
|
|
|
|
return self
|
2015-06-10 22:00:35 -07:00
|
|
|
|
2015-09-28 16:25:18 -07:00
|
|
|
def set_color(self, color):
|
|
|
|
self.highlight(color)
|
|
|
|
self.color = Color(color)
|
|
|
|
return self
|
|
|
|
|
2015-09-25 19:43:53 -07:00
|
|
|
def to_original_color(self):
|
|
|
|
self.highlight(self.color)
|
|
|
|
return self
|
|
|
|
|
|
|
|
def fade_to(self, color, alpha):
|
2016-07-18 14:03:25 -07:00
|
|
|
for mob in self.family_members_with_points():
|
2016-07-22 11:22:31 -07:00
|
|
|
start = color_to_rgb(mob.get_color())
|
|
|
|
end = color_to_rgb(color)
|
|
|
|
new_rgb = interpolate(start, end, alpha)
|
2016-07-22 18:45:34 -07:00
|
|
|
mob.highlight(Color(rgb = new_rgb), family = False)
|
2015-09-25 19:43:53 -07:00
|
|
|
return self
|
|
|
|
|
2015-12-21 22:51:26 -08:00
|
|
|
def fade(self, darkness = 0.5):
|
|
|
|
self.fade_to(BLACK, darkness)
|
2015-06-10 22:00:35 -07:00
|
|
|
return self
|
|
|
|
|
2016-04-09 20:03:57 -07:00
|
|
|
def get_color(self):
|
|
|
|
return self.color
|
|
|
|
##
|
|
|
|
|
2016-08-09 14:07:23 -07:00
|
|
|
def save_state(self):
|
2016-11-15 20:08:28 -08:00
|
|
|
if hasattr(self, "saved_state"):
|
|
|
|
#Prevent exponential growth of data
|
|
|
|
self.saved_state = None
|
2016-08-09 14:07:23 -07:00
|
|
|
self.saved_state = self.copy()
|
|
|
|
return self
|
|
|
|
|
|
|
|
def restore(self):
|
2017-02-27 15:56:22 -08:00
|
|
|
if not hasattr(self, "saved_state") or self.save_state is None:
|
2016-08-09 14:07:23 -07:00
|
|
|
raise Exception("Trying to restore without having saved")
|
2017-02-27 15:56:22 -08:00
|
|
|
self.align_data(self.saved_state)
|
|
|
|
for sm1, sm2 in zip(self.submobject_family(), self.saved_state.submobject_family()):
|
|
|
|
sm1.interpolate(sm1, sm2, 1)
|
2016-08-09 14:07:23 -07:00
|
|
|
return self
|
2016-04-09 20:03:57 -07:00
|
|
|
|
2017-03-02 00:43:13 -08:00
|
|
|
def apply_complex_function(self, function, **kwargs):
|
2016-04-09 20:03:57 -07:00
|
|
|
return self.apply_function(
|
2017-03-02 00:43:13 -08:00
|
|
|
lambda (x, y, z) : complex_to_R3(function(complex(x, y))),
|
|
|
|
**kwargs
|
2016-04-09 20:03:57 -07:00
|
|
|
)
|
|
|
|
|
2015-11-02 13:03:01 -08:00
|
|
|
def reduce_across_dimension(self, points_func, reduce_func, dim):
|
|
|
|
try:
|
2016-12-26 07:10:38 -08:00
|
|
|
points = self.get_points_defining_boundary()
|
|
|
|
values = [points_func(points[:, dim])]
|
2015-11-02 13:03:01 -08:00
|
|
|
except:
|
|
|
|
values = []
|
|
|
|
values += [
|
|
|
|
mob.reduce_across_dimension(points_func, reduce_func, dim)
|
2016-04-17 00:31:38 -07:00
|
|
|
for mob in self.submobjects
|
2015-11-02 13:03:01 -08:00
|
|
|
]
|
|
|
|
try:
|
|
|
|
return reduce_func(values)
|
|
|
|
except:
|
|
|
|
return 0
|
|
|
|
|
|
|
|
def get_merged_array(self, array_attr):
|
2016-04-10 12:34:28 -07:00
|
|
|
result = np.zeros((0, self.dim))
|
2016-07-18 14:03:25 -07:00
|
|
|
for mob in self.family_members_with_points():
|
2015-11-09 10:34:00 -08:00
|
|
|
result = np.append(result, getattr(mob, array_attr), 0)
|
|
|
|
return result
|
2015-11-02 13:03:01 -08:00
|
|
|
|
|
|
|
def get_all_points(self):
|
|
|
|
return self.get_merged_array("points")
|
|
|
|
|
2015-06-10 22:00:35 -07:00
|
|
|
### Getters ###
|
2015-06-13 19:00:23 -07:00
|
|
|
|
2016-12-26 07:10:38 -08:00
|
|
|
def get_points_defining_boundary(self):
|
|
|
|
return self.points
|
|
|
|
|
2016-04-14 19:30:47 -07:00
|
|
|
def get_num_points(self):
|
|
|
|
return len(self.points)
|
2015-11-02 13:03:01 -08:00
|
|
|
|
2016-08-20 20:23:01 -07:00
|
|
|
def get_critical_point(self, direction, use_submobject = False):
|
|
|
|
if use_submobject:
|
|
|
|
return self.get_submobject_critical_point(direction)
|
2016-04-10 12:34:28 -07:00
|
|
|
result = np.zeros(self.dim)
|
2017-01-26 19:59:55 -08:00
|
|
|
for dim in range(self.dim):
|
2015-11-02 13:03:01 -08:00
|
|
|
if direction[dim] <= 0:
|
|
|
|
min_point = self.reduce_across_dimension(np.min, np.min, dim)
|
|
|
|
if direction[dim] >= 0:
|
|
|
|
max_point = self.reduce_across_dimension(np.max, np.max, dim)
|
|
|
|
|
|
|
|
if direction[dim] == 0:
|
|
|
|
result[dim] = (max_point+min_point)/2
|
|
|
|
elif direction[dim] < 0:
|
|
|
|
result[dim] = min_point
|
|
|
|
else:
|
|
|
|
result[dim] = max_point
|
|
|
|
return result
|
|
|
|
|
2016-08-20 20:23:01 -07:00
|
|
|
def get_submobject_critical_point(self, direction):
|
|
|
|
if len(self.split()) == 1:
|
|
|
|
return self.get_critical_point(direction)
|
2017-03-10 16:54:04 -08:00
|
|
|
with_points = self.family_members_with_points()
|
|
|
|
submob_critical_points = np.array([
|
|
|
|
submob.get_critical_point(direction)
|
|
|
|
for submob in with_points
|
2016-08-20 20:23:01 -07:00
|
|
|
])
|
2017-03-10 16:54:04 -08:00
|
|
|
index = np.argmax(np.dot(direction, submob_critical_points.T))
|
|
|
|
return submob_critical_points[index]
|
2016-08-20 20:23:01 -07:00
|
|
|
|
2015-11-02 13:03:01 -08:00
|
|
|
# Pseudonyms for more general get_critical_point method
|
|
|
|
def get_edge_center(self, direction):
|
|
|
|
return self.get_critical_point(direction)
|
|
|
|
|
|
|
|
def get_corner(self, direction):
|
|
|
|
return self.get_critical_point(direction)
|
2015-06-13 19:00:23 -07:00
|
|
|
|
2015-06-10 22:00:35 -07:00
|
|
|
def get_center(self):
|
2016-04-10 12:34:28 -07:00
|
|
|
return self.get_critical_point(np.zeros(self.dim))
|
2015-08-07 18:10:00 -07:00
|
|
|
|
|
|
|
def get_center_of_mass(self):
|
2015-11-02 13:03:01 -08:00
|
|
|
return np.apply_along_axis(np.mean, 0, self.get_all_points())
|
2015-06-10 22:00:35 -07:00
|
|
|
|
2015-08-12 14:24:36 -07:00
|
|
|
def get_boundary_point(self, direction):
|
2015-11-02 13:03:01 -08:00
|
|
|
all_points = self.get_all_points()
|
|
|
|
return all_points[np.argmax(np.dot(all_points, direction))]
|
2015-09-25 19:43:53 -07:00
|
|
|
|
2015-08-07 18:10:00 -07:00
|
|
|
def get_top(self):
|
2015-08-17 11:12:56 -07:00
|
|
|
return self.get_edge_center(UP)
|
2015-08-07 18:10:00 -07:00
|
|
|
|
|
|
|
def get_bottom(self):
|
2015-08-17 11:12:56 -07:00
|
|
|
return self.get_edge_center(DOWN)
|
2015-08-07 18:10:00 -07:00
|
|
|
|
|
|
|
def get_right(self):
|
2015-08-17 11:12:56 -07:00
|
|
|
return self.get_edge_center(RIGHT)
|
2015-08-07 18:10:00 -07:00
|
|
|
|
|
|
|
def get_left(self):
|
2015-08-17 11:12:56 -07:00
|
|
|
return self.get_edge_center(LEFT)
|
2015-08-07 18:10:00 -07:00
|
|
|
|
2015-11-02 13:03:01 -08:00
|
|
|
def length_over_dim(self, dim):
|
|
|
|
return (
|
|
|
|
self.reduce_across_dimension(np.max, np.max, dim) -
|
|
|
|
self.reduce_across_dimension(np.min, np.min, dim)
|
|
|
|
)
|
|
|
|
|
2015-06-10 22:00:35 -07:00
|
|
|
def get_width(self):
|
2015-11-02 13:03:01 -08:00
|
|
|
return self.length_over_dim(0)
|
2015-06-10 22:00:35 -07:00
|
|
|
|
|
|
|
def get_height(self):
|
2015-11-02 13:03:01 -08:00
|
|
|
return self.length_over_dim(1)
|
|
|
|
|
2016-02-15 23:43:28 -08:00
|
|
|
def point_from_proportion(self, alpha):
|
2016-04-09 20:03:57 -07:00
|
|
|
raise Exception("Not implemented")
|
2015-06-10 22:00:35 -07:00
|
|
|
|
2016-04-09 20:03:57 -07:00
|
|
|
|
2016-08-10 10:26:07 -07:00
|
|
|
## Family matters
|
|
|
|
|
|
|
|
def __getitem__(self, index):
|
|
|
|
return self.split()[index]
|
|
|
|
|
|
|
|
def __iter__(self):
|
|
|
|
return iter(self.split())
|
2016-04-09 20:03:57 -07:00
|
|
|
|
2017-01-18 17:27:28 -08:00
|
|
|
def __len__(self):
|
|
|
|
return len(self.split())
|
|
|
|
|
2016-04-09 20:03:57 -07:00
|
|
|
def split(self):
|
|
|
|
result = [self] if len(self.points) > 0 else []
|
2016-04-17 00:31:38 -07:00
|
|
|
return result + self.submobjects
|
2016-04-09 20:03:57 -07:00
|
|
|
|
|
|
|
def submobject_family(self):
|
2016-04-17 00:31:38 -07:00
|
|
|
sub_families = map(Mobject.submobject_family, self.submobjects)
|
2016-12-26 07:10:38 -08:00
|
|
|
all_mobjects = [self] + list(it.chain(*sub_families))
|
2016-04-09 20:03:57 -07:00
|
|
|
return remove_list_redundancies(all_mobjects)
|
|
|
|
|
2016-07-18 14:03:25 -07:00
|
|
|
def family_members_with_points(self):
|
2016-04-09 20:03:57 -07:00
|
|
|
return filter(
|
|
|
|
lambda m : m.get_num_points() > 0,
|
|
|
|
self.submobject_family()
|
|
|
|
)
|
2016-05-25 20:23:06 -07:00
|
|
|
|
2016-07-19 11:08:31 -07:00
|
|
|
def arrange_submobjects(self, direction = RIGHT, center = True, **kwargs):
|
2016-05-03 23:14:40 -07:00
|
|
|
for m1, m2 in zip(self.submobjects, self.submobjects[1:]):
|
2016-07-19 11:08:31 -07:00
|
|
|
m2.next_to(m1, direction, **kwargs)
|
2016-05-03 23:14:40 -07:00
|
|
|
if center:
|
|
|
|
self.center()
|
|
|
|
return self
|
2016-04-09 20:03:57 -07:00
|
|
|
|
2017-02-08 13:29:32 -08:00
|
|
|
def sort_submobjects(self, point_to_num_func = lambda p : p[0]):
|
|
|
|
self.submobjects.sort(
|
|
|
|
lambda *mobs : cmp(*[
|
|
|
|
point_to_num_func(mob.get_center())
|
|
|
|
for mob in mobs
|
|
|
|
])
|
|
|
|
)
|
|
|
|
return self
|
|
|
|
|
2016-04-10 12:34:28 -07:00
|
|
|
## Alignment
|
|
|
|
def align_data(self, mobject):
|
2016-04-17 00:31:38 -07:00
|
|
|
self.align_submobjects(mobject)
|
2016-04-10 12:34:28 -07:00
|
|
|
self.align_points(mobject)
|
2015-12-13 15:41:45 -08:00
|
|
|
#Recurse
|
2016-04-17 00:31:38 -07:00
|
|
|
for m1, m2 in zip(self.submobjects, mobject.submobjects):
|
2016-04-10 12:34:28 -07:00
|
|
|
m1.align_data(m2)
|
2015-11-02 14:09:49 -08:00
|
|
|
|
2016-04-14 19:30:47 -07:00
|
|
|
def get_point_mobject(self, center = None):
|
2016-04-10 12:34:28 -07:00
|
|
|
"""
|
|
|
|
The simplest mobject to be transformed to or from self.
|
|
|
|
Should by a point of the appropriate type
|
|
|
|
"""
|
|
|
|
raise Exception("Not implemented")
|
|
|
|
|
2016-04-13 20:30:26 -07:00
|
|
|
|
2016-04-10 12:34:28 -07:00
|
|
|
def align_points(self, mobject):
|
|
|
|
count1 = self.get_num_points()
|
|
|
|
count2 = mobject.get_num_points()
|
|
|
|
if count1 < count2:
|
|
|
|
self.align_points_with_larger(mobject)
|
|
|
|
elif count2 < count1:
|
|
|
|
mobject.align_points_with_larger(self)
|
|
|
|
return self
|
|
|
|
|
|
|
|
def align_points_with_larger(self, larger_mobject):
|
|
|
|
raise Exception("Not implemented")
|
|
|
|
|
2016-04-17 00:31:38 -07:00
|
|
|
def align_submobjects(self, mobject):
|
2016-04-14 19:30:47 -07:00
|
|
|
#If one is empty, and the other is not,
|
|
|
|
#push it into its submobject list
|
|
|
|
self_has_points, mob_has_points = [
|
|
|
|
mob.get_num_points() > 0
|
|
|
|
for mob in self, mobject
|
|
|
|
]
|
|
|
|
if self_has_points and not mob_has_points:
|
2016-04-23 23:36:05 -07:00
|
|
|
mobject.null_point_align(self)
|
2016-04-14 19:30:47 -07:00
|
|
|
elif mob_has_points and not self_has_points:
|
2016-04-23 23:36:05 -07:00
|
|
|
self.null_point_align(mobject)
|
2016-04-17 00:31:38 -07:00
|
|
|
self_count = len(self.submobjects)
|
|
|
|
mob_count = len(mobject.submobjects)
|
2016-04-14 19:30:47 -07:00
|
|
|
diff = abs(self_count-mob_count)
|
|
|
|
if self_count < mob_count:
|
2016-04-17 00:31:38 -07:00
|
|
|
self.add_n_more_submobjects(diff)
|
2016-04-14 19:30:47 -07:00
|
|
|
elif mob_count < self_count:
|
2016-04-17 00:31:38 -07:00
|
|
|
mobject.add_n_more_submobjects(diff)
|
2016-04-14 19:30:47 -07:00
|
|
|
return self
|
|
|
|
|
2016-04-23 23:36:05 -07:00
|
|
|
def null_point_align(self, mobject):
|
|
|
|
"""
|
|
|
|
If self has no points, but needs to align
|
|
|
|
with mobject, which has points
|
|
|
|
"""
|
|
|
|
if self.submobjects:
|
|
|
|
mobject.push_self_into_submobjects()
|
|
|
|
else:
|
|
|
|
self.points = np.array([mobject.points[0]])
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
2016-04-17 00:31:38 -07:00
|
|
|
def push_self_into_submobjects(self):
|
2016-04-14 19:30:47 -07:00
|
|
|
copy = self.copy()
|
2016-04-17 00:31:38 -07:00
|
|
|
copy.submobjects = []
|
2016-04-17 12:59:53 -07:00
|
|
|
self.init_points()
|
2016-04-14 19:30:47 -07:00
|
|
|
self.add(copy)
|
|
|
|
return self
|
|
|
|
|
2016-04-17 00:31:38 -07:00
|
|
|
def add_n_more_submobjects(self, n):
|
2016-04-17 12:59:53 -07:00
|
|
|
curr = len(self.submobjects)
|
|
|
|
if n > 0 and curr == 0:
|
2016-04-14 19:30:47 -07:00
|
|
|
self.add(self.copy())
|
2016-04-17 12:59:53 -07:00
|
|
|
n -= 1
|
|
|
|
curr += 1
|
|
|
|
indices = curr*np.arange(curr+n)/(curr+n)
|
|
|
|
new_submobjects = []
|
|
|
|
for index in indices:
|
|
|
|
submob = self.submobjects[index]
|
|
|
|
if submob in new_submobjects:
|
2016-04-20 19:24:54 -07:00
|
|
|
submob = self.repeat_submobject(submob)
|
2016-04-17 12:59:53 -07:00
|
|
|
new_submobjects.append(submob)
|
|
|
|
self.submobjects = new_submobjects
|
2016-04-14 19:30:47 -07:00
|
|
|
return self
|
|
|
|
|
2016-04-20 19:24:54 -07:00
|
|
|
def repeat_submobject(self, submob):
|
|
|
|
return submob.copy()
|
|
|
|
|
2016-05-25 20:23:06 -07:00
|
|
|
def interpolate(self, mobject1, mobject2,
|
|
|
|
alpha, path_func = straight_path):
|
2015-06-10 22:00:35 -07:00
|
|
|
"""
|
2017-02-27 15:56:22 -08:00
|
|
|
Turns self into an interpolation between mobject1
|
2015-06-10 22:00:35 -07:00
|
|
|
and mobject2.
|
|
|
|
"""
|
2016-04-10 12:34:28 -07:00
|
|
|
self.points = path_func(
|
|
|
|
mobject1.points, mobject2.points, alpha
|
|
|
|
)
|
|
|
|
self.interpolate_color(mobject1, mobject2, alpha)
|
|
|
|
|
|
|
|
def interpolate_color(self, mobject1, mobject2, alpha):
|
2016-09-10 15:21:03 -07:00
|
|
|
pass #To implement in subclass
|
2015-06-10 22:00:35 -07:00
|
|
|
|
2016-07-22 15:50:52 -07:00
|
|
|
def become_partial(self, mobject, a, b):
|
2016-04-11 21:18:52 -07:00
|
|
|
"""
|
|
|
|
Set points in such a way as to become only
|
|
|
|
part of mobject.
|
|
|
|
Inputs 0 <= a < b <= 1 determine what portion
|
|
|
|
of mobject to become.
|
|
|
|
"""
|
2016-09-10 15:21:03 -07:00
|
|
|
pass #To implement in subclasses
|
2016-09-16 14:06:44 -07:00
|
|
|
|
2016-07-19 14:37:18 -07:00
|
|
|
#TODO, color?
|
2016-07-19 11:08:31 -07:00
|
|
|
|
|
|
|
def pointwise_become_partial(self, mobject, a, b):
|
2016-09-10 15:21:03 -07:00
|
|
|
pass #To implement in subclass
|
2015-10-28 16:03:33 -07:00
|
|
|
|
2015-08-17 11:12:56 -07:00
|
|
|
|
|
|
|
|
2016-09-07 13:38:05 -07:00
|
|
|
class Group(Mobject):
|
|
|
|
#Alternate name to improve readibility in cases where
|
|
|
|
#the mobject is used primarily for its submobject housing
|
|
|
|
#functionality.
|
|
|
|
pass
|
2015-08-17 11:12:56 -07:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2015-06-10 22:00:35 -07:00
|
|
|
|
2016-07-19 11:08:31 -07:00
|
|
|
|