Merge branch 'simple-improvements'

This commit is contained in:
Grant Sanderson 2018-04-01 11:09:02 -07:00
commit 8c1673d235
98 changed files with 3854 additions and 3727 deletions

4
.gitignore vendored
View file

@ -1,10 +1,8 @@
*.pyc
.DS_Store
homeless.py
ka_playgrounds/
playground.py
special_animations.py
prettiness_hall_of_fame.py
random_scenes/
files/
ben_playground.py
ben_cairo_test.py

View file

@ -1,33 +1,4 @@
from constants import *
from mobject.tex_mobject import TexMobject
from mobject.mobject import Mobject
from mobject.image_mobject import ImageMobject
from mobject.vectorized_mobject import *
from animation.animation import Animation
from animation.transform import *
from animation.simple_animations import *
from animation.compositions import *
from animation.playground import *
from topics.geometry import *
from topics.characters import *
from topics.functions import *
from topics.fractals import *
from topics.number_line import *
from topics.combinatorics import *
from topics.numerals import *
from topics.three_dimensions import *
from topics.objects import *
from topics.complex_numbers import *
from topics.common_scenes import *
from topics.probability import *
from scene.scene import Scene
from scene.reconfigurable_scene import ReconfigurableScene
from scene.zoomed_scene import *
from camera.camera import Camera
from mobject.svg_mobject import *
from mobject.tex_mobject import *
from big_ol_pile_of_manim_imports import *
#revert_to_original_skipping_status

View file

@ -1,33 +1,4 @@
from constants import *
from mobject.tex_mobject import TexMobject
from mobject.mobject import Mobject
from mobject.image_mobject import ImageMobject
from mobject.vectorized_mobject import *
from animation.animation import Animation
from animation.transform import *
from animation.simple_animations import *
from animation.compositions import *
from animation.playground import *
from topics.geometry import *
from topics.characters import *
from topics.functions import *
from topics.fractals import *
from topics.number_line import *
from topics.combinatorics import *
from topics.numerals import *
from topics.three_dimensions import *
from topics.objects import *
from topics.complex_numbers import *
from topics.common_scenes import *
from topics.probability import *
from scene.scene import Scene
from scene.reconfigurable_scene import ReconfigurableScene
from scene.zoomed_scene import *
from camera.camera import Camera
from mobject.svg_mobject import *
from mobject.tex_mobject import *
from big_ol_pile_of_manim_imports import *
from eop.bayes import IntroducePokerHand

View file

@ -1,36 +1,4 @@
from constants import *
from mobject.tex_mobject import TexMobject
from mobject.mobject import Mobject
from mobject.image_mobject import ImageMobject
from mobject.vectorized_mobject import *
from animation.animation import Animation
from animation.transform import *
from animation.simple_animations import *
from animation.compositions import *
from animation.playground import *
from animation.continual_animation import *
from topics.geometry import *
from topics.characters import *
from topics.functions import *
from topics.fractals import *
from topics.number_line import *
from topics.combinatorics import *
from topics.numerals import *
from topics.three_dimensions import *
from topics.objects import *
from topics.probability import *
from topics.complex_numbers import *
from scene.scene import Scene
from scene.reconfigurable_scene import ReconfigurableScene
from scene.zoomed_scene import *
from camera.camera import Camera
from mobject.svg_mobject import *
from mobject.tex_mobject import *
from topics.graph_scene import *
from topics.probability import *
from topics.common_scenes import *
from big_ol_pile_of_manim_imports import *
#revert_to_original_skipping_status

View file

@ -1,33 +1,4 @@
from constants import *
from mobject.tex_mobject import TexMobject
from mobject.mobject import Mobject
from mobject.image_mobject import ImageMobject
from mobject.vectorized_mobject import *
from animation.animation import Animation
from animation.transform import *
from animation.simple_animations import *
from animation.compositions import *
from animation.playground import *
from topics.geometry import *
from topics.characters import *
from topics.functions import *
from topics.fractals import *
from topics.number_line import *
from topics.combinatorics import *
from topics.numerals import *
from topics.three_dimensions import *
from topics.objects import *
from topics.complex_numbers import *
from topics.common_scenes import *
from topics.probability import *
from scene.scene import Scene
from scene.reconfigurable_scene import ReconfigurableScene
from scene.zoomed_scene import *
from camera.camera import Camera
from mobject.svg_mobject import *
from mobject.tex_mobject import *
from big_ol_pile_of_manim_imports import *
from scene.scene import ProgressDisplay
import scipy

View file

@ -1,5 +0,0 @@
__all__ = [
"animation",
"simple_animations",
"transform"
]

View file

@ -1,54 +1,27 @@
import numpy as np
from __future__ import absolute_import
import itertools as it
import numpy as np
from constants import *
import warnings
from mobject.mobject import Mobject, Group
from mobject.vectorized_mobject import VMobject
from mobject.tex_mobject import TextMobject
from .animation import Animation
from transform import Transform
from animation.animation import Animation
from mobject.mobject import Group
from mobject.mobject import Mobject
from utils.bezier import inverse_interpolate
from utils.config_ops import digest_config
from utils.rate_functions import squish_rate_func
class LaggedStart(Animation):
class EmptyAnimation(Animation):
CONFIG = {
"run_time" : 2,
"lag_ratio" : 0.5,
"run_time" : 0,
"empty" : True
}
def __init__(self, AnimationClass, mobject, arg_creator = None, **kwargs):
digest_config(self, kwargs)
for key in "rate_func", "run_time", "lag_ratio":
if key in kwargs:
kwargs.pop(key)
if arg_creator is None:
arg_creator = lambda mobject : (mobject,)
self.subanimations = [
AnimationClass(
*arg_creator(submob),
run_time = self.run_time,
rate_func = squish_rate_func(
self.rate_func, beta, beta + self.lag_ratio
),
**kwargs
)
for submob, beta in zip(
mobject,
np.linspace(0, 1-self.lag_ratio, len(mobject))
)
]
Animation.__init__(self, mobject, **kwargs)
def update(self, alpha):
for anim in self.subanimations:
anim.update(alpha)
return self
def clean_up(self, *args, **kwargs):
for anim in self.subanimations:
anim.clean_up(*args, **kwargs)
def __init__(self, *args, **kwargs):
return Animation.__init__(self, Group(), *args, **kwargs)
class Succession(Animation):
CONFIG = {
@ -231,11 +204,64 @@ class AnimationGroup(Animation):
for anim in self.sub_anims:
anim.update_config(**kwargs)
class EmptyAnimation(Animation):
CONFIG = {
"run_time" : 0,
"empty" : True
}
# Variants on mappin an animation over submobjectsg
class LaggedStart(Animation):
CONFIG = {
"run_time" : 2,
"lag_ratio" : 0.5,
}
def __init__(self, AnimationClass, mobject, arg_creator = None, **kwargs):
digest_config(self, kwargs)
for key in "rate_func", "run_time", "lag_ratio":
if key in kwargs:
kwargs.pop(key)
if arg_creator is None:
arg_creator = lambda mobject : (mobject,)
self.subanimations = [
AnimationClass(
*arg_creator(submob),
run_time = self.run_time,
rate_func = squish_rate_func(
self.rate_func, beta, beta + self.lag_ratio
),
**kwargs
)
for submob, beta in zip(
mobject,
np.linspace(0, 1-self.lag_ratio, len(mobject))
)
]
Animation.__init__(self, mobject, **kwargs)
def update(self, alpha):
for anim in self.subanimations:
anim.update(alpha)
return self
def clean_up(self, *args, **kwargs):
for anim in self.subanimations:
anim.clean_up(*args, **kwargs)
class ApplyToCenters(Animation):
def __init__(self, AnimationClass, mobjects, **kwargs):
full_kwargs = AnimationClass.CONFIG
full_kwargs.update(kwargs)
full_kwargs["mobject"] = Mobject(*[
mob.get_point_mobject()
for mob in mobjects
])
self.centers_container = AnimationClass(**full_kwargs)
full_kwargs.pop("mobject")
Animation.__init__(self, Mobject(*mobjects), **full_kwargs)
self.name = str(self) + AnimationClass.__name__
def update_mobject(self, alpha):
self.centers_container.update_mobject(alpha)
center_mobs = self.centers_container.mobject.split()
mobjects = self.mobject.split()
for center_mob, mobject in zip(center_mobs, mobjects):
mobject.shift(
center_mob.get_center()-mobject.get_center()
)
def __init__(self, *args, **kwargs):
return Animation.__init__(self, Group(), *args, **kwargs)

185
animation/creation.py Normal file
View file

@ -0,0 +1,185 @@
from __future__ import absolute_import
import numpy as np
from constants import *
from animation.animation import Animation
from mobject.svg.tex_mobject import TextMobject
from mobject.types.vectorized_mobject import VMobject
from mobject.types.vectorized_mobject import VectorizedPoint
from animation.transform import Transform
from utils.bezier import interpolate
from utils.config_ops import digest_config
from utils.paths import counterclockwise_path
from utils.rate_functions import double_smooth
from utils.rate_functions import smooth
#Drawing
class ShowPartial(Animation):
def update_submobject(self, submobject, starting_submobject, alpha):
submobject.pointwise_become_partial(
starting_submobject, *self.get_bounds(alpha)
)
def get_bounds(self, alpha):
raise Exception("Not Implemented")
class ShowCreation(ShowPartial):
CONFIG = {
"submobject_mode" : "one_at_a_time",
}
def get_bounds(self, alpha):
return (0, alpha)
class Uncreate(ShowCreation):
CONFIG = {
"rate_func" : lambda t : smooth(1-t),
"remover" : True
}
class Write(ShowCreation):
CONFIG = {
"rate_func" : None,
"submobject_mode" : "lagged_start",
}
def __init__(self, mob_or_text, **kwargs):
digest_config(self, kwargs)
if isinstance(mob_or_text, str):
mobject = TextMobject(mob_or_text)
else:
mobject = mob_or_text
if "run_time" not in kwargs:
self.establish_run_time(mobject)
if "lag_factor" not in kwargs:
if len(mobject.family_members_with_points()) < 4:
min_lag_factor = 1
else:
min_lag_factor = 2
self.lag_factor = max(self.run_time - 1, min_lag_factor)
ShowCreation.__init__(self, mobject, **kwargs)
def establish_run_time(self, mobject):
num_subs = len(mobject.family_members_with_points())
if num_subs < 15:
self.run_time = 1
else:
self.run_time = 2
class DrawBorderThenFill(Animation):
CONFIG = {
"run_time" : 2,
"stroke_width" : 2,
"stroke_color" : None,
"rate_func" : double_smooth,
}
def __init__(self, vmobject, **kwargs):
if not isinstance(vmobject, VMobject):
raise Exception("DrawBorderThenFill only works for VMobjects")
self.reached_halfway_point_before = False
Animation.__init__(self, vmobject, **kwargs)
def update_submobject(self, submobject, starting_submobject, alpha):
submobject.pointwise_become_partial(
starting_submobject, 0, min(2*alpha, 1)
)
if alpha < 0.5:
if self.stroke_color:
color = self.stroke_color
elif starting_submobject.stroke_width > 0:
color = starting_submobject.get_stroke_color()
else:
color = starting_submobject.get_color()
submobject.set_stroke(color, width = self.stroke_width)
submobject.set_fill(opacity = 0)
else:
if not self.reached_halfway_point_before:
self.reached_halfway_point_before = True
submobject.points = np.array(starting_submobject.points)
width, opacity = [
interpolate(start, end, 2*alpha - 1)
for start, end in [
(self.stroke_width, starting_submobject.get_stroke_width()),
(0, starting_submobject.get_fill_opacity())
]
]
submobject.set_stroke(width = width)
submobject.set_fill(opacity = opacity)
#Fading
class FadeOut(Transform):
CONFIG = {
"remover" : True,
}
def __init__(self, mobject, **kwargs):
target = mobject.copy()
target.fade(1)
Transform.__init__(self, mobject, target, **kwargs)
def clean_up(self, surrounding_scene = None):
Transform.clean_up(self, surrounding_scene)
self.update(0)
class FadeIn(Transform):
def __init__(self, mobject, **kwargs):
target = mobject.copy()
Transform.__init__(self, mobject, target, **kwargs)
self.starting_mobject.fade(1)
if isinstance(self.starting_mobject, VMobject):
self.starting_mobject.set_stroke(width = 0)
self.starting_mobject.set_fill(opacity = 0)
class FadeInAndShiftFromDirection(Transform):
CONFIG = {
"direction" : DOWN,
}
def __init__(self, mobject, **kwargs):
digest_config(self, kwargs)
target = mobject.copy()
mobject.shift(self.direction)
mobject.fade(1)
Transform.__init__(self, mobject, target, **kwargs)
class FadeInFromDown(FadeInAndShiftFromDirection):
"""
Essential a more convenient form of FadeInAndShiftFromDirection
"""
CONFIG = {
"direction" : DOWN,
}
#Growing
class GrowFromPoint(Transform):
CONFIG = {
"point_color" : None,
}
def __init__(self, mobject, point, **kwargs):
digest_config(self, kwargs)
target = mobject.copy()
point_mob = VectorizedPoint(point)
if self.point_color:
point_mob.set_color(self.point_color)
mobject.replace(point_mob)
mobject.set_color(point_mob.get_color())
Transform.__init__(self, mobject, target, **kwargs)
class GrowFromCenter(GrowFromPoint):
def __init__(self, mobject, **kwargs):
GrowFromPoint.__init__(self, mobject, mobject.get_center(), **kwargs)
class GrowArrow(GrowFromPoint):
def __init__(self, arrow, **kwargs):
GrowFromPoint.__init__(self, arrow, arrow.get_start(), **kwargs)
class SpinInFromNothing(GrowFromCenter):
CONFIG = {
"path_func" : counterclockwise_path()
}
class ShrinkToCenter(Transform):
def __init__(self, mobject, **kwargs):
Transform.__init__(
self, mobject, mobject.get_point_mobject(), **kwargs
)

187
animation/indication.py Normal file
View file

@ -0,0 +1,187 @@
from __future__ import absolute_import
import numpy as np
from constants import *
from animation.animation import Animation
from animation.movement import Homotopy
from animation.creation import ShowPartial
from animation.transform import Transform
from mobject.mobject import Group
from mobject.mobject import Mobject
from mobject.types.vectorized_mobject import VMobject
from mobject.geometry import Circle
from mobject.geometry import Dot
from utils.config_ops import digest_config
from utils.rate_functions import squish_rate_func
from utils.rate_functions import there_and_back
class FocusOn(Transform):
CONFIG = {
"opacity" : 0.2,
"color" : GREY,
"run_time" : 2,
"remover" : True,
}
def __init__(self, mobject_or_point, **kwargs):
digest_config(self, kwargs)
big_dot = Dot(
radius = FRAME_X_RADIUS+FRAME_Y_RADIUS,
stroke_width = 0,
fill_color = self.color,
fill_opacity = 0,
)
little_dot = Dot(radius = 0)
little_dot.set_fill(self.color, opacity = self.opacity)
little_dot.move_to(mobject_or_point)
Transform.__init__(self, big_dot, little_dot, **kwargs)
class Indicate(Transform):
CONFIG = {
"rate_func" : there_and_back,
"scale_factor" : 1.2,
"color" : YELLOW,
}
def __init__(self, mobject, **kwargs):
digest_config(self, kwargs)
target = mobject.copy()
target.scale_in_place(self.scale_factor)
target.set_color(self.color)
Transform.__init__(self, mobject, target, **kwargs)
class CircleIndicate(Indicate):
CONFIG = {
"rate_func" : squish_rate_func(there_and_back, 0, 0.8),
"remover" : True
}
def __init__(self, mobject, **kwargs):
digest_config(self, kwargs)
circle = Circle(color = self.color, **kwargs)
circle.surround(mobject)
Indicate.__init__(self, circle, **kwargs)
class ShowPassingFlash(ShowPartial):
CONFIG = {
"time_width" : 0.1,
"remover" : True,
}
def get_bounds(self, alpha):
alpha *= (1+self.time_width)
alpha -= self.time_width/2.0
lower = max(0, alpha - self.time_width/2.0)
upper = min(1, alpha + self.time_width/2.0)
return (lower, upper)
def clean_up(self, *args, **kwargs):
ShowPartial.clean_up(self, *args, **kwargs)
for submob, start_submob in self.get_all_families_zipped():
submob.pointwise_become_partial(start_submob, 0, 1)
class ShowCreationThenDestruction(ShowPassingFlash):
CONFIG = {
"time_width" : 2.0,
"run_time" : 1,
}
class ApplyWave(Homotopy):
CONFIG = {
"direction" : DOWN,
"amplitude" : 0.2,
"run_time" : 1,
"apply_function_kwargs" : {
"maintain_smoothness" : False,
},
}
def __init__(self, mobject, **kwargs):
digest_config(self, kwargs, locals())
left_x = mobject.get_left()[0]
right_x = mobject.get_right()[0]
vect = self.amplitude*self.direction
def homotopy(x, y, z, t):
start_point = np.array([x, y, z])
alpha = (x-left_x)/(right_x-left_x)
power = np.exp(2*(alpha-0.5))
nudge = there_and_back(t**power)
return np.array([x, y, z]) + nudge*vect
Homotopy.__init__(self, homotopy, mobject, **kwargs)
class WiggleOutThenIn(Animation):
CONFIG = {
"scale_value" : 1.1,
"rotation_angle" : 0.01*TAU,
"n_wiggles" : 6,
"run_time" : 2,
"scale_about_point" : None,
"rotate_about_point" : None,
}
def __init__(self, mobject, **kwargs):
digest_config(self, kwargs)
if self.scale_about_point is None:
self.scale_about_point = mobject.get_center()
if self.rotate_about_point is None:
self.rotate_about_point = mobject.get_center()
Animation.__init__(self, mobject, **kwargs)
def update_submobject(self, submobject, starting_sumobject, alpha):
submobject.points[:,:] = starting_sumobject.points
submobject.scale(
interpolate(1, self.scale_value, there_and_back(alpha)),
about_point = self.scale_about_point
)
submobject.rotate(
wiggle(alpha, self.n_wiggles)*self.rotation_angle,
about_point = self.rotate_about_point
)
class Vibrate(Animation):
CONFIG = {
"spatial_period" : 6,
"temporal_period" : 1,
"overtones" : 4,
"amplitude" : 0.5,
"radius" : FRAME_X_RADIUS/2,
"run_time" : 3.0,
"rate_func" : None
}
def __init__(self, mobject = None, **kwargs):
if mobject is None:
mobject = Line(3*LEFT, 3*RIGHT)
Animation.__init__(self, mobject, **kwargs)
def wave_function(self, x, t):
return sum([
reduce(op.mul, [
self.amplitude/(k**2), #Amplitude
np.sin(2*np.pi*(k**1.5)*t/self.temporal_period), #Frequency
np.sin(2*np.pi*k*x/self.spatial_period) #Number of waves
])
for k in range(1, self.overtones+1)
])
def update_mobject(self, alpha):
time = alpha*self.run_time
families = map(
Mobject.submobject_family,
[self.mobject, self.starting_mobject]
)
for mob, start in zip(*families):
mob.points = np.apply_along_axis(
lambda (x, y, z) : (x, y + self.wave_function(x, time), z),
1, start.points
)
class TurnInsideOut(Transform):
CONFIG = {
"path_arc" : TAU/4,
}
def __init__(self, mobject, **kwargs):
mobject.sort_points(np.linalg.norm)
mob_copy = mobject.copy()
mob_copy.sort_points(lambda p : -np.linalg.norm(p))
Transform.__init__(self, mobject, mob_copy, **kwargs)

74
animation/movement.py Normal file
View file

@ -0,0 +1,74 @@
from __future__ import absolute_import
from constants import *
import warnings
from animation.animation import Animation
from utils.config_ops import digest_config
class Homotopy(Animation):
CONFIG = {
"run_time" : 3,
"apply_function_kwargs" : {},
}
def __init__(self, homotopy, mobject, **kwargs):
"""
Homotopy a function from (x, y, z, t) to (x', y', z')
"""
def function_at_time_t(t):
return lambda p : homotopy(p[0], p[1], p[2], t)
self.function_at_time_t = function_at_time_t
digest_config(self, kwargs)
Animation.__init__(self, mobject, **kwargs)
def update_submobject(self, submob, start, alpha):
submob.points = start.points
submob.apply_function(
self.function_at_time_t(alpha),
**self.apply_function_kwargs
)
class SmoothedVectorizedHomotopy(Homotopy):
def update_submobject(self, submob, start, alpha):
Homotopy.update_submobject(self, submob, start, alpha)
submob.make_smooth()
class ComplexHomotopy(Homotopy):
def __init__(self, complex_homotopy, mobject, **kwargs):
"""
Complex Hootopy a function Cx[0, 1] to C
"""
def homotopy(event):
x, y, z, t = event
c = complex_homotopy((complex(x, y), t))
return (c.real, c.imag, z)
Homotopy.__init__(self, homotopy, mobject, *args, **kwargs)
class PhaseFlow(Animation):
CONFIG = {
"virtual_time" : 1,
"rate_func" : None,
}
def __init__(self, function, mobject, **kwargs):
digest_config(self, kwargs, locals())
Animation.__init__(self, mobject, **kwargs)
def update_mobject(self, alpha):
if hasattr(self, "last_alpha"):
dt = self.virtual_time*(alpha-self.last_alpha)
self.mobject.apply_function(
lambda p : p + dt*self.function(p)
)
self.last_alpha = alpha
class MoveAlongPath(Animation):
def __init__(self, mobject, path, **kwargs):
digest_config(self, kwargs, locals())
Animation.__init__(self, mobject, **kwargs)
def update_mobject(self, alpha):
point = self.path.point_from_proportion(alpha)
self.mobject.move_to(point)

61
animation/numbers.py Normal file
View file

@ -0,0 +1,61 @@
from __future__ import absolute_import
from constants import *
from animation.animation import Animation
from mobject.numbers import DecimalNumber
from utils.bezier import interpolate
from utils.config_ops import digest_config
class ChangingDecimal(Animation):
CONFIG = {
"num_decimal_points" : None,
"show_ellipsis" : None,
"position_update_func" : None,
"tracked_mobject" : None,
}
def __init__(self, decimal_number_mobject, number_update_func, **kwargs):
digest_config(self, kwargs, locals())
self.decimal_number_config = dict(
decimal_number_mobject.initial_config
)
for attr in "num_decimal_points", "show_ellipsis":
value = getattr(self, attr)
if value is not None:
self.decimal_number_config[attr] = value
if hasattr(self.decimal_number_mobject, "background_rectangle"):
self.decimal_number_config["include_background_rectangle"] = True
if self.tracked_mobject:
dmc = decimal_number_mobject.get_center()
tmc = self.tracked_mobject.get_center()
self.diff_from_tracked_mobject = dmc - tmc
Animation.__init__(self, decimal_number_mobject, **kwargs)
def update_mobject(self, alpha):
self.update_number(alpha)
self.update_position()
def update_number(self, alpha):
decimal = self.decimal_number_mobject
new_number = self.number_update_func(alpha)
new_decimal = DecimalNumber(
new_number, **self.decimal_number_config
)
new_decimal.match_height(decimal)
new_decimal.move_to(decimal)
new_decimal.match_style(decimal)
decimal.submobjects = new_decimal.submobjects
decimal.number = new_number
def update_position(self):
if self.position_update_func is not None:
self.position_update_func(self.decimal_number_mobject)
elif self.tracked_mobject is not None:
self.decimal_number_mobject.move_to(self.tracked_mobject.get_center() + self.diff_from_tracked_mobject)
class ChangeDecimalToValue(ChangingDecimal):
def __init__(self, decimal_number_mobject, target_number, **kwargs):
start_number = decimal_number_mobject.number
func = lambda alpha : interpolate(start_number, target_number, alpha)
ChangingDecimal.__init__(self, decimal_number_mobject, func, **kwargs)

View file

@ -1,62 +0,0 @@
import numpy as np
import operator as op
from .animation import Animation
from transform import Transform
from mobject.mobject import Mobject
from mobject.point_cloud_mobject import Mobject1D
from topics.geometry import Line
from utils.paths import path_along_arc
from constants import *
class Vibrate(Animation):
CONFIG = {
"spatial_period" : 6,
"temporal_period" : 1,
"overtones" : 4,
"amplitude" : 0.5,
"radius" : FRAME_X_RADIUS/2,
"run_time" : 3.0,
"rate_func" : None
}
def __init__(self, mobject = None, **kwargs):
if mobject is None:
mobject = Line(3*LEFT, 3*RIGHT)
Animation.__init__(self, mobject, **kwargs)
def wave_function(self, x, t):
return sum([
reduce(op.mul, [
self.amplitude/(k**2), #Amplitude
np.sin(2*np.pi*(k**1.5)*t/self.temporal_period), #Frequency
np.sin(2*np.pi*k*x/self.spatial_period) #Number of waves
])
for k in range(1, self.overtones+1)
])
def update_mobject(self, alpha):
time = alpha*self.run_time
families = map(
Mobject.submobject_family,
[self.mobject, self.starting_mobject]
)
for mob, start in zip(*families):
mob.points = np.apply_along_axis(
lambda (x, y, z) : (x, y + self.wave_function(x, time), z),
1, start.points
)
class TurnInsideOut(Transform):
CONFIG = {
"path_func" : path_along_arc(np.pi/2)
}
def __init__(self, mobject, **kwargs):
mobject.sort_points(np.linalg.norm)
mob_copy = mobject.copy()
mob_copy.sort_points(lambda p : -np.linalg.norm(p))
Transform.__init__(self, mobject, mob_copy, **kwargs)

61
animation/rotation.py Normal file
View file

@ -0,0 +1,61 @@
from __future__ import absolute_import
import itertools as it
import numpy as np
from constants import *
import warnings
from animation.animation import Animation
from animation.transform import Transform
from utils.config_ops import digest_config
class Rotating(Animation):
CONFIG = {
"axis" : OUT,
"radians" : 2*np.pi,
"run_time" : 5,
"rate_func" : None,
"in_place" : True,
"about_point" : None,
"about_edge" : None,
}
def update_submobject(self, submobject, starting_submobject, alpha):
submobject.points = np.array(starting_submobject.points)
def update_mobject(self, alpha):
Animation.update_mobject(self, alpha)
about_point = None
if self.about_point is not None:
about_point = self.about_point
elif self.in_place: #This is superseeded
self.about_point = self.mobject.get_center()
self.mobject.rotate(
alpha*self.radians,
axis = self.axis,
about_point = self.about_point,
about_edge = self.about_edge,
)
class Rotate(Transform):
CONFIG = {
"in_place" : False,
"about_point" : None,
}
def __init__(self, mobject, angle = np.pi, axis = OUT, **kwargs):
if "path_arc" not in kwargs:
kwargs["path_arc"] = angle
if "path_arc_axis" not in kwargs:
kwargs["path_arc_axis"] = axis
digest_config(self, kwargs, locals())
target = mobject.copy()
if self.in_place:
self.about_point = mobject.get_center()
target.rotate(
angle,
axis = axis,
about_point = self.about_point,
)
Transform.__init__(self, mobject, target, **kwargs)

View file

@ -1,336 +0,0 @@
import numpy as np
import itertools as it
from constants import *
import warnings
from mobject.mobject import Mobject, Group
from mobject.vectorized_mobject import VMobject
from mobject.tex_mobject import TextMobject
from .animation import Animation
from transform import Transform
from utils.bezier import interpolate
from utils.config_ops import digest_config
from utils.rate_functions import smooth, double_smooth, there_and_back, wiggle
class Rotating(Animation):
CONFIG = {
"axis" : OUT,
"radians" : 2*np.pi,
"run_time" : 5,
"rate_func" : None,
"in_place" : True,
"about_point" : None,
"about_edge" : None,
}
def update_submobject(self, submobject, starting_submobject, alpha):
submobject.points = np.array(starting_submobject.points)
def update_mobject(self, alpha):
Animation.update_mobject(self, alpha)
about_point = None
if self.about_point is not None:
about_point = self.about_point
elif self.in_place: #This is superseeded
self.about_point = self.mobject.get_center()
self.mobject.rotate(
alpha*self.radians,
axis = self.axis,
about_point = self.about_point,
about_edge = self.about_edge,
)
class ShowPartial(Animation):
def update_submobject(self, submobject, starting_submobject, alpha):
submobject.pointwise_become_partial(
starting_submobject, *self.get_bounds(alpha)
)
def get_bounds(self, alpha):
raise Exception("Not Implemented")
class ShowCreation(ShowPartial):
CONFIG = {
"submobject_mode" : "one_at_a_time",
}
def get_bounds(self, alpha):
return (0, alpha)
class Uncreate(ShowCreation):
CONFIG = {
"rate_func" : lambda t : smooth(1-t),
"remover" : True
}
class Write(ShowCreation):
CONFIG = {
"rate_func" : None,
"submobject_mode" : "lagged_start",
}
def __init__(self, mob_or_text, **kwargs):
digest_config(self, kwargs)
if isinstance(mob_or_text, str):
mobject = TextMobject(mob_or_text)
else:
mobject = mob_or_text
if "run_time" not in kwargs:
self.establish_run_time(mobject)
if "lag_factor" not in kwargs:
if len(mobject.family_members_with_points()) < 4:
min_lag_factor = 1
else:
min_lag_factor = 2
self.lag_factor = max(self.run_time - 1, min_lag_factor)
ShowCreation.__init__(self, mobject, **kwargs)
def establish_run_time(self, mobject):
num_subs = len(mobject.family_members_with_points())
if num_subs < 15:
self.run_time = 1
else:
self.run_time = 2
class DrawBorderThenFill(Animation):
CONFIG = {
"run_time" : 2,
"stroke_width" : 2,
"stroke_color" : None,
"rate_func" : double_smooth,
}
def __init__(self, vmobject, **kwargs):
if not isinstance(vmobject, VMobject):
raise Exception("DrawBorderThenFill only works for VMobjects")
self.reached_halfway_point_before = False
Animation.__init__(self, vmobject, **kwargs)
def update_submobject(self, submobject, starting_submobject, alpha):
submobject.pointwise_become_partial(
starting_submobject, 0, min(2*alpha, 1)
)
if alpha < 0.5:
if self.stroke_color:
color = self.stroke_color
elif starting_submobject.stroke_width > 0:
color = starting_submobject.get_stroke_color()
else:
color = starting_submobject.get_color()
submobject.set_stroke(color, width = self.stroke_width)
submobject.set_fill(opacity = 0)
else:
if not self.reached_halfway_point_before:
self.reached_halfway_point_before = True
submobject.points = np.array(starting_submobject.points)
width, opacity = [
interpolate(start, end, 2*alpha - 1)
for start, end in [
(self.stroke_width, starting_submobject.get_stroke_width()),
(0, starting_submobject.get_fill_opacity())
]
]
submobject.set_stroke(width = width)
submobject.set_fill(opacity = opacity)
class ShowPassingFlash(ShowPartial):
CONFIG = {
"time_width" : 0.1,
"remover" : True,
}
def get_bounds(self, alpha):
alpha *= (1+self.time_width)
alpha -= self.time_width/2.0
lower = max(0, alpha - self.time_width/2.0)
upper = min(1, alpha + self.time_width/2.0)
return (lower, upper)
def clean_up(self, *args, **kwargs):
ShowPartial.clean_up(self, *args, **kwargs)
for submob, start_submob in self.get_all_families_zipped():
submob.pointwise_become_partial(start_submob, 0, 1)
class ShowCreationThenDestruction(ShowPassingFlash):
CONFIG = {
"time_width" : 2.0,
"run_time" : 1,
}
class Homotopy(Animation):
CONFIG = {
"run_time" : 3,
"apply_function_kwargs" : {},
}
def __init__(self, homotopy, mobject, **kwargs):
"""
Homotopy a function from (x, y, z, t) to (x', y', z')
"""
def function_at_time_t(t):
return lambda p : homotopy(p[0], p[1], p[2], t)
self.function_at_time_t = function_at_time_t
digest_config(self, kwargs)
Animation.__init__(self, mobject, **kwargs)
def update_submobject(self, submob, start, alpha):
submob.points = start.points
submob.apply_function(
self.function_at_time_t(alpha),
**self.apply_function_kwargs
)
class SmoothedVectorizedHomotopy(Homotopy):
def update_submobject(self, submob, start, alpha):
Homotopy.update_submobject(self, submob, start, alpha)
submob.make_smooth()
class ApplyWave(Homotopy):
CONFIG = {
"direction" : DOWN,
"amplitude" : 0.2,
"run_time" : 1,
"apply_function_kwargs" : {
"maintain_smoothness" : False,
},
}
def __init__(self, mobject, **kwargs):
digest_config(self, kwargs, locals())
left_x = mobject.get_left()[0]
right_x = mobject.get_right()[0]
vect = self.amplitude*self.direction
def homotopy(x, y, z, t):
start_point = np.array([x, y, z])
alpha = (x-left_x)/(right_x-left_x)
power = np.exp(2*(alpha-0.5))
nudge = there_and_back(t**power)
return np.array([x, y, z]) + nudge*vect
Homotopy.__init__(self, homotopy, mobject, **kwargs)
class PhaseFlow(Animation):
CONFIG = {
"virtual_time" : 1,
"rate_func" : None,
}
def __init__(self, function, mobject, **kwargs):
digest_config(self, kwargs, locals())
Animation.__init__(self, mobject, **kwargs)
def update_mobject(self, alpha):
if hasattr(self, "last_alpha"):
dt = self.virtual_time*(alpha-self.last_alpha)
self.mobject.apply_function(
lambda p : p + dt*self.function(p)
)
self.last_alpha = alpha
class MoveAlongPath(Animation):
def __init__(self, mobject, path, **kwargs):
digest_config(self, kwargs, locals())
Animation.__init__(self, mobject, **kwargs)
def update_mobject(self, alpha):
point = self.path.point_from_proportion(alpha)
self.mobject.move_to(point)
class UpdateFromFunc(Animation):
"""
update_function of the form func(mobject), presumably
to be used when the state of one mobject is dependent
on another simultaneously animated mobject
"""
def __init__(self, mobject, update_function, **kwargs):
digest_config(self, kwargs, locals())
Animation.__init__(self, mobject, **kwargs)
def update_mobject(self, alpha):
self.update_function(self.mobject)
class UpdateFromAlphaFunc(UpdateFromFunc):
def update_mobject(self, alpha):
self.update_function(self.mobject, alpha)
class MaintainPositionRelativeTo(Animation):
CONFIG = {
"tracked_critical_point" : ORIGIN
}
def __init__(self, mobject, tracked_mobject, **kwargs):
digest_config(self, kwargs, locals())
tcp = self.tracked_critical_point
self.diff = mobject.get_critical_point(tcp) - \
tracked_mobject.get_critical_point(tcp)
Animation.__init__(self, mobject, **kwargs)
def update_mobject(self, alpha):
self.mobject.shift(
self.tracked_mobject.get_critical_point(self.tracked_critical_point) - \
self.mobject.get_critical_point(self.tracked_critical_point) + \
self.diff
)
class WiggleOutThenIn(Animation):
CONFIG = {
"scale_value" : 1.1,
"rotation_angle" : 0.01*TAU,
"n_wiggles" : 6,
"run_time" : 2,
"scale_about_point" : None,
"rotate_about_point" : None,
}
def __init__(self, mobject, **kwargs):
digest_config(self, kwargs)
if self.scale_about_point is None:
self.scale_about_point = mobject.get_center()
if self.rotate_about_point is None:
self.rotate_about_point = mobject.get_center()
Animation.__init__(self, mobject, **kwargs)
def update_submobject(self, submobject, starting_sumobject, alpha):
submobject.points[:,:] = starting_sumobject.points
submobject.scale(
interpolate(1, self.scale_value, there_and_back(alpha)),
about_point = self.scale_about_point
)
submobject.rotate(
wiggle(alpha, self.n_wiggles)*self.rotation_angle,
about_point = self.rotate_about_point
)
class ApplyToCenters(Animation):
def __init__(self, AnimationClass, mobjects, **kwargs):
full_kwargs = AnimationClass.CONFIG
full_kwargs.update(kwargs)
full_kwargs["mobject"] = Mobject(*[
mob.get_point_mobject()
for mob in mobjects
])
self.centers_container = AnimationClass(**full_kwargs)
full_kwargs.pop("mobject")
Animation.__init__(self, Mobject(*mobjects), **full_kwargs)
self.name = str(self) + AnimationClass.__name__
def update_mobject(self, alpha):
self.centers_container.update_mobject(alpha)
center_mobs = self.centers_container.mobject.split()
mobjects = self.mobject.split()
for center_mob, mobject in zip(center_mobs, mobjects):
mobject.shift(
center_mob.get_center()-mobject.get_center()
)
class FadeInAndShiftFromDirection(Transform):
CONFIG = {
"direction" : DOWN,
}
def __init__(self, mobject, **kwargs):
digest_config(self, kwargs)
target = mobject.copy()
mobject.shift(self.direction)
mobject.fade(1)
Transform.__init__(self, mobject, target, **kwargs)
# Essentially just a more convenient name for the above animation
class FadeInFromDown(FadeInAndShiftFromDirection):
CONFIG = {
"direction" : DOWN,
}

67
animation/specialized.py Normal file
View file

@ -0,0 +1,67 @@
from __future__ import absolute_import
import numpy as np
from constants import *
from animation.transform import ApplyMethod
from animation.composition import LaggedStart
from mobject.svg.drawings import Car
from mobject.types.vectorized_mobject import VGroup
from mobject.geometry import Circle
from utils.config_ops import digest_config
class MoveCar(ApplyMethod):
CONFIG = {
"moving_forward" : True,
}
def __init__(self, car, target_point, **kwargs):
assert isinstance(car, Car)
ApplyMethod.__init__(self, car.move_to, target_point, **kwargs)
displacement = self.target_mobject.get_right()-self.starting_mobject.get_right()
distance = np.linalg.norm(displacement)
if not self.moving_forward:
distance *= -1
tire_radius = car.get_tires()[0].get_width()/2
self.total_tire_radians = -distance/tire_radius
def update_mobject(self, alpha):
ApplyMethod.update_mobject(self, alpha)
if alpha == 0:
return
radians = alpha*self.total_tire_radians
for tire in self.mobject.get_tires():
tire.rotate_in_place(radians)
class Broadcast(LaggedStart):
CONFIG = {
"small_radius" : 0.0,
"big_radius" : 5,
"n_circles" : 5,
"start_stroke_width" : 8,
"color" : WHITE,
"remover" : True,
"lag_ratio" : 0.7,
"run_time" : 3,
"remover" : True,
}
def __init__(self, focal_point, **kwargs):
digest_config(self, kwargs)
circles = VGroup()
for x in range(self.n_circles):
circle = Circle(
radius = self.big_radius,
stroke_color = BLACK,
stroke_width = 0,
)
circle.move_to(focal_point)
circle.save_state()
circle.scale_to_fit_width(self.small_radius*2)
circle.set_stroke(self.color, self.start_stroke_width)
circles.add(circle)
LaggedStart.__init__(
self, ApplyMethod, circles,
lambda c : (c.restore,),
**kwargs
)

View file

@ -1,20 +1,21 @@
import numpy as np
import itertools as it
from __future__ import absolute_import
import inspect
import copy
import warnings
import numpy as np
from constants import *
from .animation import Animation
from mobject.mobject import Mobject, Group
from mobject.vectorized_mobject import VMobject, VectorizedPoint
from topics.geometry import Dot, Circle
from animation.animation import Animation
from mobject.mobject import Group
from mobject.mobject import Mobject
from utils.config_ops import digest_config
from utils.iterables import adjacent_pairs
from utils.paths import straight_path, path_along_arc, counterclockwise_path
from utils.rate_functions import smooth, there_and_back
from utils.paths import path_along_arc
from utils.paths import straight_path
from utils.config_ops import instantiate
from utils.rate_functions import smooth
from utils.rate_functions import squish_rate_func
from utils.space_ops import complex_to_R3
class Transform(Animation):
CONFIG = {
@ -74,7 +75,6 @@ class ReplacementTransform(Transform):
"replace_mobject_with_target_in_scene" : True,
}
class ClockwiseTransform(Transform):
CONFIG = {
"path_arc" : -np.pi
@ -91,54 +91,6 @@ class MoveToTarget(Transform):
raise Exception("MoveToTarget called on mobject without attribute 'target' ")
Transform.__init__(self, mobject, mobject.target, **kwargs)
class CyclicReplace(Transform):
CONFIG = {
"path_arc" : np.pi/2
}
def __init__(self, *mobjects, **kwargs):
start = Group(*mobjects)
target = Group(*[
m1.copy().move_to(m2)
for m1, m2 in adjacent_pairs(start)
])
Transform.__init__(self, start, target, **kwargs)
class Swap(CyclicReplace):
pass #Renaming, more understandable for two entries
class GrowFromPoint(Transform):
CONFIG = {
"point_color" : None,
}
def __init__(self, mobject, point, **kwargs):
digest_config(self, kwargs)
target = mobject.copy()
point_mob = VectorizedPoint(point)
if self.point_color:
point_mob.set_color(self.point_color)
mobject.replace(point_mob)
mobject.set_color(point_mob.get_color())
Transform.__init__(self, mobject, target, **kwargs)
class GrowFromCenter(GrowFromPoint):
def __init__(self, mobject, **kwargs):
GrowFromPoint.__init__(self, mobject, mobject.get_center(), **kwargs)
class GrowArrow(GrowFromPoint):
def __init__(self, arrow, **kwargs):
GrowFromPoint.__init__(self, arrow, arrow.get_start(), **kwargs)
class SpinInFromNothing(GrowFromCenter):
CONFIG = {
"path_func" : counterclockwise_path()
}
class ShrinkToCenter(Transform):
def __init__(self, mobject, **kwargs):
Transform.__init__(
self, mobject, mobject.get_point_mobject(), **kwargs
)
class ApplyMethod(Transform):
CONFIG = {
"submobject_mode" : "all_at_once"
@ -167,94 +119,6 @@ class ApplyMethod(Transform):
method.im_func(target, *args, **method_kwargs)
Transform.__init__(self, method.im_self, target, **kwargs)
class FadeOut(Transform):
CONFIG = {
"remover" : True,
}
def __init__(self, mobject, **kwargs):
target = mobject.copy()
target.fade(1)
Transform.__init__(self, mobject, target, **kwargs)
def clean_up(self, surrounding_scene = None):
Transform.clean_up(self, surrounding_scene)
self.update(0)
class FadeIn(Transform):
def __init__(self, mobject, **kwargs):
target = mobject.copy()
Transform.__init__(self, mobject, target, **kwargs)
self.starting_mobject.fade(1)
if isinstance(self.starting_mobject, VMobject):
self.starting_mobject.set_stroke(width = 0)
self.starting_mobject.set_fill(opacity = 0)
class FocusOn(Transform):
CONFIG = {
"opacity" : 0.2,
"color" : GREY,
"run_time" : 2,
"remover" : True,
}
def __init__(self, mobject_or_point, **kwargs):
digest_config(self, kwargs)
big_dot = Dot(
radius = FRAME_X_RADIUS+FRAME_Y_RADIUS,
stroke_width = 0,
fill_color = self.color,
fill_opacity = 0,
)
little_dot = Dot(radius = 0)
little_dot.set_fill(self.color, opacity = self.opacity)
little_dot.move_to(mobject_or_point)
Transform.__init__(self, big_dot, little_dot, **kwargs)
class Indicate(Transform):
CONFIG = {
"rate_func" : there_and_back,
"scale_factor" : 1.2,
"color" : YELLOW,
}
def __init__(self, mobject, **kwargs):
digest_config(self, kwargs)
target = mobject.copy()
target.scale_in_place(self.scale_factor)
target.set_color(self.color)
Transform.__init__(self, mobject, target, **kwargs)
class CircleIndicate(Indicate):
CONFIG = {
"rate_func" : squish_rate_func(there_and_back, 0, 0.8),
"remover" : True
}
def __init__(self, mobject, **kwargs):
digest_config(self, kwargs)
circle = Circle(color = self.color, **kwargs)
circle.surround(mobject)
Indicate.__init__(self, circle, **kwargs)
class Rotate(ApplyMethod):
CONFIG = {
"in_place" : False,
"about_point" : None,
}
def __init__(self, mobject, angle = np.pi, axis = OUT, **kwargs):
if "path_arc" not in kwargs:
kwargs["path_arc"] = angle
if "path_arc_axis" not in kwargs:
kwargs["path_arc_axis"] = axis
digest_config(self, kwargs, locals())
target = mobject.copy()
if self.in_place:
self.about_point = mobject.get_center()
target.rotate(
angle,
axis = axis,
about_point = self.about_point,
)
Transform.__init__(self, mobject, target, **kwargs)
class ApplyPointwiseFunction(ApplyMethod):
CONFIG = {
"run_time" : DEFAULT_POINTWISE_FUNCTION_RUN_TIME
@ -300,7 +164,37 @@ class ApplyMatrix(ApplyPointwiseFunction):
return np.dot(p, transpose)
ApplyPointwiseFunction.__init__(self, func, mobject, **kwargs)
class ComplexFunction(ApplyPointwiseFunction):
def __init__(self, function, mobject, **kwargs):
if "path_func" not in kwargs:
self.path_func = path_along_arc(
np.log(function(complex(1))).imag
)
ApplyPointwiseFunction.__init__(
self,
lambda (x, y, z) : complex_to_R3(function(complex(x, y))),
instantiate(mobject),
**kwargs
)
###
class CyclicReplace(Transform):
CONFIG = {
"path_arc" : np.pi/2
}
def __init__(self, *mobjects, **kwargs):
start = Group(*mobjects)
target = Group(*[
m1.copy().move_to(m2)
for m1, m2 in adjacent_pairs(start)
])
Transform.__init__(self, start, target, **kwargs)
class Swap(CyclicReplace):
pass #Renaming, more understandable for two entries
#TODO: Um...does this work
class TransformAnimations(Transform):
CONFIG = {
"rate_func" : squish_rate_func(smooth)
@ -330,5 +224,3 @@ class TransformAnimations(Transform):
self.end_anim.update(alpha)
Transform.update(self, alpha)

47
animation/update.py Normal file
View file

@ -0,0 +1,47 @@
from __future__ import absolute_import
from constants import *
from animation.animation import Animation
from utils.config_ops import digest_config
class UpdateFromFunc(Animation):
"""
update_function of the form func(mobject), presumably
to be used when the state of one mobject is dependent
on another simultaneously animated mobject
"""
def __init__(self, mobject, update_function, **kwargs):
digest_config(self, kwargs, locals())
Animation.__init__(self, mobject, **kwargs)
def update_mobject(self, alpha):
self.update_function(self.mobject)
class UpdateFromAlphaFunc(UpdateFromFunc):
def update_mobject(self, alpha):
self.update_function(self.mobject, alpha)
class MaintainPositionRelativeTo(Animation):
CONFIG = {
"tracked_critical_point" : ORIGIN
}
def __init__(self, mobject, tracked_mobject, **kwargs):
digest_config(self, kwargs, locals())
tcp = self.tracked_critical_point
self.diff = mobject.get_critical_point(tcp) - \
tracked_mobject.get_critical_point(tcp)
Animation.__init__(self, mobject, **kwargs)
def update_mobject(self, alpha):
self.mobject.shift(
self.tracked_mobject.get_critical_point(self.tracked_critical_point) - \
self.mobject.get_critical_point(self.tracked_critical_point) + \
self.diff
)

View file

@ -16,47 +16,66 @@ as a convenience for scripts createing scenes for videos
from constants import *
from animation.animation import *
from animation.compositions import *
from animation.continual_animation import *
from animation.playground import *
from animation.simple_animations import *
from animation.composition import *
from animation.creation import *
from animation.indication import *
from animation.movement import *
from animation.numbers import *
from animation.rotation import *
from animation.specialized import *
from animation.transform import *
from animation.update import *
from camera.camera import *
from camera.mapping_camera import *
from camera.moving_camera import *
from camera.three_d_camera import *
from mobject.image_mobject import *
from continual_animation.continual_animation import *
from continual_animation.from_animation import *
from continual_animation.numbers import *
from continual_animation.update import *
from mobject.frame import *
from mobject.functions import *
from mobject.geometry import *
from mobject.matrix import *
from mobject.mobject import *
from mobject.point_cloud_mobject import *
from mobject.svg_mobject import *
from mobject.tex_mobject import *
from mobject.vectorized_mobject import *
from mobject.number_line import *
from mobject.numbers import *
from mobject.probability import *
from mobject.shape_matchers import *
from mobject.svg.brace import *
from mobject.svg.svg_mobject import *
from mobject.svg.tex_mobject import *
from mobject.three_dimensions import *
from mobject.types.image_mobject import *
from mobject.types.point_cloud_mobject import *
from mobject.types.vectorized_mobject import *
from mobject.value_tracker import *
from for_3b1b_videos.common_scenes import *
from for_3b1b_videos.pi_creature import *
from for_3b1b_videos.pi_creature_animations import *
from for_3b1b_videos.pi_creature_scene import *
from scene.graph_scene import *
from scene.moving_camera_scene import *
from scene.reconfigurable_scene import *
from scene.scene import *
from scene.scene_from_video import *
from scene.tk_scene import *
from scene.three_d_scene import *
from scene.vector_space_scene 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 *
from topics.counting import *
from topics.fractals import *
from topics.functions import *
from topics.geometry import *
from topics.graph_scene import *
from topics.graph_theory import *
from topics.light import *
from topics.matrix import *
from topics.number_line import *
from topics.numerals import *
from topics.objects import *
from topics.probability import *
from topics.three_dimensions import *
from topics.vector_space_scene import *
from once_useful_constructs.arithmetic import *
from once_useful_constructs.combinatorics import *
from once_useful_constructs.complex_transformation_scene import *
from once_useful_constructs.counting import *
from once_useful_constructs.fractals import *
from once_useful_constructs.graph_theory import *
from once_useful_constructs.light import *
from utils.bezier import *
from utils.color import *
@ -70,18 +89,17 @@ from utils.sounds import *
from utils.space_ops import *
from utils.strings import *
from special_animations import *
# Non manim libraries that are also nice to have without thinking
import numpy as np
import itertools as it
import operator as op
import random
import inspect
import string
import re
import itertools as it
import numpy as np
import operator as op
import os
import random
import re
import string
import sys
from PIL import Image
from colour import Color

View file

@ -1,25 +1,27 @@
import numpy as np
import itertools as it
import numpy as np
import os
from PIL import Image
from colour import Color
import aggdraw
import copy
import time
from constants import *
from mobject.mobject import Mobject, Group
from mobject.point_cloud_mobject import PMobject
from mobject.vectorized_mobject import VMobject
from mobject.image_mobject import ImageMobject
from utils.color import rgb_to_hex, color_to_int_rgba
from utils.config_ops import digest_config, digest_locals, DictAsObject
from utils.images import get_full_raster_image_path
from utils.iterables import remove_list_redundancies, list_difference_update
from utils.iterables import batch_by_property
from utils.simple_functions import fdiv
from PIL import Image
from colour import Color
from constants import *
from mobject.types.image_mobject import ImageMobject
from mobject.mobject import Mobject
from mobject.types.point_cloud_mobject import PMobject
from mobject.types.vectorized_mobject import VMobject
from utils.color import color_to_int_rgba
from utils.color import rgb_to_hex
from utils.config_ops import digest_config
from utils.images import get_full_raster_image_path
from utils.iterables import batch_by_property
from utils.iterables import list_difference_update
from utils.iterables import remove_list_redundancies
from utils.simple_functions import fdiv
class Camera(object):
CONFIG = {
@ -607,128 +609,3 @@ class BackgroundColoredVMobjectDisplayer(object):
self.reset_canvas()
return curr_array
class MovingCamera(Camera):
"""
Stays in line with the height, width and position
of a given mobject
"""
CONFIG = {
"aligned_dimension" : "width" #or height
}
def __init__(self, mobject, **kwargs):
digest_locals(self)
Camera.__init__(self, **kwargs)
def capture_mobjects(self, *args, **kwargs):
self.space_center = self.mobject.get_center()
self.realign_frame_shape()
Camera.capture_mobjects(self, *args, **kwargs)
def realign_frame_shape(self):
height, width = self.frame_shape
if self.aligned_dimension == "height":
self.frame_shape = (self.mobject.get_height(), width)
else:
self.frame_shape = (height, self.mobject.get_width())
self.resize_frame_shape(0 if self.aligned_dimension == "height" else 1)
# TODO: Add an attribute to mobjects under which they can specify that they should just
# map their centers but remain otherwise undistorted (useful for labels, etc.)
class MappingCamera(Camera):
CONFIG = {
"mapping_func" : lambda p : p,
"min_anchor_points" : 50,
"allow_object_intrusion" : False
}
def points_to_pixel_coords(self, points):
return Camera.points_to_pixel_coords(self, np.apply_along_axis(self.mapping_func, 1, points))
def capture_mobjects(self, mobjects, **kwargs):
mobjects = self.get_mobjects_to_display(mobjects, **kwargs)
if self.allow_object_intrusion:
mobject_copies = mobjects
else:
mobject_copies = [mobject.copy() for mobject in mobjects]
for mobject in mobject_copies:
if isinstance(mobject, VMobject) and \
0 < mobject.get_num_anchor_points() < self.min_anchor_points:
mobject.insert_n_anchor_points(self.min_anchor_points)
Camera.capture_mobjects(
self, mobject_copies,
include_submobjects = False,
excluded_mobjects = None,
)
# Note: This allows layering of multiple cameras onto the same portion of the pixel array,
# the later cameras overwriting the former
#
# TODO: Add optional separator borders between cameras (or perhaps peel this off into a
# CameraPlusOverlay class)
class MultiCamera(Camera):
def __init__(self, *cameras_with_start_positions, **kwargs):
self.shifted_cameras = [
DictAsObject(
{
"camera" : camera_with_start_positions[0],
"start_x" : camera_with_start_positions[1][1],
"start_y" : camera_with_start_positions[1][0],
"end_x" : camera_with_start_positions[1][1] + camera_with_start_positions[0].pixel_shape[1],
"end_y" : camera_with_start_positions[1][0] + camera_with_start_positions[0].pixel_shape[0],
})
for camera_with_start_positions in cameras_with_start_positions
]
Camera.__init__(self, **kwargs)
def capture_mobjects(self, mobjects, **kwargs):
for shifted_camera in self.shifted_cameras:
shifted_camera.camera.capture_mobjects(mobjects, **kwargs)
self.pixel_array[
shifted_camera.start_y:shifted_camera.end_y,
shifted_camera.start_x:shifted_camera.end_x] \
= shifted_camera.camera.pixel_array
def set_background(self, pixel_array, **kwargs):
for shifted_camera in self.shifted_cameras:
shifted_camera.camera.set_background(
pixel_array[
shifted_camera.start_y:shifted_camera.end_y,
shifted_camera.start_x:shifted_camera.end_x],
**kwargs
)
def set_pixel_array(self, pixel_array, **kwargs):
Camera.set_pixel_array(self, pixel_array, **kwargs)
for shifted_camera in self.shifted_cameras:
shifted_camera.camera.set_pixel_array(
pixel_array[
shifted_camera.start_y:shifted_camera.end_y,
shifted_camera.start_x:shifted_camera.end_x],
**kwargs
)
def init_background(self):
Camera.init_background(self)
for shifted_camera in self.shifted_cameras:
shifted_camera.camera.init_background()
# A MultiCamera which, when called with two full-size cameras, initializes itself
# as a splitscreen, also taking care to resize each individual camera within it
class SplitScreenCamera(MultiCamera):
def __init__(self, left_camera, right_camera, **kwargs):
digest_config(self, kwargs)
self.left_camera = left_camera
self.right_camera = right_camera
half_width = self.pixel_shape[1] / 2
for camera in [self.left_camera, self.right_camera]:
camera.pixel_shape = (self.pixel_shape[0], half_width) # TODO: Round up on one if width is odd
camera.init_background()
camera.resize_frame_shape()
camera.reset()
MultiCamera.__init__(self, (left_camera, (0, 0)), (right_camera, (0, half_width)))

104
camera/mapping_camera.py Normal file
View file

@ -0,0 +1,104 @@
from __future__ import absolute_import
from camera.camera import Camera
from utils.config_ops import DictAsObject
from utils.config_ops import digest_config
# TODO: Add an attribute to mobjects under which they can specify that they should just
# map their centers but remain otherwise undistorted (useful for labels, etc.)
class MappingCamera(Camera):
CONFIG = {
"mapping_func" : lambda p : p,
"min_anchor_points" : 50,
"allow_object_intrusion" : False
}
def points_to_pixel_coords(self, points):
return Camera.points_to_pixel_coords(self, np.apply_along_axis(self.mapping_func, 1, points))
def capture_mobjects(self, mobjects, **kwargs):
mobjects = self.get_mobjects_to_display(mobjects, **kwargs)
if self.allow_object_intrusion:
mobject_copies = mobjects
else:
mobject_copies = [mobject.copy() for mobject in mobjects]
for mobject in mobject_copies:
if isinstance(mobject, VMobject) and \
0 < mobject.get_num_anchor_points() < self.min_anchor_points:
mobject.insert_n_anchor_points(self.min_anchor_points)
Camera.capture_mobjects(
self, mobject_copies,
include_submobjects = False,
excluded_mobjects = None,
)
# Note: This allows layering of multiple cameras onto the same portion of the pixel array,
# the later cameras overwriting the former
#
# TODO: Add optional separator borders between cameras (or perhaps peel this off into a
# CameraPlusOverlay class)
class MultiCamera(Camera):
def __init__(self, *cameras_with_start_positions, **kwargs):
self.shifted_cameras = [
DictAsObject(
{
"camera" : camera_with_start_positions[0],
"start_x" : camera_with_start_positions[1][1],
"start_y" : camera_with_start_positions[1][0],
"end_x" : camera_with_start_positions[1][1] + camera_with_start_positions[0].pixel_shape[1],
"end_y" : camera_with_start_positions[1][0] + camera_with_start_positions[0].pixel_shape[0],
})
for camera_with_start_positions in cameras_with_start_positions
]
Camera.__init__(self, **kwargs)
def capture_mobjects(self, mobjects, **kwargs):
for shifted_camera in self.shifted_cameras:
shifted_camera.camera.capture_mobjects(mobjects, **kwargs)
self.pixel_array[
shifted_camera.start_y:shifted_camera.end_y,
shifted_camera.start_x:shifted_camera.end_x] \
= shifted_camera.camera.pixel_array
def set_background(self, pixel_array, **kwargs):
for shifted_camera in self.shifted_cameras:
shifted_camera.camera.set_background(
pixel_array[
shifted_camera.start_y:shifted_camera.end_y,
shifted_camera.start_x:shifted_camera.end_x],
**kwargs
)
def set_pixel_array(self, pixel_array, **kwargs):
Camera.set_pixel_array(self, pixel_array, **kwargs)
for shifted_camera in self.shifted_cameras:
shifted_camera.camera.set_pixel_array(
pixel_array[
shifted_camera.start_y:shifted_camera.end_y,
shifted_camera.start_x:shifted_camera.end_x],
**kwargs
)
def init_background(self):
Camera.init_background(self)
for shifted_camera in self.shifted_cameras:
shifted_camera.camera.init_background()
# A MultiCamera which, when called with two full-size cameras, initializes itself
# as a splitscreen, also taking care to resize each individual camera within it
class SplitScreenCamera(MultiCamera):
def __init__(self, left_camera, right_camera, **kwargs):
digest_config(self, kwargs)
self.left_camera = left_camera
self.right_camera = right_camera
half_width = self.pixel_shape[1] / 2
for camera in [self.left_camera, self.right_camera]:
camera.pixel_shape = (self.pixel_shape[0], half_width) # TODO: Round up on one if width is odd
camera.init_background()
camera.resize_frame_shape()
camera.reset()
MultiCamera.__init__(self, (left_camera, (0, 0)), (right_camera, (0, half_width)))

32
camera/moving_camera.py Normal file
View file

@ -0,0 +1,32 @@
from __future__ import absolute_import
from camera.camera import Camera
class MovingCamera(Camera):
"""
Stays in line with the height, width and position
of a given mobject
"""
CONFIG = {
"aligned_dimension" : "width" #or height
}
def __init__(self, frame, **kwargs):
"""
frame is a Mobject, (should be a rectangle) determining
which region of space the camera displys
"""
self.frame = frame
Camera.__init__(self, **kwargs)
def capture_mobjects(self, *args, **kwargs):
self.space_center = self.frame.get_center()
self.realign_frame_shape()
Camera.capture_mobjects(self, *args, **kwargs)
def realign_frame_shape(self):
height, width = self.frame_shape
if self.aligned_dimension == "height":
self.frame_shape = (self.frame.get_height(), width)
else:
self.frame_shape = (height, self.frame.get_width())
self.resize_frame_shape(0 if self.aligned_dimension == "height" else 1)

View file

@ -1,17 +1,16 @@
from __future__ import absolute_import
import numpy as np
from constants import *
from mobject.vectorized_mobject import VGroup, VMobject, VectorizedPoint
from topics.geometry import Square, Line
from scene.scene import Scene
from camera.camera import Camera
from animation.continual_animation import AmbientMovement
from animation.transform import ApplyMethod
from mobject.types.vectorized_mobject import VectorizedPoint
from mobject.three_dimensions import should_shade_in_3d
from utils.bezier import interpolate
from utils.iterables import list_update
from utils.space_ops import rotation_matrix, rotation_about_z, z_to_vector
from utils.space_ops import rotation_about_z
from utils.space_ops import rotation_matrix
class CameraWithPerspective(Camera):
CONFIG = {
@ -42,6 +41,7 @@ class ThreeDCamera(CameraWithPerspective):
Camera.__init__(self, *args, **kwargs)
self.unit_sun_vect = self.sun_vect/np.linalg.norm(self.sun_vect)
## rotation_mobject lives in the phi-theta-distance space
## TODO, use ValueTracker for this instead
self.rotation_mobject = VectorizedPoint()
## moving_center lives in the x-y-z space
## It representes the center of rotation
@ -167,128 +167,3 @@ class ThreeDCamera(CameraWithPerspective):
self.space_center = self.moving_center.points[0]
return Camera.points_to_pixel_coords(self, new_points)
class ThreeDScene(Scene):
CONFIG = {
"camera_class" : ThreeDCamera,
"ambient_camera_rotation" : None,
}
def set_camera_position(self, phi = None, theta = None, distance = None,
center_x = None, center_y = None, center_z = None):
self.camera.set_position(phi, theta, distance, center_x, center_y, center_z)
def begin_ambient_camera_rotation(self, rate = 0.01):
self.ambient_camera_rotation = AmbientMovement(
self.camera.rotation_mobject,
direction = UP,
rate = rate
)
self.add(self.ambient_camera_rotation)
def stop_ambient_camera_rotation(self):
if self.ambient_camera_rotation is not None:
self.remove(self.ambient_camera_rotation)
self.ambient_camera_rotation = None
def move_camera(
self,
phi = None, theta = None, distance = None,
center_x = None, center_y = None, center_z = None,
added_anims = [],
**kwargs
):
target_point = self.camera.get_spherical_coords(phi, theta, distance)
movement = ApplyMethod(
self.camera.rotation_mobject.move_to,
target_point,
**kwargs
)
target_center = self.camera.get_center_of_rotation(center_x, center_y, center_z)
movement_center = ApplyMethod(
self.camera.moving_center.move_to,
target_center,
**kwargs
)
is_camera_rotating = self.ambient_camera_rotation in self.continual_animations
if is_camera_rotating:
self.remove(self.ambient_camera_rotation)
self.play(movement, movement_center, *added_anims)
target_point = self.camera.get_spherical_coords(phi, theta, distance)
if is_camera_rotating:
self.add(self.ambient_camera_rotation)
def get_moving_mobjects(self, *animations):
moving_mobjects = Scene.get_moving_mobjects(self, *animations)
if self.camera.rotation_mobject in moving_mobjects:
return list_update(self.mobjects, moving_mobjects)
return moving_mobjects
##############
def should_shade_in_3d(mobject):
return hasattr(mobject, "shade_in_3d") and mobject.shade_in_3d
def shade_in_3d(mobject):
for submob in mobject.submobject_family():
submob.shade_in_3d = True
def turn_off_3d_shading(mobject):
for submob in mobject.submobject_family():
submob.shade_in_3d = False
class ThreeDMobject(VMobject):
def __init__(self, *args, **kwargs):
VMobject.__init__(self, *args, **kwargs)
shade_in_3d(self)
class Cube(ThreeDMobject):
CONFIG = {
"fill_opacity" : 0.75,
"fill_color" : BLUE,
"stroke_width" : 0,
"propagate_style_to_family" : True,
"side_length" : 2,
}
def generate_points(self):
for vect in IN, OUT, LEFT, RIGHT, UP, DOWN:
face = Square(side_length = self.side_length)
face.shift(self.side_length*OUT/2.0)
face.apply_function(lambda p : np.dot(p, z_to_vector(vect).T))
self.add(face)
class Prism(Cube):
CONFIG = {
"dimensions" : [3, 2, 1]
}
def generate_points(self):
Cube.generate_points(self)
for dim, value in enumerate(self.dimensions):
self.rescale_to_fit(value, dim, stretch = True)

View file

View file

@ -1,9 +1,10 @@
from constants import *
from mobject.mobject import Mobject, Group
from simple_animations import MaintainPositionRelativeTo
import copy
from utils.config_ops import instantiate
from constants import *
from mobject.mobject import Group
from mobject.mobject import Mobject
from utils.config_ops import digest_config
from utils.config_ops import instantiate
class ContinualAnimation(object):
CONFIG = {
@ -66,7 +67,7 @@ class ContinualAnimationGroup(ContinualAnimation):
for continual_animation in self.continual_animations:
continual_animation.update(dt)
class AmbientRotation(ContinualAnimation):
class ContinualRotation(ContinualAnimation):
CONFIG = {
"axis" : OUT,
"rate" : np.pi/12, #Radians per second
@ -86,7 +87,7 @@ class AmbientRotation(ContinualAnimation):
about_point = about_point
)
class AmbientMovement(ContinualAnimation):
class ContinualMovement(ContinualAnimation):
CONFIG = {
"direction" : RIGHT,
"rate" : 0.05, #Units per second
@ -95,59 +96,6 @@ class AmbientMovement(ContinualAnimation):
def update_mobject(self, dt):
self.mobject.shift(dt*self.rate*self.direction)
class ContinualUpdateFromFunc(ContinualAnimation):
CONFIG = {
"function_depends_on_dt" : False
}
def __init__(self, mobject, func, **kwargs):
self.func = func
ContinualAnimation.__init__(self, mobject, **kwargs)
def update_mobject(self, dt):
if self.function_depends_on_dt:
self.func(self.mobject, dt)
else:
self.func(self.mobject)
class ContinualUpdateFromTimeFunc(ContinualUpdateFromFunc):
CONFIG = {
"function_depends_on_dt" : True
}
class ContinualMaintainPositionRelativeTo(ContinualAnimation):
# TODO: Possibly reimplement using CycleAnimation?
def __init__(self, mobject, tracked_mobject, **kwargs):
self.anim = MaintainPositionRelativeTo(mobject, tracked_mobject, **kwargs)
ContinualAnimation.__init__(self, mobject, **kwargs)
def update_mobject(self, dt):
self.anim.update(0) # 0 is arbitrary
class NormalAnimationAsContinualAnimation(ContinualAnimation):
CONFIG = {
"start_up_time" : 0,
"wind_down_time" : 0,
}
def __init__(self, animation, **kwargs):
self.animation = animation
ContinualAnimation.__init__(self, animation.mobject, **kwargs)
def update_mobject(self, dt):
self.animation.update(
min(float(self.internal_time)/self.animation.run_time, 1)
)
class CycleAnimation(ContinualAnimation):
def __init__(self, animation, **kwargs):
self.animation = animation
ContinualAnimation.__init__(self, animation.mobject, **kwargs)
def update_mobject(self, dt):
mod_value = self.internal_time % self.animation.run_time
alpha = mod_value/float(self.animation.run_time)
self.animation.update(alpha)

View file

@ -0,0 +1,28 @@
from __future__ import absolute_import
from continual_animation.continual_animation import ContinualAnimation
class NormalAnimationAsContinualAnimation(ContinualAnimation):
CONFIG = {
"start_up_time" : 0,
"wind_down_time" : 0,
}
def __init__(self, animation, **kwargs):
self.animation = animation
ContinualAnimation.__init__(self, animation.mobject, **kwargs)
def update_mobject(self, dt):
self.animation.update(
min(float(self.internal_time)/self.animation.run_time, 1)
)
class CycleAnimation(ContinualAnimation):
def __init__(self, animation, **kwargs):
self.animation = animation
ContinualAnimation.__init__(self, animation.mobject, **kwargs)
def update_mobject(self, dt):
mod_value = self.internal_time % self.animation.run_time
alpha = mod_value/float(self.animation.run_time)
self.animation.update(alpha)

View file

@ -0,0 +1,10 @@
from __future__ import absolute_import
from continual_animation.from_animation import NormalAnimationAsContinualAnimation
from animation.numbers import ChangingDecimal
class ContinualChangingDecimal(NormalAnimationAsContinualAnimation):
def __init__(self, *args, **kwargs):
NormalAnimationAsContinualAnimation.__init__(
self, ChangingDecimal(*args, **kwargs)
)

View file

@ -0,0 +1,33 @@
from __future__ import absolute_import
from continual_animation.continual_animation import ContinualAnimation
from animation.update import MaintainPositionRelativeTo
class ContinualUpdateFromFunc(ContinualAnimation):
CONFIG = {
"function_depends_on_dt" : False
}
def __init__(self, mobject, func, **kwargs):
self.func = func
ContinualAnimation.__init__(self, mobject, **kwargs)
def update_mobject(self, dt):
if self.function_depends_on_dt:
self.func(self.mobject, dt)
else:
self.func(self.mobject)
class ContinualUpdateFromTimeFunc(ContinualUpdateFromFunc):
CONFIG = {
"function_depends_on_dt" : True
}
class ContinualMaintainPositionRelativeTo(ContinualAnimation):
# TODO: Possibly reimplement using CycleAnimation?
def __init__(self, mobject, tracked_mobject, **kwargs):
self.anim = MaintainPositionRelativeTo(mobject, tracked_mobject, **kwargs)
ContinualAnimation.__init__(self, mobject, **kwargs)
def update_mobject(self, dt):
self.anim.update(0) # 0 is arbitrary

View file

@ -4,17 +4,18 @@ import sys
# import getopt
import argparse
import imp
import itertools as it
import inspect
import traceback
import imp
import inspect
import itertools as it
import os
import subprocess as sp
import traceback
from camera.camera import Camera
from constants import *
from scene.scene import Scene
from camera.camera import Camera
from utils.sounds import play_error_sound, play_finish_sound
from utils.sounds import play_error_sound
from utils.sounds import play_finish_sound
HELP_MESSAGE = """
Usage:
@ -215,7 +216,6 @@ def get_module(file_name):
return get_module_windows(file_name)
return get_module_posix(file_name)
def main():
config = get_configuration()
module = get_module(config["file"])

View file

View file

@ -1,17 +1,27 @@
from __future__ import absolute_import
import random
from constants import *
from scene.scene import Scene
from animation.animation import Animation
from animation.simple_animations import Write, DrawBorderThenFill
from animation.compositions import LaggedStart
from animation.transform import FadeIn, FadeOut, ApplyMethod
from mobject.vectorized_mobject import VGroup
from mobject.tex_mobject import TexMobject, TextMobject
from topics.characters import Mortimer, Randolph, Blink
from topics.objects import PatreonLogo
from topics.geometry import Square, Rectangle, DashedLine
from animation.composition import LaggedStart
from animation.creation import DrawBorderThenFill
from animation.creation import Write
from animation.transform import ApplyMethod
from animation.creation import FadeIn
from animation.creation import FadeOut
from mobject.svg.tex_mobject import TexMobject
from mobject.svg.tex_mobject import TextMobject
from mobject.types.vectorized_mobject import VGroup
from scene.scene import Scene
from for_3b1b_videos.pi_creature_animations import Blink
from for_3b1b_videos.pi_creature import Mortimer
from for_3b1b_videos.pi_creature import Randolph
from mobject.geometry import DashedLine
from mobject.geometry import Rectangle
from mobject.geometry import Square
from mobject.svg.drawings import PatreonLogo
class OpeningQuote(Scene):
CONFIG = {
@ -202,17 +212,3 @@ class TODOStub(Scene):
self.add(TextMobject("TODO: %s"%self.message))
self.wait()

View file

@ -0,0 +1,316 @@
import numpy as np
from constants import *
from mobject.mobject import Mobject
from mobject.svg.svg_mobject import SVGMobject
from mobject.svg.tex_mobject import TextMobject
from mobject.types.vectorized_mobject import VGroup
from mobject.types.vectorized_mobject import VMobject
from mobject.svg.drawings 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)

View file

@ -0,0 +1,96 @@
from __future__ import absolute_import
from constants import *
from mobject.mobject import Group
from mobject.svg.drawings 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)

View 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.types.vectorized_mobject import VGroup
from mobject.frame import ScreenRectangle
from mobject.svg.drawings import SpeechBubble
from mobject.svg.drawings import ThoughtBubble
from animation.transform import ApplyMethod
from animation.transform import ReplacementTransform
from animation.transform import Transform
from for_3b1b_videos.pi_creature import PiCreature
from for_3b1b_videos.pi_creature import Mortimer
from for_3b1b_videos.pi_creature import Randolph
from for_3b1b_videos.pi_creature_animations import Blink
from for_3b1b_videos.pi_creature_animations import PiCreatureBubbleIntroduction
from for_3b1b_videos.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,
)

View file

@ -1,155 +1,22 @@
from __future__ import absolute_import
import numpy as np
from constants import *
from mobject.vectorized_mobject import VMobject, VGroup
from mobject.tex_mobject import TexMobject
from topics.geometry import Line, Arrow
from topics.functions import ParametricFunction
from scene.scene import Scene
from utils.bezier import interpolate
from mobject.functions import ParametricFunction
from mobject.geometry import Arrow
from mobject.geometry import Line
from mobject.number_line import NumberLine
from mobject.svg.tex_mobject import TexMobject
from mobject.types.vectorized_mobject import VGroup
from mobject.types.vectorized_mobject import VMobject
from utils.config_ops import digest_config
from utils.space_ops import R3_to_complex
from utils.space_ops import angle_of_vector
from utils.space_ops import complex_to_R3
class NumberLine(VMobject):
CONFIG = {
"color" : BLUE,
"x_min" : -FRAME_X_RADIUS,
"x_max" : FRAME_X_RADIUS,
"unit_size" : 1,
"tick_size" : 0.1,
"tick_frequency" : 1,
"leftmost_tick" : None, #Defaults to value near x_min s.t. 0 is a tick
"numbers_with_elongated_ticks" : [0],
"numbers_to_show" : None,
"longer_tick_multiple" : 2,
"number_at_center" : 0,
"number_scale_val" : 0.75,
"label_direction" : DOWN,
"line_to_number_buff" : MED_SMALL_BUFF,
"include_tip" : False,
"propagate_style_to_family" : True,
}
def __init__(self, **kwargs):
digest_config(self, kwargs)
if self.leftmost_tick is None:
tf = self.tick_frequency
self.leftmost_tick = tf*np.ceil(self.x_min/tf)
VMobject.__init__(self, **kwargs)
if self.include_tip:
self.add_tip()
def generate_points(self):
self.main_line = Line(self.x_min*RIGHT, self.x_max*RIGHT)
self.tick_marks = VGroup()
self.add(self.main_line, self.tick_marks)
rounding_value = int(-np.log10(0.1*self.tick_frequency))
rounded_numbers_with_elongated_ticks = np.round(
self.numbers_with_elongated_ticks,
rounding_value
)
for x in self.get_tick_numbers():
rounded_x = np.round(x, rounding_value)
if rounded_x in rounded_numbers_with_elongated_ticks:
tick_size_used = self.longer_tick_multiple*self.tick_size
else:
tick_size_used = self.tick_size
self.add_tick(x, tick_size_used)
self.stretch(self.unit_size, 0)
self.shift(-self.number_to_point(self.number_at_center))
def add_tick(self, x, size = None):
self.tick_marks.add(self.get_tick(x, size))
return self
def get_tick(self, x, size = None):
if size is None: size = self.tick_size
result = Line(size*DOWN, size*UP)
result.rotate(self.main_line.get_angle())
result.move_to(self.number_to_point(x))
return result
def get_tick_marks(self):
return self.tick_marks
def get_tick_numbers(self):
epsilon = 0.001
return np.arange(
self.leftmost_tick, self.x_max+epsilon,
self.tick_frequency
)
def number_to_point(self, number):
alpha = float(number-self.x_min)/(self.x_max - self.x_min)
return interpolate(
self.main_line.get_start(),
self.main_line.get_end(),
alpha
)
def point_to_number(self, point):
left_point, right_point = self.main_line.get_start_and_end()
full_vect = right_point-left_point
def distance_from_left(p):
return np.dot(p-left_point, full_vect)/np.linalg.norm(full_vect)
return interpolate(
self.x_min, self.x_max,
distance_from_left(point)/distance_from_left(right_point)
)
def default_numbers_to_display(self):
if self.numbers_to_show is not None:
return self.numbers_to_show
return np.arange(int(self.leftmost_tick), int(self.x_max)+1)
def get_number_mobjects(self, *numbers, **kwargs):
#TODO, handle decimals
if len(numbers) == 0:
numbers = self.default_numbers_to_display()
if "force_integers" in kwargs and kwargs["force_integers"]:
numbers = map(int, numbers)
result = VGroup()
for number in numbers:
mob = TexMobject(str(number))
mob.scale(self.number_scale_val)
mob.next_to(
self.number_to_point(number),
self.label_direction,
self.line_to_number_buff,
)
result.add(mob)
return result
def get_labels(self):
return self.get_number_mobjects()
def add_numbers(self, *numbers, **kwargs):
self.numbers = self.get_number_mobjects(
*numbers, **kwargs
)
self.add(*self.numbers)
return self
def add_tip(self):
start, end = self.main_line.get_start_and_end()
vect = (end - start)/np.linalg.norm(end-start)
arrow = Arrow(start, end + MED_SMALL_BUFF*vect, buff = 0)
tip = arrow.tip
tip.set_color(self.color)
self.tip = tip
self.add(tip)
class UnitInterval(NumberLine):
CONFIG = {
"x_min" : 0,
"x_max" : 1,
"unit_size" : 6,
"tick_frequency" : 0.1,
"numbers_with_elongated_ticks" : [0, 1],
"number_at_center" : 0.5,
}
#TODO: There should be much more code reuse between Axes, NumberPlane and GraphScene
class Axes(VGroup):
CONFIG = {
@ -423,13 +290,62 @@ class NumberPlane(VMobject):
mob.make_smooth()
return self
class ComplexPlane(NumberPlane):
CONFIG = {
"color" : BLUE,
"unit_size" : 1,
"line_frequency" : 1,
"faded_line_frequency" : 0.5,
}
def __init__(self, **kwargs):
digest_config(self, kwargs)
kwargs.update({
"x_unit_size" : self.unit_size,
"y_unit_size" : self.unit_size,
"x_line_frequency" : self.line_frequency,
"x_faded_line_frequency" : self.faded_line_frequency,
"y_line_frequency" : self.line_frequency,
"y_faded_line_frequency" : self.faded_line_frequency,
})
NumberPlane.__init__(self, **kwargs)
def number_to_point(self, number):
number = complex(number)
return self.coords_to_point(number.real, number.imag)
def point_to_number(self, point):
x, y = self.point_to_coords(point)
return complex(x, y)
def get_coordinate_labels(self, *numbers):
# TODO: Should merge this with the code from NumberPlane.get_coordinate_labels
result = VGroup()
nudge = 0.1*(DOWN+RIGHT)
if len(numbers) == 0:
numbers = range(-int(self.x_radius), int(self.x_radius)+1)
numbers += [
complex(0, y)
for y in range(-int(self.y_radius), int(self.y_radius)+1)
]
for number in numbers:
if number == complex(0, 0):
continue
point = self.number_to_point(number)
num_str = str(number).replace("j", "i")
if num_str.startswith("0"):
num_str = "0"
elif num_str in ["1i", "-1i"]:
num_str = num_str.replace("1", "")
num_mob = TexMobject(num_str)
num_mob.add_background_rectangle()
num_mob.scale_to_fit_height(self.written_coordinate_height)
num_mob.next_to(point, DOWN+LEFT, SMALL_BUFF)
result.add(num_mob)
self.coordinate_labels = result
return result
def add_coordinates(self, *numbers):
self.add(*self.get_coordinate_labels(*numbers))
return self

44
mobject/frame.py Normal file
View file

@ -0,0 +1,44 @@
from __future__ import absolute_import
from constants import *
from mobject.geometry import Rectangle
from utils.config_ops import digest_config
class ScreenRectangle(Rectangle):
CONFIG = {
"width_to_height_ratio" : 16.0/9.0,
"height" : 4,
}
def generate_points(self):
self.width = self.width_to_height_ratio * self.height
Rectangle.generate_points(self)
class FullScreenRectangle(ScreenRectangle):
CONFIG = {
"height" : FRAME_HEIGHT,
}
class FullScreenFadeRectangle(FullScreenRectangle):
CONFIG = {
"stroke_width" : 0,
"fill_color" : BLACK,
"fill_opacity" : 0.7,
}
class PictureInPictureFrame(Rectangle):
CONFIG = {
"height" : 3,
"aspect_ratio" : (16, 9)
}
def __init__(self, **kwargs):
digest_config(self, kwargs)
height = self.height
if "height" in kwargs:
kwargs.pop("height")
Rectangle.__init__(
self,
width = self.aspect_ratio[0],
height = self.aspect_ratio[1],
**kwargs
)
self.scale_to_fit_height(height)

View file

@ -1,10 +1,11 @@
from scipy import integrate
from mobject.vectorized_mobject import VMobject
from utils.config_ops import digest_config
from __future__ import absolute_import
from constants import *
from mobject.types.vectorized_mobject import VMobject
from utils.config_ops import digest_config
class ParametricFunction(VMobject):
CONFIG = {
"t_min" : 0,

View file

@ -1,14 +1,21 @@
from __future__ import absolute_import
from constants import *
import itertools as it
import numpy as np
from mobject.mobject import Mobject
from mobject.vectorized_mobject import VMobject, VGroup
from mobject.types.vectorized_mobject import VGroup
from mobject.types.vectorized_mobject import VMobject
from utils.bezier import interpolate
from utils.config_ops import digest_config, digest_locals
from utils.config_ops import digest_config
from utils.config_ops import digest_locals
from utils.paths import path_along_arc
from utils.space_ops import rotate_vector, angle_of_vector, compass_directions, center_of_mass
from utils.space_ops import angle_of_vector
from utils.space_ops import center_of_mass
from utils.space_ops import compass_directions
from utils.space_ops import rotate_vector
class Arc(VMobject):
CONFIG = {

137
mobject/matrix.py Normal file
View file

@ -0,0 +1,137 @@
from __future__ import absolute_import
import numpy as np
from mobject.mobject import Mobject
from mobject.svg.tex_mobject import TexMobject
from mobject.types.vectorized_mobject import VGroup
from mobject.types.vectorized_mobject import VMobject
from mobject.shape_matchers import BackgroundRectangle
from constants import *
VECTOR_LABEL_SCALE_FACTOR = 0.8
def matrix_to_tex_string(matrix):
matrix = np.array(matrix).astype("string")
if matrix.ndim == 1:
matrix = matrix.reshape((matrix.size, 1))
n_rows, n_cols = matrix.shape
prefix = "\\left[ \\begin{array}{%s}"%("c"*n_cols)
suffix = "\\end{array} \\right]"
rows = [
" & ".join(row)
for row in matrix
]
return prefix + " \\\\ ".join(rows) + suffix
def matrix_to_mobject(matrix):
return TexMobject(matrix_to_tex_string(matrix))
def vector_coordinate_label(vector_mob, integer_labels = True,
n_dim = 2, color = WHITE):
vect = np.array(vector_mob.get_end())
if integer_labels:
vect = np.round(vect).astype(int)
vect = vect[:n_dim]
vect = vect.reshape((n_dim, 1))
label = Matrix(vect, add_background_rectangles = True)
label.scale(VECTOR_LABEL_SCALE_FACTOR)
shift_dir = np.array(vector_mob.get_end())
if shift_dir[0] >= 0: #Pointing right
shift_dir -= label.get_left() + DEFAULT_MOBJECT_TO_MOBJECT_BUFFER*LEFT
else: #Pointing left
shift_dir -= label.get_right() + DEFAULT_MOBJECT_TO_MOBJECT_BUFFER*RIGHT
label.shift(shift_dir)
label.set_color(color)
label.rect = BackgroundRectangle(label)
label.add_to_back(label.rect)
return label
class Matrix(VMobject):
CONFIG = {
"v_buff" : 0.5,
"h_buff" : 1,
"add_background_rectangles" : False
}
def __init__(self, matrix, **kwargs):
"""
Matrix can either either include numbres, tex_strings,
or mobjects
"""
VMobject.__init__(self, **kwargs)
matrix = np.array(matrix)
if matrix.ndim == 1:
matrix = matrix.reshape((matrix.size, 1))
if not isinstance(matrix[0][0], Mobject):
matrix = matrix.astype("string")
matrix = self.string_matrix_to_mob_matrix(matrix)
self.organize_mob_matrix(matrix)
self.add(*matrix.flatten())
self.add_brackets()
self.center()
self.mob_matrix = matrix
if self.add_background_rectangles:
for mob in matrix.flatten():
mob.add_background_rectangle()
def string_matrix_to_mob_matrix(self, matrix):
return np.array([
map(TexMobject, row)
for row in matrix
]).reshape(matrix.shape)
def organize_mob_matrix(self, matrix):
for i, row in enumerate(matrix):
for j, elem in enumerate(row):
mob = matrix[i][j]
if i == 0 and j == 0:
continue
elif i == 0:
mob.next_to(matrix[i][j-1], RIGHT, self.h_buff)
else:
mob.next_to(matrix[i-1][j], DOWN, self.v_buff)
return self
def add_brackets(self):
bracket_pair = TexMobject("\\big[ \\big]")
bracket_pair.scale(2)
bracket_pair.stretch_to_fit_height(self.get_height() + 0.5)
l_bracket, r_bracket = bracket_pair.split()
l_bracket.next_to(self, LEFT)
r_bracket.next_to(self, RIGHT)
self.add(l_bracket, r_bracket)
self.brackets = VGroup(l_bracket, r_bracket)
return self
def set_color_columns(self, *colors):
for i, color in enumerate(colors):
VGroup(*self.mob_matrix[:,i]).set_color(color)
return self
def add_background_to_entries(self):
for mob in self.get_entries():
mob.add_background_rectangle()
return self
def get_mob_matrix(self):
return self.mob_matrix
def get_entries(self):
return VGroup(*self.get_mob_matrix().flatten())
def get_brackets(self):
return self.brackets

View file

@ -1,20 +1,25 @@
import copy
import itertools as it
import numpy as np
import operator as op
import itertools as it
import os
import copy
from PIL import Image
from colour import Color
from constants import *
from container.container import Container
from utils.bezier import interpolate
from utils.color import color_to_rgb, color_gradient
from utils.color import color_gradient
from utils.color import color_to_rgb
from utils.color import interpolate_color
from utils.iterables import remove_list_redundancies, list_update
from utils.iterables import list_update
from utils.iterables import remove_list_redundancies
from utils.paths import straight_path
from utils.space_ops import rotation_matrix, angle_of_vector
from utils.space_ops import complex_to_R3, R3_to_complex
from utils.space_ops import R3_to_complex
from utils.space_ops import angle_of_vector
from utils.space_ops import complex_to_R3
from utils.space_ops import rotation_matrix
#TODO: Explain array_attrs

154
mobject/number_line.py Normal file
View file

@ -0,0 +1,154 @@
from __future__ import absolute_import
from constants import *
from mobject.svg.tex_mobject import TexMobject
from mobject.types.vectorized_mobject import VGroup
from mobject.types.vectorized_mobject import VMobject
from mobject.geometry import Arrow
from mobject.geometry import Line
from utils.bezier import interpolate
from utils.config_ops import digest_config
class NumberLine(VMobject):
CONFIG = {
"color" : BLUE,
"x_min" : -FRAME_X_RADIUS,
"x_max" : FRAME_X_RADIUS,
"unit_size" : 1,
"tick_size" : 0.1,
"tick_frequency" : 1,
"leftmost_tick" : None, #Defaults to value near x_min s.t. 0 is a tick
"numbers_with_elongated_ticks" : [0],
"numbers_to_show" : None,
"longer_tick_multiple" : 2,
"number_at_center" : 0,
"number_scale_val" : 0.75,
"label_direction" : DOWN,
"line_to_number_buff" : MED_SMALL_BUFF,
"include_tip" : False,
"propagate_style_to_family" : True,
}
def __init__(self, **kwargs):
digest_config(self, kwargs)
if self.leftmost_tick is None:
tf = self.tick_frequency
self.leftmost_tick = tf*np.ceil(self.x_min/tf)
VMobject.__init__(self, **kwargs)
if self.include_tip:
self.add_tip()
def generate_points(self):
self.main_line = Line(self.x_min*RIGHT, self.x_max*RIGHT)
self.tick_marks = VGroup()
self.add(self.main_line, self.tick_marks)
rounding_value = int(-np.log10(0.1*self.tick_frequency))
rounded_numbers_with_elongated_ticks = np.round(
self.numbers_with_elongated_ticks,
rounding_value
)
for x in self.get_tick_numbers():
rounded_x = np.round(x, rounding_value)
if rounded_x in rounded_numbers_with_elongated_ticks:
tick_size_used = self.longer_tick_multiple*self.tick_size
else:
tick_size_used = self.tick_size
self.add_tick(x, tick_size_used)
self.stretch(self.unit_size, 0)
self.shift(-self.number_to_point(self.number_at_center))
def add_tick(self, x, size = None):
self.tick_marks.add(self.get_tick(x, size))
return self
def get_tick(self, x, size = None):
if size is None: size = self.tick_size
result = Line(size*DOWN, size*UP)
result.rotate(self.main_line.get_angle())
result.move_to(self.number_to_point(x))
return result
def get_tick_marks(self):
return self.tick_marks
def get_tick_numbers(self):
epsilon = 0.001
return np.arange(
self.leftmost_tick, self.x_max+epsilon,
self.tick_frequency
)
def number_to_point(self, number):
alpha = float(number-self.x_min)/(self.x_max - self.x_min)
return interpolate(
self.main_line.get_start(),
self.main_line.get_end(),
alpha
)
def point_to_number(self, point):
left_point, right_point = self.main_line.get_start_and_end()
full_vect = right_point-left_point
def distance_from_left(p):
return np.dot(p-left_point, full_vect)/np.linalg.norm(full_vect)
return interpolate(
self.x_min, self.x_max,
distance_from_left(point)/distance_from_left(right_point)
)
def default_numbers_to_display(self):
if self.numbers_to_show is not None:
return self.numbers_to_show
return np.arange(int(self.leftmost_tick), int(self.x_max)+1)
def get_number_mobjects(self, *numbers, **kwargs):
#TODO, handle decimals
if len(numbers) == 0:
numbers = self.default_numbers_to_display()
if "force_integers" in kwargs and kwargs["force_integers"]:
numbers = map(int, numbers)
result = VGroup()
for number in numbers:
mob = TexMobject(str(number))
mob.scale(self.number_scale_val)
mob.next_to(
self.number_to_point(number),
self.label_direction,
self.line_to_number_buff,
)
result.add(mob)
return result
def get_labels(self):
return self.get_number_mobjects()
def add_numbers(self, *numbers, **kwargs):
self.numbers = self.get_number_mobjects(
*numbers, **kwargs
)
self.add(*self.numbers)
return self
def add_tip(self):
start, end = self.main_line.get_start_and_end()
vect = (end - start)/np.linalg.norm(end-start)
arrow = Arrow(start, end + MED_SMALL_BUFF*vect, buff = 0)
tip = arrow.tip
tip.set_color(self.color)
self.tip = tip
self.add(tip)
class UnitInterval(NumberLine):
CONFIG = {
"x_min" : 0,
"x_max" : 1,
"unit_size" : 6,
"tick_frequency" : 0.1,
"numbers_with_elongated_ticks" : [0, 1],
"number_at_center" : 0.5,
}

85
mobject/numbers.py Normal file
View file

@ -0,0 +1,85 @@
from __future__ import absolute_import
from constants import *
from mobject.svg.tex_mobject import TexMobject
from mobject.types.vectorized_mobject import VGroup
from mobject.types.vectorized_mobject import VMobject
from mobject.shape_matchers import BackgroundRectangle
class DecimalNumber(VMobject):
CONFIG = {
"num_decimal_points" : 2,
"digit_to_digit_buff" : 0.05,
"show_ellipsis" : False,
"unit" : None, #Aligned to bottom unless it starts with "^"
"include_background_rectangle" : False,
}
def __init__(self, number, **kwargs):
VMobject.__init__(self, **kwargs)
self.number = number
ndp = self.num_decimal_points
#Build number string
if isinstance(number, complex):
num_string = '%.*f%s%.*fi'%(
ndp, number.real,
"-" if number.imag < 0 else "+",
ndp, abs(number.imag)
)
else:
num_string = '%.*f'%(ndp, number)
negative_zero_string = "-%.*f"%(ndp, 0.)
if num_string == negative_zero_string:
num_string = num_string[1:]
self.add(*[
TexMobject(char, **kwargs)
for char in num_string
])
#Add non-numerical bits
if self.show_ellipsis:
self.add(TexMobject("\\dots"))
if num_string.startswith("-"):
minus = self.submobjects[0]
minus.next_to(
self.submobjects[1], LEFT,
buff = self.digit_to_digit_buff
)
if self.unit != None:
self.unit_sign = TexMobject(self.unit)
self.add(self.unit_sign)
self.arrange_submobjects(
buff = self.digit_to_digit_buff,
aligned_edge = DOWN
)
#Handle alignment of parts that should be aligned
#to the bottom
for i, c in enumerate(num_string):
if c == "-" and len(num_string) > i+1:
self[i].align_to(self[i+1], alignment_vect = UP)
if self.unit and self.unit.startswith("^"):
self.unit_sign.align_to(self, UP)
#
if self.include_background_rectangle:
self.add_background_rectangle()
def add_background_rectangle(self):
#TODO, is this the best way to handle
#background rectangles?
self.background_rectangle = BackgroundRectangle(self)
self.submobjects = [
self.background_rectangle,
VGroup(*self.submobjects)
]
return self
class Integer(DecimalNumber):
CONFIG = {
"num_decimal_points" : 0,
}

251
mobject/probability.py Normal file
View file

@ -0,0 +1,251 @@
from __future__ import absolute_import
from constants import *
from mobject.mobject import Mobject
from mobject.svg.brace import Brace
from mobject.svg.tex_mobject import TexMobject
from mobject.svg.tex_mobject import TextMobject
from mobject.types.vectorized_mobject import VGroup
from mobject.geometry import Line
from mobject.geometry import Rectangle
from utils.color import color_gradient
from utils.iterables import tuplify
EPSILON = 0.0001
class SampleSpace(Rectangle):
CONFIG = {
"height" : 3,
"width" : 3,
"fill_color" : DARK_GREY,
"fill_opacity" : 1,
"stroke_width" : 0.5,
"stroke_color" : LIGHT_GREY,
##
"default_label_scale_val" : 1,
}
def add_title(self, title = "Sample space", buff = MED_SMALL_BUFF):
##TODO, should this really exist in SampleSpaceScene
title_mob = TextMobject(title)
if title_mob.get_width() > self.get_width():
title_mob.scale_to_fit_width(self.get_width())
title_mob.next_to(self, UP, buff = buff)
self.title = title_mob
self.add(title_mob)
def add_label(self, label):
self.label = label
def complete_p_list(self, p_list):
new_p_list = list(tuplify(p_list))
remainder = 1.0 - sum(new_p_list)
if abs(remainder) > EPSILON:
new_p_list.append(remainder)
return new_p_list
def get_division_along_dimension(self, p_list, dim, colors, vect):
p_list = self.complete_p_list(p_list)
colors = color_gradient(colors, len(p_list))
last_point = self.get_edge_center(-vect)
parts = VGroup()
for factor, color in zip(p_list, colors):
part = SampleSpace()
part.set_fill(color, 1)
part.replace(self, stretch = True)
part.stretch(factor, dim)
part.move_to(last_point, -vect)
last_point = part.get_edge_center(vect)
parts.add(part)
return parts
def get_horizontal_division(
self, p_list,
colors = [GREEN_E, BLUE_E],
vect = DOWN
):
return self.get_division_along_dimension(p_list, 1, colors, vect)
def get_vertical_division(
self, p_list,
colors = [MAROON_B, YELLOW],
vect = RIGHT
):
return self.get_division_along_dimension(p_list, 0, colors, vect)
def divide_horizontally(self, *args, **kwargs):
self.horizontal_parts = self.get_horizontal_division(*args, **kwargs)
self.add(self.horizontal_parts)
def divide_vertically(self, *args, **kwargs):
self.vertical_parts = self.get_vertical_division(*args, **kwargs)
self.add(self.vertical_parts)
def get_subdivision_braces_and_labels(
self, parts, labels, direction,
buff = SMALL_BUFF,
min_num_quads = 1
):
label_mobs = VGroup()
braces = VGroup()
for label, part in zip(labels, parts):
brace = Brace(
part, direction,
min_num_quads = min_num_quads,
buff = buff
)
if isinstance(label, Mobject):
label_mob = label
else:
label_mob = TexMobject(label)
label_mob.scale(self.default_label_scale_val)
label_mob.next_to(brace, direction, buff)
braces.add(brace)
label_mobs.add(label_mob)
parts.braces = braces
parts.labels = label_mobs
parts.label_kwargs = {
"labels" : label_mobs.copy(),
"direction" : direction,
"buff" : buff,
}
return VGroup(parts.braces, parts.labels)
def get_side_braces_and_labels(self, labels, direction = LEFT, **kwargs):
assert(hasattr(self, "horizontal_parts"))
parts = self.horizontal_parts
return self.get_subdivision_braces_and_labels(parts, labels, direction, **kwargs)
def get_top_braces_and_labels(self, labels, **kwargs):
assert(hasattr(self, "vertical_parts"))
parts = self.vertical_parts
return self.get_subdivision_braces_and_labels(parts, labels, UP, **kwargs)
def get_bottom_braces_and_labels(self, labels, **kwargs):
assert(hasattr(self, "vertical_parts"))
parts = self.vertical_parts
return self.get_subdivision_braces_and_labels(parts, labels, DOWN, **kwargs)
def add_braces_and_labels(self):
for attr in "horizontal_parts", "vertical_parts":
if not hasattr(self, attr):
continue
parts = getattr(self, attr)
for subattr in "braces", "labels":
if hasattr(parts, subattr):
self.add(getattr(parts, subattr))
def __getitem__(self, index):
if hasattr(self, "horizontal_parts"):
return self.horizontal_parts[index]
elif hasattr(self, "vertical_parts"):
return self.vertical_parts[index]
return self.split()[index]
class BarChart(VGroup):
CONFIG = {
"height" : 4,
"width" : 6,
"n_ticks" : 4,
"tick_width" : 0.2,
"label_y_axis" : True,
"y_axis_label_height" : 0.25,
"max_value" : 1,
"bar_colors" : [BLUE, YELLOW],
"bar_fill_opacity" : 0.8,
"bar_stroke_width" : 3,
"bar_names" : [],
"bar_label_scale_val" : 0.75,
}
def __init__(self, values, **kwargs):
VGroup.__init__(self, **kwargs)
if self.max_value is None:
self.max_value = max(values)
self.add_axes()
self.add_bars(values)
self.center()
def add_axes(self):
x_axis = Line(self.tick_width*LEFT/2, self.width*RIGHT)
y_axis = Line(MED_LARGE_BUFF*DOWN, self.height*UP)
ticks = VGroup()
heights = np.linspace(0, self.height, self.n_ticks+1)
values = np.linspace(0, self.max_value, self.n_ticks+1)
for y, value in zip(heights, values):
tick = Line(LEFT, RIGHT)
tick.scale_to_fit_width(self.tick_width)
tick.move_to(y*UP)
ticks.add(tick)
y_axis.add(ticks)
self.add(x_axis, y_axis)
self.x_axis, self.y_axis = x_axis, y_axis
if self.label_y_axis:
labels = VGroup()
for tick, value in zip(ticks, values):
label = TexMobject(str(np.round(value, 2)))
label.scale_to_fit_height(self.y_axis_label_height)
label.next_to(tick, LEFT, SMALL_BUFF)
labels.add(label)
self.y_axis_labels = labels
self.add(labels)
def add_bars(self, values):
buff = float(self.width) / (2*len(values) + 1)
bars = VGroup()
for i, value in enumerate(values):
bar = Rectangle(
height = (value/self.max_value)*self.height,
width = buff,
stroke_width = self.bar_stroke_width,
fill_opacity = self.bar_fill_opacity,
)
bar.move_to((2*i+1)*buff*RIGHT, DOWN+LEFT)
bars.add(bar)
bars.set_color_by_gradient(*self.bar_colors)
bar_labels = VGroup()
for bar, name in zip(bars, self.bar_names):
label = TexMobject(str(name))
label.scale(self.bar_label_scale_val)
label.next_to(bar, DOWN, SMALL_BUFF)
bar_labels.add(label)
self.add(bars, bar_labels)
self.bars = bars
self.bar_labels = bar_labels
def change_bar_values(self, values):
for bar, value in zip(self.bars, values):
bar_bottom = bar.get_bottom()
bar.stretch_to_fit_height(
(value/self.max_value)*self.height
)
bar.move_to(bar_bottom, DOWN)
def copy(self):
return self.deepcopy()

56
mobject/shape_matchers.py Normal file
View file

@ -0,0 +1,56 @@
from __future__ import absolute_import
from constants import *
from mobject.geometry import Rectangle
from mobject.geometry import Line
from mobject.types.vectorized_mobject import VGroup
from utils.config_ops import digest_config
from utils.color import Color
class SurroundingRectangle(Rectangle):
CONFIG = {
"color" : YELLOW,
"buff" : SMALL_BUFF,
}
def __init__(self, mobject, **kwargs):
digest_config(self, kwargs)
kwargs["width"] = mobject.get_width() + 2*self.buff
kwargs["height"] = mobject.get_height() + 2*self.buff
Rectangle.__init__(self, **kwargs)
self.move_to(mobject)
class BackgroundRectangle(SurroundingRectangle):
CONFIG = {
"color" : BLACK,
"stroke_width" : 0,
"fill_opacity" : 0.75,
"buff" : 0
}
def __init__(self, mobject, **kwargs):
SurroundingRectangle.__init__(self, mobject, **kwargs)
self.original_fill_opacity = self.fill_opacity
def pointwise_become_partial(self, mobject, a, b):
self.set_fill(opacity = b*self.original_fill_opacity)
return self
def set_color(self):
# Can't be changin' me!
return self
def get_fill_color(self):
return Color(self.color)
class Cross(VGroup):
CONFIG = {
"stroke_color" : RED,
"stroke_width" : 6,
}
def __init__(self, mobject, **kwargs):
VGroup.__init__(self,
Line(UP+LEFT, DOWN+RIGHT),
Line(UP+RIGHT, DOWN+LEFT),
)
self.replace(mobject, stretch = True)
self.set_stroke(self.stroke_color, self.stroke_width)

0
mobject/svg/__init__.py Normal file
View file

130
mobject/svg/brace.py Normal file
View file

@ -0,0 +1,130 @@
from __future__ import absolute_import
import numpy as np
from constants import *
from animation.composition import AnimationGroup
from animation.creation import FadeIn
from animation.creation import GrowFromCenter
from mobject.svg.tex_mobject import TexMobject
from mobject.svg.tex_mobject import TextMobject
from mobject.types.vectorized_mobject import VMobject
from utils.config_ops import digest_config
class Brace(TexMobject):
CONFIG = {
"buff" : 0.2,
"width_multiplier" : 2,
"max_num_quads" : 15,
"min_num_quads" : 0,
}
def __init__(self, mobject, direction = DOWN, **kwargs):
digest_config(self, kwargs, locals())
angle = -np.arctan2(*direction[:2]) + np.pi
mobject.rotate(-angle, about_point = ORIGIN)
left = mobject.get_corner(DOWN+LEFT)
right = mobject.get_corner(DOWN+RIGHT)
target_width = right[0]-left[0]
## Adding int(target_width) qquads gives approximately the right width
num_quads = np.clip(
int(self.width_multiplier*target_width),
self.min_num_quads, self.max_num_quads
)
tex_string = "\\underbrace{%s}"%(num_quads*"\\qquad")
TexMobject.__init__(self, tex_string, **kwargs)
self.tip_point_index = np.argmin(self.get_all_points()[:,1])
self.stretch_to_fit_width(target_width)
self.shift(left - self.get_corner(UP+LEFT) + self.buff*DOWN)
for mob in mobject, self:
mob.rotate(angle, about_point = ORIGIN)
def put_at_tip(self, mob, use_next_to = True, **kwargs):
if use_next_to:
mob.next_to(
self.get_tip(),
np.round(self.get_direction()),
**kwargs
)
else:
mob.move_to(self.get_tip())
buff = kwargs.get("buff", DEFAULT_MOBJECT_TO_MOBJECT_BUFFER)
shift_distance = mob.get_width()/2.0+buff
mob.shift(self.get_direction()*shift_distance)
return self
def get_text(self, *text, **kwargs):
text_mob = TextMobject(*text)
self.put_at_tip(text_mob, **kwargs)
return text_mob
def get_tex(self, *tex, **kwargs):
tex_mob = TexMobject(*tex)
self.put_at_tip(tex_mob, **kwargs)
return tex_mob
def get_tip(self):
# Very specific to the LaTeX representation
# of a brace, but it's the only way I can think
# of to get the tip regardless of orientation.
return self.get_all_points()[self.tip_point_index]
def get_direction(self):
vect = self.get_tip() - self.get_center()
return vect/np.linalg.norm(vect)
class BraceLabel(VMobject):
CONFIG = {
"label_constructor" : TexMobject,
"label_scale" : 1,
}
def __init__(self, obj, text, brace_direction = DOWN, **kwargs):
VMobject.__init__(self, **kwargs)
self.brace_direction = brace_direction
if isinstance(obj, list): obj = VMobject(*obj)
self.brace = Brace(obj, brace_direction, **kwargs)
if isinstance(text, tuple) or isinstance(text, list):
self.label = self.label_constructor(*text, **kwargs)
else: self.label = self.label_constructor(str(text))
if self.label_scale != 1: self.label.scale(self.label_scale)
self.brace.put_at_tip(self.label)
self.submobjects = [self.brace, self.label]
def creation_anim(self, label_anim = FadeIn, brace_anim = GrowFromCenter):
return AnimationGroup(brace_anim(self.brace), label_anim(self.label))
def shift_brace(self, obj, **kwargs):
if isinstance(obj, list): obj = VMobject(*obj)
self.brace = Brace(obj, self.brace_direction, **kwargs)
self.brace.put_at_tip(self.label)
self.submobjects[0] = self.brace
return self
def change_label(self, *text, **kwargs):
self.label = self.label_constructor(*text, **kwargs)
if self.label_scale != 1: self.label.scale(self.label_scale)
self.brace.put_at_tip(self.label)
self.submobjects[1] = self.label
return self
def change_brace_label(self, obj, *text):
self.shift_brace(obj)
self.change_label(*text)
return self
def copy(self):
copy_mobject = copy.copy(self)
copy_mobject.brace = self.brace.copy()
copy_mobject.label = self.label.copy()
copy_mobject.submobjects = [copy_mobject.brace, copy_mobject.label]
return copy_mobject
class BraceText(BraceLabel):
CONFIG = {
"label_constructor" : TextMobject
}

View file

@ -1,21 +1,38 @@
from __future__ import absolute_import
from constants import *
from mobject.mobject import Mobject
from mobject.vectorized_mobject import VGroup, VMobject, VectorizedPoint
from mobject.svg_mobject import SVGMobject
from mobject.tex_mobject import TextMobject, TexMobject, Brace
from mobject.svg.svg_mobject import SVGMobject
from mobject.svg.brace import Brace
from mobject.svg.tex_mobject import TexMobject
from mobject.svg.tex_mobject import TextMobject
from mobject.types.vectorized_mobject import VGroup
from mobject.types.vectorized_mobject import VMobject
from mobject.types.vectorized_mobject import VectorizedPoint
from animation.animation import Animation
from animation.simple_animations import Rotating
from animation.compositions import LaggedStart, AnimationGroup
from animation.transform import ApplyMethod, FadeIn, GrowFromCenter
from animation.composition import AnimationGroup
from animation.composition import LaggedStart
from animation.rotation import Rotating
from animation.transform import ApplyMethod
from animation.creation import FadeIn
from animation.creation import GrowFromCenter
from topics.geometry import Circle, Line, Rectangle, Square, \
Arc, Polygon, SurroundingRectangle
from topics.three_dimensions import Cube
from utils.config_ops import digest_config, digest_locals
from utils.space_ops import rotate_vector, angle_of_vector
from utils.space_ops import complex_to_R3, R3_to_complex
from mobject.geometry import Arc
from mobject.geometry import Circle
from mobject.geometry import Line
from mobject.geometry import Polygon
from mobject.geometry import Rectangle
from mobject.geometry import Square
from mobject.shape_matchers import SurroundingRectangle
from mobject.three_dimensions import Cube
from utils.config_ops import digest_config
from utils.config_ops import digest_locals
from utils.space_ops import R3_to_complex
from utils.space_ops import angle_of_vector
from utils.space_ops import complex_to_R3
from utils.space_ops import rotate_vector
class Lightbulb(SVGMobject):
CONFIG = {
@ -503,7 +520,7 @@ class Car(SVGMobject):
self.set_stroke(color = WHITE, width = 0)
self.set_fill(self.color, opacity = 1)
from topics.characters import Randolph
from for_3b1b_videos.pi_creature import Randolph
randy = Randolph(mode = "happy")
randy.scale_to_fit_height(0.6*self.get_height())
randy.stretch(0.8, 0)
@ -568,131 +585,281 @@ class Car(SVGMobject):
def get_rear_light(self):
return self[1][8]
class MoveCar(ApplyMethod):
CONFIG = {
"moving_forward" : True,
}
def __init__(self, car, target_point, **kwargs):
ApplyMethod.__init__(self, car.move_to, target_point, **kwargs)
displacement = self.target_mobject.get_right()-self.starting_mobject.get_right()
distance = np.linalg.norm(displacement)
if not self.moving_forward:
distance *= -1
tire_radius = car.get_tires()[0].get_width()/2
self.total_tire_radians = -distance/tire_radius
### Cards ###
def update_mobject(self, alpha):
ApplyMethod.update_mobject(self, alpha)
if alpha == 0:
return
radians = alpha*self.total_tire_radians
for tire in self.mobject.get_tires():
tire.rotate_in_place(radians)
class DeckOfCards(VGroup):
def __init__(self, **kwargs):
possible_values = map(str, range(1, 11)) + ["J", "Q", "K"]
possible_suits = ["hearts", "diamonds", "spades", "clubs"]
VGroup.__init__(self, *[
PlayingCard(value = value, suit = suit, **kwargs)
for value in possible_values
for suit in possible_suits
])
#TODO: Where should this live?
class Broadcast(LaggedStart):
class PlayingCard(VGroup):
CONFIG = {
"small_radius" : 0.0,
"big_radius" : 5,
"n_circles" : 5,
"start_stroke_width" : 8,
"color" : WHITE,
"remover" : True,
"lag_ratio" : 0.7,
"run_time" : 3,
"remover" : True,
"value" : None,
"suit" : None,
"key" : None, ##String like "8H" or "KS"
"height" : 2,
"height_to_width" : 3.5/2.5,
"card_height_to_symbol_height" : 7,
"card_width_to_corner_num_width" : 10,
"card_height_to_corner_num_height" : 10,
"color" : LIGHT_GREY,
"turned_over" : False,
"possible_suits" : ["hearts", "diamonds", "spades", "clubs"],
"possible_values" : map(str, range(2, 11)) + ["J", "Q", "K", "A"],
}
def __init__(self, focal_point, **kwargs):
digest_config(self, kwargs)
circles = VGroup()
for x in range(self.n_circles):
circle = Circle(
radius = self.big_radius,
def __init__(self, key = None, **kwargs):
VGroup.__init__(self, key = key, **kwargs)
def generate_points(self):
self.add(Rectangle(
height = self.height,
width = self.height/self.height_to_width,
stroke_color = WHITE,
stroke_width = 2,
fill_color = self.color,
fill_opacity = 1,
))
if self.turned_over:
self.set_fill(DARK_GREY)
self.set_stroke(LIGHT_GREY)
contents = VectorizedPoint(self.get_center())
else:
value = self.get_value()
symbol = self.get_symbol()
design = self.get_design(value, symbol)
corner_numbers = self.get_corner_numbers(value, symbol)
contents = VGroup(design, corner_numbers)
self.design = design
self.corner_numbers = corner_numbers
self.add(contents)
def get_value(self):
value = self.value
if value is None:
if self.key is not None:
value = self.key[:-1]
else:
value = random.choice(self.possible_values)
value = string.upper(str(value))
if value == "1":
value = "A"
if value not in self.possible_values:
raise Exception("Invalid card value")
face_card_to_value = {
"J" : 11,
"Q" : 12,
"K" : 13,
"A" : 14,
}
try:
self.numerical_value = int(value)
except:
self.numerical_value = face_card_to_value[value]
return value
def get_symbol(self):
suit = self.suit
if suit is None:
if self.key is not None:
suit = dict([
(string.upper(s[0]), s)
for s in self.possible_suits
])[string.upper(self.key[-1])]
else:
suit = random.choice(self.possible_suits)
if suit not in self.possible_suits:
raise Exception("Invalud suit value")
self.suit = suit
symbol_height = float(self.height) / self.card_height_to_symbol_height
symbol = SuitSymbol(suit, height = symbol_height)
return symbol
def get_design(self, value, symbol):
if value == "A":
return self.get_ace_design(symbol)
if value in map(str, range(2, 11)):
return self.get_number_design(value, symbol)
else:
return self.get_face_card_design(value, symbol)
def get_ace_design(self, symbol):
design = symbol.copy().scale(1.5)
design.move_to(self)
return design
def get_number_design(self, value, symbol):
num = int(value)
n_rows = {
2 : 2,
3 : 3,
4 : 2,
5 : 2,
6 : 3,
7 : 3,
8 : 3,
9 : 4,
10 : 4,
}[num]
n_cols = 1 if num in [2, 3] else 2
insertion_indices = {
5 : [0],
7 : [0],
8 : [0, 1],
9 : [1],
10 : [0, 2],
}.get(num, [])
top = self.get_top() + symbol.get_height()*DOWN
bottom = self.get_bottom() + symbol.get_height()*UP
column_points = [
interpolate(top, bottom, alpha)
for alpha in np.linspace(0, 1, n_rows)
]
design = VGroup(*[
symbol.copy().move_to(point)
for point in column_points
])
if n_cols == 2:
space = 0.2*self.get_width()
column_copy = design.copy().shift(space*RIGHT)
design.shift(space*LEFT)
design.add(*column_copy)
design.add(*[
symbol.copy().move_to(
center_of_mass(column_points[i:i+2])
)
for i in insertion_indices
])
for symbol in design:
if symbol.get_center()[1] < self.get_center()[1]:
symbol.rotate_in_place(np.pi)
return design
def get_face_card_design(self, value, symbol):
from for_3b1b_videos.pi_creature import PiCreature
sub_rect = Rectangle(
stroke_color = BLACK,
stroke_width = 0,
fill_opacity = 0,
height = 0.9*self.get_height(),
width = 0.6*self.get_width(),
)
circle.move_to(focal_point)
circle.save_state()
circle.scale_to_fit_width(self.small_radius*2)
circle.set_stroke(self.color, self.start_stroke_width)
circles.add(circle)
LaggedStart.__init__(
self, ApplyMethod, circles,
lambda c : (c.restore,),
**kwargs
sub_rect.move_to(self)
# pi_color = average_color(symbol.get_color(), GREY)
pi_color = symbol.get_color()
pi_mode = {
"J" : "plain",
"Q" : "thinking",
"K" : "hooray"
}[value]
pi_creature = PiCreature(
mode = pi_mode,
color = pi_color,
)
pi_creature.scale_to_fit_width(0.8*sub_rect.get_width())
if value in ["Q", "K"]:
prefix = "king" if value == "K" else "queen"
crown = SVGMobject(file_name = prefix + "_crown")
crown.set_stroke(width = 0)
crown.set_fill(YELLOW, 1)
crown.stretch_to_fit_width(0.5*sub_rect.get_width())
crown.stretch_to_fit_height(0.17*sub_rect.get_height())
crown.move_to(pi_creature.eyes.get_center(), DOWN)
pi_creature.add_to_back(crown)
to_top_buff = 0
else:
to_top_buff = SMALL_BUFF*sub_rect.get_height()
pi_creature.next_to(sub_rect.get_top(), DOWN, to_top_buff)
# pi_creature.shift(0.05*sub_rect.get_width()*RIGHT)
pi_copy = pi_creature.copy()
pi_copy.rotate(np.pi, about_point = sub_rect.get_center())
return VGroup(sub_rect, pi_creature, pi_copy)
def get_corner_numbers(self, value, symbol):
value_mob = TextMobject(value)
width = self.get_width()/self.card_width_to_corner_num_width
height = self.get_height()/self.card_height_to_corner_num_height
value_mob.scale_to_fit_width(width)
value_mob.stretch_to_fit_height(height)
value_mob.next_to(
self.get_corner(UP+LEFT), DOWN+RIGHT,
buff = MED_LARGE_BUFF*width
)
value_mob.set_color(symbol.get_color())
corner_symbol = symbol.copy()
corner_symbol.scale_to_fit_width(width)
corner_symbol.next_to(
value_mob, DOWN,
buff = MED_SMALL_BUFF*width
)
corner_group = VGroup(value_mob, corner_symbol)
opposite_corner_group = corner_group.copy()
opposite_corner_group.rotate(
np.pi, about_point = self.get_center()
)
class BraceLabel(VMobject):
return VGroup(corner_group, opposite_corner_group)
class SuitSymbol(SVGMobject):
CONFIG = {
"label_constructor" : TexMobject,
"label_scale" : 1,
"height" : 0.5,
"fill_opacity" : 1,
"stroke_width" : 0,
"red" : "#D02028",
"black" : BLACK,
}
def __init__(self, obj, text, brace_direction = DOWN, **kwargs):
VMobject.__init__(self, **kwargs)
self.brace_direction = brace_direction
if isinstance(obj, list): obj = VMobject(*obj)
self.brace = Brace(obj, brace_direction, **kwargs)
if isinstance(text, tuple) or isinstance(text, list):
self.label = self.label_constructor(*text, **kwargs)
else: self.label = self.label_constructor(str(text))
if self.label_scale != 1: self.label.scale(self.label_scale)
self.brace.put_at_tip(self.label)
self.submobjects = [self.brace, self.label]
def creation_anim(self, label_anim = FadeIn, brace_anim = GrowFromCenter):
return AnimationGroup(brace_anim(self.brace), label_anim(self.label))
def shift_brace(self, obj, **kwargs):
if isinstance(obj, list): obj = VMobject(*obj)
self.brace = Brace(obj, self.brace_direction, **kwargs)
self.brace.put_at_tip(self.label)
self.submobjects[0] = self.brace
return self
def change_label(self, *text, **kwargs):
self.label = self.label_constructor(*text, **kwargs)
if self.label_scale != 1: self.label.scale(self.label_scale)
self.brace.put_at_tip(self.label)
self.submobjects[1] = self.label
return self
def change_brace_label(self, obj, *text):
self.shift_brace(obj)
self.change_label(*text)
return self
def copy(self):
copy_mobject = copy.copy(self)
copy_mobject.brace = self.brace.copy()
copy_mobject.label = self.label.copy()
copy_mobject.submobjects = [copy_mobject.brace, copy_mobject.label]
return copy_mobject
class BraceText(BraceLabel):
CONFIG = {
"label_constructor" : TextMobject
def __init__(self, suit_name, **kwargs):
digest_config(self, kwargs)
suits_to_colors = {
"hearts" : self.red,
"diamonds" : self.red,
"spades" : self.black,
"clubs" : self.black,
}
if suit_name not in suits_to_colors:
raise Exception("Invalid suit name")
SVGMobject.__init__(self, file_name = suit_name, **kwargs)
color = suits_to_colors[suit_name]
self.set_stroke(width = 0)
self.set_fill(color, 1)
self.scale_to_fit_height(self.height)
class DashedMobject(VMobject):
CONFIG = {
"dashes_num" : 15,
"spacing" : 0.5,
"color" : WHITE
}
def __init__(self, mob, **kwargs):
digest_locals(self)
VMobject.__init__(self, **kwargs)
buff = float(self.spacing) / self.dashes_num
for i in range(self.dashes_num):
a = ((1+buff) * i)/self.dashes_num
b = 1-((1+buff) * (self.dashes_num-1-i)) / self.dashes_num
dash = VMobject(color = self.color)
dash.pointwise_become_partial(mob, a, b)
self.submobjects.append(dash)

View file

@ -1,14 +1,18 @@
from xml.dom import minidom
import itertools as it
import re
import warnings
import string
import warnings
from xml.dom import minidom
from constants import *
from vectorized_mobject import VMobject, VGroup
from topics.geometry import Rectangle, Circle
from mobject.geometry import Circle
from mobject.geometry import Rectangle
from utils.bezier import is_closed
from utils.config_ops import digest_config, digest_locals
from utils.config_ops import digest_config
from utils.config_ops import digest_locals
from mobject.types.vectorized_mobject import VGroup
from mobject.types.vectorized_mobject import VMobject
def string_to_numbers(num_string):
num_string = num_string.replace("-",",-")
@ -227,8 +231,6 @@ class SVGMobject(VMobject):
if self.width is not None:
self.scale_to_fit_width(self.width)
class VMobjectFromSVGPathstring(VMobject):
def __init__(self, path_string, **kwargs):
digest_locals(self)

View file

@ -1,13 +1,16 @@
from constants import *
from vectorized_mobject import VMobject, VGroup, VectorizedPoint
from svg_mobject import SVGMobject, VMobjectFromSVGPathstring
from topics.geometry import BackgroundRectangle
from svg_mobject import SVGMobject
from svg_mobject import VMobjectFromSVGPathstring
from mobject.shape_matchers import BackgroundRectangle
from utils.config_ops import digest_config
from mobject.types.vectorized_mobject import VGroup
from mobject.types.vectorized_mobject import VMobject
from mobject.types.vectorized_mobject import VectorizedPoint
import collections
import sys
import operator as op
import sys
TEX_MOB_SCALE_FACTOR = 0.05
@ -228,68 +231,6 @@ class TextMobject(TexMobject):
"alignment" : "\\centering",
}
class Brace(TexMobject):
CONFIG = {
"buff" : 0.2,
"width_multiplier" : 2,
"max_num_quads" : 15,
"min_num_quads" : 0,
}
def __init__(self, mobject, direction = DOWN, **kwargs):
digest_config(self, kwargs, locals())
angle = -np.arctan2(*direction[:2]) + np.pi
mobject.rotate(-angle, about_point = ORIGIN)
left = mobject.get_corner(DOWN+LEFT)
right = mobject.get_corner(DOWN+RIGHT)
target_width = right[0]-left[0]
## Adding int(target_width) qquads gives approximately the right width
num_quads = np.clip(
int(self.width_multiplier*target_width),
self.min_num_quads, self.max_num_quads
)
tex_string = "\\underbrace{%s}"%(num_quads*"\\qquad")
TexMobject.__init__(self, tex_string, **kwargs)
self.tip_point_index = np.argmin(self.get_all_points()[:,1])
self.stretch_to_fit_width(target_width)
self.shift(left - self.get_corner(UP+LEFT) + self.buff*DOWN)
for mob in mobject, self:
mob.rotate(angle, about_point = ORIGIN)
def put_at_tip(self, mob, use_next_to = True, **kwargs):
if use_next_to:
mob.next_to(
self.get_tip(),
np.round(self.get_direction()),
**kwargs
)
else:
mob.move_to(self.get_tip())
buff = kwargs.get("buff", DEFAULT_MOBJECT_TO_MOBJECT_BUFFER)
shift_distance = mob.get_width()/2.0+buff
mob.shift(self.get_direction()*shift_distance)
return self
def get_text(self, *text, **kwargs):
text_mob = TextMobject(*text)
self.put_at_tip(text_mob, **kwargs)
return text_mob
def get_tex(self, *tex, **kwargs):
tex_mob = TexMobject(*tex)
self.put_at_tip(tex_mob, **kwargs)
return tex_mob
def get_tip(self):
# Very specific to the LaTeX representation
# of a brace, but it's the only way I can think
# of to get the tip regardless of orientation.
return self.get_all_points()[self.tip_point_index]
def get_direction(self):
vect = self.get_tip() - self.get_center()
return vect/np.linalg.norm(vect)
class BulletedList(TextMobject):
CONFIG = {
"buff" : MED_LARGE_BUFF,

View file

@ -0,0 +1,75 @@
from __future__ import absolute_import
from constants import *
from mobject.types.vectorized_mobject import VMobject
from mobject.geometry import Square
from utils.space_ops import z_to_vector
##############
def should_shade_in_3d(mobject):
return hasattr(mobject, "shade_in_3d") and mobject.shade_in_3d
def shade_in_3d(mobject):
for submob in mobject.submobject_family():
submob.shade_in_3d = True
def turn_off_3d_shading(mobject):
for submob in mobject.submobject_family():
submob.shade_in_3d = False
class ThreeDMobject(VMobject):
def __init__(self, *args, **kwargs):
VMobject.__init__(self, *args, **kwargs)
shade_in_3d(self)
class Cube(ThreeDMobject):
CONFIG = {
"fill_opacity" : 0.75,
"fill_color" : BLUE,
"stroke_width" : 0,
"propagate_style_to_family" : True,
"side_length" : 2,
}
def generate_points(self):
for vect in IN, OUT, LEFT, RIGHT, UP, DOWN:
face = Square(side_length = self.side_length)
face.shift(self.side_length*OUT/2.0)
face.apply_function(lambda p : np.dot(p, z_to_vector(vect).T))
self.add(face)
class Prism(Cube):
CONFIG = {
"dimensions" : [3, 2, 1]
}
def generate_points(self):
Cube.generate_points(self)
for dim, value in enumerate(self.dimensions):
self.rescale_to_fit(value, dim, stretch = True)

View file

View file

@ -1,12 +1,15 @@
import numpy as np
from __future__ import absolute_import
import itertools as it
import numpy as np
import os
from PIL import Image
from random import random
from constants import *
from .mobject import Mobject
from point_cloud_mobject import PMobject
from mobject.mobject import Mobject
from utils.bezier import interpolate
from utils.color import color_to_int_rgb
from utils.color import interpolate_color

View file

@ -1,9 +1,13 @@
from __future__ import absolute_import
from mobject.mobject import Mobject
from constants import *
from .mobject import Mobject
from utils.bezier import interpolate
from utils.color import color_to_rgb, color_to_rgba, rgba_to_color
from utils.color import color_gradient
from utils.color import color_to_rgb
from utils.color import color_to_rgba
from utils.color import interpolate_color
from utils.color import rgba_to_color
from utils.config_ops import digest_config
from utils.iterables import stretch_array_to_length

View file

@ -1,10 +1,16 @@
from __future__ import absolute_import
import re
from colour import Color
from mobject.mobject import Mobject
from constants import *
from .mobject import Mobject
from utils.bezier import bezier, partial_bezier_points
from utils.bezier import interpolate, get_smooth_handle_points, is_closed
from utils.bezier import bezier
from utils.bezier import get_smooth_handle_points
from utils.bezier import interpolate
from utils.bezier import is_closed
from utils.bezier import partial_bezier_points
from utils.color import color_to_rgb
from utils.color import interpolate_color
from utils.iterables import make_even
@ -487,3 +493,21 @@ class VectorizedPoint(VMobject):
def set_location(self,new_loc):
self.set_points(np.array([new_loc]))
class DashedMobject(VMobject):
CONFIG = {
"dashes_num" : 15,
"spacing" : 0.5,
"color" : WHITE
}
def __init__(self, mobject, **kwargs):
VMobject.__init__(self, **kwargs)
buff = float(self.spacing) / self.dashes_num
for i in range(self.dashes_num):
a = ((1+buff) * i)/self.dashes_num
b = 1-((1+buff) * (self.dashes_num-1-i)) / self.dashes_num
dash = VMobject(color = self.color)
dash.pointwise_become_partial(mobject, a, b)
self.submobjects.append(dash)

43
mobject/value_tracker.py Normal file
View file

@ -0,0 +1,43 @@
from __future__ import absolute_import
import numpy as np
from constants import *
from mobject.types.vectorized_mobject import VectorizedPoint
# TODO: Rather than using VectorizedPoint, there should be some UndisplayedPointSet type
class ValueTracker(VectorizedPoint):
"""
Note meant to be displayed. Instead the position encodes some
number, often one which another animation or continual_animation
uses for its update function, and by treating it as a mobject it can
still be animated and manipulated just like anything else.
"""
def __init__(self, value = 0, **kwargs):
VectorizedPoint.__init__(self, **kwargs)
self.set_value(value)
def get_value(self):
return self.get_center()[0]
def set_value(self, value):
self.move_to(value*RIGHT)
return self
def increment_value(self, d_value):
self.set_value(self.get_value() + d_value)
class ExponentialValueTracker(ValueTracker):
"""
Operates just like ValueTracker, except it encodes the value as the
exponential of a position coordinate, which changes how interpolation
behaves
"""
def get_value(self):
return np.exp(self.get_center()[0])
def set_value(self, value):
self.move_to(np.log(value)*RIGHT)
return self

View file

@ -1146,7 +1146,7 @@ class TwoDScreenInOurThreeDWorld(AltTeacherStudentsScene, ThreeDScene):
run_time = 4,
added_anims = [MoveToTarget(everything, run_time = 4)],
)
self.add(AmbientRotation(everything, axis = UP, rate = 3*DEGREES))
self.add(ContinualRotation(everything, axis = UP, rate = 3*DEGREES))
self.wait(10)
class EveryOutputPointHasAColor(ColorMappedObjectsScene):
@ -3188,7 +3188,7 @@ class PatreonScroll(Scene):
# patorons = patrons[:10] ##TO remove
scroll = AmbientMovement(patrons, direction = UP, rate = 1)
scroll = ContinualMovement(patrons, direction = UP, rate = 1)
def patrons_opacity_update(patrons):
for patron in patrons:
y = patron.get_center()[1]

View file

@ -1,31 +1,8 @@
#!/usr/bin/env python
from constants import *
from big_ol_pile_of_manim_imports import *
from mobject.tex_mobject import TexMobject
from mobject.mobject import Mobject
from mobject.image_mobject import ImageMobject
from mobject.vectorized_mobject import *
from animation.animation import Animation
from animation.transform import *
from animation.simple_animations import *
from animation.compositions import *
from animation.continual_animation import *
from animation.playground import *
from topics.geometry import *
from topics.characters import *
from topics.functions import *
from topics.number_line import *
from topics.numerals import *
from scene.scene import Scene
from camera.camera import Camera
from mobject.svg_mobject import *
from mobject.tex_mobject import *
from topics.three_dimensions import *
from topics.light import *
from once_useful_constructs.light import *
import types
import functools

View file

@ -2,6 +2,7 @@
# -*- coding: utf-8 -*-
from big_ol_pile_of_manim_imports import *
from once_useful_constructs.light import *
import types
import functools
@ -3847,7 +3848,7 @@ class ThinkBackToHowAmazingThisIs(ThreeDScene):
for n in range(0, self.max_shown_n, 2)
])
zoom_out = AmbientMovement(
zoom_out = ContinualMovement(
self.camera.rotation_mobject,
direction = OUT, rate = 0.4
)

View file

@ -2874,7 +2874,7 @@ class WriteComplexExponentialExpression(DrawFrequencyPlot):
self.wait()
ghost_dot.move_to(ORIGIN)
ambient_ghost_dot_movement = AmbientMovement(
ambient_ghost_dot_movement = ContinualMovement(
ghost_dot, rate = TAU
)
self.add(ambient_ghost_dot_movement)
@ -2896,7 +2896,7 @@ class WriteComplexExponentialExpression(DrawFrequencyPlot):
)
)
ghost_dot.move_to(ORIGIN)
ambient_ghost_dot_movement = AmbientMovement(
ambient_ghost_dot_movement = ContinualMovement(
ghost_dot, rate = 0.1*TAU
)
self.add(ambient_ghost_dot_movement)
@ -4089,7 +4089,6 @@ class SubscribeOrBinge(PiCreatureScene):
)
)
class CloseWithAPuzzle(TeacherStudentsScene):
def construct(self):
self.teacher_says("Close with a puzzle!", run_time = 1)

View file

@ -288,7 +288,7 @@ class AboutToyPuzzles(UtilitiesPuzzleScene, TeacherStudentsScene, ThreeDScene):
eulers.get_bottom(),
color = WHITE
)
self.add(AmbientRotation(cube, axis = UP))
self.add(ContinualRotation(cube, axis = UP))
self.play(
GrowArrow(arrow_to_eulers),
Write(eulers),
@ -1884,7 +1884,7 @@ class EulersFormulaForGeneralPlanarGraph(LightUpNodes, ThreeDScene):
self.play(FadeOut(self.vertices))
self.play(ReplacementTransform(regions, cube, run_time = 2))
cube.sort_submobjects(lambda p : -p[2])
self.add(AmbientRotation(cube, axis = UP, in_place = False))
self.add(ContinualRotation(cube, axis = UP, in_place = False))
self.wait(3)
self.play(
FadeOut(self.top_formula),

View file

@ -7,34 +7,7 @@ import os.path
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
from constants import *
from mobject.tex_mobject import TexMobject
from mobject.mobject import Mobject, Group
from mobject.image_mobject import ImageMobject
from mobject.vectorized_mobject import *
from animation.animation import Animation
from animation.transform import *
from animation.simple_animations import *
from animation.compositions import *
from animation.playground import *
from animation.continual_animation import *
from topics.geometry import *
from topics.characters import *
from topics.functions import *
from topics.fractals import *
from topics.number_line import *
from topics.combinatorics import *
from topics.numerals import *
from topics.three_dimensions import *
from topics.objects import *
from topics.probability import *
from topics.complex_numbers import *
from scene.scene import Scene
from scene.reconfigurable_scene import ReconfigurableScene
from scene.zoomed_scene import *
from camera.camera import Camera
from mobject.svg_mobject import *
from mobject.tex_mobject import *
from big_ol_pile_of_manim_imports import *
from nn.network import *
from nn.part1 import *

View file

@ -7,40 +7,6 @@ from old_projects.fourier import *
FREQUENCY_COLOR = RED
USE_ALMOST_FOURIER_BY_DEFAULT = False
class ValueTracker(VectorizedPoint):
"""
Note meant to be displayed. Instead the position encodes some
number, often one which another animation or continual_animation
uses for its update function, and by treating it as a mobject it can
still be animated and manipulated just like anything else.
"""
def __init__(self, value = 0, **kwargs):
VectorizedPoint.__init__(self, **kwargs)
self.set_value(value)
def get_value(self):
return self.get_center()[0]
def set_value(self, value):
self.move_to(value*RIGHT)
return self
def increment_value(self, d_value):
self.set_value(self.get_value() + d_value)
class ExponentialValueTracker(ValueTracker):
"""
Operates just like ValueTracker, except it encodes the value as the
exponential of a position coordinate, which changes how interpolation
behaves
"""
def get_value(self):
return np.exp(self.get_center()[0])
def set_value(self, value):
self.move_to(np.log(value)*RIGHT)
return self
class GaussianDistributionWrapper(Line):
"""
This is meant to encode a 2d normal distribution as
@ -608,7 +574,7 @@ class ShowPlan(PiCreatureScene):
rect = BackgroundRectangle(wave, fill_opacity = 1)
rect.stretch(2, 1)
rect.next_to(wave, LEFT, buff = 0)
wave_shift = AmbientMovement(
wave_shift = ContinualMovement(
wave, direction = LEFT, rate = 5
)
wave_fader = UpdateFromAlphaFunc(
@ -644,7 +610,7 @@ class ShowPlan(PiCreatureScene):
target = Plane()
# target.match_height(radar_dish)
target.next_to(radar_dish, RIGHT, buff = LARGE_BUFF)
target_movement = AmbientMovement(target, direction = RIGHT, rate = 1.25)
target_movement = ContinualMovement(target, direction = RIGHT, rate = 1.25)
pulse = RadarPulse(radar_dish, target)
@ -1730,7 +1696,7 @@ class MentionDopplerRadar(TeacherStudentsScene):
plane = Plane()
plane.to_edge(RIGHT)
plane.align_to(dish)
plane_flight = AmbientMovement(
plane_flight = ContinualMovement(
plane,
direction = LEFT,
rate = 1,
@ -1911,7 +1877,7 @@ class IntroduceDopplerRadar(Scene):
ShowCreation(sum_graph, run_time = 8, rate_func = None)
)
pulse = RadarPulse(dish, plane, n_pulse_singletons = 12)
plane_flight = AmbientMovement(
plane_flight = ContinualMovement(
plane, direction = LEFT, rate = 1.5
)
@ -2690,7 +2656,7 @@ class AmbiguityInLongEchos(IntroduceDopplerRadar, PiCreatureScene):
object_velocities = self.object_velocities
movements = self.object_movements = [
AmbientMovement(
ContinualMovement(
obj,
direction = v/np.linalg.norm(v),
rate = np.linalg.norm(v)
@ -3366,7 +3332,7 @@ class SortOfDopplerEffect(PiCreatureScene):
t_tracker = VectorizedPoint()
#x-coordinate gives wave number
k_tracker = VectorizedPoint(2*RIGHT)
tk_movement = AmbientMovement(t_tracker, direction = RIGHT, rate = 1)
tk_movement = ContinualMovement(t_tracker, direction = RIGHT, rate = 1)
def get_wave():
t = t_tracker.get_center()[0]
k = k_tracker.get_center()[0]
@ -3399,7 +3365,7 @@ class SortOfDopplerEffect(PiCreatureScene):
rect = ScreenRectangle(height = 2)
rect.to_edge(RIGHT)
rect_movement = AmbientMovement(rect, direction = LEFT, rate = 1)
rect_movement = ContinualMovement(rect, direction = LEFT, rate = 1)
randy = self.pi_creature
randy_look_at = ContinualUpdateFromFunc(
@ -3523,7 +3489,7 @@ class HangingWeightsScene(MovingCameraScene):
k_tracker = self.k_tracker = VectorizedPoint()
t_tracker = self.t_tracker = VectorizedPoint()
self.t_tracker_walk = AmbientMovement(t_tracker, direction = RIGHT, rate = 1)
self.t_tracker_walk = ContinualMovement(t_tracker, direction = RIGHT, rate = 1)
equilibrium_height = springs.get_height()
def update_springs(springs):
for spring in springs:
@ -3637,7 +3603,7 @@ class HangingWeightsScene(MovingCameraScene):
def moving_reference_frame(self):
rect = ScreenRectangle(height = 2.1*FRAME_Y_RADIUS)
rect_movement = AmbientMovement(rect, direction = LEFT, rate = 2)
rect_movement = ContinualMovement(rect, direction = LEFT, rate = 2)
camera_frame = self.camera_frame
self.add(rect)
@ -4371,7 +4337,7 @@ class ThinkOfHeisenbergUncertainty(PiCreatureScene):
self.add()
freq = 1
continual_anims = [
AmbientMovement(time_tracker, direction = RIGHT, rate = 1),
ContinualMovement(time_tracker, direction = RIGHT, rate = 1),
ContinualUpdateFromFunc(
dot_gdw,
lambda d : d.scale_to_fit_width(

View file

@ -0,0 +1 @@
This folder contains a collection of various things that were built for a video at some point, but were really one-off and should be given more careful consideration before being brought into the main library. In particular, there is really no guarantee of these being fully functional.

View file

View file

@ -1,10 +1,10 @@
import numpy as np
import itertools as it
import numpy as np
from constants import *
from scene.scene import Scene
from animation.animation import Animation
from mobject.tex_mobject import TexMobject
from constants import *
from mobject.svg.tex_mobject import TexMobject
from scene.scene import Scene
class RearrangeEquation(Scene):
def construct(

View file

@ -1,7 +1,7 @@
from constants import *
from mobject.vectorized_mobject import VMobject
from mobject.tex_mobject import TexMobject
from mobject.svg.tex_mobject import TexMobject
from mobject.types.vectorized_mobject import VMobject
from scene.scene import Scene
from utils.simple_functions import choose

View file

@ -1,19 +1,12 @@
from constants import *
from mobject.vectorized_mobject import VGroup
from mobject.tex_mobject import TexMobject, TextMobject
from number_line import NumberPlane
from animation.animation import Animation
from animation.transform import ApplyPointwiseFunction, MoveToTarget
from animation.simple_animations import Homotopy, ShowCreation, \
SmoothedVectorizedHomotopy
from animation.movement import SmoothedVectorizedHomotopy
from animation.transform import ApplyPointwiseFunction
from animation.transform import MoveToTarget
from mobject.coordinate_systems import ComplexPlane
from mobject.types.vectorized_mobject import VGroup
from scene.scene import Scene
from utils.config_ops import instantiate
from utils.config_ops import digest_config
from utils.paths import path_along_arc
from utils.space_ops import complex_to_R3, R3_to_complex
class ComplexTransformationScene(Scene):
CONFIG = {
@ -163,110 +156,6 @@ class ComplexTransformationScene(Scene):
*added_anims
)
##### Unsure about what comes under here...
def complex_string(complex_num):
return filter(lambda c : c not in "()", str(complex_num))
class ComplexPlane(NumberPlane):
CONFIG = {
"color" : BLUE,
"unit_size" : 1,
"line_frequency" : 1,
"faded_line_frequency" : 0.5,
}
def __init__(self, **kwargs):
digest_config(self, kwargs)
kwargs.update({
"x_unit_size" : self.unit_size,
"y_unit_size" : self.unit_size,
"x_line_frequency" : self.line_frequency,
"x_faded_line_frequency" : self.faded_line_frequency,
"y_line_frequency" : self.line_frequency,
"y_faded_line_frequency" : self.faded_line_frequency,
})
NumberPlane.__init__(self, **kwargs)
def number_to_point(self, number):
number = complex(number)
return self.coords_to_point(number.real, number.imag)
def point_to_number(self, point):
x, y = self.point_to_coords(point)
return complex(x, y)
def get_coordinate_labels(self, *numbers):
# TODO: Should merge this with the code from NumberPlane.get_coordinate_labels
result = VGroup()
nudge = 0.1*(DOWN+RIGHT)
if len(numbers) == 0:
numbers = range(-int(self.x_radius), int(self.x_radius)+1)
numbers += [
complex(0, y)
for y in range(-int(self.y_radius), int(self.y_radius)+1)
]
for number in numbers:
if number == complex(0, 0):
continue
point = self.number_to_point(number)
num_str = str(number).replace("j", "i")
if num_str.startswith("0"):
num_str = "0"
elif num_str in ["1i", "-1i"]:
num_str = num_str.replace("1", "")
num_mob = TexMobject(num_str)
num_mob.add_background_rectangle()
num_mob.scale_to_fit_height(self.written_coordinate_height)
num_mob.next_to(point, DOWN+LEFT, SMALL_BUFF)
result.add(num_mob)
self.coordinate_labels = result
return result
def add_coordinates(self, *numbers):
self.add(*self.get_coordinate_labels(*numbers))
return self
def add_spider_web(self, circle_freq = 1, angle_freq = np.pi/6):
# This code no longer works because it has this reference to self.fade_factor
# which is never initialized. Shall we delete this little-used function entirely?
self.fade(self.fade_factor)
config = {
"color" : self.color,
"density" : self.density,
}
for radius in np.arange(circle_freq, FRAME_X_RADIUS, circle_freq):
self.add(Circle(radius = radius, **config))
for angle in np.arange(0, 2*np.pi, angle_freq):
end_point = np.cos(angle)*RIGHT + np.sin(angle)*UP
end_point *= FRAME_X_RADIUS
self.add(Line(ORIGIN, end_point, **config))
return self
class ComplexFunction(ApplyPointwiseFunction):
def __init__(self, function, mobject = ComplexPlane, **kwargs):
if "path_func" not in kwargs:
self.path_func = path_along_arc(
np.log(function(complex(1))).imag
)
ApplyPointwiseFunction.__init__(
self,
lambda (x, y, z) : complex_to_R3(function(complex(x, y))),
instantiate(mobject),
**kwargs
)
class ComplexHomotopy(Homotopy):
def __init__(self, complex_homotopy, mobject = ComplexPlane, **kwargs):
"""
Complex Hootopy a function Cx[0, 1] to C
"""
def homotopy(event):
x, y, z, t = event
c = complex_homotopy((complex(x, y), t))
return (c.real, c.imag, z)
Homotopy.__init__(self, homotopy, mobject, *args, **kwargs)

View file

@ -1,13 +1,18 @@
from constants import *
from mobject.tex_mobject import TexMobject, TextMobject
from mobject.mobject import Mobject
from mobject.vectorized_mobject import VMobject, VGroup
from mobject.svg.tex_mobject import TexMobject
from mobject.svg.tex_mobject import TextMobject
from mobject.types.vectorized_mobject import VGroup
from mobject.types.vectorized_mobject import VMobject
from animation.transform import Transform, FadeIn, MoveToTarget
from animation.simple_animations import ShowCreation
from topics.geometry import Arrow, Circle, Dot
# from topics.fractals import *
from animation.creation import ShowCreation
from animation.creation import FadeIn
from animation.transform import MoveToTarget
from animation.transform import Transform
from mobject.geometry import Arrow
from mobject.geometry import Circle
from mobject.geometry import Dot
from scene.scene import Scene

View file

@ -1,18 +1,27 @@
# from mobject.mobject import Mobject, Point, Mobject1D
from mobject.vectorized_mobject import VMobject, VGroup, VectorizedPoint
from scene.scene import Scene
from animation.creation import ShowCreation
from animation.transform import Transform
from animation.simple_animations import ShowCreation
from topics.geometry import Line, Polygon, RegularPolygon, Square, Circle
from characters import PiCreature, Randolph, get_all_pi_creature_modes
from for_3b1b_videos.pi_creature import PiCreature
from for_3b1b_videos.pi_creature import Randolph
from for_3b1b_videos.pi_creature import get_all_pi_creature_modes
from mobject.types.vectorized_mobject import VGroup
from mobject.types.vectorized_mobject import VMobject
from mobject.types.vectorized_mobject import VectorizedPoint
from scene.scene import Scene
from mobject.geometry import Circle
from mobject.geometry import Line
from mobject.geometry import Polygon
from mobject.geometry import RegularPolygon
from mobject.geometry import Square
from utils.bezier import interpolate
from utils.color import color_gradient
from utils.config_ops import digest_config
from utils.space_ops import rotation_matrix, rotate_vector, compass_directions, center_of_mass
from utils.space_ops import center_of_mass
from utils.space_ops import compass_directions
from utils.space_ops import rotate_vector
from utils.space_ops import rotation_matrix
from constants import *
def rotate(points, angle = np.pi, axis = OUT):
if axis is None:
return points
@ -61,7 +70,6 @@ def fractalification_iteration(vmobject, dimension = 1.05, num_inserted_anchors_
]
return vmobject
class SelfSimilarFractal(VMobject):
CONFIG = {
"order" : 5,
@ -106,7 +114,6 @@ class SelfSimilarFractal(VMobject):
def arrange_subparts(self, *subparts):
raise Exception("Not implemented")
class Sierpinski(SelfSimilarFractal):
def get_seed_shape(self):
return Polygon(
@ -118,7 +125,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,
@ -134,7 +140,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,
@ -171,7 +176,6 @@ class PentagonalPiCreatureFractal(PentagonalFractal):
part.rotate(2*np.pi/5, about_point = ORIGIN)
PentagonalFractal.arrange_subparts(self, *subparts)
class PiCreatureFractal(VMobject):
CONFIG = {
"order" : 7,
@ -231,7 +235,6 @@ class PiCreatureFractal(VMobject):
# VMobject.init_colors(self)
# self.set_color_by_gradient(*self.colors)
class WonkyHexagonFractal(SelfSimilarFractal):
CONFIG = {
"num_subparts" : 7
@ -273,8 +276,6 @@ class CircularFractal(SelfSimilarFractal):
part.rotate(i*2*np.pi/self.num_subparts, about_point = ORIGIN)
self.num_subparts -= 1
######## Space filling curves ############
class JaggedCurvePiece(VMobject):
@ -285,7 +286,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,
@ -324,7 +324,6 @@ class FractalCurve(VMobject):
def get_anchor_points(self):
raise Exception("Not implemented")
class LindenmayerCurve(FractalCurve):
CONFIG = {
"axiom" : "A",
@ -365,7 +364,6 @@ class LindenmayerCurve(FractalCurve):
result.append(curr)
return np.array(result) - center_of_mass(result)
class SelfSimilarSpaceFillingCurve(FractalCurve):
CONFIG = {
"offsets" : [],
@ -409,8 +407,6 @@ class SelfSimilarSpaceFillingCurve(FractalCurve):
def generate_grid(self):
raise Exception("Not implemented")
class HilbertCurve(SelfSimilarSpaceFillingCurve):
CONFIG = {
"offsets" : [
@ -425,7 +421,6 @@ class HilbertCurve(SelfSimilarSpaceFillingCurve):
},
}
class HilbertCurve3D(SelfSimilarSpaceFillingCurve):
CONFIG = {
"offsets" : [
@ -461,7 +456,6 @@ class HilbertCurve3D(SelfSimilarSpaceFillingCurve):
copy += offset*self.radius*self.radius_scale_factor
return copy
class PeanoCurve(SelfSimilarSpaceFillingCurve):
CONFIG = {
"colors" : [PURPLE, TEAL],
@ -597,7 +591,6 @@ class KochCurve(KochSnowFlake):
"axiom" : "A--"
}
class QuadraticKoch(LindenmayerCurve):
CONFIG = {
"colors" : [YELLOW, WHITE, MAROON_B],
@ -611,7 +604,6 @@ class QuadraticKoch(LindenmayerCurve):
"angle" : np.pi/2
}
class QuadraticKochIsland(QuadraticKoch):
CONFIG = {
"axiom" : "A+A+A+A"

View file

@ -1,6 +1,7 @@
import itertools as it
import numpy as np
import operator as op
from random import random
from constants import *
@ -29,7 +30,6 @@ class Graph():
def __str__(self):
return self.__class__.__name__
class CubeGraph(Graph):
"""
5 7
@ -66,7 +66,6 @@ class CubeGraph(Graph):
[4, 6, 7, 5],#By convention, last region will be "outside"
]
class SampleGraph(Graph):
"""
4 2 3 8
@ -114,7 +113,6 @@ class SampleGraph(Graph):
(4, 5, 6, 7, 8, 3, 2),
]
class OctohedronGraph(Graph):
"""
3
@ -154,7 +152,6 @@ class OctohedronGraph(Graph):
(3, 4, 5),
]
class CompleteGraph(Graph):
def __init__(self, num_vertices, radius = 3):
self.num_vertices = num_vertices
@ -172,7 +169,6 @@ class CompleteGraph(Graph):
def __str__(self):
return Graph.__str__(self) + str(self.num_vertices)
class GraphScene(Scene):
args_list = [
(CubeGraph(),),

View file

@ -1,31 +1,39 @@
from constants import *
from mobject.tex_mobject import TexMobject
from mobject.geometry import AnnularSector
from mobject.geometry import Arc
from mobject.geometry import Annulus
from mobject.mobject import Mobject
from mobject.vectorized_mobject import *
from mobject.svg.svg_mobject import SVGMobject
from mobject.svg.tex_mobject import TexMobject
from mobject.types.vectorized_mobject import VGroup
from mobject.types.vectorized_mobject import VMobject
from mobject.types.vectorized_mobject import VectorizedPoint
from continual_animation.continual_animation import ContinualAnimation
from animation.animation import Animation
from animation.transform import *
from animation.simple_animations import *
from animation.compositions import *
from animation.continual_animation import *
from animation.composition import LaggedStart
from animation.transform import ApplyMethod
from animation.transform import Transform
from animation.creation import FadeIn
from animation.creation import FadeOut
from animation.playground import *
from topics.geometry import *
from topics.functions import *
from scene.scene import Scene
from camera.camera import Camera
from mobject.svg_mobject import *
from topics.three_dimensions import *
from scene.scene import Scene
from camera.three_d_camera import ThreeDCamera
from scene.three_d_scene import ThreeDScene
from utils.space_ops import angle_between
from utils.space_ops import angle_between_vectors
from utils.space_ops import project_along_vector
from utils.space_ops import rotate_vector
from utils.space_ops import rotation_matrix
from utils.space_ops import z_to_vector
from scipy.spatial import ConvexHull
from traceback import *
from utils.space_ops import rotation_matrix, z_to_vector
from utils.space_ops import rotate_vector, angle_between, angle_between_vectors
from utils.space_ops import project_along_vector
LIGHT_COLOR = YELLOW
SHADOW_COLOR = BLACK
SWITCH_ON_RUN_TIME = 1.5

View file

@ -0,0 +1,140 @@
import numpy as np
from animation.creation import ShowCreation
from animation.transform import ApplyMethod
from animation.creation import FadeOut
from animation.transform import Transform
from mobject.matrix import Matrix
from mobject.svg.tex_mobject import TexMobject
from mobject.types.vectorized_mobject import VGroup
from scene.scene import Scene
from mobject.geometry import Circle
from mobject.geometry import Line
class NumericalMatrixMultiplication(Scene):
CONFIG = {
"left_matrix" : [[1, 2], [3, 4]],
"right_matrix" : [[5, 6], [7, 8]],
"use_parens" : True,
}
def construct(self):
left_string_matrix, right_string_matrix = [
np.array(matrix).astype("string")
for matrix in self.left_matrix, self.right_matrix
]
if right_string_matrix.shape[0] != left_string_matrix.shape[1]:
raise Exception("Incompatible shapes for matrix multiplication")
left = Matrix(left_string_matrix)
right = Matrix(right_string_matrix)
result = self.get_result_matrix(
left_string_matrix, right_string_matrix
)
self.organize_matrices(left, right, result)
self.animate_product(left, right, result)
def get_result_matrix(self, left, right):
(m, k), n = left.shape, right.shape[1]
mob_matrix = np.array([VGroup()]).repeat(m*n).reshape((m, n))
for a in range(m):
for b in range(n):
template = "(%s)(%s)" if self.use_parens else "%s%s"
parts = [
prefix + template%(left[a][c], right[c][b])
for c in range(k)
for prefix in ["" if c == 0 else "+"]
]
mob_matrix[a][b] = TexMobject(parts, next_to_buff = 0.1)
return Matrix(mob_matrix)
def add_lines(self, left, right):
line_kwargs = {
"color" : BLUE,
"stroke_width" : 2,
}
left_rows = [
VGroup(*row) for row in left.get_mob_matrix()
]
h_lines = VGroup()
for row in left_rows[:-1]:
h_line = Line(row.get_left(), row.get_right(), **line_kwargs)
h_line.next_to(row, DOWN, buff = left.v_buff/2.)
h_lines.add(h_line)
right_cols = [
VGroup(*col) for col in np.transpose(right.get_mob_matrix())
]
v_lines = VGroup()
for col in right_cols[:-1]:
v_line = Line(col.get_top(), col.get_bottom(), **line_kwargs)
v_line.next_to(col, RIGHT, buff = right.h_buff/2.)
v_lines.add(v_line)
self.play(ShowCreation(h_lines))
self.play(ShowCreation(v_lines))
self.wait()
self.show_frame()
def organize_matrices(self, left, right, result):
equals = TexMobject("=")
everything = VGroup(left, right, equals, result)
everything.arrange_submobjects()
everything.scale_to_fit_width(FRAME_WIDTH-1)
self.add(everything)
def animate_product(self, left, right, result):
l_matrix = left.get_mob_matrix()
r_matrix = right.get_mob_matrix()
result_matrix = result.get_mob_matrix()
circle = Circle(
radius = l_matrix[0][0].get_height(),
color = GREEN
)
circles = VGroup(*[
entry.get_point_mobject()
for entry in l_matrix[0][0], r_matrix[0][0]
])
(m, k), n = l_matrix.shape, r_matrix.shape[1]
for mob in result_matrix.flatten():
mob.set_color(BLACK)
lagging_anims = []
for a in range(m):
for b in range(n):
for c in range(k):
l_matrix[a][c].set_color(YELLOW)
r_matrix[c][b].set_color(YELLOW)
for c in range(k):
start_parts = VGroup(
l_matrix[a][c].copy(),
r_matrix[c][b].copy()
)
result_entry = result_matrix[a][b].split()[c]
new_circles = VGroup(*[
circle.copy().shift(part.get_center())
for part in start_parts.split()
])
self.play(Transform(circles, new_circles))
self.play(
Transform(
start_parts,
result_entry.copy().set_color(YELLOW),
path_arc = -np.pi/2,
submobject_mode = "all_at_once",
),
*lagging_anims
)
result_entry.set_color(YELLOW)
self.remove(start_parts)
lagging_anims = [
ApplyMethod(result_entry.set_color, WHITE)
]
for c in range(k):
l_matrix[a][c].set_color(WHITE)
r_matrix[c][b].set_color(WHITE)
self.play(FadeOut(circles), *lagging_anims)
self.wait()

View file

@ -8,7 +8,8 @@ from utils.iterables import adjacent_pairs
from constants import *
#TODO, this whole class should be something vectorized.
# Warning: This is all now pretty depricated, and should not be expected to work
class Region(Mobject):
CONFIG = {
"display_mode" : "region"

View file

@ -1,3 +1 @@
__all__ = [
"scene"
]

View file

@ -1,19 +1,27 @@
from __future__ import absolute_import
from constants import *
from scene.scene import Scene
# from topics.geometry import
from mobject.tex_mobject import TexMobject, TextMobject
from mobject.vectorized_mobject import VGroup, VectorizedPoint
from animation.simple_animations import Write, ShowCreation, UpdateFromAlphaFunc
from animation.creation import Write
from animation.transform import Transform
from topics.number_line import NumberLine
from topics.functions import ParametricFunction
from topics.geometry import Rectangle, DashedLine, Line
from animation.update import UpdateFromAlphaFunc
from mobject.functions import ParametricFunction
from mobject.geometry import Line
from mobject.geometry import Rectangle
from mobject.number_line import NumberLine
from mobject.svg.tex_mobject import TexMobject
from mobject.svg.tex_mobject import TextMobject
from mobject.types.vectorized_mobject import VGroup
from mobject.types.vectorized_mobject import VectorizedPoint
from utils.bezier import interpolate
from utils.color import invert_color
from utils.color import color_gradient
from utils.color import invert_color
from utils.space_ops import angle_of_vector
# TODO, this should probably reimplemented entirely, especially so as to
# better reuse code from mobject/coordinate_systems
class GraphScene(Scene):
CONFIG = {
"x_min" : -1,

View file

@ -1,8 +1,10 @@
from __future__ import absolute_import
from constants import *
from camera.camera import MovingCamera
from .scene import Scene
from topics.geometry import ScreenRectangle
from scene.scene import Scene
from camera.moving_camera import MovingCamera
from mobject.frame import ScreenRectangle
class MovingCameraScene(Scene):
def setup(self):

View file

@ -1,6 +1,8 @@
from __future__ import absolute_import
import numpy as np
from .scene import Scene
from scene.scene import Scene
from animation.transform import Transform
from mobject.mobject import Mobject

146
scene/sample_space_scene.py Normal file
View file

@ -0,0 +1,146 @@
from constants import *
from scene.scene import Scene
from animation.animation import Animation
from animation.transform import MoveToTarget
from animation.transform import Transform
from animation.update import UpdateFromFunc
from mobject.types.vectorized_mobject import VGroup
from mobject.probability import SampleSpace
class SampleSpaceScene(Scene):
def get_sample_space(self, **config):
self.sample_space = SampleSpace(**config)
return self.sample_space
def add_sample_space(self, **config):
self.add(self.get_sample_space(**config))
def get_division_change_animations(
self, sample_space, parts, p_list,
dimension = 1,
new_label_kwargs = None,
**kwargs
):
if new_label_kwargs is None:
new_label_kwargs = {}
anims = []
p_list = sample_space.complete_p_list(p_list)
space_copy = sample_space.copy()
vect = DOWN if dimension == 1 else RIGHT
parts.generate_target()
for part, p in zip(parts.target, p_list):
part.replace(space_copy, stretch = True)
part.stretch(p, dimension)
parts.target.arrange_submobjects(vect, buff = 0)
parts.target.move_to(space_copy)
anims.append(MoveToTarget(parts))
if hasattr(parts, "labels") and parts.labels is not None:
label_kwargs = parts.label_kwargs
label_kwargs.update(new_label_kwargs)
new_braces, new_labels = sample_space.get_subdivision_braces_and_labels(
parts.target, **label_kwargs
)
anims += [
Transform(parts.braces, new_braces),
Transform(parts.labels, new_labels),
]
return anims
def get_horizontal_division_change_animations(self, p_list, **kwargs):
assert(hasattr(self.sample_space, "horizontal_parts"))
return self.get_division_change_animations(
self.sample_space, self.sample_space.horizontal_parts, p_list,
dimension = 1,
**kwargs
)
def get_vertical_division_change_animations(self, p_list, **kwargs):
assert(hasattr(self.sample_space, "vertical_parts"))
return self.get_division_change_animations(
self.sample_space, self.sample_space.vertical_parts, p_list,
dimension = 0,
**kwargs
)
def get_conditional_change_anims(
self, sub_sample_space_index, value, post_rects = None,
**kwargs
):
parts = self.sample_space.horizontal_parts
sub_sample_space = parts[sub_sample_space_index]
anims = self.get_division_change_animations(
sub_sample_space, sub_sample_space.vertical_parts, value,
dimension = 0,
**kwargs
)
if post_rects is not None:
anims += self.get_posterior_rectangle_change_anims(post_rects)
return anims
def get_top_conditional_change_anims(self, *args, **kwargs):
return self.get_conditional_change_anims(0, *args, **kwargs)
def get_bottom_conditional_change_anims(self, *args, **kwargs):
return self.get_conditional_change_anims(1, *args, **kwargs)
def get_prior_rectangles(self):
return VGroup(*[
self.sample_space.horizontal_parts[i].vertical_parts[0]
for i in range(2)
])
def get_posterior_rectangles(self, buff = MED_LARGE_BUFF):
prior_rects = self.get_prior_rectangles()
areas = [
rect.get_width()*rect.get_height()
for rect in prior_rects
]
total_area = sum(areas)
total_height = prior_rects.get_height()
post_rects = prior_rects.copy()
for rect, area in zip(post_rects, areas):
rect.stretch_to_fit_height(total_height * area/total_area)
rect.stretch_to_fit_width(
area/rect.get_height()
)
post_rects.arrange_submobjects(DOWN, buff = 0)
post_rects.next_to(
self.sample_space, RIGHT, buff
)
return post_rects
def get_posterior_rectangle_braces_and_labels(
self, post_rects, labels, direction = RIGHT, **kwargs
):
return self.sample_space.get_subdivision_braces_and_labels(
post_rects, labels, direction, **kwargs
)
def update_posterior_braces(self, post_rects):
braces = post_rects.braces
labels = post_rects.labels
for rect, brace, label in zip(post_rects, braces, labels):
brace.stretch_to_fit_height(rect.get_height())
brace.next_to(rect, RIGHT, SMALL_BUFF)
label.next_to(brace, RIGHT, SMALL_BUFF)
def get_posterior_rectangle_change_anims(self, post_rects):
def update_rects(rects):
new_rects = self.get_posterior_rectangles()
Transform(rects, new_rects).update(1)
if hasattr(rects, "braces"):
self.update_posterior_braces(rects)
return rects
anims = [UpdateFromFunc(post_rects, update_rects)]
if hasattr(post_rects, "braces"):
anims += map(Animation, [
post_rects.labels, post_rects.braces
])
return anims

View file

@ -1,26 +1,26 @@
import copy
import inspect
import itertools as it
import numpy as np
import os
import random
import shutil
import subprocess as sp
import time
import warnings
from PIL import Image
from colour import Color
import numpy as np
import itertools as it
import warnings
import time
import os
import shutil
import copy
from tqdm import tqdm as ProgressDisplay
import inspect
import subprocess as sp
import random
from constants import *
from camera.camera import Camera
from tk_scene import TkSceneRoot
from mobject.mobject import Mobject
from mobject.vectorized_mobject import VMobject
from animation.animation import Animation
from animation.transform import MoveToTarget
from animation.continual_animation import ContinualAnimation
from camera.camera import Camera
from continual_animation.continual_animation import ContinualAnimation
from mobject.mobject import Mobject
from mobject.types.vectorized_mobject import VMobject
from utils.iterables import list_update
from container.container import Container
@ -538,9 +538,6 @@ class Scene(Container):
self.update_frame(dont_update_when_skipping = False)
self.get_image().show()
def preview(self):
TkSceneRoot(self)
def get_image_file_path(self, name = None, dont_update = False):
folder = "images"
if dont_update:

View file

@ -1,9 +1,12 @@
import numpy as np
from __future__ import absolute_import
import cv2
import itertools as it
import numpy as np
from tqdm import tqdm as show_progress
from .scene import Scene
from scene.scene import Scene
class SceneFromVideo(Scene):

69
scene/three_d_scene.py Normal file
View file

@ -0,0 +1,69 @@
from __future__ import absolute_import
from constants import *
from continual_animation.continual_animation import ContinualMovement
from animation.transform import ApplyMethod
from camera.three_d_camera import ThreeDCamera
from mobject.types.vectorized_mobject import VGroup
from mobject.three_dimensions import should_shade_in_3d
from scene.scene import Scene
from utils.iterables import list_update
class ThreeDScene(Scene):
CONFIG = {
"camera_class" : ThreeDCamera,
"ambient_camera_rotation" : None,
}
def set_camera_position(self, phi = None, theta = None, distance = None,
center_x = None, center_y = None, center_z = None):
self.camera.set_position(phi, theta, distance, center_x, center_y, center_z)
def begin_ambient_camera_rotation(self, rate = 0.01):
self.ambient_camera_rotation = ContinualMovement(
self.camera.rotation_mobject,
direction = UP,
rate = rate
)
self.add(self.ambient_camera_rotation)
def stop_ambient_camera_rotation(self):
if self.ambient_camera_rotation is not None:
self.remove(self.ambient_camera_rotation)
self.ambient_camera_rotation = None
def move_camera(
self,
phi = None, theta = None, distance = None,
center_x = None, center_y = None, center_z = None,
added_anims = [],
**kwargs
):
target_point = self.camera.get_spherical_coords(phi, theta, distance)
movement = ApplyMethod(
self.camera.rotation_mobject.move_to,
target_point,
**kwargs
)
target_center = self.camera.get_center_of_rotation(center_x, center_y, center_z)
movement_center = ApplyMethod(
self.camera.moving_center.move_to,
target_center,
**kwargs
)
is_camera_rotating = self.ambient_camera_rotation in self.continual_animations
if is_camera_rotating:
self.remove(self.ambient_camera_rotation)
self.play(movement, movement_center, *added_anims)
target_point = self.camera.get_spherical_coords(phi, theta, distance)
if is_camera_rotating:
self.add(self.ambient_camera_rotation)
def get_moving_mobjects(self, *animations):
moving_mobjects = Scene.get_moving_mobjects(self, *animations)
if self.camera.rotation_mobject in moving_mobjects:
return list_update(self.mobjects, moving_mobjects)
return moving_mobjects

View file

@ -1,44 +0,0 @@
import Tkinter
from PIL import ImageTk, Image
import itertools as it
import time
class TkSceneRoot(Tkinter.Tk):
def __init__(self, scene):
if scene.saved_frames == []:
raise Exception(str(scene) + " has no frames!")
Tkinter.Tk.__init__(self)
kwargs = {
"height" : scene.camera.pixel_shape[0],
"width" : scene.camera.pixel_shape[1],
}
self.frame = Tkinter.Frame(self, **kwargs)
self.frame.pack()
self.canvas = Tkinter.Canvas(self.frame, **kwargs)
self.canvas.configure(background='black')
self.canvas.place(x=0, y=0)
last_time = time.time()
for frame in it.cycle(scene.saved_frames):
# try:
# self.show_new_image(frame)
# except:
# break
self.show_new_image(frame)
sleep_time = scene.frame_duration
sleep_time -= time.time() - last_time
time.sleep(max(0, sleep_time))
last_time = time.time()
self.mainloop()
def show_new_image(self, frame):
image = Image.fromarray(frame.astype('uint8')).convert('RGB')
photo = ImageTk.PhotoImage(image)
self.canvas.delete(Tkinter.ALL)
self.canvas.create_image(
0, 0,
image = photo, anchor = Tkinter.NW
)
self.update()

View file

@ -1,28 +1,41 @@
from __future__ import absolute_import
import numpy as np
from scene.scene import Scene
from mobject.mobject import Mobject
from mobject.vectorized_mobject import VMobject, VGroup
from mobject.tex_mobject import TexMobject, TextMobject
from animation.animation import Animation
from animation.transform import ApplyPointwiseFunction, Transform, \
ApplyMethod, FadeOut, ApplyFunction
from animation.simple_animations import ShowCreation, Write
from topics.number_line import NumberPlane, Axes
from topics.geometry import Vector, Line, Circle, Arrow, Dot, \
BackgroundRectangle, Square
from constants import *
from topics.matrix import Matrix, VECTOR_LABEL_SCALE_FACTOR, vector_coordinate_label
from utils.rate_functions import rush_into, rush_from
from utils.space_ops import angle_of_vector
from animation.animation import Animation
from animation.creation import ShowCreation
from animation.creation import Write
from animation.transform import ApplyFunction
from animation.transform import ApplyPointwiseFunction
from animation.creation import FadeOut
from animation.transform import Transform
from mobject.mobject import Mobject
from mobject.svg.tex_mobject import TexMobject
from mobject.svg.tex_mobject import TextMobject
from mobject.types.vectorized_mobject import VGroup
from mobject.types.vectorized_mobject import VMobject
from scene.scene import Scene
from mobject.geometry import Arrow
from mobject.geometry import Dot
from mobject.geometry import Line
from mobject.geometry import Square
from mobject.geometry import Vector
from mobject.coordinate_systems import Axes
from mobject.coordinate_systems import NumberPlane
from mobject.matrix import Matrix
from mobject.matrix import VECTOR_LABEL_SCALE_FACTOR
from mobject.matrix import vector_coordinate_label
from utils.rate_functions import rush_from
from utils.rate_functions import rush_into
from utils.space_ops import angle_of_vector
X_COLOR = GREEN_C
Y_COLOR = RED_C
Z_COLOR = BLUE_D
class VectorScene(Scene):
CONFIG = {
"basis_vector_stroke_width" : 6

View file

@ -1,11 +1,13 @@
from __future__ import absolute_import
import numpy as np
from .scene import Scene
from animation.transform import FadeIn
from mobject.mobject import Mobject
from topics.geometry import Rectangle
from scene.scene import Scene
from animation.creation import FadeIn
from camera.camera import Camera
from camera.camera import MovingCamera
from camera.moving_camera import MovingCamera
from mobject.mobject import Mobject
from mobject.geometry import Rectangle
from constants import *

View file

@ -1,10 +1,13 @@
import sys
import inspect
import itertools as it
import os
import shutil
import itertools as it
from extract_scene import is_scene, get_module
from constants import ANIMATIONS_DIR, STAGED_SCENES_DIR
import sys
from constants import ANIMATIONS_DIR
from constants import STAGED_SCENES_DIR
from extract_scene import get_module
from extract_scene import is_scene
def get_sorted_scene_names(module_name):

View file

@ -1,11 +0,0 @@
__all__ = [
"arithmetic",
"characters",
"combinatorics",
"complex_numbers",
"functions",
"geometry",
"graph_theory",
"number_line",
"three_dimensions",
]

View file

@ -1,751 +0,0 @@
import random
import numpy as np
import itertools as it
from constants import *
from mobject.mobject import Mobject, Group
from mobject.svg_mobject import SVGMobject
from mobject.vectorized_mobject import VMobject, VGroup
from mobject.tex_mobject import TextMobject, TexMobject
from topics.objects import Bubble, ThoughtBubble, SpeechBubble
from topics.geometry import ScreenRectangle
from animation.animation import Animation
from animation.transform import Transform, ApplyMethod, MoveToTarget
from animation.transform import ReplacementTransform, FadeOut, FadeIn
from animation.simple_animations import Write, ShowCreation
from animation.compositions import AnimationGroup
from scene.scene import Scene
from utils.config_ops import digest_config
from utils.rate_functions import there_and_back, squish_rate_func
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,
)

View file

@ -1,268 +0,0 @@
import numpy as np
from scene.scene import Scene
from mobject.mobject import Mobject
from mobject.vectorized_mobject import VMobject, VGroup
from mobject.tex_mobject import TexMobject, TextMobject
from animation.transform import ApplyPointwiseFunction, Transform, \
ApplyMethod, FadeOut, ApplyFunction
from animation.simple_animations import ShowCreation, Write
from topics.number_line import NumberPlane, Axes
from topics.geometry import Vector, Line, Circle, Arrow, Dot, BackgroundRectangle
from constants import *
VECTOR_LABEL_SCALE_FACTOR = 0.8
def matrix_to_tex_string(matrix):
matrix = np.array(matrix).astype("string")
if matrix.ndim == 1:
matrix = matrix.reshape((matrix.size, 1))
n_rows, n_cols = matrix.shape
prefix = "\\left[ \\begin{array}{%s}"%("c"*n_cols)
suffix = "\\end{array} \\right]"
rows = [
" & ".join(row)
for row in matrix
]
return prefix + " \\\\ ".join(rows) + suffix
def matrix_to_mobject(matrix):
return TexMobject(matrix_to_tex_string(matrix))
def vector_coordinate_label(vector_mob, integer_labels = True,
n_dim = 2, color = WHITE):
vect = np.array(vector_mob.get_end())
if integer_labels:
vect = np.round(vect).astype(int)
vect = vect[:n_dim]
vect = vect.reshape((n_dim, 1))
label = Matrix(vect, add_background_rectangles = True)
label.scale(VECTOR_LABEL_SCALE_FACTOR)
shift_dir = np.array(vector_mob.get_end())
if shift_dir[0] >= 0: #Pointing right
shift_dir -= label.get_left() + DEFAULT_MOBJECT_TO_MOBJECT_BUFFER*LEFT
else: #Pointing left
shift_dir -= label.get_right() + DEFAULT_MOBJECT_TO_MOBJECT_BUFFER*RIGHT
label.shift(shift_dir)
label.set_color(color)
label.rect = BackgroundRectangle(label)
label.add_to_back(label.rect)
return label
class Matrix(VMobject):
CONFIG = {
"v_buff" : 0.5,
"h_buff" : 1,
"add_background_rectangles" : False
}
def __init__(self, matrix, **kwargs):
"""
Matrix can either either include numbres, tex_strings,
or mobjects
"""
VMobject.__init__(self, **kwargs)
matrix = np.array(matrix)
if matrix.ndim == 1:
matrix = matrix.reshape((matrix.size, 1))
if not isinstance(matrix[0][0], Mobject):
matrix = matrix.astype("string")
matrix = self.string_matrix_to_mob_matrix(matrix)
self.organize_mob_matrix(matrix)
self.add(*matrix.flatten())
self.add_brackets()
self.center()
self.mob_matrix = matrix
if self.add_background_rectangles:
for mob in matrix.flatten():
mob.add_background_rectangle()
def string_matrix_to_mob_matrix(self, matrix):
return np.array([
map(TexMobject, row)
for row in matrix
]).reshape(matrix.shape)
def organize_mob_matrix(self, matrix):
for i, row in enumerate(matrix):
for j, elem in enumerate(row):
mob = matrix[i][j]
if i == 0 and j == 0:
continue
elif i == 0:
mob.next_to(matrix[i][j-1], RIGHT, self.h_buff)
else:
mob.next_to(matrix[i-1][j], DOWN, self.v_buff)
return self
def add_brackets(self):
bracket_pair = TexMobject("\\big[ \\big]")
bracket_pair.scale(2)
bracket_pair.stretch_to_fit_height(self.get_height() + 0.5)
l_bracket, r_bracket = bracket_pair.split()
l_bracket.next_to(self, LEFT)
r_bracket.next_to(self, RIGHT)
self.add(l_bracket, r_bracket)
self.brackets = VMobject(l_bracket, r_bracket)
return self
def set_color_columns(self, *colors):
for i, color in enumerate(colors):
VMobject(*self.mob_matrix[:,i]).set_color(color)
return self
def add_background_to_entries(self):
for mob in self.get_entries():
mob.add_background_rectangle()
return self
def get_mob_matrix(self):
return self.mob_matrix
def get_entries(self):
return VMobject(*self.get_mob_matrix().flatten())
def get_brackets(self):
return self.brackets
class NumericalMatrixMultiplication(Scene):
CONFIG = {
"left_matrix" : [[1, 2], [3, 4]],
"right_matrix" : [[5, 6], [7, 8]],
"use_parens" : True,
}
def construct(self):
left_string_matrix, right_string_matrix = [
np.array(matrix).astype("string")
for matrix in self.left_matrix, self.right_matrix
]
if right_string_matrix.shape[0] != left_string_matrix.shape[1]:
raise Exception("Incompatible shapes for matrix multiplication")
left = Matrix(left_string_matrix)
right = Matrix(right_string_matrix)
result = self.get_result_matrix(
left_string_matrix, right_string_matrix
)
self.organize_matrices(left, right, result)
self.animate_product(left, right, result)
def get_result_matrix(self, left, right):
(m, k), n = left.shape, right.shape[1]
mob_matrix = np.array([VMobject()]).repeat(m*n).reshape((m, n))
for a in range(m):
for b in range(n):
template = "(%s)(%s)" if self.use_parens else "%s%s"
parts = [
prefix + template%(left[a][c], right[c][b])
for c in range(k)
for prefix in ["" if c == 0 else "+"]
]
mob_matrix[a][b] = TexMobject(parts, next_to_buff = 0.1)
return Matrix(mob_matrix)
def add_lines(self, left, right):
line_kwargs = {
"color" : BLUE,
"stroke_width" : 2,
}
left_rows = [
VMobject(*row) for row in left.get_mob_matrix()
]
h_lines = VMobject()
for row in left_rows[:-1]:
h_line = Line(row.get_left(), row.get_right(), **line_kwargs)
h_line.next_to(row, DOWN, buff = left.v_buff/2.)
h_lines.add(h_line)
right_cols = [
VMobject(*col) for col in np.transpose(right.get_mob_matrix())
]
v_lines = VMobject()
for col in right_cols[:-1]:
v_line = Line(col.get_top(), col.get_bottom(), **line_kwargs)
v_line.next_to(col, RIGHT, buff = right.h_buff/2.)
v_lines.add(v_line)
self.play(ShowCreation(h_lines))
self.play(ShowCreation(v_lines))
self.wait()
self.show_frame()
def organize_matrices(self, left, right, result):
equals = TexMobject("=")
everything = VMobject(left, right, equals, result)
everything.arrange_submobjects()
everything.scale_to_fit_width(FRAME_WIDTH-1)
self.add(everything)
def animate_product(self, left, right, result):
l_matrix = left.get_mob_matrix()
r_matrix = right.get_mob_matrix()
result_matrix = result.get_mob_matrix()
circle = Circle(
radius = l_matrix[0][0].get_height(),
color = GREEN
)
circles = VMobject(*[
entry.get_point_mobject()
for entry in l_matrix[0][0], r_matrix[0][0]
])
(m, k), n = l_matrix.shape, r_matrix.shape[1]
for mob in result_matrix.flatten():
mob.set_color(BLACK)
lagging_anims = []
for a in range(m):
for b in range(n):
for c in range(k):
l_matrix[a][c].set_color(YELLOW)
r_matrix[c][b].set_color(YELLOW)
for c in range(k):
start_parts = VMobject(
l_matrix[a][c].copy(),
r_matrix[c][b].copy()
)
result_entry = result_matrix[a][b].split()[c]
new_circles = VMobject(*[
circle.copy().shift(part.get_center())
for part in start_parts.split()
])
self.play(Transform(circles, new_circles))
self.play(
Transform(
start_parts,
result_entry.copy().set_color(YELLOW),
path_arc = -np.pi/2,
submobject_mode = "all_at_once",
),
*lagging_anims
)
result_entry.set_color(YELLOW)
self.remove(start_parts)
lagging_anims = [
ApplyMethod(result_entry.set_color, WHITE)
]
for c in range(k):
l_matrix[a][c].set_color(WHITE)
r_matrix[c][b].set_color(WHITE)
self.play(FadeOut(circles), *lagging_anims)
self.wait()

View file

@ -1,167 +0,0 @@
from mobject.vectorized_mobject import VMobject, VGroup, VectorizedPoint
from mobject.tex_mobject import TexMobject
from animation.animation import Animation
from animation.continual_animation import ContinualAnimation
from topics.geometry import BackgroundRectangle
from scene.scene import Scene
from constants import *
from utils.bezier import interpolate
from utils.config_ops import digest_config
class DecimalNumber(VMobject):
CONFIG = {
"num_decimal_points" : 2,
"digit_to_digit_buff" : 0.05,
"show_ellipsis" : False,
"unit" : None, #Aligned to bottom unless it starts with "^"
"include_background_rectangle" : False,
}
def __init__(self, number, **kwargs):
VMobject.__init__(self, **kwargs)
self.number = number
ndp = self.num_decimal_points
#Build number string
if isinstance(number, complex):
num_string = '%.*f%s%.*fi'%(
ndp, number.real,
"-" if number.imag < 0 else "+",
ndp, abs(number.imag)
)
else:
num_string = '%.*f'%(ndp, number)
negative_zero_string = "-%.*f"%(ndp, 0.)
if num_string == negative_zero_string:
num_string = num_string[1:]
self.add(*[
TexMobject(char, **kwargs)
for char in num_string
])
#Add non-numerical bits
if self.show_ellipsis:
self.add(TexMobject("\\dots"))
if num_string.startswith("-"):
minus = self.submobjects[0]
minus.next_to(
self.submobjects[1], LEFT,
buff = self.digit_to_digit_buff
)
if self.unit != None:
self.unit_sign = TexMobject(self.unit)
self.add(self.unit_sign)
self.arrange_submobjects(
buff = self.digit_to_digit_buff,
aligned_edge = DOWN
)
#Handle alignment of parts that should be aligned
#to the bottom
for i, c in enumerate(num_string):
if c == "-" and len(num_string) > i+1:
self[i].align_to(self[i+1], alignment_vect = UP)
if self.unit and self.unit.startswith("^"):
self.unit_sign.align_to(self, UP)
#
if self.include_background_rectangle:
self.add_background_rectangle()
def add_background_rectangle(self):
#TODO, is this the best way to handle
#background rectangles?
self.background_rectangle = BackgroundRectangle(self)
self.submobjects = [
self.background_rectangle,
VGroup(*self.submobjects)
]
return self
class Integer(DecimalNumber):
CONFIG = {
"num_decimal_points" : 0,
}
class ChangingDecimal(Animation):
CONFIG = {
"num_decimal_points" : None,
"show_ellipsis" : None,
"position_update_func" : None,
"tracked_mobject" : None,
}
def __init__(self, decimal_number_mobject, number_update_func, **kwargs):
digest_config(self, kwargs, locals())
self.decimal_number_config = dict(
decimal_number_mobject.initial_config
)
for attr in "num_decimal_points", "show_ellipsis":
value = getattr(self, attr)
if value is not None:
self.decimal_number_config[attr] = value
if hasattr(self.decimal_number_mobject, "background_rectangle"):
self.decimal_number_config["include_background_rectangle"] = True
if self.tracked_mobject:
dmc = decimal_number_mobject.get_center()
tmc = self.tracked_mobject.get_center()
self.diff_from_tracked_mobject = dmc - tmc
Animation.__init__(self, decimal_number_mobject, **kwargs)
def update_mobject(self, alpha):
self.update_number(alpha)
self.update_position()
def update_number(self, alpha):
decimal = self.decimal_number_mobject
new_number = self.number_update_func(alpha)
new_decimal = DecimalNumber(
new_number, **self.decimal_number_config
)
new_decimal.match_height(decimal)
new_decimal.move_to(decimal)
new_decimal.match_style(decimal)
decimal.submobjects = new_decimal.submobjects
decimal.number = new_number
def update_position(self):
if self.position_update_func is not None:
self.position_update_func(self.decimal_number_mobject)
elif self.tracked_mobject is not None:
self.decimal_number_mobject.move_to(self.tracked_mobject.get_center() + self.diff_from_tracked_mobject)
class ChangeDecimalToValue(ChangingDecimal):
def __init__(self, decimal_number_mobject, target_number, **kwargs):
start_number = decimal_number_mobject.number
func = lambda alpha : interpolate(start_number, target_number, alpha)
ChangingDecimal.__init__(self, decimal_number_mobject, func, **kwargs)
class ContinualChangingDecimal(ContinualAnimation):
def __init__(self, decimal_number_mobject, number_update_func, **kwargs):
self.anim = ChangingDecimal(decimal_number_mobject, number_update_func, **kwargs)
ContinualAnimation.__init__(self, decimal_number_mobject, **kwargs)
def update_mobject(self, dt):
self.anim.update(self.internal_time)

View file

@ -1,642 +0,0 @@
from constants import *
from scene.scene import Scene
from animation.animation import Animation
from animation.transform import Transform, MoveToTarget
from animation.simple_animations import UpdateFromFunc
from mobject.mobject import Mobject
from mobject.vectorized_mobject import VGroup, VMobject, VectorizedPoint
from mobject.svg_mobject import SVGMobject
from mobject.tex_mobject import TextMobject, TexMobject, Brace
from topics.geometry import Circle, Line, Rectangle, Square, Arc, Polygon
from utils.bezier import interpolate
from utils.color import color_gradient, average_color
from utils.config_ops import digest_config
from utils.iterables import tuplify
from utils.space_ops import center_of_mass
EPSILON = 0.0001
class SampleSpaceScene(Scene):
def get_sample_space(self, **config):
self.sample_space = SampleSpace(**config)
return self.sample_space
def add_sample_space(self, **config):
self.add(self.get_sample_space(**config))
def get_division_change_animations(
self, sample_space, parts, p_list,
dimension = 1,
new_label_kwargs = None,
**kwargs
):
if new_label_kwargs is None:
new_label_kwargs = {}
anims = []
p_list = sample_space.complete_p_list(p_list)
space_copy = sample_space.copy()
vect = DOWN if dimension == 1 else RIGHT
parts.generate_target()
for part, p in zip(parts.target, p_list):
part.replace(space_copy, stretch = True)
part.stretch(p, dimension)
parts.target.arrange_submobjects(vect, buff = 0)
parts.target.move_to(space_copy)
anims.append(MoveToTarget(parts))
if hasattr(parts, "labels") and parts.labels is not None:
label_kwargs = parts.label_kwargs
label_kwargs.update(new_label_kwargs)
new_braces, new_labels = sample_space.get_subdivision_braces_and_labels(
parts.target, **label_kwargs
)
anims += [
Transform(parts.braces, new_braces),
Transform(parts.labels, new_labels),
]
return anims
def get_horizontal_division_change_animations(self, p_list, **kwargs):
assert(hasattr(self.sample_space, "horizontal_parts"))
return self.get_division_change_animations(
self.sample_space, self.sample_space.horizontal_parts, p_list,
dimension = 1,
**kwargs
)
def get_vertical_division_change_animations(self, p_list, **kwargs):
assert(hasattr(self.sample_space, "vertical_parts"))
return self.get_division_change_animations(
self.sample_space, self.sample_space.vertical_parts, p_list,
dimension = 0,
**kwargs
)
def get_conditional_change_anims(
self, sub_sample_space_index, value, post_rects = None,
**kwargs
):
parts = self.sample_space.horizontal_parts
sub_sample_space = parts[sub_sample_space_index]
anims = self.get_division_change_animations(
sub_sample_space, sub_sample_space.vertical_parts, value,
dimension = 0,
**kwargs
)
if post_rects is not None:
anims += self.get_posterior_rectangle_change_anims(post_rects)
return anims
def get_top_conditional_change_anims(self, *args, **kwargs):
return self.get_conditional_change_anims(0, *args, **kwargs)
def get_bottom_conditional_change_anims(self, *args, **kwargs):
return self.get_conditional_change_anims(1, *args, **kwargs)
def get_prior_rectangles(self):
return VGroup(*[
self.sample_space.horizontal_parts[i].vertical_parts[0]
for i in range(2)
])
def get_posterior_rectangles(self, buff = MED_LARGE_BUFF):
prior_rects = self.get_prior_rectangles()
areas = [
rect.get_width()*rect.get_height()
for rect in prior_rects
]
total_area = sum(areas)
total_height = prior_rects.get_height()
post_rects = prior_rects.copy()
for rect, area in zip(post_rects, areas):
rect.stretch_to_fit_height(total_height * area/total_area)
rect.stretch_to_fit_width(
area/rect.get_height()
)
post_rects.arrange_submobjects(DOWN, buff = 0)
post_rects.next_to(
self.sample_space, RIGHT, buff
)
return post_rects
def get_posterior_rectangle_braces_and_labels(
self, post_rects, labels, direction = RIGHT, **kwargs
):
return self.sample_space.get_subdivision_braces_and_labels(
post_rects, labels, direction, **kwargs
)
def update_posterior_braces(self, post_rects):
braces = post_rects.braces
labels = post_rects.labels
for rect, brace, label in zip(post_rects, braces, labels):
brace.stretch_to_fit_height(rect.get_height())
brace.next_to(rect, RIGHT, SMALL_BUFF)
label.next_to(brace, RIGHT, SMALL_BUFF)
def get_posterior_rectangle_change_anims(self, post_rects):
def update_rects(rects):
new_rects = self.get_posterior_rectangles()
Transform(rects, new_rects).update(1)
if hasattr(rects, "braces"):
self.update_posterior_braces(rects)
return rects
anims = [UpdateFromFunc(post_rects, update_rects)]
if hasattr(post_rects, "braces"):
anims += map(Animation, [
post_rects.labels, post_rects.braces
])
return anims
class SampleSpace(Rectangle):
CONFIG = {
"height" : 3,
"width" : 3,
"fill_color" : DARK_GREY,
"fill_opacity" : 1,
"stroke_width" : 0.5,
"stroke_color" : LIGHT_GREY,
##
"default_label_scale_val" : 1,
}
def add_title(self, title = "Sample space", buff = MED_SMALL_BUFF):
##TODO, should this really exist in SampleSpaceScene
title_mob = TextMobject(title)
if title_mob.get_width() > self.get_width():
title_mob.scale_to_fit_width(self.get_width())
title_mob.next_to(self, UP, buff = buff)
self.title = title_mob
self.add(title_mob)
def add_label(self, label):
self.label = label
def complete_p_list(self, p_list):
new_p_list = list(tuplify(p_list))
remainder = 1.0 - sum(new_p_list)
if abs(remainder) > EPSILON:
new_p_list.append(remainder)
return new_p_list
def get_division_along_dimension(self, p_list, dim, colors, vect):
p_list = self.complete_p_list(p_list)
colors = color_gradient(colors, len(p_list))
last_point = self.get_edge_center(-vect)
parts = VGroup()
for factor, color in zip(p_list, colors):
part = SampleSpace()
part.set_fill(color, 1)
part.replace(self, stretch = True)
part.stretch(factor, dim)
part.move_to(last_point, -vect)
last_point = part.get_edge_center(vect)
parts.add(part)
return parts
def get_horizontal_division(
self, p_list,
colors = [GREEN_E, BLUE_E],
vect = DOWN
):
return self.get_division_along_dimension(p_list, 1, colors, vect)
def get_vertical_division(
self, p_list,
colors = [MAROON_B, YELLOW],
vect = RIGHT
):
return self.get_division_along_dimension(p_list, 0, colors, vect)
def divide_horizontally(self, *args, **kwargs):
self.horizontal_parts = self.get_horizontal_division(*args, **kwargs)
self.add(self.horizontal_parts)
def divide_vertically(self, *args, **kwargs):
self.vertical_parts = self.get_vertical_division(*args, **kwargs)
self.add(self.vertical_parts)
def get_subdivision_braces_and_labels(
self, parts, labels, direction,
buff = SMALL_BUFF,
min_num_quads = 1
):
label_mobs = VGroup()
braces = VGroup()
for label, part in zip(labels, parts):
brace = Brace(
part, direction,
min_num_quads = min_num_quads,
buff = buff
)
if isinstance(label, Mobject):
label_mob = label
else:
label_mob = TexMobject(label)
label_mob.scale(self.default_label_scale_val)
label_mob.next_to(brace, direction, buff)
braces.add(brace)
label_mobs.add(label_mob)
parts.braces = braces
parts.labels = label_mobs
parts.label_kwargs = {
"labels" : label_mobs.copy(),
"direction" : direction,
"buff" : buff,
}
return VGroup(parts.braces, parts.labels)
def get_side_braces_and_labels(self, labels, direction = LEFT, **kwargs):
assert(hasattr(self, "horizontal_parts"))
parts = self.horizontal_parts
return self.get_subdivision_braces_and_labels(parts, labels, direction, **kwargs)
def get_top_braces_and_labels(self, labels, **kwargs):
assert(hasattr(self, "vertical_parts"))
parts = self.vertical_parts
return self.get_subdivision_braces_and_labels(parts, labels, UP, **kwargs)
def get_bottom_braces_and_labels(self, labels, **kwargs):
assert(hasattr(self, "vertical_parts"))
parts = self.vertical_parts
return self.get_subdivision_braces_and_labels(parts, labels, DOWN, **kwargs)
def add_braces_and_labels(self):
for attr in "horizontal_parts", "vertical_parts":
if not hasattr(self, attr):
continue
parts = getattr(self, attr)
for subattr in "braces", "labels":
if hasattr(parts, subattr):
self.add(getattr(parts, subattr))
def __getitem__(self, index):
if hasattr(self, "horizontal_parts"):
return self.horizontal_parts[index]
elif hasattr(self, "vertical_parts"):
return self.vertical_parts[index]
return self.split()[index]
class BarChart(VGroup):
CONFIG = {
"height" : 4,
"width" : 6,
"n_ticks" : 4,
"tick_width" : 0.2,
"label_y_axis" : True,
"y_axis_label_height" : 0.25,
"max_value" : 1,
"bar_colors" : [BLUE, YELLOW],
"bar_fill_opacity" : 0.8,
"bar_stroke_width" : 3,
"bar_names" : [],
"bar_label_scale_val" : 0.75,
}
def __init__(self, values, **kwargs):
VGroup.__init__(self, **kwargs)
if self.max_value is None:
self.max_value = max(values)
self.add_axes()
self.add_bars(values)
self.center()
def add_axes(self):
x_axis = Line(self.tick_width*LEFT/2, self.width*RIGHT)
y_axis = Line(MED_LARGE_BUFF*DOWN, self.height*UP)
ticks = VGroup()
heights = np.linspace(0, self.height, self.n_ticks+1)
values = np.linspace(0, self.max_value, self.n_ticks+1)
for y, value in zip(heights, values):
tick = Line(LEFT, RIGHT)
tick.scale_to_fit_width(self.tick_width)
tick.move_to(y*UP)
ticks.add(tick)
y_axis.add(ticks)
self.add(x_axis, y_axis)
self.x_axis, self.y_axis = x_axis, y_axis
if self.label_y_axis:
labels = VGroup()
for tick, value in zip(ticks, values):
label = TexMobject(str(np.round(value, 2)))
label.scale_to_fit_height(self.y_axis_label_height)
label.next_to(tick, LEFT, SMALL_BUFF)
labels.add(label)
self.y_axis_labels = labels
self.add(labels)
def add_bars(self, values):
buff = float(self.width) / (2*len(values) + 1)
bars = VGroup()
for i, value in enumerate(values):
bar = Rectangle(
height = (value/self.max_value)*self.height,
width = buff,
stroke_width = self.bar_stroke_width,
fill_opacity = self.bar_fill_opacity,
)
bar.move_to((2*i+1)*buff*RIGHT, DOWN+LEFT)
bars.add(bar)
bars.set_color_by_gradient(*self.bar_colors)
bar_labels = VGroup()
for bar, name in zip(bars, self.bar_names):
label = TexMobject(str(name))
label.scale(self.bar_label_scale_val)
label.next_to(bar, DOWN, SMALL_BUFF)
bar_labels.add(label)
self.add(bars, bar_labels)
self.bars = bars
self.bar_labels = bar_labels
def change_bar_values(self, values):
for bar, value in zip(self.bars, values):
bar_bottom = bar.get_bottom()
bar.stretch_to_fit_height(
(value/self.max_value)*self.height
)
bar.move_to(bar_bottom, DOWN)
def copy(self):
return self.deepcopy()
### Cards ###
class DeckOfCards(VGroup):
def __init__(self, **kwargs):
possible_values = map(str, range(1, 11)) + ["J", "Q", "K"]
possible_suits = ["hearts", "diamonds", "spades", "clubs"]
VGroup.__init__(self, *[
PlayingCard(value = value, suit = suit, **kwargs)
for value in possible_values
for suit in possible_suits
])
class PlayingCard(VGroup):
CONFIG = {
"value" : None,
"suit" : None,
"key" : None, ##String like "8H" or "KS"
"height" : 2,
"height_to_width" : 3.5/2.5,
"card_height_to_symbol_height" : 7,
"card_width_to_corner_num_width" : 10,
"card_height_to_corner_num_height" : 10,
"color" : LIGHT_GREY,
"turned_over" : False,
"possible_suits" : ["hearts", "diamonds", "spades", "clubs"],
"possible_values" : map(str, range(2, 11)) + ["J", "Q", "K", "A"],
}
def __init__(self, key = None, **kwargs):
VGroup.__init__(self, key = key, **kwargs)
def generate_points(self):
self.add(Rectangle(
height = self.height,
width = self.height/self.height_to_width,
stroke_color = WHITE,
stroke_width = 2,
fill_color = self.color,
fill_opacity = 1,
))
if self.turned_over:
self.set_fill(DARK_GREY)
self.set_stroke(LIGHT_GREY)
contents = VectorizedPoint(self.get_center())
else:
value = self.get_value()
symbol = self.get_symbol()
design = self.get_design(value, symbol)
corner_numbers = self.get_corner_numbers(value, symbol)
contents = VGroup(design, corner_numbers)
self.design = design
self.corner_numbers = corner_numbers
self.add(contents)
def get_value(self):
value = self.value
if value is None:
if self.key is not None:
value = self.key[:-1]
else:
value = random.choice(self.possible_values)
value = string.upper(str(value))
if value == "1":
value = "A"
if value not in self.possible_values:
raise Exception("Invalid card value")
face_card_to_value = {
"J" : 11,
"Q" : 12,
"K" : 13,
"A" : 14,
}
try:
self.numerical_value = int(value)
except:
self.numerical_value = face_card_to_value[value]
return value
def get_symbol(self):
suit = self.suit
if suit is None:
if self.key is not None:
suit = dict([
(string.upper(s[0]), s)
for s in self.possible_suits
])[string.upper(self.key[-1])]
else:
suit = random.choice(self.possible_suits)
if suit not in self.possible_suits:
raise Exception("Invalud suit value")
self.suit = suit
symbol_height = float(self.height) / self.card_height_to_symbol_height
symbol = SuitSymbol(suit, height = symbol_height)
return symbol
def get_design(self, value, symbol):
if value == "A":
return self.get_ace_design(symbol)
if value in map(str, range(2, 11)):
return self.get_number_design(value, symbol)
else:
return self.get_face_card_design(value, symbol)
def get_ace_design(self, symbol):
design = symbol.copy().scale(1.5)
design.move_to(self)
return design
def get_number_design(self, value, symbol):
num = int(value)
n_rows = {
2 : 2,
3 : 3,
4 : 2,
5 : 2,
6 : 3,
7 : 3,
8 : 3,
9 : 4,
10 : 4,
}[num]
n_cols = 1 if num in [2, 3] else 2
insertion_indices = {
5 : [0],
7 : [0],
8 : [0, 1],
9 : [1],
10 : [0, 2],
}.get(num, [])
top = self.get_top() + symbol.get_height()*DOWN
bottom = self.get_bottom() + symbol.get_height()*UP
column_points = [
interpolate(top, bottom, alpha)
for alpha in np.linspace(0, 1, n_rows)
]
design = VGroup(*[
symbol.copy().move_to(point)
for point in column_points
])
if n_cols == 2:
space = 0.2*self.get_width()
column_copy = design.copy().shift(space*RIGHT)
design.shift(space*LEFT)
design.add(*column_copy)
design.add(*[
symbol.copy().move_to(
center_of_mass(column_points[i:i+2])
)
for i in insertion_indices
])
for symbol in design:
if symbol.get_center()[1] < self.get_center()[1]:
symbol.rotate_in_place(np.pi)
return design
def get_face_card_design(self, value, symbol):
from topics.characters import PiCreature
sub_rect = Rectangle(
stroke_color = BLACK,
fill_opacity = 0,
height = 0.9*self.get_height(),
width = 0.6*self.get_width(),
)
sub_rect.move_to(self)
# pi_color = average_color(symbol.get_color(), GREY)
pi_color = symbol.get_color()
pi_mode = {
"J" : "plain",
"Q" : "thinking",
"K" : "hooray"
}[value]
pi_creature = PiCreature(
mode = pi_mode,
color = pi_color,
)
pi_creature.scale_to_fit_width(0.8*sub_rect.get_width())
if value in ["Q", "K"]:
prefix = "king" if value == "K" else "queen"
crown = SVGMobject(file_name = prefix + "_crown")
crown.set_stroke(width = 0)
crown.set_fill(YELLOW, 1)
crown.stretch_to_fit_width(0.5*sub_rect.get_width())
crown.stretch_to_fit_height(0.17*sub_rect.get_height())
crown.move_to(pi_creature.eyes.get_center(), DOWN)
pi_creature.add_to_back(crown)
to_top_buff = 0
else:
to_top_buff = SMALL_BUFF*sub_rect.get_height()
pi_creature.next_to(sub_rect.get_top(), DOWN, to_top_buff)
# pi_creature.shift(0.05*sub_rect.get_width()*RIGHT)
pi_copy = pi_creature.copy()
pi_copy.rotate(np.pi, about_point = sub_rect.get_center())
return VGroup(sub_rect, pi_creature, pi_copy)
def get_corner_numbers(self, value, symbol):
value_mob = TextMobject(value)
width = self.get_width()/self.card_width_to_corner_num_width
height = self.get_height()/self.card_height_to_corner_num_height
value_mob.scale_to_fit_width(width)
value_mob.stretch_to_fit_height(height)
value_mob.next_to(
self.get_corner(UP+LEFT), DOWN+RIGHT,
buff = MED_LARGE_BUFF*width
)
value_mob.set_color(symbol.get_color())
corner_symbol = symbol.copy()
corner_symbol.scale_to_fit_width(width)
corner_symbol.next_to(
value_mob, DOWN,
buff = MED_SMALL_BUFF*width
)
corner_group = VGroup(value_mob, corner_symbol)
opposite_corner_group = corner_group.copy()
opposite_corner_group.rotate(
np.pi, about_point = self.get_center()
)
return VGroup(corner_group, opposite_corner_group)
class SuitSymbol(SVGMobject):
CONFIG = {
"height" : 0.5,
"fill_opacity" : 1,
"stroke_width" : 0,
"red" : "#D02028",
"black" : BLACK,
}
def __init__(self, suit_name, **kwargs):
digest_config(self, kwargs)
suits_to_colors = {
"hearts" : self.red,
"diamonds" : self.red,
"spades" : self.black,
"clubs" : self.black,
}
if suit_name not in suits_to_colors:
raise Exception("Invalid suit name")
SVGMobject.__init__(self, file_name = suit_name, **kwargs)
color = suits_to_colors[suit_name]
self.set_stroke(width = 0)
self.set_fill(color, 1)
self.scale_to_fit_height(self.height)

View file

@ -1,4 +1,5 @@
import numpy as np
from scipy import linalg
from utils.simple_functions import choose

View file

@ -1,7 +1,10 @@
from colour import Color
import numpy as np
import random
from colour import Color
from constants import WHITE
from constants import PALETTE
from utils.bezier import interpolate
def color_to_rgb(color):

View file

@ -1,7 +1,8 @@
import numpy as np
import os
from PIL import Image
from constants import RASTER_IMAGE_DIR
import os
def get_full_raster_image_path(image_file_name):
possible_paths = [

View file

@ -1,5 +1,5 @@
import numpy as np
import itertools as it
import numpy as np
def remove_list_redundancies(l):
"""

View file

@ -1,7 +1,8 @@
import numpy as np
from constants import OUT
from utils.bezier import interpolate
from utils.space_ops import rotation_matrix
from constants import OUT
STRAIGHT_PATH_THRESHOLD = 0.01

View file

@ -1,6 +1,7 @@
import numpy as np
from utils.simple_functions import sigmoid
from utils.bezier import bezier
from utils.simple_functions import sigmoid
def smooth(t, inflection = 10.0):
error = sigmoid(-inflection / 2)

View file

@ -1,5 +1,5 @@
import operator as op
import numpy as np
import operator as op
def sigmoid(x):
return 1.0/(1 + np.exp(-x))

View file

@ -1,5 +1,7 @@
import numpy as np
from constants import RIGHT, OUT
from constants import OUT
from constants import RIGHT
#Matrix operations
@ -99,12 +101,3 @@ def center_of_mass(points):
points = [np.array(point).astype("float") for point in points]
return sum(points) / len(points)
# TODO: It feels like this should live elsewhere

View file

@ -17,3 +17,6 @@ def initials(name, sep_values = [" ", "_"]):
def camel_case_initials(name):
return filter(lambda c : c.isupper(), name)
def complex_string(complex_num):
return filter(lambda c : c not in "()", str(complex_num))