manim
manim copied to clipboard
Select SVG shapes by id
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.
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.
Yes exactly. And I guess it is not obvious how to find the good element in a huge list.
My approach:
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)
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.
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.
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.
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!
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.
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. 😄