From 399852a994b04a40f5920f284e74bfc522262076 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sun, 13 Dec 2015 15:41:45 -0800 Subject: [PATCH] Prepping for hilbert curve project --- animation/transform.py | 3 +- extract_scene.py | 19 ++- helpers.py | 3 +- hilbert.py | 338 ----------------------------------------- mobject/__init__.py | 2 +- mobject/mobject.py | 31 ++-- mobject/tex_mobject.py | 2 +- scene/scene.py | 9 +- topics/characters.py | 19 ++- topics/geometry.py | 12 -- 10 files changed, 57 insertions(+), 381 deletions(-) delete mode 100644 hilbert.py diff --git a/animation/transform.py b/animation/transform.py index 912b91c8..5a977437 100644 --- a/animation/transform.py +++ b/animation/transform.py @@ -7,8 +7,7 @@ import warnings from helpers import * from animation import Animation -from mobject import Mobject -from topics.geometry import Point +from mobject import Mobject, Point class Transform(Animation): DEFAULT_CONFIG = { diff --git a/extract_scene.py b/extract_scene.py index 08483473..71f6354d 100644 --- a/extract_scene.py +++ b/extract_scene.py @@ -45,7 +45,7 @@ def get_configuration(sys_argv): print str(err) sys.exit(2) config = { - "module" : None, + "file" : None, "scene_name" : "", "args_extension" : "", "display_config" : PRODUCTION_QUALITY_DISPLAY_CONFIG, @@ -83,7 +83,7 @@ def get_configuration(sys_argv): if len(args) == 0: print HELP_MESSAGE sys.exit() - config["module"] = args[0].replace(".py", "") + config["file"] = args[0] if len(args) > 1: config["scene_name"] = args[1] if len(args) > 2: @@ -180,17 +180,22 @@ def get_scene_classes(scene_names_to_classes, config): return scene_names_to_classes.values() return prompt_user_for_choice(scene_names_to_classes) +def get_module(file_name): + module_name = file_name.replace(".py", "") + last_module = imp.load_module(".", *imp.find_module(".")) + for part in module_name.split(os.sep): + load_args = imp.find_module(part, last_module.__path__) + last_module = imp.load_module(part, *load_args) + return last_module + def main(): config = get_configuration(sys.argv) - module = imp.load_module( - config["module"], - *imp.find_module(config["module"]) - ) + module = get_module(config["file"]) scene_names_to_classes = dict( inspect.getmembers(module, is_scene) ) - config["movie_prefix"] = config["module"] + config["movie_prefix"] = config["file"].replace(".py", "") scene_kwargs = config["display_config"] scene_kwargs["announce_construction"] = True for SceneClass in get_scene_classes(scene_names_to_classes, config): diff --git a/helpers.py b/helpers.py index 3ad53ff2..9893b518 100644 --- a/helpers.py +++ b/helpers.py @@ -214,7 +214,8 @@ def streth_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)/curr_len) + indices = np.arange(length)/ float(length) + indices *= curr_len return nparray[indices.astype('int')] def make_even(iterable_1, iterable_2): diff --git a/hilbert.py b/hilbert.py deleted file mode 100644 index dc69f3b6..00000000 --- a/hilbert.py +++ /dev/null @@ -1,338 +0,0 @@ - - -from mobject import Mobject, Mobject1D -from scene import Scene -from animation.transform import Transform -from animation.simple_animations import ShowCreation -from topics.geometry import Line, Point - -from helpers import * - -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 - - -class SpaceFillingCurve(Mobject1D): - DEFAULT_CONFIG = { - "radius" : 3, - "order" : 5, - "start_color" : RED, - "end_color" : GREEN, - } - - def generate_points(self): - points = self.get_anchor_points() - for pair in zip(points, points[1:]): - self.add_line(*pair) - self.gradient_highlight(self.start_color, self.end_color) - - def get_anchor_points(self): - raise Exception("Not implemented") - -class LindenmayerCurve(SpaceFillingCurve): - DEFAULT_CONFIG = { - "axiom" : "A", - "rule" : {}, - "scale_factor" : 2, - "radius" : 3, - "start_step" : RIGHT, - "angle" : np.pi/2, - } - - def expand_command_string(self, command): - result = "" - for letter in command: - if letter in self.rule: - result += self.rule[letter] - else: - result += letter - return result - - def get_command_string(self): - result = self.axiom - for x in range(self.order): - result = self.expand_command_string(result) - return result - - def get_anchor_points(self): - step = float(self.radius) * self.start_step - step /= (self.scale_factor**self.order) - curr = np.zeros(3) - result = [curr] - for letter in self.get_command_string(): - if letter is "+": - step = rotate(step, self.angle) - elif letter is "-": - step = rotate(step, -self.angle) - else: - curr = curr + step - result.append(curr) - return np.array(result) - center_of_mass(result) - - -class SelfSimilarSpaceFillingCurve(SpaceFillingCurve): - DEFAULT_CONFIG = { - "offsets" : [], - "offset_index_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 - offset. Generally meant to be extended in subclasses - """ - if offset in self.offset_index_to_rotation_axis: - return rotate( - points, - axis = self.offset_index_to_rotation_axis[offset] - ) - points /= self.scale_factor, - points += offset*self.radius*self.radius_scale_factor - return points - - def refine_into_subparts(self, points): - transformed_copies = [ - self.transform(points, offset) - for offset in self.offsets - ] - return reduce( - 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): - points = self.refine_into_subparts(points) - return points - - - -class HilbertCurve(SelfSimilarSpaceFillingCurve): - DEFAULT_CONFIG = { - "offsets" : [ - LEFT+DOWN, - LEFT+UP, - RIGHT+UP, - RIGHT+DOWN, - ], - "offset_index_to_rotation_axis" : { - 0 : RIGHT+UP, - 3 : RIGHT+DOWN, - }, - } - - -class HilbertCurve3D(SelfSimilarSpaceFillingCurve): - DEFAULT_CONFIG = { - "offsets" : [ - LEFT+DOWN+OUT, - LEFT+UP+OUT, - LEFT+UP+IN, - LEFT+DOWN+IN, - RIGHT+DOWN+IN, - RIGHT+UP+IN, - RIGHT+UP+OUT, - RIGHT+DOWN+OUT, - ], - "offset_index_to_rotation_axis" : {}#TODO - } - -class PeanoCurve(SelfSimilarSpaceFillingCurve): - DEFAULT_CONFIG = { - "start_color" : PURPLE, - "end_color" : TEAL, - "offsets" : [ - LEFT+DOWN, - LEFT, - LEFT+UP, - UP, - ORIGIN, - DOWN, - RIGHT+DOWN, - RIGHT, - RIGHT+UP, - ], - "offset_index_to_rotation_axis" : { - 1 : UP, - 3 : RIGHT, - 4 : LEFT+UP, - 5 : RIGHT, - 6 : UP, - }, - "scale_factor" : 3, - "radius_scale_factor" : 2.0/3, - } - -class TriangleFillingCurve(SelfSimilarSpaceFillingCurve): - DEFAULT_CONFIG = { - "start_color" : MAROON, - "end_color" : YELLOW, - "offsets" : [ - LEFT/4.+DOWN/6., - ORIGIN, - RIGHT/4.+DOWN/6., - UP/3., - ], - "offset_index_to_rotation_axis" : { - 1 : RIGHT, - 3 : UP, - }, - "scale_factor" : 2, - "radius_scale_factor" : 1.5, - } - -# class HexagonFillingCurve(SelfSimilarSpaceFillingCurve): -# DEFAULT_CONFIG = { -# "start_color" : WHITE, -# "end_color" : BLUE_D, -# "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), -# ((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), -# (RIGHT, np.sqrt(3)*RIGHT), -# ], -# "scale_factor" : 3, -# "radius_scale_factor" : 2/(3*np.sqrt(3)), -# } - -# def refine_into_subparts(self, points): -# return SelfSimilarSpaceFillingCurve.refine_into_subparts( -# self, -# rotate(points, np.pi/6, IN) -# ) - - -class UtahFillingCurve(SelfSimilarSpaceFillingCurve): - DEFAULT_CONFIG = { - "start_color" : WHITE, - "end_color" : BLUE_D, - "axis_offset_pairs" : [ - - ], - "scale_factor" : 3, - "radius_scale_factor" : 2/(3*np.sqrt(3)), - } - - -class FlowSnake(LindenmayerCurve): - DEFAULT_CONFIG = { - "start_color" : YELLOW, - "end_color" : 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, - } - def __init__(self, **kwargs): - LindenmayerCurve.__init__(self, **kwargs) - self.rotate(-self.order*np.pi/9) - -class Sierpinski(LindenmayerCurve): - DEFAULT_CONFIG = { - "start_color" : RED, - "end_color" : WHITE, - "axiom" : "A", - "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, - } - - -class SnakeCurve(SpaceFillingCurve): - DEFAULT_CONFIG = { - "start_color" : BLUE, - "end_color" : YELLOW, - } - def get_anchor_points(self, order): - result = [] - lower_left = ORIGIN + \ - LEFT*self.radius + \ - DOWN*self.radius - step = 2.0*self.radius / (order) - for y in range(order+1): - x_range = range(order+1) - if y%2 == 0: - x_range.reverse() - for x in x_range: - result.append( - lower_left + x*step*RIGHT + y*step*UP - ) - return result - - - -class SpaceFillingCurveScene(Scene): - @staticmethod - def args_to_string(CurveClass, order): - return CurveClass.__name__ + "Order" + str(order) - - @staticmethod - def string_to_args(arg_str): - curve_class_name, order_str = arg_str.split() - space_filling_curves = dict([ - (Class.__name__, Class) - for Class in get_all_descendent_classes(SpaceFillingCurve) - ]) - if curve_class_name not in space_filling_curves: - raise Exception( - "%s is not a space filling curve"%curve_class_name - ) - CurveClass = space_filling_curves[curve_class_name] - return CurveClass, int(order_str) - -class TransformOverIncreasingOrders(SpaceFillingCurveScene): - def construct(self, CurveClass, max_order): - sample = CurveClass(order = 1) - curve = Line(sample.radius*LEFT, sample.radius*RIGHT) - curve.gradient_highlight( - sample.start_color, - sample.end_color - ) - for order in range(1, max_order): - new_curve = CurveClass(order = order) - self.play( - Transform(curve, new_curve), - run_time = 3/np.sqrt(order), - ) - self.dither() - - -class DrawSpaceFillingCurve(SpaceFillingCurveScene): - def construct(self, CurveClass, order): - curve = CurveClass(order = order) - self.play(ShowCreation(curve), run_time = 10) - self.dither() - - - - - - - - - - - - - - diff --git a/mobject/__init__.py b/mobject/__init__.py index aadfc9b5..14733f40 100644 --- a/mobject/__init__.py +++ b/mobject/__init__.py @@ -4,4 +4,4 @@ __all__ = [ "tex_mobject", ] -from mobject import Mobject, Mobject1D, Mobject2D \ No newline at end of file +from mobject import Mobject, Point, Mobject1D, Mobject2D \ No newline at end of file diff --git a/mobject/mobject.py b/mobject/mobject.py index 5bd60ca3..fcec5c88 100644 --- a/mobject/mobject.py +++ b/mobject/mobject.py @@ -450,16 +450,15 @@ class Mobject(object): smaller.apply_over_attr_arrays( lambda a : streth_array_to_length(a, target_size) ) - - num_sub_mobjects1 = len(mobject1.sub_mobjects) - num_sub_mobjects2 = len(mobject2.sub_mobjects) - if num_sub_mobjects1 != num_sub_mobjects2: - diff = abs(num_sub_mobjects1 - num_sub_mobjects2) - if num_sub_mobjects1 < num_sub_mobjects2: - larger, smaller = mobject2, mobject1 - else: - larger, smaller = mobject1, mobject2 - for sub_mob in larger.sub_mobjects[-diff:]: + #Recurse + diff = len(mobject1.sub_mobjects) - len(mobject2.sub_mobjects) + + if diff < 0: + larger, smaller = mobject2, mobject1 + elif diff > 0: + larger, smaller = mobject1, mobject2 + if diff != 0: + for sub_mob in larger.sub_mobjects[-abs(diff):]: smaller.add(Point(sub_mob.get_center())) for m1, m2 in zip(mobject1.sub_mobjects, mobject2.sub_mobjects): Mobject.align_data(m1, m2) @@ -477,6 +476,18 @@ class Mobject(object): getattr(mobject2, attr), alpha)) + +class Point(Mobject): + DEFAULT_CONFIG = { + "color" : BLACK, + } + def __init__(self, location = ORIGIN, **kwargs): + digest_locals(self) + Mobject.__init__(self, **kwargs) + + def generate_points(self): + self.add_points([self.location]) + #TODO, Make the two implementations bellow non-redundant class Mobject1D(Mobject): DEFAULT_CONFIG = { diff --git a/mobject/tex_mobject.py b/mobject/tex_mobject.py index 7eb5d685..c0d66774 100644 --- a/mobject/tex_mobject.py +++ b/mobject/tex_mobject.py @@ -9,7 +9,7 @@ class TexMobject(Mobject): "template_tex_file" : TEMPLATE_TEX_FILE, "color" : WHITE, "point_thickness" : 1, - "should_center" : False, + "should_center" : True, } def __init__(self, expression, **kwargs): if "size" not in kwargs: diff --git a/scene/scene.py b/scene/scene.py index a52dece3..c47f93fe 100644 --- a/scene/scene.py +++ b/scene/scene.py @@ -38,8 +38,9 @@ class Scene(object): (self.height, self.width, 3), dtype = 'uint8' ) - self.background = self.original_background - self.frames = [self.background] + self.background = self.original_background + self.curr_frame = self.background + self.frames = [self.curr_frame] self.mobjects = [] self.construct(*self.construct_args) @@ -58,10 +59,10 @@ class Scene(object): return self def get_frame(self): - return self.frames[-1] + return self.curr_frame def set_frame(self, frame): - self.frames[-1] = frame + self.curr_frame = frame return self def add(self, *mobjects): diff --git a/topics/characters.py b/topics/characters.py index 09d8a095..2cdc07b2 100644 --- a/topics/characters.py +++ b/topics/characters.py @@ -1,6 +1,9 @@ from helpers import * -from mobject import Mobject, Mobject, ImageMobject, TexMobject +from mobject import Mobject +from mobject.image_mobject import ImageMobject +from mobject.tex_mobject import TexMobject +from topics.geometry import Circle PI_CREATURE_DIR = os.path.join(IMAGE_DIR, "PiCreature") @@ -163,7 +166,6 @@ class Mortimer(PiCreature): class Bubble(Mobject): DEFAULT_CONFIG = { "direction" : LEFT, - "index_of_tip" : -1, "center_point" : ORIGIN, } def __init__(self, **kwargs): @@ -174,7 +176,7 @@ class Bubble(Mobject): self.content = Mobject() def get_tip(self): - return self.points[self.index_of_tip] + raise Exception("Not implemented") def get_bubble_center(self): return self.get_center()+self.center_offset @@ -245,6 +247,9 @@ class SpeechBubble(Bubble): self.rotate(np.pi/2) self.points[:,1] *= float(self.initial_height)/self.initial_width + def get_tip(self): + pass + class ThoughtBubble(Bubble): DEFAULT_CONFIG = { "num_bulges" : 7, @@ -253,10 +258,14 @@ class ThoughtBubble(Bubble): } def __init__(self, **kwargs): Bubble.__init__(self, **kwargs) - self.index_of_tip = np.argmin(self.points[:,1]) + + def get_tip(self): + return self.small_circle.get_bottom() def generate_points(self): - self.add(Circle().scale(0.15).shift(2.5*DOWN+2*LEFT)) + self.small_circle = Circle().scale(0.15) + self.small_circle.shift(2.5*DOWN+2*LEFT) + self.add(self.small_circle) self.add(Circle().scale(0.3).shift(2*DOWN+1.5*LEFT)) for n in range(self.num_bulges): theta = 2*np.pi*n/self.num_bulges diff --git a/topics/geometry.py b/topics/geometry.py index dceafb06..77858c0f 100644 --- a/topics/geometry.py +++ b/topics/geometry.py @@ -2,18 +2,6 @@ from helpers import * from mobject import Mobject, Mobject1D - -class Point(Mobject): - DEFAULT_CONFIG = { - "color" : BLACK, - } - def __init__(self, location = ORIGIN, **kwargs): - digest_locals(self) - Mobject.__init__(self, **kwargs) - - def generate_points(self): - self.add_points([self.location]) - class Dot(Mobject1D): #Use 1D density, even though 2D DEFAULT_CONFIG = { "radius" : 0.05