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
|
||||
}
|
||||
def __init__(self, mobject, ending_mobject, **kwargs):
|
||||
mobject =mobject
|
||||
#Copy ending_mobject so as to not mess with caller
|
||||
ending_mobject = ending_mobject.copy()
|
||||
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)
|
||||
|
||||
Animation.__init__(self, mobject, **kwargs)
|
||||
|
|
25
camera.py
25
camera.py
|
@ -1,11 +1,9 @@
|
|||
import numpy as np
|
||||
import itertools as it
|
||||
import os
|
||||
import sys
|
||||
|
||||
from PIL import Image
|
||||
import cv2
|
||||
from colour import Color
|
||||
import progressbar
|
||||
import aggdraw
|
||||
|
||||
from helpers import *
|
||||
|
@ -71,15 +69,22 @@ class Camera(object):
|
|||
mob.nonempty_family_members()
|
||||
for mob in mobjects
|
||||
])
|
||||
vmobjects = []
|
||||
for mobject in mobjects:
|
||||
if isinstance(mobject, VMobject):
|
||||
self.display_vectorized(mobject)
|
||||
vmobjects.append(mobject)
|
||||
elif isinstance(mobject, PMobject):
|
||||
self.display_point_cloud(
|
||||
mobject.points, mobject.rgbs,
|
||||
self.adjusted_thickness(mobject.stroke_width)
|
||||
)
|
||||
#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):
|
||||
|
@ -95,19 +100,16 @@ class Camera(object):
|
|||
self.pixel_array[covered] = rgb
|
||||
|
||||
|
||||
def display_vectorized(self, vmobject):
|
||||
def display_vectorized(self, vmobject, canvas):
|
||||
if vmobject.is_subpath:
|
||||
#Subpath vectorized mobjects are taken care
|
||||
#of by their parent
|
||||
return
|
||||
im = Image.fromarray(self.pixel_array, mode = "RGB")
|
||||
canvas = aggdraw.Draw(im)
|
||||
pen, fill = self.get_pen_and_fill(vmobject)
|
||||
pathstring = self.get_pathstring(vmobject)
|
||||
symbol = aggdraw.Symbol(pathstring)
|
||||
canvas.symbol((0, 0), symbol, pen, fill)
|
||||
canvas.flush()
|
||||
self.pixel_array[:,:] = np.array(im)
|
||||
|
||||
|
||||
def get_pen_and_fill(self, vmobject):
|
||||
pen = aggdraw.Pen(
|
||||
|
@ -122,7 +124,7 @@ class Camera(object):
|
|||
|
||||
def get_pathstring(self, vmobject):
|
||||
result = ""
|
||||
for mob in [vmobject]+vmobject.subpath_mobjects:
|
||||
for mob in [vmobject]+vmobject.get_subpath_mobjects():
|
||||
points = mob.points
|
||||
if len(points) == 0:
|
||||
continue
|
||||
|
@ -166,12 +168,11 @@ class Camera(object):
|
|||
indices = np.dot(pixel_coords, flattener)[:,0]
|
||||
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[indices] = rgbs
|
||||
self.pixel_array = new_pa.reshape((ph, pw, 3))
|
||||
|
||||
|
||||
def align_points_to_camera(self, points):
|
||||
## This is where projection should live
|
||||
return points - self.space_center
|
||||
|
|
|
@ -452,16 +452,24 @@ class Mobject(object):
|
|||
def push_self_into_submobjects(self):
|
||||
copy = self.copy()
|
||||
copy.submobjects = []
|
||||
self.points = np.zeros((0, self.dim))
|
||||
self.init_points()
|
||||
self.add(copy)
|
||||
return self
|
||||
|
||||
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())
|
||||
n = n-1
|
||||
for i in range(n):
|
||||
self.add(self.submobjects[i].copy())
|
||||
n -= 1
|
||||
curr += 1
|
||||
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
|
||||
|
||||
def interpolate(self, mobject1, mobject2, alpha, path_func):
|
||||
|
|
|
@ -5,7 +5,7 @@ from vectorized_mobject import VMobject
|
|||
from topics.geometry import Rectangle, Circle
|
||||
from helpers import *
|
||||
|
||||
SVG_SCALE_VALUE = 0.1
|
||||
SVG_SCALE_VALUE = 0.05
|
||||
|
||||
class SVGMobject(VMobject):
|
||||
CONFIG = {
|
||||
|
@ -150,8 +150,7 @@ class VMobjectFromSVGPathstring(VMobject):
|
|||
new_points = self.string_to_points(coord_string)
|
||||
if command == "M": #moveto
|
||||
if len(points) > 0:
|
||||
self.add_subpath(new_points)
|
||||
self.growing_path = self.subpath_mobjects[-1]
|
||||
self.growing_path = self.add_subpath(new_points)
|
||||
else:
|
||||
self.growing_path.start_at(new_points[0])
|
||||
return
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
from vectorized_mobject import VMobject
|
||||
from svg_mobject import SVGMobject
|
||||
from helpers import *
|
||||
|
||||
|
@ -5,16 +6,44 @@ class TexMobject(SVGMobject):
|
|||
CONFIG = {
|
||||
"template_tex_file" : TEMPLATE_TEX_FILE,
|
||||
"color" : WHITE,
|
||||
"stroke_width" : 1,
|
||||
"stroke_width" : 0,
|
||||
"should_center" : True,
|
||||
"next_to_direction" : RIGHT,
|
||||
"next_to_buff" : 0.2,
|
||||
}
|
||||
def __init__(self, expression, **kwargs):
|
||||
digest_config(self, kwargs, locals())
|
||||
image_file = tex_to_image_file(
|
||||
self.expression,
|
||||
self.template_tex_file
|
||||
)
|
||||
SVGMobject.__init__(self, image_file, **kwargs)
|
||||
VMobject.__init__(self, **kwargs)
|
||||
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
|
||||
)
|
||||
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):
|
||||
|
@ -42,13 +71,13 @@ class Brace(TexMobject):
|
|||
def tex_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))
|
||||
if os.path.exists(image_dir):
|
||||
return get_sorted_image_list(image_dir)
|
||||
tex_file = generate_tex_file(expression, template_tex_file)
|
||||
pdf_file = tex_to_pdf(tex_file)
|
||||
return pdf_to_svg(pdf_file)
|
||||
dvi_file = tex_to_dvi(tex_file)
|
||||
return dvi_to_svg(dvi_file)
|
||||
|
||||
|
||||
def generate_tex_file(expression, template_tex_file):
|
||||
|
@ -64,11 +93,11 @@ def generate_tex_file(expression, template_tex_file):
|
|||
outfile.write(body)
|
||||
return result
|
||||
|
||||
def tex_to_pdf(tex_file):
|
||||
result = tex_file.replace(".tex", ".pdf")
|
||||
if not os.path.exists(result) or True:
|
||||
def tex_to_dvi(tex_file):
|
||||
result = tex_file.replace(".tex", ".dvi")
|
||||
if not os.path.exists(result):
|
||||
commands = [
|
||||
"pdflatex",
|
||||
"latex",
|
||||
"-interaction=batchmode",
|
||||
"-output-directory=" + TEX_DIR,
|
||||
tex_file,
|
||||
|
@ -78,26 +107,30 @@ def tex_to_pdf(tex_file):
|
|||
os.system(" ".join(commands))
|
||||
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
|
||||
directory full of enumerated pngs corresponding with these slides.
|
||||
Returns a list of PIL Image objects for these images sorted as they
|
||||
where in the dvi
|
||||
"""
|
||||
result = pdf_file.replace(".pdf", ".svg")
|
||||
if not os.path.exists(result) or True:
|
||||
result = dvi_file.replace(".dvi", ".svg")
|
||||
if not os.path.exists(result):
|
||||
commands = [
|
||||
"pdf2svg",
|
||||
pdf_file,
|
||||
"dvisvgm",
|
||||
dvi_file,
|
||||
"-n",
|
||||
"-v",
|
||||
"0",
|
||||
"-o",
|
||||
result,
|
||||
# "> /dev/null"
|
||||
"> /dev/null"
|
||||
]
|
||||
os.system(" ".join(commands))
|
||||
return result
|
||||
|
||||
|
||||
# directory, filename = os.path.split(pdf_file)
|
||||
# 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):
|
||||
|
@ -107,7 +140,7 @@ def pdf_to_svg(pdf_file, regen_if_exists = False):
|
|||
# "convert",
|
||||
# "-density",
|
||||
# str(PDF_DENSITY),
|
||||
# pdf_file,
|
||||
# dvi_file,
|
||||
# "-size",
|
||||
# str(DEFAULT_WIDTH) + "x" + str(DEFAULT_HEIGHT),
|
||||
# os.path.join(images_dir, name + ".png")
|
||||
|
|
|
@ -15,7 +15,6 @@ class VMobject(Mobject):
|
|||
"mark_paths_closed" : False,
|
||||
}
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.subpath_mobjects = []
|
||||
Mobject.__init__(self, *args, **kwargs)
|
||||
|
||||
## Colors
|
||||
|
@ -53,7 +52,10 @@ class VMobject(Mobject):
|
|||
return self.fill_opacity
|
||||
|
||||
def get_stroke_color(self):
|
||||
return Color(rgb = self.stroke_rgb)
|
||||
try:
|
||||
return Color(rgb = self.stroke_rgb)
|
||||
except:
|
||||
return Color(rgb = 0.99*self.stroke_rgb)
|
||||
|
||||
#TODO, get color? Specify if stroke or fill
|
||||
#is the predominant color attribute?
|
||||
|
@ -148,9 +150,14 @@ class VMobject(Mobject):
|
|||
is_subpath = True
|
||||
)
|
||||
subpath_mobject.set_points(points)
|
||||
self.subpath_mobjects.append(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
|
||||
|
||||
|
@ -181,28 +188,40 @@ class VMobject(Mobject):
|
|||
|
||||
def align_points_with_larger(self, larger_mobject):
|
||||
assert(isinstance(larger_mobject, VMobject))
|
||||
num_anchors = self.get_num_anchor_points()
|
||||
if num_anchors <= 1:
|
||||
point = self.points[0] if len(self.points) else np.zeros(3)
|
||||
self.points = np.zeros(larger_mobject.points.shape)
|
||||
self.points[:,:] = point
|
||||
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):
|
||||
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
|
||||
points = np.array([self.points[0]])
|
||||
target_len = larger_mobject.get_num_anchor_points()-1
|
||||
num_curves = self.get_num_anchor_points()-1
|
||||
num_curves = curr-1
|
||||
#Curves in self are buckets, and we need to know
|
||||
#how many new anchor points to put into each one.
|
||||
#Each element of index_allocation is like a bucket,
|
||||
#and its value tells you the appropriate index of
|
||||
#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):
|
||||
curr_bezier_points = self.points[3*index:3*index+4]
|
||||
num_inter_curves = sum(index_allocation == index)
|
||||
step = 1./num_inter_curves
|
||||
alphas = np.arange(0, 1+step, step)
|
||||
alphas = np.arange(0, num_inter_curves+1)/float(num_inter_curves)
|
||||
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, new_points[1:], axis = 0
|
||||
)
|
||||
|
@ -229,7 +248,7 @@ class VMobject(Mobject):
|
|||
))
|
||||
|
||||
def become_partial(self, mobject, a, b):
|
||||
assert(isinstance(mobject, VMobject))
|
||||
assert(isinstance(mobject, VMobject))
|
||||
#Partial curve includes three portions:
|
||||
#-A middle section, which matches the curve exactly
|
||||
#-A start, which is some ending portion of an inner cubic
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
\usepackage{tipa}
|
||||
\usepackage{relsize}
|
||||
|
||||
|
||||
\mode<presentation>
|
||||
{
|
||||
\usetheme{default} % or try Darmstadt, Madrid, Warsaw, ...
|
||||
|
@ -17,6 +18,7 @@
|
|||
\setbeamertemplate{navigation symbols}{}
|
||||
\setbeamertemplate{caption}[numbered]
|
||||
}
|
||||
|
||||
\begin{document}
|
||||
\centering
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue