使用Manim和Python3.7制作动画的入门教程(译)

========================
>施工中
>Under Construction
========================

这篇文章是Todd Zimmerman的一篇Manim入门教程的翻译, 原文在2019年1月8日发表, 翻译写于2019年9月。


Manim是由3Blue1Brown 创造的一个绘制数学动画的Python工具, 我曾经写过一系列文章详细讲解它的使用方法。 这之后Manim又有了许多变化,包括切换到Python3.7。 我会依照Manim在2018年12月时候的版本从头整理一遍并更新一些内容。 对应Manim代码中没有改动的一大部分内容将跟以前的文章一致。 对比我之前系列的文章,较大的变化对应于Manim的修改, 主要是跟3D场景有关的部分。 需要注意的是未来版本可能对当前的一些使用方法作调整, 不过探究出现的问题也是学习Manim运行机制不错的方法。

1.0 安装Manim

Brian Howell 已经写过一遍很好的讲解如何安装Manim必要组件的文章。 为确保所有组件正常工作,一个很有用的小技巧是使用虚拟环境。 如果你对让Manim正常工作有问题, 可以在Manim的GitHub上提问, 这里有一群活跃的用户,一般来说可以帮助到你。

Manim项目中的Readme页也有安装的指南。

你可以通过运行Manim中附带的示例来验证安装好的软件正常工作: python -m manim example_scenes.py -pl。 如果这里遇到了错误可以去检查GitHub上的Issue列表, 因为有很大的概率别人也会遇到同样的问题。

2.0 创建第一个场景(Scene)

你可以复制下面的代码到Manim项目顶层目录下的新文件中并命名为manim_tutorial_P37.py, 或者可以从我的项目中下载。.py的后缀表明这是一个Python文件。

在Manim的顶层目录打开命令行,输入python -m manim pymanim_tutorial_P37.py Shapes -pl

我们用python命令调用了Python的解释器。 如果你安装了多个版本的Python可能需要python3命令而不是python (我用Anaconda的虚拟环境来让与Manim相关的代码保持整洁)。

传给Python的第一个参数manim表示在Manim的主目录运行manim.py (这里忽略了-m选项,而你应该带上它)。 Manim似乎可以输出为直播流,传到Twitch上,但我不会使用这个特性, 所以我只会关注manim.py调用的extract_scene.py, 这部分代码会运行你的脚本并输出一个视频文件。 第二个参数manim_tutorial_P37.py是你的脚本或者说模块的文件名。 第三个参数Shapes是在脚本中定义的类名也即场景名, 它定义了构建一个场景的具体方法。 最后一个参数-pl告诉extract_scene在生成结束后进行播放和以低视频质量渲染, 低质量的渲染能够动画生成耗费的时间。 输入python -m manim --help会列出一系列调用python -m manim时可以用的选项。

from big_ol_pile_of_manim_imports import *

class Shapes(Scene):
    #A few simple shapes
    def construct(self):
        circle = Circle()
        square = Square()
        line=Line(np.array([3,0,0]),np.array([5,0,0]))
        triangle=Polygon(np.array([0,0,0]),np.array([1,1,0]),np.array([1,-1,0]))

        self.add(line)
        self.play(ShowCreation(circle))
        self.play(FadeOut(circle))
        self.play(GrowFromCenter(square))
        self.play(Transform(square,triangle))

如果一切正常,你能在终端看到类似下面的信息:

终端的第一行显示视频文件存储的位置。 后面几行列出了调用的动画的名字,以及一些处理时长之类的信息。 最后一行会告诉你在脚本中总共处理了多少段动画。

输出的视频看起来是这样的:
Manim的各种模块都包含在big_ol_pile_of_manim_imports.py中, 所以import这个文件就可以用Manim所有的基础功能了。 这不包含每一个模块,但是它包含核心的模块。 可以在这里 查看包含的模块。 我们值得稍微深入地研究一些模块来理解功能是怎么实现的, 我在这个过程中也学到了很多Python的知识。 我发现用https://github.com/3b1b/manim 的搜索功能对查找不同的类和搞懂它们的参数原理很有帮助。 同样有Manim的文档,虽然还没有完工。

2.1 场景与动画(Scenes and Animation)

Scene是一段用来定义屏幕上的对象在什么位置和如何运动的代码。 我了解到3Blue1Brown的视频都是用独立的场景生成然后使用软件拼接起来的。 你必须把每个场景定义为单独的类,它是Scene的子类。 这个类必须包含一个construce()方法, 连同其他创建对象、输出到屏幕和定义运动的代码。 在运行extract_scene.py时(被manim.py调用), construct()基本上就是类中主要被调用的方法,跟__init__的用法相似。 在创建Scene子类的实例时,这个方法会被自动调用。 在这个方法中应该定义所有的对象、所有控制对象的代码以及放置对象和定义运动的代码。

在这第一个场景中,我们创建了一个圆、一个正方形、一条线段和一个三角形。 需要注意坐标是用numpy中的数组np.array()定义的。 有时可以用三元元组来表示坐标,比如(3,0,0),但是有些变换的方法显式地要求numpy数组。

Scene类中最重要的方法之一是play(),用来处理你用到的各种动画函数。 我最喜欢的函数是Transform,它会把一个数学对象(math object, mobject)变形成另外一个。 例子中的场景展现了一个方形变成三角形,你也可以用它把两个对象合并到一起。 不用动画的时候可以用add()来把对象放置到屏幕上。 在第一帧开始的时候那条线段就已经被直接添加了,其他的物体是淡入或是放大出现。 变换函数的名字都很直白,所以它们的功能通常也很明显。

