Refactor old work

This commit is contained in:
Jérome Eertmans
2022-07-12 17:11:32 +02:00
parent ddeb20646d
commit f3d2c4e731
10 changed files with 232 additions and 114 deletions

View File

@ -1 +0,0 @@
from manim_presentation.slide import Slide

View File

@ -1,51 +0,0 @@
import cv2
import numpy as np
import json
import os
import sys
def prompt(question):
font_args = (cv2.FONT_HERSHEY_SIMPLEX, 0.7, 255)
display = np.zeros((130, 420), np.uint8)
cv2.putText(
display,
"* Manim Presentation Wizard *",
(50, 33),
*font_args
)
cv2.putText(
display,
question,
(30, 85),
*font_args
)
cv2.imshow("wizard", display)
return cv2.waitKeyEx(-1)
def main():
if(os.path.exists("./manim-presentation.json")):
print("The manim-presentation.json configuration file exists")
ans = input("Do you want to continue and overwrite it? (y/n): ")
if ans != "y": sys.exit(0)
prompt("Press any key to continue")
PLAYPAUSE_KEY = prompt("Press the PLAY/PAUSE key")
CONTINUE_KEY = prompt("Press the CONTINUE/NEXT key")
BACK_KEY = prompt("Press the BACK key")
REWIND_KEY = prompt("Press the REWIND key")
QUIT_KEY = prompt("Press the QUIT key")
config_file = open("./manim-presentation.json", "w")
json.dump(dict(
PLAYPAUSE_KEY=PLAYPAUSE_KEY,
CONTINUE_KEY=CONTINUE_KEY,
BACK_KEY=BACK_KEY,
REWIND_KEY=REWIND_KEY,
QUIT_KEY=QUIT_KEY
), config_file)
config_file.close()

3
manim_slides/__init__.py Normal file
View File

@ -0,0 +1,3 @@
from .slide import Slide, ThreeDSlide
__version__ = "3.0.0"

27
manim_slides/commons.py Normal file
View File

@ -0,0 +1,27 @@
import click
from .defaults import CONFIG_PATH
def config_path_option(function):
return click.option(
"-c",
"--config",
"config_path",
default=CONFIG_PATH,
type=click.Path(dir_okay=False),
help="Set path to configuration file.",
)(function)
def config_options(function):
function = config_path_option(function)
function = click.option(
"-f", "--force", is_flag=True, help="Overwrite any existing configuration file."
)(function)
function = click.option(
"-m",
"--merge",
is_flag=True,
help="Merge any existing configuration file with the new configuration.",
)(function)
return function

48
manim_slides/config.py Normal file
View File

@ -0,0 +1,48 @@
from typing import Optional, Set
from pydantic import BaseModel, root_validator, validator
from .defaults import LEFT_ARROW_KEY_CODE, RIGHT_ARROW_KEY_CODE
class Key(BaseModel):
ids: Set[int]
name: Optional[str] = None
@validator("ids", each_item=True)
def id_is_posint(cls, v: int):
if v < 0:
raise ValueError("Key ids cannot be negative integers")
return v
def match(self, key_id: int):
return key_id in self.ids
class Config(BaseModel):
QUIT: Key = Key(ids=[ord("q")], name="QUIT")
CONTINUE: Key = Key(ids=[RIGHT_ARROW_KEY_CODE], name="CONTINUE / NEXT")
BACK: Key = Key(ids=[LEFT_ARROW_KEY_CODE], name="BACK")
REWIND: Key = Key(ids=[ord("r")], name="REWIND")
PLAY_PAUSE: Key = Key(ids=[32], name="PLAY / PAUSE")
@root_validator
def ids_are_unique_across_keys(cls, values):
ids = set()
for key in values.values():
if len(ids.intersection(key.ids)) != 0:
raise ValueError(
f"Two or more keys share a common key code: please make sure each key has distinc key codes"
)
ids.update(key.ids)
return values
def merge_with(self, other: "Config") -> "Config":
for key_name, key in self:
other_key = getattr(other, key_name)
key.ids.update(other_key.ids)
key.name = other_key.name or key.name
return self

11
manim_slides/defaults.py Normal file
View File

@ -0,0 +1,11 @@
import platform
FOLDER_PATH: str = "./slides"
CONFIG_PATH: str = ".manim-slides.json"
if platform.system() == "Windows":
RIGHT_ARROW_KEY_CODE = 2555904
LEFT_ARROW_KEY_CODE = 2424832
else:
RIGHT_ARROW_KEY_CODE = 65363
LEFT_ARROW_KEY_CODE = 65361

