Repository: ManimCommunity/manim
Pull request: Fixed using :class:`~.Animation` without a child :class:`~.Mobject` in :class:`~.AnimationGroup`
<!--
Thanks for your contribution to ManimCommunity!

Before filling in the details, ensure:
- Your local changes are up-to-date with ManimCommunity/manim
  
- The title of your PR gives a descriptive summary to end-users. Some examples:
  - Fixed last animations not running to completion
  - Added gradient support and documentation for SVG files
  Examples of what *NOT* to do:
  - "fixed that styling issue" - not descriptive enough
  - "fixed issue #XYZ" - end-user needs to do further research
-->

## Motivation
<!-- Outline your motivation: In what way do your changes improve the library? -->
<!--changelog-start-->
`AnimationGroup` may now take `Animation` objects which do not have a child `Mobject`, such as `Wait`.
<!--changelog-end-->

Closes #1153 

## Overview / Explanation for Changes
<!-- Give an overview of your changes and explain how they
resolve the situation described in the previous section.

For PRs introducing new features, please provide code snippets
using the newly introduced functionality and ideally even the
expected rendered output. -->
I've added a check to the `AnimationGroup` initialization which, when creating the `Mobject` group, ignores animations that don't have child Mobjects.

## Testing Status
<!-- Optional (but recommended): your computer specs and
what tests you ran with their results, if any. This section
is also intended for other testing-related comments. -->

All tests pass on my local machine.

## Further Comments
<!-- Optional, any further comments regarding your PR
that might be useful for reviewers.. -->

I'm not particularly familiar with everything that `self.group` is used for in `AnimationGroup`, so there's a chance that I broke something inadvertently, but in my testing, everything worked as I expected.

## Acknowledgements
- [x] I have read the [Contributing Guidelines](https://docs.manim.community/en/latest/contributing.html)
- [x] I have chosen a descriptive PR title (see top of PR template for examples)
<!-- Once again, thanks for helping out by contributing to manim! -->


<!-- Do not modify the lines below. -->
## Reviewer Checklist
- [ ] Newly added functions/classes are either private or have a docstring
- [ ] Newly added functions/classes have [tests](https://github.com/ManimCommunity/manim/wiki/Testing) added and (optional) examples in the docs
- [ ] Newly added documentation builds, looks correctly formatted, and adds no additional build warnings
- [ ] The PR title is descriptive enough


Problem statement:
The Wait animation cannot be added to an AnimationGroup
An AnimationGroup expects each animation to have a mobject attribute.  The Wait animation does not have a mobject.

Steps to reproduce:
Compile:
```
class animgrouptest(Scene):
  def construct(self):
    anim = AnimationGroup(Write(Text("a")), Wait(3))
    self.play(anim)
```
Result:
```
  File "/manim/script.py", line 4, in construct
    anim = AnimationGroup(Write(Text("a")), Wait(3))
  File "/usr/local/lib/python3.8/site-packages/manim/animation/composition.py", line 35, in __init__
    self.group = Group(
  File "/usr/local/lib/python3.8/site-packages/manim/mobject/mobject.py", line 1628, in __init__
    self.add(*mobjects)
  File "/usr/local/lib/python3.8/site-packages/manim/mobject/mobject.py", line 198, in add
    raise TypeError("All submobjects must be of type Mobject")
TypeError: All submobjects must be of type Mobject
```

Either we give the Wait animation a dummy (empty) mobject, or we fix the AnimationGroup? I'm not really sure I understand the underlying logic.

Hints from issue discussion:

Commit message: black

Patch to review:
diff --git a/manim/animation/animation.py b/manim/animation/animation.py
index aeb16335a5..ad4edb0b75 100644
--- a/manim/animation/animation.py
+++ b/manim/animation/animation.py
@@ -196,6 +196,13 @@ def set_name(self, name: str) -> "Animation":
     def is_remover(self) -> bool:
         return self.remover
 
+    def is_dummy(self) -> bool:
+        return animation_is_dummy(self)
+
+
+def animation_is_dummy(anim: Union["Animation", "mobject._AnimationBuilder"]) -> bool:
+    return True if anim.mobject is None else False
+
 
 def prepare_animation(
     anim: Union["Animation", "mobject._AnimationBuilder"]
diff --git a/manim/animation/composition.py b/manim/animation/composition.py
index 1cffe31490..5f5ec8a3cd 100644
--- a/manim/animation/composition.py
+++ b/manim/animation/composition.py
@@ -33,7 +33,9 @@ def __init__(
         self.group = group
         if self.group is None:
             self.group = Group(
-                *remove_list_redundancies([anim.mobject for anim in animations])
+                *remove_list_redundancies(
+                    [anim.mobject for anim in animations if not anim.is_dummy()]
+                )
             )
         super().__init__(self.group, rate_func=rate_func, lag_ratio=lag_ratio, **kwargs)
         self.run_time = run_time
diff --git a/manim/mobject/mobject.py b/manim/mobject/mobject.py
index 639b2487e1..3c04b8fa03 100644
--- a/manim/mobject/mobject.py
+++ b/manim/mobject/mobject.py
@@ -2809,6 +2809,11 @@ def build(self):
 
         return anim
 
+    def is_dummy(self):
+        from ..animation.animation import animation_is_dummy
+
+        return animation_is_dummy(self)
+
 
 def override_animate(method):
     r"""Decorator for overriding method animations.
diff --git a/tests/test_composition.py b/tests/test_composition.py
index 4b61f1175a..0e6e7781e2 100644
--- a/tests/test_composition.py
+++ b/tests/test_composition.py
@@ -1,4 +1,4 @@
-from manim.animation.animation import Animation
+from manim.animation.animation import Animation, Wait
 from manim.animation.composition import AnimationGroup, Succession
 from manim.animation.fading import FadeIn, FadeInFrom, FadeOutAndShift
 from manim.constants import DOWN
@@ -85,3 +85,15 @@ def test_animationbuilder_in_group():
     assert all(isinstance(anim, Animation) for anim in animation_group.animations)
     succession = Succession(sqr.animate.shift(DOWN).scale(2), FadeIn(circ))
     assert all(isinstance(anim, Animation) for anim in succession.animations)
+
+
+def test_animationgroup_with_wait():
+    sqr = Square()
+    sqr_anim = FadeIn(sqr)
+    wait = Wait()
+    animation_group = AnimationGroup(wait, sqr_anim, lag_ratio=1)
+
+    animation_group.begin()
+    timings = animation_group.anims_with_timings
+
+    assert timings == [(wait, 0.0, 1.0), (sqr_anim, 1.0, 2.0)]


Code review comments:
diff_hunk: @@ -2809,6 +2809,11 @@ def build(self):
 
         return anim
 
+    def is_dummy(self):
+        from ..animation.animation import animation_is_dummy
+
+        return animation_is_dummy(self)
+
path: manim/mobject/mobject.py
text: @user1:
```suggestion
```

diff_hunk: @@ -33,7 +33,9 @@ def __init__(
         self.group = group
         if self.group is None:
             self.group = Group(
-                *remove_list_redundancies([anim.mobject for anim in animations])
+                *remove_list_redundancies(
+                    [anim.mobject for anim in animations if not anim.is_dummy()]
path: manim/animation/composition.py
text: @user1:
```suggestion
                    [anim.mobject for anim in self.animations if not anim.is_dummy()]
```