练习:

3.0 更多形状

使用Manim可以创建几乎所有的形状,比如圆形、方形、椭圆、线段以及箭头。 让我们看一下如何画这些形状。

可以在这里下载完整的代码:manim_tutorial_P37.py。 把教程文件放到Manim顶层目录, 然后运行python -m manim manim_tutorial_P37.py MoreShapes -pl生成场景。

class MoreShapes(Scene):
    def construct(self):
        circle = Circle(color=PURPLE_A)
        square = Square(fill_color=GOLD_B, fill_opacity=1, color=GOLD_A)
        square.move_to(UP+LEFT)
        circle.surround(square)
        rectangle = Rectangle(height=2, width=3)
        ellipse=Ellipse(width=3, height=1, color=RED)
        ellipse.shift(2*DOWN+2*RIGHT)
        pointer = CurvedArrow(2*RIGHT,5*RIGHT,color=MAROON_C)
        arrow = Arrow(LEFT,UP)
        arrow.next_to(circle,DOWN+LEFT)
        rectangle.next_to(arrow,DOWN+LEFT)
        ring=Annulus(inner_radius=.5, outer_radius=1, color=BLUE)
        ring.next_to(ellipse, RIGHT)

        self.add(pointer)
        self.play(FadeIn(square))
        self.play(Rotating(square),FadeIn(circle))
        self.play(GrowArrow(arrow))
        self.play(GrowFromCenter(rectangle), GrowFromCenter(ellipse), GrowFromCenter(ring))

可以注意到出现了一些新的形状并且用到了新的命令。 之前我们见过CircleSquareLinePolygon类, 现在增加了RectangleEllipseAnnulusArrowCurvedArrow。 除了线段和箭头,所有形状都是在原点(屏幕中心,(0,0,0))创建的。 对于线和箭头,需要指定两端的位置。

对初学者而言,我们用color=关键字给方形定义了一个颜色。 大多数的形状都是VMobjectvectorized math object)的子类, 而VMobjectMobjectmath object)的子类。 在不知道该用什么样的参数实例化类时,最好的办法是看一下VMobjectMobject中允许的参数。 可能的关键字包括radiusheightwidthcolorfill_colorfill_opacity。 对于Annulus类来说有inner_radiusouter_radius关键字。

已有命名的颜色列表可以在字典COLOR_MAP中找到, 它在constant.py文件中, 文件位于/manim/manimlib/目录下。 有名字的颜色是字典COLOR_MAP的键,对应一个十六进制颜色代码的值。 也可以向COLOR_MAP中添加条目。

3.1 方向向量

constants.py 文件包含一些其他有用的定义,比如说可以用来把对象放在场景里的方向向量。 比如说UP是一个numpy的数组(0,1,0),表示一个单位的距离。 为致敬Manim中的命名约定,我决定把距离的单位称作MUnit(math unit) (这是我个人的术语而不是Manim的)。 因此,默认的屏幕高度是8 MUnits(在constants.py中定义)。 默认的屏幕宽度是14.2 MUnits。

如果在x,y,z坐标系下考虑,UP是一个指向y轴正方向的向量(0,1,0); RIGHT是指向x轴正方向的向量(1,0,0)。 其他的方向向量有LEFT,DOWN,IN,OUT,每个向量都有1 MUnit的长度。 在创建对象实例之后可以用.move_to()方法把对象移动到屏幕上具体的位置。 注意到方向向量可以加到一起(例如UP+LEFT)或是乘一个倍数(例如2*RIGHT), 换句话说,方向向量的行为跟通常理解的数学向量一致。 如果你想定义自己的向量,需要用有三个数的numpy数组表示。 屏幕边缘的中点也已经由TOP,BOTTOM,LEFT_SIDE,RIGHT_SIDE定义。

向量整体的缩放倍数(像素和MUnits之间的关系)由constants.py中的FRAME_HEIGHT变量定义,默认值是8。 这意味着需要将一个物体移动8*UP来使其从屏幕底部移到顶部。 除了修改constants.py文件之外我没有找到修改这一点的方法。

Mobjects也可以使用next_to()方法,通过相对其他物体的偏移来定位。 命令arrow.next_to(circle,DOWN+LEFT)把arrow放置到circle左边和下边一个单位的地方。 代码中的长方形也是如此定位的。

Circle类有一个surround()方法,可以让圆形完全包围另外一个Mobject。 圆形的大小将由Mobject的尺度范围决定。

3.2 制作简单的动画(施工中)

4.0 创建文本(施工中)

4.1 修改文本(施工中)

4.2 旋转和高亮文本(施工中)

5.0 数学方程(施工中)

5.1 着色的方程(施工中)

6.0 对齐文字并使用花括号(施工中)

7.0 图形功能(施工中)

7.1 CONFIG{}字典(施工中)

8.0 更多的图形功能(施工中)

9.0 矢量场(施工中)

9.1 一个很简单的矢量场(施工中)

9.2 一个可变矢量场(施工中)

10.0 运动电荷的场(施工中)

10.1 更新运动电荷的电场(施工中)

11.0 三维场景(施工中)

12.0 使用SVG文件(施工中)