diff --git a/manim/scene/scene.py b/manim/scene/scene.py index fc3d3ede54..c45fd04a73 100644 --- a/manim/scene/scene.py +++ b/manim/scene/scene.py @@ -553,6 +553,10 @@ def replace(self, old_mobject: Mobject, new_mobject: Mobject) -> None: def replace_in_list( mobj_list: list[Mobject], old_m: Mobject, new_m: Mobject ) -> bool: + # Avoid duplicate references to the same object in self.mobjects + if new_m in mobj_list: + mobj_list.remove(new_m) + # We use breadth-first search because some Mobjects get very deep and # we expect top-level elements to be the most common targets for replace. for i in range(0, len(mobj_list)): diff --git a/tests/module/animation/test_transform.py b/tests/module/animation/test_transform.py new file mode 100644 index 0000000000..d1b9c43aef --- /dev/null +++ b/tests/module/animation/test_transform.py @@ -0,0 +1,27 @@ +from __future__ import annotations + +from manim import Circle, ReplacementTransform, Scene, Square, VGroup + + +def test_no_duplicate_references(): + scene = Scene() + c = Circle() + sq = Square() + scene.add(c, sq) + + scene.play(ReplacementTransform(c, sq)) + assert len(scene.mobjects) == 1 + assert scene.mobjects[0] is sq + + +def test_duplicate_references_in_group(): + scene = Scene() + c = Circle() + sq = Square() + vg = VGroup(c, sq) + scene.add(vg) + + scene.play(ReplacementTransform(c, sq)) + submobs = vg.submobjects + assert len(submobs) == 1 + assert submobs[0] is sq