예제 #1
0
 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)
     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)
     looking_direction = self.get_looking_direction()
     for pupil in self.pupils:
         pupil.scale_in_place(self.pupil_scale_factor)
     self.look(looking_direction)
예제 #2
0
 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)
     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)
     looking_direction = self.get_looking_direction()
     for pupil in self.pupils:
         pupil.scale_in_place(self.pupil_scale_factor)
     self.look(looking_direction)
예제 #3
0
    def construct(self):
        morty = Mortimer()
        morty.next_to(ORIGIN, DOWN)

        n_patrons = len(self.specific_patrons)
        special_thanks = TextMobject("Special thanks")
        special_thanks.highlight(YELLOW)
        special_thanks.to_edge(UP)

        patreon_logo = PatreonLogo()
        patreon_logo.next_to(morty, UP, buff=MED_LARGE_BUFF)

        patrons = map(TextMobject, self.specific_patrons)
        patron_groups = []
        index = 0
        counter = 0
        while index < len(patrons):
            next_index = index + self.patron_group_size
            group = VGroup(*patrons[index:next_index])
            group.arrange_submobjects(DOWN, aligned_edge=LEFT)
            if counter % 2 == 0:
                group.to_edge(LEFT)
            else:
                group.to_edge(RIGHT)
            patron_groups.append(group)
            index = next_index
            counter += 1

        self.play(
            morty.change_mode,
            "gracious",
            DrawBorderThenFill(patreon_logo),
        )
        self.play(Write(special_thanks, run_time=1))
        for i, group in enumerate(patron_groups):
            anims = [
                FadeIn(
                    group,
                    run_time=2,
                    submobject_mode="lagged_start",
                    lag_factor=4,
                ),
                morty.look_at,
                group.get_top(),
            ]
            if i >= 2:
                anims.append(FadeOut(patron_groups[i - 2]))
            self.play(*anims)
            self.play(morty.look_at, group.get_bottom())
            self.play(Blink(morty))
예제 #4
0
    def construct(self):
        morty = Mortimer()
        morty.next_to(ORIGIN, DOWN)

        n_patrons = len(self.specific_patrons)
        special_thanks = TextMobject("Special thanks")
        special_thanks.highlight(YELLOW)
        special_thanks.to_edge(UP)

        patreon_logo = PatreonLogo()
        patreon_logo.next_to(morty, UP, buff=MED_LARGE_BUFF)

        left_patrons = VGroup(
            *map(TextMobject, self.specific_patrons[:n_patrons / 2]))
        right_patrons = VGroup(
            *map(TextMobject, self.specific_patrons[n_patrons / 2:]))
        for patrons in left_patrons, right_patrons:
            patrons.arrange_submobjects(DOWN,
                                        aligned_edge=LEFT,
                                        buff=1.5 * MED_SMALL_BUFF)

        all_patrons = VGroup(left_patrons, right_patrons)
        all_patrons.scale(0.7)
        for patrons, vect in (left_patrons, LEFT), (right_patrons, RIGHT):
            patrons.to_corner(UP + vect, buff=MED_SMALL_BUFF)

        shift_distance = max(0, 1 - SPACE_HEIGHT - all_patrons.get_bottom()[1])
        velocity = shift_distance / 9.0

        def get_shift_anim():
            return ApplyMethod(all_patrons.shift,
                               velocity * UP,
                               rate_func=None)

        self.play(
            morty.change_mode,
            "gracious",
            DrawBorderThenFill(patreon_logo),
        )
        self.play(Write(special_thanks, run_time=1))
        self.play(Write(left_patrons), morty.look_at, left_patrons)
        self.play(Write(right_patrons), morty.look_at, right_patrons)
        self.play(Blink(morty), get_shift_anim())
        for patrons in left_patrons, right_patrons:
            for index in 0, -1:
                self.play(morty.look_at, patrons[index], get_shift_anim())
                self.play(get_shift_anim())
