Repository: ManimCommunity/manim
Pull request: feat: set_default for Animation class
<!-- Thank you for contributing to Manim! Learn more about the process in our contributing guidelines: https://docs.manim.community/en/latest/contributing.html -->

## Overview: What does this pull request change?
<!-- If there is more information than the PR title that should be added to our release changelog, add it in the following changelog section. This is optional, but recommended for larger pull requests. -->
<!--changelog-start-->
Adds set_default class method to Animation class based on existing implementation for Mobject class.


<!--changelog-end-->

## Motivation and Explanation: Why and how do your changes improve the library?
<!-- Optional for bugfixes, small enhancements, and documentation-related PRs. Otherwise, please give a short reasoning for your changes. -->
Addresses issue [#3142](https://github.com/ManimCommunity/manim/issues/3142)

## Links to added or changed documentation pages
<!-- Please add links to the affected documentation pages (edit the description after opening the PR). The link to the documentation for your PR is https://manimce--####.org.readthedocs.build/en/####/, where #### represents the PR number. -->


## Further Information and Comments
<!-- If applicable, put further comments for the reviewers here. -->



<!-- Thank you again for contributing! Do not modify the lines below, they are for reviewers. -->
## Reviewer Checklist
- [ ] The PR title is descriptive enough for the changelog, and the PR is labeled correctly
- [ ] If applicable: newly added non-private functions and classes have a docstring including a short summary and a PARAMETERS section
- [ ] If applicable: newly added functions and classes are tested


Problem statement:
Animation.set_default
## Description of proposed feature
One of the useful things in ManimCE for me is set_default method, that can be applied to any subclass of Mobject. Code without duplicates of **mob_kwargs is always nice to look at. What about Animations? I often correct the default values of Create and Uncreate, Write and Unwrite, Indicate, DrawBorderThenFill, Flash and AnimationGroup. Not only run_time, but rate_func, lag_ratio and so on. The values depend on project, that's why editing them in a single Scene is more convinient then editing the source code. Maybe other creators find this feature usefull too.

## How can the new feature be used?
```py
Indicate.set_default(run_time=3, scale_factor=1.5)
Flash.set_default(num_lines=24)
AnimationGroup.set_default(lag_ratio=0.1)
```

## Additional comments
Thank you!

Hints from issue discussion:
Seems good to keep this in mind.

Commit message: test: added missing test

Patch to review:
diff --git a/manim/animation/animation.py b/manim/animation/animation.py
index 042054b7ba..35ab008bbe 100644
--- a/manim/animation/animation.py
+++ b/manim/animation/animation.py
@@ -16,6 +16,7 @@
 
 from collections.abc import Iterable, Sequence
 from copy import deepcopy
+from functools import partialmethod
 from typing import TYPE_CHECKING, Callable
 
 from typing_extensions import Self
@@ -479,6 +480,52 @@ def is_introducer(self) -> bool:
         """
         return self.introducer
 
+    @classmethod
+    def __init_subclass__(cls, **kwargs) -> None:
+        super().__init_subclass__(**kwargs)
+
+        cls._original__init__ = cls.__init__
+
+    @classmethod
+    def set_default(cls, **kwargs) -> None:
+        """Sets the default values of keyword arguments.
+
+        If this method is called without any additional keyword
+        arguments, the original default values of the initialization
+        method of this class are restored.
+
+        Parameters
+        ----------
+
+        kwargs
+            Passing any keyword argument will update the default
+            values of the keyword arguments of the initialization
+            function of this class.
+
+        Examples
+        --------
+
+        .. manim:: ChangeDefaultAnimation
+
+            class ChangeDefaultAnimation(Scene):
+                def construct(self):
+                    Rotate.set_default(run_time=2,rate_func=rate_functions.linear)
+                    Indicate.set_default(color=None)
+
+                    S = Square(color=BLUE,fill_color=BLUE,fill_opacity=0.25)
+                    self.add(S)
+                    self.play(Rotate(S,PI))
+                    self.play(Indicate(S))
+
+                    Rotate.set_default()
+                    Indicate.set_default()
+
+        """
+        if kwargs:
+            cls.__init__ = partialmethod(cls.__init__, **kwargs)
+        else:
+            cls.__init__ = cls._original__init__
+
 
 def prepare_animation(
     anim: Animation | mobject._AnimationBuilder,
diff --git a/tests/test_graphical_units/test_animation.py b/tests/test_graphical_units/test_animation.py
new file mode 100644
index 0000000000..3758e4985b
--- /dev/null
+++ b/tests/test_graphical_units/test_animation.py
@@ -0,0 +1,16 @@
+from manim import *
+from manim.animation.animation import DEFAULT_ANIMATION_RUN_TIME
+
+__module_test__ = "animation"
+
+
+def test_animation_set_default():
+    s = Square()
+    Rotate.set_default(run_time=100)
+    anim = Rotate(s)
+    assert anim.run_time == 100
+    anim = Rotate(s, run_time=5)
+    assert anim.run_time == 5
+    Rotate.set_default()
+    anim = Rotate(s)
+    assert anim.run_time == DEFAULT_ANIMATION_RUN_TIME


Code review comments:
diff_hunk: @@ -479,6 +480,52 @@ def is_introducer(self) -> bool:
         """
         return self.introducer
 
+    @classmethod
+    def __init_subclass__(cls, **kwargs) -> None:
+        super().__init_subclass__(**kwargs)
+
+        cls._original__init__ = cls.__init__
+
+    @classmethod
+    def set_default(cls, **kwargs) -> None:
+        """Sets the default values of keyword arguments.
+
+        If this method is called without any additional keyword
+        arguments, the original default values of the initialization
+        method of this class are restored.
+
+        Parameters
+        ----------
+
+        kwargs
+            Passing any keyword argument will update the default
+            values of the keyword arguments of the initialization
+            function of this class.
+
+        Examples
+        --------
+
+        .. manim:: ChangeDefaultAnimation
+
+            class ChangeDefaultAnimation(Scene):
+                def construct(self):
+                    Rotate.set_default(run_time=2,rate_func=rate_functions.linear)
+                    Indicate.set_default(color=None)
+
+                    S = Square(color=BLUE,fill_color=BLUE,fill_opacity=0.25)
path: manim/animation/animation.py
text: @user1:
```suggestion
                    S = Square(color=BLUE, fill_color=BLUE, fill_opacity=0.25)
```

diff_hunk: @@ -479,6 +480,52 @@ def is_introducer(self) -> bool:
         """
         return self.introducer
 
+    @classmethod
+    def __init_subclass__(cls, **kwargs) -> None:
+        super().__init_subclass__(**kwargs)
+
+        cls._original__init__ = cls.__init__
+
+    @classmethod
+    def set_default(cls, **kwargs) -> None:
+        """Sets the default values of keyword arguments.
+
+        If this method is called without any additional keyword
+        arguments, the original default values of the initialization
+        method of this class are restored.
+
+        Parameters
+        ----------
+
+        kwargs
+            Passing any keyword argument will update the default
+            values of the keyword arguments of the initialization
+            function of this class.
+
+        Examples
+        --------
+
+        .. manim:: ChangeDefaultAnimation
+
+            class ChangeDefaultAnimation(Scene):
+                def construct(self):
+                    Rotate.set_default(run_time=2,rate_func=rate_functions.linear)
+                    Indicate.set_default(color=None)
+
+                    S = Square(color=BLUE,fill_color=BLUE,fill_opacity=0.25)
+                    self.add(S)
+                    self.play(Rotate(S,PI))
path: manim/animation/animation.py
text: @user1:
```suggestion
                    self.play(Rotate(S, PI))
```

diff_hunk: @@ -479,6 +480,52 @@ def is_introducer(self) -> bool:
         """
         return self.introducer
 
+    @classmethod
+    def __init_subclass__(cls, **kwargs) -> None:
+        super().__init_subclass__(**kwargs)
+
+        cls._original__init__ = cls.__init__
+
+    @classmethod
+    def set_default(cls, **kwargs) -> None:
+        """Sets the default values of keyword arguments.
+
+        If this method is called without any additional keyword
+        arguments, the original default values of the initialization
+        method of this class are restored.
+
+        Parameters
+        ----------
+
+        kwargs
+            Passing any keyword argument will update the default
+            values of the keyword arguments of the initialization
+            function of this class.
+
+        Examples
+        --------
+
+        .. manim:: ChangeDefaultAnimation
+
+            class ChangeDefaultAnimation(Scene):
+                def construct(self):
+                    Rotate.set_default(run_time=2,rate_func=rate_functions.linear)
path: manim/animation/animation.py
text: @user1:
```suggestion
                    Rotate.set_default(run_time=2, rate_func=rate_functions.linear)
```