diff --git a/manim/animation/animation.py b/manim/animation/animation.py index 8bdf236b3f..e090d99059 100644 --- a/manim/animation/animation.py +++ b/manim/animation/animation.py @@ -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 @@ -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. @@ -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 diff --git a/manim/animation/composition.py b/manim/animation/composition.py index c5a756502f..36e9e1d6e3 100644 --- a/manim/animation/composition.py +++ b/manim/animation/composition.py @@ -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 @@ -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() @@ -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: diff --git a/manim/scene/scene.py b/manim/scene/scene.py index 02c548cf7f..fe0f993413 100644 --- a/manim/scene/scene.py +++ b/manim/scene/scene.py @@ -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 @@ -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, @@ -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, @@ -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): @@ -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( diff --git a/tests/module/animation/test_animation.py b/tests/module/animation/test_animation.py index 448c0e76d6..fb2efc7853 100644 --- a/tests/module/animation/test_animation.py +++ b/tests/module/animation/test_animation.py @@ -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