Repository: ManimCommunity/manim

Issue:
OpenGLMobject sometimes adds duplicate children
## Description of bug / unexpected behavior
<!-- Add a clear and concise description of the problem you encountered. -->

While migrating cairo tests the below test was failing as it had one more family member than its `mobject` counterpart

```py
class Tests(Scene):
    def construct(self):
        mob, child1, child2, = (
            OpenGLMobject(),
            OpenGLMobject(),
            OpenGLMobject(),
        )
        gchild1, gchild2, gchild_common = OpenGLMobject(), OpenGLMobject(), OpenGLMobject()
        child1.add(gchild1, gchild_common)
        child2.add(gchild2, gchild_common)
        mob.add(child1, child2)
        family = mob.get_family()
        assert mob in family
        assert len(family) == 6
        assert family.count(gchild_common) == 1
```

It seems that when adding objects it isn't taking into consideration common children of its children as the above results in 7 family members.

`mob.get_family().count(gchild_common)` is equal to 2 in this case. 

## Expected behavior
<!-- Add a clear and concise description of what you expected to happen. -->

I would expect the same behaviour as cairo as I don't know of any reasons family logic should be different in opengl

## How to reproduce the issue
<!-- Provide a piece of code illustrating the undesired behavior. -->

Run below code and assertions will fail as it has an extra family member

<details><summary>Code for reproducing the problem</summary>

```py
class Tests(Scene):
    def construct(self):
        mob, child1, child2, = (
            OpenGLMobject(),
            OpenGLMobject(),
            OpenGLMobject(),
        )
        gchild1, gchild2, gchild_common = OpenGLMobject(), OpenGLMobject(), OpenGLMobject()
        child1.add(gchild1, gchild_common)
        child2.add(gchild2, gchild_common)
        mob.add(child1, child2)
        family = mob.get_family()
        assert mob in family
        assert len(family) == 6
        assert family.count(gchild_common) == 1
```

</details>


## Additional media files
<!-- Paste in the files manim produced on rendering the code above. -->

<details><summary>Images/GIFs</summary>

<!-- PASTE MEDIA HERE -->

</details>


## Logs
<details><summary>Terminal output</summary>
<!-- Add "-v DEBUG" when calling manim to generate more detailed logs -->

```
PASTE HERE OR PROVIDE LINK TO https://pastebin.com/ OR SIMILAR
```

<!-- Insert screenshots here (only when absolutely necessary, we prefer copy/pasted output!) -->

</details>


## System specifications

<details><summary>System Details</summary>

- OS (with version, e.g Windows 10 v2004 or macOS 10.15 (Catalina)):
- RAM:
- Python version (`python/py/python3 --version`):
- Installed modules (provide output from `pip list`):
```
PASTE HERE
```
</details>

<details><summary>LaTeX details</summary>

+ LaTeX distribution (e.g. TeX Live 2020):
+ Installed LaTeX packages:
<!-- output of `tlmgr list --only-installed` for TeX Live or a screenshot of the Packages page for MikTeX -->
</details>

<details><summary>FFMPEG</summary>

Output of `ffmpeg -version`:

```
PASTE HERE
```
</details>

## Additional comments
<!-- Add further context that you think might be relevant for this issue here. -->



Discussion:

Resolving patch:
diff --git a/manim/mobject/opengl_mobject.py b/manim/mobject/opengl_mobject.py
--- a/manim/mobject/opengl_mobject.py
+++ b/manim/mobject/opengl_mobject.py
@@ -26,6 +26,7 @@
     resize_array,
     resize_preserving_order,
     resize_with_interpolation,
+    uniq_chain,
 )
 from ..utils.paths import straight_path
 from ..utils.simple_functions import get_parameters
@@ -412,7 +413,7 @@ def split(self):
 
     def assemble_family(self):
         sub_families = (sm.get_family() for sm in self.submobjects)
-        self.family = [self, *it.chain(*sub_families)]
+        self.family = [self, *uniq_chain(*sub_families)]
         self.refresh_has_updater_status()
         self.refresh_bounding_box()
         for parent in self.parents:
diff --git a/manim/utils/iterables.py b/manim/utils/iterables.py
--- a/manim/utils/iterables.py
+++ b/manim/utils/iterables.py
@@ -171,3 +171,12 @@ def remove_nones(sequence):
 
 def concatenate_lists(*list_of_lists):
     return [item for lst in list_of_lists for item in lst]
+
+
+def uniq_chain(*args):
+    unique_items = set()
+    for x in it.chain(*args):
+        if x in unique_items:
+            continue
+        unique_items.add(x)
+        yield x


Test patch:
diff --git a/tests/opengl/test_family_opengl.py b/tests/opengl/test_family_opengl.py
new file mode 100644
--- /dev/null
+++ b/tests/opengl/test_family_opengl.py
@@ -0,0 +1,83 @@
+import numpy as np
+
+from manim import RIGHT, Circle
+from manim.mobject.opengl_mobject import OpenGLMobject
+
+
+def test_family(using_opengl_renderer):
+    """Check that the family is gathered correctly."""
+    # Check that an empty OpenGLMobject's family only contains itself
+    mob = OpenGLMobject()
+    assert mob.get_family() == [mob]
+
+    # Check that all children are in the family
+    mob = OpenGLMobject()
+    children = [OpenGLMobject() for _ in range(10)]
+    mob.add(*children)
+    family = mob.get_family()
+    assert len(family) == 1 + 10
+    assert mob in family
+    for c in children:
+        assert c in family
+
+    # Nested children should be in the family
+    mob = OpenGLMobject()
+    grandchildren = {}
+    for _ in range(10):
+        child = OpenGLMobject()
+        grandchildren[child] = [OpenGLMobject() for _ in range(10)]
+        child.add(*grandchildren[child])
+    mob.add(*list(grandchildren.keys()))
+    family = mob.get_family()
+    assert len(family) == 1 + 10 + 10 * 10
+    assert mob in family
+    for c in grandchildren:
+        assert c in family
+        for gc in grandchildren[c]:
+            assert gc in family
+
+
+def test_overlapping_family(using_opengl_renderer):
+    """Check that each member of the family is only gathered once."""
+    mob, child1, child2, = (
+        OpenGLMobject(),
+        OpenGLMobject(),
+        OpenGLMobject(),
+    )
+    gchild1, gchild2, gchild_common = OpenGLMobject(), OpenGLMobject(), OpenGLMobject()
+    child1.add(gchild1, gchild_common)
+    child2.add(gchild2, gchild_common)
+    mob.add(child1, child2)
+    family = mob.get_family()
+    assert mob in family
+    assert len(family) == 6
+    assert family.count(gchild_common) == 1
+
+
+def test_shift_family(using_opengl_renderer):
+    """Check that each member of the family is shifted along with the parent.
+
+    Importantly, here we add a common grandchild to each of the children.  So
+    this test will fail if the grandchild moves twice as much as it should.
+
+    """
+    # Note shift() needs the OpenGLMobject to have a non-empty `points` attribute, so
+    # we cannot use a plain OpenGLMobject or OpenGLVMobject.  We use Circle instead.
+    mob, child1, child2, = (
+        Circle(),
+        Circle(),
+        Circle(),
+    )
+    gchild1, gchild2, gchild_common = Circle(), Circle(), Circle()
+
+    child1.add(gchild1, gchild_common)
+    child2.add(gchild2, gchild_common)
+    mob.add(child1, child2)
+    family = mob.get_family()
+
+    positions_before = {m: m.get_center().copy() for m in family}
+    mob.shift(RIGHT)
+    positions_after = {m: m.get_center().copy() for m in family}
+
+    for m in family:
+        assert np.allclose(positions_before[m] + RIGHT, positions_after[m])