20
manim_slides/main.py Normal file
View File

@ -0,0 +1,20 @@
import click
from click_default_group import DefaultGroup
from . import __version__
from .present import present
from .wizard import wizard, init
@click.group(cls=DefaultGroup, default="present", default_if_no_args=True)
@click.version_option(__version__, "-v", "--version")
@click.help_option("-h", "--help")
def cli():
pass
cli.add_command(present)
cli.add_command(wizard)
cli.add_command(init)
if __name__ == "__main__":
cli()

View File

@ -1,34 +1,18 @@
import cv2
import numpy as np
import os
import sys
import json
import math
import os
import sys
import time
import argparse
from enum import Enum
import platform
class Config:
@classmethod
def init(cls):
if platform.system() == "Windows":
cls.QUIT_KEY = ord("q")
cls.CONTINUE_KEY = 2555904 #right arrow
cls.BACK_KEY = 2424832 #left arrow
cls.REWIND_KEY = ord("r")
cls.PLAYPAUSE_KEY = 32 #spacebar
else:
cls.QUIT_KEY = ord("q")
cls.CONTINUE_KEY = 65363 #right arrow
cls.BACK_KEY = 65361 #left arrow
cls.REWIND_KEY = ord("r")
cls.PLAYPAUSE_KEY = 32 #spacebar
import click
import cv2
import numpy as np
from .config import Config
from .defaults import CONFIG_PATH, FOLDER_PATH
from .commons import config_path_option
if os.path.exists(os.path.join(os.getcwd(), "./manim-presentation.json")):
json_config = json.load(open(os.path.join(os.getcwd(), "./manim-presentation.json"), "r"))
for key, value in json_config.items():
setattr(cls, key, value)
class State(Enum):
PLAYING = 0
@ -50,7 +34,7 @@ def fix_time(x):
return x if x > 0 else 1
class Presentation:
def __init__(self, config, last_frame_next=False):
def __init__(self, config, last_frame_next: bool = False):
self.last_frame_next = last_frame_next
self.slides = config["slides"]
self.files = config["files"]
@ -164,9 +148,10 @@ class Presentation:
class Display:
def __init__(self, presentations, start_paused=False, fullscreen=False):
def __init__(self, presentations, config, start_paused=False, fullscreen=False):
self.presentations = presentations
self.start_paused = start_paused
self.config = config
self.state = State.PLAYING
self.lastframe = None
@ -250,18 +235,18 @@ class Display:
sleep_time = math.ceil(1000/self.current_presentation.fps)
key = cv2.waitKeyEx(fix_time(sleep_time - self.lag))
if key == Config.QUIT_KEY:
if self.config.QUIT.match(key):
self.quit()
elif self.state == State.PLAYING and key == Config.PLAYPAUSE_KEY:
elif self.state == State.PLAYING and self.config.PLAY_PAUSE.match(key):
self.state = State.PAUSED
elif self.state == State.PAUSED and key == Config.PLAYPAUSE_KEY:
elif self.state == State.PAUSED and self.config.PLAY_PAUSE.match(key):
self.state = State.PLAYING
elif self.state == State.WAIT and (key == Config.CONTINUE_KEY or key == Config.PLAYPAUSE_KEY):
elif self.state == State.WAIT and (self.config.CONTINUE.match(key) or self.config.PLAY_PAUSE.match(key)):
self.current_presentation.next()
self.state = State.PLAYING
elif self.state == State.PLAYING and key == Config.CONTINUE_KEY:
elif self.state == State.PLAYING and self.config.CONTINUE.match(key):
self.current_presentation.next()
elif key == Config.BACK_KEY:
elif self.config.BACK.match(key):
if self.current_presentation.current_slide_i == 0:
self.current_presentation_i = max(0, self.current_presentation_i - 1)
self.current_presentation.reset()
@ -269,7 +254,7 @@ class Display:
else:
self.current_presentation.prev()
self.state = State.PLAYING
elif key == Config.REWIND_KEY:
elif self.config.REWIND.match(key):
self.current_presentation.rewind_slide()
self.state = State.PLAYING
@ -279,30 +264,29 @@ class Display:
sys.exit()
def main():
parser = argparse.ArgumentParser()
parser.add_argument("scenes", metavar="scenes", type=str, nargs="+", help="Scenes to present")
parser.add_argument("--folder", type=str, default="./presentation", help="Presentation files folder")
parser.add_argument("--start-paused", action="store_true", help="Start paused")
parser.add_argument("--fullscreen", action="store_true", help="Fullscreen")
parser.add_argument("--last-frame-next", action="store_true", help="Show the next animation first frame as last frame (hack)")
args = parser.parse_args()
args.folder = os.path.normcase(args.folder)
Config.init()
@click.command()
@click.argument("scenes", nargs=-1)
@config_path_option
@click.option("--folder", default=FOLDER_PATH, type=click.Path(exists=True, file_okay=False), help="Set slides folder.")
@click.option("--start-paused", is_flag=True, help="Start paused.")
@click.option("--fullscreen", is_flag=True, help="Fullscreen mode.")
@click.option("--last-frame-next", is_flag=True, help="Show the next animation first frame as last frame (hack).")
@click.help_option("-h", "--help")
def present(scenes, config_path, folder, start_paused, fullscreen, last_frame_next):
"""Present the different scenes"""
presentations = list()
for scene in args.scenes:
config_file = os.path.join(args.folder, f"{scene}.json")
for scene in scenes:
config_file = os.path.join(folder, f"{scene}.json")
if not os.path.exists(config_file):
raise Exception(f"File {config_file} does not exist, check the scene name and make sure to use Slide as your scene base class")
config = json.load(open(config_file))
presentations.append(Presentation(config, last_frame_next=args.last_frame_next))
presentations.append(Presentation(config, last_frame_next=last_frame_next))
display = Display(presentations, start_paused=args.start_paused, fullscreen=args.fullscreen)
if os.path.exists(config_path):
config = Config.parse_file(config_path)
else:
config = Config()
display = Display(presentations, config=config, start_paused=start_paused, fullscreen=fullscreen)
display.run()
if __name__ == "__main__":
main()

