案例学习¶
声明
第一个示例翻译自EulerTour的教程,其余为鹤翔万里编写
样例 example_scenes.py
包含了一些学习manim的示例场景,可以运行测试,也可以入门学习
方变圆SquareToCircle¶
在manim文件夹中尝试执行运行命令
$ python -m manim example_scenes.py SquareToCircle -p #通过clone存储库安装
$ # manim example_scenes.py SquareToCircle -p # 通过pypi安装
SquareToCircle¶
from manimlib.imports import *
class SquareToCircle(Scene):
def construct(self):
circle = Circle()
square = Square()
square.flip(RIGHT)
square.rotate(-3 * TAU / 8)
circle.set_fill(PINK, opacity=0.5)
self.play(ShowCreation(square))
self.play(Transform(square, circle))
self.play(FadeOut(square))
注解
- 选项
-p
使用系统默认播放器播放渲染出的视频文件,没有指定质量,则默认使用-w
选项最高画质(1440p60),其他常用选项: -l
低分辨率480p15渲染更快-m
中等分辨率720p30-s
只导出最后一帧
运行 python -m manim -h
或 manim -h
查看所有可用选项
让我们一行一行解析 SquareToCircle
的代码
1 | from manimlib.imports import *
|
将manim中的所有类都引入进来,可以直接使用
3 | class SquareToCircle(Scene):
|
通过编写一个 Scene
的子类来创建一个场景,用于渲染出视频
4 | def construct(self):
|
在 construct()
方法中说明当 Scene
渲染时要进行什么操作
5 6 | circle = Circle()
square = Square()
|
Circle()
和 Square()
创建了 Circle
和 Square
的实例,即一个圆一个方
它们都是 Mobject
的子类,注意如果一个 Mobject
实例
没有添加到 Scene
中, 渲染之后就不会看到任何东西
7 8 9 | square.flip(RIGHT)
square.rotate(-3 * TAU / 8)
circle.set_fill(PINK, opacity=0.5)
|
flip()
rotate()
set_fill()
在执行动画之前应用了一些mobjects的变换
调用
set_fill()
设置了Circle
填充颜色为粉色(PINK), 不透明度(opacity)为0.5.
详细教程可以看 〔manim教程〕第一讲 物体的位置与坐标变换
11 12 13 | self.play(ShowCreation(square))
self.play(Transform(square, circle))
self.play(FadeOut(square))
|
实例化 Animation
构建动画
每个 Animation
传入一个或多个 Mobject
对象参数
传递给 play()
呈现出动画,构建视频。
Mobject
实例会自动添加到 Scene
中,当使用动画时
你可以把 Mobject
通过使用 add()
方法手动添加到 Scene
中
扭曲正方形WarpSquare¶
WarpSquare¶
from manimlib.imports import *
class WarpSquare(Scene):
def construct(self):
square = Square()
self.play(ApplyPointwiseFunction(
lambda point: complex_to_R3(np.exp(R3_to_complex(point))),
square
))
self.wait()
前四行和前面的一样,不重复了。第五行同样创建了一个默认的正方形
6 7 8 9 | self.play(ApplyPointwiseFunction(
lambda point: complex_to_R3(np.exp(R3_to_complex(point))),
square
))
|
从第六行开始,执行了一个动画 ApplyPointwiseFunction
,
传入了一个函数 lambda point: complex_to_R3(np.exp(R3_to_complex(point)))
这个函数的输入值是一个点坐标,先经过 R3_to_complex
函数将点坐标转换为该点在复平面上代表的复数值,
后求了e指数,将其结果传入 complex_to_R3
函数,将结果的复数转换为在复平面上的点坐标。
将这个函数和物体square传入 ApplyPointwiseFunction
后,
会对square的点集施加这个函数的作用(将每个点设为将该点传入函数后的返回值),实现了复变换。
10 | self.wait()
|
添加了一个停顿,默认1秒,相当于 self.wait(1)
。类似的,可以使用 self.wait(3)
来停顿3秒。
书写文字WriteStuff¶
WriteStuff¶
from manimlib.imports import *
class WriteStuff(Scene):
def construct(self):
example_text = TextMobject(
"This is a some text",
tex_to_color_map={"text": YELLOW}
)
example_tex = TexMobject(
"\\sum_{k=1}^\\infty {1 \\over k^2} = {\\pi^2 \\over 6}",
)
group = VGroup(example_text, example_tex)
group.arrange(DOWN)
group.set_width(FRAME_WIDTH - 2 * LARGE_BUFF)
self.play(Write(example_text))
self.play(Write(example_tex))
self.wait()
5 6 7 8 | example_text = TextMobject(
"This is a some text",
tex_to_color_map={"text": YELLOW}
)
|
第五行到第八行创建了一个文字(TextMobject
),内容是”This is a some text”(打错字了)。
第七行传入了一个字典 tex_to_color_map
将”text”指定为黄色。这时 TextMobject
会自动识别拆分开字符串,将并将”text”部分设置为黄色。
9 10 11 | example_tex = TexMobject(
"\\sum_{k=1}^\\infty {1 \\over k^2} = {\\pi^2 \\over 6}",
)
|
第九行到第十一行创建了一个公式(TexMobject
),它使用LaTeX来渲染,所以使用LaTeX的公式语法,
并且在python中,需要将 \
转义写为 \\
,或者在字符串前加上 r
,例如这三行也可以写为:
9 10 11 | example_tex = TexMobject(
r"\sum_{k=1}^\infty {1 \over k^2} = {\pi^2 \over 6}",
)
|
12 13 14 | group = VGroup(example_text, example_tex)
group.arrange(DOWN)
group.set_width(FRAME_WIDTH - 2 * LARGE_BUFF)
|
VGroup
),包含前面创建的文字和公式arrange
方法,将 group
中的物体依次向下(DOWN)排列group
缩放到宽度为画面宽度,并且距离两边为 LARGE_BUFF
16 17 18 | self.play(Write(example_text))
self.play(Write(example_tex))
self.wait()
|
Write
动画”写”在画面中更新程序UpdatersExample¶
UpdatersExample¶
from manimlib.imports import *
class UpdatersExample(Scene):
def construct(self):
decimal = DecimalNumber(
0,
show_ellipsis=True,
num_decimal_places=3,
include_sign=True,
)
square = Square().to_edge(UP)
decimal.add_updater(lambda d: d.next_to(square, RIGHT))
decimal.add_updater(lambda d: d.set_value(square.get_center()[1]))
self.add(square, decimal)
self.play(
square.to_edge, DOWN,
rate_func=there_and_back,
run_time=5,
)
self.wait()
5 6 7 8 9 10 | decimal = DecimalNumber(
0,
show_ellipsis=True,
num_decimal_places=3,
include_sign=True,
)
|
DecimalNumber
,初始值为0show_ellipsis=True
,小数保留3位 num_decimal_places=3
,正数包含正号 include_sign=True
13 14 | decimal.add_updater(lambda d: d.next_to(square, RIGHT))
decimal.add_updater(lambda d: d.set_value(square.get_center()[1]))
|
从第十三行起为这个数字添加了两个更新程序(updater
)
1. 将这个数字始终放在正方形右侧(即始终调用 next_to
这个方法来维护数字的位置)
2. 将这个数字的值始终设置为正方形在画面中的纵坐标
设置了updater之后,每一帧在运行时都会调用传入的函数来更新当前物体,所以传入的函数的参数为一个物体,没有返回值,在函数内部调用这个物体的方法来维护属性
15 16 17 18 19 20 21 | self.add(square, decimal)
self.play(
square.to_edge, DOWN,
rate_func=there_and_back,
run_time=5,
)
self.wait()
|
第十五行直接将数字和正方形添加在画面中,即视频一开始两物体就已经存在于画面中了
square.to_edge, DOWN
表示将 square
执行了 .to_edge(DOWN)
之后设置为目标,并且变换到那个位置处rate_func=there_and_back
指明了当前动画使用的速率函数为 there_and_back
,即到位置后再回来run_time=5
指明了当前动画需要5秒整体示例OpeningManimExample¶
在看过了前面的例子之后,文件中的第一个视频就容易理解了
OpeningManimExample¶
from manimlib.imports import *
class OpeningManimExample(Scene):
def construct(self):
title = TextMobject("This is some \\LaTeX") # 文字
basel = TexMobject( # 公式
"\\sum_{n=1}^\\infty "
"\\frac{1}{n^2} = \\frac{\\pi^2}{6}"
)
VGroup(title, basel).arrange(DOWN) # 集合到一起后排列位置
self.play(
Write(title), # "写"出title文字
FadeInFrom(basel, UP), # 将basel公式从上方淡入
)
self.wait() # 停顿一秒
transform_title = TextMobject("That was a transform")
transform_title.to_corner(UP + LEFT) # 放到最左上角
self.play(
Transform(title, transform_title), # 将title变换为transform_title
LaggedStart(*map(FadeOutAndShiftDown, basel)), # 将basel公式中的每个字符依次从下方淡出
)
self.wait() # 停顿一秒
grid = NumberPlane() # 构建一个坐标平面
grid_title = TextMobject("This is a grid")
grid_title.scale(1.5)
grid_title.move_to(transform_title)
self.add(grid, grid_title) # 确保grid_title在grid上方
self.play(
FadeOut(title), # 淡出title
FadeInFromDown(grid_title), # 从下方淡入grid_title
ShowCreation(grid, run_time=3, lag_ratio=0.1), # 创建grid的动画,时长为3,延迟为10%
)
self.wait()
grid_transform_title = TextMobject(
"That was a non-linear function \\\\"
"applied to the grid"
)
grid_transform_title.move_to(grid_title, UL)
grid.prepare_for_nonlinear_transform() # 让grid准备进行非线性变换
self.play(
grid.apply_function, # 对grid施加一个函数,实现非线性变换
lambda p: p + np.array([ # 输入值为一个点,返回值也为一个点
np.sin(p[1]),
np.sin(p[0]),
0,
]),
run_time=3,
)
self.wait()
self.play(
Transform(grid_title, grid_transform_title)
)
self.wait()