feat(cli): add start at index options (#156)

* wip: start at index

* feat(cli): add start at index options

* fix(ci): correct callback

* chore(ci): fix type hint

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* fix(cli): typo in variable name...

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
Jérome Eertmans
2023-03-11 18:05:07 +01:00
committed by GitHub
parent 02f425f536
commit 9a573f29f1

View File

@ -73,7 +73,7 @@ class Presentation:
self.slides: List[SlideConfig] = config.slides self.slides: List[SlideConfig] = config.slides
self.files: List[str] = config.files self.files: List[str] = config.files
self.current_slide_index: int = 0 self.__current_slide_index: int = 0
self.current_animation: int = self.current_slide.start_animation self.current_animation: int = self.current_slide.start_animation
self.current_file: str = "" self.current_file: str = ""
@ -87,6 +87,46 @@ class Presentation:
self.reset() self.reset()
def __len__(self) -> int:
return len(self.slides)
@property
def current_slide_index(self) -> int:
return self.__current_slide_index
@current_slide_index.setter
def current_slide_index(self, value: Optional[int]):
if value:
if -len(self) <= value < len(self):
self.__current_slide_index = value
self.current_animation = self.current_slide.start_animation
else:
logger.error(
f"Could not load slide number {value}, playing first slide instead."
)
def set_current_animation_and_update_slide_number(self, value: Optional[int]):
if value:
n_files = len(self.files)
if -n_files <= value < n_files:
if value < 0:
value += n_files
for i, slide in enumerate(self.slides):
if value < slide.end_animation:
self.current_slide_index = i
self.current_animation = value
return
assert (
False
), f"An error occurred when setting the current animation to {value}, please create an issue on GitHub!"
else:
logger.error(
f"Could not load animation number {value}, playing first animation instead."
)
@property @property
def current_slide(self) -> SlideConfig: def current_slide(self) -> SlideConfig:
"""Returns currently playing slide.""" """Returns currently playing slide."""
@ -217,7 +257,7 @@ class Presentation:
return self.current_animation + 1 return self.current_animation + 1
@property @property
def is_last_animation(self) -> int: def is_last_animation(self) -> bool:
"""Returns True if current animation is the last one of current slide.""" """Returns True if current animation is the last one of current slide."""
if self.reverse: if self.reverse:
return self.current_animation == self.current_slide.start_animation return self.current_animation == self.current_slide.start_animation
@ -284,12 +324,15 @@ class Display(QThread): # type: ignore
def __init__( def __init__(
self, self,
presentations: List[PresentationConfig], presentations: List[Presentation],
config: Config = DEFAULT_CONFIG, config: Config = DEFAULT_CONFIG,
start_paused: bool = False, start_paused: bool = False,
skip_all: bool = False, skip_all: bool = False,
record_to: Optional[str] = None, record_to: Optional[str] = None,
exit_after_last_slide: bool = False, exit_after_last_slide: bool = False,
start_at_scene_number: Optional[int] = None,
start_at_slide_number: Optional[int] = None,
start_at_animation_number: Optional[int] = None,
) -> None: ) -> None:
super().__init__() super().__init__()
self.presentations = presentations self.presentations = presentations
@ -301,12 +344,36 @@ class Display(QThread): # type: ignore
self.state = State.PLAYING self.state = State.PLAYING
self.lastframe: Optional[np.ndarray] = None self.lastframe: Optional[np.ndarray] = None
self.current_presentation_index = 0
self.__current_presentation_index = 0
self.current_presentation_index = start_at_scene_number # type: ignore
self.current_presentation.current_slide_index = start_at_slide_number # type: ignore
self.current_presentation.set_current_animation_and_update_slide_number(
start_at_animation_number
)
self.run_flag = True self.run_flag = True
self.key = -1 self.key = -1
self.exit_after_last_slide = exit_after_last_slide self.exit_after_last_slide = exit_after_last_slide
def __len__(self) -> int:
return len(self.presentations)
@property
def current_presentation_index(self) -> int:
return self.__current_presentation_index
@current_presentation_index.setter
def current_presentation_index(self, value: Optional[int]):
if value:
if -len(self) <= value < len(self):
self.__current_presentation_index = value
else:
logger.error(
f"Could not load scene number {value}, playing first scene instead."
)
@property @property
def current_presentation(self) -> Presentation: def current_presentation(self) -> Presentation:
"""Returns the current presentation.""" """Returns the current presentation."""
@ -718,6 +785,35 @@ def get_scenes_presentation_config(
return presentation_configs return presentation_configs
def start_at_callback(ctx, param, values: str) -> Tuple[Optional[int], ...]:
if values == "(None, None, None)":
return (None, None, None)
def str_to_int_or_none(value: str) -> Optional[int]:
if value.lower().strip() == "":
return None
else:
try:
return int(value)
except ValueError:
raise click.BadParameter(
f"start index can only be an integer or an empty string, not `{value}`",
ctx=ctx,
param=param,
)
values_tuple = values.split(",")
n_values = len(values_tuple)
if n_values == 3:
return tuple(map(str_to_int_or_none, values_tuple))
raise click.BadParameter(
f"exactly 3 arguments are expected but you gave {n_values}, please use commas to separate them",
ctx=ctx,
param=param,
)
@click.command() @click.command()
@click.argument("scenes", nargs=-1) @click.argument("scenes", nargs=-1)
@config_path_option @config_path_option
@ -789,6 +885,43 @@ def get_scenes_presentation_config(
help='Set the background color for borders when using "keep" resize mode. Can be any valid CSS color, e.g., "green", "#FF6500" or "rgba(255, 255, 0, .5)".', help='Set the background color for borders when using "keep" resize mode. Can be any valid CSS color, e.g., "green", "#FF6500" or "rgba(255, 255, 0, .5)".',
show_default=True, show_default=True,
) )
@click.option(
"--sa",
"--start-at",
"start_at",
metavar="<SCENE,SLIDE,ANIMATION>",
type=str,
callback=start_at_callback,
default=(None, None, None),
help="Start presenting at (x, y, z), equivalent to --sacn x --sasn y --saan z, and overrides values if not None.",
)
@click.option(
"--sacn",
"--start-at-scene-number",
"start_at_scene_number",
metavar="INDEX",
type=int,
default=None,
help="Start presenting at a given scene number (0 is first, -1 is last).",
)
@click.option(
"--sasn",
"--start-at-slide-number",
"start_at_slide_number",
metavar="INDEX",
type=int,
default=None,
help="Start presenting at a given slide number (0 is first, -1 is last).",
)
@click.option(
"--saan",
"--start-at-animation-number",
"start_at_animation_number",
metavar="INDEX",
type=int,
default=0,
help="Start presenting at a given animation number (0 is first, -1 is last). This conflicts with slide number since animation number is absolute to the presentation.",
)
@click.help_option("-h", "--help") @click.help_option("-h", "--help")
@verbosity_option @verbosity_option
def present( def present(
@ -805,6 +938,10 @@ def present(
aspect_ratio: str, aspect_ratio: str,
resize_mode: str, resize_mode: str,
background_color: str, background_color: str,
start_at: Tuple[Optional[int], Optional[int], Optional[int]],
start_at_scene_number: Optional[int],
start_at_slide_number: Optional[int],
start_at_animation_number: Optional[int],
) -> None: ) -> None:
""" """
Present SCENE(s), one at a time, in order. Present SCENE(s), one at a time, in order.
@ -840,6 +977,15 @@ def present(
"Recording only support '.avi' extension. For other video formats, please convert the resulting '.avi' file afterwards." "Recording only support '.avi' extension. For other video formats, please convert the resulting '.avi' file afterwards."
) )
if start_at[0]:
start_at_scene_number = start_at[0]
if start_at[1]:
start_at_slide_number = start_at[1]
if start_at[2]:
start_at_animation_number = start_at[2]
app = QApplication(sys.argv) app = QApplication(sys.argv)
app.setApplicationName("Manim Slides") app.setApplicationName("Manim Slides")
a = App( a = App(
@ -855,6 +1001,9 @@ def present(
aspect_ratio=ASPECT_RATIO_MODES[aspect_ratio], aspect_ratio=ASPECT_RATIO_MODES[aspect_ratio],
resize_mode=RESIZE_MODES[resize_mode], resize_mode=RESIZE_MODES[resize_mode],
background_color=background_color, background_color=background_color,
start_at_scene_number=start_at_scene_number,
start_at_slide_number=start_at_slide_number,
start_at_animation_number=start_at_animation_number,
) )
a.show() a.show()
sys.exit(app.exec_()) sys.exit(app.exec_())