From b4e93b3376b6cd64ca0523a5b82f2df8a4ac35b5 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Thu, 26 Jan 2017 19:59:55 -0800 Subject: [PATCH] 80% through cube example --- animation/simple_animations.py | 13 +- eoc/chapter3.py | 689 ++++++++++++++++++++++++++++++++- mobject/mobject.py | 8 +- mobject/tex_mobject.py | 5 +- topics/characters.py | 3 +- 5 files changed, 693 insertions(+), 25 deletions(-) diff --git a/animation/simple_animations.py b/animation/simple_animations.py index 593e741d..70b6c813 100644 --- a/animation/simple_animations.py +++ b/animation/simple_animations.py @@ -100,11 +100,16 @@ class DrawBorderThenFill(Animation): Animation.__init__(self, vmobject, **kwargs) def update_submobject(self, submobject, starting_submobject, alpha): + submobject.pointwise_become_partial( + starting_submobject, 0, min(2*alpha, 1) + ) if alpha < 0.5: - submobject.pointwise_become_partial( - starting_submobject, 0, 2*alpha - ) - color = self.stroke_color or starting_submobject.get_color() + if self.stroke_color: + color = self.stroke_color + elif starting_submobject.stroke_width > 0: + color = starting_submobject.get_stroke_color() + else: + color = starting_submobject.get_color() submobject.set_stroke(color, width = self.stroke_width) submobject.set_fill(opacity = 0) else: diff --git a/eoc/chapter3.py b/eoc/chapter3.py index 15d463bc..076a7a5a 100644 --- a/eoc/chapter3.py +++ b/eoc/chapter3.py @@ -195,62 +195,717 @@ class DerivativeOfXSquaredAsGraph(GraphScene, ZoomedScene, PiCreatureScene): class NudgeSideLengthOfSquare(PiCreatureScene): CONFIG = { "square_width" : 3, - "dx" : 0.5, + "alt_square_width" : 5, + "dx" : 0.25, + "alt_dx" : 0.01, + "square_color" : GREEN, + "square_fill_opacity" : 0.75, + "three_color" : GREEN, + "dx_color" : BLUE_B, + "is_recursing_on_dx" : False, + "is_recursing_on_square_width" : False, } def construct(self): ApplyMethod(self.pi_creature.change_mode, "speaking").update(1) self.add_function_label() self.introduce_square() self.increase_area() + self.write_df_equation() + self.highlight_shapes() self.examine_thin_rectangles() self.examine_tiny_square() + self.show_smaller_dx() + self.rule_of_thumb() self.write_out_derivative() - self.shrink_dx() def add_function_label(self): label = TexMobject("f(x) = x^2") - label.next_to(ORIGIN, RIGHT) + label.next_to(ORIGIN, RIGHT, buff = (self.square_width-3)/2.) label.to_edge(UP) self.add(label) + self.function_label = label def introduce_square(self): square = Square( side_length = self.square_width, stroke_width = 0, - fill_opacity = 0.75, - fill_color = BLUE, + fill_opacity = self.square_fill_opacity, + fill_color = self.square_color, ) square.to_corner(UP+LEFT, buff = LARGE_BUFF) x_squared = TexMobject("x^2") x_squared.move_to(square) braces = VGroup() - for vect in DOWN, RIGHT: + for vect in RIGHT, DOWN: brace = Brace(square, vect) - brace.add(brace.get_text("$x$")) + text = brace.get_text("$x$") + brace.add(text) braces.add(brace) - self.play(DrawBorderThenFill(square)) - self.add(square, x_squared, braces) + self.play( + DrawBorderThenFill(square), + self.pi_creature.change_mode, "plain" + ) + self.play(*map(GrowFromCenter, braces)) + self.play(Write(x_squared)) + self.change_mode("pondering") + self.dither() + self.square = square + self.side_braces = braces def increase_area(self): - pass + color_kwargs = { + "fill_color" : YELLOW, + "fill_opacity" : self.square_fill_opacity, + "stroke_width" : 0, + } + right_rect = Rectangle( + width = self.dx, + height = self.square_width, + **color_kwargs + ) + bottom_rect = right_rect.copy().rotate(-np.pi/2) + right_rect.next_to(self.square, RIGHT, buff = 0) + bottom_rect.next_to(self.square, DOWN, buff = 0) + corner_square = Square( + side_length = self.dx, + **color_kwargs + ) + corner_square.next_to(self.square, DOWN+RIGHT, buff = 0) + + right_line = Line( + self.square.get_corner(UP+RIGHT), + self.square.get_corner(DOWN+RIGHT), + stroke_width = 0 + ) + bottom_line = Line( + self.square.get_corner(DOWN+RIGHT), + self.square.get_corner(DOWN+LEFT), + stroke_width = 0 + ) + corner_point = VectorizedPoint( + self.square.get_corner(DOWN+RIGHT) + ) + + little_braces = VGroup() + for vect in RIGHT, DOWN: + brace = Brace( + corner_square, vect, + buff = SMALL_BUFF, + tex_string = "\\underbrace{%s}"%(3*"\\quad"), + ) + text = brace.get_text("$dx$", buff = SMALL_BUFF) + text.highlight(self.dx_color) + brace.add(text) + little_braces.add(brace) + + right_brace, bottom_brace = self.side_braces + self.play( + Transform(right_line, right_rect), + Transform(bottom_line, bottom_rect), + Transform(corner_point, corner_square), + right_brace.next_to, right_rect, RIGHT, SMALL_BUFF, + bottom_brace.next_to, bottom_rect, DOWN, SMALL_BUFF, + ) + self.remove(corner_point, bottom_line, right_line) + self.add(corner_square, bottom_rect, right_rect) + self.play(*map(GrowFromCenter, little_braces)) + self.dither() + self.play(*it.chain(*[ + [mob.shift, vect*SMALL_BUFF] + for mob, vect in [ + (right_rect, RIGHT), + (bottom_rect, DOWN), + (corner_square, DOWN+RIGHT), + (right_brace, RIGHT), + (bottom_brace, DOWN), + (little_braces, DOWN+RIGHT) + ] + ])) + self.change_mode("thinking") + self.dither() + self.right_rect = right_rect + self.bottom_rect = bottom_rect + self.corner_square = corner_square + self.little_braces = little_braces + + def write_df_equation(self): + right_rect = self.right_rect + bottom_rect = self.bottom_rect + corner_square = self.corner_square + + df_equation = VGroup( + TexMobject("df").highlight(right_rect.get_color()), + TexMobject("="), + right_rect.copy(), + TextMobject("+"), + right_rect.copy(), + TexMobject("+"), + corner_square.copy() + ) + df_equation.arrange_submobjects() + df_equation.next_to( + self.function_label, DOWN, + aligned_edge = LEFT, + buff = SMALL_BUFF + ) + df, equals, r1, plus1, r2, plus2, s = df_equation + + pairs = [ + (df, self.function_label[0]), + (r1, right_rect), + (r2, bottom_rect), + (s, corner_square), + ] + for mover, origin in pairs: + mover.save_state() + Transform(mover, origin).update(1) + self.play(df.restore) + self.dither() + self.play( + *[ + mob.restore + for mob in r1, r2, s + ]+[ + Write(symbol) + for symbol in equals, plus1, plus2 + ], + run_time = 2 + ) + self.change_mode("happy") + self.dither() + + self.df_equation = df_equation + + def highlight_shapes(self): + df, equals, r1, plus1, r2, plus2, s = self.df_equation + + tups = [ + (self.right_rect, self.bottom_rect, r1, r2), + (self.corner_square, s) + ] + for tup in tups: + self.play( + *it.chain(*[ + [m.scale_in_place, 1.2, m.highlight, RED] + for m in tup + ]), + rate_func = there_and_back + ) + self.dither() def examine_thin_rectangles(self): - pass + df, equals, r1, plus1, r2, plus2, s = self.df_equation + + rects = VGroup(r1, r2) + thin_rect_brace = Brace(rects, DOWN) + text = thin_rect_brace.get_text("$2x \\, dx$") + VGroup(*text[-2:]).highlight(self.dx_color) + text.save_state() + alt_text = thin_rect_brace.get_text("$2(3)(0.01)$") + alt_text[2].highlight(self.three_color) + VGroup(*alt_text[-5:-1]).highlight(self.dx_color) + + example_value = TexMobject("=0.06") + example_value.next_to(alt_text, DOWN) + + self.play(GrowFromCenter(thin_rect_brace)) + self.play( + Write(text), + self.pi_creature.change_mode, "pondering" + ) + self.dither() + + xs = VGroup(*[ + brace[-1] + for brace in self.side_braces + ]) + dxs = VGroup(*[ + brace[-1] + for brace in self.little_braces + ]) + for group, tex, color in (xs, "3", self.three_color), (dxs, "0.01", self.dx_color): + group.save_state() + group.generate_target() + for submob in group.target: + number = TexMobject(tex) + number.highlight(color) + number.move_to(submob, LEFT) + Transform(submob, number).update(1) + self.play(MoveToTarget(xs)) + self.play(MoveToTarget(dxs)) + self.dither() + self.play(Transform(text, alt_text)) + self.dither() + self.play(Write(example_value)) + self.dither() + self.play( + FadeOut(example_value), + *[ + mob.restore + for mob in xs, dxs, text + ] + ) + self.remove(text) + text.restore() + self.add(text) + + self.dither() + self.dxs = dxs + self.thin_rect_brace = thin_rect_brace + self.thin_rect_area = text def examine_tiny_square(self): - pass + text = TexMobject("dx^2") + VGroup(*text[:2]).highlight(self.dx_color) + text.next_to(self.df_equation[-1], UP) + text.save_state() + alt_text = TextMobject("0.0001") + alt_text.move_to(text) + + self.play(Write(text)) + self.change_mode("surprised") + self.dither() + self.play( + MoveToTarget(self.dxs), + self.pi_creature.change_mode, "plain" + ) + for submob in self.dxs.target: + number = TexMobject("0.01") + number.highlight(self.dx_color) + number.move_to(submob, LEFT) + Transform(submob, number).update(1) + self.play(MoveToTarget(self.dxs)) + self.play( + Transform(text, alt_text), + self.pi_creature.change_mode, "raise_right_hand" + ) + self.dither(2) + self.play(*[ + mob.restore + for mob in self.dxs, text + ] + [ + self.pi_creature.change_mode, "erm" + ]) + self.dx_squared = text + + def show_smaller_dx(self): + self.mobjects_at_start_of_show_smaller_dx = [ + mob.copy() for mob in self.get_mobjects() + ] + if self.is_recursing_on_dx: + return + + alt_scene = self.__class__( + skip_animations = True, + dx = self.alt_dx, + is_recursing_on_dx = True + ) + for mob in self.get_mobjects(): + mob.save_state() + self.play(*[ + Transform(*pair) + for pair in zip( + self.get_mobjects(), + alt_scene.mobjects_at_start_of_show_smaller_dx, + ) + ]) + self.dither() + self.play(*[ + mob.restore + for mob in self.get_mobjects() + ]) + self.change_mode("happy") + self.dither() + + def rule_of_thumb(self): + circle = Circle(color = RED) + dx_squared_group = VGroup(self.dx_squared, self.df_equation[-1]) + circle.replace(dx_squared_group, stretch = True) + dx_squared_group.add(self.df_equation[-2]) + circle.scale_in_place(1.5) + safe_to_ignore = TextMobject("Safe to ignore") + safe_to_ignore.next_to(circle, DOWN, aligned_edge = LEFT) + safe_to_ignore.highlight(circle.get_color()) + + self.play(ShowCreation(circle)) + self.play( + Write(safe_to_ignore, run_time = 2), + self.pi_creature.change_mode, "raise_right_hand" + ) + self.play( + FadeOut(circle), + FadeOut(safe_to_ignore), + dx_squared_group.fade, 0.5, + dx_squared_group.to_corner, UP+RIGHT, + self.pi_creature.change_mode, "plain" + ) + self.dither() def write_out_derivative(self): - pass + df, equals, r1, plus1, r2, plus2, s = self.df_equation + frac_line = TexMobject("-") + frac_line.stretch_to_fit_width(df.get_width()) + frac_line.move_to(df) + dx = VGroup(*self.thin_rect_area[-2:]) + x = self.thin_rect_area[1] + + self.play( + Transform(r1, self.right_rect), + Transform(r2, self.bottom_rect), + FadeOut(plus1), + FadeOut(self.thin_rect_brace) + ) + self.play( + self.thin_rect_area.next_to, VGroup(df, equals), + RIGHT, MED_SMALL_BUFF, UP, + self.pi_creature.change_mode, "thinking" + ) + self.dither(2) + self.play( + ApplyMethod(df.next_to, frac_line, UP, SMALL_BUFF), + ApplyMethod(dx.next_to, frac_line, DOWN, SMALL_BUFF), + Write(frac_line), + path_arc = -np.pi + ) + self.dither() + + brace_xs = [ + brace[-1] + for brace in self.side_braces + ] + xs = list(brace_xs) + [x] + for x_mob in xs: + number = TexMobject("(%d)"%self.square_width) + number.move_to(x_mob, LEFT) + number.shift( + (x_mob.get_bottom()[1] - number[1].get_bottom()[1])*UP + ) + x_mob.save_state() + x_mob.target = number + self.play(*map(MoveToTarget, xs)) + self.dither(2) + + #Recursively transform to what would have happened + #with a wider square width + self.mobjects_at_end_of_write_out_derivative = self.get_mobjects() + if self.is_recursing_on_square_width or self.is_recursing_on_dx: + return + alt_scene = self.__class__( + skip_animations = True, + square_width = self.alt_square_width, + is_recursing_on_square_width = True, + ) + self.play(*[ + Transform(*pair) + for pair in zip( + self.mobjects_at_end_of_write_out_derivative, + alt_scene.mobjects_at_end_of_write_out_derivative + ) + ]) + self.change_mode("happy") + self.dither(2) + +class NudgeSideLengthOfCube(Scene): + CONFIG = { + "x_color" : BLUE, + "dx_color" : GREEN, + "df_color" : YELLOW, + "use_morty" : False, + "x" : 3, + "dx" : 0.2, + "alt_dx" : 0.02, + "offset_vect" : OUT, + "pose_angle" : np.pi/12, + "pose_axis" : UP+RIGHT, + "small_piece_scaling_factor" : 0.7, + "is_recursing" : False, + } + def construct(self): + self.add_title() + self.introduce_cube() + self.write_df_equation() + + def add_title(self): + title = TexMobject("f(x) = x^3") + title.shift(SPACE_WIDTH*LEFT/2) + title.to_edge(UP) + self.play(Write(title)) + self.dither() + + def introduce_cube(self): + cube = self.get_cube() + cube.to_edge(LEFT, buff = 2*LARGE_BUFF) + cube.shift(DOWN) + + dv_pieces = self.get_dv_pices(cube) + original_dx = self.dx + self.dx = 0 + alt_dv_pieces = self.get_dv_pices(cube) + self.dx = original_dx + alt_dv_pieces.set_fill(opacity = 0) + + x_brace = Brace(cube, LEFT, buff = SMALL_BUFF) + dx_brace = Brace( + dv_pieces[1], LEFT, buff = SMALL_BUFF, + tex_string = "\\underbrace{%s}"%(3*"\\quad"), + ) + dx_brace.stretch_in_place(1.5, 1) + for brace, tex in (x_brace, "x"), (dx_brace, "dx"): + brace.scale_in_place(0.95) + brace.rotate_in_place(-np.pi/96) + brace.shift(0.3*(UP+LEFT)) + brace.add(brace.get_text("$%s$"%tex)) + + + cube_group = VGroup(cube, dv_pieces, alt_dv_pieces) + self.pose_3d_mobject(cube_group) + + self.play(DrawBorderThenFill(cube)) + self.play(GrowFromCenter(x_brace)) + self.dither() + self.play(Transform(alt_dv_pieces, dv_pieces)) + self.remove(alt_dv_pieces) + self.add(dv_pieces) + self.play(GrowFromCenter(dx_brace)) + self.dither() + self.play(*[ + ApplyMethod( + piece.shift, + 0.5*(piece.get_center()-cube.get_center()) + ) + for piece in dv_pieces + ]+[ + ApplyMethod(dx_brace.shift, 0.7*UP) + ]) + self.dither() + + self.cube = cube + self.faces, self.bars, self.corner_cube = [ + VGroup(*[ + piece + for piece in dv_pieces + if piece.type == target_type + ]) + for target_type in "face", "bar", "corner_cube" + ] + + def write_df_equation(self): + df_equation = VGroup( + TexMobject("df"), + TexMobject("="), + self.organize_faces(self.faces.copy()), + TexMobject("+"), + self.organize_bars(self.bars.copy()), + TexMobject("+"), + self.corner_cube.copy() + ) + df, equals, faces, plus1, bars, plus2, corner_cube = df_equation + df.highlight(self.df_color) + for three_d_mob in faces, bars, corner_cube: + three_d_mob.scale(self.small_piece_scaling_factor) + # self.pose_3d_mobject(three_d_mob) + faces.set_fill(opacity = 0.3) + df_equation.arrange_submobjects(RIGHT) + df_equation.next_to(ORIGIN, RIGHT) + df_equation.to_edge(UP) + + faces_brace = Brace(faces, DOWN) + faces_brace_text = faces_brace.get_text("$3x^2", "\\, dx$") + extras_brace = Brace(VGroup(bars, corner_cube), DOWN) + ignore_text = extras_brace.get_text( + "Multiple \\\\ of $dx^2$" + ) + ignore_text.scale_in_place(0.7) + x_squared_dx = TexMobject("x^2", "\\, dx") + + + self.play(*map(Write, [df, equals])) + self.grab_pieces(self.faces, faces) + self.dither() + # self.shrink_dx() + face = self.faces[0] + face.save_state() + self.play(face.shift, SPACE_WIDTH*RIGHT) + x_squared_dx.move_to(self.faces[0]) + self.play(Write(x_squared_dx, run_time = 1)) + for submob in x_squared_dx: + self.play(submob.highlight, RED, rate_func = there_and_back) + self.play(submob.highlight, RED, rate_func = there_and_back) + self.dither() + self.play( + face.restore, + Transform(x_squared_dx, faces_brace_text), + GrowFromCenter(faces_brace) + ) + self.dither() + self.grab_pieces(self.bars, bars, plus1) + self.grab_pieces(self.corner_cube, corner_cube, plus2) + self.play( + GrowFromCenter(extras_brace), + Write(ignore_text) + ) + self.dither() + self.play(*[ + ApplyMethod(mob.fade, 0.7) + for mob in [ + plus1, bars, plus2, corner_cube, + extras_brace, ignore_text + ] + ]) + self.dither() + + def grab_pieces(self, start_pieces, end_pices, to_write = None): + for piece in start_pieces: + piece.generate_target() + piece.target.rotate_in_place( + np.pi/12, piece.get_center()-self.cube.get_center() + ) + piece.target.highlight(RED) + self.play(*map(MoveToTarget, start_pieces), rate_func = wiggle) + self.dither() + added_anims = [] + if to_write is not None: + added_anims.append(Write(to_write)) + self.play( + Transform(start_pieces.copy(), end_pices), + *added_anims + ) def shrink_dx(self): - pass - - - + self.mobjects_at_start_of_shrink_dx = self.get_mobjects() + if self.is_recursing: + return + alt_scene = self.__class__( + dx = self.alt_dx, + skip_animations = True, + is_recursing = True + ) + for mob in self.get_mobjects(): + mob.save_state() + self.play(*[ + Transform(*pair) + for pair in zip( + self.get_mobjects(), + alt_scene.mobjects_at_start_of_shrink_dx + ) + ]) + self.dither() + self.play(*[ + mob.restore + for mob in self.get_mobjects() + ]) + + def get_cube(self): + cube = self.get_prism(self.x, self.x, self.x) + cube.set_fill(color = BLUE, opacity = 0.3) + cube.set_stroke(color = WHITE, width = 1) + return cube + + def get_dv_pices(self, cube): + pieces = VGroup() + for vect in it.product([0, 1], [0, 1], [0, 1]): + if np.all(vect == ORIGIN): + continue + args = [ + self.x if bit is 0 else self.dx + for bit in vect + ] + piece = self.get_prism(*args) + piece.next_to(cube, np.array(vect), buff = 0) + pieces.add(piece) + if sum(vect) == 1: + piece.type = "face" + elif sum(vect) == 2: + piece.type = "bar" + else: + piece.type = "corner_cube" + + return pieces + + def organize_faces(self, faces): + self.unpose_3d_mobject(faces) + for face in faces: + dimensions = [ + face.length_over_dim(dim) + for dim in range(3) + ] + thin_dim = np.argmin(dimensions) + if thin_dim == 0: + face.rotate(np.pi/2, DOWN) + elif thin_dim == 1: + face.rotate(np.pi/2, RIGHT) + faces.arrange_submobjects(OUT, buff = LARGE_BUFF) + self.pose_3d_mobject(faces) + return faces + + def organize_bars(self, bars): + self.unpose_3d_mobject(bars) + for bar in bars: + dimensions = [ + bar.length_over_dim(dim) + for dim in range(3) + ] + thick_dim = np.argmax(dimensions) + if thick_dim == 0: + bar.rotate(np.pi/2, OUT) + elif thick_dim == 2: + bar.rotate(np.pi/2, LEFT) + bars.arrange_submobjects(OUT, buff = LARGE_BUFF) + self.pose_3d_mobject(bars) + return bars + + def get_corner_cube(self): + return self.get_prism(self.dx, self.dx, self.dx) + + + def get_prism(self, width, height, depth): + color_kwargs = { + "fill_color" : YELLOW, + "fill_opacity" : 0.4, + "stroke_color" : WHITE, + "stroke_width" : 0.1, + } + front = Rectangle( + width = width, + height = height, + **color_kwargs + ) + face = VGroup(front) + for vect in LEFT, RIGHT, UP, DOWN: + if vect is LEFT or vect is RIGHT: + side = Rectangle( + height = height, + width = depth, + **color_kwargs + ) + else: + side = Rectangle( + height = depth, + width = width, + **color_kwargs + ) + side.next_to(front, vect, buff = 0) + side.rotate( + np.pi/2, rotate_vector(vect, -np.pi/2), + about_point = front.get_edge_center(vect) + ) + face.add(side) + return face + + def pose_3d_mobject(self, mobject): + mobject.rotate_in_place(self.pose_angle, self.pose_axis) + return mobject + + def unpose_3d_mobject(self, mobject): + mobject.rotate_in_place(-self.pose_angle, self.pose_axis) + return mobject + +class ShowCubeDVIn3D(Scene): + def construct(self): + raise Exception("This scene is only here for the stage_scenes script.") diff --git a/mobject/mobject.py b/mobject/mobject.py index 2e1c9e89..b614f65c 100644 --- a/mobject/mobject.py +++ b/mobject/mobject.py @@ -309,6 +309,12 @@ class Mobject(object): def scale_to_fit_height(self, height): return self.stretch_to_fit(height, 1, stretch = False) + def space_out_submobjects(self, factor = 1.5): + self.scale_in_place(factor) + for submob in self.submobjects: + submob.scale_in_place(1./factor) + return self + def move_to(self, point_or_mobject, aligned_edge = ORIGIN): if isinstance(point_or_mobject, Mobject): target = point_or_mobject.get_critical_point(aligned_edge) @@ -450,7 +456,7 @@ class Mobject(object): if use_submobject: return self.get_submobject_critical_point(direction) result = np.zeros(self.dim) - for dim in [0, 1]: + for dim in range(self.dim): if direction[dim] <= 0: min_point = self.reduce_across_dimension(np.min, np.min, dim) if direction[dim] >= 0: diff --git a/mobject/tex_mobject.py b/mobject/tex_mobject.py index ffc1c9f2..2e67e238 100644 --- a/mobject/tex_mobject.py +++ b/mobject/tex_mobject.py @@ -130,11 +130,12 @@ class TextMobject(TexMobject): class Brace(TexMobject): CONFIG = { "buff" : 0.2, + "n_quads" : 3, + "tex_string" : "\\underbrace{%s}"%(3*"\\qquad"), } - TEX_STRING = "\\underbrace{%s}"%(3*"\\qquad") def __init__(self, mobject, direction = DOWN, **kwargs): digest_config(self, kwargs, locals()) - TexMobject.__init__(self, self.TEX_STRING, **kwargs) + TexMobject.__init__(self, self.tex_string, **kwargs) angle = -np.arctan2(*direction[:2]) + np.pi mobject.rotate(-angle) left = mobject.get_corner(DOWN+LEFT) diff --git a/topics/characters.py b/topics/characters.py index 13717bb8..d91660cf 100644 --- a/topics/characters.py +++ b/topics/characters.py @@ -268,6 +268,7 @@ class PiCreatureScene(Scene): CONFIG = { "total_dither_time" : 0, "use_morty" : True, + "seconds_to_blink" : 3, } def setup(self): self.pi_creature = self.get_pi_creature() @@ -326,7 +327,7 @@ class PiCreatureScene(Scene): def dither(self, time = 1, blink = True): while time > 0: - if blink and self.total_dither_time%2 == 1: + if blink and self.total_dither_time%self.seconds_to_blink == 1: self.play(Blink(self.pi_creature)) else: Scene.dither(self, time)