WIP: ThreeDSlide

This commit is contained in:
Jérome Eertmans
2022-07-13 18:00:11 +02:00
parent b3210ec285
commit d9eab15fa5
6 changed files with 102 additions and 65 deletions

View File

@ -28,19 +28,20 @@ class Example(Slide):
# Each slide MUST end with an animation (a self.wait is considered an animation) # Each slide MUST end with an animation (a self.wait is considered an animation)
self.play(dot.animate.move_to(ORIGIN)) self.play(dot.animate.move_to(ORIGIN))
class ThreeDExample(ThreeDSlide): class ThreeDExample(ThreeDSlide):
def construct(self): def construct(self):
axes = ThreeDAxes() axes = ThreeDAxes()
circle=Circle() circle = Circle()
self.set_camera_orientation(phi=75 * DEGREES, theta=30 * DEGREES) # self.set_camera_orientation(phi=75 * DEGREES, theta=30 * DEGREES)
self.add(circle,axes) self.add(circle, axes)
self.begin_ambient_camera_rotation(rate=0.1) # self.begin_ambient_camera_rotation(rate=0.1)
self.pause() self.pause()
self.stop_ambient_camera_rotation() # self.stop_ambient_camera_rotation()
self.move_camera(phi=75 * DEGREES, theta=30 * DEGREES) # self.move_camera(phi=75 * DEGREES, theta=30 * DEGREES)
self.wait() self.wait()
self.start_loop() self.start_loop()

View File

@ -12,6 +12,7 @@ from .wizard import init, wizard
def cli(): def cli():
pass pass
cli.add_command(present) cli.add_command(present)
cli.add_command(wizard) cli.add_command(wizard)
cli.add_command(init) cli.add_command(init)

View File

