Skip to content

Added project management commands #1418

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 65 commits into from
May 5, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
6c79971
added update_cfg() and copy_template_files()
e4coder Apr 24, 2021
de332e9
added select_resolution() and project command
e4coder Apr 24, 2021
3e2f2db
removed curses
e4coder Apr 25, 2021
c86af87
added init command
e4coder Apr 25, 2021
6d98632
added graph scene template
e4coder Apr 27, 2021
53eec79
worked on doc strings and finished new project command
e4coder Apr 27, 2021
38ca3e5
added moving camera scene template
e4coder Apr 27, 2021
28df86d
changed pixel_height and pixel width
e4coder Apr 27, 2021
1ba8189
added docstring for select resolution changed default.cfg to template…
e4coder Apr 27, 2021
3789669
removed the import status as i will be adding it programatically
e4coder Apr 27, 2021
2b80d4f
added add_import_statment for programatically adding import statment …
e4coder Apr 27, 2021
68c4750
fixed console.print()
e4coder Apr 27, 2021
c6f9a87
fixed class name
e4coder Apr 27, 2021
6badca6
add class name and prompt user for template name, if template name no…
e4coder Apr 27, 2021
807e719
added scene command
e4coder Apr 27, 2021
bf0b7ab
fixed extension issue
e4coder Apr 27, 2021
bef3ecd
worked on docstrings
e4coder Apr 27, 2021
4327644
fixed formating on docstring
e4coder Apr 27, 2021
b753178
added docstring to add_import_statement()
e4coder Apr 27, 2021
0f2c8d3
added manim init test
e4coder Apr 28, 2021
f87d0b3
manim init test complete passing
e4coder Apr 28, 2021
e814fe6
fixed issue with test
e4coder Apr 28, 2021
088b8df
added manim new command test
e4coder Apr 28, 2021
acfb0ef
Merge branch 'master' into project
e4coder Apr 28, 2021
ebfea15
fixed class names and class name clashes
e4coder Apr 28, 2021
ff8b527
added TEMPLATE_NAMES
e4coder Apr 28, 2021
3769135
fixed scene.replace
e4coder Apr 28, 2021
fda6391
changed default to Default
e4coder Apr 28, 2021
71035f9
Merge branch 'project' of https://github.com/e4coder/manim into project
e4coder Apr 28, 2021
d39b851
removed TEMPLATE_NAMES and added get_template_names function
e4coder Apr 28, 2021
8466099
changed templates file extension to avoid check fails
e4coder Apr 29, 2021
7f47fcc
Merge branch 'master' into project
e4coder Apr 29, 2021
dfda53f
moving init command from manim/cli/projects to manim/cli/init
e4coder Apr 29, 2021
9675aac
moving templates from manim/cli/projects/templates to manim/templates
e4coder Apr 29, 2021
1653f60
removed init command from manim/cli/project/commands.py
e4coder Apr 29, 2021
2a0dee1
fixed docstring and removed console
e4coder Apr 29, 2021
c2b29eb
moving new command group from manim/cli/project/commands.py to manim/…
e4coder Apr 29, 2021
e9b92a2
fixed docstrings
e4coder Apr 29, 2021
c879d8d
moved utility functions to file_ops.py
e4coder Apr 29, 2021
8ce7320
added import statments of new and init from their respective directories
e4coder Apr 29, 2021
1d0db1b
clean up
e4coder Apr 29, 2021
0b6ccd8
fixed docstrings
e4coder Apr 29, 2021
d808305
changed file name of manim/cli/cfg/commands.py to group.py
e4coder Apr 29, 2021
04242c1
Merge branch 'project' of https://github.com/e4coder/manim into project
e4coder Apr 29, 2021
e5ee9c1
fixed punctuation
e4coder Apr 29, 2021
7674881
removed unnecessory statements and improved docstring
e4coder Apr 29, 2021
f3cdf0d
removed unnecessory comment
e4coder Apr 29, 2021
64c7b45
clean up of manim/cli/new/group.py
e4coder Apr 29, 2021
c26ef53
fixed major issue with code
e4coder Apr 29, 2021
f11a621
further cleanup
e4coder Apr 29, 2021
e2b21aa
new command group final touches
e4coder Apr 29, 2021
73cd976
Merge branch 'master' into project
e4coder Apr 30, 2021
fc27a10
Update manim/cli/init/commands.py
e4coder Apr 30, 2021
3e0dac7
Update manim/cli/new/group.py
e4coder Apr 30, 2021
ee1fddc
Merge branch 'master' into project
e4coder May 1, 2021
bbac8e9
fixed broken tests
e4coder May 1, 2021
319768a
Merge branch 'master' into project
e4coder May 1, 2021
4547850
Merge branch 'master' into project
e4coder May 1, 2021
f180c4f
Merge branch 'master' into project
e4coder May 1, 2021
4c40f39
Merge branch 'master' into project
e4coder May 2, 2021
d45c270
Merge branch 'master' into project
e4coder May 2, 2021
097a411
Merge branch 'master' into project
e4coder May 3, 2021
3637514
Update manim/cli/init/commands.py
e4coder May 3, 2021
d5e06e0
Merge branch 'master' into project
e4coder May 3, 2021
bb2847a
Merge branch 'master' into project
e4coder May 5, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion manim/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
from click_default_group import DefaultGroup

