139 Commits

Author SHA1 Message Date
Devin Neal
d01ca92120 remove extraneous newline 2019-07-07 21:46:59 -07:00
Devin Neal
61c963a8ae update documentation link again 2019-07-07 21:44:50 -07:00
Devin Neal
e83c69d968 udpate documentation link 2019-07-07 21:37:24 -07:00
Devin Neal
dfdceb1908 allow pycairo>=1.18.0 for windows 2019-07-07 10:11:33 -07:00
Devin Neal
41792fdb5f Merge pull request #607 from 3b1b/video_dir
Add --video_output_dir flag
2019-06-21 23:18:23 -07:00
Devin Neal
3ec231f0ca add video_output_dir flag 2019-06-21 22:52:16 -07:00
Grant Sanderson
7d596d0840 Merge pull request #606 from 3b1b/diffyq
Diffyq
2019-06-19 16:14:56 -07:00
Grant Sanderson
2e0e5cfb5e Rename ode folder to diffyq 2019-06-19 16:11:46 -07:00
Grant Sanderson
96e34b969c Changed ode folder to be named diffyq, since many of these are pde videos 2019-06-19 16:10:21 -07:00
Grant Sanderson
2f6be9a389 Some of the first scenes for diffyq part4 2019-06-19 16:08:46 -07:00
Grant Sanderson
2294cdea4f Allow Bubble to flip over any axis, which was unnecessarily restricted before 2019-06-19 16:08:14 -07:00
Grant Sanderson
8ce13875a3 Added Line.set_length 2019-06-19 16:07:22 -07:00
Grant Sanderson
7ac990119a frame_center was getting double-added, messing up MovingCameraScene 2019-06-19 16:07:07 -07:00
Devin Neal
43b643db6c Merge pull request #586 from Kolloom/install_doc
Install doc
2019-06-19 14:46:34 -07:00
Grant Sanderson
9da74cb657 Beginning chapter 4 animations 2019-06-18 20:40:41 -07:00
Grant Sanderson
2680a9c373 Change Fourier scene to center on vectors, not circles 2019-06-18 20:40:29 -07:00
Devin Neal
f908b68bed Merge pull request #597 from cclauss/patch-1
Travis CI: The sudo tag is now deprecated
2019-06-18 00:19:41 -07:00
Devin Neal
71f79530be Merge pull request #600 from red5h4d0w/master
Update tex_file_writing.py
2019-06-17 13:01:47 -07:00
red5h4d0w
e72390bfc4 Update tex_file_writing.py 2019-06-17 15:40:03 -04:00
cclauss
dc83cac5b4 Travis CI: The sudo tag is now deprecated
__sudo: required__ no longer is...  [Travis are now recommending removing the __sudo__ tag](https://blog.travis-ci.com/2018-11-19-required-linux-infrastructure-migration).

"_If you currently specify __sudo:__ in your __.travis.yml__, we recommend removing that configuration_"
2019-06-16 22:48:58 +02:00
Grant Sanderson
5a86ba08f4 Merge pull request #596 from 3b1b/diffyq
Diffyq
2019-06-16 12:53:02 -07:00
Grant Sanderson
e421d3689a Merge branch 'master' of github.com:3b1b/manim into diffyq 2019-06-16 12:51:46 -07:00
Grant Sanderson
dfeb11a2ab Final animations for diffyq chapter 3 2019-06-16 12:51:34 -07:00
Devin Neal
66f9ff29e4 update windows docs 2019-06-15 21:42:05 -07:00
Grant Sanderson
62c2772982 Nearly the final animations for diffyq part 3 2019-06-15 20:52:38 -07:00
Grant Sanderson
9e7619844f Latest animations for diffyq part 3 2019-06-15 12:22:37 -07:00
Grant Sanderson
6c2e7f4c2c interpolate_mobject should return self 2019-06-15 12:22:21 -07:00
Grant Sanderson
08eee147b3 Added TangentLine 2019-06-15 12:21:29 -07:00
Kolloom
43b4508b2c Fix wording. 2019-06-15 14:03:04 -05:00
Devin Neal
dade021eaa Merge pull request #592 from Grae-Drake/feature/docs-edits
Update link to Todd Zimmerman tutorial
2019-06-15 00:09:33 -07:00
Devin Neal
0041e3fd45 Merge pull request #585 from Kolloom/doc
Add documentation for the coordinate system
2019-06-14 23:15:01 -07:00
Grae Drake
d031e5735a Update tutorial description 2019-06-14 23:06:06 -07:00
Kolloom
e64a25bd40 Add a docker diagram to README.md 2019-06-15 00:51:22 -05:00
Kolloom
d7054e61f0 Change docker-compose to reflect README.md changes 2019-06-15 00:45:14 -05:00
Kolloom
6c1fb7ddca Change docker instructions on README.md
Change all refernces of INPUT_DIR to INPUT_PATH
change all references of OUTPUT_DIR to OUTPUT_PATH

change a paragraph
2019-06-15 00:43:32 -05:00
Grae Drake
7fab15abd4 Update link to Todd Zimmerman tutorial 2019-06-14 21:38:37 -07:00
Kolloom
6571b0d88e Mention VMobject for geometry 2019-06-14 23:34:56 -05:00
Kolloom
ee83d3bcec Update assets with medium preset 2019-06-14 23:26:40 -05:00
Kolloom
542ea68824 Fix wording 2019-06-14 23:08:03 -05:00
Kolloom
2a930ff702 Use monospace for asset annotation 2019-06-14 23:06:03 -05:00
Grant Sanderson
6f7e123b1a Discrete case scenes for diffyq part 3 2019-06-14 10:07:06 -07:00
Grant Sanderson
4196bb627e New scenes for diffyq part3 2019-06-13 16:16:42 -07:00
Grant Sanderson
2cbe19af7c Bug fix for the case when the media_dir has spaces 2019-06-13 16:15:43 -07:00
Grant Sanderson
2ccf83c0aa Change output folder heirarchy back to grouping files of similar types, rather grouping them as parts of the same scene 2019-06-13 16:15:02 -07:00
Grant Sanderson
cfbbff1bdf Default color of Vector should be white 2019-06-13 16:14:03 -07:00
Grant Sanderson
f10baa3fcd Change default media output directory to from 'video' to 'videos' 2019-06-13 16:13:42 -07:00
Grant Sanderson
34d1d27c56 Merge pull request #588 from 3b1b/diffyq
Diffyq
2019-06-13 09:34:25 -07:00
Grant Sanderson
cc08c090d1 Merge branch 'master' of github.com:3b1b/manim into diffyq 2019-06-13 09:29:57 -07:00
Grant Sanderson
caa4efdfa5 Latest scenes for diffyq part 3 2019-06-13 09:27:35 -07:00
Grant Sanderson
503b2bc896 For some reason stage_scenes still has a hardcoded input directory... 2019-06-13 09:27:19 -07:00
Grant Sanderson
542ddb9afd Make sure set_opacity changes background stroke as well 2019-06-13 09:26:34 -07:00
Grant Sanderson
6214ea7a01 Added TracedPath 2019-06-13 09:26:03 -07:00
Grant Sanderson
3b42f1f709 Added int_func to SHowIncreasingSubsets 2019-06-13 09:25:14 -07:00
Devin Neal
ab75015099 Merge pull request #582 from stephenwild/master
Documentation for TipableVMobject
2019-06-13 00:48:15 -07:00
Kolloom
e2eac0bd96 Minor edit 2019-06-11 00:59:36 -05:00
Kolloom
65c23d9e9f Fix docker install instruction
Env Var used in the compose.yml is INPUT_DIR and OUTPUT_DIR

Clarify the meaning of both variables and output message.
Note on -p not working.
2019-06-11 00:49:31 -05:00
Devin Neal
1322152391 Update README.md 2019-06-10 20:09:42 -07:00
Kolloom
b71e63a540 Add documentation for the coordinate system
Update index
Add 4 assets, one of which is an image

Image uses width 700
Videos uses 700x394
Those dimension are similar to how content is laid out
in sphinx
2019-06-10 16:23:49 -05:00
Stephen Wild
41539387a5 Documentation for TipableVMobject
Add an overview of TipableVMobject's functionality and a few comments, as well as grouping some methods by functionality (roughly).
2019-06-10 03:36:16 +08:00
Devin Neal
22b4c3578b Merge pull request #580 from Kolloom/pull_jarwin_patch-3
Update learning_by_example.rst
2019-06-08 16:37:54 -07:00
Kolloom
5535997356 Simple wording change 2019-06-08 18:32:42 -05:00
Devin Neal
3e2a312f51 Merge pull request #581 from Kolloom/link_fix
fix broken link due to misnamed file
2019-06-08 15:33:13 -07:00
Devin Neal
aad68a5503 Update docker install link 2019-06-08 15:28:25 -07:00
Kolloom
7f54ac9b3a fix broken link due to misnamed file 2019-06-08 14:17:59 -05:00
Kolloom
fb9cf93470 Minor changes 2019-06-08 14:00:59 -05:00
Kolloom
d1ada7a8aa Update mac install doc to include manim
also caution on python2 incompatibility
2019-06-08 13:44:10 -05:00
Kolloom
9ffe4a6839 Rewrite learning_by_example.py
Move embedded video to below example code.

Delete ``manim -h`` output. If user is able to run manim,
they should be able to use the help flag too.

Change wording on "instance" when it should be "classes"

Move info on flags to a note block

Add a note on transform only change apparances.

Move explanations to after code block

...
2019-06-08 13:32:27 -05:00
Devin Neal
f6dc1cb50f updated Compose file and update docker section of README.md 2019-06-08 00:40:23 -07:00
Devin Neal
a024916e66 Apply link fix from master 2019-06-08 00:32:43 -05:00
Kolloom
73a1d37785 Merging mac install doc from jarwin/patch-3 2019-06-08 00:19:31 -05:00
Purusharth Saxena
62206bc0e7 Update learning_by_example.rst
Updated getting started to accomodate manimlib (pip installation)
2019-06-08 00:19:31 -05:00
Devin Neal
57b96fdd07 fix documentation links 2019-06-06 20:40:11 -07:00
Devin Neal
de0d7c907d Merge branch 'master' of github.com:3b1b/manim 2019-06-04 21:10:37 -07:00
Devin Neal
8449bc3418 update documentation 2019-06-04 21:10:12 -07:00
Devin Neal
f81c275631 make media, tex, and video directories configurable via flags 2019-06-04 20:51:18 -07:00
Devin Neal
949bfaa0b4 Merge pull request #577 from Kolloom/master
Add animation documentation and asset folder
2019-06-04 20:38:15 -07:00
Kolloom
e1fb9f480b Add animation documentation and asset folder
Animation documentation is not yet complete.
Asset folder contains renders from code snipples.

Update constants.rst to include a ref tag
Update index to include the animation doc
2019-06-04 13:15:35 -05:00
Devin Neal
caa4577cd1 reorganize media file tree 2019-06-03 23:41:05 -07:00
Grant Sanderson
43b82f2c53 Scenes up to AnalyzeSineCurve for diffyq part 3 2019-06-03 11:33:39 -07:00
Devin Neal
c203f8e8c0 remove repeated latex package 2019-06-02 21:13:41 -07:00
Devin Neal
75996a618c install less latex 2019-06-02 21:10:45 -07:00
Devin Neal
ed24541de6 update dockerfile, add flag for high quality rendering (#573)
Allow rendering in 1080p by passing `--high_quality` and update the Dockerfile and Compose file to install Manim directly into the container.
2019-06-02 18:58:33 -07:00
Kolloom
22c8aa0eb8 Add documentation for constants.py (#572)
* Add documentation for constants.py

* Update documentation for constants.py

With suggestions from eulertour
2019-06-02 18:21:11 -07:00
Kierán Meinhardt
0590c153ef remove typographically incorrect punctuation from documentation headings (#567) 2019-06-02 13:54:03 -07:00
Charly
c42ae8a55b add option -i to save as gif (#529)
* add option -i to save as gif

* re-add -c command

* No longer needs to save mp4 first
2019-06-02 12:13:22 -07:00
Devin Neal
3632d97140 install pycairo conditionally based on platform identifier (#571) 2019-05-31 10:44:51 -07:00
quark67
e9fc2aa46c Correction of a bad alt description (#566) 2019-05-30 13:17:45 -07:00
Yoshi Askharoun
20590e6823 Remove duplicate usepackage (#554)
The textcomp package was imported twice in the ctex template
2019-05-29 20:25:51 -07:00
Grant Sanderson
8bb0b4f010 Merge branch 'master' of github.com:3b1b/manim into diffyq 2019-05-29 18:29:11 -07:00
Grant Sanderson
ab2318ff9d New scenes for diffyq part 3 2019-05-29 18:28:40 -07:00
Grant Sanderson
5dcb113996 Moving the frame_center of camera was not working. This fixes it, but I suspect there is a need for a deeper fix where everything is handled in transform_points_pre_display properly for the various camera classes 2019-05-29 18:28:28 -07:00
Devin Neal
68fb61a5b2 add information to docs (#563) 2019-05-28 21:28:01 -07:00
Devin Neal
2f37e3abf2 Merge pull request #562 from eulertour/master
add master_doc to conf.py
2019-05-28 21:07:40 -07:00
Devin Neal
aecf5b0b18 add master_doc to conf.py 2019-05-28 21:03:20 -07:00
Devin Neal
224d389522 Merge pull request #561 from Kolloom/master
Add Windows installation documentation
2019-05-28 19:08:34 -07:00
Grant Sanderson
52054571ab Merge branch 'master' of github.com:3b1b/manim into diffyq 2019-05-28 13:38:50 -07:00
Grant Sanderson
ddd7ce2f12 Animations up to the preview for the breakdown into sine curves for diffyq chapter 3 2019-05-28 13:38:45 -07:00
Kolloom
0b35d8e2ec Add Windows installation documentation
The installation is done with a Windows8 machine,
further testing are needed for windows10.
2019-05-28 15:22:20 -05:00
Devin Neal
47a4a1f2df Add social badges README 2019-05-28 01:39:06 -07:00
Devin Neal
486b28e4d2 Merge pull request #557 from Kolloom/master
Add installation documentation for ubuntu
2019-05-28 01:09:00 -07:00
Devin Neal
2ce766b3bf Update docs badge 2019-05-28 01:01:48 -07:00
Devin Neal
2deeeab509 Merge pull request #558 from eulertour/master
Testing a PR making a change to the docs
2019-05-28 00:44:07 -07:00
Devin Neal
e93ef301ce test pr on docs 2019-05-28 00:39:00 -07:00
Devin Neal
c7b5aa6e05 change docs to upstream 2019-05-28 00:36:13 -07:00
Devin Neal
0edb4edfd0 rebase onto upstream 2019-05-28 00:33:05 -07:00
Devin Neal
78448b4388 make build script executable 2019-05-28 00:21:10 -07:00
Devin Neal
969bcf4154 refactor and test doc deployment 2019-05-28 00:13:27 -07:00
Devin Neal
f3f0e3ba03 third attempt at deploying docs 2019-05-27 23:58:05 -07:00
Devin Neal
aa2734477a second attempt at deploying docs 2019-05-27 23:39:00 -07:00
Devin Neal
d0eb2a0ce8 Merge branch 'master' of github.com:3b1b/manim 2019-05-27 23:16:40 -07:00
Devin Neal
a4c8302c55 first attempt at deploying docs 2019-05-27 23:08:43 -07:00
Kolloom
5d4897bd50 Add installation documentation for ubuntu 2019-05-28 00:48:25 -05:00
Devin Neal
49d8276033 Merge pull request #555 from yoshiask/patch-3
Update pycairo requirement
2019-05-27 19:55:58 -07:00
Grant Sanderson
b7fcc68b55 Merge pull request #556 from 3b1b/diffyq
Diffyq
2019-05-27 19:51:21 -07:00
Grant Sanderson
4096fc28b8 Merge branch 'master' of github.com:3b1b/manim into diffyq 2019-05-27 19:49:00 -07:00
Grant Sanderson
e6eb4dd94f Beginning heat equation solution animations 2019-05-27 19:48:48 -07:00
Grant Sanderson
29424eb6b3 Added simple midpoint function 2019-05-27 19:48:33 -07:00
Grant Sanderson
d1e3e5ed20 Formatting correction 2019-05-27 19:48:14 -07:00
Grant Sanderson
828c3dcd7a Added c2p and p2c abbreviations to Axes 2019-05-27 19:47:57 -07:00
Yoshi Askharoun
17558a4bd5 Update pycairo requirement
Many issues, such as #535, stem from the odd installation procedure of cairo and pycairo on Windows. By changing the pycairo requirement in requirements.txt, pip will no longer attempt to uninstall the working version. This should have no effect on the functionality of manim, though new installations are likely to default to pycairo 1.18.0 now.
2019-05-27 19:00:48 -07:00
Devin Neal
304822fb8c Merge branch 'master' of github.com:3b1b/manim 2019-05-27 16:14:45 -07:00
Devin Neal
9fa350d906 clean up docs 2019-05-27 16:14:20 -07:00
kyarik
c62ba223b6 Fixed typo in the docs about page (#553) 2019-05-27 15:52:41 -07:00
Devin Neal
4447bbd016 add docs 2019-05-26 00:35:45 -07:00
Grant Sanderson
98696a29f8 Merge pull request #542 from kyarik/patch-1
Fixed typo in class name in example_scenes.py
2019-05-24 15:15:12 -07:00
Grant Sanderson
84514a1f6f Merge pull request #543 from kyarik/patch-2
Mentioned -r argument in example_scenes comment
2019-05-24 15:14:53 -07:00
Grant Sanderson
c21ea85ec9 Merge pull request #544 from kyarik/patch-3
Improved the "Using manim" section in the README
2019-05-24 15:14:21 -07:00
Grant Sanderson
cec7872f48 Merge pull request #547 from 3b1b/diffyq
Diffyq
2019-05-24 15:13:40 -07:00
Grant Sanderson
ce866e8222 Removed duplicate name_animations.py 2019-05-24 15:09:53 -07:00
Grant Sanderson
91b3abae4a Change big_ol_pile_of_imports to manimlib.imports 2019-05-24 15:08:02 -07:00
Grant Sanderson
6e6dd260da Merge branch 'master' of github.com:3b1b/manim into diffyq 2019-05-24 15:06:59 -07:00
Grant Sanderson
2d3493c3d5 Fourier series name animations 2019-05-24 15:06:52 -07:00
Grant Sanderson
c4449fdfb8 Small tweaks to Fourier series animations 2019-05-24 15:06:10 -07:00
Grant Sanderson
bf8f517b49 Changed SceneFileWriter.get_default_file_name 2019-05-24 15:05:20 -07:00
Grant Sanderson
b9822db5bf Changed thumbnail of nn1 2019-05-24 15:04:45 -07:00
kyarik
2ebcec4bbe Improved the "Using manim" section in the README
Improved the wording and presentation of the "Using manim" section in the README file.
2019-05-22 11:00:57 +02:00
kyarik
68961251a5 Mentioned -r argument in example_scenes comment
It is a good option to mention because the current default for a high quality 60fps video is 2560x1440.
So, if someone wants a 1920x1080 video, they should pass -r 1080.
2019-05-21 11:54:56 +02:00
kyarik
0f5cc33002 Fixed typo in class name in example_scenes.py
Changed UdatersExample to UpdatersExample.
2019-05-21 11:44:30 +02:00
Devin Neal
e59178dae7 copy docs 2019-05-12 23:40:41 -07:00
94 changed files with 7750 additions and 287 deletions

3
.gitignore vendored
View File

@@ -12,6 +12,7 @@ ben_cairo_test.py
.floo
.flooignore
.vscode
.vs
*.xml
*.iml
media
@@ -23,4 +24,4 @@ dist/
manim.egg-info/
primes.py
/media_dir.txt
/media_dir.txt

View File

@@ -1,9 +1,12 @@
language: python
sudo: required # required for Python 3.7 (travis-ci/travis-ci#9069)
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 +19,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

@@ -3,12 +3,17 @@ RUN apt-get update \
&& apt-get install -qqy --no-install-recommends \
apt-utils \
ffmpeg \
texlive-latex-base \
texlive-full \
texlive-fonts-extra \
sox \
libcairo2-dev \
texlive \
texlive-fonts-extra \
texlive-latex-extra \
texlive-latex-recommended \
texlive-science \
tipa \
&& rm -rf /var/lib/apt/lists/*
COPY requirements.txt requirements.txt
RUN python -m pip install -r requirements.txt && rm requirements.txt
COPY . /manim
RUN cd /manim \
&& python setup.py sdist \
&& python -m pip install dist/manimlib*
ENTRYPOINT ["/bin/bash"]

View File

@@ -1,8 +1,10 @@
<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)
[![Documentation](https://img.shields.io/badge/docs-EulerTour-blue.svg)](https://www.eulertour.com/learn/manim/)
[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](http://choosealicense.com/licenses/mit/)
[![Manim Subreddit](https://img.shields.io/reddit/subreddit-subscribers/manim.svg?color=ff4301&label=reddit)](https://www.reddit.com/r/manim/)
[![Manim Discord](https://img.shields.io/discord/581738731934056449.svg?label=discord)](https://discord.gg/mMRrZQW)
Manim is an animation engine for explanatory math videos. It's used to create precise animations programmatically, as seen in the videos at [3Blue1Brown](https://www.3blue1brown.com/).
@@ -32,7 +34,7 @@ If you want to hack on manimlib itself, clone this repository and in that direct
python3 -m pip install -r requirements.txt
# Try it out
python3 -m manim example_scenes.py SquareToCircle -pl
python3 ./manim.py example_scenes.py SquareToCircle -pl
```
### Directly (Windows)
@@ -72,14 +74,29 @@ python3 -m manim example_scenes.py SquareToCircle -pl
### Using Docker
Since it's a bit tricky to get all the dependencies set up just right, there is a Dockerfile and Compose file provided in this repo as well as [a premade image on Docker Hub](https://hub.docker.com/r/eulertour/manim/tags/). The Dockerfile contains instructions on how to build a manim image, while the Compose file contains instructions on how to run the image.
The image does not contain a copy of the repo. This is intentional, as it allows you to either bind mount a repo that you've cloned locally or clone any fork/branch you want. In order to do this with the Compose file, you must set the `MANIM_PATH` environment variable to the absolute path to the manim repo.
The prebuilt container image has manin repository included.
`INPUT_PATH` is where the container looks for scene files. You must set the `INPUT_PATH`
environment variable to the absolute path containing your scene file and the
`OUTPUT_PATH` environment variable to the directory where you want media to be written.
1. [Install Docker](https://www.docker.com/products/overview)
1. [Install Docker](https://docs.docker.com)
2. [Install Docker Compose](https://docs.docker.com/compose/install/)
3. Render an animation
```sh
MANIM_PATH=/absolute/path/to/manim/repo docker-compose run manim example_scenes.py SquareToCircle -l
INPUT_PATH=/path/to/dir/containing/source/code \
OUTPUT_PATH=/path/to/output/ \
docker-compose run manim example_scenes.py SquareToCircle -l
```
The command needs to be run as root if your username is not in the docker group.
You can replace `example.scenes.py` with any relative path from your `INPUT_PATH`.
<img src=./manim_docker_diagram.png/>
After running the output will say files ready at `/tmp/output/`, which refers to path inside the container. Your OUTPUT_PATH is bind mounted to this `/tmp/output` so any changes made by the container to `/tmp/output` will be mirrored on your OUTPUT_PATH. `/media/` will be created in `OUTPUT_PATH`.
`-p` won't work as manim would look for video player in the container system, which it does not have.
The first time you execute the above command, Docker will pull the image from Docker Hub and cache it. Any subsequent runs until the image is evicted will use the cached image.
Note that the image doesn't have any development tools installed and can't preview animations. Its purpose is building and testing only.
@@ -88,15 +105,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,70 @@
from active_projects.ode.part3.staging import *
from active_projects.ode.part3.temperature_graphs import *
from active_projects.ode.part3.pi_creature_scenes import *
from active_projects.ode.part3.wordy_scenes import *
from active_projects.ode.part3.discrete_case import *
OUTPUT_DIRECTORY = "ode/part3"
SCENES_IN_ORDER = [
LastChapterWrapper,
ThreeConstraints,
OceanOfPossibilities,
ThreeMainObservations,
SimpleCosExpGraph,
AddMultipleSolutions,
FourierSeriesIllustraiton,
BreakDownAFunction,
SineCurveIsUnrealistic,
AnalyzeSineCurve,
EquationAboveSineAnalysis,
ExponentialDecay,
InvestmentGrowth,
GrowingPileOfMoney,
CarbonDecayCurve,
CarbonDecayingInMammoth,
SineWaveScaledByExp,
ShowSinExpDerivatives,
IfOnly,
BoundaryConditionInterlude,
BoundaryConditionReference,
GiantCross,
SimulateRealSineCurve,
DerivativesOfLinearFunction,
StraightLine3DGraph,
SimulateLinearGraph,
EmphasizeBoundaryPoints,
ShowNewRuleAtDiscreteBoundary,
DiscreteEvolutionPoint25,
DiscreteEvolutionPoint1,
FlatEdgesForDiscreteEvolution,
FlatEdgesForDiscreteEvolutionTinySteps,
FlatEdgesContinuousEvolution,
FlatAtBoundaryWords,
SlopeToHeatFlow,
CloserLookAtStraightLine,
WriteOutBoundaryCondition,
SoWeGotNowhere,
ManipulateSinExpSurface,
HeatEquationFrame,
ShowFreq1CosExpDecay,
ShowFreq2CosExpDecay,
ShowFreq4CosExpDecay,
CompareFreqDecays1to2,
CompareFreqDecays1to4,
CompareFreqDecays2to4,
ShowHarmonics,
ShowHarmonicSurfaces,
# SimpleCosExpGraph,
# AddMultipleSolutions,
# IveHeardOfThis,
# FourierSeriesOfLineIllustration,
# InFouriersShoes,
]
PART_4_SCENES = [
FourierSeriesIllustraiton,
FourierNameIntro,
CircleAnimationOfF,
]

View File

@@ -0,0 +1,18 @@
from active_projects.ode.part4.staging import *
from active_projects.ode.part4.fourier_series_scenes import *
from active_projects.ode.part4.pi_creature_scenes import *
OUTPUT_DIRECTORY = "ode/part4"
SCENES_IN_ORDER = [
ComplexFourierSeriesExample,
ComplexFourierSeriesExampleEnd,
FourierSeriesExampleWithRectForZoom,
ZoomedInFourierSeriesExample,
RelationToOtherVideos,
WhyWouldYouCare,
# Oldies
# FourierSeriesIllustraiton,
# FourierNameIntro,
# CircleAnimationOfF,
]

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

@@ -4,7 +4,7 @@ from manimlib.imports import *
class FourierCirclesScene(Scene):
CONFIG = {
"n_circles": 10,
"n_vectors": 10,
"big_radius": 2,
"colors": [
BLUE_D,
@@ -15,14 +15,16 @@ class FourierCirclesScene(Scene):
"circle_style": {
"stroke_width": 2,
},
"arrow_config": {
"vector_config": {
"buff": 0,
"max_tip_length_to_length_ratio": 0.35,
"tip_length": 0.15,
"max_stroke_width_to_length_ratio": 10,
"stroke_width": 2,
},
"use_vectors": True,
"circle_config": {
"stroke_width": 1,
},
"base_frequency": 1,
"slow_factor": 0.25,
"center_point": ORIGIN,
@@ -33,26 +35,35 @@ class FourierCirclesScene(Scene):
self.slow_factor_tracker = ValueTracker(
self.slow_factor
)
self.vector_clock = ValueTracker(0)
self.vector_clock.add_updater(
lambda m, dt: m.increment_value(
self.get_slow_factor() * dt
)
)
self.add(self.vector_clock)
def get_slow_factor(self):
return self.slow_factor_tracker.get_value()
def get_vector_time(self):
return self.vector_clock.get_value()
#
def get_freqs(self):
n = self.n_circles
n = self.n_vectors
all_freqs = list(range(n // 2, -n // 2, -1))
all_freqs.sort(key=abs)
return all_freqs
def get_coefficients(self):
return [complex(0) for x in range(self.n_circles)]
return [complex(0) for x in range(self.n_vectors)]
def get_color_iterator(self):
return it.cycle(self.colors)
def get_circles(self, freqs=None, coefficients=None):
circles = VGroup()
color_iterator = self.get_color_iterator()
def get_rotating_vectors(self, freqs=None, coefficients=None):
vectors = VGroup()
self.center_tracker = VectorizedPoint(self.center_point)
if freqs is None:
@@ -60,80 +71,74 @@ class FourierCirclesScene(Scene):
if coefficients is None:
coefficients = self.get_coefficients()
last_circle = None
last_vector = None
for freq, coefficient in zip(freqs, coefficients):
if last_circle:
center_func = last_circle.get_start
if last_vector:
center_func = last_vector.get_end
else:
center_func = self.center_tracker.get_location
circle = self.get_circle(
vector = self.get_rotating_vector(
coefficient=coefficient,
freq=freq,
color=next(color_iterator),
center_func=center_func,
)
circles.add(circle)
last_circle = circle
return circles
vectors.add(vector)
last_vector = vector
return vectors
def get_circle(self, coefficient, freq, color, center_func):
radius = abs(coefficient)
phase = np.log(coefficient).imag
circle = Circle(
radius=radius,
color=color,
**self.circle_style,
)
line_points = (
circle.get_center(),
circle.get_start(),
)
if self.use_vectors:
circle.radial_line = Arrow(
*line_points,
**self.arrow_config,
)
def get_rotating_vector(self, coefficient, freq, center_func):
vector = Vector(RIGHT, **self.vector_config)
vector.scale(abs(coefficient))
if abs(coefficient) == 0:
phase = 0
else:
circle.radial_line = Line(
*line_points,
color=WHITE,
**self.circle_style,
phase = np.log(coefficient).imag
vector.rotate(phase, about_point=ORIGIN)
vector.freq = freq
vector.phase = phase
vector.coefficient = coefficient
vector.center_func = center_func
vector.add_updater(self.update_vector)
return vector
def update_vector(self, vector, dt):
time = self.get_vector_time()
vector.set_angle(
vector.phase + time * vector.freq * TAU
)
vector.shift(
vector.center_func() - vector.get_start()
)
return vector
def get_circles(self, vectors):
return VGroup(*[
self.get_circle(
vector,
color=color
)
circle.add(circle.radial_line)
circle.freq = freq
circle.phase = phase
circle.rotate(phase)
circle.coefficient = coefficient
circle.center_func = center_func
for vector, color in zip(
vectors,
self.get_color_iterator()
)
])
def get_circle(self, vector, color=BLUE):
circle = Circle(color=color, **self.circle_config)
circle.center_func = vector.get_start
circle.radius_func = vector.get_length
circle.add_updater(self.update_circle)
return circle
def update_circle(self, circle, dt):
circle.rotate(
self.get_slow_factor() * circle.freq * dt * TAU
)
def update_circle(self, circle):
circle.set_width(2 * circle.radius_func())
circle.move_to(circle.center_func())
return circle
def get_rotating_vectors(self, circles):
return VGroup(*[
self.get_rotating_vector(circle)
for circle in circles
])
def get_rotating_vector(self, circle):
vector = Vector(RIGHT, color=WHITE)
vector.add_updater(lambda v, dt: v.put_start_and_end_on(
circle.get_center(),
circle.get_start(),
))
circle.vector = vector
return vector
def get_circle_end_path(self, circles, color=YELLOW):
coefs = [c.coefficient for c in circles]
freqs = [c.freq for c in circles]
center = circles[0].get_center()
def get_vector_sum_path(self, vectors, color=YELLOW):
coefs = [v.coefficient for v in vectors]
freqs = [v.freq for v in vectors]
center = vectors[0].get_start()
path = ParametricFunction(
lambda t: center + reduce(op.add, [
@@ -150,13 +155,14 @@ class FourierCirclesScene(Scene):
return path
# TODO, this should be a general animated mobect
def get_drawn_path(self, circles, stroke_width=2, **kwargs):
path = self.get_circle_end_path(circles, **kwargs)
def get_drawn_path(self, vectors, stroke_width=2, **kwargs):
path = self.get_vector_sum_path(vectors, **kwargs)
broken_path = CurvesAsSubmobjects(path)
broken_path.curr_time = 0
def update_path(path, dt):
alpha = path.curr_time * self.get_slow_factor()
# alpha = path.curr_time * self.get_slow_factor()
alpha = self.get_vector_time()
n_curves = len(path)
for a, sp in zip(np.linspace(0, 1, n_curves), path):
b = alpha - a
@@ -173,12 +179,12 @@ class FourierCirclesScene(Scene):
return broken_path
def get_y_component_wave(self,
circles,
vectors,
left_x=1,
color=PINK,
n_copies=2,
right_shift_rate=5):
path = self.get_circle_end_path(circles)
path = self.get_vector_sum_path(vectors)
wave = ParametricFunction(
lambda t: op.add(
right_shift_rate * t * LEFT,
@@ -216,15 +222,16 @@ class FourierCirclesScene(Scene):
return VGroup(wave, wave_copies)
def get_wave_y_line(self, circles, wave):
def get_wave_y_line(self, vectors, wave):
return DashedLine(
circles[-1].get_start(),
vectors[-1].get_end(),
wave[0].get_end(),
stroke_width=1,
dash_length=DEFAULT_DASH_LENGTH * 0.5,
)
# Computing Fourier series
# i.e. where all the math happens
def get_coefficients_of_path(self, path, n_samples=10000, freqs=None):
if freqs is None:
freqs = self.get_freqs()
@@ -250,7 +257,7 @@ class FourierCirclesScene(Scene):
class FourierSeriesIntroBackground4(FourierCirclesScene):
CONFIG = {
"n_circles": 4,
"n_vectors": 4,
"center_point": 4 * LEFT,
"run_time": 30,
"big_radius": 1.5,
@@ -271,7 +278,7 @@ class FourierSeriesIntroBackground4(FourierCirclesScene):
self.wait(self.run_time)
def get_ks(self):
return np.arange(1, 2 * self.n_circles + 1, 2)
return np.arange(1, 2 * self.n_vectors + 1, 2)
def get_freqs(self):
return self.base_frequency * self.get_ks()
@@ -282,52 +289,72 @@ class FourierSeriesIntroBackground4(FourierCirclesScene):
class FourierSeriesIntroBackground8(FourierSeriesIntroBackground4):
CONFIG = {
"n_circles": 8,
"n_vectors": 8,
}
class FourierSeriesIntroBackground12(FourierSeriesIntroBackground4):
CONFIG = {
"n_circles": 12,
"n_vectors": 12,
}
class FourierSeriesIntroBackground20(FourierSeriesIntroBackground4):
CONFIG = {
"n_circles": 20,
"n_vectors": 20,
}
class FourierOfPiSymbol(FourierCirclesScene):
CONFIG = {
"n_circles": 50,
"n_vectors": 51,
"center_point": ORIGIN,
"slow_factor": 0.1,
"run_time": 30,
"n_cycles": 1,
"tex": "\\pi",
"start_drawn": False,
"max_circle_stroke_width": 1,
}
def construct(self):
self.add_vectors_circles_path()
for n in range(self.n_cycles):
self.run_one_cycle()
def add_vectors_circles_path(self):
path = self.get_path()
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,
))
# approx_path = self.get_circle_end_path(circles)
drawn_path = self.get_drawn_path(circles)
vectors = self.get_rotating_vectors(coefficients=coefs)
circles = self.get_circles(vectors)
self.set_decreasing_stroke_widths(circles)
# approx_path = self.get_vector_sum_path(circles)
drawn_path = self.get_drawn_path(vectors)
if self.start_drawn:
drawn_path.curr_time = 1 / self.slow_factor
self.vector_clock.increment_value(1)
self.add(path)
self.add(vectors)
self.add(circles)
self.add(drawn_path)
self.wait(self.run_time)
self.vectors = vectors
self.circles = circles
self.path = path
self.drawn_path = drawn_path
def run_one_cycle(self):
time = 1 / self.slow_factor
self.wait(time)
def set_decreasing_stroke_widths(self, circles):
mcsw = self.max_circle_stroke_width
for k, circle in zip(it.count(1), circles):
circle.set_stroke(width=max(
# mcsw / np.sqrt(k),
mcsw / k,
mcsw,
))
return circles
def get_path(self):
tex_mob = TexMobject(self.tex)
@@ -338,16 +365,61 @@ class FourierOfPiSymbol(FourierCirclesScene):
return path
class FourierOfName(FourierOfPiSymbol):
CONFIG = {
"n_vectors": 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,
"n_vectors": 5,
"run_time": 10,
}
class FourierOfTrebleClef(FourierOfPiSymbol):
CONFIG = {
"n_circles": 100,
"n_vectors": 101,
"run_time": 10,
"start_drawn": True,
"file_name": "TrebleClef",
@@ -371,7 +443,7 @@ class FourierOfIP(FourierOfTrebleClef):
CONFIG = {
"file_name": "IP_logo2",
"height": 6,
"n_circles": 100,
"n_vectors": 100,
}
# def construct(self):
@@ -403,7 +475,7 @@ class FourierOfEighthNote(FourierOfTrebleClef):
class FourierOfN(FourierOfTrebleClef):
CONFIG = {
"height": 6,
"n_circles": 1000,
"n_vectors": 1000,
}
def get_shape(self):
@@ -413,7 +485,7 @@ class FourierOfN(FourierOfTrebleClef):
class FourierNailAndGear(FourierOfTrebleClef):
CONFIG = {
"height": 6,
"n_circles": 200,
"n_vectors": 200,
"run_time": 100,
"slow_factor": 0.01,
"parametric_function_step_size": 0.0001,
@@ -431,7 +503,7 @@ class FourierNailAndGear(FourierOfTrebleClef):
class FourierBatman(FourierOfTrebleClef):
CONFIG = {
"height": 4,
"n_circles": 100,
"n_vectors": 100,
"run_time": 10,
"arrow_config": {
"tip_length": 0.1,
@@ -447,7 +519,7 @@ class FourierBatman(FourierOfTrebleClef):
class FourierHeart(FourierOfTrebleClef):
CONFIG = {
"height": 4,
"n_circles": 100,
"n_vectors": 100,
"run_time": 10,
"arrow_config": {
"tip_length": 0.1,
@@ -469,7 +541,7 @@ class FourierHeart(FourierOfTrebleClef):
class FourierNDQ(FourierOfTrebleClef):
CONFIG = {
"height": 4,
"n_circles": 1000,
"n_vectors": 1000,
"run_time": 10,
"arrow_config": {
"tip_length": 0.1,
@@ -479,7 +551,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
@@ -487,7 +559,7 @@ class FourierNDQ(FourierOfTrebleClef):
class FourierGoogleG(FourierOfTrebleClef):
CONFIG = {
"n_circles": 10,
"n_vectors": 10,
"height": 5,
"g_colors": [
"#4285F4",
@@ -522,7 +594,7 @@ class FourierGoogleG(FourierOfTrebleClef):
class ExplainCircleAnimations(FourierCirclesScene):
CONFIG = {
"n_circles": 100,
"n_vectors": 100,
"center_point": 2 * DOWN,
"n_top_circles": 9,
"path_height": 3,

View File

@@ -185,6 +185,7 @@ class BringTwoRodsTogether(Scene):
y_label = axes.get_y_axis_label("\\text{Temperature}")
y_label.to_edge(UP)
axes.y_axis.label = y_label
axes.y_axis.add(y_label)
axes.y_axis.add_numbers(
*range(20, 100, 20)
@@ -329,7 +330,7 @@ class BringTwoRodsTogether(Scene):
else:
return 10
def update_graph(self, graph, dt, alpha=None, n_mini_steps=100):
def update_graph(self, graph, dt, alpha=None, n_mini_steps=500):
if alpha is None:
alpha = self.alpha
points = np.append(
@@ -349,16 +350,16 @@ class BringTwoRodsTogether(Scene):
if (0 < i < len(points) - 1):
second_deriv = d2y / (dx**2)
else:
second_deriv = 0.5 * d2y / dx
second_deriv = 0
second_deriv = 2 * d2y / (dx**2)
# second_deriv = 0
y_change[i] = alpha * second_deriv * dt / n_mini_steps
# y_change[0] = y_change[1]
# y_change[-1] = y_change[-2]
y_change[0] = 0
y_change[-1] = 0
y_change -= np.mean(y_change)
# y_change[0] = 0
# y_change[-1] = 0
# y_change -= np.mean(y_change)
points[:, 1] += y_change
graph.set_points_smoothly(points)
return graph
@@ -374,8 +375,17 @@ class BringTwoRodsTogether(Scene):
)[1]
for alt_x in (x - dx, x, x + dx)
]
d2y = ry - 2 * y + ly
return d2y / (dx**2)
# At the boundary, don't return the second deriv,
# but instead something matching the Neumann
# boundary condition.
if x == x_max:
return (ly - y) / dx
elif x == x_min:
return (ry - y) / dx
else:
d2y = ry - 2 * y + ly
return d2y / (dx**2)
def get_rod(self, x_min, x_max, n_pieces=None):
if n_pieces is None:
@@ -407,7 +417,7 @@ class BringTwoRodsTogether(Scene):
self.rod_point_to_color(piece.get_right()),
])
def rod_point_to_color(self, point):
def rod_point_to_graph_y(self, point):
axes = self.axes
x = axes.x_axis.p2n(point)
@@ -417,11 +427,16 @@ class BringTwoRodsTogether(Scene):
self.graph_x_max,
x,
)
y = axes.y_axis.p2n(
return axes.y_axis.p2n(
graph.point_from_proportion(alpha)
)
return temperature_to_color(
(y - 45) / 45
def y_to_color(self, y):
return temperature_to_color((y - 45) / 45)
def rod_point_to_color(self, point):
return self.y_to_color(
self.rod_point_to_graph_y(point)
)
@@ -450,6 +465,7 @@ class ShowEvolvingTempGraphWithArrows(BringTwoRodsTogether):
self.add_clock()
self.add_rod()
self.add_arrows()
self.initialize_updaters()
self.let_play()
def add_axes(self):
@@ -467,7 +483,10 @@ class ShowEvolvingTempGraphWithArrows(BringTwoRodsTogether):
self.time_label.next_to(self.clock, DOWN)
def add_rod(self):
rod = self.rod = self.get_rod(0, 10)
rod = self.rod = self.get_rod(
self.graph_x_min,
self.graph_x_max,
)
self.add(rod)
def add_arrows(self):
@@ -504,24 +523,25 @@ class ShowEvolvingTempGraphWithArrows(BringTwoRodsTogether):
self.add(arrows)
self.arrows = arrows
def initialize_updaters(self):
if hasattr(self, "graph"):
self.graph.add_updater(self.update_graph)
if hasattr(self, "rod"):
self.rod.add_updater(self.color_rod_by_graph)
if hasattr(self, "time_label"):
self.time_label.add_updater(
lambda d, dt: d.increment_value(dt)
)
def let_play(self):
graph = self.graph
rod = self.rod
clock = self.clock
time_label = self.time_label
self.run_clock(self.wait_time)
graph.add_updater(self.update_graph)
time_label.add_updater(
lambda d, dt: d.increment_value(dt)
)
rod.add_updater(self.color_rod_by_graph)
# return
def run_clock(self, time):
self.play(
ClockPassesTime(
clock,
run_time=self.wait_time,
hours_passed=self.wait_time,
self.clock,
run_time=time,
hours_passed=time,
),
)

View File

@@ -0,0 +1,303 @@
from manimlib.imports import *
from active_projects.ode.part2.heat_equation import *
class ShowNewRuleAtDiscreteBoundary(DiscreteSetup):
CONFIG = {
"axes_config": {
"x_min": 0,
"stroke_width": 1,
"x_axis_config": {
"include_tip": False,
},
},
"freq_amplitude_pairs": [
(1, 0.5),
(2, 1),
(3, 0.5),
(4, 0.3),
],
"v_line_class": DashedLine,
"v_line_config": {
},
"step_size": 1,
"wait_time": 15,
"alpha": 0.25,
}
def construct(self):
self.add_axes()
self.set_points()
self.show_boundary_point_influenced_by_neighbor()
self.add_clock()
self.let_evolve()
def set_points(self):
axes = self.axes
for mob in axes.family_members_with_points():
if isinstance(mob, Line):
mob.set_stroke(width=1)
step_size = self.step_size
xs = np.arange(
axes.x_min,
axes.x_max + step_size,
step_size
)
dots = self.dots = self.get_dots(axes, xs)
self.v_lines = self.get_v_lines(dots)
self.rod_pieces = self.get_rod_pieces(dots)
# rod_pieces
self.add(self.dots)
self.add(self.v_lines)
self.add(self.rod_pieces)
def show_boundary_point_influenced_by_neighbor(self):
dots = self.dots
ld = dots[0]
ld_in = dots[1]
rd = dots[-1]
rd_in = dots[-2]
v_len = 0.75
l_arrow = Vector(v_len * LEFT)
l_arrow.move_to(ld.get_left(), RIGHT)
r_arrow = Vector(v_len * RIGHT)
r_arrow.move_to(rd.get_right(), LEFT)
arrows = VGroup(l_arrow, r_arrow)
q_marks = VGroup(*[
TexMobject("?").scale(1.5).next_to(
arrow, arrow.get_vector()
)
for arrow in arrows
])
arrows.set_color(YELLOW)
q_marks.set_color(YELLOW)
blocking_rects = VGroup(*[
BackgroundRectangle(VGroup(
*dots[i:-i],
*self.rod_pieces[i:-i]
))
for i in [1, 2]
])
for rect in blocking_rects:
rect.stretch(1.1, dim=1, about_edge=UP)
self.play(FadeIn(blocking_rects[0]))
self.play(
LaggedStartMap(ShowCreation, arrows),
LaggedStart(*[
FadeInFrom(q_mark, -arrow.get_vector())
for q_mark, arrow in zip(q_marks, arrows)
]),
run_time=1.5
)
self.wait()
# Point to inward neighbor
new_arrows = VGroup(*[
Arrow(
d1.get_center(),
VGroup(d1, d2).get_center(),
buff=0,
).match_style(l_arrow)
for d1, d2 in [(ld, ld_in), (rd, rd_in)]
])
new_arrows.match_style(arrows)
l_brace = Brace(VGroup(ld, ld_in), DOWN)
r_brace = Brace(VGroup(rd, rd_in), DOWN)
braces = VGroup(l_brace, r_brace)
for brace in braces:
brace.align_to(
self.axes.x_axis.get_center(), UP
)
brace.shift(SMALL_BUFF * DOWN)
brace.add(brace.get_tex("\\Delta x"))
self.play(
ReplacementTransform(arrows, new_arrows),
FadeOut(q_marks),
ReplacementTransform(*blocking_rects)
)
self.wait()
self.play(FadeInFrom(braces, UP))
self.wait()
self.play(
FadeOut(new_arrows),
FadeOut(blocking_rects[1]),
FadeOut(braces),
)
def add_clock(self):
super().add_clock()
self.time_label.add_updater(
lambda d, dt: d.increment_value(dt)
)
VGroup(
self.clock,
self.time_label
).shift(2 * LEFT)
def let_evolve(self):
dots = self.dots
dots.add_updater(self.update_dots)
wait_time = self.wait_time
self.play(
ClockPassesTime(
self.clock,
run_time=wait_time,
hours_passed=wait_time,
),
)
#
def get_dots(self, axes, xs):
dots = VGroup(*[
Dot(axes.c2p(x, self.temp_func(x, 0)))
for x in xs
])
max_width = 0.8 * self.step_size
for dot in dots:
dot.add_updater(self.update_dot_color)
if dot.get_width() > max_width:
dot.set_width(max_width)
return dots
def get_v_lines(self, dots):
return always_redraw(lambda: VGroup(*[
self.get_v_line(dot)
for dot in dots
]))
def get_v_line(self, dot):
x_axis = self.axes.x_axis
bottom = dot.get_bottom()
x = x_axis.p2n(bottom)
proj_point = x_axis.n2p(x)
return self.v_line_class(
proj_point, bottom,
**self.v_line_config,
)
def get_rod_pieces(self, dots):
axis = self.axes.x_axis
factor = 1 - np.exp(-(0.8 / self.step_size)**2)
width = factor * self.step_size
pieces = VGroup()
for dot in dots:
piece = Line(ORIGIN, width * RIGHT)
piece.set_stroke(width=5)
piece.move_to(dot)
piece.set_y(axis.get_center()[1])
piece.dot = dot
piece.add_updater(
lambda p: p.match_color(p.dot)
)
pieces.add(piece)
return pieces
def update_dot_color(self, dot):
y = self.axes.y_axis.p2n(dot.get_center())
dot.set_color(self.y_to_color(y))
def update_dots(self, dots, dt):
for ds in zip(dots, dots[1:], dots[2:]):
points = [d.get_center() for d in ds]
x0, x1, x2 = [p[0] for p in points]
dx = x1 - x0
y0, y1, y2 = [p[1] for p in points]
self.update_dot(
dot=ds[1],
dt=dt,
mean_diff=0.5 * (y2 - 2 * y1 + y0) / dx
)
if ds[0] is dots[0]:
self.update_dot(
dot=ds[0],
dt=dt,
mean_diff=(y1 - y0) / dx
)
elif ds[-1] is dots[-1]:
self.update_dot(
dot=ds[-1],
dt=dt,
mean_diff=(y1 - y2) / dx
)
def update_dot(self, dot, dt, mean_diff):
dot.shift(mean_diff * self.alpha * dt * UP)
class DiscreteEvolutionPoint25(ShowNewRuleAtDiscreteBoundary):
CONFIG = {
"step_size": 0.25,
"alpha": 0.5,
"wait_time": 30,
}
def construct(self):
self.add_axes()
self.set_points()
self.add_clock()
self.let_evolve()
class DiscreteEvolutionPoint1(DiscreteEvolutionPoint25):
CONFIG = {
"step_size": 0.1,
"v_line_config": {
"stroke_width": 1,
},
"wait_time": 30,
}
class FlatEdgesForDiscreteEvolution(DiscreteEvolutionPoint1):
CONFIG = {
"wait_time": 20,
"step_size": 0.1,
}
def let_evolve(self):
lines = VGroup(*[
Line(LEFT, RIGHT)
for x in range(2)
])
lines.set_width(1.5)
lines.set_stroke(WHITE, 5, opacity=0.5)
lines.add_updater(self.update_lines)
turn_animation_into_updater(
ShowCreation(lines, run_time=2)
)
self.add(lines)
super().let_evolve()
def update_lines(self, lines):
dots = self.dots
for line, dot in zip(lines, [dots[0], dots[-1]]):
line.move_to(dot)
class FlatEdgesForDiscreteEvolutionTinySteps(FlatEdgesForDiscreteEvolution):
CONFIG = {
"step_size": 0.025,
"wait_time": 10,
"v_line_class": Line,
"v_line_config": {
"stroke_opacity": 0.5,
}
}

View File

@@ -0,0 +1,175 @@
from manimlib.imports import *
from active_projects.ode.part2.wordy_scenes import *
class IveHeardOfThis(TeacherStudentsScene):
def construct(self):
point = VectorizedPoint()
point.move_to(3 * RIGHT + 2 * UP)
self.student_says(
"I've heard\\\\", "of this!",
student_index=1,
target_mode="hooray",
bubble_kwargs={
"height": 3,
"width": 3,
"direction": RIGHT,
},
run_time=1,
)
self.change_student_modes(
"thinking", "hooray", "thinking",
look_at_arg=point,
added_anims=[self.teacher.change, "happy"]
)
self.wait(3)
self.student_says(
"But who\\\\", "cares?",
student_index=1,
target_mode="maybe",
bubble_kwargs={
"direction": RIGHT,
"width": 3,
"height": 3,
},
run_time=1,
)
self.change_student_modes(
"pondering", "maybe", "pondering",
look_at_arg=point,
added_anims=[self.teacher.change, "guilty"]
)
self.wait(5)
class InFouriersShoes(PiCreatureScene, WriteHeatEquationTemplate):
def construct(self):
randy = self.pi_creature
fourier = ImageMobject("Joseph Fourier")
fourier.set_height(4)
fourier.next_to(randy, RIGHT, LARGE_BUFF)
fourier.align_to(randy, DOWN)
equation = self.get_d1_equation()
equation.next_to(fourier, UP, MED_LARGE_BUFF)
decades = list(range(1740, 2040, 20))
time_line = NumberLine(
x_min=decades[0],
x_max=decades[-1],
tick_frequency=1,
tick_size=0.05,
longer_tick_multiple=4,
unit_size=0.2,
numbers_with_elongated_ticks=decades,
numbers_to_show=decades,
decimal_number_config={
"group_with_commas": False,
},
stroke_width=2,
)
time_line.add_numbers()
time_line.move_to(ORIGIN, RIGHT)
time_line.to_edge(UP)
triangle = ArrowTip(start_angle=-90 * DEGREES)
triangle.set_height(0.25)
triangle.move_to(time_line.n2p(2019), DOWN)
triangle.set_color(WHITE)
self.play(FadeInFrom(fourier, 2 * LEFT))
self.play(randy.change, "pondering")
self.wait()
self.play(
DrawBorderThenFill(triangle, run_time=1),
FadeInFromDown(equation),
FadeIn(time_line),
)
self.play(
Animation(triangle),
ApplyMethod(
time_line.shift,
time_line.n2p(2019) - time_line.n2p(1822),
run_time=5
),
)
self.wait()
class SineCurveIsUnrealistic(TeacherStudentsScene):
def construct(self):
self.student_says(
"But that would\\\\never happen!",
student_index=1,
bubble_kwargs={
"direction": RIGHT,
"height": 3,
"width": 4,
},
target_mode="angry"
)
self.change_student_modes(
"guilty", "angry", "hesitant",
added_anims=[
self.teacher.change, "tease"
]
)
self.wait(3)
self.play(
RemovePiCreatureBubble(self.students[1]),
self.teacher.change, "raise_right_hand"
)
self.change_all_student_modes(
"pondering",
look_at_arg=3 * UP,
)
self.wait(5)
class IfOnly(TeacherStudentsScene):
def construct(self):
self.teacher_says(
"If only!",
target_mode="angry"
)
self.change_all_student_modes(
"confused",
look_at_arg=self.screen
)
self.wait(3)
class SoWeGotNowhere(TeacherStudentsScene):
def construct(self):
self.student_says(
"So we've gotten\\\\nowhere!",
target_mode="angry",
added_anims=[
self.teacher.change, "guilty"
]
)
self.change_all_student_modes("angry")
self.wait()
text = TexMobject(
"&\\text{Actually,}\\\\",
"&\\sin\\left({x}\\right)"
"e^{-\\alpha {t}}\\\\",
"&\\text{isn't far off.}",
tex_to_color_map={
"{x}": GREEN,
"{t}": YELLOW,
}
)
text.scale(0.8)
self.teacher_says(
text,
content_introduction_class=FadeIn,
bubble_kwargs={
"width": 4,
"height": 3.5,
}
)
self.change_all_student_modes(
"pondering",
look_at_arg=self.screen
)
self.wait(3)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,919 @@
from manimlib.imports import *
from active_projects.ode.part2.wordy_scenes import *
class ThreeMainObservations(Scene):
def construct(self):
fourier = ImageMobject("Joseph Fourier")
name = TextMobject("Joseph Fourier")
name.match_width(fourier)
name.next_to(fourier, DOWN, SMALL_BUFF)
fourier.add(name)
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 = Nice",
),
TextMobject(
"2)",
"Linearity"
),
TextMobject(
"3)",
"Fourier series"
),
)
# 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=2 * 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[0])
for observation in observations
], lag_ratio=0.2))
self.play(
FadeOut(fourier),
FadeOut(bubble),
)
self.wait()
for obs in observations:
self.play(FadeInFrom(obs[1], LEFT))
self.wait()
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 ThreeConstraints(WriteHeatEquationTemplate):
def construct(self):
self.cross_out_solving()
self.show_three_conditions()
def cross_out_solving(self):
equation = self.get_d1_equation()
words = TextMobject("Solve this equation")
words.to_edge(UP)
equation.next_to(words, DOWN)
cross = Cross(words)
self.add(words, equation)
self.wait()
self.play(ShowCreation(cross))
self.wait()
self.equation = equation
self.to_remove = VGroup(words, cross)
def show_three_conditions(self):
equation = self.equation
to_remove = self.to_remove
title = TexMobject(
"\\text{Constraints }"
"T({x}, {t})"
"\\text{ must satisfy:}",
**self.tex_mobject_config
)
title.to_edge(UP)
items = VGroup(
TextMobject("1)", "The PDE"),
TextMobject("2)", "Boundary condition"),
TextMobject("3)", "Initial condition"),
)
items.scale(0.7)
items.arrange(RIGHT, buff=LARGE_BUFF)
items.set_width(FRAME_WIDTH - 2)
items.next_to(title, DOWN, LARGE_BUFF)
items[1].set_color(MAROON_B)
items[2].set_color(RED)
bc_paren = TextMobject("(Explained soon)")
bc_paren.scale(0.7)
bc_paren.next_to(items[1], DOWN)
self.play(
FadeInFromDown(title),
FadeOutAndShift(to_remove, UP),
equation.scale, 0.6,
equation.next_to, items[0], DOWN,
equation.shift_onto_screen,
LaggedStartMap(FadeIn, [
items[0],
items[1][0],
items[2][0],
])
)
self.wait()
self.play(Write(items[1][1]))
bc_paren.match_y(equation)
self.play(FadeInFrom(bc_paren, UP))
self.wait(2)
self.play(Write(items[2][1]))
self.wait(2)
self.title = title
self.items = items
self.pde = equation
self.bc_paren = bc_paren
class RectAroundEquation(WriteHeatEquationTemplate):
def construct(self):
eq = self.get_d1_equation()
self.play(ShowCreationThenFadeAround(eq))
class BorderRect(Scene):
def construct(self):
rect = FullScreenFadeRectangle()
rect.set_stroke(WHITE, 3)
rect.set_fill(opacity=0)
self.add(rect)
class SeekIdealized(Scene):
def construct(self):
phrases = VGroup(*[
TextMobject(
"Seek", text, "problems",
tex_to_color_map={
"realistic": GREEN,
"{idealized}": YELLOW,
"over-idealized": YELLOW,
"general": BLUE,
}
)
for text in [
"realistic",
"{idealized}",
"over-idealized",
"general",
]
])
phrases.scale(2)
words = VGroup()
for phrase in phrases:
phrase.center()
word = phrase[1]
words.add(word)
phrase.remove(word)
arrow = Vector(DOWN)
arrow.set_stroke(WHITE, 6)
arrow.next_to(words[3], UP)
low_arrow = arrow.copy()
low_arrow.next_to(words[3], DOWN)
solutions = TextMobject("solutions")
solutions.scale(2)
solutions.move_to(phrases[3][1], UL)
models = TextMobject("models")
models.scale(2)
models.next_to(
words[0], RIGHT, buff=0.35,
aligned_edge=DOWN
)
phrases.center()
phrase = phrases[0]
self.add(phrase)
self.add(words[0])
self.wait()
words[0].save_state()
self.play(
words[0].to_edge, DOWN,
words[0].set_opacity, 0.5,
Transform(phrase, phrases[1]),
FadeInFrom(words[1], UP)
)
self.wait()
# self.play(
# words[1].move_to, words[2], RIGHT,
# FadeIn(words[2]),
# Transform(phrase, phrases[2])
# )
# self.wait()
self.play(
words[1].next_to, arrow, UP,
ShowCreation(arrow),
MaintainPositionRelativeTo(
phrase, words[1]
),
FadeInFrom(solutions, LEFT),
FadeIn(words[3]),
)
self.wait()
words[0].generate_target()
words[0].target.next_to(low_arrow, DOWN)
words[0].target.set_opacity(1)
models.shift(
words[0].target.get_center() -
words[0].saved_state.get_center()
)
self.play(
MoveToTarget(words[0]),
ShowCreation(low_arrow),
FadeInFrom(models, LEFT)
)
self.wait()
class SecondDerivativeOfSine(Scene):
def construct(self):
equation = TexMobject(
"{d^2 \\over d{x}^2}",
"\\cos\\left({x}\\right) =",
"-\\cos\\left({x}\\right)",
tex_to_color_map={
"{x}": GREEN,
}
)
self.add(equation)
class EquationAboveSineAnalysis(WriteHeatEquationTemplate):
def construct(self):
equation = self.get_d1_equation()
equation.to_edge(UP)
equation.shift(2 * LEFT)
eq_index = equation.index_of_part_by_tex("=")
lhs = equation[:eq_index]
eq = equation[eq_index]
rhs = equation[eq_index + 1:]
t_terms = equation.get_parts_by_tex("{t}")[1:]
t_terms.save_state()
zeros = VGroup(*[
TexMobject("0").replace(t, dim_to_match=1)
for t in t_terms
])
zeros.align_to(t_terms, DOWN)
new_rhs = TexMobject(
"=", "-\\alpha \\cdot {T}", "({x}, 0)",
**self.tex_mobject_config
)
# new_rhs.move_to(equation.get_right())
# new_rhs.next_to(equation, DOWN, MED_LARGE_BUFF)
# new_rhs.align_to(eq, LEFT)
new_rhs.next_to(equation, RIGHT)
new_rhs.shift(SMALL_BUFF * DOWN)
self.add(equation)
self.play(ShowCreationThenFadeAround(rhs))
self.wait()
self.play(
FadeOutAndShift(t_terms, UP),
FadeInFrom(zeros, DOWN),
)
t_terms.fade(1)
self.wait()
self.play(
# VGroup(equation, zeros).next_to,
# new_rhs, LEFT,
FadeIn(new_rhs),
)
self.wait()
self.play(
VGroup(
lhs[6:],
eq,
rhs,
new_rhs[0],
new_rhs[-3:],
zeros,
).fade, 0.5,
)
self.play(ShowCreationThenFadeAround(lhs[:6]))
self.play(ShowCreationThenFadeAround(new_rhs[1:-3]))
self.wait()
class ExpVideoWrapper(Scene):
def construct(self):
self.add(FullScreenFadeRectangle(
fill_color=DARKER_GREY,
fill_opacity=1,
))
screen = ImageMobject("eoc_chapter5_thumbnail")
screen.set_height(6)
rect = SurroundingRectangle(screen, buff=0)
rect.set_stroke(WHITE, 2)
screen.add(rect)
title = TextMobject("Need a refresher?")
title.scale(1.5)
title.to_edge(UP)
screen.next_to(title, DOWN)
screen.center()
self.play(
# FadeInFrom(title, LEFT),
FadeInFrom(screen, DOWN),
)
self.wait()
class ShowSinExpDerivatives(WriteHeatEquationTemplate):
CONFIG = {
"tex_mobject_config": {
"tex_to_color_map": {
"{0}": WHITE,
"\\partial": WHITE,
"=": WHITE,
}
}
}
def construct(self):
pde = self.get_d1_equation_without_inputs()
pde.to_edge(UP)
pde.generate_target()
new_rhs = TexMobject(
"=- \\alpha \\cdot T",
**self.tex_mobject_config,
)
new_rhs.next_to(pde, RIGHT)
new_rhs.align_to(pde.get_part_by_tex("alpha"), DOWN)
equation1 = TexMobject(
"T({x}, {0}) = \\sin\\left({x}\\right)",
**self.tex_mobject_config
)
equation2 = TexMobject(
"T({x}, {t}) = \\sin\\left({x}\\right)",
"e^{-\\alpha{t}}",
**self.tex_mobject_config
)
for eq in equation1, equation2:
eq.next_to(pde, DOWN, MED_LARGE_BUFF)
eq2_part1 = equation2[:len(equation1)]
eq2_part2 = equation2[len(equation1):]
# Rectangles
exp_rect = SurroundingRectangle(eq2_part2)
exp_rect.set_stroke(RED, 3)
sin_rect = SurroundingRectangle(
eq2_part1[-3:]
)
sin_rect.set_color(BLUE)
VGroup(pde.target, new_rhs).center().to_edge(UP)
# Show proposed solution
self.add(pde)
self.add(equation1)
self.wait()
self.play(
MoveToTarget(pde),
FadeInFrom(new_rhs, LEFT)
)
self.wait()
self.play(
ReplacementTransform(equation1, eq2_part1),
FadeIn(eq2_part2),
)
self.play(ShowCreation(exp_rect))
self.wait()
self.play(FadeOut(exp_rect))
# Take partial derivatives wrt x
q_mark = TexMobject("?")
q_mark.next_to(pde.get_part_by_tex("="), UP)
q_mark.set_color(RED)
arrow1 = Vector(3 * DOWN + 1 * RIGHT, color=WHITE)
arrow1.scale(1.2 / arrow1.get_length())
arrow1.next_to(
eq2_part2.get_corner(DL),
DOWN, MED_LARGE_BUFF
)
ddx_label1 = TexMobject(
"\\partial \\over \\partial {x}",
**self.tex_mobject_config,
)
ddx_label1.scale(0.7)
ddx_label1.next_to(
arrow1.point_from_proportion(0.8),
UR, SMALL_BUFF
)
pde_ddx = VGroup(
*pde.get_parts_by_tex("\\partial")[2:],
pde.get_parts_by_tex("\\over")[1],
pde.get_part_by_tex("{x}"),
)
pde_ddx_rect = SurroundingRectangle(pde_ddx)
pde_ddx_rect.set_color(GREEN)
eq2_part2_rect = SurroundingRectangle(eq2_part2)
dx = TexMobject(
"\\cos\\left({x}\\right)", "e^{-\\alpha {t}}",
**self.tex_mobject_config
)
ddx = TexMobject(
"-\\sin\\left({x}\\right)", "e^{-\\alpha {t}}",
**self.tex_mobject_config
)
dx.next_to(arrow1, DOWN)
dx.align_to(eq2_part2, RIGHT)
x_shift = arrow1.get_end()[0] - arrow1.get_start()[0]
x_shift *= 2
dx.shift(x_shift * RIGHT)
arrow2 = arrow1.copy()
arrow2.next_to(dx, DOWN)
arrow2.shift(MED_SMALL_BUFF * RIGHT)
dx_arrows = VGroup(arrow1, arrow2)
ddx_label2 = ddx_label1.copy()
ddx_label2.shift(
arrow2.get_center() - arrow1.get_center()
)
ddx.next_to(arrow2, DOWN)
ddx.align_to(eq2_part2, RIGHT)
ddx.shift(2 * x_shift * RIGHT)
rhs = equation2[-6:]
self.play(
FadeInFromDown(q_mark)
)
self.play(
ShowCreation(pde_ddx_rect)
)
self.wait()
self.play(
LaggedStart(
GrowArrow(arrow1),
GrowArrow(arrow2),
),
TransformFromCopy(
pde_ddx[0], ddx_label1
),
TransformFromCopy(
pde_ddx[0], ddx_label2
),
)
self.wait()
self.play(
TransformFromCopy(rhs, dx)
)
self.wait()
self.play(
FadeIn(eq2_part2_rect)
)
self.play(
Transform(
eq2_part2_rect,
SurroundingRectangle(dx[-3:])
)
)
self.play(
FadeOut(eq2_part2_rect)
)
self.wait()
self.play(
TransformFromCopy(dx, ddx)
)
self.play(
FadeIn(
SurroundingRectangle(ddx).match_style(
pde_ddx_rect
)
)
)
self.wait()
# Take partial derivative wrt t
pde_ddt = pde[:pde.index_of_part_by_tex("=") - 1]
pde_ddt_rect = SurroundingRectangle(pde_ddt)
dt_arrow = Arrow(
arrow1.get_start(),
arrow2.get_end() + RIGHT,
buff=0
)
dt_arrow.flip(UP)
dt_arrow.next_to(dx_arrows, LEFT, MED_LARGE_BUFF)
dt_label = TexMobject(
"\\partial \\over \\partial {t}",
**self.tex_mobject_config,
)
dt_label.scale(1)
dt_label.next_to(
dt_arrow.get_center(), UL,
SMALL_BUFF,
)
rhs_copy = rhs.copy()
rhs_copy.next_to(dt_arrow.get_end(), DOWN)
rhs_copy.shift(MED_LARGE_BUFF * LEFT)
rhs_copy.match_y(ddx)
minus_alpha_in_exp = rhs_copy[-3][1:].copy()
minus_alpha_in_exp.set_color(RED)
minus_alpha = TexMobject("-\\alpha")
minus_alpha.next_to(rhs_copy, LEFT)
minus_alpha.align_to(rhs_copy[0][0], DOWN)
dot = TexMobject("\\cdot")
dot.move_to(midpoint(
minus_alpha.get_right(),
rhs_copy.get_left(),
))
self.play(
TransformFromCopy(
pde_ddx_rect,
pde_ddt_rect,
)
)
self.play(
GrowArrow(dt_arrow),
TransformFromCopy(
pde_ddt,
dt_label,
)
)
self.wait()
self.play(TransformFromCopy(rhs, rhs_copy))
self.play(FadeIn(minus_alpha_in_exp))
self.play(
ApplyMethod(
minus_alpha_in_exp.replace, minus_alpha,
path_arc=TAU / 4
),
FadeIn(dot),
)
self.play(
FadeIn(minus_alpha),
FadeOut(minus_alpha_in_exp),
)
self.wait()
rhs_copy.add(minus_alpha, dot)
self.play(
FadeIn(SurroundingRectangle(rhs_copy))
)
self.wait()
#
checkmark = TexMobject("\\checkmark")
checkmark.set_color(GREEN)
checkmark.move_to(q_mark, DOWN)
self.play(
FadeInFromDown(checkmark),
FadeOutAndShift(q_mark, UP)
)
self.wait()
class DerivativesOfLinearFunction(WriteHeatEquationTemplate):
CONFIG = {
"tex_mobject_config": {
"tex_to_color_map": {
"{c}": WHITE,
}
}
}
def construct(self):
func = TexMobject(
"T({x}, {t}) = {c} \\cdot {x}",
**self.tex_mobject_config
)
dx_T = TexMobject("{c}", **self.tex_mobject_config)
ddx_T = TexMobject("0")
dt_T = TexMobject("0")
for mob in func, dx_T, ddx_T, dt_T:
mob.scale(1.5)
func.generate_target()
arrows = VGroup(*[
Vector(1.5 * RIGHT, color=WHITE)
for x in range(3)
])
dx_arrows = arrows[:2]
dt_arrow = arrows[2]
dt_arrow.rotate(-TAU / 4)
dx_group = VGroup(
func.target,
dx_arrows[0],
dx_T,
dx_arrows[1],
ddx_T,
)
dx_group.arrange(RIGHT)
for arrow, char, vect in zip(arrows, "xxt", [UP, UP, RIGHT]):
label = TexMobject(
"\\partial \\over \\partial {%s}" % char,
**self.tex_mobject_config
)
label.scale(0.7)
label.next_to(arrow.get_center(), vect)
arrow.add(label)
dt_arrow.shift(
func.target[-3:].get_bottom() + MED_SMALL_BUFF * DOWN -
dt_arrow.get_start(),
)
dt_T.next_to(dt_arrow.get_end(), DOWN)
self.play(FadeInFromDown(func))
self.wait()
self.play(
MoveToTarget(func),
LaggedStartMap(Write, dx_arrows),
run_time=1,
)
self.play(
TransformFromCopy(func[-3:], dx_T),
path_arc=-TAU / 4,
)
self.play(
TransformFromCopy(dx_T, ddx_T),
path_arc=-TAU / 4,
)
self.wait()
# dt
self.play(Write(dt_arrow))
self.play(
TransformFromCopy(func[-3:], dt_T)
)
self.wait()
class FlatAtBoundaryWords(Scene):
def construct(self):
words = self.get_bc_words()
self.play(Write(words))
self.wait()
def get_bc_words(self):
return TextMobject(
"Flat at boundary\\\\"
"for all", "${t}$", "$> 0$",
)
class WriteOutBoundaryCondition(FlatAtBoundaryWords, ThreeConstraints, MovingCameraScene):
def construct(self):
self.force_skipping()
ThreeConstraints.construct(self)
self.revert_to_original_skipping_status()
self.add_ic()
self.write_bc_words()
self.write_bc_equation()
def add_ic(self):
image = ImageMobject("temp_initial_condition_example")
image.set_width(3)
border = SurroundingRectangle(image, buff=SMALL_BUFF)
border.shift(SMALL_BUFF * UP)
border.set_stroke(WHITE, 2)
group = Group(image, border)
group.next_to(self.items[2], DOWN)
self.add(group)
def write_bc_words(self):
bc_paren = self.bc_paren
bc_words = self.get_bc_words()
bc_words.match_width(self.items[1][1])
bc_words.move_to(bc_paren, UP)
bc_words.set_color_by_tex("{t}", YELLOW)
self.play(ShowCreationThenFadeAround(
VGroup(self.items[0], self.pde)
))
self.play(
FadeOutAndShift(bc_paren, UP),
FadeInFrom(bc_words, DOWN),
)
self.wait()
self.bc_words = bc_words
def write_bc_equation(self):
bc_words = self.bc_words
equation = TexMobject(
"{\\partial {T} \\over \\partial {x}}(0, {t}) = ",
"{\\partial {T} \\over \\partial {x}}(L, {t}) = ",
"0",
**self.tex_mobject_config,
)
equation.next_to(bc_words, DOWN, MED_LARGE_BUFF)
self.play(
self.camera_frame.shift, 0.8 * DOWN,
)
self.play(FadeInFrom(equation, UP))
self.wait()
class HeatEquationFrame(WriteHeatEquationTemplate):
def construct(self):
equation = self.get_d1_equation()
equation.to_edge(UP, buff=MED_SMALL_BUFF)
ddx = equation[-11:]
dt = equation[:11]
full_rect = FullScreenFadeRectangle(
fill_color=DARK_GREY,
fill_opacity=1,
)
smaller_rect = ScreenRectangle(
height=6,
fill_color=BLACK,
fill_opacity=1,
stroke_color=WHITE,
stroke_width=2,
)
smaller_rect.next_to(equation, DOWN)
self.add(full_rect)
self.add(smaller_rect)
self.add(equation)
self.wait()
self.play(ShowCreationThenFadeAround(
ddx,
surrounding_rectangle_config={
"stroke_color": GREEN,
}
))
self.wait()
self.play(ShowCreationThenFadeAround(dt))
self.wait()
class CompareFreqDecays1to2(Scene):
CONFIG = {
"freqs": [1, 2]
}
def construct(self):
background = FullScreenFadeRectangle(
fill_color=DARKER_GREY,
fill_opacity=1,
)
screens = VGroup(*[
ScreenRectangle(
height=4,
fill_color=BLACK,
fill_opacity=1,
stroke_width=1,
stroke_color=WHITE,
)
for x in range(2)
])
screens.arrange(RIGHT)
screens.set_width(FRAME_WIDTH - 1)
formulas = VGroup(*[
self.get_formula(freq)
for freq in self.freqs
])
for formula, screen in zip(formulas, screens):
formula.next_to(screen, UP)
self.add(background)
self.add(screens)
self.add(formulas)
self.wait()
def get_formula(self, freq):
f_str = str(freq)
return TexMobject(
"\\cos\\left(%s \\cdot {x}\\right)" % f_str,
"e^{-\\alpha \\cdot %s^2 \\cdot {t}}" % f_str,
tex_to_color_map={
"{x}": GREEN,
"{t}": YELLOW,
f_str: MAROON_B,
}
)
class CompareFreqDecays1to4(CompareFreqDecays1to2):
CONFIG = {
"freqs": [1, 4],
}
class CompareFreqDecays2to4(CompareFreqDecays1to2):
CONFIG = {
"freqs": [2, 4],
}
class WorryAboutGenerality(TeacherStudentsScene, WriteHeatEquationTemplate):
def construct(self):
eq = self.get_d1_equation()
diffyq = self.get_diffyq_set()
is_in = TexMobject("\\in")
is_in.scale(2)
group = VGroup(eq, is_in, diffyq)
group.arrange(RIGHT, buff=MED_LARGE_BUFF)
group.to_edge(UP)
arrow = Vector(DOWN)
arrow.set_stroke(WHITE, 5)
arrow.next_to(eq, DOWN)
themes = TextMobject("Frequent themes")
themes.scale(1.5)
themes.next_to(arrow, DOWN)
self.play(
self.get_student_changes(
"sad", "tired", "pleading"
),
self.teacher.change, "raise_right_hand",
FadeInFromDown(eq)
)
self.play(Write(group[1:]))
self.wait(2)
self.play(
ShowCreation(arrow),
self.get_student_changes(*3 * ["pondering"]),
)
self.play(
FadeInFrom(themes, UP),
self.get_student_changes(*3 * ["thinking"]),
self.teacher.change, "happy"
)
self.wait(4)
# def get_d1_equation(self):
# result = super().get_d1_equation()
# lp, rp = parens = TexMobject("(", ")")
# parens.match_height(result)
# lp.next_to(result, LEFT, SMALL_BUFF)
# rp.next_to(result, RIGHT, SMALL_BUFF)
# result.add_to_back(lp)
# result.add(rp)
# return result
def get_diffyq_set(self):
words = TextMobject(
"Differential\\\\equations"
)
words.scale(1.5)
words.set_color(BLUE)
lb = Brace(words, LEFT)
rb = Brace(words, RIGHT)
return VGroup(lb, words, rb)

View File

@@ -0,0 +1,242 @@
from manimlib.imports import *
from active_projects.ode.part2.fourier_series import FourierOfTrebleClef
class ComplexFourierSeriesExample(FourierOfTrebleClef):
CONFIG = {
"file_name": "EighthNote",
"run_time": 10,
"n_vectors": 200,
"n_cycles": 2,
"max_circle_stroke_width": 0.75,
"drawing_height": 5,
"center_point": DOWN,
"top_row_y": 3,
"top_row_label_y": 2,
"top_row_x_spacing": 1.75,
"top_row_copy_scale_factor": 0.9,
"start_drawn": False,
}
def construct(self):
self.add_vectors_circles_path()
self.add_top_row(self.vectors, self.circles)
self.write_title()
self.highlight_vectors_one_by_one()
self.change_shape()
def write_title(self):
title = TextMobject("Complex\\\\Fourier series")
title.scale(1.5)
title.to_edge(LEFT)
title.match_y(self.path)
self.wait(5)
self.play(FadeInFromDown(title))
self.wait(2)
self.title = title
def highlight_vectors_one_by_one(self):
# Don't know why these vectors can't get copied.
# That seems like a problem that will come up again.
labels = self.top_row[-1]
next_anims = []
for vector, circle, label in zip(self.vectors, self.circles, labels):
# v_color = vector.get_color()
c_color = circle.get_color()
c_stroke_width = circle.get_stroke_width()
rect = SurroundingRectangle(label, color=PINK)
self.play(
# vector.set_color, PINK,
circle.set_stroke, RED, 3,
FadeIn(rect),
*next_anims
)
self.wait()
next_anims = [
# vector.set_color, v_color,
circle.set_stroke, c_color, c_stroke_width,
FadeOut(rect),
]
self.play(*next_anims)
def change_shape(self):
# path_mob = TexMobject("\\pi")
path_mob = SVGMobject("Nail_And_Gear")
new_path = path_mob.family_members_with_points()[0]
new_path.set_height(4)
new_path.move_to(self.path, DOWN)
new_path.shift(0.5 * UP)
new_coefs = self.get_coefficients_of_path(new_path)
new_vectors = self.get_rotating_vectors(
coefficients=new_coefs
)
new_drawn_path = self.get_drawn_path(new_vectors)
self.vector_clock.set_value(0)
self.vector_clock.suspend_updating(0)
vectors = self.vectors
anims = []
for vect, new_vect in zip(vectors, new_vectors):
new_vect.update()
new_vect.clear_updaters()
line = Line(stroke_width=0)
line.put_start_and_end_on(*vect.get_start_and_end())
anims.append(ApplyMethod(
line.put_start_and_end_on,
*new_vect.get_start_and_end()
))
vect.freq = new_vect.freq
vect.phase = new_vect.phase
vect.coefficient = new_vect.coefficient
vect.line = line
vect.add_updater(
lambda v: v.put_start_and_end_on(
*v.line.get_start_and_end()
)
)
anims += [
FadeOut(self.drawn_path)
]
self.play(*anims, run_time=3)
self.vector_clock.resume_updating()
for vect in self.vectors:
vect.remove_updater(vect.updaters[-1])
self.add(new_drawn_path)
for n in range(self.n_cycles):
self.run_one_cycle()
#
def get_path(self):
path = super().get_path()
path.set_height(self.drawing_height)
path.to_edge(DOWN)
return path
def add_top_row(self, vectors, circles, max_freq=3):
self.top_row = self.get_top_row(
vectors, circles, max_freq
)
self.add(self.top_row)
def get_top_row(self, vectors, circles, max_freq=3):
vector_copies = VGroup()
circle_copies = VGroup()
for vector, circle in zip(vectors, circles):
if vector.freq > max_freq:
break
vcopy = vector.copy()
vcopy.clear_updaters()
ccopy = circle.copy()
ccopy.clear_updaters()
ccopy.original = circle
vcopy.original = vector
vcopy.center_point = np.array([
vector.freq * self.top_row_x_spacing,
self.top_row_y,
0
])
ccopy.center_point = vcopy.center_point
vcopy.add_updater(self.update_top_row_vector_copy)
ccopy.add_updater(self.update_top_row_circle_copy)
vector_copies.add(vcopy)
circle_copies.add(ccopy)
dots = VGroup(*[
TexMobject("\\dots").next_to(
circle_copies, direction,
MED_LARGE_BUFF,
)
for direction in [LEFT, RIGHT]
])
labels = self.get_top_row_labels(vector_copies)
return VGroup(
vector_copies,
circle_copies,
dots,
labels,
)
def update_top_row_vector_copy(self, vcopy):
vcopy.become(vcopy.original)
vcopy.scale(self.top_row_copy_scale_factor)
vcopy.shift(vcopy.center_point - vcopy.get_start())
return vcopy
def update_top_row_circle_copy(self, ccopy):
ccopy.become(ccopy.original)
ccopy.scale(self.top_row_copy_scale_factor)
ccopy.move_to(ccopy.center_point)
return ccopy
def get_top_row_labels(self, vector_copies):
labels = VGroup()
for vector_copy in vector_copies:
freq = vector_copy.freq
label = Integer(freq)
label.move_to(np.array([
freq * self.top_row_x_spacing,
self.top_row_label_y,
0
]))
labels.add(label)
return labels
class ComplexFourierSeriesExampleEnd(ExternallyAnimatedScene):
pass
class FourierSeriesExampleWithRectForZoom(ComplexFourierSeriesExample):
CONFIG = {
"n_vectors": 100,
"slow_factor": 0.01,
"rect_scale_factor": 0.15,
"parametric_function_step_size": 0.0001,
"start_drawn": True,
}
def construct(self):
self.add_vectors_circles_path()
self.circles.set_stroke(opacity=0.5)
rect = self.get_rect()
rect.set_height(self.rect_scale_factor * FRAME_HEIGHT)
rect.add_updater(lambda m: m.move_to(
center_of_mass([
v.get_end()
for v in self.vectors
])
))
self.add(rect)
self.run_one_cycle()
def get_rect(self):
return ScreenRectangle(
color=WHITE,
stroke_width=2,
)
class ZoomedInFourierSeriesExample(FourierSeriesExampleWithRectForZoom, MovingCameraScene):
CONFIG = {
"vector_config": {
"max_tip_length_to_length_ratio": 0.15,
"tip_length": 0.05,
}
}
def setup(self):
ComplexFourierSeriesExample.setup(self)
MovingCameraScene.setup(self)
def get_rect(self):
return self.camera_frame

View File

@@ -0,0 +1,22 @@
from manimlib.imports import *
class WhyWouldYouCare(TeacherStudentsScene):
def construct(self):
self.student_says(
"Who cares!",
target_mode="sassy",
student_index=2,
added_anims=[self.teacher.change, "guilty"],
)
self.wait()
self.play(
RemovePiCreatureBubble(self.students[2]),
self.teacher.change, "raise_right_hand",
self.get_student_changes(
"pondering", "erm", "thinking",
look_at_arg=self.screen,
)
)
self.look_at(self.screen)
self.wait(5)

View File

@@ -0,0 +1,345 @@
from manimlib.imports import *
from active_projects.ode.part3.temperature_graphs import TemperatureGraphScene
from active_projects.ode.part2.wordy_scenes import WriteHeatEquationTemplate
class RelationToOtherVideos(Scene):
CONFIG = {
"camera_config": {
"background_color": DARK_GREY,
},
}
def construct(self):
# Show three videos
videos = self.get_video_thumbnails()
brace = Brace(videos, UP)
text = TextMobject("Heat equation")
text.scale(2)
text.next_to(brace, UP)
self.play(
LaggedStartMap(
FadeInFrom, videos,
lambda m: (m, LEFT),
lag_ratio=0.4,
run_time=2,
),
GrowFromCenter(brace),
FadeInFromDown(text),
)
self.wait()
group = Group(text, brace, videos)
# Show Fourier thinking
fourier = ImageMobject("Joseph Fourier")
fourier.set_height(4)
fourier.to_edge(RIGHT)
group.generate_target()
group.target.to_edge(DOWN)
fourier.align_to(group.target[0], DOWN)
bubble = ThoughtBubble(
direction=RIGHT,
width=3,
height=2,
fill_opacity=0.5,
stroke_color=WHITE,
)
bubble[-1].shift(0.25 * DOWN + 0.5 * LEFT)
bubble[:-1].rotate(20 * DEGREES)
for mob in bubble[:-1]:
mob.rotate(-20 * DEGREES)
bubble.move_tip_to(
fourier.get_corner(UL) + DOWN
)
bubble.to_edge(UP, buff=SMALL_BUFF)
self.play(
MoveToTarget(group),
FadeInFrom(fourier, LEFT)
)
self.play(Write(bubble, run_time=1))
self.wait()
# Discount first two
first_two = videos[:2]
first_two.generate_target()
first_two.target.scale(0.5)
first_two.target.to_corner(DL)
new_brace = Brace(first_two.target, UP)
self.play(
# fourier.scale, 0.8,
fourier.match_x, new_brace,
fourier.to_edge, UP,
MoveToTarget(first_two),
Transform(brace, new_brace),
text.scale, 0.7,
text.next_to, new_brace, UP,
FadeOutAndShift(bubble, LEFT),
)
self.play(
videos[2].scale, 1.7,
videos[2].to_corner, UR,
)
self.wait()
#
def get_video_thumbnails(self):
thumbnails = Group(
ImageMobject("diffyq_part2_thumbnail"),
ImageMobject("diffyq_part3_thumbnail"),
ImageMobject("diffyq_part4_thumbnail"),
)
for thumbnail in thumbnails:
thumbnail.set_height(4)
thumbnail.add(SurroundingRectangle(
thumbnail,
color=WHITE,
stroke_width=2,
buff=0
))
thumbnails.arrange(RIGHT, buff=LARGE_BUFF)
thumbnails.set_width(FRAME_WIDTH - 1)
return thumbnails
class ShowLinearity(WriteHeatEquationTemplate, TemperatureGraphScene):
CONFIG = {
"temp_text": "Temp",
"alpha": 0.1,
"axes_config": {
"z_max": 2,
"z_min": -2,
"z_axis_config": {
"tick_frequency": 0.5,
"unit_size": 1.5,
},
},
"default_surface_config": {
"resolution": (16, 16)
# "resolution": (4, 4)
},
"freqs": [2, 5],
}
def setup(self):
TemperatureGraphScene.setup(self)
WriteHeatEquationTemplate.setup(self)
def construct(self):
self.init_camera()
self.add_three_graphs()
self.show_words()
self.add_function_labels()
self.change_scalars()
def init_camera(self):
self.camera.set_distance(1000)
def add_three_graphs(self):
axes_group = self.get_axes_group()
axes0, axes1, axes2 = axes_group
freqs = self.freqs
scalar_trackers = Group(
ValueTracker(1),
ValueTracker(1),
)
graphs = VGroup(
self.get_graph(axes0, [freqs[0]], [scalar_trackers[0]]),
self.get_graph(axes1, [freqs[1]], [scalar_trackers[1]]),
self.get_graph(axes2, freqs, scalar_trackers),
)
plus = TexMobject("+").scale(2)
equals = TexMobject("=").scale(2)
plus.move_to(midpoint(
axes0.get_right(),
axes1.get_left(),
))
equals.move_to(midpoint(
axes1.get_right(),
axes2.get_left(),
))
self.add(axes_group)
self.add(graphs)
self.add(plus)
self.add(equals)
self.axes_group = axes_group
self.graphs = graphs
self.scalar_trackers = scalar_trackers
self.plus = plus
self.equals = equals
def show_words(self):
equation = self.get_d1_equation()
name = TextMobject("Heat equation")
name.next_to(equation, DOWN)
name.set_color_by_gradient(RED, YELLOW)
group = VGroup(equation, name)
group.to_edge(UP)
shift_val = 0.5 * RIGHT
arrow = Vector(1.5 * RIGHT)
arrow.move_to(group)
arrow.shift(shift_val)
linear_word = TextMobject("``Linear''")
linear_word.scale(2)
linear_word.next_to(arrow, RIGHT)
self.add(group)
self.wait()
self.play(
ShowCreation(arrow),
group.next_to, arrow, LEFT
)
self.play(FadeInFrom(linear_word, LEFT))
self.wait()
def add_function_labels(self):
axes_group = self.axes_group
graphs = self.graphs
solution_labels = VGroup()
for axes in axes_group:
label = TextMobject("Solution", "$\\checkmark$")
label.set_color_by_tex("checkmark", GREEN)
label.next_to(axes, DOWN)
solution_labels.add(label)
kw = {
"tex_to_color_map": {
"T_1": BLUE,
"T_2": GREEN,
}
}
T1 = TexMobject("a", "T_1", **kw)
T2 = TexMobject("b", "T_2", **kw)
T_sum = TexMobject("T_1", "+", "T_2", **kw)
T_sum_with_scalars = TexMobject(
"a", "T_1", "+", "b", "T_2", **kw
)
T1.next_to(graphs[0], UP)
T2.next_to(graphs[1], UP)
T_sum.next_to(graphs[2], UP)
T_sum.shift(SMALL_BUFF * DOWN)
T_sum_with_scalars.move_to(T_sum)
a_brace = Brace(T1[0], UP, buff=SMALL_BUFF)
b_brace = Brace(T2[0], UP, buff=SMALL_BUFF)
s1_decimal = DecimalNumber()
s1_decimal.match_color(T1[1])
s1_decimal.next_to(a_brace, UP, SMALL_BUFF)
s1_decimal.add_updater(lambda m: m.set_value(
self.scalar_trackers[0].get_value()
))
s2_decimal = DecimalNumber()
s2_decimal.match_color(T2[1])
s2_decimal.next_to(b_brace, UP, SMALL_BUFF)
s2_decimal.add_updater(lambda m: m.set_value(
self.scalar_trackers[1].get_value()
))
self.play(
FadeInFrom(T1[1], DOWN),
FadeInFrom(solution_labels[0], UP),
)
self.play(
FadeInFrom(T2[1], DOWN),
FadeInFrom(solution_labels[1], UP),
)
self.wait()
self.play(
TransformFromCopy(T1[1], T_sum[0]),
TransformFromCopy(T2[1], T_sum[2]),
TransformFromCopy(self.plus, T_sum[1]),
*[
Transform(
graph.copy().set_fill(opacity=0),
graphs[2].copy().set_fill(opacity=0),
remover=True
)
for graph in graphs[:2]
]
)
self.wait()
self.play(FadeInFrom(solution_labels[2], UP))
self.wait()
# Show constants
self.play(
FadeIn(T1[0]),
FadeIn(T2[0]),
FadeIn(a_brace),
FadeIn(b_brace),
FadeIn(s1_decimal),
FadeIn(s2_decimal),
FadeOut(T_sum),
FadeIn(T_sum_with_scalars),
)
def change_scalars(self):
s1, s2 = self.scalar_trackers
kw = {
"run_time": 2,
}
for graph in self.graphs:
graph.resume_updating()
self.play(s2.set_value, -0.5, **kw)
self.play(s1.set_value, -0.2, **kw)
self.play(s2.set_value, 1.5, **kw)
self.play(s1.set_value, 1.2)
self.play(s2.set_value, 0.3)
self.wait()
#
def get_axes_group(self):
axes_group = VGroup(*[
self.get_axes()
for x in range(3)
])
axes_group.arrange(RIGHT, buff=2)
axes_group.set_width(FRAME_WIDTH - 1)
axes_group.to_edge(DOWN, buff=1)
return axes_group
def get_axes(self):
axes = self.get_three_d_axes()
# axes.input_plane.set_fill(opacity=0)
# axes.input_plane.set_stroke(width=0.5)
# axes.add(axes.input_plane)
self.orient_three_d_mobject(axes)
axes.rotate(-5 * DEGREES, UP)
axes.set_width(4)
axes.x_axis.label.next_to(
axes.x_axis.get_end(), DOWN,
buff=2 * SMALL_BUFF
)
return axes
def get_graph(self, axes, freqs, scalar_trackers):
L = axes.x_max
a = self.alpha
def func(x, t):
scalars = [st.get_value() for st in scalar_trackers]
return np.sum([
s * np.cos(k * x) * np.exp(-a * (k**2) * t)
for freq, s in zip(freqs, scalars)
for k in [freq * PI / L]
])
def get_surface_graph_group():
return VGroup(
self.get_surface(axes, func),
self.get_time_slice_graph(axes, func, t=0),
)
result = always_redraw(get_surface_graph_group)
result.suspend_updating()
return result

View File

@@ -6,13 +6,11 @@ services:
image: eulertour/manim:latest
# uncomment this line to build rather than pull the image
# build: .
volumes:
- ${MANIM_PATH:?MANIM_PATH environment variable isn't set}:/opt/manim
environment:
- PYTHONPATH=/opt/manim
working_dir: /opt/manim
entrypoint:
- python
- -m
- manim
- --media_dir=/tmp/output
volumes:
- ${INPUT_PATH:?INPUT_PATH environment variable isn't set}:/tmp/input
- ${OUTPUT_PATH:?OUTPUT_PATH environment variable isn't set}:/tmp/output
working_dir: /tmp/input
network_mode: "none"

19
docs/Makefile Normal file
View File

@@ -0,0 +1,19 @@
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
SOURCEDIR = source
BUILDDIR = build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# 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)

35
docs/make.bat Normal file
View File

@@ -0,0 +1,35 @@
@ECHO OFF
pushd %~dp0
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=source
set BUILDDIR=build
if "%1" == "" goto help
%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.http://sphinx-doc.org/
exit /b 1
)
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
goto end
:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
:end
popd

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``.

210
docs/source/animation.rst Normal file
View File

@@ -0,0 +1,210 @@
Animation
=========
The simplest of which is ``Scene.add``. The object appears on the first frame
without any animation::
class NoAnimation(Scene):
def construct(self):
square = Square()
self.add(square))
Animation are used in conjunction with ``scene.Play``
Fade
----
.. raw:: html
<video width="560" height="315" controls>
<source src="_static/AnimationFadeIn.mp4" type="video/mp4">
</video>
.. code-block:: python
class AnimationFadeIn(Scene):
def construct(self):
square = Square()
anno = TextMobject("Fade In")
anno.shift(2 * DOWN)
self.add(anno)
self.play(FadeIn(square))
.. raw:: html
<video width="560" height="315" controls>
<source src="_static/AnimationFadeOut.mp4" type="video/mp4">
</video>
.. code-block:: python
class AnimationFadeOut(Scene):
def construct(self):
square = Square()
anno = TextMobject("Fade Out")
anno.shift(2 * DOWN)
self.add(anno)
self.add(square)
self.play(FadeOut(square))
.. raw:: html
<video width="560" height="315" controls>
<source src="_static/AnimationFadeInFrom.mp4" type="video/mp4">
</video>
.. code-block:: python
class AnimationFadeInFrom(Scene):
def construct(self):
square = Square()
for label, edge in zip(
["LEFT", "RIGHT", "UP", "DOWN"], [LEFT, RIGHT, UP, DOWN]
):
anno = TextMobject(f"Fade In from {label}")
anno.shift(2 * DOWN)
self.add(anno)
self.play(FadeInFrom(square, edge))
self.remove(anno, square)
.. raw:: html
<video width="560" height="315" controls>
<source src="_static/AnimationFadeOutAndShift.mp4" type="video/mp4">
</video>
.. code-block:: python
class AnimationFadeOutAndShift(Scene):
def construct(self):
square = Square()
for label, edge in zip(
["LEFT", "RIGHT", "UP", "DOWN"], [LEFT, RIGHT, UP, DOWN]
):
anno = TextMobject(f"Fade Out and shift {label}")
anno.shift(2 * DOWN)
self.add(anno)
self.play(FadeOutAndShift(square, edge))
self.remove(anno, square)
.. raw:: html
<video width="560" height="315" controls>
<source src="_static/AnimationFadeInFromLarge.mp4" type="video/mp4">
</video>
.. code-block:: python
class AnimationFadeInFromLarge(Scene):
def construct(self):
square = Square()
for factor in [0.1, 0.5, 0.8, 1, 2, 5]:
anno = TextMobject(f"Fade In from large scale\_factor={factor}")
anno.shift(2 * DOWN)
self.add(anno)
self.play(FadeInFromLarge(square, scale_factor=factor))
self.remove(anno, square)
.. raw:: html
<video width="560" height="315" controls>
<source src="_static/AnimationFadeInFromPoint.mp4" type="video/mp4">
</video>
.. code-block:: python
class AnimationFadeInFromPoint(Scene):
def construct(self):
square = Square()
for i in range(-6, 7, 2):
anno = TextMobject(f"Fade In from point {i}")
anno.shift(2 * DOWN)
self.add(anno)
self.play(FadeInFromPoint(square, point=i))
self.remove(anno, square)
Grow
----
.. raw:: html
<video width="560" height="315" controls>
<source src="_static/AnimationGrowFromEdge.mp4" type="video/mp4">
</video>
.. code-block:: python
class AnimationGrowFromEdge(Scene):
def construct(self):
for label, edge in zip(
["LEFT", "RIGHT", "UP", "DOWN"], [LEFT, RIGHT, UP, DOWN]
):
anno = TextMobject(f"Grow from {label} edge")
anno.shift(2 * DOWN)
self.add(anno)
square = Square()
self.play(GrowFromEdge(square, edge))
self.remove(anno, square)
.. raw:: html
<video width="560" height="315" controls>
<source src="_static/AnimationGrowFromCenter.mp4" type="video/mp4">
</video>
.. code-block:: python
class AnimationGrowFromCenter(Scene):
def construct(self):
square = Square()
anno = TextMobject("Grow from center")
anno.shift(2 * DOWN)
self.add(anno)
self.play(GrowFromCenter(square))
Diagonal Directions
-------------------
You can combine cardinal directions to form diagonal animations
.. raw:: html
<video width="560" height="315" controls>
<source src="_static/AnimationFadeInFromDiagonal.mp4" type="video/mp4">
</video>
.. code-block:: python
class AnimationFadeInFromDiagonal(Scene):
def construct(self):
square = Square()
for diag in [UP + LEFT, UP + RIGHT, DOWN + LEFT, DOWN + RIGHT]:
self.play(FadeInFrom(square, diag))
.. note::
You can also use the abbreviated forms like ``UL, UR, DL, DR``.
See :ref:`ref-directions`.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

53
docs/source/conf.py Normal file
View File

@@ -0,0 +1,53 @@
# 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 ---------------------------------------------------
master_doc = 'index'
# 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 = ['assets']

94
docs/source/constants.rst Normal file
View File

@@ -0,0 +1,94 @@
Manim Constants
===============
The ``constants.py`` under ``manimlib/`` contains variables that are used
during setup and running manim. Some variables are not documented here as they are
only used internally by manim.
Directories
-----------
MEDIA_DIR
The directory where ``VIDEO_DIR`` and ``TEX_DIR`` will be created,
if they aren't specified via flags.
VIDEO_DIR
Used to store the scenes rendered by Manim. When a scene is
finished rendering, it will be stored under
``VIDEO_DIR/module_name/scene_name/quality/scene_name.mp4``.
Created under ``MEDIA_DIR`` by default.
TEX_DIR
Files written by Latex are stored here. It also acts as a cache
so that the files aren't rewritten each Latex is needed.
Those directories are created if they don't exist.
Tex
---
TEX_USE_CTEX
A boolean value. Change it to True if you need to use Chinese typesetting.
TEX_TEXT_TO_REPLACE
Placeholder text used by manim when generating tex files
TEMPLATE_TEX_FILE
By default ``manimlib/tex_template.tex`` is used. If ``TEX_USE_CTEX``
is set to True then ``manimlib/ctex_template.tex`` is used.
Numerical Constants
-------------------
PI
alias to ``numpy.pi``
TAU
PI * 2
DEGREES
TAU / 360
Camera Configuration
--------------------
Render setting presets
PRODUCTION_QUALITY_CAMERA_CONFIG
2560x1440 @ 60fps # This is the default when rendering a scene
HIGH_QUALITY_CAMERA_CONFIG
1920x1080 @ 60fps. # Used when the ``-h`` or ``--high_quality`` flag
is passed.
MEDIUM_QUALITY_CAMERA_CONFIG
1280x720 @ 30fps. # Used when the ``-m`` or ``--medium_quality``
flag is passed.
LOW_QUALITY_CAMERA_CONFIG
854x480 @ 15fps. # Used when the ``-l`` or ``--low_quality`` flag is
passed.
.. _ref-directions:
Coordinates
-----------
Used for 2d/3d animations and placements::
ORIGIN
UP
DOWN
RIGHT
LEFT
IN # 3d camera only, away from camera
OUT # 3d camera only, close to camera
UL = UP + LEFT # diagonal abbreviations. You can use either one
UR = UP + RIGHT
DL = DOWN + LEFT
DR = DOWN + RIGHT
TOP
BOTTOM
LEFT_SIDE
RIGHT_SIDE``
Colors
------
COLOR_MAP
A predefined color maps
PALETTE
A list of color hex strings, derived from COLOR_MAP

178
docs/source/coordinate.rst Normal file
View File

@@ -0,0 +1,178 @@
Coordinate
==========
By default, the scene in manim is made up by 8 x 14 grid. The grid is addressed using a numpy
array in the form of [x, y, z]. For 2D animations only the x and y axes are used.
.. code-block:: python
class DotMap(Scene):
def construct(self):
dots = dict()
annos = dict()
var_index = 0
for x in range(-7, 8):
for y in range(-4, 5):
annos[f"{x}{y}"] = TexMobject(f"({x}, {y})")
dots[f"{var_index}"] = Dot(np.array([x, y, 0]))
var_index = var_index + 1
for anno, dot in zip(annos.values(), dots.values()):
self.add(anno)
self.add(dot)
self.wait(0.2)
self.remove(anno)
.. raw:: html
<video width="700" height="394" controls>
<source src="_static/coordinate/DotMap.mp4" type="video/mp4">
</video>
.. note::
You can place objects outside this boundary, but it won't show up in the render.
Using Coordinates
-----------------
Coordinates are used for creating geometries (`VMobject` in manim) and animations.
Here coordinates are used to create this Polygon
.. code-block:: python
class CoorPolygon(Scene):
def construct(self):
for x in range(-7, 8):
for y in range(-4, 5):
self.add(Dot(np.array([x, y, 0]), color=DARK_GREY))
polygon = Polygon(
np.array([3, 2, 0]),
np.array([1, -1, 0]),
np.array([-5, -4, 0]),
np.array([-4, 4, 0]))
self.add(polygon)
.. Image:: assets/coordinate/CoorPolygon.png
:width: 700px
Coordinate Aliasing
-------------------
From some animations typing a ``np.array`` everytime you need a coordinate can be tedious.
Manim provides aliases to the most common coordinates::
UP == np.array([0, 1, 0])
DOWN == np.array([0, -1, 0])
LEFT == np.array([-1, 0, 0])
RIGHT == np.array([1, 0, 0])
UL == np.array([-1, 1, 0])
DL == np.array([-1, -1, 0])
UR == np.array([1, 1, 0])
DR == np.array([1, -1, 0])
Here coordinates are used for animations
.. code-block:: python
class CoorAlias(Scene):
def construct(self):
for x in range(-7, 8):
for y in range(-4, 5):
self.add(Dot(np.array([x, y, 0]), color=DARK_GREY))
aliases = {
"UP": UP,
"np.array([0,1,0])": np.array([0, 1, 0]),
"DOWN": DOWN,
"np.array([0,-1,0])": np.array([0, -1, 0]),
"LEFT": LEFT,
"np.array([-1,0,0])": np.array([-1, 0, 0]),
"RIGHT": RIGHT,
"np.array([1,0,0])": np.array([1, 0, 0]),
"UL": UL,
"np.array([-1,1,0])": np.array([-1, 1, 0]),
"DL": DL,
"np.array([-1,-1,0])": np.array([-1, -1, 0]),
"UR": UR,
"np.array([1,1,0])": np.array([1, 1, 0]),
"DR": DR,
"np.array([1,-1,0])": np.array([1, -1, 0])}
circle = Circle(color=RED, radius=0.5)
self.add(circle)
self.wait(0.5)
for text, aliase in aliases.items():
anno = TexMobject(f"\\texttt{{{text}}}")
self.play(Write(anno, run_time=0.2))
self.play(ApplyMethod(circle.shift, aliase))
self.wait(0.2)
self.play(FadeOut(anno, run_time=0.2))
.. raw:: html
<video width="700" height="394" controls>
<source src="_static/coordinate/CoorAlias.mp4" type="video/mp4">
</video>
Coordinate Arithmetic
---------------------
Numpy array allows arithmetic operations::
>>> numpy.array([2,2,0]) + 4
array([6, 6, 4])
>>> np.array([1, -3, 0]) + np.array([-4, 2, 0])
array([-3, -1, 0])
>>> np.array([2, 2, 0]) - np.array([3,6, 0])
array([-1, -4, 0])
>>> numpy.array([2,2,0]) - 3
array([-1, -1, -3])
>>> np.array([1, -3, 0]) * 3
array([ 3, -9, 0])
>>> numpy.array([2,2,0]) / 2
array([1., 1., 0.])
>>> numpy.array([2,2,0]) / numpy.array([1, 4, 0])
__main__:1: RuntimeWarning: invalid value encountered in true_divide
array([2. , 0.5, nan])
.. code-block:: python
class CoorArithmetic(Scene):
def construct(self):
for x in range(-7, 8):
for y in range(-4, 5):
self.add(Dot(np.array([x, y, 0]), color=DARK_GREY))
circle = Circle(color=RED, radius=0.5)
self.add(circle)
self.wait(0.5)
aliases = {
"LEFT * 3": LEFT * 3,
"UP + RIGHT / 2": UP + RIGHT / 2,
"DOWN + LEFT * 2": DOWN + LEFT * 2,
"RIGHT * 3.75 * DOWN": RIGHT * 3.75 * DOWN,
# certain arithmetic won't work as you expected
# In [4]: RIGHT * 3.75 * DOWN
# Out[4]: array([ 0., -0., 0.])
"RIGHT * 3.75 + DOWN": RIGHT * 3.75 + DOWN}
for text, aliase in aliases.items():
anno = TexMobject(f"\\texttt{{{text}}}")
self.play(Write(anno, run_time=0.2))
self.play(ApplyMethod(circle.shift, aliase))
self.wait(0.2)
self.play(FadeOut(anno, run_time=0.2))
.. raw:: html
<video width="700" height="394" controls>
<source src="_static/coordinate/CoorArithmetic.mp4" type="video/mp4">
</video>

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``, which has been updated to run on python 3.7. Note that you'll want
to change `from big_ol_pile_of_manim_imports import *` to `from
manimlib.imports import *` to work with the current codebase.
.. _a very nice tutorial: https://talkingphysics.wordpress.com/2019/01/08/getting-started-animating-with-manim-and-python-3-7/
.. toctree::
:caption: Contents
:maxdepth: 2
learning_by_example
mathematical_objects
animating_mobjects
making_a_scene

View File

@@ -0,0 +1,131 @@
Learning by Example
===================
SquareToCircle
--------------
``example_scenes.py`` contains simple examples that we can use to learn about manim.
Go ahead and try out the ``SquareToCircle`` scene by running it with ``$ manim example_scenes.py SquareToCircle -p``
in manim directory.
.. code-block:: python
:linenos:
from manimlib.imports import *
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))
.. raw:: html
<video width="560" height="315" controls>
<source src="../_static/SquareToCircle.mp4" type="video/mp4">
</video>
.. note::
The flag ``-p`` plays the rendered video with default video player.
Other frequently used flags are:
* ``-l`` for rendering video in lower resolution (which renders faster)
* ``-s`` to show the last frame of the video.
Run ``manim -h`` all the available flags (``python -m manim -h`` if you installed it to a venv)
Let's step through each line of ``SquareToCircle``
.. code-block:: python
:lineno-start: 3
class SquareToCircle(Scene):
You create videos in manim by writing :class:`~scene.scene.Scene` classes.
Each :class:`~scene.scene.Scene` in manim is self-contained. That means everything
you created under this scene does not exist outside the class.
.. code-block:: python
:lineno-start: 4
def construct(self):
:meth:`~scene.scene.Scene.construct` specifies what is displayed on the screen
when the :class:`~scene.scene.Scene` is rendered to video.
.. code-block:: python
:lineno-start: 5
circle = Circle()
square = Square()
``Circle()`` and ``Square()`` create :class:`~mobject.geometry.Circle` and :class:`~mobject.geometry.Square`.
Both of these are instances of :class:`~mobject.mobject.Mobject` subclasses, 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
:lineno-start: 7
square.flip(RIGHT)
square.rotate(-3 * TAU / 8)
circle.set_fill(PINK, opacity=0.5)
``flip()`` ``rotate()`` ``set_fill()`` apply various modifications to the mobjects before animating
them. The call to :meth:`~mobject.mobject.Mobject.flip` flips the
:class:`~mobject.geometry.Square` across the RIGHT vector. This is equivalent
to a refection across the x-axis.
The call to :meth:`~mobject.mobject.Mobject.rotate` rotates the
:class:`~mobject.geometry.Square` 3/8ths of a full rotation counterclockwise.
The call to :meth:`~mobject.mobject.Mobject.set_fill` sets
the fill color for the :class:`~mobject.geometry.Circle` to pink, and its opacity to 0.5.
.. code-block:: python
:lineno-start: 11
self.play(ShowCreation(square))
self.play(Transform(square, circle))
self.play(FadeOut(square))
To generated animation, :class:`~animation.animation.Animation` classes are used.
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`.
: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.
:class:`~animation.creation.FadeOut` fades a :class:`~mobject.mobject.Mobject` out of the :class:`~scene.scene.Scene`.
.. note::
Only the first argument to :class:`~animation.transform.Transform` is modified,
the second is not added to the :class:`~scene.scene.Scene`. :class:`~animation.tranform.Transform`
only changes the appearance but not the underlying properties.
After the call to ``transform()`` ``square`` is still a :class:`~mobject.geometry.Square` instance
but with the shape of :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``

30
docs/source/index.rst Normal file
View File

@@ -0,0 +1,30 @@
.. 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!
=================================
These docs are generated from the master branch of the
`Manim repo <https://github.com/3b1b/manim>`_. You can contribute by submitting
a pull request there.
.. toctree::
:maxdepth: 2
:caption: Contents
about
installation/index
getting_started/index
coordinate
animation
constants
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,41 @@
Linux
=====
Ubuntu
------
Install system libraries::
# apt install sox ffmpeg libcairo2 libcairo2-dev
Install Latex distribution::
# apt install texlive-full
Install manim via pypi::
# pip3 install manimlib
OR Install manim via the git repository with venv::
$ git clone https://github.com/3b1b/manim
$ cd manim
$ python3 -m venv ./
$ source bin/activate
$ pip3 install -r requirement.txt
To use manim in virtual environment you need to activate the environment with
the ``activate`` binary by doing ``source bin/activate``, to exit use the ``deactivate`` command.
.. note:: The git repository is updated first before the one on pypi. The git repository also
includes project files used to produce 3b1b videos. Some of the old projects might not
work as due to api changes.
.. note:: The required latex packages are dictated by
``manimlib/tex_template.tex`` which ``texlive-full`` will satisfy. The download size
can be quite large. If you wish to install only the packages required to use
manim, substitude ``texlive-full`` with::
texlive texlive-latex-extra texlive-fonts-extra
texlive-latex-recommended texlive-science texlive-fonts-extra tipa

View File

@@ -0,0 +1,12 @@
Mac
===
The simplest way to install the system dependencies on Mac OS X is with Homebrew.
Mac come preinstalled with python2, but to use manim, python3 is required
1. Install python3 https://docs.python.org/3/using/mac.html
2. Install Cairo: ``brew install cairo``
3. Install Sox: ``brew install sox``
4. Install ffmpeg: ``brew install ffmpeg``
5. Install latex (MiKTeX): https://miktex.org/howto/install-miktex-mac
6. Install manimlib ``pip install manimlib`` (or ``pip install --user manimlib`` to just yourself)

View File

@@ -0,0 +1,60 @@
Windows
=======
Install System Libraries
------------------------
Make sure you have *Python 3* for Windows installed first:
https://www.python.org/downloads/windows/
Install ffmpeg:
https://ffmpeg.org/download.html#build-windows
Install sox:
http://sox.sourceforge.net/Main/HomePage
Install a latex distribution. On Windows MikTex is commonly used:
https://miktex.org/howto/install-miktex
Path configuration
------------------
To invoke commandline without supplying path to the binary
the PATH environment needs to be configured. Below are template examples, please change
the path according to your username and specific python version. Assuming all the
softwares are installed with no alteration to the installation paths::
C:\Users\$username\AppData\local\Programs\Python\Python$version\
C:\Users\$username\AppData\local\Programs\Python\Python$version\Scripts\
C:\MikTex\miktex\bin\x64\
C:\ffmpeg\bin\
The path entries should be separated by semicolon.
Installing python packages and manim
------------------------------------
Make sure you can start pip using ``pip`` in your commandline. Then do
``pip install pyreadline`` for the ``readline`` package.
Grab the pycairo wheel binary ``pycairo1.18.0cp37cp37mwin32.whl`` from https://www.lfd.uci.edu/~gohlke/pythonlibs/#pycairo
and install it via ``python -m pip install C:\absolute\path\to\the\whl\file``
clone the manim repository if you have git ``git clone https://github.com/3b1b/manim`` or download the zip file from
the repository page with ``Clone or download`` button and unzip it.
Open the commandline within the manim directory with ``Shift + Right click`` on an empty space in the folder and select ``open command window here``
Install manim python dependencies with ``pip install -r requirement.txt``
Test the installation
---------------------
Type in ``python -m manim -h`` and if nothing went wrong during the installtion process you should see the help text.
Use ``python -m manim example_scenes.py SquareToCircle -pl`` to render the example scene and the file should play after rendering. The movie file should be
in ``media/videos/example_scenes/480p15``

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,

0
manim.py Normal file → Executable file
View File

BIN
manim_docker_diagram.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -1,5 +1,6 @@
#!/usr/bin/env python
import manimlib.config
import manimlib.constants
import manimlib.extract_scene
import manimlib.stream_starter
@@ -8,6 +9,7 @@ def main():
args = manimlib.config.parse_cli()
if not args.livestream:
config = manimlib.config.get_configuration(args)
manimlib.constants.initialize_directories(config)
manimlib.extract_scene.main(config)
else:
manimlib.stream_starter.start_livestream(

View File

@@ -6,6 +6,8 @@ from manimlib.utils.rate_functions import linear
from manimlib.utils.rate_functions import double_smooth
from manimlib.utils.rate_functions import smooth
import numpy as np
class ShowPartial(Animation):
"""
@@ -120,6 +122,7 @@ class Write(DrawBorderThenFill):
class ShowIncreasingSubsets(Animation):
CONFIG = {
"suspend_mobject_updating": False,
"int_func": np.floor,
}
def __init__(self, group, **kwargs):
@@ -128,5 +131,5 @@ class ShowIncreasingSubsets(Animation):
def interpolate_mobject(self, alpha):
n_submobs = len(self.all_submobs)
index = int(alpha * n_submobs)
index = int(self.int_func(alpha * n_submobs))
self.mobject.submobjects = self.all_submobs[:index]

View File

@@ -332,6 +332,8 @@ class Camera(object):
points = self.transform_points_pre_display(
vmobject, vmobject.points
)
# TODO, shouldn't this be handled in transform_points_pre_display?
# points = points - self.get_frame_center()
if len(points) == 0:
return

View File

@@ -47,11 +47,21 @@ def parse_cli():
action="store_true",
help="Render at a medium quality",
),
parser.add_argument(
"--high_quality",
action="store_true",
help="Render at a high quality",
),
parser.add_argument(
"-g", "--save_pngs",
action="store_true",
help="Save each frame as a png",
),
parser.add_argument(
"-i", "--save_as_gif",
action="store_true",
help="Save the video as gif",
),
parser.add_argument(
"-f", "--show_file_in_finder",
action="store_true",
@@ -102,6 +112,23 @@ def parse_cli():
action="store_true",
help="Leave progress bars displayed in terminal",
)
parser.add_argument(
"--media_dir",
help="directory to write media",
)
video_group = parser.add_mutually_exclusive_group()
video_group.add_argument(
"--video_dir",
help="directory to write file tree for video",
)
video_group.add_argument(
"--video_output_dir",
help="directory to write video",
)
parser.add_argument(
"--tex_dir",
help="directory to write tex",
)
# For live streaming
module_location.add_argument(
@@ -161,6 +188,7 @@ def get_configuration(args):
"write_to_movie": args.write_to_movie or not args.save_last_frame,
"save_last_frame": args.save_last_frame,
"save_pngs": args.save_pngs,
"save_as_gif": args.save_as_gif,
# If -t is passed in (for transparent), this will be RGBA
"png_mode": "RGBA" if args.transparent else "RGB",
"movie_file_extension": ".mov" if args.transparent else ".mp4",
@@ -181,7 +209,11 @@ def get_configuration(args):
"start_at_animation_number": args.start_at_animation_number,
"end_at_animation_number": None,
"sound": args.sound,
"leave_progress_bars": args.leave_progress_bars
"leave_progress_bars": args.leave_progress_bars,
"media_dir": args.media_dir,
"video_dir": args.video_dir,
"video_output_dir": args.video_output_dir,
"tex_dir": args.tex_dir,
}
# Camera configuration
@@ -210,6 +242,8 @@ def get_camera_configuration(args):
camera_config.update(manimlib.constants.LOW_QUALITY_CAMERA_CONFIG)
elif args.medium_quality:
camera_config.update(manimlib.constants.MEDIUM_QUALITY_CAMERA_CONFIG)
elif args.high_quality:
camera_config.update(manimlib.constants.HIGH_QUALITY_CAMERA_CONFIG)
else:
camera_config.update(manimlib.constants.PRODUCTION_QUALITY_CAMERA_CONFIG)

View File

@@ -1,47 +1,57 @@
import numpy as np
import os
# Initialize directories
env_MEDIA_DIR = os.getenv("MEDIA_DIR")
if env_MEDIA_DIR:
MEDIA_DIR = env_MEDIA_DIR
elif os.path.isfile("media_dir.txt"):
with open("media_dir.txt", 'rU') as media_file:
MEDIA_DIR = media_file.readline().strip()
else:
MEDIA_DIR = os.path.join(
os.path.expanduser('~'),
"Dropbox (3Blue1Brown)/3Blue1Brown Team Folder"
)
if not os.path.isdir(MEDIA_DIR):
MEDIA_DIR = "./media"
print(
f"Media will be stored in {MEDIA_DIR + os.sep}. You can change "
"this behavior by writing a different directory to media_dir.txt."
)
MEDIA_DIR = ""
VIDEO_DIR = ""
VIDEO_OUTPUT_DIR = ""
TEX_DIR = ""
VIDEO_DIR = os.path.join(MEDIA_DIR, "videos")
RASTER_IMAGE_DIR = os.path.join(MEDIA_DIR, "designs", "raster_images")
SVG_IMAGE_DIR = os.path.join(MEDIA_DIR, "designs", "svg_images")
SOUND_DIR = os.path.join(MEDIA_DIR, "designs", "sounds")
###
THIS_DIR = os.path.dirname(os.path.realpath(__file__))
FILE_DIR = os.path.join(os.getenv("FILE_DIR", default="."), "files")
TEX_DIR = os.path.join(FILE_DIR, "Tex")
# These two may be depricated now.
MOBJECT_DIR = os.path.join(FILE_DIR, "mobjects")
IMAGE_MOBJECT_DIR = os.path.join(MOBJECT_DIR, "image")
def initialize_directories(config):
global MEDIA_DIR
global VIDEO_DIR
global VIDEO_OUTPUT_DIR
global TEX_DIR
for folder in [FILE_DIR, RASTER_IMAGE_DIR, SVG_IMAGE_DIR, VIDEO_DIR,
TEX_DIR, MOBJECT_DIR, IMAGE_MOBJECT_DIR]:
if not os.path.exists(folder):
os.makedirs(folder)
video_path_specified = config["video_dir"] or config["video_output_dir"]
if not video_path_specified:
VIDEO_DIR = os.path.join(MEDIA_DIR, "videos")
elif config["video_output_dir"]:
VIDEO_OUTPUT_DIR = config["video_output_dir"]
else:
VIDEO_DIR = config["video_dir"]
TEX_DIR = config["tex_dir"] or os.path.join(MEDIA_DIR, "Tex")
if not (video_path_specified and config["tex_dir"]):
if config["media_dir"]:
MEDIA_DIR = config["media_dir"]
else:
MEDIA_DIR = os.path.join(
os.path.expanduser('~'),
"Dropbox (3Blue1Brown)/3Blue1Brown Team Folder"
)
if not os.path.isdir(MEDIA_DIR):
MEDIA_DIR = "./media"
print(
f"Media will be written to {MEDIA_DIR + os.sep}. You can change "
"this behavior with the --media_dir flag."
)
else:
if config["media_dir"]:
print(
"Ignoring --media_dir, since both --tex_dir and a video "
"directory were both passed"
)
for folder in [VIDEO_DIR, VIDEO_OUTPUT_DIR, TEX_DIR]:
if folder != "" and not os.path.exists(folder):
os.makedirs(folder)
TEX_USE_CTEX = False
TEX_TEXT_TO_REPLACE = "YourTextHere"
TEMPLATE_TEX_FILE = os.path.join(
THIS_DIR, "tex_template.tex" if not TEX_USE_CTEX
else "ctex_template.tex"
os.path.dirname(os.path.realpath(__file__)),
"tex_template.tex" if not TEX_USE_CTEX else "ctex_template.tex"
)
with open(TEMPLATE_TEX_FILE, "r") as infile:
TEMPLATE_TEXT_FILE_BODY = infile.read()
@@ -90,7 +100,7 @@ PRODUCTION_QUALITY_CAMERA_CONFIG = {
HIGH_QUALITY_CAMERA_CONFIG = {
"pixel_height": 1080,
"pixel_width": 1920,
"frame_rate": 30,
"frame_rate": 60,
}
MEDIUM_QUALITY_CAMERA_CONFIG = {

View File

@@ -14,7 +14,6 @@
\usepackage{ragged2e}
\usepackage{physics}
\usepackage{xcolor}
\usepackage{textcomp}
\usepackage{microtype}
%\DisableLigatures{encoding = *, family = * }
\usepackage[UTF8]{ctex}
@@ -24,4 +23,4 @@
YourTextHere
\end{document}
\end{document}

View File

@@ -15,11 +15,11 @@ from manimlib.utils.config_ops import digest_config
from manimlib.utils.space_ops import get_norm
from manimlib.utils.space_ops import normalize
pi_creature_dir_maybe = os.path.join(MEDIA_DIR, "designs", "PiCreature")
pi_creature_dir_maybe = os.path.join(MEDIA_DIR, "assets", "PiCreature")
if os.path.exists(pi_creature_dir_maybe):
PI_CREATURE_DIR = pi_creature_dir_maybe
else:
PI_CREATURE_DIR = os.path.join(FILE_DIR)
PI_CREATURE_DIR = os.path.join("assets")
PI_CREATURE_SCALE_FACTOR = 0.5

View File

@@ -1,6 +1,8 @@
from manimlib.constants import *
from manimlib.mobject.types.vectorized_mobject import VMobject
from manimlib.mobject.types.vectorized_mobject import VGroup
from manimlib.utils.rate_functions import smooth
from manimlib.utils.space_ops import get_norm
class AnimatedBoundary(VGroup):
@@ -66,3 +68,31 @@ class AnimatedBoundary(VGroup):
for sm1, sm2 in zip(family1, family2):
sm1.pointwise_become_partial(sm2, a, b)
return self
class TracedPath(VMobject):
CONFIG = {
"stroke_width": 2,
"stroke_color": WHITE,
"min_distance_to_new_point": 0.1,
}
def __init__(self, traced_point_func, **kwargs):
super().__init__(**kwargs)
self.traced_point_func = traced_point_func
self.add_updater(lambda m: m.update_path())
def update_path(self):
new_point = self.traced_point_func()
if self.has_no_points():
self.start_new_path(new_point)
self.add_line_to(new_point)
else:
# Set the end to be the new point
self.points[-1] = new_point
# Second to last point
nppcc = self.n_points_per_cubic_curve
dist = get_norm(new_point - self.points[-nppcc])
if dist >= self.min_distance_to_new_point:
self.add_line_to(new_point)

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

@@ -26,6 +26,24 @@ DEFAULT_ARROW_TIP_LENGTH = 0.35
class TipableVMobject(VMobject):
"""
Meant for shared functionality between Arc and Line.
Functionality can be classified broadly into these groups:
* Adding, Creating, Modifying tips
- add_tip calls create_tip, before pushing the new tip
into the TipableVMobject's list of submobjects
- stylistic and positional configuration
* Checking for tips
- Boolean checks for whether the TipableVMobject has a tip
and a starting tip
* Getters
- Straightforward accessors, returning information pertaining
to the TipableVMobject instance's tip(s), its length etc
"""
CONFIG = {
"tip_length": DEFAULT_ARROW_TIP_LENGTH,
# TODO
@@ -35,12 +53,15 @@ class TipableVMobject(VMobject):
"stroke_width": 0,
}
}
"""
Meant simply for shard functionality between
Arc and Line
"""
# Adding, Creating, Modifying tips
def add_tip(self, tip_length=None, at_start=False):
"""
Adds a tip to the TipableVMobject instance, recognising
that the endpoints might need to be switched if it's
a 'starting tip' or not.
"""
tip = self.create_tip(tip_length, at_start)
self.reset_endpoints_based_on_tip(tip, at_start)
self.asign_tip_attr(tip, at_start)
@@ -48,11 +69,19 @@ class TipableVMobject(VMobject):
return self
def create_tip(self, tip_length=None, at_start=False):
"""
Stylises the tip, positions it spacially, and returns
the newly instantiated tip to the caller.
"""
tip = self.get_unpositioned_tip(tip_length)
self.position_tip(tip, at_start)
return tip
def get_unpositioned_tip(self, tip_length=None):
"""
Returns a tip that has been stylistically configured,
but has not yet been given a position in space.
"""
if tip_length is None:
tip_length = self.get_default_tip_length()
color = self.get_color()
@@ -85,6 +114,7 @@ class TipableVMobject(VMobject):
# Zero length, put_start_and_end_on wouldn't
# work
return self
if at_start:
self.put_start_and_end_on(
tip.get_base(), self.get_end()
@@ -102,7 +132,34 @@ class TipableVMobject(VMobject):
self.tip = tip
return self
# Checking for tips
def has_tip(self):
return hasattr(self, "tip") and self.tip in self
def has_start_tip(self):
return hasattr(self, "start_tip") and self.start_tip in self
# Getters
def pop_tips(self):
start, end = self.get_start_and_end()
result = VGroup()
if self.has_tip():
result.add(self.tip)
self.remove(self.tip)
if self.has_start_tip():
result.add(self.start_tip)
self.remove(self.start_tip)
self.put_start_and_end_on(start, end)
return result
def get_tips(self):
"""
Returns a VGroup (collection of VMobjects) containing
the TipableVMObject instance's tips.
"""
result = VGroup()
if hasattr(self, "tip"):
result.add(self.tip)
@@ -111,6 +168,8 @@ class TipableVMobject(VMobject):
return result
def get_tip(self):
"""Returns the TipableVMobject instance's (first) tip,
otherwise throws an exception."""
tips = self.get_tips()
if len(tips) == 0:
raise Exception("tip not found")
@@ -142,23 +201,7 @@ class TipableVMobject(VMobject):
start, end = self.get_start_and_end()
return get_norm(start - end)
def has_tip(self):
return hasattr(self, "tip") and self.tip in self
def has_start_tip(self):
return hasattr(self, "start_tip") and self.start_tip in self
def pop_tips(self):
start, end = self.get_start_and_end()
result = VGroup()
if self.has_tip():
result.add(self.tip)
self.remove(self.tip)
if self.has_start_tip():
result.add(self.start_tip)
self.remove(self.start_tip)
self.put_start_and_end_on(start, end)
return result
class Arc(TipableVMobject):
@@ -471,6 +514,9 @@ class Line(TipableVMobject):
about_point=self.get_start(),
)
def set_length(self, length):
self.scale(length / self.get_length())
def set_opacity(self, opacity, family=True):
# Overwrite default, which would set
# the fill opacity
@@ -534,6 +580,25 @@ class DashedLine(Line):
return self.submobjects[-1].points[-2]
class TangentLine(Line):
CONFIG = {
"length": 1,
"d_alpha": 1e-6
}
def __init__(self, vmob, alpha, **kwargs):
digest_config(self, kwargs)
da = self.d_alpha
a1 = np.clip(alpha - da, 0, 1)
a2 = np.clip(alpha + da, 0, 1)
super().__init__(
vmob.point_from_proportion(a1),
vmob.point_from_proportion(a2),
**kwargs
)
self.scale(self.length / self.get_length())
class Elbow(VMobject):
CONFIG = {
"width": 0.2,
@@ -625,7 +690,6 @@ class Arrow(Line):
class Vector(Arrow):
CONFIG = {
"color": YELLOW,
"buff": 0,
}

View File

@@ -9,6 +9,7 @@ import sys
from colour import Color
import numpy as np
import manimlib.constants as consts
from manimlib.constants import *
from manimlib.container.container import Container
from manimlib.utils.color import color_gradient
@@ -109,7 +110,7 @@ class Mobject(Container):
def save_image(self, name=None):
self.get_image().save(
os.path.join(VIDEO_DIR, (name or str(self)) + ".png")
os.path.join(consts.VIDEO_DIR, (name or str(self)) + ".png")
)
def copy(self):
@@ -1087,6 +1088,7 @@ class Mobject(Container):
mobject1.points, mobject2.points, alpha
)
self.interpolate_color(mobject1, mobject2, alpha)
return self
def interpolate_color(self, mobject1, mobject2, alpha):
pass # To implement in subclass

View File

@@ -455,9 +455,10 @@ class Bubble(SVGMobject):
mover.shift(point - self.get_tip())
return self
def flip(self):
Mobject.flip(self)
self.direction = -np.array(self.direction)
def flip(self, axis=UP):
Mobject.flip(self, axis=axis)
if abs(axis[0]) > 0:
self.direction = -np.array(self.direction)
return self
def pin_to(self, mobject):

View File

@@ -50,9 +50,9 @@ class SVGMobject(VMobject):
if self.file_name is None:
raise Exception("Must specify file for SVGMobject")
possible_paths = [
os.path.join(SVG_IMAGE_DIR, self.file_name),
os.path.join(SVG_IMAGE_DIR, self.file_name + ".svg"),
os.path.join(SVG_IMAGE_DIR, self.file_name + ".xdv"),
os.path.join(os.path.join("assets", "svg_images"), self.file_name),
os.path.join(os.path.join("assets", "svg_images"), self.file_name + ".svg"),
os.path.join(os.path.join("assets", "svg_images"), self.file_name + ".xdv"),
self.file_name,
]
for path in possible_paths:

View File

@@ -242,6 +242,7 @@ class VMobject(Mobject):
def set_opacity(self, opacity, family=True):
self.set_fill(opacity=opacity, family=family)
self.set_stroke(opacity=opacity, family=family)
self.set_stroke(opacity=opacity, family=family, background=True)
return self
def fade(self, darkness=0.5, family=True):

View File

@@ -7,13 +7,13 @@ import _thread as thread
from time import sleep
import datetime
import manimlib.constants as consts
from manimlib.constants import FFMPEG_BIN
from manimlib.constants import STREAMING_IP
from manimlib.constants import STREAMING_PORT
from manimlib.constants import STREAMING_PROTOCOL
from manimlib.constants import VIDEO_DIR
from manimlib.utils.config_ops import digest_config
from manimlib.utils.file_ops import guarantee_existance
from manimlib.utils.file_ops import guarantee_existence
from manimlib.utils.file_ops import add_extension_if_not_present
from manimlib.utils.file_ops import get_sorted_integer_files
from manimlib.utils.sounds import get_full_sound_file_path
@@ -27,6 +27,7 @@ class SceneFileWriter(object):
"png_mode": "RGBA",
"save_last_frame": False,
"movie_file_extension": ".mp4",
"gif_file_extension": ".gif",
"livestreaming": False,
"to_twitch": False,
"twitch_key": None,
@@ -45,57 +46,69 @@ class SceneFileWriter(object):
# Output directories and files
def init_output_directories(self):
output_directory = self.output_directory or self.get_default_output_directory()
file_name = self.file_name or self.get_default_file_name()
module_directory = self.output_directory or self.get_default_module_directory()
scene_name = self.file_name or self.get_default_scene_name()
if self.save_last_frame:
image_dir = guarantee_existance(os.path.join(
VIDEO_DIR,
output_directory,
self.get_image_directory(),
))
if consts.VIDEO_DIR != "":
image_dir = guarantee_existence(os.path.join(
consts.VIDEO_DIR,
module_directory,
"images",
))
else:
image_dir = guarantee_existence(os.path.join(
consts.VIDEO_OUTPUT_DIR,
"images",
))
self.image_file_path = os.path.join(
image_dir,
add_extension_if_not_present(file_name, ".png")
add_extension_if_not_present(scene_name, ".png")
)
if self.write_to_movie:
movie_dir = guarantee_existance(os.path.join(
VIDEO_DIR,
output_directory,
self.get_movie_directory(),
))
if consts.VIDEO_DIR != "":
movie_dir = guarantee_existence(os.path.join(
consts.VIDEO_DIR,
module_directory,
self.get_resolution_directory(),
))
else:
movie_dir = guarantee_existence(consts.VIDEO_OUTPUT_DIR)
self.movie_file_path = os.path.join(
movie_dir,
add_extension_if_not_present(
file_name, self.movie_file_extension
scene_name, self.movie_file_extension
)
)
self.partial_movie_directory = guarantee_existance(os.path.join(
self.gif_file_path = os.path.join(
movie_dir,
self.get_partial_movie_directory(),
file_name,
add_extension_if_not_present(
scene_name, self.gif_file_extension
)
)
self.partial_movie_directory = guarantee_existence(os.path.join(
movie_dir,
"partial_movie_files",
scene_name,
))
def get_default_output_directory(self):
def get_default_module_directory(self):
filename = os.path.basename(self.input_file_path)
root, ext = os.path.splitext(filename)
return root if root else ext[1:]
root, _ = os.path.splitext(filename)
return root
def get_default_file_name(self):
return self.scene.__class__.__name__
def get_default_scene_name(self):
if self.file_name is None:
return self.scene.__class__.__name__
else:
return self.file_name
def get_movie_directory(self):
def get_resolution_directory(self):
pixel_height = self.scene.camera.pixel_height
frame_rate = self.scene.camera.frame_rate
return "{}p{}".format(
pixel_height, frame_rate
)
def get_image_directory(self):
return "images"
def get_partial_movie_directory(self):
return "partial_movie_files"
# Directory getters
def get_image_file_path(self):
return self.image_file_path
@@ -299,10 +312,19 @@ class SceneFileWriter(object):
'-f', 'concat',
'-safe', '0',
'-i', file_list,
'-c', 'copy',
'-loglevel', 'error',
movie_file_path
]
if not self.save_as_gif:
commands +=[
'-c', 'copy',
movie_file_path
]
if self.save_as_gif:
movie_file_path=self.gif_file_path
commands +=[
movie_file_path,
]
if not self.includes_sound:
commands.insert(-1, '-an')

View File

@@ -10,7 +10,7 @@ def add_extension_if_not_present(file_name, extension):
return file_name
def guarantee_existance(path):
def guarantee_existence(path):
if not os.path.exists(path):
os.makedirs(path)
return os.path.abspath(path)

View File

@@ -3,14 +3,13 @@ import os
from PIL import Image
from manimlib.constants import RASTER_IMAGE_DIR
from manimlib.utils.file_ops import seek_full_path_from_defaults
def get_full_raster_image_path(image_file_name):
return seek_full_path_from_defaults(
image_file_name,
default_dir=RASTER_IMAGE_DIR,
default_dir=os.path.join("assets", "raster_images"),
extensions=[".jpg", ".png", ".gif"]
)

View File

@@ -1,5 +1,4 @@
import os
from manimlib.constants import SOUND_DIR
from manimlib.utils.file_ops import seek_full_path_from_defaults
@@ -35,6 +34,6 @@ def play_finish_sound():
def get_full_sound_file_path(sound_file_name):
return seek_full_path_from_defaults(
sound_file_name,
default_dir=SOUND_DIR,
default_dir=os.path.join("assets", "sounds"),
extensions=[".wav", ".mp3"]
)

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

@@ -1,9 +1,9 @@
import os
import hashlib
from manimlib.constants import TEX_DIR
from manimlib.constants import TEX_TEXT_TO_REPLACE
from manimlib.constants import TEX_USE_CTEX
import manimlib.constants as consts
def tex_hash(expression, template_tex_file_body):
@@ -22,7 +22,7 @@ def tex_to_svg_file(expression, template_tex_file_body):
def generate_tex_file(expression, template_tex_file_body):
result = os.path.join(
TEX_DIR,
consts.TEX_DIR,
tex_hash(expression, template_tex_file_body)
) + ".tex"
if not os.path.exists(result):
@@ -44,8 +44,8 @@ def tex_to_dvi(tex_file):
"latex",
"-interaction=batchmode",
"-halt-on-error",
"-output-directory=" + TEX_DIR,
tex_file,
"-output-directory=\"{}\"".format(consts.TEX_DIR),
"\"{}\"".format(tex_file),
">",
os.devnull
] if not TEX_USE_CTEX else [
@@ -53,8 +53,8 @@ def tex_to_dvi(tex_file):
"-no-pdf",
"-interaction=batchmode",
"-halt-on-error",
"-output-directory=" + TEX_DIR,
tex_file,
"-output-directory=\"{}\"".format(consts.TEX_DIR),
"\"{}\"".format(tex_file),
">",
os.devnull
]
@@ -79,12 +79,12 @@ def dvi_to_svg(dvi_file, regen_if_exists=False):
if not os.path.exists(result):
commands = [
"dvisvgm",
dvi_file,
"\"{}\"".format(dvi_file),
"-n",
"-v",
"0",
"-o",
result,
"\"{}\"".format(result),
">",
os.devnull
]

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,6 @@ 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; sys_platform == 'linux'
pycairo>=1.18.0; sys_platform == 'win32'
pydub==0.23.0

View File

@@ -8,7 +8,7 @@ description-content-type = text/markdown; charset=UTF-8
home-page = https://github.com/3b1b/manim
project_urls =
Bug Tracker = https://github.com/3b1b/manim/issues
Documentation = https://github.com/3b1b/manim
Documentation = https://eulertour.com/learn/manim
Source Code = https://github.com/3b1b/manim
license = MIT
@@ -17,4 +17,4 @@ packages = manimlib
[entry_points]
console_scripts =
manim = manimlib:main
manim = manimlib:main

View File

@@ -4,8 +4,8 @@ import os
import sys
import importlib
import manimlib.constants as consts
from manimlib.constants import PRODUCTION_QUALITY_CAMERA_CONFIG
from manimlib.constants import VIDEO_DIR
from manimlib.config import get_module
from manimlib.extract_scene import is_child_scene
@@ -43,7 +43,7 @@ def stage_scenes(module_name):
# }
# TODO, fix this
animation_dir = os.path.join(
VIDEO_DIR, "ode", "part2", "1440p60"
consts.VIDEO_DIR, "ode", "part3", "1440p60"
)
#
files = os.listdir(animation_dir)

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.