Argparse在manim中的应用

manim是一个基于python的数学动画制作擎,想要深入了解的朋友,可以去3b1b看看


这里不是为了解析这个开源库,我只是想利用它展示一下python的实际应用。鉴于本文目标导向与博主个人能力所限,我们只会有一个大致的介绍。

工程文件架构,这里我们把注意力集中在箭头所指的三个文件上,这是这个工程最重要的三个文件,其余的文件都是一些说明性或配置性的文件。manimlib是库文件夹,manim.py是程序运行入口文件,example_scenes.py是用户文件(我们主要是在这个文件里工作)

在这里插入图片描述
manimlib里面的内容
在这里插入图片描述

manim.py

#!/usr/bin/env python
import manimlib

if __name__ == "__main__":
    manimlib.main()
else:
    manimlib.stream_starter.start_livestream()

example_scenes.py中的部分内容,因为这是用户文件夹,所以差异性比较大,这里只是列出来一部分内容

#!/usr/bin/env python

from manimlib.imports import *
# To watch one of these scenes, run the following:
# python -m manim example_scenes.py SquareToCircle -pl
#
# Use the flat -l for a faster rendering at a lower
# quality.
# Use -s to skip to the end and just save the final frame
# Use the -p to have the animation (or image, if -s was
# used) pop up once done.
# Use -n <number> to skip ahead to the n'th animation of a scene.
# Use -r <number> to specify a resolution (for example, -r 1080
# for a 1920x1080 video)


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),
            FadeInFrom(basel, UP),
        )
        self.wait()

        transform_title = TextMobject("That was a transform")
        transform_title.to_corner(UP + LEFT)
        self.play(
            Transform(title, transform_title),
            LaggedStart(*map(FadeOutAndShiftDown, 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)  # Make sure title is on top of grid
        self.play(
            FadeOut(title),
            FadeInFromDown(grid_title),
            ShowCreation(grid, run_time=3, lag_ratio=0.1),
        )
        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()
        self.play(
            grid.apply_function,
            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()


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))


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()

一条很重要的命令行

官方的教程中给了我一条这样的命令

python manim.py example_scenes.py ExampleApproximation -pl

这条命令分为三个部分

  • python:python解析器
  • manim.py:以脚本方式被执行的python文件
  • example_scenes.py,ExampleApproximation -pl:这些都是传递给manim.py的命令行参数,后面还有很多

下面我们跟随这三个部分来追踪一下程序执行流程

#!/usr/bin/env python
import manimlib

if __name__ == "__main__":
    manimlib.main()
else:
    manimlib.stream_starter.start_livestream()

这是唯一将要以脚本方式被执行的文件,先来个小插曲,python文件有一个内置属性,__name__,如果它是一个被执行的脚本文件,那么它的值就会变成"__main__",否则就是自身文件名,我们知道程序的入口只能有一个,python也不例外,解释器会寻找到__name__的值为"__main__"的文件并将它当作唯一脚本来执行。回到manim中,这是一个将要被执行的脚本文件,他的__name__的值会被设为__main__,所以条件语句中的manimlib.main()将会被执行,们跳进去看看。

#!/usr/bin/env python
import manimlib.config
import manimlib.constants
import manimlib.extract_scene
import manimlib.stream_starter


def main():
    args = manimlib.config.parse_cli()
    if not args.livestream:
        config = manimlib.config.get_configuration(args)
        manimlib.constants.initialize_directories(config)
        manimlib.extract_scene.main(config)
    else:
        manimlib.stream_starter.start_livestream(
            to_twitch=args.to_twitch,
            twitch_key=args.twitch_key,
        )

上面都是从manimlib中导入的模块,main里面的第一条代码manimlib.config.parse_cli()就是用来解析命令行参数的,命令行参数解析一般都要放在一个程序的最开头,下面我们进去看看

def parse_cli():
    try:
        parser = argparse.ArgumentParser()
        module_location = parser.add_mutually_exclusive_group()#定义一个互斥组,也就是这个组里面所要求的命令行参数不能同时出现
        module_location.add_argument(
            "file",
            nargs="?",
            help="path to file holding the python code for the scene",
        )
        parser.add_argument(
            "scene_names",
            nargs="*",
            help="Name of the Scene class you want to see",
        )
        parser.add_argument(
            "-p", "--preview",
            action="store_true",
            help="Automatically open the saved file once its done",
        ),
        parser.add_argument(
            "-w", "--write_to_movie",
            action="store_true",
            help="Render the scene as a movie file",
        ),
        parser.add_argument(
            "-s", "--save_last_frame",
            action="store_true",
            help="Save the last frame",
        ),
        parser.add_argument(
            "-l", "--low_quality",
            action="store_true",
            help="Render at a low quality (for faster rendering)",
        ),
        parser.add_argument(
            "-m", "--medium_quality",
            action="store_true",
            help="Render at a medium quality",
        ),
        parser.add_argument(
            "--high_quality",
            action="store_true",
            help="Render at a high quality",
        ),
        parser.add_argument(
            "-g", "--save_pngs",
            action="store_true",
            help="Save each frame as a png",
        ),
        parser.add_argument(
            "-i", "--save_as_gif",
            action="store_true",
            help="Save the video as gif",
        ),
        parser.add_argument(
            "-f", "--show_file_in_finder",
            action="store_true",
            help="Show the output file in finder",
        ),
        parser.add_argument(
            "-t", "--transparent",
            action="store_true",
            help="Render to a movie file with an alpha channel",
        ),
        parser.add_argument(
            "-q", "--quiet",
            action="store_true",
            help="",
        ),
        parser.add_argument(
            "-a", "--write_all",
            action="store_true",
            help="Write all the scenes from a file",
        ),
        parser.add_argument(
            "-o", "--file_name",
            help="Specify the name of the output file, if"
                 "it should be different from the scene class name",
        )
        parser.add_argument(
            "-n", "--start_at_animation_number",
            help="Start rendering not from the first animation, but"
                 "from another, specified by its index.  If you pass"
                 "in two comma separated values, e.g. \"3,6\", it will end"
                 "the rendering at the second value",
        )
        parser.add_argument(
            "-r", "--resolution",
            help="Resolution, passed as \"height,width\"",
        )
        parser.add_argument(
            "-c", "--color",
            help="Background color",
        )
        parser.add_argument(
            "--sound",
            action="store_true",
            help="Play a success/failure sound",
        )
        parser.add_argument(
            "--leave_progress_bars",
            action="store_true",
            help="Leave progress bars displayed in terminal",
        )
        parser.add_argument(
            "--media_dir",
            help="directory to write media",
        )
        parser.add_argument(
            "--video_dir",
            help="directory to write video",
        )
        parser.add_argument(
            "--tex_dir",
            help="directory to write tex",
        )

        # For live streaming
        module_location.add_argument(
            "--livestream",
            action="store_true",
            help="Run in streaming mode",
        )
        parser.add_argument(
            "--to-twitch",
            action="store_true",
            help="Stream to twitch",
        )
        parser.add_argument(
            "--with-key",
            dest="twitch_key",
            help="Stream key for twitch",
        )
        args = parser.parse_args()

        if args.file is None and not args.livestream:
            parser.print_help()
            sys.exit(2)
        if args.to_twitch and not args.livestream:
            print("You must run in streaming mode in order to stream to twitch")
            sys.exit(2)
        if args.to_twitch and args.twitch_key is None:
            print("Specify the twitch stream key with --with-key")
            sys.exit(2)
        return args
    except argparse.ArgumentError as err:
        print(str(err))
        sys.exit(2)

有点长,不过我们很容易发现,大部分内容都是结构类似的。这是一个不带参数的函数,它里面放了一个try except语句用来测试程序。第一行给出了一个解析器,第二行为这个解析器设置了一个互斥组(在代码里解释了),下面就是添加命令行参数项,第一个和第二个命令行参数是必须给出的(哪个文件里哪个模块),而且必须要按顺序(可以区分的参数不用按照顺序,不可以区分的要按照顺序),之后都是可选参数。再往下面就是对获取到的命令行参数的提取,最后是对这些参数的使用,我们最终把这个包含参数的args对象返回出去给外部使用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章