最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

transform - Why doesn't ReplacementTransform work consistently? - Stack Overflow

programmeradmin2浏览0评论

I took the example from /en/stable/reference/manim.animation.transform.ReplacementTransform.html and changed it a bit (I added more numbers):

class ReplacementTransformOrTransform(Scene):
    def construct(self):
     # set up the numbers
        r_transform = VGroup(*[Integer(i) for i in range(1, 5)])
        text_1 = Text("ReplacementTransform", color=RED)
        r_transform.add(text_1)

        transform = VGroup(*[Integer(i) for i in range(5, 9)])
        text_2 = Text("Transform", color=BLUE)
        transform.add(text_2)

        ints = VGroup(r_transform, transform)
        texts = VGroup(text_1, text_2).scale(0.75)
        r_transform.arrange(direction=UP, buff=1)
        transform.arrange(direction=UP, buff=1)

        ints.arrange(buff=2)
        self.add(ints, texts)

        # The mobs replace each other and none are left behind
        self.play(ReplacementTransform(r_transform[0], r_transform[1]))
        self.play(ReplacementTransform(r_transform[1], r_transform[2]))
        self.play(ReplacementTransform(r_transform[2], r_transform[3]))

        # The mobs linger after the Transform()
        self.play(Transform(transform[0], transform[1]))
        self.play(Transform(transform[1], transform[2]))
        self.play(Transform(transform[2], transform[3]))
        self.wait()

When I run it, this happens:

  1. The 1 goes to 2, leaving nothing behind.
  2. The 2 goes to 3, leaving nothing behind.
  3. The 3 goes to 4, leaving another 3 behind (just like a regular Transform()).

Why? And more importantly: how do I move the 3 without leaving anything behind?

NOTE: I have a fresh reinstall with python 3.13.1 and manim community 0.19.0, they are the latest versions.

I took the example from https://docs.manim.community/en/stable/reference/manim.animation.transform.ReplacementTransform.html and changed it a bit (I added more numbers):

class ReplacementTransformOrTransform(Scene):
    def construct(self):
     # set up the numbers
        r_transform = VGroup(*[Integer(i) for i in range(1, 5)])
        text_1 = Text("ReplacementTransform", color=RED)
        r_transform.add(text_1)

        transform = VGroup(*[Integer(i) for i in range(5, 9)])
        text_2 = Text("Transform", color=BLUE)
        transform.add(text_2)

        ints = VGroup(r_transform, transform)
        texts = VGroup(text_1, text_2).scale(0.75)
        r_transform.arrange(direction=UP, buff=1)
        transform.arrange(direction=UP, buff=1)

        ints.arrange(buff=2)
        self.add(ints, texts)

        # The mobs replace each other and none are left behind
        self.play(ReplacementTransform(r_transform[0], r_transform[1]))
        self.play(ReplacementTransform(r_transform[1], r_transform[2]))
        self.play(ReplacementTransform(r_transform[2], r_transform[3]))

        # The mobs linger after the Transform()
        self.play(Transform(transform[0], transform[1]))
        self.play(Transform(transform[1], transform[2]))
        self.play(Transform(transform[2], transform[3]))
        self.wait()

When I run it, this happens:

  1. The 1 goes to 2, leaving nothing behind.
  2. The 2 goes to 3, leaving nothing behind.
  3. The 3 goes to 4, leaving another 3 behind (just like a regular Transform()).

Why? And more importantly: how do I move the 3 without leaving anything behind?

NOTE: I have a fresh reinstall with python 3.13.1 and manim community 0.19.0, they are the latest versions.

Share Improve this question asked Jan 24 at 10:02 SteeveDrozSteeveDroz 6,1366 gold badges35 silver badges69 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 1 +100

Explanation

The problem is that the ReplacementTransform of an Mobject that is on the scene with another Mobject that is already on the scene is not OK; the way ReplacementTransform is currently implemented assumes the new Mobject is not on the scene. That is indeed surprising, since your code is based on the one and only example in the documentation for ReplacementTransform

There are two positions in the source code that are relevant for this:

  1. ReplacementTrasnform(mobject, target_mobject) is essentially a Transform(mobject, target_mboject) followed by a scene.replace(mobject, target_mobject), see transform.py#L214, with transform.py#L294. Indeed, for all intents and purposes, in the original source code,
    self.play(ReplacementTransform(r_transform[0], r_transform[1]))
    
    can be substituted by
    self.play(Transform(r_transform[0], r_transform[1]))
    self.replace(r_transform[0], r_transform[1])
    
    self being here the Scene (the time of the call to replace might be different but the result is the same).
  2. The function Scene#replace(old_object, new_object) will just look in the scene .mobjects list and when it finds old_object, it puts new_object at the same index, see scene.py#L560.

This means that if new_object is already on the scene, a Mobject will be duplicated, added at the position of old_object, but also kept at its original position.

Indeed, in your scene (the ReplacementTransform part), you'll have the Integers:

  initially: 1 2 3 4
after step1: 2 2 3 4
after step2: 3 2 3 4
    finally: 4 2 3 4

The following change to your code only contains the ReplacemtTransform part and add an on-scene debug text that displays those Integers and their y coordinates at each of these steps:

from manim import *

class ReplacementTransformOrTransform(Scene):
    def construct(self):
        # set up the numbers
        r_transform = VGroup(*[Integer(i) for i in range(1, 5)])
        r_transform.arrange(direction=UP, buff=1)

        # step 0
        self.add(r_transform)

        fm = self.mobjects[0] # get_mobject_family_members()
        self.add_integers_debug_text(fm, "step 0: ", 0, -3.5)

        # step 1
        self.play(ReplacementTransform(r_transform[0], r_transform[1]))
        self.add_integers_debug_text(fm, "step 1: ", 0, -3)

        # step 2
        self.play(ReplacementTransform(r_transform[1], r_transform[2]))
        self.add_integers_debug_text(fm, "step 2: ", 0, -2.5)

        # step 3
        self.play(ReplacementTransform(r_transform[2], r_transform[3]))
        self.add_integers_debug_text(fm, "step 3: ", 0, -2)

        self.wait()


    def add_integers_debug_text(self, fm, prefix ="", x = 0, y = 0):
        self.add(Text(prefix +
             " | ".join([str(fmi.number)+'@y='+format(fmi.get_y(), ".2f") for fmi in fm if type(fmi).__name__ == 'Integer']),
                    font_size=22, font='monospace').set_x(x).set_y(y))

Here's the static final image that, if read from bottom up and left to right, explains why we see what we see at each step.

The change in coordinates for some of the Mobjects are the obvious result of the transform itself: 2 is raised from y = -0.67 to y = 0.66 in step 2, and 3 is raised from y = 0.66 to y = 2 in step 3, and since the duplicates are not clones but the same objects, those values repeat to the duplicates.

It's quite obvious, the result is a mess.

Possible solutions

A. New Mobjects are not on the scene

Avoid the situation where there's the new object is already on the scene:

class ReplacementTransformOrTransform(Scene):
    def construct(self):
        # set up the numbers
        r_transforms = [VGroup(*[Integer(i) for i in range(j, 5)]) for j in range(1, 5)]
        for r_transform in r_transforms:
            r_transform.arrange(direction=UP, buff=1).to_edge(UP, buff=1.5)

        # step 0
        self.add(r_transforms[0])

        # step 1
        self.play(ReplacementTransform(r_transforms[0], r_transforms[1]))

        # step 2
        self.play(ReplacementTransform(r_transforms[1], r_transforms[2]))

        # step 3
        self.play(ReplacementTransform(r_transforms[2], r_transforms[3]))

        self.wait()

This might be excessive for complicated scenes and for the type of transforms addressed here, since it involves multiplication of all objects of the VGroup that are not affected by the transform. In the example above there are 4 + 3 + 2 + 1 Integers constructed instead of the original 4.

B. Use Scene#remove

Call self.remove(mobject) (self being the Scene) after each transform step to fully remove the Mobject that are replaced:

class ReplacementTransformOrTransform(Scene):
    def construct(self):
        # set up the numbers
        r_transform = VGroup(*[Integer(i) for i in range(1, 5)])
        r_transform.arrange(direction=UP, buff=1)

        # step 0
        self.add(r_transform)

        # step 1
        self.play(ReplacementTransform(r_transform[0], r_transform[1]))
        self.remove(r_transform[0])

        # step 2
        self.play(ReplacementTransform(r_transform[1], r_transform[2]))
        self.remove(r_transform[1])

        # step 3
        self.play(ReplacementTransform(r_transform[2], r_transform[3]))
        self.remove(r_transform[2])

        self.wait()

Note that in self.remove(r_transform[0]), r_transform[0] which is the Integer for 1 is no longer in the scene since it was removed by replace as shown in the first section. This is to remove doesn't complain if the object is not found.

In any case, the call of both remove and replace (by the ReplacementTransform) is redundant, and indeed with remove, the same effect can be obtained by using Transform everywhere instead of ReplacementTransform.

发布评论

评论列表(0)

  1. 暂无评论