Merge branch 'experimental' of github.com:ManimCommunity/manim into experimental

This commit is contained in:
Tristan Schulz 2024-06-21 19:05:15 +02:00
commit e550c5586c
12 changed files with 400 additions and 559 deletions

View file

@ -22,18 +22,6 @@ from manim.mobject.text.numbers import DecimalNumber
from manim.mobject.text.text_mobject import Text
from manim.renderer.opengl_renderer import OpenGLRenderer
def progress_through_animations(animations):
dt = t - last_t
last_t = t
for animation in animations:
animation.update_mobjects(dt)
alpha = t / animation.run_time
animation.interpolate(alpha)
self.update_frame(dt)
self.emit_frame()
if __name__ == "__main__":
with tempconfig({"renderer": "opengl"}):
win = Window(

View file

@ -10,4 +10,4 @@ class Test(Scene):
with tempconfig({"renderer": "opengl", "preview": True, "parallel": False}):
Test().render()
Manager(Test).render()

View file

@ -72,6 +72,7 @@ from .mobject.types.point_cloud_mobject import *
from .mobject.types.vectorized_mobject import *
from .mobject.value_tracker import *
from .mobject.vector_field import *
from .renderer.render_manager import *
from .scene.scene import *
from .scene.scene_file_writer import *
from .scene.section import *

View file

@ -100,7 +100,7 @@ render_options = option_group(
case_sensitive=False,
),
help="Select a renderer for your Scene.",
default="cairo",
default="opengl",
),
option(
"-g",

View file

@ -8,7 +8,6 @@ import numpy as np
from manim.utils.hashing import get_hash_from_play_call
from .. import config, logger
from ..camera.cairo_camera import CairoCamera as Camera
from ..mobject.mobject import Mobject
from ..scene.scene_file_writer import SceneFileWriter
from ..utils.exceptions import EndSceneEarlyException

View file

@ -11,7 +11,7 @@ from manim.camera.camera import Camera
from manim.mobject.opengl.opengl_vectorized_mobject import OpenGLVMobject
from manim.renderer.buffers.buffer import STD140BufferFormat
from manim.renderer.opengl_shader_program import load_shader_program_by_folder
from manim.renderer.renderer import ImageType, Renderer, RendererData
from manim.renderer.renderer import ImageType, Renderer, RendererData, RendererProtocol
from manim.utils.iterables import listify
from manim.utils.space_ops import cross2d, earclip_triangulation, z_to_vector
@ -203,14 +203,14 @@ class ProgramManager:
uniform_buffer_object.bind_to_uniform_block(idx)
class OpenGLRenderer(Renderer):
class OpenGLRenderer(Renderer, RendererProtocol):
pixel_array_dtype = np.uint8
def __init__(
self,
pixel_width: int = config.pixel_width,
pixel_height: int = config.pixel_height,
samples=4,
samples: int = 4,
background_color: c.ManimColor = color.BLACK,
background_opacity: float = 1.0,
background_image: str | None = None,
@ -407,6 +407,12 @@ class OpenGLRenderer(Renderer):
vao.release()
# return data, data_size
def render_image(self, mob):
raise NotImplementedError # TODO
def render_previous(self, camera: Camera) -> None:
raise NotImplementedError
def render_vmobject(self, mob: OpenGLVMobject) -> None: # type: ignore
self.stencil_buffer_fbo.use()
self.stencil_buffer_fbo.clear()

View file

@ -10,9 +10,6 @@ from screeninfo import get_monitors
from .. import __version__, config
if TYPE_CHECKING:
import manim.scene as m_scene
class Window(FunWindow):
fullscreen: bool = False

View file

@ -85,9 +85,8 @@ def load_shader_program_by_folder(ctx: gl.Context, folder_name: str):
raise RuntimeError("Loading Shader Program Error")
if geometry_code is None:
return ctx.program(vertex_shader=vertex_code, fragment_shader=fragment_code)
elif geometry_code is not None:
return ctx.program(
vertex_shader=vertex_code,
geometry_shader=geometry_code,
fragment_shader=fragment_code,
)
return ctx.program(
vertex_shader=vertex_code,
geometry_shader=geometry_code,
fragment_shader=fragment_code,
)

View file

@ -1,82 +1,247 @@
from __future__ import annotations
import multiprocessing as mp
import queue # NOTE: Cannot use mp.Queue because of auth keys
from typing import TYPE_CHECKING, Any, Iterable
import time
from typing import TYPE_CHECKING, Any, Callable, Iterable
import numpy as np
from manim import config, logger
from manim.constants import RendererType
from manim.renderer.cairo_renderer import CairoRenderer
from manim.utils.exceptions import EndSceneEarlyException
from ..scene.scene import Scene, SceneState
from .opengl_file_writer import FileWriter
from .opengl_renderer import OpenGLRenderer
from .opengl_renderer_window import Window
if TYPE_CHECKING:
from manim.animation.protocol import AnimationProtocol
from ..camera.camera import Camera
from ..scene.scene import SceneState
from .renderer import RendererProtocol
__all__ = ("RenderManager",)
__all__ = ("Manager",)
class RenderManager:
class Manager:
"""
Manage rendering in parallel
The Brain of Manim
.. note::
The only method of this class officially guaranteed to be
stable is :meth:`~.Manager.render`. Any other methods documented
are purely for development
Usage
-----
.. code-block:: python
class Manimation(Scene):
def construct(self):
self.play(FadeIn(Circle()))
Manager(Manimation).render()
"""
def __init__(self, scene_name: str, camera: Camera, **kwargs) -> None:
# renderer
self.renderer = OpenGLRenderer(**kwargs)
self.ctx = mp.get_context("spawn")
def __init__(self, scene_cls: type[Scene]) -> None:
# scene
self.scene: Scene = scene_cls(self)
# setup
self.processes: queue.Queue[mp.Process] = queue.Queue()
self.manager = mp.Manager()
self.manager_dict = self.manager.dict()
if not isinstance(self.scene, Scene):
raise ValueError(f"{self.scene!r} is not an instance of Scene")
# file writer
self.camera = camera
self.file_writer = FileWriter(scene_name) # TODO
self.time = 0
# Initialize window, if applicable
if config.preview:
self.window = Window()
else:
self.window = None
# this must be done AFTER instantiating a window
self.renderer = self.create_renderer()
self.renderer.use_window()
def begin(self) -> None:
# file writer
self.file_writer = FileWriter(self.scene.get_default_scene_name()) # TODO
@property
def camera(self) -> Camera:
return self.scene.camera
def create_renderer(self) -> RendererProtocol:
match config.renderer:
case RendererType.OPENGL:
return OpenGLRenderer()
case RendererType.CAIRO:
return CairoRenderer()
case rendertype:
raise ValueError(f"Invalid Config Renderer type {rendertype}")
def _setup(self) -> None:
"""Set up processes and manager"""
if self.file_writer.has_progress_display():
self.scene.show_animation_progress = False
self.scene.setup()
self.virtual_animation_start_time = 0
self.real_animation_start_time = time.perf_counter()
def render(self) -> None:
"""
Entry point to running a Manim class
Example
-------
.. code-block:: python
class MyScene(Scene):
def construct(self):
self.play(Create(Circle()))
with tempconfig({"preview": True}):
Manager(MyScene).render()
"""
self._render_first_pass()
self._render_second_pass()
self._interact()
def _render_first_pass(self) -> None:
"""
Temporarily use the normal single pass
rendering system
"""
self._setup()
try:
self.scene.construct()
self._interact()
except EndSceneEarlyException:
pass
except KeyboardInterrupt:
# Get rid keyboard interrupt symbols
print("", end="\r")
self.file_writer.ended_with_interrupt = True
self._tear_down()
def _render_second_pass(self) -> None:
"""
In the future, this method could be used
for two pass rendering
"""
...
def get_time_progression(self, run_time: float) -> Iterable[float]:
def _tear_down(self):
self.scene.tear_down()
if config.save_last_frame:
self._update_frame(0)
self.file_writer.finish()
if self.window is not None:
self.window.close()
self.window = None
def _interact(self) -> None:
if self.window is None:
return
logger.info(
"\nTips: Using the keys `d`, `f`, or `z` "
+ "you can interact with the scene. "
+ "Press `command + q` or `esc` to quit"
)
self.scene.skip_animations = False
self.scene.refresh_static_mobjects()
while not self.window.is_closing:
# TODO: Replace with actual dt instead
# of hardcoded dt
dt = 1 / self.camera.fps
self._update_frame(dt)
def _update_frame(self, dt: float):
self.time += dt
self.scene._update_mobjects(dt)
if self.window is not None:
self.window.clear()
state = self.scene.get_state()
self._render_frame(state)
if self.window is not None:
self.window.swap_buffers()
vt = self.time - self.virtual_animation_start_time
rt = time.perf_counter() - self.real_animation_start_time
if rt < vt:
self._update_frame(0)
def _play(self, *animations: AnimationProtocol):
self.scene.pre_play()
if self.window is not None:
self.real_animation_start_time = time.perf_counter()
self.virtual_animation_start_time = self.time
self.scene.begin_animations(animations)
self._progress_through_animations(animations)
self.scene.finish_animations(animations)
if self.scene.skip_animations and self.window is not None:
self._update_frame(dt=0)
self.scene.post_play()
def _wait(
self, duration: float, *, stop_condition: Callable[[], bool] | None = None
):
self.scene.pre_play()
update_mobjects = (
self.scene.should_update_mobjects()
) # TODO: this method needs to be implemented
condition = stop_condition or (lambda: False)
last_t = 0
for t in self._calc_time_progression(duration):
if update_mobjects:
dt, last_t = t - last_t, t
self._update_frame(dt)
if condition():
break
else:
self.renderer.render_previous(self.camera)
self.scene.post_play()
def _progress_through_animations(self, animations: Iterable[AnimationProtocol]):
last_t = 0
run_time = self._calc_runtime(animations)
for t in self._calc_time_progression(run_time):
dt, last_t = t - last_t, t
self.scene._update_animations(animations, t, dt)
self._update_frame(dt)
def _calc_time_progression(self, run_time: float) -> Iterable[float]:
return np.arange(0, run_time, 1 / self.camera.fps)
def render_state(self, state: SceneState, parallel: bool = True) -> None:
"""Launch a process (optionally in parallel)
to render a frame
"""
if parallel and config.in_parallel:
logger.warning("Not supported yet")
self.render_frame(state)
def _calc_runtime(self, animations: Iterable[AnimationProtocol]):
return max(animation.get_run_time() for animation in animations)
# type state: SceneState
def render_frame(self, state: SceneState) -> Any | None:
"""Renders a frame based on a state"""
data = self.send_scene_to_renderer(state)
def _render_frame(self, state: SceneState) -> Any | None:
"""Renders a frame based on a state, and writes it to a file"""
data = self._send_scene_to_renderer(state)
# result = self.file_writer.write(data)
self.manager_dict[state.time] = data
def send_scene_to_renderer(self, state: SceneState):
def _send_scene_to_renderer(self, state: SceneState):
"""Renders the State"""
result = self.renderer.render(self.camera, state.mobjects)
result = self.renderer.render(self.scene.camera, state.mobjects)
return result
def get_frames(self) -> list:
"""Get a list of every frame produced by the
manager.
.. warning::
This list is _not guarenteed_ to be sorted until
after calling :meth:`.RenderManager.finish`
"""
return self.manager_dict
def finish(self) -> None:
for process in self.processes.queue:
process.join()
self.manager_dict = dict(sorted(self.manager_dict.items()))

View file

@ -1,14 +1,22 @@
from abc import ABC, abstractclassmethod, abstractstaticmethod
from typing import TYPE_CHECKING
from __future__ import annotations
from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, Protocol
import numpy as np
from typing_extensions import TypeAlias
from manim import config
from manim._config import logger
from manim.mobject.opengl.opengl_mobject import OpenGLMobject
from manim.mobject.opengl.opengl_vectorized_mobject import OpenGLVMobject
from manim.mobject.types.image_mobject import ImageMobject
from manim.mobject.types.vectorized_mobject import VMobject
if TYPE_CHECKING:
from collections.abc import Callable, Iterable, Sequence
import moderngl as gl
from manim.camera.camera import Camera
ImageType: TypeAlias = np.ndarray
@ -20,15 +28,15 @@ class RendererData:
class Renderer(ABC):
def __init__(self):
self.capabilities = [
(OpenGLVMobject, self.render_vmobject),
(ImageMobject, self.render_image),
(OpenGLVMobject, self.render_vmobject), # type: ignore
(ImageMobject, self.render_image), # type: ignore
]
def render(self, camera, renderables: list[OpenGLVMobject]) -> None: # Image
def render(self, camera, renderables: Iterable[OpenGLMobject]) -> None: # Image
self.pre_render(camera)
for mob in renderables:
for type, render_func in self.capabilities:
if isinstance(mob, type):
for type_, render_func in self.capabilities:
if isinstance(mob, type_):
render_func(mob)
break
else:
@ -37,27 +45,54 @@ class Renderer(ABC):
)
self.post_render()
def pre_render(self, camera):
@abstractmethod
def pre_render(self, camera: Camera):
raise NotImplementedError
@abstractmethod
def post_render(self):
raise NotImplementedError
def use_window(self):
@abstractmethod
def render_vmobject(self, mob: OpenGLVMobject):
raise NotImplementedError
@abstractclassmethod
def render_vmobject(self, mob: OpenGLVMobject) -> None:
@abstractmethod
def render_image(self, mob: ImageMobject):
raise NotImplementedError
class RendererProtocol(Protocol):
capabilities: Sequence[
tuple[type[OpenGLMobject], Callable[[type[OpenGLMobject]], object]]
]
def render(self, camera: Camera, renderables: Iterable[OpenGLMobject]) -> None:
...
def render_previous(self, camera: Camera) -> None:
...
def pre_render(self, camera) -> object:
...
def post_render(self) -> object:
...
def use_window(self):
...
def render_vmobject(self, mob: OpenGLVMobject) -> object:
...
def render_mesh(self, mob) -> None:
raise NotImplementedError
...
def render_image(self, mob) -> None:
raise NotImplementedError
def render_image(self, mob: ImageMobject) -> None:
...
def get_pixels(self) -> ImageType:
raise NotImplementedError
...
# NOTE: The user should expect depth between renderers not to be handled discussed at 03.09.2023 Between jsonv and MrDiver

View file

@ -6,7 +6,7 @@ import platform
import random
import time
from collections import OrderedDict
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Sequence
import numpy as np
from IPython.terminal import pt_inputhooks
@ -24,8 +24,9 @@ from manim.mobject.frame import FullScreenRectangle
from manim.mobject.mobject import Group, Point, _AnimationBuilder
from manim.mobject.opengl.opengl_mobject import OpenGLMobject as Mobject
from manim.mobject.types.vectorized_mobject import VGroup, VMobject
from manim.renderer.render_manager import RenderManager
from manim.utils.color import RED
from manim.utils.deprecation import deprecated
from manim.utils.exceptions import EndSceneEarlyException
from manim.utils.family_ops import extract_mobject_family_members
from manim.utils.iterables import list_difference_update
from manim.utils.module_ops import get_module
@ -33,12 +34,11 @@ from manim.utils.module_ops import get_module
if TYPE_CHECKING:
from typing import Any, Callable, Iterable
from PIL.Image import Image
from manim.animation.protocol import AnimationProtocol as Animation
from manim.animation.scene_buffer import SceneBuffer
from manim.renderer.render_manager import Manager
# TODO: these keybindings should be made configureable
# TODO: these keybindings should be made configurable
PAN_3D_KEY = "d"
FRAME_SHIFT_KEY = "f"
@ -57,6 +57,7 @@ class Scene:
def __init__(
self,
manager: Manager | None = None,
window_config: dict = {},
camera_config: dict = {},
skip_animations: bool = False,
@ -81,17 +82,10 @@ class Scene:
self.embed_exception_mode = embed_exception_mode
self.embed_error_sound = embed_error_sound
# Initialize window, if applicable
if self.preview:
from manim.renderer.opengl_renderer_window import Window
self.window = Window()
else:
self.window = None
# Core state of the scene
self.camera: Camera = Camera()
self.camera.save_state()
self.manager = manager
self.mobjects: list[Mobject] = []
self.id_to_mobject_map: dict[int, Mobject] = {}
self.num_plays: int = 0
@ -100,12 +94,9 @@ class Scene:
self.original_skipping_status: bool = self.skip_animations
self.undo_stack = []
self.redo_stack = []
self.manager = RenderManager(self.get_default_scene_name(), camera=self.camera)
if self.start_at_animation_number is not None:
self.skip_animations = True
if self.manager.file_writer.has_progress_display():
self.show_animation_progress = False
# Items associated with interaction
self.mouse_point = Point()
@ -138,68 +129,31 @@ class Scene:
self.add(*buffer.to_add)
buffer.clear()
def run(self) -> None:
config.scene_name = str(self)
self.virtual_animation_start_time: float = 0
self.real_animation_start_time: float = time.time()
@deprecated(message="Use Manager(Scene).render()")
@classmethod
def run(cls) -> None:
from ..renderer.render_manager import Manager
self.setup()
try:
self.construct()
# wait until all animations rendered in parallel
self.manager.finish()
self.interact()
except EndScene:
pass
except KeyboardInterrupt:
# Get rid keyboard interrupt symbols
print("", end="\r")
self.manager.file_writer.ended_with_interrupt = True
self.tear_down()
return Manager(cls).render()
render = run
def setup(self) -> None:
"""
This is meant to be implement by any scenes which
are comonly subclassed, and have some common setup
This method is used to set up scenes to do any setup
involved before the construct method is called.
"""
pass
def construct(self) -> None:
# Where all the animation happens
# To be implemented in subclasses
pass
"""
The entrypoint to animations in Manim.
Should be overridden in the subclass to produce animations
"""
def tear_down(self) -> None:
self.stop_skipping()
if config.save_last_frame:
self.update_frame(ignore_skipping=True)
self.manager.file_writer.finish()
if self.window:
self.window.close()
self.window = None
def interact(self) -> None:
"""
If there is a window, enter a loop
which updates the frame while under
the hood calling the pyglet event loop
This method is used to clean up scenes
"""
if self.window is None:
return
logger.info(
"\nTips: Using the keys `d`, `f`, or `z` "
+ "you can interact with the scene. "
+ "Press `command + q` or `esc` to quit"
)
self.skip_animations = False
self.refresh_static_mobjects()
while not self.window.is_closing:
self.update_frame(1 / self.camera.fps)
def embed(
self,
@ -288,7 +242,7 @@ class Scene:
# Launch shell
shell(
local_ns=local_ns,
# Pretend like we're embeding in the caller function, not here
# Pretend like we're embedding in the caller function, not here
stack_depth=2,
# Specify that the present module is the caller's, not here
module=get_module(caller_frame.f_globals["__file__"]),
@ -296,54 +250,39 @@ class Scene:
# End scene when exiting an embed
if close_scene_on_exit:
raise EndScene()
raise EndSceneEarlyException()
# Only these methods should touch the camera
def update_frame(self, dt: float = 0, ignore_skipping: bool = False) -> None:
self.increment_time(dt)
self.update_mobjects(dt)
if self.skip_animations and not ignore_skipping:
return
if self.window.is_closing:
raise EndScene()
if self.window:
self.window.clear()
# self.camera.clear()
state = self.get_state()
self.manager.render_state(state)
if self.window:
self.window.swap_buffers()
vt = self.time - self.virtual_animation_start_time
rt = time.time() - self.real_animation_start_time
if rt < vt:
self.update_frame(0)
# Related to updating
def update_mobjects(self, dt: float) -> None:
def _update_mobjects(self, dt: float) -> None:
for mobject in self.mobjects:
mobject.update(dt)
def should_update_mobjects(self) -> bool:
"""
This is only called when a single Wait animation is played.
This is called to check if a wait frame should be frozen
Returns
-------
bool: does it have to be rerendered or is it static
"""
wait_animation = self.animations[0]
if wait_animation.is_static_wait is None:
should_update = (
self.always_update_mobjects
or self.updaters
or wait_animation.stop_condition is not None
or any(
mob.has_time_based_updater()
for mob in self.get_mobject_family_members()
)
)
wait_animation.is_static_wait = not should_update
return not wait_animation.is_static_wait
# always rerender by returning True
# TODO: Apply caching here
return True
# wait_animation = self.animations[0]
# if wait_animation.is_static_wait is None:
# should_update = (
# self.always_update_mobjects
# or self.updaters
# or wait_animation.stop_condition is not None
# or any(
# mob.has_time_based_updater()
# for mob in self.get_mobject_family_members()
# )
# )
# wait_animation.is_static_wait = not should_update
# return not wait_animation.is_static_wait
def has_time_based_updaters(self) -> bool:
return any(
@ -597,7 +536,7 @@ class Scene:
if (self.end_at_animation_number is not None) and (
self.num_plays >= self.end_at_animation_number
):
raise EndScene()
raise EndSceneEarlyException()
def stop_skipping(self) -> None:
self.virtual_animation_start_time = self.time
@ -605,45 +544,6 @@ class Scene:
# Methods associated with running animations
def get_time_progression(
self,
run_time: float,
n_iterations: int | None = None,
desc: str = "",
override_skip_animations: bool = False,
) -> list[float] | np.ndarray | ProgressDisplay:
if self.skip_animations and not override_skip_animations:
return [run_time]
times = np.arange(0, run_time, 1 / self.camera.fps)
# self.file_writer.set_progress_display_description(sub_desc=desc)
if self.show_animation_progress:
return ProgressDisplay(
times,
total=n_iterations,
leave=self.leave_progress_bars,
ascii=True if platform.system() == "Windows" else None,
desc=desc,
)
else:
return times
def get_run_time(self, animations: Iterable[Animation]) -> float:
return max([animation.get_run_time() for animation in animations])
def get_animation_time_progression(
self, animations: Iterable[Animation]
) -> list[float] | np.ndarray | ProgressDisplay:
animations = list(animations)
run_time = self.get_run_time(animations)
description = f"{self.num_plays} {animations[0]}"
if len(animations) > 1:
description += ", etc."
time_progression = self.manager.get_time_progression(run_time)
return time_progression
def get_wait_time_progression(
self, duration: float, stop_condition: Callable[[], bool] | None = None
) -> list[float] | np.ndarray | ProgressDisplay:
@ -662,23 +562,13 @@ class Scene:
# if not self.skip_animations:
# self.file_writer.begin_animation()
if self.window:
self.real_animation_start_time = time.time()
self.virtual_animation_start_time = self.time
self.refresh_static_mobjects()
self.manager.begin()
def post_play(self):
# if not self.skip_animations:
# self.manager.file_writer.end_animation()
if self.skip_animations and self.window is not None:
# Show some quick frames along the way
self.update_frame(dt=0, ignore_skipping=True)
self.num_plays += 1
self.manager.finish()
def refresh_static_mobjects(self) -> None:
# self.camera.refresh_static_mobjects()
@ -689,20 +579,14 @@ class Scene:
animation.begin()
self.process_buffer(animation.buffer)
def progress_through_animations(self, animations: Iterable[Animation]) -> None:
last_t = 0
for t in self.get_animation_time_progression(animations):
dt = t - last_t
last_t = t
for animation in animations:
animation.update_mobjects(dt)
alpha = t / animation.get_run_time()
animation.interpolate(alpha)
if animation.apply_buffer:
self.process_buffer(animation.buffer)
animation.apply_buffer = False
self.update_frame(dt)
self.emit_frame()
def _update_animations(self, animations: Iterable[Animation], t: float, dt: float):
for animation in animations:
animation.update_mobjects(dt)
alpha = t / animation.get_run_time()
animation.interpolate(alpha)
if animation.apply_buffer:
self.process_buffer(animation.buffer)
animation.apply_buffer = False
def finish_animations(self, animations: Iterable[Animation]) -> None:
for animation in animations:
@ -710,9 +594,9 @@ class Scene:
self.process_buffer(animation.buffer)
if self.skip_animations:
self.update_mobjects(self.get_run_time(animations))
self._update_mobjects(self.manager._calc_runtime(animations))
else:
self.update_mobjects(0)
self._update_mobjects(0)
def play(
self,
@ -727,46 +611,40 @@ class Scene:
animations = [prepare_animation(x) for x in proto_animations]
for anim in animations:
anim.update_rate_info(run_time, rate_func, lag_ratio)
self.pre_play()
self.begin_animations(animations)
self.progress_through_animations(animations)
self.finish_animations(animations)
self.post_play()
# NOTE: Should be changed at some point with the 2 pass rendering system 21.06.2024
self.manager._play(*animations)
def wait(
self,
duration: float = DEFAULT_WAIT_TIME,
stop_condition: Callable[[], bool] = None,
note: str = None,
stop_condition: Callable[[], bool] | None = None,
note: str | None = None,
ignore_presenter_mode: bool = False,
):
self.pre_play()
self.update_mobjects(dt=0) # Any problems with this?
if (
self.presenter_mode
and not self.skip_animations
and not ignore_presenter_mode
):
if note:
logger.info(note)
self.hold_loop()
else:
time_progression = self.get_wait_time_progression(duration, stop_condition)
last_t = 0
for t in time_progression:
dt = t - last_t
last_t = t
self.update_frame(dt)
self.emit_frame()
if stop_condition is not None and stop_condition():
break
self.refresh_static_mobjects()
self.post_play()
def hold_loop(self):
while self.hold_on_wait:
self.update_frame(dt=1 / self.camera.fps)
self.hold_on_wait = True
self.manager._wait(duration, stop_condition=stop_condition)
# self.pre_play()
# self.update_mobjects(dt=0) # Any problems with this?
# if (
# self.presenter_mode
# and not self.skip_animations
# and not ignore_presenter_mode
# ):
# if note:
# logger.info(note)
# self.hold_loop()
# else:
# time_progression = self.get_wait_time_progression(duration, stop_condition)
# last_t = 0
# for t in time_progression:
# dt = t - last_t
# last_t = t
# self.update_frame(dt)
# self.emit_frame()
# if stop_condition is not None and stop_condition():
# break
# self.refresh_static_mobjects()
# self.post_play()
def wait_until(self, stop_condition: Callable[[], bool], max_time: float = 60):
self.wait(max_time, stop_condition=stop_condition)
@ -995,8 +873,8 @@ class SceneState:
self.mobjects_to_copies[mob] = mob.copy()
@property
def mobjects(self) -> list[Mobject]:
return self.mobjects_to_copies
def mobjects(self) -> Sequence[Mobject]:
return tuple(self.mobjects_to_copies.keys())
def __eq__(self, state: Any) -> bool:
return isinstance(state, SceneState) and all(
@ -1007,6 +885,9 @@ class SceneState:
)
)
def __repr__(self) -> str:
return f"{type(self).__name__} of {len(self.mobjects_to_copies)} Mobjects"
def mobjects_match(self, state: SceneState):
return self.mobjects_to_copies == state.mobjects_to_copies
@ -1023,7 +904,3 @@ class SceneState:
scene.mobjects = [
mob.become(mob_copy) for mob, mob_copy in self.mobjects_to_copies.items()
]
class EndScene(Exception):
pass

286
poetry.lock generated

File diff suppressed because it is too large Load diff