diff --git a/animation/simple_animations.py b/animation/simple_animations.py index 3801d3d6..74415ecb 100644 --- a/animation/simple_animations.py +++ b/animation/simple_animations.py @@ -12,7 +12,7 @@ class Rotating(Animation): def __init__(self, mobject, axis = None, - axes = [[0, 0, 1], [0, 1, 0]], + axes = [RIGHT, UP], radians = 2 * np.pi, run_time = 20.0, alpha_func = None, @@ -35,7 +35,7 @@ class Rotating(Animation): ) class RotationAsTransform(Rotating): - def __init__(self, mobject, radians, axis = (0, 0, 1), axes = None, + def __init__(self, mobject, radians, axis = IN, axes = None, run_time = DEFAULT_ANIMATION_RUN_TIME, alpha_func = high_inflection_0_to_1, *args, **kwargs): diff --git a/animation/transform.py b/animation/transform.py index e6f004d3..368cb312 100644 --- a/animation/transform.py +++ b/animation/transform.py @@ -119,7 +119,7 @@ class ApplyMethod(Transform): ) class ApplyFunction(Transform): - def __init__(self, function, mobject, run_time = DEFAULT_ANIMATION_RUN_TIME, + def __init__(self, mobject, function, run_time = DEFAULT_ANIMATION_RUN_TIME, *args, **kwargs): map_image = copy.deepcopy(mobject) map_image.points = np.array(map(function, map_image.points)) diff --git a/displayer.py b/displayer.py index d9206a45..d3f3b1c4 100644 --- a/displayer.py +++ b/displayer.py @@ -49,10 +49,10 @@ def paint_mobject(mobject, image_array = None): #Flips y-axis points[:,1] *= -1 #Removes points out of space - points = points[ - (abs(points[:,0]) < SPACE_WIDTH) & - (abs(points[:,1]) < SPACE_HEIGHT) - ] + to_remove = (abs(points[:,0]) < SPACE_WIDTH) & \ + (abs(points[:,1]) < SPACE_HEIGHT) + points = points[to_remove] + rgbs = rgbs[to_remove] #Map points to pixel space, then create pixel array first in terms #of its flattened version try: diff --git a/helpers.py b/helpers.py index b9d4bf2f..ff8d10c2 100644 --- a/helpers.py +++ b/helpers.py @@ -116,10 +116,10 @@ def high_inflection_0_to_1(t, inflection = 10.0): return (sigmoid(inflection*(t - 0.5)) - error) / (1 - 2*error) def rush_into(t): - return 2*high_inflection_0_to_1(t/2) + return 2*high_inflection_0_to_1(t/2.0) def rush_from(t): - return 2*high_inflection_0_to_1(t/2+0.5) - 1 + return 2*high_inflection_0_to_1(t/2.0+0.5) - 1 def there_and_back(t, inflection = 10.0): new_t = 2*t if t < 0.5 else 2*(1 - t) diff --git a/mobject/creatures.py b/mobject/creatures.py index a7e367d9..4bc14dfe 100644 --- a/mobject/creatures.py +++ b/mobject/creatures.py @@ -18,7 +18,7 @@ class PiCreature(Mobject): 'arm', 'body', 'left_eye', - 'right_eye', + 'right_eye', 'left_leg', 'right_leg', 'mouth', @@ -70,6 +70,7 @@ class PiCreature(Mobject): self.rewire_part_attributes(self_from_parts = True) def highlight(self, color, condition = None): + self.rewire_part_attributes() if condition is not None: Mobject.highlight(self, color, condition) return self diff --git a/mobject/function_graphs.py b/mobject/function_graphs.py index 1525a6ac..40095efd 100644 --- a/mobject/function_graphs.py +++ b/mobject/function_graphs.py @@ -2,6 +2,7 @@ import numpy as np import itertools as it from mobject import Mobject, Mobject1D, Mobject2D, CompoundMobject +from image_mobject import tex_mobject from constants import * from helpers import * @@ -91,10 +92,10 @@ class Grid(Mobject1D): class NumberLine(Mobject1D): def __init__(self, - radius = SPACE_WIDTH, + radius = SPACE_WIDTH+1, interval_size = 0.5, tick_size = 0.1, *args, **kwargs): - self.radius = int(radius) + 1 + self.radius = int(radius) self.interval_size = interval_size self.tick_size = tick_size Mobject1D.__init__(self, *args, **kwargs) @@ -104,15 +105,34 @@ class NumberLine(Mobject1D): (x, 0, 0) for x in np.arange(-self.radius, self.radius, self.epsilon) ]) - self.add_points([ - (0, y, 0) - for y in np.arange(-2*self.tick_size, 2*self.tick_size, self.epsilon) - ]) self.add_points([ (x, y, 0) for x in np.arange(-self.radius, self.radius, self.interval_size) for y in np.arange(-self.tick_size, self.tick_size, self.epsilon) ]) + self.elongate_tick_at(0) + + def elongate_tick_at(self, x, multiple = 2): + self.add_points([ + [x, y, 0] + for y in np.arange( + -multiple*self.tick_size, + multiple*self.tick_size, + self.epsilon + ) + ]) + return self + + def add_numbers(self, intervals_per_number = 2): + max_val = int(self.radius/self.interval_size/intervals_per_number) + for x in range(-max_val, max_val+1): + num = tex_mobject(str(x)).scale(0.5) + num.shift( + DOWN*4*self.tick_size + \ + RIGHT*x*self.interval_size*intervals_per_number + ) + self.add(num) + return self class Axes(CompoundMobject): def __init__(self, *args, **kwargs): diff --git a/mobject/image_mobject.py b/mobject/image_mobject.py index 1c6ac536..0aec87de 100644 --- a/mobject/image_mobject.py +++ b/mobject/image_mobject.py @@ -66,50 +66,6 @@ class ImageMobject(Mobject2D): # potentially changed in subclasses return False -class SpeechBubble(ImageMobject): - def __init__(self, direction = LEFT, *args, **kwargs): - ImageMobject.__init__(self, "speech_bubble", *args, **kwargs) - self.direction = direction - self.scale(0.4) - self.center() - if direction[0] > 0: - self.rotate(np.pi, UP) - self.reload_tip() - - def reload_tip(self): - #TODO, perhaps make this resiliant to different point orderings - self.tip = self.points[-1] - - def speak_from(self, mobject): - dest = mobject.get_center() - dest += self.direction * mobject.get_width()/2 - dest += UP * mobject.get_height()/2 - self.shift(dest - self.tip) - self.reload_tip() - return self - - def write(self, text): - smidgeon = 0.1*UP + 0.2*self.direction - self.clear() - self.text = text_mobject(text) - self.text.scale(0.75*self.get_width() / self.text.get_width()) - self.text.shift(self.get_center() + smidgeon) - self.add(self.text) - - def clear(self): - if not hasattr(self, "text"): - return - num_text_points = self.text.points.shape[0] - self.points = self.points[:num_text_points] - self.rgbs = self.rgbs[:num_text_points] - self.text = Mobject() - -class ThoughtBubble(ImageMobject): - def __init__(self, *args, **kwargs): - ImageMobject.__init__(self, "thought_bubble", *args, **kwargs) - self.scale(0.5) - self.center() - class Face(ImageMobject): def __init__(self, mode = "simple", *args, **kwargs): """ diff --git a/mobject/mobject.py b/mobject/mobject.py index 8671e745..e8722cea 100644 --- a/mobject/mobject.py +++ b/mobject/mobject.py @@ -26,6 +26,7 @@ class Mobject(object): color = None, name = None, center = None, + **kwargs ): self.color = Color(color) if color else Color(self.DEFAULT_COLOR) if not hasattr(self, "name"): @@ -64,7 +65,8 @@ class Mobject(object): else: if rgbs.shape != points.shape: raise Exception("points and rgbs must have same shape") - self.rgbs = np.append(self.rgbs, rgbs).reshape(self.points.shape) + self.rgbs = np.append(self.rgbs, rgbs) + self.rgbs = self.rgbs.reshape((self.rgbs.size / 3, 3)) if self.has_normals: self.unit_normals = np.append( self.unit_normals, @@ -111,7 +113,7 @@ class Mobject(object): self.shift(-self.get_center()) return self - #To wrapper functions for better naming + #Wrapper functions for better naming def to_corner(self, corner = (-1, 1, 0), buff = 0.5): return self.align_on_border(corner, buff) @@ -144,6 +146,20 @@ class Mobject(object): center = self.get_center() return self.center().scale(scale_factor).shift(center) + def stretch_to_fit(self, length, dim): + center = self.get_center() + old_length = max(self.points[:,dim]) - min(self.points[:,dim]) + self.center() + self.points[:,dim] *= length/old_length + self.shift(center) + return self + + def stretch_to_fit_width(self, width): + return self.stretch_to_fit(width, 0) + + def stretch_to_fit_height(self, height): + return self.stretch_to_fit(height, 1) + def add(self, *mobjects): for mobject in mobjects: self.add_points(mobject.points, mobject.rgbs) @@ -161,6 +177,11 @@ class Mobject(object): self.rotate(np.pi / 7, [1, 0, 0]) return self + def replace(self, other_mobject): + self.scale(other_mobject.get_width()/self.get_width()) + self.center().shift(other_mobject.get_center()) + return self + def apply_function(self, function): self.points = np.apply_along_axis(function, 1, self.points) return self @@ -201,6 +222,7 @@ class Mobject(object): self.points, lambda *points : cmp(*map(function, points)) )) + return self ### Getters ### diff --git a/mobject/simple_mobjects.py b/mobject/simple_mobjects.py index 55f7c716..f82da2fa 100644 --- a/mobject/simple_mobjects.py +++ b/mobject/simple_mobjects.py @@ -13,10 +13,15 @@ class Point(Mobject): class Arrow(Mobject1D): DEFAULT_COLOR = "white" - NUNGE_DISTANCE = 0.1 - def __init__(self, point = (0, 0, 0), direction = (-1, 1, 0), - tail = None, length = 1, tip_length = 0.25, - normal = (0, 0, 1), *args, **kwargs): + DEFAULT_NUDGE_DISTANCE = 0.1 + def __init__(self, + point = (0, 0, 0), + direction = (-1, 1, 0), + tail = None, + length = 1, + tip_length = 0.25, + normal = (0, 0, 1), + *args, **kwargs): self.point = np.array(point) if tail is not None: direction = self.point - tail @@ -41,8 +46,10 @@ class Arrow(Mobject1D): for i in [0, 1] ]) - def nudge(self): - return self.shift(-self.direction * self.NUNGE_DISTANCE) + def nudge(self, distance = None): + if distance is None: + distance = self.DEFAULT_NUDGE_DISTANCE + return self.shift(-self.direction * distance) class Vector(Arrow): def __init__(self, point = (1, 0, 0), *args, **kwargs): @@ -131,6 +138,82 @@ class Circle(Mobject1D): ]) +class Bubble(Mobject): + def __init__(self, direction = LEFT, index_of_tip = -1, center = ORIGIN): + self.direction = direction + self.content = Mobject() + self.index_of_tip = index_of_tip + self.center_offset = center - Mobject.get_center(self) + if direction[0] > 0: + self.rotate(np.pi, UP) + + def get_tip(self): + return self.points[self.index_of_tip] + + def get_bubble_center(self): + return Mobject.get_center(self)+self.center_offset + + def move_tip_to(self, point): + self.shift(point - self.get_tip()) + return self + + def pin_to(self, mobject): + self.move_tip_to(sum([ + mobject.get_center(), + -self.direction * mobject.get_width()/2, + UP * mobject.get_height()/2, + ])) + return self + + def add_content(self, mobject): + mobject.scale(0.75*self.get_width() / mobject.get_width()) + mobject.shift(self.get_bubble_center()) + self.content = CompoundMobject(self.content, mobject) + self.add(self.content) + return self + + def write(self, text): + self.add_content(text_mobject(text)) + return self + + def clear(self): + num_content_points = self.content.points.shape[0] + self.points = self.points[:-num_content_points] + self.rgbs = self.rgbs[:-num_content_points] + self.contents = Mobject() + return self + +class SpeechBubble(Bubble): + def __init__(self, *args, **kwargs): + #TODO + pass + +class ThoughtBubble(Bubble): + NUM_BULGES = 7 + INITIAL_INNER_RADIUS = 1.8 + INITIAL_WIDTH = 6 + def __init__(self, *args, **kwargs): + Mobject.__init__(self, *args, **kwargs) + self.add(Circle().scale(0.15).shift(2.5*DOWN+2*LEFT)) + 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 + self.add(Circle().shift((np.cos(theta), np.sin(theta), 0))) + self.filter_out(lambda p : np.linalg.norm(p) < self.INITIAL_INNER_RADIUS) + self.stretch_to_fit_width(self.INITIAL_WIDTH) + self.highlight("white") + Bubble.__init__( + self, + index_of_tip = np.argmin(self.points[:,1]), + **kwargs + ) + + + + + + + diff --git a/script_wrapper.py b/script_wrapper.py index c3310d0a..7a524ae7 100644 --- a/script_wrapper.py +++ b/script_wrapper.py @@ -108,7 +108,7 @@ def command_line_create_scene(movie_prefix = ""): print "Constructing %s..."%name scene = SceneClass(*args, display_config = display_config) if action == "write": - scene.write_to_movie(movie_prefix + name) + scene.write_to_movie(os.path.join(movie_prefix, name)) elif action == "preview": scene.preview() elif action == "save_image":