manim icon indicating copy to clipboard operation
manim copied to clipboard

Select SVG shapes by id

Open tobiasBora opened this issue 1 year ago • 9 comments

Description of proposed feature

In the SVG format, it is possible to add an id attribute on a shape/group of shapes. It would be great to allow manim to get the list of sub-objects corresponding to a given id (for now get_mobjects_from will not be stable if later-on I add elements to my object).

How can the new feature be used?

I could do a complicated drawing in inkscape, and animate it in manim, for instance by hiding/showing/moving elements based on their ID etc.

tobiasBora avatar Sep 30 '24 11:09 tobiasBora

you can already now address all separate paths and features from an SVG file by its position in the file - they all get an index in the list of sub-mobjects depending on their ordering in the SVG file. But yes, this is easily confused when you later modify the file.

uwezi avatar Oct 01 '24 18:10 uwezi

Yes exactly. And I guess it is not obvious how to find the good element in a huge list.

tobiasBora avatar Oct 02 '24 10:10 tobiasBora

My approach:

bild

class index(Scene):
    def construct(self):
        for x in range(-7, 8):
            for y in range(-4, 5):
                if (x==0) and (y==0):
                    self.add(Dot(np.array([x, y, 0]),radius=0.03, color=RED))
                else:
                    self.add(Dot(np.array([x, y, 0]),radius=0.03, color=DARK_GREY))

        circuit = SVGMobject(r"bilder/20240924_rinrut_03.svg",
            stroke_color=WHITE, stroke_width=3, fill_color=WHITE,width=10)

        self.add(circuit)
        #self.wait(2)

        bbox = Rectangle(height=circuit.height, width=circuit.width).move_to(circuit.get_center(),ORIGIN)
        bbox.scale(1.4)
        loc = bbox.get_critical_point(UL)
        w = bbox.width
        h = bbox.height
        cf = 2*w + 2*h
        dl = cf / (len(circuit)+3)

        dir   = [dl,0,0]
        edge = 0
        positions = []
        for i in range(len(circuit)):
            positions.append(loc)
            loc = loc + dir
            if (edge == 0) and (loc[0] > bbox.get_critical_point(UP+RIGHT)[0]):
                edge = 1
                loc = loc - dir
                dir = [0,-dl,0]
                loc = loc + dir

            if (edge == 1) and (loc[1] < bbox.get_critical_point(DOWN+RIGHT)[1]):
                edge = 2
                loc = loc - dir
                dir = [-dl,0,0]
                loc = loc + dir

            if (edge == 2) and (loc[0] < bbox.get_critical_point(DOWN+LEFT)[0]):
                edge = 3
                loc = loc - dir
                dir = [0,+dl,0]
                loc = loc + dir

        for i in range(len(circuit)):
            shortest = 1e6
            found = 0
            for j in range(len(positions)):
                dist = np.sqrt((circuit[i].get_center()[0]-positions[j][0])**2 + (circuit[i].get_center()[1]-positions[j][1])**2)
                if dist < shortest:
                    shortest = dist
                    found = j


            color = [BLUE,GREEN,RED,YELLOW,PURPLE][i%5]
            circuit[i].set_color(color)
            txt = Text("{}".format(i)).scale(0.15).move_to(positions[found]).set_color(color)

            line = Line(circuit[i].get_center(),end=txt.get_center(), stroke_width=1).set_color(color)

            self.add(line)
            self.add(txt)
            # self.wait(1)

            positions.pop(found)

uwezi avatar Oct 02 '24 11:10 uwezi

Ahah fun workaround to identify the element on medium-sized picture, thanks. Another advantage of id also is that it would (hopefully) also work for groups, so that I can refer to hundred elements at once using a single name.

tobiasBora avatar Oct 02 '24 17:10 tobiasBora

I would love to be able to access SVG elements by ID.

I think an ideal implementation of SVGMobject would preserve the hierarchical structure of the underlying SVG. Currently (and afaik), the list svg.submobjects contains a flat list of all path elements but no information about the initial hierarchy of groups. I would love to have svg.submobjects containing VGroups as well as simple VMobjectFromSVGPaths, with those VGroups also being able to contain other Vgroup and so on, each group/path tagged with their appropriate ID.

leleogere avatar Apr 11 '25 21:04 leleogere

After some research, the possibility to have a VGroup hierarchy in SVGs once existed in manim through the boolean parameter unpack_groups of SVGMobject, but was removed in 309c9d41eb734ca85a7aea5533f88a6d4ee7c944 when porting the SVG parsing implementation from 3b1b version. However, it does not seem that id was tracked, but I think it wouldn't have been very complex to add with this previous implementation. I'm going to have a look at parsing SVG groups, as well as ID and class attributes of elements.

leleogere avatar Apr 12 '25 09:04 leleogere

It would absolutely make sense to support accessing the id of SVG elements (and it is actually sort of surprising that we currently don't support it). Interface-wise, a method some_svg_mobject.get_by_id(svg_id: str) would be nice; the simplest way to implement it would probably be something along the lines of doing some bookkeeping in an additional dictionary, say, SVGMobject._id_map which can be populated while parsing the SVG / converting the corresponding objects.

IIRC there was a reason why we removed unpack_groups, I think it caused a bunch of issues at the time -- but I can obviously see how it would still be very useful. Might be worth investigating again; thanks for taking a look @leleogere!

behackl avatar Apr 12 '25 09:04 behackl

Do you think the individual MObjects should also have an .id attribute? Or simply the dict is enough?

I think that being able to access the groups would be extremely useful. For complex SVG, I won't usually give an id to each individual paths, but to more complex groups representing elements. For example, in a SVG musical score, if I want to manipulate a chord, I would currently first need to retrieve each individual noteheat + stem + articulation... With groups being supported, I could simply reference the group ID, and get the VGroup corresponding to the whole chord.

leleogere avatar Apr 12 '25 09:04 leleogere

A stricter interface design would be only doing the bookkeeping on the level of SVGMobject; the children technically don't need to know about their id from the SVG. However, it might be that for some concrete use cases this becomes more inconvenient -- hard to say without thinking about it some more.

Supporting groups definitely would be nice too; I don't recall the exact problems that we found when we tried that last time. Give it a try and you will see whether its just a matter of some recursive parsing, or whehter there is more to it. 😄

behackl avatar Apr 12 '25 09:04 behackl