mirror of
https://github.com/3b1b/manim.git
synced 2025-08-05 16:49:03 +00:00
Characters are vectorized
This commit is contained in:
parent
f5e4c3b334
commit
e4c73306db
9 changed files with 231 additions and 290 deletions
|
@ -12,18 +12,28 @@ from mobject import Mobject, Point
|
|||
|
||||
class Transform(Animation):
|
||||
CONFIG = {
|
||||
"path_func" : straight_path
|
||||
"path_arc" : 0,
|
||||
"path_func" : None,
|
||||
}
|
||||
def __init__(self, mobject, ending_mobject, **kwargs):
|
||||
#Copy ending_mobject so as to not mess with caller
|
||||
ending_mobject = ending_mobject.copy()
|
||||
digest_config(self, kwargs, locals())
|
||||
mobject.align_data(ending_mobject)
|
||||
self.init_path_func()
|
||||
|
||||
Animation.__init__(self, mobject, **kwargs)
|
||||
self.name += "To" + str(ending_mobject)
|
||||
self.mobject.stroke_width = ending_mobject.stroke_width
|
||||
|
||||
def init_path_func(self):
|
||||
if self.path_func is not None:
|
||||
return
|
||||
if self.path_arc == 0:
|
||||
self.path_func = straight_path
|
||||
else:
|
||||
self.path_func = path_along_arc(self.path_arc)
|
||||
|
||||
|
||||
def update_mobject(self, alpha):
|
||||
families = map(
|
||||
|
@ -36,12 +46,12 @@ class Transform(Animation):
|
|||
|
||||
class ClockwiseTransform(Transform):
|
||||
CONFIG = {
|
||||
"path_func" : clockwise_path()
|
||||
"path_arc" : -np.pi
|
||||
}
|
||||
|
||||
class CounterclockwiseTransform(Transform):
|
||||
CONFIG = {
|
||||
"path_func" : counterclockwise_path()
|
||||
"path_arc" : np.pi
|
||||
}
|
||||
|
||||
class GrowFromCenter(Transform):
|
||||
|
|
|
@ -30,8 +30,8 @@ class Mobject(object):
|
|||
if self.name is None:
|
||||
self.name = self.__class__.__name__
|
||||
self.init_points()
|
||||
self.init_colors()
|
||||
self.generate_points()
|
||||
self.init_colors()
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
@ -172,6 +172,9 @@ class Mobject(object):
|
|||
self.do_in_place(self.rotate, angle, axis, axes)
|
||||
return self
|
||||
|
||||
def flip(self, axis = UP):
|
||||
self.rotate_in_place(np.pi, axis)
|
||||
|
||||
def scale_in_place(self, scale_factor):
|
||||
self.do_in_place(self.scale, scale_factor)
|
||||
return self
|
||||
|
|
|
@ -2,14 +2,14 @@ from .mobject import Mobject
|
|||
from helpers import *
|
||||
|
||||
class PMobject(Mobject):
|
||||
def init_colors(self):
|
||||
def init_points(self):
|
||||
self.rgbs = np.zeros((0, 3))
|
||||
self.points = np.zeros((0, 3))
|
||||
return self
|
||||
|
||||
def get_array_attrs(self):
|
||||
return Mobject.get_array_attrs(self) + ["rgbs"]
|
||||
|
||||
|
||||
def add_points(self, points, rgbs = None, color = None):
|
||||
"""
|
||||
points must be a Nx3 numpy array, as must rgbs if it is not None
|
||||
|
|
|
@ -5,59 +5,77 @@ from vectorized_mobject import VMobject
|
|||
from topics.geometry import Rectangle, Circle
|
||||
from helpers import *
|
||||
|
||||
SVG_SCALE_VALUE = 0.05
|
||||
|
||||
class SVGMobject(VMobject):
|
||||
CONFIG = {
|
||||
"stroke_width" : 0,
|
||||
"fill_opacity" : 1.0,
|
||||
"fill_color" : WHITE, #TODO...
|
||||
}
|
||||
def __init__(self, svg_file, **kwargs):
|
||||
digest_config(self, kwargs, locals())
|
||||
VMobject.__init__(self, **kwargs)
|
||||
self.move_into_position()
|
||||
|
||||
def generate_points(self):
|
||||
doc = minidom.parse(self.svg_file)
|
||||
defs = doc.getElementsByTagName("defs")[0]
|
||||
g = doc.getElementsByTagName("g")[0]
|
||||
ref_to_mob = self.get_ref_to_mobject_map(defs)
|
||||
for element in g.childNodes:
|
||||
if not isinstance(element, minidom.Element):
|
||||
continue
|
||||
mob = None
|
||||
if element.tagName == 'use':
|
||||
mob = self.use_to_mobject(element, ref_to_mob)
|
||||
elif element.tagName == 'rect':
|
||||
mob = self.rect_to_mobject(element)
|
||||
elif element.tagName == 'circle':
|
||||
mob = self.circle_to_mobject(element)
|
||||
else:
|
||||
warnings.warn("Unknown element type: " + element.tagName)
|
||||
if mob is not None:
|
||||
self.add(mob)
|
||||
self.ref_to_element = {}
|
||||
for svg in doc.getElementsByTagName("svg"):
|
||||
self.add(*self.get_mobjects_from(svg))
|
||||
doc.unlink()
|
||||
self.move_into_position()
|
||||
self.organize_submobjects()
|
||||
|
||||
def use_to_mobject(self, use_element, ref_to_mob):
|
||||
def get_mobjects_from(self, element):
|
||||
result = []
|
||||
if not isinstance(element, minidom.Element):
|
||||
return result
|
||||
if element.tagName == 'defs':
|
||||
self.update_ref_to_element(element)
|
||||
elif element.tagName == 'style':
|
||||
pass #TODO, handle style
|
||||
elif element.tagName in ['g', 'svg']:
|
||||
result += it.chain(*[
|
||||
self.get_mobjects_from(child)
|
||||
for child in element.childNodes
|
||||
])
|
||||
elif element.tagName == 'path':
|
||||
result.append(self.path_to_mobject(element))
|
||||
elif element.tagName == 'use':
|
||||
result += self.use_to_mobjects(element)
|
||||
elif element.tagName == 'rect':
|
||||
result.append(self.rect_to_mobject(element))
|
||||
elif element.tagName == 'circle':
|
||||
result.append(self.circle_to_mobject(element))
|
||||
else:
|
||||
warnings.warn("Unknown element type: " + element.tagName)
|
||||
result = filter(lambda m : m is not None, result)
|
||||
self.handle_transforms(element, VMobject(*result))
|
||||
return result
|
||||
|
||||
def g_to_mobjects(self, g_element):
|
||||
mob = VMobject(*self.get_mobjects_from(g_element))
|
||||
self.handle_transforms(g_element, mob)
|
||||
return mob.submobjects
|
||||
|
||||
def path_to_mobject(self, path_element):
|
||||
return VMobjectFromSVGPathstring(
|
||||
path_element.getAttribute('d')
|
||||
)
|
||||
|
||||
def use_to_mobjects(self, use_element):
|
||||
#Remove initial "#" character
|
||||
ref = use_element.getAttribute("xlink:href")[1:]
|
||||
try:
|
||||
mob = ref_to_mob[ref]
|
||||
return self.get_mobjects_from(
|
||||
self.ref_to_element[ref]
|
||||
)
|
||||
except:
|
||||
warnings.warn("%s not recognized"%ref)
|
||||
return
|
||||
if mob in self.submobjects:
|
||||
mob = VMobjectFromSVGPathstring(
|
||||
mob.get_original_path_string()
|
||||
)
|
||||
self.handle_transform(use_element, mob)
|
||||
self.handle_shift(use_element, mob)
|
||||
return mob
|
||||
|
||||
# <circle class="st1" cx="143.8" cy="268" r="22.6"/>
|
||||
|
||||
def circle_to_mobject(self, circle_element):
|
||||
pass
|
||||
x, y, r = [
|
||||
float(circle_element.getAttribute(key))
|
||||
if circle_element.hasAttribute(key)
|
||||
else 0.0
|
||||
for key in "cx", "cy", "r"
|
||||
]
|
||||
return Circle(radius = r).shift(x*RIGHT+y*DOWN)
|
||||
|
||||
def rect_to_mobject(self, rect_element):
|
||||
if rect_element.hasAttribute("fill"):
|
||||
|
@ -70,45 +88,30 @@ class SVGMobject(VMobject):
|
|||
fill_color = WHITE,
|
||||
fill_opacity = 1.0
|
||||
)
|
||||
self.handle_shift(rect_element, mob)
|
||||
mob.shift(mob.get_center()-mob.get_corner(DOWN+LEFT))
|
||||
return mob
|
||||
|
||||
def handle_shift(self, element, mobject):
|
||||
def handle_transforms(self, element, mobject):
|
||||
x, y = 0, 0
|
||||
if element.hasAttribute('x'):
|
||||
try:
|
||||
x = float(element.getAttribute('x'))
|
||||
if element.hasAttribute('y'):
|
||||
#Flip y
|
||||
y = -float(element.getAttribute('y'))
|
||||
except:
|
||||
pass
|
||||
mobject.shift(x*RIGHT+y*UP)
|
||||
#TODO, transforms
|
||||
|
||||
def handle_transform(self, element, mobject):
|
||||
pass
|
||||
def update_ref_to_element(self, defs):
|
||||
new_refs = dict([
|
||||
(element.getAttribute('id'), element)
|
||||
for element in defs.childNodes
|
||||
if isinstance(element, minidom.Element) and element.hasAttribute('id')
|
||||
])
|
||||
self.ref_to_element.update(new_refs)
|
||||
|
||||
def move_into_position(self):
|
||||
self.center()
|
||||
self.scale(SVG_SCALE_VALUE)
|
||||
self.init_colors()
|
||||
|
||||
def organize_submobjects(self):
|
||||
self.submobjects.sort(
|
||||
lambda m1, m2 : int((m1.get_left()-m2.get_left())[0])
|
||||
)
|
||||
|
||||
def get_ref_to_mobject_map(self, defs):
|
||||
ref_to_mob = {}
|
||||
for element in defs.childNodes:
|
||||
if not isinstance(element, minidom.Element):
|
||||
continue
|
||||
ref = element.getAttribute('id')
|
||||
if element.tagName == "path":
|
||||
path_string = element.getAttribute('d')
|
||||
mob = VMobjectFromSVGPathstring(path_string)
|
||||
ref_to_mob[ref] = mob
|
||||
if element.tagName == "use":
|
||||
ref_to_mob[ref] = self.use_to_mobject(element, ref_to_mob)
|
||||
return ref_to_mob
|
||||
pass #subclasses should tweak as needed
|
||||
|
||||
|
||||
class VMobjectFromSVGPathstring(VMobject):
|
||||
|
@ -117,7 +120,7 @@ class VMobjectFromSVGPathstring(VMobject):
|
|||
VMobject.__init__(self, **kwargs)
|
||||
|
||||
def get_path_commands(self):
|
||||
return [
|
||||
result = [
|
||||
"M", #moveto
|
||||
"L", #lineto
|
||||
"H", #horizontal lineto
|
||||
|
@ -129,6 +132,8 @@ class VMobjectFromSVGPathstring(VMobject):
|
|||
"A", #elliptical Arc
|
||||
"Z", #closepath
|
||||
]
|
||||
result += map(lambda s : s.lower(), result)
|
||||
return result
|
||||
|
||||
def generate_points(self):
|
||||
pattern = "[%s]"%("".join(self.get_path_commands()))
|
||||
|
@ -144,10 +149,14 @@ class VMobjectFromSVGPathstring(VMobject):
|
|||
self.rotate(np.pi, RIGHT)
|
||||
|
||||
def handle_command(self, command, coord_string):
|
||||
isLower = command.islower()
|
||||
command = command.upper()
|
||||
#new_points are the points that will be added to the curr_points
|
||||
#list. This variable may get modified in the conditionals below.
|
||||
points = self.growing_path.points
|
||||
new_points = self.string_to_points(coord_string)
|
||||
if isLower:
|
||||
new_points += points[-1]
|
||||
if command == "M": #moveto
|
||||
if len(points) > 0:
|
||||
self.growing_path = self.add_subpath(new_points)
|
||||
|
@ -178,9 +187,10 @@ class VMobjectFromSVGPathstring(VMobject):
|
|||
self.growing_path.add_control_points(new_points)
|
||||
|
||||
def string_to_points(self, coord_string):
|
||||
coord_string = coord_string.replace("-",",-")
|
||||
numbers = [
|
||||
float(s)
|
||||
for s in coord_string.split(" ")
|
||||
for s in re.split("[ ,]", coord_string)
|
||||
if s != ""
|
||||
]
|
||||
if len(numbers)%2 == 1:
|
||||
|
|
|
@ -2,11 +2,14 @@ from vectorized_mobject import VMobject
|
|||
from svg_mobject import SVGMobject
|
||||
from helpers import *
|
||||
|
||||
TEX_MOB_SCALE_VAL = 0.05
|
||||
|
||||
class TexMobject(SVGMobject):
|
||||
CONFIG = {
|
||||
"template_tex_file" : TEMPLATE_TEX_FILE,
|
||||
"color" : WHITE,
|
||||
"stroke_width" : 0,
|
||||
"fill_opacity" : 1.0,
|
||||
"fill_color" : WHITE,
|
||||
"should_center" : True,
|
||||
"next_to_direction" : RIGHT,
|
||||
"next_to_buff" : 0.2,
|
||||
|
@ -14,8 +17,8 @@ class TexMobject(SVGMobject):
|
|||
def __init__(self, expression, **kwargs):
|
||||
digest_config(self, kwargs, locals())
|
||||
VMobject.__init__(self, **kwargs)
|
||||
if self.should_center:
|
||||
self.center()
|
||||
self.move_into_position()
|
||||
self.organize_submobjects()
|
||||
|
||||
def generate_points(self):
|
||||
if isinstance(self.expression, list):
|
||||
|
@ -26,7 +29,6 @@ class TexMobject(SVGMobject):
|
|||
self.template_tex_file
|
||||
)
|
||||
SVGMobject.generate_points(self)
|
||||
self.init_colors()
|
||||
|
||||
|
||||
def handle_list_expression(self):
|
||||
|
@ -44,6 +46,15 @@ class TexMobject(SVGMobject):
|
|||
self.submobjects = subs
|
||||
return self
|
||||
|
||||
def organize_submobjects(self):
|
||||
self.submobjects.sort(
|
||||
lambda m1, m2 : int((m1.get_left()-m2.get_left())[0])
|
||||
)
|
||||
|
||||
def move_into_position(self):
|
||||
self.center()
|
||||
self.scale(TEX_MOB_SCALE_VAL)
|
||||
self.init_colors()
|
||||
|
||||
|
||||
class TextMobject(TexMobject):
|
||||
|
@ -130,45 +141,6 @@ def dvi_to_svg(dvi_file, regen_if_exists = False):
|
|||
return result
|
||||
|
||||
|
||||
# directory, filename = os.path.split(dvi_file)
|
||||
# name = filename.replace(".dvi", "")
|
||||
# images_dir = os.path.join(TEX_IMAGE_DIR, name)
|
||||
# if not os.path.exists(images_dir):
|
||||
# os.mkdir(images_dir)
|
||||
# if os.listdir(images_dir) == [] or regen_if_exists:
|
||||
# commands = [
|
||||
# "convert",
|
||||
# "-density",
|
||||
# str(PDF_DENSITY),
|
||||
# dvi_file,
|
||||
# "-size",
|
||||
# str(DEFAULT_WIDTH) + "x" + str(DEFAULT_HEIGHT),
|
||||
# os.path.join(images_dir, name + ".png")
|
||||
# ]
|
||||
# os.system(" ".join(commands))
|
||||
# return get_sorted_image_list(images_dir)
|
||||
|
||||
|
||||
def get_sorted_image_list(images_dir):
|
||||
return sorted([
|
||||
os.path.join(images_dir, name)
|
||||
for name in os.listdir(images_dir)
|
||||
if name.endswith(".png")
|
||||
], cmp_enumerated_files)
|
||||
|
||||
def cmp_enumerated_files(name1, name2):
|
||||
name1, name2 = [
|
||||
os.path.split(name)[1].replace(".png", "")
|
||||
for name in name1, name2
|
||||
]
|
||||
num1, num2 = [
|
||||
int(name.split("-")[-1])
|
||||
for name in (name1, name2)
|
||||
]
|
||||
return num1 - num2
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -124,16 +124,16 @@ class VMobject(Mobject):
|
|||
raise Exception("Unknown mode")
|
||||
return self
|
||||
|
||||
def change_mode(self, mode):
|
||||
def change_anchor_mode(self, mode):
|
||||
anchors, h1, h2 = self.get_anchors_and_handles()
|
||||
self.set_anchor_points(anchors, mode = mode)
|
||||
return self
|
||||
|
||||
def make_smooth(self):
|
||||
return self.change_mode("smooth")
|
||||
return self.change_anchor_mode("smooth")
|
||||
|
||||
def make_jagged(self):
|
||||
return self.change_mode("corners")
|
||||
return self.change_anchor_mode("corners")
|
||||
|
||||
def add_subpath(self, points):
|
||||
"""
|
||||
|
@ -186,16 +186,20 @@ class VMobject(Mobject):
|
|||
|
||||
## Alignment
|
||||
|
||||
def align_points(self, mobject):
|
||||
Mobject.align_points(self, mobject)
|
||||
is_subpath = self.is_subpath or mobject.is_subpath
|
||||
self.is_subpath = mobject.is_subpath = is_subpath
|
||||
mark_closed = self.mark_paths_closed and mobject.mark_paths_closed
|
||||
self.mark_paths_closed = mobject.mark_paths_closed = mark_closed
|
||||
return self
|
||||
|
||||
def align_points_with_larger(self, larger_mobject):
|
||||
assert(isinstance(larger_mobject, VMobject))
|
||||
self.insert_n_anchor_points(
|
||||
larger_mobject.get_num_anchor_points()-\
|
||||
self.get_num_anchor_points()
|
||||
)
|
||||
is_subpath = self.is_subpath or larger_mobject.is_subpath
|
||||
self.is_subpath = larger_mobject.is_subpath = is_subpath
|
||||
mark_closed = self.mark_paths_closed and larger_mobject.mark_paths_closed
|
||||
self.mark_paths_closed = larger_mobject.mark_paths_closed = mark_closed
|
||||
return self
|
||||
|
||||
def insert_n_anchor_points(self, n):
|
||||
|
|
|
@ -4,7 +4,7 @@ import itertools as it
|
|||
from helpers import *
|
||||
from scene import Scene
|
||||
from animation import Animation
|
||||
from mobject import TexMobject
|
||||
from mobject.tex_mobject import TexMobject
|
||||
|
||||
class RearrangeEquation(Scene):
|
||||
def construct(
|
||||
|
@ -12,8 +12,7 @@ class RearrangeEquation(Scene):
|
|||
start_terms,
|
||||
end_terms,
|
||||
index_map,
|
||||
size = None,
|
||||
path = counterclockwise_path(),
|
||||
path_arc = np.pi,
|
||||
start_transform = None,
|
||||
end_transform = None,
|
||||
leave_start_terms = False,
|
||||
|
@ -21,7 +20,7 @@ class RearrangeEquation(Scene):
|
|||
):
|
||||
transform_kwargs["path_func"] = path
|
||||
start_mobs, end_mobs = self.get_mobs_from_terms(
|
||||
start_terms, end_terms, size
|
||||
start_terms, end_terms
|
||||
)
|
||||
if start_transform:
|
||||
start_mobs = start_transform(Mobject(*start_mobs)).split()
|
||||
|
@ -59,7 +58,7 @@ class RearrangeEquation(Scene):
|
|||
self.dither()
|
||||
|
||||
|
||||
def get_mobs_from_terms(self, start_terms, end_terms, size):
|
||||
def get_mobs_from_terms(self, start_terms, end_terms):
|
||||
"""
|
||||
Need to ensure that all image mobjects for a tex expression
|
||||
stemming from the same string are point-for-point copies of one
|
||||
|
@ -68,8 +67,8 @@ class RearrangeEquation(Scene):
|
|||
"""
|
||||
num_start_terms = len(start_terms)
|
||||
all_mobs = np.array(
|
||||
TexMobject(start_terms, size = size).split() + \
|
||||
TexMobject(end_terms, size = size).split()
|
||||
TexMobject(start_terms).split() + \
|
||||
TexMobject(end_terms).split()
|
||||
)
|
||||
all_terms = np.array(start_terms+end_terms)
|
||||
for term in set(all_terms):
|
||||
|
|
|
@ -1,136 +1,113 @@
|
|||
from helpers import *
|
||||
|
||||
from mobject import Mobject
|
||||
from mobject.image_mobject import ImageMobject
|
||||
from mobject.tex_mobject import TexMobject, TextMobject
|
||||
from topics.geometry import Circle, Line
|
||||
from mobject.svg_mobject import SVGMobject
|
||||
from mobject.vectorized_mobject import VMobject
|
||||
from mobject.tex_mobject import TextMobject
|
||||
|
||||
|
||||
PI_CREATURE_DIR = os.path.join(IMAGE_DIR, "PiCreature")
|
||||
PI_CREATURE_SCALE_VAL = 0.5
|
||||
PI_CREATURE_MOUTH_TO_EYES_DISTANCE = 0.25
|
||||
|
||||
def part_name_to_directory(name):
|
||||
return os.path.join(PI_CREATURE_DIR, "pi_creature_"+name) + ".png"
|
||||
MOUTH_INDEX = 5
|
||||
BODY_INDEX = 4
|
||||
RIGHT_PUPIL_INDEX = 3
|
||||
LEFT_PUPIL_INDEX = 2
|
||||
RIGHT_EYE_INDEX = 1
|
||||
LEFT_EYE_INDEX = 0
|
||||
|
||||
class PiCreature(Mobject):
|
||||
|
||||
class PiCreature(SVGMobject):
|
||||
CONFIG = {
|
||||
"color" : BLUE_E
|
||||
"color" : BLUE_E,
|
||||
"stroke_width" : 0,
|
||||
"fill_opacity" : 1.0,
|
||||
}
|
||||
PART_NAMES = [
|
||||
'arm',
|
||||
'body',
|
||||
'left_eye',
|
||||
'right_eye',
|
||||
'left_leg',
|
||||
'right_leg',
|
||||
'mouth',
|
||||
]
|
||||
WHITE_PART_NAMES = ['left_eye', 'right_eye', 'mouth']
|
||||
def __init__(self, **kwargs):
|
||||
Mobject.__init__(self, **kwargs)
|
||||
for part_name in self.PART_NAMES:
|
||||
mob = ImageMobject(
|
||||
part_name_to_directory(part_name),
|
||||
should_center = False
|
||||
)
|
||||
if part_name not in self.WHITE_PART_NAMES:
|
||||
mob.highlight(self.color)
|
||||
setattr(self, part_name, mob)
|
||||
self.add(mob)
|
||||
self.eyes = Mobject(self.left_eye, self.right_eye)
|
||||
self.legs = Mobject(self.left_leg, self.right_leg)
|
||||
self.mouth.center().shift(self.get_mouth_center())
|
||||
self.add(self.mouth)
|
||||
self.scale(PI_CREATURE_SCALE_VAL)
|
||||
def __init__(self, mode = "plain", **kwargs):
|
||||
self.parts_named = False
|
||||
svg_file = os.path.join(
|
||||
PI_CREATURE_DIR,
|
||||
"PiCreatures_%s.svg"%mode
|
||||
)
|
||||
digest_config(self, kwargs, locals())
|
||||
SVGMobject.__init__(self, svg_file, **kwargs)
|
||||
self.init_colors()
|
||||
|
||||
def get_parts(self):
|
||||
return [getattr(self, pn) for pn in self.PART_NAMES]
|
||||
def move_into_position(self):
|
||||
self.scale_to_fit_height(4)
|
||||
self.center()
|
||||
|
||||
def get_white_parts(self):
|
||||
return [
|
||||
getattr(self, pn)
|
||||
for pn in self.WHITE_PART_NAMES
|
||||
]
|
||||
def name_parts(self):
|
||||
self.mouth = self.submobjects[MOUTH_INDEX]
|
||||
self.body = self.submobjects[BODY_INDEX]
|
||||
self.pupils = VMobject(*[
|
||||
self.submobjects[LEFT_PUPIL_INDEX],
|
||||
self.submobjects[RIGHT_PUPIL_INDEX]
|
||||
])
|
||||
self.eyes = VMobject(*[
|
||||
self.submobjects[LEFT_EYE_INDEX],
|
||||
self.submobjects[RIGHT_EYE_INDEX]
|
||||
])
|
||||
self.submobjects = []
|
||||
self.add(self.body, self.mouth, self.eyes, self.pupils)
|
||||
self.parts_named = True
|
||||
|
||||
def get_mouth_center(self):
|
||||
result = self.body.get_center()
|
||||
result[0] = self.eyes.get_center()[0]
|
||||
return result
|
||||
def init_colors(self):
|
||||
VMobject.init_colors(self)
|
||||
if not self.parts_named:
|
||||
self.name_parts()
|
||||
self.mouth.set_fill(BLACK)
|
||||
self.body.set_fill(self.color)
|
||||
self.pupils.set_fill(BLACK)
|
||||
self.eyes.set_fill(WHITE)
|
||||
|
||||
def highlight(self, color, condition = None):
|
||||
for part in set(self.get_parts()).difference(self.get_white_parts()):
|
||||
part.highlight(color, condition)
|
||||
|
||||
def highlight(self, color):
|
||||
self.body.set_fill(color)
|
||||
return self
|
||||
|
||||
def move_to(self, destination):
|
||||
self.shift(destination-self.get_bottom())
|
||||
return self
|
||||
|
||||
def get_eye_center(self):
|
||||
return self.eyes.get_center()
|
||||
|
||||
def make_mean(self):
|
||||
eye_x, eye_y = self.get_eye_center()[:2]
|
||||
def should_delete((x, y, z)):
|
||||
return y - eye_y > 0.3*abs(x - eye_x)
|
||||
self.eyes.highlight("black", should_delete)
|
||||
self.give_straight_face()
|
||||
def change_mode(self, mode):
|
||||
curr_center = self.get_center()
|
||||
curr_height = self.get_height()
|
||||
flip = self.is_flipped()
|
||||
self.__class__.__init__(self, mode)
|
||||
self.scale_to_fit_height(curr_height)
|
||||
self.shift(curr_center)
|
||||
if flip:
|
||||
self.flip()
|
||||
return self
|
||||
|
||||
def make_sad(self):
|
||||
eye_x, eye_y = self.get_eye_center()[:2]
|
||||
eye_y += 0.15
|
||||
def should_delete((x, y, z)):
|
||||
return y - eye_y > -0.3*abs(x - eye_x)
|
||||
self.eyey.highlight("black", should_delete)
|
||||
self.give_frown()
|
||||
def look_left(self):
|
||||
self.change_mode(self.mode + "_looking_left")
|
||||
return self
|
||||
|
||||
def get_step_intermediate(self, pi_creature):
|
||||
vect = pi_creature.get_center() - self.get_center()
|
||||
result = self.copy().shift(vect / 2.0)
|
||||
left_forward = vect[0] > 0
|
||||
if self.right_leg.get_center()[0] < self.left_leg.get_center()[0]:
|
||||
#For Mortimer's case
|
||||
left_forward = not left_forward
|
||||
if left_forward:
|
||||
result.left_leg.wag(vect/2.0, DOWN)
|
||||
result.right_leg.wag(-vect/2.0, DOWN)
|
||||
else:
|
||||
result.right_leg.wag(vect/2.0, DOWN)
|
||||
result.left_leg.wag(-vect/2.0, DOWN)
|
||||
return result
|
||||
def is_flipped(self):
|
||||
return self.eyes.submobjects[0].get_center()[0] > \
|
||||
self.eyes.submobjects[1].get_center()[0]
|
||||
|
||||
def blink(self):
|
||||
bottom = self.eyes.get_bottom()
|
||||
self.eyes.apply_function(
|
||||
lambda (x, y, z) : (x, bottom[1], z)
|
||||
)
|
||||
eye_bottom_y = self.eyes.get_bottom()[1]
|
||||
for mob in self.eyes, self.pupils:
|
||||
mob.apply_function(
|
||||
lambda p : [p[0], eye_bottom_y, p[2]]
|
||||
)
|
||||
return self
|
||||
|
||||
def shift_eyes(self):
|
||||
for eye in self.left_eye, self.right_eye:
|
||||
eye.rotate_in_place(np.pi, UP)
|
||||
return self
|
||||
|
||||
def to_symbol(self):
|
||||
Mobject.__init__(
|
||||
self,
|
||||
*list(set(self.get_parts()).difference(self.get_white_parts()))
|
||||
)
|
||||
|
||||
|
||||
class Randolph(PiCreature):
|
||||
pass #Nothing more than an alternative name
|
||||
|
||||
class Mortimer(PiCreature):
|
||||
CONFIG = {
|
||||
"color" : MAROON_E
|
||||
"color" : "#be2612"
|
||||
}
|
||||
def __init__(self, **kwargs):
|
||||
PiCreature.__init__(self, **kwargs)
|
||||
self.rotate(np.pi, UP)
|
||||
def __init__(self, *args, **kwargs):
|
||||
PiCreature.__init__(self, *args, **kwargs)
|
||||
self.flip()
|
||||
|
||||
|
||||
class Mathematician(PiCreature):
|
||||
|
@ -138,31 +115,43 @@ class Mathematician(PiCreature):
|
|||
"color" : GREY,
|
||||
}
|
||||
|
||||
class Bubble(Mobject):
|
||||
class Bubble(SVGMobject):
|
||||
CONFIG = {
|
||||
"direction" : LEFT,
|
||||
"center_point" : ORIGIN,
|
||||
"content_scale_factor" : 0.75,
|
||||
"height" : 4,
|
||||
"width" : 6,
|
||||
"file_name" : None,
|
||||
}
|
||||
def __init__(self, **kwargs):
|
||||
Mobject.__init__(self, **kwargs)
|
||||
self.center_offset = self.center_point - Mobject.get_center(self)
|
||||
digest_config(self, kwargs, locals())
|
||||
if self.file_name is None:
|
||||
raise Exception("Must invoke Bubble subclass")
|
||||
svg_file = os.path.join(
|
||||
IMAGE_DIR, self.file_name
|
||||
)
|
||||
SVGMobject.__init__(self, svg_file, **kwargs)
|
||||
self.center()
|
||||
self.stretch_to_fit_height(self.height)
|
||||
self.stretch_to_fit_width(self.width)
|
||||
if self.direction[0] > 0:
|
||||
self.rotate(np.pi, UP)
|
||||
Mobject.flip(self)
|
||||
self.content = Mobject()
|
||||
|
||||
def get_tip(self):
|
||||
raise Exception("Not implemented")
|
||||
return self.get_corner(DOWN+self.direction)
|
||||
|
||||
def get_bubble_center(self):
|
||||
return self.get_center()+self.center_offset
|
||||
return self.get_center() + self.get_height()*UP/8.0
|
||||
|
||||
def move_tip_to(self, point):
|
||||
self.shift(point - self.get_tip())
|
||||
return self
|
||||
|
||||
def flip(self):
|
||||
Mobject.flip(self)
|
||||
self.direction = -np.array(self.direction)
|
||||
self.rotate(np.pi, UP)
|
||||
return self
|
||||
|
||||
def pin_to(self, mobject):
|
||||
|
@ -175,11 +164,14 @@ class Bubble(Mobject):
|
|||
return self
|
||||
|
||||
def add_content(self, mobject):
|
||||
scaled_width = 0.75*self.get_width()
|
||||
if self.content in self.submobjects:
|
||||
self.submobjects.remove(self.content)
|
||||
scaled_width = self.content_scale_factor*self.get_width()
|
||||
if mobject.get_width() > scaled_width:
|
||||
mobject.scale(scaled_width / mobject.get_width())
|
||||
mobject.shift(self.get_bubble_center())
|
||||
self.content = mobject
|
||||
self.add(self.content)
|
||||
return self
|
||||
|
||||
def write(self, text):
|
||||
|
@ -187,69 +179,16 @@ class Bubble(Mobject):
|
|||
return self
|
||||
|
||||
def clear(self):
|
||||
self.content = Mobject()
|
||||
self.add_content(Mobject())
|
||||
return self
|
||||
|
||||
class SpeechBubble(Bubble):
|
||||
CONFIG = {
|
||||
"initial_width" : 6,
|
||||
"initial_height" : 4,
|
||||
"file_name" : "Bubbles_speech.svg",
|
||||
}
|
||||
|
||||
def generate_points(self):
|
||||
complex_power = 0.9
|
||||
radius = self.initial_width/2
|
||||
circle = Circle(radius = radius)
|
||||
circle.scale(1.0/radius)
|
||||
circle.apply_complex_function(lambda z : z**complex_power)
|
||||
circle.scale(radius)
|
||||
boundary_point_as_complex = radius*complex(-1)**complex_power
|
||||
boundary_points = [
|
||||
[
|
||||
boundary_point_as_complex.real,
|
||||
unit*boundary_point_as_complex.imag,
|
||||
0
|
||||
]
|
||||
for unit in -1, 1
|
||||
]
|
||||
tip = radius*(1.5*LEFT+UP)
|
||||
self.little_line = Line(boundary_points[0], tip)
|
||||
self.circle = circle
|
||||
self.add(
|
||||
circle,
|
||||
self.little_line,
|
||||
Line(boundary_points[1], tip)
|
||||
)
|
||||
self.highlight("white")
|
||||
self.rotate(np.pi/2)
|
||||
self.stretch_to_fit_height(self.initial_height)
|
||||
|
||||
def get_tip(self):
|
||||
return self.little_line.points[-1]
|
||||
|
||||
def get_bubble_center(self):
|
||||
return self.circle.get_center()
|
||||
|
||||
class ThoughtBubble(Bubble):
|
||||
CONFIG = {
|
||||
"num_bulges" : 7,
|
||||
"initial_inner_radius" : 1.8,
|
||||
"initial_width" : 6,
|
||||
"file_name" : "Bubbles_thought.svg",
|
||||
}
|
||||
def __init__(self, **kwargs):
|
||||
Bubble.__init__(self, **kwargs)
|
||||
|
||||
def get_tip(self):
|
||||
return self.small_circle.get_bottom()
|
||||
|
||||
def generate_points(self):
|
||||
self.small_circle = Circle().scale(0.15)
|
||||
self.small_circle.shift(2.5*DOWN+2*LEFT)
|
||||
self.add(self.small_circle)
|
||||
self.add(Circle().scale(0.3).shift(2*DOWN+1.5*LEFT))
|
||||
for n in range(self.num_bulges):
|
||||
theta = 2*np.pi*n/self.num_bulges
|
||||
self.add(Circle().shift((np.cos(theta), np.sin(theta), 0)))
|
||||
self.filter_out(lambda p : np.linalg.norm(p) < self.initial_inner_radius)
|
||||
self.stretch_to_fit_width(self.initial_width)
|
||||
self.highlight("white")
|
||||
|
|
|
@ -50,6 +50,10 @@ class Dot(Circle): #Use 1D density, even though 2D
|
|||
"fill_color" : WHITE,
|
||||
"fill_opacity" : 1.0
|
||||
}
|
||||
def __init__(self, point = ORIGIN, **kwargs):
|
||||
Circle.__init__(self, **kwargs)
|
||||
self.shift(point)
|
||||
self.init_colors()
|
||||
|
||||
|
||||
class Line(VMobject):
|
||||
|
|
Loading…
Add table
Reference in a new issue