3b1b-manim/mobject/svg/drawings.py

866 lines
26 KiB
Python
Raw Normal View History

from __future__ import absolute_import
from constants import *
from mobject.mobject import Mobject
from mobject.svg.svg_mobject import SVGMobject
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.rotation import Rotating
2016-12-26 07:10:38 -08:00
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.three_dimensions import Cube
from utils.config_ops import digest_config
from utils.space_ops import angle_of_vector
from utils.space_ops import complex_to_R3
from utils.space_ops import rotate_vector
2017-06-30 15:41:45 -07:00
class Lightbulb(SVGMobject):
CONFIG = {
"file_name": "lightbulb",
"height": 1,
"stroke_color": YELLOW,
"stroke_width": 3,
"fill_color": YELLOW,
"fill_opacity": 0,
2017-06-30 15:41:45 -07:00
}
2017-06-20 14:05:48 -07:00
class BitcoinLogo(SVGMobject):
CONFIG = {
"file_name": "Bitcoin_logo",
"height": 1,
"fill_color": "#f7931a",
"inner_color": WHITE,
"fill_opacity": 1,
"stroke_width": 0,
2017-06-20 14:05:48 -07:00
}
2017-06-20 14:05:48 -07:00
def __init__(self, **kwargs):
SVGMobject.__init__(self, **kwargs)
self[0].set_fill(self.fill_color, self.fill_opacity)
self[1].set_fill(self.inner_color, 1)
2017-06-08 21:56:53 -07:00
class Guitar(SVGMobject):
CONFIG = {
"file_name": "guitar",
"height": 2.5,
"fill_color": DARK_GREY,
"fill_opacity": 1,
"stroke_color": WHITE,
"stroke_width": 0.5,
2017-06-08 21:56:53 -07:00
}
class SunGlasses(SVGMobject):
2017-06-05 12:47:03 -07:00
CONFIG = {
"file_name": "sunglasses",
"glasses_width_to_eyes_width": 1.1,
2017-06-05 12:47:03 -07:00
}
def __init__(self, pi_creature, **kwargs):
SVGMobject.__init__(self, **kwargs)
self.set_stroke(WHITE, width=0)
self.set_fill(GREY, 1)
self.scale_to_fit_width(
self.glasses_width_to_eyes_width * pi_creature.eyes.get_width()
2017-06-05 12:47:03 -07:00
)
self.move_to(pi_creature.eyes, UP)
2017-06-05 12:47:03 -07:00
2017-04-12 09:06:04 -07:00
class Speedometer(VMobject):
CONFIG = {
"arc_angle": 4 * np.pi / 3,
"num_ticks": 8,
"tick_length": 0.2,
"needle_width": 0.1,
"needle_height": 0.8,
"needle_color": YELLOW,
2017-04-12 09:06:04 -07:00
}
2017-04-12 09:06:04 -07:00
def generate_points(self):
start_angle = np.pi / 2 + self.arc_angle / 2
end_angle = np.pi / 2 - self.arc_angle / 2
2017-04-12 09:06:04 -07:00
self.add(Arc(
start_angle=start_angle,
angle=-self.arc_angle
2017-04-12 09:06:04 -07:00
))
tick_angle_range = np.linspace(start_angle, end_angle, self.num_ticks)
for index, angle in enumerate(tick_angle_range):
vect = rotate_vector(RIGHT, angle)
tick = Line((1 - self.tick_length) * vect, vect)
label = TexMobject(str(10 * index))
2017-04-12 09:06:04 -07:00
label.scale_to_fit_height(self.tick_length)
label.shift((1 + self.tick_length) * vect)
2017-04-12 09:06:04 -07:00
self.add(tick, label)
needle = Polygon(
LEFT, UP, RIGHT,
stroke_width=0,
fill_opacity=1,
fill_color=self.needle_color
2017-04-12 09:06:04 -07:00
)
needle.stretch_to_fit_width(self.needle_width)
needle.stretch_to_fit_height(self.needle_height)
needle.rotate(start_angle - np.pi / 2, about_point=ORIGIN)
2017-04-12 09:06:04 -07:00
self.add(needle)
self.needle = needle
self.center_offset = self.get_center()
def get_center(self):
result = VMobject.get_center(self)
if hasattr(self, "center_offset"):
result -= self.center_offset
return result
def get_needle_tip(self):
return self.needle.get_anchors()[1]
def get_needle_angle(self):
return angle_of_vector(
self.get_needle_tip() - self.get_center()
)
def rotate_needle(self, angle):
self.needle.rotate(angle, about_point=self.get_center())
2017-04-12 09:06:04 -07:00
return self
def move_needle_to_velocity(self, velocity):
max_velocity = 10 * (self.num_ticks - 1)
2017-04-12 09:06:04 -07:00
proportion = float(velocity) / max_velocity
start_angle = np.pi / 2 + self.arc_angle / 2
2017-04-12 09:06:04 -07:00
target_angle = start_angle - self.arc_angle * proportion
self.rotate_needle(target_angle - self.get_needle_angle())
return self
2017-04-26 16:22:05 -07:00
class AoPSLogo(SVGMobject):
CONFIG = {
"file_name": "aops_logo",
"height": 1.5,
2017-04-26 16:22:05 -07:00
}
2017-04-26 16:22:05 -07:00
def __init__(self, **kwargs):
SVGMobject.__init__(self, **kwargs)
self.set_stroke(WHITE, width=0)
2017-04-26 16:22:05 -07:00
colors = [BLUE_E, "#008445", GREEN_B]
index_lists = [
(10, 11, 12, 13, 14, 21, 22, 23, 24, 27, 28, 29, 30),
(0, 1, 2, 3, 4, 15, 16, 17, 26),
(5, 6, 7, 8, 9, 18, 19, 20, 25)
]
for color, index_list in zip(colors, index_lists):
for i in index_list:
self.submobjects[i].set_fill(color, opacity=1)
2017-04-26 16:22:05 -07:00
self.scale_to_fit_height(self.height)
self.center()
2017-02-23 14:13:30 -08:00
class PartyHat(SVGMobject):
CONFIG = {
"file_name": "party_hat",
"height": 1.5,
"pi_creature": None,
"stroke_width": 0,
"fill_opacity": 1,
"propagate_style_to_family": True,
"frills_colors": [MAROON_B, PURPLE],
"cone_color": GREEN,
"dots_colors": [YELLOW],
2017-02-23 14:13:30 -08:00
}
NUM_FRILLS = 7
NUM_DOTS = 6
2017-02-23 14:13:30 -08:00
def __init__(self, **kwargs):
SVGMobject.__init__(self, **kwargs)
self.scale_to_fit_height(self.height)
if self.pi_creature is not None:
self.next_to(self.pi_creature.eyes, UP, buff=0)
2017-02-23 14:13:30 -08:00
self.frills = VGroup(*self[:self.NUM_FRILLS])
self.cone = self[self.NUM_FRILLS]
self.dots = VGroup(*self[self.NUM_FRILLS + 1:])
2017-02-23 14:13:30 -08:00
self.frills.set_color_by_gradient(*self.frills_colors)
2018-03-30 11:51:31 -07:00
self.cone.set_color(self.cone_color)
self.dots.set_color_by_gradient(*self.dots_colors)
2017-02-23 14:13:30 -08:00
2017-02-18 14:02:46 -08:00
class Laptop(VGroup):
CONFIG = {
"width": 3,
"body_dimensions": [4, 3, 0.05],
"screen_thickness": 0.01,
"keyboard_width_to_body_width": 0.9,
"keyboard_height_to_body_height": 0.5,
"screen_width_to_screen_plate_width": 0.9,
"key_color_kwargs": {
"stroke_width": 0,
"fill_color": BLACK,
"fill_opacity": 1,
2017-02-18 14:02:46 -08:00
},
"body_color": LIGHT_GREY,
"shaded_body_color": GREY,
"open_angle": np.pi / 4,
2017-02-18 14:02:46 -08:00
}
2017-02-18 14:02:46 -08:00
def generate_points(self):
body = Cube(side_length=1)
2017-02-18 14:02:46 -08:00
for dim, scale_factor in enumerate(self.body_dimensions):
body.stretch(scale_factor, dim=dim)
2017-02-18 14:02:46 -08:00
body.scale_to_fit_width(self.width)
body.set_fill(self.shaded_body_color, opacity=1)
body.sort_submobjects(lambda p: p[2])
2017-02-18 14:02:46 -08:00
body[-1].set_fill(self.body_color)
2018-04-18 00:13:11 -07:00
screen_plate = body.copy()
2017-02-18 14:02:46 -08:00
keyboard = VGroup(*[
VGroup(*[
Square(**self.key_color_kwargs)
for x in range(12 - y % 2)
]).arrange_submobjects(RIGHT, buff=SMALL_BUFF)
2017-02-18 14:02:46 -08:00
for y in range(4)
]).arrange_submobjects(DOWN, buff=MED_SMALL_BUFF)
2017-02-18 14:02:46 -08:00
keyboard.stretch_to_fit_width(
self.keyboard_width_to_body_width * body.get_width(),
2017-02-18 14:02:46 -08:00
)
keyboard.stretch_to_fit_height(
self.keyboard_height_to_body_height * body.get_height(),
2017-02-18 14:02:46 -08:00
)
keyboard.next_to(body, OUT, buff=0.1 * SMALL_BUFF)
keyboard.shift(MED_SMALL_BUFF * UP)
2017-02-18 14:02:46 -08:00
body.add(keyboard)
screen_plate.stretch(self.screen_thickness /
self.body_dimensions[2], dim=2)
2017-02-18 14:02:46 -08:00
screen = Rectangle(
stroke_width=0,
fill_color=BLACK,
fill_opacity=1,
2017-02-18 14:02:46 -08:00
)
screen.replace(screen_plate, stretch=True)
2017-02-18 14:02:46 -08:00
screen.scale_in_place(self.screen_width_to_screen_plate_width)
screen.next_to(screen_plate, OUT, buff=0.1 * SMALL_BUFF)
2017-02-18 14:02:46 -08:00
screen_plate.add(screen)
screen_plate.next_to(body, UP, buff=0)
2017-02-18 14:02:46 -08:00
screen_plate.rotate(
self.open_angle, RIGHT,
about_point=screen_plate.get_bottom()
2017-02-18 14:02:46 -08:00
)
self.screen_plate = screen_plate
self.screen = screen
axis = Line(
body.get_corner(UP + LEFT + OUT),
body.get_corner(UP + RIGHT + OUT),
color=BLACK,
stroke_width=2
2017-02-18 14:02:46 -08:00
)
self.axis = axis
self.add(body, screen_plate, axis)
self.rotate(5 * np.pi / 12, LEFT, about_point=ORIGIN)
self.rotate(np.pi / 6, DOWN, about_point=ORIGIN)
2017-02-18 14:02:46 -08:00
class PatreonLogo(SVGMobject):
CONFIG = {
"file_name": "patreon_logo",
"fill_color": "#F96854",
# "fill_color" : WHITE,
"fill_opacity": 1,
"stroke_width": 0,
"width": 4,
"propagate_style_to_family": True
}
def __init__(self, **kwargs):
SVGMobject.__init__(self, **kwargs)
self.scale_to_fit_width(self.width)
self.center()
class VideoIcon(SVGMobject):
CONFIG = {
"file_name": "video_icon",
"width": FRAME_WIDTH / 12.,
}
def __init__(self, **kwargs):
SVGMobject.__init__(self, **kwargs)
self.center()
self.scale_to_fit_width(self.width)
self.set_stroke(color=WHITE, width=0)
self.set_fill(color=WHITE, opacity=1)
class VideoSeries(VGroup):
CONFIG = {
"num_videos": 11,
"gradient_colors": [BLUE_B, BLUE_D],
}
def __init__(self, **kwargs):
digest_config(self, kwargs)
videos = [VideoIcon() for x in range(self.num_videos)]
VGroup.__init__(self, *videos, **kwargs)
self.arrange_submobjects()
self.scale_to_fit_width(FRAME_WIDTH - MED_LARGE_BUFF)
self.set_color_by_gradient(*self.gradient_colors)
class Headphones(SVGMobject):
CONFIG = {
"file_name": "headphones",
"height": 2,
"y_stretch_factor": 0.5,
"color": GREY,
}
def __init__(self, pi_creature=None, **kwargs):
digest_config(self, kwargs)
SVGMobject.__init__(self, file_name=self.file_name, **kwargs)
self.stretch(self.y_stretch_factor, 1)
self.scale_to_fit_height(self.height)
self.set_stroke(width=0)
self.set_fill(color=self.color)
2017-06-09 16:10:34 -07:00
if pi_creature is not None:
eyes = pi_creature.eyes
self.scale_to_fit_height(3 * eyes.get_height())
2017-06-09 16:10:34 -07:00
self.move_to(eyes, DOWN)
self.shift(DOWN * eyes.get_height() / 4)
2016-12-26 07:10:38 -08:00
class Clock(VGroup):
CONFIG = {
"propagate_style_to_family": True,
2016-12-26 07:10:38 -08:00
}
2016-12-26 07:10:38 -08:00
def __init__(self, **kwargs):
circle = Circle()
ticks = []
for x in range(12):
alpha = x / 12.
2016-12-26 07:10:38 -08:00
point = complex_to_R3(
np.exp(2 * np.pi * alpha * complex(0, 1))
2016-12-26 07:10:38 -08:00
)
length = 0.2 if x % 3 == 0 else 0.1
2016-12-26 07:10:38 -08:00
ticks.append(
Line(point, (1 - length) * point)
2016-12-26 07:10:38 -08:00
)
self.hour_hand = Line(ORIGIN, 0.3 * UP)
self.minute_hand = Line(ORIGIN, 0.6 * UP)
2016-12-26 07:10:38 -08:00
# for hand in self.hour_hand, self.minute_hand:
# #Balance out where the center is
# hand.add(VectorizedPoint(-hand.get_end()))
VGroup.__init__(
self, circle,
2016-12-26 07:10:38 -08:00
self.hour_hand, self.minute_hand,
*ticks
)
2016-12-26 07:10:38 -08:00
class ClockPassesTime(Animation):
CONFIG = {
"run_time": 5,
"hours_passed": 12,
"rate_func": None,
2016-12-26 07:10:38 -08:00
}
2016-12-26 07:10:38 -08:00
def __init__(self, clock, **kwargs):
digest_config(self, kwargs)
assert(isinstance(clock, Clock))
rot_kwargs = {
"axis": OUT,
"about_point": clock.get_center()
2016-12-26 07:10:38 -08:00
}
hour_radians = -self.hours_passed * 2 * np.pi / 12
2016-12-26 07:10:38 -08:00
self.hour_rotation = Rotating(
clock.hour_hand,
radians=hour_radians,
2016-12-26 07:10:38 -08:00
**rot_kwargs
)
self.minute_rotation = Rotating(
clock.minute_hand,
radians=12 * hour_radians,
2016-12-26 07:10:38 -08:00
**rot_kwargs
)
Animation.__init__(self, clock, **kwargs)
def update_mobject(self, alpha):
for rotation in self.hour_rotation, self.minute_rotation:
rotation.update_mobject(alpha)
class Bubble(SVGMobject):
CONFIG = {
"direction": LEFT,
"center_point": ORIGIN,
"content_scale_factor": 0.75,
"height": 5,
"width": 8,
"bubble_center_adjustment_factor": 1. / 8,
"file_name": None,
"propagate_style_to_family": True,
"fill_color": BLACK,
"fill_opacity": 0.8,
"stroke_color": WHITE,
"stroke_width": 3,
}
def __init__(self, **kwargs):
digest_config(self, kwargs, locals())
if self.file_name is None:
raise Exception("Must invoke Bubble subclass")
try:
SVGMobject.__init__(self, **kwargs)
except IOError as err:
self.file_name = os.path.join(FILE_DIR, self.file_name)
SVGMobject.__init__(self, **kwargs)
self.center()
self.stretch_to_fit_height(self.height)
self.stretch_to_fit_width(self.width)
if self.direction[0] > 0:
Mobject.flip(self)
self.direction_was_specified = ("direction" in kwargs)
self.content = Mobject()
def get_tip(self):
# TODO, find a better way
return self.get_corner(DOWN + self.direction) - 0.6 * self.direction
def get_bubble_center(self):
factor = self.bubble_center_adjustment_factor
return self.get_center() + factor * self.get_height() * UP
def move_tip_to(self, point):
VGroup(self, self.content).shift(point - self.get_tip())
return self
def flip(self):
Mobject.flip(self)
self.direction = -np.array(self.direction)
return self
def pin_to(self, mobject):
mob_center = mobject.get_center()
want_to_filp = np.sign(mob_center[0]) != np.sign(self.direction[0])
can_flip = not self.direction_was_specified
if want_to_filp and can_flip:
self.flip()
boundary_point = mobject.get_critical_point(UP - self.direction)
vector_from_center = 1.0 * (boundary_point - mob_center)
self.move_tip_to(mob_center + vector_from_center)
return self
def position_mobject_inside(self, mobject):
scaled_width = self.content_scale_factor * self.get_width()
if mobject.get_width() > scaled_width:
mobject.scale_to_fit_width(scaled_width)
mobject.shift(
self.get_bubble_center() - mobject.get_center()
)
return mobject
def add_content(self, mobject):
self.position_mobject_inside(mobject)
self.content = mobject
return self.content
def write(self, *text):
self.add_content(TextMobject(*text))
return self
def resize_to_content(self):
target_width = self.content.get_width()
2017-01-25 16:40:59 -08:00
target_width += max(MED_LARGE_BUFF, 2)
target_height = self.content.get_height()
target_height += 2.5 * LARGE_BUFF
tip_point = self.get_tip()
self.stretch_to_fit_width(target_width)
self.stretch_to_fit_height(target_height)
self.move_tip_to(tip_point)
self.position_mobject_inside(self.content)
def clear(self):
self.add_content(VMobject())
return self
class SpeechBubble(Bubble):
CONFIG = {
"file_name": "Bubbles_speech.svg",
"height": 4
}
class DoubleSpeechBubble(Bubble):
CONFIG = {
"file_name": "Bubbles_double_speech.svg",
"height": 4
}
class ThoughtBubble(Bubble):
CONFIG = {
"file_name": "Bubbles_thought.svg",
}
def __init__(self, **kwargs):
Bubble.__init__(self, **kwargs)
self.submobjects.sort(
lambda m1, m2: int((m1.get_bottom() - m2.get_bottom())[1])
)
def make_green_screen(self):
self.submobjects[-1].set_fill(GREEN_SCREEN, opacity=1)
return self
2018-02-13 21:38:10 -08:00
class Car(SVGMobject):
CONFIG = {
"file_name": "Car",
"height": 1,
"color": LIGHT_GREY,
"light_colors": [BLACK, BLACK],
2018-02-13 21:38:10 -08:00
}
2018-02-13 21:38:10 -08:00
def __init__(self, **kwargs):
SVGMobject.__init__(self, **kwargs)
self.scale_to_fit_height(self.height)
self.set_stroke(color=WHITE, width=0)
self.set_fill(self.color, opacity=1)
2018-02-13 21:38:10 -08:00
from for_3b1b_videos.pi_creature import Randolph
randy = Randolph(mode="happy")
randy.scale_to_fit_height(0.6 * self.get_height())
2018-02-13 21:38:10 -08:00
randy.stretch(0.8, 0)
randy.look(RIGHT)
randy.move_to(self)
randy.shift(0.07 * self.height * (RIGHT + UP))
2018-02-13 21:38:10 -08:00
self.randy = self.pi_creature = randy
self.add_to_back(randy)
orientation_line = Line(self.get_left(), self.get_right())
orientation_line.set_stroke(width=0)
2018-02-13 21:38:10 -08:00
self.add(orientation_line)
self.orientation_line = orientation_line
for light, color in zip(self.get_lights(), self.light_colors):
light.set_fill(color, 1)
light.is_subpath = False
self.add_treds_to_tires()
def move_to(self, point_or_mobject):
vect = rotate_vector(
UP + LEFT, self.orientation_line.get_angle()
2018-02-13 21:38:10 -08:00
)
self.next_to(point_or_mobject, vect, buff=0)
2018-02-13 21:38:10 -08:00
return self
def get_front_line(self):
return DashedLine(
self.get_corner(UP + RIGHT),
self.get_corner(DOWN + RIGHT),
color=DISTANCE_COLOR,
dashed_segment_length=0.05,
2018-02-13 21:38:10 -08:00
)
def add_treds_to_tires(self):
for tire in self.get_tires():
radius = tire.get_width() / 2
2018-02-13 21:38:10 -08:00
center = tire.get_center()
tred = Line(
0.9 * radius * RIGHT, 1.4 * radius * RIGHT,
stroke_width=2,
color=BLACK
2018-02-13 21:38:10 -08:00
)
tred.rotate_in_place(np.pi / 4)
for theta in np.arange(0, 2 * np.pi, np.pi / 4):
2018-02-13 21:38:10 -08:00
new_tred = tred.copy()
new_tred.rotate(theta, about_point=ORIGIN)
2018-02-13 21:38:10 -08:00
new_tred.shift(center)
tire.add(new_tred)
return self
def get_tires(self):
return VGroup(self[1][1], self[1][3])
def get_lights(self):
return VGroup(self.get_front_light(), self.get_rear_light())
def get_front_light(self):
return self[1][5]
def get_rear_light(self):
return self[1][8]
### 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 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()
)
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)