How to Work with Mobjects, VMobjects, and VGroups to Build Scenes in Manim
To build scenes in Manim, use VMobject subclasses for drawable shapes, VGroup to bundle them into single transformable units, and Mobject's family tree methods for layout and animation.
Manim, the mathematical animation engine created by 3Blue1Brown and maintained in the 3b1b/manim repository, organizes every visual element into a hierarchy of mobjects (mathematical objects). Understanding the relationship between the abstract Mobject base class, the vectorized VMobject, and the container VGroup is essential for composing clean, reusable animation code.
Understanding the Mobject Hierarchy
Manim’s rendering pipeline is built around three core classes defined in manimlib/mobject/mobject.py and manimlib/mobject/types/vectorized_mobject.py.
Mobject – The Abstract Foundation
Mobject is the root class for any drawable entity. It manages the family tree through submobjects (children) and parents (containers) lists, handles updaters via add_updater and remove_updater, and provides geometric transformations through apply_points_function.
Key capabilities include:
- Family management: Methods like
add,remove,clear,add_to_back,replace_submobject, andinsert_submobjectmanipulate the scene graph. - Geometry:
shift,scale,rotate,move_to,next_to, andalign_on_bordermodifyself.data['point']through the unified transformation pipeline. - Updaters: The
alwaysandf_alwaysproperties register callables that execute every frame, enabling continuous behaviors. These receiveself.timefrom the Scene class.
VMobject – Vectorized Graphics
VMobject (Vectorized Mobject) extends Mobject to store per-vertex data required for GPU rendering. Defined in manimlib/mobject/types/vectorized_mobject.py, it serves as the parent for all shape primitives like Circle, Square, and Polygon.
Critical features:
- Data buffers: Stores
stroke_rgba,stroke_width,fill_rgba,joint_angle,base_normal, andfill_border_widthin thedata_dtypedefinition. This data is sent to the GPU viaVShaderWrapper. - Styling API:
set_fill,set_stroke,set_shading, andset_flat_strokeoperate recursively on the family by default. - Path handling:
get_bezier_tuples,get_subcurve, andDashedVMobjectenable complex curve manipulation. - Group factory:
VMobject.get_group_classreturnsVGroup, ensuring that the expressiona + bautomatically produces aVGroupcontaining both objects.
VGroup – The Container Object
VGroup (defined around line 1312 in manimlib/mobject/types/vectorized_mobject.py) inherits from both Group and VMobject. This dual inheritance allows it to behave like a Python list while retaining full vector graphics capabilities.
Key behaviors:
- Hybrid inheritance: Because it is both a
GroupandVMobject, aVGroupsupports indexing (vg[0]) like a list and transformations like any other drawable object. - Uniform inheritance: Upon creation, it copies the first child’s uniform dictionary (
self.uniforms.update(self.submobjects[0].uniforms)) to ensure consistent stroke and fill settings across the group. - Layout utilities: Methods such as
arrange,arrange_in_grid,space_out_submobjects, andsort(defined onMobject) work transparently onVGroupinstances.
Working with Families and Transformations
Effective scene building relies on manipulating the mobject family tree and applying coordinate transformations.
Managing the Scene Graph
Every mobject maintains parent-child relationships. When you call self.add(mob) in your Scene, you are attaching the mobject to the scene's family tree. Changes to a parent automatically propagate to children’s bounding boxes and shader data.
Use add_to_back to control render order (z-index), and replace_submobject to swap elements during animations without breaking family references.
Applying Coordinate Functions
All geometric transformations route through apply_points_function in mobject.py. This ensures that whether you call shift, scale, or rotate, the underlying point data in self.data['point'] is updated consistently, and bounding boxes are recalculated for the entire family.
Practical Scene Building Patterns
The following patterns demonstrate how to compose scenes using the hierarchy effectively.
Pattern 1: Simple Composition with VGroup
Group related primitives to treat them as a single transformable unit.
from manimlib import *
class SimpleGroup(Scene):
def construct(self):
# Create primitive VMobjects
circle = Circle(radius=2, color=RED).set_fill(opacity=0.3)
square = Square(side_length=2, color=BLUE).set_fill(opacity=0.5)
# Group them – VGroup behaves like a single VMobject
logo = VGroup(circle, square).arrange(RIGHT, buff=0.5)
# Position the group
logo.to_edge(UP)
# Add to scene with an animation
self.play(FadeIn(logo, shift=DOWN))
# Demonstrate collective transformation
self.play(logo.animate.scale(1.5).rotate(PI / 4))
self.wait()
Key points:
VGroupautomatically inheritsanimate, enabling chained transformations on the whole group.arrange(RIGHT)uses the layout utilities defined inMobject.
Pattern 2: Nested Groups and Per-Subobject Styling
Access individual children to apply distinct styles while maintaining group coherence.
class NestedGroups(Scene):
def construct(self):
# Base shapes
outer = Circle(radius=3, color=YELLOW).set_fill(opacity=0.2)
inner = Circle(radius=1, color=GREEN).set_fill(opacity=0.8)
# Group the two circles
ring = VGroup(outer, inner)
ring.center() # Center both circles together
# Add a third element that stays independent
dot = Dot(radius=0.1, color=WHITE).next_to(ring, DOWN, buff=0.3)
# Combine into a top‑level group
whole = VGroup(ring, dot)
# Style subobjects individually
outer.set_stroke(width=8, color=ORANGE) # only outer circle gets thick stroke
inner.set_fill(opacity=0.4) # dim inner fill
self.add(whole)
self.play(whole.animate.rotate(2 * PI), run_time=4)
self.wait()
Key points:
- Sub-objects are accessible via indexing (
ring[0]isouter). - Styling calls recurse through the family by default, but you can limit recursion with
recurse=False.
Pattern 3: Live Updaters on VMobjects
Use always and f_always to create frame-wise animations without explicit self.play calls.
class LiveWave(Scene):
def construct(self):
wave = VMobject()
wave.set_points_as_corners([LEFT, RIGHT]) # start as a simple line
wave.set_stroke(color=PURPLE, width=8)
# Make the wave oscillate forever
wave.always.apply_function(
lambda pts: pts + np.column_stack((
np.zeros(len(pts)),
0.5 * np.sin(pts[:, 0] + self.time),
np.zeros(len(pts))
))
)
self.add(wave)
self.wait(5) # watch the live animation
Key points:
wave.always.apply_functionregisters a frame-wise updater that mutates points.self.timeis supplied by theScenebase class inmanimlib/scene/scene.py.
Key Source Files for Deep Dives
To understand the internals behind these patterns, study the following files in the 3b1b/manim repository:
| File | Purpose |
|---|---|
manimlib/mobject/mobject.py |
Core implementation of the family tree, point handling, updaters, and generic transformations. |
manimlib/mobject/types/vectorized_mobject.py |
Defines VMobject, its data buffers, styling API, and the VGroup container. |
manimlib/scene/scene.py |
Provides the Scene class that owns the camera, orchestrates self.add, self.play, and passes the self.time variable to updaters. |
manimlib/mobject/types/__init__.py |
Re-exports the most-used shape classes (Circle, Square, Polygon) which all inherit from VMobject. |
manimlib/mobject/svg/svg_mobject.py |
Shows how SVG paths are converted into a VMobject hierarchy, useful when you need custom vector graphics. |
Summary
Mobjectis the abstract base that handles family trees, coordinate transforms, and updaters inmanimlib/mobject/mobject.py.VMobjectadds GPU-ready vertex buffers and styling controls for vector graphics, serving as the parent for all shapes.VGroupacts as a dual-purpose container: it supports list-like indexing while retaining fullVMobjecttransform capabilities, making it ideal for bundling complex assets.- Use
alwaysandf_alwaysupdaters for continuous animations driven byself.time. - Reference
manimlib/mobject/types/vectorized_mobject.pyandmanimlib/mobject/mobject.pywhen extending or debugging the hierarchy.
Frequently Asked Questions
What is the difference between Mobject and VMobject?
Mobject is the abstract foundation that manages the scene graph, transformations, and updaters but contains no rendering data. VMobject (Vectorized Mobject) inherits from Mobject and adds GPU-specific buffers for stroke, fill, and vertex data, making it the appropriate base for any visible shape like circles or polygons.
When should I use VGroup instead of Group?
You should use VGroup when you need a container that behaves as a single drawable object. Because VGroup inherits from both Group and VMobject, it supports vector styling and transformations while allowing list-like access to its children. Use Group only when you need a pure organizational container without the rendering overhead of VMobject.
How do I access individual submobjects in a VGroup?
You can access children via standard indexing: my_vgroup[0] returns the first submobject. You can also iterate over the group with for mob in my_vgroup:. To style individual members without affecting siblings, call methods directly on the indexed submobject, as styling operations recurse through the family by default.
Can I apply different styles to submobjects within a VGroup?
Yes. While VGroup copies the first child's uniform dictionary upon creation to ensure visual consistency, you can override styles on individual submobjects afterward. Access the child via indexing and apply methods like set_stroke or set_fill. To prevent a style call from propagating to children, pass recurse=False to the styling method.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →