Skip to content

Commit 22844a2

Browse files
JasonGrace2282pre-commit-ci[bot]MrDiver
authored
Add rendering loop to experimental (#3707)
* try adding a render manager * Add a test scene * Got keys working * yay its a triangle * Cursed! * Allow support for self.add * Get it working * change test scene * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Separate Animation and Scene using SceneBuffer * Update subclasses This is likely to be a very bug-prone commit * Fix bugs with not clearing buffer * remove useless clear in scene.py' * fixes * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Add Scene.replace functionality * Make :class:`.Animation` explicitly implement `AnimationProtocol` * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix a bug with animation restructuring * fix succession * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix typo in render_manager * Added window independent resolution rendering * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Whops ? * Who needs window names anyway * fixed transform animation * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Remove printing of animation buffer * Lint, remove unused imports * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Removed useless reading from the gpu * Fixed broken interp function in opengl_vectorized mobject which used non-existing opacity * Fixed TracedPath * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Whops --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: = <=> Co-authored-by: Tristan Schulz <[email protected]>
1 parent c6dfc15 commit 22844a2

34 files changed

+1039
-1582
lines changed

example_scenes/new_test_new.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from manim.animation.creation import Create, DrawBorderThenFill, Write
1313
from manim.animation.fading import FadeIn
1414
from manim.animation.transform import Transform
15-
from manim.camera.camera import OpenGLCameraFrame
15+
from manim.camera.camera import Camera
1616
from manim.constants import LEFT, OUT, RIGHT, UP
1717
from manim.mobject.geometry.arc import Circle
1818
from manim.mobject.geometry.polygram import Square
@@ -39,6 +39,7 @@ def progress_through_animations(animations):
3939
win = Window(
4040
width=1920,
4141
height=1080,
42+
fullscreen=True,
4243
vsync=True,
4344
config=Config(double_buffer=True, samples=0),
4445
)
@@ -70,7 +71,7 @@ def progress_through_animations(animations):
7071
clock_mobject = DecimalNumber(0.0).shift(4 * LEFT + 2.5 * UP)
7172
clock_mobject.fix_in_frame()
7273

73-
camera = OpenGLCameraFrame()
74+
camera = Camera()
7475
camera.save_state()
7576
# renderer.init_camera(camera)
7677

@@ -151,6 +152,8 @@ def on_resize(width, height):
151152
if not is_finished:
152153
if virtual_time >= run_time:
153154
animation.finish()
155+
buffer = str(animation.buffer)
156+
print(f"{buffer = }")
154157
has_finished = True
155158
else:
156159
animation.update_mobjects(dt)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from manim import *
2+
3+
4+
class Test(Scene):
5+
def construct(self) -> None:
6+
s = Square()
7+
c = Circle()
8+
st = Star(color=YELLOW, fill_color=YELLOW)
9+
self.play(Succession(*[Create(x) for x in VGroup(s, c, st).arrange()]))
10+
11+
12+
with tempconfig({"renderer": "opengl", "preview": True, "parallel": False}):
13+
Test().render()

manim/__init__.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@
3737
from .animation.transform_matching_parts import *
3838
from .animation.updaters.mobject_update_utils import *
3939
from .animation.updaters.update import *
40-
from .camera.cairo_camera import *
4140
from .constants import *
4241
from .mobject.frame import *
4342
from .mobject.geometry.arc import *
@@ -73,7 +72,6 @@
7372
from .mobject.types.vectorized_mobject import *
7473
from .mobject.value_tracker import *
7574
from .mobject.vector_field import *
76-
from .renderer.cairo_renderer import *
7775
from .scene.scene import *
7876
from .scene.scene_file_writer import *
7977
from .scene.section import *

manim/_config/utils.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,7 @@ class MyScene(Scene):
311311
"write_to_movie",
312312
"zero_pad",
313313
"force_window",
314+
"parallel",
314315
}
315316

316317
def __init__(self) -> None:
@@ -582,6 +583,7 @@ def digest_parser(self, parser: configparser.ConfigParser) -> ManimConfig:
582583
"use_projection_stroke_shaders",
583584
"enable_wireframe",
584585
"force_window",
586+
"parallel",
585587
]:
586588
setattr(self, key, parser["CLI"].getboolean(key, fallback=False))
587589

@@ -995,6 +997,14 @@ def format(self, val: str) -> None:
995997
"Output format set as webm, this can be slower than other formats",
996998
)
997999

