mirror of
https://github.com/ManimCommunity/manim.git
synced 2026-06-22 10:01:47 +00:00
Port opengl_geometry.Circle stroke
This commit is contained in:
parent
5ccf1d97ef
commit
53b2d160c2
8 changed files with 969 additions and 49 deletions
|
|
@ -16,6 +16,7 @@ if typing.TYPE_CHECKING:
|
|||
from .. import logger
|
||||
from ..mobject.mobject import Mobject, _AnimationBuilder
|
||||
from ..utils.rate_functions import smooth
|
||||
from ..mobject.opengl_mobject import OpenGLMobject
|
||||
|
||||
DEFAULT_ANIMATION_RUN_TIME: float = 1.0
|
||||
DEFAULT_ANIMATION_LAG_RATIO: float = 0.0
|
||||
|
|
@ -61,7 +62,9 @@ class Animation:
|
|||
def _typecheck_input(self, mobject: Mobject) -> None:
|
||||
if mobject is None:
|
||||
logger.debug("creating dummy animation")
|
||||
elif not isinstance(mobject, Mobject):
|
||||
elif not isinstance(mobject, Mobject) and not isinstance(
|
||||
mobject, OpenGLMobject
|
||||
):
|
||||
raise TypeError("Animation only works on Mobjects")
|
||||
|
||||
def __str__(self) -> str:
|
||||
|
|
|
|||
|
|
@ -1,3 +1,16 @@
|
|||
import numpy as np
|
||||
|
||||
from ..constants import *
|
||||
from ..utils.color import *
|
||||
from ..mobject.mobject import Mobject
|
||||
from ..mobject.types.opengl_vectorized_mobject import OpenGLVGroup
|
||||
from ..mobject.types.opengl_vectorized_mobject import OpenGLVMobject
|
||||
from ..mobject.types.opengl_vectorized_mobject import OpenGLDashedVMobject
|
||||
from ..utils.iterables import adjacent_n_tuples
|
||||
from ..utils.iterables import adjacent_pairs
|
||||
from ..utils.simple_functions import fdiv
|
||||
|
||||
# from ..utils.simple_functions import clip
|
||||
from ..utils.space_ops import angle_of_vector
|
||||
from ..utils.space_ops import angle_between_vectors
|
||||
from ..utils.space_ops import compass_directions
|
||||
|
|
@ -6,44 +19,206 @@ from ..utils.space_ops import get_norm
|
|||
from ..utils.space_ops import normalize
|
||||
from ..utils.space_ops import rotate_vector
|
||||
from ..utils.space_ops import rotation_matrix_transpose
|
||||
from ..utils.iterables import adjacent_n_tuples
|
||||
from ..constants import *
|
||||
from ..mobject.geometry import TipableVMobject
|
||||
from ..mobject.types.vectorized_mobject import VMobject
|
||||
|
||||
|
||||
class OpenGLArc(TipableVMobject):
|
||||
"""A circular arc."""
|
||||
DEFAULT_DOT_RADIUS = 0.08
|
||||
DEFAULT_SMALL_DOT_RADIUS = 0.04
|
||||
DEFAULT_DASH_LENGTH = 0.05
|
||||
DEFAULT_ARROW_TIP_LENGTH = 0.35
|
||||
DEFAULT_ARROW_TIP_WIDTH = 0.35
|
||||
|
||||
opengl_compatible = True
|
||||
|
||||
class OpenGLTipableVMobject(OpenGLVMobject):
|
||||
"""
|
||||
Meant for shared functionality between Arc and Line.
|
||||
Functionality can be classified broadly into these groups:
|
||||
|
||||
* Adding, Creating, Modifying tips
|
||||
- add_tip calls create_tip, before pushing the new tip
|
||||
into the TipableVMobject's list of submobjects
|
||||
- stylistic and positional configuration
|
||||
|
||||
* Checking for tips
|
||||
- Boolean checks for whether the TipableVMobject has a tip
|
||||
and a starting tip
|
||||
|
||||
* Getters
|
||||
- Straightforward accessors, returning information pertaining
|
||||
to the TipableVMobject instance's tip(s), its length etc
|
||||
"""
|
||||
|
||||
# Adding, Creating, Modifying tips
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
tip_length=DEFAULT_ARROW_TIP_LENGTH,
|
||||
normal_vector=OUT,
|
||||
tip_config={},
|
||||
**kwargs
|
||||
):
|
||||
self.tip_length = tip_length
|
||||
self.normal_vector = normal_vector
|
||||
self.tip_config = tip_config
|
||||
OpenGLVMobject.__init__(self, **kwargs)
|
||||
|
||||
def add_tip(self, at_start=False, **kwargs):
|
||||
"""
|
||||
Adds a tip to the TipableVMobject instance, recognising
|
||||
that the endpoints might need to be switched if it's
|
||||
a 'starting tip' or not.
|
||||
"""
|
||||
tip = self.create_tip(at_start, **kwargs)
|
||||
self.reset_endpoints_based_on_tip(tip, at_start)
|
||||
self.asign_tip_attr(tip, at_start)
|
||||
self.add(tip)
|
||||
return self
|
||||
|
||||
def create_tip(self, at_start=False, **kwargs):
|
||||
"""
|
||||
Stylises the tip, positions it spacially, and returns
|
||||
the newly instantiated tip to the caller.
|
||||
"""
|
||||
tip = self.get_unpositioned_tip(**kwargs)
|
||||
self.position_tip(tip, at_start)
|
||||
return tip
|
||||
|
||||
def get_unpositioned_tip(self, **kwargs):
|
||||
"""
|
||||
Returns a tip that has been stylistically configured,
|
||||
but has not yet been given a position in space.
|
||||
"""
|
||||
config = dict()
|
||||
config.update(self.tip_config)
|
||||
config.update(kwargs)
|
||||
return ArrowTip(**config)
|
||||
|
||||
def position_tip(self, tip, at_start=False):
|
||||
# Last two control points, defining both
|
||||
# the end, and the tangency direction
|
||||
if at_start:
|
||||
anchor = self.get_start()
|
||||
handle = self.get_first_handle()
|
||||
else:
|
||||
handle = self.get_last_handle()
|
||||
anchor = self.get_end()
|
||||
tip.rotate(angle_of_vector(handle - anchor) - PI - tip.get_angle())
|
||||
tip.shift(anchor - tip.get_tip_point())
|
||||
return tip
|
||||
|
||||
def reset_endpoints_based_on_tip(self, tip, at_start):
|
||||
if self.get_length() == 0:
|
||||
# Zero length, put_start_and_end_on wouldn't
|
||||
# work
|
||||
return self
|
||||
|
||||
if at_start:
|
||||
start = tip.get_base()
|
||||
end = self.get_end()
|
||||
else:
|
||||
start = self.get_start()
|
||||
end = tip.get_base()
|
||||
self.put_start_and_end_on(start, end)
|
||||
return self
|
||||
|
||||
def asign_tip_attr(self, tip, at_start):
|
||||
if at_start:
|
||||
self.start_tip = tip
|
||||
else:
|
||||
self.tip = tip
|
||||
return self
|
||||
|
||||
# Checking for tips
|
||||
def has_tip(self):
|
||||
return hasattr(self, "tip") and self.tip in self
|
||||
|
||||
def has_start_tip(self):
|
||||
return hasattr(self, "start_tip") and self.start_tip in self
|
||||
|
||||
# Getters
|
||||
def pop_tips(self):
|
||||
start, end = self.get_start_and_end()
|
||||
result = OpenGLVGroup()
|
||||
if self.has_tip():
|
||||
result.add(self.tip)
|
||||
self.remove(self.tip)
|
||||
if self.has_start_tip():
|
||||
result.add(self.start_tip)
|
||||
self.remove(self.start_tip)
|
||||
self.put_start_and_end_on(start, end)
|
||||
return result
|
||||
|
||||
def get_tips(self):
|
||||
"""
|
||||
Returns a VGroup (collection of VMobjects) containing
|
||||
the TipableVMObject instance's tips.
|
||||
"""
|
||||
result = OpenGLVGroup()
|
||||
if hasattr(self, "tip"):
|
||||
result.add(self.tip)
|
||||
if hasattr(self, "start_tip"):
|
||||
result.add(self.start_tip)
|
||||
return result
|
||||
|
||||
def get_tip(self):
|
||||
"""Returns the TipableVMobject instance's (first) tip,
|
||||
otherwise throws an exception."""
|
||||
tips = self.get_tips()
|
||||
if len(tips) == 0:
|
||||
raise Exception("tip not found")
|
||||
else:
|
||||
return tips[0]
|
||||
|
||||
def get_default_tip_length(self):
|
||||
return self.tip_length
|
||||
|
||||
def get_first_handle(self):
|
||||
return self.get_points()[1]
|
||||
|
||||
def get_last_handle(self):
|
||||
return self.get_points()[-2]
|
||||
|
||||
def get_end(self):
|
||||
if self.has_tip():
|
||||
return self.tip.get_start()
|
||||
else:
|
||||
return OpenGLVMobject.get_end(self)
|
||||
|
||||
def get_start(self):
|
||||
if self.has_start_tip():
|
||||
return self.start_tip.get_start()
|
||||
else:
|
||||
return OpenGLVMobject.get_start(self)
|
||||
|
||||
def get_length(self):
|
||||
start, end = self.get_start_and_end()
|
||||
return get_norm(start - end)
|
||||
|
||||
|
||||
class OpenGLArc(OpenGLTipableVMobject):
|
||||
def __init__(
|
||||
self,
|
||||
start_angle=0,
|
||||
angle=TAU / 4,
|
||||
radius=1.0,
|
||||
num_components=8,
|
||||
n_components=8,
|
||||
anchors_span_full_range=True,
|
||||
arc_center=ORIGIN,
|
||||
**kwargs
|
||||
):
|
||||
if radius is None: # apparently None is passed by ArcBetweenPoints
|
||||
radius = 1.0
|
||||
self.radius = radius
|
||||
self.num_components = num_components
|
||||
self.anchors_span_full_range = anchors_span_full_range
|
||||
self.arc_center = arc_center
|
||||
self.start_angle = start_angle
|
||||
self.angle = angle
|
||||
self._failed_to_get_center = False
|
||||
TipableVMobject.__init__(self, **kwargs)
|
||||
self.radius = radius
|
||||
self.n_components = n_components
|
||||
self.anchors_span_full_range = anchors_span_full_range
|
||||
self.arc_center = arc_center
|
||||
OpenGLVMobject.__init__(self, **kwargs)
|
||||
|
||||
def init_gl_points(self):
|
||||
def init_points(self):
|
||||
self.set_points(
|
||||
OpenGLArc.create_quadratic_bezier_points(
|
||||
angle=self.angle,
|
||||
start_angle=self.start_angle,
|
||||
n_components=self.num_components,
|
||||
n_components=self.n_components,
|
||||
)
|
||||
)
|
||||
self.scale(self.radius, about_point=ORIGIN)
|
||||
|
|
@ -104,3 +279,690 @@ class OpenGLArcBetweenPoints(OpenGLArc):
|
|||
if angle == 0:
|
||||
self.set_points_as_corners([LEFT, RIGHT])
|
||||
self.put_start_and_end_on(start, end)
|
||||
|
||||
|
||||
class OpenGLCurvedArrow(OpenGLArcBetweenPoints):
|
||||
def __init__(self, start_point, end_point, **kwargs):
|
||||
OpenGLArcBetweenPoints.__init__(self, start_point, end_point, **kwargs)
|
||||
self.add_tip()
|
||||
|
||||
|
||||
class OpenGLCurvedDoubleArrow(OpenGLCurvedArrow):
|
||||
def __init__(self, start_point, end_point, **kwargs):
|
||||
OpenGLCurvedArrow.__init__(self, start_point, end_point, **kwargs)
|
||||
self.add_tip(at_start=True)
|
||||
|
||||
|
||||
class OpenGLCircle(OpenGLArc):
|
||||
def __init__(
|
||||
self, color=RED, close_new_points=True, anchors_span_full_range=False, **kwargs
|
||||
):
|
||||
OpenGLArc.__init__(
|
||||
self,
|
||||
0,
|
||||
TAU,
|
||||
color=color,
|
||||
close_new_points=close_new_points,
|
||||
anchors_span_full_range=anchors_span_full_range,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
def surround(self, mobject, dim_to_match=0, stretch=False, buff=MED_SMALL_BUFF):
|
||||
# Ignores dim_to_match and stretch; result will always be a circle
|
||||
# TODO: Perhaps create an ellipse class to handle singele-dimension stretching
|
||||
|
||||
self.replace(mobject, dim_to_match, stretch)
|
||||
self.stretch((self.get_width() + 2 * buff) / self.get_width(), 0)
|
||||
self.stretch((self.get_height() + 2 * buff) / self.get_height(), 1)
|
||||
|
||||
def point_at_angle(self, angle):
|
||||
start_angle = self.get_start_angle()
|
||||
return self.point_from_proportion((angle - start_angle) / TAU)
|
||||
|
||||
|
||||
# class Dot(Circle):
|
||||
# CONFIG = {
|
||||
# "radius": DEFAULT_DOT_RADIUS,
|
||||
# "stroke_width": 0,
|
||||
# "fill_opacity": 1.0,
|
||||
# "color": WHITE,
|
||||
# }
|
||||
#
|
||||
# def __init__(self, point=ORIGIN, **kwargs):
|
||||
# super().__init__(arc_center=point, **kwargs)
|
||||
#
|
||||
#
|
||||
# class SmallDot(Dot):
|
||||
# CONFIG = {
|
||||
# "radius": DEFAULT_SMALL_DOT_RADIUS,
|
||||
# }
|
||||
#
|
||||
#
|
||||
# class Ellipse(Circle):
|
||||
# CONFIG = {"width": 2, "height": 1}
|
||||
#
|
||||
# def __init__(self, **kwargs):
|
||||
# super().__init__(**kwargs)
|
||||
# self.set_width(self.width, stretch=True)
|
||||
# self.set_height(self.height, stretch=True)
|
||||
#
|
||||
#
|
||||
# class AnnularSector(Arc):
|
||||
# CONFIG = {
|
||||
# "inner_radius": 1,
|
||||
# "outer_radius": 2,
|
||||
# "angle": TAU / 4,
|
||||
# "start_angle": 0,
|
||||
# "fill_opacity": 1,
|
||||
# "stroke_width": 0,
|
||||
# "color": WHITE,
|
||||
# }
|
||||
#
|
||||
# def init_points(self):
|
||||
# inner_arc, outer_arc = [
|
||||
# Arc(
|
||||
# start_angle=self.start_angle,
|
||||
# angle=self.angle,
|
||||
# radius=radius,
|
||||
# arc_center=self.arc_center,
|
||||
# )
|
||||
# for radius in (self.inner_radius, self.outer_radius)
|
||||
# ]
|
||||
# outer_arc.reverse_points()
|
||||
# self.append_points(inner_arc.get_points())
|
||||
# self.add_line_to(outer_arc.get_points()[0])
|
||||
# self.append_points(outer_arc.get_points())
|
||||
# self.add_line_to(inner_arc.get_points()[0])
|
||||
#
|
||||
#
|
||||
# class Sector(AnnularSector):
|
||||
# CONFIG = {"outer_radius": 1, "inner_radius": 0}
|
||||
#
|
||||
#
|
||||
# class Annulus(Circle):
|
||||
# CONFIG = {
|
||||
# "inner_radius": 1,
|
||||
# "outer_radius": 2,
|
||||
# "fill_opacity": 1,
|
||||
# "stroke_width": 0,
|
||||
# "color": WHITE,
|
||||
# "mark_paths_closed": False,
|
||||
# }
|
||||
#
|
||||
# def init_points(self):
|
||||
# self.radius = self.outer_radius
|
||||
# outer_circle = Circle(radius=self.outer_radius)
|
||||
# inner_circle = Circle(radius=self.inner_radius)
|
||||
# inner_circle.reverse_points()
|
||||
# self.append_points(outer_circle.get_points())
|
||||
# self.append_points(inner_circle.get_points())
|
||||
# self.shift(self.arc_center)
|
||||
#
|
||||
#
|
||||
# class Line(TipableVMobject):
|
||||
# CONFIG = {
|
||||
# "buff": 0,
|
||||
# # Angle of arc specified here
|
||||
# "path_arc": 0,
|
||||
# }
|
||||
#
|
||||
# def __init__(self, start=LEFT, end=RIGHT, **kwargs):
|
||||
# digest_config(self, kwargs)
|
||||
# self.set_start_and_end_attrs(start, end)
|
||||
# super().__init__(**kwargs)
|
||||
#
|
||||
# def init_points(self):
|
||||
# self.set_points_by_ends(self.start, self.end, self.buff, self.path_arc)
|
||||
#
|
||||
# def set_points_by_ends(self, start, end, buff=0, path_arc=0):
|
||||
# if path_arc:
|
||||
# self.set_points(Arc.create_quadratic_bezier_points(path_arc))
|
||||
# self.put_start_and_end_on(start, end)
|
||||
# else:
|
||||
# self.set_points_as_corners([start, end])
|
||||
# self.account_for_buff(self.buff)
|
||||
#
|
||||
# def set_path_arc(self, new_value):
|
||||
# self.path_arc = new_value
|
||||
# self.init_points()
|
||||
#
|
||||
# def account_for_buff(self, buff):
|
||||
# if buff == 0:
|
||||
# return
|
||||
# #
|
||||
# if self.path_arc == 0:
|
||||
# length = self.get_length()
|
||||
# else:
|
||||
# length = self.get_arc_length()
|
||||
# #
|
||||
# if length < 2 * buff:
|
||||
# return
|
||||
# buff_prop = buff / length
|
||||
# self.pointwise_become_partial(self, buff_prop, 1 - buff_prop)
|
||||
# return self
|
||||
#
|
||||
# def set_start_and_end_attrs(self, start, end):
|
||||
# # If either start or end are Mobjects, this
|
||||
# # gives their centers
|
||||
# rough_start = self.pointify(start)
|
||||
# rough_end = self.pointify(end)
|
||||
# vect = normalize(rough_end - rough_start)
|
||||
# # Now that we know the direction between them,
|
||||
# # we can find the appropriate boundary point from
|
||||
# # start and end, if they're mobjects
|
||||
# self.start = self.pointify(start, vect) + self.buff * vect
|
||||
# self.end = self.pointify(end, -vect) - self.buff * vect
|
||||
#
|
||||
# def pointify(self, mob_or_point, direction=None):
|
||||
# """
|
||||
# Take an argument passed into Line (or subclass) and turn
|
||||
# it into a 3d point.
|
||||
# """
|
||||
# if isinstance(mob_or_point, Mobject):
|
||||
# mob = mob_or_point
|
||||
# if direction is None:
|
||||
# return mob.get_center()
|
||||
# else:
|
||||
# return mob.get_continuous_bounding_box_point(direction)
|
||||
# else:
|
||||
# point = mob_or_point
|
||||
# result = np.zeros(self.dim)
|
||||
# result[: len(point)] = point
|
||||
# return result
|
||||
#
|
||||
# def put_start_and_end_on(self, start, end):
|
||||
# curr_start, curr_end = self.get_start_and_end()
|
||||
# if (curr_start == curr_end).all():
|
||||
# self.set_points_by_ends(start, end, self.path_arc)
|
||||
# return super().put_start_and_end_on(start, end)
|
||||
#
|
||||
# def get_vector(self):
|
||||
# return self.get_end() - self.get_start()
|
||||
#
|
||||
# def get_unit_vector(self):
|
||||
# return normalize(self.get_vector())
|
||||
#
|
||||
# def get_angle(self):
|
||||
# return angle_of_vector(self.get_vector())
|
||||
#
|
||||
# def get_projection(self, point):
|
||||
# """
|
||||
# Return projection of a point onto the line
|
||||
# """
|
||||
# unit_vect = self.get_unit_vector()
|
||||
# start = self.get_start()
|
||||
# return start + np.dot(point - start, unit_vect) * unit_vect
|
||||
#
|
||||
# def get_slope(self):
|
||||
# return np.tan(self.get_angle())
|
||||
#
|
||||
# def set_angle(self, angle, about_point=None):
|
||||
# if about_point is None:
|
||||
# about_point = self.get_start()
|
||||
# self.rotate(
|
||||
# angle - self.get_angle(),
|
||||
# about_point=about_point,
|
||||
# )
|
||||
# return self
|
||||
#
|
||||
# def set_length(self, length):
|
||||
# self.scale(length / self.get_length())
|
||||
#
|
||||
#
|
||||
# class DashedLine(Line):
|
||||
# CONFIG = {
|
||||
# "dash_length": DEFAULT_DASH_LENGTH,
|
||||
# "dash_spacing": None,
|
||||
# "positive_space_ratio": 0.5,
|
||||
# }
|
||||
#
|
||||
# def __init__(self, *args, **kwargs):
|
||||
# super().__init__(*args, **kwargs)
|
||||
# ps_ratio = self.positive_space_ratio
|
||||
# num_dashes = self.calculate_num_dashes(ps_ratio)
|
||||
# dashes = DashedVMobject(
|
||||
# self, num_dashes=num_dashes, positive_space_ratio=ps_ratio
|
||||
# )
|
||||
# self.clear_points()
|
||||
# self.add(*dashes)
|
||||
#
|
||||
# def calculate_num_dashes(self, positive_space_ratio):
|
||||
# try:
|
||||
# full_length = self.dash_length / positive_space_ratio
|
||||
# return int(np.ceil(self.get_length() / full_length))
|
||||
# except ZeroDivisionError:
|
||||
# return 1
|
||||
#
|
||||
# def calculate_positive_space_ratio(self):
|
||||
# return fdiv(
|
||||
# self.dash_length,
|
||||
# self.dash_length + self.dash_spacing,
|
||||
# )
|
||||
#
|
||||
# def get_start(self):
|
||||
# if len(self.submobjects) > 0:
|
||||
# return self.submobjects[0].get_start()
|
||||
# else:
|
||||
# return Line.get_start(self)
|
||||
#
|
||||
# def get_end(self):
|
||||
# if len(self.submobjects) > 0:
|
||||
# return self.submobjects[-1].get_end()
|
||||
# else:
|
||||
# return Line.get_end(self)
|
||||
#
|
||||
# def get_first_handle(self):
|
||||
# return self.submobjects[0].get_points()[1]
|
||||
#
|
||||
# def get_last_handle(self):
|
||||
# return self.submobjects[-1].get_points()[-2]
|
||||
#
|
||||
#
|
||||
# class TangentLine(Line):
|
||||
# CONFIG = {"length": 1, "d_alpha": 1e-6}
|
||||
#
|
||||
# def __init__(self, vmob, alpha, **kwargs):
|
||||
# digest_config(self, kwargs)
|
||||
# da = self.d_alpha
|
||||
# a1 = clip(alpha - da, 0, 1)
|
||||
# a2 = clip(alpha + da, 0, 1)
|
||||
# super().__init__(vmob.pfp(a1), vmob.pfp(a2), **kwargs)
|
||||
# self.scale(self.length / self.get_length())
|
||||
#
|
||||
#
|
||||
# class Elbow(VMobject):
|
||||
# CONFIG = {
|
||||
# "width": 0.2,
|
||||
# "angle": 0,
|
||||
# }
|
||||
#
|
||||
# def __init__(self, **kwargs):
|
||||
# super().__init__(self, **kwargs)
|
||||
# self.set_points_as_corners([UP, UP + RIGHT, RIGHT])
|
||||
# self.set_width(self.width, about_point=ORIGIN)
|
||||
# self.rotate(self.angle, about_point=ORIGIN)
|
||||
#
|
||||
#
|
||||
# class Arrow(Line):
|
||||
# CONFIG = {
|
||||
# "fill_color": GREY_A,
|
||||
# "fill_opacity": 1,
|
||||
# "stroke_width": 0,
|
||||
# "buff": MED_SMALL_BUFF,
|
||||
# "thickness": 0.05,
|
||||
# "tip_width_ratio": 5,
|
||||
# "tip_angle": PI / 3,
|
||||
# "max_tip_length_to_length_ratio": 0.5,
|
||||
# "max_width_to_length_ratio": 0.1,
|
||||
# }
|
||||
#
|
||||
# def set_points_by_ends(self, start, end, buff=0, path_arc=0):
|
||||
# # Find the right tip length and thickness
|
||||
# vect = end - start
|
||||
# length = max(get_norm(vect), 1e-8)
|
||||
# thickness = self.thickness
|
||||
# w_ratio = fdiv(self.max_width_to_length_ratio, fdiv(thickness, length))
|
||||
# if w_ratio < 1:
|
||||
# thickness *= w_ratio
|
||||
#
|
||||
# tip_width = self.tip_width_ratio * thickness
|
||||
# tip_length = tip_width / (2 * np.tan(self.tip_angle / 2))
|
||||
# t_ratio = fdiv(self.max_tip_length_to_length_ratio, fdiv(tip_length, length))
|
||||
# if t_ratio < 1:
|
||||
# tip_length *= t_ratio
|
||||
# tip_width *= t_ratio
|
||||
#
|
||||
# # Find points for the stem
|
||||
# if path_arc == 0:
|
||||
# points1 = (length - tip_length) * np.array([RIGHT, 0.5 * RIGHT, ORIGIN])
|
||||
# points1 += thickness * UP / 2
|
||||
# points2 = points1[::-1] + thickness * DOWN
|
||||
# else:
|
||||
# # Solve for radius so that the tip-to-tail length matches |end - start|
|
||||
# a = 2 * (1 - np.cos(path_arc))
|
||||
# b = -2 * tip_length * np.sin(path_arc)
|
||||
# c = tip_length ** 2 - length ** 2
|
||||
# R = (-b + np.sqrt(b ** 2 - 4 * a * c)) / (2 * a)
|
||||
#
|
||||
# # Find arc points
|
||||
# points1 = Arc.create_quadratic_bezier_points(path_arc)
|
||||
# points2 = np.array(points1[::-1])
|
||||
# points1 *= R + thickness / 2
|
||||
# points2 *= R - thickness / 2
|
||||
# if path_arc < 0:
|
||||
# tip_length *= -1
|
||||
# rot_T = rotation_matrix_transpose(PI / 2 - path_arc, OUT)
|
||||
# for points in points1, points2:
|
||||
# points[:] = np.dot(points, rot_T)
|
||||
# points += R * DOWN
|
||||
#
|
||||
# self.set_points(points1)
|
||||
# # Tip
|
||||
# self.add_line_to(tip_width * UP / 2)
|
||||
# self.add_line_to(tip_length * LEFT)
|
||||
# self.tip_index = len(self.get_points()) - 1
|
||||
# self.add_line_to(tip_width * DOWN / 2)
|
||||
# self.add_line_to(points2[0])
|
||||
# # Close it out
|
||||
# self.append_points(points2)
|
||||
# self.add_line_to(points1[0])
|
||||
#
|
||||
# if length > 0:
|
||||
# # Final correction
|
||||
# super().scale(length / self.get_length())
|
||||
#
|
||||
# self.rotate(angle_of_vector(vect) - self.get_angle())
|
||||
# self.rotate(
|
||||
# PI / 2 - np.arccos(normalize(vect)[2]),
|
||||
# axis=rotate_vector(self.get_unit_vector(), -PI / 2),
|
||||
# )
|
||||
# self.shift(start - self.get_start())
|
||||
# self.refresh_triangulation()
|
||||
#
|
||||
# def reset_points_around_ends(self):
|
||||
# self.set_points_by_ends(
|
||||
# self.get_start(), self.get_end(), path_arc=self.path_arc
|
||||
# )
|
||||
# return self
|
||||
#
|
||||
# def get_start(self):
|
||||
# nppc = self.n_points_per_curve
|
||||
# points = self.get_points()
|
||||
# return (points[0] + points[-nppc]) / 2
|
||||
#
|
||||
# def get_end(self):
|
||||
# return self.get_points()[self.tip_index]
|
||||
#
|
||||
# def put_start_and_end_on(self, start, end):
|
||||
# self.set_points_by_ends(start, end, buff=0, path_arc=self.path_arc)
|
||||
# return self
|
||||
#
|
||||
# def scale(self, *args, **kwargs):
|
||||
# super().scale(*args, **kwargs)
|
||||
# self.reset_points_around_ends()
|
||||
# return self
|
||||
#
|
||||
# def set_thickness(self, thickness):
|
||||
# self.thickness = thickness
|
||||
# self.reset_points_around_ends()
|
||||
# return self
|
||||
#
|
||||
# def set_path_arc(self, path_arc):
|
||||
# self.path_arc = path_arc
|
||||
# self.reset_points_around_ends()
|
||||
# return self
|
||||
#
|
||||
#
|
||||
# class Vector(Arrow):
|
||||
# CONFIG = {
|
||||
# "buff": 0,
|
||||
# }
|
||||
#
|
||||
# def __init__(self, direction=RIGHT, **kwargs):
|
||||
# if len(direction) == 2:
|
||||
# direction = np.hstack([direction, 0])
|
||||
# super().__init__(ORIGIN, direction, **kwargs)
|
||||
#
|
||||
#
|
||||
# class DoubleArrow(Arrow):
|
||||
# def __init__(self, *args, **kwargs):
|
||||
# Arrow.__init__(self, *args, **kwargs)
|
||||
# self.add_tip(at_start=True)
|
||||
#
|
||||
#
|
||||
# class CubicBezier(VMobject):
|
||||
# def __init__(self, a0, h0, h1, a1, **kwargs):
|
||||
# VMobject.__init__(self, **kwargs)
|
||||
# self.add_cubic_bezier_curve(a0, h0, h1, a1)
|
||||
#
|
||||
#
|
||||
# class Polygon(VMobject):
|
||||
# def __init__(self, *vertices, **kwargs):
|
||||
# self.vertices = vertices
|
||||
# super().__init__(**kwargs)
|
||||
#
|
||||
# def init_points(self):
|
||||
# verts = self.vertices
|
||||
# self.set_points_as_corners([*verts, verts[0]])
|
||||
#
|
||||
# def get_vertices(self):
|
||||
# return self.get_start_anchors()
|
||||
#
|
||||
# def round_corners(self, radius=0.5):
|
||||
# vertices = self.get_vertices()
|
||||
# arcs = []
|
||||
# for v1, v2, v3 in adjacent_n_tuples(vertices, 3):
|
||||
# vect1 = v2 - v1
|
||||
# vect2 = v3 - v2
|
||||
# unit_vect1 = normalize(vect1)
|
||||
# unit_vect2 = normalize(vect2)
|
||||
# angle = angle_between_vectors(vect1, vect2)
|
||||
# # Negative radius gives concave curves
|
||||
# angle *= np.sign(radius)
|
||||
# # Distance between vertex and start of the arc
|
||||
# cut_off_length = radius * np.tan(angle / 2)
|
||||
# # Determines counterclockwise vs. clockwise
|
||||
# sign = np.sign(np.cross(vect1, vect2)[2])
|
||||
# arc = ArcBetweenPoints(
|
||||
# v2 - unit_vect1 * cut_off_length,
|
||||
# v2 + unit_vect2 * cut_off_length,
|
||||
# angle=sign * angle,
|
||||
# n_components=2,
|
||||
# )
|
||||
# arcs.append(arc)
|
||||
#
|
||||
# self.clear_points()
|
||||
# # To ensure that we loop through starting with last
|
||||
# arcs = [arcs[-1], *arcs[:-1]]
|
||||
# for arc1, arc2 in adjacent_pairs(arcs):
|
||||
# self.append_points(arc1.get_points())
|
||||
# line = Line(arc1.get_end(), arc2.get_start())
|
||||
# # Make sure anchors are evenly distributed
|
||||
# len_ratio = line.get_length() / arc1.get_arc_length()
|
||||
# line.insert_n_curves(int(arc1.get_num_curves() * len_ratio))
|
||||
# self.append_points(line.get_points())
|
||||
# return self
|
||||
#
|
||||
#
|
||||
# class RegularPolygon(Polygon):
|
||||
# CONFIG = {
|
||||
# "start_angle": None,
|
||||
# }
|
||||
#
|
||||
# def __init__(self, n=6, **kwargs):
|
||||
# digest_config(self, kwargs, locals())
|
||||
# if self.start_angle is None:
|
||||
# # 0 for odd, 90 for even
|
||||
# self.start_angle = (n % 2) * 90 * DEGREES
|
||||
# start_vect = rotate_vector(RIGHT, self.start_angle)
|
||||
# vertices = compass_directions(n, start_vect)
|
||||
# super().__init__(*vertices, **kwargs)
|
||||
#
|
||||
#
|
||||
# class Triangle(RegularPolygon):
|
||||
# def __init__(self, **kwargs):
|
||||
# super().__init__(n=3, **kwargs)
|
||||
#
|
||||
#
|
||||
# class ArrowTip(Triangle):
|
||||
# CONFIG = {
|
||||
# "fill_opacity": 1,
|
||||
# "fill_color": WHITE,
|
||||
# "stroke_width": 0,
|
||||
# "width": DEFAULT_ARROW_TIP_WIDTH,
|
||||
# "length": DEFAULT_ARROW_TIP_LENGTH,
|
||||
# "angle": 0,
|
||||
# }
|
||||
#
|
||||
# def __init__(self, **kwargs):
|
||||
# Triangle.__init__(self, start_angle=0, **kwargs)
|
||||
# self.set_height(self.width)
|
||||
# self.set_width(self.length, stretch=True)
|
||||
# self.rotate(self.angle)
|
||||
#
|
||||
# def get_base(self):
|
||||
# return self.point_from_proportion(0.5)
|
||||
#
|
||||
# def get_tip_point(self):
|
||||
# return self.get_points()[0]
|
||||
#
|
||||
# def get_vector(self):
|
||||
# return self.get_tip_point() - self.get_base()
|
||||
#
|
||||
# def get_angle(self):
|
||||
# return angle_of_vector(self.get_vector())
|
||||
#
|
||||
# def get_length(self):
|
||||
# return get_norm(self.get_vector())
|
||||
#
|
||||
#
|
||||
# class Rectangle(Polygon):
|
||||
# CONFIG = {
|
||||
# "color": WHITE,
|
||||
# "width": 4.0,
|
||||
# "height": 2.0,
|
||||
# "mark_paths_closed": True,
|
||||
# "close_new_points": True,
|
||||
# }
|
||||
#
|
||||
# def __init__(self, width=None, height=None, **kwargs):
|
||||
# Polygon.__init__(self, UR, UL, DL, DR, **kwargs)
|
||||
#
|
||||
# if width is None:
|
||||
# width = self.width
|
||||
# if height is None:
|
||||
# height = self.height
|
||||
#
|
||||
# self.set_width(width, stretch=True)
|
||||
# self.set_height(height, stretch=True)
|
||||
#
|
||||
#
|
||||
# class Square(Rectangle):
|
||||
# CONFIG = {
|
||||
# "side_length": 2.0,
|
||||
# }
|
||||
#
|
||||
# def __init__(self, side_length=None, **kwargs):
|
||||
# digest_config(self, kwargs)
|
||||
#
|
||||
# if side_length is None:
|
||||
# side_length = self.side_length
|
||||
#
|
||||
# super().__init__(side_length, side_length, **kwargs)
|
||||
#
|
||||
#
|
||||
# class RoundedRectangle(Rectangle):
|
||||
# CONFIG = {
|
||||
# "corner_radius": 0.5,
|
||||
# }
|
||||
#
|
||||
# def __init__(self, **kwargs):
|
||||
# Rectangle.__init__(self, **kwargs)
|
||||
# self.round_corners(self.corner_radius)
|
||||
|
||||
from ..utils.space_ops import angle_of_vector
|
||||
from ..utils.space_ops import angle_between_vectors
|
||||
from ..utils.space_ops import compass_directions
|
||||
from ..utils.space_ops import find_intersection
|
||||
from ..utils.space_ops import get_norm
|
||||
from ..utils.space_ops import normalize
|
||||
from ..utils.space_ops import rotate_vector
|
||||
from ..utils.space_ops import rotation_matrix_transpose
|
||||
from ..utils.iterables import adjacent_n_tuples
|
||||
from ..constants import *
|
||||
from ..mobject.geometry import TipableVMobject
|
||||
from ..mobject.types.vectorized_mobject import VMobject
|
||||
|
||||
|
||||
class TestOpenGLArc(TipableVMobject):
|
||||
"""A circular arc."""
|
||||
|
||||
opengl_compatible = True
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
start_angle=0,
|
||||
angle=TAU / 4,
|
||||
radius=1.0,
|
||||
num_components=8,
|
||||
anchors_span_full_range=True,
|
||||
arc_center=ORIGIN,
|
||||
**kwargs
|
||||
):
|
||||
if radius is None: # apparently None is passed by ArcBetweenPoints
|
||||
radius = 1.0
|
||||
self.radius = radius
|
||||
self.num_components = num_components
|
||||
self.anchors_span_full_range = anchors_span_full_range
|
||||
self.arc_center = arc_center
|
||||
self.start_angle = start_angle
|
||||
self.angle = angle
|
||||
self._failed_to_get_center = False
|
||||
TipableVMobject.__init__(self, **kwargs)
|
||||
|
||||
def init_gl_points(self):
|
||||
self.set_points(
|
||||
TestOpenGLArc.create_quadratic_bezier_points(
|
||||
angle=self.angle,
|
||||
start_angle=self.start_angle,
|
||||
n_components=self.num_components,
|
||||
)
|
||||
)
|
||||
self.scale(self.radius, about_point=ORIGIN)
|
||||
self.shift(self.arc_center)
|
||||
|
||||
@staticmethod
|
||||
def create_quadratic_bezier_points(angle, start_angle=0, n_components=8):
|
||||
samples = np.array(
|
||||
[
|
||||
[np.cos(a), np.sin(a), 0]
|
||||
for a in np.linspace(
|
||||
start_angle,
|
||||
start_angle + angle,
|
||||
2 * n_components + 1,
|
||||
)
|
||||
]
|
||||
)
|
||||
theta = angle / n_components
|
||||
samples[1::2] /= np.cos(theta / 2)
|
||||
|
||||
points = np.zeros((3 * n_components, 3))
|
||||
points[0::3] = samples[0:-1:2]
|
||||
points[1::3] = samples[1::2]
|
||||
points[2::3] = samples[2::2]
|
||||
return points
|
||||
|
||||
def get_arc_center(self):
|
||||
"""
|
||||
Looks at the normals to the first two
|
||||
anchors, and finds their intersection points
|
||||
"""
|
||||
# First two anchors and handles
|
||||
a1, h, a2 = self.get_points()[:3]
|
||||
# Tangent vectors
|
||||
t1 = h - a1
|
||||
t2 = h - a2
|
||||
# Normals
|
||||
n1 = rotate_vector(t1, TAU / 4)
|
||||
n2 = rotate_vector(t2, TAU / 4)
|
||||
return find_intersection(a1, n1, a2, n2)
|
||||
|
||||
def get_start_angle(self):
|
||||
angle = angle_of_vector(self.get_start() - self.get_arc_center())
|
||||
return angle % TAU
|
||||
|
||||
def get_stop_angle(self):
|
||||
angle = angle_of_vector(self.get_end() - self.get_arc_center())
|
||||
return angle % TAU
|
||||
|
||||
def move_arc_center_to(self, point):
|
||||
self.shift(point - self.get_arc_center())
|
||||
return self
|
||||
|
||||
|
||||
class TestOpenGLArcBetweenPoints(TestOpenGLArc):
|
||||
def __init__(self, start, end, angle=TAU / 4, **kwargs):
|
||||
super().__init__(angle=angle, **kwargs)
|
||||
if angle == 0:
|
||||
self.set_points_as_corners([LEFT, RIGHT])
|
||||
self.put_start_and_end_on(start, end)
|
||||
|
|
|
|||
|
|
@ -448,12 +448,12 @@ class OpenGLMobject:
|
|||
# Make sure any mobject or numpy array attributes are copied
|
||||
family = self.get_family()
|
||||
for attr, value in list(self.__dict__.items()):
|
||||
if isinstance(value, Mobject) and value in family and value is not self:
|
||||
if isinstance(value, OpenGLMobject) and value in family and value is not self:
|
||||
setattr(copy_mobject, attr, value.copy())
|
||||
if isinstance(value, np.ndarray):
|
||||
setattr(copy_mobject, attr, value.copy())
|
||||
if isinstance(value, ShaderWrapper):
|
||||
setattr(copy_mobject, attr, value.copy())
|
||||
# if isinstance(value, ShaderWrapper):
|
||||
# setattr(copy_mobject, attr, value.copy())
|
||||
return copy_mobject
|
||||
|
||||
def deepcopy(self):
|
||||
|
|
@ -1157,7 +1157,7 @@ class OpenGLMobject:
|
|||
|
||||
def align_data(self, mobject):
|
||||
# In case any data arrays get resized when aligned to shader data
|
||||
self.refresh_shader_data()
|
||||
# self.refresh_shader_data()
|
||||
for mob1, mob2 in zip(self.get_family(), mobject.get_family()):
|
||||
# Separate out how points are treated so that subclasses
|
||||
# can handle that case differently if they choose
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ from ...utils.iterables import listify
|
|||
# from manimlib.utils.space_ops import angle_between_vectors
|
||||
# from manimlib.utils.space_ops import cross2d
|
||||
# from manimlib.utils.space_ops import earclip_triangulation
|
||||
# from manimlib.utils.space_ops import get_norm
|
||||
from ...utils.space_ops import get_norm
|
||||
from ...utils.space_ops import get_unit_normal
|
||||
|
||||
# from manimlib.utils.space_ops import z_to_vector
|
||||
|
|
@ -1021,13 +1021,14 @@ class OpenGLVectorizedPoint(OpenGLPoint, OpenGLVMobject):
|
|||
artificial_height=0.01,
|
||||
**kwargs
|
||||
):
|
||||
self.color = color
|
||||
self.fill_opacity = fill_opacity
|
||||
self.stroke_width = stroke_width
|
||||
self.artificial_width = artificial_width
|
||||
self.artificial_height = artificial_height
|
||||
|
||||
super().__init__(**kwargs)
|
||||
super().__init__(
|
||||
color=color,
|
||||
fill_opacity=fill_opacity,
|
||||
stroke_width=stroke_width,
|
||||
**kwargs)
|
||||
self.set_points(np.array([location]))
|
||||
|
||||
|
||||
|
|
@ -1041,14 +1042,13 @@ class OpenGLCurvesAsSubmobjects(OpenGLVGroup):
|
|||
self.add(part)
|
||||
|
||||
|
||||
class DashedVMobject(OpenGLVMobject):
|
||||
class OpenGLDashedVMobject(OpenGLVMobject):
|
||||
def __init__(
|
||||
self, vmobject, num_dashes=15, positive_space_ratio=0.5, color=WHITE, **kwargs
|
||||
):
|
||||
self.num_dashes = num_dashes
|
||||
self.positive_space_ratio = positive_space_ratio
|
||||
self.color = color
|
||||
super().__init__(**kwargs)
|
||||
super().__init__(color=color, **kwargs)
|
||||
num_dashes = self.num_dashes
|
||||
ps_ratio = self.positive_space_ratio
|
||||
if num_dashes > 0:
|
||||
|
|
|
|||
|
|
@ -2,5 +2,5 @@ from ..mobject.opengl_mobject import *
|
|||
from ..mobject.types.opengl_vectorized_mobject import *
|
||||
from ..mobject.opengl_geometry import *
|
||||
|
||||
Arc = OpenGLArc
|
||||
ArcBetweenPoints = OpenGLArcBetweenPoints
|
||||
Arc = TestOpenGLArc
|
||||
ArcBetweenPoints = TestOpenGLArcBetweenPoints
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import numpy as np
|
|||
from ..mobject.types.vectorized_mobject import VMobject
|
||||
import itertools as it
|
||||
import time
|
||||
from .. import logger
|
||||
|
||||
from ..mobject import opengl_geometry
|
||||
|
||||
|
|
@ -298,6 +299,11 @@ class OpenGLRenderer:
|
|||
self.file_writer = None
|
||||
|
||||
def play(self, scene, *args, **kwargs):
|
||||
if len(args) == 0:
|
||||
logger.warning("Called Scene.play with no animations")
|
||||
return
|
||||
|
||||
# TODO: Handle data locking / unlocking.
|
||||
if scene.compile_animation_data(*args, **kwargs):
|
||||
self.animation_start_time = time.time()
|
||||
self.animation_elapsed_time = 0
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ from ..utils.iterables import list_update, list_difference_update
|
|||
from ..utils.family import extract_mobject_family_members
|
||||
from ..renderer.cairo_renderer import CairoRenderer
|
||||
from ..utils.exceptions import EndSceneEarlyException
|
||||
from ..utils.family_ops import restructure_list_to_exclude_certain_family_members
|
||||
|
||||
|
||||
class Scene(Container):
|
||||
|
|
@ -320,15 +321,20 @@ class Scene(Container):
|
|||
The same scene after adding the Mobjects in.
|
||||
|
||||
"""
|
||||
mobjects = [*mobjects, *self.foreground_mobjects]
|
||||
self.restructure_mobjects(to_remove=mobjects)
|
||||
self.mobjects += mobjects
|
||||
if self.moving_mobjects:
|
||||
self.restructure_mobjects(
|
||||
to_remove=mobjects, mobject_list_name="moving_mobjects"
|
||||
)
|
||||
self.moving_mobjects += mobjects
|
||||
return self
|
||||
if config["use_opengl_renderer"]:
|
||||
new_mobjects = mobjects
|
||||
self.remove(*new_mobjects)
|
||||
self.mobjects += new_mobjects
|
||||
else:
|
||||
mobjects = [*mobjects, *self.foreground_mobjects]
|
||||
self.restructure_mobjects(to_remove=mobjects)
|
||||
self.mobjects += mobjects
|
||||
if self.moving_mobjects:
|
||||
self.restructure_mobjects(
|
||||
to_remove=mobjects, mobject_list_name="moving_mobjects"
|
||||
)
|
||||
self.moving_mobjects += mobjects
|
||||
return self
|
||||
|
||||
def add_mobjects_from_animations(self, animations):
|
||||
|
||||
|
|
@ -352,9 +358,16 @@ class Scene(Container):
|
|||
*mobjects : Mobject
|
||||
The mobjects to remove.
|
||||
"""
|
||||
for list_name in "mobjects", "foreground_mobjects":
|
||||
self.restructure_mobjects(mobjects, list_name, False)
|
||||
return self
|
||||
if config["use_opengl_renderer"]:
|
||||
mobjects_to_remove = mobjects
|
||||
self.mobjects = restructure_list_to_exclude_certain_family_members(
|
||||
self.mobjects, mobjects_to_remove
|
||||
)
|
||||
return self
|
||||
else:
|
||||
for list_name in "mobjects", "foreground_mobjects":
|
||||
self.restructure_mobjects(mobjects, list_name, False)
|
||||
return self
|
||||
|
||||
def restructure_mobjects(
|
||||
self, to_remove, mobject_list_name="mobjects", extract_families=True
|
||||
|
|
@ -793,26 +806,27 @@ class Scene(Container):
|
|||
self.stop_condition = None
|
||||
self.moving_mobjects = None
|
||||
self.static_mobjects = None
|
||||
if len(self.animations) == 1 and isinstance(self.animations[0], Wait):
|
||||
self.update_mobjects(dt=0) # Any problems with this?
|
||||
if self.should_update_mobjects():
|
||||
# TODO, be smart about setting a static image
|
||||
# the same way Scene.play does
|
||||
self.renderer.static_image = None
|
||||
self.stop_condition = self.animations[0].stop_condition
|
||||
if not config["use_opengl_renderer"]:
|
||||
if len(self.animations) == 1 and isinstance(self.animations[0], Wait):
|
||||
self.update_mobjects(dt=0) # Any problems with this?
|
||||
if self.should_update_mobjects():
|
||||
# TODO, be smart about setting a static image
|
||||
# the same way Scene.play does
|
||||
self.renderer.static_image = None
|
||||
self.stop_condition = self.animations[0].stop_condition
|
||||
else:
|
||||
self.duration = self.animations[0].duration
|
||||
if not skip_rendering:
|
||||
self.add_static_frames(self.animations[0].duration)
|
||||
return None
|
||||
else:
|
||||
self.duration = self.animations[0].duration
|
||||
if not skip_rendering:
|
||||
self.add_static_frames(self.animations[0].duration)
|
||||
return None
|
||||
else:
|
||||
# Paint all non-moving objects onto the screen, so they don't
|
||||
# have to be rendered every frame
|
||||
(
|
||||
self.moving_mobjects,
|
||||
self.static_mobjects,
|
||||
) = self.get_moving_and_static_mobjects(self.animations)
|
||||
self.renderer.save_static_frame_data(self, self.static_mobjects)
|
||||
# Paint all non-moving objects onto the screen, so they don't
|
||||
# have to be rendered every frame
|
||||
(
|
||||
self.moving_mobjects,
|
||||
self.static_mobjects,
|
||||
) = self.get_moving_and_static_mobjects(self.animations)
|
||||
self.renderer.save_static_frame_data(self, self.static_mobjects)
|
||||
|
||||
self.duration = self.get_run_time(self.animations)
|
||||
self.time_progression = self._get_animation_time_progression(
|
||||
|
|
|
|||
35
manim/utils/family_ops.py
Normal file
35
manim/utils/family_ops.py
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import itertools as it
|
||||
|
||||
|
||||
def extract_mobject_family_members(mobject_list, only_those_with_points=False):
|
||||
result = list(it.chain(*[mob.get_family() for mob in mobject_list]))
|
||||
if only_those_with_points:
|
||||
result = [mob for mob in result if mob.has_points()]
|
||||
return result
|
||||
|
||||
|
||||
def restructure_list_to_exclude_certain_family_members(mobject_list, to_remove):
|
||||
"""
|
||||
Removes anything in to_remove from mobject_list, but in the event that one of
|
||||
the items to be removed is a member of the family of an item in mobject_list,
|
||||
the other family members are added back into the list.
|
||||
|
||||
This is useful in cases where a scene contains a group, e.g. Group(m1, m2, m3),
|
||||
but one of its submobjects is removed, e.g. scene.remove(m1), it's useful
|
||||
for the list of mobject_list to be edited to contain other submobjects, but not m1.
|
||||
"""
|
||||
new_list = []
|
||||
to_remove = extract_mobject_family_members(to_remove)
|
||||
|
||||
def add_safe_mobjects_from_list(list_to_examine, set_to_remove):
|
||||
for mob in list_to_examine:
|
||||
if mob in set_to_remove:
|
||||
continue
|
||||
intersect = set_to_remove.intersection(mob.get_family())
|
||||
if intersect:
|
||||
add_safe_mobjects_from_list(mob.submobjects, intersect)
|
||||
else:
|
||||
new_list.append(mob)
|
||||
|
||||
add_safe_mobjects_from_list(mobject_list, set(to_remove))
|
||||
return new_list
|
||||
Loading…
Add table
Add a link
Reference in a new issue