@ -21,24 +21,33 @@ class State(Enum):
END = 3 END = 3
def __str__(self): def __str__(self):
if self.value == 0: return "Playing" if self.value == 0:
if self.value == 1: return "Paused" return "Playing"
if self.value == 2: return "Wait" if self.value == 1:
if self.value == 3: return "End" return "Paused"
if self.value == 2:
return "Wait"
if self.value == 3:
return "End"
return "..." return "..."
def now(): def now():
return round(time.time() * 1000) return round(time.time() * 1000)
def fix_time(x): def fix_time(x):
return x if x > 0 else 1 return x if x > 0 else 1
class Presentation: class Presentation:
def __init__(self, config, last_frame_next: bool = False): def __init__(self, config, last_frame_next: bool = False):
self.last_frame_next = last_frame_next self.last_frame_next = last_frame_next
self.slides = config["slides"] self.slides = config["slides"]
self.files = config["files"] self.files = config["files"]
print(self.slides)
self.lastframe = [] self.lastframe = []
self.caps = [None for _ in self.files] self.caps = [None for _ in self.files]
@ -48,14 +57,15 @@ class Presentation:
def add_last_slide(self): def add_last_slide(self):
last_slide_end = self.slides[-1]["end_animation"] last_slide_end = self.slides[-1]["end_animation"]
last_animation = len(self.files) last_animation = len(self.files)
self.slides.append(dict( self.slides.append(
start_animation = last_slide_end, dict(
end_animation = last_animation, start_animation=last_slide_end,
type = "last", end_animation=last_animation,
number = len(self.slides) + 1, type="last",
terminated = False number=len(self.slides) + 1,
)) terminated=False,
)
)
def reset(self): def reset(self):
self.current_animation = 0 self.current_animation = 0
@ -78,7 +88,7 @@ class Presentation:
self.current_animation = self.current_slide["start_animation"] self.current_animation = self.current_slide["start_animation"]
self.current_cap.set(cv2.CAP_PROP_POS_FRAMES, 0) self.current_cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
def load_this_cap(self,cap_number): def load_this_cap(self, cap_number):
if self.caps[cap_number] == None: if self.caps[cap_number] == None:
# unload other caps # unload other caps
for i in range(len(self.caps)): for i in range(len(self.caps)):
@ -135,7 +145,10 @@ class Presentation:
self.rewind_slide() self.rewind_slide()
elif self.current_slide["type"] == "last": elif self.current_slide["type"] == "last":
self.current_slide["terminated"] = True self.current_slide["terminated"] = True
elif self.current_slide["type"] == "last" and self.current_slide["end_animation"] == self.current_animation: elif (
self.current_slide["type"] == "last"
and self.current_slide["end_animation"] == self.current_animation
):
state = State.WAIT state = State.WAIT
else: else:
# Play next video! # Play next video!
@ -162,7 +175,9 @@ class Display:
if fullscreen: if fullscreen:
cv2.namedWindow("Video", cv2.WND_PROP_FULLSCREEN) cv2.namedWindow("Video", cv2.WND_PROP_FULLSCREEN)
cv2.setWindowProperty("Video", cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN) cv2.setWindowProperty(
"Video", cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN
)
@property @property
def current_presentation(self): def current_presentation(self):
@ -170,7 +185,9 @@ class Display:
def run(self): def run(self):
while True: while True:
self.lastframe, self.state = self.current_presentation.update_state(self.state) self.lastframe, self.state = self.current_presentation.update_state(
self.state
)
if self.state == State.PLAYING or self.state == State.PAUSED: if self.state == State.PLAYING or self.state == State.PAUSED:
if self.start_paused: if self.start_paused:
self.state = State.PAUSED self.state = State.PAUSED
@ -200,39 +217,34 @@ class Display:
info, info,
f"Animation: {self.current_presentation.current_animation}", f"Animation: {self.current_presentation.current_animation}",
(grid_x[0], grid_y[0]), (grid_x[0], grid_y[0]),
*font_args *font_args,
)
cv2.putText(
info,
f"State: {self.state}",
(grid_x[1], grid_y[0]),
*font_args
) )
cv2.putText(info, f"State: {self.state}", (grid_x[1], grid_y[0]), *font_args)
cv2.putText( cv2.putText(
info, info,
f"Slide {self.current_presentation.current_slide['number']}/{len(self.current_presentation.slides)}", f"Slide {self.current_presentation.current_slide['number']}/{len(self.current_presentation.slides)}",
(grid_x[0], grid_y[1]), (grid_x[0], grid_y[1]),
*font_args *font_args,
) )
cv2.putText( cv2.putText(
info, info,
f"Slide Type: {self.current_presentation.current_slide['type']}", f"Slide Type: {self.current_presentation.current_slide['type']}",
(grid_x[1], grid_y[1]), (grid_x[1], grid_y[1]),
*font_args *font_args,
) )
cv2.putText( cv2.putText(
info, info,
f"Scene {self.current_presentation_i + 1}/{len(self.presentations)}", f"Scene {self.current_presentation_i + 1}/{len(self.presentations)}",
((grid_x[0]+grid_x[1])//2, grid_y[2]), ((grid_x[0] + grid_x[1]) // 2, grid_y[2]),
*font_args *font_args,
) )
cv2.imshow("Info", info) cv2.imshow("Info", info)
def handle_key(self): def handle_key(self):
sleep_time = math.ceil(1000/self.current_presentation.fps) sleep_time = math.ceil(1000 / self.current_presentation.fps)
key = cv2.waitKeyEx(fix_time(sleep_time - self.lag)) key = cv2.waitKeyEx(fix_time(sleep_time - self.lag))
if self.config.QUIT.match(key): if self.config.QUIT.match(key):
@ -241,7 +253,9 @@ class Display:
self.state = State.PAUSED self.state = State.PAUSED
elif self.state == State.PAUSED and self.config.PLAY_PAUSE.match(key): elif self.state == State.PAUSED and self.config.PLAY_PAUSE.match(key):
self.state = State.PLAYING self.state = State.PLAYING
elif self.state == State.WAIT and (self.config.CONTINUE.match(key) or self.config.PLAY_PAUSE.match(key)): elif self.state == State.WAIT and (
self.config.CONTINUE.match(key) or self.config.PLAY_PAUSE.match(key)
):
self.current_presentation.next() self.current_presentation.next()
self.state = State.PLAYING self.state = State.PLAYING
elif self.state == State.PLAYING and self.config.CONTINUE.match(key): elif self.state == State.PLAYING and self.config.CONTINUE.match(key):
@ -258,7 +272,6 @@ class Display:
self.current_presentation.rewind_slide() self.current_presentation.rewind_slide()
self.state = State.PLAYING self.state = State.PLAYING
def quit(self): def quit(self):
cv2.destroyAllWindows() cv2.destroyAllWindows()
sys.exit() sys.exit()
@ -267,10 +280,19 @@ class Display:
@click.command() @click.command()
@click.argument("scenes", nargs=-1) @click.argument("scenes", nargs=-1)
@config_path_option @config_path_option
@click.option("--folder", default=FOLDER_PATH, type=click.Path(exists=True, file_okay=False), help="Set slides folder.") @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("--start-paused", is_flag=True, help="Start paused.")
@click.option("--fullscreen", is_flag=True, help="Fullscreen mode.") @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.option(
"--last-frame-next",
is_flag=True,
help="Show the next animation first frame as last frame (hack).",
)
@click.help_option("-h", "--help") @click.help_option("-h", "--help")
def present(scenes, config_path, folder, start_paused, fullscreen, last_frame_next): def present(scenes, config_path, folder, start_paused, fullscreen, last_frame_next):
"""Present the different scenes""" """Present the different scenes"""
@ -279,7 +301,9 @@ def present(scenes, config_path, folder, start_paused, fullscreen, last_frame_ne
for scene in scenes: for scene in scenes:
config_file = os.path.join(folder, f"{scene}.json") config_file = os.path.join(folder, f"{scene}.json")
if not os.path.exists(config_file): 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") 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)) config = json.load(open(config_file))
presentations.append(Presentation(config, last_frame_next=last_frame_next)) presentations.append(Presentation(config, last_frame_next=last_frame_next))
@ -288,5 +312,7 @@ def present(scenes, config_path, folder, start_paused, fullscreen, last_frame_ne
else: else:
config = Config() config = Config()
display = Display(presentations, config=config, start_paused=start_paused, fullscreen=fullscreen) display = Display(
presentations, config=config, start_paused=start_paused, fullscreen=fullscreen
)
display.run() display.run()

View File

@ -9,7 +9,7 @@ from .defaults import FOLDER_PATH
class Slide(Scene): class Slide(Scene):
def __init__(self, *args, output_folder=FOLDER_PATH, **kwargs): def __init__(self, *args, output_folder=FOLDER_PATH, **kwargs):
super(Slide, self).__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.output_folder = output_folder self.output_folder = output_folder
self.slides = list() self.slides = list()
self.current_slide = 1 self.current_slide = 1
@ -18,16 +18,19 @@ class Slide(Scene):
self.pause_start_animation = 0 self.pause_start_animation = 0
def play(self, *args, **kwargs): def play(self, *args, **kwargs):
super(Slide, self).play(*args, **kwargs) print("PLAY", *args, kwargs.items())
super().play(*args, **kwargs)
self.current_animation += 1 self.current_animation += 1
def pause(self): def pause(self):
self.slides.append(dict( self.slides.append(
type="slide", dict(
start_animation=self.pause_start_animation, type="slide",
end_animation=self.current_animation, start_animation=self.pause_start_animation,
number=self.current_slide end_animation=self.current_animation,
)) number=self.current_slide,
)
)
self.current_slide += 1 self.current_slide += 1
self.pause_start_animation = self.current_animation self.pause_start_animation = self.current_animation
@ -36,13 +39,17 @@ class Slide(Scene):
self.loop_start_animation = self.current_animation self.loop_start_animation = self.current_animation
def end_loop(self): def end_loop(self):
assert self.loop_start_animation is not None, "You have to start a loop before ending it" assert (
self.slides.append(dict( self.loop_start_animation is not None
type="loop", ), "You have to start a loop before ending it"
start_animation=self.loop_start_animation, self.slides.append(
end_animation=self.current_animation, dict(
number=self.current_slide type="loop",
)) start_animation=self.loop_start_animation,
end_animation=self.current_animation,
number=self.current_slide,
)
)
self.current_slide += 1 self.current_slide += 1
self.loop_start_animation = None self.loop_start_animation = None
self.pause_start_animation = self.current_animation self.pause_start_animation = self.current_animation
@ -52,7 +59,7 @@ class Slide(Scene):
max_files_cached = config["max_files_cached"] max_files_cached = config["max_files_cached"]
config["max_files_cached"] = float("inf") config["max_files_cached"] = float("inf")
super(Slide, self).render(*args, **kwargs) super().render(*args, **kwargs)
config["max_files_cached"] = max_files_cached config["max_files_cached"] = max_files_cached
@ -78,12 +85,10 @@ class Slide(Scene):
shutil.copyfile(src_file, dst_file) shutil.copyfile(src_file, dst_file)
files.append(dst_file) files.append(dst_file)
f = open(os.path.join(self.output_folder, "%s.json" % (scene_name, )), "w") f = open(os.path.join(self.output_folder, "%s.json" % (scene_name,)), "w")
json.dump(dict( json.dump(dict(slides=self.slides, files=files), f)
slides=self.slides,
files=files
), f)
f.close() f.close()
class ThreeDSlide(ThreeDScene, Slide):
class ThreeDSlide(Slide, ThreeDScene):
pass pass

View File

@ -27,19 +27,24 @@ def wizard(config_path, force, merge):
"""Launch configuration wizard.""" """Launch configuration wizard."""
return _init(config_path, force, merge, skip_interactive=False) return _init(config_path, force, merge, skip_interactive=False)
@click.command() @click.command()
@config_options @config_options
def init(config_path, force, merge, skip_interactive=False): def init(config_path, force, merge, skip_interactive=False):
"""Initialize a new default configuration file.""" """Initialize a new default configuration file."""
return _init(config_path, force, merge, skip_interactive=True) return _init(config_path, force, merge, skip_interactive=True)
def _init(config_path, force, merge, skip_interactive=False): def _init(config_path, force, merge, skip_interactive=False):
if os.path.exists(config_path): if os.path.exists(config_path):
click.secho(f"The `{CONFIG_PATH}` configuration file exists") click.secho(f"The `{CONFIG_PATH}` configuration file exists")
if not force and not merge: 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)) 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" force = choice == "o"
merge = choice == "m" merge = choice == "m"
@ -52,7 +57,6 @@ def _init(config_path, force, merge, skip_interactive=False):
click.secho("Exiting.") click.secho("Exiting.")
sys.exit(0) sys.exit(0)
config = Config() config = Config()
if not skip_interactive: if not skip_interactive:

View File

@ -30,7 +30,7 @@ setuptools.setup(
python_requires=">=3.7", python_requires=">=3.7",
install_requires=[ install_requires=[
"click>=8.0", "click>=8.0",
"click-default-group>=1.2" "click-default-group>=1.2",
"numpy>=1.19.3", "numpy>=1.19.3",
"pydantic>=1.9.1", "pydantic>=1.9.1",
"opencv-python>=4.6", "opencv-python>=4.6",