mirror of
https://github.com/3b1b/manim.git
synced 2025-11-14 13:07:44 +00:00
Merge pull request #1987 from 3b1b/interactive-scene-update
Interactive scene update
This commit is contained in:
commit
4e674e571c
3 changed files with 66 additions and 28 deletions
|
|
@ -202,7 +202,7 @@ class Mobject(object):
|
||||||
return self
|
return self
|
||||||
|
|
||||||
@affects_data
|
@affects_data
|
||||||
def set_points(self, points: Vect3Array) -> Self:
|
def set_points(self, points: Vect3Array | list[Vect3]) -> Self:
|
||||||
self.resize_points(len(points), resize_func=resize_preserving_order)
|
self.resize_points(len(points), resize_func=resize_preserving_order)
|
||||||
self.data["point"][:] = points
|
self.data["point"][:] = points
|
||||||
return self
|
return self
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,15 @@ from __future__ import annotations
|
||||||
import itertools as it
|
import itertools as it
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pyperclip
|
import pyperclip
|
||||||
|
from IPython.core.getipython import get_ipython
|
||||||
|
|
||||||
from manimlib.animation.fading import FadeIn
|
from manimlib.animation.fading import FadeIn
|
||||||
from manimlib.constants import ARROW_SYMBOLS, CTRL_SYMBOL, DELETE_SYMBOL, SHIFT_SYMBOL
|
from manimlib.constants import ARROW_SYMBOLS, CTRL_SYMBOL, DELETE_SYMBOL, SHIFT_SYMBOL
|
||||||
from manimlib.constants import COMMAND_MODIFIER, SHIFT_MODIFIER
|
from manimlib.constants import COMMAND_MODIFIER, SHIFT_MODIFIER
|
||||||
from manimlib.constants import DL, DOWN, DR, LEFT, ORIGIN, RIGHT, UL, UP, UR
|
from manimlib.constants import DL, DOWN, DR, LEFT, ORIGIN, RIGHT, UL, UP, UR
|
||||||
from manimlib.constants import FRAME_WIDTH, SMALL_BUFF
|
from manimlib.constants import FRAME_WIDTH, FRAME_HEIGHT, SMALL_BUFF
|
||||||
from manimlib.constants import PI
|
from manimlib.constants import PI
|
||||||
|
from manimlib.constants import DEGREES
|
||||||
from manimlib.constants import MANIM_COLORS, WHITE, GREY_A, GREY_C
|
from manimlib.constants import MANIM_COLORS, WHITE, GREY_A, GREY_C
|
||||||
from manimlib.mobject.geometry import Line
|
from manimlib.mobject.geometry import Line
|
||||||
from manimlib.mobject.geometry import Rectangle
|
from manimlib.mobject.geometry import Rectangle
|
||||||
|
|
@ -25,10 +27,16 @@ from manimlib.mobject.types.vectorized_mobject import VHighlight
|
||||||
from manimlib.mobject.types.vectorized_mobject import VMobject
|
from manimlib.mobject.types.vectorized_mobject import VMobject
|
||||||
from manimlib.scene.scene import Scene
|
from manimlib.scene.scene import Scene
|
||||||
from manimlib.scene.scene import SceneState
|
from manimlib.scene.scene import SceneState
|
||||||
|
from manimlib.scene.scene import PAN_3D_KEY
|
||||||
from manimlib.utils.family_ops import extract_mobject_family_members
|
from manimlib.utils.family_ops import extract_mobject_family_members
|
||||||
from manimlib.utils.space_ops import get_norm
|
from manimlib.utils.space_ops import get_norm
|
||||||
from manimlib.utils.tex_file_writing import LatexError
|
from manimlib.utils.tex_file_writing import LatexError
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from manimlib.typing import Vect3
|
||||||
|
|
||||||
|
|
||||||
SELECT_KEY = 's'
|
SELECT_KEY = 's'
|
||||||
UNSELECT_KEY = 'u'
|
UNSELECT_KEY = 'u'
|
||||||
|
|
@ -68,7 +76,7 @@ class InteractiveScene(Scene):
|
||||||
"""
|
"""
|
||||||
corner_dot_config = dict(
|
corner_dot_config = dict(
|
||||||
color=WHITE,
|
color=WHITE,
|
||||||
radius=0.025,
|
radius=0.05,
|
||||||
glow_factor=2.0,
|
glow_factor=2.0,
|
||||||
)
|
)
|
||||||
selection_rectangle_stroke_color = WHITE
|
selection_rectangle_stroke_color = WHITE
|
||||||
|
|
@ -228,9 +236,6 @@ class InteractiveScene(Scene):
|
||||||
super().remove(*mobjects)
|
super().remove(*mobjects)
|
||||||
self.regenerate_selection_search_set()
|
self.regenerate_selection_search_set()
|
||||||
|
|
||||||
# def increment_time(self, dt: float) -> None:
|
|
||||||
# super().increment_time(dt)
|
|
||||||
|
|
||||||
# Related to selection
|
# Related to selection
|
||||||
|
|
||||||
def toggle_selection_mode(self):
|
def toggle_selection_mode(self):
|
||||||
|
|
@ -273,7 +278,7 @@ class InteractiveScene(Scene):
|
||||||
|
|
||||||
def get_corner_dots(self, mobject: Mobject) -> Mobject:
|
def get_corner_dots(self, mobject: Mobject) -> Mobject:
|
||||||
dots = DotCloud(**self.corner_dot_config)
|
dots = DotCloud(**self.corner_dot_config)
|
||||||
radius = self.corner_dot_config["radius"]
|
radius = float(self.corner_dot_config["radius"])
|
||||||
if mobject.get_depth() < 1e-2:
|
if mobject.get_depth() < 1e-2:
|
||||||
vects = [DL, UL, UR, DR]
|
vects = [DL, UL, UR, DR]
|
||||||
else:
|
else:
|
||||||
|
|
@ -339,8 +344,17 @@ class InteractiveScene(Scene):
|
||||||
# Functions for keyboard actions
|
# Functions for keyboard actions
|
||||||
|
|
||||||
def copy_selection(self):
|
def copy_selection(self):
|
||||||
ids = map(id, self.selection)
|
names = []
|
||||||
pyperclip.copy(",".join(map(str, ids)))
|
shell = get_ipython()
|
||||||
|
for mob in self.selection:
|
||||||
|
name = str(id(mob))
|
||||||
|
if shell is None:
|
||||||
|
continue
|
||||||
|
for key, value in shell.user_ns.items():
|
||||||
|
if mob is value:
|
||||||
|
name = key
|
||||||
|
names.append(name)
|
||||||
|
pyperclip.copy(", ".join(names))
|
||||||
|
|
||||||
def paste_selection(self):
|
def paste_selection(self):
|
||||||
clipboard_str = pyperclip.paste()
|
clipboard_str = pyperclip.paste()
|
||||||
|
|
@ -389,7 +403,9 @@ class InteractiveScene(Scene):
|
||||||
for mob in reversed(self.get_selection_search_set()):
|
for mob in reversed(self.get_selection_search_set()):
|
||||||
if self.selection_rectangle.is_touching(mob):
|
if self.selection_rectangle.is_touching(mob):
|
||||||
additions.append(mob)
|
additions.append(mob)
|
||||||
self.add_to_selection(*additions)
|
if self.selection_rectangle.get_arc_length() < 1e-2:
|
||||||
|
break
|
||||||
|
self.toggle_from_selection(*additions)
|
||||||
|
|
||||||
def prepare_grab(self):
|
def prepare_grab(self):
|
||||||
mp = self.mouse_point.get_center()
|
mp = self.mouse_point.get_center()
|
||||||
|
|
@ -449,6 +465,7 @@ class InteractiveScene(Scene):
|
||||||
else:
|
else:
|
||||||
self.save_mobject_to_file(self.selection)
|
self.save_mobject_to_file(self.selection)
|
||||||
|
|
||||||
|
# Key actions
|
||||||
def on_key_press(self, symbol: int, modifiers: int) -> None:
|
def on_key_press(self, symbol: int, modifiers: int) -> None:
|
||||||
super().on_key_press(symbol, modifiers)
|
super().on_key_press(symbol, modifiers)
|
||||||
char = chr(symbol)
|
char = chr(symbol)
|
||||||
|
|
@ -487,6 +504,8 @@ class InteractiveScene(Scene):
|
||||||
self.toggle_selection_mode()
|
self.toggle_selection_mode()
|
||||||
elif char == "s" and modifiers == COMMAND_MODIFIER:
|
elif char == "s" and modifiers == COMMAND_MODIFIER:
|
||||||
self.save_selection_to_file()
|
self.save_selection_to_file()
|
||||||
|
elif char == PAN_3D_KEY and modifiers == COMMAND_MODIFIER:
|
||||||
|
self.copy_frame_anim_call()
|
||||||
elif symbol in ARROW_SYMBOLS:
|
elif symbol in ARROW_SYMBOLS:
|
||||||
self.nudge_selection(
|
self.nudge_selection(
|
||||||
vect=[LEFT, UP, RIGHT, DOWN][ARROW_SYMBOLS.index(symbol)],
|
vect=[LEFT, UP, RIGHT, DOWN][ARROW_SYMBOLS.index(symbol)],
|
||||||
|
|
@ -509,7 +528,6 @@ class InteractiveScene(Scene):
|
||||||
super().on_key_release(symbol, modifiers)
|
super().on_key_release(symbol, modifiers)
|
||||||
if chr(symbol) == SELECT_KEY:
|
if chr(symbol) == SELECT_KEY:
|
||||||
self.gather_new_selection()
|
self.gather_new_selection()
|
||||||
# self.remove(self.crosshair)
|
|
||||||
if chr(symbol) in GRAB_KEYS:
|
if chr(symbol) in GRAB_KEYS:
|
||||||
self.is_grabbing = False
|
self.is_grabbing = False
|
||||||
elif chr(symbol) == INFORMATION_KEY:
|
elif chr(symbol) == INFORMATION_KEY:
|
||||||
|
|
@ -518,7 +536,7 @@ class InteractiveScene(Scene):
|
||||||
self.prepare_resizing(about_corner=False)
|
self.prepare_resizing(about_corner=False)
|
||||||
|
|
||||||
# Mouse actions
|
# Mouse actions
|
||||||
def handle_grabbing(self, point: np.ndarray):
|
def handle_grabbing(self, point: Vect3):
|
||||||
diff = point - self.mouse_to_selection
|
diff = point - self.mouse_to_selection
|
||||||
if self.window.is_key_pressed(ord(GRAB_KEY)):
|
if self.window.is_key_pressed(ord(GRAB_KEY)):
|
||||||
self.selection.move_to(diff)
|
self.selection.move_to(diff)
|
||||||
|
|
@ -527,7 +545,7 @@ class InteractiveScene(Scene):
|
||||||
elif self.window.is_key_pressed(ord(Y_GRAB_KEY)):
|
elif self.window.is_key_pressed(ord(Y_GRAB_KEY)):
|
||||||
self.selection.set_y(diff[1])
|
self.selection.set_y(diff[1])
|
||||||
|
|
||||||
def handle_resizing(self, point: np.ndarray):
|
def handle_resizing(self, point: Vect3):
|
||||||
if not hasattr(self, "scale_about_point"):
|
if not hasattr(self, "scale_about_point"):
|
||||||
return
|
return
|
||||||
vect = point - self.scale_about_point
|
vect = point - self.scale_about_point
|
||||||
|
|
@ -547,15 +565,16 @@ class InteractiveScene(Scene):
|
||||||
about_point=self.scale_about_point
|
about_point=self.scale_about_point
|
||||||
)
|
)
|
||||||
|
|
||||||
def handle_sweeping_selection(self, point: np.ndarray):
|
def handle_sweeping_selection(self, point: Vect3):
|
||||||
mob = self.point_to_mobject(
|
mob = self.point_to_mobject(
|
||||||
point, search_set=self.get_selection_search_set(),
|
point,
|
||||||
|
search_set=self.get_selection_search_set(),
|
||||||
buff=SMALL_BUFF
|
buff=SMALL_BUFF
|
||||||
)
|
)
|
||||||
if mob is not None:
|
if mob is not None:
|
||||||
self.add_to_selection(mob)
|
self.add_to_selection(mob)
|
||||||
|
|
||||||
def choose_color(self, point: np.ndarray):
|
def choose_color(self, point: Vect3):
|
||||||
# Search through all mobject on the screen, not just the palette
|
# Search through all mobject on the screen, not just the palette
|
||||||
to_search = [
|
to_search = [
|
||||||
sm
|
sm
|
||||||
|
|
@ -568,10 +587,9 @@ class InteractiveScene(Scene):
|
||||||
self.selection.set_color(mob.get_color())
|
self.selection.set_color(mob.get_color())
|
||||||
self.remove(self.color_palette)
|
self.remove(self.color_palette)
|
||||||
|
|
||||||
def on_mouse_motion(self, point: np.ndarray, d_point: np.ndarray) -> None:
|
def on_mouse_motion(self, point: Vect3, d_point: Vect3) -> None:
|
||||||
super().on_mouse_motion(point, d_point)
|
super().on_mouse_motion(point, d_point)
|
||||||
ff_point = self.frame.to_fixed_frame_point(point)
|
self.crosshair.move_to(self.frame.to_fixed_frame_point(point))
|
||||||
self.crosshair.move_to(ff_point)
|
|
||||||
if self.is_grabbing:
|
if self.is_grabbing:
|
||||||
self.handle_grabbing(point)
|
self.handle_grabbing(point)
|
||||||
elif self.window.is_key_pressed(ord(RESIZE_KEY)):
|
elif self.window.is_key_pressed(ord(RESIZE_KEY)):
|
||||||
|
|
@ -579,17 +597,34 @@ class InteractiveScene(Scene):
|
||||||
elif self.window.is_key_pressed(ord(SELECT_KEY)) and self.window.is_key_pressed(SHIFT_SYMBOL):
|
elif self.window.is_key_pressed(ord(SELECT_KEY)) and self.window.is_key_pressed(SHIFT_SYMBOL):
|
||||||
self.handle_sweeping_selection(point)
|
self.handle_sweeping_selection(point)
|
||||||
|
|
||||||
def on_mouse_release(self, point: np.ndarray, button: int, mods: int) -> None:
|
def on_mouse_drag(
|
||||||
|
self,
|
||||||
|
point: Vect3,
|
||||||
|
d_point: Vect3,
|
||||||
|
buttons: int,
|
||||||
|
modifiers: int
|
||||||
|
) -> None:
|
||||||
|
super().on_mouse_drag(point, d_point, buttons, modifiers)
|
||||||
|
self.crosshair.move_to(self.frame.to_fixed_frame_point(point))
|
||||||
|
|
||||||
|
def on_mouse_release(self, point: Vect3, button: int, mods: int) -> None:
|
||||||
super().on_mouse_release(point, button, mods)
|
super().on_mouse_release(point, button, mods)
|
||||||
if self.color_palette in self.mobjects:
|
if self.color_palette in self.mobjects:
|
||||||
self.choose_color(point)
|
self.choose_color(point)
|
||||||
return
|
|
||||||
mobject = self.point_to_mobject(
|
|
||||||
point,
|
|
||||||
search_set=self.get_selection_search_set(),
|
|
||||||
buff=1e-4,
|
|
||||||
)
|
|
||||||
if mobject is not None:
|
|
||||||
self.toggle_from_selection(mobject)
|
|
||||||
else:
|
else:
|
||||||
self.clear_selection()
|
self.clear_selection()
|
||||||
|
|
||||||
|
# Copying code to recreate state
|
||||||
|
def copy_frame_anim_call(self):
|
||||||
|
frame = self.frame
|
||||||
|
center = frame.get_center()
|
||||||
|
height = frame.get_height()
|
||||||
|
angles = frame.get_euler_angles()
|
||||||
|
|
||||||
|
call = f"self.frame.animate.reorient"
|
||||||
|
call += str(tuple((angles / DEGREES).astype(int)))
|
||||||
|
if any(center != 0):
|
||||||
|
call += f".move_to({list(np.round(center, 2))})"
|
||||||
|
if height != FRAME_HEIGHT:
|
||||||
|
call += ".set_height({:.2f})".format(height)
|
||||||
|
pyperclip.copy(call)
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,9 @@ class Window(PygletWindow):
|
||||||
py: int,
|
py: int,
|
||||||
relative: bool = False
|
relative: bool = False
|
||||||
) -> np.ndarray:
|
) -> np.ndarray:
|
||||||
|
if not hasattr(self.scene, "frame"):
|
||||||
|
return np.zeros(3)
|
||||||
|
|
||||||
pixel_shape = np.array(self.size)
|
pixel_shape = np.array(self.size)
|
||||||
fixed_frame_shape = np.array(FRAME_SHAPE)
|
fixed_frame_shape = np.array(FRAME_SHAPE)
|
||||||
frame = self.scene.frame
|
frame = self.scene.frame
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue