From d6fba3576c468aa0fe2a2b26b448ec6cae22a68c Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sat, 5 May 2018 19:41:08 -0700 Subject: [PATCH 01/12] Refactor of TexMobject to separate out SingleStringTexMobject. Should improve TexMobject transform animations --- mobject/svg/tex_mobject.py | 137 +++++++++++++++++-------------------- 1 file changed, 64 insertions(+), 73 deletions(-) diff --git a/mobject/svg/tex_mobject.py b/mobject/svg/tex_mobject.py index cbb57a72..7016e1a7 100644 --- a/mobject/svg/tex_mobject.py +++ b/mobject/svg/tex_mobject.py @@ -10,8 +10,10 @@ from mobject.types.vectorized_mobject import VectorizedPoint import operator as op -TEX_MOB_SCALE_FACTOR = 0.05 +# TODO list +# - Make sure if "color" is passed into TexMobject, it behaves as expected +TEX_MOB_SCALE_FACTOR = 0.05 class TexSymbol(VMobjectFromSVGPathstring): def pointwise_become_partial(self, mobject, a, b): @@ -31,57 +33,37 @@ class TexSymbol(VMobjectFromSVGPathstring): self.set_fill(opacity=opacity) -class TexMobject(SVGMobject): +class SingleStringTexMobject(SVGMobject): CONFIG = { "template_tex_file": TEMPLATE_TEX_FILE, "stroke_width": 0, "fill_opacity": 1.0, "fill_color": WHITE, "should_center": True, - "arg_separator": " ", "height": None, "organize_left_to_right": False, "propagate_style_to_family": True, "alignment": "", } - def __init__(self, *args, **kwargs): - digest_config(self, kwargs, locals()) - - if "color" in kwargs.keys() and "fill_color" not in kwargs.keys(): - self.fill_color = kwargs["color"] - - # TODO, Eventually remove this - if len(args) == 1 and isinstance(args[0], list): - self.args = args[0] - ## - assert(all([isinstance(a, str) for a in self.args])) - self.tex_string = self.get_modified_expression() + def __init__(self, tex_string, **kwargs): + digest_config(self, kwargs) + assert(isinstance(tex_string, str)) + self.tex_string = self.get_modified_expression(tex_string) file_name = tex_to_svg_file( self.tex_string, self.template_tex_file ) SVGMobject.__init__(self, file_name=file_name, **kwargs) - self.scale(TEX_MOB_SCALE_FACTOR) + if self.height is None: + self.scale(TEX_MOB_SCALE_FACTOR) if self.organize_left_to_right: self.organize_submobjects_left_to_right() - def path_string_to_mobject(self, path_string): - # Overwrite superclass default to use - # specialized path_string mobject - return TexSymbol(path_string) - - def generate_points(self): - SVGMobject.generate_points(self) - if len(self.args) > 1: - self.handle_multiple_args() - - def get_modified_expression(self): - result = self.arg_separator.join(self.args) - result = " ".join([self.alignment, result]) + def get_modified_expression(self, tex_string): + result = self.alignment + " " + tex_string result = result.strip() result = self.modify_special_strings(result) - return result def modify_special_strings(self, tex): @@ -123,27 +105,64 @@ class TexMobject(SVGMobject): def get_tex_string(self): return self.tex_string - def handle_multiple_args(self): + def path_string_to_mobject(self, path_string): + # Overwrite superclass default to use + # specialized path_string mobject + return TexSymbol(path_string) + + def organize_submobjects_left_to_right(self): + self.sort_submobjects(lambda p: p[0]) + return self + + def add_background_rectangle(self, color=BLACK, opacity=0.75, **kwargs): + self.background_rectangle = BackgroundRectangle( + self, color=color, + fill_opacity=opacity, + **kwargs + ) + letters = VMobject(*self.submobjects) + self.submobjects = [self.background_rectangle, letters] + return self + + +class TexMobject(SingleStringTexMobject): + CONFIG = { + "arg_separator": " ", + } + + def __init__(self, *tex_strings, **kwargs): + digest_config(self, kwargs) + self.tex_strings = tex_strings + SingleStringTexMobject.__init__( + self, self.arg_separator.join(tex_strings), **kwargs + ) + self.break_up_by_substrings() + + if self.organize_left_to_right: + self.organize_submobjects_left_to_right() + + def break_up_by_substrings(self): """ Reorganize existing submojects one layer - deeper based on the structure of args (as a list of strings) + deeper based on the structure of tex_strings (as a list + of tex_strings) """ new_submobjects = [] curr_index = 0 - self.expression_parts = list(self.args) - for expr in self.args: - sub_tex_mob = TexMobject(expr, **self.CONFIG) - sub_tex_mob.tex_string = expr # Want it unmodified + # TODO, remove this attribute + self.expression_parts = list(self.tex_strings) + for expr in self.tex_strings: + sub_tex_mob = SingleStringTexMobject(expr, **self.CONFIG) + sub_tex_mob.tex_string = expr # We want it unmodified num_submobs = len(sub_tex_mob.submobjects) new_index = curr_index + num_submobs if num_submobs == 0: - if len(self) > curr_index: - last_submob_index = curr_index - else: - last_submob_index = -1 - sub_tex_mob.submobjects = [VectorizedPoint( - self.submobjects[last_submob_index].get_right() - )] + # For cases like empty tex_strings, we want the corresponing + # part of the whole TexMobject to be a VectorizedPoint + # positioned in the right part of the TexMobject + sub_tex_mob.submobjects = [VectorizedPoint()] + last_submob_index = min(curr_index, len(self.submobjects) - 1) + sub_tex_mob.move_to(self.submobjects[last_submob_index], RIGHT) else: sub_tex_mob.submobjects = self.submobjects[curr_index:new_index] new_submobjects.append(sub_tex_mob) @@ -161,15 +180,9 @@ class TexMobject(SVGMobject): else: return tex1 == tex2 - tex_submobjects = filter( - lambda m: isinstance(m, TexMobject), - self.submobject_family() - ) - if hasattr(self, "expression_parts"): - tex_submobjects.remove(self) return VGroup(*filter( lambda m: test(tex, m.get_tex_string()), - tex_submobjects + self.submobjects )) def get_part_by_tex(self, tex, **kwargs): @@ -185,7 +198,7 @@ class TexMobject(SVGMobject): def set_color_by_tex_to_color_map(self, texs_to_color_map, **kwargs): for texs, color in texs_to_color_map.items(): try: - # If the given key behaves like strings + # If the given key behaves like tex_strings texs + '' self.set_color_by_tex(texs, color, **kwargs) except TypeError: @@ -204,28 +217,6 @@ class TexMobject(SVGMobject): part = self.get_part_by_tex(tex, **kwargs) return self.index_of_part(part) - def organize_submobjects_left_to_right(self): - self.sort_submobjects(lambda p: p[0]) - return self - - def sort_submobjects_alphabetically(self): - def alphabetical_cmp(m1, m2): - if not all([isinstance(m, TexMobject) for m in m1, m2]): - return 0 - return cmp(m1.get_tex_string(), m2.get_tex_string()) - self.submobjects.sort(alphabetical_cmp) - return self - - def add_background_rectangle(self, color=BLACK, opacity=0.75, **kwargs): - self.background_rectangle = BackgroundRectangle( - self, color=color, - fill_opacity=opacity, - **kwargs - ) - letters = VMobject(*self.submobjects) - self.submobjects = [self.background_rectangle, letters] - return self - def add_background_rectangle_to_parts(self): for part in self: part.add_background_rectangle() From ce57e342b13b707ced425c13164be3691b42e501 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sat, 5 May 2018 19:41:30 -0700 Subject: [PATCH 02/12] Added break_up_string_by_terms --- utils/strings.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/utils/strings.py b/utils/strings.py index fdadf99b..cc4307ac 100644 --- a/utils/strings.py +++ b/utils/strings.py @@ -1,5 +1,6 @@ import re import string +import itertools as it def to_camel_case(name): @@ -24,3 +25,27 @@ def camel_case_initials(name): def complex_string(complex_num): return filter(lambda c: c not in "()", str(complex_num)) + + +def break_up_string_by_terms(full_string, *terms): + """ + Given a string, and an arbitrary number of possible substrings, returns a list + of strings which would concatenate to make the full string, and in which + these special substrings appear as their own elements. + + For example, break_up_string_by_terms("to be or not to be", "to", "be") would + return ["to", " ", "be", " or not ", "to", " ", "be"] + """ + if len(terms) == 0: + return [full_string] + term = terms[0] + substrings = list(it.chain(*zip( + full_string.split(term), + it.repeat(term) + ))) + substrings.pop(-1) + substrings = filter(lambda s: s != "", substrings) + return list(it.chain(*[ + break_up_string_by_terms(substring, *terms[1:]) + for substring in substrings + ])) From 9bcd8de86539bf1e6790804b4fca0e71314a0cb7 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sat, 5 May 2018 19:49:25 -0700 Subject: [PATCH 03/12] Small cleanup on TexMobject --- mobject/svg/tex_mobject.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/mobject/svg/tex_mobject.py b/mobject/svg/tex_mobject.py index 7016e1a7..db62acab 100644 --- a/mobject/svg/tex_mobject.py +++ b/mobject/svg/tex_mobject.py @@ -49,9 +49,9 @@ class SingleStringTexMobject(SVGMobject): def __init__(self, tex_string, **kwargs): digest_config(self, kwargs) assert(isinstance(tex_string, str)) - self.tex_string = self.get_modified_expression(tex_string) + self.tex_string = tex_string file_name = tex_to_svg_file( - self.tex_string, + self.get_modified_expression(tex_string), self.template_tex_file ) SVGMobject.__init__(self, file_name=file_name, **kwargs) @@ -149,11 +149,8 @@ class TexMobject(SingleStringTexMobject): """ new_submobjects = [] curr_index = 0 - # TODO, remove this attribute - self.expression_parts = list(self.tex_strings) - for expr in self.tex_strings: - sub_tex_mob = SingleStringTexMobject(expr, **self.CONFIG) - sub_tex_mob.tex_string = expr # We want it unmodified + for tex_string in self.tex_strings: + sub_tex_mob = SingleStringTexMobject(tex_string, **self.CONFIG) num_submobs = len(sub_tex_mob.submobjects) new_index = curr_index + num_submobs if num_submobs == 0: From e8a74eafa0b32d5a45aa82f03c107bc20970ccda Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sat, 5 May 2018 19:49:54 -0700 Subject: [PATCH 04/12] Changed defaults on PiCreatureScene --- for_3b1b_videos/pi_creature_scene.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/for_3b1b_videos/pi_creature_scene.py b/for_3b1b_videos/pi_creature_scene.py index c9ce461e..b9ddaac0 100644 --- a/for_3b1b_videos/pi_creature_scene.py +++ b/for_3b1b_videos/pi_creature_scene.py @@ -32,10 +32,10 @@ class PiCreatureScene(Scene): "seconds_to_blink": 3, "pi_creatures_start_on_screen": True, "default_pi_creature_kwargs": { - "color": GREY_BROWN, - "flip_at_start": True, + "color": BLUE, + "flip_at_start": False, }, - "default_pi_creature_start_corner": DOWN + LEFT, + "default_pi_creature_start_corner": DL, } def setup(self): From dc6c47f13c10fb17527d4e0cffeb054301c89173 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sat, 5 May 2018 19:50:05 -0700 Subject: [PATCH 05/12] Beginning alt_calc project --- active_projects/alt_calc.py | 73 +++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 active_projects/alt_calc.py diff --git a/active_projects/alt_calc.py b/active_projects/alt_calc.py new file mode 100644 index 00000000..3458b49a --- /dev/null +++ b/active_projects/alt_calc.py @@ -0,0 +1,73 @@ +from big_ol_pile_of_manim_imports import * + + +class NumberlineTransformationScene(Scene): + CONFIG = { + + } + + def setup(self): + pass + + +class ExampleNumberlineTransformationScene(NumberlineTransformationScene): + def construct(self): + pass + +# Scenes + + +class WriteOpeningWords(Scene): + def construct(self): + raw_string1 = "Dear calculus student," + raw_string2 = "You're about to go through your first course. Like " + \ + "any new topic, it will take some hard work to understand," + words1, words2 = [ + TextMobject("\\Large", *rs.split(" ")) + for rs in raw_string1, raw_string2 + ] + words1.next_to(words2, UP, aligned_edge=LEFT, buff=LARGE_BUFF) + words = VGroup(*it.chain(words1, words2)) + words.scale_to_fit_width(FRAME_WIDTH - 2 * LARGE_BUFF) + words.to_edge(UP) + + letter_wait = 0.05 + word_wait = 2 * letter_wait + comma_wait = 5 * letter_wait + for word in words: + self.play(LaggedStart( + FadeIn, word, + run_time=len(word) * letter_wait, + lag_ratio=1.5 / len(word) + )) + self.wait(word_wait) + if word.get_tex_string()[-1] == ",": + self.wait(comma_wait) + + +class StartingCalc101(PiCreatureScene): + CONFIG = { + # "default_pi_creature_kwargs": { + # "color": BLUE, + # "flip_at_start": False, + # }, + } + + def construct(self): + randy = self.pi_creature + deriv_string = "\\frac{df}{dx}(x) = \\lim(\\delta x \\to \\infty)" + \ + "{f(x + \\delta x) - f(x) \\over \\delta x}" + equations = VGroup( + TexMobject(*break_up_string_by_terms(deriv_string, "\\delta x")) + ) + title = TextMobject("Calculus 101") + title.to_edge(UP) + h_line = Line(LEFT, RIGHT) + h_line.scale_to_fit_width(FRAME_WIDTH - LARGE_BUFF) + h_line.next_to(title, DOWN) + + self.add(title, h_line) + self.play(randy.change, "erm", title) + self.wait() + + From fb5f1517a95225f60c10bea9c804542a8bf46ed8 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sat, 5 May 2018 20:16:20 -0700 Subject: [PATCH 06/12] Added substrings_to_isolate to TexMobject configuration --- mobject/svg/tex_mobject.py | 12 ++++++++++++ utils/strings.py | 32 +++++++++++++++++++++----------- 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/mobject/svg/tex_mobject.py b/mobject/svg/tex_mobject.py index db62acab..52da3c0e 100644 --- a/mobject/svg/tex_mobject.py +++ b/mobject/svg/tex_mobject.py @@ -4,6 +4,7 @@ from svg_mobject import SVGMobject from svg_mobject import VMobjectFromSVGPathstring from mobject.shape_matchers import BackgroundRectangle from utils.config_ops import digest_config +from utils.strings import split_string_list_to_isolate_substring from mobject.types.vectorized_mobject import VGroup from mobject.types.vectorized_mobject import VMobject from mobject.types.vectorized_mobject import VectorizedPoint @@ -15,6 +16,7 @@ import operator as op TEX_MOB_SCALE_FACTOR = 0.05 + class TexSymbol(VMobjectFromSVGPathstring): def pointwise_become_partial(self, mobject, a, b): # TODO, this assumes a = 0 @@ -128,10 +130,12 @@ class SingleStringTexMobject(SVGMobject): class TexMobject(SingleStringTexMobject): CONFIG = { "arg_separator": " ", + "substrings_to_isolate": [], } def __init__(self, *tex_strings, **kwargs): digest_config(self, kwargs) + tex_strings = self.break_up_tex_strings(tex_strings) self.tex_strings = tex_strings SingleStringTexMobject.__init__( self, self.arg_separator.join(tex_strings), **kwargs @@ -141,6 +145,14 @@ class TexMobject(SingleStringTexMobject): if self.organize_left_to_right: self.organize_submobjects_left_to_right() + def break_up_tex_strings(self, tex_strings): + split_list = split_string_list_to_isolate_substring( + tex_strings, *self.substrings_to_isolate + ) + split_list = map(str.strip, split_list) + split_list = filter(lambda s: s != '', split_list) + return split_list + def break_up_by_substrings(self): """ Reorganize existing submojects one layer diff --git a/utils/strings.py b/utils/strings.py index cc4307ac..67ed144c 100644 --- a/utils/strings.py +++ b/utils/strings.py @@ -27,25 +27,35 @@ def complex_string(complex_num): return filter(lambda c: c not in "()", str(complex_num)) -def break_up_string_by_terms(full_string, *terms): +def split_string_to_isolate_substrings(full_string, *substrings_to_isolate): """ Given a string, and an arbitrary number of possible substrings, returns a list of strings which would concatenate to make the full string, and in which these special substrings appear as their own elements. - For example, break_up_string_by_terms("to be or not to be", "to", "be") would + For example, split_string_to_isolate_substrings("to be or not to be", "to", "be") would return ["to", " ", "be", " or not ", "to", " ", "be"] """ - if len(terms) == 0: + if len(substrings_to_isolate) == 0: return [full_string] - term = terms[0] - substrings = list(it.chain(*zip( - full_string.split(term), - it.repeat(term) + substring_to_isolate = substrings_to_isolate[0] + all_substrings = list(it.chain(*zip( + full_string.split(substring_to_isolate), + it.repeat(substring_to_isolate) ))) - substrings.pop(-1) - substrings = filter(lambda s: s != "", substrings) + all_substrings.pop(-1) + all_substrings = filter(lambda s: s != "", all_substrings) + return split_string_list_to_isolate_substring( + all_substrings, *substrings_to_isolate[1:] + ) + + +def split_string_list_to_isolate_substring(string_list, *substrings_to_isolate): + """ + Similar to split_string_to_isolate_substrings, but the first argument + is a list of strings, thought of as something already broken up a bit. + """ return list(it.chain(*[ - break_up_string_by_terms(substring, *terms[1:]) - for substring in substrings + split_string_to_isolate_substrings(s, *substrings_to_isolate) + for s in string_list ])) From c2a67346f9a1b18f6304c47ae25d8ca470432fa5 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sat, 5 May 2018 20:19:33 -0700 Subject: [PATCH 07/12] Added tex_to_color_map abilty to TexMobject config --- mobject/svg/tex_mobject.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/mobject/svg/tex_mobject.py b/mobject/svg/tex_mobject.py index 52da3c0e..16eebe7f 100644 --- a/mobject/svg/tex_mobject.py +++ b/mobject/svg/tex_mobject.py @@ -131,6 +131,7 @@ class TexMobject(SingleStringTexMobject): CONFIG = { "arg_separator": " ", "substrings_to_isolate": [], + "tex_to_color_map": {}, } def __init__(self, *tex_strings, **kwargs): @@ -141,13 +142,18 @@ class TexMobject(SingleStringTexMobject): self, self.arg_separator.join(tex_strings), **kwargs ) self.break_up_by_substrings() + self.set_color_by_tex_to_color_map(self.tex_to_color_map) if self.organize_left_to_right: self.organize_submobjects_left_to_right() def break_up_tex_strings(self, tex_strings): + substrings_to_isolate = op.add( + self.substrings_to_isolate, + self.tex_to_color_map.keys() + ) split_list = split_string_list_to_isolate_substring( - tex_strings, *self.substrings_to_isolate + tex_strings, *substrings_to_isolate ) split_list = map(str.strip, split_list) split_list = filter(lambda s: s != '', split_list) From 4403399050d66962c8674aa1ad31ff6b62b578fd Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 7 May 2018 13:32:47 -0700 Subject: [PATCH 08/12] Made add_background_rectangle are more general method for all mobjects --- active_projects/eola2/cramer.py | 2 +- mobject/matrix.py | 4 ---- mobject/mobject.py | 27 ++++++++++++++++++++++++++- mobject/numbers.py | 14 +------------- mobject/svg/tex_mobject.py | 21 +++++++-------------- old_projects/wallis.py | 2 +- 6 files changed, 36 insertions(+), 34 deletions(-) diff --git a/active_projects/eola2/cramer.py b/active_projects/eola2/cramer.py index 7dd9d951..69eb2d0a 100644 --- a/active_projects/eola2/cramer.py +++ b/active_projects/eola2/cramer.py @@ -2154,7 +2154,7 @@ class Thumbnail(TransformingAreasYCoord): words = TextMobject("Cramer's", "rule") words.scale_to_fit_width(7) - # words.add_background_rectangle_to_parts() + # words.add_background_rectangle_to_submobjects() words.add_background_rectangle() words.to_edge(UP) self.add(words) diff --git a/mobject/matrix.py b/mobject/matrix.py index 924b9917..727f8f4d 100644 --- a/mobject/matrix.py +++ b/mobject/matrix.py @@ -116,10 +116,6 @@ class Matrix(VMobject): self.brackets = VGroup(l_bracket, r_bracket) return self - def add_background_rectangle(self): - self.background_rectangle = BackgroundRectangle(self) - self.add_to_back(self.background_rectangle) - def set_color_columns(self, *colors): for i, color in enumerate(colors): VGroup(*self.mob_matrix[:, i]).set_color(color) diff --git a/mobject/mobject.py b/mobject/mobject.py index 235ed515..0e8fa665 100644 --- a/mobject/mobject.py +++ b/mobject/mobject.py @@ -1,3 +1,5 @@ +from __future__ import absolute_import + import copy import itertools as it import numpy as np @@ -464,6 +466,27 @@ class Mobject(Container): self.shift(start - self.points[0]) return self + # Background rectangle + def add_background_rectangle(self, color=BLACK, opacity=0.75, **kwargs): + from mobject.shape_matchers import BackgroundRectangle + self.background_rectangle = BackgroundRectangle( + self, color=color, + fill_opacity=opacity, + **kwargs + ) + self.add_to_back(self.background_rectangle) + return self + + def add_background_rectangle_to_submobjects(self, **kwargs): + for submobject in self.submobjects: + submobject.add_background_rectangle(**kwargs) + return self + + def add_background_rectangle_to_family_members_with_points(self, **kwargs): + for mob in self.family_members_with_points(): + mob.add_background_rectangle(**kwargs) + return self + # Match other mobvject properties def match_color(self, mobject): @@ -643,6 +666,7 @@ class Mobject(Container): return result # Pseudonyms for more general get_critical_point method + def get_edge_center(self, direction): return self.get_critical_point(direction) @@ -719,7 +743,8 @@ class Mobject(Container): def submobject_family(self): sub_families = map(Mobject.submobject_family, self.submobjects) - all_mobjects = [self] + list(it.chain(*sub_families)) + # all_mobjects = [self] + list(it.chain(*sub_families)) + all_mobjects = list(it.chain(*sub_families)) + [self] return remove_list_redundancies(all_mobjects) def family_members_with_points(self): diff --git a/mobject/numbers.py b/mobject/numbers.py index 118289f7..5a6ff682 100644 --- a/mobject/numbers.py +++ b/mobject/numbers.py @@ -3,9 +3,7 @@ from __future__ import absolute_import from constants import * from mobject.svg.tex_mobject import TexMobject -from mobject.types.vectorized_mobject import VGroup from mobject.types.vectorized_mobject import VMobject -from mobject.shape_matchers import BackgroundRectangle class DecimalNumber(VMobject): @@ -51,7 +49,7 @@ class DecimalNumber(VMobject): ) if self.unit is not None: - self.unit_sign = TexMobject(self.unit, color = self.color) + self.unit_sign = TexMobject(self.unit, color=self.color) self.add(self.unit_sign) self.arrange_submobjects( @@ -70,16 +68,6 @@ class DecimalNumber(VMobject): if self.include_background_rectangle: self.add_background_rectangle() - def add_background_rectangle(self): - # TODO, is this the best way to handle - # background rectangles? - self.background_rectangle = BackgroundRectangle(self) - self.submobjects = [ - self.background_rectangle, - VGroup(*self.submobjects) - ] - return self - class Integer(DecimalNumber): CONFIG = { diff --git a/mobject/svg/tex_mobject.py b/mobject/svg/tex_mobject.py index 16eebe7f..9d1b70f1 100644 --- a/mobject/svg/tex_mobject.py +++ b/mobject/svg/tex_mobject.py @@ -116,16 +116,6 @@ class SingleStringTexMobject(SVGMobject): self.sort_submobjects(lambda p: p[0]) return self - def add_background_rectangle(self, color=BLACK, opacity=0.75, **kwargs): - self.background_rectangle = BackgroundRectangle( - self, color=color, - fill_opacity=opacity, - **kwargs - ) - letters = VMobject(*self.submobjects) - self.submobjects = [self.background_rectangle, letters] - return self - class TexMobject(SingleStringTexMobject): CONFIG = { @@ -232,10 +222,13 @@ class TexMobject(SingleStringTexMobject): part = self.get_part_by_tex(tex, **kwargs) return self.index_of_part(part) - def add_background_rectangle_to_parts(self): - for part in self: - part.add_background_rectangle() - return self + def split(self): + # Many old scenes assume that when you pass in a single string + # to TexMobject, it indexes across the characters. + if len(self.submobjects) == 1: + return self.submobjects[0].split() + else: + return super(TexMobject, self).split() class TextMobject(TexMobject): diff --git a/old_projects/wallis.py b/old_projects/wallis.py index be7efe7d..b2ffb1ed 100644 --- a/old_projects/wallis.py +++ b/old_projects/wallis.py @@ -2135,7 +2135,7 @@ class PlugObserverIntoPolynomial(DistanceProductScene): ) question.scale(text_scale_val) question.next_to(dot, RIGHT) - question.add_background_rectangle_to_parts() + question.add_background_rectangle_to_submobjects() f_words = TextMobject("$f$", "of the way") third_words = TextMobject("$\\frac{1}{3}$", "of the way") From 99d119e1f6ecfd9dec2d1b48943eabcb641c18dd Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 7 May 2018 13:33:06 -0700 Subject: [PATCH 09/12] Added/tweaked some indication animations --- animation/indication.py | 57 +++++++++++++++++++++++++++++++++++------ 1 file changed, 49 insertions(+), 8 deletions(-) diff --git a/animation/indication.py b/animation/indication.py index 5a6f1d4e..c22bdd91 100644 --- a/animation/indication.py +++ b/animation/indication.py @@ -6,14 +6,21 @@ from constants import * from animation.animation import Animation from animation.movement import Homotopy +from animation.composition import AnimationGroup +from animation.composition import Succession +from animation.creation import ShowCreation from animation.creation import ShowPartial +from animation.creation import FadeOut from animation.transform import Transform from mobject.mobject import Mobject from mobject.geometry import Circle from mobject.geometry import Dot +from mobject.shape_matchers import SurroundingRectangle +from utils.bezier import interpolate from utils.config_ops import digest_config from utils.rate_functions import squish_rate_func from utils.rate_functions import there_and_back +from utils.rate_functions import wiggle class FocusOn(Transform): @@ -93,14 +100,49 @@ class ShowCreationThenDestruction(ShowPassingFlash): } +class AnimationOnSurroundingRectangle(AnimationGroup): + CONFIG = { + "surrounding_rectangle_config": {}, + # Function which takes in a rectangle, and spits + # out some animation. Could be some animation class, + # could be something more + "rect_to_animation": Animation + } + + def __init__(self, mobject, **kwargs): + digest_config(self, kwargs) + rect = SurroundingRectangle( + mobject, **self.surrounding_rectangle_config + ) + AnimationGroup.__init__(self, self.rect_to_animation(rect, **kwargs)) + + +class ShowPassingFlashAround(AnimationOnSurroundingRectangle): + CONFIG = { + "rect_to_animation": ShowPassingFlash + } + + +class ShowCreationThenDestructionAround(AnimationOnSurroundingRectangle): + CONFIG = { + "rect_to_animation": ShowCreationThenDestruction + } + + +class CircleThenFadeAround(AnimationOnSurroundingRectangle): + CONFIG = { + "rect_to_animation": lambda rect: Succession( + ShowCreation, rect, + FadeOut, rect, + ) + } + + class ApplyWave(Homotopy): CONFIG = { - "direction": DOWN, + "direction": UP, "amplitude": 0.2, "run_time": 1, - "apply_function_kwargs": { - "maintain_smoothness": False, - }, } def __init__(self, mobject, **kwargs): @@ -110,9 +152,9 @@ class ApplyWave(Homotopy): vect = self.amplitude * self.direction def homotopy(x, y, z, t): - start_point = np.array([x, y, z]) alpha = (x - left_x) / (right_x - left_x) - power = np.exp(2 * (alpha - 0.5)) + # lf = self.lag_factor + power = np.exp(2.0 * (alpha - 0.5)) nudge = there_and_back(t**power) return np.array([x, y, z]) + nudge * vect Homotopy.__init__(self, homotopy, mobject, **kwargs) @@ -195,7 +237,6 @@ class TurnInsideOut(Transform): } def __init__(self, mobject, **kwargs): - mobject.sort_points(np.linalg.norm) mob_copy = mobject.copy() - mob_copy.sort_points(lambda p: -np.linalg.norm(p)) + mob_copy.reverse_points() Transform.__init__(self, mobject, mob_copy, **kwargs) From 3f4b17a785f632788934da66157130d585c5ce0f Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 7 May 2018 13:37:29 -0700 Subject: [PATCH 10/12] Moved where stroke_width attr is defined --- mobject/mobject.py | 1 - mobject/types/point_cloud_mobject.py | 4 ++++ mobject/types/vectorized_mobject.py | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/mobject/mobject.py b/mobject/mobject.py index 0e8fa665..72cfd33a 100644 --- a/mobject/mobject.py +++ b/mobject/mobject.py @@ -30,7 +30,6 @@ class Mobject(Container): """ CONFIG = { "color": WHITE, - "stroke_width": DEFAULT_POINT_THICKNESS, "name": None, "dim": 3, "target": None, diff --git a/mobject/types/point_cloud_mobject.py b/mobject/types/point_cloud_mobject.py index b2b9863a..7b9d329d 100644 --- a/mobject/types/point_cloud_mobject.py +++ b/mobject/types/point_cloud_mobject.py @@ -12,6 +12,10 @@ from utils.iterables import stretch_array_to_length class PMobject(Mobject): + CONFIG = { + "stroke_width": DEFAULT_POINT_THICKNESS, + } + def init_points(self): self.rgbas = np.zeros((0, 4)) self.points = np.zeros((0, 3)) diff --git a/mobject/types/vectorized_mobject.py b/mobject/types/vectorized_mobject.py index 92ddf402..1290bc78 100644 --- a/mobject/types/vectorized_mobject.py +++ b/mobject/types/vectorized_mobject.py @@ -18,6 +18,7 @@ class VMobject(Mobject): "fill_color": None, "fill_opacity": 0.0, "stroke_color": None, + "stroke_width": DEFAULT_POINT_THICKNESS, # Indicates that it will not be displayed, but # that it should count in parent mobject's path "is_subpath": False, From 405349bb68b33cc06033a9d5a8d60a8efbd2c960 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Mon, 7 May 2018 13:37:41 -0700 Subject: [PATCH 11/12] Removed color default on TexMobject --- mobject/svg/tex_mobject.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/mobject/svg/tex_mobject.py b/mobject/svg/tex_mobject.py index 9d1b70f1..06c6df13 100644 --- a/mobject/svg/tex_mobject.py +++ b/mobject/svg/tex_mobject.py @@ -2,11 +2,9 @@ from constants import * from svg_mobject import SVGMobject from svg_mobject import VMobjectFromSVGPathstring -from mobject.shape_matchers import BackgroundRectangle from utils.config_ops import digest_config from utils.strings import split_string_list_to_isolate_substring from mobject.types.vectorized_mobject import VGroup -from mobject.types.vectorized_mobject import VMobject from mobject.types.vectorized_mobject import VectorizedPoint import operator as op @@ -40,7 +38,6 @@ class SingleStringTexMobject(SVGMobject): "template_tex_file": TEMPLATE_TEX_FILE, "stroke_width": 0, "fill_opacity": 1.0, - "fill_color": WHITE, "should_center": True, "height": None, "organize_left_to_right": False, From 31fc87f49916bd582288563922f93330d3ce2bfe Mon Sep 17 00:00:00 2001 From: Ben Hambrecht Date: Tue, 8 May 2018 09:21:56 +0200 Subject: [PATCH 12/12] finished prob dist visuals --- .../eop/chapter1/prob_dist_visuals.py | 92 ++++++++++--------- 1 file changed, 49 insertions(+), 43 deletions(-) diff --git a/active_projects/eop/chapter1/prob_dist_visuals.py b/active_projects/eop/chapter1/prob_dist_visuals.py index b72957e1..6bd2dbae 100644 --- a/active_projects/eop/chapter1/prob_dist_visuals.py +++ b/active_projects/eop/chapter1/prob_dist_visuals.py @@ -58,7 +58,7 @@ class ProbabilityDistributions(PiCreatureScene): p_rain_whole_label.next_to(brace_rain, UP) brace_sun = Brace(sun_rect, DOWN) - p_sun_label = TextMobject("$P($sun$)=$").scale(text_scale) + p_sun_label = TextMobject("$P($sunshine$)=$").scale(text_scale) p_sun_decimal = DecimalNumber(p_sun).scale(text_scale) p_sun_decimal.next_to(p_sun_label) p_sun_whole_label = VGroup(p_sun_label, p_sun_decimal) @@ -97,7 +97,7 @@ class ProbabilityDistributions(PiCreatureScene): new_brace_sun = Brace(new_sun_rect, DOWN) - new_p_sun_label = TextMobject("$P($sun$)=$").scale(text_scale) + new_p_sun_label = TextMobject("$P($sunshine$)=$").scale(text_scale) new_p_sun_decimal = DecimalNumber(new_p_sun).scale(text_scale) new_p_sun_decimal.next_to(new_p_sun_label) new_p_sun_whole_label = VGroup(new_p_sun_label, new_p_sun_decimal) @@ -147,20 +147,28 @@ class ProbabilityDistributions(PiCreatureScene): self.play(MoveToTarget(forecast)) + self.play( + FadeOut(brace_rain), + FadeOut(brace_sun), + FadeOut(p_rain_whole_label), + FadeOut(p_sun_whole_label), + ) + + # COIN FLIP - coin_flip_rect = BrickRow(3) + coin_flip_rect = BrickRow(3, height = 2, width = 10) for (i, brick) in enumerate(coin_flip_rect.rects): tally = TallyStack(3 - i, i) - tally.next_to(brick, UP) + tally.move_to(brick) coin_flip_rect.add(tally) - coin_flip_rect.scale(0.7) + coin_flip_rect.scale(0.8).shift(2*RIGHT) self.play(FadeIn(coin_flip_rect)) counts = [1, 3, 3, 1] @@ -180,75 +188,72 @@ class ProbabilityDistributions(PiCreatureScene): coin_flip_rect.add(braces, labels) - self.play(coin_flip_rect.to_corner,UR) - + coin_flip_rect.target = coin_flip_rect.copy().scale(0.6) + coin_flip_rect.target.to_corner(UR, buff = LARGE_BUFF) + self.play( + MoveToTarget(coin_flip_rect) + ) + self.play( + FadeOut(braces), + FadeOut(labels) + ) # DOUBLE DICE THROW cell_size = 0.5 dice_table = TwoDiceTable(cell_size = cell_size, label_scale = 0.7) - dice_table.shift(DOWN) + dice_table.shift(0.8 * DOWN) + dice_unit_rect = SurroundingRectangle(dice_table.cells, buff = 0, + stroke_color = WHITE) - self.play(FadeIn(dice_table)) - self.wait() - self.play( - FadeOut(dice_table.rows), - FadeOut(dice_table.labels), - dice_table.cells.fade, 0.8 - ) - - dice_table_braces = VGroup() - dice_table_probs = VGroup() dice_table_grouped_cells = VGroup() for i in range(6): cell = dice_table.cells[6 * i] start = cell.get_center() - color = cell.get_fill_color() - brace = Brace(cell, LEFT, buff = 0, color = color) - brace.stretch(0.5,0) stop = start + cell_size * LEFT + cell_size * DOWN - p_label = TexMobject("{" + str(i + 1) + "\over 36}", color = color) - p_label.scale(0.35) - p_label.next_to(brace, LEFT) - dice_table_probs.add(p_label) - dice_table_braces.add(brace) - + dice_table_grouped_cells.add(VGroup(*[ dice_table.cells[6 * i - 5 * k] for k in range(i + 1) ])) - for i in range(5): cell = dice_table.cells[31 + i] start = cell.get_center() - color = cell.get_fill_color() - brace = Brace(cell, DOWN, buff = 0, color = color) - brace.stretch(0.5, 1) stop = start + cell_size * LEFT + cell_size * DOWN - p_label = TexMobject("{" + str(5 - i) + "\over 36}", color = color) - p_label.scale(0.35) - p_label.next_to(brace, DOWN) - dice_table_probs.add(p_label) - dice_table_braces.add(brace) - + dice_table_grouped_cells.add(VGroup(*[ dice_table.cells[31 + i - 5 * k] for k in range(5 - i) ])) + self.play( + FadeIn(dice_unit_rect), + FadeIn(dice_table.rows) + ) - # group the dice table cells to make them appear in the right order + for (cell, label) in zip(dice_table.cells, dice_table.labels): + cell.add(label) self.play( - LaggedStart(ShowCreation, dice_table_braces, lag_ratio = lag_ratio, run_time = run_time), - LaggedStart(Write, dice_table_probs, lag_ratio = lag_ratio, run_time = run_time), - LaggedStart(ApplyMethod, dice_table_grouped_cells, arg_creator = - lambda m : (m.fade, -4), lag_ratio = lag_ratio, run_time = run_time - ) + LaggedStart(FadeIn, dice_table_grouped_cells, + lag_ratio = lag_ratio, run_time = run_time) + ) + + self.wait() + self.play( + FadeOut(dice_table.rows), + FadeOut(dice_unit_rect), + ) + + + self.play( + dice_table_grouped_cells.space_out_submobjects, {"factor" : 1.9}, + rate_func=there_and_back_with_pause, + run_time=run_time ) @@ -269,5 +274,6 @@ class ProbabilityDistributions(PiCreatureScene): +