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):
|
class Transform(Animation):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"path_func" : straight_path
|
"path_arc" : 0,
|
||||||
|
"path_func" : None,
|
||||||
}
|
}
|
||||||
def __init__(self, mobject, ending_mobject, **kwargs):
|
def __init__(self, mobject, ending_mobject, **kwargs):
|
||||||
#Copy ending_mobject so as to not mess with caller
|
#Copy ending_mobject so as to not mess with caller
|
||||||
ending_mobject = ending_mobject.copy()
|
ending_mobject = ending_mobject.copy()
|
||||||
digest_config(self, kwargs, locals())
|
digest_config(self, kwargs, locals())
|
||||||
mobject.align_data(ending_mobject)
|
mobject.align_data(ending_mobject)
|
||||||
|
self.init_path_func()
|
||||||
|
|
||||||
Animation.__init__(self, mobject, **kwargs)
|
Animation.__init__(self, mobject, **kwargs)
|
||||||
self.name += "To" + str(ending_mobject)
|
self.name += "To" + str(ending_mobject)
|
||||||
self.mobject.stroke_width = ending_mobject.stroke_width
|
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):
|
def update_mobject(self, alpha):
|
||||||
families = map(
|
families = map(
|
||||||
|
@ -36,12 +46,12 @@ class Transform(Animation):
|
||||||
|
|
||||||
class ClockwiseTransform(Transform):
|
class ClockwiseTransform(Transform):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"path_func" : clockwise_path()
|
"path_arc" : -np.pi
|
||||||
}
|
}
|
||||||
|
|
||||||
class CounterclockwiseTransform(Transform):
|
class CounterclockwiseTransform(Transform):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"path_func" : counterclockwise_path()
|
"path_arc" : np.pi
|
||||||
}
|
}
|
||||||
|
|
||||||
class GrowFromCenter(Transform):
|
class GrowFromCenter(Transform):
|
||||||
|
|
|
@ -30,8 +30,8 @@ class Mobject(object):
|
||||||
if self.name is None:
|
if self.name is None:
|
||||||
self.name = self.__class__.__name__
|
self.name = self.__class__.__name__
|
||||||
self.init_points()
|
self.init_points()
|
||||||
self.init_colors()
|
|
||||||
self.generate_points()
|
self.generate_points()
|
||||||
|
self.init_colors()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
@ -172,6 +172,9 @@ class Mobject(object):
|
||||||
self.do_in_place(self.rotate, angle, axis, axes)
|
self.do_in_place(self.rotate, angle, axis, axes)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
def flip(self, axis = UP):
|
||||||
|
self.rotate_in_place(np.pi, axis)
|
||||||
|
|
||||||
def scale_in_place(self, scale_factor):
|
def scale_in_place(self, scale_factor):
|
||||||
self.do_in_place(self.scale, scale_factor)
|
self.do_in_place(self.scale, scale_factor)
|
||||||
return self
|
return self
|
||||||
|
|
|
@ -2,14 +2,14 @@ from .mobject import Mobject
|
||||||
from helpers import *
|
from helpers import *
|
||||||
|
|
||||||
class PMobject(Mobject):
|
class PMobject(Mobject):
|
||||||
def init_colors(self):
|
def init_points(self):
|
||||||
self.rgbs = np.zeros((0, 3))
|
self.rgbs = np.zeros((0, 3))
|
||||||
|
self.points = np.zeros((0, 3))
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def get_array_attrs(self):
|
def get_array_attrs(self):
|
||||||
return Mobject.get_array_attrs(self) + ["rgbs"]
|
return Mobject.get_array_attrs(self) + ["rgbs"]
|
||||||
|
|
||||||
|
|
||||||
def add_points(self, points, rgbs = None, color = None):
|
def add_points(self, points, rgbs = None, color = None):
|
||||||
"""
|
"""
|
||||||
points must be a Nx3 numpy array, as must rgbs if it is not 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 topics.geometry import Rectangle, Circle
|
||||||
from helpers import *
|
from helpers import *
|
||||||
|
|
||||||
SVG_SCALE_VALUE = 0.05
|
|
||||||
|
|
||||||
class SVGMobject(VMobject):
|
class SVGMobject(VMobject):
|
||||||
CONFIG = {
|
|
||||||
"stroke_width" : 0,
|
|
||||||
"fill_opacity" : 1.0,
|
|
||||||
"fill_color" : WHITE, #TODO...
|
|
||||||
}
|
|
||||||
def __init__(self, svg_file, **kwargs):
|
def __init__(self, svg_file, **kwargs):
|
||||||
digest_config(self, kwargs, locals())
|
digest_config(self, kwargs, locals())
|
||||||
VMobject.__init__(self, **kwargs)
|
VMobject.__init__(self, **kwargs)
|
||||||
|
self.move_into_position()
|
||||||
|
|
||||||
def generate_points(self):
|
def generate_points(self):
|
||||||
doc = minidom.parse(self.svg_file)
|
doc = minidom.parse(self.svg_file)
|
||||||
defs = doc.getElementsByTagName("defs")[0]
|
self.ref_to_element = {}
|
||||||
g = doc.getElementsByTagName("g")[0]
|
for svg in doc.getElementsByTagName("svg"):
|
||||||
ref_to_mob = self.get_ref_to_mobject_map(defs)
|
self.add(*self.get_mobjects_from(svg))
|
||||||
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)
|
|
||||||
doc.unlink()
|
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
|
#Remove initial "#" character
|
||||||
ref = use_element.getAttribute("xlink:href")[1:]
|
ref = use_element.getAttribute("xlink:href")[1:]
|
||||||
try:
|
try:
|
||||||
mob = ref_to_mob[ref]
|
return self.get_mobjects_from(
|
||||||
|
self.ref_to_element[ref]
|
||||||
|
)
|
||||||
except:
|
except:
|
||||||
warnings.warn("%s not recognized"%ref)
|
warnings.warn("%s not recognized"%ref)
|
||||||
return
|
return
|
||||||
if mob in self.submobjects:
|
|
||||||
mob = VMobjectFromSVGPathstring(
|
# <circle class="st1" cx="143.8" cy="268" r="22.6"/>
|
||||||
mob.get_original_path_string()
|
|
||||||
)
|
|
||||||
self.handle_transform(use_element, mob)
|
|
||||||
self.handle_shift(use_element, mob)
|
|
||||||
return mob
|
|
||||||
|
|
||||||
def circle_to_mobject(self, circle_element):
|
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):
|
def rect_to_mobject(self, rect_element):
|
||||||
if rect_element.hasAttribute("fill"):
|
if rect_element.hasAttribute("fill"):
|
||||||
|
@ -70,45 +88,30 @@ class SVGMobject(VMobject):
|
||||||
fill_color = WHITE,
|
fill_color = WHITE,
|
||||||
fill_opacity = 1.0
|
fill_opacity = 1.0
|
||||||
)
|
)
|
||||||
self.handle_shift(rect_element, mob)
|
|
||||||
mob.shift(mob.get_center()-mob.get_corner(DOWN+LEFT))
|
mob.shift(mob.get_center()-mob.get_corner(DOWN+LEFT))
|
||||||
return mob
|
return mob
|
||||||
|
|
||||||
def handle_shift(self, element, mobject):
|
def handle_transforms(self, element, mobject):
|
||||||
x, y = 0, 0
|
x, y = 0, 0
|
||||||
if element.hasAttribute('x'):
|
try:
|
||||||
x = float(element.getAttribute('x'))
|
x = float(element.getAttribute('x'))
|
||||||
if element.hasAttribute('y'):
|
|
||||||
#Flip y
|
#Flip y
|
||||||
y = -float(element.getAttribute('y'))
|
y = -float(element.getAttribute('y'))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
mobject.shift(x*RIGHT+y*UP)
|
mobject.shift(x*RIGHT+y*UP)
|
||||||
|
#TODO, transforms
|
||||||
|
|
||||||
def handle_transform(self, element, mobject):
|
def update_ref_to_element(self, defs):
|
||||||
pass
|
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):
|
def move_into_position(self):
|
||||||
self.center()
|
pass #subclasses should tweak as needed
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
class VMobjectFromSVGPathstring(VMobject):
|
class VMobjectFromSVGPathstring(VMobject):
|
||||||
|
@ -117,7 +120,7 @@ class VMobjectFromSVGPathstring(VMobject):
|
||||||
VMobject.__init__(self, **kwargs)
|
VMobject.__init__(self, **kwargs)
|
||||||
|
|
||||||
def get_path_commands(self):
|
def get_path_commands(self):
|
||||||
return [
|
result = [
|
||||||
"M", #moveto
|
"M", #moveto
|
||||||
"L", #lineto
|
"L", #lineto
|
||||||
"H", #horizontal lineto
|
"H", #horizontal lineto
|
||||||
|
@ -129,6 +132,8 @@ class VMobjectFromSVGPathstring(VMobject):
|
||||||
"A", #elliptical Arc
|
"A", #elliptical Arc
|
||||||
"Z", #closepath
|
"Z", #closepath
|
||||||
]
|
]
|
||||||
|
result += map(lambda s : s.lower(), result)
|
||||||
|
return result
|
||||||
|
|
||||||
def generate_points(self):
|
def generate_points(self):
|
||||||
pattern = "[%s]"%("".join(self.get_path_commands()))
|
pattern = "[%s]"%("".join(self.get_path_commands()))
|
||||||
|
@ -144,10 +149,14 @@ class VMobjectFromSVGPathstring(VMobject):
|
||||||
self.rotate(np.pi, RIGHT)
|
self.rotate(np.pi, RIGHT)
|
||||||
|
|
||||||
def handle_command(self, command, coord_string):
|
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
|
#new_points are the points that will be added to the curr_points
|
||||||
#list. This variable may get modified in the conditionals below.
|
#list. This variable may get modified in the conditionals below.
|
||||||
points = self.growing_path.points
|
points = self.growing_path.points
|
||||||
new_points = self.string_to_points(coord_string)
|
new_points = self.string_to_points(coord_string)
|
||||||
|
if isLower:
|
||||||
|
new_points += points[-1]
|
||||||
if command == "M": #moveto
|
if command == "M": #moveto
|
||||||
if len(points) > 0:
|
if len(points) > 0:
|
||||||
self.growing_path = self.add_subpath(new_points)
|
self.growing_path = self.add_subpath(new_points)
|
||||||
|
@ -178,9 +187,10 @@ class VMobjectFromSVGPathstring(VMobject):
|
||||||
self.growing_path.add_control_points(new_points)
|
self.growing_path.add_control_points(new_points)
|
||||||
|
|
||||||
def string_to_points(self, coord_string):
|
def string_to_points(self, coord_string):
|
||||||
|
coord_string = coord_string.replace("-",",-")
|
||||||
numbers = [
|
numbers = [
|
||||||
float(s)
|
float(s)
|
||||||
for s in coord_string.split(" ")
|
for s in re.split("[ ,]", coord_string)
|
||||||
if s != ""
|
if s != ""
|
||||||
]
|
]
|
||||||
if len(numbers)%2 == 1:
|
if len(numbers)%2 == 1:
|
||||||
|
|
|
@ -2,11 +2,14 @@ from vectorized_mobject import VMobject
|
||||||
from svg_mobject import SVGMobject
|
from svg_mobject import SVGMobject
|
||||||
from helpers import *
|
from helpers import *
|
||||||
|
|
||||||
|
TEX_MOB_SCALE_VAL = 0.05
|
||||||
|
|
||||||
class TexMobject(SVGMobject):
|
class TexMobject(SVGMobject):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"template_tex_file" : TEMPLATE_TEX_FILE,
|
"template_tex_file" : TEMPLATE_TEX_FILE,
|
||||||
"color" : WHITE,
|
|
||||||
"stroke_width" : 0,
|
"stroke_width" : 0,
|
||||||
|
"fill_opacity" : 1.0,
|
||||||
|
"fill_color" : WHITE,
|
||||||
"should_center" : True,
|
"should_center" : True,
|
||||||
"next_to_direction" : RIGHT,
|
"next_to_direction" : RIGHT,
|
||||||
"next_to_buff" : 0.2,
|
"next_to_buff" : 0.2,
|
||||||
|
@ -14,8 +17,8 @@ class TexMobject(SVGMobject):
|
||||||
def __init__(self, expression, **kwargs):
|
def __init__(self, expression, **kwargs):
|
||||||
digest_config(self, kwargs, locals())
|
digest_config(self, kwargs, locals())
|
||||||
VMobject.__init__(self, **kwargs)
|
VMobject.__init__(self, **kwargs)
|
||||||
if self.should_center:
|
self.move_into_position()
|
||||||
self.center()
|
self.organize_submobjects()
|
||||||
|
|
||||||
def generate_points(self):
|
def generate_points(self):
|
||||||
if isinstance(self.expression, list):
|
if isinstance(self.expression, list):
|
||||||
|
@ -26,7 +29,6 @@ class TexMobject(SVGMobject):
|
||||||
self.template_tex_file
|
self.template_tex_file
|
||||||
)
|
)
|
||||||
SVGMobject.generate_points(self)
|
SVGMobject.generate_points(self)
|
||||||
self.init_colors()
|
|
||||||
|
|
||||||
|
|
||||||
def handle_list_expression(self):
|
def handle_list_expression(self):
|
||||||
|
@ -44,6 +46,15 @@ class TexMobject(SVGMobject):
|
||||||
self.submobjects = subs
|
self.submobjects = subs
|
||||||
return self
|
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):
|
class TextMobject(TexMobject):
|
||||||
|
@ -130,45 +141,6 @@ def dvi_to_svg(dvi_file, regen_if_exists = False):
|
||||||
return result
|
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")
|
raise Exception("Unknown mode")
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def change_mode(self, mode):
|
def change_anchor_mode(self, mode):
|
||||||
anchors, h1, h2 = self.get_anchors_and_handles()
|
anchors, h1, h2 = self.get_anchors_and_handles()
|
||||||
self.set_anchor_points(anchors, mode = mode)
|
self.set_anchor_points(anchors, mode = mode)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def make_smooth(self):
|
def make_smooth(self):
|
||||||
return self.change_mode("smooth")
|
return self.change_anchor_mode("smooth")
|
||||||
|
|
||||||
def make_jagged(self):
|
def make_jagged(self):
|
||||||
return self.change_mode("corners")
|
return self.change_anchor_mode("corners")
|
||||||
|
|
||||||
def add_subpath(self, points):
|
def add_subpath(self, points):
|
||||||
"""
|
"""
|
||||||
|
@ -186,16 +186,20 @@ class VMobject(Mobject):
|
||||||
|
|
||||||
## Alignment
|
## 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):
|
def align_points_with_larger(self, larger_mobject):
|
||||||
assert(isinstance(larger_mobject, VMobject))
|
assert(isinstance(larger_mobject, VMobject))
|
||||||
self.insert_n_anchor_points(
|
self.insert_n_anchor_points(
|
||||||
larger_mobject.get_num_anchor_points()-\
|
larger_mobject.get_num_anchor_points()-\
|
||||||
self.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
|
return self
|
||||||
|
|
||||||
def insert_n_anchor_points(self, n):
|
def insert_n_anchor_points(self, n):
|
||||||
|
|
|
@ -4,7 +4,7 @@ import itertools as it
|
||||||
from helpers import *
|
from helpers import *
|
||||||
from scene import Scene
|
from scene import Scene
|
||||||
from animation import Animation
|
from animation import Animation
|
||||||
from mobject import TexMobject
|
from mobject.tex_mobject import TexMobject
|
||||||
|
|
||||||
class RearrangeEquation(Scene):
|
class RearrangeEquation(Scene):
|
||||||
def construct(
|
def construct(
|
||||||
|
@ -12,8 +12,7 @@ class RearrangeEquation(Scene):
|
||||||
start_terms,
|
start_terms,
|
||||||
end_terms,
|
end_terms,
|
||||||
index_map,
|
index_map,
|
||||||
size = None,
|
path_arc = np.pi,
|
||||||
path = counterclockwise_path(),
|
|
||||||
start_transform = None,
|
start_transform = None,
|
||||||
end_transform = None,
|
end_transform = None,
|
||||||
leave_start_terms = False,
|
leave_start_terms = False,
|
||||||
|
@ -21,7 +20,7 @@ class RearrangeEquation(Scene):
|
||||||
):
|
):
|
||||||
transform_kwargs["path_func"] = path
|
transform_kwargs["path_func"] = path
|
||||||
start_mobs, end_mobs = self.get_mobs_from_terms(
|
start_mobs, end_mobs = self.get_mobs_from_terms(
|
||||||
start_terms, end_terms, size
|
start_terms, end_terms
|
||||||
)
|
)
|
||||||
if start_transform:
|
if start_transform:
|
||||||
start_mobs = start_transform(Mobject(*start_mobs)).split()
|
start_mobs = start_transform(Mobject(*start_mobs)).split()
|
||||||
|
@ -59,7 +58,7 @@ class RearrangeEquation(Scene):
|
||||||
self.dither()
|
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
|
Need to ensure that all image mobjects for a tex expression
|
||||||
stemming from the same string are point-for-point copies of one
|
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)
|
num_start_terms = len(start_terms)
|
||||||
all_mobs = np.array(
|
all_mobs = np.array(
|
||||||
TexMobject(start_terms, size = size).split() + \
|
TexMobject(start_terms).split() + \
|
||||||
TexMobject(end_terms, size = size).split()
|
TexMobject(end_terms).split()
|
||||||
)
|
)
|
||||||
all_terms = np.array(start_terms+end_terms)
|
all_terms = np.array(start_terms+end_terms)
|
||||||
for term in set(all_terms):
|
for term in set(all_terms):
|
||||||
|
|
|
@ -1,136 +1,113 @@
|
||||||
from helpers import *
|
from helpers import *
|
||||||
|
|
||||||
from mobject import Mobject
|
from mobject import Mobject
|
||||||
from mobject.image_mobject import ImageMobject
|
from mobject.svg_mobject import SVGMobject
|
||||||
from mobject.tex_mobject import TexMobject, TextMobject
|
from mobject.vectorized_mobject import VMobject
|
||||||
from topics.geometry import Circle, Line
|
from mobject.tex_mobject import TextMobject
|
||||||
|
|
||||||
|
|
||||||
PI_CREATURE_DIR = os.path.join(IMAGE_DIR, "PiCreature")
|
PI_CREATURE_DIR = os.path.join(IMAGE_DIR, "PiCreature")
|
||||||
PI_CREATURE_SCALE_VAL = 0.5
|
PI_CREATURE_SCALE_VAL = 0.5
|
||||||
PI_CREATURE_MOUTH_TO_EYES_DISTANCE = 0.25
|
|
||||||
|
|
||||||
def part_name_to_directory(name):
|
MOUTH_INDEX = 5
|
||||||
return os.path.join(PI_CREATURE_DIR, "pi_creature_"+name) + ".png"
|
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 = {
|
CONFIG = {
|
||||||
"color" : BLUE_E
|
"color" : BLUE_E,
|
||||||
|
"stroke_width" : 0,
|
||||||
|
"fill_opacity" : 1.0,
|
||||||
}
|
}
|
||||||
PART_NAMES = [
|
def __init__(self, mode = "plain", **kwargs):
|
||||||
'arm',
|
self.parts_named = False
|
||||||
'body',
|
svg_file = os.path.join(
|
||||||
'left_eye',
|
PI_CREATURE_DIR,
|
||||||
'right_eye',
|
"PiCreatures_%s.svg"%mode
|
||||||
'left_leg',
|
)
|
||||||
'right_leg',
|
digest_config(self, kwargs, locals())
|
||||||
'mouth',
|
SVGMobject.__init__(self, svg_file, **kwargs)
|
||||||
]
|
self.init_colors()
|
||||||
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 get_parts(self):
|
def move_into_position(self):
|
||||||
return [getattr(self, pn) for pn in self.PART_NAMES]
|
self.scale_to_fit_height(4)
|
||||||
|
self.center()
|
||||||
|
|
||||||
def get_white_parts(self):
|
def name_parts(self):
|
||||||
return [
|
self.mouth = self.submobjects[MOUTH_INDEX]
|
||||||
getattr(self, pn)
|
self.body = self.submobjects[BODY_INDEX]
|
||||||
for pn in self.WHITE_PART_NAMES
|
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):
|
def init_colors(self):
|
||||||
result = self.body.get_center()
|
VMobject.init_colors(self)
|
||||||
result[0] = self.eyes.get_center()[0]
|
if not self.parts_named:
|
||||||
return result
|
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()):
|
def highlight(self, color):
|
||||||
part.highlight(color, condition)
|
self.body.set_fill(color)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def move_to(self, destination):
|
def move_to(self, destination):
|
||||||
self.shift(destination-self.get_bottom())
|
self.shift(destination-self.get_bottom())
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def get_eye_center(self):
|
def change_mode(self, mode):
|
||||||
return self.eyes.get_center()
|
curr_center = self.get_center()
|
||||||
|
curr_height = self.get_height()
|
||||||
def make_mean(self):
|
flip = self.is_flipped()
|
||||||
eye_x, eye_y = self.get_eye_center()[:2]
|
self.__class__.__init__(self, mode)
|
||||||
def should_delete((x, y, z)):
|
self.scale_to_fit_height(curr_height)
|
||||||
return y - eye_y > 0.3*abs(x - eye_x)
|
self.shift(curr_center)
|
||||||
self.eyes.highlight("black", should_delete)
|
if flip:
|
||||||
self.give_straight_face()
|
self.flip()
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def make_sad(self):
|
def look_left(self):
|
||||||
eye_x, eye_y = self.get_eye_center()[:2]
|
self.change_mode(self.mode + "_looking_left")
|
||||||
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()
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def get_step_intermediate(self, pi_creature):
|
def is_flipped(self):
|
||||||
vect = pi_creature.get_center() - self.get_center()
|
return self.eyes.submobjects[0].get_center()[0] > \
|
||||||
result = self.copy().shift(vect / 2.0)
|
self.eyes.submobjects[1].get_center()[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 blink(self):
|
def blink(self):
|
||||||
bottom = self.eyes.get_bottom()
|
eye_bottom_y = self.eyes.get_bottom()[1]
|
||||||
self.eyes.apply_function(
|
for mob in self.eyes, self.pupils:
|
||||||
lambda (x, y, z) : (x, bottom[1], z)
|
mob.apply_function(
|
||||||
)
|
lambda p : [p[0], eye_bottom_y, p[2]]
|
||||||
|
)
|
||||||
return self
|
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):
|
class Randolph(PiCreature):
|
||||||
pass #Nothing more than an alternative name
|
pass #Nothing more than an alternative name
|
||||||
|
|
||||||
class Mortimer(PiCreature):
|
class Mortimer(PiCreature):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"color" : MAROON_E
|
"color" : "#be2612"
|
||||||
}
|
}
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
PiCreature.__init__(self, **kwargs)
|
PiCreature.__init__(self, *args, **kwargs)
|
||||||
self.rotate(np.pi, UP)
|
self.flip()
|
||||||
|
|
||||||
|
|
||||||
class Mathematician(PiCreature):
|
class Mathematician(PiCreature):
|
||||||
|
@ -138,31 +115,43 @@ class Mathematician(PiCreature):
|
||||||
"color" : GREY,
|
"color" : GREY,
|
||||||
}
|
}
|
||||||
|
|
||||||
class Bubble(Mobject):
|
class Bubble(SVGMobject):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"direction" : LEFT,
|
"direction" : LEFT,
|
||||||
"center_point" : ORIGIN,
|
"center_point" : ORIGIN,
|
||||||
|
"content_scale_factor" : 0.75,
|
||||||
|
"height" : 4,
|
||||||
|
"width" : 6,
|
||||||
|
"file_name" : None,
|
||||||
}
|
}
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
Mobject.__init__(self, **kwargs)
|
digest_config(self, kwargs, locals())
|
||||||
self.center_offset = self.center_point - Mobject.get_center(self)
|
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:
|
if self.direction[0] > 0:
|
||||||
self.rotate(np.pi, UP)
|
Mobject.flip(self)
|
||||||
self.content = Mobject()
|
self.content = Mobject()
|
||||||
|
|
||||||
def get_tip(self):
|
def get_tip(self):
|
||||||
raise Exception("Not implemented")
|
return self.get_corner(DOWN+self.direction)
|
||||||
|
|
||||||
def get_bubble_center(self):
|
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):
|
def move_tip_to(self, point):
|
||||||
self.shift(point - self.get_tip())
|
self.shift(point - self.get_tip())
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def flip(self):
|
def flip(self):
|
||||||
|
Mobject.flip(self)
|
||||||
self.direction = -np.array(self.direction)
|
self.direction = -np.array(self.direction)
|
||||||
self.rotate(np.pi, UP)
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def pin_to(self, mobject):
|
def pin_to(self, mobject):
|
||||||
|
@ -175,11 +164,14 @@ class Bubble(Mobject):
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def add_content(self, mobject):
|
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:
|
if mobject.get_width() > scaled_width:
|
||||||
mobject.scale(scaled_width / mobject.get_width())
|
mobject.scale(scaled_width / mobject.get_width())
|
||||||
mobject.shift(self.get_bubble_center())
|
mobject.shift(self.get_bubble_center())
|
||||||
self.content = mobject
|
self.content = mobject
|
||||||
|
self.add(self.content)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def write(self, text):
|
def write(self, text):
|
||||||
|
@ -187,69 +179,16 @@ class Bubble(Mobject):
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
self.content = Mobject()
|
self.add_content(Mobject())
|
||||||
return self
|
return self
|
||||||
|
|
||||||
class SpeechBubble(Bubble):
|
class SpeechBubble(Bubble):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"initial_width" : 6,
|
"file_name" : "Bubbles_speech.svg",
|
||||||
"initial_height" : 4,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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):
|
class ThoughtBubble(Bubble):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"num_bulges" : 7,
|
"file_name" : "Bubbles_thought.svg",
|
||||||
"initial_inner_radius" : 1.8,
|
|
||||||
"initial_width" : 6,
|
|
||||||
}
|
}
|
||||||
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_color" : WHITE,
|
||||||
"fill_opacity" : 1.0
|
"fill_opacity" : 1.0
|
||||||
}
|
}
|
||||||
|
def __init__(self, point = ORIGIN, **kwargs):
|
||||||
|
Circle.__init__(self, **kwargs)
|
||||||
|
self.shift(point)
|
||||||
|
self.init_colors()
|
||||||
|
|
||||||
|
|
||||||
class Line(VMobject):
|
class Line(VMobject):
|
||||||
|
|
Loading…
Add table
Reference in a new issue