3b1b/manim · error · TypeError

Animation only works for Mobjects.

Error message

Animation only works for Mobjects.

What it means

Animation._validate_input_type enforces that every animation operates on a Mobject instance; anything else (string, numpy array, list, arbitrary object) raises TypeError at construction time. This is the library's explicit type boundary for the Animation API.

Solutions

  1. Wrap the content in the appropriate mobject: Text('hello'), MathTex, DecimalNumber, etc.
  2. If animating a changing number, use a ValueTracker with always_redraw, not the raw float
  3. In custom Animation subclasses, validate/convert before calling super().__init__

Example fix

# before
self.play(Animation("text"))
# after
self.play(Write(Text("text")))
Defensive patterns

Strategy: type-guard

Validate before calling

assert isinstance(obj, Mobject), f"Animation needs a Mobject, got {type(obj).__name__}"

Type guard

def is_mobject(x) -> bool:
    return isinstance(x, Mobject)

Prevention

When it happens

Trigger: Animation('hello') or a custom animation subclass whose __init__ forwards a non-Mobject first argument; passing a Mobject-type name imported from the wrong module (e.g. a stub or mock) that fails isinstance.

Common situations: Wrapping raw data (strings, arrays) instead of mobjects like Text/MathTex/ValueTracker; test code passing mocks or duck-typed stand-ins; subclassing Animation and forgetting to convert the input to a Mobject first.

Related errors


AI-assisted analysis of 3b1b/manim@dee01804d4 (2026-08-14). Data as JSON: /api/errors/fdd60a955f6d7ddc. Report an issue: GitHub.

Appendix: source

Thrown at manimlib/animation/animation.py:58

        # If set to True, the mobject itself will have its internal updaters called,
        # but the start or target mobjects would not be suspended. To completely suspend
        # updating, call mobject.suspend_updating() before the animation
        suspend_mobject_updating: bool = False,
    ):
        self._validate_input_type(mobject)
        self.mobject = mobject
        self.run_time = run_time
        self.time_span = time_span
        self.rate_func = rate_func
        self.name = name or self.__class__.__name__ + str(self.mobject)
        self.remover = remover
        self.final_alpha_value = final_alpha_value
        self.lag_ratio = lag_ratio
        self.suspend_mobject_updating = suspend_mobject_updating

    def _validate_input_type(self, mobject: Mobject) -> None:
        if not isinstance(mobject, Mobject):
            raise TypeError("Animation only works for Mobjects.")

    def __str__(self) -> str:
        return self.name

    def begin(self) -> None:
        # This is called right as an animation is being
        # played.  As much initialization as possible,
        # especially any mobject copying, should live in
        # this method
        if self.time_span is not None:
            start, end = self.time_span
            self.run_time = max(end, self.run_time)
        self.mobject.set_animating_status(True)
        self.starting_mobject = self.create_starting_mobject()
        if self.suspend_mobject_updating:
            self.mobject_was_updating = not self.mobject.updating_suspended
            self.mobject.suspend_updating()
        self.families = list(self.get_all_families_zipped())

View on GitHub (pinned to dee01804d4)