Merge pull request #1953 from 3b1b/video-work

More tweaks and fixes
This commit is contained in:
Grant Sanderson 2022-12-29 21:00:00 -08:00 committed by GitHub
commit 50960eefd4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 149 additions and 23 deletions

View file

@ -193,11 +193,7 @@ class TexTransformExample(Scene):
),
)
self.wait()
self.play(
TransformMatchingStrings(
lines[1].copy(), lines[2],
),
)
self.play(TransformMatchingStrings(lines[1].copy(), lines[2]))
self.wait()
self.play(
TransformMatchingStrings(
@ -208,6 +204,17 @@ class TexTransformExample(Scene):
),
)
self.wait(2)
# You can also index into Tex mobject (or other StringMobjects)
# by substrings and regular expressions
top_equation = lines[0]
low_equation = lines[3]
self.play(LaggedStartMap(FlashAround, low_equation["C"], lag_ratio=0.5))
self.play(LaggedStartMap(FlashAround, low_equation["B"], lag_ratio=0.5))
self.play(LaggedStartMap(FlashAround, top_equation[re.compile(r"\w\^2")]))
self.play(Indicate(low_equation[R"\sqrt"]))
self.wait()
self.play(LaggedStartMap(FadeOut, lines, shift=2 * RIGHT))
# TransformMatchingShapes will try to line up all pieces of a
@ -453,6 +460,74 @@ class GraphExample(Scene):
self.wait()
class TexAndNumbersExample(InteractiveScene):
def construct(self):
axes = Axes((-3, 3), (-3, 3), unit_size=1)
axes.to_edge(DOWN)
axes.add_coordinate_labels(font_size=16)
circle = Circle(radius=2)
circle.set_stroke(YELLOW, 3)
circle.move_to(axes.get_origin())
self.add(axes, circle)
# When numbers show up in tex, they can be readily
# replaced with DecimalMobjects so that methods like
# get_value and set_value can be called on them, and
# animations like ChangeDecimalToValue can be called
# on them.
tex = Tex("x^2 + y^2 = 4.00")
tex.next_to(axes, UP, buff=0.5)
value = tex.make_number_changable("4.00")
# This will tie the right hand side of our equation to
# the square of the radius of the circle
value.add_updater(lambda v: v.set_value(circle.get_radius()**2))
self.add(tex)
text = Text("""
You can manipulate numbers
in Tex mobjects
""", font_size=30)
text.next_to(tex, RIGHT, buff=1.5)
arrow = Arrow(text, tex)
self.add(text, arrow)
self.play(
circle.animate.set_height(2.0),
run_time=4,
rate_func=there_and_back,
)
# By default, tex.make_number_changable replaces the first occurance
# of the number,but by passing replace_all=True it replaces all and
# returns a group of the results
exponents = tex.make_number_changable("2", replace_all=True)
self.play(
LaggedStartMap(
FlashAround, exponents,
lag_ratio=0.2, buff=0.1, color=RED
),
exponents.animate.set_color(RED)
)
def func(x, y):
# Switch from manim coords to axes coords
xa, ya = axes.point_to_coords(np.array([x, y, 0]))
return xa**4 + ya**4 - 4
new_curve = ImplicitFunction(func)
new_curve.match_style(circle)
circle.rotate(angle_of_vector(new_curve.get_start())) # Align
value.clear_updaters()
self.play(
*(ChangeDecimalToValue(exp, 4) for exp in exponents),
ReplacementTransform(circle.copy(), new_curve),
circle.animate.set_stroke(width=1, opacity=0.5),
)
class SurfaceExample(Scene):
default_camera_config = dict(samples=4)

View file

@ -163,12 +163,13 @@ class LaggedStartMap(LaggedStart):
group: Mobject,
arg_creator: Callable[[Mobject], tuple] | None = None,
run_time: float = 2.0,
lag_ratio: float = DEFAULT_LAGGED_START_LAG_RATIO,
**kwargs
):
anim_kwargs = dict(kwargs)
anim_kwargs.pop("lag_ratio", None)
super().__init__(
*(AnimationClass(submob, **anim_kwargs) for submob in group),
group=group,
run_time=run_time,
lag_ratio=lag_ratio,
)

View file

@ -305,7 +305,8 @@ class Camera(object):
def get_pixel_array(self) -> np.ndarray:
raw = self.get_raw_fbo_data(dtype='f4')
flat_arr = np.frombuffer(raw, dtype='f4')
arr = flat_arr.reshape([*self.fbo.size, self.n_channels])
arr = flat_arr.reshape([*reversed(self.fbo.size), self.n_channels])
arr = arr[::-1]
# Convert from float
return (self.rgb_max_val * arr).astype(self.pixel_array_dtype)

View file

@ -394,6 +394,7 @@ class Mobject(object):
if self in old_submob.parents:
old_submob.parents.remove(self)
self.submobjects[index] = new_submob
new_submob.parents.append(self)
self.assemble_family()
return self
@ -667,6 +668,9 @@ class Mobject(object):
if set(d1).difference(d2):
return False
for key in d1:
if isinstance(d1[key], np.ndarray) and isinstance(d2[key], np.ndarray):
if not d1[key].size == d2[key].size:
return False
if not np.isclose(d1[key], d2[key]).all():
return False
return True
@ -821,7 +825,7 @@ class Mobject(object):
def is_changing(self) -> bool:
return self._is_animating or self.has_updaters
def set_animating_status(self, is_animating: bool, recurse: bool = True) -> None:
def set_animating_status(self, is_animating: bool, recurse: bool = True):
for mob in (*self.get_family(recurse), *self.get_ancestors(extended=True)):
mob._is_animating = is_animating
return self

