mirror of
https://github.com/3b1b/manim.git
synced 2025-08-20 05:14:12 +00:00
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:
parent
c03336dc8b
commit
b6f5593b30
2 changed files with 108 additions and 110 deletions
|
@ -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:
|
||||||
|
|
|
@ -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,131 +15,127 @@ 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):
|
||||||
Create embedded IPython terminal configured to have access to
|
self.scene = scene
|
||||||
the local namespace of the caller
|
self.shell = self.get_ipython_shell_for_embedded_scene()
|
||||||
"""
|
|
||||||
# Triple back should take us to the context in a user's scene definition
|
|
||||||
# which is calling "self.embed"
|
|
||||||
caller_frame = inspect.currentframe().f_back.f_back.f_back
|
|
||||||
|
|
||||||
# Update the module's namespace to include local variables
|
self.enable_gui()
|
||||||
module = ModuleLoader.get_module(caller_frame.f_globals["__file__"])
|
self.ensure_frame_update_post_cell()
|
||||||
module.__dict__.update(caller_frame.f_locals)
|
self.ensure_flash_on_error()
|
||||||
module.__dict__.update(get_shortcuts(scene))
|
|
||||||
exception_mode = manim_config.embed.exception_mode
|
|
||||||
|
|
||||||
return InteractiveShellEmbed(
|
def launch(self):
|
||||||
user_module=module,
|
self.shell()
|
||||||
display_banner=False,
|
|
||||||
xmode=exception_mode
|
|
||||||
)
|
|
||||||
|
|
||||||
|
def get_ipython_shell_for_embedded_scene(self) -> InteractiveShellEmbed:
|
||||||
|
"""
|
||||||
|
Create embedded IPython terminal configured to have access to
|
||||||
|
the local namespace of the caller
|
||||||
|
"""
|
||||||
|
# Triple back should take us to the context in a user's scene definition
|
||||||
|
# which is calling "self.embed"
|
||||||
|
caller_frame = inspect.currentframe().f_back.f_back.f_back
|
||||||
|
|
||||||
def get_shortcuts(scene):
|
# Update the module's namespace to include local variables
|
||||||
"""
|
module = ModuleLoader.get_module(caller_frame.f_globals["__file__"])
|
||||||
A few custom shortcuts useful to have in the interactive shell namespace
|
module.__dict__.update(caller_frame.f_locals)
|
||||||
"""
|
module.__dict__.update(self.get_shortcuts())
|
||||||
return dict(
|
exception_mode = manim_config.embed.exception_mode
|
||||||
play=scene.play,
|
|
||||||
wait=scene.wait,
|
|
||||||
add=scene.add,
|
|
||||||
remove=scene.remove,
|
|
||||||
clear=scene.clear,
|
|
||||||
focus=scene.focus,
|
|
||||||
save_state=scene.save_state,
|
|
||||||
undo=scene.undo,
|
|
||||||
redo=scene.redo,
|
|
||||||
i2g=scene.i2g,
|
|
||||||
i2m=scene.i2m,
|
|
||||||
checkpoint_paste=scene.checkpoint_paste,
|
|
||||||
reload=reload_scene # Defined below
|
|
||||||
)
|
|
||||||
|
|
||||||
|
return InteractiveShellEmbed(
|
||||||
|
user_module=module,
|
||||||
|
display_banner=False,
|
||||||
|
xmode=exception_mode
|
||||||
|
)
|
||||||
|
|
||||||
def enable_gui(shell, scene):
|
def get_shortcuts(self):
|
||||||
"""Enables gui interactions during the embed"""
|
"""
|
||||||
def inputhook(context):
|
A few custom shortcuts useful to have in the interactive shell namespace
|
||||||
while not context.input_is_ready():
|
"""
|
||||||
if not scene.is_window_closing():
|
scene = self.scene
|
||||||
scene.update_frame(dt=0)
|
return dict(
|
||||||
if scene.is_window_closing():
|
play=scene.play,
|
||||||
shell.ask_exit()
|
wait=scene.wait,
|
||||||
|
add=scene.add,
|
||||||
|
remove=scene.remove,
|
||||||
|
clear=scene.clear,
|
||||||
|
focus=scene.focus,
|
||||||
|
save_state=scene.save_state,
|
||||||
|
undo=scene.undo,
|
||||||
|
redo=scene.redo,
|
||||||
|
i2g=scene.i2g,
|
||||||
|
i2m=scene.i2m,
|
||||||
|
checkpoint_paste=scene.checkpoint_paste,
|
||||||
|
reload=self.reload_scene # Defined below
|
||||||
|
)
|
||||||
|
|
||||||
pt_inputhooks.register("manim", inputhook)
|
def enable_gui(self):
|
||||||
shell.enable_gui("manim")
|
"""Enables gui interactions during the embed"""
|
||||||
|
def inputhook(context):
|
||||||
|
while not context.input_is_ready():
|
||||||
|
if not self.scene.is_window_closing():
|
||||||
|
self.scene.update_frame(dt=0)
|
||||||
|
if self.scene.is_window_closing():
|
||||||
|
self.shell.ask_exit()
|
||||||
|
|
||||||
|
pt_inputhooks.register("manim", inputhook)
|
||||||
|
self.shell.enable_gui("manim")
|
||||||
|
|
||||||
def ensure_frame_update_post_cell(shell, scene):
|
def ensure_frame_update_post_cell(self):
|
||||||
"""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):
|
||||||
|
"""Flash border, and potentially play sound, on exceptions"""
|
||||||
|
def custom_exc(shell, etype, evalue, tb, tb_offset=None):
|
||||||
|
# Show the error don't just swallow it
|
||||||
|
print(''.join(traceback.format_exception(etype, evalue, tb)))
|
||||||
|
rect = FullScreenRectangle().set_stroke(RED, 30).set_fill(opacity=0)
|
||||||
|
rect.fix_in_frame()
|
||||||
|
self.scene.play(VFadeInThenOut(rect, run_time=0.5))
|
||||||
|
|
||||||
def ensure_flash_on_error(shell, scene):
|
self.shell.set_custom_exc((Exception,), custom_exc)
|
||||||
"""Flash border, and potentially play sound, on exceptions"""
|
|
||||||
def custom_exc(shell, etype, evalue, tb, tb_offset=None):
|
|
||||||
# Show the error don't just swallow it
|
|
||||||
print(''.join(traceback.format_exception(etype, evalue, tb)))
|
|
||||||
rect = FullScreenRectangle().set_stroke(RED, 30).set_fill(opacity=0)
|
|
||||||
rect.fix_in_frame()
|
|
||||||
scene.play(VFadeInThenOut(rect, run_time=0.5))
|
|
||||||
|
|
||||||
shell.set_custom_exc((Exception,), custom_exc)
|
def reload_scene(self, embed_line: int | None = None) -> None:
|
||||||
|
"""
|
||||||
|
Reloads the scene just like the `manimgl` command would do with the
|
||||||
|
same arguments that were provided for the initial startup. This allows
|
||||||
|
for quick iteration during scene development since we don't have to exit
|
||||||
|
the IPython kernel and re-run the `manimgl` command again. The GUI stays
|
||||||
|
open during the reload.
|
||||||
|
|
||||||
|
If `embed_line` is provided, the scene will be reloaded at that line
|
||||||
|
number. This corresponds to the `linemarker` param of the
|
||||||
|
`extract_scene.insert_embed_line_to_module()` method.
|
||||||
|
|
||||||
def reload_scene(embed_line: int | None = None) -> None:
|
Before reload, the scene is cleared and the entire state is reset, such
|
||||||
"""
|
that we can start from a clean slate. This is taken care of by the
|
||||||
Reloads the scene just like the `manimgl` command would do with the
|
run_scenes function in __main__.py, which will catch the error raised by the
|
||||||
same arguments that were provided for the initial startup. This allows
|
`exit_raise` magic command that we invoke here.
|
||||||
for quick iteration during scene development since we don't have to exit
|
|
||||||
the IPython kernel and re-run the `manimgl` command again. The GUI stays
|
|
||||||
open during the reload.
|
|
||||||
|
|
||||||
If `embed_line` is provided, the scene will be reloaded at that line
|
Note that we cannot define a custom exception class for this error,
|
||||||
number. This corresponds to the `linemarker` param of the
|
since the IPython kernel will swallow any exception. While we can catch
|
||||||
`extract_scene.insert_embed_line_to_module()` method.
|
such an exception in our custom exception handler registered with the
|
||||||
|
`set_custom_exc` method, we cannot break out of the IPython shell by
|
||||||
|
this means.
|
||||||
|
"""
|
||||||
|
# Update the global run configuration.
|
||||||
|
run_config = manim_config.run
|
||||||
|
run_config.is_reload = True
|
||||||
|
if embed_line:
|
||||||
|
run_config.embed_line = embed_line
|
||||||
|
|
||||||
Before reload, the scene is cleared and the entire state is reset, such
|
print("Reloading...")
|
||||||
that we can start from a clean slate. This is taken care of by the
|
self.shell.run_line_magic("exit_raise", "")
|
||||||
run_scenes function in __main__.py, which will catch the error raised by the
|
|
||||||
`exit_raise` magic command that we invoke here.
|
|
||||||
|
|
||||||
Note that we cannot define a custom exception class for this error,
|
|
||||||
since the IPython kernel will swallow any exception. While we can catch
|
|
||||||
such an exception in our custom exception handler registered with the
|
|
||||||
`set_custom_exc` method, we cannot break out of the IPython shell by
|
|
||||||
this means.
|
|
||||||
"""
|
|
||||||
shell = get_ipython()
|
|
||||||
if not shell:
|
|
||||||
return
|
|
||||||
|
|
||||||
# Update the global run configuration.
|
|
||||||
run_config = manim_config.run
|
|
||||||
run_config.is_reload = True
|
|
||||||
if embed_line:
|
|
||||||
run_config.embed_line = embed_line
|
|
||||||
|
|
||||||
print("Reloading...")
|
|
||||||
shell.run_line_magic("exit_raise", "")
|
|
||||||
|
|
||||||
|
|
||||||
class CheckpointManager:
|
class CheckpointManager:
|
||||||
|
|
Loading…
Add table
Reference in a new issue