2018-08-09 17:56:05 -07:00
|
|
|
|
2018-03-31 15:11:35 -07:00
|
|
|
import copy
|
|
|
|
import itertools as it
|
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
|
2018-06-18 13:27:42 -07:00
|
|
|
import random
|
2018-03-31 15:11:35 -07:00
|
|
|
|
2015-06-10 22:00:35 -07:00
|
|
|
from colour import Color
|
|
|
|
|
2018-03-30 18:19:23 -07:00
|
|
|
from constants import *
|
|
|
|
from container.container import Container
|
|
|
|
from utils.bezier import interpolate
|
2018-03-31 15:11:35 -07:00
|
|
|
from utils.color import color_gradient
|
|
|
|
from utils.color import color_to_rgb
|
2018-03-30 18:19:23 -07:00
|
|
|
from utils.color import interpolate_color
|
2018-03-31 15:11:35 -07:00
|
|
|
from utils.iterables import list_update
|
|
|
|
from utils.iterables import remove_list_redundancies
|
2018-03-30 18:19:23 -07:00
|
|
|
from utils.paths import straight_path
|
2018-03-31 15:11:35 -07:00
|
|
|
from utils.space_ops import angle_of_vector
|
|
|
|
from utils.space_ops import complex_to_R3
|
|
|
|
from utils.space_ops import rotation_matrix
|
2018-08-12 12:17:32 -07:00
|
|
|
from utils.simple_functions import get_num_args
|
2018-08-15 17:30:24 -07:00
|
|
|
from utils.space_ops import get_norm
|
2018-07-11 11:38:59 -07:00
|
|
|
from functools import reduce
|
2015-06-10 22:00:35 -07:00
|
|
|
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
# TODO: Explain array_attrs
|
2016-04-09 20:03:57 -07:00
|
|
|
|
2018-01-29 13:34:06 -08:00
|
|
|
class Mobject(Container):
|
2015-06-10 22:00:35 -07:00
|
|
|
"""
|
|
|
|
Mathematical Object
|
|
|
|
"""
|
2016-02-27 16:32:53 -08:00
|
|
|
CONFIG = {
|
2018-04-06 13:58:59 -07:00
|
|
|
"color": WHITE,
|
|
|
|
"name": None,
|
|
|
|
"dim": 3,
|
|
|
|
"target": None,
|
2015-09-28 16:25:18 -07:00
|
|
|
}
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2016-04-17 00:31:38 -07:00
|
|
|
def __init__(self, *submobjects, **kwargs):
|
2018-01-29 13:34:06 -08:00
|
|
|
Container.__init__(self, *submobjects, **kwargs)
|
2018-08-09 17:56:05 -07:00
|
|
|
if not all([isinstance(m, Mobject) for m in submobjects]):
|
2016-09-07 13:38:05 -07:00
|
|
|
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__
|
2018-08-12 12:17:32 -07:00
|
|
|
self.updaters = []
|
2018-08-20 15:49:31 -07:00
|
|
|
self.reset_points()
|
2015-09-24 10:54:59 -07:00
|
|
|
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
|
|
|
|
2018-08-20 15:49:31 -07:00
|
|
|
def reset_points(self):
|
2016-04-09 20:03:57 -07:00
|
|
|
self.points = np.zeros((0, self.dim))
|
|
|
|
|
|
|
|
def init_colors(self):
|
2018-04-06 13:58:59 -07:00
|
|
|
# For subclasses
|
2016-04-09 20:03:57 -07:00
|
|
|
pass
|
2015-06-10 22:00:35 -07:00
|
|
|
|
2016-02-12 14:50:35 -08:00
|
|
|
def generate_points(self):
|
2018-04-06 13:58:59 -07:00
|
|
|
# Typically implemented in subclass, unless purposefully left blank
|
2016-02-12 14:50:35 -08:00
|
|
|
pass
|
2015-06-10 22:00:35 -07:00
|
|
|
|
2015-08-17 11:12:56 -07:00
|
|
|
def add(self, *mobjects):
|
2017-09-28 07:40:57 -07:00
|
|
|
if self in mobjects:
|
|
|
|
raise Exception("Mobject cannot contain self")
|
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
|
|
|
"""
|
2018-08-09 17:56:05 -07:00
|
|
|
mobject_attrs = [x for x in list(self.__dict__.values()) if isinstance(x, Mobject)]
|
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
|
|
|
|
|
2018-08-12 12:17:32 -07:00
|
|
|
# Displaying
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
def get_image(self, camera=None):
|
2017-08-25 17:56:55 -07:00
|
|
|
if camera is None:
|
2018-03-30 18:19:23 -07:00
|
|
|
from camera.camera import Camera
|
2017-08-25 17:56:55 -07:00
|
|
|
camera = Camera()
|
2016-02-27 12:44:52 -08:00
|
|
|
camera.capture_mobject(self)
|
2017-09-26 17:41:45 -07:00
|
|
|
return camera.get_image()
|
2016-04-09 20:03:57 -07:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
def show(self, camera=None):
|
|
|
|
self.get_image(camera=camera).show()
|
2015-11-02 13:03:01 -08:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
def save_image(self, name=None):
|
2016-04-09 20:03:57 -07:00
|
|
|
self.get_image().save(
|
2018-01-12 13:38:25 -08:00
|
|
|
os.path.join(ANIMATIONS_DIR, (name or str(self)) + ".png")
|
2015-11-02 13:03:01 -08:00
|
|
|
)
|
|
|
|
|
2015-11-02 19:09:55 -08:00
|
|
|
def copy(self):
|
2018-04-06 13:58:59 -07:00
|
|
|
# TODO, either justify reason for shallow copy, or
|
|
|
|
# remove this redundancy everywhere
|
2018-04-16 14:17:34 -07:00
|
|
|
# return self.deepcopy()
|
2018-02-07 07:35:18 +01:00
|
|
|
|
2017-10-03 11:49:48 -07:00
|
|
|
copy_mobject = copy.copy(self)
|
|
|
|
copy_mobject.points = np.array(self.points)
|
|
|
|
copy_mobject.submobjects = [
|
|
|
|
submob.copy() for submob in self.submobjects
|
|
|
|
]
|
2018-08-21 19:15:16 -07:00
|
|
|
family = self.get_family()
|
2018-08-09 17:56:05 -07:00
|
|
|
for attr, value in list(self.__dict__.items()):
|
2017-10-03 11:49:48 -07:00
|
|
|
if isinstance(value, Mobject) and value in family and value is not self:
|
|
|
|
setattr(copy_mobject, attr, value.copy())
|
2018-08-11 16:53:37 -07:00
|
|
|
if isinstance(value, np.ndarray):
|
|
|
|
setattr(copy_mobject, attr, np.array(value))
|
2017-10-03 11:49:48 -07:00
|
|
|
return copy_mobject
|
2017-05-12 17:08:49 +02:00
|
|
|
|
2017-03-24 17:27:09 -07:00
|
|
|
def deepcopy(self):
|
|
|
|
return copy.deepcopy(self)
|
2015-11-02 13:03:01 -08:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
def generate_target(self, use_deepcopy=False):
|
|
|
|
self.target = None # Prevent exponential explosion
|
2017-10-05 10:07:28 -07:00
|
|
|
if use_deepcopy:
|
|
|
|
self.target = self.deepcopy()
|
|
|
|
else:
|
|
|
|
self.target = self.copy()
|
2016-09-16 14:06:44 -07:00
|
|
|
return self.target
|
|
|
|
|
2018-08-12 12:17:32 -07:00
|
|
|
# Updating
|
|
|
|
|
|
|
|
def update(self, dt):
|
|
|
|
for updater in self.updaters:
|
|
|
|
num_args = get_num_args(updater)
|
|
|
|
if num_args == 1:
|
|
|
|
updater(self)
|
|
|
|
elif num_args == 2:
|
|
|
|
updater(self, dt)
|
|
|
|
else:
|
|
|
|
raise Exception(
|
|
|
|
"Mobject updater expected 1 or 2 "
|
|
|
|
"arguments, %d given" % num_args
|
|
|
|
)
|
|
|
|
|
|
|
|
def get_time_based_updaters(self):
|
|
|
|
return [
|
2018-08-12 19:14:37 -07:00
|
|
|
updater
|
2018-08-12 12:17:32 -07:00
|
|
|
for updater in self.updaters
|
|
|
|
if get_num_args(updater) == 2
|
|
|
|
]
|
|
|
|
|
|
|
|
def get_updaters(self):
|
|
|
|
return self.updaters
|
|
|
|
|
|
|
|
def add_updater(self, update_function):
|
|
|
|
self.updaters.append(update_function)
|
|
|
|
|
|
|
|
def remove_updater(self, update_function):
|
|
|
|
while update_function in self.updaters:
|
|
|
|
self.updaters.remove(update_function)
|
|
|
|
return self
|
|
|
|
|
|
|
|
def clear_updaters(self):
|
|
|
|
self.updaters = []
|
|
|
|
|
2018-04-09 13:47:46 -07:00
|
|
|
# Transforming operations
|
2016-04-09 20:03:57 -07:00
|
|
|
|
|
|
|
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)
|
2017-05-12 17:08:49 +02:00
|
|
|
for mob in self.family_members_with_points():
|
2018-04-06 13:58:59 -07:00
|
|
|
mob.points = mob.points.astype('float')
|
|
|
|
mob.points += total_vector
|
2017-05-12 17:08:49 +02:00
|
|
|
return self
|
2016-04-09 20:03:57 -07:00
|
|
|
|
2018-01-20 10:56:52 -08:00
|
|
|
def scale(self, scale_factor, **kwargs):
|
2018-01-17 09:01:46 -08:00
|
|
|
"""
|
|
|
|
Default behavior is to scale about the center of the mobject.
|
|
|
|
The argument about_edge can be a vector, indicating which side of
|
2018-04-09 13:47:46 -07:00
|
|
|
the mobject to scale about, e.g., mob.scale(about_edge = RIGHT)
|
2018-01-17 09:01:46 -08:00
|
|
|
scales about mob.get_right().
|
|
|
|
|
|
|
|
Otherwise, if about_point is given a value, scaling is done with
|
|
|
|
respect to that point.
|
|
|
|
"""
|
2018-01-20 10:56:52 -08:00
|
|
|
self.apply_points_function_about_point(
|
2018-04-06 13:58:59 -07:00
|
|
|
lambda points: scale_factor * points, **kwargs
|
2018-01-20 10:56:52 -08:00
|
|
|
)
|
2015-11-02 13:03:01 -08:00
|
|
|
return self
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
def rotate_about_origin(self, angle, axis=OUT, axes=[]):
|
|
|
|
return self.rotate(angle, axis, about_point=ORIGIN)
|
2016-12-06 13:29:21 -08:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
def rotate(self, angle, axis=OUT, **kwargs):
|
2018-01-20 10:56:52 -08:00
|
|
|
rot_matrix = rotation_matrix(angle, axis)
|
|
|
|
self.apply_points_function_about_point(
|
2018-04-06 13:58:59 -07:00
|
|
|
lambda points: np.dot(points, rot_matrix.T),
|
2018-01-20 10:56:52 -08:00
|
|
|
**kwargs
|
|
|
|
)
|
2015-11-02 13:03:01 -08:00
|
|
|
return self
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
def flip(self, axis=UP, **kwargs):
|
|
|
|
return self.rotate(TAU / 2, axis, **kwargs)
|
2018-01-20 11:33:09 -08:00
|
|
|
|
2018-01-20 10:56:52 -08:00
|
|
|
def stretch(self, factor, dim, **kwargs):
|
|
|
|
def func(points):
|
2018-04-06 13:58:59 -07:00
|
|
|
points[:, dim] *= factor
|
2018-01-20 10:56:52 -08:00
|
|
|
return points
|
2018-01-20 11:18:43 -08:00
|
|
|
self.apply_points_function_about_point(func, **kwargs)
|
2015-11-02 13:03:01 -08:00
|
|
|
return self
|
|
|
|
|
2018-01-20 11:18:43 -08:00
|
|
|
def apply_function(self, function, **kwargs):
|
2018-04-06 13:58:59 -07:00
|
|
|
# Default to applying matrix about the origin, not mobjects center
|
2018-01-20 11:18:43 -08:00
|
|
|
if len(kwargs) == 0:
|
|
|
|
kwargs["about_point"] = ORIGIN
|
2018-01-20 10:56:52 -08:00
|
|
|
self.apply_points_function_about_point(
|
2018-04-06 13:58:59 -07:00
|
|
|
lambda points: np.apply_along_axis(function, 1, points),
|
2018-01-20 11:45:47 -08:00
|
|
|
**kwargs
|
2018-01-20 10:56:52 -08:00
|
|
|
)
|
2015-11-02 13:03:01 -08:00
|
|
|
return self
|
|
|
|
|
2018-05-21 12:11:46 -07:00
|
|
|
def apply_function_to_position(self, function):
|
|
|
|
self.move_to(function(self.get_center()))
|
|
|
|
return self
|
|
|
|
|
|
|
|
def apply_function_to_submobject_positions(self, function):
|
|
|
|
for submob in self.submobjects:
|
|
|
|
submob.apply_function_to_position(function)
|
|
|
|
return self
|
|
|
|
|
2018-01-20 11:18:43 -08:00
|
|
|
def apply_matrix(self, matrix, **kwargs):
|
2018-04-06 13:58:59 -07:00
|
|
|
# Default to applying matrix about the origin, not mobjects center
|
2018-01-20 11:18:43 -08:00
|
|
|
if len(kwargs) == 0:
|
|
|
|
kwargs["about_point"] = ORIGIN
|
|
|
|
full_matrix = np.identity(self.dim)
|
|
|
|
matrix = np.array(matrix)
|
2018-04-06 13:58:59 -07:00
|
|
|
full_matrix[:matrix.shape[0], :matrix.shape[1]] = matrix
|
2018-01-20 10:56:52 -08:00
|
|
|
self.apply_points_function_about_point(
|
2018-04-06 13:58:59 -07:00
|
|
|
lambda points: np.dot(points, full_matrix.T),
|
2018-01-20 11:18:43 -08:00
|
|
|
**kwargs
|
2018-01-20 10:56:52 -08:00
|
|
|
)
|
2017-08-24 19:06:02 -07:00
|
|
|
return self
|
|
|
|
|
2018-01-20 11:33:09 -08:00
|
|
|
def apply_complex_function(self, function, **kwargs):
|
|
|
|
return self.apply_function(
|
2018-07-11 11:38:59 -07:00
|
|
|
lambda x_y_z: complex_to_R3(function(complex(x_y_z[0], x_y_z[1]))),
|
2018-01-20 11:33:09 -08:00
|
|
|
**kwargs
|
|
|
|
)
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
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(
|
2018-04-06 13:58:59 -07:00
|
|
|
lambda arr: np.array(list(reversed(arr)))
|
2016-01-04 10:15:57 -08:00
|
|
|
)
|
|
|
|
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(
|
2018-04-06 13:58:59 -07:00
|
|
|
lambda a1, a2: np.append(a1, a2, axis=0),
|
|
|
|
[array] * count
|
2015-11-02 13:03:01 -08:00
|
|
|
)
|
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
|
|
|
|
|
2018-04-09 13:47:46 -07:00
|
|
|
# In place operations.
|
2018-04-06 13:58:59 -07:00
|
|
|
# Note, much of these are now redundant with default behavior of
|
|
|
|
# above methods
|
2015-11-02 13:03:01 -08:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
def apply_points_function_about_point(self, func, about_point=None, about_edge=ORIGIN):
|
2018-01-20 10:56:52 -08:00
|
|
|
if about_point is None:
|
2018-04-10 19:47:04 -07:00
|
|
|
assert(about_edge is not None)
|
2018-01-20 10:56:52 -08:00
|
|
|
about_point = self.get_critical_point(about_edge)
|
|
|
|
for mob in self.family_members_with_points():
|
|
|
|
mob.points -= about_point
|
|
|
|
mob.points = func(mob.points)
|
|
|
|
mob.points += about_point
|
|
|
|
return self
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
def rotate_in_place(self, angle, axis=OUT):
|
2018-01-20 10:56:52 -08:00
|
|
|
# redundant with default behavior of rotate now.
|
2018-04-06 13:58:59 -07:00
|
|
|
return self.rotate(angle, axis=axis)
|
2015-06-10 22:00:35 -07:00
|
|
|
|
2018-01-20 11:33:09 -08:00
|
|
|
def scale_in_place(self, scale_factor, **kwargs):
|
2018-04-06 13:58:59 -07:00
|
|
|
# Redundant with default behavior of scale now.
|
2018-01-20 11:33:09 -08:00
|
|
|
return self.scale(scale_factor, **kwargs)
|
2015-06-10 22:00:35 -07:00
|
|
|
|
2017-02-27 22:03:33 -08:00
|
|
|
def scale_about_point(self, scale_factor, point):
|
2018-04-06 13:58:59 -07:00
|
|
|
# Redundant with default behavior of scale now.
|
|
|
|
return self.scale(scale_factor, about_point=point)
|
2017-02-27 22:03:33 -08:00
|
|
|
|
2018-01-20 11:33:09 -08:00
|
|
|
def pose_at_angle(self, **kwargs):
|
2018-04-06 13:58:59 -07:00
|
|
|
self.rotate(TAU / 14, RIGHT + UP, **kwargs)
|
2015-06-13 19:00:23 -07:00
|
|
|
return self
|
|
|
|
|
2018-04-09 13:47:46 -07:00
|
|
|
# Positioning methods
|
2018-01-20 11:33:09 -08:00
|
|
|
|
2015-06-10 22:00:35 -07:00
|
|
|
def center(self):
|
|
|
|
self.shift(-self.get_center())
|
|
|
|
return self
|
|
|
|
|
2018-04-06 13:58:59 -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.
|
|
|
|
"""
|
2018-03-30 11:25:37 -07:00
|
|
|
target_point = np.sign(direction) * (FRAME_X_RADIUS, FRAME_Y_RADIUS, 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
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
def to_corner(self, corner=LEFT + DOWN, buff=DEFAULT_MOBJECT_TO_EDGE_BUFFER):
|
2015-11-02 13:03:01 -08:00
|
|
|
return self.align_on_border(corner, buff)
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
def to_edge(self, edge=LEFT, buff=DEFAULT_MOBJECT_TO_EDGE_BUFFER):
|
2015-11-02 13:03:01 -08:00
|
|
|
return self.align_on_border(edge, buff)
|
|
|
|
|
2017-05-12 17:08:49 +02:00
|
|
|
def next_to(self, mobject_or_point,
|
2018-04-06 13:58:59 -07:00
|
|
|
direction=RIGHT,
|
|
|
|
buff=DEFAULT_MOBJECT_TO_MOBJECT_BUFFER,
|
|
|
|
aligned_edge=ORIGIN,
|
|
|
|
submobject_to_align=None,
|
|
|
|
index_of_submobject_to_align=None,
|
|
|
|
coor_mask=np.array([1, 1, 1]),
|
2016-08-20 20:23:01 -07:00
|
|
|
):
|
2016-07-25 16:04:54 -07:00
|
|
|
if isinstance(mobject_or_point, Mobject):
|
|
|
|
mob = mobject_or_point
|
2018-01-16 18:40:26 -08:00
|
|
|
if index_of_submobject_to_align is not None:
|
|
|
|
target_aligner = mob[index_of_submobject_to_align]
|
|
|
|
else:
|
|
|
|
target_aligner = mob
|
|
|
|
target_point = target_aligner.get_critical_point(
|
|
|
|
aligned_edge + direction
|
2016-08-20 20:23:01 -07:00
|
|
|
)
|
2016-07-25 16:04:54 -07:00
|
|
|
else:
|
|
|
|
target_point = mobject_or_point
|
2018-01-16 22:38:00 -08:00
|
|
|
if submobject_to_align is not None:
|
2018-01-16 18:40:26 -08:00
|
|
|
aligner = submobject_to_align
|
|
|
|
elif index_of_submobject_to_align is not None:
|
|
|
|
aligner = self[index_of_submobject_to_align]
|
|
|
|
else:
|
|
|
|
aligner = self
|
2018-01-16 22:38:00 -08:00
|
|
|
point_to_align = aligner.get_critical_point(aligned_edge - direction)
|
2018-04-06 13:58:59 -07:00
|
|
|
self.shift((target_point - point_to_align +
|
|
|
|
buff * direction) * coor_mask)
|
2015-09-24 10:54:59 -07:00
|
|
|
return self
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
def align_to(self, mobject_or_point, direction=ORIGIN, alignment_vect=UP):
|
2018-01-23 13:41:14 -08:00
|
|
|
"""
|
2018-04-09 13:47:46 -07:00
|
|
|
Examples:
|
2018-01-23 13:41:14 -08:00
|
|
|
mob1.align_to(mob2, UP) moves mob1 vertically so that its
|
|
|
|
top edge lines ups with mob2's top edge.
|
|
|
|
|
|
|
|
mob1.align_to(mob2, alignment_vector = RIGHT) moves mob1
|
|
|
|
horizontally so that it's center is directly above/below
|
|
|
|
the center of mob2
|
|
|
|
"""
|
2017-08-09 10:47:40 -07:00
|
|
|
if isinstance(mobject_or_point, Mobject):
|
|
|
|
mob = mobject_or_point
|
2018-01-23 13:41:14 -08:00
|
|
|
target_point = mob.get_critical_point(direction)
|
|
|
|
else:
|
|
|
|
target_point = mobject_or_point
|
2018-08-15 17:30:24 -07:00
|
|
|
direction_norm = get_norm(direction)
|
2018-01-23 13:41:14 -08:00
|
|
|
if direction_norm > 0:
|
2018-04-06 13:58:59 -07:00
|
|
|
alignment_vect = np.array(direction) / direction_norm
|
2018-01-23 13:41:14 -08:00
|
|
|
reference_point = self.get_critical_point(direction)
|
2017-08-09 10:47:40 -07:00
|
|
|
else:
|
2018-01-23 13:41:14 -08:00
|
|
|
reference_point = self.get_center()
|
|
|
|
diff = target_point - reference_point
|
2018-04-06 13:58:59 -07:00
|
|
|
self.shift(alignment_vect * np.dot(diff, alignment_vect))
|
2017-08-09 10:47:40 -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):
|
2018-03-30 11:25:37 -07:00
|
|
|
space_lengths = [FRAME_X_RADIUS, FRAME_Y_RADIUS]
|
2016-07-19 11:08:31 -07:00
|
|
|
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):
|
2018-03-30 11:25:37 -07:00
|
|
|
if self.get_left()[0] > FRAME_X_RADIUS:
|
2017-01-16 13:26:46 -08:00
|
|
|
return True
|
2018-03-30 11:25:37 -07:00
|
|
|
if self.get_right()[0] < -FRAME_X_RADIUS:
|
2017-01-16 13:26:46 -08:00
|
|
|
return True
|
2018-03-30 11:25:37 -07:00
|
|
|
if self.get_bottom()[1] > FRAME_Y_RADIUS:
|
2017-01-16 13:26:46 -08:00
|
|
|
return True
|
2018-03-30 11:25:37 -07:00
|
|
|
if self.get_top()[1] < -FRAME_Y_RADIUS:
|
2017-01-16 13:26:46 -08:00
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
2017-02-27 22:03:33 -08:00
|
|
|
def stretch_about_point(self, factor, dim, point):
|
2018-04-06 13:58:59 -07:00
|
|
|
return self.stretch(factor, dim, about_point=point)
|
2017-02-27 22:03:33 -08:00
|
|
|
|
2016-09-24 22:46:17 -07:00
|
|
|
def stretch_in_place(self, factor, dim):
|
2018-04-06 13:58:59 -07:00
|
|
|
# Now redundant with stretch
|
2018-01-20 11:33:09 -08:00
|
|
|
return self.stretch(factor, dim)
|
2016-09-24 22:46:17 -07:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
def rescale_to_fit(self, length, dim, stretch=False, **kwargs):
|
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:
|
2018-04-06 13:58:59 -07:00
|
|
|
self.stretch(length / old_length, dim, **kwargs)
|
2016-07-15 18:16:06 -07:00
|
|
|
else:
|
2018-04-06 13:58:59 -07:00
|
|
|
self.scale(length / old_length, **kwargs)
|
2015-08-01 11:34:33 -07:00
|
|
|
return self
|
|
|
|
|
2018-01-20 11:33:09 -08:00
|
|
|
def stretch_to_fit_width(self, width, **kwargs):
|
2018-04-06 13:58:59 -07:00
|
|
|
return self.rescale_to_fit(width, 0, stretch=True, **kwargs)
|
2015-08-01 11:34:33 -07:00
|
|
|
|
2018-01-20 11:33:09 -08:00
|
|
|
def stretch_to_fit_height(self, height, **kwargs):
|
2018-04-06 13:58:59 -07:00
|
|
|
return self.rescale_to_fit(height, 1, stretch=True, **kwargs)
|
2015-08-01 11:34:33 -07:00
|
|
|
|
2018-02-27 13:55:38 -08:00
|
|
|
def stretch_to_fit_depth(self, depth, **kwargs):
|
2018-04-06 13:58:59 -07:00
|
|
|
return self.rescale_to_fit(depth, 1, stretch=True, **kwargs)
|
2018-02-27 13:55:38 -08:00
|
|
|
|
2018-08-08 10:30:52 -07:00
|
|
|
def set_width(self, width, stretch=False, **kwargs):
|
|
|
|
return self.rescale_to_fit(width, 0, stretch=stretch, **kwargs)
|
2015-10-20 21:55:46 -07:00
|
|
|
|
2018-08-08 10:30:52 -07:00
|
|
|
def set_height(self, height, stretch=False, **kwargs):
|
|
|
|
return self.rescale_to_fit(height, 1, stretch=stretch, **kwargs)
|
2016-07-15 18:16:06 -07:00
|
|
|
|
2018-08-08 10:30:52 -07:00
|
|
|
def set_depth(self, depth, stretch=False, **kwargs):
|
|
|
|
return self.rescale_to_fit(depth, 2, stretch=stretch, **kwargs)
|
2017-09-06 20:18:19 -07:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
def space_out_submobjects(self, factor=1.5, **kwargs):
|
2018-01-20 11:33:09 -08:00
|
|
|
self.scale(factor, **kwargs)
|
2017-01-26 19:59:55 -08:00
|
|
|
for submob in self.submobjects:
|
2018-04-06 13:58:59 -07:00
|
|
|
submob.scale(1. / factor)
|
2017-01-26 19:59:55 -08:00
|
|
|
return self
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
def move_to(self, point_or_mobject, aligned_edge=ORIGIN,
|
|
|
|
coor_mask=np.array([1, 1, 1])):
|
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)
|
2018-04-06 13:58:59 -07:00
|
|
|
self.shift((target - point_to_align) * coor_mask)
|
2016-07-15 18:16:06 -07:00
|
|
|
return self
|
2015-10-20 21:55:46 -07:00
|
|
|
|
2018-04-06 13:58:59 -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),
|
2017-05-12 17:08:49 +02:00
|
|
|
dim_to_match,
|
2018-04-06 13:58:59 -07:00
|
|
|
stretch=False
|
2016-08-13 18:27:02 -07:00
|
|
|
)
|
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
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
def surround(self, mobject, dim_to_match=0, stretch=False, buffer_factor=1.2):
|
2018-01-10 17:57:22 -08:00
|
|
|
self.replace(mobject, dim_to_match, stretch)
|
|
|
|
self.scale_in_place(buffer_factor)
|
|
|
|
|
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
|
2018-08-15 17:30:24 -07:00
|
|
|
self.scale(get_norm(target_vect) / get_norm(curr_vect))
|
2016-02-21 15:44:54 -08:00
|
|
|
self.rotate(
|
2018-04-06 13:58:59 -07:00
|
|
|
angle_of_vector(target_vect) -
|
2016-02-21 15:44:54 -08:00
|
|
|
angle_of_vector(curr_vect)
|
|
|
|
)
|
2018-04-06 13:58:59 -07:00
|
|
|
self.shift(start - self.points[0])
|
2016-02-21 15:44:54 -08:00
|
|
|
return self
|
|
|
|
|
2018-05-07 13:32:47 -07:00
|
|
|
# Background rectangle
|
|
|
|
def add_background_rectangle(self, color=BLACK, opacity=0.75, **kwargs):
|
2018-06-08 17:16:11 -07:00
|
|
|
# TODO, this does not behave well when the mobject has points,
|
2018-05-09 18:22:30 +02:00
|
|
|
# since it gets displayed on top
|
2018-05-07 13:32:47 -07:00
|
|
|
from mobject.shape_matchers import BackgroundRectangle
|
|
|
|
self.background_rectangle = BackgroundRectangle(
|
|
|
|
self, color=color,
|
|
|
|
fill_opacity=opacity,
|
|
|
|
**kwargs
|
|
|
|
)
|
|
|
|
self.add_to_back(self.background_rectangle)
|
|
|
|
return self
|
|
|
|
|
|
|
|
def add_background_rectangle_to_submobjects(self, **kwargs):
|
|
|
|
for submobject in self.submobjects:
|
|
|
|
submobject.add_background_rectangle(**kwargs)
|
|
|
|
return self
|
|
|
|
|
|
|
|
def add_background_rectangle_to_family_members_with_points(self, **kwargs):
|
|
|
|
for mob in self.family_members_with_points():
|
|
|
|
mob.add_background_rectangle(**kwargs)
|
|
|
|
return self
|
|
|
|
|
2018-06-13 17:12:18 -07:00
|
|
|
# Match other mobject properties
|
2018-01-20 19:31:09 -08:00
|
|
|
|
|
|
|
def match_color(self, mobject):
|
2018-03-30 11:51:31 -07:00
|
|
|
return self.set_color(mobject.get_color())
|
2018-01-20 19:31:09 -08:00
|
|
|
|
|
|
|
def match_dim(self, mobject, dim, **kwargs):
|
|
|
|
return self.rescale_to_fit(
|
|
|
|
mobject.length_over_dim(dim), dim,
|
|
|
|
**kwargs
|
|
|
|
)
|
|
|
|
|
2018-01-25 23:50:52 -08:00
|
|
|
def match_width(self, mobject, **kwargs):
|
|
|
|
return self.match_dim(mobject, 0, **kwargs)
|
2018-01-20 19:31:09 -08:00
|
|
|
|
2018-01-25 23:50:52 -08:00
|
|
|
def match_height(self, mobject, **kwargs):
|
|
|
|
return self.match_dim(mobject, 1, **kwargs)
|
2018-01-20 19:31:09 -08:00
|
|
|
|
2018-01-25 23:50:52 -08:00
|
|
|
def match_depth(self, mobject, **kwargs):
|
|
|
|
return self.match_dim(mobject, 2, **kwargs)
|
2018-01-20 19:31:09 -08:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
# Color functions
|
2016-02-21 15:44:54 -08:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
def set_color(self, color=YELLOW_C, family=True):
|
2016-04-09 20:03:57 -07:00
|
|
|
"""
|
|
|
|
Condition is function which takes in one arguments, (x, y, z).
|
2018-04-09 13:47:46 -07:00
|
|
|
Here it just recurses to submobjects, but in subclasses this
|
2017-10-26 21:30:59 -07:00
|
|
|
should be further implemented based on the the inner workings
|
|
|
|
of color
|
2016-04-09 20:03:57 -07:00
|
|
|
"""
|
2017-10-26 21:30:59 -07:00
|
|
|
if family:
|
|
|
|
for submob in self.submobjects:
|
2018-04-06 13:58:59 -07:00
|
|
|
submob.set_color(color, family=family)
|
2018-03-30 11:51:31 -07:00
|
|
|
self.color = color
|
2017-10-26 21:30:59 -07:00
|
|
|
return self
|
2016-04-09 20:03:57 -07:00
|
|
|
|
2018-03-30 11:59:39 -07:00
|
|
|
def set_color_by_gradient(self, *colors):
|
2018-03-30 12:03:52 -07:00
|
|
|
self.set_submobject_colors_by_gradient(*colors)
|
2016-08-19 15:54:16 -07:00
|
|
|
return self
|
2016-07-22 11:22:31 -07:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
def set_colors_by_radial_gradient(self, center=None, radius=1, inner_color=WHITE, outer_color=BLACK):
|
|
|
|
self.set_submobject_colors_by_radial_gradient(
|
|
|
|
center, radius, inner_color, outer_color)
|
2018-01-17 15:18:02 -08:00
|
|
|
return self
|
|
|
|
|
2018-03-30 12:03:52 -07:00
|
|
|
def set_submobject_colors_by_gradient(self, *colors):
|
2016-08-09 14:07:23 -07:00
|
|
|
if len(colors) == 0:
|
|
|
|
raise Exception("Need at least one color")
|
|
|
|
elif len(colors) == 1:
|
2018-03-30 11:51:31 -07:00
|
|
|
return self.set_color(*colors)
|
2016-08-09 14:07:23 -07:00
|
|
|
|
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))
|
2018-01-17 15:18:02 -08:00
|
|
|
|
2016-08-09 14:07:23 -07:00
|
|
|
for mob, color in zip(mobs, new_colors):
|
2018-04-06 13:58:59 -07:00
|
|
|
mob.set_color(color, family=False)
|
2016-07-22 11:22:31 -07:00
|
|
|
return self
|
2015-06-10 22:00:35 -07:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
def set_submobject_colors_by_radial_gradient(self, center=None, radius=1, inner_color=WHITE, outer_color=BLACK):
|
2018-04-09 13:47:46 -07:00
|
|
|
if center is None:
|
2018-01-17 15:18:02 -08:00
|
|
|
center = self.get_center()
|
|
|
|
|
|
|
|
for mob in self.family_members_with_points():
|
2018-08-15 17:30:24 -07:00
|
|
|
t = get_norm(mob.get_center() - center) / radius
|
2018-04-06 13:58:59 -07:00
|
|
|
t = min(t, 1)
|
2018-01-17 15:18:02 -08:00
|
|
|
mob_color = interpolate_color(inner_color, outer_color, t)
|
2018-04-06 13:58:59 -07:00
|
|
|
mob.set_color(mob_color, family=False)
|
2018-01-17 15:18:02 -08:00
|
|
|
|
|
|
|
return self
|
|
|
|
|
2015-09-25 19:43:53 -07:00
|
|
|
def to_original_color(self):
|
2018-03-30 11:51:31 -07:00
|
|
|
self.set_color(self.color)
|
2015-09-25 19:43:53 -07:00
|
|
|
return self
|
|
|
|
|
2018-03-01 15:34:46 -08:00
|
|
|
# Some objects (e.g., VMobjects) have special fading
|
|
|
|
# behavior. We let every object handle its individual
|
2018-04-06 13:58:59 -07:00
|
|
|
# fading via fade_no_recurse (notionally a purely internal method),
|
2018-03-01 15:34:46 -08:00
|
|
|
# and then have fade() itself call this recursively on each submobject
|
|
|
|
#
|
|
|
|
# Similarly for fade_to_no_recurse and fade_to, the underlying functions
|
|
|
|
# used by default for fade()ing
|
|
|
|
|
|
|
|
def fade_to_no_recurse(self, color, alpha):
|
|
|
|
if self.get_num_points() > 0:
|
|
|
|
start = color_to_rgb(self.get_color())
|
2016-07-22 11:22:31 -07:00
|
|
|
end = color_to_rgb(color)
|
|
|
|
new_rgb = interpolate(start, end, alpha)
|
2018-04-06 13:58:59 -07:00
|
|
|
self.set_color(Color(rgb=new_rgb), family=False)
|
2018-03-01 15:34:46 -08:00
|
|
|
return self
|
|
|
|
|
|
|
|
def fade_to(self, color, alpha):
|
2018-08-21 19:15:16 -07:00
|
|
|
for mob in self.get_family():
|
2018-03-01 15:34:46 -08:00
|
|
|
mob.fade_to_no_recurse(self, color, alpha)
|
|
|
|
return self
|
|
|
|
|
|
|
|
def fade_no_recurse(self, darkness):
|
|
|
|
self.fade_to_no_recurse(BLACK, darkness)
|
2015-09-25 19:43:53 -07:00
|
|
|
return self
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
def fade(self, darkness=0.5):
|
2018-08-21 19:15:16 -07:00
|
|
|
for submob in self.get_family():
|
2018-03-01 15:34:46 -08:00
|
|
|
submob.fade_no_recurse(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
|
2018-08-12 12:17:32 -07:00
|
|
|
|
2016-04-09 20:03:57 -07:00
|
|
|
##
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
def save_state(self, use_deepcopy=False):
|
2016-11-15 20:08:28 -08:00
|
|
|
if hasattr(self, "saved_state"):
|
2018-04-06 13:58:59 -07:00
|
|
|
# Prevent exponential growth of data
|
2016-11-15 20:08:28 -08:00
|
|
|
self.saved_state = None
|
2017-10-12 17:38:25 -07:00
|
|
|
if use_deepcopy:
|
|
|
|
self.saved_state = self.deepcopy()
|
|
|
|
else:
|
|
|
|
self.saved_state = self.copy()
|
2016-08-09 14:07:23 -07:00
|
|
|
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)
|
2018-08-21 19:15:16 -07:00
|
|
|
for sm1, sm2 in zip(self.get_family(), self.saved_state.get_family()):
|
2017-02-27 15:56:22 -08:00
|
|
|
sm1.interpolate(sm1, sm2, 1)
|
2016-08-09 14:07:23 -07:00
|
|
|
return self
|
2016-04-09 20:03:57 -07:00
|
|
|
|
2018-01-20 11:33:09 -08:00
|
|
|
##
|
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):
|
2018-05-09 14:20:23 -07:00
|
|
|
points = self.get_all_points()
|
2018-05-09 14:28:26 -07:00
|
|
|
if points is None or len(points) == 0:
|
2018-05-09 14:20:23 -07:00
|
|
|
# Note, this default means things like empty VGroups
|
|
|
|
# will appear to have a center at [0, 0, 0]
|
2015-11-02 13:03:01 -08:00
|
|
|
return 0
|
2018-05-09 14:20:23 -07:00
|
|
|
values = points_func(points[:, dim])
|
|
|
|
return reduce_func(values)
|
2015-11-02 13:03:01 -08:00
|
|
|
|
2018-05-09 18:22:30 +02:00
|
|
|
def nonempty_submobjects(self):
|
2018-05-09 14:20:23 -07:00
|
|
|
return [
|
|
|
|
submob for submob in self.submobjects
|
|
|
|
if len(submob.submobjects) != 0 or len(submob.points) != 0
|
|
|
|
]
|
2018-05-09 18:22:30 +02:00
|
|
|
|
2015-11-02 13:03:01 -08:00
|
|
|
def get_merged_array(self, array_attr):
|
2018-08-15 16:23:29 -07:00
|
|
|
result = getattr(self, array_attr)
|
|
|
|
for submob in self.submobjects:
|
|
|
|
result = np.append(
|
|
|
|
result, submob.get_merged_array(array_attr),
|
|
|
|
axis=0
|
|
|
|
)
|
|
|
|
submob.get_merged_array(array_attr)
|
2015-11-09 10:34:00 -08:00
|
|
|
return result
|
2015-11-02 13:03:01 -08:00
|
|
|
|
|
|
|
def get_all_points(self):
|
|
|
|
return self.get_merged_array("points")
|
|
|
|
|
2018-04-09 13:47:46 -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
|
|
|
|
2018-01-16 18:40:26 -08:00
|
|
|
def get_critical_point(self, direction):
|
2016-04-10 12:34:28 -07:00
|
|
|
result = np.zeros(self.dim)
|
2018-08-15 17:30:24 -07:00
|
|
|
all_points = self.get_all_points()
|
2018-08-22 14:48:42 -07:00
|
|
|
if len(all_points) == 0:
|
|
|
|
return result
|
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:
|
2018-08-16 22:25:51 -07:00
|
|
|
min_val = min(all_points[:, dim])
|
2015-11-02 13:03:01 -08:00
|
|
|
if direction[dim] >= 0:
|
2018-08-16 22:25:51 -07:00
|
|
|
max_val = max(all_points[:, dim])
|
2015-11-02 13:03:01 -08:00
|
|
|
|
|
|
|
if direction[dim] == 0:
|
2018-08-15 17:30:24 -07:00
|
|
|
result[dim] = (max_val + min_val) / 2
|
2015-11-02 13:03:01 -08:00
|
|
|
elif direction[dim] < 0:
|
2018-08-15 17:30:24 -07:00
|
|
|
result[dim] = min_val
|
2015-11-02 13:03:01 -08:00
|
|
|
else:
|
2018-08-15 17:30:24 -07:00
|
|
|
result[dim] = max_val
|
2015-11-02 13:03:01 -08:00
|
|
|
return result
|
|
|
|
|
|
|
|
# Pseudonyms for more general get_critical_point method
|
2018-05-07 13:32:47 -07:00
|
|
|
|
2015-11-02 13:03:01 -08:00
|
|
|
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):
|
2018-08-11 18:42:48 -07:00
|
|
|
all_points = self.get_points_defining_boundary()
|
2015-11-02 13:03:01 -08:00
|
|
|
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
|
|
|
|
2017-08-27 14:43:18 -07:00
|
|
|
def get_zenith(self):
|
|
|
|
return self.get_edge_center(OUT)
|
|
|
|
|
|
|
|
def get_nadir(self):
|
|
|
|
return self.get_edge_center(IN)
|
|
|
|
|
2015-11-02 13:03:01 -08:00
|
|
|
def length_over_dim(self, dim):
|
|
|
|
return (
|
2017-05-12 17:08:49 +02:00
|
|
|
self.reduce_across_dimension(np.max, np.max, dim) -
|
2015-11-02 13:03:01 -08:00
|
|
|
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)
|
|
|
|
|
2017-08-27 14:43:18 -07:00
|
|
|
def get_depth(self):
|
|
|
|
return self.length_over_dim(2)
|
|
|
|
|
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
|
|
|
|
2018-08-20 15:49:31 -07:00
|
|
|
def get_pieces(self, n_pieces):
|
|
|
|
template = self.copy()
|
|
|
|
template.submobjects = []
|
|
|
|
alphas = np.linspace(0, 1, n_pieces + 1)
|
|
|
|
return Group(*[
|
|
|
|
template.copy().pointwise_become_partial(
|
|
|
|
self, a1, a2
|
|
|
|
)
|
|
|
|
for a1, a2 in zip(alphas[:-1], alphas[1:])
|
|
|
|
])
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
# Family matters
|
2016-08-10 10:26:07 -07:00
|
|
|
|
2018-01-24 11:25:55 -08:00
|
|
|
def __getitem__(self, value):
|
|
|
|
self_list = self.split()
|
|
|
|
if isinstance(value, slice):
|
|
|
|
GroupClass = self.get_group_class()
|
|
|
|
return GroupClass(*self_list.__getitem__(value))
|
|
|
|
return self_list.__getitem__(value)
|
2016-08-10 10:26:07 -07:00
|
|
|
|
|
|
|
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())
|
|
|
|
|
2018-01-24 11:25:55 -08:00
|
|
|
def get_group_class(self):
|
|
|
|
return Group
|
|
|
|
|
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
|
|
|
|
2018-08-21 19:15:16 -07:00
|
|
|
def get_family(self):
|
2018-08-21 19:16:51 -07:00
|
|
|
sub_families = list(map(Mobject.get_family, self.submobjects))
|
2018-05-08 13:23:24 +02: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):
|
2018-08-21 19:15:16 -07:00
|
|
|
return [m for m in self.get_family() if m.get_num_points() > 0]
|
2016-05-25 20:23:06 -07:00
|
|
|
|
2018-04-06 13:58:59 -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
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
def arrange_submobjects_in_grid(self, n_rows=None, n_cols=None, **kwargs):
|
2017-08-27 14:43:18 -07:00
|
|
|
submobs = self.submobjects
|
|
|
|
if n_rows is None and n_cols is None:
|
|
|
|
n_cols = int(np.sqrt(len(submobs)))
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2017-08-27 14:43:18 -07:00
|
|
|
if n_rows is not None:
|
|
|
|
v1 = RIGHT
|
|
|
|
v2 = DOWN
|
2018-08-15 16:23:29 -07:00
|
|
|
n = len(submobs) // n_rows
|
2017-08-27 14:43:18 -07:00
|
|
|
elif n_cols is not None:
|
|
|
|
v1 = DOWN
|
|
|
|
v2 = RIGHT
|
2018-08-15 16:23:29 -07:00
|
|
|
n = len(submobs) // n_cols
|
2017-08-27 14:43:18 -07:00
|
|
|
Group(*[
|
2018-04-06 13:58:59 -07:00
|
|
|
Group(*submobs[i:i + n]).arrange_submobjects(v1, **kwargs)
|
2017-08-27 14:43:18 -07:00
|
|
|
for i in range(0, len(submobs), n)
|
|
|
|
]).arrange_submobjects(v2, **kwargs)
|
|
|
|
return self
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
def sort_submobjects(self, point_to_num_func=lambda p: p[0]):
|
2017-02-08 13:29:32 -08:00
|
|
|
self.submobjects.sort(
|
2018-08-15 16:23:29 -07:00
|
|
|
key=lambda m: point_to_num_func(m.get_center())
|
2017-02-08 13:29:32 -08:00
|
|
|
)
|
|
|
|
return self
|
|
|
|
|
2018-06-18 13:27:42 -07:00
|
|
|
def shuffle_submobjects(self, recursive=False):
|
|
|
|
if recursive:
|
|
|
|
for submob in self.submobjects:
|
|
|
|
submob.shuffle_submobjects(recursive=True)
|
|
|
|
random.shuffle(self.submobjects)
|
|
|
|
|
2018-08-21 19:15:16 -07:00
|
|
|
def print_get_family(self, n_tabs=0):
|
2018-03-30 18:19:23 -07:00
|
|
|
"""For debugging purposes"""
|
2018-07-11 11:38:59 -07:00
|
|
|
print("\t" * n_tabs, self, id(self))
|
2018-03-30 18:19:23 -07:00
|
|
|
for submob in self.submobjects:
|
2018-08-21 19:15:16 -07:00
|
|
|
submob.print_get_family(n_tabs + 1)
|
2018-03-30 18:19:23 -07:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
# Alignment
|
2016-04-10 12:34:28 -07:00
|
|
|
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)
|
2018-04-06 13:58:59 -07: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
|
|
|
|
2018-04-06 13:58:59 -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")
|
|
|
|
|
|
|
|
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):
|
2018-04-06 13:58:59 -07:00
|
|
|
# If one is empty, and the other is not,
|
|
|
|
# push it into its submobject list
|
2016-04-14 19:30:47 -07:00
|
|
|
self_has_points, mob_has_points = [
|
|
|
|
mob.get_num_points() > 0
|
2018-07-11 11:38:59 -07:00
|
|
|
for mob in (self, mobject)
|
2016-04-14 19:30:47 -07:00
|
|
|
]
|
|
|
|
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)
|
2018-04-06 13:58:59 -07:00
|
|
|
diff = self_count - mob_count
|
2018-02-07 16:21:11 -08:00
|
|
|
if diff < 0:
|
|
|
|
self.add_n_more_submobjects(-diff)
|
|
|
|
elif diff > 0:
|
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):
|
|
|
|
"""
|
2017-05-12 17:08:49 +02:00
|
|
|
If self has no points, but needs to align
|
2016-04-23 23:36:05 -07:00
|
|
|
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 = []
|
2018-08-20 15:49:31 -07:00
|
|
|
self.reset_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
|
2018-08-10 15:12:49 -07:00
|
|
|
indices = curr * np.arange(curr + n) // (curr + n)
|
2016-04-17 12:59:53 -07:00
|
|
|
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()
|
|
|
|
|
2017-05-12 17:08:49 +02:00
|
|
|
def interpolate(self, mobject1, mobject2,
|
2018-04-06 13:58:59 -07:00
|
|
|
alpha, path_func=straight_path):
|
2015-06-10 22:00:35 -07:00
|
|
|
"""
|
2017-05-12 17:08:49 +02: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):
|
2018-04-06 13:58:59 -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
|
2017-05-12 17:08:49 +02:00
|
|
|
part of mobject.
|
2016-04-11 21:18:52 -07:00
|
|
|
Inputs 0 <= a < b <= 1 determine what portion
|
|
|
|
of mobject to become.
|
|
|
|
"""
|
2018-04-06 13:58:59 -07:00
|
|
|
pass # To implement in subclasses
|
2016-09-16 14:06:44 -07:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
# TODO, color?
|
2016-07-19 11:08:31 -07:00
|
|
|
|
|
|
|
def pointwise_become_partial(self, mobject, a, b):
|
2018-04-06 13:58:59 -07:00
|
|
|
pass # To implement in subclass
|
|
|
|
|
2018-08-09 11:09:24 -07:00
|
|
|
def become(self, mobject):
|
|
|
|
"""
|
|
|
|
Edit points, colors and submobjects to be idential
|
|
|
|
to another mobject
|
|
|
|
"""
|
|
|
|
self.align_points(mobject)
|
|
|
|
self.interpolate(self, mobject, 1)
|
|
|
|
self.submobjects = [sm.copy() for sm in mobject.submobjects]
|
|
|
|
|
2015-10-28 16:03:33 -07:00
|
|
|
|
2016-09-07 13:38:05 -07:00
|
|
|
class Group(Mobject):
|
2018-04-06 13:58:59 -07:00
|
|
|
# Alternate name to improve readibility in cases where
|
|
|
|
# the mobject is used primarily for its submobject housing
|
|
|
|
# functionality.
|
2017-05-12 17:08:49 +02:00
|
|
|
pass
|