ManimCommunity / ManimCommunity/manim

Combining a parent-group .animate with a child .animate in the same self.play() silently drops whichever comes first

Open
#4,865 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Python
Stars
40.9k
Forks
3.1k
Avg merge
3d 12h
Merged PRs (30d)
25

Description

## Description of bug / unexpected behavior

When a single `self.play(...)` call is given two independent `.animate`-built animations that both touch the same mobject — one via a parent `VGroup`'s `.animate`, the other directly on one of that group's submobjects — the two animations don't compose. Instead, for any attribute that only one of the two animations actually changes, **whichever animation appears later in the `self.play(...)` argument list wins**, silently discarding the other's effect on that attribute for every frame.

Root cause (as far as I can tell from `mobject.animate` / `_AnimationBuilder.build()` / `Animation.interpolate_mobject`): each `.animate` call produces its own independent `_MethodAnimation`, built from a full `self.copy()` snapshot (`generate_target()`) taken at `.animate` call time. When two such animations share a mobject in their family, `interpolate_mobject` for each animation is applied to that shared mobject **every frame**, in `self.play()`'s argument order, and each interpolation unconditionally recomputes points/style from its own start→target snapshot — including for attributes it never intended to change (where start == target for that animation, but the recomputation still overwrites whatever the other animation set moments earlier in the same frame).

## Expected behavior

Ideally either:
- combining a group-level `.animate` with a child-level `.animate` in the same `self.play()` raises a clear warning/error (detecting overlapping mobject families across simultaneously-played animations), since the current behavior is a silent, hard-to-debug footgun, or
- the two animations' effects are actually merged/composed for the shared mobject.

At minimum this seems worth a clear callout in the `Mobject.animate` / `_AnimationBuilder` docs, since I couldn't find this order-dependency documented anywhere, and it produces no error or warning — just a quietly wrong final frame.

## How to reproduce the issue

Code for reproducing the problem

```py
from manim import *

class AnimateConflict(Scene):
def construct(self):
square = Square(color=BLUE).shift(LEFT * 3)
circle = Circle(color=RED).shift(RIGHT * 3)
group = VGroup(square, circle)
self.add(group)

self.play(
group.animate.shift(RIGHT * 2),
circle.animate.set_opacity(0.5),
)
print(circle.get_center()[0]) # 3.0 -- expected 5.0, the group shift is lost
print(circle.fill_opacity) # 0.5 -- correct

self.clear()
square2 = Square(color=BLUE).shift(LEFT * 3)
circle2 = Circle(color=RED).shift(RIGHT * 3)
group2 = VGroup(square2, circle2)
self.add(group2)

# Only the ARGUMENT ORDER changes here:
self.play(
circle2.animate.set_opacity(0.5),
group2.animate.shift(RIGHT * 2),
)
print(circle2.get_center()[0]) # 5.0 -- correct this time
print(circle2.fill_opacity) # 0.5 -- correct
```

## Logs

Both `self.play()` calls run without any error, warning, or log output pointing at the discrepancy — the only way to notice is inspecting the resulting mobject state (or seeing the wrong frame in the rendered video).

## System specifications

System Details

- OS: macOS 26.5.1
- Python version: 3.12.13
- Manim version: 0.20.1

## Additional comments

Ran into this building an animation where a "row" `VGroup` was repositioned via `row.animate.arrange(...)` in the same `self.play()` as a newly-added child's own `.animate.set_opacity(1)` fade-in — the child's position silently reverted to its pre-animation spot every time, with no indication of why. Reordering the two animations (or, more robustly, computing target positions on a detached copy and animating each mobject individually) worked around it, but I only found the cause by reading the `Animation`/`_AnimationBuilder` source.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Reproduce the order-dependent result from the issue, then read mobject.animate, _AnimationBuilder.build(), and Animation.interpolate_mobject to confirm how shared mobject families are updated. Decide whether overlapping animations should warn or compose, and add coverage for both argument orders; document the behavior in the Mobject.animate or _AnimationBuilder documentation if composition is not implemented.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
computer-graphics
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.