import random import string from manimlib.animation.animation import Animation from manimlib.animation.composition import LaggedStart from manimlib.animation.creation import DrawBorderThenFill from manimlib.animation.creation import FadeIn from manimlib.animation.creation import FadeOut from manimlib.animation.creation import Write from manimlib.constants import * from manimlib.continual_animation.continual_animation import ContinualMovement from manimlib.for_3b1b_videos.pi_creature import Mortimer from manimlib.for_3b1b_videos.pi_creature import Randolph from manimlib.for_3b1b_videos.pi_creature_animations import Blink from manimlib.for_3b1b_videos.pi_creature_scene import PiCreatureScene from manimlib.mobject.geometry import DashedLine from manimlib.mobject.geometry import Line from manimlib.mobject.geometry import Rectangle from manimlib.mobject.geometry import Square from manimlib.mobject.svg.drawings import Logo from manimlib.mobject.svg.drawings import PatreonLogo from manimlib.mobject.svg.tex_mobject import TextMobject from manimlib.mobject.types.vectorized_mobject import VGroup from manimlib.scene.moving_camera_scene import MovingCameraScene from manimlib.scene.scene import Scene from manimlib.utils.space_ops import get_norm from manimlib.utils.space_ops import normalize class OpeningQuote(Scene): CONFIG = { "quote": [], "quote_arg_separator": " ", "highlighted_quote_terms": {}, "author": "", "fade_in_kwargs": { "submobject_mode": "lagged_start", "rate_func": None, "lag_factor": 4, "run_time": 5, }, "text_size": "\\Large", "use_quotation_marks": True, "top_buff": 1.0, "author_buff": 1.0, } def construct(self): self.quote = self.get_quote() self.author = self.get_author(self.quote) self.play(FadeIn(self.quote, **self.fade_in_kwargs)) self.wait(2) self.play(Write(self.author, run_time=3)) self.wait() def get_quote(self, max_width=FRAME_WIDTH - 1): text_mobject_kwargs = { "alignment": "", "arg_separator": self.quote_arg_separator, } if isinstance(self.quote, str): if self.use_quotation_marks: quote = TextMobject("``%s''" % self.quote.strip(), **text_mobject_kwargs) else: quote = TextMobject("%s" % self.quote.strip(), **text_mobject_kwargs) else: if self.use_quotation_marks: words = [self.text_size + " ``"] + list(self.quote) + ["''"] else: words = [self.text_size] + list(self.quote) quote = TextMobject(*words, **text_mobject_kwargs) # TODO, make less hacky if self.quote_arg_separator == " ": quote[0].shift(0.2 * RIGHT) quote[-1].shift(0.2 * LEFT) for term, color in self.highlighted_quote_terms: quote.set_color_by_tex(term, color) quote.to_edge(UP, buff=self.top_buff) if quote.get_width() > max_width: quote.set_width(max_width) return quote def get_author(self, quote): author = TextMobject(self.text_size + " --" + self.author) author.next_to(quote, DOWN, buff=self.author_buff) author.set_color(YELLOW) return author class PatreonThanks(Scene): CONFIG = { "specific_patrons": [], "max_patron_group_size": 20, "patron_scale_val": 0.8, } def construct(self): morty = Mortimer() morty.next_to(ORIGIN, DOWN) patreon_logo = PatreonLogo() patreon_logo.to_edge(UP) patrons = list(map(TextMobject, self.specific_patrons)) num_groups = float(len(patrons)) / self.max_patron_group_size proportion_range = np.linspace(0, 1, num_groups + 1) indices = (len(patrons) * proportion_range).astype('int') patron_groups = [ VGroup(*patrons[i:j]) for i, j in zip(indices, indices[1:]) ] for i, group in enumerate(patron_groups): left_group = VGroup(*group[:len(group) / 2]) right_group = VGroup(*group[len(group) / 2:]) for subgroup, vect in (left_group, LEFT), (right_group, RIGHT): subgroup.arrange_submobjects(DOWN, aligned_edge=LEFT) subgroup.scale(self.patron_scale_val) subgroup.to_edge(vect) last_group = None for i, group in enumerate(patron_groups): anims = [] if last_group is not None: self.play( FadeOut(last_group), morty.look, UP + LEFT ) else: anims += [ DrawBorderThenFill(patreon_logo), ] self.play( LaggedStart( FadeIn, group, run_time=2, ), morty.change, "gracious", group.get_corner(UP + LEFT), *anims ) self.play(morty.look_at, group.get_corner(DOWN + LEFT)) self.play(morty.look_at, group.get_corner(UP + RIGHT)) self.play(morty.look_at, group.get_corner(DOWN + RIGHT)) self.play(Blink(morty)) last_group = group class PatreonEndScreen(PatreonThanks, PiCreatureScene): CONFIG = { "n_patron_columns": 3, "max_patron_width": 3.5, "run_time": 20, "randomize_order": True, "capitalize": True, "name_y_spacing": 0.7, "thanks_words": "Funded by the community, with special thanks to:", } def construct(self): if self.randomize_order: random.shuffle(self.specific_patrons) if self.capitalize: self.specific_patrons = [ " ".join(map( lambda s: s.capitalize(), patron.split(" ") )) for patron in self.specific_patrons ] # self.add_title() self.scroll_through_patrons() def create_pi_creatures(self): title = self.title = TextMobject("Clicky Stuffs") title.scale(1.5) title.to_edge(UP, buff=MED_SMALL_BUFF) randy, morty = self.pi_creatures = VGroup(Randolph(), Mortimer()) for pi, vect in (randy, LEFT), (morty, RIGHT): pi.set_height(title.get_height()) pi.change_mode("thinking") pi.look(DOWN) pi.next_to(title, vect, buff=MED_LARGE_BUFF) self.add_foreground_mobjects(title, randy, morty) return self.pi_creatures def scroll_through_patrons(self): logo_box = Square(side_length=2.5) logo_box.to_corner(DOWN + LEFT, buff=MED_LARGE_BUFF) total_width = FRAME_X_RADIUS - logo_box.get_right()[0] black_rect = Rectangle( fill_color=BLACK, fill_opacity=1, stroke_width=3, stroke_color=BLACK, width=FRAME_WIDTH, height=0.6 * FRAME_HEIGHT, ) black_rect.to_edge(UP, buff=0) line = DashedLine(FRAME_X_RADIUS * LEFT, FRAME_X_RADIUS * RIGHT) line.move_to(ORIGIN) thanks = TextMobject(self.thanks_words) thanks.scale(0.9) thanks.next_to(black_rect.get_bottom(), UP, SMALL_BUFF) thanks.set_color(YELLOW) underline = Line(LEFT, RIGHT) underline.match_width(thanks) underline.scale(1.1) underline.next_to(thanks, DOWN, SMALL_BUFF) thanks.add(underline) patrons = VGroup(*list(map(TextMobject, self.specific_patrons))) patrons.scale(self.patron_scale_val) for patron in patrons: if patron.get_width() > self.max_patron_width: patron.set_width(self.max_patron_width) columns = VGroup(*[ VGroup(*patrons[i::self.n_patron_columns]) for i in range(self.n_patron_columns) ]) for column in columns: for n, name in enumerate(column): name.shift(n * self.name_y_spacing * DOWN) columns.arrange_submobjects( RIGHT, buff=LARGE_BUFF, aligned_edge=UP, ) if columns.get_width() > self.max_patron_width: columns.set_width(total_width - 1) thanks.to_edge(RIGHT) columns.next_to(thanks, DOWN, 3 * LARGE_BUFF) columns.generate_target() columns.target.move_to(2 * DOWN, DOWN) columns.target.align_to( thanks, alignment_vect=RIGHT ) vect = columns.target.get_center() - columns.get_center() distance = get_norm(vect) wait_time = 20 columns_shift = ContinualMovement( columns, direction=normalize(vect), rate=(distance / wait_time) ) self.add(columns_shift, black_rect, line, thanks) self.wait(wait_time) class LogoGenerationTemplate(MovingCameraScene): def setup(self): MovingCameraScene.setup(self) frame = self.camera_frame frame.shift(DOWN) self.logo = Logo() name = TextMobject("3Blue1Brown") name.scale(2.5) name.next_to(self.logo, DOWN, buff=MED_LARGE_BUFF) name.set_sheen(-0.2, DR) self.channel_name = name def construct(self): logo = self.logo name = self.channel_name self.play( Write(name, run_time=3, lag_factor=2.5), *self.get_logo_animations(logo) ) self.wait() def get_logo_animations(self, logo): return [] # For subclasses class ExternallyAnimatedScene(Scene): def construct(self): raise Exception("Don't actually run this class.") class TODOStub(Scene): CONFIG = { "message": "" } def construct(self): self.add(TextMobject("TODO: %s" % self.message)) self.wait()