Working with SVG Files – manim Series: Part 12

The post is part of a series on learning how to use manim.  You can find the previous tutorial post in this series here and the overview of the entire series here.

Important Note:  These posts are based on an earlier version of manim which uses Python 2.7.  The latest version of manim is using Python 3.  To follow along with these posts, use Python 2.7 and the May 9, 2018 commit of manim .

12.0 Working with SVG Files

The PiCreatures in 3B1B are scalable vector graphics (svg) files. manim has an SVGMobject class that can import svg files. To play around with using svg images in manim, I’ve created a couple of figures using (Inkscape)[https://inkscape.org/en/], an open source vector graphics package. I wanted to try to make a stick figure wave in manim so I created two figures, one normal and one with the hand waving. You can get the svg files I’ve used at the end of this post. Place them in the \design\svg_images\ folder inside your media directory (the top level directory where all the animation subfolders get created).

The code I used to import the stick figure was based on the PiCreature code located in \for_3b1b_videos\pi_creatures.py. My code looks like:

class StickMan(SVGMobject):
CONFIG = {
"color" : BLUE_E,
"stroke_width" : 2,
"stroke_color" : WHITE,
"fill_opacity" : 1.0,
"propagate_style_to_family" : True,
"height" : 3,
"corner_scale_factor" : 0.75,
"flip_at_start" : False,
"is_looking_direction_purposeful" : False,
"start_corner" : None,
#Range of proportions along body where arms are
"right_arm_range" : [0.55, 0.7],
"left_arm_range" : [.34, .462],
}
def __init__(self, mode = "plain", **kwargs):
self.parts_named = False
try:
svg_file = os.path.join(
SVG_IMAGE_DIR,
"stick_man_%s.svg"%mode
)
SVGMobject.__init__(self, file_name = svg_file, **kwargs)
except:
warnings.warn("No StickMan design with mode %s"%mode)
svg_file = os.path.join(
SVG_IMAGE_DIR,
"stick_man.svg"
)
SVGMobject.__init__(self, file_name = svg_file, **kwargs)

if self.flip_at_start:
self.flip()
if self.start_corner is not None:
self.to_corner(self.start_corner)

def name_parts(self):
#self.mouth = self.submobjects[MOUTH_INDEX]
self.head = self.submobjects[HEAD_INDEX]
self.body = self.submobjects[BODY_INDEX]
self.arms = self.submobjects[ARMS_INDEX]
self.legs = self.submobjects[LEGS_INDEX]
self.parts_named = True

def init_colors(self):
SVGMobject.init_colors(self)
if not self.parts_named:
self.name_parts()
self.head.set_fill(RED, opacity = 0)
self.body.set_fill(self.color, opacity = 1)
self.arms.set_fill(YELLOW, opacity = 0)
self.legs.set_fill(GREEN, opacity = 0)
return self

I’m sure I could trim the code more but I just wanted something that would work without too much debugging. To animate the waving, I’ve used get_all_pi_creature_modes to load the file with the waving hand. To do this, the svg file with the wave must have a similar file name. In particular, if the original file is named stick_man.svg, the other file name needs to start with the same stick_man followed by an underscore and the name of the particular mode. Thus the figure with hand extended in a wave is called stick_man_wave.svg. I can create an instance of the stick man using the waving figure using StickMan("wave"). Below is the code for the scene to make the stick man wave.

class SVGStickMan(Scene):
def construct(self):
start_man = StickMan()
plain_man = StickMan()
waving_man = StickMan("wave")

self.add(start_man)
self.wait()
self.play(Transform(start_man,waving_man))
self.play(Transform(start_man,plain_man))

The reason I create two instances of the StickMan() is because I am transforming start_man but want the image to end up back looking like the original figure.

Two things to note. (1) The stroke_width and stroke_color for PiCreatures are set to not draw the outline of objects. If you want to see lines or the outlines of shapes you will need to set these values to something visible (i.e. non-zero stroke_width and a stroke_color that is different than the background). (2) Lines in svg are labeled as paths. The way manim deals with paths it to treat them as closed shapes. That means that if I don’t set the opacity to zero for a line, I will see an enclosed shape. See the video below where I’ve set the fill_opacity in the init_colors method for everything to 1.

Although I haven’t delved into the manim code, I think all manim looks at is the outlines of the shapes and not the filling.

I created a second scene just to make sure I had a handle on the scalable vector graphics import. When creating your own images, you will need to open the .svg file in a text editor to determine the indices for each submobject. manim imports each svg entity (e.g. a path, ellipse, box, or other shape) as a single submobject, and you will need to determine the ordering of those items in the parent SVGMobject class. I created a couple of shapes ( a circle connected by lines to a pair of squares). The relevant part of the svg file for this is shown here (there is a lot more metadata in the file I left out):

<br />

The file contains a circle, two paths, and two rectangles. Thus, when imported into manim, the circle will be the first submobject (index of 0), the two paths or lines will be the second and third submobject (indices 1 and 2) and the two squares will be the fourth and fifth submobject (indices 3 and 4). The class I used for this circle and square drawing is

class CirclesAndSquares(SVGMobject):
CONFIG = {
"color" : BLUE_E,
"stroke_width" : 2,
"stroke_color" : WHITE,
"fill_opacity" : 1.0,
"propagate_style_to_family" : True,
"height" : 3,
"corner_scale_factor" : 0.75,
"flip_at_start" : False,
"is_looking_direction_purposeful" : False,
"start_corner" : None,
"circle_index" : 0,
"line1_index" :1,
"line2_index" : 2,
"square1_index" : 3,
"square2_index" : 4,
}
def __init__(self, mode = "plain", **kwargs):
try:
svg_file = os.path.join(
SVG_IMAGE_DIR,
"circles_and_squares_%s.svg"%mode
)
SVGMobject.__init__(self, file_name = svg_file, **kwargs)
except:
warnings.warn("No other mode design with mode %s"%mode)
svg_file = os.path.join(
SVG_IMAGE_DIR,
"circles_and_squares.svg"
)
SVGMobject.__init__(self, file_name = svg_file, **kwargs)

def name_parts(self):
self.circle = self.submobjects[self.circle_index]
self.line1 = self.submobjects[self.line1_index]
self.line2 = self.submobjects[self.line2_index]
self.square1 = self.submobjects[self.square1_index]
self.square2 = self.submobjects[self.square2_index]
self.parts_named = True

def init_colors(self):
SVGMobject.init_colors(self)
self.name_parts()
self.circle.set_fill(RED, opacity = 1)
self.line1.set_fill(self.color, opacity = 0)
self.line2.set_fill(self.color, opacity = 0)
self.square1.set_fill(GREEN, opacity = 1)
self.square2.set_fill(BLUE, opacity = 1)
return self

I’ve used the order of the different elements in the svg file to label the indices in my CONFIG dictionary at the start of the class. The code to display this on the screen is

class SVGCircleAndSquare(Scene):
def construct(self):
thingy = CirclesAndSquares()

self.add(thingy)
self.wait()

I know I can trim the code down for the svg class but I’ll save that for another day.


SVG Files

For some reason I’m not allowed to upload svg files to WordPress so I’ve included the full files here. Copy and paste each chunk of code into a separate text file and save it with a .svg extension). You can also find the files at https://github.com/zimmermant/manim_tutorial

stick_man.svg

<!--?xml version="1.0" encoding="UTF-8" standalone="no"?-->
<!-- Created with Inkscape (http://www.inkscape.org/) -->

image/svg+xml

stick_man_wave.svg

<!--?xml version="1.0" encoding="UTF-8" standalone="no"?-->
<!-- Created with Inkscape (http://www.inkscape.org/) -->

image/svg+xml

circles_and_squares.svg

<!--?xml version="1.0" encoding="UTF-8" standalone="no"?-->
<!-- Created with Inkscape (http://www.inkscape.org/) -->

image/svg+xml

Advertisements
This entry was posted in Just for Fun, Programming and tagged , , , . Bookmark the permalink.

4 Responses to Working with SVG Files – manim Series: Part 12

  1. Pingback: Fields of a Moving Charge – manim Series: Part 11 | Talking Physics

  2. Pingback: Learning How To Animate Videos Using manim Series – A Journey | Talking Physics

  3. hui says:

    This is amazing job, will u continue to post this series?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.