How to Leverage Manim’s Internal _AnimationBuilder for Chained Animations
Manim’s private _AnimationBuilder class transforms any Mobject into a fluent interface for sequencing transformations, capturing method calls on a hidden target copy and compiling them into a single _MethodAnimation when passed to Scene.play().
The 3b1b/manim library provides a powerful internal mechanism for creating fluent, chained animations through the _AnimationBuilder pattern. By accessing the .animate property on any Mobject, developers can leverage the internal _AnimationBuilder pattern for creating chained animations in Manim that compile multiple transformations into a single interpolated sequence.
Understanding the _AnimationBuilder Architecture
The animation builder lives in manimlib/mobject/mobject.py as the private class _AnimationBuilder. When you access the animate property of any Mobject, Manim creates an instance of this builder and prepares it to record transformations.
Construction and Target Generation
Upon instantiation, the builder immediately creates a copy of the original object that will serve as the transformation target:
# Internal mechanism in manimlib/mobject/mobject.py
self.mobject.generate_target()
The generate_target() method makes a deep copy of the Mobject and stores it as self.mobject.target. This target object receives all the transformations while the original remains unchanged until animation playback begins.
Method Chaining and Attribute Capture
Every attribute accessed on the builder (e.g., .shift, .scale, .set_fill) is resolved on the target object rather than the original:
- The underlying method is stored in
self.methods - The builder returns itself (
return self), enabling fluent chaining - Attribute access is intercepted and redirected to
self.mobject.target
This design allows you to write expressive chains like square.animate.shift(RIGHT).scale(2).set_fill(RED) where each method call is recorded for later execution.
Animation Argument Handling
Calling the builder as a function (e.g., animate(run_time=2)) stores animation-wide keyword arguments in self.anim_args via set_anim_args:
# Example usage
square.animate(run_time=3, rate_func=linear).shift(RIGHT)
These arguments can only be set once per builder instance, preventing ambiguous configuration. When the animation is finally built, these parameters are passed to the generated Animation object.
From Builder to Animation: The Execution Pipeline
When you pass the builder to Scene.play(), Manim must convert the recorded method chain into a runnable animation object.
The prepare_animation Bridge
In manimlib/animation/animation.py, the prepare_animation function handles the conversion:
def prepare_animation(anim):
if isinstance(anim, _AnimationBuilder):
return anim.build()
# ... handle other animation types
This bridge allows Scene.play to accept either concrete Animation instances or _AnimationBuilder objects seamlessly.
Building the _MethodAnimation
The _AnimationBuilder.build() method creates a _MethodAnimation, which is a subclass of MoveToTarget defined in manimlib/animation/transform.py:
- The animation iterates over the stored methods in
self.methods - It applies each method to the target copy
- During playback, it interpolates the original
Mobjecttoward the transformed target state - Animation-wide arguments (
run_time,rate_func, etc.) are applied to the final animation object
This pipeline ensures that complex chains like .shift().scale().rotate() execute as a single coherent animation rather than separate sequential steps.
Practical Implementation Examples
Basic Chained Transformations
The most common use case involves chaining multiple transformations on a single mobject:
from manim import *
class ChainDemo(Scene):
def construct(self):
square = Square()
self.add(square)
# Shift right → scale up → change fill colour, all in one animation
self.play(
square.animate.shift(RIGHT).scale(2).set_fill(RED, opacity=0.8),
run_time=3,
)
In this example, square.animate instantiates the _AnimationBuilder. Each subsequent method call (shift, scale, set_fill) is recorded and applied to the target copy. When self.play receives the builder, it compiles these into a single _MethodAnimation that executes over three seconds.
Configuring Animation Parameters
You can specify global animation parameters either through Scene.play or directly on the builder:
self.play(
circle.animate.move_to(UP).rotate(PI/2).set_stroke(BLUE, width=5),
run_time=2,
rate_func=slow_into,
)
Alternatively, use set_anim_args (invoked when calling the builder as a function):
self.play(
triangle.animate.set_anim_args(lag_ratio=0.2).rotate(2*PI),
)
Note that set_anim_args can only be called once per builder instance, ensuring unambiguous configuration of animation-wide properties like lag_ratio, run_time, or rate_func.
Custom Animations via @override_animate
For methods requiring non-standard animation behavior, Manim provides the @override_animate decorator. When the builder detects this marker, it stores the custom animation instead of generating a generic _MethodAnimation:
from manim import *
class MyMobject(VMobject):
@override_animate
def wiggle(self, amplitude=0.2):
# Return a custom Animation
return ApplyMethod(self.shift, amplitude * RIGHT)
class OverrideDemo(Scene):
def construct(self):
obj = MyMobject().set_points_as_corners([[-1, -1, 0], [1, 1, 0]])
self.add(obj)
# The builder detects the overridden method and uses the returned Animation
self.play(obj.animate.wiggle(amplitude=0.5))
Important limitation: Chaining is not supported after an overridden method. Attempting to call additional methods after .wiggle() raises NotImplementedError, as the builder cannot determine how to merge custom animations with subsequent generic transformations.
Summary
_AnimationBuilderinmanimlib/mobject/mobject.pyprovides the fluent interface accessed via.animate.- Target generation via
generate_target()creates a copy of the mobject that receives all transformations while the original remains untouched until playback. - Method chaining captures attribute access on the target, storing methods in a list for later execution by
_MethodAnimation. - Animation compilation occurs in
prepare_animation(manimlib/animation/animation.py), which invokesbuild()to create a_MethodAnimationfrom the stored method chain. - Global parameters can be set via
Scene.playarguments orset_anim_argson the builder, but only once per builder instance. - Custom animations use
@override_animateto bypass the generic method animation, though this disables further chaining.
Frequently Asked Questions
What is the _AnimationBuilder pattern in Manim?
The _AnimationBuilder pattern is an internal implementation in 3b1b/manim that enables fluent, chained animation syntax. When you access the .animate property on any Mobject, Manim instantiates this private builder class, which records every subsequent method call (like shift() or rotate()) on a hidden target copy. When the scene plays the animation, the builder compiles these recorded methods into a single _MethodAnimation that interpolates the original object toward the transformed state.
How does method chaining work with the animate property?
Method chaining works through attribute interception. When you write square.animate.shift(RIGHT).scale(2), the builder resolves shift and scale on the target copy (self.mobject.target) rather than the original object. It stores the method references in an internal list (self.methods) and returns self, allowing the next method call to continue the chain. This deferred execution model ensures all transformations are applied simultaneously during a single animation frame rather than sequentially as separate animations.
Can I configure global animation parameters using _AnimationBuilder?
Yes, you can configure animation-wide parameters such as run_time, rate_func, or lag_ratio in two ways. First, pass them directly to Scene.play() alongside the builder instance. Second, invoke the builder as a function with keyword arguments (e.g., square.animate(run_time=2).shift(RIGHT)), which internally calls set_anim_args. Note that these arguments can only be set once per builder instance; attempting to configure them multiple times will raise an error to prevent ambiguous animation settings.
Why does chaining fail after using @override_animate?
Chaining fails after @override_animate because the builder switches from recording generic method calls to executing a custom animation factory. When the builder detects the _override_animate marker on a method, it expects that method to return a fully constructed Animation object (such as ApplyMethod or a custom subclass). Since this returned animation encapsulates the transformation logic internally, the builder cannot determine how to merge subsequent method calls into the existing custom animation. Attempting to chain further methods raises NotImplementedError to prevent undefined behavior.
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 →