예제 #5
0
class PiCreature(SVGMobject):
    CONFIG = {
        "color" : BLUE_E,
        "stroke_width" : 0,
        "fill_opacity" : 1.0,
        "initial_scale_factor" : 0.01,
        "corner_scale_factor" : 0.75,
        "flip_at_start" : False,
        "is_looking_direction_purposeful" : False,
        "start_corner" : None,
    }
    def __init__(self, mode = "plain", **kwargs):
        self.parts_named = False
        svg_file = os.path.join(
            PI_CREATURE_DIR, 
            "PiCreatures_%s.svg"%mode
        )
        digest_config(self, kwargs, locals())
        SVGMobject.__init__(self, file_name = svg_file, **kwargs)
        self.init_colors()
        if self.flip_at_start:
            self.flip()
        if self.start_corner is not None:
            self.to_corner(self.start_corner)

    def name_parts(self):
        self.mouth = self.submobjects[MOUTH_INDEX]
        self.body = self.submobjects[BODY_INDEX]
        self.pupils = VGroup(*[
            self.submobjects[LEFT_PUPIL_INDEX],
            self.submobjects[RIGHT_PUPIL_INDEX]
        ])
        self.eyes = VGroup(*[
            self.submobjects[LEFT_EYE_INDEX],
            self.submobjects[RIGHT_EYE_INDEX]
        ])
        self.submobjects = []
        self.add(self.body, self.mouth, self.eyes, self.pupils)
        self.parts_named = True

    def init_colors(self):
        self.set_stroke(color = BLACK, width = self.stroke_width)
        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)
        return self


    def highlight(self, color):
        self.body.set_fill(color)
        return self

    def change_mode(self, mode):
        curr_eye_center = self.eyes.get_center()
        curr_height = self.get_height()
        should_be_flipped = self.is_flipped()
        should_look = hasattr(self, "purposeful_looking_direction")
        if should_look:
            looking_direction = self.purposeful_looking_direction
        self.__init__(mode)
        self.scale_to_fit_height(curr_height)
        self.shift(curr_eye_center - self.eyes.get_center())
        if should_be_flipped ^ self.is_flipped():
            self.flip()
        if should_look:
            self.look(looking_direction)
        return self

    def look(self, direction):
        direction = direction/np.linalg.norm(direction)
        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.move_to(eye)
            if direction[1] < 0:
                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...
            # top_diff = eye.get_top()[1]-pupil.get_top()[1]
            # if top_diff < 0:
            #     pupil.shift(top_diff*UP)
        return self

    def look_at(self, point_or_mobject):
        if isinstance(point_or_mobject, Mobject):
            point = point_or_mobject.get_center()
        else:
            point = point_or_mobject
        self.look(point - self.eyes.get_center())
        return self


    def get_looking_direction(self):
        return np.sign(np.round(
            self.pupils.get_center() - self.eyes.get_center(),
            decimals = 2
        ))

    def is_flipped(self):
        return self.eyes.submobjects[0].get_center()[0] > \
               self.eyes.submobjects[1].get_center()[0]

    def blink(self):
        eye_bottom_y = self.eyes.get_bottom()[1]
        for mob in self.eyes, self.pupils:
            mob.apply_function(
                lambda p : [p[0], eye_bottom_y, p[2]]
            )
        return self

    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)
        return self

    def get_bubble(self, bubble_type = "thought", **kwargs):
        #TODO, change bubble_type arg to have type Bubble
        if bubble_type == "thought":
            bubble = ThoughtBubble(**kwargs)
        elif bubble_type == "speech":
            bubble = SpeechBubble(**kwargs)
        else:
            raise Exception("%s is an invalid bubble type"%bubble_type)
        bubble.pin_to(self)
        return bubble

    def make_eye_contact(self, pi_creature):
        self.look_at(pi_creature.eyes)
        pi_creature.look_at(self.eyes)
        return self

    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.look(top_mouth_point - bottom_mouth_point)
        return self