1000+
@property
1001+
def in_parallel(self) -> None:
1002+
return self._d["parallel"]
1003+
1004+
@in_parallel.setter
1005+
def in_parallel(self, val: bool) -> None:
1006+
self._set_boolean("parallel", val)
1007+
9981008
ffmpeg_loglevel = property(
9991009
lambda self: self._d["ffmpeg_loglevel"],
10001010
lambda self, val: self._set_from_list(

manim/animation/animation.py

Lines changed: 50 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,23 @@
1111
from ..mobject.mobject import Mobject
1212
from ..mobject.opengl import opengl_mobject
1313
from ..utils.rate_functions import linear, smooth
14+
from .protocol import AnimationProtocol
15+
from .scene_buffer import SceneBuffer
1416

1517
__all__ = ["Animation", "Wait", "override_animation"]
1618

17-
1819
from copy import deepcopy
19-
from typing import TYPE_CHECKING, Callable, Iterable, Sequence
20+
from typing import TYPE_CHECKING, Callable, Iterable, Sequence, TypeVar
2021

2122
if TYPE_CHECKING:
22-
from manim.scene.scene import Scene
23+
from typing_extensions import Self
2324

2425

2526
DEFAULT_ANIMATION_RUN_TIME: float = 1.0
2627
DEFAULT_ANIMATION_LAG_RATIO: float = 0.0
2728

2829

29-
class Animation:
30+
class Animation(AnimationProtocol):
3031
"""An animation.
3132
3233
Animations have a fixed time span.
@@ -127,17 +128,17 @@ def __new__(
127128

128129
def __init__(
129130
self,
130-
mobject: Mobject | None,
131+
mobject: OpenGLMobject | None,
131132
lag_ratio: float = DEFAULT_ANIMATION_LAG_RATIO,
132133
run_time: float = DEFAULT_ANIMATION_RUN_TIME,
133134
rate_func: Callable[[float], float] = smooth,
134135
reverse_rate_function: bool = False,
135-
name: str = None,
136-
remover: bool = False, # remove a mobject from the screen?
136+
name: str | None = None,
137+
remover: bool = False, # remove a mobject from the screen at end of animation
137138
suspend_mobject_updating: bool = True,
138139
introducer: bool = False,
139140
*,
140-
_on_finish: Callable[[], None] = lambda _: None,
141+
_on_finish: Callable[[SceneBuffer], object] = lambda _: None,
141142
**kwargs,
142143
) -> None:
143144
self._typecheck_input(mobject)
@@ -149,15 +150,15 @@ def __init__(
149150
self.introducer: bool = introducer
150151
self.suspend_mobject_updating: bool = suspend_mobject_updating
151152
self.lag_ratio: float = lag_ratio
152-
self._on_finish: Callable[[Scene], None] = _on_finish
153-
if config["renderer"] == RendererType.OPENGL:
154-
self.starting_mobject: OpenGLMobject = OpenGLMobject()
155-
self.mobject: OpenGLMobject = (
156-
mobject if mobject is not None else OpenGLMobject()
157-
)
158-
else:
159-
self.starting_mobject: Mobject = Mobject()
160-
self.mobject: Mobject = mobject if mobject is not None else Mobject()
153+
self._on_finish = _on_finish
154+
155+
self.buffer = SceneBuffer()
156+
self.apply_buffer = False # ask scene to apply buffer
157+
158+
self.starting_mobject: OpenGLMobject = OpenGLMobject()
159+
self.mobject: OpenGLMobject = (
160+
mobject if mobject is not None else OpenGLMobject()
161+
)
161162
if kwargs:
162163
logger.debug("Animation received extra kwargs: %s", kwargs)
163164

@@ -169,7 +170,7 @@ def __init__(
169170
),
170171
)
171172

172-
def _typecheck_input(self, mobject: Mobject | None) -> None:
173+
def _typecheck_input(self, mobject: Mobject | OpenGLMobject | None) -> None:
173174
if mobject is None:
174175
logger.debug("Animation with empty mobject")
175176
elif not isinstance(mobject, (Mobject, OpenGLMobject)):
@@ -213,10 +214,12 @@ def begin(self) -> None:
213214
self.mobject.suspend_updating()
214215
self.interpolate(0)
215216

217+
# TODO: Figure out a way to check
218+
# if self.mobject in scene.get_mobject_family
219+
if self.is_introducer():
220+
self.buffer.add(self.mobject)
221+
216222
def finish(self) -> None:
217-
# TODO: begin and finish should require a scene as parameter.
218-
# That way Animation.clean_up_from_screen and Scene.add_mobjects_from_animations
219-
# could be removed as they fulfill basically the same purpose.
220223
"""Finish the animation.
221224
222225
This method gets called when the animation is over.
@@ -226,39 +229,9 @@ def finish(self) -> None:
226229
if self.suspend_mobject_updating and self.mobject is not None:
227230
self.mobject.resume_updating()
228231

229-
def clean_up_from_scene(self, scene: Scene) -> None:
230-
"""Clean up the :class:`~.Scene` after finishing the animation.
231-
232-
This includes to :meth:`~.Scene.remove` the Animation's
233-
:class:`~.Mobject` if the animation is a remover.
234-
235-
Parameters
236-
----------
237-
scene
238-
The scene the animation should be cleaned up from.
239-
"""
240-
self._on_finish(scene)
232+
self._on_finish(self.buffer)
241233
if self.is_remover():
242-
scene.remove(self.mobject)
243-
244-
def _setup_scene(self, scene: Scene) -> None:
245-
"""Setup up the :class:`~.Scene` before starting the animation.
246-
247-
This includes to :meth:`~.Scene.add` the Animation's
248-
:class:`~.Mobject` if the animation is an introducer.
249-
250-
Parameters
251-
----------
252-
scene
253-
The scene the animation should be cleaned up from.
254-
"""
255-
if scene is None:
256-
return
257-
if (
258-
self.is_introducer()
259-
and self.mobject not in scene.get_mobject_family_members()
260-
):
261-
scene.add(self.mobject)
234+
self.buffer.remove(self.mobject)
262235

263236
def create_starting_mobject(self) -> Mobject:
264237
# Keep track of where the mobject starts
@@ -294,6 +267,17 @@ def update_mobjects(self, dt: float) -> None:
294267
for mob in self.get_all_mobjects_to_update():
295268
mob.update(dt)
296269

270+
def process_subanimation_buffer(self, buffer: SceneBuffer):
271+
"""
272+
This is used in animations that are proxies around
273+
other animations, like :class:`.AnimationGroup`
274+
"""
275+
self.buffer.remove(*buffer.to_remove)
276+
for to_replace_pairs in buffer.to_replace:
277+
self.buffer.replace(*to_replace_pairs)
278+
self.buffer.add(*buffer.to_add)
279+
buffer.clear()
280+
297281
def get_all_mobjects_to_update(self) -> list[Mobject]:
298282
"""Get all mobjects to be updated during the animation.
299283
@@ -305,9 +289,9 @@ def get_all_mobjects_to_update(self) -> list[Mobject]:
305289
# The surrounding scene typically handles
306290
# updating of self.mobject. Besides, in
307291
# most cases its updating is suspended anyway
308-
return list(filter(lambda m: m is not self.mobject, self.get_all_mobjects()))
292+
return [m for m in self.get_all_mobjects() if m is not self.mobject]
309293

310-
def copy(self) -> Animation:
294+
def copy(self) -> Self:
311295
"""Create a copy of the animation.
312296
313297
Returns
@@ -343,7 +327,7 @@ def interpolate_mobject(self, alpha: float) -> None:
343327
is completed. For example, alpha-values of 0, 0.5, and 1 correspond
344328
to the animation being completed 0%, 50%, and 100%, respectively.
345329
"""
346-
families = list(self.get_all_families_zipped())
330+
families = tuple(self.get_all_families_zipped())
347331
for i, mobs in enumerate(families):
348332
sub_alpha = self.get_sub_alpha(alpha, i, len(families))
349333
self.interpolate_submobject(*mobs, sub_alpha)
@@ -356,7 +340,7 @@ def interpolate_submobject(
356340
alpha: float,
357341
) -> Animation:
358342
# Typically implemented by subclass
359-
pass
343+
raise NotImplementedError()
360344

361345
def get_sub_alpha(self, alpha: float, index: int, num_submobjects: int) -> float:
362346
"""Get the animation progress of any submobjects subanimation.
@@ -422,7 +406,7 @@ def get_run_time(self) -> float:
422406
def set_rate_func(
423407
self,
424408
rate_func: Callable[[float], float],
425-
) -> Animation:
409+
) -> Self:
426410
"""Set the rate function of the animation.
427411
428412
Parameters
@@ -451,7 +435,7 @@ def get_rate_func(
451435
"""
452436
return self.rate_func
453437

454-
def set_name(self, name: str) -> Animation:
438+
def set_name(self, name: str) -> Self:
455439
"""Set the name of the animation.
456440
457441
Parameters
@@ -489,7 +473,9 @@ def is_introducer(self) -> bool:
489473

490474

491475
def prepare_animation(
492-
anim: Animation | mobject._AnimationBuilder,
476+
anim: AnimationProtocol
477+
| mobject._AnimationBuilder
478+
| opengl_mobject._AnimationBuilder,
493479
) -> Animation:
494480
r"""Returns either an unchanged animation, or the animation built
495481
from a passed animation factory.
@@ -517,10 +503,7 @@ def prepare_animation(
517503
TypeError: Object 42 cannot be converted to an animation
518504
519505
"""
520-
if isinstance(anim, mobject._AnimationBuilder):
521-
return anim.build()
522-
523-
if isinstance(anim, opengl_mobject._AnimationBuilder):
506+
if isinstance(anim, (mobject._AnimationBuilder, opengl_mobject._AnimationBuilder)):
524507
return anim.build()
525508

526509
if isinstance(anim, Animation):
@@ -576,9 +559,6 @@ def begin(self) -> None:
576559
def finish(self) -> None:
577560
pass
578561

579-
def clean_up_from_scene(self, scene: Scene) -> None:
580-
pass
581-
582562
def update_mobjects(self, dt: float) -> None:
583563
pass
584564

@@ -625,7 +605,9 @@ def construct(self):
625605
626606
"""
627607

628-
def decorator(func):
608+
_F = TypeVar("_F", bound=Callable)
609+
610+
def decorator(func: _F) -> _F:
629611
func._override_animation = animation_class
630612
return func
631613

0 commit comments

Comments
 (0)