View file

@ -323,7 +323,6 @@ class VMobjectFromSVGPath(VMobject):
self.set_points(self.get_points_without_null_curves())
# Save to a file for future use
np.save(points_filepath, self.get_points())
self.get_triangulation()
def handle_commands(self) -> None:
segment_class_to_func_map = {

View file

@ -212,16 +212,61 @@ class Tex(StringMobject):
):
return self.set_parts_color_by_dict(color_map)
def get_tex(self) -> str:
return self.get_string()
# Specific to Tex
def substr_to_path_count(self, substr: str) -> int:
tex = self.get_tex()
if len(self) != num_tex_symbols(tex):
log.warning(
f"Estimated size of {tex} does not match true size",
)
log.warning(f"Estimated size of {tex} does not match true size")
return num_tex_symbols(substr)
def get_tex(self) -> str:
return self.get_string()
def make_number_changable(
self,
value: float | int | str,
index: int = 0,
replace_all: bool = False,
**config,
) -> VMobject:
substr = str(value)
parts = self.select_parts(substr)
if len(parts) == 0:
log.warning(f"{value} not found in Tex.make_number_changable call")
return VMobject()
if index > len(parts) - 1:
log.warning(f"Requested {index}th occurance of {value}, but only {len(parts)} exist")
return VMobject()
if not replace_all:
parts = [parts[index]]
from manimlib.mobject.numbers import DecimalNumber
decimal_mobs = []
for part in parts:
if "." in substr:
num_decimal_places = len(substr.split(".")[1])
else:
num_decimal_places = 0
decimal_mob = DecimalNumber(
float(value),
num_decimal_places=num_decimal_places,
**config,
)
decimal_mob.replace(part)
decimal_mob.match_style(part)
if len(part) > 1:
self.remove(*part[1:])
self.replace_submobject(self.submobjects.index(part[0]), decimal_mob)
decimal_mobs.append(decimal_mob)
# Replace substr with something that looks like a tex command. This
# is to ensure Tex.substr_to_path_count counts it correctly.
self.string = self.string.replace(substr, R"\decimalmob", 1)
if replace_all:
return VGroup(*decimal_mobs)
return decimal_mobs[index]
class TexText(Tex):

View file

@ -261,10 +261,9 @@ class Scene(object):
shell.showtraceback((etype, evalue, tb), tb_offset=tb_offset)
if self.embed_error_sound:
os.system("printf '\a'")
self.play(VFadeInThenOut(
FullScreenRectangle().set_stroke(RED, 30).set_fill(opacity=0),
run_time=0.5,
))
rect = FullScreenRectangle().set_stroke(RED, 30).set_fill(opacity=0)
rect.fix_in_frame()
self.play(VFadeInThenOut(rect, run_time=0.5))
shell.set_custom_exc((Exception,), custom_exc)

View file

@ -7,6 +7,8 @@ import re
import moderngl
import numpy as np
from functools import lru_cache
from manimlib.utils.directories import get_shader_dir
from manimlib.utils.file_ops import find_file
from manimlib.utils.iterables import resize_array
@ -159,7 +161,7 @@ class ShaderWrapper(object):
# For caching
filename_to_code_map: dict[str, str] = {}
@lru_cache(maxsize=12)
def get_shader_code_from_file(filename: str) -> str | None:
if not filename:
return None

View file

@ -88,7 +88,7 @@ def color_gradient(
alphas_mod1[-1] = 1
floors[-1] = len(rgbs) - 2
return [
rgb_to_color(interpolate(rgbs[i], rgbs[i + 1], alpha))
rgb_to_color(np.sqrt(interpolate(rgbs[i]**2, rgbs[i + 1]**2, alpha)))
for i, alpha in zip(floors, alphas_mod1)
]
@ -98,13 +98,13 @@ def interpolate_color(
color2: ManimColor,
alpha: float
) -> Color:
rgb = interpolate(color_to_rgb(color1), color_to_rgb(color2), alpha)
rgb = np.sqrt(interpolate(color_to_rgb(color1)**2, color_to_rgb(color2)**2, alpha))
return rgb_to_color(rgb)
def average_color(*colors: ManimColor) -> Color:
rgbs = np.array(list(map(color_to_rgb, colors)))
return rgb_to_color(rgbs.mean(0))
return rgb_to_color(np.sqrt((rgbs**2).mean(0)))
def random_color() -> Color:

View file

@ -15,7 +15,7 @@ def num_tex_symbols(tex: str) -> int:
# First, remove patterns like \begin{align}, \phantom{thing},
# \begin{array}{cc}, etc.
pattern = "|".join(
r"(\\" + s + ")" + r"(\{\w+\})?(\{\w+\})?(\[\w+\])?"
rf"(\\{s})" + r"(\{\w+\})?(\{\w+\})?(\[\w+\])?"
for s in ["begin", "end", "phantom"]
)
for tup in re.findall(pattern, tex):