Skip to content

Move configure_pygui into a Scene method and remove manim.gui #4314

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file removed manim/gui/__init__.py
Empty file.
93 changes: 0 additions & 93 deletions manim/gui/gui.py

This file was deleted.

80 changes: 75 additions & 5 deletions manim/scene/scene.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import random
import threading
import time
from pathlib import Path
from queue import Queue

import srt
Expand All @@ -25,6 +26,8 @@
import dearpygui.dearpygui as dpg
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about moving the dearpygui import down and into the scene __init__ to get rid of the weird dearpygui_imported variable?

Copy link
Contributor Author

@chopan050 chopan050 Jul 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although that variable is weird, I believe that this import is better placed where it is right now. The dpg module is used in .interactive_embed(), .interact() and the new ._configure_pygui(). If we imported it inside __init__, it would probably have to be stored as a Scene attribute.

Copy link
Contributor Author

@chopan050 chopan050 Jul 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I remembered a related discussion on Discord where I mentioned that it would probably be good to have a reference to dpg from Scene:

It would be nice to have an enum of all possible widgets

After giving it some thoughts, I believe that it's not intuitive to append a dictionary to Scene.widgets if you wanna add a widget and it's a bit prone to errors

There could be a dedicated method Scene.add_gui_widget(), but you still get no type hints for the kwargs
One could make one Scene method for each of the functions of Dear PyGui which adds a widget... but, in that case, it's much more straightforward to simply get a reference to Dear PyGui and directly use its functions instead

In my opinion, this requires a larger refactor which should be done in a subsequent PR...


dearpygui_imported = True
dpg.create_context()
window = dpg.generate_uuid()
except ImportError:
dearpygui_imported = False
from typing import TYPE_CHECKING
Expand All @@ -34,14 +37,14 @@
from watchdog.events import DirModifiedEvent, FileModifiedEvent, FileSystemEventHandler
from watchdog.observers import Observer

from manim import __version__
from manim.mobject.mobject import Mobject
from manim.mobject.opengl.opengl_mobject import OpenGLPoint

from .. import config, logger
from ..animation.animation import Animation, Wait, prepare_animation
from ..camera.camera import Camera
from ..constants import *
from ..gui.gui import configure_pygui
from ..renderer.cairo_renderer import CairoRenderer
from ..renderer.opengl_renderer import OpenGLCamera, OpenGLMobject, OpenGLRenderer
from ..renderer.shader import Object3D
Expand All @@ -51,6 +54,7 @@
from ..utils.family_ops import restructure_list_to_exclude_certain_family_members
from ..utils.file_ops import open_media_file
from ..utils.iterables import list_difference_update, list_update
from ..utils.module_ops import scene_classes_from_file

if TYPE_CHECKING:
from collections.abc import Iterable, Sequence
Expand Down Expand Up @@ -144,7 +148,7 @@ def __init__(
self.skip_animation_preview = False
self.meshes: list[Object3D] = []
self.camera_target = ORIGIN
self.widgets: list[Any] = []
self.widgets: list[dict[str, Any]] = []
self.dearpygui_imported = dearpygui_imported
self.updaters: list[Callable[[float], None]] = []
self.key_to_function_map: dict[str, Callable[[], None]] = {}
Expand Down Expand Up @@ -1406,13 +1410,12 @@ def embedded_method(*args: Any, **kwargs: Any) -> None:
if self.dearpygui_imported and config["enable_gui"]:
if not dpg.is_dearpygui_running():
gui_thread = threading.Thread(
target=configure_pygui,
args=(self.renderer, self.widgets),
target=self._configure_pygui,
kwargs={"update": False},
)
gui_thread.start()
else:
configure_pygui(self.renderer, self.widgets, update=True)
self._configure_pygui(update=True)

self.camera.model_matrix = self.camera.default_model_matrix

Expand Down Expand Up @@ -1543,6 +1546,73 @@ def embed(self) -> None:
# End scene when exiting an embed.
raise Exception("Exiting scene.")

def _configure_pygui(self, update: bool = True) -> None:
if not self.dearpygui_imported:
raise RuntimeError("Attempted to use DearPyGUI when it isn't imported.")
if update:
dpg.delete_item(window)
else:
dpg.create_viewport()
dpg.setup_dearpygui()
dpg.show_viewport()

dpg.set_viewport_title(title=f"Manim Community v{__version__}")
dpg.set_viewport_width(1015)
dpg.set_viewport_height(540)

def rerun_callback(sender: Any, data: Any) -> None:
self.queue.put(("rerun_gui", [], {}))

def continue_callback(sender: Any, data: Any) -> None:
self.queue.put(("exit_gui", [], {}))

def scene_selection_callback(sender: Any, data: Any) -> None:
config["scene_names"] = (dpg.get_value(sender),)
self.queue.put(("rerun_gui", [], {}))

scene_classes = scene_classes_from_file(
Path(config["input_file"]), full_list=True
) # type: ignore[call-overload]
scene_names = [scene_class.__name__ for scene_class in scene_classes]

with dpg.window(
id=window,
label="Manim GUI",
pos=[config["gui_location"][0], config["gui_location"][1]],
width=1000,
height=500,
):
dpg.set_global_font_scale(2)
dpg.add_button(label="Rerun", callback=rerun_callback)
dpg.add_button(label="Continue", callback=continue_callback)
dpg.add_combo(
label="Selected scene",
items=scene_names,
callback=scene_selection_callback,
default_value=config["scene_names"][0],
)
dpg.add_separator()
if len(self.widgets) != 0:
with dpg.collapsing_header(
label=f"{config['scene_names'][0]} widgets",
default_open=True,
):
for widget_config in self.widgets:
widget_config_copy = widget_config.copy()
name = widget_config_copy["name"]
widget = widget_config_copy["widget"]
if widget != "separator":
del widget_config_copy["name"]
del widget_config_copy["widget"]
getattr(dpg, f"add_{widget}")(
label=name, **widget_config_copy
)
else:
dpg.add_separator()

if not update:
dpg.start_dearpygui()

def update_to_time(self, t: float) -> None:
dt = t - self.last_t
self.last_t = t
Expand Down
3 changes: 0 additions & 3 deletions mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,6 @@ ignore_errors = True
[mypy-manim.camera.three_d_camera]
ignore_errors = True

[mypy-manim.gui.gui]
ignore_errors = True

[mypy-manim.mobject.graphing.coordinate_systems]
ignore_errors = True

Expand Down