From 50b67a67d4c970d28f4c5292eeba4aca02c4ff11 Mon Sep 17 00:00:00 2001 From: Nikhila Gurusinghe Date: Sun, 20 Oct 2024 17:45:39 +1100 Subject: [PATCH 01/10] feat: allowing VGroup to parse iterables and adding tests --- manim/mobject/types/vectorized_mobject.py | 44 +++++++++++++++++-- .../test_vectorized_mobject.py | 43 ++++++++++++++++++ 2 files changed, 84 insertions(+), 3 deletions(-) diff --git a/manim/mobject/types/vectorized_mobject.py b/manim/mobject/types/vectorized_mobject.py index 1bbc32a092..fc0b4991ca 100644 --- a/manim/mobject/types/vectorized_mobject.py +++ b/manim/mobject/types/vectorized_mobject.py @@ -47,6 +47,8 @@ from manim.utils.space_ops import rotate_vector, shoelace_direction if TYPE_CHECKING: + from types import GeneratorType + import numpy.typing as npt from typing_extensions import Self @@ -2056,7 +2058,11 @@ def construct(self): """ - def __init__(self, *vmobjects, **kwargs): + def __init__( + self, + *vmobjects: VMobject | Iterable[VMobject] | GeneratorType[VMobject], + **kwargs, + ): super().__init__(**kwargs) self.add(*vmobjects) @@ -2069,7 +2075,9 @@ def __str__(self) -> str: f"submobject{'s' if len(self.submobjects) > 0 else ''}" ) - def add(self, *vmobjects: VMobject) -> Self: + def add( + self, *vmobjects: VMobject | Iterable[VMobject] | GeneratorType[VMobject] + ) -> Self: """Checks if all passed elements are an instance of VMobject and then add them to submobjects Parameters @@ -2117,7 +2125,37 @@ def construct(self): (gr-circle_red).animate.shift(RIGHT) ) """ - return super().add(*vmobjects) + + def get_type_error_message(invalid_obj, invalid_i): + return TypeError( + f"Only values of type VMobject can be added " + f"as submobjects of VGroup, but the value " + f"{repr(invalid_obj)} (at index {invalid_i}) is of type " + f"{type(invalid_obj).__name__}." + ) + + valid_vmobjects = [] + + for vmobject_i, vmobject in enumerate(vmobjects): + if isinstance(vmobject, VMobject): + valid_vmobjects.append(vmobject) + elif isinstance(vmobject, Iterable) and type(vmobject) is not Mobject: + for subvmobject_i, subvmobject in enumerate(vmobject): + if not isinstance(subvmobject, VMobject): + raise TypeError( + get_type_error_message(subvmobject, subvmobject_i) + ) + valid_vmobjects.append(subvmobject) + elif isinstance(vmobject, Iterable) and type(vmobject) is Mobject: + # This is if vmobject is an empty Mobject + raise TypeError( + f"{get_type_error_message(vmobject, vmobject_i)} " + "You can try adding this value into a Group instead." + ) + else: + raise TypeError(get_type_error_message(vmobject, vmobject_i)) + + return super().add(*valid_vmobjects) def __add__(self, vmobject: VMobject) -> Self: return VGroup(*self.submobjects, vmobject) diff --git a/tests/module/mobject/types/vectorized_mobject/test_vectorized_mobject.py b/tests/module/mobject/types/vectorized_mobject/test_vectorized_mobject.py index 7e92a68ac8..c0037ad394 100644 --- a/tests/module/mobject/types/vectorized_mobject/test_vectorized_mobject.py +++ b/tests/module/mobject/types/vectorized_mobject/test_vectorized_mobject.py @@ -152,6 +152,49 @@ def test_vgroup_init(): ) +def test_vgroup_init_with_iterable(): + """Test VGroup instantiation with an iterable type.""" + + def type_generator(type_to_generate, n): + return (type_to_generate() for _ in range(n)) + + def mixed_type_generator(major_type, minor_type, minor_type_positions, n): + return ( + minor_type() if i in minor_type_positions else major_type() + for i in range(n) + ) + + obj = VGroup(VMobject()) + assert len(obj.submobjects) == 1 + + obj = VGroup(type_generator(VMobject, 38)) + assert len(obj.submobjects) == 38 + + # A VGroup cannot be initialised with an iterable containing a Mobject + with pytest.raises(TypeError) as init_with_mob_iterable: + VGroup(type_generator(Mobject, 5)) + assert str(init_with_mob_iterable.value) == ( + "Only values of type VMobject can be added as submobjects of VGroup, " + "but the value Mobject (at index 0) is of type Mobject." + ) + + # A VGroup cannot be initialised with an iterable containing a Mobject in any position + with pytest.raises(TypeError) as init_with_mobs_and_vmobs_iterable: + VGroup(mixed_type_generator(VMobject, Mobject, [3, 5], 7)) + assert str(init_with_mobs_and_vmobs_iterable.value) == ( + "Only values of type VMobject can be added as submobjects of VGroup, " + "but the value Mobject (at index 3) is of type Mobject." + ) + + # A VGroup cannot be initialised with an iterable containing non VMobject's in any position + with pytest.raises(TypeError) as init_with_float_and_vmobs_iterable: + VGroup(mixed_type_generator(VMobject, float, [6, 7], 9)) + assert str(init_with_float_and_vmobs_iterable.value) == ( + "Only values of type VMobject can be added as submobjects of VGroup, " + "but the value 0.0 (at index 6) is of type float." + ) + + def test_vgroup_add(): """Test the VGroup add method.""" obj = VGroup() From 1a2b7a82b03d81b48623dda713240c5975f6fb37 Mon Sep 17 00:00:00 2001 From: Nikhila Gurusinghe Date: Sun, 20 Oct 2024 19:40:15 +1100 Subject: [PATCH 02/10] feat: adding OpenGL support for allowing VGroup to parse iterables + adding tests --- manim/mobject/types/vectorized_mobject.py | 23 +++++--- .../opengl/test_opengl_vectorized_mobject.py | 53 ++++++++++++++++++- 2 files changed, 68 insertions(+), 8 deletions(-) diff --git a/manim/mobject/types/vectorized_mobject.py b/manim/mobject/types/vectorized_mobject.py index fc0b4991ca..5407947e8f 100644 --- a/manim/mobject/types/vectorized_mobject.py +++ b/manim/mobject/types/vectorized_mobject.py @@ -24,6 +24,7 @@ from manim.constants import * from manim.mobject.mobject import Mobject from manim.mobject.opengl.opengl_compatibility import ConvertToOpenGL +from manim.mobject.opengl.opengl_mobject import OpenGLMobject from manim.mobject.opengl.opengl_vectorized_mobject import OpenGLVMobject from manim.mobject.three_d.three_d_utils import ( get_3d_vmob_gradient_start_and_end_points, @@ -2076,7 +2077,8 @@ def __str__(self) -> str: ) def add( - self, *vmobjects: VMobject | Iterable[VMobject] | GeneratorType[VMobject] + self, + *vmobjects: VMobject | Iterable[VMobject] | GeneratorType[VMobject], ) -> Self: """Checks if all passed elements are an instance of VMobject and then add them to submobjects @@ -2127,26 +2129,33 @@ def construct(self): """ def get_type_error_message(invalid_obj, invalid_i): - return TypeError( - f"Only values of type VMobject can be added " + return ( + f"Only values of type {vmobject_render_type.__name__} can be added " f"as submobjects of VGroup, but the value " f"{repr(invalid_obj)} (at index {invalid_i}) is of type " f"{type(invalid_obj).__name__}." ) + vmobject_render_type = ( + OpenGLVMobject if config.renderer == RendererType.OPENGL else VMobject + ) valid_vmobjects = [] for vmobject_i, vmobject in enumerate(vmobjects): - if isinstance(vmobject, VMobject): + if isinstance(vmobject, vmobject_render_type): valid_vmobjects.append(vmobject) - elif isinstance(vmobject, Iterable) and type(vmobject) is not Mobject: + elif isinstance(vmobject, Iterable) and not isinstance( + vmobject, (Mobject, OpenGLMobject) + ): for subvmobject_i, subvmobject in enumerate(vmobject): - if not isinstance(subvmobject, VMobject): + if not isinstance(subvmobject, vmobject_render_type): raise TypeError( get_type_error_message(subvmobject, subvmobject_i) ) valid_vmobjects.append(subvmobject) - elif isinstance(vmobject, Iterable) and type(vmobject) is Mobject: + elif isinstance(vmobject, Iterable) and isinstance( + vmobject, (Mobject, OpenGLMobject) + ): # This is if vmobject is an empty Mobject raise TypeError( f"{get_type_error_message(vmobject, vmobject_i)} " diff --git a/tests/opengl/test_opengl_vectorized_mobject.py b/tests/opengl/test_opengl_vectorized_mobject.py index 6f73ef0265..0b0c7767c7 100644 --- a/tests/opengl/test_opengl_vectorized_mobject.py +++ b/tests/opengl/test_opengl_vectorized_mobject.py @@ -3,7 +3,7 @@ import numpy as np import pytest -from manim import Circle, Line, Square, VDict, VGroup +from manim import Circle, Line, Square, VDict, VGroup, VMobject from manim.mobject.opengl.opengl_mobject import OpenGLMobject from manim.mobject.opengl.opengl_vectorized_mobject import OpenGLVMobject @@ -110,6 +110,57 @@ def test_vgroup_init(using_opengl_renderer): ) +def test_vgroup_init_with_iterable(using_opengl_renderer): + """Test VGroup instantiation with an iterable type.""" + + def type_generator(type_to_generate, n): + return (type_to_generate() for _ in range(n)) + + def mixed_type_generator(major_type, minor_type, minor_type_positions, n): + return ( + minor_type() if i in minor_type_positions else major_type() + for i in range(n) + ) + + obj = VGroup(OpenGLVMobject()) + assert len(obj.submobjects) == 1 + + obj = VGroup(type_generator(OpenGLVMobject, 38)) + assert len(obj.submobjects) == 38 + + # A VGroup cannot be initialised with an iterable containing a OpenGLMobject + with pytest.raises(TypeError) as init_with_mob_iterable: + VGroup(type_generator(OpenGLMobject, 5)) + assert str(init_with_mob_iterable.value) == ( + "Only values of type OpenGLVMobject can be added as submobjects of VGroup, " + "but the value OpenGLMobject (at index 0) is of type OpenGLMobject." + ) + + # A VGroup cannot be initialised with an iterable containing a OpenGLMobject in any position + with pytest.raises(TypeError) as init_with_mobs_and_vmobs_iterable: + VGroup(mixed_type_generator(OpenGLVMobject, OpenGLMobject, [3, 5], 7)) + assert str(init_with_mobs_and_vmobs_iterable.value) == ( + "Only values of type OpenGLVMobject can be added as submobjects of VGroup, " + "but the value OpenGLMobject (at index 3) is of type OpenGLMobject." + ) + + # A VGroup cannot be initialised with an iterable containing non OpenGLVMobject's in any position + with pytest.raises(TypeError) as init_with_float_and_vmobs_iterable: + VGroup(mixed_type_generator(OpenGLVMobject, float, [6, 7], 9)) + assert str(init_with_float_and_vmobs_iterable.value) == ( + "Only values of type OpenGLVMobject can be added as submobjects of VGroup, " + "but the value 0.0 (at index 6) is of type float." + ) + + # A VGroup cannot be initialised with an iterable containing both OpenGLVMobject's and VMobject's + with pytest.raises(TypeError) as init_with_mobs_and_vmobs_iterable: + VGroup(mixed_type_generator(OpenGLVMobject, VMobject, [3, 5], 7)) + assert str(init_with_mobs_and_vmobs_iterable.value) == ( + "Only values of type OpenGLVMobject can be added as submobjects of VGroup, " + "but the value VMobject (at index 3) is of type VMobject." + ) + + def test_vgroup_add(using_opengl_renderer): """Test the VGroup add method.""" obj = VGroup() From 13f403e4ca47ecebe8160682172bfaad96c39846 Mon Sep 17 00:00:00 2001 From: Nikhila Gurusinghe Date: Sun, 20 Oct 2024 19:44:53 +1100 Subject: [PATCH 03/10] docs: updating documentation to VGroup's add method --- manim/mobject/types/vectorized_mobject.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/manim/mobject/types/vectorized_mobject.py b/manim/mobject/types/vectorized_mobject.py index 5407947e8f..b84a3a757a 100644 --- a/manim/mobject/types/vectorized_mobject.py +++ b/manim/mobject/types/vectorized_mobject.py @@ -2080,12 +2080,12 @@ def add( self, *vmobjects: VMobject | Iterable[VMobject] | GeneratorType[VMobject], ) -> Self: - """Checks if all passed elements are an instance of VMobject and then add them to submobjects + """Checks if all passed elements are an instance, or iterables of VMobject and then adds them to submobjects Parameters ---------- vmobjects - List of VMobject to add + List or iterable of VMobjects to add Returns ------- @@ -2094,7 +2094,7 @@ def add( Raises ------ TypeError - If one element of the list is not an instance of VMobject + If one element of the list, or iterable is not an instance of VMobject Examples -------- From 05526b6cf2dbf877cd64047caf0aa367b106c709 Mon Sep 17 00:00:00 2001 From: Nikhila Gurusinghe Date: Sun, 20 Oct 2024 22:39:27 +1100 Subject: [PATCH 04/10] test: adding complex VGroup initialisation --- .../types/vectorized_mobject/test_vectorized_mobject.py | 3 +++ tests/opengl/test_opengl_vectorized_mobject.py | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/tests/module/mobject/types/vectorized_mobject/test_vectorized_mobject.py b/tests/module/mobject/types/vectorized_mobject/test_vectorized_mobject.py index c0037ad394..91f0c43f24 100644 --- a/tests/module/mobject/types/vectorized_mobject/test_vectorized_mobject.py +++ b/tests/module/mobject/types/vectorized_mobject/test_vectorized_mobject.py @@ -170,6 +170,9 @@ def mixed_type_generator(major_type, minor_type, minor_type_positions, n): obj = VGroup(type_generator(VMobject, 38)) assert len(obj.submobjects) == 38 + obj = VGroup(VMobject(), [VMobject(), VMobject()], type_generator(VMobject, 38)) + assert len(obj.submobjects) == 41 + # A VGroup cannot be initialised with an iterable containing a Mobject with pytest.raises(TypeError) as init_with_mob_iterable: VGroup(type_generator(Mobject, 5)) diff --git a/tests/opengl/test_opengl_vectorized_mobject.py b/tests/opengl/test_opengl_vectorized_mobject.py index 0b0c7767c7..9bd1dcd9c4 100644 --- a/tests/opengl/test_opengl_vectorized_mobject.py +++ b/tests/opengl/test_opengl_vectorized_mobject.py @@ -128,6 +128,13 @@ def mixed_type_generator(major_type, minor_type, minor_type_positions, n): obj = VGroup(type_generator(OpenGLVMobject, 38)) assert len(obj.submobjects) == 38 + obj = VGroup( + OpenGLVMobject(), + [OpenGLVMobject(), OpenGLVMobject()], + type_generator(OpenGLVMobject, 38), + ) + assert len(obj.submobjects) == 41 + # A VGroup cannot be initialised with an iterable containing a OpenGLMobject with pytest.raises(TypeError) as init_with_mob_iterable: VGroup(type_generator(OpenGLMobject, 5)) From c6224b561bc0f5fcb639bcca4c4d3b0436ba5c66 Mon Sep 17 00:00:00 2001 From: Nikhila Gurusinghe Date: Mon, 21 Oct 2024 13:45:17 +1100 Subject: [PATCH 05/10] updating add method of VGroup with PR review suggestions --- manim/mobject/types/vectorized_mobject.py | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/manim/mobject/types/vectorized_mobject.py b/manim/mobject/types/vectorized_mobject.py index b84a3a757a..d32e05de24 100644 --- a/manim/mobject/types/vectorized_mobject.py +++ b/manim/mobject/types/vectorized_mobject.py @@ -48,8 +48,6 @@ from manim.utils.space_ops import rotate_vector, shoelace_direction if TYPE_CHECKING: - from types import GeneratorType - import numpy.typing as npt from typing_extensions import Self @@ -2061,7 +2059,7 @@ def construct(self): def __init__( self, - *vmobjects: VMobject | Iterable[VMobject] | GeneratorType[VMobject], + *vmobjects: VMobject | Iterable[VMobject], **kwargs, ): super().__init__(**kwargs) @@ -2078,7 +2076,7 @@ def __str__(self) -> str: def add( self, - *vmobjects: VMobject | Iterable[VMobject] | GeneratorType[VMobject], + *vmobjects: VMobject | Iterable[VMobject], ) -> Self: """Checks if all passed elements are an instance, or iterables of VMobject and then adds them to submobjects @@ -2131,7 +2129,7 @@ def construct(self): def get_type_error_message(invalid_obj, invalid_i): return ( f"Only values of type {vmobject_render_type.__name__} can be added " - f"as submobjects of VGroup, but the value " + "as submobjects of VGroup, but the value " f"{repr(invalid_obj)} (at index {invalid_i}) is of type " f"{type(invalid_obj).__name__}." ) @@ -2141,28 +2139,25 @@ def get_type_error_message(invalid_obj, invalid_i): ) valid_vmobjects = [] - for vmobject_i, vmobject in enumerate(vmobjects): + for i, vmobject in enumerate(vmobjects): if isinstance(vmobject, vmobject_render_type): valid_vmobjects.append(vmobject) elif isinstance(vmobject, Iterable) and not isinstance( vmobject, (Mobject, OpenGLMobject) ): - for subvmobject_i, subvmobject in enumerate(vmobject): + for j, subvmobject in enumerate(vmobject): if not isinstance(subvmobject, vmobject_render_type): - raise TypeError( - get_type_error_message(subvmobject, subvmobject_i) - ) + raise TypeError(get_type_error_message(subvmobject, j)) valid_vmobjects.append(subvmobject) elif isinstance(vmobject, Iterable) and isinstance( vmobject, (Mobject, OpenGLMobject) ): - # This is if vmobject is an empty Mobject raise TypeError( - f"{get_type_error_message(vmobject, vmobject_i)} " + f"{get_type_error_message(vmobject, i)} " "You can try adding this value into a Group instead." ) else: - raise TypeError(get_type_error_message(vmobject, vmobject_i)) + raise TypeError(get_type_error_message(vmobject, i)) return super().add(*valid_vmobjects) From 391f7b50571d975281c36be9dbb0d5554c37a380 Mon Sep 17 00:00:00 2001 From: Nikhila Gurusinghe Date: Mon, 21 Oct 2024 13:57:21 +1100 Subject: [PATCH 06/10] docs: adding example to add function in VGroup --- manim/mobject/types/vectorized_mobject.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/manim/mobject/types/vectorized_mobject.py b/manim/mobject/types/vectorized_mobject.py index d32e05de24..bc66435dfd 100644 --- a/manim/mobject/types/vectorized_mobject.py +++ b/manim/mobject/types/vectorized_mobject.py @@ -2124,6 +2124,19 @@ def construct(self): self.play( # Animate group without component (gr-circle_red).animate.shift(RIGHT) ) + + .. manim:: VGroupParseIterables + + class VGroupParseIterables(Scene): + def construct(self): + v = VGroup( + Square(), + [Circle(), Triangle()], + Dot(), + (Dot() for _ in range(2)), + ) + v.arrange() + self.add(v) """ def get_type_error_message(invalid_obj, invalid_i): From 078bd210d46e015c42debd1d39a7fea33a50e00f Mon Sep 17 00:00:00 2001 From: Nikhila Gurusinghe Date: Mon, 21 Oct 2024 14:50:45 +1100 Subject: [PATCH 07/10] docs: elaborating on documentation for VGroup add method --- manim/mobject/types/vectorized_mobject.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/manim/mobject/types/vectorized_mobject.py b/manim/mobject/types/vectorized_mobject.py index bc66435dfd..e2ed3fadf0 100644 --- a/manim/mobject/types/vectorized_mobject.py +++ b/manim/mobject/types/vectorized_mobject.py @@ -2096,6 +2096,9 @@ def add( Examples -------- + The following example shows how to add individual or multiple VMobject instances through the VGroup constructor + and its .add() method. + .. manim:: AddToVGroup class AddToVGroup(Scene): @@ -2125,15 +2128,19 @@ def construct(self): (gr-circle_red).animate.shift(RIGHT) ) - .. manim:: VGroupParseIterables + A VGroup can be created using iterables as well. Keep in mind that all generated values from an + iterable must be an instance of VMobject. This is demonstrated below. + + .. manim:: VGroupParseIterablesExample + :save_last_frame: - class VGroupParseIterables(Scene): + class VGroupParseIterablesExample(Scene): def construct(self): v = VGroup( - Square(), - [Circle(), Triangle()], + Square(), # Singular VMobject instance + [Circle(), Triangle()], # List of VMobject instances Dot(), - (Dot() for _ in range(2)), + (Dot() for _ in range(2)), # Iterable that generates VMobjects ) v.arrange() self.add(v) From 4f6de7f6ccee2add7369c0195ee9302f2516e275 Mon Sep 17 00:00:00 2001 From: Nikhila Gurusinghe Date: Mon, 21 Oct 2024 15:10:40 +1100 Subject: [PATCH 08/10] docs: small tweaks to documentation for VGroup add method --- manim/mobject/types/vectorized_mobject.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/manim/mobject/types/vectorized_mobject.py b/manim/mobject/types/vectorized_mobject.py index e2ed3fadf0..9047019ed0 100644 --- a/manim/mobject/types/vectorized_mobject.py +++ b/manim/mobject/types/vectorized_mobject.py @@ -2096,8 +2096,8 @@ def add( Examples -------- - The following example shows how to add individual or multiple VMobject instances through the VGroup constructor - and its .add() method. + The following example shows how to add individual or multiple `VMobject` instances through the `VGroup` + constructor and its `.add()` method. .. manim:: AddToVGroup @@ -2128,13 +2128,13 @@ def construct(self): (gr-circle_red).animate.shift(RIGHT) ) - A VGroup can be created using iterables as well. Keep in mind that all generated values from an - iterable must be an instance of VMobject. This is demonstrated below. + A `VGroup` can be created using iterables as well. Keep in mind that all generated values from an + iterable must be an instance of `VMobject`. This is demonstrated below: - .. manim:: VGroupParseIterablesExample + .. manim:: AddIterableToVGroupExample :save_last_frame: - class VGroupParseIterablesExample(Scene): + class AddIterableToVGroupExample(Scene): def construct(self): v = VGroup( Square(), # Singular VMobject instance From bacf6c6dde27704cf777eece3ddbea85f194113e Mon Sep 17 00:00:00 2001 From: Nikhila Gurusinghe Date: Tue, 22 Oct 2024 17:48:09 +1100 Subject: [PATCH 09/10] updating TypeError messages in add method of VGroup to be more informative --- manim/mobject/types/vectorized_mobject.py | 11 +++++----- .../test_vectorized_mobject.py | 18 ++++++++--------- .../opengl/test_opengl_vectorized_mobject.py | 20 +++++++++---------- 3 files changed, 25 insertions(+), 24 deletions(-) diff --git a/manim/mobject/types/vectorized_mobject.py b/manim/mobject/types/vectorized_mobject.py index 9047019ed0..fae4a67c36 100644 --- a/manim/mobject/types/vectorized_mobject.py +++ b/manim/mobject/types/vectorized_mobject.py @@ -2146,11 +2146,12 @@ def construct(self): self.add(v) """ - def get_type_error_message(invalid_obj, invalid_i): + def get_type_error_message(invalid_obj, invalid_indices): return ( f"Only values of type {vmobject_render_type.__name__} can be added " "as submobjects of VGroup, but the value " - f"{repr(invalid_obj)} (at index {invalid_i}) is of type " + f"{repr(invalid_obj)} (at index {invalid_indices[1]} of " + f"parameter {invalid_indices[0]}) is of type " f"{type(invalid_obj).__name__}." ) @@ -2167,17 +2168,17 @@ def get_type_error_message(invalid_obj, invalid_i): ): for j, subvmobject in enumerate(vmobject): if not isinstance(subvmobject, vmobject_render_type): - raise TypeError(get_type_error_message(subvmobject, j)) + raise TypeError(get_type_error_message(subvmobject, (i, j))) valid_vmobjects.append(subvmobject) elif isinstance(vmobject, Iterable) and isinstance( vmobject, (Mobject, OpenGLMobject) ): raise TypeError( - f"{get_type_error_message(vmobject, i)} " + f"{get_type_error_message(vmobject, (i, 0))} " "You can try adding this value into a Group instead." ) else: - raise TypeError(get_type_error_message(vmobject, i)) + raise TypeError(get_type_error_message(vmobject, (i, 0))) return super().add(*valid_vmobjects) diff --git a/tests/module/mobject/types/vectorized_mobject/test_vectorized_mobject.py b/tests/module/mobject/types/vectorized_mobject/test_vectorized_mobject.py index 91f0c43f24..4d604f2dfb 100644 --- a/tests/module/mobject/types/vectorized_mobject/test_vectorized_mobject.py +++ b/tests/module/mobject/types/vectorized_mobject/test_vectorized_mobject.py @@ -132,14 +132,14 @@ def test_vgroup_init(): VGroup(3.0) assert str(init_with_float_info.value) == ( "Only values of type VMobject can be added as submobjects of VGroup, " - "but the value 3.0 (at index 0) is of type float." + "but the value 3.0 (at index 0 of parameter 0) is of type float." ) with pytest.raises(TypeError) as init_with_mob_info: VGroup(Mobject()) assert str(init_with_mob_info.value) == ( "Only values of type VMobject can be added as submobjects of VGroup, " - "but the value Mobject (at index 0) is of type Mobject. You can try " + "but the value Mobject (at index 0 of parameter 0) is of type Mobject. You can try " "adding this value into a Group instead." ) @@ -147,7 +147,7 @@ def test_vgroup_init(): VGroup(VMobject(), Mobject()) assert str(init_with_vmob_and_mob_info.value) == ( "Only values of type VMobject can be added as submobjects of VGroup, " - "but the value Mobject (at index 1) is of type Mobject. You can try " + "but the value Mobject (at index 0 of parameter 1) is of type Mobject. You can try " "adding this value into a Group instead." ) @@ -178,7 +178,7 @@ def mixed_type_generator(major_type, minor_type, minor_type_positions, n): VGroup(type_generator(Mobject, 5)) assert str(init_with_mob_iterable.value) == ( "Only values of type VMobject can be added as submobjects of VGroup, " - "but the value Mobject (at index 0) is of type Mobject." + "but the value Mobject (at index 0 of parameter 0) is of type Mobject." ) # A VGroup cannot be initialised with an iterable containing a Mobject in any position @@ -186,7 +186,7 @@ def mixed_type_generator(major_type, minor_type, minor_type_positions, n): VGroup(mixed_type_generator(VMobject, Mobject, [3, 5], 7)) assert str(init_with_mobs_and_vmobs_iterable.value) == ( "Only values of type VMobject can be added as submobjects of VGroup, " - "but the value Mobject (at index 3) is of type Mobject." + "but the value Mobject (at index 3 of parameter 0) is of type Mobject." ) # A VGroup cannot be initialised with an iterable containing non VMobject's in any position @@ -194,7 +194,7 @@ def mixed_type_generator(major_type, minor_type, minor_type_positions, n): VGroup(mixed_type_generator(VMobject, float, [6, 7], 9)) assert str(init_with_float_and_vmobs_iterable.value) == ( "Only values of type VMobject can be added as submobjects of VGroup, " - "but the value 0.0 (at index 6) is of type float." + "but the value 0.0 (at index 6 of parameter 0) is of type float." ) @@ -211,7 +211,7 @@ def test_vgroup_add(): obj.add(3) assert str(add_int_info.value) == ( "Only values of type VMobject can be added as submobjects of VGroup, " - "but the value 3 (at index 0) is of type int." + "but the value 3 (at index 0 of parameter 0) is of type int." ) assert len(obj.submobjects) == 1 @@ -221,7 +221,7 @@ def test_vgroup_add(): obj.add(Mobject()) assert str(add_mob_info.value) == ( "Only values of type VMobject can be added as submobjects of VGroup, " - "but the value Mobject (at index 0) is of type Mobject. You can try " + "but the value Mobject (at index 0 of parameter 0) is of type Mobject. You can try " "adding this value into a Group instead." ) assert len(obj.submobjects) == 1 @@ -231,7 +231,7 @@ def test_vgroup_add(): obj.add(VMobject(), Mobject()) assert str(add_vmob_and_mob_info.value) == ( "Only values of type VMobject can be added as submobjects of VGroup, " - "but the value Mobject (at index 1) is of type Mobject. You can try " + "but the value Mobject (at index 0 of parameter 1) is of type Mobject. You can try " "adding this value into a Group instead." ) assert len(obj.submobjects) == 1 diff --git a/tests/opengl/test_opengl_vectorized_mobject.py b/tests/opengl/test_opengl_vectorized_mobject.py index 9bd1dcd9c4..ae41f83b61 100644 --- a/tests/opengl/test_opengl_vectorized_mobject.py +++ b/tests/opengl/test_opengl_vectorized_mobject.py @@ -90,14 +90,14 @@ def test_vgroup_init(using_opengl_renderer): VGroup(3.0) assert str(init_with_float_info.value) == ( "Only values of type OpenGLVMobject can be added as submobjects of " - "VGroup, but the value 3.0 (at index 0) is of type float." + "VGroup, but the value 3.0 (at index 0 of parameter 0) is of type float." ) with pytest.raises(TypeError) as init_with_mob_info: VGroup(OpenGLMobject()) assert str(init_with_mob_info.value) == ( "Only values of type OpenGLVMobject can be added as submobjects of " - "VGroup, but the value OpenGLMobject (at index 0) is of type " + "VGroup, but the value OpenGLMobject (at index 0 of parameter 0) is of type " "OpenGLMobject. You can try adding this value into a Group instead." ) @@ -105,7 +105,7 @@ def test_vgroup_init(using_opengl_renderer): VGroup(OpenGLVMobject(), OpenGLMobject()) assert str(init_with_vmob_and_mob_info.value) == ( "Only values of type OpenGLVMobject can be added as submobjects of " - "VGroup, but the value OpenGLMobject (at index 1) is of type " + "VGroup, but the value OpenGLMobject (at index 0 of parameter 1) is of type " "OpenGLMobject. You can try adding this value into a Group instead." ) @@ -140,7 +140,7 @@ def mixed_type_generator(major_type, minor_type, minor_type_positions, n): VGroup(type_generator(OpenGLMobject, 5)) assert str(init_with_mob_iterable.value) == ( "Only values of type OpenGLVMobject can be added as submobjects of VGroup, " - "but the value OpenGLMobject (at index 0) is of type OpenGLMobject." + "but the value OpenGLMobject (at index 0 of parameter 0) is of type OpenGLMobject." ) # A VGroup cannot be initialised with an iterable containing a OpenGLMobject in any position @@ -148,7 +148,7 @@ def mixed_type_generator(major_type, minor_type, minor_type_positions, n): VGroup(mixed_type_generator(OpenGLVMobject, OpenGLMobject, [3, 5], 7)) assert str(init_with_mobs_and_vmobs_iterable.value) == ( "Only values of type OpenGLVMobject can be added as submobjects of VGroup, " - "but the value OpenGLMobject (at index 3) is of type OpenGLMobject." + "but the value OpenGLMobject (at index 3 of parameter 0) is of type OpenGLMobject." ) # A VGroup cannot be initialised with an iterable containing non OpenGLVMobject's in any position @@ -156,7 +156,7 @@ def mixed_type_generator(major_type, minor_type, minor_type_positions, n): VGroup(mixed_type_generator(OpenGLVMobject, float, [6, 7], 9)) assert str(init_with_float_and_vmobs_iterable.value) == ( "Only values of type OpenGLVMobject can be added as submobjects of VGroup, " - "but the value 0.0 (at index 6) is of type float." + "but the value 0.0 (at index 6 of parameter 0) is of type float." ) # A VGroup cannot be initialised with an iterable containing both OpenGLVMobject's and VMobject's @@ -164,7 +164,7 @@ def mixed_type_generator(major_type, minor_type, minor_type_positions, n): VGroup(mixed_type_generator(OpenGLVMobject, VMobject, [3, 5], 7)) assert str(init_with_mobs_and_vmobs_iterable.value) == ( "Only values of type OpenGLVMobject can be added as submobjects of VGroup, " - "but the value VMobject (at index 3) is of type VMobject." + "but the value VMobject (at index 3 of parameter 0) is of type VMobject." ) @@ -181,7 +181,7 @@ def test_vgroup_add(using_opengl_renderer): obj.add(3) assert str(add_int_info.value) == ( "Only values of type OpenGLVMobject can be added as submobjects of " - "VGroup, but the value 3 (at index 0) is of type int." + "VGroup, but the value 3 (at index 0 of parameter 0) is of type int." ) assert len(obj.submobjects) == 1 @@ -191,7 +191,7 @@ def test_vgroup_add(using_opengl_renderer): obj.add(OpenGLMobject()) assert str(add_mob_info.value) == ( "Only values of type OpenGLVMobject can be added as submobjects of " - "VGroup, but the value OpenGLMobject (at index 0) is of type " + "VGroup, but the value OpenGLMobject (at index 0 of parameter 0) is of type " "OpenGLMobject. You can try adding this value into a Group instead." ) assert len(obj.submobjects) == 1 @@ -201,7 +201,7 @@ def test_vgroup_add(using_opengl_renderer): obj.add(OpenGLVMobject(), OpenGLMobject()) assert str(add_vmob_and_mob_info.value) == ( "Only values of type OpenGLVMobject can be added as submobjects of " - "VGroup, but the value OpenGLMobject (at index 1) is of type " + "VGroup, but the value OpenGLMobject (at index 0 of parameter 1) is of type " "OpenGLMobject. You can try adding this value into a Group instead." ) assert len(obj.submobjects) == 1 From df2ae0da4c17b331d791d242dcb4bae1e0e8f7ae Mon Sep 17 00:00:00 2001 From: Nikhila Gurusinghe Date: Wed, 23 Oct 2024 14:53:16 +1100 Subject: [PATCH 10/10] updating VGroup .add() method's docstring to be more explicit about VGroup indexing --- manim/mobject/types/vectorized_mobject.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/manim/mobject/types/vectorized_mobject.py b/manim/mobject/types/vectorized_mobject.py index fae4a67c36..748d5966a5 100644 --- a/manim/mobject/types/vectorized_mobject.py +++ b/manim/mobject/types/vectorized_mobject.py @@ -2144,6 +2144,11 @@ def construct(self): ) v.arrange() self.add(v) + + To facilitate this, the iterable is unpacked before its individual instances are added to the `VGroup`. + As a result, when you index a `VGroup`, you will never get back an iterable. + Instead, you will always receive `VMobject` instances, including those + that were part of the iterable/s that you originally added to the `VGroup`. """ def get_type_error_message(invalid_obj, invalid_indices):