mirror of
https://github.com/3b1b/manim.git
synced 2025-09-19 04:41:56 +00:00
Pulled pi_creature constructs out into their own folder, so as to note some distinction from the remainder of the library
This commit is contained in:
parent
846bd1065c
commit
b757f8b422
12 changed files with 778 additions and 793 deletions
|
@ -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 *
|
||||
|
|
0
pi_creature/__init__.py
Normal file
0
pi_creature/__init__.py
Normal file
316
pi_creature/pi_creature.py
Normal file
316
pi_creature/pi_creature.py
Normal file
|
@ -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)
|
||||
|
||||
|
96
pi_creature/pi_creature_animations.py
Normal file
96
pi_creature/pi_creature_animations.py
Normal file
|
@ -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)
|
362
pi_creature/pi_creature_scene.py
Normal file
362
pi_creature/pi_creature_scene.py
Normal file
|
@ -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,
|
||||
)
|
||||
|
||||
|
|
@ -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,
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -20,7 +20,6 @@ from topics.geometry import Rectangle
|
|||
from topics.geometry import Square
|
||||
from topics.objects import PatreonLogo
|
||||
|
||||
|
||||
class OpeningQuote(Scene):
|
||||
CONFIG = {
|
||||
"quote" : [],
|
||||
|
|
|
@ -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" : {},
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(),),
|
||||
|
|
|
@ -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]],
|
||||
|
|
Loading…
Add table
Reference in a new issue