mirror of
https://github.com/3b1b/manim.git
synced 2025-08-05 16:49:03 +00:00
commit
50960eefd4
10 changed files with 149 additions and 23 deletions
|
@ -193,11 +193,7 @@ class TexTransformExample(Scene):
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
self.wait()
|
self.wait()
|
||||||
self.play(
|
self.play(TransformMatchingStrings(lines[1].copy(), lines[2]))
|
||||||
TransformMatchingStrings(
|
|
||||||
lines[1].copy(), lines[2],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
self.wait()
|
self.wait()
|
||||||
self.play(
|
self.play(
|
||||||
TransformMatchingStrings(
|
TransformMatchingStrings(
|
||||||
|
@ -208,6 +204,17 @@ class TexTransformExample(Scene):
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
self.wait(2)
|
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))
|
self.play(LaggedStartMap(FadeOut, lines, shift=2 * RIGHT))
|
||||||
|
|
||||||
# TransformMatchingShapes will try to line up all pieces of a
|
# TransformMatchingShapes will try to line up all pieces of a
|
||||||
|
@ -453,6 +460,74 @@ class GraphExample(Scene):
|
||||||
self.wait()
|
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):
|
class SurfaceExample(Scene):
|
||||||
default_camera_config = dict(samples=4)
|
default_camera_config = dict(samples=4)
|
||||||
|
|
||||||
|
|
|
@ -163,12 +163,13 @@ class LaggedStartMap(LaggedStart):
|
||||||
group: Mobject,
|
group: Mobject,
|
||||||
arg_creator: Callable[[Mobject], tuple] | None = None,
|
arg_creator: Callable[[Mobject], tuple] | None = None,
|
||||||
run_time: float = 2.0,
|
run_time: float = 2.0,
|
||||||
|
lag_ratio: float = DEFAULT_LAGGED_START_LAG_RATIO,
|
||||||
**kwargs
|
**kwargs
|
||||||
):
|
):
|
||||||
anim_kwargs = dict(kwargs)
|
anim_kwargs = dict(kwargs)
|
||||||
anim_kwargs.pop("lag_ratio", None)
|
anim_kwargs.pop("lag_ratio", None)
|
||||||
super().__init__(
|
super().__init__(
|
||||||
*(AnimationClass(submob, **anim_kwargs) for submob in group),
|
*(AnimationClass(submob, **anim_kwargs) for submob in group),
|
||||||
group=group,
|
|
||||||
run_time=run_time,
|
run_time=run_time,
|
||||||
|
lag_ratio=lag_ratio,
|
||||||
)
|
)
|
||||||
|
|
|
@ -305,7 +305,8 @@ class Camera(object):
|
||||||
def get_pixel_array(self) -> np.ndarray:
|
def get_pixel_array(self) -> np.ndarray:
|
||||||
raw = self.get_raw_fbo_data(dtype='f4')
|
raw = self.get_raw_fbo_data(dtype='f4')
|
||||||
flat_arr = np.frombuffer(raw, 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
|
# Convert from float
|
||||||
return (self.rgb_max_val * arr).astype(self.pixel_array_dtype)
|
return (self.rgb_max_val * arr).astype(self.pixel_array_dtype)
|
||||||
|
|
||||||
|
|
|
@ -394,6 +394,7 @@ class Mobject(object):
|
||||||
if self in old_submob.parents:
|
if self in old_submob.parents:
|
||||||
old_submob.parents.remove(self)
|
old_submob.parents.remove(self)
|
||||||
self.submobjects[index] = new_submob
|
self.submobjects[index] = new_submob
|
||||||
|
new_submob.parents.append(self)
|
||||||
self.assemble_family()
|
self.assemble_family()
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
@ -667,6 +668,9 @@ class Mobject(object):
|
||||||
if set(d1).difference(d2):
|
if set(d1).difference(d2):
|
||||||
return False
|
return False
|
||||||
for key in d1:
|
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():
|
if not np.isclose(d1[key], d2[key]).all():
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
@ -821,7 +825,7 @@ class Mobject(object):
|
||||||
def is_changing(self) -> bool:
|
def is_changing(self) -> bool:
|
||||||
return self._is_animating or self.has_updaters
|
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)):
|
for mob in (*self.get_family(recurse), *self.get_ancestors(extended=True)):
|
||||||
mob._is_animating = is_animating
|
mob._is_animating = is_animating
|
||||||
return self
|
return self
|
||||||
|
|
|
@ -323,7 +323,6 @@ class VMobjectFromSVGPath(VMobject):
|
||||||
self.set_points(self.get_points_without_null_curves())
|
self.set_points(self.get_points_without_null_curves())
|
||||||
# Save to a file for future use
|
# Save to a file for future use
|
||||||
np.save(points_filepath, self.get_points())
|
np.save(points_filepath, self.get_points())
|
||||||
self.get_triangulation()
|
|
||||||
|
|
||||||
def handle_commands(self) -> None:
|
def handle_commands(self) -> None:
|
||||||
segment_class_to_func_map = {
|
segment_class_to_func_map = {
|
||||||
|
|
|
@ -212,16 +212,61 @@ class Tex(StringMobject):
|
||||||
):
|
):
|
||||||
return self.set_parts_color_by_dict(color_map)
|
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:
|
def substr_to_path_count(self, substr: str) -> int:
|
||||||
tex = self.get_tex()
|
tex = self.get_tex()
|
||||||
if len(self) != num_tex_symbols(tex):
|
if len(self) != num_tex_symbols(tex):
|
||||||
log.warning(
|
log.warning(f"Estimated size of {tex} does not match true size")
|
||||||
f"Estimated size of {tex} does not match true size",
|
|
||||||
)
|
|
||||||
return num_tex_symbols(substr)
|
return num_tex_symbols(substr)
|
||||||
|
|
||||||
def get_tex(self) -> str:
|
def make_number_changable(
|
||||||
return self.get_string()
|
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):
|
class TexText(Tex):
|
||||||
|
|
|
@ -261,10 +261,9 @@ class Scene(object):
|
||||||
shell.showtraceback((etype, evalue, tb), tb_offset=tb_offset)
|
shell.showtraceback((etype, evalue, tb), tb_offset=tb_offset)
|
||||||
if self.embed_error_sound:
|
if self.embed_error_sound:
|
||||||
os.system("printf '\a'")
|
os.system("printf '\a'")
|
||||||
self.play(VFadeInThenOut(
|
rect = FullScreenRectangle().set_stroke(RED, 30).set_fill(opacity=0)
|
||||||
FullScreenRectangle().set_stroke(RED, 30).set_fill(opacity=0),
|
rect.fix_in_frame()
|
||||||
run_time=0.5,
|
self.play(VFadeInThenOut(rect, run_time=0.5))
|
||||||
))
|
|
||||||
|
|
||||||
shell.set_custom_exc((Exception,), custom_exc)
|
shell.set_custom_exc((Exception,), custom_exc)
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,8 @@ import re
|
||||||
import moderngl
|
import moderngl
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
|
from functools import lru_cache
|
||||||
|
|
||||||
from manimlib.utils.directories import get_shader_dir
|
from manimlib.utils.directories import get_shader_dir
|
||||||
from manimlib.utils.file_ops import find_file
|
from manimlib.utils.file_ops import find_file
|
||||||
from manimlib.utils.iterables import resize_array
|
from manimlib.utils.iterables import resize_array
|
||||||
|
@ -159,7 +161,7 @@ class ShaderWrapper(object):
|
||||||
# For caching
|
# For caching
|
||||||
filename_to_code_map: dict[str, str] = {}
|
filename_to_code_map: dict[str, str] = {}
|
||||||
|
|
||||||
|
@lru_cache(maxsize=12)
|
||||||
def get_shader_code_from_file(filename: str) -> str | None:
|
def get_shader_code_from_file(filename: str) -> str | None:
|
||||||
if not filename:
|
if not filename:
|
||||||
return None
|
return None
|
||||||
|
|
|
@ -88,7 +88,7 @@ def color_gradient(
|
||||||
alphas_mod1[-1] = 1
|
alphas_mod1[-1] = 1
|
||||||
floors[-1] = len(rgbs) - 2
|
floors[-1] = len(rgbs) - 2
|
||||||
return [
|
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)
|
for i, alpha in zip(floors, alphas_mod1)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -98,13 +98,13 @@ def interpolate_color(
|
||||||
color2: ManimColor,
|
color2: ManimColor,
|
||||||
alpha: float
|
alpha: float
|
||||||
) -> Color:
|
) -> 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)
|
return rgb_to_color(rgb)
|
||||||
|
|
||||||
|
|
||||||
def average_color(*colors: ManimColor) -> Color:
|
def average_color(*colors: ManimColor) -> Color:
|
||||||
rgbs = np.array(list(map(color_to_rgb, colors)))
|
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:
|
def random_color() -> Color:
|
||||||
|
|
|
@ -15,7 +15,7 @@ def num_tex_symbols(tex: str) -> int:
|
||||||
# First, remove patterns like \begin{align}, \phantom{thing},
|
# First, remove patterns like \begin{align}, \phantom{thing},
|
||||||
# \begin{array}{cc}, etc.
|
# \begin{array}{cc}, etc.
|
||||||
pattern = "|".join(
|
pattern = "|".join(
|
||||||
r"(\\" + s + ")" + r"(\{\w+\})?(\{\w+\})?(\[\w+\])?"
|
rf"(\\{s})" + r"(\{\w+\})?(\{\w+\})?(\[\w+\])?"
|
||||||
for s in ["begin", "end", "phantom"]
|
for s in ["begin", "end", "phantom"]
|
||||||
)
|
)
|
||||||
for tup in re.findall(pattern, tex):
|
for tup in re.findall(pattern, tex):
|
||||||
|
|
Loading…
Add table
Reference in a new issue