|
8 | 8 | import inspect
|
9 | 9 | import platform
|
10 | 10 | import random
|
| 11 | +import string |
| 12 | +import threading |
11 | 13 | import types
|
| 14 | +import warnings |
| 15 | +from queue import Queue |
12 | 16 |
|
13 | 17 | import numpy as np
|
14 | 18 | from tqdm import tqdm
|
|
18 | 22 | from ..camera.camera import Camera
|
19 | 23 | from ..constants import *
|
20 | 24 | 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 |
22 | 27 | from ..renderer.cairo_renderer import CairoRenderer
|
23 |
| -from ..utils.exceptions import EndSceneEarlyException |
| 28 | +from ..utils.exceptions import EndSceneEarlyException, RerunSceneException |
24 | 29 | from ..utils.family import extract_mobject_family_members
|
25 | 30 | from ..utils.family_ops import restructure_list_to_exclude_certain_family_members
|
26 | 31 | from ..utils.file_ops import open_media_file
|
@@ -79,6 +84,7 @@ def __init__(
|
79 | 84 | self.time_progression = None
|
80 | 85 | self.duration = None
|
81 | 86 | self.last_t = None
|
| 87 | + self.queue = Queue() |
82 | 88 |
|
83 | 89 | if config.renderer == "opengl":
|
84 | 90 | # Items associated with interaction
|
@@ -179,6 +185,11 @@ def render(self, preview=False):
|
179 | 185 | self.construct()
|
180 | 186 | except EndSceneEarlyException:
|
181 | 187 | 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 |
182 | 193 | self.tear_down()
|
183 | 194 | # We have to reset these settings in case of multiple renders.
|
184 | 195 | self.renderer.scene_finished(self)
|
@@ -915,13 +926,93 @@ def play_internal(self, skip_rendering=False):
|
915 | 926 | # Closing the progress bar at the end of the play.
|
916 | 927 | self.time_progression.close()
|
917 | 928 |
|
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): |
919 | 978 | self.quit_interaction = False
|
| 979 | + keyboard_thread_needs_join = True |
920 | 980 | 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 | + |
925 | 1016 | if self.renderer.window.is_closing:
|
926 | 1017 | self.renderer.window.destroy()
|
927 | 1018 |
|
|
0 commit comments