Custom Mobjects#
[1]:
from manim import *
config.media_embed = True; config.media_width = "100%"
_RV = "-v WARNING -qm --progress_bar None --disable_caching Example"
_RI = "-v WARNING -s --progress_bar None --disable_caching Example"
Manim Community v0.15.1
In this course we will create custom VMobjects
and VGroups
, in the advanced course we will learn how to use the rest of the Mobjects.
The main difference between VMobjects
and VGroups
is that in VMobjects
we cannot use subscripts to manipulate submobjects, while VGroups
(and Groups
) do accept subscripting.
The recommendation is that if you are going to manipulate the Mobject using subscripts then choose VGroup
, if you are not going to manipulate the Mobject
with subscripts then use VMobject
.
Let’s start by explaining how VMobjects are built knowing the corners or control points of the bezier curves.
Koch fractal#
Let’s build a VMobject
that returns the Koch fractal, this VMobject
needs two points, the starting point \(\vec{p}_s\) and the ending point \(\vec{p}_e\).
The way to get the corners is like this:
\(\displaystyle \vec{L}_u:\) Unit vector of \(\vec{L}\).
\(\displaystyle \vec{L}_{Ru}:\) \(\vec{L}_u\) rotated 90°.
\(\displaystyle \vec{h}=\vec{L}_{Ru} * h\)
\(\displaystyle \vec{p}_1 = \vec{p}_s + \frac{\vec{L}}{3}\).
\(\displaystyle \vec{p}_2 = \vec{p}_s + \frac{\vec{L}}{2} + \vec{h}\).
\(\displaystyle \vec{p}_3 = \vec{p}_s + \frac{2\vec{L}}{3}\).
[2]:
def get_norm(vect):
return sum((x**2 for x in vect))**0.5
[3]:
class TriangleFractal(VMobject):
def __init__(self, start, end, **kwargs):
self.start = start
self.end = end
super().__init__(**kwargs)
def generate_points(self):
self.set_points_as_corners(
self.get_next_point(self.start, self.end)
)
def get_next_point(self, ps, pe):
L = pe - ps
length = get_norm(L)
L_u = L / length
L_u_rotated = rotate_vector(L_u, PI/2)
h = np.sqrt(3) * length / 6
p1 = ps + L / 3
p2 = ps + L / 2 + L_u_rotated * h
p3 = ps + L * 2 / 3
return [ps, p1, p2, p3, pe]
[4]:
class Example(Scene):
def construct(self):
p_s = Dot(LEFT*3)
p_e = Dot(UR+RIGHT*2)
l = TriangleFractal(p_s.get_center(), p_e.get_center())
self.add(p_s, p_e, l)
%manim $_RI

Custom ArrowDashed#
We can also create a VMobject
by adding other VMobjects
.
Let’s create a custom arrow.
[5]:
class ArrowDashed(VMobject):
def __init__(self,
start,
end,
reverse=False,
buff=0,
line_class=DashedLine,
tip_angle=20*DEGREES,
**kwargs):
super().__init__(**kwargs)
if reverse:
_tmp = start.copy()
start = end
end = _tmp
self.main_line = line_class(start, end, **kwargs)
self.unit_vector = self.main_line.get_unit_vector()
self.shift_vector = rotate_vector(self.unit_vector, PI/2)
self.line_angle = self.main_line.get_angle()
self.tip_angle = tip_angle
self.add(self.main_line)
self.add_right_tip()
self.add_left_tip()
self.shift(self.shift_vector * buff)
self.set_color(self.main_line.get_color())
def add_right_tip(self, start_angle=0, line_size=0.3, invert=False):
line = self.main_line
sign = -1 if invert else 1
index = -1 if invert else 0
tip_vector = line.get_unit_vector() * line_size
start = line.get_all_points()[index]
down_tip = Line(start , start + tip_vector * sign)
up_tip = down_tip.copy()
normal_line = up_tip.copy()
down_tip.rotate(self.tip_angle+start_angle, about_point=start)
up_tip.rotate(-self.tip_angle+start_angle, about_point=start)
normal_line.rotate(PI/2).move_to(start)
self.add(down_tip, up_tip, normal_line)
def add_left_tip(self, start_angle=0, line_size=0.3, invert=True):
self.add_right_tip(start_angle, line_size, invert)
def get_label(self, tex, buff=0.2):
tex.move_to(self.get_center())
tex.shift(self.shift_vector * buff)
return tex
def add_label(self, *args, **kwargs):
tex = self.get_label(*args, **kwargs)
self.add(tex)
return self
[6]:
class Example(Scene):
def construct(self):
sq = Square().scale(2)
sq.rotate(30*DEGREES)
v1, v2 = sq.get_vertices()[:2]
arrow = ArrowDashed(v1, v2, buff=-0.2, color=RED)
label = arrow.get_label(MathTex("x"), buff=-0.3)
self.add(sq)
self.play(GrowFromCenter(arrow))
self.play(Write(label))
self.wait()
%manim $_RV
The creation of VGroups
is equivalent to VMobjects
, it will be studied in depth in the video tutorials.
Homework#
Make a similar object but with angles, the VMobject must receive an Arc and return another one but with the tips and with a radius greater than the initial arc.
SOLUTION
class ArcDashedVMobject(DashedVMobject):
def __init__(self,
vmobject,
num_dashes=30,
dashed_ratio=0.6,
dash_offset=0.4,
color=WHITE,
equal_lengths=True,
**kwargs):
super().__init__(vmobject,
num_dashes,
dashed_ratio,
dash_offset,
color,
equal_lengths,
**kwargs)
class ArcArrowDashed(VMobject):
def __init__(self,
arc: Arc,
reverse=False,
buff=0.2,
line_class=ArcDashedVMobject,
tip_angle=20*DEGREES,
**kwargs):
super().__init__(**kwargs)
if reverse:
_tmp = start.copy()
start = end
end = _tmp
arc_radius = arc.radius + buff
arc_center = arc.arc_center
start_angle = arc.start_angle
angle = arc.angle
self.arc = Arc(arc_radius,start_angle,angle,arc_center=arc_center,**kwargs)
if line_class is not None:
self.main_arc = line_class(self.arc)
else:
self.main_arc = self.arc
self.tip_angle = tip_angle
self.add(self.main_arc)
self.add_right_tip()
self.add_left_tip()
self.set_color(self.main_arc.get_color())
def add_right_tip(self, start_angle=0, line_size=0.3, invert=False):
arc = self.arc
sign = -1 if invert else 1
index = -1 if invert else 0
tl = TangentLine(arc,abs(index),length=line_size)
tl.shift(sign * tl.get_vector()/2)
start = arc.point_from_proportion(abs(index))
down_tip = tl
up_tip = tl.copy()
normal_line = tl.copy()
down_tip.rotate(self.tip_angle+start_angle, about_point=start)
up_tip.rotate(-self.tip_angle+start_angle, about_point=start)
normal_line.rotate(PI/2).move_to(start)
self.add(down_tip, up_tip, normal_line)
def add_left_tip(self, start_angle=0, line_size=0.3, invert=True):
self.add_right_tip(start_angle, line_size, invert)
class Example(Scene):
def construct(self):
arc = Arc(2.5,-60*DEGREES,PI*0.9)
measure = ArcArrowDashed(arc,color=RED)
self.add(arc, measure)
