diff --git a/big_ol_pile_of_manim_imports.py b/big_ol_pile_of_manim_imports.py index 0977cc5d..58361f9d 100644 --- a/big_ol_pile_of_manim_imports.py +++ b/big_ol_pile_of_manim_imports.py @@ -39,6 +39,10 @@ from mobject.svg_mobject import * from mobject.tex_mobject import * from mobject.vectorized_mobject import * +from pi_creature.pi_creature import * +from pi_creature.pi_creature_animations import * +from pi_creature.pi_creature_scene import * + from scene.moving_camera_scene import * from scene.reconfigurable_scene import * from scene.scene import * @@ -46,7 +50,6 @@ from scene.scene_from_video import * from scene.zoomed_scene import * from topics.arithmetic import * -from topics.characters import * from topics.combinatorics import * from topics.common_scenes import * from topics.complex_numbers import * diff --git a/pi_creature/__init__.py b/pi_creature/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pi_creature/pi_creature.py b/pi_creature/pi_creature.py new file mode 100644 index 00000000..479c542d --- /dev/null +++ b/pi_creature/pi_creature.py @@ -0,0 +1,316 @@ +import numpy as np + +from constants import * + +from mobject.mobject import Mobject +from mobject.svg_mobject import SVGMobject +from mobject.tex_mobject import TextMobject +from mobject.vectorized_mobject import VGroup +from mobject.vectorized_mobject import VMobject + +from topics.objects import ThoughtBubble + +from animation.transform import Transform +from utils.config_ops import digest_config +from utils.rate_functions import squish_rate_func +from utils.rate_functions import there_and_back + +PI_CREATURE_DIR = os.path.join(MEDIA_DIR, "designs", "PiCreature") +PI_CREATURE_SCALE_FACTOR = 0.5 + +LEFT_EYE_INDEX = 0 +RIGHT_EYE_INDEX = 1 +LEFT_PUPIL_INDEX = 2 +RIGHT_PUPIL_INDEX = 3 +BODY_INDEX = 4 +MOUTH_INDEX = 5 + +class PiCreature(SVGMobject): + CONFIG = { + "color" : BLUE_E, + "file_name_prefix" : "PiCreatures", + "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): + digest_config(self, kwargs) + self.parts_named = False + try: + svg_file = os.path.join( + PI_CREATURE_DIR, + "%s_%s.svg"%(self.file_name_prefix, mode) + ) + SVGMobject.__init__(self, file_name = svg_file, **kwargs) + except: + warnings.warn("No %s design with mode %s"%(self.file_name_prefix, 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 set_color(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 + ]) + +def get_all_pi_creature_modes(): + result = [] + prefix = "%s_"%PiCreature.CONFIG["file_name_prefix"] + suffix = ".svg" + for file in os.listdir(PI_CREATURE_DIR): + if file.startswith(prefix) and file.endswith(suffix): + result.append( + file[len(prefix):-len(suffix)] + ) + return result + +class Randolph(PiCreature): + pass #Nothing more than an alternative name + +class Mortimer(PiCreature): + CONFIG = { + "color" : GREY_BROWN, + "flip_at_start" : True, + } + +class Mathematician(PiCreature): + CONFIG = { + "color" : GREY, + } + +class BabyPiCreature(PiCreature): + CONFIG = { + "scale_factor" : 0.5, + "eye_scale_factor" : 1.2, + "pupil_scale_factor" : 1.3 + } + 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) + +class TauCreature(PiCreature): + CONFIG = { + "file_name_prefix" : "TauCreatures" + } + +class ThreeLeggedPiCreature(PiCreature): + CONFIG = { + "file_name_prefix" : "ThreeLeggedPiCreatures" + } + +class Eyes(VMobject): + CONFIG = { + "height" : 0.3, + "thing_looked_at" : None, + "mode" : "plain", + } + def __init__(self, mobject, **kwargs): + VMobject.__init__(self, **kwargs) + self.mobject = mobject + self.submobjects = self.get_eyes().submobjects + + def get_eyes(self, mode = None, thing_to_look_at = None): + mode = mode or self.mode + if thing_to_look_at is None: + thing_to_look_at = self.thing_looked_at + + pi = Randolph(mode = mode) + eyes = VGroup(pi.eyes, pi.pupils) + pi.scale(self.height/eyes.get_height()) + if self.submobjects: + eyes.move_to(self, DOWN) + else: + eyes.move_to(self.mobject.get_top(), DOWN) + if thing_to_look_at is not None: + pi.look_at(thing_to_look_at) + return eyes + + def change_mode_anim(self, mode, **kwargs): + self.mode = mode + return Transform(self, self.get_eyes(mode = mode), **kwargs) + + def look_at_anim(self, point_or_mobject, **kwargs): + self.thing_looked_at = point_or_mobject + return Transform( + self, self.get_eyes(thing_to_look_at = point_or_mobject), + **kwargs + ) + + def blink_anim(self, **kwargs): + target = self.copy() + bottom_y = self.get_bottom()[1] + for submob in target: + submob.apply_function( + lambda p : [p[0], bottom_y, p[2]] + ) + if "rate_func" not in kwargs: + kwargs["rate_func"] = squish_rate_func(there_and_back) + return Transform(self, target, **kwargs) + + diff --git a/pi_creature/pi_creature_animations.py b/pi_creature/pi_creature_animations.py new file mode 100644 index 00000000..0a21ec39 --- /dev/null +++ b/pi_creature/pi_creature_animations.py @@ -0,0 +1,96 @@ +from __future__ import absolute_import + +from constants import * + +from mobject.mobject import Group + +from topics.objects import SpeechBubble + +from animation.creation import ShowCreation +from animation.creation import Write +from animation.composition import AnimationGroup +from animation.transform import ApplyMethod +from animation.creation import FadeOut +from animation.transform import MoveToTarget +from utils.config_ops import digest_config +from utils.rate_functions import squish_rate_func +from utils.rate_functions import there_and_back + +class Blink(ApplyMethod): + CONFIG = { + "rate_func" : squish_rate_func(there_and_back) + } + def __init__(self, pi_creature, **kwargs): + ApplyMethod.__init__(self, pi_creature.blink, **kwargs) + +class PiCreatureBubbleIntroduction(AnimationGroup): + CONFIG = { + "target_mode" : "speaking", + "bubble_class" : SpeechBubble, + "change_mode_kwargs" : {}, + "bubble_creation_class" : ShowCreation, + "bubble_creation_kwargs" : {}, + "bubble_kwargs" : {}, + "content_introduction_class" : Write, + "content_introduction_kwargs" : {}, + "look_at_arg" : None, + } + def __init__(self, pi_creature, *content, **kwargs): + digest_config(self, kwargs) + bubble = pi_creature.get_bubble( + *content, + bubble_class = self.bubble_class, + **self.bubble_kwargs + ) + Group(bubble, bubble.content).shift_onto_screen() + + pi_creature.generate_target() + pi_creature.target.change_mode(self.target_mode) + if self.look_at_arg is not None: + pi_creature.target.look_at(self.look_at_arg) + + change_mode = MoveToTarget(pi_creature, **self.change_mode_kwargs) + bubble_creation = self.bubble_creation_class( + bubble, **self.bubble_creation_kwargs + ) + content_introduction = self.content_introduction_class( + bubble.content, **self.content_introduction_kwargs + ) + AnimationGroup.__init__( + self, change_mode, bubble_creation, content_introduction, + **kwargs + ) + +class PiCreatureSays(PiCreatureBubbleIntroduction): + CONFIG = { + "target_mode" : "speaking", + "bubble_class" : SpeechBubble, + } + +class RemovePiCreatureBubble(AnimationGroup): + CONFIG = { + "target_mode" : "plain", + "look_at_arg" : None, + "remover" : True, + } + def __init__(self, pi_creature, **kwargs): + assert hasattr(pi_creature, "bubble") + digest_config(self, kwargs, locals()) + + pi_creature.generate_target() + pi_creature.target.change_mode(self.target_mode) + if self.look_at_arg is not None: + pi_creature.target.look_at(self.look_at_arg) + + AnimationGroup.__init__( + self, + MoveToTarget(pi_creature), + FadeOut(pi_creature.bubble), + FadeOut(pi_creature.bubble.content), + ) + + def clean_up(self, surrounding_scene = None): + AnimationGroup.clean_up(self, surrounding_scene) + self.pi_creature.bubble = None + if surrounding_scene is not None: + surrounding_scene.add(self.pi_creature) diff --git a/pi_creature/pi_creature_scene.py b/pi_creature/pi_creature_scene.py new file mode 100644 index 00000000..b8f46a42 --- /dev/null +++ b/pi_creature/pi_creature_scene.py @@ -0,0 +1,362 @@ +from __future__ import absolute_import + +import itertools as it +import numpy as np +import random + +from constants import * + +from mobject.vectorized_mobject import VGroup + +from topics.geometry import ScreenRectangle +from topics.objects import SpeechBubble +from topics.objects import ThoughtBubble + +from animation.transform import ApplyMethod +from animation.transform import ReplacementTransform +from animation.transform import Transform +from pi_creature.pi_creature import PiCreature +from pi_creature.pi_creature import Mortimer +from pi_creature.pi_creature import Randolph +from pi_creature.pi_creature_animations import Blink +from pi_creature.pi_creature_animations import PiCreatureBubbleIntroduction +from pi_creature.pi_creature_animations import RemovePiCreatureBubble +from scene.scene import Scene +from utils.rate_functions import squish_rate_func +from utils.rate_functions import there_and_back + +class PiCreatureScene(Scene): + CONFIG = { + "total_wait_time" : 0, + "seconds_to_blink" : 3, + "pi_creatures_start_on_screen" : True, + "default_pi_creature_kwargs" : { + "color" : GREY_BROWN, + "flip_at_start" : True, + }, + "default_pi_creature_start_corner" : DOWN+LEFT, + } + def setup(self): + self.pi_creatures = self.create_pi_creatures() + self.pi_creature = self.get_primary_pi_creature() + if self.pi_creatures_start_on_screen: + self.add(*self.pi_creatures) + + def create_pi_creatures(self): + """ + Likely updated for subclasses + """ + return VGroup(self.create_pi_creature()) + + def create_pi_creature(self): + pi_creature = PiCreature(**self.default_pi_creature_kwargs) + pi_creature.to_corner(self.default_pi_creature_start_corner) + return pi_creature + + def get_pi_creatures(self): + return self.pi_creatures + + def get_primary_pi_creature(self): + return self.pi_creatures[0] + + def any_pi_creatures_on_screen(self): + mobjects = self.get_mobjects() + return any([pi in mobjects for pi in self.get_pi_creatures()]) + + def get_on_screen_pi_creatures(self): + mobjects = self.get_mobjects() + return VGroup(*filter( + lambda pi : pi in mobjects, + self.get_pi_creatures() + )) + + def introduce_bubble(self, *args, **kwargs): + if isinstance(args[0], PiCreature): + pi_creature = args[0] + content = args[1:] + else: + pi_creature = self.get_primary_pi_creature() + content = args + + bubble_class = kwargs.pop("bubble_class", SpeechBubble) + target_mode = kwargs.pop( + "target_mode", + "thinking" if bubble_class is ThoughtBubble else "speaking" + ) + bubble_kwargs = kwargs.pop("bubble_kwargs", {}) + bubble_removal_kwargs = kwargs.pop("bubble_removal_kwargs", {}) + added_anims = kwargs.pop("added_anims", []) + + anims = [] + on_screen_mobjects = self.camera.extract_mobject_family_members( + self.get_mobjects() + ) + def has_bubble(pi): + return hasattr(pi, "bubble") and \ + pi.bubble is not None and \ + pi.bubble in on_screen_mobjects + + pi_creatures_with_bubbles = filter(has_bubble, self.get_pi_creatures()) + if pi_creature in pi_creatures_with_bubbles: + pi_creatures_with_bubbles.remove(pi_creature) + old_bubble = pi_creature.bubble + bubble = pi_creature.get_bubble( + *content, + bubble_class = bubble_class, + **bubble_kwargs + ) + anims += [ + ReplacementTransform(old_bubble, bubble), + ReplacementTransform(old_bubble.content, bubble.content), + pi_creature.change_mode, target_mode + ] + else: + anims.append(PiCreatureBubbleIntroduction( + pi_creature, + *content, + bubble_class = bubble_class, + bubble_kwargs = bubble_kwargs, + target_mode = target_mode, + **kwargs + )) + anims += [ + RemovePiCreatureBubble(pi, **bubble_removal_kwargs) + for pi in pi_creatures_with_bubbles + ] + anims += added_anims + + self.play(*anims, **kwargs) + + def pi_creature_says(self, *args, **kwargs): + self.introduce_bubble( + *args, + bubble_class = SpeechBubble, + **kwargs + ) + + def pi_creature_thinks(self, *args, **kwargs): + self.introduce_bubble( + *args, + bubble_class = ThoughtBubble, + **kwargs + ) + + def say(self, *content, **kwargs): + self.pi_creature_says(self.get_primary_pi_creature(), *content, **kwargs) + + def think(self, *content, **kwargs): + self.pi_creature_thinks(self.get_primary_pi_creature(), *content, **kwargs) + + def compile_play_args_to_animation_list(self, *args): + """ + Add animations so that all pi creatures look at the + first mobject being animated with each .play call + """ + animations = Scene.compile_play_args_to_animation_list(self, *args) + if not self.any_pi_creatures_on_screen(): + return animations + + non_pi_creature_anims = filter( + lambda anim : anim.mobject not in self.get_pi_creatures(), + animations + ) + if len(non_pi_creature_anims) == 0: + return animations + first_anim = non_pi_creature_anims[0] + #Look at ending state + first_anim.update(1) + point_of_interest = first_anim.mobject.get_center() + first_anim.update(0) + + for pi_creature in self.get_pi_creatures(): + if pi_creature not in self.get_mobjects(): + continue + if pi_creature in first_anim.mobject.submobject_family(): + continue + anims_with_pi_creature = filter( + lambda anim : pi_creature in anim.mobject.submobject_family(), + animations + ) + for anim in anims_with_pi_creature: + if isinstance(anim, Transform): + index = anim.mobject.submobject_family().index(pi_creature) + target_family = anim.target_mobject.submobject_family() + target = target_family[index] + if isinstance(target, PiCreature): + target.look_at(point_of_interest) + if not anims_with_pi_creature: + animations.append( + ApplyMethod(pi_creature.look_at, point_of_interest) + ) + return animations + + def blink(self): + self.play(Blink(random.choice(self.get_on_screen_pi_creatures()))) + + def joint_blink(self, pi_creatures = None, shuffle = True, **kwargs): + if pi_creatures is None: + pi_creatures = self.get_on_screen_pi_creatures() + creatures_list = list(pi_creatures) + if shuffle: + random.shuffle(creatures_list) + + def get_rate_func(pi): + index = creatures_list.index(pi) + proportion = float(index)/len(creatures_list) + start_time = 0.8*proportion + return squish_rate_func( + there_and_back, + start_time, start_time + 0.2 + ) + + self.play(*[ + Blink(pi, rate_func = get_rate_func(pi), **kwargs) + for pi in creatures_list + ]) + return self + + def wait(self, time = 1, blink = True): + while time >= 1: + time_to_blink = self.total_wait_time%self.seconds_to_blink == 0 + if blink and self.any_pi_creatures_on_screen() and time_to_blink: + self.blink() + self.num_plays -= 1 #This shouldn't count as an animation + else: + self.non_blink_wait() + time -= 1 + self.total_wait_time += 1 + if time > 0: + self.non_blink_wait(time) + return self + + def non_blink_wait(self, time = 1): + Scene.wait(self, time) + return self + + def change_mode(self, mode): + self.play(self.get_primary_pi_creature().change_mode, mode) + + def look_at(self, thing_to_look_at, pi_creatures = None): + if pi_creatures is None: + pi_creatures = self.get_pi_creatures() + self.play(*it.chain(*[ + [pi.look_at, thing_to_look_at] + for pi in pi_creatures + ])) + +class TeacherStudentsScene(PiCreatureScene): + CONFIG = { + "student_colors" : [BLUE_D, BLUE_E, BLUE_C], + "student_scale_factor" : 0.8, + "seconds_to_blink" : 2, + "screen_height" : 3, + } + def setup(self): + PiCreatureScene.setup(self) + self.screen = ScreenRectangle(height = self.screen_height) + self.screen.to_corner(UP+LEFT) + self.hold_up_spot = self.teacher.get_corner(UP+LEFT) + MED_LARGE_BUFF*UP + + def create_pi_creatures(self): + self.teacher = Mortimer() + self.teacher.to_corner(DOWN + RIGHT) + self.teacher.look(DOWN+LEFT) + self.students = VGroup(*[ + Randolph(color = c) + for c in self.student_colors + ]) + self.students.arrange_submobjects(RIGHT) + self.students.scale(self.student_scale_factor) + self.students.to_corner(DOWN+LEFT) + self.teacher.look_at(self.students[-1].eyes) + for student in self.students: + student.look_at(self.teacher.eyes) + + return [self.teacher] + list(self.students) + + def get_teacher(self): + return self.teacher + + def get_students(self): + return self.students + + def teacher_says(self, *content, **kwargs): + return self.pi_creature_says( + self.get_teacher(), *content, **kwargs + ) + + def student_says(self, *content, **kwargs): + if "target_mode" not in kwargs: + target_mode = random.choice([ + "raise_right_hand", + "raise_left_hand", + ]) + kwargs["target_mode"] = target_mode + student = self.get_students()[kwargs.get("student_index", 1)] + return self.pi_creature_says( + student, *content, **kwargs + ) + + def teacher_thinks(self, *content, **kwargs): + return self.pi_creature_thinks( + self.get_teacher(), *content, **kwargs + ) + + def student_thinks(self, *content, **kwargs): + student = self.get_students()[kwargs.get("student_index", 1)] + return self.pi_creature_thinks(student, *content, **kwargs) + + def change_all_student_modes(self, mode, **kwargs): + self.change_student_modes(*[mode]*len(self.students), **kwargs) + + def change_student_modes(self, *modes, **kwargs): + added_anims = kwargs.pop("added_anims", []) + self.play( + self.get_student_changes(*modes, **kwargs), + *added_anims + ) + + def get_student_changes(self, *modes, **kwargs): + pairs = zip(self.get_students(), modes) + pairs = [(s, m) for s, m in pairs if m is not None] + start = VGroup(*[s for s, m in pairs]) + target = VGroup(*[s.copy().change_mode(m) for s, m in pairs]) + if "look_at_arg" in kwargs: + for pi in target: + pi.look_at(kwargs["look_at_arg"]) + submobject_mode = kwargs.get("submobject_mode", "lagged_start") + return Transform( + start, target, + submobject_mode = submobject_mode, + run_time = 2 + ) + + def zoom_in_on_thought_bubble(self, bubble = None, radius = FRAME_Y_RADIUS+FRAME_X_RADIUS): + if bubble is None: + for pi in self.get_pi_creatures(): + if hasattr(pi, "bubble") and isinstance(pi.bubble, ThoughtBubble): + bubble = pi.bubble + break + if bubble is None: + raise Exception("No pi creatures have a thought bubble") + vect = -bubble.get_bubble_center() + def func(point): + centered = point+vect + return radius*centered/np.linalg.norm(centered) + self.play(*[ + ApplyPointwiseFunction(func, mob) + for mob in self.get_mobjects() + ]) + + def teacher_holds_up(self, mobject, target_mode = "raise_right_hand", **kwargs): + mobject.move_to(self.hold_up_spot, DOWN) + mobject.shift_onto_screen() + mobject_copy = mobject.copy() + mobject_copy.shift(DOWN) + mobject_copy.fade(1) + self.play( + ReplacementTransform(mobject_copy, mobject), + self.teacher.change, target_mode, + ) + + diff --git a/topics/characters.py b/topics/characters.py deleted file mode 100644 index ffcbd668..00000000 --- a/topics/characters.py +++ /dev/null @@ -1,762 +0,0 @@ -import itertools as it -import numpy as np -import random - -from constants import * - -from mobject.mobject import Group -from mobject.mobject import Mobject -from mobject.svg_mobject import SVGMobject -from mobject.tex_mobject import TexMobject -from mobject.tex_mobject import TextMobject -from mobject.vectorized_mobject import VGroup -from mobject.vectorized_mobject import VMobject - -from topics.geometry import ScreenRectangle -from topics.objects import Bubble -from topics.objects import SpeechBubble -from topics.objects import ThoughtBubble - -from animation.animation import Animation -from animation.composition import AnimationGroup -from animation.creation import ShowCreation -from animation.creation import Write -from animation.transform import ApplyMethod -from animation.creation import FadeIn -from animation.creation import FadeOut -from animation.transform import MoveToTarget -from animation.transform import ReplacementTransform -from animation.transform import Transform -from scene.scene import Scene -from utils.config_ops import digest_config -from utils.rate_functions import squish_rate_func -from utils.rate_functions import there_and_back - - -PI_CREATURE_DIR = os.path.join(MEDIA_DIR, "designs", "PiCreature") -PI_CREATURE_SCALE_FACTOR = 0.5 - -LEFT_EYE_INDEX = 0 -RIGHT_EYE_INDEX = 1 -LEFT_PUPIL_INDEX = 2 -RIGHT_PUPIL_INDEX = 3 -BODY_INDEX = 4 -MOUTH_INDEX = 5 - - -class PiCreature(SVGMobject): - CONFIG = { - "color" : BLUE_E, - "file_name_prefix" : "PiCreatures", - "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): - digest_config(self, kwargs) - self.parts_named = False - try: - svg_file = os.path.join( - PI_CREATURE_DIR, - "%s_%s.svg"%(self.file_name_prefix, mode) - ) - SVGMobject.__init__(self, file_name = svg_file, **kwargs) - except: - warnings.warn("No %s design with mode %s"%(self.file_name_prefix, 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 set_color(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 - ]) - -def get_all_pi_creature_modes(): - result = [] - prefix = "%s_"%PiCreature.CONFIG["file_name_prefix"] - suffix = ".svg" - for file in os.listdir(PI_CREATURE_DIR): - if file.startswith(prefix) and file.endswith(suffix): - result.append( - file[len(prefix):-len(suffix)] - ) - return result - -class Randolph(PiCreature): - pass #Nothing more than an alternative name - -class Mortimer(PiCreature): - CONFIG = { - "color" : GREY_BROWN, - "flip_at_start" : True, - } - -class Mathematician(PiCreature): - CONFIG = { - "color" : GREY, - } - -class BabyPiCreature(PiCreature): - CONFIG = { - "scale_factor" : 0.5, - "eye_scale_factor" : 1.2, - "pupil_scale_factor" : 1.3 - } - 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) - -class TauCreature(PiCreature): - CONFIG = { - "file_name_prefix" : "TauCreatures" - } - -class ThreeLeggedPiCreature(PiCreature): - CONFIG = { - "file_name_prefix" : "ThreeLeggedPiCreatures" - } - - -class Blink(ApplyMethod): - CONFIG = { - "rate_func" : squish_rate_func(there_and_back) - } - def __init__(self, pi_creature, **kwargs): - ApplyMethod.__init__(self, pi_creature.blink, **kwargs) - -class Eyes(VMobject): - CONFIG = { - "height" : 0.3, - "thing_looked_at" : None, - "mode" : "plain", - } - def __init__(self, mobject, **kwargs): - VMobject.__init__(self, **kwargs) - self.mobject = mobject - self.submobjects = self.get_eyes().submobjects - - def get_eyes(self, mode = None, thing_to_look_at = None): - mode = mode or self.mode - if thing_to_look_at is None: - thing_to_look_at = self.thing_looked_at - - pi = Randolph(mode = mode) - eyes = VGroup(pi.eyes, pi.pupils) - pi.scale(self.height/eyes.get_height()) - if self.submobjects: - eyes.move_to(self, DOWN) - else: - eyes.move_to(self.mobject.get_top(), DOWN) - if thing_to_look_at is not None: - pi.look_at(thing_to_look_at) - return eyes - - def change_mode_anim(self, mode, **kwargs): - self.mode = mode - return Transform(self, self.get_eyes(mode = mode), **kwargs) - - def look_at_anim(self, point_or_mobject, **kwargs): - self.thing_looked_at = point_or_mobject - return Transform( - self, self.get_eyes(thing_to_look_at = point_or_mobject), - **kwargs - ) - - def blink_anim(self, **kwargs): - target = self.copy() - bottom_y = self.get_bottom()[1] - for submob in target: - submob.apply_function( - lambda p : [p[0], bottom_y, p[2]] - ) - if "rate_func" not in kwargs: - kwargs["rate_func"] = squish_rate_func(there_and_back) - return Transform(self, target, **kwargs) - -####################### - -class PiCreatureBubbleIntroduction(AnimationGroup): - CONFIG = { - "target_mode" : "speaking", - "bubble_class" : SpeechBubble, - "change_mode_kwargs" : {}, - "bubble_creation_class" : ShowCreation, - "bubble_creation_kwargs" : {}, - "bubble_kwargs" : {}, - "content_introduction_class" : Write, - "content_introduction_kwargs" : {}, - "look_at_arg" : None, - } - def __init__(self, pi_creature, *content, **kwargs): - digest_config(self, kwargs) - bubble = pi_creature.get_bubble( - *content, - bubble_class = self.bubble_class, - **self.bubble_kwargs - ) - Group(bubble, bubble.content).shift_onto_screen() - - pi_creature.generate_target() - pi_creature.target.change_mode(self.target_mode) - if self.look_at_arg is not None: - pi_creature.target.look_at(self.look_at_arg) - - change_mode = MoveToTarget(pi_creature, **self.change_mode_kwargs) - bubble_creation = self.bubble_creation_class( - bubble, **self.bubble_creation_kwargs - ) - content_introduction = self.content_introduction_class( - bubble.content, **self.content_introduction_kwargs - ) - AnimationGroup.__init__( - self, change_mode, bubble_creation, content_introduction, - **kwargs - ) - -class PiCreatureSays(PiCreatureBubbleIntroduction): - CONFIG = { - "target_mode" : "speaking", - "bubble_class" : SpeechBubble, - } - -class RemovePiCreatureBubble(AnimationGroup): - CONFIG = { - "target_mode" : "plain", - "look_at_arg" : None, - "remover" : True, - } - def __init__(self, pi_creature, **kwargs): - assert hasattr(pi_creature, "bubble") - digest_config(self, kwargs, locals()) - - pi_creature.generate_target() - pi_creature.target.change_mode(self.target_mode) - if self.look_at_arg is not None: - pi_creature.target.look_at(self.look_at_arg) - - AnimationGroup.__init__( - self, - MoveToTarget(pi_creature), - FadeOut(pi_creature.bubble), - FadeOut(pi_creature.bubble.content), - ) - - def clean_up(self, surrounding_scene = None): - AnimationGroup.clean_up(self, surrounding_scene) - self.pi_creature.bubble = None - if surrounding_scene is not None: - surrounding_scene.add(self.pi_creature) - -########### - -class PiCreatureScene(Scene): - CONFIG = { - "total_wait_time" : 0, - "seconds_to_blink" : 3, - "pi_creatures_start_on_screen" : True, - "default_pi_creature_kwargs" : { - "color" : GREY_BROWN, - "flip_at_start" : True, - }, - "default_pi_creature_start_corner" : DOWN+LEFT, - } - def setup(self): - self.pi_creatures = self.create_pi_creatures() - self.pi_creature = self.get_primary_pi_creature() - if self.pi_creatures_start_on_screen: - self.add(*self.pi_creatures) - - def create_pi_creatures(self): - """ - Likely updated for subclasses - """ - return VGroup(self.create_pi_creature()) - - def create_pi_creature(self): - pi_creature = PiCreature(**self.default_pi_creature_kwargs) - pi_creature.to_corner(self.default_pi_creature_start_corner) - return pi_creature - - def get_pi_creatures(self): - return self.pi_creatures - - def get_primary_pi_creature(self): - return self.pi_creatures[0] - - def any_pi_creatures_on_screen(self): - mobjects = self.get_mobjects() - return any([pi in mobjects for pi in self.get_pi_creatures()]) - - def get_on_screen_pi_creatures(self): - mobjects = self.get_mobjects() - return VGroup(*filter( - lambda pi : pi in mobjects, - self.get_pi_creatures() - )) - - def introduce_bubble(self, *args, **kwargs): - if isinstance(args[0], PiCreature): - pi_creature = args[0] - content = args[1:] - else: - pi_creature = self.get_primary_pi_creature() - content = args - - bubble_class = kwargs.pop("bubble_class", SpeechBubble) - target_mode = kwargs.pop( - "target_mode", - "thinking" if bubble_class is ThoughtBubble else "speaking" - ) - bubble_kwargs = kwargs.pop("bubble_kwargs", {}) - bubble_removal_kwargs = kwargs.pop("bubble_removal_kwargs", {}) - added_anims = kwargs.pop("added_anims", []) - - anims = [] - on_screen_mobjects = self.camera.extract_mobject_family_members( - self.get_mobjects() - ) - def has_bubble(pi): - return hasattr(pi, "bubble") and \ - pi.bubble is not None and \ - pi.bubble in on_screen_mobjects - - pi_creatures_with_bubbles = filter(has_bubble, self.get_pi_creatures()) - if pi_creature in pi_creatures_with_bubbles: - pi_creatures_with_bubbles.remove(pi_creature) - old_bubble = pi_creature.bubble - bubble = pi_creature.get_bubble( - *content, - bubble_class = bubble_class, - **bubble_kwargs - ) - anims += [ - ReplacementTransform(old_bubble, bubble), - ReplacementTransform(old_bubble.content, bubble.content), - pi_creature.change_mode, target_mode - ] - else: - anims.append(PiCreatureBubbleIntroduction( - pi_creature, - *content, - bubble_class = bubble_class, - bubble_kwargs = bubble_kwargs, - target_mode = target_mode, - **kwargs - )) - anims += [ - RemovePiCreatureBubble(pi, **bubble_removal_kwargs) - for pi in pi_creatures_with_bubbles - ] - anims += added_anims - - self.play(*anims, **kwargs) - - def pi_creature_says(self, *args, **kwargs): - self.introduce_bubble( - *args, - bubble_class = SpeechBubble, - **kwargs - ) - - def pi_creature_thinks(self, *args, **kwargs): - self.introduce_bubble( - *args, - bubble_class = ThoughtBubble, - **kwargs - ) - - def say(self, *content, **kwargs): - self.pi_creature_says(self.get_primary_pi_creature(), *content, **kwargs) - - def think(self, *content, **kwargs): - self.pi_creature_thinks(self.get_primary_pi_creature(), *content, **kwargs) - - def compile_play_args_to_animation_list(self, *args): - """ - Add animations so that all pi creatures look at the - first mobject being animated with each .play call - """ - animations = Scene.compile_play_args_to_animation_list(self, *args) - if not self.any_pi_creatures_on_screen(): - return animations - - non_pi_creature_anims = filter( - lambda anim : anim.mobject not in self.get_pi_creatures(), - animations - ) - if len(non_pi_creature_anims) == 0: - return animations - first_anim = non_pi_creature_anims[0] - #Look at ending state - first_anim.update(1) - point_of_interest = first_anim.mobject.get_center() - first_anim.update(0) - - for pi_creature in self.get_pi_creatures(): - if pi_creature not in self.get_mobjects(): - continue - if pi_creature in first_anim.mobject.submobject_family(): - continue - anims_with_pi_creature = filter( - lambda anim : pi_creature in anim.mobject.submobject_family(), - animations - ) - for anim in anims_with_pi_creature: - if isinstance(anim, Transform): - index = anim.mobject.submobject_family().index(pi_creature) - target_family = anim.target_mobject.submobject_family() - target = target_family[index] - if isinstance(target, PiCreature): - target.look_at(point_of_interest) - if not anims_with_pi_creature: - animations.append( - ApplyMethod(pi_creature.look_at, point_of_interest) - ) - return animations - - def blink(self): - self.play(Blink(random.choice(self.get_on_screen_pi_creatures()))) - - def joint_blink(self, pi_creatures = None, shuffle = True, **kwargs): - if pi_creatures is None: - pi_creatures = self.get_on_screen_pi_creatures() - creatures_list = list(pi_creatures) - if shuffle: - random.shuffle(creatures_list) - - def get_rate_func(pi): - index = creatures_list.index(pi) - proportion = float(index)/len(creatures_list) - start_time = 0.8*proportion - return squish_rate_func( - there_and_back, - start_time, start_time + 0.2 - ) - - self.play(*[ - Blink(pi, rate_func = get_rate_func(pi), **kwargs) - for pi in creatures_list - ]) - return self - - def wait(self, time = 1, blink = True): - while time >= 1: - time_to_blink = self.total_wait_time%self.seconds_to_blink == 0 - if blink and self.any_pi_creatures_on_screen() and time_to_blink: - self.blink() - self.num_plays -= 1 #This shouldn't count as an animation - else: - self.non_blink_wait() - time -= 1 - self.total_wait_time += 1 - if time > 0: - self.non_blink_wait(time) - return self - - def non_blink_wait(self, time = 1): - Scene.wait(self, time) - return self - - def change_mode(self, mode): - self.play(self.get_primary_pi_creature().change_mode, mode) - - def look_at(self, thing_to_look_at, pi_creatures = None): - if pi_creatures is None: - pi_creatures = self.get_pi_creatures() - self.play(*it.chain(*[ - [pi.look_at, thing_to_look_at] - for pi in pi_creatures - ])) - -class TeacherStudentsScene(PiCreatureScene): - CONFIG = { - "student_colors" : [BLUE_D, BLUE_E, BLUE_C], - "student_scale_factor" : 0.8, - "seconds_to_blink" : 2, - "screen_height" : 3, - } - def setup(self): - PiCreatureScene.setup(self) - self.screen = ScreenRectangle(height = self.screen_height) - self.screen.to_corner(UP+LEFT) - self.hold_up_spot = self.teacher.get_corner(UP+LEFT) + MED_LARGE_BUFF*UP - - def create_pi_creatures(self): - self.teacher = Mortimer() - self.teacher.to_corner(DOWN + RIGHT) - self.teacher.look(DOWN+LEFT) - self.students = VGroup(*[ - Randolph(color = c) - for c in self.student_colors - ]) - self.students.arrange_submobjects(RIGHT) - self.students.scale(self.student_scale_factor) - self.students.to_corner(DOWN+LEFT) - self.teacher.look_at(self.students[-1].eyes) - for student in self.students: - student.look_at(self.teacher.eyes) - - return [self.teacher] + list(self.students) - - def get_teacher(self): - return self.teacher - - def get_students(self): - return self.students - - def teacher_says(self, *content, **kwargs): - return self.pi_creature_says( - self.get_teacher(), *content, **kwargs - ) - - def student_says(self, *content, **kwargs): - if "target_mode" not in kwargs: - target_mode = random.choice([ - "raise_right_hand", - "raise_left_hand", - ]) - kwargs["target_mode"] = target_mode - student = self.get_students()[kwargs.get("student_index", 1)] - return self.pi_creature_says( - student, *content, **kwargs - ) - - def teacher_thinks(self, *content, **kwargs): - return self.pi_creature_thinks( - self.get_teacher(), *content, **kwargs - ) - - def student_thinks(self, *content, **kwargs): - student = self.get_students()[kwargs.get("student_index", 1)] - return self.pi_creature_thinks(student, *content, **kwargs) - - def change_all_student_modes(self, mode, **kwargs): - self.change_student_modes(*[mode]*len(self.students), **kwargs) - - def change_student_modes(self, *modes, **kwargs): - added_anims = kwargs.pop("added_anims", []) - self.play( - self.get_student_changes(*modes, **kwargs), - *added_anims - ) - - def get_student_changes(self, *modes, **kwargs): - pairs = zip(self.get_students(), modes) - pairs = [(s, m) for s, m in pairs if m is not None] - start = VGroup(*[s for s, m in pairs]) - target = VGroup(*[s.copy().change_mode(m) for s, m in pairs]) - if "look_at_arg" in kwargs: - for pi in target: - pi.look_at(kwargs["look_at_arg"]) - submobject_mode = kwargs.get("submobject_mode", "lagged_start") - return Transform( - start, target, - submobject_mode = submobject_mode, - run_time = 2 - ) - - def zoom_in_on_thought_bubble(self, bubble = None, radius = FRAME_Y_RADIUS+FRAME_X_RADIUS): - if bubble is None: - for pi in self.get_pi_creatures(): - if hasattr(pi, "bubble") and isinstance(pi.bubble, ThoughtBubble): - bubble = pi.bubble - break - if bubble is None: - raise Exception("No pi creatures have a thought bubble") - vect = -bubble.get_bubble_center() - def func(point): - centered = point+vect - return radius*centered/np.linalg.norm(centered) - self.play(*[ - ApplyPointwiseFunction(func, mob) - for mob in self.get_mobjects() - ]) - - def teacher_holds_up(self, mobject, target_mode = "raise_right_hand", **kwargs): - mobject.move_to(self.hold_up_spot, DOWN) - mobject.shift_onto_screen() - mobject_copy = mobject.copy() - mobject_copy.shift(DOWN) - mobject_copy.fade(1) - self.play( - ReplacementTransform(mobject_copy, mobject), - self.teacher.change, target_mode, - ) - - - - - - - - - - - diff --git a/topics/common_scenes.py b/topics/common_scenes.py index 34a8d89b..9b2d208b 100644 --- a/topics/common_scenes.py +++ b/topics/common_scenes.py @@ -20,7 +20,6 @@ from topics.geometry import Rectangle from topics.geometry import Square from topics.objects import PatreonLogo - class OpeningQuote(Scene): CONFIG = { "quote" : [], diff --git a/topics/complex_numbers.py b/topics/complex_numbers.py index 437682d4..df566378 100644 --- a/topics/complex_numbers.py +++ b/topics/complex_numbers.py @@ -18,7 +18,6 @@ from utils.paths import path_along_arc from utils.space_ops import R3_to_complex from utils.space_ops import complex_to_R3 - class ComplexTransformationScene(Scene): CONFIG = { "plane_config" : {}, diff --git a/topics/fractals.py b/topics/fractals.py index 1e405a67..f3122dde 100644 --- a/topics/fractals.py +++ b/topics/fractals.py @@ -1,4 +1,3 @@ -# from mobject.mobject import Mobject, Point, Mobject1D from animation.creation import ShowCreation from animation.transform import Transform from characters import PiCreature @@ -72,7 +71,6 @@ def fractalification_iteration(vmobject, dimension = 1.05, num_inserted_anchors_ ] return vmobject - class SelfSimilarFractal(VMobject): CONFIG = { "order" : 5, @@ -117,7 +115,6 @@ class SelfSimilarFractal(VMobject): def arrange_subparts(self, *subparts): raise Exception("Not implemented") - class Sierpinski(SelfSimilarFractal): def get_seed_shape(self): return Polygon( @@ -129,7 +126,6 @@ class Sierpinski(SelfSimilarFractal): tri1.move_to(tri2.get_corner(DOWN+LEFT), UP) tri3.move_to(tri2.get_corner(DOWN+RIGHT), UP) - class DiamondFractal(SelfSimilarFractal): CONFIG = { "num_subparts" : 4, @@ -145,7 +141,6 @@ class DiamondFractal(SelfSimilarFractal): part.next_to(ORIGIN, vect, buff = 0) VGroup(*subparts).rotate(np.pi/4, about_point = ORIGIN) - class PentagonalFractal(SelfSimilarFractal): CONFIG = { "num_subparts" : 5, @@ -182,7 +177,6 @@ class PentagonalPiCreatureFractal(PentagonalFractal): part.rotate(2*np.pi/5, about_point = ORIGIN) PentagonalFractal.arrange_subparts(self, *subparts) - class PiCreatureFractal(VMobject): CONFIG = { "order" : 7, @@ -242,7 +236,6 @@ class PiCreatureFractal(VMobject): # VMobject.init_colors(self) # self.set_color_by_gradient(*self.colors) - class WonkyHexagonFractal(SelfSimilarFractal): CONFIG = { "num_subparts" : 7 @@ -296,7 +289,6 @@ class JaggedCurvePiece(VMobject): indices = np.linspace(0, len(anchors)-1, n+len(anchors)).astype('int') self.set_points_as_corners(anchors[indices]) - class FractalCurve(VMobject): CONFIG = { "radius" : 3, @@ -335,7 +327,6 @@ class FractalCurve(VMobject): def get_anchor_points(self): raise Exception("Not implemented") - class LindenmayerCurve(FractalCurve): CONFIG = { "axiom" : "A", @@ -376,7 +367,6 @@ class LindenmayerCurve(FractalCurve): result.append(curr) return np.array(result) - center_of_mass(result) - class SelfSimilarSpaceFillingCurve(FractalCurve): CONFIG = { "offsets" : [], @@ -420,8 +410,6 @@ class SelfSimilarSpaceFillingCurve(FractalCurve): def generate_grid(self): raise Exception("Not implemented") - - class HilbertCurve(SelfSimilarSpaceFillingCurve): CONFIG = { "offsets" : [ @@ -436,7 +424,6 @@ class HilbertCurve(SelfSimilarSpaceFillingCurve): }, } - class HilbertCurve3D(SelfSimilarSpaceFillingCurve): CONFIG = { "offsets" : [ @@ -472,7 +459,6 @@ class HilbertCurve3D(SelfSimilarSpaceFillingCurve): copy += offset*self.radius*self.radius_scale_factor return copy - class PeanoCurve(SelfSimilarSpaceFillingCurve): CONFIG = { "colors" : [PURPLE, TEAL], @@ -607,7 +593,6 @@ class KochCurve(KochSnowFlake): CONFIG = { "axiom" : "A--" } - class QuadraticKoch(LindenmayerCurve): CONFIG = { @@ -622,7 +607,6 @@ class QuadraticKoch(LindenmayerCurve): "angle" : np.pi/2 } - class QuadraticKochIsland(QuadraticKoch): CONFIG = { "axiom" : "A+A+A+A" diff --git a/topics/geometry.py b/topics/geometry.py index 8be42047..c01899cf 100644 --- a/topics/geometry.py +++ b/topics/geometry.py @@ -108,8 +108,6 @@ class Arc(VMobject): return self - - class ArcBetweenPoints(Arc): def __init__(self, start_point, end_point, angle = TAU/4, **kwargs): @@ -150,7 +148,6 @@ class CurvedArrow(ArcBetweenPoints): else: ArcBetweenPoints.__init__(self, end_point, start_point, angle = -angle, **kwargs) self.add_tip(at_start = False, at_end = True) - class CurvedDoubleArrow(ArcBetweenPoints): @@ -158,7 +155,6 @@ class CurvedDoubleArrow(ArcBetweenPoints): ArcBetweenPoints.__init__(self, start_point, end_point, angle = angle, **kwargs) self.add_tip(at_start = True, at_end = True) - class Circle(Arc): CONFIG = { "color" : RED, diff --git a/topics/graph_theory.py b/topics/graph_theory.py index f3a7f6a1..0e9ae25d 100644 --- a/topics/graph_theory.py +++ b/topics/graph_theory.py @@ -30,7 +30,6 @@ class Graph(): def __str__(self): return self.__class__.__name__ - class CubeGraph(Graph): """ 5 7 @@ -67,7 +66,6 @@ class CubeGraph(Graph): [4, 6, 7, 5],#By convention, last region will be "outside" ] - class SampleGraph(Graph): """ 4 2 3 8 @@ -115,7 +113,6 @@ class SampleGraph(Graph): (4, 5, 6, 7, 8, 3, 2), ] - class OctohedronGraph(Graph): """ 3 @@ -155,7 +152,6 @@ class OctohedronGraph(Graph): (3, 4, 5), ] - class CompleteGraph(Graph): def __init__(self, num_vertices, radius = 3): self.num_vertices = num_vertices @@ -173,7 +169,6 @@ class CompleteGraph(Graph): def __str__(self): return Graph.__str__(self) + str(self.num_vertices) - class GraphScene(Scene): args_list = [ (CubeGraph(),), diff --git a/topics/matrix.py b/topics/matrix.py index 936039f9..74630598 100644 --- a/topics/matrix.py +++ b/topics/matrix.py @@ -39,7 +39,6 @@ def matrix_to_tex_string(matrix): ] return prefix + " \\\\ ".join(rows) + suffix - def matrix_to_mobject(matrix): return TexMobject(matrix_to_tex_string(matrix)) @@ -139,8 +138,6 @@ class Matrix(VMobject): def get_brackets(self): return self.brackets - - class NumericalMatrixMultiplication(Scene): CONFIG = { "left_matrix" : [[1, 2], [3, 4]],