Merge branch '3b1b:master' into refactor

This commit is contained in:
YishiMichael 2022-04-26 10:07:40 +08:00 committed by GitHub
commit 69db53d612
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 168 additions and 73 deletions

View file

@ -245,13 +245,13 @@ def insert_embed_line(file_name: str, scene_name: str, line_marker: str):
n_spaces = get_indent(lines[prev_line_num])
new_lines = list(lines)
new_lines.insert(prev_line_num + 1, " " * n_spaces + "self.embed()\n")
with open(file_name, 'w') as fp:
alt_file = file_name.replace(".py", "_insert_embed.py")
with open(alt_file, 'w') as fp:
fp.writelines(new_lines)
try:
yield file_name
yield alt_file
finally:
with open(file_name, 'w') as fp:
fp.writelines(lines)
os.remove(alt_file)
def get_custom_config():

View file

@ -895,7 +895,15 @@ class Polygon(VMobject):
def get_vertices(self) -> list[np.ndarray]:
return self.get_start_anchors()
def round_corners(self, radius: float = 0.5):
def round_corners(self, radius: float | None = None):
if radius is None:
verts = self.get_vertices()
min_edge_length = min(
get_norm(v1 - v2)
for v1, v2 in zip(verts, verts[1:])
if not np.isclose(v1, v2).all()
)
radius = 0.25 * min_edge_length
vertices = self.get_vertices()
arcs = []
for v1, v2, v3 in adjacent_n_tuples(vertices, 3):

View file

@ -343,6 +343,26 @@ class Mobject(object):
def family_members_with_points(self):
return [m for m in self.get_family() if m.has_points()]
def get_ancestors(self, extended: bool = False) -> list[Mobject]:
"""
Returns parents, grandparents, etc.
Order of result should be from higher members of the hierarchy down.
If extended is set to true, it includes the ancestors of all family members,
e.g. any other parents of a submobject
"""
ancestors = []
to_process = list(self.get_family(recurse=extended))
excluded = set(to_process)
while to_process:
for p in to_process.pop().parents:
if p not in excluded:
ancestors.append(p)
to_process.append(p)
# Remove redundancies while preserving order
ancestors.reverse()
return list(dict.fromkeys(ancestors))
def add(self, *mobjects: Mobject):
if self in mobjects:
raise Exception("Mobject cannot contain self")
@ -354,13 +374,14 @@ class Mobject(object):
self.assemble_family()
return self
def remove(self, *mobjects: Mobject):
def remove(self, *mobjects: Mobject, reassemble: bool = True):
for mobject in mobjects:
if mobject in self.submobjects:
self.submobjects.remove(mobject)
if self in mobject.parents:
mobject.parents.remove(self)
self.assemble_family()
if reassemble:
self.assemble_family()
return self
def add_to_back(self, *mobjects: Mobject):
@ -381,7 +402,7 @@ class Mobject(object):
return self
def set_submobjects(self, submobject_list: list[Mobject]):
self.remove(*self.submobjects)
self.remove(*self.submobjects, reassemble=False)
self.add(*submobject_list)
return self
@ -526,17 +547,24 @@ class Mobject(object):
for key, value in self.uniforms.items()
}
result.submobjects = []
result.add(*(sm.copy() for sm in self.submobjects))
result.match_updaters(self)
# Instead of adding using result.add, which does some checks for updating
# updater statues and bounding box, just directly modify the family-related
# lists
result.submobjects = [sm.copy() for sm in self.submobjects]
for sm in result.submobjects:
sm.parents = [result]
result.family = [result, *it.chain(*(sm.get_family() for sm in result.submobjects))]
# Similarly, instead of calling match_updaters, since we know the status
# won't have changed, just directly match.
result.non_time_updaters = list(self.non_time_updaters)
result.time_based_updaters = list(self.time_based_updaters)
family = self.get_family()
for attr, value in list(self.__dict__.items()):
if isinstance(value, Mobject) and value is not self:
if value in family:
setattr(result, attr, result.family[self.family.index(value)])
else:
setattr(result, attr, value.copy())
if isinstance(value, np.ndarray):
setattr(result, attr, value.copy())
if isinstance(value, ShaderWrapper):
@ -590,6 +618,23 @@ class Mobject(object):
self.refresh_bounding_box(recurse_down=True)
return self
def looks_identical(self, mobject: Mobject):
fam1 = self.get_family()
fam2 = mobject.get_family()
if len(fam1) != len(fam2):
return False
for m1, m2 in zip(fam1, fam2):
for d1, d2 in [(m1.data, m2.data), (m1.uniforms, m2.uniforms)]:
if set(d1).difference(d2):
return False
for key in d1:
if isinstance(d1[key], np.ndarray):
if not np.all(d1[key] == d2[key]):
return False
elif d1[key] != d2[key]:
return False
return True
# Creating new Mobjects from this one
def replicate(self, n: int) -> Group:

