Skip to content

Commit 909ffde

Browse files
MrDiverpre-commit-ci[bot]behackl
authored
Fixing the behavior of .become to not modify target mobject via side effects fix color linking (#3508)
* Copied ndarray for rgbas when interpolating * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * changing .become to copy the target mobject * change tests and test data to reflect .become new behavior * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update tests/test_graphical_units/test_mobjects.py * removed unused copy_submobject kwarg * added doctests and improved documentation * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Benjamin Hackl <[email protected]>
1 parent 7a794e3 commit 909ffde

File tree

5 files changed

+87
-15
lines changed

5 files changed

+87
-15
lines changed

manim/mobject/mobject.py

Lines changed: 66 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2761,7 +2761,6 @@ def interpolate_color(self, mobject1: Mobject, mobject2: Mobject, alpha: float):
27612761
def become(
27622762
self,
27632763
mobject: Mobject,
2764-
copy_submobjects: bool = True,
27652764
match_height: bool = False,
27662765
match_width: bool = False,
27672766
match_depth: bool = False,
@@ -2774,20 +2773,25 @@ def become(
27742773
.. note::
27752774
27762775
If both match_height and match_width are ``True`` then the transformed :class:`~.Mobject`
2777-
will match the height first and then the width
2776+
will match the height first and then the width.
27782777
27792778
Parameters
27802779
----------
27812780
match_height
2782-
If ``True``, then the transformed :class:`~.Mobject` will match the height of the original
2781+
Whether or not to preserve the height of the original
2782+
:class:`~.Mobject`.
27832783
match_width
2784-
If ``True``, then the transformed :class:`~.Mobject` will match the width of the original
2784+
Whether or not to preserve the width of the original
2785+
:class:`~.Mobject`.
27852786
match_depth
2786-
If ``True``, then the transformed :class:`~.Mobject` will match the depth of the original
2787+
Whether or not to preserve the depth of the original
2788+
:class:`~.Mobject`.
27872789
match_center
2788-
If ``True``, then the transformed :class:`~.Mobject` will match the center of the original
2790+
Whether or not to preserve the center of the original
2791+
:class:`~.Mobject`.
27892792
stretch
2790-
If ``True``, then the transformed :class:`~.Mobject` will stretch to fit the proportions of the original
2793+
Whether or not to stretch the target mobject to match the
2794+
the proportions of the original :class:`~.Mobject`.
27912795
27922796
Examples
27932797
--------
@@ -2801,8 +2805,62 @@ def construct(self):
28012805
self.wait(0.5)
28022806
circ.become(square)
28032807
self.wait(0.5)
2804-
"""
28052808
2809+
2810+
The following examples illustrate how mobject measurements
2811+
change when using the ``match_...`` and ``stretch`` arguments.
2812+
We start with a rectangle that is 2 units high and 4 units wide,
2813+
which we want to turn into a circle of radius 3::
2814+
2815+
>>> from manim import Rectangle, Circle
2816+
>>> import numpy as np
2817+
>>> rect = Rectangle(height=2, width=4)
2818+
>>> circ = Circle(radius=3)
2819+
2820+
With ``stretch=True``, the target circle is deformed to match
2821+
the proportions of the rectangle, which results in the target
2822+
mobject being an ellipse with height 2 and width 4. We can
2823+
check that the resulting points satisfy the ellipse equation
2824+
:math:`x^2/a^2 + y^2/b^2 = 1` with :math:`a = 4/2` and :math:`b = 2/2`
2825+
being the semi-axes::
2826+
2827+
>>> result = rect.copy().become(circ, stretch=True)
2828+
>>> result.height, result.width
2829+
(2.0, 4.0)
2830+
>>> ellipse_eq = np.sum(result.get_anchors()**2 * [1/4, 1, 0], axis=1)
2831+
>>> np.allclose(ellipse_eq, 1)
2832+
True
2833+
2834+
With ``match_height=True`` and ``match_width=True`` the circle is
2835+
scaled such that the height or the width of the rectangle will
2836+
be preserved, respectively.
2837+
The points of the resulting mobject satisfy the circle equation
2838+
:math:`x^2 + y^2 = r^2` for the corresponding radius :math:`r`::
2839+
2840+
>>> result = rect.copy().become(circ, match_height=True)
2841+
>>> result.height, result.width
2842+
(2.0, 2.0)
2843+
>>> circle_eq = np.sum(result.get_anchors()**2, axis=1)
2844+
>>> np.allclose(circle_eq, 1)
2845+
True
2846+
>>> result = rect.copy().become(circ, match_width=True)
2847+
>>> result.height, result.width
2848+
(4.0, 4.0)
2849+
>>> circle_eq = np.sum(result.get_anchors()**2, axis=1)
2850+
>>> np.allclose(circle_eq, 2**2)
2851+
True
2852+
2853+
With ``match_center=True``, the resulting mobject is moved such that
2854+
its center is the same as the center of the original mobject::
2855+
2856+
>>> rect = rect.shift(np.array([0, 1, 0]))
2857+
>>> np.allclose(rect.get_center(), circ.get_center())
2858+
False
2859+
>>> result = rect.copy().become(circ, match_center=True)
2860+
>>> np.allclose(rect.get_center(), result.get_center())
2861+
True
2862+
"""
2863+
mobject = mobject.copy()
28062864
if stretch:
28072865
mobject.stretch_to_fit_height(self.height)
28082866
mobject.stretch_to_fit_width(self.width)

manim/mobject/types/vectorized_mobject.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,7 @@ def set_stroke(
352352
setattr(self, opacity_name, opacity)
353353
if color is not None and background:
354354
if isinstance(color, (list, tuple)):
355-
self.background_stroke_color = color
355+
self.background_stroke_color = ManimColor.parse(color)
356356
else:
357357
self.background_stroke_color = ManimColor(color)
358358
return self
@@ -1738,7 +1738,10 @@ def interpolate_color(
17381738
interpolate(getattr(mobject1, attr), getattr(mobject2, attr), alpha),
17391739
)
17401740
if alpha == 1.0:
1741-
setattr(self, attr, getattr(mobject2, attr))
1741+
val = getattr(mobject2, attr)
1742+
if isinstance(val, np.ndarray):
1743+
val = val.copy()
1744+
setattr(self, attr, val)
17421745

17431746
def pointwise_become_partial(
17441747
self,
Binary file not shown.
Binary file not shown.

tests/test_graphical_units/test_mobjects.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,29 @@ def test_PointCloudDot(scene):
1515
@frames_comparison
1616
def test_become(scene):
1717
s = Rectangle(width=2, height=1, color=RED).shift(UP)
18-
d1, d2, d3 = (Dot() for _ in range(3))
18+
d = Dot()
1919

20-
s1 = s.copy().become(d1, match_width=True).set_opacity(0.25).set_color(BLUE)
20+
s1 = s.copy().become(d, match_width=True).set_opacity(0.25).set_color(BLUE)
2121
s2 = (
2222
s.copy()
23-
.become(d2, match_height=True, match_center=True)
23+
.become(d, match_height=True, match_center=True)
2424
.set_opacity(0.25)
2525
.set_color(GREEN)
2626
)
27-
s3 = s.copy().become(d3, stretch=True).set_opacity(0.25).set_color(YELLOW)
27+
s3 = s.copy().become(d, stretch=True).set_opacity(0.25).set_color(YELLOW)
2828

29-
scene.add(s, d1, d2, d3, s1, s2, s3)
29+
scene.add(s, d, s1, s2, s3)
30+
31+
32+
@frames_comparison
33+
def test_become_no_color_linking(scene):
34+
a = Circle()
35+
b = Square()
36+
scene.add(a)
37+
scene.add(b)
38+
b.become(a)
39+
b.shift(1 * RIGHT)
40+
b.set_stroke(YELLOW, opacity=1)
3041

3142

3243
@frames_comparison

0 commit comments

Comments
 (0)