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 diff --git a/manimlib/scene/interactive_scene.py b/manimlib/scene/interactive_scene.py index 855eb1c4..3e2db303 100644 --- a/manimlib/scene/interactive_scene.py +++ b/manimlib/scene/interactive_scene.py @@ -3,13 +3,15 @@ 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 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 @@ -25,10 +27,16 @@ 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 +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from manimlib.typing import Vect3 + SELECT_KEY = 's' UNSELECT_KEY = 'u' @@ -68,7 +76,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 @@ -228,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): @@ -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: @@ -339,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() @@ -389,7 +403,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() @@ -449,6 +465,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) @@ -487,6 +504,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)], @@ -509,7 +528,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 +536,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 +545,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 +565,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 +587,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 +597,34 @@ 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() + + # 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) 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