Skip to content

Commit 744362a

Browse files
feat: implement __add__, __iadd__, __sub__, and __isub__ for Mobject
Co-authored-by: Leo Torres <[email protected]>
1 parent 20f8af0 commit 744362a

File tree

4 files changed

+143
-2
lines changed

4 files changed

+143
-2
lines changed

docs/source/changelog.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ Mobjects, Scenes, and Animations
9393
#. Added BraceBetweenPoints (via :pr:`693`).
9494
#. Added ArcPolygon and ArcPolygonFromArcs (via :pr:`707`).
9595
#. Added Cutout (via :pr:`760`).
96+
#. Added Mobject raise not implemented errors for dunder methods and implementations for VGroup dunder methods (via :pr:`790`).
9697
#. Added :class:`~.ManimBanner` for a animated version of our logo and banner (via :pr:`729`)
9798
#. The background color of a scene can now be changed reliably by setting, e.g.,
9899
``self.camera.background_color = RED`` (via :pr:`716`).

manim/mobject/mobject.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ def add(self, *mobjects):
7777
7878
The mobjects are added to self.submobjects.
7979
80+
Subclasses of mobject may implement + and += dunder methods.
81+
8082
Parameters
8183
----------
8284
mobjects : :class:`Mobject`
@@ -136,6 +138,12 @@ def add(self, *mobjects):
136138
self.submobjects = list_update(self.submobjects, mobjects)
137139
return self
138140

141+
def __add__(self, mobject):
142+
raise NotImplementedError
143+
144+
def __iadd__(self, mobject):
145+
raise NotImplementedError
146+
139147
def add_to_back(self, *mobjects):
140148
self.remove(*mobjects)
141149
self.submobjects = list(mobjects) + self.submobjects
@@ -146,6 +154,8 @@ def remove(self, *mobjects):
146154
147155
The mobjects are removed from self.submobjects, if they exist.
148156
157+
Subclasses of mobject may implement - and -= dunder methods.
158+
149159
Parameters
150160
----------
151161
mobjects : :class:`Mobject`
@@ -166,6 +176,12 @@ def remove(self, *mobjects):
166176
self.submobjects.remove(mobject)
167177
return self
168178

179+
def __sub__(self, other):
180+
raise NotImplementedError
181+
182+
def __isub__(self, other):
183+
raise NotImplementedError
184+
169185
def get_array_attrs(self):
170186
return ["points"]
171187

