mirror of
https://github.com/3b1b/manim.git
synced 2025-09-01 00:48:45 +00:00
TexMobjects are looking pretty good
This commit is contained in:
parent
0d4e928b6e
commit
f5e4c3b334
7 changed files with 119 additions and 62 deletions
|
@ -15,14 +15,9 @@ class Transform(Animation):
|
||||||
"path_func" : straight_path
|
"path_func" : straight_path
|
||||||
}
|
}
|
||||||
def __init__(self, mobject, ending_mobject, **kwargs):
|
def __init__(self, mobject, ending_mobject, **kwargs):
|
||||||
mobject =mobject
|
|
||||||
#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())
|
||||||
count1, count2 = mobject.get_num_points(), ending_mobject.get_num_points()
|
|
||||||
if count2 == 0:
|
|
||||||
ending_mobject = mobject.get_point_mobject()
|
|
||||||
count2 = ending_mobject.get_num_points()
|
|
||||||
mobject.align_data(ending_mobject)
|
mobject.align_data(ending_mobject)
|
||||||
|
|
||||||
Animation.__init__(self, mobject, **kwargs)
|
Animation.__init__(self, mobject, **kwargs)
|
||||||
|
|
25
camera.py
25
camera.py
|
@ -1,11 +1,9 @@
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import itertools as it
|
import itertools as it
|
||||||
import os
|
import os
|
||||||
import sys
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
import cv2
|
|
||||||
from colour import Color
|
from colour import Color
|
||||||
import progressbar
|
|
||||||
import aggdraw
|
import aggdraw
|
||||||
|
|
||||||
from helpers import *
|
from helpers import *
|
||||||
|
@ -71,15 +69,22 @@ class Camera(object):
|
||||||
mob.nonempty_family_members()
|
mob.nonempty_family_members()
|
||||||
for mob in mobjects
|
for mob in mobjects
|
||||||
])
|
])
|
||||||
|
vmobjects = []
|
||||||
for mobject in mobjects:
|
for mobject in mobjects:
|
||||||
if isinstance(mobject, VMobject):
|
if isinstance(mobject, VMobject):
|
||||||
self.display_vectorized(mobject)
|
vmobjects.append(mobject)
|
||||||
elif isinstance(mobject, PMobject):
|
elif isinstance(mobject, PMobject):
|
||||||
self.display_point_cloud(
|
self.display_point_cloud(
|
||||||
mobject.points, mobject.rgbs,
|
mobject.points, mobject.rgbs,
|
||||||
self.adjusted_thickness(mobject.stroke_width)
|
self.adjusted_thickness(mobject.stroke_width)
|
||||||
)
|
)
|
||||||
#TODO, more? Call out if it's unknown?
|
#TODO, more? Call out if it's unknown?
|
||||||
|
image = Image.fromarray(self.pixel_array, mode = "RGB")
|
||||||
|
canvas = aggdraw.Draw(image)
|
||||||
|
for vmobject in vmobjects:
|
||||||
|
self.display_vectorized(vmobject, canvas)
|
||||||
|
canvas.flush()
|
||||||
|
self.pixel_array[:,:] = np.array(image)
|
||||||
|
|
||||||
|
|
||||||
def display_region(self, region):
|
def display_region(self, region):
|
||||||
|
@ -95,19 +100,16 @@ class Camera(object):
|
||||||
self.pixel_array[covered] = rgb
|
self.pixel_array[covered] = rgb
|
||||||
|
|
||||||
|
|
||||||
def display_vectorized(self, vmobject):
|
def display_vectorized(self, vmobject, canvas):
|
||||||
if vmobject.is_subpath:
|
if vmobject.is_subpath:
|
||||||
#Subpath vectorized mobjects are taken care
|
#Subpath vectorized mobjects are taken care
|
||||||
#of by their parent
|
#of by their parent
|
||||||
return
|
return
|
||||||
im = Image.fromarray(self.pixel_array, mode = "RGB")
|
|
||||||
canvas = aggdraw.Draw(im)
|
|
||||||
pen, fill = self.get_pen_and_fill(vmobject)
|
pen, fill = self.get_pen_and_fill(vmobject)
|
||||||
pathstring = self.get_pathstring(vmobject)
|
pathstring = self.get_pathstring(vmobject)
|
||||||
symbol = aggdraw.Symbol(pathstring)
|
symbol = aggdraw.Symbol(pathstring)
|
||||||
canvas.symbol((0, 0), symbol, pen, fill)
|
canvas.symbol((0, 0), symbol, pen, fill)
|
||||||
canvas.flush()
|
|
||||||
self.pixel_array[:,:] = np.array(im)
|
|
||||||
|
|
||||||
def get_pen_and_fill(self, vmobject):
|
def get_pen_and_fill(self, vmobject):
|
||||||
pen = aggdraw.Pen(
|
pen = aggdraw.Pen(
|
||||||
|
@ -122,7 +124,7 @@ class Camera(object):
|
||||||
|
|
||||||
def get_pathstring(self, vmobject):
|
def get_pathstring(self, vmobject):
|
||||||
result = ""
|
result = ""
|
||||||
for mob in [vmobject]+vmobject.subpath_mobjects:
|
for mob in [vmobject]+vmobject.get_subpath_mobjects():
|
||||||
points = mob.points
|
points = mob.points
|
||||||
if len(points) == 0:
|
if len(points) == 0:
|
||||||
continue
|
continue
|
||||||
|
@ -166,12 +168,11 @@ class Camera(object):
|
||||||
indices = np.dot(pixel_coords, flattener)[:,0]
|
indices = np.dot(pixel_coords, flattener)[:,0]
|
||||||
indices = indices.astype('int')
|
indices = indices.astype('int')
|
||||||
|
|
||||||
# new_array = np.zeros((pw*ph, 3), dtype = 'uint8')
|
|
||||||
# new_array[indices, :] = rgbs
|
|
||||||
new_pa = self.pixel_array.reshape((ph*pw, 3))
|
new_pa = self.pixel_array.reshape((ph*pw, 3))
|
||||||
new_pa[indices] = rgbs
|
new_pa[indices] = rgbs
|
||||||
self.pixel_array = new_pa.reshape((ph, pw, 3))
|
self.pixel_array = new_pa.reshape((ph, pw, 3))
|
||||||
|
|
||||||
|
|
||||||
def align_points_to_camera(self, points):
|
def align_points_to_camera(self, points):
|
||||||
## This is where projection should live
|
## This is where projection should live
|
||||||
return points - self.space_center
|
return points - self.space_center
|
||||||
|
|
|
@ -452,16 +452,24 @@ class Mobject(object):
|
||||||
def push_self_into_submobjects(self):
|
def push_self_into_submobjects(self):
|
||||||
copy = self.copy()
|
copy = self.copy()
|
||||||
copy.submobjects = []
|
copy.submobjects = []
|
||||||
self.points = np.zeros((0, self.dim))
|
self.init_points()
|
||||||
self.add(copy)
|
self.add(copy)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def add_n_more_submobjects(self, n):
|
def add_n_more_submobjects(self, n):
|
||||||
if n > 0 and len(self.submobjects) == 0:
|
curr = len(self.submobjects)
|
||||||
|
if n > 0 and curr == 0:
|
||||||
self.add(self.copy())
|
self.add(self.copy())
|
||||||
n = n-1
|
n -= 1
|
||||||
for i in range(n):
|
curr += 1
|
||||||
self.add(self.submobjects[i].copy())
|
indices = curr*np.arange(curr+n)/(curr+n)
|
||||||
|
new_submobjects = []
|
||||||
|
for index in indices:
|
||||||
|
submob = self.submobjects[index]
|
||||||
|
if submob in new_submobjects:
|
||||||
|
submob = submob.copy()
|
||||||
|
new_submobjects.append(submob)
|
||||||
|
self.submobjects = new_submobjects
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def interpolate(self, mobject1, mobject2, alpha, path_func):
|
def interpolate(self, mobject1, mobject2, alpha, path_func):
|
||||||
|
|
|
@ -5,7 +5,7 @@ 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.1
|
SVG_SCALE_VALUE = 0.05
|
||||||
|
|
||||||
class SVGMobject(VMobject):
|
class SVGMobject(VMobject):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
|
@ -150,8 +150,7 @@ class VMobjectFromSVGPathstring(VMobject):
|
||||||
new_points = self.string_to_points(coord_string)
|
new_points = self.string_to_points(coord_string)
|
||||||
if command == "M": #moveto
|
if command == "M": #moveto
|
||||||
if len(points) > 0:
|
if len(points) > 0:
|
||||||
self.add_subpath(new_points)
|
self.growing_path = self.add_subpath(new_points)
|
||||||
self.growing_path = self.subpath_mobjects[-1]
|
|
||||||
else:
|
else:
|
||||||
self.growing_path.start_at(new_points[0])
|
self.growing_path.start_at(new_points[0])
|
||||||
return
|
return
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
from vectorized_mobject import VMobject
|
||||||
from svg_mobject import SVGMobject
|
from svg_mobject import SVGMobject
|
||||||
from helpers import *
|
from helpers import *
|
||||||
|
|
||||||
|
@ -5,16 +6,44 @@ class TexMobject(SVGMobject):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"template_tex_file" : TEMPLATE_TEX_FILE,
|
"template_tex_file" : TEMPLATE_TEX_FILE,
|
||||||
"color" : WHITE,
|
"color" : WHITE,
|
||||||
"stroke_width" : 1,
|
"stroke_width" : 0,
|
||||||
"should_center" : True,
|
"should_center" : True,
|
||||||
|
"next_to_direction" : RIGHT,
|
||||||
|
"next_to_buff" : 0.2,
|
||||||
}
|
}
|
||||||
def __init__(self, expression, **kwargs):
|
def __init__(self, expression, **kwargs):
|
||||||
digest_config(self, kwargs, locals())
|
digest_config(self, kwargs, locals())
|
||||||
image_file = tex_to_image_file(
|
VMobject.__init__(self, **kwargs)
|
||||||
self.expression,
|
if self.should_center:
|
||||||
|
self.center()
|
||||||
|
|
||||||
|
def generate_points(self):
|
||||||
|
if isinstance(self.expression, list):
|
||||||
|
self.handle_list_expression()
|
||||||
|
else:
|
||||||
|
self.svg_file = tex_to_svg_file(
|
||||||
|
"".join(self.expression),
|
||||||
self.template_tex_file
|
self.template_tex_file
|
||||||
)
|
)
|
||||||
SVGMobject.__init__(self, image_file, **kwargs)
|
SVGMobject.generate_points(self)
|
||||||
|
self.init_colors()
|
||||||
|
|
||||||
|
|
||||||
|
def handle_list_expression(self):
|
||||||
|
#TODO, next_to not sufficient?
|
||||||
|
subs = [
|
||||||
|
TexMobject(expr)
|
||||||
|
for expr in self.expression
|
||||||
|
]
|
||||||
|
for sm1, sm2 in zip(subs, subs[1:]):
|
||||||
|
sm2.next_to(
|
||||||
|
sm1,
|
||||||
|
self.next_to_direction,
|
||||||
|
self.next_to_buff
|
||||||
|
)
|
||||||
|
self.submobjects = subs
|
||||||
|
return self
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class TextMobject(TexMobject):
|
class TextMobject(TexMobject):
|
||||||
|
@ -42,13 +71,13 @@ class Brace(TexMobject):
|
||||||
def tex_hash(expression):
|
def tex_hash(expression):
|
||||||
return str(hash(expression))
|
return str(hash(expression))
|
||||||
|
|
||||||
def tex_to_image_file(expression, template_tex_file):
|
def tex_to_svg_file(expression, template_tex_file):
|
||||||
image_dir = os.path.join(TEX_IMAGE_DIR, tex_hash(expression))
|
image_dir = os.path.join(TEX_IMAGE_DIR, tex_hash(expression))
|
||||||
if os.path.exists(image_dir):
|
if os.path.exists(image_dir):
|
||||||
return get_sorted_image_list(image_dir)
|
return get_sorted_image_list(image_dir)
|
||||||
tex_file = generate_tex_file(expression, template_tex_file)
|
tex_file = generate_tex_file(expression, template_tex_file)
|
||||||
pdf_file = tex_to_pdf(tex_file)
|
dvi_file = tex_to_dvi(tex_file)
|
||||||
return pdf_to_svg(pdf_file)
|
return dvi_to_svg(dvi_file)
|
||||||
|
|
||||||
|
|
||||||
def generate_tex_file(expression, template_tex_file):
|
def generate_tex_file(expression, template_tex_file):
|
||||||
|
@ -64,11 +93,11 @@ def generate_tex_file(expression, template_tex_file):
|
||||||
outfile.write(body)
|
outfile.write(body)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def tex_to_pdf(tex_file):
|
def tex_to_dvi(tex_file):
|
||||||
result = tex_file.replace(".tex", ".pdf")
|
result = tex_file.replace(".tex", ".dvi")
|
||||||
if not os.path.exists(result) or True:
|
if not os.path.exists(result):
|
||||||
commands = [
|
commands = [
|
||||||
"pdflatex",
|
"latex",
|
||||||
"-interaction=batchmode",
|
"-interaction=batchmode",
|
||||||
"-output-directory=" + TEX_DIR,
|
"-output-directory=" + TEX_DIR,
|
||||||
tex_file,
|
tex_file,
|
||||||
|
@ -78,26 +107,30 @@ def tex_to_pdf(tex_file):
|
||||||
os.system(" ".join(commands))
|
os.system(" ".join(commands))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def pdf_to_svg(pdf_file, regen_if_exists = False):
|
def dvi_to_svg(dvi_file, regen_if_exists = False):
|
||||||
"""
|
"""
|
||||||
Converts a dvi, which potentially has multiple slides, into a
|
Converts a dvi, which potentially has multiple slides, into a
|
||||||
directory full of enumerated pngs corresponding with these slides.
|
directory full of enumerated pngs corresponding with these slides.
|
||||||
Returns a list of PIL Image objects for these images sorted as they
|
Returns a list of PIL Image objects for these images sorted as they
|
||||||
where in the dvi
|
where in the dvi
|
||||||
"""
|
"""
|
||||||
result = pdf_file.replace(".pdf", ".svg")
|
result = dvi_file.replace(".dvi", ".svg")
|
||||||
if not os.path.exists(result) or True:
|
if not os.path.exists(result):
|
||||||
commands = [
|
commands = [
|
||||||
"pdf2svg",
|
"dvisvgm",
|
||||||
pdf_file,
|
dvi_file,
|
||||||
|
"-n",
|
||||||
|
"-v",
|
||||||
|
"0",
|
||||||
|
"-o",
|
||||||
result,
|
result,
|
||||||
# "> /dev/null"
|
"> /dev/null"
|
||||||
]
|
]
|
||||||
os.system(" ".join(commands))
|
os.system(" ".join(commands))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
# directory, filename = os.path.split(pdf_file)
|
# directory, filename = os.path.split(dvi_file)
|
||||||
# name = filename.replace(".dvi", "")
|
# name = filename.replace(".dvi", "")
|
||||||
# images_dir = os.path.join(TEX_IMAGE_DIR, name)
|
# images_dir = os.path.join(TEX_IMAGE_DIR, name)
|
||||||
# if not os.path.exists(images_dir):
|
# if not os.path.exists(images_dir):
|
||||||
|
@ -107,7 +140,7 @@ def pdf_to_svg(pdf_file, regen_if_exists = False):
|
||||||
# "convert",
|
# "convert",
|
||||||
# "-density",
|
# "-density",
|
||||||
# str(PDF_DENSITY),
|
# str(PDF_DENSITY),
|
||||||
# pdf_file,
|
# dvi_file,
|
||||||
# "-size",
|
# "-size",
|
||||||
# str(DEFAULT_WIDTH) + "x" + str(DEFAULT_HEIGHT),
|
# str(DEFAULT_WIDTH) + "x" + str(DEFAULT_HEIGHT),
|
||||||
# os.path.join(images_dir, name + ".png")
|
# os.path.join(images_dir, name + ".png")
|
||||||
|
|
|
@ -15,7 +15,6 @@ class VMobject(Mobject):
|
||||||
"mark_paths_closed" : False,
|
"mark_paths_closed" : False,
|
||||||
}
|
}
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.subpath_mobjects = []
|
|
||||||
Mobject.__init__(self, *args, **kwargs)
|
Mobject.__init__(self, *args, **kwargs)
|
||||||
|
|
||||||
## Colors
|
## Colors
|
||||||
|
@ -53,7 +52,10 @@ class VMobject(Mobject):
|
||||||
return self.fill_opacity
|
return self.fill_opacity
|
||||||
|
|
||||||
def get_stroke_color(self):
|
def get_stroke_color(self):
|
||||||
|
try:
|
||||||
return Color(rgb = self.stroke_rgb)
|
return Color(rgb = self.stroke_rgb)
|
||||||
|
except:
|
||||||
|
return Color(rgb = 0.99*self.stroke_rgb)
|
||||||
|
|
||||||
#TODO, get color? Specify if stroke or fill
|
#TODO, get color? Specify if stroke or fill
|
||||||
#is the predominant color attribute?
|
#is the predominant color attribute?
|
||||||
|
@ -148,9 +150,14 @@ class VMobject(Mobject):
|
||||||
is_subpath = True
|
is_subpath = True
|
||||||
)
|
)
|
||||||
subpath_mobject.set_points(points)
|
subpath_mobject.set_points(points)
|
||||||
self.subpath_mobjects.append(subpath_mobject)
|
|
||||||
self.add(subpath_mobject)
|
self.add(subpath_mobject)
|
||||||
return self
|
return subpath_mobject
|
||||||
|
|
||||||
|
def get_subpath_mobjects(self):
|
||||||
|
return filter(
|
||||||
|
lambda m : m.is_subpath,
|
||||||
|
self.submobjects
|
||||||
|
)
|
||||||
|
|
||||||
## Information about line
|
## Information about line
|
||||||
|
|
||||||
|
@ -181,28 +188,40 @@ class VMobject(Mobject):
|
||||||
|
|
||||||
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))
|
||||||
num_anchors = self.get_num_anchor_points()
|
self.insert_n_anchor_points(
|
||||||
if num_anchors <= 1:
|
larger_mobject.get_num_anchor_points()-\
|
||||||
point = self.points[0] if len(self.points) else np.zeros(3)
|
self.get_num_anchor_points()
|
||||||
self.points = np.zeros(larger_mobject.points.shape)
|
)
|
||||||
self.points[:,:] = point
|
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):
|
||||||
|
curr = self.get_num_anchor_points()
|
||||||
|
if curr == 0:
|
||||||
|
self.points = np.zeros((1, 3))
|
||||||
|
n = n-1
|
||||||
|
if curr == 1:
|
||||||
|
self.points = np.repeat(self.points, n+1)
|
||||||
return self
|
return self
|
||||||
points = np.array([self.points[0]])
|
points = np.array([self.points[0]])
|
||||||
target_len = larger_mobject.get_num_anchor_points()-1
|
num_curves = curr-1
|
||||||
num_curves = self.get_num_anchor_points()-1
|
|
||||||
#Curves in self are buckets, and we need to know
|
#Curves in self are buckets, and we need to know
|
||||||
#how many new anchor points to put into each one.
|
#how many new anchor points to put into each one.
|
||||||
#Each element of index_allocation is like a bucket,
|
#Each element of index_allocation is like a bucket,
|
||||||
#and its value tells you the appropriate index of
|
#and its value tells you the appropriate index of
|
||||||
#the smaller curve.
|
#the smaller curve.
|
||||||
index_allocation = (np.arange(target_len) * num_curves)/target_len
|
index_allocation = (np.arange(curr+n-1) * num_curves)/(curr+n-1)
|
||||||
for index in range(num_curves):
|
for index in range(num_curves):
|
||||||
curr_bezier_points = self.points[3*index:3*index+4]
|
curr_bezier_points = self.points[3*index:3*index+4]
|
||||||
num_inter_curves = sum(index_allocation == index)
|
num_inter_curves = sum(index_allocation == index)
|
||||||
step = 1./num_inter_curves
|
alphas = np.arange(0, num_inter_curves+1)/float(num_inter_curves)
|
||||||
alphas = np.arange(0, 1+step, step)
|
|
||||||
for a, b in zip(alphas, alphas[1:]):
|
for a, b in zip(alphas, alphas[1:]):
|
||||||
new_points = partial_bezier_points(curr_bezier_points, a, b)
|
new_points = partial_bezier_points(
|
||||||
|
curr_bezier_points, a, b
|
||||||
|
)
|
||||||
points = np.append(
|
points = np.append(
|
||||||
points, new_points[1:], axis = 0
|
points, new_points[1:], axis = 0
|
||||||
)
|
)
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
\usepackage{tipa}
|
\usepackage{tipa}
|
||||||
\usepackage{relsize}
|
\usepackage{relsize}
|
||||||
|
|
||||||
|
|
||||||
\mode<presentation>
|
\mode<presentation>
|
||||||
{
|
{
|
||||||
\usetheme{default} % or try Darmstadt, Madrid, Warsaw, ...
|
\usetheme{default} % or try Darmstadt, Madrid, Warsaw, ...
|
||||||
|
@ -17,6 +18,7 @@
|
||||||
\setbeamertemplate{navigation symbols}{}
|
\setbeamertemplate{navigation symbols}{}
|
||||||
\setbeamertemplate{caption}[numbered]
|
\setbeamertemplate{caption}[numbered]
|
||||||
}
|
}
|
||||||
|
|
||||||
\begin{document}
|
\begin{document}
|
||||||
\centering
|
\centering
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue