mirror of
https://github.com/3b1b/manim.git
synced 2026-01-13 08:27:54 -05:00
Compare commits
136 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dfdceb1908 | ||
|
|
41792fdb5f | ||
|
|
3ec231f0ca | ||
|
|
7d596d0840 | ||
|
|
2e0e5cfb5e | ||
|
|
96e34b969c | ||
|
|
2f6be9a389 | ||
|
|
2294cdea4f | ||
|
|
8ce13875a3 | ||
|
|
7ac990119a | ||
|
|
43b643db6c | ||
|
|
9da74cb657 | ||
|
|
2680a9c373 | ||
|
|
f908b68bed | ||
|
|
71f79530be | ||
|
|
e72390bfc4 | ||
|
|
dc83cac5b4 | ||
|
|
5a86ba08f4 | ||
|
|
e421d3689a | ||
|
|
dfeb11a2ab | ||
|
|
66f9ff29e4 | ||
|
|
62c2772982 | ||
|
|
9e7619844f | ||
|
|
6c2e7f4c2c | ||
|
|
08eee147b3 | ||
|
|
43b4508b2c | ||
|
|
dade021eaa | ||
|
|
0041e3fd45 | ||
|
|
d031e5735a | ||
|
|
e64a25bd40 | ||
|
|
d7054e61f0 | ||
|
|
6c1fb7ddca | ||
|
|
7fab15abd4 | ||
|
|
6571b0d88e | ||
|
|
ee83d3bcec | ||
|
|
542ea68824 | ||
|
|
2a930ff702 | ||
|
|
6f7e123b1a | ||
|
|
4196bb627e | ||
|
|
2cbe19af7c | ||
|
|
2ccf83c0aa | ||
|
|
cfbbff1bdf | ||
|
|
f10baa3fcd | ||
|
|
34d1d27c56 | ||
|
|
cc08c090d1 | ||
|
|
caa4efdfa5 | ||
|
|
503b2bc896 | ||
|
|
542ddb9afd | ||
|
|
6214ea7a01 | ||
|
|
3b42f1f709 | ||
|
|
ab75015099 | ||
|
|
e2eac0bd96 | ||
|
|
65c23d9e9f | ||
|
|
1322152391 | ||
|
|
b71e63a540 | ||
|
|
41539387a5 | ||
|
|
22b4c3578b | ||
|
|
5535997356 | ||
|
|
3e2a312f51 | ||
|
|
aad68a5503 | ||
|
|
7f54ac9b3a | ||
|
|
fb9cf93470 | ||
|
|
d1ada7a8aa | ||
|
|
9ffe4a6839 | ||
|
|
f6dc1cb50f | ||
|
|
a024916e66 | ||
|
|
73a1d37785 | ||
|
|
62206bc0e7 | ||
|
|
57b96fdd07 | ||
|
|
de0d7c907d | ||
|
|
8449bc3418 | ||
|
|
f81c275631 | ||
|
|
949bfaa0b4 | ||
|
|
e1fb9f480b | ||
|
|
caa4577cd1 | ||
|
|
43b82f2c53 | ||
|
|
c203f8e8c0 | ||
|
|
75996a618c | ||
|
|
ed24541de6 | ||
|
|
22c8aa0eb8 | ||
|
|
0590c153ef | ||
|
|
c42ae8a55b | ||
|
|
3632d97140 | ||
|
|
e9fc2aa46c | ||
|
|
20590e6823 | ||
|
|
8bb0b4f010 | ||
|
|
ab2318ff9d | ||
|
|
5dcb113996 | ||
|
|
68fb61a5b2 | ||
|
|
2f37e3abf2 | ||
|
|
aecf5b0b18 | ||
|
|
224d389522 | ||
|
|
52054571ab | ||
|
|
ddd7ce2f12 | ||
|
|
0b35d8e2ec | ||
|
|
47a4a1f2df | ||
|
|
486b28e4d2 | ||
|
|
2ce766b3bf | ||
|
|
2deeeab509 | ||
|
|
e93ef301ce | ||
|
|
c7b5aa6e05 | ||
|
|
0edb4edfd0 | ||
|
|
78448b4388 | ||
|
|
969bcf4154 | ||
|
|
f3f0e3ba03 | ||
|
|
aa2734477a | ||
|
|
d0eb2a0ce8 | ||
|
|
a4c8302c55 | ||
|
|
5d4897bd50 | ||
|
|
49d8276033 | ||
|
|
b7fcc68b55 | ||
|
|
4096fc28b8 | ||
|
|
e6eb4dd94f | ||
|
|
29424eb6b3 | ||
|
|
d1e3e5ed20 | ||
|
|
828c3dcd7a | ||
|
|
17558a4bd5 | ||
|
|
304822fb8c | ||
|
|
9fa350d906 | ||
|
|
c62ba223b6 | ||
|
|
4447bbd016 | ||
|
|
98696a29f8 | ||
|
|
84514a1f6f | ||
|
|
c21ea85ec9 | ||
|
|
cec7872f48 | ||
|
|
ce866e8222 | ||
|
|
91b3abae4a | ||
|
|
6e6dd260da | ||
|
|
2d3493c3d5 | ||
|
|
c4449fdfb8 | ||
|
|
bf8f517b49 | ||
|
|
b9822db5bf | ||
|
|
2ebcec4bbe | ||
|
|
68961251a5 | ||
|
|
0f5cc33002 | ||
|
|
e59178dae7 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -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
|
||||
@@ -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
|
||||
|
||||
15
Dockerfile
15
Dockerfile
@@ -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"]
|
||||
|
||||
43
README.md
43
README.md
@@ -1,8 +1,10 @@
|
||||
<img src="logo/cropped.png"/>
|
||||
|
||||
[](https://manim.readthedocs.io/en/latest/?badge=latest)
|
||||
[](https://travis-ci.org/3b1b/manim)
|
||||
[](https://www.eulertour.com/learn/manim/)
|
||||
[](http://choosealicense.com/licenses/mit/)
|
||||
[](https://www.reddit.com/r/manim/)
|
||||
[](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.
|
||||
|
||||
|
||||
70
active_projects/diffyq/all_part3_scenes.py
Normal file
70
active_projects/diffyq/all_part3_scenes.py
Normal 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,
|
||||
]
|
||||
18
active_projects/diffyq/all_part4_scenes.py
Normal file
18
active_projects/diffyq/all_part4_scenes.py
Normal 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,
|
||||
]
|
||||
40
active_projects/diffyq/name_animations.py
Normal file
40
active_projects/diffyq/name_animations.py
Normal 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
|
||||
@@ -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,
|
||||
@@ -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,
|
||||
),
|
||||
)
|
||||
|
||||
303
active_projects/diffyq/part3/discrete_case.py
Normal file
303
active_projects/diffyq/part3/discrete_case.py
Normal 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,
|
||||
}
|
||||
}
|
||||
175
active_projects/diffyq/part3/pi_creature_scenes.py
Normal file
175
active_projects/diffyq/part3/pi_creature_scenes.py
Normal 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)
|
||||
1230
active_projects/diffyq/part3/staging.py
Normal file
1230
active_projects/diffyq/part3/staging.py
Normal file
File diff suppressed because it is too large
Load Diff
2830
active_projects/diffyq/part3/temperature_graphs.py
Normal file
2830
active_projects/diffyq/part3/temperature_graphs.py
Normal file
File diff suppressed because it is too large
Load Diff
919
active_projects/diffyq/part3/wordy_scenes.py
Normal file
919
active_projects/diffyq/part3/wordy_scenes.py
Normal 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)
|
||||
242
active_projects/diffyq/part4/fourier_series_scenes.py
Normal file
242
active_projects/diffyq/part4/fourier_series_scenes.py
Normal 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
|
||||
22
active_projects/diffyq/part4/pi_creature_scenes.py
Normal file
22
active_projects/diffyq/part4/pi_creature_scenes.py
Normal 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)
|
||||
345
active_projects/diffyq/part4/staging.py
Normal file
345
active_projects/diffyq/part4/staging.py
Normal 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
|
||||
@@ -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
19
docs/Makefile
Normal 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
35
docs/make.bat
Normal 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
11
docs/source/about.rst
Normal 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
210
docs/source/animation.rst
Normal 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`.
|
||||
BIN
docs/source/assets/AnimationFadeIn.mp4
Normal file
BIN
docs/source/assets/AnimationFadeIn.mp4
Normal file
Binary file not shown.
BIN
docs/source/assets/AnimationFadeInFrom.mp4
Normal file
BIN
docs/source/assets/AnimationFadeInFrom.mp4
Normal file
Binary file not shown.
BIN
docs/source/assets/AnimationFadeInFromDiagonal.mp4
Normal file
BIN
docs/source/assets/AnimationFadeInFromDiagonal.mp4
Normal file
Binary file not shown.
BIN
docs/source/assets/AnimationFadeInFromLarge.mp4
Normal file
BIN
docs/source/assets/AnimationFadeInFromLarge.mp4
Normal file
Binary file not shown.
BIN
docs/source/assets/AnimationFadeInFromPoint.mp4
Normal file
BIN
docs/source/assets/AnimationFadeInFromPoint.mp4
Normal file
Binary file not shown.
BIN
docs/source/assets/AnimationFadeInFromSmall.mp4
Normal file
BIN
docs/source/assets/AnimationFadeInFromSmall.mp4
Normal file
Binary file not shown.
BIN
docs/source/assets/AnimationFadeOut.mp4
Normal file
BIN
docs/source/assets/AnimationFadeOut.mp4
Normal file
Binary file not shown.
BIN
docs/source/assets/AnimationFadeOutAndShift.mp4
Normal file
BIN
docs/source/assets/AnimationFadeOutAndShift.mp4
Normal file
Binary file not shown.
BIN
docs/source/assets/AnimationGrowFromCenter.mp4
Normal file
BIN
docs/source/assets/AnimationGrowFromCenter.mp4
Normal file
Binary file not shown.
BIN
docs/source/assets/AnimationGrowFromEdge.mp4
Normal file
BIN
docs/source/assets/AnimationGrowFromEdge.mp4
Normal file
Binary file not shown.
BIN
docs/source/assets/SquareToCircle.mp4
Normal file
BIN
docs/source/assets/SquareToCircle.mp4
Normal file
Binary file not shown.
BIN
docs/source/assets/coordinate/CoorAlias.mp4
Normal file
BIN
docs/source/assets/coordinate/CoorAlias.mp4
Normal file
Binary file not shown.
BIN
docs/source/assets/coordinate/CoorArithmetic.mp4
Normal file
BIN
docs/source/assets/coordinate/CoorArithmetic.mp4
Normal file
Binary file not shown.
BIN
docs/source/assets/coordinate/CoorPolygon.png
Normal file
BIN
docs/source/assets/coordinate/CoorPolygon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
BIN
docs/source/assets/coordinate/DotMap.mp4
Normal file
BIN
docs/source/assets/coordinate/DotMap.mp4
Normal file
Binary file not shown.
53
docs/source/conf.py
Normal file
53
docs/source/conf.py
Normal 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
94
docs/source/constants.rst
Normal 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
178
docs/source/coordinate.rst
Normal 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>
|
||||
4
docs/source/getting_started/animating_mobjects.rst
Normal file
4
docs/source/getting_started/animating_mobjects.rst
Normal file
@@ -0,0 +1,4 @@
|
||||
Animating Mobjects
|
||||
==================
|
||||
|
||||
Learn about animations.
|
||||
18
docs/source/getting_started/index.rst
Normal file
18
docs/source/getting_started/index.rst
Normal 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
|
||||
131
docs/source/getting_started/learning_by_example.rst
Normal file
131
docs/source/getting_started/learning_by_example.rst
Normal 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`.
|
||||
4
docs/source/getting_started/making_a_scene.rst
Normal file
4
docs/source/getting_started/making_a_scene.rst
Normal file
@@ -0,0 +1,4 @@
|
||||
Making a Scene
|
||||
==============
|
||||
|
||||
Talk about Scenes and organization, bring it all together.
|
||||
13
docs/source/getting_started/mathematical_objects.rst
Normal file
13
docs/source/getting_started/mathematical_objects.rst
Normal 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
30
docs/source/index.rst
Normal 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`
|
||||
12
docs/source/installation/index.rst
Normal file
12
docs/source/installation/index.rst
Normal file
@@ -0,0 +1,12 @@
|
||||
Installation
|
||||
============
|
||||
|
||||
Instructions on installing Manim
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: Contents
|
||||
|
||||
linux
|
||||
mac
|
||||
windows
|
||||
41
docs/source/installation/linux.rst
Normal file
41
docs/source/installation/linux.rst
Normal 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
|
||||
12
docs/source/installation/mac.rst
Normal file
12
docs/source/installation/mac.rst
Normal 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)
|
||||
60
docs/source/installation/windows.rst
Normal file
60
docs/source/installation/windows.rst
Normal 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 ``pycairo‑1.18.0‑cp37‑cp37m‑win32.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``
|
||||
@@ -11,6 +11,8 @@ from manimlib.imports import *
|
||||
# Use the -p to have the animation (or image, if -s was
|
||||
# used) pop up once done.
|
||||
# Use -n <number> to skip ahead to the n'th animation of a scene.
|
||||
# Use -r <number> to specify a resolution (for example, -r 1080
|
||||
# for a 1920x1080 video)
|
||||
|
||||
|
||||
class OpeningManimExample(Scene):
|
||||
@@ -111,7 +113,7 @@ class WriteStuff(Scene):
|
||||
self.wait()
|
||||
|
||||
|
||||
class UdatersExample(Scene):
|
||||
class UpdatersExample(Scene):
|
||||
def construct(self):
|
||||
decimal = DecimalNumber(
|
||||
0,
|
||||
|
||||
BIN
manim_docker_diagram.png
Normal file
BIN
manim_docker_diagram.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
@@ -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(
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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": [],
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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')
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"]
|
||||
)
|
||||
|
||||
|
||||
@@ -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"]
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
]
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
10
travis/build_docs.sh
Executable 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
BIN
travis/crypt.enc
Normal file
Binary file not shown.
Reference in New Issue
Block a user