Repository: ManimCommunity/manim

Issue:
After `become()`, colors of the two VMobjects are linked
## Description of bug / unexpected behavior

After calling `a.become(b)`, where `a` and `b` are both `VMobjects`, their colors become linked. Changing color of `a` (e.g. with `set_stroke` also changes color of `b` and vice versa.

## Expected behavior
<!-- Add a clear and concise description of what you expected to happen. -->

After `become()` call, the two Mobjects should be independent and should change colors independently.

## How to reproduce the issue
<!-- Provide a piece of code illustrating the undesired behavior. -->

<details><summary>Code for reproducing the problem</summary>

```py

from manim import *

class PromoScene(Scene):
    def construct(self):
        a = Circle()
        b = Square()
        self.add(a)
        self.add(b)
        b.become(a)
        b.shift(1*RIGHT)
        b.set_stroke(YELLOW, opacity=1)
        self.wait(1)
```


</details>


## Additional media files
<!-- Paste in the files manim produced on rendering the code above. -->

<details><summary>Images/GIFs</summary>
Result I get:

![got](https://github.com/ManimCommunity/manim/assets/1253021/8decf520-d3ce-4103-9866-8e4be01b2879)

Expected:

![expected](https://github.com/ManimCommunity/manim/assets/1253021/1e2a2729-803d-4442-85ae-29a4d5e31e63)


<!-- PASTE MEDIA HERE -->

</details>



Discussion:
This seems to fix the issue:

```diff
diff '--color=auto' -rubp manim.orig/mobject/types/vectorized_mobject.py manim/mobject/types/vectorized_mobject.py
--- manim.orig/mobject/types/vectorized_mobject.py	2023-12-07 18:55:34.234530207 +0100
+++ manim/mobject/types/vectorized_mobject.py	2023-12-07 18:55:39.241205514 +0100
@@ -1692,7 +1692,7 @@ class VMobject(Mobject):
                 interpolate(getattr(mobject1, attr), getattr(mobject2, attr), alpha),
             )
             if alpha == 1.0:
-                setattr(self, attr, getattr(mobject2, attr))
+                setattr(self, attr, copy.deepcopy(getattr(mobject2, attr)))
 
     def pointwise_become_partial(
         self,
```

Resolving patch:
diff --git a/manim/mobject/mobject.py b/manim/mobject/mobject.py
--- a/manim/mobject/mobject.py
+++ b/manim/mobject/mobject.py
@@ -2761,7 +2761,6 @@ def interpolate_color(self, mobject1: Mobject, mobject2: Mobject, alpha: float):
     def become(
         self,
         mobject: Mobject,
-        copy_submobjects: bool = True,
         match_height: bool = False,
         match_width: bool = False,
         match_depth: bool = False,
@@ -2774,20 +2773,25 @@ def become(
         .. note::
 
             If both match_height and match_width are ``True`` then the transformed :class:`~.Mobject`
-            will match the height first and then the width
+            will match the height first and then the width.
 
         Parameters
         ----------
         match_height
-            If ``True``, then the transformed :class:`~.Mobject` will match the height of the original
+            Whether or not to preserve the height of the original
+            :class:`~.Mobject`.
         match_width
-            If ``True``, then the transformed :class:`~.Mobject` will match the width of the original
+            Whether or not to preserve the width of the original
+            :class:`~.Mobject`.
         match_depth
-            If ``True``, then the transformed :class:`~.Mobject` will match the depth of the original
+            Whether or not to preserve the depth of the original
+            :class:`~.Mobject`.
         match_center
-            If ``True``, then the transformed :class:`~.Mobject` will match the center of the original
+            Whether or not to preserve the center of the original
+            :class:`~.Mobject`.
         stretch
-            If ``True``, then the transformed :class:`~.Mobject` will stretch to fit the proportions of the original
+            Whether or not to stretch the target mobject to match the
+            the proportions of the original :class:`~.Mobject`.
 
         Examples
         --------
@@ -2801,8 +2805,62 @@ def construct(self):
                     self.wait(0.5)
                     circ.become(square)
                     self.wait(0.5)
-        """
 
+
+        The following examples illustrate how mobject measurements
+        change when using the ``match_...`` and ``stretch`` arguments.
+        We start with a rectangle that is 2 units high and 4 units wide,
+        which we want to turn into a circle of radius 3::
+
+            >>> from manim import Rectangle, Circle
+            >>> import numpy as np
+            >>> rect = Rectangle(height=2, width=4)
+            >>> circ = Circle(radius=3)
+
+        With ``stretch=True``, the target circle is deformed to match
+        the proportions of the rectangle, which results in the target
+        mobject being an ellipse with height 2 and width 4. We can
+        check that the resulting points satisfy the ellipse equation
+        :math:`x^2/a^2 + y^2/b^2 = 1` with :math:`a = 4/2` and :math:`b = 2/2`
+        being the semi-axes::
+
+            >>> result = rect.copy().become(circ, stretch=True)
+            >>> result.height, result.width
+            (2.0, 4.0)
+            >>> ellipse_eq = np.sum(result.get_anchors()**2 * [1/4, 1, 0], axis=1)
+            >>> np.allclose(ellipse_eq, 1)
+            True
+
+        With ``match_height=True`` and ``match_width=True`` the circle is
+        scaled such that the height or the width of the rectangle will
+        be preserved, respectively.
+        The points of the resulting mobject satisfy the circle equation
+        :math:`x^2 + y^2 = r^2` for the corresponding radius :math:`r`::
+
+            >>> result = rect.copy().become(circ, match_height=True)
+            >>> result.height, result.width
+            (2.0, 2.0)
+            >>> circle_eq = np.sum(result.get_anchors()**2, axis=1)
+            >>> np.allclose(circle_eq, 1)
+            True
+            >>> result = rect.copy().become(circ, match_width=True)
+            >>> result.height, result.width
+            (4.0, 4.0)
+            >>> circle_eq = np.sum(result.get_anchors()**2, axis=1)
+            >>> np.allclose(circle_eq, 2**2)
+            True
+
+        With ``match_center=True``, the resulting mobject is moved such that
+        its center is the same as the center of the original mobject::
+
+            >>> rect = rect.shift(np.array([0, 1, 0]))
+            >>> np.allclose(rect.get_center(), circ.get_center())
+            False
+            >>> result = rect.copy().become(circ, match_center=True)
+            >>> np.allclose(rect.get_center(), result.get_center())
+            True
+        """
+        mobject = mobject.copy()
         if stretch:
             mobject.stretch_to_fit_height(self.height)
             mobject.stretch_to_fit_width(self.width)
diff --git a/manim/mobject/types/vectorized_mobject.py b/manim/mobject/types/vectorized_mobject.py
--- a/manim/mobject/types/vectorized_mobject.py
+++ b/manim/mobject/types/vectorized_mobject.py
@@ -352,7 +352,7 @@ def set_stroke(
             setattr(self, opacity_name, opacity)
         if color is not None and background:
             if isinstance(color, (list, tuple)):
-                self.background_stroke_color = color
+                self.background_stroke_color = ManimColor.parse(color)
             else:
                 self.background_stroke_color = ManimColor(color)
         return self
@@ -1738,7 +1738,10 @@ def interpolate_color(
                 interpolate(getattr(mobject1, attr), getattr(mobject2, attr), alpha),
             )
             if alpha == 1.0:
-                setattr(self, attr, getattr(mobject2, attr))
+                val = getattr(mobject2, attr)
+                if isinstance(val, np.ndarray):
+                    val = val.copy()
+                setattr(self, attr, val)
 
     def pointwise_become_partial(
         self,


Test patch:
diff --git a/tests/test_graphical_units/control_data/mobjects/become.npz b/tests/test_graphical_units/control_data/mobjects/become.npz
Binary files a/tests/test_graphical_units/control_data/mobjects/become.npz and b/tests/test_graphical_units/control_data/mobjects/become.npz differ
diff --git a/tests/test_graphical_units/control_data/mobjects/become_no_color_linking.npz b/tests/test_graphical_units/control_data/mobjects/become_no_color_linking.npz
new file mode 100644
Binary files /dev/null and b/tests/test_graphical_units/control_data/mobjects/become_no_color_linking.npz differ
diff --git a/tests/test_graphical_units/test_mobjects.py b/tests/test_graphical_units/test_mobjects.py
--- a/tests/test_graphical_units/test_mobjects.py
+++ b/tests/test_graphical_units/test_mobjects.py
@@ -15,18 +15,29 @@ def test_PointCloudDot(scene):
 @frames_comparison
 def test_become(scene):
     s = Rectangle(width=2, height=1, color=RED).shift(UP)
-    d1, d2, d3 = (Dot() for _ in range(3))
+    d = Dot()
 
-    s1 = s.copy().become(d1, match_width=True).set_opacity(0.25).set_color(BLUE)
+    s1 = s.copy().become(d, match_width=True).set_opacity(0.25).set_color(BLUE)
     s2 = (
         s.copy()
-        .become(d2, match_height=True, match_center=True)
+        .become(d, match_height=True, match_center=True)
         .set_opacity(0.25)
         .set_color(GREEN)
     )
-    s3 = s.copy().become(d3, stretch=True).set_opacity(0.25).set_color(YELLOW)
+    s3 = s.copy().become(d, stretch=True).set_opacity(0.25).set_color(YELLOW)
 
-    scene.add(s, d1, d2, d3, s1, s2, s3)
+    scene.add(s, d, s1, s2, s3)
+
+
+@frames_comparison
+def test_become_no_color_linking(scene):
+    a = Circle()
+    b = Square()
+    scene.add(a)
+    scene.add(b)
+    b.become(a)
+    b.shift(1 * RIGHT)
+    b.set_stroke(YELLOW, opacity=1)
 
 
 @frames_comparison