diff --git a/camera.py b/camera.py index f79f7b29..55e6f6a1 100644 --- a/camera.py +++ b/camera.py @@ -11,7 +11,7 @@ from mobject import PMobject, VMobject class Camera(object): CONFIG = { - #background of a different shape will overwrite this + "background_image" : None, "pixel_shape" : (DEFAULT_HEIGHT, DEFAULT_WIDTH), #this will be resized to match pixel_shape "space_shape" : (SPACE_HEIGHT, SPACE_WIDTH), @@ -44,8 +44,13 @@ class Camera(object): self.space_shape = (space_height, space_width) def init_background(self): - if self.background is not None: - self.pixel_shape = self.background.shape[:2] + if self.background_image is not None: + path = get_full_image_path(self.background_image) + image = Image.open(path).convert('RGB') + height, width = self.pixel_shape + #TODO, how to gracefully handle backgrounds + #with different sizes? + self.background = np.array(image)[:height, :width] else: background_rgb = color_to_int_rgb(self.background_color) self.background = np.zeros( diff --git a/crypto.py b/crypto.py index b1c8e3ed..d5d7f204 100644 --- a/crypto.py +++ b/crypto.py @@ -20,6 +20,7 @@ from topics.three_dimensions import * from topics.objects import * from topics.probability import * from topics.complex_numbers import * +from topics.common_scenes import * from scene import Scene from scene.reconfigurable_scene import ReconfigurableScene from scene.zoomed_scene import * @@ -27,6 +28,13 @@ from camera import Camera from mobject.svg_mobject import * from mobject.tex_mobject import * +#force_skipping +#revert_to_original_skipping_status + +BITCOIN_COLOR = "#f7931a" + +################## + class AskQuestion(Scene): CONFIG = { "time_per_char" : 0.06, @@ -50,26 +58,352 @@ class AskQuestion(Scene): self.dither(self.time_per_char*n_spaces) self.dither(2) - - - - - - - - - - - - - - - - - - - - +class BitcoinPaperHighlightTitle(ExternallyAnimatedScene): + pass + +class TimeBitcoinCover(ExternallyAnimatedScene): + pass + +class ListOfAttributes(Scene): + def construct(self): + logo = BitcoinLogo() + + digital = TextMobject("Digital") + government, bank = buildings = [ + SVGMobject( + file_name = "%s_building"%word, + height = 2, + fill_color = LIGHT_GREY, + fill_opacity = 1, + stroke_width = 0, + ) + for word in "government", "bank" + ] + attributes = VGroup(digital, *buildings) + attributes.arrange_submobjects(RIGHT, buff = LARGE_BUFF) + for building in buildings: + building.cross = Cross(building) + building.cross.set_stroke(width = 12) + + self.play(DrawBorderThenFill(logo)) + self.play( + logo.to_corner, UP+LEFT, + Write(digital, run_time = 2) + ) + for building in buildings: + self.play(FadeIn(building)) + self.play(ShowCreation(building.cross)) + self.dither() + +class UnknownAuthor(Scene): + CONFIG = { + "camera_config" : { + "background_image" : "bitcoin_paper" + } + } + def construct(self): + rect = Rectangle(height = 0.4, width = 2.5) + rect.shift(2.45*UP) + question = TextMobject("Who is this?") + question.next_to(rect, RIGHT, buff = 1.5) + arrow = Arrow(question, rect, buff = SMALL_BUFF) + VGroup(question, arrow, rect).highlight(RED_D) + + self.play(ShowCreation(rect)) + self.play( + Write(question), + ShowCreation(arrow) + ) + self.dither() + +class NameCryptoCurrencies(TeacherStudentsScene): + def construct(self): + words = TextMobject("It's called a", "``cryptocurrency''") + words.highlight_by_tex("cryptocurrency", YELLOW) + self.teacher_says(words) + self.change_student_modes(*["pondering"]*3) + self.dither() + +class CryptocurrencyMarketCaps(ExternallyAnimatedScene): + pass + +class Hype(TeacherStudentsScene): + def construct(self): + self.teacher.change_mode("guilty") + phrases = map(TextMobject, [ + "I want some!", + "I'll get rich, right?", + "Buy them all!" + ]) + modes = ["hooray", "conniving", "surprised"] + for student, phrase, mode in zip(self.students, phrases, modes): + bubble = SpeechBubble() + bubble.set_fill(BLACK, 1) + bubble.add_content(phrase) + bubble.resize_to_content() + bubble.pin_to(student) + bubble.add(phrase) + self.play( + student.change_mode, mode, + FadeIn(bubble), + ) + self.dither(3) + +class AskQuestionCopy(AskQuestion): + pass + +class LedgerScene(PiCreatureScene): + CONFIG = { + "ledger_width" : 6, + "ledger_height" : 7, + "denomination" : "USD", + "ledger_line_height" : 0.4, + } + def setup(self): + PiCreatureScene.setup(self) + self.remove(self.pi_creatures) + + def get_ledger(self): + title = TextMobject("Ledger") + rect = Rectangle( + width = self.ledger_width, + height = self.ledger_height + ) + title.next_to(rect.get_top(), DOWN) + h_line = Line(rect.get_left(), rect.get_right()) + h_line.scale(0.8) + h_line.set_stroke(width = 2) + h_line.next_to(title, DOWN) + content = VGroup(h_line) + + self.ledger = VGroup(rect, title, h_line, content) + self.ledger.content = content + self.ledger.to_corner(UP+LEFT) + return self.ledger + + def add_line_to_ledger(self, string_or_mob): + if isinstance(string_or_mob, str): + mob = TextMobject(string_or_mob) + elif isinstance(string_or_mob, Mobject): + mob = string_or_mob + else: + raise Exception("Invalid input") + + mob.scale_to_fit_height(self.ledger_line_height) + mob.next_to( + self.ledger.content[-1], DOWN, + buff = MED_SMALL_BUFF, + aligned_edge = LEFT + ) + self.ledger.content.add(mob) + return mob + + def add_payment_line_to_ledger(self, from_name, to_name, amount): + amount_str = str(amount) + if self.denomination == "USD": + amount_str = "\\$" + amount_str + else: + amount_str += " " + self.denomination + line = TextMobject( + from_name.capitalize(), + "pays" if from_name.lower() is not "you" else "pay", + to_name.capitalize(), + amount_str + ) + for name in from_name, to_name: + if hasattr(self, name.lower()): + creature = getattr(self, name.lower()) + color = creature.get_color() + line.highlight_by_tex(name.capitalize(), color) + if self.denomination == "USD": + line.highlight_by_tex(amount_str, GREEN_D) + elif self.denomination == "BTC": + line.highlight_by_tex(amount_str, BITCOIN_COLOR) + + return self.add_line_to_ledger(line) + + def animate_payment_addition(self, *args, **kwargs): + line = self.add_payment_line_to_ledger(*args, **kwargs) + self.play(LaggedStart( + FadeIn, + VGroup(*line.family_members_with_points()), + run_time = 1 + )) + + def get_network(self): + creatures = self.pi_creatures + lines = VGroup(*[ + Line( + VGroup(pi1, pi1.label), VGroup(pi2, pi2.label), + buff = MED_SMALL_BUFF, + stroke_width = 2, + ) + for pi1, pi2 in it.combinations(creatures, 2) + ]) + labels = VGroup(*[pi.label for pi in creatures]) + + return VGroup(creatures, labels, lines) + + def create_pi_creatures(self): + creatures = VGroup(*[ + PiCreature(color = color, height = 1).shift(2*vect) + for color, vect in zip( + [BLUE_B, MAROON_D, GREY_BROWN, BLUE_E], + [UP+LEFT, UP+RIGHT, DOWN+LEFT, DOWN+RIGHT], + ) + ]) + creatures.to_edge(RIGHT) + names = ["alice", "bob", "charlie", "you"] + for name, creature in zip(names, creatures): + setattr(self, name, creature) + label = TextMobject(name.capitalize()) + label.scale(0.5) + label.next_to(creature, DOWN) + creature.label = label + if (creature.get_center() - creatures.get_center())[0] > 0: + creature.flip() + creature.look_at(creatures.get_center()) + + return creatures + + +class LayOutPlan(LedgerScene): + def construct(self): + self.ask_question() + self.show_ledger() + self.become_skeptical() + + def ask_question(self): + btc = BitcoinLogo() + group = VGroup(btc, TexMobject("= ???")) + group.arrange_submobjects(RIGHT) + + self.play( + DrawBorderThenFill(btc), + Write(group[1], run_time = 2) + ) + self.dither() + self.play( + group.scale, 0.7, + group.next_to, ORIGIN, RIGHT, + group.to_edge, UP + ) + + def show_ledger(self): + network = self.get_network() + ledger = self.get_ledger() + payments = [ + ("Alice", "Bob", 20), + ("Bob", "Charlie", 40), + ("Alice", "You", 50), + ] + + self.play(*map(FadeIn, [network, ledger])) + for payment in payments: + new_line = self.add_payment_line_to_ledger(*payment) + from_name, to_name, amount = payment + from_pi = getattr(self, from_name.lower()) + to_pi = getattr(self, to_name.lower()) + cash = TexMobject("\\$"*(amount/10)) + cash.scale(0.5) + cash.move_to(from_pi) + cash.highlight(GREEN) + + self.play( + cash.move_to, to_pi, + to_pi.change_mode, "hooray" + ) + self.play( + FadeOut(cash), + Write(new_line, run_time = 1) + ) + self.dither() + + def become_skeptical(self): + creatures = self.pi_creatures + + self.play(*[ + ApplyMethod(pi.change_mode, "sassy") + for pi in creatures + ]) + for k in range(3): + self.play(*[ + ApplyMethod( + creatures[i].look_at, + creatures[k*(i+1)%4] + ) + for i in range(4) + ]) + self.dither(2) + +class UnderlyingSystemVsUserFacing(Scene): + def construct(self): + underlying = TextMobject("Underlying \\\\ system") + underlying.shift(DOWN).to_edge(LEFT) + user_facing = TextMobject("User-facing") + user_facing.next_to(underlying, UP, LARGE_BUFF, LEFT) + + protocol = TextMobject("Bitcoin protocol") + protocol.next_to(underlying, RIGHT, MED_LARGE_BUFF) + protocol.highlight(BITCOIN_COLOR) + banking = TextMobject("Banking system") + banking.next_to(protocol, RIGHT, MED_LARGE_BUFF) + banking.highlight(GREEN) + + phone = SVGMobject( + file_name = "phone", + fill_color = WHITE, + fill_opacity = 1, + height = 1, + stroke_width = 0, + ) + phone.next_to(protocol, UP, LARGE_BUFF) + card = SVGMobject( + file_name = "credit_card", + fill_color = LIGHT_GREY, + fill_opacity = 1, + stroke_width = 0, + height = 1 + ) + card.next_to(banking, UP, LARGE_BUFF) + + btc = BitcoinLogo() + btc.next_to(phone, UP, MED_LARGE_BUFF) + dollar = TexMobject("\\$") + dollar.scale_to_fit_height(1) + dollar.highlight(GREEN) + dollar.next_to(card, UP, MED_LARGE_BUFF) + card.save_state() + card.shift(2*RIGHT) + card.set_fill(opacity = 0) + + + h_line = Line(underlying.get_left(), banking.get_right()) + h_line.next_to(underlying, DOWN, MED_SMALL_BUFF, LEFT) + h_line2 = h_line.copy() + h_line2.next_to(user_facing, DOWN, MED_LARGE_BUFF, LEFT) + h_line3 = h_line.copy() + h_line3.next_to(user_facing, UP, MED_LARGE_BUFF, LEFT) + v_line = Line(5*UP, ORIGIN) + v_line.next_to(underlying, RIGHT, MED_SMALL_BUFF) + v_line.shift(1.7*UP) + v_line2 = v_line.copy() + v_line2.next_to(protocol, RIGHT, MED_SMALL_BUFF) + v_line2.shift(1.7*UP) + + self.add(h_line, h_line2, h_line3, v_line, v_line2) + self.add(underlying, user_facing, btc) + self.play(Write(protocol)) + self.dither(2) + self.play( + card.restore, + Write(dollar) + ) + self.play(Write(banking)) + self.dither(2) + self.play(DrawBorderThenFill(phone)) + self.dither(2) + diff --git a/helpers.py b/helpers.py index 6832fbde..5b4fd10c 100644 --- a/helpers.py +++ b/helpers.py @@ -394,6 +394,20 @@ def cammel_case_initials(name): ################################################ +def get_full_image_path(image_file_name): + possible_paths = [ + image_file_name, + os.path.join(IMAGE_DIR, image_file_name), + os.path.join(IMAGE_DIR, image_file_name + ".jpg"), + os.path.join(IMAGE_DIR, image_file_name + ".png"), + os.path.join(IMAGE_DIR, image_file_name + ".gif"), + ] + for path in possible_paths: + if os.path.exists(path): + return path + raise IOError("File not Found") + + def drag_pixels(frames): curr = frames[0] new_frames = [] diff --git a/mobject/image_mobject.py b/mobject/image_mobject.py index 22b0eda7..25082217 100644 --- a/mobject/image_mobject.py +++ b/mobject/image_mobject.py @@ -26,21 +26,11 @@ class ImageMobject(PMobject): self.name = to_camel_case( os.path.split(image_file)[-1].split(".")[0] ) - possible_paths = [ - image_file, - os.path.join(IMAGE_DIR, image_file), - os.path.join(IMAGE_DIR, image_file + ".jpg"), - os.path.join(IMAGE_DIR, image_file + ".png"), - os.path.join(IMAGE_DIR, image_file + ".gif"), - ] - for path in possible_paths: - if os.path.exists(path): - self.generate_points_from_file(path) - self.scale(self.scale_factorue) - if self.should_center: - self.center() - return - raise IOError("File not Found") + path = get_full_image_path(image_file) + self.generate_points_from_file(path) + self.scale(self.scale_factorue) + if self.should_center: + self.center() def generate_points_from_file(self, path): if self.use_cache and self.read_in_cached_attrs(path): diff --git a/mobject/svg_mobject.py b/mobject/svg_mobject.py index 9bc77615..735779a7 100644 --- a/mobject/svg_mobject.py +++ b/mobject/svg_mobject.py @@ -16,8 +16,9 @@ def string_to_numbers(num_string): class SVGMobject(VMobject): CONFIG = { - "initial_scale_factor" : 1, "should_center" : True, + "height" : 2, + "width" : None, #Must be filled in in a subclass, or when called "file_name" : None, "propogate_style_to_family" : True, @@ -175,7 +176,11 @@ class SVGMobject(VMobject): def move_into_position(self): if self.should_center: self.center() - self.scale_in_place(self.initial_scale_factor) + if self.height is not None: + self.scale_to_fit_height(self.height) + if self.width is not None: + self.scale_to_fit_width(self.width) + class VMobjectFromSVGPathstring(VMobject): diff --git a/mobject/tex_mobject.py b/mobject/tex_mobject.py index 0bd74c3f..4f5ed3e1 100644 --- a/mobject/tex_mobject.py +++ b/mobject/tex_mobject.py @@ -7,9 +7,7 @@ from topics.geometry import BackgroundRectangle import collections import sys -TEX_MOB_SCALE_FACTOR = 0.05 -TEXT_MOB_SCALE_FACTOR = 0.05 - +TEX_MOB_SCALE_FACTOR = 0.04 class TexSymbol(VMobjectFromSVGPathstring): def pointwise_become_partial(self, mobject, a, b): @@ -37,7 +35,7 @@ class TexMobject(SVGMobject): "fill_color" : WHITE, "should_center" : True, "arg_separator" : " ", - "initial_scale_factor" : TEX_MOB_SCALE_FACTOR, + "height" : None, "organize_left_to_right" : False, "propogate_style_to_family" : True, "alignment" : "", @@ -55,6 +53,7 @@ class TexMobject(SVGMobject): self.template_tex_file ) SVGMobject.__init__(self, file_name = file_name, **kwargs) + self.scale(TEX_MOB_SCALE_FACTOR) if self.organize_left_to_right: self.organize_submobjects_left_to_right() @@ -189,7 +188,6 @@ class TexMobject(SVGMobject): class TextMobject(TexMobject): CONFIG = { "template_tex_file" : TEMPLATE_TEXT_FILE, - "initial_scale_factor" : TEXT_MOB_SCALE_FACTOR, "alignment" : "\\centering", } diff --git a/topics/characters.py b/topics/characters.py index 1ddd75ee..9cee8053 100644 --- a/topics/characters.py +++ b/topics/characters.py @@ -31,7 +31,7 @@ class PiCreature(SVGMobject): "stroke_color" : BLACK, "fill_opacity" : 1.0, "propogate_style_to_family" : True, - "initial_scale_factor" : 0.01, + "height" : 3, "corner_scale_factor" : 0.75, "flip_at_start" : False, "is_looking_direction_purposeful" : False, diff --git a/topics/common_scenes.py b/topics/common_scenes.py index 36f55e34..82100390 100644 --- a/topics/common_scenes.py +++ b/topics/common_scenes.py @@ -146,7 +146,9 @@ class PatreonThanks(Scene): self.play(Blink(morty)) last_group = group - +class ExternallyAnimatedScene(Scene): + def construct(self): + raise Exception("Don't actually run this class.") diff --git a/topics/objects.py b/topics/objects.py index 2de7aa1b..9c75b315 100644 --- a/topics/objects.py +++ b/topics/objects.py @@ -12,6 +12,20 @@ from topics.geometry import Circle, Line, Rectangle, Square, \ Arc, Polygon, SurroundingRectangle from topics.three_dimensions import Cube +class BitcoinLogo(SVGMobject): + CONFIG = { + "file_name" : "Bitcoin_logo", + "height" : 1, + "fill_color" : "#f7931a", + "inner_color" : WHITE, + "fill_opacity" : 1, + "stroke_width" : 0, + } + def __init__(self, **kwargs): + SVGMobject.__init__(self, **kwargs) + self[0].set_fill(self.fill_color, self.fill_opacity) + self[1].set_fill(self.inner_color, 1) + class Guitar(SVGMobject): CONFIG = { "file_name" : "guitar", @@ -21,9 +35,6 @@ class Guitar(SVGMobject): "stroke_color" : WHITE, "stroke_width" : 0.5, } - def __init__(self, **kwargs): - SVGMobject.__init__(self, **kwargs) - self.scale_to_fit_height(self.height) class SunGlasses(SVGMobject): CONFIG = {