From 3c0d682efc39dfa6be4920a5cfa6fe8786835f24 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 1 Feb 2023 13:02:34 -0800 Subject: [PATCH 1/5] Change clicking behavior in InteractiveScene --- manimlib/scene/interactive_scene.py | 50 +++++++++++++++++------------ 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/manimlib/scene/interactive_scene.py b/manimlib/scene/interactive_scene.py index 855eb1c4..51ce153e 100644 --- a/manimlib/scene/interactive_scene.py +++ b/manimlib/scene/interactive_scene.py @@ -29,6 +29,11 @@ from manimlib.utils.family_ops import extract_mobject_family_members from manimlib.utils.space_ops import get_norm from manimlib.utils.tex_file_writing import LatexError +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from manimlib.typing import Vect3 + SELECT_KEY = 's' UNSELECT_KEY = 'u' @@ -68,7 +73,7 @@ class InteractiveScene(Scene): """ corner_dot_config = dict( color=WHITE, - radius=0.025, + radius=0.05, glow_factor=2.0, ) selection_rectangle_stroke_color = WHITE @@ -273,7 +278,7 @@ class InteractiveScene(Scene): def get_corner_dots(self, mobject: Mobject) -> Mobject: 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: vects = [DL, UL, UR, DR] else: @@ -389,7 +394,9 @@ class InteractiveScene(Scene): for mob in reversed(self.get_selection_search_set()): if self.selection_rectangle.is_touching(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): mp = self.mouse_point.get_center() @@ -509,7 +516,6 @@ class InteractiveScene(Scene): super().on_key_release(symbol, modifiers) if chr(symbol) == SELECT_KEY: self.gather_new_selection() - # self.remove(self.crosshair) if chr(symbol) in GRAB_KEYS: self.is_grabbing = False elif chr(symbol) == INFORMATION_KEY: @@ -518,7 +524,7 @@ class InteractiveScene(Scene): self.prepare_resizing(about_corner=False) # Mouse actions - def handle_grabbing(self, point: np.ndarray): + def handle_grabbing(self, point: Vect3): diff = point - self.mouse_to_selection if self.window.is_key_pressed(ord(GRAB_KEY)): self.selection.move_to(diff) @@ -527,7 +533,7 @@ class InteractiveScene(Scene): elif self.window.is_key_pressed(ord(Y_GRAB_KEY)): 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"): return vect = point - self.scale_about_point @@ -547,15 +553,16 @@ class InteractiveScene(Scene): 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( - point, search_set=self.get_selection_search_set(), + point, + search_set=self.get_selection_search_set(), buff=SMALL_BUFF ) if mob is not None: 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 to_search = [ sm @@ -568,10 +575,9 @@ class InteractiveScene(Scene): self.selection.set_color(mob.get_color()) 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) - ff_point = self.frame.to_fixed_frame_point(point) - self.crosshair.move_to(ff_point) + self.crosshair.move_to(self.frame.to_fixed_frame_point(point)) if self.is_grabbing: self.handle_grabbing(point) elif self.window.is_key_pressed(ord(RESIZE_KEY)): @@ -579,17 +585,19 @@ class InteractiveScene(Scene): elif self.window.is_key_pressed(ord(SELECT_KEY)) and self.window.is_key_pressed(SHIFT_SYMBOL): 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) if self.color_palette in self.mobjects: 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: self.clear_selection() From d0c6d4d38607895182b8441594bf23d25bd8eedd Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 1 Feb 2023 13:02:56 -0800 Subject: [PATCH 2/5] Accept list of Vect3 as an input to Mobject.set_points --- manimlib/mobject/mobject.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manimlib/mobject/mobject.py b/manimlib/mobject/mobject.py index b4a6e261..c3554ee2 100644 --- a/manimlib/mobject/mobject.py +++ b/manimlib/mobject/mobject.py @@ -202,7 +202,7 @@ class Mobject(object): return self @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.data["point"][:] = points return self From b9d37a9f7ef02888c67e50f22d22b7ee9005330d Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 1 Feb 2023 13:35:10 -0800 Subject: [PATCH 3/5] Add copy_frame_anim_call --- manimlib/scene/interactive_scene.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/manimlib/scene/interactive_scene.py b/manimlib/scene/interactive_scene.py index 51ce153e..3e296e00 100644 --- a/manimlib/scene/interactive_scene.py +++ b/manimlib/scene/interactive_scene.py @@ -8,8 +8,9 @@ from manimlib.animation.fading import FadeIn from manimlib.constants import ARROW_SYMBOLS, CTRL_SYMBOL, DELETE_SYMBOL, SHIFT_SYMBOL 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 FRAME_WIDTH, SMALL_BUFF +from manimlib.constants import FRAME_WIDTH, FRAME_HEIGHT, SMALL_BUFF from manimlib.constants import PI +from manimlib.constants import DEGREES from manimlib.constants import MANIM_COLORS, WHITE, GREY_A, GREY_C from manimlib.mobject.geometry import Line from manimlib.mobject.geometry import Rectangle @@ -456,6 +457,7 @@ class InteractiveScene(Scene): else: self.save_mobject_to_file(self.selection) + # Key actions def on_key_press(self, symbol: int, modifiers: int) -> None: super().on_key_press(symbol, modifiers) char = chr(symbol) @@ -494,6 +496,8 @@ class InteractiveScene(Scene): self.toggle_selection_mode() elif char == "s" and modifiers == COMMAND_MODIFIER: self.save_selection_to_file() + elif char == PAN_3D_KEY and modifiers == COMMAND_MODIFIER: + self.copy_frame_anim_call() elif symbol in ARROW_SYMBOLS: self.nudge_selection( vect=[LEFT, UP, RIGHT, DOWN][ARROW_SYMBOLS.index(symbol)], @@ -601,3 +605,18 @@ class InteractiveScene(Scene): self.choose_color(point) else: 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) From 9cadfa181882eb116d74f2dec0e622e0109de6da Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 1 Feb 2023 13:35:46 -0800 Subject: [PATCH 4/5] Make it so that copying a mobject will copy its name, if applicable --- manimlib/scene/interactive_scene.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/manimlib/scene/interactive_scene.py b/manimlib/scene/interactive_scene.py index 3e296e00..3e2db303 100644 --- a/manimlib/scene/interactive_scene.py +++ b/manimlib/scene/interactive_scene.py @@ -3,6 +3,7 @@ from __future__ import annotations import itertools as it import numpy as np import pyperclip +from IPython.core.getipython import get_ipython from manimlib.animation.fading import FadeIn from manimlib.constants import ARROW_SYMBOLS, CTRL_SYMBOL, DELETE_SYMBOL, SHIFT_SYMBOL @@ -26,6 +27,7 @@ from manimlib.mobject.types.vectorized_mobject import VHighlight from manimlib.mobject.types.vectorized_mobject import VMobject from manimlib.scene.scene import Scene 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.space_ops import get_norm from manimlib.utils.tex_file_writing import LatexError @@ -234,9 +236,6 @@ class InteractiveScene(Scene): super().remove(*mobjects) self.regenerate_selection_search_set() - # def increment_time(self, dt: float) -> None: - # super().increment_time(dt) - # Related to selection def toggle_selection_mode(self): @@ -345,8 +344,17 @@ class InteractiveScene(Scene): # Functions for keyboard actions def copy_selection(self): - ids = map(id, self.selection) - pyperclip.copy(",".join(map(str, ids))) + names = [] + 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): clipboard_str = pyperclip.paste() From be602930c3f94cbc374a519cc93862522cfe4206 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 1 Feb 2023 20:11:31 -0800 Subject: [PATCH 5/5] Check that scene has a camera frame in pixel_to_point_coords --- manimlib/window.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/manimlib/window.py b/manimlib/window.py index 7efbca96..9bc5090a 100644 --- a/manimlib/window.py +++ b/manimlib/window.py @@ -78,6 +78,9 @@ class Window(PygletWindow): py: int, relative: bool = False ) -> np.ndarray: + if not hasattr(self.scene, "frame"): + return np.zeros(3) + pixel_shape = np.array(self.size) fixed_frame_shape = np.array(FRAME_SHAPE) frame = self.scene.frame