mirror of
https://github.com/3b1b/manim.git
synced 2025-07-31 14:03:59 +08:00
Merge branch 'master' of https://github.com/3b1b/manim into WindingNumber
This commit is contained in:
@ -45,7 +45,7 @@ def get_fourier_graph(
|
||||
# T = time_range/n_samples
|
||||
time_range = float(t_max - t_min)
|
||||
time_step_size = time_range/n_samples
|
||||
time_samples = time_func(np.linspace(t_min, t_max, n_samples))
|
||||
time_samples = np.vectorize(time_func)(np.linspace(t_min, t_max, n_samples))
|
||||
fft_output = np.fft.fft(time_samples)
|
||||
frequencies = np.linspace(0.0, n_samples/(2.0*time_range), n_samples//2)
|
||||
# #Cycles per second of fouier_samples[1]
|
||||
|
@ -1686,7 +1686,8 @@ class IntroduceDopplerRadar(Scene):
|
||||
def construct(self):
|
||||
self.setup_axes()
|
||||
self.measure_distance_with_time()
|
||||
self.measure_velocity_with_frequency()
|
||||
self.show_frequency_shift()
|
||||
self.show_frequency_shift_in_fourier()
|
||||
|
||||
def setup_axes(self):
|
||||
self.dish = RadarDish()
|
||||
@ -1694,8 +1695,8 @@ class IntroduceDopplerRadar(Scene):
|
||||
axes = Axes(
|
||||
x_min = 0,
|
||||
x_max = 10,
|
||||
y_min = -2,
|
||||
y_max = 2
|
||||
y_min = -1.5,
|
||||
y_max = 1.5
|
||||
)
|
||||
axes.move_to(DOWN)
|
||||
time_label = TextMobject("Time")
|
||||
@ -1718,20 +1719,15 @@ class IntroduceDopplerRadar(Scene):
|
||||
randy.move_to(dish.get_right(), LEFT)
|
||||
randy.shift(distance*RIGHT)
|
||||
|
||||
pulse_graph = self.get_single_pulse_graph(1, color = BLUE)
|
||||
echo_graph = self.get_single_pulse_graph(1+time_diff, color = YELLOW)
|
||||
sum_graph = axes.get_graph(
|
||||
lambda x : sum([
|
||||
pulse_graph.underlying_function(x),
|
||||
echo_graph.underlying_function(x),
|
||||
]),
|
||||
color = WHITE
|
||||
)
|
||||
sum_graph.background_image_file = "blue_yellow_gradient"
|
||||
pulse_graph, echo_graph, sum_graph = \
|
||||
self.get_pulse_and_echo_graphs(
|
||||
self.get_single_pulse_graph,
|
||||
(1,), (1+time_diff,)
|
||||
)
|
||||
words = ["Original signal", "Echo"]
|
||||
for graph, word in zip([pulse_graph, echo_graph], words):
|
||||
arrow = Vector(DOWN+LEFT)
|
||||
arrow.next_to(graph.peak_point, UP+RIGHT, SMALL_BUFF)
|
||||
arrow = Vector(DOWN)
|
||||
arrow.next_to(graph.peak_point, UP, SMALL_BUFF)
|
||||
arrow.match_color(graph)
|
||||
graph.arrow = arrow
|
||||
label = TextMobject(word)
|
||||
@ -1739,22 +1735,35 @@ class IntroduceDopplerRadar(Scene):
|
||||
label.match_color(graph)
|
||||
graph.label = label
|
||||
|
||||
double_arrow = DoubleArrow(
|
||||
pulse_graph.peak_point,
|
||||
echo_graph.peak_point,
|
||||
color = WHITE
|
||||
)
|
||||
distance_text = TextMobject("$2 \\times$ distance/(signal speed)")
|
||||
distance_text.scale_to_fit_width(0.9*double_arrow.get_width())
|
||||
distance_text.next_to(double_arrow, UP, SMALL_BUFF)
|
||||
|
||||
#v_line anim?
|
||||
|
||||
pulse = RadarPulseSingleton(dish, randy, speed = speed)
|
||||
pulse = RadarPulseSingleton(
|
||||
dish, randy,
|
||||
speed = 0.97*speed, #Just needs slightly better alignment
|
||||
)
|
||||
graph_draw = NormalAnimationAsContinualAnimation(
|
||||
ShowCreation(
|
||||
sum_graph,
|
||||
rate_func = None,
|
||||
run_time = axes.x_max
|
||||
run_time = 0.97*axes.x_max
|
||||
)
|
||||
)
|
||||
randy_look_at = ContinualUpdateFromFunc(
|
||||
randy, lambda pi : pi.look_at(pulse.mobject)
|
||||
)
|
||||
axes_anim = ContinualAnimation(axes)
|
||||
|
||||
self.add(randy_look_at, ContinualAnimation(axes), graph_draw)
|
||||
self.wait()
|
||||
self.add(randy_look_at, axes_anim, graph_draw)
|
||||
self.wait(0.5)
|
||||
self.add(pulse)
|
||||
self.play(
|
||||
Write(pulse_graph.label),
|
||||
@ -1768,35 +1777,263 @@ class IntroduceDopplerRadar(Scene):
|
||||
GrowArrow(echo_graph.arrow),
|
||||
run_time = 1
|
||||
)
|
||||
self.wait(3)
|
||||
graph_draw.update(10)
|
||||
self.add(sum_graph)
|
||||
self.wait()
|
||||
self.play(
|
||||
GrowFromCenter(double_arrow),
|
||||
FadeIn(distance_text)
|
||||
)
|
||||
self.wait()
|
||||
|
||||
self.remove(graph_draw, pulse, randy_look_at, axes_anim)
|
||||
self.add(axes)
|
||||
self.play(LaggedStart(FadeOut, VGroup(
|
||||
sum_graph, randy,
|
||||
pulse_graph.arrow, pulse_graph.label,
|
||||
echo_graph.arrow, echo_graph.label,
|
||||
double_arrow, distance_text
|
||||
)))
|
||||
|
||||
def show_frequency_shift(self):
|
||||
axes = self.axes
|
||||
dish = self.dish
|
||||
plane = Plane()
|
||||
plane.flip()
|
||||
plane.move_to(dish)
|
||||
plane.to_edge(RIGHT)
|
||||
|
||||
time_diff = 6
|
||||
|
||||
pulse_graph, echo_graph, sum_graph = graphs = \
|
||||
self.get_pulse_and_echo_graphs(
|
||||
self.get_frequency_pulse_graph,
|
||||
(1,25), (1+time_diff,50)
|
||||
)
|
||||
for graph in graphs:
|
||||
graph.set_stroke(width = 3)
|
||||
signal_graph = self.get_frequency_pulse_graph(1)
|
||||
|
||||
pulse_brace = Brace(Line(ORIGIN, RIGHT), UP)
|
||||
pulse_brace.move_to(axes.coords_to_point(1, 1.2))
|
||||
echo_brace = pulse_brace.copy()
|
||||
echo_brace.stretch(0.6, 0)
|
||||
echo_brace.move_to(axes.coords_to_point(7, 1.2))
|
||||
pulse_text = pulse_brace.get_text("Original signal")
|
||||
pulse_text.add_background_rectangle()
|
||||
echo_text = echo_brace.get_text("Echo")
|
||||
echo_subtext = TextMobject("(Higher frequency)")
|
||||
echo_subtext.next_to(echo_text, RIGHT)
|
||||
echo_subtext.match_color(echo_graph)
|
||||
|
||||
graph_draw = NormalAnimationAsContinualAnimation(
|
||||
ShowCreation(sum_graph, run_time = 8, rate_func = None)
|
||||
)
|
||||
pulse = RadarPulse(dish, plane, n_pulse_singletons = 12)
|
||||
plane_flight = AmbientMovement(
|
||||
plane, direction = LEFT, rate = 1.5
|
||||
)
|
||||
|
||||
self.add(graph_draw, pulse, plane_flight)
|
||||
self.play(UpdateFromAlphaFunc(
|
||||
plane, lambda m, a : m.set_fill(opacity = a)
|
||||
))
|
||||
self.play(
|
||||
GrowFromCenter(pulse_brace),
|
||||
FadeIn(pulse_text),
|
||||
)
|
||||
self.wait(3)
|
||||
self.play(
|
||||
GrowFromCenter(echo_brace),
|
||||
GrowFromCenter(echo_text),
|
||||
)
|
||||
self.play(UpdateFromAlphaFunc(
|
||||
plane, lambda m, a : m.set_fill(opacity = 1-a)
|
||||
))
|
||||
#Only for when -s is run
|
||||
graph_draw.update(10)
|
||||
self.wait(0.1)
|
||||
self.play(Write(echo_subtext, run_time = 1))
|
||||
self.wait()
|
||||
self.remove(graph_draw, pulse, plane_flight)
|
||||
|
||||
pulse_graph.set_stroke(width = 0)
|
||||
echo_graph.set_stroke(width = 0)
|
||||
self.time_graph_group = VGroup(
|
||||
axes, pulse_brace, pulse_text,
|
||||
echo_brace, echo_text, echo_subtext,
|
||||
pulse_graph, echo_graph, sum_graph,
|
||||
)
|
||||
self.set_variables_as_attrs(*self.time_graph_group)
|
||||
|
||||
def show_frequency_shift_in_fourier(self):
|
||||
sum_graph = self.sum_graph
|
||||
pulse_graph = self.pulse_graph
|
||||
pulse_label = VGroup(self.pulse_brace, self.pulse_text)
|
||||
echo_graph = self.echo_graph
|
||||
echo_label = VGroup(
|
||||
self.echo_brace, self.echo_text, self.echo_subtext
|
||||
)
|
||||
|
||||
#Setup all fourier graph stuff
|
||||
f_max = 0.02
|
||||
frequency_axes = Axes(
|
||||
x_min = 0, x_max = 20,
|
||||
x_axis_config = {"unit_size" : 0.5},
|
||||
y_min = -f_max, y_max = f_max,
|
||||
y_axis_config = {
|
||||
"unit_size" : 50,
|
||||
"tick_frequency" : 0.01,
|
||||
},
|
||||
)
|
||||
frequency_axes.move_to(self.axes, LEFT)
|
||||
frequency_axes.to_edge(DOWN)
|
||||
frequency_label = TextMobject("Frequency")
|
||||
frequency_label.next_to(
|
||||
frequency_axes.x_axis.get_right(), UP,
|
||||
)
|
||||
frequency_label.to_edge(RIGHT)
|
||||
frequency_axes.add(frequency_label)
|
||||
|
||||
for graph in pulse_graph, echo_graph, sum_graph:
|
||||
graph.fourier_transform = get_fourier_graph(
|
||||
frequency_axes, graph.underlying_function,
|
||||
frequency_axes.x_min, 25,
|
||||
complex_to_real_func = abs,
|
||||
)
|
||||
|
||||
#Braces labeling F.T.
|
||||
original_fourier_brace = Brace(
|
||||
Line(
|
||||
frequency_axes.coords_to_point(7, 0.9*f_max),
|
||||
frequency_axes.coords_to_point(9, 0.9*f_max),
|
||||
),
|
||||
UP,
|
||||
).highlight(BLUE)
|
||||
echo_fourier_brace = Brace(
|
||||
Line(
|
||||
frequency_axes.coords_to_point(14, 0.4*f_max),
|
||||
frequency_axes.coords_to_point(18, 0.4*f_max),
|
||||
),
|
||||
UP,
|
||||
).highlight(YELLOW)
|
||||
# braces = [original_fourier_brace, echo_fourier_brace]
|
||||
# words = ["original signal", "echo"]
|
||||
# for brace, word in zip(braces, words):
|
||||
# brace.add(brace.get_text("F.T. of \\\\ %s"%word))
|
||||
fourier_label = TexMobject("||\\text{Fourier transform}||")
|
||||
# fourier_label.next_to(sum_graph.fourier_transform, UP, MED_LARGE_BUFF)
|
||||
fourier_label.next_to(frequency_axes.y_axis, UP, buff = SMALL_BUFF)
|
||||
fourier_label.shift_onto_screen()
|
||||
fourier_label.highlight(RED)
|
||||
|
||||
|
||||
def measure_velocity_with_frequency(self):
|
||||
pass
|
||||
#v_lines
|
||||
v_line = DashedLine(
|
||||
frequency_axes.coords_to_point(8, 0),
|
||||
frequency_axes.coords_to_point(8, 1.2*f_max),
|
||||
color = YELLOW,
|
||||
dashed_segment_length = 0.025,
|
||||
)
|
||||
v_line_pair = VGroup(*[
|
||||
v_line.copy().shift(u*0.6*RIGHT)
|
||||
for u in -1, 1
|
||||
])
|
||||
v_line = VGroup(v_line)
|
||||
|
||||
double_arrow = DoubleArrow(
|
||||
frequency_axes.coords_to_point(8, 0.007),
|
||||
frequency_axes.coords_to_point(16, 0.007),
|
||||
buff = 0,
|
||||
color = WHITE
|
||||
)
|
||||
|
||||
self.play(
|
||||
self.time_graph_group.to_edge, UP,
|
||||
ApplyMethod(
|
||||
self.dish.shift, 2*UP,
|
||||
remover = True
|
||||
),
|
||||
FadeIn(frequency_axes)
|
||||
)
|
||||
self.wait()
|
||||
self.play(
|
||||
FadeOut(sum_graph),
|
||||
FadeOut(echo_label),
|
||||
pulse_graph.set_stroke, {"width" : 3},
|
||||
)
|
||||
self.play(
|
||||
ReplacementTransform(
|
||||
pulse_label[0].copy(),
|
||||
original_fourier_brace
|
||||
),
|
||||
ShowCreation(pulse_graph.fourier_transform)
|
||||
)
|
||||
self.play(Write(fourier_label))
|
||||
self.wait()
|
||||
self.play(ShowCreation(v_line))
|
||||
self.wait()
|
||||
self.play(ReplacementTransform(v_line, v_line_pair))
|
||||
self.wait()
|
||||
self.play(FadeOut(v_line_pair))
|
||||
self.wait()
|
||||
|
||||
self.play(
|
||||
FadeOut(pulse_graph),
|
||||
FadeIn(sum_graph),
|
||||
ReplacementTransform(
|
||||
pulse_graph.fourier_transform,
|
||||
sum_graph.fourier_transform
|
||||
)
|
||||
)
|
||||
self.play(FadeIn(echo_label))
|
||||
self.play(ReplacementTransform(
|
||||
echo_label[0].copy(),
|
||||
echo_fourier_brace,
|
||||
))
|
||||
self.wait(2)
|
||||
self.play(GrowFromCenter(double_arrow))
|
||||
self.wait()
|
||||
|
||||
|
||||
###
|
||||
|
||||
def get_single_pulse_graph(self, x, **kwargs):
|
||||
graph = self.axes.get_graph(
|
||||
self.get_single_pulse_function(x),
|
||||
**kwargs
|
||||
)
|
||||
def get_graph(self, func, **kwargs):
|
||||
graph = self.axes.get_graph(func, **kwargs)
|
||||
graph.peak_point = self.get_peak_point(graph)
|
||||
return graph
|
||||
|
||||
def get_single_pulse_graph(self, x, **kwargs):
|
||||
return self.get_graph(self.get_single_pulse_function(x), **kwargs)
|
||||
|
||||
def get_single_pulse_function(self, x):
|
||||
return lambda t : -2*np.sin(10*(t-x))*np.exp(-100*(t-x)**2)
|
||||
|
||||
def get_frequency_pulse_graph(self, x, freq = 50, **kwargs):
|
||||
return self.get_graph(
|
||||
self.get_frequency_pulse_function(x, freq),
|
||||
num_graph_points = 700,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
def get_frequency_pulse_function(self, x, freq):
|
||||
return lambda t : 2*np.cos(2*freq*(t-x))*min(np.exp(-(freq**2/100)*(t-x)**2), 0.5)
|
||||
|
||||
def get_peak_point(self, graph):
|
||||
anchors = graph.get_anchors()
|
||||
return anchors[np.argmax([p[1] for p in anchors])]
|
||||
|
||||
|
||||
def get_pulse_and_echo_graphs(self, func, args1, args2):
|
||||
pulse_graph = func(*args1, color = BLUE)
|
||||
echo_graph = func(*args2, color = YELLOW)
|
||||
sum_graph = self.axes.get_graph(
|
||||
lambda x : sum([
|
||||
pulse_graph.underlying_function(x),
|
||||
echo_graph.underlying_function(x),
|
||||
]),
|
||||
num_graph_points = echo_graph.get_num_anchor_points(),
|
||||
color = WHITE
|
||||
)
|
||||
sum_graph.background_image_file = "blue_yellow_gradient"
|
||||
return pulse_graph, echo_graph, sum_graph
|
||||
|
||||
|
||||
|
||||
|
@ -129,19 +129,6 @@ class Animation(object):
|
||||
return self
|
||||
|
||||
|
||||
def sync_animation_run_times_and_rate_funcs(*animations, **kwargs):
|
||||
for animation in animations:
|
||||
animation.update_config(**kwargs)
|
||||
max_run_time = max([a.run_time for a in animations])
|
||||
for animation in animations:
|
||||
if animation.run_time != max_run_time:
|
||||
new_rate_func = squish_rate_func(
|
||||
animation.get_rate_func(),
|
||||
0, float(animation.run_time)/max_run_time
|
||||
)
|
||||
animation.set_rate_func(new_rate_func)
|
||||
animation.set_run_time(max_run_time)
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -7,7 +7,6 @@ from mobject import Mobject, Group
|
||||
from mobject.vectorized_mobject import VMobject
|
||||
from mobject.tex_mobject import TextMobject
|
||||
from animation import Animation
|
||||
from animation import sync_animation_run_times_and_rate_funcs
|
||||
from transform import Transform
|
||||
|
||||
class Rotating(Animation):
|
||||
@ -498,15 +497,17 @@ class AnimationGroup(Animation):
|
||||
self.empty = True
|
||||
self.run_time = 0
|
||||
else:
|
||||
# Should really make copies of animations, instead of messing with originals...
|
||||
sync_animation_run_times_and_rate_funcs(*sub_anims, **kwargs)
|
||||
for anim in sub_anims:
|
||||
# If AnimationGroup is called with any configuration,
|
||||
# it is propagated to the sub_animations
|
||||
anim.update_config(**kwargs)
|
||||
self.run_time = max([a.run_time for a in sub_anims])
|
||||
everything = Mobject(*[a.mobject for a in sub_anims])
|
||||
Animation.__init__(self, everything, **kwargs)
|
||||
|
||||
def update_mobject(self, alpha):
|
||||
def update(self, alpha):
|
||||
for anim in self.sub_anims:
|
||||
anim.update(alpha)
|
||||
anim.update(alpha * self.run_time / anim.run_time)
|
||||
|
||||
def clean_up(self, *args, **kwargs):
|
||||
for anim in self.sub_anims:
|
||||
|
@ -17,7 +17,6 @@ from camera import Camera
|
||||
from tk_scene import TkSceneRoot
|
||||
from mobject import Mobject, VMobject
|
||||
from animation import Animation
|
||||
from animation.animation import sync_animation_run_times_and_rate_funcs
|
||||
from animation.transform import MoveToTarget
|
||||
from animation.continual_animation import ContinualAnimation
|
||||
from container import *
|
||||
@ -65,7 +64,10 @@ class Scene(Container):
|
||||
self.setup()
|
||||
if self.write_to_movie:
|
||||
self.open_movie_pipe()
|
||||
self.construct(*self.construct_args)
|
||||
try:
|
||||
self.construct(*self.construct_args)
|
||||
except EndSceneEarlyException:
|
||||
pass
|
||||
if self.write_to_movie:
|
||||
self.close_movie_pipe()
|
||||
print("Played a total of %d animations"%self.num_plays)
|
||||
@ -138,7 +140,10 @@ class Scene(Container):
|
||||
mobjects = None,
|
||||
background = None,
|
||||
include_submobjects = True,
|
||||
dont_update_when_skipping = True,
|
||||
**kwargs):
|
||||
if self.skip_animations and dont_update_when_skipping:
|
||||
return
|
||||
if mobjects is None:
|
||||
mobjects = list_update(
|
||||
self.mobjects,
|
||||
@ -331,12 +336,16 @@ class Scene(Container):
|
||||
return moving_mobjects
|
||||
|
||||
def get_time_progression(self, run_time):
|
||||
times = np.arange(0, run_time, self.frame_duration)
|
||||
if self.skip_animations:
|
||||
times = [run_time]
|
||||
else:
|
||||
step = self.frame_duration
|
||||
times = np.arange(0, run_time + step, step)
|
||||
time_progression = ProgressDisplay(times)
|
||||
return time_progression
|
||||
|
||||
def get_animation_time_progression(self, animations):
|
||||
run_time = animations[0].run_time
|
||||
run_time = np.max([animation.run_time for animation in animations])
|
||||
time_progression = self.get_time_progression(run_time)
|
||||
time_progression.set_description("".join([
|
||||
"Animation %d: "%self.num_plays,
|
||||
@ -406,24 +415,29 @@ class Scene(Container):
|
||||
compile_method(state)
|
||||
return animations
|
||||
|
||||
def handle_animation_skipping(self):
|
||||
if self.start_at_animation_number:
|
||||
if self.num_plays == self.start_at_animation_number:
|
||||
self.skip_animations = self.original_skipping_status
|
||||
if self.end_at_animation_number:
|
||||
if self.num_plays >= self.end_at_animation_number:
|
||||
self.skip_animations = True
|
||||
raise EndSceneEarlyException()
|
||||
|
||||
def play(self, *args, **kwargs):
|
||||
if len(args) == 0:
|
||||
warnings.warn("Called Scene.play with no animations")
|
||||
return
|
||||
if self.start_at_animation_number:
|
||||
if self.num_plays == self.start_at_animation_number:
|
||||
self.skip_animations = False
|
||||
if self.end_at_animation_number:
|
||||
if self.num_plays >= self.end_at_animation_number:
|
||||
self.skip_animations = True
|
||||
return self #Don't even both with the rest...
|
||||
if self.skip_animations:
|
||||
kwargs["run_time"] = 0
|
||||
|
||||
self.handle_animation_skipping()
|
||||
animations = self.compile_play_args_to_animation_list(*args)
|
||||
|
||||
sync_animation_run_times_and_rate_funcs(*animations, **kwargs)
|
||||
for animation in animations:
|
||||
# This is where kwargs to play like run_time and rate_func
|
||||
# get applied to all animations
|
||||
animation.update_config(**kwargs)
|
||||
moving_mobjects = self.get_moving_mobjects(*animations)
|
||||
|
||||
# Paint all non-moving objects onto the screen, so they don't
|
||||
# have to be rendered every frame
|
||||
self.update_frame(excluded_mobjects = moving_mobjects)
|
||||
static_image = self.get_frame()
|
||||
for t in self.get_animation_time_progression(animations):
|
||||
@ -435,7 +449,12 @@ class Scene(Container):
|
||||
self.add(*moving_mobjects)
|
||||
self.mobjects_from_last_animation = moving_mobjects
|
||||
self.clean_up_animations(*animations)
|
||||
self.continual_update(0)
|
||||
if self.skip_animations:
|
||||
# Todo, not great that this uses a variable from
|
||||
# a previous loop...
|
||||
self.continual_update(t)
|
||||
else:
|
||||
self.continual_update(0)
|
||||
self.num_plays += 1
|
||||
return self
|
||||
|
||||
@ -451,17 +470,17 @@ class Scene(Container):
|
||||
return []
|
||||
|
||||
def wait(self, duration = DEFAULT_WAIT_TIME):
|
||||
if self.skip_animations:
|
||||
return self
|
||||
|
||||
if self.should_continually_update():
|
||||
for t in self.get_time_progression(duration):
|
||||
self.continual_update()
|
||||
self.update_frame()
|
||||
self.add_frames(self.get_frame())
|
||||
else:
|
||||
elif not self.skip_animations:
|
||||
self.update_frame()
|
||||
self.add_frames(*[self.get_frame()]*int(duration / self.frame_duration))
|
||||
else:
|
||||
#If self.skip_animations is set, do nothing
|
||||
pass
|
||||
|
||||
return self
|
||||
|
||||
@ -500,7 +519,7 @@ class Scene(Container):
|
||||
#Display methods
|
||||
|
||||
def show_frame(self):
|
||||
self.update_frame()
|
||||
self.update_frame(dont_update_when_skipping = False)
|
||||
self.get_image().show()
|
||||
|
||||
def preview(self):
|
||||
@ -520,7 +539,7 @@ class Scene(Container):
|
||||
if not os.path.exists(directory_path):
|
||||
os.makedirs(directory_path)
|
||||
if not dont_update:
|
||||
self.update_frame()
|
||||
self.update_frame(dont_update_when_skipping = False)
|
||||
image = self.get_image()
|
||||
image = image.convert(mode)
|
||||
image.save(path)
|
||||
@ -573,3 +592,17 @@ class Scene(Container):
|
||||
shutil.move(*self.args_to_rename_file)
|
||||
else:
|
||||
os.rename(*self.args_to_rename_file)
|
||||
|
||||
class EndSceneEarlyException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user