예제 #6
0
class PiCreature(SVGMobject):
    CONFIG = {
        "color": BLUE_E,
        "stroke_width": 0,
        "stroke_color": BLACK,
        "fill_opacity": 1.0,
        "propogate_style_to_family": True,
        "initial_scale_factor": 0.01,
        "corner_scale_factor": 0.75,
        "flip_at_start": False,
        "is_looking_direction_purposeful": False,
        "start_corner": None,
    }

    def __init__(self, mode="plain", **kwargs):
        self.parts_named = False
        svg_file = os.path.join(PI_CREATURE_DIR, "PiCreatures_%s.svg" % mode)
        digest_config(self, kwargs, locals())
        SVGMobject.__init__(self, file_name=svg_file, **kwargs)
        if self.flip_at_start:
            self.flip()
        if self.start_corner is not None:
            self.to_corner(self.start_corner)

    def name_parts(self):
        self.mouth = self.submobjects[MOUTH_INDEX]
        self.body = self.submobjects[BODY_INDEX]
        self.pupils = VGroup(*[
            self.submobjects[LEFT_PUPIL_INDEX],
            self.submobjects[RIGHT_PUPIL_INDEX]
        ])
        self.eyes = VGroup(*[
            self.submobjects[LEFT_EYE_INDEX], self.submobjects[RIGHT_EYE_INDEX]
        ])
        self.submobjects = []
        self.add(self.body, self.mouth, self.eyes, self.pupils)
        self.parts_named = True

    def init_colors(self):
        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)
        return self

    def highlight(self, color):
        self.body.set_fill(color)
        return self

    def change_mode(self, mode):
        curr_eye_center = self.eyes.get_center()
        curr_height = self.get_height()
        should_be_flipped = self.is_flipped()
        should_look = hasattr(self, "purposeful_looking_direction")
        if should_look:
            looking_direction = self.purposeful_looking_direction
        self.__init__(mode)
        self.scale_to_fit_height(curr_height)
        self.shift(curr_eye_center - self.eyes.get_center())
        if should_be_flipped ^ self.is_flipped():
            self.flip()
        if should_look:
            self.look(looking_direction)
        return self

    def look(self, direction):
        direction = direction / np.linalg.norm(direction)
        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.move_to(eye)
            if direction[1] < 0:
                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...
            # top_diff = eye.get_top()[1]-pupil.get_top()[1]
            # if top_diff < 0:
            #     pupil.shift(top_diff*UP)
        return self

    def look_at(self, point_or_mobject):
        if isinstance(point_or_mobject, Mobject):
            point = point_or_mobject.get_center()
        else:
            point = point_or_mobject
        self.look(point - self.eyes.get_center())
        return self

    def get_looking_direction(self):
        return np.sign(
            np.round(self.pupils.get_center() - self.eyes.get_center(),
                     decimals=2))

    def is_flipped(self):
        return self.eyes.submobjects[0].get_center()[0] > \
               self.eyes.submobjects[1].get_center()[0]

    def blink(self):
        eye_bottom_y = self.eyes.get_bottom()[1]
        for mob in self.eyes, self.pupils:
            mob.apply_function(lambda p: [p[0], eye_bottom_y, p[2]])
        return self

    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)
        return self

    def get_bubble(self, bubble_type="thought", **kwargs):
        #TODO, change bubble_type arg to have type Bubble
        if bubble_type == "thought":
            bubble = ThoughtBubble(**kwargs)
        elif bubble_type == "speech":
            bubble = SpeechBubble(**kwargs)
        else:
            raise Exception("%s is an invalid bubble type" % bubble_type)
        bubble.pin_to(self)
        return bubble

    def make_eye_contact(self, pi_creature):
        self.look_at(pi_creature.eyes)
        pi_creature.look_at(self.eyes)
        return self

    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.look(top_mouth_point - bottom_mouth_point)
        return self
