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對象返回出去給外部使用。

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