|
| 1 | +import configparser |
| 2 | +from pathlib import Path |
| 3 | + |
| 4 | +import click |
| 5 | + |
| 6 | +from ... import console |
| 7 | +from ...constants import CONTEXT_SETTINGS, EPILOG, QUALITIES |
| 8 | +from ...utils.file_ops import ( |
| 9 | + add_import_statement, |
| 10 | + copy_template_files, |
| 11 | + get_template_names, |
| 12 | + get_template_path, |
| 13 | +) |
| 14 | + |
| 15 | +CFG_DEFAULTS = { |
| 16 | + "frame_rate": 30, |
| 17 | + "background_color": "BLACK", |
| 18 | + "background_opacity": 1, |
| 19 | + "scene_names": "Default", |
| 20 | + "resolution": (854, 480), |
| 21 | +} |
| 22 | + |
| 23 | + |
| 24 | +def select_resolution(): |
| 25 | + """Prompts input of type click.Choice from user. Presents options from QUALITIES constant. |
| 26 | +
|
| 27 | + Returns |
| 28 | + ------- |
| 29 | + :class:`tuple` |
| 30 | + Tuple containing height and width. |
| 31 | + """ |
| 32 | + resolution_options = [] |
| 33 | + for quality in QUALITIES.items(): |
| 34 | + resolution_options.append( |
| 35 | + (quality[1]["pixel_height"], quality[1]["pixel_width"]) |
| 36 | + ) |
| 37 | + resolution_options.pop() |
| 38 | + choice = click.prompt( |
| 39 | + "\nSelect resolution:\n", |
| 40 | + type=click.Choice([f"{i[0]}p" for i in resolution_options]), |
| 41 | + show_default=False, |
| 42 | + default="480p", |
| 43 | + ) |
| 44 | + return [res for res in resolution_options if f"{res[0]}p" == choice][0] |
| 45 | + |
| 46 | + |
| 47 | +def update_cfg(cfg_dict, project_cfg_path): |
| 48 | + """Updates the manim.cfg file after reading it from the project_cfg_path. |
| 49 | +
|
| 50 | + Parameters |
| 51 | + ---------- |
| 52 | + cfg : :class:`dict` |
| 53 | + values used to update manim.cfg found project_cfg_path. |
| 54 | + project_cfg_path : :class:`Path` |
| 55 | + Path of manim.cfg file. |
| 56 | + """ |
| 57 | + config = configparser.ConfigParser() |
| 58 | + config.read(project_cfg_path) |
| 59 | + cli_config = config["CLI"] |
| 60 | + for key, value in cfg_dict.items(): |
| 61 | + if key == "resolution": |
| 62 | + cli_config["pixel_height"] = str(value[0]) |
| 63 | + cli_config["pixel_width"] = str(value[1]) |
| 64 | + else: |
| 65 | + cli_config[key] = str(value) |
| 66 | + |
| 67 | + with open(project_cfg_path, "w") as conf: |
| 68 | + config.write(conf) |
| 69 | + |
| 70 | + |
| 71 | +@click.command( |
| 72 | + context_settings=CONTEXT_SETTINGS, |
| 73 | + epilog=EPILOG, |
| 74 | +) |
| 75 | +@click.argument("project_name", type=Path, required=False) |
| 76 | +@click.option( |
| 77 | + "-d", |
| 78 | + "--default", |
| 79 | + "default_settings", |
| 80 | + is_flag=True, |
| 81 | + help="Default settings for project creation.", |
| 82 | + nargs=1, |
| 83 | +) |
| 84 | +def project(default_settings, **args): |
| 85 | + """Creates a new project. |
| 86 | +
|
| 87 | + PROJECT_NAME is the name of the folder in which the new project will be initialized. |
| 88 | + """ |
| 89 | + if args["project_name"]: |
| 90 | + project_name = args["project_name"] |
| 91 | + else: |
| 92 | + project_name = click.prompt("Project Name", type=Path) |
| 93 | + |
| 94 | + # in the future when implementing a full template system. Choices are going to be saved in some sort of config file for templates |
| 95 | + template_name = click.prompt( |
| 96 | + "Template", |
| 97 | + type=click.Choice(get_template_names(), False), |
| 98 | + default="Default", |
| 99 | + ) |
| 100 | + |
| 101 | + if project_name.is_dir(): |
| 102 | + console.print( |
| 103 | + f"\nFolder [red]{project_name}[/red] exists. Please type another name\n" |
| 104 | + ) |
| 105 | + else: |
| 106 | + project_name.mkdir() |
| 107 | + new_cfg = dict() |
| 108 | + new_cfg_path = Path.resolve(project_name / "manim.cfg") |
| 109 | + |
| 110 | + if not default_settings: |
| 111 | + for key, value in CFG_DEFAULTS.items(): |
| 112 | + if key == "scene_names": |
| 113 | + new_cfg[key] = template_name + "Template" |
| 114 | + elif key == "resolution": |
| 115 | + new_cfg[key] = select_resolution() |
| 116 | + else: |
| 117 | + new_cfg[key] = click.prompt(f"\n{key}", default=value) |
| 118 | + |
| 119 | + console.print("\n", new_cfg) |
| 120 | + if click.confirm("Do you want to continue?", default=True, abort=True): |
| 121 | + copy_template_files(project_name, template_name) |
| 122 | + update_cfg(new_cfg, new_cfg_path) |
| 123 | + else: |
| 124 | + copy_template_files(project_name, template_name) |
| 125 | + update_cfg(CFG_DEFAULTS, new_cfg_path) |
| 126 | + |
| 127 | + |
| 128 | +@click.command( |
| 129 | + context_settings=CONTEXT_SETTINGS, |
| 130 | + no_args_is_help=True, |
| 131 | + epilog=EPILOG, |
| 132 | +) |
| 133 | +@click.argument("scene_name", type=str, required=True) |
| 134 | +@click.argument("file_name", type=str, required=False) |
| 135 | +def scene(**args): |
| 136 | + """Inserts a SCENE to an existing FILE or creates a new FILE. |
| 137 | +
|
| 138 | + SCENE is the name of the scene that will be inserted. |
| 139 | +
|
| 140 | + FILE is the name of file in which the SCENE will be inserted. |
| 141 | + """ |
| 142 | + if not Path("main.py").exists(): |
| 143 | + raise FileNotFoundError(f"{Path('main.py')} : Not a valid project direcotory.") |
| 144 | + |
| 145 | + template_name = click.prompt( |
| 146 | + "template", |
| 147 | + type=click.Choice(get_template_names(), False), |
| 148 | + default="Default", |
| 149 | + ) |
| 150 | + scene = "" |
| 151 | + with open(Path.resolve(get_template_path() / f"{template_name}.mtp")) as f: |
| 152 | + scene = f.read() |
| 153 | + scene = scene.replace(template_name + "Template", args["scene_name"], 1) |
| 154 | + |
| 155 | + if args["file_name"]: |
| 156 | + file_name = Path(args["file_name"] + ".py") |
| 157 | + |
| 158 | + if file_name.is_file(): |
| 159 | + # file exists so we are going to append new scene to that file |
| 160 | + with open(file_name, "a") as f: |
| 161 | + f.write("\n\n\n" + scene) |
| 162 | + pass |
| 163 | + else: |
| 164 | + # file does not exist so we create a new file, append the scene and prepend the import statement |
| 165 | + with open(file_name, "w") as f: |
| 166 | + f.write("\n\n\n" + scene) |
| 167 | + |
| 168 | + add_import_statement(file_name) |
| 169 | + else: |
| 170 | + # file name is not provided so we assume it is main.py |
| 171 | + # if main.py does not exist we do not continue |
| 172 | + with open(Path("main.py"), "a") as f: |
| 173 | + f.write("\n\n\n" + scene) |
| 174 | + |
| 175 | + |
| 176 | +@click.group( |
| 177 | + context_settings=CONTEXT_SETTINGS, |
| 178 | + invoke_without_command=True, |
| 179 | + no_args_is_help=True, |
| 180 | + epilog=EPILOG, |
| 181 | + help="Create a new project or insert a new scene.", |
| 182 | +) |
| 183 | +@click.pass_context |
| 184 | +def new(ctx): |
| 185 | + pass |
| 186 | + |
| 187 | + |
| 188 | +new.add_command(project) |
| 189 | +new.add_command(scene) |
0 commit comments