from . import __version__, console
from .cli.cfg.commands import cfg
from .cli.cfg.group import cfg
from .cli.init.commands import init
from .cli.new.group import new
from .cli.plugins.commands import plugins
from .cli.render.commands import render
from .constants import EPILOG
Expand Down Expand Up @@ -41,6 +43,8 @@ def main(ctx):

main.add_command(cfg)
main.add_command(plugins)
main.add_command(init)
main.add_command(new)
main.add_command(render)

if __name__ == "__main__":
Expand Down
File renamed without changes.
Empty file added manim/cli/init/__init__.py
Empty file.
31 changes: 31 additions & 0 deletions manim/cli/init/commands.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""Manim's init subcommand.

Manim's init subcommand is accessed in the command-line interface via ``manim
init``. Here you can specify options, subcommands, and subgroups for the init
group.

"""
from pathlib import Path

import click

from ...constants import CONTEXT_SETTINGS, EPILOG
from ...utils.file_ops import copy_template_files


@click.command(
context_settings=CONTEXT_SETTINGS,
epilog=EPILOG,
)
def init():
"""Sets up a project in current working directory with default settings.

It copies files from templates directory and pastes them in the current working dir.

The new project is set up with default settings.
"""
cfg = Path("manim.cfg")
if cfg.exists():
raise FileExistsError(f"\t{cfg} exists\n")
else:
copy_template_files()
Empty file added manim/cli/new/__init__.py
Empty file.
189 changes: 189 additions & 0 deletions manim/cli/new/group.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
import configparser
from pathlib import Path

import click

from ... import console
from ...constants import CONTEXT_SETTINGS, EPILOG, QUALITIES
from ...utils.file_ops import (
add_import_statement,
copy_template_files,
get_template_names,
get_template_path,
)

CFG_DEFAULTS = {
"frame_rate": 30,
"background_color": "BLACK",
"background_opacity": 1,
"scene_names": "Default",
"resolution": (854, 480),
}


def select_resolution():
"""Prompts input of type click.Choice from user. Presents options from QUALITIES constant.

Returns
-------
:class:`tuple`
Tuple containing height and width.
"""
resolution_options = []
for quality in QUALITIES.items():
resolution_options.append(
(quality[1]["pixel_height"], quality[1]["pixel_width"])
)
resolution_options.pop()
choice = click.prompt(
"\nSelect resolution:\n",
type=click.Choice([f"{i[0]}p" for i in resolution_options]),
show_default=False,
default="480p",
)
return [res for res in resolution_options if f"{res[0]}p" == choice][0]


def update_cfg(cfg_dict, project_cfg_path):
"""Updates the manim.cfg file after reading it from the project_cfg_path.

