mirror of
https://github.com/3b1b/manim.git
synced 2025-07-28 04:23:16 +08:00

This simply didn't work, and had no resilience to changes to the library. For cases where this might be useful, it's likely much better deliberately save specific data which is time-consuming to generate on the fly.
631 lines
22 KiB
Python
631 lines
22 KiB
Python
from __future__ import annotations
|
|
|
|
import itertools as it
|
|
import numpy as np
|
|
import pyperclip
|
|
from IPython.core.getipython import get_ipython
|
|
from pyglet.window import key as PygletWindowKeys
|
|
|
|
from manimlib.animation.fading import FadeIn
|
|
from manimlib.constants import ARROW_SYMBOLS, CTRL_SYMBOL, DELETE_SYMBOL, SHIFT_SYMBOL
|
|
from manimlib.constants import DL, DOWN, DR, LEFT, ORIGIN, RIGHT, UL, UP, UR
|
|
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
|
|
from manimlib.mobject.geometry import Square
|
|
from manimlib.mobject.mobject import Group
|
|
from manimlib.mobject.mobject import Mobject
|
|
from manimlib.mobject.numbers import DecimalNumber
|
|
from manimlib.mobject.svg.tex_mobject import Tex
|
|
from manimlib.mobject.svg.text_mobject import Text
|
|
from manimlib.mobject.types.dot_cloud import DotCloud
|
|
from manimlib.mobject.types.vectorized_mobject import VGroup
|
|
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.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'
|
|
GRAB_KEY = 'g'
|
|
X_GRAB_KEY = 'h'
|
|
Y_GRAB_KEY = 'v'
|
|
GRAB_KEYS = [GRAB_KEY, X_GRAB_KEY, Y_GRAB_KEY]
|
|
RESIZE_KEY = 't'
|
|
COLOR_KEY = 'c'
|
|
INFORMATION_KEY = 'i'
|
|
CURSOR_KEY = 'k'
|
|
COPY_FRAME_POSITION_KEY = 'p'
|
|
|
|
ALL_MODIFIERS = PygletWindowKeys.MOD_CTRL | PygletWindowKeys.MOD_COMMAND | PygletWindowKeys.MOD_SHIFT
|
|
|
|
# Note, a lot of the functionality here is still buggy and very much a work in progress.
|
|
|
|
|
|
class InteractiveScene(Scene):
|
|
"""
|
|
To select mobjects on screen, hold ctrl and move the mouse to highlight a region,
|
|
or just tap ctrl to select the mobject under the cursor.
|
|
|
|
Pressing command + t will toggle between modes where you either select top level
|
|
mobjects part of the scene, or low level pieces.
|
|
|
|
Hold 'g' to grab the selection and move it around
|
|
Hold 'h' to drag it constrained in the horizontal direction
|
|
Hold 'v' to drag it constrained in the vertical direction
|
|
Hold 't' to resize selection, adding 'shift' to resize with respect to a corner
|
|
|
|
Command + 'c' copies the ids of selections to clipboard
|
|
Command + 'v' will paste either:
|
|
- The copied mobject
|
|
- A Tex mobject based on copied LaTeX
|
|
- A Text mobject based on copied Text
|
|
Command + 'z' restores selection back to its original state
|
|
Command + 's' saves the selected mobjects to file
|
|
"""
|
|
corner_dot_config = dict(
|
|
color=WHITE,
|
|
radius=0.05,
|
|
glow_factor=2.0,
|
|
)
|
|
selection_rectangle_stroke_color = WHITE
|
|
selection_rectangle_stroke_width = 1.0
|
|
palette_colors = MANIM_COLORS
|
|
selection_nudge_size = 0.05
|
|
cursor_location_config = dict(
|
|
font_size=24,
|
|
fill_color=GREY_C,
|
|
num_decimal_places=3,
|
|
)
|
|
time_label_config = dict(
|
|
font_size=24,
|
|
fill_color=GREY_C,
|
|
num_decimal_places=1,
|
|
)
|
|
crosshair_width = 0.2
|
|
crosshair_style = dict(
|
|
stroke_color=GREY_A,
|
|
stroke_width=[3, 0, 3],
|
|
)
|
|
|
|
def setup(self):
|
|
self.selection = Group()
|
|
self.selection_highlight = self.get_selection_highlight()
|
|
self.selection_rectangle = self.get_selection_rectangle()
|
|
self.crosshair = self.get_crosshair()
|
|
self.information_label = self.get_information_label()
|
|
self.color_palette = self.get_color_palette()
|
|
self.unselectables = [
|
|
self.selection,
|
|
self.selection_highlight,
|
|
self.selection_rectangle,
|
|
self.crosshair,
|
|
self.information_label,
|
|
self.camera.frame
|
|
]
|
|
self.select_top_level_mobs = True
|
|
self.regenerate_selection_search_set()
|
|
|
|
self.is_selecting = False
|
|
self.is_grabbing = False
|
|
|
|
self.add(self.selection_highlight)
|
|
|
|
def get_selection_rectangle(self):
|
|
rect = Rectangle(
|
|
stroke_color=self.selection_rectangle_stroke_color,
|
|
stroke_width=self.selection_rectangle_stroke_width,
|
|
)
|
|
rect.fix_in_frame()
|
|
rect.fixed_corner = ORIGIN
|
|
rect.add_updater(self.update_selection_rectangle)
|
|
return rect
|
|
|
|
def update_selection_rectangle(self, rect: Rectangle):
|
|
p1 = rect.fixed_corner
|
|
p2 = self.frame.to_fixed_frame_point(self.mouse_point.get_center())
|
|
rect.set_points_as_corners([
|
|
p1, np.array([p2[0], p1[1], 0]),
|
|
p2, np.array([p1[0], p2[1], 0]),
|
|
p1,
|
|
])
|
|
return rect
|
|
|
|
def get_selection_highlight(self):
|
|
result = Group()
|
|
result.tracked_mobjects = []
|
|
result.add_updater(self.update_selection_highlight)
|
|
return result
|
|
|
|
def update_selection_highlight(self, highlight: Mobject):
|
|
if set(highlight.tracked_mobjects) == set(self.selection):
|
|
return
|
|
|
|
# Otherwise, refresh contents of highlight
|
|
highlight.tracked_mobjects = list(self.selection)
|
|
highlight.set_submobjects([
|
|
self.get_highlight(mob)
|
|
for mob in self.selection
|
|
])
|
|
try:
|
|
index = min((
|
|
i for i, mob in enumerate(self.mobjects)
|
|
for sm in self.selection
|
|
if sm in mob.get_family()
|
|
))
|
|
self.mobjects.remove(highlight)
|
|
self.mobjects.insert(index - 1, highlight)
|
|
except ValueError:
|
|
pass
|
|
|
|
def get_crosshair(self):
|
|
lines = VMobject().replicate(2)
|
|
lines[0].set_points([LEFT, ORIGIN, RIGHT])
|
|
lines[1].set_points([UP, ORIGIN, DOWN])
|
|
crosshair = VGroup(*lines)
|
|
|
|
crosshair.set_width(self.crosshair_width)
|
|
crosshair.set_style(**self.crosshair_style)
|
|
crosshair.set_animating_status(True)
|
|
crosshair.fix_in_frame()
|
|
return crosshair
|
|
|
|
def get_color_palette(self):
|
|
palette = VGroup(*(
|
|
Square(fill_color=color, fill_opacity=1, side_length=1)
|
|
for color in self.palette_colors
|
|
))
|
|
palette.set_stroke(width=0)
|
|
palette.arrange(RIGHT, buff=0.5)
|
|
palette.set_width(FRAME_WIDTH - 0.5)
|
|
palette.to_edge(DOWN, buff=SMALL_BUFF)
|
|
palette.fix_in_frame()
|
|
return palette
|
|
|
|
def get_information_label(self):
|
|
loc_label = VGroup(*(
|
|
DecimalNumber(**self.cursor_location_config)
|
|
for n in range(3)
|
|
))
|
|
|
|
def update_coords(loc_label):
|
|
for mob, coord in zip(loc_label, self.mouse_point.get_location()):
|
|
mob.set_value(coord)
|
|
loc_label.arrange(RIGHT, buff=loc_label.get_height())
|
|
loc_label.to_corner(DR, buff=SMALL_BUFF)
|
|
loc_label.fix_in_frame()
|
|
return loc_label
|
|
|
|
loc_label.add_updater(update_coords)
|
|
|
|
time_label = DecimalNumber(0, **self.time_label_config)
|
|
time_label.to_corner(DL, buff=SMALL_BUFF)
|
|
time_label.fix_in_frame()
|
|
time_label.add_updater(lambda m, dt: m.increment_value(dt))
|
|
|
|
return VGroup(loc_label, time_label)
|
|
|
|
# Overrides
|
|
def get_state(self):
|
|
return SceneState(self, ignore=[
|
|
self.selection_highlight,
|
|
self.selection_rectangle,
|
|
self.crosshair,
|
|
])
|
|
|
|
def restore_state(self, scene_state: SceneState):
|
|
super().restore_state(scene_state)
|
|
self.mobjects.insert(0, self.selection_highlight)
|
|
|
|
def add(self, *mobjects: Mobject):
|
|
super().add(*mobjects)
|
|
self.regenerate_selection_search_set()
|
|
|
|
def remove(self, *mobjects: Mobject):
|
|
super().remove(*mobjects)
|
|
self.regenerate_selection_search_set()
|
|
|
|
# Related to selection
|
|
|
|
def toggle_selection_mode(self):
|
|
self.select_top_level_mobs = not self.select_top_level_mobs
|
|
self.refresh_selection_scope()
|
|
self.regenerate_selection_search_set()
|
|
|
|
def get_selection_search_set(self) -> list[Mobject]:
|
|
return self.selection_search_set
|
|
|
|
def regenerate_selection_search_set(self):
|
|
selectable = list(filter(
|
|
lambda m: m not in self.unselectables,
|
|
self.mobjects
|
|
))
|
|
if self.select_top_level_mobs:
|
|
self.selection_search_set = selectable
|
|
else:
|
|
self.selection_search_set = [
|
|
submob
|
|
for mob in selectable
|
|
for submob in mob.family_members_with_points()
|
|
]
|
|
|
|
def refresh_selection_scope(self):
|
|
curr = list(self.selection)
|
|
if self.select_top_level_mobs:
|
|
self.selection.set_submobjects([
|
|
mob
|
|
for mob in self.mobjects
|
|
if any(sm in mob.get_family() for sm in curr)
|
|
])
|
|
self.selection.refresh_bounding_box(recurse_down=True)
|
|
else:
|
|
self.selection.set_submobjects(
|
|
extract_mobject_family_members(
|
|
curr, exclude_pointless=True,
|
|
)
|
|
)
|
|
|
|
def get_corner_dots(self, mobject: Mobject) -> Mobject:
|
|
dots = DotCloud(**self.corner_dot_config)
|
|
radius = float(self.corner_dot_config["radius"])
|
|
if mobject.get_depth() < 1e-2:
|
|
vects = [DL, UL, UR, DR]
|
|
else:
|
|
vects = np.array(list(it.product(*3 * [[-1, 1]])))
|
|
dots.add_updater(lambda d: d.set_points([
|
|
mobject.get_corner(v) + v * radius
|
|
for v in vects
|
|
]))
|
|
return dots
|
|
|
|
def get_highlight(self, mobject: Mobject) -> Mobject:
|
|
if isinstance(mobject, VMobject) and mobject.has_points() and not self.select_top_level_mobs:
|
|
length = max([mobject.get_height(), mobject.get_width()])
|
|
result = VHighlight(
|
|
mobject,
|
|
max_stroke_addition=min([50 * length, 10]),
|
|
)
|
|
result.add_updater(lambda m: m.replace(mobject, stretch=True))
|
|
return result
|
|
elif isinstance(mobject, DotCloud):
|
|
return Mobject()
|
|
else:
|
|
return self.get_corner_dots(mobject)
|
|
|
|
def add_to_selection(self, *mobjects: Mobject):
|
|
mobs = list(filter(
|
|
lambda m: m not in self.unselectables and m not in self.selection,
|
|
mobjects
|
|
))
|
|
if len(mobs) == 0:
|
|
return
|
|
self.selection.add(*mobs)
|
|
for mob in mobs:
|
|
mob.set_animating_status(True)
|
|
|
|
def toggle_from_selection(self, *mobjects: Mobject):
|
|
for mob in mobjects:
|
|
if mob in self.selection:
|
|
self.selection.remove(mob)
|
|
mob.set_animating_status(False)
|
|
mob.refresh_bounding_box()
|
|
else:
|
|
self.add_to_selection(mob)
|
|
|
|
def clear_selection(self):
|
|
for mob in self.selection:
|
|
mob.set_animating_status(False)
|
|
mob.refresh_bounding_box()
|
|
self.selection.set_submobjects([])
|
|
|
|
def disable_interaction(self, *mobjects: Mobject):
|
|
for mob in mobjects:
|
|
for sm in mob.get_family():
|
|
self.unselectables.append(sm)
|
|
self.regenerate_selection_search_set()
|
|
|
|
def enable_interaction(self, *mobjects: Mobject):
|
|
for mob in mobjects:
|
|
for sm in mob.get_family():
|
|
if sm in self.unselectables:
|
|
self.unselectables.remove(sm)
|
|
|
|
# Functions for keyboard actions
|
|
|
|
def copy_selection(self):
|
|
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()
|
|
# Try pasting a mobject
|
|
try:
|
|
ids = map(int, clipboard_str.split(","))
|
|
mobs = map(self.id_to_mobject, ids)
|
|
mob_copies = [m.copy() for m in mobs if m is not None]
|
|
self.clear_selection()
|
|
self.play(*(
|
|
FadeIn(mc, run_time=0.5, scale=1.5)
|
|
for mc in mob_copies
|
|
))
|
|
self.add_to_selection(*mob_copies)
|
|
return
|
|
except ValueError:
|
|
pass
|
|
# Otherwise, treat as tex or text
|
|
if set("\\^=+").intersection(clipboard_str): # Proxy to text for LaTeX
|
|
try:
|
|
new_mob = Tex(clipboard_str)
|
|
except LatexError:
|
|
return
|
|
else:
|
|
new_mob = Text(clipboard_str)
|
|
self.clear_selection()
|
|
self.add(new_mob)
|
|
self.add_to_selection(new_mob)
|
|
|
|
def delete_selection(self):
|
|
self.remove(*self.selection)
|
|
self.clear_selection()
|
|
|
|
def enable_selection(self):
|
|
self.is_selecting = True
|
|
self.add(self.selection_rectangle)
|
|
self.selection_rectangle.fixed_corner = self.frame.to_fixed_frame_point(
|
|
self.mouse_point.get_center()
|
|
)
|
|
|
|
def gather_new_selection(self):
|
|
self.is_selecting = False
|
|
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)
|
|
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()
|
|
self.mouse_to_selection = mp - self.selection.get_center()
|
|
self.is_grabbing = True
|
|
|
|
def prepare_resizing(self, about_corner=False):
|
|
center = self.selection.get_center()
|
|
mp = self.mouse_point.get_center()
|
|
if about_corner:
|
|
self.scale_about_point = self.selection.get_corner(center - mp)
|
|
else:
|
|
self.scale_about_point = center
|
|
self.scale_ref_vect = mp - self.scale_about_point
|
|
self.scale_ref_width = self.selection.get_width()
|
|
self.scale_ref_height = self.selection.get_height()
|
|
|
|
def toggle_color_palette(self):
|
|
if len(self.selection) == 0:
|
|
return
|
|
if self.color_palette not in self.mobjects:
|
|
self.save_state()
|
|
self.add(self.color_palette)
|
|
else:
|
|
self.remove(self.color_palette)
|
|
|
|
def display_information(self, show=True):
|
|
if show:
|
|
self.add(self.information_label)
|
|
else:
|
|
self.remove(self.information_label)
|
|
|
|
def group_selection(self):
|
|
group = self.get_group(*self.selection)
|
|
self.add(group)
|
|
self.clear_selection()
|
|
self.add_to_selection(group)
|
|
|
|
def ungroup_selection(self):
|
|
pieces = []
|
|
for mob in list(self.selection):
|
|
self.remove(mob)
|
|
pieces.extend(list(mob))
|
|
self.clear_selection()
|
|
self.add(*pieces)
|
|
self.add_to_selection(*pieces)
|
|
|
|
def nudge_selection(self, vect: np.ndarray, large: bool = False):
|
|
nudge = self.selection_nudge_size
|
|
if large:
|
|
nudge *= 10
|
|
self.selection.shift(nudge * vect)
|
|
|
|
# Key actions
|
|
def on_key_press(self, symbol: int, modifiers: int) -> None:
|
|
super().on_key_press(symbol, modifiers)
|
|
char = chr(symbol)
|
|
if char == SELECT_KEY and (modifiers & ALL_MODIFIERS) == 0:
|
|
self.enable_selection()
|
|
if char == UNSELECT_KEY:
|
|
self.clear_selection()
|
|
elif char in GRAB_KEYS and (modifiers & ALL_MODIFIERS) == 0:
|
|
self.prepare_grab()
|
|
elif char == RESIZE_KEY and (modifiers & PygletWindowKeys.MOD_SHIFT):
|
|
self.prepare_resizing(about_corner=((modifiers & PygletWindowKeys.MOD_SHIFT) > 0))
|
|
elif symbol == SHIFT_SYMBOL:
|
|
if self.window.is_key_pressed(ord("t")):
|
|
self.prepare_resizing(about_corner=True)
|
|
elif char == COLOR_KEY and (modifiers & ALL_MODIFIERS) == 0:
|
|
self.toggle_color_palette()
|
|
elif char == INFORMATION_KEY and (modifiers & ALL_MODIFIERS) == 0:
|
|
self.display_information()
|
|
elif char == "c" and (modifiers & (PygletWindowKeys.MOD_COMMAND | PygletWindowKeys.MOD_CTRL)):
|
|
self.copy_selection()
|
|
elif char == "v" and (modifiers & (PygletWindowKeys.MOD_COMMAND | PygletWindowKeys.MOD_CTRL)):
|
|
self.paste_selection()
|
|
elif char == "x" and (modifiers & (PygletWindowKeys.MOD_COMMAND | PygletWindowKeys.MOD_CTRL)):
|
|
self.copy_selection()
|
|
self.delete_selection()
|
|
elif symbol == DELETE_SYMBOL:
|
|
self.delete_selection()
|
|
elif char == "a" and (modifiers & (PygletWindowKeys.MOD_COMMAND | PygletWindowKeys.MOD_CTRL)):
|
|
self.clear_selection()
|
|
self.add_to_selection(*self.mobjects)
|
|
elif char == "g" and (modifiers & (PygletWindowKeys.MOD_COMMAND | PygletWindowKeys.MOD_CTRL)):
|
|
self.group_selection()
|
|
elif char == "g" and (modifiers & (PygletWindowKeys.MOD_COMMAND | PygletWindowKeys.MOD_CTRL | PygletWindowKeys.MOD_SHIFT)):
|
|
self.ungroup_selection()
|
|
elif char == "t" and (modifiers & (PygletWindowKeys.MOD_COMMAND | PygletWindowKeys.MOD_CTRL)):
|
|
self.toggle_selection_mode()
|
|
elif char == "d" and (modifiers & PygletWindowKeys.MOD_SHIFT):
|
|
self.copy_frame_positioning()
|
|
elif char == "c" and (modifiers & PygletWindowKeys.MOD_SHIFT):
|
|
self.copy_cursor_position()
|
|
elif symbol in ARROW_SYMBOLS:
|
|
self.nudge_selection(
|
|
vect=[LEFT, UP, RIGHT, DOWN][ARROW_SYMBOLS.index(symbol)],
|
|
large=(modifiers & PygletWindowKeys.MOD_SHIFT),
|
|
)
|
|
# Adding crosshair
|
|
if char == CURSOR_KEY:
|
|
if self.crosshair in self.mobjects:
|
|
self.remove(self.crosshair)
|
|
else:
|
|
self.add(self.crosshair)
|
|
if char == SELECT_KEY:
|
|
self.add(self.crosshair)
|
|
|
|
# Conditions for saving state
|
|
if char in [GRAB_KEY, X_GRAB_KEY, Y_GRAB_KEY, RESIZE_KEY]:
|
|
self.save_state()
|
|
|
|
def on_key_release(self, symbol: int, modifiers: int) -> None:
|
|
super().on_key_release(symbol, modifiers)
|
|
if chr(symbol) == SELECT_KEY:
|
|
self.gather_new_selection()
|
|
if chr(symbol) in GRAB_KEYS:
|
|
self.is_grabbing = False
|
|
elif chr(symbol) == INFORMATION_KEY:
|
|
self.display_information(False)
|
|
elif symbol == SHIFT_SYMBOL and self.window.is_key_pressed(ord(RESIZE_KEY)):
|
|
self.prepare_resizing(about_corner=False)
|
|
|
|
# Mouse actions
|
|
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)
|
|
elif self.window.is_key_pressed(ord(X_GRAB_KEY)):
|
|
self.selection.set_x(diff[0])
|
|
elif self.window.is_key_pressed(ord(Y_GRAB_KEY)):
|
|
self.selection.set_y(diff[1])
|
|
|
|
def handle_resizing(self, point: Vect3):
|
|
if not hasattr(self, "scale_about_point"):
|
|
return
|
|
vect = point - self.scale_about_point
|
|
if self.window.is_key_pressed(CTRL_SYMBOL):
|
|
for i in (0, 1):
|
|
scalar = vect[i] / self.scale_ref_vect[i]
|
|
self.selection.rescale_to_fit(
|
|
scalar * [self.scale_ref_width, self.scale_ref_height][i],
|
|
dim=i,
|
|
about_point=self.scale_about_point,
|
|
stretch=True,
|
|
)
|
|
else:
|
|
scalar = get_norm(vect) / get_norm(self.scale_ref_vect)
|
|
self.selection.set_width(
|
|
scalar * self.scale_ref_width,
|
|
about_point=self.scale_about_point
|
|
)
|
|
|
|
def handle_sweeping_selection(self, point: Vect3):
|
|
mob = self.point_to_mobject(
|
|
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: Vect3):
|
|
# Search through all mobject on the screen, not just the palette
|
|
to_search = [
|
|
sm
|
|
for mobject in self.mobjects
|
|
for sm in mobject.family_members_with_points()
|
|
if mobject not in self.unselectables
|
|
]
|
|
mob = self.point_to_mobject(point, to_search)
|
|
if mob is not None:
|
|
self.selection.set_color(mob.get_color())
|
|
self.remove(self.color_palette)
|
|
|
|
def on_mouse_motion(self, point: Vect3, d_point: Vect3) -> None:
|
|
super().on_mouse_motion(point, d_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)):
|
|
self.handle_resizing(point)
|
|
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_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)
|
|
else:
|
|
self.clear_selection()
|
|
|
|
# Copying code to recreate state
|
|
def copy_frame_positioning(self):
|
|
frame = self.frame
|
|
center = frame.get_center()
|
|
height = frame.get_height()
|
|
angles = frame.get_euler_angles()
|
|
|
|
call = f"reorient("
|
|
theta, phi, gamma = (angles / DEGREES).astype(int)
|
|
call += f"{theta}, {phi}, {gamma}"
|
|
if any(center != 0):
|
|
call += f", {tuple(np.round(center, 2))}"
|
|
if height != FRAME_HEIGHT:
|
|
call += ", {:.2f}".format(height)
|
|
call += ")"
|
|
pyperclip.copy(call)
|
|
|
|
def copy_cursor_position(self):
|
|
pyperclip.copy(str(tuple(self.mouse_point.get_center().round(2))))
|