manim/mobject/types/vectorized_mobject.py

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -975,6 +975,31 @@ class VGroup(VMobject):
975975
Examples
976976
--------
977977
978+
To add :class:`~.VMobject`s to a :class:`~.VGroup`, you can either use the
979+
:meth:`~.VGroup.add` method, or use the `+` and `+=` operators. Similarly, you
980+
can subtract elements of a VGroup via :meth:`~.VGroup.remove` method, or
981+
`-` and `-=` operators:
982+
983+
>>> from manim import Triangle, Square, VGroup
984+
>>> vg = VGroup()
985+
>>> triangle, square = Triangle(), Square()
986+
>>> vg.add(triangle)
987+
VGroup(Triangle)
988+
>>> vg + square # a new VGroup is constructed
989+
VGroup(Triangle, Square)
990+
>>> vg # not modified
991+
VGroup(Triangle)
992+
>>> vg += square; vg # modifies vg
993+
VGroup(Triangle, Square)
994+
>>> vg.remove(triangle)
995+
VGroup(Square)
996+
>>> vg - square; # a new VGroup is constructed
997+
VGroup()
998+
>>> vg # not modified
999+
VGroup(Square)
1000+
>>> vg -= square; vg # modifies vg
1001+
VGroup()
1002+
9781003
.. manim:: ArcShapeIris
9791004
:save_last_frame:
9801005
@@ -1014,16 +1039,61 @@ def add(self, *vmobjects):
10141039
10151040
Returns
10161041
-------
1017-
None
1042+
:class:`VGroup`
10181043
10191044
Raises
10201045
------
10211046
TypeError
10221047
If one element of the list is not an instance of VMobject
1048+
1049+
Examples
1050+
--------
1051+
.. manim:: AddToVGroup
1052+
1053+
class AddToVGroup(Scene):
1054+
def construct(self):
1055+
circle_red = Circle(color=RED)
1056+
circle_green = Circle(color=GREEN)
1057+
circle_blue = Circle(color=BLUE)
1058+
circle_red.shift(LEFT)
1059+
circle_blue.shift(RIGHT)
1060+
gr = VGroup(circle_red, circle_green)
1061+
gr2 = VGroup(circle_blue) # Constructor uses add directly
1062+
self.add(gr,gr2)
1063+
self.wait()
1064+
gr += gr2 # Add group to another
1065+
self.play(
1066+
gr.shift, DOWN,
1067+
)
1068+
gr -= gr2 # Remove group
1069+
self.play( # Animate groups separately
1070+
gr.shift, LEFT,
1071+
gr2.shift, UP,
1072+
)
1073+
self.play( #Animate groups without modification
1074+
(gr+gr2).shift, RIGHT
1075+
)
1076+
self.play( # Animate group without component
1077+
(gr-circle_red).shift, RIGHT
1078+
)
10231079
"""
10241080
if not all(isinstance(m, VMobject) for m in vmobjects):
10251081
raise TypeError("All submobjects must be of type VMobject")
1026-
super().add(*vmobjects)
1082+
return super().add(*vmobjects)
1083+
1084+
def __add__(self, vmobject):
1085+
return VGroup(*self.submobjects, vmobject)
1086+
1087+
def __iadd__(self, vmobject):
1088+
return self.add(vmobject)
1089+
1090+
def __sub__(self, vmobject):
1091+
copy = VGroup(*self.submobjects)
1092+
copy.remove(vmobject)
1093+
return copy
1094+
1095+
def __isub__(self, vmobject):
1096+
return self.remove(vmobject)
10271097

10281098

10291099
class VDict(VMobject):

tests/test_vectorized_mobject.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,60 @@ def test_vgroup_add():
3131
obj.add(obj)
3232

3333

34+
def test_vgroup_add_dunder():
35+
"""Test the VGroup __add__ magic method."""
36+
obj = VGroup()
37+
assert len(obj.submobjects) == 0
38+
obj + VMobject()
39+
assert len(obj.submobjects) == 0
40+
obj += VMobject()
41+
assert len(obj.submobjects) == 1
42+
with pytest.raises(TypeError):
43+
obj += Mobject()
44+
assert len(obj.submobjects) == 1
45+
with pytest.raises(TypeError):
46+
# If only one of the added object is not an instance of VMobject, none of them should be added
47+
obj += (VMobject(), Mobject())
48+
assert len(obj.submobjects) == 1
49+
with pytest.raises(Exception): # TODO change this to ValueError once #307 is merged
50+
# a Mobject cannot contain itself
51+
obj += obj
52+
53+
54+
def test_vgroup_remove():
55+
"""Test the VGroup remove method."""
56+
a = VMobject()
57+
c = VMobject()
58+
b = VGroup(c)
59+
obj = VGroup(a, b)
60+
assert len(obj.submobjects) == 2
61+
assert len(b.submobjects) == 1
62+
obj.remove(a)
63+
b.remove(c)
64+
assert len(obj.submobjects) == 1
65+
assert len(b.submobjects) == 0
66+
obj.remove(b)
67+
assert len(obj.submobjects) == 0
68+
69+
70+
def test_vgroup_remove_dunder():
71+
"""Test the VGroup __sub__ magic method."""
72+
a = VMobject()
73+
c = VMobject()
74+
b = VGroup(c)
75+
obj = VGroup(a, b)
76+
assert len(obj.submobjects) == 2
77+
assert len(b.submobjects) == 1
78+
assert len((obj - a)) == 1
79+
assert len(obj.submobjects) == 2
80+
obj -= a
81+
b -= c
82+
assert len(obj.submobjects) == 1
83+
assert len(b.submobjects) == 0
84+
obj -= b
85+
assert len(obj.submobjects) == 0
86+
87+
3488
def test_vdict_init():
3589
"""Test the VDict instantiation."""
3690
# Test empty VDict

0 commit comments

Comments
 (0)