예제 #7
0
class PiCreature(SVGMobject):
    CONFIG = {
        "color": BLUE_E,
        "stroke_width": 0,
        "stroke_color": BLACK,
        "fill_opacity": 1.0,
        "propogate_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):
        self.parts_named = False
        try:
            svg_file = os.path.join(PI_CREATURE_DIR,
                                    "PiCreatures_%s.svg" % mode)
            SVGMobject.__init__(self, file_name=svg_file, **kwargs)
        except:
            warnings.warn("No PiCreature design with mode %s" % mode)
            svg_file = os.path.join(PI_CREATURE_DIR, "PiCreatures_plain.svg")
            SVGMobject.__init__(self, file_name=svg_file, **kwargs)

        if self.flip_at_start:
            self.flip()
        if self.start_corner is not None:
            self.to_corner(self.start_corner)

    def name_parts(self):
        self.mouth = self.submobjects[MOUTH_INDEX]
        self.body = self.submobjects[BODY_INDEX]
        self.pupils = VGroup(*[
            self.submobjects[LEFT_PUPIL_INDEX],
            self.submobjects[RIGHT_PUPIL_INDEX]
        ])
        self.eyes = VGroup(*[
            self.submobjects[LEFT_EYE_INDEX], self.submobjects[RIGHT_EYE_INDEX]
        ])
        self.parts_named = True

    def init_colors(self):
        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)
        return self

    def copy(self):
        copy_mobject = SVGMobject.copy(self)
        copy_mobject.name_parts()
        return copy_mobject

    def highlight(self, color):
        self.body.set_fill(color)
        return self

    def change_mode(self, mode):
        new_self = self.__class__(mode=mode, color=self.color)
        new_self.scale_to_fit_height(self.get_height())
        if self.is_flipped() ^ new_self.is_flipped():
            new_self.flip()
        new_self.shift(self.eyes.get_center() - new_self.eyes.get_center())
        if hasattr(self, "purposeful_looking_direction"):
            new_self.look(self.purposeful_looking_direction)
        Transform(self, new_self).update(1)
        return self

    def look(self, direction):
        direction = direction / np.linalg.norm(direction)
        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.move_to(eye)
            if direction[1] < 0:
                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...
            # top_diff = eye.get_top()[1]-pupil.get_top()[1]
            # if top_diff < 0:
            #     pupil.shift(top_diff*UP)
        return self

    def look_at(self, point_or_mobject):
        if isinstance(point_or_mobject, Mobject):
            point = point_or_mobject.get_center()
        else:
            point = point_or_mobject
        self.look(point - self.eyes.get_center())
        return self

    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)
        return self

    def get_looking_direction(self):
        return np.sign(
            np.round(self.pupils.get_center() - self.eyes.get_center(),
                     decimals=2))

    def is_flipped(self):
        return self.eyes.submobjects[0].get_center()[0] > \
               self.eyes.submobjects[1].get_center()[0]

    def blink(self):
        eye_bottom_y = self.eyes.get_bottom()[1]
        for mob in self.eyes, self.pupils:
            mob.apply_function(lambda p: [p[0], eye_bottom_y, p[2]])
        return self

    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)
        return self

    def get_bubble(self, *content, **kwargs):
        bubble_class = kwargs.get("bubble_class", ThoughtBubble)
        bubble = bubble_class(**kwargs)
        if len(content) > 0:
            if isinstance(content[0], str):
                content_mob = TextMobject(*content)
            else:
                content_mob = content[0]
            bubble.add_content(content_mob)
            if "height" not in kwargs and "width" not in kwargs:
                bubble.resize_to_content()
        bubble.pin_to(self)
        self.bubble = bubble
        return bubble

    def make_eye_contact(self, pi_creature):
        self.look_at(pi_creature.eyes)
        pi_creature.look_at(self.eyes)
        return self

    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.look(top_mouth_point - bottom_mouth_point)
        return self

    def get_arm_copies(self):
        body = self.body
        return VGroup(*[
            body.copy().pointwise_become_partial(body, *alpha_range)
            for alpha_range in self.right_arm_range, self.left_arm_range
        ])
