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.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)
|
||||
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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):
|
||||
|
|
Loading…
Add table
Reference in a new issue