Repository: ManimCommunity/manim

Issue:
Chaining a combination of default and overwritten method animations is not prohibited in all cases
This change silently animates animations like `obj.animate.normal_method().overridden_method()` incorrectly.

```python
class Chaining(Scene):
    def construct(self):
        class DotsWithLine(VGroup):
            def __init__(self):
                super().__init__()
                self.left_dot = Dot().shift((-1, 0, 0))
                self.right_dot = Dot().shift((1, 0, 0))
                self.line = Line(self.left_dot, self.right_dot)
                self.add(self.left_dot, self.right_dot, self.line)

            def remove_line(self):
                self.remove(self.line)

            @override_animate(remove_line)
            def _remove_line_animation(self):
                self.remove_line()
                return Uncreate(self.line)

        x = DotsWithLine()
        self.play(x.animate.shift(RIGHT).remove_line())
```

https://user-images.githubusercontent.com/43117506/105646897-57045700-5e57-11eb-8ccc-8cfdd42255f7.mp4

_Originally posted by @eulertour in https://github.com/ManimCommunity/manim/issues/917#issuecomment-766457962_


Discussion:

Resolving patch:
diff --git a/manim/mobject/mobject.py b/manim/mobject/mobject.py
--- a/manim/mobject/mobject.py
+++ b/manim/mobject/mobject.py
@@ -1351,18 +1351,20 @@ def __init__(self, mobject):
         self.mobject = mobject
         self.overridden_animation = None
         self.mobject.generate_target()
+        self.is_chaining = False
 
     def __getattr__(self, method_name):
         method = getattr(self.mobject.target, method_name)
+        has_overridden_animation = hasattr(method, "_override_animate")
 
-        if self.overridden_animation:
+        if (self.is_chaining and has_overridden_animation) or self.overridden_animation:
             raise NotImplementedError(
                 "Method chaining is currently not supported for "
                 "overridden animations"
             )
 
         def update_target(*method_args, **method_kwargs):
-            if hasattr(method, "_override_animate"):
+            if has_overridden_animation:
                 self.overridden_animation = method._override_animate(
                     self.mobject, *method_args, **method_kwargs
                 )
@@ -1370,6 +1372,7 @@ def update_target(*method_args, **method_kwargs):
                 method(*method_args, **method_kwargs)
             return self
 
+        self.is_chaining = True
         return update_target
 
     def build(self):


Test patch:
diff --git a/tests/test_animate.py b/tests/test_animate.py
--- a/tests/test_animate.py
+++ b/tests/test_animate.py
@@ -1,4 +1,5 @@
 import numpy as np
+import pytest
 
 from manim.mobject.mobject import override_animate
 from manim.mobject.types.vectorized_mobject import VGroup
@@ -45,3 +46,31 @@ def _remove_line_animation(self):
     anim = dots_with_line.animate.remove_line().build()
     assert len(dots_with_line.submobjects) == 2
     assert type(anim) is Uncreate
+
+
+def test_chaining_overridden_animate():
+    class DotsWithLine(VGroup):
+        def __init__(self):
+            super().__init__()
+            self.left_dot = Dot().shift((-1, 0, 0))
+            self.right_dot = Dot().shift((1, 0, 0))
+            self.line = Line(self.left_dot, self.right_dot)
+            self.add(self.left_dot, self.right_dot, self.line)
+
+        def remove_line(self):
+            self.remove(self.line)
+
+        @override_animate(remove_line)
+        def _remove_line_animation(self):
+            self.remove_line()
+            return Uncreate(self.line)
+
+    with pytest.raises(
+        NotImplementedError, match="not supported for overridden animations"
+    ):
+        DotsWithLine().animate.shift((1, 0, 0)).remove_line()
+
+    with pytest.raises(
+        NotImplementedError, match="not supported for overridden animations"
+    ):
+        DotsWithLine().animate.remove_line().shift((1, 0, 0))