mirror of
https://github.com/3b1b/manim.git
synced 2025-07-29 21:12:35 +08:00
Whoa boy, I've gotta get better about my commits...
This commit is contained in:
@ -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)
|
||||
|
@ -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...
|
||||
|
0
eoc/__init__.py
Normal file
0
eoc/__init__.py
Normal file
277
eoc/chapter1.py
Normal file
277
eoc/chapter1.py
Normal file
@ -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()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -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))
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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")
|
||||
|
@ -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):
|
||||
|
@ -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")
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
||||
|
164
topics/objects.py
Normal file
164
topics/objects.py
Normal file
@ -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
|
118
wcat.py
118
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)
|
||||
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user