Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TexText.make_number_changeable will not work properly #2206

Open
Slarper opened this issue Oct 13, 2024 · 0 comments
Open

TexText.make_number_changeable will not work properly #2206

Slarper opened this issue Oct 13, 2024 · 0 comments
Labels

Comments

@Slarper
Copy link

Slarper commented Oct 13, 2024

Describe the bug

Today I try the UpdatersExample in example_scenes.py.

class UpdatersExample(Scene):
    def construct(self):

        square = Square()
        square.set_fill(BLUE_E, 1)

        # On all frames, the constructor Brace(square, UP) will
        # be called, and the mobject brace will set its data to match
        # that of the newly constructed object
        brace = always_redraw(Brace, square, UP)

        label = TexText("Width = 0.00")
        number = label.make_number_changeable("0.00", replace_all=True)

        # This ensures that the method deicmal.next_to(square)
        # is called on every frame
        label.always.next_to(brace, UP)
        # You could also write the following equivalent line
        # label.add_updater(lambda m: m.next_to(brace, UP))

        # If the argument itself might change, you can use f_always,
        # for which the arguments following the initial Mobject method
        # should be functions returning arguments to that method.
        # The following line ensures thst decimal.set_value(square.get_y())
        # is called every frame
        number.f_always.set_value(square.get_width)
        # You could also write the following equivalent line
        # number.add_updater(lambda m: m.set_value(square.get_width()))

        self.add(square, brace, label)

        # Notice that the brace and label track with the square
        self.play(
            square.animate.scale(2),
            rate_func=there_and_back,
            run_time=2,
        )
        self.wait()
        self.play(
            square.animate.set_width(5, stretch=True),
            run_time=3,
        )
        self.wait()
        self.play(
            square.animate.set_width(2),
            run_time=3
        )
        self.wait()

        # In general, you can alway call Mobject.add_updater, and pass in
        # a function that you want to be called on every frame.  The function
        # should take in either one argument, the mobject, or two arguments,
        # the mobject and the amount of time since the last frame.
        now = self.time
        w0 = square.get_width()
        square.add_updater(
            lambda m: m.set_width(w0 * math.sin(self.time - now) + w0)
        )
        self.wait(4 * PI)

and it raises IndexError: list index out of range
(I am not sure if others encounter it)

image

During 3 hours further exploring, I believe that it raises because the label = TexText("Width = 0.00") has a self.submobjects=[]. So the next line number = label.make_number_changeable("0.00", replace_all=True) will raise Error here in last 2 lines.

    def make_number_changeable(
        self,
        value: float | int | str,
        index: int = 0,
        replace_all: bool = False,
        **config,
    ) -> VMobject:
        substr = str(value)
        parts = self.select_parts(substr)
        if len(parts) == 0:
            log.warning(f"{value} not found in Tex.make_number_changeable call")
            return VMobject()
        if index > len(parts) - 1:
            log.warning(f"Requested {index}th occurance of {value}, but only {len(parts)} exist")
            return VMobject()
        if not replace_all:
            parts = [parts[index]]

        from manimlib.mobject.numbers import DecimalNumber

        decimal_mobs = []
        for part in parts:
            if "." in substr:
                num_decimal_places = len(substr.split(".")[1])
            else:
                num_decimal_places = 0
            decimal_mob = DecimalNumber(
                float(value),
                num_decimal_places=num_decimal_places,
                **config,
            )
            decimal_mob.replace(part)
            decimal_mob.match_style(part)
            if len(part) > 1:
                self.remove(*part[1:])
+++++    self.replace_submobject(self.submobjects.index(part[0]), decimal_mob)  <======== the part[0] will raise Error, because it has no submobjects!
            decimal_mobs.append(decimal_mob)

So how did the empty part generate?we can see parts = self.select_parts(substr)
the select_parts will fall back into select_unisolated_substring in this case, finally produced by last 2 lines of it.

    def select_unisolated_substring(self, pattern: str | re.Pattern) -> VGroup:
        if isinstance(pattern, str):
            pattern = re.compile(re.escape(pattern))
        result = []
        for match in re.finditer(pattern, self.string):
            index = match.start()
            start = self.substr_to_path_count(self.string[:index])
            substr = match.group()
            end = start + self.substr_to_path_count(substr)
+++    result.append(self[start:end]) // <====== here, the self[start:end] has no elements(submobjects)!
        return VGroup(*result)

because StringMObject's get_item_ magic function will eventually fall back into MObject's get_item magic function:

    def __getitem__(self, value: int | slice) -> Mobject:
        if isinstance(value, slice):
            GroupClass = self.get_group_class()
            return GroupClass(*self.split().__getitem__(value))
        return self.split().__getitem__(value)

    def split(self) -> list[Self]:
        return self.submobjects

and self.submobjects of label = TexText("Width = 0.00") is []. that's all.

there are 2 ways to solve problems. First, modify TexText's init function to initialize its submobjects. Second, modify StringMObject's getitem magic to return correct submobjects before it falls back into MObject's.

Code:
To reproduce the bug:

git clone https://github.com/3b1b/manim.git
cd manim
pip install -e .
manimgl example_scenes.py UpdatersExample

Wrong display or Error traceback:

image

Additional context

@Slarper Slarper added the bug label Oct 13, 2024
@Slarper Slarper changed the title TexText will not create its submobjects when initialized. TexText.make_number_changeable will not work properly Oct 13, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant