程序员文章、书籍推荐和程序员创业信息与资源分享平台

网站首页 > 技术文章 正文

python之manim库:拥有3Blue1Brown的数学展示动画不再是梦

hfteth 2025-07-01 19:39:21 技术文章 7 ℃

3Blue1Brown介绍

3Blue1Brown是由Grant Sanderson打造的数学、ai等概念讲解的视频频道,他的每个视频都配有生动又直观的数学动画。

很多观众喜爱他的原因大概就是他那些数学动画,因此如果你也想做相关的数学动画而又无从下手的话,由Grant Sanderson自己开源的数学动画开源库manim将会帮助你制作简洁、生动的数学动画。

展示视频

如果不想看代码分解过程的,可以直接拉到底部看完整代码。

版本说明

目前Grant Sanderson开源了两个版本库,分别是:manimmanimgl

本文是针对mainm的案例教程,请勿选择错误。

安装下载

github地址:
https://github.com/ManimCommunity/manim

pip安装:

pip install manim

勾股定理案例详情

1.1 前期准备

本文是一个勾股定理说明的数学动画案例,创建一个test.py作为测试脚本:

1.2 动画思路

最出名的勾股定理是3^2+4^2=5^2,因此我将构造三个正方形,分别是边长为3边长为4边长为5

之后我将会把正方形变换为等面积的长方形分别是:长4.5,高2;长8,高2;长12.5,高2

最后将长4.5长8的长方形左右拼接,将长12.5的长方形覆盖其上,证明勾股定理。

1.3 创建场景

manim是基于面向对象的,因此是通过类的实现来创建不同的动画,以下是场景代码:

from manim import *


class PythagoreanTheorem(Scene):
    def construct(self):
        pass

之后我们将在构造方法construct编写我们的动画代码。

1.4 创建正方形

我们将创建三个正方形放置在场景的偏上方的位置,边长分别为3、4、5,颜色为蓝色、绿色和红色

square_a = Square(side_length=3, color=BLUE).shift(UP)
square_b = Square(side_length=4, color=GREEN).shift(UP)
square_c = Square(side_length=5, color=RED).shift(UP)

1.5 创建文本到正方形内部

label_a = MathTex("a^2").move_to(square_a.get_center())
label_b = MathTex("b^2").move_to(square_b.get_center())
label_c = MathTex("c^2").move_to(square_c.get_center())

这里使用MathTex来创建数学公式文本,再通过move_to进行移动,而移动的位置是相应图形的get_center(),正是中间位置。

  • get_center():返回的是Point3D类,它继承于numpyndarray

除了使用MathTex来创建数学公式的字符串,还可以通过Tex来实现,如下:

label_a = Tex('$a^2#39;).move_to(square_a.get_center())
label_b = Tex('$b^2#39;).move_to(square_b.get_center())
label_c = Tex('$c^2#39;).move_to(square_c.get_center())

$ 符号包裹的部分将转换为数学公式,使用起来更加灵活。

1.6 播放正方形和文本

self.play(Create(square_a), Write(label_a))
self.play(Create(square_b), Write(label_b))
self.play(Create(square_c), Write(label_c))
self.wait(2)

播放使用的是 self.play() 方法,在这里我们播放时创建了正方形的同时写出公式,在结尾让视频 self.wait(2) 暂停2秒后结束。

保存后查看当前代码:

from manim import *


classPythagoreanTheorem(Scene):
    defconstruct(self):
        square_a = Square(side_length=3, color=BLUE).shift(UP)
        square_b = Square(side_length=4, color=GREEN).shift(UP)
        square_c = Square(side_length=5, color=RED).shift(UP)

        label_a = Tex('$a^2#39;).move_to(square_a.get_center())
        label_b = Tex('$b^2#39;).move_to(square_b.get_center())
        label_c = Tex('$c^2#39;).move_to(square_c.get_center())

        self.play(Create(square_a), Write(label_a))
        self.play(Create(square_b), Write(label_b))
        self.play(Create(square_c), Write(label_c))
        self.wait(2)

现在需要在命令行进行视频的生成:

manim -pql test.py PythagoreanTheorem

-pql说明:

  • -p:预览
  • -q:视频质量
  • -l:低质量

因此使用 -pql 后,将输出低质量的视频并进行预览。

可以看到视频中图形的播放顺序和我们写代码的顺序是一致的,它是串行的关系。

1.7 创建长方形,并将正方形变换为长方形

rect_a = Rectangle(width=4.5, height=2).shift(UP)
rect_a.set_fill(BLUE, opacity=0.8)

rect_b = Rectangle(width=8, height=2).shift(UP)
rect_b.set_fill(GREEN, opacity=0.8)

rect_c = Rectangle(width=12.5, height=2, color=RED).shift(UP)

创建了三个长方形,其中a和b是填充了背景色,c要进行覆盖,只画了边框颜色。

接下来将正方形转换为长方形:

self.play(Create(square_a), Write(label_a))
self.play(ReplacementTransform(square_a, rect_a))

self.play(Create(square_b), Write(label_b))
self.play(ReplacementTransform(square_b, rect_b))

self.play(Create(square_c), Write(label_c))
self.play(ReplacementTransform(square_c, rect_c))

查看视频:

1.8 缩放动画

目前看图形略大,占屏幕比较多,希望缩小一点,可以使用下面代码:

self.play(Create(square_a), Write(label_a))
self.play(ReplacementTransform(square_a, rect_a))
self.play(rect_a.animate.scale(0.5))

self.play(Create(square_b), Write(label_b))
self.play(ReplacementTransform(square_b, rect_b))
self.play(rect_b.animate.scale(0.5))

self.play(Create(square_c), Write(label_c))
self.play(ReplacementTransform(square_c, rect_c))
self.play(rect_c.animate.scale(0.5))

查看视频:

1.9 移动图形

接下来需要将之前创建的图像往上挪一挪,全部叠在一块儿不好看:

self.play(Create(square_a), Write(label_a))
self.play(ReplacementTransform(square_a, rect_a))
self.play(rect_a.animate.scale(0.5))
self.play(rect_a.animate.shift(UP * 2))

self.play(Create(square_b), Write(label_b))
self.play(ReplacementTransform(square_b, rect_b))
self.play(rect_b.animate.scale(0.5))
self.play(rect_b.animate.shift(UP))

self.play(Create(square_c), Write(label_c))
self.play(ReplacementTransform(square_c, rect_c))
self.play(rect_c.animate.scale(0.5))

查看视频:

1.10 文字移动

图形移动后,文字未动,因此要将文字一并做修改:

self.play(Create(square_a), Write(label_a))
label_a.add_updater(lambda m: m.move_to(rect_a.get_center()))
self.play(ReplacementTransform(square_a, rect_a))
self.play(rect_a.animate.scale(0.5))
self.play(rect_a.animate.shift(UP * 2))

self.play(Create(square_b), Write(label_b))
label_b.add_updater(lambda m: m.move_to(rect_b.get_center()))
self.play(ReplacementTransform(square_b, rect_b))
self.play(rect_b.animate.scale(0.5))
self.play(rect_b.animate.shift(UP))

self.play(Create(square_c), Write(label_c))
label_c.add_updater(lambda m: m.move_to(rect_c.get_center()))
self.play(ReplacementTransform(square_c, rect_c))
self.play(rect_c.animate.scale(0.5))

查看视频:

完整代码

from manim import *


classPythagoreanTheorem(Scene):
    defconstruct(self):
        square_a = Square(side_length=3, color=BLUE).shift(UP)
        rect_a = Rectangle(width=4.5, height=2).shift(UP)
        rect_a.set_fill(BLUE, opacity=0.8)

        square_b = Square(side_length=4, color=GREEN).shift(UP)
        rect_b = Rectangle(width=8, height=2).shift(UP)
        rect_b.set_fill(GREEN, opacity=0.8)

        square_c = Square(side_length=5, color=RED).shift(UP)
        rect_c = Rectangle(width=12.5, height=2, color=RED).shift(UP)

        label_a = Tex('$a^2#39;).move_to(square_a.get_center())
        label_b = Tex('$b^2#39;).move_to(square_b.get_center())
        label_c = Tex('$c^2#39;).move_to(square_c.get_center())

        self.play(Create(square_a), Write(label_a))
        label_a.add_updater(lambda m: m.move_to(rect_a.get_center()))
        self.play(ReplacementTransform(square_a, rect_a))
        self.play(rect_a.animate.scale(0.5))
        self.play(rect_a.animate.shift(UP * 2))

        self.play(Create(square_b), Write(label_b))
        label_b.add_updater(lambda m: m.move_to(rect_b.get_center()))
        self.play(ReplacementTransform(square_b, rect_b))
        self.play(rect_b.animate.scale(0.5))
        self.play(rect_b.animate.shift(UP))

        self.play(Create(square_c), Write(label_c))
        label_c.add_updater(lambda m: m.move_to(rect_c.get_center()))
        self.play(ReplacementTransform(square_c, rect_c))
        self.play(rect_c.animate.scale(0.5))

        self.play(rect_a.animate.shift(LEFT * 2))

        self.play(rect_b.animate.next_to(rect_a, RIGHT, 0))

        self.play(rect_c.animate.shift(UP * 2))

        # 创建勾股定理公式
        theorem = Tex("$a^2 + b^2 = c^2#34;).next_to(rect_c.get_bottom(), DOWN, aligned_edge=ORIGIN)
        self.play(Write(theorem))

        self.wait(2)

结尾

manim是一个非常有意思的库,其编码思想也很有动画思维。

如果这篇文章点赞超过100,我将继续研究manim,给大家带来更多的manim案例文章。

Tags:

最近发表
标签列表