样例学习¶
在了解了前面的知识后,我们可以运行更多的场景了。
example_scenes.py
中给出了许多示例场景,让我们从简单的开始一个一个研究。
交互开发 InteractiveDevlopment¶
InteractiveDevelopment¶
from manimlib import *
class InteractiveDevelopment(Scene):
def construct(self):
circle = Circle()
circle.set_fill(BLUE, opacity=0.5)
circle.set_stroke(BLUE_E, width=4)
square = Square()
self.play(ShowCreation(square))
self.wait()
# 这会打开一个iPython终端,你可以在其中继续写你想要执行的代码
# 在这个例子中,square/circle/self都会成为终端中的实例
self.embed()
# 尝试拷贝粘贴下面这些行到交互终端中
self.play(ReplacementTransform(square, circle))
self.wait()
self.play(circle.animate.stretch(4, 0))
self.play(Rotate(circle, 90 * DEGREES))
self.play(circle.animate.shift(2 * RIGHT).scale(0.25))
text = Text("""
In general, using the interactive shell
is very helpful when developing new scenes
""")
self.play(Write(text))
# 在交互终端中,你可以使用play, add, remove, clear, wait, save_state
# 和restore来代替self.play, self.add, self.remove……
# 这时如果要使用鼠标键盘来与窗口互动,需要输入执行touch()
# 然后你就可以滚动窗口,或者在按住z时滚动来缩放
# 按住d时移动鼠标来更改相机视角,按r重置相机位置
# 按q退出和窗口的交互来继续输入其他代码
# 特别的,你可以自定一个场景来和鼠标和键盘互动
always(circle.move_to, self.mouse_point)
这个场景就是我们在 快速入门 中编写的。 这里不再解释。
方法动画 AnimatingMethods¶
AnimatingMethods¶
class AnimatingMethods(Scene):
def construct(self):
grid = Tex(r"\pi").get_grid(10, 10, height=4)
self.add(grid)
# 你可以通过.animate语法来动画化物件变换方法
self.play(grid.animate.shift(LEFT))
# 或者你可以使用旧的语法,把方法和参数同时传给Scene.play
self.play(grid.shift, LEFT)
# 这两种方法都会在mobject的初始状态和应用该方法后的状态间进行插值
# 在本例中,调用grid.shift(LEFT)会将grid向左移动一个单位
# 这种用法可以用在任何方法上,包括设置颜色
self.play(grid.animate.set_color(YELLOW))
self.wait()
self.play(grid.animate.set_submobject_colors_by_gradient(BLUE, GREEN))
self.wait()
self.play(grid.animate.set_height(TAU - MED_SMALL_BUFF))
self.wait()
# 方法Mobject.apply_complex_function允许应用任意的复函数
# 将把Mobject的所有点的坐标看作复数
self.play(grid.animate.apply_complex_function(np.exp), run_time=5)
self.wait()
# 更一般地说,你可以应用Mobject.apply方法,它接受从R^3到R^3的一个函数
self.play(
grid.animate.apply_function(
lambda p: [
p[0] + 0.5 * math.sin(p[1]),
p[1] + 0.5 * math.sin(p[0]),
p[2]
]
),
run_time=5,
)
self.wait()
这个场景中新出现的用法是``.get_grid()`` 和 self.play(mob.animate.method(args))
:
.get_grid()
方法会返回一个由该物体复制得到的阵列self.play(mob.animate.method(args))
动画化方法,详细用法在上面代码注释中说明了
文字示例 TextExample¶
TextExample¶
class TextExample(Scene):
def construct(self):
# 想要正确运行这个场景,你需要确保你的计算机中安装了Consolas字体
# 关于Text全部用法,请见https://github.com/3b1b/manim/pull/680
text = Text("Here is a text", font="Consolas", font_size=90)
difference = Text(
"""
The most important difference between Text and TexText is that\n
you can change the font more easily, but can't use the LaTeX grammar
""",
font="Arial", font_size=24,
# t2c是一个由 文本-颜色 键值对组成的字典
t2c={"Text": BLUE, "TexText": BLUE, "LaTeX": ORANGE}
)
VGroup(text, difference).arrange(DOWN, buff=1)
self.play(Write(text))
self.play(FadeIn(difference, UP))
self.wait(3)
fonts = Text(
"And you can also set the font according to different words",
font="Arial",
t2f={"font": "Consolas", "words": "Consolas"},
t2c={"font": BLUE, "words": GREEN}
)
fonts.set_width(FRAME_WIDTH - 1)
slant = Text(
"And the same as slant and weight",
font="Consolas",
t2s={"slant": ITALIC},
t2w={"weight": BOLD},
t2c={"slant": ORANGE, "weight": RED}
)
VGroup(fonts, slant).arrange(DOWN, buff=0.8)
self.play(FadeOut(text), FadeOut(difference, shift=DOWN))
self.play(Write(fonts))
self.wait()
self.play(Write(slant))
self.wait()
这个场景中新出现的类是 Text
,VGroup
,Write
,FadeIn
和 FadeOut
:
Text
可以创建文字,定义字体等。相关特性在上述例子中已经清晰体现。VGroup
可以将多个VMobject
放在一起看做一个整体。例子中调用了arrange()
方法来将其中子物体依次向下排列(DOWN
),且间距为buff
Write
是显示类似书写效果的动画FadeIn
将物体淡入,第二个参数表示淡入的方向FadeOut
将物体淡出,第二个参数表示淡出的方向
匹配变换 TexTransformExample¶
TexTransformExample¶
class TexTransformExample(Scene):
def construct(self):
to_isolate = ["B", "C", "=", "(", ")"]
lines = VGroup(
# 将多个参数传递给Tex,这些参数看起来被连接在一起作为一个表达式
# 但整个mobject的每个submobject为其中的一个字符串
# 例如,下面的Tex物件将有5个子物件,对应于表达式[A^2,+,B^2,=,C^2]
Tex("A^2", "+", "B^2", "=", "C^2"),
# 这里同理
Tex("A^2", "=", "C^2", "-", "B^2"),
# 或者,你可以传入关键字参数isolate,其中包含一个字符串列表
# 这些字符串应该作为它们自己的子物件存在
# 因此,下面的一行相当于它下面注释掉的一行
Tex("A^2 = (C + B)(C - B)", isolate=["A^2", *to_isolate]),
# Tex("A^2", "=", "(", "C", "+", "B", ")", "(", "C", "-", "B", ")"),
Tex("A = \\sqrt{(C + B)(C - B)}", isolate=["A", *to_isolate])
)
lines.arrange(DOWN, buff=LARGE_BUFF)
for line in lines:
line.set_color_by_tex_to_color_map({
"A": BLUE,
"B": TEAL,
"C": GREEN,
})
play_kw = {"run_time": 2}
self.add(lines[0])
# TransformMatchingTex将源和目标中具有匹配tex字符串的部分对应变换
# 传入path_arc,使每个部分旋转到它们的最终位置,这种效果对于重新排列一个方程是很好的
self.play(
TransformMatchingTex(
lines[0].copy(), lines[1],
path_arc=90 * DEGREES,
),
**play_kw
)
self.wait()
self.play(
TransformMatchingTex(lines[1].copy(), lines[2]),
**play_kw
)
self.wait()
# …这看起来很好,但由于在lines[2]中没有匹配"C^2"或"B^2"的tex,这些子物件会淡出
# 而C和B两个子物件会淡入,如果我们希望C^2转到C,而B^2转到B,我们可以用key_map来指定
self.play(FadeOut(lines[2]))
self.play(
TransformMatchingTex(
lines[1].copy(), lines[2],
key_map={
"C^2": "C",
"B^2": "B",
}
),
**play_kw
)
self.wait()
# 也许我们想把^2上的指数转换成根号。目前,lines[2]将表达式A^2视为一个单元
# 因此我们可能会需要创建同一line的新版本,该line仅分隔出A
# 这样,当TransformMatchingTex将所有匹配的部分对应时,唯一的不匹配将是来自new_line2的"^2"
# 和来自最终行的"\sqrt"之间的不匹配。通过传入transform_missmatches=True,它会将此"^2"转换为"\sqrt"
new_line2 = Tex("A^2 = (C + B)(C - B)", isolate=["A", *to_isolate])
new_line2.replace(lines[2])
new_line2.match_style(lines[2])
self.play(
TransformMatchingTex(
new_line2, lines[3],
transform_mismatches=True,
),
**play_kw
)
self.wait(3)
self.play(FadeOut(lines, RIGHT))
# 或者,如果您不想故意分解tex字符串,您可以使用TransformMatchingShapes
# 它将尝试将源mobject的所有部分与目标的部分对齐,而不考虑每个部分中的子对象层次结构
# 根据这些部分是否具有相同的形状(尽其所能)来自动匹配变换
source = Text("the morse code", height=1)
target = Text("here come dots", height=1)
self.play(Write(source))
self.wait()
kw = {"run_time": 3, "path_arc": PI / 2}
self.play(TransformMatchingShapes(source, target, **kw))
self.wait()
self.play(TransformMatchingShapes(target, source, **kw))
self.wait()
这个场景中新出现的类是 Tex
,TexText
,TransformMatchingTex
和 TransformMatchingShapes
:
Tex
利用 LaTeX 来创建数学公式TexText
利用 LaTeX 来创建文字TransformMatchingTeX
根据Tex
中 tex 的异同来自动对子物体进行Transform
TransformMatchingShapes
直接根据物体点集的异同来自动对子物体进行Transform
更新程序 UpdatersExample¶
UpdatersExample¶
class UpdatersExample(Scene):
def construct(self):
square = Square()
square.set_fill(BLUE_E, 1)
brace = always_redraw(Brace, square, UP)
text, number = label = VGroup(
Text("Width = "),
DecimalNumber(
0,
show_ellipsis=True,
num_decimal_places=2,
include_sign=True,
)
)
label.arrange(RIGHT)
always(label.next_to, brace, UP)
f_always(number.set_value, square.get_width)
self.add(square, brace, label)
self.play(
square.animate.scale(2),
rate_func=there_and_back,
run_time=2,
)
self.wait()
self.play(
square.animate.set_width(5, stretch=True),
run_time=3,
)
self.wait()
self.play(
square.animate.set_width(2),
run_time=3
)
self.wait()
now = self.time
w0 = square.get_width()
square.add_updater(
lambda m: m.set_width(w0 * math.sin(self.time - now) + w0)
)
self.wait(4 * PI)
这个场景中新出现的类和用法是 DecimalNumber
,.to_edge()
,.center()
,
always()
,f_always()
,.set_y()
和 .add_updater()
:
DecimalNumber
是一个可变数字,通过将其拆成一个个Tex
字符来加快速度.to_edge()
表示将该物体放到画面的边位置.center()
表示将该物体置于画面中间always(f, x)
表示每帧都执行f(x)
f_always(f, g)
类似always
,每帧都执行f(g())
.set_y()
表示设置该物体在画面上的的纵坐标.add_updater()
为该物体设置一个更新函数。例如:mob1.add_updater(lambda mob: mob.next_to(mob2))
表示每帧都执行mob1.next_to(mob2)
坐标系统 CoordinateSystemExample¶
CoordinateSystemExample¶
class CoordinateSystemExample(Scene):
def construct(self):
axes = Axes(
# x轴的范围从-1到10,步长为1
x_range=(-1, 10),
# y轴的范围从-2到2,步长为0.5y-axis ranges from -2 to 10 with a step size of 0.5
y_range=(-2, 2, 0.5),
# 坐标系将会伸缩来匹配指定的height和width
height=6,
width=10,
# Axes由两个NumberLine组成,你可以通过axis_config来指定它们的样式
axis_config={
"stroke_color": GREY_A,
"stroke_width": 2,
},
# 或者,你也可以像这样分别指定各个坐标轴的样式
y_axis_config={
"include_tip": False,
}
)
# add_coordinate_labels方法的关键字参数可以传入DecimalNumber来指定它的样式
axes.add_coordinate_labels(
font_size=20,
num_decimal_places=1,
)
self.add(axes)
# Axes从CoordinateSystem类派生而来,意思是可以调用Axes.coords_to_point
# (缩写为Axes.c2p)将一组坐标与一个点相关联,如下所示:
dot = Dot(color=RED)
dot.move_to(axes.c2p(0, 0))
self.play(FadeIn(dot, scale=0.5))
self.play(dot.animate.move_to(axes.c2p(3, 2)))
self.wait()
self.play(dot.animate.move_to(axes.c2p(5, 0.5)))
self.wait()
# 同样,你可以调用Axes.point_to_coords(缩写Axes.p2c)
# print(axes.p2c(dot.get_center()))
# 我们可以从轴上画线,以便更好地标记给定点的坐标在这里
# always_redraw命令意味着在每一个新帧上重新绘制线来保证线始终跟随着点移动
h_line = always_redraw(lambda: axes.get_h_line(dot.get_left()))
v_line = always_redraw(lambda: axes.get_v_line(dot.get_bottom()))
self.play(
ShowCreation(h_line),
ShowCreation(v_line),
)
self.play(dot.animate.move_to(axes.c2p(3, -2)))
self.wait()
self.play(dot.animate.move_to(axes.c2p(1, 1)))
self.wait()
# 如果我们把这个点固定在一个特定的坐标上,当我们移动轴时,它也会跟随坐标系移动
f_always(dot.move_to, lambda: axes.c2p(1, 1))
self.play(
axes.animate.scale(0.75).to_corner(UL),
run_time=2,
)
self.wait()
self.play(FadeOut(VGroup(axes, dot, h_line, v_line)))
# manim还有一些其它的坐标系统:ThreeDAxes,NumberPlane,ComplexPlane
函数图像 GraphExample¶
GraphExample¶
class GraphExample(Scene):
def construct(self):
axes = Axes((-3, 10), (-1, 8))
axes.add_coordinate_labels()
self.play(Write(axes, lag_ratio=0.01, run_time=1))
# Axes.get_graph会返回传入方程的图像
sin_graph = axes.get_graph(
lambda x: 2 * math.sin(x),
color=BLUE,
)
# 默认情况下,它在所有采样点(x, f(x))之间稍微平滑地插值
# 但是,如果图形有棱角,可以将use_smoothing设为False
relu_graph = axes.get_graph(
lambda x: max(x, 0),
use_smoothing=False,
color=YELLOW,
)
# 对于不连续的函数,你可以指定间断点来让它不试图填补不连续的位置
step_graph = axes.get_graph(
lambda x: 2.0 if x > 3 else 1.0,
discontinuities=[3],
color=GREEN,
)
# Axes.get_graph_label可以接受字符串或者mobject。如果传入的是字符串
# 那么将将其当作LaTeX表达式传入Tex中
# 默认下,label将生成在图像的右侧,并且匹配图像的颜色
sin_label = axes.get_graph_label(sin_graph, "\\sin(x)")
relu_label = axes.get_graph_label(relu_graph, Text("ReLU"))
step_label = axes.get_graph_label(step_graph, Text("Step"), x=4)
self.play(
ShowCreation(sin_graph),
FadeIn(sin_label, RIGHT),
)
self.wait(2)
self.play(
ReplacementTransform(sin_graph, relu_graph),
FadeTransform(sin_label, relu_label),
)
self.wait()
self.play(
ReplacementTransform(relu_graph, step_graph),
FadeTransform(relu_label, step_label),
)
self.wait()
parabola = axes.get_graph(lambda x: 0.25 * x**2)
parabola.set_stroke(BLUE)
self.play(
FadeOut(step_graph),
FadeOut(step_label),
ShowCreation(parabola)
)
self.wait()
# 你可以使用Axes.input_to_graph_point(缩写Axes.i2gp)来找到图像上的一个点
dot = Dot(color=RED)
dot.move_to(axes.i2gp(2, parabola))
self.play(FadeIn(dot, scale=0.5))
# ValueTracker存储一个数值,可以帮助我们制作可变参数的动画
# 通常使用updater或者f_always让其它mobject根据其中的数值来更新
x_tracker = ValueTracker(2)
f_always(
dot.move_to,
lambda: axes.i2gp(x_tracker.get_value(), parabola)
)
self.play(x_tracker.animate.set_value(4), run_time=3)
self.play(x_tracker.animate.set_value(-2), run_time=3)
self.wait()
三维示例 SurfaceExample¶
SurfaceExample¶
class SurfaceExample(Scene):
CONFIG = {
"camera_class": ThreeDCamera,
}
def construct(self):
surface_text = Text("For 3d scenes, try using surfaces")
surface_text.fix_in_frame()
surface_text.to_edge(UP)
self.add(surface_text)
self.wait(0.1)
torus1 = Torus(r1=1, r2=1)
torus2 = Torus(r1=3, r2=1)
sphere = Sphere(radius=3, resolution=torus1.resolution)
# 你可以使用最多两个图像对曲面进行纹理处理,
# 这两个图像将被解释为朝向灯光的一侧和远离灯光的一侧。
# 这些可以是URL,也可以是指向本地文件的路径
# day_texture = "EarthTextureMap"
# night_texture = "NightEarthTextureMap"
day_texture = "https://upload.wikimedia.org/wikipedia/commons/thumb/4/4d/Whole_world_-_land_and_oceans.jpg/1280px-Whole_world_-_land_and_oceans.jpg"
night_texture = "https://upload.wikimedia.org/wikipedia/commons/thumb/b/ba/The_earth_at_night.jpg/1280px-The_earth_at_night.jpg"
surfaces = [
TexturedSurface(surface, day_texture, night_texture)
for surface in [sphere, torus1, torus2]
]
for mob in surfaces:
mob.shift(IN)
mob.mesh = SurfaceMesh(mob)
mob.mesh.set_stroke(BLUE, 1, opacity=0.5)
# 设置视角
frame = self.camera.frame
frame.set_euler_angles(
theta=-30 * DEGREES,
phi=70 * DEGREES,
)
surface = surfaces[0]
self.play(
FadeIn(surface),
ShowCreation(surface.mesh, lag_ratio=0.01, run_time=3),
)
for mob in surfaces:
mob.add(mob.mesh)
surface.save_state()
self.play(Rotate(surface, PI / 2), run_time=2)
for mob in surfaces[1:]:
mob.rotate(PI / 2)
self.play(
Transform(surface, surfaces[1]),
run_time=3
)
self.play(
Transform(surface, surfaces[2]),
# 在过渡期间移动相机帧
frame.increment_phi, -10 * DEGREES,
frame.increment_theta, -20 * DEGREES,
run_time=3
)
# 添加自动旋转相机帧
frame.add_updater(lambda m, dt: m.increment_theta(-0.1 * dt))
# 移动光源
light_text = Text("You can move around the light source")
light_text.move_to(surface_text)
light_text.fix_in_frame()
self.play(FadeTransform(surface_text, light_text))
light = self.camera.light_source
self.add(light)
light.save_state()
self.play(light.move_to, 3 * IN, run_time=5)
self.play(light.shift, 10 * OUT, run_time=5)
drag_text = Text("Try moving the mouse while pressing d or s")
drag_text.move_to(light_text)
drag_text.fix_in_frame()
self.play(FadeTransform(light_text, drag_text))
self.wait()
这个场景展示了使用三维面的例子,相关用法已经在注释中简要叙述。
.fix_in_frame()
使该物体不随画面视角变化而变化,一直显示在画面上的固定位置
整体示例 OpeningManimExample¶
OpeningManimExample¶
class OpeningManimExample(Scene):
def construct(self):
intro_words = Text("""
The original motivation for manim was to
better illustrate mathematical functions
as transformations.
""")
intro_words.to_edge(UP)
self.play(Write(intro_words))
self.wait(2)
# Linear transform
grid = NumberPlane((-10, 10), (-5, 5))
matrix = [[1, 1], [0, 1]]
linear_transform_words = VGroup(
Text("This is what the matrix"),
IntegerMatrix(matrix, include_background_rectangle=True),
Text("looks like")
)
linear_transform_words.arrange(RIGHT)
linear_transform_words.to_edge(UP)
linear_transform_words.set_stroke(BLACK, 10, background=True)
self.play(
ShowCreation(grid),
FadeTransform(intro_words, linear_transform_words)
)
self.wait()
self.play(grid.animate.apply_matrix(matrix), run_time=3)
self.wait()
# Complex map
c_grid = ComplexPlane()
moving_c_grid = c_grid.copy()
moving_c_grid.prepare_for_nonlinear_transform()
c_grid.set_stroke(BLUE_E, 1)
c_grid.add_coordinate_labels(font_size=24)
complex_map_words = TexText("""
Or thinking of the plane as $\\mathds{C}$,\\\\
this is the map $z \\rightarrow z^2$
""")
complex_map_words.to_corner(UR)
complex_map_words.set_stroke(BLACK, 5, background=True)
self.play(
FadeOut(grid),
Write(c_grid, run_time=3),
FadeIn(moving_c_grid),
FadeTransform(linear_transform_words, complex_map_words),
)
self.wait()
self.play(
moving_c_grid.animate.apply_complex_function(lambda z: z**2),
run_time=6,
)
self.wait(2)
这个场景是一个二维场景的综合运用
在看过这些场景后,你就已经了解了 manim 的部分用法了。更多的例子可以看 3b1b的视频代码。