Skip to content

Commit

Permalink
Refactored run_time validation for Animation and Scene.wait() (#…
Browse files Browse the repository at this point in the history
…3982)

* Refactored Animation run_time validation

* Rewrite error and warning messages, and add validations to wait(), pause() and wait_until()

* Undo rewriting of imports
  • Loading branch information
chopan050 authored Oct 28, 2024
1 parent 20f44b4 commit f65b7f8
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 35 deletions.
30 changes: 29 additions & 1 deletion manim/animation/animation.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ def begin(self) -> None:
method.
"""
self.run_time = validate_run_time(self.run_time, str(self))
self.starting_mobject = self.create_starting_mobject()
if self.suspend_mobject_updating:
# All calls to self.mobject's internal updaters
Expand Down Expand Up @@ -568,6 +569,33 @@ def prepare_animation(
raise TypeError(f"Object {anim} cannot be converted to an animation")


def validate_run_time(
run_time: float, caller_name: str, parameter_name: str = "run_time"
) -> float:
if run_time <= 0:
raise ValueError(
f"{caller_name} has a {parameter_name} of {run_time:g} <= 0 "
f"seconds which Manim cannot render. Please set the "
f"{parameter_name} to a positive number."
)

# config.frame_rate holds the number of frames per second
fps = config.frame_rate
seconds_per_frame = 1 / fps
if run_time < seconds_per_frame:
logger.warning(
f"The original {parameter_name} of {caller_name}, {run_time:g} "
f"seconds, is too short for the current frame rate of {fps:g} "
f"FPS. Rendering with the shortest possible {parameter_name} of "
f"{seconds_per_frame:g} seconds instead."
)
new_run_time = seconds_per_frame
else:
new_run_time = run_time

return new_run_time


class Wait(Animation):
"""A "no operation" animation.
Expand Down Expand Up @@ -610,7 +638,7 @@ def __init__(
self.mobject.shader_wrapper_list = []

def begin(self) -> None:
pass
self.run_time = validate_run_time(self.run_time, str(self))

def finish(self) -> None:
pass
Expand Down
5 changes: 3 additions & 2 deletions manim/animation/composition.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import numpy as np

from manim._config import config
from manim.animation.animation import Animation, prepare_animation
from manim.animation.animation import Animation, prepare_animation, validate_run_time
from manim.constants import RendererType
from manim.mobject.mobject import Group, Mobject
from manim.mobject.opengl.opengl_mobject import OpenGLGroup
Expand Down Expand Up @@ -87,7 +87,7 @@ def begin(self) -> None:
f"Trying to play {self} without animations, this is not supported. "
"Please add at least one subanimation."
)

self.run_time = validate_run_time(self.run_time, str(self))
self.anim_group_time = 0.0
if self.suspend_mobject_updating:
self.group.suspend_updating()
Expand Down Expand Up @@ -235,6 +235,7 @@ def begin(self) -> None:
f"Trying to play {self} without animations, this is not supported. "
"Please add at least one subanimation."
)
self.run_time = validate_run_time(self.run_time, str(self))
self.update_active_animation(0)

def finish(self) -> None:
Expand Down
28 changes: 5 additions & 23 deletions manim/scene/scene.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
from manim.mobject.opengl.opengl_mobject import OpenGLPoint

from .. import config, logger
from ..animation.animation import Animation, Wait, prepare_animation
from ..animation.animation import Animation, Wait, prepare_animation, validate_run_time
from ..camera.camera import Camera
from ..constants import *
from ..gui.gui import configure_pygui
Expand Down Expand Up @@ -1030,28 +1030,7 @@ def get_run_time(self, animations: list[Animation]):
float
The total ``run_time`` of all of the animations in the list.
"""
max_run_time = 0
frame_rate = (
1 / config.frame_rate
) # config.frame_rate holds the number of frames per second
for animation in animations:
if animation.run_time <= 0:
raise ValueError(
f"{animation} has a run_time of <= 0 seconds which Manim cannot render. "
"Please set the run_time to be positive."
)
elif animation.run_time < frame_rate:
logger.warning(
f"Original run time of {animation} is shorter than current frame "
f"rate (1 frame every {frame_rate:.2f} sec.) which cannot be rendered. "
"Rendering with the shortest possible duration instead."
)
animation.run_time = frame_rate

if animation.run_time > max_run_time:
max_run_time = animation.run_time

return max_run_time
return max(animation.run_time for animation in animations)

def play(
self,
Expand Down Expand Up @@ -1147,6 +1126,7 @@ def wait(
--------
:class:`.Wait`, :meth:`.should_mobjects_update`
"""
duration = validate_run_time(duration, str(self) + ".wait()", "duration")
self.play(
Wait(
run_time=duration,
Expand All @@ -1170,6 +1150,7 @@ def pause(self, duration: float = DEFAULT_WAIT_TIME):
--------
:meth:`.wait`, :class:`.Wait`
"""
duration = validate_run_time(duration, str(self) + ".pause()", "duration")
self.wait(duration=duration, frozen_frame=True)

def wait_until(self, stop_condition: Callable[[], bool], max_time: float = 60):
Expand All @@ -1183,6 +1164,7 @@ def wait_until(self, stop_condition: Callable[[], bool], max_time: float = 60):
max_time
The maximum wait time in seconds.
"""
max_time = validate_run_time(max_time, str(self) + ".wait_until()", "max_time")
self.wait(max_time, stop_condition=stop_condition)

def compile_animation_data(
Expand Down
14 changes: 5 additions & 9 deletions tests/module/animation/test_animation.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,20 @@
)
def test_animation_forbidden_run_time(run_time):
test_scene = Scene()
with pytest.raises(ValueError, match="Please set the run_time to be positive"):
with pytest.raises(
ValueError, match="Please set the run_time to a positive number."
):
test_scene.play(FadeIn(None, run_time=run_time))


def test_animation_run_time_shorter_than_frame_rate(manim_caplog, config):
test_scene = Scene()
test_scene.play(FadeIn(None, run_time=1 / (config.frame_rate + 1)))
assert (
"Original run time of FadeIn(Mobject) is shorter than current frame rate"
in manim_caplog.text
)
assert "too short for the current frame rate" in manim_caplog.text


@pytest.mark.parametrize("frozen_frame", [False, True])
def test_wait_run_time_shorter_than_frame_rate(manim_caplog, frozen_frame):
test_scene = Scene()
test_scene.wait(1e-9, frozen_frame=frozen_frame)
assert (
"Original run time of Wait(Mobject) is shorter than current frame rate"
in manim_caplog.text
)
assert "too short for the current frame rate" in manim_caplog.text

0 comments on commit f65b7f8

Please sign in to comment.