Kill CONFIG in drawings.py

This commit is contained in:
Grant Sanderson 2022-12-15 14:28:34 -08:00
parent 3b5181d1a3
commit c8d01e7a43

View file

@ -1,4 +1,7 @@
from operator import le
from os import DirEntry
from manimlib.animation.animation import Animation from manimlib.animation.animation import Animation
from manimlib.animation.composition import AnimationGroup
from manimlib.animation.rotation import Rotating from manimlib.animation.rotation import Rotating
from manimlib.constants import * from manimlib.constants import *
from manimlib.mobject.boolean_ops import Difference from manimlib.mobject.boolean_ops import Difference
@ -13,7 +16,7 @@ from manimlib.mobject.svg.svg_mobject import SVGMobject
from manimlib.mobject.svg.tex_mobject import Tex from manimlib.mobject.svg.tex_mobject import Tex
from manimlib.mobject.svg.tex_mobject import TexText from manimlib.mobject.svg.tex_mobject import TexText
from manimlib.mobject.svg.tex_mobject import TexTextFromPresetString from manimlib.mobject.svg.tex_mobject import TexTextFromPresetString
from manimlib.mobject.three_dimensions import Cube from manimlib.mobject.three_dimensions import VCube
from manimlib.mobject.three_dimensions import Prismify from manimlib.mobject.three_dimensions import Prismify
from manimlib.mobject.types.vectorized_mobject import VGroup from manimlib.mobject.types.vectorized_mobject import VGroup
from manimlib.mobject.types.vectorized_mobject import VMobject from manimlib.mobject.types.vectorized_mobject import VMobject
@ -23,6 +26,12 @@ from manimlib.utils.space_ops import angle_of_vector
from manimlib.utils.space_ops import complex_to_R3 from manimlib.utils.space_ops import complex_to_R3
from manimlib.utils.space_ops import midpoint from manimlib.utils.space_ops import midpoint
from manimlib.utils.space_ops import rotate_vector from manimlib.utils.space_ops import rotate_vector
from manimlib.utils.space_ops import compass_directions
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from typing import Tuple, Sequence, Callable
from manimlib.constants import ManimColor, np_vector
class Checkmark(TexTextFromPresetString): class Checkmark(TexTextFromPresetString):
tex: str = R"\ding{51}" tex: str = R"\ding{51}"
@ -34,45 +43,61 @@ class Exmark(TexTextFromPresetString):
default_color: str = RED default_color: str = RED
class Lightbulb(SVGMobject): class Lightbulb(SVGMobject):
CONFIG = { file_name = "lightbulb"
"height": 1,
"stroke_color": YELLOW,
"stroke_width": 3,
"fill_color": YELLOW,
"fill_opacity": 0,
}
def __init__(self, **kwargs): def __init__(
super().__init__("lightbulb", **kwargs) self,
height: float = 1.0,
color: ManimColor = YELLOW,
stroke_width: float = 3.0,
fill_opacity: float = 0.0,
**kwargs
):
super().__init__(
height=height,
color=color,
stroke_width=stroke_width,
fill_opacity=fill_opacity,
**kwargs
)
self.insert_n_curves(25) self.insert_n_curves(25)
class Speedometer(VMobject): class Speedometer(VMobject):
CONFIG = { def __init__(
"arc_angle": 4 * np.pi / 3, self,
"num_ticks": 8, arc_angle: float = 4 * np.pi / 3,
"tick_length": 0.2, num_ticks: int = 8,
"needle_width": 0.1, tick_length: float = 0.2,
"needle_height": 0.8, needle_width: float = 0.1,
"needle_color": YELLOW, needle_height: float = 0.8,
} needle_color: ManimColor = YELLOW,
**kwargs,
):
super().__init__(**kwargs)
def init_points(self): self.arc_angle = arc_angle
start_angle = np.pi / 2 + self.arc_angle / 2 self.num_ticks = num_ticks
end_angle = np.pi / 2 - self.arc_angle / 2 self.tick_length = tick_length
self.add(Arc( self.needle_width = needle_width
self.needle_height = needle_height
self.needle_color = needle_color
start_angle = np.pi / 2 + arc_angle / 2
end_angle = np.pi / 2 - arc_angle / 2
self.arc = Arc(
start_angle=start_angle, start_angle=start_angle,
angle=-self.arc_angle angle=-self.arc_angle
)) )
tick_angle_range = np.linspace(start_angle, end_angle, self.num_ticks) self.add(self.arc)
tick_angle_range = np.linspace(start_angle, end_angle, num_ticks)
for index, angle in enumerate(tick_angle_range): for index, angle in enumerate(tick_angle_range):
vect = rotate_vector(RIGHT, angle) vect = rotate_vector(RIGHT, angle)
tick = Line((1 - self.tick_length) * vect, vect) tick = Line((1 - tick_length) * vect, vect)
label = Tex(str(10 * index)) label = Tex(str(10 * index))
label.set_height(self.tick_length) label.set_height(tick_length)
label.shift((1 + self.tick_length) * vect) label.shift((1 + tick_length) * vect)
self.add(tick, label) self.add(tick, label)
needle = Polygon( needle = Polygon(
@ -81,8 +106,8 @@ class Speedometer(VMobject):
fill_opacity=1, fill_opacity=1,
fill_color=self.needle_color fill_color=self.needle_color
) )
needle.stretch_to_fit_width(self.needle_width) needle.stretch_to_fit_width(needle_width)
needle.stretch_to_fit_height(self.needle_height) needle.stretch_to_fit_height(needle_height)
needle.rotate(start_angle - np.pi / 2, about_point=ORIGIN) needle.rotate(start_angle - np.pi / 2, about_point=ORIGIN)
self.add(needle) self.add(needle)
self.needle = needle self.needle = needle
@ -104,7 +129,7 @@ class Speedometer(VMobject):
) )
def rotate_needle(self, angle): def rotate_needle(self, angle):
self.needle.rotate(angle, about_point=self.get_center()) self.needle.rotate(angle, about_point=self.arc.get_arc_center())
return self return self
def move_needle_to_velocity(self, velocity): def move_needle_to_velocity(self, velocity):
@ -117,66 +142,67 @@ class Speedometer(VMobject):
class Laptop(VGroup): class Laptop(VGroup):
CONFIG = { def __init__(
"width": 3, self,
"body_dimensions": [4, 3, 0.05], width: float = 3,
"screen_thickness": 0.01, body_dimensions: Tuple[float, float, float] = (4.0, 3.0, 0.05),
"keyboard_width_to_body_width": 0.9, screen_thickness: float = 0.01,
"keyboard_height_to_body_height": 0.5, keyboard_width_to_body_width: float = 0.9,
"screen_width_to_screen_plate_width": 0.9, keyboard_height_to_body_height: float = 0.5,
"key_color_kwargs": { screen_width_to_screen_plate_width: float = 0.9,
"stroke_width": 0, key_color_kwargs: dict = dict(
"fill_color": BLACK, stroke_width=0,
"fill_opacity": 1, fill_color=BLACK,
}, fill_opacity=1,
"fill_opacity": 1, ),
"stroke_width": 0, fill_opacity: float = 1.0,
"body_color": GREY_B, stroke_width: float = 0.0,
"shaded_body_color": GREY, body_color: ManimColor = GREY_B,
"open_angle": np.pi / 4, shaded_body_color: ManimColor = GREY,
} open_angle: float = np.pi / 4,
**kwargs
def __init__(self, **kwargs): ):
super().__init__(**kwargs) super().__init__(**kwargs)
body = Cube(side_length=1)
for dim, scale_factor in enumerate(self.body_dimensions): body = VCube(side_length=1)
for dim, scale_factor in enumerate(body_dimensions):
body.stretch(scale_factor, dim=dim) body.stretch(scale_factor, dim=dim)
body.set_width(self.width) body.set_width(width)
body.set_fill(self.shaded_body_color, opacity=1) body.set_fill(shaded_body_color, opacity=1)
body.sort(lambda p: p[2]) body.sort(lambda p: p[2])
body[-1].set_fill(self.body_color) body[-1].set_fill(body_color)
screen_plate = body.copy() screen_plate = body.copy()
keyboard = VGroup(*[ keyboard = VGroup(*[
VGroup(*[ VGroup(*[
Square(**self.key_color_kwargs) Square(**key_color_kwargs)
for x in range(12 - y % 2) for x in range(12 - y % 2)
]).arrange(RIGHT, buff=SMALL_BUFF) ]).arrange(RIGHT, buff=SMALL_BUFF)
for y in range(4) for y in range(4)
]).arrange(DOWN, buff=MED_SMALL_BUFF) ]).arrange(DOWN, buff=MED_SMALL_BUFF)
keyboard.stretch_to_fit_width( keyboard.stretch_to_fit_width(
self.keyboard_width_to_body_width * body.get_width(), keyboard_width_to_body_width * body.get_width(),
) )
keyboard.stretch_to_fit_height( keyboard.stretch_to_fit_height(
self.keyboard_height_to_body_height * body.get_height(), keyboard_height_to_body_height * body.get_height(),
) )
keyboard.next_to(body, OUT, buff=0.1 * SMALL_BUFF) keyboard.next_to(body, OUT, buff=0.1 * SMALL_BUFF)
keyboard.shift(MED_SMALL_BUFF * UP) keyboard.shift(MED_SMALL_BUFF * UP)
body.add(keyboard) body.add(keyboard)
screen_plate.stretch(self.screen_thickness / screen_plate.stretch(screen_thickness /
self.body_dimensions[2], dim=2) body_dimensions[2], dim=2)
screen = Rectangle( screen = Rectangle(
stroke_width=0, stroke_width=0,
fill_color=BLACK, fill_color=BLACK,
fill_opacity=1, fill_opacity=1,
) )
screen.replace(screen_plate, stretch=True) screen.replace(screen_plate, stretch=True)
screen.scale(self.screen_width_to_screen_plate_width) screen.scale(screen_width_to_screen_plate_width)
screen.next_to(screen_plate, OUT, buff=0.1 * SMALL_BUFF) screen.next_to(screen_plate, OUT, buff=0.1 * SMALL_BUFF)
screen_plate.add(screen) screen_plate.add(screen)
screen_plate.next_to(body, UP, buff=0) screen_plate.next_to(body, UP, buff=0)
screen_plate.rotate( screen_plate.rotate(
self.open_angle, RIGHT, open_angle, RIGHT,
about_point=screen_plate.get_bottom() about_point=screen_plate.get_bottom()
) )
self.screen_plate = screen_plate self.screen_plate = screen_plate
@ -196,131 +222,131 @@ class Laptop(VGroup):
class VideoIcon(SVGMobject): class VideoIcon(SVGMobject):
CONFIG = { file_name: str = "video_icon"
"width": FRAME_WIDTH / 12.,
}
def __init__(self, **kwargs): def __init__(
super().__init__(file_name="video_icon", **kwargs) self,
self.center() width: float = 1.2,
self.set_width(self.width) color=BLUE_A,
self.set_stroke(color=WHITE, width=0) **kwargs
self.set_fill(color=WHITE, opacity=1) ):
super().__init__(color=color, **kwargs)
self.set_width(width)
class VideoSeries(VGroup): class VideoSeries(VGroup):
CONFIG = { def __init__(
"num_videos": 11, self,
"gradient_colors": [BLUE_B, BLUE_D], num_videos: int = 11,
} gradient_colors: Sequence[ManimColor] = [BLUE_B, BLUE_D],
width: float = FRAME_WIDTH - MED_LARGE_BUFF,
def __init__(self, **kwargs): **kwargs
digest_config(self, kwargs) ):
videos = [VideoIcon() for x in range(self.num_videos)] super().__init__(
VGroup.__init__(self, *videos, **kwargs) *(VideoIcon() for x in range(num_videos)),
self.arrange() **kwargs
self.set_width(FRAME_WIDTH - MED_LARGE_BUFF) )
self.set_color_by_gradient(*self.gradient_colors) self.arrange(RIGHT)
self.set_width(width)
self.set_color_by_gradient(*gradient_colors)
class Clock(VGroup): class Clock(VGroup):
CONFIG = {} def __init__(
self,
def __init__(self, **kwargs): stroke_color: ManimColor = WHITE,
circle = Circle(color=WHITE) stroke_width: float = 3.0,
hour_hand_height: float = 0.3,
minute_hand_height: float = 0.6,
tick_length: float = 0.1,
**kwargs,
):
style = dict(stroke_color=stroke_color, stroke_width=stroke_width)
circle = Circle(**style)
ticks = [] ticks = []
for x in range(12): for x, point in enumerate(compass_directions(12, UP)):
alpha = x / 12. length = tick_length
point = complex_to_R3( if x % 3 == 0:
np.exp(2 * np.pi * alpha * complex(0, 1)) length *= 2
) ticks.append(Line(point, (1 - length) * point, **style))
length = 0.2 if x % 3 == 0 else 0.1 self.hour_hand = Line(ORIGIN, hour_hand_height * UP, **style)
ticks.append( self.minute_hand = Line(ORIGIN, minute_hand_height * UP, **style)
Line(point, (1 - length) * point)
)
self.hour_hand = Line(ORIGIN, 0.3 * UP)
self.minute_hand = Line(ORIGIN, 0.6 * UP)
# for hand in self.hour_hand, self.minute_hand:
# #Balance out where the center is
# hand.add(VectorizedPoint(-hand.get_end()))
VGroup.__init__( super().__init__(
self, circle, circle, self.hour_hand, self.minute_hand,
self.hour_hand, self.minute_hand,
*ticks *ticks
) )
class ClockPassesTime(Animation): class ClockPassesTime(AnimationGroup):
CONFIG = { def __init__(
"run_time": 5, self,
"hours_passed": 12, clock: Clock,
"rate_func": linear, run_time: float = 5.0,
} hours_passed: float = 12.0,
rate_func: Callable[[float], float] = linear,
def __init__(self, clock, **kwargs): **kwargs
digest_config(self, kwargs) ):
assert(isinstance(clock, Clock)) rot_kwargs = dict(
rot_kwargs = { axis=OUT,
"axis": OUT, about_point=clock.get_center()
"about_point": clock.get_center() )
} hour_radians = -hours_passed * 2 * PI / 12
hour_radians = -self.hours_passed * 2 * np.pi / 12 super().__init__(
self.hour_rotation = Rotating( Rotating(
clock.hour_hand, clock.hour_hand,
angle=hour_radians, angle=hour_radians,
**rot_kwargs **rot_kwargs
) ),
self.hour_rotation.begin() Rotating(
self.minute_rotation = Rotating(
clock.minute_hand, clock.minute_hand,
angle=12 * hour_radians, angle=12 * hour_radians,
**rot_kwargs **rot_kwargs
),
**kwargs
) )
self.minute_rotation.begin()
Animation.__init__(self, clock, **kwargs)
def interpolate_mobject(self, alpha):
for rotation in self.hour_rotation, self.minute_rotation:
rotation.interpolate_mobject(alpha)
class Bubble(SVGMobject): class Bubble(SVGMobject):
CONFIG = { file_name: str = "Bubbles_speech.svg"
"direction": LEFT,
"center_point": ORIGIN, def __init__(
"content_scale_factor": 0.7, self,
"height": 5, direction: np_vector = LEFT,
"width": 8, center_point: np_vector = ORIGIN,
"max_height": None, content_scale_factor: float = 0.7,
"max_width": None, height: float = 4.0,
"bubble_center_adjustment_factor": 1. / 8, width: float = 8.0,
"file_name": None, max_height: float | None = None,
"fill_color": BLACK, max_width: float | None = None,
"fill_opacity": 0.8, bubble_center_adjustment_factor: float = 0.125,
"stroke_color": WHITE, fill_color: ManimColor = BLACK,
"stroke_width": 3, fill_opacity: float = 0.8,
} stroke_color: ManimColor = WHITE,
stroke_width: float = 3.0,
**kwargs
):
self.direction = direction
self.bubble_center_adjustment_factor = bubble_center_adjustment_factor
super().__init__(
fill_color=fill_color,
fill_opacity=fill_opacity,
stroke_color=stroke_color,
stroke_width=stroke_width,
**kwargs
)
def __init__(self, **kwargs):
digest_config(self, kwargs)
if self.file_name is None:
raise Exception("Must invoke Bubble subclass")
SVGMobject.__init__(self, self.file_name, **kwargs)
self.center() self.center()
self.set_height(self.height, stretch=True) self.set_height(height, stretch=True)
self.set_width(self.width, stretch=True) self.set_width(width, stretch=True)
if self.max_height: if max_height:
self.set_max_height(self.max_height) self.set_max_height(max_height)
if self.max_width: if max_width:
self.set_max_width(self.max_width) self.set_max_width(max_width)
if self.direction[0] > 0: if direction[0] > 0:
self.flip() self.flip()
if "direction" in kwargs:
self.direction = kwargs["direction"]
self.direction_was_specified = True
else:
self.direction_was_specified = False
self.content = Mobject() self.content = Mobject()
self.refresh_triangulation() self.refresh_triangulation()
@ -350,8 +376,7 @@ class Bubble(SVGMobject):
def pin_to(self, mobject): def pin_to(self, mobject):
mob_center = mobject.get_center() mob_center = mobject.get_center()
want_to_flip = np.sign(mob_center[0]) != np.sign(self.direction[0]) want_to_flip = np.sign(mob_center[0]) != np.sign(self.direction[0])
can_flip = not self.direction_was_specified if want_to_flip:
if want_to_flip and can_flip:
self.flip() self.flip()
boundary_point = mobject.get_bounding_box_point(UP - self.direction) boundary_point = mobject.get_bounding_box_point(UP - self.direction)
vector_from_center = 1.0 * (boundary_point - mob_center) vector_from_center = 1.0 * (boundary_point - mob_center)
@ -389,23 +414,15 @@ class Bubble(SVGMobject):
class SpeechBubble(Bubble): class SpeechBubble(Bubble):
CONFIG = { file_name: str = "Bubbles_speech.svg"
"file_name": "Bubbles_speech.svg",
"height": 4
}
class DoubleSpeechBubble(Bubble): class DoubleSpeechBubble(Bubble):
CONFIG = { file_name: str = "Bubbles_double_speech.svg"
"file_name": "Bubbles_double_speech.svg",
"height": 4
}
class ThoughtBubble(Bubble): class ThoughtBubble(Bubble):
CONFIG = { file_name: str = "Bubbles_thought.svg"
"file_name": "Bubbles_thought.svg",
}
def __init__(self, **kwargs): def __init__(self, **kwargs):
Bubble.__init__(self, **kwargs) Bubble.__init__(self, **kwargs)
@ -419,14 +436,15 @@ class ThoughtBubble(Bubble):
class VectorizedEarth(SVGMobject): class VectorizedEarth(SVGMobject):
CONFIG = { file_name: str = "earth"
"file_name": "earth",
"height": 1.5,
"fill_color": BLACK,
}
def __init__(self, **kwargs): def __init__(
SVGMobject.__init__(self, **kwargs) self,
height: float = 2.0,
**kwargs
):
super().__init__(height=height, **kwargs)
self.insert_n_curves(20)
circle = Circle( circle = Circle(
stroke_width=3, stroke_width=3,
stroke_color=GREEN, stroke_color=GREEN,
@ -490,29 +508,28 @@ class Piano(VGroup):
class Piano3D(VGroup): class Piano3D(VGroup):
CONFIG = { def __init__(
"depth_test": True, self,
"reflectiveness": 1.0, reflectiveness: float = 1.0,
"stroke_width": 0.25, stroke_width: float = 0.25,
"stroke_color": BLACK, stroke_color: ManimColor = BLACK,
"key_depth": 0.1, key_depth: float = 0.1,
"black_key_shift": 0.05, black_key_shift: float = 0.05,
} piano_2d_config: dict = dict(
piano_2d_config = { white_key_color=GREY_A,
"white_key_color": GREY_A, key_buff=0.001
"key_buff": 0.001 ),
} **kwargs
):
def __init__(self, **kwargs): piano_2d = Piano(**piano_2d_config)
digest_config(self, kwargs)
piano_2d = Piano(**self.piano_2d_config)
super().__init__(*( super().__init__(*(
Prismify(key, self.key_depth) Prismify(key, key_depth)
for key in piano_2d for key in piano_2d
)) ))
self.set_stroke(self.stroke_color, self.stroke_width) self.set_stroke(stroke_color, stroke_width)
self.apply_depth_test() self.apply_depth_test()
# Elevate black keys # Elevate black keys
for i, key in enumerate(self): for i, key in enumerate(self):
if piano_2d[i] in piano_2d.black_keys: if piano_2d[i] in piano_2d.black_keys:
key.shift(self.black_key_shift * OUT) key.shift(black_key_shift * OUT)