예제 #8
0
class PiCreature(SVGMobject):
    CONFIG = {
        "color" : BLUE_E,
        "stroke_width" : 0,
        "stroke_color" : BLACK,
        "fill_opacity" : 1.0,
        "propogate_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):
        self.parts_named = False
        try:
            svg_file = os.path.join(
                PI_CREATURE_DIR, 
                "PiCreatures_%s.svg"%mode
            )
            SVGMobject.__init__(self, file_name = svg_file, **kwargs)
        except:
            warnings.warn("No PiCreature design with mode %s"%mode)
            svg_file = os.path.join(
                PI_CREATURE_DIR, 
                "PiCreatures_plain.svg"
            )
            SVGMobject.__init__(self, file_name = svg_file, **kwargs)

        if self.flip_at_start:
            self.flip()
        if self.start_corner is not None:
            self.to_corner(self.start_corner)

    def name_parts(self):
        self.mouth = self.submobjects[MOUTH_INDEX]
        self.body = self.submobjects[BODY_INDEX]
        self.pupils = VGroup(*[
            self.submobjects[LEFT_PUPIL_INDEX],
            self.submobjects[RIGHT_PUPIL_INDEX]
        ])
        self.eyes = VGroup(*[
            self.submobjects[LEFT_EYE_INDEX],
            self.submobjects[RIGHT_EYE_INDEX]
        ])
        self.parts_named = True

    def init_colors(self):
        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)
        return self

    def copy(self):
        copy_mobject = SVGMobject.copy(self)
        copy_mobject.name_parts()
        return copy_mobject

    def highlight(self, color):
        self.body.set_fill(color)
        return self

    def change_mode(self, mode):
        new_self = self.__class__(
            mode = mode,
            color = self.color
        )
        new_self.scale_to_fit_height(self.get_height())
        if self.is_flipped() ^ new_self.is_flipped():
            new_self.flip()
        new_self.shift(self.eyes.get_center() - new_self.eyes.get_center())
        if hasattr(self, "purposeful_looking_direction"):
            new_self.look(self.purposeful_looking_direction)
        Transform(self, new_self).update(1)
        return self

    def look(self, direction):
        direction = direction/np.linalg.norm(direction)
        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.move_to(eye)
            if direction[1] < 0:
                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...
            # top_diff = eye.get_top()[1]-pupil.get_top()[1]
            # if top_diff < 0:
            #     pupil.shift(top_diff*UP)
        return self

    def look_at(self, point_or_mobject):
        if isinstance(point_or_mobject, Mobject):
            point = point_or_mobject.get_center()
        else:
            point = point_or_mobject
        self.look(point - self.eyes.get_center())
        return self

    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)
        return self

    def get_looking_direction(self):
        return np.sign(np.round(
            self.pupils.get_center() - self.eyes.get_center(),
            decimals = 2
        ))

    def is_flipped(self):
        return self.eyes.submobjects[0].get_center()[0] > \
               self.eyes.submobjects[1].get_center()[0]

    def blink(self):
        eye_bottom_y = self.eyes.get_bottom()[1]
        for mob in self.eyes, self.pupils:
            mob.apply_function(
                lambda p : [p[0], eye_bottom_y, p[2]]
            )
        return self

    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)
        return self

    def get_bubble(self, *content, **kwargs):
        bubble_class = kwargs.get("bubble_class", ThoughtBubble)
        bubble = bubble_class(**kwargs)
        if len(content) > 0:
            if isinstance(content[0], str):
                content_mob = TextMobject(*content)
            else:
                content_mob = content[0]
            bubble.add_content(content_mob)
            if "height" not in kwargs and "width" not in kwargs:
                bubble.resize_to_content()
        bubble.pin_to(self)
        self.bubble = bubble
        return bubble

    def make_eye_contact(self, pi_creature):
        self.look_at(pi_creature.eyes)
        pi_creature.look_at(self.eyes)
        return self

    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.look(top_mouth_point - bottom_mouth_point)
        return self

    def get_arm_copies(self):
        body = self.body
        return VGroup(*[
            body.copy().pointwise_become_partial(body, *alpha_range)
            for alpha_range in self.right_arm_range, self.left_arm_range
        ])