diff --git a/active_projects/eola2/__init__.py b/active_projects/eola2/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/active_projects/eola2/determinant_puzzle.py b/active_projects/eola2/determinant_puzzle.py new file mode 100644 index 00000000..be24c390 --- /dev/null +++ b/active_projects/eola2/determinant_puzzle.py @@ -0,0 +1,286 @@ +from big_ol_pile_of_manim_imports import * + + +class WorkOutNumerically(Scene): + CONFIG = { + "M1_COLOR": TEAL, + "M2_COLOR": PINK, + } + + def construct(self): + self.add_question() + self.add_example() + self.compute_rhs() + self.compute_lhs() + + def add_question(self): + equation = self.original_equation = TexMobject( + "\\det(", "M_1", "M_2", ")", "=", + "\\det(", "M_1", ")", + "\\det(", "M_2", ")", + ) + equation.set_color_by_tex_to_color_map({ + "M_1": self.M1_COLOR, + "M_2": self.M2_COLOR, + }) + challenge = TextMobject("Explain in one sentence") + challenge.set_color(YELLOW) + group = VGroup(challenge, equation) + group.arrange_submobjects(DOWN) + group.to_edge(UP) + + self.add(equation) + self.play(Write(challenge)) + self.wait() + + def add_example(self): + M1 = self.M1 = Matrix([[2, -1], [1, 1]]) + M1.set_color(self.M1_COLOR) + self.M1_copy = M1.copy() + M2 = self.M2 = Matrix([[-1, 4], [1, 1]]) + M2.set_color(self.M2_COLOR) + self.M2_copy = M2.copy() + eq_parts = TexMobject( + "\\det", "\\big(", "\\big)", "=", + "\\det", "\\big(", "\\big)", + "\\det", "\\big(", "\\big)", + ) + for part in eq_parts.get_parts_by_tex("\\big"): + part.scale(2) + part.stretch(1.5, 1) + i1, i2, i3 = [ + eq_parts.index_of_part(part) + 1 + for part in eq_parts.get_parts_by_tex("\\big(") + ] + equation = self.equation_with_numbers = VGroup(*it.chain( + eq_parts[:i1], [M1, M2], + eq_parts[i1:i2], [self.M1_copy], + eq_parts[i2:i3], [self.M2_copy], + eq_parts[i3:], + )) + equation.arrange_submobjects(RIGHT, buff=SMALL_BUFF) + eq_parts.get_part_by_tex("=").shift(0.2 * SMALL_BUFF * DOWN) + equation.scale_to_fit_width(FRAME_WIDTH - 2 * LARGE_BUFF) + equation.next_to(self.original_equation, DOWN, MED_LARGE_BUFF) + + self.play(LaggedStart(FadeIn, equation)) + + def compute_rhs(self): + M1, M2 = self.M1_copy, self.M2_copy + + line1 = VGroup( + TexMobject( + "\\big(", "2", "\\cdot", "2", "-", + "(-1)", "\\cdot", "1", "\\big)" + ), + TexMobject( + "\\big(", "-1", "\\cdot", "1", "-", + "4", "\\cdot", "1", "\\big)" + ), + ) + line1.arrange_submobjects(RIGHT, buff=SMALL_BUFF) + line1[0].set_color(self.M1_COLOR) + line1[1].set_color(self.M2_COLOR) + indices = [1, 3, 5, 7] + + line2 = TexMobject("(3)", "(-5)") + line2.match_style(line1) + line3 = TexMobject("-15") + arrows = [TexMobject("\\downarrow") for x in range(2)] + lines = VGroup(line1, arrows[0], line2, arrows[1], line3) + lines.arrange_submobjects(DOWN, buff=MED_SMALL_BUFF) + lines.next_to(self.equation_with_numbers, DOWN, buff=MED_LARGE_BUFF) + lines.to_edge(RIGHT) + + for matrix, det in zip([M1, M2], line1): + numbers = VGroup(*[det[i] for i in indices]) + numbers_iter = iter(numbers) + non_numbers = VGroup(*filter( + lambda m: m not in numbers, + det + )) + matrix_numbers = VGroup(*[ + matrix.mob_matrix[i][j].copy() + for i, j in (0, 0), (1, 1), (0, 1), (1, 0) + ]) + self.play( + LaggedStart(FadeIn, non_numbers, run_time=1), + LaggedStart( + ReplacementTransform, + matrix_numbers, + lambda m: (m, numbers_iter.next()), + path_arc=TAU / 6 + ), + ) + self.play(LaggedStart(FadeIn, lines[1:], run_time=3)) + + def compute_lhs(self): + matrix = Matrix([[-3, 7], [0, 5]]) + matrix.set_color(BLUE) + matrix.scale(0.8) + empty_det_tex = TexMobject("\\det", "\\big(", "\\big)") + empty_det_tex[1:].scale(1.5) + empty_det_tex[1:].match_height(matrix, stretch=True) + det_tex = VGroup(empty_det_tex[:2], matrix, *empty_det_tex[2:]) + det_tex.arrange_submobjects(RIGHT, buff=SMALL_BUFF) + + group = VGroup( + det_tex, + TexMobject("\\downarrow"), + TexMobject("(-3)(5) - (7)(0)").scale(0.8), + TexMobject("\\downarrow"), + TexMobject("-15"), + ) + group.arrange_submobjects(DOWN, buff=2 * SMALL_BUFF) + # group.scale_to_fit_height(0.4*FRAME_HEIGHT) + group.next_to(self.equation_with_numbers, DOWN) + group.shift(FRAME_WIDTH * LEFT / 4) + + self.play(FadeIn(empty_det_tex)) + self.play(*[ + ReplacementTransform(M.copy(), matrix) + for M in self.M1, self.M2 + ]) + self.play(LaggedStart(FadeIn, group[1:])) + self.wait() + + +class LetsGoInOneSentence(TeacherStudentsScene): + def construct(self): + self.teacher_says( + "Here we go, \\\\", "one sentence!" + ) + self.change_all_student_modes("hooray") + self.teacher_says( + "Or three...", "", + target_mode="guilty" + ) + self.change_all_student_modes("sassy") + self.wait(4) + + +class SuccessiveLinearTransformations(LinearTransformationScene): + CONFIG = { + "matrix_2": [[3, -1], [0, 1]], + "matrix_1": [[2, 3], [-1. / 3, 2]], + } + + def construct(self): + self.create_product_and_inverse() + self.scale_area_successively() + self.apply_transformations_successively() + self.scale_area_successively( + "\\det(M_2)", "\\det(M_1)", "\\det(M_1 M_2)", + reset=False + ) + # self.show_det_as_scaling_factor() + + def create_product_and_inverse(self): + self.matrix_product = np.dot(self.matrix_1, self.matrix_2) + self.matrix_product_inverse = np.linalg.inv(self.matrix_product) + + def scale_area_successively(self, tex2="3", tex1="5", tex_prod="15", reset=True): + self.add_unit_square() + t1 = "$%s \\, \\cdot $" % tex1 + t2 = "$%s \\, \\cdot $" % tex2 + t3 = "Area" + areas = VGroup( + TextMobject("", "", t3), + TextMobject("", t2, t3), + TextMobject(t1, t2, t3), + ) + areas.scale(0.8) + areas.move_to(self.square) + area = areas[0] + self.add_moving_mobject(area, areas[1]) + + self.play( + FadeIn(self.square), + Write(area), + Animation(self.basis_vectors) + ) + self.apply_matrix(self.matrix_2) + self.wait() + self.add_moving_mobject(area, areas[2]) + self.apply_matrix(self.matrix_1) + self.wait() + + product = VGroup(area[:2]) + brace = Brace(product, DOWN, buff=SMALL_BUFF) + brace_tex = brace.get_tex(tex_prod, buff=SMALL_BUFF) + brace_tex.scale(0.8, about_edge=UP) + + self.play( + GrowFromCenter(brace), + Write(brace_tex) + ) + self.wait() + if reset: + self.play( + FadeOut(VGroup(self.square, area, brace, brace_tex)), + Animation(self.plane), + Animation(self.basis_vectors) + ) + self.transformable_mobjects.remove(self.square) + self.moving_mobjects = [] + self.reset_plane() + self.wait() + + def apply_transformations_successively(self): + M1, M2, all_space = expression = TexMobject( + "M_1", "M_2", "\\text{(All 2d space)}" + ) + expression.set_color_by_tex_to_color_map({ + "M_1": TEAL, + "M_2": PINK, + }) + expression.shift(FRAME_WIDTH * LEFT / 4) + expression.to_edge(UP) + for part in expression: + part.add_background_rectangle() + part.background_rectangle.stretch(1.05, 0) + M1.save_state() + M1.move_to(ORIGIN) + M1.fade(1) + + # Apply one after the other + self.play( + FocusOn(M2, run_time=1), + FadeIn(VGroup(M2, all_space)) + ) + self.add_foreground_mobjects(M2, all_space) + self.apply_matrix(self.matrix_2) + self.wait() + self.play(M1.restore) + self.add_foreground_mobjects(M1) + self.apply_matrix(self.matrix_1) + self.wait() + + # Show full composition + rp, lp = parens = TexMobject("()") + matrices = VGroup(M1, M2) + matrices.generate_target() + parens.match_height(matrices) + lp.move_to(matrices, RIGHT) + matrices.target.next_to(lp, LEFT, SMALL_BUFF) + rp.next_to(matrices.target, LEFT, SMALL_BUFF) + + self.reset_plane() + self.play( + MoveToTarget(matrices), + *map(GrowFromCenter, parens) + ) + self.apply_matrix(self.matrix_product) + self.wait() + self.reset_plane() + + def show_det_as_scaling_factor(self): + pass + + ### + + def reset_plane(self): + plane_and_bases = VGroup(self.plane, self.basis_vectors) + self.play(FadeOut(plane_and_bases)) + self.apply_matrix(self.matrix_product_inverse, run_time=0) + self.play(FadeIn(plane_and_bases)) diff --git a/animation/animation.py b/animation/animation.py index 4625a7be..8d281219 100644 --- a/animation/animation.py +++ b/animation/animation.py @@ -7,28 +7,30 @@ from utils.rate_functions import smooth from utils.config_ops import instantiate from utils.config_ops import digest_config + class Animation(object): CONFIG = { - "run_time" : DEFAULT_ANIMATION_RUN_TIME, - "rate_func" : smooth, - "name" : None, - #Does this animation add or remove a mobject form the screen - "remover" : False, - #Options are lagged_start, smoothed_lagged_start, - #one_at_a_time, all_at_once - "submobject_mode" : "all_at_once", - "lag_factor" : 2, + "run_time": DEFAULT_ANIMATION_RUN_TIME, + "rate_func": smooth, + "name": None, + # Does this animation add or remove a mobject form the screen + "remover": False, + # Options are lagged_start, smoothed_lagged_start, + # one_at_a_time, all_at_once + "submobject_mode": "all_at_once", + "lag_factor": 2, # Used by EmptyAnimation to announce itself ignorable # in Successions and AnimationGroups - "empty" : False + "empty": False } + def __init__(self, mobject, **kwargs): mobject = instantiate(mobject) assert(isinstance(mobject, Mobject)) digest_config(self, kwargs, locals()) self.starting_mobject = self.mobject.copy() if self.rate_func is None: - self.rate_func = (lambda x : x) + self.rate_func = (lambda x: x) if self.name is None: self.name = self.__class__.__name__ + str(self.mobject) self.all_families_zipped = self.get_all_families_zipped() @@ -37,7 +39,7 @@ class Animation(object): def update_config(self, **kwargs): digest_config(self, kwargs) if "rate_func" in kwargs and kwargs["rate_func"] is None: - self.rate_func = (lambda x : x) + self.rate_func = (lambda x: x) return self def __str__(self): @@ -58,13 +60,13 @@ class Animation(object): return self def update_submobject(self, submobject, starting_sumobject, alpha): - #Typically ipmlemented by subclass + # Typically ipmlemented by subclass pass def get_all_mobjects(self): """ Ordering must match the ording of arguments to update_submobject - """ + """ return self.mobject, self.starting_mobject def get_all_families_zipped(self): @@ -75,15 +77,15 @@ class Animation(object): def get_sub_alpha(self, alpha, index, num_submobjects): if self.submobject_mode in ["lagged_start", "smoothed_lagged_start"]: - prop = float(index)/num_submobjects + prop = float(index) / num_submobjects if self.submobject_mode is "smoothed_lagged_start": prop = smooth(prop) lf = self.lag_factor - return np.clip(lf*alpha - (lf-1)*prop, 0, 1) + return np.clip(lf * alpha - (lf - 1) * prop, 0, 1) elif self.submobject_mode == "one_at_a_time": - lower = float(index)/num_submobjects - upper = float(index+1)/num_submobjects - return np.clip((alpha-lower)/(upper-lower), 0, 1) + lower = float(index) / num_submobjects + upper = float(index + 1) / num_submobjects + return np.clip((alpha - lower) / (upper - lower), 0, 1) elif self.submobject_mode == "all_at_once": return alpha raise Exception("Invalid submobject mode") @@ -101,7 +103,8 @@ class Animation(object): def set_rate_func(self, rate_func): if rate_func is None: - rate_func = lambda x : x + def rate_func(x): + return x self.rate_func = rate_func return self @@ -115,7 +118,7 @@ class Animation(object): def is_remover(self): return self.remover - def clean_up(self, surrounding_scene = None): + def clean_up(self, surrounding_scene=None): self.update(1) if surrounding_scene is not None: if self.is_remover(): @@ -123,16 +126,3 @@ class Animation(object): else: surrounding_scene.add(self.mobject) return self - - - - - - - - - - - - - diff --git a/animation/composition.py b/animation/composition.py index f05aeb64..f5d24d34 100644 --- a/animation/composition.py +++ b/animation/composition.py @@ -14,25 +14,28 @@ from utils.bezier import inverse_interpolate from utils.config_ops import digest_config from utils.rate_functions import squish_rate_func + class EmptyAnimation(Animation): CONFIG = { - "run_time" : 0, - "empty" : True + "run_time": 0, + "empty": True } def __init__(self, *args, **kwargs): return Animation.__init__(self, Group(), *args, **kwargs) + class Succession(Animation): CONFIG = { - "rate_func" : None, + "rate_func": None, } + def __init__(self, *args, **kwargs): """ - Each arg will either be an animation, or an animation class - followed by its arguments (and potentially a dict for + Each arg will either be an animation, or an animation class + followed by its arguments (and potentially a dict for configuration). - For example, + For example, Succession( ShowCreation(circle), Transform, circle, square, @@ -42,16 +45,17 @@ class Succession(Animation): """ animations = [] state = { - "animations" : animations, - "curr_class" : None, - "curr_class_args" : [], - "curr_class_config" : {}, + "animations": animations, + "curr_class": None, + "curr_class_args": [], + "curr_class_config": {}, } + def invoke_curr_class(state): if state["curr_class"] is None: return anim = state["curr_class"]( - *state["curr_class_args"], + *state["curr_class_args"], **state["curr_class_config"] ) state["animations"].append(anim) @@ -76,43 +80,47 @@ class Succession(Animation): for anim in animations: anim.update(0) - animations = filter (lambda x : not(x.empty), animations) + animations = filter(lambda x: not(x.empty), animations) self.run_times = [anim.run_time for anim in animations] if "run_time" in kwargs: run_time = kwargs.pop("run_time") - warnings.warn("Succession doesn't currently support explicit run_time.") + warnings.warn( + "Succession doesn't currently support explicit run_time.") run_time = sum(self.run_times) self.num_anims = len(animations) if self.num_anims == 0: self.empty = True self.animations = animations - #Have to keep track of this run_time, because Scene.play - #might very well mess with it. + # Have to keep track of this run_time, because Scene.play + # might very well mess with it. self.original_run_time = run_time # critical_alphas[i] is the start alpha of self.animations[i] # critical_alphas[i + 1] is the end alpha of self.animations[i] critical_times = np.concatenate(([0], np.cumsum(self.run_times))) - self.critical_alphas = map (lambda x : np.true_divide(x, run_time), critical_times) if self.num_anims > 0 else [0.0] + self.critical_alphas = map(lambda x: np.true_divide( + x, run_time), critical_times) if self.num_anims > 0 else [0.0] # self.scene_mobjects_at_time[i] is the scene's mobjects at start of self.animations[i] # self.scene_mobjects_at_time[i + 1] is the scene mobjects at end of self.animations[i] self.scene_mobjects_at_time = [None for i in range(self.num_anims + 1)] self.scene_mobjects_at_time[0] = Group() for i in range(self.num_anims): - self.scene_mobjects_at_time[i + 1] = self.scene_mobjects_at_time[i].copy() + self.scene_mobjects_at_time[i + + 1] = self.scene_mobjects_at_time[i].copy() self.animations[i].clean_up(self.scene_mobjects_at_time[i + 1]) self.current_alpha = 0 - self.current_anim_index = 0 # If self.num_anims == 0, this is an invalid index, but so it goes + # If self.num_anims == 0, this is an invalid index, but so it goes + self.current_anim_index = 0 if self.num_anims > 0: self.mobject = self.scene_mobjects_at_time[0] self.mobject.add(self.animations[0].mobject) else: self.mobject = Group() - Animation.__init__(self, self.mobject, run_time = run_time, **kwargs) + Animation.__init__(self, self.mobject, run_time=run_time, **kwargs) # Beware: This does NOT take care of calling update(0) on the subanimation. # This was important to avoid a pernicious possibility in which subanimations were called @@ -120,7 +128,8 @@ class Succession(Animation): # continuing exponentially. def jump_to_start_of_anim(self, index): if index != self.current_anim_index: - self.mobject.remove(*self.mobject.submobjects) # Should probably have a cleaner "remove_all" method... + # Should probably have a cleaner "remove_all" method... + self.mobject.remove(*self.mobject.submobjects) self.mobject.add(*self.scene_mobjects_at_time[index].submobjects) self.mobject.add(self.animations[index].mobject) @@ -138,18 +147,18 @@ class Succession(Animation): return gt_alpha_iter = it.ifilter( - lambda i : self.critical_alphas[i+1] >= alpha, + lambda i: self.critical_alphas[i + 1] >= alpha, range(self.num_anims) ) i = next(gt_alpha_iter, None) - if i == None: - # In this case, we assume what is happening is that alpha is 1.0, + if i is None: + # In this case, we assume what is happening is that alpha is 1.0, # but that rounding error is causing us to overshoot the end of # self.critical_alphas (which is also 1.0) if not abs(alpha - 1) < 0.001: warnings.warn( - "Rounding error not near alpha=1 in Succession.update_mobject," + \ - "instead alpha = %f"%alpha + "Rounding error not near alpha=1 in Succession.update_mobject," + + "instead alpha = %f" % alpha ) print self.critical_alphas, alpha i = self.num_anims - 1 @@ -158,8 +167,8 @@ class Succession(Animation): self.jump_to_start_of_anim(i) sub_alpha = inverse_interpolate( - self.critical_alphas[i], - self.critical_alphas[i + 1], + self.critical_alphas[i], + self.critical_alphas[i + 1], alpha ) self.animations[i].update(sub_alpha) @@ -171,14 +180,16 @@ class Succession(Animation): for anim in self.animations: anim.clean_up(*args, **kwargs) + class AnimationGroup(Animation): CONFIG = { - "rate_func" : None + "rate_func": None } + def __init__(self, *sub_anims, **kwargs): - sub_anims = filter (lambda x : not(x.empty), sub_anims) + sub_anims = filter(lambda x: not(x.empty), sub_anims) digest_config(self, locals()) - self.update_config(**kwargs) # Handles propagation to self.sub_anims + self.update_config(**kwargs) # Handles propagation to self.sub_anims if len(sub_anims) == 0: self.empty = True @@ -198,7 +209,7 @@ class AnimationGroup(Animation): def update_config(self, **kwargs): Animation.update_config(self, **kwargs) - + # If AnimationGroup is called with any configuration, # it is propagated to the sub_animations for anim in self.sub_anims: @@ -206,30 +217,33 @@ class AnimationGroup(Animation): # Variants on mappin an animation over submobjectsg + class LaggedStart(Animation): CONFIG = { - "run_time" : 2, - "lag_ratio" : 0.5, + "run_time": 2, + "lag_ratio": 0.5, } - def __init__(self, AnimationClass, mobject, arg_creator = None, **kwargs): + + def __init__(self, AnimationClass, mobject, arg_creator=None, **kwargs): digest_config(self, kwargs) for key in "rate_func", "run_time", "lag_ratio": if key in kwargs: kwargs.pop(key) if arg_creator is None: - arg_creator = lambda mobject : (mobject,) + def arg_creator(mobject): + return (mobject,) self.subanimations = [ AnimationClass( *arg_creator(submob), - run_time = self.run_time, - rate_func = squish_rate_func( + run_time=self.run_time, + rate_func=squish_rate_func( self.rate_func, beta, beta + self.lag_ratio ), **kwargs ) for submob, beta in zip( - mobject, - np.linspace(0, 1-self.lag_ratio, len(mobject)) + mobject, + np.linspace(0, 1 - self.lag_ratio, len(mobject)) ) ] Animation.__init__(self, mobject, **kwargs) @@ -243,6 +257,7 @@ class LaggedStart(Animation): for anim in self.subanimations: anim.clean_up(*args, **kwargs) + class ApplyToCenters(Animation): def __init__(self, AnimationClass, mobjects, **kwargs): full_kwargs = AnimationClass.CONFIG @@ -259,9 +274,8 @@ class ApplyToCenters(Animation): def update_mobject(self, alpha): self.centers_container.update_mobject(alpha) center_mobs = self.centers_container.mobject.split() - mobjects = self.mobject.split() + mobjects = self.mobject.split() for center_mob, mobject in zip(center_mobs, mobjects): mobject.shift( - center_mob.get_center()-mobject.get_center() + center_mob.get_center() - mobject.get_center() ) - diff --git a/animation/creation.py b/animation/creation.py index e345f893..a0417b80 100644 --- a/animation/creation.py +++ b/animation/creation.py @@ -15,7 +15,8 @@ from utils.paths import counterclockwise_path from utils.rate_functions import double_smooth from utils.rate_functions import smooth -#Drawing +# Drawing + class ShowPartial(Animation): def update_submobject(self, submobject, starting_submobject, alpha): @@ -26,26 +27,31 @@ class ShowPartial(Animation): def get_bounds(self, alpha): raise Exception("Not Implemented") + class ShowCreation(ShowPartial): CONFIG = { - "submobject_mode" : "one_at_a_time", + "submobject_mode": "one_at_a_time", } + def get_bounds(self, alpha): return (0, alpha) + class Uncreate(ShowCreation): CONFIG = { - "rate_func" : lambda t : smooth(1-t), - "remover" : True + "rate_func": lambda t: smooth(1 - t), + "remover": True } + class Write(ShowCreation): CONFIG = { - "rate_func" : None, - "submobject_mode" : "lagged_start", + "rate_func": None, + "submobject_mode": "lagged_start", } + def __init__(self, mob_or_text, **kwargs): - digest_config(self, kwargs) + digest_config(self, kwargs) if isinstance(mob_or_text, str): mobject = TextMobject(mob_or_text) else: @@ -67,13 +73,15 @@ class Write(ShowCreation): else: self.run_time = 2 + class DrawBorderThenFill(Animation): CONFIG = { - "run_time" : 2, - "stroke_width" : 2, - "stroke_color" : None, - "rate_func" : double_smooth, + "run_time": 2, + "stroke_width": 2, + "stroke_color": None, + "rate_func": double_smooth, } + def __init__(self, vmobject, **kwargs): if not isinstance(vmobject, VMobject): raise Exception("DrawBorderThenFill only works for VMobjects") @@ -82,59 +90,64 @@ class DrawBorderThenFill(Animation): def update_submobject(self, submobject, starting_submobject, alpha): submobject.pointwise_become_partial( - starting_submobject, 0, min(2*alpha, 1) + starting_submobject, 0, min(2 * alpha, 1) ) if alpha < 0.5: if self.stroke_color: - color = 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) + submobject.set_stroke(color, width=self.stroke_width) + submobject.set_fill(opacity=0) else: if not self.reached_halfway_point_before: self.reached_halfway_point_before = True submobject.points = np.array(starting_submobject.points) width, opacity = [ - interpolate(start, end, 2*alpha - 1) + interpolate(start, end, 2 * alpha - 1) for start, end in [ (self.stroke_width, starting_submobject.get_stroke_width()), (0, starting_submobject.get_fill_opacity()) ] ] - submobject.set_stroke(width = width) - submobject.set_fill(opacity = opacity) + submobject.set_stroke(width=width) + submobject.set_fill(opacity=opacity) + +# Fading -#Fading class FadeOut(Transform): CONFIG = { - "remover" : True, + "remover": True, } + def __init__(self, mobject, **kwargs): target = mobject.copy() target.fade(1) Transform.__init__(self, mobject, target, **kwargs) - def clean_up(self, surrounding_scene = None): + def clean_up(self, surrounding_scene=None): Transform.clean_up(self, surrounding_scene) self.update(0) + class FadeIn(Transform): def __init__(self, mobject, **kwargs): target = mobject.copy() Transform.__init__(self, mobject, target, **kwargs) self.starting_mobject.fade(1) if isinstance(self.starting_mobject, VMobject): - self.starting_mobject.set_stroke(width = 0) - self.starting_mobject.set_fill(opacity = 0) + self.starting_mobject.set_stroke(width=0) + self.starting_mobject.set_fill(opacity=0) + class FadeInAndShiftFromDirection(Transform): CONFIG = { - "direction" : DOWN, + "direction": DOWN, } + def __init__(self, mobject, **kwargs): digest_config(self, kwargs) target = mobject.copy() @@ -142,19 +155,23 @@ class FadeInAndShiftFromDirection(Transform): mobject.fade(1) Transform.__init__(self, mobject, target, **kwargs) + class FadeInFromDown(FadeInAndShiftFromDirection): """ Essential a more convenient form of FadeInAndShiftFromDirection """ CONFIG = { - "direction" : DOWN, + "direction": DOWN, } -#Growing +# Growing + + class GrowFromPoint(Transform): CONFIG = { - "point_color" : None, + "point_color": None, } + def __init__(self, mobject, point, **kwargs): digest_config(self, kwargs) target = mobject.copy() @@ -165,19 +182,23 @@ class GrowFromPoint(Transform): mobject.set_color(point_mob.get_color()) Transform.__init__(self, mobject, target, **kwargs) + class GrowFromCenter(GrowFromPoint): def __init__(self, mobject, **kwargs): GrowFromPoint.__init__(self, mobject, mobject.get_center(), **kwargs) + class GrowArrow(GrowFromPoint): def __init__(self, arrow, **kwargs): GrowFromPoint.__init__(self, arrow, arrow.get_start(), **kwargs) + class SpinInFromNothing(GrowFromCenter): CONFIG = { - "path_func" : counterclockwise_path() + "path_func": counterclockwise_path() } + class ShrinkToCenter(Transform): def __init__(self, mobject, **kwargs): Transform.__init__( diff --git a/animation/indication.py b/animation/indication.py index 0a4ad091..5a6f1d4e 100644 --- a/animation/indication.py +++ b/animation/indication.py @@ -8,9 +8,7 @@ from animation.animation import Animation from animation.movement import Homotopy from animation.creation import ShowPartial from animation.transform import Transform -from mobject.mobject import Group from mobject.mobject import Mobject -from mobject.types.vectorized_mobject import VMobject from mobject.geometry import Circle from mobject.geometry import Dot from utils.config_ops import digest_config @@ -20,31 +18,34 @@ from utils.rate_functions import there_and_back class FocusOn(Transform): CONFIG = { - "opacity" : 0.2, - "color" : GREY, - "run_time" : 2, - "remover" : True, + "opacity": 0.2, + "color": GREY, + "run_time": 2, + "remover": True, } + def __init__(self, mobject_or_point, **kwargs): digest_config(self, kwargs) big_dot = Dot( - radius = FRAME_X_RADIUS+FRAME_Y_RADIUS, - stroke_width = 0, - fill_color = self.color, - fill_opacity = 0, + radius=FRAME_X_RADIUS + FRAME_Y_RADIUS, + stroke_width=0, + fill_color=self.color, + fill_opacity=0, ) - little_dot = Dot(radius = 0) - little_dot.set_fill(self.color, opacity = self.opacity) + little_dot = Dot(radius=0) + little_dot.set_fill(self.color, opacity=self.opacity) little_dot.move_to(mobject_or_point) Transform.__init__(self, big_dot, little_dot, **kwargs) + class Indicate(Transform): CONFIG = { - "rate_func" : there_and_back, - "scale_factor" : 1.2, - "color" : YELLOW, + "rate_func": there_and_back, + "scale_factor": 1.2, + "color": YELLOW, } + def __init__(self, mobject, **kwargs): digest_config(self, kwargs) target = mobject.copy() @@ -52,27 +53,31 @@ class Indicate(Transform): target.set_color(self.color) Transform.__init__(self, mobject, target, **kwargs) + class CircleIndicate(Indicate): CONFIG = { - "rate_func" : squish_rate_func(there_and_back, 0, 0.8), - "remover" : True + "rate_func": squish_rate_func(there_and_back, 0, 0.8), + "remover": True } + def __init__(self, mobject, **kwargs): digest_config(self, kwargs) - circle = Circle(color = self.color, **kwargs) + circle = Circle(color=self.color, **kwargs) circle.surround(mobject) Indicate.__init__(self, circle, **kwargs) + class ShowPassingFlash(ShowPartial): CONFIG = { - "time_width" : 0.1, - "remover" : True, + "time_width": 0.1, + "remover": True, } + def get_bounds(self, alpha): - alpha *= (1+self.time_width) - alpha -= self.time_width/2.0 - lower = max(0, alpha - self.time_width/2.0) - upper = min(1, alpha + self.time_width/2.0) + alpha *= (1 + self.time_width) + alpha -= self.time_width / 2.0 + lower = max(0, alpha - self.time_width / 2.0) + upper = min(1, alpha + self.time_width / 2.0) return (lower, upper) def clean_up(self, *args, **kwargs): @@ -80,43 +85,49 @@ class ShowPassingFlash(ShowPartial): for submob, start_submob in self.get_all_families_zipped(): submob.pointwise_become_partial(start_submob, 0, 1) + class ShowCreationThenDestruction(ShowPassingFlash): CONFIG = { - "time_width" : 2.0, - "run_time" : 1, + "time_width": 2.0, + "run_time": 1, } + class ApplyWave(Homotopy): CONFIG = { - "direction" : DOWN, - "amplitude" : 0.2, - "run_time" : 1, - "apply_function_kwargs" : { - "maintain_smoothness" : False, + "direction": DOWN, + "amplitude": 0.2, + "run_time": 1, + "apply_function_kwargs": { + "maintain_smoothness": False, }, } + def __init__(self, mobject, **kwargs): digest_config(self, kwargs, locals()) left_x = mobject.get_left()[0] right_x = mobject.get_right()[0] - vect = self.amplitude*self.direction + 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)) + alpha = (x - left_x) / (right_x - left_x) + power = np.exp(2 * (alpha - 0.5)) nudge = there_and_back(t**power) - return np.array([x, y, z]) + nudge*vect + return np.array([x, y, z]) + nudge * vect Homotopy.__init__(self, homotopy, mobject, **kwargs) + class WiggleOutThenIn(Animation): CONFIG = { - "scale_value" : 1.1, - "rotation_angle" : 0.01*TAU, - "n_wiggles" : 6, - "run_time" : 2, - "scale_about_point" : None, - "rotate_about_point" : None, + "scale_value": 1.1, + "rotation_angle": 0.01 * TAU, + "n_wiggles": 6, + "run_time": 2, + "scale_about_point": None, + "rotate_about_point": None, } + def __init__(self, mobject, **kwargs): digest_config(self, kwargs) if self.scale_about_point is None: @@ -126,62 +137,65 @@ class WiggleOutThenIn(Animation): Animation.__init__(self, mobject, **kwargs) def update_submobject(self, submobject, starting_sumobject, alpha): - submobject.points[:,:] = starting_sumobject.points + submobject.points[:, :] = starting_sumobject.points submobject.scale( interpolate(1, self.scale_value, there_and_back(alpha)), - about_point = self.scale_about_point + about_point=self.scale_about_point ) submobject.rotate( - wiggle(alpha, self.n_wiggles)*self.rotation_angle, - about_point = self.rotate_about_point + wiggle(alpha, self.n_wiggles) * self.rotation_angle, + about_point=self.rotate_about_point ) + class Vibrate(Animation): CONFIG = { - "spatial_period" : 6, - "temporal_period" : 1, - "overtones" : 4, - "amplitude" : 0.5, - "radius" : FRAME_X_RADIUS/2, - "run_time" : 3.0, - "rate_func" : None + "spatial_period": 6, + "temporal_period": 1, + "overtones": 4, + "amplitude": 0.5, + "radius": FRAME_X_RADIUS / 2, + "run_time": 3.0, + "rate_func": None } - def __init__(self, mobject = None, **kwargs): + + def __init__(self, mobject=None, **kwargs): if mobject is None: - mobject = Line(3*LEFT, 3*RIGHT) + mobject = Line(3 * LEFT, 3 * RIGHT) Animation.__init__(self, mobject, **kwargs) def wave_function(self, x, t): return sum([ reduce(op.mul, [ - self.amplitude/(k**2), #Amplitude - np.sin(2*np.pi*(k**1.5)*t/self.temporal_period), #Frequency - np.sin(2*np.pi*k*x/self.spatial_period) #Number of waves + self.amplitude / (k**2), # Amplitude + np.sin(2 * np.pi * (k**1.5) * t / \ + self.temporal_period), # Frequency + # Number of waves + np.sin(2 * np.pi * k * x / self.spatial_period) ]) - for k in range(1, self.overtones+1) + for k in range(1, self.overtones + 1) ]) - def update_mobject(self, alpha): - time = alpha*self.run_time + time = alpha * self.run_time families = map( Mobject.submobject_family, [self.mobject, self.starting_mobject] ) for mob, start in zip(*families): mob.points = np.apply_along_axis( - lambda (x, y, z) : (x, y + self.wave_function(x, time), z), + lambda (x, y, z): (x, y + self.wave_function(x, time), z), 1, start.points ) + class TurnInsideOut(Transform): CONFIG = { - "path_arc" : TAU/4, + "path_arc": TAU / 4, } + 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.sort_points(lambda p: -np.linalg.norm(p)) Transform.__init__(self, mobject, mob_copy, **kwargs) - - diff --git a/animation/movement.py b/animation/movement.py index 201ed779..1045532a 100644 --- a/animation/movement.py +++ b/animation/movement.py @@ -2,22 +2,22 @@ from __future__ import absolute_import from constants import * -import warnings - from animation.animation import Animation from utils.config_ops import digest_config + class Homotopy(Animation): CONFIG = { - "run_time" : 3, - "apply_function_kwargs" : {}, + "run_time": 3, + "apply_function_kwargs": {}, } + def __init__(self, homotopy, mobject, **kwargs): """ Homotopy a function from (x, y, z, t) to (x', y', z') """ def function_at_time_t(t): - return lambda p : homotopy(p[0], p[1], p[2], t) + return lambda p: homotopy(p[0], p[1], p[2], t) self.function_at_time_t = function_at_time_t digest_config(self, kwargs) Animation.__init__(self, mobject, **kwargs) @@ -29,11 +29,13 @@ class Homotopy(Animation): **self.apply_function_kwargs ) + class SmoothedVectorizedHomotopy(Homotopy): def update_submobject(self, submob, start, alpha): Homotopy.update_submobject(self, submob, start, alpha) submob.make_smooth() + class ComplexHomotopy(Homotopy): def __init__(self, complex_homotopy, mobject, **kwargs): """ @@ -48,21 +50,23 @@ class ComplexHomotopy(Homotopy): class PhaseFlow(Animation): CONFIG = { - "virtual_time" : 1, - "rate_func" : None, + "virtual_time": 1, + "rate_func": None, } + def __init__(self, function, mobject, **kwargs): - digest_config(self, kwargs, locals()) + digest_config(self, kwargs, locals()) Animation.__init__(self, mobject, **kwargs) def update_mobject(self, alpha): if hasattr(self, "last_alpha"): - dt = self.virtual_time*(alpha-self.last_alpha) + dt = self.virtual_time * (alpha - self.last_alpha) self.mobject.apply_function( - lambda p : p + dt*self.function(p) + lambda p: p + dt * self.function(p) ) self.last_alpha = alpha + class MoveAlongPath(Animation): def __init__(self, mobject, path, **kwargs): digest_config(self, kwargs, locals()) @@ -71,4 +75,3 @@ class MoveAlongPath(Animation): def update_mobject(self, alpha): point = self.path.point_from_proportion(alpha) self.mobject.move_to(point) - diff --git a/animation/numbers.py b/animation/numbers.py index 6fed7b26..f7b8d250 100644 --- a/animation/numbers.py +++ b/animation/numbers.py @@ -7,13 +7,15 @@ from mobject.numbers import DecimalNumber from utils.bezier import interpolate from utils.config_ops import digest_config + class ChangingDecimal(Animation): CONFIG = { - "num_decimal_points" : None, - "show_ellipsis" : None, - "position_update_func" : None, - "tracked_mobject" : None, + "num_decimal_points": None, + "show_ellipsis": None, + "position_update_func": None, + "tracked_mobject": None, } + def __init__(self, decimal_number_mobject, number_update_func, **kwargs): digest_config(self, kwargs, locals()) self.decimal_number_config = dict( @@ -52,10 +54,14 @@ class ChangingDecimal(Animation): if self.position_update_func is not None: self.position_update_func(self.decimal_number_mobject) elif self.tracked_mobject is not None: - self.decimal_number_mobject.move_to(self.tracked_mobject.get_center() + self.diff_from_tracked_mobject) + self.decimal_number_mobject.move_to( + self.tracked_mobject.get_center() + self.diff_from_tracked_mobject) + class ChangeDecimalToValue(ChangingDecimal): def __init__(self, decimal_number_mobject, target_number, **kwargs): start_number = decimal_number_mobject.number - func = lambda alpha : interpolate(start_number, target_number, alpha) + + def func(alpha): + return interpolate(start_number, target_number, alpha) ChangingDecimal.__init__(self, decimal_number_mobject, func, **kwargs) diff --git a/animation/rotation.py b/animation/rotation.py index f2118741..6b92e5b6 100644 --- a/animation/rotation.py +++ b/animation/rotation.py @@ -1,26 +1,25 @@ from __future__ import absolute_import -import itertools as it import numpy as np from constants import * -import warnings - from animation.animation import Animation from animation.transform import Transform from utils.config_ops import digest_config + class Rotating(Animation): CONFIG = { - "axis" : OUT, - "radians" : 2*np.pi, - "run_time" : 5, - "rate_func" : None, - "in_place" : True, - "about_point" : None, - "about_edge" : None, + "axis": OUT, + "radians": 2 * np.pi, + "run_time": 5, + "rate_func": None, + "in_place": True, + "about_point": None, + "about_edge": None, } + def update_submobject(self, submobject, starting_submobject, alpha): submobject.points = np.array(starting_submobject.points) @@ -28,22 +27,24 @@ class Rotating(Animation): Animation.update_mobject(self, alpha) about_point = None if self.about_point is not None: - about_point = self.about_point - elif self.in_place: #This is superseeded + self.about_point = about_point + elif self.in_place: # This is superseeded self.about_point = self.mobject.get_center() self.mobject.rotate( - alpha*self.radians, - axis = self.axis, - about_point = self.about_point, - about_edge = self.about_edge, + alpha * self.radians, + axis=self.axis, + about_point=self.about_point, + about_edge=self.about_edge, ) + class Rotate(Transform): CONFIG = { - "in_place" : False, - "about_point" : None, + "in_place": False, + "about_point": None, } - def __init__(self, mobject, angle = np.pi, axis = OUT, **kwargs): + + def __init__(self, mobject, angle=np.pi, axis=OUT, **kwargs): if "path_arc" not in kwargs: kwargs["path_arc"] = angle if "path_arc_axis" not in kwargs: @@ -53,9 +54,8 @@ class Rotate(Transform): if self.in_place: self.about_point = mobject.get_center() target.rotate( - angle, - axis = axis, - about_point = self.about_point, + angle, + axis=axis, + about_point=self.about_point, ) Transform.__init__(self, mobject, target, **kwargs) - diff --git a/animation/specialized.py b/animation/specialized.py index c12f85ab..31dc2fa5 100644 --- a/animation/specialized.py +++ b/animation/specialized.py @@ -11,57 +11,60 @@ from mobject.types.vectorized_mobject import VGroup from mobject.geometry import Circle from utils.config_ops import digest_config + class MoveCar(ApplyMethod): CONFIG = { - "moving_forward" : True, + "moving_forward": True, } + def __init__(self, car, target_point, **kwargs): assert isinstance(car, Car) ApplyMethod.__init__(self, car.move_to, target_point, **kwargs) - displacement = self.target_mobject.get_right()-self.starting_mobject.get_right() + displacement = self.target_mobject.get_right() - self.starting_mobject.get_right() distance = np.linalg.norm(displacement) if not self.moving_forward: distance *= -1 - tire_radius = car.get_tires()[0].get_width()/2 - self.total_tire_radians = -distance/tire_radius + tire_radius = car.get_tires()[0].get_width() / 2 + self.total_tire_radians = -distance / tire_radius def update_mobject(self, alpha): ApplyMethod.update_mobject(self, alpha) if alpha == 0: return - radians = alpha*self.total_tire_radians + radians = alpha * self.total_tire_radians for tire in self.mobject.get_tires(): tire.rotate_in_place(radians) + class Broadcast(LaggedStart): CONFIG = { - "small_radius" : 0.0, - "big_radius" : 5, - "n_circles" : 5, - "start_stroke_width" : 8, - "color" : WHITE, - "remover" : True, - "lag_ratio" : 0.7, - "run_time" : 3, - "remover" : True, + "small_radius": 0.0, + "big_radius": 5, + "n_circles": 5, + "start_stroke_width": 8, + "color": WHITE, + "remover": True, + "lag_ratio": 0.7, + "run_time": 3, + "remover": True, } + def __init__(self, focal_point, **kwargs): digest_config(self, kwargs) circles = VGroup() for x in range(self.n_circles): circle = Circle( - radius = self.big_radius, - stroke_color = BLACK, - stroke_width = 0, + radius=self.big_radius, + stroke_color=BLACK, + stroke_width=0, ) circle.move_to(focal_point) circle.save_state() - circle.scale_to_fit_width(self.small_radius*2) + circle.scale_to_fit_width(self.small_radius * 2) circle.set_stroke(self.color, self.start_stroke_width) circles.add(circle) LaggedStart.__init__( self, ApplyMethod, circles, - lambda c : (c.restore,), + lambda c: (c.restore,), **kwargs ) - diff --git a/animation/transform.py b/animation/transform.py index c684e91e..83e56462 100644 --- a/animation/transform.py +++ b/animation/transform.py @@ -17,16 +17,18 @@ from utils.rate_functions import smooth from utils.rate_functions import squish_rate_func from utils.space_ops import complex_to_R3 + class Transform(Animation): CONFIG = { - "path_arc" : 0, - "path_arc_axis" : OUT, - "path_func" : None, - "submobject_mode" : "all_at_once", - "replace_mobject_with_target_in_scene" : False, + "path_arc": 0, + "path_arc_axis": OUT, + "path_func": None, + "submobject_mode": "all_at_once", + "replace_mobject_with_target_in_scene": False, } + def __init__(self, mobject, target_mobject, **kwargs): - #Copy target_mobject so as to not mess with caller + # Copy target_mobject so as to not mess with caller self.original_target_mobject = target_mobject target_mobject = target_mobject.copy() mobject.align_data(target_mobject) @@ -35,7 +37,7 @@ class Transform(Animation): self.init_path_func() Animation.__init__(self, mobject, **kwargs) - self.name += "To" + str(target_mobject) + self.name += "To" + str(target_mobject) def update_config(self, **kwargs): Animation.update_config(self, **kwargs) @@ -63,38 +65,45 @@ class Transform(Animation): submob.interpolate(start, end, alpha, self.path_func) return self - def clean_up(self, surrounding_scene = None): + def clean_up(self, surrounding_scene=None): Animation.clean_up(self, surrounding_scene) if self.replace_mobject_with_target_in_scene and surrounding_scene is not None: surrounding_scene.remove(self.mobject) if not self.remover: surrounding_scene.add(self.original_target_mobject) + class ReplacementTransform(Transform): CONFIG = { - "replace_mobject_with_target_in_scene" : True, + "replace_mobject_with_target_in_scene": True, } + class ClockwiseTransform(Transform): CONFIG = { - "path_arc" : -np.pi + "path_arc": -np.pi } + class CounterclockwiseTransform(Transform): CONFIG = { - "path_arc" : np.pi + "path_arc": np.pi } + class MoveToTarget(Transform): def __init__(self, mobject, **kwargs): if not hasattr(mobject, "target"): - raise Exception("MoveToTarget called on mobject without attribute 'target' ") + raise Exception( + "MoveToTarget called on mobject without attribute 'target' ") Transform.__init__(self, mobject, mobject.target, **kwargs) + class ApplyMethod(Transform): CONFIG = { - "submobject_mode" : "all_at_once" + "submobject_mode": "all_at_once" } + def __init__(self, method, *args, **kwargs): """ Method is a method of Mobject. *args is for the method, @@ -104,11 +113,11 @@ class ApplyMethod(Transform): """ if not inspect.ismethod(method): raise Exception( - "Whoops, looks like you accidentally invoked " + \ - "the method you want to animate" - ) + "Whoops, looks like you accidentally invoked " + + "the method you want to animate" + ) assert(isinstance(method.im_self, Mobject)) - args = list(args) #So that args.pop() works + args = list(args) # So that args.pop() works if "method_kwargs" in kwargs: method_kwargs = kwargs["method_kwargs"] elif len(args) > 0 and isinstance(args[-1], dict): @@ -119,38 +128,46 @@ class ApplyMethod(Transform): method.im_func(target, *args, **method_kwargs) Transform.__init__(self, method.im_self, target, **kwargs) + class ApplyPointwiseFunction(ApplyMethod): CONFIG = { - "run_time" : DEFAULT_POINTWISE_FUNCTION_RUN_TIME + "run_time": DEFAULT_POINTWISE_FUNCTION_RUN_TIME } + def __init__(self, function, mobject, **kwargs): ApplyMethod.__init__( self, mobject.apply_function, function, **kwargs ) + class FadeToColor(ApplyMethod): def __init__(self, mobject, color, **kwargs): ApplyMethod.__init__(self, mobject.set_color, color, **kwargs) + class ScaleInPlace(ApplyMethod): def __init__(self, mobject, scale_factor, **kwargs): - ApplyMethod.__init__(self, mobject.scale_in_place, scale_factor, **kwargs) + ApplyMethod.__init__(self, mobject.scale_in_place, + scale_factor, **kwargs) + class ApplyFunction(Transform): CONFIG = { - "submobject_mode" : "all_at_once", + "submobject_mode": "all_at_once", } + def __init__(self, function, mobject, **kwargs): Transform.__init__( - self, - mobject, + self, + mobject, function(mobject.copy()), **kwargs ) - self.name = "ApplyFunctionTo"+str(mobject) + self.name = "ApplyFunctionTo" + str(mobject) + class ApplyMatrix(ApplyPointwiseFunction): - #Truth be told, I'm not sure if this is useful. + # Truth be told, I'm not sure if this is useful. def __init__(self, matrix, mobject, **kwargs): matrix = np.array(matrix) if matrix.shape == (2, 2): @@ -160,10 +177,12 @@ class ApplyMatrix(ApplyPointwiseFunction): elif matrix.shape != (3, 3): raise "Matrix has bad dimensions" transpose = np.transpose(matrix) + def func(p): return np.dot(p, transpose) ApplyPointwiseFunction.__init__(self, func, mobject, **kwargs) + class ComplexFunction(ApplyPointwiseFunction): def __init__(self, function, mobject, **kwargs): if "path_func" not in kwargs: @@ -172,17 +191,19 @@ class ComplexFunction(ApplyPointwiseFunction): ) ApplyPointwiseFunction.__init__( self, - lambda (x, y, z) : complex_to_R3(function(complex(x, y))), + lambda (x, y, z): complex_to_R3(function(complex(x, y))), instantiate(mobject), **kwargs ) ### + class CyclicReplace(Transform): CONFIG = { - "path_arc" : np.pi/2 + "path_arc": np.pi / 2 } + def __init__(self, *mobjects, **kwargs): start = Group(*mobjects) target = Group(*[ @@ -191,14 +212,18 @@ class CyclicReplace(Transform): ]) Transform.__init__(self, start, target, **kwargs) -class Swap(CyclicReplace): - pass #Renaming, more understandable for two entries -#TODO: Um...does this work +class Swap(CyclicReplace): + pass # Renaming, more understandable for two entries + +# TODO: Um...does this work + + class TransformAnimations(Transform): CONFIG = { - "rate_func" : squish_rate_func(smooth) + "rate_func": squish_rate_func(smooth) } + def __init__(self, start_anim, end_anim, **kwargs): digest_config(self, kwargs, locals()) if "run_time" in kwargs: @@ -207,15 +232,16 @@ class TransformAnimations(Transform): self.run_time = max(start_anim.run_time, end_anim.run_time) for anim in start_anim, end_anim: anim.set_run_time(self.run_time) - + if start_anim.starting_mobject.get_num_points() != end_anim.starting_mobject.get_num_points(): start_anim.starting_mobject.align_data(end_anim.starting_mobject) for anim in start_anim, end_anim: if hasattr(anim, "target_mobject"): anim.starting_mobject.align_data(anim.target_mobject) - Transform.__init__(self, start_anim.mobject, end_anim.mobject, **kwargs) - #Rewire starting and ending mobjects + Transform.__init__(self, start_anim.mobject, + end_anim.mobject, **kwargs) + # Rewire starting and ending mobjects start_anim.mobject = self.starting_mobject end_anim.mobject = self.target_mobject @@ -223,4 +249,3 @@ class TransformAnimations(Transform): self.start_anim.update(alpha) self.end_anim.update(alpha) Transform.update(self, alpha) - diff --git a/animation/update.py b/animation/update.py index ff3ec543..fcef3e9b 100644 --- a/animation/update.py +++ b/animation/update.py @@ -12,6 +12,7 @@ class UpdateFromFunc(Animation): to be used when the state of one mobject is dependent on another simultaneously animated mobject """ + def __init__(self, mobject, update_function, **kwargs): digest_config(self, kwargs, locals()) Animation.__init__(self, mobject, **kwargs) @@ -19,29 +20,27 @@ class UpdateFromFunc(Animation): def update_mobject(self, alpha): self.update_function(self.mobject) + class UpdateFromAlphaFunc(UpdateFromFunc): def update_mobject(self, alpha): self.update_function(self.mobject, alpha) + class MaintainPositionRelativeTo(Animation): CONFIG = { - "tracked_critical_point" : ORIGIN + "tracked_critical_point": ORIGIN } + def __init__(self, mobject, tracked_mobject, **kwargs): digest_config(self, kwargs, locals()) tcp = self.tracked_critical_point self.diff = mobject.get_critical_point(tcp) - \ - tracked_mobject.get_critical_point(tcp) + tracked_mobject.get_critical_point(tcp) Animation.__init__(self, mobject, **kwargs) def update_mobject(self, alpha): self.mobject.shift( - self.tracked_mobject.get_critical_point(self.tracked_critical_point) - \ - self.mobject.get_critical_point(self.tracked_critical_point) + \ + self.tracked_mobject.get_critical_point(self.tracked_critical_point) - + self.mobject.get_critical_point(self.tracked_critical_point) + self.diff ) - - - - - diff --git a/big_ol_pile_of_manim_imports.py b/big_ol_pile_of_manim_imports.py index 58ead133..9e941ec7 100644 --- a/big_ol_pile_of_manim_imports.py +++ b/big_ol_pile_of_manim_imports.py @@ -1,12 +1,12 @@ """ I won't pretend like this is best practice, by in creating animations for a video, -it can be very nice to simply have all of the Mobjects, Animations, Scenes, etc. +it can be very nice to simply have all of the Mobjects, Animations, Scenes, etc. of manim available without having to worry about what namespace they come from. Rather than having a large pile of "from import *" at the top of every such -script, the intent of this file is to make it so that one can just include +script, the intent of this file is to make it so that one can just include "from big_ol_pile_of_manim_imports import *". The effects of adding more modules -or refactoring the library on current or older scene scripts should be entirely +or refactoring the library on current or older scene scripts should be entirely addressible by changing this file. Note: One should NOT import from this file for main library code, it is meant only diff --git a/camera/camera.py b/camera/camera.py index c9f4dab0..e8ccb6bb 100644 --- a/camera/camera.py +++ b/camera/camera.py @@ -1,6 +1,5 @@ import itertools as it import numpy as np -import os import aggdraw import copy @@ -23,28 +22,29 @@ from utils.iterables import list_difference_update from utils.iterables import remove_list_redundancies from utils.simple_functions import fdiv + class Camera(object): CONFIG = { - "background_image" : None, - "pixel_shape" : (DEFAULT_PIXEL_HEIGHT, DEFAULT_PIXEL_WIDTH), + "background_image": None, + "pixel_shape": (DEFAULT_PIXEL_HEIGHT, DEFAULT_PIXEL_WIDTH), # Note: frame_shape will be resized to match pixel_shape - "frame_shape" : (FRAME_HEIGHT, FRAME_WIDTH), - "space_center" : ORIGIN, - "background_color" : BLACK, - #Points in vectorized mobjects with norm greater - #than this value will be rescaled. - "max_allowable_norm" : FRAME_WIDTH, - "image_mode" : "RGBA", - "n_rgb_coords" : 4, - "background_alpha" : 0, #Out of rgb_max_val - "pixel_array_dtype" : 'uint8', - "use_z_coordinate_for_display_order" : False, + "frame_shape": (FRAME_HEIGHT, FRAME_WIDTH), + "space_center": ORIGIN, + "background_color": BLACK, + # Points in vectorized mobjects with norm greater + # than this value will be rescaled. + "max_allowable_norm": FRAME_WIDTH, + "image_mode": "RGBA", + "n_rgb_coords": 4, + "background_alpha": 0, # Out of rgb_max_val + "pixel_array_dtype": 'uint8', + "use_z_coordinate_for_display_order": False, # z_buff_func is only used if the flag above is set to True. # round z coordinate to nearest hundredth when comparring - "z_buff_func" : lambda m : np.round(m.get_center()[2], 2), + "z_buff_func": lambda m: np.round(m.get_center()[2], 2), } - def __init__(self, background = None, **kwargs): + def __init__(self, background=None, **kwargs): digest_config(self, kwargs, locals()) self.rgb_max_val = np.iinfo(self.pixel_array_dtype).max self.init_background() @@ -55,22 +55,22 @@ class Camera(object): # This is to address a strange bug where deepcopying # will result in a segfault, which is somehow related # to the aggdraw library - self.canvas = None + self.canvas = None return copy.copy(self) - def resize_frame_shape(self, fixed_dimension = 0): + def resize_frame_shape(self, fixed_dimension=0): """ - Changes frame_shape to match the aspect ratio + Changes frame_shape to match the aspect ratio of pixel_shape, where fixed_dimension determines whether frame_shape[0] (height) or frame_shape[1] (width) remains fixed while the other changes accordingly. """ - aspect_ratio = float(self.pixel_shape[1])/self.pixel_shape[0] + aspect_ratio = float(self.pixel_shape[1]) / self.pixel_shape[0] frame_width, frame_height = self.frame_shape if fixed_dimension == 0: - frame_height = aspect_ratio*frame_width + frame_height = aspect_ratio * frame_width else: - frame_width = frame_height/aspect_ratio + frame_width = frame_height / aspect_ratio self.frame_shape = (frame_width, frame_height) def init_background(self): @@ -78,53 +78,56 @@ class Camera(object): path = get_full_raster_image_path(self.background_image) image = Image.open(path).convert(self.image_mode) height, width = self.pixel_shape - #TODO, how to gracefully handle backgrounds - #with different sizes? + # TODO, how to gracefully handle backgrounds + # with different sizes? self.background = np.array(image)[:height, :width] self.background = self.background.astype(self.pixel_array_dtype) else: background_rgba = color_to_int_rgba( - self.background_color, alpha = self.background_alpha + self.background_color, alpha=self.background_alpha ) self.background = np.zeros( - list(self.pixel_shape)+[self.n_rgb_coords], - dtype = self.pixel_array_dtype + list(self.pixel_shape) + [self.n_rgb_coords], + dtype=self.pixel_array_dtype ) - self.background[:,:] = background_rgba + self.background[:, :] = background_rgba def get_image(self): return Image.fromarray( self.pixel_array, - mode = self.image_mode + mode=self.image_mode ) def get_pixel_array(self): return self.pixel_array - def convert_pixel_array(self, pixel_array, convert_from_floats = False): + def convert_pixel_array(self, pixel_array, convert_from_floats=False): retval = np.array(pixel_array) if convert_from_floats: retval = np.apply_along_axis( - lambda f : (f * self.rgb_max_val).astype(self.pixel_array_dtype), + lambda f: ( + f * self.rgb_max_val).astype(self.pixel_array_dtype), 2, retval) return retval - def set_pixel_array(self, pixel_array, convert_from_floats = False): - converted_array = self.convert_pixel_array(pixel_array, convert_from_floats) - if not (hasattr(self, "pixel_array") and self.pixel_array.shape == converted_array.shape): + def set_pixel_array(self, pixel_array, convert_from_floats=False): + converted_array = self.convert_pixel_array( + pixel_array, convert_from_floats) + if not (hasattr(self, "pixel_array") and self.pixel_array.shape == converted_array.shape): self.pixel_array = converted_array else: - #Set in place - self.pixel_array[:,:,:] = converted_array[:,:,:] + # Set in place + self.pixel_array[:, :, :] = converted_array[:, :, :] - def set_background(self, pixel_array, convert_from_floats = False): - self.background = self.convert_pixel_array(pixel_array, convert_from_floats) + def set_background(self, pixel_array, convert_from_floats=False): + self.background = self.convert_pixel_array( + pixel_array, convert_from_floats) def make_background_from_func(self, coords_to_colors_func): """ - Sets background by using coords_to_colors_func to determine each pixel's color. Each input - to coords_to_colors_func is an (x, y) pair in space (in ordinary space coordinates; not + Sets background by using coords_to_colors_func to determine each pixel's color. Each input + to coords_to_colors_func is an (x, y) pair in space (in ordinary space coordinates; not pixel coordinates), and each output is expected to be an RGBA array of 4 floats. """ @@ -137,17 +140,18 @@ class Camera(object): ) print "Ending set_background; for reference, the current time is ", time.strftime("%H:%M:%S") - return self.convert_pixel_array(new_background, convert_from_floats = True) + return self.convert_pixel_array(new_background, convert_from_floats=True) def set_background_from_func(self, coords_to_colors_func): - self.set_background(self.make_background_from_func(coords_to_colors_func)) + self.set_background( + self.make_background_from_func(coords_to_colors_func)) def reset(self): self.set_pixel_array(self.background) #### - def extract_mobject_family_members(self, mobjects, only_those_with_points = False): + def extract_mobject_family_members(self, mobjects, only_those_with_points=False): if only_those_with_points: method = Mobject.family_members_with_points else: @@ -161,13 +165,13 @@ class Camera(object): )) def get_mobjects_to_display( - self, mobjects, - include_submobjects = True, - excluded_mobjects = None, - ): + self, mobjects, + include_submobjects=True, + excluded_mobjects=None, + ): if include_submobjects: mobjects = self.extract_mobject_family_members( - mobjects, only_those_with_points = True + mobjects, only_those_with_points=True ) if excluded_mobjects: all_excluded = self.extract_mobject_family_members( @@ -192,14 +196,15 @@ class Camera(object): def capture_mobjects(self, mobjects, **kwargs): mobjects = self.get_mobjects_to_display(mobjects, **kwargs) - # Organize this list into batches of the same type, and + # Organize this list into batches of the same type, and # apply corresponding function to those batches type_func_pairs = [ (VMobject, self.display_multiple_vectorized_mobjects), - (PMobject, self.display_multiple_point_cloud_mobjects), - (ImageMobject, self.display_multiple_image_mobjects), - (Mobject, lambda batch : batch), #Do nothing + (PMobject, self.display_multiple_point_cloud_mobjects), + (ImageMobject, self.display_multiple_image_mobjects), + (Mobject, lambda batch: batch), # Do nothing ] + def get_mobject_type(mobject): for mobject_type, func in type_func_pairs: if isinstance(mobject, mobject_type): @@ -209,14 +214,14 @@ class Camera(object): ) batch_type_pairs = batch_by_property(mobjects, get_mobject_type) - #Display in these batches + # Display in these batches for batch, batch_type in batch_type_pairs: - #check what the type is, and call the appropriate function + # check what the type is, and call the appropriate function for mobject_type, func in type_func_pairs: if batch_type == mobject_type: func(batch) - ## Methods associated with svg rendering + # Methods associated with svg rendering def get_aggdraw_canvas(self): if not hasattr(self, "canvas") or not self.canvas: @@ -224,15 +229,15 @@ class Camera(object): return self.canvas def reset_aggdraw_canvas(self): - image = Image.fromarray(self.pixel_array, mode = self.image_mode) + image = Image.fromarray(self.pixel_array, mode=self.image_mode) self.canvas = aggdraw.Draw(image) def display_multiple_vectorized_mobjects(self, vmobjects): if len(vmobjects) == 0: return batch_file_pairs = batch_by_property( - vmobjects, - lambda vm : vm.get_background_image_file() + vmobjects, + lambda vm: vm.get_background_image_file() ) for batch, file_name in batch_file_pairs: if file_name: @@ -247,10 +252,10 @@ class Camera(object): self.display_vectorized(vmobject, canvas) canvas.flush() - def display_vectorized(self, vmobject, canvas = None): + def display_vectorized(self, vmobject, canvas=None): if vmobject.is_subpath: - #Subpath vectorized mobjects are taken care - #of by their parent + # Subpath vectorized mobjects are taken care + # of by their parent return canvas = canvas or self.get_aggdraw_canvas() pen, fill = self.get_pen_and_fill(vmobject) @@ -267,7 +272,7 @@ class Camera(object): stroke_hex = rgb_to_hex(stroke_rgb) pen = aggdraw.Pen(stroke_hex, stroke_width) - fill_opacity = int(self.rgb_max_val*vmobject.get_fill_opacity()) + fill_opacity = int(self.rgb_max_val * vmobject.get_fill_opacity()) if fill_opacity == 0: fill = None else: @@ -291,27 +296,28 @@ class Camera(object): def get_pathstring(self, vmobject): result = "" - for mob in [vmobject]+vmobject.get_subpath_mobjects(): + for mob in [vmobject] + vmobject.get_subpath_mobjects(): points = mob.points - # points = self.adjust_out_of_range_points(points) + # points = self.adjust_out_of_range_points(points) if len(points) == 0: continue aligned_points = self.align_points_to_camera(points) coords = self.points_to_pixel_coords(aligned_points) coord_strings = coords.flatten().astype(str) - #Start new path string with M + # Start new path string with M coord_strings[0] = "M" + coord_strings[0] - #The C at the start of every 6th number communicates - #that the following 6 define a cubic Bezier - coord_strings[2::6] = map(lambda s : "C" + str(s), coord_strings[2::6]) - #Possibly finish with "Z" + # The C at the start of every 6th number communicates + # that the following 6 define a cubic Bezier + coord_strings[2::6] = map( + lambda s: "C" + str(s), coord_strings[2::6]) + # Possibly finish with "Z" if vmobject.mark_paths_closed: coord_strings[-1] = coord_strings[-1] + " Z" result += " ".join(coord_strings) return result def get_background_colored_vmobject_displayer(self): - #Quite wordy to type out a bunch + # Quite wordy to type out a bunch long_name = "background_colored_vmobject_displayer" if not hasattr(self, long_name): setattr(self, long_name, BackgroundColoredVMobjectDisplayer(self)) @@ -323,7 +329,7 @@ class Camera(object): self.overlay_rgba_array(cvmobject_pixel_array) return self - ## Methods for other rendering + # Methods for other rendering def display_multiple_point_cloud_mobjects(self, pmobjects): for pmobject in pmobjects: @@ -343,23 +349,23 @@ class Camera(object): ) rgba_len = self.pixel_array.shape[2] - rgbas = (self.rgb_max_val*rgbas).astype(self.pixel_array_dtype) + rgbas = (self.rgb_max_val * rgbas).astype(self.pixel_array_dtype) target_len = len(pixel_coords) - factor = target_len/len(rgbas) - rgbas = np.array([rgbas]*factor).reshape((target_len, rgba_len)) + factor = target_len / len(rgbas) + rgbas = np.array([rgbas] * factor).reshape((target_len, rgba_len)) - on_screen_indices = self.on_screen_pixels(pixel_coords) - pixel_coords = pixel_coords[on_screen_indices] + on_screen_indices = self.on_screen_pixels(pixel_coords) + pixel_coords = pixel_coords[on_screen_indices] rgbas = rgbas[on_screen_indices] ph, pw = self.pixel_shape - flattener = np.array([1, pw], dtype = 'int') + flattener = np.array([1, pw], dtype='int') flattener = flattener.reshape((2, 1)) - indices = np.dot(pixel_coords, flattener)[:,0] + indices = np.dot(pixel_coords, flattener)[:, 0] indices = indices.astype('int') - - new_pa = self.pixel_array.reshape((ph*pw, rgba_len)) + + new_pa = self.pixel_array.reshape((ph * pw, rgba_len)) new_pa[indices] = rgbas self.pixel_array = new_pa.reshape((ph, pw, rgba_len)) @@ -375,47 +381,46 @@ class Camera(object): impa = image_mobject.pixel_array - oh, ow = self.pixel_array.shape[:2] #Outer width and height - ih, iw = impa.shape[:2] #inner with and height + oh, ow = self.pixel_array.shape[:2] # Outer width and height + ih, iw = impa.shape[:2] # inner with and height rgb_len = self.pixel_array.shape[2] - image = np.zeros((oh, ow, rgb_len), dtype = self.pixel_array_dtype) + image = np.zeros((oh, ow, rgb_len), dtype=self.pixel_array_dtype) if right_vect[1] == 0 and down_vect[0] == 0: rv0 = right_vect[0] dv1 = down_vect[1] - x_indices = np.arange(rv0, dtype = 'int')*iw/rv0 - y_indices = np.arange(dv1, dtype = 'int')*ih/dv1 - stretched_impa = impa[y_indices][:,x_indices] + x_indices = np.arange(rv0, dtype='int') * iw / rv0 + y_indices = np.arange(dv1, dtype='int') * ih / dv1 + stretched_impa = impa[y_indices][:, x_indices] - x0, x1 = ul_coords[0], ur_coords[0] + x0, x1 = ul_coords[0], ur_coords[0] y0, y1 = ul_coords[1], dl_coords[1] if x0 >= ow or x1 < 0 or y0 >= oh or y1 < 0: return - siy0 = max(-y0, 0) #stretched_impa y0 - siy1 = dv1 - max(y1-oh, 0) + siy0 = max(-y0, 0) # stretched_impa y0 + siy1 = dv1 - max(y1 - oh, 0) six0 = max(-x0, 0) - six1 = rv0 - max(x1-ow, 0) + six1 = rv0 - max(x1 - ow, 0) x0 = max(x0, 0) y0 = max(y0, 0) image[y0:y1, x0:x1] = stretched_impa[siy0:siy1, six0:six1] else: # Alternate (slower) tactic if image is tilted - # List of all coordinates of pixels, given as (x, y), + # List of all coordinates of pixels, given as (x, y), # which matches the return type of points_to_pixel_coords, # even though np.array indexing naturally happens as (y, x) - all_pixel_coords = np.zeros((oh*ow, 2), dtype = 'int') - a = np.arange(oh*ow, dtype = 'int') - all_pixel_coords[:,0] = a%ow - all_pixel_coords[:,1] = a/ow + all_pixel_coords = np.zeros((oh * ow, 2), dtype='int') + a = np.arange(oh * ow, dtype='int') + all_pixel_coords[:, 0] = a % ow + all_pixel_coords[:, 1] = a / ow recentered_coords = all_pixel_coords - ul_coords - coord_norms = np.linalg.norm(recentered_coords, axis = 1) - with np.errstate(divide = 'ignore'): + with np.errstate(divide='ignore'): ix_coords, iy_coords = [ np.divide( - dim*np.dot(recentered_coords, vect), + dim * np.dot(recentered_coords, vect), np.dot(vect, vect), ) for vect, dim in (right_vect, iw), (down_vect, ih) @@ -424,12 +429,12 @@ class Camera(object): ix_coords >= 0, ix_coords < iw, iy_coords >= 0, iy_coords < ih, ]) - n_to_change = np.sum(to_change) - inner_flat_coords = iw*iy_coords[to_change] + ix_coords[to_change] - flat_impa = impa.reshape((iw*ih, rgb_len)) + inner_flat_coords = iw * \ + iy_coords[to_change] + ix_coords[to_change] + flat_impa = impa.reshape((iw * ih, rgb_len)) target_rgbas = flat_impa[inner_flat_coords, :] - image = image.reshape((ow*oh, rgb_len)) + image = image.reshape((ow * oh, rgb_len)) image[to_change] = target_rgbas image = image.reshape((oh, ow, rgb_len)) self.overlay_rgba_array(image) @@ -439,27 +444,27 @@ class Camera(object): bg = self.pixel_array # rgba_max_val = self.rgb_max_val src_rgb, src_a, dst_rgb, dst_a = [ - a.astype(np.float32)/self.rgb_max_val - for a in fg[...,:3], fg[...,3], bg[...,:3], bg[...,3] + a.astype(np.float32) / self.rgb_max_val + for a in fg[..., :3], fg[..., 3], bg[..., :3], bg[..., 3] ] - out_a = src_a + dst_a*(1.0-src_a) + out_a = src_a + dst_a * (1.0 - src_a) - # When the output alpha is 0 for full transparency, + # When the output alpha is 0 for full transparency, # we have a choice over what RGB value to use in our # output representation. We choose 0 here. out_rgb = fdiv( - src_rgb*src_a[..., None] + \ - dst_rgb*dst_a[..., None]*(1.0-src_a[..., None]), + src_rgb * src_a[..., None] + + dst_rgb * dst_a[..., None] * (1.0 - src_a[..., None]), out_a[..., None], - zero_over_zero_value = 0 + zero_over_zero_value=0 ) - self.pixel_array[..., :3] = out_rgb*self.rgb_max_val - self.pixel_array[..., 3] = out_a*self.rgb_max_val + self.pixel_array[..., :3] = out_rgb * self.rgb_max_val + self.pixel_array[..., 3] = out_a * self.rgb_max_val def align_points_to_camera(self, points): - ## This is where projection should live + # This is where projection should live return points - self.space_center def adjust_out_of_range_points(self, points): @@ -467,10 +472,10 @@ class Camera(object): return points norms = np.apply_along_axis(np.linalg.norm, 1, points) violator_indices = norms > self.max_allowable_norm - violators = points[violator_indices,:] + violators = points[violator_indices, :] violator_norms = norms[violator_indices] reshaped_norms = np.repeat( - violator_norms.reshape((len(violator_norms), 1)), + violator_norms.reshape((len(violator_norms), 1)), points.shape[1], 1 ) rescaled = self.max_allowable_norm * violators / reshaped_norms @@ -481,32 +486,32 @@ class Camera(object): result = np.zeros((len(points), 2)) ph, pw = self.pixel_shape sh, sw = self.frame_shape - width_mult = pw/sw - width_add = pw/2 - height_mult = ph/sh - height_add = ph/2 - #Flip on y-axis as you go + width_mult = pw / sw + width_add = pw / 2 + height_mult = ph / sh + height_add = ph / 2 + # Flip on y-axis as you go height_mult *= -1 - result[:,0] = points[:,0]*width_mult + width_add - result[:,1] = points[:,1]*height_mult + height_add + result[:, 0] = points[:, 0] * width_mult + width_add + result[:, 1] = points[:, 1] * height_mult + height_add return result.astype('int') def on_screen_pixels(self, pixel_coords): return reduce(op.and_, [ - pixel_coords[:,0] >= 0, - pixel_coords[:,0] < self.pixel_shape[1], - pixel_coords[:,1] >= 0, - pixel_coords[:,1] < self.pixel_shape[0], + pixel_coords[:, 0] >= 0, + pixel_coords[:, 0] < self.pixel_shape[1], + pixel_coords[:, 1] >= 0, + pixel_coords[:, 1] < self.pixel_shape[0], ]) def adjusted_thickness(self, thickness): big_shape = PRODUCTION_QUALITY_CAMERA_CONFIG["pixel_shape"] - factor = sum(big_shape)/sum(self.pixel_shape) - return 1 + (thickness-1)/factor + factor = sum(big_shape) / sum(self.pixel_shape) + return 1 + (thickness - 1) / factor def get_thickening_nudges(self, thickness): - _range = range(-thickness/2+1, thickness/2+1) + _range = range(-thickness / 2 + 1, thickness / 2 + 1) return np.array(list(it.product(_range, _range))) def thickened_coordinates(self, pixel_coords, thickness): @@ -516,7 +521,7 @@ class Camera(object): for nudge in nudges ]) size = pixel_coords.size - return pixel_coords.reshape((size/2, 2)) + return pixel_coords.reshape((size / 2, 2)) def get_coords_of_all_pixels(self): # These are in x, y order, to help me keep things straight @@ -525,23 +530,26 @@ class Camera(object): # These are addressed in the same y, x order as in pixel_array, but the values in them # are listed in x, y order - uncentered_pixel_coords = np.indices(self.pixel_shape)[::-1].transpose(1, 2, 0) + uncentered_pixel_coords = np.indices(self.pixel_shape)[ + ::-1].transpose(1, 2, 0) uncentered_space_coords = fdiv( - uncentered_pixel_coords * full_space_dims, + uncentered_pixel_coords * full_space_dims, full_pixel_dims) - # Could structure above line's computation slightly differently, but figured (without much - # thought) multiplying by frame_shape first, THEN dividing by pixel_shape, is probably - # better than the other order, for avoiding underflow quantization in the division (whereas + # Could structure above line's computation slightly differently, but figured (without much + # thought) multiplying by frame_shape first, THEN dividing by pixel_shape, is probably + # better than the other order, for avoiding underflow quantization in the division (whereas # overflow is unlikely to be a problem) - centered_space_coords = (uncentered_space_coords - fdiv(full_space_dims, 2)) + centered_space_coords = ( + uncentered_space_coords - fdiv(full_space_dims, 2)) - # Have to also flip the y coordinates to account for pixel array being listed in + # Have to also flip the y coordinates to account for pixel array being listed in # top-to-bottom order, opposite of screen coordinate convention centered_space_coords = centered_space_coords * (1, -1) return centered_space_coords + class BackgroundColoredVMobjectDisplayer(object): def __init__(self, camera): self.camera = camera @@ -551,20 +559,20 @@ class BackgroundColoredVMobjectDisplayer(object): def init_canvas(self): self.pixel_array = np.zeros( self.camera.pixel_array.shape, - dtype = self.camera.pixel_array_dtype, + dtype=self.camera.pixel_array_dtype, ) self.reset_canvas() def reset_canvas(self): - image = Image.fromarray(self.pixel_array, mode = self.camera.image_mode) + image = Image.fromarray(self.pixel_array, mode=self.camera.image_mode) self.canvas = aggdraw.Draw(image) def resize_background_array( - self, background_array, - new_width, new_height, - mode = "RGBA" - ): - image = Image.fromarray(background_array, mode = mode) + self, background_array, + new_width, new_height, + mode="RGBA" + ): + image = Image.fromarray(background_array, mode=mode) resized_image = image.resize((new_width, new_height)) return np.array(resized_image) @@ -582,14 +590,15 @@ class BackgroundColoredVMobjectDisplayer(object): camera = self.camera if not np.all(camera.pixel_array.shape == array.shape): - array = self.resize_background_array_to_match(array, camera.pixel_array) + array = self.resize_background_array_to_match( + array, camera.pixel_array) self.file_name_to_pixel_array_map[file_name] = array return array def display(self, *cvmobjects): batch_image_file_pairs = batch_by_property( - cvmobjects, lambda cv : cv.get_background_image_file() + cvmobjects, lambda cv: cv.get_background_image_file() ) curr_array = None for batch, image_file in batch_image_file_pairs: @@ -598,14 +607,13 @@ class BackgroundColoredVMobjectDisplayer(object): self.camera.display_vectorized(cvmobject, self.canvas) self.canvas.flush() new_array = np.array( - (background_array*self.pixel_array.astype('float')/255), - dtype = self.camera.pixel_array_dtype + (background_array * self.pixel_array.astype('float') / 255), + dtype=self.camera.pixel_array_dtype ) if curr_array is None: curr_array = new_array else: curr_array = np.maximum(curr_array, new_array) - self.pixel_array[:,:] = 0 + self.pixel_array[:, :] = 0 self.reset_canvas() return curr_array - diff --git a/camera/mapping_camera.py b/camera/mapping_camera.py index 66e38adb..b55d4bc0 100644 --- a/camera/mapping_camera.py +++ b/camera/mapping_camera.py @@ -1,21 +1,26 @@ from __future__ import absolute_import +import numpy as np + from camera.camera import Camera +from mobject.types.vectorized_mobject import VMobject from utils.config_ops import DictAsObject from utils.config_ops import digest_config -# TODO: Add an attribute to mobjects under which they can specify that they should just +# TODO: Add an attribute to mobjects under which they can specify that they should just # map their centers but remain otherwise undistorted (useful for labels, etc.) + + class MappingCamera(Camera): CONFIG = { - "mapping_func" : lambda p : p, - "min_anchor_points" : 50, - "allow_object_intrusion" : False + "mapping_func": lambda p: p, + "min_anchor_points": 50, + "allow_object_intrusion": False } def points_to_pixel_coords(self, points): return Camera.points_to_pixel_coords(self, np.apply_along_axis(self.mapping_func, 1, points)) - + def capture_mobjects(self, mobjects, **kwargs): mobjects = self.get_mobjects_to_display(mobjects, **kwargs) if self.allow_object_intrusion: @@ -24,30 +29,32 @@ class MappingCamera(Camera): mobject_copies = [mobject.copy() for mobject in mobjects] for mobject in mobject_copies: if isinstance(mobject, VMobject) and \ - 0 < mobject.get_num_anchor_points() < self.min_anchor_points: + 0 < mobject.get_num_anchor_points() < self.min_anchor_points: mobject.insert_n_anchor_points(self.min_anchor_points) Camera.capture_mobjects( - self, mobject_copies, - include_submobjects = False, - excluded_mobjects = None, + self, mobject_copies, + include_submobjects=False, + excluded_mobjects=None, ) # Note: This allows layering of multiple cameras onto the same portion of the pixel array, # the later cameras overwriting the former # -# TODO: Add optional separator borders between cameras (or perhaps peel this off into a +# TODO: Add optional separator borders between cameras (or perhaps peel this off into a # CameraPlusOverlay class) + + class MultiCamera(Camera): def __init__(self, *cameras_with_start_positions, **kwargs): self.shifted_cameras = [ DictAsObject( - { - "camera" : camera_with_start_positions[0], - "start_x" : camera_with_start_positions[1][1], - "start_y" : camera_with_start_positions[1][0], - "end_x" : camera_with_start_positions[1][1] + camera_with_start_positions[0].pixel_shape[1], - "end_y" : camera_with_start_positions[1][0] + camera_with_start_positions[0].pixel_shape[0], - }) + { + "camera": camera_with_start_positions[0], + "start_x": camera_with_start_positions[1][1], + "start_y": camera_with_start_positions[1][0], + "end_x": camera_with_start_positions[1][1] + camera_with_start_positions[0].pixel_shape[1], + "end_y": camera_with_start_positions[1][0] + camera_with_start_positions[0].pixel_shape[0], + }) for camera_with_start_positions in cameras_with_start_positions ] Camera.__init__(self, **kwargs) @@ -55,17 +62,17 @@ class MultiCamera(Camera): def capture_mobjects(self, mobjects, **kwargs): for shifted_camera in self.shifted_cameras: shifted_camera.camera.capture_mobjects(mobjects, **kwargs) - + self.pixel_array[ - shifted_camera.start_y:shifted_camera.end_y, + shifted_camera.start_y:shifted_camera.end_y, shifted_camera.start_x:shifted_camera.end_x] \ - = shifted_camera.camera.pixel_array + = shifted_camera.camera.pixel_array def set_background(self, pixel_array, **kwargs): for shifted_camera in self.shifted_cameras: shifted_camera.camera.set_background( pixel_array[ - shifted_camera.start_y:shifted_camera.end_y, + shifted_camera.start_y:shifted_camera.end_y, shifted_camera.start_x:shifted_camera.end_x], **kwargs ) @@ -75,7 +82,7 @@ class MultiCamera(Camera): for shifted_camera in self.shifted_cameras: shifted_camera.camera.set_pixel_array( pixel_array[ - shifted_camera.start_y:shifted_camera.end_y, + shifted_camera.start_y:shifted_camera.end_y, shifted_camera.start_x:shifted_camera.end_x], **kwargs ) @@ -87,18 +94,21 @@ class MultiCamera(Camera): # A MultiCamera which, when called with two full-size cameras, initializes itself # as a splitscreen, also taking care to resize each individual camera within it + + class SplitScreenCamera(MultiCamera): def __init__(self, left_camera, right_camera, **kwargs): digest_config(self, kwargs) self.left_camera = left_camera self.right_camera = right_camera - + half_width = self.pixel_shape[1] / 2 for camera in [self.left_camera, self.right_camera]: - camera.pixel_shape = (self.pixel_shape[0], half_width) # TODO: Round up on one if width is odd + # TODO: Round up on one if width is odd + camera.pixel_shape = (self.pixel_shape[0], half_width) camera.init_background() camera.resize_frame_shape() camera.reset() - MultiCamera.__init__(self, (left_camera, (0, 0)), (right_camera, (0, half_width))) - + MultiCamera.__init__(self, (left_camera, (0, 0)), + (right_camera, (0, half_width))) diff --git a/camera/moving_camera.py b/camera/moving_camera.py index f02c4012..eb68fdde 100644 --- a/camera/moving_camera.py +++ b/camera/moving_camera.py @@ -2,17 +2,19 @@ from __future__ import absolute_import from camera.camera import Camera + class MovingCamera(Camera): """ Stays in line with the height, width and position of a given mobject """ CONFIG = { - "aligned_dimension" : "width" #or height + "aligned_dimension": "width" # or height } + def __init__(self, frame, **kwargs): """ - frame is a Mobject, (should be a rectangle) determining + frame is a Mobject, (should be a rectangle) determining which region of space the camera displys """ self.frame = frame diff --git a/camera/three_d_camera.py b/camera/three_d_camera.py index 8054bef9..84ddece8 100644 --- a/camera/three_d_camera.py +++ b/camera/three_d_camera.py @@ -12,39 +12,43 @@ from utils.bezier import interpolate from utils.space_ops import rotation_about_z from utils.space_ops import rotation_matrix + class CameraWithPerspective(Camera): CONFIG = { - "camera_distance" : 20, + "camera_distance": 20, } + def points_to_pixel_coords(self, points): distance_ratios = np.divide( self.camera_distance, - self.camera_distance - points[:,2] + self.camera_distance - points[:, 2] ) scale_factors = interpolate(0, 1, distance_ratios) adjusted_points = np.array(points) for i in 0, 1: - adjusted_points[:,i] *= scale_factors + adjusted_points[:, i] *= scale_factors return Camera.points_to_pixel_coords(self, adjusted_points) + class ThreeDCamera(CameraWithPerspective): CONFIG = { - "sun_vect" : 5*UP+LEFT, - "shading_factor" : 0.2, - "distance" : 5., - "default_distance" : 5., - "phi" : 0, #Angle off z axis - "theta" : -TAU/4, #Rotation about z axis + "sun_vect": 5 * UP + LEFT, + "shading_factor": 0.2, + "distance": 5., + "default_distance": 5., + "phi": 0, # Angle off z axis + "theta": -TAU / 4, # Rotation about z axis } + def __init__(self, *args, **kwargs): Camera.__init__(self, *args, **kwargs) - self.unit_sun_vect = self.sun_vect/np.linalg.norm(self.sun_vect) - ## rotation_mobject lives in the phi-theta-distance space - ## TODO, use ValueTracker for this instead + self.unit_sun_vect = self.sun_vect / np.linalg.norm(self.sun_vect) + # rotation_mobject lives in the phi-theta-distance space + # TODO, use ValueTracker for this instead self.rotation_mobject = VectorizedPoint() - ## moving_center lives in the x-y-z space - ## It representes the center of rotation + # moving_center lives in the x-y-z space + # It representes the center of rotation self.moving_center = VectorizedPoint(self.space_center) self.set_position(self.phi, self.theta, self.distance) @@ -63,30 +67,31 @@ class ThreeDCamera(CameraWithPerspective): def get_shaded_rgb(self, rgb, normal_vect): brightness = np.dot(normal_vect, self.unit_sun_vect)**2 if brightness > 0: - alpha = self.shading_factor*brightness + alpha = self.shading_factor * brightness return interpolate(rgb, np.ones(3), alpha) else: - alpha = -self.shading_factor*brightness + alpha = -self.shading_factor * brightness return interpolate(rgb, np.zeros(3), alpha) def get_unit_normal_vect(self, vmobject): anchors = vmobject.get_anchors() if len(anchors) < 3: return OUT - normal = np.cross(anchors[1]-anchors[0], anchors[2]-anchors[1]) + normal = np.cross(anchors[1] - anchors[0], anchors[2] - anchors[1]) if normal[2] < 0: normal = -normal length = np.linalg.norm(normal) if length == 0: return OUT - return normal/length + return normal / length def display_multiple_vectorized_mobjects(self, vmobjects): camera_point = self.spherical_coords_to_point( *self.get_spherical_coords() ) + def z_cmp(*vmobs): - # Compare to three dimensional mobjects based on + # Compare to three dimensional mobjects based on # how close they are to the camera # return cmp(*[ # -np.linalg.norm(vm.get_center()-camera_point) @@ -103,22 +108,26 @@ class ThreeDCamera(CameraWithPerspective): else: return 0 Camera.display_multiple_vectorized_mobjects( - self, sorted(vmobjects, cmp = z_cmp) + self, sorted(vmobjects, cmp=z_cmp) ) - def get_spherical_coords(self, phi = None, theta = None, distance = None): + def get_spherical_coords(self, phi=None, theta=None, distance=None): curr_phi, curr_theta, curr_d = self.rotation_mobject.points[0] - if phi is None: phi = curr_phi - if theta is None: theta = curr_theta - if distance is None: distance = curr_d + if phi is None: + phi = curr_phi + if theta is None: + theta = curr_theta + if distance is None: + distance = curr_d return np.array([phi, theta, distance]) - def get_cartesian_coords(self, phi = None, theta = None, distance = None): - spherical_coords_array = self.get_spherical_coords(phi,theta,distance) + def get_cartesian_coords(self, phi=None, theta=None, distance=None): + spherical_coords_array = self.get_spherical_coords( + phi, theta, distance) phi2 = spherical_coords_array[0] theta2 = spherical_coords_array[1] d2 = spherical_coords_array[2] - return self.spherical_coords_to_point(phi2,theta2,d2) + return self.spherical_coords_to_point(phi2, theta2, d2) def get_phi(self): return self.get_spherical_coords()[0] @@ -130,13 +139,13 @@ class ThreeDCamera(CameraWithPerspective): return self.get_spherical_coords()[2] def spherical_coords_to_point(self, phi, theta, distance): - return distance*np.array([ - np.sin(phi)*np.cos(theta), - np.sin(phi)*np.sin(theta), + return distance * np.array([ + np.sin(phi) * np.cos(theta), + np.sin(phi) * np.sin(theta), np.cos(phi) ]) - def get_center_of_rotation(self, x = None, y = None, z = None): + def get_center_of_rotation(self, x=None, y=None, z=None): curr_x, curr_y, curr_z = self.moving_center.points[0] if x is None: x = curr_x @@ -146,19 +155,20 @@ class ThreeDCamera(CameraWithPerspective): z = curr_z return np.array([x, y, z]) - def set_position(self, phi = None, theta = None, distance = None, - center_x = None, center_y = None, center_z = None): + def set_position(self, phi=None, theta=None, distance=None, + center_x=None, center_y=None, center_z=None): point = self.get_spherical_coords(phi, theta, distance) self.rotation_mobject.move_to(point) self.phi, self.theta, self.distance = point - center_of_rotation = self.get_center_of_rotation(center_x, center_y, center_z) + center_of_rotation = self.get_center_of_rotation( + center_x, center_y, center_z) self.moving_center.move_to(center_of_rotation) self.space_center = self.moving_center.points[0] def get_view_transformation_matrix(self): return (self.default_distance / self.get_distance()) * np.dot( rotation_matrix(self.get_phi(), LEFT), - rotation_about_z(-self.get_theta() - np.pi/2), + rotation_about_z(-self.get_theta() - np.pi / 2), ) def points_to_pixel_coords(self, points): diff --git a/constants.py b/constants.py index 3105f1cb..af068348 100644 --- a/constants.py +++ b/constants.py @@ -3,31 +3,34 @@ import numpy as np # Things anyone wishing to use this repository for their # own use will want to change this -MEDIA_DIR = os.path.join(os.path.expanduser('~'), "Dropbox (3Blue1Brown)/3Blue1Brown Team Folder") +MEDIA_DIR = os.path.join( + os.path.expanduser('~'), + "Dropbox (3Blue1Brown)/3Blue1Brown Team Folder" +) # DEFAULT_PIXEL_HEIGHT = 1080 -DEFAULT_PIXEL_WIDTH = 1920 +DEFAULT_PIXEL_WIDTH = 1920 -LOW_QUALITY_FRAME_DURATION = 1./15 -MEDIUM_QUALITY_FRAME_DURATION = 1./30 -PRODUCTION_QUALITY_FRAME_DURATION = 1./60 +LOW_QUALITY_FRAME_DURATION = 1. / 15 +MEDIUM_QUALITY_FRAME_DURATION = 1. / 30 +PRODUCTION_QUALITY_FRAME_DURATION = 1. / 60 -#There might be other configuration than pixel_shape later... +# There might be other configuration than pixel_shape later... PRODUCTION_QUALITY_CAMERA_CONFIG = { - "pixel_shape" : (DEFAULT_PIXEL_HEIGHT, DEFAULT_PIXEL_WIDTH), + "pixel_shape": (DEFAULT_PIXEL_HEIGHT, DEFAULT_PIXEL_WIDTH), } MEDIUM_QUALITY_CAMERA_CONFIG = { - "pixel_shape" : (720, 1280), + "pixel_shape": (720, 1280), } LOW_QUALITY_CAMERA_CONFIG = { - "pixel_shape" : (480, 854), + "pixel_shape": (480, 854), } -DEFAULT_POINT_DENSITY_2D = 25 +DEFAULT_POINT_DENSITY_2D = 25 DEFAULT_POINT_DENSITY_1D = 250 DEFAULT_POINT_THICKNESS = 4 @@ -35,8 +38,8 @@ DEFAULT_POINT_THICKNESS = 4 FRAME_HEIGHT = 8.0 FRAME_WIDTH = FRAME_HEIGHT * DEFAULT_PIXEL_WIDTH / DEFAULT_PIXEL_HEIGHT -FRAME_Y_RADIUS = FRAME_HEIGHT/2 -FRAME_X_RADIUS = FRAME_WIDTH/2 +FRAME_Y_RADIUS = FRAME_HEIGHT / 2 +FRAME_X_RADIUS = FRAME_WIDTH / 2 SMALL_BUFF = 0.1 MED_SMALL_BUFF = 0.25 @@ -47,49 +50,49 @@ DEFAULT_MOBJECT_TO_EDGE_BUFFER = MED_LARGE_BUFF DEFAULT_MOBJECT_TO_MOBJECT_BUFFER = MED_SMALL_BUFF -#All in seconds +# All in seconds DEFAULT_ANIMATION_RUN_TIME = 1.0 DEFAULT_POINTWISE_FUNCTION_RUN_TIME = 3.0 DEFAULT_WAIT_TIME = 1.0 -ORIGIN = np.array(( 0., 0., 0.)) -UP = np.array(( 0., 1., 0.)) -DOWN = np.array(( 0.,-1., 0.)) -RIGHT = np.array(( 1., 0., 0.)) -LEFT = np.array((-1., 0., 0.)) -IN = np.array(( 0., 0.,-1.)) -OUT = np.array(( 0., 0., 1.)) -X_AXIS = np.array(( 1., 0., 0.)) -Y_AXIS = np.array(( 0., 1., 0.)) -Z_AXIS = np.array(( 0., 0., 1.)) +ORIGIN = np.array((0., 0., 0.)) +UP = np.array((0., 1., 0.)) +DOWN = np.array((0., -1., 0.)) +RIGHT = np.array((1., 0., 0.)) +LEFT = np.array((-1., 0., 0.)) +IN = np.array((0., 0., -1.)) +OUT = np.array((0., 0., 1.)) +X_AXIS = np.array((1., 0., 0.)) +Y_AXIS = np.array((0., 1., 0.)) +Z_AXIS = np.array((0., 0., 1.)) -TOP = FRAME_Y_RADIUS*UP -BOTTOM = FRAME_Y_RADIUS*DOWN -LEFT_SIDE = FRAME_X_RADIUS*LEFT -RIGHT_SIDE = FRAME_X_RADIUS*RIGHT +TOP = FRAME_Y_RADIUS * UP +BOTTOM = FRAME_Y_RADIUS * DOWN +LEFT_SIDE = FRAME_X_RADIUS * LEFT +RIGHT_SIDE = FRAME_X_RADIUS * RIGHT -TAU = 2*np.pi -DEGREES = TAU/360 +TAU = 2 * np.pi +DEGREES = TAU / 360 ANIMATIONS_DIR = os.path.join(MEDIA_DIR, "animations") RASTER_IMAGE_DIR = os.path.join(MEDIA_DIR, "designs", "raster_images") SVG_IMAGE_DIR = os.path.join(MEDIA_DIR, "designs", "svg_images") -#TODO, staged scenes should really go into a subdirectory of a given scenes directory -STAGED_SCENES_DIR = os.path.join(ANIMATIONS_DIR, "staged_scenes") +# TODO, staged scenes should really go into a subdirectory of a given scenes directory +STAGED_SCENES_DIR = os.path.join(ANIMATIONS_DIR, "staged_scenes") ### -THIS_DIR = os.path.dirname(os.path.realpath(__file__)) -FILE_DIR = os.path.join(THIS_DIR, "files") -TEX_DIR = os.path.join(FILE_DIR, "Tex") -TEX_IMAGE_DIR = TEX_DIR #TODO, What is this doing? -#These two may be depricated now. -MOBJECT_DIR = os.path.join(FILE_DIR, "mobjects") +THIS_DIR = os.path.dirname(os.path.realpath(__file__)) +FILE_DIR = os.path.join(THIS_DIR, "files") +TEX_DIR = os.path.join(FILE_DIR, "Tex") +TEX_IMAGE_DIR = TEX_DIR # TODO, What is this doing? +# These two may be depricated now. +MOBJECT_DIR = os.path.join(FILE_DIR, "mobjects") IMAGE_MOBJECT_DIR = os.path.join(MOBJECT_DIR, "image") if not os.path.exists(MEDIA_DIR): raise Exception(""" - Redefine MEDIA_DIR in constants.py to point to - a valid directory where movies and images will + Redefine MEDIA_DIR in constants.py to point to + a valid directory where movies and images will be written """) for folder in [FILE_DIR, RASTER_IMAGE_DIR, SVG_IMAGE_DIR, ANIMATIONS_DIR, TEX_DIR, @@ -99,82 +102,72 @@ for folder in [FILE_DIR, RASTER_IMAGE_DIR, SVG_IMAGE_DIR, ANIMATIONS_DIR, TEX_DI os.makedirs(folder) TEX_TEXT_TO_REPLACE = "YourTextHere" -TEMPLATE_TEX_FILE = os.path.join(THIS_DIR, "template.tex") +TEMPLATE_TEX_FILE = os.path.join(THIS_DIR, "template.tex") TEMPLATE_TEXT_FILE = os.path.join(THIS_DIR, "text_template.tex") FFMPEG_BIN = "ffmpeg" -### Colors ### +# Colors COLOR_MAP = { - "DARK_BLUE" : "#236B8E", - "DARK_BROWN" : "#8B4513", - "LIGHT_BROWN" : "#CD853F", - "BLUE_E" : "#1C758A", - "BLUE_D" : "#29ABCA", - "BLUE_C" : "#58C4DD", - "BLUE_B" : "#9CDCEB", - "BLUE_A" : "#C7E9F1", - "TEAL_E" : "#49A88F", - "TEAL_D" : "#55C1A7", - "TEAL_C" : "#5CD0B3", - "TEAL_B" : "#76DDC0", - "TEAL_A" : "#ACEAD7", - "GREEN_E" : "#699C52", - "GREEN_D" : "#77B05D", - "GREEN_C" : "#83C167", - "GREEN_B" : "#A6CF8C", - "GREEN_A" : "#C9E2AE", - "YELLOW_E" : "#E8C11C", - "YELLOW_D" : "#F4D345", - "YELLOW_C" : "#FFFF00", - "YELLOW_B" : "#FFEA94", - "YELLOW_A" : "#FFF1B6", - "GOLD_E" : "#C78D46", - "GOLD_D" : "#E1A158", - "GOLD_C" : "#F0AC5F", - "GOLD_B" : "#F9B775", - "GOLD_A" : "#F7C797", - "RED_E" : "#CF5044", - "RED_D" : "#E65A4C", - "RED_C" : "#FC6255", - "RED_B" : "#FF8080", - "RED_A" : "#F7A1A3", - "MAROON_E" : "#94424F", - "MAROON_D" : "#A24D61", - "MAROON_C" : "#C55F73", - "MAROON_B" : "#EC92AB", - "MAROON_A" : "#ECABC1", - "PURPLE_E" : "#644172", - "PURPLE_D" : "#715582", - "PURPLE_C" : "#9A72AC", - "PURPLE_B" : "#B189C6", - "PURPLE_A" : "#CAA3E8", - "WHITE" : "#FFFFFF", - "BLACK" : "#000000", - "LIGHT_GRAY" : "#BBBBBB", - "LIGHT_GREY" : "#BBBBBB", - "GRAY" : "#888888", - "GREY" : "#888888", - "DARK_GREY" : "#444444", - "DARK_GRAY" : "#444444", - "GREY_BROWN" : "#736357", - "PINK" : "#D147BD", + "DARK_BLUE": "#236B8E", + "DARK_BROWN": "#8B4513", + "LIGHT_BROWN": "#CD853F", + "BLUE_E": "#1C758A", + "BLUE_D": "#29ABCA", + "BLUE_C": "#58C4DD", + "BLUE_B": "#9CDCEB", + "BLUE_A": "#C7E9F1", + "TEAL_E": "#49A88F", + "TEAL_D": "#55C1A7", + "TEAL_C": "#5CD0B3", + "TEAL_B": "#76DDC0", + "TEAL_A": "#ACEAD7", + "GREEN_E": "#699C52", + "GREEN_D": "#77B05D", + "GREEN_C": "#83C167", + "GREEN_B": "#A6CF8C", + "GREEN_A": "#C9E2AE", + "YELLOW_E": "#E8C11C", + "YELLOW_D": "#F4D345", + "YELLOW_C": "#FFFF00", + "YELLOW_B": "#FFEA94", + "YELLOW_A": "#FFF1B6", + "GOLD_E": "#C78D46", + "GOLD_D": "#E1A158", + "GOLD_C": "#F0AC5F", + "GOLD_B": "#F9B775", + "GOLD_A": "#F7C797", + "RED_E": "#CF5044", + "RED_D": "#E65A4C", + "RED_C": "#FC6255", + "RED_B": "#FF8080", + "RED_A": "#F7A1A3", + "MAROON_E": "#94424F", + "MAROON_D": "#A24D61", + "MAROON_C": "#C55F73", + "MAROON_B": "#EC92AB", + "MAROON_A": "#ECABC1", + "PURPLE_E": "#644172", + "PURPLE_D": "#715582", + "PURPLE_C": "#9A72AC", + "PURPLE_B": "#B189C6", + "PURPLE_A": "#CAA3E8", + "WHITE": "#FFFFFF", + "BLACK": "#000000", + "LIGHT_GRAY": "#BBBBBB", + "LIGHT_GREY": "#BBBBBB", + "GRAY": "#888888", + "GREY": "#888888", + "DARK_GREY": "#444444", + "DARK_GRAY": "#444444", + "GREY_BROWN": "#736357", + "PINK": "#D147BD", "GREEN_SCREEN": "#00FF00", - "ORANGE" : "#FF862F", + "ORANGE": "#FF862F", } PALETTE = COLOR_MAP.values() locals().update(COLOR_MAP) -for name in filter(lambda s : s.endswith("_C"), COLOR_MAP.keys()): +for name in filter(lambda s: s.endswith("_C"), COLOR_MAP.keys()): locals()[name.replace("_C", "")] = locals()[name] - - - - - - - - - - diff --git a/container/container.py b/container/container.py index 51a2b3a2..b3e9b6a1 100644 --- a/container/container.py +++ b/container/container.py @@ -1,22 +1,24 @@ -from constants import * from utils.config_ops import digest_config # Currently, this is only used by both Scene and MOBject. # Still, we abstract its functionality here, albeit purely nominally. # All actual implementation has to be handled by derived classes for now. # -# Note that although the prototypical instances add and remove MObjects, -# there is also the possibility to add ContinualAnimations to Scenes. Thus, -# in the Container class in general, we do not make any presumptions about +# Note that although the prototypical instances add and remove MObjects, +# there is also the possibility to add ContinualAnimations to Scenes. Thus, +# in the Container class in general, we do not make any presumptions about # what types of objects may be added; this is again dependent on the specific # derived instance. + class Container(object): def __init__(self, *submobjects, **kwargs): digest_config(self, kwargs) def add(self, *items): - raise Exception("Container.add is not implemented; it is up to derived classes to implement") + raise Exception( + "Container.add is not implemented; it is up to derived classes to implement") def remove(self, *items): - raise Exception("Container.remove is not implemented; it is up to derived classes to implement") \ No newline at end of file + raise Exception( + "Container.remove is not implemented; it is up to derived classes to implement") diff --git a/continual_animation/continual_animation.py b/continual_animation/continual_animation.py index 814b00c1..5ecee74c 100644 --- a/continual_animation/continual_animation.py +++ b/continual_animation/continual_animation.py @@ -6,12 +6,14 @@ from mobject.mobject import Mobject from utils.config_ops import digest_config from utils.config_ops import instantiate + class ContinualAnimation(object): CONFIG = { - "start_up_time" : 1, - "wind_down_time" : 1, - "end_time" : np.inf, + "start_up_time": 1, + "wind_down_time": 1, + "end_time": np.inf, } + def __init__(self, mobject, **kwargs): mobject = instantiate(mobject) assert(isinstance(mobject, Mobject)) @@ -22,42 +24,45 @@ class ContinualAnimation(object): self.update(0) def setup(self): - #To implement in subclass + # To implement in subclass pass - def begin_wind_down(self, wind_down_time = None): + def begin_wind_down(self, wind_down_time=None): if wind_down_time is not None: self.wind_down_time = wind_down_time self.end_time = self.external_time + self.wind_down_time def update(self, dt): - #TODO, currenty time moves slower for a - #continual animation during its start up - #to help smooth things out. Does this have - #unwanted consequences? + # TODO, currenty time moves slower for a + # continual animation during its start up + # to help smooth things out. Does this have + # unwanted consequences? self.external_time += dt if self.external_time < self.start_up_time: - dt *= float(self.external_time)/self.start_up_time + dt *= float(self.external_time) / self.start_up_time elif self.external_time > self.end_time - self.wind_down_time: dt *= np.clip( - float(self.end_time - self.external_time)/self.wind_down_time, + float(self.end_time - self.external_time) / + self.wind_down_time, 0, 1 ) self.internal_time += dt self.update_mobject(dt) def update_mobject(self, dt): - #To implement in subclass + # To implement in subclass pass def copy(self): return copy.deepcopy(self) + class ContinualAnimationGroup(ContinualAnimation): CONFIG = { - "start_up_time" : 0, - "wind_down_time" : 0, + "start_up_time": 0, + "wind_down_time": 0, } + def __init__(self, *continual_animations, **kwargs): digest_config(self, kwargs, locals()) self.group = Group(*[ca.mobject for ca in continual_animations]) @@ -67,12 +72,13 @@ class ContinualAnimationGroup(ContinualAnimation): for continual_animation in self.continual_animations: continual_animation.update(dt) + class ContinualRotation(ContinualAnimation): CONFIG = { - "axis" : OUT, - "rate" : np.pi/12, #Radians per second - "in_place" : True, - "about_point" : None, + "axis": OUT, + "rate": np.pi / 12, # Radians per second + "in_place": True, + "about_point": None, } def update_mobject(self, dt): @@ -83,23 +89,16 @@ class ContinualRotation(ContinualAnimation): else: about_point = ORIGIN self.mobject.rotate( - dt*self.rate, axis = self.axis, - about_point = about_point + dt * self.rate, axis=self.axis, + about_point=about_point ) + class ContinualMovement(ContinualAnimation): CONFIG = { - "direction" : RIGHT, - "rate" : 0.05, #Units per second + "direction": RIGHT, + "rate": 0.05, # Units per second } def update_mobject(self, dt): - self.mobject.shift(dt*self.rate*self.direction) - - - - - - - - + self.mobject.shift(dt * self.rate * self.direction) diff --git a/continual_animation/from_animation.py b/continual_animation/from_animation.py index bcbdc0a2..d0bc9e81 100644 --- a/continual_animation/from_animation.py +++ b/continual_animation/from_animation.py @@ -2,20 +2,23 @@ from __future__ import absolute_import from continual_animation.continual_animation import ContinualAnimation + class NormalAnimationAsContinualAnimation(ContinualAnimation): CONFIG = { - "start_up_time" : 0, - "wind_down_time" : 0, + "start_up_time": 0, + "wind_down_time": 0, } + def __init__(self, animation, **kwargs): self.animation = animation ContinualAnimation.__init__(self, animation.mobject, **kwargs) def update_mobject(self, dt): self.animation.update( - min(float(self.internal_time)/self.animation.run_time, 1) + min(float(self.internal_time) / self.animation.run_time, 1) ) + class CycleAnimation(ContinualAnimation): def __init__(self, animation, **kwargs): self.animation = animation @@ -23,6 +26,5 @@ class CycleAnimation(ContinualAnimation): def update_mobject(self, dt): mod_value = self.internal_time % self.animation.run_time - alpha = mod_value/float(self.animation.run_time) + alpha = mod_value / float(self.animation.run_time) self.animation.update(alpha) - diff --git a/continual_animation/numbers.py b/continual_animation/numbers.py index 42679af6..8c618ace 100644 --- a/continual_animation/numbers.py +++ b/continual_animation/numbers.py @@ -3,6 +3,7 @@ from __future__ import absolute_import from continual_animation.from_animation import NormalAnimationAsContinualAnimation from animation.numbers import ChangingDecimal + class ContinualChangingDecimal(NormalAnimationAsContinualAnimation): def __init__(self, *args, **kwargs): NormalAnimationAsContinualAnimation.__init__( diff --git a/continual_animation/update.py b/continual_animation/update.py index d891c11c..bdc93e98 100644 --- a/continual_animation/update.py +++ b/continual_animation/update.py @@ -6,8 +6,9 @@ from animation.update import MaintainPositionRelativeTo class ContinualUpdateFromFunc(ContinualAnimation): CONFIG = { - "function_depends_on_dt" : False + "function_depends_on_dt": False } + def __init__(self, mobject, func, **kwargs): self.func = func ContinualAnimation.__init__(self, mobject, **kwargs) @@ -18,16 +19,19 @@ class ContinualUpdateFromFunc(ContinualAnimation): else: self.func(self.mobject) + class ContinualUpdateFromTimeFunc(ContinualUpdateFromFunc): CONFIG = { - "function_depends_on_dt" : True + "function_depends_on_dt": True } + class ContinualMaintainPositionRelativeTo(ContinualAnimation): # TODO: Possibly reimplement using CycleAnimation? def __init__(self, mobject, tracked_mobject, **kwargs): - self.anim = MaintainPositionRelativeTo(mobject, tracked_mobject, **kwargs) + self.anim = MaintainPositionRelativeTo( + mobject, tracked_mobject, **kwargs) ContinualAnimation.__init__(self, mobject, **kwargs) def update_mobject(self, dt): - self.anim.update(0) # 0 is arbitrary + self.anim.update(0) # 0 is arbitrary diff --git a/example_scenes.py b/example_scenes.py index fb465d92..7c543064 100644 --- a/example_scenes.py +++ b/example_scenes.py @@ -4,8 +4,8 @@ from big_ol_pile_of_manim_imports import * # To watch one of these scenes, run the following: # python extract_scene.py file_name -p -# -# Use the flat -l for a faster rendering at a lower +# +# Use the flat -l for a faster rendering at a lower # quality, use -s to skip to the end and just show # the final frame, and use -n to skip ahead # to the n'th animation of a scene. @@ -16,17 +16,18 @@ class SquareToCircle(Scene): circle = Circle() square = Square() square.flip(RIGHT) - square.rotate(-3*TAU/8) + square.rotate(-3 * TAU / 8) self.play(ShowCreation(square)) self.play(Transform(square, circle)) self.play(FadeOut(square)) + class WarpSquare(Scene): def construct(self): square = Square() self.play(ApplyPointwiseFunction( - lambda (x, y, z) : complex_to_R3(np.exp(complex(x, y))), + lambda (x, y, z): complex_to_R3(np.exp(complex(x, y))), square )) self.wait() @@ -42,8 +43,8 @@ class Rotation3d(ThreeDScene): # STEP 1 # Build two cube in the 3D scene, one for around the origin, # the other shifted along the vector RIGHT + UP + OUT - cube_origin = Cube(fill_opacity = 0.8, stroke_width = 1., - side_length = 1., fill_color = WHITE) + cube_origin = Cube(fill_opacity=0.8, stroke_width=1., + side_length=1., fill_color=WHITE) # RIGHT side: Red # UP side: Green @@ -51,15 +52,15 @@ class Rotation3d(ThreeDScene): orientations = [IN, OUT, LEFT, RIGHT, UP, DOWN] for face, orient in zip(cube_origin.family_members_with_points(), orientations): if np.array_equal(orient, RIGHT): - face.set_style_data(fill_color = RED) + face.set_style_data(fill_color=RED) elif np.array_equal(orient, UP): - face.set_style_data(fill_color = GREEN) + face.set_style_data(fill_color=GREEN) elif np.array_equal(orient, OUT): - face.set_style_data(fill_color = BLUE) + face.set_style_data(fill_color=BLUE) - cube_shifted = Cube(fill_opacity = 0.8, stroke_width = 1., - side_length = 1., fill_color = BLUE) - shift_vec = 2*(RIGHT + UP + OUT) + cube_shifted = Cube(fill_opacity=0.8, stroke_width=1., + side_length=1., fill_color=BLUE) + shift_vec = 2 * (RIGHT + UP + OUT) cube_shifted.shift(shift_vec) # STEP 2 @@ -71,8 +72,8 @@ class Rotation3d(ThreeDScene): # Setup the camera position phi, theta, distance = ThreeDCamera().get_spherical_coords() angle_factor = 0.9 - phi += 2*np.pi/4*angle_factor - theta += 3*2*np.pi/8 + phi += 2 * np.pi / 4 * angle_factor + theta += 3 * 2 * np.pi / 8 self.set_camera_position(phi, theta, distance) self.wait() @@ -80,23 +81,23 @@ class Rotation3d(ThreeDScene): # Animation # Animation 1: rotation around the Z-axis with the ORIGIN of the space # as center of rotation - theta += 2*np.pi + theta += 2 * np.pi self.move_camera(phi, theta, distance, - run_time = 5) + run_time=5) # Animation 2: shift the space in order of to get the center of the shifted cube # as the next center of rotation cube_center = cube_shifted.get_center() - self.move_camera(center_x = cube_center[0], - center_y = cube_center[1], - center_z = cube_center[2], - run_time = 2) + self.move_camera(center_x=cube_center[0], + center_y=cube_center[1], + center_z=cube_center[2], + run_time=2) - # Animation 3: rotation around the Z-axis with the center of the shifted cube + # Animation 3: rotation around the Z-axis with the center of the shifted cube # as center of rotation - theta += 2*np.pi + theta += 2 * np.pi self.move_camera(phi, theta, distance, - run_time = 5) + run_time=5) class SpinAroundCube(ThreeDScene): @@ -112,28 +113,28 @@ class SpinAroundCube(ThreeDScene): def construct(self): axes = ThreeDAxes() cube = Cube( - fill_opacity = 1, - stroke_color = LIGHT_GREY, - stroke_width = 1, + fill_opacity=1, + stroke_color=LIGHT_GREY, + stroke_width=1, ) # The constant OUT is np.array([0, 0, 1]) - cube.next_to(ORIGIN, UP+RIGHT+OUT) + cube.next_to(ORIGIN, UP + RIGHT + OUT) self.add(axes, cube) # The camera starts positioned with phi=0, meaning it - # is directly above the xy-plane, and theta = -TAU/4, + # is directly above the xy-plane, and theta = -TAU/4, # which makes the "down" direction of the screen point # in the negative y direction. # This animates a camera movement self.move_camera( # Tilted 20 degrees off xy plane (70 degrees off the vertical) - phi = (70./360.)*TAU, + phi=(70. / 360.) * TAU, # Positioned above the third quadrant of # the xy-plane - theta = (-110./360.)*TAU, + theta=(-110. / 360.) * TAU, # pass in animation config just like a .play call - run_time = 3 + run_time=3 ) self.wait() # If you want the camera to slowly rotate about @@ -142,33 +143,17 @@ class SpinAroundCube(ThreeDScene): self.wait(4) self.play(FadeOut(cube)) - - text = TextMobject("Your ad here") - text.rotate(TAU/4, axis = RIGHT) + text.rotate(TAU / 4, axis=RIGHT) text.next_to(cube, OUT) self.play(Write(text)) # If you want to play animations while moving the camera, # include them in an "added_anims" list to move_camera self.move_camera( - theta = -0.2*TAU, - added_anims = [ - text.shift, 3*OUT, - text.set_fill, {"opacity" : 1}, + theta=-0.2 * TAU, + added_anims=[ + text.shift, 3 * OUT, + text.set_fill, {"opacity": 1}, ] ) self.wait(4) - - - - - - - - - - - - - - diff --git a/extract_scene.py b/extract_scene.py index 2a928521..f35d3564 100644 --- a/extract_scene.py +++ b/extract_scene.py @@ -1,18 +1,16 @@ #!/usr/bin/env python2 import sys -# import getopt import argparse import imp -import imp import inspect import itertools as it import os import subprocess as sp import traceback -from camera.camera import Camera from constants import * + from scene.scene import Scene from utils.sounds import play_error_sound from utils.sounds import play_finish_sound @@ -45,169 +43,178 @@ NO_SCENE_MESSAGE = """ def get_configuration(): - try: - parser = argparse.ArgumentParser() - parser.add_argument( - "file", help = "path to file holding the python code for the scene" - ) - parser.add_argument( - "scene_name", help = "Name of the Scene class you want to see" - ) - optional_args = [ - ("-p", "--preview"), - ("-w", "--write_to_movie"), - ("-s", "--show_last_frame"), - ("-l", "--low_quality"), - ("-m", "--medium_quality"), - ("-g", "--save_pngs"), - ("-f", "--show_file_in_finder"), - ("-t", "--transparent"), - ("-q", "--quiet"), - ("-a", "--write_all") - ] - for short_arg, long_arg in optional_args: - parser.add_argument(short_arg, long_arg, action = "store_true") - parser.add_argument("-o", "--output_name") - parser.add_argument("-n", "--start_at_animation_number") - args = parser.parse_args() - if args.output_name != None: - output_name_root, output_name_ext = os.path.splitext(args.output_name) - expected_ext = '.png' if args.show_last_frame else '.mp4' - if not output_name_ext in ['', expected_ext]: - print "WARNING: The output will be to (doubly-dotted) %s%s"%output_name_root%expected_ext + try: + parser = argparse.ArgumentParser() + parser.add_argument( + "file", help="path to file holding the python code for the scene" + ) + parser.add_argument( + "scene_name", help="Name of the Scene class you want to see" + ) + optional_args = [ + ("-p", "--preview"), + ("-w", "--write_to_movie"), + ("-s", "--show_last_frame"), + ("-l", "--low_quality"), + ("-m", "--medium_quality"), + ("-g", "--save_pngs"), + ("-f", "--show_file_in_finder"), + ("-t", "--transparent"), + ("-q", "--quiet"), + ("-a", "--write_all") + ] + for short_arg, long_arg in optional_args: + parser.add_argument(short_arg, long_arg, action="store_true") + parser.add_argument("-o", "--output_name") + parser.add_argument("-n", "--start_at_animation_number") + args = parser.parse_args() + if args.output_name is not None: + output_name_root, output_name_ext = os.path.splitext( + args.output_name) + expected_ext = '.png' if args.show_last_frame else '.mp4' + if output_name_ext not in ['', expected_ext]: + print "WARNING: The output will be to (doubly-dotted) %s%s" % output_name_root % expected_ext + output_name = args.output_name + else: + # If anyone wants .mp4.mp4 and is surprised to only get .mp4, or such... Well, too bad. + output_name = output_name_root + else: output_name = args.output_name - else: - # If anyone wants .mp4.mp4 and is surprised to only get .mp4, or such... Well, too bad. - output_name = output_name_root - else: - output_name = args.output_name - except argparse.ArgumentError as err: - print(str(err)) - sys.exit(2) - config = { - "file" : args.file, - "scene_name" : args.scene_name, - "open_video_upon_completion" : args.preview, - "show_file_in_finder" : args.show_file_in_finder, - #By default, write to file - "write_to_movie" : args.write_to_movie or not args.show_last_frame, - "show_last_frame" : args.show_last_frame, - "save_pngs" : args.save_pngs, - #If -t is passed in (for transparent), this will be RGBA - "saved_image_mode": "RGBA" if args.transparent else "RGB", - "movie_file_extension" : ".mov" if args.transparent else ".mp4", - "quiet" : args.quiet or args.write_all, - "ignore_waits" : args.preview, - "write_all" : args.write_all, - "output_name" : output_name, - "start_at_animation_number" : args.start_at_animation_number, - "end_at_animation_number" : None, - } - if args.low_quality: - config["camera_config"] = LOW_QUALITY_CAMERA_CONFIG - config["frame_duration"] = LOW_QUALITY_FRAME_DURATION - elif args.medium_quality: - config["camera_config"] = MEDIUM_QUALITY_CAMERA_CONFIG - config["frame_duration"] = MEDIUM_QUALITY_FRAME_DURATION - else: - config["camera_config"] = PRODUCTION_QUALITY_CAMERA_CONFIG - config["frame_duration"] = PRODUCTION_QUALITY_FRAME_DURATION + except argparse.ArgumentError as err: + print(str(err)) + sys.exit(2) + config = { + "file": args.file, + "scene_name": args.scene_name, + "open_video_upon_completion": args.preview, + "show_file_in_finder": args.show_file_in_finder, + # By default, write to file + "write_to_movie": args.write_to_movie or not args.show_last_frame, + "show_last_frame": args.show_last_frame, + "save_pngs": args.save_pngs, + # If -t is passed in (for transparent), this will be RGBA + "saved_image_mode": "RGBA" if args.transparent else "RGB", + "movie_file_extension": ".mov" if args.transparent else ".mp4", + "quiet": args.quiet or args.write_all, + "ignore_waits": args.preview, + "write_all": args.write_all, + "output_name": output_name, + "start_at_animation_number": args.start_at_animation_number, + "end_at_animation_number": None, + } + if args.low_quality: + config["camera_config"] = LOW_QUALITY_CAMERA_CONFIG + config["frame_duration"] = LOW_QUALITY_FRAME_DURATION + elif args.medium_quality: + config["camera_config"] = MEDIUM_QUALITY_CAMERA_CONFIG + config["frame_duration"] = MEDIUM_QUALITY_FRAME_DURATION + else: + config["camera_config"] = PRODUCTION_QUALITY_CAMERA_CONFIG + config["frame_duration"] = PRODUCTION_QUALITY_FRAME_DURATION - stan = config["start_at_animation_number"] - if stan is not None: - if "," in stan: - start, end = stan.split(",") - config["start_at_animation_number"] = int(start) - config["end_at_animation_number"] = int(end) - else: - config["start_at_animation_number"] = int(stan) + stan = config["start_at_animation_number"] + if stan is not None: + if "," in stan: + start, end = stan.split(",") + config["start_at_animation_number"] = int(start) + config["end_at_animation_number"] = int(end) + else: + config["start_at_animation_number"] = int(stan) + + config["skip_animations"] = any([ + config["show_last_frame"] and not config["write_to_movie"], + config["start_at_animation_number"], + ]) + return config - config["skip_animations"] = any([ - config["show_last_frame"] and not config["write_to_movie"], - config["start_at_animation_number"], - ]) - return config def handle_scene(scene, **config): - import platform - if config["quiet"]: - curr_stdout = sys.stdout - sys.stdout = open(os.devnull, "w") + import platform + if config["quiet"]: + curr_stdout = sys.stdout + sys.stdout = open(os.devnull, "w") - if config["show_last_frame"]: - scene.save_image(mode = config["saved_image_mode"]) - open_file = any([ - config["show_last_frame"], - config["open_video_upon_completion"], - config["show_file_in_finder"] - ]) - if open_file: - commands = ["open"] - if (platform.system() == "Linux"): - commands = ["xdg-open"] + if config["show_last_frame"]: + scene.save_image(mode=config["saved_image_mode"]) + open_file = any([ + config["show_last_frame"], + config["open_video_upon_completion"], + config["show_file_in_finder"] + ]) + if open_file: + commands = ["open"] + if (platform.system() == "Linux"): + commands = ["xdg-open"] - if config["show_file_in_finder"]: - commands.append("-R") - # - if config["show_last_frame"]: - commands.append(scene.get_image_file_path()) - else: - commands.append(scene.get_movie_file_path()) - FNULL = open(os.devnull, 'w') - sp.call(commands, stdout=FNULL, stderr=sp.STDOUT) - FNULL.close() + if config["show_file_in_finder"]: + commands.append("-R") + # + if config["show_last_frame"]: + commands.append(scene.get_image_file_path()) + else: + commands.append(scene.get_movie_file_path()) + FNULL = open(os.devnull, 'w') + sp.call(commands, stdout=FNULL, stderr=sp.STDOUT) + FNULL.close() + + if config["quiet"]: + sys.stdout.close() + sys.stdout = curr_stdout - if config["quiet"]: - sys.stdout.close() - sys.stdout = curr_stdout def is_scene(obj): - if not inspect.isclass(obj): - return False - if not issubclass(obj, Scene): - return False - if obj == Scene: - return False - return True + if not inspect.isclass(obj): + return False + if not issubclass(obj, Scene): + return False + if obj == Scene: + return False + return True + def prompt_user_for_choice(name_to_obj): - num_to_name = {} - names = sorted(name_to_obj.keys()) - for count, name in zip(it.count(1), names): - print("%d: %s"%(count, name)) - num_to_name[count] = name - try: - user_input = raw_input(CHOOSE_NUMBER_MESSAGE) - return [ - name_to_obj[num_to_name[int(num_str)]] - for num_str in user_input.split(",") - ] - except: - print(INVALID_NUMBER_MESSAGE) - sys.exit() + num_to_name = {} + names = sorted(name_to_obj.keys()) + for count, name in zip(it.count(1), names): + print("%d: %s" % (count, name)) + num_to_name[count] = name + try: + user_input = raw_input(CHOOSE_NUMBER_MESSAGE) + return [ + name_to_obj[num_to_name[int(num_str)]] + for num_str in user_input.split(",") + ] + except: + print(INVALID_NUMBER_MESSAGE) + sys.exit() + def get_scene_classes(scene_names_to_classes, config): - if len(scene_names_to_classes) == 0: - print(NO_SCENE_MESSAGE) - return [] - if len(scene_names_to_classes) == 1: - return scene_names_to_classes.values() - if config["scene_name"] in scene_names_to_classes: - return [scene_names_to_classes[config["scene_name"]] ] - if config["scene_name"] != "": - print(SCENE_NOT_FOUND_MESSAGE) - return [] - if config["write_all"]: - return scene_names_to_classes.values() - return prompt_user_for_choice(scene_names_to_classes) + if len(scene_names_to_classes) == 0: + print(NO_SCENE_MESSAGE) + return [] + if len(scene_names_to_classes) == 1: + return scene_names_to_classes.values() + if config["scene_name"] in scene_names_to_classes: + return [scene_names_to_classes[config["scene_name"]]] + if config["scene_name"] != "": + print(SCENE_NOT_FOUND_MESSAGE) + return [] + if config["write_all"]: + return scene_names_to_classes.values() + return prompt_user_for_choice(scene_names_to_classes) + def get_module_windows(file_name): - module_name = file_name.replace(".py", "") - last_module = imp.load_module("__init__", *imp.find_module("__init__", ['.'])) - for part in module_name.split(os.sep): - load_args = imp.find_module(part, [os.path.dirname(last_module.__file__)]) - last_module = imp.load_module(part, *load_args) - return last_module + module_name = file_name.replace(".py", "") + last_module = imp.load_module( + "__init__", *imp.find_module("__init__", ['.'])) + for part in module_name.split(os.sep): + load_args = imp.find_module( + part, [os.path.dirname(last_module.__file__)]) + last_module = imp.load_module(part, *load_args) + return last_module + def get_module_posix(file_name): module_name = file_name.replace(".py", "") @@ -217,54 +224,54 @@ def get_module_posix(file_name): last_module = imp.load_module(part, *load_args) return last_module + def get_module(file_name): if os.name == 'nt': return get_module_windows(file_name) return get_module_posix(file_name) + def main(): - config = get_configuration() - module = get_module(config["file"]) - scene_names_to_classes = dict( - inspect.getmembers(module, is_scene) - ) + config = get_configuration() + module = get_module(config["file"]) + scene_names_to_classes = dict(inspect.getmembers(module, is_scene)) - config["output_directory"] = os.path.join( - ANIMATIONS_DIR, - config["file"].replace(".py", "") - ) + config["output_directory"] = os.path.join( + ANIMATIONS_DIR, + config["file"].replace(".py", "") + ) - scene_kwargs = dict([ - (key, config[key]) - for key in [ - "camera_config", - "frame_duration", - "skip_animations", - "write_to_movie", - "output_directory", - "save_pngs", - "movie_file_extension", - "start_at_animation_number", - "end_at_animation_number", - ] - ]) - - scene_kwargs["name"] = config["output_name"] - if config["save_pngs"]: - print "We are going to save a PNG sequence as well..." - scene_kwargs["save_pngs"] = True - scene_kwargs["pngs_mode"] = config["saved_image_mode"] - - for SceneClass in get_scene_classes(scene_names_to_classes, config): - try: - handle_scene(SceneClass(**scene_kwargs), **config) - play_finish_sound() - except: - print("\n\n") - traceback.print_exc() - print("\n\n") - play_error_sound() + scene_kwargs = dict([ + (key, config[key]) + for key in [ + "camera_config", + "frame_duration", + "skip_animations", + "write_to_movie", + "output_directory", + "save_pngs", + "movie_file_extension", + "start_at_animation_number", + "end_at_animation_number", + ] + ]) + + scene_kwargs["name"] = config["output_name"] + if config["save_pngs"]: + print "We are going to save a PNG sequence as well..." + scene_kwargs["save_pngs"] = True + scene_kwargs["pngs_mode"] = config["saved_image_mode"] + + for SceneClass in get_scene_classes(scene_names_to_classes, config): + try: + handle_scene(SceneClass(**scene_kwargs), **config) + play_finish_sound() + except: + print("\n\n") + traceback.print_exc() + print("\n\n") + play_error_sound() if __name__ == "__main__": - main() + main() diff --git a/for_3b1b_videos/common_scenes.py b/for_3b1b_videos/common_scenes.py index f6646855..e25cd68b 100644 --- a/for_3b1b_videos/common_scenes.py +++ b/for_3b1b_videos/common_scenes.py @@ -8,10 +8,8 @@ from animation.animation import Animation from animation.composition import LaggedStart from animation.creation import DrawBorderThenFill from animation.creation import Write -from animation.transform import ApplyMethod from animation.creation import FadeIn from animation.creation import FadeOut -from mobject.svg.tex_mobject import TexMobject from mobject.svg.tex_mobject import TextMobject from mobject.types.vectorized_mobject import VGroup from scene.scene import Scene @@ -23,42 +21,45 @@ from mobject.geometry import Rectangle from mobject.geometry import Square from mobject.svg.drawings import PatreonLogo + 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, + "quote": [], + "quote_arg_separator": " ", + "highlighted_quote_terms": {}, + "author": "", + "fade_in_kwargs": { + "submobject_mode": "lagged_start", + "rate_func": None, + "lag_factor": 4, + "run_time": 5, }, } + 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.play(Write(self.author, run_time=3)) self.wait() - def get_quote(self, max_width = FRAME_WIDTH-1): + def get_quote(self, max_width=FRAME_WIDTH - 1): text_mobject_kwargs = { - "alignment" : "", - "arg_separator" : self.quote_arg_separator, + "alignment": "", + "arg_separator": self.quote_arg_separator, } if isinstance(self.quote, str): - quote = TextMobject("``%s''"%self.quote.strip(), **text_mobject_kwargs) + quote = TextMobject("``%s''" % + self.quote.strip(), **text_mobject_kwargs) else: words = ["\\Large ``"] + list(self.quote) + ["''"] quote = TextMobject(*words, **text_mobject_kwargs) - ##TODO, make less hacky + # TODO, make less hacky if self.quote_arg_separator == " ": - quote[0].shift(0.2*RIGHT) - quote[-1].shift(0.2*LEFT) + quote[0].shift(0.2 * RIGHT) + quote[-1].shift(0.2 * LEFT) for term, color in self.set_colored_quote_terms.items(): quote.set_color_by_tex(term, color) quote.to_edge(UP) @@ -72,12 +73,14 @@ class OpeningQuote(Scene): author.set_color(YELLOW) return author + class PatreonThanks(Scene): CONFIG = { - "specific_patrons" : [], - "max_patron_group_size" : 20, - "patron_scale_val" : 0.8, + "specific_patrons": [], + "max_patron_group_size": 20, + "patron_scale_val": 0.8, } + def construct(self): morty = Mortimer() morty.next_to(ORIGIN, DOWN) @@ -89,17 +92,17 @@ class PatreonThanks(Scene): patrons = 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') + 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:]) + 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.arrange_submobjects(DOWN, aligned_edge=LEFT) subgroup.scale(self.patron_scale_val) subgroup.to_edge(vect) @@ -109,7 +112,7 @@ class PatreonThanks(Scene): if last_group is not None: self.play( FadeOut(last_group), - morty.look, UP+LEFT + morty.look, UP + LEFT ) else: anims += [ @@ -117,25 +120,27 @@ class PatreonThanks(Scene): ] self.play( LaggedStart( - FadeIn, group, - run_time = 2, + FadeIn, group, + run_time=2, ), - morty.change, "gracious", group.get_corner(UP+LEFT), + 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(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): CONFIG = { - "n_patron_columns" : 3, - "max_patron_width" : 3, - "run_time" : 20, - "randomize_order" : True, + "n_patron_columns": 3, + "max_patron_width": 3, + "run_time": 20, + "randomize_order": True, } + def construct(self): if self.randomize_order: random.shuffle(self.specific_patrons) @@ -145,32 +150,32 @@ class PatreonEndScreen(PatreonThanks): def add_title(self): title = self.title = TextMobject("Clicky Stuffs") title.scale(1.5) - title.to_edge(UP, buff = MED_SMALL_BUFF) + 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.scale_to_fit_height(title.get_height()) pi.change_mode("thinking") pi.look(DOWN) - pi.next_to(title, vect, buff = MED_LARGE_BUFF) + pi.next_to(title, vect, buff=MED_LARGE_BUFF) self.add_foreground_mobjects(title, randy, morty) def scroll_through_patrons(self): - logo_box = Square(side_length = 2.5) - logo_box.to_corner(DOWN+LEFT, buff = MED_LARGE_BUFF) + 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 = 0, - width = FRAME_WIDTH, - height = 1.1*FRAME_Y_RADIUS + fill_color=BLACK, + fill_opacity=1, + stroke_width=0, + width=FRAME_WIDTH, + height=1.1 * FRAME_Y_RADIUS ) - black_rect.to_edge(UP, buff = 0) - line = DashedLine(FRAME_X_RADIUS*LEFT, FRAME_X_RADIUS*RIGHT) + black_rect.to_edge(UP, buff=0) + line = DashedLine(FRAME_X_RADIUS * LEFT, FRAME_X_RADIUS * RIGHT) line.move_to(black_rect, DOWN) - line.shift(SMALL_BUFF*SMALL_BUFF*DOWN) + line.shift(SMALL_BUFF * SMALL_BUFF * DOWN) self.add(line) patrons = VGroup(*map(TextMobject, self.specific_patrons)) @@ -181,34 +186,36 @@ class PatreonEndScreen(PatreonThanks): columns = VGroup(*[ VGroup( *patrons[i::self.n_patron_columns] - ).arrange_submobjects(DOWN, buff = MED_SMALL_BUFF) + ).arrange_submobjects(DOWN, buff=MED_SMALL_BUFF) for i in range(self.n_patron_columns) ]) columns.arrange_submobjects( - RIGHT, buff = LARGE_BUFF, - aligned_edge = UP, + RIGHT, buff=LARGE_BUFF, + aligned_edge=UP, ) columns.scale_to_fit_width(total_width - 1) - columns.next_to(black_rect, DOWN, 3*LARGE_BUFF) + columns.next_to(black_rect, DOWN, 3 * LARGE_BUFF) columns.to_edge(RIGHT) self.play( - columns.next_to, FRAME_Y_RADIUS*DOWN, UP, LARGE_BUFF, - columns.to_edge, RIGHT, + columns.next_to, FRAME_Y_RADIUS * DOWN, UP, LARGE_BUFF, + columns.to_edge, RIGHT, Animation(black_rect), - rate_func = None, - run_time = self.run_time, + rate_func=None, + run_time=self.run_time, ) + class ExternallyAnimatedScene(Scene): def construct(self): raise Exception("Don't actually run this class.") + class TODOStub(Scene): CONFIG = { - "message" : "" + "message": "" } - def construct(self): - self.add(TextMobject("TODO: %s"%self.message)) - self.wait() + def construct(self): + self.add(TextMobject("TODO: %s" % self.message)) + self.wait() diff --git a/for_3b1b_videos/pi_creature.py b/for_3b1b_videos/pi_creature.py index e0f53707..65bec1fe 100644 --- a/for_3b1b_videos/pi_creature.py +++ b/for_3b1b_videos/pi_creature.py @@ -19,46 +19,49 @@ from utils.rate_functions import there_and_back PI_CREATURE_DIR = os.path.join(MEDIA_DIR, "designs", "PiCreature") PI_CREATURE_SCALE_FACTOR = 0.5 -LEFT_EYE_INDEX = 0 -RIGHT_EYE_INDEX = 1 -LEFT_PUPIL_INDEX = 2 +LEFT_EYE_INDEX = 0 +RIGHT_EYE_INDEX = 1 +LEFT_PUPIL_INDEX = 2 RIGHT_PUPIL_INDEX = 3 -BODY_INDEX = 4 -MOUTH_INDEX = 5 +BODY_INDEX = 4 +MOUTH_INDEX = 5 + class PiCreature(SVGMobject): CONFIG = { - "color" : BLUE_E, - "file_name_prefix" : "PiCreatures", - "stroke_width" : 0, - "stroke_color" : BLACK, - "fill_opacity" : 1.0, - "propagate_style_to_family" : True, - "height" : 3, - "corner_scale_factor" : 0.75, - "flip_at_start" : False, - "is_looking_direction_purposeful" : False, - "start_corner" : None, - #Range of proportions along body where arms are - "right_arm_range" : [0.55, 0.7], - "left_arm_range" : [.34, .462], + "color": BLUE_E, + "file_name_prefix": "PiCreatures", + "stroke_width": 0, + "stroke_color": BLACK, + "fill_opacity": 1.0, + "propagate_style_to_family": True, + "height": 3, + "corner_scale_factor": 0.75, + "flip_at_start": False, + "is_looking_direction_purposeful": False, + "start_corner": None, + # Range of proportions along body where arms are + "right_arm_range": [0.55, 0.7], + "left_arm_range": [.34, .462], } - def __init__(self, mode = "plain", **kwargs): + + def __init__(self, mode="plain", **kwargs): digest_config(self, kwargs) self.parts_named = False try: svg_file = os.path.join( - PI_CREATURE_DIR, - "%s_%s.svg"%(self.file_name_prefix, mode) + PI_CREATURE_DIR, + "%s_%s.svg" % (self.file_name_prefix, mode) ) - SVGMobject.__init__(self, file_name = svg_file, **kwargs) + SVGMobject.__init__(self, file_name=svg_file, **kwargs) except: - warnings.warn("No %s design with mode %s"%(self.file_name_prefix, mode)) + warnings.warn("No %s design with mode %s" % + (self.file_name_prefix, mode)) svg_file = os.path.join( - FILE_DIR, + FILE_DIR, "PiCreatures_plain.svg", ) - SVGMobject.__init__(self, file_name = svg_file, **kwargs) + SVGMobject.__init__(self, file_name=svg_file, **kwargs) if self.flip_at_start: self.flip() @@ -83,10 +86,10 @@ class PiCreature(SVGMobject): SVGMobject.init_colors(self) if not self.parts_named: self.name_parts() - self.mouth.set_fill(BLACK, opacity = 1) - self.body.set_fill(self.color, opacity = 1) - self.pupils.set_fill(BLACK, opacity = 1) - self.eyes.set_fill(WHITE, opacity = 1) + self.mouth.set_fill(BLACK, opacity=1) + self.body.set_fill(self.color, opacity=1) + self.pupils.set_fill(BLACK, opacity=1) + self.eyes.set_fill(WHITE, opacity=1) return self def copy(self): @@ -100,8 +103,8 @@ class PiCreature(SVGMobject): def change_mode(self, mode): new_self = self.__class__( - mode = mode, - color = self.color + mode=mode, + color=self.color ) new_self.scale_to_fit_height(self.get_height()) if self.is_flipped() ^ new_self.is_flipped(): @@ -119,16 +122,16 @@ class PiCreature(SVGMobject): direction /= norm self.purposeful_looking_direction = direction for pupil, eye in zip(self.pupils.split(), self.eyes.split()): - pupil_radius = pupil.get_width()/2. - eye_radius = eye.get_width()/2. + pupil_radius = pupil.get_width() / 2. + eye_radius = eye.get_width() / 2. pupil.move_to(eye) if direction[1] < 0: - pupil.shift(pupil_radius*DOWN/3) - pupil.shift(direction*(eye_radius-pupil_radius)) + pupil.shift(pupil_radius * DOWN / 3) + pupil.shift(direction * (eye_radius - pupil_radius)) bottom_diff = eye.get_bottom()[1] - pupil.get_bottom()[1] if bottom_diff > 0: - pupil.shift(bottom_diff*UP) - #TODO, how to handle looking up... + pupil.shift(bottom_diff * UP) + # TODO, how to handle looking up... # top_diff = eye.get_top()[1]-pupil.get_top()[1] # if top_diff < 0: # pupil.shift(top_diff*UP) @@ -142,7 +145,7 @@ class PiCreature(SVGMobject): self.look(point - self.eyes.get_center()) return self - def change(self, new_mode, look_at_arg = None): + def change(self, new_mode, look_at_arg=None): self.change_mode(new_mode) if look_at_arg is not None: self.look_at(look_at_arg) @@ -151,27 +154,27 @@ class PiCreature(SVGMobject): def get_looking_direction(self): return np.sign(np.round( self.pupils.get_center() - self.eyes.get_center(), - decimals = 2 + decimals=2 )) def is_flipped(self): return self.eyes.submobjects[0].get_center()[0] > \ - self.eyes.submobjects[1].get_center()[0] + self.eyes.submobjects[1].get_center()[0] def blink(self): eye_parts = self.eye_parts eye_bottom_y = eye_parts.get_bottom()[1] eye_parts.apply_function( - lambda p : [p[0], eye_bottom_y, p[2]] + lambda p: [p[0], eye_bottom_y, p[2]] ) return self - def to_corner(self, vect = None, **kwargs): + def to_corner(self, vect=None, **kwargs): if vect is not None: SVGMobject.to_corner(self, vect, **kwargs) else: self.scale(self.corner_scale_factor) - self.to_corner(DOWN+LEFT, **kwargs) + self.to_corner(DOWN + LEFT, **kwargs) return self def get_bubble(self, *content, **kwargs): @@ -197,8 +200,8 @@ class PiCreature(SVGMobject): def shrug(self): self.change_mode("shruggie") top_mouth_point, bottom_mouth_point = [ - self.mouth.points[np.argmax(self.mouth.points[:,1])], - self.mouth.points[np.argmin(self.mouth.points[:,1])] + self.mouth.points[np.argmax(self.mouth.points[:, 1])], + self.mouth.points[np.argmin(self.mouth.points[:, 1])] ] self.look(top_mouth_point - bottom_mouth_point) return self @@ -210,9 +213,10 @@ class PiCreature(SVGMobject): for alpha_range in self.right_arm_range, self.left_arm_range ]) + def get_all_pi_creature_modes(): result = [] - prefix = "%s_"%PiCreature.CONFIG["file_name_prefix"] + prefix = "%s_" % PiCreature.CONFIG["file_name_prefix"] suffix = ".svg" for file in os.listdir(PI_CREATURE_DIR): if file.startswith(prefix) and file.endswith(suffix): @@ -221,69 +225,78 @@ def get_all_pi_creature_modes(): ) return result + class Randolph(PiCreature): - pass #Nothing more than an alternative name + pass # Nothing more than an alternative name + class Mortimer(PiCreature): CONFIG = { - "color" : GREY_BROWN, - "flip_at_start" : True, + "color": GREY_BROWN, + "flip_at_start": True, } + class Mathematician(PiCreature): CONFIG = { - "color" : GREY, + "color": GREY, } + class BabyPiCreature(PiCreature): CONFIG = { - "scale_factor" : 0.5, - "eye_scale_factor" : 1.2, - "pupil_scale_factor" : 1.3 + "scale_factor": 0.5, + "eye_scale_factor": 1.2, + "pupil_scale_factor": 1.3 } + def __init__(self, *args, **kwargs): PiCreature.__init__(self, *args, **kwargs) self.scale(self.scale_factor) self.shift(LEFT) - self.to_edge(DOWN, buff = LARGE_BUFF) + self.to_edge(DOWN, buff=LARGE_BUFF) eyes = VGroup(self.eyes, self.pupils) eyes_bottom = eyes.get_bottom() eyes.scale(self.eye_scale_factor) - eyes.move_to(eyes_bottom, aligned_edge = DOWN) + eyes.move_to(eyes_bottom, aligned_edge=DOWN) looking_direction = self.get_looking_direction() for pupil in self.pupils: pupil.scale_in_place(self.pupil_scale_factor) self.look(looking_direction) + class TauCreature(PiCreature): CONFIG = { - "file_name_prefix" : "TauCreatures" + "file_name_prefix": "TauCreatures" } + class ThreeLeggedPiCreature(PiCreature): CONFIG = { - "file_name_prefix" : "ThreeLeggedPiCreatures" + "file_name_prefix": "ThreeLeggedPiCreatures" } + class Eyes(VMobject): CONFIG = { - "height" : 0.3, - "thing_looked_at" : None, - "mode" : "plain", + "height": 0.3, + "thing_looked_at": None, + "mode": "plain", } + def __init__(self, mobject, **kwargs): VMobject.__init__(self, **kwargs) self.mobject = mobject self.submobjects = self.get_eyes().submobjects - def get_eyes(self, mode = None, thing_to_look_at = None): + def get_eyes(self, mode=None, thing_to_look_at=None): mode = mode or self.mode if thing_to_look_at is None: thing_to_look_at = self.thing_looked_at - pi = Randolph(mode = mode) + pi = Randolph(mode=mode) eyes = VGroup(pi.eyes, pi.pupils) - pi.scale(self.height/eyes.get_height()) + pi.scale(self.height / eyes.get_height()) if self.submobjects: eyes.move_to(self, DOWN) else: @@ -294,12 +307,12 @@ class Eyes(VMobject): def change_mode_anim(self, mode, **kwargs): self.mode = mode - return Transform(self, self.get_eyes(mode = mode), **kwargs) + return Transform(self, self.get_eyes(mode=mode), **kwargs) def look_at_anim(self, point_or_mobject, **kwargs): self.thing_looked_at = point_or_mobject return Transform( - self, self.get_eyes(thing_to_look_at = point_or_mobject), + self, self.get_eyes(thing_to_look_at=point_or_mobject), **kwargs ) @@ -308,10 +321,8 @@ class Eyes(VMobject): bottom_y = self.get_bottom()[1] for submob in target: submob.apply_function( - lambda p : [p[0], bottom_y, p[2]] + lambda p: [p[0], bottom_y, p[2]] ) if "rate_func" not in kwargs: kwargs["rate_func"] = squish_rate_func(there_and_back) return Transform(self, target, **kwargs) - - diff --git a/for_3b1b_videos/pi_creature_animations.py b/for_3b1b_videos/pi_creature_animations.py index 8ee4753b..352ae5ce 100644 --- a/for_3b1b_videos/pi_creature_animations.py +++ b/for_3b1b_videos/pi_creature_animations.py @@ -16,30 +16,34 @@ from utils.config_ops import digest_config from utils.rate_functions import squish_rate_func from utils.rate_functions import there_and_back + class Blink(ApplyMethod): CONFIG = { - "rate_func" : squish_rate_func(there_and_back) + "rate_func": squish_rate_func(there_and_back) } + def __init__(self, pi_creature, **kwargs): ApplyMethod.__init__(self, pi_creature.blink, **kwargs) + class PiCreatureBubbleIntroduction(AnimationGroup): CONFIG = { - "target_mode" : "speaking", - "bubble_class" : SpeechBubble, - "change_mode_kwargs" : {}, - "bubble_creation_class" : ShowCreation, - "bubble_creation_kwargs" : {}, - "bubble_kwargs" : {}, - "content_introduction_class" : Write, - "content_introduction_kwargs" : {}, - "look_at_arg" : None, + "target_mode": "speaking", + "bubble_class": SpeechBubble, + "change_mode_kwargs": {}, + "bubble_creation_class": ShowCreation, + "bubble_creation_kwargs": {}, + "bubble_kwargs": {}, + "content_introduction_class": Write, + "content_introduction_kwargs": {}, + "look_at_arg": None, } + def __init__(self, pi_creature, *content, **kwargs): digest_config(self, kwargs) bubble = pi_creature.get_bubble( *content, - bubble_class = self.bubble_class, + bubble_class=self.bubble_class, **self.bubble_kwargs ) Group(bubble, bubble.content).shift_onto_screen() @@ -61,18 +65,21 @@ class PiCreatureBubbleIntroduction(AnimationGroup): **kwargs ) + class PiCreatureSays(PiCreatureBubbleIntroduction): CONFIG = { - "target_mode" : "speaking", - "bubble_class" : SpeechBubble, + "target_mode": "speaking", + "bubble_class": SpeechBubble, } + class RemovePiCreatureBubble(AnimationGroup): CONFIG = { - "target_mode" : "plain", - "look_at_arg" : None, - "remover" : True, + "target_mode": "plain", + "look_at_arg": None, + "remover": True, } + def __init__(self, pi_creature, **kwargs): assert hasattr(pi_creature, "bubble") digest_config(self, kwargs, locals()) @@ -89,7 +96,7 @@ class RemovePiCreatureBubble(AnimationGroup): FadeOut(pi_creature.bubble.content), ) - def clean_up(self, surrounding_scene = None): + def clean_up(self, surrounding_scene=None): AnimationGroup.clean_up(self, surrounding_scene) self.pi_creature.bubble = None if surrounding_scene is not None: diff --git a/for_3b1b_videos/pi_creature_scene.py b/for_3b1b_videos/pi_creature_scene.py index ba84a55d..220e58a7 100644 --- a/for_3b1b_videos/pi_creature_scene.py +++ b/for_3b1b_videos/pi_creature_scene.py @@ -25,17 +25,19 @@ from scene.scene import Scene from utils.rate_functions import squish_rate_func from utils.rate_functions import there_and_back + class PiCreatureScene(Scene): CONFIG = { - "total_wait_time" : 0, - "seconds_to_blink" : 3, - "pi_creatures_start_on_screen" : True, - "default_pi_creature_kwargs" : { - "color" : GREY_BROWN, - "flip_at_start" : True, + "total_wait_time": 0, + "seconds_to_blink": 3, + "pi_creatures_start_on_screen": True, + "default_pi_creature_kwargs": { + "color": GREY_BROWN, + "flip_at_start": True, }, - "default_pi_creature_start_corner" : DOWN+LEFT, + "default_pi_creature_start_corner": DOWN + LEFT, } + def setup(self): self.pi_creatures = self.create_pi_creatures() self.pi_creature = self.get_primary_pi_creature() @@ -43,7 +45,7 @@ class PiCreatureScene(Scene): self.add(*self.pi_creatures) def create_pi_creatures(self): - """ + """ Likely updated for subclasses """ return VGroup(self.create_pi_creature()) @@ -66,7 +68,7 @@ class PiCreatureScene(Scene): def get_on_screen_pi_creatures(self): mobjects = self.get_mobjects() return VGroup(*filter( - lambda pi : pi in mobjects, + lambda pi: pi in mobjects, self.get_pi_creatures() )) @@ -80,7 +82,7 @@ class PiCreatureScene(Scene): bubble_class = kwargs.pop("bubble_class", SpeechBubble) target_mode = kwargs.pop( - "target_mode", + "target_mode", "thinking" if bubble_class is ThoughtBubble else "speaking" ) bubble_kwargs = kwargs.pop("bubble_kwargs", {}) @@ -91,10 +93,11 @@ class PiCreatureScene(Scene): on_screen_mobjects = self.camera.extract_mobject_family_members( self.get_mobjects() ) + def has_bubble(pi): return hasattr(pi, "bubble") and \ - pi.bubble is not None and \ - pi.bubble in on_screen_mobjects + pi.bubble is not None and \ + pi.bubble in on_screen_mobjects pi_creatures_with_bubbles = filter(has_bubble, self.get_pi_creatures()) if pi_creature in pi_creatures_with_bubbles: @@ -102,7 +105,7 @@ class PiCreatureScene(Scene): old_bubble = pi_creature.bubble bubble = pi_creature.get_bubble( *content, - bubble_class = bubble_class, + bubble_class=bubble_class, **bubble_kwargs ) anims += [ @@ -114,9 +117,9 @@ class PiCreatureScene(Scene): anims.append(PiCreatureBubbleIntroduction( pi_creature, *content, - bubble_class = bubble_class, - bubble_kwargs = bubble_kwargs, - target_mode = target_mode, + bubble_class=bubble_class, + bubble_kwargs=bubble_kwargs, + target_mode=target_mode, **kwargs )) anims += [ @@ -130,26 +133,28 @@ class PiCreatureScene(Scene): def pi_creature_says(self, *args, **kwargs): self.introduce_bubble( *args, - bubble_class = SpeechBubble, + bubble_class=SpeechBubble, **kwargs ) def pi_creature_thinks(self, *args, **kwargs): self.introduce_bubble( *args, - bubble_class = ThoughtBubble, + bubble_class=ThoughtBubble, **kwargs ) def say(self, *content, **kwargs): - self.pi_creature_says(self.get_primary_pi_creature(), *content, **kwargs) + self.pi_creature_says( + self.get_primary_pi_creature(), *content, **kwargs) def think(self, *content, **kwargs): - self.pi_creature_thinks(self.get_primary_pi_creature(), *content, **kwargs) + self.pi_creature_thinks( + self.get_primary_pi_creature(), *content, **kwargs) def compile_play_args_to_animation_list(self, *args): """ - Add animations so that all pi creatures look at the + Add animations so that all pi creatures look at the first mobject being animated with each .play call """ animations = Scene.compile_play_args_to_animation_list(self, *args) @@ -157,13 +162,13 @@ class PiCreatureScene(Scene): return animations non_pi_creature_anims = filter( - lambda anim : anim.mobject not in self.get_pi_creatures(), + lambda anim: anim.mobject not in self.get_pi_creatures(), animations ) if len(non_pi_creature_anims) == 0: return animations first_anim = non_pi_creature_anims[0] - #Look at ending state + # Look at ending state first_anim.update(1) point_of_interest = first_anim.mobject.get_center() first_anim.update(0) @@ -174,7 +179,7 @@ class PiCreatureScene(Scene): if pi_creature in first_anim.mobject.submobject_family(): continue anims_with_pi_creature = filter( - lambda anim : pi_creature in anim.mobject.submobject_family(), + lambda anim: pi_creature in anim.mobject.submobject_family(), animations ) for anim in anims_with_pi_creature: @@ -193,7 +198,7 @@ class PiCreatureScene(Scene): def blink(self): self.play(Blink(random.choice(self.get_on_screen_pi_creatures()))) - def joint_blink(self, pi_creatures = None, shuffle = True, **kwargs): + def joint_blink(self, pi_creatures=None, shuffle=True, **kwargs): if pi_creatures is None: pi_creatures = self.get_on_screen_pi_creatures() creatures_list = list(pi_creatures) @@ -202,25 +207,25 @@ class PiCreatureScene(Scene): def get_rate_func(pi): index = creatures_list.index(pi) - proportion = float(index)/len(creatures_list) - start_time = 0.8*proportion + proportion = float(index) / len(creatures_list) + start_time = 0.8 * proportion return squish_rate_func( there_and_back, start_time, start_time + 0.2 ) self.play(*[ - Blink(pi, rate_func = get_rate_func(pi), **kwargs) + Blink(pi, rate_func=get_rate_func(pi), **kwargs) for pi in creatures_list ]) return self - def wait(self, time = 1, blink = True): + def wait(self, time=1, blink=True): while time >= 1: - time_to_blink = self.total_wait_time%self.seconds_to_blink == 0 + time_to_blink = self.total_wait_time % self.seconds_to_blink == 0 if blink and self.any_pi_creatures_on_screen() and time_to_blink: self.blink() - self.num_plays -= 1 #This shouldn't count as an animation + self.num_plays -= 1 # This shouldn't count as an animation else: self.non_blink_wait() time -= 1 @@ -229,14 +234,14 @@ class PiCreatureScene(Scene): self.non_blink_wait(time) return self - def non_blink_wait(self, time = 1): + def non_blink_wait(self, time=1): Scene.wait(self, time) return self def change_mode(self, mode): self.play(self.get_primary_pi_creature().change_mode, mode) - def look_at(self, thing_to_look_at, pi_creatures = None): + def look_at(self, thing_to_look_at, pi_creatures=None): if pi_creatures is None: pi_creatures = self.get_pi_creatures() self.play(*it.chain(*[ @@ -244,30 +249,33 @@ class PiCreatureScene(Scene): for pi in pi_creatures ])) + class TeacherStudentsScene(PiCreatureScene): CONFIG = { - "student_colors" : [BLUE_D, BLUE_E, BLUE_C], - "student_scale_factor" : 0.8, - "seconds_to_blink" : 2, - "screen_height" : 3, + "student_colors": [BLUE_D, BLUE_E, BLUE_C], + "student_scale_factor": 0.8, + "seconds_to_blink": 2, + "screen_height": 3, } + def setup(self): PiCreatureScene.setup(self) - self.screen = ScreenRectangle(height = self.screen_height) - self.screen.to_corner(UP+LEFT) - self.hold_up_spot = self.teacher.get_corner(UP+LEFT) + MED_LARGE_BUFF*UP + self.screen = ScreenRectangle(height=self.screen_height) + self.screen.to_corner(UP + LEFT) + self.hold_up_spot = self.teacher.get_corner( + UP + LEFT) + MED_LARGE_BUFF * UP def create_pi_creatures(self): self.teacher = Mortimer() self.teacher.to_corner(DOWN + RIGHT) - self.teacher.look(DOWN+LEFT) + self.teacher.look(DOWN + LEFT) self.students = VGroup(*[ - Randolph(color = c) + Randolph(color=c) for c in self.student_colors ]) self.students.arrange_submobjects(RIGHT) self.students.scale(self.student_scale_factor) - self.students.to_corner(DOWN+LEFT) + self.students.to_corner(DOWN + LEFT) self.teacher.look_at(self.students[-1].eyes) for student in self.students: student.look_at(self.teacher.eyes) @@ -288,8 +296,8 @@ class TeacherStudentsScene(PiCreatureScene): def student_says(self, *content, **kwargs): if "target_mode" not in kwargs: target_mode = random.choice([ - "raise_right_hand", - "raise_left_hand", + "raise_right_hand", + "raise_left_hand", ]) kwargs["target_mode"] = target_mode student = self.get_students()[kwargs.get("student_index", 1)] @@ -307,7 +315,7 @@ class TeacherStudentsScene(PiCreatureScene): return self.pi_creature_thinks(student, *content, **kwargs) def change_all_student_modes(self, mode, **kwargs): - self.change_student_modes(*[mode]*len(self.students), **kwargs) + self.change_student_modes(*[mode] * len(self.students), **kwargs) def change_student_modes(self, *modes, **kwargs): added_anims = kwargs.pop("added_anims", []) @@ -326,12 +334,12 @@ class TeacherStudentsScene(PiCreatureScene): pi.look_at(kwargs["look_at_arg"]) submobject_mode = kwargs.get("submobject_mode", "lagged_start") return Transform( - start, target, - submobject_mode = submobject_mode, - run_time = 2 + start, target, + submobject_mode=submobject_mode, + run_time=2 ) - def zoom_in_on_thought_bubble(self, bubble = None, radius = FRAME_Y_RADIUS+FRAME_X_RADIUS): + def zoom_in_on_thought_bubble(self, bubble=None, radius=FRAME_Y_RADIUS + FRAME_X_RADIUS): if bubble is None: for pi in self.get_pi_creatures(): if hasattr(pi, "bubble") and isinstance(pi.bubble, ThoughtBubble): @@ -340,15 +348,16 @@ class TeacherStudentsScene(PiCreatureScene): if bubble is None: raise Exception("No pi creatures have a thought bubble") vect = -bubble.get_bubble_center() + def func(point): - centered = point+vect - return radius*centered/np.linalg.norm(centered) + centered = point + vect + return radius * centered / np.linalg.norm(centered) self.play(*[ ApplyPointwiseFunction(func, mob) for mob in self.get_mobjects() ]) - def teacher_holds_up(self, mobject, target_mode = "raise_right_hand", **kwargs): + def teacher_holds_up(self, mobject, target_mode="raise_right_hand", **kwargs): mobject.move_to(self.hold_up_spot, DOWN) mobject.shift_onto_screen() mobject_copy = mobject.copy() @@ -358,5 +367,3 @@ class TeacherStudentsScene(PiCreatureScene): ReplacementTransform(mobject_copy, mobject), self.teacher.change, target_mode, ) - - diff --git a/mobject/coordinate_systems.py b/mobject/coordinate_systems.py index ece9e3d4..bb6f49ef 100644 --- a/mobject/coordinate_systems.py +++ b/mobject/coordinate_systems.py @@ -12,51 +12,52 @@ from mobject.svg.tex_mobject import TexMobject from mobject.types.vectorized_mobject import VGroup from mobject.types.vectorized_mobject import VMobject from utils.config_ops import digest_config -from utils.space_ops import R3_to_complex from utils.space_ops import angle_of_vector -from utils.space_ops import complex_to_R3 -#TODO: There should be much more code reuse between Axes, NumberPlane and GraphScene +# TODO: There should be much more code reuse between Axes, NumberPlane and GraphScene + class Axes(VGroup): CONFIG = { - "propagate_style_to_family" : True, - "three_d" : False, - "number_line_config" : { - "color" : LIGHT_GREY, - "include_tip" : True, + "propagate_style_to_family": True, + "three_d": False, + "number_line_config": { + "color": LIGHT_GREY, + "include_tip": True, }, - "x_axis_config" : {}, - "y_axis_config" : {}, - "z_axis_config" : {}, - "x_min" : -FRAME_X_RADIUS, - "x_max" : FRAME_X_RADIUS, - "y_min" : -FRAME_Y_RADIUS, - "y_max" : FRAME_Y_RADIUS, - "z_min" : -3.5, - "z_max" : 3.5, - "z_normal" : DOWN, - "default_num_graph_points" : 100, + "x_axis_config": {}, + "y_axis_config": {}, + "z_axis_config": {}, + "x_min": -FRAME_X_RADIUS, + "x_max": FRAME_X_RADIUS, + "y_min": -FRAME_Y_RADIUS, + "y_max": FRAME_Y_RADIUS, + "z_min": -3.5, + "z_max": 3.5, + "z_normal": DOWN, + "default_num_graph_points": 100, } + def __init__(self, **kwargs): VGroup.__init__(self, **kwargs) self.x_axis = self.get_axis(self.x_min, self.x_max, self.x_axis_config) self.y_axis = self.get_axis(self.y_min, self.y_max, self.y_axis_config) - self.y_axis.rotate(np.pi/2, about_point = ORIGIN) + self.y_axis.rotate(np.pi / 2, about_point=ORIGIN) self.add(self.x_axis, self.y_axis) if self.three_d: - self.z_axis = self.get_axis(self.z_min, self.z_max, self.z_axis_config) - self.z_axis.rotate(-np.pi/2, UP, about_point = ORIGIN) + self.z_axis = self.get_axis( + self.z_min, self.z_max, self.z_axis_config) + self.z_axis.rotate(-np.pi / 2, UP, about_point=ORIGIN) self.z_axis.rotate( angle_of_vector(self.z_normal), OUT, - about_point = ORIGIN + about_point=ORIGIN ) self.add(self.z_axis) def get_axis(self, min_val, max_val, extra_config): config = dict(self.number_line_config) config.update(extra_config) - return NumberLine(x_min = min_val, x_max = max_val, **config) + return NumberLine(x_min=min_val, x_max=max_val, **config) def coords_to_point(self, x, y): origin = self.x_axis.number_to_point(0) @@ -66,25 +67,25 @@ class Axes(VGroup): def point_to_coords(self, point): return ( - self.x_axis.point_to_number(point), + self.x_axis.point_to_number(point), self.y_axis.point_to_number(point), ) def get_graph( - self, function, num_graph_points = None, - x_min = None, - x_max = None, + self, function, num_graph_points=None, + x_min=None, + x_max=None, **kwargs - ): + ): kwargs["fill_opacity"] = kwargs.get("fill_opacity", 0) kwargs["num_anchor_points"] = \ num_graph_points or self.default_num_graph_points x_min = x_min or self.x_min x_max = x_max or self.x_max graph = ParametricFunction( - lambda t : self.coords_to_point(t, function(t)), - t_min = x_min, - t_max = x_max, + lambda t: self.coords_to_point(t, function(t)), + t_min=x_min, + t_max=x_max, **kwargs ) graph.underlying_function = function @@ -94,7 +95,7 @@ class Axes(VGroup): if hasattr(graph, "underlying_function"): return self.coords_to_point(x, graph.underlying_function(x)) else: - #binary search + # binary search lh, rh = 0, 1 while abs(lh - rh) > 0.001: mh = np.mean([lh, rh]) @@ -115,66 +116,69 @@ class Axes(VGroup): return points[1] return self.coords_to_point(x, graph.underlying_function(x)) + class ThreeDAxes(Axes): CONFIG = { - "x_min" : -5.5, - "x_max" : 5.5, - "y_min" : -4.5, - "y_max" : 4.5, - "three_d" : True, + "x_min": -5.5, + "x_max": 5.5, + "y_min": -4.5, + "y_max": 4.5, + "three_d": True, } + class NumberPlane(VMobject): CONFIG = { - "color" : BLUE_D, - "secondary_color" : BLUE_E, - "axes_color" : WHITE, - "secondary_stroke_width" : 1, + "color": BLUE_D, + "secondary_color": BLUE_E, + "axes_color": WHITE, + "secondary_stroke_width": 1, # TODO: Allow coordinate center of NumberPlane to not be at (0, 0) "x_radius": None, "y_radius": None, - "x_unit_size" : 1, - "y_unit_size" : 1, - "center_point" : ORIGIN, - "x_line_frequency" : 1, - "y_line_frequency" : 1, - "secondary_line_ratio" : 1, - "written_coordinate_height" : 0.2, - "propagate_style_to_family" : False, - "make_smooth_after_applying_functions" : True, + "x_unit_size": 1, + "y_unit_size": 1, + "center_point": ORIGIN, + "x_line_frequency": 1, + "y_line_frequency": 1, + "secondary_line_ratio": 1, + "written_coordinate_height": 0.2, + "propagate_style_to_family": False, + "make_smooth_after_applying_functions": True, } + def generate_points(self): if self.x_radius is None: - center_to_edge = (FRAME_X_RADIUS + abs(self.center_point[0])) + center_to_edge = (FRAME_X_RADIUS + abs(self.center_point[0])) self.x_radius = center_to_edge / self.x_unit_size if self.y_radius is None: - center_to_edge = (FRAME_Y_RADIUS + abs(self.center_point[1])) + center_to_edge = (FRAME_Y_RADIUS + abs(self.center_point[1])) self.y_radius = center_to_edge / self.y_unit_size self.axes = VMobject() self.main_lines = VMobject() self.secondary_lines = VMobject() tuples = [ ( - self.x_radius, - self.x_line_frequency, - self.y_radius*DOWN, - self.y_radius*UP, + self.x_radius, + self.x_line_frequency, + self.y_radius * DOWN, + self.y_radius * UP, RIGHT ), ( - self.y_radius, - self.y_line_frequency, - self.x_radius*LEFT, - self.x_radius*RIGHT, + self.y_radius, + self.y_line_frequency, + self.x_radius * LEFT, + self.x_radius * RIGHT, UP, ), ] for radius, freq, start, end, unit in tuples: main_range = np.arange(0, radius, freq) - step = freq/float(freq + self.secondary_line_ratio) + step = freq / float(freq + self.secondary_line_ratio) for v in np.arange(0, radius, step): - line1 = Line(start+v*unit, end+v*unit) - line2 = Line(start-v*unit, end-v*unit) + line1 = Line(start + v * unit, end + v * unit) + line2 = Line(start - v * unit, end - v * unit) if v == 0: self.axes.add(line1) elif v in main_range: @@ -185,7 +189,7 @@ class NumberPlane(VMobject): self.stretch(self.x_unit_size, 0) self.stretch(self.y_unit_size, 1) self.shift(self.center_point) - #Put x_axis before y_axis + # Put x_axis before y_axis y_axis, x_axis = self.axes.split() self.axes = VMobject(x_axis, y_axis) @@ -204,39 +208,39 @@ class NumberPlane(VMobject): def coords_to_point(self, x, y): x, y = np.array([x, y]) result = self.axes.get_center() - result += x*self.get_x_unit_size()*RIGHT - result += y*self.get_y_unit_size()*UP + result += x * self.get_x_unit_size() * RIGHT + result += y * self.get_y_unit_size() * UP return result def point_to_coords(self, point): new_point = point - self.axes.get_center() - x = new_point[0]/self.get_x_unit_size() - y = new_point[1]/self.get_y_unit_size() + x = new_point[0] / self.get_x_unit_size() + y = new_point[1] / self.get_y_unit_size() return x, y # Does not recompute center, unit_sizes for each call; useful for - # iterating over large lists of points, but does assume these + # iterating over large lists of points, but does assume these # attributes are kept accurate. (Could alternatively have a method - # which returns a function dynamically created after a single + # which returns a function dynamically created after a single # call to each of get_center(), get_x_unit_size(), etc.) def point_to_coords_cheap(self, point): new_point = point - self.center_point - x = new_point[0]/self.x_unit_size - y = new_point[1]/self.y_unit_size + x = new_point[0] / self.x_unit_size + y = new_point[1] / self.y_unit_size return x, y def get_x_unit_size(self): - return self.axes.get_width() / (2.0*self.x_radius) + return self.axes.get_width() / (2.0 * self.x_radius) def get_y_unit_size(self): - return self.axes.get_height() / (2.0*self.y_radius) + return self.axes.get_height() / (2.0 * self.y_radius) - def get_coordinate_labels(self, x_vals = None, y_vals = None): + def get_coordinate_labels(self, x_vals=None, y_vals=None): coordinate_labels = VGroup() - if x_vals == None: - x_vals = range(-int(self.x_radius), int(self.x_radius)+1) - if y_vals == None: - y_vals = range(-int(self.y_radius), int(self.y_radius)+1) + if x_vals is None: + x_vals = range(-int(self.x_radius), int(self.x_radius) + 1) + if y_vals is None: + y_vals = range(-int(self.y_radius), int(self.y_radius) + 1) for index, vals in enumerate([x_vals, y_vals]): num_pair = [0, 0] for val in vals: @@ -249,7 +253,7 @@ class NumberPlane(VMobject): num.scale_to_fit_height( self.written_coordinate_height ) - num.next_to(point, DOWN+LEFT, buff = SMALL_BUFF) + num.next_to(point, DOWN + LEFT, buff=SMALL_BUFF) coordinate_labels.add(num) self.coordinate_labels = coordinate_labels return coordinate_labels @@ -257,7 +261,7 @@ class NumberPlane(VMobject): def get_axes(self): return self.axes - def get_axis_labels(self, x_label = "x", y_label = "y"): + def get_axis_labels(self, x_label="x", y_label="y"): x_axis, y_axis = self.get_axes().split() quads = [ (x_axis, x_label, UP, RIGHT), @@ -273,39 +277,42 @@ class NumberPlane(VMobject): self.axis_labels = labels return labels - def add_coordinates(self, x_vals = None, y_vals = None): + def add_coordinates(self, x_vals=None, y_vals=None): self.add(*self.get_coordinate_labels(x_vals, y_vals)) return self def get_vector(self, coords, **kwargs): - point = coords[0]*RIGHT + coords[1]*UP - arrow = Arrow(ORIGIN, coords, **kwargs) + point = coords[0] * RIGHT + coords[1] * UP + arrow = Arrow(ORIGIN, point, **kwargs) return arrow - def prepare_for_nonlinear_transform(self, num_inserted_anchor_points = 50): + def prepare_for_nonlinear_transform(self, num_inserted_anchor_points=50): for mob in self.family_members_with_points(): num_anchors = mob.get_num_anchor_points() if num_inserted_anchor_points > num_anchors: - mob.insert_n_anchor_points(num_inserted_anchor_points-num_anchors) + mob.insert_n_anchor_points( + num_inserted_anchor_points - num_anchors) mob.make_smooth() return self + class ComplexPlane(NumberPlane): CONFIG = { - "color" : BLUE, - "unit_size" : 1, - "line_frequency" : 1, - "faded_line_frequency" : 0.5, + "color": BLUE, + "unit_size": 1, + "line_frequency": 1, + "faded_line_frequency": 0.5, } + def __init__(self, **kwargs): digest_config(self, kwargs) kwargs.update({ - "x_unit_size" : self.unit_size, - "y_unit_size" : self.unit_size, - "x_line_frequency" : self.line_frequency, - "x_faded_line_frequency" : self.faded_line_frequency, - "y_line_frequency" : self.line_frequency, - "y_faded_line_frequency" : self.faded_line_frequency, + "x_unit_size": self.unit_size, + "y_unit_size": self.unit_size, + "x_line_frequency": self.line_frequency, + "x_faded_line_frequency": self.faded_line_frequency, + "y_line_frequency": self.line_frequency, + "y_faded_line_frequency": self.faded_line_frequency, }) NumberPlane.__init__(self, **kwargs) @@ -321,12 +328,11 @@ class ComplexPlane(NumberPlane): # TODO: Should merge this with the code from NumberPlane.get_coordinate_labels result = VGroup() - nudge = 0.1*(DOWN+RIGHT) if len(numbers) == 0: - numbers = range(-int(self.x_radius), int(self.x_radius)+1) + numbers = range(-int(self.x_radius), int(self.x_radius) + 1) numbers += [ complex(0, y) - for y in range(-int(self.y_radius), int(self.y_radius)+1) + for y in range(-int(self.y_radius), int(self.y_radius) + 1) ] for number in numbers: if number == complex(0, 0): @@ -340,7 +346,7 @@ class ComplexPlane(NumberPlane): num_mob = TexMobject(num_str) num_mob.add_background_rectangle() num_mob.scale_to_fit_height(self.written_coordinate_height) - num_mob.next_to(point, DOWN+LEFT, SMALL_BUFF) + num_mob.next_to(point, DOWN + LEFT, SMALL_BUFF) result.add(num_mob) self.coordinate_labels = result return result @@ -348,4 +354,3 @@ class ComplexPlane(NumberPlane): def add_coordinates(self, *numbers): self.add(*self.get_coordinate_labels(*numbers)) return self - diff --git a/mobject/frame.py b/mobject/frame.py index 0ca977a6..cd51dcf6 100644 --- a/mobject/frame.py +++ b/mobject/frame.py @@ -4,32 +4,38 @@ from constants import * from mobject.geometry import Rectangle from utils.config_ops import digest_config + class ScreenRectangle(Rectangle): CONFIG = { - "width_to_height_ratio" : 16.0/9.0, - "height" : 4, + "width_to_height_ratio": 16.0 / 9.0, + "height": 4, } + def generate_points(self): self.width = self.width_to_height_ratio * self.height Rectangle.generate_points(self) + class FullScreenRectangle(ScreenRectangle): CONFIG = { - "height" : FRAME_HEIGHT, + "height": FRAME_HEIGHT, } + class FullScreenFadeRectangle(FullScreenRectangle): CONFIG = { - "stroke_width" : 0, - "fill_color" : BLACK, - "fill_opacity" : 0.7, + "stroke_width": 0, + "fill_color": BLACK, + "fill_opacity": 0.7, } + class PictureInPictureFrame(Rectangle): CONFIG = { - "height" : 3, - "aspect_ratio" : (16, 9) + "height": 3, + "aspect_ratio": (16, 9) } + def __init__(self, **kwargs): digest_config(self, kwargs) height = self.height @@ -37,8 +43,8 @@ class PictureInPictureFrame(Rectangle): kwargs.pop("height") Rectangle.__init__( self, - width = self.aspect_ratio[0], - height = self.aspect_ratio[1], + width=self.aspect_ratio[0], + height=self.aspect_ratio[1], **kwargs ) self.scale_to_fit_height(height) diff --git a/mobject/functions.py b/mobject/functions.py index 0d421e66..e6560580 100644 --- a/mobject/functions.py +++ b/mobject/functions.py @@ -8,57 +8,46 @@ from utils.config_ops import digest_config class ParametricFunction(VMobject): CONFIG = { - "t_min" : 0, - "t_max" : 1, - "num_anchor_points" : 100, + "t_min": 0, + "t_max": 1, + "num_anchor_points": 100, } + def __init__(self, function, **kwargs): self.function = function VMobject.__init__(self, **kwargs) def generate_points(self): - n_points = 3*self.num_anchor_points - 2 + n_points = 3 * self.num_anchor_points - 2 self.points = np.zeros((n_points, self.dim)) - self.points[:,0] = np.linspace( + self.points[:, 0] = np.linspace( self.t_min, self.t_max, n_points ) - #VMobject.apply_function takes care of preserving - #desirable tangent line properties at anchor points - self.apply_function(lambda p : self.function(p[0])) + # VMobject.apply_function takes care of preserving + # desirable tangent line properties at anchor points + self.apply_function(lambda p: self.function(p[0])) + class FunctionGraph(ParametricFunction): CONFIG = { - "color" : YELLOW, - "x_min" : -FRAME_X_RADIUS, - "x_max" : FRAME_X_RADIUS, + "color": YELLOW, + "x_min": -FRAME_X_RADIUS, + "x_max": FRAME_X_RADIUS, } + def __init__(self, function, **kwargs): digest_config(self, kwargs) - parametric_function = lambda t : t*RIGHT + function(t)*UP + + def parametric_function(t): + return t * RIGHT + function(t) * UP ParametricFunction.__init__( - self, + self, parametric_function, - t_min = self.x_min, - t_max = self.x_max, + t_min=self.x_min, + t_max=self.x_max, **kwargs ) self.function = function def get_function(self): return self.function - - - - - - - - - - - - - - - - diff --git a/mobject/geometry.py b/mobject/geometry.py index e3fee62b..79896fa4 100644 --- a/mobject/geometry.py +++ b/mobject/geometry.py @@ -17,156 +17,159 @@ from utils.space_ops import center_of_mass from utils.space_ops import compass_directions from utils.space_ops import rotate_vector + class Arc(VMobject): CONFIG = { - "radius" : 1.0, - "start_angle" : 0, - "num_anchors" : 9, - "anchors_span_full_range" : True, + "radius": 1.0, + "start_angle": 0, + "num_anchors": 9, + "anchors_span_full_range": True, } + def __init__(self, angle, **kwargs): self.angle = angle VMobject.__init__(self, **kwargs) def generate_points(self): anchors = np.array([ - np.cos(a)*RIGHT+np.sin(a)*UP + np.cos(a) * RIGHT + np.sin(a) * UP for a in np.linspace( - self.start_angle, - self.start_angle + self.angle, + self.start_angle, + self.start_angle + self.angle, self.num_anchors ) ]) - #Figure out which control points will give the - #Appropriate tangent lines to the circle - d_theta = self.angle/(self.num_anchors-1.0) + # Figure out which control points will give the + # Appropriate tangent lines to the circle + d_theta = self.angle / (self.num_anchors - 1.0) tangent_vectors = np.zeros(anchors.shape) - tangent_vectors[:,1] = anchors[:,0] - tangent_vectors[:,0] = -anchors[:,1] - handles1 = anchors[:-1] + (d_theta/3)*tangent_vectors[:-1] - handles2 = anchors[1:] - (d_theta/3)*tangent_vectors[1:] + tangent_vectors[:, 1] = anchors[:, 0] + tangent_vectors[:, 0] = -anchors[:, 1] + handles1 = anchors[:-1] + (d_theta / 3) * tangent_vectors[:-1] + handles2 = anchors[1:] - (d_theta / 3) * tangent_vectors[1:] self.set_anchors_and_handles( anchors, handles1, handles2 ) - self.scale(self.radius, about_point = ORIGIN) + self.scale(self.radius, about_point=ORIGIN) - def add_tip(self, tip_length = 0.25, at_start = False, at_end = True): + def add_tip(self, tip_length=0.25, at_start=False, at_end=True): # clear out any old tips for submob in self.submobjects: - if submob.mark_paths_closed == True: # is a tip + if submob.mark_paths_closed: self.remove(submob) - #TODO, do this a better way + # TODO, do this a better way p1 = p2 = p3 = p4 = None start_arrow = end_arrow = None if at_end: p1, p2 = self.points[-3:-1] # self.points[-2:] did overshoot start_arrow = Arrow( - p1, 2*p2 - p1, - tip_length = tip_length, - max_tip_length_to_length_ratio = 2.0 + p1, 2 * p2 - p1, + tip_length=tip_length, + max_tip_length_to_length_ratio=2.0 ) - self.add(start_arrow.split()[-1]) # just the tip + self.add(start_arrow.split()[-1]) # just the tip if at_start: p4, p3 = self.points[1:3] # self.points[:2] did overshoot end_arrow = Arrow( - p3, 2*p4 - p3, - tip_length = tip_length, - max_tip_length_to_length_ratio = 2.0 + p3, 2 * p4 - p3, + tip_length=tip_length, + max_tip_length_to_length_ratio=2.0 ) self.add(end_arrow.split()[-1]) - - - - self.set_color(self.get_color()) return self - - def get_arc_center(self): first_point = self.points[0] - radial_unit_vector = np.array([np.cos(self.start_angle),np.sin(self.start_angle),0]) + radial_unit_vector = np.array( + [np.cos(self.start_angle), np.sin(self.start_angle), 0]) arc_center = first_point - self.radius * radial_unit_vector return arc_center - - def move_arc_center_to(self,point): + def move_arc_center_to(self, point): v = point - self.get_arc_center() self.shift(v) return self - def stop_angle(self): return self.start_angle + self.angle - def set_bound_angles(self,start=0,stop=np.pi): + def set_bound_angles(self, start=0, stop=np.pi): self.start_angle = start self.angle = stop - start - + return self + class ArcBetweenPoints(Arc): - def __init__(self, start_point, end_point, angle = TAU/4, **kwargs): + def __init__(self, start_point, end_point, angle=TAU / 4, **kwargs): if angle == 0: raise Exception("Arc with zero curve angle: use Line instead.") midpoint = 0.5 * (start_point + end_point) distance_vector = end_point - start_point - normal_vector = np.array([-distance_vector[1], distance_vector[0],0]) + normal_vector = np.array([-distance_vector[1], distance_vector[0], 0]) distance = np.linalg.norm(normal_vector) normal_vector /= distance if angle < 0: normal_vector *= -1 - radius = distance/2 / np.sin(0.5 * np.abs(angle)) - l = distance/2 / np.tan(0.5 * np.abs(angle)) - arc_center = midpoint + l * normal_vector + radius = distance / 2 / np.sin(0.5 * np.abs(angle)) + length = distance / 2 / np.tan(0.5 * np.abs(angle)) + arc_center = midpoint + length * normal_vector w = start_point - arc_center if w[0] != 0: - start_angle = np.arctan2(w[1],w[0]) + start_angle = np.arctan2(w[1], w[0]) else: - start_angle = np.pi/2 + start_angle = np.pi / 2 Arc.__init__(self, angle, - radius = radius, - start_angle = start_angle, - **kwargs) - + radius=radius, + start_angle=start_angle, + **kwargs) + self.move_arc_center_to(arc_center) + class CurvedArrow(ArcBetweenPoints): - def __init__(self, start_point, end_point, angle = TAU/4, **kwargs): + def __init__(self, start_point, end_point, angle=TAU / 4, **kwargs): # I know this is in reverse, but it works if angle >= 0: - ArcBetweenPoints.__init__(self, start_point, end_point, angle = angle, **kwargs) - self.add_tip(at_start = True, at_end = False) + ArcBetweenPoints.__init__( + self, start_point, end_point, angle=angle, **kwargs) + self.add_tip(at_start=True, at_end=False) else: - ArcBetweenPoints.__init__(self, end_point, start_point, angle = -angle, **kwargs) - self.add_tip(at_start = False, at_end = True) + ArcBetweenPoints.__init__( + self, end_point, start_point, angle=-angle, **kwargs) + self.add_tip(at_start=False, at_end=True) + class CurvedDoubleArrow(ArcBetweenPoints): - def __init__(self, start_point, end_point, angle = TAU/4, **kwargs): - ArcBetweenPoints.__init__(self, start_point, end_point, angle = angle, **kwargs) - self.add_tip(at_start = True, at_end = True) + def __init__(self, start_point, end_point, angle=TAU / 4, **kwargs): + ArcBetweenPoints.__init__( + self, start_point, end_point, angle=angle, **kwargs) + self.add_tip(at_start=True, at_end=True) + class Circle(Arc): CONFIG = { - "color" : RED, - "close_new_points" : True, - "anchors_span_full_range" : False + "color": RED, + "close_new_points": True, + "anchors_span_full_range": False } - def __init__(self, **kwargs): - Arc.__init__(self, 2*np.pi, **kwargs) - def surround(self, mobject, dim_to_match = 0, stretch = False, buffer_factor = 1.2): + def __init__(self, **kwargs): + Arc.__init__(self, 2 * np.pi, **kwargs) + + def surround(self, mobject, dim_to_match=0, stretch=False, buffer_factor=1.2): # Ignores dim_to_match and stretch; result will always be a circle # TODO: Perhaps create an ellipse class to handle singele-dimension stretching @@ -174,54 +177,60 @@ class Circle(Arc): # TODO: Figure out and fix self.replace(mobject, dim_to_match, stretch) - self.scale_to_fit_width(np.sqrt(mobject.get_width()**2 + mobject.get_height()**2)) + self.scale_to_fit_width( + np.sqrt(mobject.get_width()**2 + mobject.get_height()**2)) self.scale(buffer_factor) + class Dot(Circle): CONFIG = { - "radius" : 0.08, - "stroke_width" : 0, - "fill_opacity" : 1.0, - "color" : WHITE + "radius": 0.08, + "stroke_width": 0, + "fill_opacity": 1.0, + "color": WHITE } - def __init__(self, point = ORIGIN, **kwargs): + + def __init__(self, point=ORIGIN, **kwargs): Circle.__init__(self, **kwargs) self.shift(point) self.init_colors() + class Ellipse(VMobject): CONFIG = { - "width" : 2, - "height" : 1 + "width": 2, + "height": 1 } def generate_points(self): - circle = Circle(radius = 1) + circle = Circle(radius=1) circle = circle.stretch_to_fit_width(self.width) circle = circle.stretch_to_fit_height(self.height) self.points = circle.points + class AnnularSector(VMobject): CONFIG = { - "inner_radius" : 1, - "outer_radius" : 2, - "angle" : TAU/4, - "start_angle" : 0, - "fill_opacity" : 1, - "stroke_width" : 0, - "color" : WHITE, - "mark_paths_closed" : True, + "inner_radius": 1, + "outer_radius": 2, + "angle": TAU / 4, + "start_angle": 0, + "fill_opacity": 1, + "stroke_width": 0, + "color": WHITE, + "mark_paths_closed": True, } + def generate_points(self): arc1 = Arc( - angle = self.angle, - start_angle = self.start_angle, - radius = self.inner_radius, + angle=self.angle, + start_angle=self.start_angle, + radius=self.inner_radius, ) arc2 = Arc( - angle = -1*self.angle, - start_angle = self.start_angle+self.angle, - radius = self.outer_radius, + angle=-1 * self.angle, + start_angle=self.start_angle + self.angle, + radius=self.outer_radius, ) a1_to_a2_points = np.array([ interpolate(arc1.points[-1], arc2.points[0], alpha) @@ -236,24 +245,24 @@ class AnnularSector(VMobject): self.add_control_points(arc2.points[1:]) self.add_control_points(a2_to_a1_points[1:]) - def get_arc_center(self): first_point = self.points[0] last_point = self.points[-2] v = last_point - first_point - radial_unit_vector = v/np.linalg.norm(v) + radial_unit_vector = v / np.linalg.norm(v) arc_center = first_point - self.inner_radius * radial_unit_vector return arc_center - def move_arc_center_to(self,point): + def move_arc_center_to(self, point): v = point - self.get_arc_center() self.shift(v) return self + class Sector(AnnularSector): CONFIG = { - "outer_radius" : 1, - "inner_radius" : 0 + "outer_radius": 1, + "inner_radius": 0 } @property @@ -261,35 +270,38 @@ class Sector(AnnularSector): return self.outer_radius @radius.setter - def radius(self,new_radius): + def radius(self, new_radius): self.outer_radius = new_radius + class Annulus(Circle): CONFIG = { "inner_radius": 1, "outer_radius": 2, - "fill_opacity" : 1, - "stroke_width" : 0, - "color" : WHITE, - "mark_paths_closed" : False, - "propagate_style_to_family" : True + "fill_opacity": 1, + "stroke_width": 0, + "color": WHITE, + "mark_paths_closed": False, + "propagate_style_to_family": True } def generate_points(self): self.points = [] self.radius = self.outer_radius - outer_circle = Circle(radius = self.outer_radius) + outer_circle = Circle(radius=self.outer_radius) inner_circle = Circle(radius=self.inner_radius) inner_circle.flip() self.points = outer_circle.points self.add_subpath(inner_circle.points) + class Line(VMobject): CONFIG = { - "buff" : 0, - "path_arc" : None, # angle of arc specified here - "n_arc_anchors" : 10, #Only used if path_arc is not None + "buff": 0, + "path_arc": None, # angle of arc specified here + "n_arc_anchors": 10, # Only used if path_arc is not None } + def __init__(self, start, end, **kwargs): digest_config(self, kwargs) self.set_start_and_end(start, end) @@ -306,13 +318,13 @@ class Line(VMobject): ]) self.account_for_buff() - def set_path_arc(self,new_value): + def set_path_arc(self, new_value): self.path_arc = new_value self.generate_points() def account_for_buff(self): length = self.get_arc_length() - if length < 2*self.buff or self.buff == 0: + if length < 2 * self.buff or self.buff == 0: return buff_proportion = self.buff / length self.pointwise_become_partial( @@ -325,7 +337,7 @@ class Line(VMobject): longer_dim = np.argmax(map(abs, start_to_end)) vect[longer_dim] = start_to_end[longer_dim] self.start, self.end = [ - arg.get_edge_center(unit*vect) + arg.get_edge_center(unit * vect) if isinstance(arg, Mobject) else np.array(arg) for arg, unit in zip([start, end], [1, -1]) @@ -344,7 +356,7 @@ class Line(VMobject): if self.path_arc: anchors = self.get_anchors() return sum([ - np.linalg.norm(a2-a1) + np.linalg.norm(a2 - a1) for a1, a2 in zip(anchors, anchors[1:]) ]) else: @@ -368,11 +380,11 @@ class Line(VMobject): float(end[i] - start[i]) for i in [1, 0] ] - return np.inf if run == 0 else rise/run + return np.inf if run == 0 else rise / run def get_angle(self): start, end = self.get_start_and_end() - return angle_of_vector(end-start) + return angle_of_vector(end - start) # def put_start_and_end_on(self, new_start, new_end): # self.set_start_and_end(new_start, new_end) @@ -396,7 +408,7 @@ class Line(VMobject): target_norm = np.linalg.norm(target_vect) if target_norm == 0: epsilon = 0.001 - self.scale(epsilon/curr_norm) + self.scale(epsilon / curr_norm) self.move_to(new_start) return unit_target = target_vect / target_norm @@ -410,7 +422,7 @@ class Line(VMobject): angle_diff = np.arccos( np.clip(np.dot(unit_target, unit_curr), -1, 1) ) - self.scale(target_norm/curr_norm) + self.scale(target_norm / curr_norm) self.rotate(-angle_diff, normal) self.shift(new_start - self.get_start()) return self @@ -418,7 +430,7 @@ class Line(VMobject): def insert_n_anchor_points(self, n): if not self.path_arc: n_anchors = self.get_num_anchor_points() - new_num_points = 3*(n_anchors + n)-2 + new_num_points = 3 * (n_anchors + n) - 2 self.points = np.array([ self.point_from_proportion(alpha) for alpha in np.linspace(0, 1, new_num_points) @@ -426,17 +438,19 @@ class Line(VMobject): else: VMobject.insert_n_anchor_points(self, n) + class DashedLine(Line): CONFIG = { - "dashed_segment_length" : 0.05 + "dashed_segment_length": 0.05 } + def __init__(self, *args, **kwargs): self.init_kwargs = kwargs Line.__init__(self, *args, **kwargs) def generate_points(self): - length = np.linalg.norm(self.end-self.start) - num_interp_points = int(length/self.dashed_segment_length) + length = np.linalg.norm(self.end - self.start) + num_interp_points = int(length / self.dashed_segment_length) points = [ interpolate(self.start, self.end, alpha) for alpha in np.linspace(0, 1, num_interp_points) @@ -462,23 +476,25 @@ class DashedLine(Line): else: return self.end + class Arrow(Line): CONFIG = { - "tip_length" : 0.25, - "tip_width_to_length_ratio" : 1, - "max_tip_length_to_length_ratio" : 0.35, - "max_stem_width_to_tip_width_ratio" : 0.3, - "buff" : MED_SMALL_BUFF, - "propagate_style_to_family" : False, - "preserve_tip_size_when_scaling" : True, - "normal_vector" : OUT, - "use_rectangular_stem" : True, - "rectangular_stem_width" : 0.05, + "tip_length": 0.25, + "tip_width_to_length_ratio": 1, + "max_tip_length_to_length_ratio": 0.35, + "max_stem_width_to_tip_width_ratio": 0.3, + "buff": MED_SMALL_BUFF, + "propagate_style_to_family": False, + "preserve_tip_size_when_scaling": True, + "normal_vector": OUT, + "use_rectangular_stem": True, + "rectangular_stem_width": 0.05, } + def __init__(self, *args, **kwargs): points = map(self.pointify, args) if len(args) == 1: - args = (points[0]+UP+LEFT, points[0]) + args = (points[0] + UP + LEFT, points[0]) Line.__init__(self, *args, **kwargs) self.init_tip() if self.use_rectangular_stem and not hasattr(self, "rect"): @@ -488,17 +504,17 @@ class Arrow(Line): def init_tip(self): self.add_tip() - def add_tip(self, add_at_end = True): + def add_tip(self, add_at_end=True): tip = VMobject( - close_new_points = True, - mark_paths_closed = True, - fill_color = self.color, - fill_opacity = 1, - stroke_color = self.color, - stroke_width = 0, + close_new_points=True, + mark_paths_closed=True, + fill_color=self.color, + fill_opacity=1, + stroke_color=self.color, + stroke_width=0, ) tip.add_at_end = add_at_end - self.set_tip_points(tip, add_at_end, preserve_normal = False) + self.set_tip_points(tip, add_at_end, preserve_normal=False) self.add(tip) if not hasattr(self, 'tip'): self.tip = VGroup() @@ -508,17 +524,16 @@ class Arrow(Line): def add_rectangular_stem(self): self.rect = Rectangle( - stroke_width = 0, - fill_color = self.tip.get_fill_color(), - fill_opacity = self.tip.get_fill_opacity() + stroke_width=0, + fill_color=self.tip.get_fill_color(), + fill_opacity=self.tip.get_fill_opacity() ) self.add_to_back(self.rect) - self.set_stroke(width = 0) + self.set_stroke(width=0) self.set_rectangular_stem_points() def set_rectangular_stem_points(self): start, end = self.get_start_and_end() - vect = end - start tip_base_points = self.tip[0].get_anchors()[1:] tip_base = center_of_mass(tip_base_points) tbp1, tbp2 = tip_base_points @@ -528,35 +543,35 @@ class Arrow(Line): perp_vect /= tip_base_width width = min( self.rectangular_stem_width, - self.max_stem_width_to_tip_width_ratio*tip_base_width, + self.max_stem_width_to_tip_width_ratio * tip_base_width, ) if hasattr(self, "second_tip"): start = center_of_mass( self.second_tip.get_anchors()[1:] ) self.rect.set_points_as_corners([ - tip_base + perp_vect*width/2, - start + perp_vect*width/2, - start - perp_vect*width/2, - tip_base - perp_vect*width/2, + tip_base + perp_vect * width / 2, + start + perp_vect * width / 2, + start - perp_vect * width / 2, + tip_base - perp_vect * width / 2, ]) return self def set_tip_points( self, tip, - add_at_end = True, - tip_length = None, - preserve_normal = True, - ): + add_at_end=True, + tip_length=None, + preserve_normal=True, + ): if tip_length is None: tip_length = self.tip_length if preserve_normal: normal_vector = self.get_normal_vector() else: normal_vector = self.normal_vector - line_length = np.linalg.norm(self.points[-1]-self.points[0]) + line_length = np.linalg.norm(self.points[-1] - self.points[0]) tip_length = min( - tip_length, self.max_tip_length_to_length_ratio*line_length + tip_length, self.max_tip_length_to_length_ratio * line_length ) indices = (-2, -1) if add_at_end else (1, 0) @@ -569,12 +584,12 @@ class Arrow(Line): for v in vect, perp_vect: if np.linalg.norm(v) == 0: v[0] = 1 - v *= tip_length/np.linalg.norm(v) + v *= tip_length / np.linalg.norm(v) ratio = self.tip_width_to_length_ratio tip.set_points_as_corners([ end_point, - end_point-vect+perp_vect*ratio/2, - end_point-vect-perp_vect*ratio/2, + end_point - vect + perp_vect * ratio / 2, + end_point - vect - perp_vect * ratio / 2, ]) return self @@ -586,7 +601,7 @@ class Arrow(Line): if norm == 0: return self.normal_vector else: - return result/norm + return result / norm def reset_normal_vector(self): self.normal_vector = self.get_normal_vector() @@ -603,7 +618,7 @@ class Arrow(Line): def put_start_and_end_on(self, *args, **kwargs): Line.put_start_and_end_on(self, *args, **kwargs) - self.set_tip_points(self.tip[0], preserve_normal = False) + self.set_tip_points(self.tip[0], preserve_normal=False) self.set_rectangular_stem_points() return self @@ -619,92 +634,106 @@ class Arrow(Line): def copy(self): return self.deepcopy() + class Vector(Arrow): CONFIG = { - "color" : YELLOW, - "buff" : 0, + "color": YELLOW, + "buff": 0, } + def __init__(self, direction, **kwargs): if len(direction) == 2: direction = np.append(np.array(direction), 0) Arrow.__init__(self, ORIGIN, direction, **kwargs) + class DoubleArrow(Arrow): def init_tip(self): self.tip = VGroup() for b in True, False: - t = self.add_tip(add_at_end = b) + t = self.add_tip(add_at_end=b) t.add_at_end = b self.tip.add(t) self.tip.match_style(self.tip[0]) + class CubicBezier(VMobject): def __init__(self, points, **kwargs): VMobject.__init__(self, **kwargs) self.set_points(points) + class Polygon(VMobject): CONFIG = { - "color" : GREEN_D, - "mark_paths_closed" : True, - "close_new_points" : True, + "color": GREEN_D, + "mark_paths_closed": True, + "close_new_points": True, } + def __init__(self, *vertices, **kwargs): assert len(vertices) > 1 digest_locals(self) VMobject.__init__(self, **kwargs) def generate_points(self): - self.set_anchor_points(self.vertices, mode = "corners") + self.set_anchor_points(self.vertices, mode="corners") def get_vertices(self): return self.get_anchors_and_handles()[0] + class RegularPolygon(Polygon): CONFIG = { - "start_angle" : 0 + "start_angle": 0 } - def __init__(self, n = 3, **kwargs): + + def __init__(self, n=3, **kwargs): digest_config(self, kwargs, locals()) start_vect = rotate_vector(RIGHT, self.start_angle) vertices = compass_directions(n, start_vect) Polygon.__init__(self, *vertices, **kwargs) + class Rectangle(VMobject): CONFIG = { - "color" : WHITE, - "height" : 2.0, - "width" : 4.0, - "mark_paths_closed" : True, - "close_new_points" : True, + "color": WHITE, + "height": 2.0, + "width": 4.0, + "mark_paths_closed": True, + "close_new_points": True, } + def generate_points(self): - y, x = self.height/2., self.width/2. + y, x = self.height / 2., self.width / 2. self.set_anchor_points([ - x*LEFT+y*UP, - x*RIGHT+y*UP, - x*RIGHT+y*DOWN, - x*LEFT+y*DOWN - ], mode = "corners") + x * LEFT + y * UP, + x * RIGHT + y * UP, + x * RIGHT + y * DOWN, + x * LEFT + y * DOWN + ], mode="corners") + class Square(Rectangle): CONFIG = { - "side_length" : 2.0, + "side_length": 2.0, } + def __init__(self, **kwargs): digest_config(self, kwargs) Rectangle.__init__( - self, - height = self.side_length, - width = self.side_length, + self, + height=self.side_length, + width=self.side_length, **kwargs ) + class Grid(VMobject): CONFIG = { - "height" : 6.0, - "width" : 6.0, + "height": 6.0, + "width": 6.0, } + def __init__(self, rows, columns, **kwargs): digest_config(self, kwargs, locals()) VMobject.__init__(self, **kwargs) @@ -713,15 +742,13 @@ class Grid(VMobject): x_step = self.width / self.columns y_step = self.height / self.rows - for x in np.arange(0, self.width+x_step, x_step): + for x in np.arange(0, self.width + x_step, x_step): self.add(Line( - [x-self.width/2., -self.height/2., 0], - [x-self.width/2., self.height/2., 0], + [x - self.width / 2., -self.height / 2., 0], + [x - self.width / 2., self.height / 2., 0], )) - for y in np.arange(0, self.height+y_step, y_step): + for y in np.arange(0, self.height + y_step, y_step): self.add(Line( - [-self.width/2., y-self.height/2., 0], - [self.width/2., y-self.height/2., 0] + [-self.width / 2., y - self.height / 2., 0], + [self.width / 2., y - self.height / 2., 0] )) - - diff --git a/mobject/matrix.py b/mobject/matrix.py index cf20f6de..e763032a 100644 --- a/mobject/matrix.py +++ b/mobject/matrix.py @@ -12,12 +12,13 @@ from constants import * VECTOR_LABEL_SCALE_FACTOR = 0.8 + def matrix_to_tex_string(matrix): matrix = np.array(matrix).astype("string") if matrix.ndim == 1: matrix = matrix.reshape((matrix.size, 1)) n_rows, n_cols = matrix.shape - prefix = "\\left[ \\begin{array}{%s}"%("c"*n_cols) + prefix = "\\left[ \\begin{array}{%s}" % ("c" * n_cols) suffix = "\\end{array} \\right]" rows = [ " & ".join(row) @@ -25,39 +26,43 @@ def matrix_to_tex_string(matrix): ] return prefix + " \\\\ ".join(rows) + suffix + def matrix_to_mobject(matrix): return TexMobject(matrix_to_tex_string(matrix)) -def vector_coordinate_label(vector_mob, integer_labels = True, - n_dim = 2, color = WHITE): + +def vector_coordinate_label(vector_mob, integer_labels=True, + n_dim=2, color=WHITE): vect = np.array(vector_mob.get_end()) if integer_labels: vect = np.round(vect).astype(int) vect = vect[:n_dim] vect = vect.reshape((n_dim, 1)) - label = Matrix(vect, add_background_rectangles = True) + label = Matrix(vect, add_background_rectangles=True) label.scale(VECTOR_LABEL_SCALE_FACTOR) shift_dir = np.array(vector_mob.get_end()) - if shift_dir[0] >= 0: #Pointing right - shift_dir -= label.get_left() + DEFAULT_MOBJECT_TO_MOBJECT_BUFFER*LEFT - else: #Pointing left - shift_dir -= label.get_right() + DEFAULT_MOBJECT_TO_MOBJECT_BUFFER*RIGHT + if shift_dir[0] >= 0: # Pointing right + shift_dir -= label.get_left() + DEFAULT_MOBJECT_TO_MOBJECT_BUFFER * LEFT + else: # Pointing left + shift_dir -= label.get_right() + DEFAULT_MOBJECT_TO_MOBJECT_BUFFER * RIGHT label.shift(shift_dir) label.set_color(color) label.rect = BackgroundRectangle(label) label.add_to_back(label.rect) return label + class Matrix(VMobject): CONFIG = { - "v_buff" : 0.5, - "h_buff" : 1, - "add_background_rectangles" : False + "v_buff": 0.5, + "h_buff": 1, + "add_background_rectangles": False } + def __init__(self, matrix, **kwargs): """ - Matrix can either either include numbres, tex_strings, + Matrix can either either include numbres, tex_strings, or mobjects """ VMobject.__init__(self, **kwargs) @@ -89,9 +94,9 @@ class Matrix(VMobject): if i == 0 and j == 0: continue elif i == 0: - mob.next_to(matrix[i][j-1], RIGHT, self.h_buff) + mob.next_to(matrix[i][j - 1], RIGHT, self.h_buff) else: - mob.next_to(matrix[i-1][j], DOWN, self.v_buff) + mob.next_to(matrix[i - 1][j], DOWN, self.v_buff) return self def add_brackets(self): @@ -107,7 +112,7 @@ class Matrix(VMobject): def set_color_columns(self, *colors): for i, color in enumerate(colors): - VGroup(*self.mob_matrix[:,i]).set_color(color) + VGroup(*self.mob_matrix[:, i]).set_color(color) return self def add_background_to_entries(self): @@ -123,15 +128,3 @@ class Matrix(VMobject): def get_brackets(self): return self.brackets - - - - - - - - - - - - diff --git a/mobject/mobject.py b/mobject/mobject.py index 819fb4f2..e7815caf 100644 --- a/mobject/mobject.py +++ b/mobject/mobject.py @@ -4,7 +4,6 @@ import numpy as np import operator as op import os -from PIL import Image from colour import Color from constants import * @@ -16,28 +15,28 @@ from utils.color import interpolate_color from utils.iterables import list_update from utils.iterables import remove_list_redundancies from utils.paths import straight_path -from utils.space_ops import R3_to_complex from utils.space_ops import angle_of_vector from utils.space_ops import complex_to_R3 from utils.space_ops import rotation_matrix -#TODO: Explain array_attrs +# TODO: Explain array_attrs class Mobject(Container): """ Mathematical Object """ CONFIG = { - "color" : WHITE, - "stroke_width" : DEFAULT_POINT_THICKNESS, - "name" : None, - "dim" : 3, - "target" : None, + "color": WHITE, + "stroke_width": DEFAULT_POINT_THICKNESS, + "name": None, + "dim": 3, + "target": None, } + def __init__(self, *submobjects, **kwargs): Container.__init__(self, *submobjects, **kwargs) - if not all(map(lambda m : isinstance(m, Mobject), submobjects)): + if not all(map(lambda m: isinstance(m, Mobject), submobjects)): raise Exception("All submobjects must be of type Mobject") self.submobjects = list(submobjects) self.color = Color(self.color) @@ -54,11 +53,11 @@ class Mobject(Container): self.points = np.zeros((0, self.dim)) def init_colors(self): - #For subclasses + # For subclasses pass def generate_points(self): - #Typically implemented in subclass, unless purposefully left blank + # Typically implemented in subclass, unless purposefully left blank pass def add(self, *mobjects): @@ -87,7 +86,7 @@ class Mobject(Container): in the submobjects list. """ mobject_attrs = filter( - lambda x : isinstance(x, Mobject), + lambda x: isinstance(x, Mobject), self.__dict__.values() ) self.submobjects = list_update(self.submobjects, mobject_attrs) @@ -98,24 +97,24 @@ class Mobject(Container): setattr(self, attr, func(getattr(self, attr))) return self - def get_image(self, camera = None): + def get_image(self, camera=None): if camera is None: from camera.camera import Camera camera = Camera() camera.capture_mobject(self) return camera.get_image() - def show(self, camera = None): - self.get_image(camera = camera).show() + def show(self, camera=None): + self.get_image(camera=camera).show() - def save_image(self, name = None): + def save_image(self, name=None): self.get_image().save( os.path.join(ANIMATIONS_DIR, (name or str(self)) + ".png") ) def copy(self): - #TODO, either justify reason for shallow copy, or - #remove this redundancy everywhere + # TODO, either justify reason for shallow copy, or + # remove this redundancy everywhere return self.deepcopy() copy_mobject = copy.copy(self) @@ -132,8 +131,8 @@ class Mobject(Container): def deepcopy(self): return copy.deepcopy(self) - def generate_target(self, use_deepcopy = False): - self.target = None #Prevent exponential explosion + def generate_target(self, use_deepcopy=False): + self.target = None # Prevent exponential explosion if use_deepcopy: self.target = self.deepcopy() else: @@ -149,8 +148,8 @@ class Mobject(Container): def shift(self, *vectors): total_vector = reduce(op.add, vectors) for mob in self.family_members_with_points(): - mob.points = mob.points.astype('float') - mob.points += total_vector + mob.points = mob.points.astype('float') + mob.points += total_vector return self def scale(self, scale_factor, **kwargs): @@ -164,61 +163,61 @@ class Mobject(Container): respect to that point. """ self.apply_points_function_about_point( - lambda points : scale_factor*points, **kwargs + lambda points: scale_factor * points, **kwargs ) return self - def rotate_about_origin(self, angle, axis = OUT, axes = []): - return self.rotate(angle, axis, about_point = ORIGIN) + def rotate_about_origin(self, angle, axis=OUT, axes=[]): + return self.rotate(angle, axis, about_point=ORIGIN) - def rotate(self, angle, axis = OUT, **kwargs): + def rotate(self, angle, axis=OUT, **kwargs): rot_matrix = rotation_matrix(angle, axis) self.apply_points_function_about_point( - lambda points : np.dot(points, rot_matrix.T), + lambda points: np.dot(points, rot_matrix.T), **kwargs ) return self - def flip(self, axis = UP, **kwargs): - return self.rotate(TAU/2, axis, **kwargs) + def flip(self, axis=UP, **kwargs): + return self.rotate(TAU / 2, axis, **kwargs) def stretch(self, factor, dim, **kwargs): def func(points): - points[:,dim] *= factor + points[:, dim] *= factor return points self.apply_points_function_about_point(func, **kwargs) return self def apply_function(self, function, **kwargs): - #Default to applying matrix about the origin, not mobjects center + # Default to applying matrix about the origin, not mobjects center if len(kwargs) == 0: kwargs["about_point"] = ORIGIN self.apply_points_function_about_point( - lambda points : np.apply_along_axis(function, 1, points), + lambda points: np.apply_along_axis(function, 1, points), **kwargs ) return self def apply_matrix(self, matrix, **kwargs): - #Default to applying matrix about the origin, not mobjects center + # Default to applying matrix about the origin, not mobjects center if len(kwargs) == 0: kwargs["about_point"] = ORIGIN full_matrix = np.identity(self.dim) matrix = np.array(matrix) - full_matrix[:matrix.shape[0],:matrix.shape[1]] = matrix + full_matrix[:matrix.shape[0], :matrix.shape[1]] = matrix self.apply_points_function_about_point( - lambda points : np.dot(points, full_matrix.T), + lambda points: np.dot(points, full_matrix.T), **kwargs ) return self def apply_complex_function(self, function, **kwargs): return self.apply_function( - lambda (x, y, z) : complex_to_R3(function(complex(x, y))), + lambda (x, y, z): complex_to_R3(function(complex(x, y))), **kwargs ) - def wag(self, direction = RIGHT, axis = DOWN, wag_factor = 1.0): + def wag(self, direction=RIGHT, axis=DOWN, wag_factor=1.0): for mob in self.family_members_with_points(): alphas = np.dot(mob.points, np.transpose(axis)) alphas -= min(alphas) @@ -233,7 +232,7 @@ class Mobject(Container): def reverse_points(self): for mob in self.family_members_with_points(): mob.apply_over_attr_arrays( - lambda arr : np.array(list(reversed(arr))) + lambda arr: np.array(list(reversed(arr))) ) return self @@ -243,18 +242,18 @@ class Mobject(Container): """ def repeat_array(array): return reduce( - lambda a1, a2 : np.append(a1, a2, axis = 0), - [array]*count + lambda a1, a2: np.append(a1, a2, axis=0), + [array] * count ) for mob in self.family_members_with_points(): mob.apply_over_attr_arrays(repeat_array) return self #### In place operations ###### - #Note, much of these are now redundant with default behavior of - #above methods + # Note, much of these are now redundant with default behavior of + # above methods - def apply_points_function_about_point(self, func, about_point = None, about_edge = ORIGIN): + def apply_points_function_about_point(self, func, about_point=None, about_edge=ORIGIN): if about_point is None: about_point = self.get_critical_point(about_edge) for mob in self.family_members_with_points(): @@ -263,20 +262,20 @@ class Mobject(Container): mob.points += about_point return self - def rotate_in_place(self, angle, axis = OUT): + def rotate_in_place(self, angle, axis=OUT): # redundant with default behavior of rotate now. - return self.rotate(angle, axis = axis) + return self.rotate(angle, axis=axis) def scale_in_place(self, scale_factor, **kwargs): - #Redundant with default behavior of scale now. + # Redundant with default behavior of scale now. return self.scale(scale_factor, **kwargs) def scale_about_point(self, scale_factor, point): - #Redundant with default behavior of scale now. - return self.scale(scale_factor, about_point = point) + # Redundant with default behavior of scale now. + return self.scale(scale_factor, about_point=point) def pose_at_angle(self, **kwargs): - self.rotate(TAU/14, RIGHT+UP, **kwargs) + self.rotate(TAU / 14, RIGHT + UP, **kwargs) return self #### Positioning methods #### @@ -285,7 +284,7 @@ class Mobject(Container): self.shift(-self.get_center()) return self - def align_on_border(self, direction, buff = DEFAULT_MOBJECT_TO_EDGE_BUFFER): + def align_on_border(self, direction, buff=DEFAULT_MOBJECT_TO_EDGE_BUFFER): """ Direction just needs to be a vector pointing towards side or corner in the 2d plane. @@ -297,19 +296,19 @@ class Mobject(Container): self.shift(shift_val) return self - def to_corner(self, corner = LEFT+DOWN, buff = DEFAULT_MOBJECT_TO_EDGE_BUFFER): + def to_corner(self, corner=LEFT + DOWN, buff=DEFAULT_MOBJECT_TO_EDGE_BUFFER): return self.align_on_border(corner, buff) - def to_edge(self, edge = LEFT, buff = DEFAULT_MOBJECT_TO_EDGE_BUFFER): + def to_edge(self, edge=LEFT, buff=DEFAULT_MOBJECT_TO_EDGE_BUFFER): return self.align_on_border(edge, buff) def next_to(self, mobject_or_point, - direction = RIGHT, - buff = DEFAULT_MOBJECT_TO_MOBJECT_BUFFER, - aligned_edge = ORIGIN, - submobject_to_align = None, - index_of_submobject_to_align = None, - coor_mask = np.array([1,1,1]), + direction=RIGHT, + buff=DEFAULT_MOBJECT_TO_MOBJECT_BUFFER, + aligned_edge=ORIGIN, + submobject_to_align=None, + index_of_submobject_to_align=None, + coor_mask=np.array([1, 1, 1]), ): if isinstance(mobject_or_point, Mobject): mob = mobject_or_point @@ -329,10 +328,11 @@ class Mobject(Container): else: aligner = self point_to_align = aligner.get_critical_point(aligned_edge - direction) - self.shift((target_point - point_to_align + buff*direction)*coor_mask) + self.shift((target_point - point_to_align + + buff * direction) * coor_mask) return self - def align_to(self, mobject_or_point, direction = ORIGIN, alignment_vect = UP): + def align_to(self, mobject_or_point, direction=ORIGIN, alignment_vect=UP): """ Examples: mob1.align_to(mob2, UP) moves mob1 vertically so that its @@ -349,12 +349,12 @@ class Mobject(Container): target_point = mobject_or_point direction_norm = np.linalg.norm(direction) if direction_norm > 0: - alignment_vect = np.array(direction)/direction_norm + alignment_vect = np.array(direction) / direction_norm reference_point = self.get_critical_point(direction) else: reference_point = self.get_center() diff = target_point - reference_point - self.shift(alignment_vect*np.dot(diff, alignment_vect)) + self.shift(alignment_vect * np.dot(diff, alignment_vect)) return self def shift_onto_screen(self, **kwargs): @@ -380,57 +380,57 @@ class Mobject(Container): return False def stretch_about_point(self, factor, dim, point): - return self.stretch(factor, dim, about_point = point) + return self.stretch(factor, dim, about_point=point) def stretch_in_place(self, factor, dim): - #Now redundant with stretch + # Now redundant with stretch return self.stretch(factor, dim) - def rescale_to_fit(self, length, dim, stretch = False, **kwargs): + def rescale_to_fit(self, length, dim, stretch=False, **kwargs): old_length = self.length_over_dim(dim) if old_length == 0: return self if stretch: - self.stretch(length/old_length, dim, **kwargs) + self.stretch(length / old_length, dim, **kwargs) else: - self.scale(length/old_length, **kwargs) + self.scale(length / old_length, **kwargs) return self def stretch_to_fit_width(self, width, **kwargs): - return self.rescale_to_fit(width, 0, stretch = True, **kwargs) + return self.rescale_to_fit(width, 0, stretch=True, **kwargs) def stretch_to_fit_height(self, height, **kwargs): - return self.rescale_to_fit(height, 1, stretch = True, **kwargs) + return self.rescale_to_fit(height, 1, stretch=True, **kwargs) def stretch_to_fit_depth(self, depth, **kwargs): - return self.rescale_to_fit(depth, 1, stretch = True, **kwargs) + return self.rescale_to_fit(depth, 1, stretch=True, **kwargs) def scale_to_fit_width(self, width, **kwargs): - return self.rescale_to_fit(width, 0, stretch = False, **kwargs) + return self.rescale_to_fit(width, 0, stretch=False, **kwargs) def scale_to_fit_height(self, height, **kwargs): - return self.rescale_to_fit(height, 1, stretch = False, **kwargs) + return self.rescale_to_fit(height, 1, stretch=False, **kwargs) def scale_to_fit_depth(self, depth, **kwargs): - return self.rescale_to_fit(depth, 2, stretch = False, **kwargs) + return self.rescale_to_fit(depth, 2, stretch=False, **kwargs) - def space_out_submobjects(self, factor = 1.5, **kwargs): + def space_out_submobjects(self, factor=1.5, **kwargs): self.scale(factor, **kwargs) for submob in self.submobjects: - submob.scale(1./factor) + submob.scale(1. / factor) return self - def move_to(self, point_or_mobject, aligned_edge = ORIGIN, - coor_mask = np.array([1,1,1])): + def move_to(self, point_or_mobject, aligned_edge=ORIGIN, + coor_mask=np.array([1, 1, 1])): if isinstance(point_or_mobject, Mobject): target = point_or_mobject.get_critical_point(aligned_edge) else: target = point_or_mobject point_to_align = self.get_critical_point(aligned_edge) - self.shift((target - point_to_align)*coor_mask) + self.shift((target - point_to_align) * coor_mask) return self - def replace(self, mobject, dim_to_match = 0, stretch = False): + def replace(self, mobject, dim_to_match=0, stretch=False): if not mobject.get_num_points() and not mobject.submobjects: raise Warning("Attempting to replace mobject with no points") return self @@ -441,12 +441,12 @@ class Mobject(Container): self.rescale_to_fit( mobject.length_over_dim(dim_to_match), dim_to_match, - stretch = False + stretch=False ) self.shift(mobject.get_center() - self.get_center()) return self - def surround(self, mobject, dim_to_match = 0, stretch = False, buffer_factor = 1.2): + def surround(self, mobject, dim_to_match=0, stretch=False, buffer_factor=1.2): self.replace(mobject, dim_to_match, stretch) self.scale_in_place(buffer_factor) @@ -455,15 +455,15 @@ class Mobject(Container): if np.all(curr_vect == 0): raise Exception("Cannot position endpoints of closed loop") target_vect = end - start - self.scale(np.linalg.norm(target_vect)/np.linalg.norm(curr_vect)) + self.scale(np.linalg.norm(target_vect) / np.linalg.norm(curr_vect)) self.rotate( - angle_of_vector(target_vect) - \ + angle_of_vector(target_vect) - angle_of_vector(curr_vect) ) - self.shift(start-self.points[0]) + self.shift(start - self.points[0]) return self - ## Match other mobvject properties + # Match other mobvject properties def match_color(self, mobject): return self.set_color(mobject.get_color()) @@ -483,9 +483,9 @@ class Mobject(Container): def match_depth(self, mobject, **kwargs): return self.match_dim(mobject, 2, **kwargs) - ## Color functions + # Color functions - def set_color(self, color = YELLOW_C, family = True): + def set_color(self, color=YELLOW_C, family=True): """ Condition is function which takes in one arguments, (x, y, z). Here it just recurses to submobjects, but in subclasses this @@ -494,7 +494,7 @@ class Mobject(Container): """ if family: for submob in self.submobjects: - submob.set_color(color, family = family) + submob.set_color(color, family=family) self.color = color return self @@ -502,8 +502,9 @@ class Mobject(Container): self.set_submobject_colors_by_gradient(*colors) return self - def set_colors_by_radial_gradient(self, center = None, radius = 1, inner_color = WHITE, outer_color = BLACK): - self.set_submobject_colors_by_radial_gradient(center, radius, inner_color, outer_color) + def set_colors_by_radial_gradient(self, center=None, radius=1, inner_color=WHITE, outer_color=BLACK): + self.set_submobject_colors_by_radial_gradient( + center, radius, inner_color, outer_color) return self def set_submobject_colors_by_gradient(self, *colors): @@ -516,19 +517,19 @@ class Mobject(Container): new_colors = color_gradient(colors, len(mobs)) for mob, color in zip(mobs, new_colors): - mob.set_color(color, family = False) + mob.set_color(color, family=False) return self - def set_submobject_colors_by_radial_gradient(self, center = None, radius = 1, inner_color = WHITE, outer_color = BLACK): + def set_submobject_colors_by_radial_gradient(self, center=None, radius=1, inner_color=WHITE, outer_color=BLACK): mobs = self.family_members_with_points() if center == None: center = self.get_center() for mob in self.family_members_with_points(): - t = np.linalg.norm(mob.get_center() - center)/radius - t = min(t,1) + t = np.linalg.norm(mob.get_center() - center) / radius + t = min(t, 1) mob_color = interpolate_color(inner_color, outer_color, t) - mob.set_color(mob_color, family = False) + mob.set_color(mob_color, family=False) return self @@ -538,7 +539,7 @@ class Mobject(Container): # Some objects (e.g., VMobjects) have special fading # behavior. We let every object handle its individual - # fading via fade_no_recurse (notionally a purely internal method), + # fading via fade_no_recurse (notionally a purely internal method), # and then have fade() itself call this recursively on each submobject # # Similarly for fade_to_no_recurse and fade_to, the underlying functions @@ -549,7 +550,7 @@ class Mobject(Container): start = color_to_rgb(self.get_color()) end = color_to_rgb(color) new_rgb = interpolate(start, end, alpha) - self.set_color(Color(rgb = new_rgb), family = False) + self.set_color(Color(rgb=new_rgb), family=False) return self def fade_to(self, color, alpha): @@ -561,7 +562,7 @@ class Mobject(Container): self.fade_to_no_recurse(BLACK, darkness) return self - def fade(self, darkness = 0.5): + def fade(self, darkness=0.5): for submob in self.submobject_family(): submob.fade_no_recurse(darkness) return self @@ -570,9 +571,9 @@ class Mobject(Container): return self.color ## - def save_state(self, use_deepcopy = False): + def save_state(self, use_deepcopy=False): if hasattr(self, "saved_state"): - #Prevent exponential growth of data + # Prevent exponential growth of data self.saved_state = None if use_deepcopy: self.saved_state = self.deepcopy() @@ -634,7 +635,7 @@ class Mobject(Container): max_point = self.reduce_across_dimension(np.max, np.max, dim) if direction[dim] == 0: - result[dim] = (max_point+min_point)/2 + result[dim] = (max_point + min_point) / 2 elif direction[dim] < 0: result[dim] = min_point else: @@ -694,8 +695,7 @@ class Mobject(Container): def point_from_proportion(self, alpha): raise Exception("Not implemented") - - ## Family matters + # Family matters def __getitem__(self, value): self_list = self.split() @@ -724,22 +724,22 @@ class Mobject(Container): def family_members_with_points(self): return filter( - lambda m : m.get_num_points() > 0, + lambda m: m.get_num_points() > 0, self.submobject_family() ) - def arrange_submobjects(self, direction = RIGHT, center = True, **kwargs): + def arrange_submobjects(self, direction=RIGHT, center=True, **kwargs): for m1, m2 in zip(self.submobjects, self.submobjects[1:]): m2.next_to(m1, direction, **kwargs) if center: self.center() return self - def arrange_submobjects_in_grid(self, n_rows = None, n_cols = None, **kwargs): + def arrange_submobjects_in_grid(self, n_rows=None, n_cols=None, **kwargs): submobs = self.submobjects if n_rows is None and n_cols is None: n_cols = int(np.sqrt(len(submobs))) - + if n_rows is not None: v1 = RIGHT v2 = DOWN @@ -749,35 +749,35 @@ class Mobject(Container): v2 = RIGHT n = len(submobs) / n_cols Group(*[ - Group(*submobs[i:i+n]).arrange_submobjects(v1, **kwargs) + Group(*submobs[i:i + n]).arrange_submobjects(v1, **kwargs) for i in range(0, len(submobs), n) ]).arrange_submobjects(v2, **kwargs) return self - def sort_submobjects(self, point_to_num_func = lambda p : p[0]): + def sort_submobjects(self, point_to_num_func=lambda p: p[0]): self.submobjects.sort( - lambda *mobs : cmp(*[ + lambda *mobs: cmp(*[ point_to_num_func(mob.get_center()) for mob in mobs ]) ) return self - def print_submobject_family(self, n_tabs = 0): + def print_submobject_family(self, n_tabs=0): """For debugging purposes""" - print "\t"*n_tabs, self, id(self) + print "\t" * n_tabs, self, id(self) for submob in self.submobjects: submob.print_mobject_family(n_tabs + 1) - ## Alignment + # Alignment def align_data(self, mobject): self.align_submobjects(mobject) self.align_points(mobject) - #Recurse + # Recurse for m1, m2 in zip(self.submobjects, mobject.submobjects): m1.align_data(m2) - def get_point_mobject(self, center = None): + def get_point_mobject(self, center=None): """ The simplest mobject to be transformed to or from self. Should by a point of the appropriate type @@ -797,8 +797,8 @@ class Mobject(Container): raise Exception("Not implemented") def align_submobjects(self, mobject): - #If one is empty, and the other is not, - #push it into its submobject list + # If one is empty, and the other is not, + # push it into its submobject list self_has_points, mob_has_points = [ mob.get_num_points() > 0 for mob in self, mobject @@ -809,7 +809,7 @@ class Mobject(Container): self.null_point_align(mobject) self_count = len(self.submobjects) mob_count = len(mobject.submobjects) - diff = self_count-mob_count + diff = self_count - mob_count if diff < 0: self.add_n_more_submobjects(-diff) elif diff > 0: @@ -840,7 +840,7 @@ class Mobject(Container): self.add(self.copy()) n -= 1 curr += 1 - indices = curr*np.arange(curr+n)/(curr+n) + indices = curr * np.arange(curr + n) / (curr + n) new_submobjects = [] for index in indices: submob = self.submobjects[index] @@ -854,7 +854,7 @@ class Mobject(Container): return submob.copy() def interpolate(self, mobject1, mobject2, - alpha, path_func = straight_path): + alpha, path_func=straight_path): """ Turns self into an interpolation between mobject1 and mobject2. @@ -865,7 +865,7 @@ class Mobject(Container): self.interpolate_color(mobject1, mobject2, alpha) def interpolate_color(self, mobject1, mobject2, alpha): - pass #To implement in subclass + pass # To implement in subclass def become_partial(self, mobject, a, b): """ @@ -874,15 +874,16 @@ class Mobject(Container): Inputs 0 <= a < b <= 1 determine what portion of mobject to become. """ - pass #To implement in subclasses + pass # To implement in subclasses - #TODO, color? + # TODO, color? def pointwise_become_partial(self, mobject, a, b): - pass #To implement in subclass + pass # To implement in subclass + class Group(Mobject): - #Alternate name to improve readibility in cases where - #the mobject is used primarily for its submobject housing - #functionality. + # Alternate name to improve readibility in cases where + # the mobject is used primarily for its submobject housing + # functionality. pass diff --git a/mobject/number_line.py b/mobject/number_line.py index ba332f51..e581075c 100644 --- a/mobject/number_line.py +++ b/mobject/number_line.py @@ -10,49 +10,50 @@ from mobject.geometry import Line from utils.bezier import interpolate from utils.config_ops import digest_config + class NumberLine(VMobject): CONFIG = { - "color" : BLUE, - "x_min" : -FRAME_X_RADIUS, - "x_max" : FRAME_X_RADIUS, - "unit_size" : 1, - "tick_size" : 0.1, - "tick_frequency" : 1, - "leftmost_tick" : None, #Defaults to value near x_min s.t. 0 is a tick - "numbers_with_elongated_ticks" : [0], - "numbers_to_show" : None, - "longer_tick_multiple" : 2, - "number_at_center" : 0, - "number_scale_val" : 0.75, - "label_direction" : DOWN, - "line_to_number_buff" : MED_SMALL_BUFF, - "include_tip" : False, - "propagate_style_to_family" : True, + "color": BLUE, + "x_min": -FRAME_X_RADIUS, + "x_max": FRAME_X_RADIUS, + "unit_size": 1, + "tick_size": 0.1, + "tick_frequency": 1, + "leftmost_tick": None, # Defaults to value near x_min s.t. 0 is a tick + "numbers_with_elongated_ticks": [0], + "numbers_to_show": None, + "longer_tick_multiple": 2, + "number_at_center": 0, + "number_scale_val": 0.75, + "label_direction": DOWN, + "line_to_number_buff": MED_SMALL_BUFF, + "include_tip": False, + "propagate_style_to_family": True, } def __init__(self, **kwargs): digest_config(self, kwargs) if self.leftmost_tick is None: tf = self.tick_frequency - self.leftmost_tick = tf*np.ceil(self.x_min/tf) + self.leftmost_tick = tf * np.ceil(self.x_min / tf) VMobject.__init__(self, **kwargs) if self.include_tip: self.add_tip() def generate_points(self): - self.main_line = Line(self.x_min*RIGHT, self.x_max*RIGHT) + self.main_line = Line(self.x_min * RIGHT, self.x_max * RIGHT) self.tick_marks = VGroup() self.add(self.main_line, self.tick_marks) - rounding_value = int(-np.log10(0.1*self.tick_frequency)) + rounding_value = int(-np.log10(0.1 * self.tick_frequency)) rounded_numbers_with_elongated_ticks = np.round( - self.numbers_with_elongated_ticks, + self.numbers_with_elongated_ticks, rounding_value ) for x in self.get_tick_numbers(): rounded_x = np.round(x, rounding_value) if rounded_x in rounded_numbers_with_elongated_ticks: - tick_size_used = self.longer_tick_multiple*self.tick_size + tick_size_used = self.longer_tick_multiple * self.tick_size else: tick_size_used = self.tick_size self.add_tick(x, tick_size_used) @@ -60,13 +61,14 @@ class NumberLine(VMobject): self.stretch(self.unit_size, 0) self.shift(-self.number_to_point(self.number_at_center)) - def add_tick(self, x, size = None): + def add_tick(self, x, size=None): self.tick_marks.add(self.get_tick(x, size)) return self - def get_tick(self, x, size = None): - if size is None: size = self.tick_size - result = Line(size*DOWN, size*UP) + def get_tick(self, x, size=None): + if size is None: + size = self.tick_size + result = Line(size * DOWN, size * UP) result.rotate(self.main_line.get_angle()) result.move_to(self.number_to_point(x)) return result @@ -77,12 +79,12 @@ class NumberLine(VMobject): def get_tick_numbers(self): epsilon = 0.001 return np.arange( - self.leftmost_tick, self.x_max+epsilon, + self.leftmost_tick, self.x_max + epsilon, self.tick_frequency ) def number_to_point(self, number): - alpha = float(number-self.x_min)/(self.x_max - self.x_min) + alpha = float(number - self.x_min) / (self.x_max - self.x_min) return interpolate( self.main_line.get_start(), self.main_line.get_end(), @@ -91,22 +93,23 @@ class NumberLine(VMobject): def point_to_number(self, point): left_point, right_point = self.main_line.get_start_and_end() - full_vect = right_point-left_point + full_vect = right_point - left_point + def distance_from_left(p): - return np.dot(p-left_point, full_vect)/np.linalg.norm(full_vect) + return np.dot(p - left_point, full_vect) / np.linalg.norm(full_vect) return interpolate( - self.x_min, self.x_max, - distance_from_left(point)/distance_from_left(right_point) + self.x_min, self.x_max, + distance_from_left(point) / distance_from_left(right_point) ) def default_numbers_to_display(self): if self.numbers_to_show is not None: return self.numbers_to_show - return np.arange(int(self.leftmost_tick), int(self.x_max)+1) + return np.arange(int(self.leftmost_tick), int(self.x_max) + 1) def get_number_mobjects(self, *numbers, **kwargs): - #TODO, handle decimals + # TODO, handle decimals if len(numbers) == 0: numbers = self.default_numbers_to_display() if "force_integers" in kwargs and kwargs["force_integers"]: @@ -135,20 +138,20 @@ class NumberLine(VMobject): def add_tip(self): start, end = self.main_line.get_start_and_end() - vect = (end - start)/np.linalg.norm(end-start) - arrow = Arrow(start, end + MED_SMALL_BUFF*vect, buff = 0) + vect = (end - start) / np.linalg.norm(end - start) + arrow = Arrow(start, end + MED_SMALL_BUFF * vect, buff=0) tip = arrow.tip tip.set_color(self.color) self.tip = tip self.add(tip) + class UnitInterval(NumberLine): CONFIG = { - "x_min" : 0, - "x_max" : 1, - "unit_size" : 6, - "tick_frequency" : 0.1, - "numbers_with_elongated_ticks" : [0, 1], - "number_at_center" : 0.5, + "x_min": 0, + "x_max": 1, + "unit_size": 6, + "tick_frequency": 0.1, + "numbers_with_elongated_ticks": [0, 1], + "number_at_center": 0.5, } - diff --git a/mobject/numbers.py b/mobject/numbers.py index bb50c070..1e8d84f2 100644 --- a/mobject/numbers.py +++ b/mobject/numbers.py @@ -7,29 +7,31 @@ from mobject.types.vectorized_mobject import VGroup from mobject.types.vectorized_mobject import VMobject from mobject.shape_matchers import BackgroundRectangle + class DecimalNumber(VMobject): CONFIG = { - "num_decimal_points" : 2, - "digit_to_digit_buff" : 0.05, - "show_ellipsis" : False, - "unit" : None, #Aligned to bottom unless it starts with "^" - "include_background_rectangle" : False, + "num_decimal_points": 2, + "digit_to_digit_buff": 0.05, + "show_ellipsis": False, + "unit": None, # Aligned to bottom unless it starts with "^" + "include_background_rectangle": False, } + def __init__(self, number, **kwargs): VMobject.__init__(self, **kwargs) self.number = number ndp = self.num_decimal_points - #Build number string + # Build number string if isinstance(number, complex): - num_string = '%.*f%s%.*fi'%( - ndp, number.real, + num_string = '%.*f%s%.*fi' % ( + ndp, number.real, "-" if number.imag < 0 else "+", ndp, abs(number.imag) ) else: - num_string = '%.*f'%(ndp, number) - negative_zero_string = "-%.*f"%(ndp, 0.) + num_string = '%.*f' % (ndp, number) + negative_zero_string = "-%.*f" % (ndp, 0.) if num_string == negative_zero_string: num_string = num_string[1:] self.add(*[ @@ -37,32 +39,31 @@ class DecimalNumber(VMobject): for char in num_string ]) - #Add non-numerical bits + # Add non-numerical bits if self.show_ellipsis: self.add(TexMobject("\\dots")) - if num_string.startswith("-"): minus = self.submobjects[0] minus.next_to( self.submobjects[1], LEFT, - buff = self.digit_to_digit_buff + buff=self.digit_to_digit_buff ) - if self.unit != None: + if self.unit is not None: self.unit_sign = TexMobject(self.unit) self.add(self.unit_sign) self.arrange_submobjects( - buff = self.digit_to_digit_buff, - aligned_edge = DOWN + buff=self.digit_to_digit_buff, + aligned_edge=DOWN ) - #Handle alignment of parts that should be aligned - #to the bottom + # Handle alignment of parts that should be aligned + # to the bottom for i, c in enumerate(num_string): - if c == "-" and len(num_string) > i+1: - self[i].align_to(self[i+1], alignment_vect = UP) + if c == "-" and len(num_string) > i + 1: + self[i].align_to(self[i + 1], alignment_vect=UP) if self.unit and self.unit.startswith("^"): self.unit_sign.align_to(self, UP) # @@ -70,8 +71,8 @@ class DecimalNumber(VMobject): self.add_background_rectangle() def add_background_rectangle(self): - #TODO, is this the best way to handle - #background rectangles? + # TODO, is this the best way to handle + # background rectangles? self.background_rectangle = BackgroundRectangle(self) self.submobjects = [ self.background_rectangle, @@ -79,7 +80,8 @@ class DecimalNumber(VMobject): ] return self + class Integer(DecimalNumber): CONFIG = { - "num_decimal_points" : 0, + "num_decimal_points": 0, } diff --git a/mobject/probability.py b/mobject/probability.py index d4a1ef93..9f7b923b 100644 --- a/mobject/probability.py +++ b/mobject/probability.py @@ -15,29 +15,31 @@ from utils.iterables import tuplify EPSILON = 0.0001 + class SampleSpace(Rectangle): CONFIG = { - "height" : 3, - "width" : 3, - "fill_color" : DARK_GREY, - "fill_opacity" : 1, - "stroke_width" : 0.5, - "stroke_color" : LIGHT_GREY, + "height": 3, + "width": 3, + "fill_color": DARK_GREY, + "fill_opacity": 1, + "stroke_width": 0.5, + "stroke_color": LIGHT_GREY, ## - "default_label_scale_val" : 1, + "default_label_scale_val": 1, } - def add_title(self, title = "Sample space", buff = MED_SMALL_BUFF): - ##TODO, should this really exist in SampleSpaceScene + + def add_title(self, title="Sample space", buff=MED_SMALL_BUFF): + # TODO, should this really exist in SampleSpaceScene title_mob = TextMobject(title) if title_mob.get_width() > self.get_width(): title_mob.scale_to_fit_width(self.get_width()) - title_mob.next_to(self, UP, buff = buff) + title_mob.next_to(self, UP, buff=buff) self.title = title_mob self.add(title_mob) def add_label(self, label): self.label = label - + def complete_p_list(self, p_list): new_p_list = list(tuplify(p_list)) remainder = 1.0 - sum(new_p_list) @@ -54,7 +56,7 @@ class SampleSpace(Rectangle): for factor, color in zip(p_list, colors): part = SampleSpace() part.set_fill(color, 1) - part.replace(self, stretch = True) + part.replace(self, stretch=True) part.stretch(factor, dim) part.move_to(last_point, -vect) last_point = part.get_edge_center(vect) @@ -62,17 +64,17 @@ class SampleSpace(Rectangle): return parts def get_horizontal_division( - self, p_list, - colors = [GREEN_E, BLUE_E], - vect = DOWN - ): + self, p_list, + colors=[GREEN_E, BLUE_E], + vect=DOWN + ): return self.get_division_along_dimension(p_list, 1, colors, vect) def get_vertical_division( - self, p_list, - colors = [MAROON_B, YELLOW], - vect = RIGHT - ): + self, p_list, + colors=[MAROON_B, YELLOW], + vect=RIGHT + ): return self.get_division_along_dimension(p_list, 0, colors, vect) def divide_horizontally(self, *args, **kwargs): @@ -85,16 +87,16 @@ class SampleSpace(Rectangle): def get_subdivision_braces_and_labels( self, parts, labels, direction, - buff = SMALL_BUFF, - min_num_quads = 1 - ): + buff=SMALL_BUFF, + min_num_quads=1 + ): label_mobs = VGroup() braces = VGroup() for label, part in zip(labels, parts): brace = Brace( - part, direction, - min_num_quads = min_num_quads, - buff = buff + part, direction, + min_num_quads=min_num_quads, + buff=buff ) if isinstance(label, Mobject): label_mob = label @@ -108,13 +110,13 @@ class SampleSpace(Rectangle): parts.braces = braces parts.labels = label_mobs parts.label_kwargs = { - "labels" : label_mobs.copy(), - "direction" : direction, - "buff" : buff, + "labels": label_mobs.copy(), + "direction": direction, + "buff": buff, } return VGroup(parts.braces, parts.labels) - def get_side_braces_and_labels(self, labels, direction = LEFT, **kwargs): + def get_side_braces_and_labels(self, labels, direction=LEFT, **kwargs): assert(hasattr(self, "horizontal_parts")) parts = self.horizontal_parts return self.get_subdivision_braces_and_labels(parts, labels, direction, **kwargs) @@ -145,21 +147,23 @@ class SampleSpace(Rectangle): return self.vertical_parts[index] return self.split()[index] + class BarChart(VGroup): CONFIG = { - "height" : 4, - "width" : 6, - "n_ticks" : 4, - "tick_width" : 0.2, - "label_y_axis" : True, - "y_axis_label_height" : 0.25, - "max_value" : 1, - "bar_colors" : [BLUE, YELLOW], - "bar_fill_opacity" : 0.8, - "bar_stroke_width" : 3, - "bar_names" : [], - "bar_label_scale_val" : 0.75, + "height": 4, + "width": 6, + "n_ticks": 4, + "tick_width": 0.2, + "label_y_axis": True, + "y_axis_label_height": 0.25, + "max_value": 1, + "bar_colors": [BLUE, YELLOW], + "bar_fill_opacity": 0.8, + "bar_stroke_width": 3, + "bar_names": [], + "bar_label_scale_val": 0.75, } + def __init__(self, values, **kwargs): VGroup.__init__(self, **kwargs) if self.max_value is None: @@ -170,15 +174,15 @@ class BarChart(VGroup): self.center() def add_axes(self): - x_axis = Line(self.tick_width*LEFT/2, self.width*RIGHT) - y_axis = Line(MED_LARGE_BUFF*DOWN, self.height*UP) + x_axis = Line(self.tick_width * LEFT / 2, self.width * RIGHT) + y_axis = Line(MED_LARGE_BUFF * DOWN, self.height * UP) ticks = VGroup() - heights = np.linspace(0, self.height, self.n_ticks+1) - values = np.linspace(0, self.max_value, self.n_ticks+1) + heights = np.linspace(0, self.height, self.n_ticks + 1) + values = np.linspace(0, self.max_value, self.n_ticks + 1) for y, value in zip(heights, values): tick = Line(LEFT, RIGHT) tick.scale_to_fit_width(self.tick_width) - tick.move_to(y*UP) + tick.move_to(y * UP) ticks.add(tick) y_axis.add(ticks) @@ -195,18 +199,17 @@ class BarChart(VGroup): self.y_axis_labels = labels self.add(labels) - def add_bars(self, values): - buff = float(self.width) / (2*len(values) + 1) + buff = float(self.width) / (2 * len(values) + 1) bars = VGroup() for i, value in enumerate(values): bar = Rectangle( - height = (value/self.max_value)*self.height, - width = buff, - stroke_width = self.bar_stroke_width, - fill_opacity = self.bar_fill_opacity, + height=(value / self.max_value) * self.height, + width=buff, + stroke_width=self.bar_stroke_width, + fill_opacity=self.bar_fill_opacity, ) - bar.move_to((2*i+1)*buff*RIGHT, DOWN+LEFT) + bar.move_to((2 * i + 1) * buff * RIGHT, DOWN + LEFT) bars.add(bar) bars.set_color_by_gradient(*self.bar_colors) @@ -225,27 +228,9 @@ class BarChart(VGroup): for bar, value in zip(self.bars, values): bar_bottom = bar.get_bottom() bar.stretch_to_fit_height( - (value/self.max_value)*self.height + (value / self.max_value) * self.height ) bar.move_to(bar_bottom, DOWN) def copy(self): return self.deepcopy() - - - - - - - - - - - - - - - - - - diff --git a/mobject/shape_matchers.py b/mobject/shape_matchers.py index b2e1199a..7ecbb457 100644 --- a/mobject/shape_matchers.py +++ b/mobject/shape_matchers.py @@ -8,31 +8,35 @@ from mobject.types.vectorized_mobject import VGroup from utils.config_ops import digest_config from utils.color import Color + class SurroundingRectangle(Rectangle): CONFIG = { - "color" : YELLOW, - "buff" : SMALL_BUFF, + "color": YELLOW, + "buff": SMALL_BUFF, } + def __init__(self, mobject, **kwargs): digest_config(self, kwargs) - kwargs["width"] = mobject.get_width() + 2*self.buff - kwargs["height"] = mobject.get_height() + 2*self.buff + kwargs["width"] = mobject.get_width() + 2 * self.buff + kwargs["height"] = mobject.get_height() + 2 * self.buff Rectangle.__init__(self, **kwargs) self.move_to(mobject) + class BackgroundRectangle(SurroundingRectangle): CONFIG = { - "color" : BLACK, - "stroke_width" : 0, - "fill_opacity" : 0.75, - "buff" : 0 + "color": BLACK, + "stroke_width": 0, + "fill_opacity": 0.75, + "buff": 0 } + def __init__(self, mobject, **kwargs): SurroundingRectangle.__init__(self, mobject, **kwargs) self.original_fill_opacity = self.fill_opacity def pointwise_become_partial(self, mobject, a, b): - self.set_fill(opacity = b*self.original_fill_opacity) + self.set_fill(opacity=b * self.original_fill_opacity) return self def set_color(self): @@ -42,15 +46,17 @@ class BackgroundRectangle(SurroundingRectangle): def get_fill_color(self): return Color(self.color) + class Cross(VGroup): CONFIG = { - "stroke_color" : RED, - "stroke_width" : 6, + "stroke_color": RED, + "stroke_width": 6, } + def __init__(self, mobject, **kwargs): - VGroup.__init__(self, - Line(UP+LEFT, DOWN+RIGHT), - Line(UP+RIGHT, DOWN+LEFT), - ) - self.replace(mobject, stretch = True) + VGroup.__init__(self, + Line(UP + LEFT, DOWN + RIGHT), + Line(UP + RIGHT, DOWN + LEFT), + ) + self.replace(mobject, stretch=True) self.set_stroke(self.stroke_color, self.stroke_width) diff --git a/mobject/svg/brace.py b/mobject/svg/brace.py index c56d882d..4ef048da 100644 --- a/mobject/svg/brace.py +++ b/mobject/svg/brace.py @@ -12,35 +12,37 @@ from mobject.svg.tex_mobject import TextMobject from mobject.types.vectorized_mobject import VMobject from utils.config_ops import digest_config + class Brace(TexMobject): CONFIG = { - "buff" : 0.2, - "width_multiplier" : 2, - "max_num_quads" : 15, - "min_num_quads" : 0, + "buff": 0.2, + "width_multiplier": 2, + "max_num_quads": 15, + "min_num_quads": 0, } - def __init__(self, mobject, direction = DOWN, **kwargs): + + def __init__(self, mobject, direction=DOWN, **kwargs): digest_config(self, kwargs, locals()) angle = -np.arctan2(*direction[:2]) + np.pi - mobject.rotate(-angle, about_point = ORIGIN) - left = mobject.get_corner(DOWN+LEFT) - right = mobject.get_corner(DOWN+RIGHT) - target_width = right[0]-left[0] + mobject.rotate(-angle, about_point=ORIGIN) + left = mobject.get_corner(DOWN + LEFT) + right = mobject.get_corner(DOWN + RIGHT) + target_width = right[0] - left[0] - ## Adding int(target_width) qquads gives approximately the right width + # Adding int(target_width) qquads gives approximately the right width num_quads = np.clip( - int(self.width_multiplier*target_width), + int(self.width_multiplier * target_width), self.min_num_quads, self.max_num_quads ) - tex_string = "\\underbrace{%s}"%(num_quads*"\\qquad") + tex_string = "\\underbrace{%s}" % (num_quads * "\\qquad") TexMobject.__init__(self, tex_string, **kwargs) - self.tip_point_index = np.argmin(self.get_all_points()[:,1]) + self.tip_point_index = np.argmin(self.get_all_points()[:, 1]) self.stretch_to_fit_width(target_width) - self.shift(left - self.get_corner(UP+LEFT) + self.buff*DOWN) + self.shift(left - self.get_corner(UP + LEFT) + self.buff * DOWN) for mob in mobject, self: - mob.rotate(angle, about_point = ORIGIN) + mob.rotate(angle, about_point=ORIGIN) - def put_at_tip(self, mob, use_next_to = True, **kwargs): + def put_at_tip(self, mob, use_next_to=True, **kwargs): if use_next_to: mob.next_to( self.get_tip(), @@ -50,8 +52,8 @@ class Brace(TexMobject): else: mob.move_to(self.get_tip()) buff = kwargs.get("buff", DEFAULT_MOBJECT_TO_MOBJECT_BUFFER) - shift_distance = mob.get_width()/2.0+buff - mob.shift(self.get_direction()*shift_distance) + shift_distance = mob.get_width() / 2.0 + buff + mob.shift(self.get_direction() * shift_distance) return self def get_text(self, *text, **kwargs): @@ -72,32 +74,38 @@ class Brace(TexMobject): def get_direction(self): vect = self.get_tip() - self.get_center() - return vect/np.linalg.norm(vect) + return vect / np.linalg.norm(vect) + class BraceLabel(VMobject): CONFIG = { - "label_constructor" : TexMobject, - "label_scale" : 1, + "label_constructor": TexMobject, + "label_scale": 1, } - def __init__(self, obj, text, brace_direction = DOWN, **kwargs): + + def __init__(self, obj, text, brace_direction=DOWN, **kwargs): VMobject.__init__(self, **kwargs) self.brace_direction = brace_direction - if isinstance(obj, list): obj = VMobject(*obj) + if isinstance(obj, list): + obj = VMobject(*obj) self.brace = Brace(obj, brace_direction, **kwargs) if isinstance(text, tuple) or isinstance(text, list): self.label = self.label_constructor(*text, **kwargs) - else: self.label = self.label_constructor(str(text)) - if self.label_scale != 1: self.label.scale(self.label_scale) + else: + self.label = self.label_constructor(str(text)) + if self.label_scale != 1: + self.label.scale(self.label_scale) self.brace.put_at_tip(self.label) self.submobjects = [self.brace, self.label] - def creation_anim(self, label_anim = FadeIn, brace_anim = GrowFromCenter): + def creation_anim(self, label_anim=FadeIn, brace_anim=GrowFromCenter): return AnimationGroup(brace_anim(self.brace), label_anim(self.label)) def shift_brace(self, obj, **kwargs): - if isinstance(obj, list): obj = VMobject(*obj) + if isinstance(obj, list): + obj = VMobject(*obj) self.brace = Brace(obj, self.brace_direction, **kwargs) self.brace.put_at_tip(self.label) self.submobjects[0] = self.brace @@ -105,7 +113,8 @@ class BraceLabel(VMobject): def change_label(self, *text, **kwargs): self.label = self.label_constructor(*text, **kwargs) - if self.label_scale != 1: self.label.scale(self.label_scale) + if self.label_scale != 1: + self.label.scale(self.label_scale) self.brace.put_at_tip(self.label) self.submobjects[1] = self.label @@ -124,7 +133,8 @@ class BraceLabel(VMobject): return copy_mobject + class BraceText(BraceLabel): CONFIG = { - "label_constructor" : TextMobject + "label_constructor": TextMobject } diff --git a/mobject/svg/drawings.py b/mobject/svg/drawings.py index 9b4ac6ca..6206c33c 100644 --- a/mobject/svg/drawings.py +++ b/mobject/svg/drawings.py @@ -4,7 +4,6 @@ from constants import * from mobject.mobject import Mobject from mobject.svg.svg_mobject import SVGMobject -from mobject.svg.brace import Brace from mobject.svg.tex_mobject import TexMobject from mobject.svg.tex_mobject import TextMobject from mobject.types.vectorized_mobject import VGroup @@ -12,12 +11,7 @@ from mobject.types.vectorized_mobject import VMobject from mobject.types.vectorized_mobject import VectorizedPoint from animation.animation import Animation -from animation.composition import AnimationGroup -from animation.composition import LaggedStart from animation.rotation import Rotating -from animation.transform import ApplyMethod -from animation.creation import FadeIn -from animation.creation import GrowFromCenter from mobject.geometry import Arc from mobject.geometry import Circle @@ -25,97 +19,102 @@ from mobject.geometry import Line from mobject.geometry import Polygon from mobject.geometry import Rectangle from mobject.geometry import Square -from mobject.shape_matchers import SurroundingRectangle from mobject.three_dimensions import Cube from utils.config_ops import digest_config -from utils.config_ops import digest_locals -from utils.space_ops import R3_to_complex from utils.space_ops import angle_of_vector from utils.space_ops import complex_to_R3 from utils.space_ops import rotate_vector + class Lightbulb(SVGMobject): CONFIG = { - "file_name" : "lightbulb", - "height" : 1, - "stroke_color" : YELLOW, - "stroke_width" : 3, - "fill_color" : YELLOW, - "fill_opacity" : 0, + "file_name": "lightbulb", + "height": 1, + "stroke_color": YELLOW, + "stroke_width": 3, + "fill_color": YELLOW, + "fill_opacity": 0, } + class BitcoinLogo(SVGMobject): CONFIG = { - "file_name" : "Bitcoin_logo", - "height" : 1, - "fill_color" : "#f7931a", - "inner_color" : WHITE, - "fill_opacity" : 1, - "stroke_width" : 0, + "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", - "height" : 2.5, - "fill_color" : DARK_GREY, - "fill_opacity" : 1, - "stroke_color" : WHITE, - "stroke_width" : 0.5, + "file_name": "guitar", + "height": 2.5, + "fill_color": DARK_GREY, + "fill_opacity": 1, + "stroke_color": WHITE, + "stroke_width": 0.5, } + class SunGlasses(SVGMobject): CONFIG = { - "file_name" : "sunglasses", - "glasses_width_to_eyes_width" : 1.1, + "file_name": "sunglasses", + "glasses_width_to_eyes_width": 1.1, } + def __init__(self, pi_creature, **kwargs): SVGMobject.__init__(self, **kwargs) - self.set_stroke(WHITE, width = 0) + self.set_stroke(WHITE, width=0) self.set_fill(GREY, 1) self.scale_to_fit_width( - self.glasses_width_to_eyes_width*pi_creature.eyes.get_width() + self.glasses_width_to_eyes_width * pi_creature.eyes.get_width() ) self.move_to(pi_creature.eyes, UP) + class Speedometer(VMobject): CONFIG = { - "arc_angle" : 4*np.pi/3, - "num_ticks" : 8, - "tick_length" : 0.2, - "needle_width" : 0.1, - "needle_height" : 0.8, - "needle_color" : YELLOW, + "arc_angle": 4 * np.pi / 3, + "num_ticks": 8, + "tick_length": 0.2, + "needle_width": 0.1, + "needle_height": 0.8, + "needle_color": YELLOW, } + def generate_points(self): - start_angle = np.pi/2 + self.arc_angle/2 - end_angle = np.pi/2 - self.arc_angle/2 + start_angle = np.pi / 2 + self.arc_angle / 2 + end_angle = np.pi / 2 - self.arc_angle / 2 self.add(Arc( - start_angle = start_angle, - angle = -self.arc_angle + start_angle=start_angle, + angle=-self.arc_angle )) tick_angle_range = np.linspace(start_angle, end_angle, self.num_ticks) for index, angle in enumerate(tick_angle_range): vect = rotate_vector(RIGHT, angle) - tick = Line((1-self.tick_length)*vect, vect) - label = TexMobject(str(10*index)) + tick = Line((1 - self.tick_length) * vect, vect) + label = TexMobject(str(10 * index)) label.scale_to_fit_height(self.tick_length) - label.shift((1+self.tick_length)*vect) + label.shift((1 + self.tick_length) * vect) self.add(tick, label) needle = Polygon( LEFT, UP, RIGHT, - stroke_width = 0, - fill_opacity = 1, - fill_color = self.needle_color + stroke_width=0, + fill_opacity=1, + fill_color=self.needle_color ) needle.stretch_to_fit_width(self.needle_width) needle.stretch_to_fit_height(self.needle_height) - needle.rotate(start_angle - np.pi/2, about_point = ORIGIN) + needle.rotate(start_angle - np.pi / 2, about_point=ORIGIN) self.add(needle) self.needle = needle @@ -136,25 +135,27 @@ class Speedometer(VMobject): ) def rotate_needle(self, angle): - self.needle.rotate(angle, about_point = self.get_center()) + self.needle.rotate(angle, about_point=self.get_center()) return self def move_needle_to_velocity(self, velocity): - max_velocity = 10*(self.num_ticks-1) + max_velocity = 10 * (self.num_ticks - 1) proportion = float(velocity) / max_velocity - start_angle = np.pi/2 + self.arc_angle/2 + start_angle = np.pi / 2 + self.arc_angle / 2 target_angle = start_angle - self.arc_angle * proportion self.rotate_needle(target_angle - self.get_needle_angle()) return self + class AoPSLogo(SVGMobject): CONFIG = { - "file_name" : "aops_logo", - "height" : 1.5, + "file_name": "aops_logo", + "height": 1.5, } + def __init__(self, **kwargs): SVGMobject.__init__(self, **kwargs) - self.set_stroke(WHITE, width = 0) + self.set_stroke(WHITE, width=0) colors = [BLUE_E, "#008445", GREEN_B] index_lists = [ (10, 11, 12, 13, 14, 21, 22, 23, 24, 27, 28, 29, 30), @@ -163,222 +164,239 @@ class AoPSLogo(SVGMobject): ] for color, index_list in zip(colors, index_lists): for i in index_list: - self.submobjects[i].set_fill(color, opacity = 1) + self.submobjects[i].set_fill(color, opacity=1) self.scale_to_fit_height(self.height) self.center() + class PartyHat(SVGMobject): CONFIG = { - "file_name" : "party_hat", - "height" : 1.5, - "pi_creature" : None, - "stroke_width" : 0, - "fill_opacity" : 1, - "propagate_style_to_family" : True, - "frills_colors" : [MAROON_B, PURPLE], - "cone_color" : GREEN, - "dots_colors" : [YELLOW], + "file_name": "party_hat", + "height": 1.5, + "pi_creature": None, + "stroke_width": 0, + "fill_opacity": 1, + "propagate_style_to_family": True, + "frills_colors": [MAROON_B, PURPLE], + "cone_color": GREEN, + "dots_colors": [YELLOW], } NUM_FRILLS = 7 NUM_DOTS = 6 + def __init__(self, **kwargs): SVGMobject.__init__(self, **kwargs) self.scale_to_fit_height(self.height) if self.pi_creature is not None: - self.next_to(self.pi_creature.eyes, UP, buff = 0) + self.next_to(self.pi_creature.eyes, UP, buff=0) self.frills = VGroup(*self[:self.NUM_FRILLS]) self.cone = self[self.NUM_FRILLS] - self.dots = VGroup(*self[self.NUM_FRILLS+1:]) + self.dots = VGroup(*self[self.NUM_FRILLS + 1:]) self.frills.set_color_by_gradient(*self.frills_colors) self.cone.set_color(self.cone_color) self.dots.set_color_by_gradient(*self.dots_colors) + class Laptop(VGroup): CONFIG = { - "width" : 3, - "body_dimensions" : [4, 3, 0.05], - "screen_thickness" : 0.01, - "keyboard_width_to_body_width" : 0.9, - "keyboard_height_to_body_height" : 0.5, - "screen_width_to_screen_plate_width" : 0.9, - "key_color_kwargs" : { - "stroke_width" : 0, - "fill_color" : BLACK, - "fill_opacity" : 1, + "width": 3, + "body_dimensions": [4, 3, 0.05], + "screen_thickness": 0.01, + "keyboard_width_to_body_width": 0.9, + "keyboard_height_to_body_height": 0.5, + "screen_width_to_screen_plate_width": 0.9, + "key_color_kwargs": { + "stroke_width": 0, + "fill_color": BLACK, + "fill_opacity": 1, }, - "body_color" : LIGHT_GREY, - "shaded_body_color" : GREY, - "open_angle" : np.pi/4, + "body_color": LIGHT_GREY, + "shaded_body_color": GREY, + "open_angle": np.pi / 4, } + def generate_points(self): - body = Cube(side_length = 1) + body = Cube(side_length=1) for dim, scale_factor in enumerate(self.body_dimensions): - body.stretch(scale_factor, dim = dim) + body.stretch(scale_factor, dim=dim) body.scale_to_fit_width(self.width) - body.set_fill(self.shaded_body_color, opacity = 1) - body.sort_submobjects(lambda p : p[2]) + body.set_fill(self.shaded_body_color, opacity=1) + body.sort_submobjects(lambda p: p[2]) body[-1].set_fill(self.body_color) keyboard = VGroup(*[ VGroup(*[ Square(**self.key_color_kwargs) - for x in range(12-y%2) - ]).arrange_submobjects(RIGHT, buff = SMALL_BUFF) + for x in range(12 - y % 2) + ]).arrange_submobjects(RIGHT, buff=SMALL_BUFF) for y in range(4) - ]).arrange_submobjects(DOWN, buff = MED_SMALL_BUFF) + ]).arrange_submobjects(DOWN, buff=MED_SMALL_BUFF) keyboard.stretch_to_fit_width( - self.keyboard_width_to_body_width*body.get_width(), + self.keyboard_width_to_body_width * body.get_width(), ) keyboard.stretch_to_fit_height( - self.keyboard_height_to_body_height*body.get_height(), + self.keyboard_height_to_body_height * body.get_height(), ) - keyboard.next_to(body, OUT, buff = 0.1*SMALL_BUFF) - keyboard.shift(MED_SMALL_BUFF*UP) + keyboard.next_to(body, OUT, buff=0.1 * SMALL_BUFF) + keyboard.shift(MED_SMALL_BUFF * UP) body.add(keyboard) screen_plate = body.copy() - screen_plate.stretch(self.screen_thickness/self.body_dimensions[2], dim = 2) + screen_plate.stretch(self.screen_thickness / + self.body_dimensions[2], dim=2) screen = Rectangle( - stroke_width = 0, - fill_color = BLACK, - fill_opacity = 1, + stroke_width=0, + fill_color=BLACK, + fill_opacity=1, ) - screen.replace(screen_plate, stretch = True) + screen.replace(screen_plate, stretch=True) screen.scale_in_place(self.screen_width_to_screen_plate_width) - screen.next_to(screen_plate, OUT, buff = 0.1*SMALL_BUFF) + screen.next_to(screen_plate, OUT, buff=0.1 * SMALL_BUFF) screen_plate.add(screen) - screen_plate.next_to(body, UP, buff = 0) + screen_plate.next_to(body, UP, buff=0) screen_plate.rotate( - self.open_angle, RIGHT, - about_point = screen_plate.get_bottom() + self.open_angle, RIGHT, + about_point=screen_plate.get_bottom() ) self.screen_plate = screen_plate self.screen = screen axis = Line( - body.get_corner(UP+LEFT+OUT), - body.get_corner(UP+RIGHT+OUT), - color = BLACK, - stroke_width = 2 + body.get_corner(UP + LEFT + OUT), + body.get_corner(UP + RIGHT + OUT), + color=BLACK, + stroke_width=2 ) self.axis = axis self.add(body, screen_plate, axis) - self.rotate(5*np.pi/12, LEFT, about_point = ORIGIN) - self.rotate(np.pi/6, DOWN, about_point = ORIGIN) + self.rotate(5 * np.pi / 12, LEFT, about_point=ORIGIN) + self.rotate(np.pi / 6, DOWN, about_point=ORIGIN) + class PatreonLogo(SVGMobject): CONFIG = { - "file_name" : "patreon_logo", - "fill_color" : "#F96854", + "file_name": "patreon_logo", + "fill_color": "#F96854", # "fill_color" : WHITE, - "fill_opacity" : 1, - "stroke_width" : 0, - "width" : 4, - "propagate_style_to_family" : True + "fill_opacity": 1, + "stroke_width": 0, + "width": 4, + "propagate_style_to_family": True } + def __init__(self, **kwargs): SVGMobject.__init__(self, **kwargs) self.scale_to_fit_width(self.width) self.center() + class VideoIcon(SVGMobject): CONFIG = { - "file_name" : "video_icon", - "width" : FRAME_WIDTH/12., + "file_name": "video_icon", + "width": FRAME_WIDTH / 12., } + 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) + self.set_stroke(color=WHITE, width=0) + self.set_fill(color=WHITE, opacity=1) + class VideoSeries(VGroup): CONFIG = { - "num_videos" : 11, - "gradient_colors" : [BLUE_B, BLUE_D], + "num_videos": 11, + "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.scale_to_fit_width(FRAME_WIDTH-MED_LARGE_BUFF) + self.scale_to_fit_width(FRAME_WIDTH - MED_LARGE_BUFF) self.set_color_by_gradient(*self.gradient_colors) + class Headphones(SVGMobject): CONFIG = { - "file_name" : "headphones", - "height" : 2, - "y_stretch_factor" : 0.5, - "color" : GREY, + "file_name": "headphones", + "height": 2, + "y_stretch_factor": 0.5, + "color": GREY, } - def __init__(self, pi_creature = None, **kwargs): + + def __init__(self, pi_creature=None, **kwargs): digest_config(self, kwargs) - SVGMobject.__init__(self, file_name = self.file_name, **kwargs) - self.stretch(self.y_stretch_factor, 1) + SVGMobject.__init__(self, file_name=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) + self.set_stroke(width=0) + self.set_fill(color=self.color) if pi_creature is not None: eyes = pi_creature.eyes - self.scale_to_fit_height(3*eyes.get_height()) + self.scale_to_fit_height(3 * eyes.get_height()) self.move_to(eyes, DOWN) - self.shift(DOWN*eyes.get_height()/4) + self.shift(DOWN * eyes.get_height() / 4) + class Clock(VGroup): CONFIG = { - "propagate_style_to_family" : True, + "propagate_style_to_family": True, } + def __init__(self, **kwargs): circle = Circle() ticks = [] for x in range(12): - alpha = x/12. + alpha = x / 12. point = complex_to_R3( - np.exp(2*np.pi*alpha*complex(0, 1)) + np.exp(2 * np.pi * alpha * complex(0, 1)) ) - length = 0.2 if x%3 == 0 else 0.1 + length = 0.2 if x % 3 == 0 else 0.1 ticks.append( - Line(point, (1-length)*point) + Line(point, (1 - length) * point) ) - self.hour_hand = Line(ORIGIN, 0.3*UP) - self.minute_hand = Line(ORIGIN, 0.6*UP) + self.hour_hand = Line(ORIGIN, 0.3 * UP) + self.minute_hand = Line(ORIGIN, 0.6 * UP) # for hand in self.hour_hand, self.minute_hand: # #Balance out where the center is # hand.add(VectorizedPoint(-hand.get_end())) VGroup.__init__( - self, circle, + self, circle, self.hour_hand, self.minute_hand, *ticks ) + class ClockPassesTime(Animation): CONFIG = { - "run_time" : 5, - "hours_passed" : 12, - "rate_func" : None, + "run_time": 5, + "hours_passed": 12, + "rate_func": None, } + def __init__(self, clock, **kwargs): digest_config(self, kwargs) assert(isinstance(clock, Clock)) rot_kwargs = { - "axis" : OUT, - "about_point" : clock.get_center() + "axis": OUT, + "about_point": clock.get_center() } - hour_radians = -self.hours_passed*2*np.pi/12 + hour_radians = -self.hours_passed * 2 * np.pi / 12 self.hour_rotation = Rotating( - clock.hour_hand, - radians = hour_radians, + clock.hour_hand, + radians=hour_radians, **rot_kwargs ) self.minute_rotation = Rotating( - clock.minute_hand, - radians = 12*hour_radians, + clock.minute_hand, + radians=12 * hour_radians, **rot_kwargs ) Animation.__init__(self, clock, **kwargs) @@ -387,21 +405,23 @@ class ClockPassesTime(Animation): for rotation in self.hour_rotation, self.minute_rotation: rotation.update_mobject(alpha) + 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, - "propagate_style_to_family" : True, - "fill_color" : BLACK, - "fill_opacity" : 0.8, - "stroke_color" : WHITE, - "stroke_width" : 3, + "direction": LEFT, + "center_point": ORIGIN, + "content_scale_factor": 0.75, + "height": 5, + "width": 8, + "bubble_center_adjustment_factor": 1. / 8, + "file_name": None, + "propagate_style_to_family": True, + "fill_color": BLACK, + "fill_opacity": 0.8, + "stroke_color": WHITE, + "stroke_width": 3, } + def __init__(self, **kwargs): digest_config(self, kwargs, locals()) if self.file_name is None: @@ -420,19 +440,19 @@ class Bubble(SVGMobject): self.content = Mobject() def get_tip(self): - #TODO, find a better way - return self.get_corner(DOWN+self.direction)-0.6*self.direction + # 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 + return self.get_center() + factor * self.get_height() * UP def move_tip_to(self, point): VGroup(self, self.content).shift(point - self.get_tip()) return self def flip(self): - Mobject.flip(self) + Mobject.flip(self) self.direction = -np.array(self.direction) return self @@ -442,13 +462,13 @@ class Bubble(SVGMobject): 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) + 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() + scaled_width = self.content_scale_factor * self.get_width() if mobject.get_width() > scaled_width: mobject.scale_to_fit_width(scaled_width) mobject.shift( @@ -469,7 +489,7 @@ class Bubble(SVGMobject): target_width = self.content.get_width() target_width += max(MED_LARGE_BUFF, 2) target_height = self.content.get_height() - target_height += 2.5*LARGE_BUFF + 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) @@ -480,58 +500,63 @@ class Bubble(SVGMobject): self.add_content(VMobject()) return self + class SpeechBubble(Bubble): CONFIG = { - "file_name" : "Bubbles_speech.svg", - "height" : 4 + "file_name": "Bubbles_speech.svg", + "height": 4 } + class DoubleSpeechBubble(Bubble): CONFIG = { - "file_name" : "Bubbles_double_speech.svg", - "height" : 4 + "file_name": "Bubbles_double_speech.svg", + "height": 4 } + class ThoughtBubble(Bubble): CONFIG = { - "file_name" : "Bubbles_thought.svg", + "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]) + 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) + self.submobjects[-1].set_fill(GREEN_SCREEN, opacity=1) return self + class Car(SVGMobject): CONFIG = { - "file_name" : "Car", - "height" : 1, - "color" : LIGHT_GREY, - "light_colors" : [BLACK, BLACK], + "file_name": "Car", + "height": 1, + "color": LIGHT_GREY, + "light_colors": [BLACK, BLACK], } + def __init__(self, **kwargs): SVGMobject.__init__(self, **kwargs) self.scale_to_fit_height(self.height) - self.set_stroke(color = WHITE, width = 0) - self.set_fill(self.color, opacity = 1) + self.set_stroke(color=WHITE, width=0) + self.set_fill(self.color, opacity=1) from for_3b1b_videos.pi_creature import Randolph - randy = Randolph(mode = "happy") - randy.scale_to_fit_height(0.6*self.get_height()) + randy = Randolph(mode="happy") + randy.scale_to_fit_height(0.6 * self.get_height()) randy.stretch(0.8, 0) randy.look(RIGHT) randy.move_to(self) - randy.shift(0.07*self.height*(RIGHT+UP)) + randy.shift(0.07 * self.height * (RIGHT + UP)) self.randy = self.pi_creature = randy self.add_to_back(randy) orientation_line = Line(self.get_left(), self.get_right()) - orientation_line.set_stroke(width = 0) + orientation_line.set_stroke(width=0) self.add(orientation_line) self.orientation_line = orientation_line @@ -543,32 +568,32 @@ class Car(SVGMobject): def move_to(self, point_or_mobject): vect = rotate_vector( - UP+LEFT, self.orientation_line.get_angle() + UP + LEFT, self.orientation_line.get_angle() ) - self.next_to(point_or_mobject, vect, buff = 0) + self.next_to(point_or_mobject, vect, buff=0) return self def get_front_line(self): return DashedLine( - self.get_corner(UP+RIGHT), - self.get_corner(DOWN+RIGHT), - color = DISTANCE_COLOR, - dashed_segment_length = 0.05, + self.get_corner(UP + RIGHT), + self.get_corner(DOWN + RIGHT), + color=DISTANCE_COLOR, + dashed_segment_length=0.05, ) def add_treds_to_tires(self): for tire in self.get_tires(): - radius = tire.get_width()/2 + radius = tire.get_width() / 2 center = tire.get_center() tred = Line( - 0.9*radius*RIGHT, 1.4*radius*RIGHT, - stroke_width = 2, - color = BLACK + 0.9 * radius * RIGHT, 1.4 * radius * RIGHT, + stroke_width=2, + color=BLACK ) - tred.rotate_in_place(np.pi/4) - for theta in np.arange(0, 2*np.pi, np.pi/4): + tred.rotate_in_place(np.pi / 4) + for theta in np.arange(0, 2 * np.pi, np.pi / 4): new_tred = tred.copy() - new_tred.rotate(theta, about_point = ORIGIN) + new_tred.rotate(theta, about_point=ORIGIN) new_tred.shift(center) tire.add(new_tred) return self @@ -587,43 +612,45 @@ class Car(SVGMobject): ### Cards ### + class DeckOfCards(VGroup): def __init__(self, **kwargs): possible_values = map(str, range(1, 11)) + ["J", "Q", "K"] possible_suits = ["hearts", "diamonds", "spades", "clubs"] VGroup.__init__(self, *[ - PlayingCard(value = value, suit = suit, **kwargs) + PlayingCard(value=value, suit=suit, **kwargs) for value in possible_values for suit in possible_suits ]) + class PlayingCard(VGroup): CONFIG = { - "value" : None, - "suit" : None, - "key" : None, ##String like "8H" or "KS" - "height" : 2, - "height_to_width" : 3.5/2.5, - "card_height_to_symbol_height" : 7, - "card_width_to_corner_num_width" : 10, - "card_height_to_corner_num_height" : 10, - "color" : LIGHT_GREY, - "turned_over" : False, - "possible_suits" : ["hearts", "diamonds", "spades", "clubs"], - "possible_values" : map(str, range(2, 11)) + ["J", "Q", "K", "A"], + "value": None, + "suit": None, + "key": None, # String like "8H" or "KS" + "height": 2, + "height_to_width": 3.5 / 2.5, + "card_height_to_symbol_height": 7, + "card_width_to_corner_num_width": 10, + "card_height_to_corner_num_height": 10, + "color": LIGHT_GREY, + "turned_over": False, + "possible_suits": ["hearts", "diamonds", "spades", "clubs"], + "possible_values": map(str, range(2, 11)) + ["J", "Q", "K", "A"], } - def __init__(self, key = None, **kwargs): - VGroup.__init__(self, key = key, **kwargs) + def __init__(self, key=None, **kwargs): + VGroup.__init__(self, key=key, **kwargs) def generate_points(self): self.add(Rectangle( - height = self.height, - width = self.height/self.height_to_width, - stroke_color = WHITE, - stroke_width = 2, - fill_color = self.color, - fill_opacity = 1, + height=self.height, + width=self.height / self.height_to_width, + stroke_color=WHITE, + stroke_width=2, + fill_color=self.color, + fill_opacity=1, )) if self.turned_over: self.set_fill(DARK_GREY) @@ -653,10 +680,10 @@ class PlayingCard(VGroup): raise Exception("Invalid card value") face_card_to_value = { - "J" : 11, - "Q" : 12, - "K" : 13, - "A" : 14, + "J": 11, + "Q": 12, + "K": 13, + "A": 14, } try: self.numerical_value = int(value) @@ -678,7 +705,7 @@ class PlayingCard(VGroup): raise Exception("Invalud suit value") self.suit = suit symbol_height = float(self.height) / self.card_height_to_symbol_height - symbol = SuitSymbol(suit, height = symbol_height) + symbol = SuitSymbol(suit, height=symbol_height) return symbol def get_design(self, value, symbol): @@ -697,27 +724,27 @@ class PlayingCard(VGroup): def get_number_design(self, value, symbol): num = int(value) n_rows = { - 2 : 2, - 3 : 3, - 4 : 2, - 5 : 2, - 6 : 3, - 7 : 3, - 8 : 3, - 9 : 4, - 10 : 4, + 2: 2, + 3: 3, + 4: 2, + 5: 2, + 6: 3, + 7: 3, + 8: 3, + 9: 4, + 10: 4, }[num] n_cols = 1 if num in [2, 3] else 2 insertion_indices = { - 5 : [0], - 7 : [0], - 8 : [0, 1], - 9 : [1], - 10 : [0, 2], + 5: [0], + 7: [0], + 8: [0, 1], + 9: [1], + 10: [0, 2], }.get(num, []) - top = self.get_top() + symbol.get_height()*DOWN - bottom = self.get_bottom() + symbol.get_height()*UP + top = self.get_top() + symbol.get_height() * DOWN + bottom = self.get_bottom() + symbol.get_height() * UP column_points = [ interpolate(top, bottom, alpha) for alpha in np.linspace(0, 1, n_rows) @@ -728,13 +755,13 @@ class PlayingCard(VGroup): for point in column_points ]) if n_cols == 2: - space = 0.2*self.get_width() - column_copy = design.copy().shift(space*RIGHT) - design.shift(space*LEFT) + space = 0.2 * self.get_width() + column_copy = design.copy().shift(space * RIGHT) + design.shift(space * LEFT) design.add(*column_copy) design.add(*[ symbol.copy().move_to( - center_of_mass(column_points[i:i+2]) + center_of_mass(column_points[i:i + 2]) ) for i in insertion_indices ]) @@ -746,120 +773,93 @@ class PlayingCard(VGroup): def get_face_card_design(self, value, symbol): from for_3b1b_videos.pi_creature import PiCreature sub_rect = Rectangle( - stroke_color = BLACK, - fill_opacity = 0, - height = 0.9*self.get_height(), - width = 0.6*self.get_width(), + stroke_color=BLACK, + fill_opacity=0, + height=0.9 * self.get_height(), + width=0.6 * self.get_width(), ) sub_rect.move_to(self) # pi_color = average_color(symbol.get_color(), GREY) pi_color = symbol.get_color() pi_mode = { - "J" : "plain", - "Q" : "thinking", - "K" : "hooray" + "J": "plain", + "Q": "thinking", + "K": "hooray" }[value] pi_creature = PiCreature( - mode = pi_mode, - color = pi_color, + mode=pi_mode, + color=pi_color, ) - pi_creature.scale_to_fit_width(0.8*sub_rect.get_width()) + pi_creature.scale_to_fit_width(0.8 * sub_rect.get_width()) if value in ["Q", "K"]: prefix = "king" if value == "K" else "queen" - crown = SVGMobject(file_name = prefix + "_crown") - crown.set_stroke(width = 0) + crown = SVGMobject(file_name=prefix + "_crown") + crown.set_stroke(width=0) crown.set_fill(YELLOW, 1) - crown.stretch_to_fit_width(0.5*sub_rect.get_width()) - crown.stretch_to_fit_height(0.17*sub_rect.get_height()) + crown.stretch_to_fit_width(0.5 * sub_rect.get_width()) + crown.stretch_to_fit_height(0.17 * sub_rect.get_height()) crown.move_to(pi_creature.eyes.get_center(), DOWN) pi_creature.add_to_back(crown) to_top_buff = 0 else: - to_top_buff = SMALL_BUFF*sub_rect.get_height() + to_top_buff = SMALL_BUFF * sub_rect.get_height() pi_creature.next_to(sub_rect.get_top(), DOWN, to_top_buff) # pi_creature.shift(0.05*sub_rect.get_width()*RIGHT) pi_copy = pi_creature.copy() - pi_copy.rotate(np.pi, about_point = sub_rect.get_center()) + pi_copy.rotate(np.pi, about_point=sub_rect.get_center()) return VGroup(sub_rect, pi_creature, pi_copy) def get_corner_numbers(self, value, symbol): value_mob = TextMobject(value) - width = self.get_width()/self.card_width_to_corner_num_width - height = self.get_height()/self.card_height_to_corner_num_height + width = self.get_width() / self.card_width_to_corner_num_width + height = self.get_height() / self.card_height_to_corner_num_height value_mob.scale_to_fit_width(width) value_mob.stretch_to_fit_height(height) value_mob.next_to( - self.get_corner(UP+LEFT), DOWN+RIGHT, - buff = MED_LARGE_BUFF*width + self.get_corner(UP + LEFT), DOWN + RIGHT, + buff=MED_LARGE_BUFF * width ) value_mob.set_color(symbol.get_color()) corner_symbol = symbol.copy() corner_symbol.scale_to_fit_width(width) corner_symbol.next_to( - value_mob, DOWN, - buff = MED_SMALL_BUFF*width + value_mob, DOWN, + buff=MED_SMALL_BUFF * width ) corner_group = VGroup(value_mob, corner_symbol) opposite_corner_group = corner_group.copy() opposite_corner_group.rotate( - np.pi, about_point = self.get_center() + np.pi, about_point=self.get_center() ) return VGroup(corner_group, opposite_corner_group) + class SuitSymbol(SVGMobject): CONFIG = { - "height" : 0.5, - "fill_opacity" : 1, - "stroke_width" : 0, - "red" : "#D02028", - "black" : BLACK, + "height": 0.5, + "fill_opacity": 1, + "stroke_width": 0, + "red": "#D02028", + "black": BLACK, } + def __init__(self, suit_name, **kwargs): digest_config(self, kwargs) suits_to_colors = { - "hearts" : self.red, - "diamonds" : self.red, - "spades" : self.black, - "clubs" : self.black, + "hearts": self.red, + "diamonds": self.red, + "spades": self.black, + "clubs": self.black, } if suit_name not in suits_to_colors: raise Exception("Invalid suit name") - SVGMobject.__init__(self, file_name = suit_name, **kwargs) + SVGMobject.__init__(self, file_name=suit_name, **kwargs) color = suits_to_colors[suit_name] - self.set_stroke(width = 0) + self.set_stroke(width=0) self.set_fill(color, 1) self.scale_to_fit_height(self.height) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/mobject/svg/svg_mobject.py b/mobject/svg/svg_mobject.py index 23907ce6..ebfbde3d 100644 --- a/mobject/svg/svg_mobject.py +++ b/mobject/svg/svg_mobject.py @@ -14,28 +14,31 @@ from utils.config_ops import digest_locals from mobject.types.vectorized_mobject import VGroup from mobject.types.vectorized_mobject import VMobject + def string_to_numbers(num_string): - num_string = num_string.replace("-",",-") - num_string = num_string.replace("e,-","e-") + num_string = num_string.replace("-", ",-") + num_string = num_string.replace("e,-", "e-") return [ float(s) for s in re.split("[ ,]", num_string) if s != "" ] + class SVGMobject(VMobject): CONFIG = { - "should_center" : True, - "height" : 2, - "width" : None, - #Must be filled in in a subclass, or when called - "file_name" : None, - "unpack_groups" : True, # if False, creates a hierarchy of VGroups - "stroke_width" : 0, - "fill_opacity" : 1, + "should_center": True, + "height": 2, + "width": None, + # Must be filled in in a subclass, or when called + "file_name": None, + "unpack_groups": True, # if False, creates a hierarchy of VGroups + "stroke_width": 0, + "fill_opacity": 1, # "fill_color" : LIGHT_GREY, - "propagate_style_to_family" : True, + "propagate_style_to_family": True, } + def __init__(self, **kwargs): digest_config(self, kwargs, locals()) self.ensure_valid_file() @@ -54,15 +57,18 @@ class SVGMobject(VMobject): if os.path.exists(path): self.file_path = path return - raise IOError("No file matching %s in image directory"%self.file_name) + raise IOError("No file matching %s in image directory" % + self.file_name) def generate_points(self): doc = minidom.parse(self.file_path) self.ref_to_element = {} for svg in doc.getElementsByTagName("svg"): mobjects = self.get_mobjects_from(svg) - if self.unpack_groups: self.add(*mobjects) - else: self.add(*mobjects[0].submobjects) + if self.unpack_groups: + self.add(*mobjects) + else: + self.add(*mobjects[0].submobjects) doc.unlink() def get_mobjects_from(self, element): @@ -72,7 +78,7 @@ class SVGMobject(VMobject): if element.tagName == 'defs': self.update_ref_to_element(element) elif element.tagName == 'style': - pass #TODO, handle style + pass # TODO, handle style elif element.tagName in ['g', 'svg']: result += it.chain(*[ self.get_mobjects_from(child) @@ -93,9 +99,9 @@ class SVGMobject(VMobject): elif element.tagName in ['polygon', 'polyline']: result.append(self.polygon_to_mobject(element)) else: - pass ##TODO + pass # TODO # warnings.warn("Unknown element type: " + element.tagName) - result = filter(lambda m : m is not None, result) + result = filter(lambda m: m is not None, result) self.handle_transforms(element, VMobject(*result)) if len(result) > 1 and not self.unpack_groups: result = [VGroup(*result)] @@ -111,17 +117,17 @@ class SVGMobject(VMobject): return VMobjectFromSVGPathstring(path_string) def use_to_mobjects(self, use_element): - #Remove initial "#" character + # Remove initial "#" character ref = use_element.getAttribute("xlink:href")[1:] if ref not in self.ref_to_element: - warnings.warn("%s not recognized"%ref) + warnings.warn("%s not recognized" % ref) return VMobject() return self.get_mobjects_from( self.ref_to_element[ref] ) def polygon_to_mobject(self, polygon_element): - #TODO, This seems hacky... + # TODO, This seems hacky... path_string = polygon_element.getAttribute("points") for digit in string.digits: path_string = path_string.replace(" " + digit, " L" + digit) @@ -137,7 +143,7 @@ class SVGMobject(VMobject): else 0.0 for key in "cx", "cy", "r" ] - return Circle(radius = r).shift(x*RIGHT+y*DOWN) + return Circle(radius=r).shift(x * RIGHT + y * DOWN) def ellipse_to_mobject(self, circle_element): x, y, rx, ry = [ @@ -146,74 +152,77 @@ class SVGMobject(VMobject): else 0.0 for key in "cx", "cy", "rx", "ry" ] - return Circle().scale(rx*RIGHT + ry*UP).shift(x*RIGHT+y*DOWN) + return Circle().scale(rx * RIGHT + ry * UP).shift(x * RIGHT + y * DOWN) def rect_to_mobject(self, rect_element): if rect_element.hasAttribute("fill"): if Color(str(rect_element.getAttribute("fill"))) == Color(WHITE): return mob = Rectangle( - width = float(rect_element.getAttribute("width")), - height = float(rect_element.getAttribute("height")), - stroke_width = 0, - fill_color = WHITE, - fill_opacity = 1.0 + width=float(rect_element.getAttribute("width")), + height=float(rect_element.getAttribute("height")), + stroke_width=0, + fill_color=WHITE, + fill_opacity=1.0 ) - mob.shift(mob.get_center()-mob.get_corner(UP+LEFT)) + mob.shift(mob.get_center() - mob.get_corner(UP + LEFT)) return mob def handle_transforms(self, element, mobject): x, y = 0, 0 try: x = float(element.getAttribute('x')) - #Flip y + # Flip y y = -float(element.getAttribute('y')) - mobject.shift(x*RIGHT+y*UP) + mobject.shift(x * RIGHT + y * UP) except: pass transform = element.getAttribute('transform') - try: # transform matrix + try: # transform matrix prefix = "matrix(" suffix = ")" - if not transform.startswith(prefix) or not transform.endswith(suffix): raise Exception() + if not transform.startswith(prefix) or not transform.endswith(suffix): + raise Exception() transform = transform[len(prefix):-len(suffix)] transform = string_to_numbers(transform) - transform = np.array(transform).reshape([3,2]) + transform = np.array(transform).reshape([3, 2]) x = transform[2][0] y = -transform[2][1] matrix = np.identity(self.dim) - matrix[:2,:2] = transform[:2,:] + matrix[:2, :2] = transform[:2, :] matrix[1] *= -1 - matrix[:,1] *= -1 + matrix[:, 1] *= -1 for mob in mobject.family_members_with_points(): mob.points = np.dot(mob.points, matrix) - mobject.shift(x*RIGHT+y*UP) + mobject.shift(x * RIGHT + y * UP) except: pass - try: # transform scale + try: # transform scale prefix = "scale(" suffix = ")" - if not transform.startswith(prefix) or not transform.endswith(suffix): raise Exception() + if not transform.startswith(prefix) or not transform.endswith(suffix): + raise Exception() transform = transform[len(prefix):-len(suffix)] scale_x, scale_y = string_to_numbers(transform) mobject.scale(np.array([scale_x, scale_y, 1])) except: pass - try: # transform translate + try: # transform translate prefix = "translate(" suffix = ")" - if not transform.startswith(prefix) or not transform.endswith(suffix): raise Exception() + if not transform.startswith(prefix) or not transform.endswith(suffix): + raise Exception() transform = transform[len(prefix):-len(suffix)] x, y = string_to_numbers(transform) - mobject.shift(x*RIGHT + y*DOWN) + mobject.shift(x * RIGHT + y * DOWN) except: pass - #TODO, ... + # TODO, ... def update_ref_to_element(self, defs): new_refs = dict([ @@ -231,6 +240,7 @@ class SVGMobject(VMobject): if self.width is not None: self.scale_to_fit_width(self.width) + class VMobjectFromSVGPathstring(VMobject): def __init__(self, path_string, **kwargs): digest_locals(self) @@ -238,42 +248,42 @@ class VMobjectFromSVGPathstring(VMobject): def get_path_commands(self): result = [ - "M", #moveto - "L", #lineto - "H", #horizontal lineto - "V", #vertical lineto - "C", #curveto - "S", #smooth curveto - "Q", #quadratic Bezier curve - "T", #smooth quadratic Bezier curveto - "A", #elliptical Arc - "Z", #closepath + "M", # moveto + "L", # lineto + "H", # horizontal lineto + "V", # vertical lineto + "C", # curveto + "S", # smooth curveto + "Q", # quadratic Bezier curve + "T", # smooth quadratic Bezier curveto + "A", # elliptical Arc + "Z", # closepath ] - result += map(lambda s : s.lower(), result) + result += map(lambda s: s.lower(), result) return result def generate_points(self): - pattern = "[%s]"%("".join(self.get_path_commands())) + pattern = "[%s]" % ("".join(self.get_path_commands())) pairs = zip( re.findall(pattern, self.path_string), re.split(pattern, self.path_string)[1:] ) - #Which mobject should new points be added to + # Which mobject should new points be added to self.growing_path = self for command, coord_string in pairs: self.handle_command(command, coord_string) - #people treat y-coordinate differently - self.rotate(np.pi, RIGHT, about_point = ORIGIN) + # people treat y-coordinate differently + self.rotate(np.pi, RIGHT, about_point=ORIGIN) def handle_command(self, command, coord_string): isLower = command.islower() command = command.upper() - #new_points are the points that will be added to the curr_points - #list. This variable may get modified in the conditionals below. + # new_points are the points that will be added to the curr_points + # list. This variable may get modified in the conditionals below. points = self.growing_path.points new_points = self.string_to_points(coord_string) - if command == "M": #moveto + if command == "M": # moveto if isLower and len(points) > 0: new_points[0] += points[-1] if len(points) > 0: @@ -281,7 +291,8 @@ class VMobjectFromSVGPathstring(VMobject): else: self.growing_path.start_at(new_points[0]) - if len(new_points) <= 1: return + if len(new_points) <= 1: + return points = self.growing_path.points new_points = new_points[1:] @@ -290,47 +301,47 @@ class VMobjectFromSVGPathstring(VMobject): if isLower and len(points) > 0: new_points += points[-1] - if command in ["L", "H", "V"]: #lineto + if command in ["L", "H", "V"]: # lineto if command == "H": - new_points[0,1] = points[-1,1] + new_points[0, 1] = points[-1, 1] elif command == "V": if isLower: - new_points[0,0] -= points[-1,0] - new_points[0,0] += points[-1,1] - new_points[0,1] = new_points[0,0] - new_points[0,0] = points[-1,0] - new_points = new_points.repeat(3, axis = 0) - elif command == "C": #curveto - pass #Yay! No action required - elif command in ["S", "T"]: #smooth curveto - handle1 = points[-1]+(points[-1]-points[-2]) - new_points = np.append([handle1], new_points, axis = 0) - if command in ["Q", "T"]: #quadratic Bezier curve - #TODO, this is a suboptimal approximation - new_points = np.append([new_points[0]], new_points, axis = 0) - elif command == "A": #elliptical Arc + new_points[0, 0] -= points[-1, 0] + new_points[0, 0] += points[-1, 1] + new_points[0, 1] = new_points[0, 0] + new_points[0, 0] = points[-1, 0] + new_points = new_points.repeat(3, axis=0) + elif command == "C": # curveto + pass # Yay! No action required + elif command in ["S", "T"]: # smooth curveto + handle1 = points[-1] + (points[-1] - points[-2]) + new_points = np.append([handle1], new_points, axis=0) + if command in ["Q", "T"]: # quadratic Bezier curve + # TODO, this is a suboptimal approximation + new_points = np.append([new_points[0]], new_points, axis=0) + elif command == "A": # elliptical Arc raise Exception("Not implemented") - elif command == "Z": #closepath + elif command == "Z": # closepath if not is_closed(points): - #Both handles and new anchor are the start + # Both handles and new anchor are the start new_points = points[[0, 0, 0]] # self.mark_paths_closed = True - #Handle situations where there's multiple relative control points + # Handle situations where there's multiple relative control points if isLower and len(new_points) > 3: for i in range(3, len(new_points), 3): - new_points[i:i+3] -= points[-1] - new_points[i:i+3] += new_points[i-1] + new_points[i:i + 3] -= points[-1] + new_points[i:i + 3] += new_points[i - 1] self.growing_path.add_control_points(new_points) def string_to_points(self, coord_string): numbers = string_to_numbers(coord_string) - if len(numbers)%2 == 1: + if len(numbers) % 2 == 1: numbers.append(0) - num_points = len(numbers)/2 + num_points = len(numbers) / 2 result = np.zeros((num_points, self.dim)) - result[:,:2] = np.array(numbers).reshape((num_points, 2)) + result[:, :2] = np.array(numbers).reshape((num_points, 2)) return result def get_original_path_string(self): diff --git a/mobject/svg/tex_mobject.py b/mobject/svg/tex_mobject.py index bdc48d43..b28e7c9b 100644 --- a/mobject/svg/tex_mobject.py +++ b/mobject/svg/tex_mobject.py @@ -8,49 +8,50 @@ from mobject.types.vectorized_mobject import VGroup from mobject.types.vectorized_mobject import VMobject from mobject.types.vectorized_mobject import VectorizedPoint -import collections import operator as op -import sys TEX_MOB_SCALE_FACTOR = 0.05 + class TexSymbol(VMobjectFromSVGPathstring): def pointwise_become_partial(self, mobject, a, b): - #TODO, this assumes a = 0 + # TODO, this assumes a = 0 if b < 0.5: - b = 2*b + b = 2 * b added_width = 1 opacity = 0 else: - added_width = 2 - 2*b - opacity = 2*b - 1 + added_width = 2 - 2 * b + opacity = 2 * b - 1 b = 1 VMobjectFromSVGPathstring.pointwise_become_partial( self, mobject, 0, b ) - self.set_stroke(width = added_width + mobject.get_stroke_width()) - self.set_fill(opacity = opacity) + self.set_stroke(width=added_width + mobject.get_stroke_width()) + self.set_fill(opacity=opacity) + class TexMobject(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" : "", + "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 not "fill_color" in kwargs.keys(): + + if "color" in kwargs.keys() and "fill_color" not in kwargs.keys(): self.fill_color = kwargs["color"] - ##TODO, Eventually remove this + # TODO, Eventually remove this if len(args) == 1 and isinstance(args[0], list): self.args = args[0] ## @@ -60,14 +61,14 @@ class TexMobject(SVGMobject): self.tex_string, self.template_tex_file ) - SVGMobject.__init__(self, file_name = file_name, **kwargs) + 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() def path_string_to_mobject(self, path_string): - #Overwrite superclass default to use - #specialized path_string mobject + # Overwrite superclass default to use + # specialized path_string mobject return TexSymbol(path_string) def generate_points(self): @@ -86,7 +87,7 @@ class TexMobject(SVGMobject): def modify_special_strings(self, tex): tex = self.remove_stray_braces(tex) if tex in ["\\over", "\\overline"]: - #fraction line needs something to be over + # fraction line needs something to be over tex += "\\," if tex == "\\sqrt": tex += "{\\quad}" @@ -132,7 +133,7 @@ class TexMobject(SVGMobject): 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 + sub_tex_mob.tex_string = expr # Want it unmodified num_submobs = len(sub_tex_mob.submobjects) new_index = curr_index + num_submobs if num_submobs == 0: @@ -150,7 +151,7 @@ class TexMobject(SVGMobject): self.submobjects = new_submobjects return self - def get_parts_by_tex(self, tex, substring = True, case_sensitive = True): + def get_parts_by_tex(self, tex, substring=True, case_sensitive=True): def test(tex1, tex2): if not case_sensitive: tex1 = tex1.lower() @@ -161,13 +162,13 @@ class TexMobject(SVGMobject): return tex1 == tex2 tex_submobjects = filter( - lambda m : isinstance(m, TexMobject), + 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()), + lambda m: test(tex, m.get_tex_string()), tex_submobjects )) @@ -204,7 +205,7 @@ class TexMobject(SVGMobject): return self.index_of_part(part) def organize_submobjects_left_to_right(self): - self.sort_submobjects(lambda p : p[0]) + self.sort_submobjects(lambda p: p[0]) return self def sort_submobjects_alphabetically(self): @@ -215,30 +216,33 @@ class TexMobject(SVGMobject): self.submobjects.sort(alphabetical_cmp) return self - def add_background_rectangle(self, color = BLACK, opacity = 0.75, **kwargs): + def add_background_rectangle(self, color=BLACK, opacity=0.75, **kwargs): self.background_rectangle = BackgroundRectangle( - self, color = color, - fill_opacity = opacity, + self, color=color, + fill_opacity=opacity, **kwargs ) letters = VMobject(*self.submobjects) self.submobjects = [self.background_rectangle, letters] return self + class TextMobject(TexMobject): CONFIG = { - "template_tex_file" : TEMPLATE_TEXT_FILE, - "alignment" : "\\centering", + "template_tex_file": TEMPLATE_TEXT_FILE, + "alignment": "\\centering", } + class BulletedList(TextMobject): CONFIG = { - "buff" : MED_LARGE_BUFF, - "dot_scale_factor" : 2, - #Have to include because of handle_multiple_args implementation - "template_tex_file" : TEMPLATE_TEXT_FILE, - "alignment" : "", + "buff": MED_LARGE_BUFF, + "dot_scale_factor": 2, + # Have to include because of handle_multiple_args implementation + "template_tex_file": TEMPLATE_TEXT_FILE, + "alignment": "", } + def __init__(self, *items, **kwargs): line_separated_items = [s + "\\\\" for s in items] TextMobject.__init__(self, *line_separated_items, **kwargs) @@ -247,12 +251,12 @@ class BulletedList(TextMobject): dot.next_to(part[0], LEFT, SMALL_BUFF) part.add_to_back(dot) self.arrange_submobjects( - DOWN, - aligned_edge = LEFT, - buff = self.buff + DOWN, + aligned_edge=LEFT, + buff=self.buff ) - def fade_all_but(self, index_or_string, opacity = 0.5): + def fade_all_but(self, index_or_string, opacity=0.5): arg = index_or_string if isinstance(arg, str): part = self.get_part_by_tex(arg) @@ -262,15 +266,17 @@ class BulletedList(TextMobject): raise Exception("Expected int or string, got {0}".format(arg)) for other_part in self.submobjects: if other_part is part: - other_part.set_fill(opacity = 1) + other_part.set_fill(opacity=1) else: - other_part.set_fill(opacity = opacity) + other_part.set_fill(opacity=opacity) ########## + def tex_hash(expression, template_tex_file): return str(hash(expression + template_tex_file)) + def tex_to_svg_file(expression, template_tex_file): image_dir = os.path.join( TEX_IMAGE_DIR, @@ -282,13 +288,14 @@ def tex_to_svg_file(expression, template_tex_file): dvi_file = tex_to_dvi(tex_file) return dvi_to_svg(dvi_file) + def generate_tex_file(expression, template_tex_file): result = os.path.join( TEX_DIR, tex_hash(expression, template_tex_file) ) + ".tex" if not os.path.exists(result): - print("Writing \"%s\" to %s"%( + print("Writing \"%s\" to %s" % ( "".join(expression), result )) with open(template_tex_file, "r") as infile: @@ -298,11 +305,13 @@ def generate_tex_file(expression, template_tex_file): outfile.write(body) return result + def get_null(): if os.name == "nt": return "NUL" return "/dev/null" + def tex_to_dvi(tex_file): result = tex_file.replace(".tex", ".dvi") if not os.path.exists(result): @@ -327,7 +336,8 @@ def tex_to_dvi(tex_file): "See log output above or the log file: %s" % log_file) return result -def dvi_to_svg(dvi_file, regen_if_exists = False): + +def dvi_to_svg(dvi_file, regen_if_exists=False): """ Converts a dvi, which potentially has multiple slides, into a directory full of enumerated pngs corresponding with these slides. diff --git a/mobject/three_dimensions.py b/mobject/three_dimensions.py index 29b13716..0a183d30 100644 --- a/mobject/three_dimensions.py +++ b/mobject/three_dimensions.py @@ -9,67 +9,51 @@ from utils.space_ops import z_to_vector ############## + def should_shade_in_3d(mobject): return hasattr(mobject, "shade_in_3d") and mobject.shade_in_3d + def shade_in_3d(mobject): for submob in mobject.submobject_family(): submob.shade_in_3d = True + def turn_off_3d_shading(mobject): for submob in mobject.submobject_family(): submob.shade_in_3d = False + class ThreeDMobject(VMobject): def __init__(self, *args, **kwargs): VMobject.__init__(self, *args, **kwargs) shade_in_3d(self) + class Cube(ThreeDMobject): CONFIG = { - "fill_opacity" : 0.75, - "fill_color" : BLUE, - "stroke_width" : 0, - "propagate_style_to_family" : True, - "side_length" : 2, + "fill_opacity": 0.75, + "fill_color": BLUE, + "stroke_width": 0, + "propagate_style_to_family": True, + "side_length": 2, } + def generate_points(self): for vect in IN, OUT, LEFT, RIGHT, UP, DOWN: - face = Square(side_length = self.side_length) - face.shift(self.side_length*OUT/2.0) - face.apply_function(lambda p : np.dot(p, z_to_vector(vect).T)) + face = Square(side_length=self.side_length) + face.shift(self.side_length * OUT / 2.0) + face.apply_function(lambda p: np.dot(p, z_to_vector(vect).T)) self.add(face) + class Prism(Cube): CONFIG = { - "dimensions" : [3, 2, 1] + "dimensions": [3, 2, 1] } + def generate_points(self): Cube.generate_points(self) for dim, value in enumerate(self.dimensions): - self.rescale_to_fit(value, dim, stretch = True) - - - - - - - - - - - - - - - - - - - - - - - - + self.rescale_to_fit(value, dim, stretch=True) diff --git a/mobject/types/image_mobject.py b/mobject/types/image_mobject.py index 17ca1121..55c94e18 100644 --- a/mobject/types/image_mobject.py +++ b/mobject/types/image_mobject.py @@ -1,33 +1,31 @@ from __future__ import absolute_import -import itertools as it import numpy as np -import os from PIL import Image -from random import random from constants import * from mobject.mobject import Mobject from utils.bezier import interpolate from utils.color import color_to_int_rgb -from utils.color import interpolate_color from utils.config_ops import digest_config from utils.images import get_full_raster_image_path + class ImageMobject(Mobject): """ Automatically filters out black pixels """ CONFIG = { - "filter_color" : "black", - "invert" : False, + "filter_color": "black", + "invert": False, # "use_cache" : True, "height": 2.0, - "image_mode" : "RGBA", - "pixel_array_dtype" : "uint8", + "image_mode": "RGBA", + "pixel_array_dtype": "uint8", } + def __init__(self, filename_or_array, **kwargs): digest_config(self, kwargs) if isinstance(filename_or_array, str): @@ -38,7 +36,7 @@ class ImageMobject(Mobject): self.pixel_array = np.array(filename_or_array) self.change_to_rgba_array() if self.invert: - self.pixel_array[:,:,:3] = 255-self.pixel_array[:,:,:3] + self.pixel_array[:, :, :3] = 255 - self.pixel_array[:, :, :3] Mobject.__init__(self, **kwargs) def change_to_rgba_array(self): @@ -46,43 +44,43 @@ class ImageMobject(Mobject): if len(pa.shape) == 2: pa = pa.reshape(list(pa.shape) + [1]) if pa.shape[2] == 1: - pa = pa.repeat(3, axis = 2) + pa = pa.repeat(3, axis=2) if pa.shape[2] == 3: - alphas = 255*np.ones( - list(pa.shape[:2])+[1], - dtype = self.pixel_array_dtype + alphas = 255 * np.ones( + list(pa.shape[:2]) + [1], + dtype=self.pixel_array_dtype ) - pa = np.append(pa, alphas, axis = 2) + pa = np.append(pa, alphas, axis=2) self.pixel_array = pa - def set_color(self, color, alpha = None, family = True): + def set_color(self, color, alpha=None, family=True): rgb = color_to_int_rgb(color) - self.pixel_array[:,:,:3] = rgb + self.pixel_array[:, :, :3] = rgb if alpha is not None: - self.pixel_array[:,:,3] = int(255*alpha) + self.pixel_array[:, :, 3] = int(255 * alpha) for submob in self.submobjects: submob.set_color(color, alpha, family) self.color = color return self def init_points(self): - #Corresponding corners of image are fixed to these - #Three points + # Corresponding corners of image are fixed to these + # Three points self.points = np.array([ - UP+LEFT, - UP+RIGHT, - DOWN+LEFT, + UP + LEFT, + UP + RIGHT, + DOWN + LEFT, ]) self.center() self.scale_to_fit_height(self.height) h, w = self.pixel_array.shape[:2] - self.stretch_to_fit_width(self.height*w/h) + self.stretch_to_fit_width(self.height * w / h) def set_opacity(self, alpha): - self.pixel_array[:,:,3] = int(255*alpha) + self.pixel_array[:, :, 3] = int(255 * alpha) return self - def fade(self, darkness = 0.5): + def fade(self, darkness=0.5): self.set_opacity(1 - darkness) return self @@ -94,5 +92,3 @@ class ImageMobject(Mobject): def copy(self): return self.deepcopy() - - diff --git a/mobject/types/point_cloud_mobject.py b/mobject/types/point_cloud_mobject.py index d1cb528e..b2b9863a 100644 --- a/mobject/types/point_cloud_mobject.py +++ b/mobject/types/point_cloud_mobject.py @@ -1,16 +1,16 @@ from __future__ import absolute_import -from mobject.mobject import Mobject from constants import * + +from mobject.mobject import Mobject from utils.bezier import interpolate from utils.color import color_gradient -from utils.color import color_to_rgb from utils.color import color_to_rgba -from utils.color import interpolate_color from utils.color import rgba_to_color from utils.config_ops import digest_config from utils.iterables import stretch_array_to_length + class PMobject(Mobject): def init_points(self): self.rgbas = np.zeros((0, 4)) @@ -20,38 +20,38 @@ class PMobject(Mobject): def get_array_attrs(self): return Mobject.get_array_attrs(self) + ["rgbas"] - def add_points(self, points, rgbas = None, color = None, alpha = 1): + def add_points(self, points, rgbas=None, color=None, alpha=1): """ points must be a Nx3 numpy array, as must rgbas if it is not None """ if not isinstance(points, np.ndarray): points = np.array(points) num_new_points = len(points) - self.points = np.append(self.points, points, axis = 0) + self.points = np.append(self.points, points, axis=0) if rgbas is None: color = Color(color) if color else self.color rgbas = np.repeat( [color_to_rgba(color, alpha)], num_new_points, - axis = 0 + axis=0 ) elif len(rgbas) != len(points): raise Exception("points and rgbas must have same shape") - self.rgbas = np.append(self.rgbas, rgbas, axis = 0) + self.rgbas = np.append(self.rgbas, rgbas, axis=0) return self - def set_color(self, color = YELLOW_C, family = True): + def set_color(self, color=YELLOW_C, family=True): rgba = color_to_rgba(color) mobs = self.family_members_with_points() if family else [self] for mob in mobs: - mob.rgbas[:,:] = rgba + mob.rgbas[:, :] = rgba self.color = color return self # def set_color_by_gradient(self, start_color, end_color): def set_color_by_gradient(self, *colors): self.rgbas = np.array(map( - color_to_rgba, + color_to_rgba, color_gradient(colors, len(self.points)) )) return self @@ -61,21 +61,21 @@ class PMobject(Mobject): num_points = mob.get_num_points() mob.rgbas = np.array([ interpolate(start_rgba, end_rgba, alpha) - for alpha in np.arange(num_points)/float(num_points) + for alpha in np.arange(num_points) / float(num_points) ]) return self - def set_colors_by_radial_gradient(self, center = None, radius = 1, inner_color = WHITE, outer_color = BLACK): + def set_colors_by_radial_gradient(self, center=None, radius=1, inner_color=WHITE, outer_color=BLACK): start_rgba, end_rgba = map(color_to_rgba, [start_color, end_color]) - if center == None: + if center is None: center = self.get_center() for mob in self.family_members_with_points(): num_points = mob.get_num_points() - t = min(1,np.abs(mob.get_center() - center)/radius) + t = min(1, np.abs(mob.get_center() - center) / radius) mob.rgbas = np.array( - [ interpolate(start_rgba, end_rgba, t) ] * num_points - ) + [interpolate(start_rgba, end_rgba, t)] * num_points + ) return self def match_colors(self, mobject): @@ -90,20 +90,20 @@ class PMobject(Mobject): mob.rgbas = mob.rgbas[to_eliminate] return self - def thin_out(self, factor = 5): + def thin_out(self, factor=5): """ Removes all but every nth point for n = factor """ for mob in self.family_members_with_points(): num_points = self.get_num_points() mob.apply_over_attr_arrays( - lambda arr : arr[ + lambda arr: arr[ np.arange(0, num_points, factor) ] ) return self - def sort_points(self, function = lambda p : p[0]): + def sort_points(self, function=lambda p: p[0]): """ function is any map from R^3 to R """ @@ -111,7 +111,7 @@ class PMobject(Mobject): indices = np.argsort( np.apply_along_axis(function, 1, mob.points) ) - mob.apply_over_attr_arrays(lambda arr : arr[indices]) + mob.apply_over_attr_arrays(lambda arr: arr[indices]) return self def fade_to(self, color, alpha): @@ -135,19 +135,19 @@ class PMobject(Mobject): return rgba_to_color(self.rgbas[0, :]) def point_from_proportion(self, alpha): - index = alpha*(self.get_num_points()-1) + index = alpha * (self.get_num_points() - 1) return self.points[index] # Alignment def align_points_with_larger(self, larger_mobject): assert(isinstance(larger_mobject, PMobject)) self.apply_over_attr_arrays( - lambda a : stretch_array_to_length( + lambda a: stretch_array_to_length( a, larger_mobject.get_num_points() ) ) - def get_point_mobject(self, center = None): + def get_point_mobject(self, center=None): if center is None: center = self.get_center() return Point(center) @@ -165,65 +165,70 @@ class PMobject(Mobject): for attr in self.get_array_attrs(): full_array = getattr(mobject, attr) partial_array = full_array[lower_index:upper_index] - setattr(self, attr, partial_array) + setattr(self, attr, partial_array) -#TODO, Make the two implementations bellow non-redundant +# TODO, Make the two implementations bellow non-redundant class Mobject1D(PMobject): CONFIG = { - "density" : DEFAULT_POINT_DENSITY_1D, + "density": DEFAULT_POINT_DENSITY_1D, } + def __init__(self, **kwargs): digest_config(self, kwargs) - self.epsilon = 1.0 / self.density + self.epsilon = 1.0 / self.density Mobject.__init__(self, **kwargs) - def add_line(self, start, end, color = None): + def add_line(self, start, end, color=None): start, end = map(np.array, [start, end]) length = np.linalg.norm(end - start) if length == 0: points = [start] else: - epsilon = self.epsilon/length + epsilon = self.epsilon / length points = [ interpolate(start, end, t) for t in np.arange(0, 1, epsilon) ] - self.add_points(points, color = color) + self.add_points(points, color=color) + class Mobject2D(PMobject): CONFIG = { - "density" : DEFAULT_POINT_DENSITY_2D, + "density": DEFAULT_POINT_DENSITY_2D, } + def __init__(self, **kwargs): digest_config(self, kwargs) - self.epsilon = 1.0 / self.density + self.epsilon = 1.0 / self.density Mobject.__init__(self, **kwargs) class PointCloudDot(Mobject1D): CONFIG = { - "radius" : 0.075, - "stroke_width" : 2, - "density" : DEFAULT_POINT_DENSITY_1D, - "color" : YELLOW, + "radius": 0.075, + "stroke_width": 2, + "density": DEFAULT_POINT_DENSITY_1D, + "color": YELLOW, } - def __init__(self, center = ORIGIN, **kwargs): + + def __init__(self, center=ORIGIN, **kwargs): Mobject1D.__init__(self, **kwargs) self.shift(center) def generate_points(self): self.add_points([ - r*(np.cos(theta)*RIGHT + np.sin(theta)*UP) + r * (np.cos(theta) * RIGHT + np.sin(theta) * UP) for r in np.arange(0, self.radius, self.epsilon) - for theta in np.arange(0, 2*np.pi, self.epsilon/r) + for theta in np.arange(0, 2 * np.pi, self.epsilon / r) ]) + class Point(PMobject): CONFIG = { - "color" : BLACK, + "color": BLACK, } - def __init__(self, location = ORIGIN, **kwargs): + + def __init__(self, location=ORIGIN, **kwargs): PMobject.__init__(self, **kwargs) self.add_points([location]) - diff --git a/mobject/types/vectorized_mobject.py b/mobject/types/vectorized_mobject.py index 280f732a..92ddf402 100644 --- a/mobject/types/vectorized_mobject.py +++ b/mobject/types/vectorized_mobject.py @@ -1,7 +1,5 @@ from __future__ import absolute_import -import re - from colour import Color from mobject.mobject import Mobject @@ -12,38 +10,36 @@ from utils.bezier import interpolate from utils.bezier import is_closed from utils.bezier import partial_bezier_points from utils.color import color_to_rgb -from utils.color import interpolate_color from utils.iterables import make_even - class VMobject(Mobject): CONFIG = { - "fill_color" : None, - "fill_opacity" : 0.0, - "stroke_color" : None, - #Indicates that it will not be displayed, but - #that it should count in parent mobject's path - "is_subpath" : False, - "close_new_points" : False, - "mark_paths_closed" : False, - "propagate_style_to_family" : False, - "pre_function_handle_to_anchor_scale_factor" : 0.01, - "make_smooth_after_applying_functions" : False, - "background_image_file" : None, + "fill_color": None, + "fill_opacity": 0.0, + "stroke_color": None, + # Indicates that it will not be displayed, but + # that it should count in parent mobject's path + "is_subpath": False, + "close_new_points": False, + "mark_paths_closed": False, + "propagate_style_to_family": False, + "pre_function_handle_to_anchor_scale_factor": 0.01, + "make_smooth_after_applying_functions": False, + "background_image_file": None, } def get_group_class(self): return VGroup - ## Colors + # Colors def init_colors(self): self.set_style_data( - stroke_color = self.stroke_color or self.color, - stroke_width = self.stroke_width, - fill_color = self.fill_color or self.color, - fill_opacity = self.fill_opacity, - family = self.propagate_style_to_family + stroke_color=self.stroke_color or self.color, + stroke_width=self.stroke_width, + fill_color=self.fill_color or self.color, + fill_opacity=self.fill_opacity, + family=self.propagate_style_to_family ) return self @@ -51,12 +47,12 @@ class VMobject(Mobject): for mob in self.submobject_family(): setattr(mob, attr, value) - def set_style_data(self, - stroke_color = None, - stroke_width = None, - fill_color = None, - fill_opacity = None, - family = True + def set_style_data(self, + stroke_color=None, + stroke_width=None, + fill_color=None, + fill_opacity=None, + family=True ): if stroke_color is not None: self.stroke_rgb = color_to_rgb(stroke_color) @@ -69,48 +65,48 @@ class VMobject(Mobject): if family: for mob in self.submobjects: mob.set_style_data( - stroke_color = stroke_color, - stroke_width = stroke_width, - fill_color = fill_color, - fill_opacity = fill_opacity, - family = family + stroke_color=stroke_color, + stroke_width=stroke_width, + fill_color=fill_color, + fill_opacity=fill_opacity, + family=family ) return self - def set_fill(self, color = None, opacity = None, family = True): + def set_fill(self, color=None, opacity=None, family=True): return self.set_style_data( - fill_color = color, - fill_opacity = opacity, - family = family + fill_color=color, + fill_opacity=opacity, + family=family ) - def set_stroke(self, color = None, width = None, family = True): + def set_stroke(self, color=None, width=None, family=True): return self.set_style_data( - stroke_color = color, - stroke_width = width, - family = family + stroke_color=color, + stroke_width=width, + family=family ) - def set_color(self, color, family = True): + def set_color(self, color, family=True): self.set_style_data( - stroke_color = color, - fill_color = color, - family = family + stroke_color=color, + fill_color=color, + family=family ) self.color = color return self def match_style(self, vmobject): self.set_style_data( - stroke_color = vmobject.get_stroke_color(), - stroke_width = vmobject.get_stroke_width(), - fill_color = vmobject.get_fill_color(), - fill_opacity = vmobject.get_fill_opacity(), - family = False + stroke_color=vmobject.get_stroke_color(), + stroke_width=vmobject.get_stroke_width(), + fill_color=vmobject.get_fill_color(), + fill_opacity=vmobject.get_fill_opacity(), + family=False ) - #Does its best to match up submobject lists, and - #match styles accordingly + # Does its best to match up submobject lists, and + # match styles accordingly submobs1, submobs2 = self.submobjects, vmobject.submobjects if len(submobs1) == 0: return self @@ -122,12 +118,12 @@ class VMobject(Mobject): def fade_no_recurse(self, darkness): self.set_stroke( - width = (1-darkness)*self.get_stroke_width(), - family = False + width=(1 - darkness) * self.get_stroke_width(), + family=False ) self.set_fill( - opacity = (1-darkness)*self.get_fill_opacity(), - family = False + opacity=(1 - darkness) * self.get_fill_opacity(), + family=False ) return self @@ -137,7 +133,7 @@ class VMobject(Mobject): def get_fill_color(self): try: self.fill_rgb = np.clip(self.fill_rgb, 0.0, 1.0) - return Color(rgb = self.fill_rgb) + return Color(rgb=self.fill_rgb) except: return Color(WHITE) @@ -150,7 +146,7 @@ class VMobject(Mobject): def get_stroke_color(self): try: self.stroke_rgb = np.clip(self.stroke_rgb, 0, 1) - return Color(rgb = self.stroke_rgb) + return Color(rgb=self.stroke_rgb) except: return Color(WHITE) @@ -176,7 +172,7 @@ class VMobject(Mobject): self.color_using_background_image(vmobject.get_background_image_file()) return self - ## Drawing + # Drawing def start_at(self, point): if len(self.points) == 0: self.points = np.zeros((1, 3)) @@ -188,7 +184,7 @@ class VMobject(Mobject): self.points = np.append( self.points, control_points, - axis = 0 + axis=0 ) return self @@ -196,14 +192,14 @@ class VMobject(Mobject): return is_closed(self.points) def set_anchors_and_handles(self, anchors, handles1, handles2): - assert(len(anchors) == len(handles1)+1) - assert(len(anchors) == len(handles2)+1) - total_len = 3*(len(anchors)-1) + 1 + assert(len(anchors) == len(handles1) + 1) + assert(len(anchors) == len(handles2) + 1) + total_len = 3 * (len(anchors) - 1) + 1 self.points = np.zeros((total_len, self.dim)) self.points[0] = anchors[0] arrays = [handles1, handles2, anchors[1:]] for index, array in enumerate(arrays): - self.points[index+1::3] = array + self.points[index + 1::3] = array return self.points def set_points_as_corners(self, points): @@ -212,7 +208,7 @@ class VMobject(Mobject): points = np.array(points) self.set_anchors_and_handles(points, *[ interpolate(points[:-1], points[1:], alpha) - for alpha in 1./3, 2./3 + for alpha in 1. / 3, 2. / 3 ]) return self @@ -227,11 +223,11 @@ class VMobject(Mobject): self.points = np.array(points) return self - def set_anchor_points(self, points, mode = "smooth"): + def set_anchor_points(self, points, mode="smooth"): if not isinstance(points, np.ndarray): points = np.array(points) if self.close_new_points and not is_closed(points): - points = np.append(points, [points[0]], axis = 0) + points = np.append(points, [points[0]], axis=0) if mode == "smooth": self.set_points_smoothly(points) elif mode == "corners": @@ -243,7 +239,7 @@ class VMobject(Mobject): def change_anchor_mode(self, mode): for submob in self.family_members_with_points(): anchors, h1, h2 = submob.get_anchors_and_handles() - submob.set_anchor_points(anchors, mode = mode) + submob.set_anchor_points(anchors, mode=mode) return self def make_smooth(self): @@ -262,7 +258,7 @@ class VMobject(Mobject): but will be tracked in a separate special list for when it comes time to display. """ - subpath_mobject = self.copy() ##Really helps to be of the same class + subpath_mobject = self.copy() # Really helps to be of the same class subpath_mobject.submobjects = [] subpath_mobject.is_subpath = True subpath_mobject.set_points(points) @@ -277,12 +273,12 @@ class VMobject(Mobject): self.start_at(new_points[0]) self.add_control_points(new_points[1:]) else: - self.add_control_points(2*[new_points[0]] + new_points) + self.add_control_points(2 * [new_points[0]] + new_points) return self def get_subpath_mobjects(self): return filter( - lambda m : hasattr(m, 'is_subpath') and m.is_subpath, + lambda m: hasattr(m, 'is_subpath') and m.is_subpath, self.submobjects ) @@ -290,7 +286,7 @@ class VMobject(Mobject): factor = self.pre_function_handle_to_anchor_scale_factor self.scale_handle_to_anchor_distances(factor) Mobject.apply_function(self, function) - self.scale_handle_to_anchor_distances(1./factor) + self.scale_handle_to_anchor_distances(1. / factor) if self.make_smooth_after_applying_functions: self.make_smooth() return self @@ -300,8 +296,8 @@ class VMobject(Mobject): If the distance between a given handle point H and its associated anchor point A is d, then it changes H to be a distances factor*d away from A, but so that the line from A to H doesn't change. - This is mostly useful in the context of applying a (differentiable) - function, to preserve tangency properties. One would pull all the + This is mostly useful in the context of applying a (differentiable) + function, to preserve tangency properties. One would pull all the handles closer to their anchors, apply the function then push them out again. """ @@ -311,27 +307,27 @@ class VMobject(Mobject): # print len(anchors), len(handles1), len(handles2) a_to_h1 = handles1 - anchors[:-1] a_to_h2 = handles2 - anchors[1:] - handles1 = anchors[:-1] + factor*a_to_h1 - handles2 = anchors[1:] + factor*a_to_h2 + handles1 = anchors[:-1] + factor * a_to_h1 + handles2 = anchors[1:] + factor * a_to_h2 self.set_anchors_and_handles(anchors, handles1, handles2) - ## Information about line + # Information about line def component_curves(self): - for n in range(self.get_num_anchor_points()-1): + for n in range(self.get_num_anchor_points() - 1): yield self.get_nth_curve(n) def get_nth_curve(self, n): - return bezier(self.points[3*n:3*n+4]) + return bezier(self.points[3 * n:3 * n + 4]) def get_num_anchor_points(self): - return (len(self.points) - 1)/3 + 1 + return (len(self.points) - 1) / 3 + 1 def point_from_proportion(self, alpha): - num_cubics = self.get_num_anchor_points()-1 - interpoint_alpha = num_cubics*(alpha % (1./num_cubics)) - index = min(3*int(alpha*num_cubics), 3*num_cubics) - cubic = bezier(self.points[index:index+4]) + num_cubics = self.get_num_anchor_points() - 1 + interpoint_alpha = num_cubics * (alpha % (1. / num_cubics)) + index = min(3 * int(alpha * num_cubics), 3 * num_cubics) + cubic = bezier(self.points[index:index + 4]) return cubic(interpoint_alpha) def get_anchors_and_handles(self): @@ -346,8 +342,7 @@ class VMobject(Mobject): def get_points_defining_boundary(self): return self.get_anchors() - - ## Alignment + # Alignment def align_points(self, mobject): Mobject.align_points(self, mobject) is_subpath = self.is_subpath or mobject.is_subpath @@ -355,61 +350,62 @@ class VMobject(Mobject): mark_closed = self.mark_paths_closed and mobject.mark_paths_closed self.mark_paths_closed = mobject.mark_paths_closed = mark_closed return self - + def align_points_with_larger(self, larger_mobject): assert(isinstance(larger_mobject, VMobject)) self.insert_n_anchor_points( - larger_mobject.get_num_anchor_points()-\ + larger_mobject.get_num_anchor_points() - self.get_num_anchor_points() ) return self - + def insert_n_anchor_points(self, n): curr = self.get_num_anchor_points() if curr == 0: self.points = np.zeros((1, 3)) - n = n-1 + n = n - 1 if curr == 1: - self.points = np.repeat(self.points, 3*n+1, axis = 0) + self.points = np.repeat(self.points, 3 * n + 1, axis=0) return self points = np.array([self.points[0]]) - num_curves = curr-1 - #Curves in self are buckets, and we need to know - #how many new anchor points to put into each one. - #Each element of index_allocation is like a bucket, - #and its value tells you the appropriate index of - #the smaller curve. - index_allocation = (np.arange(curr+n-1) * num_curves)/(curr+n-1) + num_curves = curr - 1 + # Curves in self are buckets, and we need to know + # how many new anchor points to put into each one. + # Each element of index_allocation is like a bucket, + # and its value tells you the appropriate index of + # the smaller curve. + index_allocation = (np.arange(curr + n - 1) * + num_curves) / (curr + n - 1) for index in range(num_curves): - curr_bezier_points = self.points[3*index:3*index+4] + curr_bezier_points = self.points[3 * index:3 * index + 4] num_inter_curves = sum(index_allocation == index) - alphas = np.linspace(0, 1, num_inter_curves+1) + alphas = np.linspace(0, 1, num_inter_curves + 1) # alphas = np.arange(0, num_inter_curves+1)/float(num_inter_curves) for a, b in zip(alphas, alphas[1:]): new_points = partial_bezier_points( curr_bezier_points, a, b ) points = np.append( - points, new_points[1:], axis = 0 + points, new_points[1:], axis=0 ) self.set_points(points) return self - - def get_point_mobject(self, center = None): + + def get_point_mobject(self, center=None): if center is None: center = self.get_center() return VectorizedPoint(center) - + def repeat_submobject(self, submobject): if submobject.is_subpath: return VectorizedPoint(submobject.points[0]) return submobject.copy() - + def interpolate_color(self, mobject1, mobject2, alpha): attrs = [ - "stroke_rgb", - "stroke_width", - "fill_rgb", + "stroke_rgb", + "stroke_width", + "fill_rgb", "fill_opacity", ] for attr in attrs: @@ -421,31 +417,31 @@ class VMobject(Mobject): if alpha == 1.0: # print getattr(mobject2, attr) setattr(self, attr, getattr(mobject2, attr)) - + def pointwise_become_partial(self, mobject, a, b): assert(isinstance(mobject, VMobject)) - #Partial curve includes three portions: - #- A middle section, which matches the curve exactly - #- A start, which is some ending portion of an inner cubic - #- An end, which is the starting portion of a later inner cubic + # Partial curve includes three portions: + # - A middle section, which matches the curve exactly + # - A start, which is some ending portion of an inner cubic + # - An end, which is the starting portion of a later inner cubic if a <= 0 and b >= 1: self.set_points(mobject.points) self.mark_paths_closed = mobject.mark_paths_closed return self self.mark_paths_closed = False - num_cubics = mobject.get_num_anchor_points()-1 - lower_index = int(a*num_cubics) - upper_index = int(b*num_cubics) + num_cubics = mobject.get_num_anchor_points() - 1 + lower_index = int(a * num_cubics) + upper_index = int(b * num_cubics) points = np.array( - mobject.points[3*lower_index:3*upper_index+4] + mobject.points[3 * lower_index:3 * upper_index + 4] ) if len(points) > 1: - a_residue = (num_cubics*a)%1 - b_residue = (num_cubics*b)%1 + a_residue = (num_cubics * a) % 1 + b_residue = (num_cubics * b) % 1 if b == 1: b_residue = 1 elif lower_index == upper_index: - b_residue = (b_residue - a_residue)/(1-a_residue) + b_residue = (b_residue - a_residue) / (1 - a_residue) points[:4] = partial_bezier_points( points[:4], a_residue, 1 @@ -456,6 +452,7 @@ class VMobject(Mobject): self.set_points(points) return self + class VGroup(VMobject): def __init__(self, *args, **kwargs): if len(args) == 1 and isinstance(args[0], (tuple, list)): @@ -465,19 +462,22 @@ class VGroup(VMobject): for arg in args: if isinstance(arg, (tuple, list)): packed_args.append(VGroup(arg)) - else: packed_args.append(arg) + else: + packed_args.append(arg) VMobject.__init__(self, *packed_args, **kwargs) + class VectorizedPoint(VMobject): CONFIG = { - "color" : BLACK, - "fill_opacity" : 0, - "stroke_width" : 0, - "artificial_width" : 0.01, - "artificial_height" : 0.01, + "color": BLACK, + "fill_opacity": 0, + "stroke_width": 0, + "artificial_width": 0.01, + "artificial_height": 0.01, } - def __init__(self, location = ORIGIN, **kwargs): + + def __init__(self, location=ORIGIN, **kwargs): VMobject.__init__(self, **kwargs) self.set_points(np.array([location])) @@ -490,24 +490,25 @@ class VectorizedPoint(VMobject): def get_location(self): return self.get_anchors()[0] - def set_location(self,new_loc): + def set_location(self, new_loc): self.set_points(np.array([new_loc])) + class DashedMobject(VMobject): CONFIG = { - "dashes_num" : 15, - "spacing" : 0.5, - "color" : WHITE + "dashes_num": 15, + "spacing": 0.5, + "color": WHITE } + def __init__(self, mobject, **kwargs): VMobject.__init__(self, **kwargs) buff = float(self.spacing) / self.dashes_num for i in range(self.dashes_num): - a = ((1+buff) * i)/self.dashes_num - b = 1-((1+buff) * (self.dashes_num-1-i)) / self.dashes_num - dash = VMobject(color = self.color) + a = ((1 + buff) * i) / self.dashes_num + b = 1 - ((1 + buff) * (self.dashes_num - 1 - i)) / self.dashes_num + dash = VMobject(color=self.color) dash.pointwise_become_partial(mobject, a, b) self.submobjects.append(dash) - diff --git a/mobject/value_tracker.py b/mobject/value_tracker.py index 48f8ba59..0af4d948 100644 --- a/mobject/value_tracker.py +++ b/mobject/value_tracker.py @@ -8,14 +8,16 @@ from mobject.types.vectorized_mobject import VectorizedPoint # TODO: Rather than using VectorizedPoint, there should be some UndisplayedPointSet type + class ValueTracker(VectorizedPoint): """ - Note meant to be displayed. Instead the position encodes some + Note meant to be displayed. Instead the position encodes some number, often one which another animation or continual_animation uses for its update function, and by treating it as a mobject it can still be animated and manipulated just like anything else. """ - def __init__(self, value = 0, **kwargs): + + def __init__(self, value=0, **kwargs): VectorizedPoint.__init__(self, **kwargs) self.set_value(value) @@ -23,21 +25,23 @@ class ValueTracker(VectorizedPoint): return self.get_center()[0] def set_value(self, value): - self.move_to(value*RIGHT) + self.move_to(value * RIGHT) return self def increment_value(self, d_value): self.set_value(self.get_value() + d_value) + class ExponentialValueTracker(ValueTracker): """ Operates just like ValueTracker, except it encodes the value as the - exponential of a position coordinate, which changes how interpolation + exponential of a position coordinate, which changes how interpolation behaves """ + def get_value(self): return np.exp(self.get_center()[0]) def set_value(self, value): - self.move_to(np.log(value)*RIGHT) + self.move_to(np.log(value) * RIGHT) return self diff --git a/once_useful_constructs/arithmetic.py b/once_useful_constructs/arithmetic.py index 173ecc73..d61b655a 100644 --- a/once_useful_constructs/arithmetic.py +++ b/once_useful_constructs/arithmetic.py @@ -1,4 +1,3 @@ -import itertools as it import numpy as np from animation.animation import Animation @@ -6,18 +5,19 @@ from constants import * from mobject.svg.tex_mobject import TexMobject from scene.scene import Scene + class RearrangeEquation(Scene): def construct( - self, - start_terms, - end_terms, + self, + start_terms, + end_terms, index_map, - path_arc = np.pi, - start_transform = None, - end_transform = None, - leave_start_terms = False, - transform_kwargs = {}, - ): + path_arc=np.pi, + start_transform=None, + end_transform=None, + leave_start_terms=False, + transform_kwargs={}, + ): transform_kwargs["path_func"] = path start_mobs, end_mobs = self.get_mobs_from_terms( start_terms, end_terms @@ -27,17 +27,17 @@ class RearrangeEquation(Scene): if end_transform: end_mobs = end_transform(Mobject(*end_mobs)).split() unmatched_start_indices = set(range(len(start_mobs))) - unmatched_end_indices = set(range(len(end_mobs))) + unmatched_end_indices = set(range(len(end_mobs))) unmatched_start_indices.difference_update( - [n%len(start_mobs) for n in index_map] + [n % len(start_mobs) for n in index_map] ) unmatched_end_indices.difference_update( - [n%len(end_mobs) for n in index_map.values()] + [n % len(end_mobs) for n in index_map.values()] ) mobject_pairs = [ (start_mobs[a], end_mobs[b]) for a, b in index_map.iteritems() - ]+ [ + ] + [ (Point(end_mobs[b].get_center()), end_mobs[b]) for b in unmatched_end_indices ] @@ -45,7 +45,7 @@ class RearrangeEquation(Scene): mobject_pairs += [ (start_mobs[a], Point(start_mobs[a].get_center())) for a in unmatched_start_indices - ] + ] self.add(*start_mobs) if leave_start_terms: @@ -57,7 +57,6 @@ class RearrangeEquation(Scene): ]) self.wait() - def get_mobs_from_terms(self, start_terms, end_terms): """ Need to ensure that all image mobjects for a tex expression @@ -67,10 +66,10 @@ class RearrangeEquation(Scene): """ num_start_terms = len(start_terms) all_mobs = np.array( - TexMobject(start_terms).split() + \ + TexMobject(start_terms).split() + TexMobject(end_terms).split() ) - all_terms = np.array(start_terms+end_terms) + all_terms = np.array(start_terms + end_terms) for term in set(all_terms): matches = all_terms == term if sum(matches) > 1: @@ -84,23 +83,21 @@ class RearrangeEquation(Scene): class FlipThroughSymbols(Animation): CONFIG = { - "start_center" : ORIGIN, - "end_center" : ORIGIN, + "start_center": ORIGIN, + "end_center": ORIGIN, } + def __init__(self, tex_list, **kwargs): mobject = TexMobject(self.curr_tex).shift(start_center) Animation.__init__(self, mobject, **kwargs) def update_mobject(self, alpha): - new_tex = self.tex_list[np.ceil(alpha*len(self.tex_list))-1] + new_tex = self.tex_list[np.ceil(alpha * len(self.tex_list)) - 1] if new_tex != self.curr_tex: self.curr_tex = new_tex self.mobject = TexMobject(new_tex).shift(self.start_center) if not all(self.start_center == self.end_center): self.mobject.center().shift( - (1-alpha)*self.start_center + alpha*self.end_center + (1 - alpha) * self.start_center + alpha * self.end_center ) - - - diff --git a/once_useful_constructs/complex_transformation_scene.py b/once_useful_constructs/complex_transformation_scene.py index efbbe21b..e5d6c9ff 100644 --- a/once_useful_constructs/complex_transformation_scene.py +++ b/once_useful_constructs/complex_transformation_scene.py @@ -2,29 +2,30 @@ from constants import * from animation.animation import Animation from animation.movement import SmoothedVectorizedHomotopy -from animation.transform import ApplyPointwiseFunction from animation.transform import MoveToTarget from mobject.coordinate_systems import ComplexPlane from mobject.types.vectorized_mobject import VGroup from scene.scene import Scene + class ComplexTransformationScene(Scene): CONFIG = { - "plane_config" : {}, - "background_fade_factor" : 0.5, - "use_multicolored_plane" : False, - "vert_start_color" : BLUE, ##TODO - "vert_end_color" : BLUE, - "horiz_start_color" : BLUE, - "horiz_end_color" : BLUE, - "num_anchors_to_add_per_line" : 50, - "post_transformation_stroke_width" : None, - "default_apply_complex_function_kwargs" : { - "run_time" : 5, + "plane_config": {}, + "background_fade_factor": 0.5, + "use_multicolored_plane": False, + "vert_start_color": BLUE, # TODO + "vert_end_color": BLUE, + "horiz_start_color": BLUE, + "horiz_end_color": BLUE, + "num_anchors_to_add_per_line": 50, + "post_transformation_stroke_width": None, + "default_apply_complex_function_kwargs": { + "run_time": 5, }, - "background_label_scale_val" : 0.5, - "include_coordinate_labels" : True, + "background_label_scale_val": 0.5, + "include_coordinate_labels": True, } + def setup(self): self.foreground_mobjects = [] self.transformable_mobjects = [] @@ -44,12 +45,12 @@ class ComplexTransformationScene(Scene): Scene.add(self, *mobjects) def add(self, *mobjects): - Scene.add(self, *list(mobjects)+self.foreground_mobjects) + Scene.add(self, *list(mobjects) + self.foreground_mobjects) def play(self, *animations, **kwargs): Scene.play( self, - *list(animations)+map(Animation, self.foreground_mobjects), + *list(animations) + map(Animation, self.foreground_mobjects), **kwargs ) @@ -67,7 +68,7 @@ class ComplexTransformationScene(Scene): self.plane = self.get_transformable_plane() self.add(self.plane) - def get_transformable_plane(self, x_range = None, y_range = None): + def get_transformable_plane(self, x_range=None, y_range=None): """ x_range and y_range would be tuples (min, max) """ @@ -76,11 +77,11 @@ class ComplexTransformationScene(Scene): if x_range is not None: x_min, x_max = x_range plane_config["x_radius"] = x_max - x_min - shift_val += (x_max+x_min)*RIGHT/2. + shift_val += (x_max + x_min) * RIGHT / 2. if y_range is not None: y_min, y_max = y_range plane_config["y_radius"] = y_max - y_min - shift_val += (y_max+y_min)*UP/2. + shift_val += (y_max + y_min) * UP / 2. plane = ComplexPlane(**plane_config) plane.shift(shift_val) if self.use_multicolored_plane: @@ -92,7 +93,7 @@ class ComplexTransformationScene(Scene): mob.prepare_for_nonlinear_transform( self.num_anchors_to_add_per_line ) - #TODO... + # TODO... def paint_plane(self, plane): for lines in plane.main_lines, plane.secondary_lines: @@ -109,7 +110,7 @@ class ComplexTransformationScene(Scene): def z_to_point(self, z): return self.background.number_to_point(z) - + def get_transformer(self, **kwargs): transform_kwargs = dict(self.default_apply_complex_function_kwargs) transform_kwargs.update(kwargs) @@ -117,16 +118,15 @@ class ComplexTransformationScene(Scene): self.prepare_for_transformation(plane) transformer = VGroup( plane, *self.transformable_mobjects - ) + ) return transformer, transform_kwargs - - def apply_complex_function(self, func, added_anims = [], **kwargs): + def apply_complex_function(self, func, added_anims=[], **kwargs): transformer, transform_kwargs = self.get_transformer(**kwargs) transformer.generate_target() - #Rescale, apply function, scale back + # Rescale, apply function, scale back transformer.target.shift(-self.background.get_center_point()) - transformer.target.scale(1./self.background.unit_size) + transformer.target.scale(1. / self.background.unit_size) transformer.target.apply_complex_function(func) transformer.target.scale(self.background.unit_size) transformer.target.shift(self.background.get_center_point()) @@ -135,14 +135,16 @@ class ComplexTransformationScene(Scene): for mob in transformer.target[0].family_members_with_points(): mob.make_smooth() if self.post_transformation_stroke_width is not None: - transformer.target.set_stroke(width = self.post_transformation_stroke_width) + transformer.target.set_stroke( + width=self.post_transformation_stroke_width) self.play( MoveToTarget(transformer, **transform_kwargs), *added_anims ) - def apply_complex_homotopy(self, complex_homotopy, added_anims = [], **kwargs): + def apply_complex_homotopy(self, complex_homotopy, added_anims=[], **kwargs): transformer, transform_kwargs = self.get_transformer(**kwargs) + def homotopy(x, y, z, t): output = complex_homotopy(complex(x, y), t) rescaled_output = self.z_to_point(output) @@ -155,24 +157,3 @@ class ComplexTransformationScene(Scene): ), *added_anims ) - - - - - - - - - - - - - - - - - - - - - diff --git a/once_useful_constructs/counting.py b/once_useful_constructs/counting.py index 8b8edeb0..87a85257 100644 --- a/once_useful_constructs/counting.py +++ b/once_useful_constructs/counting.py @@ -1,10 +1,7 @@ from constants import * -from mobject.mobject import Mobject from mobject.svg.tex_mobject import TexMobject -from mobject.svg.tex_mobject import TextMobject from mobject.types.vectorized_mobject import VGroup -from mobject.types.vectorized_mobject import VMobject from animation.creation import ShowCreation from animation.creation import FadeIn @@ -18,17 +15,18 @@ from scene.scene import Scene class CountingScene(Scene): CONFIG = { - "digit_place_colors" : [YELLOW, MAROON_B, RED, GREEN, BLUE, PURPLE_D], - "counting_dot_starting_position" : (FRAME_X_RADIUS-1)*RIGHT + (FRAME_Y_RADIUS-1)*UP, - "count_dot_starting_radius" : 0.5, - "dot_configuration_height" : 2, - "ones_configuration_location" : UP+2*RIGHT, - "num_scale_factor" : 2, - "num_start_location" : 2*DOWN, + "digit_place_colors": [YELLOW, MAROON_B, RED, GREEN, BLUE, PURPLE_D], + "counting_dot_starting_position": (FRAME_X_RADIUS - 1) * RIGHT + (FRAME_Y_RADIUS - 1) * UP, + "count_dot_starting_radius": 0.5, + "dot_configuration_height": 2, + "ones_configuration_location": UP + 2 * RIGHT, + "num_scale_factor": 2, + "num_start_location": 2 * DOWN, } + def setup(self): self.dots = VGroup() - self.number = 0 + self.number = 0 self.max_place = 0 self.number_mob = VGroup(TexMobject(str(self.number))) self.number_mob.scale(self.num_scale_factor) @@ -43,26 +41,25 @@ class CountingScene(Scene): self.add(self.number_mob) def get_template_configuration(self, place): - #This should probably be replaced for non-base-10 counting scenes - down_right = (0.5)*RIGHT + (np.sqrt(3)/2)*DOWN + # This should probably be replaced for non-base-10 counting scenes + down_right = (0.5) * RIGHT + (np.sqrt(3) / 2) * DOWN result = [] for down_right_steps in range(5): for left_steps in range(down_right_steps): result.append( - down_right_steps*down_right + left_steps*LEFT + down_right_steps * down_right + left_steps * LEFT ) return reversed(result[:self.get_place_max(place)]) def get_dot_template(self, place): - #This should be replaced for non-base-10 counting scenes - down_right = (0.5)*RIGHT + (np.sqrt(3)/2)*DOWN + # This should be replaced for non-base-10 counting scenes dots = VGroup(*[ Dot( - point, - radius = 0.25, - fill_opacity = 0, - stroke_width = 2, - stroke_color = WHITE, + point, + radius=0.25, + fill_opacity=0, + stroke_width=2, + stroke_color=WHITE, ) for point in self.get_template_configuration(place) ]) @@ -72,9 +69,9 @@ class CountingScene(Scene): def add_configuration(self): new_template = self.get_dot_template(len(self.dot_templates)) new_template.move_to(self.ones_configuration_location) - left_vect = (new_template.get_width()+LARGE_BUFF)*LEFT + left_vect = (new_template.get_width() + LARGE_BUFF) * LEFT new_template.shift( - left_vect*len(self.dot_templates) + left_vect * len(self.dot_templates) ) self.dot_templates.append(new_template) self.dot_template_iterators.append( @@ -82,27 +79,27 @@ class CountingScene(Scene): ) self.curr_configurations.append(VGroup()) - def count(self, max_val, run_time_per_anim = 1): + def count(self, max_val, run_time_per_anim=1): for x in range(max_val): self.increment(run_time_per_anim) - def increment(self, run_time_per_anim = 1): + def increment(self, run_time_per_anim=1): moving_dot = Dot( self.counting_dot_starting_position, - radius = self.count_dot_starting_radius, - color = self.digit_place_colors[0], + radius=self.count_dot_starting_radius, + color=self.digit_place_colors[0], ) moving_dot.generate_target() - moving_dot.set_fill(opacity = 0) + moving_dot.set_fill(opacity=0) kwargs = { - "run_time" : run_time_per_anim + "run_time": run_time_per_anim } continue_rolling_over = True first_move = True place = 0 while continue_rolling_over: - added_anims = [] + added_anims = [] if first_move: added_anims += self.get_digit_increment_animations() first_move = False @@ -112,26 +109,25 @@ class CountingScene(Scene): self.play(MoveToTarget(moving_dot), *added_anims, **kwargs) self.curr_configurations[place].add(moving_dot) - if len(self.curr_configurations[place].split()) == self.get_place_max(place): full_configuration = self.curr_configurations[place] self.curr_configurations[place] = VGroup() place += 1 center = full_configuration.get_center_of_mass() - radius = 0.6*max( + radius = 0.6 * max( full_configuration.get_width(), full_configuration.get_height(), ) circle = Circle( - radius = radius, - stroke_width = 0, - fill_color = self.digit_place_colors[place], - fill_opacity = 0.5, + radius=radius, + stroke_width=0, + fill_color=self.digit_place_colors[place], + fill_opacity=0.5, ) circle.move_to(center) moving_dot = VGroup(circle, full_configuration) moving_dot.generate_target() - moving_dot[0].set_fill(opacity = 0) + moving_dot[0].set_fill(opacity=0) else: continue_rolling_over = False @@ -139,23 +135,24 @@ class CountingScene(Scene): result = [] self.number += 1 is_next_digit = self.is_next_digit() - if is_next_digit: self.max_place += 1 + if is_next_digit: + self.max_place += 1 new_number_mob = self.get_number_mob(self.number) new_number_mob.move_to(self.number_mob, RIGHT) if is_next_digit: self.add_configuration() - place = len(new_number_mob.split())-1 + place = len(new_number_mob.split()) - 1 result.append(FadeIn(self.dot_templates[place])) arrow = Arrow( new_number_mob[place].get_top(), self.dot_templates[place].get_bottom(), - color = self.digit_place_colors[place] + color=self.digit_place_colors[place] ) self.arrows.add(arrow) result.append(ShowCreation(arrow)) result.append(Transform( self.number_mob, new_number_mob, - submobject_mode = "lagged_start" + submobject_mode="lagged_start" )) return result @@ -169,35 +166,42 @@ class CountingScene(Scene): self.digit_place_colors += self.digit_place_colors digit.set_color(self.digit_place_colors[place]) digit.scale(self.num_scale_factor) - digit.next_to(result, LEFT, buff = SMALL_BUFF, aligned_edge = DOWN) + digit.next_to(result, LEFT, buff=SMALL_BUFF, aligned_edge=DOWN) result.add(digit) place += 1 return result def is_next_digit(self): return False + def get_place_num(self, num, place): return 0 + def get_place_max(self, place): return 0 + class PowerCounter(CountingScene): def is_next_digit(self): number = self.number while number > 1: - if number%self.base != 0: + if number % self.base != 0: return False number /= self.base return True + def get_place_max(self, place): return self.base + def get_place_num(self, num, place): return (num / (self.base ** place)) % self.base + class CountInDecimal(PowerCounter): CONFIG = { - "base" : 10, + "base": 10, } + def construct(self): for x in range(11): self.increment() @@ -206,35 +210,41 @@ class CountInDecimal(PowerCounter): for x in range(20): self.increment() + class CountInTernary(PowerCounter): CONFIG = { - "base" : 3, - "dot_configuration_height" : 1, - "ones_configuration_location" : UP+4*RIGHT + "base": 3, + "dot_configuration_height": 1, + "ones_configuration_location": UP + 4 * RIGHT } + def construct(self): self.count(27) # def get_template_configuration(self): # return [ORIGIN, UP] + class CountInBinaryTo256(PowerCounter): CONFIG = { - "base" : 2, - "dot_configuration_height" : 1, - "ones_configuration_location" : UP+5*RIGHT + "base": 2, + "dot_configuration_height": 1, + "ones_configuration_location": UP + 5 * RIGHT } + def construct(self): self.count(128, 0.3) def get_template_configuration(self): return [ORIGIN, UP] + class FactorialBase(CountingScene): CONFIG = { - "dot_configuration_height" : 1, - "ones_configuration_location" : UP+4*RIGHT + "dot_configuration_height": 1, + "ones_configuration_location": UP + 4 * RIGHT } + def construct(self): self.count(30, 0.4) @@ -248,15 +258,7 @@ class FactorialBase(CountingScene): return (num / self.factorial(place + 1)) % self.get_place_max(place) def factorial(self, n): - if (n == 1): return 1 - else: return n * self.factorial(n - 1) - - - - - - - - - - \ No newline at end of file + if (n == 1): + return 1 + else: + return n * self.factorial(n - 1) diff --git a/once_useful_constructs/fractals.py b/once_useful_constructs/fractals.py index 68283db8..51241563 100644 --- a/once_useful_constructs/fractals.py +++ b/once_useful_constructs/fractals.py @@ -1,17 +1,11 @@ -from animation.creation import ShowCreation -from animation.transform import Transform from for_3b1b_videos.pi_creature import PiCreature from for_3b1b_videos.pi_creature import Randolph from for_3b1b_videos.pi_creature import get_all_pi_creature_modes from mobject.types.vectorized_mobject import VGroup from mobject.types.vectorized_mobject import VMobject -from mobject.types.vectorized_mobject import VectorizedPoint -from scene.scene import Scene from mobject.geometry import Circle -from mobject.geometry import Line from mobject.geometry import Polygon from mobject.geometry import RegularPolygon -from mobject.geometry import Square from utils.bezier import interpolate from utils.color import color_gradient from utils.config_ops import digest_config @@ -22,64 +16,70 @@ from utils.space_ops import rotation_matrix from constants import * -def rotate(points, angle = np.pi, axis = OUT): + +def rotate(points, angle=np.pi, axis=OUT): if axis is None: return points matrix = rotation_matrix(angle, axis) points = np.dot(points, np.transpose(matrix)) return points -def fractalify(vmobject, order = 3, *args, **kwargs): + +def fractalify(vmobject, order=3, *args, **kwargs): for x in range(order): fractalification_iteration(vmobject) return vmobject -def fractalification_iteration(vmobject, dimension = 1.05, num_inserted_anchors_range = range(1, 4)): + +def fractalification_iteration(vmobject, dimension=1.05, num_inserted_anchors_range=range(1, 4)): num_points = vmobject.get_num_points() if num_points > 0: # original_anchors = vmobject.get_anchors() original_anchors = [ vmobject.point_from_proportion(x) - for x in np.linspace(0, 1-1./num_points, num_points) + for x in np.linspace(0, 1 - 1. / num_points, num_points) ] new_anchors = [] for p1, p2, in zip(original_anchors, original_anchors[1:]): num_inserts = random.choice(num_inserted_anchors_range) inserted_points = [ interpolate(p1, p2, alpha) - for alpha in np.linspace(0, 1, num_inserts+2)[1:-1] + for alpha in np.linspace(0, 1, num_inserts + 2)[1:-1] ] - mass_scaling_factor = 1./(num_inserts+1) - length_scaling_factor = mass_scaling_factor**(1./dimension) - target_length = np.linalg.norm(p1-p2)*length_scaling_factor - curr_length = np.linalg.norm(p1-p2)*mass_scaling_factor - #offset^2 + curr_length^2 = target_length^2 + mass_scaling_factor = 1. / (num_inserts + 1) + length_scaling_factor = mass_scaling_factor**(1. / dimension) + target_length = np.linalg.norm(p1 - p2) * length_scaling_factor + curr_length = np.linalg.norm(p1 - p2) * mass_scaling_factor + # offset^2 + curr_length^2 = target_length^2 offset_len = np.sqrt(target_length**2 - curr_length**2) - unit_vect = (p1-p2)/np.linalg.norm(p1-p2) - offset_unit_vect = rotate_vector(unit_vect, np.pi/2) + unit_vect = (p1 - p2) / np.linalg.norm(p1 - p2) + offset_unit_vect = rotate_vector(unit_vect, np.pi / 2) inserted_points = [ - point + u*offset_len*offset_unit_vect + point + u * offset_len * offset_unit_vect for u, point in zip(it.cycle([-1, 1]), inserted_points) ] new_anchors += [p1] + inserted_points new_anchors.append(original_anchors[-1]) vmobject.set_points_as_corners(new_anchors) vmobject.submobjects = [ - fractalification_iteration(submob, dimension, num_inserted_anchors_range) + fractalification_iteration( + submob, dimension, num_inserted_anchors_range) for submob in vmobject.submobjects ] return vmobject + class SelfSimilarFractal(VMobject): CONFIG = { - "order" : 5, - "num_subparts" : 3, - "height" : 4, - "colors" : [RED, WHITE], - "stroke_width" : 1, - "fill_opacity" : 1, - "propagate_style_to_family" : True, + "order": 5, + "num_subparts": 3, + "height": 4, + "colors": [RED, WHITE], + "stroke_width": 1, + "fill_opacity": 1, + "propagate_style_to_family": True, } + def init_colors(self): VMobject.init_colors(self) self.set_color_by_gradient(*self.colors) @@ -114,45 +114,51 @@ class SelfSimilarFractal(VMobject): def arrange_subparts(self, *subparts): raise Exception("Not implemented") + class Sierpinski(SelfSimilarFractal): def get_seed_shape(self): return Polygon( - RIGHT, np.sqrt(3)*UP, LEFT, + RIGHT, np.sqrt(3) * UP, LEFT, ) def arrange_subparts(self, *subparts): tri1, tri2, tri3 = subparts - tri1.move_to(tri2.get_corner(DOWN+LEFT), UP) - tri3.move_to(tri2.get_corner(DOWN+RIGHT), UP) + tri1.move_to(tri2.get_corner(DOWN + LEFT), UP) + tri3.move_to(tri2.get_corner(DOWN + RIGHT), UP) + class DiamondFractal(SelfSimilarFractal): CONFIG = { - "num_subparts" : 4, - "height" : 4, - "colors" : [GREEN_E, YELLOW], + "num_subparts": 4, + "height": 4, + "colors": [GREEN_E, YELLOW], } + def get_seed_shape(self): - return RegularPolygon(n = 4) + return RegularPolygon(n=4) def arrange_subparts(self, *subparts): # VGroup(*subparts).rotate(np.pi/4) - for part, vect in zip(subparts, compass_directions(start_vect = UP+RIGHT)): - part.next_to(ORIGIN, vect, buff = 0) - VGroup(*subparts).rotate(np.pi/4, about_point = ORIGIN) + for part, vect in zip(subparts, compass_directions(start_vect=UP + RIGHT)): + part.next_to(ORIGIN, vect, buff=0) + VGroup(*subparts).rotate(np.pi / 4, about_point=ORIGIN) + class PentagonalFractal(SelfSimilarFractal): CONFIG = { - "num_subparts" : 5, - "colors" : [MAROON_B, YELLOW, RED], - "height" : 6, + "num_subparts": 5, + "colors": [MAROON_B, YELLOW, RED], + "height": 6, } + def get_seed_shape(self): - return RegularPolygon(n = 5, start_angle = np.pi/2) + return RegularPolygon(n=5, start_angle=np.pi / 2) def arrange_subparts(self, *subparts): for x, part in enumerate(subparts): - part.shift(0.95*part.get_height()*UP) - part.rotate(2*np.pi*x/5, about_point = ORIGIN) + part.shift(0.95 * part.get_height() * UP) + part.rotate(2 * np.pi * x / 5, about_point=ORIGIN) + class PentagonalPiCreatureFractal(PentagonalFractal): def init_colors(self): @@ -165,30 +171,32 @@ class PentagonalPiCreatureFractal(PentagonalFractal): colors = color_gradient(self.colors, len(internal_pis)) for pi, color in zip(internal_pis, colors): pi.init_colors() - pi.body.set_stroke(color, width = 0.5) + pi.body.set_stroke(color, width=0.5) pi.set_color(color) def get_seed_shape(self): - return Randolph(mode = "shruggie") + return Randolph(mode="shruggie") def arrange_subparts(self, *subparts): for part in subparts: - part.rotate(2*np.pi/5, about_point = ORIGIN) + part.rotate(2 * np.pi / 5, about_point=ORIGIN) PentagonalFractal.arrange_subparts(self, *subparts) + class PiCreatureFractal(VMobject): CONFIG = { - "order" : 7, - "scale_val" : 2.5, - "start_mode" : "hooray", - "height" : 6, - "colors" : [ + "order": 7, + "scale_val": 2.5, + "start_mode": "hooray", + "height": 6, + "colors": [ BLUE_D, BLUE_B, MAROON_B, MAROON_D, GREY, YELLOW, RED, GREY_BROWN, RED, RED_E, ], - "random_seed" : 0, - "stroke_width" : 0, + "random_seed": 0, + "stroke_width": 0, } + def init_colors(self): VMobject.init_colors(self) internal_pis = [ @@ -200,13 +208,12 @@ class PiCreatureFractal(VMobject): for pi in reversed(internal_pis): color = random.choice(self.colors) pi.set_color(color) - pi.set_stroke(color, width = 0) - + pi.set_stroke(color, width=0) def generate_points(self): random.seed(self.random_seed) modes = get_all_pi_creature_modes() - seed = PiCreature(mode = self.start_mode) + seed = PiCreature(mode=self.start_mode) seed.scale_to_fit_height(self.height) seed.to_edge(DOWN) creatures = [seed] @@ -216,15 +223,15 @@ class PiCreatureFractal(VMobject): for creature in creatures: for eye, vect in zip(creature.eyes, [LEFT, RIGHT]): new_creature = PiCreature( - mode = random.choice(modes) + mode=random.choice(modes) ) new_creature.scale_to_fit_height( - self.scale_val*eye.get_height() + self.scale_val * eye.get_height() ) new_creature.next_to( - eye, vect, - buff = 0, - aligned_edge = DOWN + eye, vect, + buff=0, + aligned_edge=DOWN ) new_creatures.append(new_creature) creature.look_at(random.choice(new_creatures)) @@ -235,70 +242,77 @@ class PiCreatureFractal(VMobject): # VMobject.init_colors(self) # self.set_color_by_gradient(*self.colors) + class WonkyHexagonFractal(SelfSimilarFractal): CONFIG = { - "num_subparts" : 7 + "num_subparts": 7 } + def get_seed_shape(self): return RegularPolygon(n=6) def arrange_subparts(self, *subparts): for i, piece in enumerate(subparts): - piece.rotate(i*np.pi/12, about_point = ORIGIN) + piece.rotate(i * np.pi / 12, about_point=ORIGIN) p1, p2, p3, p4, p5, p6, p7 = subparts center_row = VGroup(p1, p4, p7) - center_row.arrange_submobjects(RIGHT, buff = 0) + center_row.arrange_submobjects(RIGHT, buff=0) for p in p2, p3, p5, p6: p.scale_to_fit_width(p1.get_width()) - p2.move_to(p1.get_top(), DOWN+LEFT) - p3.move_to(p1.get_bottom(), UP+LEFT) - p5.move_to(p4.get_top(), DOWN+LEFT) - p6.move_to(p4.get_bottom(), UP+LEFT) + p2.move_to(p1.get_top(), DOWN + LEFT) + p3.move_to(p1.get_bottom(), UP + LEFT) + p5.move_to(p4.get_top(), DOWN + LEFT) + p6.move_to(p4.get_bottom(), UP + LEFT) + class CircularFractal(SelfSimilarFractal): CONFIG = { - "num_subparts" : 3, - "colors" : [GREEN, BLUE, GREY] + "num_subparts": 3, + "colors": [GREEN, BLUE, GREY] } + def get_seed_shape(self): return Circle() def arrange_subparts(self, *subparts): if not hasattr(self, "been_here"): - self.num_subparts = 3+self.order + self.num_subparts = 3 + self.order self.been_here = True for i, part in enumerate(subparts): - theta = np.pi/self.num_subparts + theta = np.pi / self.num_subparts part.next_to( ORIGIN, UP, - buff = self.height/(2*np.tan(theta)) + buff=self.height / (2 * np.tan(theta)) ) - part.rotate(i*2*np.pi/self.num_subparts, about_point = ORIGIN) + part.rotate(i * 2 * np.pi / self.num_subparts, about_point=ORIGIN) self.num_subparts -= 1 ######## Space filling curves ############ + class JaggedCurvePiece(VMobject): def insert_n_anchor_points(self, n): if self.get_num_anchor_points() == 0: self.points = np.zeros((1, 3)) anchors = self.get_anchors() - indices = np.linspace(0, len(anchors)-1, n+len(anchors)).astype('int') + indices = np.linspace(0, len(anchors) - 1, n + + len(anchors)).astype('int') self.set_points_as_corners(anchors[indices]) + class FractalCurve(VMobject): CONFIG = { - "radius" : 3, - "order" : 5, - "colors" : [RED, GREEN], - "num_submobjects" : 20, - "monochromatic" : False, - "order_to_stroke_width_map" : { - 3 : 3, - 4 : 2, - 5 : 1, + "radius": 3, + "order": 5, + "colors": [RED, GREEN], + "num_submobjects": 20, + "monochromatic": False, + "order_to_stroke_width_map": { + 3: 3, + 4: 2, + 5: 1, }, - "propagate_style_to_family" : True, + "propagate_style_to_family": True, } def generate_points(self): @@ -319,19 +333,20 @@ class FractalCurve(VMobject): self.set_color_by_gradient(*self.colors) for order in sorted(self.order_to_stroke_width_map.keys()): if self.order >= order: - self.set_stroke(width = self.order_to_stroke_width_map[order]) + self.set_stroke(width=self.order_to_stroke_width_map[order]) def get_anchor_points(self): raise Exception("Not implemented") + class LindenmayerCurve(FractalCurve): CONFIG = { - "axiom" : "A", - "rule" : {}, - "scale_factor" : 2, - "radius" : 3, - "start_step" : RIGHT, - "angle" : np.pi/2, + "axiom": "A", + "rule": {}, + "scale_factor": 2, + "radius": 3, + "start_step": RIGHT, + "angle": np.pi / 2, } def expand_command_string(self, command): @@ -350,7 +365,7 @@ class LindenmayerCurve(FractalCurve): return result def get_anchor_points(self): - step = float(self.radius) * self.start_step + step = float(self.radius) * self.start_step step /= (self.scale_factor**self.order) curr = np.zeros(3) result = [curr] @@ -364,14 +379,16 @@ class LindenmayerCurve(FractalCurve): result.append(curr) return np.array(result) - center_of_mass(result) + class SelfSimilarSpaceFillingCurve(FractalCurve): CONFIG = { - "offsets" : [], - #keys must awkwardly be in string form... - "offset_to_rotation_axis" : {}, - "scale_factor" : 2, - "radius_scale_factor" : 0.5, + "offsets": [], + # keys must awkwardly be in string form... + "offset_to_rotation_axis": {}, + "scale_factor": 2, + "radius_scale_factor": 0.5, } + def transform(self, points, offset): """ How to transform the copy of points shifted by @@ -380,11 +397,11 @@ class SelfSimilarSpaceFillingCurve(FractalCurve): copy = np.array(points) if str(offset) in self.offset_to_rotation_axis: copy = rotate( - copy, - axis = self.offset_to_rotation_axis[str(offset)] + copy, + axis=self.offset_to_rotation_axis[str(offset)] ) copy /= self.scale_factor, - copy += offset*self.radius*self.radius_scale_factor + copy += offset * self.radius * self.radius_scale_factor return copy def refine_into_subparts(self, points): @@ -393,11 +410,10 @@ class SelfSimilarSpaceFillingCurve(FractalCurve): for offset in self.offsets ] return reduce( - lambda a, b : np.append(a, b, axis = 0), + lambda a, b: np.append(a, b, axis=0), transformed_copies ) - def get_anchor_points(self): points = np.zeros((1, 3)) for count in range(self.order): @@ -407,95 +423,100 @@ class SelfSimilarSpaceFillingCurve(FractalCurve): def generate_grid(self): raise Exception("Not implemented") + class HilbertCurve(SelfSimilarSpaceFillingCurve): CONFIG = { - "offsets" : [ - LEFT+DOWN, - LEFT+UP, - RIGHT+UP, - RIGHT+DOWN, + "offsets": [ + LEFT + DOWN, + LEFT + UP, + RIGHT + UP, + RIGHT + DOWN, ], - "offset_to_rotation_axis" : { - str(LEFT+DOWN) : RIGHT+UP, - str(RIGHT+DOWN) : RIGHT+DOWN, + "offset_to_rotation_axis": { + str(LEFT + DOWN): RIGHT + UP, + str(RIGHT + DOWN): RIGHT + DOWN, }, - } + } + class HilbertCurve3D(SelfSimilarSpaceFillingCurve): CONFIG = { - "offsets" : [ - RIGHT+DOWN+IN, - LEFT+DOWN+IN, - LEFT+DOWN+OUT, - RIGHT+DOWN+OUT, - RIGHT+UP+OUT, - LEFT+UP+OUT, - LEFT+UP+IN, - RIGHT+UP+IN, + "offsets": [ + RIGHT + DOWN + IN, + LEFT + DOWN + IN, + LEFT + DOWN + OUT, + RIGHT + DOWN + OUT, + RIGHT + UP + OUT, + LEFT + UP + OUT, + LEFT + UP + IN, + RIGHT + UP + IN, ], - "offset_to_rotation_axis_and_angle" : { - str(RIGHT+DOWN+IN) : (LEFT+UP+OUT , 2*np.pi/3), - str(LEFT+DOWN+IN) : (RIGHT+DOWN+IN, 2*np.pi/3), - str(LEFT+DOWN+OUT) : (RIGHT+DOWN+IN, 2*np.pi/3), - str(RIGHT+DOWN+OUT) : (UP , np.pi ), - str(RIGHT+UP+OUT) : (UP , np.pi ), - str(LEFT+UP+OUT) : (LEFT+DOWN+OUT, 2*np.pi/3), - str(LEFT+UP+IN) : (LEFT+DOWN+OUT, 2*np.pi/3), - str(RIGHT+UP+IN) : (RIGHT+UP+IN , 2*np.pi/3), + "offset_to_rotation_axis_and_angle": { + str(RIGHT + DOWN + IN): (LEFT + UP + OUT, 2 * np.pi / 3), + str(LEFT + DOWN + IN): (RIGHT + DOWN + IN, 2 * np.pi / 3), + str(LEFT + DOWN + OUT): (RIGHT + DOWN + IN, 2 * np.pi / 3), + str(RIGHT + DOWN + OUT): (UP, np.pi), + str(RIGHT + UP + OUT): (UP, np.pi), + str(LEFT + UP + OUT): (LEFT + DOWN + OUT, 2 * np.pi / 3), + str(LEFT + UP + IN): (LEFT + DOWN + OUT, 2 * np.pi / 3), + str(RIGHT + UP + IN): (RIGHT + UP + IN, 2 * np.pi / 3), }, } # Rewrote transform method to include the rotation angle + def transform(self, points, offset): copy = np.array(points) copy = rotate( - copy, - axis = self.offset_to_rotation_axis_and_angle[str(offset)][0], - angle = self.offset_to_rotation_axis_and_angle[str(offset)][1], + copy, + axis=self.offset_to_rotation_axis_and_angle[str(offset)][0], + angle=self.offset_to_rotation_axis_and_angle[str(offset)][1], ) copy /= self.scale_factor, - copy += offset*self.radius*self.radius_scale_factor + copy += offset * self.radius * self.radius_scale_factor return copy + class PeanoCurve(SelfSimilarSpaceFillingCurve): CONFIG = { - "colors" : [PURPLE, TEAL], - "offsets" : [ - LEFT+DOWN, + "colors": [PURPLE, TEAL], + "offsets": [ + LEFT + DOWN, LEFT, - LEFT+UP, + LEFT + UP, UP, ORIGIN, DOWN, - RIGHT+DOWN, + RIGHT + DOWN, RIGHT, - RIGHT+UP, + RIGHT + UP, ], - "offset_to_rotation_axis" : { - str(LEFT) : UP, - str(UP) : RIGHT, - str(ORIGIN) : LEFT+UP, - str(DOWN) : RIGHT, - str(RIGHT) : UP, + "offset_to_rotation_axis": { + str(LEFT): UP, + str(UP): RIGHT, + str(ORIGIN): LEFT + UP, + str(DOWN): RIGHT, + str(RIGHT): UP, }, - "scale_factor" : 3, - "radius_scale_factor" : 2.0/3, + "scale_factor": 3, + "radius_scale_factor": 2.0 / 3, } + class TriangleFillingCurve(SelfSimilarSpaceFillingCurve): CONFIG = { - "colors" : [MAROON, YELLOW], - "offsets" : [ - LEFT/4.+DOWN/6., + "colors": [MAROON, YELLOW], + "offsets": [ + LEFT / 4. + DOWN / 6., ORIGIN, - RIGHT/4.+DOWN/6., - UP/3., + RIGHT / 4. + DOWN / 6., + UP / 3., ], - "offset_to_rotation_axis" : { + "offset_to_rotation_axis": { str(ORIGIN): RIGHT, - str(UP/3.) : UP, + str(UP / 3.): UP, }, - "scale_factor" : 2, - "radius_scale_factor" : 1.5, + "scale_factor": 2, + "radius_scale_factor": 1.5, } # class HexagonFillingCurve(SelfSimilarSpaceFillingCurve): @@ -505,7 +526,7 @@ class TriangleFillingCurve(SelfSimilarSpaceFillingCurve): # "axis_offset_pairs" : [ # (None, 1.5*DOWN + 0.5*np.sqrt(3)*LEFT), # (UP+np.sqrt(3)*RIGHT, 1.5*DOWN + 0.5*np.sqrt(3)*RIGHT), -# (np.sqrt(3)*UP+RIGHT, ORIGIN), +# (np.sqrt(3)*UP+RIGHT, ORIGIN), # ((UP, RIGHT), np.sqrt(3)*LEFT), # (None, 1.5*UP + 0.5*np.sqrt(3)*LEFT), # (None, 1.5*UP + 0.5*np.sqrt(3)*RIGHT), @@ -524,135 +545,132 @@ class TriangleFillingCurve(SelfSimilarSpaceFillingCurve): class UtahFillingCurve(SelfSimilarSpaceFillingCurve): CONFIG = { - "colors" : [WHITE, BLUE_D], - "axis_offset_pairs" : [ + "colors": [WHITE, BLUE_D], + "axis_offset_pairs": [ ], - "scale_factor" : 3, - "radius_scale_factor" : 2/(3*np.sqrt(3)), + "scale_factor": 3, + "radius_scale_factor": 2 / (3 * np.sqrt(3)), } + class FlowSnake(LindenmayerCurve): CONFIG = { - "colors" : [YELLOW, GREEN], - "axiom" : "A", - "rule" : { - "A" : "A-B--B+A++AA+B-", - "B" : "+A-BB--B-A++A+B", + "colors": [YELLOW, GREEN], + "axiom": "A", + "rule": { + "A": "A-B--B+A++AA+B-", + "B": "+A-BB--B-A++A+B", }, - "radius" : 6, #TODO, this is innaccurate - "scale_factor" : np.sqrt(7), - "start_step" : RIGHT, - "angle" : -np.pi/3, + "radius": 6, # TODO, this is innaccurate + "scale_factor": np.sqrt(7), + "start_step": RIGHT, + "angle": -np.pi / 3, } + def __init__(self, **kwargs): LindenmayerCurve.__init__(self, **kwargs) - self.rotate(-self.order*np.pi/9, about_point = ORIGIN) + self.rotate(-self.order * np.pi / 9, about_point=ORIGIN) + class SierpinskiCurve(LindenmayerCurve): CONFIG = { - "colors" : [RED, WHITE], - "axiom" : "B", - "rule" : { - "A" : "+B-A-B+", - "B" : "-A+B+A-", + "colors": [RED, WHITE], + "axiom": "B", + "rule": { + "A": "+B-A-B+", + "B": "-A+B+A-", }, - "radius" : 6, #TODO, this is innaccurate - "scale_factor" : 2, - "start_step" : RIGHT, - "angle" : -np.pi/3, + "radius": 6, # TODO, this is innaccurate + "scale_factor": 2, + "start_step": RIGHT, + "angle": -np.pi / 3, } + class KochSnowFlake(LindenmayerCurve): CONFIG = { - "colors" : [BLUE_D, WHITE, BLUE_D], - "axiom" : "A--A--A--", - "rule" : { - "A" : "A+A--A+A" + "colors": [BLUE_D, WHITE, BLUE_D], + "axiom": "A--A--A--", + "rule": { + "A": "A+A--A+A" }, - "radius" : 4, - "scale_factor" : 3, - "start_step" : RIGHT, - "angle" : np.pi/3, - "order_to_stroke_width_map" : { - 3 : 3, - 5 : 2, - 6 : 1, + "radius": 4, + "scale_factor": 3, + "start_step": RIGHT, + "angle": np.pi / 3, + "order_to_stroke_width_map": { + 3: 3, + 5: 2, + 6: 1, }, } def __init__(self, **kwargs): digest_config(self, kwargs) - self.scale_factor = 2*(1+np.cos(self.angle)) + self.scale_factor = 2 * (1 + np.cos(self.angle)) LindenmayerCurve.__init__(self, **kwargs) + class KochCurve(KochSnowFlake): CONFIG = { - "axiom" : "A--" + "axiom": "A--" } + class QuadraticKoch(LindenmayerCurve): CONFIG = { - "colors" : [YELLOW, WHITE, MAROON_B], - "axiom" : "A", - "rule" : { - "A" : "A+A-A-AA+A+A-A" + "colors": [YELLOW, WHITE, MAROON_B], + "axiom": "A", + "rule": { + "A": "A+A-A-AA+A+A-A" }, - "radius" : 4, - "scale_factor" : 4, - "start_step" : RIGHT, - "angle" : np.pi/2 + "radius": 4, + "scale_factor": 4, + "start_step": RIGHT, + "angle": np.pi / 2 } + class QuadraticKochIsland(QuadraticKoch): CONFIG = { - "axiom" : "A+A+A+A" + "axiom": "A+A+A+A" } + class StellarCurve(LindenmayerCurve): CONFIG = { - "start_color" : RED, - "end_color" : BLUE_E, - "rule" : { - "A" : "+B-A-B+A-B+", - "B" : "-A+B+A-B+A-", + "start_color": RED, + "end_color": BLUE_E, + "rule": { + "A": "+B-A-B+A-B+", + "B": "-A+B+A-B+A-", }, - "scale_factor" : 3, - "angle" : 2*np.pi/5, + "scale_factor": 3, + "angle": 2 * np.pi / 5, } + class SnakeCurve(FractalCurve): CONFIG = { - "start_color" : BLUE, - "end_color" : YELLOW, + "start_color": BLUE, + "end_color": YELLOW, } + def get_anchor_points(self): result = [] resolution = 2**self.order - step = 2.0*self.radius / resolution + step = 2.0 * self.radius / resolution lower_left = ORIGIN + \ - LEFT*(self.radius - step/2) + \ - DOWN*(self.radius - step/2) + LEFT * (self.radius - step / 2) + \ + DOWN * (self.radius - step / 2) for y in range(resolution): x_range = range(resolution) - if y%2 == 0: + if y % 2 == 0: x_range.reverse() for x in x_range: result.append( - lower_left + x*step*RIGHT + y*step*UP + lower_left + x * step * RIGHT + y * step * UP ) return result - - - - - - - - - - - - - diff --git a/once_useful_constructs/graph_theory.py b/once_useful_constructs/graph_theory.py index 0e9ae25d..8be6cf3c 100644 --- a/once_useful_constructs/graph_theory.py +++ b/once_useful_constructs/graph_theory.py @@ -2,8 +2,6 @@ import itertools as it import numpy as np import operator as op -from random import random - from constants import * from scene.scene import Scene @@ -13,14 +11,14 @@ from utils.space_ops import center_of_mass class Graph(): def __init__(self): - #List of points in R^3 - vertices = [] - #List of pairs of indices of vertices - edges = [] - #List of tuples of indices of vertices. The last should - #be a cycle whose interior is the entire graph, and when - #regions are computed its complement will be taken. - region_cycles = [] + # List of points in R^3 + vertices = [] + # List of pairs of indices of vertices + edges = [] + # List of tuples of indices of vertices. The last should + # be a cycle whose interior is the entire graph, and when + # regions are computed its complement will be taken. + region_cycles = [] self.construct() @@ -30,18 +28,20 @@ class Graph(): def __str__(self): return self.__class__.__name__ + class CubeGraph(Graph): """ 5 7 - 12 - 03 + 12 + 03 4 6 """ + def construct(self): self.vertices = [ (x, y, 0) for r in (1, 2) - for x, y in it.product([-r,r], [-r, r]) + for x, y in it.product([-r, r], [-r, r]) ] self.edges = [ (0, 1), @@ -63,9 +63,10 @@ class CubeGraph(Graph): [4, 6, 2, 0], [6, 7, 3, 2], [7, 5, 1, 3], - [4, 6, 7, 5],#By convention, last region will be "outside" + [4, 6, 7, 5], # By convention, last region will be "outside" ] + class SampleGraph(Graph): """ 4 2 3 8 @@ -73,17 +74,18 @@ class SampleGraph(Graph): 7 5 6 """ + def construct(self): self.vertices = [ - ( 0, 0, 0), - ( 2, 0, 0), - ( 1, 2, 0), - ( 3, 2, 0), + (0, 0, 0), + (2, 0, 0), + (1, 2, 0), + (3, 2, 0), (-1, 2, 0), - (-2,-2, 0), - ( 2,-2, 0), - ( 4,-1, 0), - ( 6, 2, 0), + (-2, -2, 0), + (2, -2, 0), + (4, -1, 0), + (6, 2, 0), ] self.edges = [ (0, 1), @@ -113,19 +115,21 @@ class SampleGraph(Graph): (4, 5, 6, 7, 8, 3, 2), ] + class OctohedronGraph(Graph): """ 3 - + 1 0 2 4 5 """ + def construct(self): self.vertices = [ - (r*np.cos(angle), r*np.sin(angle)-1, 0) + (r * np.cos(angle), r * np.sin(angle) - 1, 0) for r, s in [(1, 0), (3, 3)] - for angle in (np.pi/6) * np.array([s, 4 + s, 8 + s]) + for angle in (np.pi / 6) * np.array([s, 4 + s, 8 + s]) ] self.edges = [ (0, 1), @@ -152,35 +156,38 @@ class OctohedronGraph(Graph): (3, 4, 5), ] + class CompleteGraph(Graph): - def __init__(self, num_vertices, radius = 3): + def __init__(self, num_vertices, radius=3): self.num_vertices = num_vertices self.radius = radius Graph.__init__(self) def construct(self): self.vertices = [ - (self.radius*np.cos(theta), self.radius*np.sin(theta), 0) + (self.radius * np.cos(theta), self.radius * np.sin(theta), 0) for x in range(self.num_vertices) - for theta in [2*np.pi*x / self.num_vertices] + for theta in [2 * np.pi * x / self.num_vertices] ] self.edges = it.combinations(range(self.num_vertices), 2) def __str__(self): return Graph.__str__(self) + str(self.num_vertices) + class GraphScene(Scene): args_list = [ (CubeGraph(),), (SampleGraph(),), (OctohedronGraph(),), ] + @staticmethod def args_to_string(*args): return str(args[0]) def __init__(self, graph, *args, **kwargs): - #See CubeGraph() above for format of graph + # See CubeGraph() above for format of graph self.graph = graph Scene.__init__(self, *args, **kwargs) @@ -198,19 +205,19 @@ class GraphScene(Scene): self.region_from_cycle(cycle) for cycle in self.graph.region_cycles ] - regions[-1].complement()#Outer region painted outwardly... + regions[-1].complement() # Outer region painted outwardly... self.regions = regions def region_from_cycle(self, cycle): point_pairs = [ [ - self.points[cycle[i]], - self.points[cycle[(i+1)%len(cycle)]] + self.points[cycle[i]], + self.points[cycle[(i + 1) % len(cycle)]] ] for i in range(len(cycle)) ] return region_from_line_boundary( - *point_pairs, shape = self.shape + *point_pairs, shape=self.shape ) def draw_vertices(self, **kwargs): @@ -219,7 +226,7 @@ class GraphScene(Scene): def draw_edges(self): self.play(*[ - ShowCreation(edge, run_time = 1.0) + ShowCreation(edge, run_time=1.0) for edge in self.edges ]) @@ -227,17 +234,16 @@ class GraphScene(Scene): self.remove(*self.vertices) start = Mobject(*self.vertices) end = Mobject(*[ - Dot(point, radius = 3*Dot.DEFAULT_RADIUS, color = "lightgreen") + Dot(point, radius=3 * Dot.DEFAULT_RADIUS, color="lightgreen") for point in self.points ]) self.play(Transform( - start, end, rate_func = there_and_back, + start, end, rate_func=there_and_back, **kwargs )) self.remove(start) self.add(*self.vertices) - def replace_vertices_with(self, mobject): mobject.center() diameter = max(mobject.get_height(), mobject.get_width()) @@ -255,7 +261,7 @@ class GraphScene(Scene): for edge in self.edges ]) - def annotate_edges(self, mobject, fade_in = True, **kwargs): + def annotate_edges(self, mobject, fade_in=True, **kwargs): angles = map(np.arctan, map(Line.get_slope, self.edges)) self.edge_annotations = [ mobject.copy().rotate(angle).move_to(edge.get_center()) @@ -267,22 +273,22 @@ class GraphScene(Scene): for ann in self.edge_annotations ]) - def trace_cycle(self, cycle = None, color = "yellow", run_time = 2.0): + def trace_cycle(self, cycle=None, color="yellow", run_time=2.0): if cycle == None: cycle = self.graph.region_cycles[0] time_per_edge = run_time / len(cycle) next_in_cycle = it.cycle(cycle) - next_in_cycle.next()#jump one ahead + next_in_cycle.next() # jump one ahead self.traced_cycle = Mobject(*[ Line(self.points[i], self.points[j]).set_color(color) for i, j in zip(cycle, next_in_cycle) ]) self.play( - ShowCreation(self.traced_cycle), - run_time = run_time + ShowCreation(self.traced_cycle), + run_time=run_time ) - def generate_spanning_tree(self, root = 0, color = "yellow"): + def generate_spanning_tree(self, root=0, color="yellow"): self.spanning_tree_root = 0 pairs = deepcopy(self.graph.edges) pairs += [tuple(reversed(pair)) for pair in pairs] @@ -311,10 +317,10 @@ class GraphScene(Scene): y_sep = 2 if not hasattr(self, "spanning_tree"): self.generate_spanning_tree() - root = self.spanning_tree_root + root = self.spanning_tree_root color = self.spanning_tree.get_color() indices = range(len(self.points)) - #Build dicts + # Build dicts parent_of = dict([ tuple(reversed(pair)) for pair in self.spanning_tree_index_pairs @@ -323,12 +329,12 @@ class GraphScene(Scene): for child in parent_of: children_of[parent_of[child]].append(child) - x_coord_of = {root : 0} - y_coord_of = {root : bottom} - #width to allocate to a given node, computed as - #the maxium number of decendents in a single generation, - #minus 1, multiplied by x_sep - width_of = {} + x_coord_of = {root: 0} + y_coord_of = {root: bottom} + # width to allocate to a given node, computed as + # the maxium number of decendents in a single generation, + # minus 1, multiplied by x_sep + width_of = {} for index in indices: next_generation = children_of[index] curr_max = max(1, len(next_generation)) @@ -345,9 +351,9 @@ class GraphScene(Scene): if index not in y_coord_of: y_coord_of[index] = y_sep + y_coord_of[parent_of[index]] children = children_of[index] - left_hand = x_coord_of[index]-width_of[index]/2.0 + left_hand = x_coord_of[index] - width_of[index] / 2.0 for child in children: - x_coord_of[child] = left_hand + width_of[child]/2.0 + x_coord_of[child] = left_hand + width_of[child] / 2.0 left_hand += width_of[child] + x_sep to_process += children @@ -365,7 +371,7 @@ class GraphScene(Scene): ]) def generate_dual_graph(self): - point_at_infinity = np.array([np.inf]*3) + point_at_infinity = np.array([np.inf] * 3) cycles = self.graph.region_cycles self.dual_points = [ center_of_mass([ @@ -378,7 +384,8 @@ class GraphScene(Scene): Dot(point).set_color("green") for point in self.dual_points ] - self.dual_vertices[-1] = Circle().scale(FRAME_X_RADIUS + FRAME_Y_RADIUS) + self.dual_vertices[-1] = Circle().scale(FRAME_X_RADIUS + + FRAME_Y_RADIUS) self.dual_points[-1] = point_at_infinity self.dual_edges = [] @@ -388,43 +395,20 @@ class GraphScene(Scene): if not (pair[0] in cycle and pair[1] in cycle): continue index1, index2 = cycle.index(pair[0]), cycle.index(pair[1]) - if abs(index1 - index2) in [1, len(cycle)-1]: + if abs(index1 - index2) in [1, len(cycle) - 1]: dual_point_pair.append( self.dual_points[cycles.index(cycle)] ) assert(len(dual_point_pair) == 2) for i in 0, 1: if all(dual_point_pair[i] == point_at_infinity): - new_point = np.array(dual_point_pair[1-i]) + new_point = np.array(dual_point_pair[1 - i]) vect = center_of_mass([ self.points[pair[0]], self.points[pair[1]] ]) - new_point - new_point += FRAME_X_RADIUS*vect/np.linalg.norm(vect) + new_point += FRAME_X_RADIUS * vect / np.linalg.norm(vect) dual_point_pair[i] = new_point self.dual_edges.append( Line(*dual_point_pair).set_color() ) - - - - - - - - - - - - - - - - - - - - - - - diff --git a/once_useful_constructs/light.py b/once_useful_constructs/light.py index b5462630..841dd7bf 100644 --- a/once_useful_constructs/light.py +++ b/once_useful_constructs/light.py @@ -1,34 +1,22 @@ from constants import * from mobject.geometry import AnnularSector -from mobject.geometry import Arc from mobject.geometry import Annulus from mobject.mobject import Mobject from mobject.svg.svg_mobject import SVGMobject -from mobject.svg.tex_mobject import TexMobject -from mobject.types.vectorized_mobject import VGroup from mobject.types.vectorized_mobject import VMobject from mobject.types.vectorized_mobject import VectorizedPoint from continual_animation.continual_animation import ContinualAnimation -from animation.animation import Animation from animation.composition import LaggedStart -from animation.transform import ApplyMethod from animation.transform import Transform from animation.creation import FadeIn from animation.creation import FadeOut -from camera.camera import Camera -from scene.scene import Scene -from camera.three_d_camera import ThreeDCamera -from scene.three_d_scene import ThreeDScene - -from utils.space_ops import angle_between from utils.space_ops import angle_between_vectors from utils.space_ops import project_along_vector from utils.space_ops import rotate_vector -from utils.space_ops import rotation_matrix from utils.space_ops import z_to_vector from scipy.spatial import ConvexHull @@ -39,8 +27,8 @@ SHADOW_COLOR = BLACK SWITCH_ON_RUN_TIME = 1.5 FAST_SWITCH_ON_RUN_TIME = 0.1 NUM_LEVELS = 30 -NUM_CONES = 7 # in first lighthouse scene -NUM_VISIBLE_CONES = 5 # ibidem +NUM_CONES = 7 # in first lighthouse scene +NUM_VISIBLE_CONES = 5 # ibidem ARC_TIP_LENGTH = 0.2 AMBIENT_FULL = 0.8 AMBIENT_DIMMED = 0.5 @@ -48,11 +36,15 @@ SPOTLIGHT_FULL = 0.8 SPOTLIGHT_DIMMED = 0.5 LIGHTHOUSE_HEIGHT = 0.8 -DEGREES = TAU/360 +DEGREES = TAU / 360 -inverse_power_law = lambda maxint,scale,cutoff,exponent: \ - (lambda r: maxint * (cutoff/(r/scale+cutoff))**exponent) -inverse_quadratic = lambda maxint,scale,cutoff: inverse_power_law(maxint,scale,cutoff,2) + +def inverse_power_law(maxint, scale, cutoff, exponent): + return (lambda r: maxint * (cutoff / (r / scale + cutoff))**exponent) + + +def inverse_quadratic(maxint, scale, cutoff): + return inverse_power_law(maxint, scale, cutoff, 2) class SwitchOn(LaggedStart): @@ -62,12 +54,14 @@ class SwitchOn(LaggedStart): } def __init__(self, light, **kwargs): - if (not isinstance(light,AmbientLight) and not isinstance(light,Spotlight)): - raise Exception("Only AmbientLights and Spotlights can be switched on") + if (not isinstance(light, AmbientLight) and not isinstance(light, Spotlight)): + raise Exception( + "Only AmbientLights and Spotlights can be switched on") LaggedStart.__init__( self, FadeIn, light, **kwargs ) + class SwitchOff(LaggedStart): CONFIG = { "lag_ratio": 0.2, @@ -75,23 +69,26 @@ class SwitchOff(LaggedStart): } def __init__(self, light, **kwargs): - if (not isinstance(light,AmbientLight) and not isinstance(light,Spotlight)): - raise Exception("Only AmbientLights and Spotlights can be switched off") + if (not isinstance(light, AmbientLight) and not isinstance(light, Spotlight)): + raise Exception( + "Only AmbientLights and Spotlights can be switched off") light.submobjects = light.submobjects[::-1] LaggedStart.__init__(self, - FadeOut, light, **kwargs) + FadeOut, light, **kwargs) light.submobjects = light.submobjects[::-1] + class Lighthouse(SVGMobject): CONFIG = { - "file_name" : "lighthouse", - "height" : LIGHTHOUSE_HEIGHT, - "fill_color" : WHITE, - "fill_opacity" : 1.0, + "file_name": "lighthouse", + "height": LIGHTHOUSE_HEIGHT, + "fill_color": WHITE, + "fill_opacity": 1.0, } - def move_to(self,point): - self.next_to(point, DOWN, buff = 0) + def move_to(self, point): + self.next_to(point, DOWN, buff=0) + class AmbientLight(VMobject): @@ -104,18 +101,18 @@ class AmbientLight(VMobject): # * the number of subdivisions (levels, annuli) CONFIG = { - "source_point": VectorizedPoint(location = ORIGIN, stroke_width = 0, fill_opacity = 0), - "opacity_function" : lambda r : 1.0/(r+1.0)**2, - "color" : LIGHT_COLOR, - "max_opacity" : 1.0, - "num_levels" : NUM_LEVELS, - "radius" : 5.0 + "source_point": VectorizedPoint(location=ORIGIN, stroke_width=0, fill_opacity=0), + "opacity_function": lambda r: 1.0 / (r + 1.0)**2, + "color": LIGHT_COLOR, + "max_opacity": 1.0, + "num_levels": NUM_LEVELS, + "radius": 5.0 } def generate_points(self): # in theory, this method is only called once, right? # so removing submobs shd not be necessary - # + # # Note: Usually, yes, it is only called within Mobject.__init__, # but there is no strong guarantee of that, and you may want certain # update functions to regenerate points here and there. @@ -130,71 +127,61 @@ class AmbientLight(VMobject): for r in np.arange(0, self.radius, dr): alpha = self.max_opacity * self.opacity_function(r) annulus = Annulus( - inner_radius = r, - outer_radius = r + dr, - color = self.color, - fill_opacity = alpha + inner_radius=r, + outer_radius=r + dr, + color=self.color, + fill_opacity=alpha ) annulus.move_to(self.get_source_point()) self.add(annulus) - - - def move_source_to(self,point): - #old_source_point = self.get_source_point() - #self.shift(point - old_source_point) + def move_source_to(self, point): + # old_source_point = self.get_source_point() + # self.shift(point - old_source_point) self.move_to(point) return self - - - - def get_source_point(self): return self.source_point.get_location() - - - - - - - def dimming(self,new_alpha): + def dimming(self, new_alpha): old_alpha = self.max_opacity self.max_opacity = new_alpha for submob in self.submobjects: old_submob_alpha = submob.fill_opacity new_submob_alpha = old_submob_alpha * new_alpha / old_alpha - submob.set_fill(opacity = new_submob_alpha) + submob.set_fill(opacity=new_submob_alpha) + class Spotlight(VMobject): CONFIG = { - "source_point": VectorizedPoint(location = ORIGIN, stroke_width = 0, fill_opacity = 0), - "opacity_function" : lambda r : 1.0/(r/2+1.0)**2, - "color" : GREEN, # LIGHT_COLOR, - "max_opacity" : 1.0, - "num_levels" : 10, - "radius" : 10.0, - "screen" : None, + "source_point": VectorizedPoint(location=ORIGIN, stroke_width=0, fill_opacity=0), + "opacity_function": lambda r: 1.0 / (r / 2 + 1.0)**2, + "color": GREEN, # LIGHT_COLOR, + "max_opacity": 1.0, + "num_levels": 10, + "radius": 10.0, + "screen": None, "camera_mob": None } def projection_direction(self): # Note: This seems reasonable, though for it to work you'd - # need to be sure that any 3d scene including a spotlight - # somewhere assigns that spotlights "camera" attribute + # need to be sure that any 3d scene including a spotlight + # somewhere assigns that spotlights "camera" attribute # to be the camera associated with that scene. - if self.camera_mob == None: + if self.camera_mob is None: return OUT else: [phi, theta, r] = self.camera_mob.get_center() - v = np.array([np.sin(phi)*np.cos(theta), np.sin(phi)*np.sin(theta), np.cos(phi)]) - return v #/np.linalg.norm(v) + v = np.array([np.sin(phi) * np.cos(theta), + np.sin(phi) * np.sin(theta), np.cos(phi)]) + return v # /np.linalg.norm(v) - def project(self,point): + def project(self, point): v = self.projection_direction() - w = project_along_vector(point,v) + w = project_along_vector(point, v) return w def get_source_point(self): @@ -205,7 +192,7 @@ class Spotlight(VMobject): self.add(self.source_point) - if self.screen != None: + if self.screen is not None: # look for the screen and create annular sectors lower_angle, upper_angle = self.viewing_angles(self.screen) self.radius = float(self.radius) @@ -213,18 +200,18 @@ class Spotlight(VMobject): lower_ray, upper_ray = self.viewing_rays(self.screen) for r in np.arange(0, self.radius, dr): - new_sector = self.new_sector(r,dr,lower_angle,upper_angle) + new_sector = self.new_sector(r, dr, lower_angle, upper_angle) self.add(new_sector) - def new_sector(self,r,dr,lower_angle,upper_angle): + def new_sector(self, r, dr, lower_angle, upper_angle): alpha = self.max_opacity * self.opacity_function(r) annular_sector = AnnularSector( - inner_radius = r, - outer_radius = r + dr, - color = self.color, - fill_opacity = alpha, - start_angle = lower_angle, - angle = upper_angle - lower_angle + inner_radius=r, + outer_radius=r + dr, + color=self.color, + fill_opacity=alpha, + start_angle=lower_angle, + angle=upper_angle - lower_angle ) # rotate (not project) it into the viewing plane rotation_matrix = z_to_vector(self.projection_direction()) @@ -232,13 +219,13 @@ class Spotlight(VMobject): # now rotate it inside that plane rotated_RIGHT = np.dot(RIGHT, rotation_matrix.T) projected_RIGHT = self.project(RIGHT) - omega = angle_between_vectors(rotated_RIGHT,projected_RIGHT) - annular_sector.rotate(omega, axis = self.projection_direction()) + omega = angle_between_vectors(rotated_RIGHT, projected_RIGHT) + annular_sector.rotate(omega, axis=self.projection_direction()) annular_sector.move_arc_center_to(self.get_source_point()) return annular_sector - def viewing_angle_of_point(self,point): + def viewing_angle_of_point(self, point): # as measured from the positive x-axis v1 = self.project(RIGHT) v2 = self.project(np.array(point) - self.get_source_point()) @@ -247,63 +234,66 @@ class Spotlight(VMobject): # choice of orientation. That choice is set by the camera # position, i. e. projection direction - if np.dot(self.projection_direction(),np.cross(v1, v2)) > 0: + if np.dot(self.projection_direction(), np.cross(v1, v2)) > 0: return absolute_angle else: return -absolute_angle - def viewing_angles(self,screen): + def viewing_angles(self, screen): screen_points = screen.get_anchors() - projected_screen_points = map(self.project,screen_points) + projected_screen_points = map(self.project, screen_points) viewing_angles = np.array(map(self.viewing_angle_of_point, - projected_screen_points)) + projected_screen_points)) lower_angle = upper_angle = 0 if len(viewing_angles) != 0: lower_angle = np.min(viewing_angles) upper_angle = np.max(viewing_angles) - if upper_angle - lower_angle > TAU/2: + if upper_angle - lower_angle > TAU / 2: lower_angle, upper_angle = upper_angle, lower_angle + TAU return lower_angle, upper_angle - def viewing_rays(self,screen): + def viewing_rays(self, screen): lower_angle, upper_angle = self.viewing_angles(screen) - projected_RIGHT = self.project(RIGHT)/np.linalg.norm(self.project(RIGHT)) - lower_ray = rotate_vector(projected_RIGHT,lower_angle, axis = self.projection_direction()) - upper_ray = rotate_vector(projected_RIGHT,upper_angle, axis = self.projection_direction()) - + projected_RIGHT = self.project( + RIGHT) / np.linalg.norm(self.project(RIGHT)) + lower_ray = rotate_vector( + projected_RIGHT, lower_angle, axis=self.projection_direction()) + upper_ray = rotate_vector( + projected_RIGHT, upper_angle, axis=self.projection_direction()) + return lower_ray, upper_ray def opening_angle(self): - l,u = self.viewing_angles(self.screen) + l, u = self.viewing_angles(self.screen) return u - l def start_angle(self): - l,u = self.viewing_angles(self.screen) + l, u = self.viewing_angles(self.screen) return l def stop_angle(self): - l,u = self.viewing_angles(self.screen) + l, u = self.viewing_angles(self.screen) return u - def move_source_to(self,point): + def move_source_to(self, point): self.source_point.set_location(np.array(point)) - #self.source_point.move_to(np.array(point)) - #self.move_to(point) + # self.source_point.move_to(np.array(point)) + # self.move_to(point) self.update_sectors() return self def update_sectors(self): - if self.screen == None: + if self.screen is None: return for submob in self.submobjects: if type(submob) == AnnularSector: lower_angle, upper_angle = self.viewing_angles(self.screen) - #dr = submob.outer_radius - submob.inner_radius + # dr = submob.outer_radius - submob.inner_radius dr = self.radius / self.num_levels new_submob = self.new_sector( submob.inner_radius, dr, lower_angle, upper_angle @@ -312,7 +302,7 @@ class Spotlight(VMobject): # submob.set_fill(opacity = 10 * self.opacity_function(submob.outer_radius)) Transform(submob, new_submob).update(1) - def dimming(self,new_alpha): + def dimming(self, new_alpha): old_alpha = self.max_opacity self.max_opacity = new_alpha for submob in self.submobjects: @@ -322,26 +312,28 @@ class Spotlight(VMobject): # it's the shadow, don't dim it continue old_submob_alpha = submob.fill_opacity - new_submob_alpha = old_submob_alpha * new_alpha/old_alpha - submob.set_fill(opacity = new_submob_alpha) + new_submob_alpha = old_submob_alpha * new_alpha / old_alpha + submob.set_fill(opacity=new_submob_alpha) - def change_opacity_function(self,new_f): + def change_opacity_function(self, new_f): self.opacity_function = new_f - dr = self.radius/self.num_levels + dr = self.radius / self.num_levels sectors = [] for submob in self.submobjects: if type(submob) == AnnularSector: sectors.append(submob) - for (r,submob) in zip(np.arange(0,self.radius,dr),sectors): + for (r, submob) in zip(np.arange(0, self.radius, dr), sectors): if type(submob) != AnnularSector: # it's the shadow, don't dim it continue alpha = self.opacity_function(r) - submob.set_fill(opacity = alpha) + submob.set_fill(opacity=alpha) # Warning: This class is likely quite buggy. + + class LightSource(VMobject): # combines: # a lighthouse @@ -349,12 +341,12 @@ class LightSource(VMobject): # a spotlight # and a shadow CONFIG = { - "source_point": VectorizedPoint(location = ORIGIN, stroke_width = 0, fill_opacity = 0), + "source_point": VectorizedPoint(location=ORIGIN, stroke_width=0, fill_opacity=0), "color": LIGHT_COLOR, "num_levels": 10, "radius": 10.0, "screen": None, - "opacity_function": inverse_quadratic(1,2,1), + "opacity_function": inverse_quadratic(1, 2, 1), "max_opacity_ambient": AMBIENT_FULL, "max_opacity_spotlight": SPOTLIGHT_FULL, "camera_mob": None @@ -366,39 +358,41 @@ class LightSource(VMobject): self.lighthouse = Lighthouse() self.ambient_light = AmbientLight( - source_point = VectorizedPoint(location = self.get_source_point()), - color = self.color, - num_levels = self.num_levels, - radius = self.radius, - opacity_function = self.opacity_function, - max_opacity = self.max_opacity_ambient + source_point=VectorizedPoint(location=self.get_source_point()), + color=self.color, + num_levels=self.num_levels, + radius=self.radius, + opacity_function=self.opacity_function, + max_opacity=self.max_opacity_ambient ) if self.has_screen(): self.spotlight = Spotlight( - source_point = VectorizedPoint(location = self.get_source_point()), - color = self.color, - num_levels = self.num_levels, - radius = self.radius, - screen = self.screen, - opacity_function = self.opacity_function, - max_opacity = self.max_opacity_spotlight, - camera_mob = self.camera_mob + source_point=VectorizedPoint(location=self.get_source_point()), + color=self.color, + num_levels=self.num_levels, + radius=self.radius, + screen=self.screen, + opacity_function=self.opacity_function, + max_opacity=self.max_opacity_spotlight, + camera_mob=self.camera_mob ) else: self.spotlight = Spotlight() - self.shadow = VMobject(fill_color = SHADOW_COLOR, fill_opacity = 1.0, stroke_color = BLACK) - self.lighthouse.next_to(self.get_source_point(),DOWN,buff = 0) + self.shadow = VMobject(fill_color=SHADOW_COLOR, + fill_opacity=1.0, stroke_color=BLACK) + self.lighthouse.next_to(self.get_source_point(), DOWN, buff=0) self.ambient_light.move_source_to(self.get_source_point()) if self.has_screen(): self.spotlight.move_source_to(self.get_source_point()) self.update_shadow() - self.add(self.ambient_light,self.spotlight,self.lighthouse, self.shadow) + self.add(self.ambient_light, self.spotlight, + self.lighthouse, self.shadow) def has_screen(self): - if self.screen == None: + if self.screen is None: return False elif np.size(self.screen.points) == 0: return False @@ -408,18 +402,18 @@ class LightSource(VMobject): def dim_ambient(self): self.set_max_opacity_ambient(AMBIENT_DIMMED) - def set_max_opacity_ambient(self,new_opacity): + def set_max_opacity_ambient(self, new_opacity): self.max_opacity_ambient = new_opacity self.ambient_light.dimming(new_opacity) def dim_spotlight(self): self.set_max_opacity_spotlight(SPOTLIGHT_DIMMED) - def set_max_opacity_spotlight(self,new_opacity): + def set_max_opacity_spotlight(self, new_opacity): self.max_opacity_spotlight = new_opacity self.spotlight.dimming(new_opacity) - def set_camera_mob(self,new_cam_mob): + def set_camera_mob(self, new_cam_mob): self.camera_mob = new_cam_mob self.spotlight.camera_mob = new_cam_mob @@ -432,40 +426,40 @@ class LightSource(VMobject): camera_mob = self.spotlight.camera_mob self.remove(self.spotlight) self.spotlight = Spotlight( - source_point = VectorizedPoint(location = self.get_source_point()), - color = self.color, - num_levels = self.num_levels, - radius = self.radius, - screen = new_screen, - camera_mob = self.camera_mob, - opacity_function = self.opacity_function, - max_opacity = self.max_opacity_spotlight, + source_point=VectorizedPoint(location=self.get_source_point()), + color=self.color, + num_levels=self.num_levels, + radius=self.radius, + screen=new_screen, + camera_mob=self.camera_mob, + opacity_function=self.opacity_function, + max_opacity=self.max_opacity_spotlight, ) self.spotlight.move_source_to(self.get_source_point()) # Note: This line will make spotlight show up at the end # of the submojects list, which can make it show up on # top of the shadow. To make it show up in the - # same spot, you could try the following line, + # same spot, you could try the following line, # where "index" is what I defined above: self.submobjects.insert(index, self.spotlight) - #self.add(self.spotlight) - + # self.add(self.spotlight) + # in any case self.screen = new_screen - def move_source_to(self,point): + def move_source_to(self, point): apoint = np.array(point) v = apoint - self.get_source_point() # Note: As discussed, things stand to behave better if source # point is a submobject, so that it automatically interpolates - # during an animation, and other updates can be defined wrt + # during an animation, and other updates can be defined wrt # that source point's location self.source_point.set_location(apoint) - #self.lighthouse.next_to(apoint,DOWN,buff = 0) - #self.ambient_light.move_source_to(apoint) + # self.lighthouse.next_to(apoint,DOWN,buff = 0) + # self.ambient_light.move_source_to(apoint) self.lighthouse.shift(v) - #self.ambient_light.shift(v) + # self.ambient_light.shift(v) self.ambient_light.move_source_to(apoint) if self.has_screen(): self.spotlight.move_source_to(apoint) @@ -474,8 +468,8 @@ class LightSource(VMobject): def change_spotlight_opacity_function(self, new_of): self.spotlight.change_opacity_function(new_of) - - def set_radius(self,new_radius): + + def set_radius(self, new_radius): self.radius = new_radius self.ambient_light.radius = new_radius self.spotlight.radius = new_radius @@ -496,12 +490,12 @@ class LightSource(VMobject): def update_ambient(self): new_ambient_light = AmbientLight( - source_point = VectorizedPoint(location = ORIGIN), - color = self.color, - num_levels = self.num_levels, - radius = self.radius, - opacity_function = self.opacity_function, - max_opacity = self.max_opacity_ambient + source_point=VectorizedPoint(location=ORIGIN), + color=self.color, + num_levels=self.num_levels, + radius=self.radius, + opacity_function=self.opacity_function, + max_opacity=self.max_opacity_ambient ) new_ambient_light.apply_matrix(self.rotation_matrix()) new_ambient_light.move_source_to(self.get_source_point()) @@ -512,13 +506,12 @@ class LightSource(VMobject): def rotation_matrix(self): - if self.camera_mob == None: + if self.camera_mob is None: return np.eye(3) phi = self.camera_mob.get_center()[0] theta = self.camera_mob.get_center()[1] - R1 = np.array([ [1, 0, 0], [0, np.cos(phi), -np.sin(phi)], @@ -526,8 +519,8 @@ class LightSource(VMobject): ]) R2 = np.array([ - [np.cos(theta + TAU/4), -np.sin(theta + TAU/4), 0], - [np.sin(theta + TAU/4), np.cos(theta + TAU/4), 0], + [np.cos(theta + TAU / 4), -np.sin(theta + TAU / 4), 0], + [np.sin(theta + TAU / 4), np.cos(theta + TAU / 4), 0], [0, 0, 1] ]) @@ -542,28 +535,31 @@ class LightSource(VMobject): for point in self.screen.get_anchors(): projected_screen_points.append(self.spotlight.project(point)) - - projected_source = project_along_vector(self.get_source_point(),self.spotlight.projection_direction()) + projected_source = project_along_vector( + self.get_source_point(), self.spotlight.projection_direction()) projected_point_cloud_3d = np.append( projected_screen_points, - np.reshape(projected_source,(1,3)), - axis = 0 + np.reshape(projected_source, (1, 3)), + axis=0 ) - rotation_matrix = self.rotation_matrix() # z_to_vector(self.spotlight.projection_direction()) - back_rotation_matrix = rotation_matrix.T # i. e. its inverse + # z_to_vector(self.spotlight.projection_direction()) + rotation_matrix = self.rotation_matrix() + back_rotation_matrix = rotation_matrix.T # i. e. its inverse - rotated_point_cloud_3d = np.dot(projected_point_cloud_3d,back_rotation_matrix.T) + rotated_point_cloud_3d = np.dot( + projected_point_cloud_3d, back_rotation_matrix.T) # these points now should all have z = 0 - point_cloud_2d = rotated_point_cloud_3d[:,:2] + point_cloud_2d = rotated_point_cloud_3d[:, :2] # now we can compute the convex hull - hull_2d = ConvexHull(point_cloud_2d) # guaranteed to run ccw + hull_2d = ConvexHull(point_cloud_2d) # guaranteed to run ccw hull = [] # we also need the projected source point - source_point_2d = np.dot(self.spotlight.project(self.get_source_point()),back_rotation_matrix.T)[:2] - + source_point_2d = np.dot(self.spotlight.project( + self.get_source_point()), back_rotation_matrix.T)[:2] + index = 0 for point in point_cloud_2d[hull_2d.vertices]: if np.all(np.abs(point - source_point_2d) < 1.0e-6): @@ -574,35 +570,35 @@ class LightSource(VMobject): hull.append(point_3d) index += 1 - hull_mobject = VMobject() hull_mobject.set_points_as_corners(hull) hull_mobject.apply_matrix(rotation_matrix) - anchors = hull_mobject.get_anchors() # add two control points for the outer cone - if np.size(anchors) == 0: + if np.size(anchors) == 0: self.shadow.points = [] return ray1 = anchors[source_index - 1] - projected_source - ray1 = ray1/np.linalg.norm(ray1) * 100 + ray1 = ray1 / np.linalg.norm(ray1) * 100 ray2 = anchors[source_index] - projected_source - ray2 = ray2/np.linalg.norm(ray2) * 100 + ray2 = ray2 / np.linalg.norm(ray2) * 100 outpoint1 = anchors[source_index - 1] + ray1 outpoint2 = anchors[source_index] + ray2 new_anchors = anchors[:source_index] - new_anchors = np.append(new_anchors,np.array([outpoint1, outpoint2]),axis = 0) - new_anchors = np.append(new_anchors,anchors[source_index:],axis = 0) + new_anchors = np.append(new_anchors, np.array( + [outpoint1, outpoint2]), axis=0) + new_anchors = np.append(new_anchors, anchors[source_index:], axis=0) self.shadow.set_points_as_corners(new_anchors) # shift it closer to the camera so it is in front of the spotlight self.shadow.mark_paths_closed = True + class ScreenTracker(ContinualAnimation): def __init__(self, light_source, **kwargs): self.light_source = light_source @@ -611,20 +607,3 @@ class ScreenTracker(ContinualAnimation): def update_mobject(self, dt): self.light_source.update() - - - - - - - - - - - - - - - - - diff --git a/once_useful_constructs/matrix_multiplication.py b/once_useful_constructs/matrix_multiplication.py index a7067cb5..45fe59ee 100644 --- a/once_useful_constructs/matrix_multiplication.py +++ b/once_useful_constructs/matrix_multiplication.py @@ -1,5 +1,7 @@ import numpy as np +from constants import * + from animation.creation import ShowCreation from animation.transform import ApplyMethod from animation.creation import FadeOut @@ -11,12 +13,14 @@ from scene.scene import Scene from mobject.geometry import Circle from mobject.geometry import Line + class NumericalMatrixMultiplication(Scene): CONFIG = { - "left_matrix" : [[1, 2], [3, 4]], - "right_matrix" : [[5, 6], [7, 8]], - "use_parens" : True, + "left_matrix": [[1, 2], [3, 4]], + "right_matrix": [[5, 6], [7, 8]], + "use_parens": True, } + def construct(self): left_string_matrix, right_string_matrix = [ np.array(matrix).astype("string") @@ -34,25 +38,24 @@ class NumericalMatrixMultiplication(Scene): self.organize_matrices(left, right, result) self.animate_product(left, right, result) - def get_result_matrix(self, left, right): (m, k), n = left.shape, right.shape[1] - mob_matrix = np.array([VGroup()]).repeat(m*n).reshape((m, n)) + mob_matrix = np.array([VGroup()]).repeat(m * n).reshape((m, n)) for a in range(m): for b in range(n): template = "(%s)(%s)" if self.use_parens else "%s%s" parts = [ - prefix + template%(left[a][c], right[c][b]) + prefix + template % (left[a][c], right[c][b]) for c in range(k) for prefix in ["" if c == 0 else "+"] ] - mob_matrix[a][b] = TexMobject(parts, next_to_buff = 0.1) + mob_matrix[a][b] = TexMobject(parts, next_to_buff=0.1) return Matrix(mob_matrix) def add_lines(self, left, right): line_kwargs = { - "color" : BLUE, - "stroke_width" : 2, + "color": BLUE, + "stroke_width": 2, } left_rows = [ VGroup(*row) for row in left.get_mob_matrix() @@ -60,7 +63,7 @@ class NumericalMatrixMultiplication(Scene): h_lines = VGroup() for row in left_rows[:-1]: h_line = Line(row.get_left(), row.get_right(), **line_kwargs) - h_line.next_to(row, DOWN, buff = left.v_buff/2.) + h_line.next_to(row, DOWN, buff=left.v_buff / 2.) h_lines.add(h_line) right_cols = [ @@ -69,7 +72,7 @@ class NumericalMatrixMultiplication(Scene): v_lines = VGroup() for col in right_cols[:-1]: v_line = Line(col.get_top(), col.get_bottom(), **line_kwargs) - v_line.next_to(col, RIGHT, buff = right.h_buff/2.) + v_line.next_to(col, RIGHT, buff=right.h_buff / 2.) v_lines.add(v_line) self.play(ShowCreation(h_lines)) @@ -81,17 +84,16 @@ class NumericalMatrixMultiplication(Scene): equals = TexMobject("=") everything = VGroup(left, right, equals, result) everything.arrange_submobjects() - everything.scale_to_fit_width(FRAME_WIDTH-1) + everything.scale_to_fit_width(FRAME_WIDTH - 1) self.add(everything) - def animate_product(self, left, right, result): l_matrix = left.get_mob_matrix() r_matrix = right.get_mob_matrix() result_matrix = result.get_mob_matrix() circle = Circle( - radius = l_matrix[0][0].get_height(), - color = GREEN + radius=l_matrix[0][0].get_height(), + color=GREEN ) circles = VGroup(*[ entry.get_point_mobject() @@ -120,10 +122,10 @@ class NumericalMatrixMultiplication(Scene): self.play(Transform(circles, new_circles)) self.play( Transform( - start_parts, - result_entry.copy().set_color(YELLOW), - path_arc = -np.pi/2, - submobject_mode = "all_at_once", + start_parts, + result_entry.copy().set_color(YELLOW), + path_arc=-np.pi / 2, + submobject_mode="all_at_once", ), *lagging_anims ) @@ -137,4 +139,4 @@ class NumericalMatrixMultiplication(Scene): l_matrix[a][c].set_color(WHITE) r_matrix[c][b].set_color(WHITE) self.play(FadeOut(circles), *lagging_anims) - self.wait() \ No newline at end of file + self.wait() diff --git a/once_useful_constructs/region.py b/once_useful_constructs/region.py index 13fac5be..2507f449 100644 --- a/once_useful_constructs/region.py +++ b/once_useful_constructs/region.py @@ -1,6 +1,4 @@ -import numpy as np import itertools as it -from PIL import Image from copy import deepcopy from mobject.mobject import Mobject @@ -10,11 +8,13 @@ from constants import * # Warning: This is all now pretty depricated, and should not be expected to work + class Region(Mobject): CONFIG = { - "display_mode" : "region" + "display_mode": "region" } - def __init__(self, condition = (lambda x, y : True), **kwargs): + + def __init__(self, condition=(lambda x, y: True), **kwargs): """ Condition must be a function which takes in two real arrays (representing x and y values of space respectively) @@ -26,25 +26,26 @@ class Region(Mobject): self.condition = condition def _combine(self, region, op): - self.condition = lambda x, y : op( + self.condition = lambda x, y: op( self.condition(x, y), region.condition(x, y) ) def union(self, region): - self._combine(region, lambda bg1, bg2 : bg1 | bg2) + self._combine(region, lambda bg1, bg2: bg1 | bg2) return self def intersect(self, region): - self._combine(region, lambda bg1, bg2 : bg1 & bg2) + self._combine(region, lambda bg1, bg2: bg1 & bg2) return self def complement(self): self.bool_grid = ~self.bool_grid return self + class HalfPlane(Region): - def __init__(self, point_pair, upper_left = True, *args, **kwargs): + def __init__(self, point_pair, upper_left=True, *args, **kwargs): """ point_pair of the form [(x_0, y_0,...), (x_1, y_1,...)] @@ -56,16 +57,19 @@ class HalfPlane(Region): point_pair = list(point_pair) point_pair.reverse() (x0, y0), (x1, y1) = point_pair[0][:2], point_pair[1][:2] + def condition(x, y): - return (x1 - x0)*(y - y0) > (y1 - y0)*(x - x0) + return (x1 - x0) * (y - y0) > (y1 - y0) * (x - x0) Region.__init__(self, condition, *args, **kwargs) + def region_from_line_boundary(*lines, **kwargs): reg = Region(**kwargs) for line in lines: reg.intersect(HalfPlane(line, **kwargs)) return reg + def region_from_polygon_vertices(*vertices, **kwargs): return region_from_line_boundary(*adjacent_pairs(vertices), **kwargs) @@ -81,7 +85,7 @@ def plane_partition(*lines, **kwargs): half_planes = [HalfPlane(line, **kwargs) for line in lines] complements = [deepcopy(hp).complement() for hp in half_planes] num_lines = len(lines) - for bool_list in it.product(*[[True, False]]*num_lines): + for bool_list in it.product(*[[True, False]] * num_lines): reg = Region(**kwargs) for i in range(num_lines): if bool_list[i]: @@ -92,6 +96,7 @@ def plane_partition(*lines, **kwargs): result.append(reg) return result + def plane_partition_from_points(*points, **kwargs): """ Returns list of regions cut out by the complete graph @@ -101,9 +106,3 @@ def plane_partition_from_points(*points, **kwargs): """ lines = [[p1, p2] for (p1, p2) in it.combinations(points, 2)] return plane_partition(*lines, **kwargs) - - - - - - diff --git a/primes.py b/primes.py deleted file mode 100644 index 126a7b1b..00000000 --- a/primes.py +++ /dev/null @@ -1,252 +0,0 @@ -from big_ol_pile_of_manim_imports import * - -def is_prime(n): - - for i in primes(n**0.5): - if n % i == 0: - return False - - return True - -def primes(max_n): - - if max_n < 2: - return [] - - numbers = range(2, int(max_n) + 1) - p = [] - - while len(numbers) > 0: - q = numbers[0] - p.append(q) - numbers = [x for x in numbers if x % q != 0] - - return p - -def prime_factors(n): - - if is_prime(n): - return [n] - - i = 0 - primes_list = primes(n/2) - - factors = [] - r = n - - while r >= 2: - p = primes_list[i] - if r % p == 0: - factors.append(p) - r = r/p - else: - i += 1 - - return factors - - -RUN_TIME = 0.5 -DOWN_SHIFT = 0.0 * DOWN - -class Primes(Scene): - - def construct(self): - - N = 100 - - primes_list = np.array(primes(N)) - - palette = ["#FBA125", "#76CD42", "#30CCF5", "#9377C4", "#F95137", - # 2 3 5 7 11 - "#1B442E", TEAL_E, MAROON_A, DARK_BROWN, PINK, - # 13 17 19 23 29 - "#9C25FB", GREEN_E, MAROON_E, GOLD_E, GREEN_E, - # 31 37 41 43 47 # last prime to occur in a factorization - LIGHT_BROWN, DARK_BLUE, GREY_BROWN, GREEN_C, BLUE_C, - # 53 59 61 67 71 - PURPLE_C, RED_C, YELLOW_E, TEAL_C, MAROON_C] - # 73 79 83 89 97 - - nb_primes = len(primes_list) - print nb_primes - - prime_points_radius = 3.2 - angles = np.arange(TAU/4, -3*TAU/4, -TAU/float(nb_primes)) - print len(angles), angles - prime_points = [prime_points_radius * (np.cos(theta) * RIGHT - + np.sin(theta) * UP) - for theta in angles] - print len(prime_points) - - wheel = Wheel() - - angles = [TAU] - colors = [LIGHT_GREY] - - wheel.update_sectors(angles, colors) - wheel.rotate(-TAU/4).shift(DOWN_SHIFT) - self.add(wheel) - - number = DecimalNumber(1, num_decimal_points = 0).scale(2).shift(DOWN_SHIFT) - self.add(number) - self.wait(RUN_TIME) - - j = 0 - - for i in range(2,N+1): - - factors = prime_factors(i) - factor_indices = [np.where(primes_list == x)[0][0] for x in factors] - - nb_sectors = float(len(factor_indices)) - new_angles = np.ones(nb_sectors) / nb_sectors * TAU - - new_colors = [] - for index in factor_indices: - new_colors.append(palette[index]) - - self.play( - UpdateAngles(wheel, new_angles = new_angles, new_colors = new_colors, - run_time = RUN_TIME), - ChangeDecimalToValue(number, i, run_time = RUN_TIME) - ) - self.wait(RUN_TIME) - - if is_prime(i): - full_wheel = VGroup(wheel,number).copy() - full_wheel_copy = full_wheel.copy() - full_wheel_copy.scale(0.15).move_to(prime_points[j]) - print j - j += 1 - self.play( - Transform(full_wheel, full_wheel_copy) - ) - - - - -class Wheel(VMobject): - - CONFIG = { - "inner_radius" : 1.2, - "outer_radius" : 2.4, - "nb_sectors" : 25, - "colors" : [BLACK] * 25 - } - - def generate_points(self): - - angle = TAU/self.nb_sectors - angle_range = np.arange(0,TAU,angle) - for j in range(self.nb_sectors - len(angle_range)): - angle_range = np.append(angle_range, TAU) - self.colors.append(BLACK) - - for (i,theta) in enumerate(angle_range): - if theta != TAU: - use_angle = angle - else: - use_angle = 0 - sector = AnnularSector( - inner_radius = self.inner_radius, - outer_radius = self.outer_radius, - angle = use_angle, - start_angle = theta, - fill_color = self.colors[i], - fill_opacity = 1, - stroke_color = WHITE, - stroke_width = 5 - ).rotate_about_origin(TAU/2, axis = UP).shift(DOWN_SHIFT) - self.add(sector) - - def update_sectors(self, new_angles, new_colors): - - if len(new_angles) > self.nb_sectors: - raise "More angles than sectors!" - - for i in range(len(new_angles), self.nb_sectors): - new_angles = np.append(new_angles, 0) - new_colors.append(BLACK) - - self.colors = new_colors - - new_start_angles = -np.cumsum(new_angles) + new_angles - - for (i,sector) in enumerate(self.submobjects): - sector.angle = new_angles[i] - sector.start_angle = new_start_angles[i] - sector.set_fill(color = new_colors[i]) - sector.generate_points() - sector.rotate_about_origin(TAU/2, axis = UP).shift(DOWN_SHIFT) - - - - - -class UpdateAngles(Animation): - - def __init__(self,mobject,**kwargs): - - self.old_angles = [] - for (i, sector) in enumerate(mobject.submobjects): - self.old_angles.append(sector.angle) - - self.old_angles = np.array(self.old_angles) - self.old_start_angles = np.cumsum(self.old_angles) - self.old_angles + TAU/4 - - digest_config(self, kwargs) - Animation.__init__(self,mobject,**kwargs) - - def update_submobject(self, submobject, starting_submobject, alpha): - - i = 0 - for submob in self.mobject.submobjects: - if submobject == submob: - break - else: - i += 1 - - for j in range(len(self.new_angles), self.mobject.nb_sectors): - - self.new_angles = np.append(self.new_angles, 0) - self.new_colors.append(BLACK) - - self.new_start_angles = np.cumsum(self.new_angles) - self.new_angles + TAU/4 - # this should be in __init__! - # but has no effect there - - submobject.angle = interpolate( - self.old_angles[i], self.new_angles[i], alpha - ) - submobject.start_angle = interpolate( - self.old_start_angles[i], - self.new_start_angles[i], alpha - ) - - - interpolated_color = interpolate_color( - self.mobject.colors[i], - self.new_colors[i], - alpha - ) - - submobject.set_fill(color = interpolated_color) - submobject.generate_points() - submobject.rotate_about_origin(TAU/2, axis = UP).shift(DOWN_SHIFT) - - if alpha > 0.95: - self.mobject.colors[i] = self.new_colors[i] - - - - - - - - - - - - - - diff --git a/requirements.txt b/requirements.txt index 6695f9d6..4d9ba9a9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ +argparse==1.4.0 colour==0.1.2 numpy==1.11.0 Pillow==3.4.2 diff --git a/scene/graph_scene.py b/scene/graph_scene.py index 41b77b55..3d014d62 100644 --- a/scene/graph_scene.py +++ b/scene/graph_scene.py @@ -22,101 +22,103 @@ from utils.space_ops import angle_of_vector # TODO, this should probably reimplemented entirely, especially so as to # better reuse code from mobject/coordinate_systems + class GraphScene(Scene): CONFIG = { - "x_min" : -1, - "x_max" : 10, - "x_axis_width" : 9, - "x_tick_frequency" : 1, - "x_leftmost_tick" : None, #Change if different from x_min - "x_labeled_nums" : None, - "x_axis_label" : "$x$", - "y_min" : -1, - "y_max" : 10, - "y_axis_height" : 6, - "y_tick_frequency" : 1, - "y_bottom_tick" : None, #Change if different from y_min - "y_labeled_nums" : None, - "y_axis_label" : "$y$", - "axes_color" : GREY, - "graph_origin" : 2.5*DOWN + 4*LEFT, - "exclude_zero_label" : True, - "num_graph_anchor_points" : 25, - "default_graph_colors" : [BLUE, GREEN, YELLOW], - "default_derivative_color" : GREEN, - "default_input_color" : YELLOW, - "default_riemann_start_color" : BLUE, - "default_riemann_end_color" : GREEN, + "x_min": -1, + "x_max": 10, + "x_axis_width": 9, + "x_tick_frequency": 1, + "x_leftmost_tick": None, # Change if different from x_min + "x_labeled_nums": None, + "x_axis_label": "$x$", + "y_min": -1, + "y_max": 10, + "y_axis_height": 6, + "y_tick_frequency": 1, + "y_bottom_tick": None, # Change if different from y_min + "y_labeled_nums": None, + "y_axis_label": "$y$", + "axes_color": GREY, + "graph_origin": 2.5 * DOWN + 4 * LEFT, + "exclude_zero_label": True, + "num_graph_anchor_points": 25, + "default_graph_colors": [BLUE, GREEN, YELLOW], + "default_derivative_color": GREEN, + "default_input_color": YELLOW, + "default_riemann_start_color": BLUE, + "default_riemann_end_color": GREEN, } + def setup(self): self.default_graph_colors_cycle = it.cycle(self.default_graph_colors) - def setup_axes(self, animate = False): - ##TODO, once eoc is done, refactor this to be less redundant. + def setup_axes(self, animate=False): + # TODO, once eoc is done, refactor this to be less redundant. x_num_range = float(self.x_max - self.x_min) - self.space_unit_to_x = self.x_axis_width/x_num_range + self.space_unit_to_x = self.x_axis_width / x_num_range if self.x_labeled_nums is None: self.x_labeled_nums = [] if self.x_leftmost_tick is None: self.x_leftmost_tick = self.x_min x_axis = NumberLine( - x_min = self.x_min, - x_max = self.x_max, - unit_size = self.space_unit_to_x, - tick_frequency = self.x_tick_frequency, - leftmost_tick = self.x_leftmost_tick, - numbers_with_elongated_ticks = self.x_labeled_nums, - color = self.axes_color + x_min=self.x_min, + x_max=self.x_max, + unit_size=self.space_unit_to_x, + tick_frequency=self.x_tick_frequency, + leftmost_tick=self.x_leftmost_tick, + numbers_with_elongated_ticks=self.x_labeled_nums, + color=self.axes_color ) - x_axis.shift(self.graph_origin - x_axis.number_to_point(0)) + x_axis.shift(self.graph_origin - x_axis.number_to_point(0)) if len(self.x_labeled_nums) > 0: if self.exclude_zero_label: self.x_labeled_nums = filter( - lambda x : x != 0, + lambda x: x != 0, self.x_labeled_nums ) x_axis.add_numbers(*self.x_labeled_nums) if self.x_axis_label: x_label = TextMobject(self.x_axis_label) x_label.next_to( - x_axis.get_tick_marks(), UP+RIGHT, - buff = SMALL_BUFF + x_axis.get_tick_marks(), UP + RIGHT, + buff=SMALL_BUFF ) x_label.shift_onto_screen() x_axis.add(x_label) self.x_axis_label_mob = x_label y_num_range = float(self.y_max - self.y_min) - self.space_unit_to_y = self.y_axis_height/y_num_range + self.space_unit_to_y = self.y_axis_height / y_num_range if self.y_labeled_nums is None: self.y_labeled_nums = [] if self.y_bottom_tick is None: self.y_bottom_tick = self.y_min y_axis = NumberLine( - x_min = self.y_min, - x_max = self.y_max, - unit_size = self.space_unit_to_y, - tick_frequency = self.y_tick_frequency, - leftmost_tick = self.y_bottom_tick, - numbers_with_elongated_ticks = self.y_labeled_nums, - color = self.axes_color, - line_to_number_vect = LEFT, + x_min=self.y_min, + x_max=self.y_max, + unit_size=self.space_unit_to_y, + tick_frequency=self.y_tick_frequency, + leftmost_tick=self.y_bottom_tick, + numbers_with_elongated_ticks=self.y_labeled_nums, + color=self.axes_color, + line_to_number_vect=LEFT, ) - y_axis.shift(self.graph_origin-y_axis.number_to_point(0)) - y_axis.rotate(np.pi/2, about_point = y_axis.number_to_point(0)) + y_axis.shift(self.graph_origin - y_axis.number_to_point(0)) + y_axis.rotate(np.pi / 2, about_point=y_axis.number_to_point(0)) if len(self.y_labeled_nums) > 0: if self.exclude_zero_label: self.y_labeled_nums = filter( - lambda y : y != 0, + lambda y: y != 0, self.y_labeled_nums ) y_axis.add_numbers(*self.y_labeled_nums) if self.y_axis_label: y_label = TextMobject(self.y_axis_label) y_label.next_to( - y_axis.get_tick_marks(), UP+RIGHT, - buff = SMALL_BUFF + y_axis.get_tick_marks(), UP + RIGHT, + buff=SMALL_BUFF ) y_label.shift_onto_screen() y_axis.add(y_label) @@ -131,20 +133,20 @@ class GraphScene(Scene): def coords_to_point(self, x, y): assert(hasattr(self, "x_axis") and hasattr(self, "y_axis")) - result = self.x_axis.number_to_point(x)[0]*RIGHT - result += self.y_axis.number_to_point(y)[1]*UP + result = self.x_axis.number_to_point(x)[0] * RIGHT + result += self.y_axis.number_to_point(y)[1] * UP return result def point_to_coords(self, point): - return (self.x_axis.point_to_number(point), + return (self.x_axis.point_to_number(point), self.y_axis.point_to_number(point)) def get_graph( - self, func, - color = None, - x_min = None, - x_max = None, - ): + self, func, + color=None, + x_min=None, + x_max=None, + ): if color is None: color = self.default_graph_colors_cycle.next() if x_min is None: @@ -160,9 +162,9 @@ class GraphScene(Scene): return self.coords_to_point(x, y) graph = ParametricFunction( - parameterized_function, - color = color, - num_anchor_points = self.num_graph_anchor_points, + parameterized_function, + color=color, + num_anchor_points=self.num_graph_anchor_points, ) graph.underlying_function = func return graph @@ -170,34 +172,36 @@ class GraphScene(Scene): def input_to_graph_point(self, x, graph): return self.coords_to_point(x, graph.underlying_function(x)) - def angle_of_tangent(self, x, graph, dx = 0.01): - vect = self.input_to_graph_point(x + dx, graph) - self.input_to_graph_point(x, graph) + def angle_of_tangent(self, x, graph, dx=0.01): + vect = self.input_to_graph_point( + x + dx, graph) - self.input_to_graph_point(x, graph) return angle_of_vector(vect) def slope_of_tangent(self, *args, **kwargs): return np.tan(self.angle_of_tangent(*args, **kwargs)) - def get_derivative_graph(self, graph, dx = 0.01, **kwargs): + def get_derivative_graph(self, graph, dx=0.01, **kwargs): if "color" not in kwargs: kwargs["color"] = self.default_derivative_color + def deriv(x): return self.slope_of_tangent(x, graph, dx) / self.space_unit_to_y return self.get_graph(deriv, **kwargs) def get_graph_label( - self, - graph, - label = "f(x)", - x_val = None, - direction = RIGHT, - buff = MED_SMALL_BUFF, - color = None, - ): + self, + graph, + label="f(x)", + x_val=None, + direction=RIGHT, + buff=MED_SMALL_BUFF, + color=None, + ): label = TexMobject(label) color = color or graph.get_color() label.set_color(color) if x_val is None: - #Search from right to left + # Search from right to left for x in np.linspace(self.x_max, self.x_min, 100): point = self.input_to_graph_point(x, graph) if point[1] < FRAME_Y_RADIUS: @@ -206,26 +210,26 @@ class GraphScene(Scene): label.next_to( self.input_to_graph_point(x_val, graph), direction, - buff = buff + buff=buff ) label.shift_onto_screen() return label def get_riemann_rectangles( - self, + self, graph, - x_min = None, - x_max = None, - dx = 0.1, - input_sample_type = "left", - stroke_width = 1, - stroke_color = BLACK, - fill_opacity = 1, - start_color = None, - end_color = None, - show_signed_area = True, - width_scale_factor = 1.001 - ): + x_min=None, + x_max=None, + dx=0.1, + input_sample_type="left", + stroke_width=1, + stroke_color=BLACK, + fill_opacity=1, + start_color=None, + end_color=None, + show_signed_area=True, + width_scale_factor=1.001 + ): x_min = x_min if x_min is not None else self.x_min x_max = x_max if x_max is not None else self.x_max if start_color is None: @@ -233,47 +237,47 @@ class GraphScene(Scene): if end_color is None: end_color = self.default_riemann_end_color rectangles = VGroup() - x_range = np.arange(x_min, x_max, dx) + x_range = np.arange(x_min, x_max, dx) colors = color_gradient([start_color, end_color], len(x_range)) for x, color in zip(x_range, colors): if input_sample_type == "left": sample_input = x elif input_sample_type == "right": - sample_input = x+dx + sample_input = x + dx else: raise Exception("Invalid input sample type") graph_point = self.input_to_graph_point(sample_input, graph) points = VGroup(*map(VectorizedPoint, [ self.coords_to_point(x, 0), - self.coords_to_point(x+width_scale_factor*dx, 0), + self.coords_to_point(x + width_scale_factor * dx, 0), graph_point ])) rect = Rectangle() - rect.replace(points, stretch = True) + rect.replace(points, stretch=True) if graph_point[1] < self.graph_origin[1] and show_signed_area: fill_color = invert_color(color) else: fill_color = color - rect.set_fill(fill_color, opacity = fill_opacity) - rect.set_stroke(stroke_color, width = stroke_width) + rect.set_fill(fill_color, opacity=fill_opacity) + rect.set_stroke(stroke_color, width=stroke_width) rectangles.add(rect) return rectangles def get_riemann_rectangles_list( - self, + self, graph, n_iterations, - max_dx = 0.5, - power_base = 2, - stroke_width = 1, + max_dx=0.5, + power_base=2, + stroke_width=1, **kwargs - ): + ): return [ self.get_riemann_rectangles( - graph = graph, - dx = float(max_dx)/(power_base**n), - stroke_width = float(stroke_width)/(power_base**n), + graph=graph, + dx=float(max_dx) / (power_base**n), + stroke_width=float(stroke_width) / (power_base**n), **kwargs ) for n in range(n_iterations) @@ -281,17 +285,17 @@ class GraphScene(Scene): def transform_between_riemann_rects(self, curr_rects, new_rects, **kwargs): transform_kwargs = { - "run_time" : 2, - "submobject_mode" : "lagged_start" + "run_time": 2, + "submobject_mode": "lagged_start" } added_anims = kwargs.get("added_anims", []) transform_kwargs.update(kwargs) curr_rects.align_submobjects(new_rects) - x_coords = set() #Keep track of new repetitions + x_coords = set() # Keep track of new repetitions for rect in curr_rects: x = rect.get_center()[0] if x in x_coords: - rect.set_fill(opacity = 0) + rect.set_fill(opacity=0) else: x_coords.add(x) self.play( @@ -302,24 +306,24 @@ class GraphScene(Scene): def get_vertical_line_to_graph( self, x, graph, - line_class = Line, + line_class=Line, **line_kwargs - ): + ): if "color" not in line_kwargs: line_kwargs["color"] = graph.get_color() return line_class( self.coords_to_point(x, 0), self.input_to_graph_point(x, graph), **line_kwargs - ) + ) def get_vertical_lines_to_graph( self, graph, - x_min = None, - x_max = None, - num_lines = 20, + x_min=None, + x_max=None, + num_lines=20, **kwargs - ): + ): x_min = x_min or self.x_min x_max = x_max or self.x_max return VGroup(*[ @@ -328,20 +332,20 @@ class GraphScene(Scene): ]) def get_secant_slope_group( - self, - x, graph, - dx = None, - dx_line_color = None, - df_line_color = None, - dx_label = None, - df_label = None, - include_secant_line = True, - secant_line_color = None, - secant_line_length = 10, - ): + self, + x, graph, + dx=None, + dx_line_color=None, + df_line_color=None, + dx_label=None, + df_label=None, + include_secant_line=True, + secant_line_color=None, + secant_line_length=10, + ): """ Resulting group is of the form VGroup( - dx_line, + dx_line, df_line, dx_label, (if applicable) df_label, (if applicable) @@ -354,21 +358,21 @@ class GraphScene(Scene): group = VGroup() group.kwargs = kwargs - dx = dx or float(self.x_max - self.x_min)/10 + dx = dx or float(self.x_max - self.x_min) / 10 dx_line_color = dx_line_color or self.default_input_color df_line_color = df_line_color or graph.get_color() p1 = self.input_to_graph_point(x, graph) - p2 = self.input_to_graph_point(x+dx, graph) - interim_point = p2[0]*RIGHT + p1[1]*UP + p2 = self.input_to_graph_point(x + dx, graph) + interim_point = p2[0] * RIGHT + p1[1] * UP group.dx_line = Line( p1, interim_point, - color = dx_line_color + color=dx_line_color ) group.df_line = Line( interim_point, p2, - color = df_line_color + color=df_line_color ) group.add(group.dx_line, group.df_line) @@ -383,8 +387,8 @@ class GraphScene(Scene): group.add(group.df_label) if len(labels) > 0: - max_width = 0.8*group.dx_line.get_width() - max_height = 0.8*group.df_line.get_height() + max_width = 0.8 * group.dx_line.get_width() + max_height = 0.8 * group.df_line.get_height() if labels.get_width() > max_width: labels.scale_to_fit_width(max_width) if labels.get_height() > max_height: @@ -392,40 +396,41 @@ class GraphScene(Scene): if dx_label is not None: group.dx_label.next_to( - group.dx_line, - np.sign(dx)*DOWN, - buff = group.dx_label.get_height()/2 + group.dx_line, + np.sign(dx) * DOWN, + buff=group.dx_label.get_height() / 2 ) group.dx_label.set_color(group.dx_line.get_color()) if df_label is not None: group.df_label.next_to( - group.df_line, - np.sign(dx)*RIGHT, - buff = group.df_label.get_height()/2 + group.df_line, + np.sign(dx) * RIGHT, + buff=group.df_label.get_height() / 2 ) group.df_label.set_color(group.df_line.get_color()) if include_secant_line: secant_line_color = secant_line_color or self.default_derivative_color - group.secant_line = Line(p1, p2, color = secant_line_color) + group.secant_line = Line(p1, p2, color=secant_line_color) group.secant_line.scale_in_place( - secant_line_length/group.secant_line.get_length() + secant_line_length / group.secant_line.get_length() ) group.add(group.secant_line) return group def animate_secant_slope_group_change( - self, secant_slope_group, - target_dx = None, - target_x = None, - run_time = 3, - added_anims = None, + self, secant_slope_group, + target_dx=None, + target_x=None, + run_time=3, + added_anims=None, **anim_kwargs - ): + ): if target_dx is None and target_x is None: - raise Exception("At least one of target_x and target_dx must not be None") + raise Exception( + "At least one of target_x and target_dx must not be None") if added_anims is None: added_anims = [] @@ -435,6 +440,7 @@ class GraphScene(Scene): target_dx = start_dx if target_x is None: target_x = start_x + def update_func(group, alpha): dx = interpolate(start_dx, target_dx, alpha) x = interpolate(start_x, target_x, alpha) @@ -448,31 +454,10 @@ class GraphScene(Scene): self.play( UpdateFromAlphaFunc( secant_slope_group, update_func, - run_time = run_time, + run_time=run_time, **anim_kwargs ), *added_anims ) secant_slope_group.kwargs["x"] = target_x secant_slope_group.kwargs["dx"] = target_dx - - - - - - - - - - - - - - - - - - - - - diff --git a/scene/moving_camera_scene.py b/scene/moving_camera_scene.py index 70f0f158..f3f5ed74 100644 --- a/scene/moving_camera_scene.py +++ b/scene/moving_camera_scene.py @@ -6,10 +6,11 @@ from scene.scene import Scene from camera.moving_camera import MovingCamera from mobject.frame import ScreenRectangle + class MovingCameraScene(Scene): def setup(self): - self.camera_frame = ScreenRectangle(height = FRAME_HEIGHT) - self.camera_frame.set_stroke(width = 0) + self.camera_frame = ScreenRectangle(height=FRAME_HEIGHT) + self.camera_frame.set_stroke(width=0) self.camera = MovingCamera( self.camera_frame, **self.camera_config ) @@ -22,4 +23,4 @@ class MovingCameraScene(Scene): # When the camera is moving, so is everything, return self.mobjects else: - return moving_mobjects \ No newline at end of file + return moving_mobjects diff --git a/scene/reconfigurable_scene.py b/scene/reconfigurable_scene.py index 555ad69d..f2a0c323 100644 --- a/scene/reconfigurable_scene.py +++ b/scene/reconfigurable_scene.py @@ -1,27 +1,27 @@ from __future__ import absolute_import -import numpy as np - from scene.scene import Scene from animation.transform import Transform from mobject.mobject import Mobject from constants import * + class ReconfigurableScene(Scene): CONFIG = { - "allow_recursion" : True, + "allow_recursion": True, } + def setup(self): self.states = [] self.num_recursions = 0 def transition_to_alt_config( - self, - return_to_original_configuration = True, - transformation_kwargs = None, + self, + return_to_original_configuration=True, + transformation_kwargs=None, **new_config - ): + ): if transformation_kwargs is None: transformation_kwargs = {} original_state = self.get_state() @@ -30,41 +30,37 @@ class ReconfigurableScene(Scene): if not self.allow_recursion: return alt_scene = self.__class__( - skip_animations = True, - allow_recursion = False, + skip_animations=True, + allow_recursion=False, **new_config ) - alt_state = alt_scene.states[len(self.states)-1] + alt_state = alt_scene.states[len(self.states) - 1] if return_to_original_configuration: self.clear() self.transition_between_states( - state_copy, alt_state, + state_copy, alt_state, **transformation_kwargs ) self.transition_between_states( - state_copy, original_state, + state_copy, original_state, **transformation_kwargs ) self.clear() self.add(*original_state) else: self.transition_between_states( - original_state, alt_state, + original_state, alt_state, **transformation_kwargs ) self.__dict__.update(new_config) def get_state(self): - # Want to return a mobject that maintains the most + # Want to return a mobject that maintains the most # structure. The way to do that is to extract only # those that aren't inside another. - top_level_mobjects = self.get_top_level_mobjects() return Mobject(*self.get_top_level_mobjects()) def transition_between_states(self, start_state, target_state, **kwargs): self.play(Transform(start_state, target_state, **kwargs)) self.wait() - - - diff --git a/scene/sample_space_scene.py b/scene/sample_space_scene.py index 53bc7cac..b1fa129f 100644 --- a/scene/sample_space_scene.py +++ b/scene/sample_space_scene.py @@ -21,10 +21,10 @@ class SampleSpaceScene(Scene): def get_division_change_animations( self, sample_space, parts, p_list, - dimension = 1, - new_label_kwargs = None, + dimension=1, + new_label_kwargs=None, **kwargs - ): + ): if new_label_kwargs is None: new_label_kwargs = {} anims = [] @@ -34,12 +34,12 @@ class SampleSpaceScene(Scene): vect = DOWN if dimension == 1 else RIGHT parts.generate_target() for part, p in zip(parts.target, p_list): - part.replace(space_copy, stretch = True) + part.replace(space_copy, stretch=True) part.stretch(p, dimension) - parts.target.arrange_submobjects(vect, buff = 0) + parts.target.arrange_submobjects(vect, buff=0) parts.target.move_to(space_copy) anims.append(MoveToTarget(parts)) - if hasattr(parts, "labels") and parts.labels is not None: + if hasattr(parts, "labels") and parts.labels is not None: label_kwargs = parts.label_kwargs label_kwargs.update(new_label_kwargs) new_braces, new_labels = sample_space.get_subdivision_braces_and_labels( @@ -55,7 +55,7 @@ class SampleSpaceScene(Scene): assert(hasattr(self.sample_space, "horizontal_parts")) return self.get_division_change_animations( self.sample_space, self.sample_space.horizontal_parts, p_list, - dimension = 1, + dimension=1, **kwargs ) @@ -63,19 +63,19 @@ class SampleSpaceScene(Scene): assert(hasattr(self.sample_space, "vertical_parts")) return self.get_division_change_animations( self.sample_space, self.sample_space.vertical_parts, p_list, - dimension = 0, + dimension=0, **kwargs ) - + def get_conditional_change_anims( - self, sub_sample_space_index, value, post_rects = None, + self, sub_sample_space_index, value, post_rects=None, **kwargs - ): + ): parts = self.sample_space.horizontal_parts sub_sample_space = parts[sub_sample_space_index] anims = self.get_division_change_animations( sub_sample_space, sub_sample_space.vertical_parts, value, - dimension = 0, + dimension=0, **kwargs ) if post_rects is not None: @@ -94,10 +94,10 @@ class SampleSpaceScene(Scene): for i in range(2) ]) - def get_posterior_rectangles(self, buff = MED_LARGE_BUFF): + def get_posterior_rectangles(self, buff=MED_LARGE_BUFF): prior_rects = self.get_prior_rectangles() areas = [ - rect.get_width()*rect.get_height() + rect.get_width() * rect.get_height() for rect in prior_rects ] total_area = sum(areas) @@ -105,19 +105,19 @@ class SampleSpaceScene(Scene): post_rects = prior_rects.copy() for rect, area in zip(post_rects, areas): - rect.stretch_to_fit_height(total_height * area/total_area) + rect.stretch_to_fit_height(total_height * area / total_area) rect.stretch_to_fit_width( - area/rect.get_height() + area / rect.get_height() ) - post_rects.arrange_submobjects(DOWN, buff = 0) + post_rects.arrange_submobjects(DOWN, buff=0) post_rects.next_to( self.sample_space, RIGHT, buff ) return post_rects def get_posterior_rectangle_braces_and_labels( - self, post_rects, labels, direction = RIGHT, **kwargs - ): + self, post_rects, labels, direction=RIGHT, **kwargs + ): return self.sample_space.get_subdivision_braces_and_labels( post_rects, labels, direction, **kwargs ) @@ -132,7 +132,7 @@ class SampleSpaceScene(Scene): def get_posterior_rectangle_change_anims(self, post_rects): def update_rects(rects): - new_rects = self.get_posterior_rectangles() + new_rects = self.get_posterior_rectangles() Transform(rects, new_rects).update(1) if hasattr(rects, "braces"): self.update_posterior_braces(rects) diff --git a/scene/scene.py b/scene/scene.py index 1817a326..6a3db762 100644 --- a/scene/scene.py +++ b/scene/scene.py @@ -1,4 +1,3 @@ -import copy import inspect import itertools as it import numpy as np @@ -6,11 +5,8 @@ import os import random import shutil import subprocess as sp -import time import warnings -from PIL import Image -from colour import Color from tqdm import tqdm as ProgressDisplay from constants import * @@ -20,11 +16,11 @@ from animation.transform import MoveToTarget from camera.camera import Camera from continual_animation.continual_animation import ContinualAnimation from mobject.mobject import Mobject -from mobject.types.vectorized_mobject import VMobject from utils.iterables import list_update from container.container import Container + def add_extension_if_not_present(file_name, extension): # This could conceivably be smarter about handling existing differing extensions if(file_name[-len(extension):] != extension): @@ -32,29 +28,32 @@ def add_extension_if_not_present(file_name, extension): else: return file_name + class Scene(Container): CONFIG = { - "camera_class" : Camera, - "camera_config" : {}, - "frame_duration" : LOW_QUALITY_FRAME_DURATION, - "construct_args" : [], - "skip_animations" : False, - "ignore_waits" : False, - "write_to_movie" : False, - "save_frames" : False, - "save_pngs" : False, - "pngs_mode" : "RGBA", - "output_directory" : ANIMATIONS_DIR, - "movie_file_extension" : ".mp4", - "name" : None, - "always_continually_update" : False, - "random_seed" : 0, - "start_at_animation_number" : None, - "end_at_animation_number" : None, - "include_render_quality_in_output_directory" : True, + "camera_class": Camera, + "camera_config": {}, + "frame_duration": LOW_QUALITY_FRAME_DURATION, + "construct_args": [], + "skip_animations": False, + "ignore_waits": False, + "write_to_movie": False, + "save_frames": False, + "save_pngs": False, + "pngs_mode": "RGBA", + "output_directory": ANIMATIONS_DIR, + "movie_file_extension": ".mp4", + "name": None, + "always_continually_update": False, + "random_seed": 0, + "start_at_animation_number": None, + "end_at_animation_number": None, + "include_render_quality_in_output_directory": True, } + def __init__(self, **kwargs): - Container.__init__(self, **kwargs) # Perhaps allow passing in a non-empty *mobjects parameter? + # Perhaps allow passing in a non-empty *mobjects parameter? + Container.__init__(self, **kwargs) self.camera = self.camera_class(**self.camera_config) self.mobjects = [] self.continual_animations = [] @@ -86,7 +85,7 @@ class Scene(Container): if self.write_to_movie: self.close_movie_pipe() - print("Played a total of %d animations"%self.num_plays) + print("Played a total of %d animations" % self.num_plays) def setup(self): """ @@ -101,7 +100,7 @@ class Scene(Container): base.setup(self) def construct(self): - pass #To be implemented in subclasses + pass # To be implemented in subclasses def __str__(self): return self.name @@ -128,7 +127,7 @@ class Scene(Container): def get_attrs(self, *keys): return [getattr(self, key) for key in keys] - ### Only these methods should touch the camera + # Only these methods should touch the camera def set_camera(self, camera): self.camera = camera @@ -152,12 +151,12 @@ class Scene(Container): self.camera.capture_mobjects(mobjects, **kwargs) def update_frame( - self, - mobjects = None, - background = None, - include_submobjects = True, - dont_update_when_skipping = True, - **kwargs): + self, + mobjects=None, + background=None, + include_submobjects=True, + dont_update_when_skipping=True, + **kwargs): if self.skip_animations and dont_update_when_skipping: return if mobjects is None: @@ -179,7 +178,7 @@ class Scene(Container): self.clear() ### - def continual_update(self, dt = None): + def continual_update(self, dt=None): if dt is None: dt = self.frame_duration for continual_animation in self.continual_animations: @@ -190,10 +189,10 @@ class Scene(Container): for continual_animation in continual_animations: continual_animation.begin_wind_down(wind_down_time) self.wait(wind_down_time) - #TODO, this is not done with the remove method so as to - #keep the relevant mobjects. Better way? + # TODO, this is not done with the remove method so as to + # keep the relevant mobjects. Better way? self.continual_animations = filter( - lambda ca : ca in continual_animations, + lambda ca: ca in continual_animations, self.continual_animations ) @@ -207,6 +206,7 @@ class Scene(Container): # of another mobject from the scene mobjects = self.get_mobjects() families = [m.submobject_family() for m in mobjects] + def is_top_level(mobject): num_families = sum([ (mobject in family) @@ -239,7 +239,7 @@ class Scene(Container): mobjects, continual_animations = self.separate_mobjects_and_continual_animations( mobjects_or_continual_animations ) - self.restructure_mobjects(to_remove = mobjects) + self.restructure_mobjects(to_remove=mobjects) self.mobjects += mobjects self.continual_animations += continual_animations return self @@ -249,7 +249,7 @@ class Scene(Container): So a scene can just add all mobjects it's defined up to that point by calling add_mobjects_among(locals().values()) """ - mobjects = filter(lambda x : isinstance(x, Mobject), values) + mobjects = filter(lambda x: isinstance(x, Mobject), values) self.add(*mobjects) return self @@ -263,17 +263,17 @@ class Scene(Container): self.restructure_mobjects(mobjects, list_name, False) self.continual_animations = filter( - lambda ca : ca not in continual_animations and \ - ca.mobject not in to_remove, + lambda ca: ca not in continual_animations and + ca.mobject not in to_remove, self.continual_animations ) return self def restructure_mobjects( - self, to_remove, - mobject_list_name = "mobjects", - extract_families = True - ): + self, to_remove, + mobject_list_name="mobjects", + extract_families=True + ): """ In cases where the scene contains a group, e.g. Group(m1, m2, m3), but one of its submobjects is removed, e.g. scene.remove(m1), the list of mobjects @@ -289,6 +289,7 @@ class Scene(Container): def get_restructured_mobject_list(self, mobjects, to_remove): new_mobjects = [] + def add_safe_mobjects_from_list(list_to_examine, set_to_remove): for mob in list_to_examine: if mob in set_to_remove: @@ -364,7 +365,7 @@ class Scene(Container): run_time = np.max([animation.run_time for animation in animations]) time_progression = self.get_time_progression(run_time) time_progression.set_description("".join([ - "Animation %d: "%self.num_plays, + "Animation %d: " % self.num_plays, str(animations[0]), (", etc." if len(animations) > 1 else ""), ])) @@ -383,17 +384,18 @@ class Scene(Container): """ animations = [] state = { - "curr_method" : None, - "last_method" : None, - "method_args" : [], + "curr_method": None, + "last_method": None, + "method_args": [], } + def compile_method(state): if state["curr_method"] is None: return mobject = state["curr_method"].im_self if state["last_method"] and state["last_method"].im_self is mobject: animations.pop() - #method should already have target then. + # method should already have target then. else: mobject.generate_target() # @@ -402,7 +404,7 @@ class Scene(Container): else: method_kwargs = {} state["curr_method"].im_func( - mobject.target, + mobject.target, *state["method_args"], **method_kwargs ) @@ -453,7 +455,7 @@ class Scene(Container): # Paint all non-moving objects onto the screen, so they don't # have to be rendered every frame - self.update_frame(excluded_mobjects = moving_mobjects) + self.update_frame(excluded_mobjects=moving_mobjects) static_image = self.get_frame() for t in self.get_animation_time_progression(animations): for animation in animations: @@ -484,27 +486,28 @@ class Scene(Container): return self.mobjects_from_last_animation return [] - def wait(self, duration = DEFAULT_WAIT_TIME): + def wait(self, duration=DEFAULT_WAIT_TIME): if self.should_continually_update(): for t in self.get_time_progression(duration): self.continual_update() self.update_frame() self.add_frames(self.get_frame()) elif self.skip_animations: - #Do nothing + # Do nothing return self else: self.update_frame() - self.add_frames(*[self.get_frame()]*int(duration / self.frame_duration)) + self.add_frames(*[self.get_frame()] * + int(duration / self.frame_duration)) return self - def wait_to(self, time, assert_positive = True): - if self.ignore_waits: + def wait_to(self, time, assert_positive=True): + if self.ignore_waits: return time -= self.current_scene_time - if assert_positive: + if assert_positive: assert(time >= 0) - elif time < 0: + elif time < 0: return self.wait(time) @@ -522,23 +525,24 @@ class Scene(Container): def add_frames(self, *frames): if self.skip_animations: return - self.current_scene_time += len(frames)*self.frame_duration + self.current_scene_time += len(frames) * self.frame_duration if self.write_to_movie: for frame in frames: if self.save_pngs: - self.save_image("frame" + str(self.frame_num), self.pngs_mode, True) + self.save_image( + "frame" + str(self.frame_num), self.pngs_mode, True) self.frame_num = self.frame_num + 1 self.writing_process.stdin.write(frame.tostring()) if self.save_frames: self.saved_frames += list(frames) - #Display methods + # Display methods def show_frame(self): - self.update_frame(dont_update_when_skipping = False) + self.update_frame(dont_update_when_skipping=False) self.get_image().show() - def get_image_file_path(self, name = None, dont_update = False): + def get_image_file_path(self, name=None, dont_update=False): folder = "images" if dont_update: folder = str(self) @@ -546,23 +550,23 @@ class Scene(Container): file_name = add_extension_if_not_present(name or str(self), ".png") return os.path.join(path, file_name) - def save_image(self, name = None, mode = "RGB", dont_update = False): + def save_image(self, name=None, mode="RGB", dont_update=False): path = self.get_image_file_path(name, dont_update) directory_path = os.path.dirname(path) if not os.path.exists(directory_path): os.makedirs(directory_path) if not dont_update: - self.update_frame(dont_update_when_skipping = False) + self.update_frame(dont_update_when_skipping=False) image = self.get_image() image = image.convert(mode) image.save(path) - def get_movie_file_path(self, name = None, extension = None): + def get_movie_file_path(self, name=None, extension=None): directory = self.output_directory if self.include_render_quality_in_output_directory: - sub_dir = "%dp%d"%( + sub_dir = "%dp%d" % ( self.camera.pixel_shape[0], - int(1.0/self.frame_duration) + int(1.0 / self.frame_duration) ) directory = os.path.join(directory, sub_dir) if not os.path.exists(directory): @@ -581,21 +585,21 @@ class Scene(Container): name = str(self) file_path = self.get_movie_file_path(name) temp_file_path = file_path.replace(name, name + "Temp") - print("Writing to %s"%temp_file_path) + print("Writing to %s" % temp_file_path) self.args_to_rename_file = (temp_file_path, file_path) - fps = int(1/self.frame_duration) + fps = int(1 / self.frame_duration) height, width = self.camera.pixel_shape - + command = [ FFMPEG_BIN, - '-y', # overwrite output file if it exists + '-y', # overwrite output file if it exists '-f', 'rawvideo', - '-s', '%dx%d'%(width, height), # size of one frame + '-s', '%dx%d' % (width, height), # size of one frame '-pix_fmt', 'rgba', - '-r', str(fps), # frames per second - '-i', '-', # The imput comes from a pipe - '-an', # Tells FFMPEG not to expect any audio + '-r', str(fps), # frames per second + '-i', '-', # The imput comes from a pipe + '-an', # Tells FFMPEG not to expect any audio '-loglevel', 'error', ] if self.movie_file_extension == ".mov": @@ -603,7 +607,7 @@ class Scene(Container): # should be transparent. command += [ '-vcodec', 'png', - ] + ] else: command += [ '-vcodec', 'libx264', @@ -621,14 +625,6 @@ class Scene(Container): else: os.rename(*self.args_to_rename_file) + class EndSceneEarlyException(Exception): pass - - - - - - - - - diff --git a/scene/scene_from_video.py b/scene/scene_from_video.py index aab35ade..8498b82c 100644 --- a/scene/scene_from_video.py +++ b/scene/scene_from_video.py @@ -1,8 +1,6 @@ from __future__ import absolute_import import cv2 -import itertools as it -import numpy as np from tqdm import tqdm as show_progress @@ -11,25 +9,25 @@ from scene.scene import Scene class SceneFromVideo(Scene): def construct(self, file_name, - freeze_last_frame = True, - time_range = None): + freeze_last_frame=True, + time_range=None): cap = cv2.VideoCapture(file_name) self.shape = ( int(cap.get(cv2.cv.CV_CAP_PROP_FRAME_HEIGHT)), int(cap.get(cv2.cv.CV_CAP_PROP_FRAME_WIDTH)) ) fps = cap.get(cv2.cv.CV_CAP_PROP_FPS) - self.frame_duration = 1.0/fps + self.frame_duration = 1.0 / fps frame_count = int(cap.get(cv2.cv.CV_CAP_PROP_FRAME_COUNT)) if time_range is None: start_frame = 0 end_frame = frame_count else: - start_frame, end_frame = map(lambda t : fps*t, time_range) + start_frame, end_frame = map(lambda t: fps * t, time_range) frame_count = end_frame - start_frame print("Reading in " + file_name + "...") - for count in show_progress(range(start_frame, end_frame+1)): + for count in show_progress(range(start_frame, end_frame + 1)): returned, frame = cap.read() if not returned: break @@ -41,18 +39,17 @@ class SceneFromVideo(Scene): if freeze_last_frame and len(self.frames) > 0: self.original_background = self.background = self.frames[-1] - def apply_gaussian_blur(self, ksize = (5, 5), sigmaX = 5): + def apply_gaussian_blur(self, ksize=(5, 5), sigmaX=5): self.frames = [ cv2.GaussianBlur(frame, ksize, sigmaX) for frame in self.frames ] - def apply_edge_detection(self, threshold1 = 50, threshold2 = 100): + def apply_edge_detection(self, threshold1=50, threshold2=100): edged_frames = [ cv2.Canny(frame, threshold1, threshold2) for frame in self.frames ] for index in range(len(self.frames)): for i in range(3): - self.frames[index][:,:,i] = edged_frames[index] - + self.frames[index][:, :, i] = edged_frames[index] diff --git a/scene/three_d_scene.py b/scene/three_d_scene.py index 6896e55f..c7dcb47b 100644 --- a/scene/three_d_scene.py +++ b/scene/three_d_scene.py @@ -5,8 +5,6 @@ from constants import * from continual_animation.continual_animation import ContinualMovement from animation.transform import ApplyMethod from camera.three_d_camera import ThreeDCamera -from mobject.types.vectorized_mobject import VGroup -from mobject.three_dimensions import should_shade_in_3d from scene.scene import Scene from utils.iterables import list_update @@ -14,19 +12,20 @@ from utils.iterables import list_update class ThreeDScene(Scene): CONFIG = { - "camera_class" : ThreeDCamera, - "ambient_camera_rotation" : None, + "camera_class": ThreeDCamera, + "ambient_camera_rotation": None, } - def set_camera_position(self, phi = None, theta = None, distance = None, - center_x = None, center_y = None, center_z = None): - self.camera.set_position(phi, theta, distance, center_x, center_y, center_z) + def set_camera_position(self, phi=None, theta=None, distance=None, + center_x=None, center_y=None, center_z=None): + self.camera.set_position(phi, theta, distance, + center_x, center_y, center_z) - def begin_ambient_camera_rotation(self, rate = 0.01): + def begin_ambient_camera_rotation(self, rate=0.01): self.ambient_camera_rotation = ContinualMovement( self.camera.rotation_mobject, - direction = UP, - rate = rate + direction=UP, + rate=rate ) self.add(self.ambient_camera_rotation) @@ -37,18 +36,19 @@ class ThreeDScene(Scene): def move_camera( self, - phi = None, theta = None, distance = None, - center_x = None, center_y = None, center_z = None, - added_anims = [], + phi=None, theta=None, distance=None, + center_x=None, center_y=None, center_z=None, + added_anims=[], **kwargs - ): + ): target_point = self.camera.get_spherical_coords(phi, theta, distance) movement = ApplyMethod( self.camera.rotation_mobject.move_to, target_point, **kwargs ) - target_center = self.camera.get_center_of_rotation(center_x, center_y, center_z) + target_center = self.camera.get_center_of_rotation( + center_x, center_y, center_z) movement_center = ApplyMethod( self.camera.moving_center.move_to, target_center, diff --git a/scene/vector_space_scene.py b/scene/vector_space_scene.py index 157561e1..cd42b2e5 100644 --- a/scene/vector_space_scene.py +++ b/scene/vector_space_scene.py @@ -36,25 +36,27 @@ X_COLOR = GREEN_C Y_COLOR = RED_C Z_COLOR = BLUE_D + class VectorScene(Scene): CONFIG = { - "basis_vector_stroke_width" : 6 + "basis_vector_stroke_width": 6 } - def add_plane(self, animate = False, **kwargs): + + def add_plane(self, animate=False, **kwargs): plane = NumberPlane(**kwargs) if animate: - self.play(ShowCreation(plane, submobject_mode = "lagged_start")) + self.play(ShowCreation(plane, submobject_mode="lagged_start")) self.add(plane) return plane - def add_axes(self, animate = False, color = WHITE, **kwargs): - axes = Axes(color = color, tick_frequency = 1) + def add_axes(self, animate=False, color=WHITE, **kwargs): + axes = Axes(color=color, tick_frequency=1) if animate: - self.play(ShowCreation(axes, submobject_mode = "one_at_a_time")) + self.play(ShowCreation(axes, submobject_mode="one_at_a_time")) self.add(axes) return axes - def lock_in_faded_grid(self, dimness = 0.7, axes_dimness = 0.5): + def lock_in_faded_grid(self, dimness=0.7, axes_dimness=0.5): plane = self.add_plane() axes = plane.get_axes() plane.fade(dimness) @@ -63,9 +65,9 @@ class VectorScene(Scene): self.add(axes) self.freeze_background() - def add_vector(self, vector, color = YELLOW, animate = True, **kwargs): + def add_vector(self, vector, color=YELLOW, animate=True, **kwargs): if not isinstance(vector, Arrow): - vector = Vector(vector, color = color, **kwargs) + vector = Vector(vector, color=color, **kwargs) if animate: self.play(ShowCreation(vector)) self.add(vector) @@ -76,40 +78,41 @@ class VectorScene(Scene): self.play(Write(coords)) return coords - def get_basis_vectors(self): - return [ + def get_basis_vectors(self, i_hat_color=X_COLOR, j_hat_color=Y_COLOR): + return VGroup(*[ Vector( - vect, color = color, - stroke_width = self.basis_vector_stroke_width + vect, + color=color, + stroke_width=self.basis_vector_stroke_width ) for vect, color in [ - ([1, 0], X_COLOR), - ([0, 1], Y_COLOR) + ([1, 0], i_hat_color), + ([0, 1], j_hat_color) ] - ] + ]) def get_basis_vector_labels(self, **kwargs): i_hat, j_hat = self.get_basis_vectors() return VGroup(*[ self.get_vector_label( - vect, label, color = color, - label_scale_factor = 1, + vect, label, color=color, + label_scale_factor=1, **kwargs ) - for vect, label , color in [ + for vect, label, color in [ (i_hat, "\\hat{\\imath}", X_COLOR), (j_hat, "\\hat{\\jmath}", Y_COLOR), ] ]) - def get_vector_label(self, vector, label, - direction = "left", - rotate = False, - color = None, - label_scale_factor = VECTOR_LABEL_SCALE_FACTOR): + def get_vector_label(self, vector, label, + direction="left", + rotate=False, + color=None, + label_scale_factor=VECTOR_LABEL_SCALE_FACTOR): if not isinstance(label, TexMobject): if len(label) == 1: - label = "\\vec{\\textbf{%s}}"%label + label = "\\vec{\\textbf{%s}}" % label label = TexMobject(label) if color is None: color = vector.get_color() @@ -119,53 +122,53 @@ class VectorScene(Scene): angle = vector.get_angle() if not rotate: - label.rotate(-angle, about_point = ORIGIN) + label.rotate(-angle, about_point=ORIGIN) if direction is "left": - label.shift(-label.get_bottom() + 0.1*UP) + label.shift(-label.get_bottom() + 0.1 * UP) else: - label.shift(-label.get_top() + 0.1*DOWN) - label.rotate(angle, about_point = ORIGIN) - label.shift((vector.get_end() - vector.get_start())/2) + label.shift(-label.get_top() + 0.1 * DOWN) + label.rotate(angle, about_point=ORIGIN) + label.shift((vector.get_end() - vector.get_start()) / 2) return label - def label_vector(self, vector, label, animate = True, **kwargs): + def label_vector(self, vector, label, animate=True, **kwargs): label = self.get_vector_label(vector, label, **kwargs) if animate: - self.play(Write(label, run_time = 1)) + self.play(Write(label, run_time=1)) self.add(label) return label def position_x_coordinate(self, x_coord, x_line, vector): - x_coord.next_to(x_line, -np.sign(vector[1])*UP) + x_coord.next_to(x_line, -np.sign(vector[1]) * UP) x_coord.set_color(X_COLOR) return x_coord def position_y_coordinate(self, y_coord, y_line, vector): - y_coord.next_to(y_line, np.sign(vector[0])*RIGHT) + y_coord.next_to(y_line, np.sign(vector[0]) * RIGHT) y_coord.set_color(Y_COLOR) return y_coord - def coords_to_vector(self, vector, coords_start = 2*RIGHT+2*UP, clean_up = True): + def coords_to_vector(self, vector, coords_start=2 * RIGHT + 2 * UP, clean_up=True): starting_mobjects = list(self.mobjects) array = Matrix(vector) array.shift(coords_start) arrow = Vector(vector) - x_line = Line(ORIGIN, vector[0]*RIGHT) + x_line = Line(ORIGIN, vector[0] * RIGHT) y_line = Line(x_line.get_end(), arrow.get_end()) x_line.set_color(X_COLOR) y_line.set_color(Y_COLOR) x_coord, y_coord = array.get_mob_matrix().flatten() - self.play(Write(array, run_time = 1)) + self.play(Write(array, run_time=1)) self.wait() self.play(ApplyFunction( - lambda x : self.position_x_coordinate(x, x_line, vector), + lambda x: self.position_x_coordinate(x, x_line, vector), x_coord )) self.play(ShowCreation(x_line)) self.play( ApplyFunction( - lambda y : self.position_y_coordinate(y, y_line, vector), + lambda y: self.position_y_coordinate(y, y_line, vector), y_coord ), FadeOut(array.get_brackets()) @@ -178,7 +181,7 @@ class VectorScene(Scene): self.clear() self.add(*starting_mobjects) - def vector_to_coords(self, vector, integer_labels = True, clean_up = True): + def vector_to_coords(self, vector, integer_labels=True, clean_up=True): starting_mobjects = list(self.mobjects) show_creation = False if isinstance(vector, Arrow): @@ -187,8 +190,8 @@ class VectorScene(Scene): else: arrow = Vector(vector) show_creation = True - array = vector_coordinate_label(arrow, integer_labels = integer_labels) - x_line = Line(ORIGIN, vector[0]*RIGHT) + array = vector_coordinate_label(arrow, integer_labels=integer_labels) + x_line = Line(ORIGIN, vector[0] * RIGHT) y_line = Line(x_line.get_end(), arrow.get_end()) x_line.set_color(X_COLOR) y_line.set_color(Y_COLOR) @@ -206,18 +209,18 @@ class VectorScene(Scene): self.play( ShowCreation(x_line), Write(x_coord_start), - run_time = 1 + run_time=1 ) self.play( ShowCreation(y_line), Write(y_coord_start), - run_time = 1 + run_time=1 ) self.wait() self.play( - Transform(x_coord_start, x_coord, submobject_mode = "all_at_once"), - Transform(y_coord_start, y_coord, submobject_mode = "all_at_once"), - Write(brackets, run_time = 1), + Transform(x_coord_start, x_coord, submobject_mode="all_at_once"), + Transform(y_coord_start, y_coord, submobject_mode="all_at_once"), + Write(brackets, run_time=1), ) self.wait() @@ -236,53 +239,56 @@ class VectorScene(Scene): x_max = int(FRAME_X_RADIUS + abs(vector[0])) y_max = int(FRAME_Y_RADIUS + abs(vector[1])) dots = VMobject(*[ - Dot(x*RIGHT + y*UP) + Dot(x * RIGHT + y * UP) for x in range(-x_max, x_max) for y in range(-y_max, y_max) ]) - dots.set_fill(BLACK, opacity = 0) - dots_halfway = dots.copy().shift(vector/2).set_fill(WHITE, 1) + dots.set_fill(BLACK, opacity=0) + dots_halfway = dots.copy().shift(vector / 2).set_fill(WHITE, 1) dots_end = dots.copy().shift(vector) self.play(Transform( - dots, dots_halfway, rate_func = rush_into + dots, dots_halfway, rate_func=rush_into )) self.play(Transform( - dots, dots_end, rate_func = rush_from + dots, dots_end, rate_func=rush_from )) self.remove(dots) + class LinearTransformationScene(VectorScene): CONFIG = { - "include_background_plane" : True, - "include_foreground_plane" : True, - "foreground_plane_kwargs" : { - "x_radius" : FRAME_WIDTH, - "y_radius" : FRAME_HEIGHT, - "secondary_line_ratio" : 0 + "include_background_plane": True, + "include_foreground_plane": True, + "foreground_plane_kwargs": { + "x_radius": FRAME_WIDTH, + "y_radius": FRAME_HEIGHT, + "secondary_line_ratio": 0 }, - "background_plane_kwargs" : { - "color" : GREY, - "secondary_color" : DARK_GREY, - "axes_color" : GREY, - "stroke_width" : 2, + "background_plane_kwargs": { + "color": GREY, + "secondary_color": DARK_GREY, + "axes_color": GREY, + "stroke_width": 2, }, - "show_coordinates" : False, - "show_basis_vectors" : True, - "i_hat_color" : X_COLOR, - "j_hat_color" : Y_COLOR, - "leave_ghost_vectors" : False, - "t_matrix" : [[3, 0], [1, 2]], + "show_coordinates": False, + "show_basis_vectors": True, + "basis_vector_stroke_width": 6, + "i_hat_color": X_COLOR, + "j_hat_color": Y_COLOR, + "leave_ghost_vectors": False, + "t_matrix": [[3, 0], [1, 2]], } + def setup(self): if hasattr(self, "has_already_setup"): return self.has_already_setup = True - ##^This is to not break all the old Scenes + # ^This is to not break all the old Scenes self.background_mobjects = [] - self.foreground_mobjects = [] + self.foreground_mobjects = [] self.transformable_mobjects = [] - self.moving_vectors = [] + self.moving_vectors = [] self.transformable_labels = [] self.moving_mobjects = [] @@ -293,21 +299,18 @@ class LinearTransformationScene(VectorScene): if self.show_coordinates: self.background_plane.add_coordinates() - if self.include_background_plane: + if self.include_background_plane: self.add_background_mobject(self.background_plane) if self.include_foreground_plane: self.plane = NumberPlane(**self.foreground_plane_kwargs) self.add_transformable_mobject(self.plane) if self.show_basis_vectors: - self.i_hat, self.j_hat = [ - self.add_vector( - coords, color, animate = False, stroke_width = 6 - ) - for coords, color in [ - ((1, 0), self.i_hat_color), - ((0, 1), self.j_hat_color), - ] - ] + self.basis_vectors = self.get_basis_vectors( + i_hat_color=self.i_hat_color, + j_hat_color=self.j_hat_color, + ) + self.moving_vectors += list(self.basis_vectors) + self.i_hat, self.j_hat = self.basis_vectors def add_special_mobjects(self, mob_list, *mobs_to_add): for mobject in mobs_to_add: @@ -320,17 +323,17 @@ class LinearTransformationScene(VectorScene): def add_foreground_mobject(self, *mobjects): self.add_special_mobjects(self.foreground_mobjects, *mobjects) - + def add_transformable_mobject(self, *mobjects): self.add_special_mobjects(self.transformable_mobjects, *mobjects) - def add_moving_mobject(self, mobject, target_mobject = None): + def add_moving_mobject(self, mobject, target_mobject=None): mobject.target = target_mobject self.add_special_mobjects(self.moving_mobjects, mobject) - def add_unit_square(self, color = YELLOW, opacity = 0.3, animate = False): - square = Square(color = color, side_length = 1) - square.shift(-square.get_corner(DOWN+LEFT)) + def add_unit_square(self, color=YELLOW, opacity=0.3, animate=False): + square = Square(color=color, side_length=1) + square.shift(-square.get_corner(DOWN + LEFT)) if animate: added_anims = map(Animation, self.moving_vectors) self.play(ShowCreation(square), *added_anims) @@ -338,13 +341,13 @@ class LinearTransformationScene(VectorScene): else: square.set_fill(color, opacity) self.add_transformable_mobject(square) - self.bring_to_front(*self.moving_vectors) + self.bring_to_front(*self.moving_vectors) self.square = square return self - def add_vector(self, vector, color = YELLOW, **kwargs): + def add_vector(self, vector, color=YELLOW, **kwargs): vector = VectorScene.add_vector( - self, vector, color = color, **kwargs + self, vector, color=color, **kwargs ) self.moving_vectors.append(vector) return vector @@ -354,12 +357,12 @@ class LinearTransformationScene(VectorScene): self.add_foreground_mobject(coords) return coords - def add_transformable_label(self, vector, label, new_label = None, **kwargs): + def add_transformable_label(self, vector, label, new_label=None, **kwargs): label_mob = self.label_vector(vector, label, **kwargs) if new_label: label_mob.target_text = new_label else: - label_mob.target_text = "L(%s)"%label_mob.get_tex_string() + label_mob.target_text = "L(%s)" % label_mob.get_tex_string() label_mob.vector = vector label_mob.kwargs = kwargs if "animate" in label_mob.kwargs: @@ -367,7 +370,7 @@ class LinearTransformationScene(VectorScene): self.transformable_labels.append(label_mob) return label_mob - def add_title(self, title, scale_factor = 1.5, animate = False): + def add_title(self, title, scale_factor=1.5, animate=False): if not isinstance(title, Mobject): title = TextMobject(title).scale(scale_factor) title.to_edge(UP) @@ -378,7 +381,10 @@ class LinearTransformationScene(VectorScene): self.title = title return self - def get_matrix_transformation(self, transposed_matrix): + def get_matrix_transformation(self, matrix): + return self.get_transposed_matrix_transformation(np.array(matrix).T) + + def get_transposed_matrix_transformation(self, transposed_matrix): transposed_matrix = np.array(transposed_matrix) if transposed_matrix.shape == (2, 2): new_matrix = np.identity(3) @@ -389,11 +395,11 @@ class LinearTransformationScene(VectorScene): return lambda point: np.dot(point, transposed_matrix) def get_piece_movement(self, pieces): - start = VMobject(*pieces) + start = VMobject(*pieces) target = VMobject(*[mob.target for mob in pieces]) if self.leave_ghost_vectors: self.add(start.copy().fade(0.7)) - return Transform(start, target, submobject_mode = "all_at_once") + return Transform(start, target, submobject_mode="all_at_once") def get_moving_mobject_movement(self, func): for m in self.moving_mobjects: @@ -405,7 +411,7 @@ class LinearTransformationScene(VectorScene): def get_vector_movement(self, func): for v in self.moving_vectors: - v.target = Vector(func(v.get_end()), color = v.get_color()) + v.target = Vector(func(v.get_end()), color=v.get_color()) norm = np.linalg.norm(v.target.get_end()) if norm < 0.1: v.target.get_tip().scale_in_place(norm) @@ -418,12 +424,15 @@ class LinearTransformationScene(VectorScene): ) return self.get_piece_movement(self.transformable_labels) + def apply_matrix(self, matrix, **kwargs): + self.apply_transposed_matrix(np.array(matrix).T, **kwargs) + def apply_transposed_matrix(self, transposed_matrix, **kwargs): - func = self.get_matrix_transformation(transposed_matrix) + func = self.get_transposed_matrix_transformation(transposed_matrix) if "path_arc" not in kwargs: net_rotation = np.mean([ angle_of_vector(func(RIGHT)), - angle_of_vector(func(UP))-np.pi/2 + angle_of_vector(func(UP)) - np.pi / 2 ]) kwargs["path_arc"] = net_rotation self.apply_function(func, **kwargs) @@ -436,7 +445,7 @@ class LinearTransformationScene(VectorScene): self.plane.prepare_for_nonlinear_transform() self.apply_function(function, **kwargs) - def apply_function(self, function, added_anims = [], **kwargs): + def apply_function(self, function, added_anims=[], **kwargs): if "run_time" not in kwargs: kwargs["run_time"] = 3 anims = [ @@ -451,14 +460,3 @@ class LinearTransformationScene(VectorScene): for f_mob in self.foreground_mobjects ] + added_anims self.play(*anims, **kwargs) - - - - - - - - - - - diff --git a/scene/zoomed_scene.py b/scene/zoomed_scene.py index a5df8796..d642fcc6 100644 --- a/scene/zoomed_scene.py +++ b/scene/zoomed_scene.py @@ -4,29 +4,29 @@ import numpy as np from scene.scene import Scene from animation.creation import FadeIn -from camera.camera import Camera from camera.moving_camera import MovingCamera -from mobject.mobject import Mobject from mobject.geometry import Rectangle from constants import * + class ZoomedScene(Scene): """ Move around self.little_rectangle to determine which part of the screen is zoomed in on. """ CONFIG = { - "zoomed_canvas_frame_shape" : (3, 3), - "zoomed_canvas_center" : None, - "zoomed_canvas_corner" : UP+RIGHT, - "zoomed_canvas_corner_buff" : DEFAULT_MOBJECT_TO_EDGE_BUFFER, - "zoomed_camera_background" : None, - "little_rectangle_start_position" : ORIGIN, - "zoom_factor" : 6, - "square_color" : WHITE, - "zoom_activated" : False, + "zoomed_canvas_frame_shape": (3, 3), + "zoomed_canvas_center": None, + "zoomed_canvas_corner": UP + RIGHT, + "zoomed_canvas_corner_buff": DEFAULT_MOBJECT_TO_EDGE_BUFFER, + "zoomed_camera_background": None, + "little_rectangle_start_position": ORIGIN, + "zoom_factor": 6, + "square_color": WHITE, + "zoom_activated": False, } + def activate_zooming(self): self.generate_big_rectangle() self.setup_zoomed_canvas() @@ -52,47 +52,46 @@ class ZoomedScene(Scene): def generate_big_rectangle(self): height, width = self.zoomed_canvas_frame_shape self.big_rectangle = Rectangle( - height = height, - width = width, - color = self.square_color + height=height, + width=width, + color=self.square_color ) if self.zoomed_canvas_center is not None: self.big_rectangle.shift(self.zoomed_canvas_center) elif self.zoomed_canvas_corner is not None: self.big_rectangle.to_corner( self.zoomed_canvas_corner, - buff = self.zoomed_canvas_corner_buff + buff=self.zoomed_canvas_corner_buff ) self.add(self.big_rectangle) - def setup_zoomed_canvas(self): - upper_left = self.big_rectangle.get_corner(UP+LEFT) - lower_right = self.big_rectangle.get_corner(DOWN+RIGHT) + upper_left = self.big_rectangle.get_corner(UP + LEFT) + lower_right = self.big_rectangle.get_corner(DOWN + RIGHT) pixel_coords = self.camera.points_to_pixel_coords( np.array([upper_left, lower_right]) ) self.zoomed_canvas_pixel_indices = pixel_coords (up, left), (down, right) = pixel_coords self.zoomed_canvas_pixel_shape = ( - right-left, - down-up, + right - left, + down - up, ) def setup_zoomed_camera(self): self.little_rectangle = self.big_rectangle.copy() - self.little_rectangle.scale(1./self.zoom_factor) + self.little_rectangle.scale(1. / self.zoom_factor) self.little_rectangle.move_to( self.little_rectangle_start_position ) self.zoomed_camera = MovingCamera( self.little_rectangle, - pixel_shape = self.zoomed_canvas_pixel_shape, - background = self.zoomed_camera_background + pixel_shape=self.zoomed_canvas_pixel_shape, + background=self.zoomed_camera_background ) self.add(self.little_rectangle) - #TODO, is there a better way to hanld this? - self.zoomed_camera.adjusted_thickness = lambda x : x + # TODO, is there a better way to hanld this? + self.zoomed_camera.adjusted_thickness = lambda x: x def get_frame(self): frame = Scene.get_frame(self) @@ -105,11 +104,12 @@ class ZoomedScene(Scene): self.camera.set_pixel_array(pixel_array) if self.zoom_activated: (up, left), (down, right) = self.zoomed_canvas_pixel_indices - self.zoomed_camera.set_pixel_array(pixel_array[left:right, up:down]) + self.zoomed_camera.set_pixel_array( + pixel_array[left:right, up:down]) def set_camera_background(self, background): self.set_camera_pixel_array(self, background) - #TODO, check this... + # TODO, check this... def reset_camera(self): self.camera.reset() @@ -125,7 +125,7 @@ class ZoomedScene(Scene): self.zoomed_camera.capture_mobjects( mobjects, **kwargs ) - + def get_moving_mobjects(self, *animations): moving_mobjects = Scene.get_moving_mobjects(self, *animations) if self.zoom_activated and self.little_rectangle in moving_mobjects: @@ -133,8 +133,3 @@ class ZoomedScene(Scene): return self.mobjects else: return moving_mobjects - - - - - diff --git a/utils/bezier.py b/utils/bezier.py index cdce5198..6097e130 100644 --- a/utils/bezier.py +++ b/utils/bezier.py @@ -5,18 +5,20 @@ from utils.simple_functions import choose CLOSED_THRESHOLD = 0.0 + def bezier(points): n = len(points) - 1 - return lambda t : sum([ - ((1-t)**(n-k))*(t**k)*choose(n, k)*point + return lambda t: sum([ + ((1 - t)**(n - k)) * (t**k) * choose(n, k) * point for k, point in enumerate(points) ]) + def partial_bezier_points(points, a, b): """ - Given an array of points which define + Given an array of points which define a bezier curve, and two numbers 0<=a 0: batch_prop_pairs.append( @@ -57,6 +64,7 @@ def batch_by_property(items, property_func): add_batch_prop_pair(curr_batch) return batch_prop_pairs + def tuplify(obj): if isinstance(obj, str): return (obj,) @@ -65,14 +73,17 @@ def tuplify(obj): except: return (obj,) + def stretch_array_to_length(nparray, length): curr_len = len(nparray) if curr_len > length: - raise Warning("Trying to stretch array to a length shorter than its own") - indices = np.arange(length)/ float(length) + raise Warning( + "Trying to stretch array to a length shorter than its own") + indices = np.arange(length) / float(length) indices *= curr_len return nparray[indices.astype('int')] + def make_even(iterable_1, iterable_2): list_1, list_2 = list(iterable_1), list(iterable_2) length = max(len(list_1), len(list_2)) @@ -81,6 +92,7 @@ def make_even(iterable_1, iterable_2): [list_2[(n * len(list_2)) / length] for n in xrange(length)] ) + def make_even_by_cycling(iterable_1, iterable_2): length = max(len(iterable_1), len(iterable_2)) cycle1 = it.cycle(iterable_1) @@ -90,24 +102,23 @@ def make_even_by_cycling(iterable_1, iterable_2): [cycle2.next() for x in range(length)] ) + def composition(func_list): """ func_list should contain elements of the form (f, args) """ return reduce( - lambda (f1, args1), (f2, args2) : (lambda x : f1(f2(x, *args2), *args1)), + lambda (f1, args1), (f2, args2): (lambda x: f1(f2(x, *args2), *args1)), func_list, - lambda x : x + lambda x: x ) -def remove_nones(sequence): - return filter(lambda x : x, sequence) -## Note this is redundant with it.chain +def remove_nones(sequence): + return filter(lambda x: x, sequence) + +# Note this is redundant with it.chain + + def concatenate_lists(*list_of_lists): return [item for l in list_of_lists for item in l] - - - - - diff --git a/utils/paths.py b/utils/paths.py index fd4e6694..229960a7 100644 --- a/utils/paths.py +++ b/utils/paths.py @@ -6,6 +6,7 @@ from utils.space_ops import rotation_matrix STRAIGHT_PATH_THRESHOLD = 0.01 + def straight_path(start_points, end_points, alpha): """ Same function as interpolate, but renamed to reflect @@ -15,27 +16,31 @@ def straight_path(start_points, end_points, alpha): """ return interpolate(start_points, end_points, alpha) -def path_along_arc(arc_angle, axis = OUT): + +def path_along_arc(arc_angle, axis=OUT): """ - If vect is vector from start to end, [vect[:,1], -vect[:,0]] is + If vect is vector from start to end, [vect[:,1], -vect[:,0]] is perpendicular to vect in the left direction. """ if abs(arc_angle) < STRAIGHT_PATH_THRESHOLD: return straight_path if np.linalg.norm(axis) == 0: axis = OUT - unit_axis = axis/np.linalg.norm(axis) + unit_axis = axis / np.linalg.norm(axis) + def path(start_points, end_points, alpha): vects = end_points - start_points - centers = start_points + 0.5*vects + centers = start_points + 0.5 * vects if arc_angle != np.pi: - centers += np.cross(unit_axis, vects/2.0)/np.tan(arc_angle/2) - rot_matrix = rotation_matrix(alpha*arc_angle, unit_axis) - return centers + np.dot(start_points-centers, rot_matrix.T) + centers += np.cross(unit_axis, vects / 2.0) / np.tan(arc_angle / 2) + rot_matrix = rotation_matrix(alpha * arc_angle, unit_axis) + return centers + np.dot(start_points - centers, rot_matrix.T) return path + def clockwise_path(): return path_along_arc(-np.pi) + def counterclockwise_path(): - return path_along_arc(np.pi) \ No newline at end of file + return path_along_arc(np.pi) diff --git a/utils/rate_functions.py b/utils/rate_functions.py index 566438ba..27da4ff1 100644 --- a/utils/rate_functions.py +++ b/utils/rate_functions.py @@ -3,49 +3,60 @@ import numpy as np from utils.bezier import bezier from utils.simple_functions import sigmoid -def smooth(t, inflection = 10.0): + +def smooth(t, inflection=10.0): error = sigmoid(-inflection / 2) - return (sigmoid(inflection*(t - 0.5)) - error) / (1 - 2*error) + return (sigmoid(inflection * (t - 0.5)) - error) / (1 - 2 * error) + def rush_into(t): - return 2*smooth(t/2.0) + return 2 * smooth(t / 2.0) + def rush_from(t): - return 2*smooth(t/2.0+0.5) - 1 + return 2 * smooth(t / 2.0 + 0.5) - 1 + def slow_into(t): - return np.sqrt(1-(1-t)*(1-t)) + return np.sqrt(1 - (1 - t) * (1 - t)) + def double_smooth(t): if t < 0.5: - return 0.5*smooth(2*t) + return 0.5 * smooth(2 * t) else: - return 0.5*(1 + smooth(2*t - 1)) + return 0.5 * (1 + smooth(2 * t - 1)) -def there_and_back(t, inflection = 10.0): - new_t = 2*t if t < 0.5 else 2*(1 - t) + +def there_and_back(t, inflection=10.0): + new_t = 2 * t if t < 0.5 else 2 * (1 - t) return smooth(new_t, inflection) + def there_and_back_with_pause(t): - if t < 1./3: - return smooth(3*t) - elif t < 2./3: + if t < 1. / 3: + return smooth(3 * t) + elif t < 2. / 3: return 1 else: - return smooth(3 - 3*t) + return smooth(3 - 3 * t) -def running_start(t, pull_factor = -0.5): + +def running_start(t, pull_factor=-0.5): return bezier([0, 0, pull_factor, pull_factor, 1, 1, 1])(t) -def not_quite_there(func = smooth, proportion = 0.7): + +def not_quite_there(func=smooth, proportion=0.7): def result(t): - return proportion*func(t) + return proportion * func(t) return result -def wiggle(t, wiggles = 2): - return there_and_back(t) * np.sin(wiggles*np.pi*t) -def squish_rate_func(func, a = 0.4, b = 0.6): +def wiggle(t, wiggles=2): + return there_and_back(t) * np.sin(wiggles * np.pi * t) + + +def squish_rate_func(func, a=0.4, b=0.6): def result(t): if a == b: return a @@ -55,15 +66,15 @@ def squish_rate_func(func, a = 0.4, b = 0.6): elif t > b: return func(1) else: - return func((t-a)/(b-a)) - + return func((t - a) / (b - a)) + return result # Stylistically, should this take parameters (with default values)? # Ultimately, the functionality is entirely subsumed by squish_rate_func, -# but it may be useful to have a nice name for with nice default params for +# but it may be useful to have a nice name for with nice default params for # "lingering", different from squish_rate_func's default params + + def lingering(t): return squish_rate_func(lambda t: t, 0, 0.8)(t) - - diff --git a/utils/simple_functions.py b/utils/simple_functions.py index 52acc014..1dda6ff4 100644 --- a/utils/simple_functions.py +++ b/utils/simple_functions.py @@ -1,27 +1,33 @@ import numpy as np import operator as op + def sigmoid(x): - return 1.0/(1 + np.exp(-x)) + return 1.0 / (1 + np.exp(-x)) + def choose(n, r): - if n < r: return 0 - if r == 0: return 1 - denom = reduce(op.mul, xrange(1, r+1), 1) - numer = reduce(op.mul, xrange(n, n-r, -1), 1) - return numer//denom + if n < r: + return 0 + if r == 0: + return 1 + denom = reduce(op.mul, xrange(1, r + 1), 1) + numer = reduce(op.mul, xrange(n, n - r, -1), 1) + return numer // denom # Just to have a less heavyweight name for this extremely common operation # # We may wish to have more fine-grained control over division by zero behavior # in the future (separate specifiable values for 0/0 and x/0 with x != 0), # but for now, we just allow the option to handle indeterminate 0/0. -def fdiv(a, b, zero_over_zero_value = None): - if zero_over_zero_value != None: + + +def fdiv(a, b, zero_over_zero_value=None): + if zero_over_zero_value is not None: out = np.full_like(a, zero_over_zero_value) - where = np.logical_or (a != 0, b != 0) + where = np.logical_or(a != 0, b != 0) else: out = None where = True - return np.true_divide(a, b, out = out, where = where) \ No newline at end of file + return np.true_divide(a, b, out=out, where=where) diff --git a/utils/sounds.py b/utils/sounds.py index 28eb56d8..10b1faba 100644 --- a/utils/sounds.py +++ b/utils/sounds.py @@ -1,5 +1,6 @@ import os + def play_chord(*nums): commands = [ "play", @@ -8,10 +9,10 @@ def play_chord(*nums): "--no-show-progress", "synth", ] + [ - "sin %-"+str(num) + "sin %-" + str(num) for num in nums ] + [ - "fade h 0.5 1 0.5", + "fade h 0.5 1 0.5", "> /dev/null" ] try: @@ -19,8 +20,10 @@ def play_chord(*nums): except: pass + def play_error_sound(): play_chord(11, 8, 6, 1) + def play_finish_sound(): - play_chord(12, 9, 5, 2) \ No newline at end of file + play_chord(12, 9, 5, 2) diff --git a/utils/space_ops.py b/utils/space_ops.py index c9f1506a..792d9539 100644 --- a/utils/space_ops.py +++ b/utils/space_ops.py @@ -3,12 +3,14 @@ import numpy as np from constants import OUT from constants import RIGHT -#Matrix operations +# Matrix operations -def thick_diagonal(dim, thickness = 2): + +def thick_diagonal(dim, thickness=2): row_indices = np.arange(dim).repeat(dim).reshape((dim, dim)) col_indices = np.transpose(row_indices) - return (np.abs(row_indices - col_indices)