mirror of
https://github.com/3b1b/manim.git
synced 2025-08-05 16:49:03 +00:00
commit
cb18daab37
19 changed files with 1860 additions and 104 deletions
|
@ -1,8 +0,0 @@
|
|||
from active_projects import clacks
|
||||
|
||||
output_directory = "clacks_question"
|
||||
all_scenes = [
|
||||
clacks.NameIntro,
|
||||
clacks.MathAndPhysicsConspiring,
|
||||
clacks.LightBouncing,
|
||||
]
|
28
active_projects/clacks/all_s2_scenes.py
Normal file
28
active_projects/clacks/all_s2_scenes.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
# from active_projects.clacks import question
|
||||
# from active_projects.clacks import solution1
|
||||
from active_projects.clacks.solution2 import block_collision_scenes
|
||||
from active_projects.clacks.solution2 import simple_scenes
|
||||
from active_projects.clacks.solution2 import wordy_scenes
|
||||
from active_projects.clacks.solution2 import pi_creature_scenes
|
||||
from active_projects.clacks.solution2 import position_phase_space
|
||||
|
||||
OUTPUT_DIRECTORY = "clacks_solution2"
|
||||
ALL_SCENE_CLASSES = [
|
||||
block_collision_scenes.IntroducePreviousTwoVideos,
|
||||
block_collision_scenes.PreviousTwoVideos,
|
||||
wordy_scenes.ConnectionToOptics,
|
||||
pi_creature_scenes.OnAnsweringTwice,
|
||||
simple_scenes.LastVideoWrapper,
|
||||
position_phase_space.IntroducePositionPhaseSpace,
|
||||
position_phase_space.UnscaledPositionPhaseSpaceMass100,
|
||||
position_phase_space.EqualMassCase,
|
||||
pi_creature_scenes.AskAboutEqualMassMomentumTransfer,
|
||||
position_phase_space.FailedAngleRelation,
|
||||
position_phase_space.UnscaledPositionPhaseSpaceMass10,
|
||||
pi_creature_scenes.ComplainAboutRelevanceOfAnalogy,
|
||||
simple_scenes.LastVideoWrapper,
|
||||
position_phase_space.RescaleCoordinates,
|
||||
wordy_scenes.ConnectionToOpticsTransparent,
|
||||
position_phase_space.RescaleCoordinatesMass16,
|
||||
position_phase_space.RescaleCoordinatesMass64,
|
||||
]
|
|
@ -6,7 +6,7 @@ from active_projects.clacks.question import BlocksAndWallExample
|
|||
|
||||
class NameBump(BlocksAndWallExample):
|
||||
CONFIG = {
|
||||
"name": "Magnus Lysfjord",
|
||||
"name": "Grant Sanderson",
|
||||
"sliding_blocks_config": {
|
||||
"block1_config": {
|
||||
"mass": 1e6,
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
from big_ol_pile_of_manim_imports import *
|
||||
import subprocess
|
||||
from pydub import AudioSegment
|
||||
|
||||
|
||||
class Block(Square):
|
||||
|
@ -217,6 +215,10 @@ class ClackFlashes(ContinualAnimation):
|
|||
continue
|
||||
last_time = time
|
||||
flash = Flash(location, **self.flash_config)
|
||||
for sm in flash.mobject.family_members_with_points():
|
||||
if isinstance(sm, VMobject):
|
||||
sm.set_stroke(YELLOW, 3)
|
||||
sm.set_stroke(WHITE, 6, 0.5, background=True)
|
||||
flash.start_time = time
|
||||
flash.end_time = time + flash.run_time
|
||||
self.flashes.append(flash)
|
||||
|
@ -224,16 +226,17 @@ class ClackFlashes(ContinualAnimation):
|
|||
|
||||
def update_mobject(self, dt):
|
||||
total_time = self.external_time
|
||||
group = self.mobject
|
||||
for flash in self.flashes:
|
||||
if flash.start_time < total_time < flash.end_time:
|
||||
if flash.mobject not in self.mobject:
|
||||
self.mobject.add(flash.mobject)
|
||||
if flash.mobject not in group:
|
||||
group.add(flash.mobject)
|
||||
flash.update(
|
||||
(total_time - flash.start_time) / flash.run_time
|
||||
)
|
||||
else:
|
||||
if flash.mobject in self.mobject:
|
||||
self.mobject.remove(flash.mobject)
|
||||
if flash.mobject in group:
|
||||
group.remove(flash.mobject)
|
||||
|
||||
|
||||
class Wall(Line):
|
||||
|
@ -266,6 +269,7 @@ class Wall(Line):
|
|||
class BlocksAndWallScene(Scene):
|
||||
CONFIG = {
|
||||
"include_sound": True,
|
||||
"collision_sound": "clack.wav",
|
||||
"count_clacks": True,
|
||||
"counter_group_shift_vect": LEFT,
|
||||
"sliding_blocks_config": {},
|
||||
|
@ -273,7 +277,6 @@ class BlocksAndWallScene(Scene):
|
|||
"wall_x": -6,
|
||||
"n_wall_ticks": 15,
|
||||
"counter_label": "\\# Collisions: ",
|
||||
"collision_sound": "clack.wav",
|
||||
"show_flash_animations": True,
|
||||
"min_time_between_sounds": 0.004,
|
||||
}
|
||||
|
@ -345,56 +348,30 @@ class BlocksAndWallScene(Scene):
|
|||
return
|
||||
self.counter_mob.set_value(n_clacks)
|
||||
|
||||
def create_sound_file(self, clack_data):
|
||||
clack_file = os.path.join(SOUND_DIR, self.collision_sound)
|
||||
output_file = self.get_movie_file_path(extension='.wav')
|
||||
def add_clack_sounds(self, clack_data):
|
||||
clack_file = self.collision_sound
|
||||
total_time = self.get_time()
|
||||
times = [
|
||||
time
|
||||
for location, time in clack_data
|
||||
if time < 300 # In case of any extremes
|
||||
if time < total_time
|
||||
]
|
||||
|
||||
clack = AudioSegment.from_wav(clack_file)
|
||||
total_time = max(times) + 1
|
||||
clacks = AudioSegment.silent(int(1000 * total_time))
|
||||
last_position = 0
|
||||
min_diff = int(1000 * self.min_time_between_sounds)
|
||||
last_time = 0
|
||||
for time in times:
|
||||
position = int(1000 * time)
|
||||
d_position = position - last_position
|
||||
if d_position < min_diff:
|
||||
d_time = time - last_time
|
||||
if d_time < self.min_time_between_sounds:
|
||||
continue
|
||||
if time > self.get_time():
|
||||
break
|
||||
last_position = position
|
||||
clacks = clacks.fade(-50, start=position, end=position + 10)
|
||||
clacks = clacks.overlay(
|
||||
clack,
|
||||
position=position
|
||||
last_time = time
|
||||
self.add_sound(
|
||||
clack_file,
|
||||
time_offset=(time - total_time),
|
||||
gain=-20,
|
||||
)
|
||||
clacks.export(output_file, format="wav")
|
||||
return output_file
|
||||
return self
|
||||
|
||||
# TODO, this no longer works
|
||||
# should use Scene.add_sound instead
|
||||
def combine_movie_files(self):
|
||||
Scene.combine_movie_files(self)
|
||||
def tear_down(self):
|
||||
if self.include_sound:
|
||||
sound_file_path = self.create_sound_file(self.clack_data)
|
||||
movie_path = self.get_movie_file_path()
|
||||
temp_path = self.get_movie_file_path(str(self) + "TempSound")
|
||||
commands = [
|
||||
"ffmpeg",
|
||||
"-i", movie_path,
|
||||
"-i", sound_file_path,
|
||||
"-c:v", "copy", "-c:a", "aac",
|
||||
'-loglevel', 'error',
|
||||
"-strict", "experimental",
|
||||
temp_path,
|
||||
]
|
||||
subprocess.call(commands)
|
||||
subprocess.call(["rm", sound_file_path])
|
||||
subprocess.call(["mv", temp_path, movie_path])
|
||||
self.add_clack_sounds(self.clack_data)
|
||||
|
||||
# Animated scenes
|
||||
|
||||
|
@ -1573,6 +1550,7 @@ class Thumbnail(BlocksAndWallExample, MovingCameraScene):
|
|||
"count_clacks": False,
|
||||
"show_flash_animations": False,
|
||||
"floor_y": -3.0,
|
||||
"include_sound": False,
|
||||
}
|
||||
|
||||
def setup(self):
|
||||
|
@ -1580,7 +1558,16 @@ class Thumbnail(BlocksAndWallExample, MovingCameraScene):
|
|||
BlocksAndWallExample.setup(self)
|
||||
|
||||
def construct(self):
|
||||
self.camera_frame.shift(0.9 * UP)
|
||||
# self.camera_frame.shift(0.9 * UP)
|
||||
self.mobjects.insert(
|
||||
0,
|
||||
FullScreenFadeRectangle(
|
||||
color=DARK_GREY,
|
||||
opacity=0.5,
|
||||
sheen_direction=UL,
|
||||
sheen=0.5,
|
||||
),
|
||||
)
|
||||
self.thicken_lines()
|
||||
self.grow_labels()
|
||||
self.add_vector()
|
||||
|
|
76
active_projects/clacks/solution2/block_collision_scenes.py
Normal file
76
active_projects/clacks/solution2/block_collision_scenes.py
Normal file
|
@ -0,0 +1,76 @@
|
|||
from big_ol_pile_of_manim_imports import *
|
||||
from active_projects.clacks.question import BlocksAndWallExample
|
||||
|
||||
|
||||
class PreviousTwoVideos(BlocksAndWallExample):
|
||||
CONFIG = {
|
||||
"sliding_blocks_config": {
|
||||
"block1_config": {
|
||||
"mass": 1e2,
|
||||
"velocity": -1,
|
||||
"width": 4,
|
||||
"distance": 8,
|
||||
},
|
||||
"block2_config": {
|
||||
"width": 4,
|
||||
"distance": 3,
|
||||
},
|
||||
},
|
||||
"floor_y": -3,
|
||||
"wait_time": 15,
|
||||
}
|
||||
|
||||
def setup(self):
|
||||
super().setup()
|
||||
blocks = self.blocks
|
||||
videos = Group(
|
||||
ImageMobject("ClacksSolution1Thumbnail"),
|
||||
ImageMobject("ClacksQuestionThumbnail"),
|
||||
)
|
||||
for n, video, block in zip([2, 1], videos, blocks):
|
||||
block.fade(1)
|
||||
video.add(SurroundingRectangle(
|
||||
video, buff=0,
|
||||
color=BLUE,
|
||||
stroke_width=3,
|
||||
))
|
||||
video.replace(block)
|
||||
|
||||
title = TextMobject("Part {}".format(n))
|
||||
title.scale(1.5)
|
||||
title.next_to(video, UP, MED_SMALL_BUFF)
|
||||
video.add(title)
|
||||
|
||||
def update_videos(videos):
|
||||
for video, block in zip(videos, blocks):
|
||||
video.move_to(block, DOWN)
|
||||
video.shift(0.04 * UP)
|
||||
|
||||
videos.add_updater(update_videos)
|
||||
self.add(videos)
|
||||
if self.show_flash_animations:
|
||||
self.add(self.clack_flashes.mobject)
|
||||
self.videos = videos
|
||||
|
||||
|
||||
class IntroducePreviousTwoVideos(PreviousTwoVideos):
|
||||
CONFIG = {
|
||||
"show_flash_animations": False,
|
||||
"include_sound": False,
|
||||
}
|
||||
|
||||
def construct(self):
|
||||
blocks = self.blocks
|
||||
videos = self.videos
|
||||
|
||||
self.remove(blocks)
|
||||
videos.clear_updaters()
|
||||
self.remove(videos)
|
||||
|
||||
self.play(FadeInFromLarge(videos[1]))
|
||||
self.play(TransformFromCopy(
|
||||
videos[0].copy().fade(1).shift(2 * RIGHT),
|
||||
videos[0],
|
||||
rate_func=lambda t: rush_into(t, 3),
|
||||
))
|
||||
# self.wait()
|
78
active_projects/clacks/solution2/pi_creature_scenes.py
Normal file
78
active_projects/clacks/solution2/pi_creature_scenes.py
Normal file
|
@ -0,0 +1,78 @@
|
|||
from big_ol_pile_of_manim_imports import *
|
||||
|
||||
|
||||
class OnAnsweringTwice(TeacherStudentsScene):
|
||||
def construct(self):
|
||||
question = TextMobject("Why $\\pi$?")
|
||||
question.move_to(self.screen)
|
||||
question.to_edge(UP)
|
||||
other_questions = VGroup(
|
||||
TextMobject("Frequency of collisions?"),
|
||||
TextMobject("Efficient simulation?"),
|
||||
TextMobject("Time until last collision?"),
|
||||
)
|
||||
for mob in other_questions:
|
||||
mob.move_to(self.hold_up_spot, DOWN)
|
||||
|
||||
self.add(question)
|
||||
|
||||
self.student_says(
|
||||
"But we already \\\\ solved it",
|
||||
bubble_kwargs={"direction": LEFT},
|
||||
target_mode="raise_left_hand",
|
||||
added_anims=[self.teacher.change, "thinking"]
|
||||
)
|
||||
self.change_student_modes("sassy", "angry")
|
||||
self.wait()
|
||||
self.play(
|
||||
RemovePiCreatureBubble(self.students[2]),
|
||||
self.get_student_changes("erm", "erm"),
|
||||
ApplyMethod(
|
||||
question.move_to, self.hold_up_spot, DOWN,
|
||||
path_arc=-90 * DEGREES,
|
||||
),
|
||||
self.teacher.change, "raise_right_hand",
|
||||
)
|
||||
shown_questions = VGroup(question)
|
||||
for oq in other_questions:
|
||||
self.play(
|
||||
shown_questions.shift, 0.85 * UP,
|
||||
FadeInFromDown(oq),
|
||||
self.get_student_changes(
|
||||
*["pondering"] * 3,
|
||||
look_at_arg=oq
|
||||
)
|
||||
)
|
||||
shown_questions.add(oq)
|
||||
self.wait(3)
|
||||
|
||||
|
||||
class AskAboutEqualMassMomentumTransfer(TeacherStudentsScene):
|
||||
def construct(self):
|
||||
pass
|
||||
|
||||
|
||||
class ComplainAboutRelevanceOfAnalogy(TeacherStudentsScene):
|
||||
def construct(self):
|
||||
self.student_says(
|
||||
"Why would \\\\ you care",
|
||||
target_mode="maybe"
|
||||
)
|
||||
self.change_student_modes(
|
||||
"angry", "sassy", "maybe",
|
||||
added_anims=[self.teacher.change, "guilty"]
|
||||
)
|
||||
self.wait(2)
|
||||
self.play(
|
||||
self.teacher.change, "raise_right_hand",
|
||||
self.get_student_changes(
|
||||
"pondering", "erm", "pondering",
|
||||
look_at_arg=self.hold_up_spot,
|
||||
),
|
||||
RemovePiCreatureBubble(self.students[2])
|
||||
)
|
||||
self.play(
|
||||
self.students[2].change, "thinking",
|
||||
self.hold_up_spot + UP,
|
||||
)
|
||||
self.wait(3)
|
1337
active_projects/clacks/solution2/position_phase_space.py
Normal file
1337
active_projects/clacks/solution2/position_phase_space.py
Normal file
File diff suppressed because it is too large
Load diff
15
active_projects/clacks/solution2/simple_scenes.py
Normal file
15
active_projects/clacks/solution2/simple_scenes.py
Normal file
|
@ -0,0 +1,15 @@
|
|||
from big_ol_pile_of_manim_imports import *
|
||||
|
||||
|
||||
class LastVideoWrapper(Scene):
|
||||
def construct(self):
|
||||
title = TextMobject("Last time...")
|
||||
title.scale(1.5)
|
||||
title.to_edge(UP)
|
||||
rect = ScreenRectangle(height=6)
|
||||
rect.next_to(title, DOWN)
|
||||
self.play(
|
||||
FadeInFromDown(title),
|
||||
ShowCreation(rect)
|
||||
)
|
||||
self.wait()
|
205
active_projects/clacks/solution2/wordy_scenes.py
Normal file
205
active_projects/clacks/solution2/wordy_scenes.py
Normal file
|
@ -0,0 +1,205 @@
|
|||
from big_ol_pile_of_manim_imports import *
|
||||
|
||||
|
||||
class ConnectionToOptics(Scene):
|
||||
def construct(self):
|
||||
e_group, m_group = k_groups = self.get_kinematics_groups()
|
||||
c_group, a_group = o_groups = self.get_optics_groups()
|
||||
arrows = VGroup()
|
||||
for g1, g2 in zip(k_groups, o_groups):
|
||||
g2.align_to(g1, UP)
|
||||
g2.to_edge(RIGHT)
|
||||
arrow = TexMobject("\\Rightarrow")
|
||||
arrow.scale(1.5)
|
||||
arrow.move_to(interpolate(
|
||||
g1[0].get_right(), g2[0].get_left(), 0.5
|
||||
))
|
||||
arrows.add(arrow)
|
||||
everything = VGroup(k_groups, arrows, o_groups)
|
||||
everything.to_edge(UP)
|
||||
|
||||
everything.generate_target()
|
||||
everything.target.scale(0.9)
|
||||
everything.target.to_edge(DOWN)
|
||||
width = max([m.get_width() for m in everything.target])
|
||||
width += 2 * MED_SMALL_BUFF
|
||||
rects = VGroup()
|
||||
for k in [0, 2]:
|
||||
rect = DashedMobject(Rectangle(
|
||||
height=FRAME_HEIGHT - 1.5,
|
||||
width=width
|
||||
), dashes_num=100)
|
||||
rect.move_to(everything.target[k])
|
||||
rect.to_edge(DOWN, buff=SMALL_BUFF)
|
||||
rects.add(rect)
|
||||
titles = VGroup(
|
||||
TextMobject("Kinematics"),
|
||||
TextMobject("Optics"),
|
||||
)
|
||||
titles.scale(1.5)
|
||||
for title, rect in zip(titles, rects):
|
||||
title.next_to(rect, UP)
|
||||
titles[0].align_to(titles[1], UP)
|
||||
|
||||
self.play(FadeInFromDown(e_group))
|
||||
self.play(
|
||||
Write(arrows[0]),
|
||||
FadeInFrom(c_group, LEFT)
|
||||
)
|
||||
self.wait()
|
||||
self.play(FadeInFromDown(m_group))
|
||||
self.play(
|
||||
Write(arrows[1]),
|
||||
FadeInFrom(a_group, LEFT)
|
||||
)
|
||||
self.wait(4)
|
||||
for k in range(2):
|
||||
anims = [
|
||||
ShowCreation(rects[k]),
|
||||
FadeInFromDown(titles[k]),
|
||||
]
|
||||
if k == 0:
|
||||
anims.append(MoveToTarget(everything))
|
||||
self.play(*anims)
|
||||
self.wait()
|
||||
self.wait()
|
||||
self.wait(4)
|
||||
|
||||
def get_kinematics_groups(self):
|
||||
tex_to_color_map = {
|
||||
"m_1": BLUE,
|
||||
"m_2": BLUE,
|
||||
"v_1": RED,
|
||||
"v_2": RED,
|
||||
}
|
||||
energy_eq = TexMobject(
|
||||
"\\frac{1}{2} m_1 (v_1)^2 + "
|
||||
"\\frac{1}{2} m_2 (v_2)^2 = "
|
||||
"\\text{const.}",
|
||||
tex_to_color_map=tex_to_color_map
|
||||
)
|
||||
energy_eq.scale(0.8)
|
||||
momentum_eq = TexMobject(
|
||||
"m_1 v_1 + m_2 v_2 = \\text{const.}",
|
||||
tex_to_color_map=tex_to_color_map
|
||||
)
|
||||
energy_label = TextMobject(
|
||||
"Conservation of energy"
|
||||
)
|
||||
momentum_label = TextMobject(
|
||||
"Conservation of momentum"
|
||||
)
|
||||
energy_group = VGroup(energy_label, energy_eq)
|
||||
momentum_group = VGroup(momentum_label, momentum_eq)
|
||||
groups = VGroup(energy_group, momentum_group)
|
||||
for group in groups:
|
||||
group.arrange_submobjects(DOWN, buff=MED_LARGE_BUFF)
|
||||
group[0].set_color(GREEN)
|
||||
groups.arrange_submobjects(DOWN, buff=2)
|
||||
groups.to_edge(LEFT)
|
||||
return groups
|
||||
|
||||
def get_optics_groups(self):
|
||||
self.time_tracker = ValueTracker(0)
|
||||
self.time_tracker.add_updater(
|
||||
lambda m, dt: m.increment_value(dt)
|
||||
)
|
||||
self.add(self.time_tracker)
|
||||
return VGroup(
|
||||
self.get_speed_group(),
|
||||
self.get_angle_group()
|
||||
)
|
||||
|
||||
def get_speed_group(self):
|
||||
speed_label = TextMobject("Constant speed of light")
|
||||
speed_label.set_color(YELLOW)
|
||||
speed_light_template = Line(LEFT, RIGHT)
|
||||
speed_light_template.fade(1)
|
||||
speed_light_template.match_width(speed_label)
|
||||
speed_light_template.next_to(speed_label, DOWN, MED_LARGE_BUFF)
|
||||
speed_light = speed_light_template.deepcopy()
|
||||
|
||||
def update_speed_light(light, period=2, time_width=0.05):
|
||||
time = self.time_tracker.get_value()
|
||||
alpha = (time / period) % 1
|
||||
# alpha = 1 - 2 * abs(alpha - 0.5)
|
||||
alpha *= 1.5
|
||||
a = alpha - time_width / 2
|
||||
b = alpha + time_width / 2
|
||||
light.pointwise_become_partial(
|
||||
speed_light_template, max(a, 0), min(b, 1)
|
||||
)
|
||||
opacity = speed_label.family_members_with_points()[0].get_fill_opacity()
|
||||
light.set_stroke(YELLOW, width=3, opacity=opacity)
|
||||
# light.stretch(0.5, 0)
|
||||
# point = speed_light_template.point_from_proportion(0.25)
|
||||
# light.stretch(2, 0, about_point=point)
|
||||
|
||||
speed_light.add_updater(update_speed_light)
|
||||
result = VGroup(
|
||||
speed_label, speed_light_template, speed_light
|
||||
)
|
||||
return result
|
||||
|
||||
def get_angle_group(self):
|
||||
title = VGroup(*map(TextMobject, [
|
||||
"Angle of\\\\Incidence",
|
||||
"=",
|
||||
"Angle of\\\\Refraction",
|
||||
])).arrange_submobjects(RIGHT)
|
||||
title.set_color(YELLOW)
|
||||
h_line = Line(LEFT, RIGHT)
|
||||
h_line.match_width(title)
|
||||
h_line.set_stroke(LIGHT_GREY)
|
||||
h_line.set_sheen(1, UL)
|
||||
points = [
|
||||
h_line.get_left() + UP,
|
||||
h_line.get_center(),
|
||||
h_line.get_right() + UP,
|
||||
]
|
||||
dashed_lines = VGroup(
|
||||
DashedLine(*points[0:2]), DashedLine(*points[1:3])
|
||||
)
|
||||
dashed_lines.set_stroke(WHITE, 2)
|
||||
v_shape = VMobject()
|
||||
v_shape.set_points_as_corners(points)
|
||||
v_shape.fade(1)
|
||||
|
||||
theta = dashed_lines[1].get_angle()
|
||||
arcs = VGroup(
|
||||
Arc(start_angle=0, angle=theta),
|
||||
Arc(start_angle=PI, angle=-theta),
|
||||
)
|
||||
arcs.set_stroke(WHITE, 2)
|
||||
thetas = VGroup()
|
||||
for v in LEFT, RIGHT:
|
||||
theta = TexMobject("\\theta")
|
||||
theta.next_to(arcs, v, aligned_edge=DOWN)
|
||||
theta.shift(SMALL_BUFF * UP)
|
||||
thetas.add(theta)
|
||||
|
||||
beam = VMobject()
|
||||
|
||||
def update_beam(beam, period=2, time_width=0.05):
|
||||
time = self.time_tracker.get_value()
|
||||
alpha = (time / period) % 1
|
||||
alpha *= 1.5
|
||||
a = alpha - time_width / 2
|
||||
b = alpha + time_width / 2
|
||||
beam.pointwise_become_partial(
|
||||
v_shape, max(a, 0), min(b, 1)
|
||||
)
|
||||
opacity = title.family_members_with_points()[0].get_fill_opacity()
|
||||
beam.set_stroke(YELLOW, width=3, opacity=opacity)
|
||||
|
||||
beam.add_updater(update_beam)
|
||||
title.next_to(v_shape, UP, MED_LARGE_BUFF)
|
||||
|
||||
return VGroup(
|
||||
title, h_line, arcs, thetas,
|
||||
dashed_lines, v_shape, beam
|
||||
)
|
||||
|
||||
|
||||
class ConnectionToOpticsTransparent(ConnectionToOptics):
|
||||
pass
|
|
@ -220,10 +220,14 @@ class LaggedStart(Animation):
|
|||
}
|
||||
|
||||
def __init__(self, AnimationClass, mobject, arg_creator=None, **kwargs):
|
||||
for key in ["rate_func", "run_time"]:
|
||||
if key in AnimationClass.CONFIG:
|
||||
setattr(self, key, AnimationClass.CONFIG[key])
|
||||
digest_config(self, kwargs)
|
||||
for key in "rate_func", "run_time", "lag_ratio":
|
||||
if key in kwargs:
|
||||
kwargs.pop(key)
|
||||
|
||||
if arg_creator is None:
|
||||
def arg_creator(mobject):
|
||||
return (mobject,)
|
||||
|
|
|
@ -24,7 +24,6 @@ from manimlib.utils.rate_functions import smooth
|
|||
from manimlib.utils.rate_functions import squish_rate_func
|
||||
from manimlib.utils.rate_functions import there_and_back
|
||||
from manimlib.utils.rate_functions import wiggle
|
||||
from manimlib.utils.rate_functions import double_smooth
|
||||
|
||||
|
||||
class FocusOn(Transform):
|
||||
|
@ -125,10 +124,11 @@ class ShowPassingFlash(ShowPartial):
|
|||
}
|
||||
|
||||
def get_bounds(self, alpha):
|
||||
alpha *= (1 + self.time_width)
|
||||
alpha -= self.time_width / 2.0
|
||||
lower = max(0, alpha - self.time_width / 2.0)
|
||||
upper = min(1, alpha + self.time_width / 2.0)
|
||||
tw = self.time_width
|
||||
upper = interpolate(0, 1 + tw, alpha)
|
||||
lower = upper - tw
|
||||
upper = min(upper, 1)
|
||||
lower = max(lower, 0)
|
||||
return (lower, upper)
|
||||
|
||||
def clean_up(self, *args, **kwargs):
|
||||
|
|
|
@ -298,6 +298,8 @@ class TeacherStudentsScene(PiCreatureScene):
|
|||
"raise_left_hand",
|
||||
])
|
||||
kwargs["target_mode"] = target_mode
|
||||
if "bubble_kwargs" not in kwargs:
|
||||
kwargs["bubble_kwargs"] = {"direction": LEFT}
|
||||
student = self.get_students()[kwargs.get("student_index", 2)]
|
||||
return self.pi_creature_says(
|
||||
student, *content, **kwargs
|
||||
|
|
|
@ -17,7 +17,7 @@ from manimlib.utils.color import interpolate_color
|
|||
from manimlib.utils.iterables import list_update
|
||||
from manimlib.utils.iterables import remove_list_redundancies
|
||||
from manimlib.utils.paths import straight_path
|
||||
from manimlib.utils.simple_functions import get_num_args
|
||||
from manimlib.utils.simple_functions import get_parameters
|
||||
from manimlib.utils.space_ops import angle_of_vector
|
||||
from manimlib.utils.space_ops import get_norm
|
||||
from manimlib.utils.space_ops import rotation_matrix
|
||||
|
@ -45,6 +45,7 @@ class Mobject(Container):
|
|||
if self.name is None:
|
||||
self.name = self.__class__.__name__
|
||||
self.updaters = []
|
||||
self.updating_suspended = False
|
||||
self.reset_points()
|
||||
self.generate_points()
|
||||
self.init_colors()
|
||||
|
@ -145,24 +146,23 @@ class Mobject(Container):
|
|||
|
||||
# Updating
|
||||
|
||||
def update(self, dt=0):
|
||||
for updater in self.updaters:
|
||||
num_args = get_num_args(updater)
|
||||
if num_args == 1:
|
||||
updater(self)
|
||||
elif num_args == 2:
|
||||
updater(self, dt)
|
||||
else:
|
||||
raise Exception(
|
||||
"Mobject updater expected 1 or 2 "
|
||||
"arguments, %d given" % num_args
|
||||
)
|
||||
def update(self, dt=0, recursive=True):
|
||||
if not self.updating_suspended:
|
||||
for updater in self.updaters:
|
||||
parameters = get_parameters(updater)
|
||||
if "dt" in parameters:
|
||||
updater(self, dt)
|
||||
else:
|
||||
updater(self)
|
||||
if recursive:
|
||||
for submob in self.submobjects:
|
||||
submob.update(dt, recursive)
|
||||
return self
|
||||
|
||||
def get_time_based_updaters(self):
|
||||
return [
|
||||
updater
|
||||
for updater in self.updaters
|
||||
if get_num_args(updater) == 2
|
||||
updater for updater in self.updaters
|
||||
if "dt" in get_parameters(updater)
|
||||
]
|
||||
|
||||
def get_updaters(self):
|
||||
|
@ -186,6 +186,21 @@ class Mobject(Container):
|
|||
self.updaters = []
|
||||
return self
|
||||
|
||||
def suspend_updating(self, recursive=True):
|
||||
self.updating_suspended = True
|
||||
if recursive:
|
||||
for submob in self.submobjects:
|
||||
submob.suspend_updating(recursive)
|
||||
return self
|
||||
|
||||
def resume_updating(self, recursive=True):
|
||||
self.updating_suspended = False
|
||||
if recursive:
|
||||
for submob in self.submobjects:
|
||||
submob.resume_updating(recursive)
|
||||
self.update(dt=0, recursive=recursive)
|
||||
return self
|
||||
|
||||
# Transforming operations
|
||||
|
||||
def apply_to_family(self, func):
|
||||
|
|
|
@ -15,7 +15,7 @@ class DecimalNumber(VMobject):
|
|||
"edge_to_fix": LEFT,
|
||||
}
|
||||
|
||||
def __init__(self, number, **kwargs):
|
||||
def __init__(self, number=0, **kwargs):
|
||||
VMobject.__init__(self, **kwargs)
|
||||
self.number = number
|
||||
self.initial_config = kwargs
|
||||
|
|
|
@ -69,10 +69,6 @@ class Scene(Container):
|
|||
def tear_down(self):
|
||||
pass
|
||||
|
||||
def setup_bases(self):
|
||||
for base in self.__class__.__bases__:
|
||||
base.setup(self)
|
||||
|
||||
def construct(self):
|
||||
pass # To be implemented in subclasses
|
||||
|
||||
|
@ -151,7 +147,7 @@ class Scene(Container):
|
|||
###
|
||||
|
||||
def continual_update(self, dt):
|
||||
for mobject in self.get_mobject_family_members():
|
||||
for mobject in self.mobjects:
|
||||
mobject.update(dt)
|
||||
for continual_animation in self.continual_animations:
|
||||
continual_animation.update(dt)
|
||||
|
@ -470,6 +466,9 @@ class Scene(Container):
|
|||
# scene gets added to the scene
|
||||
if animation.mobject not in self.get_mobject_family_members():
|
||||
self.add(animation.mobject)
|
||||
# Don't call the update functions of a mobject
|
||||
# being animated
|
||||
animation.mobject.suspend_updating()
|
||||
moving_mobjects = self.get_moving_mobjects(*animations)
|
||||
|
||||
# Paint all non-moving objects onto the screen, so they don't
|
||||
|
@ -500,6 +499,7 @@ class Scene(Container):
|
|||
def clean_up_animations(self, *animations):
|
||||
for animation in animations:
|
||||
animation.clean_up(self)
|
||||
animation.mobject.resume_updating()
|
||||
return self
|
||||
|
||||
def get_mobjects_from_last_animation(self):
|
||||
|
@ -568,9 +568,9 @@ class Scene(Container):
|
|||
for frame in frames:
|
||||
self.file_writer.write_frame(frame)
|
||||
|
||||
def add_sound(self, sound_file, time_offset=0):
|
||||
def add_sound(self, sound_file, time_offset=0, gain=None, **kwargs):
|
||||
time = self.get_time() + time_offset
|
||||
self.file_writer.add_sound(sound_file, time)
|
||||
self.file_writer.add_sound(sound_file, time, gain, **kwargs)
|
||||
|
||||
def show_frame(self):
|
||||
self.update_frame(ignore_skipping=True)
|
||||
|
|
|
@ -119,7 +119,9 @@ class SceneFileWriter(object):
|
|||
def create_audio_segment(self):
|
||||
self.audio_segment = AudioSegment.silent()
|
||||
|
||||
def add_audio_segment(self, new_segment, time=None):
|
||||
def add_audio_segment(self, new_segment,
|
||||
time=None,
|
||||
gain_to_background=None):
|
||||
if not self.includes_sound:
|
||||
self.includes_sound = True
|
||||
self.create_audio_segment()
|
||||
|
@ -138,13 +140,17 @@ class SceneFileWriter(object):
|
|||
crossfade=0,
|
||||
)
|
||||
self.audio_segment = segment.overlay(
|
||||
new_segment, position=int(1000 * time)
|
||||
new_segment,
|
||||
position=int(1000 * time),
|
||||
gain_during_overlay=gain_to_background,
|
||||
)
|
||||
|
||||
def add_sound(self, sound_file, time):
|
||||
def add_sound(self, sound_file, time=None, gain=None, **kwargs):
|
||||
file_path = get_full_sound_file_path(sound_file)
|
||||
new_segment = AudioSegment.from_file(file_path)
|
||||
self.add_audio_segment(new_segment, time)
|
||||
if gain:
|
||||
new_segment = new_segment.apply_gain(gain)
|
||||
self.add_audio_segment(new_segment, time, **kwargs)
|
||||
|
||||
# Writers
|
||||
def begin_animation(self, allow_write=False):
|
||||
|
@ -305,17 +311,25 @@ class SceneFileWriter(object):
|
|||
)
|
||||
# Makes sure sound file length will match video file
|
||||
self.add_audio_segment(AudioSegment.silent(0))
|
||||
self.audio_segment.export(sound_file_path)
|
||||
self.audio_segment.export(
|
||||
sound_file_path,
|
||||
bitrate='312k',
|
||||
)
|
||||
temp_file_path = movie_file_path.replace(".", "_temp.")
|
||||
commands = commands = [
|
||||
commands = [
|
||||
"ffmpeg",
|
||||
"-i", movie_file_path,
|
||||
"-i", sound_file_path,
|
||||
'-y', # overwrite output file if it exists
|
||||
"-c:v", "copy", "-c:a", "aac",
|
||||
"-c:v", "copy",
|
||||
"-c:a", "aac",
|
||||
"-b:a", "320k",
|
||||
# select video stream from first file
|
||||
"-map", "0:v:0",
|
||||
# select audio stream from second file
|
||||
"-map", "1:a:0",
|
||||
'-loglevel', 'error',
|
||||
"-shortest",
|
||||
"-strict", "experimental",
|
||||
# "-shortest",
|
||||
temp_file_path,
|
||||
]
|
||||
subprocess.call(commands)
|
||||
|
|
|
@ -16,12 +16,12 @@ def smooth(t, inflection=10.0):
|
|||
)
|
||||
|
||||
|
||||
def rush_into(t):
|
||||
return 2 * smooth(t / 2.0)
|
||||
def rush_into(t, inflection=10.0):
|
||||
return 2 * smooth(t / 2.0, inflection)
|
||||
|
||||
|
||||
def rush_from(t):
|
||||
return 2 * smooth(t / 2.0 + 0.5) - 1
|
||||
def rush_from(t, inflection=10.0):
|
||||
return 2 * smooth(t / 2.0 + 0.5, inflection) - 1
|
||||
|
||||
|
||||
def slow_into(t):
|
||||
|
|
|
@ -30,7 +30,11 @@ def choose(n, r):
|
|||
|
||||
|
||||
def get_num_args(function):
|
||||
return len(inspect.signature(function).parameters)
|
||||
return len(get_parameters(function))
|
||||
|
||||
|
||||
def get_parameters(function):
|
||||
return inspect.signature(function).parameters
|
||||
|
||||
# Just to have a less heavyweight name for this extremely common operation
|
||||
#
|
||||
|
|
|
@ -142,9 +142,8 @@ def angle_between_vectors(v1, v2):
|
|||
Returns the angle between two 3D vectors.
|
||||
This angle will always be btw 0 and TAU/2.
|
||||
"""
|
||||
l1 = get_norm(v1)
|
||||
l2 = get_norm(v2)
|
||||
return np.arccos(np.dot(v1, v2) / (l1 * l2))
|
||||
diff = (angle_of_vector(v1) - angle_of_vector(v2)) % TAU
|
||||
return min(diff, TAU - diff)
|
||||
|
||||
|
||||
def project_along_vector(point, vector):
|
||||
|
|
Loading…
Add table
Reference in a new issue