class PiCreature(SVGMobject): CONFIG = { "color": BLUE_E, "stroke_width": 0, "stroke_color": BLACK, "fill_opacity": 1.0, "propagate_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(FILE_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.eye_parts = VGroup(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 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): norm = np.linalg.norm(direction) if norm == 0: return direction /= norm 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_parts = self.eye_parts eye_bottom_y = eye_parts.get_bottom()[1] eye_parts.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 ])
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
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
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 ])