diff --git a/docs/source/_static/pull-requests.PNG b/docs/source/_static/pull-requests.PNG index 91cbc97916..d9883971c5 100644 Binary files a/docs/source/_static/pull-requests.PNG and b/docs/source/_static/pull-requests.PNG differ diff --git a/manim/animation/animation.py b/manim/animation/animation.py index 43867651d8..a39aacaf45 100644 --- a/manim/animation/animation.py +++ b/manim/animation/animation.py @@ -236,7 +236,7 @@ def update_mobjects(self, dt: float) -> None: nothing to self.mobject. """ for mob in self.get_all_mobjects_to_update(): - mob.update(dt) + mob._apply_updaters(dt) def get_all_mobjects_to_update(self) -> List[Mobject]: """Get all mobjects to be updated during the animation. diff --git a/manim/mobject/mobject.py b/manim/mobject/mobject.py index 719627a454..6b3b2f1f8a 100644 --- a/manim/mobject/mobject.py +++ b/manim/mobject/mobject.py @@ -42,6 +42,7 @@ color_gradient, interpolate_color, ) +from ..utils.deprecation import deprecated from ..utils.exceptions import MultiAnimationOverrideException from ..utils.iterables import list_update, remove_list_redundancies from ..utils.paths import straight_path @@ -175,7 +176,7 @@ def add_animation_override( animation_class The animation type to be overridden override_func - The function returning an aniamtion replacing the default animation. It gets + The function returning an animation replacing the default animation. It gets passed the parameters given to the animnation constructor. Raises @@ -773,7 +774,11 @@ def generate_target(self, use_deepcopy=False): # Updating - def update(self, dt: float = 0, recursive: bool = True) -> "Mobject": + @deprecated(since="v0.7.0", until="v0.8.0", replacement="_apply_updaters") + def update(self, *args, **kwargs): + self._apply_updaters(*args, **kwargs) + + def _apply_updaters(self, dt: float = 0, recursive: bool = True) -> "Mobject": """Apply all updaters. Does nothing if updating is suspended. @@ -781,7 +786,8 @@ def update(self, dt: float = 0, recursive: bool = True) -> "Mobject": Parameters ---------- dt - The parameter ``dt`` to pass to the update functions. Usually this is the time in seconds since the last call of ``update``. + The parameter ``dt`` to pass to the update functions. Usually this is the + time in seconds since the last call of ``_apply_updaters``. recursive Whether to recursively update all submobjects. @@ -800,33 +806,12 @@ def update(self, dt: float = 0, recursive: bool = True) -> "Mobject": return self for updater in self.updaters: parameters = get_parameters(updater) - if "dt" in parameters: - updater(self, dt) - else: - updater(self) + updater(self, dt) if recursive: for submob in self.submobjects: - submob.update(dt, recursive) + submob._apply_updaters(dt, recursive) return self - def get_time_based_updaters(self) -> List[Updater]: - """Return all updaters using the ``dt`` parameter. - - The updaters use this parameter as the input for difference in time. - - Returns - ------- - List[:class:`Callable`] - The list of time based updaters. - - See Also - -------- - :meth:`get_updaters` - :meth:`has_time_based_updater` - - """ - return [updater for updater in self.updaters if "dt" in get_parameters(updater)] - def has_time_based_updater(self) -> bool: """Test if ``self`` has a time based updater. @@ -835,15 +820,9 @@ def has_time_based_updater(self) -> bool: class:`bool` ``True`` if at least one updater uses the ``dt`` parameter, ``False`` otherwise. - See Also - -------- - :meth:`get_time_based_updaters` """ - for updater in self.updaters: - if "dt" in get_parameters(updater): - return True - return False + return any(updater.time_based for updater in self.updaters) def get_updaters(self) -> List[Updater]: """Return all updaters. @@ -856,7 +835,6 @@ def get_updaters(self) -> List[Updater]: See Also -------- :meth:`add_updater` - :meth:`get_time_based_updaters` """ return self.updaters @@ -866,24 +844,38 @@ def get_family_updaters(self): def add_updater( self, - update_function: Updater, + updater: Updater, + *, + pass_relative_times: Optional[bool] = None, index: Optional[int] = None, call_updater: bool = False, ) -> "Mobject": """Add an update function to this mobject. - Update functions, or updaters in short, are functions that are applied to the Mobject in every frame. + Update functions, or updaters in short, are functions that are applied to the + Mobject before every frame. Parameters ---------- - update_function + updater The update function to be added. - Whenever :meth:`update` is called, this update function gets called using ``self`` as the first parameter. - The updater can have a second parameter ``dt``. If it uses this parameter, it gets called using a second value ``dt``, usually representing the time in seconds since the last call of :meth:`update`. + Whenever :meth:`update` is called, this update function gets called using + ``self`` as the first parameter. The updater can have a second parameter + that gets passed information about the execution time. + pass_relative_times + Whether the time information passed to the updater should be relative + (usually the time since last call) or absolute (sum of all relative times + since the updater was added). If ``pass_relative_times`` is ``None`` + relative times get passed, except if the second parameter of the updater is + named ``t`` or ``time``. index - The index at which the new updater should be added in ``self.updaters``. In case ``index`` is ``None`` the updater will be added at the end. + The index at which the new updater should be added in the list of updaters. + The order of this list determines the execution order of the updaters. + In case ``index`` is ``None`` the updater will be added at the end, where it + would be last in the execution order. call_updater - Whether or not to call the updater initially. If ``True``, the updater will be called using ``dt=0``. + Whether or not to call the updater initially after adding it (using ``0`` + as time if applicable). Returns ------- @@ -925,22 +917,47 @@ def construct(self): :class:`~.UpdateFromFunc` """ + # Test if updater is time based and whether to use time difference + parameters = list(get_parameters(updater).keys()) + time_based = len(parameters) > 1 + if time_based: + if parameters[1] not in ["t", "time"] and pass_relative_times is None: + pass_relative_times = True + pass_relative_times = bool(pass_relative_times) + + # Wrap updaters to allow calling all of them using updatable and dt as parameter + if time_based: + if pass_relative_times: + unified_updater = updater + else: + + def unified_updater(mob, dt): + unified_updater.total_time += dt + updater(mob, unified_updater.total_time) + + unified_updater.total_time = 0 + else: + unified_updater = lambda mob, dt: updater(mob) + + unified_updater.time_based = time_based + unified_updater.base_func = updater # used to enable removing + if index is None: - self.updaters.append(update_function) + self.updaters.append(unified_updater) else: - self.updaters.insert(index, update_function) + self.updaters.insert(index, unified_updater) if call_updater: - update_function(self, 0) + unified_updater(self, 0) return self - def remove_updater(self, update_function: Updater) -> "Mobject": + def remove_updater(self, updater: Updater) -> "Mobject": """Remove an updater. If the same updater is applied multiple times, every instance gets removed. Parameters ---------- - update_function + updater The update function to be removed. @@ -956,8 +973,12 @@ def remove_updater(self, update_function: Updater) -> "Mobject": :meth:`get_updaters` """ - while update_function in self.updaters: - self.updaters.remove(update_function) + self.updaters = list( + filter( + lambda unified_updater: unified_updater.base_func is not updater, + self.updaters, + ) + ) return self def clear_updaters(self, recursive: bool = True) -> "Mobject": @@ -1010,9 +1031,7 @@ def match_updaters(self, mobject: "Mobject") -> "Mobject": """ - self.clear_updaters() - for updater in mobject.get_updaters(): - self.add_updater(updater) + self.updaters = list(mobject.get_updaters()) return self def suspend_updating(self, recursive: bool = True) -> "Mobject": @@ -1065,7 +1084,7 @@ def resume_updating(self, recursive: bool = True) -> "Mobject": if recursive: for submob in self.submobjects: submob.resume_updating(recursive) - self.update(dt=0, recursive=recursive) + self._apply_updaters(dt=0, recursive=recursive) return self # Transforming operations @@ -2291,7 +2310,7 @@ def init_size(num, alignments, sizes): # make the grid as close to quadratic as possible. # choosing cols first can results in cols>rows. # This is favored over rows>cols since in general - # the sceene is wider than high. + # the scene is wider than high. if rows is None: rows = ceil(len(mobs) / cols) if cols is None: @@ -2361,7 +2380,7 @@ def reverse(maybe_list): mobs.extend([placeholder] * (rows * cols - len(mobs))) grid = [[mobs[flow_order(r, c)] for c in range(cols)] for r in range(rows)] - measured_heigths = [ + measured_heights = [ max([grid[r][c].height for c in range(cols)]) for r in range(rows) ] measured_widths = [ @@ -2378,7 +2397,7 @@ def init_sizes(sizes, num, measures, name): sizes[i] if sizes[i] is not None else measures[i] for i in range(num) ] - heights = init_sizes(row_heights, rows, measured_heigths, "row_heights") + heights = init_sizes(row_heights, rows, measured_heights, "row_heights") widths = init_sizes(col_widths, cols, measured_widths, "col_widths") x, y = 0, 0 diff --git a/manim/mobject/opengl_mobject.py b/manim/mobject/opengl_mobject.py index 604db4390e..2601e138d0 100644 --- a/manim/mobject/opengl_mobject.py +++ b/manim/mobject/opengl_mobject.py @@ -3,6 +3,7 @@ import random import sys from functools import wraps +from typing import Callable, Optional, Union import moderngl import numpy as np @@ -11,6 +12,7 @@ from ..constants import * from ..utils.bezier import interpolate from ..utils.color import * +from ..utils.deprecation import deprecated # from ..utils.iterables import batch_by_property from ..utils.iterables import ( @@ -26,6 +28,10 @@ from ..utils.simple_functions import get_parameters from ..utils.space_ops import angle_of_vector, rotation_matrix_transpose +Updater = Union[ + Callable[["OpenGLMobject"], None], Callable[["OpenGLMobject", float], None] +] + class OpenGLMobject: """ @@ -588,7 +594,11 @@ def init_updaters(self): self.has_updaters = False self.updating_suspended = False - def update(self, dt=0, recurse=True): + @deprecated(since="v0.7.0", until="v0.8.0", replacement="_apply_updaters") + def update(self, *args, **kwargs): + self._apply_updaters(*args, **kwargs) + + def _apply_updaters(self, dt=0, recurse=True): if not self.has_updaters or self.updating_suspended: return self for updater in self.time_based_updaters: @@ -597,12 +607,9 @@ def update(self, dt=0, recurse=True): updater(self) if recurse: for submob in self.submobjects: - submob.update(dt, recurse) + submob._apply_updaters(dt, recurse) return self - def get_time_based_updaters(self): - return self.time_based_updaters - def has_time_based_updater(self): return len(self.time_based_updaters) > 0 @@ -612,26 +619,63 @@ def get_updaters(self): def get_family_updaters(self): return list(it.chain(*[sm.get_updaters() for sm in self.get_family()])) - def add_updater(self, update_function, index=None, call_updater=True): - if "dt" in get_parameters(update_function): - updater_list = self.time_based_updaters + def add_updater( + self, + updater: Updater, + *, + pass_relative_times: Optional[bool] = None, + index: Optional[int] = None, + call_updater: bool = False, + ) -> "OpenGLMobject": + # Test if updater is time based and whether to use time difference + parameters = list(get_parameters(updater).keys()) + time_based = len(parameters) > 1 + if time_based: + if parameters[1] not in ["t", "time"] and pass_relative_times is None: + pass_relative_times = True + pass_relative_times = bool(pass_relative_times) + + # Wrap updaters to allow calling all of them using updatable and dt as parameter + if time_based: + if pass_relative_times: + dt_updater = updater + else: + + def dt_updater(mob, dt): + dt_updater.total_time += dt + updater(mob, dt_updater.total_time) + + dt_updater.total_time = 0 + dt_updater.base_func = updater # used to enable removing + updater_list = self.time_based_updaters + + updater = dt_updater else: updater_list = self.non_time_updaters if index is None: - updater_list.append(update_function) + updater_list.append(updater) else: - updater_list.insert(index, update_function) + updater_list.insert(index, updater) self.refresh_has_updater_status() if call_updater: - self.update() + self._apply_updaters() return self - def remove_updater(self, update_function): - for updater_list in [self.time_based_updaters, self.non_time_updaters]: - while update_function in updater_list: - updater_list.remove(update_function) + def remove_updater(self, updater): + self.time_based_updaters = list( + filter( + lambda x: x.base_func is not updater, + self.time_based_updaters, + ) + ) + self.non_time_updaters = list( + filter( + lambda x: x is not updater, + self.non_time_updaters, + ) + ) self.refresh_has_updater_status() return self @@ -646,8 +690,9 @@ def clear_updaters(self, recurse=True): def match_updaters(self, mobject): self.clear_updaters() - for updater in mobject.get_updaters(): - self.add_updater(updater) + self.time_based_updaters = list(mobject.time_based_updaters) + self.non_time_updaters = list(mobject.non_time_updaters) + self.refresh_has_updater_status() return self def suspend_updating(self, recurse=True): diff --git a/manim/renderer/shader.py b/manim/renderer/shader.py index c7b6c422cb..363fa5c0f2 100644 --- a/manim/renderer/shader.py +++ b/manim/renderer/shader.py @@ -97,9 +97,6 @@ def update(self, dt=0): updater(self) return self - def get_time_based_updaters(self): - return self.time_based_updaters - def has_time_based_updater(self): return len(self.time_based_updaters) > 0 diff --git a/manim/scene/scene.py b/manim/scene/scene.py index 676192c54a..ec69c1b371 100644 --- a/manim/scene/scene.py +++ b/manim/scene/scene.py @@ -305,7 +305,7 @@ def update_mobjects(self, dt): Change in time between updates. Defaults (mostly) to 1/frames_per_second """ for mobject in self.mobjects: - mobject.update(dt) + mobject._apply_updaters(dt) def update_meshes(self, dt): for mesh in self.meshes: