diff --git a/.gitignore b/.gitignore index 3175f56c..87cbc012 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/active_projects/eop/bayes.py b/active_projects/eop/bayes.py index 1890d91b..39b63e58 100644 --- a/active_projects/eop/bayes.py +++ b/active_projects/eop/bayes.py @@ -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 diff --git a/active_projects/eop/bayes_footnote.py b/active_projects/eop/bayes_footnote.py index e3719216..06f4b2b3 100644 --- a/active_projects/eop/bayes_footnote.py +++ b/active_projects/eop/bayes_footnote.py @@ -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 diff --git a/active_projects/eop/combinations.py b/active_projects/eop/combinations.py index ec7d60c1..31e88ce9 100644 --- a/active_projects/eop/combinations.py +++ b/active_projects/eop/combinations.py @@ -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 diff --git a/active_projects/eop/independence.py b/active_projects/eop/independence.py index 208b4a52..48fd868b 100644 --- a/active_projects/eop/independence.py +++ b/active_projects/eop/independence.py @@ -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 diff --git a/animation/__init__.py b/animation/__init__.py index de0b98d3..e69de29b 100644 --- a/animation/__init__.py +++ b/animation/__init__.py @@ -1,5 +0,0 @@ -__all__ = [ - "animation", - "simple_animations", - "transform" -] \ No newline at end of file diff --git a/animation/compositions.py b/animation/composition.py similarity index 89% rename from animation/compositions.py rename to animation/composition.py index 8b26b566..f05aeb64 100644 --- a/animation/compositions.py +++ b/animation/composition.py @@ -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) diff --git a/animation/creation.py b/animation/creation.py new file mode 100644 index 00000000..e345f893 --- /dev/null +++ b/animation/creation.py @@ -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 + ) diff --git a/animation/indication.py b/animation/indication.py new file mode 100644 index 00000000..0a4ad091 --- /dev/null +++ b/animation/indication.py @@ -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) + + diff --git a/animation/movement.py b/animation/movement.py new file mode 100644 index 00000000..201ed779 --- /dev/null +++ b/animation/movement.py @@ -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) + diff --git a/animation/numbers.py b/animation/numbers.py new file mode 100644 index 00000000..6fed7b26 --- /dev/null +++ b/animation/numbers.py @@ -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) diff --git a/animation/playground.py b/animation/playground.py deleted file mode 100644 index f876ba5d..00000000 --- a/animation/playground.py +++ /dev/null @@ -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) - - diff --git a/animation/rotation.py b/animation/rotation.py new file mode 100644 index 00000000..f2118741 --- /dev/null +++ b/animation/rotation.py @@ -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) + diff --git a/animation/simple_animations.py b/animation/simple_animations.py deleted file mode 100644 index 61f9ad0c..00000000 --- a/animation/simple_animations.py +++ /dev/null @@ -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, - } - - - - - diff --git a/animation/specialized.py b/animation/specialized.py new file mode 100644 index 00000000..c12f85ab --- /dev/null +++ b/animation/specialized.py @@ -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 + ) + diff --git a/animation/transform.py b/animation/transform.py index 89d299c8..c684e91e 100644 --- a/animation/transform.py +++ b/animation/transform.py @@ -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) - - diff --git a/animation/update.py b/animation/update.py new file mode 100644 index 00000000..ff3ec543 --- /dev/null +++ b/animation/update.py @@ -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 + ) + + + + + diff --git a/big_ol_pile_of_manim_imports.py b/big_ol_pile_of_manim_imports.py index bd0a9c1b..58ead133 100644 --- a/big_ol_pile_of_manim_imports.py +++ b/big_ol_pile_of_manim_imports.py @@ -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 - diff --git a/camera/camera.py b/camera/camera.py index ea047fa5..c9f4dab0 100644 --- a/camera/camera.py +++ b/camera/camera.py @@ -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))) - - diff --git a/camera/mapping_camera.py b/camera/mapping_camera.py new file mode 100644 index 00000000..66e38adb --- /dev/null +++ b/camera/mapping_camera.py @@ -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))) + diff --git a/camera/moving_camera.py b/camera/moving_camera.py new file mode 100644 index 00000000..f02c4012 --- /dev/null +++ b/camera/moving_camera.py @@ -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) diff --git a/topics/three_dimensions.py b/camera/three_d_camera.py similarity index 61% rename from topics/three_dimensions.py rename to camera/three_d_camera.py index ce02b651..8054bef9 100644 --- a/topics/three_dimensions.py +++ b/camera/three_d_camera.py @@ -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) - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/continual_animation/__init__.py b/continual_animation/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/animation/continual_animation.py b/continual_animation/continual_animation.py similarity index 58% rename from animation/continual_animation.py rename to continual_animation/continual_animation.py index fa3a21dc..814b00c1 100644 --- a/animation/continual_animation.py +++ b/continual_animation/continual_animation.py @@ -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) - - diff --git a/continual_animation/from_animation.py b/continual_animation/from_animation.py new file mode 100644 index 00000000..bcbdc0a2 --- /dev/null +++ b/continual_animation/from_animation.py @@ -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) + diff --git a/continual_animation/numbers.py b/continual_animation/numbers.py new file mode 100644 index 00000000..42679af6 --- /dev/null +++ b/continual_animation/numbers.py @@ -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) + ) diff --git a/continual_animation/update.py b/continual_animation/update.py new file mode 100644 index 00000000..d891c11c --- /dev/null +++ b/continual_animation/update.py @@ -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 diff --git a/extract_scene.py b/extract_scene.py index 5f82c885..32766b46 100644 --- a/extract_scene.py +++ b/extract_scene.py @@ -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"]) diff --git a/for_3b1b_videos/__init__.py b/for_3b1b_videos/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/topics/common_scenes.py b/for_3b1b_videos/common_scenes.py similarity index 89% rename from topics/common_scenes.py rename to for_3b1b_videos/common_scenes.py index b72b75e9..f6646855 100644 --- a/topics/common_scenes.py +++ b/for_3b1b_videos/common_scenes.py @@ -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() - - - - - - - - - - - - - - diff --git a/for_3b1b_videos/pi_creature.py b/for_3b1b_videos/pi_creature.py new file mode 100644 index 00000000..ea688c46 --- /dev/null +++ b/for_3b1b_videos/pi_creature.py @@ -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) + + diff --git a/for_3b1b_videos/pi_creature_animations.py b/for_3b1b_videos/pi_creature_animations.py new file mode 100644 index 00000000..8ee4753b --- /dev/null +++ b/for_3b1b_videos/pi_creature_animations.py @@ -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) diff --git a/for_3b1b_videos/pi_creature_scene.py b/for_3b1b_videos/pi_creature_scene.py new file mode 100644 index 00000000..ba84a55d --- /dev/null +++ b/for_3b1b_videos/pi_creature_scene.py @@ -0,0 +1,362 @@ +from __future__ import absolute_import + +import itertools as it +import numpy as np +import random + +from constants import * + +from mobject.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, + ) + + diff --git a/topics/number_line.py b/mobject/coordinate_systems.py similarity index 69% rename from topics/number_line.py rename to mobject/coordinate_systems.py index 3a67ef85..ece9e3d4 100644 --- a/topics/number_line.py +++ b/mobject/coordinate_systems.py @@ -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 diff --git a/mobject/frame.py b/mobject/frame.py new file mode 100644 index 00000000..0ca977a6 --- /dev/null +++ b/mobject/frame.py @@ -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) diff --git a/topics/functions.py b/mobject/functions.py similarity index 93% rename from topics/functions.py rename to mobject/functions.py index e72a10ed..0d421e66 100644 --- a/topics/functions.py +++ b/mobject/functions.py @@ -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, diff --git a/topics/geometry.py b/mobject/geometry.py similarity index 98% rename from topics/geometry.py rename to mobject/geometry.py index e7fc63d1..e3fee62b 100644 --- a/topics/geometry.py +++ b/mobject/geometry.py @@ -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 = { diff --git a/mobject/matrix.py b/mobject/matrix.py new file mode 100644 index 00000000..cf20f6de --- /dev/null +++ b/mobject/matrix.py @@ -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 + + + + + + + + + + + + diff --git a/mobject/mobject.py b/mobject/mobject.py index 16731950..1e67793b 100644 --- a/mobject/mobject.py +++ b/mobject/mobject.py @@ -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 diff --git a/mobject/number_line.py b/mobject/number_line.py new file mode 100644 index 00000000..ba332f51 --- /dev/null +++ b/mobject/number_line.py @@ -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, + } + diff --git a/mobject/numbers.py b/mobject/numbers.py new file mode 100644 index 00000000..bb50c070 --- /dev/null +++ b/mobject/numbers.py @@ -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, + } diff --git a/mobject/probability.py b/mobject/probability.py new file mode 100644 index 00000000..d4a1ef93 --- /dev/null +++ b/mobject/probability.py @@ -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() + + + + + + + + + + + + + + + + + + diff --git a/mobject/shape_matchers.py b/mobject/shape_matchers.py new file mode 100644 index 00000000..b2e1199a --- /dev/null +++ b/mobject/shape_matchers.py @@ -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) diff --git a/mobject/svg/__init__.py b/mobject/svg/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/mobject/svg/brace.py b/mobject/svg/brace.py new file mode 100644 index 00000000..c56d882d --- /dev/null +++ b/mobject/svg/brace.py @@ -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 + } diff --git a/topics/objects.py b/mobject/svg/drawings.py similarity index 65% rename from topics/objects.py rename to mobject/svg/drawings.py index f46f11e4..9b4ac6ca 100644 --- a/topics/objects.py +++ b/mobject/svg/drawings.py @@ -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, - stroke_color = BLACK, - stroke_width = 0, + + 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]) ) - 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 + 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, + 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() ) -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) + 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) + + + + + + + + + + + + + + + + - 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 - } -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) diff --git a/mobject/svg_mobject.py b/mobject/svg/svg_mobject.py similarity index 97% rename from mobject/svg_mobject.py rename to mobject/svg/svg_mobject.py index 3673d451..23907ce6 100644 --- a/mobject/svg_mobject.py +++ b/mobject/svg/svg_mobject.py @@ -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) diff --git a/mobject/tex_mobject.py b/mobject/svg/tex_mobject.py similarity index 82% rename from mobject/tex_mobject.py rename to mobject/svg/tex_mobject.py index cf405697..bdc48d43 100644 --- a/mobject/tex_mobject.py +++ b/mobject/svg/tex_mobject.py @@ -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, diff --git a/mobject/three_dimensions.py b/mobject/three_dimensions.py new file mode 100644 index 00000000..29b13716 --- /dev/null +++ b/mobject/three_dimensions.py @@ -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) + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobject/types/__init__.py b/mobject/types/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/mobject/image_mobject.py b/mobject/types/image_mobject.py similarity index 97% rename from mobject/image_mobject.py rename to mobject/types/image_mobject.py index 4c7daeb4..17ca1121 100644 --- a/mobject/image_mobject.py +++ b/mobject/types/image_mobject.py @@ -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 diff --git a/mobject/point_cloud_mobject.py b/mobject/types/point_cloud_mobject.py similarity index 97% rename from mobject/point_cloud_mobject.py rename to mobject/types/point_cloud_mobject.py index 5380b8f1..d1cb528e 100644 --- a/mobject/point_cloud_mobject.py +++ b/mobject/types/point_cloud_mobject.py @@ -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 diff --git a/mobject/vectorized_mobject.py b/mobject/types/vectorized_mobject.py similarity index 95% rename from mobject/vectorized_mobject.py rename to mobject/types/vectorized_mobject.py index 44d058f9..280f732a 100644 --- a/mobject/vectorized_mobject.py +++ b/mobject/types/vectorized_mobject.py @@ -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) + diff --git a/mobject/value_tracker.py b/mobject/value_tracker.py new file mode 100644 index 00000000..48f8ba59 --- /dev/null +++ b/mobject/value_tracker.py @@ -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 diff --git a/old_projects/WindingNumber_G.py b/old_projects/WindingNumber_G.py index 377e5ac3..501fc289 100644 --- a/old_projects/WindingNumber_G.py +++ b/old_projects/WindingNumber_G.py @@ -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] diff --git a/old_projects/basel/basel.py b/old_projects/basel/basel.py index b45f18a7..259c537c 100644 --- a/old_projects/basel/basel.py +++ b/old_projects/basel/basel.py @@ -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 diff --git a/old_projects/basel/basel2.py b/old_projects/basel/basel2.py index c32044b5..1638a26f 100644 --- a/old_projects/basel/basel2.py +++ b/old_projects/basel/basel2.py @@ -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 ) diff --git a/old_projects/fourier.py b/old_projects/fourier.py index ea7619bb..830f8c78 100644 --- a/old_projects/fourier.py +++ b/old_projects/fourier.py @@ -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) diff --git a/old_projects/mug.py b/old_projects/mug.py index 9bc48baa..fb8f6227 100644 --- a/old_projects/mug.py +++ b/old_projects/mug.py @@ -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), diff --git a/old_projects/nn/playground.py b/old_projects/nn/playground.py index 6537f294..1edcbf7f 100644 --- a/old_projects/nn/playground.py +++ b/old_projects/nn/playground.py @@ -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 * diff --git a/old_projects/uncertainty.py b/old_projects/uncertainty.py index 2c69cd3f..7e4bd593 100644 --- a/old_projects/uncertainty.py +++ b/old_projects/uncertainty.py @@ -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( diff --git a/once_useful_constructs/NOTE.md b/once_useful_constructs/NOTE.md new file mode 100644 index 00000000..931ae0de --- /dev/null +++ b/once_useful_constructs/NOTE.md @@ -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. \ No newline at end of file diff --git a/once_useful_constructs/__init__.py b/once_useful_constructs/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/topics/arithmetic.py b/once_useful_constructs/arithmetic.py similarity index 98% rename from topics/arithmetic.py rename to once_useful_constructs/arithmetic.py index 6656621e..173ecc73 100644 --- a/topics/arithmetic.py +++ b/once_useful_constructs/arithmetic.py @@ -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( diff --git a/topics/combinatorics.py b/once_useful_constructs/combinatorics.py similarity index 98% rename from topics/combinatorics.py rename to once_useful_constructs/combinatorics.py index 3986d52b..42340dfd 100644 --- a/topics/combinatorics.py +++ b/once_useful_constructs/combinatorics.py @@ -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 diff --git a/topics/complex_numbers.py b/once_useful_constructs/complex_transformation_scene.py similarity index 55% rename from topics/complex_numbers.py rename to once_useful_constructs/complex_transformation_scene.py index 3f026703..efbbe21b 100644 --- a/topics/complex_numbers.py +++ b/once_useful_constructs/complex_transformation_scene.py @@ -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) - diff --git a/topics/counting.py b/once_useful_constructs/counting.py similarity index 94% rename from topics/counting.py rename to once_useful_constructs/counting.py index 2da1911c..8b8edeb0 100644 --- a/topics/counting.py +++ b/once_useful_constructs/counting.py @@ -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 diff --git a/topics/fractals.py b/once_useful_constructs/fractals.py similarity index 96% rename from topics/fractals.py rename to once_useful_constructs/fractals.py index d8cc3909..68283db8 100644 --- a/topics/fractals.py +++ b/once_useful_constructs/fractals.py @@ -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], @@ -596,7 +590,6 @@ class KochCurve(KochSnowFlake): CONFIG = { "axiom" : "A--" } - class QuadraticKoch(LindenmayerCurve): CONFIG = { @@ -611,7 +604,6 @@ class QuadraticKoch(LindenmayerCurve): "angle" : np.pi/2 } - class QuadraticKochIsland(QuadraticKoch): CONFIG = { "axiom" : "A+A+A+A" diff --git a/topics/graph_theory.py b/once_useful_constructs/graph_theory.py similarity index 99% rename from topics/graph_theory.py rename to once_useful_constructs/graph_theory.py index 19a4ba04..0e9ae25d 100644 --- a/topics/graph_theory.py +++ b/once_useful_constructs/graph_theory.py @@ -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(),), diff --git a/topics/light.py b/once_useful_constructs/light.py similarity index 95% rename from topics/light.py rename to once_useful_constructs/light.py index 67138c34..b5462630 100644 --- a/topics/light.py +++ b/once_useful_constructs/light.py @@ -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 diff --git a/once_useful_constructs/matrix_multiplication.py b/once_useful_constructs/matrix_multiplication.py new file mode 100644 index 00000000..a7067cb5 --- /dev/null +++ b/once_useful_constructs/matrix_multiplication.py @@ -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() \ No newline at end of file diff --git a/mobject/region.py b/once_useful_constructs/region.py similarity index 97% rename from mobject/region.py rename to once_useful_constructs/region.py index a23a02e0..13fac5be 100644 --- a/mobject/region.py +++ b/once_useful_constructs/region.py @@ -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" diff --git a/scene/__init__.py b/scene/__init__.py index f03541e4..8b137891 100644 --- a/scene/__init__.py +++ b/scene/__init__.py @@ -1,3 +1 @@ -__all__ = [ - "scene" -] + diff --git a/topics/graph_scene.py b/scene/graph_scene.py similarity index 96% rename from topics/graph_scene.py rename to scene/graph_scene.py index ef3d92a7..41b77b55 100644 --- a/topics/graph_scene.py +++ b/scene/graph_scene.py @@ -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, diff --git a/scene/moving_camera_scene.py b/scene/moving_camera_scene.py index d3e13776..70f0f158 100644 --- a/scene/moving_camera_scene.py +++ b/scene/moving_camera_scene.py @@ -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): diff --git a/scene/reconfigurable_scene.py b/scene/reconfigurable_scene.py index a747e5b8..555ad69d 100644 --- a/scene/reconfigurable_scene.py +++ b/scene/reconfigurable_scene.py @@ -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 diff --git a/scene/sample_space_scene.py b/scene/sample_space_scene.py new file mode 100644 index 00000000..53bc7cac --- /dev/null +++ b/scene/sample_space_scene.py @@ -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 diff --git a/scene/scene.py b/scene/scene.py index 601265eb..1817a326 100644 --- a/scene/scene.py +++ b/scene/scene.py @@ -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: diff --git a/scene/scene_from_video.py b/scene/scene_from_video.py index 9eff530a..aab35ade 100644 --- a/scene/scene_from_video.py +++ b/scene/scene_from_video.py @@ -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): diff --git a/scene/three_d_scene.py b/scene/three_d_scene.py new file mode 100644 index 00000000..6896e55f --- /dev/null +++ b/scene/three_d_scene.py @@ -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 diff --git a/scene/tk_scene.py b/scene/tk_scene.py deleted file mode 100644 index b79d4332..00000000 --- a/scene/tk_scene.py +++ /dev/null @@ -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() diff --git a/topics/vector_space_scene.py b/scene/vector_space_scene.py similarity index 93% rename from topics/vector_space_scene.py rename to scene/vector_space_scene.py index 081575e2..157561e1 100644 --- a/topics/vector_space_scene.py +++ b/scene/vector_space_scene.py @@ -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 diff --git a/scene/zoomed_scene.py b/scene/zoomed_scene.py index 974e330f..a5df8796 100644 --- a/scene/zoomed_scene.py +++ b/scene/zoomed_scene.py @@ -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 * diff --git a/stage_scenes.py b/stage_scenes.py index 7177d614..7e67220f 100644 --- a/stage_scenes.py +++ b/stage_scenes.py @@ -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): diff --git a/topics/__init__.py b/topics/__init__.py deleted file mode 100644 index 1ed0fc07..00000000 --- a/topics/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -__all__ = [ - "arithmetic", - "characters", - "combinatorics", - "complex_numbers", - "functions", - "geometry", - "graph_theory", - "number_line", - "three_dimensions", -] \ No newline at end of file diff --git a/topics/characters.py b/topics/characters.py deleted file mode 100644 index bca6b509..00000000 --- a/topics/characters.py +++ /dev/null @@ -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, - ) - - - - - - - - - - - diff --git a/topics/matrix.py b/topics/matrix.py deleted file mode 100644 index e646fdba..00000000 --- a/topics/matrix.py +++ /dev/null @@ -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() - - - - - - - - - - diff --git a/topics/numerals.py b/topics/numerals.py deleted file mode 100644 index f91cda5c..00000000 --- a/topics/numerals.py +++ /dev/null @@ -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) - - - - - - - - - - - - - - - - - - - diff --git a/topics/probability.py b/topics/probability.py deleted file mode 100644 index 726c3050..00000000 --- a/topics/probability.py +++ /dev/null @@ -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) - - - - - - - - - - - - - - - - - - - diff --git a/utils/bezier.py b/utils/bezier.py index 9bbecab8..cdce5198 100644 --- a/utils/bezier.py +++ b/utils/bezier.py @@ -1,4 +1,5 @@ import numpy as np + from scipy import linalg from utils.simple_functions import choose diff --git a/utils/color.py b/utils/color.py index 183c36f0..a4e11082 100644 --- a/utils/color.py +++ b/utils/color.py @@ -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): diff --git a/utils/images.py b/utils/images.py index ea6ac556..9138166d 100644 --- a/utils/images.py +++ b/utils/images.py @@ -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 = [ diff --git a/utils/iterables.py b/utils/iterables.py index f903ff42..f77aa45d 100644 --- a/utils/iterables.py +++ b/utils/iterables.py @@ -1,5 +1,5 @@ -import numpy as np import itertools as it +import numpy as np def remove_list_redundancies(l): """ diff --git a/utils/paths.py b/utils/paths.py index bf542c46..fd4e6694 100644 --- a/utils/paths.py +++ b/utils/paths.py @@ -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 diff --git a/utils/rate_functions.py b/utils/rate_functions.py index bce06815..566438ba 100644 --- a/utils/rate_functions.py +++ b/utils/rate_functions.py @@ -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) diff --git a/utils/simple_functions.py b/utils/simple_functions.py index ccdc0d50..52acc014 100644 --- a/utils/simple_functions.py +++ b/utils/simple_functions.py @@ -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)) diff --git a/utils/space_ops.py b/utils/space_ops.py index 78d1c7a4..c9f1506a 100644 --- a/utils/space_ops.py +++ b/utils/space_ops.py @@ -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 - - - - - - - diff --git a/utils/strings.py b/utils/strings.py index bfed3b66..54eaae3e 100644 --- a/utils/strings.py +++ b/utils/strings.py @@ -16,4 +16,7 @@ def initials(name, sep_values = [" ", "_"]): ]) def camel_case_initials(name): - return filter(lambda c : c.isupper(), name) \ No newline at end of file + return filter(lambda c : c.isupper(), name) + +def complex_string(complex_num): + return filter(lambda c : c not in "()", str(complex_num))