rebase onto upstream

This commit is contained in:
Devin Neal 2019-05-28 00:33:05 -07:00
commit 0edb4edfd0
36 changed files with 1348 additions and 32 deletions

View file

@ -4,6 +4,10 @@ dist: xenial # required for Python 3.7 (travis-ci/travis-ci#9069)
python: "3.7"
cache: pip
addons:
apt:
packages:
- python3-sphinx
install:
- pip install --upgrade pip
- pip install -r requirements.txt
@ -16,6 +20,8 @@ before_script:
script:
- python setup.py test
- python setup.py bdist_wheel
after_success:
- test $TRAVIS_BRANCH = "master" && test $TRAVIS_PULL_REQUEST = "false" && travis/build_docs.sh
deploy:
provider: pypi
user: eulertour

View file

@ -1,4 +1,5 @@
# Manim - Mathematical Animation Engine
<img src="logo/cropped.png"/>
[![Documentation Status](https://readthedocs.org/projects/manim/badge/?version=latest)](https://manim.readthedocs.io/en/latest/?badge=latest)
[![Build Status](https://travis-ci.org/3b1b/manim.svg?branch=master)](https://travis-ci.org/3b1b/manim)
[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](http://choosealicense.com/licenses/mit/)
@ -87,15 +88,17 @@ Try running the following:
```sh
python3 -m manim example_scenes.py SquareToCircle -pl
```
The -p is for previewing, meaning the video file will automatically open when it is done rendering.
Use -l for a faster rendering at a lower quality.
Use -s to skip to the end and just show the final frame.
Use -n (number) to skip ahead to the n'th animation of a scene.
Use -f to show the file in finder (for osx)
The `-p` flag in the command above is for previewing, meaning the video file will automatically open when it is done rendering. The `-l` flag is for a faster rendering at a lower quality.
Set MEDIA_DIR environment variable to determine where image and animation files will be written.
Some other useful flags include:
Look through the old_projects folder to see the code for previous 3b1b videos. Note, however, that developments are often made to the library without considering backwards compatibility on those old_projects. To run them with a guarantee that they will work, you will have to go back to the commit which complete that project.
* `-s` to skip to the end and just show the final frame.
* `-n <number>` to skip ahead to the `n`'th animation of a scene.
* `-f` to show the file in finder (for OSX).
Set `MEDIA_DIR` environment variable to specify where the image and animation files will be written.
Look through the `old_projects` folder to see the code for previous 3b1b videos. Note, however, that developments are often made to the library without considering backwards compatibility with those old projects. To run an old project with a guarantee that it will work, you will have to go back to the commit which completed that project.
While developing a scene, the `-sp` flags are helpful to just see what things look like at the end without having to generate the full animation. It can also be helpful to use the `-n` flag to skip over some number of animations.

View file

@ -0,0 +1,13 @@
from active_projects.ode.part3.staging import *
from active_projects.ode.part3.temperature_graphs import *
OUTPUT_DIRECTORY = "ode/part3"
SCENES_IN_ORDER = [
FourierSeriesIllustraiton,
FourierNameIntro,
CircleAnimationOfF,
LastChapterWrapper,
ThreeMainObservations,
SimpleSinExpGraph,
]

View file

@ -0,0 +1,40 @@
#!/usr/bin/env python
from manimlib.imports import *
from active_projects.ode.part2.fourier_series import FourierOfName
name_color_pairs = [
]
circle_counts = [
# 10,
# 25,
100,
]
if __name__ == "__main__":
for name, color in name_color_pairs:
for n_circles in circle_counts:
try:
first_name = name.split(" ")[0]
scene = FourierOfName(
name_text=name,
name_color=color,
n_circles=n_circles,
file_writer_config={
"write_to_movie": True,
"output_directory": os.path.join(
"patron_fourier_names",
first_name,
),
"file_name": "{}_Fouierified_{}_Separate_paths".format(
first_name,
n_circles
),
},
camera_config={
"frame_rate": 24,
},
)
except:
pass

View file

@ -313,12 +313,7 @@ class FourierOfPiSymbol(FourierCirclesScene):
coefs = self.get_coefficients_of_path(path)
circles = self.get_circles(coefficients=coefs)
for k, circle in zip(it.count(1), circles):
circle.set_stroke(width=max(
1 / np.sqrt(k),
1,
))
self.set_decreasing_stroke_widths(circles)
# approx_path = self.get_circle_end_path(circles)
drawn_path = self.get_drawn_path(circles)
if self.start_drawn:
@ -329,6 +324,14 @@ class FourierOfPiSymbol(FourierCirclesScene):
self.add(drawn_path)
self.wait(self.run_time)
def set_decreasing_stroke_widths(self, circles):
for k, circle in zip(it.count(1), circles):
circle.set_stroke(width=max(
1 / np.sqrt(k),
1,
))
return circles
def get_path(self):
tex_mob = TexMobject(self.tex)
tex_mob.set_height(6)
@ -338,6 +341,51 @@ class FourierOfPiSymbol(FourierCirclesScene):
return path
class FourierOfName(FourierOfPiSymbol):
CONFIG = {
"n_circles": 100,
"name_color": WHITE,
"name_text": "Abc",
"time_per_symbol": 5,
"slow_factor": 1 / 5,
}
def construct(self):
name = TextMobject(self.name_text)
max_width = FRAME_WIDTH - 2
max_height = FRAME_HEIGHT - 2
name.set_width(max_width)
if name.get_height() > max_height:
name.set_height(max_height)
circles = VGroup(VectorizedPoint())
for path in name.family_members_with_points():
for subpath in path.get_subpaths():
sp_mob = VMobject()
sp_mob.set_points(subpath)
coefs = self.get_coefficients_of_path(sp_mob)
new_circles = self.get_circles(
coefficients=coefs
)
self.set_decreasing_stroke_widths(new_circles)
drawn_path = self.get_drawn_path(new_circles)
drawn_path.clear_updaters()
drawn_path.set_stroke(self.name_color, 3)
new_circles.suspend_updating()
self.play(ReplacementTransform(circles, new_circles))
new_circles.resume_updating()
circles = new_circles
self.play(
ShowCreation(drawn_path),
rate_func=linear,
run_time=self.time_per_symbol
)
circles.suspend_updating()
self.play(FadeOut(circles))
self.wait(3)
class FourierOfPiSymbol5(FourierOfPiSymbol):
CONFIG = {
"n_circles": 5,
@ -479,7 +527,7 @@ class FourierNDQ(FourierOfTrebleClef):
def get_shape(self):
path = VMobject()
shape = TexMobject("Hayley")
shape = TexMobject("NDQ")
for sp in shape.family_members_with_points():
path.append_points(sp.points)
return path

View file

@ -0,0 +1,427 @@
from manimlib.imports import *
from active_projects.ode.part2.fourier_series import FourierOfTrebleClef
class FourierNameIntro(Scene):
def construct(self):
self.show_two_titles()
self.transition_to_image()
self.show_paper()
def show_two_titles(self):
lt = TextMobject("Fourier", "Series")
rt = TextMobject("Fourier", "Transform")
lt_variants = VGroup(
TextMobject("Complex", "Fourier Series"),
TextMobject("Discrete", "Fourier Series"),
)
rt_variants = VGroup(
TextMobject("Discrete", "Fourier Transform"),
TextMobject("Fast", "Fourier Transform"),
TextMobject("Quantum", "Fourier Transform"),
)
titles = VGroup(lt, rt)
titles.scale(1.5)
for title, vect in (lt, LEFT), (rt, RIGHT):
title.move_to(vect * FRAME_WIDTH / 4)
title.to_edge(UP)
for title, variants in (lt, lt_variants), (rt, rt_variants):
title.save_state()
title.target = title.copy()
title.target.scale(1 / 1.5, about_edge=RIGHT)
for variant in variants:
variant.move_to(title.target, UR)
variant[0].set_color(YELLOW)
v_line = Line(UP, DOWN)
v_line.set_height(FRAME_HEIGHT)
v_line.set_stroke(WHITE, 2)
self.play(
FadeInFrom(lt, RIGHT),
ShowCreation(v_line)
)
self.play(
FadeInFrom(rt, LEFT),
)
# Edit in images of circle animations
# and clips from FT video
# for title, variants in (rt, rt_variants), (lt, lt_variants):
for title, variants in [(rt, rt_variants)]:
# Maybe do it for left variant, maybe not...
self.play(
MoveToTarget(title),
FadeInFrom(variants[0][0], LEFT)
)
for v1, v2 in zip(variants, variants[1:]):
self.play(
FadeOutAndShift(v1[0], UP),
FadeInFrom(v2[0], DOWN),
run_time=0.5,
)
self.wait(0.5)
self.play(
Restore(title),
FadeOut(variants[-1][0])
)
self.wait()
self.titles = titles
self.v_line = v_line
def transition_to_image(self):
titles = self.titles
v_line = self.v_line
image = ImageMobject("Joseph Fourier")
image.set_height(5)
image.to_edge(LEFT)
frame = Rectangle()
frame.replace(image, stretch=True)
name = TextMobject("Joseph", "Fourier")
fourier_part = name.get_part_by_tex("Fourier")
fourier_part.set_color(YELLOW)
F_sym = fourier_part[0]
name.match_width(image)
name.next_to(image, DOWN)
self.play(
ReplacementTransform(v_line, frame),
FadeIn(image),
FadeIn(name[0]),
*[
ReplacementTransform(
title[0].deepcopy(),
name[1]
)
for title in titles
],
titles.scale, 0.65,
titles.arrange, DOWN,
titles.next_to, image, UP,
)
self.wait()
big_F = F_sym.copy()
big_F.set_fill(opacity=0)
big_F.set_stroke(WHITE, 2)
big_F.set_height(3)
big_F.move_to(midpoint(
image.get_right(),
RIGHT_SIDE,
))
big_F.shift(DOWN)
equivalence = VGroup(
fourier_part.copy().scale(1.25),
TexMobject("\\Leftrightarrow").scale(1.5),
TextMobject("Break down into\\\\pure frequencies"),
)
equivalence.arrange(RIGHT)
equivalence.move_to(big_F)
equivalence.to_edge(UP)
self.play(
FadeIn(big_F),
TransformFromCopy(fourier_part, equivalence[0]),
Write(equivalence[1:]),
)
self.wait(3)
self.play(FadeOut(VGroup(big_F, equivalence)))
self.image = image
self.name = name
def show_paper(self):
image = self.image
paper = ImageMobject("Fourier paper")
paper.match_height(image)
paper.next_to(image, RIGHT, MED_LARGE_BUFF)
date = TexMobject("1822")
date.next_to(paper, DOWN)
date_rect = SurroundingRectangle(date)
date_rect.scale(0.3)
date_rect.set_color(RED)
date_rect.shift(1.37 * UP + 0.08 * LEFT)
date_arrow = Arrow(
date_rect.get_bottom(),
date.get_top(),
buff=SMALL_BUFF,
color=date_rect.get_color(),
)
heat_rect = SurroundingRectangle(
TextMobject("CHALEUR")
)
heat_rect.set_color(RED)
heat_rect.scale(0.6)
heat_rect.move_to(
paper.get_top() +
1.22 * DOWN + 0.37 * RIGHT
)
heat_word = TextMobject("Heat")
heat_word.scale(1.5)
heat_word.next_to(paper, UP)
heat_word.shift(paper.get_width() * RIGHT)
heat_arrow = Arrow(
heat_rect.get_top(),
heat_word.get_left(),
buff=0.1,
path_arc=-60 * DEGREES,
color=heat_rect.get_color(),
)
self.play(FadeInFrom(paper, LEFT))
self.play(
ShowCreation(date_rect),
)
self.play(
GrowFromPoint(date, date_arrow.get_start()),
ShowCreation(date_arrow),
)
self.wait(3)
# Insert animation of circles/sine waves
# approximating a square wave
self.play(
ShowCreation(heat_rect),
)
self.play(
GrowFromPoint(heat_word, heat_arrow.get_start()),
ShowCreation(heat_arrow),
)
self.wait(3)
class FourierSeriesIllustraiton(Scene):
CONFIG = {
"n_range": range(1, 31, 2),
}
def construct(self):
n_range = self.n_range
axes1 = Axes(
number_line_config={
"include_tip": False,
},
x_axis_config={
"tick_frequency": 1 / 4,
"unit_size": 4,
},
x_min=0,
x_max=1,
y_min=-1,
y_max=1,
)
axes2 = axes1.copy()
step_func = axes2.get_graph(
lambda x: (1 if x < 0.5 else -1),
discontinuities=[0.5],
color=YELLOW,
stroke_width=3,
)
dot = Dot(axes2.c2p(0.5, 0), color=step_func.get_color())
dot.scale(0.5)
step_func.add(dot)
axes2.add(step_func)
arrow = Arrow(LEFT, RIGHT, color=WHITE)
VGroup(axes1, arrow, axes2).arrange(RIGHT).shift(UP)
def generate_nth_func(n):
return lambda x: (4 / n / PI) * np.sin(TAU * n * x)
def generate_kth_partial_sum_func(k):
return lambda x: np.sum([
generate_nth_func(n)(x)
for n in n_range[:k]
])
sine_graphs = VGroup(*[
axes1.get_graph(generate_nth_func(n))
for n in n_range
])
sine_graphs.set_stroke(width=3)
sine_graphs.set_color_by_gradient(
BLUE, GREEN, RED, YELLOW, PINK,
BLUE, GREEN, RED, YELLOW, PINK,
)
partial_sums = VGroup(*[
axes1.get_graph(generate_kth_partial_sum_func(k + 1))
for k in range(len(n_range))
])
partial_sums.match_style(sine_graphs)
sum_tex = TexMobject(
"\\frac{4}{\\pi}"
"\\sum_{1, 3, 5, \\dots}"
"\\frac{1}{n} \\sin(2\\pi \\cdot n \\cdot x)"
)
sum_tex.next_to(partial_sums, DOWN, buff=0.7)
eq = TexMobject("=")
step_tex = TexMobject(
"""
1 \\quad \\text{if $x < 0.5$} \\\\
0 \\quad \\text{if $x = 0.5$} \\\\
-1 \\quad \\text{if $x > 0.5$} \\\\
"""
)
lb = Brace(step_tex, LEFT, buff=SMALL_BUFF)
step_tex.add(lb)
step_tex.next_to(axes2, DOWN, buff=MED_LARGE_BUFF)
eq.move_to(midpoint(
step_tex.get_left(),
sum_tex.get_right()
))
rects = it.chain(
[
SurroundingRectangle(sum_tex[0][i])
for i in [4, 6, 8]
],
it.cycle([None])
)
self.add(axes1, arrow, axes2)
self.add(step_func)
self.add(sum_tex, eq, step_tex)
curr_partial_sum = axes1.get_graph(lambda x: 0)
curr_partial_sum.set_stroke(width=1)
for sine_graph, partial_sum, rect in zip(sine_graphs, partial_sums, rects):
anims1 = [
ShowCreation(sine_graph)
]
partial_sum.set_stroke(BLACK, 4, background=True)
anims2 = [
curr_partial_sum.set_stroke,
{"width": 1, "opacity": 0.5},
curr_partial_sum.set_stroke,
{"width": 0, "background": True},
ReplacementTransform(
sine_graph, partial_sum,
remover=True
),
]
if rect:
rect.match_style(sine_graph)
anims1.append(ShowCreation(rect))
anims2.append(FadeOut(rect))
self.play(*anims1)
self.play(*anims2)
curr_partial_sum = partial_sum
class CircleAnimationOfF(FourierOfTrebleClef):
CONFIG = {
"height": 3,
"n_circles": 200,
"run_time": 10,
"arrow_config": {
"tip_length": 0.1,
"stroke_width": 2,
}
}
def get_shape(self):
path = VMobject()
shape = TexMobject("F")
for sp in shape.family_members_with_points():
path.append_points(sp.points)
return path
class LastChapterWrapper(Scene):
def construct(self):
full_rect = FullScreenFadeRectangle(
fill_color=DARK_GREY,
fill_opacity=1,
)
rect = ScreenRectangle(height=6)
rect.set_stroke(WHITE, 2)
rect.set_fill(BLACK, 1)
title = TextMobject("Last chapter")
title.scale(2)
title.to_edge(UP)
rect.next_to(title, DOWN)
self.add(full_rect)
self.play(
FadeIn(rect),
Write(title, run_time=2),
)
self.wait()
class ThreeMainObservations(Scene):
def construct(self):
fourier = ImageMobject("Joseph Fourier")
fourier.set_height(5)
fourier.to_corner(DR)
fourier.shift(LEFT)
bubble = ThoughtBubble(
direction=RIGHT,
height=3,
width=4,
)
bubble.move_tip_to(fourier.get_corner(UL) + 0.5 * DR)
observations = VGroup(
TextMobject(
"1)",
# "Sine waves",
# "H",
# "Heat equation",
),
TextMobject(
"2)",
# "Linearity"
),
TextMobject(
"3)",
# "Any$^{*}$ function is\\\\",
# "a sum of sine waves",
),
)
# heart = SuitSymbol("hearts")
# heart.replace(observations[0][2])
# observations[0][2].become(heart)
# observations[0][1].add(happiness)
# observations[2][2].align_to(
# observations[2][1], LEFT,
# )
observations.arrange(
DOWN,
aligned_edge=LEFT,
buff=LARGE_BUFF,
)
observations.set_height(FRAME_HEIGHT - 2)
observations.to_corner(UL, buff=LARGE_BUFF)
self.add(fourier)
self.play(ShowCreation(bubble))
self.wait()
self.play(LaggedStart(*[
TransformFromCopy(bubble, observation)
for observation in observations
], lag_ratio=0.2))
self.play(
FadeOut(fourier),
FadeOut(bubble),
)
self.wait()
class NewSceneName(Scene):
def construct(self):
pass

View file

@ -0,0 +1,261 @@
from manimlib.imports import *
class TemperatureGraphScene(SpecialThreeDScene):
CONFIG = {
"axes_config": {
"x_min": 0,
"x_max": TAU,
"y_min": 0,
"y_max": 10,
"z_min": -3,
"z_max": 3,
"x_axis_config": {
"tick_frequency": TAU / 8,
"include_tip": False,
},
"num_axis_pieces": 1,
},
"default_graph_style": {
"stroke_width": 2,
"stroke_color": WHITE,
"background_image_file": "VerticalTempGradient",
},
"default_surface_style": {
"fill_opacity": 0.1,
"checkerboard_colors": [LIGHT_GREY],
"stroke_width": 0.5,
"stroke_color": WHITE,
"stroke_opacity": 0.5,
},
}
def get_three_d_axes(self, include_labels=True):
axes = ThreeDAxes(**self.axes_config)
axes.set_stroke(width=2)
# Add number labels
# TODO?
# Add axis labels
if include_labels:
x_label = TexMobject("x")
x_label.next_to(axes.x_axis.get_right(), DOWN)
axes.x_axis.add(x_label)
t_label = TextMobject("Time")
t_label.rotate(90 * DEGREES, OUT)
t_label.next_to(axes.y_axis.get_top(), DL)
axes.y_axis.add(t_label)
temp_label = TextMobject("Temperature")
temp_label.rotate(90 * DEGREES, RIGHT)
temp_label.next_to(axes.z_axis.get_zenith(), RIGHT)
axes.z_axis.add(temp_label)
# Adjust axis orinetations
axes.x_axis.rotate(
90 * DEGREES, RIGHT,
about_point=axes.c2p(0, 0, 0),
)
axes.y_axis.rotate(
90 * DEGREES, UP,
about_point=axes.c2p(0, 0, 0),
)
# Add xy-plane
surface_config = {
"u_min": 0,
"u_max": axes.x_max,
"v_min": 0,
"v_max": axes.y_max,
"resolution": (16, 10),
}
axes.surface_config = surface_config
input_plane = ParametricSurface(
lambda x, t: axes.c2p(x, t, 0),
# lambda x, t: np.array([x, t, 0]),
**surface_config,
)
input_plane.set_style(
fill_opacity=0.5,
fill_color=BLUE_B,
stroke_width=0.5,
stroke_color=WHITE,
)
axes.input_plane = input_plane
return axes
def get_initial_state_graph(self, axes, func, **kwargs):
config = dict()
config.update(self.default_graph_style)
config.update(kwargs)
return ParametricFunction(
lambda x: axes.c2p(
x, 0, func(x)
),
t_min=axes.x_min,
t_max=axes.x_max,
**config,
)
def get_surface(self, axes, func, **kwargs):
config = dict()
config.update(axes.surface_config)
config.update(self.default_surface_style)
config.update(kwargs)
return ParametricSurface(
lambda x, t: axes.c2p(
x, t, func(x, t)
),
**config
)
def orient_three_d_mobject(self, mobject,
phi=85 * DEGREES,
theta=-80 * DEGREES):
mobject.rotate(-90 * DEGREES - theta, OUT)
mobject.rotate(phi, LEFT)
return mobject
class SimpleSinExpGraph(TemperatureGraphScene):
def construct(self):
axes = self.get_three_d_axes()
sine_graph = self.get_sine_graph(axes)
sine_exp_surface = self.get_sine_exp_surface(axes)
self.set_camera_orientation(
phi=80 * DEGREES,
theta=-80 * DEGREES,
)
self.camera.frame_center.shift(3 * RIGHT)
self.begin_ambient_camera_rotation(rate=0.01)
self.add(axes)
self.play(ShowCreation(sine_graph))
self.play(UpdateFromAlphaFunc(
sine_exp_surface,
lambda m, a: m.become(
self.get_sine_exp_surface(axes, v_max=a * 10)
),
run_time=3
))
self.wait(20)
#
def sin_exp(self, x, t, A=2, omega=1, k=0.25):
return A * np.sin(omega * x) * np.exp(-k * (omega**2) * t)
def get_sine_graph(self, axes, **config):
return self.get_initial_state_graph(
axes,
lambda x: self.sin_exp(x, 0),
**config
)
def get_sine_exp_surface(self, axes, **config):
return self.get_surface(
axes,
lambda x, t: self.sin_exp(x, t),
**config
)
class AddMultipleSolutions(SimpleSinExpGraph):
CONFIG = {
"axes_config": {
"x_axis_config": {
"unit_size": 0.7,
},
}
}
def construct(self):
axes1, axes2, axes3 = all_axes = VGroup(*[
self.get_three_d_axes(
include_labels=False,
)
for x in range(3)
])
all_axes.scale(0.5)
self.orient_three_d_mobject(all_axes)
As = [1.5, 1.5]
omegas = [1, 2]
ks = [0.25, 0.01]
quads = [
(axes1, [As[0]], [omegas[0]], [ks[0]]),
(axes2, [As[1]], [omegas[1]], [ks[1]]),
(axes3, As, omegas, ks),
]
for axes, As, omegas, ks in quads:
graph = self.get_initial_state_graph(
axes,
lambda x: np.sum([
self.sin_exp(x, 0, A, omega, k)
for A, omega, k in zip(As, omegas, ks)
])
)
surface = self.get_surface(
axes,
lambda x, t: np.sum([
self.sin_exp(x, t, A, omega)
for A, omega in zip(As, omegas)
])
)
surface.sort(lambda p: -p[2])
axes.add(surface, graph)
axes.graph = graph
axes.surface = surface
self.set_camera_orientation(distance=100)
plus = TexMobject("+").scale(2)
equals = TexMobject("=").scale(2)
group = VGroup(
axes1, plus, axes2, equals, axes3,
)
group.arrange(RIGHT, buff=SMALL_BUFF)
for axes in all_axes:
checkmark = TexMobject("\\checkmark")
checkmark.set_color(GREEN)
checkmark.scale(2)
checkmark.next_to(axes, UP)
checkmark.shift(0.7 * DOWN)
axes.checkmark = checkmark
self.add(axes1, axes2)
self.play(
LaggedStart(
Write(axes1.surface),
Write(axes2.surface),
),
LaggedStart(
FadeInFrom(axes1.checkmark, DOWN),
FadeInFrom(axes2.checkmark, DOWN),
),
lag_ratio=0.2,
run_time=1,
)
self.wait()
self.play(Write(plus))
self.play(
Transform(
axes1.copy().set_fill(opacity=0),
axes3
),
Transform(
axes2.copy().set_fill(opacity=0),
axes3
),
FadeInFrom(equals, LEFT)
)
self.play(
FadeInFrom(axes3.checkmark, DOWN),
)
self.wait()

View file

@ -4,8 +4,8 @@
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
SOURCEDIR = .
BUILDDIR = _build
SOURCEDIR = source
BUILDDIR = build
# Put it first so that "make" without argument is like "make help".
help:
@ -16,4 +16,4 @@ help:
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

View file

@ -7,8 +7,8 @@ REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=.
set BUILDDIR=_build
set SOURCEDIR=source
set BUILDDIR=build
if "%1" == "" goto help

11
docs/source/about.rst Normal file
View file

@ -0,0 +1,11 @@
About
=====
Animating technical concepts is traditionally pretty tedious, since it can be
difficult to make the animations precise enough to convey them accurately.
``Manim`` uses Python to generate animations programmatically, which makes it
possible to specify exactly how each one should run.
This project is still very much a work in progress, but I hope that the
information here will make it easier for newcomers to get started using
``Manim``.

52
docs/source/conf.py Normal file
View file

@ -0,0 +1,52 @@
# Configuration file for the Sphinx documentation builder.
#
# This file only contains a selection of the most common options. For a full
# list see the documentation:
# http://www.sphinx-doc.org/en/master/config
# -- Path setup --------------------------------------------------------------
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
# import os
# import sys
# sys.path.insert(0, os.path.abspath('.'))
# -- Project information -----------------------------------------------------
project = 'Manim'
copyright = '2019, EulerTour'
author = 'EulerTour'
# -- General configuration ---------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = []
# -- Options for HTML output -------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = 'sphinx_rtd_theme'
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']

View file

@ -0,0 +1,4 @@
Animating Mobjects
==================
Learn about animations.

View file

@ -0,0 +1,18 @@
Getting Started
===============
Todd Zimmerman put together `a very nice tutorial`_ on getting started with
``manim``, but is unfortunately outdated. It's still useful for understanding
how ``manim`` is used, but the examples won't run on the latest version of
``manim``.
.. _a very nice tutorial: https://talkingphysics.wordpress.com/2018/06/11/learning-how-to-animate-videos-using-``manim``-series-a-journey/
.. toctree::
:caption: Contents:
:maxdepth: 2
learning_by_example
mathematical_objects
animating_mobjects
making_a_scene

View file

@ -0,0 +1,132 @@
Learning by Example
===================
You create videos in manim by writing :class:`~scene.scene.Scene` instances.
``example_scenes.py`` contains a few simple ones that we can use to learn about
manim. For instance, take ``SquareToCircle``.
.. code-block:: python
:linenos:
class SquareToCircle(Scene):
def construct(self):
circle = Circle()
square = Square()
square.flip(RIGHT)
square.rotate(-3 * TAU / 8)
circle.set_fill(PINK, opacity=0.5)
self.play(ShowCreation(square))
self.play(Transform(square, circle))
self.play(FadeOut(square))
:meth:`~scene.scene.Scene.construct` specifies what is displayed on the screen
when the :class:`~scene.scene.Scene` is rendered to video. You can render a
:class:`~scene.scene.Scene` by running ``extract_scene.py``. Run ``python
extract_scene.py -h`` to see how it's used.
.. code-block:: none
> python extract_scene.py -h
usage: extract_scene.py [-h] [-p] [-w] [-s] [-l] [-m] [-g] [-f] [-t] [-q] [-a]
[-o OUTPUT_NAME] [-n START_AT_ANIMATION_NUMBER]
[-r RESOLUTION] [-c COLOR] [-d OUTPUT_DIRECTORY]
file [scene_name]
positional arguments:
file path to file holding the python code for the scene
scene_name Name of the Scene class you want to see
optional arguments:
-h, --help show this help message and exit
-p, --preview
-w, --write_to_movie
-s, --show_last_frame
-l, --low_quality
-m, --medium_quality
-g, --save_pngs
-f, --show_file_in_finder
-t, --transparent
-q, --quiet
-a, --write_all
-o OUTPUT_NAME, --output_name OUTPUT_NAME
-n START_AT_ANIMATION_NUMBER, --start_at_animation_number START_AT_ANIMATION_NUMBER
-r RESOLUTION, --resolution RESOLUTION
-c COLOR, --color COLOR
-d OUTPUT_DIRECTORY, --output_directory OUTPUT_DIRECTORY
The most common flags are ``-p``, to automatically play the generated video,
``-l``, to render in lower quality in favor of speed, and ``-s``, to show the
last frame of the :class:`~scene.scene.Scene` for faster development. Run
``python extract_scene.py example_scenes.py SquareToCircle -pl`` to produce a
file called SquareToCircle.mp4 in the media directory that you have configured,
and automatically play it.
.. raw:: html
<iframe width="560" height="315" src="https://www.youtube.com/embed/8tvYDIGLJJA?ecver=1" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>
Let's step through each line of the :class:`~scene.scene.Scene`. Lines 3 and 4
instantiate a :class:`~mobject.geometry.Circle` and
:class:`~mobject.geometry.Square`, respectively. Both of these subclass
:class:`~mobject.mobject.Mobject`, the base class for objects in manim. Note
that instantiating a :class:`~mobject.mobject.Mobject` does not add it to the
:class:`~scene.scene.Scene`, so you wouldn't see anything if you were to render
the :class:`~scene.scene.Scene` at this point.
.. code-block:: python
:linenos:
:lineno-start: 3
circle = Circle()
square = Square()
Lines 5, 6, and 7 apply various modifications to the mobjects before animating
them. The call to :meth:`~mobject.mobject.Mobject.flip` on line 5 flips the
:class:`~mobject.geometry.Square` across the RIGHT vector. This is equivalent
to a refection across the x-axis. Then the call to
:meth:`~mobject.mobject.Mobject.rotate` on line 6 rotates the
:class:`~mobject.geometry.Square` 3/8ths of a full rotation counterclockwise.
Finally, the call to :meth:`~mobject.mobject.Mobject.set_fill` on line 7 sets
the fill color for the :class:`~mobject.geometry.Circle` to pink, and its
opacity to 0.5.
.. code-block:: python
:linenos:
:lineno-start: 5
square.flip(RIGHT)
square.rotate(-3 * TAU / 8)
circle.set_fill(PINK, opacity=0.5)
Line 9 is the first to generate video.
:class:`~animation.creation.ShowCreation`,
:class:`~animation.transform.Transform`, and
:class:`~animation.creation.FadeOut` are
:class:`~animation.animation.Animation` instances. Each
:class:`~animation.animation.Animation` takes one or more
:class:`~mobject.mobject.Mobject` instances as arguments, which it animates
when passed to :meth:`~scene.scene.Scene.play`. This is how video is typically
created in manim. :class:`~mobject.mobject.Mobject` instances are automatically
added to the :class:`~scene.scene.Scene` when they are animated. You can add a
:class:`~mobject.mobject.Mobject` to the :class:`~scene.scene.Scene` manually
by passing it as an argument to :meth:`~scene.scene.Scene.add`.
.. code-block:: python
:linenos:
:lineno-start: 9
self.play(ShowCreation(square))
self.play(Transform(square, circle))
self.play(FadeOut(square))
:class:`~animation.creation.ShowCreation` draws a
:class:`~mobject.mobject.Mobject` to the screen,
:class:`~animation.transform.Transform` morphs one
:class:`~mobject.mobject.Mobject` into another, and
:class:`~animation.creation.FadeOut` fades a
:class:`~mobject.mobject.Mobject` out of the :class:`~scene.scene.Scene`. Note
that only the first argument to :class:`~animation.transform.Transform` is
modified, and the second is not added to the :class:`~scene.scene.Scene`. After
line 10 is executed ``square`` is a :class:`~mobject.geometry.Square` instance
with the shape of a :class:`~mobject.geometry.Circle`.

View file

@ -0,0 +1,4 @@
Making a Scene
==============
Talk about Scenes and organization, bring it all together.

View file

@ -0,0 +1,13 @@
Mathematical Objects
====================
Everything that appears on screen in a manim video is a
:class:`~mobject.mobject.Mobject`, or Mathematical Object. A
:class:`~mobject.mobject.Mobject`'s appearance is determined by 3
factors:
* ``m.points``, an Nx3 ``numpy.array`` specifying how to draw ``m``
* ``m``'s style attributes, such as ``m.color``, ``m.stroke_width``, and
``m.fill_opacity``
* ``m.submobjects``, a list of :class:`~mobject.mobject.Mobject` instances that
are considered part of ``m``

25
docs/source/index.rst Normal file
View file

@ -0,0 +1,25 @@
.. Manim documentation master file, created by
sphinx-quickstart on Mon May 27 14:19:19 2019.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Welcome to Manim's documentation!
=================================
test change
.. toctree::
:maxdepth: 2
:caption: Contents:
about
installation/index
getting_started/index
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`

View file

@ -0,0 +1,12 @@
Installation
============
Instructions on installing Manim
.. toctree::
:maxdepth: 2
:caption: Contents:
linux
mac
windows

View file

@ -0,0 +1,4 @@
Linux
=====
A stub for linux installation

View file

@ -0,0 +1,4 @@
Mac
===
A stub for mac installation

View file

@ -0,0 +1,4 @@
Windows
=======
A stub for windows installation

View file

@ -11,6 +11,8 @@ from manimlib.imports import *
# Use the -p to have the animation (or image, if -s was
# used) pop up once done.
# Use -n <number> to skip ahead to the n'th animation of a scene.
# Use -r <number> to specify a resolution (for example, -r 1080
# for a 1920x1080 video)
class OpeningManimExample(Scene):
@ -111,7 +113,7 @@ class WriteStuff(Scene):
self.wait()
class UdatersExample(Scene):
class UpdatersExample(Scene):
def construct(self):
decimal = DecimalNumber(
0,

BIN
logo/cropped.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

BIN
logo/graph.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

176
logo/logo.py Normal file
View file

@ -0,0 +1,176 @@
from manimlib.imports import *
NEW_BLUE = "#68a8e1"
class Thumbnail(GraphScene):
CONFIG = {
"y_max": 8,
"y_axis_height": 5,
}
def construct(self):
self.show_function_graph()
def show_function_graph(self):
self.setup_axes(animate=False)
def func(x):
return 0.1 * (x + 3-5) * (x - 3-5) * (x-5) + 5
def rect(x):
return 2.775*(x-1.5)+3.862
recta = self.get_graph(rect,x_min=-1,x_max=5)
graph = self.get_graph(func,x_min=0.2,x_max=9)
graph.set_color(NEW_BLUE)
input_tracker_p1 = ValueTracker(1.5)
input_tracker_p2 = ValueTracker(3.5)
def get_x_value(input_tracker):
return input_tracker.get_value()
def get_y_value(input_tracker):
return graph.underlying_function(get_x_value(input_tracker))
def get_x_point(input_tracker):
return self.coords_to_point(get_x_value(input_tracker), 0)
def get_y_point(input_tracker):
return self.coords_to_point(0, get_y_value(input_tracker))
def get_graph_point(input_tracker):
return self.coords_to_point(get_x_value(input_tracker), get_y_value(input_tracker))
def get_v_line(input_tracker):
return DashedLine(get_x_point(input_tracker), get_graph_point(input_tracker), stroke_width=2)
def get_h_line(input_tracker):
return DashedLine(get_graph_point(input_tracker), get_y_point(input_tracker), stroke_width=2)
#
input_triangle_p1 = RegularPolygon(n=3, start_angle=TAU / 4)
output_triangle_p1 = RegularPolygon(n=3, start_angle=0)
for triangle in input_triangle_p1, output_triangle_p1:
triangle.set_fill(WHITE, 1)
triangle.set_stroke(width=0)
triangle.scale(0.1)
#
input_triangle_p2 = RegularPolygon(n=3, start_angle=TAU / 4)
output_triangle_p2 = RegularPolygon(n=3, start_angle=0)
for triangle in input_triangle_p2, output_triangle_p2:
triangle.set_fill(WHITE, 1)
triangle.set_stroke(width=0)
triangle.scale(0.1)
#
x_label_p1 = TexMobject("a")
output_label_p1 = TexMobject("f(a)")
x_label_p2 = TexMobject("b")
output_label_p2 = TexMobject("f(b)")
v_line_p1 = get_v_line(input_tracker_p1)
v_line_p2 = get_v_line(input_tracker_p2)
h_line_p1 = get_h_line(input_tracker_p1)
h_line_p2 = get_h_line(input_tracker_p2)
graph_dot_p1 = Dot(color=WHITE)
graph_dot_p2 = Dot(color=WHITE)
# reposition mobjects
x_label_p1.next_to(v_line_p1, DOWN)
x_label_p2.next_to(v_line_p2, DOWN)
output_label_p1.next_to(h_line_p1, LEFT)
output_label_p2.next_to(h_line_p2, LEFT)
input_triangle_p1.next_to(v_line_p1, DOWN, buff=0)
input_triangle_p2.next_to(v_line_p2, DOWN, buff=0)
output_triangle_p1.next_to(h_line_p1, LEFT, buff=0)
output_triangle_p2.next_to(h_line_p2, LEFT, buff=0)
graph_dot_p1.move_to(get_graph_point(input_tracker_p1))
graph_dot_p2.move_to(get_graph_point(input_tracker_p2))
#
self.play(
ShowCreation(graph),
)
# Animacion del punto a
self.add_foreground_mobject(graph_dot_p1)
self.add_foreground_mobject(graph_dot_p2)
self.play(
DrawBorderThenFill(input_triangle_p1),
Write(x_label_p1),
ShowCreation(v_line_p1),
GrowFromCenter(graph_dot_p1),
ShowCreation(h_line_p1),
Write(output_label_p1),
DrawBorderThenFill(output_triangle_p1),
DrawBorderThenFill(input_triangle_p2),
Write(x_label_p2),
ShowCreation(v_line_p2),
GrowFromCenter(graph_dot_p2),
ShowCreation(h_line_p2),
Write(output_label_p2),
DrawBorderThenFill(output_triangle_p2),
run_time=0.5
)
self.add(
input_triangle_p2,
x_label_p2,
graph_dot_p2,
v_line_p2,
h_line_p2,
output_triangle_p2,
output_label_p2,
)
###################
pendiente_recta = self.get_secant_slope_group(
1.9, recta, dx = 1.4,
df_label = None,
dx_label = None,
dx_line_color = PURPLE,
df_line_color= ORANGE,
)
grupo_secante = self.get_secant_slope_group(
1.5, graph, dx = 2,
df_label = None,
dx_label = None,
dx_line_color = "#942357",
df_line_color= "#3f7d5c",
secant_line_color = RED,
)
self.add(
input_triangle_p2,
graph_dot_p2,
v_line_p2,
h_line_p2,
output_triangle_p2,
)
self.play(FadeIn(grupo_secante))
kwargs = {
"x_min" : 4,
"x_max" : 9,
"fill_opacity" : 0.75,
"stroke_width" : 0.25,
}
self.graph=graph
iteraciones=6
self.rect_list = self.get_riemann_rectangles_list(
graph, iteraciones,start_color=PURPLE,end_color=ORANGE, **kwargs
)
flat_rects = self.get_riemann_rectangles(
self.get_graph(lambda x : 0), dx = 0.5,start_color=invert_color(PURPLE),end_color=invert_color(ORANGE),**kwargs
)
rects = self.rect_list[0]
self.transform_between_riemann_rects(
flat_rects, rects,
replace_mobject_with_target_in_scene = True,
run_time=0.9
)
# adding manim
picture = Group(*self.mobjects)
picture.scale(0.6).to_edge(LEFT, buff=SMALL_BUFF)
manim = TextMobject("Manim").set_height(1.5) \
.next_to(picture, RIGHT) \
.shift(DOWN * 0.7)
self.add(manim)

BIN
logo/with_name.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

BIN
logo/with_subtext.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

View file

@ -171,12 +171,18 @@ class Axes(VGroup, CoordinateSystem):
result += (axis.number_to_point(coord) - origin)
return result
def c2p(self, *coords):
return self.coords_to_point(*coords)
def point_to_coords(self, point):
return tuple([
axis.point_to_number(point)
for axis in self.get_axes()
])
def p2c(self, point):
return self.point_to_coords(point)
def get_axes(self):
return self.axes

View file

@ -8,7 +8,7 @@ class ParametricFunction(VMobject):
CONFIG = {
"t_min": 0,
"t_max": 1,
"step_size": 0.01, # use "auto" (lwoercase) for automatic step size
"step_size": 0.01, # Use "auto" (lowercase) for automatic step size
"dt": 1e-8,
# TODO, be smarter about figuring these out?
"discontinuities": [],

View file

@ -34,6 +34,7 @@ class SceneFileWriter(object):
# TODO, address this in extract_scene et. al.
"file_name": None,
"output_directory": None,
"file_name": None,
}
def __init__(self, scene, **kwargs):
@ -81,7 +82,10 @@ class SceneFileWriter(object):
return root if root else ext[1:]
def get_default_file_name(self):
return self.scene.__class__.__name__
if self.file_name is None:
return self.scene.__class__.__name__
else:
return self.file_name
def get_movie_directory(self):
pixel_height = self.scene.camera.pixel_height

View file

@ -205,6 +205,10 @@ def center_of_mass(points):
return sum(points) / len(points)
def midpoint(point1, point2):
return center_of_mass([point1, point2])
def line_intersection(line1, line2):
"""
return intersection point of two lines,

View file

@ -20,7 +20,7 @@ import random
import numpy as np
from PIL import Image
from nn.mnist_loader import load_data_wrapper
from utils.space_ops import get_norm
# from utils.space_ops import get_norm
NN_DIRECTORY = os.path.dirname(os.path.realpath(__file__))
# PRETRAINED_DATA_FILE = os.path.join(NN_DIRECTORY, "pretrained_weights_and_biases_80")

View file

@ -2964,7 +2964,7 @@ class BiasForInactiviyWords(Scene):
self.play(Write(words))
self.wait(3)
class ContinualEdgeUpdate(ContinualAnimation):
class ContinualEdgeUpdate(VGroup):
CONFIG = {
"max_stroke_width" : 3,
"stroke_width_exp" : 7,
@ -2972,7 +2972,8 @@ class ContinualEdgeUpdate(ContinualAnimation):
"colors" : [GREEN, GREEN, GREEN, RED],
}
def __init__(self, network_mob, **kwargs):
digest_config(self, kwargs)
VGroup.__init__(self, **kwargs)
self.internal_time = 0
n_cycles = self.n_cycles
edges = VGroup(*it.chain(*network_mob.edge_groups))
self.move_to_targets = []
@ -2990,11 +2991,14 @@ class ContinualEdgeUpdate(ContinualAnimation):
edge.generate_target()
edge.target.set_stroke(edge.colors[0], edge.widths[0])
self.move_to_targets.append(MoveToTarget(edge))
edge.become(edge.target)
self.move_to_targets.append(edge)
self.edges = edges
ContinualAnimation.__init__(self, edges, **kwargs)
self.add(edges)
self.add_updater(lambda m, dt: self.update_edges(dt))
def update_mobject(self, dt):
def update_edges(self, dt):
self.internal_time += dt
if self.internal_time < 1:
alpha = smooth(self.internal_time)
for move_to_target in self.move_to_targets:
@ -4559,8 +4563,9 @@ class ShowAmplify(PiCreatureScene):
class Thumbnail(NetworkScene):
CONFIG = {
"network_mob_config" : {
'neuron_stroke_color' : WHITE
}
'neuron_stroke_color' : WHITE,
'layer_to_layer_buff': 1.25,
},
}
def construct(self):
network_mob = self.network_mob
@ -4568,6 +4573,23 @@ class Thumbnail(NetworkScene):
for layer in network_mob.layers:
layer.neurons.set_stroke(width = 5)
network_mob.set_height(5)
network_mob.to_edge(DOWN)
network_mob.to_edge(LEFT, buff=1)
subtitle = TextMobject(
"From the\\\\",
"ground up\\\\",
)
# subtitle.arrange(
# DOWN,
# buff=0.25,
# aligned_edge=LEFT,
# )
subtitle.set_color(YELLOW)
subtitle.set_height(2.75)
subtitle.next_to(network_mob, RIGHT, buff=MED_LARGE_BUFF)
edge_update = ContinualEdgeUpdate(
network_mob,
max_stroke_width = 10,
@ -4576,7 +4598,18 @@ class Thumbnail(NetworkScene):
edge_update.internal_time = 3
edge_update.update(0)
for mob in network_mob.family_members_with_points():
if mob.get_stroke_width() < 2:
mob.set_stroke(width=2)
title = TextMobject("Neural Networks")
title.scale(3)
title.to_edge(UP)
self.add(network_mob)
self.add(subtitle)
self.add(title)

View file

@ -6,5 +6,5 @@ progressbar==2.5
scipy==1.1.0
tqdm==4.24.0
opencv-python==3.4.2.17
pycairo==1.17.1
pycairo>=1.17.1
pydub==0.23.0

10
travis/build_docs.sh Executable file
View file

@ -0,0 +1,10 @@
#!/bin/bash
pip install sphinx_rtd_theme
make --directory docs/ html
openssl aes-256-cbc -K $encrypted_1b28e850a424_key \
-iv $encrypted_1b28e850a424_iv \
-in travis/crypt.enc \
-out travis/crypt -d
tar xf travis/crypt
travis/deploy_docs.sh

BIN
travis/crypt.enc Normal file

Binary file not shown.