From 850b6f9e888e3799a64307f6276495130600bf6d Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sat, 20 Jan 2018 10:56:52 -0800 Subject: [PATCH 01/11] Initial edits to make main movement methods take in about_point and about_edge kwargs --- mobject/mobject.py | 79 ++++++++++++++++++++++++---------------------- 1 file changed, 41 insertions(+), 38 deletions(-) diff --git a/mobject/mobject.py b/mobject/mobject.py index 8f7e0ae1..5ad8afcf 100644 --- a/mobject/mobject.py +++ b/mobject/mobject.py @@ -138,7 +138,7 @@ class Mobject(object): mob.points += total_vector return self - def scale(self, scale_factor, about_point = None, about_edge = ORIGIN): + def scale(self, scale_factor, **kwargs): """ Default behavior is to scale about the center of the mobject. The argument about_edge can be a vector, indicating which side of @@ -148,46 +148,41 @@ class Mobject(object): Otherwise, if about_point is given a value, scaling is done with respect to that point. """ - if about_point is None: - about_point = self.get_critical_point(about_edge) - self.shift(-about_point) - for mob in self.family_members_with_points(): - mob.points *= scale_factor - self.shift(about_point) + self.apply_points_function_about_point( + lambda points : scale_factor*points, **kwargs + ) return self def rotate_about_origin(self, angle, axis = OUT, axes = []): - if len(axes) == 0: - axes = [axis] - rot_matrix = np.identity(self.dim) - for axis in axes: - rot_matrix = np.dot(rot_matrix, rotation_matrix(angle, axis)) - t_rot_matrix = np.transpose(rot_matrix) - for mob in self.family_members_with_points(): - mob.points = np.dot(mob.points, t_rot_matrix) + return self.rotate(angle, axis, about_point = ORIGIN) + + def rotate(self, angle, axis = OUT, **kwargs): + rot_matrix = rotation_matrix(angle, axis) + self.apply_points_function_about_point( + lambda points : np.dot(mob.points, rot_matrix.T), + **kwargs + ) return self - def rotate(self, angle, axis = OUT, axes = [], about_point = None): - if about_point is None: - self.rotate_about_origin(angle, axis, axes) - else: - self.do_about_point(about_point, self.rotate, angle, axis, axes) + def stretch(self, factor, dim, **kwargs): + def func(points): + points[:,dim] *= factor + return points + self.apply_points_function_about_point(func, about_point) return self - def stretch(self, factor, dim): - for mob in self.family_members_with_points(): - mob.points[:,dim] *= factor + def apply_function(self, function, about_point = ORIGIN, **kwargs): + self.apply_points_function_about_point( + lambda points : np.apply_along_axis(function, 1, points), + about_point = about_point, **kwargs + ) return self - def apply_function(self, function): - for mob in self.family_members_with_points(): - mob.points = np.apply_along_axis(function, 1, mob.points) - return self - - def apply_matrix(self, matrix): - matrix = np.array(matrix) - for mob in self.family_members_with_points(): - mob.points = np.dot(mob.points, matrix.T) + def apply_matrix(self, matrix, about_point = ORIGIN, **kwargs): + self.apply_points_function_about_point( + lambda points : np.dot(points, matrix.T), + about_point = about_point, **kwargs + ) return self def wag(self, direction = RIGHT, axis = DOWN, wag_factor = 1.0): @@ -224,6 +219,15 @@ class Mobject(object): #### In place operations ###### + 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(): + mob.points -= about_point + mob.points = func(mob.points) + mob.points += about_point + return self + def do_about_point(self, point, method, *args, **kwargs): self.shift(-point) method(*args, **kwargs) @@ -235,8 +239,8 @@ class Mobject(object): return self def rotate_in_place(self, angle, axis = OUT, axes = []): - self.do_in_place(self.rotate, angle, axis, axes) - return self + # redundant with default behavior of rotate now. + return self.rotate(angle, axis = axis, axes = axes) def flip(self, axis = UP): self.rotate_in_place(np.pi, axis) @@ -244,12 +248,11 @@ class Mobject(object): def scale_in_place(self, scale_factor): #Redundant with default behavior of scale now. - self.do_in_place(self.scale, scale_factor) - return self + return self.scale(scale_factor) def scale_about_point(self, scale_factor, point): - self.do_about_point(point, self.scale, scale_factor) - return self + #Redundant with default behavior of scale now. + return self.scale(scale_factor, about_point = point) def pose_at_angle(self): self.rotate_in_place(np.pi / 7, RIGHT+UP) From 48dad34f957abce4cefec06052fefe5325e39ac2 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sat, 20 Jan 2018 11:00:23 -0800 Subject: [PATCH 02/11] Editing ApplyMethod so that if the last arg is a dict, it's treated as kwargs for the method --- animation/transform.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/animation/transform.py b/animation/transform.py index e63b8586..e47e6c3c 100644 --- a/animation/transform.py +++ b/animation/transform.py @@ -143,7 +143,13 @@ class ApplyMethod(Transform): "the method you want to animate" ) assert(isinstance(method.im_self, Mobject)) - method_kwargs = kwargs.get("method_kwargs", {}) + args = list(args) #So that args.pop() works + if "method_kwargs" in kwargs: + method_kwargs = kwargs["method_kwargs"] + elif isinstance(args[-1], dict): + method_kwargs = args.pop() + else: + method_kwargs = {} target = method.im_self.copy() method.im_func(target, *args, **method_kwargs) Transform.__init__(self, method.im_self, target, **kwargs) From fcd1e7d6a53d2bae495cfe1eaa11a2c099ed53a5 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sat, 20 Jan 2018 11:18:43 -0800 Subject: [PATCH 03/11] A few small fixes to new about_point behavior --- mobject/mobject.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/mobject/mobject.py b/mobject/mobject.py index 5ad8afcf..a16c6993 100644 --- a/mobject/mobject.py +++ b/mobject/mobject.py @@ -159,7 +159,7 @@ class Mobject(object): def rotate(self, angle, axis = OUT, **kwargs): rot_matrix = rotation_matrix(angle, axis) self.apply_points_function_about_point( - lambda points : np.dot(mob.points, rot_matrix.T), + lambda points : np.dot(points, rot_matrix.T), **kwargs ) return self @@ -168,20 +168,29 @@ class Mobject(object): def func(points): points[:,dim] *= factor return points - self.apply_points_function_about_point(func, about_point) + self.apply_points_function_about_point(func, **kwargs) return self - def apply_function(self, function, about_point = ORIGIN, **kwargs): + def apply_function(self, function, **kwargs): + #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), about_point = about_point, **kwargs ) return self - def apply_matrix(self, matrix, about_point = ORIGIN, **kwargs): + def apply_matrix(self, matrix, **kwargs): + #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 self.apply_points_function_about_point( - lambda points : np.dot(points, matrix.T), - about_point = about_point, **kwargs + lambda points : np.dot(points, full_matrix.T), + **kwargs ) return self From d07efc6cb5ad2b970ac90ae4bb82c30a0bd113a7 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sat, 20 Jan 2018 11:19:12 -0800 Subject: [PATCH 04/11] When calling Scene.play on a method followed by its args, if you end the list with a dict, it will interpret it as the kwargs of the method. --- scene/scene.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/scene/scene.py b/scene/scene.py index cd4892eb..15bd48bc 100644 --- a/scene/scene.py +++ b/scene/scene.py @@ -340,8 +340,9 @@ class Scene(object): def compile_play_args_to_animation_list(self, *args): """ - Eacn arg can either be an animation, or a mobject method - followed by that methods arguments. + Each arg can either be an animation, or a mobject method + followed by that methods arguments (and potentially follow + by a dict of kwargs for that method). This animation list is built by going through the args list, and each animation is simply added, but when a mobject method @@ -364,8 +365,15 @@ class Scene(object): #method should already have target then. else: mobject.target = mobject.copy() + # + if isinstance(state["method_args"][-1], dict): + method_kwargs = state["method_args"].pop() + else: + method_kwargs = {} state["curr_method"].im_func( - mobject.target, *state["method_args"] + mobject.target, + *state["method_args"], + **method_kwargs ) animations.append(MoveToTarget(mobject)) state["last_method"] = state["curr_method"] @@ -482,7 +490,7 @@ class Scene(object): path = os.path.join(self.output_directory, folder) file_name = (name or str(self)) + ".png" return os.path.join(path, file_name) - + 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) From 7c9f5ca71155d2cc6f88c3bdef844f71005ff11d Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sat, 20 Jan 2018 11:33:09 -0800 Subject: [PATCH 05/11] Updated the scale_to_fit Mobject methods based on new about_point and about_edge convention --- mobject/mobject.py | 68 ++++++++++++++++++++++++---------------------- 1 file changed, 36 insertions(+), 32 deletions(-) diff --git a/mobject/mobject.py b/mobject/mobject.py index a16c6993..b3a5611e 100644 --- a/mobject/mobject.py +++ b/mobject/mobject.py @@ -164,6 +164,9 @@ class Mobject(object): ) return self + 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 @@ -194,6 +197,12 @@ class Mobject(object): ) return self + def apply_complex_function(self, function, **kwargs): + return self.apply_function( + lambda (x, y, z) : complex_to_R3(function(complex(x, y))), + **kwargs + ) + 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)) @@ -227,6 +236,8 @@ class Mobject(object): return self #### In place operations ###### + #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): if about_point is None: @@ -251,22 +262,20 @@ class Mobject(object): # redundant with default behavior of rotate now. return self.rotate(angle, axis = axis, axes = axes) - def flip(self, axis = UP): - self.rotate_in_place(np.pi, axis) - return self - - def scale_in_place(self, scale_factor): + def scale_in_place(self, scale_factor, **kwargs): #Redundant with default behavior of scale now. - return self.scale(scale_factor) + 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) - def pose_at_angle(self): - self.rotate_in_place(np.pi / 7, RIGHT+UP) + def pose_at_angle(self, **kwargs): + self.rotate(TAU/14, RIGHT+UP, **kwargs) return self + #### Positioning methods #### + def center(self): self.shift(-self.get_center()) return self @@ -350,42 +359,41 @@ class Mobject(object): return False def stretch_about_point(self, factor, dim, point): - self.do_about_point(point, self.stretch, factor, dim) - return self + return self.stretch(factor, dim, about_point = point) def stretch_in_place(self, factor, dim): - self.do_in_place(self.stretch, factor, dim) - return self + #Now redundant with stretch + return self.stretch(factor, dim) - def rescale_to_fit(self, length, dim, stretch = False): + 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_in_place(length/old_length, dim) + self.stretch(length/old_length, dim, **kwargs) else: - self.scale_in_place(length/old_length) + self.scale(length/old_length, **kwargs) return self - def stretch_to_fit_width(self, width): - return self.rescale_to_fit(width, 0, stretch = True) + def stretch_to_fit_width(self, width, **kwargs): + return self.rescale_to_fit(width, 0, stretch = True, **kwargs) - def stretch_to_fit_height(self, height): - return self.rescale_to_fit(height, 1, stretch = True) + def stretch_to_fit_height(self, height, **kwargs): + return self.rescale_to_fit(height, 1, stretch = True, **kwargs) - def scale_to_fit_width(self, width): - return self.rescale_to_fit(width, 0, stretch = False) + def scale_to_fit_width(self, width, **kwargs): + return self.rescale_to_fit(width, 0, stretch = False, **kwargs) - def scale_to_fit_height(self, height): - return self.rescale_to_fit(height, 1, stretch = False) + def scale_to_fit_height(self, height, **kwargs): + return self.rescale_to_fit(height, 1, stretch = False, **kwargs) - def scale_to_fit_depth(self, depth): - return self.rescale_to_fit(depth, 2, stretch = False) + def scale_to_fit_depth(self, depth, **kwargs): + return self.rescale_to_fit(depth, 2, stretch = False, **kwargs) def space_out_submobjects(self, factor = 1.5, **kwargs): - self.scale_in_place(factor) + self.scale(factor, **kwargs) for submob in self.submobjects: - submob.scale_in_place(1./factor) + submob.scale(1./factor) return self def move_to(self, point_or_mobject, aligned_edge = ORIGIN): @@ -521,11 +529,7 @@ class Mobject(object): sm1.interpolate(sm1, sm2, 1) return self - def apply_complex_function(self, function, **kwargs): - return self.apply_function( - lambda (x, y, z) : complex_to_R3(function(complex(x, y))), - **kwargs - ) + ## def reduce_across_dimension(self, points_func, reduce_func, dim): try: From 1ffbc77eda0c9d62de5b4196a499f6d42ed03620 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sat, 20 Jan 2018 11:45:47 -0800 Subject: [PATCH 06/11] Bug fix to ApplyMethod treatment of method kwargs --- animation/transform.py | 2 +- mobject/mobject.py | 2 +- scene/scene.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/animation/transform.py b/animation/transform.py index e47e6c3c..799b776f 100644 --- a/animation/transform.py +++ b/animation/transform.py @@ -146,7 +146,7 @@ class ApplyMethod(Transform): args = list(args) #So that args.pop() works if "method_kwargs" in kwargs: method_kwargs = kwargs["method_kwargs"] - elif isinstance(args[-1], dict): + elif len(args) > 0 and isinstance(args[-1], dict): method_kwargs = args.pop() else: method_kwargs = {} diff --git a/mobject/mobject.py b/mobject/mobject.py index b3a5611e..54a23dea 100644 --- a/mobject/mobject.py +++ b/mobject/mobject.py @@ -180,7 +180,7 @@ class Mobject(object): kwargs["about_point"] = ORIGIN self.apply_points_function_about_point( lambda points : np.apply_along_axis(function, 1, points), - about_point = about_point, **kwargs + **kwargs ) return self diff --git a/scene/scene.py b/scene/scene.py index 15bd48bc..fb4ba6ca 100644 --- a/scene/scene.py +++ b/scene/scene.py @@ -366,7 +366,7 @@ class Scene(object): else: mobject.target = mobject.copy() # - if isinstance(state["method_args"][-1], dict): + if len(state["method_args"]) > 0 and isinstance(state["method_args"][-1], dict): method_kwargs = state["method_args"].pop() else: method_kwargs = {} From c5b0e260904aa261b3993cb5f784358967a51a1f Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sat, 20 Jan 2018 13:14:17 -0800 Subject: [PATCH 07/11] Addressed places which depended on rotate being about the origin --- eop/bayes.py | 2 +- eop/bayes_footnote.py | 2 +- eop/independence.py | 2 +- mobject/mobject.py | 11 ----------- mobject/svg_mobject.py | 2 +- topics/fractals.py | 12 ++++++------ topics/graph_theory.py | 2 +- topics/number_line.py | 9 ++++++--- topics/objects.py | 6 +++--- topics/vector_space_scene.py | 4 ++-- 10 files changed, 22 insertions(+), 30 deletions(-) diff --git a/eop/bayes.py b/eop/bayes.py index 0cb25d40..b916f2b6 100644 --- a/eop/bayes.py +++ b/eop/bayes.py @@ -2089,7 +2089,7 @@ class MusicExample(SampleSpaceScene, PiCreatureScene): notes = VGroup(*[note.copy() for x in range(10)]) sine_wave = FunctionGraph(np.sin, x_min = -5, x_max = 5) sine_wave.scale(0.75) - sine_wave.rotate(np.pi/6) + sine_wave.rotate(np.pi/6, about_point = ORIGIN) sine_wave.shift( notes.get_center() - \ sine_wave.point_from_proportion(0) diff --git a/eop/bayes_footnote.py b/eop/bayes_footnote.py index cdd5e49b..dd978212 100644 --- a/eop/bayes_footnote.py +++ b/eop/bayes_footnote.py @@ -1348,7 +1348,7 @@ class IntroduceTelepathyExample(StatisticsVsEmpathy): vect[1] = 0 arc = Arc(angle = angle) - arc.rotate(-angle/2 + angle_of_vector(vect)) + arc.rotate(-angle/2 + angle_of_vector(vect), about_point = ORIGIN) arc.scale(3) arcs = VGroup(*[arc.copy() for x in range(n_arcs)]) arcs.move_to(pi2.eyes.get_center(), vect) diff --git a/eop/independence.py b/eop/independence.py index e393263a..eaabbdef 100644 --- a/eop/independence.py +++ b/eop/independence.py @@ -3072,7 +3072,7 @@ class CorrectForDependence(NameBinomial): for value, alt_value, bar in zip(values, alt_values, bars): arrow = arrow_template.copy() if value < alt_value: - arrow.rotate(np.pi) + arrow.rotate(np.pi, about_point = ORIGIN) arrow.next_to(bar, UP) arrows.add(arrow) diff --git a/mobject/mobject.py b/mobject/mobject.py index 54a23dea..a68e75e0 100644 --- a/mobject/mobject.py +++ b/mobject/mobject.py @@ -248,16 +248,6 @@ class Mobject(object): mob.points += about_point return self - def do_about_point(self, point, method, *args, **kwargs): - self.shift(-point) - method(*args, **kwargs) - self.shift(point) - return self - - def do_in_place(self, method, *args, **kwargs): - self.do_about_point(self.get_center(), method, *args, **kwargs) - return self - def rotate_in_place(self, angle, axis = OUT, axes = []): # redundant with default behavior of rotate now. return self.rotate(angle, axis = axis, axes = axes) @@ -712,7 +702,6 @@ class Mobject(object): """ raise Exception("Not implemented") - def align_points(self, mobject): count1 = self.get_num_points() count2 = mobject.get_num_points() diff --git a/mobject/svg_mobject.py b/mobject/svg_mobject.py index 817e3b92..5c237a19 100644 --- a/mobject/svg_mobject.py +++ b/mobject/svg_mobject.py @@ -218,7 +218,7 @@ class VMobjectFromSVGPathstring(VMobject): for command, coord_string in pairs: self.handle_command(command, coord_string) #people treat y-coordinate differently - self.rotate(np.pi, RIGHT) + self.rotate(np.pi, RIGHT, about_point = ORIGIN) def handle_command(self, command, coord_string): isLower = command.islower() diff --git a/topics/fractals.py b/topics/fractals.py index e28f6fee..c81c52a1 100644 --- a/topics/fractals.py +++ b/topics/fractals.py @@ -127,7 +127,7 @@ class DiamondFractal(SelfSimilarFractal): # 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) + VGroup(*subparts).rotate(np.pi/4, about_point = ORIGIN) class PentagonalFractal(SelfSimilarFractal): @@ -142,7 +142,7 @@ class PentagonalFractal(SelfSimilarFractal): 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) + part.rotate(2*np.pi*x/5, about_point = ORIGIN) class PentagonalPiCreatureFractal(PentagonalFractal): def init_colors(self): @@ -163,7 +163,7 @@ class PentagonalPiCreatureFractal(PentagonalFractal): def arrange_subparts(self, *subparts): for part in subparts: - part.rotate(2*np.pi/5) + part.rotate(2*np.pi/5, about_point = ORIGIN) PentagonalFractal.arrange_subparts(self, *subparts) @@ -236,7 +236,7 @@ class WonkyHexagonFractal(SelfSimilarFractal): def arrange_subparts(self, *subparts): for i, piece in enumerate(subparts): - piece.rotate(i*np.pi/12) + 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) @@ -265,7 +265,7 @@ class CircularFractal(SelfSimilarFractal): ORIGIN, UP, buff = self.height/(2*np.tan(theta)) ) - part.rotate(i*2*np.pi/self.num_subparts) + part.rotate(i*2*np.pi/self.num_subparts, about_point = ORIGIN) self.num_subparts -= 1 @@ -548,7 +548,7 @@ class FlowSnake(LindenmayerCurve): } def __init__(self, **kwargs): LindenmayerCurve.__init__(self, **kwargs) - self.rotate(-self.order*np.pi/9) + self.rotate(-self.order*np.pi/9, about_point = ORIGIN) class SierpinskiCurve(LindenmayerCurve): CONFIG = { diff --git a/topics/graph_theory.py b/topics/graph_theory.py index 99462995..bfa53c6d 100644 --- a/topics/graph_theory.py +++ b/topics/graph_theory.py @@ -260,7 +260,7 @@ class GraphScene(Scene): 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).shift(edge.get_center()) + mobject.copy().rotate(angle).move_to(edge.get_center()) for angle, edge in zip(angles, self.edges) ] if fade_in: diff --git a/topics/number_line.py b/topics/number_line.py index 83b6d2cd..e5ced347 100644 --- a/topics/number_line.py +++ b/topics/number_line.py @@ -169,12 +169,15 @@ class Axes(VGroup): 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) + 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) - self.z_axis.rotate(angle_of_vector(self.z_normal), OUT) + 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 + ) self.add(self.z_axis) def get_axis(self, min_val, max_val, extra_config): diff --git a/topics/objects.py b/topics/objects.py index 66434722..c8c4d13a 100644 --- a/topics/objects.py +++ b/topics/objects.py @@ -94,7 +94,7 @@ class Speedometer(VMobject): ) needle.stretch_to_fit_width(self.needle_width) needle.stretch_to_fit_height(self.needle_height) - needle.rotate(start_angle - np.pi/2) + needle.rotate(start_angle - np.pi/2, about_point = ORIGIN) self.add(needle) self.needle = needle @@ -245,8 +245,8 @@ class Laptop(VGroup): self.axis = axis self.add(body, screen_plate, axis) - self.rotate(5*np.pi/12, LEFT) - self.rotate(np.pi/6, DOWN) + self.rotate(5*np.pi/12, LEFT, about_point = ORIGIN) + self.rotate(np.pi/6, DOWN, about_point = ORIGIN) class PatreonLogo(SVGMobject): CONFIG = { diff --git a/topics/vector_space_scene.py b/topics/vector_space_scene.py index 0e5f6f03..9c41b62d 100644 --- a/topics/vector_space_scene.py +++ b/topics/vector_space_scene.py @@ -104,12 +104,12 @@ class VectorScene(Scene): angle = vector.get_angle() if not rotate: - label.rotate(-angle) + label.rotate(-angle, about_point = ORIGIN) if direction is "left": label.shift(-label.get_bottom() + 0.1*UP) else: label.shift(-label.get_top() + 0.1*DOWN) - label.rotate(angle) + label.rotate(angle, about_point = ORIGIN) label.shift((vector.get_end() - vector.get_start())/2) return label From 9be177f47804addc8db54aeb00c2f03d5cdbb8f8 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sat, 20 Jan 2018 13:19:43 -0800 Subject: [PATCH 08/11] More Fourier progress --- active_projects/fourier.py | 72 ++++++++++++++++++++++++++++++++------ 1 file changed, 61 insertions(+), 11 deletions(-) diff --git a/active_projects/fourier.py b/active_projects/fourier.py index 0e32265e..481e369f 100644 --- a/active_projects/fourier.py +++ b/active_projects/fourier.py @@ -539,7 +539,7 @@ class UnmixMixedPaint(Scene): def construct(self): angles = np.arange(4)*np.pi/2 quadrants = VGroup(*[ - Quadrant().rotate(angle).highlight(color) + Quadrant().rotate(angle, about_point = ORIGIN).highlight(color) for color, angle in zip(self.colors, angles) ]) quadrants.add(*it.chain(*[ @@ -1120,9 +1120,9 @@ class DrawFrequencyPlot(WrapCosineGraphAroundCircle, PiCreatureScene): self.change_to_various_frequencies() self.introduce_frequency_plot() self.draw_full_frequency_plot() - self.recap_objects_on_screen() - self.lower_graph() - self.label_as_almost_fourier() + # self.recap_objects_on_screen() + # self.lower_graph() + # self.label_as_almost_fourier() def setup_graph(self): self.add(self.get_time_axes()) @@ -1261,6 +1261,15 @@ class DrawFrequencyPlot(WrapCosineGraphAroundCircle, PiCreatureScene): stroke_width = 6, color = fourier_graph.get_color() ) + fourier_graph_copy = fourier_graph.copy() + max_freq = self.frequency_axes.x_max + def update_fourier_graph(fg): + freq = self.graph.polarized_mobject.frequency + fg.pointwise_become_partial( + fourier_graph_copy, + 0, freq/max_freq + ) + return fg self.change_frequency(0.0) self.generate_fourier_dot_transform(fourier_graph) @@ -1275,13 +1284,8 @@ class DrawFrequencyPlot(WrapCosineGraphAroundCircle, PiCreatureScene): fourier_graph.restore() self.change_frequency( freq, - added_anims = [ShowCreation( - fourier_graph, - rate_func = lambda t : interpolate( - (freq-1.)/f_max, - float(freq)/f_max, - smooth(t) - ), + added_anims = [UpdateFromFunc( + fourier_graph, update_fourier_graph )], run_time = 5, ) @@ -1444,10 +1448,56 @@ class StudentsHorrifiedAtScene(TeacherStudentsScene): class ShowLinearity(DrawFrequencyPlot): + CONFIG = { + "lower_signal_frequency" : 2.0, + "lower_signal_color" : PINK, + } def construct(self): + self.setup_all_axes() self.show_lower_frequency_signal() self.play_with_lower_frequency_signal() self.point_out_fourier_spike() + self.show_sum_of_signals() + self.play_with_sum_signal() + self.point_out_two_spikes() + + def setup_all_axes(self): + self.add(self.get_time_axes()) + self.add(self.get_circle_plane()) + self.add(self.get_frequency_axes()) + self.remove(self.pi_creature) + + def show_lower_frequency_signal(self): + axes = self.time_axes + start_graph = self.get_cosine_wave(freq = self.signal_frequency) + graph = self.get_cosine_wave(freq = self.lower_signal_frequency) + graph.highlight(self.lower_signal_color) + start_graph.generate_target() + start_graph.target.stretch( + + ) + + self.add(start_graph) + self.play(ReplacementTransform( + start_graph, graph, run_time = 3 + )) + self.wait() + + def play_with_lower_frequency_signal(self): + pass + + def point_out_fourier_spike(self): + pass + + def show_sum_of_signals(self): + pass + + def play_with_sum_signal(self): + pass + + def point_out_two_spikes(self): + pass + From 5f3c7749528aed8b637906188c2e9cc0724d8b8f Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sat, 20 Jan 2018 13:44:14 -0800 Subject: [PATCH 09/11] Fixed bug with how Brace gets created --- mobject/tex_mobject.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mobject/tex_mobject.py b/mobject/tex_mobject.py index bfec9916..fe5a3cdd 100644 --- a/mobject/tex_mobject.py +++ b/mobject/tex_mobject.py @@ -211,7 +211,7 @@ class Brace(TexMobject): def __init__(self, mobject, direction = DOWN, **kwargs): digest_config(self, kwargs, locals()) angle = -np.arctan2(*direction[:2]) + np.pi - mobject.rotate(-angle) + mobject.rotate(-angle, about_point = ORIGIN) left = mobject.get_corner(DOWN+LEFT) right = mobject.get_corner(DOWN+RIGHT) target_width = right[0]-left[0] @@ -227,7 +227,7 @@ class Brace(TexMobject): self.stretch_to_fit_width(target_width) self.shift(left - self.get_corner(UP+LEFT) + self.buff*DOWN) for mob in mobject, self: - mob.rotate(angle) + mob.rotate(angle, about_point = ORIGIN) def put_at_tip(self, mob, use_next_to = True, **kwargs): if use_next_to: From aef961ca0ae4d732e5b37bad46f47d87a3ebf5d6 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sat, 20 Jan 2018 13:44:28 -0800 Subject: [PATCH 10/11] Further incremental fourier work --- active_projects/fourier.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/active_projects/fourier.py b/active_projects/fourier.py index 481e369f..3a05c909 100644 --- a/active_projects/fourier.py +++ b/active_projects/fourier.py @@ -659,8 +659,10 @@ class FourierMachineScene(Scene): "y_axis_config" : {"unit_size" : 0.8}, }, "circle_plane_config" : { - "x_radius" : 2, - "y_radius" : 2, + "x_radius" : 2.5, + "y_radius" : 2.5, + "x_unit_size" : 0.8, + "y_unit_size" : 0.8, }, "frequency_axes_config" : { "number_line_config" : { @@ -1120,9 +1122,9 @@ class DrawFrequencyPlot(WrapCosineGraphAroundCircle, PiCreatureScene): self.change_to_various_frequencies() self.introduce_frequency_plot() self.draw_full_frequency_plot() - # self.recap_objects_on_screen() - # self.lower_graph() - # self.label_as_almost_fourier() + self.recap_objects_on_screen() + self.lower_graph() + self.label_as_almost_fourier() def setup_graph(self): self.add(self.get_time_axes()) From 5a8a746b03b237b9fa107277c65c5134047fe60e Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sat, 20 Jan 2018 15:29:50 -0800 Subject: [PATCH 11/11] Changed README and example_scenes for updated instructions on usage. --- README.md | 14 +++++++++----- example_scenes.py | 9 +++++++-- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 272a318c..ce9ede71 100644 --- a/README.md +++ b/README.md @@ -32,16 +32,20 @@ This doesn't install freetype, but I don't think it's required for this project ## How to Use Try running the following: ```sh -python extract_scene.py -p example_scenes.py SquareToCircle +python extract_scene.py example_scenes.py SquareToCircle -p ``` -`-p` gives a preview of an animation, `-w` will write it to a file, and `-s` will show/save the final image in the animation. +The -p is for previewing, meaning the the video file will automatically open when it is done rendering. +Use -l for a faster rendering at a lower quality. +Use -s to skip to the end and just show the final frame. +Use -n to skip ahead to the n'th animation of a scene. +Use -f to show the file in finder (for osx) -You will probably want to change the ANIMATIONS_DIR constant to be whatever direction you want video files to output to. +You will probably want to change the ANIMATIONS_DIR constant to be whatever directory you want video files to output to. -Look through the old_projects folder to see the code for previous 3b1b videos. +Look through the old_projects folder to see the code for previous 3b1b videos. Note, however, that developments are often made to the library without considering backwards compatibility on those old_projects. To run them with a guarantee that they will work, you will have to go back to the commit which complete that project. -While developing a scene, the `-s` flag is helpful to just see what things look like at the end without having to generate the full animation. It can also be helpful to put `self.force_skipping()` at the top of the construct method, and `self.revert_to_original_skipping_status()` before the portion of the scene that you want to test, and run with the `-p` flag to just see a preview of one part of the scene. +While developing a scene, the `-s` flag is helpful to just see what things look like at the end without having to generate the full animation. It can also be helpful to use the -n flag to skip over some number of animations. Scene with `PiCreatures` are somewhat 3b1b specific, so the specific designs for various expressions are not part of the public repo. You should still be able to run them, but they will fall back on using the "plain" expression for the creature. diff --git a/example_scenes.py b/example_scenes.py index e03a810b..6855b3cd 100644 --- a/example_scenes.py +++ b/example_scenes.py @@ -23,8 +23,13 @@ from mobject.tex_mobject import * from mobject.vectorized_mobject import * -## To watch one of these scenes, run the following: -## python extract_scene.py file_name -p +# 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 +# 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. class SquareToCircle(Scene):