View File

@ -1,13 +1,16 @@
import os
import json
import os
import shutil
from manim import Scene, config
from manim.animation.animation import Wait
from manim import Scene, ThreeDScene, config
from .defaults import FOLDER_PATH
class Slide(Scene):
def __init__(self, *args, **kwargs):
self.output_folder = kwargs.pop("output_folder", "./presentation")
def __init__(self, *args, output_folder=FOLDER_PATH, **kwargs):
super(Slide, self).__init__(*args, **kwargs)
self.output_folder = output_folder
self.slides = list()
self.current_slide = 1
self.current_animation = 0
@ -29,7 +32,7 @@ class Slide(Scene):
self.pause_start_animation = self.current_animation
def start_loop(self):
assert self.loop_start_animation is None, "You cant nest loops"
assert self.loop_start_animation is None, "You cannot nest loops"
self.loop_start_animation = self.current_animation
def end_loop(self):
@ -81,3 +84,6 @@ class Slide(Scene):
files=files
), f)
f.close()
class ThreeDSlide(ThreeDScene, Slide):
pass

71
manim_slides/wizard.py Normal file
View File

@ -0,0 +1,71 @@
import os
import sys
import click
import cv2
import numpy as np
from .config import Config
from .commons import config_options
from .defaults import CONFIG_PATH
def prompt(question: str) -> int:
font_args = (cv2.FONT_HERSHEY_SIMPLEX, 0.7, 255)
display = np.zeros((130, 420), np.uint8)
cv2.putText(display, "* Manim Slides Wizard *", (70, 33), *font_args)
cv2.putText(display, question, (30, 85), *font_args)
cv2.imshow("Manim Slides Configuration Wizard", display)
return cv2.waitKeyEx(-1)
@click.command()
@config_options
def wizard(config_path, force, merge):
"""Launch configuration wizard."""
return _init(config_path, force, merge, skip_interactive=False)
@click.command()
@config_options
def init(config_path, force, merge, skip_interactive=False):
"""Initialize a new default configuration file."""
return _init(config_path, force, merge, skip_interactive=True)
def _init(config_path, force, merge, skip_interactive=False):
if os.path.exists(config_path):
click.secho(f"The `{CONFIG_PATH}` configuration file exists")
if not force and not merge:
choice = click.prompt("Do you want to continue and (o)verwrite / (m)erge it, or (q)uit?", type=click.Choice(["o", "m", "q"], case_sensitive=False))
force = choice == "o"
merge = choice == "m"
if force:
click.secho("Overwriting.")
elif merge:
click.secho("Merging.")
else:
click.secho("Exiting.")
sys.exit(0)
config = Config()
if not skip_interactive:
prompt("Press any key to continue")
for _, key in config:
key.ids = [prompt(f"Press the {key.name} key")]
if merge:
config = Config.parse_file(config_path).merge_with(config)
with open(config_path, "w") as config_file:
config_file.write(config.json(indent=4))
click.echo(f"Configuration file successfully save to `{config_path}`")