3b1b-manim/mobject/mobject.py

890 lines
29 KiB
Python
Raw Normal View History

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
2015-06-10 22:00:35 -07:00
from colour import Color
from constants import *
from container.container import Container
from utils.bezier import interpolate
from utils.color import color_gradient
from utils.color import color_to_rgb
from utils.color import interpolate_color
from utils.iterables import list_update
from utils.iterables import remove_list_redundancies
from utils.paths import straight_path
from utils.space_ops import angle_of_vector
from utils.space_ops import complex_to_R3
from utils.space_ops import rotation_matrix
2015-06-10 22:00:35 -07:00
# TODO: Explain array_attrs
2016-04-09 20:03:57 -07:00
class Mobject(Container):
2015-06-10 22:00:35 -07:00
"""
Mathematical Object
"""
2016-02-27 16:32:53 -08:00
CONFIG = {
"color": WHITE,
"stroke_width": DEFAULT_POINT_THICKNESS,
"name": None,
"dim": 3,
"target": None,
}
2016-04-17 00:31:38 -07:00
def __init__(self, *submobjects, **kwargs):
Container.__init__(self, *submobjects, **kwargs)
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)
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
def __str__(self):
2016-09-16 14:06:44 -07:00
return str(self.name)
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
2016-04-09 20:03:57 -07:00
pass
2015-06-10 22:00:35 -07:00
def generate_points(self):
# Typically implemented in subclass, unless purposefully left blank
pass
2015-06-10 22:00:35 -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)
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
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
def get_array_attrs(self):
2016-04-09 20:03:57 -07:00
return ["points"]
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.
"""
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)
return self
def apply_over_attr_arrays(self, func):
for attr in self.get_array_attrs():
setattr(self, attr, func(getattr(self, attr)))
return self
def get_image(self, camera=None):
if camera is None:
from camera.camera import Camera
camera = Camera()
camera.capture_mobject(self)
2017-09-26 17:41:45 -07:00
return camera.get_image()
2016-04-09 20:03:57 -07:00
def show(self, camera=None):
self.get_image(camera=camera).show()
def save_image(self, name=None):
2016-04-09 20:03:57 -07:00
self.get_image().save(
os.path.join(ANIMATIONS_DIR, (name or str(self)) + ".png")
)
def copy(self):
# TODO, either justify reason for shallow copy, or
# remove this redundancy everywhere
2018-02-07 07:35:18 +01:00
return self.deepcopy()
copy_mobject = copy.copy(self)
copy_mobject.points = np.array(self.points)
copy_mobject.submobjects = [
submob.copy() for submob in self.submobjects
]
family = self.submobject_family()
for attr, value in self.__dict__.items():
if isinstance(value, Mobject) and value in family and value is not self:
setattr(copy_mobject, attr, value.copy())
return copy_mobject
2017-05-12 17:08:49 +02:00
def deepcopy(self):
return copy.deepcopy(self)
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
2016-04-09 20:03:57 -07:00
#### Transforming operations ######
def apply_to_family(self, func):
for mob in self.family_members_with_points():
2016-04-09 20:03:57 -07:00
func(mob)
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():
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
def scale(self, scale_factor, **kwargs):
"""
Default behavior is to scale about the center of the mobject.
The argument about_edge can be a vector, indicating which side of
the mobject to scale about, e.g., mob.scale(about_edge = RIGHT)
scales about mob.get_right().
Otherwise, if about_point is given a value, scaling is done with
respect to that point.
"""
self.apply_points_function_about_point(
lambda points: scale_factor * points, **kwargs
)
return self
def rotate_about_origin(self, angle, axis=OUT, axes=[]):
return self.rotate(angle, axis, about_point=ORIGIN)
def rotate(self, angle, axis=OUT, **kwargs):
rot_matrix = rotation_matrix(angle, axis)
self.apply_points_function_about_point(
lambda points: np.dot(points, rot_matrix.T),
**kwargs
)
return self
def flip(self, axis=UP, **kwargs):
return self.rotate(TAU / 2, axis, **kwargs)
def stretch(self, factor, dim, **kwargs):
def func(points):
points[:, dim] *= factor
return points
self.apply_points_function_about_point(func, **kwargs)
return self
def apply_function(self, function, **kwargs):
# Default to applying matrix about the origin, not mobjects center
if len(kwargs) == 0:
kwargs["about_point"] = ORIGIN
self.apply_points_function_about_point(
lambda points: np.apply_along_axis(function, 1, points),
**kwargs
)
return self
def apply_matrix(self, matrix, **kwargs):
# Default to applying matrix about the origin, not mobjects center
if len(kwargs) == 0:
kwargs["about_point"] = ORIGIN
full_matrix = np.identity(self.dim)
matrix = np.array(matrix)
full_matrix[:matrix.shape[0], :matrix.shape[1]] = matrix
self.apply_points_function_about_point(
lambda points: np.dot(points, full_matrix.T),
**kwargs
)
2017-08-24 19:06:02 -07:00
return self
def apply_complex_function(self, function, **kwargs):
return self.apply_function(
lambda (x, y, z): complex_to_R3(function(complex(x, y))),
**kwargs
)
def wag(self, direction=RIGHT, axis=DOWN, wag_factor=1.0):
for mob in self.family_members_with_points():
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)),
np.array(direction).reshape((1, mob.dim))
)
return self
2016-01-04 10:15:57 -08:00
def reverse_points(self):
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)))
2016-01-04 10:15:57 -08:00
)
return self
def repeat(self, count):
"""
This can make transition animations nicer
"""
def repeat_array(array):
return reduce(
lambda a1, a2: np.append(a1, a2, axis=0),
[array] * count
)
for mob in self.family_members_with_points():
mob.apply_over_attr_arrays(repeat_array)
return self
#### In place operations ######
# Note, much of these are now redundant with default behavior of
# above methods
def apply_points_function_about_point(self, func, about_point=None, about_edge=ORIGIN):
if about_point is None:
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
def rotate_in_place(self, angle, axis=OUT):
# redundant with default behavior of rotate now.
return self.rotate(angle, axis=axis)
2015-06-10 22:00:35 -07:00
def scale_in_place(self, scale_factor, **kwargs):
# Redundant with default behavior of scale now.
return self.scale(scale_factor, **kwargs)
2015-06-10 22:00:35 -07:00
def scale_about_point(self, scale_factor, point):
# Redundant with default behavior of scale now.
return self.scale(scale_factor, about_point=point)
def pose_at_angle(self, **kwargs):
self.rotate(TAU / 14, RIGHT + UP, **kwargs)
2015-06-13 19:00:23 -07:00
return self
#### Positioning methods ####
2015-06-10 22:00:35 -07:00
def center(self):
self.shift(-self.get_center())
return self
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.
"""
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
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)
2017-05-12 17:08:49 +02:00
def next_to(self, mobject_or_point,
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
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:
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)
self.shift((target_point - point_to_align +
buff * direction) * coor_mask)
2015-09-24 10:54:59 -07:00
return self
def align_to(self, mobject_or_point, direction=ORIGIN, alignment_vect=UP):
"""
Examples:
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
target_point = mob.get_critical_point(direction)
else:
target_point = mobject_or_point
direction_norm = np.linalg.norm(direction)
if direction_norm > 0:
alignment_vect = np.array(direction) / direction_norm
reference_point = self.get_critical_point(direction)
2017-08-09 10:47:40 -07:00
else:
reference_point = self.get_center()
diff = target_point - reference_point
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):
space_lengths = [FRAME_X_RADIUS, FRAME_Y_RADIUS]
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
2017-01-16 13:26:46 -08:00
def is_off_screen(self):
if self.get_left()[0] > FRAME_X_RADIUS:
2017-01-16 13:26:46 -08:00
return True
if self.get_right()[0] < -FRAME_X_RADIUS:
2017-01-16 13:26:46 -08:00
return True
if self.get_bottom()[1] > FRAME_Y_RADIUS:
2017-01-16 13:26:46 -08:00
return True
if self.get_top()[1] < -FRAME_Y_RADIUS:
2017-01-16 13:26:46 -08:00
return True
return False
def stretch_about_point(self, factor, dim, point):
return self.stretch(factor, dim, about_point=point)
2016-09-24 22:46:17 -07:00
def stretch_in_place(self, factor, dim):
# Now redundant with stretch
return self.stretch(factor, dim)
2016-09-24 22:46:17 -07:00
def rescale_to_fit(self, length, dim, stretch=False, **kwargs):
old_length = self.length_over_dim(dim)
if old_length == 0:
return self
if stretch:
self.stretch(length / old_length, dim, **kwargs)
else:
self.scale(length / old_length, **kwargs)
return self
def stretch_to_fit_width(self, width, **kwargs):
return self.rescale_to_fit(width, 0, stretch=True, **kwargs)
def stretch_to_fit_height(self, height, **kwargs):
return self.rescale_to_fit(height, 1, stretch=True, **kwargs)
2018-02-27 13:55:38 -08:00
def stretch_to_fit_depth(self, depth, **kwargs):
return self.rescale_to_fit(depth, 1, stretch=True, **kwargs)
2018-02-27 13:55:38 -08:00
def scale_to_fit_width(self, width, **kwargs):
return self.rescale_to_fit(width, 0, stretch=False, **kwargs)
2015-10-20 21:55:46 -07:00
def scale_to_fit_height(self, height, **kwargs):
return self.rescale_to_fit(height, 1, stretch=False, **kwargs)
def scale_to_fit_depth(self, depth, **kwargs):
return self.rescale_to_fit(depth, 2, stretch=False, **kwargs)
2017-09-06 20:18:19 -07:00
def space_out_submobjects(self, factor=1.5, **kwargs):
self.scale(factor, **kwargs)
2017-01-26 19:59:55 -08:00
for submob in self.submobjects:
submob.scale(1. / factor)
2017-01-26 19:59:55 -08:00
return self
def move_to(self, point_or_mobject, aligned_edge=ORIGIN,
coor_mask=np.array([1, 1, 1])):
if isinstance(point_or_mobject, Mobject):
2016-08-20 20:23:01 -07:00
target = point_or_mobject.get_critical_point(aligned_edge)
else:
target = point_or_mobject
2016-08-20 20:23:01 -07:00
point_to_align = self.get_critical_point(aligned_edge)
self.shift((target - point_to_align) * coor_mask)
return self
2015-10-20 21:55:46 -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:
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:
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,
stretch=False
2016-08-13 18:27:02 -07:00
)
self.shift(mobject.get_center() - self.get_center())
return self
def surround(self, mobject, dim_to_match=0, stretch=False, buffer_factor=1.2):
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
self.scale(np.linalg.norm(target_vect) / np.linalg.norm(curr_vect))
2016-02-21 15:44:54 -08:00
self.rotate(
angle_of_vector(target_vect) -
2016-02-21 15:44:54 -08:00
angle_of_vector(curr_vect)
)
self.shift(start - self.points[0])
2016-02-21 15:44:54 -08:00
return self
# Match other mobvject 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
)
def match_width(self, mobject, **kwargs):
return self.match_dim(mobject, 0, **kwargs)
2018-01-20 19:31:09 -08:00
def match_height(self, mobject, **kwargs):
return self.match_dim(mobject, 1, **kwargs)
2018-01-20 19:31:09 -08:00
def match_depth(self, mobject, **kwargs):
return self.match_dim(mobject, 2, **kwargs)
2018-01-20 19:31:09 -08:00
# Color functions
2016-02-21 15:44:54 -08: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).
Here it just recurses to submobjects, but in subclasses this
should be further implemented based on the the inner workings
of color
2016-04-09 20:03:57 -07:00
"""
if family:
for submob in self.submobjects:
submob.set_color(color, family=family)
2018-03-30 11:51:31 -07:00
self.color = color
return self
2016-04-09 20:03:57 -07:00
def set_color_by_gradient(self, *colors):
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
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
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):
mob.set_color(color, family=False)
2016-07-22 11:22:31 -07:00
return self
2015-06-10 22:00:35 -07:00
def set_submobject_colors_by_radial_gradient(self, center=None, radius=1, inner_color=WHITE, outer_color=BLACK):
2018-01-17 15:18:02 -08:00
mobs = self.family_members_with_points()
if center == None:
center = self.get_center()
for mob in self.family_members_with_points():
t = np.linalg.norm(mob.get_center() - center) / radius
t = min(t, 1)
2018-01-17 15:18:02 -08:00
mob_color = interpolate_color(inner_color, outer_color, t)
mob.set_color(mob_color, family=False)
2018-01-17 15:18:02 -08:00
return self
def to_original_color(self):
2018-03-30 11:51:31 -07:00
self.set_color(self.color)
return self
# Some objects (e.g., VMobjects) have special fading
# behavior. We let every object handle its individual
# fading via fade_no_recurse (notionally a purely internal method),
# 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)
self.set_color(Color(rgb=new_rgb), family=False)
return self
def fade_to(self, color, alpha):
for mob in self.subobject_family():
mob.fade_to_no_recurse(self, color, alpha)
return self
def fade_no_recurse(self, darkness):
self.fade_to_no_recurse(BLACK, darkness)
return self
def fade(self, darkness=0.5):
for submob in self.submobject_family():
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
##
def save_state(self, use_deepcopy=False):
2016-11-15 20:08:28 -08:00
if hasattr(self, "saved_state"):
# Prevent exponential growth of data
2016-11-15 20:08:28 -08:00
self.saved_state = None
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)
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
##
2016-04-09 20:03:57 -07: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])]
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
]
try:
return reduce_func(values)
except:
return 0
def get_merged_array(self, array_attr):
2018-01-15 15:06:01 -08:00
result = None
for mob in self.family_members_with_points():
2018-01-15 15:06:01 -08:00
if result is None:
result = getattr(mob, array_attr)
else:
result = np.append(result, getattr(mob, array_attr), 0)
2015-11-09 10:34:00 -08:00
return result
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)
def get_critical_point(self, direction):
result = np.zeros(self.dim)
2017-01-26 19:59:55 -08:00
for dim in range(self.dim):
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
# 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):
return self.get_critical_point(np.zeros(self.dim))
def get_center_of_mass(self):
return np.apply_along_axis(np.mean, 0, self.get_all_points())
2015-06-10 22:00:35 -07:00
def get_boundary_point(self, direction):
all_points = self.get_all_points()
return all_points[np.argmax(np.dot(all_points, direction))]
def get_top(self):
return self.get_edge_center(UP)
def get_bottom(self):
return self.get_edge_center(DOWN)
def get_right(self):
return self.get_edge_center(RIGHT)
def get_left(self):
return self.get_edge_center(LEFT)
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)
def length_over_dim(self, dim):
return (
2017-05-12 17:08:49 +02:00
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):
return self.length_over_dim(0)
2015-06-10 22:00:35 -07:00
def get_height(self):
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
# Family matters
2016-08-10 10:26:07 -07: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())
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
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)
def family_members_with_points(self):
2016-04-09 20:03:57 -07:00
return filter(
lambda m: m.get_num_points() > 0,
2016-04-09 20:03:57 -07:00
self.submobject_family()
)
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:]):
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
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)))
2017-08-27 14:43:18 -07:00
if n_rows is not None:
v1 = RIGHT
v2 = DOWN
2017-09-26 17:41:45 -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
2017-09-26 17:41:45 -07:00
n = len(submobs) / n_cols
2017-08-27 14:43:18 -07:00
Group(*[
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
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
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):
2016-04-17 00:31:38 -07:00
self.align_submobjects(mobject)
self.align_points(mobject)
# Recurse
2016-04-17 00:31:38 -07:00
for m1, m2 in zip(self.submobjects, mobject.submobjects):
m1.align_data(m2)
def get_point_mobject(self, center=None):
"""
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):
# 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
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)
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 = []
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)
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,
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.
"""
self.points = path_func(
mobject1.points, mobject2.points, alpha
)
self.interpolate_color(mobject1, mobject2, alpha)
def interpolate_color(self, mobject1, mobject2, alpha):
pass # To implement in subclass
2015-06-10 22:00:35 -07:00
def become_partial(self, mobject, a, b):
"""
Set points in such a way as to become only
2017-05-12 17:08:49 +02:00
part of mobject.
Inputs 0 <= a < b <= 1 determine what portion
of mobject to become.
"""
pass # To implement in subclasses
2016-09-16 14:06:44 -07:00
# TODO, color?
def pointwise_become_partial(self, mobject, a, b):
pass # To implement in subclass
class Group(Mobject):
# 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