Make InteracrtiveSceneEmbed into a class

This way it can keep track of it's internal shell; use of get_ipython has a finicky relationship with reloading.
This commit is contained in:
Grant Sanderson 2024-12-11 12:40:56 -06:00
parent c03336dc8b
commit b6f5593b30
2 changed files with 108 additions and 110 deletions

View file

@ -24,7 +24,7 @@ from manimlib.mobject.mobject import Mobject
from manimlib.mobject.mobject import Point from manimlib.mobject.mobject import Point
from manimlib.mobject.types.vectorized_mobject import VGroup from manimlib.mobject.types.vectorized_mobject import VGroup
from manimlib.mobject.types.vectorized_mobject import VMobject from manimlib.mobject.types.vectorized_mobject import VMobject
from manimlib.scene.scene_embed import interactive_scene_embed from manimlib.scene.scene_embed import InteractiveSceneEmbed
from manimlib.scene.scene_embed import CheckpointManager from manimlib.scene.scene_embed import CheckpointManager
from manimlib.scene.scene_file_writer import SceneFileWriter from manimlib.scene.scene_file_writer import SceneFileWriter
from manimlib.utils.dict_ops import merge_dicts_recursively from manimlib.utils.dict_ops import merge_dicts_recursively
@ -212,8 +212,10 @@ class Scene(object):
# Embed is only relevant for interactive development with a Window # Embed is only relevant for interactive development with a Window
return return
self.show_animation_progress = show_animation_progress self.show_animation_progress = show_animation_progress
self.stop_skipping()
self.update_frame(force_draw=True)
interactive_scene_embed(self) InteractiveSceneEmbed(self).launch()
# End scene when exiting an embed # End scene when exiting an embed
if close_scene_on_exit: if close_scene_on_exit:

View file