View file

@ -206,10 +206,18 @@ class InteractiveScene(Scene):
return self.get_corner_dots(mobject)
def refresh_selection_highlight(self):
self.selection_highlight.set_submobjects([
self.get_highlight(mob)
for mob in self.selection
])
if len(self.selection) > 0:
self.remove(self.selection_highlight)
self.selection_highlight.set_submobjects([
self.get_highlight(mob)
for mob in self.selection
])
index = min((
i for i, mob in enumerate(self.mobjects)
for sm in self.selection
if sm in mob.get_family()
))
self.mobjects.insert(index, self.selection_highlight)
def add_to_selection(self, *mobjects):
mobs = list(filter(
@ -219,9 +227,11 @@ class InteractiveScene(Scene):
if len(mobs) == 0:
return
self.selection.add(*mobs)
self.selection_highlight.add(*map(self.get_highlight, mobs))
for mob in mobs:
mob.set_animating_status(True)
self.refresh_selection_highlight()
for sm in mobs:
for mob in self.mobjects:
if sm in mob.get_family():
mob.set_animating_status(True)
self.refresh_static_mobjects()
def toggle_from_selection(self, *mobjects):
@ -307,10 +317,13 @@ class InteractiveScene(Scene):
def gather_new_selection(self):
self.is_selecting = False
self.remove(self.selection_rectangle)
for mob in reversed(self.get_selection_search_set()):
if self.selection_rectangle.is_touching(mob):
self.add_to_selection(mob)
if self.selection_rectangle in self.mobjects:
self.remove(self.selection_rectangle)
additions = []
for mob in reversed(self.get_selection_search_set()):
if self.selection_rectangle.is_touching(mob):
additions.append(mob)
self.add_to_selection(*additions)
def prepare_grab(self):
mp = self.mouse_point.get_center()

View file

@ -28,7 +28,7 @@ from manimlib.mobject.types.vectorized_mobject import VMobject
from manimlib.scene.scene_file_writer import SceneFileWriter
from manimlib.utils.config_ops import digest_config
from manimlib.utils.family_ops import extract_mobject_family_members
from manimlib.utils.family_ops import restructure_list_to_exclude_certain_family_members
from manimlib.utils.iterables import list_difference_update
from typing import TYPE_CHECKING
@ -63,7 +63,7 @@ class Scene(object):
"presenter_mode": False,
"linger_after_completion": True,
"pan_sensitivity": 3,
"max_num_saved_states": 20,
"max_num_saved_states": 50,
}
def __init__(self, **kwargs):
@ -178,10 +178,7 @@ class Scene(object):
# Enables gui interactions during the embed
def inputhook(context):
while not context.input_is_ready():
if self.window.is_closing:
pass
# self.window.destroy()
else:
if not self.window.is_closing:
self.update_frame(dt=0)
pt_inputhooks.register("manim", inputhook)
@ -190,6 +187,7 @@ class Scene(object):
# Operation to run after each ipython command
def post_cell_func(*args, **kwargs):
self.refresh_static_mobjects()
self.save_state()
shell.events.register("post_run_cell", post_cell_func)
@ -304,10 +302,31 @@ class Scene(object):
))
return self
def remove(self, *mobjects_to_remove: Mobject):
self.mobjects = restructure_list_to_exclude_certain_family_members(
self.mobjects, mobjects_to_remove
)
def replace(self, mobject: Mobject, *replacements: Mobject):
if mobject in self.mobjects:
index = self.mobjects.index(mobject)
self.mobjects = [
*self.mobjects[:index],
*replacements,
*self.mobjects[index + 1:]
]
return self
def remove(self, *mobjects: Mobject):
"""
Removes anything in mobjects from scenes mobject list, but in the event that one
of the items to be removed is a member of the family of an item in mobject_list,
the other family members are added back into the list.
For example, if the scene includes Group(m1, m2, m3), and we call scene.remove(m1),
the desired behavior is for the scene to then include m2 and m3 (ungrouped).
"""
for mob in mobjects:
# First restructure self.mobjects so that parents/grandparents/etc. are replaced
# with their children, likewise for all ancestors in the extended family.
for ancestor in mob.get_ancestors(extended=True):
self.replace(ancestor, *ancestor.submobjects)
self.mobjects = list_difference_update(self.mobjects, mob.get_family())
return self
def bring_to_front(self, *mobjects: Mobject):
@ -510,6 +529,7 @@ class Scene(object):
def wrapper(self, *args, **kwargs):
if self.inside_embed:
self.save_state()
self.update_skipping_status()
should_write = not self.skip_animations
if should_write:
@ -525,6 +545,9 @@ class Scene(object):
if should_write:
self.file_writer.end_animation()
if self.inside_embed:
self.save_state()
self.num_plays += 1
return wrapper
@ -581,10 +604,10 @@ class Scene(object):
note: str = None,
ignore_presenter_mode: bool = False
):
if note:
log.info(note)
self.update_mobjects(dt=0) # Any problems with this?
if self.presenter_mode and not self.skip_animations and not ignore_presenter_mode:
if note:
log.info(note)
while self.hold_on_wait:
self.update_frame(dt=1 / self.camera.frame_rate)
self.hold_on_wait = True
@ -632,8 +655,22 @@ class Scene(object):
# Helpers for interactive development
def get_state(self) -> list[tuple[Mobject, Mobject]]:
return [(mob, mob.copy()) for mob in self.mobjects]
def get_state(self) -> tuple[list[tuple[Mobject, Mobject]], int]:
if self.undo_stack:
last_state = dict(self.undo_stack[-1])
else:
last_state = {}
result = []
n_changes = 0
for mob in self.mobjects:
# If it hasn't changed since the last state, just point to the
# same copy as before
if mob in last_state and last_state[mob].looks_identical(mob):
result.append((mob, last_state[mob]))
else:
result.append((mob, mob.copy()))
n_changes += 1
return result, n_changes
def restore_state(self, mobject_states: list[tuple[Mobject, Mobject]]):
self.mobjects = [mob.become(mob_copy) for mob, mob_copy in mobject_states]
@ -642,19 +679,23 @@ class Scene(object):
if not self.preview:
return
self.redo_stack = []
self.undo_stack.append(self.get_state())
if len(self.undo_stack) > self.max_num_saved_states:
self.undo_stack.pop(0)
state, n_changes = self.get_state()
if n_changes > 0:
self.undo_stack.append(state)
if len(self.undo_stack) > self.max_num_saved_states:
self.undo_stack.pop(0)
def undo(self):
if self.undo_stack:
self.redo_stack.append(self.get_state())
state, n_changes = self.get_state()
self.redo_stack.append(state)
self.restore_state(self.undo_stack.pop())
self.refresh_static_mobjects()
def redo(self):
if self.redo_stack:
self.undo_stack.append(self.get_state())
state, n_changes = self.get_state()
self.undo_stack.append(state)
self.restore_state(self.redo_stack.pop())
self.refresh_static_mobjects()

View file

@ -45,13 +45,30 @@ class ShaderWrapper(object):
self.init_program_code()
self.refresh_id()
def __eq__(self, shader_wrapper: ShaderWrapper):
return all((
np.all(self.vert_data == shader_wrapper.vert_data),
np.all(self.vert_indices == shader_wrapper.vert_indices),
self.shader_folder == shader_wrapper.shader_folder,
all(
np.all(self.uniforms[key] == shader_wrapper.uniforms[key])
for key in self.uniforms
),
all(
self.texture_paths[key] == shader_wrapper.texture_paths[key]
for key in self.texture_paths
),
self.depth_test == shader_wrapper.depth_test,
self.render_primitive == shader_wrapper.render_primitive,
))
def copy(self):
result = copy.copy(self)
result.vert_data = np.array(self.vert_data)
if result.vert_indices is not None:
result.vert_indices = np.array(self.vert_indices)
if self.uniforms:
result.uniforms = dict(self.uniforms)
result.uniforms = {key: np.array(value) for key, value in self.uniforms.items()}
if self.texture_paths:
result.texture_paths = dict(self.texture_paths)
return result

View file

@ -18,32 +18,3 @@ def extract_mobject_family_members(
for sm in mob.get_family()
if (not exclude_pointless) or sm.has_points()
]
def restructure_list_to_exclude_certain_family_members(
mobject_list: list[Mobject],
to_remove: list[Mobject]
) -> list[Mobject]:
"""
Removes anything in to_remove from mobject_list, but in the event that one of
the items to be removed is a member of the family of an item in mobject_list,
the other family members are added back into the list.
This is useful in cases where a scene contains a group, e.g. Group(m1, m2, m3),
but one of its submobjects is removed, e.g. scene.remove(m1), it's useful
for the list of mobject_list to be edited to contain other submobjects, but not m1.
"""
new_list = []
to_remove = extract_mobject_family_members(to_remove)
def add_safe_mobjects_from_list(list_to_examine, set_to_remove):
for mob in list_to_examine:
if mob in set_to_remove:
continue
intersect = set_to_remove.intersection(mob.get_family())
if intersect:
add_safe_mobjects_from_list(mob.submobjects, intersect)
else:
new_list.append(mob)
add_safe_mobjects_from_list(mobject_list, set(to_remove))
return new_list