From c7239a035cf745046d3e576570597913a5deaaa3 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 7 Nov 2016 11:05:41 -0800 Subject: [PATCH] Whoa boy, I've gotta get better about my commits... --- animation/simple_animations.py | 8 +- constants.py | 4 +- eoc/__init__.py | 0 eoc/chapter1.py | 277 ++++++++++++++++++++++++++++++++ mobject/svg_mobject.py | 18 ++- mobject/tex_mobject.py | 13 +- old_projects/eola/chapter10.py | 6 +- old_projects/eola/chapter11.py | 12 +- old_projects/eola/chapter6.py | 6 +- old_projects/eola/chapter7.py | 10 +- old_projects/eola/chapter8.py | 10 +- old_projects/eola/chapter8p2.py | 4 +- old_projects/eola/chapter9.py | 2 +- old_projects/eola/footnote2.py | 2 +- patreon.py | 5 +- topics/characters.py | 141 ++-------------- topics/objects.py | 164 +++++++++++++++++++ wcat.py | 118 ++++++++++++-- 18 files changed, 610 insertions(+), 190 deletions(-) create mode 100644 eoc/__init__.py create mode 100644 eoc/chapter1.py create mode 100644 topics/objects.py diff --git a/animation/simple_animations.py b/animation/simple_animations.py index 245a325c..410f812c 100644 --- a/animation/simple_animations.py +++ b/animation/simple_animations.py @@ -106,7 +106,8 @@ class MoveAlongPath(Animation): class Homotopy(Animation): CONFIG = { - "run_time" : 3 + "run_time" : 3, + "apply_function_kwargs" : {}, } def __init__(self, homotopy, mobject, **kwargs): """ @@ -120,7 +121,10 @@ class Homotopy(Animation): def update_submobject(self, submob, start, alpha): submob.points = start.points - submob.apply_function(self.function_at_time_t(alpha)) + submob.apply_function( + self.function_at_time_t(alpha), + **self.apply_function_kwargs + ) def update_mobject(self, alpha): Animation.update_mobject(self, alpha) diff --git a/constants.py b/constants.py index 66b7df09..a50ad8e3 100644 --- a/constants.py +++ b/constants.py @@ -1,8 +1,8 @@ import os import numpy as np -DEFAULT_HEIGHT = 1080 -DEFAULT_WIDTH = 1920 +DEFAULT_HEIGHT = 1080*2 +DEFAULT_WIDTH = 1920*2 DEFAULT_FRAME_DURATION = 0.04 #There might be other configuration than pixel_shape later... diff --git a/eoc/__init__.py b/eoc/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/eoc/chapter1.py b/eoc/chapter1.py new file mode 100644 index 00000000..6d03d2c2 --- /dev/null +++ b/eoc/chapter1.py @@ -0,0 +1,277 @@ +from helpers import * + +from mobject.tex_mobject import TexMobject +from mobject import Mobject +from mobject.image_mobject import ImageMobject +from mobject.vectorized_mobject import * + +from animation.animation import Animation +from animation.transform import * +from animation.simple_animations import * +from animation.playground import * +from topics.geometry import * +from topics.characters import * +from topics.functions import * +from topics.fractals import * +from topics.number_line import * +from topics.combinatorics import * +from topics.numerals import * +from topics.three_dimensions import * +from topics.objects import * +from scene import Scene +from camera import Camera +from mobject.svg_mobject import * +from mobject.tex_mobject import * + +class OpeningQuote(Scene): + CONFIG = { + "quote" : """ + The art of doing mathematics is finding + that special case that contains all the + germs of generality. + """, + "author" : "David Hilbert" + } + def construct(self): + quote = self.get_quote() + author = self.get_author(quote) + + self.play(FadeIn( + quote, + submobject_mode = "lagged_start", + run_time = 2 + )) + self.dither(2) + self.play(Write(author, run_time = 4)) + self.dither() + + def get_quote(self): + quote = TextMobject( + "``%s''"%self.quote.strip(), + alignment = "", + ) + quote.to_edge(UP) + return quote + + def get_author(self, quote): + author = TextMobject("-" + self.author) + author.next_to(quote, DOWN) + author.highlight(YELLOW) + return author + +class Introduction(TeacherStudentsScene): + def construct(self): + self.show_series() + self.go_through_students() + self.zoom_in_on_first() + + def show_series(self): + series = VideoSeries() + series.to_edge(UP) + this_video = series[0] + this_video.highlight(YELLOW) + this_video.save_state() + this_video.set_fill(opacity = 0) + this_video.center() + this_video.scale_to_fit_height(2*SPACE_HEIGHT) + self.this_video = this_video + + words = TextMobject( + "Welcome to \\\\", + "Essence of calculus" + ) + words.highlight_by_tex("Essence of calculus", YELLOW) + self.remove(self.teacher) + self.teacher.change_mode("happy") + self.add(self.teacher) + self.play( + FadeIn( + series, + submobject_mode = "lagged_start", + run_time = 2 + ), + Blink(self.get_teacher()) + ) + self.teacher_says(words, target_mode = "hooray") + self.play( + ApplyMethod(this_video.restore, run_time = 3), + *[ + ApplyFunction( + lambda p : p.change_mode("hooray").look_at(series[1]), + pi + ) + for pi in self.get_everyone() + ] + ) + def homotopy(x, y, z, t): + alpha = (0.7*x + SPACE_WIDTH)/(2*SPACE_WIDTH) + beta = squish_rate_func(smooth, alpha-0.15, alpha+0.15)(t) + return (x, y - 0.3*np.sin(np.pi*beta), z) + self.play( + Homotopy( + homotopy, series, + apply_function_kwargs = {"maintain_smoothness" : False}, + ), + *[ + ApplyMethod(pi.look_at, series[-1]) + for pi in self.get_everyone() + ], + run_time = 5 + ) + self.play( + FadeOut(self.teacher.bubble), + FadeOut(self.teacher.bubble.content), + *[ + ApplyMethod(pi.change_mode, "happy") + for pi in self.get_everyone() + ] + ) + + def go_through_students(self): + pi1, pi2, pi3 = self.get_students() + for pi in pi1, pi2, pi3: + pi.save_state() + bubble = pi1.get_bubble(width = 5) + bubble.set_fill(BLACK, opacity = 1) + remembered_symbols = VGroup( + TexMobject("\\int_0^1 \\frac{1}{1-x^2}\\,dx").shift(UP+LEFT), + TexMobject("\\frac{d}{dx} e^x = e^x").shift(DOWN+RIGHT), + ) + cant_wait = TextMobject("I litterally \\\\ can't wait") + big_derivative = TexMobject(""" + \\frac{d}{dx} \\left( \\sin(x^2)2^{\\sqrt{x}} \\right) + """) + + self.play( + pi1.change_mode, "confused", + pi1.look_at, bubble.get_right(), + ShowCreation(bubble), + pi2.fade, + pi3.fade, + ) + bubble.add_content(remembered_symbols) + self.play(Write(remembered_symbols)) + self.play(ApplyMethod( + remembered_symbols.fade, 0.7, + submobject_mode = "lagged_start", + run_time = 3 + )) + self.play( + pi1.restore, + pi1.fade, + pi2.restore, + pi2.change_mode, "hooray", + pi2.look_at, bubble.get_right(), + bubble.pin_to, pi2, + FadeOut(remembered_symbols), + ) + bubble.add_content(cant_wait) + self.play(Write(cant_wait, run_time = 2)) + self.play(Blink(pi2)) + self.play( + pi2.restore, + pi2.fade, + pi3.restore, + pi3.change_mode, "pleading", + pi3.look_at, bubble.get_right(), + bubble.pin_to, pi3, + FadeOut(cant_wait) + ) + bubble.add_content(big_derivative) + self.play(Write(big_derivative)) + self.play(Blink(pi3)) + self.dither() + + def zoom_in_on_first(self): + this_video = self.this_video + self.remove(this_video) + this_video.generate_target() + this_video.target.scale_to_fit_height(2*SPACE_HEIGHT) + this_video.target.center() + this_video.target.set_fill(opacity = 0) + + everything = VGroup(*self.get_mobjects()) + self.play( + FadeOut(everything), + MoveToTarget(this_video, run_time = 2) + ) + +class IntroduceCircle(Scene): + def construct(self): + circle = Circle(radius = 3, color = WHITE) + circle.to_edge(LEFT) + radius = Line(circle.get_center(), circle.get_right()) + radius.highlight(MAROON_B) + R = TexMobject("R").next_to(radius, UP) + + area, circumference = words = VGroup(*map(TextMobject, [ + "Area =", "Circumference =" + ])) + area.highlight(BLUE) + circumference.highlight(YELLOW) + + words.arrange_submobjects(DOWN, aligned_edge = LEFT) + words.next_to(circle, RIGHT) + pi_R, pre_squared = TexMobject("\\pi R", "{}^2") + squared = TexMobject("2").replace(pre_squared) + area_form = VGroup(pi_R, squared) + area_form.next_to(area, RIGHT) + two, pi_R = TexMobject("2", "\\pi R") + circum_form = VGroup(pi_R, two) + circum_form.next_to(circumference, RIGHT) + + self.play(ShowCreation(radius), Write(R)) + self.play( + Rotate(radius, 2*np.pi, about_point = circle.get_center()), + ShowCreation(circle) + ) + self.play( + FadeIn(area), + Write(area_form), + circle.set_fill, area.get_color(), 0.5, + Animation(radius), + Animation(R), + ) + self.dither() + self.play( + circle.set_stroke, circumference.get_color(), + FadeIn(circumference), + Animation(radius), + Animation(R), + ) + self.play(Transform( + area_form.copy(), + circum_form, + path_arc = -np.pi/2, + run_time = 3 + )) + self.dither() + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobject/svg_mobject.py b/mobject/svg_mobject.py index 5f6a0b26..a33c100c 100644 --- a/mobject/svg_mobject.py +++ b/mobject/svg_mobject.py @@ -9,27 +9,31 @@ class SVGMobject(VMobject): CONFIG = { "initial_scale_factor" : 1, "should_center" : True, + #Must be filled in in a subclass, or when called + "file_name" : None, } - def __init__(self, svg_file, **kwargs): + def __init__(self, **kwargs): digest_config(self, kwargs, locals()) self.ensure_valid_file() VMobject.__init__(self, **kwargs) self.move_into_position() def ensure_valid_file(self): + if self.file_name is None: + raise Exception("Must specify file for SVGMobject") possible_paths = [ - self.svg_file, - os.path.join(IMAGE_DIR, self.svg_file), - os.path.join(IMAGE_DIR, self.svg_file + ".svg"), + self.file_name, + os.path.join(IMAGE_DIR, self.file_name), + os.path.join(IMAGE_DIR, self.file_name + ".svg"), ] for path in possible_paths: if os.path.exists(path): - self.svg_file = path + self.file_name = path return - raise IOError("No file matching %s in image directory"%self.svg_file) + raise IOError("No file matching %s in image directory"%self.file_name) def generate_points(self): - doc = minidom.parse(self.svg_file) + doc = minidom.parse(self.file_name) self.ref_to_element = {} for svg in doc.getElementsByTagName("svg"): self.add(*self.get_mobjects_from(svg)) diff --git a/mobject/tex_mobject.py b/mobject/tex_mobject.py index 4f7c2e8e..ffc1c9f2 100644 --- a/mobject/tex_mobject.py +++ b/mobject/tex_mobject.py @@ -47,9 +47,12 @@ class TexMobject(SVGMobject): self.args = args[0] ## assert(all([isinstance(a, str) for a in self.args])) - self.tex_string = self.get_modified_expression() - VMobject.__init__(self, **kwargs) - self.move_into_position() + self.tex_string = self.get_modified_expression() + file_name = tex_to_svg_file( + self.tex_string, + self.template_tex_file + ) + SVGMobject.__init__(self, file_name = file_name, **kwargs) if self.organize_left_to_right: self.organize_submobjects_left_to_right() @@ -61,10 +64,6 @@ class TexMobject(SVGMobject): def generate_points(self): - self.svg_file = tex_to_svg_file( - self.tex_string, - self.template_tex_file - ) SVGMobject.generate_points(self) if len(self.args) > 1: self.handle_multiple_args() diff --git a/old_projects/eola/chapter10.py b/old_projects/eola/chapter10.py index 865ea36d..8d2b4ad6 100644 --- a/old_projects/eola/chapter10.py +++ b/old_projects/eola/chapter10.py @@ -115,7 +115,7 @@ class EigenThingsArentAllThatBad(TeacherStudentsScene): def construct(self): self.teacher_says( "Eigen-things aren't \\\\ actually so bad", - pi_creature_target_mode = "hooray" + target_mode = "hooray" ) self.change_student_modes( "pondering", "pondering", "erm" @@ -496,7 +496,7 @@ class CanEigenvaluesBeNegative(TeacherStudentsScene): def construct(self): self.student_says("Can eigenvalues be negative?") self.random_blink() - self.teacher_says("But of course!", pi_creature_target_mode = "hooray") + self.teacher_says("But of course!", target_mode = "hooray") self.random_blink() class EigenvalueNegativeOneHalf(LinearTransformationScene): @@ -659,7 +659,7 @@ class WordsOnComputation(TeacherStudentsScene): self.teacher_says( "I won't cover the full\\\\", "details of computation...", - pi_creature_target_mode = "guilty" + target_mode = "guilty" ) self.change_student_modes("angry", "sassy", "angry") self.random_blink() diff --git a/old_projects/eola/chapter11.py b/old_projects/eola/chapter11.py index 6092174e..7fe56c99 100644 --- a/old_projects/eola/chapter11.py +++ b/old_projects/eola/chapter11.py @@ -1427,7 +1427,7 @@ class ProposeDerivativeAsMatrix(TeacherStudentsScene): derivative with a matrix """, - pi_creature_target_mode = "hooray" + target_mode = "hooray" ) self.random_blink() self.change_student_modes("pondering", "confused", "erm") @@ -1975,7 +1975,7 @@ class BackToTheQuestion(TeacherStudentsScene): this relate to what vectors really are? """, - pi_creature_target_mode = "confused" + target_mode = "confused" ) self.random_blink(2) self.teacher_says( @@ -2435,7 +2435,7 @@ class TextbooksAreAbstract(TeacherStudentsScene): All the textbooks I found are pretty abstract. """, - pi_creature_target_mode = "pleading" + target_mode = "pleading" ) self.random_blink(3) self.teacher_says( @@ -2456,7 +2456,7 @@ class TextbooksAreAbstract(TeacherStudentsScene): self.teacher_says( "Only then should you\\\\", "think from the axioms", - pi_creature_target_mode = "surprised" + target_mode = "surprised" ) self.change_student_modes(*["pondering"]*3) self.random_blink() @@ -2465,7 +2465,7 @@ class LastAskWhatAreVectors(TeacherStudentsScene): def construct(self): self.student_says( "So...what are vectors?", - pi_creature_target_mode = "erm" + target_mode = "erm" ) self.random_blink() self.teacher_says( @@ -2559,7 +2559,7 @@ class GoodLuck(TeacherStudentsScene): def construct(self): self.teacher_says( "Good luck with \\\\ your future learning!", - pi_creature_target_mode = "hooray" + target_mode = "hooray" ) self.change_student_modes(*["happy"]*3) self.random_blink(3) diff --git a/old_projects/eola/chapter6.py b/old_projects/eola/chapter6.py index df98ab41..86d1193e 100644 --- a/old_projects/eola/chapter6.py +++ b/old_projects/eola/chapter6.py @@ -70,12 +70,12 @@ class NoComputations(TeacherStudentsScene): self.setup() self.student_says( "Will you cover \\\\ computations?", - pi_creature_target_mode = "raise_left_hand" + target_mode = "raise_left_hand" ) self.random_blink() self.teacher_says( "Well...uh...no", - pi_creature_target_mode = "guilty", + target_mode = "guilty", ) self.play(*[ ApplyMethod(student.change_mode, mode) @@ -2037,7 +2037,7 @@ class WhatAboutNonsquareMatrices(TeacherStudentsScene): def construct(self): self.student_says( "What about \\\\ nonsquare matrices?", - pi_creature_target_mode = "raise_right_hand" + target_mode = "raise_right_hand" ) self.play(self.get_students()[0].change_mode, "confused") self.random_blink(6) diff --git a/old_projects/eola/chapter7.py b/old_projects/eola/chapter7.py index 487b28aa..29dc51dd 100644 --- a/old_projects/eola/chapter7.py +++ b/old_projects/eola/chapter7.py @@ -523,7 +523,7 @@ class AskAboutSymmetry(TeacherStudentsScene): VMobject(question[3], question[5]).highlight(W_COLOR) self.student_says( question, - pi_creature_target_mode = "raise_left_hand" + target_mode = "raise_left_hand" ) self.change_student_modes("confused") self.play(self.get_teacher().change_mode, "pondering") @@ -673,7 +673,7 @@ class LurkingQuestion(TeacherStudentsScene): # two views connected? # """, # student_index = 2, - # pi_creature_target_mode = "raise_left_hand", + # target_mode = "raise_left_hand", # width = 6, # ) self.change_student_modes( @@ -1347,7 +1347,7 @@ class WhatAboutTheGeometricView(TeacherStudentsScene): What does this association mean geometrically? """, - pi_creature_target_mode = "raise_right_hand" + target_mode = "raise_right_hand" ) self.change_student_modes("pondering", "raise_right_hand", "pondering") self.random_blink(2) @@ -1897,7 +1897,7 @@ class AskAboutNonUnitVectors(TeacherStudentsScene): def construct(self): self.student_says( "What about \\\\ non-unit vectors", - pi_creature_target_mode = "raise_left_hand" + target_mode = "raise_left_hand" ) self.random_blink(2) @@ -2124,7 +2124,7 @@ class IsntThisBeautiful(TeacherStudentsScene): self.teacher.look(DOWN+LEFT) self.teacher_says( "Isn't this", "beautiful", - pi_creature_target_mode = "surprised" + target_mode = "surprised" ) for student in self.get_students(): self.play(student.change_mode, "happy") diff --git a/old_projects/eola/chapter8.py b/old_projects/eola/chapter8.py index 4d8c4bad..599db5e5 100644 --- a/old_projects/eola/chapter8.py +++ b/old_projects/eola/chapter8.py @@ -69,7 +69,7 @@ class DoTheSameForCross(TeacherStudentsScene): def construct(self): words = TextMobject("Let's do the same \\\\ for", "cross products") words.highlight_by_tex("cross products", YELLOW) - self.teacher_says(words, pi_creature_target_mode = "surprised") + self.teacher_says(words, target_mode = "surprised") self.random_blink(2) self.change_student_modes("pondering") self.random_blink() @@ -429,7 +429,7 @@ class HowDoYouCompute(TeacherStudentsScene): def construct(self): self.student_says( "How do you \\\\ compute this?", - pi_creature_target_mode = "raise_left_hand" + target_mode = "raise_left_hand" ) self.random_blink(2) @@ -1478,7 +1478,7 @@ class ThisGetsWeird(TeacherStudentsScene): def construct(self): self.teacher_says( "This gets weird...", - pi_creature_target_mode = "sassy" + target_mode = "sassy" ) self.random_blink(2) @@ -1639,7 +1639,7 @@ class ThereIsAReason(TeacherStudentsScene): "reason", "for doing it" ) words.highlight_by_tex("reason", YELLOW) - self.teacher_says(words, pi_creature_target_mode = "surprised") + self.teacher_says(words, target_mode = "surprised") self.change_student_modes( "raise_right_hand", "confused", "raise_left_hand" ) @@ -1649,7 +1649,7 @@ class RememberDuality(TeacherStudentsScene): def construct(self): words = TextMobject("Remember ", "duality", "?", arg_separator = "") words[1].gradient_highlight(BLUE, YELLOW) - self.teacher_says(words, pi_creature_target_mode = "sassy") + self.teacher_says(words, target_mode = "sassy") self.random_blink(2) class NextVideo(Scene): diff --git a/old_projects/eola/chapter8p2.py b/old_projects/eola/chapter8p2.py index 75172c88..acdff279 100644 --- a/old_projects/eola/chapter8p2.py +++ b/old_projects/eola/chapter8p2.py @@ -201,7 +201,7 @@ class DualityReview(TeacherStudentsScene): def construct(self): words = TextMobject("Quick", "duality", "review") words[1].gradient_highlight(BLUE, YELLOW) - self.teacher_says(words, pi_creature_target_mode = "surprised") + self.teacher_says(words, target_mode = "surprised") self.change_student_modes("pondering") self.random_blink(2) @@ -785,7 +785,7 @@ class WhyAreWeDoingThis(TeacherStudentsScene): def construct(self): self.student_says( "Um...why are \\\\ we doing this?", - pi_creature_target_mode = "confused" + target_mode = "confused" ) self.random_blink() self.play(self.get_teacher().change_mode, "erm") diff --git a/old_projects/eola/chapter9.py b/old_projects/eola/chapter9.py index 92084ec9..fb0eedd4 100644 --- a/old_projects/eola/chapter9.py +++ b/old_projects/eola/chapter9.py @@ -812,7 +812,7 @@ class AskAboutTranslation(TeacherStudentsScene): def construct(self): self.student_says( "\\centering How do you translate \\\\ between coordinate systems?", - pi_creature_target_mode = "raise_right_hand" + target_mode = "raise_right_hand" ) self.random_blink(3) diff --git a/old_projects/eola/footnote2.py b/old_projects/eola/footnote2.py index 12c9c012..b7c1ed41 100644 --- a/old_projects/eola/footnote2.py +++ b/old_projects/eola/footnote2.py @@ -47,7 +47,7 @@ class AnotherFootnote(TeacherStudentsScene): self.teacher.look(LEFT) self.teacher_says( "More footnotes!", - pi_creature_target_mode = "surprised", + target_mode = "surprised", run_time = 1 ) self.random_blink(2) diff --git a/patreon.py b/patreon.py index 3907a7d7..efccf8d3 100644 --- a/patreon.py +++ b/patreon.py @@ -352,7 +352,6 @@ class ClassWatching(TeacherStudentsScene): self.play(self.get_teacher().change_mode, "pondering") self.random_blink(3) - class RandolphWatching(Scene): def construct(self): randy = Randolph() @@ -464,7 +463,7 @@ class GrowRonaksSierpinski(Scene): for n in range(n_layers): ronaks_sierpinski.add(self.get_lines_at_layer(n)) ronaks_sierpinski.gradient_highlight(*self.colors) - ronaks_sierpinski.set_stroke(width = 3) + ronaks_sierpinski.set_stroke(width = 0)##TODO return ronaks_sierpinski def get_dots(self, n_layers): @@ -747,7 +746,7 @@ class EndScreen(TeacherStudentsScene): See you every other friday! """, - pi_creature_target_mode = "hooray" + target_mode = "hooray" ) self.change_student_modes(*["happy"]*3) self.random_blink() diff --git a/topics/characters.py b/topics/characters.py index a1ba1f95..1fca6458 100644 --- a/topics/characters.py +++ b/topics/characters.py @@ -5,6 +5,8 @@ from mobject.svg_mobject import SVGMobject from mobject.vectorized_mobject import VMobject, VGroup from mobject.tex_mobject import TextMobject, TexMobject +from topics.objects import Bubble, ThoughtBubble, SpeechBubble + from animation import Animation from animation.transform import Transform, ApplyMethod, \ FadeOut, FadeIn, ApplyPointwiseFunction @@ -41,7 +43,7 @@ class PiCreature(SVGMobject): "PiCreatures_%s.svg"%mode ) digest_config(self, kwargs, locals()) - SVGMobject.__init__(self, svg_file, **kwargs) + SVGMobject.__init__(self, file_name = svg_file, **kwargs) self.init_colors() if self.flip_at_start: self.flip() @@ -207,128 +209,6 @@ class Blink(ApplyMethod): -class Bubble(SVGMobject): - CONFIG = { - "direction" : LEFT, - "center_point" : ORIGIN, - "content_scale_factor" : 0.75, - "height" : 5, - "width" : 8, - "bubble_center_adjustment_factor" : 1./8, - "file_name" : None, - "propogate_style_to_family" : True, - } - def __init__(self, **kwargs): - digest_config(self, kwargs) - if self.file_name is None: - raise Exception("Must invoke Bubble subclass") - svg_file = os.path.join( - IMAGE_DIR, self.file_name - ) - SVGMobject.__init__(self, svg_file, **kwargs) - self.center() - self.stretch_to_fit_height(self.height) - self.stretch_to_fit_width(self.width) - if self.direction[0] > 0: - Mobject.flip(self) - self.direction_was_specified = ("direction" in kwargs) - self.content = Mobject() - - def get_tip(self): - #TODO, find a better way - return self.get_corner(DOWN+self.direction)-0.6*self.direction - - def get_bubble_center(self): - factor = self.bubble_center_adjustment_factor - return self.get_center() + factor*self.get_height()*UP - - def move_tip_to(self, point): - self.shift(point - self.get_tip()) - return self - - def flip(self): - Mobject.flip(self) - self.direction = -np.array(self.direction) - return self - - def pin_to(self, mobject): - mob_center = mobject.get_center() - want_to_filp = np.sign(mob_center[0]) != np.sign(self.direction[0]) - can_flip = not self.direction_was_specified - if want_to_filp and can_flip: - self.flip() - boundary_point = mobject.get_critical_point(UP-self.direction) - vector_from_center = 1.0*(boundary_point-mob_center) - self.move_tip_to(mob_center+vector_from_center) - return self - - def position_mobject_inside(self, mobject): - scaled_width = self.content_scale_factor*self.get_width() - if mobject.get_width() > scaled_width: - mobject.scale_to_fit_width(scaled_width) - mobject.shift( - self.get_bubble_center() - mobject.get_center() - ) - return mobject - - def add_content(self, mobject): - self.position_mobject_inside(mobject) - self.content = mobject - return self.content - - def write(self, *text): - self.add_content(TextMobject(*text)) - return self - - def clear(self): - self.add_content(VMobject()) - return self - -class SpeechBubble(Bubble): - CONFIG = { - "file_name" : "Bubbles_speech.svg", - "height" : 4 - } - -class DoubleSpeechBubble(Bubble): - CONFIG = { - "file_name" : "Bubbles_double_speech.svg", - "height" : 4 - } - -class ThoughtBubble(Bubble): - CONFIG = { - "file_name" : "Bubbles_thought.svg", - } - - def __init__(self, **kwargs): - Bubble.__init__(self, **kwargs) - self.submobjects.sort( - lambda m1, m2 : int((m1.get_bottom()-m2.get_bottom())[1]) - ) - - def make_green_screen(self): - self.submobjects[-1].set_fill(GREEN_SCREEN, opacity = 1) - return self - -class Headphones(SVGMobject): - CONFIG = { - "file_name" : "headphones", - "height" : 2, - "y_stretch_factor" : 0.5, - "color" : GREY, - } - def __init__(self, **kwargs): - digest_config(self, kwargs) - SVGMobject.__init__(self, self.file_name, **kwargs) - self.stretch(self.y_stretch_factor, 1) - self.scale_to_fit_height(self.height) - self.set_stroke(width = 0) - self.set_fill(color = self.color) - - - - class RandolphScene(Scene): CONFIG = { "randy_kwargs" : {}, @@ -384,6 +264,7 @@ class TeacherStudentsScene(Scene): **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), @@ -398,7 +279,7 @@ class TeacherStudentsScene(Scene): return content_intro_anims def introduce_bubble(self, content, bubble_type, pi_creature, - pi_creature_target_mode = None, + target_mode = None, added_anims = [], **bubble_kwargs): if all(map(lambda s : isinstance(s, str), content)): @@ -411,11 +292,11 @@ class TeacherStudentsScene(Scene): content, bubble_type, pi_creature, **bubble_kwargs ) - if not pi_creature_target_mode: + if not target_mode: if bubble_type is "speech": - pi_creature_target_mode = "speaking" + target_mode = "speaking" else: - pi_creature_target_mode = "pondering" + target_mode = "pondering" for p in self.get_everyone(): if (p.bubble is not None) and (p is not pi_creature): @@ -429,7 +310,7 @@ class TeacherStudentsScene(Scene): anims = added_anims + content_intro_anims + [ ApplyMethod( pi_creature.change_mode, - pi_creature_target_mode, + target_mode, ), ] self.play(*anims) @@ -441,12 +322,12 @@ class TeacherStudentsScene(Scene): ) def student_says(self, *content, **kwargs): - if "pi_creature_target_mode" not in kwargs: + if "target_mode" not in kwargs: target_mode = random.choice([ "raise_right_hand", "raise_left_hand", ]) - kwargs["pi_creature_target_mode"] = target_mode + kwargs["target_mode"] = target_mode student = self.get_students()[kwargs.get("student_index", 1)] return self.introduce_bubble(content, "speech", student, **kwargs) diff --git a/topics/objects.py b/topics/objects.py new file mode 100644 index 00000000..3e77c5d5 --- /dev/null +++ b/topics/objects.py @@ -0,0 +1,164 @@ +from helpers import * + +from mobject import Mobject +from mobject.vectorized_mobject import VGroup +from mobject.svg_mobject import SVGMobject +from mobject.tex_mobject import TextMobject + + + +class VideoIcon(SVGMobject): + CONFIG = { + "file_name" : "video_icon", + "width" : 2*SPACE_WIDTH/12., + "considered_smooth" : False, + } + def __init__(self, **kwargs): + SVGMobject.__init__(self, **kwargs) + self.center() + self.scale_to_fit_width(self.width) + self.set_stroke(color = WHITE, width = 0) + self.set_fill(color = WHITE, opacity = 1) + for mob in self: + mob.considered_smooth = False + +class VideoSeries(VGroup): + CONFIG = { + "num_videos" : 10, + "gradient_colors" : [BLUE_B, BLUE_D], + } + def __init__(self, **kwargs): + digest_config(self, kwargs) + videos = [VideoIcon() for x in range(self.num_videos)] + VGroup.__init__(self, *videos, **kwargs) + self.arrange_submobjects() + self.gradient_highlight(*self.gradient_colors) + + +class Headphones(SVGMobject): + CONFIG = { + "file_name" : "headphones", + "height" : 2, + "y_stretch_factor" : 0.5, + "color" : GREY, + } + def __init__(self, **kwargs): + digest_config(self, kwargs) + SVGMobject.__init__(self, self.file_name, **kwargs) + self.stretch(self.y_stretch_factor, 1) + self.scale_to_fit_height(self.height) + self.set_stroke(width = 0) + self.set_fill(color = self.color) + + +class Bubble(SVGMobject): + CONFIG = { + "direction" : LEFT, + "center_point" : ORIGIN, + "content_scale_factor" : 0.75, + "height" : 5, + "width" : 8, + "bubble_center_adjustment_factor" : 1./8, + "file_name" : None, + "propogate_style_to_family" : True, + } + def __init__(self, **kwargs): + digest_config(self, kwargs, locals()) + if self.file_name is None: + raise Exception("Must invoke Bubble subclass") + SVGMobject.__init__(self, **kwargs) + self.center() + self.stretch_to_fit_height(self.height) + self.stretch_to_fit_width(self.width) + if self.direction[0] > 0: + Mobject.flip(self) + self.direction_was_specified = ("direction" in kwargs) + self.content = Mobject() + + def get_tip(self): + #TODO, find a better way + return self.get_corner(DOWN+self.direction)-0.6*self.direction + + def get_bubble_center(self): + factor = self.bubble_center_adjustment_factor + return self.get_center() + factor*self.get_height()*UP + + def move_tip_to(self, point): + self.shift(point - self.get_tip()) + return self + + def flip(self): + Mobject.flip(self) + self.direction = -np.array(self.direction) + return self + + def pin_to(self, mobject): + mob_center = mobject.get_center() + want_to_filp = np.sign(mob_center[0]) != np.sign(self.direction[0]) + can_flip = not self.direction_was_specified + if want_to_filp and can_flip: + self.flip() + boundary_point = mobject.get_critical_point(UP-self.direction) + vector_from_center = 1.0*(boundary_point-mob_center) + self.move_tip_to(mob_center+vector_from_center) + return self + + def position_mobject_inside(self, mobject): + scaled_width = self.content_scale_factor*self.get_width() + if mobject.get_width() > scaled_width: + mobject.scale_to_fit_width(scaled_width) + mobject.shift( + self.get_bubble_center() - mobject.get_center() + ) + return mobject + + def add_content(self, mobject): + self.position_mobject_inside(mobject) + self.content = mobject + return self.content + + def write(self, *text): + self.add_content(TextMobject(*text)) + return self + + def resize_to_content(self): + target_width = self.content.get_width() + target_width += max(2*MED_BUFF, 2) + target_height = self.content.get_height() + target_height += 2.5*LARGE_BUFF + tip_point = self.get_tip() + self.stretch_to_fit_width(target_width) + self.stretch_to_fit_height(target_height) + self.move_tip_to(tip_point) + self.position_mobject_inside(self.content) + + def clear(self): + self.add_content(VMobject()) + return self + +class SpeechBubble(Bubble): + CONFIG = { + "file_name" : "Bubbles_speech.svg", + "height" : 4 + } + +class DoubleSpeechBubble(Bubble): + CONFIG = { + "file_name" : "Bubbles_double_speech.svg", + "height" : 4 + } + +class ThoughtBubble(Bubble): + CONFIG = { + "file_name" : "Bubbles_thought.svg", + } + + def __init__(self, **kwargs): + Bubble.__init__(self, **kwargs) + self.submobjects.sort( + lambda m1, m2 : int((m1.get_bottom()-m2.get_bottom())[1]) + ) + + def make_green_screen(self): + self.submobjects[-1].set_fill(GREEN_SCREEN, opacity = 1) + return self diff --git a/wcat.py b/wcat.py index a2c552d1..a48f6516 100644 --- a/wcat.py +++ b/wcat.py @@ -17,6 +17,7 @@ from topics.number_line import * from topics.combinatorics import * from topics.numerals import * from topics.three_dimensions import * +from topics.objects import * from scene import Scene from camera import Camera from mobject.svg_mobject import * @@ -254,7 +255,7 @@ class Introduction(TeacherStudentsScene): self.random_blink(3) self.teacher_says( "Here's why \\\\ I'm excited...", - pi_creature_target_mode = "hooray" + target_mode = "hooray" ) for pi in self.get_students(): pi.target.look_at(self.get_teacher().eyes) @@ -279,7 +280,7 @@ class WhenIWasAKid(TeacherStudentsScene): Here's why I'm excited! """, - pi_creature_target_mode = "hooray" + target_mode = "hooray" ) self.change_student_modes(*["happy"]*3) self.dither() @@ -310,7 +311,7 @@ class WhenIWasAKid(TeacherStudentsScene): Math! Excitement! You are the future! """, - pi_creature_target_mode = "hooray" + target_mode = "hooray" ) self.play( pi1.look_at, pi2.eyes, @@ -352,7 +353,7 @@ class WhenIWasAKid(TeacherStudentsScene): self.student_says( "How is this math?", student_index = -1, - pi_creature_target_mode = "pleading", + target_mode = "pleading", width = 5, height = 3, direction = RIGHT @@ -1891,7 +1892,7 @@ class ThatsTheProof(TeacherStudentsScene): Bada boom bada bang! """, - pi_creature_target_mode = "hooray", + target_mode = "hooray", width = 4 ) self.change_student_modes(*["hooray"]*3) @@ -1905,7 +1906,7 @@ class ThatsTheProof(TeacherStudentsScene): the mobius strip fact... """, - pi_creature_target_mode = "guilty", + target_mode = "guilty", width = 4, ) self.random_blink() @@ -1949,6 +1950,7 @@ class PatreonThanks(Scene): "Loo Yu Jun", "Tom", "Othman Alikhan", + "Juan Batiz-Benet", "Markus Persson", "Joseph John Cox", "Achille Brighton", @@ -1981,14 +1983,19 @@ class PatreonThanks(Scene): self.play(morty.change_mode, "gracious") self.play(Write(special_thanks, run_time = 1)) - self.play(Write(left_patrons)) - self.play(Write(right_patrons)) + self.play( + Write(left_patrons), + morty.look_at, left_patrons + ) + self.play( + Write(right_patrons), + morty.look_at, right_patrons + ) self.play(Blink(morty)) - self.play(morty.look_at, left_patrons) - self.dither() - self.play(morty.look_at, right_patrons) - self.play(Blink(morty)) - + for patrons in left_patrons, right_patrons: + for index in 0, -1: + self.play(morty.look_at, patrons[index]) + self.dither() class CreditTWo(Scene): def construct(self): @@ -2033,11 +2040,96 @@ class CreditTWo(Scene): self.play(Blink(morty)) self.dither() +class CreditThree(Scene): + def construct(self): + logo_dot = Dot().to_edge(UP).shift(3*RIGHT) + randy = Randolph() + randy.next_to(ORIGIN, DOWN) + randy.to_edge(LEFT) + randy.look(RIGHT) + self.add(randy) + bubble = randy.get_bubble(width = 2, height = 2) + + domains = VGroup(*map(TextMobject, [ + "visualnumbertheory.com", + "buymywidgets.com", + "learnwhatilearn.com", + ])) + domains.arrange_submobjects(DOWN, aligned_edge = LEFT) + domains.next_to(randy, UP, buff = LARGE_BUFF) + domains.shift_onto_screen() + + promo_code = TextMobject("Promo code: TOPOLOGY") + promo_code.shift(3*RIGHT) + self.add(promo_code) + whois = TextMobject("Free WHOIS privacy") + whois.next_to(promo_code, DOWN, buff = LARGE_BUFF) + + self.play(Blink(randy)) + self.play( + randy.change_mode, "happy", + randy.look_at, logo_dot + ) + self.dither() + self.play( + ShowCreation(bubble), + randy.change_mode, "pondering", + run_time = 2 + ) + self.play(Blink(randy)) + self.play( + Transform(bubble, VectorizedPoint(randy.get_corner(UP+LEFT))), + randy.change_mode, "sad" + ) + self.dither() + self.play( + Write(domains, run_time = 5, lag_factor = 5), + randy.look_at, domains + ) + self.dither() + self.play(Blink(randy)) + self.play( + randy.change_mode, "hooray", + randy.look_at, logo_dot, + FadeOut(domains) + ) + self.dither() + self.play( + Write(whois), + randy.change_mode, "confused", + randy.look_at, whois + ) + self.dither(2) + self.play(randy.change_mode, "sassy") + self.dither(2) + self.play( + randy.change_mode, "happy", + randy.look_at, logo_dot + ) + self.play(Blink(randy)) + self.dither() + class ShiftingLoopPairSurface(Scene): def construct(self): pass +class ThumbnailImage(ClosedLoopScene): + def construct(self): + self.add_rect_dots(square = True) + for dot in self.dots: + dot.scale_in_place(1.5) + self.add_connecting_lines(cyclic = True) + self.connecting_lines.set_stroke(width = 10) + self.loop.add(self.connecting_lines, self.dots) + + title = TextMobject("Unsolved") + title.scale(2.5) + title.to_edge(UP) + title.gradient_highlight(YELLOW, MAROON_B) + self.add(title) + self.loop.next_to(title, DOWN, buff = MED_BUFF) + self.loop.shift(2*LEFT)