Merge branch 'master' of github.com:3b1b/manim into quick-eigen-video

This commit is contained in:
Grant Sanderson
2021-06-14 09:54:20 -07:00
22 changed files with 160 additions and 52 deletions

4
.gitignore vendored
View File

@ -15,6 +15,8 @@ __pycache__/
build/
develop-eggs/
dist/
manimlib.egg-info/
downloads/
eggs/
.eggs/
@ -147,4 +149,4 @@ dmypy.json
# For manim
/videos
/custom_config.yml
/custom_config.yml

View File

@ -10,7 +10,7 @@
[![Manim Discord](https://img.shields.io/discord/581738731934056449.svg?label=discord&logo=discord)](https://discord.gg/mMRrZQW)
[![docs](https://github.com/3b1b/manim/workflows/docs/badge.svg)](https://3b1b.github.io/manim/)
Manim is an engine for precise programatic animations, designed for creating explanatory math videos.
Manim is an engine for precise programmatic animations, designed for creating explanatory math videos.
Note, there are two versions of manim. This repository began as a personal project by the author of [3Blue1Brown](https://www.3blue1brown.com/) for the purpose of animating those videos, with video-specific code available [here](https://github.com/3b1b/videos). In 2020 a group of developers forked it into what is now the [community edition](https://github.com/ManimCommunity/manim/), with a goal of being more stable, better tested, quicker to respond to community contributions, and all around friendlier to get started with. You can engage with that community by joining the discord.
@ -20,7 +20,8 @@ Since the fork, this version has evolved to work on top of OpenGL, and allows re
Manim runs on Python 3.6 or higher (Python 3.8 is recommended).
System requirements are [FFmpeg](https://ffmpeg.org/), [OpenGL](https://www.opengl.org/) and [LaTeX](https://www.latex-project.org) (optional, if you want to use LaTeX).
For Linux, [Pango](https://pango.gnome.org) along with it's developerment headers are required. See instruction [here](https://github.com/ManimCommunity/ManimPango#building).
For Linux, [Pango](https://pango.gnome.org) along with its development headers are required. See instruction [here](https://github.com/ManimCommunity/ManimPango#building).
### Directly

View File

@ -1,4 +1,44 @@
Changelog
=========
No changes now.
Unreleased
----------
Fixed bugs
^^^^^^^^^^
- Fixed the bug of :func:`~manimlib.utils.iterables.resize_with_interpolation` in the case of ``length=0``
- Fixed the bug of ``__init__`` in :class:`~manimlib.mobject.geometry.Elbow`
- If chosen monitor is not available, choose one that does exist
- Make sure mobject data gets unlocked after animations
- Fixed a bug for off-center vector fields
- Had ``Mobject.match_points`` return self
- Fixed chaining animation in example scenes
- Fixed the default color of tip
- Fixed a typo in ``ShowPassingFlashWithThinningStrokeWidth``
New Features
^^^^^^^^^^^^
- Added :class:`~manimlib.animation.indication.VShowPassingFlash`
- Added ``COLORMAP_3B1B``
- Added some methods to coordinate system to access all axes ranges
- :meth:`~manimlib.mobject.coordinate_systems.CoordinateSystem.get_origin`
- :meth:`~manimlib.mobject.coordinate_systems.CoordinateSystem.get_all_ranges`
- Added :meth:`~manimlib.mobject.mobject.Mobject.set_color_by_rgba_func`
- Updated :class:`~manimlib.mobject.vector_field.VectorField` and :class:`~manimlib.mobject.vector_field.StreamLines`
- Allow ``3b1b_colormap`` as an option for :func:`~manimlib.utils.color.get_colormap_list`
- Return ``stroke_width`` as 1d array
- Added :meth:`~manimlib.mobject.svg.text_mobject.Text.get_parts_by_text`
- Use Text not TexText for Brace
- Update to Cross to make it default to variable stroke width
- Added :class:`~manimlib.animation.indication.FlashAround` and :class:`~manimlib.animation.indication.FlashUnder`
- Allowed configuration in ``Brace.get_text``
- Added :meth:`~manimlib.camera.camera.CameraFrame.reorient` for quicker changes to frame angle
- Added ``units`` to :meth:`~manimlib.camera.camera.CameraFrame.set_euler_angles`
- Allowed any ``VMobject`` to be passed into ``TransformMatchingTex``
- Removed double brace convention in ``Tex`` and ``TexText``
- Added support for debugger launch
- Added CLI flag ``--config_file`` to load configuration file manually
- Added ``tip_style`` to ``tip_config``

View File

@ -56,7 +56,7 @@ custom_config
- ``raster_images``
The directory for storing raster images to be used in the code (including
``.jpg``, ``.png`` and ``.gif``), which will be read by ``ImageMobject``.
``.jpg``, ``.jpeg``, ``.png`` and ``.gif``), which will be read by ``ImageMobject``.
- ``vector_images``
The directory for storing vector images to be used in the code (including
@ -108,6 +108,11 @@ The relative position of the playback window on the display (two characters,
the first character means upper(U) / middle(O) / lower(D), the second character
means left(L) / middle(O) / right(R)).
``window_monitor``
------------------
The number of the monitor you want the preview window to pop up on. (default is 0)
``break_into_partial_movies``
-----------------------------

View File

@ -58,6 +58,7 @@ flag abbr function
``--color COLOR`` ``-c`` Background color
``--leave_progress_bars`` Leave progress bars displayed in terminal
``--video_dir VIDEO_DIR`` directory to write video
``--config_file CONFIG_FILE`` Path to the custom configuration file
========================================================== ====== =================================================================================================================================================================================================
custom_config
@ -85,5 +86,11 @@ following the directory structure:
└── custom_config.yml
When you enter the ``project/`` folder and run ``manimgl code.py <Scene>``,
it will overwrite ``manim/custom_config.yml`` with ``custom_config.yml``
in the ``project`` folder.
it will overwrite ``manim/default_config.yml`` with ``custom_config.yml``
in the ``project`` folder.
Alternatively, you can use ``--config_file`` flag in CLI to specify configuration file manually.
.. code-block:: sh
manimgl project/code.py --config_file /path/to/custom_config.yml

View File

@ -8,12 +8,12 @@ the simplest and one by one.
InteractiveDevlopment
---------------------
.. manim-example:: InteractiveDevlopment
:media: ../_static/example_scenes/InteractiveDevlopment.mp4
.. manim-example:: InteractiveDevelopment
:media: ../_static/example_scenes/InteractiveDevelopment.mp4
from manimlib import *
class InteractiveDevlopment(Scene):
class InteractiveDevelopment(Scene):
def construct(self):
circle = Circle()
circle.set_fill(BLUE, opacity=0.5)
@ -128,6 +128,8 @@ TextExample
class TextExample(Scene):
def construct(self):
# To run this scene properly, you should have "Consolas" font in your computer
# for full usage, you can see https://github.com/3b1b/manim/pull/680
text = Text("Here is a text", font="Consolas", font_size=90)
difference = Text(
"""
@ -135,6 +137,7 @@ TextExample
you can change the font more easily, but can't use the LaTeX grammar
""",
font="Arial", font_size=24,
# t2c is a dict that you can choose color for different text
t2c={"Text": BLUE, "TexText": BLUE, "LaTeX": ORANGE}
)
VGroup(text, difference).arrange(DOWN, buff=1)
@ -148,6 +151,7 @@ TextExample
t2f={"font": "Consolas", "words": "Consolas"},
t2c={"font": BLUE, "words": GREEN}
)
fonts.set_width(FRAME_WIDTH - 1)
slant = Text(
"And the same as slant and weight",
font="Consolas",
@ -180,20 +184,24 @@ TexTransformExample
def construct(self):
to_isolate = ["B", "C", "=", "(", ")"]
lines = VGroup(
# Surrounding substrings with double braces
# will ensure that those parts are separated
# out in the Tex. For example, here the
# Tex will have 5 submobjects, corresponding
# to the strings [A^2, +, B^2, =, C^2]
Tex("{{A^2}} + {{B^2}} = {{C^2}}"),
Tex("{{A^2}} = {{C^2}} - {{B^2}}"),
# Passing in muliple arguments to Tex will result
# in the same expression as if those arguments had
# been joined together, except that the submobject
# heirarchy of the resulting mobject ensure that the
# Tex mobject has a subject corresponding to
# each of these strings. For example, the Tex mobject
# below will have 5 subjects, corresponding to the
# expressions [A^2, +, B^2, =, C^2]
Tex("A^2", "+", "B^2", "=", "C^2"),
# Likewise here
Tex("A^2", "=", "C^2", "-", "B^2"),
# Alternatively, you can pass in the keyword argument
# "isolate" with a list of strings that should be out as
# their own submobject. So both lines below are equivalent
# to what you'd get by wrapping every instance of "B", "C"
# "=", "(" and ")" with double braces
Tex("{{A^2}} = (C + B)(C - B)", isolate=to_isolate),
Tex("A = \\sqrt{(C + B)(C - B)}", isolate=to_isolate)
# their own submobject. So the line below is equivalent
# to the commented out line below it.
Tex("A^2 = (C + B)(C - B)", isolate=["A^2", *to_isolate]),
# Tex("A^2", "=", "(", "C", "+", "B", ")", "(", "C", "-", "B", ")"),
Tex("A = \\sqrt{(C + B)(C - B)}", isolate=["A", *to_isolate])
)
lines.arrange(DOWN, buff=LARGE_BUFF)
for line in lines:
@ -252,7 +260,7 @@ TexTransformExample
# new_line2 and the "\sqrt" from the final line. By passing in,
# transform_mismatches=True, it will transform this "^2" part into
# the "\sqrt" part.
new_line2 = Tex("{{A}}^2 = (C + B)(C - B)", isolate=to_isolate)
new_line2 = Tex("A^2 = (C + B)(C - B)", isolate=["A", *to_isolate])
new_line2.replace(lines[2])
new_line2.match_style(lines[2])
@ -343,7 +351,7 @@ UpdatersExample
)
self.wait()
self.play(
square.set_width(5, stretch=True),
square.animate.set_width(5, stretch=True),
run_time=3,
)
self.wait()
@ -387,7 +395,7 @@ CoordinateSystemExample
axes = Axes(
# x-axis ranges from -1 to 10, with a default step size of 1
x_range=(-1, 10),
# y-axis ranges from -2 to 10 with a step size of 0.5
# y-axis ranges from -2 to 2 with a step size of 0.5
y_range=(-2, 2, 0.5),
# The axes will be stretched so as to match the specified
# height and width
@ -450,8 +458,7 @@ CoordinateSystemExample
# system defined by them.
f_always(dot.move_to, lambda: axes.c2p(1, 1))
self.play(
axes.animate.scale(0.75),
axes.animate.to_corner(UL),
axes.animate.scale(0.75).to_corner(UL),
run_time=2,
)
self.wait()

View File

@ -221,7 +221,7 @@ For example: input the following lines (without comment lines) into it respectiv
.. code-block:: python
# Stretched 4 times in the vertical direction
play(circle.animate.stretch(4, dim=0}))
play(circle.animate.stretch(4, dim=0))
# Rotate the ellipse 90°
play(Rotate(circle, TAU / 4))
# Move 2 units to the right and shrink to 1/4 of the original

View File

@ -99,6 +99,7 @@ Below is the directory structure of manim:
├── config_ops.py # Process CONFIG
├── customization.py # Read from custom_config.yml
├── debug.py # Utilities for debugging in program
├── directories.py # Read directories from config file
├── family_ops.py # Process family members
├── file_ops.py # Process files and directories
├── images.py # Read image

View File

@ -342,7 +342,7 @@ class CoordinateSystemExample(Scene):
axes = Axes(
# x-axis ranges from -1 to 10, with a default step size of 1
x_range=(-1, 10),
# y-axis ranges from -2 to 10 with a step size of 0.5
# y-axis ranges from -2 to 2 with a step size of 0.5
y_range=(-2, 2, 0.5),
# The axes will be stretched so as to match the specified
# height and width

View File

@ -15,3 +15,6 @@ def main():
for scene in scenes:
scene.run()
if __name__ == '__main__':
main()

View File

@ -130,7 +130,11 @@ def parse_cli():
)
parser.add_argument(
"--video_dir",
help="directory to write video",
help="Directory to write video",
)
parser.add_argument(
"--config_file",
help="Path to the custom configuration file",
)
args = parser.parse_args()
return args
@ -155,17 +159,19 @@ def get_module(file_name):
spec.loader.exec_module(module)
return module
__config_file__ = "custom_config.yml"
def get_custom_config():
filename = "custom_config.yml"
global __config_file__
global_defaults_file = os.path.join(get_manim_dir(), "manimlib", "default_config.yml")
if os.path.exists(global_defaults_file):
with open(global_defaults_file, "r") as file:
config = yaml.safe_load(file)
if os.path.exists(filename):
with open(filename, "r") as file:
if os.path.exists(__config_file__):
with open(__config_file__, "r") as file:
local_defaults = yaml.safe_load(file)
if local_defaults:
config = merge_dicts_recursively(
@ -173,22 +179,41 @@ def get_custom_config():
local_defaults,
)
else:
with open(filename, "r") as file:
with open(__config_file__, "r") as file:
config = yaml.safe_load(file)
return config
def get_configuration(args):
local_config_file = "custom_config.yml"
global __config_file__
# ensure __config_file__ always exists
if args.config_file is not None:
if not os.path.exists(args.config_file):
print(f"Can't find {args.config_file}.")
if sys.platform == 'win32':
print(f"Copying default configuration file to {args.config_file}...")
os.system(f"copy default_config.yml {args.config_file}")
elif sys.platform in ["linux2", "darwin"]:
print(f"Copying default configuration file to {args.config_file}...")
os.system(f"cp default_config.yml {args.config_file}")
else:
print("Please create the configuration file manually.")
print("Read configuration from default_config.yml.")
else:
__config_file__ = args.config_file
global_defaults_file = os.path.join(get_manim_dir(), "manimlib", "default_config.yml")
if not (os.path.exists(global_defaults_file) or os.path.exists(local_config_file)):
if not (os.path.exists(global_defaults_file) or os.path.exists(__config_file__)):
print("There is no configuration file detected. Initial configuration:\n")
init_customization()
elif not os.path.exists(local_config_file):
print(f"""Warning: Using the default configuration file, which you can modify in {global_defaults_file}
If you want to create a local configuration file, you can create a file named {local_config_file}, or run manimgl --config
""")
elif not os.path.exists(__config_file__):
print(f"Warning: Using the default configuration file, which you can modify in {global_defaults_file}")
print(f"If you want to create a local configuration file, you can create a file named {__config_file__}, or run manimgl --config")
custom_config = get_custom_config()
write_file = any([args.write_file, args.open, args.finder])

View File

@ -49,6 +49,7 @@ class TipableVMobject(VMobject):
"tip_config": {
"fill_opacity": 1,
"stroke_width": 0,
"tip_style": 0, # triangle=0, inner_smooth=1, dot=2
},
"normal_vector": OUT,
}
@ -63,6 +64,7 @@ class TipableVMobject(VMobject):
tip = self.create_tip(at_start, **kwargs)
self.reset_endpoints_based_on_tip(tip, at_start)
self.asign_tip_attr(tip, at_start)
tip.set_color(self.get_stroke_color())
self.add(tip)
return self
@ -786,12 +788,20 @@ class ArrowTip(Triangle):
"width": DEFAULT_ARROW_TIP_WIDTH,
"length": DEFAULT_ARROW_TIP_LENGTH,
"angle": 0,
"tip_style": 0, # triangle=0, inner_smooth=1, dot=2
}
def __init__(self, **kwargs):
Triangle.__init__(self, start_angle=0, **kwargs)
self.set_height(self.width)
self.set_width(self.length, stretch=True)
if self.tip_style == 1:
self.set_height(self.length * 0.9, stretch=True)
self.data["points"][4] += np.array([0.6 * self.length, 0, 0])
elif self.tip_style == 2:
h = self.length / 2
self.clear_points()
self.data["points"] = Dot().set_width(h).get_points()
self.rotate(self.angle)
def get_base(self):

View File

@ -180,7 +180,10 @@ class Tex(SingleStringTex):
pattern = "|".join(patterns)
pieces = []
for s in tex_strings:
pieces.extend(re.split(pattern, s))
if pattern:
pieces.extend(re.split(pattern, s))
else:
pieces.append(s)
return list(filter(lambda s: s, pieces))
def break_up_by_substrings(self):

View File

@ -283,7 +283,7 @@ class ShowPassingFlashWithThinningStrokeWidth(AnimationGroup):
max_stroke_width = vmobject.get_stroke_width()
max_time_width = kwargs.pop("time_width", self.time_width)
AnimationGroup.__init__(self, *[
ShowPassingFlash(
VShowPassingFlash(
vmobject.deepcopy().set_stroke(width=stroke_width),
time_width=time_width,
**kwargs

View File

@ -17,16 +17,16 @@ vec4 add_light(vec4 color,
float shadow){
if(gloss == 0.0 && shadow == 0.0) return color;
// TODO, do we actually want this? It effectively treats surfaces as two-sided
if(unit_normal.z < 0){
unit_normal *= -1;
}
// TODO, read this in as a uniform?
float camera_distance = 6;
float camera_distance = focal_distance;
// Assume everything has already been rotated such that camera is in the z-direction
vec3 to_camera = vec3(0, 0, camera_distance) - point;
vec3 to_light = light_coords - point;
// TODO, do we actually want this? It effectively treats surfaces as two-sided
if(dot(to_camera,unit_normal) < 0){
unit_normal *= -1;
}
vec3 light_reflection = -to_light + 2 * unit_normal * dot(to_light, unit_normal);
float dot_prod = dot(normalize(light_reflection), normalize(to_camera));
float shine = gloss * exp(-3 * pow(1 - dot_prod, 2));

View File

@ -3,6 +3,7 @@
uniform vec3 light_source_position;
uniform float gloss;
uniform float shadow;
uniform float focal_distance;
in vec3 xyz_coords;
in vec3 v_normal;

View File

@ -6,6 +6,7 @@ uniform float num_textures;
uniform vec3 light_source_position;
uniform float gloss;
uniform float shadow;
uniform float focal_distance;
in vec3 xyz_coords;
in vec3 v_normal;

View File

@ -4,6 +4,7 @@ uniform vec3 light_source_position;
uniform float gloss;
uniform float shadow;
uniform float anti_alias_width;
uniform float focal_distance;
in vec4 color;
in float radius;

View File

@ -23,6 +23,7 @@ def init_customization():
"background_color": "",
},
"window_position": "UR",
"window_position": 0,
"break_into_partial_movies": False,
"camera_qualities": {
"low": {

View File

@ -15,7 +15,7 @@ class Window(PygletWindow):
cursor = True
def __init__(self, scene, size=(1280, 720), **kwargs):
super().__init__()
super().__init__(size=size)
digest_config(self, kwargs)
self.scene = scene

View File

@ -16,4 +16,4 @@ pyreadline; sys_platform == 'win32'
validators
ipython
PyOpenGL
manimpango>=0.2.0,<0.3.0'
manimpango>=0.2.0,<0.4.0