2022-02-13 15:11:35 +08:00
|
|
|
from __future__ import annotations
|
|
|
|
|
2022-04-20 22:53:49 -07:00
|
|
|
import copy
|
2021-02-05 11:52:21 +01:00
|
|
|
from functools import wraps
|
2022-04-22 16:42:45 +08:00
|
|
|
import itertools as it
|
2022-04-20 21:42:59 -07:00
|
|
|
import os
|
2022-04-22 16:42:45 +08:00
|
|
|
import pickle
|
|
|
|
import random
|
|
|
|
import sys
|
2018-03-31 15:11:35 -07:00
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
import moderngl
|
2022-04-15 00:55:02 +08:00
|
|
|
import numbers
|
2018-12-24 12:37:51 -08:00
|
|
|
import numpy as np
|
2015-06-10 22:00:35 -07:00
|
|
|
|
2022-04-12 19:19:59 +08:00
|
|
|
from manimlib.constants import DEFAULT_MOBJECT_TO_EDGE_BUFFER
|
|
|
|
from manimlib.constants import DEFAULT_MOBJECT_TO_MOBJECT_BUFFER
|
|
|
|
from manimlib.constants import DOWN, IN, LEFT, ORIGIN, OUT, RIGHT, UP
|
|
|
|
from manimlib.constants import FRAME_X_RADIUS, FRAME_Y_RADIUS
|
|
|
|
from manimlib.constants import MED_SMALL_BUFF
|
|
|
|
from manimlib.constants import TAU
|
|
|
|
from manimlib.constants import WHITE
|
|
|
|
from manimlib.event_handler import EVENT_DISPATCHER
|
|
|
|
from manimlib.event_handler.event_listner import EventListner
|
|
|
|
from manimlib.event_handler.event_type import EventType
|
2022-04-20 22:40:11 -07:00
|
|
|
from manimlib.logger import log
|
2022-04-12 19:19:59 +08:00
|
|
|
from manimlib.shader_wrapper import ShaderWrapper
|
2018-12-24 12:37:51 -08:00
|
|
|
from manimlib.utils.color import color_gradient
|
2022-04-12 19:19:59 +08:00
|
|
|
from manimlib.utils.color import color_to_rgb
|
2021-01-10 14:12:15 -08:00
|
|
|
from manimlib.utils.color import get_colormap_list
|
2021-01-11 16:37:51 -10:00
|
|
|
from manimlib.utils.color import rgb_to_hex
|
2023-01-10 10:25:59 -08:00
|
|
|
from manimlib.utils.iterables import arrays_match
|
2023-01-16 13:27:20 -08:00
|
|
|
from manimlib.utils.iterables import array_is_constant
|
2020-02-17 12:15:53 -08:00
|
|
|
from manimlib.utils.iterables import batch_by_property
|
2018-12-24 12:37:51 -08:00
|
|
|
from manimlib.utils.iterables import list_update
|
2022-04-12 19:19:59 +08:00
|
|
|
from manimlib.utils.iterables import listify
|
2021-01-11 10:57:23 -10:00
|
|
|
from manimlib.utils.iterables import resize_array
|
|
|
|
from manimlib.utils.iterables import resize_preserving_order
|
2021-01-11 17:52:48 -10:00
|
|
|
from manimlib.utils.iterables import resize_with_interpolation
|
2021-06-19 18:30:23 +08:00
|
|
|
from manimlib.utils.bezier import integer_interpolate
|
2022-04-12 19:19:59 +08:00
|
|
|
from manimlib.utils.bezier import interpolate
|
2018-12-24 12:37:51 -08:00
|
|
|
from manimlib.utils.paths import straight_path
|
2019-01-29 14:21:56 -08:00
|
|
|
from manimlib.utils.simple_functions import get_parameters
|
2023-01-16 14:18:49 -08:00
|
|
|
from manimlib.utils.shaders import get_colormap_code
|
2018-12-24 12:37:51 -08:00
|
|
|
from manimlib.utils.space_ops import angle_of_vector
|
|
|
|
from manimlib.utils.space_ops import get_norm
|
2020-02-20 10:03:36 -08:00
|
|
|
from manimlib.utils.space_ops import rotation_matrix_transpose
|
2015-06-10 22:00:35 -07:00
|
|
|
|
2022-04-12 19:19:59 +08:00
|
|
|
from typing import TYPE_CHECKING
|
2015-06-10 22:00:35 -07:00
|
|
|
|
2022-04-12 19:19:59 +08:00
|
|
|
if TYPE_CHECKING:
|
2023-01-16 19:33:57 -08:00
|
|
|
from typing import Callable, Iterable, Union, Tuple
|
2022-04-12 19:19:59 +08:00
|
|
|
import numpy.typing as npt
|
2022-12-17 13:16:48 -08:00
|
|
|
from manimlib.typing import ManimColor, Vect3, Vect4, Vect3Array
|
2023-01-25 10:49:30 -08:00
|
|
|
from moderngl.context import Context
|
2015-06-10 22:00:35 -07:00
|
|
|
|
2022-12-17 13:16:48 -08:00
|
|
|
TimeBasedUpdater = Callable[["Mobject", float], "Mobject" | None]
|
|
|
|
NonTimeUpdater = Callable[["Mobject"], "Mobject" | None]
|
2022-04-12 19:19:59 +08:00
|
|
|
Updater = Union[TimeBasedUpdater, NonTimeUpdater]
|
2022-02-13 15:11:35 +08:00
|
|
|
|
|
|
|
|
2021-01-03 12:29:05 -08:00
|
|
|
class Mobject(object):
|
2015-06-10 22:00:35 -07:00
|
|
|
"""
|
|
|
|
Mathematical Object
|
|
|
|
"""
|
2022-12-15 09:18:22 -08:00
|
|
|
dim: int = 3
|
|
|
|
shader_folder: str = ""
|
|
|
|
render_primitive: int = moderngl.TRIANGLE_STRIP
|
|
|
|
# Must match in attributes of vert shader
|
2023-01-15 19:09:29 -08:00
|
|
|
shader_dtype: np.dtype = np.dtype([
|
2023-01-15 18:23:41 -08:00
|
|
|
('point', np.float32, (3,)),
|
|
|
|
('rgba', np.float32, (4,)),
|
2023-01-15 16:05:18 -08:00
|
|
|
])
|
2023-01-15 18:23:41 -08:00
|
|
|
aligned_data_keys = ['point']
|
|
|
|
pointlike_data_keys = ['point']
|
2022-12-15 09:18:22 -08:00
|
|
|
|
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
color: ManimColor = WHITE,
|
|
|
|
opacity: float = 1.0,
|
2023-01-16 19:33:57 -08:00
|
|
|
shading: Tuple[float, float, float] = (0.0, 0.0, 0.0),
|
2020-02-13 10:39:26 -08:00
|
|
|
# For shaders
|
2022-12-15 09:55:50 -08:00
|
|
|
texture_paths: dict[str, str] | None = None,
|
2020-06-08 20:27:07 -07:00
|
|
|
# If true, the mobject will not get rotated according to camera position
|
2022-12-15 09:18:22 -08:00
|
|
|
is_fixed_in_frame: bool = False,
|
2022-12-16 09:56:03 -08:00
|
|
|
depth_test: bool = False,
|
2022-12-15 09:18:22 -08:00
|
|
|
):
|
|
|
|
self.color = color
|
|
|
|
self.opacity = opacity
|
2023-01-16 19:33:57 -08:00
|
|
|
self.shading = shading
|
2022-12-15 09:18:22 -08:00
|
|
|
self.texture_paths = texture_paths
|
|
|
|
self.is_fixed_in_frame = is_fixed_in_frame
|
2022-12-16 09:56:03 -08:00
|
|
|
self.depth_test = depth_test
|
2022-12-15 09:18:22 -08:00
|
|
|
|
|
|
|
# Internal state
|
2022-02-14 21:41:45 +08:00
|
|
|
self.submobjects: list[Mobject] = []
|
|
|
|
self.parents: list[Mobject] = []
|
|
|
|
self.family: list[Mobject] = [self]
|
2022-02-13 15:11:35 +08:00
|
|
|
self.locked_data_keys: set[str] = set()
|
2023-01-16 13:27:20 -08:00
|
|
|
self.const_data_keys: set[str] = set()
|
2022-02-13 15:11:35 +08:00
|
|
|
self.needs_new_bounding_box: bool = True
|
2022-04-14 16:27:58 -07:00
|
|
|
self._is_animating: bool = False
|
2022-04-22 19:42:47 -07:00
|
|
|
self.saved_state = None
|
|
|
|
self.target = None
|
2023-01-13 14:58:52 -08:00
|
|
|
self.bounding_box: Vect3Array = np.zeros((3, 3))
|
2023-01-25 10:37:12 -08:00
|
|
|
self._shaders_initialized: bool = False
|
2023-01-25 14:13:56 -08:00
|
|
|
self._data_has_changed: bool = True
|
2020-02-13 11:43:59 -08:00
|
|
|
|
2021-01-10 18:51:47 -08:00
|
|
|
self.init_data()
|
2023-01-15 16:05:18 -08:00
|
|
|
self._data_defaults = np.ones(1, dtype=self.data.dtype)
|
2021-01-12 12:15:32 -10:00
|
|
|
self.init_uniforms()
|
2021-01-11 10:57:23 -10:00
|
|
|
self.init_updaters()
|
2021-02-02 16:04:50 +05:30
|
|
|
self.init_event_listners()
|
2020-02-13 11:43:59 -08:00
|
|
|
self.init_points()
|
2016-04-17 19:29:27 -07:00
|
|
|
self.init_colors()
|
2015-09-24 10:54:59 -07:00
|
|
|
|
2020-06-15 12:01:54 -07:00
|
|
|
if self.depth_test:
|
|
|
|
self.apply_depth_test()
|
|
|
|
|
2016-02-12 14:50:35 -08:00
|
|
|
def __str__(self):
|
2021-01-04 13:25:04 -08:00
|
|
|
return self.__class__.__name__
|
2016-02-12 14:50:35 -08:00
|
|
|
|
2022-02-14 21:41:45 +08:00
|
|
|
def __add__(self, other: Mobject) -> Mobject:
|
2021-10-31 20:01:16 +08:00
|
|
|
assert(isinstance(other, Mobject))
|
2021-11-01 13:16:50 -07:00
|
|
|
return self.get_group_class()(self, other)
|
2021-10-22 20:00:27 +08:00
|
|
|
|
2022-02-14 21:41:45 +08:00
|
|
|
def __mul__(self, other: int) -> Mobject:
|
2021-10-31 20:01:16 +08:00
|
|
|
assert(isinstance(other, int))
|
2021-10-31 18:34:23 +08:00
|
|
|
return self.replicate(other)
|
2021-10-22 20:00:27 +08:00
|
|
|
|
2023-01-15 16:05:18 -08:00
|
|
|
def init_data(self, length: int = 0):
|
2023-01-15 19:09:29 -08:00
|
|
|
self.data = np.zeros(length, dtype=self.shader_dtype)
|
2021-01-10 18:51:47 -08:00
|
|
|
|
2021-01-12 12:15:32 -10:00
|
|
|
def init_uniforms(self):
|
2023-01-09 11:56:21 -08:00
|
|
|
self.uniforms: dict[str, float | np.ndarray] = {
|
2021-01-12 12:15:32 -10:00
|
|
|
"is_fixed_in_frame": float(self.is_fixed_in_frame),
|
2023-01-16 19:33:57 -08:00
|
|
|
"shading": np.array(self.shading, dtype=float),
|
2021-01-12 12:15:32 -10:00
|
|
|
}
|
|
|
|
|
2022-02-11 23:53:21 +08:00
|
|
|
def init_colors(self):
|
|
|
|
self.set_color(self.color, self.opacity)
|
2021-01-11 16:37:51 -10:00
|
|
|
|
|
|
|
def init_points(self):
|
|
|
|
# Typically implemented in subclass, unlpess purposefully left blank
|
|
|
|
pass
|
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def set_uniforms(self, uniforms: dict):
|
2022-04-21 14:30:39 -07:00
|
|
|
for key, value in uniforms.items():
|
|
|
|
if isinstance(value, np.ndarray):
|
|
|
|
value = value.copy()
|
|
|
|
self.uniforms[key] = value
|
2021-01-13 11:55:26 -10:00
|
|
|
return self
|
2021-01-13 10:23:42 -10:00
|
|
|
|
2021-02-10 13:49:09 -08:00
|
|
|
@property
|
|
|
|
def animate(self):
|
|
|
|
# Borrowed from https://github.com/ManimCommunity/manim/
|
|
|
|
return _AnimationBuilder(self)
|
|
|
|
|
2023-01-25 16:43:47 -08:00
|
|
|
def note_changed_data(self, recurse_up: bool = True):
|
|
|
|
self._data_has_changed = True
|
|
|
|
if recurse_up:
|
|
|
|
for mob in self.parents:
|
|
|
|
mob.note_changed_data()
|
|
|
|
|
|
|
|
def affects_data(func: Callable):
|
|
|
|
@wraps(func)
|
|
|
|
def wrapper(self, *args, **kwargs):
|
|
|
|
func(self, *args, **kwargs)
|
|
|
|
self.note_changed_data()
|
|
|
|
return wrapper
|
|
|
|
|
|
|
|
def affects_family_data(func: Callable):
|
|
|
|
@wraps(func)
|
|
|
|
def wrapper(self, *args, **kwargs):
|
|
|
|
func(self, *args, **kwargs)
|
|
|
|
for mob in self.family_members_with_points():
|
|
|
|
mob.note_changed_data()
|
|
|
|
return self
|
|
|
|
return wrapper
|
|
|
|
|
2021-01-14 14:15:58 -10:00
|
|
|
# Only these methods should directly affect points
|
2023-01-25 16:43:47 -08:00
|
|
|
@affects_data
|
|
|
|
def set_data(self, data: np.ndarray):
|
|
|
|
assert(data.dtype == self.data.dtype)
|
|
|
|
self.data = data
|
|
|
|
return self
|
2021-01-14 14:15:58 -10:00
|
|
|
|
2023-01-25 16:43:47 -08:00
|
|
|
@affects_data
|
2022-02-13 15:11:35 +08:00
|
|
|
def resize_points(
|
|
|
|
self,
|
|
|
|
new_length: int,
|
|
|
|
resize_func: Callable[[np.ndarray, int], np.ndarray] = resize_array
|
|
|
|
):
|
2023-01-15 12:34:59 -08:00
|
|
|
if new_length == 0:
|
2023-01-15 16:05:18 -08:00
|
|
|
if len(self.data) > 0:
|
|
|
|
self._data_defaults[:1] = self.data[:1]
|
2023-01-15 12:34:59 -08:00
|
|
|
elif self.get_num_points() == 0:
|
2023-01-15 16:05:18 -08:00
|
|
|
self.data = self._data_defaults.copy()
|
2023-01-15 12:34:59 -08:00
|
|
|
|
2023-01-15 16:05:18 -08:00
|
|
|
self.data = resize_func(self.data, new_length)
|
2021-01-14 01:01:43 -10:00
|
|
|
self.refresh_bounding_box()
|
2021-01-13 11:55:26 -10:00
|
|
|
return self
|
2021-01-10 18:51:47 -08:00
|
|
|
|
2023-01-25 16:43:47 -08:00
|
|
|
@affects_data
|
2022-12-17 19:31:43 -08:00
|
|
|
def set_points(self, points: Vect3Array):
|
2023-01-16 13:28:09 -08:00
|
|
|
self.resize_points(len(points), resize_func=resize_preserving_order)
|
2023-01-15 18:23:41 -08:00
|
|
|
self.data["point"][:] = points
|
2021-01-13 11:55:26 -10:00
|
|
|
return self
|
2021-01-10 18:51:47 -08:00
|
|
|
|
2023-01-25 16:43:47 -08:00
|
|
|
@affects_data
|
2022-12-17 19:31:43 -08:00
|
|
|
def append_points(self, new_points: Vect3Array):
|
2022-12-28 09:22:22 -08:00
|
|
|
n = self.get_num_points()
|
|
|
|
self.resize_points(n + len(new_points))
|
2023-01-16 13:29:35 -08:00
|
|
|
# Have most data default to the last value
|
|
|
|
self.data[n:] = self.data[n - 1]
|
|
|
|
# Then read in new points
|
2023-01-15 18:23:41 -08:00
|
|
|
self.data["point"][n:] = new_points
|
2021-01-14 01:01:43 -10:00
|
|
|
self.refresh_bounding_box()
|
2021-01-11 12:39:14 -10:00
|
|
|
return self
|
|
|
|
|
2023-01-25 16:43:47 -08:00
|
|
|
@affects_family_data
|
2021-01-14 14:15:58 -10:00
|
|
|
def reverse_points(self):
|
|
|
|
for mob in self.get_family():
|
2023-01-15 16:05:18 -08:00
|
|
|
mob.data = mob.data[::-1]
|
2021-01-14 14:15:58 -10:00
|
|
|
return self
|
|
|
|
|
2023-01-25 16:43:47 -08:00
|
|
|
@affects_family_data
|
2022-02-13 15:11:35 +08:00
|
|
|
def apply_points_function(
|
|
|
|
self,
|
|
|
|
func: Callable[[np.ndarray], np.ndarray],
|
2023-01-15 17:40:05 -08:00
|
|
|
about_point: Vect3 | None = None,
|
2022-12-16 20:35:26 -08:00
|
|
|
about_edge: Vect3 = ORIGIN,
|
2022-02-13 15:11:35 +08:00
|
|
|
works_on_bounding_box: bool = False
|
|
|
|
):
|
2021-01-14 14:15:58 -10:00
|
|
|
if about_point is None and about_edge is not None:
|
|
|
|
about_point = self.get_bounding_box_point(about_edge)
|
|
|
|
|
|
|
|
for mob in self.get_family():
|
2021-01-15 09:02:07 -10:00
|
|
|
arrs = []
|
|
|
|
if mob.has_points():
|
2023-01-15 17:40:05 -08:00
|
|
|
for key in mob.pointlike_data_keys:
|
|
|
|
arrs.append(mob.data[key])
|
2021-01-14 14:15:58 -10:00
|
|
|
if works_on_bounding_box:
|
|
|
|
arrs.append(mob.get_bounding_box())
|
2021-01-15 09:02:07 -10:00
|
|
|
|
2021-01-14 14:15:58 -10:00
|
|
|
for arr in arrs:
|
|
|
|
if about_point is None:
|
|
|
|
arr[:] = func(arr)
|
|
|
|
else:
|
|
|
|
arr[:] = func(arr - about_point) + about_point
|
|
|
|
|
|
|
|
if not works_on_bounding_box:
|
|
|
|
self.refresh_bounding_box(recurse_down=True)
|
|
|
|
else:
|
|
|
|
for parent in self.parents:
|
|
|
|
parent.refresh_bounding_box()
|
|
|
|
return self
|
|
|
|
|
|
|
|
# Others related to points
|
|
|
|
|
2022-02-14 21:41:45 +08:00
|
|
|
def match_points(self, mobject: Mobject):
|
2021-01-12 07:27:32 -10:00
|
|
|
self.set_points(mobject.get_points())
|
2021-03-27 11:57:50 -07:00
|
|
|
return self
|
2021-01-12 07:27:32 -10:00
|
|
|
|
2022-12-17 13:16:48 -08:00
|
|
|
def get_points(self) -> Vect3Array:
|
2023-01-15 18:23:41 -08:00
|
|
|
return self.data["point"]
|
2021-01-10 18:51:47 -08:00
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def clear_points(self) -> None:
|
2021-01-11 10:57:23 -10:00
|
|
|
self.resize_points(0)
|
2021-01-10 18:51:47 -08:00
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def get_num_points(self) -> int:
|
2023-01-15 10:00:05 -08:00
|
|
|
return len(self.get_points())
|
2021-01-10 18:51:47 -08:00
|
|
|
|
2022-12-17 13:16:48 -08:00
|
|
|
def get_all_points(self) -> Vect3Array:
|
2021-01-14 14:15:58 -10:00
|
|
|
if self.submobjects:
|
|
|
|
return np.vstack([sm.get_points() for sm in self.get_family()])
|
|
|
|
else:
|
|
|
|
return self.get_points()
|
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def has_points(self) -> bool:
|
2023-01-15 10:00:05 -08:00
|
|
|
return len(self.get_points()) > 0
|
2021-01-14 14:15:58 -10:00
|
|
|
|
2022-12-17 13:16:48 -08:00
|
|
|
def get_bounding_box(self) -> Vect3Array:
|
2021-02-03 15:58:16 -08:00
|
|
|
if self.needs_new_bounding_box:
|
2023-01-13 14:58:52 -08:00
|
|
|
self.bounding_box[:] = self.compute_bounding_box()
|
2021-02-03 15:58:16 -08:00
|
|
|
self.needs_new_bounding_box = False
|
2023-01-13 14:58:52 -08:00
|
|
|
return self.bounding_box
|
2021-01-14 14:15:58 -10:00
|
|
|
|
2022-12-17 13:16:48 -08:00
|
|
|
def compute_bounding_box(self) -> Vect3Array:
|
2021-01-14 14:15:58 -10:00
|
|
|
all_points = np.vstack([
|
|
|
|
self.get_points(),
|
|
|
|
*(
|
|
|
|
mob.get_bounding_box()
|
|
|
|
for mob in self.get_family()[1:]
|
|
|
|
if mob.has_points()
|
|
|
|
)
|
|
|
|
])
|
|
|
|
if len(all_points) == 0:
|
2021-02-03 15:58:16 -08:00
|
|
|
return np.zeros((3, self.dim))
|
2021-01-14 14:15:58 -10:00
|
|
|
else:
|
|
|
|
# Lower left and upper right corners
|
|
|
|
mins = all_points.min(0)
|
|
|
|
maxs = all_points.max(0)
|
|
|
|
mids = (mins + maxs) / 2
|
2021-02-03 15:58:16 -08:00
|
|
|
return np.array([mins, mids, maxs])
|
2021-01-14 14:15:58 -10:00
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def refresh_bounding_box(
|
|
|
|
self,
|
|
|
|
recurse_down: bool = False,
|
|
|
|
recurse_up: bool = True
|
|
|
|
):
|
2021-01-14 14:15:58 -10:00
|
|
|
for mob in self.get_family(recurse_down):
|
|
|
|
mob.needs_new_bounding_box = True
|
|
|
|
if recurse_up:
|
|
|
|
for parent in self.parents:
|
|
|
|
parent.refresh_bounding_box()
|
|
|
|
return self
|
|
|
|
|
2022-04-20 21:41:47 -07:00
|
|
|
def are_points_touching(
|
2022-02-13 15:11:35 +08:00
|
|
|
self,
|
2022-12-17 13:16:48 -08:00
|
|
|
points: Vect3Array,
|
2022-04-28 11:59:56 -06:00
|
|
|
buff: float = 0
|
2022-12-17 13:16:48 -08:00
|
|
|
) -> np.ndarray:
|
2021-01-28 14:02:43 +05:30
|
|
|
bb = self.get_bounding_box()
|
2021-02-09 10:52:45 -08:00
|
|
|
mins = (bb[0] - buff)
|
|
|
|
maxs = (bb[2] + buff)
|
2022-04-20 21:41:47 -07:00
|
|
|
return ((points >= mins) * (points <= maxs)).all(1)
|
|
|
|
|
|
|
|
def is_point_touching(
|
|
|
|
self,
|
2022-12-16 20:35:26 -08:00
|
|
|
point: Vect3,
|
2022-04-28 11:59:56 -06:00
|
|
|
buff: float = 0
|
2022-04-20 21:41:47 -07:00
|
|
|
) -> bool:
|
|
|
|
return self.are_points_touching(np.array(point, ndmin=2), buff)[0]
|
2021-01-28 14:02:43 +05:30
|
|
|
|
2022-04-20 21:42:07 -07:00
|
|
|
def is_touching(self, mobject: Mobject, buff: float = 1e-2) -> bool:
|
|
|
|
bb1 = self.get_bounding_box()
|
|
|
|
bb2 = mobject.get_bounding_box()
|
|
|
|
return not any((
|
|
|
|
(bb2[2] < bb1[0] - buff).any(), # E.g. Right of mobject is left of self's left
|
|
|
|
(bb2[0] > bb1[2] + buff).any(), # E.g. Left of mobject is right of self's right
|
|
|
|
))
|
|
|
|
|
2020-02-21 10:56:40 -08:00
|
|
|
# Family matters
|
2021-01-14 01:01:43 -10:00
|
|
|
|
2022-12-17 17:03:34 -08:00
|
|
|
def __getitem__(self, value: int | slice) -> Mobject:
|
2020-02-21 10:56:40 -08:00
|
|
|
if isinstance(value, slice):
|
|
|
|
GroupClass = self.get_group_class()
|
2021-01-13 09:20:47 -10:00
|
|
|
return GroupClass(*self.split().__getitem__(value))
|
|
|
|
return self.split().__getitem__(value)
|
2020-02-21 10:56:40 -08:00
|
|
|
|
|
|
|
def __iter__(self):
|
|
|
|
return iter(self.split())
|
|
|
|
|
|
|
|
def __len__(self):
|
|
|
|
return len(self.split())
|
|
|
|
|
2022-12-17 17:03:34 -08:00
|
|
|
def split(self) -> list[Mobject]:
|
2021-01-13 09:20:47 -10:00
|
|
|
return self.submobjects
|
2020-02-21 10:56:40 -08:00
|
|
|
|
2023-01-25 16:43:47 -08:00
|
|
|
@affects_data
|
2020-02-21 10:56:40 -08:00
|
|
|
def assemble_family(self):
|
2021-01-14 14:15:58 -10:00
|
|
|
sub_families = (sm.get_family() for sm in self.submobjects)
|
2020-02-21 10:56:40 -08:00
|
|
|
self.family = [self, *it.chain(*sub_families)]
|
2020-06-29 11:23:01 -07:00
|
|
|
self.refresh_has_updater_status()
|
2021-01-14 01:38:40 -10:00
|
|
|
self.refresh_bounding_box()
|
2020-02-21 10:56:40 -08:00
|
|
|
for parent in self.parents:
|
|
|
|
parent.assemble_family()
|
|
|
|
return self
|
|
|
|
|
2022-12-14 10:55:32 -08:00
|
|
|
def get_family(self, recurse: bool = True) -> list[Mobject]:
|
2021-01-14 14:15:58 -10:00
|
|
|
if recurse:
|
|
|
|
return self.family
|
|
|
|
else:
|
|
|
|
return [self]
|
2020-02-21 10:56:40 -08:00
|
|
|
|
|
|
|
def family_members_with_points(self):
|
2021-01-10 18:51:47 -08:00
|
|
|
return [m for m in self.get_family() if m.has_points()]
|
2020-02-21 10:56:40 -08:00
|
|
|
|
2022-04-25 09:54:32 -07:00
|
|
|
def get_ancestors(self, extended: bool = False) -> list[Mobject]:
|
|
|
|
"""
|
|
|
|
Returns parents, grandparents, etc.
|
|
|
|
Order of result should be from higher members of the hierarchy down.
|
|
|
|
|
|
|
|
If extended is set to true, it includes the ancestors of all family members,
|
|
|
|
e.g. any other parents of a submobject
|
|
|
|
"""
|
|
|
|
ancestors = []
|
|
|
|
to_process = list(self.get_family(recurse=extended))
|
|
|
|
excluded = set(to_process)
|
|
|
|
while to_process:
|
|
|
|
for p in to_process.pop().parents:
|
|
|
|
if p not in excluded:
|
|
|
|
ancestors.append(p)
|
|
|
|
to_process.append(p)
|
2022-04-27 09:53:23 -07:00
|
|
|
# Ensure mobjects highest in the hierarchy show up first
|
2022-04-25 09:54:32 -07:00
|
|
|
ancestors.reverse()
|
2022-04-27 09:53:23 -07:00
|
|
|
# Remove list redundancies while preserving order
|
2022-04-25 09:54:32 -07:00
|
|
|
return list(dict.fromkeys(ancestors))
|
2022-04-24 10:29:02 -07:00
|
|
|
|
2022-02-14 21:41:45 +08:00
|
|
|
def add(self, *mobjects: Mobject):
|
2017-09-28 07:40:57 -07:00
|
|
|
if self in mobjects:
|
|
|
|
raise Exception("Mobject cannot contain self")
|
2020-02-21 10:56:40 -08:00
|
|
|
for mobject in mobjects:
|
|
|
|
if mobject not in self.submobjects:
|
|
|
|
self.submobjects.append(mobject)
|
|
|
|
if self not in mobject.parents:
|
|
|
|
mobject.parents.append(self)
|
|
|
|
self.assemble_family()
|
2016-08-09 14:07:23 -07:00
|
|
|
return self
|
|
|
|
|
2022-04-24 13:24:20 -07:00
|
|
|
def remove(self, *mobjects: Mobject, reassemble: bool = True):
|
2016-04-27 17:35:04 -07:00
|
|
|
for mobject in mobjects:
|
|
|
|
if mobject in self.submobjects:
|
|
|
|
self.submobjects.remove(mobject)
|
2020-02-21 10:56:40 -08:00
|
|
|
if self in mobject.parents:
|
|
|
|
mobject.parents.remove(self)
|
2022-04-24 13:24:20 -07:00
|
|
|
if reassemble:
|
|
|
|
self.assemble_family()
|
2020-02-21 10:56:40 -08:00
|
|
|
return self
|
|
|
|
|
2022-02-14 21:41:45 +08:00
|
|
|
def add_to_back(self, *mobjects: Mobject):
|
2020-02-23 22:58:47 +00:00
|
|
|
self.set_submobjects(list_update(mobjects, self.submobjects))
|
2020-02-21 10:56:40 -08:00
|
|
|
return self
|
|
|
|
|
2022-02-14 21:41:45 +08:00
|
|
|
def replace_submobject(self, index: int, new_submob: Mobject):
|
2020-02-22 13:19:51 -08:00
|
|
|
old_submob = self.submobjects[index]
|
|
|
|
if self in old_submob.parents:
|
|
|
|
old_submob.parents.remove(self)
|
|
|
|
self.submobjects[index] = new_submob
|
2022-12-29 19:42:54 -08:00
|
|
|
new_submob.parents.append(self)
|
2020-02-22 13:19:51 -08:00
|
|
|
self.assemble_family()
|
|
|
|
return self
|
|
|
|
|
2022-02-14 21:41:45 +08:00
|
|
|
def insert_submobject(self, index: int, new_submob: Mobject):
|
2021-11-30 11:30:50 -08:00
|
|
|
self.submobjects.insert(index, new_submob)
|
|
|
|
self.assemble_family()
|
|
|
|
return self
|
|
|
|
|
2022-02-14 21:41:45 +08:00
|
|
|
def set_submobjects(self, submobject_list: list[Mobject]):
|
2022-04-24 13:24:20 -07:00
|
|
|
self.remove(*self.submobjects, reassemble=False)
|
2020-02-21 10:56:40 -08:00
|
|
|
self.add(*submobject_list)
|
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 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)]
|
2020-02-21 10:56:40 -08:00
|
|
|
self.set_submobjects(list_update(self.submobjects, mobject_attrs))
|
2015-11-02 13:03:01 -08:00
|
|
|
return self
|
|
|
|
|
2021-01-14 15:07:35 -10:00
|
|
|
# Submobject organization
|
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def arrange(
|
|
|
|
self,
|
2022-12-16 20:35:26 -08:00
|
|
|
direction: Vect3 = RIGHT,
|
2022-02-13 15:11:35 +08:00
|
|
|
center: bool = True,
|
|
|
|
**kwargs
|
|
|
|
):
|
2021-01-14 15:07:35 -10:00
|
|
|
for m1, m2 in zip(self.submobjects, self.submobjects[1:]):
|
|
|
|
m2.next_to(m1, direction, **kwargs)
|
|
|
|
if center:
|
|
|
|
self.center()
|
|
|
|
return self
|
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def arrange_in_grid(
|
|
|
|
self,
|
|
|
|
n_rows: int | None = None,
|
|
|
|
n_cols: int | None = None,
|
|
|
|
buff: float | None = None,
|
|
|
|
h_buff: float | None = None,
|
|
|
|
v_buff: float | None = None,
|
|
|
|
buff_ratio: float | None = None,
|
2022-03-22 11:30:25 -07:00
|
|
|
h_buff_ratio: float = 0.5,
|
2022-02-13 15:11:35 +08:00
|
|
|
v_buff_ratio: float = 0.5,
|
2022-12-16 20:35:26 -08:00
|
|
|
aligned_edge: Vect3 = ORIGIN,
|
2022-02-13 15:11:35 +08:00
|
|
|
fill_rows_first: bool = True
|
|
|
|
):
|
2021-01-14 15:07:35 -10:00
|
|
|
submobs = self.submobjects
|
|
|
|
if n_rows is None and n_cols is None:
|
|
|
|
n_rows = int(np.sqrt(len(submobs)))
|
|
|
|
if n_rows is None:
|
|
|
|
n_rows = len(submobs) // n_cols
|
|
|
|
if n_cols is None:
|
|
|
|
n_cols = len(submobs) // n_rows
|
|
|
|
|
|
|
|
if buff is not None:
|
|
|
|
h_buff = buff
|
|
|
|
v_buff = buff
|
|
|
|
else:
|
|
|
|
if buff_ratio is not None:
|
|
|
|
v_buff_ratio = buff_ratio
|
|
|
|
h_buff_ratio = buff_ratio
|
|
|
|
if h_buff is None:
|
|
|
|
h_buff = h_buff_ratio * self[0].get_width()
|
|
|
|
if v_buff is None:
|
|
|
|
v_buff = v_buff_ratio * self[0].get_height()
|
|
|
|
|
|
|
|
x_unit = h_buff + max([sm.get_width() for sm in submobs])
|
|
|
|
y_unit = v_buff + max([sm.get_height() for sm in submobs])
|
|
|
|
|
|
|
|
for index, sm in enumerate(submobs):
|
|
|
|
if fill_rows_first:
|
|
|
|
x, y = index % n_cols, index // n_cols
|
|
|
|
else:
|
|
|
|
x, y = index // n_rows, index % n_rows
|
|
|
|
sm.move_to(ORIGIN, aligned_edge)
|
|
|
|
sm.shift(x * x_unit * RIGHT + y * y_unit * DOWN)
|
|
|
|
self.center()
|
|
|
|
return self
|
|
|
|
|
2022-05-03 12:40:43 -07:00
|
|
|
def arrange_to_fit_dim(self, length: float, dim: int, about_edge=ORIGIN):
|
|
|
|
ref_point = self.get_bounding_box_point(about_edge)
|
|
|
|
n_submobs = len(self.submobjects)
|
|
|
|
if n_submobs <= 1:
|
|
|
|
return
|
|
|
|
total_length = sum(sm.length_over_dim(dim) for sm in self.submobjects)
|
|
|
|
buff = (length - total_length) / (n_submobs - 1)
|
|
|
|
vect = np.zeros(self.dim)
|
|
|
|
vect[dim] = 1
|
|
|
|
x = 0
|
|
|
|
for submob in self.submobjects:
|
|
|
|
submob.set_coord(x, dim, -vect)
|
|
|
|
x += submob.length_over_dim(dim) + buff
|
|
|
|
self.move_to(ref_point, about_edge)
|
|
|
|
return self
|
|
|
|
|
2022-05-11 12:45:06 -07:00
|
|
|
def arrange_to_fit_width(self, width: float, about_edge=ORIGIN):
|
|
|
|
return self.arrange_to_fit_dim(width, 0, about_edge)
|
2022-05-03 12:40:43 -07:00
|
|
|
|
2022-05-11 12:45:06 -07:00
|
|
|
def arrange_to_fit_height(self, height: float, about_edge=ORIGIN):
|
|
|
|
return self.arrange_to_fit_dim(height, 1, about_edge)
|
2022-05-03 12:40:43 -07:00
|
|
|
|
2022-05-11 12:45:06 -07:00
|
|
|
def arrange_to_fit_depth(self, depth: float, about_edge=ORIGIN):
|
|
|
|
return self.arrange_to_fit_dim(depth, 2, about_edge)
|
2022-05-03 12:40:43 -07:00
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def sort(
|
|
|
|
self,
|
|
|
|
point_to_num_func: Callable[[np.ndarray], float] = lambda p: p[0],
|
2022-02-14 21:41:45 +08:00
|
|
|
submob_func: Callable[[Mobject]] | None = None
|
2022-02-13 15:11:35 +08:00
|
|
|
):
|
2021-01-14 15:07:35 -10:00
|
|
|
if submob_func is not None:
|
|
|
|
self.submobjects.sort(key=submob_func)
|
|
|
|
else:
|
|
|
|
self.submobjects.sort(key=lambda m: point_to_num_func(m.get_center()))
|
2021-11-08 21:43:57 -08:00
|
|
|
self.assemble_family()
|
2021-01-14 15:07:35 -10:00
|
|
|
return self
|
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def shuffle(self, recurse: bool = False):
|
2021-01-14 15:07:35 -10:00
|
|
|
if recurse:
|
|
|
|
for submob in self.submobjects:
|
|
|
|
submob.shuffle(recurse=True)
|
|
|
|
random.shuffle(self.submobjects)
|
2021-11-08 21:43:57 -08:00
|
|
|
self.assemble_family()
|
2021-01-14 15:07:35 -10:00
|
|
|
return self
|
|
|
|
|
2022-11-18 09:10:44 -08:00
|
|
|
def reverse_submobjects(self):
|
|
|
|
self.submobjects.reverse()
|
|
|
|
self.assemble_family()
|
|
|
|
return self
|
|
|
|
|
2022-04-21 14:32:27 -07:00
|
|
|
# Copying and serialization
|
2022-04-14 14:37:38 -07:00
|
|
|
|
2022-12-23 17:44:00 -07:00
|
|
|
def stash_mobject_pointers(func: Callable):
|
2022-04-22 19:02:44 -07:00
|
|
|
@wraps(func)
|
|
|
|
def wrapper(self, *args, **kwargs):
|
|
|
|
uncopied_attrs = ["parents", "target", "saved_state"]
|
|
|
|
stash = dict()
|
|
|
|
for attr in uncopied_attrs:
|
|
|
|
if hasattr(self, attr):
|
|
|
|
value = getattr(self, attr)
|
|
|
|
stash[attr] = value
|
2022-04-22 19:42:47 -07:00
|
|
|
null_value = [] if isinstance(value, list) else None
|
2022-04-22 19:02:44 -07:00
|
|
|
setattr(self, attr, null_value)
|
|
|
|
result = func(self, *args, **kwargs)
|
|
|
|
self.__dict__.update(stash)
|
|
|
|
return result
|
|
|
|
return wrapper
|
|
|
|
|
|
|
|
@stash_mobject_pointers
|
2022-04-21 14:32:27 -07:00
|
|
|
def serialize(self):
|
2022-04-22 19:02:44 -07:00
|
|
|
return pickle.dumps(self)
|
2021-01-14 14:15:58 -10:00
|
|
|
|
2022-04-22 11:42:26 -07:00
|
|
|
def deserialize(self, data: bytes):
|
|
|
|
self.become(pickle.loads(data))
|
|
|
|
return self
|
|
|
|
|
2022-04-23 10:16:35 -07:00
|
|
|
def deepcopy(self):
|
|
|
|
try:
|
|
|
|
# Often faster than deepcopy
|
|
|
|
return pickle.loads(pickle.dumps(self))
|
|
|
|
except AttributeError:
|
|
|
|
return copy.deepcopy(self)
|
|
|
|
|
2022-04-22 19:02:44 -07:00
|
|
|
@stash_mobject_pointers
|
|
|
|
def copy(self, deep: bool = False):
|
|
|
|
if deep:
|
2022-04-23 10:16:35 -07:00
|
|
|
return self.deepcopy()
|
2022-04-22 19:02:44 -07:00
|
|
|
|
|
|
|
result = copy.copy(self)
|
|
|
|
|
|
|
|
# The line above is only a shallow copy, so the internal
|
|
|
|
# data which are numpyu arrays or other mobjects still
|
|
|
|
# need to be further copied.
|
2023-01-15 16:05:18 -08:00
|
|
|
result.data = self.data.copy()
|
2022-04-23 10:16:35 -07:00
|
|
|
result.uniforms = {
|
|
|
|
key: np.array(value)
|
|
|
|
for key, value in self.uniforms.items()
|
|
|
|
}
|
2022-04-22 19:02:44 -07:00
|
|
|
|
2022-04-24 13:24:20 -07:00
|
|
|
# Instead of adding using result.add, which does some checks for updating
|
|
|
|
# updater statues and bounding box, just directly modify the family-related
|
|
|
|
# lists
|
|
|
|
result.submobjects = [sm.copy() for sm in self.submobjects]
|
|
|
|
for sm in result.submobjects:
|
|
|
|
sm.parents = [result]
|
|
|
|
result.family = [result, *it.chain(*(sm.get_family() for sm in result.submobjects))]
|
|
|
|
|
|
|
|
# Similarly, instead of calling match_updaters, since we know the status
|
|
|
|
# won't have changed, just directly match.
|
|
|
|
result.non_time_updaters = list(self.non_time_updaters)
|
|
|
|
result.time_based_updaters = list(self.time_based_updaters)
|
2023-01-25 16:43:47 -08:00
|
|
|
result._data_has_changed = True
|
2022-04-22 19:02:44 -07:00
|
|
|
|
|
|
|
family = self.get_family()
|
|
|
|
for attr, value in list(self.__dict__.items()):
|
2022-04-23 10:16:35 -07:00
|
|
|
if isinstance(value, Mobject) and value is not self:
|
|
|
|
if value in family:
|
|
|
|
setattr(result, attr, result.family[self.family.index(value)])
|
2022-04-22 19:02:44 -07:00
|
|
|
if isinstance(value, np.ndarray):
|
|
|
|
setattr(result, attr, value.copy())
|
|
|
|
if isinstance(value, ShaderWrapper):
|
|
|
|
setattr(result, attr, value.copy())
|
2022-04-21 14:32:27 -07:00
|
|
|
return result
|
2017-05-12 17:08:49 +02:00
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def generate_target(self, use_deepcopy: bool = False):
|
2022-04-22 19:02:44 -07:00
|
|
|
self.target = self.copy(deep=use_deepcopy)
|
2022-04-22 19:42:47 -07:00
|
|
|
self.target.saved_state = self.saved_state
|
2016-09-16 14:06:44 -07:00
|
|
|
return self.target
|
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def save_state(self, use_deepcopy: bool = False):
|
2022-04-22 19:02:44 -07:00
|
|
|
self.saved_state = self.copy(deep=use_deepcopy)
|
2022-04-22 19:42:47 -07:00
|
|
|
self.saved_state.target = self.target
|
2021-01-14 14:15:58 -10:00
|
|
|
return self
|
|
|
|
|
|
|
|
def restore(self):
|
2022-04-22 19:42:47 -07:00
|
|
|
if not hasattr(self, "saved_state") or self.saved_state is None:
|
2021-01-14 14:15:58 -10:00
|
|
|
raise Exception("Trying to restore without having saved")
|
|
|
|
self.become(self.saved_state)
|
|
|
|
return self
|
|
|
|
|
2022-04-21 15:00:58 -07:00
|
|
|
def save_to_file(self, file_path: str, supress_overwrite_warning: bool = False):
|
2022-04-20 21:42:59 -07:00
|
|
|
with open(file_path, "wb") as fp:
|
2022-04-21 14:32:27 -07:00
|
|
|
fp.write(self.serialize())
|
2022-04-20 21:42:59 -07:00
|
|
|
log.info(f"Saved mobject to {file_path}")
|
|
|
|
return self
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def load(file_path):
|
|
|
|
if not os.path.exists(file_path):
|
|
|
|
log.error(f"No file found at {file_path}")
|
|
|
|
sys.exit(2)
|
|
|
|
with open(file_path, "rb") as fp:
|
|
|
|
mobject = pickle.load(fp)
|
|
|
|
return mobject
|
|
|
|
|
2022-11-03 16:35:41 -07:00
|
|
|
def become(self, mobject: Mobject, match_updaters=False):
|
2022-04-21 14:32:27 -07:00
|
|
|
"""
|
|
|
|
Edit all data and submobjects to be idential
|
|
|
|
to another mobject
|
|
|
|
"""
|
|
|
|
self.align_family(mobject)
|
2022-05-29 16:37:44 -07:00
|
|
|
family1 = self.get_family()
|
|
|
|
family2 = mobject.get_family()
|
|
|
|
for sm1, sm2 in zip(family1, family2):
|
2022-04-21 14:32:27 -07:00
|
|
|
sm1.set_data(sm2.data)
|
|
|
|
sm1.set_uniforms(sm2.uniforms)
|
2023-01-16 13:55:53 -08:00
|
|
|
sm1.bounding_box[:] = sm2.bounding_box
|
2022-04-21 14:32:27 -07:00
|
|
|
sm1.shader_folder = sm2.shader_folder
|
|
|
|
sm1.texture_paths = sm2.texture_paths
|
|
|
|
sm1.depth_test = sm2.depth_test
|
|
|
|
sm1.render_primitive = sm2.render_primitive
|
2022-05-29 16:37:44 -07:00
|
|
|
# Make sure named family members carry over
|
|
|
|
for attr, value in list(mobject.__dict__.items()):
|
|
|
|
if isinstance(value, Mobject) and value in family2:
|
|
|
|
setattr(self, attr, family1[family2.index(value)])
|
2022-04-21 14:32:27 -07:00
|
|
|
self.refresh_bounding_box(recurse_down=True)
|
2022-11-03 16:35:41 -07:00
|
|
|
if match_updaters:
|
|
|
|
self.match_updaters(mobject)
|
2022-04-21 14:32:27 -07:00
|
|
|
return self
|
|
|
|
|
2022-12-29 12:02:20 -08:00
|
|
|
def looks_identical(self, mobject: Mobject) -> bool:
|
2022-04-27 09:52:27 -07:00
|
|
|
fam1 = self.family_members_with_points()
|
|
|
|
fam2 = mobject.family_members_with_points()
|
2022-04-23 18:50:45 -07:00
|
|
|
if len(fam1) != len(fam2):
|
|
|
|
return False
|
|
|
|
for m1, m2 in zip(fam1, fam2):
|
2023-01-15 16:05:18 -08:00
|
|
|
if m1.get_num_points() != m2.get_num_points():
|
|
|
|
return False
|
|
|
|
if not m1.data.dtype == m2.data.dtype:
|
|
|
|
return False
|
|
|
|
for key in m1.data.dtype.names:
|
|
|
|
if not np.isclose(m1.data[key], m2.data[key]).all():
|
|
|
|
return False
|
|
|
|
if set(m1.uniforms).difference(m2.uniforms):
|
|
|
|
return False
|
|
|
|
for key in m1.uniforms:
|
|
|
|
value1 = m1.uniforms[key]
|
|
|
|
value2 = m2.uniforms[key]
|
|
|
|
if isinstance(value1, np.ndarray) and isinstance(value2, np.ndarray) and not value1.size == value2.size:
|
|
|
|
return False
|
|
|
|
if not np.isclose(value1, value2).all():
|
2022-04-23 18:50:45 -07:00
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
2022-12-29 12:02:28 -08:00
|
|
|
def has_same_shape_as(self, mobject: Mobject) -> bool:
|
|
|
|
# Normalize both point sets by centering and making height 1
|
|
|
|
points1, points2 = (
|
|
|
|
(m.get_all_points() - m.get_center()) / m.get_height()
|
|
|
|
for m in (self, mobject)
|
|
|
|
)
|
|
|
|
if len(points1) != len(points2):
|
|
|
|
return False
|
|
|
|
return bool(np.isclose(points1, points2).all())
|
|
|
|
|
2022-04-21 14:32:27 -07:00
|
|
|
# Creating new Mobjects from this one
|
|
|
|
|
|
|
|
def replicate(self, n: int) -> Group:
|
|
|
|
group_class = self.get_group_class()
|
2022-04-22 19:02:44 -07:00
|
|
|
return group_class(*(self.copy() for _ in range(n)))
|
2022-04-21 14:32:27 -07:00
|
|
|
|
2022-11-18 09:11:29 -08:00
|
|
|
def get_grid(self,
|
|
|
|
n_rows: int,
|
|
|
|
n_cols: int,
|
|
|
|
height: float | None = None,
|
|
|
|
width: float | None = None,
|
|
|
|
group_by_rows: bool = False,
|
|
|
|
group_by_cols: bool = False,
|
|
|
|
**kwargs) -> Group:
|
2022-04-21 14:32:27 -07:00
|
|
|
"""
|
|
|
|
Returns a new mobject containing multiple copies of this one
|
|
|
|
arranged in a grid
|
|
|
|
"""
|
2022-11-18 09:11:29 -08:00
|
|
|
total = n_rows * n_cols
|
|
|
|
grid = self.replicate(total)
|
|
|
|
if group_by_cols:
|
|
|
|
kwargs["fill_rows_first"] = False
|
2022-04-21 14:32:27 -07:00
|
|
|
grid.arrange_in_grid(n_rows, n_cols, **kwargs)
|
|
|
|
if height is not None:
|
|
|
|
grid.set_height(height)
|
2022-11-18 09:11:29 -08:00
|
|
|
if width is not None:
|
|
|
|
grid.set_height(width)
|
|
|
|
|
|
|
|
group_class = self.get_group_class()
|
|
|
|
if group_by_rows:
|
|
|
|
return group_class(*(grid[n:n + n_cols] for n in range(0, total, n_cols)))
|
|
|
|
elif group_by_cols:
|
|
|
|
return group_class(*(grid[n:n + n_rows] for n in range(0, total, n_rows)))
|
|
|
|
else:
|
|
|
|
return grid
|
2022-04-21 14:32:27 -07:00
|
|
|
|
2018-08-12 12:17:32 -07:00
|
|
|
# Updating
|
2021-01-14 14:15:58 -10:00
|
|
|
|
2020-06-29 11:23:01 -07:00
|
|
|
def init_updaters(self):
|
2022-02-13 15:11:35 +08:00
|
|
|
self.time_based_updaters: list[TimeBasedUpdater] = []
|
|
|
|
self.non_time_updaters: list[NonTimeUpdater] = []
|
|
|
|
self.has_updaters: bool = False
|
|
|
|
self.updating_suspended: bool = False
|
2018-08-12 12:17:32 -07:00
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def update(self, dt: float = 0, recurse: bool = True):
|
2020-06-29 11:23:01 -07:00
|
|
|
if not self.has_updaters or self.updating_suspended:
|
2019-02-03 12:11:56 -08:00
|
|
|
return self
|
2020-03-19 10:46:50 -07:00
|
|
|
for updater in self.time_based_updaters:
|
|
|
|
updater(self, dt)
|
|
|
|
for updater in self.non_time_updaters:
|
|
|
|
updater(self)
|
2021-01-14 15:07:35 -10:00
|
|
|
if recurse:
|
2019-01-29 14:40:44 -08:00
|
|
|
for submob in self.submobjects:
|
2021-01-14 15:07:35 -10:00
|
|
|
submob.update(dt, recurse)
|
2019-01-29 14:40:44 -08:00
|
|
|
return self
|
2018-08-12 12:17:32 -07:00
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def get_time_based_updaters(self) -> list[TimeBasedUpdater]:
|
2020-03-19 10:46:50 -07:00
|
|
|
return self.time_based_updaters
|
2018-08-12 12:17:32 -07:00
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def has_time_based_updater(self) -> bool:
|
2020-03-19 10:46:50 -07:00
|
|
|
return len(self.time_based_updaters) > 0
|
2019-02-15 20:05:45 -08:00
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def get_updaters(self) -> list[Updater]:
|
2020-03-19 10:46:50 -07:00
|
|
|
return self.time_based_updaters + self.non_time_updaters
|
2018-08-12 12:17:32 -07:00
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def get_family_updaters(self) -> list[Updater]:
|
2020-06-29 11:23:01 -07:00
|
|
|
return list(it.chain(*[sm.get_updaters() for sm in self.get_family()]))
|
2019-04-04 14:29:52 -07:00
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def add_updater(
|
|
|
|
self,
|
|
|
|
update_function: Updater,
|
|
|
|
index: int | None = None,
|
|
|
|
call_updater: bool = True
|
|
|
|
):
|
2020-03-19 10:46:50 -07:00
|
|
|
if "dt" in get_parameters(update_function):
|
|
|
|
updater_list = self.time_based_updaters
|
|
|
|
else:
|
|
|
|
updater_list = self.non_time_updaters
|
|
|
|
|
2018-08-30 14:24:25 -07:00
|
|
|
if index is None:
|
2020-03-19 10:46:50 -07:00
|
|
|
updater_list.append(update_function)
|
2018-08-30 14:24:25 -07:00
|
|
|
else:
|
2020-03-19 10:46:50 -07:00
|
|
|
updater_list.insert(index, update_function)
|
|
|
|
|
2020-06-29 11:23:01 -07:00
|
|
|
self.refresh_has_updater_status()
|
2022-04-06 13:04:05 -07:00
|
|
|
for parent in self.parents:
|
|
|
|
parent.has_updaters = True
|
2020-06-29 11:23:01 -07:00
|
|
|
if call_updater:
|
2021-02-25 08:46:56 -08:00
|
|
|
self.update(dt=0)
|
2018-08-27 16:32:12 -07:00
|
|
|
return self
|
2018-08-12 12:17:32 -07:00
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def remove_updater(self, update_function: Updater):
|
2020-03-19 10:46:50 -07:00
|
|
|
for updater_list in [self.time_based_updaters, self.non_time_updaters]:
|
|
|
|
while update_function in updater_list:
|
|
|
|
updater_list.remove(update_function)
|
2021-02-10 16:45:23 -08:00
|
|
|
self.refresh_has_updater_status()
|
2018-08-12 12:17:32 -07:00
|
|
|
return self
|
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def clear_updaters(self, recurse: bool = True):
|
2020-03-19 10:46:50 -07:00
|
|
|
self.time_based_updaters = []
|
|
|
|
self.non_time_updaters = []
|
2021-01-14 15:07:35 -10:00
|
|
|
if recurse:
|
2019-02-03 12:11:56 -08:00
|
|
|
for submob in self.submobjects:
|
|
|
|
submob.clear_updaters()
|
2022-04-14 14:37:12 -07:00
|
|
|
self.refresh_has_updater_status()
|
2018-11-18 10:29:41 -06:00
|
|
|
return self
|
2018-08-12 12:17:32 -07:00
|
|
|
|
2022-02-14 21:41:45 +08:00
|
|
|
def match_updaters(self, mobject: Mobject):
|
2019-04-10 14:57:15 -07:00
|
|
|
self.clear_updaters()
|
|
|
|
for updater in mobject.get_updaters():
|
|
|
|
self.add_updater(updater)
|
|
|
|
return self
|
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def suspend_updating(self, recurse: bool = True):
|
2020-06-29 11:23:01 -07:00
|
|
|
self.updating_suspended = True
|
2021-01-14 15:07:35 -10:00
|
|
|
if recurse:
|
2019-01-29 14:40:44 -08:00
|
|
|
for submob in self.submobjects:
|
2021-01-14 15:07:35 -10:00
|
|
|
submob.suspend_updating(recurse)
|
2019-01-29 14:40:44 -08:00
|
|
|
return self
|
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def resume_updating(self, recurse: bool = True, call_updater: bool = True):
|
2020-06-29 11:23:01 -07:00
|
|
|
self.updating_suspended = False
|
2021-01-14 15:07:35 -10:00
|
|
|
if recurse:
|
2019-01-29 14:40:44 -08:00
|
|
|
for submob in self.submobjects:
|
2021-01-14 15:07:35 -10:00
|
|
|
submob.resume_updating(recurse)
|
2020-06-26 22:16:04 -07:00
|
|
|
for parent in self.parents:
|
2021-01-14 15:07:35 -10:00
|
|
|
parent.resume_updating(recurse=False, call_updater=False)
|
2020-06-26 22:16:04 -07:00
|
|
|
if call_updater:
|
2021-01-14 15:07:35 -10:00
|
|
|
self.update(dt=0, recurse=recurse)
|
2020-06-26 22:16:04 -07:00
|
|
|
return self
|
|
|
|
|
2020-06-29 11:23:01 -07:00
|
|
|
def refresh_has_updater_status(self):
|
2021-01-14 01:38:40 -10:00
|
|
|
self.has_updaters = any(mob.get_updaters() for mob in self.get_family())
|
2019-01-29 14:40:44 -08:00
|
|
|
return self
|
|
|
|
|
2022-04-14 16:27:58 -07:00
|
|
|
# Check if mark as static or not for camera
|
|
|
|
|
|
|
|
def is_changing(self) -> bool:
|
2022-04-22 23:14:57 -07:00
|
|
|
return self._is_animating or self.has_updaters
|
2022-04-14 16:27:58 -07:00
|
|
|
|
2022-12-29 18:52:37 -08:00
|
|
|
def set_animating_status(self, is_animating: bool, recurse: bool = True):
|
2023-01-13 13:06:50 -08:00
|
|
|
for mob in (*self.get_family(recurse), *self.get_ancestors()):
|
2022-04-20 21:46:43 -07:00
|
|
|
mob._is_animating = is_animating
|
|
|
|
return self
|
2022-04-14 16:27:58 -07:00
|
|
|
|
2018-04-09 13:47:46 -07:00
|
|
|
# Transforming operations
|
2021-01-14 14:15:58 -10:00
|
|
|
|
2022-12-16 20:35:26 -08:00
|
|
|
def shift(self, vector: Vect3):
|
2021-01-14 01:01:43 -10:00
|
|
|
self.apply_points_function(
|
|
|
|
lambda points: points + vector,
|
|
|
|
about_edge=None,
|
|
|
|
works_on_bounding_box=True,
|
|
|
|
)
|
2017-05-12 17:08:49 +02:00
|
|
|
return self
|
2016-04-09 20:03:57 -07:00
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def scale(
|
|
|
|
self,
|
|
|
|
scale_factor: float | npt.ArrayLike,
|
|
|
|
min_scale_factor: float = 1e-8,
|
2022-12-16 20:35:26 -08:00
|
|
|
about_point: Vect3 | None = None,
|
|
|
|
about_edge: Vect3 = ORIGIN
|
2022-02-13 15:11:35 +08:00
|
|
|
):
|
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.
|
|
|
|
"""
|
2022-04-15 00:55:02 +08:00
|
|
|
if isinstance(scale_factor, numbers.Number):
|
2022-01-25 14:05:32 +08:00
|
|
|
scale_factor = max(scale_factor, min_scale_factor)
|
2022-04-15 00:55:02 +08:00
|
|
|
else:
|
|
|
|
scale_factor = np.array(scale_factor).clip(min=min_scale_factor)
|
2021-08-09 15:42:32 -07:00
|
|
|
self.apply_points_function(
|
|
|
|
lambda points: scale_factor * points,
|
|
|
|
about_point=about_point,
|
|
|
|
about_edge=about_edge,
|
|
|
|
works_on_bounding_box=True,
|
|
|
|
)
|
2021-08-21 10:36:18 -07:00
|
|
|
for mob in self.get_family():
|
|
|
|
mob._handle_scale_side_effects(scale_factor)
|
2015-11-02 13:03:01 -08:00
|
|
|
return self
|
|
|
|
|
2021-08-09 16:06:19 -07:00
|
|
|
def _handle_scale_side_effects(self, scale_factor):
|
|
|
|
# In case subclasses, such as DecimalNumber, need to make
|
|
|
|
# any other changes when the size gets altered
|
|
|
|
pass
|
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def stretch(self, factor: float, dim: int, **kwargs):
|
2021-01-14 01:01:43 -10:00
|
|
|
def func(points):
|
|
|
|
points[:, dim] *= factor
|
|
|
|
return points
|
|
|
|
self.apply_points_function(func, works_on_bounding_box=True, **kwargs)
|
|
|
|
return self
|
|
|
|
|
2022-12-16 20:35:26 -08:00
|
|
|
def rotate_about_origin(self, angle: float, axis: Vect3 = OUT):
|
2018-04-06 13:58:59 -07:00
|
|
|
return self.rotate(angle, axis, about_point=ORIGIN)
|
2016-12-06 13:29:21 -08:00
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def rotate(
|
|
|
|
self,
|
|
|
|
angle: float,
|
2022-12-16 20:35:26 -08:00
|
|
|
axis: Vect3 = OUT,
|
|
|
|
about_point: Vect3 | None = None,
|
2022-02-13 15:11:35 +08:00
|
|
|
**kwargs
|
|
|
|
):
|
2020-02-20 10:03:36 -08:00
|
|
|
rot_matrix_T = rotation_matrix_transpose(angle, axis)
|
2021-01-05 21:58:43 -08:00
|
|
|
self.apply_points_function(
|
2020-02-20 10:03:36 -08:00
|
|
|
lambda points: np.dot(points, rot_matrix_T),
|
2022-02-13 15:11:35 +08:00
|
|
|
about_point,
|
2018-01-20 10:56:52 -08:00
|
|
|
**kwargs
|
|
|
|
)
|
2015-11-02 13:03:01 -08:00
|
|
|
return self
|
|
|
|
|
2022-12-16 20:35:26 -08:00
|
|
|
def flip(self, axis: Vect3 = UP, **kwargs):
|
2018-04-06 13:58:59 -07:00
|
|
|
return self.rotate(TAU / 2, axis, **kwargs)
|
2018-01-20 11:33:09 -08:00
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def apply_function(self, function: Callable[[np.ndarray], np.ndarray], **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
|
2021-01-05 21:58:43 -08:00
|
|
|
self.apply_points_function(
|
2020-02-19 23:43:33 -08:00
|
|
|
lambda points: np.array([function(p) for p in 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
|
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def apply_function_to_position(self, function: Callable[[np.ndarray], np.ndarray]):
|
2018-05-21 12:11:46 -07:00
|
|
|
self.move_to(function(self.get_center()))
|
|
|
|
return self
|
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def apply_function_to_submobject_positions(
|
|
|
|
self,
|
|
|
|
function: Callable[[np.ndarray], np.ndarray]
|
|
|
|
):
|
2018-05-21 12:11:46 -07:00
|
|
|
for submob in self.submobjects:
|
|
|
|
submob.apply_function_to_position(function)
|
|
|
|
return self
|
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def apply_matrix(self, matrix: npt.ArrayLike, **kwargs):
|
2018-04-06 13:58:59 -07:00
|
|
|
# Default to applying matrix about the origin, not mobjects center
|
2018-08-28 09:44:10 -07:00
|
|
|
if ("about_point" not in kwargs) and ("about_edge" not in kwargs):
|
2018-01-20 11:18:43 -08:00
|
|
|
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
|
2021-01-05 21:58:43 -08:00
|
|
|
self.apply_points_function(
|
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
|
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def apply_complex_function(self, function: Callable[[complex], complex], **kwargs):
|
2018-11-27 13:40:03 -08:00
|
|
|
def R3_func(point):
|
|
|
|
x, y, z = point
|
|
|
|
xy_complex = function(complex(x, y))
|
|
|
|
return [
|
|
|
|
xy_complex.real,
|
|
|
|
xy_complex.imag,
|
|
|
|
z
|
|
|
|
]
|
2022-02-13 15:11:35 +08:00
|
|
|
return self.apply_function(R3_func, **kwargs)
|
|
|
|
|
|
|
|
def wag(
|
|
|
|
self,
|
2022-12-16 20:35:26 -08:00
|
|
|
direction: Vect3 = RIGHT,
|
|
|
|
axis: Vect3 = DOWN,
|
2022-02-13 15:11:35 +08:00
|
|
|
wag_factor: float = 1.0
|
|
|
|
):
|
2016-07-18 14:03:25 -07:00
|
|
|
for mob in self.family_members_with_points():
|
2021-01-10 18:51:47 -08:00
|
|
|
alphas = np.dot(mob.get_points(), np.transpose(axis))
|
2015-11-02 13:03:01 -08:00
|
|
|
alphas -= min(alphas)
|
|
|
|
alphas /= max(alphas)
|
|
|
|
alphas = alphas**wag_factor
|
2021-01-10 18:51:47 -08:00
|
|
|
mob.set_points(mob.get_points() + np.dot(
|
2015-11-02 13:03:01 -08:00
|
|
|
alphas.reshape((len(alphas), 1)),
|
2016-04-10 12:34:28 -07:00
|
|
|
np.array(direction).reshape((1, mob.dim))
|
2021-01-10 18:51:47 -08:00
|
|
|
))
|
2015-11-02 13:03:01 -08:00
|
|
|
return self
|
|
|
|
|
2018-04-09 13:47:46 -07:00
|
|
|
# Positioning methods
|
2021-01-14 14:15:58 -10:00
|
|
|
|
2015-06-10 22:00:35 -07:00
|
|
|
def center(self):
|
|
|
|
self.shift(-self.get_center())
|
|
|
|
return self
|
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def align_on_border(
|
|
|
|
self,
|
2022-12-16 20:35:26 -08:00
|
|
|
direction: Vect3,
|
2022-02-13 15:11:35 +08:00
|
|
|
buff: float = 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)
|
2020-02-13 10:54:09 -08:00
|
|
|
point_to_align = self.get_bounding_box_point(direction)
|
2016-07-25 16:04:54 -07:00
|
|
|
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
|
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def to_corner(
|
|
|
|
self,
|
2022-12-16 20:35:26 -08:00
|
|
|
corner: Vect3 = LEFT + DOWN,
|
2022-02-13 15:11:35 +08:00
|
|
|
buff: float = DEFAULT_MOBJECT_TO_EDGE_BUFFER
|
|
|
|
):
|
2015-11-02 13:03:01 -08:00
|
|
|
return self.align_on_border(corner, buff)
|
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def to_edge(
|
|
|
|
self,
|
2022-12-16 20:35:26 -08:00
|
|
|
edge: Vect3 = LEFT,
|
2022-02-13 15:11:35 +08:00
|
|
|
buff: float = DEFAULT_MOBJECT_TO_EDGE_BUFFER
|
|
|
|
):
|
2015-11-02 13:03:01 -08:00
|
|
|
return self.align_on_border(edge, buff)
|
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def next_to(
|
|
|
|
self,
|
2022-12-16 20:35:26 -08:00
|
|
|
mobject_or_point: Mobject | Vect3,
|
|
|
|
direction: Vect3 = RIGHT,
|
2022-02-13 15:11:35 +08:00
|
|
|
buff: float = DEFAULT_MOBJECT_TO_MOBJECT_BUFFER,
|
2022-12-16 20:35:26 -08:00
|
|
|
aligned_edge: Vect3 = ORIGIN,
|
2022-02-14 21:41:45 +08:00
|
|
|
submobject_to_align: Mobject | None = None,
|
2022-02-13 15:11:35 +08:00
|
|
|
index_of_submobject_to_align: int | slice | None = None,
|
2022-12-16 20:35:26 -08:00
|
|
|
coor_mask: Vect3 = np.array([1, 1, 1]),
|
2022-02-13 15:11:35 +08: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
|
2020-02-13 10:54:09 -08:00
|
|
|
target_point = target_aligner.get_bounding_box_point(
|
2018-01-16 18:40:26 -08:00
|
|
|
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
|
2020-02-13 10:54:09 -08:00
|
|
|
point_to_align = aligner.get_bounding_box_point(aligned_edge - direction)
|
2021-01-11 10:57:23 -10:00
|
|
|
self.shift((target_point - point_to_align + buff * direction) * coor_mask)
|
2015-09-24 10:54:59 -07:00
|
|
|
return self
|
|
|
|
|
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
|
|
|
|
|
2022-12-16 20:35:26 -08:00
|
|
|
def stretch_about_point(self, factor: float, dim: int, point: Vect3):
|
2018-04-06 13:58:59 -07:00
|
|
|
return self.stretch(factor, dim, about_point=point)
|
2017-02-27 22:03:33 -08:00
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def stretch_in_place(self, factor: float, dim: int):
|
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
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def rescale_to_fit(self, length: float, dim: int, stretch: bool = 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
|
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def stretch_to_fit_width(self, width: float, **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
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def stretch_to_fit_height(self, height: float, **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
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def stretch_to_fit_depth(self, depth: float, **kwargs):
|
2021-10-16 20:59:31 +08:00
|
|
|
return self.rescale_to_fit(depth, 2, stretch=True, **kwargs)
|
2018-02-27 13:55:38 -08:00
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def set_width(self, width: float, stretch: bool = False, **kwargs):
|
2018-08-08 10:30:52 -07:00
|
|
|
return self.rescale_to_fit(width, 0, stretch=stretch, **kwargs)
|
2015-10-20 21:55:46 -07:00
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def set_height(self, height: float, stretch: bool = False, **kwargs):
|
2018-08-08 10:30:52 -07:00
|
|
|
return self.rescale_to_fit(height, 1, stretch=stretch, **kwargs)
|
2016-07-15 18:16:06 -07:00
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def set_depth(self, depth: float, stretch: bool = False, **kwargs):
|
2018-08-08 10:30:52 -07:00
|
|
|
return self.rescale_to_fit(depth, 2, stretch=stretch, **kwargs)
|
2017-09-06 20:18:19 -07:00
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def set_max_width(self, max_width: float, **kwargs):
|
2021-08-19 14:47:30 -07:00
|
|
|
if self.get_width() > max_width:
|
|
|
|
self.set_width(max_width, **kwargs)
|
|
|
|
return self
|
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def set_max_height(self, max_height: float, **kwargs):
|
2021-08-19 14:47:30 -07:00
|
|
|
if self.get_height() > max_height:
|
|
|
|
self.set_height(max_height, **kwargs)
|
|
|
|
return self
|
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def set_max_depth(self, max_depth: float, **kwargs):
|
2021-08-19 14:47:30 -07:00
|
|
|
if self.get_depth() > max_depth:
|
|
|
|
self.set_depth(max_depth, **kwargs)
|
|
|
|
return self
|
|
|
|
|
2022-02-14 14:12:06 +08:00
|
|
|
def set_min_width(self, min_width: float, **kwargs):
|
2022-02-13 15:16:16 -08:00
|
|
|
if self.get_width() < min_width:
|
|
|
|
self.set_width(min_width, **kwargs)
|
|
|
|
return self
|
|
|
|
|
2022-02-14 14:12:06 +08:00
|
|
|
def set_min_height(self, min_height: float, **kwargs):
|
2022-02-13 15:16:16 -08:00
|
|
|
if self.get_height() < min_height:
|
|
|
|
self.set_height(min_height, **kwargs)
|
|
|
|
return self
|
|
|
|
|
2022-02-14 14:12:06 +08:00
|
|
|
def set_min_depth(self, min_depth: float, **kwargs):
|
2022-02-13 15:16:16 -08:00
|
|
|
if self.get_depth() < min_depth:
|
|
|
|
self.set_depth(min_depth, **kwargs)
|
|
|
|
return self
|
|
|
|
|
2022-12-16 20:35:26 -08:00
|
|
|
def set_coord(self, value: float, dim: int, direction: Vect3 = ORIGIN):
|
2019-02-14 11:34:06 -08:00
|
|
|
curr = self.get_coord(dim, direction)
|
|
|
|
shift_vect = np.zeros(self.dim)
|
|
|
|
shift_vect[dim] = value - curr
|
|
|
|
self.shift(shift_vect)
|
|
|
|
return self
|
|
|
|
|
2022-12-16 20:35:26 -08:00
|
|
|
def set_x(self, x: float, direction: Vect3 = ORIGIN):
|
2019-02-14 11:34:06 -08:00
|
|
|
return self.set_coord(x, 0, direction)
|
|
|
|
|
2022-12-16 20:35:26 -08:00
|
|
|
def set_y(self, y: float, direction: Vect3 = ORIGIN):
|
2019-02-14 11:34:06 -08:00
|
|
|
return self.set_coord(y, 1, direction)
|
|
|
|
|
2022-12-16 20:35:26 -08:00
|
|
|
def set_z(self, z: float, direction: Vect3 = ORIGIN):
|
2019-02-14 11:34:06 -08:00
|
|
|
return self.set_coord(z, 2, direction)
|
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def space_out_submobjects(self, factor: float = 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
|
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def move_to(
|
|
|
|
self,
|
2022-12-16 20:35:26 -08:00
|
|
|
point_or_mobject: Mobject | Vect3,
|
|
|
|
aligned_edge: Vect3 = ORIGIN,
|
|
|
|
coor_mask: Vect3 = np.array([1, 1, 1])
|
2022-02-13 15:11:35 +08:00
|
|
|
):
|
2016-07-15 18:16:06 -07:00
|
|
|
if isinstance(point_or_mobject, Mobject):
|
2020-02-13 10:54:09 -08:00
|
|
|
target = point_or_mobject.get_bounding_box_point(aligned_edge)
|
2016-07-15 18:16:06 -07:00
|
|
|
else:
|
|
|
|
target = point_or_mobject
|
2020-02-13 10:54:09 -08:00
|
|
|
point_to_align = self.get_bounding_box_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
|
|
|
|
2022-02-14 21:41:45 +08:00
|
|
|
def replace(self, mobject: Mobject, dim_to_match: int = 0, stretch: bool = False):
|
2016-04-17 00:31:38 -07:00
|
|
|
if not mobject.get_num_points() and not mobject.submobjects:
|
2021-01-07 11:49:03 -08:00
|
|
|
self.scale(0)
|
2015-08-07 18:10:00 -07:00
|
|
|
return self
|
2015-08-03 22:23:00 -07:00
|
|
|
if stretch:
|
2020-06-13 15:21:47 -07:00
|
|
|
for i in range(self.dim):
|
|
|
|
self.rescale_to_fit(mobject.length_over_dim(i), i, stretch=True)
|
2015-08-03 22:23:00 -07:00
|
|
|
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
|
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def surround(
|
|
|
|
self,
|
2022-02-14 21:41:45 +08:00
|
|
|
mobject: Mobject,
|
2022-02-13 15:11:35 +08:00
|
|
|
dim_to_match: int = 0,
|
|
|
|
stretch: bool = False,
|
|
|
|
buff: float = MED_SMALL_BUFF
|
|
|
|
):
|
2018-01-10 17:57:22 -08:00
|
|
|
self.replace(mobject, dim_to_match, stretch)
|
2019-02-07 09:26:18 -08:00
|
|
|
length = mobject.length_over_dim(dim_to_match)
|
2021-01-05 22:05:15 -08:00
|
|
|
self.scale((length + buff) / length)
|
2019-02-07 09:26:18 -08:00
|
|
|
return self
|
2018-01-10 17:57:22 -08:00
|
|
|
|
2022-12-16 20:35:26 -08:00
|
|
|
def put_start_and_end_on(self, start: Vect3, end: Vect3):
|
2019-02-07 14:52:40 -08:00
|
|
|
curr_start, curr_end = self.get_start_and_end()
|
|
|
|
curr_vect = curr_end - curr_start
|
2016-02-21 15:44:54 -08:00
|
|
|
if np.all(curr_vect == 0):
|
|
|
|
raise Exception("Cannot position endpoints of closed loop")
|
|
|
|
target_vect = end - start
|
2019-02-07 21:14:30 -08:00
|
|
|
self.scale(
|
|
|
|
get_norm(target_vect) / get_norm(curr_vect),
|
|
|
|
about_point=curr_start,
|
|
|
|
)
|
2016-02-21 15:44:54 -08:00
|
|
|
self.rotate(
|
2020-06-28 10:05:00 -07:00
|
|
|
angle_of_vector(target_vect) - angle_of_vector(curr_vect),
|
2016-02-21 15:44:54 -08:00
|
|
|
)
|
2021-08-04 22:52:13 +08:00
|
|
|
self.rotate(
|
2021-08-21 10:36:18 -07:00
|
|
|
np.arctan2(curr_vect[2], get_norm(curr_vect[:2])) - np.arctan2(target_vect[2], get_norm(target_vect[:2])),
|
|
|
|
axis=np.array([-target_vect[1], target_vect[0], 0]),
|
2021-08-04 22:52:13 +08:00
|
|
|
)
|
|
|
|
self.shift(start - self.get_start())
|
2016-02-21 15:44:54 -08:00
|
|
|
return self
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
# Color functions
|
2021-01-14 14:15:58 -10:00
|
|
|
|
2023-01-25 16:43:47 -08:00
|
|
|
@affects_family_data
|
2022-02-13 15:11:35 +08:00
|
|
|
def set_rgba_array(
|
|
|
|
self,
|
|
|
|
rgba_array: npt.ArrayLike,
|
2023-01-15 16:46:03 -08:00
|
|
|
name: str = "rgba",
|
2022-02-13 15:11:35 +08:00
|
|
|
recurse: bool = False
|
|
|
|
):
|
2021-02-25 08:46:56 -08:00
|
|
|
for mob in self.get_family(recurse):
|
2023-01-15 12:34:59 -08:00
|
|
|
data = mob.data if mob.get_num_points() > 0 else mob._data_defaults
|
|
|
|
data[name][:] = rgba_array
|
2021-02-25 08:46:56 -08:00
|
|
|
return self
|
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def set_color_by_rgba_func(
|
|
|
|
self,
|
2022-12-16 20:35:26 -08:00
|
|
|
func: Callable[[Vect3], Vect4],
|
2022-02-13 15:11:35 +08:00
|
|
|
recurse: bool = True
|
|
|
|
):
|
2021-02-25 08:46:56 -08:00
|
|
|
"""
|
|
|
|
Func should take in a point in R3 and output an rgba value
|
|
|
|
"""
|
|
|
|
for mob in self.get_family(recurse):
|
|
|
|
rgba_array = [func(point) for point in mob.get_points()]
|
|
|
|
mob.set_rgba_array(rgba_array)
|
|
|
|
return self
|
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def set_color_by_rgb_func(
|
|
|
|
self,
|
2022-12-16 20:35:26 -08:00
|
|
|
func: Callable[[Vect3], Vect3],
|
2022-02-13 15:11:35 +08:00
|
|
|
opacity: float = 1,
|
|
|
|
recurse: bool = True
|
|
|
|
):
|
2021-02-25 08:46:56 -08:00
|
|
|
"""
|
|
|
|
Func should take in a point in R3 and output an rgb value
|
|
|
|
"""
|
|
|
|
for mob in self.get_family(recurse):
|
|
|
|
rgba_array = [[*func(point), opacity] for point in mob.get_points()]
|
|
|
|
mob.set_rgba_array(rgba_array)
|
|
|
|
return self
|
|
|
|
|
2023-01-25 16:43:47 -08:00
|
|
|
@affects_family_data
|
2022-02-13 15:11:35 +08:00
|
|
|
def set_rgba_array_by_color(
|
|
|
|
self,
|
2022-04-12 19:19:59 +08:00
|
|
|
color: ManimColor | Iterable[ManimColor] | None = None,
|
|
|
|
opacity: float | Iterable[float] | None = None,
|
2023-01-15 16:46:03 -08:00
|
|
|
name: str = "rgba",
|
2022-02-13 15:11:35 +08:00
|
|
|
recurse: bool = True
|
|
|
|
):
|
2022-03-22 10:35:49 -07:00
|
|
|
for mob in self.get_family(recurse):
|
2023-01-15 12:34:59 -08:00
|
|
|
data = mob.data if mob.has_points() > 0 else mob._data_defaults
|
2022-03-22 10:35:49 -07:00
|
|
|
if color is not None:
|
2023-01-19 09:51:19 -08:00
|
|
|
if isinstance(color, list):
|
|
|
|
rgbs = np.array(list(map(color_to_rgb, color)))
|
2023-01-24 13:04:13 -08:00
|
|
|
rgbs = resize_with_interpolation(rgbs, len(data))
|
2023-01-19 09:51:19 -08:00
|
|
|
else:
|
|
|
|
rgbs = color_to_rgb(color)
|
|
|
|
data[name][:, :3] = rgbs
|
2022-03-22 10:35:49 -07:00
|
|
|
if opacity is not None:
|
2023-01-19 09:51:19 -08:00
|
|
|
if isinstance(opacity, list):
|
|
|
|
opacity = resize_with_interpolation(np.array(opacity), len(data))
|
|
|
|
data[name][:, 3] = opacity
|
2022-03-22 10:35:49 -07:00
|
|
|
return self
|
2021-01-11 16:37:51 -10:00
|
|
|
|
2022-04-12 19:19:59 +08:00
|
|
|
def set_color(
|
|
|
|
self,
|
|
|
|
color: ManimColor | Iterable[ManimColor] | None,
|
|
|
|
opacity: float | Iterable[float] | None = None,
|
|
|
|
recurse: bool = True
|
|
|
|
):
|
2021-02-25 08:46:56 -08:00
|
|
|
self.set_rgba_array_by_color(color, opacity, recurse=False)
|
|
|
|
# Recurse to submobjects differently from how set_rgba_array_by_color
|
2021-01-11 16:37:51 -10:00
|
|
|
# in case they implement set_color differently
|
2021-01-11 17:03:12 -10:00
|
|
|
if recurse:
|
2017-10-26 21:30:59 -07:00
|
|
|
for submob in self.submobjects:
|
2021-01-11 17:03:12 -10:00
|
|
|
submob.set_color(color, recurse=True)
|
2017-10-26 21:30:59 -07:00
|
|
|
return self
|
2016-04-09 20:03:57 -07:00
|
|
|
|
2022-04-12 19:19:59 +08:00
|
|
|
def set_opacity(
|
|
|
|
self,
|
|
|
|
opacity: float | Iterable[float] | None,
|
|
|
|
recurse: bool = True
|
|
|
|
):
|
2021-02-25 08:46:56 -08:00
|
|
|
self.set_rgba_array_by_color(color=None, opacity=opacity, recurse=False)
|
2021-01-11 17:03:12 -10:00
|
|
|
if recurse:
|
2020-06-07 12:23:35 -07:00
|
|
|
for submob in self.submobjects:
|
2021-01-11 17:03:12 -10:00
|
|
|
submob.set_opacity(opacity, recurse=True)
|
2020-06-07 12:23:35 -07:00
|
|
|
return self
|
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def get_color(self) -> str:
|
2023-01-15 16:46:03 -08:00
|
|
|
return rgb_to_hex(self.data["rgba"][0, :3])
|
2020-06-07 12:23:35 -07:00
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def get_opacity(self) -> float:
|
2023-01-15 16:46:03 -08:00
|
|
|
return self.data["rgba"][0, 3]
|
2020-06-07 12:23:35 -07:00
|
|
|
|
2022-02-13 19:02:28 +08:00
|
|
|
def set_color_by_gradient(self, *colors: ManimColor):
|
2022-11-03 16:17:17 -07:00
|
|
|
if self.has_points():
|
|
|
|
self.set_color(colors)
|
|
|
|
else:
|
|
|
|
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
|
|
|
|
2022-02-13 19:02:28 +08:00
|
|
|
def set_submobject_colors_by_gradient(self, *colors: ManimColor):
|
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
|
|
|
|
2021-01-12 11:09:53 -10:00
|
|
|
# mobs = self.family_members_with_points()
|
|
|
|
mobs = self.submobjects
|
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):
|
2021-01-12 11:09:53 -10:00
|
|
|
mob.set_color(color)
|
2016-07-22 11:22:31 -07:00
|
|
|
return self
|
2015-06-10 22:00:35 -07:00
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def fade(self, darkness: float = 0.5, recurse: bool = True):
|
2021-01-11 17:03:12 -10:00
|
|
|
self.set_opacity(1.0 - darkness, recurse=recurse)
|
2015-06-10 22:00:35 -07:00
|
|
|
|
2023-01-16 19:33:57 -08:00
|
|
|
def get_shading(self) -> np.ndarray:
|
|
|
|
return self.uniforms["shading"]
|
2020-06-06 16:55:56 -07:00
|
|
|
|
2023-01-16 19:33:57 -08:00
|
|
|
def set_shading(
|
|
|
|
self,
|
|
|
|
reflectiveness: float | None = None,
|
|
|
|
gloss: float | None = None,
|
|
|
|
shadow: float | None = None,
|
|
|
|
recurse: bool = True
|
|
|
|
):
|
|
|
|
"""
|
|
|
|
Larger reflectiveness makes things brighter when facing the light
|
|
|
|
Larger shadow makes faces opposite the light darker
|
|
|
|
Makes parts bright where light gets reflected toward the camera
|
|
|
|
"""
|
2021-01-11 17:03:12 -10:00
|
|
|
for mob in self.get_family(recurse):
|
2023-01-16 19:33:57 -08:00
|
|
|
for i, value in enumerate([reflectiveness, gloss, shadow]):
|
|
|
|
if value is not None:
|
|
|
|
mob.uniforms["shading"][i] = value
|
2020-06-06 16:55:56 -07:00
|
|
|
return self
|
|
|
|
|
2023-01-16 19:33:57 -08:00
|
|
|
def get_reflectiveness(self) -> float:
|
|
|
|
return self.get_shading()[0]
|
|
|
|
|
|
|
|
def get_gloss(self) -> float:
|
|
|
|
return self.get_shading()[1]
|
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def get_shadow(self) -> float:
|
2023-01-16 19:33:57 -08:00
|
|
|
return self.get_shading()[2]
|
2020-06-06 16:55:56 -07:00
|
|
|
|
2023-01-16 19:33:57 -08:00
|
|
|
def set_reflectiveness(self, reflectiveness: float, recurse: bool = True):
|
|
|
|
self.set_shading(reflectiveness=reflectiveness, recurse=recurse)
|
2020-06-06 16:55:56 -07:00
|
|
|
return self
|
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def set_gloss(self, gloss: float, recurse: bool = True):
|
2023-01-16 19:33:57 -08:00
|
|
|
self.set_shading(gloss=gloss, recurse=recurse)
|
|
|
|
return self
|
|
|
|
|
|
|
|
def set_shadow(self, shadow: float, recurse: bool = True):
|
|
|
|
self.set_shading(shadow=shadow, recurse=recurse)
|
2021-11-08 21:43:57 -08:00
|
|
|
return self
|
|
|
|
|
2021-01-14 15:07:35 -10:00
|
|
|
# Background rectangle
|
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def add_background_rectangle(
|
|
|
|
self,
|
2022-02-13 19:02:28 +08:00
|
|
|
color: ManimColor | None = None,
|
2022-02-13 15:11:35 +08:00
|
|
|
opacity: float = 0.75,
|
|
|
|
**kwargs
|
|
|
|
):
|
2021-01-14 15:07:35 -10:00
|
|
|
# TODO, this does not behave well when the mobject has points,
|
|
|
|
# since it gets displayed on top
|
|
|
|
from manimlib.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
|
|
|
|
|
2021-01-14 01:01:43 -10:00
|
|
|
# Getters
|
2015-11-02 13:03:01 -08:00
|
|
|
|
2022-12-16 20:35:26 -08:00
|
|
|
def get_bounding_box_point(self, direction: Vect3) -> Vect3:
|
2020-02-13 11:43:59 -08:00
|
|
|
bb = self.get_bounding_box()
|
2021-01-14 09:31:41 -10:00
|
|
|
indices = (np.sign(direction) + 1).astype(int)
|
|
|
|
return np.array([
|
|
|
|
bb[indices[i]][i]
|
|
|
|
for i in range(3)
|
|
|
|
])
|
2020-02-13 11:43:59 -08:00
|
|
|
|
2022-12-16 20:35:26 -08:00
|
|
|
def get_edge_center(self, direction: Vect3) -> Vect3:
|
2020-02-13 10:54:09 -08:00
|
|
|
return self.get_bounding_box_point(direction)
|
2015-11-02 13:03:01 -08:00
|
|
|
|
2022-12-16 20:35:26 -08:00
|
|
|
def get_corner(self, direction: Vect3) -> Vect3:
|
2020-02-13 10:54:09 -08:00
|
|
|
return self.get_bounding_box_point(direction)
|
2015-06-13 19:00:23 -07:00
|
|
|
|
2022-04-20 21:43:16 -07:00
|
|
|
def get_all_corners(self):
|
|
|
|
bb = self.get_bounding_box()
|
|
|
|
return np.array([
|
|
|
|
[bb[indices[-i + 1]][i] for i in range(3)]
|
2022-04-22 16:42:45 +08:00
|
|
|
for indices in it.product([0, 2], repeat=3)
|
2022-04-20 21:43:16 -07:00
|
|
|
])
|
|
|
|
|
2022-12-16 20:35:26 -08:00
|
|
|
def get_center(self) -> Vect3:
|
2021-01-14 09:31:41 -10:00
|
|
|
return self.get_bounding_box()[1]
|
2015-08-07 18:10:00 -07:00
|
|
|
|
2022-12-16 20:35:26 -08:00
|
|
|
def get_center_of_mass(self) -> Vect3:
|
2020-02-19 23:43:33 -08:00
|
|
|
return self.get_all_points().mean(0)
|
2015-06-10 22:00:35 -07:00
|
|
|
|
2022-12-16 20:35:26 -08:00
|
|
|
def get_boundary_point(self, direction: Vect3) -> Vect3:
|
2021-01-14 01:01:43 -10:00
|
|
|
all_points = self.get_all_points()
|
2021-01-04 17:25:05 -08:00
|
|
|
boundary_directions = all_points - self.get_center()
|
|
|
|
norms = np.linalg.norm(boundary_directions, axis=1)
|
|
|
|
boundary_directions /= np.repeat(norms, 3).reshape((len(norms), 3))
|
|
|
|
index = np.argmax(np.dot(boundary_directions, np.array(direction).T))
|
2018-10-05 17:18:49 -07:00
|
|
|
return all_points[index]
|
2015-09-25 19:43:53 -07:00
|
|
|
|
2022-12-16 20:35:26 -08:00
|
|
|
def get_continuous_bounding_box_point(self, direction: Vect3) -> Vect3:
|
2021-01-04 17:25:05 -08:00
|
|
|
dl, center, ur = self.get_bounding_box()
|
|
|
|
corner_vect = (ur - center)
|
|
|
|
return center + direction / np.max(np.abs(np.true_divide(
|
|
|
|
direction, corner_vect,
|
|
|
|
out=np.zeros(len(direction)),
|
|
|
|
where=((corner_vect) != 0)
|
|
|
|
)))
|
|
|
|
|
2022-12-16 20:35:26 -08:00
|
|
|
def get_top(self) -> Vect3:
|
2015-08-17 11:12:56 -07:00
|
|
|
return self.get_edge_center(UP)
|
2015-08-07 18:10:00 -07:00
|
|
|
|
2022-12-16 20:35:26 -08:00
|
|
|
def get_bottom(self) -> Vect3:
|
2015-08-17 11:12:56 -07:00
|
|
|
return self.get_edge_center(DOWN)
|
2015-08-07 18:10:00 -07:00
|
|
|
|
2022-12-16 20:35:26 -08:00
|
|
|
def get_right(self) -> Vect3:
|
2015-08-17 11:12:56 -07:00
|
|
|
return self.get_edge_center(RIGHT)
|
2015-08-07 18:10:00 -07:00
|
|
|
|
2022-12-16 20:35:26 -08:00
|
|
|
def get_left(self) -> Vect3:
|
2015-08-17 11:12:56 -07:00
|
|
|
return self.get_edge_center(LEFT)
|
2015-08-07 18:10:00 -07:00
|
|
|
|
2022-12-16 20:35:26 -08:00
|
|
|
def get_zenith(self) -> Vect3:
|
2017-08-27 14:43:18 -07:00
|
|
|
return self.get_edge_center(OUT)
|
|
|
|
|
2022-12-16 20:35:26 -08:00
|
|
|
def get_nadir(self) -> Vect3:
|
2017-08-27 14:43:18 -07:00
|
|
|
return self.get_edge_center(IN)
|
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def length_over_dim(self, dim: int) -> float:
|
2020-02-20 10:03:36 -08:00
|
|
|
bb = self.get_bounding_box()
|
2021-02-03 14:18:21 -08:00
|
|
|
return abs((bb[2] - bb[0])[dim])
|
2015-11-02 13:03:01 -08:00
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def get_width(self) -> float:
|
2015-11-02 13:03:01 -08:00
|
|
|
return self.length_over_dim(0)
|
2015-06-10 22:00:35 -07:00
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def get_height(self) -> float:
|
2015-11-02 13:03:01 -08:00
|
|
|
return self.length_over_dim(1)
|
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def get_depth(self) -> float:
|
2017-08-27 14:43:18 -07:00
|
|
|
return self.length_over_dim(2)
|
|
|
|
|
2022-12-16 20:35:26 -08:00
|
|
|
def get_coord(self, dim: int, direction: Vect3 = ORIGIN) -> float:
|
2019-02-14 11:34:06 -08:00
|
|
|
"""
|
|
|
|
Meant to generalize get_x, get_y, get_z
|
|
|
|
"""
|
2020-02-20 10:03:36 -08:00
|
|
|
return self.get_bounding_box_point(direction)[dim]
|
2019-02-14 11:34:06 -08:00
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def get_x(self, direction=ORIGIN) -> float:
|
2019-02-14 11:34:06 -08:00
|
|
|
return self.get_coord(0, direction)
|
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def get_y(self, direction=ORIGIN) -> float:
|
2019-02-14 11:34:06 -08:00
|
|
|
return self.get_coord(1, direction)
|
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def get_z(self, direction=ORIGIN) -> float:
|
2019-02-14 11:34:06 -08:00
|
|
|
return self.get_coord(2, direction)
|
|
|
|
|
2022-12-16 20:35:26 -08:00
|
|
|
def get_start(self) -> Vect3:
|
2019-02-14 11:34:06 -08:00
|
|
|
self.throw_error_if_no_points()
|
2021-08-21 17:06:37 -07:00
|
|
|
return self.get_points()[0].copy()
|
2019-02-14 11:34:06 -08:00
|
|
|
|
2022-12-16 20:35:26 -08:00
|
|
|
def get_end(self) -> Vect3:
|
2019-02-14 11:34:06 -08:00
|
|
|
self.throw_error_if_no_points()
|
2021-08-21 17:06:37 -07:00
|
|
|
return self.get_points()[-1].copy()
|
2019-02-14 11:34:06 -08:00
|
|
|
|
2022-12-16 20:35:26 -08:00
|
|
|
def get_start_and_end(self) -> tuple[Vect3, Vect3]:
|
2021-08-21 17:06:37 -07:00
|
|
|
self.throw_error_if_no_points()
|
|
|
|
points = self.get_points()
|
|
|
|
return (points[0].copy(), points[-1].copy())
|
2019-02-14 11:34:06 -08:00
|
|
|
|
2022-12-16 20:35:26 -08:00
|
|
|
def point_from_proportion(self, alpha: float) -> Vect3:
|
2021-01-14 01:01:43 -10:00
|
|
|
points = self.get_points()
|
|
|
|
i, subalpha = integer_interpolate(0, len(points) - 1, alpha)
|
|
|
|
return interpolate(points[i], points[i + 1], subalpha)
|
2015-06-10 22:00:35 -07:00
|
|
|
|
2020-03-14 16:53:02 -07:00
|
|
|
def pfp(self, alpha):
|
2022-11-03 11:29:49 -07:00
|
|
|
"""Abbreviation for point_from_proportion"""
|
2020-03-14 16:53:02 -07:00
|
|
|
return self.point_from_proportion(alpha)
|
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def get_pieces(self, n_pieces: int) -> Group:
|
2018-08-20 15:49:31 -07:00
|
|
|
template = self.copy()
|
2020-02-21 10:56:40 -08:00
|
|
|
template.set_submobjects([])
|
2018-08-20 15:49:31 -07:00
|
|
|
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:])
|
|
|
|
])
|
|
|
|
|
2019-02-14 11:34:06 -08:00
|
|
|
def get_z_index_reference_point(self):
|
|
|
|
# TODO, better place to define default z_index_group?
|
|
|
|
z_index_group = getattr(self, "z_index_group", self)
|
|
|
|
return z_index_group.get_center()
|
|
|
|
|
|
|
|
# Match other mobject properties
|
|
|
|
|
2022-02-14 21:41:45 +08:00
|
|
|
def match_color(self, mobject: Mobject):
|
2019-02-14 11:34:06 -08:00
|
|
|
return self.set_color(mobject.get_color())
|
|
|
|
|
2022-02-14 21:41:45 +08:00
|
|
|
def match_dim_size(self, mobject: Mobject, dim: int, **kwargs):
|
2019-02-14 11:34:06 -08:00
|
|
|
return self.rescale_to_fit(
|
|
|
|
mobject.length_over_dim(dim), dim,
|
|
|
|
**kwargs
|
|
|
|
)
|
|
|
|
|
2022-02-14 21:41:45 +08:00
|
|
|
def match_width(self, mobject: Mobject, **kwargs):
|
2019-02-14 11:34:06 -08:00
|
|
|
return self.match_dim_size(mobject, 0, **kwargs)
|
|
|
|
|
2022-02-14 21:41:45 +08:00
|
|
|
def match_height(self, mobject: Mobject, **kwargs):
|
2019-02-14 11:34:06 -08:00
|
|
|
return self.match_dim_size(mobject, 1, **kwargs)
|
|
|
|
|
2022-02-14 21:41:45 +08:00
|
|
|
def match_depth(self, mobject: Mobject, **kwargs):
|
2019-02-14 11:34:06 -08:00
|
|
|
return self.match_dim_size(mobject, 2, **kwargs)
|
|
|
|
|
2022-02-14 14:12:06 +08:00
|
|
|
def match_coord(
|
|
|
|
self,
|
2022-12-16 20:35:26 -08:00
|
|
|
mobject_or_point: Mobject | Vect3,
|
2022-02-14 14:12:06 +08:00
|
|
|
dim: int,
|
2022-12-16 20:35:26 -08:00
|
|
|
direction: Vect3 = ORIGIN
|
2022-02-14 14:12:06 +08:00
|
|
|
):
|
2022-02-13 15:16:16 -08:00
|
|
|
if isinstance(mobject_or_point, Mobject):
|
|
|
|
coord = mobject_or_point.get_coord(dim, direction)
|
|
|
|
else:
|
|
|
|
coord = mobject_or_point[dim]
|
|
|
|
return self.set_coord(coord, dim=dim, direction=direction)
|
2019-02-14 11:34:06 -08:00
|
|
|
|
2022-02-14 14:12:06 +08:00
|
|
|
def match_x(
|
|
|
|
self,
|
2022-12-16 20:35:26 -08:00
|
|
|
mobject_or_point: Mobject | Vect3,
|
|
|
|
direction: Vect3 = ORIGIN
|
2022-02-14 14:12:06 +08:00
|
|
|
):
|
2022-02-13 15:16:16 -08:00
|
|
|
return self.match_coord(mobject_or_point, 0, direction)
|
2019-02-14 11:34:06 -08:00
|
|
|
|
2022-02-14 14:12:06 +08:00
|
|
|
def match_y(
|
|
|
|
self,
|
2022-12-16 20:35:26 -08:00
|
|
|
mobject_or_point: Mobject | Vect3,
|
|
|
|
direction: Vect3 = ORIGIN
|
2022-02-14 14:12:06 +08:00
|
|
|
):
|
2022-02-13 15:16:16 -08:00
|
|
|
return self.match_coord(mobject_or_point, 1, direction)
|
2019-02-14 11:34:06 -08:00
|
|
|
|
2022-02-14 14:12:06 +08:00
|
|
|
def match_z(
|
|
|
|
self,
|
2022-12-16 20:35:26 -08:00
|
|
|
mobject_or_point: Mobject | Vect3,
|
|
|
|
direction: Vect3 = ORIGIN
|
2022-02-14 14:12:06 +08:00
|
|
|
):
|
2022-02-13 15:16:16 -08:00
|
|
|
return self.match_coord(mobject_or_point, 2, direction)
|
2019-02-14 11:34:06 -08:00
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def align_to(
|
|
|
|
self,
|
2022-12-16 20:35:26 -08:00
|
|
|
mobject_or_point: Mobject | Vect3,
|
|
|
|
direction: Vect3 = ORIGIN
|
2022-02-13 15:11:35 +08:00
|
|
|
):
|
2019-02-14 11:34:06 -08:00
|
|
|
"""
|
|
|
|
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_vect = RIGHT) moves mob1
|
|
|
|
horizontally so that it's center is directly above/below
|
|
|
|
the center of mob2
|
|
|
|
"""
|
|
|
|
if isinstance(mobject_or_point, Mobject):
|
2020-02-13 10:54:09 -08:00
|
|
|
point = mobject_or_point.get_bounding_box_point(direction)
|
2019-02-14 11:34:06 -08:00
|
|
|
else:
|
|
|
|
point = mobject_or_point
|
|
|
|
|
|
|
|
for dim in range(self.dim):
|
|
|
|
if direction[dim] != 0:
|
|
|
|
self.set_coord(point[dim], dim, direction)
|
|
|
|
return self
|
|
|
|
|
2018-01-24 11:25:55 -08:00
|
|
|
def get_group_class(self):
|
|
|
|
return Group
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
# Alignment
|
2021-01-12 11:09:53 -10:00
|
|
|
|
2022-02-14 21:41:45 +08:00
|
|
|
def align_data_and_family(self, mobject: Mobject) -> None:
|
2021-01-12 11:09:53 -10:00
|
|
|
self.align_family(mobject)
|
|
|
|
self.align_data(mobject)
|
|
|
|
|
2022-02-14 21:41:45 +08:00
|
|
|
def align_data(self, mobject: Mobject) -> None:
|
2020-02-21 10:56:40 -08:00
|
|
|
for mob1, mob2 in zip(self.get_family(), mobject.get_family()):
|
|
|
|
mob1.align_points(mob2)
|
2015-11-02 14:09:49 -08:00
|
|
|
|
2022-02-14 21:41:45 +08:00
|
|
|
def align_points(self, mobject: Mobject):
|
2021-01-11 10:57:23 -10:00
|
|
|
max_len = max(self.get_num_points(), mobject.get_num_points())
|
2021-01-12 11:09:53 -10:00
|
|
|
for mob in (self, mobject):
|
|
|
|
mob.resize_points(max_len, resize_func=resize_preserving_order)
|
2016-04-10 12:34:28 -07:00
|
|
|
return self
|
|
|
|
|
2022-02-14 21:41:45 +08:00
|
|
|
def align_family(self, mobject: Mobject):
|
2019-02-11 20:51:57 -08:00
|
|
|
mob1 = self
|
|
|
|
mob2 = mobject
|
2021-01-18 08:06:12 -10:00
|
|
|
n1 = len(mob1)
|
|
|
|
n2 = len(mob2)
|
2021-01-13 11:55:26 -10:00
|
|
|
if n1 != n2:
|
|
|
|
mob1.add_n_more_submobjects(max(0, n2 - n1))
|
|
|
|
mob2.add_n_more_submobjects(max(0, n1 - n2))
|
2020-02-21 10:56:40 -08:00
|
|
|
# Recurse
|
|
|
|
for sm1, sm2 in zip(mob1.submobjects, mob2.submobjects):
|
2021-01-12 11:09:53 -10:00
|
|
|
sm1.align_family(sm2)
|
2016-04-14 19:30:47 -07:00
|
|
|
return self
|
|
|
|
|
2016-04-17 00:31:38 -07:00
|
|
|
def push_self_into_submobjects(self):
|
2022-04-20 22:07:10 -07:00
|
|
|
copy = self.copy()
|
2020-02-22 13:19:51 -08:00
|
|
|
copy.set_submobjects([])
|
2021-01-11 12:39:14 -10:00
|
|
|
self.resize_points(0)
|
2016-04-14 19:30:47 -07:00
|
|
|
self.add(copy)
|
|
|
|
return self
|
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def add_n_more_submobjects(self, n: int):
|
2019-02-11 20:51:57 -08:00
|
|
|
if n == 0:
|
2021-01-13 11:55:26 -10:00
|
|
|
return self
|
2019-02-11 20:51:57 -08:00
|
|
|
|
2016-04-17 12:59:53 -07:00
|
|
|
curr = len(self.submobjects)
|
2019-02-11 20:51:57 -08:00
|
|
|
if curr == 0:
|
|
|
|
# If empty, simply add n point mobjects
|
2021-01-18 08:06:12 -10:00
|
|
|
null_mob = self.copy()
|
|
|
|
null_mob.set_points([self.get_center()])
|
2020-02-21 10:56:40 -08:00
|
|
|
self.set_submobjects([
|
2021-01-18 08:06:12 -10:00
|
|
|
null_mob.copy()
|
2019-02-11 20:51:57 -08:00
|
|
|
for k in range(n)
|
2020-02-21 10:56:40 -08:00
|
|
|
])
|
2021-01-14 01:01:43 -10:00
|
|
|
return self
|
2019-02-11 20:51:57 -08:00
|
|
|
target = curr + n
|
|
|
|
repeat_indices = (np.arange(target) * curr) // target
|
|
|
|
split_factors = [
|
2020-02-20 15:52:23 -08:00
|
|
|
(repeat_indices == i).sum()
|
2019-02-11 20:51:57 -08:00
|
|
|
for i in range(curr)
|
|
|
|
]
|
|
|
|
new_submobs = []
|
|
|
|
for submob, sf in zip(self.submobjects, split_factors):
|
|
|
|
new_submobs.append(submob)
|
|
|
|
for k in range(1, sf):
|
2023-01-25 09:50:16 -08:00
|
|
|
new_submobs.append(submob.invisible_copy())
|
2020-02-21 10:56:40 -08:00
|
|
|
self.set_submobjects(new_submobs)
|
2016-04-14 19:30:47 -07:00
|
|
|
return self
|
|
|
|
|
2023-01-25 09:50:16 -08:00
|
|
|
def invisible_copy(self):
|
|
|
|
return self.copy().set_opacity(0)
|
|
|
|
|
2021-01-14 14:15:58 -10:00
|
|
|
# Interpolate
|
|
|
|
|
2023-01-25 16:43:47 -08:00
|
|
|
@affects_data
|
2022-02-13 15:11:35 +08:00
|
|
|
def interpolate(
|
|
|
|
self,
|
2022-02-14 21:41:45 +08:00
|
|
|
mobject1: Mobject,
|
|
|
|
mobject2: Mobject,
|
2022-02-13 15:11:35 +08:00
|
|
|
alpha: float,
|
|
|
|
path_func: Callable[[np.ndarray, np.ndarray, float], np.ndarray] = straight_path
|
|
|
|
):
|
2023-01-15 16:49:24 -08:00
|
|
|
keys = [k for k in self.data.dtype.names if k not in self.locked_data_keys]
|
|
|
|
for key in keys:
|
2023-01-15 18:01:37 -08:00
|
|
|
func = path_func if key in self.pointlike_data_keys else interpolate
|
2023-01-16 13:27:20 -08:00
|
|
|
md1 = mobject1.data[key]
|
|
|
|
md2 = mobject2.data[key]
|
|
|
|
if key in self.const_data_keys:
|
|
|
|
md1 = md1[0]
|
|
|
|
md2 = md2[0]
|
|
|
|
self.data[key] = func(md1, md2, alpha)
|
2023-01-15 16:49:24 -08:00
|
|
|
|
2021-01-12 12:15:32 -10:00
|
|
|
for key in self.uniforms:
|
|
|
|
self.uniforms[key] = interpolate(
|
|
|
|
mobject1.uniforms[key],
|
|
|
|
mobject2.uniforms[key],
|
|
|
|
alpha
|
|
|
|
)
|
2023-01-13 14:58:52 -08:00
|
|
|
self.bounding_box[:] = path_func(
|
|
|
|
mobject1.bounding_box, mobject2.bounding_box, alpha
|
|
|
|
)
|
2020-06-06 17:01:54 -07:00
|
|
|
return self
|
|
|
|
|
2021-01-14 14:15:58 -10:00
|
|
|
def pointwise_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 subclass
|
|
|
|
|
2021-01-12 16:08:35 -10:00
|
|
|
# Locking data
|
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def lock_data(self, keys: Iterable[str]):
|
2021-01-12 11:09:53 -10:00
|
|
|
"""
|
|
|
|
To speed up some animations, particularly transformations,
|
|
|
|
it can be handy to acknowledge which pieces of data
|
|
|
|
won't change during the animation so that calls to
|
|
|
|
interpolate can skip this, and so that it's not
|
|
|
|
read into the shader_wrapper objects needlessly
|
|
|
|
"""
|
|
|
|
if self.has_updaters:
|
|
|
|
return
|
|
|
|
self.locked_data_keys = set(keys)
|
|
|
|
|
2022-02-14 21:41:45 +08:00
|
|
|
def lock_matching_data(self, mobject1: Mobject, mobject2: Mobject):
|
2021-01-12 11:09:53 -10:00
|
|
|
for sm, sm1, sm2 in zip(self.get_family(), mobject1.get_family(), mobject2.get_family()):
|
2023-01-15 16:46:27 -08:00
|
|
|
if sm.data.dtype == sm1.data.dtype == sm2.data.dtype:
|
2023-01-16 13:27:20 -08:00
|
|
|
names = sm.data.dtype.names
|
|
|
|
sm.lock_data(filter(
|
|
|
|
lambda name: arrays_match(sm1.data[name], sm2.data[name]),
|
|
|
|
names,
|
|
|
|
))
|
|
|
|
sm.const_data_keys = set(filter(
|
|
|
|
lambda name: all(
|
|
|
|
array_is_constant(mob.data[name])
|
|
|
|
for mob in (sm, sm1, sm2)
|
|
|
|
),
|
|
|
|
names
|
|
|
|
))
|
|
|
|
|
2021-01-12 11:09:53 -10:00
|
|
|
return self
|
|
|
|
|
|
|
|
def unlock_data(self):
|
|
|
|
for mob in self.get_family():
|
|
|
|
mob.locked_data_keys = set()
|
2023-01-16 13:27:20 -08:00
|
|
|
mob.const_data_keys = set()
|
2021-01-12 11:09:53 -10:00
|
|
|
|
2020-06-27 00:01:45 -07:00
|
|
|
# Operations touching shader uniforms
|
2021-01-12 11:09:53 -10:00
|
|
|
|
2022-12-23 17:45:35 -07:00
|
|
|
def affects_shader_info_id(func: Callable):
|
2021-02-05 11:52:21 +01:00
|
|
|
@wraps(func)
|
2020-06-27 00:01:45 -07:00
|
|
|
def wrapper(self):
|
|
|
|
for mob in self.get_family():
|
|
|
|
func(mob)
|
2020-06-29 18:17:18 -07:00
|
|
|
mob.refresh_shader_wrapper_id()
|
2021-01-18 16:39:29 -08:00
|
|
|
return self
|
2020-06-27 00:01:45 -07:00
|
|
|
return wrapper
|
|
|
|
|
|
|
|
@affects_shader_info_id
|
|
|
|
def fix_in_frame(self):
|
2021-01-12 12:15:32 -10:00
|
|
|
self.uniforms["is_fixed_in_frame"] = 1.0
|
2021-11-30 11:30:34 -08:00
|
|
|
self.is_fixed_in_frame = True
|
2020-06-27 00:01:45 -07:00
|
|
|
return self
|
|
|
|
|
|
|
|
@affects_shader_info_id
|
|
|
|
def unfix_from_frame(self):
|
2021-01-12 12:15:32 -10:00
|
|
|
self.uniforms["is_fixed_in_frame"] = 0.0
|
2021-11-30 11:30:34 -08:00
|
|
|
self.is_fixed_in_frame = False
|
2020-06-27 00:01:45 -07:00
|
|
|
return self
|
|
|
|
|
|
|
|
@affects_shader_info_id
|
|
|
|
def apply_depth_test(self):
|
|
|
|
self.depth_test = True
|
|
|
|
return self
|
|
|
|
|
|
|
|
@affects_shader_info_id
|
|
|
|
def deactivate_depth_test(self):
|
|
|
|
self.depth_test = False
|
|
|
|
return self
|
|
|
|
|
2021-01-09 18:52:54 -08:00
|
|
|
# Shader code manipulation
|
2021-01-12 11:09:53 -10:00
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def replace_shader_code(self, old: str, new: str):
|
2021-01-14 14:15:58 -10:00
|
|
|
# TODO, will this work with VMobject structure, given
|
|
|
|
# that it does not simpler return shader_wrappers of
|
|
|
|
# family?
|
2021-01-09 18:52:54 -08:00
|
|
|
for wrapper in self.get_shader_wrapper_list():
|
|
|
|
wrapper.replace_code(old, new)
|
|
|
|
return self
|
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def set_color_by_code(self, glsl_code: str):
|
2021-01-09 18:52:54 -08:00
|
|
|
"""
|
|
|
|
Takes a snippet of code and inserts it into a
|
|
|
|
context which has the following variables:
|
|
|
|
vec4 color, vec3 point, vec3 unit_normal.
|
|
|
|
The code should change the color variable
|
|
|
|
"""
|
|
|
|
self.replace_shader_code(
|
|
|
|
"///// INSERT COLOR FUNCTION HERE /////",
|
|
|
|
glsl_code
|
|
|
|
)
|
|
|
|
return self
|
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def set_color_by_xyz_func(
|
|
|
|
self,
|
|
|
|
glsl_snippet: str,
|
|
|
|
min_value: float = -5.0,
|
|
|
|
max_value: float = 5.0,
|
|
|
|
colormap: str = "viridis"
|
|
|
|
):
|
2021-01-09 18:52:54 -08:00
|
|
|
"""
|
|
|
|
Pass in a glsl expression in terms of x, y and z which returns
|
|
|
|
a float.
|
|
|
|
"""
|
2021-01-10 14:12:15 -08:00
|
|
|
# TODO, add a version of this which changes the point data instead
|
|
|
|
# of the shader code
|
2021-01-09 18:52:54 -08:00
|
|
|
for char in "xyz":
|
|
|
|
glsl_snippet = glsl_snippet.replace(char, "point." + char)
|
2021-01-10 14:12:15 -08:00
|
|
|
rgb_list = get_colormap_list(colormap)
|
2021-01-09 18:52:54 -08:00
|
|
|
self.set_color_by_code(
|
2021-01-10 08:35:06 -08:00
|
|
|
"color.rgb = float_to_color({}, {}, {}, {});".format(
|
2021-01-10 14:12:15 -08:00
|
|
|
glsl_snippet,
|
|
|
|
float(min_value),
|
|
|
|
float(max_value),
|
|
|
|
get_colormap_code(rgb_list)
|
2021-01-09 18:52:54 -08:00
|
|
|
)
|
|
|
|
)
|
|
|
|
return self
|
|
|
|
|
|
|
|
# For shader data
|
2021-01-12 11:09:53 -10:00
|
|
|
|
2023-01-25 10:49:30 -08:00
|
|
|
def init_shader_data(self, ctx: Context):
|
2023-01-24 15:53:43 -08:00
|
|
|
self.shader_indices = np.zeros(0)
|
2020-06-29 18:17:18 -07:00
|
|
|
self.shader_wrapper = ShaderWrapper(
|
2023-01-25 13:45:18 -08:00
|
|
|
ctx=ctx,
|
2023-01-15 20:01:37 -08:00
|
|
|
vert_data=self.data,
|
2021-01-05 23:14:16 -08:00
|
|
|
shader_folder=self.shader_folder,
|
2020-06-15 12:01:54 -07:00
|
|
|
texture_paths=self.texture_paths,
|
|
|
|
depth_test=self.depth_test,
|
2020-12-04 08:11:33 -08:00
|
|
|
render_primitive=self.render_primitive,
|
2020-06-15 12:01:54 -07:00
|
|
|
)
|
2020-02-13 15:41:57 -08:00
|
|
|
|
2020-06-29 18:17:18 -07:00
|
|
|
def refresh_shader_wrapper_id(self):
|
2023-01-25 10:37:12 -08:00
|
|
|
if self._shaders_initialized:
|
|
|
|
self.shader_wrapper.refresh_id()
|
2020-06-27 00:01:45 -07:00
|
|
|
return self
|
|
|
|
|
2023-01-25 10:49:30 -08:00
|
|
|
def get_shader_wrapper(self, ctx: Context) -> ShaderWrapper:
|
2023-01-25 10:37:12 -08:00
|
|
|
if not self._shaders_initialized:
|
2023-01-25 10:49:30 -08:00
|
|
|
self.init_shader_data(ctx)
|
2023-01-25 10:37:12 -08:00
|
|
|
self._shaders_initialized = True
|
|
|
|
|
2021-01-11 10:57:23 -10:00
|
|
|
self.shader_wrapper.vert_data = self.get_shader_data()
|
2020-06-29 18:17:18 -07:00
|
|
|
self.shader_wrapper.vert_indices = self.get_shader_vert_indices()
|
2023-01-25 12:10:39 -08:00
|
|
|
self.shader_wrapper.uniforms.update(self.get_uniforms())
|
2020-06-29 18:17:18 -07:00
|
|
|
self.shader_wrapper.depth_test = self.depth_test
|
|
|
|
return self.shader_wrapper
|
|
|
|
|
2023-01-25 10:49:30 -08:00
|
|
|
def get_shader_wrapper_list(self, ctx: Context) -> list[ShaderWrapper]:
|
2020-06-29 18:17:18 -07:00
|
|
|
shader_wrappers = it.chain(
|
2023-01-25 10:49:30 -08:00
|
|
|
[self.get_shader_wrapper(ctx)],
|
|
|
|
*[sm.get_shader_wrapper_list(ctx) for sm in self.submobjects]
|
2020-02-17 12:15:53 -08:00
|
|
|
)
|
2020-06-29 18:17:18 -07:00
|
|
|
batches = batch_by_property(shader_wrappers, lambda sw: sw.get_id())
|
2020-02-17 12:15:53 -08:00
|
|
|
|
|
|
|
result = []
|
2020-06-29 18:17:18 -07:00
|
|
|
for wrapper_group, sid in batches:
|
|
|
|
shader_wrapper = wrapper_group[0]
|
|
|
|
if not shader_wrapper.is_valid():
|
2020-06-29 11:05:09 -07:00
|
|
|
continue
|
2020-06-29 18:17:18 -07:00
|
|
|
shader_wrapper.combine_with(*wrapper_group[1:])
|
|
|
|
if len(shader_wrapper.vert_data) > 0:
|
|
|
|
result.append(shader_wrapper)
|
2020-02-17 12:15:53 -08:00
|
|
|
return result
|
2020-02-13 10:39:26 -08:00
|
|
|
|
2021-01-11 10:57:23 -10:00
|
|
|
def get_shader_data(self):
|
2023-01-15 20:01:37 -08:00
|
|
|
return self.data
|
2021-01-12 11:09:53 -10:00
|
|
|
|
2023-01-15 20:01:37 -08:00
|
|
|
def get_uniforms(self):
|
2021-01-12 12:15:32 -10:00
|
|
|
return self.uniforms
|
2020-06-08 15:57:12 -07:00
|
|
|
|
2020-06-29 11:05:09 -07:00
|
|
|
def get_shader_vert_indices(self):
|
|
|
|
return self.shader_indices
|
|
|
|
|
2023-01-25 10:49:30 -08:00
|
|
|
def render(self, ctx: Context, camera_uniforms: dict):
|
2023-01-25 16:43:47 -08:00
|
|
|
if self._data_has_changed:
|
2023-01-25 10:49:30 -08:00
|
|
|
self.shader_wrappers = self.get_shader_wrapper_list(ctx)
|
2023-01-25 14:13:56 -08:00
|
|
|
for shader_wrapper in self.shader_wrappers:
|
2023-01-25 16:43:47 -08:00
|
|
|
shader_wrapper.generate_vao()
|
2023-01-25 14:13:56 -08:00
|
|
|
self._data_has_changed = False
|
2023-01-25 10:49:30 -08:00
|
|
|
for shader_wrapper in self.shader_wrappers:
|
2023-01-25 14:13:56 -08:00
|
|
|
shader_wrapper.uniforms.update(self.get_uniforms())
|
|
|
|
shader_wrapper.uniforms.update(camera_uniforms)
|
|
|
|
shader_wrapper.pre_render()
|
2023-01-25 10:49:30 -08:00
|
|
|
shader_wrapper.render()
|
|
|
|
|
2021-01-28 14:02:43 +05:30
|
|
|
# Event Handlers
|
2021-02-04 12:35:45 -08:00
|
|
|
"""
|
2021-01-28 14:02:43 +05:30
|
|
|
Event handling follows the Event Bubbling model of DOM in javascript.
|
|
|
|
Return false to stop the event bubbling.
|
|
|
|
To learn more visit https://www.quirksmode.org/js/events_order.html
|
2021-02-02 16:04:50 +05:30
|
|
|
|
|
|
|
Event Callback Argument is a callable function taking two arguments:
|
|
|
|
1. Mobject
|
|
|
|
2. EventData
|
2021-01-28 14:02:43 +05:30
|
|
|
"""
|
|
|
|
|
2021-02-02 16:04:50 +05:30
|
|
|
def init_event_listners(self):
|
2022-02-13 15:11:35 +08:00
|
|
|
self.event_listners: list[EventListner] = []
|
2021-01-28 14:02:43 +05:30
|
|
|
|
2022-02-13 20:03:05 +08:00
|
|
|
def add_event_listner(
|
|
|
|
self,
|
|
|
|
event_type: EventType,
|
|
|
|
event_callback: Callable[[Mobject, dict[str]]]
|
|
|
|
):
|
2021-02-02 16:04:50 +05:30
|
|
|
event_listner = EventListner(self, event_type, event_callback)
|
|
|
|
self.event_listners.append(event_listner)
|
|
|
|
EVENT_DISPATCHER.add_listner(event_listner)
|
|
|
|
return self
|
2021-01-28 14:02:43 +05:30
|
|
|
|
2022-02-13 20:03:05 +08:00
|
|
|
def remove_event_listner(
|
|
|
|
self,
|
|
|
|
event_type: EventType,
|
|
|
|
event_callback: Callable[[Mobject, dict[str]]]
|
|
|
|
):
|
2021-02-02 16:04:50 +05:30
|
|
|
event_listner = EventListner(self, event_type, event_callback)
|
|
|
|
while event_listner in self.event_listners:
|
|
|
|
self.event_listners.remove(event_listner)
|
|
|
|
EVENT_DISPATCHER.remove_listner(event_listner)
|
|
|
|
return self
|
2021-01-28 14:02:43 +05:30
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def clear_event_listners(self, recurse: bool = True):
|
2021-02-02 16:04:50 +05:30
|
|
|
self.event_listners = []
|
|
|
|
if recurse:
|
|
|
|
for submob in self.submobjects:
|
|
|
|
submob.clear_event_listners(recurse=recurse)
|
|
|
|
return self
|
2021-01-28 14:02:43 +05:30
|
|
|
|
2021-02-02 16:04:50 +05:30
|
|
|
def get_event_listners(self):
|
|
|
|
return self.event_listners
|
2021-02-04 12:35:45 -08:00
|
|
|
|
2021-02-02 16:04:50 +05:30
|
|
|
def get_family_event_listners(self):
|
|
|
|
return list(it.chain(*[sm.get_event_listners() for sm in self.get_family()]))
|
2021-01-28 14:02:43 +05:30
|
|
|
|
2021-02-02 16:04:50 +05:30
|
|
|
def get_has_event_listner(self):
|
|
|
|
return any(
|
|
|
|
mob.get_event_listners()
|
|
|
|
for mob in self.get_family()
|
|
|
|
)
|
2021-01-28 14:02:43 +05:30
|
|
|
|
2021-02-02 16:04:50 +05:30
|
|
|
def add_mouse_motion_listner(self, callback):
|
|
|
|
self.add_event_listner(EventType.MouseMotionEvent, callback)
|
2021-02-04 12:35:45 -08:00
|
|
|
|
2021-02-02 16:04:50 +05:30
|
|
|
def remove_mouse_motion_listner(self, callback):
|
|
|
|
self.remove_event_listner(EventType.MouseMotionEvent, callback)
|
|
|
|
|
|
|
|
def add_mouse_press_listner(self, callback):
|
|
|
|
self.add_event_listner(EventType.MousePressEvent, callback)
|
2021-02-04 12:35:45 -08:00
|
|
|
|
2021-02-02 16:04:50 +05:30
|
|
|
def remove_mouse_press_listner(self, callback):
|
|
|
|
self.remove_event_listner(EventType.MousePressEvent, callback)
|
|
|
|
|
|
|
|
def add_mouse_release_listner(self, callback):
|
|
|
|
self.add_event_listner(EventType.MouseReleaseEvent, callback)
|
2021-02-04 12:35:45 -08:00
|
|
|
|
2021-02-02 16:04:50 +05:30
|
|
|
def remove_mouse_release_listner(self, callback):
|
|
|
|
self.remove_event_listner(EventType.MouseReleaseEvent, callback)
|
|
|
|
|
|
|
|
def add_mouse_drag_listner(self, callback):
|
|
|
|
self.add_event_listner(EventType.MouseDragEvent, callback)
|
2021-02-04 12:35:45 -08:00
|
|
|
|
2021-02-02 16:04:50 +05:30
|
|
|
def remove_mouse_drag_listner(self, callback):
|
|
|
|
self.remove_event_listner(EventType.MouseDragEvent, callback)
|
|
|
|
|
|
|
|
def add_mouse_scroll_listner(self, callback):
|
|
|
|
self.add_event_listner(EventType.MouseScrollEvent, callback)
|
2021-02-04 12:35:45 -08:00
|
|
|
|
2021-02-02 16:04:50 +05:30
|
|
|
def remove_mouse_scroll_listner(self, callback):
|
|
|
|
self.remove_event_listner(EventType.MouseScrollEvent, callback)
|
|
|
|
|
|
|
|
def add_key_press_listner(self, callback):
|
|
|
|
self.add_event_listner(EventType.KeyPressEvent, callback)
|
2021-02-04 12:35:45 -08:00
|
|
|
|
2021-02-02 16:04:50 +05:30
|
|
|
def remove_key_press_listner(self, callback):
|
|
|
|
self.remove_event_listner(EventType.KeyPressEvent, callback)
|
|
|
|
|
|
|
|
def add_key_release_listner(self, callback):
|
|
|
|
self.add_event_listner(EventType.KeyReleaseEvent, callback)
|
2021-02-04 12:35:45 -08:00
|
|
|
|
2021-02-02 16:04:50 +05:30
|
|
|
def remove_key_release_listner(self, callback):
|
|
|
|
self.remove_event_listner(EventType.KeyReleaseEvent, callback)
|
2021-01-28 14:02:43 +05:30
|
|
|
|
2019-02-07 14:52:40 -08:00
|
|
|
# Errors
|
2021-01-12 11:09:53 -10:00
|
|
|
|
2019-02-07 14:52:40 -08:00
|
|
|
def throw_error_if_no_points(self):
|
2021-01-14 14:15:58 -10:00
|
|
|
if not self.has_points():
|
2019-03-24 11:34:07 -07:00
|
|
|
message = "Cannot call Mobject.{} " +\
|
2019-02-07 14:52:40 -08:00
|
|
|
"for a Mobject with no points"
|
|
|
|
caller_name = sys._getframe(1).f_code.co_name
|
|
|
|
raise Exception(message.format(caller_name))
|
|
|
|
|
2015-10-28 16:03:33 -07:00
|
|
|
|
2016-09-07 13:38:05 -07:00
|
|
|
class Group(Mobject):
|
2022-02-14 21:41:45 +08:00
|
|
|
def __init__(self, *mobjects: Mobject, **kwargs):
|
2019-02-06 21:16:26 -08:00
|
|
|
if not all([isinstance(m, Mobject) for m in mobjects]):
|
|
|
|
raise Exception("All submobjects must be of type Mobject")
|
|
|
|
Mobject.__init__(self, **kwargs)
|
|
|
|
self.add(*mobjects)
|
2021-11-08 21:43:57 -08:00
|
|
|
|
2022-02-14 21:41:45 +08:00
|
|
|
def __add__(self, other: Mobject | Group):
|
2021-11-01 10:19:06 +08:00
|
|
|
assert(isinstance(other, Mobject))
|
2021-10-31 18:35:28 +08:00
|
|
|
return self.add(other)
|
2020-02-14 15:29:52 -08:00
|
|
|
|
|
|
|
|
|
|
|
class Point(Mobject):
|
2022-12-15 09:18:22 -08:00
|
|
|
def __init__(
|
|
|
|
self,
|
2022-12-16 20:35:26 -08:00
|
|
|
location: Vect3 = ORIGIN,
|
2022-12-15 09:18:22 -08:00
|
|
|
artificial_width: float = 1e-6,
|
|
|
|
artificial_height: float = 1e-6,
|
|
|
|
**kwargs
|
|
|
|
):
|
|
|
|
self.artificial_width = artificial_width
|
|
|
|
self.artificial_height = artificial_height
|
|
|
|
super().__init__(**kwargs)
|
2020-02-14 15:29:52 -08:00
|
|
|
self.set_location(location)
|
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def get_width(self) -> float:
|
2020-02-14 15:29:52 -08:00
|
|
|
return self.artificial_width
|
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def get_height(self) -> float:
|
2020-02-14 15:29:52 -08:00
|
|
|
return self.artificial_height
|
|
|
|
|
2022-12-16 20:35:26 -08:00
|
|
|
def get_location(self) -> Vect3:
|
2021-01-11 10:57:23 -10:00
|
|
|
return self.get_points()[0].copy()
|
2020-02-14 15:29:52 -08:00
|
|
|
|
2022-12-16 20:35:26 -08:00
|
|
|
def get_bounding_box_point(self, *args, **kwargs) -> Vect3:
|
2020-02-14 15:29:52 -08:00
|
|
|
return self.get_location()
|
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def set_location(self, new_loc: npt.ArrayLike):
|
2021-01-15 11:10:57 -10:00
|
|
|
self.set_points(np.array(new_loc, ndmin=2, dtype=float))
|
2021-02-10 07:43:46 -06:00
|
|
|
|
|
|
|
|
|
|
|
class _AnimationBuilder:
|
2022-02-13 15:11:35 +08:00
|
|
|
def __init__(self, mobject: Mobject):
|
2021-02-10 07:43:46 -06:00
|
|
|
self.mobject = mobject
|
|
|
|
self.overridden_animation = None
|
|
|
|
self.mobject.generate_target()
|
|
|
|
self.is_chaining = False
|
2022-09-11 10:22:08 +08:00
|
|
|
self.methods: list[Callable] = []
|
|
|
|
self.anim_args = {}
|
2022-09-11 22:59:43 +08:00
|
|
|
self.can_pass_args = True
|
2021-02-10 07:43:46 -06:00
|
|
|
|
2022-02-13 15:11:35 +08:00
|
|
|
def __getattr__(self, method_name: str):
|
2021-02-10 07:43:46 -06:00
|
|
|
method = getattr(self.mobject.target, method_name)
|
|
|
|
self.methods.append(method)
|
|
|
|
has_overridden_animation = hasattr(method, "_override_animate")
|
|
|
|
|
|
|
|
if (self.is_chaining and has_overridden_animation) or self.overridden_animation:
|
|
|
|
raise NotImplementedError(
|
|
|
|
"Method chaining is currently not supported for "
|
|
|
|
"overridden animations"
|
|
|
|
)
|
|
|
|
|
|
|
|
def update_target(*method_args, **method_kwargs):
|
|
|
|
if has_overridden_animation:
|
|
|
|
self.overridden_animation = method._override_animate(
|
|
|
|
self.mobject, *method_args, **method_kwargs
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
method(*method_args, **method_kwargs)
|
|
|
|
return self
|
|
|
|
|
|
|
|
self.is_chaining = True
|
|
|
|
return update_target
|
|
|
|
|
2022-09-11 22:59:43 +08:00
|
|
|
def __call__(self, **kwargs):
|
|
|
|
return self.set_anim_args(**kwargs)
|
|
|
|
|
2022-09-11 10:22:08 +08:00
|
|
|
def set_anim_args(self, **kwargs):
|
2022-09-11 10:31:30 +08:00
|
|
|
'''
|
|
|
|
You can change the args of :class:`~manimlib.animation.transform.Transform`, such as
|
|
|
|
|
|
|
|
- ``run_time``
|
|
|
|
- ``time_span``
|
|
|
|
- ``rate_func``
|
|
|
|
- ``lag_ratio``
|
|
|
|
- ``path_arc``
|
|
|
|
- ``path_func``
|
|
|
|
|
|
|
|
and so on.
|
|
|
|
'''
|
2022-09-11 22:59:43 +08:00
|
|
|
|
|
|
|
if not self.can_pass_args:
|
|
|
|
raise ValueError(
|
|
|
|
"Animation arguments can only be passed by calling ``animate`` "
|
|
|
|
"or ``set_anim_args`` and can only be passed once",
|
|
|
|
)
|
|
|
|
|
2022-09-11 10:22:08 +08:00
|
|
|
self.anim_args = kwargs
|
2022-09-11 22:59:43 +08:00
|
|
|
self.can_pass_args = False
|
2022-09-11 10:22:08 +08:00
|
|
|
return self
|
|
|
|
|
2021-02-10 07:43:46 -06:00
|
|
|
def build(self):
|
|
|
|
from manimlib.animation.transform import _MethodAnimation
|
|
|
|
|
|
|
|
if self.overridden_animation:
|
|
|
|
return self.overridden_animation
|
|
|
|
|
2022-09-11 10:22:08 +08:00
|
|
|
return _MethodAnimation(self.mobject, self.methods, **self.anim_args)
|
2021-02-10 07:43:46 -06:00
|
|
|
|
|
|
|
|
|
|
|
def override_animate(method):
|
|
|
|
def decorator(animation_method):
|
|
|
|
method._override_animate = animation_method
|
|
|
|
return animation_method
|
|
|
|
|
|
|
|
return decorator
|