Files
Grant Sanderson db421e3981 Video work (#2318)
* Only use -no-pdf for xelatex rendering

* Instead of tracking du and dv points on surface, track points off the surface in the normal direction

This means that surface shading will not necessarily work well for arbitrary transformations of the surface. But the existing solution was flimsy anyway, and caused annoying issues with singularity points.

* Have density of anchor points on arcs depend on arc length

* Allow for specifying true normals and orientation of Sphere

* Change miter threshold on stroke shader

* Add get_start_and_end to DashedLine

* Add min_total_width option to DecimalNumber

* Have BackgroundRectangle.set_style absorb (and ignore) added configuration

Note, this feels suboptimal

* Add LineBrace

* Update font_size adjustment in Tex
2025-02-26 07:52:59 -08:00

186 lines
5.9 KiB
Python

from __future__ import annotations
import math
import copy
import numpy as np
from manimlib.constants import DEFAULT_MOBJECT_TO_MOBJECT_BUFF, SMALL_BUFF
from manimlib.constants import DOWN, LEFT, ORIGIN, RIGHT, DL, DR, UL, UP
from manimlib.constants import PI
from manimlib.animation.composition import AnimationGroup
from manimlib.animation.fading import FadeIn
from manimlib.animation.growing import GrowFromCenter
from manimlib.mobject.svg.tex_mobject import Tex
from manimlib.mobject.svg.tex_mobject import TexText
from manimlib.mobject.svg.text_mobject import Text
from manimlib.mobject.types.vectorized_mobject import VGroup
from manimlib.mobject.types.vectorized_mobject import VMobject
from manimlib.utils.iterables import listify
from manimlib.utils.space_ops import get_norm
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from typing import Iterable
from manimlib.animation.animation import Animation
from manimlib.mobject.mobject import Mobject
from manimlib.typing import Vect3
class Brace(Tex):
def __init__(
self,
mobject: Mobject,
direction: Vect3 = DOWN,
buff: float = 0.2,
tex_string: str = R"\underbrace{\qquad}",
**kwargs
):
super().__init__(tex_string, **kwargs)
angle = -math.atan2(*direction[:2]) + PI
mobject.rotate(-angle, about_point=ORIGIN)
left = mobject.get_corner(DL)
right = mobject.get_corner(DR)
target_width = right[0] - left[0]
self.tip_point_index = np.argmin(self.get_all_points()[:, 1])
self.set_initial_width(target_width)
self.shift(left - self.get_corner(UL) + buff * DOWN)
for mob in mobject, self:
mob.rotate(angle, about_point=ORIGIN)
def set_initial_width(self, width: float):
width_diff = width - self.get_width()
if width_diff > 0:
for tip, rect, vect in [(self[0], self[1], RIGHT), (self[5], self[4], LEFT)]:
rect.set_width(
width_diff / 2 + rect.get_width(),
about_edge=vect, stretch=True
)
tip.shift(-width_diff / 2 * vect)
else:
self.set_width(width, stretch=True)
return self
def put_at_tip(
self,
mob: Mobject,
use_next_to: bool = True,
**kwargs
):
if use_next_to:
mob.next_to(
self.get_tip(),
np.round(self.get_direction()),
**kwargs
)
else:
mob.move_to(self.get_tip())
buff = kwargs.get("buff", DEFAULT_MOBJECT_TO_MOBJECT_BUFF)
shift_distance = mob.get_width() / 2.0 + buff
mob.shift(self.get_direction() * shift_distance)
return self
def get_text(self, text: str, **kwargs) -> Text:
buff = kwargs.pop("buff", SMALL_BUFF)
text_mob = Text(text, **kwargs)
self.put_at_tip(text_mob, buff=buff)
return text_mob
def get_tex(self, *tex: str, **kwargs) -> Tex:
buff = kwargs.pop("buff", SMALL_BUFF)
tex_mob = Tex(*tex, **kwargs)
self.put_at_tip(tex_mob, buff=buff)
return tex_mob
def get_tip(self) -> np.ndarray:
# Very specific to the LaTeX representation
# of a brace, but it's the only way I can think
# of to get the tip regardless of orientation.
return self.get_all_points()[self.tip_point_index]
def get_direction(self) -> np.ndarray:
vect = self.get_tip() - self.get_center()
return vect / get_norm(vect)
class BraceLabel(VMobject):
label_constructor: type = Tex
def __init__(
self,
obj: VMobject | list[VMobject],
text: str | Iterable[str],
brace_direction: np.ndarray = DOWN,
label_scale: float = 1.0,
label_buff: float = DEFAULT_MOBJECT_TO_MOBJECT_BUFF,
**kwargs
) -> None:
super().__init__(**kwargs)
self.brace_direction = brace_direction
self.label_scale = label_scale
self.label_buff = label_buff
if isinstance(obj, list):
obj = VGroup(*obj)
self.brace = Brace(obj, brace_direction, **kwargs)
self.label = self.label_constructor(*listify(text), **kwargs)
self.label.scale(self.label_scale)
self.brace.put_at_tip(self.label, buff=self.label_buff)
self.set_submobjects([self.brace, self.label])
def creation_anim(
self,
label_anim: Animation = FadeIn,
brace_anim: Animation = GrowFromCenter
) -> AnimationGroup:
return AnimationGroup(brace_anim(self.brace), label_anim(self.label))
def shift_brace(self, obj: VMobject | list[VMobject], **kwargs):
if isinstance(obj, list):
obj = VMobject(*obj)
self.brace = Brace(obj, self.brace_direction, **kwargs)
self.brace.put_at_tip(self.label)
self.submobjects[0] = self.brace
return self
def change_label(self, *text: str, **kwargs):
self.label = self.label_constructor(*text, **kwargs)
if self.label_scale != 1:
self.label.scale(self.label_scale)
self.brace.put_at_tip(self.label)
self.submobjects[1] = self.label
return self
def change_brace_label(self, obj: VMobject | list[VMobject], *text: str):
self.shift_brace(obj)
self.change_label(*text)
return self
def copy(self):
copy_mobject = copy.copy(self)
copy_mobject.brace = self.brace.copy()
copy_mobject.label = self.label.copy()
copy_mobject.set_submobjects([copy_mobject.brace, copy_mobject.label])
return copy_mobject
class BraceText(BraceLabel):
label_constructor: type = TexText
class LineBrace(Brace):
def __init__(self, line: Line, direction=UP, **kwargs):
angle = line.get_angle()
line.rotate(-angle)
super().__init__(line, direction, **kwargs)
line.rotate(angle)
self.rotate(angle, about_point=line.get_center())