Skip to content

Commit

Permalink
Refactored tex_utils, and made TexMobject proper objects
Browse files Browse the repository at this point in the history
  • Loading branch information
3b1b committed Oct 29, 2015
1 parent 468d05d commit e97a870
Show file tree
Hide file tree
Showing 24 changed files with 598 additions and 587 deletions.
2 changes: 1 addition & 1 deletion constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
RIGHT_SIDE = SPACE_WIDTH*RIGHT

THIS_DIR = os.path.dirname(os.path.realpath(__file__))
FILE_DIR = os.path.join(THIS_DIR, "files")
FILE_DIR = os.path.join(THIS_DIR, os.pardir, "animation_files")
IMAGE_DIR = os.path.join(FILE_DIR, "images")
GIF_DIR = os.path.join(FILE_DIR, "gifs")
MOVIE_DIR = os.path.join(FILE_DIR, "movies")
Expand Down
3 changes: 3 additions & 0 deletions mobject/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from mobject import *
from image_mobject import *
from tex_mobject import *
1 change: 0 additions & 1 deletion image_mobject.py → mobject/image_mobject.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from random import random

from helpers import *
from tex_utils import tex_to_image
from mobject import Mobject

class ImageMobject(Mobject):
Expand Down
File renamed without changes.
161 changes: 161 additions & 0 deletions mobject/tex_mobject.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
from mobject import Mobject
from image_mobject import ImageMobject
from helpers import *

#TODO, Cleanup and refactor this file.

class TexMobject(Mobject):
DEFAULT_CONFIG = {
"template_tex_file" : TEMPLATE_TEX_FILE,
"color" : WHITE,
"point_thickness" : 1,
"should_center" : False,
}
def __init__(self, expression, **kwargs):
if "size" not in kwargs:
#Todo, make this more sophisticated.
if len("".join(expression)) < MAX_LEN_FOR_HUGE_TEX_FONT:
size = "\\Huge"
else:
size = "\\large"
digest_locals(self)
Mobject.__init__(self, **kwargs)

def generate_points(self):
image_files = tex_to_image_files(
self.expression,
self.size,
self.template_tex_file
)
for image_file in image_files:
self.add(ImageMobject(image_file))
if self.should_center:
self.center()
self.highlight(self.color)



class TextMobject(TexMobject):
DEFAULT_CONFIG = {
"template_tex_file" : TEMPLATE_TEXT_FILE,
"size" : "\\Large", #TODO, auto-adjust?
}


class Underbrace(TexMobject):
DEFAULT_CONFIG = {
"buff" : 0.2,
}
def __init__(self, left, right, **kwargs):
expression = "\\underbrace{%s}"%(14*"\\quad")
TexMobject.__init__(self, expression, **kwargs)
result.stretch_to_fit_width(right[0]-left[0])
result.shift(left - result.points[0] + buff*DOWN)



def tex_hash(expression, size):
return str(hash(expression + size))

def tex_to_image_files(expression, size, template_tex_file):
"""
Returns list of images for correpsonding with a list of expressions
"""
image_dir = os.path.join(TEX_IMAGE_DIR, tex_hash(expression, size))
if os.path.exists(image_dir):
return get_sorted_image_list(image_dir)
tex_file = generate_tex_file(expression, size, template_tex_file)
dvi_file = tex_to_dvi(tex_file)
return dvi_to_png(dvi_file)


def generate_tex_file(expression, size, template_tex_file):
if isinstance(expression, list):
expression = tex_expression_list_as_string(expression)
result = os.path.join(TEX_DIR, tex_hash(expression, size))+".tex"
if not os.path.exists(result):
print "Writing %s at size %s to %s"%(
"".join(expression), size, result
)
with open(template_tex_file, "r") as infile:
body = infile.read()
body = body.replace(SIZE_TO_REPLACE, size)
body = body.replace(TEX_TEXT_TO_REPLACE, expression)
with open(result, "w") as outfile:
outfile.write(body)
return result

def tex_to_dvi(tex_file):
result = tex_file.replace(".tex", ".dvi")
if not os.path.exists(filestem + ".dvi"):
commands = [
"latex",
"-interaction=batchmode",
"-output-directory=" + TEX_DIR,
tex_file,
"> /dev/null"
]
#TODO, Error check
os.system(" ".join(commands))
return result

def tex_expression_list_as_string(expression):
return "\n".join([
"\onslide<%d>{"%count + exp + "}"
for count, exp in zip(it.count(1), expression)
])

