mirror of
https://github.com/jeertmans/manim-slides.git
synced 2025-05-20 12:05:56 +08:00
WIP: ThreeDSlide
This commit is contained in:
13
example.py
13
example.py
@ -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()
|
||||||
|
@ -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)
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
||||||
|
@ -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:
|
||||||
|
2
setup.py
2
setup.py
@ -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",
|
||||||
|
Reference in New Issue
Block a user