Parameters
----------
cfg : :class:`dict`
values used to update manim.cfg found project_cfg_path.
project_cfg_path : :class:`Path`
Path of manim.cfg file.
"""
config = configparser.ConfigParser()
config.read(project_cfg_path)
cli_config = config["CLI"]
for key, value in cfg_dict.items():
if key == "resolution":
cli_config["pixel_height"] = str(value[0])
cli_config["pixel_width"] = str(value[1])
else:
cli_config[key] = str(value)

with open(project_cfg_path, "w") as conf:
config.write(conf)


@click.command(
context_settings=CONTEXT_SETTINGS,
epilog=EPILOG,
)
@click.argument("project_name", type=Path, required=False)
@click.option(
"-d",
"--default",
"default_settings",
is_flag=True,
help="Default settings for project creation.",
nargs=1,
)
def project(default_settings, **args):
"""Creates a new project.

PROJECT_NAME is the name of the folder in which the new project will be initialized.
"""
if args["project_name"]:
project_name = args["project_name"]
else:
project_name = click.prompt("Project Name", type=Path)

# in the future when implementing a full template system. Choices are going to be saved in some sort of config file for templates
template_name = click.prompt(
"Template",
type=click.Choice(get_template_names(), False),
default="Default",
)

if project_name.is_dir():
console.print(
f"\nFolder [red]{project_name}[/red] exists. Please type another name\n"
)
else:
project_name.mkdir()
new_cfg = dict()
new_cfg_path = Path.resolve(project_name / "manim.cfg")

if not default_settings:
for key, value in CFG_DEFAULTS.items():
if key == "scene_names":
new_cfg[key] = template_name + "Template"
elif key == "resolution":
new_cfg[key] = select_resolution()
else:
new_cfg[key] = click.prompt(f"\n{key}", default=value)

console.print("\n", new_cfg)
if click.confirm("Do you want to continue?", default=True, abort=True):
copy_template_files(project_name, template_name)
update_cfg(new_cfg, new_cfg_path)
else:
copy_template_files(project_name, template_name)
update_cfg(CFG_DEFAULTS, new_cfg_path)


@click.command(
context_settings=CONTEXT_SETTINGS,
no_args_is_help=True,
epilog=EPILOG,
)
@click.argument("scene_name", type=str, required=True)
@click.argument("file_name", type=str, required=False)
def scene(**args):
"""Inserts a SCENE to an existing FILE or creates a new FILE.

SCENE is the name of the scene that will be inserted.