def dvi_to_png(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
"""
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),
path,
"-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):
num1, num2 = [
int(name.split(".")[0].split("-")[-1])
for name in (name1, name2)
]
return num1 - num2















8 changes: 4 additions & 4 deletions old_projects/complex_multiplication_article.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ def construct(self, *reps_and_nums):
def draw_number(self, tex_representation, number):
point = self.plane.number_to_point(number)
dot = Dot(point)
label = tex_mobject(tex_representation)
label = TexMobject(tex_representation)
max_width = 0.8*self.plane.unit_to_spatial_width
if label.get_width() > max_width:
label.scale_to_fit_width(max_width)
Expand Down Expand Up @@ -235,17 +235,17 @@ def add_lines(self, tex_representation, number):
# tex_parts = tex_representation.split("+")
# elif "-" in tex_representation:
# tex_parts = tex_representation.split("-")
# x_label, y_label = map(tex_mobject, tex_parts)
# x_label, y_label = map(TexMobject, tex_parts)
# for label in x_label, y_label:
# label.scale_to_fit_height(0.5)
# x_label.next_to(x_line, point[1]*DOWN/abs(point[1]))
# y_label.next_to(y_line, point[0]*RIGHT/abs(point[0]))
norm = np.linalg.norm(point)
brace = underbrace(ORIGIN, ORIGIN+norm*RIGHT)
brace = Underbrace(ORIGIN, ORIGIN+norm*RIGHT)
if point[1] > 0:
brace.rotate(np.pi, RIGHT)
brace.rotate(np.log(number).imag)
norm_label = tex_mobject("%.1f"%abs(number))
norm_label = TexMobject("%.1f"%abs(number))
norm_label.scale(0.5)
axis = OUT if point[1] > 0 else IN
norm_label.next_to(brace, rotate_vector(point, np.pi/2, axis))
Expand Down
56 changes: 28 additions & 28 deletions old_projects/counting_in_binary.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@
}

def finger_tip_power_of_2(finger_no):
return tex_mobject(str(2**finger_no)).shift(COUNT_TO_TIP_POS[finger_no])
return TexMobject(str(2**finger_no)).shift(COUNT_TO_TIP_POS[finger_no])

class Hand(ImageMobject):
STARTING_BOTTOM_RIGHT = [4.61111111e+00, -3.98888889e+00, 9.80454690e-16]
Expand Down Expand Up @@ -114,7 +114,7 @@ def shrink(self):
# )

def get_algorithm():
return text_mobject(ALGORITHM_TEXT)
return TextMobject(ALGORITHM_TEXT)

def get_finger_colors():
return list(Color("yellow").range_to("red", 5))
Expand Down Expand Up @@ -186,7 +186,7 @@ def construct(self):
]

def get_counting_mob(self, count):
mob = tex_mobject(str(count))
mob = TexMobject(str(count))
mob.scale(2)
mob.shift(LEFT)
mob.to_edge(UP, buff = 0.1)
Expand All @@ -199,7 +199,7 @@ def construct(self):
for frame, count in zip(self.frames, it.count()):
print count, "of", len(self.frames)
mob = CompoundMobject(*[
tex_mobject(char).shift(0.3*x*RIGHT)
TexMobject(char).shift(0.3*x*RIGHT)
for char, x, in zip(str(count), it.count())
])
self.frames[count] = disp.paint_mobject(
Expand All @@ -213,7 +213,7 @@ def construct(self):
lh_map = get_hand_map("left")
def get_num(count):
return CompoundMobject(*[
tex_mobject(char).shift(0.35*x*RIGHT)
TexMobject(char).shift(0.35*x*RIGHT)
for char, x, in zip(str(count), it.count())
]).center().to_edge(UP)
self.frames = [
Expand All @@ -225,7 +225,7 @@ def get_num(count):

class Introduction(Scene):
def construct(self):
words = text_mobject("""
words = TextMobject("""
First, let's see how to count
to 31 on just one hand...
""")
Expand All @@ -242,10 +242,10 @@ def construct(self):
class ShowReadingRule(Scene):
def construct(self):
sample_counts = [6, 17, 27, 31]
question = text_mobject("""
question = TextMobject("""
How do you recognize what number a given configuration represents?
""", size = "\\Huge").scale(0.75).to_corner(UP+LEFT)
answer = text_mobject([
answer = TextMobject([
"Think of each finger as representing a power of 2, ",
"then add up the numbers represented by the standing fingers."
], size = "\\Huge").scale(0.75).to_corner(UP+LEFT).split()
Expand Down Expand Up @@ -278,7 +278,7 @@ def read_sample(self, num):
count_mobs[1].shift(0.2*DOWN + 0.2*LEFT)
if num in [6, 17]:
hand.shift(0.8*LEFT)
sum_mobs = tex_mobject(
sum_mobs = TexMobject(
" + ".join([str(2**c) for c in counts]).split(" ") + ["=%d"%num]
).to_corner(UP+RIGHT).split()
self.add(hand, *count_mobs)
Expand All @@ -298,7 +298,7 @@ def construct(self):
def to_left(words):
return "\\begin{flushleft}" + words + "\\end{flushleft}"
phrases = [
text_mobject(to_left(words), size = "\\Huge").scale(0.75).to_corner(UP+LEFT)
TextMobject(to_left(words), size = "\\Huge").scale(0.75).to_corner(UP+LEFT)
for words in [
"But while you're counting, you don't need to think about powers of 2.",
"Can you see the pattern for incrementing?",
Expand Down Expand Up @@ -337,7 +337,7 @@ def to_left(words):
self.frames += [self.frames[-1]]*int(1.0/self.frame_duration)

def get_arrow_set(self, num):
arrow = tex_mobject("\\downarrow", size = "\\Huge")
arrow = TexMobject("\\downarrow", size = "\\Huge")
arrow.highlight("green")
arrow.shift(-arrow.get_bottom())
if num == 12:
Expand Down Expand Up @@ -368,17 +368,17 @@ def get_arrow_set(self, num):

class MindFindsShortcuts(Scene):
def construct(self):
words1 = text_mobject("""
words1 = TextMobject("""
Before long, your mind starts to recognize certain
patterns without needing to perform the addition.
""", size = "\\Huge").scale(0.75).to_corner(LEFT+UP)
words2 = text_mobject("""
words2 = TextMobject("""
Which makes it faster to recognize other patterns...
""", size = "\\Huge").scale(0.75).to_corner(LEFT+UP)

hand = Hand(7).scale(0.5).center().shift(DOWN+2*LEFT)
sum421 = tex_mobject("4+2+1").shift(DOWN+2*RIGHT)
seven = tex_mobject("7").shift(DOWN+6*RIGHT)
sum421 = TexMobject("4+2+1").shift(DOWN+2*RIGHT)
seven = TexMobject("7").shift(DOWN+6*RIGHT)
compound = CompoundMobject(
Arrow(hand, sum421),
sum421,
Expand All @@ -393,7 +393,7 @@ def construct(self):
self.dither()
self.play(
Transform(compound, Arrow(hand, seven).highlight("yellow")),
ShimmerIn(text_mobject("Directly recognize").shift(1.5*DOWN+2*RIGHT))
ShimmerIn(TextMobject("Directly recognize").shift(1.5*DOWN+2*RIGHT))
)
self.dither()

Expand All @@ -406,10 +406,10 @@ def construct(self):
hands[16].shift(LEFT)
hands[7].shift(3*RIGHT)
for num in 7, 16:
hands[num].add(tex_mobject(str(num)).shift(hands[num].get_top()+0.5*UP))
plus = tex_mobject("+").shift(DOWN + RIGHT)
equals = tex_mobject("=").shift(DOWN + 2.5*LEFT)
equals23 = tex_mobject("=23").shift(DOWN + 5.5*RIGHT)
hands[num].add(TexMobject(str(num)).shift(hands[num].get_top()+0.5*UP))
plus = TexMobject("+").shift(DOWN + RIGHT)
equals = TexMobject("=").shift(DOWN + 2.5*LEFT)
equals23 = TexMobject("=23").shift(DOWN + 5.5*RIGHT)

self.add(words2, hands[23])
self.dither()
Expand All @@ -434,7 +434,7 @@ def construct(self):
class CountingExampleSentence(ShowCounting):
def construct(self):
words = "As an example, this is me counting the number of words in this sentence on just one hand!"
self.words = text_mobject(words.split(), size = "\\Huge").scale(0.7).to_corner(UP+LEFT, buff = 0.25).split()
self.words = TextMobject(words.split(), size = "\\Huge").scale(0.7).to_corner(UP+LEFT, buff = 0.25).split()
ShowCounting.construct(self)

def get_counting_mob(self, num):
Expand All @@ -443,11 +443,11 @@ def get_counting_mob(self, num):
class FinishCountingExampleSentence(Scene):
def construct(self):
words = "As an example, this is me counting the number of words in this sentence on just one hand!"
words = text_mobject(words, size = "\\Huge").scale(0.7).to_corner(UP+LEFT, buff = 0.25)
words = TextMobject(words, size = "\\Huge").scale(0.7).to_corner(UP+LEFT, buff = 0.25)
hand = Hand(18)
sixteen = tex_mobject("16").shift([0, 2.25, 0])
two = tex_mobject("2").shift([3, 3.65, 0])
eightteen = tex_mobject("18").shift([1.5, 2.5, 0])
sixteen = TexMobject("16").shift([0, 2.25, 0])
two = TexMobject("2").shift([3, 3.65, 0])
eightteen = TexMobject("18").shift([1.5, 2.5, 0])
eightteen.sort_points()
comp = CompoundMobject(sixteen, two)
self.add(hand, comp, words)
Expand All @@ -457,18 +457,18 @@ def construct(self):

class Question(Scene):
def construct(self):
self.add(text_mobject("Left to ponder: Why does this rule for incrementing work?"))
self.add(TextMobject("Left to ponder: Why does this rule for incrementing work?"))


class TwoHandStatement(Scene):
def construct(self):
self.add(text_mobject(
self.add(TextMobject(
"With 10 fingers, you can count up to $2^{10} - 1 = 1023$..."
))

class WithToes(Scene):
def construct(self):
words = text_mobject([
words = TextMobject([
"If you were dexterous enough to use your toes as well,",
"you could count to 1,048,575"
]).split()
Expand Down
Loading

0 comments on commit e97a870

Please sign in to comment.