Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[pull] master from 3b1b:master #15

Merged
merged 17 commits into from
Dec 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions manimlib/animation/composition.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ def __init__(self,
run_time: float = -1, # If negative, default to sum of inputed animation runtimes
lag_ratio: float = 0.0,
group: Mobject | None = None,
group_type: type = Group,
**kwargs
):
self.animations = [prepare_animation(anim) for anim in animations]
Expand All @@ -38,7 +39,7 @@ def __init__(self,
self.lag_ratio = lag_ratio
self.group = group
if self.group is None:
self.group = Group(*remove_list_redundancies(
self.group = group_type(*remove_list_redundancies(
[anim.mobject for anim in self.animations]
))

Expand All @@ -49,7 +50,7 @@ def __init__(self,
**kwargs
)

def get_all_mobjects(self) -> Group:
def get_all_mobjects(self) -> Mobject:
return self.group

def begin(self) -> None:
Expand Down
26 changes: 9 additions & 17 deletions manimlib/animation/transform_matching_parts.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,20 @@

import numpy as np

from manimlib.animation.animation import Animation
from manimlib.animation.composition import AnimationGroup
from manimlib.animation.fading import FadeInFromPoint
from manimlib.animation.fading import FadeOutToPoint
from manimlib.animation.fading import FadeTransformPieces
from manimlib.animation.fading import FadeTransform
from manimlib.animation.transform import ReplacementTransform
from manimlib.animation.transform import Transform
from manimlib.mobject.mobject import Mobject
from manimlib.mobject.mobject import Group
from manimlib.mobject.svg.string_mobject import StringMobject
from manimlib.mobject.svg.old_tex_mobject import OldTex
from manimlib.mobject.svg.tex_mobject import Tex
from manimlib.mobject.types.vectorized_mobject import VGroup
from manimlib.mobject.types.vectorized_mobject import VMobject

from typing import TYPE_CHECKING

if TYPE_CHECKING:
from manimlib.mobject.svg.old_tex_mobject import SingleStringTex
from manimlib.scene.scene import Scene


Expand Down Expand Up @@ -151,13 +145,14 @@ def __init__(
match_animation: type = Transform,
mismatch_animation: type = Transform,
run_time=2,
lag_ratio=0,
**kwargs,
):
self.source = source
self.target = target
matched_keys = matched_keys or list()
key_map = key_map or dict()
self.anim_config = dict(run_time=run_time, **kwargs)
self.anim_config = dict(**kwargs)

# We will progressively build up a list of transforms
# from characters in source to those in target. These
Expand Down Expand Up @@ -208,7 +203,12 @@ def __init__(
target_char, source.get_center(),
**self.anim_config
))
super().__init__(*self.anims)
super().__init__(
*self.anims,
run_time=run_time,
lag_ratio=lag_ratio,
group_type=VGroup,
)

def add_transform(
self,
Expand All @@ -230,18 +230,10 @@ def add_transform(
self.target_chars.remove(char)

def find_pairs_with_matching_shapes(self, chars1, chars2) -> list[tuple[VMobject, VMobject]]:
for char in (*chars1, *chars2):
char.save_state()
char.set_height(1)
char.center()
result = []
for char1, char2 in it.product(chars1, chars2):
p1 = char1.get_points()
p2 = char2.get_points()
if len(p1) == len(p2) and np.isclose(p1, p2 , atol=1e-1).all():
if char1.has_same_shape_as(char2):
result.append((char1, char2))
for char in (*chars1, *chars2):
char.restore()
return result

def clean_up_from_scene(self, scene: Scene) -> None:
Expand Down
2 changes: 2 additions & 0 deletions manimlib/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -491,4 +491,6 @@ def get_configuration(args: Namespace) -> dict:
"presenter_mode": args.presenter_mode,
"leave_progress_bars": args.leave_progress_bars,
"show_animation_progress": args.show_animation_progress,
"embed_exception_mode": custom_config["embed_exception_mode"],
"embed_error_sound": custom_config["embed_error_sound"],
}
2 changes: 2 additions & 0 deletions manimlib/default_config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,5 @@ camera_resolutions:
4k: "3840x2160"
default_resolution: "high"
fps: 30
embed_exception_mode: "Verbose"
embed_error_sound: False
21 changes: 5 additions & 16 deletions manimlib/extract_scene.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,22 +54,11 @@ def prompt_user_for_choice(scene_classes):


def get_scene_config(config):
return dict([
(key, config[key])
for key in [
"window_config",
"camera_config",
"file_writer_config",
"skip_animations",
"start_at_animation_number",
"end_at_animation_number",
"leave_progress_bars",
"show_animation_progress",
"preview",
"presenter_mode",
]
])

scene_parameters = inspect.signature(Scene).parameters.keys()
return {
key: config[key]
for key in set(scene_parameters).intersection(config.keys())
}

def compute_total_frames(scene_class, scene_config):
"""
Expand Down
23 changes: 14 additions & 9 deletions manimlib/mobject/mobject.py
Original file line number Diff line number Diff line change
Expand Up @@ -657,7 +657,7 @@ def become(self, mobject: Mobject, match_updaters=False):
self.match_updaters(mobject)
return self

def looks_identical(self, mobject: Mobject):
def looks_identical(self, mobject: Mobject) -> bool:
fam1 = self.family_members_with_points()
fam2 = mobject.family_members_with_points()
if len(fam1) != len(fam2):
Expand All @@ -667,15 +667,20 @@ def looks_identical(self, mobject: Mobject):
if set(d1).difference(d2):
return False
for key in d1:
eq = (d1[key] == d2[key])
if isinstance(eq, bool):
if not eq:
return False
else:
if not eq.all():
return False
if not np.isclose(d1[key], d2[key]).all():
return False
return True

def has_same_shape_as(self, mobject: Mobject) -> bool:
# Normalize both point sets by centering and making height 1
points1, points2 = (
(m.get_all_points() - m.get_center()) / m.get_height()
for m in (self, mobject)
)
if len(points1) != len(points2):
return False
return bool(np.isclose(points1, points2).all())

# Creating new Mobjects from this one

def replicate(self, n: int) -> Group:
Expand Down Expand Up @@ -1723,7 +1728,7 @@ def lock_matching_data(self, mobject1: Mobject, mobject2: Mobject):
for sm, sm1, sm2 in zip(self.get_family(), mobject1.get_family(), mobject2.get_family()):
keys = sm.data.keys() & sm1.data.keys() & sm2.data.keys()
sm.lock_data(list(filter(
lambda key: np.all(sm1.data[key] == sm2.data[key]),
lambda key: (sm1.data[key] == sm2.data[key]).all(),
keys,
)))
return self
Expand Down
9 changes: 6 additions & 3 deletions manimlib/mobject/svg/string_mobject.py
Original file line number Diff line number Diff line change
Expand Up @@ -547,7 +547,7 @@ def build_groups(self) -> VGroup:

def select_parts(self, selector: Selector) -> VGroup:
specified_substrings = self.get_specified_substrings()
if isinstance(selector, str) and selector not in specified_substrings:
if isinstance(selector, (str, re.Pattern)) and selector not in specified_substrings:
return self.select_unisolated_substring(selector)
indices_list = self.get_submob_indices_lists_by_selector(selector)
return self.build_parts_from_indices_lists(indices_list)
Expand All @@ -563,11 +563,14 @@ def select_part(self, selector: Selector, index: int = 0) -> VMobject:
def substr_to_path_count(self, substr: str) -> int:
return len(re.sub(R"\s", "", substr))

def select_unisolated_substring(self, substr: str) -> VGroup:
def select_unisolated_substring(self, pattern: str | re.Pattern) -> VGroup:
if isinstance(pattern, str):
pattern = re.compile(re.escape(pattern))
result = []
for match in re.finditer(re.escape(substr), self.string):
for match in re.finditer(pattern, self.string):
index = match.start()
start = self.substr_to_path_count(self.string[:index])
substr = match.group()
end = start + self.substr_to_path_count(substr)
result.append(self[start:end])
return VGroup(*result)
Expand Down
4 changes: 1 addition & 3 deletions manimlib/mobject/svg/svg_mobject.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,15 +292,13 @@ class VMobjectFromSVGPath(VMobject):
def __init__(
self,
path_obj: se.Path,
long_lines: bool = False,
should_subdivide_sharp_curves: bool = False,
should_remove_null_curves: bool = False,
should_remove_null_curves: bool = True,
**kwargs
):
# Get rid of arcs
path_obj.approximate_arcs_with_quads()
self.path_obj = path_obj
self.long_lines = long_lines
self.should_subdivide_sharp_curves = should_subdivide_sharp_curves
self.should_remove_null_curves = should_remove_null_curves
super().__init__(**kwargs)
Expand Down
47 changes: 19 additions & 28 deletions manimlib/mobject/types/vectorized_mobject.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,11 +148,6 @@ def add(self, *vmobjects: VMobject):
raise Exception("All submobjects must be of type VMobject")
super().add(*vmobjects)

def copy(self, deep: bool = False) -> VMobject:
result = super().copy(deep)
result.shader_wrapper_list = [sw.copy() for sw in self.shader_wrapper_list]
return result

# Colors
def init_colors(self):
self.set_fill(
Expand Down Expand Up @@ -809,6 +804,11 @@ def get_unit_normal(self) -> Vect3:
# Alignment
def align_points(self, vmobject: VMobject):
if self.get_num_points() == len(vmobject.get_points()):
# If both have fill, and they have the same shape, just
# give them the same triangulation so that it's not recalculated
# needlessly throughout an animation
if self.has_fill() and vmobject.has_fill() and self.has_same_shape_as(vmobject):
vmobject.triangulation = self.triangulation
return

for mob in self, vmobject:
Expand Down Expand Up @@ -1077,14 +1077,6 @@ def init_shader_data(self):
render_primitive=self.render_primitive,
)

self.shader_wrapper_list = [
self.stroke_shader_wrapper.copy(), # Use for back stroke
self.fill_shader_wrapper.copy(),
self.stroke_shader_wrapper.copy(),
]
for sw in self.shader_wrapper_list:
sw.uniforms = self.uniforms

def refresh_shader_wrapper_id(self):
for wrapper in [self.fill_shader_wrapper, self.stroke_shader_wrapper]:
wrapper.refresh_id()
Expand All @@ -1107,30 +1099,29 @@ def get_shader_wrapper_list(self) -> list[ShaderWrapper]:
# Build up data lists
fill_shader_wrappers = []
stroke_shader_wrappers = []
back_stroke_shader_wrappers = []
for submob in self.family_members_with_points():
if submob.has_fill():
fill_shader_wrappers.append(submob.get_fill_shader_wrapper())
if submob.has_stroke():
ssw = submob.get_stroke_shader_wrapper()
if submob.draw_stroke_behind_fill:
back_stroke_shader_wrappers.append(ssw)
else:
stroke_shader_wrappers.append(ssw)
stroke_shader_wrappers.append(submob.get_stroke_shader_wrapper())
if submob.draw_stroke_behind_fill:
self.draw_stroke_behind_fill = True

# Combine data lists
sw_lists = [
back_stroke_shader_wrappers,
fill_shader_wrappers,
stroke_shader_wrappers,
]
for sw, sw_list in zip(self.shader_wrapper_list, sw_lists):
self_sws = [self.fill_shader_wrapper, self.stroke_shader_wrapper]
sw_lists = [fill_shader_wrappers, stroke_shader_wrappers]
for sw, sw_list in zip(self_sws, sw_lists):
if not sw_list:
sw.vert_data = resize_array(sw.vert_data, 0)
continue
sw.read_in(*sw_list)
if sw is sw_list[0]:
sw.combine_with(*sw_list[1:])
else:
sw.read_in(*sw_list)
sw.depth_test = any(sw.depth_test for sw in sw_list)
sw.uniforms.update(sw_list[0].uniforms)
return list(filter(lambda sw: len(sw.vert_data) > 0, self.shader_wrapper_list))
if self.draw_stroke_behind_fill:
self_sws.reverse()
return [sw for sw in self_sws if len(sw.vert_data) > 0]

def get_stroke_shader_data(self) -> np.ndarray:
points = self.get_points()
Expand Down
24 changes: 23 additions & 1 deletion manimlib/scene/scene.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,18 @@
from tqdm import tqdm as ProgressDisplay

from manimlib.animation.animation import prepare_animation
from manimlib.animation.fading import VFadeInThenOut
from manimlib.camera.camera import Camera
from manimlib.config import get_module
from manimlib.constants import ARROW_SYMBOLS
from manimlib.constants import DEFAULT_WAIT_TIME
from manimlib.constants import COMMAND_MODIFIER
from manimlib.constants import SHIFT_MODIFIER
from manimlib.constants import RED
from manimlib.event_handler import EVENT_DISPATCHER
from manimlib.event_handler.event_type import EventType
from manimlib.logger import log
from manimlib.mobject.frame import FullScreenRectangle
from manimlib.mobject.mobject import _AnimationBuilder
from manimlib.mobject.mobject import Group
from manimlib.mobject.mobject import Mobject
Expand All @@ -34,7 +37,6 @@
from manimlib.scene.scene_file_writer import SceneFileWriter
from manimlib.utils.family_ops import extract_mobject_family_members
from manimlib.utils.family_ops import recursive_mobject_remove
from manimlib.utils.iterables import list_difference_update

from typing import TYPE_CHECKING

Expand Down Expand Up @@ -74,6 +76,8 @@ def __init__(
preview: bool = True,
presenter_mode: bool = False,
show_animation_progress: bool = False,
embed_exception_mode: str = "",
embed_error_sound: bool = False,
):
self.skip_animations = skip_animations
self.always_update_mobjects = always_update_mobjects
Expand All @@ -83,6 +87,8 @@ def __init__(
self.preview = preview
self.presenter_mode = presenter_mode
self.show_animation_progress = show_animation_progress
self.embed_exception_mode = embed_exception_mode
self.embed_error_sound = embed_error_sound

self.camera_config = {**self.default_camera_config, **camera_config}
self.window_config = {**self.default_window_config, **window_config}
Expand Down Expand Up @@ -249,6 +255,22 @@ def post_cell_func():

shell.events.register("post_run_cell", post_cell_func)

# Flash border, and potentially play sound, on exceptions
def custom_exc(shell, etype, evalue, tb, tb_offset=None):
# still show the error don't just swallow it
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,
))

shell.set_custom_exc((Exception,), custom_exc)

# Set desired exception mode
shell.magic(f"xmode {self.embed_exception_mode}")

# Launch shell
shell(
local_ns=local_ns,
Expand Down
3 changes: 2 additions & 1 deletion manimlib/shader_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,8 @@ def replace_code(self, old: str, new: str) -> None:
self.refresh_id()

def combine_with(self, *shader_wrappers: ShaderWrapper) -> ShaderWrapper:
self.read_in(self.copy(), *shader_wrappers)
if len(shader_wrappers) > 0:
self.read_in(self.copy(), *shader_wrappers)
return self

def read_in(self, *shader_wrappers: ShaderWrapper) -> ShaderWrapper:
Expand Down
Loading