diff --git a/camera.py b/camera.py new file mode 100644 index 00000000..1c03c582 --- /dev/null +++ b/camera.py @@ -0,0 +1,200 @@ +import numpy as np +import itertools as it +import os +import sys +from PIL import Image +import cv2 +from colour import Color +import progressbar + +from helpers import * + +class Camera(object): + DEFAULT_CONFIG = { + #background of a different shape will overwrite these + "pixel_width" : DEFAULT_WIDTH, + "pixel_height" : DEFAULT_HEIGHT, + "background_color" : BLACK, + # + "space_height" : SPACE_HEIGHT, + "space_center" : ORIGIN, + } + + def __init__(self, background = None, **kwargs): + digest_config(self, kwargs, locals()) + self.init_background() + self.reset() + + width_to_height = float(self.pixel_width) / self.pixel_height + self.space_width = self.space_height * width_to_height + + def init_background(self): + if self.background: + shape = self.background.shape[:2] + self.pixel_height, self.pixel_width = shape + else: + background_color = Color(self.background_color) + background_rgb = (255*np.array( + background_color.get_rgb() + )).astype('uint8') + ones = np.ones( + (self.pixel_height, self.pixel_width, 1), + dtype = 'uint8' + ) + self.background = np.dot( + ones, background_rgb.reshape((1, 3)) + ) + + def get_image(self): + return np.array(self.pixel_array) + + def set_image(self, pixel_array): + self.pixel_array = np.array(pixel_array) + + def reset(self): + self.set_image(np.array(self.background)) + + # def paint_region(region, image_array = None, color = None): + # pixels = get_pixels(image_array) + # assert region.shape == pixels.shape[:2] + # if color is None: + # #Random dark color + # rgb = 0.5 * np.random.random(3) + # else: + # rgb = np.array(Color(color).get_rgb()) + # pixels[region.bool_grid] = (255*rgb).astype('uint8') + # return pixels + + def capture_mobject(self, mobject): + return self.capture_mobjects([mobject]) + + def capture_mobjects(self, mobjects, include_sub_mobjects = True): + if include_sub_mobjects: + all_families = [ + mob.submobject_family() + for mob in mobjects + ] + mobjects = reduce(op.add, all_families, []) + + for mobject in mobjects: + self.display_points( + mobject.points, mobject.rgbs, + self.adjusted_thickness(mobject.point_thickness) + ) + + def display_points(self, points, rgbs, thickness): + if len(points) == 0: + return + points = self.align_points_to_camera(points) + pixel_coords = self.points_to_pixel_coords(points) + pixel_coords = self.thickened_coordinates( + pixel_coords, thickness + ) + + rgbs = (255*rgbs).astype('uint8') + target_len = len(pixel_coords) + factor = target_len/len(rgbs) + rgbs = np.array([rgbs]*factor).reshape((target_len, 3)) + + on_screen_indices = self.on_screen_pixels(pixel_coords) + pixel_coords = pixel_coords[on_screen_indices] + rgbs = rgbs[on_screen_indices] + + flattener = np.array([1, self.pixel_width], dtype = 'int') + flattener = flattener.reshape((2, 1)) + indices = np.dot(pixel_coords, flattener)[:,0] + indices = indices.astype('int') + + pw, ph = self.pixel_width, self.pixel_height + # new_array = np.zeros((pw*ph, 3), dtype = 'uint8') + # new_array[indices, :] = rgbs + new_pa = self.pixel_array.reshape((ph*pw, 3)) + new_pa[indices] = rgbs + self.pixel_array = new_pa.reshape((ph, pw, 3)) + + + def align_points_to_camera(self, points): + ## This is where projection should live + return points - self.space_center + + def points_to_pixel_coords(self, points): + result = np.zeros((len(points), 2)) + width_mult = self.pixel_width/self.space_width/2 + width_add = self.pixel_width/2 + height_mult = self.pixel_height/self.space_height/2 + height_add = self.pixel_height/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 + return result.astype('int') + + def on_screen_pixels(self, pixel_coords): + return reduce(op.and_, [ + pixel_coords[:,0] >= 0, + pixel_coords[:,0] < self.pixel_width, + pixel_coords[:,1] >= 0, + pixel_coords[:,1] < self.pixel_height, + ]) + + # def add_thickness(pixel_indices_and_rgbs, thickness, width, height): + # """ + # Imagine dragging each pixel around like a paintbrush in + # a plus-sign-shaped pixel arrangement surrounding it. + + # Pass rgb = None to do nothing to them + # """ + # thickness = adjusted_thickness(thickness, width, height) + # original = np.array(pixel_indices_and_rgbs) + # n_extra_columns = pixel_indices_and_rgbs.shape[1] - 2 + # for nudge in range(-thickness/2+1, thickness/2+1): + # if nudge == 0: + # continue + # for x, y in [[nudge, 0], [0, nudge]]: + # pixel_indices_and_rgbs = np.append( + # pixel_indices_and_rgbs, + # original+([x, y] + [0]*n_extra_columns), + # axis = 0 + # ) + # admissibles = (pixel_indices_and_rgbs[:,0] >= 0) & \ + # (pixel_indices_and_rgbs[:,0] < width) & \ + # (pixel_indices_and_rgbs[:,1] >= 0) & \ + # (pixel_indices_and_rgbs[:,1] < height) + # return pixel_indices_and_rgbs[admissibles] + + def adjusted_thickness(self, thickness): + big_width = PRODUCTION_QUALITY_DISPLAY_CONFIG["width"] + big_height = PRODUCTION_QUALITY_DISPLAY_CONFIG["height"] + factor = (big_width + big_height) / \ + (self.pixel_width + self.pixel_height) + return 1 + (thickness-1)/factor + + def get_thickening_nudges(self, thickness): + _range = range(-thickness/2+1, thickness/2+1) + return np.array(list(it.product(*[_range]*2))) + + def thickened_coordinates(self, pixel_coords, thickness): + nudges = self.get_thickening_nudges(thickness) + pixel_coords = np.array([ + pixel_coords + nudge + for nudge in nudges + ]) + size = pixel_coords.size + return pixel_coords.reshape((size/2, 2)) + + + + + + + + + + + + + + + + diff --git a/constants.py b/constants.py index 17bab634..39a57484 100644 --- a/constants.py +++ b/constants.py @@ -84,6 +84,8 @@ MAX_LEN_FOR_HUGE_TEX_FONT = 25 LOGO_PATH = os.path.join(IMAGE_DIR, "logo.png") +FFMPEG_BIN = "ffmpeg" + ### Colors ### diff --git a/displayer.py b/displayer.py index 8159a8ee..ac5c94e8 100644 --- a/displayer.py +++ b/displayer.py @@ -1,6 +1,5 @@ import numpy as np import itertools as it -import subprocess as sp import os import sys from PIL import Image @@ -10,165 +9,161 @@ import progressbar from helpers import * -FFMPEG_BIN = "ffmpeg" +class Camera(object): + DEFAULT_CONFIG = { + "pixel_width" : DEFAULT_WIDTH, + "pixel_height" : DEFAULT_HEIGHT, + "space_height" : SPACE_HEIGHT, + "space_center" : ORIGIN, + "background_color" : BLACK, + } -def get_pixels(image_array): #TODO, FIX WIDTH/HEIGHT PROBLEM HERE - if image_array is None: - return np.zeros( - (DEFAULT_HEIGHT, DEFAULT_WIDTH, 3), - dtype = 'uint8' - ) - else: - pixels = np.array(image_array).astype('uint8') - assert len(pixels.shape) == 3 and pixels.shape[2] == 3 - return pixels + def __init__(self, background = None, **kwargs): + digest_config(self, kwargs, locals()) + self.init_background() + self.reset() -def paint_region(region, image_array = None, color = None): - pixels = get_pixels(image_array) - assert region.shape == pixels.shape[:2] - if color is None: - #Random dark color - rgb = 0.5 * np.random.random(3) - else: - rgb = np.array(Color(color).get_rgb()) - pixels[region.bool_grid] = (255*rgb).astype('uint8') - return pixels + width_to_height = float(self.pixel_width) / self.pixel_height + self.space_width = self.space_height * width_to_height -def paint_mobject(mobject, image_array = None): - return paint_mobjects([mobject], image_array) - -def paint_mobjects(mobjects, image_array = None, include_sub_mobjects = True): - pixels = get_pixels(image_array) - height = pixels.shape[0] - width = pixels.shape[1] - space_height = SPACE_HEIGHT - space_width = SPACE_HEIGHT * width / height - pixels = pixels.reshape((pixels.size/3, 3)).astype('uint8') - - if include_sub_mobjects: - all_families = [ - mob.submobject_family() - for mob in mobjects - ] - mobjects = reduce(op.add, all_families, []) - - for mobject in mobjects: - if mobject.get_num_points() == 0: - continue - #bunch these together so rgbs never get lost from points - points_and_rgbs = np.append( - mobject.points, - 255*mobject.rgbs, - axis = 1 - ) - points_and_rgbs = place_on_screen( - points_and_rgbs, - space_width, space_height - ) - #Map points to pixel space, which requires rescaling and shifting - #Remember, 2*space_height -> height - points_and_rgbs[:,0] = points_and_rgbs[:,0]*width/space_width/2 + width/2 - #Flip on y-axis as you go - points_and_rgbs[:,1] = -1*points_and_rgbs[:,1]*height/space_height/2 + height/2 - points_and_rgbs = add_thickness( - points_and_rgbs.astype('int'), - mobject.point_thickness, - width, height - ) - - points, rgbs = points_and_rgbs[:,:2], points_and_rgbs[:,2:] - flattener = np.array([[1], [width]], dtype = 'int') - indices = np.dot(points, flattener)[:,0] - pixels[indices] = rgbs.astype('uint8') - return pixels.reshape((height, width, 3)) - -def add_thickness(pixel_indices_and_rgbs, thickness, width, height): - """ - Imagine dragging each pixel around like a paintbrush in - a plus-sign-shaped pixel arrangement surrounding it. - - Pass rgb = None to do nothing to them - """ - thickness = adjusted_thickness(thickness, width, height) - original = np.array(pixel_indices_and_rgbs) - n_extra_columns = pixel_indices_and_rgbs.shape[1] - 2 - for nudge in range(-thickness/2+1, thickness/2+1): - if nudge == 0: - continue - for x, y in [[nudge, 0], [0, nudge]]: - pixel_indices_and_rgbs = np.append( - pixel_indices_and_rgbs, - original+([x, y] + [0]*n_extra_columns), - axis = 0 + def init_background(self): + if self.background: + shape = self.background.shape[:2] + self.pixel_height, self.pixel_width = shape + else: + background_color = Color(self.background_color) + background_rgb = np.array(background_color.get_rgb()) + ones = np.ones( + (self.pixel_height, self.pixel_width, 1), + dtype = 'uint8' + ) + self.background = np.dot( + ones, background_rgb.reshape((1, 3)) ) - admissibles = (pixel_indices_and_rgbs[:,0] >= 0) & \ - (pixel_indices_and_rgbs[:,0] < width) & \ - (pixel_indices_and_rgbs[:,1] >= 0) & \ - (pixel_indices_and_rgbs[:,1] < height) - return pixel_indices_and_rgbs[admissibles] -def adjusted_thickness(thickness, width, height): - big_width = PRODUCTION_QUALITY_DISPLAY_CONFIG["width"] - big_height = PRODUCTION_QUALITY_DISPLAY_CONFIG["height"] - factor = (big_width + big_height) / (width + height) - return 1 + (thickness-1)/factor + def get_image(self): + return self.pixel_array -def place_on_screen(points_and_rgbs, space_width, space_height): - """ - Projects points to 2d space and remove those outside a - the space constraints. + def reset(self): + self.pixel_array = np.array(self.background) - Pass rbgs = None to do nothing to them. - """ - # Remove 3rd column - points_and_rgbs = np.append( - points_and_rgbs[:, :2], - points_and_rgbs[:, 3:], - axis = 1 - ) - - #Removes points out of space - to_keep = (abs(points_and_rgbs[:,0]) < space_width) & \ - (abs(points_and_rgbs[:,1]) < space_height) - return points_and_rgbs[to_keep] + # def get_pixels(image_array): #TODO, FIX WIDTH/HEIGHT PROBLEM HERE + # if image_array is None: + # return np.zeros( + # (DEFAULT_HEIGHT, DEFAULT_WIDTH, 3), + # dtype = 'uint8' + # ) + # else: + # pixels = np.array(image_array).astype('uint8') + # assert len(pixels.shape) == 3 and pixels.shape[2] == 3 + # return pixels -def get_file_path(name, extension): - file_path = os.path.join(MOVIE_DIR, name) - if not file_path.endswith(extension): - file_path += extension - directory = os.path.split(file_path)[0] - if not os.path.exists(directory): - os.makedirs(directory) - return file_path + # def paint_region(region, image_array = None, color = None): + # pixels = get_pixels(image_array) + # assert region.shape == pixels.shape[:2] + # if color is None: + # #Random dark color + # rgb = 0.5 * np.random.random(3) + # else: + # rgb = np.array(Color(color).get_rgb()) + # pixels[region.bool_grid] = (255*rgb).astype('uint8') + # return pixels -def write_to_movie(scene, name): - file_path = get_file_path(name, ".mp4") - print "Writing to %s"%file_path + def capture_mobject(self, mobject): + return self.capture_mobjects([mobject]) - fps = int(1/scene.frame_duration) - dim = (scene.width, scene.height) + def capture_mobjects(self, mobjects, include_sub_mobjects = True): + # pixels = get_pixels(image_array) + # height = pixels.shape[0] + # width = pixels.shape[1] + # space_height = SPACE_HEIGHT + # space_width = SPACE_HEIGHT * width / height + # pixels = pixels.reshape((pixels.size/3, 3)).astype('uint8') - command = [ - FFMPEG_BIN, - '-y', # overwrite output file if it exists - '-f', 'rawvideo', - '-vcodec','rawvideo', - '-s', '%dx%d'%dim, # size of one frame - '-pix_fmt', 'rgb24', - '-r', str(fps), # frames per second - '-i', '-', # The imput comes from a pipe - '-an', # Tells FFMPEG not to expect any audio - '-vcodec', 'mpeg', - '-c:v', 'libx264', - '-pix_fmt', 'yuv420p', - '-loglevel', 'error', - file_path, - ] - process = sp.Popen(command, stdin=sp.PIPE) - for frame in scene.frames: - process.stdin.write(frame.tostring()) - process.stdin.close() - process.wait() + if include_sub_mobjects: + all_families = [ + mob.submobject_family() + for mob in mobjects + ] + mobjects = reduce(op.add, all_families, []) + + for mobject in mobjects: + self.display_points( + mobject.points, mobject.rgbs, + mobject.point_thickness + ) + + def display_points(self, points, rgbs, thickness): + points = self.project_onto_screen(points) + pixel_coordinates = self.pixel_coordinates_of_points(points) + rgbs = (255*rgbs).astype('uint8') + on_screen_indices = self.on_screen_pixels(pixel_coordinates) + pixel_coordinates = pixel_coordinates[on_screen_indices] + rgbs = rgbs[on_screen_indices] + + flattener = np.array([1, self.width], dtype = 'int') + flattener = flattener.reshape((2, 1)) + indices = np.dot(pixel_coordinates, flattener)[:,0] + + pw, ph = self.pixel_width, self.pixel_height + self.pixel_array.reshape((pw*ph, 3)) + self.pixel_array[indices] = rgbs.astype('uint8') + self.pixel_array.reshape((ph, pw, 3)) + + def project_onto_screen(points): + ## TODO + points[:,2] = 0 + + def pixel_coordinates_of_points(self, points): + result = np.zeros((len(points), 2)) + width_mult = self.pixel_width/self.space_width/2 + width_add = self.pixel_width/2 + height_mult = self.pixel_height/self.space_height/2 + height_add = self.pixel_height/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 + return result + + def on_screen_pixels(self, pixel_coordinates): + return (pixel_coordinates[:,0] < 0) | \ + (pixel_coordinates[:,0] >= width) | \ + (pixel_coordinates[:,1] < 0) | \ + (pixel_coordinates[:,1] >= height) + + + # def add_thickness(pixel_indices_and_rgbs, thickness, width, height): + # """ + # Imagine dragging each pixel around like a paintbrush in + # a plus-sign-shaped pixel arrangement surrounding it. + + # Pass rgb = None to do nothing to them + # """ + # thickness = adjusted_thickness(thickness, width, height) + # original = np.array(pixel_indices_and_rgbs) + # n_extra_columns = pixel_indices_and_rgbs.shape[1] - 2 + # for nudge in range(-thickness/2+1, thickness/2+1): + # if nudge == 0: + # continue + # for x, y in [[nudge, 0], [0, nudge]]: + # pixel_indices_and_rgbs = np.append( + # pixel_indices_and_rgbs, + # original+([x, y] + [0]*n_extra_columns), + # axis = 0 + # ) + # admissibles = (pixel_indices_and_rgbs[:,0] >= 0) & \ + # (pixel_indices_and_rgbs[:,0] < width) & \ + # (pixel_indices_and_rgbs[:,1] >= 0) & \ + # (pixel_indices_and_rgbs[:,1] < height) + # return pixel_indices_and_rgbs[admissibles] + + def adjusted_thickness(thickness, width, height): + big_width = PRODUCTION_QUALITY_DISPLAY_CONFIG["width"] + big_height = PRODUCTION_QUALITY_DISPLAY_CONFIG["height"] + factor = (big_width + big_height) / (width + height) + return 1 + (thickness-1)/facto diff --git a/scene/scene.py b/scene/scene.py index bc198d93..6081e33f 100644 --- a/scene/scene.py +++ b/scene/scene.py @@ -8,13 +8,13 @@ import os import copy from tqdm import tqdm as ProgressDisplay import inspect +import subprocess as sp from helpers import * -import displayer as disp +from camera import Camera from tk_scene import TkSceneRoot from mobject import Mobject -from animation.transform import ApplyMethod class Scene(object): DEFAULT_CONFIG = { @@ -27,16 +27,11 @@ class Scene(object): } def __init__(self, **kwargs): digest_config(self, kwargs) - if self.background is not None: - self.original_background = np.array(self.background) - #TODO, Error checking? - else: - self.original_background = np.zeros( - (self.height, self.width, 3), - dtype = 'uint8' - ) - self.background = self.original_background - self.curr_frame = self.background + self.camera = Camera( + pixel_width = self.width, + pixel_height = self.height, + background = self.background + ) self.frames = [] self.mobjects = [] self.num_animations = 0 @@ -57,11 +52,7 @@ class Scene(object): return self def get_frame(self): - return self.curr_frame - - def set_frame(self, frame): - self.curr_frame = frame - return self + return self.camera.get_image() def add(self, *mobjects): """ @@ -70,9 +61,9 @@ class Scene(object): """ if not all_elements_are_instances(mobjects, Mobject): raise Exception("Adding something which is not a mobject") - self.set_frame(disp.paint_mobjects(mobjects, self.get_frame())) old_mobjects = filter(lambda m : m not in mobjects, self.mobjects) self.mobjects = old_mobjects + list(mobjects) + self.camera.capture_mobjects(mobjects) return self def add_mobjects_among(self, values): @@ -101,56 +92,17 @@ class Scene(object): def bring_to_back(self, mobject): self.remove(mobject) self.mobjects = [mobject] + self.mobjects - self.repaint_mojects() return self def clear(self): - self.reset_background() self.mobjects = [] - self.set_frame(self.background) - return self - - def highlight_region(self, region, color = None): - self.background = disp.paint_region( - region, - image_array = self.background, - color = color, - ) - self.repaint_mojects() - return self - - def highlight_region_over_time_range(self, region, time_range = None, color = "black"): - if time_range: - frame_range = map(lambda t : t / self.frame_duration, time_range) - frame_range[0] = max(frame_range[0], 0) - frame_range[1] = min(frame_range[1], len(self.frames)) - else: - frame_range = (0, len(self.frames)) - for index in range(frame_range[0], frame_range[1]): - self.frames[index] = disp.paint_region( - region, - image_array = self.frames[index], - color = color - ) - - def reset_background(self): - self.background = self.original_background return self def repaint_mojects(self): - self.set_frame(disp.paint_mobjects(self.mobjects, self.background)) + self.camera.reset() + self.camera.capture_mobjects(self.mobjects) return self - def paint_into_background(self, *mobjects): - #This way current mobjects don't have to be redrawn with - #every change, and one can later call "apply" without worrying - #about it applying to these mobjects - self.background = disp.paint_mobjects(mobjects, self.background) - return self - - def set_frame_as_background(self): - self.background = self.get_frame() - def play(self, *animations, **kwargs): self.num_animations += 1 if "run_time" in kwargs: @@ -170,11 +122,12 @@ class Scene(object): lambda m : m not in moving_mobjects, bundle.submobject_family() ) - background = disp.paint_mobjects( + self.camera.reset() + self.camera.capture_mobjects( static_mobjects, - self.background, include_sub_mobjects = False ) + static_image = self.camera.get_image() times = np.arange(0, run_time, self.frame_duration) time_progression = ProgressDisplay(times) @@ -186,15 +139,15 @@ class Scene(object): for t in time_progression: for animation in animations: animation.update(t / animation.run_time) - new_frame = disp.paint_mobjects( - [anim.mobject for anim in animations], - background - ) - self.frames.append(new_frame) + self.camera.capture_mobjects([ + anim.mobject + for anim in animations + ]) + self.frames.append(self.camera.get_image()) + self.camera.set_image(static_image) for animation in animations: animation.clean_up() self.add(*[anim.mobject for anim in animations]) - self.repaint_mojects() return self def play_over_time_range(self, t0, t1, *animations): @@ -215,14 +168,14 @@ class Scene(object): for animation in animations: animation.update((t-t0)/(t1 - t0)) index = int(t/self.frame_duration) - self.frames[index] = disp.paint_mobjects(moving_mobjects, self.frames[index]) + self.camera.set_image(self.frames[index]) + self.camera.capture_mobjects(moving_mobjects) + self.frames[index] = self.camera.get_image() for animation in animations: animation.clean_up() + self.repaint_mojects() return self - def apply(self, mobject_method, *args, **kwargs): - self.play(ApplyMethod(mobject_method, *args, **kwargs)) - def dither(self, duration = DEFAULT_DITHER_TIME): self.frames += [self.get_frame()]*int(duration / self.frame_duration) return self @@ -243,14 +196,6 @@ class Scene(object): ] return self - def write_to_movie(self, name = None): - if len(self.frames) == 0: - print "No frames, I'll just save an image instead" - self.show_frame() - self.save_image(name = name) - return - disp.write_to_movie(self, name or str(self)) - def show_frame(self): Image.fromarray(self.get_frame()).show() @@ -265,6 +210,52 @@ class Scene(object): os.makedirs(path) Image.fromarray(self.get_frame()).save(full_path) + def get_movie_file_path(self, name, extension): + file_path = os.path.join(MOVIE_DIR, name) + if not file_path.endswith(extension): + file_path += extension + directory = os.path.split(file_path)[0] + if not os.path.exists(directory): + os.makedirs(directory) + return file_path + + def write_to_movie(self, name = None): + if len(self.frames) == 0: + print "No frames, I'll just save an image instead" + self.show_frame() + self.save_image(name = name) + return + if name is None: + name = str(self) + + file_path = self.get_movie_file_path(name, ".mp4") + print "Writing to %s"%file_path + + fps = int(1/self.frame_duration) + dim = (self.width, self.height) + + command = [ + FFMPEG_BIN, + '-y', # overwrite output file if it exists + '-f', 'rawvideo', + '-vcodec','rawvideo', + '-s', '%dx%d'%dim, # size of one frame + '-pix_fmt', 'rgb24', + '-r', str(fps), # frames per second + '-i', '-', # The imput comes from a pipe + '-an', # Tells FFMPEG not to expect any audio + '-vcodec', 'mpeg', + '-c:v', 'libx264', + '-pix_fmt', 'yuv420p', + '-loglevel', 'error', + file_path, + ] + process = sp.Popen(command, stdin=sp.PIPE) + for frame in self.frames: + process.stdin.write(frame.tostring()) + process.stdin.close() + process.wait() + # To list possible args that subclasses have # Elements should always be a tuple args_list = [] diff --git a/topics/characters.py b/topics/characters.py index e8f7432a..c5309574 100644 --- a/topics/characters.py +++ b/topics/characters.py @@ -27,8 +27,6 @@ class PiCreature(Mobject): 'mouth', ] WHITE_PART_NAMES = ['left_eye', 'right_eye', 'mouth'] - MOUTH_NAMES = ["smile", "frown", "straight_mouth"] - def __init__(self, **kwargs): Mobject.__init__(self, **kwargs) for part_name in self.PART_NAMES: @@ -39,43 +37,26 @@ class PiCreature(Mobject): if part_name not in self.WHITE_PART_NAMES: mob.highlight(self.color) setattr(self, part_name, mob) + self.add(mob) self.eyes = Mobject(self.left_eye, self.right_eye) self.legs = Mobject(self.left_leg, self.right_leg) - mouth_center = self.get_mouth_center() - self.mouth.center() - self.smile = self.mouth - self.frown = self.mouth.copy().rotate(np.pi, RIGHT) - self.straight_mouth = TexMobject("-").scale(0.7) - for mouth in self.smile, self.frown, self.straight_mouth: - mouth.sort_points(lambda p : p[0]) - mouth.highlight(self.color) ##to blend into background - mouth.shift(mouth_center) - self.digest_mobject_attrs() - self.give_smile() + self.mouth.center().shift(self.get_mouth_center()) self.add(self.mouth) self.scale(PI_CREATURE_SCALE_VAL) - def get_parts(self): return [getattr(self, pn) for pn in self.PART_NAMES] def get_white_parts(self): return [ getattr(self, pn) - for pn in self.WHITE_PART_NAMES+self.MOUTH_NAMES + for pn in self.WHITE_PART_NAMES ] def get_mouth_center(self): result = self.body.get_center() result[0] = self.eyes.get_center()[0] return result - # left_center = self.left_eye.get_center() - # right_center = self.right_eye.get_center() - # l_to_r = right_center-left_center - # eyes_to_mouth = rotate_vector(l_to_r, -np.pi/2, OUT) - # eyes_to_mouth /= np.linalg.norm(eyes_to_mouth) - # return left_center/2 + right_center/2 + \ - # PI_CREATURE_MOUTH_TO_EYES_DISTANCE*eyes_to_mouth def highlight(self, color, condition = None): for part in set(self.get_parts()).difference(self.get_white_parts()): @@ -86,25 +67,6 @@ class PiCreature(Mobject): self.shift(destination-self.get_bottom()) return self - def change_mouth_to(self, mouth_name): - #TODO, This is poorly implemented - self.mouth = getattr(self, mouth_name) - self.sub_mobjects = list_update( - self.sub_mobjects, - self.get_parts() - ) - self.mouth.highlight(WHITE) - return self - - def give_smile(self): - return self.change_mouth_to("smile") - - def give_frown(self): - return self.change_mouth_to("frown") - - def give_straight_face(self): - return self.change_mouth_to("straight_mouth") - def get_eye_center(self): return self.eyes.get_center()