@ -1,9 +1,9 @@
from __future__ import annotations
import inspect import inspect
import pyperclip import pyperclip
import traceback import traceback
from IPython.core.getipython import get_ipython
from IPython.terminal import pt_inputhooks from IPython.terminal import pt_inputhooks
from IPython.terminal.embed import InteractiveShellEmbed from IPython.terminal.embed import InteractiveShellEmbed
@ -15,20 +15,24 @@ from manimlib.mobject.frame import FullScreenRectangle
from manimlib.module_loader import ModuleLoader from manimlib.module_loader import ModuleLoader
def interactive_scene_embed(scene): from typing import TYPE_CHECKING
scene.stop_skipping() if TYPE_CHECKING:
scene.update_frame(force_draw=True) from manimlib.scene.scene import Scene
shell = get_ipython_shell_for_embedded_scene(scene)
enable_gui(shell, scene)
ensure_frame_update_post_cell(shell, scene)
ensure_flash_on_error(shell, scene)
# Launch shell
shell()
def get_ipython_shell_for_embedded_scene(scene): class InteractiveSceneEmbed:
def __init__(self, scene: Scene):
self.scene = scene
self.shell = self.get_ipython_shell_for_embedded_scene()
self.enable_gui()
self.ensure_frame_update_post_cell()
self.ensure_flash_on_error()
def launch(self):
self.shell()
def get_ipython_shell_for_embedded_scene(self) -> InteractiveShellEmbed:
""" """
Create embedded IPython terminal configured to have access to Create embedded IPython terminal configured to have access to
the local namespace of the caller the local namespace of the caller
@ -40,7 +44,7 @@ def get_ipython_shell_for_embedded_scene(scene):
# Update the module's namespace to include local variables # Update the module's namespace to include local variables
module = ModuleLoader.get_module(caller_frame.f_globals["__file__"]) module = ModuleLoader.get_module(caller_frame.f_globals["__file__"])
module.__dict__.update(caller_frame.f_locals) module.__dict__.update(caller_frame.f_locals)
module.__dict__.update(get_shortcuts(scene)) module.__dict__.update(self.get_shortcuts())
exception_mode = manim_config.embed.exception_mode exception_mode = manim_config.embed.exception_mode
return InteractiveShellEmbed( return InteractiveShellEmbed(
@ -49,11 +53,11 @@ def get_ipython_shell_for_embedded_scene(scene):
xmode=exception_mode xmode=exception_mode
) )
def get_shortcuts(self):
def get_shortcuts(scene):
""" """
A few custom shortcuts useful to have in the interactive shell namespace A few custom shortcuts useful to have in the interactive shell namespace
""" """
scene = self.scene
return dict( return dict(
play=scene.play, play=scene.play,
wait=scene.wait, wait=scene.wait,
@ -67,45 +71,41 @@ def get_shortcuts(scene):
i2g=scene.i2g, i2g=scene.i2g,
i2m=scene.i2m, i2m=scene.i2m,
checkpoint_paste=scene.checkpoint_paste, checkpoint_paste=scene.checkpoint_paste,
reload=reload_scene # Defined below reload=self.reload_scene # Defined below
) )
def enable_gui(self):
def enable_gui(shell, scene):
"""Enables gui interactions during the embed""" """Enables gui interactions during the embed"""
def inputhook(context): def inputhook(context):
while not context.input_is_ready(): while not context.input_is_ready():
if not scene.is_window_closing(): if not self.scene.is_window_closing():
scene.update_frame(dt=0) self.scene.update_frame(dt=0)
if scene.is_window_closing(): if self.scene.is_window_closing():
shell.ask_exit() self.shell.ask_exit()
pt_inputhooks.register("manim", inputhook) pt_inputhooks.register("manim", inputhook)
shell.enable_gui("manim") self.shell.enable_gui("manim")
def ensure_frame_update_post_cell(self):
def ensure_frame_update_post_cell(shell, scene):
"""Ensure the scene updates its frame after each ipython cell""" """Ensure the scene updates its frame after each ipython cell"""
def post_cell_func(*args, **kwargs): def post_cell_func(*args, **kwargs):
if not scene.is_window_closing(): if not self.scene.is_window_closing():
scene.update_frame(dt=0, force_draw=True) self.scene.update_frame(dt=0, force_draw=True)
shell.events.register("post_run_cell", post_cell_func) self.shell.events.register("post_run_cell", post_cell_func)
def ensure_flash_on_error(self):
def ensure_flash_on_error(shell, scene):
"""Flash border, and potentially play sound, on exceptions""" """Flash border, and potentially play sound, on exceptions"""
def custom_exc(shell, etype, evalue, tb, tb_offset=None): def custom_exc(shell, etype, evalue, tb, tb_offset=None):
# Show the error don't just swallow it # Show the error don't just swallow it
print(''.join(traceback.format_exception(etype, evalue, tb))) print(''.join(traceback.format_exception(etype, evalue, tb)))
rect = FullScreenRectangle().set_stroke(RED, 30).set_fill(opacity=0) rect = FullScreenRectangle().set_stroke(RED, 30).set_fill(opacity=0)
rect.fix_in_frame() rect.fix_in_frame()
scene.play(VFadeInThenOut(rect, run_time=0.5)) self.scene.play(VFadeInThenOut(rect, run_time=0.5))
shell.set_custom_exc((Exception,), custom_exc) self.shell.set_custom_exc((Exception,), custom_exc)
def reload_scene(self, embed_line: int | None = None) -> None:
def reload_scene(embed_line: int | None = None) -> None:
""" """
Reloads the scene just like the `manimgl` command would do with the Reloads the scene just like the `manimgl` command would do with the
same arguments that were provided for the initial startup. This allows same arguments that were provided for the initial startup. This allows
@ -128,10 +128,6 @@ def reload_scene(embed_line: int | None = None) -> None:
`set_custom_exc` method, we cannot break out of the IPython shell by `set_custom_exc` method, we cannot break out of the IPython shell by
this means. this means.
""" """
shell = get_ipython()
if not shell:
return
# Update the global run configuration. # Update the global run configuration.
run_config = manim_config.run run_config = manim_config.run
run_config.is_reload = True run_config.is_reload = True
@ -139,7 +135,7 @@ def reload_scene(embed_line: int | None = None) -> None:
run_config.embed_line = embed_line run_config.embed_line = embed_line
print("Reloading...") print("Reloading...")
shell.run_line_magic("exit_raise", "") self.shell.run_line_magic("exit_raise", "")
class CheckpointManager: class CheckpointManager: