Skip to content

Commit 25f483c

Browse files
eulertourpre-commit-ci[bot]kilacoda-old
authored
Add :meth:~.Scene.interactive_embed for OpenGL rendering (#1285)
* Add Scene.embed_2 * Don't overwrite Scene methods * Checks * Remove extra test * Add docstring * Add rerun() command, allow calling Scene methods * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Disallow running methods with self * Remove saved methods Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: kilacoda <[email protected]>
1 parent c648260 commit 25f483c

File tree

4 files changed

+133
-31
lines changed

4 files changed

+133
-31
lines changed

manim/cli/render/commands.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,15 @@ def __repr__(self):
113113
for SceneClass in scene_classes_from_file(file):
114114
try:
115115
renderer = OpenGLRenderer()
116-
scene = SceneClass(renderer)
117-
scene.render()
116+
while True:
117+
scene_classes = scene_classes_from_file(file)
118+
SceneClass = scene_classes[0]
119+
scene = SceneClass(renderer)
120+
status = scene.render()
121+
if status:
122+
continue
123+
else:
124+
break
118125
except Exception:
119126
console.print_exception()
120127
elif config.renderer == "webgl":

manim/renderer/opengl_renderer.py

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -202,28 +202,23 @@ def init_scene(self, scene):
202202
scene.__class__.__name__,
203203
)
204204
self.scene = scene
205-
if config["preview"]:
206-
self.window = Window(self)
207-
self.context = self.window.ctx
208-
self.frame_buffer_object = self.context.detect_framebuffer()
209-
else:
210-
self.window = None
211-
self.context = moderngl.create_standalone_context()
212-
self.frame_buffer_object = self.get_frame_buffer_object(self.context, 0)
213-
self.frame_buffer_object.use()
214-
self.context.enable(moderngl.BLEND)
215-
self.context.blend_func = (
216-
moderngl.SRC_ALPHA,
217-
moderngl.ONE_MINUS_SRC_ALPHA,
218-
moderngl.ONE,
219-
moderngl.ONE,
220-
)
221-
222-
# Initialize shader map.
223-
self.id_to_shader_program = {}
224-
225-
# Initialize texture map.
226-
self.path_to_texture_id = {}
205+
if not hasattr(self, "window"):
206+
if config["preview"]:
207+
self.window = Window(self)
208+
self.context = self.window.ctx
209+
self.frame_buffer_object = self.context.detect_framebuffer()
210+
else:
211+
self.window = None
212+
self.context = moderngl.create_standalone_context()
213+
self.frame_buffer_object = self.get_frame_buffer_object(self.context, 0)
214+
self.frame_buffer_object.use()
215+
self.context.enable(moderngl.BLEND)
216+
self.context.blend_func = (
217+
moderngl.SRC_ALPHA,
218+
moderngl.ONE_MINUS_SRC_ALPHA,
219+
moderngl.ONE,
220+
moderngl.ONE,
221+
)
227222

228223
def update_depth_test(self, context, shader_wrapper):
229224
if shader_wrapper.depth_test:
@@ -381,6 +376,11 @@ def play(self, scene, *args, **kwargs):
381376
scene.begin_animations()
382377
scene.play_internal()
383378

379+
def clear_screen(self):
380+
window_background_color = color_to_rgba(config["background_color"])
381+
self.frame_buffer_object.clear(*window_background_color)
382+
self.window.swap_buffers()
383+
384384
def render(self, scene, frame_offset, moving_mobjects):
385385
def update_frame():
386386
self.frame_buffer_object.clear(*window_background_color)

manim/scene/scene.py

Lines changed: 98 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@
88
import inspect
99
import platform
1010
import random
11+
import string
12+
import threading
1113
import types
14+
import warnings
15+
from queue import Queue
1216

1317
import numpy as np
1418
from tqdm import tqdm
@@ -18,9 +22,10 @@
1822
from ..camera.camera import Camera
1923
from ..constants import *
2024
from ..container import Container
21-
from ..mobject.opengl_mobject import OpenGLPoint
25+
from ..mobject.mobject import Mobject, _AnimationBuilder
26+
from ..mobject.opengl_mobject import OpenGLMobject, OpenGLPoint
2227
from ..renderer.cairo_renderer import CairoRenderer
23-
from ..utils.exceptions import EndSceneEarlyException
28+
from ..utils.exceptions import EndSceneEarlyException, RerunSceneException
2429
from ..utils.family import extract_mobject_family_members
2530
from ..utils.family_ops import restructure_list_to_exclude_certain_family_members
2631
from ..utils.file_ops import open_media_file
@@ -79,6 +84,7 @@ def __init__(
7984
self.time_progression = None
8085
self.duration = None
8186
self.last_t = None
87+
self.queue = Queue()
8288

8389
if config.renderer == "opengl":
8490
# Items associated with interaction
@@ -179,6 +185,11 @@ def render(self, preview=False):
179185
self.construct()
180186
except EndSceneEarlyException:
181187
pass
188+
except RerunSceneException as e:
189+
self.remove(*self.mobjects)
190+
self.renderer.clear_screen()
191+
self.renderer.num_plays = 0
192+
return True
182193
self.tear_down()
183194
# We have to reset these settings in case of multiple renders.
184195
self.renderer.scene_finished(self)
@@ -915,13 +926,93 @@ def play_internal(self, skip_rendering=False):
915926
# Closing the progress bar at the end of the play.
916927
self.time_progression.close()
917928

918-
def interact(self):
929+
def interactive_embed(self):
930+
"""
931+
Like embed(), but allows for screen interaction.
932+
"""
933+
934+
def ipython(shell, namespace):
935+
import manim
936+
import manim.opengl
937+
938+
def load_module_into_namespace(module, namespace):
939+
for name in dir(module):
940+
namespace[name] = getattr(module, name)
941+
942+
load_module_into_namespace(manim, namespace)
943+
load_module_into_namespace(manim.opengl, namespace)
944+
945+
def embedded_rerun(*args, **kwargs):
946+
self.queue.put(("rerun_keyboard", args, kwargs))
947+
shell.exiter()
948+
949+
namespace["rerun"] = embedded_rerun
950+
951+
shell(local_ns=namespace)
952+
self.queue.put(("exit", [], {}))
953+
954+
def get_embedded_method(method_name):
955+
return lambda *args, **kwargs: self.queue.put((method_name, args, kwargs))
956+
957+
local_namespace = inspect.currentframe().f_back.f_locals
958+
for method in ("play", "wait", "add", "remove"):
959+
embedded_method = get_embedded_method(method)
960+
# Allow for calling scene methods without prepending 'self.'.
961+
local_namespace[method] = embedded_method
962+
963+
from IPython.terminal.embed import InteractiveShellEmbed
964+
from traitlets.config import Config
965+
966+
cfg = Config()
967+
cfg.TerminalInteractiveShell.confirm_exit = False
968+
shell = InteractiveShellEmbed(config=cfg)
969+
970+
keyboard_thread = threading.Thread(
971+
target=ipython,
972+
args=(shell, local_namespace),
973+
)
974+
keyboard_thread.start()
975+
self.interact(shell, keyboard_thread)
976+
977+
def interact(self, shell=None, keyboard_thread=None):
919978
self.quit_interaction = False
979+
keyboard_thread_needs_join = True
920980
while not (self.renderer.window.is_closing or self.quit_interaction):
921-
self.renderer.animation_start_time = 0
922-
dt = 1 / config["frame_rate"]
923-
self.renderer.render(self, dt, self.moving_mobjects)
924-
self.update_mobjects(dt)
981+
if not self.queue.empty():
982+
tup = self.queue.get_nowait()
983+
if tup[0].startswith("rerun"):
984+
kwargs = tup[2]
985+
if "from_animation_number" in kwargs:
986+
config["from_animation_number"] = kwargs[
987+
"from_animation_number"
988+
]
989+
# # TODO: This option only makes sense if interactive_embed() is run at the
990+
# # end of a scene by default.
991+
# if "upto_animation_number" in kwargs:
992+
# config["upto_animation_number"] = kwargs[
993+
# "upto_animation_number"
994+
# ]
995+
996+
keyboard_thread.join()
997+
raise RerunSceneException
998+
elif tup[0].startswith("exit"):
999+
keyboard_thread.join()
1000+
keyboard_thread_needs_join = False
1001+
break
1002+
else:
1003+
method, args, kwargs = tup
1004+
getattr(self, method)(*args, **kwargs)
1005+
else:
1006+
self.renderer.animation_start_time = 0
1007+
dt = 1 / config["frame_rate"]
1008+
self.renderer.render(self, dt, self.moving_mobjects)
1009+
self.update_mobjects(dt)
1010+
1011+
# Join the keyboard thread if necessary.
1012+
if shell is not None and keyboard_thread_needs_join:
1013+
shell.pt_app.app.exit(exception=EOFError)
1014+
keyboard_thread.join()
1015+
9251016
if self.renderer.window.is_closing:
9261017
self.renderer.window.destroy()
9271018

manim/utils/exceptions.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,6 @@
11
class EndSceneEarlyException(Exception):
22
pass
3+
4+
5+
class RerunSceneException(Exception):
6+
pass

0 commit comments

Comments
 (0)