def __init__(self, *args, **kwargs): PiCreature.__init__(self, *args, **kwargs) self.scale(self.scale_factor) self.shift(LEFT) self.to_edge(DOWN, buff = LARGE_BUFF) eyes = VGroup(self.eyes, self.pupils) eyes_bottom = eyes.get_bottom() eyes.scale(self.eye_scale_factor) eyes.move_to(eyes_bottom, aligned_edge = DOWN) looking_direction = self.get_looking_direction() for pupil in self.pupils: pupil.scale_in_place(self.pupil_scale_factor) self.look(looking_direction)
class TeacherStudentsScene(Scene): def setup(self): self.teacher = Mortimer() self.teacher.to_corner(DOWN + RIGHT) self.teacher.look(DOWN+LEFT) self.students = VGroup(*[ Randolph(color = c) for c in BLUE_D, BLUE_C, BLUE_E ]) self.students.arrange_submobjects(RIGHT) self.students.scale(0.8) self.students.to_corner(DOWN+LEFT) self.teacher.look_at(self.students[-1].eyes) for student in self.students: student.look_at(self.teacher.eyes) for pi_creature in self.get_everyone(): pi_creature.bubble = None self.add(*self.get_everyone()) def get_teacher(self): return self.teacher def get_students(self): return self.students def get_everyone(self): return [self.get_teacher()] + list(self.get_students()) def get_bubble_intro_animation(self, content, bubble_type, pi_creature, **bubble_kwargs): bubble = pi_creature.get_bubble(bubble_type, **bubble_kwargs) bubble.add_content(content) bubble.resize_to_content() if pi_creature.bubble: content_intro_anims = [ Transform(pi_creature.bubble, bubble), Transform(pi_creature.bubble.content, bubble.content) ] else: content_intro_anims = [ FadeIn(bubble), Write(content), ] pi_creature.bubble = bubble return content_intro_anims def introduce_bubble(self, content, bubble_type, pi_creature, target_mode = None, added_anims = [], **bubble_kwargs): if all(map(lambda s : isinstance(s, str), content)): content = TextMobject(*content) elif len(content) == 1 and isinstance(content[0], Mobject): content = content[0] else: raise Exception("Invalid content type") anims = [] #Remove other bubbles for p in self.get_everyone(): if (p.bubble is not None) and (p is not pi_creature): anims += [ FadeOut(p.bubble), FadeOut(p.bubble.content) ] p.bubble = None anims.append(ApplyMethod(p.change_mode, "plain")) #Bring in new bubble anims += self.get_bubble_intro_animation( content, bubble_type, pi_creature, **bubble_kwargs ) #Add changing mode if not target_mode: if bubble_type is "speech": target_mode = "speaking" else: target_mode = "pondering" anims.append( ApplyMethod( pi_creature.change_mode, target_mode, ) ) self.play(*anims + added_anims) return pi_creature.bubble def teacher_says(self, *content, **kwargs): return self.introduce_bubble( content, "speech", self.get_teacher(), **kwargs ) def student_says(self, *content, **kwargs): if "target_mode" not in kwargs: target_mode = random.choice([ "raise_right_hand", "raise_left_hand", ]) kwargs["target_mode"] = target_mode student = self.get_students()[kwargs.get("student_index", 1)] return self.introduce_bubble(content, "speech", student, **kwargs) def teacher_thinks(self, *content, **kwargs): return self.introduce_bubble( content, "thought", self.get_teacher(), **kwargs ) def student_thinks(self, *content, **kwargs): student = self.get_students()[kwargs.get("student_index", 1)] return self.introduce_bubble(content, "thought", student, **kwargs) def random_blink(self, num_times = 1): for x in range(num_times): pi_creature = random.choice(self.get_everyone()) self.play(Blink(pi_creature)) Scene.dither(self) def dither(self, time = 1): self.random_blink(num_times = max(time/2, 1)) def change_student_modes(self, *modes, **kwargs): added_anims = kwargs.get("added_anims", []) pairs = zip(self.get_students(), modes) pairs = [(s, m) for s, m in pairs if m is not None] start = VGroup(*[s for s, m in pairs]) target = VGroup(*[s.copy().change_mode(m) for s, m in pairs]) self.play( Transform( start, target, submobject_mode = "lagged_start", run_time = 2 ), *added_anims ) def zoom_in_on_thought_bubble(self, bubble = None, radius = SPACE_HEIGHT+SPACE_WIDTH): if bubble is None: for pi in self.get_everyone(): if hasattr(pi, "bubble") and isinstance(pi.bubble, ThoughtBubble): bubble = pi.bubble break if bubble is None: raise Exception("No pi creatures have a thought bubble") vect = -bubble.get_bubble_center() def func(point): centered = point+vect return radius*centered/np.linalg.norm(centered) self.play(*[ ApplyPointwiseFunction(func, mob) for mob in self.get_mobjects() ])
class TeacherStudentsScene(PiCreatureScene): CONFIG = { "student_colors" : [BLUE_D, BLUE_C, BLUE_E], "student_scale_factor" : 0.8, "seconds_to_blink" : 2, } def create_pi_creatures(self): self.teacher = Mortimer() self.teacher.to_corner(DOWN + RIGHT) self.teacher.look(DOWN+LEFT) self.students = VGroup(*[ Randolph(color = c) for c in self.student_colors ]) self.students.arrange_submobjects(RIGHT) self.students.scale(self.student_scale_factor) self.students.to_corner(DOWN+LEFT) self.teacher.look_at(self.students[-1].eyes) for student in self.students: student.look_at(self.teacher.eyes) return [self.teacher] + list(self.students) def get_teacher(self): return self.teacher def get_students(self): return self.students def teacher_says(self, *content, **kwargs): return self.pi_creature_says( self.get_teacher(), *content, **kwargs ) def student_says(self, *content, **kwargs): if "target_mode" not in kwargs: target_mode = random.choice([ "raise_right_hand", "raise_left_hand", ]) kwargs["target_mode"] = target_mode student = self.get_students()[kwargs.get("student_index", 1)] return self.pi_creature_says( student, *content, **kwargs ) def teacher_thinks(self, *content, **kwargs): return self.pi_creature_thinks( self.get_teacher(), *content, **kwargs ) def student_thinks(self, *content, **kwargs): student = self.get_students()[kwargs.get("student_index", 1)] return self.pi_creature_thinks(student, *content, **kwargs) def change_student_modes(self, *modes, **kwargs): added_anims = kwargs.get("added_anims", []) pairs = zip(self.get_students(), modes) pairs = [(s, m) for s, m in pairs if m is not None] start = VGroup(*[s for s, m in pairs]) target = VGroup(*[s.copy().change_mode(m) for s, m in pairs]) if "look_at_arg" in kwargs: for pi in target: pi.look_at(kwargs["look_at_arg"]) self.play( Transform( start, target, submobject_mode = "lagged_start", run_time = 2 ), *added_anims ) def zoom_in_on_thought_bubble(self, bubble = None, radius = SPACE_HEIGHT+SPACE_WIDTH): if bubble is None: for pi in self.get_pi_creatures(): if hasattr(pi, "bubble") and isinstance(pi.bubble, ThoughtBubble): bubble = pi.bubble break if bubble is None: raise Exception("No pi creatures have a thought bubble") vect = -bubble.get_bubble_center() def func(point): centered = point+vect return radius*centered/np.linalg.norm(centered) self.play(*[ ApplyPointwiseFunction(func, mob) for mob in self.get_mobjects() ])
class CountingScene(Scene): CONFIG = { "digit_place_colors" : [YELLOW, MAROON_B, RED, GREEN, BLUE, PURPLE_D], "counting_dot_starting_position" : (SPACE_WIDTH-1)*RIGHT + (SPACE_HEIGHT-1)*UP, "count_dot_starting_radius" : 0.5, "dot_configuration_height" : 2, "ones_configuration_location" : UP+2*RIGHT, "num_scale_factor" : 2, "num_start_location" : 2*DOWN, } def setup(self): self.dots = VGroup() self.number = 0 self.max_place = 0 self.number_mob = VGroup(TexMobject(str(self.number))) self.number_mob.scale(self.num_scale_factor) self.number_mob.shift(self.num_start_location) self.dot_templates = [] self.dot_template_iterators = [] self.curr_configurations = [] self.arrows = VGroup() self.add(self.number_mob) def get_template_configuration(self, place): #This should probably be replaced for non-base-10 counting scenes down_right = (0.5)*RIGHT + (np.sqrt(3)/2)*DOWN result = [] for down_right_steps in range(5): for left_steps in range(down_right_steps): result.append( down_right_steps*down_right + left_steps*LEFT ) return reversed(result[:self.get_place_max(place)]) def get_dot_template(self, place): #This should be replaced for non-base-10 counting scenes down_right = (0.5)*RIGHT + (np.sqrt(3)/2)*DOWN dots = VGroup(*[ Dot( point, radius = 0.25, fill_opacity = 0, stroke_width = 2, stroke_color = WHITE, ) for point in self.get_template_configuration(place) ]) dots.scale_to_fit_height(self.dot_configuration_height) return dots def add_configuration(self): new_template = self.get_dot_template(len(self.dot_templates)) new_template.move_to(self.ones_configuration_location) left_vect = (new_template.get_width()+LARGE_BUFF)*LEFT new_template.shift( left_vect*len(self.dot_templates) ) self.dot_templates.append(new_template) self.dot_template_iterators.append( it.cycle(new_template) ) self.curr_configurations.append(VGroup()) def count(self, max_val, run_time_per_anim = 1): for x in range(max_val): self.increment(run_time_per_anim) def increment(self, run_time_per_anim = 1): moving_dot = Dot( self.counting_dot_starting_position, radius = self.count_dot_starting_radius, color = self.digit_place_colors[0], ) moving_dot.generate_target() moving_dot.set_fill(opacity = 0) kwargs = { "run_time" : run_time_per_anim } continue_rolling_over = True first_move = True place = 0 while continue_rolling_over: added_anims = [] if first_move: added_anims += self.get_digit_increment_animations() first_move = False moving_dot.target.replace( self.dot_template_iterators[place].next() ) self.play(MoveToTarget(moving_dot), *added_anims, **kwargs) self.curr_configurations[place].add(moving_dot) if len(self.curr_configurations[place].split()) == self.get_place_max(place): full_configuration = self.curr_configurations[place] self.curr_configurations[place] = VGroup() place += 1 center = full_configuration.get_center_of_mass() radius = 0.6*max( full_configuration.get_width(), full_configuration.get_height(), ) circle = Circle( radius = radius, stroke_width = 0, fill_color = self.digit_place_colors[place], fill_opacity = 0.5, ) circle.move_to(center) moving_dot = VGroup(circle, full_configuration) moving_dot.generate_target() moving_dot[0].set_fill(opacity = 0) else: continue_rolling_over = False def get_digit_increment_animations(self): result = [] self.number += 1 is_next_digit = self.is_next_digit() if is_next_digit: self.max_place += 1 new_number_mob = self.get_number_mob(self.number) new_number_mob.move_to(self.number_mob, RIGHT) if is_next_digit: self.add_configuration() place = len(new_number_mob.split())-1 result.append(FadeIn(self.dot_templates[place])) arrow = Arrow( new_number_mob[place].get_top(), self.dot_templates[place].get_bottom(), color = self.digit_place_colors[place] ) self.arrows.add(arrow) result.append(ShowCreation(arrow)) result.append(Transform( self.number_mob, new_number_mob, submobject_mode = "lagged_start" )) return result def get_number_mob(self, num): result = VGroup() place = 0 max_place = self.max_place while place < max_place: digit = TexMobject(str(self.get_place_num(num, place))) if place >= len(self.digit_place_colors): self.digit_place_colors += self.digit_place_colors digit.highlight(self.digit_place_colors[place]) digit.scale(self.num_scale_factor) digit.next_to(result, LEFT, buff = SMALL_BUFF, aligned_edge = DOWN) result.add(digit) place += 1 return result def is_next_digit(self): return False def get_place_num(self, num, place): return 0 def get_place_max(self, place): return 0
class TeacherStudentsScene(PiCreatureScene): CONFIG = { "student_colors": [BLUE_D, BLUE_E, BLUE_C], "student_scale_factor": 0.8, "seconds_to_blink": 2, } def create_pi_creatures(self): self.teacher = Mortimer() self.teacher.to_corner(DOWN + RIGHT) self.teacher.look(DOWN + LEFT) self.students = VGroup( *[Randolph(color=c) for c in self.student_colors]) self.students.arrange_submobjects(RIGHT) self.students.scale(self.student_scale_factor) self.students.to_corner(DOWN + LEFT) self.teacher.look_at(self.students[-1].eyes) for student in self.students: student.look_at(self.teacher.eyes) return [self.teacher] + list(self.students) def get_teacher(self): return self.teacher def get_students(self): return self.students def teacher_says(self, *content, **kwargs): return self.pi_creature_says(self.get_teacher(), *content, **kwargs) def student_says(self, *content, **kwargs): if "target_mode" not in kwargs: target_mode = random.choice([ "raise_right_hand", "raise_left_hand", ]) kwargs["target_mode"] = target_mode student = self.get_students()[kwargs.get("student_index", 1)] return self.pi_creature_says(student, *content, **kwargs) def teacher_thinks(self, *content, **kwargs): return self.pi_creature_thinks(self.get_teacher(), *content, **kwargs) def student_thinks(self, *content, **kwargs): student = self.get_students()[kwargs.get("student_index", 1)] return self.pi_creature_thinks(student, *content, **kwargs) def change_student_modes(self, *modes, **kwargs): added_anims = kwargs.pop("added_anims", []) self.play(self.get_student_changes(*modes, **kwargs), *added_anims) def get_student_changes(self, *modes, **kwargs): pairs = zip(self.get_students(), modes) pairs = [(s, m) for s, m in pairs if m is not None] start = VGroup(*[s for s, m in pairs]) target = VGroup(*[s.copy().change_mode(m) for s, m in pairs]) if "look_at_arg" in kwargs: for pi in target: pi.look_at(kwargs["look_at_arg"]) submobject_mode = kwargs.get("submobject_mode", "lagged_start") return Transform(start, target, submobject_mode=submobject_mode, run_time=2) def zoom_in_on_thought_bubble(self, bubble=None, radius=SPACE_HEIGHT + SPACE_WIDTH): if bubble is None: for pi in self.get_pi_creatures(): if hasattr(pi, "bubble") and isinstance( pi.bubble, ThoughtBubble): bubble = pi.bubble break if bubble is None: raise Exception("No pi creatures have a thought bubble") vect = -bubble.get_bubble_center() def func(point): centered = point + vect return radius * centered / np.linalg.norm(centered) self.play( * [ApplyPointwiseFunction(func, mob) for mob in self.get_mobjects()])
def construct(self): morty = Mortimer() morty.next_to(ORIGIN, DOWN) n_patrons = len(self.specific_patrons) special_thanks = TextMobject("Special thanks") special_thanks.highlight(YELLOW) special_thanks.to_edge(UP) patreon_logo = PatreonLogo() patreon_logo.next_to(morty, UP, buff = MED_LARGE_BUFF) left_patrons = VGroup(*map(TextMobject, self.specific_patrons[:n_patrons/2] )) right_patrons = VGroup(*map(TextMobject, self.specific_patrons[n_patrons/2:] )) for patrons in left_patrons, right_patrons: patrons.arrange_submobjects( DOWN, aligned_edge = LEFT, buff = 1.5*MED_SMALL_BUFF ) all_patrons = VGroup(left_patrons, right_patrons) all_patrons.scale(self.patron_scale_val) for patrons, vect in (left_patrons, LEFT), (right_patrons, RIGHT): patrons.to_edge(vect, buff = MED_SMALL_BUFF) if patrons.get_height() > 2*SPACE_HEIGHT - LARGE_BUFF: patrons.to_edge(UP, buff = MED_SMALL_BUFF) shift_distance = max( 0, (all_patrons.get_height() - 2*SPACE_HEIGHT) ) if shift_distance > 0: shift_distance += 1 velocity = shift_distance/9.0 def get_shift_anim(): return ApplyMethod( all_patrons.shift, velocity*UP, rate_func = None ) self.play( morty.change_mode, "gracious", DrawBorderThenFill(patreon_logo), ) self.play(Write(special_thanks, run_time = 1)) self.play( Write(left_patrons), morty.look_at, left_patrons ) self.play( Write(right_patrons), morty.look_at, right_patrons ) self.play(Blink(morty), get_shift_anim()) for patrons in left_patrons, right_patrons: for index in 0, -1: self.play( morty.look_at, patrons[index], get_shift_anim() ) self.play(get_shift_anim())