From f5e4c3b3348607fc62ba8edc564bdd877f3c9df4 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sun, 17 Apr 2016 12:59:53 -0700 Subject: [PATCH] TexMobjects are looking pretty good --- animation/transform.py | 5 --- camera.py | 25 ++++++------ mobject/mobject.py | 18 ++++++--- mobject/svg_mobject.py | 5 +-- mobject/tex_mobject.py | 75 +++++++++++++++++++++++++---------- mobject/vectorized_mobject.py | 51 ++++++++++++++++-------- template.tex | 2 + 7 files changed, 119 insertions(+), 62 deletions(-) diff --git a/animation/transform.py b/animation/transform.py index 62adebc1..0af4edb7 100644 --- a/animation/transform.py +++ b/animation/transform.py @@ -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) diff --git a/camera.py b/camera.py index 2a32bb60..d50dcbe3 100644 --- a/camera.py +++ b/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 diff --git a/mobject/mobject.py b/mobject/mobject.py index d7e28938..21907be2 100644 --- a/mobject/mobject.py +++ b/mobject/mobject.py @@ -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): diff --git a/mobject/svg_mobject.py b/mobject/svg_mobject.py index c23dc308..4c8dc95c 100644 --- a/mobject/svg_mobject.py +++ b/mobject/svg_mobject.py @@ -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 diff --git a/mobject/tex_mobject.py b/mobject/tex_mobject.py index 85445d78..74eb889c 100644 --- a/mobject/tex_mobject.py +++ b/mobject/tex_mobject.py @@ -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") diff --git a/mobject/vectorized_mobject.py b/mobject/vectorized_mobject.py index 4a9f5b03..e1d1d11f 100644 --- a/mobject/vectorized_mobject.py +++ b/mobject/vectorized_mobject.py @@ -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 diff --git a/template.tex b/template.tex index 71245395..10e8ca8b 100644 --- a/template.tex +++ b/template.tex @@ -9,6 +9,7 @@ \usepackage{tipa} \usepackage{relsize} + \mode { \usetheme{default} % or try Darmstadt, Madrid, Warsaw, ... @@ -17,6 +18,7 @@ \setbeamertemplate{navigation symbols}{} \setbeamertemplate{caption}[numbered] } + \begin{document} \centering