Skip to content
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

Refactored run_time validation for Animation and Scene.wait() #3982

Merged
merged 5 commits into from
Oct 28, 2024
Merged
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
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
Dismissed Show dismissed Hide dismissed
Dismissed Show dismissed Hide dismissed
Dismissed Show dismissed Hide dismissed
Dismissed Show dismissed Hide dismissed
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
Loading