FILE is the name of file in which the SCENE will be inserted.
"""
if not Path("main.py").exists():
raise FileNotFoundError(f"{Path('main.py')} : Not a valid project direcotory.")

template_name = click.prompt(
"template",
type=click.Choice(get_template_names(), False),
default="Default",
)
scene = ""
with open(Path.resolve(get_template_path() / f"{template_name}.mtp")) as f:
scene = f.read()
scene = scene.replace(template_name + "Template", args["scene_name"], 1)

if args["file_name"]:
file_name = Path(args["file_name"] + ".py")

if file_name.is_file():
# file exists so we are going to append new scene to that file
with open(file_name, "a") as f:
f.write("\n\n\n" + scene)
pass
else:
# file does not exist so we create a new file, append the scene and prepend the import statement
with open(file_name, "w") as f:
f.write("\n\n\n" + scene)

add_import_statement(file_name)
else:
# file name is not provided so we assume it is main.py
# if main.py does not exist we do not continue
with open(Path("main.py"), "a") as f:
f.write("\n\n\n" + scene)


@click.group(
context_settings=CONTEXT_SETTINGS,
invoke_without_command=True,
no_args_is_help=True,
epilog=EPILOG,
help="Create a new project or insert a new scene.",
)
@click.pass_context
def new(ctx):
pass


new.add_command(project)
new.add_command(scene)
11 changes: 11 additions & 0 deletions manim/templates/Axes.mtp
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class AxesTemplate(Scene):
def construct(self):
graph = Axes(
x_range=[-1,10,1],
y_range=[-1,10,1],
x_length=9,
y_length=6,
axis_config={"include_tip":False}
)
labels = graph.get_axis_labels()
self.add(graph, labels)
12 changes: 12 additions & 0 deletions manim/templates/Default.mtp
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
class DefaultTemplate(Scene):
def construct(self):
circle = Circle() # create a circle
circle.set_fill(PINK, opacity=0.5) # set color and transparency

square = Square() # create a square
square.flip(RIGHT) # flip horizontally
square.rotate(-3 * TAU / 8) # rotate a certain amount

self.play(Create(square)) # animate the creation of the square
self.play(Transform(square, circle)) # interpolate the square into the circle
self.play(FadeOut(square)) # fade out animation
8 changes: 8 additions & 0 deletions manim/templates/MovingCamera.mtp
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
class MovingCameraTemplate(MovingCameraScene):
def construct(self):
text = Text("Hello World").set_color(BLUE)
self.add(text)
self.camera.frame.save_state()
self.play(self.camera.frame.animate.set(width=text.width * 1.2))
self.wait(0.3)
self.play(Restore(self.camera.frame))
7 changes: 7 additions & 0 deletions manim/templates/template.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[CLI]
frame_rate = 30
pixel_height = 480
pixel_width = 854
background_color = BLACK
background_opacity = 1
scene_names = DefaultScene
67 changes: 67 additions & 0 deletions manim/utils/file_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@
import subprocess as sp
import time
from pathlib import Path
from shutil import copyfile

from manim import __version__, config, logger

from .. import console


def add_extension_if_not_present(file_name, extension):
if file_name.suffix != extension:
Expand Down Expand Up @@ -96,3 +99,67 @@ def open_media_file(file_writer):
open_file(file_path, False)

logger.info(f"Previewed File at: {file_path}")


def get_template_names():
"""Returns template names from the templates directory.

Returns
-------
:class:`list`
"""
template_path = Path.resolve(Path(__file__).parent.parent / "templates")
return [template_name.stem for template_name in template_path.glob("*.mtp")]


def get_template_path():
"""Returns the Path of templates directory.

Returns
-------
:class:`Path`
"""
return Path.resolve(Path(__file__).parent.parent / "templates")


def add_import_statement(file):
"""Prepends an import statment in a file

Parameters
----------
file : :class:`Path`
"""
with open(file, "r+") as f:
import_line = "from manim import *"
content = f.read()
f.seek(0, 0)
f.write(import_line.rstrip("\r\n") + "\n" + content)


def copy_template_files(project_dir=Path("."), template_name="Default"):
"""Copies template files from templates dir to project_dir.

Parameters
----------
project_dir : :class:`Path`
Path to project directory.
template_name : :class:`str`
Name of template.
"""
template_cfg_path = Path.resolve(
Path(__file__).parent.parent / "templates/template.cfg"
)
template_scene_path = Path.resolve(
Path(__file__).parent.parent / f"templates/{template_name}.mtp"
)

if not template_cfg_path.exists():
raise FileNotFoundError(f"{template_cfg_path} : file does not exist")
if not template_scene_path.exists():
raise FileNotFoundError(f"{template_scene_path} : file does not exist")

copyfile(template_cfg_path, Path.resolve(project_dir / "manim.cfg"))
console.print("\n\t[green]copied[/green] [blue]manim.cfg[/blue]\n")
copyfile(template_scene_path, Path.resolve(project_dir / "main.py"))
console.print("\n\t[green]copied[/green] [blue]main.py[/blue]\n")
add_import_statement(Path.resolve(project_dir / "main.py"))
Loading