class CyclicReplace(Transform): CONFIG = { "path_arc": 90 * DEGREES, } def __init__(self, *mobjects, **kwargs): self.group = Group(*mobjects) super().__init__(self.group, **kwargs) def create_target(self): target = self.group.copy() cycled_targets = [target[-1], *target[:-1]] for m1, m2 in zip(cycled_targets, self.group): m1.move_to(m2) return target
def __init__(self, *args, **kwargs): """ Each arg will either be an animation, or an animation class followed by its arguments (and potentially a dict for configuration). For example, Succession( ShowCreation(circle), Transform, circle, square, Transform, circle, triangle, ApplyMethod, circle.shift, 2*UP, {"run_time" : 2}, ) """ animations = [] state = { "animations": animations, "curr_class": None, "curr_class_args": [], "curr_class_config": {}, } def invoke_curr_class(state): if state["curr_class"] is None: return anim = state["curr_class"](*state["curr_class_args"], **state["curr_class_config"]) state["animations"].append(anim) anim.update(1) state["curr_class"] = None state["curr_class_args"] = [] state["curr_class_config"] = {} for arg in args: if isinstance(arg, OldAnimation): animations.append(arg) arg.update(1) invoke_curr_class(state) elif isinstance(arg, type) and issubclass(arg, Animation): invoke_curr_class(state) state["curr_class"] = arg elif isinstance(arg, dict): state["curr_class_config"] = arg else: state["curr_class_args"].append(arg) invoke_curr_class(state) for anim in animations: anim.update(0) animations = [x for x in animations if not (x.empty)] self.run_times = [anim.run_time for anim in animations] if "run_time" in kwargs: run_time = kwargs.pop("run_time") warnings.warn( "Succession doesn't currently support explicit run_time.") run_time = sum(self.run_times) self.num_anims = len(animations) if self.num_anims == 0: self.empty = True self.animations = animations # Have to keep track of this run_time, because Scene.play # might very well mess with it. self.original_run_time = run_time # critical_alphas[i] is the start alpha of self.animations[i] # critical_alphas[i + 1] is the end alpha of self.animations[i] critical_times = np.concatenate(([0], np.cumsum(self.run_times))) self.critical_alphas = [ np.true_divide(x, run_time) for x in critical_times ] if self.num_anims > 0 else [0.0] # self.scene_mobjects_at_time[i] is the scene's mobjects at start of self.animations[i] # self.scene_mobjects_at_time[i + 1] is the scene mobjects at end of self.animations[i] self.scene_mobjects_at_time = [None for i in range(self.num_anims + 1)] self.scene_mobjects_at_time[0] = Group() for i in range(self.num_anims): self.scene_mobjects_at_time[ i + 1] = self.scene_mobjects_at_time[i].copy() self.animations[i].clean_up(self.scene_mobjects_at_time[i + 1]) self.current_alpha = 0 # If self.num_anims == 0, this is an invalid index, but so it goes self.current_anim_index = 0 if self.num_anims > 0: self.mobject = self.scene_mobjects_at_time[0] self.mobject.add(self.animations[0].mobject) else: self.mobject = Group() OldAnimation.__init__(self, self.mobject, run_time=run_time, **kwargs)
class OldSuccession(OldAnimation): CONFIG = { "rate_func": None, } def __init__(self, *args, **kwargs): """ Each arg will either be an animation, or an animation class followed by its arguments (and potentially a dict for configuration). For example, Succession( ShowCreation(circle), Transform, circle, square, Transform, circle, triangle, ApplyMethod, circle.shift, 2*UP, {"run_time" : 2}, ) """ animations = [] state = { "animations": animations, "curr_class": None, "curr_class_args": [], "curr_class_config": {}, } def invoke_curr_class(state): if state["curr_class"] is None: return anim = state["curr_class"](*state["curr_class_args"], **state["curr_class_config"]) state["animations"].append(anim) anim.update(1) state["curr_class"] = None state["curr_class_args"] = [] state["curr_class_config"] = {} for arg in args: if isinstance(arg, OldAnimation): animations.append(arg) arg.update(1) invoke_curr_class(state) elif isinstance(arg, type) and issubclass(arg, Animation): invoke_curr_class(state) state["curr_class"] = arg elif isinstance(arg, dict): state["curr_class_config"] = arg else: state["curr_class_args"].append(arg) invoke_curr_class(state) for anim in animations: anim.update(0) animations = [x for x in animations if not (x.empty)] self.run_times = [anim.run_time for anim in animations] if "run_time" in kwargs: run_time = kwargs.pop("run_time") warnings.warn( "Succession doesn't currently support explicit run_time.") run_time = sum(self.run_times) self.num_anims = len(animations) if self.num_anims == 0: self.empty = True self.animations = animations # Have to keep track of this run_time, because Scene.play # might very well mess with it. self.original_run_time = run_time # critical_alphas[i] is the start alpha of self.animations[i] # critical_alphas[i + 1] is the end alpha of self.animations[i] critical_times = np.concatenate(([0], np.cumsum(self.run_times))) self.critical_alphas = [ np.true_divide(x, run_time) for x in critical_times ] if self.num_anims > 0 else [0.0] # self.scene_mobjects_at_time[i] is the scene's mobjects at start of self.animations[i] # self.scene_mobjects_at_time[i + 1] is the scene mobjects at end of self.animations[i] self.scene_mobjects_at_time = [None for i in range(self.num_anims + 1)] self.scene_mobjects_at_time[0] = Group() for i in range(self.num_anims): self.scene_mobjects_at_time[ i + 1] = self.scene_mobjects_at_time[i].copy() self.animations[i].clean_up(self.scene_mobjects_at_time[i + 1]) self.current_alpha = 0 # If self.num_anims == 0, this is an invalid index, but so it goes self.current_anim_index = 0 if self.num_anims > 0: self.mobject = self.scene_mobjects_at_time[0] self.mobject.add(self.animations[0].mobject) else: self.mobject = Group() OldAnimation.__init__(self, self.mobject, run_time=run_time, **kwargs) # Beware: This does NOT take care of calling update(0) on the subanimation. # This was important to avoid a pernicious possibility in which subanimations were called # with update twice, which could in turn call a sub-Succession with update four times, # continuing exponentially. def jump_to_start_of_anim(self, index): if index != self.current_anim_index: # Should probably have a cleaner "remove_all" method... self.mobject.remove(*self.mobject.submobjects) self.mobject.add(*self.scene_mobjects_at_time[index].submobjects) self.mobject.add(self.animations[index].mobject) for i in range(index): self.animations[i].update(1) self.current_anim_index = index self.current_alpha = self.critical_alphas[index] def update_mobject(self, alpha): if self.num_anims == 0: # This probably doesn't matter for anything, but just in case, # we want it in the future, we set current_alpha even in this case self.current_alpha = alpha return gt_alpha_iter = iter( filter(lambda i: self.critical_alphas[i + 1] >= alpha, range(self.num_anims))) i = next(gt_alpha_iter, None) if i is None: # In this case, we assume what is happening is that alpha is 1.0, # but that rounding error is causing us to overshoot the end of # self.critical_alphas (which is also 1.0) if not abs(alpha - 1) < 0.001: warnings.warn( "Rounding error not near alpha=1 in Succession.update_mobject," "instead alpha = %f" % alpha) print(self.critical_alphas, alpha) i = self.num_anims - 1 # At this point, we should have self.critical_alphas[i] <= alpha <= self.critical_alphas[i +1] self.jump_to_start_of_anim(i) sub_alpha = inverse_interpolate(self.critical_alphas[i], self.critical_alphas[i + 1], alpha) self.animations[i].update(sub_alpha) self.current_alpha = alpha def clean_up(self, *args, **kwargs): # We clean up as though we've played ALL animations, even if # clean_up is called in middle of things for anim in self.animations: anim.clean_up(*args, **kwargs)
def __init__(self, *mobjects, run_time=0.001, lag_ratio=1, **kwargs): if isinstance(mobjects[-1], (int, float)): run_time = run_time mobjects = mobjects[:-1] super().__init__(Group(*mobjects), run_time=run_time, **kwargs)
def __init__(self, mobject: Mobject, target_mobject: Mobject, **kwargs): self.to_add_on_completion = target_mobject mobject.save_state() super().__init__(Group(mobject, target_mobject.copy()), **kwargs)
class ColorSliders(Group): CONFIG = { "sliders_kwargs": {}, "rect_kwargs": { "width": 2.0, "height": 0.5, "stroke_opacity": 1.0 }, "background_grid_kwargs": { "colors": [GREY_A, GREY_C], "single_square_len": 0.1 }, "sliders_buff": MED_LARGE_BUFF, "default_rgb_value": 255, "default_a_value": 1, } def __init__(self, **kwargs): digest_config(self, kwargs) rgb_kwargs = { "value": self.default_rgb_value, "min_value": 0, "max_value": 255, "step": 1 } a_kwargs = { "value": self.default_a_value, "min_value": 0, "max_value": 1, "step": 0.04 } self.r_slider = LinearNumberSlider(**self.sliders_kwargs, **rgb_kwargs) self.g_slider = LinearNumberSlider(**self.sliders_kwargs, **rgb_kwargs) self.b_slider = LinearNumberSlider(**self.sliders_kwargs, **rgb_kwargs) self.a_slider = LinearNumberSlider(**self.sliders_kwargs, **a_kwargs) self.sliders = Group(self.r_slider, self.g_slider, self.b_slider, self.a_slider) self.sliders.arrange(DOWN, buff=self.sliders_buff) self.r_slider.slider.set_color(RED) self.g_slider.slider.set_color(GREEN) self.b_slider.slider.set_color(BLUE) self.a_slider.slider.set_color_by_gradient([BLACK, WHITE]) self.selected_color_box = Rectangle(**self.rect_kwargs) self.selected_color_box.add_updater(lambda mob: mob.set_fill( self.get_picked_color(), self.get_picked_opacity())) self.background = self.get_background() super().__init__( Group(self.background, self.selected_color_box).fix_in_frame(), self.sliders, **kwargs) self.arrange(DOWN) def get_background(self): single_square_len = self.background_grid_kwargs["single_square_len"] colors = self.background_grid_kwargs["colors"] width = self.rect_kwargs["width"] height = self.rect_kwargs["height"] rows = int(height / single_square_len) cols = int(width / single_square_len) cols = (cols + 1) if (cols % 2 == 0) else cols single_square = Square(single_square_len) grid = single_square.get_grid(n_rows=rows, n_cols=cols, buff=0.0) grid.stretch_to_fit_width(width) grid.stretch_to_fit_height(height) grid.move_to(self.selected_color_box) for idx, square in enumerate(grid): assert (isinstance(square, Square)) square.set_stroke(width=0.0, opacity=0.0) square.set_fill(colors[idx % len(colors)], 1.0) return grid def set_value(self, r, g, b, a): self.r_slider.set_value(r) self.g_slider.set_value(g) self.b_slider.set_value(b) self.a_slider.set_value(a) def get_value(self): r = self.r_slider.get_value() / 255 g = self.g_slider.get_value() / 255 b = self.b_slider.get_value() / 255 alpha = self.a_slider.get_value() return color_to_rgba(rgb_to_color((r, g, b)), alpha=alpha) def get_picked_color(self): rgba = self.get_value() return rgb_to_hex(rgba[:3]) def get_picked_opacity(self): rgba = self.get_value() return rgba[3]
def __init__(self, mobject_or_chars): VMobject.__init__(self) if not isinstance(mobject_or_chars, (list, tuple, ndarray)): mobject_or_chars = [mobject_or_chars] mobject = Group(*[MobjectOrChars(each) for each in mobject_or_chars]) self.add(*mobject)
def get_group(self, *mobjects): if all(isinstance(m, VMobject) for m in mobjects): return VGroup(*mobjects) else: return Group(*mobjects)
def construct(self): if 1 == 1: try: self.add_sound( "sidewayoutput\\basicmanim\\transform001a_01_01_01_01_01.wav", time_offset=18) self.add_sound( "sidewayoutput\\basicmanim\\transform001a_01_01_01_02_01.wav", time_offset=93) self.add_sound( "sidewayoutput\\basicmanim\\transform001a_01_01_01_03_01.wav", time_offset=135) except: pass self.play( StartScreens01( [], [], [[r"\textbf{\textit{Basic-Manim from }\{Sideway\}}"], [r"\textbf{\textit{Transform}}\\{{Part\ \textspA{I}a}"], [ r"\tiny{\textrm{basic-manim.210200551v0\_transform001a}}" ], [], [ r"\scriptsize{\textbf{Warning:\ The\ content\ may\ contain\ errors,\ mistakes\ and\ inaccuracies.\ Information\ must\ be\ verified\ and\ evaluated\ before\ use.}}" ]], )) if 1 == 1: self.play( GrowFromCenter( TextMobject( r"\textit{\textbf{\underline{Transform}}}").shift( [0, 3.6, 0]))) squarea = Square() squareb = Square(side_length=4).shift([4, 0, 0]) circlea = Circle() circleb = Circle(radius=2).shift([-4, 0, 0]) linea = Line([-4, 3, 0], [4, 3, 0]) lineb = Line([-3, -3, 0], [3, -3, 0]) texta = TextMobject("AB").shift([0, -2.5, 0]) textb = TextMobject("HI").shift([0, 2.5, 0]) self.play( ShowCreation(Group(squarea, circlea, squareb, circleb, linea, lineb), lag_ratio=1, run_time=12)) self.play(Write(VGroup(texta, textb), lag_ratio=1, run_time=10)) self.play(Transform(circlea, squarea, run_time=5)) self.play(Transform(circlea, squareb, path_arc=3, run_time=5)) self.play(Transform(squarea, circleb, path_arc=3, run_time=5)) self.play(Transform(linea, lineb, path_arc=3, run_time=5)) self.play(Transform(linea, circleb, path_arc=3, run_time=5)) self.play(Transform(squareb, lineb, path_arc=3, run_time=5)) self.play(Transform(texta, textb, path_arc=3, run_time=5)) self.play(Transform(texta, circleb, path_arc=3, run_time=5)) self.play(Transform(squareb, textb, path_arc=3, run_time=5)) self.fadeout() if 1 == 1: self.play( GrowFromCenter( TextMobject( r"\textit{\textbf{\underline{Paths\ of\ Transform}}}"). shift([0, 3.6, 0]))) rows, cols = (7, 5) x0, y0 = axes_point([0, 2, cols - 1, -4], [2.3, -1, rows - 1, 3]) txtx = ["loc 1", "loc 2", "m1", "m2"] txty = [ "ClockwiseTransform", "Transform", "CounterclockwiseTransform" ] a1 = Group() a2 = Group() a3 = Group() a4 = Group() for j in range(1, rows): a1.add(Circle().scale(0.2).move_to([x0[1], y0[j], 0])) a2.add(Square().scale(0.2).move_to([x0[2], y0[j], 0])) a3.add( Dot([x0[3], y0[j], 0]).add_updater(lambda mob, obj=a1[ j - 1], x=x0[3], y=y0[j]: mob.become(obj.copy( ).move_to([x, y, 0]))).suspend_updating()) a4.add( Dot([x0[4], y0[j], 0]).add_updater(lambda mob, obj=a2[ j - 1], x=x0[4], y=y0[j]: mob.become(obj.copy( ).move_to([x, y, 0]))).suspend_updating()) self.play(FadeIn( Group( *[ TextMobject(txtx[i]).move_to(each) for i, each in enumerate(coord_grid(x0[1:], y0[0:1])) ], *[ TextMobject(txty[i]).move_to(each) for i, each in enumerate(coord_grid(x0[0:1], y0[1::2])) ], *[Dot(each) for each in coord_grid(x0[1:3], y0[1:])], TextMobject(r"$\rightarrow$").move_to( ([(x0[1] + x0[2]) / 2, y0[0], 0])), a1[::2], a2[::2], a3, a4, *[ Square(stroke_width=2, color="#FFFF00", fill_opacity=0.3).add_updater( lambda mob, obj=obj: mob.surround( obj, stretch=True, buff=0.2)) for obj in a1 ], *[ Square(stroke_width=2, color="#DC75CD").add_updater( lambda mob, obj=obj: mob.surround( obj, stretch=True, buff=0.3)) for obj in a2 ])), run_time=5) self.wait(2) self.play(AnimationGroup( ClockwiseTransform(a2[0], a1[0]), ClockwiseTransform(a1[1], a2[1]), Transform(a1[2], a2[2]), Transform(a2[3], a1[3]), CounterclockwiseTransform(a1[4], a2[4]), CounterclockwiseTransform(a2[5], a1[5]), ), run_time=25) self.wait(3) a1.shift([0.3, 0, 0]).set_color("#11FF00") self.wait(3) self.play(ApplyMethod(a1.shift, ([0.3, 0, 0])), run_time=3) self.fadeout() if 1 == 1: self.play( GrowFromCenter( TextMobject( r"\textit{\textbf{\underline{Methods\ of\ Transform}}}" ).shift([0, 3.6, 0]))) rows, cols = (9, 5) x0, y0 = axes_point([0, 2, cols - 1, -4], [2.3, -0.8, rows - 1, 3]) txtx = ["loc 1", "loc 2", "m1", "m2"] txty = [ "Transform", "ReplacementTransform", "TransformFromCopy", "MoveToTarget" ] a1 = Group() a2 = Group() a3 = Group() a4 = Group() for j in range(1, rows): a1.add(Circle().scale(0.2).move_to([x0[1], y0[j], 0])) a2.add(Square().scale(0.2).move_to([x0[2], y0[j], 0])) a3.add(Dot().move_to([x0[3], y0[j], 0]).add_updater( lambda mob, obj=a1[j - 1], x=x0[3], y=y0[j]: mob.become( obj.copy().move_to([x, y, 0]))).suspend_updating()) a4.add(Dot().move_to([x0[4], y0[j], 0]).add_updater( lambda mob, obj=a2[j - 1], x=x0[4], y=y0[j]: mob.become( obj.copy().move_to([x, y, 0]))).suspend_updating()) a1[6].target = a2[6] a1[7].target = a2[7] self.play(FadeIn( Group( *[ TextMobject(txtx[i]).move_to(each) for i, each in enumerate(coord_grid(x0[1:], y0[0:1])) ], *[ TextMobject(txty[i]).move_to(each) for i, each in enumerate(coord_grid(x0[0:1], y0[1::2])) ], *[Dot(each) for each in coord_grid(x0[1:3], y0[1:])], TextMobject(r"$\rightarrow$").move_to( ([(x0[1] + x0[2]) / 2, y0[0], 0])), a1[::2], a2[::2], a3, a4, Group(*[ Square( stroke_width=2, color="#FFFF00", fill_opacity=0.3). add_updater(lambda mob, obj=obj: mob.surround( obj, stretch=True, buff=0.2)) for obj in a1 ]), Group(*[ Square(stroke_width=2, color="#DC75CD").add_updater( lambda mob, obj=obj: mob.surround( obj, stretch=True, buff=0.3)) for obj in a2 ]))), run_time=5) self.wait(2) self.play(AnimationGroup(Transform(a1[0], a2[0]), Transform(a1[1], a2[1]), ReplacementTransform(a1[2], a2[2]), ReplacementTransform(a1[3], a2[3]), TransformFromCopy(a1[4], a2[4]), TransformFromCopy(a1[5], a2[5]), MoveToTarget(a1[6]), MoveToTarget(a1[7])), run_time=40) self.wait(3) a1.shift([0.3, 0, 0]).set_color("#11FF00") self.wait(10) self.play(ApplyMethod(a1.shift, ([0.3, 0, 0])), run_time=5) self.fadeout() if 1 == 1: self.play(EndScreen01()) self.wait(5)
def construct(self): if 1 == 1: try: self.add_sound( "sidewayoutput\\basicmanim\\growing001a_01_01_01_01_01.wav", time_offset=19) self.add_sound( "sidewayoutput\\basicmanim\\growing001a_01_01_01_02_01.wav", time_offset=43) self.add_sound( "sidewayoutput\\basicmanim\\growing001a_01_01_01_03_01.wav", time_offset=89) self.add_sound( "sidewayoutput\\basicmanim\\growing001a_01_01_01_04_01.wav", time_offset=136) self.add_sound( "sidewayoutput\\basicmanim\\growing001a_01_01_01_05_01.wav", time_offset=180) except: pass self.play( StartScreens01( [], [], [[r"\textbf{\textit{Basic-Manim from }\{Sideway\}}"], [r"\textbf{\textit{Growing}}\\{{Part\ \textspA{I}a}"], [r"\tiny{\textrm{basic-manim.210300151v0\_growing001a}}"], [], [ r"\scriptsize{\textbf{Warning:\ The\ content\ may\ contain\ errors,\ mistakes\ and\ inaccuracies.\ Information\ must\ be\ verified\ and\ evaluated\ before\ use.}}" ]], )) if 1 == 1: self.grow(r"\titleA{Growing}", shift=[0, 3.6, 0]) rows, cols = (4, 3) x0, y0 = axes_point([1, 4, cols - 1, -4.5], [2.5, -1.9, rows]) txty = ["Growing", " Diminishing", "One Direction", "Spin"] strs = [ each + "(sqs[" + str(i) + "])" for i, each in enumerate([ "GrowFromCenter", "DiminishToCenter", "GrowArrow", "SpinInFromNothing" ]) ] sqs = [GeomSquare(1.2) for i in range(rows)] self.fadein(*[ TextMobject(txty[i]).move_to(each) for i, each in enumerate(coord_grid(x0[0:1], y0)) ], *[ Group(GeomPoint(), TextMobject("sqs[" + str(i) + "]"), sqs[i]).move_to(each) for i, each in enumerate(coord_grid(x0[2:3], y0)) ], run_time=5) self.grow(*[ TextMobject(strs[i]).move_to(each) for i, each in enumerate(coord_grid(x0[1:2], y0)) ]) exec("self.play(" + ','.join(strs) + "," + "run_time=15)") self.fadeout() if 1 == 1: self.grow(r"\titleA{GrowFrom}", shift=[0, 3.6, 0]) rows, cols = (4, 3) x0, y0 = axes_point([1, 4, cols - 1, -4.5], [2.5, -1.9, rows]) txty = [ "GrowFromPoint", "GrowFromCenter", "GrowFromEdge", "GrowFromSide" ] strs = [ txty[i] + "(sqs[" + str(i) + "]" + each + ")" for i, each in enumerate([",UP", "", ",UP", ",UP"]) ] sqs = [GeomSquare(1.2) for i in range(rows)] self.fadein(*[ TextMobject(txty[i]).move_to(each) for i, each in enumerate(coord_grid(x0[0:1], y0)) ], *[ Group(GeomPoint(), TextMobject("sqs[" + str(i) + "]"), sqs[i]).move_to(each) for i, each in enumerate(coord_grid(x0[2:3], y0)) ], run_time=5) self.grow(*[ TextMobject(strs[i]).move_to(each) for i, each in enumerate(coord_grid(x0[1:2], y0)) ]) exec("self.play(" + ','.join(strs) + "," + "run_time=35)") self.fadeout() if 1 == 1: self.grow(r"\titleA{DiminishTo}", shift=[0, 3.6, 0]) rows, cols = (4, 3) x0, y0 = axes_point([1, 4, cols - 1, -4.5], [2.5, -1.9, rows]) txty = [ "DiminishToPoint", "DiminishToCenter", "DiminishToEdge", "DiminishToSide" ] strs = [ txty[i] + "(sqs[" + str(i) + "]" + each + ")" for i, each in enumerate([",UP", "", ",UP", ",UP"]) ] sqs = [GeomSquare(1.2) for i in range(rows)] self.fadein(*[ TextMobject(txty[i]).move_to(each) for i, each in enumerate(coord_grid(x0[0:1], y0)) ], *[ Group(GeomPoint(), TextMobject("sqs[" + str(i) + "]"), sqs[i]).move_to(each) for i, each in enumerate(coord_grid(x0[2:3], y0)) ], run_time=5) self.grow(*[ TextMobject(strs[i]).move_to(each) for i, each in enumerate(coord_grid(x0[1:2], y0)) ]) exec("self.play(" + ','.join(strs) + "," + "run_time=38)") self.fadeout() if 1 == 1: self.grow(r"\titleA{One Direction}", shift=[0, 3.6, 0]) rows, cols = (8, 3) x0, y0 = axes_point([1, 4, cols - 1, -4.5], [2.8, -0.9, rows]) txty = to_get_zlist( ["GrowArrow", "ExpandArrow", "DiminishArrow", "RetractArrow"], n=2) strs = [txty[i] + "(sqs[" + str(i) + "])" for i in range(rows)] sqs = to_get_zlist([GeomRectangle(0.6, 1.2), GeomArrow()], n=(4, )) self.fadein(*[ TextMobject(txty[i * 2]).move_to(each) for i, each in enumerate(coord_grid(x0[0:1], y0[:-1:2])) ], *[ Group(GeomPoint(), TextMobject("sqs[" + str(i) + "]"), sqs[i]).move_to(each) for i, each in enumerate(coord_grid(x0[2:3], y0)) ], run_time=5) self.fadein(*[ TextMobject(strs[i]).move_to(each) for i, each in enumerate(coord_grid(x0[1:2], y0)) ]) exec("self.play(" + ','.join(strs) + "," + "run_time=35)") self.fadeout() if 1 == 1: self.grow(r"\titleA{Spin}", shift=[0, 3.6, 0]) rows, cols = (5, 3) x0, y0 = axes_point([1.8, 4, cols - 1, -4.2], [3, -1.5, rows]) txty = [ "SpinInFromNothing", "SpinInFrom", "SpinOutFrom", "SpinInTo", "SpinOutTo" ] strs = [ txty[i] + "(sqs[" + str(i) + "]" + each + ")" for i, each in enumerate(["", "", "", "", ""]) ] sqs = [ GeomRegularPolygon(5, ORIGIN, 0.8).add( GeomArrow([0, 0.07, 0], [0, 0.8, 0])) for i in range(rows) ] self.fadein(*[ TextMobject(txty[i]).move_to(each) for i, each in enumerate(coord_grid(x0[0:1], y0)) ], *[ Group(GeomPoint(), TextMobject("sqs[" + str(i) + "]"), sqs[i]).move_to(each) for i, each in enumerate(coord_grid(x0[2:3], y0)) ], run_time=5) self.grow(*[ TextMobject(strs[i]).move_to(each) for i, each in enumerate(coord_grid(x0[1:2], y0)) ]) exec("self.play(" + ','.join(strs) + "," + "run_time=50)") self.fadeout() if 1 == 1: self.play(EndScreen01()) self.wait(5)
def __init__(self, *continual_animations, **kwargs): digest_config(self, kwargs, locals()) self.group = Group(*[ca.mobject for ca in continual_animations]) ContinualAnimation.__init__(self, self.group, **kwargs)
class InteractiveScene(Scene): """ To select mobjects on screen, hold ctrl and move the mouse to highlight a region, or just tap ctrl to select the mobject under the cursor. Pressing command + t will toggle between modes where you either select top level mobjects part of the scene, or low level pieces. Hold 'g' to grab the selection and move it around Hold 'h' to drag it constrained in the horizontal direction Hold 'v' to drag it constrained in the vertical direction Hold 't' to resize selection, adding 'shift' to resize with respect to a corner Command + 'c' copies the ids of selections to clipboard Command + 'v' will paste either: - The copied mobject - A Tex mobject based on copied LaTeX - A Text mobject based on copied Text Command + 'z' restores selection back to its original state Command + 's' saves the selected mobjects to file """ corner_dot_config = dict( color=WHITE, radius=0.05, glow_factor=1.0, ) selection_rectangle_stroke_color = WHITE selection_rectangle_stroke_width = 1.0 palette_colors = MANIM_COLORS selection_nudge_size = 0.05 cursor_location_config = dict( font_size=24, fill_color=GREY_C, num_decimal_places=3, ) time_label_config = dict( font_size=24, fill_color=GREY_C, num_decimal_places=1, ) crosshair_width = 0.2 crosshair_color = GREY_A def setup(self): self.selection = Group() self.selection_highlight = self.get_selection_highlight() self.selection_rectangle = self.get_selection_rectangle() self.crosshair = self.get_crosshair() self.information_label = self.get_information_label() self.color_palette = self.get_color_palette() self.unselectables = [ self.selection, self.selection_highlight, self.selection_rectangle, self.crosshair, self.information_label, self.camera.frame ] self.select_top_level_mobs = True self.regenerate_selection_search_set() self.is_selecting = False self.is_grabbing = False self.add(self.selection_highlight) def get_selection_rectangle(self): rect = Rectangle( stroke_color=self.selection_rectangle_stroke_color, stroke_width=self.selection_rectangle_stroke_width, ) rect.fix_in_frame() rect.fixed_corner = ORIGIN rect.add_updater(self.update_selection_rectangle) return rect def update_selection_rectangle(self, rect: Rectangle): p1 = rect.fixed_corner p2 = self.mouse_point.get_center() rect.set_points_as_corners([ p1, [p2[0], p1[1], 0], p2, [p1[0], p2[1], 0], p1, ]) return rect def get_selection_highlight(self): result = Group() result.tracked_mobjects = [] result.add_updater(self.update_selection_highlight) return result def update_selection_highlight(self, highlight: Mobject): if set(highlight.tracked_mobjects) == set(self.selection): return # Otherwise, refresh contents of highlight highlight.tracked_mobjects = list(self.selection) highlight.set_submobjects( [self.get_highlight(mob) for mob in self.selection]) try: index = min((i for i, mob in enumerate(self.mobjects) for sm in self.selection if sm in mob.get_family())) self.mobjects.remove(highlight) self.mobjects.insert(index - 1, highlight) except ValueError: pass def get_crosshair(self): line = Line(LEFT, RIGHT) line.insert_n_curves(1) lines = line.replicate(2) lines[1].rotate(PI / 2) crosshair = VMobject() crosshair.set_points([*lines[0].get_points(), *lines[1].get_points()]) crosshair.set_width(self.crosshair_width) crosshair.set_stroke(self.crosshair_color, width=[2, 0, 2, 2, 0, 2]) crosshair.set_animating_status(True) crosshair.fix_in_frame() return crosshair def get_color_palette(self): palette = VGroup( *(Square(fill_color=color, fill_opacity=1, side_length=1) for color in self.palette_colors)) palette.set_stroke(width=0) palette.arrange(RIGHT, buff=0.5) palette.set_width(FRAME_WIDTH - 0.5) palette.to_edge(DOWN, buff=SMALL_BUFF) palette.fix_in_frame() return palette def get_information_label(self): loc_label = VGroup(*(DecimalNumber(**self.cursor_location_config) for n in range(3))) def update_coords(loc_label): for mob, coord in zip(loc_label, self.mouse_point.get_location()): mob.set_value(coord) loc_label.arrange(RIGHT, buff=loc_label.get_height()) loc_label.to_corner(DR, buff=SMALL_BUFF) loc_label.fix_in_frame() return loc_label loc_label.add_updater(update_coords) time_label = DecimalNumber(0, **self.time_label_config) time_label.to_corner(DL, buff=SMALL_BUFF) time_label.fix_in_frame() time_label.add_updater(lambda m, dt: m.increment_value(dt)) return VGroup(loc_label, time_label) # Overrides def get_state(self): return SceneState(self, ignore=[ self.selection_highlight, self.selection_rectangle, self.crosshair, ]) def restore_state(self, scene_state: SceneState): super().restore_state(scene_state) self.mobjects.insert(0, self.selection_highlight) def add(self, *mobjects: Mobject): super().add(*mobjects) self.regenerate_selection_search_set() def remove(self, *mobjects: Mobject): super().remove(*mobjects) self.regenerate_selection_search_set() # def increment_time(self, dt: float) -> None: # super().increment_time(dt) # Related to selection def toggle_selection_mode(self): self.select_top_level_mobs = not self.select_top_level_mobs self.refresh_selection_scope() self.regenerate_selection_search_set() def get_selection_search_set(self) -> list[Mobject]: return self.selection_search_set def regenerate_selection_search_set(self): selectable = list( filter(lambda m: m not in self.unselectables, self.mobjects)) if self.select_top_level_mobs: self.selection_search_set = selectable else: self.selection_search_set = [ submob for mob in selectable for submob in mob.family_members_with_points() ] def refresh_selection_scope(self): curr = list(self.selection) if self.select_top_level_mobs: self.selection.set_submobjects([ mob for mob in self.mobjects if any(sm in mob.get_family() for sm in curr) ]) self.selection.refresh_bounding_box(recurse_down=True) else: self.selection.set_submobjects( extract_mobject_family_members( curr, exclude_pointless=True, )) def get_corner_dots(self, mobject: Mobject) -> Mobject: dots = DotCloud(**self.corner_dot_config) radius = self.corner_dot_config["radius"] if mobject.get_depth() < 1e-2: vects = [DL, UL, UR, DR] else: vects = np.array(list(it.product(*3 * [[-1, 1]]))) dots.add_updater(lambda d: d.set_points( [mobject.get_corner(v) + v * radius for v in vects])) return dots def get_highlight(self, mobject: Mobject) -> Mobject: if isinstance(mobject, VMobject) and mobject.has_points( ) and not self.select_top_level_mobs: length = max([mobject.get_height(), mobject.get_width()]) result = VHighlight( mobject, max_stroke_addition=min([50 * length, 10]), ) result.add_updater(lambda m: m.replace(mobject, stretch=True)) return result elif isinstance(mobject, DotCloud): return Mobject() else: return self.get_corner_dots(mobject) def add_to_selection(self, *mobjects: Mobject): mobs = list( filter( lambda m: m not in self.unselectables and m not in self. selection, mobjects)) if len(mobs) == 0: return self.selection.add(*mobs) self.selection.set_animating_status(True) def toggle_from_selection(self, *mobjects: Mobject): for mob in mobjects: if mob in self.selection: self.selection.remove(mob) mob.set_animating_status(False) else: self.add_to_selection(mob) self.refresh_static_mobjects() def clear_selection(self): for mob in self.selection: mob.set_animating_status(False) self.selection.set_submobjects([]) self.refresh_static_mobjects() def disable_interaction(self, *mobjects: Mobject): for mob in mobjects: for sm in mob.get_family(): self.unselectables.append(sm) self.regenerate_selection_search_set() def enable_interaction(self, *mobjects: Mobject): for mob in mobjects: for sm in mob.get_family(): if sm in self.unselectables: self.unselectables.remove(sm) # Functions for keyboard actions def copy_selection(self): ids = map(id, self.selection) pyperclip.copy(",".join(map(str, ids))) def paste_selection(self): clipboard_str = pyperclip.paste() # Try pasting a mobject try: ids = map(int, clipboard_str.split(",")) mobs = map(self.id_to_mobject, ids) mob_copies = [m.copy() for m in mobs if m is not None] self.clear_selection() self.play(*(FadeIn(mc, run_time=0.5, scale=1.5) for mc in mob_copies)) self.add_to_selection(*mob_copies) return except ValueError: pass # Otherwise, treat as tex or text if set("\\^=+").intersection(clipboard_str): # Proxy to text for LaTeX try: new_mob = Tex(clipboard_str) except LatexError: return else: new_mob = Text(clipboard_str) self.clear_selection() self.add(new_mob) self.add_to_selection(new_mob) def delete_selection(self): self.remove(*self.selection) self.clear_selection() def enable_selection(self): self.is_selecting = True self.add(self.selection_rectangle) self.selection_rectangle.fixed_corner = self.mouse_point.get_center( ).copy() def gather_new_selection(self): self.is_selecting = False if self.selection_rectangle in self.mobjects: self.remove(self.selection_rectangle) additions = [] for mob in reversed(self.get_selection_search_set()): if self.selection_rectangle.is_touching(mob): additions.append(mob) self.add_to_selection(*additions) def prepare_grab(self): mp = self.mouse_point.get_center() self.mouse_to_selection = mp - self.selection.get_center() self.is_grabbing = True def prepare_resizing(self, about_corner=False): center = self.selection.get_center() mp = self.mouse_point.get_center() if about_corner: self.scale_about_point = self.selection.get_corner(center - mp) else: self.scale_about_point = center self.scale_ref_vect = mp - self.scale_about_point self.scale_ref_width = self.selection.get_width() self.scale_ref_height = self.selection.get_height() def toggle_color_palette(self): if len(self.selection) == 0: return if self.color_palette not in self.mobjects: self.save_state() self.add(self.color_palette) else: self.remove(self.color_palette) def display_information(self, show=True): if show: self.add(self.information_label) else: self.remove(self.information_label) def group_selection(self): group = self.get_group(*self.selection) self.add(group) self.clear_selection() self.add_to_selection(group) def ungroup_selection(self): pieces = [] for mob in list(self.selection): self.remove(mob) pieces.extend(list(mob)) self.clear_selection() self.add(*pieces) self.add_to_selection(*pieces) def nudge_selection(self, vect: np.ndarray, large: bool = False): nudge = self.selection_nudge_size if large: nudge *= 10 self.selection.shift(nudge * vect) def save_selection_to_file(self): if len(self.selection) == 1: self.save_mobject_to_file(self.selection[0]) else: self.save_mobject_to_file(self.selection) def on_key_press(self, symbol: int, modifiers: int) -> None: super().on_key_press(symbol, modifiers) char = chr(symbol) if char == SELECT_KEY and modifiers == 0: self.enable_selection() elif char in GRAB_KEYS and modifiers == 0: self.prepare_grab() elif char == RESIZE_KEY and modifiers in [0, SHIFT_MODIFIER]: self.prepare_resizing(about_corner=(modifiers == SHIFT_MODIFIER)) elif symbol == SHIFT_SYMBOL: if self.window.is_key_pressed(ord("t")): self.prepare_resizing(about_corner=True) elif char == COLOR_KEY and modifiers == 0: self.toggle_color_palette() elif char == INFORMATION_KEY and modifiers == 0: self.display_information() elif char == "c" and modifiers == COMMAND_MODIFIER: self.copy_selection() elif char == "v" and modifiers == COMMAND_MODIFIER: self.paste_selection() elif char == "x" and modifiers == COMMAND_MODIFIER: self.copy_selection() self.delete_selection() elif symbol == DELETE_SYMBOL: self.delete_selection() elif char == "a" and modifiers == COMMAND_MODIFIER: self.clear_selection() self.add_to_selection(*self.mobjects) elif char == "g" and modifiers == COMMAND_MODIFIER: self.group_selection() elif char == "g" and modifiers == COMMAND_MODIFIER | SHIFT_MODIFIER: self.ungroup_selection() elif char == "t" and modifiers == COMMAND_MODIFIER: self.toggle_selection_mode() elif char == "s" and modifiers == COMMAND_MODIFIER: self.save_selection_to_file() elif symbol in ARROW_SYMBOLS: self.nudge_selection( vect=[LEFT, UP, RIGHT, DOWN][ARROW_SYMBOLS.index(symbol)], large=(modifiers & SHIFT_MODIFIER), ) # Adding crosshair if char == CURSOR_KEY: if self.crosshair in self.mobjects: self.remove(self.crosshair) else: self.add(self.crosshair) if char == SELECT_KEY: self.add(self.crosshair) # Conditions for saving state if char in [GRAB_KEY, X_GRAB_KEY, Y_GRAB_KEY, RESIZE_KEY]: self.save_state() def on_key_release(self, symbol: int, modifiers: int) -> None: super().on_key_release(symbol, modifiers) if chr(symbol) == SELECT_KEY: self.gather_new_selection() # self.remove(self.crosshair) if chr(symbol) in GRAB_KEYS: self.is_grabbing = False elif chr(symbol) == INFORMATION_KEY: self.display_information(False) elif symbol == SHIFT_SYMBOL and self.window.is_key_pressed( ord(RESIZE_KEY)): self.prepare_resizing(about_corner=False) # Mouse actions def handle_grabbing(self, point: np.ndarray): diff = point - self.mouse_to_selection if self.window.is_key_pressed(ord(GRAB_KEY)): self.selection.move_to(diff) elif self.window.is_key_pressed(ord(X_GRAB_KEY)): self.selection.set_x(diff[0]) elif self.window.is_key_pressed(ord(Y_GRAB_KEY)): self.selection.set_y(diff[1]) def handle_resizing(self, point: np.ndarray): if not hasattr(self, "scale_about_point"): return vect = point - self.scale_about_point if self.window.is_key_pressed(CTRL_SYMBOL): for i in (0, 1): scalar = vect[i] / self.scale_ref_vect[i] self.selection.rescale_to_fit( scalar * [self.scale_ref_width, self.scale_ref_height][i], dim=i, about_point=self.scale_about_point, stretch=True, ) else: scalar = get_norm(vect) / get_norm(self.scale_ref_vect) self.selection.set_width(scalar * self.scale_ref_width, about_point=self.scale_about_point) def handle_sweeping_selection(self, point: np.ndarray): mob = self.point_to_mobject(point, search_set=self.get_selection_search_set(), buff=SMALL_BUFF) if mob is not None: self.add_to_selection(mob) def choose_color(self, point: np.ndarray): # Search through all mobject on the screen, not just the palette to_search = [ sm for mobject in self.mobjects for sm in mobject.family_members_with_points() if mobject not in self.unselectables ] mob = self.point_to_mobject(point, to_search) if mob is not None: self.selection.set_color(mob.get_color()) self.remove(self.color_palette) def on_mouse_motion(self, point: np.ndarray, d_point: np.ndarray) -> None: super().on_mouse_motion(point, d_point) self.crosshair.move_to(point) if self.is_grabbing: self.handle_grabbing(point) elif self.window.is_key_pressed(ord(RESIZE_KEY)): self.handle_resizing(point) elif self.window.is_key_pressed( ord(SELECT_KEY)) and self.window.is_key_pressed(SHIFT_SYMBOL): self.handle_sweeping_selection(point) def on_mouse_release(self, point: np.ndarray, button: int, mods: int) -> None: super().on_mouse_release(point, button, mods) if self.color_palette in self.mobjects: self.choose_color(point) return mobject = self.point_to_mobject( point, search_set=self.get_selection_search_set(), buff=1e-4, ) if mobject is not None: self.toggle_from_selection(mobject) else: self.clear_selection()
def get_selection_highlight(self): result = Group() result.tracked_mobjects = [] result.add_updater(self.update_selection_highlight) return result
def __init__(self, screen01=[], screen02=[], screen03=[], lag_ratio=1, **kwargs): mobjs_1, mobjs_1_scale, mobjs_1_interval, mobjs_1_rate_func, \ title_1, title_1_color, title_1_scale, title_1_position, title_1_shadow, \ title_1_indicate_scale_factor, title_1_extra = screen01 + \ ["", 2, 0.5, linear, "", "#0808B8", 2, [DOWN], [2, slice(0, 3, 2)], 1.2, ""][len(screen01):] mobjs_2, mobjs_2_scale, mobjs_2_interval, mobjs_2_rate_func, \ title_2, title_2_color, title_2_scale, title_2_position, title_2_shadow, \ title_2_indicate_scale_factor, title_2_extra = screen02 + \ ["", 2, 0.5, linear, "", "#0808B8", 1.2, [DOWN], [2, slice(1, 4, 2)], "", [slice(1, 2), WHITE, [-2.5, 0, 0]]][len(screen02):] [title, subtitle, filename, reference, warning, mobjes_3_run_time] =\ screen03 + [[], [], [], [], [], 3][len(screen03):] [title, title_color, title_scale, title_position] = title + \ ["", WHITE, 1, [UP]][len(title):] [subtitle, subtitle_color, subtitle_scale, subtitle_position] = subtitle + \ ["", WHITE, 1, [0, 0, 0]][len(subtitle):] [filename, filename_color, filename_scale, filename_position] = filename + \ ["", WHITE, 1, [0, -2.9, 0]][len(filename):] [reference, reference_color, reference_scale, reference_position] = reference + \ ["", YELLOW, 1, [0, -3.3, 0]][len(reference):] [warning, warning_color, warning_scale, warning_position] = warning + \ ["", YELLOW, 1, [0, -3.7, 0] ][len(warning):] startscreens = AGroup() if mobjs_1 != None: if mobjs_1 == "": try: mobjs_1 = ImageMobjectGroup( np.char.mod('%01d', range(0, 10)), "sidewayoutput\\sidewayoutput2020yt") except: mobjs_1 = ImageMobjectGroup( np.char.mod('%01d', range(9, -1, -1)), "001\\") if title_1 == "": title_1 = PoweredBy title_1 = MobjectOrChars(title_1) title_1.set_color(title_1_color).scale( title_1_scale).align_on_border( *title_1_position).add_shadow_mobjects( title_1_shadow[0], title_1[title_1_shadow[1]]) if title_1_extra != "": title_1[title_1_extra[0]].set_color(title_1_extra[1]).shift( title_1_extra[2]) if title_1_indicate_scale_factor == "": title_width = mobjs_1.get_width() title_1_indicate_scale_factor = (title_width - 0.5) / title_width startscreens.add( ShowSubmobjectsOneByOneAndFadeInThenIndicateThenFadeOut( mobjs_1.scale(mobjs_1_scale), title_1, indicate_scale_factor=title_1_indicate_scale_factor, show_rate_func=mobjs_1_rate_func, run_time=mobjs_1_interval * (len(mobjs_1)), **kwargs)) if mobjs_2 != None: if mobjs_2 == "": strs = TextMobject(r"\textspA{%s}" % Project) mobjs_2 = Group( Circle(fill_opacity=0.75), RegularPolygon(fill_opacity=0.75), Triangle(color=GREEN, fill_opacity=0.75), Square(fill_opacity=0.75), strs.set_color("#FFFFFF"), strs.copy().set_color("#F8F8F8").scale(1.3), strs.copy().set_color("#F8F8B8").scale(1.6), strs.copy().set_color("#B8B8B8").scale(1.6), strs.copy().set_color("#8888B8").scale(1.6), strs.copy().set_color("#6868B8").scale(1.6), strs.copy().set_color("#4848B8").scale(1.6), strs.copy().set_color("#2828B8").scale(1.6), strs.copy().set_color("#0808B8").scale(1.6), ) if title_2 == "": title_2 = (r"{\tiny{\emph{Powered by}:}}\\ ", *PoweredBy) title_2 = MobjectOrChars(title_2) title_2.set_color(title_2_color).scale( title_2_scale).align_on_border( *title_2_position).add_shadow_mobjects( title_2_shadow[0], title_2[title_2_shadow[1]]) if title_2_extra != "": title_2[title_2_extra[0]].set_color(title_2_extra[1]).shift( title_2_extra[2]) if title_2_indicate_scale_factor == "": title_width = mobjs_2.get_width() title_2_indicate_scale_factor = (title_width - 0.5) / title_width startscreens.add( ShowSubmobjectsOneByOneAndFadeInThenIndicateThenFadeOut( mobjs_2.scale(mobjs_2_scale), title_2, indicate_scale_factor=title_2_indicate_scale_factor, show_rate_func=mobjs_2_rate_func, run_time=mobjs_2_interval * (len(mobjs_2)), **kwargs)) if title != None or subtitle != None: mobjs_3 = [Group(), "", ""] if title != None: txt_title = TextMobject(title).scale(title_scale) if txt_title.get_width() > 14: txt_title.stretch_to_fit_width(14) mobjs_3[1] = txt_title.set_color(title_color).to_edge( *title_position) mobjs_3[0].add(mobjs_3[1]) if subtitle != None: mobjs_3[0].add( TextMobject(subtitle).set_color(subtitle_color).scale( subtitle_scale).shift(subtitle_position)) if filename != None and filename != "": if reference == None or reference == "": filename_position = reference_position mobjs_3[0].add( TextMobject(filename).set_color(filename_color).scale( filename_scale).shift(filename_position)) if reference != None and reference != "": txt_reference = TextMobject(reference).scale(reference_scale) if txt_reference.get_width() > 14: txt_reference.stretch_to_fit_width(14) mobjs_3[0].add( txt_reference.set_color(reference_color).shift( reference_position)) if warning != None and warning != "": txt_warning = TextMobject(warning).scale( warning_scale) # height=0.3 if txt_warning.get_width() > 14: txt_warning.stretch_to_fit_width(14) mobjs_3[2] = txt_warning.set_color(warning_color).shift( warning_position) animations = AGroup() if len(mobjs_3[0]) > 0: animations.add( FadeIn(mobjs_3[0], run_time=0.5, scale_factor=1, color=None)) if len(mobjs_3[1]) > 0: animations.add(GrowFromCenter(Underline(mobjs_3[1]))) if len(mobjs_3[2]) > 0: animations.add( FadeInThenIndicate(mobjs_3[2], run_time=0.5, scale_factor=1.2, color=None)) startscreens.add( FadeoutSuccession(AnimationGroup(*animations, run_time=mobjes_3_run_time), run_time=0.05)) super().__init__(AnimationGroup(*startscreens, lag_ratio=1), **kwargs)
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 InteractiveScene(Scene): """ To select mobjects on screen, hold ctrl and move the mouse to highlight a region, or just tap ctrl to select the mobject under the cursor. Pressing command + t will toggle between modes where you either select top level mobjects part of the scene, or low level pieces. Hold 'g' to grab the selection and move it around Hold 'h' to drag it constrained in the horizontal direction Hold 'v' to drag it constrained in the vertical direction Hold 't' to resize selection, adding 'shift' to resize with respect to a corner Command + 'c' copies the ids of selections to clipboard Command + 'v' will paste either: - The copied mobject - A Tex mobject based on copied LaTeX - A Text mobject based on copied Text Command + 'z' restores selection back to its original state Command + 's' saves the selected mobjects to file """ corner_dot_config = dict( color=WHITE, radius=0.05, glow_factor=1.0, ) selection_rectangle_stroke_color = WHITE selection_rectangle_stroke_width = 1.0 colors = MANIM_COLORS selection_nudge_size = 0.05 def setup(self): self.selection = Group() self.selection_highlight = Group() self.selection_rectangle = self.get_selection_rectangle() self.color_palette = self.get_color_palette() self.unselectables = [ self.selection, self.selection_highlight, self.selection_rectangle, self.camera.frame ] self.saved_selection_state = [] self.select_top_level_mobs = True self.is_selecting = False self.add(self.selection_highlight) def toggle_selection_mode(self): self.select_top_level_mobs = not self.select_top_level_mobs self.refresh_selection_scope() def get_selection_search_set(self): mobs = [m for m in self.mobjects if m not in self.unselectables] if self.select_top_level_mobs: return mobs else: return [ submob for mob in mobs for submob in mob.family_members_with_points() ] def refresh_selection_scope(self): curr = list(self.selection) if self.select_top_level_mobs: self.selection.set_submobjects([ mob for mob in self.mobjects if any(sm in mob.get_family() for sm in curr) ]) self.selection.refresh_bounding_box(recurse_down=True) else: self.selection.set_submobjects( extract_mobject_family_members( curr, exclude_pointless=True, ) ) self.refresh_selection_highlight() def get_selection_rectangle(self): rect = Rectangle( stroke_color=self.selection_rectangle_stroke_color, stroke_width=self.selection_rectangle_stroke_width, ) rect.fix_in_frame() rect.fixed_corner = ORIGIN rect.add_updater(self.update_selection_rectangle) return rect def get_color_palette(self): palette = VGroup(*( Square(fill_color=color, fill_opacity=1, side_length=1) for color in self.colors )) palette.set_stroke(width=0) palette.arrange(RIGHT, buff=0.5) palette.set_width(FRAME_WIDTH - 0.5) palette.to_edge(DOWN, buff=SMALL_BUFF) palette.fix_in_frame() return palette def get_stroke_highlight(self, vmobject): outline = vmobject.copy() for sm, osm in zip(vmobject.get_family(), outline.get_family()): osm.set_fill(opacity=0) osm.set_stroke(YELLOW, width=sm.get_stroke_width() + 1.5) outline.add_updater(lambda o: o.replace(vmobject)) return outline def get_corner_dots(self, mobject): dots = DotCloud(**self.corner_dot_config) radius = self.corner_dot_config["radius"] if mobject.get_depth() < 1e-2: vects = [DL, UL, UR, DR] else: vects = list(it.product(*3 * [[-1, 1]])) dots.add_updater(lambda d: d.set_points([ mobject.get_corner(v) + v * radius for v in vects ])) return dots def get_highlight(self, mobject): if isinstance(mobject, VMobject) and mobject.has_points(): return self.get_stroke_highlight(mobject) else: return self.get_corner_dots(mobject) def refresh_selection_highlight(self): self.selection_highlight.set_submobjects([ self.get_highlight(mob) for mob in self.selection ]) def update_selection_rectangle(self, rect): p1 = rect.fixed_corner p2 = self.mouse_point.get_center() rect.set_points_as_corners([ p1, [p2[0], p1[1], 0], p2, [p1[0], p2[1], 0], p1, ]) return rect def add_to_selection(self, *mobjects): mobs = list(filter(lambda m: m not in self.unselectables, mobjects)) self.selection.add(*mobjects) self.selection_highlight.add(*map(self.get_highlight, mobs)) self.saved_selection_state = [(mob, mob.copy()) for mob in self.selection] def toggle_from_selection(self, *mobjects): for mob in mobjects: if mob in self.selection: self.selection.remove(mob) else: self.add_to_selection(mob) self.refresh_selection_highlight() def clear_selection(self): self.selection.set_submobjects([]) self.selection_highlight.set_submobjects([]) def add(self, *new_mobjects: Mobject): for mob in new_mobjects: mob.make_movable() super().add(*new_mobjects) # Selection operations def copy_selection(self): ids = map(id, self.selection) pyperclip.copy(",".join(map(str, ids))) def paste_selection(self): clipboard_str = pyperclip.paste() # Try pasting a mobject try: ids = map(int, clipboard_str.split(",")) mobs = map(self.id_to_mobject, ids) mob_copies = [m.copy() for m in mobs if m is not None] self.clear_selection() self.add_to_selection(*mob_copies) self.play(*( FadeIn(mc, run_time=0.5, scale=1.5) for mc in mob_copies )) return except ValueError: pass # Otherwise, treat as tex or text if set("\\^=+").intersection(clipboard_str): # Proxy to text for LaTeX try: new_mob = Tex(clipboard_str) except LatexError: return else: new_mob = Text(clipboard_str) self.clear_selection() self.add(new_mob) self.add_to_selection(new_mob) def delete_selection(self): self.remove(*self.selection) self.clear_selection() def saved_selection_to_file(self): directory = self.file_writer.get_saved_mobject_directory() files = os.listdir(directory) for mob in self.selection: file_name = str(mob) + "_0.mob" index = 0 while file_name in files: file_name = file_name.replace(str(index), str(index + 1)) index += 1 if platform.system() == 'Darwin': user_name = os.popen(f""" osascript -e ' set chosenfile to (choose file name default name "{file_name}" default location "{directory}") POSIX path of chosenfile' """).read() user_name = user_name.replace("\n", "") else: user_name = input( f"Enter mobject file name (default is {file_name}): " ) if user_name: file_name = user_name files.append(file_name) self.save_mobect(mob, file_name) def undo(self): mobs = [] for mob, state in self.saved_selection_state: mob.become(state) mobs.append(mob) if mob not in self.mobjects: self.add(mob) self.selection.set_submobjects(mobs) self.refresh_selection_highlight() def prepare_resizing(self, about_corner=False): center = self.selection.get_center() mp = self.mouse_point.get_center() if about_corner: self.scale_about_point = self.selection.get_corner(center - mp) else: self.scale_about_point = center self.scale_ref_vect = mp - self.scale_about_point self.scale_ref_width = self.selection.get_width() # Event handlers def on_key_press(self, symbol: int, modifiers: int) -> None: super().on_key_press(symbol, modifiers) char = chr(symbol) # Enable selection if char == SELECT_KEY and modifiers == 0: self.is_selecting = True self.add(self.selection_rectangle) self.selection_rectangle.fixed_corner = self.mouse_point.get_center().copy() # Prepare for move elif char in [GRAB_KEY, HORIZONTAL_GRAB_KEY, VERTICAL_GRAB_KEY] and modifiers == 0: mp = self.mouse_point.get_center() self.mouse_to_selection = mp - self.selection.get_center() # Prepare for resizing elif char == RESIZE_KEY and modifiers in [0, SHIFT_MODIFIER]: self.prepare_resizing(about_corner=(modifiers == SHIFT_MODIFIER)) elif symbol == SHIFT_SYMBOL: if self.window.is_key_pressed(ord("t")): self.prepare_resizing(about_corner=True) # Show color palette elif char == COLOR_KEY and modifiers == 0: if len(self.selection) == 0: return if self.color_palette not in self.mobjects: self.add(self.color_palette) else: self.remove(self.color_palette) # Command + c -> Copy mobject ids to clipboard elif char == "c" and modifiers == COMMAND_MODIFIER: self.copy_selection() # Command + v -> Paste elif char == "v" and modifiers == COMMAND_MODIFIER: self.paste_selection() # Command + x -> Cut elif char == "x" and modifiers == COMMAND_MODIFIER: # TODO, this copy won't work, because once the objects are removed, # they're not searched for in the pasting. self.copy_selection() self.delete_selection() # Delete elif symbol == DELETE_SYMBOL: self.delete_selection() # Command + a -> Select all elif char == "a" and modifiers == COMMAND_MODIFIER: self.clear_selection() self.add_to_selection(*self.mobjects) # Command + g -> Group selection elif char == "g" and modifiers == COMMAND_MODIFIER: group = self.get_group(*self.selection) self.add(group) self.clear_selection() self.add_to_selection(group) # Command + shift + g -> Ungroup the selection elif char == "g" and modifiers == COMMAND_MODIFIER | SHIFT_MODIFIER: pieces = [] for mob in list(self.selection): self.remove(mob) pieces.extend(list(mob)) self.clear_selection() self.add(*pieces) self.add_to_selection(*pieces) # Command + t -> Toggle selection mode elif char == "t" and modifiers == COMMAND_MODIFIER: self.toggle_selection_mode() # Command + z -> Restore selection to original state elif char == "z" and modifiers == COMMAND_MODIFIER: self.undo() # Command + s -> Save selections to file elif char == "s" and modifiers == COMMAND_MODIFIER: self.saved_selection_to_file() # Keyboard movements elif symbol in ARROW_SYMBOLS: nudge = self.selection_nudge_size if (modifiers & SHIFT_MODIFIER): nudge *= 10 vect = [LEFT, UP, RIGHT, DOWN][ARROW_SYMBOLS.index(symbol)] self.selection.shift(nudge * vect) def on_key_release(self, symbol: int, modifiers: int) -> None: super().on_key_release(symbol, modifiers) if chr(symbol) == SELECT_KEY: self.is_selecting = False self.remove(self.selection_rectangle) for mob in reversed(self.get_selection_search_set()): if mob.is_movable() and self.selection_rectangle.is_touching(mob): self.add_to_selection(mob) elif symbol == SHIFT_SYMBOL: if self.window.is_key_pressed(ord(RESIZE_KEY)): self.prepare_resizing(about_corner=False) def on_mouse_motion(self, point: np.ndarray, d_point: np.ndarray) -> None: super().on_mouse_motion(point, d_point) # Move selection if self.window.is_key_pressed(ord("g")): self.selection.move_to(point - self.mouse_to_selection) # Move selection restricted to horizontal elif self.window.is_key_pressed(ord("h")): self.selection.set_x((point - self.mouse_to_selection)[0]) # Move selection restricted to vertical elif self.window.is_key_pressed(ord("v")): self.selection.set_y((point - self.mouse_to_selection)[1]) # Scale selection elif self.window.is_key_pressed(ord("t")): # TODO, allow for scaling about the opposite corner vect = point - self.scale_about_point scalar = get_norm(vect) / get_norm(self.scale_ref_vect) self.selection.set_width( scalar * self.scale_ref_width, about_point=self.scale_about_point ) def on_mouse_release(self, point: np.ndarray, button: int, mods: int) -> None: super().on_mouse_release(point, button, mods) if self.color_palette in self.mobjects: # Search through all mobject on the screne, not just the palette to_search = list(it.chain(*( mobject.family_members_with_points() for mobject in self.mobjects if mobject not in self.unselectables ))) mob = self.point_to_mobject(point, to_search) if mob is not None: self.selection.set_color(mob.get_fill_color()) self.remove(self.color_palette) elif self.window.is_key_pressed(SHIFT_SYMBOL): mob = self.point_to_mobject(point) if mob is not None: self.toggle_from_selection(mob) else: self.clear_selection()
def __init__(self, *mobjects, **kwargs): self.group = Group(*mobjects) super().__init__(self.group, **kwargs)
def __init__(self, *vmobjects, **kwargs): if not all([isinstance(m, VMobject) for m in vmobjects]): raise Exception("All submobjects must be of type VMobject") VMobject.__init__(self, **kwargs) Group.add(self, *vmobjects)
class ControlPanel(Group): CONFIG = { "panel_kwargs": { "width": FRAME_WIDTH / 4, "height": MED_SMALL_BUFF + FRAME_HEIGHT, "fill_color": GREY_C, "fill_opacity": 1.0, "stroke_width": 0.0 }, "opener_kwargs": { "width": FRAME_WIDTH / 8, "height": 0.5, "fill_color": GREY_C, "fill_opacity": 1.0 }, "opener_text_kwargs": { "text": "Control Panel", "font_size": 20 } } def __init__(self, *controls: ControlMobject, **kwargs): digest_config(self, kwargs) self.panel = Rectangle(**self.panel_kwargs) self.panel.to_corner(UP + LEFT, buff=0) self.panel.shift(self.panel.get_height() * UP) self.panel.add_mouse_scroll_listner(self.panel_on_mouse_scroll) self.panel_opener_rect = Rectangle(**self.opener_kwargs) self.panel_info_text = Text(**self.opener_text_kwargs) self.panel_info_text.move_to(self.panel_opener_rect) self.panel_opener = Group(self.panel_opener_rect, self.panel_info_text) self.panel_opener.next_to(self.panel, DOWN, aligned_edge=DOWN) self.panel_opener.add_mouse_drag_listner(self.panel_opener_on_mouse_drag) self.controls = Group(*controls) self.controls.arrange(DOWN, center=False, aligned_edge=ORIGIN) self.controls.move_to(self.panel) super().__init__( self.panel, self.panel_opener, self.controls, **kwargs ) self.move_panel_and_controls_to_panel_opener() self.fix_in_frame() def move_panel_and_controls_to_panel_opener(self) -> None: self.panel.next_to( self.panel_opener_rect, direction=UP, buff=0 ) controls_old_x = self.controls.get_x() self.controls.next_to( self.panel_opener_rect, direction=UP, buff=MED_SMALL_BUFF ) self.controls.set_x(controls_old_x) def add_controls(self, *new_controls: ControlMobject) -> None: self.controls.add(*new_controls) self.move_panel_and_controls_to_panel_opener() def remove_controls(self, *controls_to_remove: ControlMobject) -> None: self.controls.remove(*controls_to_remove) self.move_panel_and_controls_to_panel_opener() def open_panel(self): panel_opener_x = self.panel_opener.get_x() self.panel_opener.to_corner(DOWN + LEFT, buff=0.0) self.panel_opener.set_x(panel_opener_x) self.move_panel_and_controls_to_panel_opener() return self def close_panel(self): panel_opener_x = self.panel_opener.get_x() self.panel_opener.to_corner(UP + LEFT, buff=0.0) self.panel_opener.set_x(panel_opener_x) self.move_panel_and_controls_to_panel_opener() return self def panel_opener_on_mouse_drag(self, mob, event_data: dict[str, np.ndarray]) -> bool: point = event_data["point"] self.panel_opener.match_y(Dot(point)) self.move_panel_and_controls_to_panel_opener() return False def panel_on_mouse_scroll(self, mob, event_data: dict[str, np.ndarray]) -> bool: offset = event_data["offset"] factor = 10 * offset[1] self.controls.set_y(self.controls.get_y() + factor) return False
class AnimationGroup(Animation): CONFIG = { # If None, this defaults to the sum of all # internal animations "run_time": None, "rate_func": linear, # If 0, all animations are played at once. # If 1, all are played successively. # If >0 and <1, they start at lagged times # from one and other. "lag_ratio": 0, "group": None, } def __init__(self, *animations: Animation, **kwargs): digest_config(self, kwargs) self.animations = [prepare_animation(anim) for anim in animations] if self.group is None: self.group = Group(*remove_list_redundancies( [anim.mobject for anim in animations])) self.init_run_time() Animation.__init__(self, self.group, **kwargs) def get_all_mobjects(self) -> Group: return self.group def begin(self) -> None: self.group.set_animating_status(True) for anim in self.animations: anim.begin() # self.init_run_time() def finish(self) -> None: self.group.set_animating_status(False) for anim in self.animations: anim.finish() def clean_up_from_scene(self, scene: Scene) -> None: for anim in self.animations: anim.clean_up_from_scene(scene) def update_mobjects(self, dt: float) -> None: for anim in self.animations: anim.update_mobjects(dt) def init_run_time(self) -> None: self.build_animations_with_timings() if self.anims_with_timings: self.max_end_time = np.max( [awt[2] for awt in self.anims_with_timings]) else: self.max_end_time = 0 if self.run_time is None: self.run_time = self.max_end_time def build_animations_with_timings(self) -> None: """ Creates a list of triplets of the form (anim, start_time, end_time) """ self.anims_with_timings = [] curr_time = 0 for anim in self.animations: start_time = curr_time end_time = start_time + anim.get_run_time() self.anims_with_timings.append((anim, start_time, end_time)) # Start time of next animation is based on # the lag_ratio curr_time = interpolate(start_time, end_time, self.lag_ratio) def interpolate(self, alpha: float) -> None: # Note, if the run_time of AnimationGroup has been # set to something other than its default, these # times might not correspond to actual times, # e.g. of the surrounding scene. Instead they'd # be a rescaled version. But that's okay! time = alpha * self.max_end_time for anim, start_time, end_time in self.anims_with_timings: anim_time = end_time - start_time if anim_time == 0: sub_alpha = 0 else: sub_alpha = clip((time - start_time) / anim_time, 0, 1) anim.interpolate(sub_alpha)
class ControlPanel(Group): CONFIG = { "listen_to_events": True, "panel_kwargs": { "width": FRAME_WIDTH / 4, "height": MED_SMALL_BUFF + FRAME_HEIGHT, "fill_color": GREY_C, "fill_opacity": 1.0, "stroke_width": 0.0 }, "opener_kwargs": { "width": FRAME_WIDTH / 8, "height": 0.5, "fill_color": GREY_C, "fill_opacity": 1.0 }, "opener_text_kwargs": { "text": "Control Panel", "size": 0.4 } } def __init__(self, *controls, **kwargs): digest_config(self, kwargs) self.panel = Rectangle(**self.panel_kwargs) self.panel.to_corner(UP + LEFT, buff=0) self.panel.shift(self.panel.get_height() * UP) self.panel_opener_rect = Rectangle(**self.opener_kwargs) self.panel_info_text = Text(**self.opener_text_kwargs) self.panel_info_text.move_to(self.panel_opener_rect) self.panel_opener = Group(self.panel_opener_rect, self.panel_info_text) self.panel_opener.next_to(self.panel, DOWN, aligned_edge=DOWN) self.controls = Group(*controls) self.controls.arrange(DOWN, center=False, aligned_edge=ORIGIN) self.controls.move_to(self.panel) super().__init__(self.panel, self.panel_opener, self.controls, **kwargs) self.move_panel_and_controls_to_panel_opener() self.fix_in_frame() def move_panel_and_controls_to_panel_opener(self): self.panel.next_to(self.panel_opener_rect, direction=UP, buff=0) controls_old_x = self.controls.get_x() self.controls.next_to(self.panel_opener_rect, direction=UP, buff=MED_SMALL_BUFF) self.controls.set_x(controls_old_x) def add_controls(self, *new_controls): self.controls.add(*new_controls) self.move_panel_and_controls_to_panel_opener() def remove_controls(self, *controls_to_remove): self.controls.remove(*controls_to_remove) self.move_panel_and_controls_to_panel_opener() def open_panel(self): panel_opener_x = self.panel_opener.get_x() self.panel_opener.to_corner(DOWN + LEFT, buff=0.0) self.panel_opener.set_x(panel_opener_x) self.move_panel_and_controls_to_panel_opener() return self def close_panel(self): panel_opener_x = self.panel_opener.get_x() self.panel_opener.to_corner(UP + LEFT, buff=0.0) self.panel_opener.set_x(panel_opener_x) self.move_panel_and_controls_to_panel_opener() return self def on_mouse_drag(self, point, d_point, buttons, modifiers): if self.panel_opener.is_point_touching(point): self.panel_opener.match_y(Dot(point)) self.move_panel_and_controls_to_panel_opener() return False def on_mouse_scroll(self, point, offset): if self.panel.is_point_touching(point): factor = 10 * offset[1] self.controls.set_y(self.controls.get_y() + factor) return False
def __init__(self, *args, **kwargs): return OldAnimation.__init__(self, Group(), *args, **kwargs)
def __init__(self, group, **kwargs): new_group = Group(*group) super().__init__(new_group, **kwargs)
def fadein(self, *mobjects, run_time=1, pre_time=0.5, post_time=1): self.wait(pre_time) self.play(FadeIn(Group(*mobjects))) self.wait(post_time) return self